diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 2e9a0cc057e0..d6da6884a22e 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -2306,6 +2306,83 @@ config DMI endmenu # "Boot options" +menu "Hypervisor" +config UH + bool "Enable micro hypervisor feature" + depends on !SEC_FACTORY + depends on !ARCH_QTI_VM + default n + help + It enables a micro hypervisor. + It's samsung's hypervisor. + RKP and etc can be loaded on it. + please check a memory map for it. + +config RKP + bool "Enable RKP(Realtime Kernel Protection) feature" + depends on UH + default n + help + This solution provides the kernel protection + using a security monitor located within an isolated execution environment. + This isolated execution environment is either the Secure World of ARM TrustZone + or a thin hypervisor that is protected by the hardware virtualization extensions. + +config KDP + bool "Enable KDP(Kernel Data Protection) feature" + depends on !SEC_VTS_TEST + depends on UH + default n + help + Prevents unauthorized cred modification, + namespace modification, mapping for page table. + +config KDP_CRED + bool "Enable KDP(Kernel Data Protection) cred feature" + depends on !SEC_VTS_TEST + depends on UH + depends on KDP + default n + help + Prevents unauthorized cred modification, + namespace modification, mapping for page table. + +config KDP_NS + bool "Enable KDP(Kernel Data Protection) namespace feature" + depends on !SEC_VTS_TEST + depends on UH + depends on KDP + default n + help + Prevents unauthorized cred modification, + namespace modification, mapping for page table. + +config RKP_TEST + bool "Enable RKP test" + depends on RKP + default n + help + It is a test feature for RKP debugging. + This configuration checks following lists. + USER_PXN + USER_PGTABLE_RO + KERNEL_PGTABLE_RO + KERNEL_L3PGT_RO + KERNEL_RANGE_RWX + +config KDP_TEST + bool "Enable KDP test" + depends on KDP_CRED && KDP_NS + default n + help + It is a test configuration for KDP debugging. + This configuration checks following lists. + TASK_CRED_RO + TASK_SECURITY_CONTEXT_RO + CRED_MATCH_BACKPOINTERS + SEC_CONTEXT_BACKPOINTER +endmenu + menu "Power management options" source "kernel/power/Kconfig" diff --git a/arch/arm64/Kconfig.debug b/arch/arm64/Kconfig.debug index 265c4461031f..0a6c6d1c4eb6 100644 --- a/arch/arm64/Kconfig.debug +++ b/arch/arm64/Kconfig.debug @@ -20,4 +20,11 @@ config ARM64_RELOC_TEST depends on m tristate "Relocation testing module" +comment "PowerManagement Feature" +menuconfig SEC_PM + bool "Samsung PowerManagement Feature" + default n + help + Samsung PowerManagement Feature. + source "drivers/hwtracing/coresight/Kconfig" diff --git a/arch/arm64/boot/dts/vendor b/arch/arm64/boot/dts/vendor new file mode 120000 index 000000000000..f8795dd2498e --- /dev/null +++ b/arch/arm64/boot/dts/vendor @@ -0,0 +1 @@ +../../../../../sm8650-devicetrees/ \ No newline at end of file diff --git a/arch/arm64/configs/lego.config b/arch/arm64/configs/lego.config new file mode 100644 index 000000000000..cbf4c837a411 --- /dev/null +++ b/arch/arm64/configs/lego.config @@ -0,0 +1,178 @@ + +CONFIG_SEC_PANEL_NOTIFIER_V2=m +CONFIG_DRV_SAMSUNG_PMIC=m +CONFIG_CIRRUS_FIRMWARE_CL_DSP=m +CONFIG_USB_TYPEC_MANAGER_NOTIFIER=m +CONFIG_IF_CB_MANAGER=m +CONFIG_SBU_SWITCH_CONTROL=y +CONFIG_CHARGER_MAX77705=m +CONFIG_SEC_INPUT_BOOSTER=m +CONFIG_SEC_INPUT_BOOSTER_MODE=y +CONFIG_SEC_INPUT_BOOSTER_QC=y +CONFIG_SEC_INPUT_BOOSTER_HANDLER=y +CONFIG_DEV_RIL_BRIDGE=m +CONFIG_PRESSURE_FACTORY=y +CONFIG_FLIP_COVER_DETECTOR_FACTORY=y +CONFIG_LIGHT_FACTORY=y +CONFIG_PROX_FACTORY=y +CONFIG_SUPPORT_BRIGHTNESS_NOTIFY_FOR_LIGHT_SENSOR=y +CONFIG_SUPPORT_DDI_COPR_FOR_LIGHT_SENSOR=y +CONFIG_SUPPORT_LIGHT_CALIBRATION=y +CONFIG_SUPPORT_PANEL_STATE_NOTIFY_FOR_LIGHT_SENSOR=y +CONFIG_SUPPORT_PROX_CALIBRATION=y +CONFIG_LPS22DF_FACTORY=y +CONFIG_LSM6DSV_FACTORY=y +CONFIG_CHARGER_MAX77775=m +CONFIG_SHIPMODE_BY_VBAT=y +CONFIG_MUIC_NOTIFIER=m +CONFIG_HICCUP_CHARGER=y +CONFIG_MUIC_AFC_RETRY=y +CONFIG_MUIC_HV=y +CONFIG_MUIC_SUPPORT_PDIC=y +CONFIG_MUIC_USE_MODULE_PARAM=y +CONFIG_USE_MUIC=y +CONFIG_ANDROID_SWITCH=m +# CONFIG_ANDROID_SWITCH_GPIO is not set +CONFIG_SEC_UWB_LOGGER=y +CONFIG_SEC_NFC_LOGGER=y +CONFIG_SDP=m +CONFIG_VBUS_NOTIFIER=m +CONFIG_INPUT_SEC_INPUT=m +CONFIG_INPUT_SEC_NOTIFIER=m +CONFIG_SEC_DEBUG_TSP_LOG=m +CONFIG_TOUCHSCREEN_DUMP_MODE=m +CONFIG_INPUT_SEC_SECURE_TOUCH=m +CONFIG_INPUT_SEC_TRUSTED_TOUCH=m +CONFIG_INPUT_TOUCHSCREEN_TCLMV2=m +# CONFIG_SEC_INPUT_MULTI_DEVICE is not set +CONFIG_SEC_INPUT_RAWDATA=m +CONFIG_SUPPORT_DROPDUMP=m +CONFIG_REGULATOR_S2DOS05=m +CONFIG_REGULATOR_S2DOS05_ELVSS_FD=y +CONFIG_TOUCHSCREEN_STM_SPI=m +CONFIG_FUELGAUGE_MAX77705=m +CONFIG_UI_SOC_PROLONGING=y +CONFIG_MFD_MAX77705=m +CONFIG_ABC_IFPMIC_EVENT=y +CONFIG_MAX77705_FW_SEPARATION_PID_BY_MODEL=y +CONFIG_VIBRATOR_VIB_INFO=m +CONFIG_SEC_STI=y +CONFIG_KPERFMON=y +CONFIG_KPERFMON_BUILD=m +CONFIG_SEC_VIBRATOR_INPUTFF=m +CONFIG_VIB_STORE_LE_PARAM=y +# CONFIG_SEC_VIB_FOLD_MODEL is not set +CONFIG_USB_NOTIFY_LAYER=m +CONFIG_USB_DEBUG_DETAILED_LOG=y +CONFIG_USB_EXTERNAL_NOTIFY=y +CONFIG_USB_HMT_SAMSUNG_INPUT=y +CONFIG_USB_HOST_NOTIFY=y +CONFIG_USB_HOST_SAMSUNG_FEATURE=y +CONFIG_USB_HW_PARAM=y +CONFIG_USB_INTERFACE_LPM_LIST=y +CONFIG_USB_NOTIFY_PROC_LOG=y +CONFIG_USB_USING_ADVANCED_USBLOG=y +CONFIG_USB_VENDOR_NOTIFY=y +CONFIG_USB_VENDOR_RECEIVER=m +CONFIG_SENSORS_FINGERPRINT=m +CONFIG_SEC_ABC_SPEC_TYPE1=m +CONFIG_SB_CORE=m +CONFIG_SB_PQUEUE=y +CONFIG_SB_NOTIFY=y +CONFIG_SB_SYSFS=y +CONFIG_SB_VOTE=y +CONFIG_PDIC_NOTIFIER=m +CONFIG_PDIC_USE_MODULE_PARAM=y +CONFIG_USB_NOTIFIER=m +CONFIG_SEC_ABC=m +CONFIG_SEC_ABC_HUB=m +CONFIG_SEC_ABC_COMMON=m +CONFIG_SEC_ABC_HUB_CORE=m +CONFIG_SEC_ABC_HUB_BOOTC=m +CONFIG_SEC_ABC_MOTTO=m +CONFIG_FUELGAUGE_MAX77775=m +CONFIG_I2C_GPIO=m +CONFIG_INPUT_CS40L26_I2C=m +CONFIG_SND_SOC_CS40L26=m +CONFIG_CS40L26_SAMSUNG_FEATURE=y +CONFIG_CS40L26_SAMSUNG_USE_DVL=y +CONFIG_CS40L26_SAMSUNG_USE_MAX_DATA_TX_SIZE=y +CONFIG_SEC_ABC_DETECT_CONN=m +CONFIG_QCOM_SEC_ABC_DETECT=m +CONFIG_INPUT_HALL_IC=m +CONFIG_HALL_NOTIFIER=m +CONFIG_HALL_LOGICAL=m +# CONFIG_HALL_DUMP_KEY_MODE is not set +CONFIG_SENSORS_FLICKER_SELF_TEST=m +CONFIG_CCIC_MAX77705=m +CONFIG_CCIC_MAX77705_DEBUG=y +CONFIG_GET_UNMASK_VBUS_HWPARAM=y +CONFIG_MAX77705_FW_PID03_SUPPORT=y +CONFIG_MAXIM_CCIC_ALTERNATE_MODE=y +CONFIG_CHARGER_PCA9481=m +CONFIG_MFD_MAX77775=m +CONFIG_MAX77775_ABC_IFPMIC_EVENT=y +CONFIG_REGULATOR_FIXED_VOLTAGE=y +CONFIG_SENSORS_STK6D2X=m +# CONFIG_FLICKER_PWM_CALIBRATION is not set +CONFIG_AFC=y +CONFIG_HV_MUIC_MAX77705_AFC=y +CONFIG_MUIC_MAX77705=y +CONFIG_MUIC_MAX77705_PDIC=y +CONFIG_REGULATOR_S2MPB03=m +CONFIG_SEC_PM_THERMISTOR=m +CONFIG_INPUT_WACOM_WEZ02=m +CONFIG_SENSORS_QFS4008=m +CONFIG_SENSORS_FINGERPRINT_MODULE=y +CONFIG_FINGERPRINT_SECURE=y +CONFIG_SENSORS_FINGERPRINT_QCOM=y +CONFIG_STAR_K250A_LEGO=m +CONFIG_SEC_SNVM_WAKELOCK_METHOD=0 +# CONFIG_STAR_MEMORY_LEAK is not set +CONFIG_WIRELESS_CHARGER_NU1668=m +CONFIG_WIRELESS_AUTH=y +CONFIG_WIRELESS_CHARGER_HIGH_VOLTAGE=y +CONFIG_WIRELESS_TX_MODE=y +CONFIG_WIRELESS_FIRMWARE_UPDATE=y +CONFIG_WIRELESS_IC_PARAM=y +CONFIG_TX_GEAR_PHM_VOUT_CTRL=y +CONFIG_WIRELESS_RX_PHM_CTRL=y +CONFIG_SAMSUNG_NFC=m +CONFIG_NFC_NXP_COMBINED=y +CONFIG_NFC_SN2XX=y +CONFIG_NFC_SN2XX_ESE_SUPPORT=y +CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE=y +# CONFIG_ESE_USE_TZ_API is not set +CONFIG_SEC_NFC_WAKELOCK_METHOD=0 +# CONFIG_CLK_ACPM_INIT is not set +CONFIG_REGULATOR_S2MPB02=m +CONFIG_SAMSUNG_UWB=m +CONFIG_UWB_SR200=y +CONFIG_CCIC_MAX77775=m +CONFIG_MAX77775_CCIC_ALTERNATE_MODE=y +CONFIG_MAX77775_GET_UNMASK_VBUS_HWPARAM=y +CONFIG_REGULATOR_S2DOS07=m +CONFIG_SEC_DISPLAYPORT=m +CONFIG_TOUCHSCREEN_SYNAPTICS_SPI=m +CONFIG_DIRECT_CHARGING=m +CONFIG_BATTERY_SAMSUNG=m +CONFIG_SEC_PD=m +CONFIG_BATTERY_GKI=y +CONFIG_BATTERY_AGE_FORECAST=y +CONFIG_BATTERY_CISD=y +CONFIG_AFC_CHARGER_MODE=y +CONFIG_BATTERY_LOGGING=y +CONFIG_ENABLE_FULL_BY_SOC=y +CONFIG_STEP_CHARGING=y +CONFIG_SUPPORT_HV_CTRL=y +CONFIG_SUPPORT_SHIP_MODE=y +CONFIG_LEDS_S2MPB02=m +CONFIG_SENSORS_VL53L8=m +CONFIG_SENSORS_VL53L8_SUPPORT_UAPI=y +CONFIG_SENSORS_VL53L8_QCOM=y +CONFIG_SEPARATE_IO_CORE_POWER=y +CONFIG_SENSORS_VL53L8_SUPPORT_RESUME_WORK=y +CONFIG_SENSORS_LAF_FAILURE_DEBUG=y +CONFIG_HV_MUIC_MAX77775_AFC=y +CONFIG_MUIC_MAX77775=y +CONFIG_MFD_S2MPB02=m diff --git a/arch/arm64/configs/oem/e3q-lego.config b/arch/arm64/configs/oem/e3q-lego.config new file mode 100644 index 000000000000..c3302ce2f5a3 --- /dev/null +++ b/arch/arm64/configs/oem/e3q-lego.config @@ -0,0 +1,305 @@ +# SEC_BSP / SEC_DEBUG +CONFIG_SOFT_WATCHDOG=m +CONFIG_SEC_CLASS=m +CONFIG_SEC_PARAM=m +CONFIG_SEC_KEY_NOTIFIER=m +CONFIG_SEC_RELOC_GPIO=m +CONFIG_SEC_QC_PARAM=m +CONFIG_SEC_DEBUG=m +CONFIG_SEC_BOOT_STAT=m +CONFIG_SEC_LOG_BUF=m +CONFIG_SEC_LOG_BUF_USING_TP_CONSOLE=y +CONFIG_SEC_PMSG=m +CONFIG_SEC_REBOOT_CMD=m +CONFIG_SEC_UPLOAD_CAUSE=m +CONFIG_SEC_CRASHKEY=m +CONFIG_SEC_CRASHKEY_LONG=m +CONFIG_SEC_DEBUG_REGION=m +CONFIG_SEC_RDX_BOOTDEV=m +CONFIG_SEC_ARM64_AP_CONTEXT=m +CONFIG_SEC_ARM64_FSIMD_DEBUG=m +CONFIG_SEC_ARM64_DEBUG=m +CONFIG_SEC_QC_DEBUG=m +CONFIG_SEC_QC_RBCMD=m +CONFIG_SEC_QC_DEBUG_PARTITION=m +CONFIG_SEC_QC_QCOM_REBOOT_REASON=m +CONFIG_SEC_QC_UPLOAD_CAUSE=m +CONFIG_SEC_QC_LOGGER=m +CONFIG_SEC_QC_SOC_ID=m +CONFIG_SEC_QC_SUMMARY=m +CONFIG_SEC_QC_USER_RESET=m +CONFIG_SEC_QC_HW_PARAM=m +CONFIG_SEC_QC_RST_EXINFO=m +CONFIG_SEC_QC_QCOM_WDT_CORE=m +CONFIG_SEC_QC_SMEM=m +CONFIG_I2C_GPIO=m + +#Audio +CONFIG_SND_SOC_CIRRUS_AMP=m +CONFIG_SND_SOC_CS35L43=m +CONFIG_SND_SOC_CS35L43_I2C=m +CONFIG_SND_SOC_SAMSUNG_AUDIO=m +CONFIG_SND_SOC_CS35L45=m +CONFIG_SND_SOC_CS35L45_I2C=m + +# Display +CONFIG_LCD_CLASS_DEVICE=m + +#POWER +CONFIG_SEC_PM=y +CONFIG_SEC_AP_PMIC=m +CONFIG_SEC_GPIO_DUMP=y +CONFIG_CPU_FREQ_LIMIT=m +CONFIG_RTC_AUTO_PWRON=m +CONFIG_SEC_PM_LOG=m + +#USB +CONFIG_I2C_EUSB2_REPEATER=m +CONFIG_USB_PHY_SETTING_QCOM=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +# CONFIG_SND_QC_USB_AUDIO_MODULE=m +CONFIG_USB_CONFIGFS_F_CONN_GADGET=m +CONFIG_USB_CONFIGFS_F_SS_MON_GADGET=m +CONFIG_USB_CONFIGFS_F_SS_ACM=m +CONFIG_USB_LINK_LAYER_TEST=m +CONFIG_USB_NET_SMSC75XX=m +CONFIG_USB_NET_SMSC95XX=m +CONFIG_USB_EHSET_TEST_FIXTURE=m +CONFIG_USB_HOST_SAMSUNG_FEATURE=y +CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION=y + +# Sensors +CONFIG_ADSP_FACTORY=m +CONFIG_SENSORS=m +CONFIG_LSM6DSO_FACTORY=y +CONFIG_AK09918_FACTORY=y +CONFIG_SUPPORT_LIGHT_SEAMLESS=y +CONFIG_SEC_SENSORS_SSC=y + +# block layer +CONFIG_BLK_SEC_COMMON=m +CONFIG_BLK_SEC_STATS=m +CONFIG_BLK_SEC_WB=m +CONFIG_MQ_IOSCHED_SSG=m +CONFIG_MQ_IOSCHED_SSG_CGROUP=m +CONFIG_MQ_IOSCHED_SSG_WB=m + +# UFS +CONFIG_SEC_UFS_FEATURE=y + +# SD +CONFIG_SEC_MMC_FEATURE=m + +# PCIE +CONFIG_SEC_PCIE=y +CONFIG_SEC_PCIE_AER=y +CONFIG_SEC_PCIE_L1SS=y + +# Network +CONFIG_INET6_AH=y +CONFIG_IP_NF_MATCH_AH=y +CONFIG_IP_NF_MATCH_RPFILTER=y +CONFIG_TCP_CONG_ADVANCED=y +CONFIG_TCP_CONG_BIC=y +# CONFIG_TCP_CONG_WESTWOOD is not set +# CONFIG_TCP_CONG_HTCP is not set +CONFIG_DEFAULT_BIC=y +CONFIG_DEFAULT_TCP_CONG="bic" +CONFIG_IPC_LOGGING_CDEV=m + +# Performance +CONFIG_RQ_STAT_SHOW=y +CONFIG_SCHED_POWER_OPTIMIZE=n + +CONFIG_I2C_CHARDEV=n + +CONFIG_SAMSUNG_PRODUCT_SHIP=y + + +# CONFIG_SEC_FACTORY is not set + + +# CONFIG_SEC_FACTORY_INTERPOSER is not set + +CONFIG_SEC_PANEL_NOTIFIER_V2=m +CONFIG_DRV_SAMSUNG_PMIC=m +CONFIG_CIRRUS_FIRMWARE_CL_DSP=m +CONFIG_USB_TYPEC_MANAGER_NOTIFIER=m +CONFIG_IF_CB_MANAGER=m +CONFIG_SBU_SWITCH_CONTROL=y +CONFIG_CHARGER_MAX77705=m +CONFIG_SEC_INPUT_BOOSTER=m +CONFIG_SEC_INPUT_BOOSTER_MODE=y +CONFIG_SEC_INPUT_BOOSTER_QC=y +CONFIG_SEC_INPUT_BOOSTER_HANDLER=y +CONFIG_DEV_RIL_BRIDGE=m +CONFIG_PRESSURE_FACTORY=y +CONFIG_FLIP_COVER_DETECTOR_FACTORY=y +CONFIG_LIGHT_FACTORY=y +CONFIG_PROX_FACTORY=y +CONFIG_SUPPORT_BRIGHTNESS_NOTIFY_FOR_LIGHT_SENSOR=y +CONFIG_SUPPORT_DDI_COPR_FOR_LIGHT_SENSOR=y +CONFIG_SUPPORT_LIGHT_CALIBRATION=y +CONFIG_SUPPORT_PANEL_STATE_NOTIFY_FOR_LIGHT_SENSOR=y +CONFIG_SUPPORT_PROX_CALIBRATION=y +CONFIG_LPS22DF_FACTORY=y +CONFIG_LSM6DSV_FACTORY=y +CONFIG_CHARGER_MAX77775=m +CONFIG_SHIPMODE_BY_VBAT=y +CONFIG_MUIC_NOTIFIER=m +CONFIG_HICCUP_CHARGER=y +CONFIG_MUIC_AFC_RETRY=y +CONFIG_MUIC_HV=y +CONFIG_MUIC_SUPPORT_PDIC=y +CONFIG_MUIC_USE_MODULE_PARAM=y +CONFIG_USE_MUIC=y +CONFIG_ANDROID_SWITCH=m +# CONFIG_ANDROID_SWITCH_GPIO is not set +CONFIG_SEC_UWB_LOGGER=y +CONFIG_SEC_NFC_LOGGER=y +CONFIG_SDP=m +CONFIG_VBUS_NOTIFIER=m +CONFIG_INPUT_SEC_INPUT=m +CONFIG_INPUT_SEC_NOTIFIER=m +CONFIG_SEC_DEBUG_TSP_LOG=m +CONFIG_TOUCHSCREEN_DUMP_MODE=m +CONFIG_INPUT_SEC_SECURE_TOUCH=m +CONFIG_INPUT_SEC_TRUSTED_TOUCH=m +CONFIG_INPUT_TOUCHSCREEN_TCLMV2=m +# CONFIG_SEC_INPUT_MULTI_DEVICE is not set +CONFIG_SEC_INPUT_RAWDATA=m +CONFIG_SUPPORT_DROPDUMP=m +CONFIG_REGULATOR_S2DOS05=m +CONFIG_REGULATOR_S2DOS05_ELVSS_FD=y +CONFIG_TOUCHSCREEN_STM_SPI=m +CONFIG_FUELGAUGE_MAX77705=m +CONFIG_UI_SOC_PROLONGING=y +CONFIG_MFD_MAX77705=m +CONFIG_ABC_IFPMIC_EVENT=y +CONFIG_MAX77705_FW_SEPARATION_PID_BY_MODEL=y +CONFIG_VIBRATOR_VIB_INFO=m +CONFIG_SEC_STI=y +CONFIG_KPERFMON=y +CONFIG_KPERFMON_BUILD=m +CONFIG_SEC_VIBRATOR_INPUTFF=m +CONFIG_VIB_STORE_LE_PARAM=y +# CONFIG_SEC_VIB_FOLD_MODEL is not set +CONFIG_USB_NOTIFY_LAYER=m +CONFIG_USB_DEBUG_DETAILED_LOG=y +CONFIG_USB_EXTERNAL_NOTIFY=y +CONFIG_USB_HMT_SAMSUNG_INPUT=y +CONFIG_USB_HOST_NOTIFY=y +CONFIG_USB_HOST_SAMSUNG_FEATURE=y +CONFIG_USB_HW_PARAM=y +CONFIG_USB_INTERFACE_LPM_LIST=y +CONFIG_USB_NOTIFY_PROC_LOG=y +CONFIG_USB_USING_ADVANCED_USBLOG=y +CONFIG_USB_VENDOR_NOTIFY=y +CONFIG_USB_VENDOR_RECEIVER=m +CONFIG_SENSORS_FINGERPRINT=m +CONFIG_SEC_ABC_SPEC_TYPE1=m +CONFIG_SB_CORE=m +CONFIG_SB_PQUEUE=y +CONFIG_SB_NOTIFY=y +CONFIG_SB_SYSFS=y +CONFIG_SB_VOTE=y +CONFIG_PDIC_NOTIFIER=m +CONFIG_PDIC_USE_MODULE_PARAM=y +CONFIG_USB_NOTIFIER=m +CONFIG_SEC_ABC=m +CONFIG_SEC_ABC_HUB=m +CONFIG_SEC_ABC_COMMON=m +CONFIG_SEC_ABC_HUB_CORE=m +CONFIG_SEC_ABC_HUB_BOOTC=m +CONFIG_SEC_ABC_MOTTO=m +CONFIG_FUELGAUGE_MAX77775=m +CONFIG_I2C_GPIO=m +CONFIG_INPUT_CS40L26_I2C=m +CONFIG_SND_SOC_CS40L26=m +CONFIG_CS40L26_SAMSUNG_FEATURE=y +CONFIG_CS40L26_SAMSUNG_USE_DVL=y +CONFIG_CS40L26_SAMSUNG_USE_MAX_DATA_TX_SIZE=y +CONFIG_SEC_ABC_DETECT_CONN=m +CONFIG_QCOM_SEC_ABC_DETECT=m +CONFIG_INPUT_HALL_IC=m +CONFIG_HALL_NOTIFIER=m +CONFIG_HALL_LOGICAL=m +# CONFIG_HALL_DUMP_KEY_MODE is not set +CONFIG_SENSORS_FLICKER_SELF_TEST=m +CONFIG_CCIC_MAX77705=m +CONFIG_CCIC_MAX77705_DEBUG=y +CONFIG_GET_UNMASK_VBUS_HWPARAM=y +CONFIG_MAX77705_FW_PID03_SUPPORT=y +CONFIG_MAXIM_CCIC_ALTERNATE_MODE=y +CONFIG_CHARGER_PCA9481=m +CONFIG_MFD_MAX77775=m +CONFIG_MAX77775_ABC_IFPMIC_EVENT=y +CONFIG_REGULATOR_FIXED_VOLTAGE=y +CONFIG_SENSORS_STK6D2X=m +# CONFIG_FLICKER_PWM_CALIBRATION is not set +CONFIG_AFC=y +CONFIG_HV_MUIC_MAX77705_AFC=y +CONFIG_MUIC_MAX77705=y +CONFIG_MUIC_MAX77705_PDIC=y +CONFIG_REGULATOR_S2MPB03=m +CONFIG_SEC_PM_THERMISTOR=m +CONFIG_INPUT_WACOM_WEZ02=m +CONFIG_SENSORS_QFS4008=m +CONFIG_SENSORS_FINGERPRINT_MODULE=y +CONFIG_FINGERPRINT_SECURE=y +CONFIG_SENSORS_FINGERPRINT_QCOM=y +CONFIG_STAR_K250A_LEGO=m +CONFIG_SEC_SNVM_WAKELOCK_METHOD=0 +# CONFIG_STAR_MEMORY_LEAK is not set +CONFIG_WIRELESS_CHARGER_NU1668=m +CONFIG_WIRELESS_AUTH=y +CONFIG_WIRELESS_CHARGER_HIGH_VOLTAGE=y +CONFIG_WIRELESS_TX_MODE=y +CONFIG_WIRELESS_FIRMWARE_UPDATE=y +CONFIG_WIRELESS_IC_PARAM=y +CONFIG_TX_GEAR_PHM_VOUT_CTRL=y +CONFIG_WIRELESS_RX_PHM_CTRL=y +CONFIG_SAMSUNG_NFC=m +CONFIG_NFC_NXP_COMBINED=y +CONFIG_NFC_SN2XX=y +CONFIG_NFC_SN2XX_ESE_SUPPORT=y +CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE=y +# CONFIG_ESE_USE_TZ_API is not set +CONFIG_SEC_NFC_WAKELOCK_METHOD=0 +# CONFIG_CLK_ACPM_INIT is not set +CONFIG_REGULATOR_S2MPB02=m +CONFIG_SAMSUNG_UWB=m +CONFIG_UWB_SR200=y +CONFIG_CCIC_MAX77775=m +CONFIG_MAX77775_CCIC_ALTERNATE_MODE=y +CONFIG_MAX77775_GET_UNMASK_VBUS_HWPARAM=y +CONFIG_REGULATOR_S2DOS07=m +CONFIG_SEC_DISPLAYPORT=m +CONFIG_TOUCHSCREEN_SYNAPTICS_SPI=m +CONFIG_DIRECT_CHARGING=m +CONFIG_BATTERY_SAMSUNG=m +CONFIG_SEC_PD=m +CONFIG_BATTERY_GKI=y +CONFIG_BATTERY_AGE_FORECAST=y +CONFIG_BATTERY_CISD=y +CONFIG_AFC_CHARGER_MODE=y +CONFIG_BATTERY_LOGGING=y +CONFIG_ENABLE_FULL_BY_SOC=y +CONFIG_STEP_CHARGING=y +CONFIG_SUPPORT_HV_CTRL=y +CONFIG_SUPPORT_SHIP_MODE=y +CONFIG_LEDS_S2MPB02=m +CONFIG_SENSORS_VL53L8=m +CONFIG_SENSORS_VL53L8_SUPPORT_UAPI=y +CONFIG_SENSORS_VL53L8_QCOM=y +CONFIG_SEPARATE_IO_CORE_POWER=y +CONFIG_SENSORS_VL53L8_SUPPORT_RESUME_WORK=y +CONFIG_SENSORS_LAF_FAILURE_DEBUG=y +CONFIG_HV_MUIC_MAX77775_AFC=y +CONFIG_MUIC_MAX77775=y +CONFIG_MFD_S2MPB02=m +CONFIG_QCOM_FSA4480_I2C=n +CONFIG_QCOM_WCD939X_I2C=n + +# Use 1M for kernel log +CONFIG_LOG_BUF_SHIFT=20 diff --git a/arch/arm64/configs/oem/e3q_defconfig b/arch/arm64/configs/oem/e3q_defconfig new file mode 100644 index 000000000000..bd9ac3c24baa --- /dev/null +++ b/arch/arm64/configs/oem/e3q_defconfig @@ -0,0 +1,1422 @@ +CONFIG_UAPI_HEADER_TEST=y +CONFIG_AUDIT=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_BPF_SYSCALL=y +CONFIG_BPF_JIT=y +CONFIG_BPF_JIT_ALWAYS_ON=y +# CONFIG_BPF_UNPRIV_DEFAULT_OFF is not set +CONFIG_PREEMPT=y +CONFIG_IRQ_TIME_ACCOUNTING=y +CONFIG_TASKSTATS=y +CONFIG_TASK_XACCT=y +CONFIG_TASK_IO_ACCOUNTING=y +CONFIG_PSI=y +CONFIG_RCU_EXPERT=y +CONFIG_RCU_BOOST=y +CONFIG_RCU_NOCB_CPU=y +CONFIG_RCU_LAZY=y +CONFIG_RCU_LAZY_DEFAULT_OFF=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_IKHEADERS=m +CONFIG_UCLAMP_TASK=y +CONFIG_UCLAMP_BUCKETS_COUNT=20 +CONFIG_CGROUPS=y +CONFIG_MEMCG=y +CONFIG_BLK_CGROUP=y +CONFIG_CGROUP_SCHED=y +CONFIG_UCLAMP_TASK_GROUP=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CPUSETS=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_BPF=y +CONFIG_NAMESPACES=y +# CONFIG_PID_NS is not set +CONFIG_RT_SOFTIRQ_AWARE_SCHED=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_BOOT_CONFIG=y +# CONFIG_SYSFS_SYSCALL is not set +# CONFIG_FHANDLE is not set +CONFIG_KALLSYMS_ALL=y +# CONFIG_RSEQ is not set +CONFIG_EMBEDDED=y +CONFIG_PROFILING=y +CONFIG_ARCH_SUNXI=y +CONFIG_ARCH_BCM=y +CONFIG_ARCH_BRCMSTB=y +CONFIG_ARCH_HISI=y +CONFIG_ARCH_QCOM=y +CONFIG_ARCH_TEGRA=y +CONFIG_SCHED_MC=y +CONFIG_NR_CPUS=32 +CONFIG_PARAVIRT_TIME_ACCOUNTING=y +CONFIG_ARM64_SW_TTBR0_PAN=y +CONFIG_COMPAT=y +CONFIG_ARMV8_DEPRECATED=y +CONFIG_SWP_EMULATION=y +CONFIG_CP15_BARRIER_EMULATION=y +CONFIG_SETEND_EMULATION=y +CONFIG_ARM64_PMEM=y +# CONFIG_ARM64_BTI_KERNEL is not set +CONFIG_RANDOMIZE_BASE=y +# CONFIG_RANDOMIZE_MODULE_REGION_FULL is not set +CONFIG_UNWIND_PATCH_PAC_INTO_SCS=y +CONFIG_CMDLINE="console=ttynull stack_depot_disable=on cgroup_disable=pressure kasan.page_alloc.sample=10 kasan.stacktrace=off kvm-arm.mode=protected bootconfig ioremap_guard" +CONFIG_CMDLINE_EXTEND=y +# CONFIG_DMI is not set +CONFIG_HIBERNATION=y +CONFIG_PM_WAKELOCKS=y +CONFIG_PM_WAKELOCKS_LIMIT=0 +# CONFIG_PM_WAKELOCKS_GC is not set +CONFIG_PM_DEBUG=y +CONFIG_PM_ADVANCED_DEBUG=y +CONFIG_ENERGY_MODEL=y +CONFIG_CPU_IDLE=y +CONFIG_CPU_IDLE_GOV_MENU=y +CONFIG_CPU_IDLE_GOV_TEO=y +CONFIG_ARM_PSCI_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_CONSERVATIVE=y +CONFIG_ARM_SCPI_CPUFREQ=y +# CONFIG_ARM_BRCMSTB_AVS_CPUFREQ is not set +CONFIG_ARM_SCMI_CPUFREQ=y +CONFIG_VIRTUALIZATION=y +CONFIG_KVM=y +CONFIG_PKVM_MODULE_PATH="/lib/modules/" +CONFIG_KPROBES=y +CONFIG_JUMP_LABEL=y +CONFIG_SHADOW_CALL_STACK=y +CONFIG_CFI_CLANG=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODVERSIONS=y +CONFIG_MODULE_SCMVERSION=y +CONFIG_MODULE_SIG=y +CONFIG_MODULE_SIG_PROTECT=y +CONFIG_MODPROBE_PATH="/system/bin/modprobe" +CONFIG_BLK_DEV_ZONED=y +CONFIG_BLK_DEV_THROTTLING=y +CONFIG_BLK_CGROUP_IOCOST=y +CONFIG_BLK_CGROUP_IOPRIO=y +CONFIG_BLK_INLINE_ENCRYPTION=y +CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK=y +CONFIG_IOSCHED_BFQ=y +CONFIG_BFQ_GROUP_IOSCHED=y +CONFIG_GKI_HACKS_TO_FIX=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_BINFMT_MISC=y +# CONFIG_SLAB_MERGE_DEFAULT is not set +CONFIG_SLAB_FREELIST_RANDOM=y +CONFIG_SLAB_FREELIST_HARDENED=y +CONFIG_SHUFFLE_PAGE_ALLOCATOR=y +# CONFIG_COMPAT_BRK is not set +CONFIG_MEMORY_HOTPLUG=y +CONFIG_MEMORY_HOTREMOVE=y +CONFIG_DEFAULT_MMAP_MIN_ADDR=32768 +CONFIG_TRANSPARENT_HUGEPAGE=y +CONFIG_TRANSPARENT_HUGEPAGE_MADVISE=y +CONFIG_CLEANCACHE=y +CONFIG_CMA=y +CONFIG_CMA_DEBUGFS=y +CONFIG_CMA_AREAS=32 +# CONFIG_ZONE_DMA is not set +CONFIG_ZONE_DEVICE=y +CONFIG_ANON_VMA_NAME=y +CONFIG_USERFAULTFD=y +CONFIG_LRU_GEN=y +CONFIG_LRU_GEN_ENABLED=y +CONFIG_DAMON=y +CONFIG_DAMON_VADDR=y +CONFIG_DAMON_SYSFS=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_XFRM_USER=y +CONFIG_XFRM_INTERFACE=y +CONFIG_XFRM_MIGRATE=y +CONFIG_XFRM_STATISTICS=y +CONFIG_NET_KEY=y +CONFIG_XDP_SOCKETS=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_NET_IPIP=y +CONFIG_NET_IPGRE_DEMUX=y +CONFIG_NET_IPGRE=y +CONFIG_NET_IPVTI=y +CONFIG_INET_ESP=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_ESP=y +CONFIG_INET6_IPCOMP=y +CONFIG_IPV6_MIP6=y +CONFIG_IPV6_VTI=y +CONFIG_IPV6_GRE=y +CONFIG_IPV6_MULTIPLE_TABLES=y +CONFIG_IPV6_MROUTE=y +CONFIG_IPV6_MROUTE_MULTIPLE_TABLES=y +CONFIG_NETFILTER=y +CONFIG_NF_CONNTRACK=y +CONFIG_NF_CONNTRACK_SECMARK=y +CONFIG_NF_CONNTRACK_PROCFS=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_DSCP=y +CONFIG_NETFILTER_XT_TARGET_IDLETIMER=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_CONNBYTES=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=y +CONFIG_NETFILTER_XT_MATCH_LENGTH=y +CONFIG_NETFILTER_XT_MATCH_LIMIT=y +CONFIG_NETFILTER_XT_MATCH_MAC=y +CONFIG_NETFILTER_XT_MATCH_MARK=y +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y +CONFIG_NETFILTER_XT_MATCH_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_ECN=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_TIPC=m +CONFIG_L2TP=m +CONFIG_BRIDGE=y +CONFIG_VLAN_8021Q=m +CONFIG_6LOWPAN=m +CONFIG_IEEE802154=m +CONFIG_IEEE802154_6LOWPAN=m +CONFIG_MAC802154=m +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=y +CONFIG_NET_SCH_PRIO=y +CONFIG_NET_SCH_MULTIQ=y +CONFIG_NET_SCH_SFQ=y +CONFIG_NET_SCH_TBF=y +CONFIG_NET_SCH_NETEM=y +CONFIG_NET_SCH_CODEL=y +CONFIG_NET_SCH_FQ_CODEL=y +CONFIG_NET_SCH_FQ=y +CONFIG_NET_SCH_INGRESS=y +CONFIG_NET_CLS_BASIC=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_CLS_MATCHALL=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_NET_ACT_POLICE=y +CONFIG_NET_ACT_GACT=y +CONFIG_NET_ACT_MIRRED=y +CONFIG_NET_ACT_SKBEDIT=y +CONFIG_NET_ACT_BPF=y +CONFIG_VSOCKETS=y +CONFIG_CGROUP_NET_PRIO=y +CONFIG_CAN=m +CONFIG_BT=m +CONFIG_BT_RFCOMM=m +CONFIG_BT_RFCOMM_TTY=y +CONFIG_BT_HIDP=m +CONFIG_BT_HCIBTSDIO=m +CONFIG_BT_HCIUART=m +CONFIG_BT_HCIUART_LL=y +CONFIG_BT_HCIUART_BCM=y +CONFIG_BT_HCIUART_QCA=y +CONFIG_RFKILL=m +CONFIG_NFC=m +CONFIG_PCI=y +CONFIG_PCIEPORTBUS=y +CONFIG_PCIEAER=y +CONFIG_PCI_IOV=y +# CONFIG_VGA_ARB is not set +CONFIG_PCI_HOST_GENERIC=y +# CONFIG_PCIE_BRCMSTB is not set +CONFIG_PCIE_DW_PLAT_EP=y +CONFIG_PCIE_QCOM=y +CONFIG_PCIE_KIRIN=y +CONFIG_PCI_ENDPOINT=y +CONFIG_FW_LOADER_USER_HELPER=y +# CONFIG_FW_CACHE is not set +# CONFIG_BRCMSTB_GISB_ARB is not set +# CONFIG_SUN50I_DE2_BUS is not set +# CONFIG_SUNXI_RSB is not set +CONFIG_ARM_SCMI_PROTOCOL=y +CONFIG_ARM_SCMI_TRANSPORT_VIRTIO=y +# CONFIG_ARM_SCMI_POWER_DOMAIN is not set +CONFIG_ARM_SCPI_PROTOCOL=y +# CONFIG_ARM_SCPI_POWER_DOMAIN is not set +# CONFIG_EFI_ARMSTUB_DTB_LOADER is not set +CONFIG_GNSS=y +CONFIG_ZRAM=m +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_LOOP_MIN_COUNT=16 +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_SIZE=8192 +CONFIG_BLK_DEV_UBLK=y +CONFIG_BLK_DEV_NVME=y +CONFIG_NVME_MULTIPATH=y +CONFIG_SRAM=y +CONFIG_UID_SYS_STATS=y +CONFIG_SCSI=y +# CONFIG_SCSI_PROC_FS is not set +CONFIG_BLK_DEV_SD=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 +CONFIG_DM_VERITY_FEC=y +CONFIG_NETDEVICES=y +CONFIG_DUMMY=y +CONFIG_WIREGUARD=y +CONFIG_IFB=y +CONFIG_MACSEC=y +CONFIG_TUN=y +CONFIG_VETH=y +CONFIG_LED_TRIGGER_PHY=y +CONFIG_AX88796B_PHY=y +CONFIG_CAN_VCAN=m +CONFIG_CAN_SLCAN=m +CONFIG_PPP=m +CONFIG_PPP_BSDCOMP=m +CONFIG_PPP_DEFLATE=m +CONFIG_PPP_MPPE=m +CONFIG_PPTP=m +CONFIG_PPPOL2TP=m +CONFIG_USB_NET_DRIVERS=m +CONFIG_USB_RTL8150=m +CONFIG_USB_RTL8152=m +CONFIG_USB_USBNET=m +CONFIG_USB_NET_CDC_EEM=m +# CONFIG_USB_NET_NET1080 is not set +# CONFIG_USB_NET_CDC_SUBSET is not set +# CONFIG_USB_NET_ZAURUS is not set +CONFIG_USB_NET_AQC111=m +# 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_WLAN_VENDOR_QUANTENNA is not set +CONFIG_WWAN=m +# CONFIG_WWAN_DEBUGFS is not set +CONFIG_INPUT_EVDEV=y +CONFIG_KEYBOARD_GPIO=y +# CONFIG_MOUSE_PS2 is not set +CONFIG_INPUT_JOYSTICK=y +CONFIG_JOYSTICK_XPAD=y +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_INPUT_MISC=y +CONFIG_INPUT_UINPUT=y +# CONFIG_VT is not set +# CONFIG_LEGACY_PTYS is not set +CONFIG_SERIAL_8250=y +# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set +CONFIG_SERIAL_8250_CONSOLE=y +# CONFIG_SERIAL_8250_EXAR is not set +CONFIG_SERIAL_8250_NR_UARTS=32 +CONFIG_SERIAL_8250_RUNTIME_UARTS=0 +CONFIG_SERIAL_8250_DW=y +# CONFIG_SERIAL_8250_BCM7271 is not set +CONFIG_SERIAL_OF_PLATFORM=y +CONFIG_SERIAL_AMBA_PL011=y +CONFIG_SERIAL_AMBA_PL011_CONSOLE=y +CONFIG_SERIAL_SAMSUNG=y +CONFIG_SERIAL_SAMSUNG_CONSOLE=y +CONFIG_SERIAL_TEGRA_TCU=y +CONFIG_SERIAL_QCOM_GENI=y +CONFIG_SERIAL_QCOM_GENI_CONSOLE=y +CONFIG_SERIAL_SPRD=y +CONFIG_SERIAL_SPRD_CONSOLE=y +CONFIG_NULL_TTY=y +CONFIG_HVC_DCC=y +CONFIG_SERIAL_DEV_BUS=y +CONFIG_HW_RANDOM=y +# CONFIG_DEVMEM is not set +# CONFIG_DEVPORT is not set +# CONFIG_I2C_COMPAT is not set +# CONFIG_I2C_HELPER_AUTO is not set +# CONFIG_I2C_BRCMSTB is not set +CONFIG_I3C=y +CONFIG_SPI=y +CONFIG_SPI_MEM=y +# CONFIG_SPMI_MSM_PMIC_ARB is not set +# CONFIG_PINCTRL_BCM2835 is not set +# CONFIG_PINCTRL_SUN8I_H3_R is not set +# CONFIG_PINCTRL_SUN50I_A64 is not set +# CONFIG_PINCTRL_SUN50I_A64_R is not set +# CONFIG_PINCTRL_SUN50I_A100 is not set +# CONFIG_PINCTRL_SUN50I_A100_R is not set +# CONFIG_PINCTRL_SUN50I_H5 is not set +# CONFIG_PINCTRL_SUN50I_H6 is not set +# CONFIG_PINCTRL_SUN50I_H6_R is not set +# CONFIG_PINCTRL_SUN50I_H616 is not set +# CONFIG_PINCTRL_SUN50I_H616_R is not set +# CONFIG_GPIO_BRCMSTB is not set +CONFIG_GPIO_GENERIC_PLATFORM=y +# CONFIG_POWER_RESET_BRCMSTB is not set +CONFIG_POWER_RESET_HISI=y +CONFIG_POWER_RESET_SYSCON=y +# CONFIG_HWMON is not set +CONFIG_THERMAL=y +CONFIG_THERMAL_NETLINK=y +CONFIG_THERMAL_STATISTICS=y +CONFIG_THERMAL_EMERGENCY_POWEROFF_DELAY_MS=100 +CONFIG_THERMAL_WRITABLE_TRIPS=y +CONFIG_THERMAL_GOV_USER_SPACE=y +CONFIG_THERMAL_GOV_POWER_ALLOCATOR=y +CONFIG_CPU_THERMAL=y +CONFIG_CPU_IDLE_THERMAL=y +CONFIG_DEVFREQ_THERMAL=y +CONFIG_THERMAL_EMULATION=y +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_CORE=y +CONFIG_MFD_ACT8945A=y +CONFIG_REGULATOR=y +CONFIG_REGULATOR_FIXED_VOLTAGE=y +CONFIG_RC_CORE=y +CONFIG_BPF_LIRC_MODE2=y +CONFIG_LIRC=y +# CONFIG_RC_MAP is not set +CONFIG_RC_DECODERS=y +CONFIG_RC_DEVICES=y +# CONFIG_MEDIA_ANALOG_TV_SUPPORT is not set +# CONFIG_MEDIA_DIGITAL_TV_SUPPORT is not set +# CONFIG_MEDIA_RADIO_SUPPORT is not set +# CONFIG_MEDIA_SDR_SUPPORT is not set +# CONFIG_MEDIA_TEST_SUPPORT is not set +CONFIG_MEDIA_USB_SUPPORT=y +CONFIG_USB_GSPCA=y +CONFIG_USB_VIDEO_CLASS=y +CONFIG_V4L_PLATFORM_DRIVERS=y +CONFIG_V4L_MEM2MEM_DRIVERS=y +CONFIG_DRM=y +CONFIG_BACKLIGHT_CLASS_DEVICE=y +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_HRTIMER=y +# CONFIG_SND_SUPPORT_OLD_API is not set +# CONFIG_SND_VERBOSE_PROCFS is not set +# CONFIG_SND_DRIVERS is not set +CONFIG_SND_USB_AUDIO=y +CONFIG_SND_SOC=y +CONFIG_HID_BATTERY_STRENGTH=y +CONFIG_HIDRAW=y +CONFIG_UHID=y +CONFIG_HID_APPLE=y +CONFIG_HID_PRODIKEYS=y +CONFIG_HID_ELECOM=y +CONFIG_HID_UCLOGIC=y +CONFIG_HID_LOGITECH=y +CONFIG_HID_LOGITECH_DJ=y +CONFIG_HID_MAGICMOUSE=y +CONFIG_HID_MICROSOFT=y +CONFIG_HID_MULTITOUCH=y +CONFIG_HID_NINTENDO=y +CONFIG_HID_PICOLCD=y +CONFIG_HID_PLANTRONICS=y +CONFIG_HID_PLAYSTATION=y +CONFIG_PLAYSTATION_FF=y +CONFIG_HID_ROCCAT=y +CONFIG_HID_SONY=y +CONFIG_SONY_FF=y +CONFIG_HID_STEAM=y +CONFIG_HID_WACOM=y +CONFIG_HID_WIIMOTE=y +CONFIG_USB_HIDDEV=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_OTG=y +CONFIG_USB_XHCI_HCD=y +CONFIG_USB_XHCI_PCI_RENESAS=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_ROOT_HUB_TT=y +CONFIG_USB_EHCI_HCD_PLATFORM=y +CONFIG_USB_ACM=m +CONFIG_USB_STORAGE=y +CONFIG_USB_UAS=y +CONFIG_USB_DWC3=y +CONFIG_USB_SERIAL=m +CONFIG_USB_SERIAL_FTDI_SIO=m +CONFIG_USB_GADGET=y +# CONFIG_USB_BDC_UDC is not set +CONFIG_USB_CONFIGFS=y +CONFIG_USB_CONFIGFS_UEVENT=y +CONFIG_USB_CONFIGFS_SERIAL=y +CONFIG_USB_CONFIGFS_ACM=y +CONFIG_USB_CONFIGFS_NCM=y +CONFIG_USB_CONFIGFS_ECM=y +CONFIG_USB_CONFIGFS_EEM=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_UAC2=y +CONFIG_USB_CONFIGFS_F_MIDI=y +CONFIG_USB_CONFIGFS_F_HID=y +CONFIG_USB_CONFIGFS_F_UVC=y +CONFIG_TYPEC=y +CONFIG_TYPEC_TCPM=y +CONFIG_TYPEC_TCPCI=y +CONFIG_TYPEC_UCSI=y +CONFIG_TYPEC_DP_ALTMODE=y +CONFIG_MMC=y +# CONFIG_PWRSEQ_EMMC is not set +# CONFIG_PWRSEQ_SIMPLE is not set +CONFIG_MMC_CRYPTO=y +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_PLTFM=y +# CONFIG_MMC_SDHCI_BRCMSTB is not set +CONFIG_SCSI_UFSHCD=y +CONFIG_SCSI_UFS_BSG=y +CONFIG_SCSI_UFS_CRYPTO=y +CONFIG_SCSI_UFSHCD_PCI=y +CONFIG_SCSI_UFSHCD_PLATFORM=y +CONFIG_SCSI_UFS_DWC_TC_PLATFORM=y +CONFIG_SCSI_UFS_HISI=y +CONFIG_LEDS_CLASS_FLASH=y +CONFIG_LEDS_CLASS_MULTICOLOR=y +CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_LEDS_TRIGGER_TRANSIENT=y +CONFIG_EDAC=y +CONFIG_RTC_CLASS=y +# CONFIG_RTC_DRV_BRCMSTB is not set +CONFIG_RTC_DRV_PL030=y +CONFIG_RTC_DRV_PL031=y +CONFIG_DMABUF_HEAPS=y +CONFIG_DMABUF_SYSFS_STATS=y +CONFIG_DMABUF_HEAPS_DEFERRED_FREE=y +CONFIG_DMABUF_HEAPS_PAGE_POOL=y +CONFIG_UIO=y +CONFIG_VIRT_DRIVERS=y +CONFIG_GUNYAH=y +# CONFIG_GUNYAH_QCOM_PLATFORM is not set +CONFIG_GUNYAH_VCPU=y +CONFIG_GUNYAH_IRQFD=y +CONFIG_GUNYAH_IOEVENTFD=y +CONFIG_MTK_GZVM=m +CONFIG_VHOST_VSOCK=y +CONFIG_STAGING=y +CONFIG_ASHMEM=y +CONFIG_COMMON_CLK_SCPI=y +# CONFIG_CLK_BCM2835 is not set +# CONFIG_SUNXI_CCU is not set +CONFIG_HWSPINLOCK=y +# CONFIG_SUN50I_ERRATUM_UNKNOWN1 is not set +CONFIG_IOMMU_IO_PGTABLE_ARMV7S=y +CONFIG_REMOTEPROC=y +CONFIG_REMOTEPROC_CDEV=y +CONFIG_RPMSG_CHAR=y +CONFIG_SOC_BRCMSTB=y +# CONFIG_BRCMSTB_PM is not set +CONFIG_QCOM_GENI_SE=y +CONFIG_ARCH_TEGRA_234_SOC=y +CONFIG_DEVFREQ_GOV_PERFORMANCE=y +CONFIG_DEVFREQ_GOV_POWERSAVE=y +CONFIG_DEVFREQ_GOV_USERSPACE=y +CONFIG_DEVFREQ_GOV_PASSIVE=y +CONFIG_PM_DEVFREQ_EVENT=y +CONFIG_MEMORY=y +# CONFIG_BRCMSTB_DPFE is not set +# CONFIG_BRCMSTB_MEMC is not set +CONFIG_IIO=y +CONFIG_IIO_BUFFER=y +CONFIG_IIO_TRIGGER=y +CONFIG_PWM=y +# CONFIG_BCM7038_L1_IRQ is not set +# CONFIG_BCM7120_L2_IRQ is not set +# CONFIG_BRCMSTB_L2_IRQ is not set +# CONFIG_RESET_BRCMSTB is not set +# CONFIG_RESET_BRCMSTB_RESCAL is not set +# CONFIG_PHY_BRCM_USB is not set +CONFIG_POWERCAP=y +CONFIG_IDLE_INJECT=y +CONFIG_ANDROID_BINDER_IPC=y +CONFIG_ANDROID_BINDERFS=y +CONFIG_ANDROID_DEBUG_SYMBOLS=y +CONFIG_ANDROID_VENDOR_HOOKS=y +CONFIG_ANDROID_DEBUG_KINFO=y +CONFIG_LIBNVDIMM=y +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_EXT4_FS_SECURITY=y +CONFIG_F2FS_FS=y +CONFIG_F2FS_FS_SECURITY=y +CONFIG_F2FS_FS_COMPRESSION=y +CONFIG_F2FS_UNFAIR_RWSEM=y +CONFIG_FS_ENCRYPTION=y +CONFIG_FS_ENCRYPTION_INLINE_CRYPT=y +CONFIG_FS_VERITY=y +CONFIG_FS_VERITY_BUILTIN_SIGNATURES=y +# CONFIG_DNOTIFY is not set +CONFIG_QUOTA=y +CONFIG_QFMT_V2=y +CONFIG_FUSE_FS=y +CONFIG_VIRTIO_FS=y +CONFIG_FUSE_BPF=y +CONFIG_OVERLAY_FS=y +CONFIG_INCREMENTAL_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_EXFAT_FS=y +CONFIG_TMPFS=y +# CONFIG_EFIVAR_FS is not set +CONFIG_PSTORE=y +CONFIG_PSTORE_CONSOLE=y +CONFIG_PSTORE_PMSG=y +CONFIG_PSTORE_RAM=y +CONFIG_EROFS_FS=y +CONFIG_EROFS_FS_PCPU_KTHREAD=y +CONFIG_EROFS_FS_PCPU_KTHREAD_HIPRI=y +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_CODEPAGE_737=y +CONFIG_NLS_CODEPAGE_775=y +CONFIG_NLS_CODEPAGE_850=y +CONFIG_NLS_CODEPAGE_852=y +CONFIG_NLS_CODEPAGE_855=y +CONFIG_NLS_CODEPAGE_857=y +CONFIG_NLS_CODEPAGE_860=y +CONFIG_NLS_CODEPAGE_861=y +CONFIG_NLS_CODEPAGE_862=y +CONFIG_NLS_CODEPAGE_863=y +CONFIG_NLS_CODEPAGE_864=y +CONFIG_NLS_CODEPAGE_865=y +CONFIG_NLS_CODEPAGE_866=y +CONFIG_NLS_CODEPAGE_869=y +CONFIG_NLS_CODEPAGE_936=y +CONFIG_NLS_CODEPAGE_950=y +CONFIG_NLS_CODEPAGE_932=y +CONFIG_NLS_CODEPAGE_949=y +CONFIG_NLS_CODEPAGE_874=y +CONFIG_NLS_ISO8859_8=y +CONFIG_NLS_CODEPAGE_1250=y +CONFIG_NLS_CODEPAGE_1251=y +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=y +CONFIG_NLS_ISO8859_2=y +CONFIG_NLS_ISO8859_3=y +CONFIG_NLS_ISO8859_4=y +CONFIG_NLS_ISO8859_5=y +CONFIG_NLS_ISO8859_6=y +CONFIG_NLS_ISO8859_7=y +CONFIG_NLS_ISO8859_9=y +CONFIG_NLS_ISO8859_13=y +CONFIG_NLS_ISO8859_14=y +CONFIG_NLS_ISO8859_15=y +CONFIG_NLS_KOI8_R=y +CONFIG_NLS_KOI8_U=y +CONFIG_NLS_MAC_ROMAN=y +CONFIG_NLS_MAC_CELTIC=y +CONFIG_NLS_MAC_CENTEURO=y +CONFIG_NLS_MAC_CROATIAN=y +CONFIG_NLS_MAC_CYRILLIC=y +CONFIG_NLS_MAC_GAELIC=y +CONFIG_NLS_MAC_GREEK=y +CONFIG_NLS_MAC_ICELAND=y +CONFIG_NLS_MAC_INUIT=y +CONFIG_NLS_MAC_ROMANIAN=y +CONFIG_NLS_MAC_TURKISH=y +CONFIG_NLS_UTF8=y +CONFIG_UNICODE=y +CONFIG_SECURITY=y +CONFIG_SECURITYFS=y +CONFIG_SECURITY_NETWORK=y +CONFIG_HARDENED_USERCOPY=y +CONFIG_FORTIFY_SOURCE=y +CONFIG_STATIC_USERMODEHELPER=y +CONFIG_STATIC_USERMODEHELPER_PATH="" +CONFIG_SECURITY_SELINUX=y +CONFIG_INIT_ON_ALLOC_DEFAULT_ON=y +CONFIG_CRYPTO_ECDH=y +CONFIG_CRYPTO_DES=y +CONFIG_CRYPTO_ADIANTUM=y +CONFIG_CRYPTO_HCTR2=y +CONFIG_CRYPTO_CHACHA20POLY1305=y +CONFIG_CRYPTO_CCM=y +CONFIG_CRYPTO_BLAKE2B=y +CONFIG_CRYPTO_CMAC=y +CONFIG_CRYPTO_MD5=y +CONFIG_CRYPTO_XCBC=y +CONFIG_CRYPTO_LZ4=y +CONFIG_CRYPTO_ZSTD=y +CONFIG_CRYPTO_ANSI_CPRNG=y +CONFIG_CRYPTO_GHASH_ARM64_CE=y +CONFIG_CRYPTO_SHA2_ARM64_CE=y +CONFIG_CRYPTO_SHA512_ARM64_CE=y +CONFIG_CRYPTO_POLYVAL_ARM64_CE=y +CONFIG_CRYPTO_AES_ARM64_CE_BLK=y +CONFIG_TRACE_MMIO_ACCESS=y +CONFIG_CRC_CCITT=y +CONFIG_XZ_DEC=y +CONFIG_DMA_CMA=y +CONFIG_PRINTK_TIME=y +CONFIG_PRINTK_CALLER=y +CONFIG_DYNAMIC_DEBUG_CORE=y +CONFIG_DEBUG_INFO_DWARF4=y +CONFIG_DEBUG_INFO_BTF=y +CONFIG_MODULE_ALLOW_BTF_MISMATCH=y +CONFIG_HEADERS_INSTALL=y +# CONFIG_SECTION_MISMATCH_WARN_ONLY is not set +CONFIG_MAGIC_SYSRQ=y +CONFIG_UBSAN=y +CONFIG_UBSAN_TRAP=y +CONFIG_UBSAN_LOCAL_BOUNDS=y +# CONFIG_UBSAN_SHIFT is not set +# CONFIG_UBSAN_BOOL is not set +# CONFIG_UBSAN_ENUM is not set +CONFIG_PAGE_OWNER=y +CONFIG_PAGE_PINNER=y +CONFIG_PER_VMA_LOCK_STATS=y +CONFIG_DEBUG_STACK_USAGE=y +CONFIG_DEBUG_MEMORY_INIT=y +CONFIG_KASAN=y +CONFIG_KASAN_HW_TAGS=y +CONFIG_KFENCE=y +CONFIG_KFENCE_SAMPLE_INTERVAL=500 +CONFIG_KFENCE_NUM_OBJECTS=63 +CONFIG_KFENCE_STATIC_KEYS=y +CONFIG_PANIC_ON_OOPS=y +CONFIG_PANIC_TIMEOUT=-1 +CONFIG_SOFTLOCKUP_DETECTOR=y +CONFIG_WQ_WATCHDOG=y +CONFIG_SCHEDSTATS=y +CONFIG_BUG_ON_DATA_CORRUPTION=y +CONFIG_HIST_TRIGGERS=y +CONFIG_PID_IN_CONTEXTIDR=y +CONFIG_KUNIT=y +CONFIG_KUNIT_DEBUGFS=y +# CONFIG_KUNIT_DEFAULT_ENABLED is not set +# CONFIG_RUNTIME_TESTING_MENU is not set +CONFIG_ARCH_CLIFFS=y +CONFIG_ARCH_PINEAPPLE=y +CONFIG_ARCH_VOLCANO=y +CONFIG_ARM_QCOM_CPUFREQ_HW=m +CONFIG_ARM_QCOM_CPUFREQ_HW_DEBUG=m +# CONFIG_ARM_QCOM_CPUFREQ_NVMEM is not set +CONFIG_ARM_SMMU=m +CONFIG_ARM_SMMU_DISABLE_BYPASS_BY_DEFAULT=y +CONFIG_ARM_SMMU_QCOM=m +CONFIG_BACKLIGHT_QCOM_SPMI_WLED=m +CONFIG_CFG80211=m +# CONFIG_CFG80211_CRDA_SUPPORT is not set +# CONFIG_CHARGER_QCOM_SMBB is not set +CONFIG_CHR_DEV_SG=m +CONFIG_COMMON_CLK_QCOM=m +CONFIG_CORESIGHT=m +CONFIG_CORESIGHT_CSR=m +CONFIG_CORESIGHT_CTI=m +CONFIG_CORESIGHT_DUMMY=m +CONFIG_CORESIGHT_HWEVENT=m +CONFIG_CORESIGHT_LINKS_AND_SINKS=m +CONFIG_CORESIGHT_LINK_AND_SINK_TMC=m +CONFIG_CORESIGHT_REMOTE_ETM=m +CONFIG_CORESIGHT_STM=m +CONFIG_CORESIGHT_TGU=m +CONFIG_CORESIGHT_TPDA=m +CONFIG_CORESIGHT_TPDM=m +CONFIG_CORESIGHT_TRACE_NOC=m +CONFIG_CPU_IDLE_GOV_QCOM_LPM=m +CONFIG_CRC_ITU_T=m +CONFIG_QTI_THERMALZONE_CONFIG_DEBUG=m +CONFIG_DRM_DISPLAY_DP_HELPER=y +CONFIG_DRM_DISPLAY_HELPER=m +CONFIG_DRM_DP_AUX_BUS=m +CONFIG_DRM_LT9611UXC=m +CONFIG_DRM_MSM_HELPER=m +CONFIG_EDAC_QCOM=m +CONFIG_EDAC_QCOM_LLCC_PANIC_ON_UE=y +CONFIG_GH_ARM64_DRV=m +CONFIG_GH_CPUSYS_VM_MEM_ACCESS=m +CONFIG_GH_CTRL=m +CONFIG_GH_DBL=m +# CONFIG_GH_GUEST_POPS is not set +CONFIG_GH_IRQ_LEND=m +CONFIG_GH_MEM_NOTIFIER=m +CONFIG_GH_MSGQ=m +CONFIG_GH_PANIC_NOTIFIER=m +CONFIG_GH_PROXY_SCHED=m +CONFIG_GH_RM_BOOSTER=m +CONFIG_GH_RM_DRV=m +CONFIG_GH_SECURE_VM_LOADER=m +CONFIG_GH_TLMM_VM_MEM_ACCESS=m +CONFIG_GH_VIRT_WATCHDOG=m +CONFIG_GIC_INTERRUPT_ROUTING=m +CONFIG_GUNYAH_DRIVERS=y +CONFIG_GUNYAH_QCOM_PLATFORM=m +CONFIG_HVC_GUNYAH=m +# CONFIG_HVC_GUNYAH_CONSOLE is not set +CONFIG_HWMON=m +CONFIG_HWSPINLOCK_QCOM=m +CONFIG_I2C_MSM_GENI=m +CONFIG_I3C_MASTER_MSM_GENI=m +CONFIG_INPUT_PM8941_PWRKEY=m +# CONFIG_INPUT_PM8XXX_VIBRATOR is not set +CONFIG_INPUT_QCOM_HV_HAPTICS=m +CONFIG_INTERCONNECT_QCOM_CLIFFS=m +CONFIG_INTERCONNECT_QCOM_DEBUG=m +CONFIG_INTERCONNECT_QCOM_PINEAPPLE=m +CONFIG_INTERCONNECT_QCOM_VOLCANO=m +CONFIG_INTERCONNECT_TEST=m +CONFIG_IOMMU_IO_PGTABLE_FAST=y +CONFIG_IPA3=m +CONFIG_IPC_LOGGING=m +CONFIG_IPC_LOG_MINIDUMP_BUFFERS=16 +CONFIG_LEDS_QPNP_VIBRATOR_LDO=m +CONFIG_LEDS_QTI_FLASH=m +CONFIG_LEDS_QTI_TRI_LED=m +CONFIG_MAC80211=m +CONFIG_MAX31760_FAN_CONTROLLER=m +CONFIG_MEM_SHARE_QMI_SERVICE=m +CONFIG_MFD_I2C_PMIC=m +CONFIG_MFD_SPMI_PMIC=m +CONFIG_MHI_BUS=m +CONFIG_MHI_BUS_MISC=y +CONFIG_MHI_SATELLITE=m +CONFIG_MHI_UCI=m +# CONFIG_MINIDUMP_ALL_TASK_INFO is not set +CONFIG_MINIDUMP_MAX_ENTRIES=200 +CONFIG_MMC_SDHCI_MSM=m +# CONFIG_MODULE_SIG_ALL is not set +CONFIG_MSM_BOOT_STATS=m +CONFIG_MSM_CORE_HANG_DETECT=m +CONFIG_MSM_GPI_DMA=m +# CONFIG_MSM_GPI_DMA_DEBUG is not set +CONFIG_MSM_PERFORMANCE=m +CONFIG_MSM_QMP=m +CONFIG_MSM_RDBG=m +CONFIG_MSM_SYSSTATS=m +CONFIG_MSM_SYSSTATS_STUB_NONEXPORTED_SYMBOLS=y +CONFIG_MSM_SYSTEM_HEALTH_MONITOR=m +CONFIG_MSM_TMECOM_QMP=m +# CONFIG_NITRO_ENCLAVES is not set +CONFIG_NL80211_TESTMODE=y +CONFIG_NOP_USB_XCEIV=m +CONFIG_NVMEM_QCOM_QFPROM=m +CONFIG_NVMEM_SPMI_SDAM=m +CONFIG_PCI_MSM=m +CONFIG_PDR_INDICATION_NOTIF_TIMEOUT=9000 +CONFIG_PHY_QCOM_UFS=m +CONFIG_PHY_QCOM_UFS_QRBTC_SDM845=m +CONFIG_PHY_QCOM_UFS_V4=m +CONFIG_PHY_QCOM_UFS_V4_CROW=m +CONFIG_PHY_QCOM_UFS_V4_PINEAPPLE=m +CONFIG_PINCTRL_CLIFFS=m +CONFIG_PINCTRL_MSM=m +CONFIG_PINCTRL_PINEAPPLE=m +CONFIG_PINCTRL_QCOM_SPMI_PMIC=m +CONFIG_PINCTRL_VOLCANO=m +# CONFIG_PM8916_WATCHDOG is not set +CONFIG_POWER_RESET_QCOM_DOWNLOAD_MODE=m +CONFIG_POWER_RESET_QCOM_DOWNLOAD_MODE_DEFAULT=y +CONFIG_POWER_RESET_QCOM_PON=m +CONFIG_POWER_RESET_QCOM_REBOOT_REASON=m +CONFIG_PWM_QTI_LPG=m +CONFIG_QCOM_ADSP_SLEEPMON=m +CONFIG_QCOM_AOSS_QMP=m +CONFIG_QCOM_BALANCE_ANON_FILE_RECLAIM=y +CONFIG_QCOM_BAM_DMA=m +CONFIG_QCOM_BWMON=m +CONFIG_QCOM_BWPROF=m +CONFIG_QCOM_CDSP_RM=m +CONFIG_QCOM_CLK_RPMH=m +# CONFIG_QCOM_COINCELL is not set +CONFIG_QCOM_COMMAND_DB=m +CONFIG_QCOM_CPUCP=m +CONFIG_QCOM_CPUSS_SLEEP_STATS=m +CONFIG_QCOM_CPU_VENDOR_HOOKS=m +CONFIG_QCOM_CRM=m +CONFIG_QCOM_DCC_V2=m +CONFIG_QCOM_DCVS=m +CONFIG_QCOM_DCVS_FP=m +CONFIG_QCOM_DEBUG_SYMBOL=m +CONFIG_QCOM_DMABUF_HEAPS=m +CONFIG_QCOM_DMABUF_HEAPS_CARVEOUT=y +CONFIG_QCOM_DMABUF_HEAPS_CMA=y +CONFIG_QCOM_DMABUF_HEAPS_PAGE_POOL_REFILL=y +CONFIG_QCOM_DMABUF_HEAPS_SYSTEM=y +CONFIG_QCOM_DMABUF_HEAPS_SYSTEM_SECURE=y +CONFIG_QCOM_DMABUF_HEAPS_UBWCP=y +# CONFIG_QCOM_DYN_MINIDUMP_STACK is not set +CONFIG_QCOM_EUD=m +CONFIG_QCOM_FORCE_WDOG_BITE_ON_PANIC=y +CONFIG_QCOM_FSA4480_I2C=n +CONFIG_QCOM_GDSC_REGULATOR=m +CONFIG_QCOM_GLINK=m +CONFIG_QCOM_GLINK_PKT=m +CONFIG_QCOM_HUNG_TASK_ENH=m +CONFIG_QCOM_IOMMU_DEBUG=m +CONFIG_QCOM_IOMMU_UTIL=m +# CONFIG_QCOM_IPA is not set +CONFIG_QCOM_IPCC=m +CONFIG_QCOM_LAZY_MAPPING=m +CONFIG_QCOM_LLCC=m +CONFIG_QCOM_LLCC_PERFMON=m +CONFIG_QCOM_LLCC_PMU=m +CONFIG_QCOM_LOGBUF_BOOTLOG=m +CONFIG_QCOM_LOGBUF_VENDOR_HOOKS=m +CONFIG_QCOM_MDT_LOADER=m +CONFIG_QCOM_MEMLAT=m +CONFIG_QCOM_MEMORY_DUMP_V2=m +CONFIG_QCOM_MEM_BUF=m +CONFIG_QCOM_MEM_BUF_DEV=m +CONFIG_QCOM_MEM_BUF_DEV_GH=y +CONFIG_QCOM_MEM_BUF_GH=y +CONFIG_QCOM_MEM_BUF_MSGQ=m +CONFIG_QCOM_MEM_HOOKS=m +CONFIG_QCOM_MEM_OFFLINE=m +CONFIG_QCOM_MINIDUMP=m +CONFIG_QCOM_MINIDUMP_FTRACE=y +CONFIG_QCOM_MINIDUMP_PANIC_CPU_CONTEXT=y +CONFIG_QCOM_MINIDUMP_PANIC_DUMP=y +CONFIG_QCOM_MINIDUMP_PANIC_MEMORY_INFO=y +CONFIG_QCOM_MINIDUMP_PSTORE=y +CONFIG_QCOM_PANEL_EVENT_NOTIFIER=m +CONFIG_QCOM_PANIC_ON_NOTIF_TIMEOUT=y +CONFIG_QCOM_PANIC_ON_PDR_NOTIF_TIMEOUT=y +CONFIG_QCOM_PCIE_PDC=m +CONFIG_QCOM_PDC=m +CONFIG_QCOM_PDR_HELPERS=m +CONFIG_QCOM_PIL_INFO=m +CONFIG_QCOM_PMU_LIB=m +# CONFIG_QCOM_Q6V5_ADSP is not set +CONFIG_QCOM_Q6V5_COMMON=m +# CONFIG_QCOM_Q6V5_MSS is not set +CONFIG_QCOM_Q6V5_PAS=m +# CONFIG_QCOM_Q6V5_WCSS is not set +CONFIG_QCOM_QFPROM_SYS=m +CONFIG_QCOM_QMI_HELPERS=m +CONFIG_QCOM_RAMDUMP=m +CONFIG_QCOM_RPMH=m +CONFIG_QCOM_RPROC_BOTH_DUMPS=y +CONFIG_QCOM_RPROC_COMMON=m +# CONFIG_QCOM_RUN_QUEUE_STATS is not set +CONFIG_QCOM_SCM=m +# CONFIG_QCOM_SCM_DOWNLOAD_MODE_DEFAULT is not set +CONFIG_QCOM_SECURE_BUFFER=m +CONFIG_QCOM_SHOW_RESUME_IRQ=m +CONFIG_QCOM_SMEM=m +CONFIG_QCOM_SMP2P=m +CONFIG_QCOM_SMP2P_SLEEPSTATE=m +# CONFIG_QCOM_SMSM is not set +CONFIG_QCOM_SOCINFO=m +# CONFIG_QCOM_SOC_WATCHDOG is not set +CONFIG_QCOM_SPMI_ADC5_GEN3=m +CONFIG_QCOM_SPMI_TEMP_ALARM=m +CONFIG_QCOM_SPSS=m +CONFIG_QCOM_STATS=m +CONFIG_QCOM_SYSMON=m +CONFIG_QCOM_SYSMON_SUBSYSTEM_STATS=m +CONFIG_QCOM_TSENS=m +CONFIG_QCOM_VADC_COMMON=m +CONFIG_QCOM_VA_MINIDUMP=m +CONFIG_QCOM_VM_DMESG_DUMPER=m +CONFIG_QCOM_WATCHDOG_BARK_TIME=11000 +CONFIG_QCOM_WATCHDOG_IPI_PING=y +CONFIG_QCOM_WATCHDOG_PET_TIME=9360 +# CONFIG_QCOM_WATCHDOG_USERSPACE_PET is not set +CONFIG_QCOM_WATCHDOG_WAKEUP_ENABLE=y +CONFIG_QCOM_WCD_USBSS_I2C=m +# CONFIG_QCOM_WCNSS_PIL is not set +# CONFIG_QCOM_WDOG_BITE_EARLY_PANIC is not set +CONFIG_QCOM_WDT_CORE=m +CONFIG_QRTR=m +CONFIG_QRTR_GUNYAH=m +CONFIG_QRTR_MHI=m +CONFIG_QRTR_SMD=m +CONFIG_QSEECOM_PROXY=m +CONFIG_QSEE_IPC_IRQ_BRIDGE=m +CONFIG_QTI_ALTMODE_GLINK=m +CONFIG_QTI_BATTERY_CHARGER=m +CONFIG_QTI_BATTERY_GLINK_DEBUG=m +CONFIG_QTI_BCL_PMIC5=m +CONFIG_QTI_BCL_SOC_DRIVER=m +CONFIG_QTI_C1DCVS_SCMI_V2=m +CONFIG_QTI_CHARGER_ULOG_GLINK=m +CONFIG_QTI_CPUCP_LOG=m +CONFIG_QTI_CPUFREQ_CDEV=m +CONFIG_QTI_CPUFREQ_STATS_SCMI_V2=m +CONFIG_QTI_CPU_HOTPLUG_COOLING_DEVICE=m +CONFIG_QTI_CPU_PAUSE_COOLING_DEVICE=m +CONFIG_QTI_CPU_VOLTAGE_COOLING_DEVICE=m +CONFIG_QTI_CRYPTO_COMMON=m +# CONFIG_QTI_CRYPTO_TZ is not set +CONFIG_QTI_DDR_COOLING_DEVICE=m +CONFIG_QTI_DEVFREQ_CDEV=m +CONFIG_QTI_DYNPF_SCMI=m +CONFIG_QTI_GLINK_ADC=m +CONFIG_QTI_GPU_DUMP_SKIP_COOLING_DEVICE=m +CONFIG_QTI_HW_KEY_MANAGER=m +CONFIG_QTI_IOMMU_SUPPORT=m +CONFIG_QTI_MPAM=m +CONFIG_QTI_PMIC_EUSB2_REPEATER=m +CONFIG_QTI_PMIC_GLINK=m +# CONFIG_QTI_PMIC_GLINK_CLIENT_DEBUG is not set +CONFIG_QTI_PMIC_GLINK_DEBUG=m +CONFIG_QTI_PMIC_PON_LOG=m +CONFIG_QTI_QCOM_SCMI_CLIENT=m +CONFIG_QTI_QMI_COOLING_DEVICE=m +CONFIG_QTI_QMI_SENSOR_V2=m +CONFIG_QTI_SCMI_VENDOR_PROTOCOL=m +CONFIG_QTI_SYS_PM_VX=m +CONFIG_QTI_THERMAL_LIMITS_DCVS=m +CONFIG_QTI_THERMAL_MINIDUMP=m +CONFIG_QTI_USERSPACE_CDEV=m +CONFIG_REBOOT_MODE=m +CONFIG_REGMAP_QTI_DEBUGFS=m +# CONFIG_REGMAP_QTI_DEBUGFS_ALLOW_WRITE is not set +CONFIG_REGULATOR_DEBUG_CONTROL=m +CONFIG_REGULATOR_PROXY_CONSUMER=m +# CONFIG_REGULATOR_PROXY_CONSUMER_LEGACY is not set +CONFIG_REGULATOR_QCOM_PM8008=m +CONFIG_REGULATOR_QPNP_AMOLED=m +CONFIG_REGULATOR_QPNP_LCDB=m +CONFIG_REGULATOR_QTI_FIXED_VOLTAGE=m +CONFIG_REGULATOR_QTI_OCP_NOTIFIER=m +CONFIG_REGULATOR_RPMH=m +CONFIG_REGULATOR_STUB=m +CONFIG_RPMSG_QCOM_GLINK=m +CONFIG_RPMSG_QCOM_GLINK_SMEM=m +CONFIG_RPMSG_QCOM_GLINK_SPSS=m +CONFIG_RPMSG_QCOM_SMD=m +CONFIG_RPROC_SSR_NOTIF_TIMEOUT=20000 +CONFIG_RPROC_SYSMON_NOTIF_TIMEOUT=20000 +CONFIG_RTC_DRV_PM8XXX=m +CONFIG_SCHED_WALT=m +# CONFIG_SCHED_WALT_DEBUG is not set +CONFIG_SCSI_UFS_CRYPTO_QTI=m +CONFIG_SCSI_UFS_QCOM=m +CONFIG_SENSORS_QTI_AMOLED_ECM=m +CONFIG_SERIAL_MSM_GENI=m +CONFIG_SHOW_SUSPEND_EPOCH=m +CONFIG_SLIMBUS=m +CONFIG_SLIM_QCOM_NGD_CTRL=m +CONFIG_SM_CAMCC_CLIFFS=m +CONFIG_SM_CAMCC_PINEAPPLE=m +CONFIG_SM_CAMCC_VOLCANO=m +CONFIG_SM_DEBUGCC_CLIFFS=m +CONFIG_SM_DEBUGCC_PINEAPPLE=m +CONFIG_SM_DEBUGCC_VOLCANO=m +CONFIG_SM_DISPCC_PINEAPPLE=m +CONFIG_SM_DISPCC_VOLCANO=m +CONFIG_SM_GCC_CLIFFS=m +CONFIG_SM_GCC_PINEAPPLE=m +CONFIG_SM_GCC_VOLCANO=m +CONFIG_SM_GPUCC_CLIFFS=m +CONFIG_SM_GPUCC_PINEAPPLE=m +CONFIG_SM_GPUCC_VOLCANO=m +CONFIG_SM_TCSRCC_PINEAPPLE=m +CONFIG_SM_VIDEOCC_PINEAPPLE=m +CONFIG_SM_VIDEOCC_VOLCANO=m +CONFIG_SND_USB_AUDIO_QMI=m +CONFIG_SPI_MSM_GENI=m +CONFIG_SPMI_MSM_PMIC_ARB=m +CONFIG_SPMI_MSM_PMIC_ARB_DEBUG=m +CONFIG_SPS=m +CONFIG_SPS_SUPPORT_NDP_BAM=y +CONFIG_STM=m +CONFIG_STM_PROTO_OST=m +CONFIG_STM_SOURCE_CONSOLE=m +CONFIG_STM_SOURCE_FTRACE=m +CONFIG_STM_SOURCE_HEARTBEAT=m +CONFIG_UCSI_QTI_GLINK=m +CONFIG_UIO_MSM_SHAREDMEM=m +CONFIG_USB_CONFIGFS_F_CCID=m +CONFIG_USB_CONFIGFS_F_CDEV=m +CONFIG_USB_CONFIGFS_F_GSI=m +CONFIG_USB_CONFIGFS_F_QDSS=m +CONFIG_USB_DWC3_MSM=m +CONFIG_USB_F_CCID=m +CONFIG_USB_F_CDEV=m +CONFIG_USB_F_FS_IPC_LOGGING=m +CONFIG_USB_F_GSI=m +CONFIG_USB_F_QDSS=m +CONFIG_USB_MSM_EUSB2_PHY=m +CONFIG_USB_MSM_SSPHY_QMP=m +CONFIG_USB_QCOM_EMU_PHY=m +CONFIG_USB_REDRIVER=m +CONFIG_USB_REDRIVER_NB7VPQ904M=m +CONFIG_USB_REPEATER=m +# CONFIG_VIDEO_QCOM_VENUS is not set +# SEC_BSP / SEC_DEBUG +CONFIG_SOFT_WATCHDOG=m +CONFIG_SEC_CLASS=m +CONFIG_SEC_PARAM=m +CONFIG_SEC_KEY_NOTIFIER=m +CONFIG_SEC_RELOC_GPIO=m +CONFIG_SEC_QC_PARAM=m +CONFIG_SEC_DEBUG=m +CONFIG_SEC_BOOT_STAT=m +CONFIG_SEC_LOG_BUF=m +CONFIG_SEC_LOG_BUF_USING_TP_CONSOLE=y +CONFIG_SEC_PMSG=m +CONFIG_SEC_REBOOT_CMD=m +CONFIG_SEC_UPLOAD_CAUSE=m +CONFIG_SEC_CRASHKEY=m +CONFIG_SEC_CRASHKEY_LONG=m +CONFIG_SEC_DEBUG_REGION=m +CONFIG_SEC_RDX_BOOTDEV=m +CONFIG_SEC_ARM64_AP_CONTEXT=m +CONFIG_SEC_ARM64_FSIMD_DEBUG=m +CONFIG_SEC_ARM64_DEBUG=m +CONFIG_SEC_QC_DEBUG=m +CONFIG_SEC_QC_RBCMD=m +CONFIG_SEC_QC_DEBUG_PARTITION=m +CONFIG_SEC_QC_QCOM_REBOOT_REASON=m +CONFIG_SEC_QC_UPLOAD_CAUSE=m +CONFIG_SEC_QC_LOGGER=m +CONFIG_SEC_QC_SOC_ID=m +CONFIG_SEC_QC_SUMMARY=m +CONFIG_SEC_QC_USER_RESET=m +CONFIG_SEC_QC_HW_PARAM=m +CONFIG_SEC_QC_RST_EXINFO=m +CONFIG_SEC_QC_QCOM_WDT_CORE=m +CONFIG_SEC_QC_SMEM=m +CONFIG_I2C_GPIO=m + +#Audio +CONFIG_SND_SOC_CIRRUS_AMP=m +CONFIG_SND_SOC_CS35L43=m +CONFIG_SND_SOC_CS35L43_I2C=m +CONFIG_SND_SOC_SAMSUNG_AUDIO=m +CONFIG_SND_SOC_CS35L45=m +CONFIG_SND_SOC_CS35L45_I2C=m + +# Display +CONFIG_LCD_CLASS_DEVICE=m + +#POWER +CONFIG_SEC_PM=y +CONFIG_SEC_AP_PMIC=m +CONFIG_SEC_GPIO_DUMP=y +CONFIG_CPU_FREQ_LIMIT=m +CONFIG_RTC_AUTO_PWRON=m +CONFIG_SEC_PM_LOG=m + +#USB +CONFIG_I2C_EUSB2_REPEATER=m +CONFIG_USB_PHY_SETTING_QCOM=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +# CONFIG_SND_QC_USB_AUDIO_MODULE=m +CONFIG_USB_CONFIGFS_F_CONN_GADGET=m +CONFIG_USB_CONFIGFS_F_SS_MON_GADGET=m +CONFIG_USB_CONFIGFS_F_SS_ACM=m +CONFIG_USB_LINK_LAYER_TEST=m +CONFIG_USB_NET_SMSC75XX=m +CONFIG_USB_NET_SMSC95XX=m +CONFIG_USB_EHSET_TEST_FIXTURE=m +CONFIG_USB_HOST_SAMSUNG_FEATURE=y +CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION=y + +# Sensors +CONFIG_ADSP_FACTORY=m +CONFIG_SENSORS=m +CONFIG_LSM6DSO_FACTORY=y +CONFIG_AK09918_FACTORY=y +CONFIG_SUPPORT_LIGHT_SEAMLESS=y +CONFIG_SEC_SENSORS_SSC=y + +# block layer +CONFIG_BLK_SEC_COMMON=m +CONFIG_BLK_SEC_STATS=m +CONFIG_BLK_SEC_WB=m +CONFIG_MQ_IOSCHED_SSG=m +CONFIG_MQ_IOSCHED_SSG_CGROUP=m +CONFIG_MQ_IOSCHED_SSG_WB=m + +# UFS +CONFIG_SEC_UFS_FEATURE=y + +# SD +CONFIG_SEC_MMC_FEATURE=m + +# PCIE +CONFIG_SEC_PCIE=y +CONFIG_SEC_PCIE_AER=y +CONFIG_SEC_PCIE_L1SS=y + +# Network +CONFIG_INET6_AH=y +CONFIG_IP_NF_MATCH_AH=y +CONFIG_IP_NF_MATCH_RPFILTER=y +CONFIG_TCP_CONG_ADVANCED=y +CONFIG_TCP_CONG_BIC=y +# CONFIG_TCP_CONG_WESTWOOD is not set +# CONFIG_TCP_CONG_HTCP is not set +CONFIG_DEFAULT_BIC=y +CONFIG_DEFAULT_TCP_CONG="bic" +CONFIG_IPC_LOGGING_CDEV=m + +# Performance +CONFIG_RQ_STAT_SHOW=y +CONFIG_SCHED_POWER_OPTIMIZE=y + +CONFIG_I2C_CHARDEV=y + +CONFIG_SAMSUNG_PRODUCT_SHIP=y + + +# CONFIG_SEC_FACTORY is not set + + +# CONFIG_SEC_FACTORY_INTERPOSER is not set + +CONFIG_SEC_PANEL_NOTIFIER_V2=m +CONFIG_DRV_SAMSUNG_PMIC=m +CONFIG_CIRRUS_FIRMWARE_CL_DSP=m +CONFIG_USB_TYPEC_MANAGER_NOTIFIER=m +CONFIG_IF_CB_MANAGER=m +CONFIG_SBU_SWITCH_CONTROL=y +CONFIG_CHARGER_MAX77705=m +CONFIG_SEC_INPUT_BOOSTER=m +CONFIG_SEC_INPUT_BOOSTER_MODE=y +CONFIG_SEC_INPUT_BOOSTER_QC=y +CONFIG_SEC_INPUT_BOOSTER_HANDLER=y +CONFIG_DEV_RIL_BRIDGE=m +CONFIG_PRESSURE_FACTORY=y +CONFIG_FLIP_COVER_DETECTOR_FACTORY=y +CONFIG_LIGHT_FACTORY=y +CONFIG_PROX_FACTORY=y +CONFIG_SUPPORT_BRIGHTNESS_NOTIFY_FOR_LIGHT_SENSOR=y +CONFIG_SUPPORT_DDI_COPR_FOR_LIGHT_SENSOR=y +CONFIG_SUPPORT_LIGHT_CALIBRATION=y +CONFIG_SUPPORT_PANEL_STATE_NOTIFY_FOR_LIGHT_SENSOR=y +CONFIG_SUPPORT_PROX_CALIBRATION=y +CONFIG_LPS22DF_FACTORY=y +CONFIG_LSM6DSV_FACTORY=y +CONFIG_CHARGER_MAX77775=m +CONFIG_SHIPMODE_BY_VBAT=y +CONFIG_MUIC_NOTIFIER=m +CONFIG_HICCUP_CHARGER=y +CONFIG_MUIC_AFC_RETRY=y +CONFIG_MUIC_HV=y +CONFIG_MUIC_SUPPORT_PDIC=y +CONFIG_MUIC_USE_MODULE_PARAM=y +CONFIG_USE_MUIC=y +CONFIG_ANDROID_SWITCH=m +# CONFIG_ANDROID_SWITCH_GPIO is not set +CONFIG_SEC_UWB_LOGGER=y +CONFIG_SEC_NFC_LOGGER=y +CONFIG_SDP=m +CONFIG_VBUS_NOTIFIER=m +CONFIG_INPUT_SEC_INPUT=m +CONFIG_INPUT_SEC_NOTIFIER=m +CONFIG_SEC_DEBUG_TSP_LOG=m +CONFIG_TOUCHSCREEN_DUMP_MODE=m +CONFIG_INPUT_SEC_SECURE_TOUCH=m +CONFIG_INPUT_SEC_TRUSTED_TOUCH=m +CONFIG_INPUT_TOUCHSCREEN_TCLMV2=m +# CONFIG_SEC_INPUT_MULTI_DEVICE is not set +CONFIG_SEC_INPUT_RAWDATA=m +CONFIG_SUPPORT_DROPDUMP=m +CONFIG_REGULATOR_S2DOS05=m +CONFIG_REGULATOR_S2DOS05_ELVSS_FD=y +CONFIG_TOUCHSCREEN_STM_SPI=m +CONFIG_FUELGAUGE_MAX77705=m +CONFIG_UI_SOC_PROLONGING=y +CONFIG_MFD_MAX77705=m +CONFIG_ABC_IFPMIC_EVENT=y +CONFIG_MAX77705_FW_SEPARATION_PID_BY_MODEL=y +CONFIG_VIBRATOR_VIB_INFO=m +CONFIG_SEC_STI=y +CONFIG_KPERFMON=y +CONFIG_KPERFMON_BUILD=m +CONFIG_SEC_VIBRATOR_INPUTFF=m +CONFIG_VIB_STORE_LE_PARAM=y +# CONFIG_SEC_VIB_FOLD_MODEL is not set +CONFIG_USB_NOTIFY_LAYER=m +CONFIG_USB_DEBUG_DETAILED_LOG=y +CONFIG_USB_EXTERNAL_NOTIFY=y +CONFIG_USB_HMT_SAMSUNG_INPUT=y +CONFIG_USB_HOST_NOTIFY=y +CONFIG_USB_HOST_SAMSUNG_FEATURE=y +CONFIG_USB_HW_PARAM=y +CONFIG_USB_INTERFACE_LPM_LIST=y +CONFIG_USB_NOTIFY_PROC_LOG=y +CONFIG_USB_USING_ADVANCED_USBLOG=y +CONFIG_USB_VENDOR_NOTIFY=y +CONFIG_USB_VENDOR_RECEIVER=m +CONFIG_SENSORS_FINGERPRINT=m +CONFIG_SEC_ABC_SPEC_TYPE1=m +CONFIG_SB_CORE=m +CONFIG_SB_PQUEUE=y +CONFIG_SB_NOTIFY=y +CONFIG_SB_SYSFS=y +CONFIG_SB_VOTE=y +CONFIG_PDIC_NOTIFIER=m +CONFIG_PDIC_USE_MODULE_PARAM=y +CONFIG_USB_NOTIFIER=m +CONFIG_SEC_ABC=m +CONFIG_SEC_ABC_HUB=m +CONFIG_SEC_ABC_COMMON=m +CONFIG_SEC_ABC_HUB_CORE=m +CONFIG_SEC_ABC_HUB_BOOTC=m +CONFIG_SEC_ABC_MOTTO=m +CONFIG_FUELGAUGE_MAX77775=m +CONFIG_I2C_GPIO=m +CONFIG_INPUT_CS40L26_I2C=m +CONFIG_SND_SOC_CS40L26=m +CONFIG_CS40L26_SAMSUNG_FEATURE=y +CONFIG_CS40L26_SAMSUNG_USE_DVL=y +CONFIG_CS40L26_SAMSUNG_USE_MAX_DATA_TX_SIZE=y +CONFIG_SEC_ABC_DETECT_CONN=m +CONFIG_QCOM_SEC_ABC_DETECT=m +CONFIG_INPUT_HALL_IC=m +CONFIG_HALL_NOTIFIER=m +CONFIG_HALL_LOGICAL=m +# CONFIG_HALL_DUMP_KEY_MODE is not set +CONFIG_SENSORS_FLICKER_SELF_TEST=m +CONFIG_CCIC_MAX77705=m +CONFIG_CCIC_MAX77705_DEBUG=y +CONFIG_GET_UNMASK_VBUS_HWPARAM=y +CONFIG_MAX77705_FW_PID03_SUPPORT=y +CONFIG_MAXIM_CCIC_ALTERNATE_MODE=y +CONFIG_CHARGER_PCA9481=m +CONFIG_MFD_MAX77775=m +CONFIG_MAX77775_ABC_IFPMIC_EVENT=y +CONFIG_REGULATOR_FIXED_VOLTAGE=y +CONFIG_SENSORS_STK6D2X=m +# CONFIG_FLICKER_PWM_CALIBRATION is not set +CONFIG_AFC=y +CONFIG_HV_MUIC_MAX77705_AFC=y +CONFIG_MUIC_MAX77705=y +CONFIG_MUIC_MAX77705_PDIC=y +CONFIG_REGULATOR_S2MPB03=m +CONFIG_SEC_PM_THERMISTOR=m +CONFIG_INPUT_WACOM_WEZ02=m +CONFIG_SENSORS_QFS4008=m +CONFIG_SENSORS_FINGERPRINT_MODULE=y +CONFIG_FINGERPRINT_SECURE=y +CONFIG_SENSORS_FINGERPRINT_QCOM=y +CONFIG_STAR_K250A_LEGO=m +CONFIG_SEC_SNVM_WAKELOCK_METHOD=0 +# CONFIG_STAR_MEMORY_LEAK is not set +CONFIG_WIRELESS_CHARGER_NU1668=m +CONFIG_WIRELESS_AUTH=y +CONFIG_WIRELESS_CHARGER_HIGH_VOLTAGE=y +CONFIG_WIRELESS_TX_MODE=y +CONFIG_WIRELESS_FIRMWARE_UPDATE=y +CONFIG_WIRELESS_IC_PARAM=y +CONFIG_TX_GEAR_PHM_VOUT_CTRL=y +CONFIG_WIRELESS_RX_PHM_CTRL=y +CONFIG_SAMSUNG_NFC=m +CONFIG_NFC_NXP_COMBINED=y +CONFIG_NFC_SN2XX=y +CONFIG_NFC_SN2XX_ESE_SUPPORT=y +CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE=y +# CONFIG_ESE_USE_TZ_API is not set +CONFIG_SEC_NFC_WAKELOCK_METHOD=0 +# CONFIG_CLK_ACPM_INIT is not set +CONFIG_REGULATOR_S2MPB02=m +CONFIG_SAMSUNG_UWB=m +CONFIG_UWB_SR200=y +CONFIG_CCIC_MAX77775=m +CONFIG_MAX77775_CCIC_ALTERNATE_MODE=y +CONFIG_MAX77775_GET_UNMASK_VBUS_HWPARAM=y +CONFIG_REGULATOR_S2DOS07=m +CONFIG_SEC_DISPLAYPORT=m +CONFIG_TOUCHSCREEN_SYNAPTICS_SPI=m +CONFIG_DIRECT_CHARGING=m +CONFIG_BATTERY_SAMSUNG=m +CONFIG_SEC_PD=m +CONFIG_BATTERY_GKI=y +CONFIG_BATTERY_AGE_FORECAST=y +CONFIG_BATTERY_CISD=y +CONFIG_AFC_CHARGER_MODE=y +CONFIG_BATTERY_LOGGING=y +CONFIG_ENABLE_FULL_BY_SOC=y +CONFIG_STEP_CHARGING=y +CONFIG_SUPPORT_HV_CTRL=y +CONFIG_SUPPORT_SHIP_MODE=y +CONFIG_LEDS_S2MPB02=m +CONFIG_SENSORS_VL53L8=m +CONFIG_SENSORS_VL53L8_SUPPORT_UAPI=y +CONFIG_SENSORS_VL53L8_QCOM=y +CONFIG_SEPARATE_IO_CORE_POWER=y +CONFIG_SENSORS_VL53L8_SUPPORT_RESUME_WORK=y +CONFIG_SENSORS_LAF_FAILURE_DEBUG=y +CONFIG_HV_MUIC_MAX77775_AFC=y +CONFIG_MUIC_MAX77775=y +CONFIG_MFD_S2MPB02=m diff --git a/arch/arm64/configs/oem/pineapple_sec_defconfig b/arch/arm64/configs/oem/pineapple_sec_defconfig new file mode 100644 index 000000000000..b0a44224fde8 --- /dev/null +++ b/arch/arm64/configs/oem/pineapple_sec_defconfig @@ -0,0 +1,122 @@ +# SEC_BSP / SEC_DEBUG +CONFIG_SOFT_WATCHDOG=m +CONFIG_SEC_CLASS=m +CONFIG_SEC_PARAM=m +CONFIG_SEC_KEY_NOTIFIER=m +CONFIG_SEC_RELOC_GPIO=m +CONFIG_SEC_QC_PARAM=m +CONFIG_SEC_DEBUG=m +CONFIG_SEC_BOOT_STAT=m +CONFIG_SEC_LOG_BUF=m +CONFIG_SEC_LOG_BUF_USING_TP_CONSOLE=y +CONFIG_SEC_PMSG=m +CONFIG_SEC_REBOOT_CMD=m +CONFIG_SEC_UPLOAD_CAUSE=m +CONFIG_SEC_CRASHKEY=m +CONFIG_SEC_CRASHKEY_LONG=m +CONFIG_SEC_DEBUG_REGION=m +CONFIG_SEC_RDX_BOOTDEV=m +CONFIG_SEC_ARM64_AP_CONTEXT=m +CONFIG_SEC_ARM64_FSIMD_DEBUG=m +CONFIG_SEC_ARM64_DEBUG=m +CONFIG_SEC_QC_DEBUG=m +CONFIG_SEC_QC_RBCMD=m +CONFIG_SEC_QC_DEBUG_PARTITION=m +CONFIG_SEC_QC_QCOM_REBOOT_REASON=m +CONFIG_SEC_QC_UPLOAD_CAUSE=m +CONFIG_SEC_QC_LOGGER=m +CONFIG_SEC_QC_SOC_ID=m +CONFIG_SEC_QC_SUMMARY=m +CONFIG_SEC_QC_USER_RESET=m +CONFIG_SEC_QC_HW_PARAM=m +CONFIG_SEC_QC_RST_EXINFO=m +CONFIG_SEC_QC_QCOM_WDT_CORE=m +CONFIG_SEC_QC_SMEM=m +CONFIG_I2C_GPIO=m + +#Audio +CONFIG_SND_SOC_CIRRUS_AMP=m +CONFIG_SND_SOC_CS35L43=m +CONFIG_SND_SOC_CS35L43_I2C=m +CONFIG_SND_SOC_SAMSUNG_AUDIO=m +CONFIG_SND_SOC_CS35L45=m +CONFIG_SND_SOC_CS35L45_I2C=m + +# Display +CONFIG_LCD_CLASS_DEVICE=m + +#POWER +CONFIG_SEC_PM=y +CONFIG_SEC_AP_PMIC=m +CONFIG_SEC_GPIO_DUMP=y +CONFIG_CPU_FREQ_LIMIT=m +CONFIG_RTC_AUTO_PWRON=m +CONFIG_SEC_PM_LOG=m + +#USB +CONFIG_I2C_EUSB2_REPEATER=m +CONFIG_USB_PHY_SETTING_QCOM=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +# CONFIG_SND_QC_USB_AUDIO_MODULE=m +CONFIG_USB_CONFIGFS_F_CONN_GADGET=m +CONFIG_USB_CONFIGFS_F_SS_MON_GADGET=m +CONFIG_USB_CONFIGFS_F_SS_ACM=m +CONFIG_USB_LINK_LAYER_TEST=m +CONFIG_USB_NET_SMSC75XX=m +CONFIG_USB_NET_SMSC95XX=m +CONFIG_USB_EHSET_TEST_FIXTURE=m +CONFIG_USB_HOST_SAMSUNG_FEATURE=y +CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION=y + +# Sensors +CONFIG_ADSP_FACTORY=m +CONFIG_SENSORS=m +CONFIG_LSM6DSO_FACTORY=y +CONFIG_AK09918_FACTORY=y +CONFIG_SUPPORT_LIGHT_SEAMLESS=y +CONFIG_SEC_SENSORS_SSC=y + +# block layer +CONFIG_BLK_SEC_COMMON=m +CONFIG_BLK_SEC_STATS=m +CONFIG_BLK_SEC_WB=m +CONFIG_MQ_IOSCHED_SSG=m +CONFIG_MQ_IOSCHED_SSG_CGROUP=m +CONFIG_MQ_IOSCHED_SSG_WB=m + +# UFS +CONFIG_SEC_UFS_FEATURE=y + +# SD +CONFIG_SEC_MMC_FEATURE=m + +# PCIE +CONFIG_SEC_PCIE=y +CONFIG_SEC_PCIE_AER=y +CONFIG_SEC_PCIE_L1SS=y + +# Network +CONFIG_INET6_AH=y +CONFIG_IP_NF_MATCH_AH=y +CONFIG_IP_NF_MATCH_RPFILTER=y +CONFIG_TCP_CONG_ADVANCED=y +CONFIG_TCP_CONG_BIC=y +# CONFIG_TCP_CONG_WESTWOOD is not set +# CONFIG_TCP_CONG_HTCP is not set +CONFIG_DEFAULT_BIC=y +CONFIG_DEFAULT_TCP_CONG="bic" +CONFIG_IPC_LOGGING_CDEV=m + +# Performance +CONFIG_RQ_STAT_SHOW=y +CONFIG_SCHED_POWER_OPTIMIZE=y + +CONFIG_I2C_CHARDEV=y + +CONFIG_SAMSUNG_PRODUCT_SHIP=y + + +# CONFIG_SEC_FACTORY is not set + + +# CONFIG_SEC_FACTORY_INTERPOSER is not set diff --git a/arch/arm64/configs/oem/pineapple_sec_eng_defconfig b/arch/arm64/configs/oem/pineapple_sec_eng_defconfig new file mode 100644 index 000000000000..83f48845653b --- /dev/null +++ b/arch/arm64/configs/oem/pineapple_sec_eng_defconfig @@ -0,0 +1,10 @@ +CONFIG_SEC_FORCE_ERR=y +CONFIG_SEC_RELOC_GPIO_EN=y +CONFIG_SEC_QC_SOC_ID_EN=y +CONFIG_USB_PHY_TUNING_QCOM=y +CONFIG_SEC_PCIE_DEV=y +CONFIG_SEC_GPIO_DVS=m +CONFIG_SEC_CDSP_NO_CRASH_FOR_ENG=y + +# Battery driver +CONFIG_ENG_BATTERY_CONCEPT=y diff --git a/arch/arm64/configs/oem/pineapple_sec_userdebug_defconfig b/arch/arm64/configs/oem/pineapple_sec_userdebug_defconfig new file mode 100644 index 000000000000..d8ce3860e284 --- /dev/null +++ b/arch/arm64/configs/oem/pineapple_sec_userdebug_defconfig @@ -0,0 +1,7 @@ +CONFIG_SEC_FORCE_ERR=y +CONFIG_SEC_RELOC_GPIO_EN=y +CONFIG_SEC_QC_SOC_ID_EN=y +CONFIG_USB_PHY_TUNING_QCOM=y + +# Battery driver +CONFIG_ENG_BATTERY_CONCEPT=y diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile index 3e38b269487a..1678acda12a8 100644 --- a/arch/arm64/kernel/Makefile +++ b/arch/arm64/kernel/Makefile @@ -100,5 +100,7 @@ ifeq ($(CONFIG_DEBUG_EFI),y) AFLAGS_head.o += -DVMLINUX_PATH="\"$(realpath $(objtree)/vmlinux)\"" endif +obj-$(CONFIG_UH) += uh_entry.o + # for cleaning subdir- += vdso vdso32 diff --git a/arch/arm64/kernel/perf_event.c b/arch/arm64/kernel/perf_event.c new file mode 100644 index 000000000000..7b0643fe2f13 --- /dev/null +++ b/arch/arm64/kernel/perf_event.c @@ -0,0 +1,1461 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ARMv8 PMUv3 Performance Events handling code. + * + * Copyright (C) 2012 ARM Limited + * Author: Will Deacon + * + * This code is based heavily on the ARMv7 perf event code. + */ + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/* ARMv8 Cortex-A53 specific event types. */ +#define ARMV8_A53_PERFCTR_PREF_LINEFILL 0xC2 + +/* ARMv8 Cavium ThunderX specific event types. */ +#define ARMV8_THUNDER_PERFCTR_L1D_CACHE_MISS_ST 0xE9 +#define ARMV8_THUNDER_PERFCTR_L1D_CACHE_PREF_ACCESS 0xEA +#define ARMV8_THUNDER_PERFCTR_L1D_CACHE_PREF_MISS 0xEB +#define ARMV8_THUNDER_PERFCTR_L1I_CACHE_PREF_ACCESS 0xEC +#define ARMV8_THUNDER_PERFCTR_L1I_CACHE_PREF_MISS 0xED + +/* + * ARMv8 Architectural defined events, not all of these may + * be supported on any given implementation. Unsupported events will + * be disabled at run-time based on the PMCEID registers. + */ +static const unsigned armv8_pmuv3_perf_map[PERF_COUNT_HW_MAX] = { + PERF_MAP_ALL_UNSUPPORTED, + [PERF_COUNT_HW_CPU_CYCLES] = ARMV8_PMUV3_PERFCTR_CPU_CYCLES, + [PERF_COUNT_HW_INSTRUCTIONS] = ARMV8_PMUV3_PERFCTR_INST_RETIRED, + [PERF_COUNT_HW_CACHE_REFERENCES] = ARMV8_PMUV3_PERFCTR_L1D_CACHE, + [PERF_COUNT_HW_CACHE_MISSES] = ARMV8_PMUV3_PERFCTR_L1D_CACHE_REFILL, + [PERF_COUNT_HW_BRANCH_INSTRUCTIONS] = ARMV8_PMUV3_PERFCTR_PC_WRITE_RETIRED, + [PERF_COUNT_HW_BRANCH_MISSES] = ARMV8_PMUV3_PERFCTR_BR_MIS_PRED, + [PERF_COUNT_HW_BUS_CYCLES] = ARMV8_PMUV3_PERFCTR_BUS_CYCLES, + [PERF_COUNT_HW_STALLED_CYCLES_FRONTEND] = ARMV8_PMUV3_PERFCTR_STALL_FRONTEND, + [PERF_COUNT_HW_STALLED_CYCLES_BACKEND] = ARMV8_PMUV3_PERFCTR_STALL_BACKEND, +}; + +static const unsigned armv8_pmuv3_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] + [PERF_COUNT_HW_CACHE_OP_MAX] + [PERF_COUNT_HW_CACHE_RESULT_MAX] = { + PERF_CACHE_MAP_ALL_UNSUPPORTED, + + [C(L1D)][C(OP_READ)][C(RESULT_ACCESS)] = ARMV8_PMUV3_PERFCTR_L1D_CACHE, + [C(L1D)][C(OP_READ)][C(RESULT_MISS)] = ARMV8_PMUV3_PERFCTR_L1D_CACHE_REFILL, + + [C(L1I)][C(OP_READ)][C(RESULT_ACCESS)] = ARMV8_PMUV3_PERFCTR_L1I_CACHE, + [C(L1I)][C(OP_READ)][C(RESULT_MISS)] = ARMV8_PMUV3_PERFCTR_L1I_CACHE_REFILL, + + [C(DTLB)][C(OP_READ)][C(RESULT_MISS)] = ARMV8_PMUV3_PERFCTR_L1D_TLB_REFILL, + [C(DTLB)][C(OP_READ)][C(RESULT_ACCESS)] = ARMV8_PMUV3_PERFCTR_L1D_TLB, + + [C(ITLB)][C(OP_READ)][C(RESULT_MISS)] = ARMV8_PMUV3_PERFCTR_L1I_TLB_REFILL, + [C(ITLB)][C(OP_READ)][C(RESULT_ACCESS)] = ARMV8_PMUV3_PERFCTR_L1I_TLB, + + [C(LL)][C(OP_READ)][C(RESULT_MISS)] = ARMV8_PMUV3_PERFCTR_LL_CACHE_MISS_RD, + [C(LL)][C(OP_READ)][C(RESULT_ACCESS)] = ARMV8_PMUV3_PERFCTR_LL_CACHE_RD, + + [C(BPU)][C(OP_READ)][C(RESULT_ACCESS)] = ARMV8_PMUV3_PERFCTR_BR_PRED, + [C(BPU)][C(OP_READ)][C(RESULT_MISS)] = ARMV8_PMUV3_PERFCTR_BR_MIS_PRED, +}; + +static const unsigned armv8_a53_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] + [PERF_COUNT_HW_CACHE_OP_MAX] + [PERF_COUNT_HW_CACHE_RESULT_MAX] = { + PERF_CACHE_MAP_ALL_UNSUPPORTED, + + [C(L1D)][C(OP_PREFETCH)][C(RESULT_MISS)] = ARMV8_A53_PERFCTR_PREF_LINEFILL, + + [C(NODE)][C(OP_READ)][C(RESULT_ACCESS)] = ARMV8_IMPDEF_PERFCTR_BUS_ACCESS_RD, + [C(NODE)][C(OP_WRITE)][C(RESULT_ACCESS)] = ARMV8_IMPDEF_PERFCTR_BUS_ACCESS_WR, +}; + +static const unsigned armv8_a57_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] + [PERF_COUNT_HW_CACHE_OP_MAX] + [PERF_COUNT_HW_CACHE_RESULT_MAX] = { + PERF_CACHE_MAP_ALL_UNSUPPORTED, + + [C(L1D)][C(OP_READ)][C(RESULT_ACCESS)] = ARMV8_IMPDEF_PERFCTR_L1D_CACHE_RD, + [C(L1D)][C(OP_READ)][C(RESULT_MISS)] = ARMV8_IMPDEF_PERFCTR_L1D_CACHE_REFILL_RD, + [C(L1D)][C(OP_WRITE)][C(RESULT_ACCESS)] = ARMV8_IMPDEF_PERFCTR_L1D_CACHE_WR, + [C(L1D)][C(OP_WRITE)][C(RESULT_MISS)] = ARMV8_IMPDEF_PERFCTR_L1D_CACHE_REFILL_WR, + + [C(DTLB)][C(OP_READ)][C(RESULT_MISS)] = ARMV8_IMPDEF_PERFCTR_L1D_TLB_REFILL_RD, + [C(DTLB)][C(OP_WRITE)][C(RESULT_MISS)] = ARMV8_IMPDEF_PERFCTR_L1D_TLB_REFILL_WR, + + [C(NODE)][C(OP_READ)][C(RESULT_ACCESS)] = ARMV8_IMPDEF_PERFCTR_BUS_ACCESS_RD, + [C(NODE)][C(OP_WRITE)][C(RESULT_ACCESS)] = ARMV8_IMPDEF_PERFCTR_BUS_ACCESS_WR, +}; + +static const unsigned armv8_a73_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] + [PERF_COUNT_HW_CACHE_OP_MAX] + [PERF_COUNT_HW_CACHE_RESULT_MAX] = { + PERF_CACHE_MAP_ALL_UNSUPPORTED, + + [C(L1D)][C(OP_READ)][C(RESULT_ACCESS)] = ARMV8_IMPDEF_PERFCTR_L1D_CACHE_RD, + [C(L1D)][C(OP_WRITE)][C(RESULT_ACCESS)] = ARMV8_IMPDEF_PERFCTR_L1D_CACHE_WR, +}; + +static const unsigned armv8_thunder_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] + [PERF_COUNT_HW_CACHE_OP_MAX] + [PERF_COUNT_HW_CACHE_RESULT_MAX] = { + PERF_CACHE_MAP_ALL_UNSUPPORTED, + + [C(L1D)][C(OP_READ)][C(RESULT_ACCESS)] = ARMV8_IMPDEF_PERFCTR_L1D_CACHE_RD, + [C(L1D)][C(OP_READ)][C(RESULT_MISS)] = ARMV8_IMPDEF_PERFCTR_L1D_CACHE_REFILL_RD, + [C(L1D)][C(OP_WRITE)][C(RESULT_ACCESS)] = ARMV8_IMPDEF_PERFCTR_L1D_CACHE_WR, + [C(L1D)][C(OP_WRITE)][C(RESULT_MISS)] = ARMV8_THUNDER_PERFCTR_L1D_CACHE_MISS_ST, + [C(L1D)][C(OP_PREFETCH)][C(RESULT_ACCESS)] = ARMV8_THUNDER_PERFCTR_L1D_CACHE_PREF_ACCESS, + [C(L1D)][C(OP_PREFETCH)][C(RESULT_MISS)] = ARMV8_THUNDER_PERFCTR_L1D_CACHE_PREF_MISS, + + [C(L1I)][C(OP_PREFETCH)][C(RESULT_ACCESS)] = ARMV8_THUNDER_PERFCTR_L1I_CACHE_PREF_ACCESS, + [C(L1I)][C(OP_PREFETCH)][C(RESULT_MISS)] = ARMV8_THUNDER_PERFCTR_L1I_CACHE_PREF_MISS, + + [C(DTLB)][C(OP_READ)][C(RESULT_ACCESS)] = ARMV8_IMPDEF_PERFCTR_L1D_TLB_RD, + [C(DTLB)][C(OP_READ)][C(RESULT_MISS)] = ARMV8_IMPDEF_PERFCTR_L1D_TLB_REFILL_RD, + [C(DTLB)][C(OP_WRITE)][C(RESULT_ACCESS)] = ARMV8_IMPDEF_PERFCTR_L1D_TLB_WR, + [C(DTLB)][C(OP_WRITE)][C(RESULT_MISS)] = ARMV8_IMPDEF_PERFCTR_L1D_TLB_REFILL_WR, +}; + +static const unsigned armv8_vulcan_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] + [PERF_COUNT_HW_CACHE_OP_MAX] + [PERF_COUNT_HW_CACHE_RESULT_MAX] = { + PERF_CACHE_MAP_ALL_UNSUPPORTED, + + [C(L1D)][C(OP_READ)][C(RESULT_ACCESS)] = ARMV8_IMPDEF_PERFCTR_L1D_CACHE_RD, + [C(L1D)][C(OP_READ)][C(RESULT_MISS)] = ARMV8_IMPDEF_PERFCTR_L1D_CACHE_REFILL_RD, + [C(L1D)][C(OP_WRITE)][C(RESULT_ACCESS)] = ARMV8_IMPDEF_PERFCTR_L1D_CACHE_WR, + [C(L1D)][C(OP_WRITE)][C(RESULT_MISS)] = ARMV8_IMPDEF_PERFCTR_L1D_CACHE_REFILL_WR, + + [C(DTLB)][C(OP_READ)][C(RESULT_ACCESS)] = ARMV8_IMPDEF_PERFCTR_L1D_TLB_RD, + [C(DTLB)][C(OP_WRITE)][C(RESULT_ACCESS)] = ARMV8_IMPDEF_PERFCTR_L1D_TLB_WR, + [C(DTLB)][C(OP_READ)][C(RESULT_MISS)] = ARMV8_IMPDEF_PERFCTR_L1D_TLB_REFILL_RD, + [C(DTLB)][C(OP_WRITE)][C(RESULT_MISS)] = ARMV8_IMPDEF_PERFCTR_L1D_TLB_REFILL_WR, + + [C(NODE)][C(OP_READ)][C(RESULT_ACCESS)] = ARMV8_IMPDEF_PERFCTR_BUS_ACCESS_RD, + [C(NODE)][C(OP_WRITE)][C(RESULT_ACCESS)] = ARMV8_IMPDEF_PERFCTR_BUS_ACCESS_WR, +}; + +static ssize_t +armv8pmu_events_sysfs_show(struct device *dev, + struct device_attribute *attr, char *page) +{ + struct perf_pmu_events_attr *pmu_attr; + + pmu_attr = container_of(attr, struct perf_pmu_events_attr, attr); + + return sprintf(page, "event=0x%04llx\n", pmu_attr->id); +} + +#define ARMV8_EVENT_ATTR(name, config) \ + PMU_EVENT_ATTR_ID(name, armv8pmu_events_sysfs_show, config) + +static struct attribute *armv8_pmuv3_event_attrs[] = { + ARMV8_EVENT_ATTR(sw_incr, ARMV8_PMUV3_PERFCTR_SW_INCR), + ARMV8_EVENT_ATTR(l1i_cache_refill, ARMV8_PMUV3_PERFCTR_L1I_CACHE_REFILL), + ARMV8_EVENT_ATTR(l1i_tlb_refill, ARMV8_PMUV3_PERFCTR_L1I_TLB_REFILL), + ARMV8_EVENT_ATTR(l1d_cache_refill, ARMV8_PMUV3_PERFCTR_L1D_CACHE_REFILL), + ARMV8_EVENT_ATTR(l1d_cache, ARMV8_PMUV3_PERFCTR_L1D_CACHE), + ARMV8_EVENT_ATTR(l1d_tlb_refill, ARMV8_PMUV3_PERFCTR_L1D_TLB_REFILL), + ARMV8_EVENT_ATTR(ld_retired, ARMV8_PMUV3_PERFCTR_LD_RETIRED), + ARMV8_EVENT_ATTR(st_retired, ARMV8_PMUV3_PERFCTR_ST_RETIRED), + ARMV8_EVENT_ATTR(inst_retired, ARMV8_PMUV3_PERFCTR_INST_RETIRED), + ARMV8_EVENT_ATTR(exc_taken, ARMV8_PMUV3_PERFCTR_EXC_TAKEN), + ARMV8_EVENT_ATTR(exc_return, ARMV8_PMUV3_PERFCTR_EXC_RETURN), + ARMV8_EVENT_ATTR(cid_write_retired, ARMV8_PMUV3_PERFCTR_CID_WRITE_RETIRED), + ARMV8_EVENT_ATTR(pc_write_retired, ARMV8_PMUV3_PERFCTR_PC_WRITE_RETIRED), + ARMV8_EVENT_ATTR(br_immed_retired, ARMV8_PMUV3_PERFCTR_BR_IMMED_RETIRED), + ARMV8_EVENT_ATTR(br_return_retired, ARMV8_PMUV3_PERFCTR_BR_RETURN_RETIRED), + ARMV8_EVENT_ATTR(unaligned_ldst_retired, ARMV8_PMUV3_PERFCTR_UNALIGNED_LDST_RETIRED), + ARMV8_EVENT_ATTR(br_mis_pred, ARMV8_PMUV3_PERFCTR_BR_MIS_PRED), + ARMV8_EVENT_ATTR(cpu_cycles, ARMV8_PMUV3_PERFCTR_CPU_CYCLES), + ARMV8_EVENT_ATTR(br_pred, ARMV8_PMUV3_PERFCTR_BR_PRED), + ARMV8_EVENT_ATTR(mem_access, ARMV8_PMUV3_PERFCTR_MEM_ACCESS), + ARMV8_EVENT_ATTR(l1i_cache, ARMV8_PMUV3_PERFCTR_L1I_CACHE), + ARMV8_EVENT_ATTR(l1d_cache_wb, ARMV8_PMUV3_PERFCTR_L1D_CACHE_WB), + ARMV8_EVENT_ATTR(l2d_cache, ARMV8_PMUV3_PERFCTR_L2D_CACHE), + ARMV8_EVENT_ATTR(l2d_cache_refill, ARMV8_PMUV3_PERFCTR_L2D_CACHE_REFILL), + ARMV8_EVENT_ATTR(l2d_cache_wb, ARMV8_PMUV3_PERFCTR_L2D_CACHE_WB), + ARMV8_EVENT_ATTR(bus_access, ARMV8_PMUV3_PERFCTR_BUS_ACCESS), + ARMV8_EVENT_ATTR(memory_error, ARMV8_PMUV3_PERFCTR_MEMORY_ERROR), + ARMV8_EVENT_ATTR(inst_spec, ARMV8_PMUV3_PERFCTR_INST_SPEC), + ARMV8_EVENT_ATTR(ttbr_write_retired, ARMV8_PMUV3_PERFCTR_TTBR_WRITE_RETIRED), + ARMV8_EVENT_ATTR(bus_cycles, ARMV8_PMUV3_PERFCTR_BUS_CYCLES), + /* Don't expose the chain event in /sys, since it's useless in isolation */ + ARMV8_EVENT_ATTR(l1d_cache_allocate, ARMV8_PMUV3_PERFCTR_L1D_CACHE_ALLOCATE), + ARMV8_EVENT_ATTR(l2d_cache_allocate, ARMV8_PMUV3_PERFCTR_L2D_CACHE_ALLOCATE), + ARMV8_EVENT_ATTR(br_retired, ARMV8_PMUV3_PERFCTR_BR_RETIRED), + ARMV8_EVENT_ATTR(br_mis_pred_retired, ARMV8_PMUV3_PERFCTR_BR_MIS_PRED_RETIRED), + ARMV8_EVENT_ATTR(stall_frontend, ARMV8_PMUV3_PERFCTR_STALL_FRONTEND), + ARMV8_EVENT_ATTR(stall_backend, ARMV8_PMUV3_PERFCTR_STALL_BACKEND), + ARMV8_EVENT_ATTR(l1d_tlb, ARMV8_PMUV3_PERFCTR_L1D_TLB), + ARMV8_EVENT_ATTR(l1i_tlb, ARMV8_PMUV3_PERFCTR_L1I_TLB), + ARMV8_EVENT_ATTR(l2i_cache, ARMV8_PMUV3_PERFCTR_L2I_CACHE), + ARMV8_EVENT_ATTR(l2i_cache_refill, ARMV8_PMUV3_PERFCTR_L2I_CACHE_REFILL), + ARMV8_EVENT_ATTR(l3d_cache_allocate, ARMV8_PMUV3_PERFCTR_L3D_CACHE_ALLOCATE), + ARMV8_EVENT_ATTR(l3d_cache_refill, ARMV8_PMUV3_PERFCTR_L3D_CACHE_REFILL), + ARMV8_EVENT_ATTR(l3d_cache, ARMV8_PMUV3_PERFCTR_L3D_CACHE), + ARMV8_EVENT_ATTR(l3d_cache_wb, ARMV8_PMUV3_PERFCTR_L3D_CACHE_WB), + ARMV8_EVENT_ATTR(l2d_tlb_refill, ARMV8_PMUV3_PERFCTR_L2D_TLB_REFILL), + ARMV8_EVENT_ATTR(l2i_tlb_refill, ARMV8_PMUV3_PERFCTR_L2I_TLB_REFILL), + ARMV8_EVENT_ATTR(l2d_tlb, ARMV8_PMUV3_PERFCTR_L2D_TLB), + ARMV8_EVENT_ATTR(l2i_tlb, ARMV8_PMUV3_PERFCTR_L2I_TLB), + ARMV8_EVENT_ATTR(remote_access, ARMV8_PMUV3_PERFCTR_REMOTE_ACCESS), + ARMV8_EVENT_ATTR(ll_cache, ARMV8_PMUV3_PERFCTR_LL_CACHE), + ARMV8_EVENT_ATTR(ll_cache_miss, ARMV8_PMUV3_PERFCTR_LL_CACHE_MISS), + ARMV8_EVENT_ATTR(dtlb_walk, ARMV8_PMUV3_PERFCTR_DTLB_WALK), + ARMV8_EVENT_ATTR(itlb_walk, ARMV8_PMUV3_PERFCTR_ITLB_WALK), + ARMV8_EVENT_ATTR(ll_cache_rd, ARMV8_PMUV3_PERFCTR_LL_CACHE_RD), + ARMV8_EVENT_ATTR(ll_cache_miss_rd, ARMV8_PMUV3_PERFCTR_LL_CACHE_MISS_RD), + ARMV8_EVENT_ATTR(remote_access_rd, ARMV8_PMUV3_PERFCTR_REMOTE_ACCESS_RD), + ARMV8_EVENT_ATTR(l1d_cache_lmiss_rd, ARMV8_PMUV3_PERFCTR_L1D_CACHE_LMISS_RD), + ARMV8_EVENT_ATTR(op_retired, ARMV8_PMUV3_PERFCTR_OP_RETIRED), + ARMV8_EVENT_ATTR(op_spec, ARMV8_PMUV3_PERFCTR_OP_SPEC), + ARMV8_EVENT_ATTR(stall, ARMV8_PMUV3_PERFCTR_STALL), + ARMV8_EVENT_ATTR(stall_slot_backend, ARMV8_PMUV3_PERFCTR_STALL_SLOT_BACKEND), + ARMV8_EVENT_ATTR(stall_slot_frontend, ARMV8_PMUV3_PERFCTR_STALL_SLOT_FRONTEND), + ARMV8_EVENT_ATTR(stall_slot, ARMV8_PMUV3_PERFCTR_STALL_SLOT), + ARMV8_EVENT_ATTR(sample_pop, ARMV8_SPE_PERFCTR_SAMPLE_POP), + ARMV8_EVENT_ATTR(sample_feed, ARMV8_SPE_PERFCTR_SAMPLE_FEED), + ARMV8_EVENT_ATTR(sample_filtrate, ARMV8_SPE_PERFCTR_SAMPLE_FILTRATE), + ARMV8_EVENT_ATTR(sample_collision, ARMV8_SPE_PERFCTR_SAMPLE_COLLISION), + ARMV8_EVENT_ATTR(cnt_cycles, ARMV8_AMU_PERFCTR_CNT_CYCLES), + ARMV8_EVENT_ATTR(stall_backend_mem, ARMV8_AMU_PERFCTR_STALL_BACKEND_MEM), + ARMV8_EVENT_ATTR(l1i_cache_lmiss, ARMV8_PMUV3_PERFCTR_L1I_CACHE_LMISS), + ARMV8_EVENT_ATTR(l2d_cache_lmiss_rd, ARMV8_PMUV3_PERFCTR_L2D_CACHE_LMISS_RD), + ARMV8_EVENT_ATTR(l2i_cache_lmiss, ARMV8_PMUV3_PERFCTR_L2I_CACHE_LMISS), + ARMV8_EVENT_ATTR(l3d_cache_lmiss_rd, ARMV8_PMUV3_PERFCTR_L3D_CACHE_LMISS_RD), + ARMV8_EVENT_ATTR(trb_wrap, ARMV8_PMUV3_PERFCTR_TRB_WRAP), + ARMV8_EVENT_ATTR(trb_trig, ARMV8_PMUV3_PERFCTR_TRB_TRIG), + ARMV8_EVENT_ATTR(trcextout0, ARMV8_PMUV3_PERFCTR_TRCEXTOUT0), + ARMV8_EVENT_ATTR(trcextout1, ARMV8_PMUV3_PERFCTR_TRCEXTOUT1), + ARMV8_EVENT_ATTR(trcextout2, ARMV8_PMUV3_PERFCTR_TRCEXTOUT2), + ARMV8_EVENT_ATTR(trcextout3, ARMV8_PMUV3_PERFCTR_TRCEXTOUT3), + ARMV8_EVENT_ATTR(cti_trigout4, ARMV8_PMUV3_PERFCTR_CTI_TRIGOUT4), + ARMV8_EVENT_ATTR(cti_trigout5, ARMV8_PMUV3_PERFCTR_CTI_TRIGOUT5), + ARMV8_EVENT_ATTR(cti_trigout6, ARMV8_PMUV3_PERFCTR_CTI_TRIGOUT6), + ARMV8_EVENT_ATTR(cti_trigout7, ARMV8_PMUV3_PERFCTR_CTI_TRIGOUT7), + ARMV8_EVENT_ATTR(ldst_align_lat, ARMV8_PMUV3_PERFCTR_LDST_ALIGN_LAT), + ARMV8_EVENT_ATTR(ld_align_lat, ARMV8_PMUV3_PERFCTR_LD_ALIGN_LAT), + ARMV8_EVENT_ATTR(st_align_lat, ARMV8_PMUV3_PERFCTR_ST_ALIGN_LAT), + ARMV8_EVENT_ATTR(mem_access_checked, ARMV8_MTE_PERFCTR_MEM_ACCESS_CHECKED), + ARMV8_EVENT_ATTR(mem_access_checked_rd, ARMV8_MTE_PERFCTR_MEM_ACCESS_CHECKED_RD), + ARMV8_EVENT_ATTR(mem_access_checked_wr, ARMV8_MTE_PERFCTR_MEM_ACCESS_CHECKED_WR), + NULL, +}; + +static umode_t +armv8pmu_event_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int unused) +{ + struct device *dev = kobj_to_dev(kobj); + struct pmu *pmu = dev_get_drvdata(dev); + struct arm_pmu *cpu_pmu = container_of(pmu, struct arm_pmu, pmu); + struct perf_pmu_events_attr *pmu_attr; + + pmu_attr = container_of(attr, struct perf_pmu_events_attr, attr.attr); + + if (pmu_attr->id < ARMV8_PMUV3_MAX_COMMON_EVENTS && + test_bit(pmu_attr->id, cpu_pmu->pmceid_bitmap)) + return attr->mode; + + if (pmu_attr->id >= ARMV8_PMUV3_EXT_COMMON_EVENT_BASE) { + u64 id = pmu_attr->id - ARMV8_PMUV3_EXT_COMMON_EVENT_BASE; + + if (id < ARMV8_PMUV3_MAX_COMMON_EVENTS && + test_bit(id, cpu_pmu->pmceid_ext_bitmap)) + return attr->mode; + } + + return 0; +} + +static const struct attribute_group armv8_pmuv3_events_attr_group = { + .name = "events", + .attrs = armv8_pmuv3_event_attrs, + .is_visible = armv8pmu_event_attr_is_visible, +}; + +PMU_FORMAT_ATTR(event, "config:0-15"); +PMU_FORMAT_ATTR(long, "config1:0"); +PMU_FORMAT_ATTR(rdpmc, "config1:1"); + +static int sysctl_perf_user_access __read_mostly; + +static inline bool armv8pmu_event_is_64bit(struct perf_event *event) +{ + return event->attr.config1 & 0x1; +} + +static inline bool armv8pmu_event_want_user_access(struct perf_event *event) +{ + return event->attr.config1 & 0x2; +} + +static struct attribute *armv8_pmuv3_format_attrs[] = { + &format_attr_event.attr, + &format_attr_long.attr, + &format_attr_rdpmc.attr, + NULL, +}; + +static const struct attribute_group armv8_pmuv3_format_attr_group = { + .name = "format", + .attrs = armv8_pmuv3_format_attrs, +}; + +static ssize_t slots_show(struct device *dev, struct device_attribute *attr, + char *page) +{ + struct pmu *pmu = dev_get_drvdata(dev); + struct arm_pmu *cpu_pmu = container_of(pmu, struct arm_pmu, pmu); + u32 slots = cpu_pmu->reg_pmmir & ARMV8_PMU_SLOTS_MASK; + + return sysfs_emit(page, "0x%08x\n", slots); +} + +static DEVICE_ATTR_RO(slots); + +static ssize_t bus_slots_show(struct device *dev, struct device_attribute *attr, + char *page) +{ + struct pmu *pmu = dev_get_drvdata(dev); + struct arm_pmu *cpu_pmu = container_of(pmu, struct arm_pmu, pmu); + u32 bus_slots = (cpu_pmu->reg_pmmir >> ARMV8_PMU_BUS_SLOTS_SHIFT) + & ARMV8_PMU_BUS_SLOTS_MASK; + + return sysfs_emit(page, "0x%08x\n", bus_slots); +} + +static DEVICE_ATTR_RO(bus_slots); + +static ssize_t bus_width_show(struct device *dev, struct device_attribute *attr, + char *page) +{ + struct pmu *pmu = dev_get_drvdata(dev); + struct arm_pmu *cpu_pmu = container_of(pmu, struct arm_pmu, pmu); + u32 bus_width = (cpu_pmu->reg_pmmir >> ARMV8_PMU_BUS_WIDTH_SHIFT) + & ARMV8_PMU_BUS_WIDTH_MASK; + u32 val = 0; + + /* Encoded as Log2(number of bytes), plus one */ + if (bus_width > 2 && bus_width < 13) + val = 1 << (bus_width - 1); + + return sysfs_emit(page, "0x%08x\n", val); +} + +static DEVICE_ATTR_RO(bus_width); + +static struct attribute *armv8_pmuv3_caps_attrs[] = { + &dev_attr_slots.attr, + &dev_attr_bus_slots.attr, + &dev_attr_bus_width.attr, + NULL, +}; + +static const struct attribute_group armv8_pmuv3_caps_attr_group = { + .name = "caps", + .attrs = armv8_pmuv3_caps_attrs, +}; + +/* + * Perf Events' indices + */ +#define ARMV8_IDX_CYCLE_COUNTER 0 +#define ARMV8_IDX_COUNTER0 1 +#define ARMV8_IDX_CYCLE_COUNTER_USER 32 + +/* + * We unconditionally enable ARMv8.5-PMU long event counter support + * (64-bit events) where supported. Indicate if this arm_pmu has long + * event counter support. + */ +static bool armv8pmu_has_long_event(struct arm_pmu *cpu_pmu) +{ + return (cpu_pmu->pmuver >= ID_AA64DFR0_EL1_PMUVer_V3P5); +} + +static inline bool armv8pmu_event_has_user_read(struct perf_event *event) +{ + return event->hw.flags & PERF_EVENT_FLAG_USER_READ_CNT; +} + +/* + * We must chain two programmable counters for 64 bit events, + * except when we have allocated the 64bit cycle counter (for CPU + * cycles event) or when user space counter access is enabled. + */ +static inline bool armv8pmu_event_is_chained(struct perf_event *event) +{ + int idx = event->hw.idx; + struct arm_pmu *cpu_pmu = to_arm_pmu(event->pmu); + + return !armv8pmu_event_has_user_read(event) && + armv8pmu_event_is_64bit(event) && + !armv8pmu_has_long_event(cpu_pmu) && + (idx != ARMV8_IDX_CYCLE_COUNTER); +} + +/* + * ARMv8 low level PMU access + */ + +/* + * Perf Event to low level counters mapping + */ +#define ARMV8_IDX_TO_COUNTER(x) \ + (((x) - ARMV8_IDX_COUNTER0) & ARMV8_PMU_COUNTER_MASK) + +/* + * This code is really good + */ + +#define PMEVN_CASE(n, case_macro) \ + case n: case_macro(n); break + +#define PMEVN_SWITCH(x, case_macro) \ + do { \ + switch (x) { \ + PMEVN_CASE(0, case_macro); \ + PMEVN_CASE(1, case_macro); \ + PMEVN_CASE(2, case_macro); \ + PMEVN_CASE(3, case_macro); \ + PMEVN_CASE(4, case_macro); \ + PMEVN_CASE(5, case_macro); \ + PMEVN_CASE(6, case_macro); \ + PMEVN_CASE(7, case_macro); \ + PMEVN_CASE(8, case_macro); \ + PMEVN_CASE(9, case_macro); \ + PMEVN_CASE(10, case_macro); \ + PMEVN_CASE(11, case_macro); \ + PMEVN_CASE(12, case_macro); \ + PMEVN_CASE(13, case_macro); \ + PMEVN_CASE(14, case_macro); \ + PMEVN_CASE(15, case_macro); \ + PMEVN_CASE(16, case_macro); \ + PMEVN_CASE(17, case_macro); \ + PMEVN_CASE(18, case_macro); \ + PMEVN_CASE(19, case_macro); \ + PMEVN_CASE(20, case_macro); \ + PMEVN_CASE(21, case_macro); \ + PMEVN_CASE(22, case_macro); \ + PMEVN_CASE(23, case_macro); \ + PMEVN_CASE(24, case_macro); \ + PMEVN_CASE(25, case_macro); \ + PMEVN_CASE(26, case_macro); \ + PMEVN_CASE(27, case_macro); \ + PMEVN_CASE(28, case_macro); \ + PMEVN_CASE(29, case_macro); \ + PMEVN_CASE(30, case_macro); \ + default: WARN(1, "Invalid PMEV* index\n"); \ + } \ + } while (0) + +#define RETURN_READ_PMEVCNTRN(n) \ + return read_sysreg(pmevcntr##n##_el0) +static unsigned long read_pmevcntrn(int n) +{ + PMEVN_SWITCH(n, RETURN_READ_PMEVCNTRN); + return 0; +} + +#define WRITE_PMEVCNTRN(n) \ + write_sysreg(val, pmevcntr##n##_el0) +static void write_pmevcntrn(int n, unsigned long val) +{ + PMEVN_SWITCH(n, WRITE_PMEVCNTRN); +} + +#define WRITE_PMEVTYPERN(n) \ + write_sysreg(val, pmevtyper##n##_el0) +static void write_pmevtypern(int n, unsigned long val) +{ + PMEVN_SWITCH(n, WRITE_PMEVTYPERN); +} + +static inline u32 armv8pmu_pmcr_read(void) +{ + return read_sysreg(pmcr_el0); +} + +static inline void armv8pmu_pmcr_write(u32 val) +{ + val &= ARMV8_PMU_PMCR_MASK; + isb(); + write_sysreg(val, pmcr_el0); +} + +static inline int armv8pmu_has_overflowed(u32 pmovsr) +{ + return pmovsr & ARMV8_PMU_OVERFLOWED_MASK; +} + +static inline int armv8pmu_counter_has_overflowed(u32 pmnc, int idx) +{ + return pmnc & BIT(ARMV8_IDX_TO_COUNTER(idx)); +} + +static inline u64 armv8pmu_read_evcntr(int idx) +{ + u32 counter = ARMV8_IDX_TO_COUNTER(idx); + + return read_pmevcntrn(counter); +} + +static inline u64 armv8pmu_read_hw_counter(struct perf_event *event) +{ + int idx = event->hw.idx; + u64 val = armv8pmu_read_evcntr(idx); + + if (armv8pmu_event_is_chained(event)) + val = (val << 32) | armv8pmu_read_evcntr(idx - 1); + return val; +} + +/* + * The cycle counter is always a 64-bit counter. When ARMV8_PMU_PMCR_LP + * is set the event counters also become 64-bit counters. Unless the + * user has requested a long counter (attr.config1) then we want to + * interrupt upon 32-bit overflow - we achieve this by applying a bias. + */ +static bool armv8pmu_event_needs_bias(struct perf_event *event) +{ + struct arm_pmu *cpu_pmu = to_arm_pmu(event->pmu); + struct hw_perf_event *hwc = &event->hw; + int idx = hwc->idx; + + if (armv8pmu_event_is_64bit(event)) + return false; + + if (armv8pmu_has_long_event(cpu_pmu) || + idx == ARMV8_IDX_CYCLE_COUNTER) + return true; + + return false; +} + +static u64 armv8pmu_bias_long_counter(struct perf_event *event, u64 value) +{ + if (armv8pmu_event_needs_bias(event)) + value |= GENMASK(63, 32); + + return value; +} + +static u64 armv8pmu_unbias_long_counter(struct perf_event *event, u64 value) +{ + if (armv8pmu_event_needs_bias(event)) + value &= ~GENMASK(63, 32); + + return value; +} + +static u64 armv8pmu_read_counter(struct perf_event *event) +{ + struct hw_perf_event *hwc = &event->hw; + int idx = hwc->idx; + u64 value; + + if (idx == ARMV8_IDX_CYCLE_COUNTER) + value = read_sysreg(pmccntr_el0); + else + value = armv8pmu_read_hw_counter(event); + + return armv8pmu_unbias_long_counter(event, value); +} + +static inline void armv8pmu_write_evcntr(int idx, u64 value) +{ + u32 counter = ARMV8_IDX_TO_COUNTER(idx); + + write_pmevcntrn(counter, value); +} + +static inline void armv8pmu_write_hw_counter(struct perf_event *event, + u64 value) +{ + int idx = event->hw.idx; + + if (armv8pmu_event_is_chained(event)) { + armv8pmu_write_evcntr(idx, upper_32_bits(value)); + armv8pmu_write_evcntr(idx - 1, lower_32_bits(value)); + } else { + armv8pmu_write_evcntr(idx, value); + } +} + +static void armv8pmu_write_counter(struct perf_event *event, u64 value) +{ + struct hw_perf_event *hwc = &event->hw; + int idx = hwc->idx; + + value = armv8pmu_bias_long_counter(event, value); + + if (idx == ARMV8_IDX_CYCLE_COUNTER) + write_sysreg(value, pmccntr_el0); + else + armv8pmu_write_hw_counter(event, value); +} + +static inline void armv8pmu_write_evtype(int idx, u32 val) +{ + u32 counter = ARMV8_IDX_TO_COUNTER(idx); + + val &= ARMV8_PMU_EVTYPE_MASK; + write_pmevtypern(counter, val); +} + +static inline void armv8pmu_write_event_type(struct perf_event *event) +{ + struct hw_perf_event *hwc = &event->hw; + int idx = hwc->idx; + + /* + * For chained events, the low counter is programmed to count + * the event of interest and the high counter is programmed + * with CHAIN event code with filters set to count at all ELs. + */ + if (armv8pmu_event_is_chained(event)) { + u32 chain_evt = ARMV8_PMUV3_PERFCTR_CHAIN | + ARMV8_PMU_INCLUDE_EL2; + + armv8pmu_write_evtype(idx - 1, hwc->config_base); + armv8pmu_write_evtype(idx, chain_evt); + } else { + if (idx == ARMV8_IDX_CYCLE_COUNTER) + write_sysreg(hwc->config_base, pmccfiltr_el0); + else + armv8pmu_write_evtype(idx, hwc->config_base); + } +} + +static u32 armv8pmu_event_cnten_mask(struct perf_event *event) +{ + int counter = ARMV8_IDX_TO_COUNTER(event->hw.idx); + u32 mask = BIT(counter); + + if (armv8pmu_event_is_chained(event)) + mask |= BIT(counter - 1); + return mask; +} + +static inline void armv8pmu_enable_counter(u32 mask) +{ + /* + * Make sure event configuration register writes are visible before we + * enable the counter. + * */ + isb(); + write_sysreg(mask, pmcntenset_el0); +} + +static inline void armv8pmu_enable_event_counter(struct perf_event *event) +{ + struct perf_event_attr *attr = &event->attr; + u32 mask = armv8pmu_event_cnten_mask(event); + + kvm_set_pmu_events(mask, attr); + + /* We rely on the hypervisor switch code to enable guest counters */ + if (!kvm_pmu_counter_deferred(attr)) + armv8pmu_enable_counter(mask); +} + +static inline void armv8pmu_disable_counter(u32 mask) +{ + write_sysreg(mask, pmcntenclr_el0); + /* + * Make sure the effects of disabling the counter are visible before we + * start configuring the event. + */ + isb(); +} + +static inline void armv8pmu_disable_event_counter(struct perf_event *event) +{ + struct perf_event_attr *attr = &event->attr; + u32 mask = armv8pmu_event_cnten_mask(event); + + kvm_clr_pmu_events(mask); + + /* We rely on the hypervisor switch code to disable guest counters */ + if (!kvm_pmu_counter_deferred(attr)) + armv8pmu_disable_counter(mask); +} + +static inline void armv8pmu_enable_intens(u32 mask) +{ + write_sysreg(mask, pmintenset_el1); +} + +static inline void armv8pmu_enable_event_irq(struct perf_event *event) +{ + u32 counter = ARMV8_IDX_TO_COUNTER(event->hw.idx); + armv8pmu_enable_intens(BIT(counter)); +} + +static inline void armv8pmu_disable_intens(u32 mask) +{ + write_sysreg(mask, pmintenclr_el1); + isb(); + /* Clear the overflow flag in case an interrupt is pending. */ + write_sysreg(mask, pmovsclr_el0); + isb(); +} + +static inline void armv8pmu_disable_event_irq(struct perf_event *event) +{ + u32 counter = ARMV8_IDX_TO_COUNTER(event->hw.idx); + armv8pmu_disable_intens(BIT(counter)); +} + +static inline u32 armv8pmu_getreset_flags(void) +{ + u32 value; + + /* Read */ + value = read_sysreg(pmovsclr_el0); + + /* Write to clear flags */ + value &= ARMV8_PMU_OVSR_MASK; + write_sysreg(value, pmovsclr_el0); + + return value; +} + +static void armv8pmu_disable_user_access(void) +{ + write_sysreg(0, pmuserenr_el0); +} + +static void armv8pmu_enable_user_access(struct arm_pmu *cpu_pmu) +{ + int i; + struct pmu_hw_events *cpuc = this_cpu_ptr(cpu_pmu->hw_events); + + /* Clear any unused counters to avoid leaking their contents */ + for_each_clear_bit(i, cpuc->used_mask, cpu_pmu->num_events) { + if (i == ARMV8_IDX_CYCLE_COUNTER) + write_sysreg(0, pmccntr_el0); + else + armv8pmu_write_evcntr(i, 0); + } + + write_sysreg(0, pmuserenr_el0); + write_sysreg(ARMV8_PMU_USERENR_ER | ARMV8_PMU_USERENR_CR, pmuserenr_el0); +} + +static void armv8pmu_enable_event(struct perf_event *event) +{ + /* + * Enable counter and interrupt, and set the counter to count + * the event that we're interested in. + */ + + /* + * Disable counter + */ + armv8pmu_disable_event_counter(event); + + /* + * Set event. + */ + armv8pmu_write_event_type(event); + + /* + * Enable interrupt for this counter + */ + armv8pmu_enable_event_irq(event); + + /* + * Enable counter + */ + armv8pmu_enable_event_counter(event); +} + +static void armv8pmu_disable_event(struct perf_event *event) +{ + /* + * Disable counter + */ + armv8pmu_disable_event_counter(event); + + /* + * Disable interrupt for this counter + */ + armv8pmu_disable_event_irq(event); +} + +static void armv8pmu_start(struct arm_pmu *cpu_pmu) +{ + struct perf_event_context *task_ctx = + this_cpu_ptr(cpu_pmu->pmu.pmu_cpu_context)->task_ctx; + + if (sysctl_perf_user_access && task_ctx && task_ctx->nr_user) + armv8pmu_enable_user_access(cpu_pmu); + else + armv8pmu_disable_user_access(); + + /* Enable all counters */ + armv8pmu_pmcr_write(armv8pmu_pmcr_read() | ARMV8_PMU_PMCR_E); +} + +static void armv8pmu_stop(struct arm_pmu *cpu_pmu) +{ + /* Disable all counters */ + armv8pmu_pmcr_write(armv8pmu_pmcr_read() & ~ARMV8_PMU_PMCR_E); +} + +static irqreturn_t armv8pmu_handle_irq(struct arm_pmu *cpu_pmu) +{ + u32 pmovsr; + struct perf_sample_data data; + struct pmu_hw_events *cpuc = this_cpu_ptr(cpu_pmu->hw_events); + struct pt_regs *regs; + int idx; + + /* + * Get and reset the IRQ flags + */ + pmovsr = armv8pmu_getreset_flags(); + + /* + * Did an overflow occur? + */ + if (!armv8pmu_has_overflowed(pmovsr)) + return IRQ_NONE; + + /* + * Handle the counter(s) overflow(s) + */ + regs = get_irq_regs(); + + /* + * Stop the PMU while processing the counter overflows + * to prevent skews in group events. + */ + armv8pmu_stop(cpu_pmu); + for (idx = 0; idx < cpu_pmu->num_events; ++idx) { + struct perf_event *event = cpuc->events[idx]; + struct hw_perf_event *hwc; + + /* Ignore if we don't have an event. */ + if (!event) + continue; + + /* + * We have a single interrupt for all counters. Check that + * each counter has overflowed before we process it. + */ + if (!armv8pmu_counter_has_overflowed(pmovsr, idx)) + continue; + + hwc = &event->hw; + armpmu_event_update(event); + perf_sample_data_init(&data, 0, hwc->last_period); + if (!armpmu_event_set_period(event)) + continue; + + /* + * Perf event overflow will queue the processing of the event as + * an irq_work which will be taken care of in the handling of + * IPI_IRQ_WORK. + */ + if (perf_event_overflow(event, &data, regs)) + cpu_pmu->disable(event); + } + armv8pmu_start(cpu_pmu); + + return IRQ_HANDLED; +} + +static int armv8pmu_get_single_idx(struct pmu_hw_events *cpuc, + struct arm_pmu *cpu_pmu) +{ + int idx; + + for (idx = ARMV8_IDX_COUNTER0; idx < cpu_pmu->num_events; idx++) { + if (!test_and_set_bit(idx, cpuc->used_mask)) + return idx; + } + return -EAGAIN; +} + +static int armv8pmu_get_chain_idx(struct pmu_hw_events *cpuc, + struct arm_pmu *cpu_pmu) +{ + int idx; + + /* + * Chaining requires two consecutive event counters, where + * the lower idx must be even. + */ + for (idx = ARMV8_IDX_COUNTER0 + 1; idx < cpu_pmu->num_events; idx += 2) { + if (!test_and_set_bit(idx, cpuc->used_mask)) { + /* Check if the preceding even counter is available */ + if (!test_and_set_bit(idx - 1, cpuc->used_mask)) + return idx; + /* Release the Odd counter */ + clear_bit(idx, cpuc->used_mask); + } + } + return -EAGAIN; +} + +static int armv8pmu_get_event_idx(struct pmu_hw_events *cpuc, + struct perf_event *event) +{ + struct arm_pmu *cpu_pmu = to_arm_pmu(event->pmu); + struct hw_perf_event *hwc = &event->hw; + unsigned long evtype = hwc->config_base & ARMV8_PMU_EVTYPE_EVENT; + + /* Always prefer to place a cycle counter into the cycle counter. */ + if (evtype == ARMV8_PMUV3_PERFCTR_CPU_CYCLES) { + if (!test_and_set_bit(ARMV8_IDX_CYCLE_COUNTER, cpuc->used_mask)) + return ARMV8_IDX_CYCLE_COUNTER; + else if (armv8pmu_event_is_64bit(event) && + armv8pmu_event_want_user_access(event) && + !armv8pmu_has_long_event(cpu_pmu)) + return -EAGAIN; + } + + /* + * Otherwise use events counters + */ + if (armv8pmu_event_is_chained(event)) + return armv8pmu_get_chain_idx(cpuc, cpu_pmu); + else + return armv8pmu_get_single_idx(cpuc, cpu_pmu); +} + +static void armv8pmu_clear_event_idx(struct pmu_hw_events *cpuc, + struct perf_event *event) +{ + int idx = event->hw.idx; + + clear_bit(idx, cpuc->used_mask); + if (armv8pmu_event_is_chained(event)) + clear_bit(idx - 1, cpuc->used_mask); +} + +static int armv8pmu_user_event_idx(struct perf_event *event) +{ + if (!sysctl_perf_user_access || !armv8pmu_event_has_user_read(event)) + return 0; + + /* + * We remap the cycle counter index to 32 to + * match the offset applied to the rest of + * the counter indices. + */ + if (event->hw.idx == ARMV8_IDX_CYCLE_COUNTER) + return ARMV8_IDX_CYCLE_COUNTER_USER; + + return event->hw.idx; +} + +/* + * Add an event filter to a given event. + */ +static int armv8pmu_set_event_filter(struct hw_perf_event *event, + struct perf_event_attr *attr) +{ + unsigned long config_base = 0; + + if (attr->exclude_idle) + return -EPERM; + + /* + * If we're running in hyp mode, then we *are* the hypervisor. + * Therefore we ignore exclude_hv in this configuration, since + * there's no hypervisor to sample anyway. This is consistent + * with other architectures (x86 and Power). + */ + if (is_kernel_in_hyp_mode()) { + if (!attr->exclude_kernel && !attr->exclude_host) + config_base |= ARMV8_PMU_INCLUDE_EL2; + if (attr->exclude_guest) + config_base |= ARMV8_PMU_EXCLUDE_EL1; + if (attr->exclude_host) + config_base |= ARMV8_PMU_EXCLUDE_EL0; + } else { + if (!attr->exclude_hv && !attr->exclude_host) + config_base |= ARMV8_PMU_INCLUDE_EL2; + } + + /* + * Filter out !VHE kernels and guest kernels + */ + if (attr->exclude_kernel) + config_base |= ARMV8_PMU_EXCLUDE_EL1; + + if (attr->exclude_user) + config_base |= ARMV8_PMU_EXCLUDE_EL0; + + /* + * Install the filter into config_base as this is used to + * construct the event type. + */ + event->config_base = config_base; + + return 0; +} + +static int armv8pmu_filter_match(struct perf_event *event) +{ + unsigned long evtype = event->hw.config_base & ARMV8_PMU_EVTYPE_EVENT; + return evtype != ARMV8_PMUV3_PERFCTR_CHAIN; +} + +static void armv8pmu_reset(void *info) +{ + struct arm_pmu *cpu_pmu = (struct arm_pmu *)info; + u32 pmcr; + + /* The counter and interrupt enable registers are unknown at reset. */ + armv8pmu_disable_counter(U32_MAX); + armv8pmu_disable_intens(U32_MAX); + + /* Clear the counters we flip at guest entry/exit */ + kvm_clr_pmu_events(U32_MAX); + + /* + * Initialize & Reset PMNC. Request overflow interrupt for + * 64 bit cycle counter but cheat in armv8pmu_write_counter(). + */ + pmcr = ARMV8_PMU_PMCR_P | ARMV8_PMU_PMCR_C | ARMV8_PMU_PMCR_LC; + + /* Enable long event counter support where available */ + if (armv8pmu_has_long_event(cpu_pmu)) + pmcr |= ARMV8_PMU_PMCR_LP; + + armv8pmu_pmcr_write(pmcr); +} + +static int __armv8_pmuv3_map_event(struct perf_event *event, + const unsigned (*extra_event_map) + [PERF_COUNT_HW_MAX], + const unsigned (*extra_cache_map) + [PERF_COUNT_HW_CACHE_MAX] + [PERF_COUNT_HW_CACHE_OP_MAX] + [PERF_COUNT_HW_CACHE_RESULT_MAX]) +{ + int hw_event_id; + struct arm_pmu *armpmu = to_arm_pmu(event->pmu); + + hw_event_id = armpmu_map_event(event, &armv8_pmuv3_perf_map, + &armv8_pmuv3_perf_cache_map, + ARMV8_PMU_EVTYPE_EVENT); + + if (armv8pmu_event_is_64bit(event)) + event->hw.flags |= ARMPMU_EVT_64BIT; + + /* + * User events must be allocated into a single counter, and so + * must not be chained. + * + * Most 64-bit events require long counter support, but 64-bit + * CPU_CYCLES events can be placed into the dedicated cycle + * counter when this is free. + */ + if (armv8pmu_event_want_user_access(event)) { + if (!(event->attach_state & PERF_ATTACH_TASK)) + return -EINVAL; + if (armv8pmu_event_is_64bit(event) && + (hw_event_id != ARMV8_PMUV3_PERFCTR_CPU_CYCLES) && + !armv8pmu_has_long_event(armpmu)) + return -EOPNOTSUPP; + + event->hw.flags |= PERF_EVENT_FLAG_USER_READ_CNT; + } + + /* Only expose micro/arch events supported by this PMU */ + if ((hw_event_id > 0) && (hw_event_id < ARMV8_PMUV3_MAX_COMMON_EVENTS) + && test_bit(hw_event_id, armpmu->pmceid_bitmap)) { + return hw_event_id; + } + + return armpmu_map_event(event, extra_event_map, extra_cache_map, + ARMV8_PMU_EVTYPE_EVENT); +} + +static int armv8_pmuv3_map_event(struct perf_event *event) +{ + return __armv8_pmuv3_map_event(event, NULL, NULL); +} + +static int armv8_a53_map_event(struct perf_event *event) +{ + return __armv8_pmuv3_map_event(event, NULL, &armv8_a53_perf_cache_map); +} + +static int armv8_a57_map_event(struct perf_event *event) +{ + return __armv8_pmuv3_map_event(event, NULL, &armv8_a57_perf_cache_map); +} + +static int armv8_a73_map_event(struct perf_event *event) +{ + return __armv8_pmuv3_map_event(event, NULL, &armv8_a73_perf_cache_map); +} + +static int armv8_thunder_map_event(struct perf_event *event) +{ + return __armv8_pmuv3_map_event(event, NULL, + &armv8_thunder_perf_cache_map); +} + +static int armv8_vulcan_map_event(struct perf_event *event) +{ + return __armv8_pmuv3_map_event(event, NULL, + &armv8_vulcan_perf_cache_map); +} + +struct armv8pmu_probe_info { + struct arm_pmu *pmu; + bool present; +}; + +static void __armv8pmu_probe_pmu(void *info) +{ + struct armv8pmu_probe_info *probe = info; + struct arm_pmu *cpu_pmu = probe->pmu; + u64 dfr0; + u64 pmceid_raw[2]; + u32 pmceid[2]; + int pmuver; + + dfr0 = read_sysreg(id_aa64dfr0_el1); + pmuver = cpuid_feature_extract_unsigned_field(dfr0, + ID_AA64DFR0_EL1_PMUVer_SHIFT); + if (pmuver == ID_AA64DFR0_EL1_PMUVer_IMP_DEF || pmuver == 0) + return; + + cpu_pmu->pmuver = pmuver; + probe->present = true; + + /* Read the nb of CNTx counters supported from PMNC */ + cpu_pmu->num_events = (armv8pmu_pmcr_read() >> ARMV8_PMU_PMCR_N_SHIFT) + & ARMV8_PMU_PMCR_N_MASK; + + /* Add the CPU cycles counter */ + cpu_pmu->num_events += 1; + + pmceid[0] = pmceid_raw[0] = read_sysreg(pmceid0_el0); + pmceid[1] = pmceid_raw[1] = read_sysreg(pmceid1_el0); + + bitmap_from_arr32(cpu_pmu->pmceid_bitmap, + pmceid, ARMV8_PMUV3_MAX_COMMON_EVENTS); + + pmceid[0] = pmceid_raw[0] >> 32; + pmceid[1] = pmceid_raw[1] >> 32; + + bitmap_from_arr32(cpu_pmu->pmceid_ext_bitmap, + pmceid, ARMV8_PMUV3_MAX_COMMON_EVENTS); + + /* store PMMIR_EL1 register for sysfs */ + if (pmuver >= ID_AA64DFR0_EL1_PMUVer_V3P4 && (pmceid_raw[1] & BIT(31))) + cpu_pmu->reg_pmmir = read_cpuid(PMMIR_EL1); + else + cpu_pmu->reg_pmmir = 0; +} + +static int armv8pmu_probe_pmu(struct arm_pmu *cpu_pmu) +{ + struct armv8pmu_probe_info probe = { + .pmu = cpu_pmu, + .present = false, + }; + int ret; + + ret = smp_call_function_any(&cpu_pmu->supported_cpus, + __armv8pmu_probe_pmu, + &probe, 1); + if (ret) + return ret; + + return probe.present ? 0 : -ENODEV; +} + +static void armv8pmu_disable_user_access_ipi(void *unused) +{ + armv8pmu_disable_user_access(); +} + +static int armv8pmu_proc_user_access_handler(struct ctl_table *table, int write, + void *buffer, size_t *lenp, loff_t *ppos) +{ + int ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos); + if (ret || !write || sysctl_perf_user_access) + return ret; + + on_each_cpu(armv8pmu_disable_user_access_ipi, NULL, 1); + return 0; +} + +static struct ctl_table armv8_pmu_sysctl_table[] = { + { + .procname = "perf_user_access", + .data = &sysctl_perf_user_access, + .maxlen = sizeof(unsigned int), + .mode = 0644, + .proc_handler = armv8pmu_proc_user_access_handler, + .extra1 = SYSCTL_ZERO, + .extra2 = SYSCTL_ONE, + }, + { } +}; + +static void armv8_pmu_register_sysctl_table(void) +{ + static u32 tbl_registered = 0; + + if (!cmpxchg_relaxed(&tbl_registered, 0, 1)) + register_sysctl("kernel", armv8_pmu_sysctl_table); +} + +static int armv8_pmu_init(struct arm_pmu *cpu_pmu, char *name, + int (*map_event)(struct perf_event *event), + const struct attribute_group *events, + const struct attribute_group *format, + const struct attribute_group *caps) +{ + int ret = armv8pmu_probe_pmu(cpu_pmu); + if (ret) + return ret; + + cpu_pmu->handle_irq = armv8pmu_handle_irq; + cpu_pmu->enable = armv8pmu_enable_event; + cpu_pmu->disable = armv8pmu_disable_event; + cpu_pmu->read_counter = armv8pmu_read_counter; + cpu_pmu->write_counter = armv8pmu_write_counter; + cpu_pmu->get_event_idx = armv8pmu_get_event_idx; + cpu_pmu->clear_event_idx = armv8pmu_clear_event_idx; + cpu_pmu->start = armv8pmu_start; + cpu_pmu->stop = armv8pmu_stop; + cpu_pmu->reset = armv8pmu_reset; + cpu_pmu->set_event_filter = armv8pmu_set_event_filter; + cpu_pmu->filter_match = armv8pmu_filter_match; + + cpu_pmu->pmu.event_idx = armv8pmu_user_event_idx; + + cpu_pmu->name = name; + cpu_pmu->map_event = map_event; + cpu_pmu->attr_groups[ARMPMU_ATTR_GROUP_EVENTS] = events ? + events : &armv8_pmuv3_events_attr_group; + cpu_pmu->attr_groups[ARMPMU_ATTR_GROUP_FORMATS] = format ? + format : &armv8_pmuv3_format_attr_group; + cpu_pmu->attr_groups[ARMPMU_ATTR_GROUP_CAPS] = caps ? + caps : &armv8_pmuv3_caps_attr_group; + + armv8_pmu_register_sysctl_table(); + return 0; +} + +static int armv8_pmu_init_nogroups(struct arm_pmu *cpu_pmu, char *name, + int (*map_event)(struct perf_event *event)) +{ + return armv8_pmu_init(cpu_pmu, name, map_event, NULL, NULL, NULL); +} + +#define PMUV3_INIT_SIMPLE(name) \ +static int name##_pmu_init(struct arm_pmu *cpu_pmu) \ +{ \ + return armv8_pmu_init_nogroups(cpu_pmu, #name, armv8_pmuv3_map_event);\ +} + +PMUV3_INIT_SIMPLE(armv8_pmuv3) + +PMUV3_INIT_SIMPLE(armv8_cortex_a34) +PMUV3_INIT_SIMPLE(armv8_cortex_a55) +PMUV3_INIT_SIMPLE(armv8_cortex_a65) +PMUV3_INIT_SIMPLE(armv8_cortex_a75) +PMUV3_INIT_SIMPLE(armv8_cortex_a76) +PMUV3_INIT_SIMPLE(armv8_cortex_a77) +PMUV3_INIT_SIMPLE(armv8_cortex_a78) +PMUV3_INIT_SIMPLE(armv9_cortex_a510) +PMUV3_INIT_SIMPLE(armv9_cortex_a710) +PMUV3_INIT_SIMPLE(armv8_cortex_x1) +PMUV3_INIT_SIMPLE(armv9_cortex_x2) +PMUV3_INIT_SIMPLE(armv8_neoverse_e1) +PMUV3_INIT_SIMPLE(armv8_neoverse_n1) +PMUV3_INIT_SIMPLE(armv9_neoverse_n2) +PMUV3_INIT_SIMPLE(armv8_neoverse_v1) + +PMUV3_INIT_SIMPLE(armv8_nvidia_carmel) +PMUV3_INIT_SIMPLE(armv8_nvidia_denver) + +static int armv8_a35_pmu_init(struct arm_pmu *cpu_pmu) +{ + return armv8_pmu_init_nogroups(cpu_pmu, "armv8_cortex_a35", + armv8_a53_map_event); +} + +static int armv8_a53_pmu_init(struct arm_pmu *cpu_pmu) +{ + return armv8_pmu_init_nogroups(cpu_pmu, "armv8_cortex_a53", + armv8_a53_map_event); +} + +static int armv8_a57_pmu_init(struct arm_pmu *cpu_pmu) +{ + return armv8_pmu_init_nogroups(cpu_pmu, "armv8_cortex_a57", + armv8_a57_map_event); +} + +static int armv8_a72_pmu_init(struct arm_pmu *cpu_pmu) +{ + return armv8_pmu_init_nogroups(cpu_pmu, "armv8_cortex_a72", + armv8_a57_map_event); +} + +static int armv8_a73_pmu_init(struct arm_pmu *cpu_pmu) +{ + return armv8_pmu_init_nogroups(cpu_pmu, "armv8_cortex_a73", + armv8_a73_map_event); +} + +static int armv8_thunder_pmu_init(struct arm_pmu *cpu_pmu) +{ + return armv8_pmu_init_nogroups(cpu_pmu, "armv8_cavium_thunder", + armv8_thunder_map_event); +} + +static int armv8_vulcan_pmu_init(struct arm_pmu *cpu_pmu) +{ + return armv8_pmu_init_nogroups(cpu_pmu, "armv8_brcm_vulcan", + armv8_vulcan_map_event); +} + +static const struct of_device_id armv8_pmu_of_device_ids[] = { + {.compatible = "arm,armv8-pmuv3", .data = armv8_pmuv3_pmu_init}, + {.compatible = "arm,cortex-a34-pmu", .data = armv8_cortex_a34_pmu_init}, + {.compatible = "arm,cortex-a35-pmu", .data = armv8_a35_pmu_init}, + {.compatible = "arm,cortex-a53-pmu", .data = armv8_a53_pmu_init}, + {.compatible = "arm,cortex-a55-pmu", .data = armv8_cortex_a55_pmu_init}, + {.compatible = "arm,cortex-a57-pmu", .data = armv8_a57_pmu_init}, + {.compatible = "arm,cortex-a65-pmu", .data = armv8_cortex_a65_pmu_init}, + {.compatible = "arm,cortex-a72-pmu", .data = armv8_a72_pmu_init}, + {.compatible = "arm,cortex-a73-pmu", .data = armv8_a73_pmu_init}, + {.compatible = "arm,cortex-a75-pmu", .data = armv8_cortex_a75_pmu_init}, + {.compatible = "arm,cortex-a76-pmu", .data = armv8_cortex_a76_pmu_init}, + {.compatible = "arm,cortex-a77-pmu", .data = armv8_cortex_a77_pmu_init}, + {.compatible = "arm,cortex-a78-pmu", .data = armv8_cortex_a78_pmu_init}, + {.compatible = "arm,cortex-a510-pmu", .data = armv9_cortex_a510_pmu_init}, + {.compatible = "arm,cortex-a710-pmu", .data = armv9_cortex_a710_pmu_init}, + {.compatible = "arm,cortex-x1-pmu", .data = armv8_cortex_x1_pmu_init}, + {.compatible = "arm,cortex-x2-pmu", .data = armv9_cortex_x2_pmu_init}, + {.compatible = "arm,neoverse-e1-pmu", .data = armv8_neoverse_e1_pmu_init}, + {.compatible = "arm,neoverse-n1-pmu", .data = armv8_neoverse_n1_pmu_init}, + {.compatible = "arm,neoverse-n2-pmu", .data = armv9_neoverse_n2_pmu_init}, + {.compatible = "arm,neoverse-v1-pmu", .data = armv8_neoverse_v1_pmu_init}, + {.compatible = "cavium,thunder-pmu", .data = armv8_thunder_pmu_init}, + {.compatible = "brcm,vulcan-pmu", .data = armv8_vulcan_pmu_init}, + {.compatible = "nvidia,carmel-pmu", .data = armv8_nvidia_carmel_pmu_init}, + {.compatible = "nvidia,denver-pmu", .data = armv8_nvidia_denver_pmu_init}, + {}, +}; + +static int armv8_pmu_device_probe(struct platform_device *pdev) +{ + return arm_pmu_device_probe(pdev, armv8_pmu_of_device_ids, NULL); +} + +static struct platform_driver armv8_pmu_driver = { + .driver = { + .name = ARMV8_PMU_PDEV_NAME, + .of_match_table = armv8_pmu_of_device_ids, + .suppress_bind_attrs = true, + }, + .probe = armv8_pmu_device_probe, +}; + +static int __init armv8_pmu_driver_init(void) +{ + if (acpi_disabled) + return platform_driver_register(&armv8_pmu_driver); + else + return arm_pmu_acpi_probe(armv8_pmuv3_pmu_init); +} +device_initcall(armv8_pmu_driver_init) + +void arch_perf_update_userpage(struct perf_event *event, + struct perf_event_mmap_page *userpg, u64 now) +{ + struct clock_read_data *rd; + unsigned int seq; + u64 ns; + + userpg->cap_user_time = 0; + userpg->cap_user_time_zero = 0; + userpg->cap_user_time_short = 0; + userpg->cap_user_rdpmc = armv8pmu_event_has_user_read(event); + + if (userpg->cap_user_rdpmc) { + if (event->hw.flags & ARMPMU_EVT_64BIT) + userpg->pmc_width = 64; + else + userpg->pmc_width = 32; + } + + do { + rd = sched_clock_read_begin(&seq); + + if (rd->read_sched_clock != arch_timer_read_counter) + return; + + userpg->time_mult = rd->mult; + userpg->time_shift = rd->shift; + userpg->time_zero = rd->epoch_ns; + userpg->time_cycles = rd->epoch_cyc; + userpg->time_mask = rd->sched_clock_mask; + + /* + * Subtract the cycle base, such that software that + * doesn't know about cap_user_time_short still 'works' + * assuming no wraps. + */ + ns = mul_u64_u32_shr(rd->epoch_cyc, rd->mult, rd->shift); + userpg->time_zero -= ns; + + } while (sched_clock_read_retry(seq)); + + userpg->time_offset = userpg->time_zero - now; + + /* + * time_shift is not expected to be greater than 31 due to + * the original published conversion algorithm shifting a + * 32-bit value (now specifies a 64-bit value) - refer + * perf_event_mmap_page documentation in perf_event.h. + */ + if (userpg->time_shift == 32) { + userpg->time_shift = 31; + userpg->time_mult >>= 1; + } + + /* + * Internal timekeeping for enabled/running/stopped times + * is always computed with the sched_clock. + */ + userpg->cap_user_time = 1; + userpg->cap_user_time_zero = 1; + userpg->cap_user_time_short = 1; +} diff --git a/arch/arm64/kernel/uh_entry.S b/arch/arm64/kernel/uh_entry.S new file mode 100644 index 000000000000..bd56f03bb358 --- /dev/null +++ b/arch/arm64/kernel/uh_entry.S @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * + * 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. + * + */ +#include +#include + +SYM_CODE_START(uh_call) +entry: + stp x1, x0, [sp, #-16]! + stp x3, x2, [sp, #-16]! + stp x5, x4, [sp, #-16]! + stp x7, x6, [sp, #-16]! +back: + smc #0x0 + /* save smc return result to x8 */ + mov x8, x0 + /* interrupted case does not need x0-x7 be restored */ + cmp x8, #0x1 + b.eq back + + ldp x7, x6, [sp], #16 + ldp x5, x4, [sp], #16 + ldp x3, x2, [sp], #16 + ldp x1, x0, [sp], #16 + + cmp x8, #0x0 + /* busy return case does need x0-x7 be restored */ + b.ne entry + + ret +SYM_CODE_END(uh_call) + diff --git a/block/Kconfig b/block/Kconfig index 444c5ab3b67e..2a74fbb92182 100644 --- a/block/Kconfig +++ b/block/Kconfig @@ -203,6 +203,29 @@ config BLK_INLINE_ENCRYPTION_FALLBACK by falling back to the kernel crypto API when inline encryption hardware is not present. +config BLK_SEC_COMMON + tristate "Samsung specific module in block layer" + default n + help + Say Y here if you want to be enable samsung specific module + in block layer. + +config BLK_SEC_STATS + tristate "Samsung statistics module in block layer" + default n + select BLK_SEC_COMMON + help + Say Y here if you want to be enable samsung statistics module + in block layer. + +config BLK_SEC_WB + tristate "Samsung Write Booster module in block layer" + default n + select BLK_SEC_COMMON + help + Say Y here if you want to be enable samsung write booster module + in block layer. + source "block/partitions/Kconfig" config BLOCK_COMPAT diff --git a/block/Kconfig.iosched b/block/Kconfig.iosched index 615516146086..8116b1409dfd 100644 --- a/block/Kconfig.iosched +++ b/block/Kconfig.iosched @@ -43,4 +43,25 @@ config BFQ_CGROUP_DEBUG Enable some debugging help. Currently it exports additional stat files in a cgroup which can be useful for debugging. +config MQ_IOSCHED_SSG + tristate "SamSung Generic I/O scheduler" + default n + help + SamSung Generic IO scheduler. + +config MQ_IOSCHED_SSG_CGROUP + tristate "Control Group for SamSung Generic I/O scheduler" + default n + depends on BLK_CGROUP + depends on MQ_IOSCHED_SSG + help + Control Group for SamSung Generic IO scheduler. + +config MQ_IOSCHED_SSG_WB + tristate "Write Booster for SamSung Generic I/O scheduler" + default n + depends on MQ_IOSCHED_SSG + help + Write Booster for SamSung Generic IO scheduler. + endmenu diff --git a/block/Makefile b/block/Makefile index 4e01bb71ad6e..1e96d2539df9 100644 --- a/block/Makefile +++ b/block/Makefile @@ -25,6 +25,10 @@ obj-$(CONFIG_MQ_IOSCHED_DEADLINE) += mq-deadline.o obj-$(CONFIG_MQ_IOSCHED_KYBER) += kyber-iosched.o bfq-y := bfq-iosched.o bfq-wf2q.o bfq-cgroup.o obj-$(CONFIG_IOSCHED_BFQ) += bfq.o +ssg-$(CONFIG_MQ_IOSCHED_SSG) := ssg-iosched.o ssg-stat.o +ssg-$(CONFIG_MQ_IOSCHED_SSG_CGROUP) += ssg-cgroup.o +ssg-$(CONFIG_MQ_IOSCHED_SSG_WB) += ssg-wb.o +obj-$(CONFIG_MQ_IOSCHED_SSG) += ssg.o obj-$(CONFIG_BLK_DEV_INTEGRITY) += bio-integrity.o blk-integrity.o obj-$(CONFIG_BLK_DEV_INTEGRITY_T10) += t10-pi.o @@ -41,3 +45,7 @@ obj-$(CONFIG_BLK_INLINE_ENCRYPTION) += blk-crypto.o blk-crypto-profile.o \ blk-crypto-sysfs.o obj-$(CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK) += blk-crypto-fallback.o obj-$(CONFIG_BLOCK_HOLDER_DEPRECATED) += holder.o +obj-$(CONFIG_BLK_SEC_COMMON) += blk-sec-common.o +blk-sec-stats-$(CONFIG_BLK_SEC_STATS) := blk-sec-stat.o blk-sec-stat-pio.o blk-sec-stat-traffic.o +obj-$(CONFIG_BLK_SEC_STATS) += blk-sec-stats.o +obj-$(CONFIG_BLK_SEC_WB) += blk-sec-wb.o diff --git a/block/blk-sec-common.c b/block/blk-sec-common.c new file mode 100644 index 000000000000..f865a7fc3359 --- /dev/null +++ b/block/blk-sec-common.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Samsung specific module in block layer + * + * Copyright (C) 2021 Manjong Lee + * Copyright (C) 2021 Junho Kim + * Copyright (C) 2021 Changheun Lee + * Copyright (C) 2021 Seunghwan Hyun + * Copyright (C) 2021 Tran Xuan Nam + */ + +#include +#include +#include +#include +#include +#include + +#include "blk-sec.h" + +struct disk_info { + /* fields related with target device itself */ + struct gendisk *gd; + struct request_queue *queue; +}; + +struct device *blk_sec_dev; +EXPORT_SYMBOL(blk_sec_dev); + +struct workqueue_struct *blk_sec_common_wq; +EXPORT_SYMBOL(blk_sec_common_wq); + +static struct disk_info internal_disk; +static unsigned int internal_min_size_mb = 10 * 1024; /* 10GB */ + +#define SECTORS2MB(x) ((x) / 2 / 1024) + +#define SCSI_DISK0_MAJOR 8 +#define MMC_BLOCK_MAJOR 179 +#define MAJOR8_DEV_NUM 16 /* maximum number of minor devices in scsi disk0 */ +#define SCSI_MINORS 16 /* first minor number of scsi disk0 */ +#define MMC_TARGET_DEV 16 /* number of mmc devices set of target (maximum 256) */ +#define MMC_MINORS 8 /* first minor number of mmc disk */ + +static bool is_internal_bdev(struct block_device *dev) +{ + int size_mb; + + if (bdev_is_partition(dev)) + return false; + + if (dev->bd_disk->flags & GENHD_FL_REMOVABLE) + return false; + + size_mb = SECTORS2MB(get_capacity(dev->bd_disk)); + if (size_mb >= internal_min_size_mb) + return true; + + return false; +} + +static struct gendisk *find_internal_disk(void) +{ + struct block_device *bdev; + struct gendisk *gd = NULL; + int idx; + dev_t devno = MKDEV(0, 0); + + for (idx = 0; idx < MAJOR8_DEV_NUM; idx++) { + devno = MKDEV(SCSI_DISK0_MAJOR, SCSI_MINORS * idx); + bdev = blkdev_get_by_dev(devno, FMODE_READ, NULL); + if (IS_ERR(bdev)) + continue; + + if (bdev->bd_disk && is_internal_bdev(bdev)) + gd = bdev->bd_disk; + + blkdev_put(bdev, FMODE_READ); + + if (gd) + return gd; + } + + for (idx = 0; idx < MMC_TARGET_DEV; idx++) { + devno = MKDEV(MMC_BLOCK_MAJOR, MMC_MINORS * idx); + bdev = blkdev_get_by_dev(devno, FMODE_READ, NULL); + if (IS_ERR(bdev)) + continue; + + if (bdev->bd_disk && is_internal_bdev(bdev)) + gd = bdev->bd_disk; + + blkdev_put(bdev, FMODE_READ); + + if (gd) + return gd; + } + + return NULL; +} + +static inline int init_internal_disk_info(void) +{ + if (!internal_disk.gd) { + internal_disk.gd = find_internal_disk(); + if (unlikely(!internal_disk.gd)) { + pr_err("%s: can't find internal disk\n", __func__); + return -ENODEV; + } + internal_disk.queue = internal_disk.gd->queue; + } + + return 0; +} + +static inline void clear_internal_disk_info(void) +{ + internal_disk.gd = NULL; + internal_disk.queue = NULL; +} + +struct gendisk *blk_sec_internal_disk(void) +{ + if (unlikely(!internal_disk.gd)) + init_internal_disk_info(); + + return internal_disk.gd; +} +EXPORT_SYMBOL(blk_sec_internal_disk); + +static int blk_sec_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + return add_uevent_var(env, "DEVNAME=%s", dev->kobj.name); +} + +static struct device_type blk_sec_type = { + .uevent = blk_sec_uevent, +}; + +static int __init blk_sec_common_init(void) +{ + int retval; + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + blk_sec_dev = sec_device_create(NULL, "blk_sec"); + if (unlikely(IS_ERR(blk_sec_dev))) { + pr_err("%s: Failed to create blk-sec device\n", __func__); + return PTR_ERR(blk_sec_dev); + } + + blk_sec_dev->type = &blk_sec_type; +#endif + + blk_sec_common_wq = create_freezable_workqueue("blk_sec_common"); + + retval = init_internal_disk_info(); + if (retval) { + clear_internal_disk_info(); + pr_err("%s: Can't find internal disk info!", __func__); + } + + return 0; +} + +static void __exit blk_sec_common_exit(void) +{ + clear_internal_disk_info(); +} + +module_init(blk_sec_common_init); +module_exit(blk_sec_common_exit); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Changheun Lee "); +MODULE_DESCRIPTION("Samsung specific module in block layer"); +MODULE_VERSION("1.0"); diff --git a/block/blk-sec-stat-pio.c b/block/blk-sec-stat-pio.c new file mode 100644 index 000000000000..8def33556be5 --- /dev/null +++ b/block/blk-sec-stat-pio.c @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Samsung Block Statistics + * + * Copyright (C) 2021 Manjong Lee + * Copyright (C) 2021 Junho Kim + * Copyright (C) 2021 Changheun Lee + * Copyright (C) 2021 Seunghwan Hyun + * Copyright (C) 2021 Tran Xuan Nam + */ + +#include +#include +#include +#include + +#include "blk-sec.h" + +#define MAX_PIO_NODE_NUM 10000 +#define SORT_PIO_NODE_NUM 100 +#define PIO_HASH_SIZE 100 + +#define GET_HASH_KEY(tgid) ((unsigned int)(tgid) % PIO_HASH_SIZE) +#define RESET_PIO_IO(pio) \ +do { \ + atomic_set(&(pio)->kb[REQ_OP_READ], 0); \ + atomic_set(&(pio)->kb[REQ_OP_WRITE], 0); \ + atomic_set(&(pio)->kb[REQ_OP_FLUSH], 0); \ + atomic_set(&(pio)->kb[REQ_OP_DISCARD], 0); \ +} while (0) +#define GET_PIO_PRIO(pio) \ + (atomic_read(&(pio)->kb[REQ_OP_READ]) + \ + atomic_read(&(pio)->kb[REQ_OP_WRITE]) * 2) + +LIST_HEAD(pio_list); +LIST_HEAD(inflight_pios); +static DEFINE_SPINLOCK(pio_list_lock); +static DEFINE_SPINLOCK(inflight_pios_lock); +static int pio_cnt; +static int pio_enabled; +static unsigned int pio_duration_ms = 5000; +static unsigned long pio_timeout; +static struct kmem_cache *pio_cache; +static struct pio_node *pio_hash[PIO_HASH_SIZE]; +static struct pio_node others; + +static struct pio_node *add_pio_node(struct request *rq, + struct task_struct *gleader) +{ + struct pio_node *pio = NULL; + unsigned int hash_key = 0; + + if (pio_cnt >= MAX_PIO_NODE_NUM) { +add_others: + return &others; + } + + pio = kmem_cache_alloc(pio_cache, GFP_NOWAIT); + if (!pio) + goto add_others; + + INIT_LIST_HEAD(&pio->list); + + pio->tgid = task_tgid_nr(gleader); + strncpy(pio->name, gleader->comm, TASK_COMM_LEN - 1); + pio->name[TASK_COMM_LEN - 1] = '\0'; + pio->start_time = gleader->start_time; + + RESET_PIO_IO(pio); + atomic_set(&pio->ref_count, 1); + + hash_key = GET_HASH_KEY(pio->tgid); + + spin_lock(&pio_list_lock); + list_add(&pio->list, &pio_list); + pio->h_next = pio_hash[hash_key]; + pio_hash[hash_key] = pio; + pio_cnt++; + spin_unlock(&pio_list_lock); + + return pio; +} + +static struct pio_node *find_pio_node(pid_t tgid, u64 tg_start_time) +{ + struct pio_node *pio; + + for (pio = pio_hash[GET_HASH_KEY(tgid)]; pio; pio = pio->h_next) { + if (pio->tgid != tgid) + continue; + if (pio->start_time != tg_start_time) + continue; + return pio; + } + + return NULL; +} + +static void free_pio_nodes(struct list_head *remove_list) +{ + struct pio_node *pio; + struct pio_node *pion; + + /* move previous inflight pios to remove_list */ + spin_lock(&inflight_pios_lock); + list_splice_init(&inflight_pios, remove_list); + spin_unlock(&inflight_pios_lock); + + list_for_each_entry_safe(pio, pion, remove_list, list) { + list_del(&pio->list); + if (atomic_read(&pio->ref_count)) { + spin_lock(&inflight_pios_lock); + list_add(&pio->list, &inflight_pios); + spin_unlock(&inflight_pios_lock); + continue; + } + kmem_cache_free(pio_cache, pio); + } +} + +struct pio_node *get_pio_node(struct request *rq) +{ + struct task_struct *gleader = current->group_leader; + struct pio_node *pio; + + if (pio_enabled == 0) + return NULL; + if (time_after(jiffies, pio_timeout)) + return NULL; + if (req_op(rq) > REQ_OP_DISCARD) + return NULL; + + spin_lock(&pio_list_lock); + pio = find_pio_node(task_tgid_nr(gleader), gleader->start_time); + if (pio) { + atomic_inc(&pio->ref_count); + spin_unlock(&pio_list_lock); + return pio; + } + spin_unlock(&pio_list_lock); + + return add_pio_node(rq, gleader); +} + +void update_pio_node(struct request *rq, + unsigned int data_size, struct pio_node *pio) +{ + if (!pio) + return; + + /* transfer bytes to kbytes via '>> 10' */ + atomic_add((req_op(rq) == REQ_OP_FLUSH) ? 1 : data_size >> 10, + &pio->kb[req_op(rq)]); +} + +void put_pio_node(struct pio_node *pio) +{ + if (!pio) + return; + + atomic_dec(&pio->ref_count); +} + +static void sort_pios(struct list_head *pios) +{ + struct pio_node *max_pio = NULL; + struct pio_node *pio; + unsigned long long max = 0; + LIST_HEAD(sorted_list); + int i; + + for (i = 0; i < SORT_PIO_NODE_NUM; i++) { + list_for_each_entry(pio, pios, list) { + if (GET_PIO_PRIO(pio) > max) { + max = GET_PIO_PRIO(pio); + max_pio = pio; + } + } + if (max_pio != NULL) + list_move_tail(&max_pio->list, &sorted_list); + + max = 0; + max_pio = NULL; + } + list_splice_init(&sorted_list, pios); +} + +static ssize_t pio_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + LIST_HEAD(curr_pios); + int curr_pio_cnt; + struct pio_node curr_others; + struct pio_node *pio; + int len = 0; + + spin_lock(&pio_list_lock); + list_replace_init(&pio_list, &curr_pios); + curr_pio_cnt = pio_cnt; + curr_others = others; + memset(pio_hash, 0x0, sizeof(pio_hash)); + pio_cnt = 0; + RESET_PIO_IO(&others); + spin_unlock(&pio_list_lock); + + if (curr_pio_cnt > SORT_PIO_NODE_NUM) + sort_pios(&curr_pios); + + list_for_each_entry(pio, &curr_pios, list) { + if (PAGE_SIZE - len > 80) { + len += scnprintf(buf + len, PAGE_SIZE - len, + "%d %d %d %s\n", + pio->tgid, + atomic_read(&pio->kb[REQ_OP_READ]), + atomic_read(&pio->kb[REQ_OP_WRITE]), + (pio->name[0]) ? pio->name : "-"); + continue; + } + + atomic_add(atomic_read(&pio->kb[REQ_OP_READ]), + &curr_others.kb[REQ_OP_READ]); + atomic_add(atomic_read(&pio->kb[REQ_OP_WRITE]), + &curr_others.kb[REQ_OP_WRITE]); + atomic_add(atomic_read(&pio->kb[REQ_OP_FLUSH]), + &curr_others.kb[REQ_OP_FLUSH]); + atomic_add(atomic_read(&pio->kb[REQ_OP_DISCARD]), + &curr_others.kb[REQ_OP_DISCARD]); + } + + if (atomic_read(&curr_others.kb[REQ_OP_READ]) + + atomic_read(&curr_others.kb[REQ_OP_WRITE])) + len += scnprintf(buf + len, PAGE_SIZE - len, + "%d %d %d %s\n", + curr_others.tgid, + atomic_read(&curr_others.kb[REQ_OP_READ]), + atomic_read(&curr_others.kb[REQ_OP_WRITE]), + curr_others.name); + + free_pio_nodes(&curr_pios); + pio_timeout = jiffies + msecs_to_jiffies(pio_duration_ms); + + return len; +} + +static ssize_t pio_enabled_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + LIST_HEAD(curr_pios); + int enable = 0; + int ret; + + ret = kstrtoint(buf, 10, &enable); + if (ret) + return ret; + + pio_enabled = (enable >= 1) ? 1 : 0; + + spin_lock(&pio_list_lock); + list_replace_init(&pio_list, &curr_pios); + memset(pio_hash, 0x0, sizeof(pio_hash)); + pio_cnt = 0; + RESET_PIO_IO(&others); + spin_unlock(&pio_list_lock); + + free_pio_nodes(&curr_pios); + pio_timeout = jiffies + msecs_to_jiffies(pio_duration_ms); + + return count; +} + +static ssize_t pio_enabled_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + int len = 0; + + len = scnprintf(buf, PAGE_SIZE, "%d\n", pio_enabled); + + return len; +} + +static ssize_t pio_duration_ms_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + int ret; + + ret = kstrtoint(buf, 10, &pio_duration_ms); + if (ret) + return ret; + + if (pio_duration_ms > 10000) + pio_duration_ms = 10000; + + return count; +} + +static ssize_t pio_duration_ms_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + int len = 0; + + len = scnprintf(buf, PAGE_SIZE, "%u\n", pio_duration_ms); + + return len; +} + +static struct kobj_attribute pios_attr = __ATTR(pios, 0444, pio_show, NULL); +static struct kobj_attribute pios_enable_attr = __ATTR(pios_enable, 0644, + pio_enabled_show, pio_enabled_store); +static struct kobj_attribute pios_duration_ms_attr = __ATTR(pios_duration_ms, 0644, + pio_duration_ms_show, pio_duration_ms_store); + +static const struct attribute *blk_sec_stat_pio_attrs[] = { + &pios_attr.attr, + &pios_enable_attr.attr, + &pios_duration_ms_attr.attr, + NULL, +}; + +int blk_sec_stat_pio_init(struct kobject *kobj) +{ + int retval; + + if (!kobj) + return -EINVAL; + + pio_cache = kmem_cache_create("pio_node", sizeof(struct pio_node), 0, 0, NULL); + if (!pio_cache) + return -ENOMEM; + + retval = sysfs_create_files(kobj, blk_sec_stat_pio_attrs); + if (retval) { + kmem_cache_destroy(pio_cache); + return retval; + } + + /* init others */ + INIT_LIST_HEAD(&others.list); + others.tgid = INT_MAX; + strncpy(others.name, "others", TASK_COMM_LEN - 1); + others.name[TASK_COMM_LEN - 1] = '\0'; + others.start_time = 0; + RESET_PIO_IO(&others); + atomic_set(&others.ref_count, 1); + others.h_next = NULL; + + return 0; +} + +void blk_sec_stat_pio_exit(struct kobject *kobj) +{ + if (!kobj) + return; + + sysfs_remove_files(kobj, blk_sec_stat_pio_attrs); + kmem_cache_destroy(pio_cache); +} diff --git a/block/blk-sec-stat-traffic.c b/block/blk-sec-stat-traffic.c new file mode 100644 index 000000000000..2f9253df66c8 --- /dev/null +++ b/block/blk-sec-stat-traffic.c @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Samsung Block Statistics + * + * Copyright (C) 2021 Manjong Lee + * Copyright (C) 2021 Junho Kim + * Copyright (C) 2021 Changheun Lee + * Copyright (C) 2021 Seunghwan Hyun + * Copyright (C) 2021 Tran Xuan Nam + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "blk-sec.h" + +struct traffic { + u64 transferred_bytes; + int level; + unsigned int timestamp; + + struct gendisk *gd; + struct work_struct update_work; + struct delayed_work notify_work; +}; + +static DEFINE_PER_CPU(u64, transferred_bytes); +static DEFINE_PER_CPU(struct freq_qos_request, cpufreq_req); +static struct pm_qos_request cpu_pm_req; +static unsigned int interval_ms = 1000; +static unsigned int interval_bytes = 100 * 1024 * 1024; +static struct traffic traffic; + +#define TL0_UEVENT_DELAY_MS 2000 + +#define UPDATE_WORK_TO_TRAFFIC(work) \ + container_of(work, struct traffic, update_work) +#define NOTIFY_WORK_TO_TRAFFIC(work) \ + container_of(to_delayed_work(work), struct traffic, notify_work) + +static u64 get_transferred_bytes(void) +{ + u64 bytes = 0; + int cpu; + + for_each_possible_cpu(cpu) + bytes += per_cpu(transferred_bytes, cpu); + + return bytes; +} + +/* + * Convert throughput to level. Level is defined as below: + * 0: 0 - "< 100" MB/s + * 1: 100 - "< 200" MB/s + * 2: 200 - "< 400" MB/s + * 3: 400 - "< 800" MB/s + * ...so on + */ +static int tp2level(int tput) +{ + if (tput < 100) + return 0; + return (int) ilog2(tput / 100) + 1; +} + +static void notify_traffic_level(struct traffic *traffic) +{ +#define BUF_SIZE 16 + char buf[BUF_SIZE]; + char *envp[] = { "NAME=IO_TRAFFIC", buf, NULL, }; + int ret; + + if (unlikely(IS_ERR(blk_sec_dev))) + return; + + memset(buf, 0, BUF_SIZE); + snprintf(buf, BUF_SIZE, "LEVEL=%d", traffic->level); + ret = kobject_uevent_env(&blk_sec_dev->kobj, KOBJ_CHANGE, envp); + if (ret) + pr_err("%s: couldn't send uevent (%d)", __func__, ret); +} + +#define MB(x) ((x) / 1024 / 1024) + +static void update_traffic(struct work_struct *work) +{ + struct traffic *traffic = UPDATE_WORK_TO_TRAFFIC(work); + struct traffic old = *traffic; + unsigned int duration_ms; + u64 amount; + int tput; + int delay = 0; + + traffic->transferred_bytes = get_transferred_bytes(); + traffic->timestamp = jiffies_to_msecs(jiffies); + + duration_ms = traffic->timestamp - old.timestamp; + amount = traffic->transferred_bytes - old.transferred_bytes; + tput = MB(amount) * 1000 / duration_ms; + traffic->level = tp2level(tput); + + if (!!traffic->level == !!old.level) + return; + + if (traffic->level == 0) /* level !0 -> 0 */ + delay = msecs_to_jiffies(TL0_UEVENT_DELAY_MS); + + cancel_delayed_work_sync(&traffic->notify_work); + schedule_delayed_work(&traffic->notify_work, delay); +} + +static void send_uevent(struct work_struct *work) +{ + struct traffic *traffic = NOTIFY_WORK_TO_TRAFFIC(work); + notify_traffic_level(traffic); +} + +void blk_sec_stat_traffic_update(struct request *rq, unsigned int data_size) +{ + unsigned int duration_ms; + u64 amount; + + if (req_op(rq) > REQ_OP_WRITE) + return; + + this_cpu_add(transferred_bytes, data_size); + + duration_ms = jiffies_to_msecs(jiffies) - traffic.timestamp; + amount = get_transferred_bytes() - traffic.transferred_bytes; + + if ((duration_ms < interval_ms) && (amount < interval_bytes)) + return; + + traffic.gd = rq->part->bd_disk; + schedule_work(&traffic.update_work); +} + +static void init_traffic(struct traffic *traffic) +{ + traffic->transferred_bytes = 0; + traffic->level = 0; + traffic->timestamp = jiffies_to_msecs(jiffies); + traffic->gd = NULL; + INIT_WORK(&traffic->update_work, update_traffic); + INIT_DELAYED_WORK(&traffic->notify_work, send_uevent); +} + +static void allow_cpu_lpm(bool enable) +{ + if (enable) + cpu_latency_qos_update_request(&cpu_pm_req, PM_QOS_DEFAULT_VALUE); + else + cpu_latency_qos_update_request(&cpu_pm_req, 0); +} + +static ssize_t transferred_bytes_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%llu\n", get_transferred_bytes()); +} + +static ssize_t cpufreq_min_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct freq_qos_request *req; + int len = 0; + int i; + + for_each_possible_cpu(i) { + req = &per_cpu(cpufreq_req, i); + if (IS_ERR_OR_NULL(req->qos)) + continue; + len += scnprintf(buf + len, PAGE_SIZE - len, "%d: %d, %d, %d\n", + i, + req->qos->min_freq.target_value, + req->qos->min_freq.default_value, + req->qos->min_freq.no_constraint_value); + } + + return len; +} + +static ssize_t cpufreq_min_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + struct freq_qos_request *req; + struct cpufreq_policy *policy; + s32 cpufreq_min; + int i; + int ret; + + ret = kstrtoint(buf, 10, &cpufreq_min); + if (ret) + return ret; + + for_each_possible_cpu(i) { + req = &per_cpu(cpufreq_req, i); + if (IS_ERR_OR_NULL(req->qos)) { + policy = cpufreq_cpu_get(i); + if (!policy) + continue; + + freq_qos_add_request(&policy->constraints, + req, FREQ_QOS_MIN, cpufreq_min); + cpufreq_cpu_put(policy); + } + freq_qos_update_request(req, cpufreq_min); + } + + return count; +} + +static ssize_t cpu_lpm_enabled_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + if (IS_ERR_OR_NULL(cpu_pm_req.qos)) + return 0; + + return scnprintf(buf, PAGE_SIZE, "%d: %d, %d, %d\n", + !!cpu_pm_req.qos->target_value, + cpu_pm_req.qos->target_value, + cpu_pm_req.qos->default_value, + cpu_pm_req.qos->no_constraint_value); +} + +static ssize_t cpu_lpm_enabled_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int enable; + int ret; + + ret = kstrtoint(buf, 10, &enable); + if (ret) + return ret; + + allow_cpu_lpm(!!enable); + + return count; +} + +static struct kobj_attribute transferred_bytes_attr = + __ATTR(transferred_bytes, 0444, transferred_bytes_show, NULL); +static struct kobj_attribute cpufreq_min_attr = + __ATTR(cpufreq_min, 0600, cpufreq_min_show, cpufreq_min_store); +static struct kobj_attribute cpu_lpm_enable_attr = + __ATTR(cpu_lpm_enable, 0600, cpu_lpm_enabled_show, cpu_lpm_enabled_store); + +static const struct attribute *blk_sec_stat_traffic_attrs[] = { + &transferred_bytes_attr.attr, + &cpufreq_min_attr.attr, + &cpu_lpm_enable_attr.attr, + NULL, +}; + +int blk_sec_stat_traffic_init(struct kobject *kobj) +{ + if (!kobj) + return -EINVAL; + + init_traffic(&traffic); + + cpu_latency_qos_add_request(&cpu_pm_req, PM_QOS_DEFAULT_VALUE); + + return sysfs_create_files(kobj, blk_sec_stat_traffic_attrs); +} + +void blk_sec_stat_traffic_exit(struct kobject *kobj) +{ + if (!kobj) + return; + + allow_cpu_lpm(true); + cpu_latency_qos_remove_request(&cpu_pm_req); + cancel_delayed_work_sync(&traffic.notify_work); + + sysfs_remove_files(kobj, blk_sec_stat_traffic_attrs); +} diff --git a/block/blk-sec-stat.c b/block/blk-sec-stat.c new file mode 100644 index 000000000000..b9f0812ec33b --- /dev/null +++ b/block/blk-sec-stat.c @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Samsung Block Statistics + * + * Copyright (C) 2021 Manjong Lee + * Copyright (C) 2021 Junho Kim + * Copyright (C) 2021 Changheun Lee + * Copyright (C) 2021 Seunghwan Hyun + * Copyright (C) 2021 Tran Xuan Nam + */ + +#include +#include +#include +#include +#include +#include + +#include "blk-sec.h" + +struct accumulated_stat { + struct timespec64 uptime; + unsigned long sectors[3]; /* READ, WRITE, DISCARD */ + unsigned long ios[3]; + unsigned long iot; +}; +static struct accumulated_stat old, new; + +extern int blk_sec_stat_pio_init(struct kobject *kobj); +extern void blk_sec_stat_pio_exit(struct kobject *kobj); +extern struct pio_node *get_pio_node(struct request *rq); +extern void update_pio_node(struct request *rq, + unsigned int data_size, struct pio_node *pio); +extern void put_pio_node(struct pio_node *pio); + +extern int blk_sec_stat_traffic_init(struct kobject *kobj); +extern void blk_sec_stat_traffic_exit(struct kobject *kobj); +extern void blk_sec_stat_traffic_update(struct request *rq, + unsigned int data_size); + +void blk_sec_stat_account_init(struct request_queue *q) +{ + if (!blk_sec_internal_disk()) + pr_err("%s: Can't find internal disk info!", __func__); +} +EXPORT_SYMBOL(blk_sec_stat_account_init); + +void blk_sec_stat_account_exit(struct elevator_queue *eq) +{ +} +EXPORT_SYMBOL(blk_sec_stat_account_exit); + +#define UNSIGNED_DIFF(n, o) (((n) >= (o)) ? ((n) - (o)) : ((n) + (0 - (o)))) +#define SECTORS2KB(x) ((x) / 2) + +static inline void get_monotonic_boottime(struct timespec64 *ts) +{ + *ts = ktime_to_timespec64(ktime_get_boottime()); +} + +static ssize_t diskios_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + struct gendisk *gd = blk_sec_internal_disk(); + struct block_device *bdev; + long hours; + int ret; + + if (unlikely(!gd)) + return -EINVAL; + + bdev = gd->part0; + + new.ios[STAT_READ] = part_stat_read(bdev, ios[STAT_READ]); + new.ios[STAT_WRITE] = part_stat_read(bdev, ios[STAT_WRITE]); + new.ios[STAT_DISCARD] = part_stat_read(bdev, ios[STAT_DISCARD]); + new.sectors[STAT_READ] = part_stat_read(bdev, sectors[STAT_READ]); + new.sectors[STAT_WRITE] = part_stat_read(bdev, sectors[STAT_WRITE]); + new.sectors[STAT_DISCARD] = part_stat_read(bdev, sectors[STAT_DISCARD]); + new.iot = jiffies_to_msecs(part_stat_read(bdev, io_ticks)) / 1000; + + get_monotonic_boottime(&(new.uptime)); + hours = (new.uptime.tv_sec - old.uptime.tv_sec) / 60; + hours = (hours + 30) / 60; + + ret = sprintf(buf, "\"ReadC\":\"%lu\",\"ReadKB\":\"%lu\"," + "\"WriteC\":\"%lu\",\"WriteKB\":\"%lu\"," + "\"DiscardC\":\"%lu\",\"DiscardKB\":\"%lu\"," + "\"IOT\":\"%lu\"," + "\"Hours\":\"%ld\"\n", + UNSIGNED_DIFF(new.ios[STAT_READ], old.ios[STAT_READ]), + SECTORS2KB(UNSIGNED_DIFF(new.sectors[STAT_READ], old.sectors[STAT_READ])), + UNSIGNED_DIFF(new.ios[STAT_WRITE], old.ios[STAT_WRITE]), + SECTORS2KB(UNSIGNED_DIFF(new.sectors[STAT_WRITE], old.sectors[STAT_WRITE])), + UNSIGNED_DIFF(new.ios[STAT_DISCARD], old.ios[STAT_DISCARD]), + SECTORS2KB(UNSIGNED_DIFF(new.sectors[STAT_DISCARD], old.sectors[STAT_DISCARD])), + UNSIGNED_DIFF(new.iot, old.iot), + hours); + + old.ios[STAT_READ] = new.ios[STAT_READ]; + old.ios[STAT_WRITE] = new.ios[STAT_WRITE]; + old.ios[STAT_DISCARD] = new.ios[STAT_DISCARD]; + old.sectors[STAT_READ] = new.sectors[STAT_READ]; + old.sectors[STAT_WRITE] = new.sectors[STAT_WRITE]; + old.sectors[STAT_DISCARD] = new.sectors[STAT_DISCARD]; + old.uptime = new.uptime; + old.iot = new.iot; + + return ret; +} + +static inline bool may_account_rq(struct request *rq) +{ + struct gendisk *gd = blk_sec_internal_disk(); + + if (unlikely(!gd)) + return false; + + if (gd->queue != rq->q) + return false; + + return true; +} + +void blk_sec_stat_account_io_prepare(struct request *rq, void *ptr_pio) +{ + if (unlikely(!may_account_rq(rq))) + return; + + *(struct pio_node **)ptr_pio = get_pio_node(rq); +} +EXPORT_SYMBOL(blk_sec_stat_account_io_prepare); + +void blk_sec_stat_account_io_complete(struct request *rq, + unsigned int data_size, void *pio) +{ + if (unlikely(!may_account_rq(rq))) + return; + + blk_sec_stat_traffic_update(rq, data_size); + update_pio_node(rq, data_size, (struct pio_node *)pio); +} +EXPORT_SYMBOL(blk_sec_stat_account_io_complete); + +void blk_sec_stat_account_io_finish(struct request *rq, void *ptr_pio) +{ + if (unlikely(!may_account_rq(rq))) + return; + + put_pio_node(*(struct pio_node **)ptr_pio); + *(struct pio_node **)ptr_pio = NULL; +} +EXPORT_SYMBOL(blk_sec_stat_account_io_finish); + +static struct kobj_attribute diskios_attr = __ATTR(diskios, 0444, diskios_show, NULL); + +static const struct attribute *blk_sec_stat_attrs[] = { + &diskios_attr.attr, + NULL, +}; + +static struct kobject *blk_sec_stats_kobj; + +static int __init blk_sec_stats_init(void) +{ + int retval; + + blk_sec_stats_kobj = kobject_create_and_add("blk_sec_stats", kernel_kobj); + if (!blk_sec_stats_kobj) + return -ENOMEM; + + retval = sysfs_create_files(blk_sec_stats_kobj, blk_sec_stat_attrs); + if (retval) { + kobject_put(blk_sec_stats_kobj); + return retval; + } + + retval = blk_sec_stat_pio_init(blk_sec_stats_kobj); + if (retval) + pr_err("%s: fail to initialize PIO sub module", __func__); + + retval = blk_sec_stat_traffic_init(blk_sec_stats_kobj); + if (retval) + pr_err("%s: fail to initialize TRAFFIC sub module", __func__); + + return 0; +} + +static void __exit blk_sec_stats_exit(void) +{ + blk_sec_stat_traffic_exit(blk_sec_stats_kobj); + blk_sec_stat_pio_exit(blk_sec_stats_kobj); + sysfs_remove_files(blk_sec_stats_kobj, blk_sec_stat_attrs); + kobject_put(blk_sec_stats_kobj); +} + +module_init(blk_sec_stats_init); +module_exit(blk_sec_stats_exit); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Manjong Lee "); +MODULE_DESCRIPTION("Samsung block layer statistics module for various purposes"); +MODULE_VERSION("1.0"); diff --git a/block/blk-sec-wb.c b/block/blk-sec-wb.c new file mode 100644 index 000000000000..e51d96f91c5c --- /dev/null +++ b/block/blk-sec-wb.c @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Samsung Block Write Booster + * + * Copyright (C) 2023 Jisoo Oh + * Copyright (C) 2023 Changheun Lee + */ + +#include +#include +#include +#include +#include +#include + +#include "blk-sec.h" +#include "../drivers/ufs/host/ufs-sec-feature.h" + +struct blk_sec_wb { + struct mutex lock; + + volatile unsigned long request; + unsigned int state; + + struct work_struct ctrl_work; + struct timer_list user_wb_off_timer; +}; + +static struct blk_sec_wb wb; + +static void notify_wb_change(bool enabled) +{ +#define BUF_SIZE 16 + char buf[BUF_SIZE]; + char *envp[] = { "NAME=BLK_SEC_WB", buf, NULL, }; + int ret; + + if (unlikely(IS_ERR(blk_sec_dev))) + return; + + memset(buf, 0, BUF_SIZE); + snprintf(buf, BUF_SIZE, "ENABLED=%d", enabled); + ret = kobject_uevent_env(&blk_sec_dev->kobj, KOBJ_CHANGE, envp); + if (ret) + pr_err("%s: couldn't send uevent (%d)", __func__, ret); +} + +/* + * don't call this function in interrupt context, + * it will be sleep when ufs_sec_wb_ctrl() is called + * + * Context: can sleep + */ +static int wb_ctrl(bool enable) +{ + int ret = 0; + + might_sleep(); + + mutex_lock(&wb.lock); + + if (enable && (wb.state == WB_ON)) + goto out; + + if (!enable && (wb.state == WB_OFF)) + goto out; + + ret = ufs_sec_wb_ctrl(enable); + if (ret) + goto out; + + if (enable) + wb.state = WB_ON; + else + wb.state = WB_OFF; + + notify_wb_change(enable); + +out: + mutex_unlock(&wb.lock); + return ret; +} + +static void wb_ctrl_work(struct work_struct *work) +{ + wb_ctrl(!!wb.request); +} + +static void user_wb_off_handler(struct timer_list *timer) +{ + clear_bit(WB_REQ_USER, &wb.request); + queue_work(blk_sec_common_wq, &wb.ctrl_work); +} + +static void ufs_reset_notify(void) +{ + queue_work(blk_sec_common_wq, &wb.ctrl_work); +} + +int blk_sec_wb_ctrl(bool enable, int req_type) +{ + if (req_type < 0 || req_type >= NR_WB_REQ_TYPE) + return -EINVAL; + + if (enable) + set_bit(req_type, &wb.request); + else + clear_bit(req_type, &wb.request); + + return wb_ctrl(!!wb.request); +} +EXPORT_SYMBOL(blk_sec_wb_ctrl); + +int blk_sec_wb_ctrl_async(bool enable, int req_type) +{ + if (req_type < 0 || req_type >= NR_WB_REQ_TYPE) + return -EINVAL; + + if (enable) + set_bit(req_type, &wb.request); + else + clear_bit(req_type, &wb.request); + + queue_work(blk_sec_common_wq, &wb.ctrl_work); + return 0; +} +EXPORT_SYMBOL(blk_sec_wb_ctrl_async); + +bool blk_sec_wb_is_supported(struct gendisk *gd) +{ + if (blk_sec_internal_disk() != gd) + return false; + + return ufs_sec_is_wb_supported(); +} +EXPORT_SYMBOL(blk_sec_wb_is_supported); + +static ssize_t request_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%lx\n", wb.request); +} + +static ssize_t state_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%u\n", wb.state); +} + +static ssize_t enable_ms_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + unsigned long expire_jiffies = wb.user_wb_off_timer.expires; + unsigned long current_jiffies = jiffies; + + return scnprintf(buf, PAGE_SIZE, "%u\n", + time_after(expire_jiffies, current_jiffies) ? + jiffies_to_msecs(expire_jiffies - current_jiffies) : 0); +} + +static ssize_t enable_ms_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int wb_on_duration = 0; + unsigned long expire_jiffies = 0; + int ret; + + ret = kstrtoint(buf, 10, &wb_on_duration); + if (ret) + return ret; + + if (wb_on_duration <= 0) + return count; + + if (wb_on_duration < 100) + wb_on_duration = 100; + if (wb_on_duration > 5000) + wb_on_duration = 5000; + + expire_jiffies = jiffies + msecs_to_jiffies(wb_on_duration); + if (time_after(expire_jiffies, wb.user_wb_off_timer.expires)) + mod_timer(&wb.user_wb_off_timer, expire_jiffies); + + blk_sec_wb_ctrl(true, WB_REQ_USER); + + return count; +} + +static struct kobj_attribute request_attr = __ATTR_RO(request); +static struct kobj_attribute state_attr = __ATTR_RO(state); +static struct kobj_attribute enable_ms_attr = +__ATTR(enable_ms, 0644, enable_ms_show, enable_ms_store); + +static const struct attribute *blk_sec_wb_attrs[] = { + &request_attr.attr, + &state_attr.attr, + &enable_ms_attr.attr, + NULL, +}; + +static struct kobject *blk_sec_wb_kobj; + +static int __init blk_sec_wb_init(void) +{ + int retval; + + blk_sec_wb_kobj = kobject_create_and_add("blk_sec_wb", kernel_kobj); + if (!blk_sec_wb_kobj) + return -ENOMEM; + + retval = sysfs_create_files(blk_sec_wb_kobj, blk_sec_wb_attrs); + if (retval) { + kobject_put(blk_sec_wb_kobj); + return retval; + } + + mutex_init(&wb.lock); + wb.state = WB_OFF; + INIT_WORK(&wb.ctrl_work, wb_ctrl_work); + timer_setup(&wb.user_wb_off_timer, user_wb_off_handler, 0); + ufs_sec_wb_register_reset_notify(&ufs_reset_notify); + + return 0; +} + +static void __exit blk_sec_wb_exit(void) +{ + del_timer_sync(&wb.user_wb_off_timer); + sysfs_remove_files(blk_sec_wb_kobj, blk_sec_wb_attrs); + kobject_put(blk_sec_wb_kobj); +} + +module_init(blk_sec_wb_init); +module_exit(blk_sec_wb_exit); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Jisoo Oh "); +MODULE_DESCRIPTION("Samsung write booster module in block layer"); +MODULE_VERSION("1.0"); diff --git a/block/blk-sec.h b/block/blk-sec.h new file mode 100644 index 000000000000..9b9471302d90 --- /dev/null +++ b/block/blk-sec.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef BLK_SEC_H +#define BLK_SEC_H + +enum { + WB_REQ_IOSCHED = 0, + WB_REQ_USER, + + NR_WB_REQ_TYPE +}; + +#if IS_ENABLED(CONFIG_BLK_SEC_COMMON) +extern struct device *blk_sec_dev; +extern struct workqueue_struct *blk_sec_common_wq; + +extern struct gendisk *blk_sec_internal_disk(void); +#else +static struct gendisk *blk_sec_internal_disk(void) +{ + return NULL; +} +#endif + +#if IS_ENABLED(CONFIG_BLK_SEC_STATS) +struct pio_node { + struct list_head list; + + pid_t tgid; + char name[TASK_COMM_LEN]; + u64 start_time; + + atomic_t kb[REQ_OP_DISCARD + 1]; + + atomic_t ref_count; + struct pio_node *h_next; /* next pio_node for hash */ +}; + +extern void blk_sec_stat_account_init(struct request_queue *q); +extern void blk_sec_stat_account_exit(struct elevator_queue *eq); +extern void blk_sec_stat_account_io_prepare(struct request *rq, + void *ptr_pio); +extern void blk_sec_stat_account_io_complete(struct request *rq, + unsigned int data_size, void *pio); +extern void blk_sec_stat_account_io_finish(struct request *rq, + void *ptr_pio); +#else +static inline void blk_sec_stat_account_init(struct request_queue *q) +{ +} + +static inline void blk_sec_stat_account_exit(struct elevator_queue *eq) +{ +} + +static inline void blk_sec_stat_account_io_prepare(struct request *rq, + void *ptr_pio) +{ +} + +static inline void blk_sec_stat_account_io_complete(struct request *rq, + unsigned int data_size, void *pio) +{ +} + +static inline void blk_sec_stat_account_io_finish(struct request *rq, + void *ptr_pio) +{ +} +#endif + +#if IS_ENABLED(CONFIG_BLK_SEC_WB) +extern int blk_sec_wb_ctrl(bool enable, int req_type); +extern int blk_sec_wb_ctrl_async(bool enable, int req_type); +extern bool blk_sec_wb_is_supported(struct gendisk *gd); +#else +static inline int blk_sec_wb_ctrl(bool enable, int req_type) +{ + return 0; +} + +static inline int blk_sec_wb_ctrl_async(bool enable, int req_type) +{ + return 0; +} + +static inline bool blk_sec_wb_is_supported(struct gendisk *gd) +{ + return false; +} +#endif + +#endif // BLK_SEC_H diff --git a/block/ssg-cgroup.c b/block/ssg-cgroup.c new file mode 100644 index 000000000000..14670103c9ce --- /dev/null +++ b/block/ssg-cgroup.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Control Group of SamSung Generic I/O scheduler + * + * Copyright (C) 2021 Changheun Lee + */ + +#include +#include + +#include "blk-cgroup.h" +#include "blk-mq.h" +#include "blk-mq-tag.h" +#include "ssg.h" + + + +static struct blkcg_policy ssg_blkcg_policy; + + + +#define CPD_TO_SSG_BLKCG(_cpd) \ + container_of_safe((_cpd), struct ssg_blkcg, cpd) +#define BLKCG_TO_SSG_BLKCG(_blkcg) \ + CPD_TO_SSG_BLKCG(blkcg_to_cpd((_blkcg), &ssg_blkcg_policy)) + +#define PD_TO_SSG_BLKG(_pd) \ + container_of_safe((_pd), struct ssg_blkg, pd) +#define BLKG_TO_SSG_BLKG(_blkg) \ + PD_TO_SSG_BLKG(blkg_to_pd((_blkg), &ssg_blkcg_policy)) + +#define CSS_TO_SSG_BLKCG(css) BLKCG_TO_SSG_BLKCG(css_to_blkcg(css)) + + + +static struct blkcg_policy_data *ssg_blkcg_cpd_alloc(gfp_t gfp) +{ + struct ssg_blkcg *ssg_blkcg; + + ssg_blkcg = kzalloc(sizeof(struct ssg_blkcg), gfp); + if (ZERO_OR_NULL_PTR(ssg_blkcg)) + return NULL; + + return &ssg_blkcg->cpd; +} + +static void ssg_blkcg_cpd_init(struct blkcg_policy_data *cpd) +{ + struct ssg_blkcg *ssg_blkcg = CPD_TO_SSG_BLKCG(cpd); + + if (IS_ERR_OR_NULL(ssg_blkcg)) + return; + + ssg_blkcg->max_available_ratio = 100; +} + +static void ssg_blkcg_cpd_free(struct blkcg_policy_data *cpd) +{ + struct ssg_blkcg *ssg_blkcg = CPD_TO_SSG_BLKCG(cpd); + + if (IS_ERR_OR_NULL(ssg_blkcg)) + return; + + kfree(ssg_blkcg); +} + +static void ssg_blkcg_set_shallow_depth(struct ssg_blkcg *ssg_blkcg, + struct ssg_blkg *ssg_blkg, struct blk_mq_tags *tags) +{ + unsigned int depth = tags->bitmap_tags.sb.depth; + unsigned int map_nr = tags->bitmap_tags.sb.map_nr; + + ssg_blkg->max_available_rqs = + depth * ssg_blkcg->max_available_ratio / 100U; + ssg_blkg->shallow_depth = + max_t(unsigned int, 1, ssg_blkg->max_available_rqs / map_nr); +} + +static struct blkg_policy_data *ssg_blkcg_pd_alloc(gfp_t gfp, + struct request_queue *q, struct blkcg *blkcg) +{ + struct ssg_blkg *ssg_blkg; + + ssg_blkg = kzalloc_node(sizeof(struct ssg_blkg), gfp, q->node); + if (ZERO_OR_NULL_PTR(ssg_blkg)) + return NULL; + + return &ssg_blkg->pd; +} + +static void ssg_blkcg_pd_init(struct blkg_policy_data *pd) +{ + struct ssg_blkg *ssg_blkg; + struct ssg_blkcg *ssg_blkcg; + struct blk_mq_hw_ctx *hctx; + unsigned long i; + + ssg_blkg = PD_TO_SSG_BLKG(pd); + if (IS_ERR_OR_NULL(ssg_blkg)) + return; + + ssg_blkcg = BLKCG_TO_SSG_BLKCG(pd->blkg->blkcg); + if (IS_ERR_OR_NULL(ssg_blkcg)) + return; + + atomic_set(&ssg_blkg->current_rqs, 0); + queue_for_each_hw_ctx(pd->blkg->q, hctx, i) + ssg_blkcg_set_shallow_depth(ssg_blkcg, ssg_blkg, + hctx->sched_tags); +} + +static void ssg_blkcg_pd_free(struct blkg_policy_data *pd) +{ + struct ssg_blkg *ssg_blkg = PD_TO_SSG_BLKG(pd); + + if (IS_ERR_OR_NULL(ssg_blkg)) + return; + + kfree(ssg_blkg); +} + +unsigned int ssg_blkcg_shallow_depth(struct request_queue *q) +{ + struct blkcg_gq *blkg; + struct ssg_blkg *ssg_blkg; + + rcu_read_lock(); + blkg = blkg_lookup(css_to_blkcg(curr_css()), q); + ssg_blkg = BLKG_TO_SSG_BLKG(blkg); + rcu_read_unlock(); + + if (IS_ERR_OR_NULL(ssg_blkg)) + return 0; + + if (atomic_read(&ssg_blkg->current_rqs) < ssg_blkg->max_available_rqs) + return 0; + + return ssg_blkg->shallow_depth; +} + +void ssg_blkcg_depth_updated(struct blk_mq_hw_ctx *hctx) +{ + struct request_queue *q = hctx->queue; + struct cgroup_subsys_state *pos_css; + struct blkcg_gq *blkg; + struct ssg_blkg *ssg_blkg; + struct ssg_blkcg *ssg_blkcg; + + rcu_read_lock(); + blkg_for_each_descendant_pre(blkg, pos_css, q->root_blkg) { + ssg_blkg = BLKG_TO_SSG_BLKG(blkg); + if (IS_ERR_OR_NULL(ssg_blkg)) + continue; + + ssg_blkcg = BLKCG_TO_SSG_BLKCG(blkg->blkcg); + if (IS_ERR_OR_NULL(ssg_blkcg)) + continue; + + atomic_set(&ssg_blkg->current_rqs, 0); + ssg_blkcg_set_shallow_depth(ssg_blkcg, ssg_blkg, hctx->sched_tags); + } + rcu_read_unlock(); +} + +void ssg_blkcg_inc_rq(struct blkcg_gq *blkg) +{ + struct ssg_blkg *ssg_blkg = BLKG_TO_SSG_BLKG(blkg); + + if (IS_ERR_OR_NULL(ssg_blkg)) + return; + + atomic_inc(&ssg_blkg->current_rqs); +} + +void ssg_blkcg_dec_rq(struct blkcg_gq *blkg) +{ + struct ssg_blkg *ssg_blkg = BLKG_TO_SSG_BLKG(blkg); + + if (IS_ERR_OR_NULL(ssg_blkg)) + return; + + atomic_dec(&ssg_blkg->current_rqs); +} + +static int ssg_blkcg_show_max_available_ratio(struct seq_file *sf, void *v) +{ + struct ssg_blkcg *ssg_blkcg = CSS_TO_SSG_BLKCG(seq_css(sf)); + + if (IS_ERR_OR_NULL(ssg_blkcg)) + return -EINVAL; + + seq_printf(sf, "%d\n", ssg_blkcg->max_available_ratio); + + return 0; +} + +static int ssg_blkcg_set_max_available_ratio(struct cgroup_subsys_state *css, + struct cftype *cftype, u64 ratio) +{ + struct blkcg *blkcg = css_to_blkcg(css); + struct ssg_blkcg *ssg_blkcg = CSS_TO_SSG_BLKCG(css); + struct blkcg_gq *blkg; + struct ssg_blkg *ssg_blkg; + struct blk_mq_hw_ctx *hctx; + unsigned long i; + + if (IS_ERR_OR_NULL(ssg_blkcg)) + return -EINVAL; + + if (ratio > 100) + return -EINVAL; + + spin_lock_irq(&blkcg->lock); + ssg_blkcg->max_available_ratio = ratio; + hlist_for_each_entry(blkg, &blkcg->blkg_list, blkcg_node) { + ssg_blkg = BLKG_TO_SSG_BLKG(blkg); + if (IS_ERR_OR_NULL(ssg_blkg)) + continue; + + queue_for_each_hw_ctx(blkg->q, hctx, i) + ssg_blkcg_set_shallow_depth(ssg_blkcg, ssg_blkg, + hctx->sched_tags); + } + spin_unlock_irq(&blkcg->lock); + + return 0; +} + +struct cftype ssg_blkg_files[] = { + { + .name = "ssg.max_available_ratio", + .flags = CFTYPE_NOT_ON_ROOT, + .seq_show = ssg_blkcg_show_max_available_ratio, + .write_u64 = ssg_blkcg_set_max_available_ratio, + }, + + {} /* terminate */ +}; + +static struct blkcg_policy ssg_blkcg_policy = { + .legacy_cftypes = ssg_blkg_files, + + .cpd_alloc_fn = ssg_blkcg_cpd_alloc, + .cpd_init_fn = ssg_blkcg_cpd_init, + .cpd_free_fn = ssg_blkcg_cpd_free, + + .pd_alloc_fn = ssg_blkcg_pd_alloc, + .pd_init_fn = ssg_blkcg_pd_init, + .pd_free_fn = ssg_blkcg_pd_free, +}; + +int ssg_blkcg_activate(struct request_queue *q) +{ + return blkcg_activate_policy(q, &ssg_blkcg_policy); +} + +void ssg_blkcg_deactivate(struct request_queue *q) +{ + blkcg_deactivate_policy(q, &ssg_blkcg_policy); +} + +int ssg_blkcg_init(void) +{ + return blkcg_policy_register(&ssg_blkcg_policy); +} + +void ssg_blkcg_exit(void) +{ + blkcg_policy_unregister(&ssg_blkcg_policy); +} diff --git a/block/ssg-iosched.c b/block/ssg-iosched.c new file mode 100644 index 000000000000..82033d544d64 --- /dev/null +++ b/block/ssg-iosched.c @@ -0,0 +1,900 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SamSung Generic I/O scheduler + * for the blk-mq scheduling framework + * + * Copyright (C) 2021 Jisoo Oh + * Copyright (C) 2021 Manjong Lee + * Copyright (C) 2021 Changheun Lee + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "blk.h" +#include "elevator.h" +#include "blk-mq.h" +#include "blk-mq-debugfs.h" +#include "blk-mq-tag.h" +#include "blk-mq-sched.h" +#include "ssg.h" +#include "blk-sec.h" + +#define MAX_ASYNC_WRITE_RQS 8 + +static const int read_expire = HZ / 2; /* max time before a read is submitted. */ +static const int write_expire = 5 * HZ; /* ditto for writes, these limits are SOFT! */ +static const int max_write_starvation = 2; /* max times reads can starve a write */ +static const int congestion_threshold = 90; /* percentage of congestion threshold */ +static const int max_tgroup_io_ratio = 50; /* maximum service ratio for each thread group */ +static const int max_async_write_ratio = 25; /* maximum service ratio for async write */ + +static inline struct rb_root *ssg_rb_root(struct ssg_data *ssg, struct request *rq) +{ + return &ssg->sort_list[rq_data_dir(rq)]; +} + +/* + * get the request after `rq' in sector-sorted order + */ +static inline struct request *ssg_latter_request(struct request *rq) +{ + struct rb_node *node = rb_next(&rq->rb_node); + + if (node) + return rb_entry_rq(node); + + return NULL; +} + +static void ssg_add_rq_rb(struct ssg_data *ssg, struct request *rq) +{ + struct rb_root *root = ssg_rb_root(ssg, rq); + + elv_rb_add(root, rq); +} + +static inline void ssg_del_rq_rb(struct ssg_data *ssg, struct request *rq) +{ + const int data_dir = rq_data_dir(rq); + + if (ssg->next_rq[data_dir] == rq) + ssg->next_rq[data_dir] = ssg_latter_request(rq); + + elv_rb_del(ssg_rb_root(ssg, rq), rq); +} + +static inline struct ssg_request_info *ssg_rq_info(struct ssg_data *ssg, + struct request *rq) +{ + if (unlikely(!ssg->rq_info)) + return NULL; + + if (unlikely(!rq)) + return NULL; + + if (unlikely(rq->internal_tag < 0)) + return NULL; + + if (unlikely(rq->internal_tag >= rq->q->nr_requests)) + return NULL; + + return &ssg->rq_info[rq->internal_tag]; +} + +/* + * remove rq from rbtree and fifo. + */ +static void ssg_remove_request(struct request_queue *q, struct request *rq) +{ + struct ssg_data *ssg = q->elevator->elevator_data; + + list_del_init(&rq->queuelist); + + /* + * We might not be on the rbtree, if we are doing an insert merge + */ + if (!RB_EMPTY_NODE(&rq->rb_node)) + ssg_del_rq_rb(ssg, rq); + + elv_rqhash_del(q, rq); + if (q->last_merge == rq) + q->last_merge = NULL; +} + +static void ssg_request_merged(struct request_queue *q, struct request *req, + enum elv_merge type) +{ + struct ssg_data *ssg = q->elevator->elevator_data; + + /* + * if the merge was a front merge, we need to reposition request + */ + if (type == ELEVATOR_FRONT_MERGE) { + elv_rb_del(ssg_rb_root(ssg, req), req); + ssg_add_rq_rb(ssg, req); + } +} + +static void ssg_merged_requests(struct request_queue *q, struct request *req, + struct request *next) +{ + /* + * if next expires before rq, assign its expire time to rq + * and move into next position (next will be deleted) in fifo + */ + if (!list_empty(&req->queuelist) && !list_empty(&next->queuelist)) { + if (time_before((unsigned long)next->fifo_time, + (unsigned long)req->fifo_time)) { + list_move(&req->queuelist, &next->queuelist); + req->fifo_time = next->fifo_time; + } + } + + /* + * kill knowledge of next, this one is a goner + */ + ssg_remove_request(q, next); +} + +/* + * move an entry to dispatch queue + */ +static void ssg_move_request(struct ssg_data *ssg, struct request *rq) +{ + const int data_dir = rq_data_dir(rq); + + ssg->next_rq[READ] = NULL; + ssg->next_rq[WRITE] = NULL; + ssg->next_rq[data_dir] = ssg_latter_request(rq); + + /* + * take it off the sort and fifo list + */ + ssg_remove_request(rq->q, rq); +} + +/* + * ssg_check_fifo returns 0 if there are no expired requests on the fifo, + * 1 otherwise. Requires !list_empty(&ssg->fifo_list[data_dir]) + */ +static inline int ssg_check_fifo(struct ssg_data *ssg, int ddir) +{ + struct request *rq = rq_entry_fifo(ssg->fifo_list[ddir].next); + + /* + * rq is expired! + */ + if (time_after_eq(jiffies, (unsigned long)rq->fifo_time)) + return 1; + + return 0; +} + +/* + * For the specified data direction, return the next request to + * dispatch using arrival ordered lists. + */ +static struct request *ssg_fifo_request(struct ssg_data *ssg, int data_dir) +{ + struct request *rq; + unsigned long flags; + + if (WARN_ON_ONCE(data_dir != READ && data_dir != WRITE)) + return NULL; + + if (list_empty(&ssg->fifo_list[data_dir])) + return NULL; + + rq = rq_entry_fifo(ssg->fifo_list[data_dir].next); + if (data_dir == READ || !blk_queue_is_zoned(rq->q)) + return rq; + + /* + * Look for a write request that can be dispatched, that is one with + * an unlocked target zone. + */ + spin_lock_irqsave(&ssg->zone_lock, flags); + list_for_each_entry(rq, &ssg->fifo_list[WRITE], queuelist) { + if (blk_req_can_dispatch_to_zone(rq)) + goto out; + } + rq = NULL; +out: + spin_unlock_irqrestore(&ssg->zone_lock, flags); + + return rq; +} + +/* + * For the specified data direction, return the next request to + * dispatch using sector position sorted lists. + */ +static struct request *ssg_next_request(struct ssg_data *ssg, int data_dir) +{ + struct request *rq; + unsigned long flags; + + if (WARN_ON_ONCE(data_dir != READ && data_dir != WRITE)) + return NULL; + + rq = ssg->next_rq[data_dir]; + if (!rq) + return NULL; + + if (data_dir == READ || !blk_queue_is_zoned(rq->q)) + return rq; + + /* + * Look for a write request that can be dispatched, that is one with + * an unlocked target zone. + */ + spin_lock_irqsave(&ssg->zone_lock, flags); + while (rq) { + if (blk_req_can_dispatch_to_zone(rq)) + break; + rq = ssg_latter_request(rq); + } + spin_unlock_irqrestore(&ssg->zone_lock, flags); + + return rq; +} + +/* + * ssg_dispatch_requests selects the best request according to + * read/write expire, etc + */ +static struct request *__ssg_dispatch_request(struct ssg_data *ssg) +{ + struct request *rq, *next_rq; + bool reads, writes; + int data_dir; + + if (!list_empty(&ssg->dispatch)) { + rq = list_first_entry(&ssg->dispatch, struct request, queuelist); + list_del_init(&rq->queuelist); + goto done; + } + + reads = !list_empty(&ssg->fifo_list[READ]); + writes = !list_empty(&ssg->fifo_list[WRITE]); + + /* + * select the appropriate data direction (read / write) + */ + + if (reads) { + BUG_ON(RB_EMPTY_ROOT(&ssg->sort_list[READ])); + + if (ssg_fifo_request(ssg, WRITE) && + (ssg->starved_writes++ >= ssg->max_write_starvation)) + goto dispatch_writes; + + data_dir = READ; + + goto dispatch_find_request; + } + + /* + * there are either no reads or writes have been starved + */ + + if (writes) { +dispatch_writes: + BUG_ON(RB_EMPTY_ROOT(&ssg->sort_list[WRITE])); + + ssg->starved_writes = 0; + + data_dir = WRITE; + + goto dispatch_find_request; + } + + return NULL; + +dispatch_find_request: + /* + * we are not running a batch, find best request for selected data_dir + */ + next_rq = ssg_next_request(ssg, data_dir); + if (ssg_check_fifo(ssg, data_dir) || !next_rq) { + /* + * A deadline has expired, the last request was in the other + * direction, or we have run out of higher-sectored requests. + * Start again from the request with the earliest expiry time. + */ + rq = ssg_fifo_request(ssg, data_dir); + } else { + /* + * The last req was the same dir and we have a next request in + * sort order. No expired requests so continue on from here. + */ + rq = next_rq; + } + + /* + * For a zoned block device, if we only have writes queued and none of + * them can be dispatched, rq will be NULL. + */ + if (!rq) + return NULL; + + /* + * rq is the selected appropriate request. + */ + ssg_move_request(ssg, rq); +done: + /* + * If the request needs its target zone locked, do it. + */ + blk_req_zone_write_lock(rq); + rq->rq_flags |= RQF_STARTED; + return rq; +} + +/* + * One confusing aspect here is that we get called for a specific + * hardware queue, but we may return a request that is for a + * different hardware queue. This is because ssg-iosched has shared + * state for all hardware queues, in terms of sorting, FIFOs, etc. + */ +static struct request *ssg_dispatch_request(struct blk_mq_hw_ctx *hctx) +{ + struct ssg_data *ssg = hctx->queue->elevator->elevator_data; + struct request *rq; + struct ssg_request_info *rqi; + + spin_lock(&ssg->lock); + rq = __ssg_dispatch_request(ssg); + spin_unlock(&ssg->lock); + + rqi = ssg_rq_info(ssg, rq); + if (likely(rqi)) { + rqi->sector = blk_rq_pos(rq); + rqi->data_size = blk_rq_bytes(rq); + } + + return rq; +} + +static void ssg_completed_request(struct request *rq, u64 now) +{ + struct ssg_data *ssg = rq->q->elevator->elevator_data; + struct ssg_request_info *rqi; + + rqi = ssg_rq_info(ssg, rq); + if (likely(rqi && rqi->sector == blk_rq_pos(rq))) { + ssg_stat_account_io_done(ssg, rq, rqi->data_size, now); + blk_sec_stat_account_io_complete(rq, rqi->data_size, rqi->pio); + } +} + +static void ssg_set_shallow_depth(struct ssg_data *ssg, struct blk_mq_tags *tags) +{ + unsigned int depth = tags->bitmap_tags.sb.depth; + unsigned int map_nr = tags->bitmap_tags.sb.map_nr; + + ssg->max_async_write_rqs = depth * max_async_write_ratio / 100U; + ssg->max_async_write_rqs = + min_t(int, ssg->max_async_write_rqs, MAX_ASYNC_WRITE_RQS); + ssg->async_write_shallow_depth = + max_t(unsigned int, ssg->max_async_write_rqs / map_nr, 1); + + ssg->max_tgroup_rqs = depth * max_tgroup_io_ratio / 100U; + ssg->tgroup_shallow_depth = + max_t(unsigned int, ssg->max_tgroup_rqs / map_nr, 1); +} + +static void ssg_depth_updated(struct blk_mq_hw_ctx *hctx) +{ + struct request_queue *q = hctx->queue; + struct ssg_data *ssg = q->elevator->elevator_data; + struct blk_mq_tags *tags = hctx->sched_tags; + unsigned int depth = tags->bitmap_tags.sb.depth; + + ssg->congestion_threshold_rqs = depth * congestion_threshold / 100U; + + kfree(ssg->rq_info); + ssg->rq_info = kmalloc_array(depth, sizeof(struct ssg_request_info), + GFP_KERNEL | __GFP_ZERO); + if (ZERO_OR_NULL_PTR(ssg->rq_info)) + ssg->rq_info = NULL; + + ssg_set_shallow_depth(ssg, tags); + sbitmap_queue_min_shallow_depth(&tags->bitmap_tags, + ssg->async_write_shallow_depth); + + ssg_blkcg_depth_updated(hctx); + ssg_wb_depth_updated(hctx); +} + +static inline bool ssg_op_is_async_write(unsigned int op) +{ + return (op & REQ_OP_MASK) == REQ_OP_WRITE && !op_is_sync(op); +} + +static unsigned int ssg_async_write_shallow_depth(unsigned int op, + struct blk_mq_alloc_data *data) +{ + struct ssg_data *ssg = data->q->elevator->elevator_data; + + if (!ssg_op_is_async_write(op)) + return 0; + + if (atomic_read(&ssg->async_write_rqs) < ssg->max_async_write_rqs) + return 0; + + return ssg->async_write_shallow_depth; +} + +static unsigned int ssg_tgroup_shallow_depth(struct blk_mq_alloc_data *data) +{ + struct ssg_data *ssg = data->q->elevator->elevator_data; + pid_t tgid = task_tgid_nr(current->group_leader); + int nr_requests = data->q->nr_requests; + int tgroup_rqs = 0; + int i; + + if (unlikely(!ssg->rq_info)) + return 0; + + for (i = 0; i < nr_requests; i++) + if (tgid == ssg->rq_info[i].tgid) + tgroup_rqs++; + + if (tgroup_rqs < ssg->max_tgroup_rqs) + return 0; + + return ssg->tgroup_shallow_depth; +} + +static void ssg_limit_depth(unsigned int op, struct blk_mq_alloc_data *data) +{ + struct ssg_data *ssg = data->q->elevator->elevator_data; + unsigned int shallow_depth = ssg_blkcg_shallow_depth(data->q); + + shallow_depth = min_not_zero(shallow_depth, + ssg_async_write_shallow_depth(op, data)); + + if (atomic_read(&ssg->allocated_rqs) > ssg->congestion_threshold_rqs) + shallow_depth = min_not_zero(shallow_depth, + ssg_tgroup_shallow_depth(data)); + + data->shallow_depth = shallow_depth; +} + +static int ssg_init_hctx(struct blk_mq_hw_ctx *hctx, unsigned int hctx_idx) +{ + struct ssg_data *ssg = hctx->queue->elevator->elevator_data; + struct blk_mq_tags *tags = hctx->sched_tags; + + ssg_set_shallow_depth(ssg, tags); + sbitmap_queue_min_shallow_depth(&tags->bitmap_tags, + ssg->async_write_shallow_depth); + + return 0; +} + +static void ssg_exit_queue(struct elevator_queue *e) +{ + struct ssg_data *ssg = e->elevator_data; + + ssg_blkcg_deactivate(ssg->queue); + + BUG_ON(!list_empty(&ssg->fifo_list[READ])); + BUG_ON(!list_empty(&ssg->fifo_list[WRITE])); + + ssg_stat_exit(ssg); + ssg_wb_exit(ssg); + blk_sec_stat_account_exit(e); + + kfree(ssg->rq_info); + kfree(ssg); +} + +/* + * initialize elevator private data (ssg_data). + */ +static int ssg_init_queue(struct request_queue *q, struct elevator_type *e) +{ + struct ssg_data *ssg; + struct elevator_queue *eq; + + eq = elevator_alloc(q, e); + if (!eq) + return -ENOMEM; + + ssg = kzalloc_node(sizeof(*ssg), GFP_KERNEL, q->node); + if (!ssg) { + kobject_put(&eq->kobj); + return -ENOMEM; + } + eq->elevator_data = ssg; + + ssg->queue = q; + INIT_LIST_HEAD(&ssg->fifo_list[READ]); + INIT_LIST_HEAD(&ssg->fifo_list[WRITE]); + ssg->sort_list[READ] = RB_ROOT; + ssg->sort_list[WRITE] = RB_ROOT; + ssg->fifo_expire[READ] = read_expire; + ssg->fifo_expire[WRITE] = write_expire; + ssg->max_write_starvation = max_write_starvation; + ssg->front_merges = 1; + + atomic_set(&ssg->allocated_rqs, 0); + atomic_set(&ssg->async_write_rqs, 0); + ssg->congestion_threshold_rqs = + q->nr_requests * congestion_threshold / 100U; + ssg->rq_info = kmalloc_array(q->nr_requests, + sizeof(struct ssg_request_info), + GFP_KERNEL | __GFP_ZERO); + if (ZERO_OR_NULL_PTR(ssg->rq_info)) + ssg->rq_info = NULL; + + spin_lock_init(&ssg->lock); + spin_lock_init(&ssg->zone_lock); + INIT_LIST_HEAD(&ssg->dispatch); + + ssg_blkcg_activate(q); + + q->elevator = eq; + + ssg_stat_init(ssg); + blk_stat_enable_accounting(q); + blk_sec_stat_account_init(q); + ssg_wb_init(ssg); + + return 0; +} + +static int ssg_request_merge(struct request_queue *q, struct request **rq, + struct bio *bio) +{ + struct ssg_data *ssg = q->elevator->elevator_data; + sector_t sector = bio_end_sector(bio); + struct request *__rq; + + if (!ssg->front_merges) + return ELEVATOR_NO_MERGE; + + __rq = elv_rb_find(&ssg->sort_list[bio_data_dir(bio)], sector); + if (__rq) { + BUG_ON(sector != blk_rq_pos(__rq)); + + if (elv_bio_merge_ok(__rq, bio)) { + *rq = __rq; + return ELEVATOR_FRONT_MERGE; + } + } + + return ELEVATOR_NO_MERGE; +} + +static bool ssg_bio_merge(struct request_queue *q, struct bio *bio, + unsigned int nr_segs) +{ + struct ssg_data *ssg = q->elevator->elevator_data; + struct request *free = NULL; + bool ret; + + spin_lock(&ssg->lock); + ret = blk_mq_sched_try_merge(q, bio, nr_segs, &free); + spin_unlock(&ssg->lock); + + if (free) + blk_mq_free_request(free); + + return ret; +} + +/* + * add rq to rbtree and fifo + */ +static void ssg_insert_request(struct blk_mq_hw_ctx *hctx, struct request *rq, + bool at_head) +{ + struct request_queue *q = hctx->queue; + struct ssg_data *ssg = q->elevator->elevator_data; + const int data_dir = rq_data_dir(rq); + LIST_HEAD(free); + + /* + * This may be a requeue of a write request that has locked its + * target zone. If it is the case, this releases the zone lock. + */ + blk_req_zone_write_unlock(rq); + + if (blk_mq_sched_try_insert_merge(q, rq, &free)) { + blk_mq_free_requests(&free); + return; + } + + trace_block_rq_insert(rq); + + if (at_head || blk_rq_is_passthrough(rq)) { + if (at_head) + list_add(&rq->queuelist, &ssg->dispatch); + else + list_add_tail(&rq->queuelist, &ssg->dispatch); + } else { + ssg_add_rq_rb(ssg, rq); + + if (rq_mergeable(rq)) { + elv_rqhash_add(q, rq); + if (!q->last_merge) + q->last_merge = rq; + } + + /* + * set expire time and add to fifo list + */ + rq->fifo_time = jiffies + ssg->fifo_expire[data_dir]; + list_add_tail(&rq->queuelist, &ssg->fifo_list[data_dir]); + } +} + +static void ssg_insert_requests(struct blk_mq_hw_ctx *hctx, + struct list_head *list, bool at_head) +{ + struct request_queue *q = hctx->queue; + struct ssg_data *ssg = q->elevator->elevator_data; + + spin_lock(&ssg->lock); + while (!list_empty(list)) { + struct request *rq; + + rq = list_first_entry(list, struct request, queuelist); + list_del_init(&rq->queuelist); + ssg_insert_request(hctx, rq, at_head); + } + spin_unlock(&ssg->lock); +} + +/* + * Nothing to do here. This is defined only to ensure that .finish_request + * method is called upon request completion. + */ +static void ssg_prepare_request(struct request *rq) +{ + struct ssg_data *ssg = rq->q->elevator->elevator_data; + struct ssg_request_info *rqi; + + atomic_inc(&ssg->allocated_rqs); + + ssg_wb_ctrl(ssg, rq); + + rqi = ssg_rq_info(ssg, rq); + if (likely(rqi)) { + rqi->tgid = task_tgid_nr(current->group_leader); + + rcu_read_lock(); + rqi->blkg = blkg_lookup(css_to_blkcg(curr_css()), rq->q); + ssg_blkcg_inc_rq(rqi->blkg); + rcu_read_unlock(); + + blk_sec_stat_account_io_prepare(rq, &rqi->pio); + } + + if (ssg_op_is_async_write(rq->cmd_flags)) + atomic_inc(&ssg->async_write_rqs); +} + +/* + * For zoned block devices, write unlock the target zone of + * completed write requests. Do this while holding the zone lock + * spinlock so that the zone is never unlocked while ssg_fifo_request() + * or ssg_next_request() are executing. This function is called for + * all requests, whether or not these requests complete successfully. + * + * For a zoned block device, __ssg_dispatch_request() may have stopped + * dispatching requests if all the queued requests are write requests directed + * at zones that are already locked due to on-going write requests. To ensure + * write request dispatch progress in this case, mark the queue as needing a + * restart to ensure that the queue is run again after completion of the + * request and zones being unlocked. + */ +static void ssg_finish_request(struct request *rq) +{ + struct request_queue *q = rq->q; + struct ssg_data *ssg = q->elevator->elevator_data; + struct ssg_request_info *rqi; + + if (blk_queue_is_zoned(q)) { + unsigned long flags; + + spin_lock_irqsave(&ssg->zone_lock, flags); + blk_req_zone_write_unlock(rq); + if (!list_empty(&ssg->fifo_list[WRITE])) + blk_mq_sched_mark_restart_hctx(rq->mq_hctx); + spin_unlock_irqrestore(&ssg->zone_lock, flags); + } + + if (unlikely(!(rq->rq_flags & RQF_ELVPRIV))) + return; + + atomic_dec(&ssg->allocated_rqs); + + rqi = ssg_rq_info(ssg, rq); + if (likely(rqi)) { + rqi->tgid = 0; + + ssg_blkcg_dec_rq(rqi->blkg); + rqi->blkg = NULL; + + blk_sec_stat_account_io_finish(rq, &rqi->pio); + } + + if (ssg_op_is_async_write(rq->cmd_flags)) + atomic_dec(&ssg->async_write_rqs); +} + +static bool ssg_has_work(struct blk_mq_hw_ctx *hctx) +{ + struct ssg_data *ssg = hctx->queue->elevator->elevator_data; + + return !list_empty_careful(&ssg->dispatch) || + !list_empty_careful(&ssg->fifo_list[0]) || + !list_empty_careful(&ssg->fifo_list[1]); +} + +/* + * sysfs parts below + */ +static ssize_t ssg_var_show(int var, char *page) +{ + return sprintf(page, "%d\n", var); +} + +static void ssg_var_store(int *var, const char *page) +{ + long val; + + if (!kstrtol(page, 10, &val)) + *var = val; +} + +#define SHOW_FUNCTION(__FUNC, __VAR, __CONV) \ +static ssize_t __FUNC(struct elevator_queue *e, char *page) \ +{ \ + struct ssg_data *ssg = e->elevator_data; \ + int __data = __VAR; \ + if (__CONV) \ + __data = jiffies_to_msecs(__data); \ + return ssg_var_show(__data, (page)); \ +} +SHOW_FUNCTION(ssg_read_expire_show, ssg->fifo_expire[READ], 1); +SHOW_FUNCTION(ssg_write_expire_show, ssg->fifo_expire[WRITE], 1); +SHOW_FUNCTION(ssg_max_write_starvation_show, ssg->max_write_starvation, 0); +SHOW_FUNCTION(ssg_front_merges_show, ssg->front_merges, 0); +SHOW_FUNCTION(ssg_tgroup_shallow_depth_show, ssg->tgroup_shallow_depth, 0); +SHOW_FUNCTION(ssg_async_write_shallow_depth_show, ssg->async_write_shallow_depth, 0); +#undef SHOW_FUNCTION + +#define STORE_FUNCTION(__FUNC, __PTR, MIN, MAX, __CONV) \ +static ssize_t __FUNC(struct elevator_queue *e, const char *page, size_t count) \ +{ \ + struct ssg_data *ssg = e->elevator_data; \ + int __data; \ + ssg_var_store(&__data, (page)); \ + if (__data < (MIN)) \ + __data = (MIN); \ + else if (__data > (MAX)) \ + __data = (MAX); \ + if (__CONV) \ + *(__PTR) = msecs_to_jiffies(__data); \ + else \ + *(__PTR) = __data; \ + return count; \ +} +STORE_FUNCTION(ssg_read_expire_store, &ssg->fifo_expire[READ], 0, INT_MAX, 1); +STORE_FUNCTION(ssg_write_expire_store, &ssg->fifo_expire[WRITE], 0, INT_MAX, 1); +STORE_FUNCTION(ssg_max_write_starvation_store, &ssg->max_write_starvation, INT_MIN, INT_MAX, 0); +STORE_FUNCTION(ssg_front_merges_store, &ssg->front_merges, 0, 1, 0); +#undef STORE_FUNCTION + +#define SSG_ATTR(name) \ + __ATTR(name, 0644, ssg_##name##_show, ssg_##name##_store) +#define SSG_ATTR_RO(name) \ + __ATTR(name, 0444, ssg_##name##_show, NULL) +#define SSG_STAT_ATTR_RO(name) \ + __ATTR(name, 0444, ssg_stat_##name##_show, NULL) + +static struct elv_fs_entry ssg_attrs[] = { + SSG_ATTR(read_expire), + SSG_ATTR(write_expire), + SSG_ATTR(max_write_starvation), + SSG_ATTR(front_merges), + SSG_ATTR_RO(tgroup_shallow_depth), + SSG_ATTR_RO(async_write_shallow_depth), + + SSG_STAT_ATTR_RO(read_latency), + SSG_STAT_ATTR_RO(write_latency), + SSG_STAT_ATTR_RO(flush_latency), + SSG_STAT_ATTR_RO(discard_latency), + SSG_STAT_ATTR_RO(inflight), + SSG_STAT_ATTR_RO(rqs_info), + +#if IS_ENABLED(CONFIG_MQ_IOSCHED_SSG_WB) + SSG_ATTR(wb_on_rqs), + SSG_ATTR(wb_off_rqs), + SSG_ATTR(wb_on_dirty_bytes), + SSG_ATTR(wb_off_dirty_bytes), + SSG_ATTR(wb_on_sync_write_bytes), + SSG_ATTR(wb_off_sync_write_bytes), + SSG_ATTR(wb_on_dirty_busy_written_bytes), + SSG_ATTR(wb_on_dirty_busy_msecs), + SSG_ATTR(wb_off_delay_msecs), + SSG_ATTR_RO(wb_triggered), +#endif + + __ATTR_NULL +}; + +static struct elevator_type ssg_iosched = { + .ops = { + .insert_requests = ssg_insert_requests, + .dispatch_request = ssg_dispatch_request, + .completed_request = ssg_completed_request, + .prepare_request = ssg_prepare_request, + .finish_request = ssg_finish_request, + .next_request = elv_rb_latter_request, + .former_request = elv_rb_former_request, + .bio_merge = ssg_bio_merge, + .request_merge = ssg_request_merge, + .requests_merged = ssg_merged_requests, + .request_merged = ssg_request_merged, + .has_work = ssg_has_work, + .limit_depth = ssg_limit_depth, + .depth_updated = ssg_depth_updated, + .init_hctx = ssg_init_hctx, + .init_sched = ssg_init_queue, + .exit_sched = ssg_exit_queue, + }, + + .elevator_attrs = ssg_attrs, + .elevator_name = "ssg", + .elevator_alias = "ssg", + .elevator_features = ELEVATOR_F_ZBD_SEQ_WRITE, + .elevator_owner = THIS_MODULE, +}; +MODULE_ALIAS("ssg"); + +static int __init ssg_iosched_init(void) +{ + int ret; + + ret = elv_register(&ssg_iosched); + if (ret) + return ret; + + ret = ssg_blkcg_init(); + if (ret) { + elv_unregister(&ssg_iosched); + return ret; + } + + return ret; +} + +static void __exit ssg_iosched_exit(void) +{ + ssg_blkcg_exit(); + elv_unregister(&ssg_iosched); +} + +module_init(ssg_iosched_init); +module_exit(ssg_iosched_exit); + +MODULE_AUTHOR("Jisoo Oh"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("SSG IO Scheduler"); diff --git a/block/ssg-stat.c b/block/ssg-stat.c new file mode 100644 index 000000000000..0d9877584d5e --- /dev/null +++ b/block/ssg-stat.c @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Statistics of SamSung Generic I/O scheduler + * + * Copyright (C) 2021 Changheun Lee + */ + +#include +#include +#include + +#include "elevator.h" +#include "blk-mq.h" +#include "blk-mq-tag.h" +#include "ssg.h" + +#define IO_TYPES (REQ_OP_DISCARD + 1) + +static unsigned int byte_table[] = { + 4096, // 4KB + 32768, // 32KB + 65536, // 64KB + 131072, // 128KB + 524288, // 512KB + 1048576, // 1MB + + UINT_MAX // should be last in this array +}; +#define BYTE_TABLE_SIZE (sizeof(byte_table)/sizeof(unsigned int)) + +static u64 nsec_table[] = { + 500000, // 0.5ms + 1000000, // 1ms + 2000000, // 2ms + 3000000, // 3ms + 4000000, // 4ms + 5000000, // 5ms + 10000000, // 10ms + 20000000, // 20ms + + ULLONG_MAX // should be last in this array +}; +#define NSEC_TABLE_SIZE (sizeof(nsec_table)/sizeof(u64)) + +struct ssg_stats { + u64 io_latency_cnt[IO_TYPES][BYTE_TABLE_SIZE][NSEC_TABLE_SIZE]; +}; + +struct ssg_bt_tags_iter_data { + struct blk_mq_tags *tags; + void *data; + bool reserved; +}; + +static unsigned int byte_to_index(unsigned int byte) +{ + unsigned int idx; + + for (idx = 0; idx < BYTE_TABLE_SIZE; idx++) + if (byte <= byte_table[idx]) + return idx; + + return BYTE_TABLE_SIZE - 1; +} + +static unsigned int nsec_to_index(u64 nsec) +{ + unsigned int idx; + + for (idx = 0; idx < NSEC_TABLE_SIZE; idx++) + if (nsec <= nsec_table[idx]) + return idx; + + return NSEC_TABLE_SIZE - 1; +} + +static void update_io_latency(struct ssg_data *ssg, struct request *rq, + unsigned int data_size, u64 now) +{ + struct ssg_stats *stats = this_cpu_ptr(ssg->stats); + int type, byte_idx, ns_idx; + + if (req_op(rq) > REQ_OP_DISCARD) + return; + + if (rq->io_start_time_ns > now) + return; + + type = req_op(rq); + byte_idx = byte_to_index(data_size); + ns_idx = nsec_to_index(now - rq->io_start_time_ns); + stats->io_latency_cnt[type][byte_idx][ns_idx]++; +} + +void ssg_stat_account_io_done(struct ssg_data *ssg, struct request *rq, + unsigned int data_size, u64 now) +{ + if (unlikely(!ssg->stats)) + return; + + update_io_latency(ssg, rq, data_size, now); +} + +static int print_io_latency(struct ssg_stats __percpu *stats, int io_type, + char *buf, int buf_size) +{ + u64 sum[BYTE_TABLE_SIZE][NSEC_TABLE_SIZE] = { 0, }; + int cpu; + int len = 0; + int byte_idx, ns_idx; + + for_each_possible_cpu(cpu) { + struct ssg_stats *s = per_cpu_ptr(stats, cpu); + + for (byte_idx = 0; byte_idx < BYTE_TABLE_SIZE; byte_idx++) + for (ns_idx = 0; ns_idx < NSEC_TABLE_SIZE; ns_idx++) + sum[byte_idx][ns_idx] += + s->io_latency_cnt[io_type][byte_idx][ns_idx]; + } + + for (byte_idx = 0; byte_idx < BYTE_TABLE_SIZE; byte_idx++) { + len += snprintf(buf + len, buf_size - len, "%u:", + byte_table[byte_idx] / 1024); + for (ns_idx = 0; ns_idx < NSEC_TABLE_SIZE; ns_idx++) + len += snprintf(buf + len, buf_size - len, " %llu", + sum[byte_idx][ns_idx]); + len += snprintf(buf + len, buf_size - len, "\n"); + } + + return len; +} + +#define IO_LATENCY_SHOW_FUNC(__FUNC, __IO_TYPE) \ +ssize_t __FUNC(struct elevator_queue *e, char *page) \ +{ \ + struct ssg_data *ssg = e->elevator_data; \ + if (unlikely(!ssg->stats)) \ + return 0; \ + return print_io_latency(ssg->stats, \ + __IO_TYPE, page, PAGE_SIZE); \ +} +IO_LATENCY_SHOW_FUNC(ssg_stat_read_latency_show, REQ_OP_READ); +IO_LATENCY_SHOW_FUNC(ssg_stat_write_latency_show, REQ_OP_WRITE); +IO_LATENCY_SHOW_FUNC(ssg_stat_flush_latency_show, REQ_OP_FLUSH); +IO_LATENCY_SHOW_FUNC(ssg_stat_discard_latency_show, REQ_OP_DISCARD); + +static bool ssg_count_inflight(struct sbitmap *bitmap, unsigned int bitnr, void *data) +{ + struct ssg_bt_tags_iter_data *iter_data = data; + struct blk_mq_tags *tags = iter_data->tags; + unsigned int *inflight = iter_data->data; + bool reserved = iter_data->reserved; + struct request *rq; + + if (!reserved) + bitnr += tags->nr_reserved_tags; + + rq = tags->static_rqs[bitnr]; + + if (!rq) + return true; + + if (req_op(rq) < IO_TYPES) + inflight[req_op(rq)]++; + + return true; +} + +static void get_ssg_inflight(struct request_queue *q, unsigned int *inflight) +{ + struct blk_mq_hw_ctx *hctx; + struct blk_mq_tags *tags; + unsigned long i; + struct ssg_bt_tags_iter_data iter_data = { + .data = inflight, + }; + + if (blk_mq_is_shared_tags(q->tag_set->flags)) { + tags = q->sched_shared_tags; + iter_data.tags = tags; + + if (tags->nr_reserved_tags) { + iter_data.reserved = true; + sbitmap_for_each_set(&tags->breserved_tags.sb, + ssg_count_inflight, &iter_data); + } + + iter_data.reserved = false; + sbitmap_for_each_set(&tags->bitmap_tags.sb, + ssg_count_inflight, &iter_data); + } else { + queue_for_each_hw_ctx(q, hctx, i) { + /* + * If no software queues are currently mapped to this + * hardware queue, there's nothing to check + */ + if (!blk_mq_hw_queue_mapped(hctx)) + continue; + + tags = hctx->sched_tags; + iter_data.tags = tags; + + if (tags->nr_reserved_tags) { + iter_data.reserved = true; + sbitmap_for_each_set(&tags->breserved_tags.sb, + ssg_count_inflight, &iter_data); + } + + iter_data.reserved = false; + sbitmap_for_each_set(&tags->bitmap_tags.sb, + ssg_count_inflight, &iter_data); + } + } +} + +ssize_t ssg_stat_inflight_show(struct elevator_queue *e, char *page) +{ + struct ssg_data *ssg = e->elevator_data; + unsigned int inflight[IO_TYPES] = {0, }; + + if (unlikely(!ssg->stats)) + return 0; + + get_ssg_inflight(ssg->queue, inflight); + + return snprintf(page, PAGE_SIZE, "%u %u %u\n", inflight[REQ_OP_READ], + inflight[REQ_OP_WRITE], inflight[REQ_OP_DISCARD]); +} + +static bool print_ssg_rq_info(struct sbitmap *bitmap, unsigned int bitnr, void *data) +{ + struct ssg_bt_tags_iter_data *iter_data = data; + struct blk_mq_tags *tags = iter_data->tags; + bool reserved = iter_data->reserved; + char *page = iter_data->data; + struct request *rq; + int len = strlen(page); + + if (!reserved) + bitnr += tags->nr_reserved_tags; + + rq = tags->static_rqs[bitnr]; + + if (!rq) + return true; + + scnprintf(page + len, PAGE_SIZE - len, "%d %d %x %x %llu %u %llu %d\n", + rq->tag, rq->internal_tag, req_op(rq), rq->rq_flags, + blk_rq_pos(rq), blk_rq_bytes(rq), rq->start_time_ns, rq->state); + + return true; +} + +static void print_ssg_rqs(struct request_queue *q, char *page) +{ + struct blk_mq_hw_ctx *hctx; + struct blk_mq_tags *tags; + unsigned long i; + struct ssg_bt_tags_iter_data iter_data = { + .data = page, + }; + + if (blk_mq_is_shared_tags(q->tag_set->flags)) { + tags = q->sched_shared_tags; + iter_data.tags = tags; + + if (tags->nr_reserved_tags) { + iter_data.reserved = true; + sbitmap_for_each_set(&tags->breserved_tags.sb, + print_ssg_rq_info, &iter_data); + } + + iter_data.reserved = false; + sbitmap_for_each_set(&tags->bitmap_tags.sb, + print_ssg_rq_info, &iter_data); + } else { + queue_for_each_hw_ctx(q, hctx, i) { + /* + * If no software queues are currently mapped to this + * hardware queue, there's nothing to check + */ + if (!blk_mq_hw_queue_mapped(hctx)) + continue; + + tags = hctx->sched_tags; + iter_data.tags = tags; + + if (tags->nr_reserved_tags) { + iter_data.reserved = true; + sbitmap_for_each_set(&tags->breserved_tags.sb, + print_ssg_rq_info, &iter_data); + } + + iter_data.reserved = false; + sbitmap_for_each_set(&tags->bitmap_tags.sb, + print_ssg_rq_info, &iter_data); + } + } +} + +ssize_t ssg_stat_rqs_info_show(struct elevator_queue *e, char *page) +{ + struct ssg_data *ssg = e->elevator_data; + + if (unlikely(!ssg->stats)) + return 0; + + print_ssg_rqs(ssg->queue, page); + + return strlen(page); +} + +int ssg_stat_init(struct ssg_data *ssg) +{ + ssg->stats = alloc_percpu_gfp(struct ssg_stats, + GFP_KERNEL | __GFP_ZERO); + if (!ssg->stats) + return -ENOMEM; + + return 0; +} + +void ssg_stat_exit(struct ssg_data *ssg) +{ + if (ssg->stats) + free_percpu(ssg->stats); +} diff --git a/block/ssg-wb.c b/block/ssg-wb.c new file mode 100644 index 000000000000..5ee4a07ef21f --- /dev/null +++ b/block/ssg-wb.c @@ -0,0 +1,367 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Write Booster of SamSung Generic I/O scheduler + * + * Copyright (C) 2022 Jisoo Oh + * Copyright (C) 2023 Changheun Lee + */ + +#include +#include +#include + +#include "elevator.h" +#include "blk-mq.h" +#include "blk-mq-tag.h" +#include "blk-sec.h" +#include "ssg.h" + +struct ssg_wb_data { + int on_rqs; + int off_rqs; + int on_dirty_bytes; + int off_dirty_bytes; + int on_sync_write_bytes; + int off_sync_write_bytes; + int on_dirty_busy_written_pages; + int on_dirty_busy_jiffies; + int off_delay_jiffies; + + unsigned long dirty_busy_start_jiffies; + unsigned long dirty_busy_start_written_pages; + + atomic_t wb_triggered; + + struct request_queue *queue; + struct delayed_work wb_ctrl_work; + struct delayed_work wb_deferred_off_work; +}; + +struct io_amount_data { + unsigned int allocated_rqs; + unsigned int sync_write_bytes; + unsigned long dirty_bytes; +}; + +struct ssg_wb_iter_data { + struct blk_mq_tags *tags; + void *data; + bool reserved; +}; + +static const int _on_rqs_ratio = 90; +static const int _off_rqs_ratio = 40; +static const int _on_dirty_bytes = 50*1024*1024; +static const int _off_dirty_bytes = 25*1024*1024; +static const int _on_sync_write_bytes = 2*1024*1024; +static const int _off_sync_write_bytes = 1*1024*1024; +static const int _on_dirty_busy_written_bytes = 100*1024*1024; +static const int _on_dirty_busy_msecs = 1000; +static const int _off_delay_msecs = 5000; + +#define may_wb_on(io_amount, ssg_wb) \ + ((io_amount).allocated_rqs >= (ssg_wb)->on_rqs || \ + (io_amount).dirty_bytes >= (ssg_wb)->on_dirty_bytes || \ + (io_amount).sync_write_bytes >= (ssg_wb)->on_sync_write_bytes || \ + (ssg_wb->dirty_busy_start_written_pages && \ + (global_node_page_state(NR_WRITTEN) - (ssg_wb)->dirty_busy_start_written_pages) \ + > (ssg_wb)->on_dirty_busy_written_pages)) +#define may_wb_off(io_amount, ssg_wb) \ + ((io_amount).allocated_rqs < (ssg_wb)->off_rqs && \ + (io_amount).dirty_bytes < (ssg_wb)->off_dirty_bytes && \ + (io_amount).sync_write_bytes < (ssg_wb)->off_sync_write_bytes) + +static void trigger_wb_on(struct ssg_wb_data *ssg_wb) +{ + cancel_delayed_work_sync(&ssg_wb->wb_deferred_off_work); + blk_sec_wb_ctrl(true, WB_REQ_IOSCHED); + atomic_set(&ssg_wb->wb_triggered, true); +} + +static void wb_off_work(struct work_struct *work) +{ + blk_sec_wb_ctrl(false, WB_REQ_IOSCHED); +} + +static void trigger_wb_off(struct ssg_wb_data *ssg_wb) +{ + queue_delayed_work(blk_sec_common_wq, + &ssg_wb->wb_deferred_off_work, ssg_wb->off_delay_jiffies); + + atomic_set(&ssg_wb->wb_triggered, false); +} + +static bool wb_count_io(struct sbitmap *bitmap, unsigned int bitnr, void *data) +{ + struct ssg_wb_iter_data *iter_data = data; + struct blk_mq_tags *tags = iter_data->tags; + struct io_amount_data *io_amount = iter_data->data; + bool reserved = iter_data->reserved; + struct request *rq; + + if (!reserved) + bitnr += tags->nr_reserved_tags; + + rq = tags->static_rqs[bitnr]; + if (!rq) + return true; + + io_amount->allocated_rqs++; + if (req_op(rq) == REQ_OP_WRITE && rq->cmd_flags & REQ_SYNC) + io_amount->sync_write_bytes += blk_rq_bytes(rq); + + return true; +} + +static void wb_get_io_amount(struct request_queue *q, struct io_amount_data *io_amount) +{ + struct blk_mq_hw_ctx *hctx; + struct blk_mq_tags *tags; + unsigned long i; + struct ssg_wb_iter_data iter_data = { + .data = io_amount, + }; + + if (blk_mq_is_shared_tags(q->tag_set->flags)) { + tags = q->sched_shared_tags; + iter_data.tags = tags; + + if (tags->nr_reserved_tags) { + iter_data.reserved = true; + sbitmap_for_each_set(&tags->breserved_tags.sb, + wb_count_io, &iter_data); + } + iter_data.reserved = false; + sbitmap_for_each_set(&tags->bitmap_tags.sb, + wb_count_io, &iter_data); + } else { + queue_for_each_hw_ctx(q, hctx, i) { + /* + * If no software queues are currently mapped to this + * hardware queue, there's nothing to check + */ + if (!blk_mq_hw_queue_mapped(hctx)) + continue; + + tags = hctx->sched_tags; + iter_data.tags = tags; + + if (tags->nr_reserved_tags) { + iter_data.reserved = true; + sbitmap_for_each_set(&tags->breserved_tags.sb, + wb_count_io, &iter_data); + } + iter_data.reserved = false; + sbitmap_for_each_set(&tags->bitmap_tags.sb, + wb_count_io, &iter_data); + } + } +} + +static void wb_ctrl_work(struct work_struct *work) +{ + struct ssg_wb_data *ssg_wb = container_of(to_delayed_work(work), + struct ssg_wb_data, wb_ctrl_work); + struct io_amount_data io_amount = { + .allocated_rqs = 0, + .sync_write_bytes = 0, + }; + + wb_get_io_amount(ssg_wb->queue, &io_amount); + io_amount.dirty_bytes = (global_node_page_state(NR_FILE_DIRTY) + + global_node_page_state(NR_WRITEBACK)) * PAGE_SIZE; + + if (time_after(jiffies, ssg_wb->dirty_busy_start_jiffies + ssg_wb->on_dirty_busy_jiffies)) { + ssg_wb->dirty_busy_start_jiffies = 0; + ssg_wb->dirty_busy_start_written_pages = 0; + } + + if (!ssg_wb->dirty_busy_start_jiffies && io_amount.dirty_bytes >= ssg_wb->off_dirty_bytes) { + ssg_wb->dirty_busy_start_jiffies = jiffies; + ssg_wb->dirty_busy_start_written_pages = global_node_page_state(NR_WRITTEN); + } + + if (atomic_read(&ssg_wb->wb_triggered)) { + if (may_wb_off(io_amount, ssg_wb)) + trigger_wb_off(ssg_wb); + } else { + if (may_wb_on(io_amount, ssg_wb)) + trigger_wb_on(ssg_wb); + } + + if (atomic_read(&ssg_wb->wb_triggered)) + queue_delayed_work(blk_sec_common_wq, &ssg_wb->wb_ctrl_work, + ssg_wb->off_delay_jiffies); +} + +void ssg_wb_ctrl(struct ssg_data *ssg, struct request *rq) +{ + struct ssg_wb_data *ssg_wb = ssg->wb_data; + + if (!ssg_wb) + return; + + if (atomic_read(&ssg_wb->wb_triggered)) + return; + + if (((rq->cmd_flags & REQ_OP_MASK) == REQ_OP_READ) + && atomic_read(&ssg->allocated_rqs) < ssg_wb->on_rqs) + return; + + if (!work_busy(&ssg_wb->wb_ctrl_work.work)) + queue_delayed_work(blk_sec_common_wq, &ssg_wb->wb_ctrl_work, 0); +} + +void ssg_wb_depth_updated(struct blk_mq_hw_ctx *hctx) +{ + struct request_queue *q = hctx->queue; + struct ssg_data *ssg = q->elevator->elevator_data; + struct ssg_wb_data *ssg_wb = ssg->wb_data; + int nr_rqs; + + if (!ssg_wb) + return; + + nr_rqs = hctx->sched_tags->bitmap_tags.sb.depth; + ssg_wb->on_rqs = nr_rqs * _on_rqs_ratio / 100U; + ssg_wb->off_rqs = nr_rqs * _off_rqs_ratio / 100U; +} + +void ssg_wb_init(struct ssg_data *ssg) +{ + struct ssg_wb_data *ssg_wb; + struct gendisk *gd = ssg->queue->disk; + + if (!gd) + return; + + if (!blk_sec_wb_is_supported(gd)) + return; + + ssg_wb = kzalloc(sizeof(*ssg_wb), GFP_KERNEL); + if (!ssg_wb) + return; + ssg->wb_data = ssg_wb; + + INIT_DELAYED_WORK(&ssg_wb->wb_ctrl_work, wb_ctrl_work); + INIT_DELAYED_WORK(&ssg_wb->wb_deferred_off_work, wb_off_work); + + ssg_wb->on_rqs = ssg->queue->nr_requests * _on_rqs_ratio / 100U; + ssg_wb->off_rqs = ssg->queue->nr_requests * _off_rqs_ratio / 100U; + ssg_wb->on_dirty_bytes = _on_dirty_bytes; + ssg_wb->off_dirty_bytes = _off_dirty_bytes; + ssg_wb->on_sync_write_bytes = _on_sync_write_bytes; + ssg_wb->off_sync_write_bytes = _off_sync_write_bytes; + ssg_wb->on_dirty_busy_written_pages = _on_dirty_busy_written_bytes / PAGE_SIZE; + ssg_wb->on_dirty_busy_jiffies = msecs_to_jiffies(_on_dirty_busy_msecs); + ssg_wb->dirty_busy_start_written_pages = 0; + ssg_wb->dirty_busy_start_jiffies = 0; + ssg_wb->off_delay_jiffies = msecs_to_jiffies(_off_delay_msecs); + ssg_wb->queue = ssg->queue; + + atomic_set(&ssg_wb->wb_triggered, false); +} + +void ssg_wb_exit(struct ssg_data *ssg) +{ + struct ssg_wb_data *ssg_wb = ssg->wb_data; + + if (!ssg_wb) + return; + + cancel_delayed_work_sync(&ssg_wb->wb_ctrl_work); + cancel_delayed_work_sync(&ssg_wb->wb_deferred_off_work); + + if (atomic_read(&ssg_wb->wb_triggered)) + blk_sec_wb_ctrl(false, WB_REQ_IOSCHED); + + ssg->wb_data = NULL; + kfree(ssg_wb); +} + +/* sysfs */ +#define SHOW_FUNC(__NAME, __VAR, __CONV) \ +ssize_t ssg_wb_##__NAME##_show(struct elevator_queue *e, char *page) \ +{ \ + struct ssg_data *ssg = e->elevator_data; \ + struct ssg_wb_data *ssg_wb = ssg->wb_data; \ + int val; \ + \ + if (!ssg_wb) \ + return 0; \ + \ + if (__CONV == 1) \ + val = jiffies_to_msecs(__VAR); \ + else if (__CONV == 2) \ + val = __VAR * PAGE_SIZE; \ + else \ + val = __VAR; \ + \ + return snprintf(page, PAGE_SIZE, "%d\n", val); \ +} +SHOW_FUNC(on_rqs, ssg_wb->on_rqs, 0); +SHOW_FUNC(off_rqs, ssg_wb->off_rqs, 0); +SHOW_FUNC(on_dirty_bytes, ssg_wb->on_dirty_bytes, 0); +SHOW_FUNC(off_dirty_bytes, ssg_wb->off_dirty_bytes, 0); +SHOW_FUNC(on_sync_write_bytes, ssg_wb->on_sync_write_bytes, 0); +SHOW_FUNC(off_sync_write_bytes, ssg_wb->off_sync_write_bytes, 0); +SHOW_FUNC(on_dirty_busy_written_bytes, ssg_wb->on_dirty_busy_written_pages, 2); +SHOW_FUNC(on_dirty_busy_msecs, ssg_wb->on_dirty_busy_jiffies, 1); +SHOW_FUNC(off_delay_msecs, ssg_wb->off_delay_jiffies, 1); +#undef SHOW_FUNC + +#define STORE_FUNC(__NAME, __PTR, __VAR, __COND, __CONV) \ +ssize_t ssg_wb_##__NAME##_store(struct elevator_queue *e, \ + const char *page, size_t count) \ +{ \ + struct ssg_data *ssg = e->elevator_data; \ + struct ssg_wb_data *ssg_wb = ssg->wb_data; \ + int __VAR; \ + \ + if (!ssg_wb) \ + return count; \ + \ + if (kstrtoint(page, 10, &__VAR)) \ + return count; \ + \ + if (!(__COND)) \ + return count; \ + \ + if (__CONV == 1) \ + *(__PTR) = msecs_to_jiffies(__VAR); \ + else if (__CONV == 2) \ + *(__PTR) = __VAR / PAGE_SIZE; \ + else \ + *(__PTR) = __VAR; \ + \ + return count; \ +} +STORE_FUNC(on_rqs, &ssg_wb->on_rqs, val, + val >= ssg_wb->off_rqs, 0); +STORE_FUNC(off_rqs, &ssg_wb->off_rqs, val, + val >= 0 && val <= ssg_wb->on_rqs, 0); +STORE_FUNC(on_dirty_bytes, &ssg_wb->on_dirty_bytes, val, + val >= ssg_wb->off_dirty_bytes, 0); +STORE_FUNC(off_dirty_bytes, &ssg_wb->off_dirty_bytes, val, + val >= 0 && val <= ssg_wb->on_dirty_bytes, 0); +STORE_FUNC(on_sync_write_bytes, &ssg_wb->on_sync_write_bytes, val, + val >= ssg_wb->off_sync_write_bytes, 0); +STORE_FUNC(off_sync_write_bytes, &ssg_wb->off_sync_write_bytes, val, + val >= 0 && val <= ssg_wb->on_sync_write_bytes, 0); +STORE_FUNC(on_dirty_busy_written_bytes, &ssg_wb->on_dirty_busy_written_pages, val, + val >= 0, 2); +STORE_FUNC(on_dirty_busy_msecs, &ssg_wb->on_dirty_busy_jiffies, val, + val >= 0, 1); +STORE_FUNC(off_delay_msecs, &ssg_wb->off_delay_jiffies, val, val >= 0, 1); +#undef STORE_FUNC + +ssize_t ssg_wb_triggered_show(struct elevator_queue *e, char *page) +{ + struct ssg_data *ssg = e->elevator_data; + struct ssg_wb_data *ssg_wb = ssg->wb_data; + + if (!ssg_wb) + return 0; + + return snprintf(page, PAGE_SIZE, "%d\n", atomic_read(&ssg_wb->wb_triggered)); +} diff --git a/block/ssg.h b/block/ssg.h new file mode 100644 index 000000000000..104bd4f21774 --- /dev/null +++ b/block/ssg.h @@ -0,0 +1,191 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef SSG_H +#define SSG_H + +#include "blk-cgroup.h" + +struct ssg_request_info { + pid_t tgid; + + sector_t sector; + unsigned int data_size; + + struct blkcg_gq *blkg; + + void *pio; +}; + +struct ssg_data { + struct request_queue *queue; + + /* + * requests are present on both sort_list and fifo_list + */ + struct rb_root sort_list[2]; + struct list_head fifo_list[2]; + + /* + * next in sort order. read, write or both are NULL + */ + struct request *next_rq[2]; + unsigned int starved_writes; /* times reads have starved writes */ + + /* + * settings that change how the i/o scheduler behaves + */ + int fifo_expire[2]; + int max_write_starvation; + int front_merges; + + /* + * to control request allocation + */ + atomic_t allocated_rqs; + atomic_t async_write_rqs; + int congestion_threshold_rqs; + int max_tgroup_rqs; + int max_async_write_rqs; + unsigned int tgroup_shallow_depth; /* thread group shallow depth for each tag map */ + unsigned int async_write_shallow_depth; /* async write shallow depth for each tag map */ + + /* + * I/O context information for each request + */ + struct ssg_request_info *rq_info; + + /* + * Statistics + */ + void __percpu *stats; + + spinlock_t lock; + spinlock_t zone_lock; + struct list_head dispatch; + + /* + * Write booster + */ + void *wb_data; +}; + +static inline struct cgroup_subsys_state *curr_css(void) +{ + return task_css(current, io_cgrp_id); +} + +/* ssg-stat.c */ +extern int ssg_stat_init(struct ssg_data *ssg); +extern void ssg_stat_exit(struct ssg_data *ssg); +extern void ssg_stat_account_io_done(struct ssg_data *ssg, + struct request *rq, unsigned int data_size, u64 now); +extern ssize_t ssg_stat_read_latency_show(struct elevator_queue *e, char *page); +extern ssize_t ssg_stat_write_latency_show(struct elevator_queue *e, char *page); +extern ssize_t ssg_stat_flush_latency_show(struct elevator_queue *e, char *page); +extern ssize_t ssg_stat_discard_latency_show(struct elevator_queue *e, char *page); +extern ssize_t ssg_stat_inflight_show(struct elevator_queue *e, char *page); +extern ssize_t ssg_stat_rqs_info_show(struct elevator_queue *e, char *page); + +/* ssg-cgroup.c */ +#if IS_ENABLED(CONFIG_MQ_IOSCHED_SSG_CGROUP) +struct ssg_blkcg { + struct blkcg_policy_data cpd; /* must be the first member */ + + int max_available_ratio; +}; + +struct ssg_blkg { + struct blkg_policy_data pd; /* must be the first member */ + + atomic_t current_rqs; + int max_available_rqs; + unsigned int shallow_depth; /* shallow depth for each tag map to get sched tag */ +}; + +extern int ssg_blkcg_init(void); +extern void ssg_blkcg_exit(void); +extern int ssg_blkcg_activate(struct request_queue *q); +extern void ssg_blkcg_deactivate(struct request_queue *q); +extern unsigned int ssg_blkcg_shallow_depth(struct request_queue *q); +extern void ssg_blkcg_depth_updated(struct blk_mq_hw_ctx *hctx); +extern void ssg_blkcg_inc_rq(struct blkcg_gq *blkg); +extern void ssg_blkcg_dec_rq(struct blkcg_gq *blkg); +#else +static inline int ssg_blkcg_init(void) +{ + return 0; +} + +static inline void ssg_blkcg_exit(void) +{ +} + +static inline int ssg_blkcg_activate(struct request_queue *q) +{ + return 0; +} + +static inline void ssg_blkcg_deactivate(struct request_queue *q) +{ +} + +static inline unsigned int ssg_blkcg_shallow_depth(struct request_queue *q) +{ + return 0; +} + +static inline void ssg_blkcg_depth_updated(struct blk_mq_hw_ctx *hctx) +{ +} + +static inline void ssg_blkcg_inc_rq(struct blkcg_gq *blkg) +{ +} + +static inline void ssg_blkcg_dec_rq(struct blkcg_gq *blkg) +{ +} +#endif + +/* ssg-wb.c */ +#if IS_ENABLED(CONFIG_MQ_IOSCHED_SSG_WB) +extern void ssg_wb_ctrl(struct ssg_data *ssg, struct request *rq); +extern void ssg_wb_depth_updated(struct blk_mq_hw_ctx *hctx); +extern void ssg_wb_init(struct ssg_data *ssg); +extern void ssg_wb_exit(struct ssg_data *ssg); +extern ssize_t ssg_wb_on_rqs_show(struct elevator_queue *e, char *page); +extern ssize_t ssg_wb_on_rqs_store(struct elevator_queue *e, const char *page, size_t count); +extern ssize_t ssg_wb_off_rqs_show(struct elevator_queue *e, char *page); +extern ssize_t ssg_wb_off_rqs_store(struct elevator_queue *e, const char *page, size_t count); +extern ssize_t ssg_wb_on_dirty_bytes_show(struct elevator_queue *e, char *page); +extern ssize_t ssg_wb_on_dirty_bytes_store(struct elevator_queue *e, const char *page, size_t count); +extern ssize_t ssg_wb_off_dirty_bytes_show(struct elevator_queue *e, char *page); +extern ssize_t ssg_wb_off_dirty_bytes_store(struct elevator_queue *e, const char *page, size_t count); +extern ssize_t ssg_wb_on_sync_write_bytes_show(struct elevator_queue *e, char *page); +extern ssize_t ssg_wb_on_sync_write_bytes_store(struct elevator_queue *e, const char *page, size_t count); +extern ssize_t ssg_wb_off_sync_write_bytes_show(struct elevator_queue *e, char *page); +extern ssize_t ssg_wb_off_sync_write_bytes_store(struct elevator_queue *e, const char *page, size_t count); +extern ssize_t ssg_wb_on_dirty_busy_written_bytes_show(struct elevator_queue *e, char *page); +extern ssize_t ssg_wb_on_dirty_busy_written_bytes_store(struct elevator_queue *e, const char *page, size_t count); +extern ssize_t ssg_wb_on_dirty_busy_msecs_show(struct elevator_queue *e, char *page); +extern ssize_t ssg_wb_on_dirty_busy_msecs_store(struct elevator_queue *e, const char *page, size_t count); +extern ssize_t ssg_wb_off_delay_msecs_show(struct elevator_queue *e, char *page); +extern ssize_t ssg_wb_off_delay_msecs_store(struct elevator_queue *e, const char *page, size_t count); +extern ssize_t ssg_wb_triggered_show(struct elevator_queue *e, char *page); +#else +static inline void ssg_wb_ctrl(struct ssg_data *ssg, struct request *rq) +{ +} + +static inline void ssg_wb_depth_updated(struct blk_mq_hw_ctx *hctx) +{ +} + +static inline void ssg_wb_init(struct ssg_data *ssg) +{ +} + +static inline void ssg_wb_exit(struct ssg_data *ssg) +{ +} +#endif +#endif // SSG_H diff --git a/drivers/Kconfig b/drivers/Kconfig index 19ee995bd0ae..f378379eb8be 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -239,4 +239,56 @@ source "drivers/peci/Kconfig" source "drivers/hte/Kconfig" +source "drivers/samsung/Kconfig" + +source "drivers/sensors/Kconfig" + +source "drivers/adsp_factory/Kconfig" +source "drivers/sec_panel_notifier_v2/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/regulator/pmic_class/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/usb/typec/manager/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/battery/charger/max77705_charger/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/input/input_boost/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/battery/charger/max77775_charger/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/muic/common/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/staging/android/switch/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/uwb/uwb_logger/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/sdp/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/usb/common/vbus_notifier/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/input/sec_input/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/net/dropdump/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/regulator/s2dos05/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/input/sec_input/stm_spi/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/battery/fuelgauge/max77705_fuelgauge/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/mfd/maxim/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/vibrator/common/vib_info/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/sti/common/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/kperfmon/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/vibrator/common/inputff/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/usb/notify/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/usb/vendor_notify/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/fingerprint/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/battery/core/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/usb/typec/common/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/sti/abc/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/battery/fuelgauge/max77775_fuelgauge/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/vibrator/cs/cs40l26/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/input/misc/hall/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/optics/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/usb/typec/maxim/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/battery/charger/pca9481_charger/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/regulator/s2mpb03/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/samsung/pm/sec_thermistor/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/input/sec_input/wacom/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/nfc/snvm/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/battery/wireless/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/nfc/nxp_combined/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/regulator/s2mpb02/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/uwb/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/regulator/s2dos07/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/secdp/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/input/sec_input/synaptics/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/battery/common/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/sensors/vl53l8/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "drivers/mfd/slsi/s2mpb02/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT endmenu diff --git a/drivers/Makefile b/drivers/Makefile index bdf1c66141c9..485c8dbeea0b 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -186,6 +186,55 @@ obj-$(CONFIG_SIOX) += siox/ obj-$(CONFIG_GNSS) += gnss/ obj-$(CONFIG_INTERCONNECT) += interconnect/ obj-$(CONFIG_COUNTER) += counter/ +obj-$(CONFIG_SENSORS) += sensors/ +obj-$(CONFIG_ADSP_FACTORY) += adsp_factory/ obj-$(CONFIG_MOST) += most/ obj-$(CONFIG_PECI) += peci/ obj-$(CONFIG_HTE) += hte/ +obj-y += samsung/ +obj-$(CONFIG_UH) += uh/ +obj-y += sec_panel_notifier_v2/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += regulator/pmic_class/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += usb/typec/manager/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += battery/charger/max77705_charger/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += input/input_boost/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += battery/charger/max77775_charger/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += muic/common/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += staging/android/switch/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += uwb/uwb_logger/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += sdp/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += usb/common/vbus_notifier/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += input/sec_input/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += net/dropdump/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += regulator/s2dos05/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += input/sec_input/stm_spi/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += battery/fuelgauge/max77705_fuelgauge/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += mfd/maxim/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += vibrator/common/vib_info/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += kperfmon/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += vibrator/common/inputff/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += usb/notify/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += usb/vendor_notify/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += fingerprint/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += battery/core/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += usb/typec/common/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += sti/abc/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += battery/fuelgauge/max77775_fuelgauge/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += vibrator/cs/cs40l26/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += input/misc/hall/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += optics/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += usb/typec/maxim/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += battery/charger/pca9481_charger/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += regulator/s2mpb03/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += samsung/pm/sec_thermistor/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += input/sec_input/wacom/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += nfc/snvm/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += battery/wireless/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += nfc/nxp_combined/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += regulator/s2mpb02/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += uwb/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += regulator/s2dos07/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += input/sec_input/synaptics/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += battery/common/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += sensors/vl53l8/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += mfd/slsi/s2mpb02/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT diff --git a/drivers/adsp_factory/Kconfig b/drivers/adsp_factory/Kconfig new file mode 100755 index 000000000000..e25de2eea053 --- /dev/null +++ b/drivers/adsp_factory/Kconfig @@ -0,0 +1,348 @@ +# +# factory sensor drivers configuration +# +config ADSP_FACTORY + tristate "MSM ADSP factory driver" + help + This driver communicate with SSC DAEMON. + register each sensor device. + send selftest request using netlink. + receive test result using netlink. + +config LSM6DSO_FACTORY + bool "factory test for SSC - LSM6DSO" + depends on ADSP_FACTORY + help + lsm6dso factory driver. + provide sysfs for factory test. + request selftest to adsp_factory. + receive test result from adsp_factory. + +config LSM6DSL_FACTORY + bool "factory test for SSC - LSM6DSL" + depends on ADSP_FACTORY + help + lsm6dsl factory driver. + provide sysfs for factory test. + request selftest to adsp_factory. + receive test result from adsp_factory. + +config LSM6DSV_FACTORY + bool "factory test for SSC - LSM6DSV" + depends on ADSP_FACTORY + help + lsm6dsv factory driver. + provide sysfs for factory test. + request selftest to adsp_factory. + receive test result from adsp_factory. + +config AK09918_FACTORY + bool "factory test for SSC - ak09918" + depends on ADSP_FACTORY + help + ak09918 factory driver. + provide sysfs for factory test. + request selftest to adsp_factory. + receive test result from adsp_factory. + +config SUPPORT_MAG_ABS_SUM + tristate "mag abs sum for SSC" + depends on AK09918_FACTORY + help + Support the mag abs sum check + check the mag abs sum value. + +config LPS22HH_FACTORY + bool "factory test for SSC - lps22hh" + depends on ADSP_FACTORY + help + lps22hh factory driver. + provide sysfs for factory test. + request selftest to adsp_factory. + receive test result from adsp_factory. + +config LPS22DF_FACTORY + bool "factory test for SSC - lps22df" + depends on ADSP_FACTORY + help + lps22df factory driver. + provide sysfs for factory test. + request selftest to adsp_factory. + receive test result from adsp_factory. + +config PRESSURE_FACTORY + bool "factory test for SSC - pressure" + depends on ADSP_FACTORY + help + pressure factory driver. + provide sysfs for factory test. + request selftest to adsp_factory. + receive test result from adsp_factory. + +config LIGHT_FACTORY + bool "factory test for SSC - light" + depends on ADSP_FACTORY + help + light factory driver. + provide sysfs for factory test. + request selftest to adsp_factory. + receive test result from adsp_factory. + +config LIGHT_SUB_FACTORY + bool "factory test for SSC - light sub" + depends on ADSP_FACTORY + help + light sub factory driver. + provide sysfs for factory test. + request selftest to adsp_factory. + receive test result from adsp_factory. + +config PROX_FACTORY + bool "factory test for SSC - prox" + depends on ADSP_FACTORY + help + prox factory driver. + provide sysfs for factory test. + request selftest to adsp_factory. + receive test result from adsp_factory. + +config STK33610_FACTORY + bool "factory test for SSC - STK33610" + depends on ADSP_FACTORY + help + stk33610 factory driver. + provide sysfs for factory test. + request selftest through factory daemon to slpi. + receive test result through factory daemon from slpi. + +config STK33610_SUB_FACTORY + bool "factory test for SSC - STK33610" + depends on ADSP_FACTORY + help + stk33610 factory driver. + provide sysfs for factory test. + request selftest through factory daemon to slpi. + receive test result through factory daemon from slpi. + +config SUPPORT_LIGHT_CALIBRATION + bool "light cal for SSC" + depends on ADSP_FACTORY + help + light calibration feature. + provide sysfs for light calibration. + request light cal to adsp_factory. + receive cal value from adsp_factory. + +config SUPPORT_PROX_CALIBRATION + bool "prox cal for SSC" + depends on ADSP_FACTORY + help + prox calibration feature. + provide sysfs for prox calibration. + request prox cal to adsp_factory. + receive cal value from adsp_factory. + +config SUPPORT_CONTROL_PROX_LED_GPIO + bool "control prox led gpio for SSC" + depends on ADSP_FACTORY + help + Support to control prox led gpio. + +config SUPPORT_PROX_POWER_ON_CAL + bool "Sensors support proximity sensor power on cal" + depends on ADSP_FACTORY + help + Support power on calibration for proximity sensor + make calibration process done as the device power up. + +config SUPPORT_BRIGHTNESS_NOTIFY_FOR_LIGHT_SENSOR + bool "Sensors support brightness notify" + depends on ADSP_FACTORY + help + Support brightness notify for light sensor. + receive aor and brightness level from lcd driver. + +config SUPPORT_PANEL_STATE_NOTIFY_FOR_LIGHT_SENSOR + bool "Sensors support panel state notify" + depends on ADSP_FACTORY + help + Support panel state notify for light sensor. + receive panel state from lcd driver. + +config SUPPORT_DDI_COPR_FOR_LIGHT_SENSOR + bool "Sensors support ddi copr" + depends on ADSP_FACTORY + help + Support ddi copr for light sensor. + provide copr sysfs for factory and afc service. + DDI must be connected with sensor core + +config SUPPORT_FIFO_DEBUG_FOR_LIGHT_SENSOR + bool "Sensors support fifo debug" + depends on ADSP_FACTORY + help + Support fifo debug for light sensor. + provide fifo debug msg for light sensor. + DDI must be connected with sensor core + +config SUPPORT_DUAL_DDI_COPR_FOR_LIGHT_SENSOR + bool "Sensors support dual ddi copr" + depends on ADSP_FACTORY + help + Support dual ddi copr for light sensor. + provide copr sysfs for factory and afc service. + DDI must be connected with sensor core + +config SUPPORT_DUAL_6AXIS + bool "Sensors support dual 6axis" + depends on ADSP_FACTORY + help + Support the dual accel and gyro function. + provide sysfs for factory test. + request selftest to adsp_factory. + receive test result from adsp_factory. + +config SUPPORT_DUAL_OPTIC + bool "Sensors support dual optic" + depends on ADSP_FACTORY + help + Support the dual prox and light function. + provide sysfs for factory test. + request selftest to adsp_factory. + receive test result from adsp_factory. + +config SUPPORT_VIRTUAL_OPTIC + bool "Sensors support virtual optic" + depends on ADSP_FACTORY + help + Support the virtual prox and light function. + provide sysfs for factory test. + request selftest to adsp_factory. + receive test result from adsp_factory. + +config SUPPORT_DUAL_OPTIC_BUT_SUPPORT_SINGLE_PROX + bool "Sensors support virtual optic but support single prox" + depends on ADSP_FACTORY + help + Support the virtual prox and light function. + provide sysfs for factory test. + request selftest to adsp_factory. + receive test result from adsp_factory. + +config SUPPORT_SENSOR_FLIP_MODEL + bool "Sensors support sensor flip model" + depends on ADSP_FACTORY + help + Support sensor flip model. + Separate the flip model from the factory test. + +config SUPPORT_AK09973 + bool "Support ak09973" + depends on ADSP_FACTORY + help + Support ak09973. + +config SUPPORT_DHALL_SWITCH + bool "Support DHALL_SWITCH" + depends on ADSP_FACTORY + help + Support ak09973. + +config SUPPORT_DEVICE_MODE + bool "Support device mode" + depends on ADSP_FACTORY + help + Support device mode. + +config SUPPORT_SENSOR_FOLD + bool "Support fold state by sensor algorithm" + depends on ADSP_FACTORY + help + Support fold state by sensor algorithm. + +config VEML3235_FACTORY + bool "factory test for SSC - veml3235" + depends on ADSP_FACTORY + help + veml3235 factory driver. + provide sysfs for factory test. + request selftest to adsp_factory. + receive test result from adsp_factory. + +config VEML3235_SUB_FACTORY + bool "factory test for SSC - veml3235_sub" + depends on ADSP_FACTORY + help + veml3235_sub factory driver. + provide sysfs for factory test. + request selftest to adsp_factory. + receive test result from adsp_factory. + +config VEML3328_FACTORY + bool "factory test for SSC - veml3328" + depends on ADSP_FACTORY + help + veml3328 factory driver. + provide sysfs for factory test. + request selftest to adsp_factory. + receive test result from adsp_factory. + +config VEML3328_SUB_FACTORY + bool "factory test for SSC - veml3328_sub" + depends on ADSP_FACTORY + help + veml3328_sub factory driver. + provide sysfs for factory test. + request selftest to adsp_factory. + receive test result from adsp_factory. + +config SUPPORT_LIGHT_SEAMLESS + bool "Support Light seamless" + depends on ADSP_FACTORY + help + Support Light seamless. + +config BACKTAP_FACTORY + bool "factory test for SSC - Back Tap Sensor" + depends on ADSP_FACTORY + help + backtap factory driver. + provide sysfs for setting back tap peak threshold + +config FLIP_COVER_DETECTOR_FACTORY + bool "factory test for SSC - Flip Cover Detector" + depends on ADSP_FACTORY + help + flip_cover_detector factory driver. + provide sysfs for cover status by nfc + +config FLIP_COVER_DETECTOR_NOTIFIER + bool "flip cover detector notifier" + depends on FLIP_COVER_DETECTOR_FACTORY + default y + help + Support notifier for flip cover attach/detach events + +config SSC_WAKEUP_DEBUG + bool "debug to ap wakeup due to SSC" + depends on SEC_SENSORS_SSC + help + debug to ap wakeup due to SSC event frequently + +config SLPI_LOADING_FAILURE_DEBUG + bool "debug to slpi loading fail" + depends on SEC_SENSORS_SSC + help + debug to to slpi loading fail + +config SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL + bool "support ref angle without digital hall" + depends on ADSP_FACTORY + help + support ref angle without digital hall + +config SEC_SENSORS_RECOVERY + bool "recovery slpi or adsp for sensor" + depends on SEC_SENSORS_SSC + help + recovery to slpi or adsp for sensor diff --git a/drivers/adsp_factory/Makefile b/drivers/adsp_factory/Makefile new file mode 100755 index 000000000000..1bbd7dd9e81f --- /dev/null +++ b/drivers/adsp_factory/Makefile @@ -0,0 +1,13 @@ +obj-$(CONFIG_ADSP_FACTORY) += adsp_factory_module.o +adsp_factory_module-$(CONFIG_ADSP_FACTORY) := adsp_factory.o ssc_core.o +adsp_factory_module-$(CONFIG_LSM6DSO_FACTORY) += lsm6dso_accel.o lsm6dso_gyro.o +adsp_factory_module-$(CONFIG_AK09918_FACTORY) += ak09918_mag.o +adsp_factory_module-$(CONFIG_LPS22HH_FACTORY) += lps22hh_pressure.o +adsp_factory_module-$(CONFIG_PRESSURE_FACTORY) += pressure_factory.o +adsp_factory_module-$(CONFIG_LIGHT_FACTORY) += light_factory.o +adsp_factory_module-$(CONFIG_PROX_FACTORY) += prox_factory.o +adsp_factory_module-$(CONFIG_SUPPORT_DUAL_6AXIS) += lsm6dso_sub_accel.o lsm6dso_sub_gyro.o +adsp_factory_module-$(CONFIG_LSM6DSL_FACTORY) += lsm6dsl_accel.o lsm6dsl_gyro.o +adsp_factory_module-$(CONFIG_SUPPORT_AK09973) += ak09973_digital_hall.o +adsp_factory_module-$(CONFIG_FLIP_COVER_DETECTOR_FACTORY) += flip_cover_detector.o +adsp_factory_module-$(CONFIG_BACKTAP_FACTORY) += back_tap.o \ No newline at end of file diff --git a/drivers/adsp_factory/adsp.h b/drivers/adsp_factory/adsp.h new file mode 100755 index 000000000000..e1837e854238 --- /dev/null +++ b/drivers/adsp_factory/adsp.h @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __ADSP_SENSOR_H__ +#define __ADSP_SENSOR_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +#include + +#define TIMEOUT_CNT 200 +#define TIMEOUT_DHR_CNT 50 + +#define PATH_LEN 50 +#define FILE_BUF_LEN 110 +#define ID_INDEX_NUMS 2 +#define RETRY_MAX 3 +#define VERSION_FILE_NAME_LEN 20 +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_6AXIS) +/* To avoid wrong folder close */ +#define LSM6DSO_SELFTEST_TRUE 3 +#define LSM6DSO_SELFTEST_FALSE 4 +#endif + +#define UNKNOWN_INDEX 0 +#define DEVICE_INFO_LENGTH 10 + +enum { + D_FACTOR, + R_COEF, + G_COEF, + B_COEF, + C_COEF, + CT_COEF, + CT_OFFSET, + THD_HIGH, + THD_LOW, + IRIS_PROX_THD, + SUM_CRC, + EFS_SAVE_NUMS, +}; + +enum { + ID_UTYPE, + ID_BLACK, + ID_WHITE, + ID_GOLD, + ID_SILVER, + ID_GREEN, + ID_BLUE, + ID_PINKGOLD, + ID_MAX, +}; + +#if IS_ENABLED(CONFIG_SUPPORT_SENSOR_FOLD) +struct sensor_fold_state { + int64_t ts; + int state; // 0: unfold, 1: close(fold) +}; +#endif + +/* Main struct containing all the data */ +struct adsp_data { + struct device *adsp; + struct device *sensor_device[MSG_SENSOR_MAX]; + struct device_attribute **sensor_attr[MSG_SENSOR_MAX]; + struct sock *adsp_skt; + int32_t *msg_buf[MSG_SENSOR_MAX]; + unsigned int ready_flag[MSG_TYPE_MAX]; + bool sysfs_created[MSG_SENSOR_MAX]; + struct mutex prox_factory_mutex; + struct mutex light_factory_mutex; + struct mutex accel_factory_mutex; + struct mutex remove_sysfs_mutex; +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) + struct mutex vir_optic_factory_mutex; +#endif +#if IS_ENABLED(CONFIG_FLIP_COVER_DETECTOR_FACTORY) + struct mutex flip_cover_factory_mutex; +#endif +#if IS_ENABLED(CONFIG_BACKTAP_FACTORY) + struct mutex backtap_factory_mutex; +#endif +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) + struct mutex digital_hall_mutex; +#endif + struct notifier_block adsp_nb; +#ifdef CONFIG_VBUS_NOTIFIER + struct notifier_block vbus_nb; +#endif + int32_t fac_fstate; +#if IS_ENABLED(CONFIG_LIGHT_FACTORY) + struct delayed_work light_init_work; + bool light_factory_is_ready; + char light_device_vendor[2][10]; + char light_device_name[2][10]; +#endif +#if IS_ENABLED(CONFIG_SUPPORT_LIGHT_CALIBRATION) + struct delayed_work light_cal_work; + int32_t light_cal_result; + int32_t light_cal1; + int32_t light_cal2; + int32_t copr_w; + int32_t sub_light_cal_result; + int32_t sub_light_cal1; + int32_t sub_light_cal2; + int32_t sub_copr_w; +#endif +#if IS_ENABLED(CONFIG_SUPPORT_DDI_COPR_FOR_LIGHT_SENSOR) + struct delayed_work light_copr_debug_work; + int light_copr_debug_count; +#endif +#if IS_ENABLED(CONFIG_SUPPORT_FIFO_DEBUG_FOR_LIGHT_SENSOR) + struct delayed_work light_fifo_debug_work; +#endif +#if IS_ENABLED(CONFIG_SUPPORT_BRIGHTNESS_NOTIFY_FOR_LIGHT_SENSOR) + struct work_struct light_br_work; +#endif +#if IS_ENABLED(CONFIG_LIGHT_FACTORY) + int32_t light_temp_reg; + int32_t brightness_info[6]; + int32_t pre_bl_level[2]; + int32_t pre_panel_state[2]; + int32_t pre_screen_mode[2]; + int32_t pre_panel_idx; + int32_t pre_display_idx; + int32_t light_debug_info_cmd; + int32_t hyst[4]; + int32_t brightness_resolution[2]; +#endif +#if IS_ENABLED(CONFIG_SUPPORT_PROX_CALIBRATION) + int32_t prox_cal; + int32_t prox_sub_cal; +#endif + struct delayed_work accel_cal_work; +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_6AXIS) + struct delayed_work sub_accel_cal_work; +#endif +#if IS_ENABLED(CONFIG_SUPPORT_LIGHT_SEAMLESS) + struct delayed_work light_seamless_work; +#endif +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) || defined(CONFIG_SUPPORT_AK09973) + struct delayed_work lsm6dso_selftest_stop_work; + struct delayed_work dhall_cal_work; +#elif IS_ENABLED(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) || defined(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) + struct delayed_work lsm6dso_selftest_stop_work; +#endif + struct delayed_work pressure_cal_work; + char press_device_vendor[DEVICE_INFO_LENGTH]; + char press_device_name[DEVICE_INFO_LENGTH]; +#if IS_ENABLED(CONFIG_SUPPORT_SENSOR_FOLD) + struct sensor_fold_state fold_state; +#endif + uint32_t support_algo; + bool restrict_mode; + int turn_over_crash; + bool send_probe_fail_msg; +}; + +struct device_id_t { + uint8_t device_id; + char device_vendor[DEVICE_INFO_LENGTH]; + char device_name[DEVICE_INFO_LENGTH]; +}; + +#ifdef CONFIG_SEC_FACTORY +int get_mag_raw_data(int32_t *raw_data); +#endif +int get_accel_raw_data(int32_t *raw_data); +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_6AXIS) +int get_sub_accel_raw_data(int32_t *raw_data); +#endif +int adsp_get_sensor_data(int sensor_type); +int adsp_factory_register(unsigned int type, + struct device_attribute *attributes[]); +int adsp_factory_unregister(unsigned int type); +#if IS_ENABLED(CONFIG_SUPPORT_DEVICE_MODE) || defined(CONFIG_VBUS_NOTIFIER) +struct adsp_data* adsp_ssc_core_register(unsigned int type, + struct device_attribute *attributes[]); +struct adsp_data* adsp_ssc_core_unregister(unsigned int type); +#endif +int adsp_unicast(void *param, int param_size, u16 sensor_type, + u32 portid, u16 msg_type); +int sensors_register(struct device **dev, void *drvdata, + struct device_attribute *attributes[], char *name); +void sensors_unregister(struct device *dev, + struct device_attribute *attributes[]); +int core_factory_init(void); +void core_factory_exit(void); +#if IS_ENABLED(CONFIG_LSM6DSO_FACTORY) +int lsm6dso_accel_factory_init(void); +void lsm6dso_accel_factory_exit(void); +int lsm6dso_gyro_factory_init(void); +void lsm6dso_gyro_factory_exit(void); +#endif +#if IS_ENABLED(CONFIG_LSM6DSL_FACTORY) +int lsm6dsl_accel_factory_init(void); +void lsm6dsl_accel_factory_exit(void); +int lsm6dsl_gyro_factory_init(void); +void lsm6dsl_gyro_factory_exit(void); +#endif +void accel_factory_init_work(struct adsp_data *data); +void accel_cal_work_func(struct work_struct *work); +#if IS_ENABLED(CONFIG_AK09918_FACTORY) +int ak09918_factory_init(void); +void ak09918_factory_exit(void); +#endif +#if IS_ENABLED(CONFIG_LPS22HH_FACTORY) +int lps22hh_pressure_factory_init(void); +void lps22hh_pressure_factory_exit(void); +#elif IS_ENABLED(CONFIG_PRESSURE_FACTORY) +int pressure_factory_init(void); +void pressure_factory_exit(void); +#endif +#if IS_ENABLED(CONFIG_LIGHT_FACTORY) +int light_factory_init(void); +void light_factory_exit(void); +void light_init_work(struct adsp_data *data); +void light_init_work_func(struct work_struct *work); +#endif +#if IS_ENABLED(CONFIG_PROX_FACTORY) +int prox_factory_init(void); +void prox_factory_exit(void); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_6AXIS) +int lsm6dso_sub_accel_factory_init(void); +void lsm6dso_sub_accel_factory_exit(void); +int lsm6dso_sub_gyro_factory_init(void); +void lsm6dso_sub_gyro_factory_exit(void); +void sub_accel_factory_init_work(struct adsp_data *data); +void sub_accel_cal_work_func(struct work_struct *work); +#endif + +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) +int ak09970_factory_init(void); +void ak09970_factory_exit(void); +void lsm6dso_selftest_stop_work_func(struct work_struct *work); +#elif IS_ENABLED(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) || defined(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) +void lsm6dso_selftest_stop_work_func(struct work_struct *work); +#endif + +#if IS_ENABLED(CONFIG_SUPPORT_DEVICE_MODE) +void sns_device_mode_init_work(void); +void sns_flip_init_work(void); +#endif +#ifdef CONFIG_VBUS_NOTIFIER +void sns_vbus_init_work(void); +#endif + +#if IS_ENABLED(CONFIG_BACKTAP_FACTORY) +int backtap_factory_init(void); +void backtap_factory_exit(void); +#endif + +#if IS_ENABLED(CONFIG_FLIP_COVER_DETECTOR_FACTORY) +int flip_cover_detector_factory_init(void); +void flip_cover_detector_factory_exit(void); +#endif + +#if IS_ENABLED(CONFIG_SUPPORT_LIGHT_CALIBRATION) +void light_cal_init_work(struct adsp_data *data); +void light_cal_read_work_func(struct work_struct *work); +#endif /* CONFIG_SUPPORT_LIGHT_CALIBRATION */ +#if IS_ENABLED(CONFIG_SUPPORT_DDI_COPR_FOR_LIGHT_SENSOR) +void light_copr_debug_work_func(struct work_struct *work); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_FIFO_DEBUG_FOR_LIGHT_SENSOR) +void light_fifo_debug_work_func(struct work_struct *work); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_PROX_CALIBRATION) +void prox_cal_init_work(struct adsp_data *data); +void prox_send_cal_data(struct adsp_data *data, uint16_t prox_idx, bool fac_cal); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_PROX_POWER_ON_CAL) +void prox_factory_init_work(void); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) +void digital_hall_factory_auto_cal_init_work(struct adsp_data *data); +int get_hall_angle_data(int32_t *raw_data); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_LIGHT_SEAMLESS) +void light_seamless_work_func(struct work_struct *work); +void light_seamless_init_work(struct adsp_data *data); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) +int get_light_sidx(struct adsp_data *data); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_BRIGHTNESS_NOTIFY_FOR_LIGHT_SENSOR) && \ + IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) +struct adsp_data* adsp_get_struct_data(void); +void light_brightness_work_func(struct work_struct *work); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) +void dhall_cal_work_func(struct work_struct *work); +#endif +void pressure_factory_init_work(struct adsp_data *data); +void pressure_cal_work_func(struct work_struct *work); + +bool sns_check_ignore_crash(void); +#endif /* __ADSP_SENSOR_H__ */ diff --git a/drivers/adsp_factory/adsp_factory.c b/drivers/adsp_factory/adsp_factory.c new file mode 100755 index 000000000000..75d7f6a23a2a --- /dev/null +++ b/drivers/adsp_factory/adsp_factory.c @@ -0,0 +1,676 @@ +/* + * Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "adsp.h" + +static u8 msg_size[MSG_SENSOR_MAX] = { + MSG_ACCEL_MAX, + MSG_GYRO_MAX, + MSG_MAG_MAX, + MSG_PRESSURE_MAX, + MSG_LIGHT_MAX, + MSG_PROX_MAX, +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) + MSG_LIGHT_MAX, + MSG_PROX_MAX, +#endif +#if IS_ENABLED(CONFIG_SUPPORT_FLICKER) + MSG_FLICKER_MAX, +#endif +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_6AXIS) + MSG_ACCEL_MAX, + MSG_GYRO_MAX, +#endif + MSG_TYPE_SIZE_ZERO, /* PHYSICAL_SENSOR_SYSFS */ + MSG_GYRO_TEMP_MAX, +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_6AXIS) + MSG_GYRO_TEMP_MAX, +#endif + MSG_PRESSURE_TEMP_MAX, + MSG_TYPE_SIZE_ZERO, +#if IS_ENABLED(CONFIG_FLIP_COVER_DETECTOR_FACTORY) + MSG_FLIP_COVER_DETECTOR_MAX, +#endif +#if IS_ENABLED(CONFIG_SUPPORT_VIRTUAL_OPTIC) + MSG_VOPTIC_MAX, +#endif + MSG_REG_SNS_MAX, +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) + MSG_DIGITAL_HALL_MAX, + MSG_DIGITAL_HALL_ANGLE_MAX, +#if ENABLE_LF_STREAM + MSG_DIGITAL_HALL_ANGLE_MAX, +#endif +#elif IS_ENABLED(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) || defined(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) + MSG_REF_ANGLE_MAX, +#endif +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_DDI_COPR_FOR_LIGHT_SENSOR) + MSG_DDI_MAX, +#endif +#if IS_ENABLED(CONFIG_SUPPORT_LIGHT_MAIN2_SENSOR) || defined(CONFIG_SUPPORT_LIGHT_MAIN2_SENSOR) + MSG_TYPE_SIZE_ZERO, +#endif +#if IS_ENABLED(CONFIG_BACKTAP_FACTORY) || defined(CONFIG_BACKTAP_FACTORY) + MSG_BACKTAP_MAX, +#endif + MSG_TYPE_SIZE_ZERO, + MSG_COMMON_INFO_MAX, +}; + +/* The netlink socket */ +struct adsp_data *data; + +DEFINE_MUTEX(factory_mutex); + +#if IS_ENABLED(CONFIG_SUPPORT_BRIGHTNESS_NOTIFY_FOR_LIGHT_SENSOR) && \ + IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) +struct adsp_data* adsp_get_struct_data(void) +{ + return data; +} +#endif + +/* Function used to send message to the user space */ +int adsp_unicast(void *param, int param_size, u16 sensor_type, + u32 portid, u16 msg_type) +{ + struct sk_buff *skb; + struct nlmsghdr *nlh; + void *msg; + int ret = -1; + u16 nlmsg_type = (sensor_type << 8) | msg_type; + + if (data->restrict_mode && msg_type == MSG_TYPE_SET_ACCEL_MOTOR) { + pr_err("[FACTORY] %s - restrict_mode\n", __func__); + return ret; + } + + data->ready_flag[msg_type] &= ~(1 << sensor_type); + skb = nlmsg_new(param_size, GFP_KERNEL); + if (!skb) { + pr_err("[FACTORY] %s - nlmsg_new fail\n", __func__); + return -ENOMEM; + } + + nlh = nlmsg_put(skb, portid, 0, nlmsg_type, param_size, 0); + if (nlh == NULL) { + pr_err("[FACTORY] %s - nlmsg_put fail\n", __func__); + nlmsg_free(skb); + return -EMSGSIZE; + } + msg = nlmsg_data(nlh); + memcpy(msg, param, param_size); + NETLINK_CB(skb).dst_group = 0; + ret = nlmsg_unicast(data->adsp_skt, skb, PID); + if (ret != 0) + pr_err("[FACTORY] %s - ret = %d\n", __func__, ret); + + return ret; +} +EXPORT_SYMBOL(adsp_unicast); + +#if IS_ENABLED(CONFIG_SUPPORT_DEVICE_MODE) || defined(CONFIG_VBUS_NOTIFIER) +struct adsp_data* adsp_ssc_core_register(unsigned int type, + struct device_attribute *attributes[]) +{ + int ret = 0; + + data->sensor_attr[type] = attributes; + ret = sensors_register(&data->sensor_device[type], data, + data->sensor_attr[type], "ssc_core"); + + data->sysfs_created[type] = true; + pr_info("[FACTORY] %s - type:%u ptr:%pK\n", + __func__, type, data->sensor_device[type]); + + return data; +} + +struct adsp_data* adsp_ssc_core_unregister(unsigned int type) +{ + pr_info("[FACTORY] %s - type:%u ptr:%pK\n", + __func__, type, data->sensor_device[type]); + + if (data->sysfs_created[type]) { + sensors_unregister(data->sensor_device[type], + data->sensor_attr[type]); + data->sysfs_created[type] = false; + } else { + pr_info("[FACTORY] %s: skip type %u\n", __func__, type); + } + return data; +} +#endif + +int adsp_factory_register(unsigned int type, + struct device_attribute *attributes[]) +{ + int ret = 0; + char *dev_name; + + switch (type) { + case MSG_ACCEL: + dev_name = "accelerometer_sensor"; + break; + case MSG_GYRO: + dev_name = "gyro_sensor"; + break; +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_6AXIS) + case MSG_ACCEL_SUB: + dev_name = "sub_accelerometer_sensor"; + break; + case MSG_GYRO_SUB: + dev_name = "sub_gyro_sensor"; + break; +#endif + case MSG_MAG: + dev_name = "magnetic_sensor"; + break; + case MSG_PRESSURE: + dev_name = "barometer_sensor"; + break; + case MSG_LIGHT: + dev_name = "light_sensor"; + break; + case MSG_PROX: + dev_name = "proximity_sensor"; + break; +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) + case MSG_LIGHT_SUB: + dev_name = "sub_light_sensor"; + break; + case MSG_PROX_SUB: + dev_name = "sub_proximity_sensor"; + break; +#endif +#if IS_ENABLED(CONFIG_FLIP_COVER_DETECTOR_FACTORY) + case MSG_FLIP_COVER_DETECTOR: + dev_name = "flip_cover_detector_sensor"; + break; +#endif +#if IS_ENABLED(CONFIG_BACKTAP_FACTORY) + case MSG_BACKTAP: + dev_name = "backtap_sensor"; + break; +#endif + case MSG_SSC_CORE: + dev_name = "ssc_core"; + break; +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) + case MSG_DIGITAL_HALL: + dev_name = "digital_hall"; + break; +#endif + default: + dev_name = "unknown_sensor"; + break; + } + + data->sensor_attr[type] = attributes; + ret = sensors_register(&data->sensor_device[type], data, + data->sensor_attr[type], dev_name); + + data->sysfs_created[type] = true; + pr_info("[FACTORY] %s - type:%u ptr:%pK\n", + __func__, type, data->sensor_device[type]); + + return ret; +} +EXPORT_SYMBOL(adsp_factory_register); + +int adsp_factory_unregister(unsigned int type) +{ + pr_info("[FACTORY] %s - type:%u ptr:%pK\n", + __func__, type, data->sensor_device[type]); + + if (data->sysfs_created[type]) { + sensors_unregister(data->sensor_device[type], + data->sensor_attr[type]); + data->sysfs_created[type] = false; + } else { + pr_info("[FACTORY] %s: skip type %u\n", __func__, type); + } + return 0; +} +EXPORT_SYMBOL(adsp_factory_unregister); + +int get_accel_raw_data(int32_t *raw_data) +{ + uint8_t cnt = 0; + + adsp_unicast(NULL, 0, MSG_ACCEL, 0, MSG_TYPE_GET_RAW_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & 1 << MSG_ACCEL) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_ACCEL); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return -1; + } + + memcpy(raw_data, &data->msg_buf[MSG_ACCEL][0], sizeof(int32_t) * 3); + + return 0; +} +EXPORT_SYMBOL(get_accel_raw_data); + +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_6AXIS) +int get_sub_accel_raw_data(int32_t *raw_data) +{ + uint8_t cnt = 0; + + adsp_unicast(NULL, 0, MSG_ACCEL_SUB, 0, MSG_TYPE_GET_RAW_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & 1 << MSG_ACCEL_SUB) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_ACCEL_SUB); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return -1; + } + + memcpy(raw_data, &data->msg_buf[MSG_ACCEL_SUB][0], sizeof(int32_t) * 3); + + return 0; +} +#endif +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) +void lsm6dso_selftest_stop_work_func(struct work_struct *work) +{ + int msg_buf = LSM6DSO_SELFTEST_FALSE; + adsp_unicast(&msg_buf, sizeof(msg_buf), + MSG_DIGITAL_HALL_ANGLE, 0, MSG_TYPE_OPTION_DEFINE); +} +#elif IS_ENABLED(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) || defined(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) +void lsm6dso_selftest_stop_work_func(struct work_struct *work) +{ + int msg_buf = LSM6DSO_SELFTEST_FALSE; + adsp_unicast(&msg_buf, sizeof(msg_buf), + MSG_REF_ANGLE, 0, MSG_TYPE_OPTION_DEFINE); +} +#endif + + +#ifdef CONFIG_SEC_FACTORY +int get_mag_raw_data(int32_t *raw_data) +{ + uint8_t cnt = 0; + + adsp_unicast(NULL, 0, MSG_MAG, 0, MSG_TYPE_GET_RAW_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & 1 << MSG_MAG) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_MAG); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return -1; + } + + memcpy(raw_data, &data->msg_buf[MSG_MAG][0], sizeof(int32_t) * 3); + + return 0; +} +#endif + +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) +int get_hall_angle_data(int32_t *raw_data) +{ + uint8_t cnt = 0; + + mutex_lock(&data->digital_hall_mutex); + adsp_unicast(NULL, 0, MSG_DIGITAL_HALL_ANGLE, 0, MSG_TYPE_GET_RAW_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & 1 << MSG_DIGITAL_HALL_ANGLE) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_DIGITAL_HALL_ANGLE); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return -1; + } + + pr_info("[FACTORY] %s - st %d/%d, akm %d/%d, lf %d/%d, hall %d/%d/%d(uT)\n", + __func__, data->msg_buf[MSG_DIGITAL_HALL_ANGLE][0], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][1], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][2], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][3], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][4], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][5], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][6], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][7], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][8]); + + *raw_data = data->msg_buf[MSG_DIGITAL_HALL_ANGLE][2]; + mutex_unlock(&data->digital_hall_mutex); + + return 0; +} +#endif + +static int process_received_msg(struct sk_buff *skb, struct nlmsghdr *nlh) +{ + u16 sensor_type = nlh->nlmsg_type >> 8; + u16 msg_type = nlh->nlmsg_type & 0xff; + + /* check the boundary to prevent memory attack */ + if (msg_type >= MSG_TYPE_MAX || sensor_type >= MSG_SENSOR_MAX || + nlh->nlmsg_len - (int32_t)sizeof(struct nlmsghdr) > + sizeof(int32_t) * msg_size[sensor_type]) { + pr_err("[FACTORY] %s %d, %d, %d\n", __func__, msg_type, sensor_type, nlh->nlmsg_len); + return 0; + } + + if (sensor_type == MSG_FACTORY_INIT_CMD) { + pr_info("[FACTORY] %s - MSG_FACTORY_INIT_CMD\n", __func__); + accel_factory_init_work(data); +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_6AXIS) + sub_accel_factory_init_work(data); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_DEVICE_MODE) + sns_device_mode_init_work(); + sns_flip_init_work(); +#endif +#if IS_ENABLED(CONFIG_LIGHT_FACTORY) + light_init_work(data); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_LIGHT_CALIBRATION) + light_cal_init_work(data); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_PROX_CALIBRATION) + prox_cal_init_work(data); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_PROX_POWER_ON_CAL) + prox_factory_init_work(); +#endif +#ifdef CONFIG_VBUS_NOTIFIER + sns_vbus_init_work(); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) + digital_hall_factory_auto_cal_init_work(data); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_LIGHT_SEAMLESS) + light_seamless_init_work(data); +#endif +#if IS_ENABLED(CONFIG_LPS22HH_FACTORY) || IS_ENABLED(CONFIG_PRESSURE_FACTORY) + pressure_factory_init_work(data); +#endif + return 0; + } + + memcpy(data->msg_buf[sensor_type], + (int32_t *)NLMSG_DATA(nlh), + nlh->nlmsg_len - (int32_t)sizeof(struct nlmsghdr)); + data->ready_flag[msg_type] |= 1 << sensor_type; + + return 0; +} + +static void factory_receive_skb(struct sk_buff *skb) +{ + struct netlink_ext_ack extack = {}; + struct nlmsghdr *nlh; + int len; + int err; + + nlh = (struct nlmsghdr *)skb->data; + len = skb->len; + while (NLMSG_OK(nlh, len)) { + err = process_received_msg(skb, nlh); + /* if err or if this message says it wants a response */ + if (err || (nlh->nlmsg_flags & NLM_F_ACK)) + netlink_ack(skb, nlh, err, &extack); + nlh = NLMSG_NEXT(nlh, len); + } +} + +/* Receive messages from netlink socket. */ +static void factory_test_result_receive(struct sk_buff *skb) +{ + mutex_lock(&factory_mutex); + factory_receive_skb(skb); + mutex_unlock(&factory_mutex); +} + +struct netlink_kernel_cfg netlink_cfg = { + .input = factory_test_result_receive, +}; + +static int __init factory_adsp_init(void) +{ + int i; + + pr_info("[FACTORY] %s\n", __func__); + data = kzalloc(sizeof(*data), GFP_KERNEL); + + for (i = 0; i < MSG_SENSOR_MAX; i++) { + if (msg_size[i] > 0) + data->msg_buf[i] = kzalloc(sizeof(int32_t) * msg_size[i], + GFP_KERNEL); + } + + data->adsp_skt = netlink_kernel_create(&init_net, + NETLINK_ADSP_FAC, &netlink_cfg); + + for (i = 0; i < MSG_SENSOR_MAX; i++) + data->sysfs_created[i] = false; + for (i = 0; i < MSG_TYPE_MAX; i++) + data->ready_flag[i] = 0; + + data->restrict_mode = false; + data->turn_over_crash = 0; + mutex_init(&data->accel_factory_mutex); + mutex_init(&data->prox_factory_mutex); + mutex_init(&data->light_factory_mutex); + mutex_init(&data->remove_sysfs_mutex); +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) + mutex_init(&data->vir_optic_factory_mutex); +#endif +#if IS_ENABLED(CONFIG_FLIP_COVER_DETECTOR_FACTORY) + mutex_init(&data->flip_cover_factory_mutex); +#endif +#if IS_ENABLED(CONFIG_BACKTAP_FACTORY) + mutex_init(&data->backtap_factory_mutex); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) + mutex_init(&data->digital_hall_mutex); + INIT_DELAYED_WORK(&data->dhall_cal_work, dhall_cal_work_func); +#endif +#if IS_ENABLED(CONFIG_LIGHT_FACTORY) + INIT_DELAYED_WORK(&data->light_init_work, light_init_work_func); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_LIGHT_CALIBRATION) + INIT_DELAYED_WORK(&data->light_cal_work, light_cal_read_work_func); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_DDI_COPR_FOR_LIGHT_SENSOR) + INIT_DELAYED_WORK(&data->light_copr_debug_work, + light_copr_debug_work_func); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_FIFO_DEBUG_FOR_LIGHT_SENSOR) + INIT_DELAYED_WORK(&data->light_fifo_debug_work, + light_fifo_debug_work_func); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_BRIGHTNESS_NOTIFY_FOR_LIGHT_SENSOR) && \ + IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) + INIT_WORK(&data->light_br_work, light_brightness_work_func); +#endif + INIT_DELAYED_WORK(&data->accel_cal_work, accel_cal_work_func); +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_6AXIS) + INIT_DELAYED_WORK(&data->sub_accel_cal_work, sub_accel_cal_work_func); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) || IS_ENABLED(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) || defined(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) + INIT_DELAYED_WORK(&data->lsm6dso_selftest_stop_work, lsm6dso_selftest_stop_work_func); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_LIGHT_SEAMLESS) + INIT_DELAYED_WORK(&data->light_seamless_work, light_seamless_work_func); +#endif +#if IS_ENABLED(CONFIG_LPS22HH_FACTORY) || IS_ENABLED(CONFIG_PRESSURE_FACTORY) + INIT_DELAYED_WORK(&data->pressure_cal_work, pressure_cal_work_func); +#endif + core_factory_init(); +#if IS_ENABLED(CONFIG_LSM6DSO_FACTORY) + lsm6dso_accel_factory_init(); + lsm6dso_gyro_factory_init(); +#endif +#if IS_ENABLED(CONFIG_LSM6DSL_FACTORY) + lsm6dsl_accel_factory_init(); + lsm6dsl_gyro_factory_init(); +#endif +#if IS_ENABLED(CONFIG_AK09918_FACTORY) + ak09918_factory_init(); +#endif +#if IS_ENABLED(CONFIG_LPS22HH_FACTORY) + lps22hh_pressure_factory_init(); +#elif IS_ENABLED(CONFIG_PRESSURE_FACTORY) + pressure_factory_init(); +#endif +#if IS_ENABLED(CONFIG_BACKTAP_FACTORY) + backtap_factory_init(); +#endif +#if IS_ENABLED(CONFIG_LIGHT_FACTORY) + light_factory_init(); +#endif +#if IS_ENABLED(CONFIG_PROX_FACTORY) + prox_factory_init(); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_6AXIS) + lsm6dso_sub_accel_factory_init(); + lsm6dso_sub_gyro_factory_init(); +#endif + +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) + ak09970_factory_init(); +#endif + +#if IS_ENABLED(CONFIG_FLIP_COVER_DETECTOR_FACTORY) + flip_cover_detector_factory_init(); +#endif + + pr_info("[FACTORY] %s: Timer Init\n", __func__); + return 0; +} + +static void __exit factory_adsp_exit(void) +{ + int i; + + core_factory_exit(); +#if IS_ENABLED(CONFIG_LSM6DSO_FACTORY) + lsm6dso_accel_factory_exit(); + lsm6dso_gyro_factory_exit(); +#endif +#if IS_ENABLED(CONFIG_LSM6DSL_FACTORY) + lsm6dsl_accel_factory_exit(); + lsm6dsl_gyro_factory_exit(); +#endif +#if IS_ENABLED(CONFIG_AK09918_FACTORY) + ak09918_factory_exit(); +#endif +#if IS_ENABLED(CONFIG_LPS22HH_FACTORY) + lps22hh_pressure_factory_exit(); +#elif IS_ENABLED(CONFIG_PRESSURE_FACTORY) + pressure_factory_exit(); +#endif +#if IS_ENABLED(CONFIG_BACKTAP_FACTORY) + backtap_factory_exit(); +#endif +#if IS_ENABLED(CONFIG_LIGHT_FACTORY) + cancel_delayed_work_sync(&data->light_init_work); + light_factory_exit(); +#endif +#if IS_ENABLED(CONFIG_PROX_FACTORY) + prox_factory_exit(); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_6AXIS) + lsm6dso_sub_accel_factory_exit(); + lsm6dso_sub_gyro_factory_exit(); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) + ak09970_factory_exit(); +#endif +#if IS_ENABLED(CONFIG_FLIP_COVER_DETECTOR_FACTORY) + flip_cover_detector_factory_exit(); +#endif + + mutex_destroy(&data->accel_factory_mutex); + mutex_destroy(&data->prox_factory_mutex); + mutex_destroy(&data->light_factory_mutex); + mutex_destroy(&data->remove_sysfs_mutex); +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) + mutex_destroy(&data->vir_optic_factory_mutex); +#endif +#if IS_ENABLED(CONFIG_FLIP_COVER_DETECTOR_FACTORY) + mutex_destroy(&data->flip_cover_factory_mutex); +#endif +#if IS_ENABLED(CONFIG_BACKTAP_FACTORY) + mutex_destroy(&data->backtap_factory_mutex); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) + mutex_destroy(&data->digital_hall_mutex); + cancel_delayed_work_sync(&data->dhall_cal_work); + cancel_delayed_work_sync(&data->lsm6dso_selftest_stop_work); +#elif IS_ENABLED(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) || defined(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) + cancel_delayed_work_sync(&data->lsm6dso_selftest_stop_work); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_LIGHT_CALIBRATION) + cancel_delayed_work_sync(&data->light_cal_work); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_DDI_COPR_FOR_LIGHT_SENSOR) + cancel_delayed_work_sync(&data->light_copr_debug_work); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_FIFO_DEBUG_FOR_LIGHT_SENSOR) + cancel_delayed_work_sync(&data->light_fifo_debug_work); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_BRIGHTNESS_NOTIFY_FOR_LIGHT_SENSOR) + cancel_work_sync(&data->light_br_work); +#endif + cancel_delayed_work_sync(&data->accel_cal_work); +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_6AXIS) + cancel_delayed_work_sync(&data->sub_accel_cal_work); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_LIGHT_SEAMLESS) + cancel_delayed_work_sync(&data->light_seamless_work); +#endif + cancel_delayed_work_sync(&data->pressure_cal_work); + + for (i = 0; i < MSG_SENSOR_MAX; i++) + kfree(data->msg_buf[i]); + pr_info("[FACTORY] %s\n", __func__); +} + +module_init(factory_adsp_init); +module_exit(factory_adsp_exit); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Support for factory test sensors (adsp)"); diff --git a/drivers/adsp_factory/ak09918_mag.c b/drivers/adsp_factory/ak09918_mag.c new file mode 100755 index 000000000000..14916557c11e --- /dev/null +++ b/drivers/adsp_factory/ak09918_mag.c @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include "adsp.h" +#define VENDOR "AKM" +#define CHIP_ID "AK09918" + +#define MAG_ST_TRY_CNT 3 +#define ABS(x) (((x)>0)?(x):-(x)) +#define AKM_ST_FAIL (-1) + +static ssize_t mag_vendor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR); +} + +static ssize_t mag_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", CHIP_ID); +} + +static ssize_t mag_check_cntl(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "OK\n"); +} + +static ssize_t mag_check_registers(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, + "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); +} + +static ssize_t mag_get_asa(struct device *dev, + struct device_attribute *attr, char *buf) +{ + /* Do not have Fuserom */ + return snprintf(buf, PAGE_SIZE, "%u,%u,%u\n", 128, 128, 128); +} + +static ssize_t mag_get_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + /* Do not have Fuserom */ + return snprintf(buf, PAGE_SIZE, "%s\n", "OK"); +} + +static ssize_t mag_raw_data_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + + adsp_unicast(NULL, 0, MSG_MAG, 0, MSG_TYPE_GET_RAW_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & 1 << MSG_MAG) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_MAG); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "0,0,0\n"); + } + + return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n", + data->msg_buf[MSG_MAG][0], + data->msg_buf[MSG_MAG][1], + data->msg_buf[MSG_MAG][2]); +} + +static ssize_t mag_raw_data_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + adsp_unicast(NULL, 0, MSG_MAG_CAL, 0, MSG_TYPE_FACTORY_ENABLE); + msleep(20); + adsp_unicast(NULL, 0, MSG_MAG_CAL, 0, MSG_TYPE_SET_CAL_DATA); + msleep(20); + adsp_unicast(NULL, 0, MSG_MAG_CAL, 0, MSG_TYPE_FACTORY_DISABLE); + + return size; +} + +static ssize_t mag_selftest_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + int retry = 0, i; + int abs_adc_sum = 0, abs_adc_x = 0, abs_adc_y = 0, abs_adc_z = 0; + int st_status = 0; + +RETRY_MAG_SELFTEST: + pr_info("[FACTORY] %s - start\n", __func__); + adsp_unicast(NULL, 0, MSG_MAG, 0, MSG_TYPE_ST_SHOW_DATA); + + while (!(data->ready_flag[MSG_TYPE_ST_SHOW_DATA] & 1 << MSG_MAG) && + cnt++ < TIMEOUT_CNT) + msleep(26); + + data->ready_flag[MSG_TYPE_ST_SHOW_DATA] &= ~(1 << MSG_MAG); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + data->msg_buf[MSG_MAG][0] = -1; +#ifdef CONFIG_SEC_FACTORY + panic("force crash : sensor selftest timeout\n"); +#endif + } + + if (!(data->msg_buf[MSG_MAG][0] == 0)) { + if (retry < MAG_ST_TRY_CNT) { + retry++; + for (i = 0; i < 10; i++) + data->msg_buf[MSG_MAG][i] = 0; + + msleep(100); + cnt = 0; + pr_info("[FACTORY] %s - retry %d\n", __func__, retry); + goto RETRY_MAG_SELFTEST; + } + + adsp_unicast(NULL, 0, MSG_MAG_CAL, 0, MSG_TYPE_FACTORY_ENABLE); + msleep(20); + adsp_unicast(NULL, 0, MSG_MAG_CAL, 0, MSG_TYPE_SET_CAL_DATA); + msleep(20); + adsp_unicast(NULL, 0, MSG_MAG_CAL, 0, MSG_TYPE_FACTORY_DISABLE); + return snprintf(buf, PAGE_SIZE, "-1,0,0,0,0,0,0,0,0,0\n"); + } + + if (data->msg_buf[MSG_MAG][1] != 0) { + pr_info("[FACTORY] %s - msg_buf[1] 0x%x\n", __func__, data->msg_buf[MSG_MAG][1]); + st_status = AKM_ST_FAIL; + } + pr_info("[FACTORY] status=%d, st_status=%d, st_xyz=%d,%d,%d, dac=%d, adc=%d, adc_xyz=%d,%d,%d\n", + data->msg_buf[MSG_MAG][0], st_status, + data->msg_buf[MSG_MAG][2], data->msg_buf[MSG_MAG][3], + data->msg_buf[MSG_MAG][4], data->msg_buf[MSG_MAG][5], + data->msg_buf[MSG_MAG][6], data->msg_buf[MSG_MAG][7], + data->msg_buf[MSG_MAG][8], data->msg_buf[MSG_MAG][9]); + + abs_adc_x = ABS(data->msg_buf[MSG_MAG][7]); + abs_adc_y = ABS(data->msg_buf[MSG_MAG][8]); + abs_adc_z = ABS(data->msg_buf[MSG_MAG][9]); + abs_adc_sum = abs_adc_x + abs_adc_y + abs_adc_z; + + if (abs_adc_sum >= 26666) { + pr_info("[FACTORY] abs_adc_sum is higher then 40Gauss\n"); + st_status = AKM_ST_FAIL; + } + + adsp_unicast(NULL, 0, MSG_MAG_CAL, 0, MSG_TYPE_FACTORY_ENABLE); + msleep(20); + adsp_unicast(NULL, 0, MSG_MAG_CAL, 0, MSG_TYPE_SET_CAL_DATA); + msleep(20); + adsp_unicast(NULL, 0, MSG_MAG_CAL, 0, MSG_TYPE_FACTORY_DISABLE); + return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", + data->msg_buf[MSG_MAG][0], st_status, + data->msg_buf[MSG_MAG][2], data->msg_buf[MSG_MAG][3], + data->msg_buf[MSG_MAG][4], data->msg_buf[MSG_MAG][5], + data->msg_buf[MSG_MAG][6], data->msg_buf[MSG_MAG][7], + data->msg_buf[MSG_MAG][8], data->msg_buf[MSG_MAG][9]); +} + +static ssize_t mag_dhr_sensor_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + + adsp_unicast(NULL, 0, MSG_MAG, 0, MSG_TYPE_GET_DHR_INFO); + while (!(data->ready_flag[MSG_TYPE_GET_DHR_INFO] & 1 << MSG_MAG) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_DHR_INFO] &= ~(1 << MSG_MAG); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + } else { + pr_info("[FACTORY] %s - [00h-03h] %02x,%02x,%02x,%02x [10h-16h,18h] %02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x [30h-32h] %02x,%02x,%02x\n", + __func__, + data->msg_buf[MSG_MAG][0], data->msg_buf[MSG_MAG][1], + data->msg_buf[MSG_MAG][2], data->msg_buf[MSG_MAG][3], + data->msg_buf[MSG_MAG][4], data->msg_buf[MSG_MAG][5], + data->msg_buf[MSG_MAG][6], data->msg_buf[MSG_MAG][7], + data->msg_buf[MSG_MAG][8], data->msg_buf[MSG_MAG][9], + data->msg_buf[MSG_MAG][10], data->msg_buf[MSG_MAG][11], + data->msg_buf[MSG_MAG][12], data->msg_buf[MSG_MAG][13], + data->msg_buf[MSG_MAG][14]); + } + + return snprintf(buf, PAGE_SIZE, "%s\n", "Done"); +} + +static DEVICE_ATTR(name, 0444, mag_name_show, NULL); +static DEVICE_ATTR(vendor, 0444, mag_vendor_show, NULL); +static DEVICE_ATTR(raw_data, 0664, mag_raw_data_show, mag_raw_data_store); +static DEVICE_ATTR(dac, 0444, mag_check_cntl, NULL); +static DEVICE_ATTR(chk_registers, 0444, mag_check_registers, NULL); +static DEVICE_ATTR(selftest, 0440, mag_selftest_show, NULL); +static DEVICE_ATTR(asa, 0444, mag_get_asa, NULL); +static DEVICE_ATTR(status, 0444, mag_get_status, NULL); +#ifdef CONFIG_SEC_FACTORY +static DEVICE_ATTR(dhr_sensor_info, 0444, mag_dhr_sensor_info_show, NULL); +#else +static DEVICE_ATTR(dhr_sensor_info, 0440, mag_dhr_sensor_info_show, NULL); +#endif + +static struct device_attribute *mag_attrs[] = { + &dev_attr_name, + &dev_attr_vendor, + &dev_attr_raw_data, + &dev_attr_dac, + &dev_attr_chk_registers, + &dev_attr_selftest, + &dev_attr_asa, + &dev_attr_status, + &dev_attr_dhr_sensor_info, + NULL, +}; + +int ak09918_factory_init(void) +{ + adsp_factory_register(MSG_MAG, mag_attrs); + + pr_info("[FACTORY] %s\n", __func__); + + return 0; +} + +void ak09918_factory_exit(void) +{ + adsp_factory_unregister(MSG_MAG); + + pr_info("[FACTORY] %s\n", __func__); +} diff --git a/drivers/adsp_factory/ak09973_digital_hall.c b/drivers/adsp_factory/ak09973_digital_hall.c new file mode 100755 index 000000000000..78197594d6bd --- /dev/null +++ b/drivers/adsp_factory/ak09973_digital_hall.c @@ -0,0 +1,1234 @@ +/* + * Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include "adsp.h" +#define VENDOR "AKM" +#define CHIP_ID "AK09973" +#define IDX_180_X 0 +#define IDX_180_Y 1 +#define IDX_180_Z 2 +#define IDX_90_X 3 +#define IDX_90_Y 4 +#define IDX_90_Z 5 +#define IDX_0_X 6 +#define IDX_0_Y 7 +#define IDX_0_Z 8 +#define SPEC_180_X_IDX 3 +#define SPEC_90_X_IDX 4 +#define SPEC_0_X_IDX 5 +#define SPEC_180_Y_IDX 6 +#define SPEC_90_Y_IDX 7 +#define SPEC_0_Y_IDX 8 +#define SPEC_180_Z_IDX 9 +#define SPEC_90_Z_IDX 10 +#define SPEC_0_Z_IDX 11 +#define SPEC_MIN_IDX 0 +#define SPEC_MAX_IDX 1 +#define PASS 0 +#define FAIL (-1) +#define FAIL_SPECOUT (-2) +#define FAIL_STUCK (-3) +#define ST_SPEC_MASK 0xE000 +#define DEGREE_180 180 +#define DEGREE_90 90 +#define DEGREE_0 0 +#define FAC_CAL_DATA_NUM 9 +#define HALL_ANGLE_SPEC 15 +#define FLEX_LO 80 +#define FLEX_COVER_HI 70 + +#if ENABLE_LF_STREAM +#define LF_STREAM_AUTO_CAL_X_PATH "/data/digital_hall_auto_cal_x" +#define LF_STREAM_AUTO_CAL_Y_PATH "/data/digital_hall_auto_cal_y" +#define LF_STREAM_AUTO_CAL_Z_PATH "/data/digital_hall_auto_cal_z" +#endif + +static int32_t spec[12][2] = { + {170, 180}, {80, 110}, {0, 10}, //ref angle 180, 90, 0 + {-39640, 39640}, {-39640, 39640}, {-39640, 39640}, // X 180, 90, 0 + {-39640, 39640}, {-39640, 39640}, {-39640, 39640}, // Y 180, 90, 0 + {-39640, 39640}, {-39640, 39640}, {-39640, 39640} // Z 180, 90, 0 +}; + +static int32_t test_spec[10][2] = { + {150, 170}, {40, 60}, //ref angle 160, 50 + {-39640, 39640}, {-39640, 39640}, // X 160, 50 + {-39640, 39640}, {-39640, 39640}, // Y 160, 50 + {-39640, 39640}, {-39640, 39640}, // Z 160, 50 + {145, 175}, {35, 65} //hall angle 160, 50 +}; + +int32_t curr_angle; + +#if ENABLE_LF_STREAM +struct lf_stream_data { + int32_t ref_x[19]; + int32_t ref_y[19]; + int32_t ref_z[19]; + int32_t flg_update; +}; +static struct lf_stream_data *pdata; +#endif + +struct autocal_data_force_update { +#ifdef CONFIG_SEC_FACTORY + struct workqueue_struct *autocal_debug_wq; + struct work_struct work_autocal_debug; +#endif + struct hrtimer rftest_timer; + struct work_struct work_rftest; + struct workqueue_struct *rftest_wq; + int32_t ref_x[19]; + int32_t ref_y[19]; + int32_t ref_z[19]; + int32_t flg_update; + int32_t min_angle; + int32_t max_angle; + int32_t init_angle; + short rftest_timer_enabled; + int32_t flex_low; + int32_t flex_cover_hi; +#ifdef CONFIG_SEC_FACTORY + uint8_t block_autocal; +#endif +}; +static struct autocal_data_force_update *auto_cal_data; + +#ifdef CONFIG_SEC_FACTORY +void autocal_debug_work_func(struct work_struct *work) +{ + adsp_unicast(NULL, 0, MSG_DIGITAL_HALL_ANGLE, 0, MSG_TYPE_SET_CAL_DATA); +} +#endif + +static enum hrtimer_restart rftest_timer_func(struct hrtimer *timer) +{ + queue_work(auto_cal_data->rftest_wq, &auto_cal_data->work_rftest); + hrtimer_forward_now(&auto_cal_data->rftest_timer, + ns_to_ktime(2000 * NSEC_PER_MSEC)); + return HRTIMER_RESTART; +} + +static void rftest_work_func(struct work_struct *work) +{ + int32_t hall_angle = 0; + + get_hall_angle_data(&hall_angle); + + if (hall_angle < auto_cal_data->min_angle) + auto_cal_data->min_angle = hall_angle; + if (hall_angle > auto_cal_data->max_angle) + auto_cal_data->max_angle = hall_angle; + + pr_info("[FACTORY] %s - curr/min/max = %d/%d/%d\n", + __func__, hall_angle, + auto_cal_data->min_angle, auto_cal_data->max_angle); +} + +static ssize_t digital_hall_vendor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR); +} + +static ssize_t digital_hall_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", CHIP_ID); +} + +static ssize_t digital_hall_selftest_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + + pr_info("[FACTORY] %s - start", __func__); + + mutex_lock(&data->digital_hall_mutex); + adsp_unicast(NULL, 0, MSG_DIGITAL_HALL, 0, MSG_TYPE_ST_SHOW_DATA); + + while (!(data->ready_flag[MSG_TYPE_ST_SHOW_DATA] & 1 << MSG_DIGITAL_HALL) && + cnt++ < TIMEOUT_CNT) + msleep(26); + + data->ready_flag[MSG_TYPE_ST_SHOW_DATA] &= ~(1 << MSG_DIGITAL_HALL); + mutex_unlock(&data->digital_hall_mutex); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "-1,0,0,0,0,0,0,0,0,0,0\n"); + } +/* + * Meaning of data->msg_buf[MSG_DIGITAL_HALL][14] value. Refer sns_ak0997x_hal.h + * + * TLIMIT_NO_INVALID_ID (1 << 1) + * TLIMIT_NO_RESET (1 << 2) + * TLIMIT_NO_SET_SELFTEST (1 << 3) + * TLIMIT_NO_CNT_CNTL2 (1 << 4) + * TLIMIT_NO_CNT_WAIT (1 << 5) + * TLIMIT_NO_CNT_READ (1 << 6) + * TLIMIT_NO_DATA_CHANGED (1 << 7) + * TLIMIT_NO_SLF_RVHX (1 << 13) + * TLIMIT_NO_SLF_RVHY (1 << 14) + * TLIMIT_NO_SLF_RVHZ (1 << 15) + * TLIMIT_NO_SLF_ST2 (1 << 16) + */ + pr_info("[FACTORY] I2C_ERROR: %d, ST_RES: %d, min: %d/%d/%d, max: %d/%d/%d, avg: %d/%d/%d, st: %d/%d/%d, err: %d\n", + data->msg_buf[MSG_DIGITAL_HALL][0], data->msg_buf[MSG_DIGITAL_HALL][1], + data->msg_buf[MSG_DIGITAL_HALL][2], data->msg_buf[MSG_DIGITAL_HALL][3], + data->msg_buf[MSG_DIGITAL_HALL][4], data->msg_buf[MSG_DIGITAL_HALL][5], + data->msg_buf[MSG_DIGITAL_HALL][6], data->msg_buf[MSG_DIGITAL_HALL][7], + data->msg_buf[MSG_DIGITAL_HALL][8], data->msg_buf[MSG_DIGITAL_HALL][9], + data->msg_buf[MSG_DIGITAL_HALL][10], data->msg_buf[MSG_DIGITAL_HALL][11], + data->msg_buf[MSG_DIGITAL_HALL][12], data->msg_buf[MSG_DIGITAL_HALL][13], + data->msg_buf[MSG_DIGITAL_HALL][14]); + + if (data->msg_buf[MSG_DIGITAL_HALL][1] == FAIL_STUCK) + data->msg_buf[MSG_DIGITAL_HALL][1] = FAIL; + + if ((data->msg_buf[MSG_DIGITAL_HALL][14] & ST_SPEC_MASK) != 0) + data->msg_buf[MSG_DIGITAL_HALL][1] = FAIL_SPECOUT; + + return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", + data->msg_buf[MSG_DIGITAL_HALL][0], data->msg_buf[MSG_DIGITAL_HALL][1], + data->msg_buf[MSG_DIGITAL_HALL][2], data->msg_buf[MSG_DIGITAL_HALL][3], + data->msg_buf[MSG_DIGITAL_HALL][4], data->msg_buf[MSG_DIGITAL_HALL][5], + data->msg_buf[MSG_DIGITAL_HALL][6], data->msg_buf[MSG_DIGITAL_HALL][7], + data->msg_buf[MSG_DIGITAL_HALL][11], data->msg_buf[MSG_DIGITAL_HALL][12], + data->msg_buf[MSG_DIGITAL_HALL][13]); +} + +static ssize_t digital_hall_spec_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", + spec[0][0], spec[0][1], spec[1][0], spec[1][1], spec[2][0], spec[2][1], + spec[3][0], spec[3][1], spec[4][0], spec[4][1], spec[5][0], spec[5][1], + spec[6][0], spec[6][1], spec[7][0], spec[7][1], spec[8][0], spec[8][1], + spec[9][0], spec[9][1], spec[10][0], spec[10][1], spec[11][0], spec[11][1]); +} + +static ssize_t digital_hall_test_spec_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", + test_spec[0][0], test_spec[0][1], test_spec[1][0], test_spec[1][1], + test_spec[2][0], test_spec[2][1], test_spec[3][0], test_spec[3][1], + test_spec[4][0], test_spec[4][1], test_spec[5][0], test_spec[5][1], + test_spec[6][0], test_spec[6][1], test_spec[7][0], test_spec[7][1], + test_spec[8][0], test_spec[8][1], test_spec[9][0], test_spec[9][1]); +} + +static ssize_t digital_hall_ref_angle_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + int32_t min, max, result; + + min = curr_angle - 10; + max = curr_angle + 10; + result = PASS; + + mutex_lock(&data->digital_hall_mutex); + adsp_unicast(NULL, 0, MSG_DIGITAL_HALL_ANGLE, 0, MSG_TYPE_GET_RAW_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & 1 << MSG_DIGITAL_HALL_ANGLE) && + cnt++ < TIMEOUT_CNT) + msleep(20); + + data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_DIGITAL_HALL_ANGLE); + mutex_unlock(&data->digital_hall_mutex); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "-1\n"); + } + + pr_info("[FACTORY] %s - st %d/%d, akm %d/%d, lf %d/%d, hall %d/%d/%d(uT)\n", + __func__, data->msg_buf[MSG_DIGITAL_HALL_ANGLE][0], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][1], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][2], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][3], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][4], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][5], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][6], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][7], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][8]); + + if (data->msg_buf[MSG_DIGITAL_HALL_ANGLE][0] < min || + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][0] > max) + result = FAIL; + + return snprintf(buf, PAGE_SIZE, "%d,%d\n", + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][0], result); +} + +static ssize_t digital_hall_read_data_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + + mutex_lock(&data->digital_hall_mutex); + adsp_unicast(NULL, 0, MSG_DIGITAL_HALL_ANGLE, 0, MSG_TYPE_GET_RAW_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & 1 << MSG_DIGITAL_HALL_ANGLE) && + cnt++ < TIMEOUT_CNT) + msleep(20); + + data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_DIGITAL_HALL_ANGLE); + mutex_unlock(&data->digital_hall_mutex); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "-1\n"); + } + + pr_info("[FACTORY] %s - st %d/%d, akm %d/%d, lf %d/%d, hall %d/%d/%d(uT)\n", + __func__, data->msg_buf[MSG_DIGITAL_HALL_ANGLE][0], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][1], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][2], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][3], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][4], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][5], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][6], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][7], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][8]); + + return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d,%d,%d,%d,%d,%d\n", + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][0], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][1], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][2], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][3], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][4], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][5], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][6], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][7], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][8]); +} + +static ssize_t digital_hall_test_read_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + int result = PASS; + int hall_angle = 0; + int min = curr_angle - HALL_ANGLE_SPEC; + int max = curr_angle + HALL_ANGLE_SPEC; + + mutex_lock(&data->digital_hall_mutex); + adsp_unicast(NULL, 0, MSG_DIGITAL_HALL_ANGLE, 0, MSG_TYPE_GET_RAW_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & 1 << MSG_DIGITAL_HALL_ANGLE) && + cnt++ < TIMEOUT_CNT) + msleep(20); + + data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_DIGITAL_HALL_ANGLE); + mutex_unlock(&data->digital_hall_mutex); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "-1,-1,-1,-1,-1,-1\n"); + } + + pr_info("[FACTORY] %s - st %d/%d, akm %d/%d, lf %d/%d, hall %d/%d/%d(uT)\n", + __func__, data->msg_buf[MSG_DIGITAL_HALL_ANGLE][0], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][1], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][2], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][3], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][4], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][5], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][6], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][7], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][8]); + + hall_angle = data->msg_buf[MSG_DIGITAL_HALL_ANGLE][2]; + + if (hall_angle < min || hall_angle > max) { + pr_info("[FACTORY] %s - %d (%d, %d)\n", __func__, + hall_angle, min, max); + result = FAIL; + } + + return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d,%d,%d\n", + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][0], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][6], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][7], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][8], + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][2], + result); +} + +static ssize_t digital_hall_test_read_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + if (sysfs_streq(buf, "50")) { + curr_angle = 50; + } else if (sysfs_streq(buf, "160")) { + curr_angle = 160; + } else { + pr_err("[FACTORY] %s - wrong degree !!!\n", __func__); + return size; + } + + pr_info("[FACTORY] %s - Test read at degree %d\n", + __func__, curr_angle); + + return size; +} + +static ssize_t reset_auto_cal_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int reset_data[58] = { 0, }; + uint8_t cnt = 0; + + mutex_lock(&data->digital_hall_mutex); + /* reset */ + adsp_unicast(reset_data, sizeof(reset_data), + MSG_DIGITAL_HALL_ANGLE, 0, MSG_TYPE_SET_REGISTER); + + /* read */ + adsp_unicast(NULL, 0, MSG_DIGITAL_HALL_ANGLE, 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] & 1 << MSG_DIGITAL_HALL_ANGLE) && + cnt++ < 3) + msleep(30); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << MSG_DIGITAL_HALL_ANGLE); + mutex_unlock(&data->digital_hall_mutex); + + if (cnt >= 3) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "-1\n"); + } + + pr_info("[FACTORY] %s: flg_update=%d\n", __func__, data->msg_buf[MSG_DIGITAL_HALL_ANGLE][0]); + + return snprintf(buf, PAGE_SIZE, "%d\n", + data->msg_buf[MSG_DIGITAL_HALL_ANGLE][0]); +} + +static ssize_t check_auto_cal_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + + pr_info("[FACTORY] %s\n", __func__); + + mutex_lock(&data->digital_hall_mutex); + /* Try to backup auto cal table */ + adsp_unicast(NULL, 0, MSG_DIGITAL_HALL_ANGLE, 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] & 1 << MSG_DIGITAL_HALL_ANGLE) && + cnt++ < 10) + msleep(30); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << MSG_DIGITAL_HALL_ANGLE); + mutex_unlock(&data->digital_hall_mutex); + + if (cnt >= 10) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "-1\n"); + } + + + auto_cal_data->flg_update = data->msg_buf[MSG_DIGITAL_HALL_ANGLE][0]; + pr_info("[FACTORY] %s: flg_update=%d\n", __func__, auto_cal_data->flg_update); + + + if (auto_cal_data->flg_update) { + memcpy(auto_cal_data->ref_x, &data->msg_buf[MSG_DIGITAL_HALL_ANGLE][1], sizeof(int32_t) * 19); + memcpy(auto_cal_data->ref_y, &data->msg_buf[MSG_DIGITAL_HALL_ANGLE][20], sizeof(int32_t) * 19); + memcpy(auto_cal_data->ref_z, &data->msg_buf[MSG_DIGITAL_HALL_ANGLE][39], sizeof(int32_t) * 19); + } + +#ifdef CONFIG_SEC_FACTORY + /* Print mx, my, mz buffer in SSC_DAEMON log */ + adsp_unicast(NULL, 0, MSG_DIGITAL_HALL_ANGLE, 0, MSG_TYPE_SET_CAL_DATA); +#endif + return snprintf(buf, PAGE_SIZE, "%d\n", auto_cal_data->flg_update); +} + +static ssize_t backup_restore_auto_cal_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int new_value; + int32_t auto_cal_buf[58] = { 0, }; + uint8_t cnt = 0; + + if (sysfs_streq(buf, "0")) + new_value = 0; + else if (sysfs_streq(buf, "1")) + new_value = 1; + else + return size; + + pr_info("[FACTORY] %s: new_value %d\n", __func__, new_value); + + if (new_value) { + mutex_lock(&data->digital_hall_mutex); + adsp_unicast(NULL, 0, MSG_DIGITAL_HALL_ANGLE, 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] & 1 << MSG_DIGITAL_HALL_ANGLE) && + cnt++ < 3) + msleep(30); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << MSG_DIGITAL_HALL_ANGLE); + mutex_unlock(&data->digital_hall_mutex); + + if (cnt >= 3) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return size; + } + + pr_info("[FACTORY] %s: flg_update=%d\n", __func__, data->msg_buf[MSG_DIGITAL_HALL_ANGLE][0]); + + if (!data->msg_buf[MSG_DIGITAL_HALL_ANGLE][0]) + return size; + + auto_cal_data->flg_update = data->msg_buf[MSG_DIGITAL_HALL_ANGLE][0]; + memcpy(auto_cal_data->ref_x, &data->msg_buf[MSG_DIGITAL_HALL_ANGLE][1], sizeof(int32_t) * 19); + memcpy(auto_cal_data->ref_y, &data->msg_buf[MSG_DIGITAL_HALL_ANGLE][20], sizeof(int32_t) * 19); + memcpy(auto_cal_data->ref_z, &data->msg_buf[MSG_DIGITAL_HALL_ANGLE][39], sizeof(int32_t) * 19); + + pr_info("[FACTORY] %s: backup auto_cal\n", __func__); + pr_info("[FACTORY] %s: %d/%d/%d/%d\n", __func__, + auto_cal_data->flg_update, auto_cal_data->ref_x[18], + auto_cal_data->ref_y[18], auto_cal_data->ref_z[18]); + +#if IS_ENABLED(CONFIG_SUPPORT_DHALL_SWITCH) + cnt = 0; + pr_info("[FACTORY] %s: Saving bop/brp to registry\n", __func__); + mutex_lock(&data->digital_hall_mutex); + adsp_unicast(NULL, 0, MSG_DIGITAL_HALL, 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] & 1 << MSG_DIGITAL_HALL) && + cnt++ < 3) + msleep(30); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << MSG_DIGITAL_HALL); + mutex_unlock(&data->digital_hall_mutex); + + if (cnt >= 3) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + } else { + pr_info("[FACTORY] %s: bop/brp %d/%d\n", __func__, + data->msg_buf[MSG_DIGITAL_HALL][0], + data->msg_buf[MSG_DIGITAL_HALL][1]); + } +#endif +#ifdef CONFIG_SEC_FACTORY + queue_work(auto_cal_data->autocal_debug_wq, + &auto_cal_data->work_autocal_debug); +#endif + } else { + if (auto_cal_data->flg_update == 0) { + pr_info("[FACTORY] %s: flg_update is zero\n", __func__); + return size; + } + auto_cal_buf[0] = auto_cal_data->flg_update; + memcpy(&auto_cal_buf[1], auto_cal_data->ref_x, sizeof(int32_t) * 19); + memcpy(&auto_cal_buf[20], auto_cal_data->ref_y, sizeof(int32_t) * 19); + memcpy(&auto_cal_buf[39], auto_cal_data->ref_z, sizeof(int32_t) * 19); + + pr_info("[FACTORY] %s: restore auto_cal\n", __func__); + pr_info("[FACTORY] %s: %d/%d/%d/%d\n", __func__, + auto_cal_buf[0], auto_cal_buf[1], + auto_cal_buf[20], auto_cal_buf[39]); + adsp_unicast(auto_cal_buf, sizeof(auto_cal_buf), + MSG_DIGITAL_HALL_ANGLE, 0, MSG_TYPE_SET_REGISTER); + } + + return size; +} + +static ssize_t rf_test_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("[FACTORY] %s - init %d, min %d, max %d\n", __func__, + auto_cal_data->init_angle, + auto_cal_data->min_angle, + auto_cal_data->max_angle); + + return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n", + auto_cal_data->init_angle, + auto_cal_data->min_angle, + auto_cal_data->max_angle); +} + +static ssize_t rf_test_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int new_value; + int32_t angle = 0; + + if (sysfs_streq(buf, "1")) + new_value = 1; + else + new_value = 0; + + if (new_value == auto_cal_data->rftest_timer_enabled) + return size; + + if (new_value == 1) { + auto_cal_data->rftest_timer_enabled = 1; + get_hall_angle_data(&angle); + auto_cal_data->init_angle = angle; + hrtimer_start(&auto_cal_data->rftest_timer, + ns_to_ktime(2000 * NSEC_PER_MSEC), + HRTIMER_MODE_REL); + } else { + auto_cal_data->rftest_timer_enabled = 0; + hrtimer_cancel(&auto_cal_data->rftest_timer); + cancel_work_sync(&auto_cal_data->work_rftest); + auto_cal_data->init_angle = 0; + auto_cal_data->min_angle = 180; + auto_cal_data->max_angle = 0; + } + + pr_info("[FACTORY] %s - %d. init_angle %d\n", __func__, + auto_cal_data->rftest_timer_enabled, + auto_cal_data->init_angle); + + return size; +} + +#if ENABLE_LF_STREAM +static ssize_t lf_stream_reset_auto_cal_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int reset_data[58] = { 0, }; + uint8_t cnt = 0; + + mutex_lock(&data->digital_hall_mutex); + /* reset */ + adsp_unicast(reset_data, sizeof(reset_data), + MSG_LF_STREAM, 0, MSG_TYPE_SET_REGISTER); + + /* read */ + adsp_unicast(NULL, 0, MSG_LF_STREAM, 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] & 1 << MSG_LF_STREAM) && + cnt++ < 3) + msleep(30); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << MSG_LF_STREAM); + mutex_unlock(&data->digital_hall_mutex); + + if (cnt >= 3) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "-1\n"); + } + + pr_info("[FACTORY] %s: flg_update=%d\n", __func__, data->msg_buf[MSG_LF_STREAM][0]); + + return snprintf(buf, PAGE_SIZE, "%d\n", + data->msg_buf[MSG_LF_STREAM][0]); +} + +int set_lf_stream_auto_cal_data(bool first_booting) +{ + struct file *auto_cal_filp = NULL; + mm_segment_t old_fs; + int flag, ret = 0; + umode_t mode = 0; + char *write_buf = kzalloc(AUTO_CAL_FILE_BUF_LEN, GFP_KERNEL); + + if (first_booting) { + flag = O_TRUNC | O_RDWR | O_CREAT; + mode = 0600; + } else { + flag = O_RDWR; + mode = 0660; + } + + /* auto_cal X */ + old_fs = get_fs(); + set_fs(KERNEL_DS); + + auto_cal_filp = filp_open(LF_STREAM_AUTO_CAL_X_PATH, flag, mode); + + if (IS_ERR(auto_cal_filp)) { + set_fs(old_fs); + ret = PTR_ERR(auto_cal_filp); + pr_err("[FACTORY] %s: open fail lf_stream auto_cal_x_filp:%d\n", + __func__, ret); + kfree(write_buf); + return ret; + } + + snprintf(write_buf, AUTO_CAL_FILE_BUF_LEN, + "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + pdata->flg_update, pdata->ref_x[0], pdata->ref_x[1], + pdata->ref_x[2], pdata->ref_x[3], pdata->ref_x[4], + pdata->ref_x[5], pdata->ref_x[6], pdata->ref_x[7], + pdata->ref_x[8], pdata->ref_x[9], pdata->ref_x[10], + pdata->ref_x[11], pdata->ref_x[12], pdata->ref_x[13], + pdata->ref_x[14], pdata->ref_x[15], pdata->ref_x[16], + pdata->ref_x[17], pdata->ref_x[18]); + + ret = vfs_write(auto_cal_filp, (char *)write_buf, + AUTO_CAL_FILE_BUF_LEN * sizeof(char), &auto_cal_filp->f_pos); + + if (ret < 0) + pr_err("[FACTORY] %s: lf_stream auto_cal_x fd write:%d\n", + __func__, ret); + + filp_close(auto_cal_filp, current->files); + set_fs(old_fs); + + /* auto_cal Y */ + old_fs = get_fs(); + set_fs(KERNEL_DS); + + auto_cal_filp = filp_open(LF_STREAM_AUTO_CAL_Y_PATH, flag, mode); + + if (IS_ERR(auto_cal_filp)) { + set_fs(old_fs); + ret = PTR_ERR(auto_cal_filp); + pr_err("[FACTORY] %s: open fail lf_stream auto_cal_y_filp:%d\n", + __func__, ret); + kfree(write_buf); + return ret; + } + + snprintf(write_buf, AUTO_CAL_FILE_BUF_LEN, + "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + pdata->ref_y[0], pdata->ref_y[1], pdata->ref_y[2], + pdata->ref_y[3], pdata->ref_y[4], pdata->ref_y[5], + pdata->ref_y[6], pdata->ref_y[7], pdata->ref_y[8], + pdata->ref_y[9], pdata->ref_y[10], pdata->ref_y[11], + pdata->ref_y[12], pdata->ref_y[13], pdata->ref_y[14], + pdata->ref_y[15], pdata->ref_y[16], pdata->ref_y[17], + pdata->ref_y[18]); + + ret = vfs_write(auto_cal_filp, (char *)write_buf, + AUTO_CAL_FILE_BUF_LEN * sizeof(char), &auto_cal_filp->f_pos); + + if (ret < 0) + pr_err("[FACTORY] %s: lf_stream auto_cal_y fd write:%d\n", + __func__, ret); + + filp_close(auto_cal_filp, current->files); + set_fs(old_fs); + + /* auto_cal Z */ + old_fs = get_fs(); + set_fs(KERNEL_DS); + + auto_cal_filp = filp_open(LF_STREAM_AUTO_CAL_Z_PATH, flag, mode); + + if (IS_ERR(auto_cal_filp)) { + set_fs(old_fs); + ret = PTR_ERR(auto_cal_filp); + pr_err("[FACTORY] %s: open fail lf_stream auto_cal_z_filp:%d\n", + __func__, ret); + kfree(write_buf); + return ret; + } + + snprintf(write_buf, AUTO_CAL_FILE_BUF_LEN, + "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + pdata->ref_z[0], pdata->ref_z[1], pdata->ref_z[2], + pdata->ref_z[3], pdata->ref_z[4], pdata->ref_z[5], + pdata->ref_z[6], pdata->ref_z[7], pdata->ref_z[8], + pdata->ref_z[9], pdata->ref_z[10], pdata->ref_z[11], + pdata->ref_z[12], pdata->ref_z[13], pdata->ref_z[14], + pdata->ref_z[15], pdata->ref_z[16], pdata->ref_z[17], + pdata->ref_z[18]); + + ret = vfs_write(auto_cal_filp, (char *)write_buf, + AUTO_CAL_FILE_BUF_LEN * sizeof(char), &auto_cal_filp->f_pos); + + if (ret < 0) + pr_err("[FACTORY] %s: lf_stream auto_cal_z fd write:%d\n", + __func__, ret); + + filp_close(auto_cal_filp, current->files); + set_fs(old_fs); + + kfree(write_buf); + + pr_info("[FACTORY] %s: saved", __func__); + return ret; +} + +static ssize_t lf_stream_auto_cal_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + + pr_info("[FACTORY] %s\n", __func__); + + mutex_lock(&data->digital_hall_mutex); + adsp_unicast(NULL, 0, MSG_LF_STREAM, 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] & 1 << MSG_LF_STREAM) && + cnt++ < 3) + msleep(30); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << MSG_LF_STREAM); + mutex_unlock(&data->digital_hall_mutex); + + if (cnt >= 3) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, + "lf_stream autocal save failed\n"); + } + + pdata->flg_update = data->msg_buf[MSG_LF_STREAM][0]; + pr_info("[FACTORY] %s: flg_update=%d\n", __func__, pdata->flg_update); + + if (!pdata->flg_update) + return snprintf(buf, PAGE_SIZE, "flg_update is not true\n"); + + memcpy(pdata->ref_x, &data->msg_buf[MSG_LF_STREAM][1], sizeof(int32_t) * 19); + memcpy(pdata->ref_y, &data->msg_buf[MSG_LF_STREAM][20], sizeof(int32_t) * 19); + memcpy(pdata->ref_z, &data->msg_buf[MSG_LF_STREAM][39], sizeof(int32_t) * 19); + + set_lf_stream_auto_cal_data(false); + + return snprintf(buf, PAGE_SIZE, + "lf_stream autocal was saved in file system\n"); +} + +static ssize_t lf_stream_auto_cal_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct file *cal_filp = NULL; + mm_segment_t old_fs; + int ret = 0; + int tmp[58] = { 0, }; + char *auto_cal_buf = kzalloc(AUTO_CAL_FILE_BUF_LEN * sizeof(char), + GFP_KERNEL); + + pr_info("[FACTORY] %s - lf_stream autocal restor start!!!\n", __func__); + + /* auto_cal X */ + old_fs = get_fs(); + set_fs(KERNEL_DS); + + cal_filp = filp_open(LF_STREAM_AUTO_CAL_X_PATH, O_RDONLY, 0440); + if (PTR_ERR(cal_filp) == -ENOENT || PTR_ERR(cal_filp) == -ENXIO) { + pr_info("[FACTORY] %s - no lf_stream_auto_cal_x file\n", + __func__); + set_fs(old_fs); + set_lf_stream_auto_cal_data(true); + } else if (IS_ERR(cal_filp)) { + pr_err("[FACTORY]: %s - filp_open error: lf_stream_auto_cal_x\n", + __func__); + set_fs(old_fs); + } else { + pr_info("[FACTORY] %s - already exist: lf_stream_auto_cal_x\n", __func__); + + ret = vfs_read(cal_filp, (char *)auto_cal_buf, + AUTO_CAL_FILE_BUF_LEN * sizeof(char), &cal_filp->f_pos); + if (ret < 0) { + pr_err("[FACTORY] %s - read fail:%d\n", __func__, ret); + filp_close(cal_filp, current->files); + set_fs(old_fs); + kfree(auto_cal_buf); + return size; + } + + ret = sscanf(auto_cal_buf, + "%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d", + &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4], &tmp[5], + &tmp[6], &tmp[7], &tmp[8], &tmp[9], &tmp[10], &tmp[11], + &tmp[12], &tmp[13], &tmp[14], &tmp[15], &tmp[16], &tmp[17], + &tmp[18], &tmp[19]); + + + if (ret != AUTO_CAL_DATA_NUM + 1) { + pr_err("[FACTORY] %s - lf_stream_auto_cal_x: sscanf fail %d\n", + __func__, ret); + filp_close(cal_filp, current->files); + set_fs(old_fs); + kfree(auto_cal_buf); + return size; + } + + pdata->flg_update = tmp[0]; + memcpy(pdata->ref_x, &tmp[1], sizeof(int32_t) * 19); + pr_info("[FACTORY] %s: flg_update=%d\n", + __func__, pdata->flg_update); + filp_close(cal_filp, current->files); + set_fs(old_fs); + } + + /* auto_cal Y */ + old_fs = get_fs(); + set_fs(KERNEL_DS); + + cal_filp = filp_open(LF_STREAM_AUTO_CAL_Y_PATH, O_RDONLY, 0440); + if (PTR_ERR(cal_filp) == -ENOENT || PTR_ERR(cal_filp) == -ENXIO) { + pr_info("[FACTORY] %s - no lf_stream_auto_cal_y file\n", + __func__); + set_fs(old_fs); + } else if (IS_ERR(cal_filp)) { + pr_err("[FACTORY]: %s - filp_open error: lf_stream_auto_cal_y\n", + __func__); + set_fs(old_fs); + } else { + pr_info("[FACTORY] %s - already exist: lf_stream_auto_cal_y\n", __func__); + + ret = vfs_read(cal_filp, (char *)auto_cal_buf, + AUTO_CAL_FILE_BUF_LEN * sizeof(char), &cal_filp->f_pos); + if (ret < 0) { + pr_err("[FACTORY] %s - read fail:%d\n", __func__, ret); + filp_close(cal_filp, current->files); + set_fs(old_fs); + kfree(auto_cal_buf); + return size; + } + + ret = sscanf(auto_cal_buf, + "%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d", + &tmp[20], &tmp[21], &tmp[22], &tmp[23], &tmp[24], + &tmp[25], &tmp[26], &tmp[27], &tmp[28], &tmp[29], + &tmp[30], &tmp[31], &tmp[32], &tmp[33], &tmp[34], + &tmp[35], &tmp[36], &tmp[37], &tmp[38]); + + + if (ret != AUTO_CAL_DATA_NUM) { + pr_err("[FACTORY] %s - lf_stream_auto_cal_y: sscanf fail %d\n", + __func__, ret); + filp_close(cal_filp, current->files); + set_fs(old_fs); + kfree(auto_cal_buf); + return size; + } + + memcpy(pdata->ref_y, &tmp[20], sizeof(int32_t) * 19); + filp_close(cal_filp, current->files); + set_fs(old_fs); + } + + /* auto_cal Z */ + old_fs = get_fs(); + set_fs(KERNEL_DS); + + cal_filp = filp_open(LF_STREAM_AUTO_CAL_Z_PATH, O_RDONLY, 0440); + if (PTR_ERR(cal_filp) == -ENOENT || PTR_ERR(cal_filp) == -ENXIO) { + pr_info("[FACTORY] %s - no lf_stream_auto_cal_z file\n", + __func__); + set_fs(old_fs); + } else if (IS_ERR(cal_filp)) { + pr_err("[FACTORY]: %s - filp_open error: lf_stream_auto_cal_z\n", + __func__); + set_fs(old_fs); + } else { + pr_info("[FACTORY] %s - already exist: lf_stream_auto_cal_z\n", __func__); + + ret = vfs_read(cal_filp, (char *)auto_cal_buf, + AUTO_CAL_FILE_BUF_LEN * sizeof(char), &cal_filp->f_pos); + if (ret < 0) { + pr_err("[FACTORY] %s - read fail:%d\n", __func__, ret); + filp_close(cal_filp, current->files); + set_fs(old_fs); + kfree(auto_cal_buf); + return size; + } + + ret = sscanf(auto_cal_buf, + "%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d,%9d", + &tmp[39], &tmp[40], &tmp[41], &tmp[42], &tmp[43], + &tmp[44], &tmp[45], &tmp[46], &tmp[47], &tmp[48], + &tmp[49], &tmp[50], &tmp[51], &tmp[52], &tmp[53], + &tmp[54], &tmp[55], &tmp[56], &tmp[57]); + + + if (ret != AUTO_CAL_DATA_NUM) { + pr_err("[FACTORY] %s - lf_stream_auto_cal_z: sscanf fail %d\n", + __func__, ret); + filp_close(cal_filp, current->files); + set_fs(old_fs); + kfree(auto_cal_buf); + return size; + } + + memcpy(pdata->ref_z, &tmp[39], sizeof(int32_t) * 19); + filp_close(cal_filp, current->files); + set_fs(old_fs); + } + kfree(auto_cal_buf); + + adsp_unicast(tmp, sizeof(tmp), + MSG_LF_STREAM, 0, MSG_TYPE_SET_REGISTER); + + pr_info("[FACTORY] %s - lf_stream autocal was restored!!!\n", __func__); + return size; +} +#endif + +static ssize_t flexcover_thd_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("[FACTORY] %s: Curr THD %d/%d\n", __func__, + auto_cal_data->flex_low, auto_cal_data->flex_cover_hi); + + return snprintf(buf, PAGE_SIZE, "%d,%d\n", + auto_cal_data->flex_low, auto_cal_data->flex_cover_hi); +} + +static ssize_t flexcover_thd_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int cnt = 0; + int32_t msg_buf[1]; + + if (sscanf(buf, "%2d", &msg_buf[0]) != 1) { + pr_err("[FACTORY]: %s - The number of data are wrong\n", + __func__); + return -EINVAL; + } + + pr_info("[FACTORY] %s: msg_buf[0] = %d\n", __func__, msg_buf[0]); + + mutex_lock(&data->digital_hall_mutex); + adsp_unicast(msg_buf, sizeof(msg_buf), + MSG_DIGITAL_HALL_ANGLE, 0, MSG_TYPE_SET_THRESHOLD); + + while (!(data->ready_flag[MSG_TYPE_SET_THRESHOLD] & 1 << MSG_DIGITAL_HALL_ANGLE) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_SET_THRESHOLD] &= ~(1 << MSG_DIGITAL_HALL_ANGLE); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + } else { + auto_cal_data->flex_low = data->msg_buf[MSG_DIGITAL_HALL_ANGLE][0]; + auto_cal_data->flex_cover_hi = data->msg_buf[MSG_DIGITAL_HALL_ANGLE][1]; + } + + pr_info("[FACTORY] %s: New THD %d/%d\n", __func__, + auto_cal_data->flex_low, auto_cal_data->flex_cover_hi); + + mutex_unlock(&data->digital_hall_mutex); + + return size; +} + +#ifdef CONFIG_SEC_FACTORY +static ssize_t block_autocal_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("[FACTORY] %s: block_autocal = %u\n", + __func__, auto_cal_data->block_autocal); + + return snprintf(buf, PAGE_SIZE, "%u\n", auto_cal_data->block_autocal); +} + +static ssize_t block_autocal_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int cnt = 0; + int32_t msg_buf[1]; + + if (sysfs_streq(buf, "0")) + msg_buf[0] = 0; + else if (sysfs_streq(buf, "1")) + msg_buf[0] = 1; + else + return size; + + pr_info("[FACTORY] %s: msg_buf[0] = %d\n", __func__, msg_buf[0]); + + mutex_lock(&data->digital_hall_mutex); + adsp_unicast(msg_buf, sizeof(msg_buf), + MSG_DIGITAL_HALL_ANGLE, 0, MSG_TYPE_OPTION_DEFINE); + + while (!(data->ready_flag[MSG_TYPE_OPTION_DEFINE] & 1 << MSG_DIGITAL_HALL_ANGLE) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_OPTION_DEFINE] &= ~(1 << MSG_DIGITAL_HALL_ANGLE); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + } else { + auto_cal_data->block_autocal = + (uint8_t)data->msg_buf[MSG_DIGITAL_HALL_ANGLE][0]; + pr_info("[FACTORY] %s: block_autocal = %u\n", + __func__, auto_cal_data->block_autocal); + } + mutex_unlock(&data->digital_hall_mutex); + + return size; +} +#endif + +static ssize_t digital_hall_dhr_sensor_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + + adsp_unicast(NULL, 0, MSG_DIGITAL_HALL, 0, MSG_TYPE_GET_DHR_INFO); + while (!(data->ready_flag[MSG_TYPE_GET_DHR_INFO] & 1 << MSG_DIGITAL_HALL) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_DHR_INFO] &= ~(1 << MSG_DIGITAL_HALL); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + } else { + pr_info("[FACTORY] %s - ST: %02x, HX/HY/HZ: %d/%d/%d, CNTL: %02x/%02x/%02x, BOP/BRP: %d/%d\n", + __func__, + data->msg_buf[MSG_DIGITAL_HALL][0], data->msg_buf[MSG_DIGITAL_HALL][1], + data->msg_buf[MSG_DIGITAL_HALL][2], data->msg_buf[MSG_DIGITAL_HALL][3], + data->msg_buf[MSG_DIGITAL_HALL][4], data->msg_buf[MSG_DIGITAL_HALL][5], + data->msg_buf[MSG_DIGITAL_HALL][6], data->msg_buf[MSG_DIGITAL_HALL][7], + data->msg_buf[MSG_DIGITAL_HALL][8]); + } + + return snprintf(buf, PAGE_SIZE, "ST: %02x, HX/HY/HZ: %d/%d/%d, CNTL: %02x/%02x/%02x, BOP/BRP: %d/%d\n", + data->msg_buf[MSG_DIGITAL_HALL][0], data->msg_buf[MSG_DIGITAL_HALL][1], + data->msg_buf[MSG_DIGITAL_HALL][2], data->msg_buf[MSG_DIGITAL_HALL][3], + data->msg_buf[MSG_DIGITAL_HALL][4], data->msg_buf[MSG_DIGITAL_HALL][5], + data->msg_buf[MSG_DIGITAL_HALL][6], data->msg_buf[MSG_DIGITAL_HALL][7], + data->msg_buf[MSG_DIGITAL_HALL][8]); +} + + +static DEVICE_ATTR(name, 0444, digital_hall_name_show, NULL); +static DEVICE_ATTR(vendor, 0444, digital_hall_vendor_show, NULL); +static DEVICE_ATTR(selftest, 0440, digital_hall_selftest_show, NULL); +static DEVICE_ATTR(spec, 0440, digital_hall_spec_show, NULL); +static DEVICE_ATTR(test_spec, 0440, digital_hall_test_spec_show, NULL); +static DEVICE_ATTR(ref_angle, 0440, digital_hall_ref_angle_show, NULL); +static DEVICE_ATTR(read_data, 0440, digital_hall_read_data_show, NULL); +static DEVICE_ATTR(test_read, 0660, + digital_hall_test_read_show, digital_hall_test_read_store); +static DEVICE_ATTR(reset_auto_cal, 0440, reset_auto_cal_show, NULL); +static DEVICE_ATTR(check_auto_cal, 0440, check_auto_cal_show, NULL); +static DEVICE_ATTR(backup_restore_auto_cal, 0220, + NULL, backup_restore_auto_cal_store); +#if ENABLE_LF_STREAM +static DEVICE_ATTR(lf_stream_reset_auto_cal, 0440, + lf_stream_reset_auto_cal_show, NULL); +static DEVICE_ATTR(lf_stream_auto_cal, 0660, + lf_stream_auto_cal_show, lf_stream_auto_cal_store); +#endif +static DEVICE_ATTR(rf_test, 0660, rf_test_show, rf_test_store); +static DEVICE_ATTR(flexcover_thd, 0660, flexcover_thd_show, flexcover_thd_store); +#ifdef CONFIG_SEC_FACTORY +static DEVICE_ATTR(block_autocal, 0660, block_autocal_show, block_autocal_store); +#endif +#ifdef CONFIG_SEC_FACTORY +static DEVICE_ATTR(dhr_sensor_info, 0444, digital_hall_dhr_sensor_info_show, NULL); +#else +static DEVICE_ATTR(dhr_sensor_info, 0440, digital_hall_dhr_sensor_info_show, NULL); +#endif + +static struct device_attribute *digital_hall_attrs[] = { + &dev_attr_name, + &dev_attr_vendor, + &dev_attr_selftest, + &dev_attr_spec, + &dev_attr_test_spec, + &dev_attr_ref_angle, + &dev_attr_read_data, + &dev_attr_test_read, + &dev_attr_reset_auto_cal, + &dev_attr_check_auto_cal, + &dev_attr_backup_restore_auto_cal, +#if ENABLE_LF_STREAM + &dev_attr_lf_stream_auto_cal, + &dev_attr_lf_stream_reset_auto_cal, +#endif + &dev_attr_rf_test, + &dev_attr_flexcover_thd, +#ifdef CONFIG_SEC_FACTORY + &dev_attr_block_autocal, +#endif + &dev_attr_dhr_sensor_info, + NULL, +}; + +int __init ak09970_factory_init(void) +{ + adsp_factory_register(MSG_DIGITAL_HALL, digital_hall_attrs); +#if ENABLE_LF_STREAM + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); +#endif + auto_cal_data = kzalloc(sizeof(*auto_cal_data), GFP_KERNEL); + + hrtimer_init(&auto_cal_data->rftest_timer, + CLOCK_MONOTONIC, HRTIMER_MODE_REL); + auto_cal_data->rftest_timer.function = rftest_timer_func; + auto_cal_data->rftest_wq = + create_singlethread_workqueue("hall_angle_rftest_wq"); + INIT_WORK(&auto_cal_data->work_rftest, rftest_work_func); + + auto_cal_data->init_angle = 0; + auto_cal_data->min_angle = 180; + auto_cal_data->max_angle = 0; + auto_cal_data->rftest_timer_enabled = 0; + auto_cal_data->flex_low = FLEX_LO; + auto_cal_data->flex_cover_hi = FLEX_COVER_HI; + +#ifdef CONFIG_SEC_FACTORY + auto_cal_data->block_autocal = 0; + auto_cal_data->autocal_debug_wq = + create_singlethread_workqueue("autocal_dbg_wq"); + if (auto_cal_data->autocal_debug_wq == NULL) { + pr_err("[FACTORY]: %s - could not create autocal_dbg_wq", + __func__); + } + INIT_WORK(&auto_cal_data->work_autocal_debug, autocal_debug_work_func); +#endif + pr_info("[FACTORY] %s\n", __func__); + + return 0; +} + +void __exit ak09970_factory_exit(void) +{ + if (auto_cal_data->rftest_timer_enabled == 1) { + hrtimer_cancel(&auto_cal_data->rftest_timer); + cancel_work_sync(&auto_cal_data->work_rftest); + } + destroy_workqueue(auto_cal_data->rftest_wq); + +#ifdef CONFIG_SEC_FACTORY + if (auto_cal_data->autocal_debug_wq) + destroy_workqueue(auto_cal_data->autocal_debug_wq); +#endif + adsp_factory_unregister(MSG_DIGITAL_HALL); +#if ENABLE_LF_STREAM + if (pdata) + kfree(pdata); +#endif + if (auto_cal_data) + kfree(auto_cal_data); + + pr_info("[FACTORY] %s\n", __func__); +} diff --git a/drivers/adsp_factory/back_tap.c b/drivers/adsp_factory/back_tap.c new file mode 100644 index 000000000000..c803c33c2c0d --- /dev/null +++ b/drivers/adsp_factory/back_tap.c @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2024, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include "adsp.h" +#define NAME "Back Tap" +#define VENDOR "Samsung" + +/* 1: Double Tap + * 2: Triple Tap + * 3: Double and Triple Tap + */ +static int backtap_type = 3; +static int backtap_thd; + +static ssize_t backtap_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", NAME); +} + +static ssize_t backtap_vendor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR); +} + +static ssize_t backtap_peak_thd_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("[FACTORY] %s: Curr THD %d\n", __func__, backtap_thd); + + return snprintf(buf, PAGE_SIZE, "%d\n", backtap_thd); +} + +static ssize_t backtap_peak_thd_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int cnt = 0; + int msg_buf; + int ret = 0; + + ret = kstrtoint(buf, 10, &msg_buf); + if (ret < 0) { + pr_err("[FACTORY] %s: kstrtoint fail\n", __func__); + return -EINVAL; + } + if (msg_buf < 0 || msg_buf > 2) { + pr_err("[FACTORY] %s: Invalid value\n", __func__); + return -EINVAL; + } + + pr_info("[FACTORY] %s: msg_buf = %d\n", __func__, msg_buf); + + mutex_lock(&data->backtap_factory_mutex); + adsp_unicast(&msg_buf, sizeof(int), + MSG_BACKTAP, 0, MSG_TYPE_SET_THRESHOLD); + + while (!(data->ready_flag[MSG_TYPE_SET_THRESHOLD] & 1 << MSG_BACKTAP) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_SET_THRESHOLD] &= ~(1 << MSG_BACKTAP); + + if (cnt >= TIMEOUT_CNT) + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + else + backtap_thd = data->msg_buf[MSG_BACKTAP][0]; + + pr_info("[FACTORY] %s: New THD %d\n", __func__, backtap_thd); + mutex_unlock(&data->backtap_factory_mutex); + + return size; +} + +static ssize_t backtap_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("[FACTORY] %s: Curr Type %d\n", __func__, backtap_type); + + return snprintf(buf, PAGE_SIZE, "%d\n", backtap_type); +} + +static ssize_t backtap_type_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int cnt = 0; + int msg_buf; + int ret = 0; + + ret = kstrtoint(buf, 10, &msg_buf); + if (ret < 0) { + pr_err("[FACTORY] %s: kstrtoint fail\n", __func__); + return -EINVAL; + } + if (msg_buf <= 0 || msg_buf >= 4) { + pr_err("[FACTORY] %s: Invalid value\n", __func__); + return -EINVAL; + } + + pr_info("[FACTORY] %s: msg_buf = %d\n", __func__, msg_buf); + + mutex_lock(&data->backtap_factory_mutex); + adsp_unicast(&msg_buf, sizeof(int), + MSG_BACKTAP, 0, MSG_TYPE_OPTION_DEFINE); + + while (!(data->ready_flag[MSG_TYPE_OPTION_DEFINE] & 1 << MSG_BACKTAP) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_OPTION_DEFINE] &= ~(1 << MSG_BACKTAP); + + if (cnt >= TIMEOUT_CNT) + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + else + backtap_type = data->msg_buf[MSG_BACKTAP][0]; + + pr_info("[FACTORY] %s: New Type %d\n", __func__, backtap_type); + mutex_unlock(&data->backtap_factory_mutex); + + return size; +} + +static DEVICE_ATTR(name, 0444, backtap_name_show, NULL); +static DEVICE_ATTR(vendor, 0444, backtap_vendor_show, NULL); +static DEVICE_ATTR(backtap_peak_thd, 0660, backtap_peak_thd_show, backtap_peak_thd_store); +static DEVICE_ATTR(backtap_type, 0660, backtap_type_show, backtap_type_store); + +static struct device_attribute *backtap_attrs[] = { + &dev_attr_name, + &dev_attr_vendor, + &dev_attr_backtap_peak_thd, + &dev_attr_backtap_type, + NULL, +}; + +int __init backtap_factory_init(void) +{ + adsp_factory_register(MSG_BACKTAP, backtap_attrs); + pr_info("[FACTORY] %s\n", __func__); + return 0; +} + +void __exit backtap_factory_exit(void) +{ + adsp_factory_unregister(MSG_BACKTAP); + pr_info("[FACTORY] %s\n", __func__); +} diff --git a/drivers/adsp_factory/flip_cover_detector.c b/drivers/adsp_factory/flip_cover_detector.c new file mode 100755 index 000000000000..931ffe0492b9 --- /dev/null +++ b/drivers/adsp_factory/flip_cover_detector.c @@ -0,0 +1,695 @@ +/* + * Copyright (C) 2020, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include "adsp.h" + +enum { + OFF = 0, + ON = 1 +}; + +enum { + X = 0, + Y = 1, + Z = 2, + AXIS_MAX +}; + +#define ATTACH "ATTACH" +#define DETACH "DETACH" +#define PASS "PASS" +#define FAIL "FAIL" + +#define AXIS_SELECT X +#define THRESHOLD -500 +#define DETACH_MARGIN 100 +#define SATURATION_VALUE 4900 +#define MAG_DELAY_MS 10 + +#define COVER_DETACH 0 // OPEN +#define COVER_ATTACH 1 // CLOSE +#define COVER_ATTACH_NFC_ACTIVE 2 // CLOSE +#define COVER_ATTACH_NFC_TAG_PRESENT 7 // CLOSE + +static bool fcd_available; +static int fcd_dual_cal_matrix; + +struct factory_cover_status_data { + char cover_status[10]; + uint32_t axis_select; + int32_t threshold; + int32_t init[AXIS_MAX]; + int32_t attach[AXIS_MAX]; + int32_t attach_extremum[AXIS_MAX]; + int32_t detach[AXIS_MAX]; + char attach_result[10]; + char detach_result[10]; + char final_result[10]; + + uint8_t factory_test_status; + int32_t attach_diff; + int32_t detach_diff; + int32_t failed_attach_max; + int32_t failed_attach_min; + uint8_t saturation; +}; + +struct flip_cover_detector_data { + struct hrtimer fcd_timer; + struct workqueue_struct *fcd_wq; + struct work_struct work_fcd; + ktime_t poll_delay; + struct delayed_work notifier_work; + struct adsp_data *data; +}; + +struct factory_cover_status_data *factory_data; +struct flip_cover_detector_data *fcd_data; + +static int nfc_cover_status = -1; +static uint32_t axis_update = AXIS_SELECT; +static int32_t threshold_update = THRESHOLD; + +char sysfs_cover_status[10]; + +#if IS_ENABLED(CONFIG_FLIP_COVER_DETECTOR_NOTIFIER) +static BLOCKING_NOTIFIER_HEAD(fcd_nb_head); + +int fcd_notifier_register(struct notifier_block *nb) +{ + if (!fcd_available) { + pr_info("%s: fcd not available\n", __func__); + return -1; + } else { + pr_info("%s\n", __func__); + return blocking_notifier_chain_register(&fcd_nb_head, nb); + } +} +EXPORT_SYMBOL_GPL(fcd_notifier_register); + +int fcd_notifier_unregister(struct notifier_block *nb) +{ + if (!fcd_available) { + pr_info("%s: fcd not available\n", __func__); + return -1; + } else { + pr_info("%s\n", __func__); + return blocking_notifier_chain_unregister(&fcd_nb_head, nb); + } +} +EXPORT_SYMBOL(fcd_notifier_unregister); + +int fcd_notifier_call_chain(unsigned long val, void *v) +{ + if (!fcd_available) { + pr_info("%s: fcd not available\n", __func__); + return -1; + } else { + pr_info("%s - %d\n", __func__, val); + return blocking_notifier_call_chain(&fcd_nb_head, val, v); + } +} +#endif + +static void send_axis_threshold_settings(struct adsp_data *data, int axis, int threshold) +{ + uint8_t cnt = 0; + uint16_t flip_cover_detector_idx = MSG_FLIP_COVER_DETECTOR; + int32_t msg_buf[2]; + + msg_buf[0] = axis; + msg_buf[1] = threshold; + + mutex_lock(&data->flip_cover_factory_mutex); + + adsp_unicast(msg_buf, sizeof(msg_buf), + flip_cover_detector_idx, 0, MSG_TYPE_SET_THRESHOLD); + + while (!(data->ready_flag[MSG_TYPE_SET_THRESHOLD] & 1 << flip_cover_detector_idx) && + cnt++ < TIMEOUT_CNT) + usleep_range(20000, 20000); + + data->ready_flag[MSG_TYPE_SET_THRESHOLD] &= ~(1 << flip_cover_detector_idx); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + } else { + axis_update = axis; + threshold_update = threshold; + } + + pr_info("[FACTORY] %s: axis=%d, threshold=%d\n", __func__, axis_update, threshold_update); + + mutex_unlock(&data->flip_cover_factory_mutex); +} + +static void send_flip_cover_status_to_mag_sensor(struct adsp_data *data, int val) +{ + uint8_t cnt = 0; + int32_t msg_buf[1]; + + msg_buf[0] = val; + + mutex_lock(&data->flip_cover_factory_mutex); + + adsp_unicast(msg_buf, sizeof(msg_buf), + MSG_MAG, 0, MSG_TYPE_SET_TEMPORARY_MSG); + + while (!(data->ready_flag[MSG_TYPE_SET_TEMPORARY_MSG] & 1 << MSG_MAG) && + cnt++ < TIMEOUT_CNT) + usleep_range(20000, 20000); + + data->ready_flag[MSG_TYPE_SET_TEMPORARY_MSG] &= ~(1 << MSG_MAG); + + if (cnt >= TIMEOUT_CNT) + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + + pr_info("[FACTORY] %s: nfc_cover_status=%d set in mag sensor\n", __func__, nfc_cover_status); + + mutex_unlock(&data->flip_cover_factory_mutex); +} + +static void notify_fcd_status(struct work_struct *work) +{ +#if IS_ENABLED(CONFIG_FLIP_COVER_DETECTOR_NOTIFIER) + fcd_notifier_call_chain(nfc_cover_status, NULL); +#endif +} + +static void send_nfc_cover_status(struct adsp_data *data, int val) +{ + uint8_t cnt = 0; + uint16_t flip_cover_detector_idx = MSG_FLIP_COVER_DETECTOR; + int32_t msg_buf[1]; + + msg_buf[0] = val; + + mutex_lock(&data->flip_cover_factory_mutex); + + adsp_unicast(msg_buf, sizeof(msg_buf), + flip_cover_detector_idx, 0, MSG_TYPE_OPTION_DEFINE); + + while (!(data->ready_flag[MSG_TYPE_OPTION_DEFINE] & 1 << flip_cover_detector_idx) && + cnt++ < TIMEOUT_CNT) + usleep_range(20000, 20000); + + data->ready_flag[MSG_TYPE_OPTION_DEFINE] &= ~(1 << flip_cover_detector_idx); + + if (cnt >= TIMEOUT_CNT) + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + else + nfc_cover_status = data->msg_buf[flip_cover_detector_idx][0]; + + pr_info("[FACTORY] %s: nfc_cover_status=%d\n", __func__, nfc_cover_status); + + mutex_unlock(&data->flip_cover_factory_mutex); + + if (fcd_dual_cal_matrix) + send_flip_cover_status_to_mag_sensor(data, val); + + schedule_delayed_work(&fcd_data->notifier_work, msecs_to_jiffies(0)); +} + +static void get_mag_cal_data_with_saturation(struct adsp_data *data, int *mag_data) +{ + uint8_t cnt = 0; + + adsp_unicast(NULL, 0, MSG_MAG, 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] & 1 << MSG_MAG) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 500); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << MSG_MAG); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + } else { + mag_data[X] = data->msg_buf[MSG_MAG][0]; + mag_data[Y] = data->msg_buf[MSG_MAG][1]; + mag_data[Z] = data->msg_buf[MSG_MAG][2]; + factory_data->saturation = data->msg_buf[MSG_MAG][3]; + } + + pr_info("[FACTORY] %s: %d, %d, %d, %d\n", __func__, + mag_data[0], mag_data[1], mag_data[2], factory_data->saturation); +} + +void check_cover_detection_factory(int *mag_data, int axis_select) +{ + int axis = 0; + + if (!strcmp(factory_data->cover_status, DETACH)) { + if (mag_data[axis_select] > factory_data->failed_attach_max) { + factory_data->failed_attach_max = mag_data[axis_select]; + + if (abs(factory_data->failed_attach_max - factory_data->init[axis_select]) + > abs(factory_data->failed_attach_min - factory_data->init[axis_select])) { + for (axis = X; axis < AXIS_MAX; axis++) { + factory_data->attach[axis] = mag_data[axis]; + } + } + } else if (mag_data[axis_select] < factory_data->failed_attach_min) { + factory_data->failed_attach_min = mag_data[axis_select]; + + if (abs(factory_data->failed_attach_max - factory_data->init[axis_select]) + < abs(factory_data->failed_attach_min - factory_data->init[axis_select])) { + for (axis = X; axis < AXIS_MAX; axis++) { + factory_data->attach[axis] = mag_data[axis]; + } + } + } + + pr_info("[FACTORY] %s: failed_attach_max=%d, failed_attach_min=%d\n", __func__, + factory_data->failed_attach_max, factory_data->failed_attach_min); + + factory_data->attach_diff = mag_data[axis_select] - factory_data->init[axis_select]; + + if (abs(factory_data->attach_diff) > factory_data->threshold) { + snprintf(factory_data->cover_status, 10, ATTACH); + snprintf(factory_data->attach_result, 10, PASS); + for (axis = X; axis < AXIS_MAX; axis++) { + factory_data->attach[axis] = mag_data[axis]; + factory_data->attach_extremum[axis] = mag_data[axis]; + } + pr_info("[FACTORY] %s: COVER ATTACHED\n", __func__); + } + } else { + if (factory_data->attach_diff > 0) { + if (factory_data->saturation) { + for (axis = X; axis < AXIS_MAX; axis++) { + mag_data[axis] = SATURATION_VALUE; + } + } + + if (mag_data[axis_select] > factory_data->attach_extremum[axis_select]) { + for (axis = X; axis < AXIS_MAX; axis++) { + factory_data->attach_extremum[axis] = mag_data[axis]; + factory_data->detach[axis] = 0; + } + } + } else { + if (factory_data->saturation) { + for (axis = X; axis < AXIS_MAX; axis++) { + mag_data[axis] = -SATURATION_VALUE; + } + } + + if (mag_data[axis_select] < factory_data->attach_extremum[axis_select]) { + for (axis = X; axis < AXIS_MAX; axis++) { + factory_data->attach_extremum[axis] = mag_data[axis]; + factory_data->detach[axis] = 0; + } + } + } + + factory_data->detach_diff = mag_data[axis_select] - factory_data->attach_extremum[axis_select]; + + if (factory_data->attach_diff > 0) { + if (mag_data[axis_select] < (factory_data->attach_extremum[axis_select] - DETACH_MARGIN)) { + for (axis = X; axis < AXIS_MAX; axis++) { + factory_data->detach[axis] = mag_data[axis]; + } + } + + if (factory_data->detach_diff < -factory_data->threshold) { + snprintf(factory_data->cover_status, 10, DETACH); + snprintf(factory_data->detach_result, 10, PASS); + snprintf(factory_data->final_result, 10, PASS); + factory_data->factory_test_status = OFF; + pr_info("[FACTORY] %s: COVER_DETACHED\n", __func__); + } + } else { + if (mag_data[axis_select] > (factory_data->attach_extremum[axis_select] + DETACH_MARGIN)) { + for (axis = X; axis < AXIS_MAX; axis++) { + factory_data->detach[axis] = mag_data[axis]; + } + } + + if (factory_data->detach_diff > factory_data->threshold) { + snprintf(factory_data->cover_status, 10, DETACH); + snprintf(factory_data->detach_result, 10, PASS); + snprintf(factory_data->final_result, 10, PASS); + factory_data->factory_test_status = OFF; + pr_info("[FACTORY] %s: COVER_DETACHED\n", __func__); + } + } + } + + pr_info("[FACTORY1] %s: cover_status=%s, axis_select=%d, thd=%d, \ + x_init=%d, x_attach=%d, x_min_max=%d, x_detach=%d, \ + y_init=%d, y_attach=%d, y_min_max=%d, y_detach=%d, \ + z_init=%d, z_attach=%d, z_min_max=%d, z_detach=%d, \ + attach_result=%s, detach_result=%s, final_result=%s\n", + __func__, factory_data->cover_status, factory_data->axis_select, factory_data->threshold, + factory_data->init[X], factory_data->attach[X], factory_data->attach_extremum[X], factory_data->detach[X], + factory_data->init[Y], factory_data->attach[Y], factory_data->attach_extremum[Y], factory_data->detach[Y], + factory_data->init[Z], factory_data->attach[Z], factory_data->attach_extremum[Z], factory_data->detach[Z], + factory_data->attach_result, factory_data->detach_result, factory_data->final_result); +} + +static void fcd_work_func(struct work_struct *work) +{ + int mag_data[AXIS_MAX]; + + if (factory_data->factory_test_status == ON) { + get_mag_cal_data_with_saturation(fcd_data->data, mag_data); + check_cover_detection_factory(mag_data, factory_data->axis_select); + } +} + +static enum hrtimer_restart fcd_timer_func(struct hrtimer *timer) +{ + queue_work(fcd_data->fcd_wq, &fcd_data->work_fcd); + hrtimer_forward_now(&fcd_data->fcd_timer, fcd_data->poll_delay); + + if (factory_data->factory_test_status == ON) + return HRTIMER_RESTART; + else + return HRTIMER_NORESTART; +} + +static void factory_data_init(void) +{ + int mag_data[AXIS_MAX]; + int axis = 0; + + memset(factory_data, 0, sizeof(struct factory_cover_status_data)); + + get_mag_cal_data_with_saturation(fcd_data->data, mag_data); + + factory_data->axis_select = axis_update; + factory_data->threshold = (threshold_update > 0) ? threshold_update : threshold_update * (-1); + + for (axis = X; axis < AXIS_MAX; axis++) { + factory_data->init[axis] = mag_data[axis]; + factory_data->attach[axis] = mag_data[axis]; + } + + factory_data->failed_attach_max = mag_data[factory_data->axis_select]; + factory_data->failed_attach_min = mag_data[factory_data->axis_select]; + + snprintf(factory_data->cover_status, 10, DETACH); + snprintf(factory_data->attach_result, 10, FAIL); + snprintf(factory_data->detach_result, 10, FAIL); + snprintf(factory_data->final_result, 10, FAIL); +} + +static void enable_factory_test(int request) +{ + if (request == ON) { + factory_data_init(); + factory_data->factory_test_status = ON; + hrtimer_start(&fcd_data->fcd_timer, fcd_data->poll_delay, HRTIMER_MODE_REL); + } else { + hrtimer_cancel(&fcd_data->fcd_timer); + cancel_work_sync(&fcd_data->work_fcd); + factory_data->factory_test_status = OFF; + } +} + +static ssize_t nfc_cover_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (nfc_cover_status == COVER_ATTACH || nfc_cover_status == COVER_ATTACH_NFC_ACTIVE || nfc_cover_status == COVER_ATTACH_NFC_TAG_PRESENT) { + snprintf(sysfs_cover_status, 10, "CLOSE"); + } else if (nfc_cover_status == COVER_DETACH) { + snprintf(sysfs_cover_status, 10, "OPEN"); + } + + pr_info("[FACTORY] %s: sysfs_cover_status=%s\n", __func__, sysfs_cover_status); + + return snprintf(buf, PAGE_SIZE, "%s\n", sysfs_cover_status); +} + +static ssize_t nfc_cover_status_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int status = 0; + + if (kstrtoint(buf, 10, &status)) { + pr_err("[FACTORY] %s: kstrtoint fail\n", __func__); + return size; + } + + send_nfc_cover_status(data, status); + + pr_info("[FACTORY] %s: status=%d\n", __func__, status); + + return size; +} + +static ssize_t factory_cover_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("[FACTORY] %s: status=%s, axis=%d, thd=%d, \ + x_init=%d, x_attach=%d, x_min_max=%d, x_detach=%d, \ + y_init=%d, y_attach=%d, y_min_max=%d, y_detach=%d, \ + z_init=%d, z_attach=%d, z_min_max=%d, z_detach=%d, \ + attach_result=%s, detach_result=%s, final_result=%s\n", + __func__, factory_data->cover_status, factory_data->axis_select, factory_data->threshold, + factory_data->init[X], factory_data->attach[X], factory_data->attach_extremum[X], factory_data->detach[X], + factory_data->init[Y], factory_data->attach[Y], factory_data->attach_extremum[Y], factory_data->detach[Y], + factory_data->init[Z], factory_data->attach[Z], factory_data->attach_extremum[Z], factory_data->detach[Z], + factory_data->attach_result, factory_data->detach_result, factory_data->final_result); + + return snprintf(buf, PAGE_SIZE, "%s,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s\n", + factory_data->cover_status, factory_data->axis_select, factory_data->threshold, + factory_data->init[X], factory_data->attach[X], factory_data->attach_extremum[X], factory_data->detach[X], + factory_data->init[Y], factory_data->attach[Y], factory_data->attach_extremum[Y], factory_data->detach[Y], + factory_data->init[Z], factory_data->attach[Z], factory_data->attach_extremum[Z], factory_data->detach[Z], + factory_data->attach_result, factory_data->detach_result, factory_data->final_result); +} + +static ssize_t factory_cover_status_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int factory_test_request = -1; + + fcd_data->data = data; + + if (kstrtoint(buf, 10, &factory_test_request)) { + pr_err("[FACTORY] %s: kstrtoint fail\n", __func__); + return size; + } + + pr_info("[FACTORY] %s: factory_test_request=%d\n", __func__, factory_test_request); + + if (factory_test_request == ON && factory_data->factory_test_status == OFF) + enable_factory_test(ON); + else if (factory_test_request == OFF && factory_data->factory_test_status == ON) + enable_factory_test(OFF); + + return size; +} + +static ssize_t axis_threshold_setting_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("[FACTORY] %s: axis=%d, threshold=%d\n", __func__, axis_update, threshold_update); + + return snprintf(buf, PAGE_SIZE, "%d,%d\n", axis_update, threshold_update); +} + +static ssize_t axis_threshold_setting_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int ret; + int axis, threshold; + + ret = sscanf(buf, "%d,%d", &axis, &threshold); + + if (ret != 2) { + pr_err("[FACTORY] %s: Invalid values received, ret=%d\n", __func__, ret); + return -EINVAL; + } + + if (axis < 0 || axis >= AXIS_MAX) { + pr_err("[FACTORY] %s: Invalid axis value received\n", __func__); + return -EINVAL; + } + + pr_info("[FACTORY] %s: axis=%d, threshold=%d\n", __func__, axis, threshold); + + send_axis_threshold_settings(data, axis, threshold); + + return size; +} + +static ssize_t mag_cal_matrix_num_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + int cal_matrix_num; + + adsp_unicast(NULL, 0, MSG_MAG, 0, MSG_TYPE_GET_THRESHOLD); + while (!(data->ready_flag[MSG_TYPE_GET_THRESHOLD] & 1 << MSG_MAG) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_THRESHOLD] &= ~(1 << MSG_MAG); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + cal_matrix_num = -1; + } else { + pr_info("[FACTORY] %s cal_matix_num = %d\n", __func__, data->msg_buf[MSG_MAG][0]); + cal_matrix_num = data->msg_buf[MSG_MAG][0]; + } + + return snprintf(buf, PAGE_SIZE, "%d\n", cal_matrix_num); +} + +static DEVICE_ATTR(nfc_cover_status, 0664, nfc_cover_status_show, nfc_cover_status_store); +static DEVICE_ATTR(factory_cover_status, 0664, factory_cover_status_show, factory_cover_status_store); +static DEVICE_ATTR(axis_threshold_setting, 0664, axis_threshold_setting_show, axis_threshold_setting_store); +static DEVICE_ATTR(cal_matrix_num, 0444, mag_cal_matrix_num_show, NULL); + +static struct device_attribute *flip_cover_detector_attrs[] = { + &dev_attr_nfc_cover_status, + &dev_attr_factory_cover_status, + &dev_attr_axis_threshold_setting, + &dev_attr_cal_matrix_num, + NULL, +}; + +void check_device_default_thd(struct device_node *np) +{ + int ret = of_property_read_u32(np, "fcd,attach_thd", &threshold_update); + if (ret < 0) { + pr_info("[FACTORY] %s: attach_thd not found, ret=%d\n", __func__, ret); + threshold_update = THRESHOLD; + } + pr_info("[FACTORY] %s: %d\n", __func__, threshold_update); +} + +void check_device_axis_update(struct device_node *np) +{ + int ret = of_property_read_u32(np, "fcd,axis", &axis_update); + if (ret < 0) { + pr_info("[FACTORY] %s: fcd,axis not found, ret=%d\n", __func__, ret); + axis_update = AXIS_SELECT; + } + + pr_info("[FACTORY] %s: %d\n", __func__, axis_update); +} + +void check_fcd_dual_cal_matrix_support(struct device_node *np) +{ + int ret = of_property_read_u32(np, "fcd,dual_cal_matrix", &fcd_dual_cal_matrix); + if (ret < 0) { + pr_info("[FACTORY] %s: fcd,dual_cal_matrix not found, ret=%d\n", __func__, ret); + fcd_dual_cal_matrix = 0; + } + + pr_info("[FACTORY] %s: %d\n", __func__, fcd_dual_cal_matrix); +} + +static bool check_flip_cover_detector_availability(void) +{ + struct device_node *np = of_find_node_by_name(NULL, "flip_cover_detector_sensor"); + + if (np == NULL) { + pr_info("[FACTORY] %s: false\n", __func__); + fcd_available = false; + } else { + pr_info("[FACTORY] %s: true\n", __func__); + fcd_available = true; + + check_fcd_dual_cal_matrix_support(np); + check_device_default_thd(np); + check_device_axis_update(np); + } + + return fcd_available; +} + +int flip_cover_detector_factory_init(void) +{ + fcd_available = check_flip_cover_detector_availability(); + + if (!fcd_available) + return 0; + + adsp_factory_register(MSG_FLIP_COVER_DETECTOR, flip_cover_detector_attrs); + + fcd_data = kzalloc(sizeof(*fcd_data), GFP_KERNEL); + if (fcd_data == NULL) { + pr_err("[FACTORY] %s: Memory allocation failed for fcd_data\n", __func__); + return -ENOMEM; + } + + factory_data = kzalloc(sizeof(*factory_data), GFP_KERNEL); + if (factory_data == NULL) { + pr_err("[FACTORY] %s: Memory allocation failed for factory_data\n", __func__); + kfree(fcd_data); + return -ENOMEM; + } + + hrtimer_init(&fcd_data->fcd_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + fcd_data->fcd_timer.function = fcd_timer_func; + + fcd_data->fcd_wq = create_singlethread_workqueue("flip_cover_detector_wq"); + if (fcd_data->fcd_wq == NULL) { + pr_err("[FACTORY] %s: could not create flip cover detector wq\n", __func__); + kfree(fcd_data); + kfree(factory_data); + return -ENOMEM; + } + + INIT_WORK(&fcd_data->work_fcd, fcd_work_func); + fcd_data->poll_delay = ns_to_ktime(MAG_DELAY_MS * NSEC_PER_MSEC); + + INIT_DELAYED_WORK(&fcd_data->notifier_work, notify_fcd_status); + + snprintf(sysfs_cover_status, 10, "OPEN"); + + pr_info("[FACTORY] %s\n", __func__); + + return 0; +} + +void flip_cover_detector_factory_exit(void) +{ + if (!fcd_available) + return; + + cancel_delayed_work(&fcd_data->notifier_work); + + adsp_factory_unregister(MSG_FLIP_COVER_DETECTOR); + + if (fcd_data != NULL) { + if (factory_data->factory_test_status == ON) + enable_factory_test(OFF); + + if (fcd_data->fcd_wq != NULL) { + destroy_workqueue(fcd_data->fcd_wq); + fcd_data->fcd_wq = NULL; + } + + kfree(fcd_data); + kfree(factory_data); + } + + pr_info("[FACTORY] %s\n", __func__); +} diff --git a/drivers/adsp_factory/light_factory.c b/drivers/adsp_factory/light_factory.c new file mode 100755 index 000000000000..95062f512b81 --- /dev/null +++ b/drivers/adsp_factory/light_factory.c @@ -0,0 +1,1714 @@ +/* + * Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include "adsp.h" + +#define DEVICE_LIST_NUM 13 + +static const struct device_id_t device_list[DEVICE_LIST_NUM] = { + /* ID, Vendor, Name */ + {0x00, "Unknown", "Unknown"}, + {0x18, "AMS", "TCS3701"}, + {0x88, "AMS", "TMD4913"}, + {0x95, "AMS", "TMD4914"}, + {0x61, "SensorTek", "STK33911"}, + {0x62, "SensorTek", "STK33917"}, + {0x63, "SensorTek", "STK33910"}, + {0x65, "SensorTek", "STK33915"}, + {0x66, "SensorTek", "STK31610"}, + {0x70, "Capella", "VEML3235"}, + {0x71, "Capella", "VEML3328"}, + {0xF0, "SensorTek", "STK33F00"}, + {0xF1, "SensorTek", "STK33F11"}, +}; + +#define ASCII_TO_DEC(x) (x - 48) +int brightness; + +enum { + OPTION_TYPE_COPR_ENABLE, + OPTION_TYPE_BOLED_ENABLE, + OPTION_TYPE_LCD_ONOFF, + OPTION_TYPE_GET_COPR, + OPTION_TYPE_GET_DDI_DEVICE_ID, + OPTION_TYPE_SET_HALLIC_INFO, + OPTION_TYPE_GET_LIGHT_CAL, + OPTION_TYPE_SET_LIGHT_CAL, + OPTION_TYPE_SET_LCD_VERSION, + OPTION_TYPE_SET_UB_DISCONNECT, + OPTION_TYPE_GET_LIGHT_DEBUG_INFO, + OPTION_TYPE_SET_DEVICE_MODE, + OPTION_TYPE_SET_PANEL_STATE, + OPTION_TYPE_SET_PANEL_TEST_STATE, + OPTION_TYPE_SET_AUTO_BRIGHTNESS_HYST, + OPTION_TYPE_SET_PANEL_SCREEN_MODE, + OPTION_TYPE_GET_LIGHT_CIRCLE_COORDINATES, + OPTION_TYPE_SAVE_LIGHT_CAL, + OPTION_TYPE_LOAD_LIGHT_CAL, + OPTION_TYPE_GET_LIGHT_DEVICE_ID, + OPTION_TYPE_GET_TRIM_CHECK, + OPTION_TYPE_GET_SUB_ALS_LUX, + OPTION_TYPE_GET_MAX_BRIGHTNESS, + OPTION_TYPE_MAX +}; + +#if IS_ENABLED(CONFIG_SUPPORT_BRIGHTNESS_NOTIFY_FOR_LIGHT_SENSOR) && \ + IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) +#include +#endif + +#define LIGHT_CAL_PASS 1 +#define LIGHT_CAL_FAIL 0 + +/*************************************************************************/ +/* factory Sysfs */ +/*************************************************************************/ + +int get_light_sidx(struct adsp_data *data) +{ + int ret = MSG_LIGHT; +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) + switch (data->fac_fstate) { + case FSTATE_INACTIVE: + case FSTATE_FAC_INACTIVE: + ret = MSG_LIGHT; + break; + case FSTATE_ACTIVE: + case FSTATE_FAC_ACTIVE: + case FSTATE_FAC_INACTIVE_2: + ret = MSG_LIGHT_SUB; + break; + default: + break; + } +#endif + return ret; +} + +int get_light_display_sidx(int32_t idx) +{ + int ret = MSG_LIGHT; +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) + ret = idx == 0 ? MSG_LIGHT : MSG_LIGHT_SUB; +#endif + return ret; +} + +static void light_get_device_id(struct adsp_data *data, uint16_t light_idx) +{ + int32_t cmd = OPTION_TYPE_GET_LIGHT_DEVICE_ID, i; + int32_t device_index = UNKNOWN_INDEX; + int32_t display_idx = (light_idx == MSG_LIGHT) ? 0 : 1; + uint8_t cnt = 0, device_id = 0; + + mutex_lock(&data->light_factory_mutex); + + adsp_unicast(&cmd, sizeof(cmd), light_idx, 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] + & 1 << light_idx) && cnt++ < TIMEOUT_CNT) + usleep_range(1000, 1100); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << light_idx); + mutex_unlock(&data->light_factory_mutex); + + if (cnt >= TIMEOUT_CNT) + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + else + device_id = (uint8_t)data->msg_buf[light_idx][0]; + pr_err("[SSC_FAC] %s: device_id : %d\n", __func__, device_id); + + if (device_id == 0) { + pr_err("[SSC_FAC] %s: No information\n", __func__); + } else { + for (i = 0; i < DEVICE_LIST_NUM; i++) + if (device_id == device_list[i].device_id) + break; + if (i >= DEVICE_LIST_NUM) + pr_err("[SSC_FAC] %s: Unknown ID - (0x%x)\n", + __func__, device_id); + else + device_index = i; + } + + memcpy(data->light_device_vendor[display_idx], + device_list[device_index].device_vendor, + sizeof(char) * DEVICE_INFO_LENGTH); + memcpy(data->light_device_name[display_idx], + device_list[device_index].device_name, + sizeof(char) * DEVICE_INFO_LENGTH); + + pr_info("[SSC_FAC] %s: Device ID - %s(%s)\n", __func__, + data->light_device_name[display_idx], + data->light_device_vendor[display_idx]); +} + +static ssize_t light_vendor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int32_t display_idx = (get_light_sidx(data) == MSG_LIGHT) ? 0 : 1; + + if (data->light_device_vendor[display_idx][0] == 0) + light_get_device_id(data, get_light_sidx(data)); + + return snprintf(buf, PAGE_SIZE, "%s\n", + data->light_device_vendor[display_idx]); +} + +static ssize_t light_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int32_t display_idx = (get_light_sidx(data) == MSG_LIGHT) ? 0 : 1; + + if (data->light_device_name[display_idx][0] == 0) + light_get_device_id(data, get_light_sidx(data)); + + return snprintf(buf, PAGE_SIZE, "%s\n", + data->light_device_name[display_idx]); +} + +static ssize_t light_raw_data_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint16_t light_idx = get_light_sidx(data); + uint8_t cnt = 0; + + mutex_lock(&data->light_factory_mutex); + adsp_unicast(NULL, 0, light_idx, 0, MSG_TYPE_GET_RAW_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & 1 << light_idx) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << light_idx); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + mutex_unlock(&data->light_factory_mutex); + return snprintf(buf, PAGE_SIZE, "0,0,0,0,0,0\n"); + } + + mutex_unlock(&data->light_factory_mutex); + return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d,%d,%d\n", + data->msg_buf[light_idx][0], data->msg_buf[light_idx][1], + data->msg_buf[light_idx][2], data->msg_buf[light_idx][3], + data->msg_buf[light_idx][4], data->msg_buf[light_idx][5]); +} + +static ssize_t light_get_dhr_sensor_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint16_t light_idx = get_light_sidx(data); + uint8_t cnt = 0; + + pr_info("[SSC_FAC] %s: start\n", __func__); + mutex_lock(&data->light_factory_mutex); + adsp_unicast(NULL, 0, light_idx, 0, MSG_TYPE_GET_DHR_INFO); + while (!(data->ready_flag[MSG_TYPE_GET_DHR_INFO] & 1 << light_idx) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_DHR_INFO] &= ~(1 << light_idx); + + if (cnt >= TIMEOUT_CNT) + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + + mutex_unlock(&data->light_factory_mutex); + return data->msg_buf[light_idx][0]; +} + +#if !IS_ENABLED(CONFIG_SUPPORT_BRIGHTNESS_NOTIFY_FOR_LIGHT_SENSOR) +static ssize_t light_brightness_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("[SSC_FAC] %s: %d\n", __func__, brightness); + return snprintf(buf, PAGE_SIZE, "%d\n", brightness); +} + +static ssize_t light_brightness_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint16_t light_idx = get_light_sidx(data); + + brightness = ASCII_TO_DEC(buf[0]) * 100 + ASCII_TO_DEC(buf[1]) * 10 + ASCII_TO_DEC(buf[2]); + pr_info("[SSC_FAC]%s: %d\n", __func__, brightness); + + adsp_unicast(&brightness, sizeof(brightness), light_idx, 0, MSG_TYPE_SET_CAL_DATA); + + pr_info("[SSC_FAC]%s: done\n", __func__); + + return size; +} +#endif + +static ssize_t light_register_read_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint16_t light_idx = get_light_sidx(data); + int cnt = 0; + int32_t msg_buf[1]; + + msg_buf[0] = data->light_temp_reg; + + mutex_lock(&data->light_factory_mutex); + adsp_unicast(msg_buf, sizeof(msg_buf), + light_idx, 0, MSG_TYPE_GET_REGISTER); + + while (!(data->ready_flag[MSG_TYPE_GET_REGISTER] & 1 << light_idx) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_REGISTER] &= ~(1 << light_idx); + + if (cnt >= TIMEOUT_CNT) + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + + pr_info("[SSC_FAC] %s: [0x%x]: 0x%x\n", + __func__, msg_buf[0], data->msg_buf[light_idx][0]); + + mutex_unlock(&data->light_factory_mutex); + + return snprintf(buf, PAGE_SIZE, "[0x%x]: 0x%x\n", + msg_buf[0], data->msg_buf[light_idx][0]); +} + +static ssize_t light_register_read_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int reg = 0; + struct adsp_data *data = dev_get_drvdata(dev); + + if (sscanf(buf, "%3x", ®) != 1) { + pr_err("[SSC_FAC]: %s - The number of data are wrong\n", + __func__); + return -EINVAL; + } + + data->light_temp_reg = reg; + pr_info("[SSC_FAC] %s: [0x%x]\n", __func__, data->light_temp_reg); + + return size; +} + +static ssize_t light_register_write_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint16_t light_idx = get_light_sidx(data); + int cnt = 0; + int32_t msg_buf[2]; + + if (sscanf(buf, "%3x,%3x", &msg_buf[0], &msg_buf[1]) != 2) { + pr_err("[SSC_FAC]: %s - The number of data are wrong\n", + __func__); + return -EINVAL; + } + + mutex_lock(&data->light_factory_mutex); + adsp_unicast(msg_buf, sizeof(msg_buf), + light_idx, 0, MSG_TYPE_SET_REGISTER); + + while (!(data->ready_flag[MSG_TYPE_SET_REGISTER] & 1 << light_idx) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_SET_REGISTER] &= ~(1 << light_idx); + + if (cnt >= TIMEOUT_CNT) + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + + data->msg_buf[light_idx][MSG_LIGHT_MAX - 1] = msg_buf[0]; + pr_info("[SSC_FAC] %s: 0x%x - 0x%x\n", + __func__, msg_buf[0], data->msg_buf[light_idx][0]); + mutex_unlock(&data->light_factory_mutex); + + return size; +} + +static ssize_t light_hyst_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + + pr_info("[SSC_FAC] %s: %d,%d,%d,%d\n", __func__, + data->hyst[0], data->hyst[1], data->hyst[2], data->hyst[3]); + + return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d\n", + data->hyst[0], data->hyst[1], data->hyst[2], data->hyst[3]); +} + +static ssize_t light_hyst_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint16_t light_idx = get_light_sidx(data); + int32_t msg_buf[5]; + + if (sscanf(buf, "%11d,%11d,%11d,%11d", &data->hyst[0], &data->hyst[1], + &data->hyst[2], &data->hyst[3]) != 4) { + pr_err("[SSC_FAC]: %s - The number of data are wrong\n", + __func__); + return -EINVAL; + } + + pr_info("[SSC_FAC] %s: (%d) %d < %d < %d\n", __func__, + data->hyst[0], data->hyst[1], data->hyst[2], data->hyst[3]); + + msg_buf[0] = OPTION_TYPE_SET_AUTO_BRIGHTNESS_HYST; + msg_buf[1] = data->hyst[0]; + msg_buf[2] = data->hyst[1]; + msg_buf[3] = data->hyst[2]; + msg_buf[4] = data->hyst[3]; + + mutex_lock(&data->light_factory_mutex); + adsp_unicast(msg_buf, sizeof(msg_buf), + light_idx, 0, MSG_TYPE_OPTION_DEFINE); + mutex_unlock(&data->light_factory_mutex); + + return size; +} + +#if IS_ENABLED(CONFIG_SUPPORT_BRIGHTNESS_NOTIFY_FOR_LIGHT_SENSOR) && \ + IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) +void light_brightness_work_func(struct work_struct *work) +{ + struct adsp_data *data = container_of((struct work_struct *)work, + struct adsp_data, light_br_work); + uint16_t light_idx = get_light_display_sidx(data->brightness_info[2]); + int32_t display_idx = data->brightness_info[2]; + + int cnt = 0; + + if (display_idx >= 0 && display_idx < 2) + data->brightness_info[5] = data->pre_panel_state[display_idx]; + + if (!data->light_factory_is_ready) { + pr_info("[SSC_FAC] %s: Factory daemon is not ready.\n", + __func__); + return; + } + + mutex_lock(&data->light_factory_mutex); + adsp_unicast(data->brightness_info, sizeof(data->brightness_info), + light_idx, 0, MSG_TYPE_SET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_SET_CAL_DATA] & 1 << light_idx) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_SET_CAL_DATA] &= ~(1 << light_idx); + + if (cnt >= TIMEOUT_CNT) + pr_err("[SSC_FAC] %s: Timeout!!! br: %d\n", __func__, + data->brightness_info[0]); + + mutex_unlock(&data->light_factory_mutex); +} + +int light_panel_data_notify(struct notifier_block *nb, + unsigned long val, void *v) +{ + struct adsp_data *data = adsp_get_struct_data(); + static int32_t pre_ub_con_state[2] = {-1, -1}; +#if IS_ENABLED(CONFIG_SUPPORT_PANEL_STATE_NOTIFY_FOR_LIGHT_SENSOR) + static int32_t pre_finger_mask_hbm_on[2] = {-1, -1}; + static int32_t pre_acl_status[2] = {-1, -1}; +#endif + uint16_t light_idx = get_light_sidx(data); + struct panel_notifier_event_data *panel_event = v; + uint8_t display_idx = panel_event->display_index; + enum panel_notifier_event_state_t state = panel_event->state; + + if (val == PANEL_EVENT_BL_STATE_CHANGED) { + struct panel_event_bl_data bl = panel_event->d.bl; + + if (display_idx > 1) + return 0; + + if (bl.level) + bl.level /= data->brightness_resolution[display_idx]; + + data->brightness_info[0] = bl.level; + data->brightness_info[1] = bl.aor; +#if IS_ENABLED(CONFIG_SUPPORT_PANEL_STATE_NOTIFY_FOR_LIGHT_SENSOR) + data->brightness_info[2] = (int32_t)display_idx; + data->brightness_info[3] = bl.finger_mask_hbm_on; + data->brightness_info[4] = bl.gradual_acl_val; + + if ((data->brightness_info[0] == data->pre_bl_level[display_idx]) && + (data->brightness_info[2] == data->pre_display_idx) && + (data->brightness_info[3] == pre_finger_mask_hbm_on[display_idx]) && + (data->brightness_info[4] == pre_acl_status[display_idx])) + return 0; + + pre_finger_mask_hbm_on[display_idx] = data->brightness_info[3]; + if (pre_acl_status[display_idx] != data->brightness_info[4]) { + pr_info("[SSC_FAC] %s: change acl status : %d -> %d\n", + __func__, pre_acl_status[display_idx], + data->brightness_info[4]); + pre_acl_status[display_idx] = data->brightness_info[4]; + } +#else + if (data->brightness_info[0] == data->pre_bl_level[display_idx]) + return 0; +#endif + if (data->brightness_info[0] <= 1 || data->pre_bl_level[display_idx] <= 1) + pr_info("[SSC_FAC] %s: br: %d(inx: %d)\n", __func__, + data->brightness_info[0], + data->brightness_info[2]); + + data->pre_bl_level[display_idx] = data->brightness_info[0]; + data->pre_display_idx = data->brightness_info[2]; + + schedule_work(&data->light_br_work); + + } else if (val == PANEL_EVENT_UB_CON_STATE_CHANGED) { + int32_t msg_buf[2]; + + if ((display_idx > 1) || + ((int32_t)state == pre_ub_con_state[display_idx])) + return 0; + + pre_ub_con_state[display_idx] = (int32_t)state; + msg_buf[0] = OPTION_TYPE_SET_UB_DISCONNECT; + msg_buf[1] = (int32_t)state; + + pr_info("[SSC_FAC] %s: ub disconnected %d\n", + __func__, msg_buf[1]); + + if (!data->light_factory_is_ready) { + pr_info("[SSC_FAC] %s: Factory daemon is not ready.\n", + __func__); + return 0; + } + + mutex_lock(&data->light_factory_mutex); + adsp_unicast(msg_buf, sizeof(msg_buf), + light_idx, 0, MSG_TYPE_OPTION_DEFINE); + mutex_unlock(&data->light_factory_mutex); +#if IS_ENABLED(CONFIG_SUPPORT_PANEL_STATE_NOTIFY_FOR_LIGHT_SENSOR) + } else if (val == PANEL_EVENT_PANEL_STATE_CHANGED) { + int32_t panel_state = (int32_t)state - PANEL_EVENT_PANEL_STATE_OFF; + int32_t msg_buf[4]; + + if ((display_idx > 1) || + (panel_state >= PANEL_EVENT_PANEL_STATE_LPM) || + ((data->pre_panel_state[display_idx] == panel_state) && + (data->pre_panel_idx == (int32_t)display_idx))) + return 0; + + data->pre_panel_state[display_idx] = panel_state; + data->pre_panel_idx = (int32_t)display_idx; + + msg_buf[0] = OPTION_TYPE_SET_PANEL_STATE; + msg_buf[1] = panel_state; + msg_buf[2] = (int32_t)display_idx; + msg_buf[3] = data->pre_screen_mode[display_idx]; + + light_idx = get_light_display_sidx(display_idx); + + pr_info("[SSC_FAC] %s: panel_state %d(inx: %d, mode: %d)\n", + __func__, panel_state, display_idx, + data->pre_screen_mode[display_idx]); + + if (!data->light_factory_is_ready) { + pr_info("[SSC_FAC] %s: Factory daemon is not ready.\n", + __func__); + return 0; + } + + mutex_lock(&data->light_factory_mutex); + adsp_unicast(msg_buf, sizeof(msg_buf), + light_idx, 0, MSG_TYPE_OPTION_DEFINE); + mutex_unlock(&data->light_factory_mutex); + } else if (val == PANEL_EVENT_TEST_MODE_STATE_CHANGED) { + int32_t test_state = (int32_t)state - PANEL_EVENT_TEST_MODE_STATE_NONE; + int32_t msg_buf[3]; + + if (display_idx > 1) + return 0; + + msg_buf[0] = OPTION_TYPE_SET_PANEL_TEST_STATE; + msg_buf[1] = test_state; + msg_buf[2] = (int32_t)display_idx; + + light_idx = get_light_display_sidx(display_idx); + + mutex_lock(&data->light_factory_mutex); + pr_info("[SSC_FAC] %s: panel test state %d (%d)\n", + __func__, test_state, display_idx); + + adsp_unicast(msg_buf, sizeof(msg_buf), + light_idx, 0, MSG_TYPE_OPTION_DEFINE); + mutex_unlock(&data->light_factory_mutex); + } else if (val == PANEL_EVENT_SCREEN_MODE_STATE_CHANGED) { + uint8_t screen_mode = panel_event->d.screen_mode; + int32_t msg_buf[3]; + + if ((display_idx > 1) || + (data->pre_screen_mode[display_idx] == (int32_t)screen_mode)) + return 0; + + data->pre_screen_mode[display_idx] = (int32_t)screen_mode; + msg_buf[0] = OPTION_TYPE_SET_PANEL_SCREEN_MODE; + msg_buf[1] = (int32_t)screen_mode; + msg_buf[2] = (int32_t)display_idx; + + light_idx = get_light_display_sidx(display_idx); + pr_info("[SSC_FAC] %s: panel screen mode %d (%d)\n", + __func__, screen_mode, display_idx); + + if (!data->light_factory_is_ready) { + pr_info("[SSC_FAC] %s: Factory daemon is not ready.\n", + __func__); + return 0; + } + + mutex_lock(&data->light_factory_mutex); + adsp_unicast(msg_buf, sizeof(msg_buf), + light_idx, 0, MSG_TYPE_OPTION_DEFINE); + mutex_unlock(&data->light_factory_mutex); +#endif /* CONFIG_SUPPORT_PANEL_STATE_NOTIFY_FOR_LIGHT_SENSOR */ + } + + return 0; +} + +static struct notifier_block light_panel_data_notifier = { + .notifier_call = light_panel_data_notify, + .priority = 1, +}; +#endif /* CONFIG_SUPPORT_BRIGHTNESS_NOTIFY_FOR_LIGHT_SENSOR && CONFIG_SEC_PANEL_NOTIFIER_V2 */ + +static ssize_t light_hallic_info_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ +#ifndef CONFIG_SEC_FACTORY + struct adsp_data *data = dev_get_drvdata(dev); + uint16_t light_idx = get_light_sidx(data); + int32_t msg_buf[2]; +#endif + int new_value; + + if (sysfs_streq(buf, "0")) + new_value = 0; + else if (sysfs_streq(buf, "1")) + new_value = 1; + else + return size; + + pr_info("[SSC_FAC] %s: new_value %d\n", __func__, new_value); + +#ifndef CONFIG_SEC_FACTORY + msg_buf[0] = OPTION_TYPE_SET_HALLIC_INFO; + msg_buf[1] = new_value; + + mutex_lock(&data->light_factory_mutex); + adsp_unicast(msg_buf, sizeof(msg_buf), + light_idx, 0, MSG_TYPE_OPTION_DEFINE); + mutex_unlock(&data->light_factory_mutex); +#endif + return size; +} + +static ssize_t light_lcd_onoff_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint16_t light_idx = get_light_sidx(data); + uint16_t display_idx = (light_idx == MSG_LIGHT) ? 0 : 1; + + int32_t msg_buf[3]; + int new_value, cnt = 0; + + if (sysfs_streq(buf, "0")) + new_value = 0; + else if (sysfs_streq(buf, "1")) + new_value = 1; + else + return size; + +#if IS_ENABLED(CONFIG_SUPPORT_PANEL_STATE_NOTIFY_FOR_LIGHT_SENSOR) && IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) + if (data->pre_panel_idx >= 0) + light_idx = get_light_display_sidx(data->pre_panel_idx); +#endif + pr_info("[SSC_FAC] %s: new_value %d(idx: %d)\n", + __func__, new_value, data->pre_panel_idx); + + data->pre_bl_level[0] = data->pre_bl_level[1] = -1; + msg_buf[0] = OPTION_TYPE_LCD_ONOFF; + msg_buf[1] = new_value; + msg_buf[2] = data->pre_panel_state[display_idx]; + + if (!data->light_factory_is_ready) { + pr_info("[SSC_FAC] %s: Factory daemon is not ready.\n", + __func__); + return size; + } + + if (new_value == 1) { +#if IS_ENABLED(CONFIG_SUPPORT_DDI_COPR_FOR_LIGHT_SENSOR) + schedule_delayed_work(&data->light_copr_debug_work, + msecs_to_jiffies(1000)); + data->light_copr_debug_count = 0; +#endif +#if IS_ENABLED(CONFIG_SUPPORT_FIFO_DEBUG_FOR_LIGHT_SENSOR) + schedule_delayed_work(&data->light_fifo_debug_work, + msecs_to_jiffies(10 * 1000)); +#endif + } else { +#if IS_ENABLED(CONFIG_SUPPORT_DDI_COPR_FOR_LIGHT_SENSOR) + cancel_delayed_work_sync(&data->light_copr_debug_work); + data->light_copr_debug_count = 5; +#endif +#if IS_ENABLED(CONFIG_SUPPORT_FIFO_DEBUG_FOR_LIGHT_SENSOR) + cancel_delayed_work_sync(&data->light_fifo_debug_work); +#endif + } + mutex_lock(&data->light_factory_mutex); + adsp_unicast(msg_buf, sizeof(msg_buf), + light_idx, 0, MSG_TYPE_OPTION_DEFINE); + while (!(data->ready_flag[MSG_TYPE_OPTION_DEFINE] & 1 << light_idx) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + data->ready_flag[MSG_TYPE_OPTION_DEFINE] &= ~(1 << light_idx); + if (cnt >= TIMEOUT_CNT) + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + adsp_unicast(msg_buf, sizeof(msg_buf), + MSG_SSC_CORE, 0, MSG_TYPE_OPTION_DEFINE); + mutex_unlock(&data->light_factory_mutex); + + return size; +} + +static ssize_t light_circle_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int32_t cmd = OPTION_TYPE_GET_LIGHT_CIRCLE_COORDINATES; + uint16_t light_idx = get_light_sidx(data); + uint8_t cnt = 0; + + mutex_lock(&data->light_factory_mutex); + adsp_unicast(&cmd, sizeof(cmd), light_idx, 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] + & 1 << light_idx) && cnt++ < TIMEOUT_CNT) + usleep_range(1000, 1100); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << light_idx); + mutex_unlock(&data->light_factory_mutex); + +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) + if (cnt >= TIMEOUT_CNT) + return snprintf(buf, PAGE_SIZE, "0 0 0 0 0 0\n"); + + return snprintf(buf, PAGE_SIZE, "%d.%d %d.%d %d.%d %d.%d %d.%d %d.%d\n", + data->msg_buf[light_idx][0] / 10, + abs(data->msg_buf[light_idx][0]) % 10, + data->msg_buf[light_idx][1] / 10, + abs(data->msg_buf[light_idx][1]) % 10, + data->msg_buf[light_idx][4] / 10, + abs(data->msg_buf[light_idx][4]) % 10, + data->msg_buf[light_idx][2] / 10, + abs(data->msg_buf[light_idx][2]) % 10, + data->msg_buf[light_idx][3] / 10, + abs(data->msg_buf[light_idx][3]) % 10, + data->msg_buf[light_idx][4] / 10, + abs(data->msg_buf[light_idx][4]) % 10); +#else + if (cnt >= TIMEOUT_CNT) + return snprintf(buf, PAGE_SIZE, "0 0 0\n"); + + return snprintf(buf, PAGE_SIZE, "%d.%d %d.%d %d.%d\n", + data->msg_buf[light_idx][0] / 10, + data->msg_buf[light_idx][0] % 10, + data->msg_buf[light_idx][1] / 10, + data->msg_buf[light_idx][1] % 10, + data->msg_buf[light_idx][2] / 10, + data->msg_buf[light_idx][2] % 10); +#endif +} + +#if IS_ENABLED(CONFIG_SUPPORT_DDI_COPR_FOR_LIGHT_SENSOR) +static ssize_t light_read_copr_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint16_t light_idx = get_light_sidx(data); + int32_t msg_buf[2]; + int new_value; + + if (sysfs_streq(buf, "0")) + new_value = 0; + else if (sysfs_streq(buf, "1")) + new_value = 1; + else + return size; + + pr_info("[SSC_FAC] %s: new_value %d\n", __func__, new_value); + msg_buf[0] = OPTION_TYPE_COPR_ENABLE; + msg_buf[1] = new_value; + + mutex_lock(&data->light_factory_mutex); + adsp_unicast(msg_buf, sizeof(msg_buf), + light_idx, 0, MSG_TYPE_OPTION_DEFINE); + mutex_unlock(&data->light_factory_mutex); + + return size; +} + +static ssize_t light_read_copr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int32_t cmd = OPTION_TYPE_GET_COPR; + uint16_t light_idx = get_light_sidx(data); + uint8_t cnt = 0; + + mutex_lock(&data->light_factory_mutex); + adsp_unicast(&cmd, sizeof(cmd), light_idx, 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] & 1 << light_idx) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << light_idx); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + mutex_unlock(&data->light_factory_mutex); + return snprintf(buf, PAGE_SIZE, "-1\n"); + } + + pr_info("[SSC_FAC] %s: %d\n", __func__, data->msg_buf[light_idx][4]); + mutex_unlock(&data->light_factory_mutex); + + return snprintf(buf, PAGE_SIZE, "%d\n", data->msg_buf[light_idx][4]); +} + +static ssize_t light_copr_roix_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint16_t light_idx = get_light_sidx(data); + uint8_t cnt = 0; + + mutex_lock(&data->light_factory_mutex); + adsp_unicast(NULL, 0, light_idx, 0, MSG_TYPE_GET_DUMP_REGISTER); + + while (!(data->ready_flag[MSG_TYPE_GET_DUMP_REGISTER] & 1 << light_idx) + && cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_DUMP_REGISTER] &= ~(1 << light_idx); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + mutex_unlock(&data->light_factory_mutex); + return snprintf(buf, PAGE_SIZE, "-1,-1,-1,-1\n"); + } + + pr_info("[SSC_FAC] %s: %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", __func__, + data->msg_buf[light_idx][0], data->msg_buf[light_idx][1], + data->msg_buf[light_idx][2], data->msg_buf[light_idx][3], + data->msg_buf[light_idx][4], data->msg_buf[light_idx][5], + data->msg_buf[light_idx][6], data->msg_buf[light_idx][7], + data->msg_buf[light_idx][8], data->msg_buf[light_idx][9], + data->msg_buf[light_idx][10], data->msg_buf[light_idx][11]); + + mutex_unlock(&data->light_factory_mutex); + + return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", + data->msg_buf[light_idx][0], data->msg_buf[light_idx][1], + data->msg_buf[light_idx][2], data->msg_buf[light_idx][3], + data->msg_buf[light_idx][4], data->msg_buf[light_idx][5], + data->msg_buf[light_idx][6], data->msg_buf[light_idx][7], + data->msg_buf[light_idx][8], data->msg_buf[light_idx][9], + data->msg_buf[light_idx][10], data->msg_buf[light_idx][11]); +} + +static ssize_t light_test_copr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int32_t cmd = OPTION_TYPE_GET_COPR; + uint16_t light_idx = get_light_sidx(data); + uint8_t cnt = 0; + + mutex_lock(&data->light_factory_mutex); + adsp_unicast(&cmd, sizeof(cmd), light_idx, 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] & 1 << light_idx) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << light_idx); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + mutex_unlock(&data->light_factory_mutex); + return snprintf(buf, PAGE_SIZE, "-1,-1,-1,-1\n"); + } + + pr_info("[SSC_FAC] %s: %d,%d,%d,%d\n", __func__, + data->msg_buf[light_idx][0], data->msg_buf[light_idx][1], + data->msg_buf[light_idx][2], data->msg_buf[light_idx][3]); + + mutex_unlock(&data->light_factory_mutex); + + return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d\n", + data->msg_buf[light_idx][0], data->msg_buf[light_idx][1], + data->msg_buf[light_idx][2], data->msg_buf[light_idx][3]); +} + +static ssize_t light_ddi_spi_check_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int32_t cmd = OPTION_TYPE_GET_DDI_DEVICE_ID; + uint16_t light_idx = get_light_sidx(data); + uint8_t cnt = 0; + + mutex_lock(&data->light_factory_mutex); + adsp_unicast(&cmd, sizeof(cmd), light_idx, 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] & 1 << light_idx) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << light_idx); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + mutex_unlock(&data->light_factory_mutex); + return snprintf(buf, PAGE_SIZE, "-1\n"); + } + + pr_info("[SSC_FAC] %s: %d\n", __func__, data->msg_buf[light_idx][0]); + + mutex_unlock(&data->light_factory_mutex); + + return snprintf(buf, PAGE_SIZE, "%d\n", data->msg_buf[light_idx][0]); +} + +static ssize_t light_boled_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint16_t light_idx = get_light_sidx(data); + int32_t msg_buf[2]; + int new_value; + + if (sysfs_streq(buf, "0")) + new_value = 0; + else if (sysfs_streq(buf, "1")) + new_value = 1; + else + return size; + + pr_info("[SSC_FAC] %s: new_value %d\n", __func__, new_value); + msg_buf[0] = OPTION_TYPE_BOLED_ENABLE; + msg_buf[1] = new_value; + + mutex_lock(&data->light_factory_mutex); + adsp_unicast(msg_buf, sizeof(msg_buf), + light_idx, 0, MSG_TYPE_OPTION_DEFINE); + mutex_unlock(&data->light_factory_mutex); + + return size; +} + +void light_copr_debug_work_func(struct work_struct *work) +{ + struct adsp_data *data = container_of((struct delayed_work *)work, + struct adsp_data, light_copr_debug_work); + uint16_t light_idx = get_light_sidx(data); + uint8_t cnt = 0; +#if IS_ENABLED(CONFIG_SUPPORT_BRIGHTNESS_NOTIFY_FOR_LIGHT_SENSOR) && \ + IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) + uint16_t display_idx = (light_idx == MSG_LIGHT) ? 0 : 1; + + if (data->pre_panel_state[display_idx] == 0) + return; +#endif + mutex_lock(&data->light_factory_mutex); + adsp_unicast(NULL, 0, light_idx, 0, MSG_TYPE_GET_DUMP_REGISTER); + + while (!(data->ready_flag[MSG_TYPE_GET_DUMP_REGISTER] & 1 << light_idx) + && cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_DUMP_REGISTER] &= ~(1 << light_idx); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + mutex_unlock(&data->light_factory_mutex); + return; + } + + pr_info("[SSC_FAC] %s: %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", __func__, + data->msg_buf[light_idx][0], data->msg_buf[light_idx][1], + data->msg_buf[light_idx][2], data->msg_buf[light_idx][3], + data->msg_buf[light_idx][4], data->msg_buf[light_idx][5], + data->msg_buf[light_idx][6], data->msg_buf[light_idx][7], + data->msg_buf[light_idx][8], data->msg_buf[light_idx][9], + data->msg_buf[light_idx][10], data->msg_buf[light_idx][11]); + + mutex_unlock(&data->light_factory_mutex); + + if (data->light_copr_debug_count++ < 5) + schedule_delayed_work(&data->light_copr_debug_work, + msecs_to_jiffies(1000)); +} +#endif /* CONFIG_SUPPORT_DDI_COPR_FOR_LIGHT_SENSOR */ +#if IS_ENABLED(CONFIG_SUPPORT_FIFO_DEBUG_FOR_LIGHT_SENSOR) +void light_fifo_debug_work_func(struct work_struct *work) +{ + struct adsp_data *data = container_of((struct delayed_work *)work, + struct adsp_data, light_fifo_debug_work); + uint16_t light_idx = get_light_sidx(data); + uint8_t cnt = 0; + + mutex_lock(&data->light_factory_mutex); + adsp_unicast(NULL, 0, light_idx, 0, MSG_TYPE_SET_THRESHOLD); + + while (!(data->ready_flag[MSG_TYPE_SET_THRESHOLD] & 1 << light_idx) + && cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_SET_THRESHOLD] &= ~(1 << light_idx); + mutex_unlock(&data->light_factory_mutex); + + schedule_delayed_work(&data->light_fifo_debug_work, + msecs_to_jiffies(2 * 60 * 1000)); +} +#endif + +static ssize_t light_debug_info_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int reg = 0; + + if (sscanf(buf, "%3d", ®) != 1) { + pr_err("[SSC_FAC]: %s - The number of data are wrong\n", + __func__); + return -EINVAL; + } + + data->light_debug_info_cmd = reg; + + return size; +} + +static ssize_t light_debug_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int32_t cmd[2]; + uint16_t light_idx = get_light_sidx(data); + uint8_t cnt = 0; + + mutex_lock(&data->light_factory_mutex); + cmd[0] = OPTION_TYPE_GET_LIGHT_DEBUG_INFO; + cmd[1] = data->light_debug_info_cmd; + adsp_unicast(&cmd, sizeof(cmd), light_idx, 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] & 1 << light_idx) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << light_idx); + mutex_unlock(&data->light_factory_mutex); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "0,0,0,0,0,0\n"); + } + + return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d,%d,%d\n", + data->msg_buf[light_idx][0], data->msg_buf[light_idx][1], + data->msg_buf[light_idx][2], data->msg_buf[light_idx][3], + data->msg_buf[light_idx][4] >> 16, + data->msg_buf[light_idx][4] & 0xffff); +} + +#if IS_ENABLED(CONFIG_SUPPORT_SSC_AOD_RECT) +static ssize_t light_set_aod_rect_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int32_t msg_buf[5] = {OPTION_TYPE_SSC_AOD_RECT, 0, 0, 0, 0}; + + if (sscanf(buf, "%3d,%3d,%3d,%3d", + &msg_buf[1], &msg_buf[2], &msg_buf[3], &msg_buf[4]) != 4) { + pr_err("[SSC_FAC]: %s - The number of data are wrong\n", + __func__); + return -EINVAL; + } + + pr_info("[SSC_FAC] %s: rect:%d,%d,%d,%d\n", __func__, + msg_buf[1], msg_buf[2], msg_buf[3], msg_buf[4]); + adsp_unicast(msg_buf, sizeof(msg_buf), + MSG_SSC_CORE, 0, MSG_TYPE_OPTION_DEFINE); + return size; +} + +void light_rect_init_work(void) +{ + int32_t rect_msg[5] = {OPTION_TYPE_SSC_AOD_LIGHT_CIRCLE, 546, 170, 576, 200}; + + adsp_unicast(rect_msg, sizeof(rect_msg), + MSG_SSC_CORE, 0, MSG_TYPE_OPTION_DEFINE); +} +#endif + +void light_get_brightness_resolution(struct adsp_data *data, int max_br, + uint8_t display_idx) +{ + if (max_br >= 25500) + data->brightness_resolution[display_idx] = 100; + else if (max_br >= 2550) + data->brightness_resolution[display_idx] = 10; + else + data->brightness_resolution[display_idx] = 1; + + pr_info("[SSC_FAC] %s: brightness resolution %d(%d)", __func__, + data->brightness_resolution[display_idx], display_idx); +} + +void light_init_work_func(struct work_struct *work) +{ + struct adsp_data *data = container_of((struct delayed_work *)work, + struct adsp_data, light_init_work); +#if IS_ENABLED(CONFIG_SUPPORT_BRIGHTNESS_NOTIFY_FOR_LIGHT_SENSOR) && \ + IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) + int32_t msg_buf[4], i, light_idx, max_idx = 1; + int32_t cmd, cnt = 0; + +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) + max_idx = 2; +#endif + + for (i = 0; i < max_idx; i++) { + msg_buf[0] = OPTION_TYPE_SET_PANEL_STATE; + msg_buf[1] = data->pre_panel_state[i]; + msg_buf[2] = i; + msg_buf[3] = data->pre_screen_mode[i]; + + mutex_lock(&data->light_factory_mutex); + + light_idx = get_light_display_sidx(i); + pr_info("[SSC_FAC] %s: panel_state %d(inx: %d, mode: %d)\n", + __func__, data->pre_panel_state[i], i, + data->pre_screen_mode[i]); + adsp_unicast(msg_buf, sizeof(msg_buf), + light_idx, 0, MSG_TYPE_OPTION_DEFINE); + mutex_unlock(&data->light_factory_mutex); + + msleep(25); + + cmd = OPTION_TYPE_GET_MAX_BRIGHTNESS; + adsp_unicast(&cmd, sizeof(int32_t), + light_idx, 0, MSG_TYPE_SET_TEMPORARY_MSG); + + while (!(data->ready_flag[MSG_TYPE_SET_TEMPORARY_MSG] + & 1 << light_idx) && cnt++ < TIMEOUT_CNT) + usleep_range(1000, 1100); + + data->ready_flag[MSG_TYPE_SET_TEMPORARY_MSG] &= ~(1 << light_idx); + mutex_unlock(&data->light_factory_mutex); + if (cnt >= TIMEOUT_CNT) { + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + } + + light_get_brightness_resolution(data, data->msg_buf[light_idx][0], i); + + pr_info("[SSC_FAC] %s: max brightness %d(%d)\n", __func__, + data->msg_buf[light_idx][0], i); + + cnt = 0; + + msleep(25); + } +#endif + light_get_device_id(data, MSG_LIGHT); +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) + light_get_device_id(data, MSG_LIGHT_SUB); +#endif + +#if IS_ENABLED(CONFIG_SUPPORT_BRIGHTNESS_NOTIFY_FOR_LIGHT_SENSOR) && \ + IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) + schedule_work(&data->light_br_work); +#endif +} + +void light_init_work(struct adsp_data *data) +{ + data->pre_bl_level[0] = data->pre_bl_level[1] = -1; + data->pre_panel_idx = -1; + data->pre_display_idx = -1; + data->light_debug_info_cmd = 0; + data->light_factory_is_ready = true; + data->brightness_resolution[0] = 1; + data->brightness_resolution[1] = 1; + + schedule_delayed_work(&data->light_init_work, msecs_to_jiffies(1000)); +} + +#if IS_ENABLED(CONFIG_SUPPORT_LIGHT_CALIBRATION) +void light_cal_read_work_func(struct work_struct *work) +{ + struct adsp_data *data = container_of((struct delayed_work *)work, + struct adsp_data, light_cal_work); + uint16_t light_idx = get_light_sidx(data); + int32_t msg_buf[5] = {0, }, cmd, cnt = 0, i = 1; + +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) + light_idx = MSG_LIGHT; + i = 2; +#endif + + while (i--) { + mutex_lock(&data->light_factory_mutex); + cmd = OPTION_TYPE_LOAD_LIGHT_CAL; + adsp_unicast(&cmd, sizeof(int32_t), + light_idx, 0, MSG_TYPE_SET_TEMPORARY_MSG); + + while (!(data->ready_flag[MSG_TYPE_SET_TEMPORARY_MSG] + & 1 << light_idx) && cnt++ < TIMEOUT_CNT) + usleep_range(1000, 1100); + + data->ready_flag[MSG_TYPE_SET_TEMPORARY_MSG] &= ~(1 << light_idx); + mutex_unlock(&data->light_factory_mutex); + if (cnt >= TIMEOUT_CNT) { + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) + light_idx = MSG_LIGHT_SUB; + cnt = 0; +#endif + continue; + } else if (data->msg_buf[light_idx][0] < 0) { + pr_err("[SSC_FAC] %s: UB is not matched!!!(%d %d)\n", __func__, + light_idx, data->msg_buf[light_idx][0]); +#if IS_ENABLED(CONFIG_SUPPORT_PROX_CALIBRATION) +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC_BUT_SUPPORT_SINGLE_PROX) + if (light_idx == MSG_LIGHT_SUB) + prox_send_cal_data(data, MSG_PROX, false); +#else + if (light_idx == MSG_LIGHT) + prox_send_cal_data(data, MSG_PROX, false); +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) + else if (light_idx == MSG_LIGHT_SUB) + prox_send_cal_data(data, MSG_PROX_SUB, false); +#endif +#endif +#endif /* CONFIG_SUPPORT_PROX_CALIBRATION */ +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) + light_idx = MSG_LIGHT_SUB; + msg_buf[0] = msg_buf[1] = msg_buf[2] = msg_buf[3] = msg_buf[4] = 0; + cnt = 0; + continue; +#else + return; +#endif + } + + msg_buf[0] = OPTION_TYPE_SET_LIGHT_CAL; + if (light_idx == MSG_LIGHT) { + msg_buf[1] = data->light_cal_result = data->msg_buf[light_idx][0]; + msg_buf[2] = data->light_cal1 = data->msg_buf[light_idx][1]; + msg_buf[3] = data->light_cal2 = data->msg_buf[light_idx][2]; + msg_buf[4] = data->copr_w = data->msg_buf[light_idx][3]; + } else { + msg_buf[1] = data->sub_light_cal_result = data->msg_buf[light_idx][0]; + msg_buf[2] = data->sub_light_cal1 = data->msg_buf[light_idx][1]; + msg_buf[3] = data->sub_light_cal2 = data->msg_buf[light_idx][2]; + msg_buf[4] = data->sub_copr_w = data->msg_buf[light_idx][3]; + } + +#if IS_ENABLED(CONFIG_SUPPORT_PROX_CALIBRATION) +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC_BUT_SUPPORT_SINGLE_PROX) + if (light_idx == MSG_LIGHT_SUB) { + data->prox_cal = data->msg_buf[light_idx][4]; + prox_send_cal_data(data, MSG_PROX, true); + } +#else + if (light_idx == MSG_LIGHT) { + data->prox_cal = data->msg_buf[light_idx][4]; + prox_send_cal_data(data, MSG_PROX, true); + } +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) + else if (light_idx == MSG_LIGHT_SUB) { + data->prox_sub_cal = data->msg_buf[light_idx][4]; + prox_send_cal_data(data, MSG_PROX_SUB, true); + } +#endif +#endif +#endif /* CONFIG_SUPPORT_PROX_CALIBRATION */ + + if (msg_buf[1] == LIGHT_CAL_PASS) { + mutex_lock(&data->light_factory_mutex); + adsp_unicast(msg_buf, sizeof(msg_buf), + light_idx, 0, MSG_TYPE_OPTION_DEFINE); + mutex_unlock(&data->light_factory_mutex); + } + +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) + light_idx = MSG_LIGHT_SUB; + msg_buf[0] = msg_buf[1] = msg_buf[2] = msg_buf[3] = msg_buf[4] = 0; + cnt = 0; +#endif + } +} + +void light_cal_init_work(struct adsp_data *data) +{ + data->light_cal_result = LIGHT_CAL_FAIL; + data->light_cal1 = -1; + data->light_cal2 = -1; + data->copr_w = -1; + + data->sub_light_cal_result = LIGHT_CAL_FAIL; + data->sub_light_cal1 = -1; + data->sub_light_cal2 = -1; + data->sub_copr_w = -1; + + schedule_delayed_work(&data->light_cal_work, msecs_to_jiffies(8000)); +} + +static ssize_t light_cal_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int32_t cmd_buf[2] = {0, }, cur_lux, cnt = 0; + uint16_t light_idx = get_light_sidx(data); + uint16_t display_idx = (light_idx == MSG_LIGHT) ? 0 : 1; + + mutex_lock(&data->light_factory_mutex); + + pr_info("[SSC_FAC] %s: br: %d (idx: %u)\n", __func__, + data->pre_bl_level[display_idx], display_idx); + + cmd_buf[0] = OPTION_TYPE_GET_LIGHT_CAL; + cmd_buf[1] = data->pre_bl_level[display_idx]; + + adsp_unicast(cmd_buf, sizeof(cmd_buf), light_idx, + 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] + & 1 << light_idx) && cnt++ < TIMEOUT_CNT) + usleep_range(1000, 1100); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << light_idx); + + mutex_unlock(&data->light_factory_mutex); + if (cnt >= TIMEOUT_CNT) { + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + cur_lux = -1; + } else { + cur_lux = data->msg_buf[light_idx][4]; + } + + if (light_idx == MSG_LIGHT) { + pr_info("[SSC_FAC] %s: cal_data (P/F: %d, Cal1: %d, Cal2: %d, COPR_W: %d, cur lux: %d)\n", + __func__, data->light_cal_result, data->light_cal1, + data->light_cal2, data->copr_w, cur_lux); + + return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n", + data->light_cal_result, data->light_cal2, cur_lux); + } else { + pr_info("[SSC_FAC] %s: cal_data (P/F: %d, Cal1: %d, Cal2: %d, COPR_W: %d, cur lux: %d)\n", + __func__, data->sub_light_cal_result, data->sub_light_cal1, + data->sub_light_cal2, data->sub_copr_w, cur_lux); + + return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n", + data->sub_light_cal_result, data->sub_light_cal2, cur_lux); + } +} + +static ssize_t light_cal_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int32_t cmd_buf[2] = {0, }, new_value, cnt = 0; + int32_t msg_buf[5] = {0, }; + uint16_t light_idx = get_light_sidx(data); + uint16_t display_idx = (light_idx == MSG_LIGHT) ? 0 : 1; + + if (sysfs_streq(buf, "0")) + new_value = 0; + else if (sysfs_streq(buf, "1")) + new_value = 1; + else + return size; + + pr_info("[SSC_FAC] %s: cmd: %d, br: %d (idx: %u)\n", __func__, + new_value, data->pre_bl_level[display_idx], display_idx); + + mutex_lock(&data->light_factory_mutex); + + if (new_value == 1) { + cmd_buf[0] = OPTION_TYPE_GET_LIGHT_CAL; + cmd_buf[1] = data->pre_bl_level[display_idx]; + + adsp_unicast(cmd_buf, sizeof(cmd_buf), light_idx, 0, + MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] + & 1 << light_idx) && cnt++ < TIMEOUT_CNT) + usleep_range(1000, 1100); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << light_idx); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + mutex_unlock(&data->light_factory_mutex); + return size; + } + + pr_info("[SSC_FAC] %s: (P/F: %d, Cal1: %d, Cal2: %d, COPR_W: %d)\n", + __func__, data->msg_buf[light_idx][0], + data->msg_buf[light_idx][1], + data->msg_buf[light_idx][2], + data->msg_buf[light_idx][3]); + + if (data->msg_buf[light_idx][0] == LIGHT_CAL_PASS) { + if (light_idx == MSG_LIGHT) { + data->light_cal_result = data->msg_buf[light_idx][0]; + data->light_cal1 = data->msg_buf[light_idx][1]; + data->light_cal2 = data->msg_buf[light_idx][2]; + data->copr_w = data->msg_buf[light_idx][3]; + } else { + data->sub_light_cal_result = data->msg_buf[light_idx][0]; + data->sub_light_cal1 = data->msg_buf[light_idx][1]; + data->sub_light_cal2 = data->msg_buf[light_idx][2]; + data->sub_copr_w = data->msg_buf[light_idx][3]; + } + } else { + mutex_unlock(&data->light_factory_mutex); + return size; + } + } else { + if (light_idx == MSG_LIGHT) { + data->light_cal_result = LIGHT_CAL_FAIL; + data->light_cal1 = 0; + data->light_cal2 = 0; + data->copr_w = 0; + } else { + data->sub_light_cal_result = LIGHT_CAL_FAIL; + data->sub_light_cal1 = 0; + data->sub_light_cal2 = 0; + data->sub_copr_w = 0; + } + } + + cnt = 0; + msg_buf[0] = OPTION_TYPE_SAVE_LIGHT_CAL; + if (light_idx == MSG_LIGHT) { + msg_buf[1] = data->light_cal_result; + msg_buf[2] = data->light_cal1; + msg_buf[3] = data->light_cal2; + msg_buf[4] = data->copr_w; + } else { + msg_buf[1] = data->sub_light_cal_result; + msg_buf[2] = data->sub_light_cal1; + msg_buf[3] = data->sub_light_cal2; + msg_buf[4] = data->sub_copr_w; + } + adsp_unicast(msg_buf, sizeof(msg_buf), + light_idx, 0, MSG_TYPE_SET_TEMPORARY_MSG); + + while (!(data->ready_flag[MSG_TYPE_SET_TEMPORARY_MSG] + & 1 << light_idx) && cnt++ < TIMEOUT_CNT) + usleep_range(1000, 1100); + + data->ready_flag[MSG_TYPE_SET_TEMPORARY_MSG] &= ~(1 << light_idx); + if (cnt >= TIMEOUT_CNT) { + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + mutex_unlock(&data->light_factory_mutex); + return size; + } + + msg_buf[0] = OPTION_TYPE_SET_LIGHT_CAL; + adsp_unicast(msg_buf, sizeof(msg_buf), + light_idx, 0, MSG_TYPE_OPTION_DEFINE); + mutex_unlock(&data->light_factory_mutex); + + return size; +} + +static ssize_t light_test_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int32_t cnt = 0, cmd = OPTION_TYPE_GET_LIGHT_CAL; + int32_t test_value; + uint16_t light_idx = get_light_sidx(data); + + mutex_lock(&data->light_factory_mutex); + adsp_unicast(&cmd, sizeof(cmd), light_idx, 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] + & 1 << light_idx) && cnt++ < TIMEOUT_CNT) + usleep_range(1000, 1100); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << light_idx); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + test_value = -1; + } else { + test_value = data->msg_buf[light_idx][2]; + } + + mutex_unlock(&data->light_factory_mutex); + + pr_info("[SSC_FAC] %s: test_data (Cal1: %d, Cal2: %d, COPR_W: %d, 16ms lux: %d)\n", + __func__, data->light_cal1, data->light_cal2, + data->copr_w, test_value); + + return snprintf(buf, PAGE_SIZE, "%d, %d, %d, %d\n", + data->light_cal1, data->light_cal2, data->copr_w, test_value); +} + +static DEVICE_ATTR(light_test, 0444, light_test_show, NULL); +#elif defined (CONFIG_TABLET_MODEL_CONCEPT) +void light_cal_data(struct adsp_data *data, int light_idx, int *max_als, int *cur_lux) +{ + int32_t cmd = OPTION_TYPE_GET_LIGHT_CAL, cnt = 0; + adsp_unicast(&cmd, sizeof(int32_t), light_idx, + 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] + & 1 << light_idx) && cnt++ < TIMEOUT_CNT) + usleep_range(1000, 1100); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << light_idx); + if (cnt >= TIMEOUT_CNT) { + pr_err("[SSC_FAC] %s - %d : Timeout!!!\n", __func__, light_idx); + *cur_lux = -1; + } else { + *max_als = data->msg_buf[light_idx][1]; + *cur_lux = data->msg_buf[light_idx][2]; + } +} +static ssize_t light_cal_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int32_t cur_lux, max_als = 0; + int32_t cur_lux_sub = 0, max_als_sub = 0; + uint16_t light_idx = 0; + + if (data->fac_fstate == LIGHT_DUAL_CHECK_MODE) { + mutex_lock(&data->light_factory_mutex); + + light_cal_data(data, MSG_LIGHT, &max_als, &cur_lux); + light_cal_data(data, MSG_LIGHT_SUB, &max_als_sub, &cur_lux_sub); + + mutex_unlock(&data->light_factory_mutex); + return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d,%d,%d\n", + 1, max_als, cur_lux, 1, max_als_sub, cur_lux_sub); + + } else { + mutex_lock(&data->light_factory_mutex); + + light_idx = get_light_sidx(data); + light_cal_data(data, light_idx, &max_als, &cur_lux); + + mutex_unlock(&data->light_factory_mutex); + return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n", + 1, max_als, cur_lux); + } +} +static ssize_t light_cal_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + pr_info("[SSC_FAC]%s: done\n", __func__); + return size; +} +#endif + +static ssize_t light_trim_check_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint16_t light_idx = get_light_sidx(data); + int cnt = 0; + int32_t msg = OPTION_TYPE_GET_TRIM_CHECK; + + mutex_lock(&data->light_factory_mutex); + adsp_unicast(&msg, sizeof(int32_t), light_idx, 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] & 1 << light_idx) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << light_idx); + mutex_unlock(&data->light_factory_mutex); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "NG\n"); + } + + pr_info("[SSC_FAC] %s: [%s]: 0x%x, 0x%x\n", + __func__, (data->msg_buf[light_idx][0] > 0) ? "TRIM" : "UNTRIM", + (uint16_t)data->msg_buf[light_idx][1], + (uint16_t)data->msg_buf[light_idx][2]); + + return snprintf(buf, PAGE_SIZE, "%s\n", + (data->msg_buf[light_idx][0] > 0) ? "TRIM" : "UNTRIM"); +} + +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) && !IS_ENABLED(CONFIG_TABLET_MODEL_CONCEPT) +static ssize_t light_sub_als_lux_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint16_t light_idx = get_light_sidx(data); + int cnt = 0; + int32_t msg = OPTION_TYPE_GET_SUB_ALS_LUX; + + if (light_idx == MSG_LIGHT) { + mutex_lock(&data->light_factory_mutex); + adsp_unicast(&msg, sizeof(int32_t), light_idx, 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] & 1 << light_idx) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << light_idx); + mutex_unlock(&data->light_factory_mutex); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "NG\n"); + } + + return snprintf(buf, PAGE_SIZE, "%d\n", data->msg_buf[light_idx][0]); + } else { + return snprintf(buf, PAGE_SIZE, "-1\n"); + } +} +#endif + +#if IS_ENABLED(CONFIG_SUPPORT_LIGHT_CALIBRATION) || IS_ENABLED(CONFIG_TABLET_MODEL_CONCEPT) +static DEVICE_ATTR(light_cal, 0664, light_cal_show, light_cal_store); +#endif +static DEVICE_ATTR(lcd_onoff, 0220, NULL, light_lcd_onoff_store); +static DEVICE_ATTR(hallic_info, 0220, NULL, light_hallic_info_store); +static DEVICE_ATTR(light_circle, 0444, light_circle_show, NULL); + +#if IS_ENABLED(CONFIG_SUPPORT_DDI_COPR_FOR_LIGHT_SENSOR) +static DEVICE_ATTR(read_copr, 0664, + light_read_copr_show, light_read_copr_store); +static DEVICE_ATTR(test_copr, 0444, light_test_copr_show, NULL); +static DEVICE_ATTR(boled_enable, 0220, NULL, light_boled_enable_store); +static DEVICE_ATTR(copr_roix, 0444, light_copr_roix_show, NULL); +static DEVICE_ATTR(sensorhub_ddi_spi_check, 0444, + light_ddi_spi_check_show, NULL); +#endif +static DEVICE_ATTR(register_write, 0220, NULL, light_register_write_store); +static DEVICE_ATTR(register_read, 0664, + light_register_read_show, light_register_read_store); +static DEVICE_ATTR(vendor, 0444, light_vendor_show, NULL); +static DEVICE_ATTR(name, 0444, light_name_show, NULL); +static DEVICE_ATTR(lux, 0444, light_raw_data_show, NULL); +static DEVICE_ATTR(raw_data, 0444, light_raw_data_show, NULL); +static DEVICE_ATTR(dhr_sensor_info, 0444, light_get_dhr_sensor_info_show, NULL); +static DEVICE_ATTR(debug_info, 0664, + light_debug_info_show, light_debug_info_store); +static DEVICE_ATTR(hyst, 0664, light_hyst_show, light_hyst_store); +#if !IS_ENABLED(CONFIG_SUPPORT_BRIGHTNESS_NOTIFY_FOR_LIGHT_SENSOR) +static DEVICE_ATTR(brightness, 0664, light_brightness_show, light_brightness_store); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_SSC_AOD_RECT) +static DEVICE_ATTR(set_aod_rect, 0220, NULL, light_set_aod_rect_store); +#endif +static DEVICE_ATTR(trim_check, 0444, light_trim_check_show, NULL); +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) && !IS_ENABLED(CONFIG_TABLET_MODEL_CONCEPT) +static DEVICE_ATTR(sub_als_lux, 0444, light_sub_als_lux_show, NULL); +#endif + +static struct device_attribute *light_attrs[] = { + &dev_attr_vendor, + &dev_attr_name, + &dev_attr_lux, + &dev_attr_raw_data, + &dev_attr_dhr_sensor_info, + &dev_attr_register_write, + &dev_attr_register_read, + &dev_attr_lcd_onoff, + &dev_attr_hallic_info, + &dev_attr_light_circle, +#if IS_ENABLED(CONFIG_SUPPORT_DDI_COPR_FOR_LIGHT_SENSOR) + &dev_attr_read_copr, + &dev_attr_test_copr, + &dev_attr_boled_enable, + &dev_attr_copr_roix, + &dev_attr_sensorhub_ddi_spi_check, +#endif +#if IS_ENABLED(CONFIG_SUPPORT_LIGHT_CALIBRATION) || IS_ENABLED(CONFIG_TABLET_MODEL_CONCEPT) + &dev_attr_light_cal, +#endif +#if IS_ENABLED(CONFIG_SUPPORT_LIGHT_CALIBRATION) + &dev_attr_light_test, +#endif + &dev_attr_debug_info, + &dev_attr_hyst, +#if !IS_ENABLED(CONFIG_SUPPORT_BRIGHTNESS_NOTIFY_FOR_LIGHT_SENSOR) + &dev_attr_brightness, +#endif +#if IS_ENABLED(CONFIG_SUPPORT_SSC_AOD_RECT) + &dev_attr_set_aod_rect, +#endif + &dev_attr_trim_check, +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) && !IS_ENABLED(CONFIG_TABLET_MODEL_CONCEPT) + &dev_attr_sub_als_lux, +#endif + NULL, +}; + +int light_factory_init(void) +{ + adsp_factory_register(MSG_LIGHT, light_attrs); +#if IS_ENABLED(CONFIG_SUPPORT_BRIGHTNESS_NOTIFY_FOR_LIGHT_SENSOR) && \ + IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) + panel_notifier_register(&light_panel_data_notifier); +#endif + pr_info("[SSC_FAC] %s\n", __func__); + + return 0; +} + +void light_factory_exit(void) +{ + adsp_factory_unregister(MSG_LIGHT); +#if IS_ENABLED(CONFIG_SUPPORT_BRIGHTNESS_NOTIFY_FOR_LIGHT_SENSOR) && \ + IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) + panel_notifier_unregister(&light_panel_data_notifier); +#endif + pr_info("[SSC_FAC] %s\n", __func__); +} diff --git a/drivers/adsp_factory/lps22hh_pressure.c b/drivers/adsp_factory/lps22hh_pressure.c new file mode 100755 index 000000000000..d0f20b9662fa --- /dev/null +++ b/drivers/adsp_factory/lps22hh_pressure.c @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include "adsp.h" +#define VENDOR "STM" +#if IS_ENABLED(CONFIG_LPS22DF_FACTORY) +#define CHIP_ID "LPS22DF" +#else +#define CHIP_ID "LPS22HH" +#endif + +#define CALIBRATION_FILE_PATH "/efs/FactoryApp/baro_delta" + +#define PR_MAX 8388607 /* 24 bit 2'compl */ +#define PR_MIN -8388608 +#define SNS_SUCCESS 0 +#define ST_PASS 1 +#define ST_FAIL 0 + +static int sea_level_pressure; +static int pressure_cal; + +static ssize_t pressure_vendor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR); +} + +static ssize_t pressure_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", CHIP_ID); +} + +static ssize_t sea_level_pressure_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", sea_level_pressure); +} + +static ssize_t sea_level_pressure_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + if (sscanf(buf, "%10d", &sea_level_pressure) != 1) { + pr_err("[FACTORY] %s: sscanf error\n", __func__); + return size; + } + + sea_level_pressure = sea_level_pressure / 100; + + pr_info("[FACTORY] %s: sea_level_pressure = %d\n", __func__, + sea_level_pressure); + + return size; +} + +/* +int pressure_open_calibration(struct adsp_data *data) +{ + int error = 0; + + return error; +} +*/ + +static ssize_t pressure_cabratioin_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + + schedule_delayed_work(&data->pressure_cal_work, 0); + return size; +} + +static ssize_t pressure_cabratioin_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + //struct adsp_data *data = dev_get_drvdata(dev); + + //pressure_open_calibration(data); + + return snprintf(buf, PAGE_SIZE, "%d\n", pressure_cal); +} + +static ssize_t temperature_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + + adsp_unicast(NULL, 0, MSG_PRESSURE_TEMP, 0, MSG_TYPE_GET_RAW_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & + 1 << MSG_PRESSURE_TEMP) && cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_PRESSURE_TEMP); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "-99\n"); + } + return snprintf(buf, PAGE_SIZE, "%d\n", + data->msg_buf[MSG_PRESSURE_TEMP][0]); +} + +static ssize_t selftest_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + + adsp_unicast(NULL, 0, MSG_PRESSURE, 0, MSG_TYPE_ST_SHOW_DATA); + + while (!(data->ready_flag[MSG_TYPE_ST_SHOW_DATA] & + 1 << MSG_PRESSURE) && cnt++ < TIMEOUT_CNT) + msleep(26); + + data->ready_flag[MSG_TYPE_ST_SHOW_DATA] &= ~(1 << MSG_PRESSURE); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "0\n"); + } + + pr_info("[FACTORY] %s : P:%d, T:%d, RES:%d\n", + __func__, data->msg_buf[MSG_PRESSURE][0], + data->msg_buf[MSG_PRESSURE][1], data->msg_buf[MSG_PRESSURE][2]); + + if (SNS_SUCCESS == data->msg_buf[MSG_PRESSURE][2]) + return snprintf(buf, PAGE_SIZE, "%d\n", ST_PASS); + else + return snprintf(buf, PAGE_SIZE, "%d\n", ST_FAIL); +} + +static ssize_t pressure_dhr_sensor_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + int i = 0; + + adsp_unicast(NULL, 0, MSG_PRESSURE, 0, MSG_TYPE_GET_DHR_INFO); + while (!(data->ready_flag[MSG_TYPE_GET_DHR_INFO] & 1 << MSG_PRESSURE) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_DHR_INFO] &= ~(1 << MSG_PRESSURE); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + } else { + for (i = 0; i < 8; i++) { + pr_info("[FACTORY] %s - %02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x\n", + __func__, + data->msg_buf[MSG_PRESSURE][i * 16 + 0], + data->msg_buf[MSG_PRESSURE][i * 16 + 1], + data->msg_buf[MSG_PRESSURE][i * 16 + 2], + data->msg_buf[MSG_PRESSURE][i * 16 + 3], + data->msg_buf[MSG_PRESSURE][i * 16 + 4], + data->msg_buf[MSG_PRESSURE][i * 16 + 5], + data->msg_buf[MSG_PRESSURE][i * 16 + 6], + data->msg_buf[MSG_PRESSURE][i * 16 + 7], + data->msg_buf[MSG_PRESSURE][i * 16 + 8], + data->msg_buf[MSG_PRESSURE][i * 16 + 9], + data->msg_buf[MSG_PRESSURE][i * 16 + 10], + data->msg_buf[MSG_PRESSURE][i * 16 + 11], + data->msg_buf[MSG_PRESSURE][i * 16 + 12], + data->msg_buf[MSG_PRESSURE][i * 16 + 13], + data->msg_buf[MSG_PRESSURE][i * 16 + 14], + data->msg_buf[MSG_PRESSURE][i * 16 + 15]); + } + } + + return snprintf(buf, PAGE_SIZE, "%s\n", "Done"); +} + +static ssize_t pressure_sw_offset_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + int input = 0; + int sw_offset = 0; + int ret; + + ret = kstrtoint(buf, 10, &input); + if (ret < 0) { + pr_err("[FACTORY] %s: kstrtoint fail\n", __func__); + return -EINVAL; + } + + pr_info("[FACTORY] %s: write value = %d\n", __func__, input); + + adsp_unicast(&input, sizeof(int), + MSG_PRESSURE, 0, MSG_TYPE_SET_THRESHOLD); + + while (!(data->ready_flag[MSG_TYPE_SET_THRESHOLD] & 1 << MSG_PRESSURE) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_SET_THRESHOLD] &= ~(1 << MSG_PRESSURE); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + } else { + sw_offset = data->msg_buf[MSG_PRESSURE][0]; + } + + pr_info("[FACTORY] %s: sw_offset %d\n", __func__, sw_offset); + + return size; +} + +static ssize_t pressure_sw_offset_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + + adsp_unicast(NULL, 0, MSG_PRESSURE, 0, MSG_TYPE_GET_THRESHOLD); + + while (!(data->ready_flag[MSG_TYPE_GET_THRESHOLD] & + 1 << MSG_PRESSURE) && cnt++ < TIMEOUT_CNT) + msleep(20); + + data->ready_flag[MSG_TYPE_GET_THRESHOLD] &= ~(1 << MSG_PRESSURE); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "0\n"); + } + + pr_info("[FACTORY] %s : sw_offset %d\n", + __func__, data->msg_buf[MSG_PRESSURE][0]); + + return snprintf(buf, PAGE_SIZE, "%d\n", data->msg_buf[MSG_PRESSURE][0]); +} + +static DEVICE_ATTR(vendor, 0444, pressure_vendor_show, NULL); +static DEVICE_ATTR(name, 0444, pressure_name_show, NULL); +static DEVICE_ATTR(calibration, 0664, + pressure_cabratioin_show, pressure_cabratioin_store); +static DEVICE_ATTR(sea_level_pressure, 0664, + sea_level_pressure_show, sea_level_pressure_store); +static DEVICE_ATTR(temperature, 0444, temperature_show, NULL); +static DEVICE_ATTR(selftest, 0444, selftest_show, NULL); +#ifdef CONFIG_SEC_FACTORY +static DEVICE_ATTR(dhr_sensor_info, 0444, + pressure_dhr_sensor_info_show, NULL); +#else +static DEVICE_ATTR(dhr_sensor_info, 0440, + pressure_dhr_sensor_info_show, NULL); +#endif +static DEVICE_ATTR(sw_offset, 0664, + pressure_sw_offset_show, pressure_sw_offset_store); + +static struct device_attribute *pressure_attrs[] = { + &dev_attr_vendor, + &dev_attr_name, + &dev_attr_calibration, + &dev_attr_sea_level_pressure, + &dev_attr_temperature, + &dev_attr_selftest, + &dev_attr_dhr_sensor_info, + &dev_attr_sw_offset, + NULL, +}; + +void pressure_cal_work_func(struct work_struct *work) +{ + struct adsp_data *data = container_of((struct delayed_work *)work, + struct adsp_data, pressure_cal_work); + int cnt = 0; + int temp = 0; + + adsp_unicast(&temp, sizeof(temp), MSG_PRESSURE, 0, MSG_TYPE_SET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_SET_CAL_DATA] & 1 << MSG_PRESSURE) && + cnt++ < 3) + msleep(30); + + data->ready_flag[MSG_TYPE_SET_CAL_DATA] &= ~(1 << MSG_PRESSURE); + + if (cnt >= 3) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return; + } + + pressure_cal = data->msg_buf[MSG_PRESSURE][0]; + pr_info("[FACTORY] %s: pressure_cal = %d (lsb)\n", __func__, data->msg_buf[MSG_PRESSURE][0]); +} +EXPORT_SYMBOL(pressure_cal_work_func); +void pressure_factory_init_work(struct adsp_data *data) +{ + schedule_delayed_work(&data->pressure_cal_work, msecs_to_jiffies(8000)); +} +EXPORT_SYMBOL(pressure_factory_init_work); + +int __init lps22hh_pressure_factory_init(void) +{ + adsp_factory_register(MSG_PRESSURE, pressure_attrs); + + pr_info("[FACTORY] %s\n", __func__); + + return 0; +} +void __exit lps22hh_pressure_factory_exit(void) +{ + adsp_factory_unregister(MSG_PRESSURE); + + pr_info("[FACTORY] %s\n", __func__); +} diff --git a/drivers/adsp_factory/lsm6dsl_gyro.c b/drivers/adsp_factory/lsm6dsl_gyro.c new file mode 100755 index 000000000000..fe371839a91c --- /dev/null +++ b/drivers/adsp_factory/lsm6dsl_gyro.c @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include "adsp.h" +#define VENDOR "STM" +#define CHIP_ID "LSM6DSL" +#define ST_PASS 1 +#define ST_FAIL 0 +#define ST_ZRO_MIN (-40) +#define ST_ZRO_MAX 40 +#define SELFTEST_REVISED 1 + +static ssize_t gyro_vendor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR); +} + +static ssize_t gyro_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", CHIP_ID); +} + +static ssize_t selftest_revised_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", SELFTEST_REVISED); +} + +static ssize_t gyro_power_off(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("[FACTORY]: %s\n", __func__); + + return snprintf(buf, PAGE_SIZE, "%d\n", 1); +} + +static ssize_t gyro_power_on(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("[FACTORY]: %s\n", __func__); + + return snprintf(buf, PAGE_SIZE, "%d\n", 1); +} + +static ssize_t gyro_temp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + + adsp_unicast(NULL, 0, MSG_GYRO_TEMP, 0, MSG_TYPE_GET_RAW_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & 1 << MSG_GYRO_TEMP) + && cnt++ < TIMEOUT_CNT) + msleep(20); + + data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_GYRO_TEMP); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "-99\n"); + } + + pr_info("[FACTORY] %s: gyro_temp = %d\n", __func__, + data->msg_buf[MSG_GYRO_TEMP][0]); + + return snprintf(buf, PAGE_SIZE, "%d\n", + data->msg_buf[MSG_GYRO_TEMP][0]); +} + +static ssize_t gyro_selftest_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + int st_diff_res = ST_FAIL; + int st_zro_res = ST_FAIL; + + pr_info("[FACTORY] %s - start", __func__); + adsp_unicast(NULL, 0, MSG_GYRO, 0, MSG_TYPE_ST_SHOW_DATA); + + while (!(data->ready_flag[MSG_TYPE_ST_SHOW_DATA] & 1 << MSG_GYRO) && + cnt++ < TIMEOUT_CNT) + msleep(25); + + data->ready_flag[MSG_TYPE_ST_SHOW_DATA] &= ~(1 << MSG_GYRO); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, + "0,0,0,0,0,0,0,0,0,0,0,0,%d,%d\n", + ST_FAIL, ST_FAIL); + } + + if (data->msg_buf[MSG_GYRO][1] != 0) { + pr_info("[FACTORY] %s - failed(%d, %d)\n", __func__, + data->msg_buf[MSG_GYRO][1], + data->msg_buf[MSG_GYRO][5]); + + pr_info("[FACTORY]: %s - %d,%d,%d\n", __func__, + data->msg_buf[MSG_GYRO][2], + data->msg_buf[MSG_GYRO][3], + data->msg_buf[MSG_GYRO][4]); + + return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n", + data->msg_buf[MSG_GYRO][2], + data->msg_buf[MSG_GYRO][3], + data->msg_buf[MSG_GYRO][4]); + } + + if (!data->msg_buf[MSG_GYRO][5]) + st_diff_res = ST_PASS; + + if((ST_ZRO_MIN <= data->msg_buf[MSG_GYRO][6]) + && (data->msg_buf[MSG_GYRO][6] <= ST_ZRO_MAX) + && (ST_ZRO_MIN <= data->msg_buf[MSG_GYRO][7]) + && (data->msg_buf[MSG_GYRO][7] <= ST_ZRO_MAX) + && (ST_ZRO_MIN <= data->msg_buf[MSG_GYRO][8]) + && (data->msg_buf[MSG_GYRO][8]<= ST_ZRO_MAX)) + st_zro_res = ST_PASS; + + pr_info("[FACTORY]: %s - %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", + __func__, + data->msg_buf[MSG_GYRO][2], data->msg_buf[MSG_GYRO][3], + data->msg_buf[MSG_GYRO][4], data->msg_buf[MSG_GYRO][6], + data->msg_buf[MSG_GYRO][7], data->msg_buf[MSG_GYRO][8], + data->msg_buf[MSG_GYRO][9], data->msg_buf[MSG_GYRO][10], + data->msg_buf[MSG_GYRO][11], data->msg_buf[MSG_GYRO][12], + data->msg_buf[MSG_GYRO][13], data->msg_buf[MSG_GYRO][14], + st_diff_res, st_zro_res); + + return snprintf(buf, PAGE_SIZE, + "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", + data->msg_buf[MSG_GYRO][2], data->msg_buf[MSG_GYRO][3], + data->msg_buf[MSG_GYRO][4], data->msg_buf[MSG_GYRO][6], + data->msg_buf[MSG_GYRO][7], data->msg_buf[MSG_GYRO][8], + data->msg_buf[MSG_GYRO][9], data->msg_buf[MSG_GYRO][10], + data->msg_buf[MSG_GYRO][11], data->msg_buf[MSG_GYRO][12], + data->msg_buf[MSG_GYRO][13], data->msg_buf[MSG_GYRO][14], + st_diff_res, st_zro_res); +} + +static DEVICE_ATTR(name, 0444, gyro_name_show, NULL); +static DEVICE_ATTR(vendor, 0444, gyro_vendor_show, NULL); +static DEVICE_ATTR(selftest, 0440, gyro_selftest_show, NULL); +static DEVICE_ATTR(power_on, 0444, gyro_power_on, NULL); +static DEVICE_ATTR(power_off, 0444, gyro_power_off, NULL); +static DEVICE_ATTR(temperature, 0440, gyro_temp_show, NULL); +static DEVICE_ATTR(selftest_revised, 0440, selftest_revised_show, NULL); + +static struct device_attribute *gyro_attrs[] = { + &dev_attr_name, + &dev_attr_vendor, + &dev_attr_selftest, + &dev_attr_power_on, + &dev_attr_power_off, + &dev_attr_temperature, + &dev_attr_selftest_revised, + NULL, +}; + +int __init lsm6dsl_gyro_factory_init(void) +{ + adsp_factory_register(MSG_GYRO, gyro_attrs); + + pr_info("[FACTORY] %s\n", __func__); + + return 0; +} + +void __exit lsm6dsl_gyro_factory_exit(void) +{ + adsp_factory_unregister(MSG_GYRO); + + pr_info("[FACTORY] %s\n", __func__); +} diff --git a/drivers/adsp_factory/lsm6dso_accel.c b/drivers/adsp_factory/lsm6dso_accel.c new file mode 100755 index 000000000000..170dd2498a20 --- /dev/null +++ b/drivers/adsp_factory/lsm6dso_accel.c @@ -0,0 +1,809 @@ +/* + * Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include "adsp.h" +#ifdef CONFIG_SEC_VIB_NOTIFIER +#include +#endif +#define VENDOR "STM" +#if IS_ENABLED(CONFIG_LSM6DSV_FACTORY) +#define CHIP_ID "LSM6DSV" +#else +#define CHIP_ID "LSM6DSO" +#endif +#define ACCEL_ST_TRY_CNT 3 +#define ACCEL_FACTORY_CAL_CNT 20 +#define ACCEL_RAW_DATA_CNT 3 +#define MAX_ACCEL_1G 2048 + +#define STM_LSM6DSO_INT_CHECK_RUNNING 4 + +#define MAX_MOTOR_STOP_TIMEOUT (8 * NSEC_PER_SEC) +/* Haptic Pattern A vibrate during 7ms. + * touch, touchkey, operation feedback use this. + * Do not call motor_workfunc when duration is 7ms. + */ +#define DURATION_SKIP 10 +#define MOTOR_OFF 0 +#define MOTOR_ON 1 +#define CALL_VIB_IDX 65534 + +#ifdef CONFIG_SEC_VIB_NOTIFIER +struct accel_motor_data { + struct notifier_block motor_nb; + struct hrtimer motor_stop_timer; + struct workqueue_struct *slpi_motor_wq; + struct work_struct work_slpi_motor; + atomic_t motor_state; + int idx; + int timeout; +}; + +struct accel_motor_data *pdata_motor; +#endif + +struct accel_data { + struct work_struct work_accel; + struct workqueue_struct *accel_wq; + struct adsp_data *dev_data; + bool is_complete_cal; + bool lpf_onoff; + bool st_complete; + int32_t raw_data[ACCEL_RAW_DATA_CNT]; + int32_t avg_data[ACCEL_RAW_DATA_CNT]; +}; + +static struct accel_data *pdata; +static bool is_ignore_crash_factory = false; + +static ssize_t accel_vendor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR); +} + +static ssize_t accel_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", CHIP_ID); +} + +static ssize_t sensor_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", "ADSP"); +} + +int get_accel_cal_data(struct adsp_data *data, int32_t *cal_data) +{ + uint8_t cnt = 0; + + adsp_unicast(NULL, 0, MSG_ACCEL, 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] & 1 << MSG_ACCEL) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << MSG_ACCEL); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return 0; + } + + if (data->msg_buf[MSG_ACCEL][3] != ACCEL_RAW_DATA_CNT) { + pr_err("[FACTORY] %s: Reading Bytes Num %d!!!\n", + __func__, data->msg_buf[MSG_ACCEL][3]); + return 0; + } + + cal_data[0] = data->msg_buf[MSG_ACCEL][0]; + cal_data[1] = data->msg_buf[MSG_ACCEL][1]; + cal_data[2] = data->msg_buf[MSG_ACCEL][2]; + + pr_info("[FACTORY] %s: %d, %d, %d, %d\n", __func__, + cal_data[0], cal_data[1], cal_data[2], + data->msg_buf[MSG_ACCEL][3]); + + return data->msg_buf[MSG_ACCEL][3]; +} + +void set_accel_cal_data(struct adsp_data *data) +{ + uint8_t cnt = 0; + + pr_info("[FACTORY] %s: %d, %d, %d\n", __func__, pdata->avg_data[0], + pdata->avg_data[1], pdata->avg_data[2]); + adsp_unicast(pdata->avg_data, sizeof(pdata->avg_data), + MSG_ACCEL, 0, MSG_TYPE_SET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_SET_CAL_DATA] & 1 << MSG_ACCEL) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_SET_CAL_DATA] &= ~(1 << MSG_ACCEL); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + } else if (data->msg_buf[MSG_ACCEL][0] != ACCEL_RAW_DATA_CNT) { + pr_err("[FACTORY] %s: Write Bytes Num %d!!!\n", + __func__, data->msg_buf[MSG_ACCEL][0]); + } +} + +static ssize_t accel_calibration_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int32_t cal_data[ACCEL_RAW_DATA_CNT] = {0, }; + int ret = 0; + + mutex_lock(&data->accel_factory_mutex); + ret = get_accel_cal_data(data, cal_data); + mutex_unlock(&data->accel_factory_mutex); + if (ret > 0) { + pr_info("[FACTORY] %s: %d, %d, %d\n", __func__, + cal_data[0], cal_data[1], cal_data[2]); + if (cal_data[0] == 0 && cal_data[1] == 0 && cal_data[2] == 0) + return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d\n", + 0, 0, 0, 0); + else + return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d\n", + true, cal_data[0], cal_data[1], cal_data[2]); + } else { + pr_err("[FACTORY] %s: get_accel_cal_data fail\n", __func__); + return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d\n", 0, 0, 0, 0); + } +} + +static ssize_t accel_calibration_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + + pdata->dev_data = data; + if (sysfs_streq(buf, "0")) { + mutex_lock(&data->accel_factory_mutex); + memset(pdata->avg_data, 0, sizeof(pdata->avg_data)); + set_accel_cal_data(data); + mutex_unlock(&data->accel_factory_mutex); + } else { + pdata->is_complete_cal = false; + queue_work(pdata->accel_wq, &pdata->work_accel); + while (pdata->is_complete_cal == false) { + pr_info("[FACTORY] %s: In factory cal\n", __func__); + msleep(20); + } + mutex_lock(&data->accel_factory_mutex); + set_accel_cal_data(data); + mutex_unlock(&data->accel_factory_mutex); + } + + return size; +} + +static void accel_work_func(struct work_struct *work) +{ + struct accel_data *data = container_of((struct work_struct *)work, + struct accel_data, work_accel); + int i; + + mutex_lock(&data->dev_data->accel_factory_mutex); + memset(pdata->avg_data, 0, sizeof(pdata->avg_data)); + adsp_unicast(pdata->avg_data, sizeof(pdata->avg_data), + MSG_ACCEL, 0, MSG_TYPE_SET_CAL_DATA); + msleep(30); /* for init of bias */ + for (i = 0; i < ACCEL_FACTORY_CAL_CNT; i++) { + msleep(20); + get_accel_raw_data(pdata->raw_data); + pdata->avg_data[0] += pdata->raw_data[0]; + pdata->avg_data[1] += pdata->raw_data[1]; + pdata->avg_data[2] += pdata->raw_data[2]; + pr_info("[FACTORY] %s: %d, %d, %d\n", __func__, + pdata->raw_data[0], pdata->raw_data[1], + pdata->raw_data[2]); + } + + for (i = 0; i < ACCEL_RAW_DATA_CNT; i++) { + pdata->avg_data[i] /= ACCEL_FACTORY_CAL_CNT; + pr_info("[FACTORY] %s: avg : %d\n", + __func__, pdata->avg_data[i]); + } + + if (pdata->avg_data[2] > 0) + pdata->avg_data[2] -= MAX_ACCEL_1G; + else if (pdata->avg_data[2] < 0) + pdata->avg_data[2] += MAX_ACCEL_1G; + + mutex_unlock(&data->dev_data->accel_factory_mutex); + pdata->is_complete_cal = true; +} + +void accel_cal_work_func(struct work_struct *work) +{ + struct adsp_data *data = container_of((struct delayed_work *)work, + struct adsp_data, accel_cal_work); + int ret = 0; + + mutex_lock(&data->accel_factory_mutex); + ret = get_accel_cal_data(data, pdata->avg_data); + mutex_unlock(&data->accel_factory_mutex); + if (ret > 0) { + pr_info("[FACTORY] %s: ret(%d) %d, %d, %d\n", __func__, ret, + pdata->avg_data[0], + pdata->avg_data[1], + pdata->avg_data[2]); + + mutex_lock(&data->accel_factory_mutex); + set_accel_cal_data(data); + mutex_unlock(&data->accel_factory_mutex); + } else { + pr_err("[FACTORY] %s: get_accel_cal_data fail (%d)\n", + __func__, ret); + } +} + +static ssize_t accel_selftest_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + int retry = 0; +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) || defined(CONFIG_SUPPORT_AK09973) + int msg_buf = LSM6DSO_SELFTEST_TRUE; + + adsp_unicast(&msg_buf, sizeof(msg_buf), + MSG_DIGITAL_HALL_ANGLE, 0, MSG_TYPE_OPTION_DEFINE); +#elif IS_ENABLED(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) || defined(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) + int msg_buf = LSM6DSO_SELFTEST_TRUE; + + adsp_unicast(&msg_buf, sizeof(msg_buf), + MSG_REF_ANGLE, 0, MSG_TYPE_OPTION_DEFINE); +#endif + + pdata->st_complete = false; +RETRY_ACCEL_SELFTEST: + adsp_unicast(NULL, 0, MSG_ACCEL, 0, MSG_TYPE_ST_SHOW_DATA); + + while (!(data->ready_flag[MSG_TYPE_ST_SHOW_DATA] & 1 << MSG_ACCEL) && + cnt++ < TIMEOUT_CNT) + msleep(26); + + data->ready_flag[MSG_TYPE_ST_SHOW_DATA] &= ~(1 << MSG_ACCEL); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + data->msg_buf[MSG_ACCEL][1] = -1; +#ifdef CONFIG_SEC_FACTORY + panic("sensor force crash : accel selftest timeout\n"); +#endif + } + + pr_info("[FACTORY] %s : init = %d, result = %d, XYZ = %d, %d, %d, nXYZ = %d, %d, %d\n", + __func__, data->msg_buf[MSG_ACCEL][0], + data->msg_buf[MSG_ACCEL][1], data->msg_buf[MSG_ACCEL][2], + data->msg_buf[MSG_ACCEL][3], data->msg_buf[MSG_ACCEL][4], + data->msg_buf[MSG_ACCEL][5], data->msg_buf[MSG_ACCEL][6], + data->msg_buf[MSG_ACCEL][7]); + pr_info("[FACTORY] %s : pre/postP/postN [%d, %d, %d/%d, %d, %d/%d, %d, %d], comm_err_cnt %d/%d/%d\n", + __func__, data->msg_buf[MSG_ACCEL][8], + data->msg_buf[MSG_ACCEL][9], data->msg_buf[MSG_ACCEL][10], + data->msg_buf[MSG_ACCEL][11], data->msg_buf[MSG_ACCEL][12], + data->msg_buf[MSG_ACCEL][13], data->msg_buf[MSG_ACCEL][14], + data->msg_buf[MSG_ACCEL][15], data->msg_buf[MSG_ACCEL][16], + data->msg_buf[MSG_ACCEL][17], data->msg_buf[MSG_ACCEL][18], + data->msg_buf[MSG_ACCEL][19]); + + if (data->msg_buf[MSG_ACCEL][1] == 1) { + pr_info("[FACTORY] %s : Pass - result = %d, retry = %d\n", + __func__, data->msg_buf[MSG_ACCEL][1], retry); + } else { + data->msg_buf[MSG_ACCEL][1] = -5; + pr_err("[FACTORY] %s : Fail - result = %d, retry = %d\n", + __func__, data->msg_buf[MSG_ACCEL][1], retry); + + if (retry < ACCEL_ST_TRY_CNT) { + retry++; + msleep(200); + cnt = 0; + pr_info("[FACTORY] %s: retry\n", __func__); + goto RETRY_ACCEL_SELFTEST; + } + } + + pdata->st_complete = true; + +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) || defined(CONFIG_SUPPORT_AK09973) ||\ + IS_ENABLED(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) || defined(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) + schedule_delayed_work(&data->lsm6dso_selftest_stop_work, msecs_to_jiffies(300)); +#endif + return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d,%d,%d,%d\n", + data->msg_buf[MSG_ACCEL][1], + (int)abs(data->msg_buf[MSG_ACCEL][2]), + (int)abs(data->msg_buf[MSG_ACCEL][3]), + (int)abs(data->msg_buf[MSG_ACCEL][4]), + (int)abs(data->msg_buf[MSG_ACCEL][5]), + (int)abs(data->msg_buf[MSG_ACCEL][6]), + (int)abs(data->msg_buf[MSG_ACCEL][7])); +} + +static ssize_t accel_raw_data_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int32_t raw_data[ACCEL_RAW_DATA_CNT] = {0, }; + static int32_t prev_raw_data[ACCEL_RAW_DATA_CNT] = {0, }; + int ret = 0; +#ifdef CONFIG_SEC_FACTORY + static int same_cnt = 0; +#endif + + if (pdata->st_complete == false) { + pr_info("[FACTORY] %s: selftest is running\n", __func__); + return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n", + raw_data[0], raw_data[1], raw_data[2]); + } + + mutex_lock(&data->accel_factory_mutex); + ret = get_accel_raw_data(raw_data); + mutex_unlock(&data->accel_factory_mutex); + +#ifdef CONFIG_SEC_FACTORY + pr_info("[FACTORY] %s: %d, %d, %d\n", __func__, + raw_data[0], raw_data[1], raw_data[2]); + + if (prev_raw_data[0] == raw_data[0] && + prev_raw_data[1] == raw_data[1] && + prev_raw_data[2] == raw_data[2]) { + same_cnt++; + pr_info("[FACTORY] %s: same_cnt %d\n", __func__, same_cnt); + if (same_cnt >= 20) + panic("sensor force crash : accel raw_data stuck\n"); + } else + same_cnt = 0; +#endif + + if (!ret) { + memcpy(prev_raw_data, raw_data, sizeof(int32_t) * 3); + } else if (!pdata->lpf_onoff) { + pr_err("[FACTORY] %s: using prev data!!!\n", __func__); + memcpy(raw_data, prev_raw_data, sizeof(int32_t) * 3); + } else { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + } + + return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n", + raw_data[0], raw_data[1], raw_data[2]); +} + +static ssize_t accel_reactive_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + bool success = false; + int32_t msg_buf = 0; + + mutex_lock(&data->accel_factory_mutex); + adsp_unicast(&msg_buf, sizeof(int32_t), MSG_ACCEL, + 0, MSG_TYPE_GET_REGISTER); + + while (!(data->ready_flag[MSG_TYPE_GET_REGISTER] & 1 << MSG_ACCEL) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_REGISTER] &= ~(1 << MSG_ACCEL); + mutex_unlock(&data->accel_factory_mutex); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "%d\n", (int)success); + } + + pr_info("[FACTORY]: %s - %d\n", __func__, + data->msg_buf[MSG_ACCEL][0]); + + if (data->msg_buf[MSG_ACCEL][0] == 0) + success = true; + else + panic("sensor accel interrupt check fail!!"); + + return snprintf(buf, PAGE_SIZE, "%d\n", (int)success); +} + +static ssize_t accel_reactive_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int32_t msg_buf; + uint8_t cnt = 0; + + if (sysfs_streq(buf, "1")) + pr_info("[FACTORY]: %s - on\n", __func__); + else if (sysfs_streq(buf, "0")) + pr_info("[FACTORY]: %s - off\n", __func__); + else if (sysfs_streq(buf, "2")) { + pr_info("[FACTORY]: %s - factory\n", __func__); + msg_buf = 1; + + mutex_lock(&data->accel_factory_mutex); + adsp_unicast(&msg_buf, sizeof(int32_t), MSG_ACCEL, + 0, MSG_TYPE_GET_REGISTER); + + while (!(data->ready_flag[MSG_TYPE_GET_REGISTER] & 1 << MSG_ACCEL) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_REGISTER] &= ~(1 << MSG_ACCEL); + mutex_unlock(&data->accel_factory_mutex); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return size; + } + + if (data->msg_buf[MSG_ACCEL][0] == STM_LSM6DSO_INT_CHECK_RUNNING) + pr_info("[FACTORY]: %s - STM_LSM6DSx_INT_CHECK_RUNNING\n", __func__); + else + pr_info("[FACTORY]: %s - Something wrong\n", __func__); + } + + return size; +} + +bool sns_check_ignore_crash(void) +{ + return is_ignore_crash_factory; +} +EXPORT_SYMBOL(sns_check_ignore_crash); + +static ssize_t accel_lowpassfilter_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + int32_t msg_buf; + + if (sysfs_streq(buf, "1")) { + msg_buf = 1; + is_ignore_crash_factory = false; + } else if (sysfs_streq(buf, "0")) { + msg_buf = 0; + is_ignore_crash_factory = false; +#ifdef CONFIG_SEC_FACTORY + } else if (sysfs_streq(buf, "2")) { + msg_buf = 2; + is_ignore_crash_factory = true; + pr_info("[FACTORY] %s: Pretest\n", __func__); + } else if (sysfs_streq(buf, "3")) { + msg_buf = 3; + is_ignore_crash_factory = true; + pr_info("[FACTORY] %s: Questt\n", __func__); + return size; +#endif + } else { + is_ignore_crash_factory = false; + pr_info("[FACTORY] %s: wrong value\n", __func__); + return size; + } + + mutex_lock(&data->accel_factory_mutex); + adsp_unicast(&msg_buf, sizeof(int32_t), MSG_ACCEL, + 0, MSG_TYPE_SET_ACCEL_LPF); + + while (!(data->ready_flag[MSG_TYPE_SET_ACCEL_LPF] & 1 << MSG_ACCEL) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_SET_ACCEL_LPF] &= ~(1 << MSG_ACCEL); + mutex_unlock(&data->accel_factory_mutex); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return size; + } + + pdata->lpf_onoff = (bool)data->msg_buf[MSG_ACCEL][0]; + +#if IS_ENABLED(CONFIG_LSM6DSV_FACTORY) + pr_info("[FACTORY] %s: %d, 0x0A:%02x 0x0D:%02x 0x18:%02x\n", __func__, + data->msg_buf[MSG_ACCEL][0], data->msg_buf[MSG_ACCEL][1], + data->msg_buf[MSG_ACCEL][2], data->msg_buf[MSG_ACCEL][3]); +#else + pr_info("[FACTORY] %s: %d, 0x0A:%02x 0x0D:%02x 0x10:%02x\n", __func__, + data->msg_buf[MSG_ACCEL][0], data->msg_buf[MSG_ACCEL][1], + data->msg_buf[MSG_ACCEL][2], data->msg_buf[MSG_ACCEL][3]); +#endif + return size; +} + +#ifdef CONFIG_SEC_VIB_NOTIFIER +uint64_t motor_stop_timeout(int timeout_ms) +{ + uint64_t timeout_ns = timeout_ms * NSEC_PER_MSEC; + + if (timeout_ns > MAX_MOTOR_STOP_TIMEOUT) + return timeout_ns; + else + return MAX_MOTOR_STOP_TIMEOUT; +} + +int ssc_motor_notify(struct notifier_block *nb, + unsigned long enable, void *v) +{ + struct vib_notifier_context *vib = (struct vib_notifier_context *)v; + uint64_t timeout_ns = 0; + + pr_info("[FACTORY] %s: %s, idx: %d timeout: %d\n", + __func__, enable ? "ON" : "OFF", vib->index, vib->timeout); + + if(enable == 1) { + pdata_motor->idx = vib->index; + pdata_motor->timeout = vib->timeout; + + if (pdata_motor->idx == 0) + timeout_ns = motor_stop_timeout(pdata_motor->timeout); + else + timeout_ns = MAX_MOTOR_STOP_TIMEOUT; + + if (atomic_read(&pdata_motor->motor_state) == MOTOR_OFF) { + atomic_set(&pdata_motor->motor_state, MOTOR_ON); + queue_work(pdata_motor->slpi_motor_wq, + &pdata_motor->work_slpi_motor); + } else { + hrtimer_cancel(&pdata_motor->motor_stop_timer); + if (pdata_motor->idx == CALL_VIB_IDX) { + queue_work(pdata_motor->slpi_motor_wq, + &pdata_motor->work_slpi_motor); + return 0; + } + hrtimer_start(&pdata_motor->motor_stop_timer, + ns_to_ktime(timeout_ns), + HRTIMER_MODE_REL); + } + } else { + if (pdata_motor->idx == CALL_VIB_IDX) { + atomic_set(&pdata_motor->motor_state, MOTOR_OFF); + queue_work(pdata_motor->slpi_motor_wq, + &pdata_motor->work_slpi_motor); + } else + pr_info("[FACTORY] %s: Not support OFF\n", __func__); + } + + return 0; +} + +static enum hrtimer_restart motor_stop_timer_func(struct hrtimer *timer) +{ + pr_info("[FACTORY] %s\n", __func__); + atomic_set(&pdata_motor->motor_state, MOTOR_OFF); + queue_work(pdata_motor->slpi_motor_wq, &pdata_motor->work_slpi_motor); + + return HRTIMER_NORESTART; +} + +void slpi_motor_work_func(struct work_struct *work) +{ + int32_t msg_buf = 0; + uint64_t timeout_ns = 0; + if (pdata_motor->idx == 0) + timeout_ns = motor_stop_timeout(pdata_motor->timeout); + else + timeout_ns = MAX_MOTOR_STOP_TIMEOUT; + + if (atomic_read(&pdata_motor->motor_state) == MOTOR_ON) { + if (pdata_motor->idx != CALL_VIB_IDX) + hrtimer_start(&pdata_motor->motor_stop_timer, + ns_to_ktime(timeout_ns), + HRTIMER_MODE_REL); + msg_buf = 1; + } else if (atomic_read(&pdata_motor->motor_state) == MOTOR_OFF) { + if (pdata_motor->idx != CALL_VIB_IDX) + hrtimer_cancel(&pdata_motor->motor_stop_timer); + msg_buf = 0; + } else { + pr_info("[FACTORY] %s: invalid state %d\n", + __func__, (int)atomic_read(&pdata_motor->motor_state)); + } + + pr_info("[FACTORY] %s: msg_buf = %d, idx/timeout = %d/%d\n", + __func__, msg_buf, pdata_motor->idx, + (int)(timeout_ns / NSEC_PER_MSEC)); + + adsp_unicast(&msg_buf, sizeof(int32_t), MSG_ACCEL, + 0, MSG_TYPE_SET_ACCEL_MOTOR); +#ifdef CONFIG_SUPPORT_DUAL_6AXIS + usleep_range(500, 550); + adsp_unicast(&msg_buf, sizeof(int32_t), MSG_ACCEL_SUB, + 0, MSG_TYPE_SET_ACCEL_MOTOR); +#endif +} +#endif + +static ssize_t accel_dhr_sensor_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + char ctrl1_xl = 0; + uint8_t fullscale = 0; + + adsp_unicast(NULL, 0, MSG_ACCEL, 0, MSG_TYPE_GET_DHR_INFO); + while (!(data->ready_flag[MSG_TYPE_GET_DHR_INFO] & 1 << MSG_ACCEL) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_DHR_INFO] &= ~(1 << MSG_ACCEL); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + } else { + ctrl1_xl = data->msg_buf[MSG_ACCEL][16]; + + ctrl1_xl &= 0xC; + + switch (ctrl1_xl) { + case 0xC: + fullscale = 8; + break; + case 0x8: + fullscale = 4; + break; + case 0x4: + fullscale = 16; + break; + case 0: + fullscale = 2; + break; + default: + break; + } + } + pr_info("[FACTORY] %s: f/s %u\n", __func__, fullscale); + + return snprintf(buf, PAGE_SIZE, "\"FULL_SCALE\":\"%uG\"\n", fullscale); +} + +static ssize_t accel_turn_over_crash_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + + pr_info("[FACTORY] %s: %d, \n", __func__, data->turn_over_crash); + return snprintf(buf, PAGE_SIZE, "%d\n", data->turn_over_crash); +} + +static ssize_t accel_turn_over_crash_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int32_t msg_buf[2] = {0, }; + + if (sysfs_streq(buf, "1")) { + data->turn_over_crash = 1; + msg_buf[1] = 1; + } else if (sysfs_streq(buf, "2")) { + data->turn_over_crash = 2; + msg_buf[1] = 2; + } else { + data->turn_over_crash = 0; + msg_buf[1] = 0; + } + + adsp_unicast(msg_buf, sizeof(msg_buf), MSG_ACCEL, + 0, MSG_TYPE_OPTION_DEFINE); + pr_info("[FACTORY] %s: %d, \n", __func__, msg_buf[1]); + + return size; +} + +static DEVICE_ATTR(name, 0444, accel_name_show, NULL); +static DEVICE_ATTR(vendor, 0444, accel_vendor_show, NULL); +static DEVICE_ATTR(type, 0444, sensor_type_show, NULL); +static DEVICE_ATTR(calibration, 0664, + accel_calibration_show, accel_calibration_store); +static DEVICE_ATTR(selftest, 0440, + accel_selftest_show, NULL); +static DEVICE_ATTR(raw_data, 0444, accel_raw_data_show, NULL); +static DEVICE_ATTR(reactive_alert, 0664, + accel_reactive_show, accel_reactive_store); +static DEVICE_ATTR(lowpassfilter, 0220, + NULL, accel_lowpassfilter_store); +#ifdef CONFIG_SEC_FACTORY +static DEVICE_ATTR(dhr_sensor_info, 0444, + accel_dhr_sensor_info_show, NULL); +#else +static DEVICE_ATTR(dhr_sensor_info, 0440, + accel_dhr_sensor_info_show, NULL); +#endif +static DEVICE_ATTR(turn_over_crash, 0664, + accel_turn_over_crash_show, accel_turn_over_crash_store); + +static struct device_attribute *acc_attrs[] = { + &dev_attr_name, + &dev_attr_vendor, + &dev_attr_type, + &dev_attr_calibration, + &dev_attr_selftest, + &dev_attr_raw_data, + &dev_attr_reactive_alert, + &dev_attr_lowpassfilter, + &dev_attr_dhr_sensor_info, + &dev_attr_turn_over_crash, + NULL, +}; + +void accel_factory_init_work(struct adsp_data *data) +{ + schedule_delayed_work(&data->accel_cal_work, msecs_to_jiffies(8000)); +} +EXPORT_SYMBOL(accel_factory_init_work); + +int __init lsm6dso_accel_factory_init(void) +{ + adsp_factory_register(MSG_ACCEL, acc_attrs); +#ifdef CONFIG_SEC_VIB_NOTIFIER + pdata_motor = kzalloc(sizeof(*pdata_motor), GFP_KERNEL); + + if (pdata_motor == NULL) + return -ENOMEM; + + pdata_motor->motor_nb.notifier_call = ssc_motor_notify, + pdata_motor->motor_nb.priority = 1, + sec_vib_notifier_register(&pdata_motor->motor_nb); + + hrtimer_init(&pdata_motor->motor_stop_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + pdata_motor->motor_stop_timer.function = motor_stop_timer_func; + pdata_motor->slpi_motor_wq = + create_singlethread_workqueue("slpi_motor_wq"); + + if (pdata_motor->slpi_motor_wq == NULL) { + pr_err("[FACTORY]: %s - could not create motor wq\n", __func__); + kfree(pdata_motor); + return -ENOMEM; + } + + INIT_WORK(&pdata_motor->work_slpi_motor, slpi_motor_work_func); + + atomic_set(&pdata_motor->motor_state, MOTOR_OFF); +#endif + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + pdata->accel_wq = create_singlethread_workqueue("accel_wq"); + INIT_WORK(&pdata->work_accel, accel_work_func); + + pdata->lpf_onoff = true; + pdata->st_complete = true; + pr_info("[FACTORY] %s\n", __func__); + + return 0; +} + +void __exit lsm6dso_accel_factory_exit(void) +{ + adsp_factory_unregister(MSG_ACCEL); +#ifdef CONFIG_SEC_VIB_NOTIFIER + if (atomic_read(&pdata_motor->motor_state) == MOTOR_ON) + hrtimer_cancel(&pdata_motor->motor_stop_timer); + + if (pdata_motor != NULL && pdata_motor->slpi_motor_wq != NULL) { + cancel_work_sync(&pdata_motor->work_slpi_motor); + destroy_workqueue(pdata_motor->slpi_motor_wq); + pdata_motor->slpi_motor_wq = NULL; + kfree(pdata_motor); + } +#endif + pr_info("[FACTORY] %s\n", __func__); +} diff --git a/drivers/adsp_factory/lsm6dso_gyro.c b/drivers/adsp_factory/lsm6dso_gyro.c new file mode 100755 index 000000000000..ff2f9016043d --- /dev/null +++ b/drivers/adsp_factory/lsm6dso_gyro.c @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include "adsp.h" +#define VENDOR "STM" +#if IS_ENABLED(CONFIG_LSM6DSV_FACTORY) +#define CHIP_ID "LSM6DSV" +#else +#define CHIP_ID "LSM6DSO" +#endif +#define ST_PASS 1 +#define ST_FAIL 0 +#define STARTUP_BIT_FAIL 2 +#define OIS_ST_BIT_SET 3 +#define G_ZRL_DELTA_FAIL 4 +#define OIS_RW_FAIL 5 +#define SFLP_FAIL 6 +#define SELFTEST_REVISED 1 + +static ssize_t gyro_vendor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR); +} + +static ssize_t gyro_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", CHIP_ID); +} + +static ssize_t selftest_revised_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", SELFTEST_REVISED); +} + +static ssize_t gyro_power_off(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("[FACTORY]: %s\n", __func__); + + return snprintf(buf, PAGE_SIZE, "%d\n", 1); +} + +static ssize_t gyro_power_on(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("[FACTORY]: %s\n", __func__); + + return snprintf(buf, PAGE_SIZE, "%d\n", 1); +} + +static ssize_t gyro_temp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + + adsp_unicast(NULL, 0, MSG_GYRO_TEMP, 0, MSG_TYPE_GET_RAW_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & 1 << MSG_GYRO_TEMP) + && cnt++ < TIMEOUT_CNT) + msleep(20); + + data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_GYRO_TEMP); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "-99\n"); + } + + pr_info("[FACTORY] %s: gyro_temp = %d\n", __func__, + data->msg_buf[MSG_GYRO_TEMP][0]); + + return snprintf(buf, PAGE_SIZE, "%d\n", + data->msg_buf[MSG_GYRO_TEMP][0]); +} + +static ssize_t gyro_selftest_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + int st_diff_res = ST_FAIL; + int st_zro_res = ST_FAIL; +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) || defined(CONFIG_SUPPORT_AK09973) + int msg_buf = LSM6DSO_SELFTEST_TRUE; + + adsp_unicast(&msg_buf, sizeof(msg_buf), + MSG_DIGITAL_HALL_ANGLE, 0, MSG_TYPE_OPTION_DEFINE); +#elif IS_ENABLED(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) || defined(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) + int msg_buf = LSM6DSO_SELFTEST_TRUE; + + adsp_unicast(&msg_buf, sizeof(msg_buf), + MSG_REF_ANGLE, 0, MSG_TYPE_OPTION_DEFINE); +#endif + + pr_info("[FACTORY] %s - start\n", __func__); + adsp_unicast(NULL, 0, MSG_GYRO, 0, MSG_TYPE_ST_SHOW_DATA); + + while (!(data->ready_flag[MSG_TYPE_ST_SHOW_DATA] & 1 << MSG_GYRO) && + cnt++ < TIMEOUT_CNT) + usleep_range(30000, 30100); /* 30 * 200 = 6 sec */ + + data->ready_flag[MSG_TYPE_ST_SHOW_DATA] &= ~(1 << MSG_GYRO); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); +#ifdef CONFIG_SEC_FACTORY + panic("sensor force crash : gyro selftest timeout\n"); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) || defined(CONFIG_SUPPORT_AK09973) ||\ + IS_ENABLED(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) || defined(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) + schedule_delayed_work(&data->lsm6dso_selftest_stop_work, msecs_to_jiffies(300)); +#endif + return snprintf(buf, PAGE_SIZE, + "0,0,0,0,0,0,0,0,0,0,0,0,%d,%d\n", + ST_FAIL, ST_FAIL); + } + + if (data->msg_buf[MSG_GYRO][1] != 0) { + pr_info("[FACTORY] %s - failed(%d, %d)\n", __func__, + data->msg_buf[MSG_GYRO][1], + data->msg_buf[MSG_GYRO][5]); + + pr_info("[FACTORY]: %s - %d,%d,%d\n", __func__, + data->msg_buf[MSG_GYRO][2], + data->msg_buf[MSG_GYRO][3], + data->msg_buf[MSG_GYRO][4]); + + if (data->msg_buf[MSG_GYRO][5] == OIS_ST_BIT_SET) + pr_info("[FACTORY] %s - OIS_ST_BIT fail\n", __func__); + else if (data->msg_buf[MSG_GYRO][5] == G_ZRL_DELTA_FAIL) + pr_info("[FACTORY] %s - ZRL Delta fail\n", __func__); + else if (data->msg_buf[MSG_GYRO][5] == OIS_RW_FAIL) + pr_info("[FACTORY] %s - Gyro OIS read write fail\n", __func__); + +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) || defined(CONFIG_SUPPORT_AK09973) ||\ + IS_ENABLED(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) || defined(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) + schedule_delayed_work(&data->lsm6dso_selftest_stop_work, msecs_to_jiffies(300)); +#endif + return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n", + data->msg_buf[MSG_GYRO][2], + data->msg_buf[MSG_GYRO][3], + data->msg_buf[MSG_GYRO][4]); + } else { + st_zro_res = ST_PASS; + } + + if (!data->msg_buf[MSG_GYRO][5]) + st_diff_res = ST_PASS; + else if (data->msg_buf[MSG_GYRO][5] == STARTUP_BIT_FAIL) + pr_info("[FACTORY] %s - Gyro Start Up Bit fail\n", __func__); + else if (data->msg_buf[MSG_GYRO][5] == OIS_RW_FAIL) { + pr_info("[FACTORY] %s - Gyro OIS read write fail\n", __func__); + st_diff_res = OIS_RW_FAIL; + } + else if (data->msg_buf[MSG_GYRO][5] == SFLP_FAIL) { + pr_info("[FACTORY] %s - SFLP sanity test fail\n", __func__); + st_diff_res = SFLP_FAIL; + } + + pr_info("[FACTORY] %s - %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", + __func__, + data->msg_buf[MSG_GYRO][2], data->msg_buf[MSG_GYRO][3], + data->msg_buf[MSG_GYRO][4], data->msg_buf[MSG_GYRO][6], + data->msg_buf[MSG_GYRO][7], data->msg_buf[MSG_GYRO][8], + data->msg_buf[MSG_GYRO][9], data->msg_buf[MSG_GYRO][10], + data->msg_buf[MSG_GYRO][11], data->msg_buf[MSG_GYRO][12], + data->msg_buf[MSG_GYRO][13], data->msg_buf[MSG_GYRO][14], + st_diff_res, st_zro_res); + +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) || defined(CONFIG_SUPPORT_AK09973) ||\ + IS_ENABLED(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) || defined(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) + schedule_delayed_work(&data->lsm6dso_selftest_stop_work, msecs_to_jiffies(300)); +#endif + return snprintf(buf, PAGE_SIZE, + "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", + data->msg_buf[MSG_GYRO][2], data->msg_buf[MSG_GYRO][3], + data->msg_buf[MSG_GYRO][4], data->msg_buf[MSG_GYRO][6], + data->msg_buf[MSG_GYRO][7], data->msg_buf[MSG_GYRO][8], + data->msg_buf[MSG_GYRO][9], data->msg_buf[MSG_GYRO][10], + data->msg_buf[MSG_GYRO][11], data->msg_buf[MSG_GYRO][12], + data->msg_buf[MSG_GYRO][13], data->msg_buf[MSG_GYRO][14], + st_diff_res, st_zro_res); +} + +static ssize_t trimmed_odr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + + adsp_unicast(NULL, 0, MSG_GYRO, 0, MSG_TYPE_GET_REGISTER); + + while (!(data->ready_flag[MSG_TYPE_GET_REGISTER] & 1 << MSG_GYRO) + && cnt++ < TIMEOUT_CNT) + msleep(20); + + data->ready_flag[MSG_TYPE_GET_REGISTER] &= ~(1 << MSG_GYRO); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "0\n"); + } + + pr_info("[FACTORY] %s: 0x63h = 0x%02x, trimmed_odr = %d Hz\n", __func__, + data->msg_buf[MSG_GYRO][0], data->msg_buf[MSG_GYRO][1]); + + return snprintf(buf, PAGE_SIZE, "%d\n", + data->msg_buf[MSG_GYRO][1]); +} + +static DEVICE_ATTR(name, 0444, gyro_name_show, NULL); +static DEVICE_ATTR(vendor, 0444, gyro_vendor_show, NULL); +static DEVICE_ATTR(selftest, 0440, gyro_selftest_show, NULL); +static DEVICE_ATTR(power_on, 0444, gyro_power_on, NULL); +static DEVICE_ATTR(power_off, 0444, gyro_power_off, NULL); +static DEVICE_ATTR(temperature, 0440, gyro_temp_show, NULL); +static DEVICE_ATTR(selftest_revised, 0440, selftest_revised_show, NULL); +static DEVICE_ATTR(trimmed_odr, 0440, trimmed_odr_show, NULL); + +static struct device_attribute *gyro_attrs[] = { + &dev_attr_name, + &dev_attr_vendor, + &dev_attr_selftest, + &dev_attr_power_on, + &dev_attr_power_off, + &dev_attr_temperature, + &dev_attr_selftest_revised, + &dev_attr_trimmed_odr, + NULL, +}; + +int __init lsm6dso_gyro_factory_init(void) +{ + adsp_factory_register(MSG_GYRO, gyro_attrs); + + pr_info("[FACTORY] %s\n", __func__); + + return 0; +} + +void __exit lsm6dso_gyro_factory_exit(void) +{ + adsp_factory_unregister(MSG_GYRO); + + pr_info("[FACTORY] %s\n", __func__); +} diff --git a/drivers/adsp_factory/lsm6dso_sub_accel.c b/drivers/adsp_factory/lsm6dso_sub_accel.c new file mode 100755 index 000000000000..658129adb30f --- /dev/null +++ b/drivers/adsp_factory/lsm6dso_sub_accel.c @@ -0,0 +1,686 @@ +/* + * Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include "adsp.h" +#define VENDOR "STM" +#if IS_ENABLED(CONFIG_LSM6DSV_FACTORY) +#define CHIP_ID "LSM6DSVW" +#else +#define CHIP_ID "LSM6DSOW" +#endif +#define ACCEL_ST_TRY_CNT 3 +#define ACCEL_FACTORY_CAL_CNT 20 +#define ACCEL_RAW_DATA_CNT 3 +#define MAX_ACCEL_1G 2048 +#define PASS 0 + +#define STM_LSM6DSO_INT_CHECK_RUNNING 4 + +struct sub_accel_data { + struct work_struct work_accel; + struct workqueue_struct *accel_wq; + struct adsp_data *dev_data; + bool is_complete_cal; + bool lpf_onoff; + bool st_complete; + int32_t raw_data[ACCEL_RAW_DATA_CNT]; + int32_t avg_data[ACCEL_RAW_DATA_CNT]; +}; + +static struct sub_accel_data *pdata; + +static ssize_t sub_accel_vendor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR); +} + +static ssize_t sub_accel_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", CHIP_ID); +} + +static ssize_t sensor_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", "ADSP"); +} + +int get_sub_accel_cal_data(struct adsp_data *data, int32_t *cal_data) +{ + uint8_t cnt = 0; + + adsp_unicast(NULL, 0, MSG_ACCEL_SUB, 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] & 1 << MSG_ACCEL_SUB) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << MSG_ACCEL_SUB); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return 0; + } + + if (data->msg_buf[MSG_ACCEL_SUB][3] != ACCEL_RAW_DATA_CNT) { + pr_err("[FACTORY] %s: Reading Bytes Num %d!!!\n", + __func__, data->msg_buf[MSG_ACCEL_SUB][3]); + return 0; + } + + cal_data[0] = data->msg_buf[MSG_ACCEL_SUB][0]; + cal_data[1] = data->msg_buf[MSG_ACCEL_SUB][1]; + cal_data[2] = data->msg_buf[MSG_ACCEL_SUB][2]; + + pr_info("[FACTORY] %s: %d, %d, %d, %d\n", __func__, + cal_data[0], cal_data[1], cal_data[2], + data->msg_buf[MSG_ACCEL_SUB][3]); + + return data->msg_buf[MSG_ACCEL_SUB][3]; +} + +void set_sub_accel_cal_data(struct adsp_data *data) +{ + uint8_t cnt = 0; + + pr_info("[FACTORY] %s: %d, %d, %d\n", __func__, pdata->avg_data[0], + pdata->avg_data[1], pdata->avg_data[2]); + adsp_unicast(pdata->avg_data, sizeof(pdata->avg_data), + MSG_ACCEL_SUB, 0, MSG_TYPE_SET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_SET_CAL_DATA] & 1 << MSG_ACCEL_SUB) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_SET_CAL_DATA] &= ~(1 << MSG_ACCEL_SUB); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + } else if (data->msg_buf[MSG_ACCEL_SUB][0] != ACCEL_RAW_DATA_CNT) { + pr_err("[FACTORY] %s: Write Bytes Num %d!!!\n", + __func__, data->msg_buf[MSG_ACCEL_SUB][0]); + } +} + +static ssize_t sub_accel_calibration_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int32_t cal_data[ACCEL_RAW_DATA_CNT] = {0, }; + int ret = 0; + + mutex_lock(&data->accel_factory_mutex); + ret = get_sub_accel_cal_data(data, cal_data); + mutex_unlock(&data->accel_factory_mutex); + if (ret > 0) { + pr_info("[FACTORY] %s: %d, %d, %d\n", __func__, + cal_data[0], cal_data[1], cal_data[2]); + if (cal_data[0] == 0 && cal_data[1] == 0 && cal_data[2] == 0) + return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d\n", + 0, 0, 0, 0); + else + return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d\n", + true, cal_data[0], cal_data[1], cal_data[2]); + } else { + pr_err("[FACTORY] %s: get_sub_accel_cal_data fail\n", __func__); + return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d\n", 0, 0, 0, 0); + } +} + +static ssize_t sub_accel_calibration_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + + pdata->dev_data = data; + if (sysfs_streq(buf, "0")) { + mutex_lock(&data->accel_factory_mutex); + memset(pdata->avg_data, 0, sizeof(pdata->avg_data)); + set_sub_accel_cal_data(data); + mutex_unlock(&data->accel_factory_mutex); + } else { + pdata->is_complete_cal = false; + queue_work(pdata->accel_wq, &pdata->work_accel); + while (pdata->is_complete_cal == false) { + pr_info("[FACTORY] %s: In factory cal\n", __func__); + msleep(20); + } + mutex_lock(&data->accel_factory_mutex); + set_sub_accel_cal_data(data); + mutex_unlock(&data->accel_factory_mutex); + } + + return size; +} + +static void sub_accel_work_func(struct work_struct *work) +{ + struct sub_accel_data *data = container_of((struct work_struct *)work, + struct sub_accel_data, work_accel); + int i; + + mutex_lock(&data->dev_data->accel_factory_mutex); + memset(pdata->avg_data, 0, sizeof(pdata->avg_data)); + adsp_unicast(pdata->avg_data, sizeof(pdata->avg_data), + MSG_ACCEL_SUB, 0, MSG_TYPE_SET_CAL_DATA); + msleep(30); /* for init of bias */ + for (i = 0; i < ACCEL_FACTORY_CAL_CNT; i++) { + msleep(20); + get_sub_accel_raw_data(pdata->raw_data); + pdata->avg_data[0] += pdata->raw_data[0]; + pdata->avg_data[1] += pdata->raw_data[1]; + pdata->avg_data[2] += pdata->raw_data[2]; + pr_info("[FACTORY] %s: %d, %d, %d\n", __func__, + pdata->raw_data[0], pdata->raw_data[1], + pdata->raw_data[2]); + } + + for (i = 0; i < ACCEL_RAW_DATA_CNT; i++) { + pdata->avg_data[i] /= ACCEL_FACTORY_CAL_CNT; + pr_info("[FACTORY] %s: avg : %d\n", + __func__, pdata->avg_data[i]); + } + + if (pdata->avg_data[2] > 0) + pdata->avg_data[2] -= MAX_ACCEL_1G; + else if (pdata->avg_data[2] < 0) + pdata->avg_data[2] += MAX_ACCEL_1G; + + mutex_unlock(&data->dev_data->accel_factory_mutex); + pdata->is_complete_cal = true; +} + +void sub_accel_cal_work_func(struct work_struct *work) +{ + struct adsp_data *data = container_of((struct delayed_work *)work, + struct adsp_data, sub_accel_cal_work); + int ret = 0; + + mutex_lock(&data->accel_factory_mutex); + ret = get_sub_accel_cal_data(data, pdata->avg_data); + mutex_unlock(&data->accel_factory_mutex); + if (ret > 0) { + pr_info("[FACTORY] %s: ret(%d) %d, %d, %d\n", __func__, ret, + pdata->avg_data[0], + pdata->avg_data[1], + pdata->avg_data[2]); + + mutex_lock(&data->accel_factory_mutex); + set_sub_accel_cal_data(data); + mutex_unlock(&data->accel_factory_mutex); + } else { + pr_err("[FACTORY] %s: get_accel_cal_data fail (%d)\n", + __func__, ret); + } +} + +static ssize_t sub_accel_selftest_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + int retry = 0; +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) || defined(CONFIG_SUPPORT_AK09973) + int msg_buf = LSM6DSO_SELFTEST_TRUE; + + adsp_unicast(&msg_buf, sizeof(msg_buf), + MSG_DIGITAL_HALL_ANGLE, 0, MSG_TYPE_OPTION_DEFINE); +#elif IS_ENABLED(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) || defined(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) + int msg_buf = LSM6DSO_SELFTEST_TRUE; + + adsp_unicast(&msg_buf, sizeof(msg_buf), + MSG_REF_ANGLE, 0, MSG_TYPE_OPTION_DEFINE); +#endif + + pdata->st_complete = false; +RETRY_ACCEL_SELFTEST: + adsp_unicast(NULL, 0, MSG_ACCEL_SUB, 0, MSG_TYPE_ST_SHOW_DATA); + + while (!(data->ready_flag[MSG_TYPE_ST_SHOW_DATA] & 1 << MSG_ACCEL_SUB) && + cnt++ < TIMEOUT_CNT) + msleep(26); + + data->ready_flag[MSG_TYPE_ST_SHOW_DATA] &= ~(1 << MSG_ACCEL_SUB); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + data->msg_buf[MSG_ACCEL_SUB][1] = -1; +#ifdef CONFIG_SEC_FACTORY + panic("sensor force crash : sub accel selftest timeout\n"); +#endif + } + + pr_info("[FACTORY] %s : init = %d, result = %d, XYZ = %d, %d, %d, nXYZ = %d, %d, %d\n", + __func__, data->msg_buf[MSG_ACCEL_SUB][0], + data->msg_buf[MSG_ACCEL_SUB][1], data->msg_buf[MSG_ACCEL_SUB][2], + data->msg_buf[MSG_ACCEL_SUB][3], data->msg_buf[MSG_ACCEL_SUB][4], + data->msg_buf[MSG_ACCEL_SUB][5], data->msg_buf[MSG_ACCEL_SUB][6], + data->msg_buf[MSG_ACCEL_SUB][7]); + + pr_info("[FACTORY] %s : pre/postP/postN [%d, %d, %d/%d, %d, %d/%d, %d, %d], comm_err_cnt %d/%d/%d\n", + __func__, data->msg_buf[MSG_ACCEL_SUB][8], + data->msg_buf[MSG_ACCEL_SUB][9], data->msg_buf[MSG_ACCEL_SUB][10], + data->msg_buf[MSG_ACCEL_SUB][11], data->msg_buf[MSG_ACCEL_SUB][12], + data->msg_buf[MSG_ACCEL_SUB][13], data->msg_buf[MSG_ACCEL_SUB][14], + data->msg_buf[MSG_ACCEL_SUB][15], data->msg_buf[MSG_ACCEL_SUB][16], + data->msg_buf[MSG_ACCEL_SUB][17], data->msg_buf[MSG_ACCEL_SUB][18], + data->msg_buf[MSG_ACCEL_SUB][19]); + + if (data->msg_buf[MSG_ACCEL_SUB][1] == 1) { + pr_info("[FACTORY] %s : Pass - result = %d, retry = %d\n", + __func__, data->msg_buf[MSG_ACCEL_SUB][1], retry); + } else { + data->msg_buf[MSG_ACCEL_SUB][1] = -5; + pr_err("[FACTORY] %s : Fail - result = %d, retry = %d\n", + __func__, data->msg_buf[MSG_ACCEL_SUB][1], retry); + + if (retry < ACCEL_ST_TRY_CNT) { + retry++; + msleep(200); + cnt = 0; + pr_info("[FACTORY] %s: retry\n", __func__); + goto RETRY_ACCEL_SELFTEST; + } + } + + pdata->st_complete = true; + +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) || defined(CONFIG_SUPPORT_AK09973) ||\ + IS_ENABLED(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) || defined(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) + schedule_delayed_work(&data->lsm6dso_selftest_stop_work, msecs_to_jiffies(300)); +#endif + + return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d,%d,%d,%d\n", + data->msg_buf[MSG_ACCEL_SUB][1], + (int)abs(data->msg_buf[MSG_ACCEL_SUB][2]), + (int)abs(data->msg_buf[MSG_ACCEL_SUB][3]), + (int)abs(data->msg_buf[MSG_ACCEL_SUB][4]), + (int)abs(data->msg_buf[MSG_ACCEL_SUB][5]), + (int)abs(data->msg_buf[MSG_ACCEL_SUB][6]), + (int)abs(data->msg_buf[MSG_ACCEL_SUB][7])); +} + +static ssize_t sub_accel_raw_data_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int32_t raw_data[ACCEL_RAW_DATA_CNT] = {0, }; + static int32_t prev_raw_data[ACCEL_RAW_DATA_CNT] = {0, }; + int ret = 0; +#ifdef CONFIG_SEC_FACTORY + static int same_cnt; +#endif + + if (pdata->st_complete == false) { + pr_info("[FACTORY] %s: selftest is running\n", __func__); + return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n", + raw_data[0], raw_data[1], raw_data[2]); + } + + mutex_lock(&data->accel_factory_mutex); + ret = get_sub_accel_raw_data(raw_data); + mutex_unlock(&data->accel_factory_mutex); + +#ifdef CONFIG_SEC_FACTORY + pr_info("[FACTORY] %s: %d, %d, %d\n", __func__, + raw_data[0], raw_data[1], raw_data[2]); + + if (prev_raw_data[0] == raw_data[0] && + prev_raw_data[1] == raw_data[1] && + prev_raw_data[2] == raw_data[2]) { + same_cnt++; + pr_info("[FACTORY] %s: same_cnt %d\n", __func__, same_cnt); + if (same_cnt >= 20) + panic("sensor force crash : sub accel raw_data stuck\n"); + } else + same_cnt = 0; +#endif + + if (!ret) { + memcpy(prev_raw_data, raw_data, sizeof(int32_t) * 3); + } else if (!pdata->lpf_onoff) { + pr_err("[FACTORY] %s: using prev data!!!\n", __func__); + memcpy(raw_data, prev_raw_data, sizeof(int32_t) * 3); + } else { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + } + + return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n", + raw_data[0], raw_data[1], raw_data[2]); +} + +static ssize_t sub_accel_reactive_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + bool success = false; + int32_t msg_buf = 0; + + mutex_lock(&data->accel_factory_mutex); + adsp_unicast(&msg_buf, sizeof(int32_t), MSG_ACCEL_SUB, + 0, MSG_TYPE_GET_REGISTER); + + while (!(data->ready_flag[MSG_TYPE_GET_REGISTER] & 1 << MSG_ACCEL_SUB) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_REGISTER] &= ~(1 << MSG_ACCEL_SUB); + mutex_unlock(&data->accel_factory_mutex); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "%d\n", (int)success); + } + + pr_info("[FACTORY]: %s - %d\n", __func__, + data->msg_buf[MSG_ACCEL_SUB][0]); + + if (data->msg_buf[MSG_ACCEL_SUB][0] == 0) + success = true; + else + panic("sensor sub accel interrupt check fail!!"); + + return snprintf(buf, PAGE_SIZE, "%d\n", (int)success); +} + +static ssize_t sub_accel_reactive_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int32_t msg_buf; + uint8_t cnt = 0; + + if (sysfs_streq(buf, "1")) + pr_info("[FACTORY]: %s - on\n", __func__); + else if (sysfs_streq(buf, "0")) + pr_info("[FACTORY]: %s - off\n", __func__); + else if (sysfs_streq(buf, "2")) { + pr_info("[FACTORY]: %s - factory\n", __func__); + msg_buf = 1; + + mutex_lock(&data->accel_factory_mutex); + adsp_unicast(&msg_buf, sizeof(int32_t), MSG_ACCEL_SUB, + 0, MSG_TYPE_GET_REGISTER); + + while (!(data->ready_flag[MSG_TYPE_GET_REGISTER] & 1 << MSG_ACCEL_SUB) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_REGISTER] &= ~(1 << MSG_ACCEL_SUB); + mutex_unlock(&data->accel_factory_mutex); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return size; + } + + if (data->msg_buf[MSG_ACCEL_SUB][0] == STM_LSM6DSO_INT_CHECK_RUNNING) + pr_info("[FACTORY]: %s - STM_LSM6DSO_INT_CHECK_RUNNING\n", __func__); + else + pr_info("[FACTORY]: %s - Something wrong\n", __func__); + } + + return size; +} + +static ssize_t sub_accel_lowpassfilter_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + int32_t msg_buf; + + if (sysfs_streq(buf, "1")) { + msg_buf = 1; + } else if (sysfs_streq(buf, "0")) { + msg_buf = 0; +#ifdef CONFIG_SEC_FACTORY + } else if (sysfs_streq(buf, "2")) { + msg_buf = 2; + pr_info("[FACTORY] %s: Pretest\n", __func__); +#endif + } else { + pr_info("[FACTORY] %s: wrong value\n", __func__); + return size; + } + + mutex_lock(&data->accel_factory_mutex); + adsp_unicast(&msg_buf, sizeof(int32_t), MSG_ACCEL_SUB, + 0, MSG_TYPE_SET_ACCEL_LPF); + + while (!(data->ready_flag[MSG_TYPE_SET_ACCEL_LPF] & 1 << MSG_ACCEL_SUB) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_SET_ACCEL_LPF] &= ~(1 << MSG_ACCEL_SUB); + mutex_unlock(&data->accel_factory_mutex); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return size; + } + + pdata->lpf_onoff = (bool)data->msg_buf[MSG_ACCEL_SUB][0]; + +#if IS_ENABLED(CONFIG_LSM6DSV_FACTORY) + pr_info("[FACTORY] %s: %d, 0x0A:%02x 0x0D:%02x 0x18:%02x\n", __func__, + data->msg_buf[MSG_ACCEL][0], data->msg_buf[MSG_ACCEL][1], + data->msg_buf[MSG_ACCEL][2], data->msg_buf[MSG_ACCEL][3]); +#else + pr_info("[FACTORY] %s: %d, 0x0A:%02x 0x0D:%02x 0x10:%02x\n", __func__, + data->msg_buf[MSG_ACCEL_SUB][0], data->msg_buf[MSG_ACCEL_SUB][1], + data->msg_buf[MSG_ACCEL_SUB][2], data->msg_buf[MSG_ACCEL_SUB][3]); +#endif + + return size; +} + +static ssize_t sub_accel_dhr_sensor_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + char ctrl1_xl = 0; + uint8_t fullscale = 0; + + adsp_unicast(NULL, 0, MSG_ACCEL_SUB, 0, MSG_TYPE_GET_DHR_INFO); + while (!(data->ready_flag[MSG_TYPE_GET_DHR_INFO] & 1 << MSG_ACCEL_SUB) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_DHR_INFO] &= ~(1 << MSG_ACCEL_SUB); + + if (cnt >= TIMEOUT_CNT) + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + + ctrl1_xl = data->msg_buf[MSG_ACCEL_SUB][16]; + + ctrl1_xl &= 0xC; + + switch (ctrl1_xl) { + case 0xC: + fullscale = 8; + break; + case 0x8: + fullscale = 4; + break; + case 0x4: + fullscale = 16; + break; + case 0: + fullscale = 2; + break; + default: + break; + } + + pr_info("[FACTORY] %s: f/s %u\n", __func__, fullscale); + + return snprintf(buf, PAGE_SIZE, "\"FULL_SCALE\":\"%uG\"\n", fullscale); +} + +#if IS_ENABLED(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) || defined(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) +static ssize_t ref_angle_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + int32_t result = PASS; + + mutex_lock(&data->accel_factory_mutex); + adsp_unicast(NULL, 0, MSG_REF_ANGLE, 0, MSG_TYPE_GET_RAW_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & 1 << MSG_REF_ANGLE) && + cnt++ < TIMEOUT_CNT) + msleep(20); + + data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_REF_ANGLE); + mutex_unlock(&data->accel_factory_mutex); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "-1\n"); + } + + pr_info("[FACTORY] %s - st %d/%d, akm %d/%d, lf %d/%d, hall %d/%d/%d(uT)\n", + __func__, data->msg_buf[MSG_REF_ANGLE][0], + data->msg_buf[MSG_REF_ANGLE][1], + data->msg_buf[MSG_REF_ANGLE][2], + data->msg_buf[MSG_REF_ANGLE][3], + data->msg_buf[MSG_REF_ANGLE][4], + data->msg_buf[MSG_REF_ANGLE][5], + data->msg_buf[MSG_REF_ANGLE][6], + data->msg_buf[MSG_REF_ANGLE][7], + data->msg_buf[MSG_REF_ANGLE][8]); + + return snprintf(buf, PAGE_SIZE, "%d,%d\n", + data->msg_buf[MSG_REF_ANGLE][0], result); +} + +static ssize_t angle_read_data_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + + mutex_lock(&data->accel_factory_mutex); + adsp_unicast(NULL, 0, MSG_REF_ANGLE, 0, MSG_TYPE_GET_RAW_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & 1 << MSG_REF_ANGLE) && + cnt++ < TIMEOUT_CNT) + msleep(20); + + data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_REF_ANGLE); + mutex_unlock(&data->accel_factory_mutex); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "-1\n"); + } + + pr_info("[FACTORY] %s - st %d/%d, akm %d/%d, lf %d/%d, hall %d/%d/%d(uT)\n", + __func__, data->msg_buf[MSG_REF_ANGLE][0], + data->msg_buf[MSG_REF_ANGLE][1], + data->msg_buf[MSG_REF_ANGLE][2], + data->msg_buf[MSG_REF_ANGLE][3], + data->msg_buf[MSG_REF_ANGLE][4], + data->msg_buf[MSG_REF_ANGLE][5], + data->msg_buf[MSG_REF_ANGLE][6], + data->msg_buf[MSG_REF_ANGLE][7], + data->msg_buf[MSG_REF_ANGLE][8]); + + return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d,%d,%d,%d,%d,%d\n", + data->msg_buf[MSG_REF_ANGLE][0], + data->msg_buf[MSG_REF_ANGLE][1], + data->msg_buf[MSG_REF_ANGLE][2], + data->msg_buf[MSG_REF_ANGLE][3], + data->msg_buf[MSG_REF_ANGLE][4], + data->msg_buf[MSG_REF_ANGLE][5], + data->msg_buf[MSG_REF_ANGLE][6], + data->msg_buf[MSG_REF_ANGLE][7], + data->msg_buf[MSG_REF_ANGLE][8]); +} +#endif + +static DEVICE_ATTR(name, 0444, sub_accel_name_show, NULL); +static DEVICE_ATTR(vendor, 0444, sub_accel_vendor_show, NULL); +static DEVICE_ATTR(type, 0444, sensor_type_show, NULL); +static DEVICE_ATTR(calibration, 0664, + sub_accel_calibration_show, sub_accel_calibration_store); +static DEVICE_ATTR(selftest, 0440, + sub_accel_selftest_show, NULL); +static DEVICE_ATTR(raw_data, 0444, sub_accel_raw_data_show, NULL); +static DEVICE_ATTR(reactive_alert, 0664, + sub_accel_reactive_show, sub_accel_reactive_store); +static DEVICE_ATTR(lowpassfilter, 0220, + NULL, sub_accel_lowpassfilter_store); +#ifdef CONFIG_SEC_FACTORY +static DEVICE_ATTR(dhr_sensor_info, 0444, + sub_accel_dhr_sensor_info_show, NULL); +#else +static DEVICE_ATTR(dhr_sensor_info, 0440, + sub_accel_dhr_sensor_info_show, NULL); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) || defined(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) +static DEVICE_ATTR(ref_angle, 0444, ref_angle_show, NULL); +static DEVICE_ATTR(read_angle_data, 0444, angle_read_data_show, NULL); +#endif + +static struct device_attribute *acc_attrs[] = { + &dev_attr_name, + &dev_attr_vendor, + &dev_attr_type, + &dev_attr_calibration, + &dev_attr_selftest, + &dev_attr_raw_data, + &dev_attr_reactive_alert, + &dev_attr_lowpassfilter, + &dev_attr_dhr_sensor_info, +#if IS_ENABLED(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) || defined(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) + &dev_attr_ref_angle, + &dev_attr_read_angle_data, +#endif + NULL, +}; + +void sub_accel_factory_init_work(struct adsp_data *data) +{ + schedule_delayed_work(&data->sub_accel_cal_work, msecs_to_jiffies(8000)); +} + +int __init lsm6dso_sub_accel_factory_init(void) +{ + adsp_factory_register(MSG_ACCEL_SUB, acc_attrs); + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + pdata->accel_wq = create_singlethread_workqueue("sub_accel_wq"); + INIT_WORK(&pdata->work_accel, sub_accel_work_func); + + pdata->lpf_onoff = true; + pdata->st_complete = true; + pr_info("[FACTORY] %s\n", __func__); + + return 0; +} + +void __exit lsm6dso_sub_accel_factory_exit(void) +{ + adsp_factory_unregister(MSG_ACCEL_SUB); + pr_info("[FACTORY] %s\n", __func__); +} diff --git a/drivers/adsp_factory/lsm6dso_sub_gyro.c b/drivers/adsp_factory/lsm6dso_sub_gyro.c new file mode 100755 index 000000000000..bf24ecc960f3 --- /dev/null +++ b/drivers/adsp_factory/lsm6dso_sub_gyro.c @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include "adsp.h" +#define VENDOR "STM" +#if IS_ENABLED(CONFIG_LSM6DSV_FACTORY) +#define CHIP_ID "LSM6DSVW" +#else +#define CHIP_ID "LSM6DSOW" +#endif +#define ST_PASS 1 +#define ST_FAIL 0 +#define STARTUP_BIT_FAIL 2 +#define G_ZRL_DELTA_FAIL 4 +#define SFLP_FAIL 6 +#define SELFTEST_REVISED 1 + +static ssize_t sub_gyro_vendor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR); +} + +static ssize_t sub_gyro_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", CHIP_ID); +} + +static ssize_t selftest_revised_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", SELFTEST_REVISED); +} + +static ssize_t gyro_power_off(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("[FACTORY]: %s\n", __func__); + + return snprintf(buf, PAGE_SIZE, "%d\n", 1); +} + +static ssize_t gyro_power_on(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("[FACTORY]: %s\n", __func__); + + return snprintf(buf, PAGE_SIZE, "%d\n", 1); +} + +static ssize_t sub_gyro_temp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + + adsp_unicast(NULL, 0, MSG_GYRO_SUB_TEMP, 0, MSG_TYPE_GET_RAW_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & 1 << MSG_GYRO_SUB_TEMP) + && cnt++ < TIMEOUT_CNT) + msleep(20); + + data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_GYRO_SUB_TEMP); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "-99\n"); + } + + pr_info("[FACTORY] %s: sub_gyro_temp = %d\n", __func__, + data->msg_buf[MSG_GYRO_SUB_TEMP][0]); + + return snprintf(buf, PAGE_SIZE, "%d\n", + data->msg_buf[MSG_GYRO_SUB_TEMP][0]); +} + +static ssize_t sub_gyro_selftest_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + int st_diff_res = ST_FAIL; + int st_zro_res = ST_FAIL; +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) || defined(CONFIG_SUPPORT_AK09973) + int msg_buf = LSM6DSO_SELFTEST_TRUE; + + adsp_unicast(&msg_buf, sizeof(msg_buf), + MSG_DIGITAL_HALL_ANGLE, 0, MSG_TYPE_OPTION_DEFINE); +#elif IS_ENABLED(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) || defined(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) + int msg_buf = LSM6DSO_SELFTEST_TRUE; + + adsp_unicast(&msg_buf, sizeof(msg_buf), + MSG_REF_ANGLE, 0, MSG_TYPE_OPTION_DEFINE); +#endif + + pr_info("[FACTORY] %s - start", __func__); + adsp_unicast(NULL, 0, MSG_GYRO_SUB, 0, MSG_TYPE_ST_SHOW_DATA); + + while (!(data->ready_flag[MSG_TYPE_ST_SHOW_DATA] & 1 << MSG_GYRO_SUB) && + cnt++ < TIMEOUT_CNT) + usleep_range(30000, 30100); /* 30 * 200 = 6 sec */ + + data->ready_flag[MSG_TYPE_ST_SHOW_DATA] &= ~(1 << MSG_GYRO_SUB); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); +#ifdef CONFIG_SEC_FACTORY + panic("sensor force crash : sub gyro selftest timeout\n"); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) || defined(CONFIG_SUPPORT_AK09973) ||\ + IS_ENABLED(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) || defined(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) + schedule_delayed_work(&data->lsm6dso_selftest_stop_work, msecs_to_jiffies(300)); +#endif + return snprintf(buf, PAGE_SIZE, + "0,0,0,0,0,0,0,0,0,0,0,0,%d,%d\n", + ST_FAIL, ST_FAIL); + } + + if (data->msg_buf[MSG_GYRO_SUB][1] != 0) { + pr_info("[FACTORY] %s - failed(%d, %d)\n", __func__, + data->msg_buf[MSG_GYRO_SUB][1], + data->msg_buf[MSG_GYRO_SUB][5]); + + pr_info("[FACTORY]: %s - %d,%d,%d\n", __func__, + data->msg_buf[MSG_GYRO_SUB][2], + data->msg_buf[MSG_GYRO_SUB][3], + data->msg_buf[MSG_GYRO_SUB][4]); + +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) || defined(CONFIG_SUPPORT_AK09973) ||\ + IS_ENABLED(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) || defined(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) + schedule_delayed_work(&data->lsm6dso_selftest_stop_work, msecs_to_jiffies(300)); +#endif + + if (data->msg_buf[MSG_GYRO_SUB][5] == G_ZRL_DELTA_FAIL) + pr_info("[FACTORY] %s - ZRL Delta fail\n", __func__); + return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n", + data->msg_buf[MSG_GYRO_SUB][2], + data->msg_buf[MSG_GYRO_SUB][3], + data->msg_buf[MSG_GYRO_SUB][4]); + } else { + st_zro_res = ST_PASS; + } + + if (!data->msg_buf[MSG_GYRO_SUB][5]) + st_diff_res = ST_PASS; + else if (data->msg_buf[MSG_GYRO_SUB][5] == STARTUP_BIT_FAIL) + pr_info("[FACTORY] %s - Gyro Start Up Bit fail\n", __func__); + else if (data->msg_buf[MSG_GYRO_SUB][5] == SFLP_FAIL) { + pr_info("[FACTORY] %s - SFLP sanity test fail\n", __func__); + st_diff_res = SFLP_FAIL; + } + + pr_info("[FACTORY]: %s - %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", + __func__, + data->msg_buf[MSG_GYRO_SUB][2], data->msg_buf[MSG_GYRO_SUB][3], + data->msg_buf[MSG_GYRO_SUB][4], data->msg_buf[MSG_GYRO_SUB][6], + data->msg_buf[MSG_GYRO_SUB][7], data->msg_buf[MSG_GYRO_SUB][8], + data->msg_buf[MSG_GYRO_SUB][9], data->msg_buf[MSG_GYRO_SUB][10], + data->msg_buf[MSG_GYRO_SUB][11], data->msg_buf[MSG_GYRO_SUB][12], + data->msg_buf[MSG_GYRO_SUB][13], data->msg_buf[MSG_GYRO_SUB][14], + st_diff_res, st_zro_res); + +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) || defined(CONFIG_SUPPORT_AK09973) ||\ + IS_ENABLED(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) || defined(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) + schedule_delayed_work(&data->lsm6dso_selftest_stop_work, msecs_to_jiffies(300)); +#endif + + return snprintf(buf, PAGE_SIZE, + "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", + data->msg_buf[MSG_GYRO_SUB][2], data->msg_buf[MSG_GYRO_SUB][3], + data->msg_buf[MSG_GYRO_SUB][4], data->msg_buf[MSG_GYRO_SUB][6], + data->msg_buf[MSG_GYRO_SUB][7], data->msg_buf[MSG_GYRO_SUB][8], + data->msg_buf[MSG_GYRO_SUB][9], data->msg_buf[MSG_GYRO_SUB][10], + data->msg_buf[MSG_GYRO_SUB][11], data->msg_buf[MSG_GYRO_SUB][12], + data->msg_buf[MSG_GYRO_SUB][13], data->msg_buf[MSG_GYRO_SUB][14], + st_diff_res, st_zro_res); +} + +static DEVICE_ATTR(name, 0444, sub_gyro_name_show, NULL); +static DEVICE_ATTR(vendor, 0444, sub_gyro_vendor_show, NULL); +static DEVICE_ATTR(selftest, 0440, sub_gyro_selftest_show, NULL); +static DEVICE_ATTR(power_on, 0444, gyro_power_on, NULL); +static DEVICE_ATTR(power_off, 0444, gyro_power_off, NULL); +static DEVICE_ATTR(temperature, 0440, sub_gyro_temp_show, NULL); +static DEVICE_ATTR(selftest_revised, 0440, selftest_revised_show, NULL); + +static struct device_attribute *gyro_attrs[] = { + &dev_attr_name, + &dev_attr_vendor, + &dev_attr_selftest, + &dev_attr_power_on, + &dev_attr_power_off, + &dev_attr_temperature, + &dev_attr_selftest_revised, + NULL, +}; + +int __init lsm6dso_sub_gyro_factory_init(void) +{ + adsp_factory_register(MSG_GYRO_SUB, gyro_attrs); + + pr_info("[FACTORY] %s\n", __func__); + + return 0; +} + +void __exit lsm6dso_sub_gyro_factory_exit(void) +{ + adsp_factory_unregister(MSG_GYRO_SUB); + + pr_info("[FACTORY] %s\n", __func__); +} diff --git a/drivers/adsp_factory/pressure_factory.c b/drivers/adsp_factory/pressure_factory.c new file mode 100755 index 000000000000..bec52081eab8 --- /dev/null +++ b/drivers/adsp_factory/pressure_factory.c @@ -0,0 +1,436 @@ +/* + * Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include "adsp.h" + +#define CALIBRATION_FILE_PATH "/efs/FactoryApp/baro_delta" + +#define PR_MAX 8388607 /* 24 bit 2'compl */ +#define PR_MIN -8388608 +#define SNS_SUCCESS 0 +#define ST_PASS 1 +#define ST_FAIL 0 + +#define PRESS_DEVICE_LIST_MAX 5 + +enum { + OPTION_TYPE_PRESS_GET_DEVICE_ID, + OPTION_TYPE_PRESS_MAX +}; + +static int sea_level_pressure; +static int pressure_cal; + +static const struct device_id_t press_device_list[PRESS_DEVICE_LIST_MAX] = { + /* ID, Vendor, Name */ + {0x00, "Unknown", "Unknown"}, + {0xB1, "STM", "LPS22HB"}, + {0xB3, "STM", "LPS22HH"}, + {0xB4, "STM", "LPS22DF"}, + {0x50, "Bosch", "BMP580"} +}; + +static void press_get_device_id(struct adsp_data *data) +{ + int32_t cmd = OPTION_TYPE_PRESS_GET_DEVICE_ID, i; + int32_t device_index = UNKNOWN_INDEX; + uint8_t cnt = 0, device_id = 0; + + adsp_unicast(&cmd, sizeof(cmd), MSG_PRESSURE, 0, MSG_TYPE_OPTION_DEFINE); + + while (!(data->ready_flag[MSG_TYPE_OPTION_DEFINE] + & 1 << MSG_PRESSURE) && cnt++ < TIMEOUT_CNT) + usleep_range(1000, 1100); + + data->ready_flag[MSG_TYPE_OPTION_DEFINE] &= ~(1 << MSG_PRESSURE); + + if (cnt >= TIMEOUT_CNT) + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + else + device_id = (uint8_t)data->msg_buf[MSG_PRESSURE][2]; + pr_err("[FACTORY] %s: device_id : %d,%d,%d,%d\n", __func__, device_id, + (int)data->msg_buf[MSG_PRESSURE][0], + (int)data->msg_buf[MSG_PRESSURE][1], + (int)data->msg_buf[MSG_PRESSURE][2]); + + if (device_id == 0) { + pr_err("[FACTORY] %s: No information\n", __func__); + } else { + for (i = 0; i < PRESS_DEVICE_LIST_MAX; i++) + if (device_id == press_device_list[i].device_id) + break; + if (i >= PRESS_DEVICE_LIST_MAX) + pr_err("[FACTORY] %s: Unknown ID - (0x%x)\n", + __func__, device_id); + else + device_index = i; + } + + memcpy(data->press_device_vendor, + press_device_list[device_index].device_vendor, + sizeof(char) * DEVICE_INFO_LENGTH); + memcpy(data->press_device_name, + press_device_list[device_index].device_name, + sizeof(char) * DEVICE_INFO_LENGTH); + + pr_info("[FACTORY] %s: Device ID - %s(%s)\n", __func__, + data->press_device_name, data->press_device_vendor); +} + +static ssize_t pressure_vendor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + if (!strcmp(data->press_device_vendor, press_device_list[0].device_vendor)) + press_get_device_id(data); + + return snprintf(buf, PAGE_SIZE, "%s\n", data->press_device_vendor); +} + +static ssize_t pressure_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + if (!strcmp(data->press_device_name, press_device_list[0].device_name)) + press_get_device_id(data); + + return snprintf(buf, PAGE_SIZE, "%s\n", data->press_device_name); +} + +static ssize_t sea_level_pressure_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", sea_level_pressure); +} + +static ssize_t sea_level_pressure_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + if (sscanf(buf, "%10d", &sea_level_pressure) != 1) { + pr_err("[FACTORY] %s: sscanf error\n", __func__); + return size; + } + + sea_level_pressure = sea_level_pressure / 100; + + pr_info("[FACTORY] %s: sea_level_pressure = %d\n", __func__, + sea_level_pressure); + + return size; +} + +/* +int pressure_open_calibration(struct adsp_data *data) +{ + int error = 0; + + return error; +} +*/ + +static ssize_t pressure_cabratioin_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + + schedule_delayed_work(&data->pressure_cal_work, 0); + return size; +} + +static ssize_t pressure_cabratioin_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + //struct adsp_data *data = dev_get_drvdata(dev); + + //pressure_open_calibration(data); + + return snprintf(buf, PAGE_SIZE, "%d\n", pressure_cal); +} + +static ssize_t temperature_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + + adsp_unicast(NULL, 0, MSG_PRESSURE_TEMP, 0, MSG_TYPE_GET_RAW_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & + 1 << MSG_PRESSURE_TEMP) && cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_PRESSURE_TEMP); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "-99\n"); + } + return snprintf(buf, PAGE_SIZE, "%d\n", + data->msg_buf[MSG_PRESSURE_TEMP][0]); +} + +static ssize_t selftest_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + + adsp_unicast(NULL, 0, MSG_PRESSURE, 0, MSG_TYPE_ST_SHOW_DATA); + + while (!(data->ready_flag[MSG_TYPE_ST_SHOW_DATA] & + 1 << MSG_PRESSURE) && cnt++ < TIMEOUT_CNT) + msleep(26); + + data->ready_flag[MSG_TYPE_ST_SHOW_DATA] &= ~(1 << MSG_PRESSURE); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "0\n"); + } + + pr_info("[FACTORY] %s : P:%d, T:%d, RES:%d\n", + __func__, data->msg_buf[MSG_PRESSURE][0], + data->msg_buf[MSG_PRESSURE][1], data->msg_buf[MSG_PRESSURE][2]); + + if (SNS_SUCCESS == data->msg_buf[MSG_PRESSURE][2]) + return snprintf(buf, PAGE_SIZE, "%d\n", ST_PASS); + else + return snprintf(buf, PAGE_SIZE, "%d\n", ST_FAIL); +} + +static ssize_t pressure_dhr_sensor_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + int i = 0; + + adsp_unicast(NULL, 0, MSG_PRESSURE, 0, MSG_TYPE_GET_DHR_INFO); + while (!(data->ready_flag[MSG_TYPE_GET_DHR_INFO] & 1 << MSG_PRESSURE) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_DHR_INFO] &= ~(1 << MSG_PRESSURE); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + } else { + for (i = 0; i < 8; i++) { + pr_info("[FACTORY] %s - %02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x\n", + __func__, + data->msg_buf[MSG_PRESSURE][i * 16 + 0], + data->msg_buf[MSG_PRESSURE][i * 16 + 1], + data->msg_buf[MSG_PRESSURE][i * 16 + 2], + data->msg_buf[MSG_PRESSURE][i * 16 + 3], + data->msg_buf[MSG_PRESSURE][i * 16 + 4], + data->msg_buf[MSG_PRESSURE][i * 16 + 5], + data->msg_buf[MSG_PRESSURE][i * 16 + 6], + data->msg_buf[MSG_PRESSURE][i * 16 + 7], + data->msg_buf[MSG_PRESSURE][i * 16 + 8], + data->msg_buf[MSG_PRESSURE][i * 16 + 9], + data->msg_buf[MSG_PRESSURE][i * 16 + 10], + data->msg_buf[MSG_PRESSURE][i * 16 + 11], + data->msg_buf[MSG_PRESSURE][i * 16 + 12], + data->msg_buf[MSG_PRESSURE][i * 16 + 13], + data->msg_buf[MSG_PRESSURE][i * 16 + 14], + data->msg_buf[MSG_PRESSURE][i * 16 + 15]); + } + } + + return snprintf(buf, PAGE_SIZE, "%s\n", "Done"); +} + +static ssize_t pressure_sw_offset_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + int input = 0; + int sw_offset = 0; + int ret; + + ret = kstrtoint(buf, 10, &input); + if (ret < 0) { + pr_err("[FACTORY] %s: kstrtoint fail\n", __func__); + return -EINVAL; + } + + pr_info("[FACTORY] %s: write value = %d\n", __func__, input); + + adsp_unicast(&input, sizeof(int), + MSG_PRESSURE, 0, MSG_TYPE_SET_THRESHOLD); + + while (!(data->ready_flag[MSG_TYPE_SET_THRESHOLD] & 1 << MSG_PRESSURE) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_SET_THRESHOLD] &= ~(1 << MSG_PRESSURE); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + } else { + sw_offset = data->msg_buf[MSG_PRESSURE][0]; + } + + pr_info("[FACTORY] %s: sw_offset %d\n", __func__, sw_offset); + + return size; +} + +static ssize_t pressure_sw_offset_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + + adsp_unicast(NULL, 0, MSG_PRESSURE, 0, MSG_TYPE_GET_THRESHOLD); + + while (!(data->ready_flag[MSG_TYPE_GET_THRESHOLD] & + 1 << MSG_PRESSURE) && cnt++ < TIMEOUT_CNT) + msleep(20); + + data->ready_flag[MSG_TYPE_GET_THRESHOLD] &= ~(1 << MSG_PRESSURE); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "0\n"); + } + + pr_info("[FACTORY] %s : sw_offset %d\n", + __func__, data->msg_buf[MSG_PRESSURE][0]); + + return snprintf(buf, PAGE_SIZE, "%d\n", data->msg_buf[MSG_PRESSURE][0]); +} + +static ssize_t pressure_esn_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + + adsp_unicast(NULL, 0, MSG_PRESSURE, 0, MSG_TYPE_GET_REGISTER); + + while (!(data->ready_flag[MSG_TYPE_GET_REGISTER] & + 1 << MSG_PRESSURE) && cnt++ < TIMEOUT_CNT) + msleep(20); + + data->ready_flag[MSG_TYPE_GET_REGISTER] &= ~(1 << MSG_PRESSURE); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "0\n"); + } + + pr_info("[FACTORY] %s : esn %02X%02X%02X%02X%02X%02X%02X%02X\n", + __func__, data->msg_buf[MSG_PRESSURE][0], data->msg_buf[MSG_PRESSURE][1], + data->msg_buf[MSG_PRESSURE][2], data->msg_buf[MSG_PRESSURE][3], + data->msg_buf[MSG_PRESSURE][4], data->msg_buf[MSG_PRESSURE][5], + data->msg_buf[MSG_PRESSURE][6], data->msg_buf[MSG_PRESSURE][7]); + + return snprintf(buf, PAGE_SIZE, "%02X%02X%02X%02X%02X%02X%02X%02X\n", + data->msg_buf[MSG_PRESSURE][0], data->msg_buf[MSG_PRESSURE][1], + data->msg_buf[MSG_PRESSURE][2], data->msg_buf[MSG_PRESSURE][3], + data->msg_buf[MSG_PRESSURE][4], data->msg_buf[MSG_PRESSURE][5], + data->msg_buf[MSG_PRESSURE][6], data->msg_buf[MSG_PRESSURE][7]); +} + +static DEVICE_ATTR(vendor, 0444, pressure_vendor_show, NULL); +static DEVICE_ATTR(name, 0444, pressure_name_show, NULL); +static DEVICE_ATTR(calibration, 0664, + pressure_cabratioin_show, pressure_cabratioin_store); +static DEVICE_ATTR(sea_level_pressure, 0664, + sea_level_pressure_show, sea_level_pressure_store); +static DEVICE_ATTR(temperature, 0444, temperature_show, NULL); +static DEVICE_ATTR(selftest, 0444, selftest_show, NULL); +#ifdef CONFIG_SEC_FACTORY +static DEVICE_ATTR(dhr_sensor_info, 0444, + pressure_dhr_sensor_info_show, NULL); +#else +static DEVICE_ATTR(dhr_sensor_info, 0440, + pressure_dhr_sensor_info_show, NULL); +#endif +static DEVICE_ATTR(sw_offset, 0664, + pressure_sw_offset_show, pressure_sw_offset_store); +static DEVICE_ATTR(esn, 0440, pressure_esn_show, NULL); + +static struct device_attribute *pressure_attrs[] = { + &dev_attr_vendor, + &dev_attr_name, + &dev_attr_calibration, + &dev_attr_sea_level_pressure, + &dev_attr_temperature, + &dev_attr_selftest, + &dev_attr_dhr_sensor_info, + &dev_attr_sw_offset, + &dev_attr_esn, + NULL, +}; + +void pressure_cal_work_func(struct work_struct *work) +{ + struct adsp_data *data = container_of((struct delayed_work *)work, + struct adsp_data, pressure_cal_work); + int cnt = 0; + int temp = 0; + + adsp_unicast(&temp, sizeof(temp), MSG_PRESSURE, 0, MSG_TYPE_SET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_SET_CAL_DATA] & 1 << MSG_PRESSURE) && + cnt++ < 3) + msleep(30); + + data->ready_flag[MSG_TYPE_SET_CAL_DATA] &= ~(1 << MSG_PRESSURE); + + if (cnt >= 3) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return; + } + + pressure_cal = data->msg_buf[MSG_PRESSURE][0]; + if (!strcmp(data->press_device_vendor, press_device_list[0].device_vendor) || + !strcmp(data->press_device_name, press_device_list[0].device_name)) + press_get_device_id(data); + + pr_info("[FACTORY] %s: pressure_cal = %d (lsb)\n", __func__, data->msg_buf[MSG_PRESSURE][0]); +} +EXPORT_SYMBOL(pressure_cal_work_func); +void pressure_factory_init_work(struct adsp_data *data) +{ + memcpy(data->press_device_vendor, + press_device_list[0].device_vendor, + sizeof(char) * DEVICE_INFO_LENGTH); + memcpy(data->press_device_name, + press_device_list[0].device_name, + sizeof(char) * DEVICE_INFO_LENGTH); + + schedule_delayed_work(&data->pressure_cal_work, msecs_to_jiffies(8000)); +} +EXPORT_SYMBOL(pressure_factory_init_work); + +int __init pressure_factory_init(void) +{ + adsp_factory_register(MSG_PRESSURE, pressure_attrs); + + pr_info("[FACTORY] %s\n", __func__); + + return 0; +} +void __exit pressure_factory_exit(void) +{ + adsp_factory_unregister(MSG_PRESSURE); + + pr_info("[FACTORY] %s\n", __func__); +} diff --git a/drivers/adsp_factory/prox_factory.c b/drivers/adsp_factory/prox_factory.c new file mode 100755 index 000000000000..80d93f7deaed --- /dev/null +++ b/drivers/adsp_factory/prox_factory.c @@ -0,0 +1,991 @@ +/* + * Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include "adsp.h" +#if IS_ENABLED(CONFIG_SUPPORT_CONTROL_PROX_LED_GPIO) +#include +#include +#include +#define PROX_LED_EN_GPIO 113 +#endif +#define PROX_AVG_COUNT 40 +#define PROX_ALERT_THRESHOLD 200 +#define PROX_TH_READ 0 +#define PROX_TH_WRITE 1 +#define BUFFER_MAX 128 +#define PROX_REG_START 0x80 +#define PROX_DETECT_HIGH_TH 16368 +#define PROX_DETECT_LOW_TH 1000 + +struct prox_data { + struct hrtimer prox_timer; + struct work_struct work_prox; + struct workqueue_struct *prox_wq; + struct adsp_data *dev_data; + int min; + int max; + int avg; + int val; + int offset; + int reg_backup[2]; + int debug_info_cmd; + short avgwork_check; + short avgtimer_enabled; +}; + +enum { + PRX_THRESHOLD_DETECT_H, + PRX_THRESHOLD_HIGH_DETECT_L, + PRX_THRESHOLD_HIGH_DETECT_H, + PRX_THRESHOLD_RELEASE_L, +}; + +enum { + PROX_CMD_TYPE_GET_TRIM_CHECK, + PROX_CMD_TYPE_GET_CAL_DATA, + PROX_CMD_TYPE_INIT_CAL_DATA, + PROX_CMD_TYPE_LED_CONTROL, + PROX_CMD_TYPE_SAVE_CAL_DATA, + PROX_CMD_TYPE_TOUCH_PROX, + PROX_CMD_TYPE_MAX +}; + +static struct prox_data *pdata; + +static int get_prox_sidx(struct adsp_data *data) +{ + int ret = MSG_PROX; +#if defined(CONFIG_SUPPORT_DUAL_OPTIC) && !defined(CONFIG_SUPPORT_DUAL_OPTIC_BUT_SUPPORT_SINGLE_PROX) + switch (data->fac_fstate) { + case FSTATE_INACTIVE: + case FSTATE_FAC_INACTIVE: + ret = MSG_PROX; + break; + case FSTATE_ACTIVE: + case FSTATE_FAC_ACTIVE: + case FSTATE_FAC_INACTIVE_2: + ret = MSG_PROX_SUB; + break; + default: + break; + } +#endif + return ret; +} + +#if IS_ENABLED(CONFIG_SUPPORT_PROX_CALIBRATION) +void prox_send_cal_data(struct adsp_data *data, uint16_t prox_idx, bool fac_cal) +{ + int32_t msg = -1, cnt = 0, prox_cal; +#if IS_ENABLED(CONFIG_SUPPORT_CONTROL_PROX_LED_GPIO) + if (prox_idx == MSG_PROX) { + int led_gpio, ret; + struct device_node *np = of_find_node_by_name(NULL, "ssc_prox_led_en_gpio"); + + if (np == NULL) { + pr_info("[SSC_FAC] %s: ssc_prox_led_en_gpio is NULL\n", __func__); + } else { + led_gpio = of_get_named_gpio_flags(np, "qcom,prox_led-en-gpio", + 0, NULL); + if (led_gpio >= 0) { + ret = gpio_request(led_gpio, NULL); + if (ret >= 0) { + pr_info("[SSC_FAC] %s: prox_led_en_gpio set\n", + __func__); + gpio_direction_output(led_gpio, 1); + gpio_free(led_gpio); + } else { + pr_err("[SSC_FAC] %s - gpio_request fail(%d)\n", + __func__, ret); + } + } else { + pr_err("[SSC_FAC] %s: prox_led_en_gpio fail(%d)\n", + __func__, led_gpio); + } + } + } +#endif + if (prox_idx == MSG_PROX) + prox_cal = data->prox_cal; + else + prox_cal = data->prox_sub_cal; + + if (!fac_cal || (prox_cal == 0)) { +#if IS_ENABLED(CONFIG_SEC_FACTORY) + pr_info("[SSC_FAC] %s[%d]: No cal data (%d)\n", + __func__, (int)prox_idx - MSG_PROX, prox_cal); +#else + mutex_lock(&data->prox_factory_mutex); + adsp_unicast(&msg, sizeof(int32_t), + prox_idx, 0, MSG_TYPE_SET_CAL_DATA); + while (!(data->ready_flag[MSG_TYPE_SET_CAL_DATA] & + 1 << prox_idx) && cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + if (cnt >= TIMEOUT_CNT) + pr_err("[SSC_FAC] %s[%d]: Timeout!!!\n", + __func__, prox_idx); + data->ready_flag[MSG_TYPE_SET_CAL_DATA] &= ~(1 << prox_idx); + mutex_unlock(&data->prox_factory_mutex); + pr_info("[SSC_FAC] %s[%d]: Excute in-use cal\n", + __func__, (int)prox_idx - MSG_PROX); +#endif + } else if (prox_cal > 0) { + mutex_lock(&data->prox_factory_mutex); + msg = prox_cal; + adsp_unicast(&msg, sizeof(int32_t), + prox_idx, 0, MSG_TYPE_SET_CAL_DATA); + while (!(data->ready_flag[MSG_TYPE_SET_CAL_DATA] & + 1 << prox_idx) && cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + if (cnt >= TIMEOUT_CNT) + pr_err("[SSC_FAC] %s[%d]: Timeout!!!\n", + __func__, (int)prox_idx - MSG_PROX); + data->ready_flag[MSG_TYPE_SET_CAL_DATA] &= ~(1 << prox_idx); + mutex_unlock(&data->prox_factory_mutex); + pr_info("[SSC_FAC] %s[%d]: Cal data: %d\n", __func__, + (int)prox_idx - MSG_PROX, msg); + } else { + pr_info("[SSC_FAC] %s[%d]: No cal data\n", + __func__, (int)prox_idx - MSG_PROX); + } +} + +void prox_cal_init_work(struct adsp_data *data) +{ + data->prox_cal = 0; + data->prox_sub_cal = 0; +} +#endif + +static ssize_t prox_vendor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ +#if IS_ENABLED(CONFIG_LIGHT_FACTORY) + struct adsp_data *data = dev_get_drvdata(dev); + int32_t display_idx = (get_prox_sidx(data) == MSG_PROX) ? 0 : 1; + + return snprintf(buf, PAGE_SIZE, "%s\n", + data->light_device_vendor[display_idx]); +#else + return snprintf(buf, PAGE_SIZE, "UNKNOWN\n"); +#endif +} + +static ssize_t prox_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ +#if IS_ENABLED(CONFIG_LIGHT_FACTORY) + struct adsp_data *data = dev_get_drvdata(dev); + int32_t display_idx = (get_prox_sidx(data) == MSG_PROX) ? 0 : 1; + + return snprintf(buf, PAGE_SIZE, "%s\n", + data->light_device_name[display_idx]); +#else + return snprintf(buf, PAGE_SIZE, "UNKNOWN\n"); +#endif +} + +int get_prox_raw_data(struct adsp_data *data, int *raw_data, int *offset) +{ + uint8_t cnt = 0; + uint16_t prox_idx = get_prox_sidx(data); + + mutex_lock(&data->prox_factory_mutex); + adsp_unicast(NULL, 0, prox_idx, 0, MSG_TYPE_GET_RAW_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & 1 << prox_idx) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << prox_idx); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + mutex_unlock(&data->prox_factory_mutex); + return -1; + } + + *raw_data = data->msg_buf[prox_idx][0]; + *offset = data->msg_buf[prox_idx][1]; + mutex_unlock(&data->prox_factory_mutex); + + return 0; +} + +static ssize_t prox_raw_data_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + + if (pdata->avgwork_check == 0) + get_prox_raw_data(data, &pdata->val, &pdata->offset); + + return snprintf(buf, PAGE_SIZE, "%d\n", pdata->val); +} + +static ssize_t prox_avg_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n", pdata->min, + pdata->avg, pdata->max); +} + +static ssize_t prox_avg_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int new_value; + + if (sysfs_streq(buf, "0")) + new_value = 0; + else + new_value = 1; + + if (new_value == pdata->avgtimer_enabled) + return size; + + if (new_value == 0) { + pdata->avgtimer_enabled = 0; + hrtimer_cancel(&pdata->prox_timer); + cancel_work_sync(&pdata->work_prox); + } else { + pdata->avgtimer_enabled = 1; + pdata->dev_data = data; + hrtimer_start(&pdata->prox_timer, + ns_to_ktime(2000 * NSEC_PER_MSEC), + HRTIMER_MODE_REL); + } + + return size; +} + +static void prox_work_func(struct work_struct *work) +{ + int min = 0, max = 0, avg = 0; + int i; + + pdata->avgwork_check = 1; + for (i = 0; i < PROX_AVG_COUNT; i++) { + msleep(20); + + get_prox_raw_data(pdata->dev_data, &pdata->val, &pdata->offset); + avg += pdata->val; + + if (!i) + min = pdata->val; + else if (pdata->val < min) + min = pdata->val; + + if (pdata->val > max) + max = pdata->val; + } + avg /= PROX_AVG_COUNT; + + pdata->min = min; + pdata->avg = avg; + pdata->max = max; + pdata->avgwork_check = 0; +} + +static enum hrtimer_restart prox_timer_func(struct hrtimer *timer) +{ + queue_work(pdata->prox_wq, &pdata->work_prox); + hrtimer_forward_now(&pdata->prox_timer, + ns_to_ktime(2000 * NSEC_PER_MSEC)); + return HRTIMER_RESTART; +} + + +static void prox_led_control(struct adsp_data *data, int led_number) +{ + uint16_t prox_idx = get_prox_sidx(data); + int cnt = 0; + int32_t msg[2]; + + msg[0] = PROX_CMD_TYPE_LED_CONTROL; + msg[1] = led_number; + + mutex_lock(&data->prox_factory_mutex); + adsp_unicast(&msg, sizeof(msg), prox_idx, 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] & 1 << prox_idx) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << prox_idx); + mutex_unlock(&data->prox_factory_mutex); + + if (cnt >= TIMEOUT_CNT) + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); +} + +static ssize_t prox_led_test_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int data_buf, offset = 0, ret = 0, result = 1; + + prox_led_control(data, 0); + msleep(200); + ret = get_prox_raw_data(data, &data_buf, &offset); + prox_led_control(data, 4); + + if (ret != 0) + result = -1; + + pr_info("[SSC_FAC] %s: [%d] %d\n", __func__, result, data_buf); + + return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d,%d\n", result, + data_buf, data_buf, data_buf, data_buf); +} + +static int prox_get_threshold(struct adsp_data *data, int type) +{ + uint8_t cnt = 0; + uint16_t prox_idx = get_prox_sidx(data); + int32_t msg_buf[2]; + int ret = 0; + + msg_buf[0] = type; + msg_buf[1] = 0; + + mutex_lock(&data->prox_factory_mutex); + adsp_unicast(msg_buf, sizeof(msg_buf), + prox_idx, 0, MSG_TYPE_GET_THRESHOLD); + + while (!(data->ready_flag[MSG_TYPE_GET_THRESHOLD] & 1 << prox_idx) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_THRESHOLD] &= ~(1 << prox_idx); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + mutex_unlock(&data->prox_factory_mutex); + return ret; + } + + ret = data->msg_buf[prox_idx][0]; + mutex_unlock(&data->prox_factory_mutex); + + return ret; +} + +static void prox_set_threshold(struct adsp_data *data, int type, int val) +{ + uint8_t cnt = 0; + uint16_t prox_idx = get_prox_sidx(data); + int32_t msg_buf[2]; + + msg_buf[0] = type; + msg_buf[1] = val; + + mutex_lock(&data->prox_factory_mutex); + adsp_unicast(msg_buf, sizeof(msg_buf), + prox_idx, 0, MSG_TYPE_SET_THRESHOLD); + + while (!(data->ready_flag[MSG_TYPE_SET_THRESHOLD] & 1 << prox_idx) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_SET_THRESHOLD] &= ~(1 << prox_idx); + + if (cnt >= TIMEOUT_CNT) + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + + mutex_unlock(&data->prox_factory_mutex); +} + +static ssize_t prox_cal_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ +#if IS_ENABLED(CONFIG_SUPPORT_PROX_CALIBRATION) + struct adsp_data *data = dev_get_drvdata(dev); + + if (get_prox_sidx(data) == MSG_PROX) + return snprintf(buf, PAGE_SIZE, "%d,0,0\n", data->prox_cal); + else + return snprintf(buf, PAGE_SIZE, "%d,0,0\n", data->prox_sub_cal); +#else + return snprintf(buf, PAGE_SIZE, "0,0,0\n"); +#endif +} + +static ssize_t prox_cal_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ +#if IS_ENABLED(CONFIG_SUPPORT_PROX_CALIBRATION) + struct adsp_data *data = dev_get_drvdata(dev); + int32_t cmd, msg_buf[2], cnt = 0; + uint16_t prox_idx = get_prox_sidx(data); + + if (sysfs_streq(buf, "1")) { + cmd = PROX_CMD_TYPE_GET_CAL_DATA; + } else if (sysfs_streq(buf, "0")) { + cmd = PROX_CMD_TYPE_INIT_CAL_DATA; + } else { + pr_err("[SSC_FAC] %s: wrong value\n", __func__); + return size; + } + + pr_info("[SSC_FAC] %s[%d]: msg %d\n", + __func__, (int)prox_idx - MSG_PROX, cmd); + + mutex_lock(&data->prox_factory_mutex); + adsp_unicast(&cmd, sizeof(int32_t), prox_idx, 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] & 1 << prox_idx) && + cnt++ < TIMEOUT_CNT) + msleep(20); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << prox_idx); + mutex_unlock(&data->prox_factory_mutex); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[SSC_FAC] %s[%d]: Timeout!!!\n", __func__, + (int)prox_idx - MSG_PROX); + return size; + } else if (data->msg_buf[prox_idx][0] < 0) { + pr_err("[SSC_FAC] %s[%d]: fail! %d\n", __func__, + (int)prox_idx - MSG_PROX, + data->msg_buf[prox_idx][0]); + return size; + } + + cnt = 0; + msg_buf[0] = PROX_CMD_TYPE_SAVE_CAL_DATA; + msg_buf[1] = data->msg_buf[prox_idx][0]; + + if (prox_idx == MSG_PROX) + data->prox_cal = data->msg_buf[prox_idx][0]; + else + data->prox_sub_cal = data->msg_buf[prox_idx][0]; + + mutex_lock(&data->prox_factory_mutex); + adsp_unicast(msg_buf, sizeof(msg_buf), prox_idx, 0, + MSG_TYPE_SET_TEMPORARY_MSG); + while (!(data->ready_flag[MSG_TYPE_SET_TEMPORARY_MSG] & 1 << prox_idx) && + cnt++ < TIMEOUT_CNT) + msleep(20); + if (cnt >= TIMEOUT_CNT) + pr_err("[SSC_FAC] %s[%d]: SAVE_CAL_DATA Timeout!!!\n", + __func__, (int)prox_idx - MSG_PROX); + + data->ready_flag[MSG_TYPE_SET_TEMPORARY_MSG] &= ~(1 << prox_idx); + mutex_unlock(&data->prox_factory_mutex); + if ((prox_idx == MSG_PROX) && (data->prox_cal > 0)) + prox_send_cal_data(data, prox_idx, true); +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) + else if ((prox_idx == MSG_PROX_SUB) && (data->prox_sub_cal > 0)) + prox_send_cal_data(data, prox_idx, true); +#endif + +#else + pr_info("[SSC_FAC] %s: unsupported prox cal!\n", __func__); +#endif + return size; +} + +static ssize_t prox_thresh_high_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int thd; + + thd = prox_get_threshold(data, PRX_THRESHOLD_DETECT_H); + pr_info("[SSC_FAC] %s: %d\n", __func__, thd); + + return snprintf(buf, PAGE_SIZE, "%d\n", thd); +} + +static ssize_t prox_thresh_high_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int thd = 0; + + if (kstrtoint(buf, 10, &thd)) { + pr_err("[SSC_FAC] %s: kstrtoint fail\n", __func__); + return size; + } + + prox_set_threshold(data, PRX_THRESHOLD_DETECT_H, thd); + pr_info("[SSC_FAC] %s: %d\n", __func__, thd); + + return size; +} + +static ssize_t prox_thresh_low_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int thd; + + thd = prox_get_threshold(data, PRX_THRESHOLD_RELEASE_L); + pr_info("[SSC_FAC] %s: %d\n", __func__, thd); + + return snprintf(buf, PAGE_SIZE, "%d\n", thd); +} + +static ssize_t prox_thresh_low_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int thd = 0; + + if (kstrtoint(buf, 10, &thd)) { + pr_err("[SSC_FAC] %s: kstrtoint fail\n", __func__); + return size; + } + + prox_set_threshold(data, PRX_THRESHOLD_RELEASE_L, thd); + pr_info("[SSC_FAC] %s: %d\n", __func__, thd); + + return size; +} + +static ssize_t prox_thresh_detect_high_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int thd; + + thd = prox_get_threshold(data, PRX_THRESHOLD_HIGH_DETECT_H); + pr_info("[SSC_FAC] %s: %d\n", __func__, thd); + + return snprintf(buf, PAGE_SIZE, "%d\n", thd); +} + +static ssize_t prox_thresh_detect_high_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int thd = 0; + + if (kstrtoint(buf, 10, &thd)) { + pr_err("[SSC_FAC] %s: kstrtoint fail\n", __func__); + return size; + } + + prox_set_threshold(data, PRX_THRESHOLD_HIGH_DETECT_H, thd); + pr_info("[SSC_FAC] %s: %d\n", __func__, thd); + + return size; +} + +static ssize_t prox_thresh_detect_low_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int thd; + + thd = prox_get_threshold(data, PRX_THRESHOLD_HIGH_DETECT_L); + pr_info("[SSC_FAC] %s: %d\n", __func__, thd); + + return snprintf(buf, PAGE_SIZE, "%d\n", thd); +} + +static ssize_t prox_thresh_detect_low_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int thd = 0; + + if (kstrtoint(buf, 10, &thd)) { + pr_err("[SSC_FAC] %s: kstrtoint fail\n", __func__); + return size; + } + + prox_set_threshold(data, PRX_THRESHOLD_HIGH_DETECT_L, thd); + pr_info("[SSC_FAC] %s: %d\n", __func__, thd); + + return size; +} + +static ssize_t prox_cancel_pass_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ +#if IS_ENABLED(CONFIG_SUPPORT_PROX_CALIBRATION) + struct adsp_data *data = dev_get_drvdata(dev); + + if (get_prox_sidx(data) == MSG_PROX) + return snprintf(buf, PAGE_SIZE, "%d\n", + (data->prox_cal > 0) ? 1 : 0); + else + return snprintf(buf, PAGE_SIZE, "%d\n", + (data->prox_sub_cal > 0) ? 1 : 0); +#else + return snprintf(buf, PAGE_SIZE, "1\n"); +#endif +} + +static ssize_t prox_default_trim_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", pdata->offset); +} + +static ssize_t prox_alert_thresh_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", PROX_ALERT_THRESHOLD); +} + +static ssize_t prox_register_read_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint16_t prox_idx = get_prox_sidx(data); + int cnt = 0; + int32_t msg_buf[1]; + + msg_buf[0] = pdata->reg_backup[0]; + + mutex_lock(&data->prox_factory_mutex); + adsp_unicast(msg_buf, sizeof(msg_buf), + prox_idx, 0, MSG_TYPE_GET_REGISTER); + + while (!(data->ready_flag[MSG_TYPE_GET_REGISTER] & 1 << prox_idx) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_REGISTER] &= ~(1 << prox_idx); + + if (cnt >= TIMEOUT_CNT) + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + + pdata->reg_backup[1] = data->msg_buf[prox_idx][0]; + pr_info("[SSC_FAC] %s: [0x%x]: %d\n", + __func__, pdata->reg_backup[0], pdata->reg_backup[1]); + + mutex_unlock(&data->prox_factory_mutex); + + return snprintf(buf, PAGE_SIZE, "%d\n", pdata->reg_backup[1]); +} + +static ssize_t prox_register_read_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int reg = 0; + + if (sscanf(buf, "%3d", ®) != 1) { + pr_err("[SSC_FAC]: %s - The number of data are wrong\n", + __func__); + return -EINVAL; + } + + pdata->reg_backup[0] = reg; + pr_info("[SSC_FAC] %s: [0x%x]\n", __func__, pdata->reg_backup[0]); + + return size; +} + +static ssize_t prox_register_write_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint16_t prox_idx = get_prox_sidx(data); + int cnt = 0; + int32_t msg_buf[2]; + + if (sscanf(buf, "%3d,%5d", &msg_buf[0], &msg_buf[1]) != 2) { + pr_err("[SSC_FAC]: %s - The number of data are wrong\n", + __func__); + return -EINVAL; + } + + mutex_lock(&data->prox_factory_mutex); + adsp_unicast(msg_buf, sizeof(msg_buf), + prox_idx, 0, MSG_TYPE_SET_REGISTER); + + while (!(data->ready_flag[MSG_TYPE_SET_REGISTER] & 1 << prox_idx) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_SET_REGISTER] &= ~(1 << prox_idx); + + if (cnt >= TIMEOUT_CNT) + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + + pdata->reg_backup[0] = msg_buf[0]; + pr_info("[SSC_FAC] %s: 0x%x - %d\n", + __func__, msg_buf[0], data->msg_buf[prox_idx][0]); + mutex_unlock(&data->prox_factory_mutex); + + return size; +} + +static ssize_t prox_touch_prox_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint16_t prox_idx = get_prox_sidx(data); + int cnt = 0; + int32_t msg_buf[2]; + + if (sscanf(buf, "%2d", &msg_buf[1]) != 1) { + pr_err("[SSC_FAC]: %s - The number of data are wrong\n", + __func__); + return -EINVAL; + } + + msg_buf[0] = PROX_CMD_TYPE_TOUCH_PROX; + + mutex_lock(&data->prox_factory_mutex); + adsp_unicast(msg_buf, sizeof(msg_buf), prox_idx, 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] & 1 << prox_idx) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << prox_idx); + + pr_info("[SSC_FAC] %s: event: %d\n", __func__, msg_buf[1]); + mutex_unlock(&data->prox_factory_mutex); + + if (cnt >= TIMEOUT_CNT) + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + + return size; +} + +static ssize_t prox_debug_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint16_t prox_idx = get_prox_sidx(data); + int cnt = 0; + int32_t msg_buf[1]; + + msg_buf[0] = pdata->debug_info_cmd; + + mutex_lock(&data->prox_factory_mutex); + adsp_unicast(msg_buf, sizeof(msg_buf), + prox_idx, 0, MSG_TYPE_GET_DUMP_REGISTER); + + while (!(data->ready_flag[MSG_TYPE_GET_DUMP_REGISTER] & 1 << prox_idx) + && cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_DUMP_REGISTER] &= ~(1 << prox_idx); + + if (cnt >= TIMEOUT_CNT) + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + + mutex_unlock(&data->prox_factory_mutex); + + return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", + data->msg_buf[prox_idx][0], data->msg_buf[prox_idx][1], + data->msg_buf[prox_idx][2], data->msg_buf[prox_idx][3], + data->msg_buf[prox_idx][4], data->msg_buf[prox_idx][5], + data->msg_buf[prox_idx][6], data->msg_buf[prox_idx][7], + data->msg_buf[prox_idx][8], data->msg_buf[prox_idx][9], + data->msg_buf[prox_idx][10], data->msg_buf[prox_idx][11]); +} + +static ssize_t prox_debug_info_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int reg = 0; + + if (sscanf(buf, "%3d", ®) != 1) { + pr_err("[SSC_FAC]: %s - The number of data are wrong\n", + __func__); + return -EINVAL; + } + + pdata->debug_info_cmd = reg; + + return size; +} + +static ssize_t prox_light_get_dhr_sensor_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint16_t prox_idx = get_prox_sidx(data); + uint8_t cnt = 0; + int offset = 0; + int32_t *info = data->msg_buf[prox_idx]; + + mutex_lock(&data->prox_factory_mutex); + adsp_unicast(NULL, 0, prox_idx, 0, MSG_TYPE_GET_DHR_INFO); + while (!(data->ready_flag[MSG_TYPE_GET_DHR_INFO] & 1 << prox_idx) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_DHR_INFO] &= ~(1 << prox_idx); + + if (cnt >= TIMEOUT_CNT) + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + + pr_info("[SSC_FAC] %d,%d,%d,%d,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%d\n", + info[0], info[1], info[2], info[3], info[4], info[5], + info[6], info[7], info[8], info[9], info[10], info[11]); + + offset += snprintf(buf + offset, PAGE_SIZE - offset, + "\"THD\":\"%d %d %d %d\",", info[0], info[1], info[2], info[3]); + offset += snprintf(buf + offset, PAGE_SIZE - offset, + "\"PDRIVE_CURRENT\":\"%02x\",", info[4]); + offset += snprintf(buf + offset, PAGE_SIZE - offset, + "\"PERSIST_TIME\":\"%02x\",", info[5]); + offset += snprintf(buf + offset, PAGE_SIZE - offset, + "\"PPULSE\":\"%02x\",", info[6]); + offset += snprintf(buf + offset, PAGE_SIZE - offset, + "\"PGAIN\":\"%02x\",", info[7]); + offset += snprintf(buf + offset, PAGE_SIZE - offset, + "\"PTIME\":\"%02x\",", info[8]); + offset += snprintf(buf + offset, PAGE_SIZE - offset, + "\"PPLUSE_LEN\":\"%02x\",", info[9]); + offset += snprintf(buf + offset, PAGE_SIZE - offset, + "\"ATIME\":\"%02x\",", info[10]); + offset += snprintf(buf + offset, PAGE_SIZE - offset, + "\"POFFSET\":\"%d\"\n", info[11]); + + mutex_unlock(&data->prox_factory_mutex); + return offset; +} + +static ssize_t prox_wakelock_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + return size; +} + +static ssize_t prox_trim_check_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint16_t prox_idx = get_prox_sidx(data); + int cnt = 0; + int32_t msg = PROX_CMD_TYPE_GET_TRIM_CHECK; + + mutex_lock(&data->prox_factory_mutex); + adsp_unicast(&msg, sizeof(int32_t), prox_idx, 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] & 1 << prox_idx) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << prox_idx); + mutex_unlock(&data->prox_factory_mutex); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[SSC_FAC] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "NG\n"); + } + + pr_info("[SSC_FAC] %s: [%s]: 0x%x, 0x%x\n", + __func__, (data->msg_buf[prox_idx][0] > 0) ? "TRIM" : "UNTRIM", + (uint16_t)data->msg_buf[prox_idx][1], + (uint16_t)data->msg_buf[prox_idx][2]); + + return snprintf(buf, PAGE_SIZE, "%s\n", + (data->msg_buf[prox_idx][0] > 0) ? "TRIM" : "UNTRIM"); +} + +static DEVICE_ATTR(vendor, 0444, prox_vendor_show, NULL); +static DEVICE_ATTR(name, 0444, prox_name_show, NULL); +static DEVICE_ATTR(state, 0444, prox_raw_data_show, NULL); +static DEVICE_ATTR(raw_data, 0444, prox_raw_data_show, NULL); +static DEVICE_ATTR(prox_led_test, 0444, prox_led_test_show, NULL); +static DEVICE_ATTR(prox_avg, 0664, + prox_avg_show, prox_avg_store); +static DEVICE_ATTR(prox_cal, 0664, + prox_cal_show, prox_cal_store); +static DEVICE_ATTR(thresh_high, 0664, + prox_thresh_high_show, prox_thresh_high_store); +static DEVICE_ATTR(thresh_low, 0664, + prox_thresh_low_show, prox_thresh_low_store); +static DEVICE_ATTR(register_write, 0220, + NULL, prox_register_write_store); +static DEVICE_ATTR(register_read, 0664, + prox_register_read_show, prox_register_read_store); +static DEVICE_ATTR(prox_offset_pass, 0444, prox_cancel_pass_show, NULL); +static DEVICE_ATTR(prox_trim, 0444, prox_default_trim_show, NULL); +static DEVICE_ATTR(thresh_detect_high, 0664, + prox_thresh_detect_high_show, prox_thresh_detect_high_store); +static DEVICE_ATTR(thresh_detect_low, 0664, + prox_thresh_detect_low_show, prox_thresh_detect_low_store); +static DEVICE_ATTR(prox_alert_thresh, 0444, prox_alert_thresh_show, NULL); +static DEVICE_ATTR(dhr_sensor_info, 0440, + prox_light_get_dhr_sensor_info_show, NULL); +static DEVICE_ATTR(prox_wakelock, 0220, NULL, prox_wakelock_store); +static DEVICE_ATTR(trim_check, 0444, prox_trim_check_show, NULL); +static DEVICE_ATTR(debug_info, 0664, + prox_debug_info_show, prox_debug_info_store); +static DEVICE_ATTR(touch_prox, 0220, NULL, prox_touch_prox_store); + +static struct device_attribute *prox_attrs[] = { + &dev_attr_vendor, + &dev_attr_name, + &dev_attr_state, + &dev_attr_raw_data, + &dev_attr_prox_led_test, + &dev_attr_prox_avg, + &dev_attr_prox_cal, + &dev_attr_thresh_high, + &dev_attr_thresh_low, + &dev_attr_prox_offset_pass, + &dev_attr_prox_trim, + &dev_attr_thresh_detect_high, + &dev_attr_thresh_detect_low, + &dev_attr_prox_alert_thresh, + &dev_attr_dhr_sensor_info, + &dev_attr_register_write, + &dev_attr_register_read, + &dev_attr_prox_wakelock, + &dev_attr_trim_check, + &dev_attr_debug_info, + &dev_attr_touch_prox, + NULL, +}; + +int prox_factory_init(void) +{ + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + adsp_factory_register(MSG_PROX, prox_attrs); + pr_info("[SSC_FAC] %s\n", __func__); + + hrtimer_init(&pdata->prox_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + pdata->prox_timer.function = prox_timer_func; + pdata->prox_wq = create_singlethread_workqueue("prox_wq"); + + /* this is the thread function we run on the work queue */ + INIT_WORK(&pdata->work_prox, prox_work_func); + + pdata->avgwork_check = 0; + pdata->avgtimer_enabled = 0; + pdata->avg = 0; + pdata->min = 0; + pdata->max = 0; + pdata->offset = 0; + pdata->debug_info_cmd = 0; + + return 0; +} + +void prox_factory_exit(void) +{ + if (pdata->avgtimer_enabled == 1) { + hrtimer_cancel(&pdata->prox_timer); + cancel_work_sync(&pdata->work_prox); + } + destroy_workqueue(pdata->prox_wq); + adsp_factory_unregister(MSG_PROX); + kfree(pdata); + pr_info("[SSC_FAC] %s\n", __func__); +} diff --git a/drivers/adsp_factory/ssc_core.c b/drivers/adsp_factory/ssc_core.c new file mode 100755 index 000000000000..a5a874569125 --- /dev/null +++ b/drivers/adsp_factory/ssc_core.c @@ -0,0 +1,1897 @@ +/* + * Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#ifdef CONFIG_VBUS_NOTIFIER +#include +#endif +#include + +#if IS_ENABLED(CONFIG_HALL_NOTIFIER) +#define SUPPORT_HALL_NOTIFIER +#endif + +#if IS_ENABLED(CONFIG_SUPPORT_DEVICE_MODE) +#ifdef SUPPORT_HALL_NOTIFIER +#include +#endif // SUPPORT_HALL_NOTIFIER +#endif +#if IS_ENABLED(CONFIG_SUPPORT_SSC_SPU) +#include +#endif +#include "adsp.h" + +#ifdef CONFIG_SUPPORT_SSC_MODE +#include +#include +#endif + +#include + +#include +#include + +#include +#include + +#define HISTORY_CNT 5 +#define NO_SSR 0xFF +#define SSR_REASON_LEN 256 +#define TIME_LEN 24 +#ifdef CONFIG_SEC_FACTORY +#define SLPI_STUCK "SLPI_STUCK" +#define SLPI_PASS "SLPI_PASS" +#define PROBE_FAIL "PROBE_FAIL" +#endif + +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_6AXIS) +#define SUPPORT_DUAL_SENSOR "DUAL_GYRO" +#define SENSOR_DUMP_CNT 5 +#define SENSOR_RDUMP_CNT 6 +#else +#define SUPPORT_DUAL_SENSOR "SINGLE_GYRO" +#define SENSOR_DUMP_CNT 4 +#define SENSOR_RDUMP_CNT 5 +#endif + +#define SENSOR_DUMP_DONE "SENSOR_DUMP_DONE" + +#define SENSOR_HW_REVISION_MAX 31 + +#define SSR_DUMP_START 1 +#define SSR_DUMP_STOP 0 + +#ifdef CONFIG_SUPPORT_SSC_MODE +#ifdef CONFIG_SUPPORT_SSC_MODE_FOR_MAG +#define ANT_NFC_MST 0 +#define ANT_NFC_ONLY 1 +#define ANT_DUMMY 2 +#endif +#endif + +static struct delayed_work ssr_dump_work; +static int pid; +static char panic_msg[SSR_REASON_LEN]; +static char ssr_history[HISTORY_CNT][TIME_LEN + SSR_REASON_LEN]; +static unsigned char ssr_idx = NO_SSR; +#if IS_ENABLED(CONFIG_SLPI_LOADING_FAILURE_DEBUG) +static unsigned int removed_sensors; +#endif + +#if IS_ENABLED(CONFIG_SUPPORT_DEVICE_MODE) || IS_ENABLED(CONFIG_SUPPORT_VIRTUAL_OPTIC) +#if IS_ENABLED(CONFIG_SUPPORT_DEVICE_MODE) +#ifdef SUPPORT_HALL_NOTIFIER +#define FLIP_HALL_NAME "flip" +static struct hall_notifier_context *hall_notifier; +#endif //SUPPORT_HALL_NOTIFIER +#endif +static int32_t curr_fstate; +#endif + +enum ic_num { + MAIN_GRIP = 0, + SUB_GRIP, + SUB2_GRIP, + WIFI_GRIP, + GRIP_MAX_CNT +}; + +struct sdump_data { + struct workqueue_struct *sdump_wq; + struct work_struct work_sdump; + struct adsp_data *dev_data; + + u32 grip_error[GRIP_MAX_CNT]; +}; +struct sdump_data *pdata_sdump; + +//#if defined(CONFIG_SEC_FACTORY) && IS_ENABLED(CONFIG_SUPPORT_DUAL_6AXIS) +//extern bool is_pretest(void); +//#endif +#if IS_ENABLED(CONFIG_SUPPORT_SSC_SPU) +static int fw_idx = SSC_ORI; +void ssc_set_fw_idx(int src) +{ + fw_idx = src; +} +EXPORT_SYMBOL(ssc_set_fw_idx); +#endif + +void send_ssc_recovery_command(int type) +{ + int32_t msg_buf[2] = {0, }; + if (type == 1) { + msg_buf[0] = OPTION_TYPE_SSC_SSR_DUMP; + msg_buf[1] = SSR_DUMP_START; + } else if (type == 2) { + msg_buf[0] = OPTION_TYPE_SSC_SSR_DUMP; + msg_buf[1] = SSR_DUMP_STOP; + } else { + msg_buf[0] = OPTION_TYPE_SSC_RECOVERY; + } + pr_info("[FACTORY] %s: msg_buf = %d,%d\n", + __func__, msg_buf[0], msg_buf[1]); + adsp_unicast(msg_buf, sizeof(msg_buf), + MSG_SSC_CORE, 0, MSG_TYPE_OPTION_DEFINE); +} +EXPORT_SYMBOL(send_ssc_recovery_command); + +static unsigned int sec_hw_rev(void); + +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) +void dhall_cal_work_func(struct work_struct *work) +{ + int buf[58] = { 1, }; + + adsp_unicast(buf, sizeof(buf), + MSG_DIGITAL_HALL_ANGLE, 0, MSG_TYPE_SET_REGISTER); +} + +void digital_hall_factory_auto_cal_init_work(struct adsp_data *data) +{ + schedule_delayed_work(&data->dhall_cal_work, msecs_to_jiffies(8000)); +} +#endif +/* + * send ssr notice to ois mcu + */ +#define NO_OIS_STRUCT (-1) + +#if IS_ENABLED(CONFIG_SUPPORT_DEVICE_MODE) +struct ssc_flip_data { + struct workqueue_struct *ssc_flip_wq; + struct work_struct work_ssc_flip; + bool is_opend; + bool only_update; +}; +struct ssc_flip_data *pdata_ssc_flip; +#endif + +#ifdef CONFIG_VBUS_NOTIFIER +#define CHARGE_START true +#define CHARGE_END false + +struct ssc_charge_data { + struct workqueue_struct *ssc_charge_wq; + struct work_struct work_ssc_charge; + bool is_charging; +}; +struct ssc_charge_data *pdata_ssc_charge; +#endif + +struct ois_sensor_interface{ + void *core; + void (*ois_func)(void *); +}; + +static struct ois_sensor_interface ois_reset; + +int ois_reset_register(struct ois_sensor_interface *ois) +{ + if (ois->core) + ois_reset.core = ois->core; + + if (ois->ois_func) + ois_reset.ois_func = ois->ois_func; + + if (!ois->core || !ois->ois_func) { + pr_info("[FACTORY] %s - no ois struct\n", __func__); + return NO_OIS_STRUCT; + } + + return 0; +} +EXPORT_SYMBOL(ois_reset_register); + +void ois_reset_unregister(void) +{ + ois_reset.core = NULL; + ois_reset.ois_func = NULL; +} +EXPORT_SYMBOL(ois_reset_unregister); + +#ifdef CONFIG_VBUS_NOTIFIER +void ssc_charge_work_func(struct work_struct *work) +{ + int32_t msg_buf[2]; + msg_buf[0] = OPTION_TYPE_SSC_CHARGING_STATE; + msg_buf[1] = (int32_t)pdata_ssc_charge->is_charging; + pr_info("[FACTORY] %s: msg_buf = %d\n", __func__, msg_buf[1]); + adsp_unicast(msg_buf, sizeof(msg_buf), + MSG_SSC_CORE, 0, MSG_TYPE_OPTION_DEFINE); +} + +static int ssc_core_vbus_notifier(struct notifier_block *nb, + unsigned long action, void *data) +{ + vbus_status_t vbus_type = *(vbus_status_t *) data; + static int vbus_pre_attach; + + if (vbus_pre_attach == vbus_type) + return 0; + + switch (vbus_type) { + case STATUS_VBUS_HIGH: + case STATUS_VBUS_LOW: + pdata_ssc_charge->is_charging = + (vbus_type == STATUS_VBUS_HIGH) ? true : false; + pr_info("[FACTORY] vbus high:%d \n", (int)pdata_ssc_charge->is_charging); + queue_work(pdata_ssc_charge->ssc_charge_wq, + &pdata_ssc_charge->work_ssc_charge); + break; + default: + pr_info("[FACTORY] vbus skip attach = %d\n", vbus_type); + break; + } + + vbus_pre_attach = vbus_type; + + return 0; +} + +void sns_vbus_init_work(void) +{ + pr_info("[FACTORY] sns_vbus_init_work:%d \n", (int)pdata_ssc_charge->is_charging); + queue_work(pdata_ssc_charge->ssc_charge_wq, + &pdata_ssc_charge->work_ssc_charge); +} +#endif + +/*************************************************************************/ +/* factory Sysfs */ +/*************************************************************************/ + +static char operation_mode_flag[11] = "normal"; + +static ssize_t dumpstate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int32_t type[1]; + + if (pid != 0) { + pr_info("[FACTORY] to take the logs\n"); + type[0] = 0; + } else { + type[0] = 2; + pr_info("[FACTORY] logging service was stopped %d\n", pid); + } + + pr_info("[FACTORY] %s support_algo = %u\n", __func__, data->support_algo); + + return snprintf(buf, PAGE_SIZE, "SSC_CORE\n"); +} + +static ssize_t operation_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s", operation_mode_flag); +} + +static ssize_t operation_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + int i; + + for (i = 0; i < 10 && buf[i] != '\0'; i++) + operation_mode_flag[i] = buf[i]; + operation_mode_flag[i] = '\0'; + + if (!strncmp(operation_mode_flag, "restrict", 8)) { + pr_info("[FACTORY] %s: set restrict_mode\n", __func__); + data->restrict_mode = true; + } else { + pr_info("[FACTORY] %s: set normal_mode\n", __func__); + data->restrict_mode = false; + } + pr_info("[FACTORY] %s: operation_mode_flag = %s\n", __func__, + operation_mode_flag); + return size; +} + +static ssize_t mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long timeout; + unsigned long timeout_2; + int32_t type[1]; + + if (pid != 0) { + timeout = jiffies + (2 * HZ); + timeout_2 = jiffies + (10 * HZ); + type[0] = 1; + pr_info("[FACTORY] To stop logging %d\n", pid); + adsp_unicast(type, sizeof(type), MSG_SSC_CORE, 0, MSG_TYPE_DUMPSTATE); + + while (pid != 0) { + msleep(25); + if (time_after(jiffies, timeout)) + pr_info("[FACTORY] %s: Timeout!!!\n", __func__); + if (time_after(jiffies, timeout_2)) { +// panic("force crash : ssc core\n"); + pr_info("[FACTORY] pid %d\n", pid); + return snprintf(buf, PAGE_SIZE, "1\n"); + } + } + } + + return snprintf(buf, PAGE_SIZE, "0\n"); +} + +static ssize_t mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int data = 0; + int32_t type[1]; + if (kstrtoint(buf, 10, &data)) { + pr_err("[FACTORY] %s: kstrtoint fail\n", __func__); + return -EINVAL; + } + + if (data != 1) { + pr_err("[FACTORY] %s: data was wrong\n", __func__); + return -EINVAL; + } + + if (pid != 0) { + type[0] = 1; + adsp_unicast(type, sizeof(type), MSG_SSC_CORE, 0, MSG_TYPE_DUMPSTATE); + } + + return size; +} + +static ssize_t pid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", pid); +} + +static ssize_t pid_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int data = 0; + + if (kstrtoint(buf, 10, &data)) { + pr_err("[FACTORY] %s: kstrtoint fail\n", __func__); + return -EINVAL; + } + + pid = data; + pr_info("[FACTORY] %s: pid %d\n", __func__, pid); + + return size; +} + +static ssize_t remove_sensor_sysfs_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + unsigned int type = MSG_SENSOR_MAX; + struct adsp_data *data = dev_get_drvdata(dev); + + if (kstrtouint(buf, 10, &type)) { + pr_err("[FACTORY] %s: kstrtouint fail\n", __func__); + return -EINVAL; + } + + if (type > PHYSICAL_SENSOR_SYSFS) { + pr_err("[FACTORY] %s: Invalid type %u\n", __func__, type); + return size; + } + pr_info("[FACTORY] %s: type = %u\n", __func__, type); +#if IS_ENABLED(CONFIG_SLPI_LOADING_FAILURE_DEBUG) + removed_sensors |= (0x1 << type); + if ((removed_sensors & 0x3f) == 0x3f) +#if 0 + panic("slpi is not loaded, force panic\n"); +#else + pr_info("[FACTORY] %s: slpi is not loaded\n", __func__); +#endif +#endif + mutex_lock(&data->remove_sysfs_mutex); + adsp_factory_unregister(type); + mutex_unlock(&data->remove_sysfs_mutex); + return size; +} + +static unsigned int system_rev __read_mostly; +static void sec_hw_rev_setup(void) +{ + const char *revision_str; + int err; + + revision_str = sec_cmdline_get_val("androidboot.revision"); + if (!revision_str) { + pr_err("[FACTORY] androidboot.revision is missing.\n"); + system_rev = 31; + return; + } + + err = kstrtouint(revision_str, 0, &system_rev); + if (err < 0) { + pr_err("[FACTORY] androidboot.revision is malformed (%s)\n", + revision_str); + system_rev = 31; + return; + } +} + +static unsigned int sec_hw_rev(void) +{ + return system_rev; +} + +int ssc_system_rev_test = -1; +static ssize_t ssc_hw_rev_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + uint32_t hw_rev = sec_hw_rev(); + + if(hw_rev > SENSOR_HW_REVISION_MAX) + hw_rev = SENSOR_HW_REVISION_MAX; + + pr_info("[FACTORY] %s: ssc_rev:%d, ssc_system_rev_test %d\n", __func__, hw_rev, ssc_system_rev_test); + + if (ssc_system_rev_test < 0) + return snprintf(buf, PAGE_SIZE, "%d\n", hw_rev); + else + return snprintf(buf, PAGE_SIZE, "%d\n", ssc_system_rev_test); +} + +static ssize_t ssc_hw_rev_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + if (kstrtoint(buf, 10, &ssc_system_rev_test)) { + pr_err("[FACTORY] %s: kstrtoint fail\n", __func__); + return -EINVAL; + } + + pr_info("[FACTORY] %s: system_rev_ssc %d\n", __func__, ssc_system_rev_test); + + return size; +} + +void ssr_reason_call_back(char reason[], int len) +{ + struct timespec64 ts; + struct tm tm; + + if (len <= 0) { + pr_info("[FACTORY] ssr %d\n", len); + return; + } + memset(panic_msg, 0, SSR_REASON_LEN); + strlcpy(panic_msg, reason, min(len, (int)(SSR_REASON_LEN - 1))); + + // Please do not modify "[FACTORY] ssr" string. + // It is refered from BL to log in history_auto_xxxxxx.lst file. + pr_info("[FACTORY] ssr %s\n", panic_msg); + + if (ois_reset.ois_func != NULL && ois_reset.core != NULL) { + ois_reset.ois_func(ois_reset.core); + pr_info("[FACTORY] %s - send ssr notice to ois mcu\n", __func__); + } else { + pr_info("[FACTORY] %s - no ois struct\n", __func__); + } + + if (ssr_idx == HISTORY_CNT || ssr_idx == NO_SSR) + ssr_idx = 0; + + memset(ssr_history[ssr_idx], 0, TIME_LEN + SSR_REASON_LEN); + + ktime_get_real_ts64(&ts); + time64_to_tm(ts.tv_sec, 0, &tm); + + sprintf(ssr_history[ssr_idx], "[%d%02d%02d %02d:%02d:%02d] ", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); + + strlcat(ssr_history[ssr_idx++], panic_msg, TIME_LEN + SSR_REASON_LEN); +} +EXPORT_SYMBOL_GPL(ssr_reason_call_back); + +static ssize_t ssr_msg_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ +#ifdef CONFIG_SEC_FACTORY + struct adsp_data *data = dev_get_drvdata(dev); + int32_t raw_data_acc[3] = {0, }; + int32_t raw_data_mag[3] = {0, }; + int ret_acc = 0; + int ret_mag = 0; +//#if IS_ENABLED(CONFIG_SUPPORT_DUAL_6AXIS) +// if (is_pretest()) +// return snprintf(buf, PAGE_SIZE, "%s\n", panic_msg); +//#endif + if (!data->sysfs_created[MSG_ACCEL] && !data->sysfs_created[MSG_MAG] + && !data->sysfs_created[MSG_PRESSURE]) { + pr_err("[FACTORY] %s: sensor probe fail\n", __func__); + return snprintf(buf, PAGE_SIZE, "%s\n", PROBE_FAIL); + } + + mutex_lock(&data->accel_factory_mutex); + ret_acc = get_accel_raw_data(raw_data_acc); + mutex_unlock(&data->accel_factory_mutex); + + ret_mag = get_mag_raw_data(raw_data_mag); + + pr_info("[FACTORY] %s: accel(%d, %d, %d), mag(%d, %d, %d)\n", __func__, + raw_data_acc[0], raw_data_acc[1], raw_data_acc[2], + raw_data_mag[0], raw_data_mag[1], raw_data_mag[2]); + + if (ret_acc == -1 && ret_mag == -1) { + pr_err("[FACTORY] %s: SLPI stuck, check hal log\n", __func__); + return snprintf(buf, PAGE_SIZE, "%s\n", SLPI_STUCK); + } else { + return snprintf(buf, PAGE_SIZE, "%s\n", SLPI_PASS); + } +#else + return snprintf(buf, PAGE_SIZE, "%s\n", panic_msg); +#endif +} + +static struct rproc* get_adsp_remoteproc(void) +{ + struct device_node *np; + phandle rproc_phandle; + struct rproc *adsp; + struct property *prop; + int size; + + np = of_find_node_by_name(NULL, "ssc_adsp_rproc_phandle"); + if (np == NULL) { + pr_err("Didn't find node for ssc_adsp_rproc_phandle\n"); + return NULL; + } + prop = of_find_property(np, "qcom,rproc-handle", &size); + if (!prop) { + pr_err("Missing remotproc handle\n"); + return NULL; + } + rproc_phandle = be32_to_cpup(prop->value); + adsp = rproc_get_by_phandle(rproc_phandle); + if (!adsp) { + pr_err("fail to get rproc\n"); + return NULL; + } + + return adsp; +} + +static ssize_t ssr_reset_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rproc *adsp = get_adsp_remoteproc(); + int32_t type = 0xff; + + if (adsp == NULL) { + pr_info("[FACTORY] %s, adsp remoteproc null\n", __func__); + goto reset_return; + } + + adsp_unicast(&type, sizeof(int32_t), MSG_SSC_CORE, 0, MSG_TYPE_DUMPSTATE); + pr_info("[FACTORY] %s, fssr:%d, %d->%d\n", __func__, (int)adsp->fssr, + (int)adsp->prev_recovery_disabled, + (int)adsp->recovery_disabled); + if (adsp->fssr) { + adsp->fssr = false; + adsp->recovery_disabled = adsp->prev_recovery_disabled; + } +reset_return: + return snprintf(buf, PAGE_SIZE, "%s\n", "Success"); +} + +static ssize_t support_algo_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + unsigned int support_algo = 0; + struct adsp_data *data = dev_get_drvdata(dev); + + if (kstrtouint(buf, 10, &support_algo)) { + pr_err("[FACTORY] %s: kstrtouint fail\n", __func__); + return -EINVAL; + } + + data->support_algo = (uint32_t)support_algo; + pr_info("[FACTORY] %s support_algo = %u\n", __func__, support_algo); + + return size; +} + +#if IS_ENABLED(CONFIG_SUPPORT_DEVICE_MODE) +void ssc_flip_work_func(struct work_struct *work) +{ + int32_t msg_buf[4]; +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) + if (pdata_ssc_flip->only_update) { + msg_buf[0] = VOPTIC_OP_CMD_SSC_FLIP_UPDATE; + pdata_ssc_flip->only_update = false; + } else { + msg_buf[0] = VOPTIC_OP_CMD_SSC_FLIP; + } + msg_buf[1] = (int32_t)curr_fstate; + pr_info("[FACTORY] %s: msg_buf = %d\n", __func__, msg_buf[1]); +#if !defined(CONFIG_SEC_FACTORY) || !defined(CONFIG_SUPPORT_SENSOR_FLIP_MODEL) + adsp_unicast(msg_buf, sizeof(msg_buf), + MSG_VIR_OPTIC, 0, MSG_TYPE_OPTION_DEFINE); +#endif +#else + msg_buf[0] = 11; + msg_buf[1] = (int32_t)curr_fstate; + pr_info("[FACTORY] %s: msg_buf = %d\n", __func__, msg_buf[1]); + adsp_unicast(msg_buf, sizeof(msg_buf), + MSG_LIGHT, 0, MSG_TYPE_OPTION_DEFINE); +#endif +} + +void sns_flip_init_work(void) +{ + pr_info("[FACTORY] sns_flip_init_work:%d \n", (int)curr_fstate); + queue_work(pdata_ssc_flip->ssc_flip_wq, + &pdata_ssc_flip->work_ssc_flip); +} + +#ifdef SUPPORT_HALL_NOTIFIER +int sns_device_mode_notify(struct notifier_block *nb, + unsigned long flip_state, void *v) +{ + struct adsp_data *data = container_of(nb, struct adsp_data, adsp_nb); + hall_notifier = v; + + if (strncmp(hall_notifier->name, FLIP_HALL_NAME, 4)) + return 0; + + pr_info("[FACTORY] %s - before device mode curr:%d, fstate:%d", + __func__, curr_fstate, data->fac_fstate); + + curr_fstate = (int32_t)flip_state; +#if !defined(CONFIG_SEC_FACTORY) || !defined(CONFIG_SUPPORT_SENSOR_FLIP_MODEL) + data->fac_fstate = curr_fstate; +#endif + pr_info("[FACTORY] %s - after device mode curr:%d, fstate:%d", + __func__, curr_fstate, data->fac_fstate); + + if(curr_fstate == 0) + adsp_unicast(NULL, 0, MSG_SSC_CORE, 0, MSG_TYPE_FACTORY_ENABLE); + else + adsp_unicast(NULL, 0, MSG_SSC_CORE, 0, MSG_TYPE_FACTORY_DISABLE); + + // send the flip state by qmi. + queue_work(pdata_ssc_flip->ssc_flip_wq, &pdata_ssc_flip->work_ssc_flip); + + return 0; +} +#endif + +void sns_device_mode_init_work(void) +{ + if(curr_fstate == 0) + adsp_unicast(NULL, 0, MSG_SSC_CORE, 0, MSG_TYPE_FACTORY_ENABLE); + else + adsp_unicast(NULL, 0, MSG_SSC_CORE, 0, MSG_TYPE_FACTORY_DISABLE); +} +#endif + +#if IS_ENABLED(CONFIG_SUPPORT_VIRTUAL_OPTIC) +static ssize_t fac_fstate_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + int32_t fstate[2] = {VOPTIC_OP_CMD_FAC_FLIP, 0}; + + mutex_lock(&data->vir_optic_factory_mutex); + + if (sysfs_streq(buf, "0")) + data->fac_fstate = fstate[1] = curr_fstate; + else if (sysfs_streq(buf, "1")) + data->fac_fstate = fstate[1] = FSTATE_FAC_INACTIVE; + else if (sysfs_streq(buf, "2")) + data->fac_fstate = fstate[1] = FSTATE_FAC_ACTIVE; + else if (sysfs_streq(buf, "3")) + data->fac_fstate = fstate[1] = FSTATE_FAC_INACTIVE_2; +#if IS_ENABLED(CONFIG_TABLET_MODEL_CONCEPT) + else if (sysfs_streq(buf, "13")) + data->fac_fstate = fstate[1] = LIGHT_DUAL_CHECK_MODE; +#endif + else + data->fac_fstate = fstate[1] = curr_fstate; + + adsp_unicast(fstate, sizeof(fstate), + MSG_VIR_OPTIC, 0, MSG_TYPE_OPTION_DEFINE); + while (!(data->ready_flag[MSG_TYPE_OPTION_DEFINE] & 1 << MSG_VIR_OPTIC) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_OPTION_DEFINE] &= ~(1 << MSG_VIR_OPTIC); + + if (cnt >= TIMEOUT_CNT) + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + + mutex_unlock(&data->vir_optic_factory_mutex); + + pr_info("[SSC_FAC] %s - Factory flip state:%d(%d)", + __func__, fstate[1], data->msg_buf[MSG_VIR_OPTIC][0]); + + return size; +} +#endif + +static ssize_t wakeup_reason_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + int32_t msg_buf[2] = {OPTION_TYPE_SSC_WAKEUP_REASON, 0}; + + adsp_unicast(msg_buf, sizeof(msg_buf), MSG_SSC_CORE, 0, MSG_TYPE_GET_CAL_DATA); + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] & 1 << MSG_SSC_CORE) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << MSG_SSC_CORE); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "0\n"); + } + + return snprintf(buf, PAGE_SIZE, "\ +\"SW_SMD\":\"%d\",\"SW_TILT\":\"%d\",\"SW_PICKUP\":\"%d\",\"SW_SBM\":\"%d\",\"SW_WU_MOTION\":\"%d\",\ +\"SW_PROX\":\"%d\",\"SW_CALLGESTURE\":\"%d\",\"SW_POCKET\":\"%d\",\"SW_LEDCOVER\":\"%d\",\"SW_FCD\":\"%d\",\ +\"SW_DROPCLASSIFIER\":\"%d\",\"SW_POCKET_POSE\":\"%d\",\"SW_STEPCOUNT_ALERT\":\"%d\",\"SW_FOLDING_LPM\":\"%d\",\"SW_HINGE_ANGLE\":\"%d\",\ +\"SW_LID_ANGLE\":\"%d\",\"SW_ANGLE_STATUS\":\"%d\",\"SW_SMARTALERT\":\"%d\",\"SW_SEM_ROTATE\":\"%d\",\"SW_WCD\":\"%d\",\ +\"SW_PUTDOWN\":\"%d\",\"SW_SLOCATION\":\"%d\",\"SW_AMD\":\"%d\",\"SW_AOD\":\"%d\",\"SW_FLATMOTION\":\"%d\",\ +\"SW_SENSORCHECK\":\"%d\",\"SW_D_POSITION\":\"%d\",\"SW_LTG\":\"%d\",\"SW_FREEFALL\":\"%d\",\"SW_PEDOMETER\":\"%d\",\ +\"SW_SLM\":\"%d\",\"SW_AT\":\"%d\"\n", + data->msg_buf[MSG_SSC_CORE][0], data->msg_buf[MSG_SSC_CORE][1], + data->msg_buf[MSG_SSC_CORE][2], data->msg_buf[MSG_SSC_CORE][3], + data->msg_buf[MSG_SSC_CORE][4], data->msg_buf[MSG_SSC_CORE][5], + data->msg_buf[MSG_SSC_CORE][6], data->msg_buf[MSG_SSC_CORE][7], + data->msg_buf[MSG_SSC_CORE][8], data->msg_buf[MSG_SSC_CORE][9], + data->msg_buf[MSG_SSC_CORE][10], data->msg_buf[MSG_SSC_CORE][11], + data->msg_buf[MSG_SSC_CORE][12], data->msg_buf[MSG_SSC_CORE][13], + data->msg_buf[MSG_SSC_CORE][14], data->msg_buf[MSG_SSC_CORE][15], + data->msg_buf[MSG_SSC_CORE][16], data->msg_buf[MSG_SSC_CORE][17], + data->msg_buf[MSG_SSC_CORE][18], data->msg_buf[MSG_SSC_CORE][19], + data->msg_buf[MSG_SSC_CORE][20], data->msg_buf[MSG_SSC_CORE][21], + data->msg_buf[MSG_SSC_CORE][22], data->msg_buf[MSG_SSC_CORE][23], + data->msg_buf[MSG_SSC_CORE][24], data->msg_buf[MSG_SSC_CORE][25], + data->msg_buf[MSG_SSC_CORE][26], data->msg_buf[MSG_SSC_CORE][27], + data->msg_buf[MSG_SSC_CORE][28], data->msg_buf[MSG_SSC_CORE][29], + data->msg_buf[MSG_SSC_CORE][30], + (data->msg_buf[MSG_SSC_CORE][31] + data->msg_buf[MSG_SSC_CORE][32] + + data->msg_buf[MSG_SSC_CORE][33] + data->msg_buf[MSG_SSC_CORE][34] + data->msg_buf[MSG_SSC_CORE][35])); +} + +static ssize_t probe_fail_reason_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); + uint8_t cnt = 0; + + adsp_unicast(NULL, 0, MSG_REG_SNS, 0, MSG_TYPE_OPTION_DEFINE); + while (!(data->ready_flag[MSG_TYPE_OPTION_DEFINE] & 1 << MSG_REG_SNS) && + cnt++ < TIMEOUT_CNT) + usleep_range(500, 550); + + data->ready_flag[MSG_TYPE_OPTION_DEFINE] &= ~(1 << MSG_REG_SNS); + + if (cnt >= TIMEOUT_CNT) { + pr_err("[FACTORY] %s: Timeout!!!\n", __func__); + return snprintf(buf, PAGE_SIZE, "0\n"); + } + + if (!data->send_probe_fail_msg) { + data->send_probe_fail_msg = true; + return snprintf(buf, PAGE_SIZE, "\ +\"ACCEL_REASON\":\"%d\",\"ACCEL_INFO\":\"%d\",\"SUBACCEL_REASON\":\"%d\",\"SUBACCEL_INFO\":\"%d\",\ +\"MAG_REASON\":\"%d\",\"MAG_INFO\":\"%d\",\ +\"PROX_REASON\":\"%d\",\"PROX_INFO\":\"%d\",\"SUBPROX_REASON\":\"%d\",\"SUBPROX_INFO\":\"%d\",\ +\"LIGHT_REASON\":\"%d\",\"LIGHT_INFO\":\"%d\",\"SUBLIGHT_REASON\":\"%d\",\"SUBLIGHT_INFO\":\"%d\",\ +\"PRESSURE_REASON\":\"%d\",\"PREESURE_INFO\":\"%d\",\ +\n", + data->msg_buf[MSG_REG_SNS][1], data->msg_buf[MSG_REG_SNS][2], + data->msg_buf[MSG_REG_SNS][4], data->msg_buf[MSG_REG_SNS][5], + data->msg_buf[MSG_REG_SNS][7], data->msg_buf[MSG_REG_SNS][8], + data->msg_buf[MSG_REG_SNS][10], data->msg_buf[MSG_REG_SNS][11], + data->msg_buf[MSG_REG_SNS][13], data->msg_buf[MSG_REG_SNS][14], + data->msg_buf[MSG_REG_SNS][16], data->msg_buf[MSG_REG_SNS][17], + data->msg_buf[MSG_REG_SNS][19], data->msg_buf[MSG_REG_SNS][20], + data->msg_buf[MSG_REG_SNS][22], data->msg_buf[MSG_REG_SNS][23]); + } else { + return snprintf(buf, PAGE_SIZE, ""); + } +} + +#if IS_ENABLED(CONFIG_SUPPORT_DEVICE_MODE) && IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) +static ssize_t update_ssc_flip_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + pdata_ssc_flip->only_update = true; + queue_work(pdata_ssc_flip->ssc_flip_wq, &pdata_ssc_flip->work_ssc_flip); + pr_info("[FACTORY] %s", __func__); + + return size; +} +#endif + +static ssize_t support_dual_sensor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("[FACTORY] %s: %s\n", __func__, SUPPORT_DUAL_SENSOR); + return snprintf(buf, PAGE_SIZE, "%s\n", SUPPORT_DUAL_SENSOR); +} + +static ssize_t algo_lcd_onoff_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int32_t msg_buf[2] = {0, }; + int cmd_type; + + if (kstrtoint(buf, 10, &cmd_type)) { + pr_err("[FACTORY] %s: kstrtoint fail\n", __func__); + return size; + } + + switch (cmd_type) { + case COMMON_DATA_SET_ABS_ON: + case COMMON_DATA_SET_ABS_OFF: + msg_buf[0] = OPTION_TYPE_SSC_ABS_LCD_TYPE; + msg_buf[1] = (cmd_type == COMMON_DATA_SET_ABS_ON) ? 1 : 0; + break; + case COMMON_DATA_SET_LCD_INTENT_ON: + case COMMON_DATA_SET_LCD_INTENT_OFF: + msg_buf[0] = OPTION_TYPE_SSC_LCD_INTENT_TYPE; + msg_buf[1] = (cmd_type == COMMON_DATA_SET_LCD_INTENT_ON) ? 1 : 0; + break; + default: + pr_info("[FACTORY] %s: Not support:%d \n", __func__, cmd_type); + return size; + } + + pr_info("[FACTORY] %s: lcd:%d,%d\n", __func__, msg_buf[0], msg_buf[1]); + adsp_unicast(msg_buf, sizeof(msg_buf), + MSG_SSC_CORE, 0, MSG_TYPE_OPTION_DEFINE); + + return size; +} + +static void print_sensor_dump(struct adsp_data *data, int sensor) +{ + int i = 0; + + switch (sensor) { + case MSG_ACCEL: +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_6AXIS) + case MSG_ACCEL_SUB: +#endif + for (i = 0; i < 8; i++) { + pr_info("[FACTORY] %s - %d: %02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x\n", + __func__, sensor, + data->msg_buf[sensor][i * 16 + 0], + data->msg_buf[sensor][i * 16 + 1], + data->msg_buf[sensor][i * 16 + 2], + data->msg_buf[sensor][i * 16 + 3], + data->msg_buf[sensor][i * 16 + 4], + data->msg_buf[sensor][i * 16 + 5], + data->msg_buf[sensor][i * 16 + 6], + data->msg_buf[sensor][i * 16 + 7], + data->msg_buf[sensor][i * 16 + 8], + data->msg_buf[sensor][i * 16 + 9], + data->msg_buf[sensor][i * 16 + 10], + data->msg_buf[sensor][i * 16 + 11], + data->msg_buf[sensor][i * 16 + 12], + data->msg_buf[sensor][i * 16 + 13], + data->msg_buf[sensor][i * 16 + 14], + data->msg_buf[sensor][i * 16 + 15]); + } + break; + case MSG_MAG: + pr_info("[FACTORY] %s - %d: [00h-03h] %02x,%02x,%02x,%02x [10h-16h,18h] %02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x [30h-32h] %02x,%02x,%02x\n", + __func__, sensor, + data->msg_buf[sensor][0], data->msg_buf[sensor][1], + data->msg_buf[sensor][2], data->msg_buf[sensor][3], + data->msg_buf[sensor][4], data->msg_buf[sensor][5], + data->msg_buf[sensor][6], data->msg_buf[sensor][7], + data->msg_buf[sensor][8], data->msg_buf[sensor][9], + data->msg_buf[sensor][10], data->msg_buf[sensor][11], + data->msg_buf[sensor][12], data->msg_buf[sensor][13], + data->msg_buf[sensor][14]); + break; + case MSG_PRESSURE: + for (i = 0; i < 7; i++) { + pr_info("[FACTORY] %s - %d: %02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x\n", + __func__, sensor, + data->msg_buf[sensor][i * 16 + 0], + data->msg_buf[sensor][i * 16 + 1], + data->msg_buf[sensor][i * 16 + 2], + data->msg_buf[sensor][i * 16 + 3], + data->msg_buf[sensor][i * 16 + 4], + data->msg_buf[sensor][i * 16 + 5], + data->msg_buf[sensor][i * 16 + 6], + data->msg_buf[sensor][i * 16 + 7], + data->msg_buf[sensor][i * 16 + 8], + data->msg_buf[sensor][i * 16 + 9], + data->msg_buf[sensor][i * 16 + 10], + data->msg_buf[sensor][i * 16 + 11], + data->msg_buf[sensor][i * 16 + 12], + data->msg_buf[sensor][i * 16 + 13], + data->msg_buf[sensor][i * 16 + 14], + data->msg_buf[sensor][i * 16 + 15]); + } + pr_info("[FACTORY] %s - %d: %02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x\n", + __func__, sensor, + data->msg_buf[sensor][i * 16 + 0], + data->msg_buf[sensor][i * 16 + 1], + data->msg_buf[sensor][i * 16 + 2], + data->msg_buf[sensor][i * 16 + 3], + data->msg_buf[sensor][i * 16 + 4], + data->msg_buf[sensor][i * 16 + 5], + data->msg_buf[sensor][i * 16 + 6], + data->msg_buf[sensor][i * 16 + 7]); + break; + case MSG_LIGHT: + pr_info("[FACTORY] %s - %d: %d,%d,%d,%d,%d,%d,%d [P]:%d,%d,%d [L]:%d,%d,%d [E]:%d,%d,%d\n", + __func__, sensor, + data->msg_buf[sensor][0], data->msg_buf[sensor][1], + data->msg_buf[sensor][2], data->msg_buf[sensor][3], + data->msg_buf[sensor][4], data->msg_buf[sensor][5], + data->msg_buf[sensor][6], data->msg_buf[sensor][7], + data->msg_buf[sensor][8], data->msg_buf[sensor][9], + data->msg_buf[sensor][10], data->msg_buf[sensor][11], + data->msg_buf[sensor][12], data->msg_buf[sensor][13], + data->msg_buf[sensor][14], data->msg_buf[sensor][15]); +#if IS_ENABLED(CONFIG_SUPPORT_LIGHT_CALIBRATION) && IS_ENABLED(CONFIG_SUPPORT_PROX_CALIBRATION) + pr_info("[FACTORY] %s - %d: [L] R:%d, %d, %d, W:%d, P:%d\n", + __func__, sensor, data->light_cal_result, + data->light_cal1, data->light_cal2, + data->copr_w, data->prox_cal); +#endif + break; +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) + case MSG_LIGHT_SUB: + pr_info("[FACTORY] %s - %d: %d,%d,%d,%d,%d,%d,%d [P]:%d,%d,%d [L]:%d,%d,%d [E]:%d,%d,%d\n", + __func__, sensor, + data->msg_buf[sensor][0], data->msg_buf[sensor][1], + data->msg_buf[sensor][2], data->msg_buf[sensor][3], + data->msg_buf[sensor][4], data->msg_buf[sensor][5], + data->msg_buf[sensor][6], data->msg_buf[sensor][7], + data->msg_buf[sensor][8], data->msg_buf[sensor][9], + data->msg_buf[sensor][10], data->msg_buf[sensor][11], + data->msg_buf[sensor][12], data->msg_buf[sensor][13], + data->msg_buf[sensor][14], data->msg_buf[sensor][15]); +#if IS_ENABLED(CONFIG_SUPPORT_LIGHT_CALIBRATION) && IS_ENABLED(CONFIG_SUPPORT_PROX_CALIBRATION) + pr_info("[FACTORY] %s - %d: [L] R:%d, %d, %d, W:%d, P:%d\n", + __func__, sensor, data->sub_light_cal_result, + data->sub_light_cal1, data->sub_light_cal2, + data->sub_copr_w, data->prox_sub_cal); +#endif +#endif + break; +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) + case MSG_DIGITAL_HALL: + pr_info("[FACTORY] %s - %d: ST: %02x, HX/HY/HZ: %d/%d/%d, CNTL: %02x/%02x/%02x, BOP/BRP: %d/%d\n", + __func__, sensor, + data->msg_buf[MSG_DIGITAL_HALL][0], data->msg_buf[MSG_DIGITAL_HALL][1], + data->msg_buf[MSG_DIGITAL_HALL][2], data->msg_buf[MSG_DIGITAL_HALL][3], + data->msg_buf[MSG_DIGITAL_HALL][4], data->msg_buf[MSG_DIGITAL_HALL][5], + data->msg_buf[MSG_DIGITAL_HALL][6], data->msg_buf[MSG_DIGITAL_HALL][7], + data->msg_buf[MSG_DIGITAL_HALL][8]); + break; +#endif + default: + break; + } +} + +static void print_ssr_history(void) +{ + int i; + + if (ssr_idx == NO_SSR) { + pr_info("[FACTORY] No SSR history\n"); + return; + } + + pr_info("[FACTORY] Print SSR history\n"); + for (i = 0; i < HISTORY_CNT; i++) { + if (ssr_history[i][0] == '\0') + break; + + pr_info("[FACTORY] %s\n", ssr_history[i]); + } +} + +static BLOCKING_NOTIFIER_HEAD(sensordump_notifier_list); +int sensordump_notifier_register(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&sensordump_notifier_list, nb); +} +EXPORT_SYMBOL(sensordump_notifier_register); + +int sensordump_notifier_unregister(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&sensordump_notifier_list, nb); +} +EXPORT_SYMBOL(sensordump_notifier_unregister); + +int sensordump_notifier_call_chain(unsigned long val, void *v) +{ + return blocking_notifier_call_chain(&sensordump_notifier_list, val, v); +} +EXPORT_SYMBOL(sensordump_notifier_call_chain); + +static void sensor_get_dhr_info(struct adsp_data *data, int sensor_num) +{ + int cnt = 0; + + pr_info("[FACTORY] %s: %d\n", __func__, sensor_num); + + adsp_unicast(NULL, 0, sensor_num, 0, MSG_TYPE_GET_DHR_INFO); + while (!(data->ready_flag[MSG_TYPE_GET_DHR_INFO] & 1 << sensor_num) && + cnt++ < TIMEOUT_DHR_CNT) + msleep(20); + + data->ready_flag[MSG_TYPE_GET_DHR_INFO] &= ~(1 << sensor_num); + if (cnt >= TIMEOUT_DHR_CNT) { + pr_err("[FACTORY] %s: %d Timeout!!!\n", __func__, sensor_num); + } else { + print_sensor_dump(data, sensor_num); + msleep(200); + } +} +#if IS_ENABLED(CONFIG_SENSORS_GRIP_FAILURE_DEBUG) +static void sensor_get_grip_info(void) +{ + int i = 0; + if (pdata_sdump == NULL) + return; + + for (i = 0; i < GRIP_MAX_CNT; i++) + pr_err("GRIP%d_REASON : %d\n", i, pdata_sdump->grip_error[i]); +} +#endif +void sensor_dump_work_func(struct work_struct *work) +{ + struct sdump_data *sensor_dump_data = container_of((struct work_struct *)work, + struct sdump_data, work_sdump); + struct adsp_data *data = sensor_dump_data->dev_data; +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) + int cnt = 0; +#endif +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_6AXIS) + int sensor_type[SENSOR_DUMP_CNT] = { MSG_ACCEL, MSG_MAG, MSG_PRESSURE, MSG_LIGHT, MSG_ACCEL_SUB }; +#else + int sensor_type[SENSOR_DUMP_CNT] = { MSG_ACCEL, MSG_MAG, MSG_PRESSURE, MSG_LIGHT }; +#endif + int32_t msg_buf[2] = {OPTION_TYPE_SSC_DUMP_TYPE, 0}; + int i; + + sensordump_notifier_call_chain(1, NULL); + + for (i = 0; i < SENSOR_DUMP_CNT; i++) { + if (!data->sysfs_created[sensor_type[i]]) { + pr_info("[FACTORY] %s: %d was not probed\n", + __func__, sensor_type[i]); + continue; + } + + sensor_get_dhr_info(data, sensor_type[i]); + } + +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) + sensor_get_dhr_info(data, MSG_LIGHT_SUB); +#endif +#if IS_ENABLED(CONFIG_SENSORS_GRIP_FAILURE_DEBUG) + sensor_get_grip_info(); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) + if (data->sysfs_created[MSG_DIGITAL_HALL]) + sensor_get_dhr_info(data, MSG_DIGITAL_HALL); + + adsp_unicast(NULL, 0, MSG_DIGITAL_HALL_ANGLE, 0, MSG_TYPE_GET_CAL_DATA); + + while (!(data->ready_flag[MSG_TYPE_GET_CAL_DATA] & 1 << MSG_DIGITAL_HALL_ANGLE) && + cnt++ < 3) + msleep(30); + + data->ready_flag[MSG_TYPE_GET_CAL_DATA] &= ~(1 << MSG_DIGITAL_HALL_ANGLE); + + if (cnt >= 3) + pr_err("[FACTORY] %s: Read D/Hall Auto Cal Table Timeout!!!\n", + __func__); + + pr_info("[FACTORY] %s: flg_update=%d\n", + __func__, data->msg_buf[MSG_DIGITAL_HALL_ANGLE][0]); +#endif + + adsp_unicast(msg_buf, sizeof(msg_buf), + MSG_SSC_CORE, 0, MSG_TYPE_OPTION_DEFINE); + + adsp_unicast(NULL, 0, MSG_REG_SNS, 0, MSG_TYPE_OPTION_DEFINE); + print_ssr_history(); +} + +#define MAX_DUMP_CNT 128 +#define LEN_REGISTER 3 /* "ff " */ +#define LEN_EXTRA 1 /* "\0" "\n" */ +#define LEN_LINE_REG 16 +#define LEN_PREFIX 11 /* "@@TYPE:%d##\n" */ + +static int sensor_handle = -1; +static int sensor_idx = -1; +static int sensor_get_registerdump(struct adsp_data *data, int s_handle, int sensor, char *buf) +{ + int cnt = 0, i = 0; + char dump_buff[MAX_DUMP_CNT * LEN_REGISTER + LEN_EXTRA]; + char temp_buff[LEN_REGISTER * LEN_LINE_REG + LEN_EXTRA]; + + memset(dump_buff, 0, sizeof(dump_buff)); + memset(temp_buff, 0, sizeof(temp_buff)); + + pr_info("[FACTORY] %s: %d\n", __func__, sensor); + + adsp_unicast(NULL, 0, sensor, 0, MSG_TYPE_GET_DHR_INFO); + while (!(data->ready_flag[MSG_TYPE_GET_DHR_INFO] & 1 << sensor) && + cnt++ < TIMEOUT_DHR_CNT) + msleep(20); + + data->ready_flag[MSG_TYPE_GET_DHR_INFO] &= ~(1 << sensor); + if (cnt >= TIMEOUT_DHR_CNT) { + pr_err("[FACTORY] %s: %d Timeout!!!\n", __func__, sensor); + } else { + switch (sensor) { + case MSG_ACCEL: + for (i = 0; i < 8; i++) { + snprintf(temp_buff, sizeof(temp_buff), + "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + data->msg_buf[MSG_ACCEL][i * 16 + 0], + data->msg_buf[MSG_ACCEL][i * 16 + 1], + data->msg_buf[MSG_ACCEL][i * 16 + 2], + data->msg_buf[MSG_ACCEL][i * 16 + 3], + data->msg_buf[MSG_ACCEL][i * 16 + 4], + data->msg_buf[MSG_ACCEL][i * 16 + 5], + data->msg_buf[MSG_ACCEL][i * 16 + 6], + data->msg_buf[MSG_ACCEL][i * 16 + 7], + data->msg_buf[MSG_ACCEL][i * 16 + 8], + data->msg_buf[MSG_ACCEL][i * 16 + 9], + data->msg_buf[MSG_ACCEL][i * 16 + 10], + data->msg_buf[MSG_ACCEL][i * 16 + 11], + data->msg_buf[MSG_ACCEL][i * 16 + 12], + data->msg_buf[MSG_ACCEL][i * 16 + 13], + data->msg_buf[MSG_ACCEL][i * 16 + 14], + data->msg_buf[MSG_ACCEL][i * 16 + 15]); + strlcat(dump_buff, temp_buff, sizeof(dump_buff)); + } + break; +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_6AXIS) + case MSG_ACCEL_SUB: + for (i = 0; i < 8; i++) { + snprintf(temp_buff, sizeof(temp_buff), + "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + data->msg_buf[MSG_ACCEL_SUB][i * 16 + 0], + data->msg_buf[MSG_ACCEL_SUB][i * 16 + 1], + data->msg_buf[MSG_ACCEL_SUB][i * 16 + 2], + data->msg_buf[MSG_ACCEL_SUB][i * 16 + 3], + data->msg_buf[MSG_ACCEL_SUB][i * 16 + 4], + data->msg_buf[MSG_ACCEL_SUB][i * 16 + 5], + data->msg_buf[MSG_ACCEL_SUB][i * 16 + 6], + data->msg_buf[MSG_ACCEL_SUB][i * 16 + 7], + data->msg_buf[MSG_ACCEL_SUB][i * 16 + 8], + data->msg_buf[MSG_ACCEL_SUB][i * 16 + 9], + data->msg_buf[MSG_ACCEL_SUB][i * 16 + 10], + data->msg_buf[MSG_ACCEL_SUB][i * 16 + 11], + data->msg_buf[MSG_ACCEL_SUB][i * 16 + 12], + data->msg_buf[MSG_ACCEL_SUB][i * 16 + 13], + data->msg_buf[MSG_ACCEL_SUB][i * 16 + 14], + data->msg_buf[MSG_ACCEL_SUB][i * 16 + 15]); + strlcat(dump_buff, temp_buff, sizeof(dump_buff)); + } + break; +#endif + case MSG_MAG: + snprintf(temp_buff, sizeof(temp_buff), + "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + data->msg_buf[MSG_MAG][0], data->msg_buf[MSG_MAG][1], + data->msg_buf[MSG_MAG][2], data->msg_buf[MSG_MAG][3], + data->msg_buf[MSG_MAG][4], data->msg_buf[MSG_MAG][5], + data->msg_buf[MSG_MAG][6], data->msg_buf[MSG_MAG][7], + data->msg_buf[MSG_MAG][8], data->msg_buf[MSG_MAG][9], + data->msg_buf[MSG_MAG][10], data->msg_buf[MSG_MAG][11], + data->msg_buf[MSG_MAG][12], data->msg_buf[MSG_MAG][13], + data->msg_buf[MSG_MAG][14]); + strlcat(dump_buff, temp_buff, sizeof(dump_buff)); + break; + case MSG_PRESSURE: + for (i = 0; i < 7; i++) { + snprintf(temp_buff, sizeof(temp_buff), + "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + data->msg_buf[MSG_PRESSURE][i * 16 + 0], + data->msg_buf[MSG_PRESSURE][i * 16 + 1], + data->msg_buf[MSG_PRESSURE][i * 16 + 2], + data->msg_buf[MSG_PRESSURE][i * 16 + 3], + data->msg_buf[MSG_PRESSURE][i * 16 + 4], + data->msg_buf[MSG_PRESSURE][i * 16 + 5], + data->msg_buf[MSG_PRESSURE][i * 16 + 6], + data->msg_buf[MSG_PRESSURE][i * 16 + 7], + data->msg_buf[MSG_PRESSURE][i * 16 + 8], + data->msg_buf[MSG_PRESSURE][i * 16 + 9], + data->msg_buf[MSG_PRESSURE][i * 16 + 10], + data->msg_buf[MSG_PRESSURE][i * 16 + 11], + data->msg_buf[MSG_PRESSURE][i * 16 + 12], + data->msg_buf[MSG_PRESSURE][i * 16 + 13], + data->msg_buf[MSG_PRESSURE][i * 16 + 14], + data->msg_buf[MSG_PRESSURE][i * 16 + 15]); + strlcat(dump_buff, temp_buff, sizeof(dump_buff)); + } + snprintf(temp_buff, sizeof(temp_buff), + "%02x %02x %02x %02x %02x %02x %02x %02x\n", + data->msg_buf[MSG_PRESSURE][i * 16 + 0], + data->msg_buf[MSG_PRESSURE][i * 16 + 1], + data->msg_buf[MSG_PRESSURE][i * 16 + 2], + data->msg_buf[MSG_PRESSURE][i * 16 + 3], + data->msg_buf[MSG_PRESSURE][i * 16 + 4], + data->msg_buf[MSG_PRESSURE][i * 16 + 5], + data->msg_buf[MSG_PRESSURE][i * 16 + 6], + data->msg_buf[MSG_PRESSURE][i * 16 + 7]); + strlcat(dump_buff, temp_buff, sizeof(dump_buff)); + break; + case MSG_LIGHT: + snprintf(temp_buff, sizeof(temp_buff), + "%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n", + data->msg_buf[MSG_LIGHT][0], data->msg_buf[MSG_LIGHT][1], + data->msg_buf[MSG_LIGHT][2], data->msg_buf[MSG_LIGHT][3], + data->msg_buf[MSG_LIGHT][4], data->msg_buf[MSG_LIGHT][5], + data->msg_buf[MSG_LIGHT][6], data->msg_buf[MSG_LIGHT][7], + data->msg_buf[MSG_LIGHT][8], data->msg_buf[MSG_LIGHT][9], + data->msg_buf[MSG_LIGHT][10], data->msg_buf[MSG_LIGHT][11], + data->msg_buf[MSG_LIGHT][12], data->msg_buf[MSG_LIGHT][13], + data->msg_buf[MSG_LIGHT][14], data->msg_buf[MSG_LIGHT][15]); + strlcat(dump_buff, temp_buff, sizeof(dump_buff)); + break; + } + msleep(20); + } + return snprintf(buf, sizeof(dump_buff) + LEN_PREFIX, + "@@TYPE:%d##\n%s", s_handle, dump_buff); +} + +static ssize_t sensor_dump_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adsp_data *data = dev_get_drvdata(dev); +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_6AXIS) + int sensor_type[SENSOR_RDUMP_CNT] = { MSG_ACCEL, MSG_MAG, MSG_ACCEL, MSG_LIGHT, MSG_PRESSURE, MSG_ACCEL_SUB }; + int arr_handle[SENSOR_RDUMP_CNT] = { + SENSOR_HANDLE_ACCELEROMETER, + SENSOR_HANDLE_GEOMAGNETIC_FIELD, + SENSOR_HANDLE_GYROSCOPE, + SENSOR_HANDLE_LIGHT, + SENSOR_HANDLE_PRESSURE, + SENSOR_HANDLE_ACCELEROMETER_SUB }; +#else + int sensor_type[SENSOR_RDUMP_CNT] = { MSG_ACCEL, MSG_MAG, MSG_ACCEL, MSG_LIGHT, MSG_PRESSURE }; + int arr_handle[SENSOR_RDUMP_CNT] = { + SENSOR_HANDLE_ACCELEROMETER, + SENSOR_HANDLE_GEOMAGNETIC_FIELD, + SENSOR_HANDLE_GYROSCOPE, + SENSOR_HANDLE_LIGHT, + SENSOR_HANDLE_PRESSURE }; +#endif + char dump_buff[(MAX_DUMP_CNT * LEN_REGISTER + LEN_PREFIX) * SENSOR_DUMP_CNT]; + char rbuf[MAX_DUMP_CNT * LEN_REGISTER + LEN_PREFIX + LEN_EXTRA]; + char prefix[] = "!@#REG_DUMP!@#\n"; + + memset(dump_buff, 0, sizeof(dump_buff)); + memset(rbuf, 0, sizeof(rbuf)); + + if (sensor_handle == -1) { + pdata_sdump->dev_data = data; + queue_work(pdata_sdump->sdump_wq, &pdata_sdump->work_sdump); + return snprintf(buf, PAGE_SIZE, "%s\n", SENSOR_DUMP_DONE); + } else { + switch (sensor_handle) { + case SENSOR_HANDLE_ALL: + for (int i = 0; i < SENSOR_RDUMP_CNT; i++) { + if (!data->sysfs_created[sensor_type[i]]) { + pr_info("[FACTORY] %s: %d was not probed\n", + __func__, sensor_type[i]); + continue; + } + sensor_get_registerdump(data, arr_handle[i], sensor_type[i], rbuf); + strlcat(dump_buff, rbuf, sizeof(dump_buff)); + } + break; + default: + if (!data->sysfs_created[sensor_idx]) { + pr_info("[FACTORY] %s: %d was not probed\n", __func__, sensor_idx); + break; + } + sensor_get_registerdump(data, sensor_handle, sensor_idx, dump_buff); + break; + } + + sensor_handle = -1; + return snprintf(buf, PAGE_SIZE, "%s%s%s", prefix, dump_buff, prefix); + } +} + +static ssize_t sensor_dump_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + char name[SENSOR_NAME_MAX + 1] = {0,}; + + if (sscanf(buf, "%40s", name) != 1) { + return -EINVAL; + } + + if (strcmp(name, "all") == 0) { + sensor_handle = SENSOR_HANDLE_ALL; + } else if (strcmp(name, "accelerometer") == 0) { + sensor_handle = SENSOR_HANDLE_ACCELEROMETER; + sensor_idx = MSG_ACCEL; +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_6AXIS) + } else if (strcmp(name, "accelerometer_sub") == 0) { + sensor_handle = SENSOR_HANDLE_ACCELEROMETER_SUB; + sensor_idx = MSG_ACCEL_SUB; +#endif + } else if (strcmp(name, "gyro") == 0) { + sensor_handle = SENSOR_HANDLE_GYROSCOPE; + sensor_idx = MSG_ACCEL; + } else if (strcmp(name, "magnetic") == 0) { + sensor_handle = SENSOR_HANDLE_GEOMAGNETIC_FIELD; + sensor_idx = MSG_MAG; + } else if (strcmp(name, "pressure") == 0) { + sensor_handle = SENSOR_HANDLE_PRESSURE; + sensor_idx = MSG_PRESSURE; + } else if (strcmp(name, "light") == 0) { + sensor_handle = SENSOR_HANDLE_LIGHT; + sensor_idx = MSG_LIGHT; + } else { + pr_info("[FACTORY] not supported : %s", name); + sensor_handle = -1; + sensor_idx = -1; + return -EINVAL; + } + return size; +} + +#if IS_ENABLED(CONFIG_SUPPORT_SSC_SPU) +static char ver_buf[SPU_VER_LEN]; +static ssize_t ssc_firmware_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (fw_idx == SSC_SPU) { + return snprintf(buf, PAGE_SIZE, "idx:%d, CL:%s\n", fw_idx, ver_buf); + } else { + return snprintf(buf, PAGE_SIZE, "idx:1, CL:0, 0-0-0, 0:0:0.0\n"); + } +} + +static ssize_t ssc_firmware_info_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int min_len = 0; + int buf_len = strlen(buf); + + pr_info("[FACTORY] %s:buf:%s, %d\n", __func__, buf, buf_len); + min_len = buf_len > SPU_VER_LEN ? SPU_VER_LEN : buf_len; + memcpy(ver_buf, buf, min_len); + + return size; +} +#endif + +#ifdef CONFIG_SUPPORT_SSC_MODE +static int ssc_mode; +static ssize_t ssc_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("[FACTORY] ssc_mode:%d\n", ssc_mode); + + return snprintf(buf, PAGE_SIZE, "%d\n", ssc_mode); +} + +static ssize_t ssc_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + if (kstrtoint(buf, 10, &ssc_mode)) { + pr_err("[FACTORY] %s: kstrtoint fail\n", __func__); + return -EINVAL; + } + + pr_info("[FACTORY] ssc_mode %d\n", ssc_mode); + + return size; +} +#endif + +#if IS_ENABLED(CONFIG_SUPPORT_LIGHT_SEAMLESS) +static int light_seamless_lux_low, light_seamless_lux_high; +static int sub_light_seamless_lux_low, sub_light_seamless_lux_high; +void light_seamless_work_func(struct work_struct *work) +{ + if (light_seamless_lux_low != 0 + || light_seamless_lux_high != 0 + || sub_light_seamless_lux_low != 0 + || sub_light_seamless_lux_high != 0) { + int32_t msg_buf[5] = {0, }; + msg_buf[0] = OPTION_TYPE_SSC_LIGHT_SEAMLESS; + msg_buf[1] = light_seamless_lux_low; + msg_buf[2] = light_seamless_lux_high; + msg_buf[3] = sub_light_seamless_lux_low; + msg_buf[4] = sub_light_seamless_lux_high; + adsp_unicast(msg_buf, sizeof(msg_buf), + MSG_SSC_CORE, 0, MSG_TYPE_OPTION_DEFINE); + } + pr_info("[FACTORY] light seamless init, M%d,%d, S:%d,%d\n", + light_seamless_lux_low, light_seamless_lux_high, + sub_light_seamless_lux_low, sub_light_seamless_lux_high); +} + +static ssize_t light_seamless_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("[FACTORY] light seamless M%d,%d, S:%d,%d\n", + light_seamless_lux_low, light_seamless_lux_high, + sub_light_seamless_lux_low, sub_light_seamless_lux_high); + return snprintf(buf, PAGE_SIZE, "M:%d,%d, S:%d,%d\n", + light_seamless_lux_low, light_seamless_lux_high, + sub_light_seamless_lux_low, sub_light_seamless_lux_high); +} + +static ssize_t light_seamless_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int32_t msg_buf[5] = {0, }; + int32_t ret = 0; + + ret = sscanf(buf, "%5d,%5d,%5d,%5d", + &light_seamless_lux_low, &light_seamless_lux_high, + &sub_light_seamless_lux_low, &sub_light_seamless_lux_high); + if (ret != 4) { + pr_err("[FACTORY]: %s - The number of data are wrong,%d\n", + __func__, ret); + return -EINVAL; + } + + msg_buf[0] = OPTION_TYPE_SSC_LIGHT_SEAMLESS; + msg_buf[1] = light_seamless_lux_low; + msg_buf[2] = light_seamless_lux_high; + msg_buf[3] = sub_light_seamless_lux_low; + msg_buf[4] = sub_light_seamless_lux_high; + adsp_unicast(msg_buf, sizeof(msg_buf), + MSG_SSC_CORE, 0, MSG_TYPE_OPTION_DEFINE); + pr_info("[FACTORY] light_seamless_lux, M:%d,%d, S:%d,%d\n", + light_seamless_lux_low, light_seamless_lux_high, + sub_light_seamless_lux_low, sub_light_seamless_lux_high); + + return size; +} + +void light_seamless_init_work(struct adsp_data *data) +{ + schedule_delayed_work(&data->light_seamless_work, msecs_to_jiffies(5000)); +} +#endif + +static ssize_t ar_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int32_t msg_buf[2] = {OPTION_TYPE_SSC_AUTO_ROTATION_MODE, 0}; + + msg_buf[1] = buf[0] - 48; + pr_info("[FACTORY]%s: ar_mode:%d\n", __func__, msg_buf[1]); + adsp_unicast(msg_buf, sizeof(msg_buf), + MSG_SSC_CORE, 0, MSG_TYPE_OPTION_DEFINE); + + return size; +} + +static int sbm_init; +static ssize_t sbm_init_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("[FACTORY] %s : %d\n", __func__, sbm_init); + + return snprintf(buf, PAGE_SIZE, "%d\n", sbm_init); +} + +static ssize_t sbm_init_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int32_t msg_buf[2] = {OPTION_TYPE_SSC_SBM_INIT, 0}; + + if (kstrtoint(buf, 10, &sbm_init)) { + pr_err("[FACTORY] %s: kstrtoint fail\n", __func__); + return -EINVAL; + } + + if (sbm_init) { + msg_buf[1] = sbm_init; + pr_info("[FACTORY] %s : %d\n", __func__, sbm_init); + adsp_unicast(msg_buf, sizeof(msg_buf), + MSG_SSC_CORE, 0, MSG_TYPE_OPTION_DEFINE); + } + + return size; +} + +static ssize_t pocket_inject_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int32_t msg_buf[2] = {OPTION_TYPE_SSC_POCKET_INJECT, 0}; + int pocket_inject_data = 0; + + if (kstrtoint(buf, 10, &pocket_inject_data)) { + pr_err("[FACTORY] %s: kstrtoint fail\n", __func__); + return -EINVAL; + } + + if (pocket_inject_data) { + msg_buf[1] = pocket_inject_data; + adsp_unicast(msg_buf, sizeof(msg_buf), + MSG_SSC_CORE, 0, MSG_TYPE_OPTION_DEFINE); + } + + return size; +} + +static ssize_t sns_crash_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int mode; + + if (kstrtoint(buf, 10, &mode)) { + pr_err("[FACTORY] %s: kstrtoint fail\n", __func__); + return -EINVAL; + } + + pr_info("[FACTORY] %s : %d\n", __func__, mode); + if (mode == 1) { + panic("sensor is abnormal, force panic\n"); + } else if (mode == 2) { + schedule_delayed_work(&ssr_dump_work, msecs_to_jiffies(7000)); + } else if (mode == 8 || mode == 9) { // only used in sensordebug + struct rproc *adsp = get_adsp_remoteproc(); + + if (adsp == NULL) { + pr_info("[FACTORY] %s, adsp remoteproc null\n", __func__); + goto crash_return; + } + pr_info("fssr:%d, %d->%d\n", (int)adsp->fssr, + (int)adsp->prev_recovery_disabled, + (int)adsp->recovery_disabled); + + if (mode == 8) { + adsp->recovery_disabled = true; + } else { + adsp->recovery_disabled = false; + } + } +crash_return: + return size; +} + +#if IS_ENABLED(CONFIG_SENSORS_GRIP_FAILURE_DEBUG) +void update_grip_error(u8 idx, u32 error_state) +{ + if ((pdata_sdump == NULL) || (idx >= GRIP_MAX_CNT)) { + pr_info("[FACTORY] %s IC num %d or dump is NULL \n", __func__, + idx); + return; + } + pdata_sdump->grip_error[idx] = error_state; + pr_info("[FACTORY] %s [IC num %d] grip_error %d\n", __func__, + idx, error_state); +} +EXPORT_SYMBOL(update_grip_error); +static ssize_t grip_fail_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i = 0, j = 0; + + if (pdata_sdump != NULL) { + for (i = 0; i < GRIP_MAX_CNT; i++) { + j += snprintf(buf + j, PAGE_SIZE - j, + "\"GRIP%d_REASON\":\"%d\",", + i, pdata_sdump->grip_error[i]); + pr_info("[FACTORY] %s \"GRIP%d_REASON\":\"%d\",", + __func__, i, pdata_sdump->grip_error[i]); + pdata_sdump->grip_error[i] = 0; + } + } + + return j; +} +#endif + +void ssr_dump_work_func(struct work_struct *work) +{ + send_ssc_recovery_command(2); +} + +#if IS_ENABLED(CONFIG_SENSORS_GRIP_FAILURE_DEBUG) +static DEVICE_ATTR(grip_fail, 0440, grip_fail_show, NULL); +#endif +static DEVICE_ATTR(dumpstate, 0440, dumpstate_show, NULL); +static DEVICE_ATTR(operation_mode, 0664, + operation_mode_show, operation_mode_store); +static DEVICE_ATTR(mode, 0660, mode_show, mode_store); +static DEVICE_ATTR(ssc_pid, 0660, pid_show, pid_store); +static DEVICE_ATTR(remove_sysfs, 0220, NULL, remove_sensor_sysfs_store); +static DEVICE_ATTR(ssc_hw_rev, 0664, ssc_hw_rev_show, ssc_hw_rev_store); +static DEVICE_ATTR(ssr_msg, 0440, ssr_msg_show, NULL); +static DEVICE_ATTR(ssr_reset, 0440, ssr_reset_show, NULL); +static DEVICE_ATTR(support_algo, 0220, NULL, support_algo_store); +#if IS_ENABLED(CONFIG_SUPPORT_VIRTUAL_OPTIC) +static DEVICE_ATTR(fac_fstate, 0220, NULL, fac_fstate_store); +#endif +static DEVICE_ATTR(wakeup_reason, 0440, wakeup_reason_show, NULL); +static DEVICE_ATTR(probe_fail_reason, 0440, probe_fail_reason_show, NULL); +#if IS_ENABLED(CONFIG_SUPPORT_DEVICE_MODE) && IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) +static DEVICE_ATTR(update_ssc_flip, 0220, NULL, update_ssc_flip_store); +#endif +static DEVICE_ATTR(support_dual_sensor, 0440, support_dual_sensor_show, NULL); +static DEVICE_ATTR(algo_lcd_onoff, 0220, NULL, algo_lcd_onoff_store); +static DEVICE_ATTR(sensor_dump, 0664, sensor_dump_show, sensor_dump_store); +#if IS_ENABLED(CONFIG_SUPPORT_SSC_SPU) +static DEVICE_ATTR(ssc_firmware_info, 0660, + ssc_firmware_info_show, ssc_firmware_info_store); +#endif +#ifdef CONFIG_SUPPORT_SSC_MODE +static DEVICE_ATTR(ssc_mode, 0664, ssc_mode_show, ssc_mode_store); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_LIGHT_SEAMLESS) +static DEVICE_ATTR(light_seamless, 0660, + light_seamless_show, light_seamless_store); +#endif +static DEVICE_ATTR(ar_mode, 0220, NULL, ar_mode_store); +static DEVICE_ATTR(sbm_init, 0660, sbm_init_show, sbm_init_store); +static DEVICE_ATTR(pocket_inject, 0220, NULL, pocket_inject_store); +static DEVICE_ATTR(sns_crash, 0220, NULL, sns_crash_store); + +static struct device_attribute *core_attrs[] = { + &dev_attr_dumpstate, + &dev_attr_operation_mode, + &dev_attr_mode, + &dev_attr_ssc_pid, + &dev_attr_remove_sysfs, + &dev_attr_ssc_hw_rev, + &dev_attr_ssr_msg, + &dev_attr_ssr_reset, + &dev_attr_support_algo, +#if IS_ENABLED(CONFIG_SUPPORT_VIRTUAL_OPTIC) + &dev_attr_fac_fstate, +#endif + &dev_attr_wakeup_reason, + &dev_attr_probe_fail_reason, +#if IS_ENABLED(CONFIG_SUPPORT_DEVICE_MODE) && IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) + &dev_attr_update_ssc_flip, +#endif + &dev_attr_support_dual_sensor, + &dev_attr_algo_lcd_onoff, + &dev_attr_sensor_dump, +#if IS_ENABLED(CONFIG_SUPPORT_SSC_SPU) + &dev_attr_ssc_firmware_info, +#endif +#ifdef CONFIG_SUPPORT_SSC_MODE + &dev_attr_ssc_mode, +#endif +#if IS_ENABLED(CONFIG_SUPPORT_LIGHT_SEAMLESS) + &dev_attr_light_seamless, +#endif + &dev_attr_ar_mode, + &dev_attr_sbm_init, + &dev_attr_pocket_inject, + &dev_attr_sns_crash, +#if IS_ENABLED(CONFIG_SENSORS_GRIP_FAILURE_DEBUG) + &dev_attr_grip_fail, +#endif + NULL, +}; + +#ifdef CONFIG_SUPPORT_SSC_MODE +static int ssc_core_probe(struct platform_device *pdev) +{ +#ifdef CONFIG_SUPPORT_SSC_MODE_FOR_MAG + struct device *dev = &pdev->dev; + +#ifdef CONFIG_OF + if (dev->of_node) { + struct device_node *np = dev->of_node; + int check_mst_gpio, check_nfc_gpio; + int value_mst = 0, value_nfc = 0; + + check_mst_gpio = of_get_named_gpio_flags(np, "ssc_core,mst_gpio", 0, NULL); + if(check_mst_gpio >= 0) + value_mst = gpio_get_value(check_mst_gpio); + + if (value_mst == 1) { + ssc_mode = ANT_NFC_MST; + return 0; + } + + check_nfc_gpio = of_get_named_gpio_flags(np, "ssc_core,nfc_gpio", 0, NULL); + if(check_nfc_gpio >= 0) + value_nfc = gpio_get_value(check_nfc_gpio); + + if (value_nfc == 1) { + ssc_mode = ANT_NFC_ONLY; + return 0; + } + } +#endif + ssc_mode = ANT_DUMMY; +#endif + return 0; +} + +#if defined(CONFIG_OF) +static const struct of_device_id ssc_core_dt_ids[] = { + { .compatible = "ssc_core" }, + { }, +}; +MODULE_DEVICE_TABLE(of, ssc_core_dt_ids); +#endif /* CONFIG_OF */ + +static struct platform_driver ssc_core_driver = { + .probe = ssc_core_probe, + .driver = { + .name = "ssc_core", + .owner = THIS_MODULE, +#if defined(CONFIG_OF) + .of_match_table = ssc_core_dt_ids, +#endif /* CONFIG_OF */ + } +}; +#endif + +int __init core_factory_init(void) +{ +#if IS_ENABLED(CONFIG_SUPPORT_DEVICE_MODE) || defined(CONFIG_VBUS_NOTIFIER) + struct adsp_data *data = adsp_ssc_core_register(MSG_SSC_CORE, core_attrs); +#else + adsp_factory_register(MSG_SSC_CORE, core_attrs); +#endif + pr_info("[FACTORY] %s\n", __func__); + sec_hw_rev_setup(); +#if IS_ENABLED(CONFIG_SUPPORT_DEVICE_MODE) + pdata_ssc_flip = kzalloc(sizeof(*pdata_ssc_flip), GFP_KERNEL); + if (pdata_ssc_flip == NULL) + return -ENOMEM; + + pdata_ssc_flip->ssc_flip_wq = + create_singlethread_workqueue("ssc_flip_wq"); + if (pdata_ssc_flip->ssc_flip_wq == NULL) { + pr_err("[FACTORY]: %s - couldn't create ssc charge wq\n", __func__); + kfree(pdata_ssc_flip); + return -ENOMEM; + } + INIT_WORK(&pdata_ssc_flip->work_ssc_flip, ssc_flip_work_func); +#ifdef SUPPORT_HALL_NOTIFIER + data->adsp_nb.notifier_call = sns_device_mode_notify, + data->adsp_nb.priority = 1, + hall_notifier_register(&data->adsp_nb); +#endif +#endif + +#ifdef CONFIG_SUPPORT_SSC_MODE + platform_driver_register(&ssc_core_driver); +#endif + + pdata_sdump = kzalloc(sizeof(*pdata_sdump), GFP_KERNEL); + if (pdata_sdump == NULL) + return -ENOMEM; + + pdata_sdump->sdump_wq = create_singlethread_workqueue("sdump_wq"); + if (pdata_sdump->sdump_wq == NULL) { + pr_err("[FACTORY]: %s - could not create sdump wq\n", __func__); + kfree(pdata_sdump); + return -ENOMEM; + } + INIT_WORK(&pdata_sdump->work_sdump, sensor_dump_work_func); + + INIT_DELAYED_WORK(&ssr_dump_work, ssr_dump_work_func); + + pr_info("[FACTORY] %s\n", __func__); + +#ifdef CONFIG_VBUS_NOTIFIER + pdata_ssc_charge = kzalloc(sizeof(*pdata_ssc_charge), GFP_KERNEL); + if (pdata_ssc_charge == NULL) + return -ENOMEM; + + pdata_ssc_charge->ssc_charge_wq = + create_singlethread_workqueue("ssc_charge_wq"); + if (pdata_ssc_charge->ssc_charge_wq == NULL) { + pr_err("[FACTORY]: %s - couldn't create ssc charge wq\n", __func__); + kfree(pdata_ssc_charge); + return -ENOMEM; + } + INIT_WORK(&pdata_ssc_charge->work_ssc_charge, ssc_charge_work_func); + pdata_ssc_charge->is_charging = false; + vbus_notifier_register(&data->vbus_nb, + ssc_core_vbus_notifier, VBUS_NOTIFY_DEV_CHARGER); +#endif + pr_info("[FACTORY] %s\n", __func__); + + return 0; +} + +void __exit core_factory_exit(void) +{ +#if IS_ENABLED(CONFIG_SUPPORT_DEVICE_MODE) || defined(CONFIG_VBUS_NOTIFIER) + struct adsp_data *data = adsp_ssc_core_unregister(MSG_SSC_CORE);; +#if IS_ENABLED(CONFIG_SUPPORT_DEVICE_MODE) +#ifdef SUPPORT_HALL_NOTIFIER + hall_notifier_unregister(&data->adsp_nb); +#endif //SUPPORT_HALL_NOTIFIER +#endif +#ifdef CONFIG_VBUS_NOTIFIER + vbus_notifier_unregister(&data->vbus_nb); +#endif +#else + adsp_factory_unregister(MSG_SSC_CORE); +#endif +#ifdef CONFIG_VBUS_NOTIFIER + if (pdata_ssc_charge != NULL && pdata_ssc_charge->ssc_charge_wq != NULL) { + cancel_work_sync(&pdata_ssc_charge->work_ssc_charge); + destroy_workqueue(pdata_ssc_charge->ssc_charge_wq); + pdata_ssc_charge->ssc_charge_wq = NULL; + } +#endif + +#if IS_ENABLED(CONFIG_SUPPORT_DEVICE_MODE) + if (pdata_ssc_flip != NULL && pdata_ssc_flip->ssc_flip_wq != NULL) { + cancel_work_sync(&pdata_ssc_flip->work_ssc_flip); + destroy_workqueue(pdata_ssc_flip->ssc_flip_wq); + pdata_ssc_flip->ssc_flip_wq = NULL; + } +#endif + +#ifdef CONFIG_SUPPORT_SSC_MODE + platform_driver_unregister(&ssc_core_driver); +#endif + if (pdata_sdump->sdump_wq) + destroy_workqueue(pdata_sdump->sdump_wq); + if (pdata_sdump) + kfree(pdata_sdump); + + pr_info("[FACTORY] %s\n", __func__); +} diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index 6f04b831a5c0..2b8fd6bb7da0 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -230,4 +230,16 @@ config GENERIC_ARCH_NUMA Enable support for generic NUMA implementation. Currently, RISC-V and ARM64 use it. +config FW_DEVLINK_SYNC_STATE_TIMEOUT + bool "sync_state() behavior defaults to timeout instead of strict" + help + This is build time equivalent of adding kernel command line parameter + "fw_devlink.sync_state=timeout". Give up waiting on consumers and + call sync_state() on any devices that haven't yet received their + sync_state() calls after deferred_probe_timeout has expired or by + late_initcall() if !CONFIG_MODULES. You should almost always want to + select N here unless you have already successfully tested with the + command line option on every system/board your kernel is expected to + work on. + endmenu diff --git a/drivers/base/base.h b/drivers/base/base.h index b902d1ecc247..24c9c4d2a9ed 100644 --- a/drivers/base/base.h +++ b/drivers/base/base.h @@ -191,6 +191,7 @@ extern void device_links_no_driver(struct device *dev); extern bool device_links_busy(struct device *dev); extern void device_links_unbind_consumers(struct device *dev); extern void fw_devlink_drivers_done(void); +extern void fw_devlink_probing_done(void); /* device pm support */ void device_pm_move_to_tail(struct device *dev); diff --git a/drivers/base/core.c b/drivers/base/core.c index af90bfb0cc3d..bc900ca143c5 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -1726,6 +1726,31 @@ static int __init fw_devlink_strict_setup(char *arg) } early_param("fw_devlink.strict", fw_devlink_strict_setup); +#define FW_DEVLINK_SYNC_STATE_STRICT 0 +#define FW_DEVLINK_SYNC_STATE_TIMEOUT 1 + +#ifndef CONFIG_FW_DEVLINK_SYNC_STATE_TIMEOUT +static int fw_devlink_sync_state; +#else +static int fw_devlink_sync_state = FW_DEVLINK_SYNC_STATE_TIMEOUT; +#endif + +static int __init fw_devlink_sync_state_setup(char *arg) +{ + if (!arg) + return -EINVAL; + + if (strcmp(arg, "strict") == 0) { + fw_devlink_sync_state = FW_DEVLINK_SYNC_STATE_STRICT; + return 0; + } else if (strcmp(arg, "timeout") == 0) { + fw_devlink_sync_state = FW_DEVLINK_SYNC_STATE_TIMEOUT; + return 0; + } + return -EINVAL; +} +early_param("fw_devlink.sync_state", fw_devlink_sync_state_setup); + static inline u32 fw_devlink_get_flags(u8 fwlink_flags) { if (fwlink_flags & FWLINK_FLAG_CYCLE) @@ -1796,6 +1821,44 @@ void fw_devlink_drivers_done(void) device_links_write_unlock(); } +static int fw_devlink_dev_sync_state(struct device *dev, void *data) +{ + struct device_link *link = to_devlink(dev); + struct device *sup = link->supplier; + + if (!(link->flags & DL_FLAG_MANAGED) || + link->status == DL_STATE_ACTIVE || sup->state_synced || + !dev_has_sync_state(sup)) + return 0; + + if (fw_devlink_sync_state == FW_DEVLINK_SYNC_STATE_STRICT) { + dev_warn(sup, "sync_state() pending due to %s\n", + dev_name(link->consumer)); + return 0; + } + + if (!list_empty(&sup->links.defer_sync)) + return 0; + + dev_warn(sup, "Timed out. Forcing sync_state()\n"); + sup->state_synced = true; + get_device(sup); + list_add_tail(&sup->links.defer_sync, data); + + return 0; +} + +void fw_devlink_probing_done(void) +{ + LIST_HEAD(sync_list); + + device_links_write_lock(); + class_for_each_device(&devlink_class, NULL, &sync_list, + fw_devlink_dev_sync_state); + device_links_write_unlock(); + device_links_flush_sync_list(&sync_list, NULL); +} + /** * wait_for_init_devices_probe - Try to probe any device needed for init * diff --git a/drivers/base/dd.c b/drivers/base/dd.c index be0ff2f35e3e..ec7874bc68ec 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -317,6 +317,8 @@ static void deferred_probe_timeout_work_func(struct work_struct *work) list_for_each_entry(p, &deferred_probe_pending_list, deferred_probe) dev_info(p->device, "deferred probe pending\n"); mutex_unlock(&deferred_probe_mutex); + + fw_devlink_probing_done(); } static DECLARE_DELAYED_WORK(deferred_probe_timeout_work, deferred_probe_timeout_work_func); @@ -366,6 +368,10 @@ static int deferred_probe_initcall(void) schedule_delayed_work(&deferred_probe_timeout_work, driver_deferred_probe_timeout * HZ); } + + if (!IS_ENABLED(CONFIG_MODULES)) + fw_devlink_probing_done(); + return 0; } late_initcall(deferred_probe_initcall); diff --git a/drivers/battery/charger/max77705_charger/Kconfig b/drivers/battery/charger/max77705_charger/Kconfig new file mode 100644 index 000000000000..ba2a897f44ca --- /dev/null +++ b/drivers/battery/charger/max77705_charger/Kconfig @@ -0,0 +1,29 @@ +config CHARGER_DUMMY + bool "dummy charger driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y here to enable + support for dummy charger driver. + This driver source code implemented + skeleton source code for charger functions. + +config CHARGER_MAX77705 + tristate "MAX77705 battery charger support" + depends on BATTERY_SAMSUNG + help + Say Y or M here to enable + support for the MAX77705 charger. + This includes boost mode. + This is Switching Mode Charger. + +config MAX77705_CHECK_B2SOVRC + bool "enable for check B2SOVRC" + default n + depends on CHARGER_MAX77705 + help + Say Y here to enable + support for CHARGER_MAX77705 check B2SCOVRC. + MAX77705 onply supports this checking options. + this is for MAX77705 monitoring B2SCOVRC. + diff --git a/drivers/battery/charger/max77705_charger/Makefile b/drivers/battery/charger/max77705_charger/Makefile new file mode 100644 index 000000000000..589fd4f07c70 --- /dev/null +++ b/drivers/battery/charger/max77705_charger/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_CHARGER_MAX77705) += max77705_charger.o + +ccflags-y := -Wformat diff --git a/drivers/battery/charger/max77705_charger/max77705_charger.bzl b/drivers/battery/charger/max77705_charger/max77705_charger.bzl new file mode 100644 index 000000000000..ca416eb261eb --- /dev/null +++ b/drivers/battery/charger/max77705_charger/max77705_charger.bzl @@ -0,0 +1,7 @@ +ko_list = [ + { + "ko_names" : [ + "drivers/battery/charger/max77705_charger/max77705_charger.ko" + ] + } +] diff --git a/drivers/battery/charger/max77705_charger/max77705_charger.c b/drivers/battery/charger/max77705_charger/max77705_charger.c new file mode 100644 index 000000000000..7f83d03e99f2 --- /dev/null +++ b/drivers/battery/charger/max77705_charger/max77705_charger.c @@ -0,0 +1,3155 @@ +/* + * max77705_charger.c + * Samsung max77705 Charger Driver + * + * Copyright (C) 2012 Samsung Electronics + * + * + * 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. + */ +#define DEBUG + +#include +#include +#include +#include +#include +#include +#include "max77705_charger.h" +#include +#ifdef CONFIG_USB_HOST_NOTIFY +#include +#endif +#include + +#define ENABLE 1 +#define DISABLE 0 + +#if defined(CONFIG_SEC_FACTORY) +#define WC_CURRENT_WORK_STEP 250 +#else +#define WC_CURRENT_WORK_STEP 1000 +#endif +#define WC_CURRENT_WORK_STEP_OTG 200 +#define AICL_WORK_DELAY 100 + +static unsigned int __read_mostly lpcharge; +module_param(lpcharge, uint, 0444); +#if defined(CONFIG_SEC_FACTORY) +static int __read_mostly factory_mode; +module_param(factory_mode, int, 0444); +#endif +static int __read_mostly factory_mode_siso; +module_param(factory_mode_siso, int, 0444); + +extern void max77705_usbc_icurr(u8 curr); +extern void max77705_set_fw_noautoibus(int enable); +#if defined(CONFIG_SUPPORT_SHIP_MODE) +extern void max77705_set_fw_ship_mode(int enable); +extern int max77705_get_fw_ship_mode(void); +#endif + +static enum power_supply_property max77705_charger_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static enum power_supply_property max77705_otg_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static struct device_attribute max77705_charger_attrs[] = { + MAX77705_CHARGER_ATTR(chip_id), + MAX77705_CHARGER_ATTR(data), +}; + +static void max77705_charger_initialize(struct max77705_charger_data *charger); +static int max77705_get_vbus_state(struct max77705_charger_data *charger); +static int max77705_get_charger_state(struct max77705_charger_data *charger); +static void max77705_init_aicl_irq(struct max77705_charger_data *charger); +static void max77705_chg_set_mode_state(struct max77705_charger_data *charger, + unsigned int state); +static void max77705_set_switching_frequency(struct max77705_charger_data *charger, + int frequency); + +static unsigned int max77705_get_lpmode(void) { return lpcharge; } +#if defined(CONFIG_SEC_FACTORY) +static unsigned int max77705_get_facmode(void) { return factory_mode; } +#endif +static unsigned int max77705_get_facmode_siso(void) { return factory_mode_siso; } + +static bool max77705_charger_unlock(struct max77705_charger_data *charger) +{ + u8 reg_data, chgprot; + int retry_cnt = 0; + bool need_init = false; + + do { + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_06, ®_data); + chgprot = ((reg_data & 0x0C) >> 2); + if (chgprot != 0x03) { + pr_err("%s: unlock err, chgprot(0x%x), retry(%d)\n", + __func__, chgprot, retry_cnt); + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_06, + (0x03 << 2), (0x03 << 2)); + need_init = true; + msleep(20); + } else { + break; + } + } while ((chgprot != 0x03) && (++retry_cnt < 10)); + + return need_init; +} + +static void check_charger_unlock_state(struct max77705_charger_data *charger) +{ + pr_debug("%s\n", __func__); + + if (max77705_charger_unlock(charger)) { + pr_err("%s: charger locked state, reg init\n", __func__); + max77705_charger_initialize(charger); + } +} + +static void max77705_test_read(struct max77705_charger_data *charger) +{ + u8 data = 0; + u32 addr = 0; + char str[1024] = { 0, }; + + for (addr = 0xB1; addr <= 0xC3; addr++) { + max77705_read_reg(charger->i2c, addr, &data); + sprintf(str + strlen(str), "[0x%02x]0x%02x, ", addr, data); + } + pr_info("max77705 : %s\n", str); +} + +static int max77705_get_autoibus(struct max77705_charger_data *charger) +{ + u8 reg_data; + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_DETAILS_00, ®_data); + if (reg_data & 0x80) + return 1; /* set by charger */ + + return 0; /* set by USBC */ +} + +static int max77705_get_vbus_state(struct max77705_charger_data *charger) +{ + u8 reg_data; + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_DETAILS_00, ®_data); + + if (is_wcin_type(charger->cable_type)) + reg_data = ((reg_data & MAX77705_WCIN_DTLS) >> + MAX77705_WCIN_DTLS_SHIFT); + else + reg_data = ((reg_data & MAX77705_CHGIN_DTLS) >> + MAX77705_CHGIN_DTLS_SHIFT); + + switch (reg_data) { + case 0x00: + pr_info("%s: VBUS is invalid. CHGIN < CHGIN_UVLO\n", __func__); + break; + case 0x01: + pr_info("%s: VBUS is invalid. CHGIN < MBAT+CHGIN2SYS and CHGIN > CHGIN_UVLO\n", __func__); + break; + case 0x02: + pr_info("%s: VBUS is invalid. CHGIN > CHGIN_OVLO\n", __func__); + break; + case 0x03: + pr_info("%s: VBUS is valid. CHGIN < CHGIN_OVLO\n", __func__); + break; + default: + break; + } + + return reg_data; +} + +static int max77705_get_charger_state(struct max77705_charger_data *charger) +{ + int status = POWER_SUPPLY_STATUS_UNKNOWN; + u8 reg_data; + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_DETAILS_01, ®_data); + pr_info("%s : charger status (0x%02x)\n", __func__, reg_data); + + reg_data &= 0x0f; + switch (reg_data) { + case 0x00: + case 0x01: + case 0x02: + status = POWER_SUPPLY_STATUS_CHARGING; + break; + case 0x03: + case 0x04: + status = POWER_SUPPLY_STATUS_FULL; + break; + case 0x05: + case 0x06: + case 0x07: + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case 0x08: + case 0xA: + case 0xB: + status = POWER_SUPPLY_STATUS_DISCHARGING; + break; + default: + break; + } + + return (int)status; +} + +static bool max77705_chg_get_wdtmr_status(struct max77705_charger_data *charger) +{ + u8 reg_data; + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_DETAILS_01, ®_data); + reg_data = ((reg_data & MAX77705_CHG_DTLS) >> MAX77705_CHG_DTLS_SHIFT); + + if (reg_data == 0x0B) { + dev_info(charger->dev, "WDT expired 0x%x !!\n", reg_data); + return true; + } + + return false; +} + +static int max77705_chg_set_wdtmr_en(struct max77705_charger_data *charger, + bool enable) +{ + pr_info("%s: WDT en = %d\n", __func__, enable); + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_00, + (enable ? CHG_CNFG_00_WDTEN_MASK : 0), CHG_CNFG_00_WDTEN_MASK); + + return 0; +} + +static int max77705_chg_set_wdtmr_kick(struct max77705_charger_data *charger) +{ + pr_info("%s: WDT Kick\n", __func__); + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_06, + (MAX77705_WDTCLR << CHG_CNFG_06_WDTCLR_SHIFT), + CHG_CNFG_06_WDTCLR_MASK); + + return 0; +} + +static void max77705_set_float_voltage(struct max77705_charger_data *charger, + int float_voltage) +{ + u8 reg_data = 0; + + if (float_voltage > charger->pdata->chg_float_voltage) { + pr_info("%s: set float voltage(%d <-%d)\n", __func__, + charger->pdata->chg_float_voltage, float_voltage); + float_voltage = charger->pdata->chg_float_voltage; + } +#if defined(CONFIG_SEC_FACTORY) + if (max77705_get_facmode()) { + float_voltage = charger->pdata->fac_vsys; + pr_info("%s: Factory Mode Skip set float voltage(%d)\n", __func__, float_voltage); + // do not return here + } +#endif + reg_data = + (float_voltage == 0) ? 0x13 : + (float_voltage == 3800) ? 0x38 : + (float_voltage == 3900) ? 0x39 : + (float_voltage >= 4500) ? 0x23 : + (float_voltage <= 4200) ? (float_voltage - 4000) / 50 : + (((float_voltage - 4200) / 10) + 0x04); + + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_04, + (reg_data << CHG_CNFG_04_CHG_CV_PRM_SHIFT), + CHG_CNFG_04_CHG_CV_PRM_MASK); + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_04, ®_data); + pr_info("%s: battery cv voltage 0x%x\n", __func__, reg_data); +} + +static int max77705_get_float_voltage(struct max77705_charger_data *charger) +{ + u8 reg_data = 0; + int float_voltage; + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_04, ®_data); + reg_data &= 0x3F; + float_voltage = + (reg_data == 0x39) ? 3900 : + (reg_data == 0x38) ? 3800 : + (reg_data == 0x23) ? 4500 : + (reg_data <= 0x04) ? reg_data * 50 + 4000 : + (reg_data - 4) * 10 + 4200; + pr_debug("%s: battery cv reg : 0x%x, float voltage val : %d\n", + __func__, reg_data, float_voltage); + + return float_voltage; +} + +static int max77705_get_charging_health(struct max77705_charger_data *charger) +{ + union power_supply_propval value = {0,}, val_iin = {0,}, val_vbyp = {0,}; + int state = POWER_SUPPLY_HEALTH_GOOD; + int vbus_state, retry_cnt; + u8 chg_dtls, reg_data, chg_cnfg_00; + bool wdt_status, abnormal_status = false; + + /* watchdog kick */ + max77705_chg_set_wdtmr_kick(charger); + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_DETAILS_01, ®_data); + reg_data = ((reg_data & MAX77705_BAT_DTLS) >> MAX77705_BAT_DTLS_SHIFT); + + if (charger->pdata->enable_noise_wa) { + psy_do_property("battery", get, POWER_SUPPLY_PROP_CAPACITY, value); + if ((value.intval >= 80) && + (charger->fsw_now != MAX77705_CHG_FSW_3MHz)) + max77705_set_switching_frequency(charger, MAX77705_CHG_FSW_3MHz); + else if ((value.intval < 80) && + (charger->fsw_now != MAX77705_CHG_FSW_1_5MHz)) + max77705_set_switching_frequency(charger, MAX77705_CHG_FSW_1_5MHz); + } + + pr_info("%s: reg_data(0x%x)\n", __func__, reg_data); + switch (reg_data) { + case 0x00: + pr_info("%s: No battery and the charger is suspended\n", __func__); + break; + case 0x01: + pr_info("%s: battery is okay but its voltage is low(~VPQLB)\n", __func__); + break; + case 0x02: + pr_info("%s: battery dead\n", __func__); + break; + case 0x03: + break; + case 0x04: + pr_info("%s: battery is okay but its voltage is low\n", __func__); + break; + case 0x05: + pr_info("%s: battery ovp\n", __func__); + break; + default: + pr_info("%s: battery unknown\n", __func__); + break; + } + if (charger->is_charging) { + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_DETAILS_00, ®_data); + pr_info("%s: details00(0x%x)\n", __func__, reg_data); + } + + /* get wdt status */ + wdt_status = max77705_chg_get_wdtmr_status(charger); + + psy_do_property("battery", get, POWER_SUPPLY_EXT_PROP_HEALTH, value); + /* VBUS OVP state return battery OVP state */ + vbus_state = max77705_get_vbus_state(charger); + /* read CHG_DTLS and detecting battery terminal error */ + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_DETAILS_01, &chg_dtls); + chg_dtls = ((chg_dtls & MAX77705_CHG_DTLS) >> MAX77705_CHG_DTLS_SHIFT); + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_00, &chg_cnfg_00); + + /* print the log at the abnormal case */ + if ((charger->is_charging == 1) && !charger->uno_on + && ((chg_dtls == 0x08) || (chg_dtls == 0x0B))) { + max77705_test_read(charger); + max77705_chg_set_mode_state(charger, SEC_BAT_CHG_MODE_CHARGING_OFF); + max77705_set_float_voltage(charger, charger->float_voltage); + max77705_chg_set_mode_state(charger, SEC_BAT_CHG_MODE_CHARGING); + abnormal_status = true; + } + + val_iin.intval = SEC_BATTERY_IIN_MA; + psy_do_property("max77705-fuelgauge", get, + POWER_SUPPLY_EXT_PROP_MEASURE_INPUT, val_iin); + + val_vbyp.intval = SEC_BATTERY_VBYP; + psy_do_property("max77705-fuelgauge", get, + POWER_SUPPLY_EXT_PROP_MEASURE_INPUT, val_vbyp); + + pr_info("%s: vbus_state: 0x%x, chg_dtls: 0x%x, iin: %dmA, vbyp: %dmV, health: %d, abnormal: %s\n", + __func__, vbus_state, chg_dtls, val_iin.intval, + val_vbyp.intval, value.intval, (abnormal_status ? "true" : "false")); + + /* OVP is higher priority */ + if (vbus_state == 0x02) { /* CHGIN_OVLO */ + pr_info("%s: vbus ovp\n", __func__); + if (is_wcin_type(charger->cable_type)) { + retry_cnt = 0; + do { + msleep(50); + vbus_state = max77705_get_vbus_state(charger); + } while ((retry_cnt++ < 2) && (vbus_state == 0x02)); + if (vbus_state == 0x02) { + state = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + pr_info("%s: wpc and over-voltage\n", __func__); + } else { + state = POWER_SUPPLY_HEALTH_GOOD; + } + } else { + state = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } + } else if (((vbus_state == 0x0) || (vbus_state == 0x01)) && (chg_dtls & 0x08) + && (chg_cnfg_00 & MAX77705_MODE_5_BUCK_CHG_ON) + && !is_wcin_type(charger->cable_type)) { + pr_info("%s: vbus is under\n", __func__); + state = POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE; + } else if ((value.intval == POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE) && + ((vbus_state == 0x0) || (vbus_state == 0x01)) && + !is_wcin_type(charger->cable_type)) { + pr_info("%s: keep under-voltage\n", __func__); + state = POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE; + } else if (wdt_status) { + pr_info("%s: wdt expired\n", __func__); + state = POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE; + } else if (is_wireless_type(charger->cable_type)) { + if (abnormal_status || (vbus_state == 0x00) || (vbus_state == 0x01)) + charger->misalign_cnt++; + else + charger->misalign_cnt = 0; + + if (charger->misalign_cnt >= 3) { + pr_info("%s: invalid WCIN, Misalign occurs!\n", __func__); + value.intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + psy_do_property(charger->pdata->wireless_charger_name, + set, POWER_SUPPLY_PROP_STATUS, value); + } + } + + return (int)state; +} + +static int max77705_get_charge_current(struct max77705_charger_data *charger) +{ + u8 reg_data; + int get_current = 0; + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_02, ®_data); + reg_data &= MAX77705_CHG_CC; + + get_current = reg_data <= 0x2 ? 100 : reg_data * 50; + + pr_info("%s: reg:(0x%x), charging_current:(%d)\n", + __func__, reg_data, get_current); + + return get_current; +} + +static int max77705_get_input_current_type(struct max77705_charger_data + *charger, int cable_type) +{ + u8 reg_data; + int get_current = 0; + + if (cable_type == SEC_BATTERY_CABLE_WIRELESS) { + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_10, ®_data); + /* AND operation for removing the formal 2bit */ + reg_data &= MAX77705_CHG_WCIN_LIM; + + if (reg_data <= 0x3) + get_current = 100; + else if (reg_data >= 0x3F) + get_current = 1600; + else + get_current = (reg_data + 0x01) * 25; + } else { + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_09, ®_data); + /* AND operation for removing the formal 1bit */ + reg_data &= MAX77705_CHG_CHGIN_LIM; + + if (reg_data <= 0x3) + get_current = 100; + else if (reg_data >= 0x7F) + get_current = 3200; + else + get_current = (reg_data + 0x01) * 25; + } + pr_info("%s: reg:(0x%x), charging_current:(%d)\n", + __func__, reg_data, get_current); + + return get_current; +} + +static int max77705_get_input_current(struct max77705_charger_data *charger) +{ + if (is_wcin_type(charger->cable_type)) + return max77705_get_input_current_type(charger, SEC_BATTERY_CABLE_WIRELESS); + else + return max77705_get_input_current_type(charger, SEC_BATTERY_CABLE_TA); +} + +static int reduce_input_current(struct max77705_charger_data *charger) +{ + int input_current = 0, max_value = 0; + + input_current = max77705_get_input_current(charger); + if (input_current <= MINIMUM_INPUT_CURRENT) + return input_current; + + if (is_wcin_type(charger->cable_type)) + max_value = 1600; + else + max_value = 3200; + + input_current -= REDUCE_CURRENT_STEP; + input_current = (input_current > max_value) ? max_value : + ((input_current < MINIMUM_INPUT_CURRENT) ? MINIMUM_INPUT_CURRENT : input_current); + + sec_votef("ICL", VOTER_AICL, true, input_current); + charger->input_current = max77705_get_input_current(charger); + + return input_current; +} + +static bool max77705_check_battery(struct max77705_charger_data *charger) +{ + u8 reg_data, reg_data2; + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_INT_OK, ®_data); + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_DETAILS_00, ®_data2); + pr_info("%s: CHG_INT_OK(0x%x), CHG_DETAILS00(0x%x)\n", + __func__, reg_data, reg_data2); + + if ((reg_data & MAX77705_BATP_OK) || !(reg_data2 & MAX77705_BATP_DTLS)) + return true; + else + return false; +} + +static void max77705_check_cnfg12_reg(struct max77705_charger_data *charger) +{ + static bool is_valid = true; + u8 valid_cnfg12, reg_data; + + if (is_valid) { + reg_data = MAX77705_CHG_WCINSEL; + valid_cnfg12 = is_wcin_type(charger->cable_type) ? + reg_data : (reg_data | (1 << CHG_CNFG_12_CHGINSEL_SHIFT)); + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_12, ®_data); + pr_info("%s: valid_data = 0x%2x, reg_data = 0x%2x\n", + __func__, valid_cnfg12, reg_data); + if (valid_cnfg12 != reg_data) { + max77705_test_read(charger); + is_valid = false; + } + } +} +static void max77705_force_change_charge_path(struct max77705_charger_data *charger) +{ + u8 cnfg12 = (1 << CHG_CNFG_12_CHGINSEL_SHIFT); + + if (!max77705_get_facmode_siso()) { + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_12, + cnfg12, CHG_CNFG_12_CHGINSEL_MASK); + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_12, &cnfg12); + pr_info("%s : CHG_CNFG_12(0x%02x)\n", __func__, cnfg12); + } + + max77705_check_cnfg12_reg(charger); +} + +static void max77705_change_charge_path(struct max77705_charger_data *charger, + int path) +{ + u8 cnfg12; + + if (!max77705_get_facmode_siso()) { + if (is_wcin_type(charger->cable_type)) + cnfg12 = (0 << CHG_CNFG_12_CHGINSEL_SHIFT); + else + cnfg12 = (1 << CHG_CNFG_12_CHGINSEL_SHIFT); + + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_12, + cnfg12, CHG_CNFG_12_CHGINSEL_MASK); + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_12, &cnfg12); + pr_info("%s : CHG_CNFG_12(0x%02x)\n", __func__, cnfg12); + } + max77705_check_cnfg12_reg(charger); +} + +static void max77705_set_ship_mode(struct max77705_charger_data *charger, + int enable) +{ + u8 cnfg07 = ((enable ? 1 : 0) << CHG_CNFG_07_REG_SHIPMODE_SHIFT); + + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_07, + cnfg07, CHG_CNFG_07_REG_SHIPMODE_MASK); + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_07, &cnfg07); + pr_info("%s : CHG_CNFG_07(0x%02x)\n", __func__, cnfg07); +} + +static void max77705_set_auto_ship_mode(struct max77705_charger_data *charger, + int enable) +{ + u8 cnfg03 = ((enable ? 1 : 0) << CHG_CNFG_03_REG_AUTO_SHIPMODE_SHIFT); + + /* auto ship mode should work under 2.6V */ + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_03, + cnfg03, CHG_CNFG_03_REG_AUTO_SHIPMODE_MASK); + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_03, &cnfg03); + pr_info("%s : CHG_CNFG_03(0x%02x)\n", __func__, cnfg03); +} + +static void max77705_set_input_current(struct max77705_charger_data *charger, + int input_current) +{ + int curr_step = 25; + u8 set_reg, set_mask, reg_data = 0; +#if defined(CONFIG_SEC_FACTORY) + if (max77705_get_facmode()) { + pr_info("%s: Factory Mode Skip set input current\n", __func__); + return; + } +#endif + + charger->dpm_last_icl = input_current; + if (!charger->bat_det && input_current < charger->pdata->dpm_icl) { + input_current = charger->pdata->dpm_icl; + pr_info("%s : DPM, icl setting value change (%dmA)\n", __func__, input_current); + } + + mutex_lock(&charger->charger_mutex); + + if (is_wcin_type(charger->cable_type)) { + set_reg = MAX77705_CHG_REG_CNFG_10; + set_mask = MAX77705_CHG_WCIN_LIM; + input_current = (input_current > 1600) ? 1600 : input_current; + } else { + set_reg = MAX77705_CHG_REG_CNFG_09; + set_mask = MAX77705_CHG_CHGIN_LIM; + input_current = (input_current > 3200) ? 3200 : input_current; + } + + if (input_current >= 100) + reg_data = (input_current / curr_step) - 0x01; + + max77705_update_reg(charger->i2c, set_reg, reg_data, set_mask); + + if (!max77705_get_autoibus(charger)) + max77705_set_fw_noautoibus(MAX77705_AUTOIBUS_AT_OFF); + + mutex_unlock(&charger->charger_mutex); + pr_info("[%s] REG(0x%02x) DATA(0x%02x), CURRENT(%d)\n", + __func__, set_reg, reg_data, input_current); +} + +static void max77705_set_charge_current(struct max77705_charger_data *charger, + int fast_charging_current) +{ + int curr_step = 50; + u8 reg_data = 0; +#if defined(CONFIG_SEC_FACTORY) + if (max77705_get_facmode()) { + pr_info("%s: Factory Mode Skip set charge current\n", __func__); + return; + } +#endif + + fast_charging_current = + (fast_charging_current > charger->pdata->max_fcc) ? charger->pdata->max_fcc : fast_charging_current; + if (fast_charging_current >= 100) + reg_data |= (fast_charging_current / curr_step); + + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_02, + reg_data, MAX77705_CHG_CC); + + pr_info("[%s] REG(0x%02x) DATA(0x%02x), CURRENT(%d)\n", __func__, + MAX77705_CHG_REG_CNFG_02, reg_data, fast_charging_current); +} + +static void max77705_set_wireless_input_current( + struct max77705_charger_data *charger, int input_current) +{ + union power_supply_propval value = {0, }; + unsigned int work_state; + + cancel_delayed_work(&charger->wc_chg_current_work); + __pm_relax(charger->wc_chg_current_ws); + + if (is_wireless_type(charger->cable_type)) { + work_state = work_busy(&charger->wc_current_work.work); + pr_info("%s: check wc_current_work state(0x%x)\n", __func__, work_state); + if (!(work_state & (WORK_BUSY_PENDING | WORK_BUSY_RUNNING))) { + /* Wcurr-A) In cases of wireless input current change, + * configure the Vrect adj room to 270mV for safe wireless charging. + */ + if (is_hv_wireless_type(charger->cable_type)) + value.intval = WIRELESS_VRECT_ADJ_ROOM_1; /* Vrect Room 277mV */ + else + value.intval = charger->pdata->nv_wc_headroom; + psy_do_property(charger->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_RX_CONTROL, value); + msleep(500); /* delay 0.5sec */ + } + charger->wc_pre_current = max77705_get_input_current(charger); + charger->wc_current = input_current; + pr_info("%s: wc_current(%d), wc_pre_current(%d)\n", + __func__, charger->wc_current, charger->wc_pre_current); + if (charger->wc_current > charger->wc_pre_current) + max77705_set_charge_current(charger, charger->charging_current); + } + mutex_lock(&charger->icl_mutex); + __pm_stay_awake(charger->wc_current_ws); + queue_delayed_work(charger->wqueue, &charger->wc_current_work, 0); + mutex_unlock(&charger->icl_mutex); +} + +static void max77705_set_topoff_current(struct max77705_charger_data *charger, + int termination_current) +{ + int curr_base = 150, curr_step = 50; + u8 reg_data; + + if (termination_current < curr_base) + termination_current = curr_base; + else if (termination_current > 500) + termination_current = 500; + + reg_data = (termination_current - curr_base) / curr_step; + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_03, + reg_data, CHG_CNFG_03_TO_ITH_MASK); + + pr_info("%s: reg_data(0x%02x), topoff(%dmA)\n", + __func__, reg_data, termination_current); +} + +static void max77705_set_topoff_time(struct max77705_charger_data *charger, + int topoff_time) +{ + u8 reg_data = (topoff_time / 10) << CHG_CNFG_03_TO_TIME_SHIFT; + + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_03, + reg_data, CHG_CNFG_03_TO_TIME_MASK); + + pr_info("%s: reg_data(0x%02x), topoff_time(%dmin)\n", + __func__, reg_data, topoff_time); +} + +static void max77705_set_switching_frequency(struct max77705_charger_data *charger, + int frequency) +{ + u8 cnfg_08; + + /* Set Switching Frequency */ + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_08, + (frequency << CHG_CNFG_08_REG_FSW_SHIFT), + CHG_CNFG_08_REG_FSW_MASK); + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_08, &cnfg_08); + + charger->fsw_now = frequency; + pr_info("%s : CHG_CNFG_08(0x%02x)\n", __func__, cnfg_08); +} + +static void max77705_set_skipmode(struct max77705_charger_data *charger, + int enable) +{ + u8 reg_data = enable ? MAX77705_AUTO_SKIP : MAX77705_DISABLE_SKIP; + + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_12, + reg_data << CHG_CNFG_12_REG_DISKIP_SHIFT, + CHG_CNFG_12_REG_DISKIP_MASK); +} + +static void max77705_set_b2sovrc(struct max77705_charger_data *charger, + u32 ocp_current, u32 ocp_dtc) +{ + u8 reg_data = MAX77705_B2SOVRC_4_6A; + + if (ocp_current == 0) + reg_data = MAX77705_B2SOVRC_DISABLE; + else + reg_data += (ocp_current - 4600) / 200; + + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_05, + (reg_data << CHG_CNFG_05_REG_B2SOVRC_SHIFT), + CHG_CNFG_05_REG_B2SOVRC_MASK); + + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_06, + ((ocp_dtc == 100 ? MAX77705_B2SOVRC_DTC_100MS : 0) + << CHG_CNFG_06_B2SOVRC_DTC_SHIFT), CHG_CNFG_06_B2SOVRC_DTC_MASK); + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_05, ®_data); + pr_info("%s : CHG_CNFG_05(0x%02x)\n", __func__, reg_data); + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_06, ®_data); + pr_info("%s : CHG_CNFG_06(0x%02x)\n", __func__, reg_data); + + return; +} + +static int max77705_check_wcin_before_otg_on(struct max77705_charger_data *charger) +{ + union power_supply_propval value = {0,}; + struct power_supply *psy; + u8 reg_data; + + psy = get_power_supply_by_name("wireless"); + if (!psy) + return -ENODEV; + if ((psy->desc->get_property != NULL) && + (psy->desc->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &value) >= 0)) { + if (value.intval) + return 0; + } else { + return -ENODEV; + } + power_supply_put(psy); + +#if defined(CONFIG_WIRELESS_TX_MODE) + /* check TX status */ + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_00, ®_data); + reg_data &= CHG_CNFG_00_MODE_MASK; + if (reg_data == MAX77705_MODE_8_BOOST_UNO_ON || + reg_data == MAX77705_MODE_C_BUCK_BOOST_UNO_ON || + reg_data == MAX77705_MODE_D_BUCK_CHG_BOOST_UNO_ON) { + value.intval = BATT_TX_EVENT_WIRELESS_TX_OTG_ON; + psy_do_property("wireless", set, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ERR, value); + return 0; + } +#endif + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_DETAILS_00, ®_data); + reg_data = ((reg_data & MAX77705_WCIN_DTLS) >> MAX77705_WCIN_DTLS_SHIFT); + if ((reg_data != 0x03) || (charger->pdata->wireless_charger_name == NULL)) + return 0; + + psy_do_property(charger->pdata->wireless_charger_name, get, + POWER_SUPPLY_PROP_ENERGY_NOW, value); + if (value.intval <= 0) + return -ENODEV; + + value.intval = WIRELESS_VOUT_5V; + psy_do_property(charger->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_INPUT_VOLTAGE_REGULATION, value); + + return 0; +} + +static void max77705_set_otg(struct max77705_charger_data *charger, int enable) +{ + union power_supply_propval value = {0, }; + u8 chg_int_state, otg_lim; + int ret = 0; + + pr_info("%s: CHGIN-OTG %s\n", __func__, enable > 0 ? "on" : "off"); + if (charger->otg_on == enable || max77705_get_lpmode()) + return; + + if (charger->pdata->wireless_charger_name) { + ret = max77705_check_wcin_before_otg_on(charger); + pr_info("%s: wc_state = %d\n", __func__, ret); + if (ret < 0) + return; + } + + __pm_stay_awake(charger->otg_ws); + /* CHGIN-OTG */ + value.intval = enable; + mutex_lock(&charger->charger_mutex); + charger->otg_on = enable; + mutex_unlock(&charger->charger_mutex); + + if (!enable) + charger->hp_otg = false; + + /* otg current limit 900mA or 1500mA */ + if (charger->hp_otg) + otg_lim = MAX77705_OTG_ILIM_1500; + else + otg_lim = MAX77705_OTG_ILIM_900; + + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_02, + otg_lim << CHG_CNFG_02_OTG_ILIM_SHIFT, + CHG_CNFG_02_OTG_ILIM_MASK); + + if (enable) { + psy_do_property("wireless", set, + POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL, value); + + mutex_lock(&charger->charger_mutex); + /* OTG on, boost on */ + max77705_chg_set_mode_state(charger, SEC_BAT_CHG_MODE_OTG_ON); + mutex_unlock(&charger->charger_mutex); + } else { + mutex_lock(&charger->charger_mutex); + /* OTG off(UNO on), boost off */ + max77705_chg_set_mode_state(charger, SEC_BAT_CHG_MODE_OTG_OFF); + mutex_unlock(&charger->charger_mutex); + msleep(50); + + psy_do_property("wireless", set, + POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL, value); + } + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_INT_MASK, &chg_int_state); + pr_info("%s: INT_MASK(0x%x)\n", __func__, chg_int_state); + + power_supply_changed(charger->psy_otg); + __pm_relax(charger->otg_ws); +} + +static void max77705_check_slow_charging(struct max77705_charger_data *charger, + int input_current) +{ + union power_supply_propval value = {0, }; + + /* under 400mA considered as slow charging concept for VZW */ + if (input_current <= SLOW_CHARGING_CURRENT_STANDARD && + !is_nocharge_type(charger->cable_type)) { + charger->slow_charging = true; + pr_info("%s: slow charging on : input current(%dmA), cable type(%d)\n", + __func__, input_current, charger->cable_type); + + psy_do_property("battery", set, POWER_SUPPLY_PROP_CHARGE_TYPE, value); + } else { + charger->slow_charging = false; + } +} + +static void max77705_charger_initialize(struct max77705_charger_data *charger) +{ + u8 reg_data; + int jig_gpio; + + pr_info("%s\n", __func__); + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_00, ®_data); + charger->cnfg00_mode = (reg_data & CHG_CNFG_00_MODE_MASK); + + /* unlock charger setting protect slowest LX slope + */ + reg_data = (0x03 << 2); + reg_data |= 0x60; + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_06, reg_data, reg_data); + +#if !defined(CONFIG_SEC_FACTORY) + /* If DIS_AICL is enabled(CNFG06[4]: 1) from factory_mode, + * clear to 0 to disable DIS_AICL + */ + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_06, + MAX77705_DIS_AICL << CHG_CNFG_06_DIS_AICL_SHIFT, + CHG_CNFG_06_DIS_AICL_MASK); +#endif + + /* fast charge timer disable + * restart threshold disable + * pre-qual charge disable + */ + reg_data = (MAX77705_FCHGTIME_DISABLE << CHG_CNFG_01_FCHGTIME_SHIFT) | + (MAX77705_CHG_RSTRT_DISABLE << CHG_CNFG_01_CHG_RSTRT_SHIFT) | + (MAX77705_CHG_PQEN_DISABLE << CHG_CNFG_01_PQEN_SHIFT); + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_01, reg_data, + (CHG_CNFG_01_FCHGTIME_MASK | CHG_CNFG_01_CHG_RSTRT_MASK | CHG_CNFG_01_PQEN_MASK)); + + /* enalbe RECYCLE_EN for ocp */ + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_01, + MAX77705_RECYCLE_EN_ENABLE << CHG_CNFG_01_RECYCLE_EN_SHIFT, + CHG_CNFG_01_RECYCLE_EN_MASK); + + /* OTG off(UNO on), boost off */ + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_00, + 0, CHG_CNFG_00_OTG_CTRL); + + /* otg current limit 900mA */ + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_02, + MAX77705_OTG_ILIM_900 << CHG_CNFG_02_OTG_ILIM_SHIFT, + CHG_CNFG_02_OTG_ILIM_MASK); + + /* UNO ILIM 1.0A */ + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_05, + MAX77705_UNOILIM_1000 << CHG_CNFG_05_REG_UNOILIM_SHIFT, + CHG_CNFG_05_REG_UNOILIM_MASK); + + /* BAT to SYS OCP */ + max77705_set_b2sovrc(charger, + charger->pdata->chg_ocp_current, charger->pdata->chg_ocp_dtc); + + /* top off current 150mA */ + reg_data = (MAX77705_TO_ITH_150MA << CHG_CNFG_03_TO_ITH_SHIFT) | + (MAX77705_SYS_TRACK_DISABLE << CHG_CNFG_03_SYS_TRACK_DIS_SHIFT); + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_03, reg_data, + (CHG_CNFG_03_TO_ITH_MASK | CHG_CNFG_03_TO_TIME_MASK | CHG_CNFG_03_SYS_TRACK_DIS_MASK)); + + /* topoff_time */ + max77705_set_topoff_time(charger, charger->pdata->topoff_time); + + /* cv voltage 4.2V or 4.35V */ + max77705_set_float_voltage(charger, charger->pdata->chg_float_voltage); + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_INT_OK, ®_data); + /* + * VCHGIN : REG=4.6V, UVLO=4.8V + * to fix CHGIN-UVLO issues including cheapy battery packs + */ + if (!max77705_get_facmode_siso()) { + if (reg_data & MAX77705_CHGIN_OK) + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_12, + CHG_CNFG_12_VCHGIN(REG_4600_UVLO_4800), CHG_CNFG_12_VCHGIN_REG_MASK); + /* VCHGIN : REG=4.5V, UVLO=4.7V */ + else + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_12, + CHG_CNFG_12_VCHGIN(REG_4500_UVLO_4700), CHG_CNFG_12_VCHGIN_REG_MASK); + } else { + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_12, ®_data); + pr_info("%s: maintain siso settings(cnfg_12 = 0x%2x)\n", __func__, reg_data); + } + + /* Boost mode possible in FACTORY MODE */ + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_07, + MAX77705_CHG_FMBST, CHG_CNFG_07_REG_FMBST_MASK); + + if (charger->jig_low_active) + jig_gpio = !gpio_get_value(charger->jig_gpio); + else + jig_gpio = gpio_get_value(charger->jig_gpio); + + pr_info("%s: jig_gpio = %d\n", __func__, jig_gpio); + + if (charger->pdata->enable_dpm && charger->pdata->disqbat) { + if (max77705_check_battery(charger)) { + /* disqbat set to LOW (Qbat ON) */ + charger->bat_det = true; + gpio_direction_output(charger->pdata->disqbat, 0); + } else { + /* disqbat set to HIGH (Qbat OFF) */ + charger->bat_det = false; + gpio_direction_output(charger->pdata->disqbat, 1); + } + pr_info("%s: battery detection = %d\n", __func__, charger->bat_det); + } + + + +#if defined(CONFIG_SEC_FACTORY) + if (max77705_get_facmode()) { + /* fgsrc should depend on factory_mode since 301k and 619k do not triger jig_gpio */ + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_07, + (1 << CHG_CNFG_07_REG_FGSRC_SHIFT), + CHG_CNFG_07_REG_FGSRC_MASK); + /* Watchdog Disable */ + max77705_chg_set_wdtmr_en(charger, 0); + } else { + /* fgsrc should depend on jig_gpio */ + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_07, + (jig_gpio << CHG_CNFG_07_REG_FGSRC_SHIFT), + CHG_CNFG_07_REG_FGSRC_MASK); + /* Watchdog Enable */ + max77705_chg_set_wdtmr_en(charger, 1); + } +#else + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_07, + (jig_gpio << CHG_CNFG_07_REG_FGSRC_SHIFT), + CHG_CNFG_07_REG_FGSRC_MASK); + /* Watchdog Enable */ + max77705_chg_set_wdtmr_en(charger, 1); +#endif + + /* Active Discharge Enable */ + max77705_update_reg(charger->pmic_i2c, MAX77705_PMIC_REG_MAINCTRL1, 0x01, 0x01); + + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_09, + MAX77705_CHG_EN, MAX77705_CHG_EN); + + /* VBYPSET=5.0V */ + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_11, + 0x00, CHG_CNFG_11_VBYPSET_MASK); + + /* Switching Frequency */ + max77705_set_switching_frequency(charger, charger->pdata->fsw); + + /* Auto skip mode */ + max77705_set_skipmode(charger, 1); + + /* disable shipmode */ + max77705_set_ship_mode(charger, 0); + + /* enable auto shipmode, this should work under 2.6V */ + max77705_set_auto_ship_mode(charger, 0); + + max77705_test_read(charger); +} + +static void max77705_set_sysovlo(struct max77705_charger_data *charger, int enable) +{ + u8 reg_data; + + max77705_read_reg(charger->pmic_i2c, + MAX77705_PMIC_REG_SYSTEM_INT_MASK, ®_data); + + reg_data = enable ? reg_data & 0xDF : reg_data | 0x20; + max77705_write_reg(charger->pmic_i2c, + MAX77705_PMIC_REG_SYSTEM_INT_MASK, reg_data); + + max77705_read_reg(charger->pmic_i2c, + MAX77705_PMIC_REG_SYSTEM_INT_MASK, ®_data); + pr_info("%s: check topsys irq mask(0x%x), enable(%d)\n", + __func__, reg_data, enable); +} + +static void max77705_chg_monitor_work(struct max77705_charger_data *charger) +{ + u8 reg_b2sovrc = 0, reg_mode = 0; + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_00, ®_mode); + reg_mode = (reg_mode & CHG_CNFG_00_MODE_MASK) >> CHG_CNFG_00_MODE_SHIFT; + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_05, ®_b2sovrc); + reg_b2sovrc = + (reg_b2sovrc & CHG_CNFG_05_REG_B2SOVRC_MASK) >> CHG_CNFG_05_REG_B2SOVRC_SHIFT; + + pr_info("%s: [CHG] MODE(0x%x), B2SOVRC(0x%x), otg_on(%d)\n", + __func__, reg_mode, reg_b2sovrc, charger->otg_on); + + /* protection code for sync with battery and charger driver */ + if (charger->pdata->enable_dpm && !charger->bat_det) { + union power_supply_propval value = {0, }; + + psy_do_property("battery", get, POWER_SUPPLY_EXT_PROP_MISC_EVENT, value); + if (!(value.intval & DPM_MISC)) { + /* disable thermal control */ + value.intval = 1; + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_THERMAL_ZONE, value); + + /* set DPM misc event for PMS UI control */ + value.intval = DPM_MISC; + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_MISC_EVENT, value); + } + } +} + +static int max77705_chg_create_attrs(struct device *dev) +{ + int i, rc; + + for (i = 0; i < (int)ARRAY_SIZE(max77705_charger_attrs); i++) { + rc = device_create_file(dev, &max77705_charger_attrs[i]); + if (rc) + goto create_attrs_failed; + } + return rc; + +create_attrs_failed: + dev_err(dev, "%s: failed (%d)\n", __func__, rc); + while (i--) + device_remove_file(dev, &max77705_charger_attrs[i]); + return rc; +} + +ssize_t max77705_chg_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct max77705_charger_data *charger = power_supply_get_drvdata(psy); + const ptrdiff_t offset = attr - max77705_charger_attrs; + int i = 0; + u8 addr, data; + + switch (offset) { + case CHIP_ID: + max77705_read_reg(charger->pmic_i2c, MAX77705_PMIC_REG_PMICID1, &data); + i += scnprintf(buf + i, PAGE_SIZE - i, "%x\n", data); + break; + case DATA: + for (addr = 0xB1; addr <= 0xC3; addr++) { + max77705_read_reg(charger->i2c, addr, &data); + i += scnprintf(buf + i, PAGE_SIZE - i, + "0x%02x : 0x%02x\n", addr, data); + } + break; + default: + return -EINVAL; + } + return i; +} + +ssize_t max77705_chg_store_attrs(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct max77705_charger_data *charger = power_supply_get_drvdata(psy); + const ptrdiff_t offset = attr - max77705_charger_attrs; + int ret = 0; + int x, y; + + switch (offset) { + case CHIP_ID: + ret = count; + break; + case DATA: + if (sscanf(buf, "0x%8x 0x%8x", &x, &y) == 2) { + if (x >= 0xB1 && x <= 0xC3) { + u8 addr = x, data = y; + + if (max77705_write_reg(charger->i2c, addr, data) < 0) + dev_info(charger->dev, "%s: addr: 0x%x write fail\n", + __func__, addr); + } else { + dev_info(charger->dev, "%s: addr: 0x%x is wrong\n", + __func__, x); + } + } + ret = count; + break; + default: + ret = -EINVAL; + } + return ret; +} + +static void max77705_set_uno(struct max77705_charger_data *charger, int en) +{ + u8 chg_int_state; + u8 reg; + + if (charger->otg_on) { + pr_info("%s: OTG ON, then skip UNO Control\n", __func__); + if (en) { +#if defined(CONFIG_WIRELESS_TX_MODE) + union power_supply_propval value = {0, }; + psy_do_property("battery", get, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ENABLE, value); + if (value.intval) { + value.intval = BATT_TX_EVENT_WIRELESS_TX_ETC; + psy_do_property("wireless", set, POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ERR, value); + } +#endif + } + return; + } + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_INT_OK, ®); + if (en && (reg & MAX77705_WCIN_OK)) { + pr_info("%s: WCIN is already valid by wireless charging, then skip UNO Control\n", + __func__); + return; + } + + if (en == SEC_BAT_CHG_MODE_UNO_ONLY) { + charger->uno_on = true; + charger->cnfg00_mode = MAX77705_MODE_8_BOOST_UNO_ON; + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_00, + charger->cnfg00_mode, CHG_CNFG_00_MODE_MASK); + } else if (en == SEC_BAT_CHG_MODE_CHARGING_OFF) { + charger->uno_on = true; + /* UNO on, boost on */ + max77705_chg_set_mode_state(charger, SEC_BAT_CHG_MODE_UNO_ON); + } else { + charger->uno_on = false; + /* boost off */ + max77705_chg_set_mode_state(charger, SEC_BAT_CHG_MODE_UNO_OFF); + msleep(50); + } + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_INT_MASK, &chg_int_state); + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_00, ®); + pr_info("%s: UNO(%d), INT_MASK(0x%x), CHG_CNFG_00(0x%x)\n", + __func__, charger->uno_on, chg_int_state, reg); +} + +static void max77705_set_uno_iout(struct max77705_charger_data *charger, int iout) +{ + u8 reg = 0; + + if (iout < 300) + reg = MAX77705_UNOILIM_200; + else if (iout >= 300 && iout < 400) + reg = MAX77705_UNOILIM_300; + else if (iout >= 400 && iout < 600) + reg = MAX77705_UNOILIM_400; + else if (iout >= 600 && iout < 800) + reg = MAX77705_UNOILIM_600; + else if (iout >= 800 && iout < 1000) + reg = MAX77705_UNOILIM_800; + else if (iout >= 1000 && iout < 1500) + reg = MAX77705_UNOILIM_1000; + else if (iout >= 1500) + reg = MAX77705_UNOILIM_1500; + + if (reg) + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_05, + reg << CHG_CNFG_05_REG_UNOILIM_SHIFT, + CHG_CNFG_05_REG_UNOILIM_MASK); + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_05, ®); + pr_info("@Tx_mode %s: CNFG_05 (0x%x)\n", __func__, reg); +} + +static void max77705_set_uno_vout(struct max77705_charger_data *charger, int vout) +{ + u8 reg = 0; + + if (vout == WC_TX_VOUT_OFF) { + pr_info("%s: set UNO default\n", __func__); + } else { + if (vout < WC_TX_VOUT_MIN) { + pr_err("%s: vout(%d) is lower than min\n", __func__, vout); + vout = WC_TX_VOUT_MIN; + } else if (vout > WC_TX_VOUT_MAX) { + pr_err("%s: vout(%d) is higher than max\n", __func__, vout); + vout = WC_TX_VOUT_MAX; + } + /* Set TX Vout(VBYPSET) */ + if (vout <= 5000) + reg = 0; + else + reg = (vout - 5000) / 100; + pr_info("%s: UNO VOUT (0x%x)\n", __func__, reg); + } + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_11, + reg, CHG_CNFG_11_VBYPSET_MASK); + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_11, ®); + pr_info("@Tx_mode %s: CNFG_11(0x%x)\n", __func__, reg); +} + +static int max77705_chg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max77705_charger_data *charger = power_supply_get_drvdata(psy); + enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; + u8 reg_data; + + if (atomic_read(&charger->shutdown_cnt) > 0) { + dev_info(charger->dev, "%s: charger already shutdown\n", __func__); + return -EINVAL; + } + + switch ((int)psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = SEC_BATTERY_CABLE_NONE; + if (!max77705_read_reg(charger->i2c, + MAX77705_CHG_REG_INT_OK, ®_data)) { + if (reg_data & MAX77705_WCIN_OK) { +#if defined(CONFIG_USE_POGO) + val->intval = SEC_BATTERY_CABLE_POGO; +#else + val->intval = SEC_BATTERY_CABLE_WIRELESS; +#endif + } else if (reg_data & MAX77705_CHGIN_OK) { + val->intval = SEC_BATTERY_CABLE_TA; + } + } + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = max77705_check_battery(charger); + break; + case POWER_SUPPLY_PROP_STATUS: + val->intval = max77705_get_charger_state(charger); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + if (!charger->is_charging) { + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + } else if (charger->slow_charging) { + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + pr_info("%s: slow-charging mode\n", __func__); + } else { + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + } + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = max77705_get_charging_health(charger); + max77705_check_cnfg12_reg(charger); + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + val->intval = charger->input_current; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + val->intval = charger->charging_current; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + val->intval = max77705_get_float_voltage(charger); + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + max77705_read_reg(charger->i2c, + MAX77705_CHG_REG_DETAILS_01, ®_data); + reg_data &= 0x0F; + switch (reg_data) { + case 0x01: + val->strval = "CC Mode"; + break; + case 0x02: + val->strval = "CV Mode"; + break; + case 0x03: + val->strval = "EOC"; + break; + case 0x04: + val->strval = "DONE"; + break; + default: + val->strval = "NONE"; + break; + } + break; + case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: + switch (ext_psp) { + case POWER_SUPPLY_EXT_PROP_WDT_STATUS: + if (max77705_chg_get_wdtmr_status(charger)) { + dev_info(charger->dev, "charger WDT is expired!!\n"); + max77705_test_read(charger); + } + break; + case POWER_SUPPLY_EXT_PROP_CHIP_ID: + if (!max77705_read_reg(charger->i2c, + MAX77705_PMIC_REG_PMICREV, ®_data)) { + /* pmic_ver should below 0x7 */ + val->intval = + (charger->pmic_ver >= 0x1 && charger->pmic_ver <= 0x7); + pr_info("%s : IF PMIC ver.0x%x\n", __func__, + charger->pmic_ver); + } else { + val->intval = 0; + pr_info("%s : IF PMIC I2C fail.\n", __func__); + } + break; + case POWER_SUPPLY_EXT_PROP_MONITOR_WORK: + max77705_test_read(charger); + max77705_chg_monitor_work(charger); + break; + case POWER_SUPPLY_EXT_PROP_CHARGE_BOOST: + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_00, ®_data); + reg_data &= CHG_CNFG_00_MODE_MASK; + val->intval = (reg_data & MAX77705_MODE_BOOST) ? 1 : 0; + break; + case POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL: + mutex_lock(&charger->charger_mutex); + val->intval = charger->otg_on; + mutex_unlock(&charger->charger_mutex); + break; + case POWER_SUPPLY_EXT_PROP_CHARGE_COUNTER_SHADOW: + break; + case POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED: + val->intval = charger->charge_mode; + break; + case POWER_SUPPLY_EXT_PROP_SHIPMODE_TEST: +#if defined(CONFIG_SUPPORT_SHIP_MODE) + val->intval = max77705_get_fw_ship_mode(); + pr_info("%s: ship mode op is %d\n", __func__, val->intval); +#else + val->intval = 0; + pr_info("%s: ship mode is not supported\n", __func__); +#endif + break; + case POWER_SUPPLY_EXT_PROP_INPUT_CURRENT_LIMIT_WRL: + val->intval = max77705_get_input_current_type(charger, SEC_BATTERY_CABLE_WIRELESS); + break; + case POWER_SUPPLY_EXT_PROP_CHARGER_IC_NAME: + val->strval = "MAX77705"; + break; + case POWER_SUPPLY_EXT_PROP_SPSN_TEST: + /* MODE set to 0 */ + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_00, + 0, CHG_CNFG_00_MODE_MASK); + + /* SPSN_DET set to 1 */ + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_00, + MAX77705_SPSN_DET_ENABLE << CHG_CNFG_00_SPSN_DET_EN_SHIFT, + CHG_CNFG_00_SPSN_DET_EN_MASK); + + /* delay for DTLS_00 update */ + msleep(50); + + /* read SPSN_DTLS */ + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_DETAILS_00, ®_data); + reg_data = (reg_data & MAX77705_SPSN_DTLS) >> MAX77705_SPSN_DTLS_SHIFT; + + /* return test result */ + val->intval = reg_data; + + /* read CNFG_00 for check test condition */ + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_00, ®_data); + pr_info("%s: SPSN_DTLS (0x%x), CNFG_00 (0x%x)\n", __func__, val->intval, reg_data); + + /* restore SPSN_DET */ + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_00, + MAX77705_SPSN_DET_DISABLE << CHG_CNFG_00_SPSN_DET_EN_SHIFT, + CHG_CNFG_00_SPSN_DET_EN_MASK); + + /* restore MODE */ + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_00, + charger->cnfg00_mode, CHG_CNFG_00_MODE_MASK); + + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + return 0; +} + +static void max77705_chg_set_mode_state(struct max77705_charger_data *charger, + unsigned int state) +{ + u8 reg; + union power_supply_propval value = {0,}; + + if (state == SEC_BAT_CHG_MODE_CHARGING) + charger->is_charging = true; + else if (state == SEC_BAT_CHG_MODE_CHARGING_OFF || + state == SEC_BAT_CHG_MODE_BUCK_OFF) + charger->is_charging = false; + +#if defined(CONFIG_SEC_FACTORY) + if (max77705_get_facmode()) { + if (state == SEC_BAT_CHG_MODE_CHARGING || + state == SEC_BAT_CHG_MODE_CHARGING_OFF || + state == SEC_BAT_CHG_MODE_BUCK_OFF) { + pr_info("%s: Factory Mode Skip set charger state\n", __func__); + return; + } + } +#endif + if (!charger->bat_det) { + pr_info("%s : DPM, chg state force set to buck on, chg off\n", __func__); + state = SEC_BAT_CHG_MODE_CHARGING_OFF; + charger->is_charging = false; + } + + + mutex_lock(&charger->mode_mutex); + pr_info("%s: current_mode(0x%x), state(%d)\n", __func__, charger->cnfg00_mode, state); + switch (charger->cnfg00_mode) { + /* all off */ + case MAX77705_MODE_0_ALL_OFF: + if (state == SEC_BAT_CHG_MODE_CHARGING_OFF) + charger->cnfg00_mode = MAX77705_MODE_4_BUCK_ON; + else if (state == SEC_BAT_CHG_MODE_CHARGING) + charger->cnfg00_mode = MAX77705_MODE_5_BUCK_CHG_ON; + else if (state == SEC_BAT_CHG_MODE_OTG_ON) + charger->cnfg00_mode = MAX77705_MODE_A_BOOST_OTG_ON; + else if (state == SEC_BAT_CHG_MODE_UNO_ON) + charger->cnfg00_mode = MAX77705_MODE_8_BOOST_UNO_ON; + break; + /* buck only */ + case MAX77705_MODE_4_BUCK_ON: + if (state == SEC_BAT_CHG_MODE_CHARGING) + charger->cnfg00_mode = MAX77705_MODE_5_BUCK_CHG_ON; + else if (state == SEC_BAT_CHG_MODE_BUCK_OFF) + charger->cnfg00_mode = MAX77705_MODE_0_ALL_OFF; + else if (state == SEC_BAT_CHG_MODE_OTG_ON) + charger->cnfg00_mode = MAX77705_MODE_E_BUCK_BOOST_OTG_ON; + else if (state == SEC_BAT_CHG_MODE_UNO_ON) + charger->cnfg00_mode = MAX77705_MODE_C_BUCK_BOOST_UNO_ON; + break; + /* buck, charger on */ + case MAX77705_MODE_5_BUCK_CHG_ON: + if (state == SEC_BAT_CHG_MODE_BUCK_OFF) + charger->cnfg00_mode = MAX77705_MODE_0_ALL_OFF; + else if (state == SEC_BAT_CHG_MODE_CHARGING_OFF) + charger->cnfg00_mode = MAX77705_MODE_4_BUCK_ON; + else if (state == SEC_BAT_CHG_MODE_OTG_ON) + charger->cnfg00_mode = MAX77705_MODE_F_BUCK_CHG_BOOST_OTG_ON; + else if (state == SEC_BAT_CHG_MODE_UNO_ON) + charger->cnfg00_mode = MAX77705_MODE_D_BUCK_CHG_BOOST_UNO_ON; + break; + case MAX77705_MODE_8_BOOST_UNO_ON: + if (state == SEC_BAT_CHG_MODE_CHARGING_OFF) + charger->cnfg00_mode = MAX77705_MODE_C_BUCK_BOOST_UNO_ON; + else if (state == SEC_BAT_CHG_MODE_CHARGING) + charger->cnfg00_mode = MAX77705_MODE_D_BUCK_CHG_BOOST_UNO_ON; + else if (state == SEC_BAT_CHG_MODE_UNO_OFF) + charger->cnfg00_mode = MAX77705_MODE_0_ALL_OFF; + /* UNO -> OTG */ + else if (state == SEC_BAT_CHG_MODE_OTG_ON) { + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_00, + MAX77705_MODE_4_BUCK_ON, CHG_CNFG_00_MODE_MASK); + usleep_range(1000, 2000); + /* mode 0x4, and 1msec delay, and then otg on */ + charger->cnfg00_mode = MAX77705_MODE_A_BOOST_OTG_ON; + } + break; + case MAX77705_MODE_A_BOOST_OTG_ON: + if (state == SEC_BAT_CHG_MODE_CHARGING_OFF) + charger->cnfg00_mode = MAX77705_MODE_E_BUCK_BOOST_OTG_ON; + else if (state == SEC_BAT_CHG_MODE_CHARGING) + charger->cnfg00_mode = MAX77705_MODE_F_BUCK_CHG_BOOST_OTG_ON; + else if (state == SEC_BAT_CHG_MODE_OTG_OFF) + charger->cnfg00_mode = MAX77705_MODE_0_ALL_OFF; + /* OTG -> UNO */ + else if (state == SEC_BAT_CHG_MODE_UNO_ON) { + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_00, + MAX77705_MODE_4_BUCK_ON, CHG_CNFG_00_MODE_MASK); + usleep_range(1000, 2000); + /* mode 0x4, and 1msec delay, and then uno on */ + charger->cnfg00_mode = MAX77705_MODE_8_BOOST_UNO_ON; + } + break; + case MAX77705_MODE_C_BUCK_BOOST_UNO_ON: + if (state == SEC_BAT_CHG_MODE_BUCK_OFF) + charger->cnfg00_mode = MAX77705_MODE_8_BOOST_UNO_ON; + else if (state == SEC_BAT_CHG_MODE_CHARGING) + charger->cnfg00_mode = MAX77705_MODE_D_BUCK_CHG_BOOST_UNO_ON; + else if (state == SEC_BAT_CHG_MODE_UNO_OFF) + charger->cnfg00_mode = MAX77705_MODE_4_BUCK_ON; + /* UNO -> OTG */ + else if (state == SEC_BAT_CHG_MODE_OTG_ON) { + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_00, + MAX77705_MODE_4_BUCK_ON, CHG_CNFG_00_MODE_MASK); + usleep_range(1000, 2000); + /* mode 0x4, and 1msec delay, and then otg on */ + charger->cnfg00_mode = MAX77705_MODE_E_BUCK_BOOST_OTG_ON; + } + break; + case MAX77705_MODE_D_BUCK_CHG_BOOST_UNO_ON: + if (state == SEC_BAT_CHG_MODE_BUCK_OFF) + charger->cnfg00_mode = MAX77705_MODE_8_BOOST_UNO_ON; + else if (state == SEC_BAT_CHG_MODE_CHARGING_OFF) + charger->cnfg00_mode = MAX77705_MODE_C_BUCK_BOOST_UNO_ON; + else if (state == SEC_BAT_CHG_MODE_UNO_OFF) + charger->cnfg00_mode = MAX77705_MODE_5_BUCK_CHG_ON; + /* UNO -> OTG */ + else if (state == SEC_BAT_CHG_MODE_OTG_ON) { + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_00, + MAX77705_MODE_4_BUCK_ON, CHG_CNFG_00_MODE_MASK); + usleep_range(1000, 2000); + /* mode 0x4, and 1msec delay, and then otg on */ + charger->cnfg00_mode = MAX77705_MODE_E_BUCK_BOOST_OTG_ON; + } + break; + case MAX77705_MODE_E_BUCK_BOOST_OTG_ON: + if (state == SEC_BAT_CHG_MODE_BUCK_OFF) + charger->cnfg00_mode = MAX77705_MODE_A_BOOST_OTG_ON; + else if (state == SEC_BAT_CHG_MODE_CHARGING) + charger->cnfg00_mode = MAX77705_MODE_F_BUCK_CHG_BOOST_OTG_ON; + else if (state == SEC_BAT_CHG_MODE_OTG_OFF) + charger->cnfg00_mode = MAX77705_MODE_4_BUCK_ON; + /* OTG -> UNO */ + else if (state == SEC_BAT_CHG_MODE_UNO_ON) { + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_00, + MAX77705_MODE_4_BUCK_ON, CHG_CNFG_00_MODE_MASK); + usleep_range(1000, 2000); + /* mode 0x4, and 1msec delay, and then uno on */ + charger->cnfg00_mode = MAX77705_MODE_C_BUCK_BOOST_UNO_ON; + } + break; + case MAX77705_MODE_F_BUCK_CHG_BOOST_OTG_ON: + if (state == SEC_BAT_CHG_MODE_CHARGING_OFF) + charger->cnfg00_mode = MAX77705_MODE_E_BUCK_BOOST_OTG_ON; + else if (state == SEC_BAT_CHG_MODE_BUCK_OFF) + charger->cnfg00_mode = MAX77705_MODE_A_BOOST_OTG_ON; + else if (state == SEC_BAT_CHG_MODE_OTG_OFF) + charger->cnfg00_mode = MAX77705_MODE_5_BUCK_CHG_ON; + break; + } + + if (state == SEC_BAT_CHG_MODE_OTG_ON && + charger->cnfg00_mode == MAX77705_MODE_F_BUCK_CHG_BOOST_OTG_ON) { + /* W/A for shutdown problem when turn on OTG during wireless charging */ + pr_info("%s : disable WCIN_SEL before change mode to 0xF\n", __func__); + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_12, + (0 << CHG_CNFG_12_WCINSEL_SHIFT), CHG_CNFG_12_WCINSEL_MASK); + } + + pr_info("%s: current_mode(0x%x)\n", __func__, charger->cnfg00_mode); + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_00, + charger->cnfg00_mode, CHG_CNFG_00_MODE_MASK); + + if (state == SEC_BAT_CHG_MODE_OTG_ON && + charger->cnfg00_mode == MAX77705_MODE_F_BUCK_CHG_BOOST_OTG_ON) { + /* W/A for shutdown problem when turn on OTG during wireless charging */ + pr_info("%s : enable WCIN_SEL after change mode to 0xF\n", __func__); + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_12, + MAX77705_CHG_WCINSEL, CHG_CNFG_12_WCINSEL_MASK); + if (is_wireless_type(charger->cable_type)) { + value.intval = WIRELESS_VOUT_5V; + psy_do_property(charger->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_INPUT_VOLTAGE_REGULATION, value); + } + } + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_00, ®); + pr_info("%s: CNFG_00 (0x%x)\n", __func__, reg); + mutex_unlock(&charger->mode_mutex); +} + +static bool max77705_irq_enable(int irq, bool en) +{ + bool ret = false; + + if (irq <= 0) + return ret; + + if (en && irqd_irq_disabled(&irq_to_desc(irq)->irq_data)) { + enable_irq(irq); + ret = true; + } else if (!en && !irqd_irq_disabled(&irq_to_desc(irq)->irq_data)) { + disable_irq_nosync(irq); + ret = true; + } + pr_info("%s : irq: %d, en: %d, st: %d\n", __func__, irq, en, + irqd_irq_disabled(&irq_to_desc(irq)->irq_data)); + + return ret; +} + +static void max77705_aicl_irq_enable(struct max77705_charger_data *charger, + bool en) +{ + u8 reg_data = 0; + bool ret = false; + + ret = max77705_irq_enable(charger->irq_aicl, en); + + if (ret) { + max77705_read_reg(charger->i2c, + MAX77705_CHG_REG_INT_MASK, ®_data); + pr_info("%s: %s aicl : 0x%x\n", + __func__, en ? "enable" : "disable", reg_data); + } +} + + +#if defined(CONFIG_UPDATE_BATTERY_DATA) +static int max77705_charger_parse_dt(struct max77705_charger_data *charger); +#endif +static int max77705_chg_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max77705_charger_data *charger = power_supply_get_drvdata(psy); + enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; + u8 reg_data = 0; + + if (atomic_read(&charger->shutdown_cnt) > 0) { + dev_info(charger->dev, "%s: charger already shutdown\n", __func__); + return -EINVAL; + } + + /* check unlock status before does set the register */ + max77705_charger_unlock(charger); + switch ((int)psp) { + /* val->intval : type */ + case POWER_SUPPLY_PROP_STATUS: + charger->status = val->intval; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: +#if defined(CONFIG_USE_POGO) + { + union power_supply_propval value = {0, }; + int pogo_state = 0; + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_DETAILS_00, ®_data); + pogo_state = (reg_data & MAX77705_WCIN_DTLS) >> MAX77705_WCIN_DTLS_SHIFT; + + value.intval = SEC_BATTERY_VBYP; + psy_do_property("max77705-fuelgauge", get, + POWER_SUPPLY_EXT_PROP_MEASURE_INPUT, value); + + pr_info("%s: pogo_state(%d), Vwcin(%d)\n", __func__, pogo_state, value.intval); + + if (pogo_state) { + if (value.intval > 8000) + value.intval = 2; + else + value.intval = 1; + } else + value.intval = 0; + + psy_do_property("pogo", set, POWER_SUPPLY_PROP_ONLINE, value); + } +#endif + break; + case POWER_SUPPLY_PROP_ONLINE: + charger->cable_type = val->intval; + charger->aicl_curr = 0; + charger->slow_charging = false; + charger->input_current = max77705_get_input_current(charger); + max77705_change_charge_path(charger, charger->cable_type); + if (!max77705_get_autoibus(charger)) + max77705_set_fw_noautoibus(MAX77705_AUTOIBUS_AT_OFF); + + if (charger->pdata->boosting_voltage_aicl) + max77705_aicl_irq_enable(charger, true); + + if (is_nocharge_type(charger->cable_type)) { + charger->wc_pre_current = WC_CURRENT_START; + if (!max77705_get_facmode_siso()) { + /* VCHGIN : REG=4.5V, UVLO=4.7V */ + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_12, + CHG_CNFG_12_VCHGIN(REG_4500_UVLO_4700), CHG_CNFG_12_VCHGIN_REG_MASK); + } + if (charger->pdata->enable_sysovlo_irq) + max77705_set_sysovlo(charger, 1); + + if (!charger->pdata->boosting_voltage_aicl) + max77705_aicl_irq_enable(charger, true); + } else if (is_wired_type(charger->cable_type)) { + if (!max77705_get_facmode_siso()) { + /* + * VCHGIN : REG=4.6V, UVLO=4.8V + * to fix CHGIN-UVLO issues including cheapy battery packs + */ + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_12, + CHG_CNFG_12_VCHGIN(REG_4600_UVLO_4800), CHG_CNFG_12_VCHGIN_REG_MASK); + } + if (is_hv_wire_type(charger->cable_type) || + (charger->cable_type == SEC_BATTERY_CABLE_HV_TA_CHG_LIMIT)) { + if (!charger->pdata->boosting_voltage_aicl) { + max77705_aicl_irq_enable(charger, false); + cancel_delayed_work(&charger->aicl_work); + __pm_relax(charger->aicl_ws); + } + } + } + break; + /* val->intval : input charging current */ + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + { + int input_current = val->intval; + + if (delayed_work_pending(&charger->aicl_work)) { + cancel_delayed_work(&charger->aicl_work); + charger->aicl_curr = 0; + queue_delayed_work(charger->wqueue, &charger->aicl_work, + msecs_to_jiffies(AICL_WORK_DELAY)); + } + + if (is_wireless_type(charger->cable_type)) + max77705_set_wireless_input_current(charger, input_current); + else + max77705_set_input_current(charger, input_current); + + if (is_nocharge_type(charger->cable_type)) + max77705_set_wireless_input_current(charger, input_current); + + charger->input_current = input_current; + } + break; + /* val->intval : charging current */ + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + charger->charging_current = val->intval; + max77705_set_charge_current(charger, val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + charger->float_voltage = val->intval; + pr_info("%s: float voltage(%d)\n", __func__, val->intval); + max77705_set_float_voltage(charger, val->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX: + max77705_init_aicl_irq(charger); + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_INT_OK, ®_data); + if (!(reg_data & MAX77705_AICL_OK)) + queue_delayed_work(charger->wqueue, &charger->aicl_work, + msecs_to_jiffies(AICL_WORK_DELAY)); + break; + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + max77705_set_topoff_current(charger, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: + switch (ext_psp) { + case POWER_SUPPLY_EXT_PROP_SURGE: + if (val->intval) { + pr_info("%s : Charger IC reset by surge. charger re-initialize\n", + __func__); + check_charger_unlock_state(charger); + } + break; + case POWER_SUPPLY_EXT_PROP_CHGINSEL: + if (val->intval == WL_TO_W) + max77705_force_change_charge_path(charger); + else + max77705_change_charge_path(charger, charger->cable_type); + break; + case POWER_SUPPLY_EXT_PROP_PAD_VOLT_CTRL: + break; + case POWER_SUPPLY_EXT_PROP_SHIPMODE_TEST: +#if defined(CONFIG_SUPPORT_SHIP_MODE) + if (val->intval == SHIP_MODE_EN) { + pr_info("%s: set ship mode enable\n", __func__); + max77705_set_ship_mode(charger, 1); + } else if (val->intval == SHIP_MODE_EN_OP) { + pr_info("%s: set ship mode op enable\n", __func__); + max77705_set_fw_ship_mode(1); + } else { + pr_info("%s: ship mode disable is not supported\n", __func__); + } +#else + pr_info("%s: ship mode(%d) is not supported\n", __func__, val->intval); +#endif + break; + case POWER_SUPPLY_EXT_PROP_AUTO_SHIPMODE_CONTROL: + if (val->intval) { + pr_info("%s: auto ship mode is enabled\n", __func__); + max77705_set_auto_ship_mode(charger, 1); + } else { + pr_info("%s: auto ship mode is disabled\n", __func__); + max77705_set_auto_ship_mode(charger, 0); + } + break; + case POWER_SUPPLY_EXT_PROP_FGSRC_SWITCHING: + { + u8 reg_data = 0, reg_fgsrc = 0; + + /* if jig attached, change the power source */ + /* from the VBATFG to the internal VSYS */ + if ((val->intval == SEC_BAT_INBAT_FGSRC_SWITCHING_VSYS) || + (val->intval == SEC_BAT_FGSRC_SWITCHING_VSYS)) + reg_fgsrc = 1; + else + reg_fgsrc = 0; + + max77705_update_reg(charger->i2c, + MAX77705_CHG_REG_CNFG_07, + (reg_fgsrc << CHG_CNFG_07_REG_FGSRC_SHIFT), + CHG_CNFG_07_REG_FGSRC_MASK); + max77705_read_reg(charger->i2c, + MAX77705_CHG_REG_CNFG_07, ®_data); + + pr_info("%s: POWER_SUPPLY_EXT_PROP_FGSRC_SWITCHING(%d): reg(0x%x) val(0x%x)\n", + __func__, reg_fgsrc, MAX77705_CHG_REG_CNFG_07, reg_data); + } + break; + case POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED: + charger->charge_mode = val->intval; + charger->misalign_cnt = 0; + max77705_chg_set_mode_state(charger, charger->charge_mode); + break; + case POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL: + max77705_set_otg(charger, val->intval); + break; +#if defined(CONFIG_UPDATE_BATTERY_DATA) + case POWER_SUPPLY_EXT_PROP_POWER_DESIGN: + max77705_charger_parse_dt(charger); + break; +#endif + case POWER_SUPPLY_EXT_PROP_CONSTANT_CHARGE_CURRENT_WRL: + charger->charging_current = val->intval; + __pm_stay_awake(charger->wc_chg_current_ws); + queue_delayed_work(charger->wqueue, &charger->wc_chg_current_work, + msecs_to_jiffies(0)); + break; + case POWER_SUPPLY_EXT_PROP_SPSN_TEST: + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + return 0; +} + +static int max77705_otg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max77705_charger_data *charger = power_supply_get_drvdata(psy); + enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; + u8 reg_data; + + if (atomic_read(&charger->shutdown_cnt) > 0) { + dev_info(charger->dev, "%s: charger already shutdown\n", __func__); + return -EINVAL; + } + + switch ((int)psp) { + case POWER_SUPPLY_PROP_ONLINE: + mutex_lock(&charger->charger_mutex); + val->intval = charger->otg_on; + mutex_unlock(&charger->charger_mutex); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + break; + case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: + switch (ext_psp) { + case POWER_SUPPLY_EXT_PROP_CHARGE_UNO_CONTROL: + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_00, + ®_data); + reg_data &= CHG_CNFG_00_MODE_MASK; + if (reg_data == MAX77705_MODE_8_BOOST_UNO_ON || + reg_data == MAX77705_MODE_C_BUCK_BOOST_UNO_ON || + reg_data == MAX77705_MODE_D_BUCK_CHG_BOOST_UNO_ON) + val->intval = 1; + else + val->intval = 0; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + return 0; +} + +static int max77705_otg_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max77705_charger_data *charger = power_supply_get_drvdata(psy); + enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; + bool mfc_fw_update = false; + union power_supply_propval value = {0, }; + + if (atomic_read(&charger->shutdown_cnt) > 0) { + dev_info(charger->dev, "%s: charger already shutdown\n", __func__); + return -EINVAL; + } + + switch ((int)psp) { + case POWER_SUPPLY_PROP_ONLINE: + psy_do_property("battery", get, + POWER_SUPPLY_EXT_PROP_MFC_FW_UPDATE, value); + mfc_fw_update = value.intval; + if (!mfc_fw_update) { + max77705_set_otg(charger, val->intval); + if (val->intval) { + max77705_aicl_irq_enable(charger, false); + cancel_delayed_work(&charger->aicl_work); + __pm_relax(charger->aicl_ws); + charger->aicl_curr = 0; + charger->slow_charging = false; + } else if (!val->intval) { + max77705_aicl_irq_enable(charger, true); + } + } else { + pr_info("%s : max77705_set_otg skip, mfc_fw_update(%d)\n", + __func__, mfc_fw_update); + } + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + pr_info("POWER_SUPPLY_PROP_VOLTAGE_MAX, set otg current limit %dmA\n", (val->intval) ? 1500 : 900); + + if (val->intval) { + charger->hp_otg = true; + /* otg current limit 1500mA */ + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_02, + MAX77705_OTG_ILIM_1500 << CHG_CNFG_02_OTG_ILIM_SHIFT, + CHG_CNFG_02_OTG_ILIM_MASK); + } else { + charger->hp_otg = false; + /* otg current limit 900mA */ + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_02, + MAX77705_OTG_ILIM_900 << CHG_CNFG_02_OTG_ILIM_SHIFT, + CHG_CNFG_02_OTG_ILIM_MASK); + } + break; + case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: + switch (ext_psp) { + case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_VOUT: + max77705_set_uno_vout(charger, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_IOUT: + max77705_set_uno_iout(charger, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_CHARGE_UNO_CONTROL: + pr_info("%s: WCIN-UNO %d\n", __func__, val->intval); + max77705_set_uno(charger, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_OTG_VBUS_CTRL: + pr_info("%s: OTG_VBUS_CTRL %d\n", __func__, val->intval); + mutex_lock(&charger->charger_mutex); + if (val->intval == TURN_OTG_ON) { + max77705_chg_set_mode_state(charger, SEC_BAT_CHG_MODE_OTG_ON); + } else if (val->intval == TURN_OTG_OFF) { + max77705_chg_set_mode_state(charger, SEC_BAT_CHG_MODE_OTG_OFF); + } else if (val->intval == TURN_RB_OFF) { + value.intval = 0; + psy_do_property("sec-direct-charger", set, + POWER_SUPPLY_EXT_PROP_OTG_VBUS_CTRL, value); + } else if (val->intval == TURN_RB_ON) { + value.intval = 1; + psy_do_property("sec-direct-charger", set, + POWER_SUPPLY_EXT_PROP_OTG_VBUS_CTRL, value); + } else if (val->intval == TURN_OTG_OFF_RB_ON) { + max77705_chg_set_mode_state(charger, SEC_BAT_CHG_MODE_OTG_OFF); + value.intval = 1; + psy_do_property("sec-direct-charger", set, + POWER_SUPPLY_EXT_PROP_OTG_VBUS_CTRL, value); + } else { + pr_info("%s: Unknown OTG_VBUS_CTRL %d\n", __func__, val->intval); + } + mutex_unlock(&charger->charger_mutex); + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + return 0; +} + +static int max77705_debugfs_show(struct seq_file *s, void *data) +{ + struct max77705_charger_data *charger = s->private; + u8 reg, reg_data; + + seq_puts(s, "MAX77705 CHARGER IC :\n"); + seq_puts(s, "===================\n"); + for (reg = 0xB0; reg <= 0xC3; reg++) { + max77705_read_reg(charger->i2c, reg, ®_data); + seq_printf(s, "0x%02x:\t0x%02x\n", reg, reg_data); + } + + seq_puts(s, "\n"); + return 0; +} + +static int max77705_debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, max77705_debugfs_show, inode->i_private); +} + +static const struct file_operations max77705_debugfs_fops = { + .open = max77705_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void max77705_chg_isr_work(struct work_struct *work) +{ + struct max77705_charger_data *charger = + container_of(work, struct max77705_charger_data, isr_work.work); + + max77705_get_charger_state(charger); + max77705_get_charging_health(charger); +} + +static irqreturn_t max77705_chg_irq_thread(int irq, void *irq_data) +{ + struct max77705_charger_data *charger = irq_data; + + pr_info("%s: Charger interrupt occurred\n", __func__); + + if ((charger->pdata->full_check_type == SEC_BATTERY_FULLCHARGED_CHGINT) + || (charger->pdata->ovp_uvlo_check_type == SEC_BATTERY_OVP_UVLO_CHGINT)) + schedule_delayed_work(&charger->isr_work, 0); + + return IRQ_HANDLED; +} + +static irqreturn_t max77705_batp_irq(int irq, void *data) +{ + struct max77705_charger_data *charger = data; + union power_supply_propval value = {0, }; + u8 reg_data; + + pr_info("%s : irq(%d)\n", __func__, irq); + + check_charger_unlock_state(charger); + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_INT_OK, ®_data); + + if (charger->pdata->enable_dpm && charger->pdata->disqbat) { + if (!(reg_data & MAX77705_BATP_OK)) { + if (!charger->bat_det) { + pr_info("%s : ignore duplicated irq(%d)\n", __func__, charger->bat_det); + return IRQ_HANDLED; + } + + /* disqbat set to HIGH (Qbat OFF) */ + charger->bat_det = false; + gpio_direction_output(charger->pdata->disqbat, 1); + + /* disable thermal control */ + value.intval = 1; + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_THERMAL_ZONE, value); + + /* set DPM misc event for PMS UI control */ + value.intval = DPM_MISC; + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_MISC_EVENT, value); + } else { + if (charger->bat_det) { + pr_info("%s : ignore duplicated irq(%d)\n", __func__, charger->bat_det); + return IRQ_HANDLED; + } + + /* fuelgauge reset before battery insertion control */ + value.intval = SEC_FUELGAUGE_CAPACITY_TYPE_RESET; + psy_do_property("max77705-fuelgauge", set, POWER_SUPPLY_PROP_CAPACITY, value); + pr_info("%s : do reset SOC for battery insertion control\n", __func__); + + /* disqbat set to LOW (Qbat ON) */ + charger->bat_det = true; + gpio_direction_output(charger->pdata->disqbat, 0); + + /* enable thermal control */ + value.intval = 0; + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_THERMAL_ZONE, value); + + /* set DPM misc event for PMS UI control */ + value.intval = DPM_MISC; + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_MISC_EVENT_CLEAR, value); + } + max77705_set_input_current(charger, charger->dpm_last_icl); + max77705_chg_set_mode_state(charger, charger->charge_mode); + pr_info("%s: battery_detect(%d)\n", __func__, charger->bat_det); + } else { /* original code that does not use DPM */ + if (!(reg_data & MAX77705_BATP_OK)) + psy_do_property("battery", set, POWER_SUPPLY_PROP_PRESENT, value); + } + return IRQ_HANDLED; +} + +#if defined(CONFIG_MAX77705_CHECK_B2SOVRC) +#if defined(CONFIG_REGULATOR_S2MPS18) +extern void s2mps18_print_adc_val_power(void); +#endif +static irqreturn_t max77705_bat_irq(int irq, void *data) +{ + struct max77705_charger_data *charger = data; + union power_supply_propval value = {0, }; + u8 reg_int_ok, reg_data; + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_INT_OK, ®_int_ok); + if (!(reg_int_ok & MAX77705_BAT_OK)) { + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_DETAILS_01, ®_data); + reg_data = ((reg_data & MAX77705_BAT_DTLS) >> MAX77705_BAT_DTLS_SHIFT); + if (reg_data == 0x06) { + pr_info("OCP(B2SOVRC)\n"); + + if (charger->uno_on) { +#if defined(CONFIG_WIRELESS_TX_MODE) + union power_supply_propval val; + val.intval = BATT_TX_EVENT_WIRELESS_TX_OCP; + psy_do_property("wireless", set, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ERR, val); +#endif + } +#if defined(CONFIG_REGULATOR_S2MPS18) + s2mps18_print_adc_val_power(); +#endif + /* print vnow, inow */ + psy_do_property("max77705-fuelgauge", get, + POWER_SUPPLY_EXT_PROP_FUELGAUGE_LOG, value); + } + } else { + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_DETAILS_01, ®_data); + reg_data = ((reg_data & MAX77705_BAT_DTLS) >> MAX77705_BAT_DTLS_SHIFT); + pr_info("%s: reg_data(0x%x)\n", __func__, reg_data); + } + check_charger_unlock_state(charger); + + return IRQ_HANDLED; +} +#endif + +static irqreturn_t max77705_bypass_irq(int irq, void *data) +{ + struct max77705_charger_data *charger = data; +#ifdef CONFIG_USB_HOST_NOTIFY + struct otg_notify *o_notify; +#endif + union power_supply_propval val; + u8 dtls_02, byp_dtls; + + pr_info("%s: irq(%d)\n", __func__, irq); + + /* check and unlock */ + check_charger_unlock_state(charger); + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_DETAILS_02, &dtls_02); + + byp_dtls = ((dtls_02 & MAX77705_BYP_DTLS) >> MAX77705_BYP_DTLS_SHIFT); + pr_info("%s: BYP_DTLS(0x%02x)\n", __func__, byp_dtls); + + if (byp_dtls & 0x1) { + pr_info("%s: bypass overcurrent limit\n", __func__); + /* disable the register values just related to OTG and + * keep the values about the charging + */ + if (charger->otg_on) { +#ifdef CONFIG_USB_HOST_NOTIFY + o_notify = get_otg_notify(); + if (o_notify) + send_otg_notify(o_notify, NOTIFY_EVENT_OVERCURRENT, 0); +#endif + val.intval = 0; + psy_do_property("otg", set, POWER_SUPPLY_PROP_ONLINE, val); + } else if (charger->uno_on) { +#if defined(CONFIG_WIRELESS_TX_MODE) + val.intval = BATT_TX_EVENT_WIRELESS_TX_OCP; + psy_do_property("wireless", set, POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ERR, val); +#endif + } + } + return IRQ_HANDLED; +} + +static void max77705_aicl_isr_work(struct work_struct *work) +{ + struct max77705_charger_data *charger = + container_of(work, struct max77705_charger_data, aicl_work.work); + union power_supply_propval value = {0, }; + bool aicl_mode = false; + u8 aicl_state = 0; + int aicl_current = 0; + int hv_pdo = 0; + + psy_do_property("battery", get, + POWER_SUPPLY_EXT_PROP_HV_PDO, value); + hv_pdo = value.intval; + pr_info("%s : hv_pdo(%d)\n", __func__, hv_pdo); + + if (is_nocharge_type(charger->cable_type) || + (is_wireless_type(charger->cable_type) && charger->wc_pre_current > charger->wc_current) || + irqd_irq_disabled(&irq_to_desc(charger->irq_aicl)->irq_data) || + hv_pdo) { + pr_info("%s: skip\n", __func__); + charger->aicl_curr = 0; + sec_votef("ICL", VOTER_AICL, false, 0); + __pm_relax(charger->aicl_ws); + return; + } + + /* check and unlock */ + check_charger_unlock_state(charger); + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_INT_OK, &aicl_state); + + if (!(aicl_state & MAX77705_AICL_OK)) { + /* AICL mode */ + pr_info("%s : AICL Mode : CHG_INT_OK(0x%02x)\n", + __func__, aicl_state); + + mutex_lock(&charger->icl_mutex); + cancel_delayed_work(&charger->wc_current_work); + __pm_relax(charger->wc_current_ws); + mutex_unlock(&charger->icl_mutex); + + charger->aicl_curr = reduce_input_current(charger); + + if (is_not_wireless_type(charger->cable_type)) + max77705_check_slow_charging(charger, charger->input_current); + + if (charger->input_current <= MINIMUM_INPUT_CURRENT) { + max77705_aicl_irq_enable(charger, false); + + /* notify aicl current, no more aicl check */ + aicl_current = MINIMUM_INPUT_CURRENT; + } else { + aicl_mode = true; + queue_delayed_work(charger->wqueue, &charger->aicl_work, + msecs_to_jiffies(AICL_WORK_DELAY)); + } + } else { + /* Not in AICL mode */ + pr_info("%s : Not in AICL Mode : CHG_INT_OK(0x%02x), aicl_curr(%d)\n", + __func__, aicl_state, charger->aicl_curr); + if (charger->aicl_curr) { + /* notify aicl current, if aicl is on and aicl state is cleard */ + aicl_current = charger->aicl_curr; + } + } + + if (aicl_current) { + value.intval = aicl_current; + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_AICL_CURRENT, value); + } + + /* keep wakeup_source if this is not last work to prevent to enter suspend */ + if (!aicl_mode && !delayed_work_pending(&charger->aicl_work)) + __pm_relax(charger->aicl_ws); +} + +static irqreturn_t max77705_aicl_irq(int irq, void *data) +{ + struct max77705_charger_data *charger = data; + + __pm_stay_awake(charger->aicl_ws); + queue_delayed_work(charger->wqueue, &charger->aicl_work, + msecs_to_jiffies(AICL_WORK_DELAY)); + + pr_info("%s: irq(%d)\n", __func__, irq); + + return IRQ_HANDLED; +} + +static void max77705_init_aicl_irq(struct max77705_charger_data *charger) +{ + int ret; + + charger->irq_aicl = charger->max77705_pdata->irq_base + MAX77705_CHG_IRQ_AICL_I; + ret = request_threaded_irq(charger->irq_aicl, NULL, + max77705_aicl_irq, 0, + "aicl-irq", charger); + if (ret < 0) { + pr_err("%s: fail to request aicl IRQ: %d: %d\n", + __func__, charger->irq_aicl, ret); + } + pr_info("%s: %d\n", __func__, irqd_irq_disabled(&irq_to_desc(charger->irq_aicl)->irq_data)); +} + +static void max77705_wc_current_work(struct work_struct *work) +{ + struct max77705_charger_data *charger = + container_of(work, struct max77705_charger_data, wc_current_work.work); + union power_supply_propval value = {0, }; + int diff_current = 0; + + if (is_not_wireless_type(charger->cable_type)) { + charger->wc_pre_current = WC_CURRENT_START; + max77705_write_reg(charger->i2c, MAX77705_CHG_REG_CNFG_10, WC_DEFAULT_CURRENT); + __pm_relax(charger->wc_current_ws); + return; + } + + if (charger->wc_pre_current == charger->wc_current) { + max77705_set_input_current(charger, charger->wc_pre_current); + max77705_set_charge_current(charger, charger->charging_current); +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_FASTCHG_LIMIT_CURRENT, value); +#endif + /* Wcurr-B) Restore Vrect adj room to previous value + * after finishing wireless input current setting. + * Refer to Wcurr-A) step + */ + msleep(500); + value.intval = WIRELESS_VRECT_ADJ_OFF; /* Vrect Room 0mV */ + psy_do_property(charger->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_RX_CONTROL, value); + /* keep wakeup_source if this is not last work to prevent to enter suspend */ + if (!delayed_work_pending(&charger->wc_current_work)) + __pm_relax(charger->wc_current_ws); + } else { + diff_current = charger->wc_pre_current - charger->wc_current; + diff_current = (diff_current > charger->pdata->wc_current_step) ? charger->pdata->wc_current_step : + ((diff_current < -charger->pdata->wc_current_step) ? -charger->pdata->wc_current_step : diff_current); + + charger->wc_pre_current -= diff_current; + max77705_set_input_current(charger, charger->wc_pre_current); + __pm_stay_awake(charger->wc_current_ws); + queue_delayed_work(charger->wqueue, &charger->wc_current_work, + msecs_to_jiffies(charger->otg_on ? + WC_CURRENT_WORK_STEP_OTG : WC_CURRENT_WORK_STEP)); + } + pr_info("%s: wc_current(%d), wc_pre_current(%d), diff(%d)\n", __func__, + charger->wc_current, charger->wc_pre_current, diff_current); +} + +static void max77705_wc_chg_current_work(struct work_struct *work) +{ + struct max77705_charger_data *charger = + container_of(work, struct max77705_charger_data, wc_chg_current_work.work); +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + union power_supply_propval value = {0, }; +#endif + + max77705_set_charge_current(charger, charger->charging_current); +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_FASTCHG_LIMIT_CURRENT, value); +#endif + __pm_relax(charger->wc_chg_current_ws); +} + +#if defined(CONFIG_USE_POGO) +static void max77705_wcin_det_work(struct work_struct *work) +{ + struct max77705_charger_data *charger = container_of(work, + struct max77705_charger_data, wcin_det_work.work); + u8 reg_data, wcin_state, wcin_dtls = 0; + union power_supply_propval value = {0, }, val_vbyp = {0, }; + + max77705_update_reg(charger->i2c, + MAX77705_CHG_REG_INT_MASK, 0, MAX77705_WCIN_IM); + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_INT_OK, ®_data); + wcin_state = (reg_data & MAX77705_WCIN_OK) >> MAX77705_WCIN_OK_SHIFT; + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_DETAILS_00, ®_data); + wcin_dtls = (reg_data & MAX77705_WCIN_DTLS) >> MAX77705_WCIN_DTLS_SHIFT; + + pr_info("%s wcin_state(%d) wcin_dtls(%d)\n", __func__, wcin_state, wcin_dtls); + + val_vbyp.intval = SEC_BATTERY_VBYP; + psy_do_property("max77705-fuelgauge", get, + POWER_SUPPLY_EXT_PROP_MEASURE_INPUT, val_vbyp); + + if (wcin_state && wcin_dtls) { + if (val_vbyp.intval > 8000) + value.intval = 2; + else + value.intval = 1; + } else + value.intval = 0; + + psy_do_property("pogo", set, POWER_SUPPLY_PROP_ONLINE, value); + + /* Do unmask again. (for frequent wcin irq problem) */ + max77705_update_reg(charger->i2c, + MAX77705_CHG_REG_INT_MASK, 0, MAX77705_WCIN_IM); + + __pm_relax(charger->wcin_det_ws); +} + +static irqreturn_t max77705_wcin_irq(int irq, void *data) +{ + struct max77705_charger_data *charger = data; + + pr_info("%s: irq(%d)\n", __func__, irq); + + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_INT_MASK, + MAX77705_WCIN_IM, MAX77705_WCIN_IM); + __pm_stay_awake(charger->wcin_det_ws); + queue_delayed_work(charger->wqueue, &charger->wcin_det_work, + msecs_to_jiffies(500)); + + return IRQ_HANDLED; +} +#endif + +static irqreturn_t max77705_sysovlo_irq(int irq, void *data) +{ + struct max77705_charger_data *charger = data; + union power_supply_propval value = {0, }; + + pr_info("%s\n", __func__); + __pm_wakeup_event(charger->sysovlo_ws, jiffies_to_msecs(HZ * 5)); + + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_SYSOVLO, value); + + max77705_set_sysovlo(charger, 0); + return IRQ_HANDLED; +} + +#ifdef CONFIG_OF +static int max77705_charger_parse_dt(struct max77705_charger_data *charger) +{ + struct device_node *np; + max77705_charger_platform_data_t *pdata = charger->pdata; + int ret = 0; + + np = of_find_node_by_name(NULL, "battery"); + if (!np) { + pr_err("%s: np(battery) NULL\n", __func__); + } else { + ret = of_property_read_u32(np, "battery,chg_float_voltage", + &pdata->chg_float_voltage); + if (ret) { + pr_info("%s: battery,chg_float_voltage is Empty\n", __func__); + pdata->chg_float_voltage = 4200; + } + charger->float_voltage = pdata->chg_float_voltage; + + pdata->boosting_voltage_aicl = of_property_read_bool(np, + "battery,boosting_voltage_aicl"); + + ret = of_property_read_u32(np, "battery,chg_ocp_current", + &pdata->chg_ocp_current); + if (ret) { + pr_info("%s: battery,chg_ocp_current is Empty\n", __func__); + pdata->chg_ocp_current = 5600; /* mA */ + } + + ret = of_property_read_u32(np, "battery,chg_ocp_dtc", + &pdata->chg_ocp_dtc); + if (ret) { + pr_info("%s: battery,chg_ocp_dtc is Empty\n", __func__); + pdata->chg_ocp_dtc = 6; /* ms */ + } + + ret = of_property_read_u32(np, "battery,topoff_time", + &pdata->topoff_time); + if (ret) { + pr_info("%s: battery,topoff_time is Empty\n", __func__); + pdata->topoff_time = 30; /* min */ + } + + ret = of_property_read_string(np, "battery,wireless_charger_name", + (char const **)&pdata->wireless_charger_name); + if (ret) + pr_info("%s: Wireless charger name is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,full_check_type_2nd", + &pdata->full_check_type_2nd); + if (ret) + pr_info("%s : Full check type 2nd is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,wireless_cc_cv", + &pdata->wireless_cc_cv); + if (ret) + pr_info("%s : wireless_cc_cv is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,nv_wc_headroom", + &pdata->nv_wc_headroom); + if (ret) { + pr_info("%s : nv_wc_headroom is Empty\n", __func__); + pdata->nv_wc_headroom = WIRELESS_VRECT_ADJ_ROOM_1; /* 277mV */ + } + + pr_info("%s: fv : %d, ocp_curr : %d, ocp_dtc : %d, topoff_time : %d\n", + __func__, charger->float_voltage, pdata->chg_ocp_current, + pdata->chg_ocp_dtc, pdata->topoff_time); + + ret = of_property_read_u32(np, "battery,max_charging_current", + &pdata->max_fcc); + if (ret) { + pr_info("%s: battery,max_charging_current is Empty\n", __func__); + pdata->max_fcc = 3150; /* mA */ + } + + } + + np = of_find_node_by_name(NULL, "max77705-charger"); + if (!np) { + pr_err("%s: np(max77705-charger) NULL\n", __func__); + } else { + ret = of_property_read_u32(np, "charger,fac_vsys", &pdata->fac_vsys); + if (ret) { + pr_info("%s : fac_vsys is Empty\n", __func__); + pdata->fac_vsys = 3800; /* mV */ + } +#if defined(CONFIG_SEC_FACTORY) + pdata->factory_wcin_irq = + of_property_read_bool(np, "charger,factory_wcin_irq"); +#endif + pdata->user_wcin_irq = + of_property_read_bool(np, "charger,user_wcin_irq"); + + pdata->enable_sysovlo_irq = + of_property_read_bool(np, "charger,enable_sysovlo_irq"); + + pdata->enable_noise_wa = + of_property_read_bool(np, "charger,enable_noise_wa"); + + pdata->enable_dpm = + of_property_read_bool(np, "charger,enable_dpm"); + + ret = of_property_read_u32(np, "charger,fac_vchgin_reg", &pdata->fac_vchgin_reg); + if (ret) { + pr_info("%s : fac_vchgin_reg is default\n", __func__); + pdata->fac_vchgin_reg = 0; + } + pr_info("%s: fac_vchgin_reg:%d\n", __func__, pdata->fac_vchgin_reg); + + if (of_property_read_u32(np, "charger,dpm_icl", &pdata->dpm_icl)) { + pr_info("%s : dpm_icl is Empty\n", __func__); + pdata->dpm_icl = 1800; + } + + pdata->disqbat = of_get_named_gpio(np, "charger,disqbat", 0); + if (pdata->disqbat < 0) { + pr_info("%s : can't get disqbat\n", __func__); + pdata->disqbat = 0; + } + pr_info("%s: pdata->disqbat %d\n", __func__, pdata->disqbat); + + if (of_property_read_u32(np, "charger,fsw", &pdata->fsw)) { + pr_info("%s : fsw is Empty\n", __func__); + pdata->fsw = MAX77705_CHG_FSW_1_5MHz; + } + charger->fsw_now = pdata->fsw; + + ret = of_property_read_u32(np, "charger,wc_current_step", + &pdata->wc_current_step); + if (ret) { + pr_info("%s: battery,wc_current_step is Empty\n", __func__); + pdata->wc_current_step = WC_CURRENT_STEP; /* default 100mA */ + ret = 0; + } + + pr_info("%s: fac_vsys:%d, fsw:%d, wc_current_step:%d\n", __func__, + pdata->fac_vsys, pdata->fsw, pdata->wc_current_step); + } + + np = of_find_node_by_name(NULL, "max77705-fuelgauge"); + if (!np) { + pr_err("%s: np(max77705-fuelgauge) NULL\n", __func__); + } else { + charger->jig_low_active = of_property_read_bool(np, + "fuelgauge,jig_low_active"); + charger->jig_gpio = of_get_named_gpio(np, "fuelgauge,jig_gpio", 0); + if (charger->jig_gpio < 0) { + pr_err("%s: error reading jig_gpio = %d\n", + __func__, charger->jig_gpio); + charger->jig_gpio = 0; + } + } + + return ret; +} +#endif + +static const struct power_supply_desc max77705_charger_power_supply_desc = { + .name = "max77705-charger", + .type = POWER_SUPPLY_TYPE_UNKNOWN, + .properties = max77705_charger_props, + .num_properties = ARRAY_SIZE(max77705_charger_props), + .get_property = max77705_chg_get_property, + .set_property = max77705_chg_set_property, + .no_thermal = true, +}; + +static char *max77705_otg_supply_list[] = { + "otg", +}; + +static const struct power_supply_desc max77705_otg_power_supply_desc = { + .name = "max77705-otg", + .type = POWER_SUPPLY_TYPE_UNKNOWN, + .properties = max77705_otg_props, + .num_properties = ARRAY_SIZE(max77705_otg_props), + .get_property = max77705_otg_get_property, + .set_property = max77705_otg_set_property, +}; + +static int max77705_charger_probe(struct platform_device *pdev) +{ + struct max77705_dev *max77705 = dev_get_drvdata(pdev->dev.parent); + struct max77705_platform_data *pdata = dev_get_platdata(max77705->dev); + max77705_charger_platform_data_t *charger_data; + struct max77705_charger_data *charger; + struct power_supply_config charger_cfg = { }; + int ret = 0; + u8 reg_data; + + pr_info("%s: max77705 Charger Driver Loading\n", __func__); + + charger = kzalloc(sizeof(*charger), GFP_KERNEL); + if (!charger) + return -ENOMEM; + + charger_data = kzalloc(sizeof(max77705_charger_platform_data_t), GFP_KERNEL); + if (!charger_data) { + ret = -ENOMEM; + goto err_free; + } + + mutex_init(&charger->charger_mutex); + mutex_init(&charger->mode_mutex); + mutex_init(&charger->icl_mutex); + + charger->dev = &pdev->dev; + charger->i2c = max77705->charger; + charger->pmic_i2c = max77705->i2c; + charger->pdata = charger_data; + charger->aicl_curr = 0; + charger->slow_charging = false; + charger->otg_on = false; + charger->uno_on = false; + charger->max77705_pdata = pdata; + charger->wc_pre_current = WC_CURRENT_START; + charger->cable_type = SEC_BATTERY_CABLE_NONE; + charger->hp_otg = false; + charger->bat_det = true; + charger->dpm_last_icl = 100; + atomic_set(&charger->shutdown_cnt, 0); + +#if defined(CONFIG_OF) + ret = max77705_charger_parse_dt(charger); + if (ret < 0) + pr_err("%s not found charger dt! ret[%d]\n", __func__, ret); +#endif + platform_set_drvdata(pdev, charger); + + max77705_charger_initialize(charger); + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_INT_OK, ®_data); + if (reg_data & MAX77705_WCIN_OK) +#if defined(CONFIG_USE_POGO) + charger->cable_type = SEC_BATTERY_CABLE_POGO; +#else + charger->cable_type = SEC_BATTERY_CABLE_WIRELESS; +#endif + charger->input_current = max77705_get_input_current(charger); + charger->charging_current = max77705_get_charge_current(charger); + + if (max77705_read_reg(max77705->i2c, MAX77705_PMIC_REG_PMICREV, ®_data) < 0) { + pr_err("device not found on this channel (this is not an error)\n"); + ret = -ENOMEM; + goto err_pdata_free; + } else { + charger->pmic_ver = (reg_data & 0x7); + pr_info("%s : device found : ver.0x%x\n", __func__, charger->pmic_ver); + } + + (void)debugfs_create_file("max77705-regs", + S_IRUGO, NULL, (void *)charger, + &max77705_debugfs_fops); + + charger->wqueue = create_singlethread_workqueue(dev_name(&pdev->dev)); + if (!charger->wqueue) { + pr_err("%s: Fail to Create Workqueue\n", __func__); + goto err_pdata_free; + } +#if defined(CONFIG_USE_POGO) + charger->wcin_det_ws = wakeup_source_register(&pdev->dev, "charger-wcin-det"); +#endif + charger->aicl_ws = wakeup_source_register(&pdev->dev, "charger-aicl"); + charger->wc_current_ws = wakeup_source_register(&pdev->dev, "charger->wc-current"); + charger->otg_ws = wakeup_source_register(&pdev->dev, "charger->otg"); + charger->wc_chg_current_ws = wakeup_source_register(&pdev->dev, "charger->wc-chg-current"); + +#if defined(CONFIG_USE_POGO) + INIT_DELAYED_WORK(&charger->wcin_det_work, max77705_wcin_det_work); +#endif + INIT_DELAYED_WORK(&charger->aicl_work, max77705_aicl_isr_work); + INIT_DELAYED_WORK(&charger->wc_current_work, max77705_wc_current_work); + INIT_DELAYED_WORK(&charger->wc_chg_current_work, max77705_wc_chg_current_work); + + charger_cfg.drv_data = charger; + charger->psy_chg = power_supply_register(&pdev->dev, + &max77705_charger_power_supply_desc, &charger_cfg); + if (IS_ERR(charger->psy_chg)) { + ret = PTR_ERR(charger->psy_chg); + pr_err("%s: Failed to Register psy_chg(%d)\n", __func__, ret); + goto err_power_supply_register; + } + + charger->psy_otg = power_supply_register(&pdev->dev, + &max77705_otg_power_supply_desc, &charger_cfg); + if (IS_ERR(charger->psy_otg)) { + ret = PTR_ERR(charger->psy_otg); + pr_err("%s: Failed to Register otg_chg(%d)\n", __func__, ret); + goto err_power_supply_register_otg; + } + charger->psy_otg->supplied_to = max77705_otg_supply_list; + charger->psy_otg->num_supplicants = ARRAY_SIZE(max77705_otg_supply_list); + + if (charger->pdata->chg_irq) { + INIT_DELAYED_WORK(&charger->isr_work, max77705_chg_isr_work); + + ret = request_threaded_irq(charger->pdata->chg_irq, NULL, + max77705_chg_irq_thread, 0, + "charger-irq", charger); + if (ret) { + pr_err("%s: Failed to Request IRQ\n", __func__); + goto err_irq; + } + + ret = enable_irq_wake(charger->pdata->chg_irq); + if (ret < 0) + pr_err("%s: Failed to Enable Wakeup Source(%d)\n", __func__, ret); + } + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_INT_OK, ®_data); + + charger->irq_bypass = pdata->irq_base + MAX77705_CHG_IRQ_BYP_I; + ret = request_threaded_irq(charger->irq_bypass, NULL, + max77705_bypass_irq, 0, + "bypass-irq", charger); + if (ret < 0) + pr_err("%s: fail to request bypass IRQ: %d: %d\n", + __func__, charger->irq_bypass, ret); + + charger->irq_batp = pdata->irq_base + MAX77705_CHG_IRQ_BATP_I; + ret = request_threaded_irq(charger->irq_batp, NULL, + max77705_batp_irq, 0, + "batp-irq", charger); + if (ret < 0) + pr_err("%s: fail to request Battery Present IRQ: %d: %d\n", + __func__, charger->irq_batp, ret); + +#if defined(CONFIG_MAX77705_CHECK_B2SOVRC) + if ((sec_debug_get_debug_level() & 0x1) == 0x1) { + /* only work for debug level is mid */ + charger->irq_bat = pdata->irq_base + MAX77705_CHG_IRQ_BAT_I; + ret = request_threaded_irq(charger->irq_bat, NULL, + max77705_bat_irq, 0, + "bat-irq", charger); + if (ret < 0) + pr_err("%s: fail to request Battery IRQ: %d: %d\n", + __func__, charger->irq_bat, ret); + } +#endif + +#if defined(CONFIG_USE_POGO) + if (charger->pdata->factory_wcin_irq || charger->pdata->user_wcin_irq) { + charger->irq_wcin = pdata->irq_base + MAX77705_CHG_IRQ_WCIN_I; + ret = request_threaded_irq(charger->irq_wcin, + NULL, max77705_wcin_irq, + IRQF_TRIGGER_FALLING, "wcin-irq", charger); + if (ret < 0) + pr_err("%s: fail to request wcin det IRQ: %d: %d\n", + __func__, charger->irq_wcin, ret); + } +#endif + + if (charger->pdata->enable_sysovlo_irq) { + charger->sysovlo_ws = wakeup_source_register(&pdev->dev, "max77705-sysovlo"); + /* Enable BIAS */ + max77705_update_reg(max77705->i2c, MAX77705_PMIC_REG_MAINCTRL1, + 0x80, 0x80); + /* set IRQ thread */ + charger->irq_sysovlo = + pdata->irq_base + MAX77705_SYSTEM_IRQ_SYSOVLO_INT; + ret = request_threaded_irq(charger->irq_sysovlo, NULL, + max77705_sysovlo_irq, 0, + "sysovlo-irq", charger); + if (ret < 0) + pr_err("%s: fail to request sysovlo IRQ: %d: %d\n", + __func__, charger->irq_sysovlo, ret); + enable_irq_wake(charger->irq_sysovlo); + } + + ret = max77705_chg_create_attrs(&charger->psy_chg->dev); + if (ret) { + dev_err(charger->dev, "%s : Failed to max77705_chg_create_attrs\n", __func__); + goto err_atts; + } + + /* watchdog kick */ + max77705_chg_set_wdtmr_kick(charger); + + sec_chg_set_dev_init(SC_DEV_MAIN_CHG); + + pr_info("%s: MAX77705 Charger Driver Loaded\n", __func__); + + return 0; + +err_atts: + free_irq(charger->pdata->chg_irq, charger); +err_irq: + power_supply_unregister(charger->psy_otg); +err_power_supply_register_otg: + power_supply_unregister(charger->psy_chg); +err_power_supply_register: + destroy_workqueue(charger->wqueue); + wakeup_source_unregister(charger->sysovlo_ws); + wakeup_source_unregister(charger->otg_ws); + wakeup_source_unregister(charger->wc_current_ws); + wakeup_source_unregister(charger->aicl_ws); + wakeup_source_unregister(charger->wc_chg_current_ws); +#if defined(CONFIG_USE_POGO) + wakeup_source_unregister(charger->wcin_det_ws); +#endif +err_pdata_free: + kfree(charger_data); +err_free: + kfree(charger); + + return ret; +} + +static int max77705_charger_remove(struct platform_device *pdev) +{ + struct max77705_charger_data *charger = platform_get_drvdata(pdev); + + pr_info("%s: ++\n", __func__); + + destroy_workqueue(charger->wqueue); + + if (charger->i2c) { + u8 reg_data; + + reg_data = MAX77705_MODE_4_BUCK_ON; + max77705_write_reg(charger->i2c, MAX77705_CHG_REG_CNFG_00, reg_data); + reg_data = 0x0F; + max77705_write_reg(charger->i2c, MAX77705_CHG_REG_CNFG_09, reg_data); + reg_data = 0x10; + max77705_write_reg(charger->i2c, MAX77705_CHG_REG_CNFG_10, reg_data); + reg_data = 0x60; + max77705_write_reg(charger->i2c, MAX77705_CHG_REG_CNFG_12, reg_data); + } else { + pr_err("%s: no max77705 i2c client\n", __func__); + } + + if (charger->irq_sysovlo) + free_irq(charger->irq_sysovlo, charger); +#if defined(CONFIG_USE_POGO) + if (charger->irq_wcin) + free_irq(charger->irq_wcin, charger); +#endif + if (charger->pdata->chg_irq) + free_irq(charger->pdata->chg_irq, charger); + if (charger->psy_chg) + power_supply_unregister(charger->psy_chg); + if (charger->psy_otg) + power_supply_unregister(charger->psy_otg); + + wakeup_source_unregister(charger->sysovlo_ws); + wakeup_source_unregister(charger->otg_ws); + wakeup_source_unregister(charger->wc_current_ws); + wakeup_source_unregister(charger->aicl_ws); + wakeup_source_unregister(charger->wc_chg_current_ws); +#if defined(CONFIG_USE_POGO) + wakeup_source_unregister(charger->wcin_det_ws); +#endif + + kfree(charger); + + pr_info("%s: --\n", __func__); + + return 0; +} + +#if defined CONFIG_PM +static int max77705_charger_prepare(struct device *dev) +{ + struct max77705_charger_data *charger = dev_get_drvdata(dev); + + pr_info("%s\n", __func__); + + if ((charger->cable_type == SEC_BATTERY_CABLE_USB || + charger->cable_type == SEC_BATTERY_CABLE_TA) + && charger->input_current >= 500) { + u8 reg_data; + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_09, ®_data); + reg_data &= MAX77705_CHG_CHGIN_LIM; + max77705_usbc_icurr(reg_data); + max77705_set_fw_noautoibus(MAX77705_AUTOIBUS_ON); + } + + return 0; +} + +static int max77705_charger_suspend(struct device *dev) +{ + return 0; +} + +static int max77705_charger_resume(struct device *dev) +{ + return 0; +} + +static void max77705_charger_complete(struct device *dev) +{ + struct max77705_charger_data *charger = dev_get_drvdata(dev); + + pr_info("%s\n", __func__); + + if (!max77705_get_autoibus(charger)) + max77705_set_fw_noautoibus(MAX77705_AUTOIBUS_AT_OFF); +} +#else +#define max77705_charger_prepare NULL +#define max77705_charger_suspend NULL +#define max77705_charger_resume NULL +#define max77705_charger_complete NULL +#endif + +static void max77705_charger_set_shtdn_vchgin(struct max77705_charger_data *charger, bool set) +{ + u8 reg_data; + + if (!max77705_get_facmode_siso()) { + if (charger->pdata->fac_vchgin_reg) { +#if defined(CONFIG_SEC_FACTORY) + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_12, + CHG_CNFG_12_VCHGIN(charger->pdata->fac_vchgin_reg), CHG_CNFG_12_VCHGIN_REG_MASK); +#else + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_12, + CHG_CNFG_12_VCHGIN(REG_4500_UVLO_4700), CHG_CNFG_12_VCHGIN_REG_MASK); +#endif + } else if (set) { + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_12, + CHG_CNFG_12_VCHGIN(REG_4500_UVLO_4700), CHG_CNFG_12_VCHGIN_REG_MASK); + } + } + + max77705_read_reg(charger->i2c, MAX77705_CHG_REG_CNFG_12, ®_data); + pr_info("%s: reg_data = 0x%2x\n", __func__, reg_data); +} + +static void max77705_charger_shutdown(struct platform_device *pdev) +{ + struct max77705_charger_data *charger = platform_get_drvdata(pdev); + + pr_info("%s: ++\n", __func__); + + atomic_inc(&charger->shutdown_cnt); + +#if defined(CONFIG_SEC_FACTORY) + if (max77705_get_facmode()) { + /* Maintain settings for old models */ + max77705_charger_set_shtdn_vchgin(charger, false); + goto free_chg; /* prevent SMPL during SMD ARRAY shutdown */ + } +#endif + if (charger->i2c) { + u8 reg_data; + + reg_data = MAX77705_MODE_4_BUCK_ON; /* Buck on, Charge off */ + max77705_write_reg(charger->i2c, MAX77705_CHG_REG_CNFG_00, reg_data); +#if !defined(CONFIG_SEC_FACTORY) + if ((is_wired_type(charger->cable_type)) + && (charger->cable_type != SEC_BATTERY_CABLE_USB)) + reg_data = 0x3B; /* CHGIN_ILIM 1500mA */ + else +#endif + reg_data = 0x13; /* CHGIN_ILIM 500mA */ + max77705_write_reg(charger->i2c, MAX77705_CHG_REG_CNFG_09, reg_data); + reg_data = 0x13; /* WCIN_ILIM 500mA */ + max77705_write_reg(charger->i2c, MAX77705_CHG_REG_CNFG_10, reg_data); + /* CHGINSEL/WCINSEL enable */ + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_12, + 1 << CHG_CNFG_12_CHGINSEL_SHIFT, CHG_CNFG_12_CHGINSEL_MASK); + max77705_update_reg(charger->i2c, MAX77705_CHG_REG_CNFG_12, + 1 << CHG_CNFG_12_WCINSEL_SHIFT, CHG_CNFG_12_WCINSEL_MASK); + max77705_charger_set_shtdn_vchgin(charger, true); + + /* enable auto shipmode, this should work under 2.6V */ + max77705_set_auto_ship_mode(charger, 1); + } else { + pr_err("%s: no max77705 i2c client\n", __func__); + } + +#if defined(CONFIG_SEC_FACTORY) +free_chg: +#endif + free_irq(charger->irq_aicl, charger); + free_irq(charger->irq_chgin, charger); + free_irq(charger->irq_bypass, charger); + free_irq(charger->irq_batp, charger); +#if defined(CONFIG_MAX77705_CHECK_B2SOVRC) + if (charger->irq_bat) + free_irq(charger->irq_bat, charger); +#endif + if (charger->irq_sysovlo) + free_irq(charger->irq_sysovlo, charger); + if (charger->pdata->chg_irq) { + free_irq(charger->pdata->chg_irq, charger); + cancel_delayed_work(&charger->isr_work); + } + + cancel_delayed_work(&charger->aicl_work); + cancel_delayed_work(&charger->wc_current_work); + + pr_info("%s: --\n", __func__); +} + +static const struct dev_pm_ops max77705_charger_pm_ops = { + .prepare = max77705_charger_prepare, + .suspend = max77705_charger_suspend, + .resume = max77705_charger_resume, + .complete = max77705_charger_complete, +}; + +static struct platform_driver max77705_charger_driver = { + .driver = { + .name = "max77705-charger", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &max77705_charger_pm_ops, +#endif + }, + .probe = max77705_charger_probe, + .remove = max77705_charger_remove, + .shutdown = max77705_charger_shutdown, +}; + +static int __init max77705_charger_init(void) +{ + pr_info("%s:\n", __func__); + return platform_driver_register(&max77705_charger_driver); +} + +static void __exit max77705_charger_exit(void) +{ + platform_driver_unregister(&max77705_charger_driver); +} + +module_init(max77705_charger_init); +module_exit(max77705_charger_exit); + +MODULE_DESCRIPTION("Samsung MAX77705 Charger Driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/battery/charger/max77705_charger/max77705_charger.dtsi b/drivers/battery/charger/max77705_charger/max77705_charger.dtsi new file mode 100644 index 000000000000..4d86e2b6d985 --- /dev/null +++ b/drivers/battery/charger/max77705_charger/max77705_charger.dtsi @@ -0,0 +1,19 @@ +&smd { + max77705_charger: max77705-charger { + charger,fac_vsys = <3800>; + }; + + battery { + battery,fgsrc_switch_name = "max77705-charger"; + battery,otg_name = "max77705-otg"; + }; +}; + + +/* /home/dpi/qb5_8814/workspace/P4_1716/android/kernel_platform/kmodule/battery/stable/eureka/charger/max77705/max77705_charger.e3q.dtsi */ +&max77705_charger { + charger,enable_sysovlo_irq; + charger,fac_vsys = <4400>; + charger,fsw = <0>; /* 3MHz */ +}; + diff --git a/drivers/battery/charger/max77705_charger/max77705_charger.h b/drivers/battery/charger/max77705_charger/max77705_charger.h new file mode 100644 index 000000000000..2b024837ff47 --- /dev/null +++ b/drivers/battery/charger/max77705_charger/max77705_charger.h @@ -0,0 +1,459 @@ +/* + * max77705_charger.h + * Samsung max77705 Charger Header + * + * Copyright (C) 2015 Samsung Electronics, 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 __MAX77705_CHARGER_H +#define __MAX77705_CHARGER_H __FILE__ + +#include +#include +#include +#include +#include +#include "../../common/sec_charging_common.h" +#include + +enum { + CHIP_ID = 0, + DATA, +}; + +enum { + SHIP_MODE_DISABLE = 0, + SHIP_MODE_EN_OP, + SHIP_MODE_EN, +}; + +enum { + REG_4500_UVLO_4700 = 0, + REG_4600_UVLO_4800, + REG_4700_UVLO_4900, + REG_4850_UVLO_5050, +}; + +ssize_t max77705_chg_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf); + +ssize_t max77705_chg_store_attrs(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +#define MAX77705_CHARGER_ATTR(_name) \ +{ \ + .attr = {.name = #_name, .mode = 0664}, \ + .show = max77705_chg_show_attrs, \ + .store = max77705_chg_store_attrs, \ +} + +#define MAX77705_CHG_SAFEOUT2 0x80 + +/* MAX77705_CHG_REG_CHG_INT */ +#define MAX77705_BYP_I (1 << 0) +#define MAX77705_INP_LIMIT_I (1 << 1) +#define MAX77705_BATP_I (1 << 2) +#define MAX77705_BAT_I (1 << 3) +#define MAX77705_CHG_I (1 << 4) +#define MAX77705_WCIN_I (1 << 5) +#define MAX77705_CHGIN_I (1 << 6) +#define MAX77705_AICL_I (1 << 7) + +/* MAX77705_CHG_REG_CHG_INT_MASK */ +#define MAX77705_BYP_IM (1 << 0) +#define MAX77705_INP_LIMIT_IM (1 << 1) +#define MAX77705_BATP_IM (1 << 2) +#define MAX77705_BAT_IM (1 << 3) +#define MAX77705_CHG_IM (1 << 4) +#define MAX77705_WCIN_IM (1 << 5) +#define MAX77705_CHGIN_IM (1 << 6) +#define MAX77705_AICL_IM (1 << 7) + +/* MAX77705_CHG_REG_CHG_INT_OK */ +#define MAX77705_BYP_OK 0x01 +#define MAX77705_BYP_OK_SHIFT 0 +#define MAX77705_DISQBAT_OK 0x02 +#define MAX77705_DISQBAT_OK_SHIFT 1 +#define MAX77705_BATP_OK 0x04 +#define MAX77705_BATP_OK_SHIFT 2 +#define MAX77705_BAT_OK 0x08 +#define MAX77705_BAT_OK_SHIFT 3 +#define MAX77705_CHG_OK 0x10 +#define MAX77705_CHG_OK_SHIFT 4 +#define MAX77705_WCIN_OK 0x20 +#define MAX77705_WCIN_OK_SHIFT 5 +#define MAX77705_CHGIN_OK 0x40 +#define MAX77705_CHGIN_OK_SHIFT 6 +#define MAX77705_AICL_OK 0x80 +#define MAX77705_AICL_OK_SHIFT 7 +#define MAX77705_DETBAT 0x04 +#define MAX77705_DETBAT_SHIFT 2 + +/* MAX77705_CHG_REG_CHG_DTLS_00 */ +#define MAX77705_BATP_DTLS 0x01 +#define MAX77705_BATP_DTLS_SHIFT 0 +#define MAX77705_WCIN_DTLS 0x18 +#define MAX77705_WCIN_DTLS_SHIFT 3 +#define MAX77705_CHGIN_DTLS 0x60 +#define MAX77705_CHGIN_DTLS_SHIFT 5 +#define MAX77705_SPSN_DTLS 0x06 +#define MAX77705_SPSN_DTLS_SHIFT 1 + +/* MAX77705_CHG_REG_CHG_DTLS_01 */ +#define MAX77705_CHG_DTLS 0x0F +#define MAX77705_CHG_DTLS_SHIFT 0 +#define MAX77705_BAT_DTLS 0x70 +#define MAX77705_BAT_DTLS_SHIFT 4 + +/* MAX77705_CHG_REG_CHG_DTLS_02 */ +#define MAX77705_BYP_DTLS 0x0F +#define MAX77705_BYP_DTLS_SHIFT 0 +#define MAX77705_BYP_DTLS0 0x1 +#define MAX77705_BYP_DTLS1 0x2 +#define MAX77705_BYP_DTLS2 0x4 +#define MAX77705_BYP_DTLS3 0x8 + +#if 1 +/* MAX77705_CHG_REG_CHG_CNFG_00 */ +#define CHG_CNFG_00_MODE_SHIFT 0 +#define CHG_CNFG_00_CHG_SHIFT 0 +#define CHG_CNFG_00_UNO_SHIFT 1 +#define CHG_CNFG_00_OTG_SHIFT 1 +#define CHG_CNFG_00_BUCK_SHIFT 2 +#define CHG_CNFG_00_BOOST_SHIFT 3 +#define CHG_CNFG_00_WDTEN_SHIFT 4 +#define CHG_CNFG_00_MODE_MASK (0x0F << CHG_CNFG_00_MODE_SHIFT) +#define CHG_CNFG_00_CHG_MASK (1 << CHG_CNFG_00_CHG_SHIFT) +#define CHG_CNFG_00_UNO_MASK (1 << CHG_CNFG_00_UNO_SHIFT) +#define CHG_CNFG_00_OTG_MASK (1 << CHG_CNFG_00_OTG_SHIFT) +#define CHG_CNFG_00_BUCK_MASK (1 << CHG_CNFG_00_BUCK_SHIFT) +#define CHG_CNFG_00_BOOST_MASK (1 << CHG_CNFG_00_BOOST_SHIFT) +#define CHG_CNFG_00_WDTEN_MASK (1 << CHG_CNFG_00_WDTEN_SHIFT) +#define CHG_CNFG_00_UNO_CTRL (CHG_CNFG_00_UNO_MASK | CHG_CNFG_00_BOOST_MASK) +#define CHG_CNFG_00_OTG_CTRL (CHG_CNFG_00_OTG_MASK | CHG_CNFG_00_BOOST_MASK) +#define MAX77705_MODE_DEFAULT 0x04 +#define MAX77705_MODE_CHGR 0x01 +#define MAX77705_MODE_UNO 0x01 +#define MAX77705_MODE_OTG 0x02 +#define MAX77705_MODE_BUCK 0x04 +#define MAX77705_MODE_BOOST 0x08 +#endif +#define CHG_CNFG_00_MODE_SHIFT 0 +#define CHG_CNFG_00_MODE_MASK (0x0F << CHG_CNFG_00_MODE_SHIFT) +#define CHG_CNFG_00_WDTEN_SHIFT 4 +#define CHG_CNFG_00_WDTEN_MASK (1 << CHG_CNFG_00_WDTEN_SHIFT) +#define CHG_CNFG_00_SPSN_DET_EN_SHIFT 7 +#define CHG_CNFG_00_SPSN_DET_EN_MASK (1 << CHG_CNFG_00_SPSN_DET_EN_SHIFT) +#define MAX77705_SPSN_DET_ENABLE 0x01 +#define MAX77705_SPSN_DET_DISABLE 0x00 + +/* MAX77705_CHG_REG_CHG_CNFG_00 MODE[3:0] */ +#define MAX77705_MODE_0_ALL_OFF 0x0 +#define MAX77705_MODE_1_ALL_OFF 0x1 +#define MAX77705_MODE_2_ALL_OFF 0x2 +#define MAX77705_MODE_3_ALL_OFF 0x3 +#define MAX77705_MODE_4_BUCK_ON 0x4 +#define MAX77705_MODE_5_BUCK_CHG_ON 0x5 +#define MAX77705_MODE_6_BUCK_CHG_ON 0x6 +#define MAX77705_MODE_7_BUCK_CHG_ON 0x7 +#define MAX77705_MODE_8_BOOST_UNO_ON 0x8 +#define MAX77705_MODE_9_BOOST_ON 0x9 +#define MAX77705_MODE_A_BOOST_OTG_ON 0xA +#define MAX77705_MODE_B_RESERVED 0xB +#define MAX77705_MODE_C_BUCK_BOOST_UNO_ON 0xC +#define MAX77705_MODE_D_BUCK_CHG_BOOST_UNO_ON 0xD +#define MAX77705_MODE_E_BUCK_BOOST_OTG_ON 0xE +#define MAX77705_MODE_F_BUCK_CHG_BOOST_OTG_ON 0xF + +/* MAX77705_CHG_REG_CHG_CNFG_01 */ +#define CHG_CNFG_01_FCHGTIME_SHIFT 0 +#define CHG_CNFG_01_FCHGTIME_MASK (0x7 << CHG_CNFG_01_FCHGTIME_SHIFT) +#define MAX77705_FCHGTIME_DISABLE 0x0 + +#define CHG_CNFG_01_RECYCLE_EN_SHIFT 3 +#define CHG_CNFG_01_RECYCLE_EN_MASK (0x1 << CHG_CNFG_01_RECYCLE_EN_SHIFT) +#define MAX77705_RECYCLE_EN_ENABLE 0x1 + +#define CHG_CNFG_01_CHG_RSTRT_SHIFT 4 +#define CHG_CNFG_01_CHG_RSTRT_MASK (0x3 << CHG_CNFG_01_CHG_RSTRT_SHIFT) +#define MAX77705_CHG_RSTRT_DISABLE 0x3 + +#define CHG_CNFG_01_PQEN_SHIFT 7 +#define CHG_CNFG_01_PQEN_MASK (0x1 << CHG_CNFG_01_PQEN_SHIFT) +#define MAX77705_CHG_PQEN_DISABLE 0x0 +#define MAX77705_CHG_PQEN_ENABLE 0x1 + +/* MAX77705_CHG_REG_CHG_CNFG_02 */ +#define CHG_CNFG_02_OTG_ILIM_SHIFT 6 +#define CHG_CNFG_02_OTG_ILIM_MASK (0x3 << CHG_CNFG_02_OTG_ILIM_SHIFT) +#define MAX77705_OTG_ILIM_500 0x0 +#define MAX77705_OTG_ILIM_900 0x1 +#define MAX77705_OTG_ILIM_1200 0x2 +#define MAX77705_OTG_ILIM_1500 0x3 +#define MAX77705_CHG_CC 0x3F + +/* MAX77705_CHG_REG_CHG_CNFG_03 */ +#define CHG_CNFG_03_TO_ITH_SHIFT 0 +#define CHG_CNFG_03_TO_ITH_MASK (0x7 << CHG_CNFG_03_TO_ITH_SHIFT) +#define MAX77705_TO_ITH_150MA 0x0 + +#define CHG_CNFG_03_TO_TIME_SHIFT 3 +#define CHG_CNFG_03_TO_TIME_MASK (0x7 << CHG_CNFG_03_TO_TIME_SHIFT) +#define MAX77705_TO_TIME_30M 0x3 +#define MAX77705_TO_TIME_70M 0x7 + +#define CHG_CNFG_03_REG_AUTO_SHIPMODE_SHIFT 6 +#define CHG_CNFG_03_REG_AUTO_SHIPMODE_MASK (0x1 << CHG_CNFG_03_REG_AUTO_SHIPMODE_SHIFT) + +#define CHG_CNFG_03_SYS_TRACK_DIS_SHIFT 7 +#define CHG_CNFG_03_SYS_TRACK_DIS_MASK (0x1 << CHG_CNFG_03_SYS_TRACK_DIS_SHIFT) +#define MAX77705_SYS_TRACK_ENABLE 0x0 +#define MAX77705_SYS_TRACK_DISABLE 0x1 + +/* MAX77705_CHG_REG_CHG_CNFG_04 */ +#define MAX77705_CHG_MINVSYS_MASK 0xC0 +#define MAX77705_CHG_MINVSYS_SHIFT 6 +#define MAX77705_CHG_PRM_MASK 0x1F +#define MAX77705_CHG_PRM_SHIFT 0 + +#define CHG_CNFG_04_CHG_CV_PRM_SHIFT 0 +#define CHG_CNFG_04_CHG_CV_PRM_MASK (0x3F << CHG_CNFG_04_CHG_CV_PRM_SHIFT) + +/* MAX77705_CHG_REG_CHG_CNFG_05 */ +#define CHG_CNFG_05_REG_B2SOVRC_SHIFT 0 +#define CHG_CNFG_05_REG_B2SOVRC_MASK (0xF << CHG_CNFG_05_REG_B2SOVRC_SHIFT) +#define MAX77705_B2SOVRC_DISABLE 0x0 +#define MAX77705_B2SOVRC_4_6A 0x7 +#define MAX77705_B2SOVRC_4_8A 0x8 +#define MAX77705_B2SOVRC_5_0A 0x9 +#define MAX77705_B2SOVRC_5_2A 0xA +#define MAX77705_B2SOVRC_5_4A 0xB +#define MAX77705_B2SOVRC_5_6A 0xC +#define MAX77705_B2SOVRC_5_8A 0xD +#define MAX77705_B2SOVRC_6_0A 0xE +#define MAX77705_B2SOVRC_6_2A 0xF + +#define CHG_CNFG_05_REG_UNOILIM_SHIFT 4 +#define CHG_CNFG_05_REG_UNOILIM_MASK (0x7 << CHG_CNFG_05_REG_UNOILIM_SHIFT) +#define MAX77705_UNOILIM_200 0x1 +#define MAX77705_UNOILIM_300 0x2 +#define MAX77705_UNOILIM_400 0x3 +#define MAX77705_UNOILIM_600 0x4 +#define MAX77705_UNOILIM_800 0x5 +#define MAX77705_UNOILIM_1000 0x6 +#define MAX77705_UNOILIM_1500 0x7 + +/* MAX77705_CHG_CNFG_06 */ +#define CHG_CNFG_06_WDTCLR_SHIFT 0 +#define CHG_CNFG_06_WDTCLR_MASK (0x3 << CHG_CNFG_06_WDTCLR_SHIFT) +#define MAX77705_WDTCLR 0x01 +#define CHG_CNFG_06_DIS_AICL_SHIFT 4 +#define CHG_CNFG_06_DIS_AICL_MASK (0x1 << CHG_CNFG_06_DIS_AICL_SHIFT) +#define MAX77705_DIS_AICL 0x0 +#define CHG_CNFG_06_B2SOVRC_DTC_SHIFT 7 +#define CHG_CNFG_06_B2SOVRC_DTC_MASK (0x1 << CHG_CNFG_06_B2SOVRC_DTC_SHIFT) +#define MAX77705_B2SOVRC_DTC_100MS 0x1 + +/* MAX77705_CHG_REG_CHG_CNFG_07 */ +#define MAX77705_CHG_FMBST 0x04 +#define CHG_CNFG_07_REG_FMBST_SHIFT 2 +#define CHG_CNFG_07_REG_FMBST_MASK (0x1 << CHG_CNFG_07_REG_FMBST_SHIFT) +#define CHG_CNFG_07_REG_FGSRC_SHIFT 1 +#define CHG_CNFG_07_REG_FGSRC_MASK (0x1 << CHG_CNFG_07_REG_FGSRC_SHIFT) +#define CHG_CNFG_07_REG_SHIPMODE_SHIFT 0 +#define CHG_CNFG_07_REG_SHIPMODE_MASK (0x1 << CHG_CNFG_07_REG_SHIPMODE_SHIFT) + +/* MAX77705_CHG_REG_CHG_CNFG_08 */ +#define CHG_CNFG_08_REG_FSW_SHIFT 0 +#define CHG_CNFG_08_REG_FSW_MASK (0x3 << CHG_CNFG_08_REG_FSW_SHIFT) +#define MAX77705_CHG_FSW_3MHz 0x00 +#define MAX77705_CHG_FSW_2MHz 0x01 +#define MAX77705_CHG_FSW_1_5MHz 0x02 + +/* MAX77705_CHG_REG_CHG_CNFG_09 */ +#define MAX77705_CHG_CHGIN_LIM 0x7F +#define MAX77705_CHG_EN 0x80 + +/* MAX77705_CHG_REG_CHG_CNFG_10 */ +#define MAX77705_CHG_WCIN_LIM 0x3F + +/* MAX77705_CHG_REG_CHG_CNFG_11 */ +#define CHG_CNFG_11_VBYPSET_SHIFT 0 +#define CHG_CNFG_11_VBYPSET_MASK (0x7F << CHG_CNFG_11_VBYPSET_SHIFT) + +/* MAX77705_CHG_REG_CHG_CNFG_12 */ +#define MAX77705_CHG_WCINSEL 0x40 +#define CHG_CNFG_12_CHGINSEL_SHIFT 5 +#define CHG_CNFG_12_CHGINSEL_MASK (0x1 << CHG_CNFG_12_CHGINSEL_SHIFT) +#define CHG_CNFG_12_WCINSEL_SHIFT 6 +#define CHG_CNFG_12_WCINSEL_MASK (0x1 << CHG_CNFG_12_WCINSEL_SHIFT) +#define CHG_CNFG_12_VCHGIN_REG_MASK (0x3 << 3) +#define CHG_CNFG_12_VCHGIN_SHIFT 3 +#define CHG_CNFG_12_WCIN_REG_MASK (0x3 << 1) +#define CHG_CNFG_12_REG_DISKIP_SHIFT 0 +#define CHG_CNFG_12_REG_DISKIP_MASK (0x1 << CHG_CNFG_12_REG_DISKIP_SHIFT) +#define MAX77705_DISABLE_SKIP 0x1 +#define MAX77705_AUTO_SKIP 0x0 +#define CHG_CNFG_12_VCHGIN(val) (val << CHG_CNFG_12_VCHGIN_SHIFT) + +/* MAX77705_CHG_REG_CHG_SWI_INT */ +#define MAX77705_CLIENT_TREG_I (1 << 0) +#define MAX77705_CV_I (1 << 1) +#define MAX77705_CLIENT_FAULT_I (1 << 2) + +/* MAX77705_CHG_REG_CHG_SWI_INT_MASK */ +#define MAX77705_CLIENT_TREG_IM (1 << 0) +#define MAX77705_CV_IM (1 << 1) +#define MAX77705_CLIENT_FAULT_IM (1 << 2) + +/* MAX77705_CHG_REG_CHG_SWI_STATUS */ +#define MAX77705_CLIENT_TREG_S 0x00 +#define MAX77705_CV_S 0x01 + +/* MAX77705_CHG_REG_CHG_SWI_STATUS */ +#define MAX77705_DIS_MIN_SELECTOR 0x80 + +/* MAX77705_CHG_REG_CHG_CLIENT_READBACK */ +#define MAX77705_SWI_READBACK 0x3F + +/* MAX77705_CHG_REG_CHG_CLIENT_CNTL */ +#define MAX77705_BOVE 0x03 + +#define REDUCE_CURRENT_STEP 100 +#define MINIMUM_INPUT_CURRENT 300 + +#define WC_CURRENT_STEP 100 +#define WC_CURRENT_START 480 + +#define WC_DEFAULT_CURRENT 0x10 + +#define DPM_MISC 0x4000 /* BATT_MISC_EVENT_DIRECT_POWER_MODE = 0x00004000 */ + +typedef struct max77705_charger_platform_data { + /* wirelss charger */ + char *wireless_charger_name; + int wireless_cc_cv; + int wc_current_step; + unsigned int nv_wc_headroom; + + /* float voltage (mV) */ + int chg_float_voltage; + int chg_irq; + unsigned int chg_ocp_current; + unsigned int chg_ocp_dtc; + unsigned int topoff_time; + int fac_vsys; + bool enable_noise_wa; + bool factory_wcin_irq; + bool user_wcin_irq; + bool enable_sysovlo_irq; + bool boosting_voltage_aicl; + int fsw; + bool enable_dpm; + int disqbat; + int dpm_icl; + int max_fcc; + int fac_vchgin_reg; + + /* OVP/UVLO check */ + int ovp_uvlo_check_type; + /* 1st full check */ + int full_check_type; + /* 2nd full check */ + int full_check_type_2nd; + +} max77705_charger_platform_data_t; + +struct max77705_charger_data { + struct device *dev; + struct i2c_client *i2c; + struct i2c_client *pmic_i2c; + + struct max77705_platform_data *max77705_pdata; + + struct power_supply *psy_chg; + struct power_supply *psy_otg; + + atomic_t shutdown_cnt; + + struct workqueue_struct *wqueue; + struct delayed_work aicl_work; + struct delayed_work isr_work; + struct delayed_work wc_current_work; + struct delayed_work wc_chg_current_work; +#if defined(CONFIG_USE_POGO) + struct delayed_work wcin_det_work; +#endif + + /* mutex */ + struct mutex charger_mutex; + struct mutex mode_mutex; + struct mutex icl_mutex; + + /* wakelock */ + struct wakeup_source *wc_current_ws; + struct wakeup_source *aicl_ws; + struct wakeup_source *otg_ws; + struct wakeup_source *sysovlo_ws; + struct wakeup_source *wc_chg_current_ws; +#if defined(CONFIG_USE_POGO) + struct wakeup_source *wcin_det_ws; +#endif + + unsigned int is_charging; + unsigned int cable_type; + unsigned int input_current; + unsigned int charging_current; + unsigned int vbus_state; + int aicl_curr; + bool slow_charging; + int status; + int charge_mode; + u8 cnfg00_mode; + int fsw_now; + + bool bat_det; + int irq_bypass; + int irq_batp; +#if defined(CONFIG_MAX77705_CHECK_B2SOVRC) + int irq_bat; +#endif + int irq_chgin; + int irq_aicl; + int irq_sysovlo; +#if defined(CONFIG_USE_POGO) + int irq_wcin; +#endif + int wc_current; + int wc_pre_current; + + bool jig_low_active; + int jig_gpio; + + bool otg_on; + bool uno_on; + + int pmic_ver; + int float_voltage; + + int misalign_cnt; + bool hp_otg; + + int dpm_last_icl; + + max77705_charger_platform_data_t *pdata; +}; + +#endif /* __MAX77705_CHARGER_H */ diff --git a/drivers/battery/charger/max77775_charger/Kconfig b/drivers/battery/charger/max77775_charger/Kconfig new file mode 100644 index 000000000000..8f8c9ca1e63a --- /dev/null +++ b/drivers/battery/charger/max77775_charger/Kconfig @@ -0,0 +1,39 @@ +config CHARGER_DUMMY + bool "dummy charger driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y here to enable + support for dummy charger driver. + This driver source code implemented + skeleton source code for charger functions. + +config CHARGER_MAX77775 + tristate "MAX77775 battery charger support" + depends on BATTERY_SAMSUNG + help + Say Y or M here to enable + support for the MAX77775 charger. + This includes boost mode. + This is Switching Mode Charger. + +config MAX77775_CHECK_B2SOVRC + bool "enable for check B2SOVRC" + default n + depends on CHARGER_MAX77775 + help + Say Y here to enable + support for CHARGER_MAX77775 check B2SCOVRC. + MAX77775 onply supports this checking options. + this is for MAX77775 monitoring B2SCOVRC. + +config SHIPMODE_BY_VBAT + bool "enable for check shipmode by vbat" + default n + depends on CHARGER_MAX77775 + help + Say Y here to enable + support for SHIPMODE_BY_VBAT. + MAX77775 only supports this checking options. + this is for shipmode by vbat. + diff --git a/drivers/battery/charger/max77775_charger/Makefile b/drivers/battery/charger/max77775_charger/Makefile new file mode 100644 index 000000000000..0ba70a881ea5 --- /dev/null +++ b/drivers/battery/charger/max77775_charger/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_CHARGER_MAX77775) += max77775_charger.o + +ccflags-y := -Wformat diff --git a/drivers/battery/charger/max77775_charger/max77775_charger.bzl b/drivers/battery/charger/max77775_charger/max77775_charger.bzl new file mode 100644 index 000000000000..83202210fe4e --- /dev/null +++ b/drivers/battery/charger/max77775_charger/max77775_charger.bzl @@ -0,0 +1,7 @@ +ko_list = [ + { + "ko_names" : [ + "drivers/battery/charger/max77775_charger/max77775_charger.ko" + ] + } +] diff --git a/drivers/battery/charger/max77775_charger/max77775_charger.c b/drivers/battery/charger/max77775_charger/max77775_charger.c new file mode 100644 index 000000000000..ed01dd53cad5 --- /dev/null +++ b/drivers/battery/charger/max77775_charger/max77775_charger.c @@ -0,0 +1,3835 @@ +/* + * max77775_charger.c + * Samsung max77775 Charger Driver + * + * Copyright (C) 2012 Samsung Electronics + * + * + * 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. + */ +#define DEBUG + +#include +#include +#include +#include +#include +#include +#include "max77775_charger.h" +#include +#ifdef CONFIG_USB_HOST_NOTIFY +#include +#endif +#include + +#define ENABLE 1 +#define DISABLE 0 + +#if defined(CONFIG_SEC_FACTORY) +#define WC_CURRENT_WORK_STEP 250 +#else +#define WC_CURRENT_WORK_STEP 200 +#endif +#define WC_CURRENT_WORK_STEP_OTG 200 +#define AICL_WORK_DELAY 100 + +static unsigned int __read_mostly lpcharge; +module_param(lpcharge, uint, 0444); +static int factory_mode; +module_param(factory_mode, int, 0444); + +extern void max77775_usbc_icurr(u8 curr); +extern void max77775_set_fw_noautoibus(int enable); +extern void max77775_usb_id_set(u8 mode); +extern void max77775_usbc_icurr_autoibus_on(u8 curr); +extern void max77775_set_shipmode_op(int enable, u8 data); +extern void max77775_chgrcv_ramp(bool enable); +#if !defined(CONFIG_SEC_FACTORY) +extern void max77775_bypass_maintain(void); +#endif + +static enum power_supply_property max77775_charger_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static enum power_supply_property max77775_otg_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static struct device_attribute max77775_charger_attrs[] = { + MAX77775_CHARGER_ATTR(chip_id), + MAX77775_CHARGER_ATTR(data), +}; + +static void max77775_charger_initialize(struct max77775_charger_data *charger); +static int max77775_get_vbus_state(struct max77775_charger_data *charger); +static int max77775_get_charger_state(struct max77775_charger_data *charger); +static void max77775_init_aicl_irq(struct max77775_charger_data *charger); +static void max77775_chg_set_mode_state(struct max77775_charger_data *charger, + unsigned int state); +static void max77775_set_switching_frequency(struct max77775_charger_data *charger, + int frequency); + +static unsigned int max77775_get_lpmode(void) { return lpcharge; } +static unsigned int max77775_get_facmode(void) { return factory_mode; } + +static bool max77775_charger_unlock(struct max77775_charger_data *charger) +{ + u8 reg_data, chgprot; + int retry_cnt = 0; + bool need_init = false; + + do { + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_06, ®_data); + chgprot = ((reg_data & 0x0C) >> 2); + if (chgprot != 0x03) { + pr_err("%s: unlock err, chgprot(0x%x), retry(%d)\n", + __func__, chgprot, retry_cnt); + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_06, + (0x03 << 2), (0x03 << 2)); + need_init = true; + msleep(20); + } else { + break; + } + } while ((chgprot != 0x03) && (++retry_cnt < 10)); + + return need_init; +} + +static void check_charger_unlock_state(struct max77775_charger_data *charger) +{ + pr_debug("%s\n", __func__); + + if (max77775_charger_unlock(charger)) { + pr_err("%s: charger locked state, reg init\n", __func__); + max77775_charger_initialize(charger); + } +} + +static void max77775_test_read(struct max77775_charger_data *charger) +{ + u8 data = 0; + u32 addr = 0; + char str[1024] = { 0, }; + + for (addr = 0xB1; addr <= 0xC8; addr++) { + max77775_read_reg(charger->i2c, addr, &data); + sprintf(str + strlen(str), "[0x%02x]0x%02x, ", addr, data); + } + pr_info("max77775_charger %s[B1~C8] : %s\n", __func__, str); +} + +static int max77775_get_autoibus(struct max77775_charger_data *charger) +{ + u8 reg_data; + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_DETAILS_00, ®_data); + if (reg_data & 0x80) + return 1; /* set by charger */ + + return 0; /* set by USBC */ +} + +static int max77775_get_vbus_state(struct max77775_charger_data *charger) +{ + u8 reg_data; + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_DETAILS_00, ®_data); + + if (is_wcin_type(charger->cable_type)) + reg_data = ((reg_data & MAX77775_WCIN_DTLS) >> + MAX77775_WCIN_DTLS_SHIFT); + else + reg_data = ((reg_data & MAX77775_CHGIN_DTLS) >> + MAX77775_CHGIN_DTLS_SHIFT); + + switch (reg_data) { + case 0x00: + pr_info("%s: VBUS is invalid. CHGIN < CHGIN_UVLO\n", __func__); + break; + case 0x01: + pr_info("%s: VBUS is invalid. CHGIN < MBAT+CHGIN2SYS and CHGIN > CHGIN_UVLO\n", __func__); + break; + case 0x02: + pr_info("%s: VBUS is invalid. CHGIN > CHGIN_OVLO\n", __func__); + break; + case 0x03: + pr_info("%s: VBUS is valid. CHGIN < CHGIN_OVLO\n", __func__); + break; + default: + break; + } + + return reg_data; +} + +static int max77775_get_charger_state(struct max77775_charger_data *charger) +{ + int status = POWER_SUPPLY_STATUS_UNKNOWN; + u8 reg_data; + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_DETAILS_01, ®_data); + pr_info("%s : charger status (0x%02x)\n", __func__, reg_data); + + reg_data &= 0x0f; + switch (reg_data) { + case 0x00: + case 0x01: + case 0x02: + status = POWER_SUPPLY_STATUS_CHARGING; + break; + case 0x03: + case 0x04: + status = POWER_SUPPLY_STATUS_FULL; + break; + case 0x05: + case 0x06: + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case 0x07: + case 0x08: + case 0x0A: + case 0x0B: + status = POWER_SUPPLY_STATUS_DISCHARGING; + break; + default: + break; + } + + return (int)status; +} + +static bool max77775_chg_get_wdtmr_status(struct max77775_charger_data *charger) +{ + u8 reg_data; + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_DETAILS_01, ®_data); + reg_data = ((reg_data & MAX77775_CHG_DTLS) >> MAX77775_CHG_DTLS_SHIFT); + + if (reg_data == 0x0B) { + dev_info(charger->dev, "WDT expired 0x%x !!\n", reg_data); + return true; + } + + return false; +} + +static int max77775_chg_set_wdtmr_en(struct max77775_charger_data *charger, + bool enable) +{ + pr_info("%s: WDT en = %d\n", __func__, enable); + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, + (enable ? CHG_CNFG_00_WDTEN_MASK : 0), CHG_CNFG_00_WDTEN_MASK); + + return 0; +} + +static int max77775_chg_set_wdtmr_kick(struct max77775_charger_data *charger) +{ + pr_info("%s: WDT Kick\n", __func__); + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_06, + (MAX77775_WDTCLR << CHG_CNFG_06_WDTCLR_SHIFT), + CHG_CNFG_06_WDTCLR_MASK); + + return 0; +} + +static void max77775_set_termination_voltage(struct max77775_charger_data *charger, + int float_voltage) +{ + u8 reg_data = 0; + + if (float_voltage > charger->pdata->chg_float_voltage) { + pr_info("%s: set float voltage(%d <-%d)\n", __func__, + charger->pdata->chg_float_voltage, float_voltage); + float_voltage = charger->pdata->chg_float_voltage; + } + + if (float_voltage <= 4000) + reg_data = ((float_voltage - 3400) / 100) + 0x00; + else if (float_voltage <= 4200) + reg_data = ((float_voltage - 4000) / 50) + 0x06; + else if (float_voltage <= 4400) + reg_data = ((float_voltage - 4200) / 10) + 0x0A; + else + reg_data = ((float_voltage - 4400) / 5) + 0x1E; + + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_04, + (reg_data << CHG_CNFG_04_CHG_CV_PRM_SHIFT), + CHG_CNFG_04_CHG_CV_PRM_MASK); + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_04, ®_data); + pr_info("%s: battery cv voltage 0x%x\n", __func__, reg_data); +} + +static void max77775_set_float_voltage(struct max77775_charger_data *charger, + int float_voltage) +{ + if (max77775_get_facmode()) { + float_voltage = charger->pdata->fac_vsys; + pr_info("%s: Factory mode Skip set float voltage(%d)\n", __func__, float_voltage); + // do not return here + } + + max77775_set_termination_voltage(charger, float_voltage); +} + +static int max77775_get_float_voltage(struct max77775_charger_data *charger) +{ + u8 reg_data = 0; + int float_voltage; + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_04, ®_data); + reg_data &= 0x3F; + + if (reg_data <= 0x06) + float_voltage = 3400 + (reg_data * 100); + else if (reg_data <= 0x0A) + float_voltage = 4000 + ((reg_data - 0x06) * 50); + else if (reg_data <= 0x1E) + float_voltage = 4200 + ((reg_data - 0x0A) * 10); + else + float_voltage = 4400 + ((reg_data - 0x1E) * 5); + + pr_debug("%s: battery cv reg : 0x%x, float voltage val : %d\n", + __func__, reg_data, float_voltage); + + return float_voltage; +} + +static int max77775_get_charging_health(struct max77775_charger_data *charger) +{ + union power_supply_propval value = {0,}, val_iin = {0,}, val_vbyp = {0,}; + int state = POWER_SUPPLY_HEALTH_GOOD; + int vbus_state, retry_cnt; + u8 chg_dtls, reg_data, chg_cnfg_00; + bool wdt_status, abnormal_status = false; + + /* watchdog kick */ + max77775_chg_set_wdtmr_kick(charger); + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_DETAILS_01, ®_data); + reg_data = ((reg_data & MAX77775_BAT_DTLS) >> MAX77775_BAT_DTLS_SHIFT); + + if (charger->pdata->enable_noise_wa) { + psy_do_property("battery", get, POWER_SUPPLY_PROP_CAPACITY, value); + if ((value.intval >= 80) && + (charger->fsw_now != MAX77775_CHG_FSW_3MHz)) + max77775_set_switching_frequency(charger, MAX77775_CHG_FSW_3MHz); + else if ((value.intval < 80) && + (charger->fsw_now != MAX77775_CHG_FSW_1_5MHz)) + max77775_set_switching_frequency(charger, MAX77775_CHG_FSW_1_5MHz); + } + + pr_info("%s: reg_data(0x%x)\n", __func__, reg_data); + switch (reg_data) { + case 0x00: + pr_info("%s: No battery and the charger is suspended\n", __func__); + break; + case 0x01: + pr_info("%s: battery is okay but its voltage is low(~VPQLB)\n", __func__); + break; + case 0x02: + pr_info("%s: battery dead\n", __func__); + break; + case 0x03: + break; + case 0x04: + pr_info("%s: battery is okay but its voltage is low\n", __func__); + break; + case 0x05: + pr_info("%s: battery ovp\n", __func__); + break; + case 0x06: + pr_info("%s: battery ocp\n", __func__); + break; + default: + pr_info("%s: battery unknown\n", __func__); + break; + } + if (charger->is_charging) { + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_DETAILS_00, ®_data); + pr_info("%s: details00(0x%x)\n", __func__, reg_data); + } + + /* get wdt status */ + wdt_status = max77775_chg_get_wdtmr_status(charger); + + psy_do_property("battery", get, POWER_SUPPLY_EXT_PROP_HEALTH, value); + /* VBUS OVP state return battery OVP state */ + vbus_state = max77775_get_vbus_state(charger); + /* read CHG_DTLS and detecting battery terminal error */ + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_DETAILS_01, &chg_dtls); + chg_dtls = ((chg_dtls & MAX77775_CHG_DTLS) >> MAX77775_CHG_DTLS_SHIFT); + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, &chg_cnfg_00); + + /* print the log at the abnormal case */ + if ((charger->is_charging == 1) && !charger->uno_on + && ((chg_dtls == 0x08) || (chg_dtls == 0x0B))) { + max77775_test_read(charger); + max77775_chg_set_mode_state(charger, SEC_BAT_CHG_MODE_CHARGING_OFF); + max77775_set_float_voltage(charger, charger->float_voltage); + max77775_chg_set_mode_state(charger, SEC_BAT_CHG_MODE_CHARGING); + abnormal_status = true; + } + + val_iin.intval = SEC_BATTERY_IIN_MA; + psy_do_property("max77775-fuelgauge", get, + POWER_SUPPLY_EXT_PROP_MEASURE_INPUT, val_iin); + + val_vbyp.intval = SEC_BATTERY_VBYP; + psy_do_property("max77775-fuelgauge", get, + POWER_SUPPLY_EXT_PROP_MEASURE_INPUT, val_vbyp); + + pr_info("%s: vbus_state: 0x%x, chg_dtls: 0x%x, iin: %dmA, vbyp: %dmV, health: %d, abnormal: %s\n", + __func__, vbus_state, chg_dtls, val_iin.intval, + val_vbyp.intval, value.intval, (abnormal_status ? "true" : "false")); + + /* OVP is higher priority */ + if (vbus_state == 0x02) { /* CHGIN_OVLO */ + pr_info("%s: vbus ovp\n", __func__); + if (is_wcin_type(charger->cable_type)) { + retry_cnt = 0; + do { + msleep(50); + vbus_state = max77775_get_vbus_state(charger); + } while ((retry_cnt++ < 2) && (vbus_state == 0x02)); + if (vbus_state == 0x02) { + state = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + pr_info("%s: wpc and over-voltage\n", __func__); + } else { + state = POWER_SUPPLY_HEALTH_GOOD; + } + } else { + state = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } + } else if (((vbus_state == 0x0) || (vbus_state == 0x01)) && (chg_dtls & 0x08) + && (chg_cnfg_00 & MAX77775_MODE_5_BUCK_CHG_ON) + && !is_wcin_type(charger->cable_type)) { + pr_info("%s: vbus is under\n", __func__); + state = POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE; + } else if ((value.intval == POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE) && + ((vbus_state == 0x0) || (vbus_state == 0x01)) && + !is_wcin_type(charger->cable_type)) { + pr_info("%s: keep under-voltage\n", __func__); + state = POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE; + } else if (wdt_status) { + pr_info("%s: wdt expired\n", __func__); + state = POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE; + } else if (is_wireless_type(charger->cable_type)) { + if (abnormal_status || (vbus_state == 0x00) || (vbus_state == 0x01)) + charger->misalign_cnt++; + else + charger->misalign_cnt = 0; + + if (charger->misalign_cnt >= 3) { + pr_info("%s: invalid WCIN, Misalign occurs!\n", __func__); + value.intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + psy_do_property(charger->pdata->wireless_charger_name, + set, POWER_SUPPLY_PROP_STATUS, value); + } + } + + return (int)state; +} + +static int max77775_get_charge_current(struct max77775_charger_data *charger) +{ + struct max77775_dev *max77775 = dev_get_drvdata(charger->dev->parent); + u8 reg_data; + int get_current = 0; + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_02, ®_data); + reg_data &= MAX77775_CHG_CC; + + if (max77775->pmic_rev == MAX77775_PASS1) + get_current = reg_data <= 0x04 ? 267 : (int)((reg_data * 6665) / 100); + else + get_current = (int)((reg_data * 6665) / 100); + + pr_info("%s: reg:(0x%x), charging_current:(%d)\n", + __func__, reg_data, get_current); + + return get_current; +} + +static int max77775_get_input_current_type(struct max77775_charger_data + *charger, int cable_type) +{ + u8 reg_data; + int get_current = 0; + + if (cable_type == SEC_BATTERY_CABLE_WIRELESS) { + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_10, ®_data); + /* AND operation for removing the formal 2bit */ + reg_data &= MAX77775_CHG_WCIN_LIM; + + if (reg_data <= 0x3) + get_current = 100; + else if (reg_data >= 0x3F) + get_current = 1600; + else + get_current = (reg_data + 0x01) * 25; + } else { + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_09, ®_data); + /* AND operation for removing the formal 1bit */ + reg_data &= MAX77775_CHG_CHGIN_LIM; + + if (reg_data <= 0x3) + get_current = 100; + else if (reg_data >= 0x7F) + get_current = 3200; + else + get_current = (reg_data + 0x01) * 25; + } + pr_info("%s: reg:(0x%x), charging_current:(%d)\n", + __func__, reg_data, get_current); + + return get_current; +} + +static int max77775_get_input_current(struct max77775_charger_data *charger) +{ + if (is_wcin_type(charger->cable_type) || is_wireless_fake_type(charger->cable_type)) + return max77775_get_input_current_type(charger, SEC_BATTERY_CABLE_WIRELESS); + else + return max77775_get_input_current_type(charger, SEC_BATTERY_CABLE_TA); +} + +static int reduce_input_current(struct max77775_charger_data *charger) +{ + int input_current = 0, max_value = 0; + + input_current = max77775_get_input_current(charger); + if (input_current <= MINIMUM_INPUT_CURRENT) { + charger->input_current = input_current; + return input_current; + } + + if (is_wcin_type(charger->cable_type) || is_wireless_fake_type(charger->cable_type)) + max_value = 1600; + else + max_value = 3200; + + input_current -= REDUCE_CURRENT_STEP; + input_current = (input_current > max_value) ? max_value : + ((input_current < MINIMUM_INPUT_CURRENT) ? MINIMUM_INPUT_CURRENT : input_current); + + sec_votef("ICL", VOTER_AICL, true, input_current); + charger->input_current = max77775_get_input_current(charger); + + return input_current; +} + +static bool max77775_check_battery(struct max77775_charger_data *charger) +{ + u8 reg_data; + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_INT_OK, ®_data); + pr_info("%s: CHG_INT_OK(0x%x)\n", + __func__, reg_data); + + if (reg_data & MAX77775_BATP_OK) + return true; + else + return false; +} + +static void max77775_check_cnfg12_reg(struct max77775_charger_data *charger) +{ + static bool is_valid = true; + u8 valid_cnfg12, reg_data; + + if (is_valid) { + reg_data = MAX77775_CHG_WCINSEL; + valid_cnfg12 = is_wcin_type(charger->cable_type) ? + reg_data : (reg_data | (1 << CHG_CNFG_12_CHGINSEL_SHIFT)); + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_12, ®_data); + pr_info("%s: valid_data = 0x%2x, reg_data = 0x%2x\n", + __func__, valid_cnfg12, reg_data); + if (valid_cnfg12 != (reg_data & 0x7F)) { + max77775_test_read(charger); + is_valid = false; + } + } +} + +static void max77775_set_charge_path(struct max77775_charger_data *charger, int path, int ctrl_mask) +{ + u8 cnfg12 = (1 << CHG_CNFG_12_CHGINSEL_SHIFT); + u8 path_mask = (CHG_CNFG_12_CHGINSEL_MASK | CHG_CNFG_12_WCINSEL_MASK); + + switch (path) { + case CHGIN_SEL: + cnfg12 = (1 << CHG_CNFG_12_CHGINSEL_SHIFT); + if (ctrl_mask == CHGIN_SEL) + path_mask = CHG_CNFG_12_CHGINSEL_MASK; + break; + case WCIN_SEL: + cnfg12 = (1 << CHG_CNFG_12_WCINSEL_SHIFT); + if (ctrl_mask == WCIN_SEL) + path_mask = CHG_CNFG_12_WCINSEL_MASK; + break; + case WC_CHG_ALL_ON: + cnfg12 = ((1 << CHG_CNFG_12_CHGINSEL_SHIFT) | (1 << CHG_CNFG_12_WCINSEL_SHIFT)); + + break; + case WC_CHG_ALL_OFF: + cnfg12 = 0; + break; + default: + pr_info("%s could not apply charge_path (%d)\n", + __func__, path); + } + + if (max77775_get_facmode()) { + cnfg12 |= (1 << CHG_CNFG_12_CHGINSEL_SHIFT); + path_mask |= CHG_CNFG_12_CHGINSEL_MASK; + } + + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_12, + cnfg12, path_mask); + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_12, &cnfg12); + pr_info("%s : CHG_CNFG_12(0x%02x)\n", __func__, cnfg12); + + max77775_check_cnfg12_reg(charger); +} + +static void max77775_check_charge_path(struct max77775_charger_data *charger) +{ + int chgin_sel = (charger->uno_on || is_nocharge_type(charger->cable_type) ? WC_CHG_ALL_ON : CHGIN_SEL); + int wcin_sel = (charger->otg_on ? WC_CHG_ALL_ON : WCIN_SEL); + + if (is_wcin_type(charger->cable_type) || is_wireless_fake_type(charger->cable_type)) + max77775_set_charge_path(charger, wcin_sel, WC_CHG_ALL_ON); + else + max77775_set_charge_path(charger, chgin_sel, WC_CHG_ALL_ON); +} + +static void max77775_set_ship_mode(struct max77775_charger_data *charger, + int enable) +{ + u8 cnfg07 = ((enable ? 1 : 0) << CHG_CNFG_07_REG_SHIPMODE_SHIFT); + + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_07, + cnfg07, CHG_CNFG_07_REG_SHIPMODE_MASK); + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_07, &cnfg07); + pr_info("%s : CHG_CNFG_07(0x%02x)\n", __func__, cnfg07); +} + +static void max77775_set_ship_exit_db(struct max77775_charger_data *charger, + int exit_time_ms) +{ + u8 reg_data = 0; + + pr_info("%s : exit_time_ms(%d)\n", __func__, exit_time_ms); + + if (exit_time_ms < 500) + pr_info("%s could not apply ship mode exit_time_ms(%d) under 500ms\n", + __func__, exit_time_ms); + else if (exit_time_ms >= 500 && exit_time_ms < 1000) + reg_data = 0x00; + else if (exit_time_ms >= 1000 && exit_time_ms < 2000) + reg_data = 0x01; + else if (exit_time_ms >= 2000 && exit_time_ms < 4000) + reg_data = 0x02; + else + reg_data = 0x03; + + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_13, + reg_data << CHG_CNFG_13_SHIP_EXT_DB_SHIFT, CHG_CNFG_13_SHIP_EXT_DB_MASK); + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_13, ®_data); + pr_info("%s : CHG_CNFG_13(0x%02x)\n", __func__, reg_data); +} + +static void max77775_set_ship_entry_db(struct max77775_charger_data *charger, + int ent_time_s) +{ + u8 reg_data = 0; + + pr_info("%s : ent_time_s(%d)\n", __func__, ent_time_s); + + if (ent_time_s < 0) + pr_info("%s could not apply ship mode ent_time_s(%d) under 0\n", + __func__, ent_time_s); + else if (ent_time_s >= 0 && ent_time_s < 16) + reg_data = 0x00; + else if (ent_time_s >= 16 && ent_time_s < 32) + reg_data = 0x01; + else if (ent_time_s >= 32 && ent_time_s < 192) + reg_data = 0x02; + else + reg_data = 0x03; + + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_13, + reg_data, CHG_CNFG_13_SHIP_ENTRY_DB_MASK); + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_13, ®_data); + pr_info("%s : CHG_CNFG_13(0x%02x)\n", __func__, reg_data); +} + +static int max77775_get_ship_mode(struct max77775_charger_data *charger) +{ + int ret = 0; + u8 cnfg07 = 0; + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_07, &cnfg07); + ret = (cnfg07 & CHG_CNFG_07_REG_SHIPMODE_MASK); + pr_info("%s : CHG_CNFG_07(0x%02x, %d)\n", __func__, cnfg07, ret); + + return ret; +} + +static void max77775_set_auto_ship_mode(struct max77775_charger_data *charger, + int enable) +{ + u8 cnfg13 = ((enable ? 1 : 0) << CHG_CNFG_13_REG_AUTO_SHIPMODE_SHIFT); + + /* auto ship mode should work under 2.6V */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_13, + cnfg13, CHG_CNFG_13_REG_AUTO_SHIPMODE_MASK); + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_13, &cnfg13); + pr_info("%s : CHG_CNFG_13(0x%02x)\n", __func__, cnfg13); +} + +static void max77775_set_bypass_auto_ship_en(struct max77775_charger_data *charger, + int enable) +{ + u8 cnfg13 = ((enable ? 1 : 0) << CHG_CNFG_13_BYPASS_AUTOSHIP_EN_SHIFT); + + /* auto ship mode should work under 2.6V */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_13, + cnfg13, CHG_CNFG_13_BYPASS_AUTOSHIP_EN_MASK); + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_13, &cnfg13); + pr_info("%s : CHG_CNFG_13(0x%02x)\n", __func__, cnfg13); +} + +#if defined(CONFIG_SHIPMODE_BY_VBAT) && !defined(CONFIG_SEC_FACTORY) +static bool max77775_check_current_level(void) +{ + union power_supply_propval val_avg_curr = {0, }, val_now_curr = {0, }; + + val_avg_curr.intval = SEC_BATTERY_CURRENT_MA; + val_now_curr.intval = SEC_BATTERY_CURRENT_MA; + psy_do_property("max77775-fuelgauge", get, POWER_SUPPLY_PROP_CURRENT_AVG, val_avg_curr); + psy_do_property("max77775-fuelgauge", get, POWER_SUPPLY_PROP_CURRENT_NOW, val_now_curr); + pr_info("[DEBUG]%s: current: %d, %d\n", + __func__, val_avg_curr.intval, val_now_curr.intval); + + return ((val_avg_curr.intval > 6000) && (val_now_curr.intval > 6000)) ? true : false; +} + +static u8 max77775_get_auto_shipmode_data(int voltage, int offset) +{ + u8 ret = 0x00; + + if (voltage >= 4000) + ret = 0x03; + else if (voltage >= 3700) + ret = 0x02; + else if (voltage >= 3400) + ret = 0x01; + + if (ret > offset) + ret = (ret) ? + (offset ? (ret - offset) : ret) : 0; + else + ret = 0x00; + + return ret; +} + +static void max77775_check_auto_shipmode_level(struct max77775_charger_data *charger, int offset) +{ + union power_supply_propval value = { 0, }; + int voltage = 2600; + u8 reg_data, read_data = 0; + int ari_cond = charger->spcom ? 91 : 0; + + psy_do_property("max77775-fuelgauge", get, POWER_SUPPLY_PROP_VOLTAGE_NOW, value); + if (value.intval >= 4200) + voltage = 4000; + else if (value.intval >= 3900) + voltage = 3700; + else if (value.intval >= 3700) + voltage = 3400; + + /* Not delivered ari cnt or under 90, set 2.6v auto ship mode */ + /* no dts, but if ari cnt write, + it is judged to be abnormal and set 2.6v auto ship mode */ + if (charger->ari_cnt < ari_cond) + voltage = 2600; + + reg_data = max77775_get_auto_shipmode_data(voltage, offset); + max77775_set_shipmode_op(true, reg_data); + + pr_info("[DEBUG]%s: check shipmode %d, %d, 0x%x, 0x%x\n", + __func__, value.intval, voltage, reg_data, read_data); +} +#endif + +static void max77775_set_input_current(struct max77775_charger_data *charger, + int input_current) +{ + int curr_step = 25; + u8 set_reg, set_mask, reg_data = 0; + + if (max77775_get_facmode() || charger->bypass_mode) { + pr_info("%s: %s Skip set input current\n", __func__, + (charger->bypass_mode ? "bypass mode" : "Factory mode")); + return; + } + + charger->dpm_last_icl = input_current; + if (!charger->bat_det && input_current < charger->pdata->dpm_icl) { + input_current = charger->pdata->dpm_icl; + pr_info("%s : DPM, icl setting value change (%dmA)\n", __func__, input_current); + } + + mutex_lock(&charger->charger_mutex); + + if (is_wcin_type(charger->cable_type) || is_wireless_fake_type(charger->cable_type)) { + set_reg = MAX77775_CHG_REG_CNFG_10; + set_mask = MAX77775_CHG_WCIN_LIM; + input_current = (input_current > 1600) ? 1600 : input_current; + } else { + set_reg = MAX77775_CHG_REG_CNFG_09; + set_mask = MAX77775_CHG_CHGIN_LIM; + input_current = (input_current > 3200) ? 3200 : input_current; + } + + if (input_current >= 100) + reg_data = (input_current / curr_step) - 0x01; + + max77775_update_reg(charger->i2c, set_reg, reg_data, set_mask); + pr_info("[%s] REG(0x%02x) DATA(0x%02x), CURRENT(%d)\n", + __func__, set_reg, reg_data, input_current); + + if (is_nocharge_type(charger->cable_type)) { + set_reg = MAX77775_CHG_REG_CNFG_10; + set_mask = MAX77775_CHG_WCIN_LIM; + input_current = (input_current > 1600) ? 1600 : input_current; + if (input_current >= 100) + reg_data = (input_current / curr_step) - 0x01; + max77775_update_reg(charger->i2c, set_reg, reg_data, set_mask); + pr_info("[%s] REG(0x%02x) DATA(0x%02x), CURRENT(%d)\n", + __func__, set_reg, reg_data, input_current); + } + + if (!max77775_get_autoibus(charger)) + max77775_set_fw_noautoibus(MAX77775_AUTOIBUS_AT_OFF); + + mutex_unlock(&charger->charger_mutex); +} + +static void max77775_set_charge_current(struct max77775_charger_data *charger, + int fast_charging_current) +{ + struct max77775_dev *max77775 = dev_get_drvdata(charger->dev->parent); + u8 reg_data = 0; + + if (max77775_get_facmode()) { + pr_info("%s: Factory Mode Skip set charge current\n", __func__); + return; + } + + fast_charging_current = + (fast_charging_current > 4199) ? 4199 : fast_charging_current; + + if (max77775->pmic_rev == MAX77775_PASS1) { + if (fast_charging_current > 267) + reg_data = (u8)((fast_charging_current * 100) / 6665); + else + reg_data = 1; + } else { + reg_data = (u8)((fast_charging_current * 100) / 6665); + } + + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_02, + reg_data, MAX77775_CHG_CC); + + pr_info("[%s] REG(0x%02x) DATA(0x%02x), CURRENT(%d)\n", __func__, + MAX77775_CHG_REG_CNFG_02, reg_data, fast_charging_current); +} + +static void max77775_set_wireless_input_current( + struct max77775_charger_data *charger, int input_current) +{ + union power_supply_propval value = {0, }; + unsigned int work_state; + + cancel_delayed_work(&charger->wc_chg_current_work); + __pm_relax(charger->wc_chg_current_ws); + + if (is_wireless_type(charger->cable_type)) { + work_state = work_busy(&charger->wc_current_work.work); + pr_info("%s: check wc_current_work state(0x%x)\n", __func__, work_state); + if (!(work_state & (WORK_BUSY_PENDING | WORK_BUSY_RUNNING))) { + /* Wcurr-A) In cases of wireless input current change, + * configure the Vrect adj room to 270mV for safe wireless charging. + */ + if (is_hv_wireless_type(charger->cable_type)) + value.intval = WIRELESS_VRECT_ADJ_ROOM_1; /* Vrect Room 277mV */ + else + value.intval = charger->pdata->nv_wc_headroom; + psy_do_property(charger->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_RX_CONTROL, value); + msleep(500); /* delay 0.5sec */ + } + charger->wc_pre_current = max77775_get_input_current(charger); + charger->wc_current = input_current; + pr_info("%s: wc_current(%d), wc_pre_current(%d)\n", + __func__, charger->wc_current, charger->wc_pre_current); + if (charger->wc_current > charger->wc_pre_current) + max77775_set_charge_current(charger, charger->charging_current); + } + mutex_lock(&charger->icl_mutex); + __pm_stay_awake(charger->wc_current_ws); + queue_delayed_work(charger->wqueue, &charger->wc_current_work, 0); + mutex_unlock(&charger->icl_mutex); +} + +static void max77775_set_topoff_current(struct max77775_charger_data *charger, + int termination_current) +{ + struct max77775_dev *max77775 = dev_get_drvdata(charger->dev->parent); + int curr_base = 200, curr_step = 67, curr_max = 1200; + u8 reg_data; + + if (max77775->pmic_rev == MAX77775_PASS1) { + curr_base = 200; + curr_step = 67; + curr_max = 1200; + } else { + curr_base = 133; + curr_step = 67; + curr_max = 1133; + } + + if (termination_current < curr_base) + termination_current = curr_base; + else if (termination_current > curr_max) + termination_current = curr_max; + + reg_data = (u8)((termination_current - curr_base) / curr_step); + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_03, + reg_data, CHG_CNFG_03_TO_ITH_MASK); + + pr_info("%s: reg_data(0x%02x), topoff(%dmA)\n", + __func__, reg_data, termination_current); +} + +static void max77775_set_topoff_time(struct max77775_charger_data *charger, + int topoff_time) +{ + u8 reg_data = 0; + switch (topoff_time) { + case 30: + reg_data = MAX77775_TO_TIME_30M << CHG_CNFG_03_TO_TIME_SHIFT; + break; + case 40: + reg_data = MAX77775_TO_TIME_40M << CHG_CNFG_03_TO_TIME_SHIFT; + break; + case 50: + reg_data = MAX77775_TO_TIME_50M << CHG_CNFG_03_TO_TIME_SHIFT; + break; + case 70: + reg_data = MAX77775_TO_TIME_70M << CHG_CNFG_03_TO_TIME_SHIFT; + break; + default: + pr_info("%s could not apply topoff_time (%d)\n", + __func__, topoff_time); + return; + } + + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_03, + reg_data, CHG_CNFG_03_TO_TIME_MASK); + + pr_info("%s: reg_data(0x%02x), topoff_time(%dmin)\n", + __func__, reg_data, topoff_time); +} + +static void max77775_set_bypass_mode(struct max77775_charger_data *charger, + int enable) +{ + u8 cnfg_08; + + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, + MAX77775_MODE_4_BUCK_ON, CHG_CNFG_00_MODE_MASK); + max77775_set_input_current(charger, 3200); + + /* enable bypass mode write en */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_17, + (0x01 << CHG_CNFG_17_BYPASS_MODE_WR_EN_SHIFT), + CHG_CNFG_17_BYPASS_MODE_WR_EN_MASK); + + /* Set bypass mode */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_08, + (enable << CHG_CNFG_08_BYPASS_MODE_SHIFT), + CHG_CNFG_08_BYPASS_MODE_MASK); + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_08, &cnfg_08); + + pr_info("%s : set bypass mode - CHG_CNFG_08(0x%02x)\n", __func__, cnfg_08); + + /* disable bypass mode write en */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_17, + (0x00 << CHG_CNFG_17_BYPASS_MODE_WR_EN_SHIFT), + CHG_CNFG_17_BYPASS_MODE_WR_EN_MASK); + + charger->bypass_mode = enable ? true : false; +} + +static void max77775_set_switching_frequency(struct max77775_charger_data *charger, + int frequency) +{ + u8 cnfg_08; + + /* Set Switching Frequency */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_08, + (frequency << CHG_CNFG_08_REG_FSW_SHIFT), + CHG_CNFG_08_REG_FSW_MASK); + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_08, &cnfg_08); + + charger->fsw_now = frequency; + pr_info("%s : CHG_CNFG_08(0x%02x)\n", __func__, cnfg_08); +} + +static void max77775_set_skipmode(struct max77775_charger_data *charger, + int enable) +{ + u8 reg_data = enable ? MAX77775_AUTO_SKIP : MAX77775_DISABLE_SKIP; + + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_12, + reg_data << CHG_CNFG_12_REG_DISKIP_SHIFT, + CHG_CNFG_12_REG_DISKIP_MASK); +} + +static void max77775_set_b2sovrc(struct max77775_charger_data *charger, + u32 ocp_current, u32 ocp_dtc) +{ + u8 reg_data = 1; // 4400 mA + + if (ocp_current == 0) + reg_data = MAX77775_B2SOVRC_DISABLE; + else + reg_data += (ocp_current - 4400) / 400; + + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_05, + (reg_data << CHG_CNFG_05_REG_B2SOVRC_SHIFT), + CHG_CNFG_05_REG_B2SOVRC_MASK); + + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_06, + ((ocp_dtc == 100 ? MAX77775_B2SOVRC_DTC_100MS : 0) + << CHG_CNFG_06_B2SOVRC_DTC_SHIFT), CHG_CNFG_06_B2SOVRC_DTC_MASK); + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_05, ®_data); + pr_info("%s : CHG_CNFG_05(0x%02x)\n", __func__, reg_data); + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_06, ®_data); + pr_info("%s : CHG_CNFG_06(0x%02x)\n", __func__, reg_data); + + return; +} + +static int max77775_set_spsn_det_res(struct max77775_charger_data *charger, int resistor) +{ + u8 reg_data = MAX77775_CHG_SPSN_OPEN; + int ret = 0; + + switch (resistor) { + case 1: + reg_data = MAX77775_CHG_SPSN_1K; + break; + case 10: + reg_data = MAX77775_CHG_SPSN_10K; + break; + case 100: + reg_data = MAX77775_CHG_SPSN_100K; + break; + } + + ret = max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_08, + (reg_data << CHG_CNFG_08_REG_SPSN_DET_SHIFT), + CHG_CNFG_08_REG_SPSN_DET_MASK); + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_08, ®_data); + pr_info("%s: ret = %d, reg_data = 0x%x\n", __func__, ret, reg_data); + return ret; +} + +static int max77775_check_wcin_before_otg_on(struct max77775_charger_data *charger) +{ + union power_supply_propval value = {0,}; + struct power_supply *psy; + u8 reg_data; + + psy = get_power_supply_by_name("wireless"); + if (!psy) + return -ENODEV; + if ((psy->desc->get_property != NULL) && + (psy->desc->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &value) >= 0)) { + if (value.intval) + return 0; + } else { + return -ENODEV; + } + power_supply_put(psy); + +#if defined(CONFIG_WIRELESS_TX_MODE) + psy_do_property("battery", get, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ENABLE, value); + /* check TX status */ + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, ®_data); + reg_data &= CHG_CNFG_00_MODE_MASK; + if (value.intval && + (reg_data == MAX77775_MODE_8_BOOST_UNO_ON || + reg_data == MAX77775_MODE_C_BUCK_BOOST_UNO_ON || + reg_data == MAX77775_MODE_D_BUCK_CHG_BOOST_UNO_ON)) { + value.intval = BATT_TX_EVENT_WIRELESS_TX_OTG_ON; + psy_do_property("wireless", set, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ERR, value); + return 0; + } +#endif + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_DETAILS_00, ®_data); + reg_data = ((reg_data & MAX77775_WCIN_DTLS) >> MAX77775_WCIN_DTLS_SHIFT); + if ((reg_data != 0x03) || (charger->pdata->wireless_charger_name == NULL)) + return 0; + + psy_do_property(charger->pdata->wireless_charger_name, get, + POWER_SUPPLY_PROP_ENERGY_NOW, value); + if (value.intval < 0) + return -ENODEV; + + return 0; +} + +static void max77775_set_otg(struct max77775_charger_data *charger, int enable) +{ + union power_supply_propval value = {0, }; + u8 chg_int_state, otg_lim; + int ret = 0; + + pr_info("%s: CHGIN-OTG %s\n", __func__, enable > 0 ? "on" : "off"); + if (charger->otg_on == enable || max77775_get_lpmode()) + return; + + if (charger->pdata->wireless_charger_name) { + ret = max77775_check_wcin_before_otg_on(charger); + pr_info("%s: wc_state = %d\n", __func__, ret); + if (ret < 0) + return; + } + + __pm_stay_awake(charger->otg_ws); + /* CHGIN-OTG */ + value.intval = enable; + mutex_lock(&charger->charger_mutex); + charger->otg_on = enable; + mutex_unlock(&charger->charger_mutex); + + if (!enable) + charger->hp_otg = false; + + /* otg current limit 900mA or 1500mA */ + if (charger->hp_otg) + otg_lim = MAX77775_OTG_ILIM_1500; + else + otg_lim = MAX77775_OTG_ILIM_900; + + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_02, + otg_lim << CHG_CNFG_02_OTG_ILIM_SHIFT, + CHG_CNFG_02_OTG_ILIM_MASK); + + if (enable) { + psy_do_property("wireless", set, + POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL, value); + + mutex_lock(&charger->charger_mutex); + /* OTG on, boost on */ + max77775_chg_set_mode_state(charger, SEC_BAT_CHG_MODE_OTG_ON); + mutex_unlock(&charger->charger_mutex); + } else { + mutex_lock(&charger->charger_mutex); + /* OTG off(UNO on), boost off */ + max77775_chg_set_mode_state(charger, SEC_BAT_CHG_MODE_OTG_OFF); + mutex_unlock(&charger->charger_mutex); + msleep(50); + + psy_do_property("wireless", set, + POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL, value); + } + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_INT_MASK, &chg_int_state); + pr_info("%s: INT_MASK(0x%x)\n", __func__, chg_int_state); + + power_supply_changed(charger->psy_otg); + __pm_relax(charger->otg_ws); +} + +static void max77775_check_slow_charging(struct max77775_charger_data *charger, + int input_current) +{ + union power_supply_propval value = {0, }; + + /* under 400mA considered as slow charging concept for VZW */ + if (input_current <= SLOW_CHARGING_CURRENT_STANDARD && + !is_nocharge_type(charger->cable_type)) { + charger->slow_charging = true; + pr_info("%s: slow charging on : input current(%dmA), cable type(%d)\n", + __func__, input_current, charger->cable_type); + + psy_do_property("battery", set, POWER_SUPPLY_PROP_CHARGE_TYPE, value); + } else { + charger->slow_charging = false; + } +} + +static void max77775_set_factory_mode(struct max77775_charger_data *charger) +{ + /* fmbst 0 */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_07, + 0, CHG_CNFG_07_REG_FMBST_MASK); + + max77775_set_fw_noautoibus(MAX77775_AUTOIBUS_FW_AT_OFF); + + /* FG src change to vsys */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_07, + (0x01 << CHG_CNFG_07_REG_FGSRC_SHIFT), + CHG_CNFG_07_REG_FGSRC_MASK); + + /* buck on, charge off */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, + MAX77775_MODE_4_BUCK_ON, CHG_CNFG_00_MODE_MASK); + + /* float voltage 4.4v */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_04, + (0x1E << CHG_CNFG_04_CHG_CV_PRM_SHIFT), + CHG_CNFG_04_CHG_CV_PRM_MASK); + + /* chginlim 3200mA */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_09, + 0x7F, MAX77775_CHG_CHGIN_LIM); + + /* chgcc 267mA */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_02, + 0x01, MAX77775_CHG_CC); + + /* disable AICL */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_06, + MAX77775_DIS_AICL << CHG_CNFG_06_DIS_AICL_SHIFT, + CHG_CNFG_06_DIS_AICL_MASK); + + /* vchgin uvlo 4.7 */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_12, + 0x00, CHG_CNFG_12_VCHGIN_REG_MASK); + + /* SPSN_DET set to 1 */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, + MAX77775_SPSN_DET_ENABLE << CHG_CNFG_00_SPSN_DET_EN_SHIFT, + CHG_CNFG_00_SPSN_DET_EN_MASK); + + factory_mode = 1; + pr_info("%s : enter power on factory mode(523k)\n", __func__); +} + +static void max77775_charger_initialize(struct max77775_charger_data *charger) +{ + u8 reg_data; + int jig_gpio; + + pr_info("%s\n", __func__); + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, ®_data); + charger->cnfg00_mode = (reg_data & CHG_CNFG_00_MODE_MASK); + + /* unlock charger setting protect slowest LX slope + */ + reg_data = (0x03 << 2); // unlock + reg_data |= 0x60; // slowest LX slop + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_06, reg_data, reg_data); + + if (!max77775_get_facmode()) { + /* If DIS_AICL is enabled(CNFG06[4]: 1) from factory_mode, + * clear to 0 to disable DIS_AICL + */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_06, + MAX77775_DIS_AICL << CHG_CNFG_06_DIS_AICL_SHIFT, + CHG_CNFG_06_DIS_AICL_MASK); + } + + /* fast charge timer disable + * restart threshold disable + * pre-qual charge disable + */ + reg_data = (MAX77775_FCHGTIME_DISABLE << CHG_CNFG_01_FCHGTIME_SHIFT) | + (MAX77775_CHG_RSTRT_DISABLE << CHG_CNFG_01_CHG_RSTRT_SHIFT) | + (MAX77775_CHG_VTRICKLE_EN_DISABLE << CHG_CNFG_01_VTRICKLE_EN_SHIFT); + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_01, reg_data, + (CHG_CNFG_01_FCHGTIME_MASK | CHG_CNFG_01_CHG_RSTRT_MASK | CHG_CNFG_01_VTRICKLE_EN_MASK)); + + /* enalbe RECYCLE_EN for ocp */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_01, + MAX77775_RECYCLE_EN_ENABLE << CHG_CNFG_01_RECYCLE_EN_SHIFT, + CHG_CNFG_01_RECYCLE_EN_MASK); + + /* otg current limit 900mA */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_02, + MAX77775_OTG_ILIM_900 << CHG_CNFG_02_OTG_ILIM_SHIFT, + CHG_CNFG_02_OTG_ILIM_MASK); + + /* UNO ILIM 1.0A */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_05, + MAX77775_UNOILIM_1000 << CHG_CNFG_05_REG_UNOILIM_SHIFT, + CHG_CNFG_05_REG_UNOILIM_MASK); + + /* BAT to SYS OCP */ + max77775_set_b2sovrc(charger, + charger->pdata->chg_ocp_current, charger->pdata->chg_ocp_dtc); + + /* top off current 150mA */ + reg_data = (MAX77775_TO_ITH_150MA << CHG_CNFG_03_TO_ITH_SHIFT) | + (MAX77775_SYS_TRACK_DISABLE << CHG_CNFG_03_SYS_TRACK_DIS_SHIFT); + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_03, reg_data, + (CHG_CNFG_03_TO_ITH_MASK | CHG_CNFG_03_SYS_TRACK_DIS_MASK)); + + /* topoff_time */ + max77775_set_topoff_time(charger, charger->pdata->topoff_time); + + /* cv voltage 4.2V or 4.35V */ + max77775_set_float_voltage(charger, charger->pdata->chg_float_voltage); + + if (!max77775_get_facmode()) { + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_INT_OK, ®_data); + /* VCHGIN : REG=4.6V, UVLO=4.8V + * to fix CHGIN-UVLO issues including cheapy battery packs + */ + if (reg_data & MAX77775_CHGIN_OK) { + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_12, + 0x08, CHG_CNFG_12_VCHGIN_REG_MASK); + } else if (reg_data & MAX77775_WCIN_OK) { + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_12, + 0x00, CHG_CNFG_12_VCHGIN_REG_MASK); + } else { + /* VCHGIN : REG=4.5V, UVLO=4.7V */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_12, + 0x00, CHG_CNFG_12_VCHGIN_REG_MASK); + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, + 0x00, CHG_CNFG_00_CHG_MASK); + charger->cnfg00_mode &= ~CHG_CNFG_00_CHG_MASK; + } + } + + if (charger->jig_low_active) + jig_gpio = !gpio_get_value(charger->jig_gpio); + else + jig_gpio = gpio_get_value(charger->jig_gpio); + + pr_info("%s: jig_gpio = %d\n", __func__, jig_gpio); + + if (charger->pdata->enable_dpm && charger->pdata->disqbat) { + if (max77775_check_battery(charger)) { + /* disqbat set to LOW (Qbat ON) */ + charger->bat_det = true; + gpio_direction_output(charger->pdata->disqbat, 0); + } else { + /* disqbat set to HIGH (Qbat OFF) */ + charger->bat_det = false; + gpio_direction_output(charger->pdata->disqbat, 1); + } + pr_info("%s: battery detection = %d\n", __func__, charger->bat_det); + } + + if (max77775_get_facmode()) { + /* fgsrc should depend on factory_mode since 301k and 619k do not triger jig_gpio */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_07, + (1 << CHG_CNFG_07_REG_FGSRC_SHIFT), + CHG_CNFG_07_REG_FGSRC_MASK); + /* Watchdog Disable */ + max77775_chg_set_wdtmr_en(charger, 0); + } else { + /* fgsrc should depend on jig_gpio */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_07, + (jig_gpio << CHG_CNFG_07_REG_FGSRC_SHIFT), + CHG_CNFG_07_REG_FGSRC_MASK); + /* Watchdog Enable */ + max77775_chg_set_wdtmr_en(charger, 1); + } + + /* THRM_DIS = 0 */ + max77775_update_reg(charger->pmic_i2c, MAX77775_PMIC_REG_MAINCTRL1, 0x00, 0x01); + + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_09, + MAX77775_CHG_EN, MAX77775_CHG_EN); + + /* VBYPSET=5.0V */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_11, + 0x00, CHG_CNFG_11_VBYPSET_MASK); + + /* Switching Frequency */ + max77775_set_switching_frequency(charger, charger->pdata->fsw); + + /* Auto skip mode */ + max77775_set_skipmode(charger, 1); + + /* disable shipmode */ + max77775_set_ship_mode(charger, 0); + max77775_set_ship_exit_db(charger, 4000); + + /* disable auto shipmode, this should work under 2.6V */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_13, + 0x00 << CHG_CNFG_13_AUTOSHIP_TH_SHIFT, + CHG_CNFG_13_AUTOSHIP_TH_MASK); + max77775_set_auto_ship_mode(charger, 0); + max77775_set_ship_entry_db(charger, 192); + + max77775_test_read(charger); +} + +static void max77775_set_sysovlo(struct max77775_charger_data *charger, int enable) +{ + u8 reg_data; + + max77775_read_reg(charger->pmic_i2c, + MAX77775_PMIC_REG_SYSTEM_INT_MASK, ®_data); + + reg_data = enable ? reg_data & 0xDF : reg_data | 0x20; + max77775_write_reg(charger->pmic_i2c, + MAX77775_PMIC_REG_SYSTEM_INT_MASK, reg_data); + + max77775_read_reg(charger->pmic_i2c, + MAX77775_PMIC_REG_SYSTEM_INT_MASK, ®_data); + pr_info("%s: check topsys irq mask(0x%x), enable(%d)\n", + __func__, reg_data, enable); +} + +static void max77775_check_mode_status(struct max77775_charger_data *charger) +{ + union power_supply_propval val_status; + + psy_do_property("battery", get, POWER_SUPPLY_PROP_STATUS, val_status); + + if ((val_status.intval == POWER_SUPPLY_STATUS_FULL) && + !is_wireless_all_type(charger->cable_type)) { + if (!charger->cv_mode_check) { + charger->cv_mode_check = true; + max77775_chg_set_mode_state(charger, charger->charge_mode); + } + } else if (charger->cv_mode_check) { + charger->cv_mode_check = false; + max77775_chg_set_mode_state(charger, charger->charge_mode); + } + pr_info("%s: cv_mode_check(%d)\n", __func__, + charger->cv_mode_check ? 1 : 0); +} + +static void max77775_chg_monitor_work(struct max77775_charger_data *charger) +{ + u8 reg_b2sovrc = 0, reg_mode = 0; + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, ®_mode); + reg_mode = (reg_mode & CHG_CNFG_00_MODE_MASK) >> CHG_CNFG_00_MODE_SHIFT; + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_05, ®_b2sovrc); + reg_b2sovrc = + (reg_b2sovrc & CHG_CNFG_05_REG_B2SOVRC_MASK) >> CHG_CNFG_05_REG_B2SOVRC_SHIFT; + + pr_info("%s: [CHG] MODE(0x%x), B2SOVRC(0x%x), otg_on(%d)\n", + __func__, reg_mode, reg_b2sovrc, charger->otg_on); + + /* protection code for sync with battery and charger driver */ + if (charger->pdata->enable_dpm && !charger->bat_det) { + union power_supply_propval value = {0, }; + + psy_do_property("battery", get, POWER_SUPPLY_EXT_PROP_MISC_EVENT, value); + if (!(value.intval & DPM_MISC)) { + /* disable thermal control */ + value.intval = 1; + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_THERMAL_ZONE, value); + + /* set DPM misc event for PMS UI control */ + value.intval = DPM_MISC; + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_MISC_EVENT, value); + } + } + max77775_check_mode_status(charger); +} + +static int max77775_chg_create_attrs(struct device *dev) +{ + int i, rc; + + for (i = 0; i < (int)ARRAY_SIZE(max77775_charger_attrs); i++) { + rc = device_create_file(dev, &max77775_charger_attrs[i]); + if (rc) + goto create_attrs_failed; + } + return rc; + +create_attrs_failed: + dev_err(dev, "%s: failed (%d)\n", __func__, rc); + while (i--) + device_remove_file(dev, &max77775_charger_attrs[i]); + return rc; +} + +ssize_t max77775_chg_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct max77775_charger_data *charger = power_supply_get_drvdata(psy); + const ptrdiff_t offset = attr - max77775_charger_attrs; + int i = 0; + u8 addr, data; + + switch (offset) { + case CHIP_ID: + max77775_read_reg(charger->pmic_i2c, MAX77775_PMIC_REG_PMICID, &data); + i += scnprintf(buf + i, PAGE_SIZE - i, "%x\n", data); + break; + case DATA: + for (addr = 0xB1; addr <= 0xC8; addr++) { + max77775_read_reg(charger->i2c, addr, &data); + i += scnprintf(buf + i, PAGE_SIZE - i, + "0x%02x : 0x%02x\n", addr, data); + } + break; + default: + return -EINVAL; + } + return i; +} + +ssize_t max77775_chg_store_attrs(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct max77775_charger_data *charger = power_supply_get_drvdata(psy); + const ptrdiff_t offset = attr - max77775_charger_attrs; + int ret = 0; + int x, y; + + switch (offset) { + case CHIP_ID: + ret = count; + break; + case DATA: + if (sscanf(buf, "0x%8x 0x%8x", &x, &y) == 2) { + if (x >= 0xB1 && x <= 0xC8) { + u8 addr = x, data = y; + + if (max77775_write_reg(charger->i2c, addr, data) < 0) + dev_info(charger->dev, "%s: addr: 0x%x write fail\n", + __func__, addr); + } else { + dev_info(charger->dev, "%s: addr: 0x%x is wrong\n", + __func__, x); + } + } + ret = count; + break; + default: + ret = -EINVAL; + } + return ret; +} + +static void max77775_set_uno(struct max77775_charger_data *charger, int en) +{ + u8 chg_int_state; + u8 reg; + + if (charger->otg_on) { + if (en) { +#if defined(CONFIG_WIRELESS_TX_MODE) + union power_supply_propval value = {0, }; + psy_do_property("battery", get, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ENABLE, value); + if (value.intval) { + pr_info("%s: OTG ON, then skip UNO Control\n", __func__); + value.intval = BATT_TX_EVENT_WIRELESS_TX_ETC; + psy_do_property("wireless", set, POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ERR, value); + return; + } +#endif + } + } + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_INT_OK, ®); + if (en && (reg & MAX77775_WCIN_OK)) { + pr_info("%s: WCIN is already valid by wireless charging, then skip UNO Control\n", + __func__); + return; + } + + if (en == SEC_BAT_CHG_MODE_UNO_ONLY) { + charger->uno_on = true; + max77775_set_charge_path(charger, WCIN_SEL, WCIN_SEL); + charger->cnfg00_mode = MAX77775_MODE_8_BOOST_UNO_ON; + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, + charger->cnfg00_mode, CHG_CNFG_00_MODE_MASK); + } else if (en) { + charger->uno_on = true; + /* UNO on, boost on */ + max77775_chg_set_mode_state(charger, SEC_BAT_CHG_MODE_UNO_ON); + } else { + charger->uno_on = false; + /* boost off */ + max77775_chg_set_mode_state(charger, SEC_BAT_CHG_MODE_UNO_OFF); + msleep(50); + } + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_INT_MASK, &chg_int_state); + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, ®); + pr_info("%s: UNO(%d), INT_MASK(0x%x), CHG_CNFG_00(0x%x)\n", + __func__, charger->uno_on, chg_int_state, reg); +} + +static void max77775_set_uno_iout(struct max77775_charger_data *charger, int iout) +{ + u8 reg = 0; + + if (iout < 800) + reg = MAX77775_UNOILIM_600; + else if (iout >= 800 && iout < 1000) + reg = MAX77775_UNOILIM_800; + else if (iout >= 1000 && iout < 1200) + reg = MAX77775_UNOILIM_1000; + else if (iout >= 1200 && iout < 1400) + reg = MAX77775_UNOILIM_1200; + else if (iout >= 1400 && iout < 1600) + reg = MAX77775_UNOILIM_1400; + else if (iout >= 1600 && iout < 2000) + reg = MAX77775_UNOILIM_1600; + else if (iout >= 2000) + reg = MAX77775_UNOILIM_2000; + + if (reg) + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_05, + reg << CHG_CNFG_05_REG_UNOILIM_SHIFT, + CHG_CNFG_05_REG_UNOILIM_MASK); + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_05, ®); + pr_info("@Tx_mode %s: CNFG_05 (0x%x)\n", __func__, reg); +} + +static void max77775_set_uno_vout(struct max77775_charger_data *charger, int vout) +{ + u8 reg = 0; + + if (vout == WC_TX_VOUT_OFF) { + pr_info("%s: set UNO default\n", __func__); + } else { + if (vout < WC_TX_VOUT_MIN) { + pr_err("%s: vout(%d) is lower than min\n", __func__, vout); + vout = WC_TX_VOUT_MIN; + } else if (vout > WC_TX_VOUT_MAX) { + pr_err("%s: vout(%d) is higher than max\n", __func__, vout); + vout = WC_TX_VOUT_MAX; + } + /* Set TX Vout(VBYPSET) */ + if (vout <= 5000) + reg = 0; + else + reg = (vout - 5000) / 50; + pr_info("%s: UNO VOUT (0x%x)\n", __func__, reg); + } + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_11, + reg, CHG_CNFG_11_VBYPSET_MASK); + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_11, ®); + pr_info("@Tx_mode %s: CNFG_11(0x%x)\n", __func__, reg); +} + +static int max77775_chg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max77775_charger_data *charger = power_supply_get_drvdata(psy); + enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; + u8 reg_data; + + if (atomic_read(&charger->shutdown_cnt) > 0) { + dev_info(charger->dev, "%s: charger already shutdown\n", __func__); + return -EINVAL; + } + + switch ((int)psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = SEC_BATTERY_CABLE_NONE; + if (!max77775_read_reg(charger->i2c, + MAX77775_CHG_REG_INT_OK, ®_data)) { + if (reg_data & MAX77775_WCIN_OK) { +#if defined(CONFIG_USE_POGO) + val->intval = SEC_BATTERY_CABLE_POGO; +#else + val->intval = SEC_BATTERY_CABLE_WIRELESS; +#endif + } else if (reg_data & MAX77775_CHGIN_OK) { + val->intval = SEC_BATTERY_CABLE_TA; + } + } + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = max77775_check_battery(charger); + break; + case POWER_SUPPLY_PROP_STATUS: + val->intval = max77775_get_charger_state(charger); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + if (!charger->is_charging) { + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + } else if (charger->slow_charging) { + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + pr_info("%s: slow-charging mode\n", __func__); + } else { + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + } + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = max77775_get_charging_health(charger); + max77775_check_cnfg12_reg(charger); + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + val->intval = charger->input_current; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + val->intval = charger->charging_current; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + val->intval = max77775_get_float_voltage(charger); + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + max77775_read_reg(charger->i2c, + MAX77775_CHG_REG_DETAILS_01, ®_data); + reg_data &= 0x0F; + switch (reg_data) { + case 0x01: + val->strval = "CC Mode"; + break; + case 0x02: + val->strval = "CV Mode"; + break; + case 0x03: + val->strval = "EOC"; + break; + case 0x04: + val->strval = "DONE"; + break; + default: + val->strval = "NONE"; + break; + } + break; + case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: + switch (ext_psp) { + case POWER_SUPPLY_EXT_PROP_WDT_STATUS: + if (max77775_chg_get_wdtmr_status(charger)) { + dev_info(charger->dev, "charger WDT is expired!!\n"); + max77775_test_read(charger); + } + break; + case POWER_SUPPLY_EXT_PROP_CHIP_ID: + if (!max77775_read_reg(charger->i2c, + MAX77775_PMIC_REG_PMICREV, ®_data)) { + /* pmic_ver should below 0x7 */ + val->intval = + (charger->pmic_ver >= 0x1 && charger->pmic_ver <= 0x7); + pr_info("%s : IF PMIC ver.0x%x\n", __func__, + charger->pmic_ver); + } else { + val->intval = 0; + pr_info("%s : IF PMIC I2C fail.\n", __func__); + } + break; + case POWER_SUPPLY_EXT_PROP_MONITOR_WORK: + max77775_test_read(charger); + max77775_chg_monitor_work(charger); + break; + case POWER_SUPPLY_EXT_PROP_CHARGE_BOOST: + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, ®_data); + reg_data &= CHG_CNFG_00_MODE_MASK; + val->intval = (reg_data & MAX77775_MODE_BOOST) ? 1 : 0; + break; + case POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL: + mutex_lock(&charger->charger_mutex); + val->intval = charger->otg_on; + mutex_unlock(&charger->charger_mutex); + break; + case POWER_SUPPLY_EXT_PROP_CHARGE_COUNTER_SHADOW: + break; + case POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED: + val->intval = charger->charge_mode; + break; + case POWER_SUPPLY_EXT_PROP_SHIPMODE_TEST: +#if defined(CONFIG_SUPPORT_SHIP_MODE) + val->intval = max77775_get_ship_mode(charger); + pr_info("%s: ship mode is %d\n", __func__, val->intval); +#else + val->intval = 0; + pr_info("%s: ship mode is not supported\n", __func__); +#endif + break; + case POWER_SUPPLY_EXT_PROP_INPUT_CURRENT_LIMIT_WRL: + val->intval = max77775_get_input_current_type(charger, SEC_BATTERY_CABLE_WIRELESS); + break; + case POWER_SUPPLY_EXT_PROP_CHARGER_IC_NAME: + val->strval = "MAX77775"; + break; + case POWER_SUPPLY_EXT_PROP_SPSN_TEST: + /* MODE set to 0 */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, + 0, CHG_CNFG_00_MODE_MASK); + + /* SPSN_DET set to 1 */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, + MAX77775_SPSN_DET_ENABLE << CHG_CNFG_00_SPSN_DET_EN_SHIFT, + CHG_CNFG_00_SPSN_DET_EN_MASK); + + /* delay for DTLS_00 update */ + msleep(50); + + /* read SPSN_DTLS */ + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_DETAILS_00, ®_data); + reg_data = (reg_data & MAX77775_SPSN_DTLS) >> MAX77775_SPSN_DTLS_SHIFT; + + /* return test result */ + val->intval = reg_data; + + /* read CNFG_00 for check test condition */ + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, ®_data); + pr_info("%s: SPSN_DTLS (0x%x), CNFG_00 (0x%x)\n", __func__, val->intval, reg_data); + + /* restore SPSN_DET */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, + MAX77775_SPSN_DET_DISABLE << CHG_CNFG_00_SPSN_DET_EN_SHIFT, + CHG_CNFG_00_SPSN_DET_EN_MASK); + + /* restore MODE */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, + charger->cnfg00_mode, CHG_CNFG_00_MODE_MASK); + break; + case POWER_SUPPLY_EXT_PROP_FACTORY_MODE: + break; + case POWER_SUPPLY_EXT_PROP_ARI_CNT: + val->intval = charger->ari_cnt; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + return 0; +} + +static bool is_timeout(int total_time) +{ + if (total_time <= 0) { + pr_info("%s: timeout for load step down\n", __func__); + return true; + } + + return false; +} + +static bool escape_load_step_down(bool is_otg_on, int wc_status) +{ + if (!is_otg_on || is_nocharge_type(wc_status)) { + pr_info("%s: Disconnect OTG or Wirless charging\n", __func__); + return true; + } + + return false; +} + +static void set_otg_boost_before_load_step_down(struct i2c_client *i2c) +{ + u8 reg = 0; + + max77775_update_reg(i2c, MAX77775_CHG_REG_CNFG_00, + MAX77775_MODE_A_BOOST_OTG_ON, CHG_CNFG_00_MODE_MASK); + pr_info("%s: Set 0xA temporarily before 0xF\n", __func__); + max77775_read_reg(i2c, MAX77775_CHG_REG_CNFG_00, ®); + pr_info("%s: CNFG_00 (0x%x)\n", __func__, reg); +} + +/* check vout for 0xA mode + */ +static bool do_step1(int vout) +{ + if (vout > 0) + return false; + + return true; +} + +/* wait WC type for BPP / EPP type for EPP + */ +static bool do_step2(char *wireless_charger_name, int wc_status) +{ + union power_supply_propval op_mode = {0, }; + + if (!is_wireless_fake_type(wc_status)) + return false; + + psy_do_property(wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_WIRELESS_OP_MODE, op_mode); + if (op_mode.intval == 0x2) /* EPP : 0x2. Should be checked operation mode register of rx ic */ + return false; + + return true; +} + +/* wait to reduce input current + */ +#define MIN_ICL MINIMUM_INPUT_CURRENT +static bool do_step3(int icl) +{ + if (icl <= MIN_ICL) + return false; + + return true; +} + +/* wait to reduce input voltage + */ +static bool do_step4(int vout, int vout_condition) +{ + if (vout <= vout_condition) + return false; + + return true; +} + +static bool load_step_down_before_wcin_otg(struct max77775_charger_data *charger) +{ + union power_supply_propval read_vout = {0, }, set_vout = {0, }, wc_status = {0, }; + int total_time = 20000; + int vout_condition = 0; + char *wireless_charger_name = charger->pdata->wireless_charger_name; + u8 reg = 0; + + pr_info("%s\n", __func__); + + psy_do_property(wireless_charger_name, get, + POWER_SUPPLY_PROP_ENERGY_NOW, read_vout); + if (do_step1(read_vout.intval)) { + if (charger->cnfg00_mode != MAX77775_MODE_A_BOOST_OTG_ON) + set_otg_boost_before_load_step_down(charger->i2c); + total_time += 5000; + } + + psy_do_property("wireless", get, + POWER_SUPPLY_EXT_PROP_WIRELESS_WC_STATUS, wc_status); + while (do_step2(wireless_charger_name, wc_status.intval)) { + if (escape_load_step_down(charger->otg_on, wc_status.intval)) + goto skip_load_step_down; + if (is_timeout(total_time)) + goto skip_load_step_down; + + pr_info("%s: Set 0xF mode after 200ms\n", __func__); + msleep(200); + total_time -= 200; + psy_do_property("wireless", get, + POWER_SUPPLY_EXT_PROP_WIRELESS_WC_STATUS, wc_status); + } + + do { + psy_do_property("wireless", get, + POWER_SUPPLY_EXT_PROP_WIRELESS_WC_STATUS, wc_status); + + if (escape_load_step_down(charger->otg_on, wc_status.intval)) + goto skip_load_step_down; + + if (max77775_get_input_current_type(charger, SEC_BATTERY_CABLE_WIRELESS) > 700) { + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_10, + 0x1B, MAX77775_CHG_WCIN_LIM); + msleep(300); + total_time -= 300; + } else if (max77775_get_input_current_type(charger, SEC_BATTERY_CABLE_WIRELESS) > MIN_ICL) { + max77775_update_reg(charger->i2c, + MAX77775_CHG_REG_CNFG_10, 0x07, MAX77775_CHG_WCIN_LIM); + msleep(300); + total_time -= 300; + } + } while (do_step3(max77775_get_input_current_type(charger, SEC_BATTERY_CABLE_WIRELESS))); + pr_info("%s: Finish to reduce input current\n", __func__); + + vout_condition = (4700 + 50); + do { + psy_do_property("wireless", get, + POWER_SUPPLY_EXT_PROP_WIRELESS_WC_STATUS, wc_status); + + if (escape_load_step_down(charger->otg_on, wc_status.intval)) + goto skip_load_step_down; + if (is_timeout(total_time)) + goto skip_load_step_down; + + psy_do_property(wireless_charger_name, get, + POWER_SUPPLY_PROP_ENERGY_NOW, read_vout); + pr_info("%s: Wireless charger Vout: %dmV\n", __func__, read_vout.intval); + + if (read_vout.intval > (9000 + 500)) { + set_vout.intval = WIRELESS_VOUT_FORCE_9V; + psy_do_property(wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_INPUT_VOLTAGE_REGULATION, set_vout); + msleep(300); + total_time -= 300; + } else { + set_vout.intval = WIRELESS_VOUT_FORCE_4_7V; + psy_do_property(wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_INPUT_VOLTAGE_REGULATION, set_vout); + msleep(300); + total_time -= 300; + } + } while (do_step4(read_vout.intval, vout_condition)); + pr_info("%s: Finish to reduce input voltage\n", __func__); + + msleep(300); + pr_info("%s: Finish to decrease load\n", __func__); + + return true; + +skip_load_step_down: + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, + charger->cnfg00_mode, CHG_CNFG_00_MODE_MASK); + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, ®); + pr_info("%s: CNFG_00 (0x%x)\n", __func__, reg); + + return false; +} + +static void set_icl_wcin_otg_work(struct work_struct *work) +{ + struct max77775_charger_data *charger = + container_of(work, struct max77775_charger_data, set_icl_wcin_otg_work.work); + union power_supply_propval value = {0, }; + + psy_do_property("wireless", set, + POWER_SUPPLY_EXT_PROP_CHARGE_OTG_ICL_CONTROL, value); + + msleep(200); + value.intval = WIRELESS_VOUT_FORCE_4_8V; + psy_do_property(charger->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_INPUT_VOLTAGE_REGULATION, value); + + msleep(100); + value.intval = WIRELESS_VOUT_FORCE_4_9V; + psy_do_property(charger->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_INPUT_VOLTAGE_REGULATION, value); + + msleep(100); + value.intval = WIRELESS_VOUT_FORCE_5V; + psy_do_property(charger->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_INPUT_VOLTAGE_REGULATION, value); + + __pm_relax(charger->set_icl_wcin_otg_ws); +} + +static void max77775_chg_set_mode_state(struct max77775_charger_data *charger, + unsigned int state) +{ + u8 reg, prev_mode; + + if (state == SEC_BAT_CHG_MODE_CHARGING) + charger->is_charging = true; + else if (state == SEC_BAT_CHG_MODE_CHARGING_OFF || + state == SEC_BAT_CHG_MODE_BUCK_OFF) + charger->is_charging = false; + + if (max77775_get_facmode() || charger->bypass_mode) { + if (state == SEC_BAT_CHG_MODE_CHARGING || + state == SEC_BAT_CHG_MODE_CHARGING_OFF || + state == SEC_BAT_CHG_MODE_BUCK_OFF) { + pr_info("%s: %s Skip set charger state\n", __func__, + (charger->bypass_mode ? "bypass mode" : "Factory mode")); + return; + } + } + + if (!charger->bat_det) { + pr_info("%s : DPM, chg state force set to buck on, chg off\n", __func__); + state = SEC_BAT_CHG_MODE_CHARGING_OFF; + charger->is_charging = false; + } + + + mutex_lock(&charger->mode_mutex); + pr_info("%s: current_mode(0x%x), state(%d)\n", __func__, charger->cnfg00_mode, state); + prev_mode = charger->cnfg00_mode; + switch (charger->cnfg00_mode) { + /* all off */ + case MAX77775_MODE_0_ALL_OFF: + if (state == SEC_BAT_CHG_MODE_CHARGING_OFF) + charger->cnfg00_mode = MAX77775_MODE_4_BUCK_ON; + else if (state == SEC_BAT_CHG_MODE_CHARGING) + charger->cnfg00_mode + = charger->cv_mode_check ? MAX77775_MODE_7_BUCK_ON : MAX77775_MODE_5_BUCK_CHG_ON; + else if (state == SEC_BAT_CHG_MODE_OTG_ON) + charger->cnfg00_mode = MAX77775_MODE_A_BOOST_OTG_ON; + else if (state == SEC_BAT_CHG_MODE_UNO_ON) + charger->cnfg00_mode = MAX77775_MODE_8_BOOST_UNO_ON; + break; + /* buck only */ + case MAX77775_MODE_4_BUCK_ON: + if (state == SEC_BAT_CHG_MODE_CHARGING) + charger->cnfg00_mode + = charger->cv_mode_check ? MAX77775_MODE_7_BUCK_ON : MAX77775_MODE_5_BUCK_CHG_ON; + else if (state == SEC_BAT_CHG_MODE_BUCK_OFF) + charger->cnfg00_mode = MAX77775_MODE_0_ALL_OFF; + else if (state == SEC_BAT_CHG_MODE_OTG_ON) { + if (is_wcin_type(charger->cable_type)) { + if (load_step_down_before_wcin_otg(charger)) { + max77775_set_charge_path(charger, WC_CHG_ALL_ON, WC_CHG_ALL_ON); + max77775_chgrcv_ramp(false); + msleep(50); + charger->cnfg00_mode = MAX77775_MODE_E_BUCK_BOOST_OTG_ON; + } else { + charger->cnfg00_mode = MAX77775_MODE_A_BOOST_OTG_ON; + pr_info("%s : Can not change mode to 0xE\n", __func__); + } + } else { + charger->cnfg00_mode = MAX77775_MODE_A_BOOST_OTG_ON; + } + } else if (state == SEC_BAT_CHG_MODE_UNO_ON) { + if (is_wired_type(charger->cable_type)) + charger->cnfg00_mode = MAX77775_MODE_C_BUCK_BOOST_UNO_ON; + else + charger->cnfg00_mode = MAX77775_MODE_8_BOOST_UNO_ON; + } + break; + /* buck, charger on */ + case MAX77775_MODE_5_BUCK_CHG_ON: + if (state == SEC_BAT_CHG_MODE_BUCK_OFF) + charger->cnfg00_mode = MAX77775_MODE_0_ALL_OFF; + else if (state == SEC_BAT_CHG_MODE_CHARGING_OFF) + charger->cnfg00_mode = MAX77775_MODE_4_BUCK_ON; + else if (state == SEC_BAT_CHG_MODE_OTG_ON) { + if (load_step_down_before_wcin_otg(charger)) { + charger->cnfg00_mode = MAX77775_MODE_F_BUCK_CHG_BOOST_OTG_ON; + } else { + charger->cnfg00_mode = MAX77775_MODE_A_BOOST_OTG_ON; + pr_info("%s : Can not change mode to 0xF\n", __func__); + } + } + else if (state == SEC_BAT_CHG_MODE_UNO_ON) + charger->cnfg00_mode = MAX77775_MODE_D_BUCK_CHG_BOOST_UNO_ON; + else if (charger->cv_mode_check && (state == SEC_BAT_CHG_MODE_CHARGING)) + charger->cnfg00_mode = MAX77775_MODE_7_BUCK_ON; + break; + case MAX77775_MODE_7_BUCK_ON: + pr_info("%s : set 0x04 mode after 0x07 mode\n", __func__); + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, + MAX77775_MODE_4_BUCK_ON, CHG_CNFG_00_MODE_MASK); + usleep_range(100, 200); + if (state == SEC_BAT_CHG_MODE_BUCK_OFF) + charger->cnfg00_mode = MAX77775_MODE_0_ALL_OFF; + else if (state == SEC_BAT_CHG_MODE_CHARGING_OFF) + charger->cnfg00_mode = MAX77775_MODE_4_BUCK_ON; + else if (state == SEC_BAT_CHG_MODE_OTG_ON) + charger->cnfg00_mode = MAX77775_MODE_F_BUCK_CHG_BOOST_OTG_ON; + else if (state == SEC_BAT_CHG_MODE_UNO_ON) + charger->cnfg00_mode = MAX77775_MODE_D_BUCK_CHG_BOOST_UNO_ON; + else if (!charger->cv_mode_check && (state == SEC_BAT_CHG_MODE_CHARGING)) + charger->cnfg00_mode = MAX77775_MODE_5_BUCK_CHG_ON; + break; + case MAX77775_MODE_8_BOOST_UNO_ON: + if (state == SEC_BAT_CHG_MODE_CHARGING_OFF) { + if (is_wired_type(charger->cable_type)) + charger->cnfg00_mode = MAX77775_MODE_C_BUCK_BOOST_UNO_ON; + } else if (state == SEC_BAT_CHG_MODE_CHARGING) { + max77775_set_charge_path(charger, WC_CHG_ALL_ON, WC_CHG_ALL_ON); + max77775_chgrcv_ramp(false); + msleep(50); + charger->cnfg00_mode = MAX77775_MODE_D_BUCK_CHG_BOOST_UNO_ON; + } else if (state == SEC_BAT_CHG_MODE_UNO_OFF) { + if (charger->charge_mode == SEC_BAT_CHG_MODE_BUCK_OFF) + charger->cnfg00_mode = MAX77775_MODE_0_ALL_OFF; + else + charger->cnfg00_mode = MAX77775_MODE_4_BUCK_ON; + /* UNO -> UNO + OTG */ + } else if (state == SEC_BAT_CHG_MODE_OTG_ON) + charger->cnfg00_mode = MAX77775_MODE_B_BOOST_OTG_UNO_ON; + break; + case MAX77775_MODE_A_BOOST_OTG_ON: + if (state == SEC_BAT_CHG_MODE_CHARGING_OFF) { + if (is_wcin_type(charger->cable_type)) { + if (load_step_down_before_wcin_otg(charger)) { + max77775_set_charge_path(charger, WC_CHG_ALL_ON, WC_CHG_ALL_ON); + max77775_chgrcv_ramp(false); + msleep(50); + charger->cnfg00_mode = MAX77775_MODE_E_BUCK_BOOST_OTG_ON; + } else { + pr_info("%s : Can not change mode to 0xE\n", __func__); + } + } + } else if (state == SEC_BAT_CHG_MODE_CHARGING) { + if (load_step_down_before_wcin_otg(charger)) { + max77775_set_charge_path(charger, WC_CHG_ALL_ON, WC_CHG_ALL_ON); + max77775_chgrcv_ramp(false); + msleep(50); + charger->cnfg00_mode = MAX77775_MODE_F_BUCK_CHG_BOOST_OTG_ON; + } else { + pr_info("%s : Can not change mode to 0xF\n", __func__); + } + } else if (state == SEC_BAT_CHG_MODE_OTG_OFF) { + if (charger->charge_mode == SEC_BAT_CHG_MODE_BUCK_OFF) + charger->cnfg00_mode = MAX77775_MODE_0_ALL_OFF; + else + charger->cnfg00_mode = MAX77775_MODE_4_BUCK_ON; + /* OTG -> OTG + UNO */ + } else if (state == SEC_BAT_CHG_MODE_UNO_ON) + charger->cnfg00_mode = MAX77775_MODE_B_BOOST_OTG_UNO_ON; + break; + case MAX77775_MODE_B_BOOST_OTG_UNO_ON: + /* OTG + UNO -> OTG */ + if (state == SEC_BAT_CHG_MODE_UNO_OFF) + charger->cnfg00_mode = MAX77775_MODE_A_BOOST_OTG_ON; + /* OTG + UNO -> UNO */ + else if (state == SEC_BAT_CHG_MODE_OTG_OFF) + charger->cnfg00_mode = MAX77775_MODE_8_BOOST_UNO_ON; + break; + case MAX77775_MODE_C_BUCK_BOOST_UNO_ON: + if (state == SEC_BAT_CHG_MODE_BUCK_OFF) + charger->cnfg00_mode = MAX77775_MODE_8_BOOST_UNO_ON; + else if (state == SEC_BAT_CHG_MODE_CHARGING) + charger->cnfg00_mode = MAX77775_MODE_D_BUCK_CHG_BOOST_UNO_ON; + else if (state == SEC_BAT_CHG_MODE_UNO_OFF) + charger->cnfg00_mode = MAX77775_MODE_4_BUCK_ON; + /* UNO -> OTG + UNO */ + else if ((state == SEC_BAT_CHG_MODE_OTG_ON) && (!is_wired_type(charger->cable_type))) + charger->cnfg00_mode = MAX77775_MODE_B_BOOST_OTG_UNO_ON; + break; + case MAX77775_MODE_D_BUCK_CHG_BOOST_UNO_ON: + if (state == SEC_BAT_CHG_MODE_BUCK_OFF) { + charger->cnfg00_mode = MAX77775_MODE_8_BOOST_UNO_ON; + } else if (state == SEC_BAT_CHG_MODE_CHARGING_OFF) { + if (is_wired_type(charger->cable_type)) + charger->cnfg00_mode = MAX77775_MODE_C_BUCK_BOOST_UNO_ON; + else + charger->cnfg00_mode = MAX77775_MODE_8_BOOST_UNO_ON; + } else if (state == SEC_BAT_CHG_MODE_UNO_OFF) { + charger->cnfg00_mode + = charger->cv_mode_check ? MAX77775_MODE_7_BUCK_ON : MAX77775_MODE_5_BUCK_CHG_ON; + } else if (state == SEC_BAT_CHG_MODE_OTG_ON) { /* UNO -> OTG */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, + MAX77775_MODE_4_BUCK_ON, CHG_CNFG_00_MODE_MASK); + usleep_range(1000, 2000); + /* mode 0x4, and 1msec delay, and then otg on */ + charger->cnfg00_mode = MAX77775_MODE_E_BUCK_BOOST_OTG_ON; + } + + if (charger->cnfg00_mode != MAX77775_MODE_D_BUCK_CHG_BOOST_UNO_ON) { + max77775_chgrcv_ramp(true); + msleep(50); + } + break; + case MAX77775_MODE_E_BUCK_BOOST_OTG_ON: + if (state == SEC_BAT_CHG_MODE_BUCK_OFF) + charger->cnfg00_mode = MAX77775_MODE_A_BOOST_OTG_ON; + else if (state == SEC_BAT_CHG_MODE_CHARGING_OFF) { + if (!is_wcin_type(charger->cable_type)) + charger->cnfg00_mode = MAX77775_MODE_A_BOOST_OTG_ON; + } + else if (state == SEC_BAT_CHG_MODE_CHARGING) + charger->cnfg00_mode = MAX77775_MODE_F_BUCK_CHG_BOOST_OTG_ON; + else if (state == SEC_BAT_CHG_MODE_OTG_OFF) + charger->cnfg00_mode = MAX77775_MODE_4_BUCK_ON; + /* OTG -> OTG + UNO */ + else if ((state == SEC_BAT_CHG_MODE_UNO_ON) && (!is_wcin_type(charger->cable_type))) + charger->cnfg00_mode = MAX77775_MODE_B_BOOST_OTG_UNO_ON; + break; + case MAX77775_MODE_F_BUCK_CHG_BOOST_OTG_ON: + if (state == SEC_BAT_CHG_MODE_CHARGING_OFF) { + if (is_wcin_type(charger->cable_type)) + charger->cnfg00_mode = MAX77775_MODE_E_BUCK_BOOST_OTG_ON; + else + charger->cnfg00_mode = MAX77775_MODE_A_BOOST_OTG_ON; + } else if (state == SEC_BAT_CHG_MODE_BUCK_OFF) { + charger->cnfg00_mode = MAX77775_MODE_A_BOOST_OTG_ON; + } else if (state == SEC_BAT_CHG_MODE_OTG_OFF) { + charger->cnfg00_mode + = charger->cv_mode_check ? MAX77775_MODE_7_BUCK_ON : MAX77775_MODE_5_BUCK_CHG_ON; + } + + if (charger->cnfg00_mode != MAX77775_MODE_F_BUCK_CHG_BOOST_OTG_ON) { + max77775_chgrcv_ramp(true); + msleep(50); + } + break; + } + + /* disable wcinsel, chginsel */ + if (charger->cnfg00_mode == MAX77775_MODE_0_ALL_OFF) + max77775_set_charge_path(charger, WC_CHG_ALL_OFF, WC_CHG_ALL_OFF); + else + max77775_check_charge_path(charger); + + if (charger->cv_mode_check && (charger->cnfg00_mode == MAX77775_MODE_7_BUCK_ON)) { + pr_info("%s : set 0x04 mode before 0x07 mode\n", __func__); + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, + MAX77775_MODE_4_BUCK_ON, CHG_CNFG_00_MODE_MASK); + usleep_range(100, 200); + } + + if (max77775_get_facmode()) + charger->cnfg00_mode |= MAX77775_MODE_4_BUCK_ON; + + pr_info("%s: current_mode(0x%x)\n", __func__, charger->cnfg00_mode); + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, + charger->cnfg00_mode, CHG_CNFG_00_MODE_MASK); + + if (((charger->cnfg00_mode == MAX77775_MODE_F_BUCK_CHG_BOOST_OTG_ON) && + ((prev_mode == MAX77775_MODE_A_BOOST_OTG_ON) || (prev_mode == MAX77775_MODE_5_BUCK_CHG_ON))) || + ((charger->cnfg00_mode == MAX77775_MODE_E_BUCK_BOOST_OTG_ON) && + ((prev_mode == MAX77775_MODE_A_BOOST_OTG_ON) || (prev_mode == MAX77775_MODE_4_BUCK_ON)))) { + if (delayed_work_pending(&charger->set_icl_wcin_otg_work)) { + cancel_delayed_work(&charger->set_icl_wcin_otg_work); + __pm_relax(charger->set_icl_wcin_otg_ws); + } + __pm_stay_awake(charger->set_icl_wcin_otg_ws); + queue_delayed_work(charger->wqueue, &charger->set_icl_wcin_otg_work, 0); + } + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, ®); + pr_info("%s: CNFG_00 (0x%x)\n", __func__, reg); + mutex_unlock(&charger->mode_mutex); +} + +static bool max77775_irq_enable(int irq, bool en) +{ + bool ret = false; + + if (irq <= 0) + return ret; + + if (en && irqd_irq_disabled(&irq_to_desc(irq)->irq_data)) { + enable_irq(irq); + ret = true; + } else if (!en && !irqd_irq_disabled(&irq_to_desc(irq)->irq_data)) { + disable_irq_nosync(irq); + ret = true; + } + pr_info("%s : irq: %d, en: %d, st: %d\n", __func__, irq, en, + irqd_irq_disabled(&irq_to_desc(irq)->irq_data)); + + return ret; +} + +static void max77775_aicl_irq_enable(struct max77775_charger_data *charger, + bool en) +{ + mutex_lock(&charger->irq_aicl_mutex); + + if (max77775_irq_enable(charger->irq_aicl, en)) { + u8 reg_data = 0; + + max77775_read_reg(charger->i2c, + MAX77775_CHG_REG_INT_MASK, ®_data); + pr_info("%s: %s aicl : 0x%x\n", + __func__, en ? "enable" : "disable", reg_data); + } + + mutex_unlock(&charger->irq_aicl_mutex); +} + + +#if defined(CONFIG_UPDATE_BATTERY_DATA) +static int max77775_charger_parse_dt(struct max77775_charger_data *charger); +#endif +static int max77775_chg_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max77775_charger_data *charger = power_supply_get_drvdata(psy); + enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; + u8 reg_data = 0; + + if (atomic_read(&charger->shutdown_cnt) > 0) { + dev_info(charger->dev, "%s: charger already shutdown\n", __func__); + return -EINVAL; + } + + /* check unlock status before does set the register */ + max77775_charger_unlock(charger); + switch ((int)psp) { + /* val->intval : type */ + case POWER_SUPPLY_PROP_STATUS: + charger->status = val->intval; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: +#if defined(CONFIG_USE_POGO) + { + union power_supply_propval value = {0, }; + int pogo_state = 0; + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_DETAILS_00, ®_data); + pogo_state = (reg_data & MAX77775_WCIN_DTLS) >> MAX77775_WCIN_DTLS_SHIFT; + + value.intval = SEC_BATTERY_VBYP; + psy_do_property("max77775-fuelgauge", get, + POWER_SUPPLY_EXT_PROP_MEASURE_INPUT, value); + + pr_info("%s: pogo_state(%d), Vwcin(%d)\n", __func__, pogo_state, value.intval); + + if (pogo_state) { + if (value.intval > 8000) + value.intval = 2; + else + value.intval = 1; + } else + value.intval = 0; + + psy_do_property("pogo", set, POWER_SUPPLY_PROP_ONLINE, value); + } +#endif + break; + case POWER_SUPPLY_PROP_ONLINE: + charger->cable_type = val->intval; + charger->aicl_curr = 0; + charger->slow_charging = false; + charger->input_current = max77775_get_input_current(charger); + max77775_check_charge_path(charger); + if (!max77775_get_autoibus(charger)) + max77775_set_fw_noautoibus(MAX77775_AUTOIBUS_AT_OFF); + + if (charger->pdata->boosting_voltage_aicl) + max77775_aicl_irq_enable(charger, true); + + if (is_nocharge_type(charger->cable_type)) { + charger->wc_pre_current = WC_CURRENT_START; + /* VCHGIN : REG=4.5V, UVLO=4.7V */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_12, + 0x00, CHG_CNFG_12_VCHGIN_REG_MASK); + if (charger->pdata->enable_sysovlo_irq) + max77775_set_sysovlo(charger, 1); + + if (!charger->pdata->boosting_voltage_aicl) + max77775_aicl_irq_enable(charger, true); + } else if (is_wired_type(charger->cable_type)) { + /* VCHGIN : REG=4.6V, UVLO=4.8V + * to fix CHGIN-UVLO issues including cheapy battery packs + */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_12, + 0x08, CHG_CNFG_12_VCHGIN_REG_MASK); + if (is_hv_wire_type(charger->cable_type) || + (charger->cable_type == SEC_BATTERY_CABLE_HV_TA_CHG_LIMIT)) { + if (!charger->pdata->boosting_voltage_aicl) { + max77775_aicl_irq_enable(charger, false); + cancel_delayed_work(&charger->aicl_work); + __pm_relax(charger->aicl_ws); + } + } + } + break; + /* val->intval : input charging current */ + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + { + int input_current = val->intval; + + if (delayed_work_pending(&charger->aicl_work)) { + cancel_delayed_work(&charger->aicl_work); + charger->aicl_curr = 0; + queue_delayed_work(charger->wqueue, &charger->aicl_work, + msecs_to_jiffies(AICL_WORK_DELAY)); + } + + if (is_wireless_type(charger->cable_type)) + max77775_set_wireless_input_current(charger, input_current); + else + max77775_set_input_current(charger, input_current); + + charger->input_current = input_current; + } + break; + /* val->intval : charging current */ + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + charger->charging_current = val->intval; + max77775_set_charge_current(charger, val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + charger->float_voltage = val->intval; + pr_info("%s: float voltage(%d)\n", __func__, val->intval); + max77775_set_float_voltage(charger, val->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX: + max77775_init_aicl_irq(charger); + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_INT_OK, ®_data); + if (!(reg_data & MAX77775_AICL_OK)) + queue_delayed_work(charger->wqueue, &charger->aicl_work, + msecs_to_jiffies(AICL_WORK_DELAY)); + break; + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + max77775_set_topoff_current(charger, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: + switch (ext_psp) { + case POWER_SUPPLY_EXT_PROP_FACTORY_VOLTAGE_REGULATION: + charger->float_voltage = val->intval; + pr_info("%s: factory voltage regulation(%d)\n", __func__, val->intval); + max77775_set_termination_voltage(charger, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_SURGE: + if (val->intval) { + pr_info("%s : Charger IC reset by surge. charger re-initialize\n", + __func__); + check_charger_unlock_state(charger); + } + break; + case POWER_SUPPLY_EXT_PROP_CHGINSEL: + if (val->intval == WL_TO_W) + max77775_set_charge_path(charger, CHGIN_SEL, CHGIN_SEL); + else + max77775_check_charge_path(charger); + break; + case POWER_SUPPLY_EXT_PROP_PAD_VOLT_CTRL: + break; + case POWER_SUPPLY_EXT_PROP_SHIPMODE_TEST: +#if defined(CONFIG_SUPPORT_SHIP_MODE) + if ((val->intval == SHIP_MODE_EN) || (val->intval == SHIP_MODE_EN_OP)) { + pr_info("%s: set ship mode enable\n", __func__); + max77775_set_ship_mode(charger, 1); + max77775_set_bypass_auto_ship_en(charger, 1); + max77775_set_auto_ship_mode(charger, 1); + max77775_set_ship_entry_db(charger, 1); + } else { + pr_info("%s: ship mode disable is not supported\n", __func__); + } +#else + pr_info("%s: ship mode(%d) is not supported\n", __func__, val->intval); +#endif + break; + case POWER_SUPPLY_EXT_PROP_AUTO_SHIPMODE_CONTROL: + if (val->intval) { + pr_info("%s: auto ship mode is enabled\n", __func__); + max77775_set_auto_ship_mode(charger, 1); + } else { + pr_info("%s: auto ship mode is disabled\n", __func__); + max77775_set_auto_ship_mode(charger, 0); + } + break; + case POWER_SUPPLY_EXT_PROP_FGSRC_SWITCHING: + { + u8 reg_data = 0, reg_fgsrc = 0; + + /* if jig attached, change the power source */ + /* from the VBATFG to the internal VSYS */ + if ((val->intval == SEC_BAT_INBAT_FGSRC_SWITCHING_VSYS) || + (val->intval == SEC_BAT_FGSRC_SWITCHING_VSYS)) + reg_fgsrc = 1; + else + reg_fgsrc = 0; + + max77775_update_reg(charger->i2c, + MAX77775_CHG_REG_CNFG_07, + (reg_fgsrc << CHG_CNFG_07_REG_FGSRC_SHIFT), + CHG_CNFG_07_REG_FGSRC_MASK); + max77775_read_reg(charger->i2c, + MAX77775_CHG_REG_CNFG_07, ®_data); + + pr_info("%s: POWER_SUPPLY_EXT_PROP_FGSRC_SWITCHING(%d): reg(0x%x) val(0x%x)\n", + __func__, reg_fgsrc, MAX77775_CHG_REG_CNFG_07, reg_data); + } + break; + case POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED: + charger->charge_mode = val->intval; + charger->misalign_cnt = 0; + max77775_chg_set_mode_state(charger, charger->charge_mode); + break; + case POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL: + max77775_set_otg(charger, val->intval); + break; +#if defined(CONFIG_UPDATE_BATTERY_DATA) + case POWER_SUPPLY_EXT_PROP_POWER_DESIGN: + max77775_charger_parse_dt(charger); + break; +#endif + case POWER_SUPPLY_EXT_PROP_CONSTANT_CHARGE_CURRENT_WRL: + charger->charging_current = val->intval; + __pm_stay_awake(charger->wc_chg_current_ws); + queue_delayed_work(charger->wqueue, &charger->wc_chg_current_work, + msecs_to_jiffies(0)); + break; + case POWER_SUPPLY_EXT_PROP_CURRENT_MEASURE: + pr_info("%s: keystring bypass mode is %s\n", + __func__, val->intval ? "enable" : "disable"); + if (val->intval) { + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, + MAX77775_MODE_4_BUCK_ON, CHG_CNFG_00_MODE_MASK); + max77775_set_input_current(charger, 3200); + /* write 255k for bypass mode */ + max77775_usb_id_set(0x04); +#if !defined(CONFIG_SEC_FACTORY) + } else + max77775_bypass_maintain(); +#else + } +#endif + break; + case POWER_SUPPLY_EXT_PROP_INPUT_VOLTAGE_REGULATION: + if (val->intval) + max77775_set_bypass_mode(charger, 1); + break; + case POWER_SUPPLY_EXT_PROP_SPSN_TEST: + max77775_set_spsn_det_res(charger, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_FACTORY_MODE: + max77775_set_factory_mode(charger); + break; + case POWER_SUPPLY_EXT_PROP_ARI_CNT: + if (charger->spcom) { + charger->ari_cnt = val->intval; + dev_info(charger->dev, "%s: ari cnt:%d\n", + __func__, charger->ari_cnt); + } else { + charger->ari_cnt = -1; + dev_info(charger->dev, "%s: not support ari cnt: %d\n", + __func__, val->intval); + } + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + return 0; +} + +static int max77775_otg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max77775_charger_data *charger = power_supply_get_drvdata(psy); + enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; + u8 reg_data; + + if (atomic_read(&charger->shutdown_cnt) > 0) { + dev_info(charger->dev, "%s: charger already shutdown\n", __func__); + return -EINVAL; + } + + switch ((int)psp) { + case POWER_SUPPLY_PROP_ONLINE: + mutex_lock(&charger->charger_mutex); + val->intval = charger->otg_on; + mutex_unlock(&charger->charger_mutex); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + break; + case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: + switch (ext_psp) { + case POWER_SUPPLY_EXT_PROP_CHARGE_UNO_CONTROL: + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, + ®_data); + reg_data &= CHG_CNFG_00_MODE_MASK; + if (reg_data == MAX77775_MODE_8_BOOST_UNO_ON || + reg_data == MAX77775_MODE_C_BUCK_BOOST_UNO_ON || + reg_data == MAX77775_MODE_D_BUCK_CHG_BOOST_UNO_ON) + val->intval = 1; + else + val->intval = 0; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + return 0; +} + +static int max77775_otg_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max77775_charger_data *charger = power_supply_get_drvdata(psy); + enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; + bool mfc_fw_update = false; + union power_supply_propval value = {0, }; + + if (atomic_read(&charger->shutdown_cnt) > 0) { + dev_info(charger->dev, "%s: charger already shutdown\n", __func__); + return -EINVAL; + } + + switch ((int)psp) { + case POWER_SUPPLY_PROP_ONLINE: + psy_do_property("battery", get, + POWER_SUPPLY_EXT_PROP_MFC_FW_UPDATE, value); + mfc_fw_update = value.intval; + if (!mfc_fw_update) { + max77775_set_otg(charger, val->intval); + if (val->intval) { + max77775_aicl_irq_enable(charger, false); + cancel_delayed_work(&charger->aicl_work); + __pm_relax(charger->aicl_ws); + charger->aicl_curr = 0; + charger->slow_charging = false; + } else if (!val->intval) { + max77775_aicl_irq_enable(charger, true); + } + } else { + pr_info("%s : max77775_set_otg skip, mfc_fw_update(%d)\n", + __func__, mfc_fw_update); + } + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + pr_info("POWER_SUPPLY_PROP_VOLTAGE_MAX, set otg current limit %dmA\n", (val->intval) ? 1500 : 900); + + if (val->intval) { + charger->hp_otg = true; + /* otg current limit 1500mA */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_02, + MAX77775_OTG_ILIM_1500 << CHG_CNFG_02_OTG_ILIM_SHIFT, + CHG_CNFG_02_OTG_ILIM_MASK); + } else { + charger->hp_otg = false; + /* otg current limit 900mA */ + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_CNFG_02, + MAX77775_OTG_ILIM_900 << CHG_CNFG_02_OTG_ILIM_SHIFT, + CHG_CNFG_02_OTG_ILIM_MASK); + } + break; + case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: + switch (ext_psp) { + case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_VOUT: + max77775_set_uno_vout(charger, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_IOUT: + max77775_set_uno_iout(charger, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_CHARGE_UNO_CONTROL: + pr_info("%s: WCIN-UNO %d\n", __func__, val->intval); + max77775_set_uno(charger, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_OTG_VBUS_CTRL: + pr_info("%s: OTG_VBUS_CTRL %d\n", __func__, val->intval); + mutex_lock(&charger->charger_mutex); + if (val->intval == TURN_OTG_ON) { + max77775_chg_set_mode_state(charger, SEC_BAT_CHG_MODE_OTG_ON); + } else if (val->intval == TURN_OTG_OFF) { + max77775_chg_set_mode_state(charger, SEC_BAT_CHG_MODE_OTG_OFF); + } else if (val->intval == TURN_RB_OFF) { + value.intval = 0; + psy_do_property("sec-direct-charger", set, + POWER_SUPPLY_EXT_PROP_OTG_VBUS_CTRL, value); + } else if (val->intval == TURN_RB_ON) { + value.intval = 1; + psy_do_property("sec-direct-charger", set, + POWER_SUPPLY_EXT_PROP_OTG_VBUS_CTRL, value); + } else if (val->intval == TURN_OTG_OFF_RB_ON) { + max77775_chg_set_mode_state(charger, SEC_BAT_CHG_MODE_OTG_OFF); + value.intval = 1; + psy_do_property("sec-direct-charger", set, + POWER_SUPPLY_EXT_PROP_OTG_VBUS_CTRL, value); + } else { + pr_info("%s: Unknown OTG_VBUS_CTRL %d\n", __func__, val->intval); + } + mutex_unlock(&charger->charger_mutex); + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + return 0; +} + +static int max77775_debugfs_show(struct seq_file *s, void *data) +{ + struct max77775_charger_data *charger = s->private; + u8 reg, reg_data; + + seq_puts(s, "MAX77775 CHARGER IC :\n"); + seq_puts(s, "===================\n"); + for (reg = 0xB0; reg <= 0xC8; reg++) { + max77775_read_reg(charger->i2c, reg, ®_data); + seq_printf(s, "0x%02x:\t0x%02x\n", reg, reg_data); + } + + seq_puts(s, "\n"); + return 0; +} + +static int max77775_debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, max77775_debugfs_show, inode->i_private); +} + +static const struct file_operations max77775_debugfs_fops = { + .open = max77775_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void max77775_chg_isr_work(struct work_struct *work) +{ + struct max77775_charger_data *charger = + container_of(work, struct max77775_charger_data, isr_work.work); + + max77775_get_charger_state(charger); + max77775_get_charging_health(charger); +} + +static irqreturn_t max77775_chg_irq_thread(int irq, void *irq_data) +{ + struct max77775_charger_data *charger = irq_data; + + pr_info("%s: Charger interrupt occurred\n", __func__); + + if ((charger->pdata->full_check_type == SEC_BATTERY_FULLCHARGED_CHGINT) + || (charger->pdata->ovp_uvlo_check_type == SEC_BATTERY_OVP_UVLO_CHGINT)) + schedule_delayed_work(&charger->isr_work, 0); + + return IRQ_HANDLED; +} + +static irqreturn_t max77775_batp_irq(int irq, void *data) +{ + struct max77775_charger_data *charger = data; + union power_supply_propval value = {0, }; + u8 reg_data; + + pr_info("%s : irq(%d)\n", __func__, irq); + + check_charger_unlock_state(charger); + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_INT_OK, ®_data); + + if (charger->pdata->enable_dpm && charger->pdata->disqbat) { + if (!(reg_data & MAX77775_BATP_OK)) { + if (!charger->bat_det) { + pr_info("%s : ignore duplicated irq(%d)\n", __func__, charger->bat_det); + return IRQ_HANDLED; + } + + /* disqbat set to HIGH (Qbat OFF) */ + charger->bat_det = false; + gpio_direction_output(charger->pdata->disqbat, 1); + + /* disable thermal control */ + value.intval = 1; + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_THERMAL_ZONE, value); + + /* set DPM misc event for PMS UI control */ + value.intval = DPM_MISC; + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_MISC_EVENT, value); + } else { + if (charger->bat_det) { + pr_info("%s : ignore duplicated irq(%d)\n", __func__, charger->bat_det); + return IRQ_HANDLED; + } + + /* fuelgauge reset before battery insertion control */ + value.intval = SEC_FUELGAUGE_CAPACITY_TYPE_RESET; + psy_do_property("max77775-fuelgauge", set, POWER_SUPPLY_PROP_CAPACITY, value); + pr_info("%s : do reset SOC for battery insertion control\n", __func__); + + /* disqbat set to LOW (Qbat ON) */ + charger->bat_det = true; + gpio_direction_output(charger->pdata->disqbat, 0); + + /* enable thermal control */ + value.intval = 0; + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_THERMAL_ZONE, value); + + /* set DPM misc event for PMS UI control */ + value.intval = DPM_MISC; + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_MISC_EVENT_CLEAR, value); + } + max77775_set_input_current(charger, charger->dpm_last_icl); + max77775_chg_set_mode_state(charger, charger->charge_mode); + pr_info("%s: battery_detect(%d)\n", __func__, charger->bat_det); + } else { /* original code that does not use DPM */ + if (!(reg_data & MAX77775_BATP_OK)) + psy_do_property("battery", set, POWER_SUPPLY_PROP_PRESENT, value); + } + return IRQ_HANDLED; +} + +#if defined(CONFIG_MAX77775_CHECK_B2SOVRC) +#if defined(CONFIG_REGULATOR_S2MPS18) +extern void s2mps18_print_adc_val_power(void); +#endif +static irqreturn_t max77775_bat_irq(int irq, void *data) +{ + struct max77775_charger_data *charger = data; + union power_supply_propval value = {0, }; + u8 reg_int_ok, reg_data; + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_INT_OK, ®_int_ok); + if (!(reg_int_ok & MAX77775_BAT_OK)) { + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_DETAILS_01, ®_data); + reg_data = ((reg_data & MAX77775_BAT_DTLS) >> MAX77775_BAT_DTLS_SHIFT); + if (reg_data == 0x06) { + pr_info("OCP(B2SOVRC)\n"); + + if (charger->uno_on) { +#if defined(CONFIG_WIRELESS_TX_MODE) + union power_supply_propval val; + val.intval = BATT_TX_EVENT_WIRELESS_TX_OCP; + psy_do_property("wireless", set, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ERR, val); +#endif + } +#if defined(CONFIG_REGULATOR_S2MPS18) + s2mps18_print_adc_val_power(); +#endif + /* print vnow, inow */ + psy_do_property("max77775-fuelgauge", get, + POWER_SUPPLY_EXT_PROP_FUELGAUGE_LOG, value); + } + } else { + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_DETAILS_01, ®_data); + reg_data = ((reg_data & MAX77775_BAT_DTLS) >> MAX77775_BAT_DTLS_SHIFT); + pr_info("%s: reg_data(0x%x)\n", __func__, reg_data); + } + check_charger_unlock_state(charger); + + return IRQ_HANDLED; +} +#endif + +static irqreturn_t max77775_bypass_irq(int irq, void *data) +{ + struct max77775_charger_data *charger = data; +#ifdef CONFIG_USB_HOST_NOTIFY + struct otg_notify *o_notify; +#endif + union power_supply_propval val; + u8 dtls_02, byp_dtls; + + pr_info("%s: irq(%d)\n", __func__, irq); + + /* check and unlock */ + check_charger_unlock_state(charger); + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_DETAILS_02, &dtls_02); + + byp_dtls = ((dtls_02 & MAX77775_BYP_DTLS) >> MAX77775_BYP_DTLS_SHIFT); + pr_info("%s: BYP_DTLS(0x%02x)\n", __func__, byp_dtls); + + if (byp_dtls & 0x1) { + pr_info("%s: bypass overcurrent limit\n", __func__); + /* disable the register values just related to OTG and + * keep the values about the charging + */ + if (charger->otg_on) { +#ifdef CONFIG_USB_HOST_NOTIFY + o_notify = get_otg_notify(); + if (o_notify) + send_otg_notify(o_notify, NOTIFY_EVENT_OVERCURRENT, 0); +#endif + val.intval = 0; + psy_do_property("otg", set, POWER_SUPPLY_PROP_ONLINE, val); + } else if (charger->uno_on) { +#if defined(CONFIG_WIRELESS_TX_MODE) + val.intval = BATT_TX_EVENT_WIRELESS_TX_OCP; + psy_do_property("wireless", set, POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ERR, val); +#endif + } + } + return IRQ_HANDLED; +} + +static bool aicl_work_skip_condition(struct max77775_charger_data *charger) +{ + union power_supply_propval value = {0, }; + int hv_pdo = 0; + int aicl_voter_value = 0; + + if (is_nocharge_type(charger->cable_type)) + return true; + if (irqd_irq_disabled(&irq_to_desc(charger->irq_aicl)->irq_data)) + return true; + + psy_do_property("battery", get, + POWER_SUPPLY_EXT_PROP_HV_PDO, value); + hv_pdo = value.intval; + if (hv_pdo) { + pr_info("%s : skip because of hv_pdo\n", __func__); + return true; + } + + if (is_wireless_type(charger->cable_type) && + (charger->wc_pre_current > charger->wc_current)) { + if (get_sec_voter_statusf("ICL", VOTER_AICL, &aicl_voter_value) < 0) + return true; + + if (charger->wc_current < aicl_voter_value) { + pr_info("%s : aicl voter(%dmA) is higher than other voter(%dmA), skip first." + " aicl voter will be set when irq occurs again later\n", + __func__, aicl_voter_value, charger->wc_current); + return true; + } + } + + return false; +} + +static void max77775_aicl_isr_work(struct work_struct *work) +{ + struct max77775_charger_data *charger = + container_of(work, struct max77775_charger_data, aicl_work.work); + bool aicl_mode = false; + u8 aicl_state = 0; + int aicl_current = 0; + + if (aicl_work_skip_condition(charger)) { + pr_info("%s: skip\n", __func__); + charger->aicl_curr = 0; + sec_votef("ICL", VOTER_AICL, false, 0); + cancel_delayed_work(&charger->aicl_work); + __pm_relax(charger->aicl_ws); + return; + } + + /* check and unlock */ + check_charger_unlock_state(charger); + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_INT_OK, &aicl_state); + + if (!(aicl_state & MAX77775_AICL_OK)) { + /* AICL mode */ + pr_info("%s : AICL Mode : CHG_INT_OK(0x%02x)\n", + __func__, aicl_state); + + mutex_lock(&charger->icl_mutex); + cancel_delayed_work(&charger->wc_current_work); + __pm_relax(charger->wc_current_ws); + mutex_unlock(&charger->icl_mutex); + + charger->aicl_curr = reduce_input_current(charger); + + if (is_not_wireless_type(charger->cable_type)) + max77775_check_slow_charging(charger, charger->input_current); + + if (charger->input_current <= MINIMUM_INPUT_CURRENT) { + max77775_aicl_irq_enable(charger, false); + + /* notify aicl current, no more aicl check */ + aicl_current = MINIMUM_INPUT_CURRENT; + } else { + aicl_mode = true; + queue_delayed_work(charger->wqueue, &charger->aicl_work, + msecs_to_jiffies(AICL_WORK_DELAY)); + } + } else { + /* Not in AICL mode */ + pr_info("%s : Not in AICL Mode : CHG_INT_OK(0x%02x), aicl_curr(%d)\n", + __func__, aicl_state, charger->aicl_curr); + if (charger->aicl_curr) { + /* notify aicl current, if aicl is on and aicl state is cleard */ + aicl_current = charger->aicl_curr; + } + } + + if (aicl_current) { + union power_supply_propval value = {0, }; + + value.intval = aicl_current; + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_AICL_CURRENT, value); + } + + /* keep wakeup_source if this is not last work to prevent to enter suspend */ + if (!aicl_mode && !delayed_work_pending(&charger->aicl_work)) + __pm_relax(charger->aicl_ws); +} + +static irqreturn_t max77775_aicl_irq(int irq, void *data) +{ + struct max77775_charger_data *charger = data; + + __pm_stay_awake(charger->aicl_ws); + queue_delayed_work(charger->wqueue, &charger->aicl_work, + msecs_to_jiffies(AICL_WORK_DELAY)); + + pr_info("%s: irq(%d)\n", __func__, irq); + + return IRQ_HANDLED; +} + +static void max77775_init_aicl_irq(struct max77775_charger_data *charger) +{ + int ret; + + charger->irq_aicl = charger->max77775_pdata->irq_base + MAX77775_CHG_IRQ_AICL_I; + ret = request_threaded_irq(charger->irq_aicl, NULL, + max77775_aicl_irq, 0, + "aicl-irq", charger); + if (ret < 0) { + pr_err("%s: fail to request aicl IRQ: %d: %d\n", + __func__, charger->irq_aicl, ret); + } + pr_info("%s: %d\n", __func__, irqd_irq_disabled(&irq_to_desc(charger->irq_aicl)->irq_data)); +} + +static void max77775_wc_current_work(struct work_struct *work) +{ + struct max77775_charger_data *charger = + container_of(work, struct max77775_charger_data, wc_current_work.work); + union power_supply_propval value = {0, }; + int diff_current = 0; + + if (is_not_wireless_type(charger->cable_type)) { + charger->wc_pre_current = WC_CURRENT_START; + max77775_write_reg(charger->i2c, MAX77775_CHG_REG_CNFG_10, WC_DEFAULT_CURRENT); + __pm_relax(charger->wc_current_ws); + return; + } + + if (charger->wc_pre_current == charger->wc_current) { + max77775_set_input_current(charger, charger->wc_pre_current); + max77775_set_charge_current(charger, charger->charging_current); +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_FASTCHG_LIMIT_CURRENT, value); +#endif + /* Wcurr-B) Restore Vrect adj room to previous value + * after finishing wireless input current setting. + * Refer to Wcurr-A) step + */ + msleep(500); + value.intval = WIRELESS_VRECT_ADJ_OFF; /* Vrect Room 0mV */ + psy_do_property(charger->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_RX_CONTROL, value); + /* keep wakeup_source if this is not last work to prevent to enter suspend */ + if (!delayed_work_pending(&charger->wc_current_work)) + __pm_relax(charger->wc_current_ws); + } else { + diff_current = charger->wc_pre_current - charger->wc_current; + diff_current = (diff_current > charger->pdata->wc_current_step) ? charger->pdata->wc_current_step : + ((diff_current < -charger->pdata->wc_current_step) ? -charger->pdata->wc_current_step : diff_current); + + charger->wc_pre_current -= diff_current; + max77775_set_input_current(charger, charger->wc_pre_current); + __pm_stay_awake(charger->wc_current_ws); + queue_delayed_work(charger->wqueue, &charger->wc_current_work, + msecs_to_jiffies(charger->otg_on ? + WC_CURRENT_WORK_STEP_OTG : WC_CURRENT_WORK_STEP)); + } + pr_info("%s: wc_current(%d), wc_pre_current(%d), diff(%d)\n", __func__, + charger->wc_current, charger->wc_pre_current, diff_current); +} + +static void max77775_wc_chg_current_work(struct work_struct *work) +{ + struct max77775_charger_data *charger = + container_of(work, struct max77775_charger_data, wc_chg_current_work.work); +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + union power_supply_propval value = {0, }; +#endif + + max77775_set_charge_current(charger, charger->charging_current); +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_FASTCHG_LIMIT_CURRENT, value); +#endif + __pm_relax(charger->wc_chg_current_ws); +} + +#if defined(CONFIG_USE_POGO) +static void max77775_wcin_det_work(struct work_struct *work) +{ + struct max77775_charger_data *charger = container_of(work, + struct max77775_charger_data, wcin_det_work.work); + u8 reg_data, wcin_state, wcin_dtls = 0; + union power_supply_propval value = {0, }, val_vbyp = {0, }; + + max77775_update_reg(charger->i2c, + MAX77775_CHG_REG_INT_MASK, 0, MAX77775_WCIN_IM); + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_INT_OK, ®_data); + wcin_state = (reg_data & MAX77775_WCIN_OK) >> MAX77775_WCIN_OK_SHIFT; + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_DETAILS_00, ®_data); + wcin_dtls = (reg_data & MAX77775_WCIN_DTLS) >> MAX77775_WCIN_DTLS_SHIFT; + + pr_info("%s wcin_state(%d) wcin_dtls(%d)\n", __func__, wcin_state, wcin_dtls); + + val_vbyp.intval = SEC_BATTERY_VBYP; + psy_do_property("max77775-fuelgauge", get, + POWER_SUPPLY_EXT_PROP_MEASURE_INPUT, val_vbyp); + + if (wcin_state && wcin_dtls) { + if (val_vbyp.intval > 8000) + value.intval = 2; + else + value.intval = 1; + } else + value.intval = 0; + + psy_do_property("pogo", set, POWER_SUPPLY_PROP_ONLINE, value); + + /* Do unmask again. (for frequent wcin irq problem) */ + max77775_update_reg(charger->i2c, + MAX77775_CHG_REG_INT_MASK, 0, MAX77775_WCIN_IM); + + __pm_relax(charger->wcin_det_ws); +} + +static irqreturn_t max77775_wcin_irq(int irq, void *data) +{ + struct max77775_charger_data *charger = data; + + pr_info("%s: irq(%d)\n", __func__, irq); + + max77775_update_reg(charger->i2c, MAX77775_CHG_REG_INT_MASK, + MAX77775_WCIN_IM, MAX77775_WCIN_IM); + __pm_stay_awake(charger->wcin_det_ws); + queue_delayed_work(charger->wqueue, &charger->wcin_det_work, + msecs_to_jiffies(500)); + + return IRQ_HANDLED; +} +#endif + +static irqreturn_t max77775_sysovlo_irq(int irq, void *data) +{ + struct max77775_charger_data *charger = data; + union power_supply_propval value = {0, }; + + pr_info("%s\n", __func__); + __pm_wakeup_event(charger->sysovlo_ws, jiffies_to_msecs(HZ * 5)); + + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_SYSOVLO, value); + + max77775_set_sysovlo(charger, 0); + return IRQ_HANDLED; +} + +#ifdef CONFIG_OF +static int max77775_charger_parse_dt(struct max77775_charger_data *charger) +{ + struct device_node *np; + struct device_node *spss_region_dn; + max77775_charger_platform_data_t *pdata = charger->pdata; + int ret = 0; + + spss_region_dn = of_find_node_by_name(NULL, "qcom,spcom"); + if (spss_region_dn == NULL) { +#if IS_ENABLED(CONFIG_QCOM_SPSS) + pr_info("[%s] coudln't find qcom,spcom, config enabled\n", __func__); +#else + pr_info("[%s] coudln't find qcom,spcom\n", __func__); +#endif + charger->spcom = false; + } else { + charger->spcom = true; + pr_info("[%s] found spcom\n", __func__); + } + + np = of_find_node_by_name(NULL, "battery"); + if (!np) { + pr_err("%s: np(battery) NULL\n", __func__); + } else { + ret = of_property_read_u32(np, "battery,chg_float_voltage", + &pdata->chg_float_voltage); + if (ret) { + pr_info("%s: battery,chg_float_voltage is Empty\n", __func__); + pdata->chg_float_voltage = 4200; + } + charger->float_voltage = pdata->chg_float_voltage; + + pdata->boosting_voltage_aicl = of_property_read_bool(np, + "battery,boosting_voltage_aicl"); + + ret = of_property_read_u32(np, "battery,chg_ocp_current", + &pdata->chg_ocp_current); + if (ret) { + pr_info("%s: battery,chg_ocp_current is Empty\n", __func__); + pdata->chg_ocp_current = 5600; /* mA */ + } + + ret = of_property_read_u32(np, "battery,chg_ocp_dtc", + &pdata->chg_ocp_dtc); + if (ret) { + pr_info("%s: battery,chg_ocp_dtc is Empty\n", __func__); + pdata->chg_ocp_dtc = 6; /* ms */ + } + + ret = of_property_read_u32(np, "battery,topoff_time", + &pdata->topoff_time); + if (ret) { + pr_info("%s: battery,topoff_time is Empty\n", __func__); + pdata->topoff_time = 30; /* min */ + } + + ret = of_property_read_string(np, "battery,wireless_charger_name", + (char const **)&pdata->wireless_charger_name); + if (ret) + pr_info("%s: Wireless charger name is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,full_check_type_2nd", + &pdata->full_check_type_2nd); + if (ret) + pr_info("%s : Full check type 2nd is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,wireless_cc_cv", + &pdata->wireless_cc_cv); + if (ret) + pr_info("%s : wireless_cc_cv is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,nv_wc_headroom", + &pdata->nv_wc_headroom); + if (ret) { + pr_info("%s : nv_wc_headroom is Empty\n", __func__); + pdata->nv_wc_headroom = WIRELESS_VRECT_ADJ_ROOM_1; /* 277mV */ + } + + pr_info("%s: fv : %d, ocp_curr : %d, ocp_dtc : %d, topoff_time : %d\n", + __func__, charger->float_voltage, pdata->chg_ocp_current, + pdata->chg_ocp_dtc, pdata->topoff_time); + } + + np = of_find_node_by_name(NULL, "max77775-charger"); + if (!np) { + pr_err("%s: np(max77775-charger) NULL\n", __func__); + } else { + ret = of_property_read_u32(np, "charger,fac_vsys", &pdata->fac_vsys); + if (ret) { + pr_info("%s : fac_vsys is Empty\n", __func__); + pdata->fac_vsys = 3800; /* mV */ + } +#if defined(CONFIG_SEC_FACTORY) + pdata->factory_wcin_irq = + of_property_read_bool(np, "charger,factory_wcin_irq"); +#endif + pdata->user_wcin_irq = + of_property_read_bool(np, "charger,user_wcin_irq"); + + pdata->enable_sysovlo_irq = + of_property_read_bool(np, "charger,enable_sysovlo_irq"); + + pdata->enable_noise_wa = + of_property_read_bool(np, "charger,enable_noise_wa"); + + pdata->enable_dpm = + of_property_read_bool(np, "charger,enable_dpm"); + + if (of_property_read_u32(np, "charger,dpm_icl", &pdata->dpm_icl)) { + pr_info("%s : dpm_icl is Empty\n", __func__); + pdata->dpm_icl = 1800; + } + + pdata->disqbat = of_get_named_gpio(np, "charger,disqbat", 0); + if (pdata->disqbat < 0) { + pr_info("%s : can't get disqbat\n", __func__); + pdata->disqbat = 0; + } + pr_info("%s: pdata->disqbat %d\n", __func__, pdata->disqbat); + + if (of_property_read_u32(np, "charger,fsw", &pdata->fsw)) { + pr_info("%s : fsw is Empty\n", __func__); + pdata->fsw = MAX77775_CHG_FSW_1_5MHz; + } + charger->fsw_now = pdata->fsw; + + ret = of_property_read_u32(np, "charger,wc_current_step", + &pdata->wc_current_step); + if (ret) { + pr_info("%s: battery,wc_current_step is Empty\n", __func__); + pdata->wc_current_step = WC_CURRENT_STEP; /* default 100mA */ + ret = 0; + } + + pr_info("%s: fac_vsys:%d, fsw:%d, wc_current_step:%d\n", __func__, + pdata->fac_vsys, pdata->fsw, pdata->wc_current_step); + } + + np = of_find_node_by_name(NULL, "max77775-fuelgauge"); + if (!np) { + pr_err("%s: np(max77775-fuelgauge) NULL\n", __func__); + } else { + charger->jig_low_active = of_property_read_bool(np, + "fuelgauge,jig_low_active"); + charger->jig_gpio = of_get_named_gpio(np, "fuelgauge,jig_gpio", 0); + if (charger->jig_gpio < 0) { + pr_err("%s: error reading jig_gpio = %d\n", + __func__, charger->jig_gpio); + charger->jig_gpio = 0; + } + } + + return ret; +} +#endif + +static const struct power_supply_desc max77775_charger_power_supply_desc = { + .name = "max77775-charger", + .type = POWER_SUPPLY_TYPE_UNKNOWN, + .properties = max77775_charger_props, + .num_properties = ARRAY_SIZE(max77775_charger_props), + .get_property = max77775_chg_get_property, + .set_property = max77775_chg_set_property, + .no_thermal = true, +}; + +static char *max77775_otg_supply_list[] = { + "otg", +}; + +static const struct power_supply_desc max77775_otg_power_supply_desc = { + .name = "max77775-otg", + .type = POWER_SUPPLY_TYPE_UNKNOWN, + .properties = max77775_otg_props, + .num_properties = ARRAY_SIZE(max77775_otg_props), + .get_property = max77775_otg_get_property, + .set_property = max77775_otg_set_property, +}; + +static int max77775_charger_probe(struct platform_device *pdev) +{ + struct max77775_dev *max77775 = dev_get_drvdata(pdev->dev.parent); + struct max77775_platform_data *pdata = dev_get_platdata(max77775->dev); + max77775_charger_platform_data_t *charger_data; + struct max77775_charger_data *charger; + struct power_supply_config charger_cfg = { }; + int ret = 0; + u8 reg_data; + + pr_info("%s: max77775 Charger Driver Loading\n", __func__); + + charger = kzalloc(sizeof(*charger), GFP_KERNEL); + if (!charger) + return -ENOMEM; + + charger_data = kzalloc(sizeof(max77775_charger_platform_data_t), GFP_KERNEL); + if (!charger_data) { + ret = -ENOMEM; + goto err_free; + } + + mutex_init(&charger->charger_mutex); + mutex_init(&charger->mode_mutex); + mutex_init(&charger->icl_mutex); + mutex_init(&charger->irq_aicl_mutex); + + charger->dev = &pdev->dev; + charger->i2c = max77775->charger; + charger->pmic_i2c = max77775->i2c; + charger->pdata = charger_data; + charger->aicl_curr = 0; + charger->slow_charging = false; + charger->otg_on = false; + charger->uno_on = false; + charger->max77775_pdata = pdata; + charger->wc_pre_current = WC_CURRENT_START; + charger->cable_type = SEC_BATTERY_CABLE_NONE; + charger->hp_otg = false; + charger->bypass_mode = false; + charger->bat_det = true; + charger->dpm_last_icl = 100; + charger->cv_mode_check = false; + charger->ari_cnt = 0; + + atomic_set(&charger->shutdown_cnt, 0); + +#if defined(CONFIG_OF) + ret = max77775_charger_parse_dt(charger); + if (ret < 0) + pr_err("%s not found charger dt! ret[%d]\n", __func__, ret); +#endif + platform_set_drvdata(pdev, charger); + + max77775_charger_initialize(charger); + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_INT_OK, ®_data); + if (reg_data & MAX77775_WCIN_OK) +#if defined(CONFIG_USE_POGO) + charger->cable_type = SEC_BATTERY_CABLE_POGO; +#else + charger->cable_type = SEC_BATTERY_CABLE_WIRELESS; +#endif + charger->input_current = max77775_get_input_current(charger); + charger->charging_current = max77775_get_charge_current(charger); + + if (max77775_read_reg(max77775->i2c, MAX77775_PMIC_REG_PMICREV, ®_data) < 0) { + pr_err("device not found on this channel (this is not an error)\n"); + ret = -ENOMEM; + goto err_pdata_free; + } else { + charger->pmic_ver = (reg_data & 0x7); + pr_info("%s : device found : ver.0x%x\n", __func__, charger->pmic_ver); + } + + (void)debugfs_create_file("max77775-regs", + S_IRUGO, NULL, (void *)charger, + &max77775_debugfs_fops); + + charger->wqueue = create_singlethread_workqueue(dev_name(&pdev->dev)); + if (!charger->wqueue) { + pr_err("%s: Fail to Create Workqueue\n", __func__); + goto err_pdata_free; + } +#if defined(CONFIG_USE_POGO) + charger->wcin_det_ws = wakeup_source_register(&pdev->dev, "charger-wcin-det"); +#endif + charger->aicl_ws = wakeup_source_register(&pdev->dev, "charger-aicl"); + charger->wc_current_ws = wakeup_source_register(&pdev->dev, "charger->wc-current"); + charger->otg_ws = wakeup_source_register(&pdev->dev, "charger->otg"); + charger->wc_chg_current_ws = wakeup_source_register(&pdev->dev, "charger->wc-chg-current"); + charger->set_icl_wcin_otg_ws = wakeup_source_register(&pdev->dev, "charger->sec-icl-wcin-otg"); + +#if defined(CONFIG_USE_POGO) + INIT_DELAYED_WORK(&charger->wcin_det_work, max77775_wcin_det_work); +#endif + INIT_DELAYED_WORK(&charger->aicl_work, max77775_aicl_isr_work); + INIT_DELAYED_WORK(&charger->wc_current_work, max77775_wc_current_work); + INIT_DELAYED_WORK(&charger->wc_chg_current_work, max77775_wc_chg_current_work); + INIT_DELAYED_WORK(&charger->set_icl_wcin_otg_work, set_icl_wcin_otg_work); + + charger_cfg.drv_data = charger; + charger->psy_chg = power_supply_register(&pdev->dev, + &max77775_charger_power_supply_desc, &charger_cfg); + if (IS_ERR(charger->psy_chg)) { + ret = PTR_ERR(charger->psy_chg); + pr_err("%s: Failed to Register psy_chg(%d)\n", __func__, ret); + goto err_power_supply_register; + } + + charger->psy_otg = power_supply_register(&pdev->dev, + &max77775_otg_power_supply_desc, &charger_cfg); + if (IS_ERR(charger->psy_otg)) { + ret = PTR_ERR(charger->psy_otg); + pr_err("%s: Failed to Register otg_chg(%d)\n", __func__, ret); + goto err_power_supply_register_otg; + } + charger->psy_otg->supplied_to = max77775_otg_supply_list; + charger->psy_otg->num_supplicants = ARRAY_SIZE(max77775_otg_supply_list); + + if (charger->pdata->chg_irq) { + INIT_DELAYED_WORK(&charger->isr_work, max77775_chg_isr_work); + + ret = request_threaded_irq(charger->pdata->chg_irq, NULL, + max77775_chg_irq_thread, 0, + "charger-irq", charger); + if (ret) { + pr_err("%s: Failed to Request IRQ\n", __func__); + goto err_irq; + } + + ret = enable_irq_wake(charger->pdata->chg_irq); + if (ret < 0) + pr_err("%s: Failed to Enable Wakeup Source(%d)\n", __func__, ret); + } + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_INT_OK, ®_data); + + charger->irq_bypass = pdata->irq_base + MAX77775_CHG_IRQ_BYP_I; + ret = request_threaded_irq(charger->irq_bypass, NULL, + max77775_bypass_irq, 0, + "bypass-irq", charger); + if (ret < 0) + pr_err("%s: fail to request bypass IRQ: %d: %d\n", + __func__, charger->irq_bypass, ret); + + charger->irq_batp = pdata->irq_base + MAX77775_CHG_IRQ_BATP_I; + ret = request_threaded_irq(charger->irq_batp, NULL, + max77775_batp_irq, 0, + "batp-irq", charger); + if (ret < 0) + pr_err("%s: fail to request Battery Present IRQ: %d: %d\n", + __func__, charger->irq_batp, ret); + +#if defined(CONFIG_MAX77775_CHECK_B2SOVRC) + if ((sec_debug_get_debug_level() & 0x1) == 0x1) { + /* only work for debug level is mid */ + charger->irq_bat = pdata->irq_base + MAX77775_CHG_IRQ_BAT_I; + ret = request_threaded_irq(charger->irq_bat, NULL, + max77775_bat_irq, 0, + "bat-irq", charger); + if (ret < 0) + pr_err("%s: fail to request Battery IRQ: %d: %d\n", + __func__, charger->irq_bat, ret); + } +#endif + +#if defined(CONFIG_USE_POGO) + if (charger->pdata->factory_wcin_irq || charger->pdata->user_wcin_irq) { + charger->irq_wcin = pdata->irq_base + MAX77775_CHG_IRQ_WCIN_I; + ret = request_threaded_irq(charger->irq_wcin, + NULL, max77775_wcin_irq, + IRQF_TRIGGER_FALLING, "wcin-irq", charger); + if (ret < 0) + pr_err("%s: fail to request wcin det IRQ: %d: %d\n", + __func__, charger->irq_wcin, ret); + } +#endif + + if (charger->pdata->enable_sysovlo_irq) { + charger->sysovlo_ws = wakeup_source_register(&pdev->dev, "max77775-sysovlo"); + /* Enable BIAS */ + max77775_update_reg(max77775->i2c, MAX77775_PMIC_REG_MAINCTRL1, + 0x80, 0x80); + /* set IRQ thread */ + charger->irq_sysovlo = + pdata->irq_base + MAX77775_SYSTEM_IRQ_SYSOVLO_INT; + ret = request_threaded_irq(charger->irq_sysovlo, NULL, + max77775_sysovlo_irq, 0, + "sysovlo-irq", charger); + if (ret < 0) + pr_err("%s: fail to request sysovlo IRQ: %d: %d\n", + __func__, charger->irq_sysovlo, ret); + enable_irq_wake(charger->irq_sysovlo); + } + + ret = max77775_chg_create_attrs(&charger->psy_chg->dev); + if (ret) { + dev_err(charger->dev, "%s : Failed to max77775_chg_create_attrs\n", __func__); + goto err_atts; + } + + /* watchdog kick */ + max77775_chg_set_wdtmr_kick(charger); + + sec_chg_set_dev_init(SC_DEV_MAIN_CHG); + + pr_info("%s: MAX77775 Charger Driver Loaded\n", __func__); + + return 0; + +err_atts: + free_irq(charger->pdata->chg_irq, charger); +err_irq: + power_supply_unregister(charger->psy_otg); +err_power_supply_register_otg: + power_supply_unregister(charger->psy_chg); +err_power_supply_register: + destroy_workqueue(charger->wqueue); + wakeup_source_unregister(charger->sysovlo_ws); + wakeup_source_unregister(charger->otg_ws); + wakeup_source_unregister(charger->wc_current_ws); + wakeup_source_unregister(charger->aicl_ws); + wakeup_source_unregister(charger->wc_chg_current_ws); + wakeup_source_unregister(charger->set_icl_wcin_otg_ws); +#if defined(CONFIG_USE_POGO) + wakeup_source_unregister(charger->wcin_det_ws); +#endif +err_pdata_free: + kfree(charger_data); +err_free: + kfree(charger); + + return ret; +} + +static int max77775_charger_remove(struct platform_device *pdev) +{ + struct max77775_charger_data *charger = platform_get_drvdata(pdev); + + pr_info("%s: ++\n", __func__); + + destroy_workqueue(charger->wqueue); + + if (charger->i2c) { + u8 reg_data; + + reg_data = MAX77775_MODE_4_BUCK_ON; + max77775_write_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, reg_data); + reg_data = 0x0F; + max77775_write_reg(charger->i2c, MAX77775_CHG_REG_CNFG_09, reg_data); + reg_data = 0x10; + max77775_write_reg(charger->i2c, MAX77775_CHG_REG_CNFG_10, reg_data); + reg_data = 0x60; + max77775_write_reg(charger->i2c, MAX77775_CHG_REG_CNFG_12, reg_data); + } else { + pr_err("%s: no max77775 i2c client\n", __func__); + } + + if (charger->irq_sysovlo) + free_irq(charger->irq_sysovlo, charger); +#if defined(CONFIG_USE_POGO) + if (charger->irq_wcin) + free_irq(charger->irq_wcin, charger); +#endif + if (charger->pdata->chg_irq) + free_irq(charger->pdata->chg_irq, charger); + if (charger->psy_chg) + power_supply_unregister(charger->psy_chg); + if (charger->psy_otg) + power_supply_unregister(charger->psy_otg); + + wakeup_source_unregister(charger->sysovlo_ws); + wakeup_source_unregister(charger->otg_ws); + wakeup_source_unregister(charger->wc_current_ws); + wakeup_source_unregister(charger->aicl_ws); + wakeup_source_unregister(charger->wc_chg_current_ws); + wakeup_source_unregister(charger->set_icl_wcin_otg_ws); +#if defined(CONFIG_USE_POGO) + wakeup_source_unregister(charger->wcin_det_ws); +#endif + + kfree(charger); + + pr_info("%s: --\n", __func__); + + return 0; +} + +#if defined CONFIG_PM +static int max77775_charger_prepare(struct device *dev) +{ + struct max77775_charger_data *charger = dev_get_drvdata(dev); + + pr_info("%s\n", __func__); + + if ((charger->cable_type == SEC_BATTERY_CABLE_USB || + charger->cable_type == SEC_BATTERY_CABLE_TA) + && charger->input_current >= 500) { + u8 reg_data; + + max77775_read_reg(charger->i2c, MAX77775_CHG_REG_CNFG_09, ®_data); + reg_data &= MAX77775_CHG_CHGIN_LIM; + max77775_usbc_icurr_autoibus_on(reg_data); + } + + return 0; +} + +static int max77775_charger_suspend(struct device *dev) +{ + return 0; +} + +static int max77775_charger_resume(struct device *dev) +{ + return 0; +} + +static void max77775_charger_complete(struct device *dev) +{ + struct max77775_charger_data *charger = dev_get_drvdata(dev); + + pr_info("%s\n", __func__); + + if (!max77775_get_autoibus(charger)) + max77775_set_fw_noautoibus(MAX77775_AUTOIBUS_AT_OFF); +} +#else +#define max77775_charger_prepare NULL +#define max77775_charger_suspend NULL +#define max77775_charger_resume NULL +#define max77775_charger_complete NULL +#endif + +static void max77775_charger_shutdown(struct platform_device *pdev) +{ + struct max77775_charger_data *charger = platform_get_drvdata(pdev); + + pr_info("%s: ++\n", __func__); + + atomic_inc(&charger->shutdown_cnt); + + if (max77775_get_facmode()) + goto free_chg; /* prevent SMPL during SMD ARRAY shutdown */ + + if (charger->i2c) { + u8 reg_data; + + reg_data = MAX77775_MODE_4_BUCK_ON; /* Buck on, Charge off */ + max77775_write_reg(charger->i2c, MAX77775_CHG_REG_CNFG_00, reg_data); +#if !defined(CONFIG_SEC_FACTORY) + if ((is_wired_type(charger->cable_type)) + && (charger->cable_type != SEC_BATTERY_CABLE_USB)) + reg_data = 0x3B; /* CHGIN_ILIM 1500mA */ + else +#endif + reg_data = 0x13; /* CHGIN_ILIM 500mA */ + max77775_write_reg(charger->i2c, MAX77775_CHG_REG_CNFG_09, reg_data); + reg_data = 0x13; /* WCIN_ILIM 500mA */ + max77775_write_reg(charger->i2c, MAX77775_CHG_REG_CNFG_10, reg_data); + reg_data = 0x60; /* CHGINSEL/WCINSEL enable */ + max77775_write_reg(charger->i2c, MAX77775_CHG_REG_CNFG_12, reg_data); + +#if defined(CONFIG_SHIPMODE_BY_VBAT) && !defined(CONFIG_SEC_FACTORY) + /* case with stray voltage due to TA connection */ + if (!is_nocharge_type(charger->cable_type) || lpcharge) { + if (max77775_check_current_level()) + max77775_check_auto_shipmode_level(charger, 2); + else + max77775_check_auto_shipmode_level(charger, 1); + } else + max77775_check_auto_shipmode_level(charger, 0); +#else + max77775_set_auto_ship_mode(charger, 1); +#endif + } else { + pr_err("%s: no max77775 i2c client\n", __func__); + } + +free_chg: + if (charger->irq_aicl) + free_irq(charger->irq_aicl, charger); + if (charger->irq_chgin) + free_irq(charger->irq_chgin, charger); + if (charger->irq_bypass) + free_irq(charger->irq_bypass, charger); + if (charger->irq_batp) + free_irq(charger->irq_batp, charger); +#if defined(CONFIG_MAX77775_CHECK_B2SOVRC) + if (charger->irq_bat) + free_irq(charger->irq_bat, charger); +#endif + if (charger->irq_sysovlo) + free_irq(charger->irq_sysovlo, charger); + if (charger->pdata->chg_irq) { + free_irq(charger->pdata->chg_irq, charger); + cancel_delayed_work(&charger->isr_work); + } + + cancel_delayed_work(&charger->aicl_work); + cancel_delayed_work(&charger->wc_current_work); + cancel_delayed_work(&charger->wc_chg_current_work); +#if defined(CONFIG_USE_POGO) + cancel_delayed_work(&charger->wcin_det_work); +#endif + cancel_delayed_work(&charger->set_icl_wcin_otg_work); + + pr_info("%s: --\n", __func__); +} + +static const struct dev_pm_ops max77775_charger_pm_ops = { + .prepare = max77775_charger_prepare, + .suspend = max77775_charger_suspend, + .resume = max77775_charger_resume, + .complete = max77775_charger_complete, +}; + +static struct platform_driver max77775_charger_driver = { + .driver = { + .name = "max77775-charger", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &max77775_charger_pm_ops, +#endif + }, + .probe = max77775_charger_probe, + .remove = max77775_charger_remove, + .shutdown = max77775_charger_shutdown, +}; + +static int __init max77775_charger_init(void) +{ + pr_info("%s:\n", __func__); + return platform_driver_register(&max77775_charger_driver); +} + +static void __exit max77775_charger_exit(void) +{ + platform_driver_unregister(&max77775_charger_driver); +} + +module_init(max77775_charger_init); +module_exit(max77775_charger_exit); + +MODULE_DESCRIPTION("Samsung MAX77775 Charger Driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/battery/charger/max77775_charger/max77775_charger.dtsi b/drivers/battery/charger/max77775_charger/max77775_charger.dtsi new file mode 100644 index 000000000000..e2b9b7b878fe --- /dev/null +++ b/drivers/battery/charger/max77775_charger/max77775_charger.dtsi @@ -0,0 +1,20 @@ +&smd { + max77775_charger: max77775-charger { + charger,fac_vsys = <3800>; + }; + + battery { + battery,fgsrc_switch_name = "max77775-charger"; + battery,otg_name = "max77775-otg"; + }; +}; + + +/* /home/dpi/qb5_8814/workspace/P4_1716/android/kernel_platform/kmodule/battery/stable/eureka/charger/max77775/max77775_charger.e3q.dtsi */ +&max77775_charger { + charger,enable_sysovlo_irq; + charger,fac_vsys = <4400>; + + charger,fsw = <2>; /* 1.5MHz */ + charger,enable_noise_wa; +}; diff --git a/drivers/battery/charger/max77775_charger/max77775_charger.h b/drivers/battery/charger/max77775_charger/max77775_charger.h new file mode 100644 index 000000000000..b83dd8b70fd8 --- /dev/null +++ b/drivers/battery/charger/max77775_charger/max77775_charger.h @@ -0,0 +1,452 @@ +/* + * max77775_charger.h + * Samsung max77775 Charger Header + * + * Copyright (C) 2015 Samsung Electronics, 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 __MAX77775_CHARGER_H +#define __MAX77775_CHARGER_H __FILE__ + +#include +#include +#include +#include +#include +#include "../../common/sec_charging_common.h" +#include + +enum { + CHIP_ID = 0, + DATA, +}; + +enum { + SHIP_MODE_DISABLE = 0, + SHIP_MODE_EN_OP, + SHIP_MODE_EN, +}; + +enum { + CHGIN_SEL = 0, + WCIN_SEL, + WC_CHG_ALL_ON, + WC_CHG_ALL_OFF, +}; + +ssize_t max77775_chg_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf); + +ssize_t max77775_chg_store_attrs(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +#define MAX77775_CHARGER_ATTR(_name) \ +{ \ + .attr = {.name = #_name, .mode = 0664}, \ + .show = max77775_chg_show_attrs, \ + .store = max77775_chg_store_attrs, \ +} + +#define MAX77775_CHG_SAFEOUT2 0x80 + +/* MAX77775_CHG_REG_CHG_INT */ +#define MAX77775_BYP_I (1 << 0) +#define MAX77775_INP_LIMIT_I (1 << 1) +#define MAX77775_BATP_I (1 << 2) +#define MAX77775_BAT_I (1 << 3) +#define MAX77775_CHG_I (1 << 4) +#define MAX77775_WCIN_I (1 << 5) +#define MAX77775_CHGIN_I (1 << 6) +#define MAX77775_AICL_I (1 << 7) + +/* MAX77775_CHG_REG_CHG_INT_MASK */ +#define MAX77775_BYP_IM (1 << 0) +#define MAX77775_INP_LIMIT_IM (1 << 1) +#define MAX77775_BATP_IM (1 << 2) +#define MAX77775_BAT_IM (1 << 3) +#define MAX77775_CHG_IM (1 << 4) +#define MAX77775_WCIN_IM (1 << 5) +#define MAX77775_CHGIN_IM (1 << 6) +#define MAX77775_AICL_IM (1 << 7) + +/* MAX77775_CHG_REG_CHG_INT_OK */ +#define MAX77775_BYP_OK 0x01 +#define MAX77775_BYP_OK_SHIFT 0 +#define MAX77775_DISQBAT_OK 0x02 +#define MAX77775_DISQBAT_OK_SHIFT 1 +#define MAX77775_BATP_OK 0x04 +#define MAX77775_BATP_OK_SHIFT 2 +#define MAX77775_BAT_OK 0x08 +#define MAX77775_BAT_OK_SHIFT 3 +#define MAX77775_CHG_OK 0x10 +#define MAX77775_CHG_OK_SHIFT 4 +#define MAX77775_WCIN_OK 0x20 +#define MAX77775_WCIN_OK_SHIFT 5 +#define MAX77775_CHGIN_OK 0x40 +#define MAX77775_CHGIN_OK_SHIFT 6 +#define MAX77775_AICL_OK 0x80 +#define MAX77775_AICL_OK_SHIFT 7 +#define MAX77775_DETBAT 0x04 +#define MAX77775_DETBAT_SHIFT 2 + +/* MAX77775_CHG_REG_CHG_DTLS_00 */ +#define MAX77775_BATP_DTLS 0x01 +#define MAX77775_BATP_DTLS_SHIFT 0 +#define MAX77775_WCIN_DTLS 0x18 +#define MAX77775_WCIN_DTLS_SHIFT 3 +#define MAX77775_CHGIN_DTLS 0x60 +#define MAX77775_CHGIN_DTLS_SHIFT 5 +#define MAX77775_SPSN_DTLS 0x06 +#define MAX77775_SPSN_DTLS_SHIFT 1 + +/* MAX77775_CHG_REG_CHG_DTLS_01 */ +#define MAX77775_CHG_DTLS 0x0F +#define MAX77775_CHG_DTLS_SHIFT 0 +#define MAX77775_BAT_DTLS 0x70 +#define MAX77775_BAT_DTLS_SHIFT 4 + +/* MAX77775_CHG_REG_CHG_DTLS_02 */ +#define MAX77775_BYP_DTLS 0x0F +#define MAX77775_BYP_DTLS_SHIFT 0 +#define MAX77775_BYP_DTLS0 0x1 +#define MAX77775_BYP_DTLS1 0x2 +#define MAX77775_BYP_DTLS2 0x4 +#define MAX77775_BYP_DTLS3 0x8 + +#if 1 +/* MAX77775_CHG_REG_CHG_CNFG_00 */ +#define CHG_CNFG_00_MODE_SHIFT 0 +#define CHG_CNFG_00_CHG_SHIFT 0 +#define CHG_CNFG_00_UNO_SHIFT 1 +#define CHG_CNFG_00_OTG_SHIFT 1 +#define CHG_CNFG_00_BUCK_SHIFT 2 +#define CHG_CNFG_00_BOOST_SHIFT 3 +#define CHG_CNFG_00_WDTEN_SHIFT 4 +#define CHG_CNFG_00_MODE_MASK (0x0F << CHG_CNFG_00_MODE_SHIFT) +#define CHG_CNFG_00_CHG_MASK (1 << CHG_CNFG_00_CHG_SHIFT) +#define CHG_CNFG_00_UNO_MASK (1 << CHG_CNFG_00_UNO_SHIFT) +#define CHG_CNFG_00_OTG_MASK (1 << CHG_CNFG_00_OTG_SHIFT) +#define CHG_CNFG_00_BUCK_MASK (1 << CHG_CNFG_00_BUCK_SHIFT) +#define CHG_CNFG_00_BOOST_MASK (1 << CHG_CNFG_00_BOOST_SHIFT) +#define CHG_CNFG_00_WDTEN_MASK (1 << CHG_CNFG_00_WDTEN_SHIFT) +#define CHG_CNFG_00_UNO_CTRL (CHG_CNFG_00_UNO_MASK | CHG_CNFG_00_BOOST_MASK) +#define CHG_CNFG_00_OTG_CTRL (CHG_CNFG_00_OTG_MASK | CHG_CNFG_00_BOOST_MASK) +#define MAX77775_MODE_DEFAULT 0x04 +#define MAX77775_MODE_CHGR 0x01 +#define MAX77775_MODE_UNO 0x01 +#define MAX77775_MODE_OTG 0x02 +#define MAX77775_MODE_BUCK 0x04 +#define MAX77775_MODE_BOOST 0x08 +#endif +#define CHG_CNFG_00_SPSN_DET_EN_SHIFT 7 +#define CHG_CNFG_00_SPSN_DET_EN_MASK (1 << CHG_CNFG_00_SPSN_DET_EN_SHIFT) +#define MAX77775_SPSN_DET_ENABLE 0x01 +#define MAX77775_SPSN_DET_DISABLE 0x00 + +/* MAX77775_CHG_REG_CHG_CNFG_00 MODE[3:0] */ +#define MAX77775_MODE_0_ALL_OFF 0x0 +#define MAX77775_MODE_1_ALL_OFF 0x1 +#define MAX77775_MODE_2_ALL_OFF 0x2 +#define MAX77775_MODE_3_ALL_OFF 0x3 +#define MAX77775_MODE_4_BUCK_ON 0x4 +#define MAX77775_MODE_5_BUCK_CHG_ON 0x5 +#define MAX77775_MODE_6_BUCK_CHG_ON 0x6 +#define MAX77775_MODE_7_BUCK_ON 0x7 +#define MAX77775_MODE_8_BOOST_UNO_ON 0x8 +#define MAX77775_MODE_9_BOOST_ON 0x9 +#define MAX77775_MODE_A_BOOST_OTG_ON 0xA +#define MAX77775_MODE_B_BOOST_OTG_UNO_ON 0xB +#define MAX77775_MODE_C_BUCK_BOOST_UNO_ON 0xC +#define MAX77775_MODE_D_BUCK_CHG_BOOST_UNO_ON 0xD +#define MAX77775_MODE_E_BUCK_BOOST_OTG_ON 0xE +#define MAX77775_MODE_F_BUCK_CHG_BOOST_OTG_ON 0xF + +/* MAX77775_CHG_REG_CHG_CNFG_01 */ +#define CHG_CNFG_01_FCHGTIME_SHIFT 0 +#define CHG_CNFG_01_FCHGTIME_MASK (0x7 << CHG_CNFG_01_FCHGTIME_SHIFT) +#define MAX77775_FCHGTIME_DISABLE 0x0 + +#define CHG_CNFG_01_RECYCLE_EN_SHIFT 3 +#define CHG_CNFG_01_RECYCLE_EN_MASK (0x1 << CHG_CNFG_01_RECYCLE_EN_SHIFT) +#define MAX77775_RECYCLE_EN_ENABLE 0x1 + +#define CHG_CNFG_01_CHG_RSTRT_SHIFT 4 +#define CHG_CNFG_01_CHG_RSTRT_MASK (0x3 << CHG_CNFG_01_CHG_RSTRT_SHIFT) +#define MAX77775_CHG_RSTRT_DISABLE 0x3 + +#define CHG_CNFG_01_VTRICKLE_EN_SHIFT 7 +#define CHG_CNFG_01_VTRICKLE_EN_MASK (0x1 << CHG_CNFG_01_VTRICKLE_EN_SHIFT) +#define MAX77775_CHG_VTRICKLE_EN_DISABLE 0x0 +#define MAX77775_CHG_VTRICKLE_EN_ENABLE 0x1 + +/* MAX77775_CHG_REG_CHG_CNFG_02 */ +#define CHG_CNFG_02_OTG_ILIM_SHIFT 6 +#define CHG_CNFG_02_OTG_ILIM_MASK (0x3 << CHG_CNFG_02_OTG_ILIM_SHIFT) +#define MAX77775_OTG_ILIM_500 0x0 +#define MAX77775_OTG_ILIM_900 0x1 +#define MAX77775_OTG_ILIM_1200 0x2 +#define MAX77775_OTG_ILIM_1500 0x3 +#define MAX77775_CHG_CC 0x3F + +/* MAX77775_CHG_REG_CHG_CNFG_03 */ +#define CHG_CNFG_03_TO_ITH_SHIFT 0 +#define CHG_CNFG_03_TO_ITH_MASK (0xF << CHG_CNFG_03_TO_ITH_SHIFT) +#define MAX77775_TO_ITH_150MA 0x0 + +#define CHG_CNFG_03_TO_TIME_SHIFT 4 +#define CHG_CNFG_03_TO_TIME_MASK (0x3 << CHG_CNFG_03_TO_TIME_SHIFT) +#define MAX77775_TO_TIME_30M 0x0 +#define MAX77775_TO_TIME_40M 0x1 +#define MAX77775_TO_TIME_50M 0x2 +#define MAX77775_TO_TIME_70M 0x3 + +#define CHG_CNFG_03_SYS_TRACK_DIS_SHIFT 7 +#define CHG_CNFG_03_SYS_TRACK_DIS_MASK (0x1 << CHG_CNFG_03_SYS_TRACK_DIS_SHIFT) +#define MAX77775_SYS_TRACK_ENABLE 0x0 +#define MAX77775_SYS_TRACK_DISABLE 0x1 + +/* MAX77775_CHG_REG_CHG_CNFG_04 */ +#define MAX77775_CHG_MINVSYS_MASK 0xC0 +#define MAX77775_CHG_MINVSYS_SHIFT 6 +#define MAX77775_CHG_PRM_MASK 0x1F +#define MAX77775_CHG_PRM_SHIFT 0 + +#define CHG_CNFG_04_CHG_CV_PRM_SHIFT 0 +#define CHG_CNFG_04_CHG_CV_PRM_MASK (0x3F << CHG_CNFG_04_CHG_CV_PRM_SHIFT) + +/* MAX77775_CHG_REG_CHG_CNFG_05 */ +#define CHG_CNFG_05_REG_B2SOVRC_SHIFT 0 +#define CHG_CNFG_05_REG_B2SOVRC_MASK (0xF << CHG_CNFG_05_REG_B2SOVRC_SHIFT) +#define MAX77775_B2SOVRC_DISABLE 0x0 + +#define CHG_CNFG_05_REG_UNOILIM_SHIFT 4 +#define CHG_CNFG_05_REG_UNOILIM_MASK (0x7 << CHG_CNFG_05_REG_UNOILIM_SHIFT) +#define MAX77775_UNOILIM_600 0x1 +#define MAX77775_UNOILIM_800 0x2 +#define MAX77775_UNOILIM_1000 0x3 +#define MAX77775_UNOILIM_1200 0x4 +#define MAX77775_UNOILIM_1400 0x5 +#define MAX77775_UNOILIM_1600 0x6 +#define MAX77775_UNOILIM_2000 0x7 + + +/* MAX77775_CHG_CNFG_06 */ +#define CHG_CNFG_06_WDTCLR_SHIFT 0 +#define CHG_CNFG_06_WDTCLR_MASK (0x3 << CHG_CNFG_06_WDTCLR_SHIFT) +#define MAX77775_WDTCLR 0x01 +#define CHG_CNFG_06_DIS_AICL_SHIFT 4 +#define CHG_CNFG_06_DIS_AICL_MASK (0x1 << CHG_CNFG_06_DIS_AICL_SHIFT) +#define MAX77775_DIS_AICL 0x0 +#define CHG_CNFG_06_B2SOVRC_DTC_SHIFT 7 +#define CHG_CNFG_06_B2SOVRC_DTC_MASK (0x1 << CHG_CNFG_06_B2SOVRC_DTC_SHIFT) +#define MAX77775_B2SOVRC_DTC_100MS 0x1 + +/* MAX77775_CHG_REG_CHG_CNFG_07 */ +#define MAX77775_CHG_FMBST 0x04 +#define CHG_CNFG_07_REG_FMBST_SHIFT 2 +#define CHG_CNFG_07_REG_FMBST_MASK (0x1 << CHG_CNFG_07_REG_FMBST_SHIFT) +#define CHG_CNFG_07_REG_FGSRC_SHIFT 1 +#define CHG_CNFG_07_REG_FGSRC_MASK (0x1 << CHG_CNFG_07_REG_FGSRC_SHIFT) +#define CHG_CNFG_07_REG_SHIPMODE_SHIFT 0 +#define CHG_CNFG_07_REG_SHIPMODE_MASK (0x1 << CHG_CNFG_07_REG_SHIPMODE_SHIFT) + +/* MAX77775_CHG_REG_CHG_CNFG_08 */ +#define CHG_CNFG_08_REG_FSW_SHIFT 0 +#define CHG_CNFG_08_REG_FSW_MASK (0x3 << CHG_CNFG_08_REG_FSW_SHIFT) +#define MAX77775_CHG_FSW_3MHz 0x00 +#define MAX77775_CHG_FSW_2MHz 0x01 +#define MAX77775_CHG_FSW_1_5MHz 0x02 +#define CHG_CNFG_08_REG_VOTG_SHIFT 4 +#define CHG_CNFG_08_REG_VOTG_MASK (0x3 << CHG_CNFG_08_REG_VOTG_SHIFT) +#define CHG_CNFG_08_REG_SPSN_DET_SHIFT 2 +#define CHG_CNFG_08_REG_SPSN_DET_MASK (0x3 << CHG_CNFG_08_REG_SPSN_DET_SHIFT) +#define MAX77775_CHG_SPSN_1K 0x00 +#define MAX77775_CHG_SPSN_10K 0x01 +#define MAX77775_CHG_SPSN_100K 0x02 +#define MAX77775_CHG_SPSN_OPEN 0x03 +#define CHG_CNFG_08_BYPASS_MODE_SHIFT 7 +#define CHG_CNFG_08_BYPASS_MODE_MASK (0x1 << CHG_CNFG_08_BYPASS_MODE_SHIFT) + +/* MAX77775_CHG_REG_CHG_CNFG_09 */ +#define MAX77775_CHG_CHGIN_LIM 0x7F +#define MAX77775_CHG_EN 0x80 + +/* MAX77775_CHG_REG_CHG_CNFG_10 */ +#define MAX77775_CHG_WCIN_LIM 0x3F + +/* MAX77775_CHG_REG_CHG_CNFG_11 */ +#define CHG_CNFG_11_VBYPSET_SHIFT 0 +#define CHG_CNFG_11_VBYPSET_MASK (0xFF << CHG_CNFG_11_VBYPSET_SHIFT) + +/* MAX77775_CHG_REG_CHG_CNFG_12 */ +#define MAX77775_CHG_WCINSEL 0x40 +#define CHG_CNFG_12_CHGINSEL_SHIFT 5 +#define CHG_CNFG_12_CHGINSEL_MASK (0x1 << CHG_CNFG_12_CHGINSEL_SHIFT) +#define CHG_CNFG_12_WCINSEL_SHIFT 6 +#define CHG_CNFG_12_WCINSEL_MASK (0x1 << CHG_CNFG_12_WCINSEL_SHIFT) +#define CHG_CNFG_12_VCHGIN_REG_MASK (0x3 << 3) +#define CHG_CNFG_12_WCIN_REG_MASK (0x3 << 1) +#define CHG_CNFG_12_REG_DISKIP_SHIFT 0 +#define CHG_CNFG_12_REG_DISKIP_MASK (0x1 << CHG_CNFG_12_REG_DISKIP_SHIFT) +#define MAX77775_DISABLE_SKIP 0x1 +#define MAX77775_AUTO_SKIP 0x0 + +/* MAX77775_CHG_REG_CHG_CNFG_13 */ +#define CHG_CNFG_13_AUTOSHIP_TH_SHIFT 6 +#define CHG_CNFG_13_AUTOSHIP_TH_MASK (0x3 << CHG_CNFG_13_AUTOSHIP_TH_SHIFT) +#define CHG_CNFG_13_SHIP_EXT_DB_SHIFT 4 +#define CHG_CNFG_13_SHIP_EXT_DB_MASK (0x3 << CHG_CNFG_13_SHIP_EXT_DB_SHIFT) +#define CHG_CNFG_13_REG_AUTO_SHIPMODE_SHIFT 3 +#define CHG_CNFG_13_REG_AUTO_SHIPMODE_MASK (0x1 << CHG_CNFG_13_REG_AUTO_SHIPMODE_SHIFT) +#define CHG_CNFG_13_BYPASS_AUTOSHIP_EN_SHIFT 2 +#define CHG_CNFG_13_BYPASS_AUTOSHIP_EN_MASK (0x1 << CHG_CNFG_13_BYPASS_AUTOSHIP_EN_SHIFT) +#define CHG_CNFG_13_SHIP_ENTRY_DB_MASK 0x3 + +/* MAX77775_CHG_REG_CHG_CNFG_17 */ +#define CHG_CNFG_17_BYPASS_MODE_WR_EN_SHIFT 0 +#define CHG_CNFG_17_BYPASS_MODE_WR_EN_MASK (0x1 << CHG_CNFG_17_BYPASS_MODE_WR_EN_SHIFT) + +#define REDUCE_CURRENT_STEP 100 +#define MINIMUM_INPUT_CURRENT 300 + +#define WC_CURRENT_STEP 100 +#define WC_CURRENT_START 480 + +#define WC_DEFAULT_CURRENT 0x10 + +#define DPM_MISC 0x4000 /* BATT_MISC_EVENT_DIRECT_POWER_MODE = 0x00004000 */ + +typedef struct max77775_charger_platform_data { + /* wirelss charger */ + char *wireless_charger_name; + int wireless_cc_cv; + int wc_current_step; + unsigned int nv_wc_headroom; + + /* float voltage (mV) */ + int chg_float_voltage; + int chg_irq; + unsigned int chg_ocp_current; + unsigned int chg_ocp_dtc; + unsigned int topoff_time; + int fac_vsys; + bool enable_noise_wa; + bool factory_wcin_irq; + bool user_wcin_irq; + bool enable_sysovlo_irq; + bool boosting_voltage_aicl; + int fsw; + bool enable_dpm; + int disqbat; + int dpm_icl; + + /* OVP/UVLO check */ + int ovp_uvlo_check_type; + /* 1st full check */ + int full_check_type; + /* 2nd full check */ + int full_check_type_2nd; + +} max77775_charger_platform_data_t; + +struct max77775_charger_data { + struct device *dev; + struct i2c_client *i2c; + struct i2c_client *pmic_i2c; + + struct max77775_platform_data *max77775_pdata; + + struct power_supply *psy_chg; + struct power_supply *psy_otg; + + atomic_t shutdown_cnt; + + struct workqueue_struct *wqueue; + struct delayed_work aicl_work; + struct delayed_work isr_work; + struct delayed_work wc_current_work; + struct delayed_work wc_chg_current_work; +#if defined(CONFIG_USE_POGO) + struct delayed_work wcin_det_work; +#endif + struct delayed_work set_icl_wcin_otg_work; + + /* mutex */ + struct mutex charger_mutex; + struct mutex mode_mutex; + struct mutex icl_mutex; + struct mutex irq_aicl_mutex; + + /* wakelock */ + struct wakeup_source *wc_current_ws; + struct wakeup_source *aicl_ws; + struct wakeup_source *otg_ws; + struct wakeup_source *sysovlo_ws; + struct wakeup_source *wc_chg_current_ws; +#if defined(CONFIG_USE_POGO) + struct wakeup_source *wcin_det_ws; +#endif + struct wakeup_source *set_icl_wcin_otg_ws; + + unsigned int is_charging; + unsigned int cable_type; + unsigned int input_current; + unsigned int charging_current; + unsigned int vbus_state; + int aicl_curr; + bool slow_charging; + int status; + int charge_mode; + u8 cnfg00_mode; + int fsw_now; + + bool bat_det; + int irq_bypass; + int irq_batp; +#if defined(CONFIG_MAX77775_CHECK_B2SOVRC) + int irq_bat; +#endif + int irq_chgin; + int irq_aicl; + int irq_sysovlo; +#if defined(CONFIG_USE_POGO) + int irq_wcin; +#endif + int wc_current; + int wc_pre_current; + + bool jig_low_active; + int jig_gpio; + + bool otg_on; + bool uno_on; + + int pmic_ver; + int float_voltage; + + int misalign_cnt; + bool hp_otg; + bool bypass_mode; + + int dpm_last_icl; + + bool cv_mode_check; + int ari_cnt; + bool spcom; + + max77775_charger_platform_data_t *pdata; +}; + +#endif /* __MAX77775_CHARGER_H */ diff --git a/drivers/battery/charger/pca9481_charger/Kconfig b/drivers/battery/charger/pca9481_charger/Kconfig new file mode 100644 index 000000000000..b85d5d883d53 --- /dev/null +++ b/drivers/battery/charger/pca9481_charger/Kconfig @@ -0,0 +1,19 @@ +config CHARGER_PCA9481 + tristate "PCA9481 charger driver" + default n + depends on DIRECT_CHARGING + help + Say Y or M here, + to enable support for the PCA9481 charger. + This is 2:1 switched capacitor direct charger. + To compile the driver as a module, choose M here. + +config SEND_PDMSG_IN_PPS_REQUEST_WORK + bool "PCA9481 send pdmsg in request work" + default n + help + Say Y here to enable + support for send pdmsg in direct charger. + This options for pdic not support periodic send pdmsg. + If this is Y in direct charger, direct charger send pdmsg periodic. + diff --git a/drivers/battery/charger/pca9481_charger/Makefile b/drivers/battery/charger/pca9481_charger/Makefile new file mode 100644 index 000000000000..bd2f7c56cd4b --- /dev/null +++ b/drivers/battery/charger/pca9481_charger/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_CHARGER_PCA9481) += pca9481_charger.o + +ccflags-y := -Wformat diff --git a/drivers/battery/charger/pca9481_charger/pca9481_charger.bzl b/drivers/battery/charger/pca9481_charger/pca9481_charger.bzl new file mode 100644 index 000000000000..ea341f0e3fee --- /dev/null +++ b/drivers/battery/charger/pca9481_charger/pca9481_charger.bzl @@ -0,0 +1,7 @@ +ko_list = [ + { + "ko_names" : [ + "drivers/battery/charger/pca9481_charger/pca9481_charger.ko" + ] + } +] diff --git a/drivers/battery/charger/pca9481_charger/pca9481_charger.c b/drivers/battery/charger/pca9481_charger/pca9481_charger.c new file mode 100644 index 000000000000..6e993a5be000 --- /dev/null +++ b/drivers/battery/charger/pca9481_charger/pca9481_charger.c @@ -0,0 +1,8332 @@ +/* + * Driver for the NXP PCA9481 battery charger. + * + * Copyright 2021-2023 NXP + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) +#include +#include "pca9481_charger.h" +#include "../../common/sec_charging_common.h" +#include "../../common/sec_direct_charger.h" +#include +#else +#include +#endif +#include +#ifdef CONFIG_USBPD_PHY_QCOM +#include // Use Qualcomm USBPD PHY +#endif + +#if defined(CONFIG_OF) +#include +#include +#endif /* CONFIG_OF */ + +#include +#ifdef CONFIG_USBPD_PHY_QCOM +#include // Use Qualcomm USBPD PHY +#endif + +#ifdef CONFIG_USBPD_PHY_QCOM +static int pca9481_usbpd_setup(struct pca9481_charger *pca9481); +#endif + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) +static int pca9481_send_pd_message(struct pca9481_charger *pca9481, unsigned int msg_type); + +/*******************************/ +/* Switching charger control function */ +/*******************************/ +char *charging_state_str[] = { + "NO_CHARGING", "CHECK_VBAT", "PRESET_DC", "CHECK_ACTIVE", "ADJUST_CC", + "START_CC", "CC_MODE", "START_CV", "CV_MODE", "CHARGING_DONE", + "ADJUST_TAVOL", "ADJUST_TACUR", "BYPASS_MODE", "DCMODE_CHANGE", + "REVERSE_MODE", "FPDO_CV_MODE", +}; + +static int pca9481_read_reg(struct pca9481_charger *pca9481, unsigned reg, void *val) +{ + int ret = 0; + + mutex_lock(&pca9481->i2c_lock); + ret = regmap_read(pca9481->regmap, reg, val); + mutex_unlock(&pca9481->i2c_lock); + if (ret < 0) + pr_info("%s: reg(0x%x), ret(%d)\n", __func__, reg, ret); + return ret; +} + +static int pca9481_bulk_read_reg(struct pca9481_charger *pca9481, int reg, void *val, int count) +{ + int ret = 0; + + mutex_lock(&pca9481->i2c_lock); + ret = regmap_bulk_read(pca9481->regmap, reg, val, count); + mutex_unlock(&pca9481->i2c_lock); + if (ret < 0) + pr_info("%s: reg(0x%x), ret(%d)\n", __func__, reg, ret); + return ret; +} + +static int pca9481_write_reg(struct pca9481_charger *pca9481, int reg, u8 val) +{ + int ret = 0; + + mutex_lock(&pca9481->i2c_lock); + ret = regmap_write(pca9481->regmap, reg, val); + mutex_unlock(&pca9481->i2c_lock); + if (ret < 0) + pr_info("%s: reg(0x%x), ret(%d)\n", __func__, reg, ret); + return ret; +} + +static int pca9481_update_reg(struct pca9481_charger *pca9481, int reg, u8 mask, u8 val) +{ + int ret = 0; + + mutex_lock(&pca9481->i2c_lock); + ret = regmap_update_bits(pca9481->regmap, reg, mask, val); + mutex_unlock(&pca9481->i2c_lock); + if (ret < 0) + pr_info("%s: reg(0x%x), ret(%d)\n", __func__, reg, ret); + return ret; +} + +static int pca9481_read_adc(struct pca9481_charger *pca9481, u8 adc_ch); + +static int pca9481_set_charging_state(struct pca9481_charger *pca9481, unsigned int charging_state) +{ + union power_supply_propval value = {0,}; + static int prev_val = DC_STATE_NO_CHARGING; + + pca9481->charging_state = charging_state; + + switch (charging_state) { + case DC_STATE_NO_CHARGING: + value.intval = SEC_DIRECT_CHG_MODE_DIRECT_OFF; + break; + case DC_STATE_CHECK_VBAT: + value.intval = SEC_DIRECT_CHG_MODE_DIRECT_CHECK_VBAT; + break; + case DC_STATE_PRESET_DC: + value.intval = SEC_DIRECT_CHG_MODE_DIRECT_PRESET; + break; + case DC_STATE_CHECK_ACTIVE: + case DC_STATE_START_CC: + case DC_STATE_START_CV: + case DC_STATE_ADJUST_TAVOL: + case DC_STATE_ADJUST_TACUR: + value.intval = SEC_DIRECT_CHG_MODE_DIRECT_ON_ADJUST; + break; + case DC_STATE_CC_MODE: + case DC_STATE_CV_MODE: + value.intval = SEC_DIRECT_CHG_MODE_DIRECT_ON; + break; + case DC_STATE_CHARGING_DONE: + value.intval = SEC_DIRECT_CHG_MODE_DIRECT_DONE; + break; + case DC_STATE_BYPASS_MODE: + value.intval = SEC_DIRECT_CHG_MODE_DIRECT_BYPASS; + break; + default: + return -1; + } + + if (prev_val == value.intval) + return -1; + + prev_val = value.intval; + psy_do_property(pca9481->pdata->sec_dc_name, set, + POWER_SUPPLY_EXT_PROP_DIRECT_CHARGER_MODE, value); + + return 0; +} + +static void pca9481_init_adc_val(struct pca9481_charger *pca9481, int val) +{ + int i = 0; + + for (i = 0; i < ADC_READ_MAX; ++i) + pca9481->adc_val[i] = val; +} + +static void pca9481_test_read(struct pca9481_charger *pca9481) +{ + int address = 0; + unsigned int val; + char str[1024] = { 0, }; + + for (address = PCA9481_REG_DEVICE_0_STS; address <= PCA9481_REG_ADC_READ_I_VBAT_CURRENT_1; address++) { + pca9481_read_reg(pca9481, address, &val); + sprintf(str + strlen(str), "[0x%02x]0x%02x, ", address, val); + } + pr_info("%s : %s\n", __func__, str); +} + +static void pca9481_monitor_work(struct pca9481_charger *pca9481) +{ + int ta_vol = pca9481->ta_vol / PCA9481_SEC_DENOM_U_M; + int ta_cur = pca9481->ta_cur / PCA9481_SEC_DENOM_U_M; + + if (pca9481->charging_state == DC_STATE_NO_CHARGING) + return; + /* update adc value */ + pca9481_read_adc(pca9481, ADCCH_VIN); + pca9481_read_adc(pca9481, ADCCH_VIN_CURRENT); + pca9481_read_adc(pca9481, ADCCH_BATP_BATN); + pca9481_read_adc(pca9481, ADCCH_DIE_TEMP); + pr_info("%s: state(%s), iin_cc(%dmA), v_float(%dmV), vbat(%dmV), vin(%dmV), iin(%dmA), die_temp(%d), pps_requested(%d/%dmV/%dmA)", __func__, + charging_state_str[pca9481->charging_state], + pca9481->iin_cc / PCA9481_SEC_DENOM_U_M, pca9481->vfloat / PCA9481_SEC_DENOM_U_M, + pca9481->adc_val[ADCCH_BATP_BATN], pca9481->adc_val[ADCCH_VIN], + pca9481->adc_val[ADCCH_VIN_CURRENT], pca9481->adc_val[ADCCH_DIE_TEMP], + pca9481->ta_objpos, ta_vol, ta_cur); +} + +/**************************************/ +/* Switching charger control function */ +/**************************************/ +/* This function needs some modification by a customer */ +static void pca9481_set_wdt_enable(struct pca9481_charger *pca9481, bool enable) +{ + int ret; + unsigned int val; + + val = enable << MASK2SHIFT(PCA9481_BIT_WATCHDOG_EN); + ret = pca9481_update_reg(pca9481, PCA9481_REG_DEVICE_CNTL_0, + PCA9481_BIT_WATCHDOG_EN, val); + pr_info("%s: set wdt enable = %d\n", __func__, enable); +} + +static void pca9481_set_wdt_timer(struct pca9481_charger *pca9481, int time) +{ + int ret; + unsigned int val; + + val = time << MASK2SHIFT(PCA9481_BIT_WATCHDOG_CFG); + ret = pca9481_update_reg(pca9481, PCA9481_REG_DEVICE_CNTL_0, + PCA9481_BIT_WATCHDOG_CFG, val); + pr_info("%s: set wdt time = %d\n", __func__, time); +} + +static void pca9481_check_wdt_control(struct pca9481_charger *pca9481) +{ + struct device *dev = pca9481->dev; + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + + if (pca9481->wdt_kick) { + pca9481_set_wdt_timer(pca9481, WDT_16SEC); + schedule_delayed_work(&pca9481->wdt_control_work, msecs_to_jiffies(PCA9481_BATT_WDT_CONTROL_T)); + } else { + pca9481_set_wdt_timer(pca9481, WDT_16SEC); + if (client->addr == 0xff) + client->addr = 0x57; + } +} + +static void pca9481_wdt_control_work(struct work_struct *work) +{ + struct pca9481_charger *pca9481 = container_of(work, struct pca9481_charger, + wdt_control_work.work); + struct device *dev = pca9481->dev; + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + int vin, iin; + + pca9481_set_wdt_timer(pca9481, WDT_4SEC); + + /* this is for kick watchdog */ + vin = pca9481_read_adc(pca9481, ADCCH_VIN); + iin = pca9481_read_adc(pca9481, ADCCH_VIN_CURRENT); + + pca9481_send_pd_message(pca9481, PD_MSG_REQUEST_APDO); + + client->addr = 0xff; + + pr_info("## %s: disable client addr (vin:%dmV, iin:%dmA)\n", + __func__, vin / PCA9481_SEC_DENOM_U_M, iin / PCA9481_SEC_DENOM_U_M); +} + +static void pca9481_set_done(struct pca9481_charger *pca9481, bool enable) +{ + int ret = 0; + union power_supply_propval value = {0, }; + + value.intval = enable; + psy_do_property(pca9481->pdata->sec_dc_name, set, + POWER_SUPPLY_EXT_PROP_DIRECT_DONE, value); + + if (ret < 0) + pr_info("%s: error set_done, ret=%d\n", __func__, ret); +} + +static void pca9481_set_switching_charger(struct pca9481_charger *pca9481, bool enable) +{ + int ret = 0; + union power_supply_propval value = {0, }; + + value.intval = enable; + psy_do_property(pca9481->pdata->sec_dc_name, set, + POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED_DC, value); + + if (ret < 0) + pr_info("%s: error switching_charger, ret=%d\n", __func__, ret); +} +#else +static int pca9481_set_switching_charger(bool enable, + unsigned int input_current, + unsigned int charging_current, + unsigned int vfloat) +{ + int ret; + struct power_supply *psy_swcharger; + union power_supply_propval val; + + pr_info("%s: enable=%d, iin=%d, ichg=%d, vfloat=%d\n", + __func__, enable, input_current, charging_current, vfloat); + + /* Insert Code */ + /* Get power supply name */ +#ifdef CONFIG_USBPD_PHY_QCOM + psy_swcharger = power_supply_get_by_name("usb"); +#else + /* Change "sw-charger" to the customer's switching charger name */ + psy_swcharger = power_supply_get_by_name("sw-charger"); +#endif + if (psy_swcharger == NULL) { + pr_err("%s: cannot get power_supply_name-usb\n", __func__); + ret = -ENODEV; + goto error; + } + + if (enable == true) { + /* Set Switching charger */ + + /* input current */ + val.intval = input_current; + ret = power_supply_set_property(psy_swcharger, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &val); + if (ret < 0) + goto error; + /* charging current */ + val.intval = charging_current; + ret = power_supply_set_property(psy_swcharger, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, &val); + if (ret < 0) + goto error; + /* vfloat voltage */ + val.intval = vfloat; + ret = power_supply_set_property(psy_swcharger, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, &val); + if (ret < 0) + goto error; + + /* it depends on customer's code to enable charger */ +#ifdef CONFIG_USBPD_PHY_QCOM + val.intval = enable; + ret = power_supply_set_property(psy_swcharger, POWER_SUPPLY_PROP_CHARGING_ENABLED, &val); +#else + val.intval = enable; + ret = power_supply_set_property(psy_swcharger, POWER_SUPPLY_PROP_ONLINE, &val); +#endif + if (ret < 0) + goto error; + } else { + /* disable charger */ + /* it depends on customer's code to disable charger */ +#ifdef CONFIG_USBPD_PHY_QCOM + val.intval = enable; + ret = power_supply_set_property(psy_swcharger, POWER_SUPPLY_PROP_CHARGING_ENABLED, &val); +#else + val.intval = enable; + ret = power_supply_set_property(psy_swcharger, POWER_SUPPLY_PROP_ONLINE, &val); +#endif + if (ret < 0) + goto error; + + /* input_current */ + val.intval = input_current; + ret = power_supply_set_property(psy_swcharger, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &val); + if (ret < 0) + goto error; + } + +error: + pr_info("%s: End, ret=%d\n", __func__, ret); + return ret; +} +#endif + +#if !IS_ENABLED(CONFIG_BATTERY_SAMSUNG) +static int pca9481_get_switching_charger_property(enum power_supply_property prop, + union power_supply_propval *val) +{ + int ret; + struct power_supply *psy_swcharger; + + /* Get power supply name */ +#ifdef CONFIG_USBPD_PHY_QCOM + psy_swcharger = power_supply_get_by_name("usb"); +#else + psy_swcharger = power_supply_get_by_name("sw-charger"); +#endif + if (psy_swcharger == NULL) { + ret = -EINVAL; + goto error; + } + ret = power_supply_get_property(psy_swcharger, prop, val); + +error: + pr_info("%s: End, ret=%d\n", __func__, ret); + return ret; +} +#endif + +/*******************/ +/* Send PD message */ +/*******************/ +/* Send Request message to the source */ +/* This function needs some modification by a customer */ +static int pca9481_send_pd_message(struct pca9481_charger *pca9481, unsigned int msg_type) +{ +#ifdef CONFIG_USBPD_PHY_QCOM +#elif IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + unsigned int pdo_idx, pps_vol, pps_cur; +#else + u8 msg_buf[4]; /* Data Buffer for raw PD message */ + unsigned int max_cur; + unsigned int op_cur, out_vol; +#endif + int ret = 0; + + pr_info("%s: ======Start========\n", __func__); + + /* Cancel pps request timer */ + cancel_delayed_work(&pca9481->pps_work); + + mutex_lock(&pca9481->lock); + + if (((pca9481->charging_state == DC_STATE_NO_CHARGING) && + (msg_type == PD_MSG_REQUEST_APDO)) || + (pca9481->mains_online == false)) { + /* Vbus reset happened in the previous PD communication */ + goto out; + } + +#ifdef CONFIG_USBPD_PHY_QCOM + /* check the phandle */ + if (pca9481->pd == NULL) { + pr_info("%s: get phandle\n", __func__); + ret = pca9481_usbpd_setup(pca9481); + if (ret != 0) { + dev_err(pca9481->dev, "Error usbpd setup!\n"); + pca9481->pd = NULL; + goto out; + } + } +#endif + + /* Check whether requested TA voltage and current are in valid range or not */ + if ((msg_type == PD_MSG_REQUEST_APDO) && + (pca9481->dc_mode != PTM_1TO1) && + ((pca9481->ta_vol < TA_MIN_VOL) || (pca9481->ta_cur < TA_MIN_CUR))) { + /* request TA voltage or current is less than minimum threshold */ + /* This is abnormal case, too low input voltage and current */ + /* Normally VIN_UVLO already happened */ + pr_err("%s: Abnormal low RDO, ta_vol=%d, ta_cur=%d\n", __func__, pca9481->ta_vol, pca9481->ta_cur); + ret = -EINVAL; + goto out; + } + + pr_info("%s: msg_type=%d, ta_cur=%d, ta_vol=%d, ta_objpos=%d\n", + __func__, msg_type, pca9481->ta_cur, pca9481->ta_vol, pca9481->ta_objpos); + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pdo_idx = pca9481->ta_objpos; + pps_vol = pca9481->ta_vol / PCA9481_SEC_DENOM_U_M; + pps_cur = pca9481->ta_cur / PCA9481_SEC_DENOM_U_M; + pr_info("## %s: msg_type=%d, pdo_idx=%d, pps_vol=%dmV(max_vol=%dmV), pps_cur=%dmA(max_cur=%dmA)\n", + __func__, msg_type, pdo_idx, + pps_vol, pca9481->pdo_max_voltage, + pps_cur, pca9481->pdo_max_current); +#endif + + switch (msg_type) { + case PD_MSG_REQUEST_APDO: +#ifdef CONFIG_USBPD_PHY_QCOM + ret = usbpd_request_pdo(pca9481->pd, pca9481->ta_objpos, pca9481->ta_vol, pca9481->ta_cur); + if (ret == -EBUSY) { + /* wait 100ms */ + msleep(100); + /* try again */ + ret = usbpd_request_pdo(pca9481->pd, pca9481->ta_objpos, pca9481->ta_vol, pca9481->ta_cur); + } +#elif IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + ret = sec_pd_select_pps(pdo_idx, pps_vol, pps_cur); + if (ret == -EBUSY) { + pr_info("%s: request again ret=%d\n", __func__, ret); + msleep(100); + ret = sec_pd_select_pps(pdo_idx, pps_vol, pps_cur); + } +#else + op_cur = pca9481->ta_cur/50000; // Operating Current 50mA units + out_vol = pca9481->ta_vol/20000; // Output Voltage in 20mV units + msg_buf[0] = op_cur & 0x7F; // Operating Current 50mA units - B6...0 + msg_buf[1] = (out_vol<<1) & 0xFE; // Output Voltage in 20mV units - B19..(B15)..B9 + msg_buf[2] = (out_vol>>7) & 0x0F; // Output Voltage in 20mV units - B19..(B16)..B9, + msg_buf[3] = pca9481->ta_objpos<<4; // Object Position - B30...B28 + + /* Send the PD message to CC/PD chip */ + /* Todo - insert code */ +#endif + /* Start pps request timer */ + if (ret == 0) { + queue_delayed_work(pca9481->dc_wq, + &pca9481->pps_work, + msecs_to_jiffies(PPS_PERIODIC_T)); + } + break; + case PD_MSG_REQUEST_FIXED_PDO: +#ifdef CONFIG_USBPD_PHY_QCOM + ret = usbpd_request_pdo(pca9481->pd, pca9481->ta_objpos, pca9481->ta_vol, pca9481->ta_cur); + if (ret == -EBUSY) { + /* wait 100ms */ + msleep(100); + /* try again */ + ret = usbpd_request_pdo(pca9481->pd, pca9481->ta_objpos, pca9481->ta_vol, pca9481->ta_cur); + } +#elif IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + if (pca9481->ta_type == TA_TYPE_USBPD_20) { + pr_err("%s: ta_type(%d)! skip pd_select_pps\n", __func__, pca9481->ta_type); + } else { + ret = sec_pd_select_pps(pdo_idx, pps_vol, pps_cur); + if (ret == -EBUSY) { + pr_info("%s: request again ret=%d\n", __func__, ret); + msleep(100); + ret = sec_pd_select_pps(pdo_idx, pps_vol, pps_cur); + } + } +#else + max_cur = pca9481->ta_cur/10000; // Maximum Operation Current 10mA units + op_cur = max_cur; // Operating Current 10mA units + msg_buf[0] = max_cur & 0xFF; // Maximum Operation Current -B9..(7)..0 + msg_buf[1] = ((max_cur>>8) & 0x03) | ((op_cur<<2) & 0xFC); // Operating Current - B19..(15)..10 + // Operating Current - B19..(16)..10, Unchunked Extended Messages Supported - not support + msg_buf[2] = ((op_cur>>6) & 0x0F); + msg_buf[3] = pca9481->ta_objpos<<4; // Object Position - B30...B28 + + /* Send the PD message to CC/PD chip */ + /* Todo - insert code */ +#endif + break; + default: + break; + } + +out: + if (((pca9481->charging_state == DC_STATE_NO_CHARGING) && + (msg_type == PD_MSG_REQUEST_APDO)) || + (pca9481->mains_online == false)) { + /* Even though PD communication success, Vbus reset might happen */ + /* So, check the charging state again */ + ret = -EINVAL; + } + + pr_info("%s: ret=%d\n", __func__, ret); + mutex_unlock(&pca9481->lock); + return ret; +} + +/************************/ +/* Get APDO max power */ +/************************/ +/* Get the max current/voltage/power of APDO from the CC/PD driver */ +/* This function needs some modification by a customer */ +static int pca9481_get_apdo_max_power(struct pca9481_charger *pca9481) +{ + int ret = 0; +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + unsigned int ta_max_vol_mv = (pca9481->ta_max_vol / PCA9481_SEC_DENOM_U_M); + unsigned int ta_max_cur_ma = 0; + unsigned int ta_max_pwr_uw = 0; +#endif + +#ifdef CONFIG_USBPD_PHY_QCOM + /* check the phandle */ + if (pca9481->pd == NULL) { + pr_info("%s: get phandle\n", __func__); + ret = pca9481_usbpd_setup(pca9481); + if (ret != 0) { + dev_err(pca9481->dev, "Error usbpd setup!\n"); + pca9481->pd = NULL; + goto out; + } + } + + /* Put ta_max_vol to the desired ta maximum value, ex) 9800mV */ + /* Get new ta_max_vol and ta_max_cur, ta_max_power and proper object position by CC/PD IC */ + ret = usbpd_get_apdo_max_power(pca9481->pd, &pca9481->ta_objpos, + &pca9481->ta_max_vol, &pca9481->ta_max_cur, &pca9481->ta_max_pwr); +#elif IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + ret = sec_pd_get_apdo_max_power(&pca9481->ta_objpos, + &ta_max_vol_mv, &ta_max_cur_ma, &ta_max_pwr_uw); + /* mA,mV,uW --> uA,uV,uW */ + pca9481->ta_max_vol = ta_max_vol_mv * PCA9481_SEC_DENOM_U_M; + pca9481->ta_max_cur = ta_max_cur_ma * PCA9481_SEC_DENOM_U_M; + pca9481->ta_max_pwr = ta_max_pwr_uw; + + pr_info("%s: ta_max_vol=%d, ta_max_cur=%d, ta_max_pwr=%d\n", + __func__, pca9481->ta_max_vol, pca9481->ta_max_cur, pca9481->ta_max_pwr); + + pca9481->pdo_index = pca9481->ta_objpos; + pca9481->pdo_max_voltage = ta_max_vol_mv; + pca9481->pdo_max_current = ta_max_cur_ma; +#else + /* Need to implement by a customer */ + /* Get new ta_max_vol and ta_max_cur, ta_max_power and proper object position by CC/PD IC */ +#endif + +#ifdef CONFIG_USBPD_PHY_QCOM +out: +#endif + return ret; +} + + +/******************/ +/* Set RX voltage */ +/******************/ +/* Send RX voltage to RX IC */ +/* This function needs some modification by a customer */ +static int pca9481_send_rx_voltage(struct pca9481_charger *pca9481, unsigned int msg_type) +{ + struct power_supply *psy; + union power_supply_propval pro_val; + int ret = 0; + + mutex_lock(&pca9481->lock); + + if (pca9481->mains_online == false) { + /* Vbus reset happened in the previous PD communication */ + goto out; + } + + pr_info("%s: rx_vol=%d\n", __func__, pca9481->ta_vol); + + /* Need to implement send RX voltage to wireless RX IC */ + + /* The below code should be modified by the customer */ + /* Get power supply name */ + psy = power_supply_get_by_name("wireless"); + if (!psy) { + dev_err(pca9481->dev, "Cannot find wireless power supply\n"); + ret = -ENODEV; + goto out; + } + + /* Set the RX voltage */ + pro_val.intval = pca9481->ta_vol; + + /* Set the property */ + ret = power_supply_set_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, &pro_val); + power_supply_put(psy); + if (ret < 0) { + dev_err(pca9481->dev, "Cannot set RX voltage\n"); + goto out; + } + +out: + if (pca9481->mains_online == false) { + /* Even though PD communication success, Vbus reset might happen */ + /* So, check the charging state again */ + ret = -EINVAL; + } + + pr_info("%s: ret=%d\n", __func__, ret); + mutex_unlock(&pca9481->lock); + return ret; +} + + +/************************/ +/* Get RX max power */ +/************************/ +/* Get the max current/voltage/power of RXIC from the WCRX driver */ +/* This function needs some modification by a customer */ +static int pca9481_get_rx_max_power(struct pca9481_charger *pca9481) +{ + struct power_supply *psy; + union power_supply_propval pro_val; + int ret = 0; + + /* insert code */ + + /* Get power supply name */ + psy = power_supply_get_by_name("wireless"); + if (!psy) { + dev_err(pca9481->dev, "Cannot find wireless power supply\n"); + ret = -ENODEV; + return ret; + } + + /* Get the maximum voltage */ + ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_MAX, &pro_val); + if (ret < 0) { + dev_err(pca9481->dev, "Cannot get the maximum RX voltage\n"); + goto error; + } + + if (pca9481->ta_max_vol > pro_val.intval) { + /* RX IC cannot support the request maximum voltage */ + ret = -EINVAL; + goto error; + } else { + pca9481->ta_max_vol = pro_val.intval; + } + + /* Get the maximum current */ + ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_CURRENT_MAX, &pro_val); + if (ret < 0) { + dev_err(pca9481->dev, "Cannot get the maximum RX current\n"); + goto error; + } + + pca9481->ta_max_cur = pro_val.intval; + pca9481->ta_max_pwr = (pca9481->ta_max_vol/1000)*(pca9481->ta_max_cur/1000); + +error: + power_supply_put(psy); + return ret; +} + +/**************************/ +/* PCA9481 Local function */ +/**************************/ +/* ADC Read function */ +static int pca9481_read_adc(struct pca9481_charger *pca9481, u8 adc_ch) +{ + union power_supply_propval value = {0,}; + u8 reg_data[2]; + u16 raw_adc = 0; // raw ADC value + int conv_adc; // conversion ADC value + int ret; + + switch (adc_ch) { + case ADCCH_VIN: + // Read ADC value + ret = pca9481_bulk_read_reg(pca9481, PCA9481_REG_ADC_READ_VIN_0 | PCA9481_REG_BIT_AI, reg_data, 2); + if (ret < 0) { + conv_adc = ret; + goto error; + } + // Convert ADC + raw_adc = ((reg_data[1] & PCA9481_BIT_ADC_READ_11_8)<<8) | reg_data[0]; + conv_adc = raw_adc * VIN_STEP; + break; + + case ADCCH_VFET_IN: + // Read ADC value + ret = pca9481_bulk_read_reg(pca9481, + PCA9481_REG_ADC_READ_VFET_IN_0 | PCA9481_REG_BIT_AI, reg_data, 2); + if (ret < 0) { + conv_adc = ret; + goto error; + } + // Convert ADC + raw_adc = ((reg_data[1] & PCA9481_BIT_ADC_READ_11_8)<<8) | reg_data[0]; + conv_adc = raw_adc * VFET_IN_STEP; + break; + + case ADCCH_OVP_OUT: + // Read ADC value + ret = pca9481_bulk_read_reg(pca9481, + PCA9481_REG_ADC_READ_OVP_OUT_0 | PCA9481_REG_BIT_AI, reg_data, 2); + if (ret < 0) { + conv_adc = ret; + goto error; + } + // Convert ADC + raw_adc = ((reg_data[1] & PCA9481_BIT_ADC_READ_11_8)<<8) | reg_data[0]; + conv_adc = raw_adc * OVP_OUT_STEP; + break; + + case ADCCH_BATP_BATN: + // Read ADC value + ret = pca9481_bulk_read_reg(pca9481, + PCA9481_REG_ADC_READ_BATP_BATN_0 | PCA9481_REG_BIT_AI, reg_data, 2); + if (ret < 0) { + conv_adc = ret; + goto error; + } + // Convert ADC + raw_adc = ((reg_data[1] & PCA9481_BIT_ADC_READ_11_8)<<8) | reg_data[0]; + conv_adc = raw_adc * BATP_BATN_STEP; + + pr_info("%s: pca9481 vbatt_adc, before convert_val=%d\n", __func__, conv_adc); + + ret = psy_do_property(pca9481->pdata->fg_name, get, + POWER_SUPPLY_PROP_VOLTAGE_NOW, value); + if (ret < 0) { + conv_adc = ret; + goto error; + } + conv_adc = value.intval * PCA9481_SEC_DENOM_U_M; + pr_info("%s: pca9481 vbatt_adc, after convert_val=%d\n", __func__, conv_adc); + break; + + case ADCCH_VOUT: + // Read ADC value + ret = pca9481_bulk_read_reg(pca9481, + PCA9481_REG_ADC_READ_VOUT_0 | PCA9481_REG_BIT_AI, reg_data, 2); + if (ret < 0) { + conv_adc = ret; + goto error; + } + // Convert ADC + raw_adc = ((reg_data[1] & PCA9481_BIT_ADC_READ_11_8)<<8) | reg_data[0]; + conv_adc = raw_adc * VOUT_STEP; + break; + + case ADCCH_NTC: + // Read ADC value + ret = pca9481_bulk_read_reg(pca9481, + PCA9481_REG_ADC_READ_NTC_0 | PCA9481_REG_BIT_AI, reg_data, 2); + if (ret < 0) { + conv_adc = ret; + goto error; + } + // Convert ADC + raw_adc = ((reg_data[1] & PCA9481_BIT_ADC_READ_11_8)<<8) | reg_data[0]; + conv_adc = raw_adc * NTC_STEP; + break; + + case ADCCH_DIE_TEMP: + // Read ADC value + ret = pca9481_bulk_read_reg(pca9481, + PCA9481_REG_ADC_READ_DIE_TEMP_0 | PCA9481_REG_BIT_AI, reg_data, 2); + if (ret < 0) { + conv_adc = ret; + goto error; + } + // Convert ADC + raw_adc = ((reg_data[1] & PCA9481_BIT_ADC_READ_11_8)<<8) | reg_data[0]; + conv_adc = raw_adc * DIE_TEMP_STEP / DIE_TEMP_DENOM; // Temp, unit - C + if (conv_adc > DIE_TEMP_MAX) + conv_adc = DIE_TEMP_MAX; + break; + + case ADCCH_VIN_CURRENT: + // Read ADC value + ret = pca9481_bulk_read_reg(pca9481, + PCA9481_REG_ADC_READ_VIN_CURRENT_0 | PCA9481_REG_BIT_AI, reg_data, 2); + if (ret < 0) { + conv_adc = ret; + goto error; + } + // Convert ADC + raw_adc = ((reg_data[1] & PCA9481_BIT_ADC_READ_11_8)<<8) | reg_data[0]; + /* Compensate raw_adc */ + conv_adc = PCA9481_IIN_ADC_COMP_GAIN*raw_adc + PCA9481_IIN_ADC_COMP_OFFSET; + if (conv_adc < 0) + conv_adc = raw_adc * VIN_CURRENT_STEP; + break; + + case ADCCH_BAT_CURRENT: + // Read ADC value + ret = pca9481_bulk_read_reg(pca9481, + PCA9481_REG_ADC_READ_I_VBAT_CURRENT_0 | PCA9481_REG_BIT_AI, reg_data, 2); + if (ret < 0) { + conv_adc = ret; + goto error; + } + // Convert ADC + raw_adc = ((reg_data[1] & PCA9481_BIT_ADC_READ_11_8)<<8) | reg_data[0]; + conv_adc = raw_adc * BAT_CURRENT_STEP; + break; + + default: + conv_adc = -EINVAL; + break; + } + +error: + pr_info("%s: adc_ch=%d, raw_adc=0x%x, convert_val=%d\n", __func__, + adc_ch, raw_adc, conv_adc); + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + if (adc_ch == ADCCH_DIE_TEMP) + pca9481->adc_val[adc_ch] = conv_adc; + else + pca9481->adc_val[adc_ch] = conv_adc / PCA9481_SEC_DENOM_U_M; +#endif + return conv_adc; +} + + +static int pca9481_set_vfloat(struct pca9481_charger *pca9481, unsigned int vfloat) +{ + int ret, val; + + pr_info("%s: vfloat=%d\n", __func__, vfloat); + + /* float voltage - battery regulation voltage */ + /* maximum value is 5V */ + if (vfloat > PCA9481_VBAT_REG_MAX) + vfloat = PCA9481_VBAT_REG_MAX; + /* minimum value is 3.725V */ + if (vfloat < PCA9481_VBAT_REG_MIN) + vfloat = PCA9481_VBAT_REG_MIN; + + val = PCA9481_VBAT_REG(vfloat); + ret = pca9481_write_reg(pca9481, PCA9481_REG_CHARGING_CNTL_2, val); + return ret; +} + +static int pca9481_set_charging_current(struct pca9481_charger *pca9481, unsigned int ichg) +{ + int ret, val; + + pr_info("%s: ichg=%d\n", __func__, ichg); + + /* charging current - battery regulation current */ + /* round-up charging current with ibat regulation resolution */ + if (ichg % PCA9481_IBAT_REG_STEP) + ichg = ichg + PCA9481_IBAT_REG_STEP; + + /* Add 100mA offset to avoid frequent battery current regulation */ + ichg = ichg + PCA9481_IBAT_REG_OFFSET; + + /* maximum value is 10A */ + if (ichg > PCA9481_IBAT_REG_MAX) + ichg = PCA9481_IBAT_REG_MAX; + /* minimum value is 1A */ + if (ichg < PCA9481_IBAT_REG_MIN) + ichg = PCA9481_IBAT_REG_MIN; + + val = PCA9481_IBAT_REG(ichg); + ret = pca9481_write_reg(pca9481, PCA9481_REG_CHARGING_CNTL_3, val); + return ret; +} + +static int pca9481_set_input_current(struct pca9481_charger *pca9481, unsigned int iin) +{ + int ret, val; + + pr_info("%s: iin=%d\n", __func__, iin); + + /* input current - input regulation current */ + + /* round-up input current with input regulation resolution */ + if (iin % PCA9481_IIN_REG_STEP) + iin = iin + PCA9481_IIN_REG_STEP; + + /* Add offset for input current regulation */ + /* Check TA type */ + if (pca9481->ta_type == TA_TYPE_USBPD_20) { +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + if (iin < pca9481->pdata->fpdo_dc_iin_lowest_limit) { + pr_info("%s: IIN LOWEST LIMIT! IIN %d -> %d\n", __func__, + iin, pca9481->pdata->fpdo_dc_iin_lowest_limit); + iin = pca9481->pdata->fpdo_dc_iin_lowest_limit; + } +#endif + /* Apply FPDO offset */ + iin = iin + PCA9481_IIN_REG_OFFSET_FPDO; + } else { + /* Add offset for input current regulation */ + if (iin < PCA9481_IIN_REG_OFFSET1_TH) + iin = iin + PCA9481_IIN_REG_OFFSET1; + else if (iin < PCA9481_IIN_REG_OFFSET2_TH) + iin = iin + PCA9481_IIN_REG_OFFSET2; + else if (iin < PCA9481_IIN_REG_OFFSET3_TH) + iin = iin + PCA9481_IIN_REG_OFFSET3; + else if (iin < PCA9481_IIN_REG_OFFSET4_TH) + iin = iin + PCA9481_IIN_REG_OFFSET4; + else + iin = iin + PCA9481_IIN_REG_OFFSET5; + } + + /* maximum value is 6A */ + if (iin > PCA9481_IIN_REG_MAX) + iin = PCA9481_IIN_REG_MAX; + /* minimum value is 500mA */ + if (iin < PCA9481_IIN_REG_MIN) + iin = PCA9481_IIN_REG_MIN; + + val = PCA9481_IIN_REG(iin); + ret = pca9481_write_reg(pca9481, PCA9481_REG_CHARGING_CNTL_1, val); + + pr_info("%s: real iin_cfg=%d\n", __func__, val*PCA9481_IIN_REG_STEP + PCA9481_IIN_REG_MIN); + return ret; +} + +static int pca9481_set_charging(struct pca9481_charger *pca9481, bool enable) +{ + int ret, val; + u8 sc_op_reg; + int stop_cnt; + + pr_info("%s: enable=%d\n", __func__, enable); + + if (pca9481->dc_mode == PTM_1TO1) { + /* Forward 1:1 mode */ + sc_op_reg = PCA9481_SC_OP_F_11; + } else { + /* 2:1 Switching mode */ + sc_op_reg = PCA9481_SC_OP_21; + + /* Disable HALF_VIN_OVP_EN */ + val = 0; + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_DEVICE_CNTL_1, + PCA9481_BIT_HALF_VIN_OVP_EN, val); + } + + /* Check device's current status */ + /* Read DEVICE_3_STS register */ + ret = regmap_read(pca9481->regmap, PCA9481_REG_DEVICE_3_STS, &val); + if (ret < 0) + goto error; + pr_info("%s: power state = 0x%x\n", __func__, val); + + if (enable == true) { + /* Set NTC Protection */ + ret = pca9481_update_reg(pca9481, PCA9481_REG_DEVICE_CNTL_0, + PCA9481_BIT_STANDBY_BY_NTC_EN, + pca9481->pdata->ntc_en << MASK2SHIFT(PCA9481_BIT_STANDBY_BY_NTC_EN)); + + /* Set STANDBY_EN bit to 1 */ + ret = pca9481_update_reg(pca9481, PCA9481_REG_SC_CNTL_3, + PCA9481_BIT_STANDBY_EN | PCA9481_BIT_SC_OPERATION_MODE, + PCA9481_STANDBY_FORCE | sc_op_reg); + + /* Set EN_CFG to active low */ + ret = pca9481_update_reg(pca9481, PCA9481_REG_DEVICE_CNTL_2, + PCA9481_BIT_EN_CFG, PCA9481_EN_ACTIVE_L); + + /* Set STANDBY_EN bit to 0 */ + ret = pca9481_update_reg(pca9481, PCA9481_REG_SC_CNTL_3, + PCA9481_BIT_STANDBY_EN | PCA9481_BIT_SC_OPERATION_MODE, + PCA9481_STANDBY_DONOT | sc_op_reg); + /* Set enable flag to true */ + pca9481->enable = true; + } else { + /* Disable NTC Protection */ + ret = pca9481_update_reg(pca9481, PCA9481_REG_DEVICE_CNTL_0, + PCA9481_BIT_STANDBY_BY_NTC_EN, 0); + + /* Set EN_CFG to active high - Disable PCA9481 */ + ret = pca9481_update_reg(pca9481, PCA9481_REG_DEVICE_CNTL_2, + PCA9481_BIT_EN_CFG, PCA9481_EN_ACTIVE_H); + + /* Check SC_OFF status - maximum wait time 400ms */ + for (stop_cnt = 0; stop_cnt < 4; stop_cnt++) { + ret = regmap_read(pca9481->regmap, PCA9481_REG_SC_0_STS, &val); + if (ret < 0) + goto error; + if ((val & PCA9481_BIT_SC_OFF) == 0) { + /* on soft-stop - wait 100ms */ + msleep(100); + } else { + pr_info("%s: switching off\n", __func__); + /* switching off - return */ + break; + } + } + + /* Set enable flag to false */ + pca9481->enable = false; + } + +error: + pr_info("%s: End, ret=%d\n", __func__, ret); + return ret; +} + +static int pca9481_set_vin_ocp(struct pca9481_charger *pca9481, unsigned int vin_ocp) +{ + int ret, val; + + pr_info("%s: vin_ocp=%d\n", __func__, vin_ocp); + + /* maximum value is 2A */ + if (vin_ocp > PCA9481_VIN_OCP_12_11_MAX) + vin_ocp = PCA9481_VIN_OCP_12_11_MAX; + /* minimum value is 500mA */ + if (vin_ocp < PCA9481_VIN_OCP_12_11_MIN) + vin_ocp = PCA9481_VIN_OCP_12_11_MIN; + + /* Set VIN OCP current */ + val = PCA9481_VIN_OCP_12_11(vin_ocp); + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_CHARGING_CNTL_5, + PCA9481_BIT_VIN_OCP_CURRENT_12_11, + val << MASK2SHIFT(PCA9481_BIT_VIN_OCP_CURRENT_12_11)); + return ret; +} + +static int pca9481_set_reverse_mode(struct pca9481_charger *pca9481, bool enable) +{ + int ret, val; + u8 sc_op_reg; + + pr_info("%s: enable=%d\n", __func__, enable); + + if (pca9481->rev_mode == POWER_SUPPLY_DC_REVERSE_1TO2) { + /* 1:2 switching mode */ + sc_op_reg = PCA9481_SC_OP_12; + /* Disable HALF_VIN_OVP_EN */ + val = 0; + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_DEVICE_CNTL_1, + PCA9481_BIT_HALF_VIN_OVP_EN, val); + } else { + /* Reverse 1:1 bypass mode */ + sc_op_reg = PCA9481_SC_OP_R_11; + } + + /* Check device's current status */ + /* Read DEVICE_3_STS register */ + ret = regmap_read(pca9481->regmap, PCA9481_REG_DEVICE_3_STS, &val); + if (ret < 0) + goto error; + pr_info("%s: power state = 0x%x\n", __func__, val); + + if (enable == true) { + /* Enable VIN_OCP_12_11_EN */ + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_CHARGING_CNTL_5, + PCA9481_BIT_VIN_OCP_12_11_EN, PCA9481_BIT_VIN_OCP_12_11_EN); + /* Set NTC Protection */ + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_DEVICE_CNTL_0, + PCA9481_BIT_STANDBY_BY_NTC_EN, + pca9481->pdata->ntc_en << MASK2SHIFT(PCA9481_BIT_STANDBY_BY_NTC_EN)); + /* Set STANDBY_EN bit to 0 */ + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_SC_CNTL_3, + PCA9481_BIT_STANDBY_EN | PCA9481_BIT_SC_OPERATION_MODE, + PCA9481_STANDBY_DONOT | sc_op_reg); + /* Set STANDBY_EN bit to 1 */ + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_SC_CNTL_3, + PCA9481_BIT_STANDBY_EN | PCA9481_BIT_SC_OPERATION_MODE, + PCA9481_STANDBY_FORCE | sc_op_reg); + /* Set EN_CFG to active low */ + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_DEVICE_CNTL_2, + PCA9481_BIT_EN_CFG, PCA9481_EN_ACTIVE_L); + /* Set STANDBY_EN bit to 0 */ + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_SC_CNTL_3, + PCA9481_BIT_STANDBY_EN | PCA9481_BIT_SC_OPERATION_MODE, + PCA9481_STANDBY_DONOT | sc_op_reg); + /* Set enable flag to true */ + pca9481->enable = true; + } else { + /* Disable NTC Protection */ + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_DEVICE_CNTL_0, + PCA9481_BIT_STANDBY_BY_NTC_EN, 0); + /* Disable VIN_OCP_12_11_EN */ + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_CHARGING_CNTL_5, + PCA9481_BIT_VIN_OCP_12_11_EN, 0); + /* Set EN_CFG to active high - Disable PCA9481 */ + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_DEVICE_CNTL_2, + PCA9481_BIT_EN_CFG, PCA9481_EN_ACTIVE_H); + /* Set enable flag to false */ + pca9481->enable = false; + } + + /* Read DEVICE_3_STS register */ + ret = regmap_read(pca9481->regmap, PCA9481_REG_DEVICE_3_STS, &val); + if (ret < 0) + goto error; + pr_info("%s: power state after setting = 0x%x\n", __func__, val); + +error: + pr_info("%s: End, ret=%d\n", __func__, ret); + return ret; +} + +static int pca9481_softreset(struct pca9481_charger *pca9481) +{ + int ret, val; + u8 reg_val[10]; /* Dump for control register */ + + pr_info("%s: do soft reset\n", __func__); + + + /* Check the current register before softreset */ + pr_info("%s: Before softreset\n", __func__); + + /* Read all status registers for debugging */ + ret = pca9481_bulk_read_reg(pca9481, PCA9481_REG_DEVICE_0_STS | PCA9481_REG_BIT_AI, + ®_val[0], 7); + if (ret < 0) + goto error; + pr_info("%s: status reg[0x0F]=0x%x,[0x10]=0x%x,[0x11]=0x%x,[0x12]=0x%x,[0x13]=0x%x,[0x14]=0x%x,[0x15]=0x%x\n", + __func__, reg_val[0], reg_val[1], reg_val[2], reg_val[3], reg_val[4], reg_val[5], reg_val[6]); + + /* Read all control registers for debugging */ + /* Read device, auto restart, and rcp control registers for debugging */ + ret = pca9481_bulk_read_reg(pca9481, PCA9481_REG_DEVICE_CNTL_0 | PCA9481_REG_BIT_AI, + ®_val[0], 7); + if (ret < 0) + goto error; + pr_info("%s: control reg[0x16]=0x%x,[0x17]=0x%x,[0x18]=0x%x,[0x19]=0x%x,[0x1A]=0x%x,[0x1B]=0x%x,[0x1C]=0x%x\n", + __func__, reg_val[0], reg_val[1], reg_val[2], reg_val[3], reg_val[4], reg_val[5], reg_val[6]); + + /* Read charging control registers for debugging */ + ret = pca9481_bulk_read_reg(pca9481, PCA9481_REG_CHARGING_CNTL_0 | PCA9481_REG_BIT_AI, + ®_val[0], 7); + if (ret < 0) + goto error; + pr_info("%s: control reg[0x1D]=0x%x,[0x1E]=0x%x,[0x1F]=0x%x,[0x20]=0x%x,[0x21]=0x%x,[0x22]=0x%x,[0x23]=0x%x\n", + __func__, reg_val[0], reg_val[1], reg_val[2], reg_val[3], reg_val[4], reg_val[5], reg_val[6]); + + /* Read ntc, sc, and adc control registers for debugging */ + ret = pca9481_bulk_read_reg(pca9481, PCA9481_REG_NTC_0_CNTL | PCA9481_REG_BIT_AI, + ®_val[0], 9); + if (ret < 0) + goto error; + pr_info("%s: control reg[0x24]=0x%x,[0x25]=0x%x,[0x26]=0x%x,[0x27]=0x%x,[0x28]=0x%x,[0x29]=0x%x\n", + __func__, reg_val[0], reg_val[1], reg_val[2], reg_val[3], reg_val[4], reg_val[5]); + pr_info("%s: control reg[0x2A]=0x%x,[0x2B]=0x%x,[0x2C]=0x%x\n", + __func__, reg_val[6], reg_val[7], reg_val[8]); + + /* Do softreset */ + + /* Set softreset register */ + ret = pca9481_update_reg(pca9481, PCA9481_REG_DEVICE_CNTL_0, + PCA9481_BIT_SOFT_RESET, PCA9481_BIT_SOFT_RESET); + + /* Wait 5ms */ + usleep_range(5000, 6000); + + /* Reset PCA9481 and all regsiters values go to POR values */ + /* Check the current register after softreset */ + /* Read all control registers for debugging */ + pr_info("%s: After softreset\n", __func__); + + /* Read all status registers for debugging */ + ret = pca9481_bulk_read_reg(pca9481, PCA9481_REG_DEVICE_0_STS | PCA9481_REG_BIT_AI, + ®_val[0], 7); + if (ret < 0) + goto error; + pr_info("%s: status reg[0x0F]=0x%x,[0x10]=0x%x,[0x11]=0x%x,[0x12]=0x%x,[0x13]=0x%x,[0x14]=0x%x,[0x15]=0x%x\n", + __func__, reg_val[0], reg_val[1], reg_val[2], reg_val[3], reg_val[4], reg_val[5], reg_val[6]); + + /* Read all control registers for debugging */ + /* Read device, auto restart, and rcp control registers for debugging */ + ret = pca9481_bulk_read_reg(pca9481, PCA9481_REG_DEVICE_CNTL_0 | PCA9481_REG_BIT_AI, + ®_val[0], 7); + if (ret < 0) + goto error; + pr_info("%s: control reg[0x16]=0x%x,[0x17]=0x%x,[0x18]=0x%x,[0x19]=0x%x,[0x1A]=0x%x,[0x1B]=0x%x,[0x1C]=0x%x\n", + __func__, reg_val[0], reg_val[1], reg_val[2], reg_val[3], reg_val[4], reg_val[5], reg_val[6]); + + /* Read charging control registers for debugging */ + ret = pca9481_bulk_read_reg(pca9481, PCA9481_REG_CHARGING_CNTL_0 | PCA9481_REG_BIT_AI, + ®_val[0], 7); + if (ret < 0) + goto error; + pr_info("%s: control reg[0x1D]=0x%x,[0x1E]=0x%x,[0x1F]=0x%x,[0x20]=0x%x,[0x21]=0x%x,[0x22]=0x%x,[0x23]=0x%x\n", + __func__, reg_val[0], reg_val[1], reg_val[2], reg_val[3], reg_val[4], reg_val[5], reg_val[6]); + + /* Read ntc, sc, and adc control registers for debugging */ + ret = pca9481_bulk_read_reg(pca9481, PCA9481_REG_NTC_0_CNTL | PCA9481_REG_BIT_AI, + ®_val[0], 9); + if (ret < 0) + goto error; + pr_info("%s: control reg[0x24]=0x%x,[0x25]=0x%x,[0x26]=0x%x,[0x27]=0x%x,[0x28]=0x%x,[0x29]=0x%x\n", + __func__, reg_val[0], reg_val[1], reg_val[2], reg_val[3], reg_val[4], reg_val[5]); + pr_info("%s: control reg[0x2A]=0x%x,[0x2B]=0x%x,[0x2C]=0x%x\n", + __func__, reg_val[6], reg_val[7], reg_val[8]); + + /* Set the initial register value */ + + /* Set VIN_CURRENT_OCP_21_11 to 1000mA */ + val = PCA9481_BIT_VIN_CURRENT_OCP_21_11; + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_CHARGING_CNTL_0, + PCA9481_BIT_VIN_CURRENT_OCP_21_11, val); + if (ret < 0) + goto error; + + /* Set Reverse Current Detection */ + val = PCA9481_BIT_RCP_EN; + ret = pca9481_update_reg(pca9481, PCA9481_REG_RCP_CNTL, + PCA9481_BIT_RCP_EN, val); + if (ret < 0) + goto error; + + /* Die Temperature regulation 120'C */ + val = 0x3 << MASK2SHIFT(PCA9481_BIT_THERMAL_REGULATION_CFG); + ret = pca9481_update_reg(pca9481, PCA9481_REG_DEVICE_CNTL_1, + PCA9481_BIT_THERMAL_REGULATION_CFG | PCA9481_BIT_THERMAL_REGULATION_EN, + val | PCA9481_BIT_THERMAL_REGULATION_EN); + if (ret < 0) + goto error; + + /* Set external sense resistor value */ + val = pca9481->pdata->snsres << MASK2SHIFT(PCA9481_BIT_IBAT_SENSE_R_SEL); + ret = pca9481_update_reg(pca9481, PCA9481_REG_CHARGING_CNTL_4, + PCA9481_BIT_IBAT_SENSE_R_SEL, val); + if (ret < 0) + goto error; + + /* Set external sense resistor location */ + val = pca9481->pdata->snsres_cfg << MASK2SHIFT(PCA9481_BIT_IBAT_SENSE_R_CFG); + ret = pca9481_update_reg(pca9481, PCA9481_REG_DEVICE_CNTL_2, + PCA9481_BIT_IBAT_SENSE_R_CFG, val); + if (ret < 0) + goto error; + + /* Disable battery charge current regulation loop */ + /* Disable current measurement through CSP and CSN */ + val = 0; + ret = pca9481_update_reg(pca9481, PCA9481_REG_CHARGING_CNTL_0, + PCA9481_BIT_CSP_CSN_MEASURE_EN | PCA9481_BIT_I_VBAT_LOOP_EN, + val); + if (ret < 0) + goto error; + + /* Set the ADC channel */ + val = (PCA9481_BIT_ADC_READ_VIN_CURRENT_EN | /* IIN ADC */ + PCA9481_BIT_ADC_READ_DIE_TEMP_EN | /* DIE_TEMP ADC */ + PCA9481_BIT_ADC_READ_NTC_EN | /* NTC ADC */ + PCA9481_BIT_ADC_READ_VOUT_EN | /* VOUT ADC */ + PCA9481_BIT_ADC_READ_OVP_OUT_EN | /* OVP_OUT ADC */ + PCA9481_BIT_ADC_READ_VIN_EN); /* VIN ADC */ + + ret = pca9481_write_reg(pca9481, PCA9481_REG_ADC_EN_CNTL_0, val); + if (ret < 0) + goto error; + + /* Enable ADC */ + val = PCA9481_BIT_ADC_EN | ((unsigned int)(ADC_AVG_16sample << MASK2SHIFT(PCA9481_BIT_ADC_AVERAGE_TIMES))); + ret = pca9481_write_reg(pca9481, PCA9481_REG_ADC_CNTL, val); + if (ret < 0) + goto error; + + /* + * Configure the Mask Register for interrupts: disable all interrupts by default. + */ + reg_val[REG_DEVICE_0] = 0xFF; + reg_val[REG_DEVICE_1] = 0xFF; + reg_val[REG_DEVICE_2] = 0xFF; + reg_val[REG_DEVICE_3] = 0xFF; + reg_val[REG_CHARGING] = 0xFF; + reg_val[REG_SC_0] = 0xFF; + reg_val[REG_SC_1] = 0xFF; + + ret = regmap_bulk_write(pca9481->regmap, PCA9481_REG_INT_DEVICE_0_MASK | PCA9481_REG_BIT_AI, + ®_val[0], REG_BUFFER_MAX); + if (ret < 0) + goto error; + + return 0; + +error: + pr_info("%s: i2c error, ret=%d\n", __func__, ret); + return ret; +} + +/* Check Active status */ +static int pca9481_check_error(struct pca9481_charger *pca9481) +{ + union power_supply_propval value = {0, }; + int ret; + unsigned int reg_val; + int vbatt; + int rt; + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481->chg_status = POWER_SUPPLY_STATUS_CHARGING; + pca9481->health_status = POWER_SUPPLY_HEALTH_GOOD; +#endif + + /* Read DEVICE_3_STS register */ + ret = pca9481_read_reg(pca9481, PCA9481_REG_DEVICE_3_STS, ®_val); + if (ret < 0) + goto error; + + /* Read VBAT_ADC */ + vbatt = pca9481_read_adc(pca9481, ADCCH_BATP_BATN); + + /* Check Active status */ + if ((reg_val & PCA9481_BIT_STATUS_CHANGE) == PCA9481_21SW_F11_MODE) { + /* PCA9481 is in 2:1 switching mode */ + /* Check whether the battery voltage is over the minimum voltage level or not */ + if (vbatt > DC_VBAT_MIN) { + /* Normal charging battery level */ + /* Check temperature regulation loop */ + /* Read DEVICE_2_STS register */ + ret = pca9481_read_reg(pca9481, PCA9481_REG_DEVICE_2_STS, ®_val); + if (reg_val & PCA9481_BIT_THEM_REG) { + /* Thermal regulation happened */ + pr_err("%s: Device is in temperature regulation\n", __func__); + ret = -EINVAL; + } else { + /* Normal temperature */ + ret = 0; + } + } else { + /* Abnormal battery level */ + pr_err("%s: Error abnormal battery voltage=%d\n", __func__, vbatt); + ret = -EINVAL; + } + } else { + /* PCA9481 is not in 2:1 switching mode - standby or shutdown state */ + /* Stop charging in timer_work */ + + /* Read all status register for debugging */ + u8 val[REG_BUFFER_MAX]; + u8 deg_val[10]; /* Dump for control register */ + + rt = pca9481_bulk_read_reg(pca9481, PCA9481_REG_DEVICE_0_STS | PCA9481_REG_BIT_AI, + &val[REG_DEVICE_0], REG_BUFFER_MAX); + if (rt < 0) + goto error; + pr_err("%s: Error reg[0x0F]=0x%x,[0x10]=0x%x,[0x11]=0x%x,[0x12]=0x%x,[0x13]=0x%x,[0x14]=0x%x,[0x15]=0x%x\n", + __func__, val[0], val[1], val[2], val[3], val[4], val[5], val[6]); + + /* Check status register */ + if ((val[REG_DEVICE_0] & PCA9481_BIT_VIN_VALID) != PCA9481_BIT_VIN_VALID) { + /* Invalid VIN or VOUT */ + if (val[REG_DEVICE_0] & PCA9481_BIT_VOUT_MAX_OV) { + pr_err("%s: VOUT MAX_OV\n", __func__); /* VOUT > VOUT_MAX */ + ret = -EINVAL; + } else if (val[REG_DEVICE_0] & PCA9481_BIT_RCP_DETECTED) { + pr_err("%s: RCP_DETECTED\n", __func__); /* Reverse Current Protection */ + ret = -EINVAL; + value.intval = true; + psy_do_property(pca9481->pdata->sec_dc_name, set, + POWER_SUPPLY_EXT_PROP_DC_RCP, value); + } else if (val[REG_DEVICE_0] & PCA9481_BIT_VIN_UNPLUG) { + pr_err("%s: VIN_UNPLUG\n", __func__); /* VIN < VIN_UNPLUG */ + ret = -EINVAL; + } else if (val[REG_DEVICE_0] & PCA9481_BIT_VIN_OVP) { + pr_err("%s: VIN_OVP\n", __func__); /* VIN > VIN_OVP */ + ret = -EINVAL; + } else if (val[REG_DEVICE_0] & PCA9481_BIT_VIN_OV_TRACKING) { + pr_err("%s: VIN_OV_TRACKING\n", __func__); /* VIN > VIN_OV_TRACKING */ + ret = -EAGAIN; + } else if (val[REG_DEVICE_0] & PCA9481_BIT_VIN_UV_TRACKING) { + pr_err("%s: VIN_UV_TRACKING\n", __func__); /* VIN < VIN_UV_TRACKING */ + ret = -EINVAL; + } else { + pr_err("%s: Invalid VIN or VOUT\n", __func__); + ret = -EINVAL; + } + } else if (val[REG_DEVICE_0] & PCA9481_BIT_RCP_DETECTED) { + /* Valid VIN and RCP happens - ignore it */ + /* If Vin increase, PCA9481 will exit RCP condition */ + pr_err("%s: RCP_DETECTED\n", __func__); /* Reverse Current Protection */ + /* Check charging state - Only retry in check active state */ + if (pca9481->charging_state == DC_STATE_CHECK_ACTIVE) + ret = -EAGAIN; + else + ret = -ERROR_DCRCP; + } else if (val[REG_DEVICE_1] & PCA9481_BIT_SINK_RCP_ENABLED) { + /* Valid VIN and RCP happens - ignore it */ + /* If Vin increase, PCA9481 will exit RCP condition */ + pr_err("%s: SINK_RCP_ENABLED\n", __func__); /* Isink_rcp Enabled */ + /* Check charging state - Only retry in check active state */ + if (pca9481->charging_state == DC_STATE_CHECK_ACTIVE) + ret = -EAGAIN; + else + ret = -ERROR_DCRCP; + } else if (val[REG_DEVICE_1] & PCA9481_BIT_SINK_RCP_TIMEOUT) { + /* Valid VIN and RCP happens - ignore it */ + /* If Vin increase, PCA9481 will exit RCP condition */ + pr_err("%s: SINK_RCP_TIMEOUT\n", __func__); /* discharge timer expired */ + /* Check charging state - Only retry in check active state */ + if (pca9481->charging_state == DC_STATE_CHECK_ACTIVE) + ret = -EAGAIN; + else + ret = -ERROR_DCRCP; + } else if (val[REG_DEVICE_1] & (PCA9481_BIT_NTC_0_DETECTED | PCA9481_BIT_NTC_1_DETECTED)) { + if (pca9481->pdata->ntc_en == 0) { + /* NTC protection function is disabled */ + /* ignore it */ + pr_err("%s: NTC_THRESHOLD - retry\n", __func__); /* NTC_THRESHOLD */ + ret = -EAGAIN; + } else { + pr_err("%s: NTC_THRESHOLD\n", __func__); /* NTC_THRESHOLD */ + ret = -EINVAL; + } + } else if (val[REG_DEVICE_1] & PCA9481_BIT_VIN_OCP_21_11) { + pr_err("%s: VIN_OCP_21_11\n", __func__); /* VIN_OCP */ + ret = -EINVAL; + } else if (val[REG_DEVICE_2] & PCA9481_BIT_THEM_REG) { + pr_err("%s: THEM_REGULATION\n", __func__); /* Thermal Regulation */ + ret = -EINVAL; + } else if (val[REG_DEVICE_2] & PCA9481_BIT_THSD) { + pr_err("%s: THERMAL_SHUTDOWN\n", __func__); /* Thermal Shutdown */ + ret = -EINVAL; + } else if (val[REG_DEVICE_2] & PCA9481_BIT_WATCHDOG_TIMER_OUT) { + pr_err("%s: WATCHDOG_TIMER_OUT\n", __func__); /* Watchdog timer out */ + ret = -EINVAL; + } else if (val[REG_DEVICE_3] & PCA9481_BIT_VFET_IN_OVP) { + pr_err("%s: VFET_IN_OVP\n", __func__); /* VFET_IN > VFET_IN_OVP */ + ret = -EINVAL; + } else if (val[REG_CHARGING] & PCA9481_BIT_CHARGER_SAFETY_TIMER) { + pr_err("%s: CHARGER_SAFETY_TIMER_OUT\n", __func__); /* Charger Safety Timer is expired */ + ret = -EINVAL; + } else if (val[REG_CHARGING] & PCA9481_BIT_VBAT_OVP) { + pr_err("%s: VBAT_OVP\n", __func__); /* VBAT > VBAT_OVP */ + ret = -EINVAL; + } else if (val[REG_SC_0] & PCA9481_BIT_PHASE_B_FAULT) { + pr_err("%s: PHASE_B_FAULT\n", __func__); /* Phase B CFLY short */ + ret = -EINVAL; + } else if (val[REG_SC_0] & PCA9481_BIT_PHASE_A_FAULT) { + pr_err("%s: PHASE_A_FAULT\n", __func__); /* Phase A CFLY short */ + ret = -EINVAL; + } else if (val[REG_SC_0] & PCA9481_BIT_PIN_SHORT) { + pr_err("%s: PIN_SHORT\n", __func__); /* CFLY, SW, VIN, and OVP_OUT short */ + ret = -EINVAL; + } else if (val[REG_SC_1] & PCA9481_BIT_OVPOUT_ERRLO) { + pr_err("%s: OVPOUT_ERRLO\n", __func__); /* OVP_OUT is too low */ + ret = -EINVAL; + } else { + pr_err("%s: Power state error\n", __func__); /* Power State error */ + /* Check charging state - Only retry in check active state */ + if (pca9481->charging_state == DC_STATE_CHECK_ACTIVE) + ret = -EAGAIN; /* retry */ + else + ret = -EINVAL; + } + + /* Read all interrupt registers for debugging */ + rt = regmap_bulk_read(pca9481->regmap, PCA9481_REG_INT_DEVICE_0 | PCA9481_REG_BIT_AI, + &val[REG_DEVICE_0], REG_BUFFER_MAX); + if (rt < 0) + goto error; + pr_err("%s: interrupt reg[0x01]=0x%x,[0x02]=0x%x,[0x03]=0x%x,[0x04]=0x%x,[0x05]=0x%x,[0x06]=0x%x,[0x07]=0x%x\n", + __func__, val[0], val[1], val[2], val[3], val[4], val[5], val[6]); + + /* Read all control registers for debugging */ + /* Read device, auto restart, and rcp control registers for debugging */ + rt = regmap_bulk_read(pca9481->regmap, PCA9481_REG_DEVICE_CNTL_0 | PCA9481_REG_BIT_AI, + °_val[0], 7); + if (rt < 0) + goto error; + pr_info("%s: control reg[0x16]=0x%x,[0x17]=0x%x,[0x18]=0x%x,[0x19]=0x%x,[0x1A]=0x%x,[0x1B]=0x%x,[0x1C]=0x%x\n", + __func__, deg_val[0], deg_val[1], deg_val[2], deg_val[3], deg_val[4], deg_val[5], deg_val[6]); + + /* Read charging control registers for debugging */ + rt = regmap_bulk_read(pca9481->regmap, PCA9481_REG_CHARGING_CNTL_0 | PCA9481_REG_BIT_AI, + °_val[0], 7); + if (rt < 0) + goto error; + pr_info("%s: control reg[0x1D]=0x%x,[0x1E]=0x%x,[0x1F]=0x%x,[0x20]=0x%x,[0x21]=0x%x,[0x22]=0x%x,[0x23]=0x%x\n", + __func__, deg_val[0], deg_val[1], deg_val[2], deg_val[3], deg_val[4], deg_val[5], deg_val[6]); + + /* Read ntc, sc, and adc control registers for debugging */ + rt = regmap_bulk_read(pca9481->regmap, PCA9481_REG_NTC_0_CNTL | PCA9481_REG_BIT_AI, + °_val[0], 9); + if (rt < 0) + goto error; + pr_info("%s: control reg[0x24]=0x%x,[0x25]=0x%x,[0x26]=0x%x,[0x27]=0x%x,[0x28]=0x%x,[0x29]=0x%x\n", + __func__, deg_val[0], deg_val[1], deg_val[2], deg_val[3], deg_val[4], deg_val[5]); + pr_info("%s: control reg[0x2A]=0x%x,[0x2B]=0x%x,[0x2C]=0x%x\n", + __func__, deg_val[6], deg_val[7], deg_val[8]); + + /* Read ADC register for debugging */ + rt = regmap_bulk_read(pca9481->regmap, PCA9481_REG_ADC_READ_VIN_0 | PCA9481_REG_BIT_AI, + °_val[0], 10); + if (rt < 0) + goto error; + pr_info("%s: adc reg[0x2D]=0x%x,[0x2E]=0x%x,[0x2F]=0x%x,[0x30]=0x%x,[0x31]=0x%x,[0x32]=0x%x\n", + __func__, deg_val[0], deg_val[1], deg_val[2], deg_val[3], deg_val[4], deg_val[5]); + pr_info("%s: adc reg[0x33]=0x%x,[0x34]=0x%x,[0x35]=0x%x,[0x36]=0x%x\n", + __func__, deg_val[6], deg_val[7], deg_val[8], deg_val[9]); + + rt = regmap_bulk_read(pca9481->regmap, PCA9481_REG_ADC_READ_NTC_0 | PCA9481_REG_BIT_AI, + °_val[0], 8); + if (rt < 0) + goto error; + pr_info("%s: adc reg[0x37]=0x%x,[0x38]=0x%x,[0x39]=0x%x,[0x3A]=0x%x,[0x3B]=0x%x,[0x3C]=0x%x\n", + __func__, deg_val[0], deg_val[1], deg_val[2], deg_val[3], deg_val[4], deg_val[5]); + pr_info("%s: adc reg[0x3D]=0x%x,[0x3E]=0x%x\n", __func__, deg_val[6], deg_val[7]); + } + +error: + /* Check RCP DONE case */ + if (ret == -ERROR_DCRCP) { + /* Check DC state first */ + if ((pca9481->charging_state == DC_STATE_START_CV) || + (pca9481->charging_state == DC_STATE_CV_MODE)) { + /* Now present state is start_cv or cv_mode */ + /* Compare VBAT_ADC with Vfloat threshold */ + if (pca9481->prev_vbat > pca9481->vfloat) { + /* Keep RCP DONE error */ + pr_info("%s: Keep RCP_DONE error type(%d)\n", + __func__, ret); + } else { + /* Overwrite error type to -EINVAL */ + ret = -EINVAL; + pr_info("%s: Overwrite RCP_DONE error, prev_vbat=%duV\n", + __func__, pca9481->prev_vbat); + } + } else { + /* Overwrite error type to -EINVAL */ + ret = -EINVAL; + pr_info("%s: Overwrite RCP_DONE error, charging_state=%d\n", + __func__, pca9481->charging_state); + } + if (ret == -EINVAL) { + value.intval = true; + psy_do_property(pca9481->pdata->sec_dc_name, set, + POWER_SUPPLY_EXT_PROP_DC_RCP, value); + } + } + pr_info("%s: Active Status=%d\n", __func__, ret); + return ret; +} + +/* Check Reverse active status */ +static int pca9481_check_reverse_error(struct pca9481_charger *pca9481) +{ + int ret; + unsigned int reg_val; + int rt; + + /* Read DEVICE_3_STS register */ + ret = regmap_read(pca9481->regmap, PCA9481_REG_DEVICE_3_STS, ®_val); + if (ret < 0) + goto error; + + /* Check Active status */ + if ((reg_val & PCA9481_BIT_STATUS_CHANGE) == PCA9481_12SW_R11_MODE) { + /* PCA9481 is in 1:2 switching mode or reverse 1:1 mode */ + /* Check temperature regulation loop */ + /* Read DEVICE_2_STS register */ + ret = regmap_read(pca9481->regmap, PCA9481_REG_DEVICE_2_STS, ®_val); + if (reg_val & PCA9481_BIT_THEM_REG) { + /* Thermal regulation happened */ + pr_err("%s: Device is in temperature regulation\n", __func__); + ret = -EINVAL; + } else { + /* Normal temperature */ + ret = 0; + } + } else { + /* PCA9481 is not in 1:2 switching mode or reverse 1:1 mode - standby or shutdown state */ + /* Stop charging in timer_work */ + + /* Read all status register for debugging */ + u8 val[REG_BUFFER_MAX]; + u8 deg_val[10]; /* Dump for control register */ + + ret = regmap_bulk_read(pca9481->regmap, PCA9481_REG_DEVICE_0_STS | PCA9481_REG_BIT_AI, + &val[REG_DEVICE_0], REG_BUFFER_MAX); + pr_err("%s: Error reg[0x0F]=0x%x,[0x10]=0x%x,[0x11]=0x%x,[0x12]=0x%x,[0x13]=0x%x,[0x14]=0x%x,[0x15]=0x%x\n", + __func__, val[0], val[1], val[2], val[3], val[4], val[5], val[6]); + + /* Check status register */ + if (val[REG_DEVICE_0] & PCA9481_BIT_VOUT_MAX_OV) { + pr_err("%s: VOUT MAX_OV\n", __func__); /* VOUT > VOUT_MAX */ + ret = -EINVAL; + } else if (val[REG_DEVICE_1] & (PCA9481_BIT_NTC_0_DETECTED | PCA9481_BIT_NTC_1_DETECTED)) { + if (pca9481->pdata->ntc_en == 0) { + /* NTC protection function is disabled */ + /* ignore it */ + pr_err("%s: NTC_THRESHOLD - ignore\n", __func__); /* NTC_THRESHOLD */ + ret = -EAGAIN; + } else { + pr_err("%s: NTC_THRESHOLD\n", __func__); /* NTC_THRESHOLD */ + ret = -EINVAL; + } + } else if (val[REG_DEVICE_2] & PCA9481_BIT_VIN_OCP_12_11) { + pr_err("%s: VIN_OCP_12_11\n", __func__); /* VIN_OCP */ + ret = -EINVAL; + } else if (val[REG_DEVICE_2] & PCA9481_BIT_THSD) { + pr_err("%s: THERMAL_SHUTDOWN\n", __func__); /* Thermal Shutdown */ + ret = -EINVAL; + } else if (val[REG_DEVICE_2] & PCA9481_BIT_WATCHDOG_TIMER_OUT) { + pr_err("%s: WATCHDOG_TIMER_OUT\n", __func__); /* Watchdog timer out */ + ret = -EINVAL; + } else if (val[REG_SC_0] & PCA9481_BIT_PHASE_B_FAULT) { + pr_err("%s: PHASE_B_FAULT\n", __func__); /* Phase B CFLY short */ + ret = -EINVAL; + } else if (val[REG_SC_0] & PCA9481_BIT_PHASE_A_FAULT) { + pr_err("%s: PHASE_A_FAULT\n", __func__); /* Phase A CFLY short */ + ret = -EINVAL; + } else if (val[REG_SC_0] & PCA9481_BIT_PIN_SHORT) { + pr_err("%s: PIN_SHORT\n", __func__); /* CFLY, SW, VIN, and OVP_OUT short */ + ret = -EINVAL; + } else if (val[REG_SC_1] & PCA9481_BIT_OVPOUT_ERRLO) { + pr_err("%s: OVPOUT_ERRLO\n", __func__); /* OVP_OUT is too low */ + ret = -EINVAL; + } else if (val[REG_SC_1] & PCA9481_BIT_REVERSE_SW_SS_OC) { + pr_err("%s: REVERSE_SW_SS_OC\n", __func__); /* OC detect during 1:2 or reverse 1:1 mode soft-start */ + ret = -EINVAL; + } else { + pr_err("%s: Power state error\n", __func__); /* Power State error */ + ret = -EAGAIN; /* retry */ + } + + /* Read all interrupt registers for debugging */ + rt = regmap_bulk_read(pca9481->regmap, PCA9481_REG_INT_DEVICE_0 | PCA9481_REG_BIT_AI, + &val[REG_DEVICE_0], REG_BUFFER_MAX); + if (rt < 0) + goto error; + pr_err("%s: interrupt reg[0x01]=0x%x,[0x02]=0x%x,[0x03]=0x%x,[0x04]=0x%x,[0x05]=0x%x,[0x06]=0x%x,[0x07]=0x%x\n", + __func__, val[0], val[1], val[2], val[3], val[4], val[5], val[6]); + + /* Read all control registers for debugging */ + rt = regmap_bulk_read(pca9481->regmap, PCA9481_REG_INT_DEVICE_0 | PCA9481_REG_BIT_AI, + &val[REG_DEVICE_0], REG_BUFFER_MAX); + if (rt < 0) + goto error; + pr_err("%s: interrupt reg[0x01]=0x%x,[0x02]=0x%x,[0x03]=0x%x,[0x04]=0x%x,[0x05]=0x%x,[0x06]=0x%x,[0x07]=0x%x\n", + __func__, val[0], val[1], val[2], val[3], val[4], val[5], val[6]); + /* Read device, auto restart, and rcp control registers for debugging */ + rt = regmap_bulk_read(pca9481->regmap, PCA9481_REG_DEVICE_CNTL_0 | PCA9481_REG_BIT_AI, + °_val[0], 7); + if (rt < 0) + goto error; + pr_info("%s: control reg[0x16]=0x%x,[0x17]=0x%x,[0x18]=0x%x,[0x19]=0x%x,[0x1A]=0x%x,[0x1B]=0x%x,[0x1C]=0x%x\n", + __func__, deg_val[0], deg_val[1], deg_val[2], deg_val[3], deg_val[4], deg_val[5], deg_val[6]); + + /* Read charging control registers for debugging */ + rt = regmap_bulk_read(pca9481->regmap, PCA9481_REG_CHARGING_CNTL_0 | PCA9481_REG_BIT_AI, + °_val[0], 7); + if (rt < 0) + goto error; + pr_info("%s: control reg[0x1D]=0x%x,[0x1E]=0x%x,[0x1F]=0x%x,[0x20]=0x%x,[0x21]=0x%x,[0x22]=0x%x,[0x23]=0x%x\n", + __func__, deg_val[0], deg_val[1], deg_val[2], deg_val[3], deg_val[4], deg_val[5], deg_val[6]); + + /* Read ntc, sc, and adc control registers for debugging */ + rt = regmap_bulk_read(pca9481->regmap, PCA9481_REG_NTC_0_CNTL | PCA9481_REG_BIT_AI, + °_val[0], 9); + if (rt < 0) + goto error; + pr_info("%s: control reg[0x24]=0x%x,[0x25]=0x%x,[0x26]=0x%x,[0x27]=0x%x,[0x28]=0x%x,[0x29]=0x%x\n", + __func__, deg_val[0], deg_val[1], deg_val[2], deg_val[3], deg_val[4], deg_val[5]); + pr_info("%s: control reg[0x2A]=0x%x,[0x2B]=0x%x,[0x2C]=0x%x\n", + __func__, deg_val[6], deg_val[7], deg_val[8]); + + /* Read ADC register for debugging */ + rt = regmap_bulk_read(pca9481->regmap, PCA9481_REG_ADC_READ_VIN_0 | PCA9481_REG_BIT_AI, + °_val[0], 10); + if (rt < 0) + goto error; + pr_info("%s: adc reg[0x2D]=0x%x,[0x2E]=0x%x,[0x2F]=0x%x,[0x30]=0x%x,[0x31]=0x%x,[0x32]=0x%x\n", + __func__, deg_val[0], deg_val[1], deg_val[2], deg_val[3], deg_val[4], deg_val[5]); + pr_info("%s: adc reg[0x33]=0x%x,[0x34]=0x%x,[0x35]=0x%x,[0x36]=0x%x\n", + __func__, deg_val[6], deg_val[7], deg_val[8], deg_val[9]); + + rt = regmap_bulk_read(pca9481->regmap, PCA9481_REG_ADC_READ_NTC_0 | PCA9481_REG_BIT_AI, + °_val[0], 8); + if (rt < 0) + goto error; + pr_info("%s: adc reg[0x37]=0x%x,[0x38]=0x%x,[0x39]=0x%x,[0x3A]=0x%x,[0x3B]=0x%x,[0x3C]=0x%x\n", + __func__, deg_val[0], deg_val[1], deg_val[2], deg_val[3], deg_val[4], deg_val[5]); + pr_info("%s: adc reg[0x3D]=0x%x,[0x3E]=0x%x\n", __func__, deg_val[6], deg_val[7]); + } + +error: +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + if (ret == -EINVAL) { + pca9481->chg_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + pca9481->health_status = POWER_SUPPLY_EXT_HEALTH_DC_ERR; + } +#endif + pr_info("%s: Active Status=%d\n", __func__, ret); + return ret; +} + +/* Check DC Mode status */ +static int pca9481_check_dcmode_status(struct pca9481_charger *pca9481) +{ + unsigned int reg_val; + int ret, i; + int vbatt; + + /* Read CHARGING_STS */ + ret = pca9481_read_reg(pca9481, PCA9481_REG_CHARGING_STS, ®_val); + if (ret < 0) + goto error; + + /* Read battery voltage from fuelgauge */ + vbatt = pca9481_read_adc(pca9481, ADCCH_BATP_BATN); + if (vbatt < 0) { + ret = vbatt; + goto error; + } + + /* Check CHARGING_STS */ + if (vbatt > pca9481->vfloat) { + ret = DCMODE_VFLT_LOOP; + pr_info("%s: FG Vnow=%d, FG Vfloat\n", __func__, vbatt); + } else if (reg_val & PCA9481_BIT_VBAT_REG_LOOP) + ret = DCMODE_VFLT_LOOP; + else if (reg_val & PCA9481_BIT_I_VBAT_CC_LOOP) + ret = DCMODE_CHG_LOOP; + else if (reg_val & PCA9481_BIT_I_VIN_CC_LOOP) { + ret = DCMODE_IIN_LOOP; + /* Check IIN_LOOP again to avoid unstable IIN_LOOP period */ + for (i = 0; i < 4; i++) { + /* Wait 2ms */ + usleep_range(2000, 3000); + /* Read CHARGING_STS again */ + ret = pca9481_read_reg(pca9481, PCA9481_REG_CHARGING_STS, ®_val); + if (ret < 0) + goto error; + /* Overwrite status */ + ret = DCMODE_IIN_LOOP; + /* Check CHARGING_STS again */ + if ((reg_val & PCA9481_BIT_I_VIN_CC_LOOP) != PCA9481_BIT_I_VIN_CC_LOOP) { + /* Now pca9481 is in unstable IIN_LOOP period */ + /* Ignore IIN_LOOP status */ + pr_info("%s: Unstable IIN_LOOP\n", __func__); + ret = DCMODE_LOOP_INACTIVE; + break; + } + } + } else + ret = DCMODE_LOOP_INACTIVE; + +error: + pr_info("%s: DCMODE Status=%d\n", __func__, ret); + return ret; +} + +/* Stop Charging */ +static int pca9481_stop_charging(struct pca9481_charger *pca9481) +{ + int ret = 0; + int val; + + /* Check the current state and pca9481 enable status */ + if ((pca9481->charging_state != DC_STATE_NO_CHARGING) || + (pca9481->enable == true)) { + /* Recover switching charger ICL */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_switching_charger(pca9481, true); +#else + ret = pca9481_set_switching_charger(true, SWCHG_ICL_NORMAL, + pca9481->ichg_cfg, + pca9481->vfloat); +#endif + if (ret < 0) { + pr_err("%s: Error-set_switching charger ICL\n", __func__); + goto error; + } + + /* Stop Direct charging */ + cancel_delayed_work(&pca9481->timer_work); + cancel_delayed_work(&pca9481->pps_work); + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_ID_NONE; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + + /* Clear parameter */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_NO_CHARGING); + pca9481_init_adc_val(pca9481, -1); +#else + pca9481->charging_state = DC_STATE_NO_CHARGING; +#endif + pca9481->ret_state = DC_STATE_NO_CHARGING; + pca9481->ta_target_vol = TA_MAX_VOL; + pca9481->prev_iin = 0; + pca9481->prev_inc = INC_NONE; + mutex_lock(&pca9481->lock); + pca9481->req_new_iin = false; + pca9481->req_new_vfloat = false; + mutex_unlock(&pca9481->lock); + pca9481->chg_mode = CHG_NO_DC_MODE; + pca9481->ta_ctrl = TA_CTRL_CL_MODE; + + /* Keep iin_cfg, ichg_cfg, vfloat and iin_topoff to the current value */ + /* Keep new vfloat and new iin */ + + /* Clear new DC mode and DC mode */ + pca9481->new_dc_mode = PTM_NONE; + pca9481->dc_mode = PTM_NONE; + pca9481->req_new_dc_mode = false; + + /* Clear switching frequency change sequence */ + pca9481->req_sw_freq = REQ_SW_FREQ_0; + + /* Set vfloat decrement flag to false */ + pca9481->dec_vfloat = false; + + /* Clear reverse mode */ + pca9481->rev_mode = POWER_SUPPLY_DC_REVERSE_STOP; + pca9481->iin_rev = 0; + + /* Clear previous VBAT_ADC */ + pca9481->prev_vbat = 0; + + /* Clear charging done counter */ + pca9481->done_cnt = 0; + + /* Disable VIN_OCP_12_11_EN */ + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_CHARGING_CNTL_5, + PCA9481_BIT_VIN_OCP_12_11_EN, 0); + if (ret < 0) { + pr_err("%s: Error-set VIN_OCP_12_11_EN\n", __func__); + goto error; + } + + /* Enable reverse current detection */ + ret = pca9481_update_reg(pca9481, PCA9481_REG_RCP_CNTL, + PCA9481_BIT_RCP_EN, PCA9481_BIT_RCP_EN); + if (ret < 0) { + pr_err("%s: Error-set RCP detection\n", __func__); + goto error; + } + + /* Enable OV_TRACKING_EN */ + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_SC_CNTL_2, + PCA9481_BIT_OV_TRACKING_EN, PCA9481_BIT_OV_TRACKING_EN); + if (ret < 0) { + pr_err("%s: Error-set OV_TRACKING_EN\n", __func__); + goto error; + } + + /* Clear retry counter */ + pca9481->retry_cnt = 0; + + /* Clear CV transition sequence */ + pca9481->cv_trans = CV_TRANS_0; + /* Clear TA target current */ + pca9481->ta_target_cur = 0; + + /* Disable VBAT_ADC channel */ + val = 0; + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_ADC_EN_CNTL_0, + PCA9481_BIT_ADC_READ_BATP_BATN_EN, val); + if (ret < 0) { + pr_err("%s: Error-set ADC_EN_CNTL\n", __func__); + goto error; + } + + /* Recover ADC mode to AUTO mode */ + /* Configure ADC operation mode to Auto mode */ + val = AUTO_MODE << MASK2SHIFT(PCA9481_BIT_ADC_MODE_CFG); + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_ADC_CNTL, + PCA9481_BIT_ADC_MODE_CFG, val); + if (ret < 0) { + pr_err("%s: Error-set ADC_MODE\n", __func__); + goto error; + } + /* Read ADC_CNTL register to confirm recover */ + ret = regmap_read(pca9481->regmap, PCA9481_REG_ADC_CNTL, &val); + pr_info("%s: ADC_CTRL : 0x%02x\n", __func__, val); + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + /* Set watchdog timer disable */ + pca9481_set_wdt_enable(pca9481, WDT_DISABLE); +#endif + ret = pca9481_set_charging(pca9481, false); + if (ret < 0) { + pr_err("%s: Error-set_charging(main)\n", __func__); + goto error; + } + } + +error: + __pm_relax(pca9481->monitor_wake_lock); + pr_info("%s: END, ret=%d\n", __func__, ret); + + return ret; +} + +/* Compensate TA current for the target input current */ +static int pca9481_set_ta_current_comp(struct pca9481_charger *pca9481) +{ + int iin, ichg; + + /* Read IIN ADC */ + iin = pca9481_read_adc(pca9481, ADCCH_VIN_CURRENT); + /* Read ICHG ADC */ + ichg = pca9481_read_adc(pca9481, ADCCH_BAT_CURRENT); + + pr_info("%s: iin=%d, ichg=%d\n", __func__, iin, ichg); + + /* Compare IIN ADC with target input current */ + if (iin > (pca9481->iin_cc + IIN_CC_COMP_OFFSET)) { + /* TA current is higher than the target input current */ + /* Compare TA current with IIN_CC - LOW_OFFSET */ + if (pca9481->ta_cur > pca9481->iin_cc - TA_CUR_LOW_OFFSET) { + /* TA current is higher than IIN_CC - LOW_OFFSET */ + /* Assume that TA operation mode is CL mode, so decrease TA current */ + /* Decrease TA current (50mA) */ + pca9481->ta_cur = pca9481->ta_cur - PD_MSG_TA_CUR_STEP; + pr_info("%s: Comp. Cont1: ta_cur=%d\n", __func__, pca9481->ta_cur); + } else { + /* TA current is already lower than IIN_CC - LOW_OFFSET */ + /* IIN_ADC is stiil in invalid range even though TA current is less than IIN_CC - LOW_OFFSET */ + /* TA has abnormal behavior */ + /* Decrease TA voltage (20mV) */ + pca9481->ta_vol = pca9481->ta_vol - PD_MSG_TA_VOL_STEP; + pr_info("%s: Comp. Cont2: ta_vol=%d\n", __func__, pca9481->ta_vol); + /* Update TA target voltage */ + pca9481->ta_target_vol = pca9481->ta_vol; + } + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } else { + if (iin < (pca9481->iin_cc - IIN_CC_COMP_OFFSET)) { + /* TA current is lower than the target input current */ + /* Compare present TA voltage with TA maximum voltage first */ + if (pca9481->ta_vol == pca9481->ta_max_vol) { + /* TA voltage is already the maximum voltage */ + /* Compare present TA current with TA maximum current */ + if (pca9481->ta_cur == pca9481->ta_max_cur) { + /* Both of present TA voltage and current are already maximum values */ + pr_info("%s: Comp. End1(Max value): ta_vol=%d, ta_cur=%d\n", + __func__, pca9481->ta_vol, pca9481->ta_cur); + /* Set timer */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_CCMODE; + pca9481->timer_period = CCMODE_CHECK_T; + mutex_unlock(&pca9481->lock); + } else { + /* TA voltage is maximum voltage, but TA current is not maximum current */ + /* Increase TA current (50mA) */ + pca9481->ta_cur = pca9481->ta_cur + PD_MSG_TA_CUR_STEP; + if (pca9481->ta_cur > pca9481->ta_max_cur) + pca9481->ta_cur = pca9481->ta_max_cur; + pr_info("%s: Comp. Cont3: ta_cur=%d\n", __func__, pca9481->ta_cur); + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + + /* Set TA increment flag */ + pca9481->prev_inc = INC_TA_CUR; + } + } else { + /* TA voltage is not maximum voltage */ + /* Compare IIN ADC with previous IIN ADC + 20mA */ + if (iin > (pca9481->prev_iin + IIN_ADC_OFFSET)) { + /* In this case, TA voltage is not enough to supply */ + /* the operating current of RDO. So, increase TA voltage */ + /* Increase TA voltage (20mV) */ + pca9481->ta_vol = pca9481->ta_vol + PD_MSG_TA_VOL_STEP; + if (pca9481->ta_vol > pca9481->ta_max_vol) + pca9481->ta_vol = pca9481->ta_max_vol; + pr_info("%s: Comp. Cont4: ta_vol=%d\n", + __func__, pca9481->ta_vol); + /* Update TA target voltage */ + pca9481->ta_target_vol = pca9481->ta_vol; + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + + /* Set TA increment flag */ + pca9481->prev_inc = INC_TA_VOL; + } else { + /* Input current increment is too low */ + /* It is possible that TA is in current limit mode or has low TA voltage */ + /* Increase TA current or voltage */ + /* Check the previous TA increment */ + if (pca9481->prev_inc == INC_TA_VOL) { + /* The previous increment is TA voltage, but input current does not increase */ + /* Try to increase TA current */ + /* Compare present TA current with TA maximum current */ + if (pca9481->ta_cur == pca9481->ta_max_cur) { + /* TA current is already the maximum current */ + + /* Increase TA voltage (20mV) */ + pca9481->ta_vol = pca9481->ta_vol + PD_MSG_TA_VOL_STEP; + if (pca9481->ta_vol > pca9481->ta_max_vol) + pca9481->ta_vol = pca9481->ta_max_vol; + pr_info("%s: Comp. Cont5: ta_vol=%d\n", + __func__, pca9481->ta_vol); + /* Update TA target voltage */ + pca9481->ta_target_vol = pca9481->ta_vol; + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + + /* Set TA increment flag */ + pca9481->prev_inc = INC_TA_VOL; + } else { + /* Check the present TA current */ + /* Consider tolerance offset(100mA) */ + if (pca9481->ta_cur >= (pca9481->iin_cc + TA_IIN_OFFSET)) { + /* Maybe TA supply current is enough, but TA voltage is low */ + /* Increase TA voltage (20mV) */ + pca9481->ta_vol = pca9481->ta_vol + PD_MSG_TA_VOL_STEP; + if (pca9481->ta_vol > pca9481->ta_max_vol) + pca9481->ta_vol = pca9481->ta_max_vol; + pr_info("%s: Comp. Cont6: ta_vol=%d\n", + __func__, pca9481->ta_vol); + /* Update TA target voltage */ + pca9481->ta_target_vol = pca9481->ta_vol; + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + + /* Set TA increment flag */ + pca9481->prev_inc = INC_TA_VOL; + } else { + /* It is possible that TA is in current limit mode */ + /* Increase TA current (50mA) */ + pca9481->ta_cur = pca9481->ta_cur + PD_MSG_TA_CUR_STEP; + if (pca9481->ta_cur > pca9481->ta_max_cur) + pca9481->ta_cur = pca9481->ta_max_cur; + pr_info("%s: Comp. Cont7: ta_cur=%d\n", + __func__, pca9481->ta_cur); + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + + /* Set TA increment flag */ + pca9481->prev_inc = INC_TA_CUR; + } + } + } else { + /* The previous increment is TA current, but input current does not increase */ + /* Try to increase TA voltage */ + /* Increase TA voltage (20mV) */ + pca9481->ta_vol = pca9481->ta_vol + PD_MSG_TA_VOL_STEP; + if (pca9481->ta_vol > pca9481->ta_max_vol) + pca9481->ta_vol = pca9481->ta_max_vol; + pr_info("%s: Comp. Cont8: ta_vol=%d\n", + __func__, pca9481->ta_vol); + /* Update TA target voltage */ + pca9481->ta_target_vol = pca9481->ta_vol; + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + + /* Set TA increment flag */ + pca9481->prev_inc = INC_TA_VOL; + } + } + } + } else { + /* IIN ADC is in valid range */ + /* IIN_CC - 50mA < IIN ADC < IIN_CC + 50mA */ + pr_info("%s: Comp. End2(valid): ta_vol=%d, ta_cur=%d\n", + __func__, pca9481->ta_vol, pca9481->ta_cur); + /* Clear TA increment flag */ + pca9481->prev_inc = INC_NONE; + /* Set timer */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_CCMODE; + pca9481->timer_period = CCMODE_CHECK_T; + mutex_unlock(&pca9481->lock); + } + } + + /* Save previous iin adc */ + pca9481->prev_iin = iin; + + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + + return 0; +} + +/* Compensate TA current for constant power mode */ +static int pca9481_set_ta_current_comp2(struct pca9481_charger *pca9481) +{ + int iin, ichg; + unsigned int val; + unsigned int iin_apdo; + + /* Read IIN ADC */ + iin = pca9481_read_adc(pca9481, ADCCH_VIN_CURRENT); + /* Read ICHG ADC */ + ichg = pca9481_read_adc(pca9481, ADCCH_BAT_CURRENT); + + pr_info("%s: iin=%d, ichg=%d\n", __func__, iin, ichg); + + /* Compare IIN ADC with target input current */ + if (iin > (pca9481->iin_cfg + IIN_CC_COMP_OFFSET)) { + /* TA current is higher than the target input current */ + /* Compare TA current with IIN_CC - LOW_OFFSET */ + if (pca9481->ta_cur > pca9481->iin_cc - TA_CUR_LOW_OFFSET) { + /* TA current is higher than IIN_CC - LOW_OFFSET */ + /* Assume that TA operation mode is CL mode, so decrease TA current */ + /* Decrease TA current (50mA) */ + pca9481->ta_cur = pca9481->ta_cur - PD_MSG_TA_CUR_STEP; + pr_info("%s: Comp. Cont1: ta_cur=%d\n", __func__, pca9481->ta_cur); + } else { + /* TA current is already lower than IIN_CC - LOW_OFFSET */ + /* IIN_ADC is stiil in invalid range even though TA current is less than IIN_CC - LOW_OFFSET */ + /* TA has abnormal behavior */ + /* Decrease TA voltage (20mV) */ + pca9481->ta_vol = pca9481->ta_vol - PD_MSG_TA_VOL_STEP; + pr_info("%s: Comp. Cont2: ta_vol=%d\n", __func__, pca9481->ta_vol); + /* Update TA target voltage */ + pca9481->ta_target_vol = pca9481->ta_vol; + } + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } else if (iin < (pca9481->iin_cc - IIN_CC_COMP_OFFSET_CP)) { + /* TA current is lower than the target input current */ + /* IIN_ADC < IIN_CC -20mA */ + if (pca9481->ta_vol == pca9481->ta_max_vol) { + /* Check IIN_ADC < IIN_CC - 50mA */ + if (iin < (pca9481->iin_cc - IIN_CC_COMP_OFFSET)) { + /* Compare the TA current with IIN_CC and maximum current of APDO */ + if ((pca9481->ta_cur >= (pca9481->iin_cc/pca9481->chg_mode)) || + (pca9481->ta_cur == pca9481->ta_max_cur)) { + /* TA current is higher than IIN_CC or maximum TA current */ + /* Set new IIN_CC to IIN_CC - 50mA */ + pca9481->iin_cc = pca9481->iin_cc - IIN_CC_COMP_OFFSET; + /* Set new TA_MAX_VOL to TA_MAX_PWR/IIN_CC */ + /* Adjust new IIN_CC with APDO resolution */ + iin_apdo = pca9481->iin_cc/PD_MSG_TA_CUR_STEP; + iin_apdo = iin_apdo*PD_MSG_TA_CUR_STEP; + val = pca9481->ta_max_pwr/(iin_apdo/pca9481->chg_mode/1000); /* mV */ + val = val*1000/PD_MSG_TA_VOL_STEP; /* Adjust values with APDO resolution(20mV) */ + val = val*PD_MSG_TA_VOL_STEP; /* uV */ + /* Set new TA_MAX_VOL */ + pca9481->ta_max_vol = MIN(val, TA_MAX_VOL*pca9481->chg_mode); + /* Increase TA voltage(40mV) */ + pca9481->ta_vol = pca9481->ta_vol + PD_MSG_TA_VOL_STEP*2; + if (pca9481->ta_vol > pca9481->ta_max_vol) + pca9481->ta_vol = pca9481->ta_max_vol; + pr_info("%s: Comp. Cont3: ta_vol=%d\n", __func__, pca9481->ta_vol); + /* Update TA target voltage */ + pca9481->ta_target_vol = pca9481->ta_vol; + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } else { + /* TA current is less than IIN_CC and not maximum current */ + /* Increase TA current (50mA) */ + pca9481->ta_cur = pca9481->ta_cur + PD_MSG_TA_CUR_STEP; + if (pca9481->ta_cur > pca9481->ta_max_cur) + pca9481->ta_cur = pca9481->ta_max_cur; + pr_info("%s: Comp. Cont4: ta_cur=%d\n", __func__, pca9481->ta_cur); + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } + } else { + /* Wait for next current step compensation */ + /* IIN_CC - 50mA < IIN ADC < IIN_CC - 20mA */ + pr_info("%s: Comp.(wait): ta_vol=%d\n", __func__, pca9481->ta_vol); + /* Set timer */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_CCMODE; + pca9481->timer_period = CCMODE_CHECK_T; + mutex_unlock(&pca9481->lock); + } + } else { + /* Increase TA voltage(40mV) */ + pca9481->ta_vol = pca9481->ta_vol + PD_MSG_TA_VOL_STEP*2; + if (pca9481->ta_vol > pca9481->ta_max_vol) + pca9481->ta_vol = pca9481->ta_max_vol; + pr_info("%s: Comp. Cont5: ta_vol=%d\n", __func__, pca9481->ta_vol); + /* Update TA target voltage */ + pca9481->ta_target_vol = pca9481->ta_vol; + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } + } else { + /* IIN ADC is in valid range */ + /* IIN_CC - 20mA < IIN ADC < IIN_CFG + 50mA */ + pr_info("%s: Comp. End(valid): ta_vol=%d\n", __func__, pca9481->ta_vol); + /* Set timer */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_CCMODE; + pca9481->timer_period = CCMODE_CHECK_T; + mutex_unlock(&pca9481->lock); + } + + /* Save previous iin adc */ + pca9481->prev_iin = iin; + + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + + return 0; +} + +/* Compensate TA voltage for the target input current */ +static int pca9481_set_ta_voltage_comp(struct pca9481_charger *pca9481) +{ + int iin, ichg; + + pr_info("%s: ======START=======\n", __func__); + + /* Read IIN ADC */ + iin = pca9481_read_adc(pca9481, ADCCH_VIN_CURRENT); + /* Read ICHG ADC */ + ichg = pca9481_read_adc(pca9481, ADCCH_BAT_CURRENT); + + pr_info("%s: iin=%d, ichg=%d\n", __func__, iin, ichg); + + /* Compare IIN ADC with target input current */ + if (iin > (pca9481->iin_cc + IIN_CC_COMP_OFFSET)) { + /* TA current is higher than the target input current */ + /* Decrease TA voltage (20mV) */ + pca9481->ta_vol = pca9481->ta_vol - PD_MSG_TA_VOL_STEP; + pr_info("%s: Comp. Cont1: ta_vol=%d\n", __func__, pca9481->ta_vol); + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } else { + if (iin < (pca9481->iin_cc - IIN_CC_COMP_OFFSET)) { + /* TA current is lower than the target input current */ + /* Compare TA max voltage */ + if (pca9481->ta_vol == pca9481->ta_max_vol) { + /* TA current is already the maximum voltage */ + pr_info("%s: Comp. End1(max TA vol): ta_vol=%d\n", __func__, pca9481->ta_vol); + /* Set timer */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_CCMODE; + pca9481->timer_period = CCMODE_CHECK_T; + mutex_unlock(&pca9481->lock); + } else { + /* Increase TA voltage (20mV) */ + pca9481->ta_vol = pca9481->ta_vol + PD_MSG_TA_VOL_STEP; + pr_info("%s: Comp. Cont2: ta_vol=%d\n", __func__, pca9481->ta_vol); + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } + } else { + /* IIN ADC is in valid range */ + /* IIN_CC - 50mA < IIN ADC < IIN_CC + 50mA */ + pr_info("%s: Comp. End(valid): ta_vol=%d\n", __func__, pca9481->ta_vol); + /* Set timer */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_CCMODE; + pca9481->timer_period = CCMODE_CHECK_T; + mutex_unlock(&pca9481->lock); + } + } + + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + + return 0; +} + +/* Compensate RX voltage for the target input current */ +static int pca9481_set_rx_voltage_comp(struct pca9481_charger *pca9481) +{ + int iin, ichg; + + pr_info("%s: ======START=======\n", __func__); + + /* Read IIN ADC */ + iin = pca9481_read_adc(pca9481, ADCCH_VIN_CURRENT); + /* Read ICHG ADC */ + ichg = pca9481_read_adc(pca9481, ADCCH_BAT_CURRENT); + + pr_info("%s: iin=%d, ichg=%d\n", __func__, iin, ichg); + + /* Compare IIN ADC with target input current */ + if (iin > (pca9481->iin_cc + IIN_CC_COMP_OFFSET)) { + /* RX current is higher than the target input current */ + /* Decrease RX voltage (100mV) */ + pca9481->ta_vol = pca9481->ta_vol - WCRX_VOL_STEP; + pr_info("%s: Comp. Cont1: rx_vol=%d\n", __func__, pca9481->ta_vol); + + /* Set RX Voltage */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } else { + if (iin < (pca9481->iin_cc - IIN_CC_COMP_OFFSET)) { + /* RX current is lower than the target input current */ + /* Compare RX max voltage */ + if (pca9481->ta_vol == pca9481->ta_max_vol) { + /* TA current is already the maximum voltage */ + pr_info("%s: Comp. End1(max RX vol): rx_vol=%d\n", __func__, pca9481->ta_vol); + /* Set timer */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_CCMODE; + pca9481->timer_period = CCMODE_CHECK_T; + mutex_unlock(&pca9481->lock); + } else { + /* Increase RX voltage (100mV) */ + pca9481->ta_vol = pca9481->ta_vol + WCRX_VOL_STEP; + pr_info("%s: Comp. Cont2: rx_vol=%d\n", __func__, pca9481->ta_vol); + + /* Set RX Voltage */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } + } else { + /* IIN ADC is in valid range */ + /* IIN_CC - 50mA < IIN ADC < IIN_CC + 50mA */ + pr_info("%s: Comp. End(valid): rx_vol=%d\n", __func__, pca9481->ta_vol); + /* Set timer */ + /* Check the current charging state */ + /* Set timer */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_CCMODE; + pca9481->timer_period = CCMODE_CHECK_T; + mutex_unlock(&pca9481->lock); + } + } + + /* Set TA target voltage to TA voltage */ + pca9481->ta_target_vol = pca9481->ta_vol; + + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + + return 0; +} + +/* Change switching frequency */ +static int pca9481_change_sw_freq(struct pca9481_charger *pca9481) +{ + int ret = 0; + int vbat; + unsigned int val; + unsigned int power_state; + + pr_info("%s: ======START=======\n", __func__); + + /* Check request sequence */ + switch (pca9481->req_sw_freq) { + case REQ_SW_FREQ_1: + /* REQ_SW_FREQ_1 - Decrease TA voltage */ + /* Read VBAT_ADC */ + vbat = pca9481_read_adc(pca9481, ADCCH_BATP_BATN); + /* Set TA voltage to MIN[(2*VBAT_ADC + offset), TA target voltage] */ + pca9481->ta_vol = MIN((2*vbat*pca9481->chg_mode + TA_VOL_PRE_OFFSET), pca9481->ta_target_vol); + val = pca9481->ta_vol/PD_MSG_TA_VOL_STEP; /* PPS voltage resolution is 20mV */ + pca9481->ta_vol = val*PD_MSG_TA_VOL_STEP; + + pr_info("%s: REQ_SW_FREQ_%d, ta_cur=%d, ta_vol=%d\n", + __func__, pca9481->req_sw_freq, pca9481->ta_cur, pca9481->ta_vol); + + /* Set next request sequence */ + pca9481->req_sw_freq = REQ_SW_FREQ_2; + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + break; + + case REQ_SW_FREQ_2: + /* REQ_SW_FREQ_2 - Disable DC */ + /* Disable charging */ + ret = pca9481_set_charging(pca9481, false); + if (ret < 0) + goto error; + + pr_info("%s: REQ_SW_FREQ_%d, disable DC\n", __func__, pca9481->req_sw_freq); + + /* Set next request sequence */ + pca9481->req_sw_freq = REQ_SW_FREQ_3; + + /* Set timer */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_ADJUST_TACUR; + pca9481->timer_period = DISABLE_DELAY_T; /* Wait dely time */ + mutex_unlock(&pca9481->lock); + break; + + case REQ_SW_FREQ_3: + /* REQ_SW_FREQ_3 - Set switching frequency */ + if (pca9481->iin_cc > IIN_LOW_TH_SW_FREQ) { + /* Set switching frequency to high frequency */ + pca9481->fsw_cfg = pca9481->pdata->fsw_cfg; + } else { + /* Set switching frequency to low frequency */ + pca9481->fsw_cfg = pca9481->pdata->fsw_cfg_low; + } + ret = pca9481_write_reg(pca9481, PCA9481_REG_SC_CNTL_0, PCA9481_FSW_CFG(pca9481->fsw_cfg)); + if (ret < 0) + goto error; + + pr_info("%s: REQ_SW_FREQ_%d, sw_freq=%dkHz\n", __func__, pca9481->req_sw_freq, pca9481->fsw_cfg); + + /* Set next request sequence */ + pca9481->req_sw_freq = REQ_SW_FREQ_4; + + /* Set timer */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_ADJUST_TACUR; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + break; + + case REQ_SW_FREQ_4: + /* REQ_SW_FREQ_4 - Set TA current */ + + /* Adjust IIN_CC with APDO resolution(50mA) - It will recover to the original value after sending PD message */ + val = pca9481->iin_cc/PD_MSG_TA_CUR_STEP; + pca9481->iin_cc = val*PD_MSG_TA_CUR_STEP; + /* Set TA current to IIN_CC */ + pca9481->ta_cur = pca9481->iin_cc/pca9481->chg_mode; + + /* Set TA voltage again before enable charging */ + /* Read VBAT_ADC */ + vbat = pca9481_read_adc(pca9481, ADCCH_BATP_BATN); + /* Set TA voltage to MIN[(2*VBAT_ADC + offset), TA target voltage] */ + pca9481->ta_vol = MIN((2*vbat*pca9481->chg_mode + TA_VOL_PRE_OFFSET), pca9481->ta_target_vol); + val = pca9481->ta_vol/PD_MSG_TA_VOL_STEP; /* PPS voltage resolution is 20mV */ + pca9481->ta_vol = val*PD_MSG_TA_VOL_STEP; + + pr_info("%s: REQ_SW_FREQ_%d, ta_cur=%d, ta_vol=%d\n", + __func__, pca9481->req_sw_freq, pca9481->ta_cur, pca9481->ta_vol); + + /* Set next request sequence */ + pca9481->req_sw_freq = REQ_SW_FREQ_5; + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + break; + + case REQ_SW_FREQ_5: + /* REQ_SW_FREQ_5 - Enable DC */ + ret = pca9481_set_charging(pca9481, true); + if (ret < 0) + goto error; + + pr_info("%s: REQ_SW_FREQ_%d, enable DC\n", __func__, pca9481->req_sw_freq); + + /* Set next request sequence */ + pca9481->req_sw_freq = REQ_SW_FREQ_6; + + /* Set timer */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_ADJUST_TACUR; + pca9481->timer_period = ENABLE_DELAY_T; /* Wait 150ms */ + mutex_unlock(&pca9481->lock); + break; + + case REQ_SW_FREQ_6: + /* REQ_SW_FREQ_6 - Increase TA voltage */ + + /* Check power state */ + /* Read DEVICE_3_STS register */ + ret = regmap_read(pca9481->regmap, PCA9481_REG_DEVICE_3_STS, &power_state); + if (ret < 0) + goto error; + if ((power_state & PCA9481_BIT_STATUS_CHANGE) == PCA9481_21SW_F11_MODE) { + /* TA voltage is initial voltage for switching frequency change */ + /* Increase TA voltage to TA target voltage */ + + /* Calculate new TA maximum current and voltage that used in the direct charging */ + /* Adjust IIN_CC with APDO resolution(50mA) - It will recover to the original value after max voltage calculation */ + val = pca9481->iin_cc/(PD_MSG_TA_CUR_STEP*pca9481->chg_mode); + pca9481->iin_cc = val*(PD_MSG_TA_CUR_STEP*pca9481->chg_mode); + /* Set TA_MAX_VOL to MIN[TA_MAX_VOL, (TA_MAX_PWR/IIN_CC)] */ + val = pca9481->ta_max_pwr/(pca9481->iin_cc/pca9481->chg_mode/1000); /* mV */ + val = val*1000/PD_MSG_TA_VOL_STEP; /* Adjust values with APDO resolution(20mV) */ + val = val*PD_MSG_TA_VOL_STEP; /* uV */ + pca9481->ta_max_vol = MIN(val, TA_MAX_VOL*pca9481->chg_mode); + /* Recover IIN_CC to the original value(new_iin) */ + pca9481->iin_cc = pca9481->new_iin; + + /* Set TA voltage to TA target voltage */ + pca9481->ta_vol = pca9481->ta_target_vol; + /* Adjust IIN_CC with APDO resolution(50mA) - It will recover to the original value after sending PD message */ + val = pca9481->iin_cc/PD_MSG_TA_CUR_STEP; + pca9481->iin_cc = val*PD_MSG_TA_CUR_STEP; + /* Set TA current to IIN_CC */ + pca9481->ta_cur = pca9481->iin_cc/pca9481->chg_mode; + + pr_info("%s: REQ_SW_FREQ_%d, ta_cur=%d, ta_vol=%d, ta_max_vol=%d, iin_cc=%d, chg_mode=%d\n", + __func__, pca9481->req_sw_freq, pca9481->ta_cur, pca9481->ta_vol, pca9481->ta_max_vol, + pca9481->iin_cc, pca9481->chg_mode); + + /* Set next request sequence */ + pca9481->req_sw_freq = REQ_SW_FREQ_0; + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } else { + /* pca9481 already set to enable charging, but power state is in standby state */ + /* DC error happens and check error type */ + pr_info("%s: Error - REQ_SW_FREQ_%d\n", __func__, pca9481->req_sw_freq); + ret = pca9481_check_error(pca9481); + /* Overwrite error type to invalid error */ + /* Will stop charging in timer_work */ + ret = -EINVAL; + goto error; + } + break; + + default: + /* Cannot enter here */ + pr_info("%s: Error - REQ_SW_FREQ_%d\n", __func__, pca9481->req_sw_freq); + pca9481->req_sw_freq = REQ_SW_FREQ_0; + ret = -EINVAL; + goto error; + } + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + +error: + pr_info("%s: ret=%d\n", __func__, ret); + return ret; +} + +/* Set TA current for target current */ +static int pca9481_adjust_ta_current(struct pca9481_charger *pca9481) +{ + int ret = 0; + int vbat; + unsigned int val; + + pr_info("%s: ======START=======\n", __func__); + + /* Set charging state to ADJUST_TACUR */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_ADJUST_TACUR); +#else + pca9481->charging_state = DC_STATE_ADJUST_TACUR; +#endif + + /* Check switching frequency change request */ + if (pca9481->req_sw_freq != REQ_SW_FREQ_0) { + /* There is request for switching frequency change */ + ret = pca9481_change_sw_freq(pca9481); + if (ret < 0) + goto error; + } else { + /* There is no request for switching frequency change */ + + /* Check whether TA current is same as IIN_CC or not */ + if (pca9481->ta_cur == pca9481->iin_cc/pca9481->chg_mode) { + /* finish sending PD message */ + /* Recover IIN_CC to the original value(new_iin) */ + pca9481->iin_cc = pca9481->new_iin; + + /* Update iin_cfg */ + pca9481->iin_cfg = pca9481->iin_cc; + /* Set IIN_CFG to new IIN */ + ret = pca9481_set_input_current(pca9481, pca9481->iin_cc); + if (ret < 0) + goto error; + + /* Check req_new_iin clear condition */ + /* Compare new_iin with iin_cc */ + if (pca9481->new_iin != pca9481->iin_cc) { + /* new_iin is different from iin_cc */ + /* Battery driver set new input current during input current change */ + /* Keep req_new_iin to true */ + pr_info("%s: Keep req_new_iin to true, new_iin=%d, iin_cc=%d\n", + __func__, pca9481->new_iin, pca9481->iin_cc); + } else { + /* new_iin is same as iin_cc */ + /* Clear req_new_iin */ + mutex_lock(&pca9481->lock); + pca9481->req_new_iin = false; + mutex_unlock(&pca9481->lock); + /* Compare new_iin with iin_cc again */ + if (pca9481->new_iin != pca9481->iin_cc) { + /* new_iin is different from iin_cc */ + /* Set req_new_iin again */ + mutex_lock(&pca9481->lock); + pca9481->req_new_iin = true; + mutex_unlock(&pca9481->lock); + } + } + + pr_info("%s: adj. End, ta_cur=%d, ta_vol=%d, iin_cc=%d, chg_mode=%d\n", + __func__, pca9481->ta_cur, pca9481->ta_vol, pca9481->iin_cc, pca9481->chg_mode); + + /* Go to return state */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, pca9481->ret_state); +#else + pca9481->charging_state = pca9481->ret_state; +#endif + /* Set timer */ + mutex_lock(&pca9481->lock); + if (pca9481->charging_state == DC_STATE_CC_MODE) + pca9481->timer_id = TIMER_CHECK_CCMODE; + else + pca9481->timer_id = TIMER_CHECK_CVMODE; + pca9481->timer_period = 1000; /* Wait 1s */ + mutex_unlock(&pca9481->lock); + } else { + /* Compare new IIN with current IIN_CFG */ + if (pca9481->iin_cc > pca9481->iin_cfg) { + /* New iin is higher than current iin_cc(iin_cfg) */ + /* Compare new IIN with IIN_LOW_TH */ + if (pca9481->iin_cc > IIN_LOW_TH_SW_FREQ) { + /* New IIN is high current */ + /* Check current switching frequency */ + if (pca9481->fsw_cfg == pca9481->pdata->fsw_cfg) { + /* Current switching frequency is high frequency */ + /* Don't need to change switching frequency */ + /* Update iin_cfg */ + pca9481->iin_cfg = pca9481->iin_cc; + /* Set IIN_CFG to new IIN */ + ret = pca9481_set_input_current(pca9481, pca9481->iin_cc); + if (ret < 0) + goto error; + + /* Set ICHG_CFG to enough high value */ + if (pca9481->ichg_cfg < 2*pca9481->iin_cfg) + pca9481->ichg_cfg = 2*pca9481->iin_cfg; + ret = pca9481_set_charging_current(pca9481, pca9481->ichg_cfg); + if (ret < 0) + goto error; + + /* Check req_new_iin clear condition */ + /* Compare new_iin with iin_cc */ + if (pca9481->new_iin != pca9481->iin_cc) { + /* new_iin is different from iin_cc */ + /* Battery driver set new input current during input current change */ + /* Keep req_new_iin to true */ + pr_info("%s: Keep req_new_iin to true, new_iin=%d, iin_cc=%d\n", + __func__, pca9481->new_iin, pca9481->iin_cc); + } else { + /* new_iin is same as iin_cc */ + /* Clear req_new_iin */ + mutex_lock(&pca9481->lock); + pca9481->req_new_iin = false; + mutex_unlock(&pca9481->lock); + /* Compare new_iin with iin_cc again */ + if (pca9481->new_iin != pca9481->iin_cc) { + /* new_iin is different from iin_cc */ + /* Set req_new_iin again */ + mutex_lock(&pca9481->lock); + pca9481->req_new_iin = true; + mutex_unlock(&pca9481->lock); + } + } + + /* Set new TA voltage and current */ + /* Read VBAT ADC */ + vbat = pca9481_read_adc(pca9481, ADCCH_BATP_BATN); + + /* Calculate new TA maximum current and voltage that used in the direct charging */ + /* Set IIN_CC to MIN[IIN, TA_MAX_CUR*chg_mode]*/ + pca9481->iin_cc = MIN(pca9481->iin_cfg, pca9481->ta_max_cur*pca9481->chg_mode); + /* Set the current IIN_CC to iin_cfg for recovering it after resolution adjustment */ + pca9481->iin_cfg = pca9481->iin_cc; + /* Calculate new TA max voltage */ + /* Adjust IIN_CC with APDO resolution(50mA) - It will recover to the original value after max voltage calculation */ + val = pca9481->iin_cc/(PD_MSG_TA_CUR_STEP*pca9481->chg_mode); + pca9481->iin_cc = val*(PD_MSG_TA_CUR_STEP*pca9481->chg_mode); + /* Set TA_MAX_VOL to MIN[TA_MAX_VOL, (TA_MAX_PWR/IIN_CC)] */ + val = pca9481->ta_max_pwr/(pca9481->iin_cc/pca9481->chg_mode/1000); /* mV */ + val = val*1000/PD_MSG_TA_VOL_STEP; /* Adjust values with APDO resolution(20mV) */ + val = val*PD_MSG_TA_VOL_STEP; /* uV */ + pca9481->ta_max_vol = MIN(val, TA_MAX_VOL*pca9481->chg_mode); + + /* Set TA voltage to MAX[TA_MIN_VOL_PRESET*chg_mode, (2*VBAT_ADC*chg_mode + offset)] */ + pca9481->ta_vol = max(TA_MIN_VOL_PRESET*pca9481->chg_mode, (2*vbat*pca9481->chg_mode + TA_VOL_PRE_OFFSET)); + val = pca9481->ta_vol/PD_MSG_TA_VOL_STEP; /* PPS voltage resolution is 20mV */ + pca9481->ta_vol = val*PD_MSG_TA_VOL_STEP; + /* Set TA voltage to MIN[TA voltage, TA_MAX_VOL] */ + pca9481->ta_vol = MIN(pca9481->ta_vol, pca9481->ta_max_vol); + /* Set TA current to IIN_CC */ + pca9481->ta_cur = pca9481->iin_cc/pca9481->chg_mode; + /* Recover IIN_CC to the original value(iin_cfg) */ + pca9481->iin_cc = pca9481->iin_cfg; + + pr_info("%s: New IIN(1), ta_max_vol=%d, ta_max_cur=%d, ta_max_pwr=%d, iin_cc=%d, chg_mode=%d\n", + __func__, pca9481->ta_max_vol, pca9481->ta_max_cur, pca9481->ta_max_pwr, pca9481->iin_cc, pca9481->chg_mode); + + pr_info("%s: New IIN(1), ta_vol=%d, ta_cur=%d, sw_freq=%d\n", + __func__, pca9481->ta_vol, pca9481->ta_cur, pca9481->fsw_cfg); + + /* Clear previous IIN ADC */ + pca9481->prev_iin = 0; + /* Clear TA increment flag */ + pca9481->prev_inc = INC_NONE; + + /* Send PD Message and go to Adjust CC mode */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_ADJUST_CC); +#else + pca9481->charging_state = DC_STATE_ADJUST_CC; +#endif + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = IIN_CFG_WAIT_T; + mutex_unlock(&pca9481->lock); + } else { + /* Need switching frequency change */ + /* Compare TA voltage with TA target voltage */ + if (pca9481->ta_vol == pca9481->ta_target_vol) { + /* Decrease TA voltage first before disable DC to avoid VIN voltage jumping */ + /* Read VBAT ADC */ + vbat = pca9481_read_adc(pca9481, ADCCH_BATP_BATN); + /* Set TA voltage to 2*VBAT_ADC + offset */ + pca9481->ta_vol = 2*vbat*pca9481->chg_mode + TA_VOL_PRE_OFFSET; + val = pca9481->ta_vol/PD_MSG_TA_VOL_STEP; /* PPS voltage resolution is 20mV */ + pca9481->ta_vol = val*PD_MSG_TA_VOL_STEP; + + pr_info("%s: New IIN(2), ta_vol=%d, ta_cur=%d, sw_freq=%d\n", + __func__, pca9481->ta_vol, pca9481->ta_cur, pca9481->fsw_cfg); + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } else { + /* Disable DC */ + ret = pca9481_set_charging(pca9481, false); + if (ret < 0) + goto error; + + /* Update iin_cfg */ + pca9481->iin_cfg = pca9481->iin_cc; + + /* Check req_new_iin clear condition */ + /* Compare new_iin with iin_cc */ + if (pca9481->new_iin != pca9481->iin_cc) { + /* new_iin is different from iin_cc */ + /* Battery driver set new input current during input current change */ + /* Keep req_new_iin to true */ + pr_info("%s: Keep req_new_iin to true, new_iin=%d, iin_cc=%d\n", + __func__, pca9481->new_iin, pca9481->iin_cc); + } else { + /* new_iin is same as iin_cc */ + /* Clear req_new_iin */ + mutex_lock(&pca9481->lock); + pca9481->req_new_iin = false; + mutex_unlock(&pca9481->lock); + /* Compare new_iin with iin_cc again */ + if (pca9481->new_iin != pca9481->iin_cc) { + /* new_iin is different from iin_cc */ + /* Set req_new_iin again */ + mutex_lock(&pca9481->lock); + pca9481->req_new_iin = true; + mutex_unlock(&pca9481->lock); + } + } + pr_info("%s: New IIN(3), iin_cc=%d, go to DC_STATE_PRESET_DC\n", __func__, pca9481->iin_cc); + + /* Go to DC_STATE_PRESET_DC */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PRESET_DC; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } + } + } else { + /* Current switching frequency is low frequency */ + /* New IIN is also low current */ + /* Don't need to change switching frequency */ + /* Update iin_cfg */ + pca9481->iin_cfg = pca9481->iin_cc; + /* Set IIN_CFG to new IIN */ + ret = pca9481_set_input_current(pca9481, pca9481->iin_cc); + if (ret < 0) + goto error; + + /* Set ICHG_CFG to enough high value */ + if (pca9481->ichg_cfg < 2*pca9481->iin_cfg) + pca9481->ichg_cfg = 2*pca9481->iin_cfg; + ret = pca9481_set_charging_current(pca9481, pca9481->ichg_cfg); + if (ret < 0) + goto error; + + /* Check req_new_iin clear condition */ + /* Compare new_iin with iin_cc */ + if (pca9481->new_iin != pca9481->iin_cc) { + /* new_iin is different from iin_cc */ + /* Battery driver set new input current during input current change */ + /* Keep req_new_iin to true */ + pr_info("%s: Keep req_new_iin to true, new_iin=%d, iin_cc=%d\n", + __func__, pca9481->new_iin, pca9481->iin_cc); + } else { + /* new_iin is same as iin_cc */ + /* Clear req_new_iin */ + mutex_lock(&pca9481->lock); + pca9481->req_new_iin = false; + mutex_unlock(&pca9481->lock); + /* Compare new_iin with iin_cc again */ + if (pca9481->new_iin != pca9481->iin_cc) { + /* new_iin is different from iin_cc */ + /* Set req_new_iin again */ + mutex_lock(&pca9481->lock); + pca9481->req_new_iin = true; + mutex_unlock(&pca9481->lock); + } + } + + /* Set new TA voltage and current */ + /* Read VBAT ADC */ + vbat = pca9481_read_adc(pca9481, ADCCH_BATP_BATN); + + /* Calculate new TA maximum current and voltage that used in the direct charging */ + /* Set IIN_CC to MIN[IIN, TA_MAX_CUR*chg_mode]*/ + pca9481->iin_cc = MIN(pca9481->iin_cfg, pca9481->ta_max_cur*pca9481->chg_mode); + /* Set the current IIN_CC to iin_cfg for recovering it after resolution adjustment */ + pca9481->iin_cfg = pca9481->iin_cc; + /* Calculate new TA max voltage */ + /* Adjust IIN_CC with APDO resolution(50mA) - It will recover to the original value after max voltage calculation */ + val = pca9481->iin_cc/(PD_MSG_TA_CUR_STEP*pca9481->chg_mode); + pca9481->iin_cc = val*(PD_MSG_TA_CUR_STEP*pca9481->chg_mode); + /* Set TA_MAX_VOL to MIN[TA_MAX_VOL, (TA_MAX_PWR/IIN_CC)] */ + val = pca9481->ta_max_pwr/(pca9481->iin_cc/pca9481->chg_mode/1000); /* mV */ + val = val*1000/PD_MSG_TA_VOL_STEP; /* Adjust values with APDO resolution(20mV) */ + val = val*PD_MSG_TA_VOL_STEP; /* uV */ + pca9481->ta_max_vol = MIN(val, TA_MAX_VOL*pca9481->chg_mode); + + /* Set TA voltage to MAX[TA_MIN_VOL_PRESET*chg_mode, (2*VBAT_ADC*chg_mode + offset)] */ + pca9481->ta_vol = max(TA_MIN_VOL_PRESET*pca9481->chg_mode, (2*vbat*pca9481->chg_mode + TA_VOL_PRE_OFFSET)); + val = pca9481->ta_vol/PD_MSG_TA_VOL_STEP; /* PPS voltage resolution is 20mV */ + pca9481->ta_vol = val*PD_MSG_TA_VOL_STEP; + /* Set TA voltage to MIN[TA voltage, TA_MAX_VOL] */ + pca9481->ta_vol = MIN(pca9481->ta_vol, pca9481->ta_max_vol); + /* Set TA current to IIN_CC */ + pca9481->ta_cur = pca9481->iin_cc/pca9481->chg_mode; + /* Recover IIN_CC to the original value(iin_cfg) */ + pca9481->iin_cc = pca9481->iin_cfg; + + pr_info("%s: New IIN(4), ta_max_vol=%d, ta_max_cur=%d, ta_max_pwr=%d, iin_cc=%d, chg_mode=%d\n", + __func__, pca9481->ta_max_vol, pca9481->ta_max_cur, pca9481->ta_max_pwr, pca9481->iin_cc, pca9481->chg_mode); + + pr_info("%s: New IIN(4), ta_vol=%d, ta_cur=%d, sw_freq=%d\n", + __func__, pca9481->ta_vol, pca9481->ta_cur, pca9481->fsw_cfg); + + /* Clear previous IIN ADC */ + pca9481->prev_iin = 0; + /* Clear TA increment flag */ + pca9481->prev_inc = INC_NONE; + + /* Send PD Message and go to Adjust CC mode */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_ADJUST_CC); +#else + pca9481->charging_state = DC_STATE_ADJUST_CC; +#endif + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = IIN_CFG_WAIT_T; + mutex_unlock(&pca9481->lock); + } + } else { + /* New iin is lower than current iin_cc(iin_cfg) */ + /* Compare new IIN with IIN_LOW_TH */ + if (pca9481->iin_cc > IIN_LOW_TH_SW_FREQ) { + /* New IIN is high current */ + /* Check current switching frequency */ + if (pca9481->fsw_cfg == pca9481->pdata->fsw_cfg) { + /* Current switching frequency is high frequency */ + /* Don't need to change switching frequency */ + /* Calculate new TA_MAX_VOL */ + /* Adjust IIN_CC with APDO resolution(50mA) - It will recover to the original value after max voltage calculation */ + val = pca9481->iin_cc/(PD_MSG_TA_CUR_STEP*pca9481->chg_mode); + pca9481->iin_cc = val*(PD_MSG_TA_CUR_STEP*pca9481->chg_mode); + /* Set TA_MAX_VOL to MIN[TA_MAX_VOL, (TA_MAX_PWR/IIN_CC)] */ + val = pca9481->ta_max_pwr/(pca9481->iin_cc/pca9481->chg_mode/1000); /* mV */ + val = val*1000/PD_MSG_TA_VOL_STEP; /* Adjust values with APDO resolution(20mV) */ + val = val*PD_MSG_TA_VOL_STEP; /* uV */ + pca9481->ta_max_vol = MIN(val, TA_MAX_VOL*pca9481->chg_mode); + /* Recover IIN_CC to the original value(new_iin) */ + pca9481->iin_cc = pca9481->new_iin; + + /* Read VBAT ADC */ + vbat = pca9481_read_adc(pca9481, ADCCH_BATP_BATN); + /* Set TA voltage to 2*VBAT_ADC + offset */ + pca9481->ta_vol = 2*vbat*pca9481->chg_mode + TA_VOL_PRE_OFFSET; + val = pca9481->ta_vol/PD_MSG_TA_VOL_STEP; /* PPS voltage resolution is 20mV */ + pca9481->ta_vol = val*PD_MSG_TA_VOL_STEP; + /* Adjust IIN_CC with APDO resolution(50mA) - It will recover to the original value after sending PD message */ + val = pca9481->iin_cc/PD_MSG_TA_CUR_STEP; + pca9481->iin_cc = val*PD_MSG_TA_CUR_STEP; + /* Set TA current to IIN_CC */ + pca9481->ta_cur = pca9481->iin_cc/pca9481->chg_mode; + + pr_info("%s: adj. cont1, ta_cur=%d, ta_vol=%d, ta_max_vol=%d, iin_cc=%d, chg_mode=%d\n", + __func__, pca9481->ta_cur, pca9481->ta_vol, pca9481->ta_max_vol, pca9481->iin_cc, pca9481->chg_mode); + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } else { + /* Current switching frequency is low frequency */ + /* Need to change switching frequency */ + /* Set switching frequency request sequence #1 */ + pca9481->req_sw_freq = REQ_SW_FREQ_1; + pr_info("%s: adj. cont2, go to switching freq change sequence #1(high freq)\n", __func__); + + /* Set timer */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_ADJUST_TACUR; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } + } else { + /* New IIN is low current */ + /* Check current switching frequency */ + if (pca9481->fsw_cfg == pca9481->pdata->fsw_cfg_low) { + /* Current switching frequency is low frequency */ + /* Don't need to change switching frequency */ + /* Calculate new TA_MAX_VOL */ + /* Adjust IIN_CC with APDO resolution(50mA) - It will recover to the original value after max voltage calculation */ + val = pca9481->iin_cc/(PD_MSG_TA_CUR_STEP*pca9481->chg_mode); + pca9481->iin_cc = val*(PD_MSG_TA_CUR_STEP*pca9481->chg_mode); + /* Set TA_MAX_VOL to MIN[TA_MAX_VOL, (TA_MAX_PWR/IIN_CC)] */ + val = pca9481->ta_max_pwr/(pca9481->iin_cc/pca9481->chg_mode/1000); /* mV */ + val = val*1000/PD_MSG_TA_VOL_STEP; /* Adjust values with APDO resolution(20mV) */ + val = val*PD_MSG_TA_VOL_STEP; /* uV */ + pca9481->ta_max_vol = MIN(val, TA_MAX_VOL*pca9481->chg_mode); + /* Recover IIN_CC to the original value(new_iin) */ + pca9481->iin_cc = pca9481->new_iin; + + /* Read VBAT ADC */ + vbat = pca9481_read_adc(pca9481, ADCCH_BATP_BATN); + /* Set TA voltage to 2*VBAT_ADC + offset */ + pca9481->ta_vol = 2*vbat*pca9481->chg_mode + TA_VOL_PRE_OFFSET; + val = pca9481->ta_vol/PD_MSG_TA_VOL_STEP; /* PPS voltage resolution is 20mV */ + pca9481->ta_vol = val*PD_MSG_TA_VOL_STEP; + /* Adjust IIN_CC with APDO resolution(50mA) - It will recover to the original value after sending PD message */ + val = pca9481->iin_cc/PD_MSG_TA_CUR_STEP; + pca9481->iin_cc = val*PD_MSG_TA_CUR_STEP; + /* Set TA current to IIN_CC */ + pca9481->ta_cur = pca9481->iin_cc/pca9481->chg_mode; + + pr_info("%s: adj. cont3, ta_cur=%d, ta_vol=%d, ta_max_vol=%d, iin_cc=%d, chg_mode=%d\n", + __func__, pca9481->ta_cur, pca9481->ta_vol, pca9481->ta_max_vol, pca9481->iin_cc, pca9481->chg_mode); + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } else { + /* Current switching frequency is high frequency */ + /* Need to change switching frequency */ + /* Set switching frequency request sequence #1 */ + pca9481->req_sw_freq = REQ_SW_FREQ_1; + pr_info("%s: adj. cont4, go to switching freq change sequence #1(low freq)\n", __func__); + + /* Set timer */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_ADJUST_TACUR; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } /* if (pca9481->fsw_cfg == pca9481->pdata->fsw_cfg_low) else */ + } /* if (pca9481->iin_cc > IIN_LOW_TH_SW_FREQ) else */ + } /* if (pca9481->iin_cc > pca9481->iin_cfg) else */ + } /* if (pca9481->ta_cur == pca9481->iin_cc/pca9481->chg_mode) else */ + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } /* if (pca9481->req_sw_freq != REQ_SW_FREQ_0) else */ + +error: + pr_info("%s: ret=%d\n", __func__, ret); + return ret; +} + + +/* Set TA voltage for target current */ +static int pca9481_adjust_ta_voltage(struct pca9481_charger *pca9481) +{ + int iin; + + pr_info("%s: ======START=======\n", __func__); + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_ADJUST_TAVOL); +#else + pca9481->charging_state = DC_STATE_ADJUST_TAVOL; +#endif + /* Read IIN ADC */ + iin = pca9481_read_adc(pca9481, ADCCH_VIN_CURRENT); + + /* Compare IIN ADC with targer input current */ + if (iin > (pca9481->iin_cc + PD_MSG_TA_CUR_STEP)) { + /* TA current is higher than the target input current */ + /* Decrease TA voltage (20mV) */ + pca9481->ta_vol = pca9481->ta_vol - PD_MSG_TA_VOL_STEP; + + pr_info("%s: adj. Cont1, ta_vol=%d\n", __func__, pca9481->ta_vol); + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } else { + if (iin < (pca9481->iin_cc - PD_MSG_TA_CUR_STEP)) { + /* TA current is lower than the target input current */ + /* Compare TA max voltage */ + if (pca9481->ta_vol == pca9481->ta_max_vol) { + /* TA current is already the maximum voltage */ + /* Check req_new_iin clear condition */ + /* Compare new_iin with iin_cc */ + if (pca9481->new_iin != pca9481->iin_cc) { + /* new_iin is different from iin_cc */ + /* Battery driver set new input current during input current change */ + /* Keep req_new_iin to true */ + pr_info("%s: Keep req_new_iin to true, new_iin=%d, iin_cc=%d\n", + __func__, pca9481->new_iin, pca9481->iin_cc); + } else { + /* new_iin is same as iin_cc */ + /* Clear req_new_iin */ + mutex_lock(&pca9481->lock); + pca9481->req_new_iin = false; + mutex_unlock(&pca9481->lock); + /* Compare new_iin with iin_cc again */ + if (pca9481->new_iin != pca9481->iin_cc) { + /* new_iin is different from iin_cc */ + /* Set req_new_iin again */ + mutex_lock(&pca9481->lock); + pca9481->req_new_iin = true; + mutex_unlock(&pca9481->lock); + } + } + /* Return charging state to the previous state */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, pca9481->ret_state); +#else + pca9481->charging_state = pca9481->ret_state; +#endif + pr_info("%s: adj. End1, ta_cur=%d, ta_vol=%d, iin_cc=%d, chg_mode=%d\n", + __func__, pca9481->ta_cur, pca9481->ta_vol, pca9481->iin_cc, pca9481->chg_mode); + + /* Go to return state */ + /* Set timer */ + mutex_lock(&pca9481->lock); + if (pca9481->charging_state == DC_STATE_CC_MODE) + pca9481->timer_id = TIMER_CHECK_CCMODE; + else + pca9481->timer_id = TIMER_CHECK_CVMODE; + pca9481->timer_period = 1000; /* Wait 1000ms */ + mutex_unlock(&pca9481->lock); + } else { + /* Increase TA voltage (20mV) */ + pca9481->ta_vol = pca9481->ta_vol + PD_MSG_TA_VOL_STEP; + + pr_info("%s: adj. Cont2, ta_vol=%d\n", __func__, pca9481->ta_vol); + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } + } else { + /* IIN ADC is in valid range */ + /* Check req_new_iin clear condition */ + /* Compare new_iin with iin_cc */ + if (pca9481->new_iin != pca9481->iin_cc) { + /* new_iin is different from iin_cc */ + /* Battery driver set new input current during input current change */ + /* Keep req_new_iin to true */ + pr_info("%s: Keep req_new_iin to true, new_iin=%d, iin_cc=%d\n", + __func__, pca9481->new_iin, pca9481->iin_cc); + } else { + /* new_iin is same as iin_cc */ + /* Clear req_new_iin */ + mutex_lock(&pca9481->lock); + pca9481->req_new_iin = false; + mutex_unlock(&pca9481->lock); + /* Compare new_iin with iin_cc again */ + if (pca9481->new_iin != pca9481->iin_cc) { + /* new_iin is different from iin_cc */ + /* Set req_new_iin again */ + mutex_lock(&pca9481->lock); + pca9481->req_new_iin = true; + mutex_unlock(&pca9481->lock); + } + } + + /* IIN_CC - 50mA < IIN ADC < IIN_CC + 50mA */ + /* Return charging state to the previous state */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, pca9481->ret_state); +#else + pca9481->charging_state = pca9481->ret_state; +#endif + pr_info("%s: adj. End2, ta_cur=%d, ta_vol=%d, iin_cc=%d, chg_mode=%d\n", + __func__, pca9481->ta_cur, pca9481->ta_vol, pca9481->iin_cc, pca9481->chg_mode); + + /* Go to return state */ + /* Set timer */ + mutex_lock(&pca9481->lock); + if (pca9481->charging_state == DC_STATE_CC_MODE) + pca9481->timer_id = TIMER_CHECK_CCMODE; + else + pca9481->timer_id = TIMER_CHECK_CVMODE; + pca9481->timer_period = 1000; /* Wait 1000ms */ + mutex_unlock(&pca9481->lock); + } + } + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + + return 0; +} + + +/* Set RX voltage for target current */ +static int pca9481_adjust_rx_voltage(struct pca9481_charger *pca9481) +{ + int iin; + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_ADJUST_TAVOL); +#else + pca9481->charging_state = DC_STATE_ADJUST_TAVOL; +#endif + /* Read IIN ADC */ + iin = pca9481_read_adc(pca9481, ADCCH_VIN_CURRENT); + + /* Compare IIN ADC with targer input current */ + if (iin > (pca9481->iin_cc + IIN_CC_COMP_OFFSET)) { + /* RX current is higher than the target input current */ + /* Decrease RX voltage (100mV) */ + pca9481->ta_vol = pca9481->ta_vol - WCRX_VOL_STEP; + + pr_info("%s: adj. Cont1, rx_vol=%d\n", __func__, pca9481->ta_vol); + + /* Set RX voltage */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } else { + if (iin < (pca9481->iin_cc - IIN_CC_COMP_OFFSET)) { + /* RX current is lower than the target input current */ + /* Compare RX max voltage */ + if (pca9481->ta_vol == pca9481->ta_max_vol) { + /* RX current is already the maximum voltage */ + /* Check req_new_iin clear condition */ + /* Compare new_iin with iin_cc */ + if (pca9481->new_iin != pca9481->iin_cc) { + /* new_iin is different from iin_cc */ + /* Battery driver set new input current during input current change */ + /* Keep req_new_iin to true */ + pr_info("%s: Keep req_new_iin to true, new_iin=%d, iin_cc=%d\n", + __func__, pca9481->new_iin, pca9481->iin_cc); + } else { + /* new_iin is same as iin_cc */ + /* Clear req_new_iin */ + mutex_lock(&pca9481->lock); + pca9481->req_new_iin = false; + mutex_unlock(&pca9481->lock); + /* Compare new_iin with iin_cc again */ + if (pca9481->new_iin != pca9481->iin_cc) { + /* new_iin is different from iin_cc */ + /* Set req_new_iin again */ + mutex_lock(&pca9481->lock); + pca9481->req_new_iin = true; + mutex_unlock(&pca9481->lock); + } + } + + pr_info("%s: adj. End1(max vol), rx_vol=%d, iin_cc=%d, chg_mode=%d\n", + __func__, pca9481->ta_vol, pca9481->iin_cc, pca9481->chg_mode); + + /* Return charging state to the previous state */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, pca9481->ret_state); +#else + pca9481->charging_state = pca9481->ret_state; +#endif + /* Go to return state */ + /* Set timer */ + mutex_lock(&pca9481->lock); + if (pca9481->charging_state == DC_STATE_CC_MODE) + pca9481->timer_id = TIMER_CHECK_CCMODE; + else + pca9481->timer_id = TIMER_CHECK_CVMODE; + pca9481->timer_period = 1000; /* Wait 1000ms */ + mutex_unlock(&pca9481->lock); + } else { + /* Increase RX voltage (100mV) */ + pca9481->ta_vol = pca9481->ta_vol + WCRX_VOL_STEP; + if (pca9481->ta_vol > pca9481->ta_max_vol) + pca9481->ta_vol = pca9481->ta_max_vol; + + pr_info("%s: adj. Cont2, rx_vol=%d\n", __func__, pca9481->ta_vol); + + /* Set RX voltage */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } + } else { + /* IIN ADC is in valid range */ + /* Check req_new_iin clear condition */ + /* Compare new_iin with iin_cc */ + if (pca9481->new_iin != pca9481->iin_cc) { + /* new_iin is different from iin_cc */ + /* Battery driver set new input current during input current change */ + /* Keep req_new_iin to true */ + pr_info("%s: Keep req_new_iin to true, new_iin=%d, iin_cc=%d\n", + __func__, pca9481->new_iin, pca9481->iin_cc); + } else { + /* new_iin is same as iin_cc */ + /* Clear req_new_iin */ + mutex_lock(&pca9481->lock); + pca9481->req_new_iin = false; + mutex_unlock(&pca9481->lock); + /* Compare new_iin with iin_cc again */ + if (pca9481->new_iin != pca9481->iin_cc) { + /* new_iin is different from iin_cc */ + /* Set req_new_iin again */ + mutex_lock(&pca9481->lock); + pca9481->req_new_iin = true; + mutex_unlock(&pca9481->lock); + } + } + + /* IIN_CC - 50mA < IIN ADC < IIN_CC + 50mA */ + pr_info("%s: adj. End2(valid), rx_vol=%d, iin_cc=%d, chg_mode=%d\n", + __func__, pca9481->ta_vol, pca9481->iin_cc, pca9481->chg_mode); + + /* Return charging state to the previous state */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, pca9481->ret_state); +#else + pca9481->charging_state = pca9481->ret_state; +#endif + /* Go to return state */ + /* Set timer */ + mutex_lock(&pca9481->lock); + if (pca9481->charging_state == DC_STATE_CC_MODE) + pca9481->timer_id = TIMER_CHECK_CCMODE; + else + pca9481->timer_id = TIMER_CHECK_CVMODE; + pca9481->timer_period = 1000; /* Wait 1000ms */ + mutex_unlock(&pca9481->lock); + } + } + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + + return 0; +} + +/* Set TA voltage for bypass mode */ +static int pca9481_set_bypass_ta_voltage_by_soc(struct pca9481_charger *pca9481, int delta_soc) +{ + int ret = 0; + unsigned int prev_ta_vol = pca9481->ta_vol; + + if (delta_soc < 0) { // increase soc (soc_now - ref_soc) + pca9481->ta_vol += PD_MSG_TA_VOL_STEP; + } else if (delta_soc > 0) { // decrease soc (soc_now - ref_soc) + pca9481->ta_vol -= PD_MSG_TA_VOL_STEP; + } else { + pr_info("%s: abnormal delta_soc=%d\n", __func__, delta_soc); + return -1; + } + + pr_info("%s: delta_soc=%d, prev_ta_vol=%d, ta_vol=%d, ta_cur=%d\n", + __func__, delta_soc, prev_ta_vol, pca9481->ta_vol, pca9481->ta_cur); + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + schedule_delayed_work(&pca9481->timer_work, msecs_to_jiffies(pca9481->timer_period)); + + return ret; +} + +/* Set TA current for bypass mode */ +static int pca9481_set_bypass_ta_current(struct pca9481_charger *pca9481) +{ + int ret = 0; + unsigned int val; + + /* Set charging state to BYPASS mode state */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_BYPASS_MODE); +#else + pca9481->charging_state = DC_STATE_BYPASS_MODE; +#endif + pr_info("%s: new_iin=%d\n", __func__, pca9481->new_iin); + + /* Set IIN_CFG to new_IIN */ + pca9481->iin_cfg = pca9481->new_iin; + pca9481->iin_cc = pca9481->new_iin; + ret = pca9481_set_input_current(pca9481, pca9481->iin_cc); + if (ret < 0) + goto error; + + /* Check req_new_iin clear condition */ + /* Compare new_iin with iin_cc */ + if (pca9481->new_iin != pca9481->iin_cc) { + /* new_iin is different from iin_cc */ + /* Battery driver set new input current during input current change */ + /* Keep req_new_iin to true */ + pr_info("%s: Keep req_new_iin to true, new_iin=%d, iin_cc=%d\n", + __func__, pca9481->new_iin, pca9481->iin_cc); + } else { + /* new_iin is same as iin_cc */ + /* Clear req_new_iin */ + mutex_lock(&pca9481->lock); + pca9481->req_new_iin = false; + mutex_unlock(&pca9481->lock); + /* Compare new_iin with iin_cc again */ + if (pca9481->new_iin != pca9481->iin_cc) { + /* new_iin is different from iin_cc */ + /* Set req_new_iin again */ + mutex_lock(&pca9481->lock); + pca9481->req_new_iin = true; + mutex_unlock(&pca9481->lock); + } + } + + /* Adjust IIN_CC with APDO resolution(50mA) - It will recover to the original value after sending PD message */ + val = pca9481->iin_cc/PD_MSG_TA_CUR_STEP; + pca9481->iin_cc = val*PD_MSG_TA_CUR_STEP; + /* Set TA current to IIN_CC */ + pca9481->ta_cur = pca9481->iin_cc/pca9481->chg_mode; + + pr_info("%s: ta_cur=%d, ta_vol=%d\n", __func__, pca9481->ta_cur, pca9481->ta_vol); + + /* Recover IIN_CC to the original value(new_iin) */ + pca9481->iin_cc = pca9481->new_iin; + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + +error: + pr_info("%s: ret=%d\n", __func__, ret); + return ret; +} + +/* Set TA voltage for bypass mode */ +static int pca9481_set_bypass_ta_voltage(struct pca9481_charger *pca9481) +{ + int ret = 0; + unsigned int val; + int vbat; + + /* Set charging state to BYPASS mode state */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_BYPASS_MODE); +#else + pca9481->charging_state = DC_STATE_BYPASS_MODE; +#endif + pr_info("%s: new_vfloat=%d\n", __func__, pca9481->new_vfloat); + + /* Set VFLOAT to new vfloat */ + pca9481->vfloat = pca9481->new_vfloat; + ret = pca9481_set_vfloat(pca9481, pca9481->vfloat); + if (ret < 0) + goto error; + + /* Clear Request flag */ + mutex_lock(&pca9481->lock); + pca9481->req_new_vfloat = false; + mutex_unlock(&pca9481->lock); + + + /* It needs to optimize TA voltage as calculating TA voltage with battery voltage later */ + /* Read VBAT ADC */ + vbat = pca9481_read_adc(pca9481, ADCCH_BATP_BATN); + + /* Check DC mode */ + if (pca9481->dc_mode == PTM_1TO1) { + /* Set TA voltage to VBAT_ADC + Offset */ + val = vbat + TA_VOL_OFFSET_1TO1_BYPASS; + } else { + /* Set TA voltage to 2*VBAT_ADC + Offset */ + val = 2 * vbat + TA_VOL_OFFSET_2TO1_BYPASS; + } + pca9481->ta_vol = val; + + pr_info("%s: vbat=%d, ta_vol=%d, ta_cur=%d\n", + __func__, vbat, pca9481->ta_vol, pca9481->ta_cur); + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + +error: + pr_info("%s: ret=%d\n", __func__, ret); + return ret; +} + +/* Set new input current */ +static int pca9481_set_new_iin(struct pca9481_charger *pca9481) +{ + int ret = 0; + + pr_info("%s: new_iin=%d\n", __func__, pca9481->new_iin); + + /* Check current DC mode */ + if ((pca9481->dc_mode == PTM_1TO1) || + (pca9481->dc_mode == PTM_2TO1)) { + /* DC mode is bypass mode */ + /* Set new iin for bypass mode */ + ret = pca9481_set_bypass_ta_current(pca9481); + } else { + /* DC mode is normal mode */ + /* Set new IIN to IIN_CC */ + pca9481->iin_cc = pca9481->new_iin; + /* Save return state */ + pca9481->ret_state = pca9481->charging_state; + + /* Check the TA type first */ + if (pca9481->ta_type == TA_TYPE_WIRELESS) { + /* Wireless Charger is connected */ + ret = pca9481_adjust_rx_voltage(pca9481); + } else { + /* USBPD TA is connected */ + /* Check new IIN with the minimum TA current */ + if (pca9481->iin_cc < (TA_MIN_CUR * pca9481->chg_mode)) { + /* Set the TA current to TA_MIN_CUR(1.0A) */ + pca9481->ta_cur = TA_MIN_CUR; + /* Need to control TA voltage for request current */ + ret = pca9481_adjust_ta_voltage(pca9481); + } else { + /* Need to control TA current for request current */ + ret = pca9481_adjust_ta_current(pca9481); + } + } + } + + pr_info("%s: ret=%d\n", __func__, ret); + return ret; +} + + +/* Set new float voltage */ +static int pca9481_set_new_vfloat(struct pca9481_charger *pca9481) +{ + int ret = 0; + int vbat; + unsigned int val; + + /* Check current DC mode */ + if ((pca9481->dc_mode == PTM_1TO1) || + (pca9481->dc_mode == PTM_2TO1)) { + /* DC mode is bypass mode */ + /* Set new vfloat for bypass mode */ + ret = pca9481_set_bypass_ta_voltage(pca9481); + } else { + /* DC mode is normal mode */ + /* Read VBAT ADC */ + vbat = pca9481_read_adc(pca9481, ADCCH_BATP_BATN); + + /* Compare the new VBAT with present vfloat */ + if (pca9481->new_vfloat > pca9481->vfloat) { + /* cancel delayed_work */ + cancel_delayed_work(&pca9481->timer_work); + + /* Set vfloat decrement flag to false */ + pca9481->dec_vfloat = false; + + /* Set VFLOAT to new vfloat */ + pca9481->vfloat = pca9481->new_vfloat; + /* Set PCA9481 VFLOAT to default value */ + ret = pca9481_set_vfloat(pca9481, PCA9481_VBAT_REG_DFT); + if (ret < 0) + goto error; + /* Set IIN_CFG to the current IIN_CC */ + /* save the current iin_cc in iin_cfg */ + pca9481->iin_cfg = pca9481->iin_cc; + pca9481->iin_cfg = MIN(pca9481->iin_cfg, pca9481->ta_max_cur*pca9481->chg_mode); + ret = pca9481_set_input_current(pca9481, pca9481->iin_cfg); + if (ret < 0) + goto error; + + pca9481->iin_cc = pca9481->iin_cfg; + + /* Set ICHG_CFG to enough high value */ + if (pca9481->ichg_cfg < 2*pca9481->iin_cfg) + pca9481->ichg_cfg = 2*pca9481->iin_cfg; + ret = pca9481_set_charging_current(pca9481, pca9481->ichg_cfg); + if (ret < 0) + goto error; + + /* Clear req_new_vfloat */ + mutex_lock(&pca9481->lock); + pca9481->req_new_vfloat = false; + mutex_unlock(&pca9481->lock); + + /* Check the TA type */ + if (pca9481->ta_type == TA_TYPE_WIRELESS) { + /* Wireless Charger is connected */ + /* Set RX voltage to MAX[TA_MIN_VOL_PRESET*chg_mode, (2*VBAT_ADC*chg_mode + offset)] */ + pca9481->ta_vol = max(TA_MIN_VOL_PRESET*pca9481->chg_mode, (2*vbat*pca9481->chg_mode + TA_VOL_PRE_OFFSET)); + val = pca9481->ta_vol/WCRX_VOL_STEP; /* RX voltage resolution is 100mV */ + pca9481->ta_vol = val*WCRX_VOL_STEP; + /* Set RX voltage to MIN[RX voltage, RX_MAX_VOL] */ + pca9481->ta_vol = MIN(pca9481->ta_vol, pca9481->ta_max_vol); + + pr_info("%s: New VFLOAT, rx_max_vol=%d, rx_vol=%d, iin_cc=%d, chg_mode=%d\n", + __func__, pca9481->ta_max_vol, pca9481->ta_vol, pca9481->iin_cc, pca9481->chg_mode); + } else { + /* USBPD TA is connected */ + /* Calculate new TA maximum voltage that used in the direct charging */ + /* Calculate new TA max voltage */ + /* Adjust IIN_CC with APDO resoultion(50mA) - It will recover to the original value after max voltage calculation */ + val = pca9481->iin_cc/(PD_MSG_TA_CUR_STEP*pca9481->chg_mode); + pca9481->iin_cc = val*(PD_MSG_TA_CUR_STEP*pca9481->chg_mode); + /* Set TA_MAX_VOL to MIN[TA_MAX_VOL, (TA_MAX_PWR/IIN_CC)] */ + val = pca9481->ta_max_pwr/(pca9481->iin_cc/pca9481->chg_mode/1000); /* mV */ + val = val*1000/PD_MSG_TA_VOL_STEP; /* uV */ + val = val*PD_MSG_TA_VOL_STEP; /* Adjust values with APDO resolution(20mV) */ + pca9481->ta_max_vol = MIN(val, TA_MAX_VOL*pca9481->chg_mode); + + /* Set TA voltage to MAX[TA_MIN_VOL_PRESET*chg_mode, (2*VBAT_ADC*chg_mode + offset)] */ + pca9481->ta_vol = max(TA_MIN_VOL_PRESET*pca9481->chg_mode, (2*vbat*pca9481->chg_mode + TA_VOL_PRE_OFFSET)); + val = pca9481->ta_vol/PD_MSG_TA_VOL_STEP; /* PPS voltage resolution is 20mV */ + pca9481->ta_vol = val*PD_MSG_TA_VOL_STEP; + /* Set TA voltage to MIN[TA voltage, TA_MAX_VOL] */ + pca9481->ta_vol = MIN(pca9481->ta_vol, pca9481->ta_max_vol); + /* Set TA current to IIN_CC */ + pca9481->ta_cur = pca9481->iin_cc/pca9481->chg_mode; + /* Recover IIN_CC to the original value(iin_cfg) */ + pca9481->iin_cc = pca9481->iin_cfg; + + pr_info("%s: New VFLOAT, ta_max_vol=%d, ta_max_cur=%d, ta_max_pwr=%d, iin_cc=%d, chg_mode=%d\n", + __func__, pca9481->ta_max_vol, pca9481->ta_max_cur, pca9481->ta_max_pwr, pca9481->iin_cc, pca9481->chg_mode); + } + + /* Clear previous IIN ADC */ + pca9481->prev_iin = 0; + /* Clear TA increment flag */ + pca9481->prev_inc = INC_NONE; + + /* Send PD Message and go to Adjust CC mode */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_ADJUST_CC); +#else + pca9481->charging_state = DC_STATE_ADJUST_CC; +#endif + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } else if (pca9481->new_vfloat == pca9481->vfloat) { + /* New vfloat is sameas the present vfloat */ + /* Don't need any setting */ + /* cancel delayed_work */ + cancel_delayed_work(&pca9481->timer_work); + + /* Clear req_new_vfloat */ + mutex_lock(&pca9481->lock); + pca9481->req_new_vfloat = false; + mutex_unlock(&pca9481->lock); + + /* Go to the present state */ + pr_info("%s: New vfloat is same as present vfloat and go to the present state\n", __func__); + + /* Set timer */ + mutex_lock(&pca9481->lock); + if (pca9481->charging_state == DC_STATE_CC_MODE) + pca9481->timer_id = TIMER_CHECK_CCMODE; + else + pca9481->timer_id = TIMER_CHECK_CVMODE; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + }else { + /* The new vfloat is lower than present vfloat */ + /* cancel delayed_work */ + cancel_delayed_work(&pca9481->timer_work); + + /* Set vfloat decrement flag */ + pca9481->dec_vfloat = true; + + /* Set VFLOAT to new vfloat */ + pca9481->vfloat = pca9481->new_vfloat; + /* Set PCA9481 VFLOAT to default value */ + ret = pca9481_set_vfloat(pca9481, PCA9481_VBAT_REG_DFT); + if (ret < 0) + goto error; + + /* Clear req_new_vfloat */ + mutex_lock(&pca9481->lock); + pca9481->req_new_vfloat = false; + mutex_unlock(&pca9481->lock); + + pr_info("%s: New vfloat is lower than present vfloat and go to Pre-CV state\n", __func__); + + /* Go to Pre-CV mode */ + pca9481->charging_state = DC_STATE_START_CV; + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_ENTER_CVMODE; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } + } + +error: + pr_info("%s: ret=%d\n", __func__, ret); + return ret; +} + +/* Set new direct charging mode */ +static int pca9481_set_new_dc_mode(struct pca9481_charger *pca9481) +{ + int ret = 0; + unsigned int val; + int vbat; + + pr_info("%s: ======START=======\n", __func__); + + /* Read VBAT ADC */ + vbat = pca9481_read_adc(pca9481, ADCCH_BATP_BATN); + + /* Check new dc mode */ + switch (pca9481->new_dc_mode) { + case PTM_1TO1: + case PTM_2TO1: + /* Change normal mode to 1:1 or 2:1 bypass mode */ + /* Check current dc mode */ + if ((pca9481->dc_mode == PTM_1TO1) || + (pca9481->dc_mode == PTM_2TO1)) { + /* TA voltage already changed to 1:1 or 2:1 mode */ + /* Disable reverse current detection */ + val = 0; + ret = pca9481_update_reg(pca9481, PCA9481_REG_RCP_CNTL, + PCA9481_BIT_RCP_EN, val); + if (ret < 0) + goto error; + + /* Set UV_TRACK_DELTA to 600mV and VIN_UV_TRACKING_DEGLITCH to 21ms */ + val = (UV_TRACK_DELTA_600mV << MASK2SHIFT(PCA9481_BIT_UV_TRACK_DELTA) | + UV_TRACK_DEGLITCH_21ms << MASK2SHIFT(PCA9481_BIT_VIN_UV_TRACKING_DEGLITCH)); + ret = pca9481_update_reg(pca9481, PCA9481_REG_SC_CNTL_1, + PCA9481_BIT_UV_TRACK_DELTA | PCA9481_BIT_VIN_UV_TRACKING_DEGLITCH, + val); + if (ret < 0) + goto error; + + /* Set FSW_CFG to fsw_cfg_byp */ + val = PCA9481_FSW_CFG(pca9481->pdata->fsw_cfg_byp); + ret = pca9481_write_reg(pca9481, PCA9481_REG_SC_CNTL_0, val); + if (ret < 0) + goto error; + pr_info("%s: New DC mode, BYP mode=%d, sw_freq=%dkHz\n", + __func__, pca9481->dc_mode, pca9481->pdata->fsw_cfg_byp); + + /* Enable Charging - recover dc as 1:1 or 2:1 bypass mode */ + ret = pca9481_set_charging(pca9481, true); + if (ret < 0) + goto error; + + /* Clear request flag */ + mutex_lock(&pca9481->lock); + pca9481->req_new_dc_mode = false; + mutex_unlock(&pca9481->lock); + + pr_info("%s: New DC mode, Normal->BYP mode(%d) done\n", __func__, pca9481->dc_mode); + + /* Wait 500ms and go to bypass state */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_BYPASS_MODE); +#else + pca9481->charging_state = DC_STATE_BYPASS_MODE; +#endif + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_BYPASSMODE; + pca9481->timer_period = BYPASS_WAIT_T; /* 200ms */ + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } else { + /* DC mode is normal mode */ + /* TA voltage is not changed to 1:1 or 2:1 bypass mode yet */ + /* Disable charging */ + ret = pca9481_set_charging(pca9481, false); + if (ret < 0) + goto error; + /* Set dc mode to new dc mode, 1:1 or 2:1 bypass mode */ + pca9481->dc_mode = pca9481->new_dc_mode; + if (pca9481->dc_mode == PTM_2TO1) { + /* Set TA voltage to 2:1 bypass voltage */ + pca9481->ta_vol = 2*vbat + TA_VOL_OFFSET_2TO1_BYPASS; + pr_info("%s: New DC mode, Normal->2:1BYP, ta_vol=%d, ta_cur=%d\n", + __func__, pca9481->ta_vol, pca9481->ta_cur); + } else { + /* Set TA voltage to 1:1 voltage */ + pca9481->ta_vol = vbat + TA_VOL_OFFSET_1TO1_BYPASS; + pr_info("%s: New DC mode, Normal->1:1BYP, ta_vol=%d, ta_cur=%d\n", + __func__, pca9481->ta_vol, pca9481->ta_cur); + } + + /* Send PD Message and go to dcmode change state */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_DCMODE_CHANGE); +#else + pca9481->charging_state = DC_STATE_DCMODE_CHANGE; +#endif + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } + break; + + case PTM_NONE: + /* Change bypass mode to normal mode */ + /* Disable charging */ + ret = pca9481_set_charging(pca9481, false); + if (ret < 0) + goto error; + + /* Enable reverse current detection */ + val = PCA9481_BIT_RCP_EN; + ret = pca9481_update_reg(pca9481, PCA9481_REG_RCP_CNTL, + PCA9481_BIT_RCP_EN, val); + if (ret < 0) + goto error; + + /* Set dc mode to new dc mode, normal mode */ + pca9481->dc_mode = pca9481->new_dc_mode; + /* Clear request flag */ + mutex_lock(&pca9481->lock); + pca9481->req_new_dc_mode = false; + mutex_unlock(&pca9481->lock); + pr_info("%s: New DC mode, BYP->Normal\n", __func__); + + /* Go to DC_STATE_PRESET_DC */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PRESET_DC; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + break; + + default: + ret = -EINVAL; + pr_info("%s: New DC mode, Invalid mode=%d\n", __func__, pca9481->new_dc_mode); + break; + } + +error: + pr_info("%s: ret=%d\n", __func__, ret); + return ret; +} + +/* 2:1 Direct Charging Adjust CC MODE control */ +static int pca9481_charge_adjust_ccmode(struct pca9481_charger *pca9481) +{ + int iin, ichg, vbatt, ccmode; + int val; + int ret = 0; + + pr_info("%s: ======START=======\n", __func__); + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_ADJUST_CC); +#else + pca9481->charging_state = DC_STATE_ADJUST_CC; +#endif + + ret = pca9481_check_error(pca9481); + if (ret != 0) + goto error; // This is not active mode. + /* Check the status */ + ccmode = pca9481_check_dcmode_status(pca9481); + if (ccmode < 0) { + ret = ccmode; + goto error; + } + + switch (ccmode) { + case DCMODE_IIN_LOOP: + case DCMODE_CHG_LOOP: + /* Read IIN ADC */ + iin = pca9481_read_adc(pca9481, ADCCH_VIN_CURRENT); + /* Read ICHG ADC */ + ichg = pca9481_read_adc(pca9481, ADCCH_BAT_CURRENT); + /* Read VBAT ADC */ + vbatt = pca9481_read_adc(pca9481, ADCCH_BATP_BATN); + + /* Check the TA type first */ + if (pca9481->ta_type == TA_TYPE_WIRELESS) { + /* Decrease RX voltage (100mV) */ + pca9481->ta_vol = pca9481->ta_vol - WCRX_VOL_STEP; + pr_info("%s: CC adjust End(LOOP): iin=%d, ichg=%d, vbatt=%d, rx_vol=%d\n", + __func__, iin, ichg, vbatt, pca9481->ta_vol); + + /* Set TA target voltage to TA voltage */ + pca9481->ta_target_vol = pca9481->ta_vol; + /* Clear TA increment flag */ + pca9481->prev_inc = INC_NONE; + /* Send PD Message and then go to CC mode */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_CC_MODE); +#else + pca9481->charging_state = DC_STATE_CC_MODE; +#endif + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } else { + /* Check TA current */ + if ((pca9481->ta_cur > TA_MIN_CUR) && + (pca9481->ta_ctrl == TA_CTRL_CL_MODE)) { + /* TA current is higher than 1.0A */ + /* Decrease TA current (50mA) */ + pca9481->ta_cur = pca9481->ta_cur - PD_MSG_TA_CUR_STEP; + } else { + /* Decrease TA voltage (20mV) */ + pca9481->ta_vol = pca9481->ta_vol - PD_MSG_TA_VOL_STEP; + } + /* Set TA target voltage to TA voltage */ + pca9481->ta_target_vol = pca9481->ta_vol; + /* Clear TA increment flag */ + pca9481->prev_inc = INC_NONE; + /* Send PD Message and then go to CC mode */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_CC_MODE); +#else + pca9481->charging_state = DC_STATE_CC_MODE; +#endif + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + + pr_info("%s: CC adjust End(LOOP): iin=%d, ichg=%d, vbatt=%d, ta_cur=%d, ta_vol=%d, ta_target_vol=%d\n", + __func__, iin, ichg, vbatt, pca9481->ta_cur, pca9481->ta_vol, pca9481->ta_target_vol); + } + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + break; + + case DCMODE_VFLT_LOOP: + /* Read IIN ADC */ + iin = pca9481_read_adc(pca9481, ADCCH_VIN_CURRENT); + /* Read ICHG ADC */ + ichg = pca9481_read_adc(pca9481, ADCCH_BAT_CURRENT); + /* Read VBAT ADC */ + vbatt = pca9481_read_adc(pca9481, ADCCH_BATP_BATN); + pr_info("%s: CC adjust End(VFLOAT): vbatt=%d, iin=%d, ichg=%d, ta_vol=%d\n", + __func__, vbatt, iin, ichg, pca9481->ta_vol); + + /* Save TA target voltage*/ + pca9481->ta_target_vol = pca9481->ta_vol; + /* Clear TA increment flag */ + pca9481->prev_inc = INC_NONE; + /* Go to Pre-CV mode */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_ENTER_CVMODE; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + break; + + case DCMODE_LOOP_INACTIVE: + /* Check IIN ADC with IIN */ + iin = pca9481_read_adc(pca9481, ADCCH_VIN_CURRENT); + /* Check ICHG ADC */ + ichg = pca9481_read_adc(pca9481, ADCCH_BAT_CURRENT); + /* Read VBAT ADC */ + vbatt = pca9481_read_adc(pca9481, ADCCH_BATP_BATN); + + /* Check the TA type first */ + if (pca9481->ta_type == TA_TYPE_WIRELESS) { + /* IIN_ADC > IIN_CC -20mA ? */ + if (iin > (pca9481->iin_cc - IIN_ADC_OFFSET)) { + /* Input current is already over IIN_CC */ + /* End RX voltage adjustment */ + /* change charging state to CC mode */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_CC_MODE); +#else + pca9481->charging_state = DC_STATE_CC_MODE; +#endif + pr_info("%s: CC adjust End: iin=%d, ichg=%d, vbatt=%d, rx_vol=%d\n", + __func__, iin, ichg, vbatt, pca9481->ta_vol); + + /* Save TA target voltage*/ + pca9481->ta_target_vol = pca9481->ta_vol; + /* Clear TA increment flag */ + pca9481->prev_inc = INC_NONE; + /* Go to CC mode */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_CCMODE; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } else { + /* Check RX voltage */ + if (pca9481->ta_vol == pca9481->ta_max_vol) { + /* RX voltage is already max value */ + pr_info("%s: CC adjust End: MAX value, iin=%d, ichg=%d, vbatt=%d, rx_vol=%d\n", + __func__, iin, ichg, vbatt, pca9481->ta_vol); + + /* Save TA target voltage*/ + pca9481->ta_target_vol = pca9481->ta_vol; + /* Clear TA increment flag */ + pca9481->prev_inc = INC_NONE; + /* Go to CC mode */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_CCMODE; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } else { + /* Try to increase RX voltage(100mV) */ + pca9481->ta_vol = pca9481->ta_vol + WCRX_VOL_STEP; + if (pca9481->ta_vol > pca9481->ta_max_vol) + pca9481->ta_vol = pca9481->ta_max_vol; + pr_info("%s: CC adjust Cont: iin=%d, ichg=%d, vbatt=%d, rx_vol=%d\n", + __func__, iin, ichg, vbatt, pca9481->ta_vol); + /* Set RX voltage */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } + } + } else { + /* USBPD TA is connected */ + + /* IIN_ADC > IIN_CC -20mA ? */ + if (iin > (pca9481->iin_cc - IIN_ADC_OFFSET)) { + if (pca9481->ta_ctrl == TA_CTRL_CL_MODE) { + /* TA control method is CL mode */ + /* Input current is already over IIN_CC */ + /* End TA voltage and current adjustment */ + /* change charging state to Start CC mode */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_START_CC); +#else + pca9481->charging_state = DC_STATE_START_CC; +#endif + + pr_info("%s: CC adjust End(Normal): iin=%d, ichg=%d, vbatt=%d, ta_vol=%d, ta_cur=%d\n", + __func__, iin, ichg, vbatt, pca9481->ta_vol, pca9481->ta_cur); + + /* TA target voltage = TA voltage + (VFLOAT - VBAT_ADC)*2*CHG_mode + 100mV */ + val = pca9481->ta_vol + (pca9481->vfloat - vbatt)*2*pca9481->chg_mode + 100000; + val = val/PD_MSG_TA_VOL_STEP; + pca9481->ta_target_vol = val*PD_MSG_TA_VOL_STEP; + if (pca9481->ta_target_vol > pca9481->ta_max_vol) + pca9481->ta_target_vol = pca9481->ta_max_vol; + pr_info("%s: CC adjust End: ta_target_vol=%d\n", __func__, pca9481->ta_target_vol); + /* Clear TA increment flag */ + pca9481->prev_inc = INC_NONE; + /* Go to Start CC mode */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_ENTER_CCMODE; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } else { + /* TA control method is CV mode */ + pr_info("%s: CC adjust End(Normal,CV): iin=%d, ichg=%d, vbatt=%d, ta_vol=%d, ta_cur=%d\n", + __func__, iin, ichg, vbatt, pca9481->ta_vol, pca9481->ta_cur); + /* Save TA target voltage */ + pca9481->ta_target_vol = pca9481->ta_vol; + pr_info("%s: CC adjust End(Normal): ta_ctrl=%d, ta_target_vol=%d\n", + __func__, pca9481->ta_ctrl, pca9481->ta_target_vol); + /* Clear TA increment flag */ + pca9481->prev_inc = INC_NONE; + /* Go to CC mode */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_CCMODE; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } + } else { + /* Compare TA maximum voltage */ + if (pca9481->ta_vol == pca9481->ta_max_vol) { + /* Compare TA maximum current */ + if (pca9481->ta_cur == pca9481->ta_max_cur) { + /* TA voltage and current are already max value */ + pr_info("%s: CC adjust End(MAX_VOL/CUR): iin=%d, ichg=%d, ta_vol=%d, ta_cur=%d\n", + __func__, iin, ichg, pca9481->ta_vol, pca9481->ta_cur); + /* Save TA target voltage */ + pca9481->ta_target_vol = pca9481->ta_vol; + /* Clear TA increment flag */ + pca9481->prev_inc = INC_NONE; + /* Go to CC mode */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_CCMODE; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } else { + /* TA current is not maximum value */ + /* Increase TA current(50mA) */ + pca9481->ta_cur = pca9481->ta_cur + PD_MSG_TA_CUR_STEP; + if (pca9481->ta_cur > pca9481->ta_max_cur) + pca9481->ta_cur = pca9481->ta_max_cur; + pr_info("%s: CC adjust Cont(1): iin=%d, ichg=%d, ta_cur=%d\n", + __func__, iin, ichg, pca9481->ta_cur); + + /* Set TA increment flag */ + pca9481->prev_inc = INC_TA_CUR; + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } + } else { + /* The current input current compares the final input current(IIN_CC) with 200mA offset */ + /* PPS current tolerance has +/-150mA, so offset defined 200mA(tolerance -50mA) */ + if (iin < (pca9481->iin_cc - TA_IIN_LOW_OFFSET)) { + /* TA voltage too low to enter TA CC mode, so we should increae TA voltage */ + pca9481->ta_vol = pca9481->ta_vol + TA_VOL_STEP_ADJ_CC*pca9481->chg_mode; + if (pca9481->ta_vol > pca9481->ta_max_vol) + pca9481->ta_vol = pca9481->ta_max_vol; + pr_info("%s: CC adjust Cont(2): iin=%d, ichg=%d, ta_vol=%d\n", + __func__, iin, ichg, pca9481->ta_vol); + + /* Set TA increment flag */ + pca9481->prev_inc = INC_TA_VOL; + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } else { + /* compare IIN ADC with previous IIN ADC + 20mA */ + if (iin > (pca9481->prev_iin + IIN_ADC_OFFSET)) { + /* TA can supply more current if TA voltage is high */ + /* TA voltage too low to enter TA CC mode, so we should increae TA voltage */ + pca9481->ta_vol = pca9481->ta_vol + TA_VOL_STEP_ADJ_CC*pca9481->chg_mode; + if (pca9481->ta_vol > pca9481->ta_max_vol) + pca9481->ta_vol = pca9481->ta_max_vol; + pr_info("%s: CC adjust Cont(3): iin=%d, ichg=%d, ta_vol=%d\n", + __func__, iin, ichg, pca9481->ta_vol); + + /* Set TA increment flag */ + pca9481->prev_inc = INC_TA_VOL; + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } else { + /* Check the previous increment */ + if (pca9481->prev_inc == INC_TA_CUR) { + /* The previous increment is TA current, but input current does not increase */ + /* Try to increase TA voltage(40mV) */ + pca9481->ta_vol = pca9481->ta_vol + TA_VOL_STEP_ADJ_CC*pca9481->chg_mode; + if (pca9481->ta_vol > pca9481->ta_max_vol) + pca9481->ta_vol = pca9481->ta_max_vol; + pr_info("%s: CC adjust Cont(4): iin=%d, ichg=%d, ta_vol=%d\n", + __func__, iin, ichg, pca9481->ta_vol); + + /* Set TA increment flag */ + pca9481->prev_inc = INC_TA_VOL; + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } else { + /* The previous increment is TA voltage, but input current does not increase */ + /* Try to increase TA current */ + /* Check APDO max current */ + if (pca9481->ta_cur == pca9481->ta_max_cur) { + if (pca9481->ta_ctrl == TA_CTRL_CL_MODE) { + /* Current TA control method is CL mode */ + /* TA current is maximum current */ + pr_info("%s: CC adjust End(MAX_CUR): iin=%d, ichg=%d, ta_vol=%d, ta_cur=%d\n", + __func__, iin, ichg, pca9481->ta_vol, pca9481->ta_cur); + + /* TA target voltage = TA voltage + (VFLOAT - VBAT_ADC)*2*CHG_mode + 100mV */ + val = pca9481->ta_vol + (pca9481->vfloat - vbatt)*2*pca9481->chg_mode + 100000; + val = val/PD_MSG_TA_VOL_STEP; + pca9481->ta_target_vol = val*PD_MSG_TA_VOL_STEP; + if (pca9481->ta_target_vol > pca9481->ta_max_vol) + pca9481->ta_target_vol = pca9481->ta_max_vol; + pr_info("%s: CC adjust End: ta_target_vol=%d\n", __func__, pca9481->ta_target_vol); + + /* Clear TA increment flag */ + pca9481->prev_inc = INC_NONE; + /* Go to Start CC mode */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_ENTER_CCMODE; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } else { + /* Current TA control method is CV mode */ + pr_info("%s: CC adjust End(MAX_CUR,CV): iin=%d, ichg=%d, ta_vol=%d, ta_cur=%d\n", + __func__, iin, ichg, pca9481->ta_vol, pca9481->ta_cur); + /* Save TA target voltage */ + pca9481->ta_target_vol = pca9481->ta_vol; + pr_info("%s: CC adjust End(Normal): ta_ctrl=%d, ta_target_vol=%d\n", + __func__, pca9481->ta_ctrl, pca9481->ta_target_vol); + /* Clear TA increment flag */ + pca9481->prev_inc = INC_NONE; + /* Go to CC mode */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_CCMODE; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } + } else { + /* Check the present TA current */ + /* Consider tolerance offset(100mA) */ + if (pca9481->ta_cur >= (pca9481->iin_cc + TA_IIN_OFFSET)) { + /* TA voltage increment has high priority than TA current increment */ + /* Try to increase TA voltage(40mV) */ + pca9481->ta_vol = pca9481->ta_vol + TA_VOL_STEP_ADJ_CC*pca9481->chg_mode; + if (pca9481->ta_vol > pca9481->ta_max_vol) + pca9481->ta_vol = pca9481->ta_max_vol; + pr_info("%s: CC adjust Cont(5): iin=%d, ichg=%d, ta_vol=%d\n", + __func__, iin, ichg, pca9481->ta_vol); + + /* Set TA increment flag */ + pca9481->prev_inc = INC_TA_VOL; + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } else { + /* TA has tolerance and compensate it as real current */ + /* Increase TA current(50mA) */ + pca9481->ta_cur = pca9481->ta_cur + PD_MSG_TA_CUR_STEP; + if (pca9481->ta_cur > pca9481->ta_max_cur) + pca9481->ta_cur = pca9481->ta_max_cur; + pr_info("%s: CC adjust Cont(6): iin=%d, ichg=%d, ta_cur=%d\n", + __func__, iin, ichg, pca9481->ta_cur); + + /* Set TA increment flag */ + pca9481->prev_inc = INC_TA_CUR; + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } + } + } + } + } + } + } + } + /* Save previous iin adc */ + pca9481->prev_iin = iin; + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + break; + + default: + break; + } + +error: + pr_info("%s: End, ret=%d\n", __func__, ret); + return ret; +} + +/* 2:1 Direct Charging Start CC MODE control - Pre CC MODE */ +/* Increase TA voltage to TA target voltage */ +static int pca9481_charge_start_ccmode(struct pca9481_charger *pca9481) +{ + int ret = 0; + int ccmode; + + pr_info("%s: ======START=======\n", __func__); + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_START_CC); +#else + pca9481->charging_state = DC_STATE_START_CC; +#endif + ret = pca9481_check_error(pca9481); + if (ret != 0) + goto error; // This is not active mode. + + /* Check the status */ + ccmode = pca9481_check_dcmode_status(pca9481); + if (ccmode < 0) { + ret = ccmode; + goto error; + } + + /* Check input regulation loop */ + if (ccmode == DCMODE_IIN_LOOP) { + /* Set TA target voltage to TA voltage */ + pca9481->ta_target_vol = pca9481->ta_vol; + /* Decrease TA current (50mA) */ + pca9481->ta_cur = pca9481->ta_cur - PD_MSG_TA_CUR_STEP; + /* Change DC state to CC mode */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_CC_MODE); +#else + pca9481->charging_state = DC_STATE_CC_MODE; +#endif + pr_info("%s: PreCC End(IIN LOOP): ta_vol=%d, ta_target_vol=%d\n", + __func__, pca9481->ta_vol, pca9481->ta_target_vol); + } else { + /* Increase TA voltage */ + pca9481->ta_vol = pca9481->ta_vol + TA_VOL_STEP_PRE_CC * pca9481->chg_mode; + /* Check TA target voltage */ + if (pca9481->ta_vol >= pca9481->ta_target_vol) { + pca9481->ta_vol = pca9481->ta_target_vol; + pr_info("%s: PreCC End: ta_vol=%d\n", __func__, pca9481->ta_vol); + + /* Change DC state to CC mode */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_CC_MODE); +#else + pca9481->charging_state = DC_STATE_CC_MODE; +#endif + } else { + pr_info("%s: PreCC Cont: ta_vol=%d\n", __func__, pca9481->ta_vol); + } + } + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + +error: + pr_info("%s: End, ret=%d\n", __func__, ret); + return ret; +} + +/* 2:1 Direct Charging CC MODE control */ +static int pca9481_charge_ccmode(struct pca9481_charger *pca9481) +{ + int ret = 0; + int ccmode; + int iin, ichg; + + pr_info("%s: ======START=======\n", __func__); + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_CC_MODE); +#else + pca9481->charging_state = DC_STATE_CC_MODE; +#endif + /* Check the charging type */ + ret = pca9481_check_error(pca9481); + if (ret < 0) { + if (ret == -EAGAIN) { + /* DC error happens, but it is retry case */ + if (pca9481->ta_ctrl == TA_CTRL_CL_MODE) { + /* Current TA control method is Current Limit mode */ + /* Retry DC as Constant Voltage mode */ + pr_info("%s: Retry DC : ta_ctrl=%d\n", __func__, pca9481->ta_ctrl); + + /* Disable charging */ + ret = pca9481_set_charging(pca9481, false); + if (ret < 0) + goto error; + + /* Softreset */ + ret = pca9481_softreset(pca9481); + if (ret < 0) + goto error; + + /* Set TA control method to Constant Voltage mode */ + pca9481->ta_ctrl = TA_CTRL_CV_MODE; + + /* Go to DC_STATE_PRESET_DC */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PRESET_DC; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + ret = 0; + goto error; + } else { + /* Current TA control method is Constant Voltage mode */ + /* Don't retry DC */ + pr_info("%s: Retry DC, but still failed - stop DC\n", __func__); + goto error; + } + } else { + /* Don't retry DC */ + goto error; + } + } + + /* Check new request */ + if (pca9481->req_new_dc_mode == true) { + ret = pca9481_set_new_dc_mode(pca9481); + } else if (pca9481->req_new_iin == true) { + ret = pca9481_set_new_iin(pca9481); + } else if (pca9481->req_new_vfloat == true) { + /* Clear request flag */ + mutex_lock(&pca9481->lock); + pca9481->req_new_vfloat = false; + mutex_unlock(&pca9481->lock); + ret = pca9481_set_new_vfloat(pca9481); + } else { + /* Check the charging type */ + ccmode = pca9481_check_dcmode_status(pca9481); + if (ccmode < 0) { + ret = ccmode; + goto error; + } + + switch (ccmode) { + case DCMODE_LOOP_INACTIVE: + /* Set input current compensation */ + /* Check the TA type */ + if (pca9481->ta_type == TA_TYPE_WIRELESS) { + /* Need RX voltage compensation */ + ret = pca9481_set_rx_voltage_comp(pca9481); + pr_info("%s: CC INACTIVE: rx_vol=%d\n", __func__, pca9481->ta_vol); + } else { + /* Check the current TA current with TA_MIN_CUR */ + if ((pca9481->ta_cur <= TA_MIN_CUR) || + (pca9481->ta_ctrl == TA_CTRL_CV_MODE)) { + /* Need input voltage compensation */ + ret = pca9481_set_ta_voltage_comp(pca9481); + } else { + if (pca9481->ta_max_vol >= TA_MAX_VOL_CP) { + /* This TA can support the input current without power limit */ + /* Need input current compensation */ + ret = pca9481_set_ta_current_comp(pca9481); + } else { + /* This TA has the power limitation for the input current compenstaion */ + /* The input current cannot increase over the constant power */ + /* Need input current compensation in constant power mode */ + ret = pca9481_set_ta_current_comp2(pca9481); + } + } + pr_info("%s: CC INACTIVE: ta_cur=%d, ta_vol=%d\n", __func__, pca9481->ta_cur, pca9481->ta_vol); + } + break; + + case DCMODE_VFLT_LOOP: + /* Read IIN_ADC */ + iin = pca9481_read_adc(pca9481, ADCCH_VIN_CURRENT); + /* Read ICHG_ADC */ + ichg = pca9481_read_adc(pca9481, ADCCH_BAT_CURRENT); + pr_info("%s: CC VFLOAT: iin=%d, ichg=%d\n", __func__, iin, ichg); + /* Set CV transtion sequence to #1 */ + pca9481->cv_trans = CV_TRANS_1; + /* go to Pre-CV mode */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_ENTER_CVMODE; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + break; + + case DCMODE_IIN_LOOP: + case DCMODE_CHG_LOOP: + /* Read IIN_ADC */ + iin = pca9481_read_adc(pca9481, ADCCH_VIN_CURRENT); + /* Read ICHG_ADC */ + ichg = pca9481_read_adc(pca9481, ADCCH_BAT_CURRENT); + /* Check the TA type */ + if (pca9481->ta_type == TA_TYPE_WIRELESS) { + /* Wireless Charger is connected */ + /* Decrease RX voltage (100mV) */ + pca9481->ta_vol = pca9481->ta_vol - WCRX_VOL_STEP; + pr_info("%s: CC LOOP(WC):iin=%d, ichg=%d, next_rx_vol=%d\n", __func__, iin, ichg, pca9481->ta_vol); + } else { + /* USBPD TA is connected */ + + /* Check the current TA current with TA_MIN_CUR */ + if ((pca9481->ta_cur <= TA_MIN_CUR) || + (pca9481->ta_ctrl == TA_CTRL_CV_MODE)) { + /* Decrease TA voltage (20mV) */ + pca9481->ta_vol = pca9481->ta_vol - PD_MSG_TA_VOL_STEP; + pr_info("%s: CC LOOP(1):iin=%d, ichg=%d, next_ta_vol=%d\n", __func__, iin, ichg, pca9481->ta_vol); + } else { + /* Check TA current and compare it with IIN_CC */ + if (pca9481->ta_cur <= pca9481->iin_cc - TA_CUR_LOW_OFFSET) { + /* IIN_LOOP still happens even though TA current is less than IIN_CC - 200mA */ + /* TA has abnormal behavior */ + /* Decrease TA voltage (20mV) */ +#ifdef CONFIG_SEC_FACTORY + pca9481->ta_vol = pca9481->ta_vol - TA_VOL_DEC_STEP_CC; +#else + pca9481->ta_vol = pca9481->ta_vol - PD_MSG_TA_VOL_STEP; +#endif + pr_info("%s: CC LOOP(2):iin=%d, ichg=%d, ta_cur=%d, next_ta_vol=%d\n", + __func__, iin, ichg, pca9481->ta_cur, pca9481->ta_vol); + /* Update TA target voltage */ + pca9481->ta_target_vol = pca9481->ta_vol; + } else { + /* TA current is higher than IIN_CC - 200mA */ + /* Decrease TA current first to reduce input current */ + /* Decrease TA current (50mA) */ + pca9481->ta_cur = pca9481->ta_cur - PD_MSG_TA_CUR_STEP; + pr_info("%s: CC LOOP(3):iin=%d, ichg=%d, ta_vol=%d, next_ta_cur=%d\n", + __func__, iin, ichg, pca9481->ta_vol, pca9481->ta_cur); + } + } + } + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + break; + + default: + break; + } + } +error: + pr_info("%s: End, ret=%d\n", __func__, ret); + return ret; +} + + +/* 2:1 Direct Charging Start CV MODE control - Pre CV MODE */ +static int pca9481_charge_start_cvmode(struct pca9481_charger *pca9481) +{ + int ret = 0; + int cvmode = 0; + int iin = 0; + int ichg, vbat; + unsigned int val; + + pr_info("%s: ======START=======\n", __func__); + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_START_CV); +#else + pca9481->charging_state = DC_STATE_START_CV; +#endif + + /* Check the charging type */ + ret = pca9481_check_error(pca9481); + if (ret != 0) { + /* Check error type */ + if (ret == -ERROR_DCRCP) { + /* Set dcmode to DCMODE_CHG_DONE */ + cvmode = DCMODE_CHG_DONE; + pr_info("%s: dcmode is DCMODE_CHG_DONE by RCP\n", __func__); + } else { + /* DC error */ + goto error; // This is not active mode. + } } + /* Check the status */ + if (cvmode != DCMODE_CHG_DONE) { + cvmode = pca9481_check_dcmode_status(pca9481); + if (cvmode < 0) { + ret = cvmode; + goto error; + } + + /* Read IIN_ADC */ + iin = pca9481_read_adc(pca9481, ADCCH_VIN_CURRENT); + /* Read ICHG_ADC */ + ichg = pca9481_read_adc(pca9481, ADCCH_BAT_CURRENT); + + /* Read VBAT_ADC */ + vbat = pca9481_read_adc(pca9481, ADCCH_BATP_BATN); + /* Store VBAT_ADC to previous vbat */ + pca9481->prev_vbat = vbat; + + /* Check charging done state */ + /* Compare iin with input topoff current */ + pr_info("%s: iin=%d, ichg=%d, iin_topoff=%d\n", + __func__, iin, ichg, pca9481->iin_topoff); + if (iin < pca9481->iin_topoff) { + /* Change cvmode status to charging done */ + cvmode = DCMODE_CHG_DONE; + pr_info("%s: start CVMODE Status=%d\n", __func__, cvmode); + } + } + + /* Check CV transition sequence */ + if (pca9481->cv_trans == CV_TRANS_0) { + /* Enter this transition sequence during ADJUST_CC-CV transition or new_vfloat - Normal sequence */ + switch (cvmode) { + case DCMODE_CHG_DONE: + /* Charging Done */ + /* Keep start CV mode until battery driver send stop charging */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_CHARGING_DONE); +#else + pca9481->charging_state = DC_STATE_CHARGING_DONE; +#endif + pr_info("%s: Start CV - Charging Done\n", __func__); +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_done(pca9481, true); +#endif + + /* Check the charging status after notification function */ + if (pca9481->charging_state != DC_STATE_NO_CHARGING) { + /* Notification function does not stop timer work yet */ + /* Keep the charging done state */ + /* Set timer */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_ENTER_CVMODE; + pca9481->timer_period = CVMODE_CHECK_T; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } else { + /* Already called stop charging by notification function */ + pr_info("%s: Already stop DC\n", __func__); + } + break; + + case DCMODE_CHG_LOOP: + case DCMODE_IIN_LOOP: + /* Check the TA type */ + if (pca9481->ta_type == TA_TYPE_WIRELESS) { + /* Decrease RX voltage (100mV) */ + pca9481->ta_vol = pca9481->ta_vol - WCRX_VOL_STEP; + pr_info("%s: PreCV Cont(IIN_LOOP): rx_vol=%d\n", __func__, pca9481->ta_vol); + } else { + /* Check TA current */ + if (pca9481->ta_cur > TA_MIN_CUR) { + /* TA current is higher than 1.0A */ + /* Decrease TA current (50mA) */ + pca9481->ta_cur = pca9481->ta_cur - PD_MSG_TA_CUR_STEP; + pr_info("%s: PreCV Cont: ta_cur=%d\n", __func__, pca9481->ta_cur); + } else { + /* TA current is less than 1.0A */ + /* Decrease TA voltage (20mV) */ + pca9481->ta_vol = pca9481->ta_vol - PD_MSG_TA_VOL_STEP; + pr_info("%s: PreCV Cont(IIN_LOOP): ta_vol=%d\n", __func__, pca9481->ta_vol); + } + } + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + break; + + case DCMODE_VFLT_LOOP: + /* Check the TA type */ + if (pca9481->ta_type == TA_TYPE_WIRELESS) { + /* Decrease RX voltage (100mV) */ + pca9481->ta_vol = pca9481->ta_vol - WCRX_VOL_STEP; + pr_info("%s: PreCV Cont: rx_vol=%d\n", __func__, pca9481->ta_vol); + } else { + /* Check present vfloat */ + if (pca9481->vfloat >= pca9481->max_vfloat) { + /* Decrease TA voltage (40mV) */ + pca9481->ta_vol = pca9481->ta_vol - TA_VOL_STEP_PRE_CV * pca9481->chg_mode; + pr_info("%s: PreCV Cont(40mV): ta_vol=%d\n", __func__, pca9481->ta_vol); + } else { + /* Decrease TA voltage (20mV) */ + pca9481->ta_vol = pca9481->ta_vol - PD_MSG_TA_VOL_STEP * pca9481->chg_mode; + pr_info("%s: PreCV Cont(20mV): ta_vol=%d\n", __func__, pca9481->ta_vol); + } + } + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + break; + + case DCMODE_LOOP_INACTIVE: + /* Exit Pre CV mode */ + pr_info("%s: PreCV End: ta_vol=%d, ta_cur=%d\n", __func__, pca9481->ta_vol, pca9481->ta_cur); + + /* Set TA target voltage to TA voltage */ + pca9481->ta_target_vol = pca9481->ta_vol; + + /* Need to implement notification to other driver */ + /* To do here */ + + /* Set UV_TRACK_DELTA to 200mV and VIN_UV_TRACKING_DEGLITCH to 1ms to avoid VIN_UV Error */ + val = (UV_TRACK_DELTA_200mV << MASK2SHIFT(PCA9481_BIT_UV_TRACK_DELTA) | + UV_TRACK_DEGLITCH_1ms << MASK2SHIFT(PCA9481_BIT_VIN_UV_TRACKING_DEGLITCH)); + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_SC_CNTL_1, + PCA9481_BIT_UV_TRACK_DELTA | PCA9481_BIT_VIN_UV_TRACKING_DEGLITCH, + val); + if (ret < 0) + goto error; + + /* Go to CV mode */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_CVMODE; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + break; + + default: + break; + } + } else { + /* Enter this transtion sequence during CC-CV transition - New method */ + /* Check CV transition sequence */ + switch (pca9481->cv_trans) { + case CV_TRANS_1: + /* Set TA target current to TA current */ + pca9481->ta_target_cur = pca9481->ta_cur; + /* Set TA current to 0.95*TA target current */ + pca9481->ta_cur = pca9481->ta_target_cur / CUR_RATIO_DENOM_CV_TRANS * TA_CUR_RATIO_CV_TRANS; + /* Adjust PPS resolution (50mA) */ + pca9481->ta_cur = pca9481->ta_cur/PD_MSG_TA_CUR_STEP; + pca9481->ta_cur = pca9481->ta_cur*PD_MSG_TA_CUR_STEP; + pr_info("%s: CV trans #1: ta_cur=%d\n", __func__, pca9481->ta_cur); + /* Go to CV trans #2 */ + pca9481->cv_trans = CV_TRANS_2; + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + break; + + case CV_TRANS_2: + /* Compare IIN_ADC with 0.9*IIN_CC */ + if (iin < (pca9481->iin_cc / CUR_RATIO_DENOM_CV_TRANS * IIN_LOW_TH_CV_TRANS)) { + /* IIN is less than 0.9*IIN_CC */ + pr_info("%s: CV trans #2(end): iin_adc=%d, iin_cc=%d\n", __func__, iin, pca9481->iin_cc); + /* Go to CV trans #3 */ + pca9481->cv_trans = CV_TRANS_3; + + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_ENTER_CVMODE; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } else { + /* Decrease TA voltage (40mV) */ + pca9481->ta_vol = pca9481->ta_vol - TA_VOL_STEP_PRE_CV * pca9481->chg_mode; + pr_info("%s: CV trans #2(cont.): ta_vol=%d\n", __func__, pca9481->ta_vol); + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } + break; + + case CV_TRANS_3: + /* Recover TA current */ + /* Set TA current to TA target current */ + pca9481->ta_cur = pca9481->ta_target_cur; + pr_info("%s: CV trans #3: ta_cur=%d\n", __func__, pca9481->ta_cur); + /* Go to CV trans #4 */ + pca9481->cv_trans = CV_TRANS_4; + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + break; + + case CV_TRANS_4: + /* Check VFLOAT_LOOP happens */ + if (cvmode == DCMODE_VFLT_LOOP) { + /* Set TA target voltage to TA voltage */ + pca9481->ta_target_vol = pca9481->ta_vol; + pr_info("%s: CV trans #4(end): ta_vol=%d\n", __func__, pca9481->ta_vol); + /* Clear cv transition sequence */ + pca9481->cv_trans = CV_TRANS_0; + + /* Set UV_TRACK_DELTA to 200mV and VIN_UV_TRACKING_DEGLITCH to 1ms to avoid VIN_UV Error */ + val = (UV_TRACK_DELTA_200mV << MASK2SHIFT(PCA9481_BIT_UV_TRACK_DELTA) | + UV_TRACK_DEGLITCH_1ms << MASK2SHIFT(PCA9481_BIT_VIN_UV_TRACKING_DEGLITCH)); + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_SC_CNTL_1, + PCA9481_BIT_UV_TRACK_DELTA | PCA9481_BIT_VIN_UV_TRACKING_DEGLITCH, + val); + if (ret < 0) + goto error; + + /* Go to CV mode */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_CVMODE; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } else { + /* Compare IIN_ADC with 0.95*IIN_CC */ + if (iin > pca9481->iin_cc / CUR_RATIO_DENOM_CV_TRANS * TA_CUR_RATIO_CV_TRANS) { + /* Maintain present TA voltage */ + pr_info("%s: CV trans #4(cont1.): ta_vol=%d\n", __func__, pca9481->ta_vol); + /* Wait 1sec */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_ENTER_CVMODE; + pca9481->timer_period = PDMSG_WAIT_T; + mutex_unlock(&pca9481->lock); + } else { + /* Increase TA voltage (20mV) */ + pca9481->ta_vol = pca9481->ta_vol + PD_MSG_TA_VOL_STEP * pca9481->chg_mode; + pr_info("%s: CV trans #4(cont2.): ta_vol=%d\n", __func__, pca9481->ta_vol); + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } + } + break; + + default: + /* Cannot enter here */ + pr_info("%s: Error - CV_TRANS_%d\n", __func__, pca9481->cv_trans); + pca9481->cv_trans = CV_TRANS_0; + ret = -EINVAL; + goto error; + break; + } + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + + } + +error: + pr_info("%s: End, ret=%d\n", __func__, ret); + return ret; +} + +/* 2:1 Direct Charging CV MODE control */ +static int pca9481_charge_cvmode(struct pca9481_charger *pca9481) +{ + int ret = 0; + int cvmode = 0; + int iin, ichg, vbat; + + pr_info("%s: ======START=======\n", __func__); + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_CV_MODE); +#else + pca9481->charging_state = DC_STATE_CV_MODE; +#endif + + ret = pca9481_check_error(pca9481); + if (ret != 0) { + /* Check error type */ + if (ret == -ERROR_DCRCP) { + /* Set dcmode to DCMODE_CHG_DONE */ + cvmode = DCMODE_CHG_DONE; + pr_info("%s: dcmode is DCMODE_CHG_DONE by RCP\n", __func__); + } else { + /* DC error */ + goto error; // This is not active mode. + } } + /* Check new request */ + if (pca9481->req_new_dc_mode == true) { + ret = pca9481_set_new_dc_mode(pca9481); + } else if (pca9481->req_new_iin == true) { + ret = pca9481_set_new_iin(pca9481); + } else if (pca9481->req_new_vfloat == true) { + ret = pca9481_set_new_vfloat(pca9481); + } else { + /* Check the status */ + if (cvmode != DCMODE_CHG_DONE) { + cvmode = pca9481_check_dcmode_status(pca9481); + if (cvmode < 0) { + ret = cvmode; + goto error; + } + + /* Read IIN_ADC */ + iin = pca9481_read_adc(pca9481, ADCCH_VIN_CURRENT); + /* Read ICHG_ADC */ + ichg = pca9481_read_adc(pca9481, ADCCH_BAT_CURRENT); + /* Read VBAT_ADC */ + vbat = pca9481_read_adc(pca9481, ADCCH_BATP_BATN); + /* Store VBAT_ADC to previous vbat */ + pca9481->prev_vbat = vbat; + + /* Check charging done state */ + if (cvmode == DCMODE_LOOP_INACTIVE) { + /* Compare iin with input topoff current */ + pr_info("%s: iin=%d, ichg=%d, iin_topoff=%d\n", + __func__, iin, ichg, pca9481->iin_topoff); + if (iin < pca9481->iin_topoff) { + /* Change cvmode status to charging done */ + cvmode = DCMODE_CHG_DONE; + pr_info("%s: CVMODE Status=%d\n", __func__, cvmode); + } + } + } + switch (cvmode) { + case DCMODE_CHG_DONE: + /* Charging Done */ + /* Keep CV mode until battery driver send stop charging */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_CHARGING_DONE); +#else + pca9481->charging_state = DC_STATE_CHARGING_DONE; +#endif + /* Need to implement notification function */ + /* A customer should insert code */ + + pr_info("%s: CV Done\n", __func__); +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_done(pca9481, true); +#endif + /* Check the charging status after notification function */ + if (pca9481->charging_state != DC_STATE_NO_CHARGING) { + /* Notification function does not stop timer work yet */ + /* Keep the charging done state */ + /* Set timer */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_CVMODE; + + /* Add to check charging step and set the polling time */ + if (pca9481->vfloat < pca9481->pdata->step1_vth) { + /* Step1 charging - polling time is cv_polling */ + pca9481->timer_period = pca9481->pdata->cv_polling; + } else if ((pca9481->dec_vfloat == true) || (pca9481->vfloat >= pca9481->max_vfloat)) { + /* present vfloat is lower than previous vfloat or + * present vfloat is maximum vfloat + * pollig time is CVMODE_CHECK2_T + */ + pca9481->timer_period = CVMODE_CHECK2_T; + } else { + /* Step2 or 3 charging - polling time is CVMODE_CHECK_T */ + pca9481->timer_period = CVMODE_CHECK_T; + } + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } else { + /* Already called stop charging by notification function */ + pr_info("%s: Already stop DC\n", __func__); + } + break; + + case DCMODE_CHG_LOOP: + case DCMODE_IIN_LOOP: + /* Check the TA type */ + if (pca9481->ta_type == TA_TYPE_WIRELESS) { + /* Decrease RX Voltage (100mV) */ + pca9481->ta_vol = pca9481->ta_vol - WCRX_VOL_STEP; + pr_info("%s: CV LOOP(WC), Cont: iin=%d, ichg=%d, rx_vol=%d\n", + __func__, iin, ichg, pca9481->ta_vol); + } else { + /* Check TA current */ + if (pca9481->ta_cur > TA_MIN_CUR) { + /* TA current is higher than (1.0A*chg_mode) */ + /* Check TA current and compare it with IIN_CC */ + if (pca9481->ta_cur <= pca9481->iin_cc - TA_CUR_LOW_OFFSET) { + /* IIN_LOOP still happens even though TA current is less than IIN_CC - 200mA */ + /* TA has abnormal behavior */ + /* Decrease TA voltage (20mV) */ + pca9481->ta_vol = pca9481->ta_vol - PD_MSG_TA_VOL_STEP; + pr_info("%s: CV LOOP(1):iin=%d, ichg=%d, ta_cur=%d, next_ta_vol=%d\n", + __func__, iin, ichg, pca9481->ta_cur, pca9481->ta_vol); + /* Update TA target voltage */ + pca9481->ta_target_vol = pca9481->ta_vol; + } else { + /* TA current is higher than IIN_CC - 200mA */ + /* Decrease TA current first to reduce input current */ + /* Decrease TA current (50mA) */ + pca9481->ta_cur = pca9481->ta_cur - PD_MSG_TA_CUR_STEP; + pr_info("%s: CV LOOP(2):iin=%d, ichg=%d, ta_vol=%d, next_ta_cur=%d\n", + __func__, iin, ichg, pca9481->ta_vol, pca9481->ta_cur); + } + } else { + /* TA current is less than (1.0A*chg_mode) */ + /* Decrease TA Voltage (20mV) */ + pca9481->ta_vol = pca9481->ta_vol - PD_MSG_TA_VOL_STEP; + pr_info("%s: CV LOOP(3), Cont: iin=%d, ichg=%d, ta_vol=%d\n", + __func__, iin, ichg, pca9481->ta_vol); + } + } + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + break; + + case DCMODE_VFLT_LOOP: + /* Check the TA type */ + if (pca9481->ta_type == TA_TYPE_WIRELESS) { + /* Decrease RX voltage */ + pca9481->ta_vol = pca9481->ta_vol - WCRX_VOL_STEP; + pr_info("%s: CV VFLOAT, Cont: iin=%d, ichg=%d, rx_vol=%d\n", + __func__, iin, ichg, pca9481->ta_vol); + } else { + /* Decrease TA voltage */ + pca9481->ta_vol = pca9481->ta_vol - PD_MSG_TA_VOL_STEP; + pr_info("%s: CV VFLOAT, Cont: iin=%d, ichg=%d, ta_vol=%d\n", + __func__, iin, ichg, pca9481->ta_vol); + } + + /* Set TA target voltage to TA voltage */ + pca9481->ta_target_vol = pca9481->ta_vol; + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + break; + + case DCMODE_LOOP_INACTIVE: + /* Set timer */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_CVMODE; + /* Add to check charging step and set the polling time */ + if (pca9481->vfloat < pca9481->pdata->step1_vth) { + /* Step1 charging - polling time is cv_polling */ + pca9481->timer_period = pca9481->pdata->cv_polling; + } else if ((pca9481->dec_vfloat == true) || (pca9481->vfloat >= pca9481->max_vfloat)) { + /* present vfloat is lower than previous vfloat or + * present vfloat is maximum vfloat + * pollig time is CVMODE_CHECK2_T + */ + pca9481->timer_period = CVMODE_CHECK2_T; + } else { + /* Step2 or 3 charging - polling time is CVMODE_CHECK_T */ + pca9481->timer_period = CVMODE_CHECK_T; + } + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + break; + + default: + break; + } + } + +error: + pr_info("%s: End, ret=%d\n", __func__, ret); + return ret; +} + +/* 2:1 Direct Charging FPDO CV MODE control */ +static int pca9481_charge_fpdo_cvmode(struct pca9481_charger *pca9481) +{ + int ret = 0; + int cvmode; + int iin, ichg; +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + union power_supply_propval val; +#endif + + pr_info("%s: ======START=======\n", __func__); + + pca9481->charging_state = DC_STATE_FPDO_CV_MODE; + + ret = pca9481_check_error(pca9481); + if (ret != 0) + goto error; // This is not active mode. + + /* Check new request */ + if (pca9481->req_new_iin == true) { + /* Set IIN_CC to new iin */ + pca9481->iin_cc = pca9481->new_iin; + /* Update iin_cfg */ + pca9481->iin_cfg = pca9481->iin_cc; + /* Set IIN_CFG to new IIN */ + ret = pca9481_set_input_current(pca9481, pca9481->iin_cc); + if (ret < 0) + goto error; + + /* Check req_new_iin clear condition */ + /* Compare new_iin with iin_cc */ + if (pca9481->new_iin != pca9481->iin_cc) { + /* new_iin is different from iin_cc */ + /* Battery driver set new input current during input current change */ + /* Keep req_new_iin to true */ + pr_info("%s: Keep req_new_iin to true, new_iin=%d, iin_cc=%d\n", + __func__, pca9481->new_iin, pca9481->iin_cc); + } else { + /* new_iin is same as iin_cc */ + /* Clear req_new_iin */ + mutex_lock(&pca9481->lock); + pca9481->req_new_iin = false; + mutex_unlock(&pca9481->lock); + /* Compare new_iin with iin_cc again */ + if (pca9481->new_iin != pca9481->iin_cc) { + /* new_iin is different from iin_cc */ + /* Set req_new_iin again */ + mutex_lock(&pca9481->lock); + pca9481->req_new_iin = true; + mutex_unlock(&pca9481->lock); + } + } + + /* Set timer - 1s */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_FPDOCVMODE; + pca9481->timer_period = CVMODE_CHECK2_T; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } else if (pca9481->req_new_vfloat == true) { + /* Set VFLOAT to new vfloat */ + pca9481->vfloat = pca9481->new_vfloat; + ret = pca9481_set_vfloat(pca9481, pca9481->vfloat); + if (ret < 0) + goto error; + + /* Clear req_new_vfloat */ + mutex_lock(&pca9481->lock); + pca9481->req_new_vfloat = false; + mutex_unlock(&pca9481->lock); + + /* Set timer - 1s */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_FPDOCVMODE; + pca9481->timer_period = CVMODE_CHECK2_T; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } else { + cvmode = pca9481_check_dcmode_status(pca9481); + if (cvmode < 0) { + ret = cvmode; + goto error; + } + + /* Read IIN_ADC */ + iin = pca9481_read_adc(pca9481, ADCCH_VIN_CURRENT); + /* Read ICHG_ADC */ + ichg = pca9481_read_adc(pca9481, ADCCH_BAT_CURRENT); + + /* Check charging done state */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + psy_do_property("battery", get, POWER_SUPPLY_PROP_VOLTAGE_NOW, val); + + if (cvmode == DCMODE_LOOP_INACTIVE || val.intval >= pca9481->fpdo_dc_vnow_topoff) { + /* Compare iin with input topoff current */ + pr_info("%s: iin=%d, ichg=%d, vnow=%d, fpdo_dc_iin_topoff=%d, fpdo_dc_vnow_topoff=%d\n", + __func__, iin, ichg, val.intval, + pca9481->fpdo_dc_iin_topoff, pca9481->fpdo_dc_vnow_topoff); + if (val.intval >= pca9481->fpdo_dc_vnow_topoff || iin < pca9481->fpdo_dc_iin_topoff) { +#else + if (cvmode == DCMODE_LOOP_INACTIVE) { + /* Compare iin with input topoff current */ + pr_info("%s: iin=%d, ichg=%d, iin_topoff=%d\n", + __func__, iin, ichg, pca9481->iin_topoff); + if (iin < pca9481->iin_topoff) { +#endif + /* Check charging done counter */ + if (pca9481->done_cnt < FPDO_DONE_CNT) { + /* Keep cvmode status */ + pr_info("%s: Keep FPDO CVMODE Status=%d\n", __func__, cvmode); + /* Increase charging done counter */ + pca9481->done_cnt++; + } else { + /* Change cvmode status to charging done */ + cvmode = DCMODE_CHG_DONE; + pr_info("%s: FPDO_CVMODE Status=%d\n", __func__, cvmode); + /* Clear charging done counter */ + pca9481->done_cnt = 0; + } + } else { + /* Clear charging done counter */ + pca9481->done_cnt = 0; + } + } + + switch (cvmode) { + case DCMODE_CHG_DONE: + /* Charging Done */ + /* Keep FPDO CV mode until battery driver send stop charging */ + pca9481->charging_state = DC_STATE_CHARGING_DONE; + /* Need to implement notification function */ + /* A customer should insert code */ + + pr_info("%s: FPDO CV Done\n", __func__); +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_done(pca9481, true); +#endif + + /* Check the charging status after notification function */ + if (pca9481->charging_state != DC_STATE_NO_CHARGING) { + /* Notification function does not stop timer work yet */ + /* Keep the charging done state */ + /* Set timer */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_FPDOCVMODE; + pca9481->timer_period = CVMODE_CHECK2_T; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } else { + /* Already called stop charging by notification function */ + pr_info("%s: Already stop DC\n", __func__); + } + break; + + case DCMODE_CHG_LOOP: + case DCMODE_IIN_LOOP: + /* IIN_LOOP happens */ + pr_info("%s: FPDO CV IIN_LOOP\n", __func__); + /* Need to stop DC by battery driver */ + + /* Need to implement notification function */ + /* A customer should insert code */ + /* To do here */ + + /* Check the charging status after notification function */ + if (pca9481->charging_state != DC_STATE_NO_CHARGING) { + /* Notification function does not stop timer work yet */ + /* Keep the current state */ + /* Set timer - 1s */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_FPDOCVMODE; + pca9481->timer_period = CVMODE_CHECK2_T; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } else { + /* Already called stop charging by notification function */ + pr_info("%s: Already stop DC\n", __func__); + } + break; + + case DCMODE_VFLT_LOOP: + /* VFLOAT_LOOP happens */ + pr_info("%s: FPDO CV VFLOAT_LOOP\n", __func__); + /* Need to stop DC and transit to switching charger by battery driver */ + + /* Need to implement notification function */ + /* A customer should insert code */ + /* To do here */ + + /* Check the charging status after notification function */ + if (pca9481->charging_state != DC_STATE_NO_CHARGING) { + /* Notification function does not stop timer work yet */ + /* Keep the current state */ + /* Set timer */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_FPDOCVMODE; + pca9481->timer_period = CVMODE_CHECK2_T; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } else { + /* Already called stop charging by notification function */ + pr_info("%s: Already stop DC\n", __func__); + } + break; + + case DCMODE_LOOP_INACTIVE: + /* Set timer */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_FPDOCVMODE; + pca9481->timer_period = CVMODE_CHECK3_T; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + break; + + default: + break; + } + } + +error: + pr_info("%s: End, ret=%d\n", __func__, ret); + return ret; +} + +/* Direct Charging Bypass Mode Control */ +static int pca9481_charge_bypass_mode(struct pca9481_charger *pca9481) +{ + int ret = 0; + int dc_status; + int vbat, iin; + + pr_info("%s: ======START=======\n", __func__); + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_BYPASS_MODE); +#else + pca9481->charging_state = DC_STATE_BYPASS_MODE; +#endif + + ret = pca9481_check_error(pca9481); + if (ret < 0) + goto error; // This is not active mode. + + /* Check new request */ + if (pca9481->req_new_dc_mode == true) { + ret = pca9481_set_new_dc_mode(pca9481); + } else if (pca9481->req_new_iin == true) { + ret = pca9481_set_new_iin(pca9481); + } else if (pca9481->req_new_vfloat == true) { + ret = pca9481_set_new_vfloat(pca9481); + } else { + dc_status = pca9481_check_dcmode_status(pca9481); + if (dc_status < 0) { + ret = dc_status; + goto error; + } + + /* Read IIN ADC */ + iin = pca9481_read_adc(pca9481, ADCCH_VIN_CURRENT); + /* Read VBAT ADC */ + vbat = pca9481_read_adc(pca9481, ADCCH_BATP_BATN); + + pr_info("%s: iin=%d, vbat=%d\n", __func__, iin, vbat); + + if (dc_status == DCMODE_IIN_LOOP) { + /* Decrease input current */ + /* Check TA current and compare it with IIN_CC */ + if (pca9481->ta_cur <= pca9481->iin_cc - TA_CUR_LOW_OFFSET) { + /* IIN_LOOP still happens even though TA current is less than IIN_CC - 200mA */ + /* TA has abnormal behavior */ + /* Decrease TA voltage (20mV) */ + pca9481->ta_vol = pca9481->ta_vol - PD_MSG_TA_VOL_STEP; + pr_info("%s: IIN LOOP:iin=%d, ta_cur=%d, next_ta_vol=%d\n", + __func__, iin, pca9481->ta_cur, pca9481->ta_vol); + } else { + /* TA current is higher than IIN_CC - 200mA */ + /* Decrease TA current first to reduce input current */ + /* Decrease TA current (50mA) */ + pca9481->ta_cur = pca9481->ta_cur - PD_MSG_TA_CUR_STEP; + pr_info("%s: IIN LOOP:iin=%d, next_ta_cur=%d\n", + __func__, iin, pca9481->ta_cur); + } + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } else { + /* Ignore other status */ + /* Keep Bypass mode */ + pr_info("%s: Bypass mode, status=%d, ta_cur=%d, ta_vol=%d\n", + __func__, dc_status, pca9481->ta_cur, pca9481->ta_vol); + /* Set timer - 10s */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_BYPASSMODE; + pca9481->timer_period = BYPMODE_CHECK_T; /* 10s */ + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } + } + +error: + pr_info("%s: End, ret=%d\n", __func__, ret); + return ret; +} + +/* Direct Charging DC mode Change Control */ +static int pca9481_charge_dcmode_change(struct pca9481_charger *pca9481) +{ + int ret = 0; + + pr_info("%s: ======START=======\n", __func__); + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_DCMODE_CHANGE); +#else + pca9481->charging_state = DC_STATE_DCMODE_CHANGE; +#endif + + ret = pca9481_set_new_dc_mode(pca9481); + + pr_info("%s: End, ret=%d\n", __func__, ret); + return ret; +} + +/* Preset TA voltage and current for Direct Charging Mode */ +static int pca9481_preset_dcmode(struct pca9481_charger *pca9481) +{ + int vbat; + unsigned int val; + int ret = 0; + int chg_mode; + + pr_info("%s: ======START=======\n", __func__); + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_PRESET_DC); +#else + pca9481->charging_state = DC_STATE_PRESET_DC; +#endif + + /* Check TA type */ + if (pca9481->ta_type == TA_TYPE_USBPD_20) { + /* TA type is USBPD 2.0 and support only FPDO */ + pr_info("%s: ta type : fixed PDO\n", __func__); + + /* Disable OV_TRACKING_EN */ + val = 0; + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_SC_CNTL_2, + PCA9481_BIT_OV_TRACKING_EN, val); + /* Set PDO object position to 9V FPDO */ + pca9481->ta_objpos = 2; + /* Set TA voltage to 9V */ + pca9481->ta_vol = 9000000; + /* Set TA maximum voltage to 9V */ + pca9481->ta_max_vol = 9000000; + /* Set IIN_CC to iin_cfg */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481->iin_cc = pca9481->pdata->fpdo_dc_iin_lowest_limit; +#else + pca9481->iin_cc = pca9481->iin_cfg; +#endif + /* Set TA operating current and maximum current to iin_cc */ + pca9481->ta_cur = pca9481->iin_cc; + pca9481->ta_max_cur = pca9481->iin_cc; + /* Calculate TA maximum power */ + pca9481->ta_max_pwr = (pca9481->ta_max_vol/DENOM_U_M)*(pca9481->ta_max_cur/DENOM_U_M); + + pr_info("%s: Preset DC(FPDO), ta_max_vol=%d, ta_max_cur=%d, ta_max_pwr=%d, iin_cc=%d, chg_mode=%d\n", + __func__, pca9481->ta_max_vol, pca9481->ta_max_cur, pca9481->ta_max_pwr, pca9481->iin_cc, pca9481->chg_mode); + } else { + /* Read VBAT ADC */ + vbat = pca9481_read_adc(pca9481, ADCCH_BATP_BATN); + if (vbat < 0) { + ret = vbat; + goto error; + } + + /* Compare VBAT with VBAT ADC */ + if (vbat > pca9481->vfloat) { + /* Warn "Invalid battery voltage to start direct charging" */ + pr_err("%s: Warning - vbat adc(%duV) is higher than VFLOAT(%duV)\n", + __func__, vbat, pca9481->vfloat); + } + + /* Check minimum VBAT level */ + if (vbat <= DC_VBAT_MIN_ERR) { + /* Invalid battery level to start direct charging */ + pr_err("%s: This vbat(%duV) will make VIN_OV_TRACKING error\n", __func__, vbat); + ret = -EINVAL; + goto error; + } + + /* Check the TA type and set the charging mode */ + if (pca9481->ta_type == TA_TYPE_WIRELESS) { + /* Wireless Charger is connected */ + /* Set the RX max current to input request current(iin_cfg) initially */ + /* to get RX maximum current from RX IC */ + pca9481->ta_max_cur = pca9481->iin_cfg; + /* Set the RX max voltage to enough high value to find RX maximum voltage initially */ + pca9481->ta_max_vol = WCRX_MAX_VOL * pca9481->pdata->chg_mode; + /* Get the RX max current/voltage(RX_MAX_CUR/VOL) */ + ret = pca9481_get_rx_max_power(pca9481); + if (ret < 0) { + /* RX IC does not have the desired maximum voltage */ + /* Check the desired mode */ + if (pca9481->pdata->chg_mode == CHG_4TO1_DC_MODE) { + /* RX IC doesn't have any maximum voltage to support 4:1 mode */ + /* Get the RX max current/voltage with 2:1 mode */ + pca9481->ta_max_vol = WCRX_MAX_VOL; + ret = pca9481_get_rx_max_power(pca9481); + if (ret < 0) { + pr_err("%s: RX IC doesn't have any RX voltage to support 2:1 or 4:1\n", + __func__); + pca9481->chg_mode = CHG_NO_DC_MODE; + goto error; + } else { + /* RX IC has the maximum RX voltage to support 2:1 mode */ + pca9481->chg_mode = CHG_2TO1_DC_MODE; + } + } else { + /* The desired CHG mode is 2:1 mode */ + /* RX IC doesn't have any RX voltage to support 2:1 mode*/ + pr_err("%s: RX IC doesn't have any RX voltage to support 2:1\n", __func__); + pca9481->chg_mode = CHG_NO_DC_MODE; + goto error; + } + } else { + /* RX IC has the desired RX voltage */ + pca9481->chg_mode = pca9481->pdata->chg_mode; + } + + chg_mode = pca9481->chg_mode; + + /* Set IIN_CC to MIN[IIN, (RX_MAX_CUR by RX IC)*chg_mode]*/ + pca9481->iin_cc = MIN(pca9481->iin_cfg, (pca9481->ta_max_cur*chg_mode)); + /* Set the current IIN_CC to iin_cfg */ + pca9481->iin_cfg = pca9481->iin_cc; + + /* Set RX voltage to MAX[(2*VBAT_ADC*chg_mode + offset), TA_MIN_VOL_PRESET*chg_mode] */ + pca9481->ta_vol = max(TA_MIN_VOL_PRESET*chg_mode, (2*vbat*chg_mode + TA_VOL_PRE_OFFSET)); + val = pca9481->ta_vol/WCRX_VOL_STEP; /* RX voltage resolution is 100mV */ + pca9481->ta_vol = val*WCRX_VOL_STEP; + /* Set RX voltage to MIN[RX voltage, RX_MAX_VOL*chg_mode] */ + pca9481->ta_vol = MIN(pca9481->ta_vol, pca9481->ta_max_vol); + + pr_info("%s: Preset DC, rx_max_vol=%d, rx_max_cur=%d, rx_max_pwr=%d, iin_cc=%d, chg_mode=%d\n", + __func__, pca9481->ta_max_vol, pca9481->ta_max_cur, pca9481->ta_max_pwr, + pca9481->iin_cc, pca9481->chg_mode); + + pr_info("%s: Preset DC, rx_vol=%d\n", __func__, pca9481->ta_vol); + + } else { + /* USBPD TA is connected */ + /* Set the TA max current to input request current(iin_cfg) initially */ + /* to get TA maximum current from PD IC */ + pca9481->ta_max_cur = pca9481->iin_cfg; + /* Set the TA max voltage to enough high value to find TA maximum voltage initially */ + pca9481->ta_max_vol = TA_MAX_VOL * pca9481->pdata->chg_mode; + /* Search the proper object position of PDO */ + pca9481->ta_objpos = 0; + /* Get the APDO max current/voltage(TA_MAX_CUR/VOL) */ + ret = pca9481_get_apdo_max_power(pca9481); + if (ret < 0) { + /* TA does not have the desired APDO */ + /* Check the desired mode */ + if (pca9481->pdata->chg_mode == CHG_4TO1_DC_MODE) { + /* TA doesn't have any APDO to support 4:1 mode */ + /* Get the APDO max current/voltage with 2:1 mode */ + pca9481->ta_max_vol = TA_MAX_VOL; + pca9481->ta_objpos = 0; + ret = pca9481_get_apdo_max_power(pca9481); + if (ret < 0) { + pr_err("%s: TA doesn't have any APDO to support 2:1 or 4:1\n", + __func__); + pca9481->chg_mode = CHG_NO_DC_MODE; + goto error; + } else { + /* TA has APDO to support 2:1 mode */ + pca9481->chg_mode = CHG_2TO1_DC_MODE; + } + } else { + /* The desired TA mode is 2:1 mode */ + /* TA doesn't have any APDO to support 2:1 mode*/ + pr_err("%s: TA doesn't have any APDO to support 2:1\n", __func__); + pca9481->chg_mode = CHG_NO_DC_MODE; + goto error; + } + } else { + /* TA has the desired APDO */ + pca9481->chg_mode = pca9481->pdata->chg_mode; + } + + chg_mode = pca9481->chg_mode; + + /* Calculate new TA maximum current and voltage that used in the direct charging */ + /* Set IIN_CC to MIN[IIN, (TA_MAX_CUR by APDO)*chg_mode]*/ + pca9481->iin_cc = MIN(pca9481->iin_cfg, (pca9481->ta_max_cur*chg_mode)); + /* Set the current IIN_CC to iin_cfg for recovering it after resolution adjustment */ + pca9481->iin_cfg = pca9481->iin_cc; + /* Calculate new TA max voltage */ + /* Adjust IIN_CC with APDO resoultion(50mA) */ + /* - It will recover to the original value after max voltage calculation */ + val = pca9481->iin_cc/PD_MSG_TA_CUR_STEP; + pca9481->iin_cc = val*PD_MSG_TA_CUR_STEP; + /* Set TA_MAX_VOL to MIN[TA_MAX_VOL*chg_mode, TA_MAX_PWR/(IIN_CC/chg_mode)] */ + val = pca9481->ta_max_pwr/(pca9481->iin_cc/chg_mode/1000); /* mV */ + val = val*1000/PD_MSG_TA_VOL_STEP; /* Adjust values with APDO resolution(20mV) */ + val = val*PD_MSG_TA_VOL_STEP; /* uV */ + pca9481->ta_max_vol = MIN(val, TA_MAX_VOL*chg_mode); + + /* Set TA voltage to MAX[TA_MIN_VOL_PRESET*chg_mode, (2*VBAT_ADC*chg_mode + offset)] */ + pca9481->ta_vol = max(TA_MIN_VOL_PRESET*chg_mode, (2*vbat*chg_mode + TA_VOL_PRE_OFFSET)); + val = pca9481->ta_vol/PD_MSG_TA_VOL_STEP; /* PPS voltage resolution is 20mV */ + pca9481->ta_vol = val*PD_MSG_TA_VOL_STEP; + /* Set TA voltage to MIN[TA voltage, TA_MAX_VOL*chg_mode] */ + pca9481->ta_vol = MIN(pca9481->ta_vol, pca9481->ta_max_vol); + /* Set the initial TA current to IIN_CC/chg_mode */ + pca9481->ta_cur = pca9481->iin_cc/chg_mode; + /* Recover IIN_CC to the original value(iin_cfg) */ + pca9481->iin_cc = pca9481->iin_cfg; + + pr_info("%s: Preset DC, ta_max_vol=%d, ta_max_cur=%d, ta_max_pwr=%d, iin_cc=%d, chg_mode=%d\n", + __func__, pca9481->ta_max_vol, pca9481->ta_max_cur, pca9481->ta_max_pwr, + pca9481->iin_cc, pca9481->chg_mode); + + pr_info("%s: Preset DC, ta_vol=%d, ta_cur=%d\n", + __func__, pca9481->ta_vol, pca9481->ta_cur); + } + } + + /* Send PD Message */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PDMSG_SEND; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + +error: + pr_info("%s: End, ret=%d\n", __func__, ret); + return ret; +} + + +/* Preset direct charging configuration */ +static int pca9481_preset_config(struct pca9481_charger *pca9481) +{ + int ret = 0; + + pr_info("%s: ======START=======\n", __func__); + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_PRESET_DC); +#else + pca9481->charging_state = DC_STATE_PRESET_DC; +#endif + + /* Set IIN_CFG to IIN_CC */ + ret = pca9481_set_input_current(pca9481, pca9481->iin_cc); + if (ret < 0) + goto error; + + /* Set ICHG_CFG to enough high value */ + if (pca9481->ichg_cfg < 2*pca9481->iin_cfg) + pca9481->ichg_cfg = 2*pca9481->iin_cfg; + ret = pca9481_set_charging_current(pca9481, pca9481->ichg_cfg); + if (ret < 0) + goto error; + + /* Check TA type */ + if (pca9481->ta_type == TA_TYPE_USBPD_20) { + /* Set VFLOAT to VBAT */ + ret = pca9481_set_vfloat(pca9481, pca9481->vfloat); + if (ret < 0) + goto error; + + /* Set switching frequency */ + pca9481->fsw_cfg = pca9481->pdata->fsw_cfg_fpdo; + } else { + /* Set VFLOAT to default value */ + ret = pca9481_set_vfloat(pca9481, PCA9481_VBAT_REG_DFT); + if (ret < 0) + goto error; + + /* Set switching frequency */ + if (pca9481->iin_cc > IIN_LOW_TH_SW_FREQ) + pca9481->fsw_cfg = pca9481->pdata->fsw_cfg; + else + pca9481->fsw_cfg = pca9481->pdata->fsw_cfg_low; + } + ret = pca9481_write_reg(pca9481, PCA9481_REG_SC_CNTL_0, PCA9481_FSW_CFG(pca9481->fsw_cfg)); + if (ret < 0) + goto error; + + pr_info("%s: sw_freq=%dkHz\n", __func__, pca9481->fsw_cfg); + + /* Enable PCA9481 */ + ret = pca9481_set_charging(pca9481, true); + if (ret < 0) + goto error; + + /* Clear previous iin adc */ + pca9481->prev_iin = 0; + + /* Clear TA increment flag */ + pca9481->prev_inc = INC_NONE; + + /* Go to CHECK_ACTIVE state after 150ms*/ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_ACTIVE; + pca9481->timer_period = ENABLE_DELAY_T; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + ret = 0; + +error: + pr_info("%s: End, ret=%d\n", __func__, ret); + return ret; +} + +/* Check the charging status before entering the adjust cc mode */ +static int pca9481_check_active_state(struct pca9481_charger *pca9481) +{ + int ret = 0; + + pr_info("%s: ======START=======\n", __func__); + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_CHECK_ACTIVE); +#else + pca9481->charging_state = DC_STATE_CHECK_ACTIVE; +#endif + ret = pca9481_check_error(pca9481); + + if (ret == 0) { + /* PCA9481 is in active state */ + /* Clear retry counter */ + pca9481->retry_cnt = 0; + /* Check TA type */ + if (pca9481->ta_type == TA_TYPE_USBPD_20) { + /* Go to FPDO CV mode */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_FPDOCVMODE; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } else { + /* Go to Adjust CC mode */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_ADJUST_CCMODE; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + } + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } else if (ret == -EAGAIN) { + /* It is the retry condition */ + /* Check the retry counter */ + if (pca9481->retry_cnt < MAX_RETRY_CNT) { + /* Disable charging */ + ret = pca9481_set_charging(pca9481, false); + if (ret < 0) + goto error; + /* Softreset */ + ret = pca9481_softreset(pca9481); + if (ret < 0) + goto error; + /* Increase retry counter */ + pca9481->retry_cnt++; + pr_err("%s: retry charging start - retry_cnt=%d\n", __func__, pca9481->retry_cnt); + /* Go to DC_STATE_PRESET_DC */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PRESET_DC; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + ret = 0; + } else { + pr_err("%s: retry fail\n", __func__); + /* Disable charging */ + ret = pca9481_set_charging(pca9481, false); + if (ret < 0) + goto error; + /* Softreset */ + ret = pca9481_softreset(pca9481); + if (ret < 0) + goto error; + /* Notify maximum retry error */ + ret = -EINVAL; + /* Stop charging in timer_work */ + } + } else { + pr_err("%s: dc start fail\n", __func__); + /* Implement error handler function if it is needed */ + /* Disable charging */ + ret = pca9481_set_charging(pca9481, false); + if (ret < 0) + goto error; + /* Softreset */ + ret = pca9481_softreset(pca9481); + if (ret < 0) + goto error; + /* Notify invalid error */ + ret = -EINVAL; + /* Stop charging in timer_work */ + } + +error: + pr_info("%s: End, ret=%d\n", __func__, ret); + return ret; +} + +/* Start reverse mode setting */ +static int pca9481_charge_start_reverse(struct pca9481_charger *pca9481) +{ + int ret = 0; + int val; + + pr_info("%s: ======START=======\n", __func__); + + pca9481->charging_state = DC_STATE_REVERSE_MODE; + + /* Shutdown ADC operation to reduce leakage current */ + /* Configure ADC operation mode to Force shutdown mode */ + val = FORCE_SHUTDOWN_MODE << MASK2SHIFT(PCA9481_BIT_ADC_MODE_CFG); + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_ADC_CNTL, + PCA9481_BIT_ADC_MODE_CFG, val); + if (ret < 0) + goto error; + /* Read ADC_CNTL register to confirm shutdown */ + ret = regmap_read(pca9481->regmap, PCA9481_REG_ADC_CNTL, &val); + pr_info("%s: ADC_CTRL : 0x%02x\n", __func__, val); + + ret = pca9481_set_reverse_mode(pca9481, true); + if (ret < 0) + goto error; + + /* Go to reverse mode */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_REVERSE_ACTIVE; + pca9481->timer_period = REVERSE_WAIT_T; // ms unit + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + +error: + pr_info("%s: End, ret=%d\n", __func__, ret); + return ret; +} + +/* Check active status before entering reverse mode */ +static int pca9481_check_reverse_active_state(struct pca9481_charger *pca9481) +{ + int ret = 0; + int val; + + pr_info("%s: ======START=======\n", __func__); + + pca9481->charging_state = DC_STATE_REVERSE_MODE; + + ret = pca9481_check_reverse_error(pca9481); + + if (ret == 0) { + /* PCA9481 is in active state */ + /* Clear retry counter */ + pca9481->retry_cnt = 0; + /* Go to Reverse mode */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_REVERSE_MODE; + pca9481->timer_period = REVERSE_CHECK_T; + mutex_unlock(&pca9481->lock); + + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } else if (ret == -EAGAIN) { + /* It is the retry condition */ + /* Check the retry counter */ + if (pca9481->retry_cnt < MAX_RETRY_CNT) { + /* Disable reverse mode */ + ret = pca9481_set_reverse_mode(pca9481, false); + if (ret < 0) + goto error; + /* Softreset */ + ret = pca9481_softreset(pca9481); + if (ret < 0) + goto error; + /* Increase retry counter */ + pca9481->retry_cnt++; + pr_err("%s: retry to set reverse mode - retry_cnt=%d\n", __func__, pca9481->retry_cnt); + /* Set VIN_OCP_CURRENT_12_11 again */ + ret = pca9481_set_vin_ocp(pca9481, pca9481->iin_rev); + if (ret < 0) + goto error; + + /* Shutdown ADC operation to reduce leakage current */ + /* Configure ADC operation mode to Force shutdown mode */ + val = FORCE_SHUTDOWN_MODE << MASK2SHIFT(PCA9481_BIT_ADC_MODE_CFG); + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_ADC_CNTL, + PCA9481_BIT_ADC_MODE_CFG, val); + if (ret < 0) + goto error; + + /* Set reverse mode */ + ret = pca9481_set_reverse_mode(pca9481, true); + if (ret < 0) + goto error; + + /* Go to DC_STATE_REVERSE_MODE */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_REVERSE_ACTIVE; + pca9481->timer_period = REVERSE_WAIT_T; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + ret = 0; + } else { + pr_err("%s: retry fail\n", __func__); + /* Disable reverse mode */ + ret = pca9481_set_reverse_mode(pca9481, false); + if (ret < 0) + goto error; + /* Softreset */ + ret = pca9481_softreset(pca9481); + if (ret < 0) + goto error; + /* Notify maximum retry error */ + ret = -EINVAL; + /* Stop charging in timer_work */ + } + } else { + pr_err("%s: reverse mode setting fail\n", __func__); + /* Implement error handler function if it is needed */ + /* Disable reverse mode */ + ret = pca9481_set_reverse_mode(pca9481, false); + if (ret < 0) + goto error; + /* Softreset */ + ret = pca9481_softreset(pca9481); + if (ret < 0) + goto error; + /* Stop charging in timer_work */ + ret = -EINVAL; + } + +error: + pr_info("%s: End, ret=%d\n", __func__, ret); + return ret; + +} + +/* Check reverse mode status in polling time */ +static int pca9481_charge_reverse_mode(struct pca9481_charger *pca9481) +{ + int ret; + int vin, iin, vbat; + + pr_info("%s: =========START=========\n", __func__); + + ret = pca9481_check_reverse_error(pca9481); + if (ret < 0) { + /* Error happens and stop reverse mode */ + pr_info("%s: Error happens in reverse mode\n", __func__); + } else { + /* Check reverse mode status in polling time */ + /* Read VIN_ADC, IIN_ADC, and VBAT_ADC */ + vin = pca9481_read_adc(pca9481, ADCCH_VIN); + iin = pca9481_read_adc(pca9481, ADCCH_VIN_CURRENT); + vbat = pca9481_read_adc(pca9481, ADCCH_BATP_BATN); + pr_info("%s: reverse mode, vin=%d, iin=%d, vbat=%d\n", + __func__, vin, iin, vbat); + + /* Set timer - 5s */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_CHECK_REVERSE_MODE; + pca9481->timer_period = REVERSE_CHECK_T; /* 5s */ + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } + + pr_info("%s: End, ret=%d\n", __func__, ret); + return ret; +} + +/* Enter direct charging algorithm */ +static int pca9481_start_direct_charging(struct pca9481_charger *pca9481) +{ + int ret; + unsigned int val; + u8 reg_val[REG_BUFFER_MAX]; + union power_supply_propval prop_val; + + pr_info("%s: =========START=========\n", __func__); + +#ifdef CONFIG_SEC_FACTORY + /* Set OV_TRACK_EN to disable */ + /* Factory mode just use VIN_FIXED_OVP_EN(10.5V) and don't use OV_TRACKING_EN */ + val = 0 << MASK2SHIFT(PCA9481_BIT_OV_TRACKING_EN); + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_SC_CNTL_2, + PCA9481_BIT_OV_TRACKING_EN, val); +#else + /* Set OV_TRACK_DELTA to 800mV */ + val = OV_TRACK_DELTA_800mV << MASK2SHIFT(PCA9481_BIT_OV_TRACK_DELTA); + ret = pca9481_update_reg(pca9481, PCA9481_REG_SC_CNTL_2, + PCA9481_BIT_OV_TRACK_DELTA, val); +#endif + if (ret < 0) + return ret; + + /* Enable VBAT_ADC channel */ + val = PCA9481_BIT_ADC_READ_BATP_BATN_EN; + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_ADC_EN_CNTL_0, + PCA9481_BIT_ADC_READ_BATP_BATN_EN, val); + if (ret < 0) + return ret; + /* Wait 5ms to update ADC */ + usleep_range(5000, 6000); + + /* Set UV_TRACK_DELTA to 0mV and VIN_UV_TRACKING_DEGLITCH to 1ms */ + val = (UV_TRACK_DELTA_0mV << MASK2SHIFT(PCA9481_BIT_UV_TRACK_DELTA) | + UV_TRACK_DEGLITCH_1ms << MASK2SHIFT(PCA9481_BIT_VIN_UV_TRACKING_DEGLITCH)); + ret = pca9481_update_reg(pca9481, PCA9481_REG_SC_CNTL_1, + PCA9481_BIT_UV_TRACK_DELTA | PCA9481_BIT_VIN_UV_TRACKING_DEGLITCH, + val); + if (ret < 0) + return ret; + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + /* Set watchdog timer enable */ + pca9481_set_wdt_enable(pca9481, WDT_ENABLE); + pca9481_check_wdt_control(pca9481); +#endif + + /* Set Switching Frequency - kHz unit */ + val = PCA9481_FSW_CFG(pca9481->pdata->fsw_cfg); + ret = pca9481_write_reg(pca9481, PCA9481_REG_SC_CNTL_0, val); + if (ret < 0) + return ret; + pr_info("%s: sw_freq=%dkHz\n", __func__, pca9481->pdata->fsw_cfg); + + /* Set VIN_CURRENT_OCP_21_11 to 1000mA */ + val = PCA9481_BIT_VIN_CURRENT_OCP_21_11; + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_CHARGING_CNTL_0, + PCA9481_BIT_VIN_CURRENT_OCP_21_11, val); + if (ret < 0) + return ret; + + /* Set EN_CFG to active high - Disable PCA9481 */ + val = PCA9481_EN_ACTIVE_H; + ret = pca9481_update_reg(pca9481, PCA9481_REG_DEVICE_CNTL_2, + PCA9481_BIT_EN_CFG, val); + if (ret < 0) + return ret; + + /* Set NTC0 voltage threshold - cold or cool condition */ + val = PCA9481_NTC_TRIGGER_VOLTAGE(pca9481->pdata->ntc0_th); + val = val << MASK2SHIFT(PCA9481_BIT_NTC_0_TRIGGER_VOLTAGE); + ret = pca9481_write_reg(pca9481, PCA9481_REG_NTC_0_CNTL, val | PCA9481_BIT_NTC_EN); + if (ret < 0) + return ret; + + /* Set NTC1 voltage threshold - warm or hot condition */ + val = PCA9481_NTC_TRIGGER_VOLTAGE(pca9481->pdata->ntc1_th); + ret = pca9481_write_reg(pca9481, PCA9481_REG_NTC_1_CNTL, val); + if (ret < 0) + return ret; + + /* Get TA type information from battery psy */ + + psy_do_property("battery", get, + POWER_SUPPLY_PROP_ONLINE, prop_val); + + if (prop_val.intval == POWER_SUPPLY_TYPE_WIRELESS) { + /* The present power supply type is wireless charger */ + pca9481->ta_type = TA_TYPE_WIRELESS; + } else if (prop_val.intval == SEC_BATTERY_CABLE_FPDO_DC) { + /* The present power supply type is USBPD charger with only fixed PDO */ + pca9481->ta_type = TA_TYPE_USBPD_20; + } else if (prop_val.intval == SEC_BATTERY_CABLE_PDIC_APDO) { + /* The present power supply type is USBPD with APDO */ + pca9481->ta_type = TA_TYPE_USBPD; + } else { + /* DC cannot support the present power supply type - unknown power supply type */ + pca9481->ta_type = TA_TYPE_UNKNOWN; + } + pr_info("%s: ta_type = %d\n", __func__, pca9481->ta_type); + + /* wake lock */ + __pm_stay_awake(pca9481->monitor_wake_lock); + + /* Clear all interrupt registers before starting DC for debugging */ + ret = regmap_bulk_read(pca9481->regmap, PCA9481_REG_INT_DEVICE_0 | PCA9481_REG_BIT_AI, + ®_val[REG_DEVICE_0], REG_BUFFER_MAX); + if (ret < 0) + return ret; + pr_info("%s: reg[0x01]=0x%x,[0x02]=0x%x,[0x03]=0x%x,[0x04]=0x%x,[0x05]=0x%x,[0x06]=0x%x,[0x07]=0x%x\n", + __func__, reg_val[0], reg_val[1], reg_val[2], reg_val[3], reg_val[4], reg_val[5], reg_val[6]); + + /* Preset charging configuration and TA condition */ + ret = pca9481_preset_dcmode(pca9481); + + pr_info("%s: End, ret=%d\n", __func__, ret); + return ret; +} + + +/* Check Vbat minimum level to start direct charging */ +static int pca9481_check_vbatmin(struct pca9481_charger *pca9481) +{ + int vbat; + int ret; + union power_supply_propval val; + + pr_info("%s: =========START=========\n", __func__); + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_CHECK_VBAT); +#else + pca9481->charging_state = DC_STATE_CHECK_VBAT; +#endif + /* Check Vbat */ + vbat = pca9481_read_adc(pca9481, ADCCH_BATP_BATN); + if (vbat < 0) + ret = vbat; + + /* Read switching charger status */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + ret = psy_do_property(pca9481->pdata->sec_dc_name, get, + POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED_DC, val); +#else + ret = pca9481_get_switching_charger_property(POWER_SUPPLY_PROP_CHARGING_ENABLED, &val); +#endif + if (ret < 0) { + /* Start Direct Charging again after 1sec */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_VBATMIN_CHECK; + pca9481->timer_period = VBATMIN_CHECK_T; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + goto error; + } + + if (val.intval == 0) { + /* already disabled switching charger */ + /* Clear retry counter */ + pca9481->retry_cnt = 0; + /* Preset TA voltage and DC parameters */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_PRESET_DC; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } else { + /* Switching charger is enabled */ + if (vbat > DC_VBAT_MIN) { + /* Start Direct Charging */ + /* now switching charger is enabled */ + /* disable switching charger first */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_switching_charger(pca9481, false); +#else + ret = pca9481_set_switching_charger(false, 0, 0, 0); +#endif + } + + /* Wait 1sec for stopping switching charger or Start 1sec timer for battery check */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_VBATMIN_CHECK; + pca9481->timer_period = VBATMIN_CHECK_T; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } + +error: + pr_info("%s: End, ret=%d\n", __func__, ret); + return ret; +} + +#ifdef CONFIG_RTC_HCTOSYS +static int get_current_time(unsigned long *now_tm_sec) +{ + struct rtc_time tm; + struct rtc_device *rtc; + int rc; + + rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE); + if (rtc == NULL) { + pr_err("%s: unable to open rtc device (%s)\n", + __FILE__, CONFIG_RTC_HCTOSYS_DEVICE); + return -EINVAL; + } + + rc = rtc_read_time(rtc, &tm); + if (rc) { + pr_err("Error reading rtc device (%s) : %d\n", + CONFIG_RTC_HCTOSYS_DEVICE, rc); + goto close_time; + } + + rc = rtc_valid_tm(&tm); + if (rc) { + pr_err("Invalid RTC time (%s): %d\n", + CONFIG_RTC_HCTOSYS_DEVICE, rc); + goto close_time; + } + *now_tm_sec = rtc_tm_to_time64(&tm); + +close_time: + rtc_class_close(rtc); + return rc; +} +#endif + +/* delayed work function for charging timer */ +static void pca9481_timer_work(struct work_struct *work) +{ + struct pca9481_charger *pca9481 = container_of(work, struct pca9481_charger, + timer_work.work); + int ret = 0; + unsigned int val; +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + union power_supply_propval value = {0,}; + int wire_status = 0, ta_alert_mode = 0; + + psy_do_property("battery", get, + POWER_SUPPLY_EXT_PROP_CHARGE_COUNTER_SHADOW, value); + wire_status = value.intval; + + psy_do_property("battery", get, + POWER_SUPPLY_EXT_PROP_DIRECT_TA_ALERT, value); + ta_alert_mode = value.intval; + + if ((wire_status == SEC_BATTERY_CABLE_NONE) && pca9481->mains_online) { + if (ta_alert_mode > OCP_NONE) + goto error; + else + return; + } +#endif + +#ifdef CONFIG_RTC_HCTOSYS + get_current_time(&pca9481->last_update_time); + + pr_info("%s: timer id=%d, charging_state=%d, last_update_time=%lu\n", + __func__, pca9481->timer_id, pca9481->charging_state, pca9481->last_update_time); +#else + pr_info("%s: timer id=%d, charging_state=%d\n", + __func__, pca9481->timer_id, pca9481->charging_state); +#endif + + /* Check req_enable flag */ + if (pca9481->req_enable == false) { + /* This case is when battery driver set to stop DC during timer_work is workinig */ + /* And after resuming time_work, timer_id is overwritten by pca9481 function */ + /* Timer id shall be TIMER_ID_NONE */ + pca9481->timer_id = TIMER_ID_NONE; + pr_info("%s: req_enable=%d, timer id=%d, charging_state=%d\n", + __func__, pca9481->req_enable, pca9481->timer_id, pca9481->charging_state); + } + + switch (pca9481->timer_id) { + case TIMER_VBATMIN_CHECK: + ret = pca9481_check_vbatmin(pca9481); + if (ret < 0) + goto error; + break; + + case TIMER_PRESET_DC: + ret = pca9481_start_direct_charging(pca9481); + if (ret < 0) + goto error; + break; + + case TIMER_PRESET_CONFIG: + ret = pca9481_preset_config(pca9481); + if (ret < 0) + goto error; + break; + + case TIMER_CHECK_ACTIVE: + ret = pca9481_check_active_state(pca9481); + if (ret < 0) + goto error; + break; + + case TIMER_ADJUST_CCMODE: + ret = pca9481_charge_adjust_ccmode(pca9481); + if (ret < 0) + goto error; + break; + + case TIMER_ENTER_CCMODE: + ret = pca9481_charge_start_ccmode(pca9481); + if (ret < 0) + goto error; + break; + + case TIMER_CHECK_CCMODE: + ret = pca9481_charge_ccmode(pca9481); + if (ret < 0) + goto error; + break; + + case TIMER_ENTER_CVMODE: + /* Enter Pre-CV mode */ + ret = pca9481_charge_start_cvmode(pca9481); + if (ret < 0) + goto error; + break; + + case TIMER_CHECK_CVMODE: + ret = pca9481_charge_cvmode(pca9481); + if (ret < 0) + goto error; + break; + + case TIMER_PDMSG_SEND: + /* Adjust TA current and voltage step */ + if (pca9481->ta_type == TA_TYPE_WIRELESS) { + val = pca9481->ta_vol/WCRX_VOL_STEP; /* RX voltage resolution is 100mV */ + pca9481->ta_vol = val*WCRX_VOL_STEP; + + /* Set RX voltage */ + ret = pca9481_send_rx_voltage(pca9481, WCRX_REQUEST_VOLTAGE); + } else if (pca9481->ta_type == TA_TYPE_USBPD_20) { + /* Send PD Message */ + ret = pca9481_send_pd_message(pca9481, PD_MSG_REQUEST_FIXED_PDO); + } else { + val = pca9481->ta_vol/PD_MSG_TA_VOL_STEP; /* PPS voltage resolution is 20mV */ + pca9481->ta_vol = val*PD_MSG_TA_VOL_STEP; + val = pca9481->ta_cur/PD_MSG_TA_CUR_STEP; /* PPS current resolution is 50mA */ + pca9481->ta_cur = val*PD_MSG_TA_CUR_STEP; + if (pca9481->ta_cur < TA_MIN_CUR) /* PPS minimum current is 1000mA */ + pca9481->ta_cur = TA_MIN_CUR; + + /* Send PD Message */ + ret = pca9481_send_pd_message(pca9481, PD_MSG_REQUEST_APDO); + } + if (ret < 0) + goto error; + + /* Go to the next state */ + mutex_lock(&pca9481->lock); + switch (pca9481->charging_state) { + case DC_STATE_PRESET_DC: + pca9481->timer_id = TIMER_PRESET_CONFIG; + break; + case DC_STATE_ADJUST_CC: + pca9481->timer_id = TIMER_ADJUST_CCMODE; + break; + case DC_STATE_START_CC: + pca9481->timer_id = TIMER_ENTER_CCMODE; + break; + case DC_STATE_CC_MODE: + pca9481->timer_id = TIMER_CHECK_CCMODE; + break; + case DC_STATE_START_CV: + pca9481->timer_id = TIMER_ENTER_CVMODE; + break; + case DC_STATE_CV_MODE: + pca9481->timer_id = TIMER_CHECK_CVMODE; + break; + case DC_STATE_ADJUST_TAVOL: + pca9481->timer_id = TIMER_ADJUST_TAVOL; + break; + case DC_STATE_ADJUST_TACUR: + pca9481->timer_id = TIMER_ADJUST_TACUR; + break; + case DC_STATE_BYPASS_MODE: + pca9481->timer_id = TIMER_CHECK_BYPASSMODE; + break; + case DC_STATE_DCMODE_CHANGE: + pca9481->timer_id = TIMER_DCMODE_CHANGE; + break; + default: + ret = -EINVAL; + break; + } + pca9481->timer_period = PDMSG_WAIT_T; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + break; + + case TIMER_ADJUST_TAVOL: + ret = pca9481_adjust_ta_voltage(pca9481); + if (ret < 0) + goto error; + break; + + case TIMER_ADJUST_TACUR: + ret = pca9481_adjust_ta_current(pca9481); + if (ret < 0) + goto error; + break; + + case TIMER_CHECK_BYPASSMODE: + ret = pca9481_charge_bypass_mode(pca9481); + if (ret < 0) + goto error; + break; + + case TIMER_DCMODE_CHANGE: + ret = pca9481_charge_dcmode_change(pca9481); + if (ret < 0) + goto error; + break; + + case TIMER_START_REVERSE: + ret = pca9481_charge_start_reverse(pca9481); + if (ret < 0) + goto error; + break; + + case TIMER_CHECK_REVERSE_ACTIVE: + ret = pca9481_check_reverse_active_state(pca9481); + if (ret < 0) + goto error; + break; + + case TIMER_CHECK_REVERSE_MODE: + ret = pca9481_charge_reverse_mode(pca9481); + if (ret < 0) + goto error; + break; + + case TIMER_CHECK_FPDOCVMODE: + ret = pca9481_charge_fpdo_cvmode(pca9481); + if (ret < 0) + goto error; + break; + + case TIMER_ID_NONE: + ret = pca9481_stop_charging(pca9481); + if (ret < 0) + goto error; + break; + + default: + break; + } + + /* Check the charging state again */ + if (pca9481->charging_state == DC_STATE_NO_CHARGING) { + /* Cancel work queue again */ + cancel_delayed_work(&pca9481->timer_work); + cancel_delayed_work(&pca9481->pps_work); + } + return; + +error: +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481->chg_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + pca9481->health_status = POWER_SUPPLY_EXT_HEALTH_DC_ERR; +#endif + pca9481_stop_charging(pca9481); +} + +/* delayed work function for pps periodic timer */ +static void pca9481_pps_request_work(struct work_struct *work) +{ + struct pca9481_charger *pca9481 = container_of(work, struct pca9481_charger, + pps_work.work); + + int ret = 0; +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + int vin, iin; + + /* this is for wdt */ + vin = pca9481_read_adc(pca9481, ADCCH_VIN); + iin = pca9481_read_adc(pca9481, ADCCH_VIN_CURRENT); + pr_info("%s: pps_work_start (vin:%dmV, iin:%dmA)\n", + __func__, vin/PCA9481_SEC_DENOM_U_M, iin/PCA9481_SEC_DENOM_U_M); +#else + pr_info("%s: pps_work_start\n", __func__); +#endif + +#if defined(CONFIG_SEND_PDMSG_IN_PPS_REQUEST_WORK) + /* Send PD message */ + ret = pca9481_send_pd_message(pca9481, PD_MSG_REQUEST_APDO); +#endif + pr_info("%s: End, ret=%d\n", __func__, ret); +} + +static int pca9481_hw_init(struct pca9481_charger *pca9481) +{ + unsigned int val; + int ret; + u8 mask[REG_BUFFER_MAX]; + + pr_info("%s: =========START=========\n", __func__); + + /* Read Device info register */ + ret = pca9481_read_reg(pca9481, PCA9481_REG_DEVICE_ID, &val); + if ((ret < 0) || (val != PCA9481_DEVICE_ID)) { + /* Read Device info register again */ + ret = pca9481_read_reg(pca9481, PCA9481_REG_DEVICE_ID, &val); + if ((ret < 0) || (val != PCA9481_DEVICE_ID)) { + dev_err(pca9481->dev, "reading DEVICE_INFO failed, val=0x%x\n", val); + ret = -EINVAL; + return ret; + } + } + dev_info(pca9481->dev, "%s: reading DEVICE_INFO, val=0x%x\n", __func__, val); + + /* + * Program the platform specific configuration values to the device + * first. + */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481->chg_status = POWER_SUPPLY_STATUS_DISCHARGING; + pca9481->health_status = POWER_SUPPLY_HEALTH_GOOD; +#endif + +#ifdef CONFIG_SEC_FACTORY + /* Set OV_TRACK_EN to disable */ + /* Factory mode just use VIN_FIXED_OVP_EN(10.5V) and don't use OV_TRACKING_EN */ + val = 0 << MASK2SHIFT(PCA9481_BIT_OV_TRACKING_EN); + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_SC_CNTL_2, + PCA9481_BIT_OV_TRACKING_EN, val); +#else + /* Set OV_TRACK_DELTA to 800mV */ + val = OV_TRACK_DELTA_800mV << MASK2SHIFT(PCA9481_BIT_OV_TRACK_DELTA); + ret = pca9481_update_reg(pca9481, PCA9481_REG_SC_CNTL_2, + PCA9481_BIT_OV_TRACK_DELTA, val); +#endif + if (ret < 0) + return ret; + + /* Set UV_TRACK_DELTA to 0mV and VIN_UV_TRACKING_DEGLITCH to 1ms */ + val = (UV_TRACK_DELTA_0mV << MASK2SHIFT(PCA9481_BIT_UV_TRACK_DELTA) | + UV_TRACK_DEGLITCH_1ms << MASK2SHIFT(PCA9481_BIT_VIN_UV_TRACKING_DEGLITCH)); + ret = pca9481_update_reg(pca9481, PCA9481_REG_SC_CNTL_1, + PCA9481_BIT_UV_TRACK_DELTA | PCA9481_BIT_VIN_UV_TRACKING_DEGLITCH, + val); + if (ret < 0) + return ret; + + /* Set Switching Frequency - kHz unit */ + val = PCA9481_FSW_CFG(pca9481->pdata->fsw_cfg); + ret = pca9481_write_reg(pca9481, PCA9481_REG_SC_CNTL_0, val); + if (ret < 0) + return ret; + + /* Set VIN_CURRENT_OCP_21_11 to 1000mA */ + val = PCA9481_BIT_VIN_CURRENT_OCP_21_11; + ret = regmap_update_bits(pca9481->regmap, PCA9481_REG_CHARGING_CNTL_0, + PCA9481_BIT_VIN_CURRENT_OCP_21_11, val); + if (ret < 0) + return ret; + + /* Set Reverse Current Detection */ + val = PCA9481_BIT_RCP_EN; + ret = pca9481_update_reg(pca9481, PCA9481_REG_RCP_CNTL, + PCA9481_BIT_RCP_EN, val); + if (ret < 0) + return ret; + + /* Set EN pin polarity - Disable PCA9481 */ + val = PCA9481_EN_ACTIVE_H; + ret = pca9481_update_reg(pca9481, PCA9481_REG_DEVICE_CNTL_2, + PCA9481_BIT_EN_CFG, val); + if (ret < 0) + return ret; + + /* Set standby mode*/ + val = PCA9481_STANDBY_FORCE; + ret = pca9481_update_reg(pca9481, PCA9481_REG_SC_CNTL_3, + PCA9481_BIT_STANDBY_EN | PCA9481_BIT_SC_OPERATION_MODE, + val | PCA9481_SC_OP_21); + if (ret < 0) + return ret; + + /* Die Temperature regulation 120'C */ + val = 0x3 << MASK2SHIFT(PCA9481_BIT_THERMAL_REGULATION_CFG); + ret = pca9481_update_reg(pca9481, PCA9481_REG_DEVICE_CNTL_1, + PCA9481_BIT_THERMAL_REGULATION_CFG | PCA9481_BIT_THERMAL_REGULATION_EN, + val | PCA9481_BIT_THERMAL_REGULATION_EN); + if (ret < 0) + return ret; + + /* Set external sense resistor value */ + val = pca9481->pdata->snsres << MASK2SHIFT(PCA9481_BIT_IBAT_SENSE_R_SEL); + ret = pca9481_update_reg(pca9481, PCA9481_REG_CHARGING_CNTL_4, + PCA9481_BIT_IBAT_SENSE_R_SEL, val); + if (ret < 0) + return ret; + + /* Set external sense resistor location */ + val = pca9481->pdata->snsres_cfg << MASK2SHIFT(PCA9481_BIT_IBAT_SENSE_R_CFG); + ret = pca9481_update_reg(pca9481, PCA9481_REG_DEVICE_CNTL_2, + PCA9481_BIT_IBAT_SENSE_R_CFG, val); + if (ret < 0) + return ret; + + /* Disable battery charge current regulation loop */ + /* Disable current measurement through CSP and CSN */ + val = 0; + ret = pca9481_update_reg(pca9481, PCA9481_REG_CHARGING_CNTL_0, + PCA9481_BIT_CSP_CSN_MEASURE_EN | PCA9481_BIT_I_VBAT_LOOP_EN, + val); + if (ret < 0) + return ret; + + /* Set the ADC channel */ + val = (PCA9481_BIT_ADC_READ_VIN_CURRENT_EN | /* IIN ADC */ + PCA9481_BIT_ADC_READ_DIE_TEMP_EN | /* DIE_TEMP ADC */ + PCA9481_BIT_ADC_READ_NTC_EN | /* NTC ADC */ + PCA9481_BIT_ADC_READ_VOUT_EN | /* VOUT ADC */ + PCA9481_BIT_ADC_READ_OVP_OUT_EN | /* OVP_OUT ADC */ + PCA9481_BIT_ADC_READ_VIN_EN); /* VIN ADC */ + + ret = pca9481_write_reg(pca9481, PCA9481_REG_ADC_EN_CNTL_0, val); + if (ret < 0) + return ret; + + /* Enable ADC */ + val = PCA9481_BIT_ADC_EN | ((unsigned int)(ADC_AVG_16sample << MASK2SHIFT(PCA9481_BIT_ADC_AVERAGE_TIMES))); + ret = pca9481_write_reg(pca9481, PCA9481_REG_ADC_CNTL, val); + if (ret < 0) + return ret; + + /* + * Configure the Mask Register for interrupts: disable all interrupts by default. + */ + mask[REG_DEVICE_0] = 0xFF; + mask[REG_DEVICE_1] = 0xFF; + mask[REG_DEVICE_2] = 0xFF; + mask[REG_DEVICE_3] = 0xFF; + mask[REG_CHARGING] = 0xFF; + mask[REG_SC_0] = 0xFF; + mask[REG_SC_1] = 0xFF; + ret = regmap_bulk_write(pca9481->regmap, PCA9481_REG_INT_DEVICE_0_MASK | PCA9481_REG_BIT_AI, + mask, REG_BUFFER_MAX); + if (ret < 0) + return ret; + + /* input current - uA*/ + ret = pca9481_set_input_current(pca9481, pca9481->pdata->iin_cfg); + if (ret < 0) + return ret; + + /* charging current */ + ret = pca9481_set_charging_current(pca9481, pca9481->pdata->ichg_cfg); + if (ret < 0) + return ret; + + /* float voltage */ + ret = pca9481_set_vfloat(pca9481, pca9481->pdata->vfloat); + if (ret < 0) + return ret; + + /* Save initial charging parameters */ + pca9481->iin_cfg = pca9481->pdata->iin_cfg; + pca9481->ichg_cfg = pca9481->pdata->ichg_cfg; + pca9481->vfloat = pca9481->pdata->vfloat; + pca9481->max_vfloat = pca9481->pdata->vfloat; + pca9481->iin_topoff = pca9481->pdata->iin_topoff; +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481->fpdo_dc_iin_topoff = pca9481->pdata->fpdo_dc_iin_topoff; + pca9481->fpdo_dc_vnow_topoff = pca9481->pdata->fpdo_dc_vnow_topoff; +#endif + + /* Clear new iin and new vfloat */ + pca9481->new_iin = 0; + pca9481->new_vfloat = 0; + + /* Initial TA control method is Current Limit mode */ + pca9481->ta_ctrl = TA_CTRL_CL_MODE; + + /* Clear switching frequency change sequence */ + pca9481->req_sw_freq = REQ_SW_FREQ_0; + + /* Set vfloat decrement flag to false by default */ + pca9481->dec_vfloat = false; + + /* Clear charging done counter */ + pca9481->done_cnt = 0; + + /* Clear CV transition sequence */ + pca9481->cv_trans = CV_TRANS_0; + + return ret; +} + + +static irqreturn_t pca9481_interrupt_handler(int irq, void *data) +{ + struct pca9481_charger *pca9481 = data; + u8 int_reg[REG_BUFFER_MAX], sts_reg[REG_BUFFER_MAX], mask_reg[REG_BUFFER_MAX]; + u8 masked_int[REG_BUFFER_MAX]; /* masked int */ + bool handled = false; + int ret, i; + + /* Read interrupt registers */ + ret = pca9481_bulk_read_reg(pca9481, PCA9481_REG_INT_DEVICE_0 | PCA9481_REG_BIT_AI, int_reg, REG_BUFFER_MAX); + if (ret < 0) { + dev_err(pca9481->dev, "reading Interrupt registers failed\n"); + handled = false; + goto error; + } + pr_info("%s: INT reg[0x01]=0x%x,[0x02]=0x%x,[0x03]=0x%x,[0x04]=0x%x,[0x05]=0x%x,[0x06]=0x%x,[0x07]=0x%x\n", + __func__, int_reg[0], int_reg[1], int_reg[2], int_reg[3], int_reg[4], int_reg[5], int_reg[6]); + + /* Read mask registers */ + ret = pca9481_bulk_read_reg(pca9481, PCA9481_REG_INT_DEVICE_0_MASK | PCA9481_REG_BIT_AI, mask_reg, REG_BUFFER_MAX); + if (ret < 0) { + dev_err(pca9481->dev, "reading Mask registers failed\n"); + handled = false; + goto error; + } + pr_info("%s: MASK reg[0x08]=0x%x,[0x09]=0x%x,[0x0A]=0x%x,[0x0B]=0x%x,[0x0C]=0x%x,[0x0D]=0x%x,[0x0E]=0x%x\n", + __func__, mask_reg[0], mask_reg[1], mask_reg[2], mask_reg[3], mask_reg[4], mask_reg[5], mask_reg[6]); + + /* Read status registers */ + ret = pca9481_bulk_read_reg(pca9481, PCA9481_REG_DEVICE_0_STS | PCA9481_REG_BIT_AI, sts_reg, REG_BUFFER_MAX); + if (ret < 0) { + dev_err(pca9481->dev, "reading Status registers failed\n"); + handled = false; + goto error; + } + pr_info("%s: STS reg[0x0F]=0x%x,[0x10]=0x%x,[0x11]=0x%x,[0x12]=0x%x,[0x13]=0x%x,[0x14]=0x%x,[0x15]=0x%x\n", + __func__, sts_reg[0], sts_reg[1], sts_reg[2], sts_reg[3], sts_reg[4], sts_reg[5], sts_reg[6]); + + /* Check the masked interrupt */ + for (i = 0; i < REG_BUFFER_MAX; i++) + masked_int[i] = int_reg[i] & !mask_reg[i]; + + pr_info("%s: Masked INT reg[0x01]=0x%x,[0x02]=0x%x,[0x03]=0x%x,[0x04]=0x%x,[0x05]=0x%x,[0x06]=0x%x,[0x07]=0x%x\n", + __func__, masked_int[0], masked_int[1], masked_int[2], masked_int[3], masked_int[4], masked_int[5], masked_int[6]); + + handled = true; + + /* Should implement code by a customer if pca9481 needs additional functions or actions */ + +error: + return handled ? IRQ_HANDLED : IRQ_NONE; +} + +static int pca9481_irq_init(struct pca9481_charger *pca9481, + struct i2c_client *client) +{ + const struct pca9481_platform_data *pdata = pca9481->pdata; + int ret, msk[REG_BUFFER_MAX], irq; + + pr_info("%s: =========START=========\n", __func__); + + irq = gpio_to_irq(pdata->irq_gpio); + + ret = gpio_request_one(pdata->irq_gpio, GPIOF_IN, client->name); + if (ret < 0) + goto fail; + + ret = request_threaded_irq(irq, NULL, pca9481_interrupt_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + client->name, pca9481); + if (ret < 0) + goto fail_gpio; + + /* + * Configure the Mask Register for interrupts: disable all interrupts by default. + */ + msk[REG_DEVICE_0] = 0xFF; + msk[REG_DEVICE_1] = 0xFF; + msk[REG_DEVICE_2] = 0xFF; + msk[REG_DEVICE_3] = 0xFF; + msk[REG_CHARGING] = 0xFF; + msk[REG_SC_0] = 0xFF; + msk[REG_SC_1] = 0xFF; + + ret = regmap_bulk_write(pca9481->regmap, PCA9481_REG_INT_DEVICE_0_MASK | PCA9481_REG_BIT_AI, + msk, REG_BUFFER_MAX); + if (ret < 0) + goto fail_write; + + client->irq = irq; + return 0; + +fail_write: + free_irq(irq, pca9481); +fail_gpio: + gpio_free(pdata->irq_gpio); +fail: + client->irq = 0; + return ret; +} + + +/* + * Returns the input current limit programmed + * into the charger in uA. + */ +static int get_input_current_limit(struct pca9481_charger *pca9481) +{ + int ret, intval; + unsigned int val; + + ret = pca9481_read_reg(pca9481, PCA9481_REG_CHARGING_CNTL_1, &val); + if (ret < 0) + return ret; + + intval = val * PCA9481_IIN_REG_STEP + PCA9481_IIN_REG_MIN; + + if (intval > PCA9481_IIN_REG_MAX) + intval = PCA9481_IIN_REG_MAX; + + return intval; +} + +/* + * Returns the constant charge current programmed + * into the charger in uA. + */ +static int get_const_charge_current(struct pca9481_charger *pca9481) +{ + int ret, intval; + unsigned int val; + + ret = pca9481_read_reg(pca9481, PCA9481_REG_CHARGING_CNTL_3, &val); + if (ret < 0) + return ret; + + intval = val * PCA9481_IBAT_REG_STEP + PCA9481_IBAT_REG_MIN; + + if (intval > PCA9481_IBAT_REG_MAX) + intval = PCA9481_IBAT_REG_MAX; + + return intval; +} + +/* + * Returns the constant charge voltage programmed + * into the charger in uV. + */ +static int get_const_charge_voltage(struct pca9481_charger *pca9481) +{ + int ret, intval; + unsigned int val; + + ret = pca9481_read_reg(pca9481, PCA9481_REG_CHARGING_CNTL_2, &val); + if (ret < 0) + return ret; + + intval = val * PCA9481_VBAT_REG_STEP + PCA9481_VBAT_REG_MIN; + + if (intval > PCA9481_VBAT_REG_MAX) + intval = PCA9481_VBAT_REG_MAX; + + return intval; +} + +/* + * Returns the enable or disable value. + * into 1 or 0. + */ +static int get_charging_enabled(struct pca9481_charger *pca9481) +{ + int ret, intval; + unsigned int val; + + ret = pca9481_read_reg(pca9481, PCA9481_REG_SC_CNTL_3, &val); + if (ret < 0) + return ret; + + intval = (val & PCA9481_BIT_STANDBY_EN) ? 0 : 1; + + return intval; +} + +static int pca9481_chg_set_adc_force_mode(struct pca9481_charger *pca9481, u8 enable) +{ + unsigned int temp = 0; + int ret = 0; + + if (enable) { + /* Disable low power mode */ + temp = PCA9481_BIT_LOW_POWER_MODE_DISABLE; + ret = pca9481_update_reg(pca9481, PCA9481_REG_DEVICE_CNTL_1, + PCA9481_BIT_LOW_POWER_MODE_DISABLE, temp); + if (ret < 0) + return ret; + + /* Set Forced Normal Mode to ADC mode */ + temp = FORCE_NORMAL_MODE << MASK2SHIFT(PCA9481_BIT_ADC_MODE_CFG); + ret = pca9481_update_reg(pca9481, PCA9481_REG_ADC_CNTL, + PCA9481_BIT_ADC_MODE_CFG, temp); + if (ret < 0) + return ret; + + /* Wait 2ms to update ADC */ + usleep_range(2000, 3000); + } else { + /* Set Auto Mode to ADC mode */ + temp = AUTO_MODE << MASK2SHIFT(PCA9481_BIT_ADC_MODE_CFG); + ret = pca9481_update_reg(pca9481, PCA9481_REG_ADC_CNTL, + PCA9481_BIT_ADC_MODE_CFG, temp); + if (ret < 0) + return ret; + + /* Enable low power mode */ + temp = 0; + ret = pca9481_update_reg(pca9481, PCA9481_REG_DEVICE_CNTL_1, + PCA9481_BIT_LOW_POWER_MODE_DISABLE, temp); + if (ret < 0) + return ret; + } + + ret = pca9481_read_reg(pca9481, PCA9481_REG_ADC_CNTL, &temp); + pr_info("%s: ADC_CTRL : 0x%02x\n", __func__, temp); + + return ret; +} + +static int pca9481_chg_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + struct pca9481_charger *pca9481 = power_supply_get_drvdata(psy); +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) prop; + unsigned int temp = 0; +#endif + int ret = 0; + + pr_info("%s: =========START=========\n", __func__); + pr_info("%s: prop=%d, val=%d\n", __func__, prop, val->intval); + + switch ((int)prop) { + /* Todo - Insert code */ + /* It needs modification by a customer */ + + case POWER_SUPPLY_PROP_ONLINE: + /* Check whether pca9481 is in reverse mode */ + if (pca9481->rev_mode == POWER_SUPPLY_DC_REVERSE_STOP) { + if (val->intval == 0) { + pca9481->mains_online = false; + /* Check TA detachment and clear new_iin */ + pca9481->new_iin = 0; + /* Cancel delayed work */ + cancel_delayed_work(&pca9481->timer_work); + cancel_delayed_work(&pca9481->pps_work); + /* Stop Direct Charging */ + mutex_lock(&pca9481->lock); + pca9481->timer_id = TIMER_ID_NONE; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + + pca9481->chg_status = POWER_SUPPLY_STATUS_DISCHARGING; + pca9481->health_status = POWER_SUPPLY_HEALTH_GOOD; + + } else { + /* Start Direct charging */ + pca9481->mains_online = true; + ret = 0; + } + } else { + /* PCA9481 is in reverse bypass mode or reverse 1:2 switching mode */ + /* Ignore online property setting */ + pr_info("%s: reverse mode(%d), ignore ONLINE property setting\n", + __func__, pca9481->rev_mode); + } + break; + case POWER_SUPPLY_PROP_PRESENT: + /* Set the USBPD-TA is plugged in or out */ + pca9481->mains_online = val->intval; + break; + + case POWER_SUPPLY_PROP_TYPE: + /* Set power supply type */ + if (val->intval == POWER_SUPPLY_TYPE_WIRELESS) { + /* The current power supply type is wireless charger */ + pca9481->ta_type = TA_TYPE_WIRELESS; + pr_info("%s: The current power supply type is WC, ta_type=%d\n", __func__, pca9481->ta_type); + } else { + /* Default TA type is USBPD TA */ + pca9481->ta_type = TA_TYPE_USBPD; + pr_info("%s: The current power supply type is USBPD, ta_type=%d\n", __func__, pca9481->ta_type); + } + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481->float_voltage = val->intval; + temp = pca9481->float_voltage * PCA9481_SEC_DENOM_U_M; + if (temp != pca9481->new_vfloat) { + /* request new float voltage */ + pca9481->new_vfloat = temp; + /* Check the charging state */ + if ((pca9481->charging_state == DC_STATE_NO_CHARGING) || + (pca9481->charging_state == DC_STATE_CHECK_VBAT)) { + /* Apply new vfloat when the direct charging is started */ + pca9481->vfloat = pca9481->new_vfloat; + } else { + /* Check whether the previous request is done or not */ + if (pca9481->req_new_vfloat == true) { + /* The previous request is not done yet */ + pr_err("%s: There is the previous request for New vfloat\n", __func__); + ret = -EBUSY; + } else { + /* Set request flag */ + mutex_lock(&pca9481->lock); + pca9481->req_new_vfloat = true; + mutex_unlock(&pca9481->lock); + + /* Check the charging state */ + if ((pca9481->charging_state == DC_STATE_CC_MODE) || + (pca9481->charging_state == DC_STATE_CV_MODE) || + (pca9481->charging_state == DC_STATE_BYPASS_MODE) || + (pca9481->charging_state == DC_STATE_CHARGING_DONE) || + (pca9481->charging_state == DC_STATE_FPDO_CV_MODE)) { + /* cancel delayed_work */ + cancel_delayed_work(&pca9481->timer_work); + /* do delayed work at once */ + mutex_lock(&pca9481->lock); + pca9481->timer_period = 0; // ms unit + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } else { + /* Wait for next valid state - cc, cv, or bypass state */ + pr_info("%s: Not support new vfloat yet in charging state=%d\n", + __func__, pca9481->charging_state); + } + } + } + } +#else + if (val->intval != pca9481->new_vfloat) { + /* request new float voltage */ + pca9481->new_vfloat = val->intval; + /* Check the charging state */ + if ((pca9481->charging_state == DC_STATE_NO_CHARGING) || + (pca9481->charging_state == DC_STATE_CHECK_VBAT)) { + /* Apply new vfloat when the direct charging is started */ + pca9481->vfloat = pca9481->new_vfloat; + } else { + /* Check whether the previous request is done or not */ + if (pca9481->req_new_vfloat == true) { + /* The previous request is not done yet */ + pr_err("%s: There is the previous request for New vfloat\n", __func__); + ret = -EBUSY; + } else { + /* Set request flag */ + mutex_lock(&pca9481->lock); + pca9481->req_new_vfloat = true; + mutex_unlock(&pca9481->lock); + + /* Check the charging state */ + if ((pca9481->charging_state == DC_STATE_CC_MODE) || + (pca9481->charging_state == DC_STATE_CV_MODE) || + (pca9481->charging_state == DC_STATE_BYPASS_MODE) || + (pca9481->charging_state == DC_STATE_CHARGING_DONE) || + (pca9481->charging_state == DC_STATE_FPDO_CV_MODE)) { + /* cancel delayed_work */ + cancel_delayed_work(&pca9481->timer_work); + /* do delayed work at once */ + mutex_lock(&pca9481->lock); + pca9481->timer_period = 0; // ms unit + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } else { + /* Wait for next valid state - cc, cv, or bypass state */ + pr_info("%s: Not support new vfloat yet in charging state=%d\n", + __func__, pca9481->charging_state); + } + } + } + } +#endif + break; + + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481->input_current = val->intval; + temp = pca9481->input_current * PCA9481_SEC_DENOM_U_M; + if (pca9481->ta_type == TA_TYPE_USBPD_20 && temp < pca9481->pdata->fpdo_dc_iin_lowest_limit) { + pr_info("%s: PSP_ICL, IIN LOWEST LIMIT! IIN %d -> %d\n", __func__, + temp, pca9481->pdata->fpdo_dc_iin_lowest_limit); + temp = pca9481->pdata->fpdo_dc_iin_lowest_limit; + } + if (temp != pca9481->new_iin) { + /* Compare with topoff current */ + if (temp < pca9481->iin_topoff) { + /* This new iin is abnormal input current */ + pr_err("%s: This new iin(%duA) is abnormal value\n", __func__, val->intval); + ret = -EINVAL; + break; + } + /* request new input current */ + pca9481->new_iin = temp; + /* Check the charging state */ + if ((pca9481->charging_state == DC_STATE_NO_CHARGING) || + (pca9481->charging_state == DC_STATE_CHECK_VBAT)) { + /* Apply new iin when the direct charging is started */ + pca9481->iin_cfg = pca9481->new_iin; + } else { + /* Check whether the previous request is done or not */ + if (pca9481->req_new_iin == true) { + /* The previous request is not done yet */ + pr_err("%s: There is the previous request for New iin\n", __func__); + ret = -EBUSY; + } else { + /* Set request flag */ + mutex_lock(&pca9481->lock); + pca9481->req_new_iin = true; + mutex_unlock(&pca9481->lock); + /* Check the charging state */ + if ((pca9481->charging_state == DC_STATE_CC_MODE) || + (pca9481->charging_state == DC_STATE_CV_MODE) || + (pca9481->charging_state == DC_STATE_BYPASS_MODE) || + (pca9481->charging_state == DC_STATE_FPDO_CV_MODE) || + (pca9481->charging_state == DC_STATE_CHARGING_DONE)) { + /* cancel delayed_work */ + cancel_delayed_work(&pca9481->timer_work); + /* do delayed work at once */ + + mutex_lock(&pca9481->lock); + pca9481->timer_period = 0; // ms unit + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } else { + /* Wait for next valid state - cc, cv, or bypass state */ + pr_info("%s: Not support new iin yet in charging state=%d\n", + __func__, pca9481->charging_state); + } + } + } + } else { + /* Compare with topoff current */ + if (temp < pca9481->iin_topoff) { + /* This new iin is abnormal input current */ + pr_err("%s: This new iin(%duA) is abnormal value\n", __func__, val->intval); + ret = -EINVAL; + } else { + /* new iin is same as previous new_iin, but iin_cfg is different from it */ + /* Check the charging state */ + if ((pca9481->charging_state == DC_STATE_NO_CHARGING) || + (pca9481->charging_state == DC_STATE_CHECK_VBAT)) { + /* Apply new iin when the direct charging is started */ + pca9481->iin_cfg = pca9481->new_iin; + pr_info("%s: charging state=%d, new iin(%uA) and iin_cfg(%uA)\n", + __func__, pca9481->charging_state, + pca9481->new_iin, pca9481->iin_cfg); + } + } + } +#else + if (val->intval != pca9481->new_iin) { + /* request new input current */ + pca9481->new_iin = val->intval; + /* Check the charging state */ + if ((pca9481->charging_state == DC_STATE_NO_CHARGING) || + (pca9481->charging_state == DC_STATE_CHECK_VBAT)) { + /* Apply new iin when the direct charging is started */ + pca9481->iin_cfg = pca9481->new_iin; + } else { + /* Check TA type */ + if (pca9481->ta_type == TA_TYPE_USBPD_20) { + /* Cannot support change input current during DC */ + /* Because FPDO cannot control input current by PD messsage */ + pr_err("%s: Error - FPDO cannot control input current\n", __func__); + ret = -EINVAL; + } else { + /* Check whether the previous request is done or not */ + if (pca9481->req_new_iin == true) { + /* The previous request is not done yet */ + pr_err("%s: There is the previous request for New iin\n", __func__); + ret = -EBUSY; + } else { + /* Set request flag */ + mutex_lock(&pca9481->lock); + pca9481->req_new_iin = true; + mutex_unlock(&pca9481->lock); + /* Check the charging state */ + if ((pca9481->charging_state == DC_STATE_CC_MODE) || + (pca9481->charging_state == DC_STATE_CV_MODE) || + (pca9481->charging_state == DC_STATE_BYPASS_MODE) || + (pca9481->charging_state == DC_STATE_CHARGING_DONE) || + (pca9481->charging_state == DC_STATE_FPDO_CV_MODE)) { + /* cancel delayed_work */ + cancel_delayed_work(&pca9481->timer_work); + /* do delayed work at once */ + mutex_lock(&pca9481->lock); + pca9481->timer_period = 0; // ms unit + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } else { + /* Wait for next valid state - cc, cv, or bypass state */ + pr_info("%s: Not support new iin yet in charging state=%d\n", + __func__, pca9481->charging_state); + } + } + } + } + } else { + /* Compare with topoff current */ + if (val->intval < pca9481->iin_topoff) { + /* This new iin is abnormal input current */ + pr_err("%s: This new iin(%duA) is abnormal value\n", __func__, val->intval); + ret = -EINVAL; + } else { + /* new iin is same as previous new_iin, but iin_cfg is different from it */ + /* Check the charging state */ + if ((pca9481->charging_state == DC_STATE_NO_CHARGING) || + (pca9481->charging_state == DC_STATE_CHECK_VBAT)) { + /* Apply new iin when the direct charging is started */ + pca9481->iin_cfg = pca9481->new_iin; + pr_info("%s: charging state=%d, new iin(%uA) and iin_cfg(%uA)\n", + __func__, pca9481->charging_state, + pca9481->new_iin, pca9481->iin_cfg); + } + } + } +#endif + break; + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + pca9481->pdata->vfloat = val->intval * PCA9481_SEC_DENOM_U_M; + pr_info("%s: v_float(%duV)\n", __func__, pca9481->pdata->vfloat); + /* Save maximum vfloat to max_vfloat */ + pca9481->max_vfloat = pca9481->pdata->vfloat; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + pca9481->charging_current = val->intval; + break; + case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: + switch (ext_psp) { +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + case POWER_SUPPLY_EXT_PROP_DIRECT_WDT_CONTROL: + if (val->intval) { + pca9481->wdt_kick = true; + } else { + pca9481->wdt_kick = false; + cancel_delayed_work(&pca9481->wdt_control_work); + } + pr_info("%s: wdt kick (%d)\n", __func__, pca9481->wdt_kick); + break; +#endif + case POWER_SUPPLY_EXT_PROP_DIRECT_CURRENT_MAX: + pca9481->input_current = val->intval; + temp = pca9481->input_current * PCA9481_SEC_DENOM_U_M; + if (pca9481->ta_type == TA_TYPE_USBPD_20 && temp < pca9481->pdata->fpdo_dc_iin_lowest_limit) { + pr_info("%s: PSP_DCM, IIN LOWEST LIMIT! IIN %d -> %d\n", __func__, + temp, pca9481->pdata->fpdo_dc_iin_lowest_limit); + temp = pca9481->pdata->fpdo_dc_iin_lowest_limit; + } + if (temp != pca9481->new_iin) { + /* request new input current */ + pca9481->new_iin = temp; + /* Check the charging state */ + if ((pca9481->charging_state == DC_STATE_NO_CHARGING) || + (pca9481->charging_state == DC_STATE_CHECK_VBAT)) { + /* Apply new iin when the direct charging is started */ + pca9481->iin_cfg = pca9481->new_iin; + } else { + /* Check whether the previous request is done or not */ + if (pca9481->req_new_iin == true) { + /* The previous request is not done yet */ + pr_err("%s: There is the previous request for New iin\n", __func__); + ret = -EBUSY; + } else { + /* Set request flag */ + mutex_lock(&pca9481->lock); + pca9481->req_new_iin = true; + mutex_unlock(&pca9481->lock); + /* Check the charging state */ + if ((pca9481->charging_state == DC_STATE_CC_MODE) || + (pca9481->charging_state == DC_STATE_CV_MODE) || + (pca9481->charging_state == DC_STATE_BYPASS_MODE) || + (pca9481->charging_state == DC_STATE_CHARGING_DONE)) { + /* cancel delayed_work */ + cancel_delayed_work(&pca9481->timer_work); + /* do delayed work at once */ + mutex_lock(&pca9481->lock); + pca9481->timer_period = 0; // ms unit + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } else { + /* Wait for next valid state - cc or cv state */ + pr_info("%s: Not support new iin yet in charging state=%d\n", + __func__, pca9481->charging_state); + } + } + } + pr_info("## %s: input current(new_iin: %duA)\n", __func__, pca9481->new_iin); + } + break; + case POWER_SUPPLY_EXT_PROP_DIRECT_ADC_CTRL: + pca9481_chg_set_adc_force_mode(pca9481, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED: + if (val->intval == 0) { + /* Set req_enable flag to false */ + pca9481->req_enable = false; + /* Stop direct charging */ + ret = pca9481_stop_charging(pca9481); +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481->chg_status = POWER_SUPPLY_STATUS_DISCHARGING; + pca9481->health_status = POWER_SUPPLY_HEALTH_GOOD; +#endif + if (ret < 0) + goto error; + } else { +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + if (pca9481->charging_state != DC_STATE_NO_CHARGING) { + pr_info("## %s: duplicate charging enabled(%d)\n", __func__, val->intval); + goto error; + } + if (!pca9481->mains_online) { + pr_info("## %s: mains_online is not attached(%d)\n", __func__, val->intval); + goto error; + } +#endif + /* Start Direct Charging */ + /* Set req_enable flag to true */ + pca9481->req_enable = true; + /* Set initial wake up timeout - 10s */ + pm_wakeup_ws_event(pca9481->monitor_wake_lock, INIT_WAKEUP_T, false); + /* Start 1sec timer for battery check */ + mutex_lock(&pca9481->lock); +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_set_charging_state(pca9481, DC_STATE_CHECK_VBAT); +#else + pca9481->charging_state = DC_STATE_CHECK_VBAT; +#endif + pca9481->timer_id = TIMER_VBATMIN_CHECK; + pca9481->timer_period = VBATMIN_CHECK_T; /* The delay time for PD state goes to PE_SNK_STATE */ + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + ret = 0; + } + break; + case POWER_SUPPLY_EXT_PROP_PASS_THROUGH_MODE: + pr_info("[PASS_THROUGH] %s: called\n", __func__); + if (val->intval != pca9481->new_dc_mode) { + /* Request new dc mode */ + pca9481->new_dc_mode = val->intval; + /* Check the charging state */ + if (pca9481->charging_state == DC_STATE_NO_CHARGING) { + /* Not support state */ + pr_info("%s: Not support dc mode in charging state=%d\n", __func__, pca9481->charging_state); + ret = -EINVAL; + } else { + /* Check whether the previous request is done or not */ + if (pca9481->req_new_dc_mode == true) { + /* The previous request is not done yet */ + pr_err("%s: There is the previous request for New DC mode\n", __func__); + ret = -EBUSY; + } else { + /* Set request flag */ + mutex_lock(&pca9481->lock); + pca9481->req_new_dc_mode = true; + mutex_unlock(&pca9481->lock); + /* Check the charging state */ + if ((pca9481->charging_state == DC_STATE_CC_MODE) || + (pca9481->charging_state == DC_STATE_CV_MODE) || + (pca9481->charging_state == DC_STATE_BYPASS_MODE)) { + /* cancel delayed_work */ + cancel_delayed_work(&pca9481->timer_work); + /* do delayed work at once */ + mutex_lock(&pca9481->lock); + pca9481->timer_period = 0; // ms unit + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } else { + /* Wait for next valid state - cc, cv, or bypass state */ + pr_info("%s: Not support new dc mode yet in charging state=%d\n", + __func__, pca9481->charging_state); + } + } + } + } + break; + case POWER_SUPPLY_EXT_PROP_PASS_THROUGH_MODE_TA_VOL: + if ((pca9481->charging_state == DC_STATE_BYPASS_MODE) && + (pca9481->dc_mode != PTM_NONE)) { + pr_info("[PASS_THROUGH_VOL] %s, bypass mode\n", __func__); + /* Set TA voltage for bypass mode */ + pca9481_set_bypass_ta_voltage_by_soc(pca9481, val->intval); + } else { + pr_info("[PASS_THROUGH_VOL] %s, not bypass mode\n", __func__); + } + break; + case POWER_SUPPLY_EXT_PROP_DC_VIN_OVERCURRENT: + /* Set VIN OCP current */ + pca9481->iin_rev = val->intval; + ret = pca9481_set_vin_ocp(pca9481, pca9481->iin_rev); + break; + + case POWER_SUPPLY_EXT_PROP_DC_REVERSE_MODE: + /* Set reverse mode */ + pca9481->rev_mode = val->intval; + /* Set reverse mode */ + if (pca9481->rev_mode == POWER_SUPPLY_DC_REVERSE_STOP) { + /* Set req_enable flag to false */ + pca9481->req_enable = false; + /* Cancel delayed_work */ + cancel_delayed_work(&pca9481->timer_work); + /* Stop reverse mode */ + ret = pca9481_stop_charging(pca9481); + } else { + if (pca9481->charging_state == DC_STATE_NO_CHARGING) { + /* Set req_enable flag to true */ + pca9481->req_enable = true; + /* Start Reverse Mode */ + mutex_lock(&pca9481->lock); + pca9481->charging_state = DC_STATE_REVERSE_MODE; + pca9481->timer_id = TIMER_START_REVERSE; + pca9481->timer_period = 0; + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } else { + /* Charging state does not support reverse mode */ + pr_info("%s: Not support reverse mode in charging state=%d\n", + __func__, pca9481->charging_state); + ret = -EINVAL; + } + } + break; + default: + return -EINVAL; + } + break; +#endif + default: + ret = -EINVAL; + break; + } + +error: + pr_info("%s: End, ret=%d\n", __func__, ret); + return ret; +} + +static int pca9481_chg_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + int ret = 0; + struct pca9481_charger *pca9481 = power_supply_get_drvdata(psy); +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) prop; +#endif + + pr_debug("%s: =========START=========\n", __func__); + pr_debug("%s: prop=%d\n", __func__, prop); + + switch ((int)prop) { + case POWER_SUPPLY_PROP_PRESENT: + /* TA present */ + val->intval = pca9481->mains_online; + break; + + case POWER_SUPPLY_PROP_TYPE: + if (pca9481->ta_type == TA_TYPE_WIRELESS) + val->intval = POWER_SUPPLY_TYPE_WIRELESS; + else + val->intval = POWER_SUPPLY_TYPE_USB_PD; + break; + + case POWER_SUPPLY_PROP_ONLINE: + val->intval = pca9481->mains_online; + break; + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + case POWER_SUPPLY_PROP_STATUS: + val->intval = pca9481->chg_status; + pr_info("%s: CHG STATUS : %d\n", __func__, pca9481->chg_status); + break; + + case POWER_SUPPLY_PROP_HEALTH: + if (pca9481->charging_state >= DC_STATE_CHECK_ACTIVE && + pca9481->charging_state <= DC_STATE_CV_MODE) + ret = pca9481_check_error(pca9481); + + val->intval = pca9481->health_status; + pr_info("%s: HEALTH STATUS : %d, ret = %d\n", + __func__, pca9481->health_status, ret); + ret = 0; + break; +#endif + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = get_const_charge_voltage(pca9481); + if (ret < 0) { + val->intval = 0; + } else { +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + val->intval = pca9481->float_voltage; +#else + val->intval = ret; +#endif + ret = 0; + } + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + /* Maximum vfloat */ + val->intval = pca9481->pdata->vfloat / DENOM_U_M; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = get_const_charge_current(pca9481); + if (ret < 0) { + val->intval = 0; + } else { +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + val->intval = pca9481->charging_current; +#else + val->intval = ret; +#endif + ret = 0; + } + break; + + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = get_input_current_limit(pca9481); + if (ret < 0) { + val->intval = 0; + } else { +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + val->intval = ret / PCA9481_SEC_DENOM_U_M; +#else + val->intval = ret; +#endif + ret = 0; + } + break; + + case POWER_SUPPLY_PROP_TEMP: + /* return NTC voltage - uV unit */ + ret = pca9481_read_adc(pca9481, ADCCH_NTC); + if (ret < 0) { + val->intval = 0; + } else { + val->intval = ret; + ret = 0; + } + break; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + /* return the output current - uA unit */ + /* check charging status */ + if (pca9481->charging_state == DC_STATE_NO_CHARGING) { + /* return invalid */ + val->intval = 0; + ret = 0; + } else { + int iin; + /* get ibat current */ + iin = pca9481_read_adc(pca9481, ADCCH_BAT_CURRENT); + if (ret < 0) { + dev_err(pca9481->dev, "Invalid IBAT ADC\n"); + val->intval = 0; + } else { + val->intval = iin; + ret = 0; + } + } + break; + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: + switch (ext_psp) { + case POWER_SUPPLY_EXT_PROP_MONITOR_WORK: + pca9481_monitor_work(pca9481); + pca9481_test_read(pca9481); + break; + case POWER_SUPPLY_EXT_PROP_MEASURE_INPUT: + switch (val->intval) { + case SEC_BATTERY_IIN_MA: + pca9481_read_adc(pca9481, ADCCH_VIN_CURRENT); + val->intval = pca9481->adc_val[ADCCH_VIN_CURRENT]; + break; + case SEC_BATTERY_IIN_UA: + pca9481_read_adc(pca9481, ADCCH_VIN_CURRENT); + val->intval = pca9481->adc_val[ADCCH_VIN_CURRENT] * PCA9481_SEC_DENOM_U_M; + break; + case SEC_BATTERY_VIN_MA: + pca9481_read_adc(pca9481, ADCCH_VIN); + val->intval = pca9481->adc_val[ADCCH_VIN]; + break; + case SEC_BATTERY_VIN_UA: + pca9481_read_adc(pca9481, ADCCH_VIN); + val->intval = pca9481->adc_val[ADCCH_VIN] * PCA9481_SEC_DENOM_U_M; + break; + default: + val->intval = 0; + break; + } + break; + case POWER_SUPPLY_EXT_PROP_MEASURE_SYS: + /* get_system_current function isn't supported. Cannot get accurate value of Isys */ + val->intval = 0; + pr_info("%s: get_system_current function isn't supported. Cannot get accurate value of Isys\n", __func__); + break; + case POWER_SUPPLY_EXT_PROP_DIRECT_CHARGER_CHG_STATUS: + val->strval = charging_state_str[pca9481->charging_state]; + pr_info("%s: CHARGER_STATUS(%s)\n", __func__, val->strval); + break; + case POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED: + ret = get_charging_enabled(pca9481); + if (ret < 0) + return ret; + + val->intval = ret; + break; + case POWER_SUPPLY_EXT_PROP_PASS_THROUGH_MODE: + break; + case POWER_SUPPLY_EXT_PROP_PASS_THROUGH_MODE_TA_VOL: + break; + case POWER_SUPPLY_EXT_PROP_DC_VIN_OVERCURRENT: + /* Get vin_ocp_current_12_11 */ + val->intval = pca9481->iin_rev; + break; + case POWER_SUPPLY_EXT_PROP_DC_REVERSE_MODE: + /* Get reverse mode */ + val->intval = pca9481->rev_mode; + break; + default: + return -EINVAL; + } + break; +#endif + default: + ret = -EINVAL; + } + + pr_debug("%s: End, prop=%d, val=%d, ret=%d\n", __func__, prop, val->intval, ret); + return ret; +} + +static enum power_supply_property pca9481_charger_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static const struct regmap_config pca9481_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = PCA9481_MAX_REGISTER, +}; + +static char *pca9481_supplied_to[] = { + "pca9481-charger", +}; + +static const struct power_supply_desc pca9481_mains_desc = { + .name = "pca9481-charger", + .type = POWER_SUPPLY_TYPE_UNKNOWN, + .get_property = pca9481_chg_get_property, + .set_property = pca9481_chg_set_property, + .properties = pca9481_charger_props, + .num_properties = ARRAY_SIZE(pca9481_charger_props), +}; + +#if defined(CONFIG_OF) +static int pca9481_charger_parse_dt(struct device *dev, + struct pca9481_platform_data *pdata) +{ + struct device_node *np_pca9481 = dev->of_node; +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + struct device_node *np; +#endif + int ret; + + if (!np_pca9481) + return -EINVAL; + + /* irq gpio */ + pdata->irq_gpio = of_get_named_gpio(np_pca9481, "pca9481,irq-gpio", 0); + if (pdata->irq_gpio < 0) + pr_err("%s : cannot get irq-gpio : %d\n", + __func__, pdata->irq_gpio); + else + pr_info("%s: irq-gpio: %d\n", __func__, pdata->irq_gpio); + + /* input current limit */ + ret = of_property_read_u32(np_pca9481, "pca9481,input-current-limit", + &pdata->iin_cfg); + if (ret) { + pr_info("%s: nxp,input-current-limit is Empty\n", __func__); + pdata->iin_cfg = PCA9481_IIN_REG_DFT; + } + pr_info("%s: pca9481,iin_cfg is %d\n", __func__, pdata->iin_cfg); + + /* charging current */ + ret = of_property_read_u32(np_pca9481, "pca9481,charging-current", + &pdata->ichg_cfg); + if (ret) { + pr_info("%s: pca9481,charging-current is Empty\n", __func__); + pdata->ichg_cfg = PCA9481_IBAT_REG_DFT; + } + pr_info("%s: pca9481,ichg_cfg is %d\n", __func__, pdata->ichg_cfg); + +#if !IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + /* charging float voltage */ + ret = of_property_read_u32(np_pca9481, "pca9481,float-voltage", + &pdata->vfloat); + if (ret) { + pr_info("%s: pca9481,float-voltage is Empty\n", __func__); + pdata->vfloat = PCA9481_VBAT_REG_DFT; + } + pr_info("%s: pca9481,vfloat is %d\n", __func__, pdata->vfloat); +#endif + + /* input topoff current */ + ret = of_property_read_u32(np_pca9481, "pca9481,input-itopoff", + &pdata->iin_topoff); + if (ret) { + pr_info("%s: pca9481,input-itopoff is Empty\n", __func__); + pdata->iin_topoff = IIN_DONE_DFT; + } + pr_info("%s: pca9481,iin_topoff is %d\n", __func__, pdata->iin_topoff); + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + /* fpdo_dc input topoff current */ + ret = of_property_read_u32(np_pca9481, "pca9481,fpdo_dc_input-itopoff", + &pdata->fpdo_dc_iin_topoff); + if (ret) { + pr_info("%s: pca9481,fpdo_dc_input-itopoff is Empty\n", __func__); + pdata->fpdo_dc_iin_topoff = 1700000; /* 1700mA */ + } + pr_info("%s: pca9481,fpdo_dc_iin_topoff is %d\n", __func__, pdata->fpdo_dc_iin_topoff); + + /* fpdo_dc Vnow topoff condition */ + ret = of_property_read_u32(np_pca9481, "pca9481,fpdo_dc_vnow-topoff", + &pdata->fpdo_dc_vnow_topoff); + if (ret) { + pr_info("%s: pca9481,fpdo_dc_vnow-topoff is Empty\n", __func__); + pdata->fpdo_dc_vnow_topoff = 5000000; /* Vnow 5000000uV means disable */ + } + pr_info("%s: pca9481,fpdo_dc_vnow_topoff is %d\n", __func__, pdata->fpdo_dc_vnow_topoff); +#endif + + /* external sense resistor value */ + ret = of_property_read_u32(np_pca9481, "pca9481,sense-resistance", + &pdata->snsres); + if (ret) { + pr_info("%s: pca9481,sense-resistance is Empty\n", __func__); + pdata->snsres = PCA9481_SENSE_R_DFT; + } + pr_info("%s: pca9481,snsres is %d\n", __func__, pdata->snsres); + + /* external sense resistor location */ + ret = of_property_read_u32(np_pca9481, "pca9481,sense-config", + &pdata->snsres_cfg); + if (ret) { + pr_info("%s: pca9481,sense-config is Empty\n", __func__); + pdata->snsres_cfg = PCA9481_SENSE_R_CFG_DFT; + } + pr_info("%s: pca9481,snsres_cfg is %d\n", __func__, pdata->snsres_cfg); + + /* switching frequency */ + ret = of_property_read_u32(np_pca9481, "pca9481,switching-frequency", + &pdata->fsw_cfg); + if (ret) { + pr_info("%s: pca9481,switching frequency is Empty\n", __func__); + pdata->fsw_cfg = PCA9481_FSW_CFG_DFT; + } + pr_info("%s: pca9481,fsw_cfg is %d\n", __func__, pdata->fsw_cfg); + + /* switching frequency - bypass */ + ret = of_property_read_u32(np_pca9481, "pca9481,switching-frequency-byp", + &pdata->fsw_cfg_byp); + if (ret) { + pr_info("%s: pca9481,switching frequency bypass is Empty\n", __func__); + pdata->fsw_cfg_byp = PCA9481_FSW_CFG_BYP_DFT; + } + pr_info("%s: pca9481,fsw_cfg_byp is %d\n", __func__, pdata->fsw_cfg_byp); + + /* switching frequency for low input current */ + ret = of_property_read_u32(np_pca9481, "nxp,switching-frequency-low", + &pdata->fsw_cfg_low); + if (ret) { + pr_info("%s: nxp,switching frequency for low input current is Empty\n", __func__); + pdata->fsw_cfg_low = PCA9481_FSW_CFG_LOW_DFT; + } + pr_info("%s: nxp,fsw_cfg_low is %d\n", __func__, pdata->fsw_cfg_low); + + /* switching frequency for fixed pdo */ + ret = of_property_read_u32(np_pca9481, "nxp,switching-frequency-fpdo", + &pdata->fsw_cfg_fpdo); + if (ret) { + pr_info("%s: nxp,switching frequency for FPDO is Empty\n", __func__); + pdata->fsw_cfg_fpdo = PCA9481_FSW_CFG_DFT; + } + pr_info("%s: nxp,fsw_cfg_fpdo is %d\n", __func__, pdata->fsw_cfg_fpdo); + + /* NTC0 threshold voltage */ + ret = of_property_read_u32(np_pca9481, "pca9481,ntc0-threshold", + &pdata->ntc0_th); + if (ret) { + pr_info("%s: pca9481,ntc0 threshold voltage is Empty\n", __func__); + pdata->ntc0_th = PCA9481_NTC_0_TH_DFT; + } + pr_info("%s: pca9481,ntc0_th is %d\n", __func__, pdata->ntc0_th); + + /* NTC1 threshold voltage */ + ret = of_property_read_u32(np_pca9481, "pca9481,ntc1-threshold", + &pdata->ntc1_th); + if (ret) { + pr_info("%s: pca9481,ntc1 threshold voltage is Empty\n", __func__); + pdata->ntc1_th = PCA9481_NTC_1_TH_DFT; + } + pr_info("%s: pca9481,ntc1_th is %d\n", __func__, pdata->ntc1_th); + + /* NTC protection */ + ret = of_property_read_u32(np_pca9481, "pca9481,ntc-protection", + &pdata->ntc_en); + if (ret) { + pr_info("%s: pca9481,ntc protection is Empty\n", __func__); + pdata->ntc_en = 0; /* Disable */ + } + pr_info("%s: pca9481,ntc_en is %d\n", __func__, pdata->ntc_en); + + /* Charging mode */ + ret = of_property_read_u32(np_pca9481, "pca9481,chg-mode", + &pdata->chg_mode); + if (ret) { + pr_info("%s: pca9481,charging mode is Empty\n", __func__); + pdata->chg_mode = CHG_2TO1_DC_MODE; + } + pr_info("%s: pca9481,chg_mode is %d\n", __func__, pdata->chg_mode); + + /* cv mode polling time in step1 charging */ + ret = of_property_read_u32(np_pca9481, "pca9481,cv-polling", + &pdata->cv_polling); + if (ret) { + pr_info("%s: pca9481,cv-polling is Empty\n", __func__); + pdata->cv_polling = CVMODE_CHECK_T; + } + pr_info("%s: pca9481,cv polling is %d\n", __func__, pdata->cv_polling); + + /* vfloat threshold in step1 charging */ + ret = of_property_read_u32(np_pca9481, "pca9481,step1-vth", + &pdata->step1_vth); + if (ret) { + pr_info("%s: pca9481,step1-vfloat-threshold is Empty\n", __func__); + pdata->step1_vth = STEP1_VFLOAT_THRESHOLD; + } + pr_info("%s: pca9481,step1_vth is %d\n", __func__, pdata->step1_vth); + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + np = of_find_node_by_name(NULL, "battery"); + if (!np) { + pr_err("## %s: np(battery) NULL\n", __func__); + } else { + ret = of_property_read_string(np, "battery,charger_name", + (char const **)&pdata->sec_dc_name); + if (ret) { + pr_err("## %s: direct_charger is Empty\n", __func__); + pdata->sec_dc_name = "sec-direct-charger"; + } + pr_info("%s: battery,charger_name is %s\n", __func__, pdata->sec_dc_name); + + /* Fuelgauge power supply name */ + ret = of_property_read_string(np, "battery,fuelgauge_name", + (char const **)&pdata->fg_name); + if (ret) { + pr_info("## %s: fuelgauge_name name is Empty\n", __func__); + pdata->fg_name = "battery"; + } + pr_info("%s: fuelgauge name is %s\n", __func__, pdata->fg_name); + + /* charging float voltage */ + ret = of_property_read_u32(np, "battery,chg_float_voltage", + &pdata->vfloat); + pdata->vfloat *= PCA9481_SEC_DENOM_U_M; + if (ret) { + pr_info("%s: battery,dc_float_voltage is Empty\n", __func__); + pdata->vfloat = PCA9481_VBAT_REG_DFT; + } + pr_info("%s: battery,v_float is %d\n", __func__, pdata->vfloat); + + /* the lowest limit to FPDO DC IIN */ + ret = of_property_read_u32(np, "battery,fpdo_dc_charge_power", + &pdata->fpdo_dc_iin_lowest_limit); + pdata->fpdo_dc_iin_lowest_limit *= PCA9481_SEC_DENOM_U_M; + pdata->fpdo_dc_iin_lowest_limit /= PCA9481_SEC_FPDO_DC_IV; + if (ret) { + pr_info("%s: battery,fpdo_dc_charge_power is Empty\n", __func__); + pdata->fpdo_dc_iin_lowest_limit = 10000000; /* 10A */ + } + pr_info("%s: fpdo_dc_iin_lowest_limit is %d\n", __func__, pdata->fpdo_dc_iin_lowest_limit); + } +#endif + + return 0; +} +#else +static int pca9481_charger_parse_dt(struct device *dev, + struct pca9481_platform_data *pdata) +{ + return 0; +} +#endif /* CONFIG_OF */ + +#ifdef CONFIG_USBPD_PHY_QCOM +static int pca9481_usbpd_setup(struct pca9481_charger *pca9481) +{ + int ret = 0; + const char *pd_phandle = "usbpd-phy"; + + pca9481->pd = devm_usbpd_get_by_phandle(pca9481->dev, pd_phandle); + + if (IS_ERR(pca9481->pd)) { + pr_err("get_usbpd phandle failed (%ld)\n", + PTR_ERR(pca9481->pd)); + return PTR_ERR(pca9481->pd); + } + + return ret; +} +#endif + +static int read_reg(void *data, u64 *val) +{ + struct pca9481_charger *chip = data; + int rc; + unsigned int temp; + + rc = regmap_read(chip->regmap, chip->debug_address, &temp); + if (rc) { + pr_err("Couldn't read reg %x rc = %d\n", + chip->debug_address, rc); + return -EAGAIN; + } + *val = temp; + return 0; +} + +static int write_reg(void *data, u64 val) +{ + struct pca9481_charger *chip = data; + int rc; + u8 temp; + + temp = (u8) val; + rc = regmap_write(chip->regmap, chip->debug_address, temp); + if (rc) { + pr_err("Couldn't write 0x%02x to 0x%02x rc= %d\n", + temp, chip->debug_address, rc); + return -EAGAIN; + } + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(register_debug_ops, read_reg, write_reg, "0x%02llx\n"); + +static int pca9481_create_debugfs_entries(struct pca9481_charger *chip) +{ + struct dentry *ent; + int rc = 0; + + chip->debug_root = debugfs_create_dir("charger-pca9481", NULL); + if (!chip->debug_root) { + dev_err(chip->dev, "Couldn't create debug dir\n"); + rc = -ENOENT; + } else { + debugfs_create_x32("address", S_IFREG | S_IWUSR | S_IRUGO, + chip->debug_root, &(chip->debug_address)); + + ent = debugfs_create_file("data", S_IFREG | S_IWUSR | S_IRUGO, + chip->debug_root, chip, + ®ister_debug_ops); + if (!ent) { + dev_err(chip->dev, + "Couldn't create data debug file\n"); + rc = -ENOENT; + } + } + + return rc; +} + +static int pca9481_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct power_supply_config mains_cfg = {}; + struct pca9481_platform_data *pdata; + struct device *dev = &client->dev; + struct pca9481_charger *pca9481_chg; + int ret; + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + dev_info(&client->dev, "%s: PCA9481 Charger Driver Loading\n", __func__); +#endif + dev_info(&client->dev, "%s: =========START=========\n", __func__); + + pca9481_chg = devm_kzalloc(dev, sizeof(*pca9481_chg), GFP_KERNEL); + if (!pca9481_chg) + return -ENOMEM; + +#if defined(CONFIG_OF) + if (client->dev.of_node) { + pdata = devm_kzalloc(&client->dev, sizeof(struct pca9481_platform_data), + GFP_KERNEL); + if (!pdata) { + dev_err(&client->dev, "Failed to allocate memory\n"); + return -ENOMEM; + } + + ret = pca9481_charger_parse_dt(&client->dev, pdata); + if (ret < 0) { + dev_err(&client->dev, "Failed to get device of_node\n"); + return -ENOMEM; + } + + client->dev.platform_data = pdata; + } else { + pdata = client->dev.platform_data; + } +#else + pdata = dev->platform_data; +#endif + if (!pdata) + return -EINVAL; + + i2c_set_clientdata(client, pca9481_chg); + + mutex_init(&pca9481_chg->lock); + mutex_init(&pca9481_chg->i2c_lock); + pca9481_chg->dev = &client->dev; + pca9481_chg->pdata = pdata; + pca9481_chg->charging_state = DC_STATE_NO_CHARGING; +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_chg->wdt_kick = false; +#endif + + /* Create a work queue for the direct charger */ + pca9481_chg->dc_wq = alloc_ordered_workqueue("pca9481_dc_wq", WQ_MEM_RECLAIM); + if (pca9481_chg->dc_wq == NULL) { + dev_err(pca9481_chg->dev, "failed to create work queue\n"); + return -ENOMEM; + } + + pca9481_chg->monitor_wake_lock = wakeup_source_register(&client->dev, "pca9481-charger-monitor"); + if (!pca9481_chg->monitor_wake_lock) { + dev_err(dev, "%s: Cannot register wakeup source\n", __func__); + return -ENOMEM; + } + + /* initialize work */ + INIT_DELAYED_WORK(&pca9481_chg->timer_work, pca9481_timer_work); + mutex_lock(&pca9481_chg->lock); + pca9481_chg->timer_id = TIMER_ID_NONE; + pca9481_chg->timer_period = 0; + mutex_unlock(&pca9481_chg->lock); + + INIT_DELAYED_WORK(&pca9481_chg->pps_work, pca9481_pps_request_work); +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + INIT_DELAYED_WORK(&pca9481_chg->wdt_control_work, pca9481_wdt_control_work); +#endif + + pca9481_chg->regmap = devm_regmap_init_i2c(client, &pca9481_regmap); + if (IS_ERR(pca9481_chg->regmap)) { + ret = -EINVAL; + goto error; + } + +#ifdef CONFIG_USBPD_PHY_QCOM + if (pca9481_usbpd_setup(pca9481_chg)) { + dev_err(dev, "Error usbpd setup!\n"); + pca9481_chg->pd = NULL; + } +#endif + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + pca9481_init_adc_val(pca9481_chg, -1); +#endif + + ret = pca9481_hw_init(pca9481_chg); + if (ret < 0) + goto error; + + mains_cfg.supplied_to = pca9481_supplied_to; + mains_cfg.num_supplicants = ARRAY_SIZE(pca9481_supplied_to); + mains_cfg.drv_data = pca9481_chg; + pca9481_chg->mains = power_supply_register(dev, &pca9481_mains_desc, + &mains_cfg); + if (IS_ERR(pca9481_chg->mains)) { + ret = -ENODEV; + goto error; + } + + /* + * Interrupt pin is optional. If it is connected, we setup the + * interrupt support here. + */ + if (pdata->irq_gpio >= 0) { + ret = pca9481_irq_init(pca9481_chg, client); + if (ret < 0) { + dev_warn(dev, "failed to initialize IRQ: %d\n", ret); + dev_warn(dev, "disabling IRQ support\n"); + } + /* disable interrupt */ + disable_irq(client->irq); + } + + ret = pca9481_create_debugfs_entries(pca9481_chg); + if (ret < 0) + goto error; + + sec_chg_set_dev_init(SC_DEV_DIR_CHG); + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + dev_info(&client->dev, "%s: PCA9481 Charger Driver Loaded\n", __func__); +#endif + dev_info(&client->dev, "%s: =========END=========\n", __func__); + + return 0; + +error: + destroy_workqueue(pca9481_chg->dc_wq); + mutex_destroy(&pca9481_chg->lock); + wakeup_source_unregister(pca9481_chg->monitor_wake_lock); + return ret; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) +static int pca9481_remove(struct i2c_client *client) +#else +static void pca9481_remove(struct i2c_client *client) +#endif +{ + struct pca9481_charger *pca9481_chg = i2c_get_clientdata(client); + + /* stop charging if it is active */ + pca9481_stop_charging(pca9481_chg); + + if (client->irq) { + free_irq(client->irq, pca9481_chg); + gpio_free(pca9481_chg->pdata->irq_gpio); + } + + /* Delete the work queue */ + destroy_workqueue(pca9481_chg->dc_wq); + + wakeup_source_unregister(pca9481_chg->monitor_wake_lock); + if (pca9481_chg->mains) { + power_supply_put(pca9481_chg->mains); + power_supply_unregister(pca9481_chg->mains); + } + debugfs_remove(pca9481_chg->debug_root); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) + return 0; +#endif +} + +static void pca9481_shutdown(struct i2c_client *client) +{ + struct pca9481_charger *pca9481_chg = i2c_get_clientdata(client); + + pr_info("%s: ++\n", __func__); + + if (pca9481_set_charging(pca9481_chg, false) < 0) + pr_info("%s: failed to disable charging\n", __func__); + + if (client->irq) + free_irq(client->irq, pca9481_chg); + + cancel_delayed_work(&pca9481_chg->timer_work); + cancel_delayed_work(&pca9481_chg->pps_work); +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + cancel_delayed_work(&pca9481_chg->wdt_control_work); +#endif + + pr_info("%s: --\n", __func__); +} + +static const struct i2c_device_id pca9481_id[] = { + { "pca9481-charger", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pca9481_id); + +#if defined(CONFIG_OF) +static const struct of_device_id pca9481_i2c_dt_ids[] = { + { .compatible = "nxp,pca9481" }, + { }, +}; +MODULE_DEVICE_TABLE(of, pca9481_i2c_dt_ids); +#endif /* CONFIG_OF */ + +#if defined(CONFIG_PM) +#ifdef CONFIG_RTC_HCTOSYS +static void pca9481_check_and_update_charging_timer(struct pca9481_charger *pca9481) +{ + unsigned long current_time = 0, next_update_time, time_left; + + get_current_time(¤t_time); + + if (pca9481->timer_id != TIMER_ID_NONE) { + next_update_time = pca9481->last_update_time + (pca9481->timer_period / 1000); // unit is second + + pr_info("%s: current_time=%ld, next_update_time=%ld\n", __func__, current_time, next_update_time); + + if (next_update_time > current_time) + time_left = next_update_time - current_time; + else + time_left = 0; + + mutex_lock(&pca9481->lock); + pca9481->timer_period = time_left * 1000; // ms unit + mutex_unlock(&pca9481->lock); + schedule_delayed_work(&pca9481->timer_work, msecs_to_jiffies(pca9481->timer_period)); + pr_info("%s: timer_id=%d, time_period=%ld\n", __func__, pca9481->timer_id, pca9481->timer_period); + } + pca9481->last_update_time = current_time; +} +#endif + +static int pca9481_suspend(struct device *dev) +{ + struct pca9481_charger *pca9481 = dev_get_drvdata(dev); + + if (pca9481->timer_id != TIMER_ID_NONE) { + pr_debug("%s: cancel delayed work\n", __func__); + + /* cancel delayed_work */ + cancel_delayed_work(&pca9481->timer_work); + } + return 0; +} + +static int pca9481_resume(struct device *dev) +{ + struct pca9481_charger *pca9481 = dev_get_drvdata(dev); + +#ifdef CONFIG_RTC_HCTOSYS + pca9481_check_and_update_charging_timer(pca9481); +#else + if (pca9481->timer_id != TIMER_ID_NONE) { + pr_debug("%s: update_timer\n", __func__); + + /* Update the current timer */ + mutex_lock(&pca9481->lock); + pca9481->timer_period = 0; // ms unit + mutex_unlock(&pca9481->lock); + queue_delayed_work(pca9481->dc_wq, + &pca9481->timer_work, + msecs_to_jiffies(pca9481->timer_period)); + } +#endif + return 0; +} +#else +#define pca9481_suspend NULL +#define pca9481_resume NULL +#endif + +const struct dev_pm_ops pca9481_pm_ops = { + .suspend = pca9481_suspend, + .resume = pca9481_resume, +}; + +static struct i2c_driver pca9481_driver = { + .driver = { + .name = "pca9481-charger", +#if defined(CONFIG_OF) + .of_match_table = pca9481_i2c_dt_ids, +#endif /* CONFIG_OF */ +#if defined(CONFIG_PM) + .pm = &pca9481_pm_ops, +#endif + }, + .probe = pca9481_probe, + .remove = pca9481_remove, + .shutdown = pca9481_shutdown, + .id_table = pca9481_id, +}; + +module_i2c_driver(pca9481_driver); + +MODULE_DESCRIPTION("PCA9481 charger driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0.25S"); diff --git a/drivers/battery/charger/pca9481_charger/pca9481_charger.dtsi b/drivers/battery/charger/pca9481_charger/pca9481_charger.dtsi new file mode 100644 index 000000000000..caf60381e1b7 --- /dev/null +++ b/drivers/battery/charger/pca9481_charger/pca9481_charger.dtsi @@ -0,0 +1,32 @@ +&qupv3_hub_i2c4 { + status = "okay"; + + pca9481_charger: pca9481@57 { + compatible = "nxp,pca9481"; + reg = <0x57>; + + pca9481,input-current-limit = <3000000>; /* 3.0A */ + pca9481,charging-current = <6000000>; /* 6.0A */ + pca9481,input-itopoff = <500000>; /* 500mA */ + pca9481,sense-resistance = <2>; /* 5mOhm */ + pca9481,sense-config = <0>; /* Bottom side */ + pca9481,switching-frequency = <1000>; /* 1000kHz */ + pca9481,ntc0-threshold = <1110000>; /* 1.11V */ + pca9481,ntc1-threshold = <495000>; /* 0.495V */ + pca9481,ntc-en = <0>; /* Disable NTC protection function */ + pca9481,chg-mode = <1>; /* 2:1 direct charging mode */ + pca9481,cv-polling = <2000>; /* 2000ms */ + pca9481,step1-cv=<4200000>; /* 4200mV */ + }; +}; + +/* /home/dpi/qb5_8814/workspace/P4_1716/android/kernel_platform/kmodule/battery/stable/eureka/charger/pca9481/pca9481_charger.e3q.dtsi */ +&pca9481_charger { + pca9481,input-itopoff = <400000>; /* 400mA */ + pca9481,fpdo_dc_input-itopoff = <1700000>; /* 1700mA */ + pca9481,fpdo_dc_vnow-topoff = <4280000>; /* 4280mV */ + nxp,switching-frequency-low = <1000>; + pca9481,switching-frequency = <1000>; /* 300kHz */ +}; + +/* /home/dpi/qb5_8814/workspace/P4_1716/android/kernel_platform/kmodule/battery/stable/eureka/charger/pca9481/pca9481_charger.e3q.09.dtsi */ diff --git a/drivers/battery/charger/pca9481_charger/pca9481_charger.h b/drivers/battery/charger/pca9481_charger/pca9481_charger.h new file mode 100644 index 000000000000..5a4c0334a411 --- /dev/null +++ b/drivers/battery/charger/pca9481_charger/pca9481_charger.h @@ -0,0 +1,895 @@ +/* + * Platform data for the NXP PCA9481 battery charger driver. + * + * Copyright 2021-2023 NXP + * + * 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. + */ + +#ifndef _PCA9481_CHARGER_H_ +#define _PCA9481_CHARGER_H_ + +/* IIN offset as the switching frequency in uA*/ +static int iin_fsw_cfg[16] = { 9990, 10540, 11010, 11520, 12000, 12520, 12990, 13470, + 5460, 6050, 6580, 7150, 7670, 8230, 8720, 9260 }; + +struct pca9481_platform_data { + int irq_gpio; /* GPIO pin that's connected to INT# */ + unsigned int iin_cfg; /* Input Current Limit - uA unit */ + unsigned int ichg_cfg; /* Charging Current - uA unit */ + unsigned int vfloat; /* Float Voltage - uV unit */ + unsigned int iin_topoff; /* Input Topoff current - uV unit */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + unsigned int fpdo_dc_iin_topoff; /* FPDO DC Input Topoff current - uA unit */ + unsigned int fpdo_dc_vnow_topoff; /* FPDO DC Vnow Topoff condition - uV unit */ + unsigned int fpdo_dc_iin_lowest_limit; /* FPDO DC IIN lowest limit condition - uA unit */ +#endif + unsigned int snsres; /* External sense resistor value for IBAT measurement, 0 - 1mOhm, 1 - 2mOhm, 2 - 5mOhm */ + unsigned int snsres_cfg; /* External sense resistor loacation, 0 - bottom side, 1 - top side */ + unsigned int fsw_cfg; /* Switching frequency, 200kHz ~ 1.75MHz in 50kHz step - kHz unit */ + unsigned int fsw_cfg_byp;/* Switching frequency for bypass mode, 200kHz ~ 1.75MHz in 50kHz step - kHz unit */ + unsigned int fsw_cfg_low;/* Switching frequency for low current, 200kHz ~ 1.75MHz in 50kHz step - kHz unit */ + unsigned int fsw_cfg_fpdo; /* Switching frequency for fixed pdo, 200kHz ~ 1.75MHz in 50kHz step - kHz unit */ + unsigned int ntc0_th; /* NTC0 voltage threshold - cool or cold: 0 ~ 1.5V, 15mV step - uV unit */ + unsigned int ntc1_th; /* NTC1 voltage threshold - warm or hot: 0 ~ 1.5V, 15mV step - uV unit */ + unsigned int ntc_en; /* Enable or Disable NTC protection, 0 - Disable, 1 - Enable */ + unsigned int chg_mode; /* Default charging mode, 0 - No direct charging, 1 - 2:1 charging mode, 2 - 4:1 charging mode */ + unsigned int cv_polling; /* CV mode polling time in step1 charging - ms unit */ + unsigned int step1_vth; /* Step1 vfloat threshold - uV unit */ + char *fg_name; /* fuelgauge power supply name */ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + char *sec_dc_name; +#endif +}; + +#define BITS(_end, _start) ((BIT(_end) - BIT(_start)) + BIT(_end)) +#define MASK2SHIFT(_mask) __ffs(_mask) +#define MIN(a, b) ((a < b) ? (a):(b)) +#define MAX(a, b) ((a > b) ? (a):(b)) + +/************************/ +/* PCA9481 Register Map */ +/************************/ +#define PCA9481_REG_DEVICE_ID 0x00 // Device ID Register +#define PCA9481_BIT_DEV_REV BITS(7, 4) +#define PCA9481_BIT_DEV_ID BITS(3, 0) +#define PCA9481_DEVICE_ID 0x10 // Default ID + +#define PCA9481_REG_INT_DEVICE_0 0x01 // Interrupt Register 0 for device operation +#define PCA9481_BIT_VOUT_MAX_OV BIT(7) +#define PCA9481_BIT_RCP_DETECTED BIT(6) +#define PCA9481_BIT_VIN_UNPLUG BIT(5) +#define PCA9481_BIT_VIN_OVP BIT(4) +#define PCA9481_BIT_VIN_OV_TRACKING BIT(3) +#define PCA9481_BIT_VIN_UV_TRACKING BIT(2) +#define PCA9481_BIT_VIN_NOT_VALID BIT(1) +#define PCA9481_BIT_VIN_VALID BIT(0) + +#define PCA9481_REG_INT_DEVICE_1 0x02 // Interrupt Register 1 for device operation +#define PCA9481_BIT_NTC_1_DETECTED BIT(6) +#define PCA9481_BIT_NTC_0_DETECTED BIT(5) +#define PCA9481_BIT_ADC_READ_DONE BIT(4) +#define PCA9481_BIT_VIN_CURRENT_LIMITED BIT(3) +#define PCA9481_BIT_VIN_OCP_21_11 BIT(2) +#define PCA9481_BIT_SINK_RCP_TIMEOUT BIT(1) +#define PCA9481_BIT_SINK_RCP_ENABLED BIT(0) + +#define PCA9481_REG_INT_DEVICE_2 0x03 // Interrupt Register 2 for device operation +#define PCA9481_BIT_VIN_OCP_12_11 BIT(6) +#define PCA9481_BIT_VFET_IN_OK BIT(5) +#define PCA9481_BIT_THEM_REG_EXIT BIT(4) +#define PCA9481_BIT_THEM_REG BIT(3) +#define PCA9481_BIT_THSD_EXIT BIT(2) +#define PCA9481_BIT_THSD BIT(1) +#define PCA9481_BIT_WATCHDOG_TIMER_OUT BIT(0) + +#define PCA9481_REG_INT_DEVICE_3 0x04 // Interrupt Register 3 for device operation +#define PCA9481_BIT_STATUS_CHANGE_INT BIT(6) +#define PCA9481_BIT_VFET_IN_OVP_EXIT BIT(5) +#define PCA9481_BIT_VFET_IN_OVP BIT(4) +#define PCA9481_BIT_VIN_OCP_ALARM_12_11_EXIT BIT(3) +#define PCA9481_BIT_VIN_OCP_ALARM_12_11 BIT(2) +#define PCA9481_BIT_VIN_OCP_ALARM_21_11_EXIT BIT(1) +#define PCA9481_BIT_VIN_OCP_ALARM_21_11 BIT(0) + +#define PCA9481_REG_INT_CHARGING 0x05 // Interrupt Register for Charging operation +#define PCA9481_BIT_CHG_SAFETY_TIMER BIT(5) +#define PCA9481_BIT_VBAT_OVP_EXIT BIT(4) +#define PCA9481_BIT_VBAT_OVP BIT(3) +#define PCA9481_BIT_VBAT_REG_LOOP BIT(2) +#define PCA9481_BIT_I_VBAT_CC_LOOP BIT(1) +#define PCA9481_BIT_I_VIN_CC_LOOP BIT(0) + +#define PCA9481_REG_INT_SC_0 0x06 // Interrupt Register 0 for Switched Capacitor(SC) operation +#define PCA9481_BIT_PHASE_B_FAULT BIT(6) +#define PCA9481_BIT_PHASE_A_FAULT BIT(5) +#define PCA9481_BIT_PIN_SHORT BIT(4) +#define PCA9481_BIT_STANDBY_EXIT BIT(3) +#define PCA9481_BIT_STANDBY_ENTER BIT(2) +#define PCA9481_BIT_SWITCHING_ENABLED BIT(1) +#define PCA9481_BIT_SC_OFF BIT(0) + +#define PCA9481_REG_INT_SC_1 0x07 // Interrupt Register 1 for Switched Capacitor(SC) operation +#define PCA9481_BIT_OVPOUT_ERRLO BIT(2) +#define PCA9481_BIT_11_ENABLED BIT(1) +#define PCA9481_BIT_REVERSE_SW_SS_OC BIT(0) + +#define PCA9481_REG_INT_DEVICE_0_MASK 0x08 // Interrupt Mask Register for INT_DEVICE_0 register +#define PCA9481_REG_INT_DEVICE_1_MASK 0x09 // Interrupt Mask Register for INT_DEVICE_1 register +#define PCA9481_REG_INT_DEVICE_2_MASK 0x0A // Interrupt Mask Register for INT_DEVICE_2 register +#define PCA9481_REG_INT_DEVICE_3_MASK 0x0B // Interrupt Mask Register for INT_DEVICE_3 register +#define PCA9481_REG_INT_CHARGING_MASK 0x0C // Interrupt Mask Register for INT_CHARGING register +#define PCA9481_REG_INT_SC_0_MASK 0x0D // Interrupt Mask Register for INT_SC_0 register +#define PCA9481_REG_INT_SC_1_MASK 0x0E // Interrupt Mask Register for INT_SC_1 register + +#define PCA9481_REG_DEVICE_0_STS 0x0F // Status Register for INT_DEVICE_0 register +#define PCA9481_REG_DEVICE_1_STS 0x10 // Status Register for INT_DEVICE_1 register +#define PCA9481_REG_DEVICE_2_STS 0x11 // Status Register for INT_DEVICE_2 register +#define PCA9481_REG_DEVICE_3_STS 0x12 // Status Register for INT_DEVICE_3 register +#define PCA9481_BIT_STATUS_CHANGE BITS(7, 6) +#define PCA9481_REG_CHARGING_STS 0x13 // Status Register for INT_CHARGING register +#define PCA9481_REG_SC_0_STS 0x14 // Status Register for INT_SC_0 registger +#define PCA9481_REG_SC_1_STS 0x15 // Status Register for INT_SC_1 registger + +#define PCA9481_REG_DEVICE_CNTL_0 0x16 // Device control register 0 +#define PCA9481_BIT_STANDBY_BY_NTC_EN BIT(7) +#define PCA9481_BIT_THERMAL_SHUTDOWN_CFG BITS(6, 5) +#define PCA9481_BIT_WATCHDOG_TIMER_DOUBLE_EN BIT(4) +#define PCA9481_BIT_WATCHDOG_CFG BITS(3, 2) +#define PCA9481_BIT_WATCHDOG_EN BIT(1) +#define PCA9481_BIT_SOFT_RESET BIT(0) + +#define PCA9481_REG_DEVICE_CNTL_1 0x17 // Device control register 1 +#define PCA9481_BIT_HALF_VIN_OVP_EN BIT(7) +#define PCA9481_BIT_LOW_POWER_MODE_DISABLE BIT(6) +#define PCA9481_BIT_VIN_OVP_CFG BITS(5, 4) +#define PCA9481_BIT_VIN_FIXED_OVP_EN BIT(3) +#define PCA9481_BIT_THERMAL_REGULATION_CFG BITS(2, 1) +#define PCA9481_BIT_THERMAL_REGULATION_EN BIT(0) + +#define PCA9481_REG_DEVICE_CNTL_2 0x18 // Device control register 2 +#define PCA9481_BIT_IBAT_SENSE_R_CFG BIT(7) +#define PCA9481_BIT_EN_CFG BIT(6) +#define PCA9481_BIT_VFET_IN_OVP_CFG BITS(5, 3) +#define PCA9481_BIT_EXT_OVP_FUNCTION_EN BIT(2) +#define PCA9481_BIT_VIN_VALID_DEGLITCH BITS(1, 0) + +#define PCA9481_REG_DEVICE_CNTL_3 0x19 // Device control register 3 +#define PCA9481_BIT_SYNC_FUNCTION_EN BIT(7) +#define PCA9481_BIT_SYNC_SECONDARY_EN BIT(6) +#define PCA9481_BIT_FORCE_SHUTDOWN BIT(5) +#define PCA9481_BIT_VIN_OCP_ALRAM_CURRENT_12_11 BITS(4, 1) +#define PCA9481_BIT_VIN_ALARM_OCP_12_11_EN BIT(0) + +#define PCA9481_REG_AUTO_RESTART_CNTL_0 0x1A // Device auto restart register 0 +#define PCA9481_BIT_AUTO_RESTART_NTC_EN BIT(7) +#define PCA9481_BIT_AUTO_RESTART_FIXED_OV_EN BIT(6) +#define PCA9481_BIT_AUTO_RESTART_OV_TRACKING_EN BIT(5) +#define PCA9481_BIT_AUTO_RESTART_UV_TRACKING_EN BIT(4) +#define PCA9481_BIT_AUTO_RESTART_THEM_EN BIT(3) +#define PCA9481_BIT_AUTO_RESTART_VBAT_OVP_EN BIT(2) +#define PCA9481_BIT_AUTO_RESTART_VIN_OCP_21_11_EN BIT(1) +#define PCA9481_BIT_AUTO_RESTART_RCP_EN BIT(0) + +#define PCA9481_REG_AUTO_RESTART_CNTL_1 0x1B // Device auto restart register 1 + +#define PCA9481_REG_RCP_CNTL 0x1C // RCP control register +#define PCA9481_BIT_I_RCP_CURRENT_DEGLITCH BITS(7, 6) +#define PCA9481_BIT_I_SINK_RCP_TIMER BIT(5) +#define PCA9481_BIT_I_RCP_THRESHOLD BITS(4, 2) +#define PCA9481_BIT_I_SINK_RCP BIT(1) +#define PCA9481_BIT_RCP_EN BIT(0) + +#define PCA9481_REG_CHARGING_CNTL_0 0x1D // Charging control register 0 +#define PCA9481_BIT_VIN_CURRENT_SLOPE BIT(7) +#define PCA9481_BIT_OCP_DEGLITCH_TIME_21_11 BIT(6) +#define PCA9481_BIT_VIN_CURRENT_OCP_21_11 BIT(5) +#define PCA9481_BIT_VIN_OCP_21_11_EN BIT(4) +#define PCA9481_BIT_CSP_CSN_MEASURE_EN BIT(3) +#define PCA9481_BIT_VBAT_LOOP_EN BIT(2) +#define PCA9481_BIT_I_VBAT_LOOP_EN BIT(1) +#define PCA9481_BIT_I_VIN_LOOP_EN BIT(0) + +#define PCA9481_REG_CHARGING_CNTL_1 0x1E // Charging control register 1 +#define PCA9481_BIT_VIN_REGULATION_CURRENT BITS(7, 0) + +#define PCA9481_REG_CHARGING_CNTL_2 0x1F // Charging control register 2 +#define PCA9481_BIT_VBAT_REGULATION BITS(7, 0) + +#define PCA9481_REG_CHARGING_CNTL_3 0x20 // Charging control register 3 +#define PCA9481_BIT_I_VBAT_REGULATION BITS(7, 0) + +#define PCA9481_REG_CHARGING_CNTL_4 0x21 // Charging control register 4 +#define PCA9481_BIT_IBAT_SENSE_R_SEL BITS(7, 6) +#define PCA9481_BIT_VIN_ALARM_OCP_21_11_EN BIT(5) +#define PCA9481_BIT_CHARGER_SAFETY_TIMER BITS(4, 3) +#define PCA9481_BIT_CHARGER_SAFETY_TIMER_EN BIT(2) +#define PCA9481_BIT_VBAT_OVP_DEGLITCH_TIME BITS(1, 0) + +#define PCA9481_REG_CHARGING_CNTL_5 0x22 // Charging control register 5 +#define PCA9481_BIT_VBAT_OVP_EN BIT(6) +#define PCA9481_BIT_VIN_OCP_CURRENT_12_11 BITS(5, 2) +#define PCA9481_BIT_OCP_DEGLITCH_TIME_12_11 BIT(1) +#define PCA9481_BIT_VIN_OCP_12_11_EN BIT(0) + +#define PCA9481_REG_CHARGING_CNTL_6 0x23 // Charging control register 6 +#define PCA9481_BIT_VIN_OCP_ALARM_CURRENT BITS(7, 0) + +#define PCA9481_REG_NTC_0_CNTL 0x24 // NTC 0 control register +#define PCA9481_BIT_NTC_0_TRIGGER_VOLTAGE BITS(7, 1) +#define PCA9481_BIT_NTC_EN BIT(0) + +#define PCA9481_REG_NTC_1_CNTL 0x25 // NTC 1 control register +#define PCA9481_BIT_NTC_1_TRIGGER_VOLTAGE BITS(6, 0) + +#define PCA9481_REG_SC_CNTL_0 0x26 // Switched Capacitor converter control register 0 +#define PCA9481_BIT_FSW_CFG BITS(4, 0) + +#define PCA9481_REG_SC_CNTL_1 0x27 // Switched Capacitor converter control register 1 +#define PCA9481_BIT_VIN_UV_TRACKING_DEGLITCH BITS(5, 4) +#define PCA9481_BIT_UV_TRACKING_HYSTERESIS BIT(3) +#define PCA9481_BIT_UV_TRACK_DELTA BITS(2, 1) +#define PCA9481_BIT_UV_TRACKING_EN BIT(0) + +#define PCA9481_REG_SC_CNTL_2 0x28 // Switched Capacitor converter control register 2 +#define PCA9481_BIT_VOUT_MAX_OV_EN BIT(4) +#define PCA9481_BIT_OV_TRACK_DELTA BITS(3, 2) +#define PCA9481_BIT_OV_TRACKING_HYSTERESIS BIT(1) +#define PCA9481_BIT_OV_TRACKING_EN BIT(0) + +#define PCA9481_REG_SC_CNTL_3 0x29 // Switched Capacitor converter control register 3 +#define PCA9481_BIT_STANDBY_EN BIT(7) +#define PCA9481_BIT_SC_OPERATION_MODE BITS(6, 5) +#define PCA9481_BIT_PRECHARGE_CFLY_TIME_OUT BITS(4, 3) +#define PCA9481_BIT_PRECHARGE_CFLY_I BITS(2, 0) + +#define PCA9481_REG_ADC_CNTL 0x2A // ADC control register +#define PCA9481_BIT_ADC_IN_SHUTDOWN_STATE BIT(7) +#define PCA9481_BIT_ADC_MODE_CFG BITS(6, 5) +#define PCA9481_BIT_ADC_HIBERNATE_READ_INTERVAL BITS(4, 3) +#define PCA9481_BIT_ADC_AVERAGE_TIMES BITS(2, 1) +#define PCA9481_BIT_ADC_EN BIT(0) + +#define PCA9481_REG_ADC_EN_CNTL_0 0x2B // ADC enable control register 0 +#define PCA9481_BIT_ADC_READ_I_BAT_CURRENT_EN BIT(7) +#define PCA9481_BIT_ADC_READ_VIN_CURRENT_EN BIT(6) +#define PCA9481_BIT_ADC_READ_DIE_TEMP_EN BIT(5) +#define PCA9481_BIT_ADC_READ_NTC_EN BIT(4) +#define PCA9481_BIT_ADC_READ_VOUT_EN BIT(3) +#define PCA9481_BIT_ADC_READ_BATP_BATN_EN BIT(2) +#define PCA9481_BIT_ADC_READ_OVP_OUT_EN BIT(1) +#define PCA9481_BIT_ADC_READ_VIN_EN BIT(0) + +#define PCA9481_REG_ADC_EN_CNTL_1 0x2C // ADC enable control register 1 +#define PCA9481_BIT_ADC_READ_VFET_IN_EN BIT(0) + +#define PCA9481_REG_ADC_READ_VIN_0 0x2D // ADC VIN read register 0 +#define PCA9481_REG_ADC_READ_VIN_1 0x2E // ADC VIN read register 1 +#define PCA9481_REG_ADC_READ_VFET_IN_0 0x2F // ADC VFET_IN read register 0 +#define PCA9481_REG_ADC_READ_VFET_IN_1 0x30 // ADC VFET_IN read register 1 +#define PCA9481_REG_ADC_READ_OVP_OUT_0 0x31 // ADC OVP_OUT read registe 0 +#define PCA9481_REG_ADC_READ_OVP_OUT_1 0x32 // ADC OVP_OUT read registe 1 +#define PCA9481_REG_ADC_READ_BATP_BATN_0 0x33 // ADC BATP_BATN read registe 0 +#define PCA9481_REG_ADC_READ_BATP_BATN_1 0x34 // ADC BATP_BATN read registe 1 +#define PCA9481_REG_ADC_READ_VOUT_0 0x35 // ADC VOUT read registe 0 +#define PCA9481_REG_ADC_READ_VOUT_1 0x36 // ADC VOUT read registe 1 +#define PCA9481_REG_ADC_READ_NTC_0 0x37 // ADC NTC read registe 0 +#define PCA9481_REG_ADC_READ_NTC_1 0x38 // ADC NTC read registe 1 +#define PCA9481_REG_ADC_READ_DIE_TEMP_0 0x39 // ADC Die temperature read registe 0 +#define PCA9481_REG_ADC_READ_DIE_TEMP_1 0x3A // ADC Die temperature read registe 1 +#define PCA9481_REG_ADC_READ_VIN_CURRENT_0 0x3B // ADC VIN current read registe 0 +#define PCA9481_REG_ADC_READ_VIN_CURRENT_1 0x3C // ADC VIN current read registe 1 +#define PCA9481_REG_ADC_READ_I_VBAT_CURRENT_0 0x3D // ADC I_VBAT charge current read registe 0 +#define PCA9481_REG_ADC_READ_I_VBAT_CURRENT_1 0x3E // ADC I_VBAT charge current read registe 1 +#define PCA9481_BIT_ADC_READ_7_0 BITS(7, 0) // ADC bit[7:0] +#define PCA9481_BIT_ADC_READ_11_8 BITS(3, 0) // ADC bit[11:8] + +#define PCA9481_MAX_REGISTER 0x4F + +#define PCA9481_REG_BIT_AI BIT(7) // Auto Increment bit + + +/* Regulation voltage and current */ +#define PCA9481_IIN_REG_STEP 25000 // VIN_REGULATION_CURRENT, input current step, unit - uA +#define PCA9481_IBAT_REG_STEP 50000 // I_VBAT_REGUATION, charge current step, unit - uA +#define PCA9481_VBAT_REG_STEP 5000 // VBAT_REGULATION, battery regulation voltage step, unit - uV + +#define PCA9481_IIN_REG_MAX 6000000 // 6A +#define PCA9481_IBAT_REG_MAX 10000000 // 10A +#define PCA9481_VBAT_REG_MAX 5000000 // 5V + +#define PCA9481_IIN_REG_MIN 500000 // 500mA +#define PCA9481_IBAT_REG_MIN 1000000 // 1000mA +#define PCA9481_VBAT_REG_MIN 3725000 // 3725mV + +#define PCA9481_IIN_REG(_input_current) ((_input_current - PCA9481_IIN_REG_MIN)/PCA9481_IIN_REG_STEP) // input current, unit - uA +#define PCA9481_IBAT_REG(_chg_current) ((_chg_current - PCA9481_IBAT_REG_MIN)/PCA9481_IBAT_REG_STEP) // charge current, unit - uA +#define PCA9481_VBAT_REG(_bat_voltage) ((_bat_voltage - PCA9481_VBAT_REG_MIN)/PCA9481_VBAT_REG_STEP) // battery voltage, unit - uV + +/* VIN OCP current for 1:2 switching and 1:1 reverse mode*/ +#define PCA9481_VIN_OCP_12_11_STEP 100000 // 100mA +#define PCA9481_VIN_OCP_12_11_MIN 500000 // 500mA +#define PCA9481_VIN_OCP_12_11_MAX 2000000 // 2000mA +#define PCA9481_VIN_OCP_12_11(_ocp_current) ((_ocp_current - PCA9481_VIN_OCP_12_11_MIN)/PCA9481_VIN_OCP_12_11_STEP) // ocp current, unit - uA + +/* NTC trigger voltage */ +#define PCA9481_NTC_TRIGGER_VOLTAGE_STEP 15000 // 15mV, unit - uV +#define PCA9481_NTC_TRIGGER_VOLTAGE(_ntc_vol) (_ntc_vol/PCA9481_NTC_TRIGGER_VOLTAGE_STEP) // ntc voltage, unit - uV + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) +#define PCA9481_SEC_DENOM_U_M 1000 // 1000, denominator +#define PCA9481_SEC_FPDO_DC_IV 9 // 9V +#define PCA9481_BATT_WDT_CONTROL_T 30000 // 30s +#endif + +/* IBAT sense location */ +enum { + IBAT_SENSE_R_BOTTOM_SIDE, + IBAT_SENSE_R_TOP_SIDE, +}; + +/* IBAT sense resistor */ +enum { + IBAT_SENSE_R_1mOhm, + IBAT_SENSE_R_2mOhm, + IBAT_SENSE_R_5mOhm, +}; + +/* VIN OV TRACK DELTA */ +enum { + OV_TRACK_DELTA_200mV, + OV_TRACK_DELTA_400mV, + OV_TRACK_DELTA_600mV, + OV_TRACK_DELTA_800mV, +}; + +/* VIN UV TRACK DELTA */ +enum { + UV_TRACK_DELTA_0mV, + UV_TRACK_DELTA_200mV, + UV_TRACK_DELTA_400mV, + UV_TRACK_DELTA_600mV, +}; + +/* VIN UV TRACK DEGLITCH */ +enum { + UV_TRACK_DEGLITCH_21ms, + UV_TRACK_DEGLITCH_8ms, + UV_TRACK_DEGLITCH_2ms, + UV_TRACK_DEGLITCH_1ms, +}; + +/* ADC_AVERAGE_TIMES */ +enum { + ADC_AVG_2sample = 0, + ADC_AVG_4sample, + ADC_AVG_8sample, + ADC_AVG_16sample, +}; + +/* Switching frequency */ +#define PCA9481_FSW_MIN 200 +#define PCA9481_FSW_MAX 1750 +#define PCA9481_FSW_CFG_STEP 50 // 50kHz +#define PCA9481_FSW_CFG(_frequency) ((_frequency - 200)/PCA9481_FSW_CFG_STEP) // switching frequency, unit - kHz + +/* Enable pin polarity selection */ +#define PCA9481_EN_ACTIVE_H 0x00 +#define PCA9481_EN_ACTIVE_L PCA9481_BIT_EN_CFG +#define PCA9481_STANDBY_FORCE PCA9481_BIT_STANDBY_EN +#define PCA9481_STANDBY_DONOT 0 + +/* Operation SC mode */ +#define PCA9481_SC_OP_21 0x0 // 00b: 2:1 Switching Mode +#define PCA9481_SC_OP_12 0x20 // 01b: 1:2 Switching Mode +#define PCA9481_SC_OP_F_11 0x40 // 10b: Forward 1:1 mode +#define PCA9481_SC_OP_R_11 0x60 // 11b: Reverse 1:1 mode + +/* Device current status */ +#define PCA9481_SHUTDOWN_STATE 0x00 // 00b: in shutdown state +#define PCA9481_STANDBY_STATE 0x40 // 01b: in standby state +#define PCA9481_21SW_F11_MODE 0x80 // 10b: 2:1 switching or forward 1:1 mode +#define PCA9481_12SW_R11_MODE 0xC0 // 11b: 1:2 switching or reverse 1:1 mode + +/* ADC Channel */ +enum { + ADCCH_VIN, + ADCCH_VFET_IN, + ADCCH_OVP_OUT, + ADCCH_BATP_BATN, + ADCCH_VOUT, + ADCCH_NTC, + ADCCH_DIE_TEMP, + ADCCH_VIN_CURRENT, + ADCCH_BAT_CURRENT, + ADC_READ_MAX, +}; + +/* ADC step and maximum value */ +#define VIN_STEP 4000 // 4mV(4000uV) LSB, Range(0V ~ 15.36V) +#define VIN_MAX 15360000 // 15.36V(15360mV, 15360000uV) +#define VFET_IN_STEP 5250 // 5.25mV(5250uV) LSB, Range(0V ~ 20V) +#define VFET_IN_MAX 20000000 // 20.0V(20000mV, 20000000uV) +#define OVP_OUT_STEP 4000 // 4mV(4000uV) LSB, Range(0V ~ 15.36V) +#define OVP_OUT_MAX 15360000 // 15.36V(15360mV, 15360000uV) +#define BATP_BATN_STEP 2000 // 2mV(2000uV) LSB, Range(0V ~ 5V) +#define BATP_BATN_MAX 5000000 // 5V(5000mV, 5000000uV) +#define VOUT_STEP 2000 // 2mV(2000uV) LSB, Range(0V ~ 5V) +#define VOUT_MAX 5000000 // 5V(5000mV, 5000000uV) +#define NTC_STEP 1000 // 1mV(1000uV) LSB, Range(0V ~ 3.3V) +#define NTC_MAX 1500000 // 1.5V(1500mV, 1500000uV) +#define DIE_TEMP_STEP 500 // 0.5C LSB, Range(0 ~ 150C) +#define DIE_TEMP_DENOM 1000 // 1000, denominator +#define DIE_TEMP_MAX 150 // 150C +#define VIN_CURRENT_STEP 2000 // 2mA(2000uA) LSB, Range(0A ~ 6.5A) +#define VIN_CURRENT_MAX 6500000 // 6.5A(6500mA, 6500000uA) +#define BAT_CURRENT_STEP 5000 // 5mA(5000uA) LSB, Range(0A ~ 10A) +#define BAT_CURRENT_MAX 10000000 // 10A(10000mA, 10000000uA) + + +/* Timer definition */ +#if defined(CONFIG_SEC_FACTORY) +#define VBATMIN_CHECK_T 0 +#else +#define VBATMIN_CHECK_T 1000 // Vbat min check timer - 1000ms +#endif +#define CCMODE_CHECK_T 2000 // CC mode polling timer - 2000ms +#define CVMODE_CHECK_T 2000 // CV mode polling timer - 2000ms +#define CVMODE_CHECK2_T 1000 // CV mode polling timer2 - 1000ms +#define CVMODE_CHECK3_T 5000 // CV mode polling timer3 for fixed PDO - 5000ms +#define BYPMODE_CHECK_T 10000 // Bypass mode polling timer - 10000ms +#define PDMSG_WAIT_T 200 // PD message waiting time - 200ms +#define ENABLE_DELAY_T 150 // DC Enable waiting time - 150ms +#define PPS_PERIODIC_T 10000 // PPS periodic request message timer - 10000ms +#define UVLO_CHECK_T 1000 // UVLO check timer - 1000ms +#define BYPASS_WAIT_T 200 // Bypass mode waiting time - 200ms +#define INIT_WAKEUP_T 10000 // Initial wakeup timeout - 10000ms +#define DISABLE_DELAY_T 300 // DC Disable waiting time for sw_freq change - 300ms +#define REVERSE_WAIT_T 10 // Reverse mode waiting time - 10ms +#define REVERSE_CHECK_T 5000 // Reverse mode polling timer - 5000ms +#define IIN_CFG_WAIT_T 150 // Input regulation settle time for soft start - 150ms + +/* Battery minimum voltage Threshold for direct charging */ +#define DC_VBAT_MIN 3100000 // 3100000uV +/* Battery minimum voltage threshold for direct charging error */ +#define DC_VBAT_MIN_ERR 3100000 // 3100000uV +/* Input Current Limit default value - Input Current Regulation */ +#define PCA9481_IIN_REG_DFT 3000000 // 3000000uA +/* Input Current Limit offset value - Input Current Regulation */ +#define PCA9481_IIN_REG_OFFSET1 300000 // 300mA +#define PCA9481_IIN_REG_OFFSET2 350000 // 350mA +#define PCA9481_IIN_REG_OFFSET3 400000 // 400mA +#define PCA9481_IIN_REG_OFFSET4 450000 // 450mA +#define PCA9481_IIN_REG_OFFSET5 500000 // 500mA +#define PCA9481_IIN_REG_OFFSET1_TH 2000000 // 2000mA +#define PCA9481_IIN_REG_OFFSET2_TH 3000000 // 3000mA +#define PCA9481_IIN_REG_OFFSET3_TH 4000000 // 4000mA +#define PCA9481_IIN_REG_OFFSET4_TH 4500000 // 4500mA +#define PCA9481_IIN_REG_OFFSET_FPDO 200000 // 200mA - for FPDO +/* Charging Current default value - Charge Current Regulation */ +#define PCA9481_IBAT_REG_DFT 6000000 // 6000000uA +#define PCA9481_IBAT_REG_OFFSET 100000 // 100mA - Software offset for DC algorithm +/* Charging Float Voltage default value - Battery Voltage Regulation */ +#define PCA9481_VBAT_REG_DFT 4460000 // 4350000uV --> 4500mV --> 4460mV + +/* IBAT Sense Resistor default value */ +#define PCA9481_SENSE_R_DFT IBAT_SENSE_R_1mOhm // 1mOhm +/* IBAT Sense Resistor location default value */ +#define PCA9481_SENSE_R_CFG_DFT IBAT_SENSE_R_BOTTOM_SIDE // Bottom side - connect to BATN +/* Switching Frequency default value */ +#define PCA9481_FSW_CFG_DFT 1000 // 1.0MHz(1000kHz) +/* Switching Frequency default value for bypass */ +#define PCA9481_FSW_CFG_BYP_DFT 500 // 500kHz +/* Switching Frequency default value for low current */ +#define PCA9481_FSW_CFG_LOW_DFT 500 // 500kHz +/* NTC_0 threshold voltage default value */ +#define PCA9481_NTC_0_TH_DFT 1110000 // 1.11V(1110000uV) +/* NTC_1 threshold voltage default value */ +#define PCA9481_NTC_1_TH_DFT 495000 // 0.495V(495000uV) + +/* IIN ADC compensation gain */ +#define PCA9481_IIN_ADC_COMP_GAIN 1867 // uA unit +/* IIN ADC compensation offset */ +#define PCA9481_IIN_ADC_COMP_OFFSET -44178 // uA unit + +/* Charging Done Condition */ +#define ICHG_DONE_DFT 1000000 // 1000mA +#define IIN_DONE_DFT 500000 // 500mA + +/* Maximum TA voltage threshold */ +#define TA_MAX_VOL 10200000 // 10200000uV +/* Minimum TA voltage threshold */ +#define TA_MIN_VOL 7000000 // 7000000uV +/* Maximum TA current threshold */ +#define TA_MAX_CUR 4500000 // 4500000uA +/* Minimum TA current threshold */ +#define TA_MIN_CUR 1000000 // 1000000uA - PPS minimum current + +/* Minimum TA voltage threshold in Preset mode */ +#if defined(CONFIG_SEC_FACTORY) +#define TA_MIN_VOL_PRESET 9000000 // 9000000uV +#else +#define TA_MIN_VOL_PRESET 7700000 // 7700000uV +#endif +/* TA voltage offset for the initial TA voltage */ +#define TA_VOL_PRE_OFFSET 500000 // 500000uV +/* Adjust CC mode TA voltage step */ +#if defined(CONFIG_SEC_FACTORY) +#define TA_VOL_STEP_ADJ_CC 80000 // 80000uV +#else +#define TA_VOL_STEP_ADJ_CC 40000 // 40000uV +#endif +/* Pre CC mode TA voltage step */ +#define TA_VOL_STEP_PRE_CC 100000 // 100000uV +/* Pre CV mode TA voltage step */ +#define TA_VOL_STEP_PRE_CV 40000 // 40000uV +/* IIN_CC adc offset for accuracy */ +#define IIN_ADC_OFFSET 20000 // 20000uA +/* IIN_CC compensation offset */ +#define IIN_CC_COMP_OFFSET 50000 // 50000uA +/* IIN_CC compensation offset in Power Limit Mode(Constant Power) TA */ +#define IIN_CC_COMP_OFFSET_CP 20000 // 20000uA +/* TA maximum voltage that can support constant current in Constant Power Mode */ +#define TA_MAX_VOL_CP 10000000 // 9760000uV --> 9800000uV --> 10000000uV +/* maximum retry counter for restarting charging */ +#define MAX_RETRY_CNT 3 // 3times +/* TA IIN tolerance */ +#define TA_IIN_OFFSET 100000 // 100mA +/* TA IIN low tolerance */ +#define TA_IIN_LOW_OFFSET 200000 // 200mA + +#ifdef CONFIG_SEC_FACTORY +/* TA decrement step in CC state for factory test */ +#define TA_VOL_DEC_STEP_CC 60000 // 60mV - for fast test +#endif /* TA current low offset for reducing input current */ + +/* TA current low offset for reducing input current */ +#ifdef CONFIG_SEC_FACTORY +#define TA_CUR_LOW_OFFSET 0 // 0mA - UCT300 does not work CL mode +#else +#define TA_CUR_LOW_OFFSET 200000 // 200mA +#endif +/* TA voltage offset for 1:1 bypass mode */ +#define TA_VOL_OFFSET_1TO1_BYPASS 100000 // 100mV +/* TA voltalge offset for 2:1 bypass mode */ +#define TA_VOL_OFFSET_2TO1_BYPASS 200000 // 200mV +/* Input low current threshold to change switching frequency */ +#define IIN_LOW_TH_SW_FREQ 1100000 // 1100000uA +/* TA voltage offset value for frequency change */ +#define TA_VOL_OFFSET_SW_FREQ 600000 // 600mV + +/* PD Message Voltage and Current Step */ +#define PD_MSG_TA_VOL_STEP 20000 // 20mV +#define PD_MSG_TA_CUR_STEP 50000 // 50mA + +#define DENOM_U_M 1000 // 1000, denominator for change between micro and mili unit + +/* Maximum WCRX voltage threshold */ +#define WCRX_MAX_VOL 10000000 // 10000000uV +/* WCRX voltage Step */ +#define WCRX_VOL_STEP 100000 // 100mV + +/* Switching charger minimum current */ +#define SWCHG_ICL_MIN 100000 // 100mA +#define SWCHG_ICL_NORMAL 3000000 // 3000mA + +/* Step1 vfloat threshold */ +#define STEP1_VFLOAT_THRESHOLD 4200000 // 4200000uV + +/* RCP_DONE Error */ +#define ERROR_DCRCP 99 /* RCP Error - 99 */ + +/* FPDO Charging Done counter */ +#define FPDO_DONE_CNT 3 + +/* TA current decrement ratio in CV transition */ +#define TA_CUR_RATIO_CV_TRANS 95 +/* current ratio denominator in CV transition */ +#define CUR_RATIO_DENOM_CV_TRANS 100 +/* Input current low threshold in CV transition */ +#define IIN_LOW_TH_CV_TRANS 90 + +enum { + WDT_DISABLE, + WDT_ENABLE, +}; + +enum { + WDT_4SEC, + WDT_8SEC, + WDT_16SEC, + WDT_32SEC, +}; + +/* ADC operation mode */ +enum { + AUTO_MODE = 0, + FORCE_SHUTDOWN_MODE, + FORCE_HIBERNATE_MODE, + FORCE_NORMAL_MODE, +}; + +/* Interrupt and Status Register Buffer */ +enum { + REG_DEVICE_0, + REG_DEVICE_1, + REG_DEVICE_2, + REG_DEVICE_3, + REG_CHARGING, + REG_SC_0, + REG_SC_1, + REG_BUFFER_MAX +}; + +/* Direct Charging State */ +enum { + DC_STATE_NO_CHARGING, /* No charging */ + DC_STATE_CHECK_VBAT, /* Check min battery level */ + DC_STATE_PRESET_DC, /* Preset TA voltage/current for the direct charging */ + DC_STATE_CHECK_ACTIVE, /* Check active status before entering Adjust CC mode */ + DC_STATE_ADJUST_CC, /* Adjust CC mode */ + DC_STATE_START_CC, /* Start CC mode */ + DC_STATE_CC_MODE, /* Check CC mode status */ + DC_STATE_START_CV, /* Start CV mode */ + DC_STATE_CV_MODE, /* Check CV mode status */ + DC_STATE_CHARGING_DONE, /* Charging Done */ + DC_STATE_ADJUST_TAVOL, /* Adjust TA voltage to set new TA current under 1000mA input */ + DC_STATE_ADJUST_TACUR, /* Adjust TA current to set new TA current over 1000mA input */ + DC_STATE_BYPASS_MODE, /* Check Bypass mode status */ + DC_STATE_DCMODE_CHANGE, /* DC mode change from Normal to 1:1 or 2:1 bypass */ + DC_STATE_REVERSE_MODE, /* Reverse 1:2 switching or reverse 1:1 bypass */ + DC_STATE_FPDO_CV_MODE, /* Check FPDO CV mode status */ + DC_STATE_MAX, +}; + +/* DC Mode Status */ +enum { + DCMODE_CHG_LOOP, + DCMODE_VFLT_LOOP, + DCMODE_IIN_LOOP, + DCMODE_LOOP_INACTIVE, + DCMODE_CHG_DONE, +}; + +/* Timer ID */ +enum { + TIMER_ID_NONE, + TIMER_VBATMIN_CHECK, + TIMER_PRESET_DC, + TIMER_PRESET_CONFIG, + TIMER_CHECK_ACTIVE, + TIMER_ADJUST_CCMODE, + TIMER_ENTER_CCMODE, + TIMER_CHECK_CCMODE, + TIMER_ENTER_CVMODE, + TIMER_CHECK_CVMODE, + TIMER_PDMSG_SEND, + TIMER_ADJUST_TAVOL, + TIMER_ADJUST_TACUR, + TIMER_CHECK_BYPASSMODE, + TIMER_DCMODE_CHANGE, + TIMER_START_REVERSE, + TIMER_CHECK_REVERSE_ACTIVE, + TIMER_CHECK_REVERSE_MODE, + TIMER_CHECK_FPDOCVMODE, +}; + +/* PD Message Type */ +enum { + PD_MSG_REQUEST_APDO, + PD_MSG_REQUEST_FIXED_PDO, + WCRX_REQUEST_VOLTAGE, +}; + +/* TA increment Type */ +enum { + INC_NONE, /* No increment */ + INC_TA_VOL, /* TA voltage increment */ + INC_TA_CUR, /* TA current increment */ +}; + +/* TA Type for the direct charging */ +enum { + TA_TYPE_UNKNOWN, + TA_TYPE_USBPD, + TA_TYPE_WIRELESS, + TA_TYPE_USBPD_20, /* USBPD 2.0 - fixed PDO */ +}; + +/* TA Control method for the direct charging */ +enum { + TA_CTRL_CL_MODE, + TA_CTRL_CV_MODE, +}; + +/* Direct Charging Mode for the direct charging */ +enum { + CHG_NO_DC_MODE, + CHG_2TO1_DC_MODE, + CHG_4TO1_DC_MODE, +}; + +/* Switching Frequency change request sequence */ +enum { + REQ_SW_FREQ_0, /* No need or frequency change done */ + REQ_SW_FREQ_1, /* Decrease TA voltage */ + REQ_SW_FREQ_2, /* Disable DC */ + REQ_SW_FREQ_3, /* Set switching frequency */ + REQ_SW_FREQ_4, /* Set TA current */ + REQ_SW_FREQ_5, /* Enable DC */ + REQ_SW_FREQ_6, /* Increase TA voltage */ +}; + +/* CV state transition sequence */ +enum { + CV_TRANS_0, /* No need new method */ + CV_TRANS_1, /* Set TA current to 0.95*TA current */ + CV_TRANS_2, /* Decrease TA voltage until IIN_ADC is less than 0.9*IIN_CC */ + CV_TRANS_3, /* Recover TA current to TA target current */ + CV_TRANS_4, /* Increase TA voltage until VBAT_ADC is higher than VFLOAT threshold */ +}; + +/** + * struct pca9481_charger - pca9481 charger instance + * @monitor_wake_lock: lock to enter the suspend mode + * @lock: protects concurrent access to online variables + * @dev: pointer to device + * @regmap: pointer to driver regmap + * @mains: power_supply instance for AC/DC power + * @dc_wq: work queue for the algorithm and monitor timer + * @timer_work: timer work for charging + * @timer_id: timer id for timer_work + * @timer_period: timer period for timer_work + * @pps_work: pps work for PPS periodic time + * @pd: phandle for qualcomm PMI usbpd-phy + * @mains_online: is AC/DC input connected + * @charging_state: direct charging state + * @ret_state: return direct charging state after DC_STATE_ADJUST_TAVOL is done + * @iin_cc: input current for the direct charging in cc mode, uA + * @iin_cfg: input current limit, uA + * @vfloat: floating voltage, uV + * @max_vfloat: maximum float voltage, uV + * @ichg_cfg: charging current limit, uA + * @dc_mode: direct charging mode, normal, 1:1 bypass, or 2:1 bypass mode + * @iin_topoff: input topoff current, uA + * @ta_cur: AC/DC(TA) current, uA + * @ta_vol: AC/DC(TA) voltage, uV + * @ta_objpos: AC/DC(TA) PDO object position + * @ta_target_vol: TA target voltage before any compensation + * @ta_max_cur: TA maximum current of APDO, uA + * @ta_max_vol: TA maximum voltage for the direct charging, uV + * @ta_max_pwr: TA maximum power, uW + * @prev_iin: Previous IIN ADC, uA + * @prev_inc: Previous TA voltage or current increment factor + * @req_new_iin: Request for new input current limit, true or false + * @req_new_vfloat: Request for new vfloat, true or false + * @req_new_dc_mode: Request for new dc mode, true or false + * @new_iin: New request input current limit, uA + * @new_vfloat: New request vfloat, uV + * @new_dc_mode: New request dc mode, normal, 1:1 bypass, or 2:1 bypass mode + * @adc_comp_gain: adc gain for compensation + * @retry_cnt: retry counter for re-starting charging if charging stop happens + * @ta_type: TA type for the direct charging, USBPD TA or Wireless Charger. + * @ta_ctrl: TA control method for the direct charging, Current Limit mode or Constant Voltage mode. + * @chg_mode: charging mode that TA can support for the direct charging, 2:1 or 4:1 mode + * @fsw_cfg: Switching frequency, kHz + * @req_sw_freq: Switching frequency change sequence. + * @rev_mode: reverse mode, 1:2 switching, reverse 1:1, or stop reverse mode. + * @iin_rev: vin_ocp current for 1:2 switching or reverse 1:1 mode + * @prev_vbat: Previous VBAT_ADC in start cv and cv state, uV + * @done_cnt: Charging done counter. + * @cv_trans: CV transition sequence + * @ta_target_cur: TA current before CV transition, uA + * @pdata: pointer to platform data + * @debug_root: debug entry + * @debug_address: debug register address + */ +struct pca9481_charger { + struct wakeup_source *monitor_wake_lock; + struct mutex lock; + struct mutex i2c_lock; + struct device *dev; + struct regmap *regmap; + struct power_supply *mains; + struct workqueue_struct *dc_wq; + struct delayed_work timer_work; + unsigned int timer_id; + unsigned long timer_period; + unsigned long last_update_time; + + struct delayed_work pps_work; +#ifdef CONFIG_USBPD_PHY_QCOM + struct usbpd *pd; +#endif + + bool mains_online; + unsigned int charging_state; + unsigned int ret_state; + + unsigned int iin_cc; + unsigned int iin_cfg; + unsigned int vfloat; + unsigned int max_vfloat; + unsigned int ichg_cfg; + unsigned int iin_topoff; +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + unsigned int fpdo_dc_iin_topoff; + unsigned int fpdo_dc_vnow_topoff; +#endif + unsigned int dc_mode; + + unsigned int ta_cur; + unsigned int ta_vol; + unsigned int ta_objpos; + + unsigned int ta_target_vol; + + unsigned int ta_max_cur; + unsigned int ta_max_vol; + unsigned int ta_max_pwr; + + unsigned int prev_iin; + unsigned int prev_inc; + + bool req_new_iin; + bool req_new_vfloat; + bool req_new_dc_mode; + unsigned int new_iin; + unsigned int new_vfloat; + unsigned int new_dc_mode; + + int adc_comp_gain; + + int retry_cnt; + + int ta_type; + int ta_ctrl; + int chg_mode; + unsigned int fsw_cfg; + unsigned int req_sw_freq; + + bool dec_vfloat; + bool req_enable; + bool enable; + + int rev_mode; + int iin_rev; + + int prev_vbat; + int done_cnt; + + unsigned int cv_trans; + unsigned int ta_target_cur; + + struct pca9481_platform_data *pdata; + + /* debug */ + struct dentry *debug_root; + u32 debug_address; + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + int input_current; + int charging_current; + int float_voltage; + int chg_status; + int health_status; + bool wdt_kick; + + int adc_val[ADC_READ_MAX]; + + unsigned int pdo_index; + unsigned int pdo_max_voltage; + unsigned int pdo_max_current; + + struct delayed_work wdt_control_work; +#endif +}; + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) +extern int sec_pd_select_pps(int num, int ppsVol, int ppsCur); +extern int sec_pd_get_apdo_max_current(unsigned int *pdo_pos, unsigned int taMaxVol, unsigned int *taMaxCur); +#endif + +#endif diff --git a/drivers/battery/common/Kconfig b/drivers/battery/common/Kconfig new file mode 100644 index 000000000000..09b7034e79a6 --- /dev/null +++ b/drivers/battery/common/Kconfig @@ -0,0 +1,363 @@ +config DIRECT_CHARGING + tristate "support for direct charging" + help + Say Y to include support for direct charging + support for direct charging models. + Direct charging models should enable this charging option. + it include some charging scenario for direct charging models. + + +menu "Samsung Battery Common drivers" + +config BATTERY_SAMSUNG + tristate "samsung battery driver" + help + Say Y to include support for samsung battery driver + This battery driver integrated all battery-related functions + To see battery-related functions, + refer to sec_charging_common.h + +config CHARGING_VZWCONCEPT + bool "VZW concept about the charging" + default n + depends on BATTERY_SAMSUNG + help + Say Y here to enable + support for apply the VZW concepts. + VZW models should enable this charging option. + it include some charging scenario for VZW models. + +config STEP_CHARGING + bool "support for step charging" + help + Say Y here to enable + support for step charging. + it could be used with direct charging. + it needs step charging tables. + +config ENABLE_FULL_BY_SOC + bool "make full by soc 100%" + help + default n + Say Y here to enable + support to make full charged by soc 100%. + If this is N in models, + battery common drivers make full by other conditions. + +config SEC_PD + tristate "support for sec pd" + help + Say Y to include support for sec pd control module. + This sec_pd driver integrated all pdo related function. + To see pdo related functions, + refer to sec_pd.h + +config UPDATE_BATTERY_DATA + bool "support for updating battery data" + default n + depends on BATTERY_SAMSUNG && OF + help + Say Y here to enable + support for update battery data. + This integrated load and parsing data functions again. + it need to battery data file for update. + +config BATTERY_CISD + bool "support for cisd" + help + Say Y here to enable + support for CISD. + cisd means cell internal short detection. + it include some other detection. + +config AFC_CHARGER_MODE + bool "afc charging support in sec battery driver" + default n + help + Say Y here to enable + support for sec afc charging support + it includes some AFC charging options and + information about AFC charging. + +config SAMSUNG_BATTERY_ENG_TEST + bool "enable ENG mode for battery test" + default n + help + Say Y to include support for battery test + enable this feature only ENG mode + this featuren must disabled user binary + stability test etc.. + +config SAMSUNG_BATTERY_FACTORY + bool "enable for factory test" + default n + help + Say Y to include support for factory test + enable this feature only factory mode + this featuren must disabled user binary + stability test etc.. + +config USE_POGO + bool "enable pogo charging" + default n + help + Say Y here to enable + support POGO properties. + some models support POGO, + then it make Y. + +config STORE_MODE + bool "enable store mode" + default n + help + Say Y here to enable + support store mode charging concpet. + The LDU or RDU enable this STORE_MODE option, + it include some charging scenario for store. + +config BATTERY_AGE_FORECAST + bool "battery age forecast" + default n + help + Say Y here to enable + support AGE FORECAST functions. + it include some charging scenario for aged batteries. + it need age forecast charging tables. + +config BATTERY_AGE_FORECAST_DETACHABLE + tristate "battery age forecast for detachable" + default n + select BATTERY_AGE_FORECAST + help + Say Y here to enable + support AGE FORECAST functions for detachable model. + it include some charging scenario for aged batteries. + it need age forecast charging tables. + +config BATTERY_AGE_FORECAST_B2B + tristate "battery age forecast for B2B" + default n + depends on BATTERY_AGE_FORECAST + help + Say Y here to enable + support AGE FORECAST functions for B2B. + it include some charging scenario for aged batteries. + it need age forecast charging tables. + +config BATTERY_LOGGING + bool "battery logging" + default n + depends on BATTERY_SAMSUNG + help + Say Y to enable + support for the battery logging feature which + allows of logging battery related information + during power on and power-off charging. As well + as battery dump mechanism for periodically logging + in an external file. + +config ENG_BATTERY_CONCEPT + bool "enable temp block" + default n + help + Say Y here to enable + support CONFIG_ENG_BATTERY_CONCEPT. + It is for only in ENG bianry + USER binary should disalbe this. + +config LIMIT_CHARGING_DURING_CALL + bool "limit charging during call" + default n + help + Say Y here to enable + support limit charging during call. + some models support this for limiting charging current, + then it make Y. + +config TABLET_MODEL_CONCEPT + bool "tablet model concept" + default n + help + Say Y here to enable + do not enable for cellphone models. + tablet models support this for charging, + then it make Y. + +config PD_CHARGER_HV_DISABLE + bool "enable supporing disable high voltage pd charger" + depends on BATTERY_SAMSUNG && I2C + help + Say Y here to enable + supporting disable high voltage pd charger. + some models need this for support options that + not support high voltage. + +config BATTERY_GKI + bool "temporary support for GKI build" + help + Say Y here to enable + support for module build normally. + this is temporary added for prevent build break in module build. + it will be deleted soon. + +config SUPPORT_SHIP_MODE + bool "support ship mode" + default n + help + Say Y here to enable + support to ship mode. + If this is N in models, + not support to ship mode. + +config SUPPORT_HV_CTRL + bool "support for controlling voltage" + default n + help + Say Y here to enable + support for controlling voltage + If this is N in models, + not support to control voltage. + +config NO_BATTERY + bool "support for no battery" + default n + help + Say Y here to enable + support for no battery models to turn off charging + If this is N in models, + not support to no battery. + +config SEC_BATTERY_TEST + bool "KUnit test for sec_battery_test" + depends on SEC_KUNIT + help + Say Y here to enable + support to kunit test. + for kunitest + for sec_battery_test + +config SEC_BATTERY_WC_TEST + bool "KUnit test for sec_battery_wc_test" + depends on SEC_KUNIT + help + Say Y here to enable + support to kunit test. + for kunitest + for sec_battery_wc_test + +config SEC_BATTERY_THERMAL_TEST + bool "KUnit test for sec_battery_thermal_test" + depends on SEC_KUNIT + help + Say Y here to enable + support to kunit test. + for kunitest + for sec_battery_thermal_test + +config SEC_BATTERY_VOTE_TEST + bool "KUnit test for sec_battery_vote_test" + depends on SEC_KUNIT + help + Say Y here to enable + support to kunit test. + for kunitest + for sec_battery_vote_test + +config SEC_CISD_TEST + bool "KUnit test for sec_cisd_test" + depends on SEC_KUNIT + help + Say Y here to enable + support to kunit test. + for kunitest + for sec_cisd_test + +config SEC_ADC_TEST + bool "KUnit test for sec_adc_test" + depends on SEC_KUNIT + help + Say Y here to enable + support to kunit test. + for kunitest + for sec_adc_test + +config SEC_BATTERY_SYSFS_TEST + bool "KUnit test for sec_battery_sysfs_test" + depends on SEC_KUNIT + help + Say Y here to enable + support to kunit test. + for kunitest + for sec_battery_sysfs_test + +config SEC_BATTERY_TTF_TEST + bool "KUnit test for sec_battery_ttf_test" + depends on SEC_KUNIT + help + Say Y here to enable + support to kunit test. + for kunitest + for sec_battery_ttf_test + +config SEC_STEP_CHARGING_TEST + tristate "KUnit test for sec_step_charging_test" + depends on SEC_KUNIT + help + Say Y here to enable + support to kunit test. + for kunitest + for sec_step_charging_test + +config SEC_PD_TEST + bool "KUnit test for sec_pd_test" + depends on SEC_KUNIT + help + Say Y here to enable + support to kunit test. + for kunitest + for sec_pd_test + +config SEC_BATTERY_DT_TEST + bool "KUnit test for sec_battery_dt_test" + depends on SEC_KUNIT + help + Say Y here to enable + support to kunit test. + for kunitest + for sec_pd_test + +config SEC_BATTERY_MISC_TEST + bool "KUnit test for sec_battery_misc_test" + depends on SEC_KUNIT + help + Say Y here to enable + support to kunit test. + for kunitest + for sec_pd_test + +config USB_FACTORY_MODE + bool "enable USB factory mode" + default n + help + Say Y to enable CONFIG_USB_FACTORY_MODE + This feature is used for models that support + factory mode using USB cable instead of Anyway JIG. + Code for this is added in charger and battery driver. + +config BATTERY_SAMSUNG_REBOOT + bool "support for check sec_reboot" + default n + help + Say Y here, + to support for check sec_reboot. + This options for only ARCH_EXYNOS. + If this is Y, checking reboot when power off. + +config SBP_FG + bool "SBP FUELGAUGE" + default n + help + Say Y here to enable + Support for SBP FUELGAUGE. + +endmenu diff --git a/drivers/battery/common/Makefile b/drivers/battery/common/Makefile new file mode 100644 index 000000000000..efc92121be53 --- /dev/null +++ b/drivers/battery/common/Makefile @@ -0,0 +1,44 @@ +obj-$(CONFIG_DIRECT_CHARGING) += sec-direct-charger.o +sec-direct-charger-$(CONFIG_DIRECT_CHARGING) += sb_pass_through.o sec_direct_charger.o + +ccflags-y := -Wformat + +obj-$(CONFIG_SEC_PD) += sec_pd.o +obj-$(CONFIG_BATTERY_SAMSUNG) += sb_wireless.o + +obj-$(CONFIG_BATTERY_SAMSUNG) += sec-battery.o +sec-battery-$(CONFIG_ENG_BATTERY_CONCEPT) += sb_checklist_app.o +sec-battery-$(CONFIG_BATTERY_SAMSUNG) += sec_charging_modprobe.o sec_battery.o sec_battery_vote.o sec_battery_thermal.o sec_battery_sysfs.o sec_battery_dt.o sec_battery_ttf.o sec_adc.o sec_cisd.o sb_full_soc.o +sec-battery-$(CONFIG_STEP_CHARGING) += sec_step_charging.o +sec-battery-$(CONFIG_WIRELESS_AUTH) += sec_battery_misc.o +sec-battery-$(CONFIG_WIRELESS_CHARGING) += sec_battery_wc.o +sec-battery-$(CONFIG_WIRELESS_TX_MODE) += sb_tx.o +sec-battery-$(CONFIG_BATTERY_LOGGING) += battery_logger.o sb_batt_dump.o + +obj-$(CONFIG_UPDATE_BATTERY_DATA) += sec_battery_data.o + +ifeq ($(CONFIG_SEC_KUNIT), y) +ifeq ($(CONFIG_BATTERY_SAMSUNG), m) +GCOV_PROFILE_sec_battery.o := y +GCOV_PROFILE_sec_battery_thermal.o := y +GCOV_PROFILE_sec_battery_vote.o := y +GCOV_PROFILE_sec_adc.o := y +GCOV_PROFILE_sec_battery_sysfs.o := y +GCOV_PROFILE_sec_battery_ttf.o := y +GCOV_PROFILE_sec_battery_dt.o := y +GCOV_PROFILE_sec_cisd.o := y +ifneq ($(CONFIG_WIRELESS_CHARGING), n) +GCOV_PROFILE_sec_battery_wc.o := y +endif +ifneq ($(CONFIG_STEP_CHARGING), n) +GCOV_PROFILE_sec_step_charging.o := y +endif +ifneq ($(CONFIG_WIRELESS_AUTH), n) +GCOV_PROFILE_sec_battery_misc.o := y +endif +endif +GCOV_PROFILE_sec_pd.o := y +ifeq ($(CONFIG_UML), y) +endif +endif +ccflags-y := -Wformat diff --git a/drivers/battery/common/battery_logger.c b/drivers/battery/common/battery_logger.c new file mode 100644 index 000000000000..8b5f3e14b550 --- /dev/null +++ b/drivers/battery/common/battery_logger.c @@ -0,0 +1,227 @@ +/* + * battery_logger.c + * Samsung Mobile Battery Driver + * + * Copyright (C) 2021 Samsung Electronics + * + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0)) +#include +#else +#include +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0)) +#include +#define SEC_TIMESPEC timespec64 +#define SEC_GETTIMEOFDAY ktime_get_real_ts64 +#define SEC_RTC_TIME_TO_TM rtc_time64_to_tm +#else +#include +#define SEC_TIMESPEC timeval +#define SEC_GETTIMEOFDAY do_gettimeofday +#define SEC_RTC_TIME_TO_TM rtc_time_to_tm +#endif + +#include +#include "sec_battery.h" +#include "battery_logger.h" + +#define BATTERYLOG_MAX_STRING_SIZE (1 << 7) /* 128 */ +#define BATTERYLOG_MAX_BUF_SIZE 200 /* 200 */ + +struct batterylog_buf { + unsigned long log_index; + char batstr_buffer[BATTERYLOG_MAX_BUF_SIZE*BATTERYLOG_MAX_STRING_SIZE]; +}; + +struct batterylog_root_str { + struct batterylog_buf *batterylog_buffer; + struct mutex battery_log_lock; + int init; +}; + +static struct batterylog_root_str batterylog_root; + +#if !defined(CONFIG_UML) +static void logger_get_time_of_the_day_in_hr_min_sec(char *tbuf, int len) +{ + struct SEC_TIMESPEC tv; + struct rtc_time tm; + unsigned long local_time; + + /* Format the Log time R#: [hr:min:sec.microsec] */ + SEC_GETTIMEOFDAY(&tv); + /* Convert rtc to local time */ + local_time = (u32)(tv.tv_sec - (sys_tz.tz_minuteswest * 60)); + SEC_RTC_TIME_TO_TM(local_time, &tm); + + scnprintf(tbuf, len, + "[%d-%02d-%02d %02d:%02d:%02d]", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); +} +#endif + +static int batterylog_proc_show(struct seq_file *m, void *v) +{ + struct batterylog_buf *temp_buffer; + + temp_buffer = batterylog_root.batterylog_buffer; + + if (!temp_buffer) + goto err; + pr_info("%s\n", __func__); + + if (sec_bat_get_lpmode()) { + seq_printf(m, + "*****Battery LPM Logs*****\n"); + } else { + seq_printf(m, + "*****Battery Power On Logs*****\n"); + } + + seq_printf(m, "%s", temp_buffer->batstr_buffer); + +err: + return 0; +} + +#if defined(CONFIG_UML) +void store_battery_log(const char *fmt, ...) {} +#else +void store_battery_log(const char *fmt, ...) +{ + unsigned long long tnsec; + unsigned long rem_nsec; + unsigned long target_index; + char *bat_buf; + int string_len, rem_buf; + char temp[BATTERYLOG_MAX_STRING_SIZE]; + va_list ap; + + if (!batterylog_root.init) + return; + + mutex_lock(&batterylog_root.battery_log_lock); + tnsec = local_clock(); + rem_nsec = do_div(tnsec, 1000000000); + + logger_get_time_of_the_day_in_hr_min_sec(temp, BATTERYLOG_MAX_STRING_SIZE); + string_len = strlen(temp); + + /* To give rem buf size to vsnprint so that it can add '\0' at the end of string. */ + rem_buf = BATTERYLOG_MAX_STRING_SIZE - string_len; + + pr_debug("%s string len after storing time = %d, time = %llu , rem temp buf = %d\n", + __func__, string_len, tnsec, rem_buf); + + va_start(ap, fmt); + + /* store upto buff size, data after buff is ignored, hence can't have illegal buff overflow access */ + vsnprintf(temp+string_len, sizeof(char)*rem_buf, fmt, ap); + + va_end(ap); + + target_index = batterylog_root.batterylog_buffer->log_index; + + /* Remaining size of actual storage buffer. -2 is used as last 2 indexs are fixed for '\n' and '\0' */ + rem_buf = BATTERYLOG_MAX_BUF_SIZE*BATTERYLOG_MAX_STRING_SIZE - target_index-2; + + string_len = strlen(temp); + + /* If remaining buff size is less than the string then overwrite from start */ + if (rem_buf < string_len) + target_index = 0; + + bat_buf = &batterylog_root.batterylog_buffer->batstr_buffer[target_index]; + if (bat_buf == NULL) { + pr_err("%s target_buffer error\n", __func__); + goto err; + } + strncpy(bat_buf, temp, string_len); + + /* '\n' Diffrentiator between two stored strings */ + bat_buf[string_len] = '\n'; + + target_index = target_index+string_len+1; + + batterylog_root.batterylog_buffer->log_index = target_index; + +err: + mutex_unlock(&batterylog_root.battery_log_lock); +} +#endif +EXPORT_SYMBOL(store_battery_log); + +static int batterylog_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, batterylog_proc_show, NULL); +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)) +static const struct proc_ops batterylog_proc_fops = { + .proc_open = batterylog_proc_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = single_release, +}; +#else +static const struct file_operations batterylog_proc_fops = { + .open = batterylog_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; +#endif + +int register_batterylog_proc(void) +{ + int ret = 0; + + if (batterylog_root.init) { + pr_err("%s already registered\n", __func__); + goto err; + } + pr_info("%s\n", __func__); + mutex_init(&batterylog_root.battery_log_lock); + + batterylog_root.batterylog_buffer + = kzalloc(sizeof(struct batterylog_buf), GFP_KERNEL); + if (!batterylog_root.batterylog_buffer) { + ret = -ENOMEM; + goto err; + } + pr_info("%s size=%zu\n", __func__, sizeof(struct batterylog_buf)); + proc_create("batterylog", 0, NULL, &batterylog_proc_fops); + batterylog_root.init = 1; + +err: + return ret; +} +EXPORT_SYMBOL(register_batterylog_proc); + +void unregister_batterylog_proc(void) +{ + pr_info("%s\n", __func__); + mutex_destroy(&batterylog_root.battery_log_lock); + kfree(batterylog_root.batterylog_buffer); + batterylog_root.batterylog_buffer = NULL; + remove_proc_entry("batterylog", NULL); + batterylog_root.init = 0; +} +EXPORT_SYMBOL(unregister_batterylog_proc); diff --git a/drivers/battery/common/battery_logger.h b/drivers/battery/common/battery_logger.h new file mode 100644 index 000000000000..8682f49a89be --- /dev/null +++ b/drivers/battery/common/battery_logger.h @@ -0,0 +1,33 @@ +/* + * battery_logger.h + * Samsung Mobile Charger Header + * + * Copyright (C) 2021 Samsung Electronics, 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 __BATTERY_LOGGER_H +#define __BATTERY_LOGGER_H __FILE__ + +#if defined(CONFIG_BATTERY_LOGGING) +extern void store_battery_log(const char *fmt, ...); +extern int register_batterylog_proc(void); +extern void unregister_batterylog_proc(void); +#else +static inline void store_battery_log(const char *fmt, ...) {} +static inline int register_batterylog_proc(void) +{ return 0; } +static inline void unregister_batterylog_proc(void) {} +#endif + +#endif /* __BATTERY_LOGGER_H */ diff --git a/drivers/battery/common/sb_batt_dump.c b/drivers/battery/common/sb_batt_dump.c new file mode 100644 index 000000000000..35425ec679ff --- /dev/null +++ b/drivers/battery/common/sb_batt_dump.c @@ -0,0 +1,201 @@ +/* + * sb_batt_dump.c + * Samsung Mobile Battery Dump Driver + * + * Copyright (C) 2021 Samsung Electronics + * + * + * 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. + * + */ + +#include + +#include +#include + +#include "sec_battery.h" +#include "sec_charging_common.h" +#include "sb_batt_dump.h" + +#define bd_log(str, ...) pr_info("[BATT-DUMP]:%s: "str, __func__, ##__VA_ARGS__) + +#define BD_MODULE_NAME "batt-dump" + +struct sb_bd { + struct notifier_block nb; +}; + +static ssize_t show_attrs(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t store_attrs(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +#define BD_SYSFS_ATTR(_name) \ +{ \ + .attr = {.name = #_name, .mode = 0664}, \ + .show = show_attrs, \ + .store = store_attrs, \ +} + +static struct device_attribute bd_attr[] = { + BD_SYSFS_ATTR(battery_dump), +}; + +enum sb_bd_attrs { + BATTERY_DUMP = 0, +}; + +static ssize_t show_attrs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_battery_info *battery = power_supply_get_drvdata(psy); + const ptrdiff_t offset = attr - bd_attr; + ssize_t count = 0; + union power_supply_propval value = {0, }; + + switch (offset) { + case BATTERY_DUMP: + { + char temp_buf[1024] = {0,}; + int size = 1024; + union power_supply_propval dc_state = {0, }; + + dc_state.strval = "NO_CHARGING"; +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_DIRECT_CHARGER_CHG_STATUS, dc_state); +#endif + + snprintf(temp_buf + strlen(temp_buf), size, + "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s,%s,%s,%d,%s,%d,%d,%lu,0x%x,0x%x,0x%x,%d,%d,", + battery->voltage_now, battery->current_now, + battery->current_max, battery->charging_current, + battery->capacity, + battery->temperature, battery->usb_temp, + battery->chg_temp, battery->wpc_temp, + battery->blkt_temp, battery->lrp, + battery->dchg_temp, battery->sub_bat_temp, + sb_get_bst_str(battery->status), + dc_state.strval, + sb_get_cm_str(battery->charging_mode), + sb_get_hl_str(battery->health), + sb_get_ct_str(battery->cable_type), + battery->muic_cable_type, + sb_get_tz_str(battery->thermal_zone), + is_slate_mode(battery), + battery->store_mode, + (battery->expired_time / 1000), + battery->current_event, + battery->misc_event, + battery->tx_event, +#if defined(CONFIG_WIRELESS_RX_PHM_CTRL) + battery->wc_rx_pdetb_mode, +#else + battery->wc_rx_phm_mode, +#endif + + battery->srccap_transit + ); + size = sizeof(temp_buf) - strlen(temp_buf); + + { + unsigned short vid = 0, pid = 0; + unsigned int xid = 0; + + sec_pd_get_vid_pid(&vid, &pid, &xid); + snprintf(temp_buf+strlen(temp_buf), size, + "%04x,%04x,%08x,", vid, pid, xid); + size = sizeof(temp_buf) - strlen(temp_buf); + } + + snprintf(temp_buf+strlen(temp_buf), size, + "%d,%d,%d,%d,", + battery->voltage_avg_main, battery->voltage_avg_sub, + battery->current_avg_main, battery->current_avg_sub); + size = sizeof(temp_buf) - strlen(temp_buf); + + snprintf(temp_buf+strlen(temp_buf), size, "%d,", battery->batt_cycle); + size = sizeof(temp_buf) - strlen(temp_buf); + + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_EXT_PROP_BATT_DUMP, value); + snprintf(temp_buf+strlen(temp_buf), size, "%s,", value.strval); + size = sizeof(temp_buf) - strlen(temp_buf); + + /* Wireless charging related log added at the end of the string */ + value.intval = SB_WRL_NONE; +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + if (battery->wc_tx_enable) { + value.intval = SB_WRL_TX_MODE; + snprintf(temp_buf+strlen(temp_buf), size, "%d,", SB_WRL_TX_MODE); + } else if (is_wireless_all_type(battery->cable_type)) { + value.intval = SB_WRL_RX_MODE; + snprintf(temp_buf+strlen(temp_buf), size, "%d,", SB_WRL_RX_MODE); + } else + goto skip_wc; + + size = sizeof(temp_buf) - strlen(temp_buf); + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_BATT_DUMP, value); + + snprintf(temp_buf+strlen(temp_buf), size, "%s", value.strval); + size = sizeof(temp_buf) - strlen(temp_buf); +skip_wc: +#endif + if (value.intval == SB_WRL_NONE) { + snprintf(temp_buf+strlen(temp_buf), size, "%d,", 0); + size = sizeof(temp_buf) - strlen(temp_buf); + } + + count += scnprintf(buf + count, PAGE_SIZE - count, "%s\n", temp_buf); + } + break; + default: + break; + } + + return count; +} + +static ssize_t store_attrs(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + const ptrdiff_t offset = attr - bd_attr; + + switch (offset) { + case BATTERY_DUMP: + break; + default: + break; + } + + return count; +} + +static int sb_noti_handler(struct notifier_block *nb, unsigned long action, void *data) +{ + return 0; +} + +int sb_bd_init(void) +{ + struct sb_bd *bd; + int ret = 0; + + bd = kzalloc(sizeof(struct sb_bd), GFP_KERNEL); + if (!bd) + return -ENOMEM; + + ret = sb_sysfs_add_attrs(BD_MODULE_NAME, bd, bd_attr, ARRAY_SIZE(bd_attr)); + bd_log("sb_sysfs_add_attrs ret = %s\n", (ret) ? "fail" : "success"); + + ret = sb_notify_register(&bd->nb, sb_noti_handler, BD_MODULE_NAME, SB_DEV_MODULE); + bd_log("sb_notify_register ret = %s\n", (ret) ? "fail" : "success"); + + return ret; +} +EXPORT_SYMBOL(sb_bd_init); diff --git a/drivers/battery/common/sb_batt_dump.h b/drivers/battery/common/sb_batt_dump.h new file mode 100644 index 000000000000..2dca7fb21c96 --- /dev/null +++ b/drivers/battery/common/sb_batt_dump.h @@ -0,0 +1,36 @@ +/* + * sb_batt_dump.h + * Samsung Mobile Battery Dump Header + * + * Copyright (C) 2021 Samsung Electronics, 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 __SB_BATT_DUMP_H +#define __SB_BATT_DUMP_H __FILE__ + +#include + +struct bd_pt; +struct device; +enum power_supply_property; +union power_supply_propval; + +#if defined(CONFIG_BATTERY_LOGGING) +int sb_bd_init(void); +#else +static inline int sb_bd_init(void) +{ return 0; } +#endif + +#endif /* __SB_BATT_DUMP_H */ + diff --git a/drivers/battery/common/sb_checklist_app.c b/drivers/battery/common/sb_checklist_app.c new file mode 100644 index 000000000000..f64d278ace70 --- /dev/null +++ b/drivers/battery/common/sb_checklist_app.c @@ -0,0 +1,524 @@ +/* + * sec_checklist_app_sysfs.c + * Samsung Mobile Battery Driver + * + * Copyright (C) 2021 Samsung Electronics + * + * + * 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. + */ + +#include "sec_battery.h" +#include "sb_checklist_app.h" +#include +#include + +#define ca_log(str, ...) pr_info("[CHECKLIST-APP]:%s: "str, __func__, ##__VA_ARGS__) + +struct sb_ca { + const char *name; + struct notifier_block nb; +}; + +int char_to_int(char *s) +{ + int num = 0; + int sign = 1; + + do { + if (*s == '-') + sign *= -1; + + else if (*s >= '0' && *s <= '9') + num = (num * 10) + (*s - '0'); + + else if (num > 0) + break; + } while (*s++); + + return (num * sign); +} + +void get_dts_property(const struct device_node *np, const char *name, + char *buf, unsigned int *p_size, int *i, int value) +{ + bool check = of_property_read_bool(np, name); + + if (check) + *i += scnprintf(buf + *i, *p_size - *i, "%d ", value); +} + +void store_wire_menu(struct sec_battery_info *battery, int tc, int y) +{ + if (tc == OVERHEATLIMIT_THRESH) { + battery->overheatlimit_threshold = y; + } else if (tc == OVERHEATLIMIT_RECOVERY) { + battery->overheatlimit_recovery = y; + } else if (tc == WIRE_WARM_OVERHEAT_THRESH) { + battery->pdata->wire_warm_overheat_thresh = y; + } else if (tc == WIRE_NORMAL_WARM_THRESH) { + battery->pdata->wire_normal_warm_thresh = y; + } else if (tc == WIRE_COOL1_NORMAL_THRESH) { + battery->pdata->wire_cool1_normal_thresh = y; + } else if (tc == WIRE_COOL2_COOL1_THRESH) { + battery->pdata->wire_cool2_cool1_thresh = y; + } else if (tc == WIRE_COOL3_COOL2_THRESH) { + battery->pdata->wire_cool3_cool2_thresh = y; + } else if (tc == WIRE_COLD_COOL3_THRESH) { + battery->pdata->wire_cold_cool3_thresh = y; + } else if (tc == WIRE_WARM_CURRENT) { + battery->pdata->wire_warm_current + = y > battery->pdata->max_charging_current ? + battery->pdata->max_charging_current : y; + } else if (tc == WIRE_COOL1_CURRENT) { + battery->pdata->wire_cool1_current + = y > battery->pdata->max_charging_current ? + battery->pdata->max_charging_current : y; + } else if (tc == WIRE_COOL2_CURRENT) { + battery->pdata->wire_cool2_current + = y > battery->pdata->max_charging_current ? + battery->pdata->max_charging_current : y; + } else if (tc == WIRE_COOL3_CURRENT) { + battery->pdata->wire_cool3_current + = y > battery->pdata->max_charging_current ? + battery->pdata->max_charging_current : y; + } +} + +void store_wireless_menu(struct sec_battery_info *battery, int tc, int y) +{ + if (tc == OVERHEATLIMIT_THRESH) { + battery->overheatlimit_threshold = y; + } else if (tc == OVERHEATLIMIT_RECOVERY) { + battery->overheatlimit_recovery = y; + } else if (tc == WIRELESS_WARM_OVERHEAT_THRESH) { + battery->pdata->wireless_warm_overheat_thresh = y; + } else if (tc == WIRELESS_NORMAL_WARM_THRESH) { + battery->pdata->wireless_normal_warm_thresh = y; + } else if (tc == WIRELESS_COOL1_NORMAL_THRESH) { + battery->pdata->wireless_cool1_normal_thresh = y; + } else if (tc == WIRELESS_COOL2_COOL1_THRESH) { + battery->pdata->wireless_cool2_cool1_thresh = y; + } else if (tc == WIRELESS_COOL3_COOL2_THRESH) { + battery->pdata->wireless_cool3_cool2_thresh = y; + } else if (tc == WIRELESS_COLD_COOL3_THRESH) { + battery->pdata->wireless_cold_cool3_thresh = y; + } else if (tc == WIRELESS_WARM_CURRENT) { + battery->pdata->wireless_warm_current + = y > battery->pdata->max_charging_current ? + battery->pdata->max_charging_current : y; + } else if (tc == WIRELESS_COOL1_CURRENT) { + battery->pdata->wireless_cool1_current + = y > battery->pdata->max_charging_current ? + battery->pdata->max_charging_current : y; + } else if (tc == WIRELESS_COOL2_CURRENT) { + battery->pdata->wireless_cool2_current + = y > battery->pdata->max_charging_current ? + battery->pdata->max_charging_current : y; + } else if (tc == WIRELESS_COOL3_CURRENT) { + battery->pdata->wireless_cool3_current + = y > battery->pdata->max_charging_current + ? battery->pdata->max_charging_current : y; + } else if (tc == TX_HIGH_THRESHOLD) { + battery->pdata->tx_high_threshold = y; + } else if (tc == TX_HIGH_THRESHOLD_RECOVERY) { + battery->pdata->tx_high_recovery = y; + } else if (tc == TX_LOW_THRESHOLD) { + battery->pdata->tx_low_threshold = y; + } else if (tc == TX_LOW_THRESHOLD_RECOVERY) { + battery->pdata->tx_low_recovery = y; + } +} +#if defined(CONFIG_STEP_CHARGING) +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) +void store_dc_step_charging_menu(struct sec_battery_info *battery, int tc, + char val[MAX_STEP_CHG_STEP + 2][MAX_STEP_CHG_STEP + 2]) +{ + int res, i; + unsigned int dc_step_chg_type = 0; + + for (i = 0; i < battery->dc_step_chg_step; i++) + dc_step_chg_type |= battery->dc_step_chg_type[i]; + + if (tc == DCHG_STEP_CHG_COND_VOL) { + if (dc_step_chg_type & STEP_CHARGING_CONDITION_VOLTAGE) { + for (i = 0; i < battery->dc_step_chg_step; i++) { + res = char_to_int(val[i + 2]); + battery->pdata->dc_step_chg_cond_vol[battery->pdata->age_step][i] = res; + } + } + } else if (tc == DCHG_STEP_CHG_COND_SOC) { + if (dc_step_chg_type & STEP_CHARGING_CONDITION_SOC || + dc_step_chg_type & STEP_CHARGING_CONDITION_SOC_INIT_ONLY) { + for (i = 0; i < battery->dc_step_chg_step; i++) { + res = char_to_int(val[i + 2]); + battery->pdata->dc_step_chg_cond_soc[battery->pdata->age_step][i] = res; + } + } + } else if (tc == DCHG_STEP_CHG_VAL_VFLOAT) { + if (dc_step_chg_type & STEP_CHARGING_CONDITION_FLOAT_VOLTAGE) { + for (i = 0; i < battery->dc_step_chg_step; i++) { + res = char_to_int(val[i + 2]); + battery->pdata->dc_step_chg_val_vfloat[battery->pdata->age_step][i] = res; + } + } + } else if (tc == DCHG_STEP_CHG_VAL_IOUT) { + for (i = 0; i < battery->dc_step_chg_step; i++) { + res = char_to_int(val[i + 2]); + battery->pdata->dc_step_chg_val_iout[battery->pdata->age_step][i] = res; + } + if (dc_step_chg_type & STEP_CHARGING_CONDITION_INPUT_CURRENT) { + for (i = 0; i < (battery->dc_step_chg_step - 1); i++) { + battery->pdata->dc_step_chg_cond_iin[i] = + battery->pdata->dc_step_chg_val_iout[battery->pdata->age_step][i+1] / 2; + ca_log("Condition Iin [step %d] %dmA", + i, battery->pdata->dc_step_chg_cond_iin[i]); + } + } + } +} +#endif +void store_step_charging_menu(struct sec_battery_info *battery, int tc, + char val[MAX_STEP_CHG_STEP + 2][MAX_STEP_CHG_STEP + 2]) +{ + int res, i; + + if (tc == STEP_CHG_COND) { + for (i = 0; i < battery->step_chg_step; i++) { + res = char_to_int(val[i + 2]); + battery->pdata->step_chg_cond[battery->pdata->age_step][i] = res; + } + } else if (tc == STEP_CHG_COND_CURR) { + for (i = 0; i < battery->step_chg_step; i++) { + res = char_to_int(val[i + 2]); + battery->pdata->step_chg_cond_curr[i] = res; + } + } else if (tc == STEP_CHG_CURR) { + for (i = 0; i < battery->step_chg_step; i++) { + res = char_to_int(val[i + 2]); + battery->pdata->step_chg_curr[battery->pdata->age_step][i] = res; + } + } else if (tc == STEP_CHG_VFLOAT) { + for (i = 0; i < battery->step_chg_step; i++) { + res = char_to_int(val[i + 2]); + battery->pdata->step_chg_vfloat[battery->pdata->age_step][i] = res; + } + } +} +#endif + +void store_others_menu(struct sec_battery_info *battery, int tc, int y) +{ + if (tc == CHG_HIGH_TEMP) + battery->pdata->chg_high_temp = y; + else if (tc == CHG_HIGH_TEMP_RECOVERY) + battery->pdata->chg_high_temp_recovery = y; + else if (tc == CHG_INPUT_LIMIT_CURRENT) + battery->pdata->chg_input_limit_current = y; + else if (tc == CHG_CHARGING_LIMIT_CURRENT) + battery->pdata->chg_charging_limit_current = y; + else if (tc == MIX_HIGH_TEMP) + battery->pdata->mix_high_temp = y; + else if (tc == MIX_HIGH_CHG_TEMP) + battery->pdata->mix_high_chg_temp = y; + else if (tc == MIX_HIGH_TEMP_RECOVERY) + battery->pdata->mix_high_temp_recovery = y; +} + +int show_battery_checklist_app_values(struct sec_battery_info *battery, char *buf, unsigned int p_size) +{ + int i = 0; + struct device_node *np; +#if defined(CONFIG_STEP_CHARGING) + int j = 0; + bool check; + unsigned int dc_step_chg_type = 0; +#endif + + np = of_find_node_by_name(NULL, "battery"); + if (!np) { + pr_err("%s : battery node is NULL\n", __func__); + } else { + get_dts_property(np, "battery,overheatlimit_threshold", + buf, &p_size, &i, battery->overheatlimit_threshold); + get_dts_property(np, "battery,overheatlimit_recovery", + buf, &p_size, &i, battery->overheatlimit_recovery); + get_dts_property(np, "battery,wire_warm_overheat_thresh", + buf, &p_size, &i, battery->pdata->wire_warm_overheat_thresh); + get_dts_property(np, "battery,wire_normal_warm_thresh", + buf, &p_size, &i, battery->pdata->wire_normal_warm_thresh); + get_dts_property(np, "battery,wire_cool1_normal_thresh", + buf, &p_size, &i, battery->pdata->wire_cool1_normal_thresh); + get_dts_property(np, "battery,wire_cool2_cool1_thresh", + buf, &p_size, &i, battery->pdata->wire_cool2_cool1_thresh); + get_dts_property(np, "battery,wire_cool3_cool2_thresh", + buf, &p_size, &i, battery->pdata->wire_cool3_cool2_thresh); + get_dts_property(np, "battery,wire_cold_cool3_thresh", + buf, &p_size, &i, battery->pdata->wire_cold_cool3_thresh); + get_dts_property(np, "battery,wire_warm_current", + buf, &p_size, &i, battery->pdata->wire_warm_current); + get_dts_property(np, "battery,wire_cool1_current", + buf, &p_size, &i, battery->pdata->wire_cool1_current); + get_dts_property(np, "battery,wire_cool2_current", + buf, &p_size, &i, battery->pdata->wire_cool2_current); + get_dts_property(np, "battery,wire_cool3_current", + buf, &p_size, &i, battery->pdata->wire_cool3_current); + get_dts_property(np, "battery,wireless_warm_overheat_thresh", + buf, &p_size, &i, battery->pdata->wireless_warm_overheat_thresh); + get_dts_property(np, "battery,wireless_normal_warm_thresh", + buf, &p_size, &i, battery->pdata->wireless_normal_warm_thresh); + get_dts_property(np, "battery,wireless_cool1_normal_thresh", + buf, &p_size, &i, battery->pdata->wireless_cool1_normal_thresh); + get_dts_property(np, "battery,wireless_cool2_cool1_thresh", + buf, &p_size, &i, battery->pdata->wireless_cool2_cool1_thresh); + get_dts_property(np, "battery,wireless_cool3_cool2_thresh", + buf, &p_size, &i, battery->pdata->wireless_cool3_cool2_thresh); + get_dts_property(np, "battery,wireless_cold_cool3_thresh", + buf, &p_size, &i, battery->pdata->wireless_cold_cool3_thresh); + get_dts_property(np, "battery,wireless_warm_current", + buf, &p_size, &i, battery->pdata->wireless_warm_current); + get_dts_property(np, "battery,wireless_cool1_current", + buf, &p_size, &i, battery->pdata->wireless_cool1_current); + get_dts_property(np, "battery,wireless_cool2_current", + buf, &p_size, &i, battery->pdata->wireless_cool2_current); + get_dts_property(np, "battery,wireless_cool3_current", + buf, &p_size, &i, battery->pdata->wireless_cool3_current); + get_dts_property(np, "battery,tx_high_threshold", + buf, &p_size, &i, battery->pdata->tx_high_threshold); + get_dts_property(np, "battery,tx_high_recovery", + buf, &p_size, &i, battery->pdata->tx_high_recovery); + get_dts_property(np, "battery,tx_low_threshold", + buf, &p_size, &i, battery->pdata->tx_low_threshold); + get_dts_property(np, "battery,tx_low_recovery", + buf, &p_size, &i, battery->pdata->tx_low_recovery); + get_dts_property(np, "battery,chg_high_temp", + buf, &p_size, &i, battery->pdata->chg_high_temp); + get_dts_property(np, "battery,chg_high_temp_recovery", + buf, &p_size, &i, battery->pdata->chg_high_temp_recovery); + get_dts_property(np, "battery,chg_input_limit_current", + buf, &p_size, &i, battery->pdata->chg_input_limit_current); + get_dts_property(np, "battery,chg_charging_limit_current", + buf, &p_size, &i, battery->pdata->chg_charging_limit_current); + get_dts_property(np, "battery,mix_high_temp", + buf, &p_size, &i, battery->pdata->mix_high_temp); + get_dts_property(np, "battery,mix_high_chg_temp", + buf, &p_size, &i, battery->pdata->mix_high_chg_temp); + get_dts_property(np, "battery,mix_high_temp_recovery", + buf, &p_size, &i, battery->pdata->mix_high_temp_recovery); + +#if defined(CONFIG_STEP_CHARGING) + for (j = 0; j < battery->dc_step_chg_step; j++) + dc_step_chg_type |= battery->dc_step_chg_type[j]; + + if (dc_step_chg_type & STEP_CHARGING_CONDITION_VOLTAGE) { + check = of_property_read_bool(np, "battery,dc_step_chg_cond_vol"); + if (check) { + for (j = 0; j < battery->dc_step_chg_step; j++) + i += scnprintf(buf + i, p_size - i, "%d ", + battery->pdata->dc_step_chg_cond_vol[battery->pdata->age_step][j]); + } + } else { + check = of_property_read_bool(np, "battery,dc_step_chg_cond_vol"); + if (check) { + for (j = 0; j < battery->dc_step_chg_step; j++) + i += scnprintf(buf + i, p_size - i, "%d ", -1); + } + } + + if (dc_step_chg_type & STEP_CHARGING_CONDITION_SOC || + dc_step_chg_type & STEP_CHARGING_CONDITION_SOC_INIT_ONLY) { + check = of_property_read_bool(np, "battery,dc_step_chg_cond_soc"); + if (check) { + for (j = 0; j < battery->dc_step_chg_step; j++) + i += scnprintf(buf + i, p_size - i, "%d ", + battery->pdata->dc_step_chg_cond_soc[battery->pdata->age_step][j]); + } + } else { + check = of_property_read_bool(np, "battery,dc_step_chg_cond_soc"); + if (check) { + for (j = 0; j < battery->dc_step_chg_step; j++) + i += scnprintf(buf + i, p_size - i, "%d ", -1); + } + } + + if (dc_step_chg_type & STEP_CHARGING_CONDITION_FLOAT_VOLTAGE) { + check = of_property_read_bool(np, "battery,dc_step_chg_val_vfloat"); + if (check) { + for (j = 0; j < battery->dc_step_chg_step; j++) + i += scnprintf(buf + i, p_size - i, "%d ", + battery->pdata->dc_step_chg_val_vfloat[battery->pdata->age_step][j]); + } + } else { + check = of_property_read_bool(np, "battery,dc_step_chg_val_vfloat"); + if (check) { + for (j = 0; j < battery->dc_step_chg_step; j++) + i += scnprintf(buf + i, p_size - i, "%d ", -1); + } + } + + check = of_property_read_bool(np, "battery,dc_step_chg_val_iout"); + if (check) { + for (j = 0; j < battery->dc_step_chg_step; j++) + i += scnprintf(buf + i, p_size - i, "%d ", + battery->pdata->dc_step_chg_val_iout[battery->pdata->age_step][j]); + } + + check = of_property_read_bool(np, "battery,step_chg_cond"); + if (check) { + for (j = 0; j < battery->step_chg_step; j++) + i += scnprintf(buf + i, p_size - i, "%d ", + battery->pdata->step_chg_cond[battery->pdata->age_step][j]); + } + + + check = of_property_read_bool(np, "battery,step_chg_cond_curr"); + if (check) { + for (j = 0; j < battery->step_chg_step; j++) + i += scnprintf(buf + i, p_size - i, "%d ", + battery->pdata->step_chg_cond_curr[j]); + } + + check = of_property_read_bool(np, "battery,step_chg_curr"); + if (check) { + for (j = 0; j < battery->step_chg_step; j++) + i += scnprintf(buf + i, p_size - i, "%d ", + battery->pdata->step_chg_curr[battery->pdata->age_step][j]); + } + + check = of_property_read_bool(np, "battery,step_chg_vfloat"); + if (check) { + for (j = 0; j < battery->step_chg_step; j++) + i += scnprintf(buf + i, p_size - i, "%d ", + battery->pdata->step_chg_vfloat[battery->pdata->age_step][j]); + } +#endif + } + + return i; +} + +void store_battery_checklist_app_values(struct sec_battery_info *battery, const char *buf) +{ + + char type = buf[0]; + int i, j, cnt; + int tc, y; + char val[MAX_STEP_CHG_STEP + 2][MAX_STEP_CHG_STEP + 2]; + + cnt = i = j = 0; + for (i = 0; buf[i]; i++) { + if (buf[i] == ' ') { + val[cnt][j] = 0; + cnt++; + j = 0; + continue; + } + val[cnt][j] = buf[i]; + j++; + } + val[cnt][j] = 0; + + tc = char_to_int(val[1]); + y = char_to_int(val[2]); + + switch (type) { + case 'w': //wire menu + store_wire_menu(battery, tc, y); + break; + case 'l': //wireless menu + store_wireless_menu(battery, tc, y); + break; +#if defined(CONFIG_STEP_CHARGING) +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + case 'd': //dc step charging menu + store_dc_step_charging_menu(battery, tc, val); + break; +#endif + case 's': //step charging menu + store_step_charging_menu(battery, tc, val); + break; +#endif + case 'o': //others (mix temp, chg_temp, dchg temp) + store_others_menu(battery, tc, y); + break; + } +} + +static ssize_t show_attrs(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t store_attrs(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +#define CA_SYSFS_ATTR(_name) \ +{ \ + .attr = {.name = #_name, .mode = 0664}, \ + .show = show_attrs, \ + .store = store_attrs, \ +} + +static struct device_attribute ca_attr[] = { + CA_SYSFS_ATTR(batt_checklist_app), +}; + +enum sb_ca_attrs { + BATT_CHECKLIST_APP = 0, +}; + +static ssize_t show_attrs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_battery_info *battery = power_supply_get_drvdata(psy); + const ptrdiff_t offset = attr - ca_attr; + ssize_t count = 0; + + switch (offset) { + case BATT_CHECKLIST_APP: + count = show_battery_checklist_app_values(battery, buf, PAGE_SIZE); + break; + default: + break; + } + + return count; +} + +static ssize_t store_attrs(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_battery_info *battery = power_supply_get_drvdata(psy); + const ptrdiff_t offset = attr - ca_attr; + + switch (offset) { + case BATT_CHECKLIST_APP: + store_battery_checklist_app_values(battery, buf); + break; + default: + break; + } + return count; +} + +static int sb_noti_handler(struct notifier_block *nb, unsigned long action, void *data) +{ + return 0; +} + +void sb_ca_init(struct device *parent) +{ + struct sb_ca *ca; + int ret = 0; + + ca = kzalloc(sizeof(struct sb_ca), GFP_KERNEL); + ca->name = "sb-ca"; + ret = sb_sysfs_add_attrs(ca->name, ca, ca_attr, ARRAY_SIZE(ca_attr)); + ca_log("sb_sysfs_add_attrs ret = %s\n", (ret) ? "fail" : "success"); + + ret = sb_notify_register(&ca->nb, sb_noti_handler, ca->name, SB_DEV_MODULE); + ca_log("sb_notify_register ret = %s\n", (ret) ? "fail" : "success"); + +} +EXPORT_SYMBOL(sb_ca_init); diff --git a/drivers/battery/common/sb_checklist_app.h b/drivers/battery/common/sb_checklist_app.h new file mode 100644 index 000000000000..198f86054e96 --- /dev/null +++ b/drivers/battery/common/sb_checklist_app.h @@ -0,0 +1,98 @@ +/* + * sb_checklist_app.h + * Samsung Mobile + * + * Copyright (C) 2021 Samsung Electronics, 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 __SB_CHECLIST_APP_H +#define __SB_CHECLIST_APP_H __FILE__ + +#define STEP_CHARGING_CONDITION_VOLTAGE 0x01 +#define STEP_CHARGING_CONDITION_SOC 0x02 +#define STEP_CHARGING_CONDITION_CHARGE_POWER 0x04 +#define STEP_CHARGING_CONDITION_ONLINE 0x08 +#define STEP_CHARGING_CONDITION_CURRENT_NOW 0x10 +#define STEP_CHARGING_CONDITION_FLOAT_VOLTAGE 0x20 +#define STEP_CHARGING_CONDITION_INPUT_CURRENT 0x40 +#define STEP_CHARGING_CONDITION_SOC_INIT_ONLY 0x80 /* use this to consider SOC to decide starting step only */ + +#define STEP_CHARGING_CONDITION_DC_INIT (STEP_CHARGING_CONDITION_VOLTAGE |\ + STEP_CHARGING_CONDITION_SOC |\ + STEP_CHARGING_CONDITION_SOC_INIT_ONLY) +//need to update above values if sec_step_charging.c file update +#define MAX_STEP_CHG_STEP 3 +//wire menu +#define OVERHEATLIMIT_THRESH 0 +#define OVERHEATLIMIT_RECOVERY 1 +#define WIRE_WARM_OVERHEAT_THRESH 2 +#define WIRE_NORMAL_WARM_THRESH 3 +#define WIRE_COOL1_NORMAL_THRESH 4 +#define WIRE_COOL2_COOL1_THRESH 5 +#define WIRE_COOL3_COOL2_THRESH 6 +#define WIRE_COLD_COOL3_THRESH 7 +#define WIRE_WARM_CURRENT 8 +#define WIRE_COOL1_CURRENT 9 +#define WIRE_COOL2_CURRENT 10 +#define WIRE_COOL3_CURRENT 11 + +//wireless menu +#define OVERHEATLIMIT_THRESH 0 +#define OVERHEATLIMIT_RECOVERY 1 +#define WIRELESS_WARM_OVERHEAT_THRESH 2 +#define WIRELESS_NORMAL_WARM_THRESH 3 +#define WIRELESS_COOL1_NORMAL_THRESH 4 +#define WIRELESS_COOL2_COOL1_THRESH 5 +#define WIRELESS_COOL3_COOL2_THRESH 6 +#define WIRELESS_COLD_COOL3_THRESH 7 +#define WIRELESS_WARM_CURRENT 8 +#define WIRELESS_COOL1_CURRENT 9 +#define WIRELESS_COOL2_CURRENT 10 +#define WIRELESS_COOL3_CURRENT 11 +#define TX_HIGH_THRESHOLD 12 +#define TX_HIGH_THRESHOLD_RECOVERY 13 +#define TX_LOW_THRESHOLD 14 +#define TX_LOW_THRESHOLD_RECOVERY 15 + +//dchg step charging +#define DCHG_STEP_CHG_COND_VOL 1 +#define DCHG_STEP_CHG_COND_SOC 2 +#define DCHG_STEP_CHG_VAL_VFLOAT 3 +#define DCHG_STEP_CHG_VAL_IOUT 4 +//step charging menu +#define STEP_CHG_COND 6 +#define STEP_CHG_COND_CURR 7 +#define STEP_CHG_CURR 8 +#define STEP_CHG_VFLOAT 9 + +//others menu: mix temp, chg temp, dchg temp +#define CHG_HIGH_TEMP 0 +#define CHG_HIGH_TEMP_RECOVERY 1 +#define CHG_INPUT_LIMIT_CURRENT 2 +#define CHG_CHARGING_LIMIT_CURRENT 3 +#define MIX_HIGH_TEMP 4 +#define MIX_HIGH_CHG_TEMP 5 +#define MIX_HIGH_TEMP_RECOVERY 6 +#define DCHG_HIGH_TEMP 7 +#define DCHG_HIGH_TEMP_RECOVERY 8 +#define DCHG_HIGH_BATT_TEMP 9 +#define DCHG_HIGH_BATT_TEMP_RECOVERY 10 +#define DCHG_INPUT_LIMIT_CURRENT 11 +#define DCHG_CHARGING_BATT_TEMP_RECOVERY 12 + +#if defined(CONFIG_ENG_BATTERY_CONCEPT) +extern void sb_ca_init(struct device *parent); +#else +static inline void sb_ca_init(struct device *parent) {} +#endif +#endif /* __SB_CHECLIST_APP_H */ diff --git a/drivers/battery/common/sb_full_soc.c b/drivers/battery/common/sb_full_soc.c new file mode 100644 index 000000000000..4c444f60b964 --- /dev/null +++ b/drivers/battery/common/sb_full_soc.c @@ -0,0 +1,568 @@ +/* + * sb_full_soc.c + * Samsung Mobile Battery Full SoC Module + * + * Copyright (C) 2023 Samsung Electronics + * + * + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#include "sb_full_soc.h" +#include "sec_battery.h" +#include "battery_logger.h" + +struct sb_full_soc { + int full_capacity; + unsigned int full_cap_event; + + bool is_eu_eco_rechg; + bool eu_eco_rechg_state; + sec_battery_recharge_condition_t old_recharge_condition_type; + unsigned int old_recharge_condition_soc; + + bool eu_eco_chg_done; + + struct mutex lock; + struct wakeup_source *ws; + struct delayed_work eu_eco_work; + + struct sec_battery_info *battery; +}; + +enum { + SB_FULL_CAP_EVENT_NONE = 0, + SB_FULL_CAP_EVENT_HIGHSOC, + SB_FULL_CAP_EVENT_SLEEP, + SB_FULL_CAP_EVENT_OPTION, +}; + +#define MAX_CAP_EVENT_STR 16 +#define DEF_ECO_RECHG_DIF 5 +#define DEF_RECHG_SOC_DIF 2 + +static int conv_full_cap_event_value(const char *str) +{ + if (str == NULL) + return SB_FULL_CAP_EVENT_NONE; + + if (!strncmp(str, "HIGHSOC", MAX_CAP_EVENT_STR)) + return SB_FULL_CAP_EVENT_HIGHSOC; + + if (!strncmp(str, "SLEEP", MAX_CAP_EVENT_STR)) + return SB_FULL_CAP_EVENT_SLEEP; + + if (!strncmp(str, "OPTION", MAX_CAP_EVENT_STR)) + return SB_FULL_CAP_EVENT_OPTION; + + return SB_FULL_CAP_EVENT_NONE; +} + +static const char *conv_full_cap_str(unsigned int val) +{ + switch (val) { + case SB_FULL_CAP_EVENT_HIGHSOC: + return "HIGHSOC"; + case SB_FULL_CAP_EVENT_SLEEP: + return "SLEEP"; + case SB_FULL_CAP_EVENT_OPTION: + return "OPTION"; + } + + return "NONE"; +} + +int get_full_capacity(struct sb_full_soc *fs) +{ + if (fs == NULL) + return 100; + + return fs->full_capacity; +} +EXPORT_SYMBOL(get_full_capacity); + +static void set_full_capacity(struct sb_full_soc *fs, int new_cap) +{ + if (fs == NULL) + return; + + fs->full_capacity = new_cap; +} + +bool is_full_capacity(struct sb_full_soc *fs) +{ + if (fs == NULL) + return false; + + return ((fs->full_capacity > 0) && (fs->full_capacity < 100)); +} +EXPORT_SYMBOL(is_full_capacity); + +static void set_full_cap_event(struct sb_full_soc *fs, unsigned int new_cap_event) +{ + if (fs == NULL) + return; + + fs->full_cap_event = new_cap_event; +} + +static unsigned int get_full_cap_event(struct sb_full_soc *fs) +{ + if (fs == NULL) + return 0; + + return fs->full_cap_event; +} + +static bool is_full_cap_event_highsoc(struct sb_full_soc *fs) +{ + if (fs == NULL) + return false; + + return (fs->full_cap_event == SB_FULL_CAP_EVENT_HIGHSOC); +} + +static void set_eu_eco_rechg(struct sb_full_soc *fs, bool enable) +{ + if (fs == NULL) + return; + + fs->eu_eco_rechg_state = enable; +} + +bool is_eu_eco_rechg(struct sb_full_soc *fs) +{ + if (fs == NULL) + return false; + + return fs->is_eu_eco_rechg; +} +EXPORT_SYMBOL(is_eu_eco_rechg); + +bool check_eu_eco_full_status(struct sec_battery_info *battery) +{ + struct sb_full_soc *fs = battery->fs; + + if (fs == NULL) + return false; + + if ((!fs->is_eu_eco_rechg) || + (battery->status != POWER_SUPPLY_STATUS_FULL)) { + fs->eu_eco_chg_done = false; + return false; + } + + if (battery->capacity >= 100) { + fs->eu_eco_chg_done = true; + } else if (fs->eu_eco_chg_done) { + fs->eu_eco_chg_done = false; + + if ((battery->is_recharging) || + (battery->charging_mode == SEC_BATTERY_CHARGING_2ND)) { + pr_info("%s: fixed the 2nd fullcharged!!!(%d, %d)\n", + __func__, battery->is_recharging, battery->charging_mode); + return true; + } + } + + return false; +} +EXPORT_SYMBOL(check_eu_eco_full_status); + +static void enable_eu_eco_rechg(struct sec_battery_info *battery) +{ + battery->pdata->recharge_condition_type = SEC_BATTERY_RECHARGE_CONDITION_SOC; + battery->pdata->recharge_condition_soc = 100 - DEF_ECO_RECHG_DIF; +} + +static void disable_eu_eco_rechg(struct sec_battery_info *battery) +{ + struct sb_full_soc *fs = battery->fs; + + battery->pdata->recharge_condition_type = fs->old_recharge_condition_type; + battery->pdata->recharge_condition_soc = fs->old_recharge_condition_soc; +} + +bool check_eu_eco_rechg_ui_condition(struct sec_battery_info *battery) +{ + if (battery->pdata->recharge_condition_type & SEC_BATTERY_RECHARGE_CONDITION_SOC) + return (battery->capacity <= battery->pdata->recharge_condition_soc); + + return false; +} +EXPORT_SYMBOL(check_eu_eco_rechg_ui_condition); + +static void eu_eco_work(struct work_struct *work) +{ + struct sb_full_soc *fs = container_of(work, struct sb_full_soc, eu_eco_work.work); + struct sec_battery_info *battery = fs->battery; + union power_supply_propval value = {0, }; + + pr_info("%s: start (%d, %d, %d, %d)\n", + __func__, + fs->eu_eco_rechg_state, fs->is_eu_eco_rechg, + battery->status, battery->capacity); + + mutex_lock(&fs->lock); + if (fs->is_eu_eco_rechg == fs->eu_eco_rechg_state) + goto end_work; + + if ((battery->status != POWER_SUPPLY_STATUS_FULL) || + (battery->capacity >= 100)) + goto update_state; + + if (fs->eu_eco_rechg_state) { + pr_info("%s : Update fg scale to %d%%\n", __func__, battery->capacity); + value.intval = 99; + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_PROP_CHARGE_FULL, value); + } else { + sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_CHARGING); + battery->is_recharging = false; + battery->charging_mode = SEC_BATTERY_CHARGING_1ST; + if (battery->pdata->change_FV_after_full) + sec_vote(battery->fv_vote, VOTER_FULL_CHARGE, false, battery->pdata->chg_float_voltage); + sec_vote(battery->chgen_vote, VOTER_CABLE, true, SEC_BAT_CHG_MODE_CHARGING); + sec_vote(battery->topoff_vote, VOTER_FULL_CHARGE, false, 0); + sec_vote(battery->chgen_vote, VOTER_FULL_CHARGE, false, 0); + pr_info("%s: battery status full -> charging, Cap(%d)\n", + __func__, battery->capacity); + value.intval = POWER_SUPPLY_STATUS_CHARGING; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_PROP_STATUS, value); + } + + /* start polling work */ + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + +update_state: + pr_info("%s: update eu eco rechg(%d --> %d)\n", + __func__, fs->is_eu_eco_rechg, fs->eu_eco_rechg_state); + store_battery_log("EUECO:%d->%d,%s,%d%%", + fs->is_eu_eco_rechg, fs->eu_eco_rechg_state, + sb_get_bst_str(battery->status), battery->capacity); + fs->is_eu_eco_rechg = fs->eu_eco_rechg_state; +end_work: + mutex_unlock(&fs->lock); + __pm_relax(fs->ws); +} + +static ssize_t +sb_full_soc_show_attrs(struct device *, struct device_attribute *, char *); +static ssize_t +sb_full_soc_store_attrs(struct device *, struct device_attribute *, const char *, size_t); + +#define SB_FULL_SOC_ATTR(_name) \ +{ \ + .attr = {.name = #_name, .mode = 0664}, \ + .show = sb_full_soc_show_attrs, \ + .store = sb_full_soc_store_attrs, \ +} + +enum sec_bat_attrs { + BATT_FULL_CAPACITY = 0, + BATT_SOC_RECHG, +#if defined(CONFIG_ENG_BATTERY_CONCEPT) + BATT_FULL_CAP_EVENT, + BATT_FULL_SOC_TEST, +#endif +}; + +static struct device_attribute sb_full_soc_attrs[] = { + SB_FULL_SOC_ATTR(batt_full_capacity), + SB_FULL_SOC_ATTR(batt_soc_rechg), +#if defined(CONFIG_ENG_BATTERY_CONCEPT) + SB_FULL_SOC_ATTR(batt_full_cap_event), + SB_FULL_SOC_ATTR(batt_full_soc_test), +#endif +}; + + +static ssize_t sb_full_soc_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_battery_info *battery = power_supply_get_drvdata(psy); + const ptrdiff_t offset = attr - sb_full_soc_attrs; + int i = 0; + + switch (offset) { + case BATT_FULL_CAPACITY: + i += scnprintf(buf, PAGE_SIZE, "%d\n", get_full_capacity(battery->fs)); + break; + case BATT_SOC_RECHG: + i += scnprintf(buf, PAGE_SIZE, "%d\n", is_eu_eco_rechg(battery->fs)); + break; +#if defined(CONFIG_ENG_BATTERY_CONCEPT) + case BATT_FULL_CAP_EVENT: + i += scnprintf(buf, PAGE_SIZE, "%s\n", + conv_full_cap_str(get_full_cap_event(battery->fs))); + break; + case BATT_FULL_SOC_TEST: + i += scnprintf(buf, PAGE_SIZE, "%d, 0x%x, %d\n", + is_eu_eco_rechg(battery->fs), + battery->pdata->recharge_condition_type, + battery->pdata->recharge_condition_soc); + break; +#endif + default: + return -EINVAL; + } + + return i; +} + +static ssize_t sb_full_soc_store_attrs(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_battery_info *battery = power_supply_get_drvdata(psy); + const ptrdiff_t offset = attr - sb_full_soc_attrs; + + switch (offset) { + case BATT_FULL_CAPACITY: + { + unsigned int full_cap_event = SB_FULL_CAP_EVENT_NONE; + int x = 0, n = 0; + bool is_changed = false; + + if (sscanf(buf, "%10d%n", &x, &n) <= 0) { + pr_info("%s: invalid arguments\n", __func__); + return -EINVAL; + } else if (x < 0 || x > 100) { + pr_info("%s: out of range(%d)\n", __func__, x); + break; + } + + if (n > 0) { + char cap_event[MAX_CAP_EVENT_STR] = { 0, }; + + if ((count - n) > MAX_CAP_EVENT_STR) + pr_info("%s: out of range\n", __func__); + else if (sscanf(buf + n, "%s\n", cap_event) > 0) + full_cap_event = conv_full_cap_event_value(cap_event); + } + + if ((get_full_capacity(battery->fs) != x) || + (get_full_cap_event(battery->fs) != full_cap_event)) { + is_changed = true; + + store_battery_log("FCAP:%d%%->%d%%,%s->%s", + get_full_capacity(battery->fs), x, + conv_full_cap_str(get_full_cap_event(battery->fs)), conv_full_cap_str(full_cap_event)); + + set_full_capacity(battery->fs, x); + set_full_cap_event(battery->fs, full_cap_event); + + /* recov full cap */ + sec_bat_recov_full_capacity(battery); + + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + } + pr_info("%s: %s full cap(%d, %s)\n", + __func__, (is_changed ? "set" : "same"), x, conv_full_cap_str(full_cap_event)); + } + break; + case BATT_SOC_RECHG: + { + int x = 0; + + if (sscanf(buf, "%10d\n", &x) != 1) { + pr_info("%s: invalid arguments\n", __func__); + return -EINVAL; + } + + mutex_lock(&battery->fs->lock); + set_eu_eco_rechg(battery->fs, !!x); + if (x) + enable_eu_eco_rechg(battery); + else + disable_eu_eco_rechg(battery); + + /* start eu eco work */ + __pm_stay_awake(battery->fs->ws); + queue_delayed_work(battery->monitor_wqueue, &battery->fs->eu_eco_work, 0); + mutex_unlock(&battery->fs->lock); + + pr_info("%s: set eu eco rechg(%d)\n", + __func__, is_eu_eco_rechg(battery->fs)); + } + break; +#if defined(CONFIG_ENG_BATTERY_CONCEPT) + case BATT_FULL_CAP_EVENT: + break; + case BATT_FULL_SOC_TEST: + { + int x; + + if (sscanf(buf, "%10d\n", &x) != 1) { + pr_info("%s: invalid arguments\n", __func__); + return -EINVAL; + } + + battery->pdata->recharge_condition_soc = x; + } + break; +#endif + default: + return -EINVAL; + } + + return count; +} + +static int sb_full_soc_create_attrs(struct device *dev) +{ + unsigned long i = 0; + int rc = 0; + + for (i = 0; i < ARRAY_SIZE(sb_full_soc_attrs); i++) { + rc = device_create_file(dev, &sb_full_soc_attrs[i]); + if (rc) + goto create_attrs_failed; + } + goto create_attrs_succeed; + +create_attrs_failed: + while (i--) + device_remove_file(dev, &sb_full_soc_attrs[i]); +create_attrs_succeed: + return rc; +} + +static void sb_full_soc_remove_attrs(struct device *dev) +{ + int i = 0; + + for (; i < ARRAY_SIZE(sb_full_soc_attrs); i++) + device_remove_file(dev, &sb_full_soc_attrs[i]); +} + +void sec_bat_recov_full_capacity(struct sec_battery_info *battery) +{ + sec_bat_set_misc_event(battery, 0, BATT_MISC_EVENT_FULL_CAPACITY); + if (battery->status == POWER_SUPPLY_STATUS_NOT_CHARGING + && battery->health == POWER_SUPPLY_HEALTH_GOOD) { +#if defined(CONFIG_ENABLE_FULL_BY_SOC) + if (battery->capacity >= 100) + sec_bat_set_charging_status(battery, + POWER_SUPPLY_STATUS_FULL); + else +#endif + sec_bat_set_charging_status(battery, + POWER_SUPPLY_STATUS_CHARGING); + } + + if (!is_full_cap_event_highsoc(battery->fs)) + sec_vote(battery->chgen_vote, VOTER_FULL_CAPACITY, false, 0); +} +EXPORT_SYMBOL(sec_bat_recov_full_capacity); + +void sec_bat_check_full_capacity(struct sec_battery_info *battery) +{ + int now_full_capacity = get_full_capacity(battery->fs); + int rechg_capacity = now_full_capacity - DEF_RECHG_SOC_DIF; + + if (is_full_cap_event_highsoc(battery->fs) && + (battery->capacity <= now_full_capacity)) { + set_full_cap_event(battery->fs, SB_FULL_CAP_EVENT_NONE); + sec_vote(battery->chgen_vote, VOTER_FULL_CAPACITY, + (battery->misc_event & BATT_MISC_EVENT_FULL_CAPACITY), SEC_BAT_CHG_MODE_CHARGING_OFF); + } + + if (!is_full_capacity(battery->fs) || + battery->status == POWER_SUPPLY_STATUS_DISCHARGING) { + if (battery->misc_event & BATT_MISC_EVENT_FULL_CAPACITY) { + pr_info("%s: full_capacity(%d) status(%d)\n", + __func__, now_full_capacity, battery->status); + sec_bat_recov_full_capacity(battery); + } + return; + } + + if (battery->misc_event & BATT_MISC_EVENT_FULL_CAPACITY) { + if (battery->capacity <= rechg_capacity || + battery->status == POWER_SUPPLY_STATUS_CHARGING) { + pr_info("%s : start re-charging(%d, %d) status(%d)\n", + __func__, battery->capacity, rechg_capacity, battery->status); + set_full_cap_event(battery->fs, SB_FULL_CAP_EVENT_NONE); + sec_bat_recov_full_capacity(battery); + } + } else if (battery->capacity >= now_full_capacity) { + union power_supply_propval value = {0, }; + + pr_info("%s : stop charging(%d, %d, %s)\n", __func__, + battery->capacity, now_full_capacity, + conv_full_cap_str(get_full_cap_event(battery->fs))); + sec_bat_set_misc_event(battery, BATT_MISC_EVENT_FULL_CAPACITY, + BATT_MISC_EVENT_FULL_CAPACITY); + sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_NOT_CHARGING); + sec_vote(battery->chgen_vote, VOTER_FULL_CAPACITY, true, + (is_full_cap_event_highsoc(battery->fs) ? + SEC_BAT_CHG_MODE_BUCK_OFF : SEC_BAT_CHG_MODE_CHARGING_OFF)); + + if (is_wireless_all_type(battery->cable_type)) { + value.intval = POWER_SUPPLY_STATUS_FULL; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_PROP_STATUS, value); + } + } +} +EXPORT_SYMBOL(sec_bat_check_full_capacity); + +int sb_full_soc_init(struct sec_battery_info *battery) +{ + struct sb_full_soc *fs; + int ret = 0; + + fs = kzalloc(sizeof(struct sb_full_soc), GFP_KERNEL); + if (!fs) + return -ENOMEM; + + ret = sb_full_soc_create_attrs(&battery->psy_bat->dev); + if (ret) { + pr_err("%s: failed to create attrs(%d)\n", __func__, ret); + goto err_attrs; + } + + fs->ws = wakeup_source_register(NULL, "full-soc"); + if (!fs->ws) { + pr_err("%s: failed to register wakeup\n", __func__); + goto err_ws; + } + + mutex_init(&fs->lock); + INIT_DELAYED_WORK(&fs->eu_eco_work, eu_eco_work); + + fs->battery = battery; + + fs->full_capacity = 0; + fs->full_cap_event = SB_FULL_CAP_EVENT_NONE; + + fs->is_eu_eco_rechg = false; + fs->eu_eco_rechg_state = false; + fs->old_recharge_condition_type = battery->pdata->recharge_condition_type; + fs->old_recharge_condition_soc = battery->pdata->recharge_condition_soc; + + battery->fs = fs; + return 0; + +err_ws: + sb_full_soc_remove_attrs(&battery->psy_bat->dev); +err_attrs: + kfree(fs); + return ret; +} +EXPORT_SYMBOL(sb_full_soc_init); diff --git a/drivers/battery/common/sb_full_soc.h b/drivers/battery/common/sb_full_soc.h new file mode 100644 index 000000000000..4dc66eeecd84 --- /dev/null +++ b/drivers/battery/common/sb_full_soc.h @@ -0,0 +1,36 @@ +/* + * sb_full_soc.h + * Samsung Mobile Battery Full SoC Header + * + * Copyright (C) 2023 Samsung Electronics, 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 __SB_FULL_SOC_H +#define __SB_FULL_SOC_H __FILE__ + +struct sec_battery_info; +struct sb_full_soc; + +int get_full_capacity(struct sb_full_soc *fs); +bool is_full_capacity(struct sb_full_soc *fs); +bool is_eu_eco_rechg(struct sb_full_soc *fs); + +bool check_eu_eco_full_status(struct sec_battery_info *battery); +bool check_eu_eco_rechg_ui_condition(struct sec_battery_info *battery); +void sec_bat_recov_full_capacity(struct sec_battery_info *battery); +void sec_bat_check_full_capacity(struct sec_battery_info *battery); + +int sb_full_soc_init(struct sec_battery_info *battery); + +#endif /* __SB_FULL_SOC_H */ diff --git a/drivers/battery/common/sb_pass_through.c b/drivers/battery/common/sb_pass_through.c new file mode 100644 index 000000000000..f8d695f1c4d7 --- /dev/null +++ b/drivers/battery/common/sb_pass_through.c @@ -0,0 +1,780 @@ +/* + * sb_tx.c + * Samsung Mobile Wireless TX Driver + * + * Copyright (C) 2021 Samsung Electronics + * + * + * 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. + */ + +#include +#include + +#include +#include + +#include "sec_battery.h" +#include "sec_charging_common.h" +#include "sb_pass_through.h" + +#define pt_log(str, ...) pr_info("[PASS-THROUGH]:%s: "str, __func__, ##__VA_ARGS__) + +#define PT_MODULE_NAME "pass-through" +#define IV_VOTE_NAME "IV" +#define ICL_VOTE_NAME "ICL" +#define FCC_VOTE_NAME "FCC" +#define CHGEN_VOTE_NAME "CHGEN" + +struct sb_pt { + struct notifier_block nb; + struct mutex mlock; + + struct wakeup_source *ws; + + struct workqueue_struct *wq; + struct delayed_work start_work; + struct delayed_work adjust_work; + struct delayed_work step_work; + + /* state flags */ + int user_mode; + int chg_src; + int step; + int ref_cap; + + int adj_state; + unsigned int adj_cnt; + unsigned int adj_op_cnt; + + /* battery status */ + int cable_type; + int batt_status; + + int dc_status; + + /* dt data */ + bool is_enabled; + + unsigned int start_delay; + unsigned int init_delay; + unsigned int adj_delay; + unsigned int adj_max_cnt; + unsigned int min_cap; + unsigned int fixed_sc_cap; + unsigned int max_icl; + unsigned int vfloat; + + char *sc_name; + char *dc_name; + char *fg_name; +}; + +enum pt_step { + PT_STEP_NONE = 0, + PT_STEP_INIT, + PT_STEP_PRESET, + PT_STEP_ADJUST, + PT_STEP_MONITOR, + PT_STEP_RESET, +}; + +static const char *get_step_str(int step) +{ + switch (step) { + case PT_STEP_NONE: + return "None"; + case PT_STEP_INIT: + return "Init"; + case PT_STEP_PRESET: + return "Preset"; + case PT_STEP_ADJUST: + return "Adjust"; + case PT_STEP_MONITOR: + return "Monitor"; + case PT_STEP_RESET: + return "Reset"; + } + + return "Unknown"; +} + +static void set_misc_event(bool state) +{ + struct sbn_bit_event misc; + + misc.value = (state) ? BATT_MISC_EVENT_PASS_THROUGH : 0; + misc.mask = BATT_MISC_EVENT_PASS_THROUGH; + + sb_notify_call(SB_NOTIFY_EVENT_MISC, cast_to_sb_pdata(&misc)); +} + +static int set_dc_ta_volt(struct sb_pt *pt, int value) +{ + union power_supply_propval val = { value, }; + + return psy_do_property(pt->dc_name, set, + POWER_SUPPLY_EXT_PROP_PASS_THROUGH_MODE_TA_VOL, val); +} + +/* don't call the mutex lock in static functions */ +static bool check_state(struct sb_pt *pt) +{ + if (!pt) + return false; + + if (!pt->is_enabled) + return false; + + if (pt->user_mode == PTM_NONE) + return false; + + if (!is_pd_apdo_wire_type(pt->cable_type)) + return false; + + if (pt->batt_status != POWER_SUPPLY_STATUS_CHARGING) + return false; + + if (pt->step == PT_STEP_NONE) { + union power_supply_propval value = { 0, }; + + value.intval = SEC_FUELGAUGE_CAPACITY_TYPE_DYNAMIC_SCALE; + psy_do_property(pt->fg_name, get, + POWER_SUPPLY_PROP_CAPACITY, value); + if (pt->min_cap >= value.intval) + return false; + } + + return true; +} + +static bool check_preset_state(int dc_status) +{ + return (dc_status == SEC_DIRECT_CHG_MODE_DIRECT_ON) || + (dc_status == SEC_DIRECT_CHG_MODE_DIRECT_DONE) || + (dc_status == SEC_DIRECT_CHG_MODE_DIRECT_BYPASS); +} + +#define CAP_HIGH (1) +#define CAP_NORMAL (0) +#define CAP_LOW (-1) +static int check_cap(struct sb_pt *pt) +{ + union power_supply_propval value = {0, }; + int ncap1, ncap2, rcap1, rcap2; + int delta_cap = 0; + + value.intval = SEC_FUELGAUGE_CAPACITY_TYPE_DYNAMIC_SCALE; + psy_do_property(pt->fg_name, get, + POWER_SUPPLY_PROP_CAPACITY, value); + + ncap1 = (value.intval / 10); + ncap2 = (value.intval % 10); + + rcap1 = (pt->ref_cap / 10); + rcap2 = (pt->ref_cap % 10); + + value.intval = SEC_BATTERY_CURRENT_MA; + psy_do_property(pt->fg_name, get, + POWER_SUPPLY_PROP_CURRENT_NOW, value); + + if (ncap1 == rcap1) + delta_cap = (((ncap2 >= 6) && (value.intval > 0)) ? CAP_HIGH : + (((ncap2 <= 4) && (value.intval < 0)) ? CAP_LOW : CAP_NORMAL)); + else if (ncap1 > rcap1) + delta_cap = (value.intval > 0) ? CAP_HIGH : CAP_NORMAL; + else + delta_cap = (value.intval < 0) ? CAP_LOW : CAP_NORMAL; + + pt_log("Now Cap(%03d.%d%%), Ref Cap(%03d.%d%%), Current(%04dmA), delta(%d)\n", + ncap1, ncap2, rcap1, rcap2, value.intval, delta_cap); + return delta_cap; +} + +static void clear_state(struct sb_pt *pt, int init_step) +{ + if (pt->step == PT_STEP_NONE) + return; + + pt_log("latest step = %d, init_step = %d\n", pt->step, init_step); + + sec_votef(IV_VOTE_NAME, VOTER_PASS_THROUGH, false, 0); + sec_votef(ICL_VOTE_NAME, VOTER_PASS_THROUGH, false, 0); + sec_votef(FCC_VOTE_NAME, VOTER_PASS_THROUGH, false, 0); + sec_votef(CHGEN_VOTE_NAME, VOTER_PASS_THROUGH, false, 0); + + pt->chg_src = SEC_CHARGING_SOURCE_SWITCHING; + pt->adj_state = CAP_NORMAL; + pt->adj_cnt = 0; + pt->adj_op_cnt = 0; + + if (init_step == PT_STEP_NONE) { + pt->ref_cap = 0; + + /* set charging off before re-starting dc */ + sec_votef(CHGEN_VOTE_NAME, VOTER_PASS_THROUGH, true, SEC_BAT_CHG_MODE_CHARGING_OFF); + sec_votef(CHGEN_VOTE_NAME, VOTER_PASS_THROUGH, false, 0); + + /* clear event */ + set_misc_event(false); + sec_pd_detach_with_cc(0); + } + pt->step = init_step; +} + +static void cb_start_work(struct work_struct *work) +{ + struct sb_pt *pt = container_of(work, + struct sb_pt, start_work.work); + + mutex_lock(&pt->mlock); + + pt_log("now step = %s\n", get_step_str(pt->step)); + + if (!check_state(pt)) + goto end_work; + + if (pt->step == PT_STEP_NONE) + pt->step = PT_STEP_INIT; + + if (pt->step != PT_STEP_INIT) + goto end_work; + + __pm_wakeup_event(pt->ws, msecs_to_jiffies(1000)); + queue_delayed_work(pt->wq, &pt->step_work, 0); + +end_work: + mutex_unlock(&pt->mlock); +} + +#define PT_ADJ_OP_MAX_CNT 100 +#define PT_ADJ_CURR 500 +static void cb_adjust_work(struct work_struct *work) +{ + struct sb_pt *pt = container_of(work, struct sb_pt, adjust_work.work); + union power_supply_propval value = {0, }; + int now_state = CAP_NORMAL; + + mutex_lock(&pt->mlock); + + if (!check_state(pt)) + goto end_work; + + if (pt->step != PT_STEP_ADJUST) + goto end_work; + + if (pt->chg_src == SEC_CHARGING_SOURCE_SWITCHING) { + pt->step = PT_STEP_MONITOR; + goto end_work; + } + + if (pt->adj_op_cnt++ >= PT_ADJ_OP_MAX_CNT) { + pt_log("over working(%d) !!\n", pt->adj_op_cnt); + pt->step = PT_STEP_MONITOR; + goto end_work; + } + + now_state = check_cap(pt); + if (now_state != pt->adj_state) { + pt->adj_cnt = 0; + pt->adj_state = now_state; + goto re_work; + } + + value.intval = SEC_BATTERY_CURRENT_MA; + psy_do_property(pt->fg_name, get, + POWER_SUPPLY_PROP_CURRENT_NOW, value); + + switch (pt->adj_state) { + case CAP_LOW: + if (value.intval < (-PT_ADJ_CURR)) { + pt->adj_cnt = 0; + set_dc_ta_volt(pt, CAP_LOW); + } else if (value.intval > (PT_ADJ_CURR)) { + pt->adj_cnt = 0; + } else { + pt->adj_cnt++; + } + break; + case CAP_HIGH: + if (value.intval > (PT_ADJ_CURR)) { + pt->adj_cnt = 0; + set_dc_ta_volt(pt, CAP_HIGH); + } else if (value.intval < (-PT_ADJ_CURR)) { + pt->adj_cnt = 0; + } else { + pt->adj_cnt++; + } + break; + case CAP_NORMAL: + if (value.intval < (-PT_ADJ_CURR)) { + pt->adj_cnt = 0; + set_dc_ta_volt(pt, CAP_LOW); + } else if (value.intval > (PT_ADJ_CURR)) { + pt->adj_cnt = 0; + set_dc_ta_volt(pt, CAP_HIGH); + } else { + pt->adj_cnt++; + } + break; + default: + break; + } + pt_log("ADJ STATE(%d, %d), CURR(%d), CNT(%d)\n", + pt->adj_state, now_state, value.intval, pt->adj_cnt); + + if (pt->adj_cnt >= pt->adj_max_cnt) { + pt->step = PT_STEP_MONITOR; + goto end_work; + } + +re_work: + mutex_unlock(&pt->mlock); + + queue_delayed_work(pt->wq, &pt->adjust_work, msecs_to_jiffies(pt->adj_delay)); + return; + +end_work: + pt->adj_state = CAP_NORMAL; + pt->adj_cnt = 0; + pt->adj_op_cnt = 0; + + mutex_unlock(&pt->mlock); + __pm_relax(pt->ws); +} + +static void cb_step_work(struct work_struct *work) +{ + struct sb_pt *pt = container_of(work, struct sb_pt, step_work.work); + + sb_pt_monitor(pt, pt->chg_src); +} + +static void push_start_work(struct sb_pt *pt, unsigned int delay) +{ + unsigned int work_state; + + if (!check_state(pt)) + return; + + work_state = work_busy(&pt->start_work.work); + pt_log("work_state = 0x%x, delay = %d\n", work_state, delay); + if (!(work_state & (WORK_BUSY_PENDING | WORK_BUSY_RUNNING))) { + __pm_wakeup_event(pt->ws, msecs_to_jiffies(delay + 1000)); + queue_delayed_work(pt->wq, &pt->start_work, msecs_to_jiffies(delay)); + } +} + +static ssize_t show_attrs(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t store_attrs(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +#define PT_SYSFS_ATTR(_name) \ +{ \ + .attr = {.name = #_name, .mode = 0664}, \ + .show = show_attrs, \ + .store = store_attrs, \ +} + +static struct device_attribute pt_attr[] = { + PT_SYSFS_ATTR(pass_through), +}; + +enum sb_pt_attrs { + PASS_THROUGH = 0, +}; + +static ssize_t show_attrs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sb_pt *pt = sb_sysfs_get_pdata(PT_MODULE_NAME); + const ptrdiff_t offset = attr - pt_attr; + ssize_t count = 0; + + switch (offset) { + case PASS_THROUGH: + count += scnprintf(buf + count, PAGE_SIZE - count, "%d\n", pt->user_mode); + break; + default: + break; + } + + return count; +} + +static ssize_t store_attrs(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct sb_pt *pt = sb_sysfs_get_pdata(PT_MODULE_NAME); + const ptrdiff_t offset = attr - pt_attr; + + switch (offset) { + case PASS_THROUGH: + { + int x = 0; + + if (sscanf(buf, "%10d\n", &x) == 1) { + mutex_lock(&pt->mlock); + + pt_log("user_mode = %d <-> %d, %s\n", + x, pt->user_mode, ((x != PTM_NONE) ? "enabled" : "disabled")); + + x = (x) ? PTM_2TO1 : PTM_NONE; + if (pt->user_mode != x) { + pt->user_mode = x; + + if (pt->step != PT_STEP_NONE) + clear_state(pt, PT_STEP_NONE); + + if (pt->user_mode) + push_start_work(pt, pt->start_delay); + } + + mutex_unlock(&pt->mlock); + } + } + break; + default: + break; + } + + return count; +} + +static int sb_noti_handler(struct notifier_block *nb, unsigned long action, void *data) +{ + return 0; +} + +static int parse_dt(struct sb_pt *pt, struct device *parent) +{ +#if defined(CONFIG_OF) + struct device_node *np; + int ret = 0; + + if (!parent) + return -EINVAL; + + np = of_find_node_by_name(NULL, PT_MODULE_NAME); + if (!np) { + pt_log("failed to find root node\n"); + return -ENODEV; + } + + pt->is_enabled = true; + + sb_of_parse_u32(np, pt, start_delay, 5000); + sb_of_parse_u32(np, pt, init_delay, 5000); + sb_of_parse_u32(np, pt, adj_delay, 500); + sb_of_parse_u32(np, pt, adj_max_cnt, 3); + sb_of_parse_u32(np, pt, min_cap, 200); + sb_of_parse_u32(np, pt, fixed_sc_cap, 900); + sb_of_parse_u32(np, pt, max_icl, 3000); + sb_of_parse_u32(np, pt, vfloat, 4400); + + np = of_find_node_by_name(NULL, "battery"); + if (np) { + ret = of_property_read_string(np, + "battery,fuelgauge_name", (const char **)&pt->fg_name); + if (ret) + pt_log("failed to get fg name in battery dt (ret = %d)\n", ret); + } + + ret = of_property_read_string(parent->of_node, + "charger,main_charger", (const char **)&pt->sc_name); + if (ret) + pt_log("failed to get sc name in dc drv (ret = %d)\n", ret); + + ret = of_property_read_string(parent->of_node, + "charger,direct_charger", (const char **)&pt->dc_name); + if (ret) + pt_log("failed to get dc name in dc drv (ret = %d)\n", ret); +#endif + return 0; +} + +struct sb_pt *sb_pt_init(struct device *parent) +{ + struct sb_pt *pt; + int ret = 0; + + pt = kzalloc(sizeof(struct sb_pt), GFP_KERNEL); + if (!pt) + return ERR_PTR(-ENOMEM); + + ret = parse_dt(pt, parent); + pt_log("parse_dt ret = %s\n", (ret) ? "fail" : "success"); + if (ret) + goto failed_dt; + + pt->wq = create_singlethread_workqueue(PT_MODULE_NAME); + if (!pt->wq) { + ret = -ENOMEM; + goto failed_wq; + } + + pt->ws = wakeup_source_register(parent, PT_MODULE_NAME); + + INIT_DELAYED_WORK(&pt->start_work, cb_start_work); + INIT_DELAYED_WORK(&pt->adjust_work, cb_adjust_work); + INIT_DELAYED_WORK(&pt->step_work, cb_step_work); + + mutex_init(&pt->mlock); + + pt->chg_src = SEC_CHARGING_SOURCE_SWITCHING; + pt->step = PT_STEP_NONE; + pt->ref_cap = 0; + pt->user_mode = PTM_NONE; + pt->adj_state = CAP_NORMAL; + pt->adj_cnt = 0; + pt->adj_op_cnt = 0; + + ret = sb_sysfs_add_attrs(PT_MODULE_NAME, pt, pt_attr, ARRAY_SIZE(pt_attr)); + pt_log("sb_sysfs_add_attrs ret = %s\n", (ret) ? "fail" : "success"); + + ret = sb_notify_register(&pt->nb, sb_noti_handler, PT_MODULE_NAME, SB_DEV_MODULE); + pt_log("sb_notify_register ret = %s\n", (ret) ? "fail" : "success"); + + return pt; + +failed_wq: +failed_dt: + kfree(pt); + + return ERR_PTR(ret); +} +EXPORT_SYMBOL(sb_pt_init); + +int sb_pt_psy_set_property(struct sb_pt *pt, enum power_supply_property psp, const union power_supply_propval *value) +{ + if (!pt) + return 0; + + switch ((int)psp) { + case POWER_SUPPLY_PROP_STATUS: + pt->batt_status = value->intval; + break; + case POWER_SUPPLY_PROP_ONLINE: + pt->cable_type = value->intval; + + mutex_lock(&pt->mlock); + if (!is_pd_apdo_wire_type(pt->cable_type) && + (pt->step != PT_STEP_NONE)) + clear_state(pt, PT_STEP_NONE); + mutex_unlock(&pt->mlock); + break; + case POWER_SUPPLY_EXT_PROP_DIRECT_CHARGER_MODE: + /* Caution : dead lock */ + pt->dc_status = value->intval; + break; + default: + break; + } + + return 0; +} +EXPORT_SYMBOL(sb_pt_psy_set_property); + +int sb_pt_psy_get_property(struct sb_pt *pt, enum power_supply_property psp, union power_supply_propval *value) +{ + int ret = 0; + + if (!pt) + return 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + mutex_lock(&pt->mlock); + if ((pt->step != PT_STEP_NONE) && + (pt->chg_src == SEC_CHARGING_SOURCE_SWITCHING)) { + union power_supply_propval val; + + val.intval = 0; + psy_do_property(pt->sc_name, get, psp, val); + if (val.intval == POWER_SUPPLY_STATUS_FULL) { + ret = -EBUSY; + pt_log("prevent charging status\n"); + value->intval = POWER_SUPPLY_STATUS_CHARGING; + } + } + mutex_unlock(&pt->mlock); + break; + case POWER_SUPPLY_PROP_HEALTH: + mutex_lock(&pt->mlock); + if (pt->step != PT_STEP_NONE) { + union power_supply_propval val; + + val.intval = 0; + psy_do_property(pt->dc_name, get, psp, val); + + if (val.intval == POWER_SUPPLY_EXT_HEALTH_DC_ERR) { + pt_log("clear pt state because of DC err(%d)\n", val.intval); + clear_state(pt, PT_STEP_NONE); + } + } + mutex_unlock(&pt->mlock); + break; + default: + break; + } + + return ret; +} +EXPORT_SYMBOL(sb_pt_psy_get_property); + +int sb_pt_monitor(struct sb_pt *pt, int chg_src) +{ + if (!pt) + return -EINVAL; + + mutex_lock(&pt->mlock); + + if (!check_state(pt)) { + clear_state(pt, PT_STEP_NONE); + goto end_monitor; + } + + pt_log("start - step = %s, chg_src = %d, dc_status = %d\n", get_step_str(pt->step), chg_src, pt->dc_status); + + switch (pt->step) { + case PT_STEP_NONE: + push_start_work(pt, pt->start_delay); + pt->chg_src = chg_src; + break; + case PT_STEP_INIT: + if (pt->ref_cap <= 0) { + union power_supply_propval value = { 0, }; + + value.intval = SEC_FUELGAUGE_CAPACITY_TYPE_DYNAMIC_SCALE; + psy_do_property(pt->fg_name, get, + POWER_SUPPLY_PROP_CAPACITY, value); + + pt->ref_cap = value.intval; + pt_log("update ref_cap = %d\n", pt->ref_cap); + + if ((chg_src == SEC_CHARGING_SOURCE_DIRECT) && + (pt->ref_cap > pt->fixed_sc_cap)) { + /* reset chg src to switching charger */ + sec_votef(CHGEN_VOTE_NAME, VOTER_PASS_THROUGH, true, SEC_BAT_CHG_MODE_CHARGING_OFF); + pt->step = PT_STEP_PRESET; + break; + } + } + + if (chg_src == SEC_CHARGING_SOURCE_SWITCHING) { + pt->step = PT_STEP_PRESET; + } else { + if (check_preset_state(pt->dc_status)) + pt->step = PT_STEP_PRESET; + else + push_start_work(pt, pt->init_delay); + } + pt->chg_src = chg_src; + break; + case PT_STEP_PRESET: + { + union power_supply_propval value = { 0, }; + int iv, icl, fcc, chgen; + + if ((chg_src == SEC_CHARGING_SOURCE_DIRECT) && + (pt->ref_cap > pt->fixed_sc_cap)) + chg_src = SEC_CHARGING_SOURCE_SWITCHING; + + pt->chg_src = chg_src; + pt->step = PT_STEP_ADJUST; + + if (chg_src == SEC_CHARGING_SOURCE_SWITCHING) { + iv = SEC_INPUT_VOLTAGE_5V; + icl = fcc = pt->max_icl; + } else { + iv = SEC_INPUT_VOLTAGE_APDO; + icl = fcc = pt->max_icl * pt->user_mode; + } + chgen = SEC_BAT_CHG_MODE_PASS_THROUGH; + + sec_votef(IV_VOTE_NAME, VOTER_PASS_THROUGH, true, iv); + sec_votef(ICL_VOTE_NAME, VOTER_PASS_THROUGH, true, icl); + sec_votef(FCC_VOTE_NAME, VOTER_PASS_THROUGH, true, fcc); + sec_votef(CHGEN_VOTE_NAME, VOTER_PASS_THROUGH, true, chgen); + + if (chg_src == SEC_CHARGING_SOURCE_SWITCHING) { + value.intval = pt->user_mode; + psy_do_property(pt->sc_name, set, + POWER_SUPPLY_EXT_PROP_PASS_THROUGH_MODE, value); + + /* skip adjust work */ + pt->step = PT_STEP_MONITOR; + } else { + value.intval = pt->user_mode; + psy_do_property(pt->dc_name, set, + POWER_SUPPLY_EXT_PROP_PASS_THROUGH_MODE, value); + sec_pd_detach_with_cc(1); + + value.intval = pt->vfloat; + psy_do_property(pt->dc_name, set, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, value); + + /* set adj state */ + pt->adj_state = check_cap(pt); + + /* start adj work */ + __pm_stay_awake(pt->ws); + queue_delayed_work(pt->wq, &pt->adjust_work, msecs_to_jiffies(pt->adj_delay)); + } + + set_misc_event(true); + } + break; + case PT_STEP_ADJUST: + /* not working */ + break; + case PT_STEP_MONITOR: + { + int cap_state = CAP_NORMAL; + + if (pt->chg_src != chg_src) { + clear_state(pt, PT_STEP_INIT); + break; + } + + cap_state = check_cap(pt); + if (pt->chg_src == SEC_CHARGING_SOURCE_SWITCHING) { + if (cap_state == CAP_LOW) + sec_votef(CHGEN_VOTE_NAME, VOTER_PASS_THROUGH, true, SEC_BAT_CHG_MODE_CHARGING); + else + sec_votef(CHGEN_VOTE_NAME, VOTER_PASS_THROUGH, true, SEC_BAT_CHG_MODE_PASS_THROUGH); + } else { + set_dc_ta_volt(pt, cap_state); + } + } + break; + case PT_STEP_RESET: + break; + default: + break; + } + +end_monitor: + pt_log("end - step = %s\n", get_step_str(pt->step)); + mutex_unlock(&pt->mlock); + + return 0; +} +EXPORT_SYMBOL(sb_pt_monitor); + +int sb_pt_check_chg_src(struct sb_pt *pt, int chg_src) +{ + if (!pt) + return chg_src; + + if ((pt->step != PT_STEP_NONE) && + (pt->ref_cap > pt->fixed_sc_cap)) + return SEC_CHARGING_SOURCE_SWITCHING; + + return chg_src; +} +EXPORT_SYMBOL(sb_pt_check_chg_src); + diff --git a/drivers/battery/common/sb_pass_through.h b/drivers/battery/common/sb_pass_through.h new file mode 100644 index 000000000000..24bfe5846e8f --- /dev/null +++ b/drivers/battery/common/sb_pass_through.h @@ -0,0 +1,43 @@ +/* + * sb_tx.h + * Samsung Mobile Wireless TX Header + * + * Copyright (C) 2021 Samsung Electronics, 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 __SB_PASS_THROUGH_H +#define __SB_PASS_THROUGH_H __FILE__ + +#include + +enum pass_through_mode { + PTM_NONE = 0, + PTM_1TO1, + PTM_2TO1, +}; + +struct sb_pt; +struct device; +enum power_supply_property; +union power_supply_propval; + +struct sb_pt *sb_pt_init(struct device *parent); +int sb_pt_psy_set_property(struct sb_pt *pt, enum power_supply_property psp, const union power_supply_propval *value); +int sb_pt_psy_get_property(struct sb_pt *pt, enum power_supply_property psp, union power_supply_propval *value); +int sb_pt_monitor(struct sb_pt *pt, int chg_src); + +int sb_pt_check_chg_src(struct sb_pt *pt, int chg_src); + +#endif /* __SB_PASS_THROUGH_H */ + diff --git a/drivers/battery/common/sb_tx.c b/drivers/battery/common/sb_tx.c new file mode 100644 index 000000000000..bc9242018651 --- /dev/null +++ b/drivers/battery/common/sb_tx.c @@ -0,0 +1,511 @@ +/* + * sb_tx.c + * Samsung Mobile Wireless TX Driver + * + * Copyright (C) 2021 Samsung Electronics + * + * + * 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. + */ + +#include +#include + +#include +#include + +#include "sec_battery.h" +#include "sec_charging_common.h" +#include "sb_tx.h" + +#define tx_log(str, ...) pr_info("[SB-TX]:%s: "str, __func__, ##__VA_ARGS__) + +#define AOV_VOUT_STEP 500 +#define AOV_VOUT_MAX 7500 + +enum sb_tx_aov_state { + AOV_STATE_NONE = 0, + AOV_STATE_PRESET, + AOV_STATE_MONITOR, + AOV_STATE_PHM, + AOV_STATE_ERR, +}; + +enum sb_tx_chg_state { + AOV_WITH_NONE = 0, + AOV_WITH_NOCHG, + AOV_WITH_CHG, +}; + +static const char *get_aov_state_str(int state) +{ + switch (state) { + case AOV_STATE_NONE: + return "None"; + case AOV_STATE_PRESET: + return "Preset"; + case AOV_STATE_MONITOR: + return "Monitor"; + case AOV_STATE_PHM: + return "PHM"; + case AOV_STATE_ERR: + return "Error"; + } + + return "Unknown"; +} + +struct sb_tx_aov { + bool enable; + int state; + int tx_chg_state; + + /* dt data */ + unsigned int start_vout; + unsigned int low_freq; + unsigned int high_freq; + unsigned int delay; + unsigned int preset_delay; + unsigned int phm_icl; + unsigned int phm_icl_full; + unsigned int vout_min; +}; + +struct sb_tx { + /* temporary attributes */ + struct sec_battery_info *battery; + + struct notifier_block nb; + + struct wakeup_source *ws; + + struct workqueue_struct *wq; + struct delayed_work tx_err_work; + + bool enable; + unsigned int event; + + struct mutex event_lock; + + /* option */ + struct sb_tx_aov aov; + + char *wrl_name; + char *fg_name; +}; +struct sb_tx *gtx; +static DEFINE_MUTEX(tx_lock); + +static struct sb_tx *get_sb_tx(void) +{ + struct sb_tx *tx = NULL; + + mutex_lock(&tx_lock); + tx = gtx; + mutex_unlock(&tx_lock); + + return tx; +} + +static bool check_full_state(struct sb_tx *tx) +{ + union power_supply_propval value = {0, }; + + value.intval = SEC_FUELGAUGE_CAPACITY_TYPE_DYNAMIC_SCALE; + psy_do_property(tx->fg_name, get, + POWER_SUPPLY_PROP_CAPACITY, value); + + return (value.intval >= 970); +} + +int sb_tx_init_aov(void) +{ + struct sb_tx *tx = get_sb_tx(); + struct sb_tx_aov *aov; + + if (!tx) + return -ENODEV; + + aov = &tx->aov; + + mutex_lock(&tx_lock); + + aov->state = AOV_STATE_NONE; + aov->tx_chg_state = AOV_WITH_NONE; + sec_vote(tx->battery->input_vote, VOTER_WC_TX, false, 0); + + mutex_unlock(&tx_lock); + + return 0; +} + +bool sb_tx_is_aov_enabled(int cable_type) +{ + struct sb_tx *tx = get_sb_tx(); + struct sb_tx_aov *aov = &tx->aov; + + if (!tx) + return false; + + if (!tx->aov.enable) + return false; + + if (tx->aov.state == AOV_STATE_ERR) + return false; + + if (is_pd_apdo_wire_type(cable_type)) { + if (tx->aov.state == AOV_STATE_NONE) { + unsigned int min_iv = aov->vout_min, max_iv = AOV_VOUT_MAX; + + if (sec_pd_get_pdo_power(NULL, &min_iv, &max_iv, NULL) <= 0) + return false; + } + aov->tx_chg_state = AOV_WITH_CHG; + } else if (!is_nocharge_type(cable_type)) { + if (tx->aov.state != AOV_STATE_NONE) + sb_tx_init_aov(); + return false; + } else { + aov->tx_chg_state = AOV_WITH_NOCHG; + } + + return true; +} + +int sb_tx_monitor_aov(int vout, bool phm) +{ + struct sb_tx *tx = get_sb_tx(); + struct sec_battery_info *battery; + struct sb_tx_aov *aov; + int prev_aov_state; + static int prev_tx_chg_state; + + if (!tx) + return -ENODEV; + + aov = &tx->aov; + battery = tx->battery; + mutex_lock(&tx_lock); + prev_aov_state = aov->state; + + switch (aov->state) { + case AOV_STATE_NONE: + prev_tx_chg_state = AOV_WITH_NONE; + aov->state = AOV_STATE_PRESET; + fallthrough; + case AOV_STATE_PRESET: + if (vout < aov->start_vout) { + if (prev_aov_state == AOV_STATE_NONE) { + sec_bat_run_wpc_tx_work(battery, (aov->preset_delay + 1000)); + } else { + vout = vout + AOV_VOUT_STEP; + sec_vote(battery->iv_vote, VOTER_WC_TX, true, vout); + sec_bat_wireless_vout_cntl(tx->battery, vout); + sec_bat_run_wpc_tx_work(battery, + ((vout == aov->start_vout) ? (aov->preset_delay * 2) : 500)); + } + break; + } + + aov->state = AOV_STATE_MONITOR; + fallthrough; + case AOV_STATE_MONITOR: + if (phm) { + int phm_icl = (check_full_state(tx)) ? + aov->phm_icl_full : aov->phm_icl; + + sec_vote(battery->iv_vote, VOTER_WC_TX, true, SEC_INPUT_VOLTAGE_5V); + sec_vote(battery->input_vote, VOTER_WC_TX, true, phm_icl); + sec_bat_wireless_vout_cntl(battery, WC_TX_VOUT_5000MV); + sec_bat_wireless_iout_cntl(battery, battery->pdata->tx_uno_iout, 1000); + + aov->state = AOV_STATE_PHM; + } else { + union power_supply_propval freq = {0, }; + int prev_vout = vout; + + psy_do_property(tx->wrl_name, get, POWER_SUPPLY_EXT_PROP_WIRELESS_OP_FREQ, freq); + if ((freq.intval <= aov->low_freq) && (vout < AOV_VOUT_MAX)) + vout = vout + AOV_VOUT_STEP; + else if ((freq.intval >= aov->high_freq) && (vout > aov->vout_min)) + vout = vout - AOV_VOUT_STEP; + + if ((prev_vout != vout) || + ((prev_tx_chg_state == AOV_WITH_NOCHG) && (aov->tx_chg_state == AOV_WITH_CHG))) { + sec_vote(battery->iv_vote, VOTER_WC_TX, true, vout); + sec_bat_wireless_vout_cntl(battery, vout); + sec_bat_run_wpc_tx_work(battery, aov->delay); + } else { + sec_vote_refresh(battery->iv_vote); + } + } + break; + case AOV_STATE_PHM: + if (!phm) { + vout = aov->start_vout + AOV_VOUT_STEP; + sec_vote(battery->iv_vote, VOTER_WC_TX, true, vout); + if (battery->pdata->icl_by_tx_gear) + sec_vote(battery->input_vote, VOTER_WC_TX, true, battery->pdata->icl_by_tx_gear); + else + sec_vote(battery->input_vote, VOTER_WC_TX, false, 0); + sec_bat_wireless_vout_cntl(battery, vout); + sec_bat_run_wpc_tx_work(battery, aov->delay); + + aov->state = AOV_STATE_MONITOR; + } else { + int phm_icl = (check_full_state(tx)) ? + aov->phm_icl_full : aov->phm_icl; + + sec_vote(battery->input_vote, VOTER_WC_TX, true, phm_icl); + } + break; + default: + break; + } + prev_tx_chg_state = aov->tx_chg_state; + sec_vote(battery->chgen_vote, VOTER_WC_TX, false, 0); + tx_log("aov state = %s, tx_chg_state = %d, vout = %d\n", + get_aov_state_str(aov->state), aov->tx_chg_state, vout); + + mutex_unlock(&tx_lock); + + return aov->state; +} + +static bool check_tx_err_state(struct sb_tx *tx) +{ + union power_supply_propval value = { 0, }; + int vout, iout, freq; + + psy_do_property(tx->wrl_name, get, POWER_SUPPLY_EXT_PROP_WIRELESS_TX_UNO_VIN, value); + vout = value.intval; + + psy_do_property(tx->wrl_name, get, POWER_SUPPLY_EXT_PROP_WIRELESS_TX_UNO_IIN, value); + iout = value.intval; + + psy_do_property(tx->wrl_name, get, POWER_SUPPLY_EXT_PROP_WIRELESS_OP_FREQ, value); + freq = value.intval; + + return (vout <= 0) && (iout <= 0) && (freq <= 0); +} + +static void cb_tx_err_work(struct work_struct *work) +{ + struct sb_tx *tx = container_of(work, + struct sb_tx, tx_err_work.work); + + tx_log("start!\n"); + + if (check_tx_err_state(tx)) { + union power_supply_propval value = { 0, }; + + tx_log("set tx retry!!!\n"); + value.intval = BATT_TX_EVENT_WIRELESS_TX_ETC; + psy_do_property("wireless", set, POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ERR, value); + } + + __pm_relax(tx->ws); +} + +static ssize_t show_attrs(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t store_attrs(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +#define TX_SYSFS_ATTR(_name) \ +{ \ + .attr = {.name = #_name, .mode = 0664}, \ + .show = show_attrs, \ + .store = store_attrs, \ +} + +static struct device_attribute tx_attr[] = { + TX_SYSFS_ATTR(tx_test), +}; + +enum tx_attrs { + TX_TEST = 0, +}; + +static ssize_t show_attrs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return 0; +} + +static ssize_t store_attrs(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + return count; +} + +static int sb_noti_handler(struct notifier_block *nb, unsigned long action, void *data) +{ + return 0; +} + +#ifdef CONFIG_OF +static int sb_tx_parse_aov_dt(struct device_node *np, struct sb_tx_aov *aov) +{ + if (of_property_read_bool(np, "disable")) + return -1; + + sb_of_parse_u32(np, aov, start_vout, WC_TX_VOUT_5500MV); + sb_of_parse_u32(np, aov, phm_icl, 800); + sb_of_parse_u32(np, aov, phm_icl_full, 100); + sb_of_parse_u32(np, aov, low_freq, 131); + sb_of_parse_u32(np, aov, high_freq, 147); + sb_of_parse_u32(np, aov, delay, 3000); + sb_of_parse_u32(np, aov, preset_delay, 3000); + sb_of_parse_u32(np, aov, vout_min, 5000); + return 0; +} + +static int sb_tx_parse_dt(struct sb_tx *tx) +{ + struct device_node *np, *child; + int ret = 0; + + np = of_find_node_by_name(NULL, TX_MODULE_NAME); + if (!np) + return -ENODEV; + + for_each_child_of_node(np, child) { + if (!strcmp(child->name, "aov")) { + ret = sb_tx_parse_aov_dt(child, &tx->aov); + + tx->aov.enable = !(ret); + tx_log("AOV = %s\n", tx->aov.enable ? "Enable" : "Disable"); + } + } + + np = of_find_node_by_name(NULL, "battery"); + if (np) { + ret = of_property_read_string(np, + "battery,fuelgauge_name", (const char **)&tx->fg_name); + if (ret) + tx_log("failed to get fg name in battery dt (ret = %d)\n", ret); + } + + return ret; +} +#else +static int sb_tx_parse_dt(struct sb_tx *tx) +{ + return 0; +} +#endif + +int sb_tx_init(struct sec_battery_info *battery, char *wrl_name) +{ + struct sb_tx *tx; + int ret = 0; + + if ((battery == NULL) || (wrl_name == NULL)) + return -EINVAL; + + /* temporary code */ + if (get_sb_tx() != NULL) + return 0; + + mutex_lock(&tx_lock); + + tx = kzalloc(sizeof(struct sb_tx), GFP_KERNEL); + if (!tx) { + mutex_unlock(&tx_lock); + return -ENOMEM; + } + + ret = sb_tx_parse_dt(tx); + if (ret) + goto err_parse_dt; + + tx->wq = create_singlethread_workqueue(TX_MODULE_NAME); + if (!tx->wq) { + ret = -ENOMEM; + goto err_parse_dt; + } + + tx->ws = wakeup_source_register(battery->dev, TX_MODULE_NAME); + + INIT_DELAYED_WORK(&tx->tx_err_work, cb_tx_err_work); + + mutex_init(&tx->event_lock); + + tx->battery = battery; + tx->wrl_name = wrl_name; + tx->enable = false; + + ret = sb_sysfs_add_attrs(TX_MODULE_NAME, tx, tx_attr, ARRAY_SIZE(tx_attr)); + tx_log("sb_sysfs_add_attrs ret = %s\n", (ret) ? "fail" : "success"); + + ret = sb_notify_register(&tx->nb, sb_noti_handler, TX_MODULE_NAME, SB_DEV_MODULE); + tx_log("sb_notify_register ret = %s\n", (ret) ? "fail" : "success"); + + gtx = tx; + + mutex_unlock(&tx_lock); + return 0; + +err_parse_dt: + kfree(tx); + + mutex_unlock(&tx_lock); + return ret; +} +EXPORT_SYMBOL(sb_tx_init); + +int sb_tx_set_enable(bool tx_enable, int cable_type) +{ + struct sb_tx *tx = get_sb_tx(); + + if (tx_enable) { + if (check_tx_err_state(tx)) { + tx_log("abnormal case - run tx err work!\n"); + + __pm_stay_awake(tx->ws); + queue_delayed_work(tx->wq, &tx->tx_err_work, msecs_to_jiffies(1000)); + } + } else { + cancel_delayed_work(&tx->tx_err_work); + __pm_relax(tx->ws); + } + + return 0; +} +EXPORT_SYMBOL(sb_tx_set_enable); + +bool sb_tx_get_enable(void) +{ + struct sb_tx *tx = get_sb_tx(); + + return (tx != NULL) ? tx->enable : false; +} +EXPORT_SYMBOL(sb_tx_get_enable); + +int sb_tx_set_event(int value, int mask) +{ + return 0; +} +EXPORT_SYMBOL(sb_tx_set_event); + +int sb_tx_psy_set_property(enum power_supply_property psp, const union power_supply_propval *value) +{ + return 0; +} +EXPORT_SYMBOL(sb_tx_psy_set_property); + +int sb_tx_psy_get_property(enum power_supply_property psp, union power_supply_propval *value) +{ + return 0; +} +EXPORT_SYMBOL(sb_tx_psy_get_property); + +int sb_tx_monitor(int cable_type, int capacity, int lcd_state) +{ + return 0; +} +EXPORT_SYMBOL(sb_tx_monitor); + diff --git a/drivers/battery/common/sb_tx.h b/drivers/battery/common/sb_tx.h new file mode 100644 index 000000000000..4b0c421447d4 --- /dev/null +++ b/drivers/battery/common/sb_tx.h @@ -0,0 +1,110 @@ +/* + * sb_tx.h + * Samsung Mobile Wireless TX Header + * + * Copyright (C) 2021 Samsung Electronics, 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 __SB_TX_H +#define __SB_TX_H __FILE__ + +#define TX_MODULE_NAME "sb-tx" + +/* tx_event */ +#define SB_TX_EVENT_TX_STATUS 0x00000001 +#define SB_TX_EVENT_RX_CONNECT 0x00000002 +#define SB_TX_EVENT_TX_FOD 0x00000004 +#define SB_TX_EVENT_TX_HIGH_TEMP 0x00000008 +#define SB_TX_EVENT_RX_UNSAFE_TEMP 0x00000010 +#define SB_TX_EVENT_RX_CHG_SWITCH 0x00000020 +#define SB_TX_EVENT_RX_CS100 0x00000040 +#define SB_TX_EVENT_TX_OTG_ON 0x00000080 +#define SB_TX_EVENT_TX_LOW_TEMP 0x00000100 +#define SB_TX_EVENT_TX_SOC_DRAIN 0x00000200 +#define SB_TX_EVENT_TX_CRITICAL_EOC 0x00000400 +#define SB_TX_EVENT_TX_CAMERA_ON 0x00000800 +#define SB_TX_EVENT_TX_OCP 0x00001000 +#define SB_TX_EVENT_TX_MISALIGN 0x00002000 +#define SB_TX_EVENT_TX_ETC 0x00004000 +#define SB_TX_EVENT_TX_RETRY 0x00008000 +#define SB_TX_EVENT_TX_5V_TA 0x00010000 +#define SB_TX_EVENT_TX_AC_MISSING 0x00020000 +#define SB_TX_EVENT_ALL_MASK 0x0003ffff +#define SB_TX_EVENT_TX_ERR (SB_TX_EVENT_TX_FOD | \ + SB_TX_EVENT_TX_HIGH_TEMP | SB_TX_EVENT_RX_UNSAFE_TEMP | \ + SB_TX_EVENT_RX_CHG_SWITCH | SB_TX_EVENT_RX_CS100 | \ + SB_TX_EVENT_TX_OTG_ON | SB_TX_EVENT_TX_LOW_TEMP | \ + SB_TX_EVENT_TX_SOC_DRAIN | SB_TX_EVENT_TX_CRITICAL_EOC | \ + SB_TX_EVENT_TX_CAMERA_ON | SB_TX_EVENT_TX_OCP | \ + SB_TX_EVENT_TX_MISALIGN | SB_TX_EVENT_TX_ETC | \ + SB_TX_EVENT_TX_5V_TA | SB_TX_EVENT_TX_AC_MISSING) + +#define SB_TX_RETRY_NONE 0x0000 +#define SB_TX_RETRY_MISALIGN 0x0001 +#define SB_TX_RETRY_CAMERA 0x0002 +#define SB_TX_RETRY_CALL 0x0004 +#define SB_TX_RETRY_MIX_TEMP 0x0008 +#define SB_TX_RETRY_HIGH_TEMP 0x0010 +#define SB_TX_RETRY_LOW_TEMP 0x0020 +#define SB_TX_RETRY_OCP 0x0040 + +enum power_supply_property; +union power_supply_propval; + +#define SB_TX_DISABLE (-2222) +#if defined(CONFIG_WIRELESS_TX_MODE) +int sb_tx_init(struct sec_battery_info *battery, char *wrl_name); + +int sb_tx_set_enable(bool tx_enable, int cable_type); +bool sb_tx_get_enable(void); + +int sb_tx_set_event(int value, int mask); + +/* for set/get properties - called in wireless set/get property */ +int sb_tx_psy_set_property(enum power_supply_property psp, const union power_supply_propval *value); +int sb_tx_psy_get_property(enum power_supply_property psp, union power_supply_propval *value); + +/* for monitor tx state - called in battery drv */ +int sb_tx_monitor(int cable_type, int capacity, int lcd_state); + +/* temporary function */ +int sb_tx_init_aov(void); +bool sb_tx_is_aov_enabled(int cable_type); +int sb_tx_monitor_aov(int vout, bool phm); +#else +static inline int sb_tx_init(struct sec_battery_info *battery, char *wrl_name) { return SB_TX_DISABLE; } + +static inline int sb_tx_set_enable(bool tx_enable, int cable_type) { return SB_TX_DISABLE; } +static inline bool sb_tx_get_enable(void) { return false; } + +static inline int sb_tx_set_event(int value, int mask) { return SB_TX_DISABLE; } + +/* for set/get properties - called in wireless set/get property */ +static inline int sb_tx_psy_set_property(enum power_supply_property psp, const union power_supply_propval *value) +{ return SB_TX_DISABLE; } +static inline int sb_tx_psy_get_property(enum power_supply_property psp, union power_supply_propval *value) +{ return SB_TX_DISABLE; } + +/* for monitor tx state - called in battery drv */ +static inline int sb_tx_monitor(int cable_type, int capacity, int lcd_state) { return SB_TX_DISABLE; } + + +/* temporary function */ +static inline int sb_tx_init_aov(void) { return SB_TX_DISABLE; } +static inline bool sb_tx_is_aov_enabled(int cable_type) { return false; } +static inline int sb_tx_monitor_aov(int vout, bool phm) { return SB_TX_DISABLE; } +#endif + +#endif /* __SB_TX_H */ + diff --git a/drivers/battery/common/sb_wireless.c b/drivers/battery/common/sb_wireless.c new file mode 100644 index 000000000000..354bad816c2f --- /dev/null +++ b/drivers/battery/common/sb_wireless.c @@ -0,0 +1,218 @@ +/* + * sb_wireless.c + * Samsung Mobile Battery Wireless Module + * + * Copyright (C) 2023 Samsung Electronics + * + * + * 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. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#define sbw_log(str, ...) pr_info("[SB-WIRELESS]:%s: "str, __func__, ##__VA_ARGS__) +#define SBW_MODULE_NAME "sb-wireless" + +const char *sb_wrl_op_mode_str(int op_mode) +{ + switch (op_mode) { + case WPC_OP_MODE_PPDE: + return "PPDE"; + case WPC_OP_MODE_EPP: + return "EPP"; + case WPC_OP_MODE_MPP: + return "MPP"; + case WPC_OP_MODE_BPP: + return "BPP"; + } + + return "Unknown"; +} +EXPORT_SYMBOL(sb_wrl_op_mode_str); + +struct sb_wireless { + struct notifier_block nb; + + const struct sb_wireless_op *op; + void *pdata; +}; + +static struct sb_wireless *get_inst(void) +{ + static struct sb_wireless *sbw; + + if (sbw) + return sbw; + + sbw = kzalloc(sizeof(struct sb_wireless), GFP_KERNEL); + return sbw; +} + +static bool check_valid_op(const struct sb_wireless_op *op) +{ + return (op != NULL) && + (op->get_op_mode != NULL) && + (op->get_qi_ver != NULL) && + (op->get_auth_mode != NULL); +} + +static int get_op_mode(void) +{ + struct sb_wireless *sbw = get_inst(); + + if (!sbw) + return -ENOMEM; + + if (!check_valid_op(sbw->op)) + return -ENODEV; + + return sbw->op->get_op_mode(sbw->pdata); +} + +static int get_qi_ver(void) +{ + struct sb_wireless *sbw = get_inst(); + + if (!sbw) + return -ENOMEM; + + if (!check_valid_op(sbw->op)) + return -ENODEV; + + return sbw->op->get_qi_ver(sbw->pdata); +} + +static int get_auth_mode(void) +{ + struct sb_wireless *sbw = get_inst(); + + if (!sbw) + return -ENOMEM; + + if (!check_valid_op(sbw->op)) + return -ENODEV; + + return sbw->op->get_auth_mode(sbw->pdata); +} + +int sb_wireless_set_op(void *pdata, const struct sb_wireless_op *op) +{ + struct sb_wireless *sbw = get_inst(); + + if (!sbw) + return -ENOMEM; + + if (check_valid_op(sbw->op)) + return -EBUSY; + + if (!pdata || !check_valid_op(op)) + return -EINVAL; + + sbw->pdata = pdata; + sbw->op = op; + return 0; +} +EXPORT_SYMBOL(sb_wireless_set_op); + +static ssize_t +sb_wireless_show_attrs(struct device *, struct device_attribute *, char *); +static ssize_t +sb_wireless_store_attrs(struct device *, struct device_attribute *, const char *, size_t); + +#define SB_WIRELESS_ATTR(_name) \ +{ \ + .attr = {.name = #_name, .mode = 0664}, \ + .show = sb_wireless_show_attrs, \ + .store = sb_wireless_store_attrs, \ +} + +enum { + WPC_OP_MODE = 0, + WPC_QI_VER, + WPC_AUTH_MODE, +}; + +static struct device_attribute sb_wireless_attrs[] = { + SB_WIRELESS_ATTR(wpc_op_mode), + SB_WIRELESS_ATTR(wpc_qi_ver), + SB_WIRELESS_ATTR(wpc_auth_mode), +}; + +static ssize_t sb_wireless_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const ptrdiff_t offset = attr - sb_wireless_attrs; + int i = 0; + + switch (offset) { + case WPC_OP_MODE: + i += scnprintf(buf, PAGE_SIZE, "%d\n", get_op_mode()); + break; + case WPC_QI_VER: + i += scnprintf(buf, PAGE_SIZE, "%d\n", get_qi_ver()); + break; + case WPC_AUTH_MODE: + i += scnprintf(buf, PAGE_SIZE, "%d\n", get_auth_mode()); + break; + default: + return -EINVAL; + } + + return i; +} + +static ssize_t sb_wireless_store_attrs(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + const ptrdiff_t offset = attr - sb_wireless_attrs; + + switch (offset) { + case WPC_OP_MODE: + break; + case WPC_QI_VER: + break; + case WPC_AUTH_MODE: + break; + default: + return -EINVAL; + } + + return count; +} + +static int sb_noti_handler(struct notifier_block *nb, unsigned long action, void *data) +{ + return 0; +} + +static int __init sb_wireless_init(void) +{ + struct sb_wireless *sbw = get_inst(); + int ret = 0; + + if (!sbw) + return -ENOMEM; + + ret = sb_sysfs_add_attrs(SBW_MODULE_NAME, sbw, sb_wireless_attrs, ARRAY_SIZE(sb_wireless_attrs)); + sbw_log("sb_sysfs_add_attrs ret = %s\n", (ret) ? "fail" : "success"); + + ret = sb_notify_register(&sbw->nb, sb_noti_handler, SBW_MODULE_NAME, SB_DEV_MODULE); + sbw_log("sb_notify_register ret = %s\n", (ret) ? "fail" : "success"); + + return ret; +} +module_init(sb_wireless_init); + +MODULE_DESCRIPTION("Samsung Battery Wireless"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); \ No newline at end of file diff --git a/drivers/battery/common/sec_adc.c b/drivers/battery/common/sec_adc.c new file mode 100644 index 000000000000..ef2abffdc0e5 --- /dev/null +++ b/drivers/battery/common/sec_adc.c @@ -0,0 +1,400 @@ +/* + * sec_adc.c + * Samsung Mobile Battery Driver + * + * Copyright (C) 2012 Samsung Electronics + * + * + * 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. + */ + +#include +#include "sec_adc.h" + +#define DEBUG +#if defined(CONFIG_SEC_KUNIT) +#include +#endif + +struct adc_list { + const char *name; + struct iio_channel *channel; + bool is_used; + int prev_value; +}; +static DEFINE_MUTEX(adclock); + +static struct adc_list batt_adc_list[SEC_BAT_ADC_CHANNEL_NUM] = { + {.name = "adc-cable"}, + {.name = "adc-bat-id"}, + {.name = "adc-temp"}, + {.name = "adc-temp-amb"}, + {.name = "adc-full"}, + {.name = "adc-volt"}, + {.name = "adc-chg-temp"}, + {.name = "adc-in-bat"}, + {.name = "adc-dischg"}, + {.name = "adc-dischg-ntc"}, + {.name = "adc-wpc-temp"}, + {.name = "adc-sub-chg-temp"}, + {.name = "adc-usb-temp"}, + {.name = "adc-sub-bat"}, + {.name = "adc-blkt-temp"}, +}; + +static int adc_init_count; + +#if defined(CONFIG_SEC_KUNIT) +int __mockable adc_read_type(struct device *dev, int channel, int batt_adc_type) +#else +int adc_read_type(struct device *dev, int channel, int batt_adc_type) +#endif +{ + int adc = -1; + int ret = 0; + int retry_cnt = RETRY_CNT; + + /* adc init retry because adc init was failed when probe time */ + if (!adc_init_count) { + int i = 0; + struct iio_channel *temp_adc; + + pr_err("%s: ADC init retry!!\n", __func__); + for (i = 0; i < SEC_BAT_ADC_CHANNEL_NUM; i++) { + temp_adc = iio_channel_get(dev, batt_adc_list[i].name); + batt_adc_list[i].channel = temp_adc; + batt_adc_list[i].is_used = !IS_ERR_OR_NULL(temp_adc); + if (batt_adc_list[i].is_used) + adc_init_count++; + } + } + + if (batt_adc_list[channel].is_used) { + do { + switch (batt_adc_type) { + case SEC_BATTERY_ADC_RAW: + ret = iio_read_channel_raw(batt_adc_list[channel].channel, &adc); + break; + default: + /* SEC_BATTERY_ADC_PROCESSED */ + ret = iio_read_channel_processed(batt_adc_list[channel].channel, &adc); + break; + } + + retry_cnt--; + } while ((retry_cnt > 0) && (adc < 0)); + } else { + ret = 0; + } + + if (retry_cnt <= 0) { + pr_err("%s: Error in ADC\n", __func__); + adc = batt_adc_list[channel].prev_value; + } else { + batt_adc_list[channel].prev_value = adc; + } + + pr_debug("%s: [%d] ADC (type:%s) = %d\n", __func__, channel, + (batt_adc_type ? "raw" : "proc."), adc); + + return adc; +} + +int sec_bat_get_adc_data(struct device *dev, int adc_ch, int count, int batt_adc_type) +{ + int adc_data = 0; + int adc_max = 0; + int adc_min = 0xFFFF; + int adc_total = 0; + int i = 0; + + if (count < 3) + count = 3; + + for (i = 0; i < count; i++) { + mutex_lock(&adclock); + adc_data = adc_read_type(dev, adc_ch, batt_adc_type); + mutex_unlock(&adclock); + + if (i != 0) { + if (adc_data > adc_max) + adc_max = adc_data; + else if (adc_data < adc_min) + adc_min = adc_data; + } else { + adc_max = adc_data; + adc_min = adc_data; + } + adc_total += adc_data; + } + + return (adc_total - adc_max - adc_min) / (count - 2); +} +EXPORT_SYMBOL(sec_bat_get_adc_data); + +int sec_bat_get_charger_type_adc(struct sec_battery_info *battery) +{ + /* It is true something valid is connected to the device for charging. + * By default this something is considered to be USB. + */ + int result = SEC_BATTERY_CABLE_USB; + + int adc = 0; + int i = 0; + + /* Do NOT check cable type when cable_switch_check() returns false + * and keep current cable type + */ + if (battery->pdata->cable_switch_check && !battery->pdata->cable_switch_check()) + return battery->cable_type; + + adc = sec_bat_get_adc_data(battery->dev, SEC_BAT_ADC_CHANNEL_CABLE_CHECK, + battery->pdata->adc_check_count, battery->pdata->adc_read_type); + + /* Do NOT check cable type when cable_switch_normal() returns false + * and keep current cable type + */ + if (battery->pdata->cable_switch_normal && !battery->pdata->cable_switch_normal()) + return battery->cable_type; + + for (i = 0; i < SEC_BATTERY_CABLE_MAX; i++) + if ((adc > battery->pdata->cable_adc_value[i].min) && (adc < battery->pdata->cable_adc_value[i].max)) + break; + + if (i >= SEC_BATTERY_CABLE_MAX) + dev_err(battery->dev, "%s: default USB\n", __func__); + else + result = i; + + dev_dbg(battery->dev, "%s: result(%d), adc(%d)\n", __func__, result, adc); + + return result; +} +EXPORT_SYMBOL(sec_bat_get_charger_type_adc); + +bool sec_bat_convert_adc_to_val(int adc, int offset, sec_bat_adc_table_data_t *adc_table, int size, int *value) +{ + int temp = 0; + int low = 0; + int high = 0; + int mid = 0; + + if (size <= 0) + return false; + + adc = (offset) ? (offset - adc) : (adc); + + if (adc_table[0].adc >= adc) { + temp = adc_table[0].data; + goto temp_by_adc_goto; + } else if (adc_table[size-1].adc <= adc) { + temp = adc_table[size-1].data; + goto temp_by_adc_goto; + } + + high = size - 1; + + while (low <= high) { + mid = (low + high) / 2; + if (adc_table[mid].adc > adc) + high = mid - 1; + else if (adc_table[mid].adc < adc) + low = mid + 1; + else { + temp = adc_table[mid].data; + goto temp_by_adc_goto; + } + } + + temp = adc_table[high].data; + temp += ((adc_table[low].data - adc_table[high].data) * + (adc - adc_table[high].adc)) / + (adc_table[low].adc - adc_table[high].adc); + +temp_by_adc_goto: + *value = temp; + pr_debug("%s: Temp(%d), Temp-ADC(%d)\n", __func__, temp, adc); + + return true; +} +EXPORT_SYMBOL(sec_bat_convert_adc_to_val); + +int sec_bat_get_inbat_vol_by_adc(struct sec_battery_info *battery) +{ + int inbat = 0; + int inbat_adc; + int low = 0; + int high = 0; + int mid = 0; + const sec_bat_adc_table_data_t *inbat_adc_table; + unsigned int inbat_adc_table_size; + + if (!battery->pdata->inbat_adc_table) { + dev_err(battery->dev, "%s: not designed to read in-bat voltage\n", __func__); + return -1; + } + + inbat_adc_table = battery->pdata->inbat_adc_table; + inbat_adc_table_size = battery->pdata->inbat_adc_table_size; + + inbat_adc = sec_bat_get_adc_data(battery->dev, SEC_BAT_ADC_CHANNEL_INBAT_VOLTAGE, + battery->pdata->adc_check_count, battery->pdata->adc_read_type); + if (inbat_adc <= 0) + return inbat_adc; + + battery->inbat_adc = inbat_adc; + + if (inbat_adc_table[0].adc <= inbat_adc) { + inbat = inbat_adc_table[0].data; + goto inbat_by_adc_goto; + } else if (inbat_adc_table[inbat_adc_table_size-1].adc >= inbat_adc) { + inbat = inbat_adc_table[inbat_adc_table_size-1].data; + goto inbat_by_adc_goto; + } + + high = inbat_adc_table_size - 1; + + while (low <= high) { + mid = (low + high) / 2; + if (inbat_adc_table[mid].adc < inbat_adc) + high = mid - 1; + else if (inbat_adc_table[mid].adc > inbat_adc) + low = mid + 1; + else { + inbat = inbat_adc_table[mid].data; + goto inbat_by_adc_goto; + } + } + + inbat = inbat_adc_table[high].data; + inbat += + ((inbat_adc_table[low].data - inbat_adc_table[high].data) * + (inbat_adc - inbat_adc_table[high].adc)) / + (inbat_adc_table[low].adc - inbat_adc_table[high].adc); + + if (inbat < 0) + inbat = 0; + +inbat_by_adc_goto: + dev_info(battery->dev, "%s: inbat(%d), inbat-ADC(%d)\n", __func__, inbat, inbat_adc); + + return inbat; +} +EXPORT_SYMBOL(sec_bat_get_inbat_vol_by_adc); + +bool sec_bat_check_vf_adc(struct sec_battery_info *battery) +{ + int adc = 0; + + adc = sec_bat_get_adc_data(battery->dev, + SEC_BAT_ADC_CHANNEL_BATID_CHECK, + battery->pdata->adc_check_count, + battery->pdata->adc_read_type); + + if (adc < 0) { + dev_err(battery->dev, "%s: VF ADC error\n", __func__); + adc = battery->check_adc_value; + } else + battery->check_adc_value = adc; + + if ((battery->check_adc_value <= battery->pdata->check_adc_max) && + (battery->check_adc_value >= battery->pdata->check_adc_min)) { + return true; + } else { + dev_info(battery->dev, "%s: VF_ADC(%d) is out of range(min:%d, max:%d)\n", + __func__, battery->check_adc_value, battery->pdata->check_adc_min, battery->pdata->check_adc_max); + return false; + } +} +EXPORT_SYMBOL(sec_bat_check_vf_adc); + +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) +int sec_bat_get_direct_chg_temp_adc( + struct sec_battery_info *battery, int adc_data, int count, int check_type) +{ + int temp = 0; + int temp_adc; + int low = 0; + int high = 0; + int mid = 0; + const sec_bat_adc_table_data_t *temp_adc_table = {0 , }; + unsigned int temp_adc_table_size = 0; + int offset = battery->pdata->dchg_thm_info.offset; + + if (check_type == SEC_BATTERY_TEMP_CHECK_FAKE) + return FAKE_TEMP; + + temp_adc = (offset) ? (offset - adc_data) : (adc_data); + if (temp_adc < 0) + return 0; + + temp_adc_table = battery->pdata->dchg_thm_info.adc_table; + temp_adc_table_size = battery->pdata->dchg_thm_info.adc_table_size; + battery->pdata->dchg_thm_info.adc = temp_adc; + + if (temp_adc_table[0].adc >= temp_adc) { + temp = temp_adc_table[0].data; + goto direct_chg_temp_goto; + } else if (temp_adc_table[temp_adc_table_size - 1].adc <= temp_adc) { + temp = temp_adc_table[temp_adc_table_size - 1].data; + goto direct_chg_temp_goto; + } + + high = temp_adc_table_size - 1; + while (low <= high) { + mid = (low + high) / 2; + if (temp_adc_table[mid].adc > temp_adc) + high = mid - 1; + else if (temp_adc_table[mid].adc < temp_adc) + low = mid + 1; + else { + temp = temp_adc_table[mid].data; + goto direct_chg_temp_goto; + } + } + + temp = temp_adc_table[high].data; + temp += ((temp_adc_table[low].data - temp_adc_table[high].data) * + (temp_adc - temp_adc_table[high].adc)) / + (temp_adc_table[low].adc - temp_adc_table[high].adc); + +direct_chg_temp_goto: + dev_info(battery->dev, "%s: temp(%d), direct-chg-temp-ADC(%d)\n", __func__, temp, adc_data); + + return temp; +} +EXPORT_SYMBOL(sec_bat_get_direct_chg_temp_adc); +#endif + +void adc_init(struct platform_device *pdev, struct sec_battery_info *battery) +{ + int i = 0; + struct iio_channel *temp_adc; + + for (i = 0; i < SEC_BAT_ADC_CHANNEL_NUM; i++) { + temp_adc = iio_channel_get(&pdev->dev, batt_adc_list[i].name); + batt_adc_list[i].channel = temp_adc; + batt_adc_list[i].is_used = !IS_ERR_OR_NULL(temp_adc); + if (batt_adc_list[i].is_used) + battery->adc_init_count++; + } + + for (i = 0; i < SEC_BAT_ADC_CHANNEL_NUM; i++) + pr_info("%s: %s - %s\n", __func__, + batt_adc_list[i].name, batt_adc_list[i].is_used ? "used" : "not used"); +} +EXPORT_SYMBOL(adc_init); + +void adc_exit(struct sec_battery_info *battery) +{ + int i = 0; + + for (i = 0; i < SEC_BAT_ADC_CHANNEL_NUM; i++) { + if (batt_adc_list[i].is_used) + iio_channel_release(batt_adc_list[i].channel); + } +} +EXPORT_SYMBOL(adc_exit); + diff --git a/drivers/battery/common/sec_adc.h b/drivers/battery/common/sec_adc.h new file mode 100644 index 000000000000..e5bf61dd5c30 --- /dev/null +++ b/drivers/battery/common/sec_adc.h @@ -0,0 +1,33 @@ +/* + * sec_adc.h + * Samsung Mobile Charger Header + * + * Copyright (C) 2012 Samsung Electronics, 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 __SEC_ADC_H +#define __SEC_ADC_H __FILE__ + +#include +#include "sec_battery.h" +#include "sec_charging_common.h" + +#define VENDOR_UNKNOWN 0 +#define VENDOR_LSI 1 +#define VENDOR_QCOM 2 +#define RETRY_CNT 3 +#define FAKE_TEMP 300 + +#endif /* __SEC_ADC_H */ + diff --git a/drivers/battery/common/sec_battery.c b/drivers/battery/common/sec_battery.c new file mode 100644 index 000000000000..f0921d71ede2 --- /dev/null +++ b/drivers/battery/common/sec_battery.c @@ -0,0 +1,9704 @@ +/* + * sec_battery.c + * Samsung Mobile Battery Driver + * + * Copyright (C) 2021 Samsung Electronics + * + * + * 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. + */ +#include +#include + +#include "sec_battery.h" +#include "sec_battery_sysfs.h" +#include "sec_battery_dt.h" +#include "sec_battery_ttf.h" +#if defined(CONFIG_SEC_COMMON) +#include +#endif + +#if IS_ENABLED(CONFIG_SEC_ABC) +#include +#endif + +#if defined(CONFIG_ARCH_QCOM) && !(defined(CONFIG_ARCH_EXYNOS) || defined(CONFIG_ARCH_MEDIATEK)) +#include +#endif +#include + +#if defined(CONFIG_SEC_KUNIT) +#include +#else +#define __visible_for_testing static +#endif + +#if defined(CONFIG_UML) +#include "kunit_test/uml_dummy.h" +#endif + +#include "battery_logger.h" +#include "sb_tx.h" +#include "sb_batt_dump.h" +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) && IS_ENABLED(CONFIG_LSI_IFPMIC) +#include +#endif + +static unsigned int __read_mostly lpcharge; +module_param(lpcharge, uint, 0444); +static int __read_mostly fg_reset; +module_param(fg_reset, int, 0444); +static int factory_mode; +module_param(factory_mode, int, 0444); +static unsigned int __read_mostly charging_mode; +module_param(charging_mode, uint, 0444); +static unsigned int __read_mostly pd_disable; +module_param(pd_disable, uint, 0444); +static char * __read_mostly sales_code; +module_param(sales_code, charp, 0444); +#if IS_ENABLED(CONFIG_USB_FACTORY_MODE) +static char __read_mostly *f_mode; +module_param(f_mode, charp, 0444); +#endif + +static const char *sec_voter_name[] = { + FOREACH_VOTER(GENERATE_STRING) +}; + +static enum power_supply_property sec_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TEMP_AMBIENT, +#if IS_ENABLED(CONFIG_FUELGAUGE_MAX77705) + POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_POWER_AVG, +#endif + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, + POWER_SUPPLY_PROP_CHARGE_COUNTER, +}; + +static enum power_supply_property sec_power_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static enum power_supply_property sec_wireless_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_PRESENT, +}; + +static enum power_supply_property sec_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_TEMP, +}; + +static enum power_supply_property sec_otg_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_MAX, +}; + +static char *supply_list[] = { + "battery", +}; + +const char *sb_get_ct_str(int ct) +{ + switch (ct) { + case SEC_BATTERY_CABLE_UNKNOWN: + return "UNKNOWN"; + case SEC_BATTERY_CABLE_NONE: + return "NONE"; + case SEC_BATTERY_CABLE_PREPARE_TA: + return "PREPARE_TA"; + case SEC_BATTERY_CABLE_TA: + return "TA"; + case SEC_BATTERY_CABLE_USB: + return "USB"; + case SEC_BATTERY_CABLE_USB_CDP: + return "USB_CDP"; + case SEC_BATTERY_CABLE_9V_TA: + return "9V_TA"; + case SEC_BATTERY_CABLE_9V_ERR: + return "9V_ERR"; + case SEC_BATTERY_CABLE_9V_UNKNOWN: + return "9V_UNKNOWN"; + case SEC_BATTERY_CABLE_12V_TA: + return "12V_TA"; + case SEC_BATTERY_CABLE_WIRELESS: + return "WC"; + case SEC_BATTERY_CABLE_HV_WIRELESS: + return "HV_WC"; + case SEC_BATTERY_CABLE_PMA_WIRELESS: + return "PMA_WC"; + case SEC_BATTERY_CABLE_WIRELESS_PACK: + return "WC_PACK"; + case SEC_BATTERY_CABLE_WIRELESS_HV_PACK: + return "WC_HV_PACK"; + case SEC_BATTERY_CABLE_WIRELESS_STAND: + return "WC_STAND"; + case SEC_BATTERY_CABLE_WIRELESS_HV_STAND: + return "WC_HV_STAND"; + case SEC_BATTERY_CABLE_QC20: + return "QC20"; + case SEC_BATTERY_CABLE_QC30: + return "QC30"; + case SEC_BATTERY_CABLE_PDIC: + return "PDIC"; + case SEC_BATTERY_CABLE_UARTOFF: + return "UARTOFF"; + case SEC_BATTERY_CABLE_OTG: + return "OTG"; + case SEC_BATTERY_CABLE_LAN_HUB: + return "LAN_HUB"; + case SEC_BATTERY_CABLE_LO_TA: + return "LO_TA"; + case SEC_BATTERY_CABLE_POWER_SHARING: + return "POWER_SHARING"; + case SEC_BATTERY_CABLE_HMT_CONNECTED: + return "HMT_CONNECTED"; + case SEC_BATTERY_CABLE_HMT_CHARGE: + return "HMT_CHARGE"; + case SEC_BATTERY_CABLE_HV_TA_CHG_LIMIT: + return "HV_TA_CHG_LIMIT"; + case SEC_BATTERY_CABLE_WIRELESS_VEHICLE: + return "WC_VEHICLE"; + case SEC_BATTERY_CABLE_WIRELESS_HV_VEHICLE: + return "WC_HV_VEHICLE"; + case SEC_BATTERY_CABLE_PREPARE_WIRELESS_HV: + return "WC_HV_PREPARE"; + case SEC_BATTERY_CABLE_TIMEOUT: + return "TIMEOUT"; + case SEC_BATTERY_CABLE_SMART_OTG: + return "SMART_OTG"; + case SEC_BATTERY_CABLE_SMART_NOTG: + return "SMART_NOTG"; + case SEC_BATTERY_CABLE_WIRELESS_TX: + return "WC_TX"; + case SEC_BATTERY_CABLE_HV_WIRELESS_20: + return "HV_WC_20"; + case SEC_BATTERY_CABLE_HV_WIRELESS_20_LIMIT: + return "HV_WC_20_LIMIT"; + case SEC_BATTERY_CABLE_WIRELESS_FAKE: + return "WC_FAKE"; + case SEC_BATTERY_CABLE_PREPARE_WIRELESS_20: + return "HV_WC_20_PREPARE"; + case SEC_BATTERY_CABLE_PDIC_APDO: + return "PDIC_APDO"; + case SEC_BATTERY_CABLE_POGO: + return "POGO"; + case SEC_BATTERY_CABLE_POGO_9V: + return "POGO_9V"; + case SEC_BATTERY_CABLE_FPDO_DC: + return "FPDO_DC"; + case SEC_BATTERY_CABLE_WIRELESS_EPP: + return "WC_EPP"; + case SEC_BATTERY_CABLE_WIRELESS_EPP_NV: + return "WC_EPP_NV"; + case SEC_BATTERY_CABLE_WIRELESS_EPP_FAKE: + return "WC_EPP_FAKE"; + default: + return "UNDEFINED"; + } +} +EXPORT_SYMBOL(sb_get_ct_str); + +const char *sb_get_cm_str(int charging_mode) +{ + switch (charging_mode) { + case SEC_BATTERY_CHARGING_NONE: + return "None"; + case SEC_BATTERY_CHARGING_1ST: + return "Normal"; + case SEC_BATTERY_CHARGING_2ND: + return "Additional"; + case SEC_BATTERY_CHARGING_RECHARGING: + return "Re-Charging"; + default: + return "UNDEFINED"; + } +} +EXPORT_SYMBOL(sb_get_cm_str); + +const char *sb_get_bst_str(int status) +{ + switch (status) { + case POWER_SUPPLY_STATUS_UNKNOWN: + return "Unknown"; + case POWER_SUPPLY_STATUS_CHARGING: + return "Charging"; + case POWER_SUPPLY_STATUS_DISCHARGING: + return "Discharging"; + case POWER_SUPPLY_STATUS_NOT_CHARGING: + return "Not-charging"; + case POWER_SUPPLY_STATUS_FULL: + return "Full"; + default: + return "UNDEFINED"; + } +} +EXPORT_SYMBOL(sb_get_bst_str); + +const char *sb_get_hl_str(int health) +{ + switch (health) { + case POWER_SUPPLY_HEALTH_GOOD: + return "Good"; + case POWER_SUPPLY_HEALTH_OVERHEAT: + return "Overheat"; + case POWER_SUPPLY_HEALTH_DEAD: + return "Dead"; + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + return "Over voltage"; + case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: + return "Unspecified failure"; + case POWER_SUPPLY_HEALTH_COLD: + return "Cold"; + case POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE: + return "Watchdog timer expire"; + case POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE: + return "Safety timer expire"; + case POWER_SUPPLY_HEALTH_OVERCURRENT: + return "Over current"; + case POWER_SUPPLY_HEALTH_CALIBRATION_REQUIRED: + return "Cal required"; + case POWER_SUPPLY_HEALTH_WARM: + return "Warm"; + case POWER_SUPPLY_HEALTH_COOL: + return "Cool"; + case POWER_SUPPLY_HEALTH_HOT: + return "Hot"; + case POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE: + return "UnderVoltage"; + case POWER_SUPPLY_EXT_HEALTH_OVERHEATLIMIT: + return "OverheatLimit"; + case POWER_SUPPLY_EXT_HEALTH_VSYS_OVP: + return "VsysOVP"; + case POWER_SUPPLY_EXT_HEALTH_VBAT_OVP: + return "VbatOVP"; + case POWER_SUPPLY_EXT_HEALTH_DC_ERR: + return "DCErr"; + default: + return "UNDEFINED"; + } +} +EXPORT_SYMBOL(sb_get_hl_str); + +const char *sb_get_tz_str(int tz) +{ + switch (tz) { + case BAT_THERMAL_NORMAL: + return "Normal"; + case BAT_THERMAL_COLD: + return "COLD"; + case BAT_THERMAL_COOL3: + return "COOL3"; + case BAT_THERMAL_COOL2: + return "COOL2"; + case BAT_THERMAL_COOL1: + return "COOL1"; + case BAT_THERMAL_WARM: + return "WARM"; + case BAT_THERMAL_OVERHEAT: + return "OVERHEAT"; + case BAT_THERMAL_OVERHEATLIMIT: + return "OVERHEATLIM"; + default: + return "UNDEFINED"; + } +} +EXPORT_SYMBOL(sb_get_tz_str); + +const char *sb_charge_mode_str(int charge_mode) +{ + switch (charge_mode) { + case SEC_BAT_CHG_MODE_BUCK_OFF: + return "Buck-Off"; + case SEC_BAT_CHG_MODE_CHARGING_OFF: + return "Charging-Off"; + case SEC_BAT_CHG_MODE_PASS_THROUGH: + return "Pass-Through"; + case SEC_BAT_CHG_MODE_CHARGING: + return "Charging-On"; + case SEC_BAT_CHG_MODE_OTG_ON: + return "OTG-On"; + case SEC_BAT_CHG_MODE_OTG_OFF: + return "OTG-Off"; + case SEC_BAT_CHG_MODE_UNO_ON: + return "UNO-On"; + case SEC_BAT_CHG_MODE_UNO_OFF: + return "UNO-Off"; + case SEC_BAT_CHG_MODE_UNO_ONLY: + return "UNO-Only"; + case SEC_BAT_CHG_MODE_NOT_SET: + return "Not-Set"; + case SEC_BAT_CHG_MODE_MAX: + return "Max"; + default: + return "UNDEFINED"; + } +} +EXPORT_SYMBOL(sb_charge_mode_str); + +const char *sb_rx_type_str(int type) +{ + switch (type) { + case NO_DEV: + return "No Dev"; + case OTHER_DEV: + return "Other Dev"; + case SS_GEAR: + return "Gear"; + case SS_PHONE: + return "Phone"; + case SS_BUDS: + return "Buds"; + default: + return "UNDEFINED"; + } +} +EXPORT_SYMBOL(sb_rx_type_str); + +const char *sb_vout_ctr_mode_str(int vout_mode) +{ + switch (vout_mode) { + case WIRELESS_VOUT_OFF: + return "Set VOUT Off"; + case WIRELESS_VOUT_NORMAL_VOLTAGE: + return "Set VOUT NV"; + case WIRELESS_VOUT_RESERVED: + return "Set VOUT Rsv"; + case WIRELESS_VOUT_HIGH_VOLTAGE: + return "Set VOUT HV"; + case WIRELESS_VOUT_CC_CV_VOUT: + return "Set VOUT CV"; + case WIRELESS_VOUT_CALL: + return "Set VOUT Call"; + case WIRELESS_VOUT_5V: + return "Set VOUT 5V"; + case WIRELESS_VOUT_9V: + return "Set VOUT 9V"; + case WIRELESS_VOUT_10V: + return "Set VOUT 10V"; + case WIRELESS_VOUT_11V: + return "Set VOUT 11V"; + case WIRELESS_VOUT_12V: + return "Set VOUT 12V"; + case WIRELESS_VOUT_12_5V: + return "Set VOUT 12.5V"; + case WIRELESS_VOUT_4_5V_STEP: + return "Set VOUT 4.5V Step"; + case WIRELESS_VOUT_5V_STEP: + return "Set VOUT 5V Step"; + case WIRELESS_VOUT_5_5V_STEP: + return "Set VOUT 5.5V Step"; + case WIRELESS_VOUT_9V_STEP: + return "Set VOUT 9V Step"; + case WIRELESS_VOUT_10V_STEP: + return "Set VOUT 10V Step"; + case WIRELESS_VOUT_OTG: + return "Set VOUT OTG"; + case WIRELESS_VOUT_5_5V: + return "Set VOUT 5.5V"; + default: + return "UNDEFINED"; + } +} +EXPORT_SYMBOL(sb_vout_ctr_mode_str); + +const char *sb_rx_vout_str(int vout) +{ + switch (vout) { + case MFC_VOUT_4_5V: + return "VOUT 4.5V"; + case MFC_VOUT_4_7V: + return "VOUT 4.7V"; + case MFC_VOUT_4_8V: + return "VOUT 4.8V"; + case MFC_VOUT_4_9V: + return "VOUT 4.9V"; + case MFC_VOUT_5V: + return "VOUT 5V"; + case MFC_VOUT_5_5V: + return "VOUT 5.5V"; + case MFC_VOUT_6V: + return "VOUT 6V"; + case MFC_VOUT_7V: + return "VOUT 7V"; + case MFC_VOUT_8V: + return "VOUT 8V"; + case MFC_VOUT_9V: + return "VOUT 9V"; + case MFC_VOUT_10V: + return "VOUT 10V"; + case MFC_VOUT_11V: + return "VOUT 11V"; + case MFC_VOUT_12V: + return "VOUT 12V"; + case MFC_VOUT_12_5V: + return "VOUT 12.5V"; + case MFC_VOUT_OTG: + return "VOUT OTG"; + default: + return "UNDEFINED"; + } +} +EXPORT_SYMBOL(sb_rx_vout_str); + +__visible_for_testing int sec_bat_check_afc_input_current(struct sec_battery_info *battery); +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) +__visible_for_testing void sec_bat_set_rp_current(struct sec_battery_info *battery, int cable_type); +#endif + +unsigned int sec_bat_get_lpmode(void) { return lpcharge; } +void sec_bat_set_lpmode(unsigned int value) { lpcharge = value; } +EXPORT_SYMBOL_KUNIT(sec_bat_set_lpmode); +int sec_bat_get_fgreset(void) { return fg_reset; } +int sec_bat_get_facmode(void) { return factory_mode; } +#if IS_ENABLED(CONFIG_USB_FACTORY_MODE) +void sec_bat_set_facmode(int value) { factory_mode = value; } +#endif +unsigned int sec_bat_get_chgmode(void) { return charging_mode; } +EXPORT_SYMBOL_KUNIT(sec_bat_get_chgmode); +void sec_bat_set_chgmode(unsigned int value) { charging_mode = value; } +EXPORT_SYMBOL_KUNIT(sec_bat_set_chgmode); +unsigned int sec_bat_get_dispd(void) { return pd_disable; } +EXPORT_SYMBOL_KUNIT(sec_bat_get_dispd); +void sec_bat_set_dispd(unsigned int value) { pd_disable = value; } +EXPORT_SYMBOL_KUNIT(sec_bat_set_dispd); +char *sec_bat_get_sales_code(void) {return sales_code; } + +#if !defined(CONFIG_SEC_FACTORY) +#define SALE_CODE_STR_LEN 3 +bool sales_code_is(char *str) +{ + if (sec_bat_get_sales_code() == NULL) + return false; + + pr_info("%s: %s\n", __func__, sec_bat_get_sales_code()); + + return !strncmp(sec_bat_get_sales_code(), str, SALE_CODE_STR_LEN + 1); +} +#endif + +static bool check_silent_type(int ct) +{ +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + if (ct == ATTACHED_DEV_RETRY_TIMEOUT_OPEN_MUIC || + ct == ATTACHED_DEV_RETRY_AFC_CHARGER_5V_MUIC || + ct == ATTACHED_DEV_RETRY_AFC_CHARGER_9V_MUIC) + return true; +#endif + return false; +} +static bool check_afc_disabled_type(int ct) +{ +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + if (ct == ATTACHED_DEV_AFC_CHARGER_DISABLED_MUIC) + return true; +#endif + return false; +} + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) +__visible_for_testing void sec_bat_divide_limiter_current(struct sec_battery_info *battery, int limiter_current) +{ + unsigned int main_current = 0, sub_current = 0, main_current_rate = 0, sub_current_rate = 0; + union power_supply_propval value = {0, }; + + if (is_pd_apdo_wire_type(battery->cable_type) && battery->pd_list.now_isApdo) { + if (limiter_current < battery->pdata->charging_current[SEC_BATTERY_CABLE_PDIC_APDO].fast_charging_current) + limiter_current = battery->pdata->charging_current[SEC_BATTERY_CABLE_PDIC_APDO].fast_charging_current; + } + + if (battery->pdata->sub_fto && limiter_current >= battery->pdata->sub_fto_current_thresh) { + value.intval = 3; + psy_do_property(battery->pdata->sub_limiter_name, set, + POWER_SUPPLY_EXT_PROP_CHG_MODE, value); + } else if (battery->pdata->sub_fto && limiter_current < battery->pdata->sub_fto_current_thresh) { + value.intval = 0; + psy_do_property(battery->pdata->sub_limiter_name, set, + POWER_SUPPLY_EXT_PROP_CHG_MODE, value); + } + + if (limiter_current >= battery->pdata->zone3_limiter_current) { + main_current_rate = battery->pdata->main_zone3_current_rate; + sub_current_rate = battery->pdata->sub_zone3_current_rate; + } else if (limiter_current >= battery->pdata->zone2_limiter_current) { + main_current_rate = battery->pdata->main_zone2_current_rate; + sub_current_rate = battery->pdata->sub_zone2_current_rate; + } else if (limiter_current > battery->pdata->zone1_limiter_current) { + main_current_rate = battery->pdata->main_zone1_current_rate; + sub_current_rate = battery->pdata->sub_zone1_current_rate; + } else { /* like discharge current 100mA */ + main_current_rate = battery->pdata->min_main_limiter_current; + sub_current_rate = battery->pdata->min_sub_limiter_current; + } + + /* divide setting has ratio value(percent value, max 99%) and current value(ex 1500mA) */ + main_current = (main_current_rate > 100) ? main_current_rate : ((main_current_rate * limiter_current) / 100); + sub_current = (sub_current_rate > 100) ? sub_current_rate : ((sub_current_rate * limiter_current) / 100); + + pr_info("%s: (%d) -> main_current(%d), sub_current(%d)\n", __func__, + limiter_current, main_current, sub_current); // debug + + if (battery->current_event & SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING_COOL3) { + if (battery->pdata->limiter_main_cool3_current) + main_current = battery->pdata->limiter_main_cool3_current; + if (battery->pdata->limiter_sub_cool3_current) + sub_current = battery->pdata->limiter_sub_cool3_current; + } else if (battery->current_event & SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING_COOL2) { + if (battery->pdata->limiter_main_cool2_current) + main_current = battery->pdata->limiter_main_cool2_current; + if (battery->pdata->limiter_sub_cool2_current) + sub_current = battery->pdata->limiter_sub_cool2_current; + } else if (battery->current_event & SEC_BAT_CURRENT_EVENT_HIGH_TEMP_SWELLING) { + if (battery->pdata->limiter_main_warm_current) + main_current = battery->pdata->limiter_main_warm_current; + if (battery->pdata->limiter_sub_warm_current) + sub_current = battery->pdata->limiter_sub_warm_current; + } else if (battery->current_event & SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING_COOL1) { + if (battery->pdata->limiter_main_cool1_current) + main_current = battery->pdata->limiter_main_cool1_current; + if (battery->pdata->limiter_sub_cool1_current) + sub_current = battery->pdata->limiter_sub_cool1_current; + } + + /* calculate main battery current */ + if (main_current > battery->pdata->max_main_limiter_current) + main_current = battery->pdata->max_main_limiter_current; + + /* calculate sub battery current */ + if (sub_current > battery->pdata->max_sub_limiter_current) + sub_current = battery->pdata->max_sub_limiter_current; + + pr_info("%s: main_current(%d), sub_current(%d)\n", __func__, + main_current, sub_current); + + battery->main_current = main_current; + battery->sub_current = sub_current; +} +EXPORT_SYMBOL_KUNIT(sec_bat_divide_limiter_current); + +__visible_for_testing void sec_bat_set_limiter_current(struct sec_battery_info *battery) +{ + union power_supply_propval value = {0, }; + + pr_info("%s: charge_m(%d), charge_s(%d)\n", __func__, + battery->main_current, battery->sub_current); + + value.intval = battery->main_current; + psy_do_property(battery->pdata->main_limiter_name, set, + POWER_SUPPLY_EXT_PROP_FASTCHG_LIMIT_CURRENT, value); + + value.intval = battery->sub_current; + psy_do_property(battery->pdata->sub_limiter_name, set, + POWER_SUPPLY_EXT_PROP_FASTCHG_LIMIT_CURRENT, value); +} +EXPORT_SYMBOL_KUNIT(sec_bat_set_limiter_current); +#endif + +__visible_for_testing int set_charging_current(void *data, int v) +{ + union power_supply_propval value = {0, }; + struct sec_battery_info *battery = data; + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + sec_bat_divide_limiter_current(battery, v); + /* to set higher current, ex) 500mA -> 1000mA */ + if (battery->charging_current < v) { + pr_info("%s: set limiter current right away\n", __func__); + sec_bat_set_limiter_current(battery); + } +#endif + + value.intval = v; + if (is_wireless_type(battery->cable_type)) { + if (battery->charging_current < v) { + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, value); + } else { + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_CONSTANT_CHARGE_CURRENT_WRL, value); + } + } else { + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, value); + } + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + /* to set lower current, ex) 1000mA -> 500mA */ + if (battery->charging_current > v) { + if (!is_wireless_type(battery->cable_type)) { + if (is_pd_apdo_wire_type(battery->cable_type) && battery->pd_list.now_isApdo) { + battery->set_lower_curr = true; + pr_info("%s: set limiter current in next polling\n", __func__); + } else { + pr_info("%s: set limiter current right away\n", __func__); + sec_bat_set_limiter_current(battery); + } + } + } +#endif + battery->charging_current = v; + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + pr_info("%s: power(%d), input(%d), charge(%d), charge_m(%d), charge_s(%d)\n", __func__, + battery->charge_power, battery->input_current, battery->charging_current, battery->main_current, battery->sub_current); +#else + pr_info("%s: power(%d), input(%d), charge(%d)\n", __func__, + battery->charge_power, battery->input_current, battery->charging_current); +#endif + return v; +} +EXPORT_SYMBOL_KUNIT(set_charging_current); + +__visible_for_testing int set_input_current(void *data, int v) +{ + union power_supply_propval value = {0, }; + struct sec_battery_info *battery = data; + + battery->input_current = v; + battery->charge_power = mW_by_mVmA(battery->input_voltage, v); + if (battery->charge_power > battery->max_charge_power) + battery->max_charge_power = battery->charge_power; + value.intval = v; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, value); + return v; +} +EXPORT_SYMBOL_KUNIT(set_input_current); + +__visible_for_testing int set_float_voltage(void *data, int voltage) +{ + struct sec_battery_info *battery = data; + union power_supply_propval value = {0, }; + + value.intval = voltage; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, value); + return voltage; +} +EXPORT_SYMBOL_KUNIT(set_float_voltage); + +__visible_for_testing int set_dc_float_voltage(void *data, int voltage) +{ + struct sec_battery_info *battery = data; + union power_supply_propval value = {0, }; + + value.intval = voltage; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_DIRECT_CONSTANT_CHARGE_VOLTAGE, value); + return voltage; +} +EXPORT_SYMBOL_KUNIT(set_dc_float_voltage); + +__visible_for_testing int set_topoff_current(void *data, int v) +{ + struct sec_battery_info *battery = data; + union power_supply_propval value = {0, }; + bool do_chgen_vote = false; + + if (battery->charging_mode == SEC_BATTERY_CHARGING_2ND || + battery->pdata->full_check_type == SEC_BATTERY_FULLCHARGED_CHGPSY || + battery->pdata->full_check_type == SEC_BATTERY_FULLCHARGED_CHGINT) + do_chgen_vote = true; + + if (do_chgen_vote) + sec_vote(battery->chgen_vote, VOTER_TOPOFF_CHANGE, true, SEC_BAT_CHG_MODE_CHARGING_OFF); + + value.intval = v; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, value); + + if (do_chgen_vote) + sec_vote(battery->chgen_vote, VOTER_TOPOFF_CHANGE, false, 0); + battery->topoff_condition = v; + + return v; +} +EXPORT_SYMBOL_KUNIT(set_topoff_current); + +int get_chg_power_type(int ct, int ws, int pd_max_pw, int max_pw) +{ + if (!is_wireless_type(ct)) { + if (is_pd_wire_type(ct) && + pd_max_pw >= HV_CHARGER_STATUS_STANDARD4) + return SFC_45W; + else if (is_pd_wire_type(ct) && + pd_max_pw >= HV_CHARGER_STATUS_STANDARD3) + return SFC_25W; + else if (is_hv_wire_12v_type(ct) || + max_pw >= HV_CHARGER_STATUS_STANDARD2) /* 20000mW */ + return AFC_12V_OR_20W; + else if (is_hv_wire_type(ct) || + (is_pd_wire_type(ct) && + pd_max_pw >= HV_CHARGER_STATUS_STANDARD1) || +#if !defined(CONFIG_BC12_DEVICE) + ws == SEC_BATTERY_CABLE_PREPARE_TA || +#endif + max_pw >= HV_CHARGER_STATUS_STANDARD1) /* 12000mW */ + return AFC_9V_OR_15W; + } + + return NORMAL_TA; +} +EXPORT_SYMBOL_KUNIT(get_chg_power_type); + +static void sec_bat_run_input_check_work(struct sec_battery_info *battery, int work_delay) +{ + unsigned int ws_duration = 0; + unsigned int offset = 3; /* 3 seconds */ + + pr_info("%s: for %s after %d msec\n", __func__, sb_get_ct_str(battery->cable_type), work_delay); + + cancel_delayed_work(&battery->input_check_work); + ws_duration = offset + (work_delay / 1000); + __pm_wakeup_event(battery->input_ws, jiffies_to_msecs(HZ * ws_duration)); + queue_delayed_work(battery->monitor_wqueue, + &battery->input_check_work, msecs_to_jiffies(work_delay)); +} + +static void sec_bat_cancel_input_check_work(struct sec_battery_info *battery) +{ + cancel_delayed_work(&battery->input_check_work); + battery->input_check_cnt = 0; +} + +static bool sec_bat_recheck_input_work(struct sec_battery_info *battery) +{ + return !delayed_work_pending(&battery->input_check_work) && + (battery->current_event & SEC_BAT_CURRENT_EVENT_SELECT_PDO); +} + +__visible_for_testing int sec_bat_change_iv(void *data, int voltage) +{ + struct sec_battery_info *battery = data; + + if ((is_hv_wire_type(battery->cable_type) || is_hv_wire_type(battery->wire_status)) && + (battery->cable_type != SEC_BATTERY_CABLE_QC30)) { + /* no need to set afc prepare current in already 9v cable status */ + if (!is_hv_wire_type(battery->wire_status) || + voltage != SEC_INPUT_VOLTAGE_9V) + sec_bat_check_afc_input_current(battery); +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) +#if IS_ENABLED(CONFIG_MTK_CHARGER) && IS_ENABLED(CONFIG_AFC_CHARGER) + afc_set_voltage(voltage); +#else + muic_afc_request_voltage(AFC_REQUEST_CHARGER, voltage/1000); +#endif +#if defined(CONFIG_BC12_DEVICE) + battery->input_voltage = voltage; +#endif +#endif + } else if (is_pd_wire_type(battery->cable_type) || + (is_pd_wire_type(battery->wire_status) && is_slate_mode(battery))) { + int curr_pdo = 0, pdo = 0, iv = 0, icl = 0; + + if (voltage == SEC_INPUT_VOLTAGE_APDO) { + pr_info("%s : Doesn't control input voltage during Direct Charging\n", __func__); + return voltage; + } + + iv = voltage; + if (sec_pd_get_pdo_power(&pdo, &iv, &iv, &icl) <= 0) { + pr_err("%s: failed to get pdo\n", __func__); + return -1; + } + pr_info("%s: target pdo = %d, iv = %d, icl = %d\n", __func__, pdo, iv, icl); + + if (sec_pd_get_current_pdo(&curr_pdo) < 0) { + pr_err("%s: failed to get current pdo\n", __func__); + return -1; + } + + if (curr_pdo != pdo) { + /* change input current before request new pdo if new pdo's input current is less than now */ + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_SELECT_PDO, + SEC_BAT_CURRENT_EVENT_SELECT_PDO); + sec_vote(battery->input_vote, VOTER_SELECT_PDO, true, + min(battery->sink_status.power_list[pdo].max_current, + battery->sink_status.power_list[curr_pdo].max_current)); + sec_bat_run_input_check_work(battery, 1000); +#if !defined(CONFIG_BC12_DEVICE) + battery->pdic_ps_rdy = false; +#endif + } + + if (sec_pd_is_apdo(pdo)) + sec_pd_select_pps(pdo, voltage, icl); + else + sec_pd_select_pdo(pdo); + } + + return voltage; +} +EXPORT_SYMBOL_KUNIT(sec_bat_change_iv); + +void sec_bat_set_misc_event(struct sec_battery_info *battery, + unsigned int misc_event_val, unsigned int misc_event_mask) +{ + + unsigned int temp = battery->misc_event; + + mutex_lock(&battery->misclock); + + battery->misc_event &= ~misc_event_mask; + battery->misc_event |= misc_event_val; + + pr_info("%s: misc event before(0x%x), after(0x%x)\n", + __func__, temp, battery->misc_event); + + mutex_unlock(&battery->misclock); + + if (battery->prev_misc_event != battery->misc_event) { + cancel_delayed_work(&battery->misc_event_work); + __pm_stay_awake(battery->misc_event_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->misc_event_work, 0); + } +} +EXPORT_SYMBOL(sec_bat_set_misc_event); + +void sec_bat_set_tx_event(struct sec_battery_info *battery, + unsigned int tx_event_val, unsigned int tx_event_mask) +{ + + unsigned int temp = battery->tx_event; + + mutex_lock(&battery->txeventlock); + + battery->tx_event &= ~tx_event_mask; + battery->tx_event |= tx_event_val; + + pr_info("@Tx_Mode %s: tx event before(0x%x), after(0x%x)\n", + __func__, temp, battery->tx_event); + + if (temp != battery->tx_event) { + /* Assure receiving tx_event to App for sleep case */ + __pm_wakeup_event(battery->tx_event_ws, jiffies_to_msecs(HZ * 2)); + power_supply_changed(battery->psy_bat); + } + mutex_unlock(&battery->txeventlock); +} +EXPORT_SYMBOL_KUNIT(sec_bat_set_tx_event); + +void sec_bat_set_current_event(struct sec_battery_info *battery, + unsigned int current_event_val, unsigned int current_event_mask) +{ + unsigned int temp = battery->current_event; + + mutex_lock(&battery->current_eventlock); + + battery->current_event &= ~current_event_mask; + battery->current_event |= current_event_val; + + pr_info("%s: current event before(0x%x), after(0x%x)\n", + __func__, temp, battery->current_event); + + mutex_unlock(&battery->current_eventlock); +} +EXPORT_SYMBOL(sec_bat_set_current_event); + +void sec_bat_set_temp_control_test(struct sec_battery_info *battery, bool temp_enable) +{ + if (temp_enable) { + if (battery->current_event & SEC_BAT_CURRENT_EVENT_TEMP_CTRL_TEST) { + pr_info("%s : BATT_TEMP_CONTROL_TEST already ENABLED\n", __func__); + return; + } + pr_info("%s : BATT_TEMP_CONTROL_TEST ENABLE\n", __func__); + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_TEMP_CTRL_TEST, + SEC_BAT_CURRENT_EVENT_TEMP_CTRL_TEST); + battery->pdata->usb_temp_check_type_backup = battery->pdata->usb_thm_info.check_type; + battery->pdata->usb_thm_info.check_type = SEC_BATTERY_TEMP_CHECK_NONE; + battery->overheatlimit_threshold_backup = battery->overheatlimit_threshold; + battery->overheatlimit_threshold = 990; + battery->overheatlimit_recovery_backup = battery->overheatlimit_recovery; + battery->overheatlimit_recovery = 980; + } else { + if (!(battery->current_event & SEC_BAT_CURRENT_EVENT_TEMP_CTRL_TEST)) { + pr_info("%s : BATT_TEMP_CONTROL_TEST already END\n", __func__); + return; + } + pr_info("%s : BATT_TEMP_CONTROL_TEST END\n", __func__); + sec_bat_set_current_event(battery, 0, + SEC_BAT_CURRENT_EVENT_TEMP_CTRL_TEST); + battery->pdata->usb_thm_info.check_type = battery->pdata->usb_temp_check_type_backup; + battery->overheatlimit_threshold = battery->overheatlimit_threshold_backup; + battery->overheatlimit_recovery = battery->overheatlimit_recovery_backup; + } +} +EXPORT_SYMBOL(sec_bat_set_temp_control_test); + +void sec_bat_change_default_current(struct sec_battery_info *battery, + int cable_type, int input, int output) +{ +#if defined(CONFIG_ENG_BATTERY_CONCEPT) + if (!battery->test_max_current) +#endif + battery->pdata->charging_current[cable_type].input_current_limit = input; +#if defined(CONFIG_ENG_BATTERY_CONCEPT) + if (!battery->test_charge_current) +#endif + battery->pdata->charging_current[cable_type].fast_charging_current = output; + pr_info("%s: cable_type: %d(%d,%d), input: %d, output: %d\n", + __func__, cable_type, battery->cable_type, battery->wire_status, + battery->pdata->charging_current[cable_type].input_current_limit, + battery->pdata->charging_current[cable_type].fast_charging_current); +} +EXPORT_SYMBOL_KUNIT(sec_bat_change_default_current); + +#if IS_ENABLED(CONFIG_USB_FACTORY_MODE) +#if defined(CONFIG_SEC_FACTORY) +static bool sec_bat_usb_factory_set_vote(struct sec_battery_info *battery, bool vote_en) +{ + if (vote_en) { + if ((battery->cable_type == SEC_BATTERY_CABLE_USB) && + !(battery->batt_f_mode == IB_MODE) && + !sec_bat_get_lpmode()) + if (battery->sink_status.rp_currentlvl == RP_CURRENT_LEVEL_NONE) { + sec_vote(battery->fcc_vote, VOTER_USB_FAC_100MA, true, 100); + sec_vote(battery->input_vote, VOTER_USB_FAC_100MA, true, 100); + dev_info(battery->dev, "%s: usb factory 100mA\n", __func__); + return true; + } + } else { + if ((battery->cable_type == SEC_BATTERY_CABLE_USB) && + !(battery->batt_f_mode == IB_MODE) && !sec_bat_get_lpmode()) + if (battery->sink_status.rp_currentlvl != RP_CURRENT_LEVEL_NONE) { + sec_vote(battery->fcc_vote, VOTER_USB_FAC_100MA, false, 100); + sec_vote(battery->input_vote, VOTER_USB_FAC_100MA, false, 100); + dev_info(battery->dev, "%s: recover usb factory 100mA\n", __func__); + } + } + return false; +} +#endif + +static void sec_bat_usb_factory_clear(struct sec_battery_info *battery) +{ + union power_supply_propval val = {0, }; + +#if defined(CONFIG_SEC_FACTORY) + if (battery->usb_factory_slate_mode || (battery->batt_f_mode == IB_MODE)) { + if (is_slate_mode(battery)) { + sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_SLATE); + sec_vote(battery->chgen_vote, VOTER_SLATE, false, 0); + sec_vote(battery->chgen_vote, VOTER_SMART_SLATE, false, 0); + sec_bat_set_mfc_on(battery, WPC_EN_SLATE); + dev_info(battery->dev, "%s: disable slate mode\n", __func__); + } + battery->usb_factory_slate_mode = false; + } +#endif + if (battery->batt_f_mode == IB_MODE) { + val.intval = 0; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_IB_MODE, val); + } else if (battery->batt_f_mode == OB_MODE) { + val.intval = 0; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_OB_MODE_CABLE_REMOVED, val); + } +} +#endif + +#if !defined(CONFIG_SEC_FACTORY) +#if defined(CONFIG_SUPPORT_HV_CTRL) +static int sec_bat_chk_siop_scenario_idx(struct sec_battery_info *battery, + int siop_level); +static bool sec_bat_chk_siop_skip_scenario(struct sec_battery_info *battery, + int ct, int ws, int scenario_idx); + +__visible_for_testing bool sec_bat_change_vbus_condition(int ct, unsigned int evt) +{ + if (!(is_hv_wire_type(ct) || is_pd_wire_type(ct)) || + (ct == SEC_BATTERY_CABLE_QC30)) + return false; + + if ((evt & SEC_BAT_CURRENT_EVENT_AFC) || + (evt & SEC_BAT_CURRENT_EVENT_SELECT_PDO)) + return false; + return true; +} +EXPORT_SYMBOL_KUNIT(sec_bat_change_vbus_condition); + +__visible_for_testing bool sec_bat_change_vbus(struct sec_battery_info *battery, + int ct, unsigned int evt, int siop_level) +{ + int s_idx = -1; + + if (battery->pdata->chg_thm_info.check_type == SEC_BATTERY_TEMP_CHECK_NONE || + battery->store_mode || + ((battery->siop_level == 80) && is_wired_type(battery->cable_type))) + return false; + if (!sec_bat_change_vbus_condition(ct, evt)) + return false; + + s_idx = sec_bat_chk_siop_scenario_idx(battery, battery->siop_level); + + if ((siop_level >= 100) || + sec_bat_chk_siop_skip_scenario(battery, + battery->cable_type, battery->wire_status, s_idx)) + sec_vote(battery->iv_vote, VOTER_SIOP, false, 0); + else { + sec_vote(battery->iv_vote, VOTER_SIOP, true, SEC_INPUT_VOLTAGE_5V); + pr_info("%s: vbus set 5V by level(%d), Cable(%s, %s, %d, %d)\n", + __func__, battery->siop_level, + sb_get_ct_str(ct), sb_get_ct_str(battery->wire_status), + battery->muic_cable_type, battery->pd_usb_attached); + return true; + } + return false; +} +EXPORT_SYMBOL_KUNIT(sec_bat_change_vbus); +#else +__visible_for_testing bool sec_bat_change_vbus(struct sec_battery_info *battery, + int ct, unsigned int evt, int siop_level) +{ + return false; +} +EXPORT_SYMBOL_KUNIT(sec_bat_change_vbus); +#endif +#endif + +__visible_for_testing int sec_bat_check_afc_input_current(struct sec_battery_info *battery) +{ + int work_delay = 0; + int input_current; + + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_AFC, + (SEC_BAT_CURRENT_EVENT_CHG_LIMIT | SEC_BAT_CURRENT_EVENT_AFC)); + if (!is_wireless_type(battery->cable_type)) { + input_current = battery->pdata->pre_afc_input_current; // 1000mA + work_delay = battery->pdata->pre_afc_work_delay; + } else { + input_current = battery->pdata->pre_wc_afc_input_current; + /* do not reduce this time, this is for noble pad */ + work_delay = battery->pdata->pre_wc_afc_work_delay; + } + sec_vote(battery->input_vote, VOTER_VBUS_CHANGE, true, input_current); + + if (!delayed_work_pending(&battery->input_check_work)) + sec_bat_run_input_check_work(battery, work_delay); + + pr_info("%s: change input_current(%d), cable_type(%d)\n", __func__, input_current, battery->cable_type); + + return input_current; +} +EXPORT_SYMBOL_KUNIT(sec_bat_check_afc_input_current); + +__visible_for_testing void sec_bat_get_input_current_in_power_list(struct sec_battery_info *battery) +{ + int pdo_num = battery->sink_status.current_pdo_num; + int max_input_current = 0; + + if (is_pd_apdo_wire_type(battery->wire_status) && battery->pd_list.now_isApdo) + pdo_num = 1; + + max_input_current = battery->pdata->charging_current[SEC_BATTERY_CABLE_PDIC].input_current_limit = + battery->sink_status.power_list[pdo_num].max_current; + battery->pdata->charging_current[SEC_BATTERY_CABLE_PDIC_APDO].input_current_limit = + battery->sink_status.power_list[pdo_num].max_current; + + if (battery->is_fpdo_dc && !(battery->current_event & SEC_BAT_CURRENT_EVENT_HV_DISABLE)) { + max_input_current = + battery->pdata->charging_current[SEC_BATTERY_CABLE_FPDO_DC].input_current_limit = + mA_by_mWmV(battery->pdata->pd_charging_charge_power, + battery->sink_status.power_list[pdo_num].max_voltage); + battery->pdata->charging_current[SEC_BATTERY_CABLE_FPDO_DC].fast_charging_current = + battery->sink_status.power_list[pdo_num].max_current * 2; + } + + pr_info("%s:max_input_current : %dmA, pdo_num : %d\n", __func__, max_input_current, pdo_num); + sec_vote(battery->input_vote, VOTER_CABLE, true, max_input_current); +} +EXPORT_SYMBOL_KUNIT(sec_bat_get_input_current_in_power_list); + +__visible_for_testing void sec_bat_get_charging_current_in_power_list(struct sec_battery_info *battery) +{ + int max_charging_current = 0, pd_power = 0; + int pdo_num = battery->sink_status.current_pdo_num; + + if (is_pd_apdo_wire_type(battery->wire_status) && battery->pd_list.now_isApdo) + pdo_num = 1; + + pd_power = mW_by_mVmA(battery->sink_status.power_list[pdo_num].max_voltage, + battery->sink_status.power_list[pdo_num].max_current); + + /* We assume that output voltage to float voltage */ + max_charging_current = mA_by_mWmV(pd_power, + (battery->pdata->chg_float_voltage / battery->pdata->chg_float_voltage_conv)); + max_charging_current = max_charging_current > battery->pdata->max_charging_current ? + battery->pdata->max_charging_current : max_charging_current; + battery->pdata->charging_current[SEC_BATTERY_CABLE_PDIC].fast_charging_current = max_charging_current; + +#if defined(CONFIG_STEP_CHARGING) + if (battery->dchg_dc_in_swelling && (battery->current_event & SEC_BAT_CURRENT_EVENT_HIGH_TEMP_SWELLING)) { + pr_info("%s: max cc(%d) warm_cc(%d)\n", __func__, + max_charging_current, battery->pdata->wire_warm_current); + max_charging_current = max(max_charging_current, battery->pdata->wire_warm_current); + } + if (is_pd_apdo_wire_type(battery->wire_status) && !battery->pd_list.now_isApdo && + battery->step_chg_status < 0) +#else + if (is_pd_apdo_wire_type(battery->wire_status) && !battery->pd_list.now_isApdo) +#endif + battery->pdata->charging_current[SEC_BATTERY_CABLE_PDIC_APDO].fast_charging_current = + max_charging_current; + battery->charge_power = pd_power; + + pr_info("%s:pd_charge_power : %dmW, max_charging_current : %dmA\n", __func__, + battery->charge_power, max_charging_current); + sec_vote(battery->fcc_vote, VOTER_CABLE, true, + battery->pdata->charging_current[battery->wire_status].fast_charging_current); +} +EXPORT_SYMBOL_KUNIT(sec_bat_get_charging_current_in_power_list); + +#if !defined(CONFIG_SEC_FACTORY) +void sec_bat_check_temp_ctrl_by_cable(struct sec_battery_info *battery) +{ + int ct = battery->cable_type; + int siop_lvl = battery->siop_level; + bool is_apdo = false; + +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + is_apdo = (is_pd_apdo_wire_type(ct) && battery->pd_list.now_isApdo) ? 1 : 0; +#endif + + if (battery->pdata->enable_mix_v2) + sec_bat_check_mix_temp_v2(battery); + else + sec_bat_check_mix_temp(battery, ct, siop_lvl, is_apdo); + + if (is_apdo) { + sec_bat_check_direct_chg_temp(battery, siop_lvl); + } else if (is_wireless_type(ct)) { + if (battery->pdata->enable_check_wpc_temp_v2) + sec_bat_check_wpc_temp_v2(battery); + else + sec_bat_check_wpc_temp(battery, ct, siop_lvl); + } else { + if (!sec_bat_change_vbus(battery, ct, battery->current_event, siop_lvl)) { + if (is_pd_wire_type(ct)) + sec_bat_check_pdic_temp(battery, siop_lvl); + else { + sec_bat_check_afc_temp(battery, siop_lvl); + } + } else if (battery->chg_limit) { + sec_vote(battery->fcc_vote, VOTER_CHG_TEMP, false, 0); + sec_vote(battery->input_vote, VOTER_CHG_TEMP, false, 0); + battery->chg_limit = false; + } + } + if (!is_wireless_all_type(ct)) + sec_bat_check_lrp_temp(battery, + ct, battery->wire_status, siop_lvl, battery->lcd_status); + + pr_info("%s: ct : %s\n", __func__, sb_get_ct_str(ct)); +} +#endif +int sec_bat_set_charging_current(struct sec_battery_info *battery) +{ + mutex_lock(&battery->iolock); + + if (!is_nocharge_type(battery->cable_type)) { +#if !defined(CONFIG_SEC_FACTORY) + sec_bat_check_temp_ctrl_by_cable(battery); +#endif + +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + /* Calculate wireless input current under the specific conditions (wpc_sleep_mode, chg_limit)*/ + /* VOTER_WPC_CUR */ + if (battery->wc_status != SEC_BATTERY_CABLE_NONE) { + sec_bat_get_wireless_current(battery); + } +#endif + +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + if (battery->dc_float_voltage_set) { + int age_step = battery->pdata->age_step; + int chg_step = battery->step_chg_status; + + pr_info("%s : step float voltage = %d\n", __func__, + battery->pdata->dc_step_chg_val_vfloat[age_step][chg_step]); + sec_vote(battery->dc_fv_vote, VOTER_DC_STEP_CHARGE, true, + battery->pdata->dc_step_chg_val_vfloat[age_step][chg_step]); + battery->dc_float_voltage_set = false; + } +#endif + + /* check topoff current */ + if (battery->charging_mode == SEC_BATTERY_CHARGING_2ND && + (battery->pdata->full_check_type_2nd == SEC_BATTERY_FULLCHARGED_CHGPSY || + battery->pdata->full_check_type_2nd == SEC_BATTERY_FULLCHARGED_LIMITER)) { + sec_vote(battery->topoff_vote, VOTER_FULL_CHARGE, true, battery->pdata->full_check_current_2nd); + } + + }/* !is_nocharge_type(battery->cable_type) */ + mutex_unlock(&battery->iolock); + return 0; +} +EXPORT_SYMBOL(sec_bat_set_charging_current); + +int sec_bat_set_charge(void * data, int chg_mode) +{ + struct sec_battery_info *battery = data; + union power_supply_propval val = {0, }; + struct timespec64 ts = {0, }; + + if (chg_mode == SEC_BAT_CHG_MODE_NOT_SET) { + pr_info("%s: temp mode for decrease 5v\n", __func__); + return chg_mode; + } + + if ((battery->current_event & SEC_BAT_CURRENT_EVENT_CHARGE_DISABLE) && + (chg_mode == SEC_BAT_CHG_MODE_CHARGING)) { + dev_info(battery->dev, "%s: charge disable by HMT\n", __func__); + chg_mode = SEC_BAT_CHG_MODE_CHARGING_OFF; + } + + battery->charger_mode = chg_mode; + pr_info("%s set %s mode\n", __func__, sb_charge_mode_str(chg_mode)); + + val.intval = battery->status; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_PROP_STATUS, val); + ts = ktime_to_timespec64(ktime_get_boottime()); + + if (chg_mode == SEC_BAT_CHG_MODE_CHARGING) { + /*Reset charging start time only in initial charging start */ + if (battery->charging_start_time == 0) { + if (ts.tv_sec < 1) + ts.tv_sec = 1; + battery->charging_start_time = ts.tv_sec; + battery->charging_next_time = + battery->pdata->charging_reset_time; + } +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + if (is_pd_apdo_wire_type(battery->cable_type)) { + sec_bat_reset_step_charging(battery); + sec_bat_check_dc_step_charging(battery); + } +#endif +#if defined(CONFIG_WIRELESS_RX_PHM_CTRL) + if (is_wireless_type(battery->cable_type)) { + val.intval = EXIT_PHM; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_RX_PHM, val); + } +#endif + } else { + battery->charging_start_time = 0; + battery->charging_passed_time = 0; + battery->charging_next_time = 0; + battery->charging_fullcharged_time = 0; + battery->full_check_cnt = 0; +#if defined(CONFIG_STEP_CHARGING) + sec_bat_reset_step_charging(battery); +#endif + battery->usb_overheat_check = false; + battery->cisd.ab_vbat_check_count = 0; + } + + val.intval = chg_mode; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED, val); + val.intval = chg_mode; + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED, val); + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + if ((battery->pdata->full_check_type_2nd == SEC_BATTERY_FULLCHARGED_FG_CURRENT) && + (battery->charging_mode == SEC_BATTERY_CHARGING_NONE && battery->status == POWER_SUPPLY_STATUS_FULL)) { + /* + * Enable supplement mode for 2nd charging done, should cut off charger then limiter sequence, + * this case only for full_check_type_2nd is + * SEC_BATTERY_FULLCHARGED_FG_CURRENT not SEC_BATTERY_FULLCHARGED_LIMITER + */ + val.intval = 1; + psy_do_property(battery->pdata->dual_battery_name, set, + POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED, val); + } else if (!(battery->charging_mode == SEC_BATTERY_CHARGING_NONE && battery->status == POWER_SUPPLY_STATUS_FULL) && + !(battery->charging_mode == SEC_BATTERY_CHARGING_NONE && battery->thermal_zone == BAT_THERMAL_WARM)) { + /* + * Limiter should disable supplement mode to do battery balancing properly in case of + * charging, discharging and buck off. But needs to disable supplement mode except + * 2nd full charge done and swelling charging. + */ + val.intval = 0; + psy_do_property(battery->pdata->dual_battery_name, set, + POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED, val); + } +#endif + return chg_mode; +} +EXPORT_SYMBOL(sec_bat_set_charge); + +__visible_for_testing bool sec_bat_check_by_psy(struct sec_battery_info *battery) +{ + char *psy_name = NULL; + union power_supply_propval value = {0, }; + bool ret = true; + + switch (battery->pdata->battery_check_type) { + case SEC_BATTERY_CHECK_PMIC: + psy_name = battery->pdata->pmic_name; + break; + case SEC_BATTERY_CHECK_FUELGAUGE: + psy_name = battery->pdata->fuelgauge_name; + break; + case SEC_BATTERY_CHECK_CHARGER: + psy_name = battery->pdata->charger_name; + break; + default: + dev_err(battery->dev, "%s: Invalid Battery Check Type\n", __func__); + ret = false; + goto battery_check_error; + break; + } + + psy_do_property(psy_name, get, POWER_SUPPLY_PROP_PRESENT, value); + ret = (bool)value.intval; + +battery_check_error: + return ret; +} +EXPORT_SYMBOL_KUNIT(sec_bat_check_by_psy); + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) && IS_ENABLED(CONFIG_LIMITER_S2ASL01) +static bool sec_bat_check_by_gpio(struct sec_battery_info *battery) +{ + union power_supply_propval value = {0, }; + bool ret = true; + int main_det = -1, sub_det = -1; + + value.intval = SEC_DUAL_BATTERY_MAIN; + psy_do_property(battery->pdata->dual_battery_name, get, + POWER_SUPPLY_EXT_PROP_DUAL_BAT_DET, value); + main_det = value.intval; + + value.intval = SEC_DUAL_BATTERY_SUB; + psy_do_property(battery->pdata->dual_battery_name, get, + POWER_SUPPLY_EXT_PROP_DUAL_BAT_DET, value); + sub_det = value.intval; + + ret = (bool)(main_det & sub_det); + if (!ret) + pr_info("%s : main det = %d, sub det = %d\n", __func__, main_det, sub_det); + + return ret; +} + +#if !defined(CONFIG_SEC_FACTORY) +static void sec_bat_powerpath_check(struct sec_battery_info *battery, int m_health, int s_health) +{ + static int m_abnormal_cnt, s_abnormal_cnt; + bool m_supplement_status = false, s_supplement_status = false; + union power_supply_propval value = {0, }; + + /* do not check battery power path when limiter is not ok */ + if (m_health != POWER_SUPPLY_HEALTH_GOOD || + s_health != POWER_SUPPLY_HEALTH_GOOD) { + pr_info("%s : do not check current status\n", __func__); + return; + } + + /* get main limiter's supplement status */ + psy_do_property(battery->pdata->main_limiter_name, get, + POWER_SUPPLY_EXT_PROP_SUPLLEMENT_MODE, value); + m_supplement_status = value.intval; + + /* get sub limiter's supplement status */ + psy_do_property(battery->pdata->sub_limiter_name, get, + POWER_SUPPLY_EXT_PROP_SUPLLEMENT_MODE, value); + s_supplement_status = value.intval; + + if ((battery->current_avg_main == 0) && + !m_supplement_status && + !(battery->misc_event & BATT_MISC_EVENT_MAIN_POWERPATH)) { + m_abnormal_cnt++; + } else if (battery->current_avg_main != 0) { + if (battery->misc_event & BATT_MISC_EVENT_MAIN_POWERPATH) { + pr_info("%s : main power path get back to normal\n", __func__); + sec_bat_set_misc_event(battery, + 0, BATT_MISC_EVENT_MAIN_POWERPATH); + } + m_abnormal_cnt = 0; + } + + if ((battery->current_avg_sub == 0) && + !s_supplement_status && + !(battery->misc_event & BATT_MISC_EVENT_SUB_POWERPATH)) { + s_abnormal_cnt++; + } else if (battery->current_avg_sub != 0) { + if (battery->misc_event & BATT_MISC_EVENT_SUB_POWERPATH) { + pr_info("%s : sub power path get back to normal\n", __func__); + sec_bat_set_misc_event(battery, + 0, BATT_MISC_EVENT_SUB_POWERPATH); + } + s_abnormal_cnt = 0; + } + + if (m_abnormal_cnt > 5) { + pr_info("%s : main power path seems to have problem\n", __func__); + sec_bat_set_misc_event(battery, + BATT_MISC_EVENT_MAIN_POWERPATH, BATT_MISC_EVENT_MAIN_POWERPATH); + m_abnormal_cnt = 0; + } + + if (s_abnormal_cnt > 5) { + pr_info("%s : sub power path seems to have problem\n", __func__); + sec_bat_set_misc_event(battery, + BATT_MISC_EVENT_SUB_POWERPATH, BATT_MISC_EVENT_SUB_POWERPATH); + s_abnormal_cnt = 0; + } +} + +static void sec_bat_limiter_check(struct sec_battery_info *battery) +{ + union power_supply_propval m_value = {0, }, s_value = {0, }; + int main_enb, main_enb2, sub_enb; + + pr_info("%s: Start\n", __func__); + + /* do not check limiter status when enb is not active status since it is certain test mode using enb pin */ + main_enb = gpio_get_value(battery->pdata->main_bat_enb_gpio); + main_enb2 = gpio_get_value(battery->pdata->main_bat_enb2_gpio); + sub_enb = gpio_get_value(battery->pdata->sub_bat_enb_gpio); + + if (main_enb2 || sub_enb) { + pr_info("%s : main_enb = %d, main_enb2 = %d, sub_enb = %d\n", __func__, main_enb, main_enb2, sub_enb); + return; + } + + /* check powermeter and vchg, vbat value of main limiter */ + psy_do_property(battery->pdata->main_limiter_name, get, + POWER_SUPPLY_PROP_HEALTH, m_value); + + /* check powermeter and vchg, vbat value of sub limiter */ + psy_do_property(battery->pdata->sub_limiter_name, get, + POWER_SUPPLY_PROP_HEALTH, s_value); + + /* discharging case with non sleep current since it has low powermeter resolution */ + if (is_nocharge_type(battery->cable_type) && + is_nocharge_type(battery->wire_status) && + battery->current_avg < (-500) && + !is_slate_mode(battery)) + sec_bat_powerpath_check(battery, m_value.intval, s_value.intval); + + /* do not check limiter status input curruent is not set fully */ + if (is_nocharge_type(battery->cable_type) || + is_wireless_all_type(battery->cable_type) || + battery->health != POWER_SUPPLY_HEALTH_GOOD || + battery->status != POWER_SUPPLY_STATUS_CHARGING || + battery->current_event & SEC_BAT_CURRENT_EVENT_SWELLING_MODE || + battery->current_event & SEC_BAT_CURRENT_EVENT_AICL || + battery->charge_power < 9000 || + battery->siop_level < 100 || + battery->lcd_status || + battery->limiter_check) { + return; + } + + if (m_value.intval != POWER_SUPPLY_HEALTH_GOOD) { + pr_info("%s : main limiter wa will work\n", __func__); + battery->cisd.event_data[EVENT_MAIN_BAT_ERR]++; + sec_vote(battery->chgen_vote, VOTER_FULL_CHARGE, true, SEC_BAT_CHG_MODE_CHARGING_OFF); + + m_value.intval = 1000; + psy_do_property(battery->pdata->main_limiter_name, set, + POWER_SUPPLY_EXT_PROP_DISCHG_LIMIT_CURRENT, m_value); + + m_value.intval = 0; + psy_do_property(battery->pdata->main_limiter_name, set, + POWER_SUPPLY_EXT_PROP_DISCHG_MODE, m_value); + + /* deactivate main limiter */ + gpio_direction_output(battery->pdata->main_bat_enb2_gpio, 1); + usleep_range(1000, 2000); + /* activate main limiter */ + gpio_direction_output(battery->pdata->main_bat_enb2_gpio, 0); + msleep(50); + + /* needs to init limiter setting again */ + m_value.intval = 1; + psy_do_property(battery->pdata->main_limiter_name, set, + POWER_SUPPLY_EXT_PROP_POWERMETER_ENABLE, m_value); + msleep(100); + + sec_vote(battery->chgen_vote, VOTER_FULL_CHARGE, true, SEC_BAT_CHG_MODE_CHARGING); + battery->limiter_check = true; + pr_info("%s : main limiter wa done\n", __func__); + + /* re-check powermeter and vchg, vbat value of main limiter */ + psy_do_property(battery->pdata->main_limiter_name, get, + POWER_SUPPLY_PROP_HEALTH, m_value); + + if (m_value.intval != POWER_SUPPLY_HEALTH_GOOD) { + pr_info("%s : main limiter wa did not work\n", __func__); + battery->cisd.event_data[EVENT_BAT_WA_ERR]++; + } +#if IS_ENABLED(CONFIG_SEC_ABC) + sec_abc_send_event("MODULE=battery@WARN=lim_stuck"); +#endif + } + + if (s_value.intval != POWER_SUPPLY_HEALTH_GOOD) { + pr_info("%s : sub limiter wa will work\n", __func__); + battery->cisd.event_data[EVENT_SUB_BAT_ERR]++; + sec_vote(battery->chgen_vote, VOTER_FULL_CHARGE, true, SEC_BAT_CHG_MODE_CHARGING_OFF); + + s_value.intval = 1000; + psy_do_property(battery->pdata->sub_limiter_name, set, + POWER_SUPPLY_EXT_PROP_DISCHG_LIMIT_CURRENT, s_value); + + s_value.intval = 0; + psy_do_property(battery->pdata->sub_limiter_name, set, + POWER_SUPPLY_EXT_PROP_DISCHG_MODE, s_value); + + /* deactivate sub limiter */ + gpio_direction_output(battery->pdata->sub_bat_enb_gpio, 1); + usleep_range(1000, 2000); + /* activate sub limiter */ + gpio_direction_output(battery->pdata->sub_bat_enb_gpio, 0); + msleep(50); + + /* needs to init limiter setting again */ + s_value.intval = 1; + psy_do_property(battery->pdata->sub_limiter_name, set, + POWER_SUPPLY_EXT_PROP_POWERMETER_ENABLE, s_value); + msleep(100); + + sec_vote(battery->chgen_vote, VOTER_FULL_CHARGE, true, SEC_BAT_CHG_MODE_CHARGING); + + battery->limiter_check = true; + pr_info("%s : sub limiter wa done\n", __func__); + + /* re-check powermeter and vchg, vbat value of sub limiter */ + psy_do_property(battery->pdata->sub_limiter_name, get, + POWER_SUPPLY_PROP_HEALTH, s_value); + + if (s_value.intval != POWER_SUPPLY_HEALTH_GOOD) { + pr_info("%s : main limiter wa did not work\n", __func__); + battery->cisd.event_data[EVENT_BAT_WA_ERR]++; + } +#if IS_ENABLED(CONFIG_SEC_ABC) + sec_abc_send_event("MODULE=battery@WARN=lim_stuck"); +#endif + } +} +#endif +#endif + +static bool sec_bat_check(struct sec_battery_info *battery) +{ + bool ret = true; + + if (battery->factory_mode || battery->is_jig_on || sec_bat_get_facmode()) { + dev_dbg(battery->dev, "%s: No need to check in factory mode\n", + __func__); + return ret; + } + + if (battery->health != POWER_SUPPLY_HEALTH_GOOD && + battery->health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) { + dev_dbg(battery->dev, "%s: No need to check\n", __func__); + return ret; + } + + switch (battery->pdata->battery_check_type) { + case SEC_BATTERY_CHECK_ADC: + if (is_nocharge_type(battery->cable_type)) + ret = battery->present; + else + ret = sec_bat_check_vf_adc(battery); + break; + case SEC_BATTERY_CHECK_INT: + case SEC_BATTERY_CHECK_CALLBACK: + if (is_nocharge_type(battery->cable_type)) { + ret = battery->present; + } else { + if (battery->pdata->check_battery_callback) + ret = battery->pdata->check_battery_callback(); + } + break; + case SEC_BATTERY_CHECK_PMIC: + case SEC_BATTERY_CHECK_FUELGAUGE: + case SEC_BATTERY_CHECK_CHARGER: + ret = sec_bat_check_by_psy(battery); + break; +#if IS_ENABLED(CONFIG_DUAL_BATTERY) && IS_ENABLED(CONFIG_LIMITER_S2ASL01) + case SEC_BATTERY_CHECK_DUAL_BAT_GPIO: + ret = sec_bat_check_by_gpio(battery); + break; +#endif + case SEC_BATTERY_CHECK_NONE: + dev_dbg(battery->dev, "%s: No Check\n", __func__); + break; + default: + break; + } + + return ret; +} + +static void sec_bat_send_cs100(struct sec_battery_info *battery) +{ + union power_supply_propval value = {0, }; + + if (is_wireless_all_type(battery->cable_type)) { + value.intval = POWER_SUPPLY_STATUS_FULL; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_PROP_STATUS, value); + } +} + +__visible_for_testing bool sec_bat_get_cable_type(struct sec_battery_info *battery, int cable_source_type) +{ + bool ret = false; + int cable_type = battery->cable_type; + + if (cable_source_type & SEC_BATTERY_CABLE_SOURCE_CALLBACK) { + if (battery->pdata->check_cable_callback) + cable_type = battery->pdata->check_cable_callback(); + } + + if (cable_source_type & SEC_BATTERY_CABLE_SOURCE_ADC) { + if (gpio_get_value_cansleep( + battery->pdata->bat_gpio_ta_nconnected) ^ + battery->pdata->bat_polarity_ta_nconnected) + cable_type = SEC_BATTERY_CABLE_NONE; + else + cable_type = sec_bat_get_charger_type_adc(battery); + } + + if (battery->cable_type == cable_type) { + dev_dbg(battery->dev, "%s: No need to change cable status\n", __func__); + } else { + if (cable_type < SEC_BATTERY_CABLE_NONE || cable_type >= SEC_BATTERY_CABLE_MAX) { + dev_err(battery->dev, "%s: Invalid cable type\n", __func__); + } else { + battery->cable_type = cable_type; + if (battery->pdata->check_cable_result_callback) + battery->pdata->check_cable_result_callback(battery->cable_type); + + ret = true; + + dev_dbg(battery->dev, "%s: Cable Changed (%d)\n", __func__, battery->cable_type); + } + } + + return ret; +} +EXPORT_SYMBOL_KUNIT(sec_bat_get_cable_type); + +void sec_bat_set_charging_status(struct sec_battery_info *battery, int status) +{ + union power_supply_propval value = {0, }; + +#if defined(CONFIG_NO_BATTERY) + status = POWER_SUPPLY_STATUS_DISCHARGING; +#endif + switch (status) { + case POWER_SUPPLY_STATUS_CHARGING: + if (battery->siop_level < 100 || battery->lcd_status || battery->wc_tx_enable) + battery->stop_timer = true; + break; + case POWER_SUPPLY_STATUS_NOT_CHARGING: + case POWER_SUPPLY_STATUS_DISCHARGING: + if (battery->status == POWER_SUPPLY_STATUS_FULL && + battery->capacity < 100 && is_eu_eco_rechg(battery->fs)) { + + pr_info("%s : EU eco case do not Fg scale, capacity(%d)\n", __func__, battery->capacity); + + } else if ((battery->status == POWER_SUPPLY_STATUS_FULL || + (battery->capacity == 100 && !is_slate_mode(battery))) && + !battery->store_mode) { + + pr_info("%s : Update fg scale to 101%%\n", __func__); + value.intval = 100; + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_PROP_CHARGE_FULL, value); + + /* To get SOC value (NOT raw SOC), need to reset value */ + value.intval = 0; + psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_PROP_CAPACITY, value); + battery->capacity = value.intval; + } + battery->expired_time = battery->pdata->expired_time; + battery->prev_safety_time = 0; + break; + case POWER_SUPPLY_STATUS_FULL: + sec_bat_send_cs100(battery); + break; + default: + break; + } + battery->status = status; +} +EXPORT_SYMBOL_KUNIT(sec_bat_set_charging_status); + +void sec_bat_set_health(struct sec_battery_info *battery, int health) +{ + if (battery->health != health) { + if (health == POWER_SUPPLY_EXT_HEALTH_OVERHEATLIMIT) + sec_bat_set_misc_event(battery, + BATT_MISC_EVENT_HEALTH_OVERHEATLIMIT, BATT_MISC_EVENT_HEALTH_OVERHEATLIMIT); + else + sec_bat_set_misc_event(battery, 0, BATT_MISC_EVENT_HEALTH_OVERHEATLIMIT); + +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) && !defined(CONFIG_SEC_FACTORY) + if (is_wireless_all_type(battery->cable_type)) { + union power_supply_propval val = {0, }; + + if (!battery->wc_ept_timeout) { + battery->wc_ept_timeout = true; + __pm_stay_awake(battery->wc_ept_timeout_ws); + /* 10 secs time out */ + queue_delayed_work(battery->monitor_wqueue, &battery->wc_ept_timeout_work, msecs_to_jiffies(10000)); + val.intval = health; + psy_do_property(battery->pdata->wireless_charger_name, set, POWER_SUPPLY_PROP_HEALTH, val); + } + } +#endif + } + + battery->health = health; + + if (health != POWER_SUPPLY_HEALTH_GOOD) + store_battery_log( + "Health:%d%%,%dmV,%s,ct(%s,%s,%d,%d),%s", + battery->capacity, + battery->voltage_now, + sb_get_bst_str(battery->status), + sb_get_ct_str(battery->cable_type), + sb_get_ct_str(battery->wire_status), + battery->muic_cable_type, + battery->pd_usb_attached, + sb_get_hl_str(battery->health) + ); +} +EXPORT_SYMBOL_KUNIT(sec_bat_set_health); + +static bool sec_bat_battery_cable_check(struct sec_battery_info *battery) +{ + if (!sec_bat_check(battery)) { + if (battery->check_count < battery->pdata->check_count) + battery->check_count++; + else { + dev_err(battery->dev, + "%s: Battery Disconnected\n", __func__); + battery->present = false; + sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_UNSPEC_FAILURE); + + if (battery->status != + POWER_SUPPLY_STATUS_DISCHARGING) { + sec_bat_set_charging_status(battery, + POWER_SUPPLY_STATUS_NOT_CHARGING); + sec_vote(battery->chgen_vote, VOTER_BATTERY, true, SEC_BAT_CHG_MODE_BUCK_OFF); + } + + if (battery->pdata->check_battery_result_callback) + battery->pdata-> + check_battery_result_callback(); + return false; + } + } else + battery->check_count = 0; + + battery->present = true; + + if (battery->health == POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) { + sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_GOOD); + + if (battery->status == POWER_SUPPLY_STATUS_NOT_CHARGING) { + sec_bat_set_charging_status(battery, + POWER_SUPPLY_STATUS_CHARGING); + sec_vote(battery->chgen_vote, VOTER_BATTERY, false, 0); + } + } + + dev_dbg(battery->dev, "%s: Battery Connected\n", __func__); + + if (battery->pdata->cable_check_type & + SEC_BATTERY_CABLE_CHECK_POLLING) { + if (sec_bat_get_cable_type(battery, + battery->pdata->cable_source_type)) { + __pm_stay_awake(battery->cable_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->cable_work, 0); + } + } + return true; +} + +static int sec_bat_ovp_uvlo_by_psy(struct sec_battery_info *battery) +{ + char *psy_name = NULL; + union power_supply_propval value = {0, }; + int ret = 0; + + value.intval = POWER_SUPPLY_HEALTH_GOOD; + + switch (battery->pdata->ovp_uvlo_check_type) { + case SEC_BATTERY_OVP_UVLO_PMICPOLLING: + psy_name = battery->pdata->pmic_name; + break; + case SEC_BATTERY_OVP_UVLO_CHGPOLLING: + psy_name = battery->pdata->charger_name; + break; + default: + dev_err(battery->dev, + "%s: Invalid OVP/UVLO Check Type\n", __func__); + goto ovp_uvlo_check_error; + break; + } + + ret = psy_do_property(psy_name, get, + POWER_SUPPLY_PROP_HEALTH, value); + +ovp_uvlo_check_error: + /* Need to not display HEALTH UNKNOWN if driver cannot be read */ + return ((ret == 0) ? value.intval : POWER_SUPPLY_HEALTH_GOOD); +} +#if defined(CONFIG_SEC_KUNIT) +int __mockable chk_ap_wake_chg(void) +#else +static int chk_ap_wake_chg(void) +#endif +{ + static int en = -1; + + if (en == -1) { + struct device_node *np; + + np = of_find_node_by_name(NULL, "battery"); + if (!np) { + pr_info("%s: np NULL\n", __func__); + en = 0; + } else { + en = of_property_read_bool(np, "battery,ap_wake_chg"); + } + pr_info("%s: en: %d\n", __func__, en); + } + return en; +} + +__visible_for_testing void sb_set_vbus_wake(struct wakeup_source *vbus_ws, int health, int cable_type) +{ + if (!chk_ap_wake_chg() || chg_can_sleep_type(cable_type) || + (health == POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE) || (health == POWER_SUPPLY_HEALTH_OVERVOLTAGE)) + __pm_wakeup_event(vbus_ws, jiffies_to_msecs(HZ * 10)); + else + __pm_stay_awake(vbus_ws); +} +EXPORT_SYMBOL_KUNIT(sb_set_vbus_wake); + +static bool sec_bat_ovp_uvlo_result(struct sec_battery_info *battery, int health) +{ +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + union power_supply_propval value = {0, }; +#endif + + if (health == POWER_SUPPLY_EXT_HEALTH_DC_ERR) { + dev_info(battery->dev, + "%s: DC err (%d)\n", + __func__, health); + battery->is_recharging = false; + battery->health_check_count = DEFAULT_HEALTH_CHECK_COUNT; + /* Enable charging anyway to check actual DC's health */ + sec_vote(battery->chgen_vote, VOTER_DC_ERR, true, SEC_BAT_CHG_MODE_CHARGING_OFF); + sec_vote(battery->chgen_vote, VOTER_DC_ERR, false, 0); + } + +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + if (health == POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE) { + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_PROP_CHARGE_EMPTY, value); + if (value.intval == 0) { + dev_info(battery->dev, "%s: skip undervoltage for WL %d %d\n", + __func__, health, battery->health); + health = battery->health; + } + } +#endif + + if (battery->health != health) { + sec_bat_set_health(battery, health); + sb_set_vbus_wake(battery->vbus_ws, health, battery->cable_type); + switch (health) { + case POWER_SUPPLY_HEALTH_GOOD: + dev_info(battery->dev, "%s: Safe voltage\n", __func__); + dev_info(battery->dev, "%s: is_recharging : %d\n", __func__, battery->is_recharging); + sec_bat_set_charging_status(battery, + POWER_SUPPLY_STATUS_CHARGING); + battery->charging_mode = SEC_BATTERY_CHARGING_1ST; + sec_vote(battery->chgen_vote, VOTER_VBUS_OVP, false, 0); + battery->health_check_count = 0; + break; + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + case POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE: + dev_info(battery->dev, + "%s: Unsafe voltage (%d)\n", + __func__, health); + sec_bat_set_charging_status(battery, + POWER_SUPPLY_STATUS_NOT_CHARGING); + sec_vote(battery->chgen_vote, VOTER_VBUS_OVP, true, SEC_BAT_CHG_MODE_CHARGING_OFF); + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + battery->is_recharging = false; + battery->health_check_count = DEFAULT_HEALTH_CHECK_COUNT; + battery->cisd.data[CISD_DATA_UNSAFETY_VOLTAGE]++; + battery->cisd.data[CISD_DATA_UNSAFE_VOLTAGE_PER_DAY]++; + break; + case POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE: + dev_info(battery->dev, + "%s: watchdog timer expired (%d)\n", + __func__, health); + sec_bat_set_charging_status(battery, + POWER_SUPPLY_STATUS_NOT_CHARGING); + sec_vote(battery->chgen_vote, VOTER_WDT_EXPIRE, true, SEC_BAT_CHG_MODE_CHARGING_OFF); + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + battery->is_recharging = false; + battery->health_check_count = DEFAULT_HEALTH_CHECK_COUNT; + break; + } + power_supply_changed(battery->psy_bat); + if (health != POWER_SUPPLY_HEALTH_GOOD) + return true; + } + + return false; +} + +static bool sec_bat_ovp_uvlo(struct sec_battery_info *battery) +{ + int health = POWER_SUPPLY_HEALTH_GOOD; + + if (battery->wdt_kick_disable) { + dev_dbg(battery->dev, + "%s: No need to check in wdt test\n", + __func__); + return false; + } else if ((battery->status == POWER_SUPPLY_STATUS_FULL) && + (battery->charging_mode == SEC_BATTERY_CHARGING_NONE)) { + dev_dbg(battery->dev, "%s: No need to check in Full status", __func__); + return false; + } + + if (battery->health != POWER_SUPPLY_HEALTH_GOOD && + battery->health != POWER_SUPPLY_HEALTH_OVERVOLTAGE && + battery->health != POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE && + battery->health != POWER_SUPPLY_EXT_HEALTH_DC_ERR && + battery->health != POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE) { + dev_dbg(battery->dev, "%s: No need to check\n", __func__); + return false; + } + health = battery->health; + + switch (battery->pdata->ovp_uvlo_check_type) { + case SEC_BATTERY_OVP_UVLO_CALLBACK: + if (battery->pdata->ovp_uvlo_callback) + health = battery->pdata->ovp_uvlo_callback(); + break; + case SEC_BATTERY_OVP_UVLO_PMICPOLLING: + case SEC_BATTERY_OVP_UVLO_CHGPOLLING: + health = sec_bat_ovp_uvlo_by_psy(battery); + break; + case SEC_BATTERY_OVP_UVLO_PMICINT: + case SEC_BATTERY_OVP_UVLO_CHGINT: + /* nothing for interrupt check */ + default: + break; + } + + if (battery->factory_mode) { + dev_dbg(battery->dev, + "%s: No need to check in factory mode\n", + __func__); + return false; + } + + return sec_bat_ovp_uvlo_result(battery, health); +} + +__visible_for_testing bool sec_bat_check_recharge(struct sec_battery_info *battery) +{ + if (battery->current_event & SEC_BAT_CURRENT_EVENT_HIGH_TEMP_SWELLING) + return false; + + if ((battery->status == POWER_SUPPLY_STATUS_CHARGING) && + (battery->pdata->full_condition_type & + SEC_BATTERY_FULL_CONDITION_NOTIMEFULL) && + (battery->charging_mode == SEC_BATTERY_CHARGING_NONE)) { + dev_info(battery->dev, "%s: Re-charging by NOTIMEFULL (%d)\n", + __func__, battery->capacity); + goto check_recharge_check_count; + } + + if (battery->status == POWER_SUPPLY_STATUS_FULL && + battery->charging_mode == SEC_BATTERY_CHARGING_NONE) { + int recharging_voltage = battery->pdata->recharge_condition_vcell, + recharging_soc = battery->pdata->recharge_condition_soc; + + if (battery->current_event & SEC_BAT_CURRENT_EVENT_LOW_TEMP_MODE) { + if (battery->current_event & SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING_COOL3) + recharging_voltage = battery->pdata->swelling_low_cool3_rechg_voltage; + else + recharging_voltage = battery->pdata->swelling_low_rechg_voltage; + } + dev_info(battery->dev, "%s: recharging voltage(%d) soc(%d) %s\n", + __func__, recharging_voltage, recharging_soc, + (battery->current_event & SEC_BAT_CURRENT_EVENT_LOW_TEMP_MODE ? + "changed by low temp" : "normal")); + + if ((battery->pdata->recharge_condition_type & + SEC_BATTERY_RECHARGE_CONDITION_SOC) && + (battery->capacity <= recharging_soc)) { + dev_info(battery->dev, "%s: Re-charging by SOC (%d)\n", + __func__, battery->capacity); + goto check_recharge_check_count; + } + + if ((battery->pdata->recharge_condition_type & + SEC_BATTERY_RECHARGE_CONDITION_AVGVCELL) && + (battery->voltage_avg <= recharging_voltage)) { + dev_info(battery->dev, "%s: Re-charging by average VCELL (%d)\n", + __func__, battery->voltage_avg); + goto check_recharge_check_count; + } + + if ((battery->pdata->recharge_condition_type & + SEC_BATTERY_RECHARGE_CONDITION_VCELL) && + (battery->voltage_now <= recharging_voltage)) { + dev_info(battery->dev, "%s: Re-charging by VCELL (%d)\n", + __func__, battery->voltage_now); + goto check_recharge_check_count; + } + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + if (battery->pdata->recharge_condition_type & + SEC_BATTERY_RECHARGE_CONDITION_LIMITER) { + int voltage = max(battery->voltage_avg_main, battery->voltage_avg_sub); + if (voltage <= recharging_voltage) { + dev_info(battery->dev, "%s: Re-charging by VPACK (%d)mV\n", + __func__, voltage); + goto check_recharge_check_count; + } else if (abs(battery->voltage_avg_main - battery->voltage_avg_sub) > + battery->pdata->force_recharge_margin) { + dev_info(battery->dev, "%s: Force Re-charging by VPACK diff(%d, %d)mV\n", + __func__, battery->voltage_avg_main, battery->voltage_avg_sub); + goto check_recharge_check_count; + } + } +#endif + } + + battery->recharge_check_cnt = 0; + return false; + +check_recharge_check_count: + battery->expired_time = battery->pdata->recharging_expired_time; + battery->prev_safety_time = 0; + + if (battery->recharge_check_cnt < + battery->pdata->recharge_check_count) + battery->recharge_check_cnt++; + dev_dbg(battery->dev, + "%s: recharge count = %d\n", + __func__, battery->recharge_check_cnt); + + if (battery->recharge_check_cnt >= + battery->pdata->recharge_check_count) + return true; + else + return false; +} +EXPORT_SYMBOL_KUNIT(sec_bat_check_recharge); + +static bool sec_bat_voltage_check(struct sec_battery_info *battery) +{ + union power_supply_propval value = {0, }; + + if (battery->status == POWER_SUPPLY_STATUS_DISCHARGING || + is_nocharge_type(battery->cable_type) || + is_wireless_fake_type(battery->cable_type)) { + dev_dbg(battery->dev, + "%s: Charging Disabled\n", __func__); + return true; + } + + /* OVP/UVLO check */ + if (sec_bat_ovp_uvlo(battery)) { + if (battery->pdata->ovp_uvlo_result_callback) + battery->pdata-> + ovp_uvlo_result_callback(battery->health); + return false; + } + + if ((battery->status == POWER_SUPPLY_STATUS_FULL) && + ((battery->charging_mode != SEC_BATTERY_CHARGING_NONE && + battery->charger_mode == SEC_BAT_CHG_MODE_CHARGING) || + (battery->current_event & SEC_BAT_CURRENT_EVENT_HIGH_TEMP_SWELLING))) { + int voltage_now = battery->voltage_now; + int voltage_ref = battery->pdata->recharge_condition_vcell - 50; +#if defined(CONFIG_ENABLE_FULL_BY_SOC) + int soc_ref = 98; +#else + int soc_ref = battery->pdata->full_condition_soc; +#endif + + if (battery->current_event & SEC_BAT_CURRENT_EVENT_LOW_TEMP_MODE) { + if (battery->current_event & SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING_COOL3) + voltage_ref = battery->pdata->swelling_low_cool3_rechg_voltage - 50; + else + voltage_ref = battery->pdata->swelling_low_rechg_voltage - 50; + } + + if (is_eu_eco_rechg(battery->fs)) + soc_ref = (soc_ref > battery->pdata->recharge_condition_soc) ? + battery->pdata->recharge_condition_soc : soc_ref; + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + if (battery->pdata->recharge_condition_type & + SEC_BATTERY_RECHARGE_CONDITION_LIMITER) { + voltage_now = max(battery->voltage_avg_main, battery->voltage_avg_sub); + } +#endif + + value.intval = 0; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CAPACITY, value); + + pr_info("%s: chg mode (%d), voltage_ref(%d), voltage_now(%d), soc_ref(%d), capacity(%d)\n", + __func__, battery->charging_mode, voltage_ref, voltage_now, soc_ref, value.intval); + + if (value.intval < soc_ref && voltage_now < voltage_ref) { + sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_CHARGING); + battery->is_recharging = false; + battery->charging_mode = SEC_BATTERY_CHARGING_1ST; + if (battery->pdata->change_FV_after_full) + sec_vote(battery->fv_vote, VOTER_FULL_CHARGE, false, battery->pdata->chg_float_voltage); + sec_vote(battery->chgen_vote, VOTER_CABLE, true, SEC_BAT_CHG_MODE_CHARGING); + sec_vote(battery->topoff_vote, VOTER_FULL_CHARGE, false, 0); + sec_vote(battery->chgen_vote, VOTER_FULL_CHARGE, false, 0); + pr_info("%s: battery status full -> charging, RepSOC(%d)\n", __func__, value.intval); + value.intval = POWER_SUPPLY_STATUS_CHARGING; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_PROP_STATUS, value); + return false; + } + } + + /* Re-Charging check */ + if (sec_bat_check_recharge(battery)) { + if (battery->pdata->full_check_type != + SEC_BATTERY_FULLCHARGED_NONE) + battery->charging_mode = SEC_BATTERY_CHARGING_1ST; + else + battery->charging_mode = SEC_BATTERY_CHARGING_2ND; + + battery->is_recharging = true; + battery->cisd.data[CISD_DATA_RECHARGING_COUNT]++; + battery->cisd.data[CISD_DATA_RECHARGING_COUNT_PER_DAY]++; + if (is_wireless_type(battery->cable_type)) { +#if defined(CONFIG_WIRELESS_RX_PHM_CTRL) + value.intval = EXIT_PHM; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_RX_PHM, value); +#endif + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_1ST_DONE, value); + + value.intval = WIRELESS_VRECT_ADJ_OFF; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_RX_CONTROL, value); + } + + if (battery->pdata->change_FV_after_full) + sec_vote(battery->fv_vote, VOTER_FULL_CHARGE, false, battery->pdata->chg_float_voltage); + sec_vote(battery->chgen_vote, VOTER_CABLE, true, SEC_BAT_CHG_MODE_CHARGING); + sec_vote(battery->topoff_vote, VOTER_FULL_CHARGE, false, 0); + sec_vote(battery->chgen_vote, VOTER_FULL_CHARGE, false, 0); + + return false; + } + + return true; +} + +__visible_for_testing bool sec_bat_set_aging_step(struct sec_battery_info *battery, int step) +{ + union power_supply_propval value = {0, }; + + if (battery->pdata->num_age_step <= 0 || step < 0 || step >= battery->pdata->num_age_step) { + pr_info("%s: [AGE] abnormal age step : %d/%d\n", + __func__, step, battery->pdata->num_age_step-1); + return false; + } + + battery->pdata->age_step = step; + + /* float voltage */ + battery->pdata->chg_float_voltage = + battery->pdata->age_data[battery->pdata->age_step].float_voltage; + sec_vote(battery->fv_vote, VOTER_AGING_STEP, true, battery->pdata->chg_float_voltage); + + /* full/recharge condition */ + battery->pdata->recharge_condition_vcell = + battery->pdata->age_data[battery->pdata->age_step].recharge_condition_vcell; + battery->pdata->full_condition_soc = + battery->pdata->age_data[battery->pdata->age_step].full_condition_soc; + battery->pdata->full_condition_vcell = + battery->pdata->age_data[battery->pdata->age_step].full_condition_vcell; + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + value.intval = battery->pdata->age_step; + psy_do_property(battery->pdata->dual_battery_name, set, + POWER_SUPPLY_EXT_PROP_FULL_CONDITION, value); + + if (battery->pdata->limiter_aging_float_offset > 0) { + value.intval = battery->pdata->chg_float_voltage - + battery->pdata->limiter_aging_float_offset; + psy_do_property(battery->pdata->dual_battery_name, set, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, value); + } +#endif +#if defined(CONFIG_LSI_IFPMIC) + value.intval = battery->pdata->age_step; + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_EXT_PROP_UPDATE_BATTERY_DATA, value); +#else + value.intval = battery->pdata->full_condition_soc; + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, value); +#endif +#if defined(CONFIG_STEP_CHARGING) + sec_bat_set_aging_info_step_charging(battery); +#endif +#if IS_ENABLED(CONFIG_MTK_CHARGER) + if (battery->pdata->dynamic_cv_factor) { + value.intval = (battery->pdata->chg_float_voltage) * 1000; + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, value); + } +#endif + + dev_info(battery->dev, + "%s: Step(%d/%d), Cycle(%d), float_v(%d), r_v(%d), f_s(%d), f_vl(%d)\n", + __func__, + battery->pdata->age_step, battery->pdata->num_age_step-1, max(battery->batt_cycle, battery->batt_full_status_usage), + battery->pdata->chg_float_voltage, + battery->pdata->recharge_condition_vcell, + battery->pdata->full_condition_soc, + battery->pdata->full_condition_vcell); + +#if defined(CONFIG_BATTERY_AGE_FORECAST_B2B) + { + int i; + bool bChanged = false; + + battery->pdata->max_charging_current = + battery->pdata->age_data[battery->pdata->age_step].max_charging_current; + + for (i = 0; i < SEC_BATTERY_CABLE_MAX; i++) { + if (battery->pdata->charging_current[i].fast_charging_current > + battery->pdata->max_charging_current) { + + dev_info(battery->dev, "%s: cable(%d) charging current(%d->%d)\n", + __func__, i, + battery->pdata->charging_current[i].fast_charging_current, + battery->pdata->max_charging_current); + battery->pdata->charging_current[i].fast_charging_current = + battery->pdata->max_charging_current; + if (battery->cable_type == i) + bChanged = true; + } + } + + if (bChanged) + sec_bat_set_charging_current(battery); + } +#endif + return true; +} +EXPORT_SYMBOL_KUNIT(sec_bat_set_aging_step); + +void sec_bat_aging_check(struct sec_battery_info *battery) +{ + int prev_step = battery->pdata->age_step; + int calc_step; + bool ret = 0; + static bool init; /* false */ + + if ((battery->pdata->num_age_step <= 0) || (battery->batt_cycle < 0)) + return; + + if (battery->temperature < 50) { + pr_info("%s: [AGE] skip (temperature:%d)\n", __func__, battery->temperature); + return; + } + + for (calc_step = battery->pdata->num_age_step - 1; calc_step >= 0; calc_step--) { + if (battery->pdata->age_data[calc_step].cycle <= max(battery->batt_cycle, battery->batt_full_status_usage)) + break; + } + + if ((calc_step == prev_step) && init) + return; + + init = true; + ret = sec_bat_set_aging_step(battery, calc_step); + dev_info(battery->dev, + "%s: %s change step (%d->%d), Cycle(%d)\n", + __func__, ret ? "Succeed in" : "Fail to", + prev_step, battery->pdata->age_step, max(battery->batt_cycle, battery->batt_full_status_usage)); +} +EXPORT_SYMBOL(sec_bat_aging_check); + +void sec_bat_check_battery_health(struct sec_battery_info *battery) +{ + static battery_health_condition default_table[3] = + {{.cycle = 900, .asoc = 75}, {.cycle = 1200, .asoc = 65}, {.cycle = 1500, .asoc = 55}}; + + battery_health_condition *ptable = default_table; + battery_health_condition state; + int i, battery_health, size = BATTERY_HEALTH_MAX; + + if (battery->pdata->health_condition == NULL) { + /* + * If a new type is added to misc_battery_health, default table cannot verify the actual state except "bad". + * If you want to modify to return the correct values for all states, + * add a table that matches the state added to the dt file. + */ + pr_info("%s: does not set health_condition_table, use default table\n", __func__); + size = 3; + } else { + ptable = battery->pdata->health_condition; + } + + /* Checking Cycle and ASoC */ + state.cycle = state.asoc = BATTERY_HEALTH_BAD; + for (i = size - 1; i >= 0; i--) { + if (ptable[i].cycle >= (battery->batt_cycle % 10000)) + state.cycle = i + BATTERY_HEALTH_GOOD; + if (ptable[i].asoc <= battery->batt_asoc) + state.asoc = i + BATTERY_HEALTH_GOOD; + } + battery_health = max(state.cycle, state.asoc); + pr_info("%s: update battery_health(%d), (%d - %d)\n", + __func__, battery_health, state.cycle, state.asoc); + /* Update battery health */ + sec_bat_set_misc_event(battery, + (battery_health << BATTERY_HEALTH_SHIFT), BATT_MISC_EVENT_BATTERY_HEALTH); +} +EXPORT_SYMBOL(sec_bat_check_battery_health); + +__visible_for_testing bool sec_bat_check_fullcharged_condition(struct sec_battery_info *battery) +{ + int full_check_type = SEC_BATTERY_FULLCHARGED_NONE; + + if (battery->charging_mode == SEC_BATTERY_CHARGING_1ST) + full_check_type = battery->pdata->full_check_type; + else + full_check_type = battery->pdata->full_check_type_2nd; + + switch (full_check_type) { + /* + * If these is NOT full check type or NONE full check type, + * it is full-charged + */ + case SEC_BATTERY_FULLCHARGED_CHGINT: + case SEC_BATTERY_FULLCHARGED_TIME: + case SEC_BATTERY_FULLCHARGED_NONE: + return true; + default: + break; + } + +#if defined(CONFIG_ENABLE_FULL_BY_SOC) + if (battery->capacity >= 100 && !battery->is_recharging) { + dev_info(battery->dev, "%s: enough SOC (%d%%), skip!\n", __func__, battery->capacity); + return true; + } +#endif + + if (battery->pdata->full_condition_type & SEC_BATTERY_FULL_CONDITION_SOC) { + if (battery->capacity < battery->pdata->full_condition_soc) { + dev_dbg(battery->dev, "%s: Not enough SOC (%d%%)\n", __func__, battery->capacity); + return false; + } + } + + if (battery->pdata->full_condition_type & SEC_BATTERY_FULL_CONDITION_VCELL) { + int full_condition_vcell = battery->pdata->full_condition_vcell; + + if (battery->thermal_zone == BAT_THERMAL_WARM) /* high temp swelling full */ + full_condition_vcell = battery->pdata->high_temp_float - 100; + + if (battery->voltage_now < full_condition_vcell) { + dev_dbg(battery->dev, "%s: Not enough VCELL (%dmV)\n", __func__, battery->voltage_now); + return false; + } + } + + if (battery->pdata->full_condition_type & SEC_BATTERY_FULL_CONDITION_AVGVCELL) { + if (battery->voltage_avg < battery->pdata->full_condition_avgvcell) { + dev_dbg(battery->dev, "%s: Not enough AVGVCELL (%dmV)\n", __func__, battery->voltage_avg); + return false; + } + } + + if (battery->pdata->full_condition_type & SEC_BATTERY_FULL_CONDITION_OCV) { + if (battery->voltage_ocv < battery->pdata->full_condition_ocv) { + dev_dbg(battery->dev, "%s: Not enough OCV (%dmV)\n", __func__, battery->voltage_ocv); + return false; + } + } + + return true; +} +EXPORT_SYMBOL_KUNIT(sec_bat_check_fullcharged_condition); + +__visible_for_testing int sec_bat_do_test_function(struct sec_battery_info *battery) +{ + union power_supply_propval value = {0, }; + + if (battery->test_mode == 0) + return battery->test_mode; + + pr_info("%s: Test Mode\n", __func__); + switch (battery->test_mode) { + case 1: + if (battery->status == POWER_SUPPLY_STATUS_CHARGING) { + sec_vote(battery->chgen_vote, VOTER_TEST_MODE, true, SEC_BAT_CHG_MODE_CHARGING_OFF); + sec_bat_set_charging_status(battery, + POWER_SUPPLY_STATUS_DISCHARGING); + } + break; + case 2: + if (battery->status == POWER_SUPPLY_STATUS_DISCHARGING) { + sec_vote(battery->chgen_vote, VOTER_TEST_MODE, true, SEC_BAT_CHG_MODE_CHARGING); + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_PROP_STATUS, value); + sec_bat_set_charging_status(battery, value.intval); + } + battery->test_mode = 0; + break; + case 3: // clear temp block + sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_GOOD); + sec_bat_set_charging_status(battery, + POWER_SUPPLY_STATUS_DISCHARGING); + break; + case 4: + if (battery->status == POWER_SUPPLY_STATUS_DISCHARGING) { + sec_vote(battery->chgen_vote, VOTER_TEST_MODE, true, SEC_BAT_CHG_MODE_CHARGING); + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_PROP_STATUS, value); + sec_bat_set_charging_status(battery, value.intval); + } + break; + default: + pr_info("%s: error test: unknown state\n", __func__); + break; + } + return battery->test_mode; +} +EXPORT_SYMBOL_KUNIT(sec_bat_do_test_function); + +static bool sec_bat_time_management(struct sec_battery_info *battery) +{ + struct timespec64 ts = {0, }; + unsigned long charging_time; + + if (battery->charging_start_time == 0 || !battery->safety_timer_set) { + dev_dbg(battery->dev, + "%s: Charging Disabled\n", __func__); + return true; + } + + ts = ktime_to_timespec64(ktime_get_boottime()); + + if (ts.tv_sec >= battery->charging_start_time) { + charging_time = ts.tv_sec - battery->charging_start_time; + } else { + charging_time = 0xFFFFFFFF - battery->charging_start_time + + ts.tv_sec; + } + + battery->charging_passed_time = charging_time; + + switch (battery->status) { + case POWER_SUPPLY_STATUS_FULL: + if (battery->expired_time == 0) { + dev_info(battery->dev, + "%s: Recharging Timer Expired\n", __func__); + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE); + sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_NOT_CHARGING); + battery->is_recharging = false; + sec_vote(battery->chgen_vote, VOTER_TIME_EXPIRED, true, SEC_BAT_CHG_MODE_CHARGING_OFF); + return false; + } + break; + case POWER_SUPPLY_STATUS_CHARGING: + if ((battery->pdata->full_condition_type & + SEC_BATTERY_FULL_CONDITION_NOTIMEFULL) && + (battery->is_recharging && (battery->expired_time == 0))) { + dev_info(battery->dev, + "%s: Recharging Timer Expired\n", __func__); + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE); + sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_NOT_CHARGING); + battery->is_recharging = false; + sec_vote(battery->chgen_vote, VOTER_TIME_EXPIRED, true, SEC_BAT_CHG_MODE_CHARGING_OFF); + return false; + } else if (!battery->is_recharging && + (battery->expired_time == 0)) { + dev_info(battery->dev, + "%s: Charging Timer Expired\n", __func__); + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE); + sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_NOT_CHARGING); + battery->cisd.data[CISD_DATA_SAFETY_TIMER]++; + battery->cisd.data[CISD_DATA_SAFETY_TIMER_PER_DAY]++; +#if IS_ENABLED(CONFIG_SEC_ABC) + sec_abc_send_event("MODULE=battery@WARN=safety_timer"); +#endif + sec_vote(battery->chgen_vote, VOTER_TIME_EXPIRED, true, SEC_BAT_CHG_MODE_CHARGING_OFF); + return false; + } + break; + default: + dev_err(battery->dev, + "%s: Undefine Battery Status\n", __func__); + return true; + } + + return true; +} + +bool sec_bat_check_full(struct sec_battery_info *battery, int full_check_type) +{ + union power_supply_propval value = {0, }; + int current_adc = 0; + bool ret = false; + int err = 0; + + switch (full_check_type) { + case SEC_BATTERY_FULLCHARGED_ADC: + current_adc = + sec_bat_get_adc_data(battery->dev, + SEC_BAT_ADC_CHANNEL_FULL_CHECK, + battery->pdata->adc_check_count, + battery->pdata->adc_read_type); + + dev_dbg(battery->dev, "%s: Current ADC (%d)\n", __func__, current_adc); + +#if !defined(CONFIG_SEC_KUNIT) /* In Kunit test, battery->current_adc is changed by KUNIT TC */ + if (current_adc < 0) + break; + + battery->current_adc = current_adc; +#endif + + if (battery->current_adc < battery->topoff_condition) { + battery->full_check_cnt++; + dev_dbg(battery->dev, "%s: Full Check ADC (%d)\n", + __func__, battery->full_check_cnt); + } else { + battery->full_check_cnt = 0; + } + break; + case SEC_BATTERY_FULLCHARGED_FG_CURRENT: + if ((battery->current_now > 0 && battery->current_now < + battery->topoff_condition) && + (battery->current_avg > 0 && battery->current_avg < battery->topoff_condition)) { + battery->full_check_cnt++; + dev_dbg(battery->dev, "%s: Full Check Current (%d)\n", + __func__, battery->full_check_cnt); + } else { + battery->full_check_cnt = 0; + } + break; + case SEC_BATTERY_FULLCHARGED_TIME: + if ((battery->charging_mode == + SEC_BATTERY_CHARGING_2ND ? + (battery->charging_passed_time - + battery->charging_fullcharged_time) : + battery->charging_passed_time) > + battery->topoff_condition) { + battery->full_check_cnt++; + dev_dbg(battery->dev, "%s: Full Check Time (%d)\n", + __func__, battery->full_check_cnt); + } else { + battery->full_check_cnt = 0; + } + break; + case SEC_BATTERY_FULLCHARGED_SOC: + if (battery->capacity <= + battery->topoff_condition) { + battery->full_check_cnt++; + dev_dbg(battery->dev, "%s: Full Check SOC (%d)\n", + __func__, battery->full_check_cnt); + } else { + battery->full_check_cnt = 0; + } + break; + + case SEC_BATTERY_FULLCHARGED_CHGGPIO: + err = gpio_request( + battery->pdata->chg_gpio_full_check, + "GPIO_CHG_FULL"); + if (err) { + dev_err(battery->dev, + "%s: Error in Request of GPIO\n", __func__); + break; + } + if (!(gpio_get_value_cansleep( + battery->pdata->chg_gpio_full_check) ^ + !battery->pdata->chg_polarity_full_check)) { + battery->full_check_cnt++; + dev_dbg(battery->dev, "%s: Full Check GPIO (%d)\n", + __func__, battery->full_check_cnt); + } else { + battery->full_check_cnt = 0; + } + gpio_free(battery->pdata->chg_gpio_full_check); + break; + case SEC_BATTERY_FULLCHARGED_CHGINT: + case SEC_BATTERY_FULLCHARGED_CHGPSY: + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_PROP_STATUS, value); + + if (value.intval == POWER_SUPPLY_STATUS_FULL) { + battery->full_check_cnt++; + dev_info(battery->dev, "%s: Full Check Charger (%d)\n", + __func__, battery->full_check_cnt); + } else { + battery->full_check_cnt = 0; + } + break; + + /* + * If these is NOT full check type or NONE full check type, + * it is full-charged + */ + case SEC_BATTERY_FULLCHARGED_NONE: + battery->full_check_cnt = 0; + ret = true; + break; +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + case SEC_BATTERY_FULLCHARGED_LIMITER: + value.intval = 1; + psy_do_property(battery->pdata->dual_battery_name, get, + POWER_SUPPLY_PROP_STATUS, value); + + if (value.intval == POWER_SUPPLY_STATUS_FULL) { + battery->full_check_cnt++; + dev_info(battery->dev, "%s: Full Check Limiter (%d)\n", + __func__, battery->full_check_cnt); + } else { + battery->full_check_cnt = 0; + } + break; +#endif + default: + dev_err(battery->dev, + "%s: Invalid Full Check\n", __func__); + break; + } + +#if defined(CONFIG_ENABLE_FULL_BY_SOC) + if (battery->capacity >= 100 && + battery->charging_mode == SEC_BATTERY_CHARGING_1ST && + !battery->is_recharging) { + battery->full_check_cnt = battery->pdata->full_check_count; + dev_info(battery->dev, + "%s: enough SOC to make FULL(%d%%)\n", + __func__, battery->capacity); + } +#endif + + if (battery->full_check_cnt >= + battery->pdata->full_check_count) { + battery->full_check_cnt = 0; + ret = true; + } + + if (ret && (battery->current_event & SEC_BAT_CURRENT_EVENT_SWELLING_MODE)) { + battery->cisd.data[CISD_DATA_SWELLING_FULL_CNT]++; + battery->cisd.data[CISD_DATA_SWELLING_FULL_CNT_PER_DAY]++; + } + return ret; +} +EXPORT_SYMBOL_KUNIT(sec_bat_check_full); + +static void sec_bat_check_eoc_for_repcap(struct sec_battery_info *battery, int full_check_type) +{ + union power_supply_propval value = {0, }; + + if (battery->pdata->soc_by_repcap_en && battery->eoc_d->eoc_check) { + pr_info("%s: check eoc for repcap\n", __func__); + switch (full_check_type) { + case SEC_BATTERY_FULLCHARGED_FG_CURRENT: + if ((battery->current_now > 0 && + battery->current_now < battery->pdata->full_check_current_1st) && + (battery->current_avg > 0 && + battery->current_avg < battery->pdata->full_check_current_1st)) { + battery->eoc_d->eoc_cnt++; + pr_info("%s : full cnt = %d FG current full check cnt = %d\n", __func__, + battery->pdata->full_check_count, battery->eoc_d->eoc_cnt); + if (battery->eoc_d->eoc_cnt >= battery->pdata->full_check_count) { + battery->eoc_d->eoc_check = false; + value.intval = battery->capacity; + /*update 1st EOC repcap value */ + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_EXT_PROP_STATUS_FULL_REPCAP, value); + pr_info("%s:first EOC\n", __func__); + } + } else { + pr_info("%s: not first EOC\n", __func__); + battery->eoc_d->eoc_cnt = 0; // check by default value + } + break; + default: + dev_err(battery->dev, + "%s: Invalid Full Check\n", __func__); + break; + } + } +} + +static void sec_bat_enable_eoc_check(struct sec_battery_info *battery) +{ + if (battery->pdata->soc_by_repcap_en && battery->capacity < 100 && + battery->charging_mode == SEC_BATTERY_CHARGING_1ST) { + battery->eoc_d->eoc_check = true; // check for default value, check charging mode value + + pr_info("%s : eoc_check(%d) charging mode int(%d) , battery_capacity: %d\n", + __func__, battery->eoc_d->eoc_check, battery->charging_mode, battery->capacity); + } +} + +bool sec_bat_check_fullcharged(struct sec_battery_info *battery) +{ + int full_check_type = SEC_BATTERY_FULLCHARGED_NONE; + + sec_bat_enable_eoc_check(battery); + + if (!sec_bat_check_fullcharged_condition(battery)) + return false; + + sec_bat_check_eoc_for_repcap(battery, battery->pdata->full_check_type); + + if (battery->charging_mode == SEC_BATTERY_CHARGING_1ST) + full_check_type = battery->pdata->full_check_type; + else + full_check_type = battery->pdata->full_check_type_2nd; + + return sec_bat_check_full(battery, full_check_type); +} + +__visible_for_testing void sec_bat_do_fullcharged(struct sec_battery_info *battery, bool force_fullcharged) +{ + union power_supply_propval value = {0, }; + + /* + * To let charger/fuel gauge know the full status, + * set status before calling sec_bat_set_charge() + */ + if (battery->status != POWER_SUPPLY_STATUS_FULL) { + battery->cisd.data[CISD_DATA_FULL_COUNT]++; + battery->cisd.data[CISD_DATA_FULL_COUNT_PER_DAY]++; + } + sec_bat_set_charging_status(battery, + POWER_SUPPLY_STATUS_FULL); + + if (battery->charging_mode == SEC_BATTERY_CHARGING_1ST && + battery->pdata->full_check_type_2nd != SEC_BATTERY_FULLCHARGED_NONE && !force_fullcharged) { + battery->charging_mode = SEC_BATTERY_CHARGING_2ND; + battery->charging_fullcharged_time = battery->charging_passed_time; + sec_vote(battery->topoff_vote, VOTER_FULL_CHARGE, true, battery->pdata->full_check_current_2nd); + sec_vote(battery->chgen_vote, VOTER_FULL_CHARGE, true, SEC_BAT_CHG_MODE_CHARGING); + pr_info("%s: 1st charging is done\n", __func__); + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_1ST_DONE, value); + } else { + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + battery->is_recharging = false; + + if (!battery->wdt_kick_disable) { + pr_info("%s: wdt kick enable -> Charger Off, %d\n", + __func__, battery->wdt_kick_disable); + sec_vote(battery->chgen_vote, VOTER_FULL_CHARGE, true, SEC_BAT_CHG_MODE_CHARGING_OFF); + if (battery->pdata->change_FV_after_full) + sec_vote(battery->fv_vote, VOTER_FULL_CHARGE, true, battery->pdata->change_FV_after_full); + pr_info("%s: 2nd charging is done\n", __func__); + if (is_wireless_type(battery->cable_type)) { + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_2ND_DONE, value); +#if defined(CONFIG_WIRELESS_RX_PHM_CTRL) + value.intval = ENTER_PHM; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_RX_PHM, value); +#endif + } + } else { + pr_info("%s: wdt kick disabled -> skip charger off, %d\n", + __func__, battery->wdt_kick_disable); + } + + sec_bat_aging_check(battery); + + /* this concept is only for power-off charging mode*/ + if (!battery->store_mode && sec_bat_get_lpmode()) { + /* vbus level : 9V --> 5V */ + sec_vote(battery->iv_vote, VOTER_FULL_CHARGE, true, SEC_INPUT_VOLTAGE_5V); + pr_info("%s: vbus is set 5V by 2nd full\n", __func__); + } + + value.intval = POWER_SUPPLY_STATUS_FULL; + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_PROP_STATUS, value); + } + + /* + * platform can NOT get information of battery + * because wakeup time is too short to check uevent + * To make sure that target is wakeup if full-charged, + * activated wake lock in a few seconds + */ + if (battery->pdata->polling_type == SEC_BATTERY_MONITOR_ALARM) + sb_set_vbus_wake(battery->vbus_ws, battery->health, battery->cable_type); +} +EXPORT_SYMBOL_KUNIT(sec_bat_do_fullcharged); + +static bool sec_bat_fullcharged_check(struct sec_battery_info *battery) +{ + if ((battery->charging_mode == SEC_BATTERY_CHARGING_NONE) || + (battery->status == POWER_SUPPLY_STATUS_NOT_CHARGING)) { + dev_dbg(battery->dev, + "%s: No Need to Check Full-Charged\n", __func__); + return true; + } + + if (sec_bat_check_fullcharged(battery)) { + union power_supply_propval value = {0, }; + if (battery->capacity < 100) { + /* update capacity max */ + value.intval = battery->capacity; + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_PROP_CHARGE_FULL, value); + pr_info("%s : forced full-charged sequence for the capacity(%d)\n", + __func__, battery->capacity); + battery->full_check_cnt = battery->pdata->full_check_count; + } else { + sec_bat_do_fullcharged(battery, false); + } + } + + dev_info(battery->dev, + "%s: Charging Mode : %s\n", __func__, + battery->is_recharging ? + sb_get_cm_str(SEC_BATTERY_CHARGING_RECHARGING) : + sb_get_cm_str(battery->charging_mode)); + + return true; +} + +int sec_bat_get_inbat_vol_ocv(struct sec_battery_info *battery) +{ + sec_battery_platform_data_t *pdata = battery->pdata; + union power_supply_propval value = {0, }; + int j, k, ocv, jig_on = 0, ocv_data[10]; + int ret = 0; + + switch (pdata->inbat_ocv_type) { + case SEC_BATTERY_OCV_FG_SRC_CHANGE: + pr_info("%s: FGSRC_SWITCHING_VBAT\n", __func__); + value.intval = SEC_BAT_INBAT_FGSRC_SWITCHING_VBAT; + psy_do_property(pdata->fgsrc_switch_name, set, + POWER_SUPPLY_EXT_PROP_FGSRC_SWITCHING, value); + + for (j = 0; j < 10; j++) { + msleep(175); + value.intval = SEC_BATTERY_VOLTAGE_MV; + psy_do_property(pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_VOLTAGE_NOW, value); + ocv_data[j] = value.intval; + } + + ret = psy_do_property(pdata->fuelgauge_name, get, + POWER_SUPPLY_EXT_PROP_JIG_GPIO, value); + if (value.intval < 0 || ret < 0) + jig_on = 0; + else if (value.intval == 0) + jig_on = 1; + + if (battery->is_jig_on || sec_bat_get_facmode() || battery->factory_mode || jig_on) { + pr_info("%s: FGSRC_SWITCHING_VSYS\n", __func__); + value.intval = SEC_BAT_INBAT_FGSRC_SWITCHING_VSYS; + psy_do_property(pdata->fgsrc_switch_name, set, + POWER_SUPPLY_EXT_PROP_FGSRC_SWITCHING, value); + } + + for (j = 1; j < 10; j++) { + ocv = ocv_data[j]; + k = j; + while (k > 0 && ocv_data[k-1] > ocv) { + ocv_data[k] = ocv_data[k-1]; + k--; + } + ocv_data[k] = ocv; + } + + for (j = 0; j < 10; j++) + pr_info("%s: [%d] %d\n", __func__, j, ocv_data[j]); + + ocv = 0; + for (j = 2; j < 8; j++) + ocv += ocv_data[j]; + + ret = ocv / 6; + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + /* just for debug */ + value.intval = SEC_DUAL_BATTERY_MAIN; + psy_do_property(pdata->dual_battery_name, get, + POWER_SUPPLY_PROP_VOLTAGE_AVG, value); + value.intval = SEC_DUAL_BATTERY_SUB; + psy_do_property(pdata->dual_battery_name, get, + POWER_SUPPLY_PROP_VOLTAGE_AVG, value); +#endif + break; + case SEC_BATTERY_OCV_FG_NOSRC_CHANGE: + value.intval = SEC_BATTERY_VOLTAGE_MV; + psy_do_property(pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_VOLTAGE_NOW, value); + ret = value.intval; + break; + case SEC_BATTERY_OCV_ADC: + /* run twice */ + ret = (sec_bat_get_inbat_vol_by_adc(battery) + + sec_bat_get_inbat_vol_by_adc(battery)) / 2; + break; + case SEC_BATTERY_OCV_VOLT_FROM_PMIC: + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_EXT_PROP_PMIC_BAT_VOLTAGE, + value); + ret = value.intval; + break; + case SEC_BATTERY_OCV_NONE: + default: + break; + }; + + pr_info("%s: [%d] in-battery voltage ocv(%d)\n", + __func__, pdata->inbat_ocv_type, ret); + + return ret; +} + +#if !defined(CONFIG_SEC_FACTORY) +static bool sec_bat_is_unknown_wpc_temp( + int wpc_temp, int usb_temp, bool loosened_unknown_temp) +{ + if (loosened_unknown_temp) { + if (usb_temp - wpc_temp >= 250) + return true; + } else { + if (wpc_temp <= (-200)) + return true; + } + return false; +} + +static void sec_bat_calc_unknown_wpc_temp( + struct sec_battery_info *battery, int *batt_temp, int wpc_temp, int usb_temp) +{ + if ((battery->pdata->wpc_thm_info.source != SEC_BATTERY_THERMAL_SOURCE_NONE) && + !is_wireless_all_type(battery->cable_type)) { + if (sec_bat_is_unknown_wpc_temp(wpc_temp, usb_temp, + battery->pdata->loosened_unknown_temp)) { + if (usb_temp >= 270) + *batt_temp = (usb_temp + 60) > 900 ? 900 : (usb_temp + 60); + else if (usb_temp <= 210) + *batt_temp = (usb_temp - 50) < (-200) ? (-200) : (usb_temp - 50); + else + *batt_temp = (170 * usb_temp - 26100) / 60; + pr_info("%s : Tbat(wpc_temp:%d) to (%d)\n", __func__, wpc_temp, *batt_temp); + } + } +} + +int adjust_bat_temp(struct sec_battery_info *battery, int batt_temp, int sub_bat_temp) +{ + int bat_t = 0, bat_t_1 = 0; /* bat_t = bat_v(t), bat_t_1 = bat_v(t_1) */ + int lr_delta = battery->pdata->lr_delta; + struct timespec64 ts = {0, }; + + ts = ktime_to_timespec64(ktime_get_boottime()); + + /* this temperature prediction formula has high accuracy at room temperature */ + if (batt_temp <= 250 || sub_bat_temp <= 0) { + battery->lrp_temp = 0x7FFF; + battery->lr_start_time = 0; + battery->lr_time_span = 0; + + return batt_temp; + } + + battery->lr_time_span = ts.tv_sec - battery->lr_start_time; + battery->lr_start_time = battery->lr_start_time + battery->lr_time_span; + + if (battery->lr_time_span > 60) + battery->lr_time_span = 60; + if (battery->lr_time_span < 1) + battery->lr_time_span = 1; + + if (battery->lrp_temp == 0x7FFF) { + /* init bat_t_1 as bat(0) */ + bat_t_1 = (sub_bat_temp*battery->pdata->lr_param_init_sub_bat_thm + batt_temp*battery->pdata->lr_param_init_bat_thm)/100; + } else + bat_t_1 = battery->lr_bat_t_1; /* bat_v(t) as previous bat_v(t_1) */ + + /* + * calculate bat_v(t) + * bat_v(0) = bat_v + * bat_v(t) = bat_v(t_1) + ((raw - bat_v(t_1))*0.016*time_span+0.5) + * lrp_temp = A*bat_v(t) + B*bat + */ + bat_t = ((bat_t_1*1000) + ((batt_temp - bat_t_1)*lr_delta*battery->lr_time_span+battery->pdata->lr_round_off))/1000; + battery->lrp_temp = (battery->pdata->lr_param_bat_thm*bat_t + battery->pdata->lr_param_sub_bat_thm*sub_bat_temp)/1000; + + if ((is_wireless_all_type(battery->cable_type) || + battery->misc_event & BATT_MISC_EVENT_WIRELESS_DET_LEVEL) && + battery->lrp_temp >= 350) + battery->lrp_temp = battery->lrp_temp - 10; + + pr_info("%s : LRP: %d, lrp_temp_raw: %d wpc_temp: %d lrp_bat_t_1: %d lrp_bat_v: %d time_span: %lds\n", + __func__, battery->lrp_temp, batt_temp, sub_bat_temp, bat_t_1, bat_t, battery->lr_time_span); + + battery->lr_bat_t_1 = bat_t; + + return battery->lrp_temp; +} +EXPORT_SYMBOL(adjust_bat_temp); +#endif + +int sec_bat_get_temperature(struct device *dev, struct sec_bat_thm_info *info, int old_val, + char *chg_name, char *fg_name, int adc_read_type) +{ + union power_supply_propval value = {0, }; + int temp = old_val; + int adc; + + if (info->check_type == SEC_BATTERY_TEMP_CHECK_FAKE) + return FAKE_TEMP; + + if (info->test > -300 && info->test < 3000) { + pr_info("%s : temperature test %d\n", __func__, info->test); + return info->test; + } + /* get battery thm info */ + switch (info->source) { + case SEC_BATTERY_THERMAL_SOURCE_FG: + psy_do_property(fg_name, get, POWER_SUPPLY_PROP_TEMP, value); + temp = value.intval; + break; + case SEC_BATTERY_THERMAL_SOURCE_CALLBACK: + pr_info("%s : need to implement\n", __func__); + break; + case SEC_BATTERY_THERMAL_SOURCE_ADC: + adc = sec_bat_get_adc_data(dev, info->channel, info->check_count, adc_read_type); + if (adc >= 0) { + info->adc = adc; + sec_bat_convert_adc_to_val(adc, info->offset, + info->adc_table, info->adc_table_size, &temp); + } + break; + case SEC_BATTERY_THERMAL_SOURCE_CHG_ADC: + if (!psy_do_property(chg_name, get, POWER_SUPPLY_PROP_TEMP, value)) { + info->adc = value.intval; + sec_bat_convert_adc_to_val(value.intval, info->offset, + info->adc_table, info->adc_table_size, &temp); + } + break; + case SEC_BATTERY_THERMAL_SOURCE_FG_ADC: +#if IS_ENABLED(CONFIG_MTK_CHARGER) + value.intval = SEC_BATTERY_TEMP_ADC; +#endif + if (!psy_do_property(fg_name, get, POWER_SUPPLY_PROP_TEMP, value)) { + info->adc = value.intval; + sec_bat_convert_adc_to_val(value.intval, info->offset, + info->adc_table, info->adc_table_size, &temp); + } + break; + default: + break; + } + + return temp; +} +EXPORT_SYMBOL(sec_bat_get_temperature); + + +int sec_bat_adjust_temperature(struct sec_battery_info *battery, int read_temp, int prev_temp) +{ + int ret = read_temp; + static bool temp_init; + + if (temp_init == false) { + temp_init = true; + + return ret; + } + + if ((read_temp - prev_temp) > battery->pdata->batt_temp_adj_gap_inc) + ret = prev_temp + battery->pdata->batt_temp_adj_gap_inc; + + pr_info("%s: read: %d, prev: %d, now: %d\n", + __func__, read_temp, prev_temp, ret); + return ret; +} + +__visible_for_testing void sec_bat_get_temperature_info(struct sec_battery_info *battery) +{ + union power_supply_propval value = {0, }; + static bool shipmode_en = false; + int batt_temp = battery->temperature; + int usb_temp = battery->usb_temp; + int chg_temp = battery->chg_temp; + int dchg_temp = battery->dchg_temp; + int wpc_temp = battery->wpc_temp; + int sub_bat_temp = battery->sub_bat_temp; + int blkt_temp = battery->blkt_temp; + char *fg_name = battery->pdata->fuelgauge_name; + char *chg_name = battery->pdata->charger_name; + + /* get battery thm info */ + batt_temp = sec_bat_get_temperature(battery->dev, &battery->pdata->bat_thm_info, batt_temp, + chg_name, fg_name, battery->pdata->adc_read_type); + /* get usb thm info */ + usb_temp = sec_bat_get_temperature(battery->dev, &battery->pdata->usb_thm_info, usb_temp, + chg_name, fg_name, battery->pdata->adc_read_type); + /* get chg thm info */ + chg_temp = sec_bat_get_temperature(battery->dev, &battery->pdata->chg_thm_info, chg_temp, + chg_name, fg_name, battery->pdata->adc_read_type); + /* get wpc thm info */ + wpc_temp = sec_bat_get_temperature(battery->dev, &battery->pdata->wpc_thm_info, wpc_temp, + chg_name, fg_name, battery->pdata->adc_read_type); + /* get sub_bat thm info */ + sub_bat_temp = sec_bat_get_temperature(battery->dev, &battery->pdata->sub_bat_thm_info, sub_bat_temp, + chg_name, fg_name, battery->pdata->adc_read_type); +#if !defined(CONFIG_SEC_FACTORY) + if (battery->pdata->lr_enable) + batt_temp = adjust_bat_temp(battery, batt_temp, sub_bat_temp); +#endif + /* get blkt thm info */ + blkt_temp = sec_bat_get_temperature(battery->dev, &battery->pdata->blk_thm_info, blkt_temp, + chg_name, fg_name, battery->pdata->adc_read_type); + +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + if (battery->pdata->dctp_by_cgtp) + dchg_temp = chg_temp; + else if (is_pd_apdo_wire_type(battery->wire_status)) + dchg_temp = sec_bat_get_temperature(battery->dev, &battery->pdata->dchg_thm_info, + dchg_temp, chg_name, fg_name, battery->pdata->adc_read_type); +#endif + +#if !defined(CONFIG_SEC_FACTORY) + if (battery->pdata->sub_temp_control_source == TEMP_CONTROL_SOURCE_WPC_THM) + sec_bat_calc_unknown_wpc_temp(battery, &sub_bat_temp, wpc_temp, usb_temp); + else + sec_bat_calc_unknown_wpc_temp(battery, &batt_temp, wpc_temp, usb_temp); +#endif + + if (battery->pdata->batt_temp_adj_gap_inc) + battery->temperature = sec_bat_adjust_temperature(battery, + batt_temp, battery->temperature); + else + battery->temperature = batt_temp; + + if (battery->pdata->lrpts_by_batts) + battery->lrp = battery->temperature; + + battery->temper_amb = batt_temp; + battery->usb_temp = usb_temp; + battery->chg_temp = chg_temp; + battery->dchg_temp = dchg_temp; + battery->wpc_temp = wpc_temp; + battery->sub_bat_temp = sub_bat_temp; + battery->blkt_temp = blkt_temp; + + value.intval = battery->temperature; +#if defined(CONFIG_SEC_FACTORY) + if (battery->pdata->usb_thm_info.check_type && + (battery->temperature <= (-200))) { + value.intval = (battery->usb_temp <= (-200) ? battery->chg_temp : battery->usb_temp); + } +#endif + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_PROP_TEMP, value); + + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_PROP_TEMP_AMBIENT, value); + + if (battery->pdata->en_auto_shipmode_temp_ctrl) { + if (battery->temperature < 0 && !shipmode_en) { + value.intval = 0; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_AUTO_SHIPMODE_CONTROL, value); + shipmode_en = true; + } else if (battery->temperature >= 50 && shipmode_en) { + value.intval = 1; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_AUTO_SHIPMODE_CONTROL, value); + shipmode_en = false; + } + } +} +EXPORT_SYMBOL_KUNIT(sec_bat_get_temperature_info); + +void sec_bat_get_battery_info(struct sec_battery_info *battery) +{ + union power_supply_propval value = {0, }; + char str[1024] = {0, }; + + value.intval = SEC_BATTERY_VOLTAGE_MV; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_VOLTAGE_NOW, value); + battery->voltage_now = value.intval; + + value.intval = SEC_BATTERY_VOLTAGE_MV; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_VOLTAGE_AVG, value); + battery->voltage_avg = value.intval; + + /* Do not call it to reduce time after cable_work, this function call FG full log */ + if (!(battery->current_event & SEC_BAT_CURRENT_EVENT_SKIP_HEATING_CONTROL)) { + value.intval = SEC_BATTERY_VOLTAGE_MV; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_VOLTAGE_OCV, value); + battery->voltage_ocv = value.intval; + } + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + /* get main voltage */ + value.intval = SEC_DUAL_BATTERY_MAIN; + psy_do_property(battery->pdata->dual_battery_name, get, + POWER_SUPPLY_PROP_VOLTAGE_NOW, value); + battery->voltage_now_main = value.intval; + + /* get sub voltage */ + value.intval = SEC_DUAL_BATTERY_SUB; + psy_do_property(battery->pdata->dual_battery_name, get, + POWER_SUPPLY_PROP_VOLTAGE_NOW, value); + battery->voltage_now_sub = value.intval; + + /* get main voltage */ + value.intval = SEC_DUAL_BATTERY_MAIN; + psy_do_property(battery->pdata->dual_battery_name, get, + POWER_SUPPLY_PROP_VOLTAGE_AVG, value); + battery->voltage_avg_main = value.intval; + + /* get sub voltage */ + value.intval = SEC_DUAL_BATTERY_SUB; + psy_do_property(battery->pdata->dual_battery_name, get, + POWER_SUPPLY_PROP_VOLTAGE_AVG, value); + battery->voltage_avg_sub = value.intval; + + /* get main current */ + value.intval = SEC_DUAL_BATTERY_MAIN; + psy_do_property(battery->pdata->dual_battery_name, get, + POWER_SUPPLY_PROP_CURRENT_NOW, value); + battery->current_now_main = value.intval; + + /* get sub current */ + value.intval = SEC_DUAL_BATTERY_SUB; + psy_do_property(battery->pdata->dual_battery_name, get, + POWER_SUPPLY_PROP_CURRENT_NOW, value); + battery->current_now_sub = value.intval; + + /* get main current */ + value.intval = SEC_DUAL_BATTERY_MAIN; + psy_do_property(battery->pdata->dual_battery_name, get, + POWER_SUPPLY_PROP_CURRENT_AVG, value); + battery->current_avg_main = value.intval; + + /* get sub current */ + value.intval = SEC_DUAL_BATTERY_SUB; + psy_do_property(battery->pdata->dual_battery_name, get, + POWER_SUPPLY_PROP_CURRENT_AVG, value); + battery->current_avg_sub = value.intval; +#endif + +#if IS_ENABLED(CONFIG_DUAL_FUELGAUGE) + /* get main soc */ + value.intval = SEC_DUAL_BATTERY_MAIN; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_EXT_PROP_REPSOC, value); + battery->main_capacity = value.intval; + + /* get sub soc */ + value.intval = SEC_DUAL_BATTERY_SUB; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_EXT_PROP_REPSOC, value); + battery->sub_capacity = value.intval; +#endif + + value.intval = SEC_BATTERY_CURRENT_MA; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CURRENT_NOW, value); + battery->current_now = value.intval; + + value.intval = SEC_BATTERY_CURRENT_MA; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CURRENT_AVG, value); + battery->current_avg = value.intval; + +#if IS_ENABLED(CONFIG_FUELGAUGE_MAX77705) + value.intval = SEC_BATTERY_ISYS_AVG_MA; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_EXT_PROP_MEASURE_SYS, value); + battery->current_sys_avg = value.intval; + + value.intval = SEC_BATTERY_ISYS_MA; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_EXT_PROP_MEASURE_SYS, value); + battery->current_sys = value.intval; +#endif + + /* input current limit in charger */ + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, value); + battery->current_max = value.intval; + + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CHARGE_COUNTER, value); + battery->charge_counter = value.intval; + + /* check abnormal status for wireless charging */ + if (!(battery->current_event & SEC_BAT_CURRENT_EVENT_SKIP_HEATING_CONTROL) && + (is_wireless_type(battery->cable_type) || battery->wc_tx_enable)) { + value.intval = (battery->status == POWER_SUPPLY_STATUS_FULL) ? + 100 : battery->capacity; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_PROP_ENERGY_NOW, value); + } +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + value.intval = (battery->status == POWER_SUPPLY_STATUS_FULL) ? + 100 : battery->capacity; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_PROP_CAPACITY, value); +#endif + + sec_bat_get_temperature_info(battery); + + /* To get SOC value (NOT raw SOC), need to reset value */ + value.intval = 0; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CAPACITY, value); + /* if the battery status was full, and SOC wasn't 100% yet, + then ignore FG SOC, and report (previous SOC +1)% */ + battery->capacity = value.intval; + + /* voltage information */ +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + sprintf(str, "%s:Vm(%dmV),Vavgm(%dmV),Vs(%dmV),Vavgs(%dmV),", __func__, + battery->voltage_now_main, battery->voltage_avg_main, + battery->voltage_now_sub, battery->voltage_avg_sub + ); +#else + sprintf(str, "%s:Vnow(%dmV),Vavg(%dmV),", __func__, + battery->voltage_now, battery->voltage_avg + ); +#endif + + /* current information */ +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + sprintf(str + strlen(str), "Inow(%dmA),Iavg(%dmA),Isysavg(%dmA),Inow_m(%dmA),Iavg_m(%dmA),Inow_s(%dmA),Iavg_s(%dmA),Imax(%dmA),Ichg(%dmA),Ichg_m(%dmA),Ichg_s(%dmA),SOC(%d%%),", + battery->current_now, battery->current_avg, + battery->current_sys_avg, battery->current_now_main, + battery->current_avg_main, battery->current_now_sub, + battery->current_avg_sub, battery->current_max, + battery->charging_current, battery->main_current, + battery->sub_current, battery->capacity + ); +#else + sprintf(str + strlen(str), "Inow(%dmA),Iavg(%dmA),Isysavg(%dmA),Imax(%dmA),Ichg(%dmA),SOC(%d%%),", + battery->current_now, battery->current_avg, + battery->current_sys_avg, battery->current_max, + battery->charging_current, battery->capacity + ); +#endif + + /* soc information */ +#if IS_ENABLED(CONFIG_DUAL_FUELGAUGE) + sprintf(str + strlen(str), "MSOC(%d.%d%%),SSOC(%d.%d%%),", + battery->main_capacity/10, battery->main_capacity%10, + battery->sub_capacity/10, battery->sub_capacity%10 + ); +#endif + + /* temperature information */ +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + sprintf(str + strlen(str), "Tsub(%d),", + battery->sub_bat_temp + ); +#endif + sprintf(str + strlen(str), "Tbat(%d),Tusb(%d),Tchg(%d),Twpc(%d),Tblkt(%d),Tlrp(%d),", + battery->temperature, battery->usb_temp, + battery->chg_temp, battery->wpc_temp, + battery->blkt_temp, battery->lrp + ); +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + sprintf(str + strlen(str), "Tdchg(%d)\n", + battery->dchg_temp + ); +#endif + pr_info("%s", str); + +#if defined(CONFIG_ARCH_QCOM) && !(defined(CONFIG_ARCH_EXYNOS) || defined(CONFIG_ARCH_MEDIATEK)) + if (!strcmp(battery->pdata->chip_vendor, "QCOM")) + battery_last_dcvs(battery->capacity, battery->voltage_avg, + battery->temperature, battery->current_avg); +#endif +#if IS_ENABLED(CONFIG_SEC_DEBUG_EXTRA_INFO) && !defined(CONFIG_ARCH_MTK_PROJECT) + if (!strcmp(battery->pdata->chip_vendor, "LSI")) + secdbg_exin_set_batt(battery->capacity, battery->voltage_avg, + battery->temperature, battery->current_avg); +#endif +} +EXPORT_SYMBOL(sec_bat_get_battery_info); + +__visible_for_testing void sec_bat_polling_work(struct work_struct *work) +{ + struct sec_battery_info *battery = container_of( + work, struct sec_battery_info, polling_work.work); + + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + dev_dbg(battery->dev, "%s: Activated\n", __func__); +} +EXPORT_SYMBOL_KUNIT(sec_bat_polling_work); + +static void sec_bat_program_alarm( + struct sec_battery_info *battery, int seconds) +{ + alarm_start(&battery->polling_alarm, + ktime_add(battery->last_poll_time, ktime_set(seconds, 0))); +} + +static unsigned int sec_bat_get_polling_time( + struct sec_battery_info *battery) +{ + if (battery->status == POWER_SUPPLY_STATUS_FULL) + battery->polling_time = battery->pdata->polling_time[SEC_BATTERY_POLLING_TIME_CHARGING]; + else + battery->polling_time = battery->pdata->polling_time[battery->status]; + + battery->polling_short = true; + + switch (battery->status) { + case POWER_SUPPLY_STATUS_CHARGING: + if (battery->polling_in_sleep) + battery->polling_short = false; + break; + case POWER_SUPPLY_STATUS_DISCHARGING: + if (battery->polling_in_sleep) + battery->polling_time = battery->pdata->polling_time[SEC_BATTERY_POLLING_TIME_SLEEP]; + else + battery->polling_time = battery->pdata->polling_time[battery->status]; + + if (!battery->wc_enable || + (battery->d2d_auth == D2D_AUTH_SRC)) { + battery->polling_time = battery->pdata->polling_time[SEC_BATTERY_POLLING_TIME_CHARGING]; + pr_info("%s: wc_enable is false, or hp d2d, polling time is 30sec\n", __func__); + } + + battery->polling_short = false; + break; + case POWER_SUPPLY_STATUS_FULL: + if (battery->polling_in_sleep) { + if (!(battery->pdata->full_condition_type & SEC_BATTERY_FULL_CONDITION_NOSLEEPINFULL) && + battery->charging_mode == SEC_BATTERY_CHARGING_NONE) + battery->polling_time = battery->pdata->polling_time[SEC_BATTERY_POLLING_TIME_SLEEP]; + battery->polling_short = false; + } else { + if (battery->charging_mode == SEC_BATTERY_CHARGING_NONE) + battery->polling_short = false; + } + break; + case POWER_SUPPLY_STATUS_NOT_CHARGING: + if ((battery->health == POWER_SUPPLY_HEALTH_OVERVOLTAGE || + (battery->health == POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE)) && + (battery->health_check_count > 0)) { + battery->health_check_count--; + battery->polling_time = 1; + battery->polling_short = false; + } + break; + } + +#if defined(CONFIG_WIRELESS_TX_MODE) + if (battery->wc_tx_enable) { + battery->polling_time = 10; + battery->polling_short = false; + pr_info("%s: Tx mode enable polling time is 10sec\n", __func__); + } +#endif + + if (is_pd_apdo_wire_type(battery->cable_type) && + (battery->pd_list.now_isApdo || battery->ta_alert_mode != OCP_NONE)) { + battery->polling_time = 10; + battery->polling_short = false; + pr_info("%s: DC mode enable polling time is 10sec\n", __func__); + } + + if (battery->polling_short) + return battery->pdata->polling_time[SEC_BATTERY_POLLING_TIME_BASIC]; + + return battery->polling_time; +} + +__visible_for_testing bool sec_bat_is_short_polling(struct sec_battery_info *battery) +{ + /* + * Change the full and short monitoring sequence + * Originally, full monitoring was the last time of polling_count + * But change full monitoring to first time + * because temperature check is too late + */ + if (!battery->polling_short || battery->polling_count == 1) + return false; + else + return true; +} +EXPORT_SYMBOL_KUNIT(sec_bat_is_short_polling); + +static void sec_bat_update_polling_count(struct sec_battery_info *battery) +{ + /* + * do NOT change polling count in sleep + * even though it is short polling + * to keep polling count along sleep/wakeup + */ + if (battery->polling_short && battery->polling_in_sleep) + return; + + if (battery->polling_short && + ((battery->polling_time / battery->pdata->polling_time[SEC_BATTERY_POLLING_TIME_BASIC]) > + battery->polling_count)) + battery->polling_count++; + else + battery->polling_count = 1; /* initial value = 1 */ +} + +__visible_for_testing int sec_bat_set_polling(struct sec_battery_info *battery) +{ + unsigned int polling_time_temp = 0; + + dev_dbg(battery->dev, "%s: Start\n", __func__); + + polling_time_temp = sec_bat_get_polling_time(battery); + + dev_dbg(battery->dev, "%s: Status:%s, Sleep:%s, Charging:%s, Short Poll:%s\n", + __func__, sb_get_bst_str(battery->status), + battery->polling_in_sleep ? "Yes" : "No", + (battery->charging_mode == + SEC_BATTERY_CHARGING_NONE) ? "No" : "Yes", + battery->polling_short ? "Yes" : "No"); + dev_info(battery->dev, "%s: Polling time %d/%d sec.\n", __func__, + battery->polling_short ? + (polling_time_temp * battery->polling_count) : + polling_time_temp, battery->polling_time); + + /* + * To sync with log above, + * change polling count after log is displayed + * Do NOT update polling count in initial monitor + */ + if (!battery->pdata->monitor_initial_count) + sec_bat_update_polling_count(battery); + else + dev_dbg(battery->dev, + "%s: Initial monitor %d times left.\n", __func__, + battery->pdata->monitor_initial_count); + + switch (battery->pdata->polling_type) { + case SEC_BATTERY_MONITOR_WORKQUEUE: + if (battery->pdata->monitor_initial_count) { + battery->pdata->monitor_initial_count--; + schedule_delayed_work(&battery->polling_work, HZ); + } else + schedule_delayed_work(&battery->polling_work, + polling_time_temp * HZ); + break; + case SEC_BATTERY_MONITOR_ALARM: + battery->last_poll_time = ktime_get_boottime(); + + if (battery->pdata->monitor_initial_count) { + battery->pdata->monitor_initial_count--; + sec_bat_program_alarm(battery, 1); + } else + sec_bat_program_alarm(battery, polling_time_temp); + break; + case SEC_BATTERY_MONITOR_TIMER: + break; + default: + break; + } + dev_dbg(battery->dev, "%s: End\n", __func__); + + return polling_time_temp; +} +EXPORT_SYMBOL_KUNIT(sec_bat_set_polling); + +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) +extern bool get_usb_enumeration_state(void); +#else +static bool get_usb_enumeration_state(void) +{ + return true; +} +#endif +/* To display slow charging when usb charging 100MA*/ +__visible_for_testing void sec_bat_check_slowcharging_work(struct work_struct *work) +{ + struct sec_battery_info *battery = container_of(work, + struct sec_battery_info, slowcharging_work.work); + + if (battery->sink_status.rp_currentlvl == RP_CURRENT_LEVEL_DEFAULT && + battery->cable_type == SEC_BATTERY_CABLE_USB) { + if (!get_usb_enumeration_state() && + (battery->current_event & SEC_BAT_CURRENT_EVENT_USB_100MA)) { + battery->usb_slow_chg = true; + battery->max_charge_power = mW_by_mVmA(battery->input_voltage, battery->current_max); + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + } + } + + pr_info("%s:\n", __func__); +} +EXPORT_SYMBOL_KUNIT(sec_bat_check_slowcharging_work); + +static int sec_bat_chk_siop_scenario_idx(struct sec_battery_info *battery, + int siop_level) +{ + int scenario_idx = -1; + int i = 0; + + if (battery->pdata->siop_scenarios_num > 0) { + for (i = 0; i < battery->pdata->siop_scenarios_num; ++i) + if (siop_level == battery->pdata->siop_table[i].level) { + scenario_idx = i; + pr_info("%s: siop level(%d), scenario_idx(%d)\n", + __func__, siop_level, scenario_idx); + break; + } + } + + return scenario_idx; +} + +static bool sec_bat_chk_siop_skip_scenario(struct sec_battery_info *battery, + int ct, int ws, int scenario_idx) +{ + struct sec_siop_table *siop_table; + int type = SIOP_CURR_TYPE_NV; + + if (scenario_idx < 0) + return false; + + if (is_hv_wireless_type(ct)) + type = SIOP_CURR_TYPE_WPC_HV; + else if (is_nv_wireless_type(ct)) + type = SIOP_CURR_TYPE_WPC_NV; + else if (is_hv_wire_type(ct) && is_hv_wire_type(ws)) + type = SIOP_CURR_TYPE_HV; + else if (is_pd_apdo_wire_type(ct) && battery->pd_list.now_isApdo) + type = SIOP_CURR_TYPE_APDO; + else if (is_pd_wire_type(ct)) + type = SIOP_CURR_TYPE_FPDO; + + if (type >= battery->pdata->siop_curr_type_num) + return false; + + siop_table = &battery->pdata->siop_table[scenario_idx]; + if ((siop_table->icl[type] == SIOP_SKIP) && (siop_table->fcc[type] == SIOP_SKIP)) { + pr_info("%s: scenario_idx(%d), type(%d), siop_icl(%d), siop_fcc(%d)\n", + __func__, scenario_idx, type, siop_table->icl[type], siop_table->fcc[type]); + return true; + } + + return false; +} + +static int sec_bat_get_siop_scenario_icl(struct sec_battery_info *battery, int icl, int scenario_idx, int type) +{ + int siop_icl = icl; + + if (scenario_idx < 0) + return icl; + + if (type >= SIOP_CURR_TYPE_MAX || type >= battery->pdata->siop_curr_type_num) + return icl; + + siop_icl = battery->pdata->siop_table[scenario_idx].icl[type]; + if (siop_icl == SIOP_DEFAULT || siop_icl > icl || siop_icl <= 0 || + siop_icl == SIOP_SKIP) + return icl; + pr_info("%s: scenario_idx(%d), type(%d), siop_icl(%d)\n", + __func__, scenario_idx, type, siop_icl); + + return siop_icl; +} + +static int sec_bat_get_siop_scenario_fcc(struct sec_battery_info *battery, int fcc, int scenario_idx, int type) +{ + int siop_fcc = fcc; + + if (scenario_idx < 0) + return fcc; + + if (type >= SIOP_CURR_TYPE_MAX || type >= battery->pdata->siop_curr_type_num) + return fcc; + + siop_fcc = battery->pdata->siop_table[scenario_idx].fcc[type]; + if (siop_fcc == SIOP_DEFAULT || siop_fcc > fcc || siop_fcc <= 0 || + siop_fcc == SIOP_SKIP) + return fcc; + pr_info("%s: scenario_idx(%d), type(%d), siop_fcc(%d)\n", + __func__, scenario_idx, type, siop_fcc); + + return siop_fcc; +} + +static void sec_bat_siop_level_work(struct work_struct *work) +{ + struct sec_battery_info *battery = container_of(work, + struct sec_battery_info, siop_level_work.work); + sec_battery_platform_data_t *pdata = battery->pdata; + + int icl = INT_MAX; /* Input Current Limit */ + int fcc = INT_MAX; /* Fast Charging Current */ + int scenario_idx = -1; + int skip_vote = 0; + + enum { + SIOP_STEP1, /* siop level 70 */ + SIOP_STEP2, /* siop level 10 */ + SIOP_STEP3, /* siop level 0 */ + }; + int siop_step = (battery->siop_level == 0) ? + SIOP_STEP3 : ((battery->siop_level == 10) ? SIOP_STEP2 : SIOP_STEP1); + + pr_info("%s : set current by siop level(%d), siop_step(%d)\n", __func__, battery->siop_level, siop_step + 1); + + scenario_idx = sec_bat_chk_siop_scenario_idx(battery, battery->siop_level); + + if (sec_bat_chk_siop_skip_scenario(battery, + battery->cable_type, battery->wire_status, scenario_idx) || + (battery->siop_level >= 80)) { +#if defined(CONFIG_SUPPORT_HV_CTRL) + sec_vote(battery->iv_vote, VOTER_SIOP, false, 0); +#endif + sec_vote(battery->fcc_vote, VOTER_SIOP, false, 0); + sec_vote(battery->input_vote, VOTER_SIOP, false, 0); + + __pm_relax(battery->siop_level_ws); + return; + } + + if (is_hv_wireless_type(battery->cable_type)) { + icl = pdata->siop_hv_wpc_icl; + fcc = pdata->siop_hv_wpc_fcc[siop_step]; + if (battery->pdata->siop_scenarios_num > 0) { + icl = sec_bat_get_siop_scenario_icl(battery, icl, scenario_idx, SIOP_CURR_TYPE_WPC_HV); + fcc = sec_bat_get_siop_scenario_fcc(battery, fcc, scenario_idx, SIOP_CURR_TYPE_WPC_HV); + } + } else if (is_nv_wireless_type(battery->cable_type)) { + icl = pdata->siop_wpc_icl; + fcc = pdata->siop_wpc_fcc[siop_step]; + if (battery->pdata->siop_scenarios_num > 0) { + icl = sec_bat_get_siop_scenario_icl(battery, icl, scenario_idx, SIOP_CURR_TYPE_WPC_NV); + fcc = sec_bat_get_siop_scenario_fcc(battery, fcc, scenario_idx, SIOP_CURR_TYPE_WPC_NV); + } + } else if (is_hv_wire_12v_type(battery->cable_type) && is_hv_wire_type(battery->wire_status)) { + icl = pdata->siop_hv_12v_icl; + fcc = pdata->siop_hv_12v_fcc; + if (battery->pdata->siop_scenarios_num > 0) { + icl = sec_bat_get_siop_scenario_icl(battery, icl, scenario_idx, SIOP_CURR_TYPE_HV); + fcc = sec_bat_get_siop_scenario_fcc(battery, fcc, scenario_idx, SIOP_CURR_TYPE_HV); + } + } else if (is_hv_wire_type(battery->cable_type) && is_hv_wire_type(battery->wire_status)) { +#if defined(CONFIG_SUPPORT_HV_CTRL) && !defined(CONFIG_SEC_FACTORY) + if (sec_bat_change_vbus(battery, battery->cable_type, battery->current_event, battery->siop_level)) { + if (battery->misc_event & BATT_MISC_EVENT_HV_BY_AICL) { + icl = pdata->siop_hv_icl; + fcc = pdata->siop_hv_fcc; + } else { + icl = pdata->siop_icl; + fcc = pdata->siop_fcc; + } + } else { + skip_vote = 1; + } +#else + icl = (siop_step == SIOP_STEP3) ? pdata->siop_hv_icl_2nd : pdata->siop_hv_icl; + fcc = pdata->siop_hv_fcc; +#endif + if (battery->pdata->siop_scenarios_num > 0) { + icl = sec_bat_get_siop_scenario_icl(battery, icl, scenario_idx, SIOP_CURR_TYPE_HV); + fcc = sec_bat_get_siop_scenario_fcc(battery, fcc, scenario_idx, SIOP_CURR_TYPE_HV); + } + } else if (is_pd_apdo_wire_type(battery->cable_type) && battery->pd_list.now_isApdo) { + icl = pdata->siop_apdo_icl; + fcc = pdata->siop_apdo_fcc; + if (battery->pdata->siop_scenarios_num > 0) { + icl = sec_bat_get_siop_scenario_icl(battery, icl, scenario_idx, SIOP_CURR_TYPE_APDO); + fcc = sec_bat_get_siop_scenario_fcc(battery, fcc, scenario_idx, SIOP_CURR_TYPE_APDO); + } + } else if (is_pd_wire_type(battery->cable_type)) { +#if defined(CONFIG_SUPPORT_HV_CTRL) && !defined(CONFIG_SEC_FACTORY) + if (sec_bat_change_vbus(battery, battery->cable_type, battery->current_event, battery->siop_level)) { + if (battery->misc_event & BATT_MISC_EVENT_HV_BY_AICL) { + icl = mA_by_mWmV(pdata->power_value, SEC_INPUT_VOLTAGE_9V); + fcc = pdata->siop_hv_fcc; + } else { + icl = mA_by_mWmV(pdata->power_value, SEC_INPUT_VOLTAGE_5V); + fcc = pdata->siop_fcc; + } + } else { + skip_vote = 1; + } +#else + icl = mA_by_mWmV(pdata->power_value, battery->input_voltage); + fcc = pdata->siop_fcc; +#endif + if (battery->pdata->siop_scenarios_num > 0) { + icl = sec_bat_get_siop_scenario_icl(battery, icl, scenario_idx, SIOP_CURR_TYPE_FPDO); + fcc = sec_bat_get_siop_scenario_fcc(battery, fcc, scenario_idx, SIOP_CURR_TYPE_FPDO); + } + } else { +#if defined(CONFIG_SUPPORT_HV_CTRL) + if (battery->wire_status != SEC_BATTERY_CABLE_HV_TA_CHG_LIMIT) + sec_vote(battery->iv_vote, VOTER_SIOP, false, 0); +#endif + icl = pdata->siop_icl; + fcc = pdata->siop_fcc; + if (battery->pdata->siop_scenarios_num > 0) { + icl = sec_bat_get_siop_scenario_icl(battery, icl, scenario_idx, SIOP_CURR_TYPE_NV); + fcc = sec_bat_get_siop_scenario_fcc(battery, fcc, scenario_idx, SIOP_CURR_TYPE_NV); + } + } + + if (!skip_vote) { + pr_info("%s: icl(%d), fcc(%d)\n", __func__, icl, fcc); + sec_vote(battery->input_vote, VOTER_SIOP, true, icl); + sec_vote(battery->fcc_vote, VOTER_SIOP, true, fcc); + } + + __pm_relax(battery->siop_level_ws); +} + +#if defined(CONFIG_WIRELESS_FIRMWARE_UPDATE) +#define MFC_FW_UPDATE_MIN_CAP 20 +__visible_for_testing void sec_bat_fw_init_work(struct work_struct *work) +{ + struct sec_battery_info *battery = container_of(work, + struct sec_battery_info, fw_init_work.work); + +#if defined(CONFIG_WIRELESS_IC_PARAM) + union power_supply_propval value = {0, }; + + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_WIRELESS_CHECK_FW_VER, value); + if (value.intval) { + pr_info("%s: wireless firmware is already updated.\n", __func__); + return; + } +#endif + + /* auto mfc firmware update when only no otg, mst, wpc, jig */ + if (sec_bat_check_boost_mfc_condition(battery, SEC_WIRELESS_FW_UPDATE_AUTO_MODE) && + (battery->capacity >= MFC_FW_UPDATE_MIN_CAP) && !sec_bat_get_lpmode() && !battery->is_jig_on) { + sec_bat_fw_update(battery, SEC_WIRELESS_FW_UPDATE_AUTO_MODE); + } +} +EXPORT_SYMBOL_KUNIT(sec_bat_fw_init_work); +#endif + +#if defined(CONFIG_UPDATE_BATTERY_DATA) +static void sec_bat_update_data_work(struct work_struct *work) +{ + struct sec_battery_info *battery = container_of(work, + struct sec_battery_info, batt_data_work.work); + + sec_battery_update_data(battery->data_path); + __pm_relax(battery->batt_data_ws); +} +#endif + +static void sec_bat_misc_event_work(struct work_struct *work) +{ + struct sec_battery_info *battery = container_of(work, + struct sec_battery_info, misc_event_work.work); + int xor_misc_event = battery->prev_misc_event ^ battery->misc_event; + + if (xor_misc_event & BATT_MISC_EVENT_MUIC_ABNORMAL) { + if (battery->misc_event & BATT_MISC_EVENT_MUIC_ABNORMAL) + sec_vote(battery->chgen_vote, VOTER_MUIC_ABNORMAL, true, SEC_BAT_CHG_MODE_BUCK_OFF); + else if (battery->prev_misc_event & BATT_MISC_EVENT_MUIC_ABNORMAL) + sec_vote(battery->chgen_vote, VOTER_MUIC_ABNORMAL, false, 0); + } + + pr_info("%s: change misc event(0x%x --> 0x%x)\n", + __func__, battery->prev_misc_event, battery->misc_event); + battery->prev_misc_event = battery->misc_event; + __pm_relax(battery->misc_event_ws); + + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); +} + +static void sec_bat_calculate_safety_time(struct sec_battery_info *battery) +{ + unsigned long long expired_time = battery->expired_time; + struct timespec64 ts = {0, }; + int curr = 0; + int input_power = 0; + int charging_power = mW_by_mVmA(battery->charging_current, + (battery->pdata->chg_float_voltage / battery->pdata->chg_float_voltage_conv)); + static int discharging_cnt = 0; +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + int direct_chg_done = 0; + union power_supply_propval value = {0,}; +#endif + +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_DIRECT_DONE, value); + direct_chg_done = value.intval; + + if (is_pd_apdo_wire_type(battery->cable_type) && + !battery->chg_limit && !battery->lrp_limit && + (battery->pd_list.now_isApdo || direct_chg_done)) + input_power = battery->pd_max_charge_power; + else + input_power = mW_by_mVmA(battery->input_voltage, battery->current_max); +#else + input_power = mW_by_mVmA(battery->input_voltage, battery->current_max); +#endif + + if (battery->current_avg < 0) { + discharging_cnt++; + } else { + discharging_cnt = 0; + } + + if (discharging_cnt >= 5) { + battery->expired_time = battery->pdata->expired_time; + battery->prev_safety_time = 0; + pr_info("%s : SAFETY TIME RESET! DISCHARGING CNT(%d)\n", + __func__, discharging_cnt); + discharging_cnt = 0; + return; + } else if ((battery->lcd_status || battery->wc_tx_enable) && battery->stop_timer) { + battery->prev_safety_time = 0; + return; + } + + ts = ktime_to_timespec64(ktime_get_boottime()); + + if (battery->prev_safety_time == 0) { + battery->prev_safety_time = ts.tv_sec; + } + + if (input_power > charging_power) { + curr = battery->charging_current; + } else { + curr = mA_by_mWmV(input_power, (battery->pdata->chg_float_voltage / battery->pdata->chg_float_voltage_conv)); + curr = (curr * 9) / 10; + } + + if ((battery->lcd_status || battery->wc_tx_enable) && !battery->stop_timer) { + battery->stop_timer = true; + } else if (!(battery->lcd_status || battery->wc_tx_enable) && battery->stop_timer) { + battery->stop_timer = false; + } + + pr_info("%s : EXPIRED_TIME(%llu), IP(%d), CP(%d), CURR(%d), STANDARD(%d)\n", + __func__, expired_time, input_power, charging_power, curr, battery->pdata->standard_curr); + + if (curr == 0) + return; + else if (curr > battery->pdata->standard_curr) + curr = battery->pdata->standard_curr; + + expired_time = (expired_time * battery->pdata->standard_curr) / curr; + + pr_info("%s : CAL_EXPIRED_TIME(%llu) TIME NOW(%llu) TIME PREV(%ld)\n", __func__, expired_time, ts.tv_sec, battery->prev_safety_time); + + if (expired_time <= ((ts.tv_sec - battery->prev_safety_time) * 1000)) + expired_time = 0; + else + expired_time -= ((ts.tv_sec - battery->prev_safety_time) * 1000); + + battery->cal_safety_time = expired_time; + expired_time = (expired_time * curr) / battery->pdata->standard_curr; + + battery->expired_time = expired_time; + battery->prev_safety_time = ts.tv_sec; + pr_info("%s : REMAIN_TIME(%ld) CAL_REMAIN_TIME(%ld)\n", __func__, battery->expired_time, battery->cal_safety_time); +} + +static bool sb_check_skip_monitor(struct sec_battery_info *battery) +{ + static struct timespec64 old_ts = {0, }; + struct timespec64 c_ts = {0, }; + union power_supply_propval val = {0, }; + + c_ts = ktime_to_timespec64(ktime_get_boottime()); + + /* monitor once after wakeup */ + if (battery->polling_in_sleep) { + battery->polling_in_sleep = false; + if (battery->status == POWER_SUPPLY_STATUS_DISCHARGING && + !battery->wc_tx_enable && + (battery->d2d_auth != D2D_AUTH_SRC) && + ((unsigned long)(c_ts.tv_sec - old_ts.tv_sec) < 10 * 60) + ) { + val.intval = SEC_BATTERY_VOLTAGE_MV; + psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_PROP_VOLTAGE_NOW, val); + battery->voltage_now = val.intval; + + val.intval = 0; + psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_PROP_CAPACITY, val); + battery->capacity = val.intval; + + sec_bat_get_temperature_info(battery); + sec_bat_cisd_check(battery); + power_supply_changed(battery->psy_bat); + pr_info("Skip monitor work(%llu, Vnow:%d(mV), SoC:%d(%%), Tbat:%d(0.1'C))\n", + c_ts.tv_sec - old_ts.tv_sec, battery->voltage_now, + battery->capacity, battery->temperature); + return true; + } + } + /* update last monitor time */ + old_ts = c_ts; + return false; +} + +static void sec_bat_check_store_mode(struct sec_battery_info *battery) +{ + + if (sec_bat_get_facmode()) + return; +#if defined(CONFIG_SEC_FACTORY) + if (!is_nocharge_type(battery->cable_type)) { +#else + if (!is_nocharge_type(battery->cable_type) && battery->store_mode) { +#endif + pr_info("%s: capacity(%d), status(%d), store_mode(%d)\n", + __func__, battery->capacity, battery->status, battery->store_mode); +#if IS_ENABLED(CONFIG_USB_FACTORY_MODE) + pr_info("%s: @battery->batt_f_mode: %s\n", __func__, + BOOT_MODE_STRING[battery->batt_f_mode]); +#endif + + /* + * VOTER_STORE_MODE + * Set limited max power when store mode is set and LDU + * Limited max power should be set with over 5% capacity + * since target could be turned off during boot up + * display test requirement : do not decrease fcc in store mode condition + */ + if (!battery->display_test && battery->store_mode && battery->capacity >= 5) { + sec_vote(battery->input_vote, VOTER_STORE_MODE, true, + mA_by_mWmV(battery->pdata->store_mode_max_input_power, battery->input_voltage)); + } + + if (battery->capacity >= battery->pdata->store_mode_charging_max) { + int chg_mode = SEC_BAT_CHG_MODE_CHARGING_OFF; + /* to discharge the battery, off buck */ + if (battery->capacity > battery->pdata->store_mode_charging_max + || battery->pdata->store_mode_buckoff) + chg_mode = SEC_BAT_CHG_MODE_BUCK_OFF; + +#if IS_ENABLED(CONFIG_USB_FACTORY_MODE) + if ((sec_bat_get_facmode() || battery->batt_f_mode != NO_MODE) && + chg_mode == SEC_BAT_CHG_MODE_BUCK_OFF) +#else + if (sec_bat_get_facmode() && + chg_mode == SEC_BAT_CHG_MODE_BUCK_OFF) +#endif + chg_mode = SEC_BAT_CHG_MODE_CHARGING_OFF; + + sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_DISCHARGING); + sec_vote(battery->chgen_vote, VOTER_STORE_MODE, true, chg_mode); + } + + if (battery->capacity <= battery->pdata->store_mode_charging_min && + battery->status == POWER_SUPPLY_STATUS_DISCHARGING) { + sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_CHARGING); + sec_vote(battery->chgen_vote, VOTER_STORE_MODE, false, 0); + } + } +} + +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) +static void sec_bat_check_boottime(void *data, + bool dctp_en) +{ + struct sec_battery_info *battery = data; + + if (!dctp_en) + return; + + if (battery->capacity > 10) { + pr_info("%s: %d\n", __func__, battery->pdata->dctp_bootmode_en); + battery->pdata->dctp_bootmode_en = false; + } +} + +static void sec_bat_d2d_check(struct sec_battery_info *battery, + unsigned int capacity, int batt_t, int lrp_t) +{ + union power_supply_propval value = {0, }; + int auth = AUTH_LOW_PWR; + + if ((battery->pdata->d2d_check_type == SB_D2D_NONE) || + (battery->d2d_auth != D2D_AUTH_SRC) || + battery->vpdo_ocp) { + return; + } + + if (battery->pdata->d2d_check_type == SB_D2D_SRCSNK) { + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_D2D_REVERSE_VOLTAGE, value); + battery->vpdo_src_boost = value.intval ? true : false; + } + + if (battery->vpdo_src_boost) { + value.intval = SEC_BATTERY_VIN_MA; + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_D2D_REVERSE_OCP, value); + + battery->vpdo_ocp = value.intval ? true : false; + pr_info("%s : reverse ocp(%d)\n", __func__, value.intval); + if (battery->vpdo_ocp) + battery->hp_d2d = HP_D2D_OCP; + } + + if ((batt_t >= 500) || (batt_t <= 0)) { + battery->hp_d2d = HP_D2D_BATT_TMP; + } else if (lrp_t > 370) { + battery->hp_d2d = HP_D2D_LRP_TMP; + } else if (battery->vpdo_ocp) { + battery->hp_d2d = HP_D2D_OCP; + } else if (capacity < 30) { + battery->hp_d2d = HP_D2D_SOC; + } else { + if (battery->hp_d2d == HP_D2D_BATT_TMP) { + if ((batt_t <= 480) && (batt_t >= 20)) { + auth = AUTH_HIGH_PWR; + battery->hp_d2d = HP_D2D_ON; + } else + battery->vpdo_auth_stat = auth; + } else { + if (battery->lcd_status) { + battery->hp_d2d = HP_D2D_LCD; + } else { + auth = AUTH_HIGH_PWR; + battery->hp_d2d = HP_D2D_ON; + } + } + } + + pr_info("%s : auth %s, hp d2d (%d)\n", __func__, + (auth == AUTH_LOW_PWR) ? "LOW PWR" : "HIGH PWR", battery->hp_d2d); + + if (battery->vpdo_auth_stat != auth) { + sec_pd_vpdo_auth(auth, battery->pdata->d2d_check_type); + + pr_info("%s : vpdo auth changed\n", __func__); + battery->vpdo_auth_stat = auth; + } +} + +static void sec_bat_check_direct_charger(struct sec_battery_info *battery) +{ + union power_supply_propval val = {0, }; + int ret = 0; + + if (battery->status != POWER_SUPPLY_STATUS_CHARGING || + battery->health != POWER_SUPPLY_HEALTH_GOOD) { + battery->dc_check_cnt = 0; + return; + } + + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_CHARGER_MODE_DIRECT, val); + if (val.intval != SEC_BAT_CHG_MODE_CHARGING) { + battery->dc_check_cnt = 0; + return; + } + + val.strval = "GETCHARGING"; + ret = psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_DIRECT_CHARGER_CHG_STATUS, val); + if (ret < 0) + return; + + if (strncmp(val.strval, "NO_CHARGING", 12) == 0) { + pr_info("%s: cnt(%d)\n", __func__, battery->dc_check_cnt); + if (++battery->dc_check_cnt < 3) + return; + sec_bat_set_current_event(battery, + SEC_BAT_CURRENT_EVENT_DC_ERR, SEC_BAT_CURRENT_EVENT_DC_ERR); + sec_vote(battery->chgen_vote, VOTER_DC_ERR, true, + SEC_BAT_CHG_MODE_CHARGING_OFF); + msleep(100); + sec_bat_set_current_event(battery, + 0, SEC_BAT_CURRENT_EVENT_DC_ERR); + sec_vote(battery->chgen_vote, VOTER_DC_ERR, false, 0); +#if IS_ENABLED(CONFIG_SEC_ABC) + sec_abc_send_event("MODULE=battery@WARN=dc_error"); +#endif + } + battery->dc_check_cnt = 0; +} +#endif + +static void sb_monitor_preliminary_checks(struct sec_battery_info *battery) +{ +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + if (battery->set_lower_curr) { + pr_info("%s: set lower limiter current\n", __func__); + battery->set_lower_curr = false; + sec_bat_set_limiter_current(battery); + } +#endif + sec_bat_get_battery_info(battery); +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + sec_bat_d2d_check(battery, battery->capacity, battery->temperature, battery->lrp); +#endif + sec_bat_cisd_check(battery); +#if defined(CONFIG_STEP_CHARGING) + sec_bat_check_step_charging(battery); +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + sec_bat_check_wpc_step_charging(battery); +#endif +#endif + /* time to full check */ + sec_bat_calc_time_to_full(battery); + sec_bat_check_full_capacity(battery); +#if defined(CONFIG_WIRELESS_TX_MODE) + /* tx mode check */ + sec_bat_check_tx_mode(battery); +#endif +} + +static bool sb_monitor_critical_checks(struct sec_battery_info *battery) +{ + bool checks_pass = + !sec_bat_do_test_function(battery) && /* 0. test mode */ + sec_bat_battery_cable_check(battery) && /* 1. battery check */ + sec_bat_voltage_check(battery); /* 2. voltage check */ + + return checks_pass; +} + +static void sb_monitor_nonurgent_checks(struct sec_battery_info *battery) +{ + /* 4. bat thm check */ + sec_bat_thermal_check(battery); + + /* 5. full charging check */ + if (!(battery->current_event & SEC_BAT_CURRENT_EVENT_HIGH_TEMP_SWELLING)) + sec_bat_fullcharged_check(battery); + + /* 5-1. eu eco check */ + if (check_eu_eco_full_status(battery)) + sec_bat_do_fullcharged(battery, true); + + /* 6. additional check */ + if (battery->pdata->monitor_additional_check) + battery->pdata->monitor_additional_check(); + +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + if (is_wireless_type(battery->cable_type) && !battery->wc_cv_mode && battery->charging_passed_time > 10) + sec_bat_wc_cv_mode_check(battery); +#endif + +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + sec_bat_check_boottime(battery, battery->pdata->dctp_bootmode_en); +#if defined(CONFIG_STEP_CHARGING) + if (is_pd_apdo_wire_type(battery->cable_type)) + sec_bat_check_dc_step_charging(battery); +#endif +#endif + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) && IS_ENABLED(CONFIG_LIMITER_S2ASL01) && !defined(CONFIG_SEC_FACTORY) + sec_bat_limiter_check(battery); +#endif +} + +static bool sb_monitor_checks(struct sec_battery_info *battery) +{ + bool update_battery_info = true; + + sb_monitor_preliminary_checks(battery); /* Preliminary Checks */ + if (sb_monitor_critical_checks(battery)) { /* Do Critical Checks*/ + /* CheckShort Polling & Monitor Count */ + if (battery->pdata->monitor_initial_count || sec_bat_is_short_polling(battery)) + update_battery_info = false; // Don't Update Battery Info + else if (sec_bat_time_management(battery)) /* 3. time management */ + sb_monitor_nonurgent_checks(battery); + } + return update_battery_info; +} + +static void sb_monitor_update(struct sec_battery_info *battery, bool update_battery_info) +{ + union power_supply_propval val = {0, }; + + // Update Battery Info + if (update_battery_info) { + /* clear HEATING_CONTROL*/ + sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_SKIP_HEATING_CONTROL); + + /* calculate safety time */ + if (battery->charger_mode == SEC_BAT_CHG_MODE_CHARGING) + sec_bat_calculate_safety_time(battery); + + /* set charging current */ + sec_bat_set_charging_current(battery); + + if (sec_bat_recheck_input_work(battery)) + sec_bat_run_input_check_work(battery, 0); + } + + // Update Charger + psy_do_property(battery->pdata->charger_name, get, POWER_SUPPLY_EXT_PROP_MONITOR_WORK, val); +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + if (is_pd_apdo_wire_type(battery->cable_type) && (val.intval == LOW_VBAT_SET)) + sec_vote_refresh(battery->fcc_vote); + if (is_pd_apdo_wire_type(battery->cable_type)) + sec_bat_check_direct_charger(battery); +#endif + + // Update Fuelgauge + psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_EXT_PROP_MONITOR_WORK, val); + if (battery->pdata->wireless_charger_name) + psy_do_property(battery->pdata->wireless_charger_name, get, POWER_SUPPLY_EXT_PROP_MONITOR_WORK, val); +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + psy_do_property(battery->pdata->dual_battery_name, get, POWER_SUPPLY_EXT_PROP_MONITOR_WORK, val); +#endif +} + +static void sb_monitor_internal(struct sec_battery_info *battery) +{ + bool update_battery_info = true; + bool bypass_checks = false; + +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + sec_bat_check_wc_available(battery); + if (is_wireless_type(battery->cable_type) && !battery->wc_auth_retried && !sec_bat_get_lpmode()) + sec_bat_check_wc_re_auth(battery); +#endif + +#if IS_ENABLED(CONFIG_USB_FACTORY_MODE) + if ((battery->cable_type != SEC_BATTERY_CABLE_NONE) && (battery->batt_f_mode == OB_MODE)) { + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_DISCHARGING); + battery->cable_type = SEC_BATTERY_CABLE_NONE; + bypass_checks = true; // Skip Checks + } +#endif + if (!bypass_checks) + update_battery_info = sb_monitor_checks(battery); + + sb_monitor_update(battery, update_battery_info); +} + +static void sb_monitor_print(struct sec_battery_info *battery, const char *funcname) +{ + char str[512] = {0, }; + + sprintf(str, "%s: Status(%s), mode(%s), Health(%s), Cable(%s, %s, %d, %d), rp(%d), HV(%s), flash(%d), mst(%d)", + funcname, + sb_get_bst_str(battery->status), + sb_get_cm_str(battery->charging_mode), + sb_get_hl_str(battery->health), + sb_get_ct_str(battery->cable_type), + sb_get_ct_str(battery->wire_status), + battery->muic_cable_type, + battery->pd_usb_attached, + battery->sink_status.rp_currentlvl, + battery->hv_chg_name, + battery->flash_state, + battery->mst_en + ); + +#if defined(CONFIG_WIRELESS_RX_PHM_CTRL) + sprintf(str + strlen(str), ", phm(%d)", + battery->wc_rx_pdetb_mode + ); +#endif + + sprintf(str + strlen(str), ", lcd(%d), slate(%d), store(%d), siop_level(%d), sleep_mode(%d)", + battery->lcd_status, + is_slate_mode(battery), + battery->store_mode, + battery->siop_level, + battery->sleep_mode + ); + +#if defined(CONFIG_BATTERY_AGE_FORECAST_DETACHABLE) + sprintf(str + strlen(str), ", Cycle(%dw)", + battery->batt_cycle + ); +#else + sprintf(str + strlen(str), ", Cycle(%d, %d)", + battery->batt_cycle, battery->batt_full_status_usage + ); +#endif + +#if defined(CONFIG_ENG_BATTERY_CONCEPT) + sprintf(str + strlen(str), ", battery->stability_test(%d), battery->eng_not_full_status(%d)", + battery->stability_test, + battery->eng_not_full_status); +#endif + + sprintf(str + strlen(str), "\n"); + pr_info("%s", str); + +#if defined(CONFIG_WIRELESS_TX_MODE) + if (battery->wc_tx_enable) { + pr_info("@Tx_Mode %s: Rx(%s), WC_TX_VOUT(%dmV), UNO_IOUT(%d), MFC_IOUT(%d) AFC_DISABLE(%d)\n", + funcname, sb_rx_type_str(battery->wc_rx_type), battery->wc_tx_vout, + battery->tx_uno_iout, battery->tx_mfc_iout, battery->afc_disable); + } +#endif +} + +static void sec_bat_monitor_work(struct work_struct *work) +{ + struct sec_battery_info *battery = container_of(work, struct sec_battery_info, monitor_work.work); + + dev_dbg(battery->dev, "%s: Start\n", __func__); + + if (!sb_check_skip_monitor(battery)) { + sb_monitor_internal(battery); // Internal Function Which Performs the Checks + sb_monitor_print(battery, __func__); // Print Monitor + + /* store mode & fac bin */ + sec_bat_check_store_mode(battery); + power_supply_changed(battery->psy_bat); + } + + sec_bat_set_polling(battery); + +#if defined(CONFIG_WIRELESS_TX_MODE) + if (battery->tx_switch_mode_change) + sec_bat_run_wpc_tx_work(battery, 0); +#endif + + if (battery->capacity <= 0 || battery->health_change) + __pm_wakeup_event(battery->monitor_ws, jiffies_to_msecs(HZ * 5)); + else + __pm_relax(battery->monitor_ws); + + dev_dbg(battery->dev, "%s: End\n", __func__); +} + +static enum alarmtimer_restart sec_bat_alarm(struct alarm *alarm, ktime_t now) +{ + struct sec_battery_info *battery = container_of(alarm, + struct sec_battery_info, polling_alarm); + + dev_dbg(battery->dev, "%s\n", __func__); + + /* + * In wake up, monitor work will be queued in complete function + * To avoid duplicated queuing of monitor work, + * do NOT queue monitor work in wake up by polling alarm + */ + if (!battery->polling_in_sleep) { + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + dev_dbg(battery->dev, "%s: Activated\n", __func__); + } + + return ALARMTIMER_NORESTART; +} + +__visible_for_testing void sec_bat_check_input_voltage(struct sec_battery_info *battery, int cable_type) +{ + unsigned int voltage = 0; + int input_current = battery->pdata->charging_current[cable_type].input_current_limit; + +#if !defined(CONFIG_NO_BATTERY) + if (battery->status == POWER_SUPPLY_STATUS_DISCHARGING) + return; +#endif + + if (is_pd_wire_type(cable_type)) { + battery->max_charge_power = battery->pd_max_charge_power; + return; + } else if (is_hv_wire_12v_type(cable_type)) + voltage = SEC_INPUT_VOLTAGE_12V; + else if (is_hv_wire_9v_type(cable_type)) + voltage = SEC_INPUT_VOLTAGE_9V; +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + else if (cable_type == SEC_BATTERY_CABLE_PREPARE_WIRELESS_20 || is_pwr_nego_wireless_type(cable_type)) + voltage = battery->wc20_vout; + else if (is_hv_wireless_type(cable_type) || cable_type == SEC_BATTERY_CABLE_PREPARE_WIRELESS_HV) + voltage = SEC_INPUT_VOLTAGE_10V; + else if (is_nv_wireless_type(cable_type)) + voltage = SEC_INPUT_VOLTAGE_5_5V; +#endif + else + voltage = SEC_INPUT_VOLTAGE_5V; + + battery->input_voltage = voltage; + battery->charge_power = mW_by_mVmA(voltage, input_current); +#if !defined(CONFIG_SEC_FACTORY) + if (battery->charge_power > battery->max_charge_power) +#endif + battery->max_charge_power = battery->charge_power; + + pr_info("%s: input_voltage:%dmV, charge_power:%dmW, max_charge_power:%dmW)\n", __func__, + battery->input_voltage, battery->charge_power, battery->max_charge_power); +} +EXPORT_SYMBOL_KUNIT(sec_bat_check_input_voltage); + +static void sec_bat_set_usb_configure(struct sec_battery_info *battery, int usb_status) +{ + int cable_work_delay = 0; + + pr_info("%s: usb configured %d -> %d\n", __func__, battery->prev_usb_conf, usb_status); + + if (usb_status == USB_CURRENT_UNCONFIGURED) { + sec_bat_set_current_event(battery, + SEC_BAT_CURRENT_EVENT_USB_100MA, SEC_BAT_CURRENT_EVENT_USB_STATE); + if (battery->cable_type == SEC_BATTERY_CABLE_USB && !sec_bat_get_lpmode()) { + sec_vote(battery->fcc_vote, VOTER_USB_100MA, true, 100); + sec_vote(battery->input_vote, VOTER_USB_100MA, true, 100); + } + } else if (usb_status == USB_CURRENT_HIGH_SPEED || usb_status == USB_CURRENT_SUPER_SPEED) { + battery->usb_slow_chg = false; + sec_vote(battery->fcc_vote, VOTER_USB_100MA, false, 0); + sec_vote(battery->input_vote, VOTER_USB_100MA, false, 0); + if (usb_status == USB_CURRENT_HIGH_SPEED) { + sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_USB_STATE); + if (battery->cable_type == SEC_BATTERY_CABLE_USB) { + sec_vote(battery->fcc_vote, VOTER_CABLE, true, + battery->pdata->default_usb_charging_current); + sec_vote(battery->input_vote, VOTER_CABLE, true, + battery->pdata->default_usb_input_current); + } + } else { + sec_bat_set_current_event(battery, + SEC_BAT_CURRENT_EVENT_USB_SUPER, SEC_BAT_CURRENT_EVENT_USB_STATE); + if (battery->cable_type == SEC_BATTERY_CABLE_USB) { + sec_vote(battery->fcc_vote, VOTER_CABLE, true, USB_CURRENT_SUPER_SPEED); + sec_vote(battery->input_vote, VOTER_CABLE, true, USB_CURRENT_SUPER_SPEED); + } + } + if (battery->sink_status.rp_currentlvl == RP_CURRENT_LEVEL3) { + if (battery->current_event & SEC_BAT_CURRENT_EVENT_HV_DISABLE) { + sec_vote(battery->fcc_vote, VOTER_CABLE, true, + battery->pdata->default_charging_current); + sec_vote(battery->input_vote, VOTER_CABLE, true, + battery->pdata->default_input_current); + } else { + if (battery->store_mode) { + sec_vote(battery->fcc_vote, VOTER_CABLE, true, + battery->pdata->max_charging_current); + sec_vote(battery->input_vote, VOTER_CABLE, true, + battery->pdata->rp_current_rdu_rp3); + } else { + if (!(is_pd_wire_type(battery->wire_status) || + is_pd_wire_type(battery->cable_type))) { + sec_vote(battery->fcc_vote, VOTER_CABLE, true, + battery->pdata->max_charging_current); + sec_vote(battery->input_vote, VOTER_CABLE, true, + battery->pdata->rp_current_rp3); + } + } + } + } else if (battery->sink_status.rp_currentlvl == RP_CURRENT_LEVEL2) { + if (!(is_pd_wire_type(battery->wire_status) || + is_pd_wire_type(battery->cable_type))) { + sec_vote(battery->fcc_vote, VOTER_CABLE, true, battery->pdata->rp_current_rp2); + sec_vote(battery->input_vote, VOTER_CABLE, true, battery->pdata->rp_current_rp2); + } + } + } else if (usb_status == USB_CURRENT_SUSPENDED) { + battery->usb_slow_chg = false; + sec_bat_set_current_event(battery, + SEC_BAT_CURRENT_EVENT_USB_SUSPENDED, SEC_BAT_CURRENT_EVENT_USB_STATE); + sec_vote(battery->chgen_vote, VOTER_SUSPEND, true, SEC_BAT_CHG_MODE_BUCK_OFF); + if (battery->sink_status.rp_currentlvl == RP_CURRENT_LEVEL_DEFAULT) { + sec_vote(battery->fcc_vote, VOTER_USB_100MA, true, 100); + sec_vote(battery->input_vote, VOTER_USB_100MA, true, 100); + } + cable_work_delay = 500; + store_battery_log( + "USB suspend:%d%%,%dmV,%s,ct(%d,%d,%d),cev(0x%x),mev(0x%x)", + battery->capacity, + battery->voltage_now, + sb_get_bst_str(battery->status), + battery->cable_type, + battery->wire_status, + battery->muic_cable_type, + battery->current_event, + battery->misc_event + ); + } else if (usb_status == USB_CURRENT_CLEAR) { + sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_USB_STATE); + sec_vote(battery->chgen_vote, VOTER_SUSPEND, false, 0); + sec_vote(battery->fcc_vote, VOTER_USB_100MA, false, 0); + sec_vote(battery->input_vote, VOTER_USB_100MA, false, 0); + } + + if (usb_status != USB_CURRENT_SUSPENDED) + sec_vote(battery->chgen_vote, VOTER_SUSPEND, false, 0); + + battery->prev_usb_conf = usb_status; + + cancel_delayed_work(&battery->cable_work); + __pm_stay_awake(battery->cable_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->cable_work, + msecs_to_jiffies(cable_work_delay)); +} + +#define REDUCE_STEP 500 +#define MIN_FCC_VALUE 3500 +static void sec_bat_set_abnormal_ta_fcc(struct sec_battery_info *battery, bool enable) +{ + union power_supply_propval value = {0, }; + int fcc; + + if (!enable) { + battery->abnormal_ta = false; + pr_info("%s: enable(%d)", __func__, enable); + sec_vote(battery->fcc_vote, VOTER_ABNORMAL_TA, false, 0); + return; + } + + fcc = get_sec_vote_result(battery->fcc_vote) - REDUCE_STEP; + if (fcc > MIN_FCC_VALUE) { + pr_info("%s: reduce chg current(%d)", __func__, fcc); + sec_vote(battery->fcc_vote, VOTER_ABNORMAL_TA, true, fcc); + } else { + battery->abnormal_ta = true; + pr_info("%s: update charging source(%d)", __func__, enable); + sec_vote(battery->fcc_vote, VOTER_ABNORMAL_TA, true, MIN_FCC_VALUE); + if (is_pd_apdo_wire_type(battery->cable_type)) + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_REFRESH_CHARGING_SOURCE, value); + } +} + +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) +__visible_for_testing void sec_bat_ext_event_work(struct work_struct *work) +{ + struct sec_battery_info *battery = container_of(work, + struct sec_battery_info, ext_event_work.work); + + sec_bat_ext_event_work_content(battery); +} +EXPORT_SYMBOL_KUNIT(sec_bat_ext_event_work); + +static void sec_bat_wpc_tx_work(struct work_struct *work) +{ + struct sec_battery_info *battery = container_of(work, + struct sec_battery_info, wpc_tx_work.work); + + sec_bat_wpc_tx_work_content(battery); +} + +__visible_for_testing void sec_bat_wpc_tx_en_work(struct work_struct *work) +{ + struct sec_battery_info *battery = container_of(work, + struct sec_battery_info, wpc_tx_en_work.work); + + sec_bat_wpc_tx_en_work_content(battery); +} +EXPORT_SYMBOL_KUNIT(sec_bat_wpc_tx_en_work); + +__visible_for_testing void sec_bat_txpower_calc_work(struct work_struct *work) +{ + struct sec_battery_info *battery = container_of(work, + struct sec_battery_info, wpc_txpower_calc_work.work); + + sec_bat_txpower_calc(battery); +} +EXPORT_SYMBOL_KUNIT(sec_bat_txpower_calc_work); + +static void sec_bat_wc20_current_work(struct work_struct *work) +{ + struct sec_battery_info *battery = container_of(work, + struct sec_battery_info, wc20_current_work.work); + + sec_bat_set_wc20_current(battery); + __pm_relax(battery->wc20_current_ws); +} + +static void sec_bat_wc_ept_timeout_work(struct work_struct *work) +{ + struct sec_battery_info *battery = container_of(work, + struct sec_battery_info, wc_ept_timeout_work.work); + + battery->wc_ept_timeout = false; + __pm_relax(battery->wc_ept_timeout_ws); +} +#endif + +static void cw_check_pd_wire_type(struct sec_battery_info *battery) +{ + if (is_pd_wire_type(battery->wire_status)) { + int pdo_num = battery->sink_status.current_pdo_num; + + sec_bat_get_input_current_in_power_list(battery); + sec_bat_get_charging_current_in_power_list(battery); + +#if defined(CONFIG_STEP_CHARGING) +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + if (!is_pd_apdo_wire_type(battery->cable_type)) { + sec_bat_reset_step_charging(battery); + } else if (is_pd_apdo_wire_type(battery->cable_type) && (battery->ta_alert_mode != OCP_NONE)) { + battery->ta_alert_mode = OCP_WA_ACTIVE; + sec_bat_reset_step_charging(battery); + } +#else + sec_bat_reset_step_charging(battery); +#endif +#endif + if (!battery->sink_status.power_list[pdo_num].comm_capable + || !battery->sink_status.power_list[pdo_num].suspend) { + pr_info("%s : clear suspend event pdo_num:%d, comm:%d, suspend:%d\n", __func__, + pdo_num, + battery->sink_status.power_list[pdo_num].comm_capable, + battery->sink_status.power_list[pdo_num].suspend); + sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_USB_SUSPENDED); + sec_vote(battery->chgen_vote, VOTER_SUSPEND, false, 0); + sec_vote(battery->fcc_vote, VOTER_USB_100MA, false, 0); + sec_vote(battery->input_vote, VOTER_USB_100MA, false, 0); + } + } + +} + +static bool cw_check_skip_condition(struct sec_battery_info *battery, int current_cable_type) +{ + static bool first_run = true; + + if (first_run) { + first_run = false; + + if (current_cable_type == SEC_BATTERY_CABLE_NONE) { + dev_info(battery->dev, "%s: do not skip! first cable work\n", __func__); + + return false; + } + } + + if ((current_cable_type == battery->cable_type) && !is_slate_mode(battery) + && !(battery->current_event & SEC_BAT_CURRENT_EVENT_USB_SUSPENDED)) { + if (is_pd_wire_type(current_cable_type) && is_pd_wire_type(battery->cable_type)) { + sec_bat_set_current_event(battery, 0, + SEC_BAT_CURRENT_EVENT_AFC | SEC_BAT_CURRENT_EVENT_AICL); + sec_vote(battery->input_vote, VOTER_AICL, false, 0); + sec_vote(battery->input_vote, VOTER_VBUS_CHANGE, false, 0); + power_supply_changed(battery->psy_bat); + } else if (battery->prev_usb_conf != USB_CURRENT_NONE) { + dev_info(battery->dev, "%s: set usb charging current to %d mA\n", + __func__, battery->prev_usb_conf); + sec_bat_set_charging_current(battery); + battery->prev_usb_conf = USB_CURRENT_NONE; + } + + dev_info(battery->dev, "%s: Cable is NOT Changed(%d)\n", __func__, battery->cable_type); + /* Do NOT activate cable work for NOT changed */ + + return true; + } + return false; +} + +__visible_for_testing int cw_check_cable_switch(struct sec_battery_info *battery, int prev_ct, + int cur_ct, bool afc_disabled) +{ + if ((is_wired_type(prev_ct) && is_wireless_all_type(cur_ct)) + || (is_wireless_all_type(prev_ct) && is_wired_type(cur_ct)) + || afc_disabled) { + battery->max_charge_power = 0; + sec_bat_set_threshold(battery, cur_ct); + } + + if (cur_ct == SEC_BATTERY_CABLE_HV_TA_CHG_LIMIT) + cur_ct = SEC_BATTERY_CABLE_9V_TA; + + if (!can_usb_suspend_type(cur_ct) && + battery->current_event & SEC_BAT_CURRENT_EVENT_USB_SUSPENDED) { + pr_info("%s: clear suspend event prev_cable_type:%s -> %s\n", __func__, + sb_get_ct_str(prev_ct), sb_get_ct_str(cur_ct)); + sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_USB_SUSPENDED); + sec_vote(battery->chgen_vote, VOTER_SUSPEND, false, 0); + sec_vote(battery->fcc_vote, VOTER_USB_100MA, false, 0); + sec_vote(battery->input_vote, VOTER_USB_100MA, false, 0); + } + + return cur_ct; +} + +#if defined(CONFIG_USE_POGO) +static int cw_check_pogo(struct sec_battery_info *battery) +{ + int pogo_current, wire_current; + + if (battery->pogo_status) { + if (battery->wire_status != SEC_BATTERY_CABLE_NONE) { + if (battery->pogo_9v) { + pogo_current = battery->pdata->charging_current[SEC_BATTERY_CABLE_POGO_9V].input_current_limit; + pogo_current = pogo_current * SEC_INPUT_VOLTAGE_9V; + } else { + pogo_current = battery->pdata->charging_current[SEC_BATTERY_CABLE_POGO].input_current_limit; + pogo_current = pogo_current * SEC_INPUT_VOLTAGE_5V; + } + if (battery->wire_status == SEC_BATTERY_CABLE_PDIC) { + if (pogo_current < battery->pd_max_charge_power) + return battery->wire_status; + } else { + wire_current = (battery->wire_status == SEC_BATTERY_CABLE_PREPARE_TA ? + battery->pdata->charging_current[SEC_BATTERY_CABLE_TA].input_current_limit : + battery->pdata->charging_current[battery->wire_status].input_current_limit); + + wire_current = wire_current * (is_hv_wire_type(battery->wire_status) ? + (battery->wire_status == SEC_BATTERY_CABLE_12V_TA ? SEC_INPUT_VOLTAGE_12V : SEC_INPUT_VOLTAGE_9V) + : SEC_INPUT_VOLTAGE_5V); + pr_info("%s: pogo_cur(%d), wr_cur(%d), wire_cable_type(%d)\n", + __func__, pogo_current, wire_current, battery->wire_status); + + if (pogo_current < wire_current) + return battery->wire_status; + else + return (battery->pogo_9v) ? SEC_BATTERY_CABLE_POGO_9V : SEC_BATTERY_CABLE_POGO; + } + } + return (battery->pogo_9v) ? SEC_BATTERY_CABLE_POGO_9V : SEC_BATTERY_CABLE_POGO; + } + return battery->wire_status; +} +#endif + +static void cw_set_psp_online2drivers(struct sec_battery_info *battery) +{ + union power_supply_propval val = {0, }; + + if (is_slate_mode(battery)) + val.intval = SEC_BATTERY_CABLE_NONE; + else + val.intval = battery->cable_type; + psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_PROP_ONLINE, val); + + val.intval = battery->cable_type; + psy_do_property(battery->pdata->fuelgauge_name, set, POWER_SUPPLY_PROP_ONLINE, val); + + if (battery->wc_tx_enable) { + val.intval = is_wired_type(battery->wire_status) ? 1 : 0; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_WR_CONNECTED, val); + } + + if (!is_wireless_fake_type(battery->cable_type)) + sec_vote_refresh(battery->chgen_vote); +} + +static void cw_check_wc_condition(struct sec_battery_info *battery, int prev_cable_type) +{ + /* need to move to wireless set property */ + if (is_wireless_all_type(battery->cable_type)) { + power_supply_changed(battery->psy_bat); + } + + /* For wire + wireless case */ + if (!is_wireless_type(prev_cable_type) && is_wireless_type(battery->cable_type)) { + pr_info("%s: non-wl -> wl: prev_cable(%s) , current_cable(%s)\n", + __func__, sb_get_ct_str(prev_cable_type), sb_get_ct_str(battery->cable_type)); + battery->wc_cv_mode = false; + battery->charging_passed_time = 0; + } +} + +static void chg_retention_time_check(struct sec_battery_info *battery) +{ + struct timespec64 ts = {0, }; + + ts = ktime_to_timespec64(ktime_get_boottime()); + pr_info("%s: end time = %lld\n", __func__, ts.tv_sec); + + if (ts.tv_sec <= battery->charging_retention_time || !battery->charging_retention_time) + return; + + battery->charging_retention_time = ts.tv_sec - battery->charging_retention_time; + /* get only minutes part */ + battery->charging_retention_time /= 60; + pr_info("%s: retention time = %ld mins\n", __func__, battery->charging_retention_time); + battery->cisd.data[CISD_DATA_TOTAL_CHG_RETENTION_TIME_PER_DAY] += battery->charging_retention_time; + battery->cisd.data[CISD_DATA_CHG_RETENTION_TIME_PER_DAY] = + max(battery->cisd.data[CISD_DATA_CHG_RETENTION_TIME_PER_DAY], (int)battery->charging_retention_time); + battery->charging_retention_time = 0; +} + +static void chg_start_time_check(struct sec_battery_info *battery) +{ + struct timespec64 ts = {0, }; + + ts = ktime_to_timespec64(ktime_get_boottime()); + battery->charging_retention_time = ts.tv_sec; + pr_info("%s: start time = %lu\n", __func__, battery->charging_retention_time); +} + +void sec_bat_smart_sw_src(struct sec_battery_info *battery, bool enable, int curr) +{ + if (enable) + battery->smart_sw_src = true; + else if (battery->smart_sw_src) + battery->smart_sw_src = false; + else + return; + + sec_pd_change_src(curr); + dev_info(battery->dev, + "%s: smart switch src, %s src cap max current\n", + __func__, enable ? "reduce" : "recover"); +} + +static void cw_nocharge_type(struct sec_battery_info *battery) +{ + int i; + + /* initialize all status */ + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + battery->is_recharging = false; + battery->cisd.ab_vbat_check_count = 0; + battery->cisd.state &= ~CISD_STATE_OVER_VOLTAGE; + battery->d2d_check = false; +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + battery->wc20_power_class = 0; + battery->wc20_rx_power = 0; + battery->wc20_vout = 0; +#endif + battery->input_voltage = 0; + battery->charge_power = 0; + battery->max_charge_power = 0; + battery->pd_max_charge_power = 0; + battery->pd_rated_power = 0; + sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_DISCHARGING); + battery->thermal_zone = BAT_THERMAL_NORMAL; + battery->chg_limit = false; + battery->lrp_limit = false; + battery->lrp_step = LRP_NONE; +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + battery->lrp_chg_src = SEC_CHARGING_SOURCE_DIRECT; +#endif + battery->mix_limit = false; + battery->chg_limit_recovery_cable = SEC_BATTERY_CABLE_NONE; + battery->wc_heating_start_time = 0; + sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_GOOD); + battery->prev_usb_conf = USB_CURRENT_NONE; + battery->ta_alert_mode = OCP_NONE; + battery->prev_tx_phm_mode = false; + battery->wpc_temp_v2_offset = 0; + battery->is_fpdo_dc = false; + + sec_bat_cancel_input_check_work(battery); + sec_bat_change_default_current(battery, SEC_BATTERY_CABLE_USB, + battery->pdata->default_usb_input_current, + battery->pdata->default_usb_charging_current); + sec_bat_change_default_current(battery, SEC_BATTERY_CABLE_TA, + battery->pdata->default_input_current, + battery->pdata->default_charging_current); + sec_bat_change_default_current(battery, SEC_BATTERY_CABLE_HV_WIRELESS_20, + battery->pdata->default_wc20_input_current, + battery->pdata->default_wc20_charging_current); + sec_bat_change_default_current(battery, SEC_BATTERY_CABLE_WIRELESS_EPP, + battery->pdata->default_wc20_input_current, + battery->pdata->default_wc20_charging_current); + /* usb default current is 100mA before configured*/ + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_USB_100MA, + (SEC_BAT_CURRENT_EVENT_CHARGE_DISABLE | + SEC_BAT_CURRENT_EVENT_AFC | + SEC_BAT_CURRENT_EVENT_VBAT_OVP | + SEC_BAT_CURRENT_EVENT_VSYS_OVP | + SEC_BAT_CURRENT_EVENT_CHG_LIMIT | + SEC_BAT_CURRENT_EVENT_AICL | + SEC_BAT_CURRENT_EVENT_SELECT_PDO | + SEC_BAT_CURRENT_EVENT_WDT_EXPIRED | + SEC_BAT_CURRENT_EVENT_25W_OCP | + SEC_BAT_CURRENT_EVENT_DC_ERR | + SEC_BAT_CURRENT_EVENT_USB_STATE | + SEC_BAT_CURRENT_EVENT_SEND_UVDM)); + sec_bat_set_misc_event(battery, 0, + (BATT_MISC_EVENT_WIRELESS_MISALIGN | BATT_MISC_EVENT_HV_BY_AICL)); + sec_bat_recov_full_capacity(battery); /* should call this after setting discharging */ + + /* slate_mode needs to be clear manually since smart switch does not disable slate_mode sometimes */ + if (is_slate_mode(battery)) { + int voter_status = SEC_BAT_CHG_MODE_CHARGING; + if (get_sec_voter_status(battery->chgen_vote, VOTER_SMART_SLATE, &voter_status) < 0) + pr_err("%s: INVALID VOTER ID\n", __func__); + pr_info("%s: voter_status: %d\n", __func__, voter_status); // debug + if (voter_status == SEC_BAT_CHG_MODE_BUCK_OFF) { + sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_SLATE); + sec_bat_set_mfc_on(battery, WPC_EN_SLATE); + dev_info(battery->dev, + "%s: disable slate mode(smart switch) manually\n", __func__); + } + } + sec_bat_smart_sw_src(battery, false, 500); + + chg_retention_time_check(battery); + battery->wc_cv_mode = false; + battery->is_sysovlo = false; + battery->is_vbatovlo = false; + battery->is_abnormal_temp = false; + battery->auto_mode = false; +#if !defined(CONFIG_SEC_FACTORY) + if (sec_bat_get_lpmode()) + battery->usb_conn_status = USB_CONN_NORMAL; +#endif + for (i = 0; i < VOTER_MAX; i++) { + if (i != VOTER_FLASH && + i != VOTER_MST && + i != VOTER_FW) + sec_vote(battery->iv_vote, i, false, 0); + if (i == VOTER_SIOP || + i == VOTER_SLATE || + i == VOTER_AGING_STEP || + i == VOTER_WC_TX || + i == VOTER_MUIC_ABNORMAL || + i == VOTER_NO_BATTERY || + i == VOTER_FULL_CAPACITY) + continue; + sec_vote(battery->topoff_vote, i, false, 0); + if (i != VOTER_FW) + sec_vote(battery->chgen_vote, i, false, 0); + sec_vote(battery->input_vote, i, false, 0); + sec_vote(battery->fcc_vote, i, false, 0); + sec_vote(battery->fv_vote, i, false, 0); + sec_vote(battery->dc_fv_vote, i, false, 0); + } + cancel_delayed_work(&battery->slowcharging_work); +#if !defined(CONFIG_NO_BATTERY) + /* Discharging has 100mA current unlike non LEGO model */ + sec_vote(battery->fcc_vote, VOTER_USB_100MA, true, 100); + sec_vote(battery->input_vote, VOTER_USB_100MA, true, 100); +#endif + battery->usb_slow_chg = false; +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + battery->limiter_check = false; +#endif + sec_bat_set_abnormal_ta_fcc(battery, false); +} + +static void cw_slate_mode(struct sec_battery_info *battery) +{ +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + union power_supply_propval val = {0, }; +#endif + int j = 0; + + /* Some charger ic's buck is enabled after vbus off, So disable buck again*/ + sec_vote_refresh(battery->chgen_vote); + battery->is_recharging = false; + battery->cable_type = SEC_BATTERY_CABLE_NONE; + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_GOOD); + battery->is_sysovlo = false; + battery->is_vbatovlo = false; + battery->is_abnormal_temp = false; + battery->lrp_limit = false; + battery->lrp_step = LRP_NONE; +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + battery->lrp_chg_src = SEC_CHARGING_SOURCE_DIRECT; +#endif + + for (j = 0; j < VOTER_MAX; j++) { + sec_vote(battery->iv_vote, j, false, 0); + if (j == VOTER_SIOP || + j == VOTER_SLATE || + j == VOTER_SMART_SLATE || + j == VOTER_AGING_STEP || + j == VOTER_WC_TX || + j == VOTER_FULL_CAPACITY) + continue; + sec_vote(battery->topoff_vote, j, false, 0); + sec_vote(battery->chgen_vote, j, false, 0); + sec_vote(battery->input_vote, j, false, 0); + sec_vote(battery->fcc_vote, j, false, 0); + sec_vote(battery->fv_vote, j, false, 0); + } + +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + /* No need val data */ + psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_EXT_PROP_DC_INITIALIZE, val); +#endif + battery->thermal_zone = BAT_THERMAL_NORMAL; + sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_DISCHARGING); +} + +static void cw_usb_suspend(struct sec_battery_info *battery) +{ + /* Some charger ic's buck is enabled after vbus off, So disable buck again*/ + sec_vote_refresh(battery->chgen_vote); + battery->is_recharging = false; + battery->cable_type = SEC_BATTERY_CABLE_NONE; + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_GOOD); + battery->is_sysovlo = false; + battery->is_vbatovlo = false; + battery->is_abnormal_temp = false; + battery->thermal_zone = BAT_THERMAL_NORMAL; + sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_DISCHARGING); +} + +static bool is_afc_evt_clear(int cable_type, int wire_status, int wc_status, unsigned int rp_currentlvl) +{ + if ((cable_type == SEC_BATTERY_CABLE_TA && (rp_currentlvl == RP_CURRENT_LEVEL_DEFAULT || + rp_currentlvl == RP_CURRENT_LEVEL_NONE)) || + cable_type == SEC_BATTERY_CABLE_WIRELESS || + cable_type == SEC_BATTERY_CABLE_PMA_WIRELESS || + (is_hv_wire_type(cable_type) && + (wc_status == SEC_BATTERY_CABLE_PREPARE_WIRELESS_20 || + is_pwr_nego_wireless_type(wc_status) || + wire_status == SEC_BATTERY_CABLE_HV_TA_CHG_LIMIT))) + return false; + else + return true; +} + +static void cw_prev_cable_is_nocharge(struct sec_battery_info *battery) +{ +#if IS_ENABLED(CONFIG_MTK_CHARGER) + union power_supply_propval value = {0, }; +#endif + + chg_start_time_check(battery); + + if ((battery->cable_type == SEC_BATTERY_CABLE_WIRELESS) && + (battery->health > POWER_SUPPLY_HEALTH_GOOD)) { + pr_info("%s: prev cable type was fake and health is not good\n", __func__); + return; + } +#if defined(CONFIG_ARCH_MTK_PROJECT) || IS_ENABLED(CONFIG_SEC_MTK_CHARGER) + if (battery->current_event & SEC_BAT_CURRENT_EVENT_USB_100MA) { + if ((battery->cable_type == SEC_BATTERY_CABLE_USB) && !lpcharge) { + pr_info("%s: usb unconfigured\n", __func__); + sec_vote(battery->fcc_vote, VOTER_USB_100MA, true, 100); + sec_vote(battery->input_vote, VOTER_USB_100MA, true, 100); + } + } +#endif + if (battery->pdata->full_check_type != SEC_BATTERY_FULLCHARGED_NONE) + battery->charging_mode = SEC_BATTERY_CHARGING_1ST; + else + battery->charging_mode = SEC_BATTERY_CHARGING_2ND; + +#if defined(CONFIG_ENABLE_FULL_BY_SOC) + if (battery->capacity >= 100) { + sec_bat_do_fullcharged(battery, true); + pr_info("%s: charging start at full, do not turn on charging\n", __func__); +#if IS_ENABLED(CONFIG_MTK_CHARGER) + /* + * MTK check the AFC when charging-on. + * Even though do not charging-on, need to check AFC still. + */ + value.intval = battery->cable_type; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_AFC_INIT, value); +#endif + } else if (!(battery->misc_event & BATT_MISC_EVENT_FULL_CAPACITY)) { + sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_CHARGING); + sec_vote(battery->topoff_vote, VOTER_FULL_CHARGE, false, 0); + sec_vote(battery->chgen_vote, VOTER_FULL_CHARGE, false, 0); + } +#else + if (!(battery->misc_event & BATT_MISC_EVENT_FULL_CAPACITY)) { + sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_CHARGING); + sec_vote(battery->topoff_vote, VOTER_FULL_CHARGE, false, 0); + sec_vote(battery->chgen_vote, VOTER_FULL_CHARGE, false, 0); + } +#endif + sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_GOOD); + + /* bat thm check to prevent charging current spike */ + sec_bat_thermal_check(battery); + + sec_vote(battery->chgen_vote, VOTER_CABLE, true, SEC_BAT_CHG_MODE_CHARGING); + + if (battery->cable_type == SEC_BATTERY_CABLE_USB && !sec_bat_get_lpmode()) + queue_delayed_work(battery->monitor_wqueue, &battery->slowcharging_work, msecs_to_jiffies(3000)); + if (is_hv_wireless_type(battery->cable_type) && battery->sleep_mode) + sec_vote(battery->input_vote, VOTER_SLEEP_MODE, true, battery->pdata->sleep_mode_limit_current); + + ttf_work_start(battery); +} + +static int sec_bat_check_pd_iv(SEC_PD_SINK_STATUS *sink_status) +{ + int i; + int max_voltage = SEC_INPUT_VOLTAGE_5V; + + for (i = 1; i <= sink_status->available_pdo_num; i++) { + if ((sink_status->power_list[i].pdo_type == APDO_TYPE) + || !sink_status->power_list[i].accept) + break; + + if (sink_status->power_list[i].max_voltage == SEC_INPUT_VOLTAGE_9V) { + max_voltage = SEC_INPUT_VOLTAGE_9V; + break; + } + } + + return max_voltage; +} + +#if defined(CONFIG_SUPPORT_HV_CTRL) && !defined(CONFIG_SEC_FACTORY) +static bool sb_ct_has_9v(struct sec_battery_info *battery, int ct) +{ + if (is_hv_wire_type(ct)) + return true; + else if (is_pd_wire_type(ct)) { + if (sec_bat_check_pd_iv(&battery->sink_status) == SEC_INPUT_VOLTAGE_9V) + return true; + } + return false; +} +#endif + +static void cw_set_iv(struct sec_battery_info *battery) +{ + if (is_nocharge_type(battery->cable_type) || is_wireless_all_type(battery->cable_type)) + return; + + if (is_hv_wire_type(battery->cable_type)) + sec_vote(battery->iv_vote, VOTER_CABLE, true, SEC_INPUT_VOLTAGE_9V); + else if (is_pd_wire_type(battery->cable_type)) + sec_vote(battery->iv_vote, VOTER_CABLE, true, sec_bat_check_pd_iv(&battery->sink_status)); +#if defined(CONFIG_USE_POGO) + else if (battery->cable_type == SEC_BATTERY_CABLE_POGO_9V) + sec_vote(battery->iv_vote, VOTER_CABLE, true, SEC_INPUT_VOLTAGE_9V); +#endif + else + sec_vote(battery->iv_vote, VOTER_CABLE, true, SEC_INPUT_VOLTAGE_5V); + +} + +__visible_for_testing void sec_bat_cable_work(struct work_struct *work) +{ + struct sec_battery_info *battery = container_of(work, + struct sec_battery_info, cable_work.work); + int current_cable_type = SEC_BATTERY_CABLE_NONE; + unsigned int input_current; + unsigned int charging_current; + bool clear_afc_evt = false; + int prev_cable_type = battery->cable_type; + int monitor_work_delay = 0; + + dev_info(battery->dev, "%s: Start\n", __func__); + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_SKIP_HEATING_CONTROL, + SEC_BAT_CURRENT_EVENT_SKIP_HEATING_CONTROL); + +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) +#if !IS_ENABLED(CONFIG_MTK_CHARGER) || IS_ENABLED(CONFIG_VIRTUAL_MUIC) + /* + * showing charging icon and noti(no sound, vi, haptic) only + * if slow insertion is detected by MUIC + */ + if (!(battery->pogo_status && battery->pdata->pogo_chgin)) + sec_bat_set_misc_event(battery, + (battery->muic_cable_type == ATTACHED_DEV_TIMEOUT_OPEN_MUIC ? BATT_MISC_EVENT_TIMEOUT_OPEN_TYPE : 0), + BATT_MISC_EVENT_TIMEOUT_OPEN_TYPE); +#endif +#endif + +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + current_cable_type = sec_bat_choose_cable_type(battery); +#elif defined(CONFIG_USE_POGO) + current_cable_type = cw_check_pogo(battery); +#else + current_cable_type = battery->wire_status; +#endif + + /* check wire type PD charger case */ + cw_check_pd_wire_type(battery); + + /* check cable work skip condition */ + if (cw_check_skip_condition(battery, current_cable_type)) + goto end_of_cable_work; + + /* to clear this value when cable type switched without detach */ + prev_cable_type = battery->cable_type; + battery->cable_type = cw_check_cable_switch(battery, prev_cable_type, + current_cable_type, check_afc_disabled_type(battery->muic_cable_type)); + + /* set online(cable type) */ + cw_set_psp_online2drivers(battery); + + /* check wireless charging condition */ + cw_check_wc_condition(battery, prev_cable_type); + + if (battery->pdata->check_cable_result_callback) + battery->pdata->check_cable_result_callback(battery->cable_type); + + if (is_nocharge_type(battery->cable_type) || + ((battery->pdata->cable_check_type & SEC_BATTERY_CABLE_CHECK_NOINCOMPATIBLECHARGE) && + battery->cable_type == SEC_BATTERY_CABLE_UNKNOWN)) { + pr_info("%s: prev_cable_type(%d)\n", __func__, prev_cable_type); + cw_nocharge_type(battery); + } else if (is_slate_mode(battery)) { + pr_info("%s: slate mode on\n", __func__); + cw_slate_mode(battery); + } else if (battery->current_event & SEC_BAT_CURRENT_EVENT_USB_SUSPENDED) { + pr_info("%s: usb suspend\n", __func__); + cw_usb_suspend(battery); + battery->prev_usb_conf = USB_CURRENT_NONE; + monitor_work_delay = 3000; + goto run_monitor_work; + } else if (!battery->is_recharging && + (is_nocharge_type(prev_cable_type) || is_wireless_fake_type(prev_cable_type))) { + pr_info("%s: c: %d, ov: %d, at: %d, cm: %d, tz: %d\n", __func__, + battery->cable_type, battery->is_vbatovlo, battery->is_abnormal_temp, + battery->charger_mode, battery->thermal_zone); + clear_afc_evt = + is_afc_evt_clear(battery->cable_type, battery->wire_status, battery->wc_status, battery->sink_status.rp_currentlvl); + if (!clear_afc_evt) + sec_bat_check_afc_input_current(battery); + + cw_prev_cable_is_nocharge(battery); + + sec_vote_refresh(battery->iv_vote); + } else if (is_hv_wire_type(battery->cable_type) && (battery->current_event & SEC_BAT_CURRENT_EVENT_AFC)) { + clear_afc_evt = + is_afc_evt_clear(battery->cable_type, battery->wire_status, battery->wc_status, battery->sink_status.rp_currentlvl); + } + +#if defined(CONFIG_STEP_CHARGING) + if (!is_hv_wire_type(battery->cable_type) && !is_pd_wire_type(battery->cable_type) + && (battery->sink_status.rp_currentlvl != RP_CURRENT_LEVEL3)) + sec_bat_reset_step_charging(battery); +#endif + + /* set input voltage by cable type */ + cw_set_iv(battery); + +#if IS_ENABLED(CONFIG_USB_FACTORY_MODE) && defined(CONFIG_SEC_FACTORY) + sec_bat_usb_factory_set_vote(battery, true); +#endif + + /* Check VOTER_SIOP to set up current based on cable_type */ + __pm_stay_awake(battery->siop_level_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->siop_level_work, 0); + + if (battery->cable_type != SEC_BATTERY_CABLE_WIRELESS_FAKE) { + sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_AICL); + sec_vote(battery->input_vote, VOTER_AICL, false, 0); + sec_bat_check_input_voltage(battery, battery->cable_type); + /* to init battery type current when wireless charging -> battery case */ + sec_vote_refresh(battery->input_vote); +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + sec_wireless_otg_icl_control(battery); +#endif + input_current = battery->pdata->charging_current[current_cable_type].input_current_limit; + charging_current = battery->pdata->charging_current[current_cable_type].fast_charging_current; + sec_vote(battery->fcc_vote, VOTER_CABLE, true, charging_current); + sec_vote(battery->input_vote, VOTER_CABLE, true, input_current); + } + + if ((!is_nocharge_type(battery->cable_type) && battery->cable_type != SEC_BATTERY_CABLE_USB) || sec_bat_get_lpmode()) { + sec_vote(battery->fcc_vote, VOTER_USB_100MA, false, 0); + sec_vote(battery->input_vote, VOTER_USB_100MA, false, 0); + } + + if (clear_afc_evt) { + sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_AFC); + sec_vote(battery->input_vote, VOTER_VBUS_CHANGE, false, 0); + } + store_battery_log( + "CW:%d%%,%dmV,%s,ct(%s,%s,%d,%d),slate(%d),cev(0x%x),mev(0x%x)", + battery->capacity, + battery->voltage_now, + sb_get_bst_str(battery->status), + sb_get_ct_str(battery->cable_type), + sb_get_ct_str(battery->wire_status), + battery->muic_cable_type, + battery->pd_usb_attached, + is_slate_mode(battery), + battery->current_event, + battery->misc_event + ); + + /* + * polling time should be reset when cable is changed + * polling_in_sleep should be reset also + * before polling time is re-calculated + * to prevent from counting 1 for events + * right after cable is connected + */ + battery->polling_in_sleep = false; + sec_bat_get_polling_time(battery); + + pr_info("%s: Status:%s, Sleep:%s, Charging:%s, Short Poll:%s\n", + __func__, sb_get_bst_str(battery->status), + battery->polling_in_sleep ? "Yes" : "No", + (battery->charging_mode == SEC_BATTERY_CHARGING_NONE) ? "No" : "Yes", + battery->polling_short ? "Yes" : "No"); + pr_info("%s: Polling time is reset to %d sec.\n", __func__, battery->polling_time); + + battery->polling_count = 1; /* initial value = 1 */ + +run_monitor_work: + __pm_stay_awake(battery->monitor_ws); + /* run monitor_work immediately if SEC_BAT_CURRENT_EVENT_USB_SUSPENDED is cleared for timing issue */ + if (!(battery->current_event & SEC_BAT_CURRENT_EVENT_USB_SUSPENDED)) + cancel_delayed_work(&battery->monitor_work); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, msecs_to_jiffies(monitor_work_delay)); +end_of_cable_work: +#if IS_ENABLED(CONFIG_MTK_CHARGER) && !IS_ENABLED(CONFIG_VIRTUAL_MUIC) + if ((battery->bc12_cable != SEC_BATTERY_CABLE_TIMEOUT) && + (battery->misc_event & BATT_MISC_EVENT_TIMEOUT_OPEN_TYPE)) + sec_bat_set_misc_event(battery, 0, + BATT_MISC_EVENT_TIMEOUT_OPEN_TYPE); +#endif + + sb_set_vbus_wake(battery->vbus_ws, battery->health, battery->cable_type); + __pm_relax(battery->cable_ws); + dev_info(battery->dev, "%s: End\n", __func__); +} +EXPORT_SYMBOL_KUNIT(sec_bat_cable_work); + +#define MAX_INPUT_CHECK_COUNT 3 +__visible_for_testing void sec_bat_input_check_work(struct work_struct *work) +{ + struct sec_battery_info *battery = container_of(work, + struct sec_battery_info, input_check_work.work); + union power_supply_propval value = {0, }; + + dev_info(battery->dev, "%s for %s start\n", __func__, sb_get_ct_str(battery->cable_type)); + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, value); + battery->current_max = value.intval; + + if (battery->current_event & SEC_BAT_CURRENT_EVENT_AFC) { + sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_AFC); + sec_vote(battery->input_vote, VOTER_VBUS_CHANGE, false, 0); +#if !defined(CONFIG_SEC_FACTORY) + sec_bat_change_vbus(battery, battery->cable_type, battery->current_event, battery->siop_level); +#endif + + if (battery->cable_type == SEC_BATTERY_CABLE_TA) + battery->cisd.cable_data[CISD_CABLE_TA]++; + +#if defined(CONFIG_WIRELESS_TX_MODE) + if (battery->wc_tx_enable) + sec_bat_run_wpc_tx_work(battery, 0); +#endif + } else if (battery->current_event & SEC_BAT_CURRENT_EVENT_SELECT_PDO) { + int curr_pdo = 0, pdo = 0, iv = 0, icl = 0; + + iv = get_sec_vote_result(battery->iv_vote); + if ((iv == SEC_INPUT_VOLTAGE_APDO) || + (sec_pd_get_pdo_power(&pdo, &iv, &iv, &icl) <= 0) || + (sec_pd_get_current_pdo(&curr_pdo) < 0) || + (pdo == curr_pdo)) { + pr_info("%s: clear select_pdo event(%d, %d, %d)\n", + __func__, iv, pdo, curr_pdo); + +#if IS_ENABLED(CONFIG_MTK_CHARGER) + if (sec_pd_get_current_pdo(&curr_pdo) >= 0) { + pr_info("%s: sink_status.current_pdo_num(%d), curr_pdo(%d)\n", + __func__, battery->sink_status.current_pdo_num, curr_pdo); + battery->sink_status.current_pdo_num = curr_pdo; + sec_bat_get_input_current_in_power_list(battery); + sec_bat_get_charging_current_in_power_list(battery); + } +#endif + sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_SELECT_PDO); + sec_vote(battery->input_vote, VOTER_SELECT_PDO, false, 0); + battery->input_check_cnt = 0; + + /* Check VOTER_SIOP to set up current after select pdo. */ + __pm_stay_awake(battery->siop_level_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->siop_level_work, 0); + } else if ((++battery->input_check_cnt) % MAX_INPUT_CHECK_COUNT) { + pr_info("%s: refresh(%d)!! pdo: %d, curr_pdo: %d\n", + __func__, battery->input_check_cnt, pdo, curr_pdo); + sec_vote_refresh(battery->iv_vote); + return; + } + } else { + dev_info(battery->dev, "%s: Nothing to do\n", __func__); + } + + dev_info(battery->dev, "%s: End\n", __func__); +} +EXPORT_SYMBOL_KUNIT(sec_bat_input_check_work); + +static void sec_bat_transit_clear_work(struct work_struct *work) +{ + struct sec_battery_info *battery = container_of(work, + struct sec_battery_info, transit_clear_work.work); + + sec_vote(battery->chgen_vote, VOTER_SRCCAP_TRANSIT, false, 0); + battery->srccap_transit = false; + pr_info("%s: disable VOTER_SRCCAP_TRANSIT manually\n", __func__); +} + +static void sec_bat_usb_conn_check_work(struct work_struct *work) +{ + struct sec_battery_info *battery = container_of(work, + struct sec_battery_info, usb_conn_check_work.work); + + pr_info("%s: usb_conn_check_cnt(%d)\n", __func__, ++battery->usb_conn_check_cnt); + + if (battery->usb_conn_check_cnt > MAX_USB_CONN_CHECK_CNT || + battery->usb_conn_status != USB_CONN_NORMAL || + battery->current_event & SEC_BAT_CURRENT_EVENT_TEMP_CTRL_TEST) { + battery->run_usb_conn_check = false; + battery->usb_conn_check_cnt = 0; + __pm_relax(battery->usb_conn_check_ws); + return; + } + + if (sec_usb_conn_check(battery) != USB_CONN_NORMAL) { + pr_info("%s: Set BATT_MISC_EVENT_TEMP_HICCUP_TYPE in usb_conn_check_work\n", __func__); + battery->run_usb_conn_check = false; + battery->usb_conn_check_cnt = 0; + battery->polling_count = 1; + __pm_stay_awake(battery->monitor_ws); + __pm_relax(battery->usb_conn_check_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + } else { + queue_delayed_work(battery->monitor_wqueue, + &battery->usb_conn_check_work, msecs_to_jiffies(1000)); + } +} + +#if IS_ENABLED(CONFIG_MTK_CHARGER) && !IS_ENABLED(CONFIG_VIRTUAL_MUIC) +static void sec_bat_handle_bc12_connection(struct sec_battery_info *battery) +{ + mutex_lock(&battery->bc12_notylock); + + dev_info(battery->dev, + "%s: prev_cable_type(%d), bc12_cable(%d), wire_status(%d)\n", + __func__, battery->cable_type, battery->bc12_cable, battery->wire_status); +#if defined(CONFIG_SUPPORT_HV_CTRL) + if (battery->cable_type == SEC_BATTERY_CABLE_9V_TA && + battery->bc12_cable == SEC_BATTERY_CABLE_9V_TA) { + sec_vote(battery->input_vote, VOTER_CABLE, true, battery->pdata->charging_current[battery->bc12_cable].input_current_limit); + sec_vote(battery->fcc_vote, VOTER_CABLE, true, battery->pdata->charging_current[battery->bc12_cable].fast_charging_current); + } +#endif + + battery->wire_status = battery->bc12_cable; + + if (is_hv_wire_type(battery->bc12_cable)) { + sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_AICL); + sec_vote(battery->input_vote, VOTER_AICL, false, 0); + } + if (battery->bc12_cable == SEC_BATTERY_CABLE_NONE) { +#if IS_ENABLED(CONFIG_USB_FACTORY_MODE) + sec_bat_usb_factory_clear(battery); +#endif +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + battery->muic_cable_type = ATTACHED_DEV_NONE_MUIC; +#endif +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + battery->init_src_cap = false; + battery->sink_status.rp_currentlvl = RP_CURRENT_LEVEL_NONE; +#endif + } +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + else if (battery->cable_type != battery->bc12_cable && + battery->sink_status.rp_currentlvl >= RP_CURRENT_LEVEL_DEFAULT && + (battery->bc12_cable == SEC_BATTERY_CABLE_USB || + battery->bc12_cable == SEC_BATTERY_CABLE_TA)) { + sec_bat_set_rp_current(battery, battery->bc12_cable); + } +#endif + else if ((battery->bc12_cable == SEC_BATTERY_CABLE_TIMEOUT) && + (!battery->init_src_cap)) + /* + * showing charging icon and noti(no sound, vi, haptic) only + * if slow insertion is detected by BC1.2 + */ + sec_bat_set_misc_event(battery, BATT_MISC_EVENT_TIMEOUT_OPEN_TYPE, + BATT_MISC_EVENT_TIMEOUT_OPEN_TYPE); + + /* cable is attached or detached + * if battery->bc12_cable is minus value, + * check cable by sec_bat_get_cable_type() + * although SEC_BATTERY_CABLE_SOURCE_EXTERNAL is set + * (0 is SEC_BATTERY_CABLE_UNKNOWN) + */ + if ((battery->bc12_cable >= 0) && + (battery->bc12_cable < SEC_BATTERY_CABLE_MAX) && + (battery->pdata->cable_source_type & + SEC_BATTERY_CABLE_SOURCE_EXTERNAL)) { + + __pm_stay_awake(battery->cable_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->cable_work, 0); + } else { + if (sec_bat_get_cable_type(battery, + battery->pdata->cable_source_type)) { + __pm_stay_awake(battery->cable_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->cable_work, 0); + } + } + mutex_unlock(&battery->bc12_notylock); +} /* sec_bat_handle_bc12_connection */ +#endif + +#define TRANSIT_CNT 5 +#define MAX_TRANSIT_CNT 100 +static void sec_bat_check_srccap_transit(struct sec_battery_info *battery, int enable) +{ + int voter_status; + + pr_info("%s: set init_src_cap(%d->%d)", + __func__, battery->init_src_cap, enable); + + if (enable) { + battery->init_src_cap = true; + return; + } + + if (++battery->srccap_transit_cnt < TRANSIT_CNT) { + sec_vote(battery->chgen_vote, VOTER_SRCCAP_TRANSIT, true, SEC_BAT_CHG_MODE_BUCK_OFF); + battery->srccap_transit = true; + queue_delayed_work(battery->monitor_wqueue, + &battery->transit_clear_work, msecs_to_jiffies(10000)); + return; + } + + if (battery->srccap_transit_cnt > MAX_TRANSIT_CNT) + battery->srccap_transit_cnt = TRANSIT_CNT; + voter_status = SEC_BAT_CHG_MODE_CHARGING; + if (get_sec_voter_status(battery->chgen_vote, VOTER_SRCCAP_TRANSIT, &voter_status) < 0) { + return; + } + pr_info("%s: voter_status: %d cnt: %d\n", __func__, voter_status, battery->srccap_transit_cnt); + + if (voter_status == SEC_BAT_CHG_MODE_BUCK_OFF) { + sec_vote(battery->chgen_vote, VOTER_SRCCAP_TRANSIT, false, 0); + battery->srccap_transit = false; + pr_info("%s: disable VOTER_SRCCAP_TRANSIT manually\n", __func__); + } +#if IS_ENABLED(CONFIG_SEC_ABC) + sec_abc_send_event("MODULE=battery@WARN=dc_error"); +#endif +} + +static int sec_bat_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct sec_battery_info *battery = power_supply_get_drvdata(psy); + int current_cable_type = SEC_BATTERY_CABLE_NONE; + int full_check_type = SEC_BATTERY_FULLCHARGED_NONE; + union power_supply_propval value = {0, }; + enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; + + dev_dbg(battery->dev, + "%s: (%d,%d)\n", __func__, psp, val->intval); + + switch ((int)psp) { + case POWER_SUPPLY_PROP_STATUS: + if (battery->charging_mode == SEC_BATTERY_CHARGING_1ST) + full_check_type = battery->pdata->full_check_type; + else + full_check_type = battery->pdata->full_check_type_2nd; + if ((full_check_type == SEC_BATTERY_FULLCHARGED_CHGINT) && + (val->intval == POWER_SUPPLY_STATUS_FULL)) + sec_bat_do_fullcharged(battery, false); + sec_bat_set_charging_status(battery, val->intval); + break; + case POWER_SUPPLY_PROP_HEALTH: + if (!is_wireless_fake_type(battery->cable_type)) { + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->monitor_work, msecs_to_jiffies(100)); + } + break; + case POWER_SUPPLY_PROP_ONLINE: + current_cable_type = val->intval; + if (current_cable_type < 0) { + dev_info(battery->dev, + "%s: ignore event(%d)\n", + __func__, current_cable_type); + } else { + if (current_cable_type == SEC_BATTERY_CABLE_OTG) { + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + battery->is_recharging = false; + sec_bat_set_charging_status(battery, + POWER_SUPPLY_STATUS_DISCHARGING); + battery->cable_type = current_cable_type; + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->monitor_work, 0); + break; + } else { + battery->wire_status = current_cable_type; + if (is_nocharge_type(battery->wire_status) && + (battery->wc_status != SEC_BATTERY_CABLE_NONE)) + current_cable_type = SEC_BATTERY_CABLE_WIRELESS; + } +#if IS_ENABLED(CONFIG_MTK_CHARGER) && !IS_ENABLED(CONFIG_VIRTUAL_MUIC) + battery->bc12_cable = current_cable_type; + +/* Skip notify from BC1.2 if PDIC is attached already */ + if ((is_pd_wire_type(battery->wire_status) || battery->init_src_cap) && + (battery->bc12_cable != SEC_BATTERY_CABLE_NONE)) { + if (lpcharge) + break; + else if (battery->usb_conn_status == USB_CONN_NORMAL && + !(battery->misc_event & BATT_MISC_EVENT_TEMP_HICCUP_TYPE)) + break; + } + sec_bat_handle_bc12_connection(battery); +#endif + } +#if !IS_ENABLED(CONFIG_MTK_CHARGER) || IS_ENABLED(CONFIG_VIRTUAL_MUIC) + dev_info(battery->dev, + "%s: current_cable(%d), wc_status(%d), wire_status(%d)\n", + __func__, current_cable_type, battery->wc_status, + battery->wire_status); + + /* + * cable is attached or detached + * if current_cable_type is minus value, + * check cable by sec_bat_get_cable_type() + * although SEC_BATTERY_CABLE_SOURCE_EXTERNAL is set + * (0 is SEC_BATTERY_CABLE_UNKNOWN) + */ + if ((current_cable_type >= 0) && + (current_cable_type < SEC_BATTERY_CABLE_MAX) && + (battery->pdata->cable_source_type & + SEC_BATTERY_CABLE_SOURCE_EXTERNAL)) { + + __pm_stay_awake(battery->cable_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->cable_work,0); + } else { + if (sec_bat_get_cable_type(battery, + battery->pdata->cable_source_type)) { + __pm_stay_awake(battery->cable_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->cable_work,0); + } + } +#endif + break; + case POWER_SUPPLY_PROP_CAPACITY: + battery->capacity = val->intval; + power_supply_changed(battery->psy_bat); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + break; + case POWER_SUPPLY_PROP_PRESENT: + battery->present = val->intval; + + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + pr_info("%s: Valert was occurred! run monitor work for updating cisd data!\n", __func__); + battery->cisd.data[CISD_DATA_VALERT_COUNT]++; + battery->cisd.data[CISD_DATA_VALERT_COUNT_PER_DAY]++; + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work_on(0, battery->monitor_wqueue, + &battery->monitor_work, 0); + break; + case POWER_SUPPLY_PROP_ENERGY_NOW: + if (!battery->factory_mode_boot_on) + factory_mode = 1; + break; + case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: + switch (ext_psp) { + case POWER_SUPPLY_EXT_PROP_AICL_CURRENT: + battery->max_charge_power = battery->charge_power = + mW_by_mVmA(battery->input_voltage, val->intval); + /* Voter should be removed after all chagers is fixed */ + sec_vote(battery->input_vote, VOTER_AICL, true, val->intval); +#if defined(CONFIG_SUPPORT_HV_CTRL) && !defined(CONFIG_SEC_FACTORY) + if (battery->pdata->boosting_voltage_aicl) { + sec_bat_set_misc_event(battery, BATT_MISC_EVENT_HV_BY_AICL, + BATT_MISC_EVENT_HV_BY_AICL); + if (sb_ct_has_9v(battery, battery->cable_type)) { + sec_vote(battery->input_vote, VOTER_AICL, false, 0); + sec_vote(battery->iv_vote, VOTER_AICL, true, SEC_INPUT_VOLTAGE_9V); + + /* Check siop level to set current */ + __pm_stay_awake(battery->siop_level_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->siop_level_work, 0); + } + } +#endif + + pr_info("%s: aicl : %dmA, %dmW)\n", __func__, + val->intval, battery->charge_power); + if (is_wired_type(battery->cable_type)) { + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_AICL, + SEC_BAT_CURRENT_EVENT_AICL); + store_battery_log( + "AICL:%d%%,curr(%dmA),%dmV,%s,ct(%d,%d,%d,%d),cev(0x%x)", + battery->capacity, + val->intval, + battery->voltage_now, + sb_get_bst_str(battery->status), + battery->cable_type, + battery->wire_status, + battery->muic_cable_type, + battery->pd_usb_attached, + battery->current_event + ); + } + battery->cisd.data[CISD_DATA_AICL_COUNT]++; + battery->cisd.data[CISD_DATA_AICL_COUNT_PER_DAY]++; + break; + case POWER_SUPPLY_EXT_PROP_SYSOVLO: + if (battery->status != POWER_SUPPLY_STATUS_DISCHARGING) { + pr_info("%s: Vsys is ovlo !!\n", __func__); + battery->is_sysovlo = true; + battery->is_recharging = false; + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + sec_bat_set_health(battery, POWER_SUPPLY_EXT_HEALTH_VSYS_OVP); + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_VSYS_OVP, SEC_BAT_CURRENT_EVENT_VSYS_OVP); + sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_NOT_CHARGING); + battery->cisd.data[CISD_DATA_VSYS_OVP]++; + battery->cisd.data[CISD_DATA_VSYS_OVP_PER_DAY]++; +#if IS_ENABLED(CONFIG_SEC_ABC) + sec_abc_send_event("MODULE=battery@WARN=vsys_ovp"); +#endif + sec_vote(battery->chgen_vote, VOTER_SYSOVLO, true, SEC_BAT_CHG_MODE_CHARGING_OFF); + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->monitor_work, 0); + } + break; + case POWER_SUPPLY_EXT_PROP_VBAT_OVP: + if (battery->status != POWER_SUPPLY_STATUS_DISCHARGING) { + pr_info("%s: Vbat is ovlo !!\n", __func__); + battery->is_vbatovlo = true; + battery->is_recharging = false; + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + sec_bat_set_health(battery, POWER_SUPPLY_EXT_HEALTH_VBAT_OVP); + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_VBAT_OVP, SEC_BAT_CURRENT_EVENT_VBAT_OVP); + sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_NOT_CHARGING); + + sec_vote(battery->chgen_vote, VOTER_VBAT_OVP, true, SEC_BAT_CHG_MODE_CHARGING_OFF); + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->monitor_work, 0); + } + break; + case POWER_SUPPLY_EXT_PROP_USB_CONFIGURE: +#if IS_ENABLED(CONFIG_USB_FACTORY_MODE) && defined(CONFIG_SEC_FACTORY) + pr_info("%s: usb configured %d\n", __func__, val->intval); + + if (sec_bat_usb_factory_set_vote(battery, true)) + break; +#endif + if (val->intval == USB_CURRENT_CLEAR || val->intval != battery->prev_usb_conf) + sec_bat_set_usb_configure(battery, val->intval); + + break; + case POWER_SUPPLY_EXT_PROP_OVERHEAT_NOTIFY: + pr_info("%s: POWER_SUPPLY_EXT_PROP_OVERHEAT_NOTIFY!\n", __func__); + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->monitor_work, 0); + break; + case POWER_SUPPLY_EXT_PROP_HV_DISABLE: +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) +#if defined(CONFIG_PD_CHARGER_HV_DISABLE) + pr_info("None PD wired charging mode is %s\n", (val->intval == CH_MODE_AFC_DISABLE_VAL ? "Disabled" : "Enabled")); + if (val->intval == CH_MODE_AFC_DISABLE_VAL) { + sec_bat_set_current_event(battery, + SEC_BAT_CURRENT_EVENT_NOPD_HV_DISABLE, SEC_BAT_CURRENT_EVENT_NOPD_HV_DISABLE); + } else { + sec_bat_set_current_event(battery, + 0, SEC_BAT_CURRENT_EVENT_NOPD_HV_DISABLE); + } +#else + pr_info("HV wired charging mode is %s\n", (val->intval == CH_MODE_AFC_DISABLE_VAL ? "Disabled" : "Enabled")); + if (val->intval == CH_MODE_AFC_DISABLE_VAL) { + sec_bat_set_current_event(battery, + SEC_BAT_CURRENT_EVENT_HV_DISABLE, SEC_BAT_CURRENT_EVENT_HV_DISABLE); + } else { + sec_bat_set_current_event(battery, + 0, SEC_BAT_CURRENT_EVENT_HV_DISABLE); + } + + if (is_pd_wire_type(battery->cable_type)) { + battery->update_pd_list = true; + pr_info("%s: update pd list\n", __func__); +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + if (is_pd_apdo_wire_type(battery->cable_type)) + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_REFRESH_CHARGING_SOURCE, value); +#endif + sec_vote_refresh(battery->iv_vote); + } +#endif +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + if ((battery->cable_type == SEC_BATTERY_CABLE_TA) && + (battery->sink_status.rp_currentlvl == RP_CURRENT_LEVEL3)) { + sec_bat_set_rp_current(battery, battery->cable_type); + sec_vote(battery->fcc_vote, VOTER_CABLE, true, + battery->pdata->charging_current[battery->cable_type].fast_charging_current); + sec_vote(battery->input_vote, VOTER_CABLE, true, + battery->pdata->charging_current[battery->cable_type].input_current_limit); + } +#endif +#endif + break; + case POWER_SUPPLY_EXT_PROP_WC_CONTROL: +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + pr_info("%s: Recover MFC IC (wc_enable: %d)\n", + __func__, battery->wc_enable); + + mutex_lock(&battery->wclock); + if (battery->wc_enable) { + sec_bat_set_mfc_off(battery, WPC_EN_CHARGING, false); + msleep(500); + + sec_bat_set_mfc_on(battery, WPC_EN_CHARGING); + } + mutex_unlock(&battery->wclock); +#endif + break; + case POWER_SUPPLY_EXT_PROP_WDT_STATUS: + if (val->intval) + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_WDT_EXPIRED, + SEC_BAT_CURRENT_EVENT_WDT_EXPIRED); + break; + case POWER_SUPPLY_EXT_PROP_CURRENT_EVENT: + if (!(battery->current_event & val->intval)) { + pr_info("%s: set new current_event %d\n", __func__, val->intval); + if (val->intval == SEC_BAT_CURRENT_EVENT_DC_ERR) + battery->cisd.event_data[EVENT_DC_ERR]++; + sec_bat_set_current_event(battery, val->intval, val->intval); + } + break; + case POWER_SUPPLY_EXT_PROP_CURRENT_EVENT_CLEAR: + pr_info("%s: new current_event clear %d\n", __func__, val->intval); + sec_bat_set_current_event(battery, 0, val->intval); + break; +#if defined(CONFIG_WIRELESS_TX_MODE) + case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_AVG_CURR: + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ENABLE: + sec_wireless_set_tx_enable(battery, val->intval); + break; +#endif + case POWER_SUPPLY_EXT_PROP_SRCCAP: + sec_bat_check_srccap_transit(battery, val->intval); + break; +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + case POWER_SUPPLY_EXT_PROP_DIRECT_TA_ALERT: + if (battery->ta_alert_wa) { + pr_info("@TA_ALERT: %s: TA OCP DETECT\n", __func__); + battery->cisd.event_data[EVENT_TA_OCP_DET]++; + if (battery->ta_alert_mode == OCP_NONE) + battery->cisd.event_data[EVENT_TA_OCP_ON]++; + battery->ta_alert_mode = OCP_DETECT; + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_25W_OCP, + SEC_BAT_CURRENT_EVENT_25W_OCP); + store_battery_log( + "TA OCP:%d%%,%dmV,%s,ct(%d,%d,%d,%d),cev(0x%x),mev(0x%x)", + battery->capacity, + battery->voltage_now, + sb_get_bst_str(battery->status), + battery->cable_type, + battery->wire_status, + battery->muic_cable_type, + battery->pd_usb_attached, + battery->current_event, + battery->misc_event + ); + } + break; + case POWER_SUPPLY_EXT_PROP_DIRECT_SEND_UVDM: + if (is_pd_apdo_wire_type(battery->cable_type)) { + char direct_charging_source_status[2] = {0, }; + + pr_info("@SEND_UVDM: Request Change Charging Source : %s\n", + val->intval == 0 ? "Switch Charger" : "Direct Charger" ); + + direct_charging_source_status[0] = SEC_SEND_UVDM; + direct_charging_source_status[1] = val->intval; + + sec_bat_set_current_event(battery, val->intval == 0 ? + SEC_BAT_CURRENT_EVENT_SEND_UVDM : 0, SEC_BAT_CURRENT_EVENT_SEND_UVDM); + value.strval = direct_charging_source_status; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_CHANGE_CHARGING_SOURCE, value); + } + break; + case POWER_SUPPLY_EXT_PROP_DIRECT_FIXED_PDO: + if (!is_slate_mode(battery) && !is_wireless_all_type(battery->cable_type)) { + sec_vote(battery->iv_vote, VOTER_DC_MODE, true, val->intval); + if (val->intval == SEC_INPUT_VOLTAGE_APDO) { + sec_vote(battery->iv_vote, VOTER_AICL, false, 0); + sec_bat_set_misc_event(battery, 0, BATT_MISC_EVENT_HV_BY_AICL); + } + } + break; +#endif +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + case POWER_SUPPLY_EXT_PROP_FASTCHG_LIMIT_CURRENT: + if (is_wireless_type(battery->cable_type)) + sec_bat_set_limiter_current(battery); + break; +#endif + case POWER_SUPPLY_EXT_PROP_WPC_EN: + sec_bat_set_current_event(battery, + val->intval ? 0 : SEC_BAT_CURRENT_EVENT_WPC_EN, SEC_BAT_CURRENT_EVENT_WPC_EN); + break; + case POWER_SUPPLY_EXT_PROP_CHARGE_UNO_CONTROL: + value.intval = val->intval; + pr_info("%s: WCIN-UNO %s\n", __func__, value.intval > 0 ? "on" : "off"); + psy_do_property("otg", set, + POWER_SUPPLY_EXT_PROP_CHARGE_UNO_CONTROL, value); + break; +#if IS_ENABLED(CONFIG_USB_FACTORY_MODE) + case POWER_SUPPLY_EXT_PROP_BATT_F_MODE: + battery->batt_f_mode = val->intval; + if (battery->batt_f_mode == OB_MODE) { + sec_bat_set_facmode(true); + battery->factory_mode = true; + } else if (((battery->cable_type == SEC_BATTERY_CABLE_NONE) && + (battery->wire_status != SEC_BATTERY_CABLE_NONE)) && + (battery->batt_f_mode == IB_MODE)) { + battery->charging_mode = SEC_BATTERY_CHARGING_1ST; + sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_CHARGING); + battery->cable_type = battery->wire_status; + sec_bat_set_facmode(false); + battery->factory_mode = false; + } else { + sec_bat_set_facmode(false); + battery->factory_mode = false; + } + value.intval = battery->batt_f_mode; + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_EXT_PROP_BATT_F_MODE, value); + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + break; +#endif + case POWER_SUPPLY_EXT_PROP_INPUT_VOLTAGE_REGULATION: + case POWER_SUPPLY_EXT_PROP_WIRELESS_RX_CONTROL: + break; + case POWER_SUPPLY_EXT_PROP_CHARGE_COUNTER_SHADOW: + break; +#if defined(CONFIG_UPDATE_BATTERY_DATA) + case POWER_SUPPLY_EXT_PROP_POWER_DESIGN: + sec_bat_parse_dt(battery->dev, battery); + break; +#endif + case POWER_SUPPLY_EXT_PROP_MFC_FW_UPDATE: + battery->mfc_fw_update = val->intval; + if (!battery->mfc_fw_update) { + pr_info("%s: fw update done: (5V -> 9V).\n", __func__); + sec_vote(battery->chgen_vote, VOTER_FW, false, 0); + sec_vote(battery->iv_vote, VOTER_FW, false, 0); +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) +#if !IS_ENABLED(CONFIG_MTK_CHARGER) || !IS_ENABLED(CONFIG_AFC_CHARGER) + muic_afc_request_voltage(AFC_REQUEST_MFC, SEC_INPUT_VOLTAGE_9V / 1000); +#endif +#endif + } + break; + case POWER_SUPPLY_EXT_PROP_THERMAL_ZONE: + pr_info("%s : bat_thm_info.check_type set to %s\n", __func__, val->intval ? "NONE" : "TEMP"); + if (val->intval) { + battery->skip_swelling = true; /* restore thermal_zone to NORMAL */ + battery->pdata->bat_thm_info.check_type = SEC_BATTERY_TEMP_CHECK_NONE; + battery->pdata->lrp_temp_check_type = SEC_BATTERY_TEMP_CHECK_NONE; + sec_vote(battery->iv_vote, VOTER_LRP_TEMP, false, 0); + sec_vote(battery->fcc_vote, VOTER_LRP_TEMP, false, 0); + sec_vote(battery->input_vote, VOTER_LRP_TEMP, false, 0); + battery->lrp_limit = false; + battery->lrp_step = LRP_NONE; +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + battery->lrp_chg_src = SEC_CHARGING_SOURCE_DIRECT; +#endif + } else { + battery->skip_swelling = false; + battery->pdata->bat_thm_info.check_type = SEC_BATTERY_TEMP_CHECK_TEMP; + battery->pdata->lrp_temp_check_type = SEC_BATTERY_TEMP_CHECK_TEMP; + /* Check VOTER_SIOP to set up current based on cable_type */ + __pm_stay_awake(battery->siop_level_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->siop_level_work, 0); + +#if !defined(CONFIG_SEC_FACTORY) + sec_bat_check_lrp_temp(battery, + battery->cable_type, battery->wire_status, + battery->siop_level, battery->lcd_status); +#endif + } + break; + case POWER_SUPPLY_EXT_PROP_TEMP_CHECK_TYPE: + break; + case POWER_SUPPLY_EXT_PROP_SUB_TEMP: + break; + case POWER_SUPPLY_EXT_PROP_WPC_FREQ_STRENGTH: + pr_info("%s : wpc vout strength(%d)\n", __func__, val->intval); + sec_bat_set_misc_event(battery, + ((val->intval <= 0) ? BATT_MISC_EVENT_WIRELESS_MISALIGN : 0), + BATT_MISC_EVENT_WIRELESS_MISALIGN); + break; + case POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL: + value.intval = val->intval; + pr_info("%s: d2d reverse boost : %d\n", __func__, val->intval); + if (val->intval) { + if (!battery->d2d_check) { + battery->cisd.event_data[EVENT_D2D]++; + battery->d2d_check = true; + } + sec_vote(battery->chgen_vote, VOTER_D2D_WIRE, true, SEC_BAT_CHG_MODE_BUCK_OFF); + } + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_D2D_REVERSE_VOLTAGE, value); + if (!val->intval) { + battery->d2d_check = false; + sec_vote(battery->chgen_vote, VOTER_D2D_WIRE, false, 0); + battery->vpdo_auth_stat = AUTH_NONE; + } + break; + case POWER_SUPPLY_EXT_PROP_FLASH_STATE: /* check only for MTK */ + battery->flash_state = val->intval; +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + if (is_pd_apdo_wire_type(battery->cable_type)) + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_REFRESH_CHARGING_SOURCE, value); +#endif + if (val->intval) { + pr_info("%s: Flash on: (9V -> 5V).\n", __func__); + sec_vote(battery->iv_vote, VOTER_FLASH, true, SEC_INPUT_VOLTAGE_5V); + } else { + pr_info("%s: Flash off: (5V -> 9V).\n", __func__); + sec_vote(battery->iv_vote, VOTER_FLASH, false, 0); + } + break; +#if IS_ENABLED(CONFIG_MTK_CHARGER) + case POWER_SUPPLY_EXT_PROP_MTK_FG_INIT: /* check only for MTK */ + battery->mtk_fg_init = val->intval; + pr_info("%s: mtk_fg_init (%d)\n", __func__, battery->mtk_fg_init); +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + if (is_pd_apdo_wire_type(battery->cable_type)) + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_REFRESH_CHARGING_SOURCE, value); +#endif + break; +#endif + case POWER_SUPPLY_EXT_PROP_USB_BOOTCOMPLETE: + battery->usb_bootcomplete = val->intval; + pr_info("%s: usb_bootcomplete (%d)\n", __func__, battery->usb_bootcomplete); + break; + case POWER_SUPPLY_EXT_PROP_MISC_EVENT: + if (!(battery->misc_event & val->intval)) { + pr_info("%s: set new misc_event %d\n", __func__, val->intval); + sec_bat_set_misc_event(battery, val->intval, val->intval); + } + break; + case POWER_SUPPLY_EXT_PROP_MISC_EVENT_CLEAR: + pr_info("%s: new misc_event clear %d\n", __func__, val->intval); + sec_bat_set_misc_event(battery, 0, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_MST_EN: + pr_info("%s: POWER_SUPPLY_EXT_PROP_MST_EN(%d)\n", __func__, val->intval); + battery->mst_en = val->intval; + if (val->intval) + sec_vote(battery->iv_vote, VOTER_MST, true, SEC_INPUT_VOLTAGE_5V); + else + sec_vote(battery->iv_vote, VOTER_MST, false, 0); + break; + case POWER_SUPPLY_EXT_PROP_ABNORMAL_SRCCAP: + store_battery_log( + "ABNORMAL_SRCCAP:%d%%,%dmV,%s,PDO(0x%X)", + battery->capacity, + battery->voltage_now, + sb_get_bst_str(battery->status), + val->intval + ); + break; + case POWER_SUPPLY_EXT_PROP_HARDRESET_OCCUR: + if (is_pd_wire_type(battery->cable_type)) { + battery->sink_status.selected_pdo_num = -1; + store_battery_log( + "HARDRESET_%s:%d%%,%dmV,%s,SELPDO(%d),CURPDO(%d)", + val->intval ? "SENT" : "RECEIVED", + battery->capacity, + battery->voltage_now, + sb_get_bst_str(battery->status), + battery->sink_status.selected_pdo_num, + battery->sink_status.current_pdo_num + ); + } + break; + case POWER_SUPPLY_EXT_PROP_ABNORMAL_TA: + pr_info("%s: POWER_SUPPLY_EXT_PROP_ABNORMAL_TA(%d)\n", __func__, val->intval); + sec_bat_set_abnormal_ta_fcc(battery, true); + break; + case POWER_SUPPLY_EXT_PROP_RX_PHM: +#if defined(CONFIG_WIRELESS_RX_PHM_CTRL) + pr_info("%s : PHM set %d\n", __func__, val->intval); + if (val->intval) + sec_vote(battery->chgen_vote, VOTER_PHM, true, SEC_BAT_CHG_MODE_BUCK_OFF); + else + sec_vote(battery->chgen_vote, VOTER_PHM, false, 0); +#endif + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int sec_bat_check_status(struct sec_battery_info *battery) +{ + /* need to update for DPM UI */ + if (battery->misc_event & BATT_MISC_EVENT_DIRECT_POWER_MODE) + return POWER_SUPPLY_STATUS_NOT_CHARGING; + + if ((battery->health == POWER_SUPPLY_HEALTH_OVERVOLTAGE) || + (battery->health == POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE)) + return POWER_SUPPLY_STATUS_DISCHARGING; + + if ((battery->pdata->cable_check_type & + SEC_BATTERY_CABLE_CHECK_NOUSBCHARGE) && + !sec_bat_get_lpmode()) { + switch (battery->cable_type) { + case SEC_BATTERY_CABLE_USB: + case SEC_BATTERY_CABLE_USB_CDP: + return POWER_SUPPLY_STATUS_DISCHARGING; + } + } + +#if defined(CONFIG_STORE_MODE) + if (battery->store_mode && !sec_bat_get_lpmode() && + !is_nocharge_type(battery->cable_type) && + battery->status == POWER_SUPPLY_STATUS_DISCHARGING) + return POWER_SUPPLY_STATUS_CHARGING; +#endif + + if (is_eu_eco_rechg(battery->fs) && + (battery->status == POWER_SUPPLY_STATUS_FULL)) { + if (battery->is_recharging) { +#if defined(CONFIG_ENABLE_FULL_BY_SOC) + if (battery->capacity < 100) + return POWER_SUPPLY_STATUS_CHARGING; +#else + return POWER_SUPPLY_STATUS_CHARGING; +#endif + } + + if (check_eu_eco_rechg_ui_condition(battery)) + return POWER_SUPPLY_STATUS_CHARGING; + } + + return battery->status; +} + +static int sec_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_battery_info *battery = power_supply_get_drvdata(psy); + union power_supply_propval value = {0, }; + enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; + + switch ((int)psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = sec_bat_check_status(battery); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + { + unsigned int input_current = battery->pdata->charging_current[battery->cable_type].input_current_limit; + if (is_nocharge_type(battery->cable_type)) { + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + } else if (is_hv_wire_type(battery->cable_type) || is_pd_wire_type(battery->cable_type) || is_wireless_all_type(battery->cable_type)) { + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + } else if (!battery->usb_bootcomplete && !lpcharge && battery->pdata->slowcharging_usb_bootcomplete) { + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + } else if (input_current <= SLOW_CHARGING_CURRENT_STANDARD || battery->usb_slow_chg) { + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + } else { + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_PROP_CHARGE_TYPE, value); + if (value.intval == SEC_BATTERY_CABLE_UNKNOWN) + /* + * if error in CHARGE_TYPE of charger + * set CHARGE_TYPE as NONE + */ + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + else + val->intval = value.intval; + } + } + break; + case POWER_SUPPLY_PROP_HEALTH: + if (sec_bat_get_lpmode() && + (battery->health == POWER_SUPPLY_HEALTH_OVERVOLTAGE || + battery->health == POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE || + battery->health == POWER_SUPPLY_EXT_HEALTH_DC_ERR)) { + val->intval = POWER_SUPPLY_HEALTH_GOOD; + } else if (battery->health >= POWER_SUPPLY_EXT_HEALTH_MIN) { + if (battery->health == POWER_SUPPLY_EXT_HEALTH_OVERHEATLIMIT) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else + val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; + } else { + val->intval = battery->health; + } + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = battery->present; + + /* BATT_MISC_EVENT_DIRECT_POWER_MODE event is activated only in the DPM application model. */ + /* need to update for SSRM cooldown mode */ + if (battery->misc_event & BATT_MISC_EVENT_DIRECT_POWER_MODE) + val->intval = 0; + + break; + case POWER_SUPPLY_PROP_ONLINE: + /* SEC_BATTERY_CABLE_SILENT_TYPE, defines with PMS team for avoid charger connection sound */ + if (check_silent_type(battery->muic_cable_type)) + val->intval = SEC_BATTERY_CABLE_SILENT_TYPE; +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + else if (is_hv_wireless_type(battery->cable_type) || + (battery->cable_type == SEC_BATTERY_CABLE_PREPARE_WIRELESS_HV)) { + if (sec_bat_hv_wc_normal_mode_check(battery)) + val->intval = SEC_BATTERY_CABLE_WIRELESS; + else + val->intval = SEC_BATTERY_CABLE_HV_WIRELESS_ETX; + } else if ((battery->cable_type == SEC_BATTERY_CABLE_PREPARE_WIRELESS_20) || + (battery->cable_type == SEC_BATTERY_CABLE_WIRELESS_EPP_FAKE)) { + if (sec_bat_hv_wc_normal_mode_check(battery) || + (battery->wc20_rx_power < WFC10_WIRELESS_POWER)) + val->intval = SEC_BATTERY_CABLE_WIRELESS; + else + val->intval = SEC_BATTERY_CABLE_HV_WIRELESS_ETX; + } else if (battery->cable_type == SEC_BATTERY_CABLE_WIRELESS_PACK || + battery->cable_type == SEC_BATTERY_CABLE_WIRELESS_STAND || + battery->cable_type == SEC_BATTERY_CABLE_PMA_WIRELESS || + battery->cable_type == SEC_BATTERY_CABLE_WIRELESS_VEHICLE || + battery->cable_type == SEC_BATTERY_CABLE_WIRELESS_TX || + battery->cable_type == SEC_BATTERY_CABLE_WIRELESS_FAKE) { + val->intval = SEC_BATTERY_CABLE_WIRELESS; + } +#endif + else + val->intval = battery->cable_type; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = battery->pdata->technology; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: +#ifdef CONFIG_SEC_FACTORY + value.intval = SEC_BATTERY_VOLTAGE_MV; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_VOLTAGE_NOW, value); + battery->voltage_now = value.intval; + dev_err(battery->dev, + "%s: voltage now(%d)\n", __func__, battery->voltage_now); +#endif + /* voltage value should be in uV */ + val->intval = battery->voltage_now * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: +#ifdef CONFIG_SEC_FACTORY + value.intval = SEC_BATTERY_VOLTAGE_MV; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_VOLTAGE_AVG, value); + battery->voltage_avg = value.intval; + dev_err(battery->dev, + "%s: voltage avg(%d)\n", __func__, battery->voltage_avg); +#endif + /* voltage value should be in uV */ + val->intval = battery->voltage_avg * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + value.intval = SEC_BATTERY_CURRENT_MA; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CURRENT_NOW, value); + val->intval = value.intval; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + value.intval = SEC_BATTERY_CURRENT_MA; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CURRENT_AVG, value); + val->intval = value.intval; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = battery->pdata->battery_full_capacity * 1000; + break; + /* charging mode (differ from power supply) */ + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = battery->charging_mode; + break; + case POWER_SUPPLY_PROP_CAPACITY: + if (battery->pdata->fake_capacity) { + val->intval = 70; + pr_info("%s : capacity(%d)\n", __func__, val->intval); + } else { + val->intval = battery->capacity; + if ((battery->status == POWER_SUPPLY_STATUS_FULL) && +#if defined(CONFIG_ENG_BATTERY_CONCEPT) + !battery->eng_not_full_status && +#endif + !is_eu_eco_rechg(battery->fs)) + val->intval = 100; + } + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = battery->temperature; + break; + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + val->intval = battery->temper_amb; + break; +#if IS_ENABLED(CONFIG_FUELGAUGE_MAX77705) + case POWER_SUPPLY_PROP_POWER_NOW: + value.intval = SEC_BATTERY_ISYS_MA; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_EXT_PROP_MEASURE_SYS, value); + val->intval = value.intval; + break; + case POWER_SUPPLY_PROP_POWER_AVG: + value.intval = SEC_BATTERY_ISYS_AVG_MA; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_EXT_PROP_MEASURE_SYS, value); + val->intval = value.intval; + break; +#endif + case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: + val->intval = ttf_display(battery->capacity, battery->status, + battery->thermal_zone, battery->ttf_d->timetofull); + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + if (battery->current_event & SEC_BAT_CURRENT_EVENT_SWELLING_MODE) + val->intval = 1; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + val->intval = battery->charge_counter; + break; + case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: + switch (ext_psp) { + case POWER_SUPPLY_EXT_PROP_CHARGE_POWER: + val->intval = battery->charge_power; + break; + case POWER_SUPPLY_EXT_PROP_CURRENT_EVENT: + val->intval = battery->current_event; + break; +#if defined(CONFIG_WIRELESS_TX_MODE) + case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_AVG_CURR: + val->intval = battery->tx_avg_curr; + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ENABLE: + val->intval = battery->wc_tx_enable; + break; +#endif +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + case POWER_SUPPLY_EXT_PROP_DIRECT_CHARGER_MODE: + val->intval = battery->pd_list.now_isApdo; + break; + case POWER_SUPPLY_EXT_PROP_DIRECT_HAS_APDO: + val->intval = battery->sink_status.has_apdo; + break; + case POWER_SUPPLY_EXT_PROP_PAD_VOLT_CTRL: + if (battery->pdata->wpc_vout_ctrl_lcd_on) + val->intval = battery->lcd_status; + else + val->intval = false; + break; + case POWER_SUPPLY_EXT_PROP_DIRECT_TA_ALERT: + if (battery->ta_alert_wa) { + val->intval = battery->ta_alert_mode; + } else + val->intval = OCP_NONE; + break; + case POWER_SUPPLY_EXT_PROP_DIRECT_SEND_UVDM: + break; +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + case POWER_SUPPLY_EXT_PROP_DIRECT_VBAT_CHECK: + pr_info("%s : step(%d/%d), vmp:%dmV, vsp:%dmV\n", __func__, + battery->step_chg_status, battery->dc_step_chg_step - 1, + battery->voltage_avg_main, battery->voltage_avg_sub); + if ((battery->step_chg_status == battery->dc_step_chg_step - 1) && + ((battery->voltage_avg_main >= battery->pdata->sc_vbat_thresh_main) || + (battery->voltage_avg_sub >= battery->pdata->sc_vbat_thresh_sub))) + val->intval = 1; + else + val->intval = 0; + break; + case POWER_SUPPLY_EXT_PROP_VOLTAGE_PACK_MAIN: + val->intval = battery->voltage_avg_main; + break; + case POWER_SUPPLY_EXT_PROP_VOLTAGE_PACK_SUB: + val->intval = battery->voltage_avg_sub; + break; +#endif +#endif + case POWER_SUPPLY_EXT_PROP_HV_PDO: + val->intval = battery->hv_pdo; + break; + case POWER_SUPPLY_EXT_PROP_CHARGE_UNO_CONTROL: + break; + case POWER_SUPPLY_EXT_PROP_CHARGE_COUNTER_SHADOW: + val->intval = battery->wire_status; + break; + case POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL: + break; + case POWER_SUPPLY_EXT_PROP_HEALTH: + val->intval = battery->health; + break; + case POWER_SUPPLY_EXT_PROP_MFC_FW_UPDATE: + val->intval = battery->mfc_fw_update; + break; + case POWER_SUPPLY_EXT_PROP_THERMAL_ZONE: + val->intval = battery->thermal_zone; + break; + case POWER_SUPPLY_EXT_PROP_TEMP_CHECK_TYPE: + switch (val->intval) { + case THM_INFO_BAT: + val->intval = battery->pdata->bat_thm_info.check_type; + break; + case THM_INFO_USB: + val->intval = battery->pdata->usb_thm_info.check_type; + break; + case THM_INFO_CHG: + val->intval = battery->pdata->chg_thm_info.check_type; + break; + case THM_INFO_WPC: + val->intval = battery->pdata->wpc_thm_info.check_type; + break; + case THM_INFO_SUB_BAT: + val->intval = battery->pdata->sub_bat_thm_info.check_type; + break; + case THM_INFO_BLK: + val->intval = battery->pdata->blk_thm_info.check_type; + break; + case THM_INFO_DCHG: + val->intval = battery->pdata->dchg_thm_info.check_type; + break; + default: + val->intval = SEC_BATTERY_TEMP_CHECK_NONE; + break; + } + break; + case POWER_SUPPLY_EXT_PROP_SUB_TEMP: + val->intval = battery->sub_bat_temp; + break; + case POWER_SUPPLY_EXT_PROP_MIX_LIMIT: + val->intval = battery->mix_limit; + break; + case POWER_SUPPLY_EXT_PROP_WPC_FREQ_STRENGTH: + break; + case POWER_SUPPLY_EXT_PROP_LCD_FLICKER: + val->intval = (battery->lcd_status && battery->wpc_vout_ctrl_mode) ? 1 : 0; + break; + case POWER_SUPPLY_EXT_PROP_FLASH_STATE: /* check only for MTK */ + val->intval = battery->flash_state; + break; +#if IS_ENABLED(CONFIG_MTK_CHARGER) + case POWER_SUPPLY_EXT_PROP_MTK_FG_INIT: /* check only for MTK */ + val->intval = battery->mtk_fg_init; + break; +#endif + case POWER_SUPPLY_EXT_PROP_SRCCAP: + val->intval = battery->init_src_cap; + break; +#if IS_ENABLED(CONFIG_MTK_CHARGER) + case POWER_SUPPLY_EXT_PROP_RP_LEVEL: + val->intval = battery->sink_status.rp_currentlvl; + break; +#endif + case POWER_SUPPLY_EXT_PROP_LRP_CHG_SRC: + val->intval = battery->lrp_chg_src; + break; + case POWER_SUPPLY_EXT_PROP_MISC_EVENT: + val->intval = battery->misc_event; + break; + case POWER_SUPPLY_EXT_PROP_MST_EN: + val->intval = battery->mst_en; + break; + case POWER_SUPPLY_EXT_PROP_DIRECT_DONE: +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_DIRECT_DONE, value); + val->intval = value.intval; +#else + val->intval = 0; +#endif + break; + case POWER_SUPPLY_EXT_PROP_FPDO_DC_THERMAL_CHECK: + pr_info("%s: FPDO_DC, Tbat(%d), chg_limit(%d), lrp_limit(%d), siop(%d), tz(%d), pdo(%d)\n", + __func__, battery->temperature, battery->chg_limit, battery->lrp_limit, + battery->siop_level, battery->thermal_zone, battery->sink_status.current_pdo_num); + if (battery->chg_limit || battery->lrp_limit || battery->siop_level < 80 || + battery->thermal_zone != BAT_THERMAL_NORMAL || + battery->sink_status.current_pdo_num < 2 || + battery->temperature <= battery->pdata->wire_cool1_normal_thresh || + battery->temperature >= battery->pdata->wire_normal_warm_thresh) + val->intval = 1; + else + val->intval = 0; + break; + case POWER_SUPPLY_EXT_PROP_ABNORMAL_TA: + if (battery->abnormal_ta && battery->charging_current <= MIN_FCC_VALUE) + val->intval = 1; + else + val->intval = 0; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + return 0; +} + +static int sec_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_battery_info *battery = power_supply_get_drvdata(psy); + + if (psp != POWER_SUPPLY_PROP_ONLINE) + return -EINVAL; + + if ((battery->health == POWER_SUPPLY_HEALTH_OVERVOLTAGE) || + (battery->health == POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE)) { + val->intval = 0; + return 0; + } + /* Set enable=1 only if the USB charger is connected */ + switch (battery->wire_status) { + case SEC_BATTERY_CABLE_USB: + case SEC_BATTERY_CABLE_USB_CDP: + val->intval = 1; + break; + case SEC_BATTERY_CABLE_PDIC: + case SEC_BATTERY_CABLE_FPDO_DC: + val->intval = (battery->pd_usb_attached) ? 1:0; + break; + default: + val->intval = 0; + break; + } + + if (is_slate_mode(battery)) + val->intval = 0; + return 0; +} + +static int sec_ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_battery_info *battery = power_supply_get_drvdata(psy); + enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; + + switch ((int)psp) { + case POWER_SUPPLY_PROP_ONLINE: + if ((battery->health == POWER_SUPPLY_HEALTH_OVERVOLTAGE) || + (battery->health == POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE)) { + val->intval = 0; + return 0; + } + + /* Set enable=1 only if the AC charger is connected */ + switch (battery->cable_type) { + case SEC_BATTERY_CABLE_TA: + case SEC_BATTERY_CABLE_UARTOFF: + case SEC_BATTERY_CABLE_LAN_HUB: + case SEC_BATTERY_CABLE_LO_TA: + case SEC_BATTERY_CABLE_UNKNOWN: + case SEC_BATTERY_CABLE_PREPARE_TA: + case SEC_BATTERY_CABLE_9V_ERR: + case SEC_BATTERY_CABLE_9V_UNKNOWN: + case SEC_BATTERY_CABLE_9V_TA: + case SEC_BATTERY_CABLE_12V_TA: + case SEC_BATTERY_CABLE_HMT_CONNECTED: + case SEC_BATTERY_CABLE_HMT_CHARGE: + case SEC_BATTERY_CABLE_HV_TA_CHG_LIMIT: + case SEC_BATTERY_CABLE_QC20: + case SEC_BATTERY_CABLE_QC30: + case SEC_BATTERY_CABLE_TIMEOUT: + case SEC_BATTERY_CABLE_SMART_OTG: + case SEC_BATTERY_CABLE_SMART_NOTG: + case SEC_BATTERY_CABLE_POGO: + case SEC_BATTERY_CABLE_POGO_9V: + case SEC_BATTERY_CABLE_PDIC_APDO: + val->intval = 1; + break; + case SEC_BATTERY_CABLE_PDIC: + case SEC_BATTERY_CABLE_FPDO_DC: + val->intval = (battery->pd_usb_attached) ? 0:1; + break; + default: + val->intval = 0; + break; + } + if (sec_bat_get_lpmode() && (battery->misc_event & BATT_MISC_EVENT_UNDEFINED_RANGE_TYPE)) + val->intval = 1; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = battery->chg_temp; + break; + case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: + switch (ext_psp) { + case POWER_SUPPLY_EXT_PROP_WATER_DETECT: + if (battery->misc_event & (BATT_MISC_EVENT_UNDEFINED_RANGE_TYPE | + BATT_MISC_EVENT_WATER_HICCUP_TYPE)) { + val->intval = 1; + pr_info("%s: Water Detect\n", __func__); + } else { + val->intval = 0; + } + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int sec_wireless_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_battery_info *battery = power_supply_get_drvdata(psy); + enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; + + switch ((int)psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = is_wireless_all_type(battery->cable_type) ? 1 : 0; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = (battery->pdata->wireless_charger_name) ? + 1 : 0; + break; + case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: + switch (ext_psp) { + case POWER_SUPPLY_EXT_PROP_POWER_DESIGN: +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + if (battery->cable_type == SEC_BATTERY_CABLE_PREPARE_WIRELESS_20) + val->intval = battery->wc20_power_class; + else +#endif + val->intval = 0; + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ERR: + val->intval = battery->tx_event; + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_RETRY_CASE: + val->intval = battery->tx_retry_case; + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_WC_STATUS: + val->intval = battery->wc_status; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int sec_wireless_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct sec_battery_info *battery = power_supply_get_drvdata(psy); + enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + union power_supply_propval value = {0, }; +#endif + + switch ((int)psp) { + case POWER_SUPPLY_PROP_ONLINE: + if (val->intval != SEC_BATTERY_CABLE_NONE && battery->wc_status == SEC_BATTERY_CABLE_NONE) { + battery->cisd.data[CISD_DATA_WIRELESS_COUNT]++; + battery->cisd.data[CISD_DATA_WIRELESS_COUNT_PER_DAY]++; + } + pr_info("%s : wireless_type(%s)\n", __func__, sb_get_ct_str(val->intval)); + + /* Clear the FOD , AUTH State */ + sec_bat_set_misc_event(battery, 0, BATT_MISC_EVENT_WIRELESS_FOD); + + battery->wc_status = val->intval; + +#if !defined(CONFIG_WIRELESS_RX_PHM_CTRL) + if ((battery->ext_event & BATT_EXT_EVENT_CALL) && + (battery->wc_status == SEC_BATTERY_CABLE_WIRELESS_PACK || + battery->wc_status == SEC_BATTERY_CABLE_WIRELESS_HV_PACK || + battery->wc_status == SEC_BATTERY_CABLE_WIRELESS_TX)) { + battery->wc_rx_phm_mode = true; + } +#endif + + if (battery->wc_status == SEC_BATTERY_CABLE_NONE) { + if (!battery->is_otg_on) + battery->wpc_vout_level = WIRELESS_VOUT_10V; + battery->wpc_max_vout_level = WIRELESS_VOUT_12_5V; + battery->auto_mode = false; + sec_bat_set_misc_event(battery, 0, + (BATT_MISC_EVENT_WIRELESS_DET_LEVEL | /* clear wpc_det level status */ + BATT_MISC_EVENT_WIRELESS_AUTH_START | + BATT_MISC_EVENT_WIRELESS_AUTH_RECVED | + BATT_MISC_EVENT_WIRELESS_AUTH_FAIL | + BATT_MISC_EVENT_WIRELESS_AUTH_PASS | + BATT_MISC_EVENT_WC_JIG_PAD)); + change_sec_voter_pri(battery->input_vote, VOTER_WPC_CUR, VOTE_PRI_0); + } else if (!is_wireless_fake_type(battery->wc_status)) { + sec_bat_set_misc_event(battery, BATT_MISC_EVENT_WIRELESS_DET_LEVEL, /* set wpc_det level status */ + BATT_MISC_EVENT_WIRELESS_DET_LEVEL); + + if (battery->wc_status == SEC_BATTERY_CABLE_HV_WIRELESS_20) { + sec_bat_set_misc_event(battery, BATT_MISC_EVENT_WIRELESS_AUTH_PASS, + BATT_MISC_EVENT_WIRELESS_AUTH_PASS); + if (battery->wc_status == SEC_BATTERY_CABLE_HV_WIRELESS_20) + battery->cisd.cable_data[CISD_CABLE_HV_WC_20]++; + } + } + + __pm_stay_awake(battery->cable_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->cable_work, 0); + + if (battery->wc_status == SEC_BATTERY_CABLE_NONE || + battery->wc_status == SEC_BATTERY_CABLE_WIRELESS_PACK || + battery->wc_status == SEC_BATTERY_CABLE_WIRELESS_HV_PACK || + battery->wc_status == SEC_BATTERY_CABLE_WIRELESS_VEHICLE) { + sec_bat_set_misc_event(battery, + (battery->wc_status == SEC_BATTERY_CABLE_NONE ? + 0 : BATT_MISC_EVENT_WIRELESS_BACKPACK_TYPE), + BATT_MISC_EVENT_WIRELESS_BACKPACK_TYPE); + } + break; + case POWER_SUPPLY_PROP_AUTHENTIC: + pr_info("%s : tx_type(0x%x)\n", __func__, val->intval); + count_cisd_pad_data(&battery->cisd, val->intval); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + sec_vote(battery->input_vote, VOTER_AICL, false, 0); + pr_info("%s: reset aicl\n", __func__); + break; + case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: + switch (ext_psp) { +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + case POWER_SUPPLY_EXT_PROP_WIRELESS_ERR: + if (is_wireless_type(battery->cable_type)) + sec_bat_set_misc_event(battery, val->intval ? BATT_MISC_EVENT_WIRELESS_FOD : 0, + BATT_MISC_EVENT_WIRELESS_FOD); + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ERR: + if (val->intval & BATT_TX_EVENT_WIRELESS_TX_MISALIGN) { + if (battery->tx_event & BATT_TX_EVENT_WIRELESS_TX_RETRY) + sec_bat_handle_tx_misalign(battery, false); + else + sec_bat_handle_tx_misalign(battery, true); + } else if (val->intval & BATT_TX_EVENT_WIRELESS_TX_OCP) { + if (battery->tx_event & BATT_TX_EVENT_WIRELESS_TX_RETRY) + sec_bat_handle_tx_ocp(battery, false); + else + sec_bat_handle_tx_ocp(battery, true); + } else if (val->intval & BATT_TX_EVENT_WIRELESS_TX_AC_MISSING) { + if (battery->wc_tx_enable) + sec_wireless_set_tx_enable(battery, false); + battery->tx_retry_case |= SEC_BAT_TX_RETRY_AC_MISSING; + /* clear tx all event */ + sec_bat_set_tx_event(battery, 0, BATT_TX_EVENT_WIRELESS_ALL_MASK); + sec_bat_set_tx_event(battery, + BATT_TX_EVENT_WIRELESS_TX_RETRY, BATT_TX_EVENT_WIRELESS_TX_RETRY); + } else if (val->intval & BATT_TX_EVENT_WIRELESS_TX_RETRY) { + sec_wireless_set_tx_enable(battery, false); + /* clear tx all event */ + sec_bat_set_tx_event(battery, 0, BATT_TX_EVENT_WIRELESS_ALL_MASK); + sec_bat_set_tx_event(battery, + BATT_TX_EVENT_WIRELESS_TX_RETRY, BATT_TX_EVENT_WIRELESS_TX_RETRY); + } else { + sec_wireless_set_tx_enable(battery, false); + sec_bat_set_tx_event(battery, val->intval, val->intval); + } + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_RX_CONNECTED: + sec_bat_set_tx_event(battery, val->intval ? BATT_TX_EVENT_WIRELESS_RX_CONNECT : 0, + BATT_TX_EVENT_WIRELESS_RX_CONNECT); + battery->wc_rx_connected = val->intval; + battery->wc_tx_phm_mode = false; + battery->prev_tx_phm_mode = false; + + if (!val->intval) { + battery->wc_rx_type = NO_DEV; + + battery->tx_switch_mode = TX_SWITCH_MODE_OFF; + battery->tx_switch_mode_change = false; + battery->tx_switch_start_soc = 0; + + sec_bat_run_wpc_tx_work(battery, 0); + } + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_RX_TYPE: + battery->wc_rx_type = val->intval; + if (battery->wc_rx_type) { + if (battery->wc_rx_type == SS_BUDS) { + battery->cisd.tx_data[SS_PHONE]--; + } + battery->cisd.tx_data[battery->wc_rx_type]++; + } + pr_info("@Tx_Mode %s : RX_TYPE=%d\n", __func__, battery->wc_rx_type); + sec_bat_run_wpc_tx_work(battery, 0); + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_AUTH_ADT_STATUS: + if (val->intval == WIRELESS_AUTH_START) + sec_bat_set_misc_event(battery, BATT_MISC_EVENT_WIRELESS_AUTH_START, BATT_MISC_EVENT_WIRELESS_AUTH_START); + else if (val->intval == WIRELESS_AUTH_RECEIVED) + sec_bat_set_misc_event(battery, BATT_MISC_EVENT_WIRELESS_AUTH_RECVED, BATT_MISC_EVENT_WIRELESS_AUTH_RECVED); + else if (val->intval == WIRELESS_AUTH_SENT) /* need to be clear this value when data is sent */ + sec_bat_set_misc_event(battery, 0, BATT_MISC_EVENT_WIRELESS_AUTH_START | BATT_MISC_EVENT_WIRELESS_AUTH_RECVED); + else if (val->intval == WIRELESS_AUTH_FAIL) + sec_bat_set_misc_event(battery, BATT_MISC_EVENT_WIRELESS_AUTH_FAIL, BATT_MISC_EVENT_WIRELESS_AUTH_FAIL); + break; + case POWER_SUPPLY_EXT_PROP_CALL_EVENT: +#if !defined(CONFIG_WIRELESS_RX_PHM_CTRL) + if (val->intval == 1) { + pr_info("%s : PHM enabled\n", __func__); + battery->wc_rx_phm_mode = true; + } +#endif + break; + case POWER_SUPPLY_EXT_PROP_RX_PHM: +#if defined(CONFIG_WIRELESS_RX_PHM_CTRL) + pr_info("%s : PHM %d\n", __func__, val->intval); + battery->wc_rx_pdetb_mode = val->intval; +#endif + break; + case POWER_SUPPLY_EXT_PROP_GEAR_PHM_EVENT: + battery->wc_tx_phm_mode = val->intval; + pr_info("@Tx_Mode %s : tx phm status(%d)\n", __func__, val->intval); + sec_bat_run_wpc_tx_work(battery, 0); + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_RX_POWER: + pr_info("@MPP @EPP %s : rx power %d\n", __func__, val->intval); + battery->wc20_rx_power = val->intval; + + __pm_stay_awake(battery->wc20_current_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->wc20_current_work, + msecs_to_jiffies(0)); + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_MAX_VOUT: + { + unsigned int vout; + + pr_info("@MPP @EPP %s: max vout(%s)\n", __func__, sb_vout_ctr_mode_str(val->intval)); + + vout = get_wc20_vout(val->intval); + if (vout) { + battery->wpc_max_vout_level = val->intval; + battery->wc20_vout = vout; + } + } + break; + case POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL: + sec_wireless_otg_vout_control(battery, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_CHARGE_OTG_ICL_CONTROL: + sec_wireless_otg_icl_control(battery); + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_JIG_PAD: + if (sec_bat_get_lpmode()) { + sec_bat_set_misc_event(battery, + BATT_MISC_EVENT_WC_JIG_PAD, BATT_MISC_EVENT_WC_JIG_PAD); + } + break; + case POWER_SUPPLY_EXT_PROP_MPP_CLOAK: + pr_info("@MPP %s: set MPP_CLOAK(%d)\n", __func__, val->intval); + value.intval = val->intval; + + if (val->intval == 1) { // Cloak Enable. + int icl = get_sec_vote_result(battery->input_vote); + if (icl > 400) { + union power_supply_propval value = {0, }; + + value.intval = 1; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_MPP_ICL_CTRL, value); + break; + } + + sec_bat_mfc_ldo_cntl(battery, MFC_LDO_OFF); + sec_vote(battery->chgen_vote, VOTER_WC_TX, true, SEC_BAT_CHG_MODE_BUCK_OFF); + psy_do_property(battery->pdata->wireless_charger_name, set, + psp, value); + sec_vote(battery->input_vote, VOTER_WPC_CUR, true, + battery->pdata->default_mpp_input_current); +#if 0 + } else if (val->intval == 0) { // Cloak Disable - send cmd + psy_do_property(battery->pdata->wireless_charger_name, set, + psp, value); +#endif + } else { // Cloak Disalbe - off + psy_do_property(battery->pdata->wireless_charger_name, set, + psp, value); + sec_vote(battery->chgen_vote, VOTER_WC_TX, false, 0); + sec_bat_mfc_ldo_cntl(battery, MFC_LDO_ON); + } + break; + case POWER_SUPPLY_EXT_PROP_MPP_ICL_CTRL: + { + int icl = get_sec_vote_result(battery->input_vote); + + if (val->intval) { + int max_icl = + (battery->pdata->charging_current[battery->cable_type].input_current_limit - battery->wpc_temp_v2_offset); + + icl = (icl + 100) > max_icl ? max_icl : (icl + 100); + if (icl == max_icl) { + union power_supply_propval value = {0, }; + + value.intval = 0; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_MPP_INC_INT_CTRL, value); + } + pr_info("@MPP %s: Increase ICL(%d)\n", __func__, icl); + } else { + int prev_icl = icl; + + icl = (icl - 100) < 100 ? 100 : (icl - 100); + if ((prev_icl > 400) && (icl <= 400)) { + union power_supply_propval value = {0, }; + //int cloak_status = 0; + + icl = 400; + value.intval = 0; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_MPP_ICL_CTRL, value); +#if 0 + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_MPP_CLOAK, value); + cloak_status = value.intval; + + if (cloak_status) { + value.intval = 1; + psy_do_property("wireless", set, + POWER_SUPPLY_EXT_PROP_MPP_CLOAK, value); + } +#endif + } + pr_info("@MPP %s: Decrase ICL(%d)\n", __func__, icl); + } + sec_vote(battery->input_vote, VOTER_WPC_CUR, true, icl); + } + break; +#endif + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static void sec_wireless_test_print_sgf_data(const char *buf, int count) +{ + char temp_buf[1024] = {0,}; + int i, size = 0; + + snprintf(temp_buf, sizeof(temp_buf), "0x%x", buf[0]); + size = sizeof(temp_buf) - strlen(temp_buf); + + for (i = 1; i < count; i++) { + snprintf(temp_buf+strlen(temp_buf), size, " 0x%x", buf[i]); + size = sizeof(temp_buf) - strlen(temp_buf); + } + pr_info("%s: %s\n", __func__, temp_buf); +} + +static ssize_t sec_wireless_sgf_show_attr(struct device *dev, + struct device_attribute *attr, char *buf) +{ +/* + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_battery_info *battery = power_supply_get_drvdata(psy); +*/ + return -EINVAL; +} + +static ssize_t sec_wireless_sgf_store_attr(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_battery_info *battery = power_supply_get_drvdata(psy); + union power_supply_propval value = {0, }; + + if (count < 4) { + pr_err("%s: invalid data\n", __func__); + return -EINVAL; + } + + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ID, value); + pr_info("%s!!!(cable_type = %d, tx_id = %d, count = %ld)\n", + __func__, battery->cable_type, value.intval, count); + sec_wireless_test_print_sgf_data(buf, (int)count); + + if (is_wireless_type(battery->cable_type) && value.intval == 0xEF) { + value.strval = buf; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_SGF, value); + } + + return count; +} + +static DEVICE_ATTR(sgf, 0664, sec_wireless_sgf_show_attr, sec_wireless_sgf_store_attr); + +static int sec_pogo_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ +#if defined(CONFIG_USE_POGO) + struct sec_battery_info *battery = power_supply_get_drvdata(psy); +#endif + + if (psp != POWER_SUPPLY_PROP_ONLINE) + return -EINVAL; + + val->intval = 0; +#if defined(CONFIG_USE_POGO) + val->intval = battery->pogo_status; + pr_info("%s: POGO online : %d\n", __func__, val->intval); +#endif + + return 0; +} + +static int sec_pogo_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ +#if defined(CONFIG_USE_POGO) + struct sec_battery_info *battery = power_supply_get_drvdata(psy); +#endif + + switch ((int)psp) { + case POWER_SUPPLY_PROP_ONLINE: +#if defined(CONFIG_USE_POGO) + battery->pogo_status = (val->intval) ? 1 : 0; + battery->pogo_9v = (val->intval > 1) ? true : false; + + if (battery->pogo_status && battery->pdata->pogo_chgin) { + battery->muic_cable_type = ATTACHED_DEV_POGO_DOCK_MUIC; + sec_bat_set_misc_event(battery, 0, BATT_MISC_EVENT_TIMEOUT_OPEN_TYPE); + } + + __pm_stay_awake(battery->cable_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->cable_work, 0); + pr_info("%s: pogo_status : %d\n", __func__, battery->pogo_status); +#endif + break; + default: + return -EINVAL; + } + + return 0; +} + +static int sec_otg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_battery_info *battery = power_supply_get_drvdata(psy); + union power_supply_propval value = {0,}; + int ret = 0; + + value.intval = val->intval; + ret = psy_do_property(battery->pdata->otg_name, get, psp, value); + val->intval = value.intval; + + return ret; +} + +static int sec_otg_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct sec_battery_info *battery = power_supply_get_drvdata(psy); + union power_supply_propval value = {0,}; + int ret = 0; + + value.intval = val->intval; + + if ((int)psp == POWER_SUPPLY_PROP_ONLINE) { + battery->is_otg_on = val->intval; + if (value.intval && !battery->otg_check) { + battery->cisd.event_data[EVENT_OTG]++; + battery->otg_check = true; + } else if (!value.intval) + battery->otg_check = false; + } + + ret = psy_do_property(battery->pdata->otg_name, set, psp, value); + return ret; +} + +static void sec_otg_external_power_changed(struct power_supply *psy) +{ + power_supply_changed(psy); +} + +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) || IS_ENABLED(CONFIG_MUIC_NOTIFIER) +__visible_for_testing int sec_bat_cable_check(struct sec_battery_info *battery, + muic_attached_dev_t attached_dev) +{ + int current_cable_type = -1; + union power_supply_propval val = {0, }; +#if IS_ENABLED(CONFIG_SBP_FG) + int batt_present = 1; + int ret; + + ret = psy_do_property(battery->pdata->charger_name, get, POWER_SUPPLY_PROP_PRESENT, val); + if (ret >= 0) { + batt_present = val.intval; + pr_info("%s: batt_present(%d)\n", __func__, batt_present); + } +#endif + + pr_info("[%s]ATTACHED(%d)\n", __func__, attached_dev); + + switch (attached_dev) + { + case ATTACHED_DEV_JIG_UART_OFF_MUIC: + case ATTACHED_DEV_JIG_UART_ON_MUIC: + battery->is_jig_on = true; + battery->skip_cisd = true; + current_cable_type = SEC_BATTERY_CABLE_NONE; + break; + case ATTACHED_DEV_SMARTDOCK_MUIC: + case ATTACHED_DEV_DESKDOCK_MUIC: + case ATTACHED_DEV_JIG_USB_ON_MUIC: + case ATTACHED_DEV_JIG_USB_OFF_MUIC: + current_cable_type = SEC_BATTERY_CABLE_NONE; + break; + case ATTACHED_DEV_UNDEFINED_CHARGING_MUIC: + case ATTACHED_DEV_UNDEFINED_RANGE_MUIC: + current_cable_type = SEC_BATTERY_CABLE_NONE; + break; + case ATTACHED_DEV_HICCUP_MUIC: + current_cable_type = SEC_BATTERY_CABLE_NONE; + break; + case ATTACHED_DEV_OTG_MUIC: + case ATTACHED_DEV_JIG_UART_OFF_VB_OTG_MUIC: + case ATTACHED_DEV_HMT_MUIC: + current_cable_type = SEC_BATTERY_CABLE_OTG; + break; + case ATTACHED_DEV_TIMEOUT_OPEN_MUIC: + case ATTACHED_DEV_RETRY_TIMEOUT_OPEN_MUIC: + current_cable_type = SEC_BATTERY_CABLE_TIMEOUT; + break; + case ATTACHED_DEV_USB_MUIC: + case ATTACHED_DEV_SMARTDOCK_USB_MUIC: + case ATTACHED_DEV_UNOFFICIAL_ID_USB_MUIC: + current_cable_type = SEC_BATTERY_CABLE_USB; + break; + case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC: +#if defined(CONFIG_LSI_IFPMIC) + current_cable_type = SEC_BATTERY_CABLE_NONE; + break; +#endif + case ATTACHED_DEV_JIG_UART_ON_VB_MUIC: + case ATTACHED_DEV_JIG_UART_OFF_VB_FG_MUIC: + current_cable_type = SEC_BATTERY_CABLE_UARTOFF; + if (sec_bat_get_facmode()) + current_cable_type = SEC_BATTERY_CABLE_NONE; + break; + case ATTACHED_DEV_RDU_TA_MUIC: + battery->store_mode = true; + __pm_stay_awake(battery->parse_mode_dt_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->parse_mode_dt_work, 0); + current_cable_type = SEC_BATTERY_CABLE_TA; + break; + case ATTACHED_DEV_TA_MUIC: +#if defined(CONFIG_BC12_DEVICE) + if (battery->pdata->bc12_ifcon_wa && !battery->sink_status.rp_currentlvl) { + pr_err("%s: IFCON_WA, wait rp_currentlvl\n", __func__); + sec_vote(battery->input_vote, VOTER_IFCON_WA, true, + battery->pdata->default_usb_input_current); + } +#endif + case ATTACHED_DEV_CARDOCK_MUIC: + case ATTACHED_DEV_DESKDOCK_VB_MUIC: + case ATTACHED_DEV_SMARTDOCK_TA_MUIC: + case ATTACHED_DEV_UNOFFICIAL_TA_MUIC: + case ATTACHED_DEV_UNOFFICIAL_ID_TA_MUIC: + case ATTACHED_DEV_UNOFFICIAL_ID_ANY_MUIC: + case ATTACHED_DEV_UNSUPPORTED_ID_VB_MUIC: + case ATTACHED_DEV_AFC_CHARGER_DISABLED_MUIC: + current_cable_type = SEC_BATTERY_CABLE_TA; + break; + case ATTACHED_DEV_AFC_CHARGER_5V_MUIC: + case ATTACHED_DEV_RETRY_AFC_CHARGER_5V_MUIC: + case ATTACHED_DEV_QC_CHARGER_5V_MUIC: + case ATTACHED_DEV_AFC_CHARGER_5V_DUPLI_MUIC: + current_cable_type = SEC_BATTERY_CABLE_HV_TA_CHG_LIMIT; + break; + case ATTACHED_DEV_CDP_MUIC: + case ATTACHED_DEV_UNOFFICIAL_ID_CDP_MUIC: + current_cable_type = SEC_BATTERY_CABLE_USB_CDP; + break; + case ATTACHED_DEV_USB_LANHUB_MUIC: + current_cable_type = SEC_BATTERY_CABLE_LAN_HUB; + break; + case ATTACHED_DEV_LO_TA_MUIC: + current_cable_type = SEC_BATTERY_CABLE_LO_TA; + break; + case ATTACHED_DEV_CHARGING_CABLE_MUIC: + current_cable_type = SEC_BATTERY_CABLE_POWER_SHARING; + break; + case ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC: + case ATTACHED_DEV_QC_CHARGER_PREPARE_MUIC: + current_cable_type = SEC_BATTERY_CABLE_PREPARE_TA; + break; + case ATTACHED_DEV_QC_CHARGER_9V_MUIC: + current_cable_type = SEC_BATTERY_CABLE_9V_TA; + if ((battery->cable_type == SEC_BATTERY_CABLE_TA) || + (battery->cable_type == SEC_BATTERY_CABLE_NONE)) + battery->cisd.cable_data[CISD_CABLE_QC]++; + break; + case ATTACHED_DEV_AFC_CHARGER_9V_MUIC: + case ATTACHED_DEV_RETRY_AFC_CHARGER_9V_MUIC: + case ATTACHED_DEV_AFC_CHARGER_9V_DUPLI_MUIC: + current_cable_type = SEC_BATTERY_CABLE_9V_TA; + if ((battery->cable_type == SEC_BATTERY_CABLE_TA) || + (battery->cable_type == SEC_BATTERY_CABLE_NONE)) + battery->cisd.cable_data[CISD_CABLE_AFC]++; + break; +#if defined(CONFIG_MUIC_HV_12V) + case ATTACHED_DEV_AFC_CHARGER_12V_MUIC: + case ATTACHED_DEV_AFC_CHARGER_12V_DUPLI_MUIC: + current_cable_type = SEC_BATTERY_CABLE_12V_TA; + break; +#endif + case ATTACHED_DEV_AFC_CHARGER_ERR_V_MUIC: + case ATTACHED_DEV_AFC_CHARGER_ERR_V_DUPLI_MUIC: + battery->cisd.cable_data[CISD_CABLE_AFC_FAIL]++; + break; + case ATTACHED_DEV_QC_CHARGER_ERR_V_MUIC: + battery->cisd.cable_data[CISD_CABLE_QC_FAIL]++; + break; + case ATTACHED_DEV_HV_ID_ERR_UNDEFINED_MUIC: + case ATTACHED_DEV_HV_ID_ERR_UNSUPPORTED_MUIC: + case ATTACHED_DEV_HV_ID_ERR_SUPPORTED_MUIC: + current_cable_type = SEC_BATTERY_CABLE_9V_UNKNOWN; + break; + case ATTACHED_DEV_VZW_INCOMPATIBLE_MUIC: + current_cable_type = SEC_BATTERY_CABLE_UNKNOWN; + break; + default: + pr_err("%s: invalid type for charger:%d\n", + __func__, attached_dev); + break; + } + +#if IS_ENABLED(CONFIG_SBP_FG) + switch (attached_dev) { + case ATTACHED_DEV_JIG_UART_OFF_MUIC: + case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC: + case ATTACHED_DEV_JIG_UART_OFF_VB_FG_MUIC: + if (batt_present == 0) + psy_do_property(battery->pdata->fuelgauge_name, set, POWER_SUPPLY_EXT_PROP_FAKE_SOC, val); + break; + default: + break; + } +#endif + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) && IS_ENABLED(CONFIG_LIMITER_S2ASL01) && defined(CONFIG_SEC_FACTORY) + if (!sec_bat_check_by_gpio(battery)) { + if (attached_dev == ATTACHED_DEV_JIG_UART_OFF_MUIC || + attached_dev == ATTACHED_DEV_JIG_USB_ON_MUIC) { + pr_info("%s No Main or Sub Battery, 301k or 523k with FACTORY\n", __func__); + gpio_direction_output(battery->pdata->sub_bat_enb_gpio, 1); + } + } else { + if (attached_dev == ATTACHED_DEV_JIG_UART_OFF_MUIC || + attached_dev == ATTACHED_DEV_JIG_UART_ON_MUIC || + attached_dev == ATTACHED_DEV_JIG_USB_ON_MUIC) { + pr_info("%s 301k or 523k or 619k with FACTORY\n", __func__); + gpio_direction_output(battery->pdata->sub_bat_enb_gpio, 0); + } + } +#endif + + if (battery->is_jig_on && !battery->pdata->support_fgsrc_change) + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_PROP_ENERGY_NOW, val); + +#if defined(CONFIG_LSI_IFPMIC) + switch (attached_dev) { + case ATTACHED_DEV_JIG_USB_ON_MUIC: + case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC: + val.intval = attached_dev; + if (!battery->factory_mode_boot_on) + factory_mode = 1; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_PROP_ENERGY_NOW, val); + pr_err("%s : FACTORY MODE TEST! (%d, %d)\n", __func__, val.intval, + battery->factory_mode_boot_on); + break; +#if IS_ENABLED(CONFIG_CHARGER_S2MF301) + case ATTACHED_DEV_JIG_USB_OFF_MUIC: + val.intval = attached_dev; + if (!battery->factory_mode_boot_on) + factory_mode = 0; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_PROP_ENERGY_NOW, val); + pr_err("%s : FACTORY MODE TEST! (%d, %d)\n", __func__, val.intval, + battery->factory_mode_boot_on); + break; +#endif +#if defined(CONFIG_SIDO_OVP) + case ATTACHED_DEV_JIG_UART_ON_MUIC: +#endif + case ATTACHED_DEV_JIG_UART_ON_VB_MUIC: + val.intval = 0; + if (!battery->factory_mode_boot_on) + factory_mode = 0; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_PROP_ENERGY_NOW, val); + pr_err("%s : FACTORY MODE TEST! (%d, %d)\n", __func__, val.intval, + battery->factory_mode_boot_on); + break; + case ATTACHED_DEV_JIG_UART_OFF_MUIC: + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_ENABLE_HW_FACTORY_MODE, val); + pr_err("%s : HW FACTORY MODE ENABLE TEST! (%d)\n", __func__, val.intval); + break; + default: + break; + } +#endif + + return current_cable_type; +} +EXPORT_SYMBOL_KUNIT(sec_bat_cable_check); +#endif + +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) +__visible_for_testing void sec_bat_set_rp_current(struct sec_battery_info *battery, int cable_type) +{ + int icl = 0; + int fcc = 0; + +#if IS_ENABLED(CONFIG_USB_FACTORY_MODE) && defined(CONFIG_SEC_FACTORY) + sec_bat_usb_factory_set_vote(battery, false); +#endif + + switch (battery->sink_status.rp_currentlvl) { + case RP_CURRENT_ABNORMAL: + icl = battery->pdata->rp_current_abnormal_rp3; + fcc = battery->pdata->rp_current_abnormal_rp3; + sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_AFC); + break; + case RP_CURRENT_LEVEL3: +#if defined(CONFIG_PD_CHARGER_HV_DISABLE) + if (battery->current_event & SEC_BAT_CURRENT_EVENT_NOPD_HV_DISABLE) { +#else + if (battery->current_event & SEC_BAT_CURRENT_EVENT_HV_DISABLE) { +#endif + icl = battery->pdata->default_input_current; + fcc = battery->pdata->default_charging_current; + } else { + if (battery->store_mode) { + icl = battery->pdata->rp_current_rdu_rp3; + fcc = battery->pdata->max_charging_current; + } else { + icl = battery->pdata->rp_current_rp3; + fcc = battery->pdata->max_charging_current; + } + } + sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_AFC); + break; + case RP_CURRENT_LEVEL2: + icl = battery->pdata->rp_current_rp2; + fcc = battery->pdata->rp_current_rp2; + sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_AFC); + break; + case RP_CURRENT_LEVEL_DEFAULT: + if (cable_type == SEC_BATTERY_CABLE_USB) { + if (battery->current_event & SEC_BAT_CURRENT_EVENT_USB_SUPER) { + icl = USB_CURRENT_SUPER_SPEED; + fcc = USB_CURRENT_SUPER_SPEED; + } else { + icl = battery->pdata->default_usb_input_current; + fcc = battery->pdata->default_usb_charging_current; + } + } else if (cable_type == SEC_BATTERY_CABLE_TA) { + icl = battery->pdata->default_input_current; + fcc = battery->pdata->default_charging_current; + } else { + icl = battery->pdata->default_usb_input_current; + fcc = battery->pdata->default_usb_charging_current; + pr_err("%s: wrong cable type(%d)\n", __func__, cable_type); + } + break; + default: + icl = battery->pdata->default_usb_input_current; + fcc = battery->pdata->default_usb_charging_current; + pr_err("%s: undefined rp_currentlvl(%d)\n", __func__, battery->sink_status.rp_currentlvl); + break; + } + + sec_bat_change_default_current(battery, cable_type, icl, fcc); + + pr_info("%s:(%d)\n", __func__, battery->sink_status.rp_currentlvl); + battery->max_charge_power = 0; + sec_bat_check_input_voltage(battery, cable_type); + +#if defined(CONFIG_BC12_DEVICE) + if (battery->pdata->bc12_ifcon_wa) { + if (battery->sink_status.rp_currentlvl) + sec_vote(battery->input_vote, VOTER_IFCON_WA, false, 0); + else + pr_err("%s: IFCON_WA keeps 500mA ICL, rp_currentlvl none\n", __func__); + } +#endif + sec_vote(battery->input_vote, VOTER_AICL, false, 0); +} +EXPORT_SYMBOL_KUNIT(sec_bat_set_rp_current); + +static int usb_typec_handle_id_attach(struct sec_battery_info *battery, PD_NOTI_ATTACH_TYPEDEF *pdata, int *cable_type, const char **cmd) +{ + struct pdic_notifier_struct *pd_noti = pdata->pd; + SEC_PD_SINK_STATUS *psink_status = NULL; + + if (pd_noti) + psink_status = &pd_noti->sink_status; + + switch (pdata->attach) { + case MUIC_NOTIFY_CMD_DETACH: + case MUIC_NOTIFY_CMD_LOGICALLY_DETACH: + *cmd = "DETACH"; + battery->is_jig_on = false; + battery->pd_usb_attached = false; + *cable_type = SEC_BATTERY_CABLE_NONE; + battery->muic_cable_type = ATTACHED_DEV_NONE_MUIC; + battery->sink_status.rp_currentlvl = RP_CURRENT_LEVEL_NONE; +#if IS_ENABLED(CONFIG_USB_FACTORY_MODE) + sec_bat_usb_factory_clear(battery); +#endif + break; + case MUIC_NOTIFY_CMD_ATTACH: + case MUIC_NOTIFY_CMD_LOGICALLY_ATTACH: + /* Skip notify from MUIC if PDIC is attached already */ + if (is_pd_wire_type(battery->wire_status) || battery->init_src_cap) { + if (sec_bat_get_lpmode() || + (battery->usb_conn_status == USB_CONN_NORMAL && + !(battery->misc_event & BATT_MISC_EVENT_TEMP_HICCUP_TYPE))) { + return -1; /* skip usb_typec_handle_after_id() */ + } + } + *cmd = "ATTACH"; + battery->muic_cable_type = pdata->cable_type; + *cable_type = sec_bat_cable_check(battery, battery->muic_cable_type); + if (battery->cable_type != *cable_type && + battery->sink_status.rp_currentlvl >= RP_CURRENT_LEVEL_DEFAULT && + (*cable_type == SEC_BATTERY_CABLE_USB || *cable_type == SEC_BATTERY_CABLE_TA)) { + sec_bat_set_rp_current(battery, *cable_type); + } else if (psink_status && + pd_noti->event == PDIC_NOTIFY_EVENT_PDIC_ATTACH && + psink_status->rp_currentlvl >= RP_CURRENT_LEVEL_DEFAULT && + (*cable_type == SEC_BATTERY_CABLE_USB || *cable_type == SEC_BATTERY_CABLE_TA)) { + battery->sink_status.rp_currentlvl = psink_status->rp_currentlvl; + sec_bat_set_rp_current(battery, *cable_type); + } + break; + default: + *cmd = "ERROR"; + *cable_type = -1; + battery->muic_cable_type = pdata->cable_type; + break; + } + battery->pdic_attach = false; + battery->pdic_ps_rdy = false; + battery->init_src_cap = false; + if (battery->muic_cable_type == ATTACHED_DEV_QC_CHARGER_9V_MUIC || + battery->muic_cable_type == ATTACHED_DEV_QC_CHARGER_ERR_V_MUIC) + battery->hv_chg_name = "QC"; + else if (battery->muic_cable_type == ATTACHED_DEV_AFC_CHARGER_9V_MUIC || + battery->muic_cable_type == ATTACHED_DEV_AFC_CHARGER_9V_DUPLI_MUIC || + battery->muic_cable_type == ATTACHED_DEV_AFC_CHARGER_ERR_V_MUIC || + battery->muic_cable_type == ATTACHED_DEV_AFC_CHARGER_ERR_V_DUPLI_MUIC) + battery->hv_chg_name = "AFC"; +#if defined(CONFIG_MUIC_HV_12V) + else if (battery->muic_cable_type == ATTACHED_DEV_AFC_CHARGER_12V_MUIC || + battery->muic_cable_type == ATTACHED_DEV_AFC_CHARGER_12V_DUPLI_MUIC) + battery->hv_chg_name = "12V"; +#endif + else + battery->hv_chg_name = "NONE"; + + dev_info(battery->dev, "%s: cable_type:%d, muic_cable_type:%d\n", + __func__, *cable_type, battery->muic_cable_type); + + return 0; +} + +static void sb_disable_reverse_boost(struct sec_battery_info *battery) +{ + union power_supply_propval value = { 0, }; + + if (battery->pdata->d2d_check_type == SB_D2D_SNKONLY) + return; + + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_D2D_REVERSE_VOLTAGE, value); + battery->vpdo_src_boost = value.intval ? true : false; + + if (battery->vpdo_src_boost) { + /* turn off dc reverse boost */ + value.intval = 0; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_D2D_REVERSE_VOLTAGE, value); + } +} + +static int usb_typec_handle_id_power_status(struct sec_battery_info *battery, + PD_NOTI_POWER_STATUS_TYPEDEF *pdata, int *cable_type, const char **cmd) +{ + int pdata_fpdo_max_power = battery->current_event & SEC_BAT_CURRENT_EVENT_HV_DISABLE ? + battery->pdata->nv_charge_power : battery->pdata->pd_charging_charge_power; + int pdata_apdo_max_power = battery->current_event & SEC_BAT_CURRENT_EVENT_HV_DISABLE ? + battery->pdata->nv_charge_power : battery->pdata->max_charging_charge_power; + int max_power = 0, apdo_max_power = 0, fpdo_max_power = 0; + int i = 0, current_pdo = 0, num_pd_list = 0; + bool bPdIndexChanged = false, bPrintPDlog = true; + union power_supply_propval value = {0, }; + struct pdic_notifier_struct *pd_noti = pdata->pd; + SEC_PD_SINK_STATUS *psink_status = NULL; + + if (pd_noti) + psink_status = &pd_noti->sink_status; + + if (!psink_status) { + dev_err(battery->dev, "%s: sink_status is NULL\n", __func__); + return -1; /* skip usb_typec_handle_after_id() */ + } + + dev_info(battery->dev, "%s: pd_event(%d)\n", __func__, pd_noti->event); + +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) + if (pd_noti->event != PDIC_NOTIFY_EVENT_DETACH && + pd_noti->event != PDIC_NOTIFY_EVENT_PD_PRSWAP_SNKTOSRC) { + if (!sec_bat_get_lpmode() && (battery->usb_conn_status || + (battery->misc_event & BATT_MISC_EVENT_TEMP_HICCUP_TYPE))) { + return 0; + } + } +#endif + + switch (pd_noti->event) { + case PDIC_NOTIFY_EVENT_DETACH: + dev_info(battery->dev, "%s: skip pd operation - attach(%d)\n", __func__, pdata->attach); + battery->pdic_attach = false; + battery->pdic_ps_rdy = false; + battery->init_src_cap = false; + battery->hv_pdo = false; + battery->pd_list.now_isApdo = false; + return -1; /* usb_typec_handle_after_id() */ + break; + case PDIC_NOTIFY_EVENT_PDIC_ATTACH: + battery->sink_status.rp_currentlvl = psink_status->rp_currentlvl; + dev_info(battery->dev, "%s: battery->rp_currentlvl(%d)\n", + __func__, battery->sink_status.rp_currentlvl); + if (battery->pdata->support_usb_conn_check && + battery->wire_status == SEC_BATTERY_CABLE_NONE && !delayed_work_pending(&battery->usb_conn_check_work) && + !battery->run_usb_conn_check) { +#if !defined(CONFIG_SEC_FACTORY) + battery->run_usb_conn_check = true; + battery->usb_conn_check_cnt = 0; + __pm_stay_awake(battery->usb_conn_check_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->usb_conn_check_work, msecs_to_jiffies(1000)); +#endif + } else if (battery->wire_status == SEC_BATTERY_CABLE_USB || battery->wire_status == SEC_BATTERY_CABLE_TA) { + *cable_type = battery->wire_status; + battery->chg_limit = false; + battery->lrp_limit = false; + battery->lrp_step = LRP_NONE; + sec_bat_set_rp_current(battery, *cable_type); + return 0; + } + return -1; /* skip usb_typec_handle_after_id() */ + break; + case PDIC_NOTIFY_EVENT_PD_SOURCE: + case PDIC_NOTIFY_EVENT_PD_SINK: + break; + case PDIC_NOTIFY_EVENT_PD_SINK_CAP: + battery->update_pd_list = true; + break; + case PDIC_NOTIFY_EVENT_PD_PRSWAP_SNKTOSRC: + *cmd = "PD_PRWAP"; + dev_info(battery->dev, "%s: PRSWAP_SNKTOSRC(%d)\n", __func__, pdata->attach); + *cable_type = SEC_BATTERY_CABLE_NONE; + + battery->pdic_attach = false; + battery->pdic_ps_rdy = false; + battery->init_src_cap = false; + battery->hv_pdo = false; + + if (battery->pdata->d2d_check_type == SB_D2D_NONE) + return 0; + + /* for 15w d2d snk to src prswap */ + if (battery->pdata->d2d_check_type == SB_D2D_SNKONLY) { + value.intval = 1; + psy_do_property(battery->pdata->otg_name, set, + POWER_SUPPLY_PROP_VOLTAGE_MAX, value); + } + + if (battery->d2d_auth == D2D_AUTH_SNK) + battery->d2d_auth = D2D_AUTH_SRC; + return 0; + case PDIC_NOTIFY_EVENT_PD_PRSWAP_SRCTOSNK: + *cmd = "PD_PRWAP"; + dev_info(battery->dev, "%s: PRSWAP_SRCTOSNK(%d)\n", __func__, pdata->attach); + + if (battery->d2d_auth == D2D_AUTH_SRC) { + if (battery->pdata->d2d_check_type == SB_D2D_SRCSNK) { + sb_disable_reverse_boost(battery); + battery->vpdo_ocp = false; + } + battery->d2d_auth = D2D_AUTH_SNK; + + value.intval = 0; + psy_do_property(battery->pdata->otg_name, set, + POWER_SUPPLY_PROP_VOLTAGE_MAX, value); + } + battery->vpdo_auth_stat = AUTH_NONE; + battery->hp_d2d = HP_D2D_NONE; + sec_vote(battery->chgen_vote, VOTER_D2D_WIRE, false, 0); + return 0; + default: + break; + } + + *cmd = "PD_ATTACH"; + battery->init_src_cap = false; + + if (battery->update_pd_list) { + pr_info("%s : update_pd_list(%d)\n", __func__, battery->update_pd_list); +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) +#if defined(CONFIG_STEP_CHARGING) + sec_bat_reset_step_charging(battery); + if (is_pd_apdo_wire_type(battery->cable_type)) + sec_bat_check_dc_step_charging(battery); +#endif + value.intval = 0; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_DIRECT_CLEAR_ERR, value); +#endif + battery->pdic_attach = false; + } + + if (!battery->pdic_attach) { + battery->sink_status = *psink_status; + bPdIndexChanged = true; + } else { + dev_info(battery->dev, "%s: sel_pdo(%d -> %d), cur_pdo(%d -> %d)\n", + __func__, battery->sink_status.selected_pdo_num, psink_status->selected_pdo_num, + battery->sink_status.current_pdo_num, psink_status->current_pdo_num); + + if (battery->sink_status.current_pdo_num != psink_status->current_pdo_num) + bPdIndexChanged = true; + + battery->sink_status.selected_pdo_num = psink_status->selected_pdo_num; + battery->sink_status.current_pdo_num = psink_status->current_pdo_num; + battery->pdic_ps_rdy = true; + sec_bat_get_input_current_in_power_list(battery); + sec_bat_get_charging_current_in_power_list(battery); + sec_vote(battery->chgen_vote, VOTER_SRCCAP_TRANSIT, false, 0); + battery->srccap_transit = false; + battery->srccap_transit_cnt = 0; + } + current_pdo = battery->sink_status.current_pdo_num; + if (battery->sink_status.power_list[current_pdo].max_voltage > SEC_INPUT_VOLTAGE_5V) + battery->hv_pdo = true; + else + battery->hv_pdo = false; + dev_info(battery->dev, "%s: battery->pdic_ps_rdy(%d), hv_pdo(%d)\n", + __func__, battery->pdic_ps_rdy, battery->hv_pdo); + + if (battery->sink_status.has_apdo) { + *cable_type = SEC_BATTERY_CABLE_PDIC_APDO; + if (battery->sink_status.power_list[current_pdo].pdo_type == APDO_TYPE) { + battery->hv_chg_name = "PDIC_APDO"; + battery->pd_list.now_isApdo = true; + } else { + battery->hv_chg_name = "PDIC_FIXED"; + battery->pd_list.now_isApdo = false; + } + + if (battery->pdic_attach) + bPrintPDlog = false; + } else { + *cable_type = SEC_BATTERY_CABLE_PDIC; + if (battery->sink_status.power_list[current_pdo].pdo_type == VPDO_TYPE) { + battery->hv_chg_name = "PDIC_VPDO"; + if ((battery->pdata->d2d_check_type == SB_D2D_SRCSNK) && + (battery->d2d_auth == D2D_AUTH_SNK)) { + /* preset auth vpdo for pr swap case (snk to src) */ + sec_pd_vpdo_auth(AUTH_HIGH_PWR, SB_D2D_SRCSNK); + } + } else + battery->hv_chg_name = "PDIC_FIXED"; + battery->pd_list.now_isApdo = false; + } + battery->muic_cable_type = ATTACHED_DEV_NONE_MUIC; + battery->input_voltage = battery->sink_status.power_list[current_pdo].max_voltage; + dev_info(battery->dev, "%s: available pdo : %d, current pdo : %d\n", __func__, + battery->sink_status.available_pdo_num, current_pdo); + + for (i = 1; i <= battery->sink_status.available_pdo_num; i++) { + bool isUpdated = false; + int temp_power = 0; + int pdo_type = battery->sink_status.power_list[i].pdo_type; + int max_volt = battery->sink_status.power_list[i].max_voltage; + int min_volt = battery->sink_status.power_list[i].min_voltage; + int max_curr = battery->sink_status.power_list[i].max_current; + bool isAccept = battery->sink_status.power_list[i].accept; + + if ((pdo_type == APDO_TYPE) && + (sec_pd_get_apdo_prog_volt(pdo_type, max_volt) > battery->pdata->apdo_max_volt)) + temp_power = battery->pdata->apdo_max_volt * max_curr; /* Protect to show sfc2.0 with 45w ta + 3a cable */ + else + temp_power = sec_pd_get_max_power(pdo_type, min_volt, max_volt, max_curr); + + if ((pdo_type != APDO_TYPE) && isAccept && + (max_volt > battery->pdata->max_input_voltage)) { + battery->sink_status.power_list[i].accept = false; + isAccept = battery->sink_status.power_list[i].accept; + } + + if (!(battery->current_event & SEC_BAT_CURRENT_EVENT_HV_DISABLE) && + !battery->sink_status.has_apdo && + pdo_type == FPDO_TYPE && + battery->pdata->support_fpdo_dc && + max_volt == 9000 && + (max_curr >= 3000 || battery->is_fpdo_dc)) { + dev_info(battery->dev, "%s: cable_type update to FPDO_DC\n", __func__); + if (!battery->is_fpdo_dc) + battery->cisd.cable_data[CISD_CABLE_FPDO_DC]++; + battery->is_fpdo_dc = true; + *cable_type = SEC_BATTERY_CABLE_FPDO_DC; + pdata_fpdo_max_power = battery->pdata->fpdo_dc_charge_power; + + pr_info("%s: FPDO_DC, refresh charging source.\n", __func__); + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_REFRESH_CHARGING_SOURCE, value); +#if defined(CONFIG_STEP_CHARGING) + sec_bat_check_step_charging(battery); +#endif + } + + if (bPrintPDlog) + pr_info("%s:%spower_list[%d,%s,%s], maxVol:%d, minVol:%d, maxCur:%d, power:%d\n", + __func__, i == current_pdo ? "**" : " ", + i, sec_pd_pdo_type_str(pdo_type), isAccept ? "O" : "X", + max_volt, min_volt, max_curr, temp_power); + + if (!battery->pdic_attach && isAccept) { + if (pdo_type == APDO_TYPE) { + if (temp_power > apdo_max_power) { + max_power = temp_power; + apdo_max_power = max_power; + } + } else { + if (temp_power > fpdo_max_power) { + max_power = temp_power; + fpdo_max_power = max_power; + } + } + } + + if (!isAccept) + continue; + /* no change apdo */ + if (pdo_type == APDO_TYPE) { + num_pd_list++; + continue; + } + + if (temp_power > (pdata_fpdo_max_power * 1000)) { + fpdo_max_power = pdata_fpdo_max_power * 1000; + max_curr = mA_by_mWmV(pdata_fpdo_max_power, max_volt); + isUpdated = true; + } + if (max_curr > battery->pdata->max_input_current) { + max_curr = battery->pdata->max_input_current; + isUpdated = true; + } + + if (isUpdated) { + battery->sink_status.power_list[i].max_current = max_curr; + if (bPrintPDlog) + pr_info("%s:->updated [%d,%s,%s], maxVol:%d, minVol:%d, maxCur:%d, power:%d\n", + __func__, i, sec_pd_pdo_type_str(pdo_type), + isAccept ? "O" : "X", battery->sink_status.power_list[i].max_voltage, + battery->sink_status.power_list[i].min_voltage, + battery->sink_status.power_list[i].max_current, + sec_pd_get_max_power(pdo_type, min_volt, max_volt, max_curr)); + } + num_pd_list++; + } + + if (!battery->pdic_attach) { + if (battery->sink_status.has_apdo) + battery->max_charge_power = (apdo_max_power / 1000) > pdata_apdo_max_power ? + pdata_apdo_max_power : (apdo_max_power / 1000); + else + battery->max_charge_power = (fpdo_max_power / 1000) > pdata_fpdo_max_power ? + pdata_fpdo_max_power : (fpdo_max_power / 1000); + battery->pd_max_charge_power = battery->max_charge_power; + pr_info("%s: pd_max_charge_power(%d,%d,%d) = %d\n", + __func__, max_power, apdo_max_power, fpdo_max_power, battery->pd_max_charge_power * 1000); + battery->pd_rated_power = max_power / 1000 / 1000; /* save as W */ + count_cisd_power_data(&battery->cisd, max_power / 1000); + if (battery->cable_type == SEC_BATTERY_CABLE_NONE) { + if (battery->pd_max_charge_power > HV_CHARGER_STATUS_STANDARD1) + battery->cisd.cable_data[CISD_CABLE_PD_HIGH]++; + else + battery->cisd.cable_data[CISD_CABLE_PD]++; + } +#if defined(CONFIG_SUPPORT_HV_CTRL) && !defined(CONFIG_SEC_FACTORY) + if (battery->pdata->boosting_voltage_aicl && + (battery->misc_event & BATT_MISC_EVENT_HV_BY_AICL)) { + if (sb_ct_has_9v(battery, battery->cable_type)) { + sec_vote(battery->input_vote, VOTER_AICL, false, 0); + sec_vote(battery->iv_vote, VOTER_AICL, true, SEC_INPUT_VOLTAGE_9V); + } else { + sec_vote(battery->iv_vote, VOTER_AICL, false, 0); + } + } +#endif + battery->pd_list.max_pd_count = num_pd_list; + pr_info("%s: total num_pd_list: %d\n", __func__, battery->pd_list.max_pd_count); + if (battery->pd_list.max_pd_count <= 0 + || battery->pd_list.max_pd_count > MAX_PDO_NUM) + return -1; /* skip usb_typec_handle_after_id() */ + } + battery->pdic_attach = true; +#if defined(CONFIG_BC12_DEVICE) + battery->pdic_ps_rdy = true; +#endif + if (is_pd_wire_type(battery->cable_type) && bPdIndexChanged) { + if (battery->update_pd_list || ((battery->d2d_auth == D2D_AUTH_SNK) && + (battery->sink_status.power_list[current_pdo].pdo_type == FPDO_TYPE)) || + (battery->sink_status.has_apdo && (battery->current_event & SEC_BAT_CURRENT_EVENT_DC_ERR)) || + (!battery->sink_status.has_apdo && (battery->sink_status.selected_pdo_num == -1))) { + int curr_volt = battery->sink_status.power_list[current_pdo].max_voltage; + int vote_volt = get_sec_vote_result(battery->iv_vote); + + /* request vpdo for 15w d2d */ + sec_vote(battery->iv_vote, VOTER_CABLE, true, + sec_bat_check_pd_iv(&battery->sink_status)); + + /* current pdo voltage and voter voltage are different, 9v should be select */ + if (battery->update_pd_list || ((curr_volt != vote_volt) && + (sec_bat_check_pd_iv(&battery->sink_status) == vote_volt))) { + pr_info("%s: update_pd_list: %d\n", __func__, battery->update_pd_list); + sec_vote_refresh(battery->iv_vote); + } + + battery->update_pd_list = false; + } + + /* Check VOTER_SIOP to set up current based on chagned index */ + __pm_stay_awake(battery->siop_level_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->siop_level_work, 0); + +#if !defined(CONFIG_SEC_FACTORY) + sec_bat_check_lrp_temp(battery, + battery->cable_type, battery->wire_status, battery->siop_level, battery->lcd_status); +#endif + } + + if (is_pd_apdo_wire_type(battery->wire_status) && !bPdIndexChanged && + (battery->sink_status.power_list[current_pdo].pdo_type == APDO_TYPE)) { + battery->wire_status = *cable_type; + return -1; /* skip usb_typec_handle_after_id() */ + } + + store_battery_log("CURR_PDO:PDO(%d/%d),Max_V(%d),Min_V(%d),Max_C(%d),PD_MCP(%d),PD_RP(%d),", + battery->sink_status.current_pdo_num, + battery->sink_status.available_pdo_num, + battery->sink_status.power_list[current_pdo].max_voltage, + battery->sink_status.power_list[current_pdo].min_voltage, + battery->sink_status.power_list[current_pdo].max_current, + battery->pd_max_charge_power, + battery->pd_rated_power); + + return 0; +} + +static int usb_typec_handle_id_usb(struct sec_battery_info *battery, PD_NOTI_ATTACH_TYPEDEF *pdata) +{ + if (pdata->cable_type == PD_USB_TYPE) + battery->pd_usb_attached = true; + else if (pdata->cable_type == PD_NONE_TYPE) + battery->pd_usb_attached = false; + dev_info(battery->dev, "%s: PDIC_NOTIFY_ID_USB: %d\n", __func__, battery->pd_usb_attached); + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + + return -1; /* skip usb_typec_handle_after_id() */ +} + +static void sb_auth_detach(struct sec_battery_info *battery) +{ + union power_supply_propval value = { 0, }; + + battery->d2d_auth = D2D_AUTH_NONE; + battery->vpdo_ocp = false; + battery->vpdo_auth_stat = AUTH_NONE; + battery->hp_d2d = HP_D2D_NONE; + /* clear vpdo auth for snk to src(pr swap) with detach case */ + sec_pd_vpdo_auth(AUTH_NONE, battery->pdata->d2d_check_type); + + sb_disable_reverse_boost(battery); + + /* set otg ocp limit 0.9A */ + value.intval = 0; + psy_do_property(battery->pdata->otg_name, set, + POWER_SUPPLY_PROP_VOLTAGE_MAX, value); + + /* clear buck off vote */ + sec_vote(battery->chgen_vote, VOTER_D2D_WIRE, false, 0); +} + +static int usb_typec_handle_id_device_info(struct sec_battery_info *battery, + PD_NOTI_DEVICE_INFO_TYPEDEF *pdata) +{ + union power_supply_propval value = { 0, }; + + if ((pdata->vendor_id != AUTH_VENDOR_ID) || + (pdata->product_id != AUTH_PRODUCT_ID) || + (pdata->version <= 0)) { + pr_info("%s : skip id_device_info\n", __func__); + return -1; + } + + if (battery->pdata->d2d_check_type != SB_D2D_NONE) { + if (battery->pdata->d2d_check_type == SB_D2D_SNKONLY) { + /* set otg ocp limit 1.5A */ + value.intval = 1; + psy_do_property(battery->pdata->otg_name, set, + POWER_SUPPLY_PROP_VOLTAGE_MAX, value); + } + + battery->d2d_auth = D2D_AUTH_SRC; + +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + sec_bat_d2d_check(battery, + battery->capacity, battery->temperature, battery->lrp); +#endif + } else + battery->d2d_auth = D2D_AUTH_SNK; + + pr_info("%s set d2d auth %s : (0x%04x, 0x%04x, %d)\n", __func__, + ((battery->d2d_auth == D2D_AUTH_SRC) ? "src" : "snk"), + pdata->vendor_id, pdata->product_id, pdata->version); + + return -1; +} + +static int usb_typec_handle_id_svid_info(struct sec_battery_info *battery, + PD_NOTI_SVID_INFO_TYPEDEF *pdata) +{ + if (pdata->standard_vendor_id == AUTH_VENDOR_ID) + battery->d2d_auth = D2D_AUTH_SNK; + + pr_info("%s set vpdo snk auth : %s (0x%04x)\n", __func__, + (battery->d2d_auth == D2D_AUTH_SNK) ? "enable" : "disable", pdata->standard_vendor_id); + + return -1; +} + +static int usb_typec_handle_id_clear_info(struct sec_battery_info *battery, + PD_NOTI_CLEAR_INFO_TYPEDEF *pdata) +{ + if ((pdata->clear_id == PDIC_NOTIFY_ID_DEVICE_INFO) || + (pdata->clear_id == PDIC_NOTIFY_ID_SVID_INFO)) { + sb_auth_detach(battery); + } + + pr_info("%s clear vpdo auth : (%d)\n", __func__, + pdata->clear_id); + + return -1; +} + +static int usb_typec_handle_after_id(struct sec_battery_info *battery, int cable_type, const char *cmd) +{ +#if defined(CONFIG_WIRELESS_TX_MODE) + if (!is_hv_wire_type(cable_type) && + !is_hv_pdo_wire_type(cable_type, battery->hv_pdo)) { + sec_vote(battery->chgen_vote, VOTER_CHANGE_CHGMODE, false, 0); + sec_vote(battery->iv_vote, VOTER_CHANGE_CHGMODE, false, 0); + } +#endif + +#if defined(CONFIG_PD_CHARGER_HV_DISABLE) && !defined(CONFIG_SEC_FACTORY) + if (check_afc_disabled_type(battery->muic_cable_type)) { + pr_info("%s set SEC_BAT_CURRENT_EVENT_AFC_DISABLE\n", __func__); + sec_bat_set_current_event(battery, + SEC_BAT_CURRENT_EVENT_AFC_DISABLE, SEC_BAT_CURRENT_EVENT_AFC_DISABLE); + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->monitor_work, 0); + } else { + sec_bat_set_current_event(battery, + 0, SEC_BAT_CURRENT_EVENT_AFC_DISABLE); + } +#endif + sec_bat_set_misc_event(battery, + (battery->muic_cable_type == ATTACHED_DEV_UNDEFINED_CHARGING_MUIC ? + BATT_MISC_EVENT_UNDEFINED_RANGE_TYPE : 0) | + (battery->muic_cable_type == ATTACHED_DEV_UNDEFINED_RANGE_MUIC ? + BATT_MISC_EVENT_UNDEFINED_RANGE_TYPE : 0), + BATT_MISC_EVENT_UNDEFINED_RANGE_TYPE); + + if (battery->muic_cable_type == ATTACHED_DEV_HICCUP_MUIC) { + if (battery->usb_conn_status || (battery->misc_event & BATT_MISC_EVENT_TEMP_HICCUP_TYPE)) { + pr_info("%s: Hiccup Set because of USB Temp\n", __func__); + sec_bat_set_misc_event(battery, + BATT_MISC_EVENT_TEMP_HICCUP_TYPE, BATT_MISC_EVENT_TEMP_HICCUP_TYPE); + battery->usb_conn_status = USB_CONN_NORMAL; + } else { + pr_info("%s: Hiccup Set because of Water detect\n", __func__); + sec_bat_set_misc_event(battery, + BATT_MISC_EVENT_WATER_HICCUP_TYPE, BATT_MISC_EVENT_WATER_HICCUP_TYPE); + } + battery->hiccup_status = 1; + } else { + battery->hiccup_status = 0; + if (battery->hiccup_clear) { + sec_bat_set_misc_event(battery, 0, + (BATT_MISC_EVENT_WATER_HICCUP_TYPE | BATT_MISC_EVENT_TEMP_HICCUP_TYPE)); + battery->hiccup_clear = false; + pr_info("%s : Hiccup event clear! hiccup clear bit set (%d)\n", + __func__, battery->hiccup_clear); + } else if (battery->misc_event & + (BATT_MISC_EVENT_WATER_HICCUP_TYPE | BATT_MISC_EVENT_TEMP_HICCUP_TYPE)) { + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + } + } + + if (cable_type < 0 || cable_type > SEC_BATTERY_CABLE_MAX) { + dev_info(battery->dev, "%s: ignore event(%d)\n", + __func__, battery->muic_cable_type); + return -1; /* skip usb_typec_handle_after_id() */ + } else if ((cable_type == SEC_BATTERY_CABLE_UNKNOWN) && + (battery->status != POWER_SUPPLY_STATUS_DISCHARGING)) { + battery->cable_type = cable_type; + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + dev_info(battery->dev, "%s: UNKNOWN cable plugin\n", __func__); + return -1; /* skip usb_typec_handle_after_id() */ + } + battery->wire_status = cable_type; + +#if defined(CONFIG_WIRELESS_TX_MODE) + if (battery->wc_tx_enable && battery->uno_en) { + int work_delay = 0; + + if (battery->wire_status == SEC_BATTERY_CABLE_NONE) { + battery->buck_cntl_by_tx = true; + sec_vote(battery->chgen_vote, VOTER_WC_TX, true, SEC_BAT_CHG_MODE_BUCK_OFF); + if (battery->tx_switch_mode != TX_SWITCH_GEAR_PPS) { + battery->tx_switch_mode = TX_SWITCH_MODE_OFF; + battery->tx_switch_mode_change = false; + } + battery->tx_switch_start_soc = 0; + } + + if (battery->pdata->tx_5v_disable && battery->wire_status == SEC_BATTERY_CABLE_TA) + work_delay = battery->pdata->pre_afc_work_delay + 500; //add delay more afc check + + if (battery->tx_switch_mode != TX_SWITCH_GEAR_PPS) + sec_bat_run_wpc_tx_work(battery, work_delay); + } +#endif + + cancel_delayed_work(&battery->cable_work); + __pm_relax(battery->cable_ws); + + if (cable_type == SEC_BATTERY_CABLE_HV_TA_CHG_LIMIT) { + /* set current event */ + sec_bat_cancel_input_check_work(battery); + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_CHG_LIMIT, + (SEC_BAT_CURRENT_EVENT_CHG_LIMIT | SEC_BAT_CURRENT_EVENT_AFC)); + sec_vote(battery->input_vote, VOTER_VBUS_CHANGE, false, 0); + + /* Check VOTER_SIOP to set up current based on cable_type */ + __pm_stay_awake(battery->siop_level_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->siop_level_work, 0); + + __pm_stay_awake(battery->monitor_ws); + battery->polling_count = 1; /* initial value = 1 */ + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + } else if ((battery->wire_status == battery->cable_type) && + (((battery->wire_status == SEC_BATTERY_CABLE_USB || battery->wire_status == SEC_BATTERY_CABLE_TA) && + battery->sink_status.rp_currentlvl >= RP_CURRENT_LEVEL_DEFAULT && + !(battery->current_event & SEC_BAT_CURRENT_EVENT_AFC)) || + is_hv_wire_type(battery->wire_status))) { + sec_bat_cancel_input_check_work(battery); + sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_AFC); + sec_vote(battery->input_vote, VOTER_CABLE, true, battery->pdata->charging_current[cable_type].input_current_limit); + sec_vote(battery->fcc_vote, VOTER_CABLE, true, battery->pdata->charging_current[cable_type].fast_charging_current); + sec_vote(battery->input_vote, VOTER_VBUS_CHANGE, false, 0); + + __pm_stay_awake(battery->monitor_ws); + battery->polling_count = 1; /* initial value = 1 */ + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + } else if (cable_type == SEC_BATTERY_CABLE_PREPARE_TA) { + sec_bat_check_afc_input_current(battery); + } else { + __pm_stay_awake(battery->cable_ws); + if (battery->ta_alert_wa && battery->ta_alert_mode != OCP_NONE) { + if (!strcmp(cmd, "DETACH")) { + queue_delayed_work(battery->monitor_wqueue, + &battery->cable_work, msecs_to_jiffies(3000)); + } else { + queue_delayed_work(battery->monitor_wqueue, + &battery->cable_work, 0); + } + } else { + queue_delayed_work(battery->monitor_wqueue, + &battery->cable_work, 0); + } + } + + return 0; +} + +static int usb_typec_handle_notification(struct notifier_block *nb, + unsigned long action, void *data) +{ + const char *cmd = "NONE"; + struct sec_battery_info *battery = + container_of(nb, struct sec_battery_info, usb_typec_nb); + int cable_type = SEC_BATTERY_CABLE_NONE; + PD_NOTI_TYPEDEF *pdata = (PD_NOTI_TYPEDEF *)data; + struct pdic_notifier_struct *pd_noti = pdata->pd; + SEC_PD_SINK_STATUS *psink_status = NULL; + int ret_handle = 0; + + dev_info(battery->dev, "%s: action:%ld src:%d, dest:%d, id:%d, sub1:%d, sub2:%d, sub3:%d\n", + __func__, action, pdata->src, pdata->dest, pdata->id, pdata->sub1, pdata->sub2, pdata->sub3); + + if ((pdata->dest != PDIC_NOTIFY_DEV_BATT) && (pdata->dest != PDIC_NOTIFY_DEV_ALL)) { + dev_info(battery->dev, "%s: skip handler dest(%d)\n", + __func__, pdata->dest); + return 0; + } + + if (!pd_noti) { + dev_info(battery->dev, "%s: pd_noti(pdata->pd) is NULL\n", __func__); + } else { + psink_status = &pd_noti->sink_status; + + if (!battery->psink_status) { + battery->psink_status = psink_status; + sec_pd_init_data(battery->psink_status); + sec_pd_register_chg_info_cb(count_cisd_pd_data); + } + } + + mutex_lock(&battery->typec_notylock); + switch (pdata->id) { + case PDIC_NOTIFY_ID_WATER: + case PDIC_NOTIFY_ID_ATTACH: + ret_handle = usb_typec_handle_id_attach(battery, (PD_NOTI_ATTACH_TYPEDEF *)pdata, &cable_type, &cmd); + break; + case PDIC_NOTIFY_ID_POWER_STATUS: + ret_handle = usb_typec_handle_id_power_status(battery, (PD_NOTI_POWER_STATUS_TYPEDEF *)pdata, &cable_type, &cmd); + break; + case PDIC_NOTIFY_ID_USB: + ret_handle = usb_typec_handle_id_usb(battery, (PD_NOTI_ATTACH_TYPEDEF *)pdata); + break; + case PDIC_NOTIFY_ID_DEVICE_INFO: + ret_handle = usb_typec_handle_id_device_info(battery, (PD_NOTI_DEVICE_INFO_TYPEDEF *)pdata); + break; + case PDIC_NOTIFY_ID_SVID_INFO: + ret_handle = usb_typec_handle_id_svid_info(battery, (PD_NOTI_SVID_INFO_TYPEDEF *)pdata); + break; + case PDIC_NOTIFY_ID_CLEAR_INFO: + ret_handle = usb_typec_handle_id_clear_info(battery, (PD_NOTI_CLEAR_INFO_TYPEDEF *)pdata); + break; + default: + cmd = "ERROR"; + cable_type = -1; + battery->muic_cable_type = ATTACHED_DEV_NONE_MUIC; + battery->hv_chg_name = "NONE"; + break; + } + + if (ret_handle < 0) + goto skip_handle_after_id; + + usb_typec_handle_after_id(battery, cable_type, cmd); + +skip_handle_after_id: + dev_info(battery->dev, "%s: CMD[%s], CABLE_TYPE[%d]\n", __func__, cmd, cable_type); + mutex_unlock(&battery->typec_notylock); + return 0; +} +#else +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) +static int batt_handle_notification(struct notifier_block *nb, + unsigned long action, void *data) +{ + const char *cmd; + int cable_type = SEC_BATTERY_CABLE_NONE; + struct sec_battery_info *battery = + container_of(nb, struct sec_battery_info, batt_nb); + union power_supply_propval value = {0, }; + +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) || IS_ENABLED(CONFIG_CCIC_NOTIFIER) + PD_NOTI_ATTACH_TYPEDEF *p_noti = (PD_NOTI_ATTACH_TYPEDEF *)data; + muic_attached_dev_t attached_dev = p_noti->cable_type; +#else + muic_attached_dev_t attached_dev = *(muic_attached_dev_t *)data; +#endif + + mutex_lock(&battery->batt_handlelock); + switch (action) { + case MUIC_NOTIFY_CMD_DETACH: + case MUIC_NOTIFY_CMD_LOGICALLY_DETACH: + cmd = "DETACH"; + battery->is_jig_on = false; + cable_type = SEC_BATTERY_CABLE_NONE; + battery->muic_cable_type = ATTACHED_DEV_NONE_MUIC; + break; + case MUIC_NOTIFY_CMD_ATTACH: + case MUIC_NOTIFY_CMD_LOGICALLY_ATTACH: + cmd = "ATTACH"; + cable_type = sec_bat_cable_check(battery, attached_dev); + battery->muic_cable_type = attached_dev; + break; + default: + cmd = "ERROR"; + cable_type = -1; + battery->muic_cable_type = ATTACHED_DEV_NONE_MUIC; + break; + } + + sec_bat_set_misc_event(battery, +#if !defined(CONFIG_ENG_BATTERY_CONCEPT) && !defined(CONFIG_SEC_FACTORY) + (battery->muic_cable_type == ATTACHED_DEV_JIG_UART_ON_MUIC ? BATT_MISC_EVENT_UNDEFINED_RANGE_TYPE : 0) | + (battery->muic_cable_type == ATTACHED_DEV_JIG_USB_ON_MUIC ? BATT_MISC_EVENT_UNDEFINED_RANGE_TYPE : 0) | +#endif + (battery->muic_cable_type == ATTACHED_DEV_UNDEFINED_RANGE_MUIC ? BATT_MISC_EVENT_UNDEFINED_RANGE_TYPE : 0), + BATT_MISC_EVENT_UNDEFINED_RANGE_TYPE); + + if (battery->muic_cable_type == ATTACHED_DEV_HICCUP_MUIC) { + if (battery->usb_conn_status || (battery->misc_event & BATT_MISC_EVENT_TEMP_HICCUP_TYPE)) { + pr_info("%s: Hiccup Set because of USB Temp\n", __func__); + sec_bat_set_misc_event(battery, + BATT_MISC_EVENT_TEMP_HICCUP_TYPE, BATT_MISC_EVENT_TEMP_HICCUP_TYPE); + battery->usb_conn_status = USB_CONN_NORMAL; + } else { + pr_info("%s: Hiccup Set because of Water detect\n", __func__); + sec_bat_set_misc_event(battery, + BATT_MISC_EVENT_WATER_HICCUP_TYPE, BATT_MISC_EVENT_WATER_HICCUP_TYPE); + } + battery->hiccup_status = 1; + } else { + battery->hiccup_status = 0; + if (battery->misc_event & + (BATT_MISC_EVENT_WATER_HICCUP_TYPE | BATT_MISC_EVENT_TEMP_HICCUP_TYPE)) { + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + } + } + + /* If PD cable is already attached, return this function */ + if (battery->pdic_attach) { + dev_info(battery->dev, "%s: ignore event pdic attached(%d)\n", + __func__, battery->pdic_attach); + mutex_unlock(&battery->batt_handlelock); + return 0; + } + + if (attached_dev == ATTACHED_DEV_MHL_MUIC) { + mutex_unlock(&battery->batt_handlelock); + return 0; + } + + if (cable_type < 0) { + dev_info(battery->dev, "%s: ignore event(%d)\n", + __func__, cable_type); + } else if ((cable_type == SEC_BATTERY_CABLE_UNKNOWN) && + (battery->status != POWER_SUPPLY_STATUS_DISCHARGING)) { + battery->cable_type = cable_type; + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + dev_info(battery->dev, + "%s: UNKNOWN cable plugin\n", __func__); + mutex_unlock(&battery->batt_handlelock); + return 0; + } else { + battery->wire_status = cable_type; + if (is_nocharge_type(battery->wire_status) && (battery->wc_status != SEC_BATTERY_CABLE_NONE)) + cable_type = SEC_BATTERY_CABLE_WIRELESS; + } + dev_info(battery->dev, + "%s: current_cable(%d), wc_status(%d), wire_status(%d)\n", + __func__, cable_type, battery->wc_status, + battery->wire_status); + + mutex_unlock(&battery->batt_handlelock); + if (attached_dev == ATTACHED_DEV_USB_LANHUB_MUIC) { + if (!strcmp(cmd, "ATTACH")) { + value.intval = true; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_CHARGE_POWERED_OTG_CONTROL, + value); + dev_info(battery->dev, + "%s: Powered OTG cable attached\n", __func__); + } else { + value.intval = false; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_CHARGE_POWERED_OTG_CONTROL, + value); + dev_info(battery->dev, + "%s: Powered OTG cable detached\n", __func__); + } + } + + if (!strcmp(cmd, "ATTACH")) { + if ((battery->muic_cable_type >= ATTACHED_DEV_QC_CHARGER_PREPARE_MUIC) && + (battery->muic_cable_type <= ATTACHED_DEV_QC_CHARGER_9V_MUIC)) { + battery->hv_chg_name = "QC"; + } else if ((battery->muic_cable_type >= ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC) && + (battery->muic_cable_type <= ATTACHED_DEV_AFC_CHARGER_ERR_V_DUPLI_MUIC)) { + battery->hv_chg_name = "AFC"; +#if defined(CONFIG_MUIC_HV_12V) + } else if (battery->muic_cable_type == ATTACHED_DEV_AFC_CHARGER_12V_MUIC || + battery->muic_cable_type == ATTACHED_DEV_AFC_CHARGER_12V_DUPLI_MUIC) { + battery->hv_chg_name = "12V"; +#endif + } else + battery->hv_chg_name = "NONE"; + } else { + battery->hv_chg_name = "NONE"; + } + + pr_info("%s : HV_CHARGER_NAME(%s)\n", + __func__, battery->hv_chg_name); + + if ((cable_type >= 0) && + cable_type <= SEC_BATTERY_CABLE_MAX) { + if (cable_type == SEC_BATTERY_CABLE_NONE) { + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + } else if (cable_type != battery->cable_type) { + __pm_stay_awake(battery->cable_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->cable_work, 0); + } else { + dev_info(battery->dev, + "%s: Cable is Not Changed(%d)\n", + __func__, battery->cable_type); + } + } + + pr_info("%s: CMD=%s, attached_dev=%d\n", __func__, cmd, attached_dev); + + return 0; +} +#endif /* CONFIG_MUIC_NOTIFIER */ +#endif + +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) +static int vbus_handle_notification(struct notifier_block *nb, + unsigned long action, void *data) +{ + vbus_status_t vbus_status = *(vbus_status_t *)data; + struct sec_battery_info *battery = + container_of(nb, struct sec_battery_info, vbus_nb); + + mutex_lock(&battery->batt_handlelock); + pr_info("battery: %s: action=%d, vbus_status=%s, otg=%s\n", + __func__, (int)action, vbus_status == STATUS_VBUS_HIGH ? "HIGH" : "LOW", + battery->is_otg_on ? "ON" : "OFF"); + +#if IS_ENABLED(CONFIG_LSI_IFPMIC) + if (battery->pdata->support_vpdo && battery->cable_type == SEC_BATTERY_CABLE_PDIC) { + sec_vote_refresh(battery->input_vote); + sec_vote_refresh(battery->fcc_vote); + sec_vote_refresh(battery->chgen_vote); + } +#endif + if (battery->pdata->support_usb_conn_check && + !battery->is_otg_on && vbus_status == STATUS_VBUS_HIGH && !delayed_work_pending(&battery->usb_conn_check_work) && + !battery->run_usb_conn_check) { +#if !defined(CONFIG_SEC_FACTORY) + battery->run_usb_conn_check = true; + battery->usb_conn_check_cnt = 0; + __pm_stay_awake(battery->usb_conn_check_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->usb_conn_check_work, msecs_to_jiffies(1000)); +#endif + } + mutex_unlock(&battery->batt_handlelock); + + return 0; +} +#endif + +__visible_for_testing void sec_bat_parse_param_value(struct sec_battery_info *battery) +{ +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + union power_supply_propval value = {0, }; +#endif + int chg_mode = 0; + int pd_hv_disable = 0; +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + int afc_mode = 0; +#endif + + chg_mode = sec_bat_get_chgmode() & 0x000000FF; + pr_info("%s: charging_mode: 0x%x (charging_night_mode:0x%x)\n", + __func__, sec_bat_get_chgmode(), chg_mode); + + pd_hv_disable = sec_bat_get_dispd() & 0x000000FF; + pr_info("%s: pd_disable: 0x%x (pd_hv_disable:0x%x)\n", + __func__, sec_bat_get_dispd(), pd_hv_disable); + +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + afc_mode = get_afc_mode(); + pr_info("%s: afc_mode: 0x%x\n", __func__, afc_mode); +#endif + +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + battery->charging_night_mode = chg_mode; + + /* Check High Voltage charging option for wireless charging */ + /* '1' means disabling High Voltage charging */ + if (battery->charging_night_mode == '1') /* 0x31 */ + battery->sleep_mode = true; + else + battery->sleep_mode = false; + value.intval = battery->sleep_mode; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_SLEEP_MODE, value); +#endif +#if defined(CONFIG_PD_CHARGER_HV_DISABLE) + if (pd_hv_disable == '1') { /* 0x31 */ + battery->pd_disable = true; + pr_info("PD wired charging mode is disabled\n"); + sec_bat_set_current_event(battery, + SEC_BAT_CURRENT_EVENT_HV_DISABLE, SEC_BAT_CURRENT_EVENT_HV_DISABLE); + } +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + /* Check High Voltage charging option for wired charging */ + if (afc_mode == CH_MODE_AFC_DISABLE_VAL) { + pr_info("None PD wired charging mode is disabled\n"); + sec_bat_set_current_event(battery, + SEC_BAT_CURRENT_EVENT_NOPD_HV_DISABLE, SEC_BAT_CURRENT_EVENT_NOPD_HV_DISABLE); + } +#endif +#else +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + /* Check High Voltage charging option for wired charging */ + if (afc_mode == CH_MODE_AFC_DISABLE_VAL) { + pr_info("HV wired charging mode is disabled\n"); + sec_bat_set_current_event(battery, + SEC_BAT_CURRENT_EVENT_HV_DISABLE, SEC_BAT_CURRENT_EVENT_HV_DISABLE); + } +#endif +#endif + +#if IS_ENABLED(CONFIG_USB_FACTORY_MODE) + pr_info("%s: f_mode: %s\n", __func__, f_mode); + + if (!f_mode) { + battery->batt_f_mode = NO_MODE; + } else if ((strncmp(f_mode, "OB", 2) == 0) || (strncmp(f_mode, "DL", 2) == 0)) { + /* Set factory mode variables in OB mode */ + sec_bat_set_facmode(true); + battery->factory_mode = true; + battery->batt_f_mode = OB_MODE; + } else if (strncmp(f_mode, "IB", 2) == 0) { + battery->batt_f_mode = IB_MODE; + } else { + battery->batt_f_mode = NO_MODE; + } + pr_info("[BAT] %s: f_mode: %s\n", __func__, BOOT_MODE_STRING[battery->batt_f_mode]); + + battery->usb_factory_init = false; +#if defined(CONFIG_SEC_FACTORY) + battery->usb_factory_slate_mode = false; +#endif +#endif + + battery->factory_mode_boot_on = sec_bat_get_facmode(); +} +EXPORT_SYMBOL_KUNIT(sec_bat_parse_param_value); + +static void sec_bat_afc_init_work(struct work_struct *work) +{ + int ret = 0; + +#if defined(CONFIG_AFC_CHARGER_MODE) + ret = muic_hv_charger_init(); +#endif + pr_info("%s, ret(%d)\n", __func__, ret); +} + +static void sec_bat_check_spsn_open(struct sec_battery_info *battery) +{ +#if !defined(CONFIG_SEC_FACTORY) + union power_supply_propval value = {0, }; + + if (sec_bat_get_facmode() || battery->factory_mode || + !battery->pdata->support_spsn_ctrl) { + pr_info("%s: skip factory mode or not support spsn ctrl\n", __func__); + return; + } + + if (battery->voltage_now < 3000) { + pr_info("%s: voltage = %d\n", __func__, battery->voltage_now); + return; + } + + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_SPSN_TEST, value); + if (!value.intval) { + value.intval = 100; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_SPSN_TEST, value); + } +#endif +} + +static void sec_batt_dev_init_work(struct work_struct *work) +{ + struct sec_battery_info *battery = container_of(work, + struct sec_battery_info, dev_init_work.work); + union power_supply_propval value = {0, }; +#if defined(CONFIG_STORE_MODE) && !defined(CONFIG_SEC_FACTORY) && IS_ENABLED(CONFIG_DIRECT_CHARGING) + char direct_charging_source_status[2] = {0, }; +#endif + + pr_info("%s: Start\n", __func__); + + sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_DISCHARGING); + sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_GOOD); + battery->capacity = 50; + + sec_chg_check_modprobe(); + + /* updates temperatures on boot */ + sec_bat_get_temperature_info(battery); + + /* initialize battery voltage*/ + value.intval = SEC_BATTERY_VOLTAGE_MV; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_VOLTAGE_NOW, value); + battery->voltage_now = value.intval; + + /* initialize battery level*/ + value.intval = 0; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CAPACITY, value); + battery->capacity = value.intval; + + sec_bat_check_spsn_open(battery); + +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + sec_bat_check_boottime(battery, battery->pdata->dctp_bootmode_en); +#endif + + sec_bat_parse_param_value(battery); +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) +#if defined(CONFIG_WIRELESS_FIRMWARE_UPDATE) + queue_delayed_work(battery->monitor_wqueue, &battery->fw_init_work, msecs_to_jiffies(2000)); +#endif + + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_WPC_EN, value); + sec_bat_set_current_event(battery, + value.intval ? SEC_BAT_CURRENT_EVENT_WPC_EN : 0, SEC_BAT_CURRENT_EVENT_WPC_EN); + + /* + * notify wireless charger driver when sec_battery probe is done, + * if wireless charging is possible, POWER_SUPPLY_PROP_ONLINE of wireless property will be called. + */ + value.intval = 0; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_PROP_CHARGE_TYPE, value); +#endif +#if defined(CONFIG_USE_POGO) + /* + * notify pogo charger driver when sec_battery probe is done, + * if pogo charging is possible, POWER_SUPPLY_PROP_ONLINE of pogo property will be called. + */ + value.intval = 0; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_PROP_CHARGE_TYPE, value); +#endif + +#if defined(CONFIG_STORE_MODE) && !defined(CONFIG_SEC_FACTORY) +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + direct_charging_source_status[0] = SEC_STORE_MODE; + direct_charging_source_status[1] = SEC_CHARGING_SOURCE_SWITCHING; + value.strval = direct_charging_source_status; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_CHANGE_CHARGING_SOURCE, value); +#endif +#endif + register_batterylog_proc(); +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + battery->sink_status.rp_currentlvl = RP_CURRENT_LEVEL_NONE; + manager_notifier_register(&battery->usb_typec_nb, + usb_typec_handle_notification, MANAGER_NOTIFY_PDIC_BATTERY); +#else +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + muic_notifier_register(&battery->batt_nb, + batt_handle_notification, MUIC_NOTIFY_DEV_CHARGER); +#endif +#endif + value.intval = true; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, value); + +#if defined(CONFIG_SEC_COMMON) + /* make fg_reset true again for actual normal booting after recovery kernel is done */ + if (sec_bat_get_fgreset() && seccmn_recv_is_boot_recovery()) { + pr_info("%s: fg_reset(%d) boot_recov(%d)\n", + __func__, sec_bat_get_fgreset(), seccmn_recv_is_boot_recovery()); + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_PROP_ENERGY_NOW, value); + pr_info("%s: make fg_reset true again for actual normal booting\n", __func__); + } +#endif +#if defined(CONFIG_NO_BATTERY) + sec_vote(battery->chgen_vote, VOTER_NO_BATTERY, true, SEC_BAT_CHG_MODE_CHARGING_OFF); +#else + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_USB_100MA, SEC_BAT_CURRENT_EVENT_USB_100MA); + sec_vote(battery->input_vote, VOTER_USB_100MA, true, 100); + sec_vote(battery->fcc_vote, VOTER_USB_100MA, true, 100); + sec_vote(battery->topoff_vote, VOTER_FULL_CHARGE, true, battery->pdata->full_check_current_1st); + sec_vote(battery->fv_vote, VOTER_FULL_CHARGE, true, battery->pdata->chg_float_voltage); +#endif + +#if defined(CONFIG_BC12_DEVICE) +#if defined(CONFIG_SEC_FACTORY) + value.intval = 0; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_EXT_PROP_CHECK_INIT, value); + battery->vbat_adc_open = (value.intval == 1) ? true : false; +#endif + value.intval = 0; + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_CHECK_INIT, value); +#endif + +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) + vbus_notifier_register(&battery->vbus_nb, + vbus_handle_notification, VBUS_NOTIFY_DEV_BATTERY); +#endif + + queue_delayed_work(battery->monitor_wqueue, &battery->afc_init_work, 0); + if ((battery->wire_status == SEC_BATTERY_CABLE_NONE) || + (battery->wire_status == SEC_BATTERY_CABLE_PREPARE_TA)) { + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + } + pr_info("%s: End\n", __func__); + + __pm_relax(battery->dev_init_ws); +} + +static int sb_handle_notification(struct notifier_block *nb, unsigned long action, void *data) +{ + struct sec_battery_info *battery = container_of(nb, struct sec_battery_info, sb_nb); + sb_data *sbd = data; + int ret = 0; + + switch (action) { + case SB_NOTIFY_DEV_PROBE: + ret = sb_sysfs_create_attrs(&battery->psy_bat->dev); + pr_info("%s: create sysfs node (name = %s, count = %d)\n", + __func__, cast_sb_data_ptr(const char, sbd), ret); + break; + case SB_NOTIFY_DEV_LIST: + { + struct sbn_dev_list *tmp_list = cast_sb_data_ptr(struct sbn_dev_list, sbd); + + ret = sb_sysfs_create_attrs(&battery->psy_bat->dev); + pr_info("%s: create sysfs node (dev_count = %d, sysfs_count = %d)\n", + __func__, tmp_list->count, ret); + } + break; + case SB_NOTIFY_EVENT_MISC: + { + struct sbn_bit_event *misc = cast_sb_data_ptr(struct sbn_bit_event, sbd); + + sec_bat_set_misc_event(battery, misc->value, misc->mask); + } + break; + case SB_NOTIFY_DEV_SHUTDOWN: + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct power_supply_desc battery_power_supply_desc = { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = sec_battery_props, + .num_properties = ARRAY_SIZE(sec_battery_props), + .get_property = sec_bat_get_property, + .set_property = sec_bat_set_property, +}; + +static const struct power_supply_desc usb_power_supply_desc = { + .name = "usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = sec_power_props, + .num_properties = ARRAY_SIZE(sec_power_props), + .get_property = sec_usb_get_property, +}; + +static const struct power_supply_desc ac_power_supply_desc = { + .name = "ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = sec_ac_props, + .num_properties = ARRAY_SIZE(sec_ac_props), + .get_property = sec_ac_get_property, +}; + +static const struct power_supply_desc wireless_power_supply_desc = { + .name = "wireless", + .type = POWER_SUPPLY_TYPE_WIRELESS, + .properties = sec_wireless_props, + .num_properties = ARRAY_SIZE(sec_wireless_props), + .get_property = sec_wireless_get_property, + .set_property = sec_wireless_set_property, +}; + +static const struct power_supply_desc pogo_power_supply_desc = { + .name = "pogo", + .type = POWER_SUPPLY_TYPE_UNKNOWN, + .properties = sec_power_props, + .num_properties = ARRAY_SIZE(sec_power_props), + .get_property = sec_pogo_get_property, + .set_property = sec_pogo_set_property, +}; + +static const struct power_supply_desc otg_power_supply_desc = { + .name = "otg", + .type = POWER_SUPPLY_TYPE_UNKNOWN, + .properties = sec_otg_props, + .num_properties = ARRAY_SIZE(sec_otg_props), + .get_property = sec_otg_get_property, + .set_property = sec_otg_set_property, + .external_power_changed = sec_otg_external_power_changed, +}; + +static int sec_battery_probe(struct platform_device *pdev) +{ + sec_battery_platform_data_t *pdata = NULL; + struct sec_battery_info *battery; + struct power_supply_config battery_cfg = {}; + int ret = 0; + + dev_info(&pdev->dev, + "%s: SEC Battery Driver Loading\n", __func__); + + battery = kzalloc(sizeof(*battery), GFP_KERNEL); + if (!battery) + return -ENOMEM; + + if (pdev->dev.of_node) { + pdata = devm_kzalloc(&pdev->dev, + sizeof(sec_battery_platform_data_t), + GFP_KERNEL); + if (!pdata) { + dev_err(&pdev->dev, "Failed to allocate memory\n"); + ret = -ENOMEM; + goto err_bat_free; + } + + battery->pdata = pdata; + + if (sec_bat_parse_dt(&pdev->dev, battery)) { + dev_err(&pdev->dev, + "%s: Failed to get battery dt\n", __func__); + ret = -EINVAL; + goto err_bat_free; + } + } else { + pdata = dev_get_platdata(&pdev->dev); + battery->pdata = pdata; + } + + if (battery->pdata->soc_by_repcap_en) { + battery->eoc_d = kzalloc(sizeof(struct sec_eoc_info), + GFP_KERNEL); + if (!battery->eoc_d) { + ret = -EINVAL; + goto err_pdata_free; + } + } + platform_set_drvdata(pdev, battery); + + battery->dev = &pdev->dev; + + mutex_init(&battery->iolock); + mutex_init(&battery->misclock); + mutex_init(&battery->txeventlock); + mutex_init(&battery->batt_handlelock); + mutex_init(&battery->current_eventlock); + mutex_init(&battery->typec_notylock); +#if IS_ENABLED(CONFIG_MTK_CHARGER) && !IS_ENABLED(CONFIG_VIRTUAL_MUIC) + mutex_init(&battery->bc12_notylock); +#endif + mutex_init(&battery->wclock); + mutex_init(&battery->voutlock); + + dev_dbg(battery->dev, "%s: ADC init\n", __func__); + + adc_init(pdev, battery); + + battery->monitor_ws = wakeup_source_register(&pdev->dev, "sec-battery-monitor"); + battery->cable_ws = wakeup_source_register(&pdev->dev, "sec-battery-cable"); + battery->vbus_ws = wakeup_source_register(&pdev->dev, "sec-battery-vbus"); + battery->input_ws = wakeup_source_register(&pdev->dev, "sec-battery-input"); + battery->siop_level_ws = wakeup_source_register(&pdev->dev, "sec-battery-siop_level"); +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + battery->ext_event_ws = wakeup_source_register(&pdev->dev, "sec-battery-ext_event"); + battery->wpc_tx_ws = wakeup_source_register(&pdev->dev, "sec-battery-wcp-tx"); + battery->wpc_tx_en_ws = wakeup_source_register(&pdev->dev, "sec-battery-wpc_tx_en"); + battery->tx_event_ws = wakeup_source_register(&pdev->dev, "sec-battery-tx-event"); + battery->wc20_current_ws = wakeup_source_register(&pdev->dev, "sec-battery-wc20-current"); + battery->wc_ept_timeout_ws = wakeup_source_register(&pdev->dev, "sec-battery-ept-timeout"); +#endif +#if defined(CONFIG_UPDATE_BATTERY_DATA) + battery->batt_data_ws = wakeup_source_register(&pdev->dev, "sec-battery-update-data"); +#endif + battery->misc_event_ws = wakeup_source_register(&pdev->dev, "sec-battery-misc-event"); + battery->parse_mode_dt_ws = wakeup_source_register(&pdev->dev, "sec-battery-parse_mode_dt"); + battery->dev_init_ws = wakeup_source_register(&pdev->dev, "sec-battery-dev-init"); + battery->usb_conn_check_ws = wakeup_source_register(&pdev->dev, "sec-battery-usb-thm-check"); + + /* create work queue */ + battery->monitor_wqueue = + create_singlethread_workqueue(dev_name(&pdev->dev)); + if (!battery->monitor_wqueue) { + dev_err(battery->dev, + "%s: Fail to Create Workqueue\n", __func__); + goto err_irq; + } + + INIT_DELAYED_WORK(&battery->monitor_work, sec_bat_monitor_work); + INIT_DELAYED_WORK(&battery->cable_work, sec_bat_cable_work); + INIT_DELAYED_WORK(&battery->slowcharging_work, sec_bat_check_slowcharging_work); + INIT_DELAYED_WORK(&battery->input_check_work, sec_bat_input_check_work); + INIT_DELAYED_WORK(&battery->siop_level_work, sec_bat_siop_level_work); +#if defined(CONFIG_WIRELESS_FIRMWARE_UPDATE) + INIT_DELAYED_WORK(&battery->fw_init_work, sec_bat_fw_init_work); +#endif +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + INIT_DELAYED_WORK(&battery->wpc_tx_work, sec_bat_wpc_tx_work); + INIT_DELAYED_WORK(&battery->wpc_tx_en_work, sec_bat_wpc_tx_en_work); + INIT_DELAYED_WORK(&battery->wpc_txpower_calc_work, sec_bat_txpower_calc_work); + INIT_DELAYED_WORK(&battery->ext_event_work, sec_bat_ext_event_work); + INIT_DELAYED_WORK(&battery->wc20_current_work, sec_bat_wc20_current_work); + INIT_DELAYED_WORK(&battery->wc_ept_timeout_work, sec_bat_wc_ept_timeout_work); +#endif +#if defined(CONFIG_UPDATE_BATTERY_DATA) + INIT_DELAYED_WORK(&battery->batt_data_work, sec_bat_update_data_work); +#endif + INIT_DELAYED_WORK(&battery->misc_event_work, sec_bat_misc_event_work); + INIT_DELAYED_WORK(&battery->parse_mode_dt_work, sec_bat_parse_mode_dt_work); + INIT_DELAYED_WORK(&battery->dev_init_work, sec_batt_dev_init_work); + INIT_DELAYED_WORK(&battery->afc_init_work, sec_bat_afc_init_work); + INIT_DELAYED_WORK(&battery->usb_conn_check_work, sec_bat_usb_conn_check_work); + INIT_DELAYED_WORK(&battery->transit_clear_work, sec_bat_transit_clear_work); + + battery->fcc_vote = sec_vote_init("FCC", SEC_VOTE_MIN, VOTER_MAX, + 500, sec_voter_name, set_charging_current, battery); + battery->input_vote = sec_vote_init("ICL", SEC_VOTE_MIN, VOTER_MAX, + 500, sec_voter_name, set_input_current, battery); + battery->fv_vote = sec_vote_init("FV", SEC_VOTE_MIN, VOTER_MAX, + battery->pdata->chg_float_voltage, sec_voter_name, set_float_voltage, battery); + battery->dc_fv_vote = sec_vote_init("DCFV", SEC_VOTE_MIN, VOTER_MAX, + battery->pdata->chg_float_voltage, sec_voter_name, set_dc_float_voltage, battery); + battery->chgen_vote = sec_vote_init("CHGEN", SEC_VOTE_MIN, VOTER_MAX, + SEC_BAT_CHG_MODE_CHARGING_OFF, sec_voter_name, sec_bat_set_charge, battery); + battery->topoff_vote = sec_vote_init("TOPOFF", SEC_VOTE_MIN, VOTER_MAX, + battery->pdata->full_check_current_1st, sec_voter_name, set_topoff_current, battery); + battery->iv_vote = sec_vote_init("IV", SEC_VOTE_MIN, VOTER_MAX, + SEC_INPUT_VOLTAGE_5V, sec_voter_name, sec_bat_change_iv, battery); + + /* set vote priority */ + change_sec_voter_pri(battery->iv_vote, VOTER_MUIC_ABNORMAL, VOTE_PRI_10); + change_sec_voter_pri(battery->iv_vote, VOTER_MST, VOTE_PRI_10); + change_sec_voter_pri(battery->iv_vote, VOTER_CHANGE_CHGMODE, VOTE_PRI_9); + change_sec_voter_pri(battery->iv_vote, VOTER_WC_TX, VOTE_PRI_8); + change_sec_voter_pri(battery->iv_vote, VOTER_AICL, VOTE_PRI_1); + change_sec_voter_pri(battery->iv_vote, VOTER_FLASH, VOTE_PRI_3); + change_sec_voter_pri(battery->chgen_vote, VOTER_NO_BATTERY, VOTE_PRI_10); + change_sec_voter_pri(battery->chgen_vote, VOTER_CHANGE_CHGMODE, VOTE_PRI_9); + change_sec_voter_pri(battery->fv_vote, VOTER_SWELLING, VOTE_PRI_10); + change_sec_voter_pri(battery->dc_fv_vote, VOTER_SWELLING, VOTE_PRI_10); + + switch (pdata->polling_type) { + case SEC_BATTERY_MONITOR_WORKQUEUE: + INIT_DELAYED_WORK(&battery->polling_work, + sec_bat_polling_work); + break; + case SEC_BATTERY_MONITOR_ALARM: + battery->last_poll_time = ktime_get_boottime(); + alarm_init(&battery->polling_alarm, ALARM_BOOTTIME, + sec_bat_alarm); + break; + default: + break; + } + + battery->pogo_status = 0; + battery->pogo_9v = false; + + battery->is_fpdo_dc = false; + + battery->ta_alert_mode = OCP_NONE; + battery->present = true; + battery->is_jig_on = false; + battery->wdt_kick_disable = 0; + battery->polling_count = 1; /* initial value = 1 */ + battery->polling_time = pdata->polling_time[ + SEC_BATTERY_POLLING_TIME_DISCHARGING]; + battery->polling_in_sleep = false; + battery->polling_short = false; + battery->check_count = 0; + battery->check_adc_count = 0; + battery->check_adc_value = 0; + battery->input_current = 0; + battery->charging_current = 0; +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + battery->main_current = 0; + battery->sub_current = 0; + battery->limiter_check = false; + battery->set_lower_curr = false; +#endif + battery->topoff_condition = 0; + battery->wpc_vout_level = WIRELESS_VOUT_10V; + battery->wpc_max_vout_level = WIRELESS_VOUT_12_5V; + battery->charging_retention_time = 0; + battery->charging_start_time = 0; + battery->charging_passed_time = 0; + battery->wc_heating_start_time = 0; + battery->wc_heating_passed_time = 0; + battery->charging_next_time = 0; + battery->charging_fullcharged_time = 0; + battery->siop_level = 100; + battery->wc_enable = true; + battery->wc_enable_cnt = 0; + battery->wc_enable_cnt_value = 3; + battery->stability_test = 0; + battery->eng_not_full_status = 0; +#if defined(CONFIG_ENG_BATTERY_CONCEPT) +#if defined(CONFIG_STEP_CHARGING) + battery->test_step_condition = 0x7FFF; +#endif + battery->test_max_current = false; + battery->test_charge_current = false; +#endif + battery->wc_status = SEC_BATTERY_CABLE_NONE; + battery->wc_cv_mode = false; + battery->wire_status = SEC_BATTERY_CABLE_NONE; +#if IS_ENABLED(CONFIG_MTK_CHARGER) && !IS_ENABLED(CONFIG_VIRTUAL_MUIC) + battery->bc12_cable = SEC_BATTERY_CABLE_NONE; +#endif +#if defined(CONFIG_WIRELESS_RX_PHM_CTRL) + battery->wc_rx_pdetb_mode = false; +#else + battery->wc_rx_phm_mode = false; +#endif + battery->wc_tx_phm_mode = false; + battery->wc_tx_enable = false; + battery->uno_en = false; + battery->afc_disable = false; + battery->pd_disable = false; + battery->buck_cntl_by_tx = false; + battery->wc_tx_vout = WC_TX_VOUT_5000MV; + battery->wc_rx_type = NO_DEV; + battery->tx_mfc_iout = 0; + battery->tx_uno_iout = 0; + battery->wc_need_ldo_on = false; + battery->tx_minduty = battery->pdata->tx_minduty_default; + battery->chg_limit = false; + battery->lrp_limit = false; + battery->lrp_step = LRP_NONE; + battery->mix_limit = false; + battery->usb_temp = 0; + battery->dchg_temp = 0; + battery->blkt_temp = 0; + battery->lrp = 0; + battery->lrp_test = 0; +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + battery->lrp_chg_src = SEC_CHARGING_SOURCE_DIRECT; +#endif + battery->skip_swelling = false; + battery->wpc_temp_v2_offset = 0; + battery->bat_thm_count = 0; + battery->adc_init_count = 0; + battery->led_cover = 0; + battery->mag_cover = 0; + battery->hiccup_status = 0; + battery->hiccup_clear = false; + battery->ext_event = BATT_EXT_EVENT_NONE; + battery->tx_retry_case = SEC_BAT_TX_RETRY_NONE; + battery->tx_misalign_cnt = 0; + battery->tx_ocp_cnt = 0; + battery->auto_mode = false; + battery->update_pd_list = false; + battery->charging_mode = SEC_BATTERY_CHARGING_NONE; + battery->is_recharging = false; + battery->cable_type = SEC_BATTERY_CABLE_NONE; + battery->test_mode = 0; + battery->factory_mode = false; + battery->display_test = false; + battery->store_mode = false; + battery->prev_usb_conf = USB_CURRENT_NONE; + battery->is_hc_usb = false; + battery->is_sysovlo = false; + battery->is_vbatovlo = false; + battery->is_abnormal_temp = false; + battery->hv_pdo = false; + battery->safety_timer_set = true; + battery->stop_timer = false; + battery->prev_safety_time = 0; + battery->lcd_status = true; + battery->wc_auth_retried = false; +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + battery->wc_ept_timeout = false; + battery->wc20_power_class = 0; + battery->wc20_vout = 0; + battery->wc20_rx_power = 0; +#endif + battery->thermal_zone = BAT_THERMAL_NORMAL; + sec_bat_set_threshold(battery, battery->cable_type); + battery->usb_overheat_check = false; + battery->skip_cisd = false; + battery->batt_cycle = -1; + battery->batt_full_status_usage = -1; + battery->pdata->age_step = 0; + battery->batt_asoc = 100; + battery->health_change = false; + battery->usb_conn_status = USB_CONN_NORMAL; + battery->lrp_temp = 0x7FFF; + battery->lr_start_time = 0; + battery->lr_time_span = 0; + battery->usb_slow_chg = false; + battery->usb_bootcomplete = false; +#if IS_ENABLED(CONFIG_MTK_CHARGER) + battery->mtk_fg_init = false; +#endif + battery->d2d_auth = D2D_AUTH_NONE; + battery->vpdo_src_boost = false; + battery->vpdo_ocp = false; + battery->vpdo_auth_stat = AUTH_NONE; + battery->hp_d2d = HP_D2D_NONE; + if (battery->pdata->soc_by_repcap_en) { + battery->eoc_d->eoc_check = false; + battery->eoc_d->eoc_cnt = 0; + } + battery->dc_check_cnt = 0; + battery->srccap_transit_cnt = 0; + battery->abnormal_ta = false; + battery->smart_sw_src = false; + + ttf_init(battery); + sec_battery_cisd_init(battery); +#if defined(CONFIG_STORE_MODE) && !defined(CONFIG_SEC_FACTORY) + battery->store_mode = true; + sec_bat_parse_mode_dt(battery); +#endif +#if defined(CONFIG_WIRELESS_AUTH) + sec_bat_misc_init(battery); +#endif +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + if ((battery->pdata->charging_current[SEC_BATTERY_CABLE_WIRELESS].input_current_limit <= battery->pdata->wpc_input_limit_current) || + (battery->pdata->charging_current[SEC_BATTERY_CABLE_WIRELESS].input_current_limit <= battery->pdata->wpc_lcd_on_input_limit_current)) + battery->nv_wc_temp_ctrl_skip = true; + else + battery->nv_wc_temp_ctrl_skip = false; +#endif + if (battery->pdata->charger_name == NULL) + battery->pdata->charger_name = "sec-charger"; + if (battery->pdata->fuelgauge_name == NULL) + battery->pdata->fuelgauge_name = "sec-fuelgauge"; + if (battery->pdata->check_battery_callback) + battery->present = battery->pdata->check_battery_callback(); + + if (sec_bat_get_fgreset()) + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_FG_RESET, + SEC_BAT_CURRENT_EVENT_FG_RESET); + + battery_cfg.drv_data = battery; + + /* init power supplier framework */ + battery->psy_usb = power_supply_register(&pdev->dev, &usb_power_supply_desc, &battery_cfg); + if (IS_ERR(battery->psy_usb)) { + ret = PTR_ERR(battery->psy_usb); + dev_err(battery->dev, + "%s: Failed to Register psy_usb(%d)\n", __func__, ret); + goto err_workqueue; + } + battery->psy_usb->supplied_to = supply_list; + battery->psy_usb->num_supplicants = ARRAY_SIZE(supply_list); + + battery->psy_ac = power_supply_register(&pdev->dev, &ac_power_supply_desc, &battery_cfg); + if (IS_ERR(battery->psy_ac)) { + ret = PTR_ERR(battery->psy_ac); + dev_err(battery->dev, + "%s: Failed to Register psy_ac(%d)\n", __func__, ret); + goto err_supply_unreg_usb; + } + battery->psy_ac->supplied_to = supply_list; + battery->psy_ac->num_supplicants = ARRAY_SIZE(supply_list); + + battery->psy_bat = power_supply_register(&pdev->dev, &battery_power_supply_desc, &battery_cfg); + if (IS_ERR(battery->psy_bat)) { + ret = PTR_ERR(battery->psy_bat); + dev_err(battery->dev, + "%s: Failed to Register psy_bat(%d)\n", __func__, ret); + goto err_supply_unreg_ac; + } + + battery->psy_pogo = power_supply_register(&pdev->dev, &pogo_power_supply_desc, &battery_cfg); + if (IS_ERR(battery->psy_pogo)) { + ret = PTR_ERR(battery->psy_pogo); + dev_err(battery->dev, + "%s: Failed to Register psy_pogo(%d)\n", __func__, ret); + goto err_supply_unreg_bat; + } + + battery->psy_wireless = power_supply_register(&pdev->dev, &wireless_power_supply_desc, &battery_cfg); + if (IS_ERR(battery->psy_wireless)) { + ret = PTR_ERR(battery->psy_wireless); + dev_err(battery->dev, + "%s: Failed to Register psy_wireless(%d)\n", __func__, ret); + goto err_supply_unreg_pogo; + } + battery->psy_wireless->supplied_to = supply_list; + battery->psy_wireless->num_supplicants = ARRAY_SIZE(supply_list); + + battery->psy_otg = power_supply_register(&pdev->dev, &otg_power_supply_desc, &battery_cfg); + if (IS_ERR(battery->psy_otg)) { + ret = PTR_ERR(battery->psy_otg); + dev_err(battery->dev, + "%s: Failed to Register psy_otg(%d)\n", __func__, ret); + goto err_supply_unreg_wireless; + } + + if (device_create_file(&battery->psy_wireless->dev, &dev_attr_sgf)) + dev_err(battery->dev, + "%s: failed to create sgf attr\n", __func__); + + ret = sec_bat_create_attrs(&battery->psy_bat->dev); + if (ret) { + dev_err(battery->dev, + "%s : Failed to sec_bat_create_attrs\n", __func__); + goto err_req_irq; + } + + ret = sec_pogo_create_attrs(&battery->psy_pogo->dev); + if (ret) { + dev_err(battery->dev, + "%s : Failed to sec_pogo_create_attrs\n", __func__); + goto err_req_irq; + } + + ret = sec_otg_create_attrs(&battery->psy_otg->dev); + if (ret) { + dev_err(battery->dev, + "%s : Failed to sec_otg_create_attrs\n", __func__); + goto err_req_irq; + } + + ret = sb_full_soc_init(battery); + dev_info(battery->dev, "%s: sb_full_soc (%s)\n", __func__, (ret) ? "fail" : "success"); + + ret = sb_notify_register(&battery->sb_nb, sb_handle_notification, "battery", SB_DEV_BATTERY); + dev_info(battery->dev, "%s: sb_notify_register (%s)\n", __func__, (ret) ? "fail" : "success"); + + sb_ca_init(battery->dev); + sb_bd_init(); + + __pm_stay_awake(battery->dev_init_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->dev_init_work, 0); + + pr_info("%s: SEC Battery Driver Loaded\n", __func__); + return 0; + +err_req_irq: + power_supply_unregister(battery->psy_otg); +err_supply_unreg_wireless: + power_supply_unregister(battery->psy_wireless); +err_supply_unreg_pogo: + power_supply_unregister(battery->psy_pogo); +err_supply_unreg_bat: + power_supply_unregister(battery->psy_bat); +err_supply_unreg_ac: + power_supply_unregister(battery->psy_ac); +err_supply_unreg_usb: + power_supply_unregister(battery->psy_usb); +err_workqueue: + destroy_workqueue(battery->monitor_wqueue); +err_irq: + wakeup_source_unregister(battery->monitor_ws); + wakeup_source_unregister(battery->cable_ws); + wakeup_source_unregister(battery->vbus_ws); + wakeup_source_unregister(battery->input_ws); + wakeup_source_unregister(battery->siop_level_ws); +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + wakeup_source_unregister(battery->ext_event_ws); + wakeup_source_unregister(battery->wpc_tx_ws); + wakeup_source_unregister(battery->wpc_tx_en_ws); + wakeup_source_unregister(battery->tx_event_ws); + wakeup_source_unregister(battery->wc20_current_ws); + wakeup_source_unregister(battery->wc_ept_timeout_ws); +#endif +#if defined(CONFIG_UPDATE_BATTERY_DATA) + wakeup_source_unregister(battery->batt_data_ws); +#endif + wakeup_source_unregister(battery->misc_event_ws); + wakeup_source_unregister(battery->parse_mode_dt_ws); + wakeup_source_unregister(battery->dev_init_ws); + wakeup_source_unregister(battery->usb_conn_check_ws); + mutex_destroy(&battery->iolock); + mutex_destroy(&battery->misclock); + mutex_destroy(&battery->txeventlock); + mutex_destroy(&battery->batt_handlelock); + mutex_destroy(&battery->current_eventlock); + mutex_destroy(&battery->typec_notylock); +#if IS_ENABLED(CONFIG_MTK_CHARGER) && !IS_ENABLED(CONFIG_VIRTUAL_MUIC) + mutex_destroy(&battery->bc12_notylock); +#endif + mutex_destroy(&battery->wclock); + mutex_destroy(&battery->voutlock); +err_pdata_free: + if (battery->pdata->soc_by_repcap_en) + kfree(pdata); +err_bat_free: + kfree(battery); + + return ret; +} + +static int sec_battery_remove(struct platform_device *pdev) +{ + struct sec_battery_info *battery = platform_get_drvdata(pdev); + + pr_info("%s: ++\n", __func__); + + switch (battery->pdata->polling_type) { + case SEC_BATTERY_MONITOR_WORKQUEUE: + cancel_delayed_work(&battery->polling_work); + break; + case SEC_BATTERY_MONITOR_ALARM: + alarm_cancel(&battery->polling_alarm); + break; + default: + break; + } + unregister_batterylog_proc(); + flush_workqueue(battery->monitor_wqueue); + destroy_workqueue(battery->monitor_wqueue); + wakeup_source_unregister(battery->monitor_ws); + wakeup_source_unregister(battery->cable_ws); + wakeup_source_unregister(battery->vbus_ws); + wakeup_source_unregister(battery->input_ws); + wakeup_source_unregister(battery->siop_level_ws); + wakeup_source_unregister(battery->misc_event_ws); +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + wakeup_source_unregister(battery->ext_event_ws); + wakeup_source_unregister(battery->wpc_tx_ws); + wakeup_source_unregister(battery->wpc_tx_en_ws); + wakeup_source_unregister(battery->tx_event_ws); + wakeup_source_unregister(battery->wc20_current_ws); + wakeup_source_unregister(battery->wc_ept_timeout_ws); +#endif +#if defined(CONFIG_UPDATE_BATTERY_DATA) + wakeup_source_unregister(battery->batt_data_ws); +#endif + wakeup_source_unregister(battery->parse_mode_dt_ws); + wakeup_source_unregister(battery->dev_init_ws); + wakeup_source_unregister(battery->usb_conn_check_ws); + mutex_destroy(&battery->iolock); + mutex_destroy(&battery->misclock); + mutex_destroy(&battery->txeventlock); + mutex_destroy(&battery->batt_handlelock); + mutex_destroy(&battery->current_eventlock); + mutex_destroy(&battery->typec_notylock); +#if IS_ENABLED(CONFIG_MTK_CHARGER) && !IS_ENABLED(CONFIG_VIRTUAL_MUIC) + mutex_destroy(&battery->bc12_notylock); +#endif + mutex_destroy(&battery->wclock); + mutex_destroy(&battery->voutlock); + + adc_exit(battery); + + power_supply_unregister(battery->psy_otg); + power_supply_unregister(battery->psy_wireless); + power_supply_unregister(battery->psy_pogo); + power_supply_unregister(battery->psy_ac); + power_supply_unregister(battery->psy_usb); + power_supply_unregister(battery->psy_bat); + + kfree(battery); + + pr_info("%s: --\n", __func__); + + return 0; +} + +static int sec_battery_prepare(struct device *dev) +{ + struct sec_battery_info *battery + = dev_get_drvdata(dev); + + dev_info(battery->dev, "%s: Start\n", __func__); + + switch (battery->pdata->polling_type) { + case SEC_BATTERY_MONITOR_WORKQUEUE: + cancel_delayed_work(&battery->polling_work); + break; + case SEC_BATTERY_MONITOR_ALARM: + alarm_cancel(&battery->polling_alarm); + break; + default: + break; + } + + /* monitor_ws should be unlocked before cancel monitor_work */ + __pm_relax(battery->monitor_ws); + cancel_delayed_work_sync(&battery->monitor_work); + + battery->polling_in_sleep = true; + + sec_bat_set_polling(battery); + + /* + * cancel work for polling + * that is set in sec_bat_set_polling() + * no need for polling in sleep + */ + if (battery->pdata->polling_type == + SEC_BATTERY_MONITOR_WORKQUEUE) + cancel_delayed_work(&battery->polling_work); + + dev_info(battery->dev, "%s: End\n", __func__); + + return 0; +} + +static int sec_battery_suspend(struct device *dev) +{ + return 0; +} + +static int sec_battery_resume(struct device *dev) +{ + return 0; +} + +static void sec_battery_complete(struct device *dev) +{ + struct sec_battery_info *battery + = dev_get_drvdata(dev); + + dev_info(battery->dev, "%s: Start\n", __func__); + + /* cancel current alarm and reset after monitor work */ + if (battery->pdata->polling_type == SEC_BATTERY_MONITOR_ALARM) + alarm_cancel(&battery->polling_alarm); + + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->monitor_work, 0); + + dev_info(battery->dev, "%s: End\n", __func__); + + return; +} + +static void sec_battery_shutdown(struct platform_device *pdev) +{ + struct sec_battery_info *battery = platform_get_drvdata(pdev); + + pr_info("%s: ++\n", __func__); + + switch (battery->pdata->polling_type) { + case SEC_BATTERY_MONITOR_WORKQUEUE: + cancel_delayed_work(&battery->polling_work); + break; + case SEC_BATTERY_MONITOR_ALARM: + alarm_cancel(&battery->polling_alarm); + break; + default: + break; + } + + cancel_delayed_work(&battery->monitor_work); + cancel_delayed_work(&battery->cable_work); + cancel_delayed_work(&battery->slowcharging_work); + cancel_delayed_work(&battery->input_check_work); + cancel_delayed_work(&battery->siop_level_work); +#if defined(CONFIG_WIRELESS_FIRMWARE_UPDATE) + cancel_delayed_work(&battery->fw_init_work); +#endif +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + cancel_delayed_work(&battery->wpc_tx_work); + cancel_delayed_work(&battery->wpc_tx_en_work); + cancel_delayed_work(&battery->wpc_txpower_calc_work); + cancel_delayed_work(&battery->ext_event_work); + cancel_delayed_work(&battery->wc20_current_work); + cancel_delayed_work(&battery->wc_ept_timeout_work); +#endif +#if defined(CONFIG_UPDATE_BATTERY_DATA) + cancel_delayed_work(&battery->batt_data_work); +#endif + cancel_delayed_work(&battery->misc_event_work); + cancel_delayed_work(&battery->parse_mode_dt_work); + cancel_delayed_work(&battery->dev_init_work); + cancel_delayed_work(&battery->afc_init_work); + cancel_delayed_work(&battery->usb_conn_check_work); + cancel_delayed_work(&battery->transit_clear_work); + + pr_info("%s: --\n", __func__); +} + +#ifdef CONFIG_OF +static struct of_device_id sec_battery_dt_ids[] = { + { .compatible = "samsung,sec-battery" }, + { } +}; +MODULE_DEVICE_TABLE(of, sec_battery_dt_ids); +#endif /* CONFIG_OF */ + +static const struct dev_pm_ops sec_battery_pm_ops = { + .prepare = sec_battery_prepare, + .suspend = sec_battery_suspend, + .resume = sec_battery_resume, + .complete = sec_battery_complete, +}; + +static struct platform_driver sec_battery_driver = { + .driver = { + .name = "sec-battery", + .owner = THIS_MODULE, + .pm = &sec_battery_pm_ops, +#ifdef CONFIG_OF + .of_match_table = sec_battery_dt_ids, +#endif + }, + .probe = sec_battery_probe, + .remove = sec_battery_remove, + .shutdown = sec_battery_shutdown, +}; + +static int __init sec_battery_init(void) +{ + sec_chg_init_gdev(); + return platform_driver_register(&sec_battery_driver); +} + +static void __exit sec_battery_exit(void) +{ + platform_driver_unregister(&sec_battery_driver); +} + +late_initcall(sec_battery_init); +module_exit(sec_battery_exit); + +MODULE_DESCRIPTION("Samsung Battery Driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/battery/common/sec_battery.h b/drivers/battery/common/sec_battery.h new file mode 100644 index 000000000000..e5592f3a0e1c --- /dev/null +++ b/drivers/battery/common/sec_battery.h @@ -0,0 +1,1432 @@ +/* + * sec_battery.h + * Samsung Mobile Battery Header + * + * + * Copyright (C) 2012 Samsung Electronics, 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 __SEC_BATTERY_H +#define __SEC_BATTERY_H __FILE__ + +#include "sec_charging_common.h" +#include +#include +#include +#include +#include +#include +#include + +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) +#include +#endif + +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) +#include +#else +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) +#include +#include +#endif +#endif +#include +#include "sec_cisd.h" +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) +#include "sec_direct_charger.h" +#endif +#if defined(CONFIG_WIRELESS_AUTH) +#include "sec_battery_misc.h" +#endif +#include "sec_adc.h" +#include "sb_checklist_app.h" +#include "sb_full_soc.h" + +extern const char *sb_get_ct_str(int cable_type); +extern const char *sb_get_cm_str(int chg_mode); +extern const char *sb_get_bst_str(int st); +extern const char *sb_get_hl_str(int health); +extern const char *sb_get_tz_str(int tz); +extern const char *sb_charge_mode_str(int charge_mode); + +#ifndef EXPORT_SYMBOL_KUNIT +#define EXPORT_SYMBOL_KUNIT(sym) /* nothing */ +#endif + +/* current event */ +#define SEC_BAT_CURRENT_EVENT_NONE 0x000000 +#define SEC_BAT_CURRENT_EVENT_AFC 0x000001 +#define SEC_BAT_CURRENT_EVENT_CHARGE_DISABLE 0x000002 +#define SEC_BAT_CURRENT_EVENT_SKIP_HEATING_CONTROL 0x000004 +#define SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING_COOL1 0x000008 +#define SEC_BAT_CURRENT_EVENT_HIGH_TEMP_SWELLING 0x000020 +#define SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING_COOL2 0x000080 +#define SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING_COOL3 0x000010 +#define SEC_BAT_CURRENT_EVENT_SWELLING_MODE (SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING_COOL1 | SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING_COOL2 | SEC_BAT_CURRENT_EVENT_HIGH_TEMP_SWELLING | SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING_COOL3) +#define SEC_BAT_CURRENT_EVENT_LOW_TEMP_MODE (SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING_COOL1 | SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING_COOL2 | SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING_COOL3) +#define SEC_BAT_CURRENT_EVENT_CHG_LIMIT 0x000200 +#define SEC_BAT_CURRENT_EVENT_CALL 0x000400 +#define SEC_BAT_CURRENT_EVENT_SLATE 0x000800 +#define SEC_BAT_CURRENT_EVENT_VBAT_OVP 0x001000 +#define SEC_BAT_CURRENT_EVENT_VSYS_OVP 0x002000 +#define SEC_BAT_CURRENT_EVENT_WPC_VOUT_LOCK 0x004000 +#define SEC_BAT_CURRENT_EVENT_AICL 0x008000 +#define SEC_BAT_CURRENT_EVENT_HV_DISABLE 0x010000 +#define SEC_BAT_CURRENT_EVENT_SELECT_PDO 0x020000 +#define SEC_BAT_CURRENT_EVENT_FG_RESET 0x040000 +#define SEC_BAT_CURRENT_EVENT_WDT_EXPIRED 0x080000 +#define SEC_BAT_CURRENT_EVENT_NOPD_HV_DISABLE 0x100000 +#if defined(CONFIG_ISDB_CHARGING_CONTROL) +#define SEC_BAT_CURRENT_EVENT_ISDB 0x200000 +#else +#define SEC_BAT_CURRENT_EVENT_ISDB 0x000000 +#endif +#define SEC_BAT_CURRENT_EVENT_DC_ERR 0x400000 +#define SEC_BAT_CURRENT_EVENT_SIOP_LIMIT 0x800000 +#define SEC_BAT_CURRENT_EVENT_TEMP_CTRL_TEST 0x1000000 +#define SEC_BAT_CURRENT_EVENT_25W_OCP 0x2000000 +#define SEC_BAT_CURRENT_EVENT_AFC_DISABLE 0x4000000 +#define SEC_BAT_CURRENT_EVENT_SEND_UVDM 0x8000000 + +#define SEC_BAT_CURRENT_EVENT_USB_SUSPENDED 0x10000000 +#define SEC_BAT_CURRENT_EVENT_USB_SUPER 0x20000000 +#define SEC_BAT_CURRENT_EVENT_USB_100MA 0x40000000 +#define SEC_BAT_CURRENT_EVENT_USB_STATE (SEC_BAT_CURRENT_EVENT_USB_SUSPENDED |\ + SEC_BAT_CURRENT_EVENT_USB_SUPER |\ + SEC_BAT_CURRENT_EVENT_USB_100MA) +#define SEC_BAT_CURRENT_EVENT_WPC_EN 0x80000000 + +/* misc_event */ +#define BATT_MISC_EVENT_UNDEFINED_RANGE_TYPE 0x00000001 +#define BATT_MISC_EVENT_WIRELESS_BACKPACK_TYPE 0x00000002 +#define BATT_MISC_EVENT_TIMEOUT_OPEN_TYPE 0x00000004 +#define BATT_MISC_EVENT_BATT_RESET_SOC 0x00000008 +#define BATT_MISC_EVENT_WATER_HICCUP_TYPE 0x00000020 +#define BATT_MISC_EVENT_WIRELESS_DET_LEVEL 0x00000040 +#define BATT_MISC_EVENT_WIRELESS_FOD 0x00000100 +#define BATT_MISC_EVENT_WIRELESS_AUTH_START 0x00000200 +#define BATT_MISC_EVENT_WIRELESS_AUTH_RECVED 0x00000400 +#define BATT_MISC_EVENT_WIRELESS_AUTH_FAIL 0x00000800 +#define BATT_MISC_EVENT_WIRELESS_AUTH_PASS 0x00001000 +#define BATT_MISC_EVENT_TEMP_HICCUP_TYPE 0x00002000 +#define BATT_MISC_EVENT_DIRECT_POWER_MODE 0x00004000 +#define BATT_MISC_EVENT_BATTERY_HEALTH 0x000F0000 +#define BATT_MISC_EVENT_HEALTH_OVERHEATLIMIT 0x00100000 +//#define BATT_MISC_EVENT_ABNORMAL_PAD 0x00200000 +#define BATT_MISC_EVENT_WIRELESS_MISALIGN 0x00400000 +#define BATT_MISC_EVENT_FULL_CAPACITY 0x01000000 +#define BATT_MISC_EVENT_PASS_THROUGH 0x02000000 +#define BATT_MISC_EVENT_MAIN_POWERPATH 0x04000000 +#define BATT_MISC_EVENT_SUB_POWERPATH 0x08000000 +#define BATT_MISC_EVENT_HV_BY_AICL 0x10000000 +#define BATT_MISC_EVENT_WC_JIG_PAD 0x20000000 + +#define BATTERY_HEALTH_SHIFT 16 +enum misc_battery_health { + BATTERY_HEALTH_UNKNOWN = 0, + BATTERY_HEALTH_GOOD, + BATTERY_HEALTH_NORMAL, + BATTERY_HEALTH_AGED, + BATTERY_HEALTH_MAX = BATTERY_HEALTH_AGED, + + /* For event */ + BATTERY_HEALTH_BAD = 0xF, +}; +#define BATT_MISC_EVENT_MUIC_ABNORMAL (BATT_MISC_EVENT_UNDEFINED_RANGE_TYPE |\ + BATT_MISC_EVENT_WATER_HICCUP_TYPE |\ + BATT_MISC_EVENT_TEMP_HICCUP_TYPE) + +#define DEFAULT_HEALTH_CHECK_COUNT 5 + +#define SIOP_INPUT_LIMIT_CURRENT 1200 +#define SIOP_CHARGING_LIMIT_CURRENT 1800 +#define SIOP_WIRELESS_INPUT_LIMIT_CURRENT 600 +#define SIOP_HV_WIRELESS_INPUT_LIMIT_CURRENT 700 +#define SIOP_STORE_HV_WIRELESS_CHARGING_LIMIT_CURRENT 450 +#define SIOP_HV_INPUT_LIMIT_CURRENT 700 +#define SIOP_HV_CHARGING_LIMIT_CURRENT 1800 +#define SIOP_HV_12V_INPUT_LIMIT_CURRENT 535 +#define SIOP_HV_12V_CHARGING_LIMIT_CURRENT 1000 +#define SIOP_APDO_INPUT_LIMIT_CURRENT 1000 +#define SIOP_APDO_CHARGING_LIMIT_CURRENT 2000 + +#define WIRELESS_OTG_INPUT_CURRENT 900 + +enum { + SEC_INPUT_VOLTAGE_0V = 0, + SEC_INPUT_VOLTAGE_NONE = 1000, + SEC_INPUT_VOLTAGE_APDO = 1234, + SEC_INPUT_VOLTAGE_5V = 5000, + SEC_INPUT_VOLTAGE_5_5V = 5500, + SEC_INPUT_VOLTAGE_9V = 9000, + SEC_INPUT_VOLTAGE_10V = 10000, + SEC_INPUT_VOLTAGE_12V = 12000, + SEC_INPUT_VOLTAGE_12_5V = 12500, +}; + +#define HV_CHARGER_STATUS_STANDARD1 12000 /* mW */ +#define HV_CHARGER_STATUS_STANDARD2 20000 /* mW */ +#define HV_CHARGER_STATUS_STANDARD3 24500 /* mW */ +#define HV_CHARGER_STATUS_STANDARD4 40000 /* mW */ + +#define WFC10_WIRELESS_POWER 7500000 /* mW */ +#define WFC20_WIRELESS_POWER 12000000 /* mW */ +#define WFC21_WIRELESS_POWER 15000000 /* mW */ + +#define mW_by_mVmA(v, a) ((v) * (a) / 1000) +#define mV_by_mWmA(w, a) ((a) ? (((w) * 1000) / (a)) : (0)) +#define mA_by_mWmV(w, v) ((v) ? (((w) * 1000) / (v)) : (0)) + +enum battery_misc_test { + MISC_TEST_RESET = 0, + MISC_TEST_DISPLAY, + MISC_TEST_EPT_UNKNOWN, + MISC_TEST_MAX, +}; + +enum { + NORMAL_TA, + AFC_9V_OR_15W, + AFC_12V_OR_20W, + SFC_25W, + SFC_45W, +}; + +struct sec_bat_pdic_list { + unsigned int max_pd_count; + bool now_isApdo; + unsigned int num_fpdo; + unsigned int num_apdo; +}; + +enum { + USB_CONN_NORMAL = 0x0, + USB_CONN_SLOPE_OVER = 0x1, + USB_CONN_GAP_OVER1 = 0x2, + USB_CONN_GAP_OVER2 = 0x4, + USB_CONN_OVERHEATLIMIT = 0x8, +}; + +#define MAX_USB_CONN_CHECK_CNT 10 + +typedef struct sec_charging_current { + unsigned int input_current_limit; + unsigned int fast_charging_current; +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + unsigned int main_limiter_current; + unsigned int sub_limiter_current; +#endif +} sec_charging_current_t; + +/** + * struct sec_bat_adc_table_data - adc to temperature table for sec battery + * driver + * @adc: adc value + * @temperature: temperature(C) * 10 + */ +typedef struct sec_bat_adc_table_data { + int adc; + int data; +} sec_bat_adc_table_data_t; + +typedef struct sec_bat_adc_region { + int min; + int max; +} sec_bat_adc_region_t; + +/* nv, hv, fpdo, apdo, wpc, wpc_hv */ +enum sec_siop_curr_type { + SIOP_CURR_TYPE_NV = 0, + SIOP_CURR_TYPE_HV, + SIOP_CURR_TYPE_FPDO, + SIOP_CURR_TYPE_APDO, + SIOP_CURR_TYPE_WPC_NV, + SIOP_CURR_TYPE_WPC_HV, + SIOP_CURR_TYPE_MAX, +}; + +struct sec_siop_table { + int level; + int icl[SIOP_CURR_TYPE_MAX]; + int fcc[SIOP_CURR_TYPE_MAX]; +}; + +#define SIOP_SCENARIO_NUM_MAX 10 + +struct sec_bat_thm_info { + int source; + sec_bat_adc_table_data_t *adc_table; + unsigned int adc_table_size; + int offset; + int check_type; + unsigned int check_count; + int test; + int adc; + int channel; +}; + +enum sec_battery_thm_info { + THM_INFO_NONE = 0, + THM_INFO_BAT, + THM_INFO_USB, + THM_INFO_CHG, + THM_INFO_WPC, + THM_INFO_SUB_BAT, + THM_INFO_BLK, + THM_INFO_DCHG, +}; + +/* LRP structure */ +#define LRP_PROPS 12 +#define FOREACH_LRP_TYPE(GEN_LRP_TYPE) \ + GEN_LRP_TYPE(LRP_NORMAL) \ + GEN_LRP_TYPE(LRP_25W) \ + GEN_LRP_TYPE(LRP_45W) \ + GEN_LRP_TYPE(LRP_MAX) + +#define GENERATE_LRP_ENUM(ENUM) ENUM, +#define GENERATE_LRP_STRING(STRING) #STRING, + +enum LRP_TYPE_ENUM { + FOREACH_LRP_TYPE(GENERATE_LRP_ENUM) +}; + +static const char * const LRP_TYPE_STRING[] = { + FOREACH_LRP_TYPE(GENERATE_LRP_STRING) +}; + +enum { + LRP_NONE = 0, + LRP_STEP1, + LRP_STEP2, +}; + +enum { + ST1 = 0, + ST2, +}; + +enum { + LCD_ON = 0, + LCD_OFF, +}; + +struct lrp_temp_t { + int trig[2][2]; + int recov[2][2]; +}; + +struct lrp_current_t { + int st_icl[2]; + int st_fcc[2]; +}; + +typedef struct sec_battery_platform_data { + /* NO NEED TO BE CHANGED */ + /* callback functions */ + void (*initial_check)(void); + void (*monitor_additional_check)(void); + bool (*bat_gpio_init)(void); + bool (*fg_gpio_init)(void); + bool (*is_lpm)(void); + bool (*check_jig_status)(void); + bool (*is_interrupt_cable_check_possible)(int); + int (*check_cable_callback)(void); + int (*get_cable_from_extended_cable_type)(int); + bool (*cable_switch_check)(void); + bool (*cable_switch_normal)(void); + bool (*check_cable_result_callback)(int); + bool (*check_battery_callback)(void); + bool (*check_battery_result_callback)(void); + int (*ovp_uvlo_callback)(void); + bool (*ovp_uvlo_result_callback)(int); + bool (*fuelalert_process)(bool); + bool (*get_temperature_callback)( + enum power_supply_property, + union power_supply_propval*); + + /* ADC region by power supply type + * ADC region should be exclusive + */ + sec_bat_adc_region_t *cable_adc_value; + /* charging current for type (0: not use) */ + sec_charging_current_t charging_current[SEC_BATTERY_CABLE_MAX]; + unsigned int *polling_time; + char *chip_vendor; + /* NO NEED TO BE CHANGED */ + unsigned int pre_afc_input_current; + unsigned int pre_wc_afc_input_current; + unsigned int select_pd_input_current; + unsigned int store_mode_max_input_power; + + char *pmic_name; + + /* battery */ + char *vendor; + int technology; + void *battery_data; + + int bat_gpio_ta_nconnected; + /* 1 : active high, 0 : active low */ + int bat_polarity_ta_nconnected; + sec_battery_cable_check_t cable_check_type; + sec_battery_cable_source_t cable_source_type; + + unsigned int swelling_high_rechg_voltage; + unsigned int swelling_low_rechg_voltage; + unsigned int swelling_low_cool3_rechg_voltage; + bool chgen_over_swell_rechg_vol; + +#if IS_ENABLED(CONFIG_STEP_CHARGING) + /* step charging */ + unsigned int **step_chg_cond; + unsigned int **wpc_step_chg_cond; + unsigned int **step_chg_cond_soc; +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + unsigned int **step_chg_cond_sub; +#endif + unsigned int *step_chg_cond_curr; + unsigned int **step_chg_curr; + unsigned int **step_chg_vfloat; + unsigned int *wpc_step_chg_cond_curr; + unsigned int **wpc_step_chg_curr; + unsigned int **wpc_step_chg_vfloat; +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + unsigned int **dc_step_chg_cond_vol_sub; + unsigned int dc_step_cond_v_margin_main; + unsigned int dc_step_cond_v_margin_sub; + unsigned int sc_vbat_thresh_main; /* main vbat threshold which dc to sc */ + unsigned int sc_vbat_thresh_sub; /* sub vbat threshold which dc to sc */ +#endif + unsigned int dc_step_chg_cond_v_margin; + unsigned int **dc_step_chg_cond_vol; + unsigned int **dc_step_chg_cond_soc; + unsigned int *dc_step_chg_cond_iin; + unsigned int *dc_step_chg_vol_offset; + int dc_step_chg_iin_check_cnt; + + unsigned int **dc_step_chg_val_iout; + unsigned int **dc_step_chg_val_vfloat; +#endif +#endif + + /* Monitor setting */ + int polling_type; + /* for initial check */ + unsigned int monitor_initial_count; + + /* Battery check */ + sec_battery_check_t battery_check_type; + /* how many times do we need to check battery */ + unsigned int check_count; + /* ADC */ + /* battery check ADC maximum value */ + unsigned int check_adc_max; + /* battery check ADC minimum value */ + unsigned int check_adc_min; + + /* OVP/UVLO check */ + int ovp_uvlo_check_type; + + struct sec_bat_thm_info bat_thm_info; + struct sec_bat_thm_info usb_thm_info; + struct sec_bat_thm_info chg_thm_info; + struct sec_bat_thm_info wpc_thm_info; + struct sec_bat_thm_info sub_bat_thm_info; + struct sec_bat_thm_info blk_thm_info; + struct sec_bat_thm_info dchg_thm_info; + bool dctp_by_cgtp; + bool dctp_bootmode_en; + bool lrpts_by_batts; + int usb_temp_check_type_backup; /* sec_bat_set_temp_control_test() */ + int lrp_temp_check_type; + unsigned int temp_check_count; + + sec_bat_adc_table_data_t *inbat_adc_table; + unsigned int inbat_adc_table_size; + unsigned int inbat_voltage; + unsigned int inbat_ocv_type; + + /* + * limit can be ADC value or Temperature + * depending on temp_check_type + * temperature should be temp x 10 (0.1 degree) + */ + int wireless_cold_cool3_thresh; + int wireless_cool3_cool2_thresh; + int wireless_cool2_cool1_thresh; + int wireless_cool1_normal_thresh; + int wireless_normal_warm_thresh; + int wireless_warm_overheat_thresh; + + int wire_cold_cool3_thresh; + int wire_cool3_cool2_thresh; + int wire_cool2_cool1_thresh; + int wire_cool1_normal_thresh; + int wire_normal_warm_thresh; + int wire_warm_overheat_thresh; + + int wire_warm_current; + int wire_cool1_current; + int wire_cool2_current; + int wire_cool3_current; + int wireless_warm_current; + int wireless_cool1_current; + int wireless_cool2_current; + int wireless_cool3_current; + int high_temp_float; + int low_temp_float; + int low_temp_cool3_float; + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + unsigned int limiter_main_warm_current; + unsigned int limiter_sub_warm_current; + unsigned int limiter_main_cool1_current; + unsigned int limiter_sub_cool1_current; + unsigned int limiter_main_cool2_current; + unsigned int limiter_sub_cool2_current; + unsigned int limiter_main_cool3_current; + unsigned int limiter_sub_cool3_current; + unsigned int limiter_aging_float_offset; +#endif + + int buck_recovery_margin; + + int tx_high_threshold; + int tx_high_recovery; + int tx_low_threshold; + int tx_low_recovery; + int chg_12v_high_temp; + int chg_high_temp; + int chg_high_temp_recovery; + int dchg_high_temp[4]; + int dchg_high_temp_recovery[4]; + int dchg_high_batt_temp[4]; + int dchg_high_batt_temp_recovery[4]; + + struct lrp_temp_t lrp_temp[LRP_MAX]; + struct lrp_current_t lrp_curr[LRP_MAX]; + + unsigned int chg_charging_limit_current; + unsigned int chg_input_limit_current; + unsigned int dchg_charging_limit_current; + unsigned int dchg_input_limit_current; + unsigned int sub_temp_control_source; + unsigned int wpc_temp_control_source; + unsigned int wpc_temp_lcd_on_control_source; + int wpc_high_temp; + int wpc_high_temp_recovery; + int wpc_high_temp_12w; + int wpc_high_temp_recovery_12w; + int wpc_high_temp_15w; + int wpc_high_temp_recovery_15w; + unsigned int wpc_input_limit_current; + unsigned int wpc_charging_limit_current; + bool enable_check_wpc_temp_v2; + int wpc_temp_v2_cond; + int wpc_temp_v2_cond_12w; + int wpc_temp_v2_cond_15w; + + int wpc_lrp_high_temp; + int wpc_lrp_high_temp_recovery; + int wpc_lrp_high_temp_12w; + int wpc_lrp_high_temp_recovery_12w; + int wpc_lrp_high_temp_15w; + int wpc_lrp_high_temp_recovery_15w; + int wpc_lrp_temp_v2_cond; + int wpc_lrp_temp_v2_cond_12w; + int wpc_lrp_temp_v2_cond_15w; + + unsigned int wpc_step_limit_size; + unsigned int *wpc_step_limit_temp; + unsigned int *wpc_step_limit_fcc; + unsigned int *wpc_step_limit_fcc_12w; + unsigned int *wpc_step_limit_fcc_15w; + + int wpc_lcd_on_high_temp; + int wpc_lcd_on_high_temp_rec; + int wpc_lcd_on_high_temp_12w; + int wpc_lcd_on_high_temp_rec_12w; + int wpc_lcd_on_high_temp_15w; + int wpc_lcd_on_high_temp_rec_15w; + unsigned int wpc_lcd_on_input_limit_current; + unsigned int wpc_flicker_wa_input_limit_current; + unsigned int sleep_mode_limit_current; + unsigned int wc_full_input_limit_current; + unsigned int max_charging_current; + unsigned int max_charging_charge_power; + unsigned int apdo_max_volt; + + int mix_high_temp; + int mix_high_chg_temp; + int mix_high_temp_recovery; + + bool enable_mix_v2; + int mix_v2_lrp_recov; + int mix_v2_lrp_cond; + int mix_v2_bat_cond; + int mix_v2_chg_cond; + int mix_v2_dchg_cond; + + bool wpc_high_check_using_lrp; + + unsigned int icl_by_tx_gear; /* check limited charging current during wireless power sharing with cable charging */ + unsigned int fcc_by_tx; + unsigned int fcc_by_tx_gear; + unsigned int wpc_input_limit_by_tx_check; /* check limited wpc input current with tx device */ + unsigned int wpc_input_limit_current_by_tx; + + /* If these is NOT full check type or NONE full check type, + * it is skipped + */ + /* 1st full check */ + int full_check_type; + /* 2nd full check */ + int full_check_type_2nd; + unsigned int full_check_count; + int chg_gpio_full_check; + /* 1 : active high, 0 : active low */ + int chg_polarity_full_check; + sec_battery_full_condition_t full_condition_type; + unsigned int full_condition_soc; + unsigned int full_condition_vcell; + unsigned int full_condition_avgvcell; + unsigned int full_condition_ocv; + + unsigned int recharge_check_count; + sec_battery_recharge_condition_t recharge_condition_type; + unsigned int recharge_condition_soc; + unsigned int recharge_condition_vcell; + + unsigned long charging_reset_time; + + /* fuel gauge */ + char *fuelgauge_name; + + unsigned int store_mode_charging_max; + unsigned int store_mode_charging_min; + unsigned int store_mode_buckoff; + + /* charger */ + char *charger_name; + char *otg_name; + char *fgsrc_switch_name; + bool support_fgsrc_change; + bool dynamic_cv_factor; + bool slowcharging_usb_bootcomplete; + + /* wireless charger */ + char *wireless_charger_name; + int wireless_cc_cv; + bool p2p_cv_headroom; + + /* float voltage (mV) */ + unsigned int chg_float_voltage; + unsigned int chg_float_voltage_conv; + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + /* current limiter */ + char *dual_battery_name; + char *main_limiter_name; + char *sub_limiter_name; + int main_bat_enb_gpio; + int main_bat_enb2_gpio; + int sub_bat_enb_gpio; +#endif + +#if IS_ENABLED(CONFIG_DUAL_FUELGAUGE) + char *dual_fuelgauge_name; + char *main_fuelgauge_name; + char *sub_fuelgauge_name; + unsigned int main_design_capacity; + unsigned int sub_design_capacity; +#endif + + int num_age_step; + int age_step; + sec_age_data_t* age_data; + battery_health_condition* health_condition; + + int siop_icl; + int siop_fcc; + int siop_hv_icl; + int siop_hv_icl_2nd; + int siop_hv_fcc; + int siop_hv_12v_icl; + int siop_hv_12v_fcc; + int siop_apdo_icl; + int siop_apdo_fcc; + + int siop_wpc_icl; + int *siop_wpc_fcc; + int siop_hv_wpc_icl; + int rechg_hv_wpc_icl; + int *siop_hv_wpc_fcc; + int wireless_otg_input_current; + int wc_hero_stand_cc_cv; + int wc_hero_stand_cv_current; + int wc_hero_stand_hv_cv_current; + + int siop_scenarios_num; + int siop_curr_type_num; + struct sec_siop_table siop_table[SIOP_SCENARIO_NUM_MAX]; + + int default_input_current; + int default_charging_current; + int default_usb_input_current; + int default_usb_charging_current; + unsigned int default_wc20_input_current; + unsigned int default_wc20_charging_current; + unsigned int default_mpp_input_current; + unsigned int default_mpp_charging_current; + int max_input_voltage; + int max_input_current; + int pre_afc_work_delay; + int pre_wc_afc_work_delay; + + unsigned int rp_current_rp1; + unsigned int rp_current_rp2; + unsigned int rp_current_rp3; + unsigned int rp_current_rdu_rp3; + unsigned int rp_current_abnormal_rp3; + + bool fake_capacity; + bool en_auto_shipmode_temp_ctrl; + bool boosting_voltage_aicl; + bool tx_5v_disable; + unsigned int phm_vout_ctrl_dev; + unsigned int power_value; + + bool bc12_ifcon_wa; + + bool mass_with_usb_thm; + bool usb_protection; + + /* tx power sharging */ + unsigned int tx_stop_capacity; + + unsigned int battery_full_capacity; + unsigned int cisd_cap_high_thr; + unsigned int cisd_cap_low_thr; + unsigned int cisd_cap_limit; + int max_voltage_thr; + unsigned int cisd_alg_index; + unsigned int *ignore_cisd_index; + unsigned int *ignore_cisd_index_d; + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + /* zone 1 : 0C ~ 0.4C */ + unsigned int zone1_limiter_current; + unsigned int main_zone1_current_rate; + unsigned int sub_zone1_current_rate; + /* zone 2 : 0.4C ~ 1.1C */ + unsigned int zone2_limiter_current; + unsigned int main_zone2_current_rate; + unsigned int sub_zone2_current_rate; + /* zone 3 : 1.1C ~ MAX */ + unsigned int zone3_limiter_current; + unsigned int main_zone3_current_rate; + unsigned int sub_zone3_current_rate; + + unsigned int force_recharge_margin; + unsigned int max_main_limiter_current; + unsigned int min_main_limiter_current; + unsigned int max_sub_limiter_current; + unsigned int min_sub_limiter_current; + /* fully turn on flag */ + bool main_fto; + bool sub_fto; + /* fully turn on threshold current */ + unsigned int main_fto_current_thresh; + unsigned int sub_fto_current_thresh; +#endif + + /* ADC setting */ + unsigned int adc_check_count; + + unsigned int adc_read_type; + + unsigned int full_check_current_1st; + unsigned int full_check_current_2nd; + + unsigned int pd_charging_charge_power; + unsigned int nv_charge_power; + + unsigned int expired_time; + unsigned int recharging_expired_time; + int standard_curr; + + unsigned int tx_minduty_5V; + unsigned int tx_minduty_default; + + unsigned int tx_ping_duty_no_ta; + unsigned int tx_ping_duty_default; + + unsigned int tx_uno_vout; + unsigned int tx_uno_iout; + unsigned int tx_uno_iout_gear; + unsigned int tx_uno_iout_aov_gear; + unsigned int tx_buds_vout; // true wireless stereo type like buds + unsigned int tx_gear_vout; // watch type + unsigned int tx_ping_vout; + unsigned int tx_mfc_iout_gear; + unsigned int tx_mfc_iout_aov_gear; + unsigned int tx_mfc_iout_phone; + unsigned int tx_mfc_iout_phone_5v; + unsigned int tx_mfc_iout_lcd_on; + + unsigned int tx_aov_start_vout; + unsigned int tx_aov_freq_low; + unsigned int tx_aov_freq_high; + unsigned int tx_aov_delay; + unsigned int tx_aov_delay_phm_escape; + + /* MAIN LRPST compensation */ + bool lr_enable; + unsigned int lr_param_bat_thm; + unsigned int lr_param_sub_bat_thm; + unsigned int lr_delta; + unsigned int lr_param_init_bat_thm; + unsigned int lr_param_init_sub_bat_thm; + unsigned int lr_round_off; + + bool wpc_vout_ctrl_lcd_on; + bool soc_by_repcap_en; + + unsigned int d2d_check_type; + bool support_vpdo; + bool support_fpdo_dc; + unsigned int fpdo_dc_charge_power; + + bool sc_LRP_25W; + int batt_temp_adj_gap_inc; + int change_FV_after_full; + + bool support_usb_conn_check; + unsigned int usb_conn_slope_avg; + + bool wpc_warm_fod; + unsigned int wpc_warm_fod_icc; + + unsigned int wc21_icl; + + bool loosened_unknown_temp; + + bool support_spsn_ctrl; + + bool pogo_chgin; +} sec_battery_platform_data_t; + +struct sec_ttf_data; + +struct sec_eoc_info { + bool eoc_check; + unsigned int eoc_cnt; +}; + +struct sec_battery_info { + struct device *dev; + sec_battery_platform_data_t *pdata; + struct sec_ttf_data *ttf_d; + struct sec_eoc_info *eoc_d; + + /* power supply used in Android */ + struct power_supply *psy_bat; + struct power_supply *psy_usb; + struct power_supply *psy_ac; + struct power_supply *psy_wireless; + struct power_supply *psy_pogo; + struct power_supply *psy_otg; + + int pd_usb_attached; +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + struct notifier_block usb_typec_nb; +#else +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + struct notifier_block batt_nb; +#endif +#endif + struct notifier_block sb_nb; + + bool pdic_attach; + bool pdic_ps_rdy; + bool init_src_cap; + SEC_PD_SINK_STATUS sink_status; + SEC_PD_SINK_STATUS *psink_status; + struct sec_bat_pdic_list pd_list; + + bool hv_pdo; + bool update_pd_list; + int d2d_auth; + bool vpdo_src_boost; + bool vpdo_ocp; + int vpdo_auth_stat; + int hp_d2d; + + bool is_sysovlo; + bool is_vbatovlo; + bool is_abnormal_temp; + + bool safety_timer_set; + bool lcd_status; + bool skip_swelling; + bool wc_auth_retried; + + int status; + int health; + bool present; + unsigned int charger_mode; + + int voltage_now; /* cell voltage (mV) */ + int voltage_avg; /* average voltage (mV) */ + int voltage_ocv; /* open circuit voltage (mV) */ + int current_now; /* current (mA) */ + int inbat_adc; /* inbat adc */ + int current_avg; /* average current (mA) */ + int current_max; /* input current limit (mA) */ + int current_sys; /* system current (mA) */ + int current_sys_avg; /* average system current (mA) */ + int charge_counter; /* remaining capacity (uAh) */ + int current_adc; + + int voltage_now_main; /* pack voltage main battery (mV) */ + int voltage_now_sub; /* pack voltage sub battery (mV) */ + int voltage_avg_main; /* pack voltage main battery (mV) */ + int voltage_avg_sub; /* pack voltage sub battery (mV) */ + int current_now_main; /* current from main battery (mA) */ + int current_now_sub; /* current from sub battery (mA) */ + int current_avg_main; /* current from main battery (mA) */ + int current_avg_sub; /* current from sub battery (mA) */ +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + unsigned int limiter_check; + bool set_lower_curr; +#endif + + unsigned int capacity; /* SOC (%) */ +#if IS_ENABLED(CONFIG_DUAL_FUELGAUGE) + unsigned int main_capacity; /* MAIN SOC (%) */ + unsigned int sub_capacity; /* SUB SOC (%) */ +#endif + + unsigned int input_voltage; /* CHGIN/WCIN input voltage (V) */ + unsigned int charge_power; /* charge power (mW) */ + unsigned int max_charge_power; /* max charge power (mW) */ + unsigned int pd_max_charge_power; /* max charge power for pd (mW) */ + unsigned int pd_rated_power; /* rated power for pd (W) */ + + /* keep awake until monitor is done */ + struct wakeup_source *monitor_ws; + struct workqueue_struct *monitor_wqueue; + struct delayed_work monitor_work; + unsigned int polling_count; + unsigned int polling_time; + bool polling_in_sleep; + bool polling_short; + + struct delayed_work polling_work; + struct alarm polling_alarm; + ktime_t last_poll_time; + + struct cisd cisd; + bool skip_cisd; + bool usb_overheat_check; + bool otg_check; + bool d2d_check; + int prev_volt; + int prev_temp; + int prev_jig_on; + int enable_update_data; + int prev_chg_on; + +#if defined(CONFIG_WIRELESS_AUTH) + sec_bat_misc_dev_t *misc_dev; +#endif + + /* battery check */ + unsigned int check_count; + /* ADC check */ + unsigned int check_adc_count; + unsigned int check_adc_value; + + /* health change check*/ + bool health_change; + /* ovp-uvlo health check */ + int health_check_count; + + /* time check */ + unsigned long charging_retention_time; /* retention time of charger connection */ + unsigned long charging_start_time; + unsigned long charging_passed_time; + unsigned long charging_next_time; + unsigned long charging_fullcharged_time; + + unsigned long wc_heating_start_time; + unsigned long wc_heating_passed_time; + + /* chg temperature check */ + unsigned int chg_limit; + unsigned int chg_limit_recovery_cable; + unsigned int mix_limit; + + /* lrp temperature check */ + unsigned int lrp_limit; + unsigned int lrp_step; + + /* temperature check */ + int temperature; /* battery temperature */ +#if defined(CONFIG_ENG_BATTERY_CONCEPT) + bool test_max_current; + bool test_charge_current; +#if IS_ENABLED(CONFIG_STEP_CHARGING) + int test_step_condition; +#endif +#endif + int temper_amb; /* target temperature */ + int usb_temp; + int chg_temp; /* charger temperature */ + int wpc_temp; + int sub_bat_temp; + int usb_conn_status; + int usb_protection_temp; + int temp_gap_bat_usb; + int dchg_temp; + int blkt_temp; /* blanket temperature(instead of batt temp in mix_temp func for tablet model) */ + + int lrp; + int lrp_test; + unsigned int lrp_chg_src; + + int overheatlimit_threshold_backup; /* sec_bat_set_temp_control_test() */ + int overheatlimit_recovery_backup; /* sec_bat_set_temp_control_test() */ + int overheatlimit_threshold; + int overheatlimit_recovery; + int cold_cool3_thresh; + int cool3_cool2_thresh; + int cool2_cool1_thresh; + int cool1_normal_thresh; + int normal_warm_thresh; + int warm_overheat_thresh; + int thermal_zone; + int bat_thm_count; + int adc_init_count; + + /* charging */ + unsigned int charging_mode; + bool is_recharging; + int wdt_kick_disable; + + bool is_jig_on; + int cable_type; + int muic_cable_type; + + bool auto_mode; + + struct wakeup_source *cable_ws; + struct delayed_work cable_work; + struct wakeup_source *vbus_ws; + struct wakeup_source *input_ws; + struct delayed_work input_check_work; +#if defined(CONFIG_WIRELESS_FIRMWARE_UPDATE) + struct delayed_work fw_init_work; +#endif + struct delayed_work siop_level_work; + struct wakeup_source *siop_level_ws; + struct wakeup_source *wpc_tx_ws; + struct delayed_work wpc_tx_work; + struct wakeup_source *wpc_tx_en_ws; + struct delayed_work wpc_tx_en_work; +#if defined(CONFIG_UPDATE_BATTERY_DATA) + struct delayed_work batt_data_work; + struct wakeup_source *batt_data_ws; + char *data_path; +#endif +#ifdef CONFIG_OF + struct delayed_work parse_mode_dt_work; + struct wakeup_source *parse_mode_dt_ws; +#endif + struct delayed_work dev_init_work; + struct wakeup_source *dev_init_ws; + struct delayed_work afc_init_work; + struct delayed_work usb_conn_check_work; + struct wakeup_source *usb_conn_check_ws; + struct delayed_work transit_clear_work; + + char batt_type[48]; + unsigned int full_check_cnt; + unsigned int recharge_check_cnt; + + unsigned int input_check_cnt; + unsigned int usb_conn_check_cnt; + bool run_usb_conn_check; + + struct mutex iolock; + int input_current; + int charging_current; +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + unsigned int main_current; + unsigned int sub_current; +#endif + int topoff_condition; + int wpc_vout_level; + int wpc_max_vout_level; + unsigned int current_event; + + /* wireless charging enable */ + struct mutex wclock; + bool wc_enable; + int wc_enable_cnt; + int wc_enable_cnt_value; + int led_cover; + int mag_cover; + int wc_status; + bool wc_cv_mode; +#if defined(CONFIG_WIRELESS_RX_PHM_CTRL) + bool wc_rx_pdetb_mode; // phm state in case of rx mode with models which support pdet_b gpio +#else + bool wc_rx_phm_mode; // phm state in case of rx mode with models which non-support pdet_b gpio +#endif + bool wc_tx_phm_mode; // phm state in case of tx mode + bool prev_tx_phm_mode; // prev phm state in case of tx mode + bool wc_tx_adaptive_vout; + bool wc_need_ldo_on; + + int wire_status; +#if IS_ENABLED(CONFIG_MTK_CHARGER) && !IS_ENABLED(CONFIG_VIRTUAL_MUIC) + int bc12_cable; +#endif + + /* wireless tx */ + bool wc_tx_enable; + bool wc_rx_connected; + bool afc_disable; + bool pd_disable; + bool buck_cntl_by_tx; + bool tx_switch_mode_change; + int wc_tx_vout; + bool uno_en; + unsigned int wc_rx_type; + unsigned int tx_minduty; + unsigned int tx_ping_duty; + unsigned int tx_switch_mode; + unsigned int tx_switch_start_soc; + + unsigned int tx_mfc_iout; + unsigned int tx_uno_iout; + + int pogo_status; + bool pogo_9v; + + /* test mode */ + int test_mode; + bool factory_mode; + bool factory_mode_boot_on; + bool display_test; + bool store_mode; +#if defined(CONFIG_BC12_DEVICE) && defined(CONFIG_SEC_FACTORY) + bool vbat_adc_open; +#endif + + /* usb suspend */ + int prev_usb_conf; + + /* MTBF test for CMCC */ + bool is_hc_usb; + + int siop_level; + int stability_test; + int eng_not_full_status; + + int wpc_temp_v2_offset; + bool wpc_vout_ctrl_mode; + char *hv_chg_name; +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + bool disable_mfc; + bool nv_wc_temp_ctrl_skip; + int tx_avg_curr; + int tx_time_cnt; + int tx_total_power; + struct delayed_work wpc_txpower_calc_work; + + bool wc_ept_timeout; + unsigned int wc20_vout; + unsigned int wc20_power_class; + unsigned int wc20_rx_power; + struct delayed_work wc20_current_work; + struct delayed_work wc_ept_timeout_work; + struct wakeup_source *wc20_current_ws; + struct wakeup_source *wc_ept_timeout_ws; +#endif + struct delayed_work slowcharging_work; + int batt_cycle; + int batt_asoc; + int batt_full_status_usage; +#if IS_ENABLED(CONFIG_STEP_CHARGING) + bool step_charging_skip_lcd_on; + bool step_chg_en_in_factory; + unsigned int step_chg_type; + unsigned int step_chg_charge_power; + int step_chg_status; + int step_chg_step; +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + unsigned int wpc_step_chg_type; + unsigned int wpc_step_chg_charge_power; + int wpc_step_chg_step; + int wpc_step_chg_status; +#endif +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + int dc_step_chg_step; + unsigned int *dc_step_chg_type; + unsigned int dc_step_chg_charge_power; + + bool dc_float_voltage_set; + unsigned int dc_step_chg_iin_cnt; +#endif +#endif + bool dchg_dc_in_swelling; + struct mutex misclock; + struct mutex txeventlock; + unsigned int misc_event; + unsigned int tx_event; + unsigned int ext_event; + unsigned int prev_misc_event; + unsigned int tx_retry_case; + unsigned int tx_misalign_cnt; + unsigned int tx_ocp_cnt; + struct delayed_work ext_event_work; + struct delayed_work misc_event_work; + struct wakeup_source *ext_event_ws; + struct wakeup_source *misc_event_ws; + struct wakeup_source *tx_event_ws; + struct mutex batt_handlelock; + struct mutex current_eventlock; + struct mutex typec_notylock; +#if IS_ENABLED(CONFIG_MTK_CHARGER) && !IS_ENABLED(CONFIG_VIRTUAL_MUIC) + struct mutex bc12_notylock; +#endif + struct mutex voutlock; + unsigned long tx_misalign_start_time; + unsigned long tx_misalign_passed_time; + unsigned long tx_ocp_start_time; + unsigned long tx_ocp_passed_time; + + unsigned int hiccup_status; + bool hiccup_clear; + + bool stop_timer; + unsigned long prev_safety_time; + unsigned long expired_time; + unsigned long cal_safety_time; + int fg_reset; + + struct sec_vote *fcc_vote; + struct sec_vote *input_vote; + struct sec_vote *fv_vote; + struct sec_vote *dc_fv_vote; + struct sec_vote *chgen_vote; + struct sec_vote *topoff_vote; + struct sec_vote *iv_vote; + + struct sb_full_soc *fs; + + /* 25w ta alert */ + bool ta_alert_wa; + int ta_alert_mode; + + bool sleep_mode; + bool mfc_fw_update; + + int charging_night_mode; + + /* MAIN LRPST compensation */ + unsigned long lr_start_time; + unsigned long lr_time_span; + int lrp_temp; + int lr_bat_t_1; + + bool is_fpdo_dc; + +#if IS_ENABLED(CONFIG_USB_FACTORY_MODE) + bool usb_factory_init; + int usb_factory_mode; +#if defined(CONFIG_SEC_FACTORY) + bool usb_factory_slate_mode; +#endif + unsigned int batt_f_mode; +#endif + bool abnormal_ta; + int srccap_transit_cnt; + bool srccap_transit; + int dc_check_cnt; + bool usb_slow_chg; + bool usb_bootcomplete; + unsigned int flash_state; + unsigned int mst_en; +#if IS_ENABLED(CONFIG_MTK_CHARGER) + unsigned int mtk_fg_init; +#endif +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) + struct notifier_block vbus_nb; +#endif + bool is_otg_on; + bool smart_sw_src; +}; + +enum { + EXT_DEV_NONE = 0, + EXT_DEV_GAMEPAD_CHG, + EXT_DEV_GAMEPAD_OTG, +}; + +#if IS_ENABLED(CONFIG_MTK_CHARGER) && IS_ENABLED(CONFIG_AFC_CHARGER) +extern int afc_set_voltage(int vol); +#endif +extern unsigned int sec_bat_get_lpmode(void); +extern void sec_bat_set_lpmode(unsigned int value); +extern int sec_bat_get_fgreset(void); +extern int sec_bat_get_facmode(void); +extern unsigned int sec_bat_get_chgmode(void); +extern void sec_bat_set_chgmode(unsigned int value); +extern unsigned int sec_bat_get_dispd(void); +extern void sec_bat_set_dispd(unsigned int value); + +extern int adc_read(struct sec_battery_info *battery, int channel); +extern void adc_init(struct platform_device *pdev, struct sec_battery_info *battery); +extern void adc_exit(struct sec_battery_info *battery); +extern void sec_cable_init(struct platform_device *pdev, struct sec_battery_info *battery); +extern int sec_bat_get_adc_data(struct device *dev, int adc_ch, int count, int batt_adc_type); +extern int sec_bat_get_charger_type_adc(struct sec_battery_info *battery); +extern bool sec_bat_convert_adc_to_val(int adc, int offset, + sec_bat_adc_table_data_t *adc_table, int size, int *value); +extern int sec_bat_get_adc_value(struct sec_battery_info *battery, int channel); +extern int sec_bat_get_inbat_vol_by_adc(struct sec_battery_info *battery); +extern bool sec_bat_check_vf_adc(struct sec_battery_info *battery); +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) +extern int sec_bat_get_direct_chg_temp_adc(struct sec_battery_info *battery, int adc_data, int count, int check_type); +#endif +extern void sec_bat_set_misc_event(struct sec_battery_info *battery, unsigned int misc_event_val, unsigned int misc_event_mask); +extern void sec_bat_set_tx_event(struct sec_battery_info *battery, + unsigned int tx_event_val, unsigned int tx_event_mask); +extern void sec_bat_set_current_event(struct sec_battery_info *battery, unsigned int current_event_val, unsigned int current_event_mask); +extern void sec_bat_set_temp_control_test(struct sec_battery_info *battery, bool temp_enable); +extern void sec_bat_get_battery_info(struct sec_battery_info *battery); +extern int sec_bat_set_charging_current(struct sec_battery_info *battery); +extern void sec_bat_aging_check(struct sec_battery_info *battery); + +extern void sec_bat_set_threshold(struct sec_battery_info *battery, int cable_type); +extern void sec_bat_thermal_check(struct sec_battery_info *battery); +extern void sec_bat_set_charging_status(struct sec_battery_info *battery, int status); +void sec_bat_set_health(struct sec_battery_info *battery, int status); +extern bool sec_bat_check_full(struct sec_battery_info *battery, int full_check_type); +extern bool sec_bat_check_fullcharged(struct sec_battery_info *battery); +extern void sec_bat_check_wpc_temp(struct sec_battery_info *battery, int ct, int siop_level); +extern void sec_bat_check_wpc_temp_v2(struct sec_battery_info *battery); +extern void sec_bat_check_mix_temp(struct sec_battery_info *battery, int ct, int siop_level, bool is_apdo); +extern void sec_bat_check_mix_temp_v2(struct sec_battery_info *battery); +extern void sec_bat_check_afc_temp(struct sec_battery_info *battery, int siop_level); +extern void sec_bat_check_pdic_temp(struct sec_battery_info *battery, int siop_level); +extern void sec_bat_check_direct_chg_temp(struct sec_battery_info *battery, int siop_level); +extern int sec_bat_check_power_type(int max_chg_pwr, int pd_max_chg_pwr, int ct, int ws, int is_apdo); +extern void sec_bat_check_lrp_temp(struct sec_battery_info *battery, int ct, int ws, int siop_level, bool lcd_sts); +extern void sec_bat_check_tx_temperature(struct sec_battery_info *battery); +extern void sec_bat_change_default_current(struct sec_battery_info *battery, int cable_type, int input, int output); +extern int sec_bat_set_charge(void *data, int chg_mode); +extern int adjust_bat_temp(struct sec_battery_info *battery, int batt_temp, int sub_bat_temp); +extern int get_chg_power_type(int ct, int ws, int pd_max_pw, int max_pw); +extern int sec_usb_conn_check(struct sec_battery_info *battery); +#if !defined(CONFIG_SEC_FACTORY) +extern void sec_bat_check_temp_ctrl_by_cable(struct sec_battery_info *battery); +#endif + +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) +extern void sec_bat_get_wireless_current(struct sec_battery_info *battery); +extern void sec_bat_mfc_work(struct work_struct *work); +extern int sec_bat_check_wc_available(struct sec_battery_info *battery); +extern bool sec_bat_hv_wc_normal_mode_check(struct sec_battery_info *battery); +extern void sec_bat_ext_event_work_content(struct sec_battery_info *battery); +extern void sec_bat_wpc_tx_work_content(struct sec_battery_info *battery); +extern void sec_bat_wpc_tx_en_work_content(struct sec_battery_info *battery); +extern void sec_bat_set_wc20_current(struct sec_battery_info *battery); +extern void sec_wireless_otg_vout_control(struct sec_battery_info *battery, int enable); +extern void sec_wireless_otg_icl_control(struct sec_battery_info *battery); +extern void sec_bat_set_mfc_off(struct sec_battery_info *battery, char flag, bool need_ept); +extern void sec_bat_set_mfc_on(struct sec_battery_info *battery, char flag); +extern int sec_bat_choose_cable_type(struct sec_battery_info *battery); +extern void sec_bat_handle_tx_misalign(struct sec_battery_info *battery, bool trigger_misalign); +extern void sec_bat_handle_tx_ocp(struct sec_battery_info *battery, bool trigger_ocp); +extern void sec_bat_wireless_minduty_cntl(struct sec_battery_info *battery, unsigned int duty_val); +extern void sec_bat_wireless_uno_cntl(struct sec_battery_info *battery, bool en); +extern void sec_bat_wireless_iout_cntl(struct sec_battery_info *battery, int uno_iout, int mfc_iout); +extern void sec_bat_wireless_vout_cntl(struct sec_battery_info *battery, int vout_now); +extern void sec_bat_check_tx_mode(struct sec_battery_info *battery); +extern void sec_bat_wc_cv_mode_check(struct sec_battery_info *battery); +extern void sec_bat_run_wpc_tx_work(struct sec_battery_info *battery, int work_delay); +extern void sec_bat_txpower_calc(struct sec_battery_info *battery); +extern void sec_wireless_set_tx_enable(struct sec_battery_info *battery, bool wc_tx_enable); +extern void sec_bat_check_wc_re_auth(struct sec_battery_info *battery); +extern unsigned int get_wc20_vout(unsigned int vout); +extern void sec_bat_mfc_ldo_cntl(struct sec_battery_info *battery, bool en); +extern int sec_bat_check_wpc_vout(struct sec_battery_info *battery, int ct, unsigned int chg_limit, + int pre_vout, unsigned int evt); +#else +static inline void sec_bat_set_mfc_off(struct sec_battery_info *battery, char flag, bool need_ept) {} +static inline void sec_bat_set_mfc_on(struct sec_battery_info *battery, char flag) {} +#endif + +#if defined(CONFIG_WIRELESS_FIRMWARE_UPDATE) +extern void sec_bat_fw_update(struct sec_battery_info *battery, int mode); +extern bool sec_bat_check_boost_mfc_condition(struct sec_battery_info *battery, int mode); +#endif + +#if IS_ENABLED(CONFIG_STEP_CHARGING) +extern void sec_bat_reset_step_charging(struct sec_battery_info *battery); +extern void sec_step_charging_init(struct sec_battery_info *battery, struct device *dev); +extern bool sec_bat_check_step_charging(struct sec_battery_info *battery); +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) +extern bool sec_bat_check_wpc_step_charging(struct sec_battery_info *battery); +#endif +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) +extern bool sec_bat_check_dc_step_charging(struct sec_battery_info *battery); +#endif +void sec_bat_set_aging_info_step_charging(struct sec_battery_info *battery); +#endif + +#if !defined(CONFIG_SEC_FACTORY) +bool sales_code_is(char *str); +#endif + +#if defined(CONFIG_UPDATE_BATTERY_DATA) +extern int sec_battery_update_data(const char* file_path); +#endif +extern bool sec_bat_cisd_check(struct sec_battery_info *battery); +extern void sec_battery_cisd_init(struct sec_battery_info *battery); +extern void set_cisd_pad_data(struct sec_battery_info *battery, const char* buf); +extern void set_cisd_power_data(struct sec_battery_info *battery, const char* buf); +extern void set_cisd_pd_data(struct sec_battery_info *battery, const char *buf); + +#if defined(CONFIG_WIRELESS_AUTH) +extern int sec_bat_misc_init(struct sec_battery_info *battery); +#endif + +int sec_bat_parse_dt(struct device *dev, struct sec_battery_info *battery); +void sec_bat_parse_mode_dt(struct sec_battery_info *battery); +void sec_bat_parse_mode_dt_work(struct work_struct *work); +void sec_bat_check_battery_health(struct sec_battery_info *battery); +bool sec_bat_hv_wc_normal_mode_check(struct sec_battery_info *battery); +int sec_bat_get_temperature(struct device *dev, struct sec_bat_thm_info *info, int old_val, + char *chg_name, char *fg_name, int batt_adc_type); +int sec_bat_get_inbat_vol_ocv(struct sec_battery_info *battery); +void sec_bat_smart_sw_src(struct sec_battery_info *battery, bool enable, int curr); + +#endif /* __SEC_BATTERY_H */ diff --git a/drivers/battery/common/sec_battery_data.c b/drivers/battery/common/sec_battery_data.c new file mode 100644 index 000000000000..c27fb172d8ee --- /dev/null +++ b/drivers/battery/common/sec_battery_data.c @@ -0,0 +1,436 @@ +/* + * sec_battery_data.c + * Samsung Mobile Battery Driver + * + * Copyright (C) 2012 Samsung Electronics + * + * + * 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. + */ + +#include +#include +#include +#include +#include + +enum battery_data_type { + BATTERY_DATA_TYPE_NONE = 0, + BATTERY_DATA_TYPE_INFO, + BATTERY_DATA_TYPE_VALUE, + BATTERY_DATA_TYPE_STRING, +}; + +enum battery_data_flag { + BATTERY_DATA_FLAG_NONE = 0, + BATTERY_DATA_FLAG_ADD, + BATTERY_DATA_FLAG_REMOVE, + BATTERY_DATA_FLAG_EDIT, +}; + +#define CHECK_ERROR_DATA(logical_test, return_value, error_value, action) { \ + if (logical_test) { \ + return_value = error_value; \ + pr_err("%s: error!!! (ret = %d)\n", __func__, error_value); \ + action; \ + } else { \ + return_value = 0; \ + } \ +} + +#define NODE_NAME_SIZE 32 +#define PROPERTY_SIZE 68 +#define MAX_VALUE_SIZE (5 * 1024) + +struct battery_data_info { + int version; + int hw_rev; + int prop_count; + int value_length; +}; + +struct battery_data { + unsigned int type; + unsigned int flag; + char node_name[NODE_NAME_SIZE]; + char property[PROPERTY_SIZE]; + unsigned int length; +}; + +struct battery_property { + struct property *property; + unsigned int type; + unsigned int flag; + char *new_value; + char *old_value; + int new_length; + int old_length; + struct battery_property *next; +}; + +struct battery_node { + char name[NODE_NAME_SIZE]; + struct device_node *np; + struct battery_node *next; + struct battery_property *start_prop; +}; + +static struct battery_node *get_battery_node( + struct battery_node **start_node, char *name) +{ + struct battery_node *batt_node = NULL; + struct battery_node *temp_node = NULL; + + if (name == NULL) { + pr_err("%s: name is invalid!!\n", __func__); + return NULL; + } + + batt_node = *start_node; + while (batt_node) { + if (!strcmp(batt_node->name, name)) { + return batt_node; + } else { + temp_node = batt_node; + batt_node = batt_node->next; + } + } + + batt_node = kzalloc(sizeof(struct battery_node), GFP_KERNEL); + if (!batt_node) { + pr_err("%s: nomem!!\n", __func__); + return NULL; + } + batt_node->np = of_find_node_by_name(NULL, name); + if (IS_ERR_OR_NULL(batt_node->np)) { + pr_err("%s: failed to find node(name=%s)\n", __func__, name); + kfree(batt_node); + return NULL; + } + strcpy(batt_node->name, name); + if (temp_node) + temp_node->next = batt_node; + batt_node->start_prop = NULL; + if (*start_node == NULL) *start_node = batt_node; + pr_info("%s: add battery_node(name = %s)\n", __func__, name); + return batt_node; +} + +static int add_battery_property( struct battery_node *batt_node, + struct battery_property *batt_prop) +{ + struct battery_property *start_prop = NULL; + struct battery_property *temp_prop = NULL; + + if (!batt_node || !batt_prop) { + return -ENODATA; + } + + start_prop = batt_node->start_prop; + while (start_prop) { + if (!strcmp(start_prop->property->name, batt_prop->property->name)) { + return 0; + } else { + temp_prop = start_prop; + start_prop = start_prop->next; + } + } + + if (!temp_prop) + batt_node->start_prop = batt_prop; + else { + temp_prop->next = batt_prop; + } + return 0; +} + +static void change_battery_pdata( + struct battery_node *start_node, bool is_valid) +{ + struct battery_node *temp_node = NULL; + + temp_node = start_node; + pr_info("%s: start update(%d)\n", __func__, is_valid); + while (is_valid && temp_node) { + struct battery_property *batt_prop = NULL; + struct power_supply *psy = NULL; + union power_supply_propval value; + + batt_prop = temp_node->start_prop; + while (batt_prop) { + if (batt_prop->flag != BATTERY_DATA_FLAG_REMOVE) { + batt_prop->property->value = batt_prop->new_value; + batt_prop->property->length = batt_prop->new_length; + } + batt_prop = batt_prop->next; + } + + psy = power_supply_get_by_name(start_node->name); + if (psy) { + value.intval = 0; + psy->desc->set_property(psy, POWER_SUPPLY_EXT_PROP_POWER_DESIGN, &value); + power_supply_put(psy); + } + + temp_node = temp_node->next; + } + + pr_info("%s: release battery pdata\n", __func__); + while (start_node) { + struct battery_property *temp_batt_prop = NULL; + struct battery_property *batt_prop = NULL; + + batt_prop = start_node->start_prop; + while (batt_prop) { + switch (batt_prop->flag) { + case BATTERY_DATA_FLAG_REMOVE: + pr_debug("%s: re-set property(ret=%d, flag=%d, name=%s)\n", + __func__, of_add_property(start_node->np, batt_prop->property), + batt_prop->flag, batt_prop->property->name); + break; + case BATTERY_DATA_FLAG_EDIT: + pr_debug("%s: re-set property(type=%d, flag=%d, name=%s)\n", + __func__, batt_prop->type, + batt_prop->flag, batt_prop->property->name); + if (!is_valid) { + kfree(batt_prop->new_value); + } else { + /* In normal case, String type should not free. */ + if (batt_prop->type != BATTERY_DATA_TYPE_STRING) { + batt_prop->property->value = batt_prop->old_value; + batt_prop->property->length = batt_prop->old_length; + kfree(batt_prop->new_value); + } + } + break; + case BATTERY_DATA_FLAG_ADD: + pr_debug("%s: re-set property(ret=%d, flag=%d, name=%s)\n", + __func__, of_remove_property(start_node->np, batt_prop->property), + batt_prop->flag, batt_prop->property->name); + if (batt_prop->new_value && (!is_valid || batt_prop->type != BATTERY_DATA_TYPE_STRING)) { + kfree(batt_prop->new_value); + } + kfree(batt_prop->property->name); + kfree(batt_prop->property); + break; + } + + temp_batt_prop = batt_prop; + batt_prop = batt_prop->next; + kfree(temp_batt_prop); + } + + temp_node = start_node; + start_node = start_node->next; + kfree(temp_node); + } +} + +static int sec_battery_check_info(struct file *fp, + struct battery_data_info *batt_info) +{ + struct device_node *np; + struct battery_data batt_data; + int dt_version, hw_rev, hw_rev_end; + int ret = 0, read_size; + + read_size = fp->f_op->read(fp, (char*)&batt_data, sizeof(struct battery_data), &fp->f_pos); + CHECK_ERROR_DATA((read_size <= 0), ret, (-ENODATA), goto finish_check_info); + CHECK_ERROR_DATA((batt_data.type != BATTERY_DATA_TYPE_INFO), ret, (-EINVAL), goto finish_check_info); + + read_size = fp->f_op->read(fp, (char*)batt_info, sizeof(struct battery_data_info), &fp->f_pos); + CHECK_ERROR_DATA((read_size <= 0 || read_size != batt_data.length), ret, (-ENODATA), goto finish_check_info); + CHECK_ERROR_DATA( + (batt_info->version < 0 || batt_info->hw_rev < 0 || batt_info->prop_count <= 0) || + (batt_info->value_length < 0 || batt_info->value_length > MAX_VALUE_SIZE), + ret, (-EINVAL), goto finish_check_info); + + np = of_find_node_by_name(NULL, batt_data.node_name); + ret = of_property_read_u32(np, "battery,batt_data_version", &dt_version); + if (ret) { + pr_info("%s : batt_data_version is Empty\n", __func__); + dt_version = 0; + } + + np = of_find_all_nodes(NULL); + ret = of_property_read_u32(np, "model_info-hw_rev", &hw_rev); + if (ret) { + pr_info("%s: model_info-hw_rev is Empty\n", __func__); + hw_rev = 0; + } + ret = of_property_read_u32(np, "model_info-hw_rev_end", &hw_rev_end); + if (ret) { + pr_info("%s: model_info-hw_rev_end is Empty\n", __func__); + hw_rev_end = 99; + } + + ret = (batt_info->version < dt_version) ? -1 : + ((batt_info->hw_rev > hw_rev_end || batt_info->hw_rev < hw_rev) ? -2 : 0); + + pr_info("%s: check info(ret=%d), version(%d <-> %d), hw_rev(%d ~ %d <-> %d), prop_count(%d), value_length(%d)\n", + __func__, ret, + dt_version, batt_info->version, + hw_rev, hw_rev_end, batt_info->hw_rev, + batt_info->prop_count, batt_info->value_length); + +finish_check_info: + return ret; +} + +static int sec_battery_check_none(struct file *fp) +{ + struct battery_data batt_data; + int ret = 0, read_size; + + read_size = fp->f_op->read(fp, (char*)&batt_data, sizeof(struct battery_data), &fp->f_pos); + CHECK_ERROR_DATA((read_size <= 0), ret, (-ENODATA), goto finish_check_none); + + if (batt_data.type != BATTERY_DATA_TYPE_NONE) { + pr_info("%s: invalid type(%d)\n", __func__, batt_data.type); + ret = -EINVAL; + } + +finish_check_none: + return ret; +} + +static char *sec_battery_check_value(struct file *fp, + struct battery_data_info *batt_info, struct battery_data *batt_data) +{ + char *temp_buf = NULL; + int read_size; + + if (batt_data->length < 0 || batt_data->length > batt_info->value_length) { + pr_info("%s: length(%d) of data is invalid\n", __func__, batt_data->length); + return NULL; + } else if (batt_data->length == 0) { + pr_info("%s: skip alloc buffer(length=%d)\n", __func__, batt_data->length); + return NULL; + } + + temp_buf = kzalloc(batt_data->length, GFP_KERNEL); + if (temp_buf) { + read_size = fp->f_op->read(fp, temp_buf, batt_data->length, &fp->f_pos); + if (read_size <= 0) { + pr_info("%s: failed to read value\n", __func__); + kfree(temp_buf); + temp_buf = NULL; + } + } + + return temp_buf; +} + +int sec_battery_update_data(const char* file_path) +{ + struct battery_node *batt_node = NULL; + struct battery_node *temp_node = NULL; + struct property *batt_property = NULL; + struct battery_data_info batt_info; + struct battery_property *batt_prop; + struct battery_data batt_data; + struct file* fp = NULL; + char *temp_buf = NULL; + int length, read_size; + int ret = 0; + + fp = filp_open(file_path, O_RDONLY, 0); + CHECK_ERROR_DATA(IS_ERR(fp), ret, (int)(PTR_ERR(fp)), goto err_filp_open); + + ret = sec_battery_check_info(fp, &batt_info); + CHECK_ERROR_DATA((ret), ret, (-EINVAL), goto skip_check_data); + + while (batt_info.prop_count-- > 0) { + read_size = fp->f_op->read(fp, (char*)&batt_data, sizeof(struct battery_data), &fp->f_pos); + CHECK_ERROR_DATA((read_size <= 0), ret, (-ENODATA), goto finish_update_data); + pr_debug("%s: read batt_data(type=%d, flag=%d, node_name=%s, property=%s, length=%d)\n", + __func__, batt_data.type, batt_data.flag, batt_data.node_name, batt_data.property, batt_data.length); + + temp_node = get_battery_node(&batt_node, batt_data.node_name); + CHECK_ERROR_DATA((!temp_node), ret, (-ENODEV), goto finish_update_data); + + batt_prop = kzalloc(sizeof(struct battery_property), GFP_KERNEL); + CHECK_ERROR_DATA((!batt_prop), ret, (-ENOMEM), goto finish_update_data); + + batt_property = of_find_property(temp_node->np, batt_data.property, &length); + switch (batt_data.flag) { + case BATTERY_DATA_FLAG_ADD: + if (IS_ERR_OR_NULL(batt_property)) { + temp_buf = sec_battery_check_value(fp, &batt_info, &batt_data); + CHECK_ERROR_DATA((!temp_buf && batt_data.length != 0), ret, (-ENOMEM), + {kfree(batt_prop); goto finish_update_data;}); + + batt_property = kzalloc(sizeof(struct property), GFP_KERNEL); + CHECK_ERROR_DATA((!batt_property), ret, (-ENOMEM), + {kfree(batt_prop); kfree(temp_buf); goto finish_update_data;}); + + batt_property->name = kzalloc(PROPERTY_SIZE, GFP_KERNEL); + CHECK_ERROR_DATA((!batt_property->name), ret, (-ENOMEM), + {kfree(batt_prop); kfree(temp_buf); kfree(batt_property); goto finish_update_data;}); + + memcpy(batt_property->name, batt_data.property, PROPERTY_SIZE); + ret = of_add_property(temp_node->np, batt_property); + CHECK_ERROR_DATA((ret), ret, ret, + {kfree(batt_prop); kfree(temp_buf); kfree(batt_property->name); kfree(batt_property); goto finish_update_data;}); + } else { + pr_info("%s: invalid data(name=%s, property=%s, flag=%d)\n", + __func__, batt_data.node_name, batt_data.property, batt_data.flag); + ret = -EINVAL; + kfree(batt_prop); + goto finish_update_data; + } + break; + case BATTERY_DATA_FLAG_EDIT: + if (!IS_ERR_OR_NULL(batt_property)) { + temp_buf = sec_battery_check_value(fp, &batt_info, &batt_data); + CHECK_ERROR_DATA((!temp_buf), ret, (-ENOMEM), + {kfree(batt_prop); goto finish_update_data;}); + } else { + pr_info("%s: invalid data(name=%s, property=%s, flag=%d)\n", + __func__, batt_data.node_name, batt_data.property, batt_data.flag); + ret = -EINVAL; + kfree(batt_prop); + goto finish_update_data; + } + break; + case BATTERY_DATA_FLAG_REMOVE: + if (!IS_ERR_OR_NULL(batt_property)) { + temp_buf = NULL; + + ret = of_remove_property(temp_node->np, batt_property); + CHECK_ERROR_DATA((ret), ret, ret, {kfree(batt_prop); goto finish_update_data;}); + } else { + pr_info("%s: invalid data(name=%s, property=%s, flag=%d)\n", + __func__, batt_data.node_name, batt_data.property, batt_data.flag); + ret = -EINVAL; + kfree(batt_prop); + goto finish_update_data; + } + break; + default: + pr_info("%s: invalid flag(%d)\n", __func__, batt_data.flag); + ret = -EINVAL; + kfree(batt_prop); + goto finish_update_data; + } + + batt_prop->property = batt_property; + batt_prop->type = batt_data.type; + batt_prop->flag = batt_data.flag; + batt_prop->new_value = temp_buf; + batt_prop->old_value = batt_property->value; + batt_prop->new_length = batt_data.length; + batt_prop->old_length = batt_property->length; + add_battery_property(temp_node, batt_prop); + } + ret = sec_battery_check_none(fp); + +finish_update_data: + change_battery_pdata(batt_node, (ret == 0)); +skip_check_data: + filp_close(fp, NULL); +err_filp_open: + return ret; +} diff --git a/drivers/battery/common/sec_battery_dt.c b/drivers/battery/common/sec_battery_dt.c new file mode 100644 index 000000000000..f7e399d061d5 --- /dev/null +++ b/drivers/battery/common/sec_battery_dt.c @@ -0,0 +1,2656 @@ +/* + * sec_battery_dt.c + * Samsung Mobile Battery Driver + * + * Copyright (C) 2018 Samsung Electronics + * + * + * 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. + */ +#include "sec_battery.h" +#include "sec_battery_dt.h" +#if defined(CONFIG_SEC_KUNIT) +#include +#else +#define __visible_for_testing static +#endif + +#ifdef CONFIG_OF +#define PROPERTY_NAME_SIZE 128 +int sec_bat_parse_dt_siop( + struct sec_battery_info *battery, struct device_node *np) +{ + sec_battery_platform_data_t *pdata = battery->pdata; + int ret = 0, len = 0; + const u32 *p; + + ret = of_property_read_u32(np, "battery,siop_icl", + &pdata->siop_icl); + if (ret) + pdata->siop_icl = SIOP_INPUT_LIMIT_CURRENT; + + ret = of_property_read_u32(np, "battery,siop_fcc", + &pdata->siop_fcc); + if (ret) + pdata->siop_fcc = SIOP_CHARGING_LIMIT_CURRENT; + + ret = of_property_read_u32(np, "battery,siop_hv_12v_icl", + &pdata->siop_hv_12v_icl); + if (ret) + pdata->siop_hv_12v_icl = SIOP_HV_12V_INPUT_LIMIT_CURRENT; + + ret = of_property_read_u32(np, "battery,siop_hv_12v_fcc", + &pdata->siop_hv_12v_fcc); + if (ret) + pdata->siop_hv_12v_fcc = SIOP_HV_12V_CHARGING_LIMIT_CURRENT; + + ret = of_property_read_u32(np, "battery,siop_hv_icl", + &pdata->siop_hv_icl); + if (ret) + pdata->siop_hv_icl = SIOP_HV_INPUT_LIMIT_CURRENT; + + ret = of_property_read_u32(np, "battery,siop_hv_icl_2nd", + &pdata->siop_hv_icl_2nd); + if (ret) + pdata->siop_hv_icl_2nd = SIOP_HV_INPUT_LIMIT_CURRENT; + + ret = of_property_read_u32(np, "battery,siop_hv_fcc", + &pdata->siop_hv_fcc); + if (ret) + pdata->siop_hv_fcc = SIOP_HV_CHARGING_LIMIT_CURRENT; + + ret = of_property_read_u32(np, "battery,siop_apdo_icl", + &pdata->siop_apdo_icl); + if (ret) + pdata->siop_apdo_icl = SIOP_APDO_INPUT_LIMIT_CURRENT; + + ret = of_property_read_u32(np, "battery,siop_apdo_fcc", + &pdata->siop_apdo_fcc); + if (ret) + pdata->siop_apdo_fcc = SIOP_APDO_CHARGING_LIMIT_CURRENT; + + ret = of_property_read_u32(np, "battery,siop_wpc_icl", + &pdata->siop_wpc_icl); + if (ret) + pdata->siop_wpc_icl = SIOP_WIRELESS_INPUT_LIMIT_CURRENT; + + p = of_get_property(np, "battery,siop_wpc_fcc", &len); + if (!p) { + pr_info("%s : battery,siop_wpc_fcc is Empty\n", __func__); + } else { + len = len / sizeof(u32); + pdata->siop_wpc_fcc = + kzalloc(sizeof(*pdata->siop_wpc_fcc) * len, GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,siop_wpc_fcc", + pdata->siop_wpc_fcc, len); + + pr_info("%s: parse siop_wpc_fcc, ret = %d, len = %d\n", __func__, ret, len); + } + + ret = of_property_read_u32(np, "battery,siop_hv_wpc_icl", + &pdata->siop_hv_wpc_icl); + if (ret) + pdata->siop_hv_wpc_icl = SIOP_HV_WIRELESS_INPUT_LIMIT_CURRENT; + + ret = of_property_read_u32(np, "battery,rechg_hv_wpc_icl", + &pdata->rechg_hv_wpc_icl); + if (ret) + pdata->rechg_hv_wpc_icl = pdata->siop_hv_wpc_icl; + + p = of_get_property(np, "battery,siop_hv_wpc_fcc", &len); + if (!p) { + pr_info("%s : battery,siop_hv_wpc_fcc is Empty\n", __func__); + } else { + len = len / sizeof(u32); + pdata->siop_hv_wpc_fcc = + kzalloc(sizeof(*pdata->siop_hv_wpc_fcc) * len, GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,siop_hv_wpc_fcc", + pdata->siop_hv_wpc_fcc, len); + + pr_info("%s: parse siop_hv_wpc_fcc, ret = %d, len = %d\n", __func__, ret, len); + } + + len = of_property_count_u32_elems(np, "battery,siop_scenarios"); + if (len > 0) { + int siop_scenarios[SIOP_SCENARIO_NUM_MAX]; + + pdata->siop_scenarios_num = (len > SIOP_SCENARIO_NUM_MAX) ? SIOP_SCENARIO_NUM_MAX : len; + ret = of_property_read_u32_array(np, "battery,siop_scenarios", + (u32 *)siop_scenarios, pdata->siop_scenarios_num); + + ret = of_property_read_u32(np, "battery,siop_curr_type_num", + &pdata->siop_curr_type_num); + if (ret) { + pdata->siop_scenarios_num = 0; + pdata->siop_curr_type_num = 0; + goto parse_siop_next; + } + + pdata->siop_curr_type_num = + (pdata->siop_curr_type_num > SIOP_CURR_TYPE_MAX) ? SIOP_CURR_TYPE_MAX : pdata->siop_curr_type_num; + + if (pdata->siop_curr_type_num > 0) { + int i, j, siop_level; + char prop_name[PROPERTY_NAME_SIZE]; + + for (i = 0; i < pdata->siop_scenarios_num; ++i) { + siop_level = siop_scenarios[i]; + pdata->siop_table[i].level = siop_level; + + snprintf(prop_name, PROPERTY_NAME_SIZE, "battery,siop_icl_%d", siop_level); + ret = of_property_read_u32_array(np, prop_name, + (u32 *)pdata->siop_table[i].icl, pdata->siop_curr_type_num); + + snprintf(prop_name, PROPERTY_NAME_SIZE, "battery,siop_fcc_%d", siop_level); + ret = of_property_read_u32_array(np, prop_name, + (u32 *)pdata->siop_table[i].fcc, pdata->siop_curr_type_num); + + for (j = 0; j < pdata->siop_curr_type_num; ++j) + pr_info("%s: level=%d, [%d].siop_icl[%d]=%d, [%d].siop_fcc[%d]=%d\n", + __func__, pdata->siop_table[i].level, + i, j, pdata->siop_table[i].icl[j], + i, j, pdata->siop_table[i].fcc[j]); + } + } + } + +parse_siop_next: + + return ret; +} + +int sec_bat_parse_dt_lrp( + struct sec_battery_info *battery, struct device_node *np, int type) +{ + sec_battery_platform_data_t *pdata = battery->pdata; + int ret = 0, len = 0; + char prop_name[PROPERTY_NAME_SIZE]; + int lrp_table[LRP_PROPS]; + + snprintf(prop_name, PROPERTY_NAME_SIZE, + "battery,temp_table_%s", LRP_TYPE_STRING[type]); + len = of_property_count_u32_elems(np, prop_name); + if (len != LRP_PROPS) + return -1; + + ret = of_property_read_u32_array(np, prop_name, + (u32 *)lrp_table, LRP_PROPS); + if (ret) { + pr_info("%s: failed to parse %s!!, ret = %d\n", + __func__, LRP_TYPE_STRING[type], ret); + return ret; + } + + pdata->lrp_temp[type].trig[ST2][LCD_OFF] = lrp_table[0]; + pdata->lrp_temp[type].recov[ST2][LCD_OFF] = lrp_table[1]; + pdata->lrp_temp[type].trig[ST1][LCD_OFF] = lrp_table[2]; + pdata->lrp_temp[type].recov[ST1][LCD_OFF] = lrp_table[3]; + pdata->lrp_temp[type].trig[ST2][LCD_ON] = lrp_table[4]; + pdata->lrp_temp[type].recov[ST2][LCD_ON] = lrp_table[5]; + pdata->lrp_temp[type].trig[ST1][LCD_ON] = lrp_table[6]; + pdata->lrp_temp[type].recov[ST1][LCD_ON] = lrp_table[7]; + pdata->lrp_curr[type].st_icl[ST1] = lrp_table[8]; + pdata->lrp_curr[type].st_fcc[ST1] = lrp_table[9]; + pdata->lrp_curr[type].st_icl[ST2] = lrp_table[10]; + pdata->lrp_curr[type].st_fcc[ST2] = lrp_table[11]; + + pr_info("%s: lrp_temp[%s].trig_st1=%d\n", + __func__, LRP_TYPE_STRING[type], pdata->lrp_temp[type].trig[ST1][LCD_OFF]); + pr_info("%s: lrp_temp[%s].trig_st2=%d\n", + __func__, LRP_TYPE_STRING[type], pdata->lrp_temp[type].trig[ST2][LCD_OFF]); + pr_info("%s: lrp_temp[%s].recov_st1=%d\n", + __func__, LRP_TYPE_STRING[type], pdata->lrp_temp[type].recov[ST1][LCD_OFF]); + pr_info("%s: lrp_temp[%s].recov_st2=%d\n", + __func__, LRP_TYPE_STRING[type], pdata->lrp_temp[type].recov[ST2][LCD_OFF]); + pr_info("%s: lrp_temp[%s].trig_st1_lcdon=%d\n", + __func__, LRP_TYPE_STRING[type], pdata->lrp_temp[type].trig[ST1][LCD_ON]); + pr_info("%s: lrp_temp[%s].trig_st2_lcdon=%d\n", + __func__, LRP_TYPE_STRING[type], pdata->lrp_temp[type].trig[ST2][LCD_ON]); + pr_info("%s: lrp_temp[%s].recov_st1_lcdon=%d\n", + __func__, LRP_TYPE_STRING[type], pdata->lrp_temp[type].recov[ST1][LCD_ON]); + pr_info("%s: lrp_temp[%s].recov_st2_lcdon=%d\n", + __func__, LRP_TYPE_STRING[type], pdata->lrp_temp[type].recov[ST2][LCD_ON]); + pr_info("%s: lrp_temp[%s].st1_icl=%d\n", + __func__, LRP_TYPE_STRING[type], pdata->lrp_curr[type].st_icl[ST1]); + pr_info("%s: lrp_temp[%s].st1_fcc=%d\n", + __func__, LRP_TYPE_STRING[type], pdata->lrp_curr[type].st_fcc[ST1]); + pr_info("%s: lrp_temp[%s].st2_icl=%d\n", + __func__, LRP_TYPE_STRING[type], pdata->lrp_curr[type].st_icl[ST2]); + pr_info("%s: lrp_temp[%s].st2_fcc=%d\n", + __func__, LRP_TYPE_STRING[type], pdata->lrp_curr[type].st_fcc[ST2]); + + return 0; +} + +/* ret: table size */ +static int sec_bat_parse_adc_table(struct device_node *np, char *name, sec_bat_adc_table_data_t **adc_table, int offset) +{ + char adc_str[64], data_str[64]; + sec_bat_adc_table_data_t *table; + const u32 *p; + int i, ret, len = 0; + int n_len = strlen(name); + + strcpy(adc_str, name); + strcpy(adc_str+n_len, "temp_table_adc"); + + p = of_get_property(np, adc_str, &len); + if (!p) + return 0; + + len = len / sizeof(u32); + table = kcalloc(len, sizeof(sec_bat_adc_table_data_t), GFP_KERNEL); + + strcpy(data_str, name); + strcpy(data_str+n_len, "temp_table_data"); + for (i = 0; i < len; i++) { + u32 temp; + + ret = of_property_read_u32_index(np, adc_str, i, &temp); + table[i].adc = (offset) ? (offset - (int)temp) : ((int)temp); + if (ret) + pr_info("%s : %s is Empty\n", __func__, adc_str); + ret = of_property_read_u32_index(np, data_str, i, &temp); + table[i].data = (int)temp; + if (ret) + pr_info("%s : %s is Empty\n", __func__, data_str); + } + *adc_table = table; + return len; +} + +__visible_for_testing void sec_bat_parse_thm_info(struct device_node *np, char *name, struct sec_bat_thm_info *info) +{ + char buf[64]; + int ret; + int n_len = strlen(name); + + strcpy(buf, name); + strcpy(buf+n_len, "thermal_source"); + ret = of_property_read_u32(np, buf, &info->source); + if (ret) { + info->source = SEC_BATTERY_THERMAL_SOURCE_NONE; + pr_info("%s : %s is Empty, %d\n", __func__, buf, ret); + } + + strcpy(buf+n_len, "temp_offset"); + ret = of_property_read_u32(np, buf, &info->offset); + if (ret) + pr_info("%s : %s is Empty\n", __func__, buf); + info->adc_table_size = sec_bat_parse_adc_table(np, name, + &info->adc_table, info->offset); + + strcpy(buf+n_len, "temp_check_type"); + ret = of_property_read_u32(np, buf, &info->check_type); + if (ret) + pr_info("%s : %s is Empty %d\n", __func__, buf, ret); + + if (info->source == SEC_BATTERY_THERMAL_SOURCE_ADC) { + ret = of_property_read_u32(np, "battery,adc_check_count", + &info->check_count); + if (ret) + pr_info("%s : Adc check count is Empty, %d\n", __func__, ret); + } + info->test = 0x7FFF; +} + +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) +static void sec_bat_parse_dc_thm(struct device_node *np, sec_battery_platform_data_t *pdata) +{ + int ret = 0, len = 0, i = 0; + const u32 *p; + int len_step = 4; + char str[256] = {0, }; + + sec_bat_parse_thm_info(np, "battery,dchg_", &pdata->dchg_thm_info); + + pdata->dctp_by_cgtp = of_property_read_bool(np, "battery,dctp_by_cgtp"); + pdata->dctp_bootmode_en = of_property_read_bool(np, "battery,dctp_bootmode_en"); + pdata->lrpts_by_batts = of_property_read_bool(np, "battery,lrpts_by_batts"); + + /* dchg_high_temp */ + p = of_get_property(np, "battery,dchg_high_temp", &len); + if (!p) { + pr_info("%s: failed to parse dchg_high_temp!\n", __func__); + for (i = 0; i < len_step; i++) + pdata->dchg_high_temp[i] = 690; + return; + } + len = len / sizeof(u32); + ret = of_property_read_u32_array(np, "battery,dchg_high_temp", + pdata->dchg_high_temp, len); + if (len != len_step) { + pr_err("%s not match size of dchg_high_temp: %d\n", __func__, len); + for (i = 1; i < len_step; i++) + pdata->dchg_high_temp[i] = pdata->dchg_high_temp[0]; + } + + /* dchg_high_temp_recovery */ + p = of_get_property(np, "battery,dchg_high_temp_recovery", &len); + if (!p) { + pr_info("%s: failed to parse dchg_high_temp_recovery!\n", __func__); + for (i = 0; i < len_step; i++) + pdata->dchg_high_temp_recovery[i] = 630; + } + len = len / sizeof(u32); + ret = of_property_read_u32_array(np, "battery,dchg_high_temp_recovery", + pdata->dchg_high_temp_recovery, len); + if (len != len_step) { + pr_err("%s not match size of dchg_high_temp_recovery: %d\n", __func__, len); + for (i = 1; i < len_step; i++) + pdata->dchg_high_temp_recovery[i] = pdata->dchg_high_temp_recovery[0]; + } + + /* dchg_high_batt_temp */ + p = of_get_property(np, "battery,dchg_high_batt_temp", &len); + if (!p) { + pr_info("%s: failed to parse dchg_high_batt_temp!\n", __func__); + for (i = 0; i < len_step; i++) + pdata->dchg_high_batt_temp[i] = 400; + } + len = len / sizeof(u32); + ret = of_property_read_u32_array(np, "battery,dchg_high_batt_temp", + pdata->dchg_high_batt_temp, len); + if (len != len_step) { + pr_err("%s not match size of dchg_high_batt_temp: %d\n", __func__, len); + for (i = 1; i < len_step; i++) + pdata->dchg_high_batt_temp[i] = pdata->dchg_high_batt_temp[0]; + } + + /* dchg_high_batt_temp_recovery */ + p = of_get_property(np, "battery,dchg_high_batt_temp_recovery", &len); + if (!p) { + pr_info("%s: failed to parse dchg_high_batt_temp_recovery!\n", __func__); + for (i = 0; i < len_step; i++) + pdata->dchg_high_batt_temp_recovery[i] = 380; + } + len = len / sizeof(u32); + ret = of_property_read_u32_array(np, "battery,dchg_high_batt_temp_recovery", + pdata->dchg_high_batt_temp_recovery, len); + if (len != len_step) { + pr_err("%s not match size of dchg_high_batt_temp_recovery: %d\n", __func__, len); + for (i = 1; i < len_step; i++) + pdata->dchg_high_batt_temp_recovery[i] = pdata->dchg_high_batt_temp_recovery[0]; + } + + sprintf(str, "%s: dchg_htemp: ", __func__); + for (i = 0; i < len_step; i++) + sprintf(str + strlen(str), "%d ", pdata->dchg_high_temp[i]); + sprintf(str + strlen(str), ",dchg_htemp_rec: "); + for (i = 0; i < len_step; i++) + sprintf(str + strlen(str), "%d ", pdata->dchg_high_temp_recovery[i]); + sprintf(str + strlen(str), ",dchg_batt_htemp: "); + for (i = 0; i < len_step; i++) + sprintf(str + strlen(str), "%d ", pdata->dchg_high_batt_temp[i]); + sprintf(str + strlen(str), ",dchg_batt_htemp_rec: "); + for (i = 0; i < len_step; i++) + sprintf(str + strlen(str), "%d ", pdata->dchg_high_batt_temp_recovery[i]); + sprintf(str + strlen(str), "\n"); + pr_info("%s", str); + + return; +} +#else +static void sec_bat_parse_dc_thm(struct device_node *np, sec_battery_platform_data_t *pdata) +{ + pr_info("%s: direct charging is not set\n", __func__); +} +#endif + +static void sec_bat_parse_health_condition(struct device_node *np, sec_battery_platform_data_t *pdata) +{ + int ret = 0, len = 0; + unsigned int i = 0; + + const u32 *p = of_get_property(np, "battery,health_condition_cycle", &len); + + len /= sizeof(u32); + if (!p || len != BATTERY_HEALTH_MAX) { + pdata->health_condition = NULL; + pr_err("%s there is not health_condition, len(%d)\n", __func__, len); + return; + } + + pdata->health_condition = kzalloc(len*sizeof(battery_health_condition), GFP_KERNEL); + + for (i = 0; i < len; i++) { + ret = of_property_read_u32_index(np, "battery,health_condition_cycle", + i, &(pdata->health_condition[i].cycle)); + if (ret) + break; + ret = of_property_read_u32_index(np, "battery,health_condition_asoc", + i, &(pdata->health_condition[i].asoc)); + if (ret) + break; + pr_err("%s: [BATTERY_HEALTH] %d: Cycle(~ %d), ASoC(~ %d)\n", + __func__, i, pdata->health_condition[i].cycle, pdata->health_condition[i].asoc); + } + + if (ret) { + pr_err("%s failed to read battery->pdata->health_condition: %d\n", __func__, ret); + kfree(pdata->health_condition); + pdata->health_condition = NULL; + } +} + +static int sec_bat_parse_age_data_by_offset(struct device_node *np, sec_battery_platform_data_t *pdata) +{ + int ret = 0, len = 0; + u32 temp = 0, i = 0, n_len = 0; + char *age_data_prefix = "battery,age_data_"; + char cycle_str[64]; + char full_condition_soc_str[64]; + char chg_float_voltage_offset_str[64]; + char full_condition_vcell_offset_str[64]; + char recharge_condition_vcell_offset_str[64]; +#if defined(CONFIG_BATTERY_AGE_FORECAST_B2B) + char max_charging_current_offset_str[64]; +#endif + + n_len = strlen(age_data_prefix); + strcpy(cycle_str, age_data_prefix); + strcpy(cycle_str + n_len, "cycle"); + strcpy(full_condition_soc_str, age_data_prefix); + strcpy(full_condition_soc_str + n_len, "full_condition_soc"); + strcpy(chg_float_voltage_offset_str, age_data_prefix); + strcpy(chg_float_voltage_offset_str + n_len, "chg_float_voltage_offset"); + strcpy(full_condition_vcell_offset_str, age_data_prefix); + strcpy(full_condition_vcell_offset_str + n_len, "full_condition_vcell_offset"); + strcpy(recharge_condition_vcell_offset_str, age_data_prefix); + strcpy(recharge_condition_vcell_offset_str + n_len, "recharge_condition_vcell_offset"); +#if defined(CONFIG_BATTERY_AGE_FORECAST_B2B) + strcpy(max_charging_current_offset_str, age_data_prefix); + strcpy(max_charging_current_offset_str + n_len, "max_charging_current_offset"); +#endif + + len = of_property_count_u32_elems(np, cycle_str); + if (len > 0) { + pdata->num_age_step = len; + + len = of_property_count_u32_elems(np, full_condition_soc_str); + if (len != pdata->num_age_step) { + pr_info("%s : %s has %d elements - expected %d\n", + __func__, full_condition_soc_str, len, pdata->num_age_step); + return -EINVAL; + } + len = of_property_count_u32_elems(np, chg_float_voltage_offset_str); + if (len != pdata->num_age_step) { + pr_info("%s : %s has %d elements - expected %d\n", + __func__, chg_float_voltage_offset_str, len, pdata->num_age_step); + return -EINVAL; + } + len = of_property_count_u32_elems(np, full_condition_vcell_offset_str); + if (len != pdata->num_age_step) { + pr_info("%s : %s has %d elements - expected %d\n", + __func__, full_condition_vcell_offset_str, len, pdata->num_age_step); + return -EINVAL; + } + len = of_property_count_u32_elems(np, recharge_condition_vcell_offset_str); + if (len != pdata->num_age_step) { + pr_info("%s : %s has %d elements - expected %d\n", + __func__, recharge_condition_vcell_offset_str, len, pdata->num_age_step); + return -EINVAL; + } +#if defined(CONFIG_BATTERY_AGE_FORECAST_B2B) + len = of_property_count_u32_elems(np, max_charging_current_offset_str); + if (len != pdata->num_age_step) { + pr_info("%s : %s has %d elements - expected %d\n", + __func__, max_charging_current_offset_str, len, pdata->num_age_step); + return -EINVAL; + } +#endif + } else { + pr_info("%s : Error in Calculating Age Data Table Size - %d\n", __func__, len); + return -EINVAL; + } + + pdata->age_data = kzalloc(len * sizeof(sec_age_data_t), GFP_KERNEL); + pr_info("%s : Read Age Data Using Offsets - Table Size %d\n", __func__, len); + for (i = 0; i < len; i++) { + ret = of_property_read_u32_index(np, cycle_str, i, &temp); + if (ret) { + pr_info("%s : %s is Empty\n", __func__, cycle_str); + break; + } + pdata->age_data[i].cycle = temp; + + ret = of_property_read_u32_index(np, full_condition_soc_str, i, &temp); + if (ret) { + pr_info("%s : %s is Empty\n", __func__, full_condition_soc_str); + break; + } + pdata->age_data[i].full_condition_soc = temp; + + ret = of_property_read_u32_index(np, chg_float_voltage_offset_str, i, &temp); + if (ret) { + pr_info("%s : %s is Empty\n", __func__, chg_float_voltage_offset_str); + break; + } + pdata->age_data[i].float_voltage = pdata->chg_float_voltage - temp; + + ret = of_property_read_u32_index(np, full_condition_vcell_offset_str, i, &temp); + if (ret) { + pr_info("%s : %s is Empty\n", __func__, full_condition_vcell_offset_str); + break; + } + pdata->age_data[i].full_condition_vcell = pdata->full_condition_vcell - temp; + + ret = of_property_read_u32_index(np, recharge_condition_vcell_offset_str, i, &temp); + if (ret) { + pr_info("%s : %s is Empty\n", __func__, recharge_condition_vcell_offset_str); + break; + } + pdata->age_data[i].recharge_condition_vcell = pdata->recharge_condition_vcell - temp; + +#if defined(CONFIG_BATTERY_AGE_FORECAST_B2B) + ret = of_property_read_u32_index(np, max_charging_current_offset_str, i, &temp); + if (ret) { + pr_info("%s : %s is Empty\n", __func__, max_charging_current_offset_str); + break; + } + pdata->age_data[i].max_charging_current = pdata->max_charging_current - temp; + +#endif + } + + return ret; +} + +static void sec_bat_parse_age_data(struct device_node *np, sec_battery_platform_data_t *pdata) +{ + int ret = 0, len = 0; + unsigned int i = 0; + const u32 *p; + char str[256] = {0, }; + bool age_data_by_offset = of_property_read_bool(np, "battery,age_data_by_offset"); + + pr_info("%s: age_data_by_offset is %s", __func__, (age_data_by_offset ? "true" : "false")); + + if (age_data_by_offset) { + ret = sec_bat_parse_age_data_by_offset(np, pdata); + } else { + p = of_get_property(np, "battery,age_data", &len); + if (p) { + pdata->num_age_step = len / sizeof(sec_age_data_t); + pdata->age_data = kzalloc(len, GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,age_data", + (u32 *)pdata->age_data, len/sizeof(u32)); + } + } + if (ret) { + pr_err("%s failed to read battery->pdata->age_data: %d\n", __func__, ret); + kfree(pdata->age_data); + pdata->age_data = NULL; + pdata->num_age_step = 0; + } else { + pr_err("%s num_age_step : %d\n", __func__, pdata->num_age_step); + for (i = 0; i < pdata->num_age_step; ++i) { + memset(str, 0x0, sizeof(str)); + sprintf(str + strlen(str), "[%d/%d]cycle:%d, float:%d, full_v:%d, recharge_v:%d, soc:%d", + i, pdata->num_age_step-1, + pdata->age_data[i].cycle, + pdata->age_data[i].float_voltage, + pdata->age_data[i].full_condition_vcell, + pdata->age_data[i].recharge_condition_vcell, + pdata->age_data[i].full_condition_soc); +#if defined(CONFIG_BATTERY_AGE_FORECAST_B2B) + sprintf(str + strlen(str), ", max_c:%d", + pdata->age_data[i].max_charging_current); +#endif + pr_err("%s: %s", __func__, str); + } + } +} + +int sec_bat_parse_dt(struct device *dev, + struct sec_battery_info *battery) +{ + struct device_node *np; + sec_battery_platform_data_t *pdata = battery->pdata; + int ret = 0, len = 0; + unsigned int i = 0; + const u32 *p; + u32 temp = 0; + + np = of_find_node_by_name(NULL, "cable-info"); + if (!np) { + pr_err ("%s : np NULL\n", __func__); + } else { + struct device_node *child; + u32 input_current = 0, charging_current = 0; + + ret = of_property_read_u32(np, "default_input_current", &input_current); + ret = of_property_read_u32(np, "default_charging_current", &charging_current); + ret = of_property_read_u32(np, "full_check_current_1st", &pdata->full_check_current_1st); + ret = of_property_read_u32(np, "full_check_current_2nd", &pdata->full_check_current_2nd); + if (!pdata->full_check_current_2nd) + pdata->full_check_current_2nd = pdata->full_check_current_1st; + + pdata->default_input_current = input_current; + pdata->default_charging_current = charging_current; + + for (i = 0; i < SEC_BATTERY_CABLE_MAX; i++) { + pdata->charging_current[i].input_current_limit = (unsigned int)input_current; + pdata->charging_current[i].fast_charging_current = (unsigned int)charging_current; + } + + for_each_child_of_node(np, child) { + ret = of_property_read_u32(child, "input_current", &input_current); + ret = of_property_read_u32(child, "charging_current", &charging_current); + p = of_get_property(child, "cable_number", &len); + if (!p) + return 1; + + len = len / sizeof(u32); + + for (i = 0; i <= len; i++) { + ret = of_property_read_u32_index(child, "cable_number", i, &temp); + pdata->charging_current[temp].input_current_limit = (unsigned int)input_current; + pdata->charging_current[temp].fast_charging_current = (unsigned int)charging_current; + } + } + } + + for (i = 0; i < SEC_BATTERY_CABLE_MAX; i++) { + pr_info("%s : CABLE_NUM(%d) INPUT(%d) CHARGING(%d)\n", + __func__, i, + pdata->charging_current[i].input_current_limit, + pdata->charging_current[i].fast_charging_current); + } + + pr_info("%s : TOPOFF_1ST(%d), TOPOFF_2ND(%d)\n", + __func__, pdata->full_check_current_1st, pdata->full_check_current_2nd); + + pdata->default_usb_input_current = pdata->charging_current[SEC_BATTERY_CABLE_USB].input_current_limit; + pdata->default_usb_charging_current = pdata->charging_current[SEC_BATTERY_CABLE_USB].fast_charging_current; + pdata->default_wc20_input_current = pdata->charging_current[SEC_BATTERY_CABLE_HV_WIRELESS_20].input_current_limit; + pdata->default_wc20_charging_current = pdata->charging_current[SEC_BATTERY_CABLE_HV_WIRELESS_20].fast_charging_current; +#ifdef CONFIG_SEC_FACTORY + pdata->default_charging_current = 1500; + pdata->charging_current[SEC_BATTERY_CABLE_TA].fast_charging_current = 1500; +#endif + np = of_find_node_by_name(NULL, "battery"); + if (!np) { + pr_info("%s: np NULL\n", __func__); + return 1; + } + + ret = of_property_read_u32(np, "battery,battery_full_capacity", + &pdata->battery_full_capacity); + if (ret) + pr_info("%s : battery_full_capacity is Empty\n", __func__); + + pdata->soc_by_repcap_en = of_property_read_bool(np, "battery,soc_by_repcap_en"); + +#ifdef CONFIG_SEC_FACTORY + ret = of_property_read_u32(np, "battery,factory_chg_limit_max", + &pdata->store_mode_charging_max); + if (ret) { + pr_info("%s :factory_chg_limit_max is Empty\n", __func__); + pdata->store_mode_charging_max = 80; + } + + ret = of_property_read_u32(np, "battery,factory_chg_limit_min", + &pdata->store_mode_charging_min); + if (ret) { + pr_info("%s :factory_chg_limit_min is Empty\n", __func__); + pdata->store_mode_charging_min = 70; + } +#else + ret = of_property_read_u32(np, "battery,store_mode_charging_max", + &pdata->store_mode_charging_max); + if (ret) { + pr_info("%s :factory_chg_limit_max is Empty\n", __func__); + pdata->store_mode_charging_max = 70; + } + + ret = of_property_read_u32(np, "battery,store_mode_charging_min", + &pdata->store_mode_charging_min); + if (ret) { + pr_info("%s :factory_chg_limit_min is Empty\n", __func__); + pdata->store_mode_charging_min = 60; + } + /* VZW's prepaid devices has "VPP" as sales_code, not "VZW" */ + if (sales_code_is("VZW") || sales_code_is("VPP")) { + pr_info("%s: Sales is VZW or VPP\n", __func__); + + pdata->store_mode_charging_max = 35; + pdata->store_mode_charging_min = 30; + } +#endif /*CONFIG_SEC_FACTORY */ + + else { + pr_info("%s : battery_full_capacity : %d\n", __func__, pdata->battery_full_capacity); + pdata->cisd_cap_high_thr = pdata->battery_full_capacity + 1000; /* battery_full_capacity + 1000 */ + pdata->cisd_cap_low_thr = pdata->battery_full_capacity + 500; /* battery_full_capacity + 500 */ + pdata->cisd_cap_limit = (pdata->battery_full_capacity * 11) / 10; /* battery_full_capacity + 10% */ + } + + ret = of_property_read_u32(np, "battery,cisd_max_voltage_thr", + &pdata->max_voltage_thr); + if (ret) { + pr_info("%s : cisd_max_voltage_thr is Empty\n", __func__); + pdata->max_voltage_thr = 4400; + } + + ret = of_property_read_u32(np, "battery,cisd_alg_index", + &pdata->cisd_alg_index); + if (ret) { + pr_info("%s : cisd_alg_index is Empty. Defalut set to six\n", __func__); + pdata->cisd_alg_index = 6; + } else { + pr_info("%s : set cisd_alg_index : %d\n", __func__, pdata->cisd_alg_index); + } + + ret = of_property_read_u32(np, + "battery,expired_time", &temp); + if (ret) { + pr_info("expired time is empty\n"); + pdata->expired_time = 3 * 60 * 60; + } else { + pdata->expired_time = (unsigned int) temp; + } + pdata->expired_time *= 1000; + battery->expired_time = pdata->expired_time; + + ret = of_property_read_u32(np, + "battery,recharging_expired_time", &temp); + if (ret) { + pr_info("expired time is empty\n"); + pdata->recharging_expired_time = 90 * 60; + } else { + pdata->recharging_expired_time = (unsigned int) temp; + } + pdata->recharging_expired_time *= 1000; + + ret = of_property_read_u32(np, + "battery,standard_curr", &pdata->standard_curr); + if (ret) { + pr_info("standard_curr is empty\n"); + pdata->standard_curr = 2150; + } + + ret = of_property_read_string(np, + "battery,vendor", (char const **)&pdata->vendor); + if (ret) + pr_info("%s: Vendor is Empty\n", __func__); + + ret = of_property_read_string(np, + "battery,charger_name", (char const **)&pdata->charger_name); + if (ret) + pr_info("%s: Charger name is Empty\n", __func__); + + ret = of_property_read_string(np, + "battery,otg_name", (char const **)&pdata->otg_name); + if (ret) + pr_info("%s: otg_name is Empty\n", __func__); + + ret = of_property_read_string(np, + "battery,fuelgauge_name", (char const **)&pdata->fuelgauge_name); + if (ret) + pr_info("%s: Fuelgauge name is Empty\n", __func__); + + ret = of_property_read_string(np, + "battery,wireless_charger_name", (char const **)&pdata->wireless_charger_name); + if (ret) + pr_info("%s: Wireless charger name is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,inbat_ocv_type", + &pdata->inbat_ocv_type); + if (ret) { + pr_info("%s : inbat_ocv_type is Empty\n", __func__); + pdata->inbat_ocv_type = SEC_BATTERY_OCV_NONE; + } + + ret = of_property_read_string(np, + "battery,fgsrc_switch_name", (char const **)&pdata->fgsrc_switch_name); + if (ret) { + pdata->support_fgsrc_change = false; + pr_info("%s: fgsrc_switch_name is Empty\n", __func__); + } else { + pdata->support_fgsrc_change = true; + pdata->inbat_ocv_type = SEC_BATTERY_OCV_FG_SRC_CHANGE; + } + + pdata->dynamic_cv_factor = of_property_read_bool(np, + "battery,dynamic_cv_factor"); + + pdata->slowcharging_usb_bootcomplete = of_property_read_bool(np, + "battery,slowcharging_usb_bootcomplete"); + + ret = of_property_read_string(np, + "battery,chip_vendor", (char const **)&pdata->chip_vendor); + if (ret) + pr_info("%s: Chip vendor is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,technology", + &pdata->technology); + if (ret) + pr_info("%s : technology is Empty\n", __func__); + + ret = of_property_read_u32(np, + "battery,wireless_cc_cv", &pdata->wireless_cc_cv); + + pdata->p2p_cv_headroom = of_property_read_bool(np, + "battery,p2p_cv_headroom"); + + pdata->fake_capacity = of_property_read_bool(np, + "battery,fake_capacity"); + + pdata->bc12_ifcon_wa = of_property_read_bool(np, + "battery,bc12_ifcon_wa"); + + ret = of_property_read_u32(np, "battery,power_value", + &pdata->power_value); + if (ret) { + pdata->power_value = 6000; + pr_info("%s : power_value is Empty\n", __func__); + } + + pdata->en_auto_shipmode_temp_ctrl = of_property_read_bool(np, + "battery,en_auto_shipmode_temp_ctrl"); + + pdata->boosting_voltage_aicl = of_property_read_bool(np, + "battery,boosting_voltage_aicl"); + + battery->ta_alert_wa = of_property_read_bool(np, "battery,ta_alert_wa"); + +#if !defined(CONFIG_SEC_FACTORY) + pdata->mass_with_usb_thm = of_property_read_bool(np, + "battery,mass_with_usb_thm"); + pdata->usb_protection = of_property_read_bool(np, + "battery,usb_protection"); +#endif + + p = of_get_property(np, "battery,polling_time", &len); + if (!p) + return 1; + + len = len / sizeof(u32); + pdata->polling_time = kzalloc(sizeof(*pdata->polling_time) * len, GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,polling_time", + pdata->polling_time, len); + if (ret) + pr_info("%s : battery,polling_time is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,temp_check_count", + &pdata->temp_check_count); + if (ret) + pr_info("%s : Temp check count is Empty\n", __func__); + + sec_bat_parse_thm_info(np, "battery,", &pdata->bat_thm_info); + pdata->bat_thm_info.channel = SEC_BAT_ADC_CHANNEL_TEMP; + sec_bat_parse_thm_info(np, "battery,usb_", &pdata->usb_thm_info); + pdata->usb_thm_info.channel = SEC_BAT_ADC_CHANNEL_USB_TEMP; + sec_bat_parse_thm_info(np, "battery,chg_", &pdata->chg_thm_info); + pdata->chg_thm_info.channel = SEC_BAT_ADC_CHANNEL_CHG_TEMP; + sec_bat_parse_thm_info(np, "battery,wpc_", &pdata->wpc_thm_info); + pdata->wpc_thm_info.channel = SEC_BAT_ADC_CHANNEL_WPC_TEMP; + sec_bat_parse_thm_info(np, "battery,sub_bat_", &pdata->sub_bat_thm_info); + pdata->sub_bat_thm_info.channel = SEC_BAT_ADC_CHANNEL_SUB_BAT_TEMP; + sec_bat_parse_thm_info(np, "battery,blkt_", &pdata->blk_thm_info); + pdata->blk_thm_info.channel = SEC_BAT_ADC_CHANNEL_BLKT_TEMP; + + ret = of_property_read_u32(np, "battery,adc_read_type", + &pdata->adc_read_type); + if (ret || + (pdata->adc_read_type != SEC_BATTERY_ADC_PROCESSED && + pdata->adc_read_type != SEC_BATTERY_ADC_RAW)) { + pdata->adc_read_type = SEC_BATTERY_ADC_PROCESSED; + pr_info("%s : adc_read_type is default (processed)\n", __func__); + } else { + pr_info("%s : adc_read_type is %s\n", + __func__, (pdata->adc_read_type ? "raw" : "proc.")); + } + + /* parse dc thm info */ + sec_bat_parse_dc_thm(np, pdata); + + ret = of_property_read_u32(np, "battery,d2d_check_type", + &pdata->d2d_check_type); + if (ret) { + pdata->d2d_check_type = SB_D2D_NONE; + pr_info("%s : d2d_check_type is Empty\n", __func__); + } + + pdata->support_vpdo = of_property_read_bool(np, + "battery,support_vpdo"); + + ret = of_property_read_u32(np, "battery,lrp_temp_check_type", + &pdata->lrp_temp_check_type); + if (ret) + pr_info("%s : lrp_temp_check_type is Empty\n", __func__); + + if (pdata->lrp_temp_check_type) { + for (i = 0; i < LRP_MAX; i++) { + if (sec_bat_parse_dt_lrp(battery, np, i) < 0) { + pdata->lrp_temp[i].trig[ST1][LCD_OFF] = 375; + pdata->lrp_temp[i].trig[ST2][LCD_OFF] = 375; + pdata->lrp_temp[i].recov[ST1][LCD_OFF] = 365; + pdata->lrp_temp[i].recov[ST2][LCD_OFF] = 365; + pdata->lrp_temp[i].trig[ST1][LCD_ON] = 375; + pdata->lrp_temp[i].trig[ST2][LCD_ON] = 375; + pdata->lrp_temp[i].recov[ST1][LCD_ON] = 365; + pdata->lrp_temp[i].recov[ST2][LCD_ON] = 365; + pdata->lrp_curr[i].st_icl[0] = pdata->default_input_current; + pdata->lrp_curr[i].st_fcc[0] = pdata->default_charging_current; + pdata->lrp_curr[i].st_icl[1] = pdata->default_input_current; + pdata->lrp_curr[i].st_fcc[1] = pdata->default_charging_current; + } + } + pdata->sc_LRP_25W = of_property_read_bool(np, + "battery,sc_LRP_25W"); + } +/* mix temp v2 */ + pdata->enable_mix_v2 = of_property_read_bool(np, "battery,enable_mix_v2"); + + ret = of_property_read_u32(np, "battery,mix_v2_lrp_recov", &pdata->mix_v2_lrp_recov); + if (ret) { + pr_info("%s : mix_v2_lrp_recov is Empty\n", __func__); + pdata->mix_v2_lrp_recov = 0; + } + ret = of_property_read_u32(np, "battery,mix_v2_lrp_cond", &pdata->mix_v2_lrp_cond); + if (ret) { + pr_info("%s : mix_v2_lrp_cond is Empty\n", __func__); + pdata->mix_v2_lrp_cond = 0; + } + ret = of_property_read_u32(np, "battery,mix_v2_bat_cond", &pdata->mix_v2_bat_cond); + if (ret) { + pr_info("%s : mix_v2_bat_cond is Empty\n", __func__); + pdata->mix_v2_bat_cond = 0; + } + ret = of_property_read_u32(np, "battery,mix_v2_chg_cond", &pdata->mix_v2_chg_cond); + if (ret) { + pr_info("%s : mix_v2_chg_cond is Empty\n", __func__); + pdata->mix_v2_chg_cond = 0; + } +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + ret = of_property_read_u32(np, "battery,mix_v2_dchg_cond", &pdata->mix_v2_dchg_cond); + if (ret) { + pr_info("%s : mix_v2_dchg_cond is Empty\n", __func__); + pdata->mix_v2_dchg_cond = 0; + } +#else + pr_info("%s : mix_v2_dchg_cond is not supported\n", __func__); + pdata->mix_v2_dchg_cond = 0; +#endif +/* mix temp v2 */ + + if (pdata->chg_thm_info.check_type) { + ret = of_property_read_u32(np, "battery,chg_12v_high_temp", + &temp); + pdata->chg_12v_high_temp = (int)temp; + if (ret) + pr_info("%s : chg_12v_high_temp is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,chg_high_temp", + &temp); + pdata->chg_high_temp = (int)temp; + if (ret) + pr_info("%s : chg_high_temp is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,chg_high_temp_recovery", + &temp); + pdata->chg_high_temp_recovery = (int)temp; + if (ret) + pr_info("%s : chg_temp_recovery is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,chg_charging_limit_current", + &pdata->chg_charging_limit_current); + if (ret) + pr_info("%s : chg_charging_limit_current is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,chg_input_limit_current", + &pdata->chg_input_limit_current); + if (ret) + pr_info("%s : chg_input_limit_current is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,dchg_charging_limit_current", + &pdata->dchg_charging_limit_current); + if (ret) { + pr_info("%s : dchg_charging_limit_current is Empty\n", __func__); + pdata->dchg_charging_limit_current = pdata->chg_charging_limit_current; + } + + ret = of_property_read_u32(np, "battery,dchg_input_limit_current", + &pdata->dchg_input_limit_current); + if (ret) { + pr_info("%s : dchg_input_limit_current is Empty\n", __func__); + pdata->dchg_input_limit_current = pdata->chg_input_limit_current; + } + + ret = of_property_read_u32(np, "battery,mix_high_temp", + &temp); + pdata->mix_high_temp = (int)temp; + if (ret) + pr_info("%s : mix_high_temp is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,mix_high_chg_temp", + &temp); + pdata->mix_high_chg_temp = (int)temp; + if (ret) + pr_info("%s : mix_high_chg_temp is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,mix_high_temp_recovery", + &temp); + pdata->mix_high_temp_recovery = (int)temp; + if (ret) + pr_info("%s : mix_high_temp_recovery is Empty\n", __func__); + } + + if (pdata->wpc_thm_info.check_type) { + ret = of_property_read_u32(np, "battery,sub_temp_control_source", + &pdata->sub_temp_control_source); + if (ret) { + pr_info("%s : sub_temp_control_source is Empty\n", __func__); + pdata->sub_temp_control_source = TEMP_CONTROL_SOURCE_BAT_THM; + } + + ret = of_property_read_u32(np, "battery,wpc_temp_control_source", + &pdata->wpc_temp_control_source); + if (ret) { + pr_info("%s : wpc_temp_control_source is Empty\n", __func__); + pdata->wpc_temp_control_source = TEMP_CONTROL_SOURCE_CHG_THM; + } + + ret = of_property_read_u32(np, "battery,wpc_temp_lcd_on_control_source", + &pdata->wpc_temp_lcd_on_control_source); + if (ret) { + pr_info("%s : wpc_temp_lcd_on_control_source is Empty\n", __func__); + pdata->wpc_temp_lcd_on_control_source = TEMP_CONTROL_SOURCE_CHG_THM; + } + + ret = of_property_read_u32(np, "battery,wpc_high_temp", + &pdata->wpc_high_temp); + if (ret) + pr_info("%s : wpc_high_temp is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,wpc_high_temp_recovery", + &pdata->wpc_high_temp_recovery); + if (ret) + pr_info("%s : wpc_high_temp_recovery is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,wpc_high_temp_12w", + &pdata->wpc_high_temp_12w); + if (ret) { + pr_info("%s : wpc_high_temp_12w is Empty\n", __func__); + pdata->wpc_high_temp_12w = pdata->wpc_high_temp; + } + + ret = of_property_read_u32(np, "battery,wpc_high_temp_recovery_12w", + &pdata->wpc_high_temp_recovery_12w); + if (ret) { + pr_info("%s : wpc_high_temp_recovery_12w is Empty\n", __func__); + pdata->wpc_high_temp_recovery_12w = pdata->wpc_high_temp_recovery; + } + + ret = of_property_read_u32(np, "battery,wpc_high_temp_15w", + &pdata->wpc_high_temp_15w); + if (ret) { + pr_info("%s : wpc_high_temp_15w is Empty\n", __func__); + pdata->wpc_high_temp_15w = pdata->wpc_high_temp; + } + + ret = of_property_read_u32(np, "battery,wpc_high_temp_recovery_15w", + &pdata->wpc_high_temp_recovery_15w); + if (ret) { + pr_info("%s : wpc_high_temp_recovery_15w is Empty\n", __func__); + pdata->wpc_high_temp_recovery_15w = pdata->wpc_high_temp_recovery; + } + + pdata->wpc_high_check_using_lrp = of_property_read_bool(np, "battery,wpc_high_check_using_lrp"); + + if (pdata->wpc_high_check_using_lrp) { + ret = of_property_read_u32(np, "battery,wpc_lrp_high_temp", + &pdata->wpc_lrp_high_temp); + if (ret) + pr_info("%s : wpc_lrp_high_temp is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,wpc_lrp_high_temp_recovery", + &pdata->wpc_lrp_high_temp_recovery); + if (ret) + pr_info("%s : wpc_lrp_high_temp_recovery is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,wpc_lrp_high_temp_12w", + &pdata->wpc_lrp_high_temp_12w); + if (ret) { + pr_info("%s : wpc_lrp_high_temp_12w is Empty\n", __func__); + pdata->wpc_lrp_high_temp_12w = pdata->wpc_lrp_high_temp; + } + + ret = of_property_read_u32(np, "battery,wpc_lrp_high_temp_recovery_12w", + &pdata->wpc_lrp_high_temp_recovery_12w); + if (ret) { + pr_info("%s : wpc_lrp_high_temp_recovery_12w is Empty\n", __func__); + pdata->wpc_lrp_high_temp_recovery_12w = pdata->wpc_lrp_high_temp_recovery; + } + + ret = of_property_read_u32(np, "battery,wpc_lrp_high_temp_15w", + &pdata->wpc_lrp_high_temp_15w); + if (ret) { + pr_info("%s : wpc_lrp_high_temp_15w is Empty\n", __func__); + pdata->wpc_lrp_high_temp_15w = pdata->wpc_lrp_high_temp; + } + + ret = of_property_read_u32(np, "battery,wpc_lrp_high_temp_recovery_15w", + &pdata->wpc_lrp_high_temp_recovery_15w); + if (ret) { + pr_info("%s : wpc_lrp_high_temp_recovery_15w is Empty\n", __func__); + pdata->wpc_lrp_high_temp_recovery_15w = pdata->wpc_lrp_high_temp_recovery; + } + } + + pdata->enable_check_wpc_temp_v2 = + of_property_read_bool(np, "battery,enable_check_wpc_temp_v2"); + + if (pdata->enable_check_wpc_temp_v2) { + ret = of_property_read_u32(np, "battery,wpc_temp_v2_cond", &pdata->wpc_temp_v2_cond); + if (ret) { + pr_info("%s : wpc_temp_v2_cond is Empty\n", __func__); + pdata->wpc_temp_v2_cond = 0; + } + ret = of_property_read_u32(np, "battery,wpc_temp_v2_cond_12w", &pdata->wpc_temp_v2_cond_12w); + if (ret) { + pr_info("%s : wpc_temp_v2_cond_12w is Empty\n", __func__); + pdata->wpc_temp_v2_cond_12w = pdata->wpc_temp_v2_cond; + } + ret = of_property_read_u32(np, "battery,wpc_temp_v2_cond_15w", &pdata->wpc_temp_v2_cond_15w); + if (ret) { + pr_info("%s : wpc_temp_v2_cond_15w is Empty\n", __func__); + pdata->wpc_temp_v2_cond_15w = pdata->wpc_temp_v2_cond; + } + if (pdata->wpc_high_check_using_lrp) { + ret = of_property_read_u32(np, "battery,wpc_lrp_temp_v2_cond", + &pdata->wpc_lrp_temp_v2_cond); + if (ret) { + pr_info("%s : wpc_lrp_temp_v2_cond is Empty\n", __func__); + pdata->wpc_lrp_temp_v2_cond = 0; + } + ret = of_property_read_u32(np, "battery,wpc_lrp_temp_v2_cond_12w", + &pdata->wpc_lrp_temp_v2_cond_12w); + if (ret) { + pr_info("%s : wpc_lrp_temp_v2_cond_12w is Empty\n", __func__); + pdata->wpc_lrp_temp_v2_cond_12w = pdata->wpc_lrp_temp_v2_cond; + } + ret = of_property_read_u32(np, "battery,wpc_lrp_temp_v2_cond_15w", + &pdata->wpc_lrp_temp_v2_cond_15w); + if (ret) { + pr_info("%s : wpc_lrp_temp_v2_cond_15w is Empty\n", __func__); + pdata->wpc_lrp_temp_v2_cond_15w = pdata->wpc_lrp_temp_v2_cond; + } + } + } + + ret = of_property_read_u32(np, "battery,wpc_lcd_on_high_temp", + &pdata->wpc_lcd_on_high_temp); + if (ret) { + pr_info("%s : wpc_lcd_on_high_temp is Empty\n", __func__); + pdata->wpc_lcd_on_high_temp = pdata->wpc_high_temp; + } + + ret = of_property_read_u32(np, "battery,wpc_lcd_on_high_temp_rec", + &pdata->wpc_lcd_on_high_temp_rec); + if (ret) { + pr_info("%s : wpc_lcd_on_high_temp_rec is Empty\n", __func__); + pdata->wpc_lcd_on_high_temp_rec = pdata->wpc_high_temp_recovery; + } + + ret = of_property_read_u32(np, "battery,wpc_lcd_on_high_temp_12w", + &pdata->wpc_lcd_on_high_temp_12w); + if (ret) { + pr_info("%s : wpc_lcd_on_high_temp_12w is Empty\n", __func__); + pdata->wpc_lcd_on_high_temp_12w = pdata->wpc_high_temp_12w; + } + + ret = of_property_read_u32(np, "battery,wpc_lcd_on_high_temp_rec_12w", + &pdata->wpc_lcd_on_high_temp_rec_12w); + if (ret) { + pr_info("%s : wpc_lcd_on_high_temp_rec_12w is Empty\n", __func__); + pdata->wpc_lcd_on_high_temp_rec_12w = pdata->wpc_high_temp_recovery_12w; + } + + ret = of_property_read_u32(np, "battery,wpc_lcd_on_high_temp_15w", + &pdata->wpc_lcd_on_high_temp_15w); + if (ret) { + pr_info("%s : wpc_lcd_on_high_temp_15w is Empty\n", __func__); + pdata->wpc_lcd_on_high_temp_15w = pdata->wpc_high_temp_15w; + } + + ret = of_property_read_u32(np, "battery,wpc_lcd_on_high_temp_rec_15w", + &pdata->wpc_lcd_on_high_temp_rec_15w); + if (ret) { + pr_info("%s : wpc_lcd_on_high_temp_rec_15w is Empty\n", __func__); + pdata->wpc_lcd_on_high_temp_rec_15w = pdata->wpc_high_temp_recovery_15w; + } + + ret = of_property_read_u32(np, "battery,wpc_input_limit_current", + &pdata->wpc_input_limit_current); + if (ret) + pr_info("%s : wpc_input_limit_current is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,wpc_charging_limit_current", + &pdata->wpc_charging_limit_current); + if (ret) + pr_info("%s : wpc_charging_limit_current is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,wpc_lcd_on_input_limit_current", + &pdata->wpc_lcd_on_input_limit_current); + if (ret) { + pr_info("%s : wpc_lcd_on_input_limit_current is Empty\n", __func__); + pdata->wpc_lcd_on_input_limit_current = + pdata->wpc_input_limit_current; + } + + pdata->wpc_vout_ctrl_lcd_on = of_property_read_bool(np, + "battery,wpc_vout_ctrl_lcd_on"); + if (pdata->wpc_vout_ctrl_lcd_on) { + ret = of_property_read_u32(np, "battery,wpc_flicker_wa_input_limit_current", + &pdata->wpc_flicker_wa_input_limit_current); + if (ret) + pr_info("%s : wpc_flicker_wa_input_limit_current is Empty\n", __func__); + } + + len = of_property_count_u32_elems(np, "battery,wpc_step_limit_temp"); + if (len > 0) { + pdata->wpc_step_limit_size = len; + len = of_property_count_u32_elems(np, "battery,wpc_step_limit_fcc"); + if (pdata->wpc_step_limit_size != len) { + pr_err("%s: not matched, wpc_step_limit_temp is %d, wpc_step_limit_fcc is %d\n", + __func__, pdata->wpc_step_limit_size, len); + pdata->wpc_step_limit_size = 0; + } else { + pdata->wpc_step_limit_temp = + kcalloc(pdata->wpc_step_limit_size, sizeof(unsigned int), GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,wpc_step_limit_temp", + (u32 *)pdata->wpc_step_limit_temp, pdata->wpc_step_limit_size); + if (ret < 0) { + pr_err("%s failed to read battery,wpc_step_limit_temp: %d\n", + __func__, ret); + + kfree(pdata->wpc_step_limit_temp); + pdata->wpc_step_limit_temp = NULL; + } + + pdata->wpc_step_limit_fcc = + kcalloc(pdata->wpc_step_limit_size, sizeof(unsigned int), GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,wpc_step_limit_fcc", + (u32 *)pdata->wpc_step_limit_fcc, pdata->wpc_step_limit_size); + if (ret < 0) { + pr_err("%s failed to read battery,wpc_step_limit_fcc: %d\n", + __func__, ret); + + kfree(pdata->wpc_step_limit_fcc); + pdata->wpc_step_limit_fcc = NULL; + } + + if (!pdata->wpc_step_limit_temp || !pdata->wpc_step_limit_fcc) { + pdata->wpc_step_limit_size = 0; + } else { + for (i = 0; i < pdata->wpc_step_limit_size; ++i) { + pr_info("%s: wpc_step temp:%d, fcc:%d\n", __func__, + pdata->wpc_step_limit_temp[i], pdata->wpc_step_limit_fcc[i]); + } + } + } + + len = of_property_count_u32_elems(np, "battery,wpc_step_limit_fcc_12w"); + if (pdata->wpc_step_limit_size != len) { + pr_err("%s: not matched, wpc_step_limit_temp is %d, wpc_step_limit_fcc_12w is %d\n", + __func__, pdata->wpc_step_limit_size, len); + pdata->wpc_step_limit_fcc_12w = + kcalloc(pdata->wpc_step_limit_size, sizeof(unsigned int), GFP_KERNEL); + for (i = 0; i < pdata->wpc_step_limit_size; ++i) { + pdata->wpc_step_limit_fcc_12w[i] = pdata->wpc_step_limit_fcc[i]; + pr_info("%s: wpc_step temp:%d, fcc_12w:%d\n", __func__, + pdata->wpc_step_limit_temp[i], pdata->wpc_step_limit_fcc_12w[i]); + } + } else { + pdata->wpc_step_limit_fcc_12w = + kcalloc(pdata->wpc_step_limit_size, sizeof(unsigned int), GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,wpc_step_limit_fcc_12w", + (u32 *)pdata->wpc_step_limit_fcc_12w, + pdata->wpc_step_limit_size); + if (ret < 0) { + pr_err("%s failed to read battery,wpc_step_limit_fcc_12w: %d\n", + __func__, ret); + + for (i = 0; i < pdata->wpc_step_limit_size; ++i) { + pdata->wpc_step_limit_fcc_12w[i] = pdata->wpc_step_limit_fcc[i]; + pr_info("%s: wpc_step temp:%d, fcc_12w:%d\n", __func__, + pdata->wpc_step_limit_temp[i], + pdata->wpc_step_limit_fcc_12w[i]); + } + } else { + for (i = 0; i < pdata->wpc_step_limit_size; ++i) { + pr_info("%s: wpc_step temp:%d, fcc_12w:%d\n", __func__, + pdata->wpc_step_limit_temp[i], + pdata->wpc_step_limit_fcc_12w[i]); + } + } + } + + len = of_property_count_u32_elems(np, "battery,wpc_step_limit_fcc_15w"); + if (pdata->wpc_step_limit_size != len) { + pr_err("%s: not matched, wpc_step_limit_temp is %d, wpc_step_limit_fcc_15w is %d\n", + __func__, pdata->wpc_step_limit_size, len); + pdata->wpc_step_limit_fcc_15w = + kcalloc(pdata->wpc_step_limit_size, sizeof(unsigned int), GFP_KERNEL); + for (i = 0; i < pdata->wpc_step_limit_size; ++i) { + pdata->wpc_step_limit_fcc_15w[i] = pdata->wpc_step_limit_fcc[i]; + pr_info("%s: wpc_step temp:%d, fcc_15w:%d\n", __func__, + pdata->wpc_step_limit_temp[i], pdata->wpc_step_limit_fcc_15w[i]); + } + } else { + pdata->wpc_step_limit_fcc_15w = + kcalloc(pdata->wpc_step_limit_size, sizeof(unsigned int), GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,wpc_step_limit_fcc_15w", + (u32 *)pdata->wpc_step_limit_fcc_15w, + pdata->wpc_step_limit_size); + if (ret < 0) { + pr_err("%s failed to read battery,wpc_step_limit_fcc_15w: %d\n", + __func__, ret); + + for (i = 0; i < pdata->wpc_step_limit_size; ++i) { + pdata->wpc_step_limit_fcc_15w[i] = pdata->wpc_step_limit_fcc[i]; + pr_info("%s: wpc_step temp:%d, fcc_15w:%d\n", __func__, + pdata->wpc_step_limit_temp[i], + pdata->wpc_step_limit_fcc_15w[i]); + } + } else { + for (i = 0; i < pdata->wpc_step_limit_size; ++i) { + pr_info("%s: wpc_step temp:%d, fcc_15w:%d\n", __func__, + pdata->wpc_step_limit_temp[i], + pdata->wpc_step_limit_fcc_15w[i]); + } + } + } + } else { + pdata->wpc_step_limit_size = 0; + pr_info("%s : wpc_step_limit_temp is Empty. len(%d), wpc_step_limit_size(%d)\n", + __func__, len, pdata->wpc_step_limit_size); + } + + } + + ret = of_property_read_u32(np, "battery,wc_full_input_limit_current", + &pdata->wc_full_input_limit_current); + if (ret) + pr_info("%s : wc_full_input_limit_current is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,wc_hero_stand_cc_cv", + &pdata->wc_hero_stand_cc_cv); + if (ret) { + pr_info("%s : wc_hero_stand_cc_cv is Empty\n", __func__); + pdata->wc_hero_stand_cc_cv = 70; + } + ret = of_property_read_u32(np, "battery,wc_hero_stand_cv_current", + &pdata->wc_hero_stand_cv_current); + if (ret) { + pr_info("%s : wc_hero_stand_cv_current is Empty\n", __func__); + pdata->wc_hero_stand_cv_current = 600; + } + ret = of_property_read_u32(np, "battery,wc_hero_stand_hv_cv_current", + &pdata->wc_hero_stand_hv_cv_current); + if (ret) { + pr_info("%s : wc_hero_stand_hv_cv_current is Empty\n", __func__); + pdata->wc_hero_stand_hv_cv_current = 450; + } + + ret = of_property_read_u32(np, "battery,sleep_mode_limit_current", + &pdata->sleep_mode_limit_current); + if (ret) + pr_info("%s : sleep_mode_limit_current is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,inbat_voltage", + &pdata->inbat_voltage); + if (ret) + pr_info("%s : inbat_voltage is Empty\n", __func__); + + if (pdata->inbat_voltage) { + p = of_get_property(np, "battery,inbat_voltage_table_adc", &len); + if (!p) + return 1; + + len = len / sizeof(u32); + + pdata->inbat_adc_table_size = len; + + pdata->inbat_adc_table = + kzalloc(sizeof(sec_bat_adc_table_data_t) * + pdata->inbat_adc_table_size, GFP_KERNEL); + + for (i = 0; i < pdata->inbat_adc_table_size; i++) { + ret = of_property_read_u32_index(np, + "battery,inbat_voltage_table_adc", i, &temp); + pdata->inbat_adc_table[i].adc = (int)temp; + if (ret) + pr_info("%s : inbat_adc_table(adc) is Empty\n", + __func__); + + ret = of_property_read_u32_index(np, + "battery,inbat_voltage_table_data", i, &temp); + pdata->inbat_adc_table[i].data = (int)temp; + if (ret) + pr_info("%s : inbat_adc_table(data) is Empty\n", + __func__); + } + } + + ret = of_property_read_u32(np, "battery,pre_afc_input_current", + &pdata->pre_afc_input_current); + if (ret) { + pr_info("%s : pre_afc_input_current is Empty\n", __func__); + pdata->pre_afc_input_current = 1000; + } + + ret = of_property_read_u32(np, "battery,select_pd_input_current", + &pdata->select_pd_input_current); + if (ret) { + pr_info("%s : select_pd_input_current is Empty\n", __func__); + pdata->select_pd_input_current = 1000; + } + + ret = of_property_read_u32(np, "battery,pre_afc_work_delay", + &pdata->pre_afc_work_delay); + if (ret) { + pr_info("%s : pre_afc_work_delay is Empty\n", __func__); + pdata->pre_afc_work_delay = 2000; + } + + ret = of_property_read_u32(np, "battery,pre_wc_afc_input_current", + &pdata->pre_wc_afc_input_current); + if (ret) { + pr_info("%s : pre_wc_afc_input_current is Empty\n", __func__); + pdata->pre_wc_afc_input_current = 500; /* wc input default */ + } + + ret = of_property_read_u32(np, "battery,pre_wc_afc_work_delay", + &pdata->pre_wc_afc_work_delay); + if (ret) { + pr_info("%s : pre_wc_afc_work_delay is Empty\n", __func__); + pdata->pre_wc_afc_work_delay = 4000; + } + + ret = of_property_read_u32(np, "battery,select_pd_input_current", + &pdata->select_pd_input_current); + if (ret) { + pr_info("%s : select_pd_input_current is Empty\n", __func__); + pdata->select_pd_input_current = 1000; + } + + ret = of_property_read_u32(np, "battery,tx_stop_capacity", + &pdata->tx_stop_capacity); + if (ret) { + pr_info("%s : tx_stop_capacity is Empty\n", __func__); + pdata->tx_stop_capacity = 30; + } + + ret = of_property_read_u32(np, "battery,adc_check_count", + &pdata->adc_check_count); + if (ret) + pr_info("%s : Adc check count is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,cable_check_type", + &pdata->cable_check_type); + if (ret) + pr_info("%s : Cable check type is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,cable_source_type", + &pdata->cable_source_type); + if (ret) + pr_info("%s: Cable_source_type is Empty\n", __func__); +#if defined(CONFIG_CHARGING_VZWCONCEPT) + pdata->cable_check_type &= ~SEC_BATTERY_CABLE_CHECK_NOUSBCHARGE; + pdata->cable_check_type |= SEC_BATTERY_CABLE_CHECK_NOINCOMPATIBLECHARGE; +#endif + ret = of_property_read_u32(np, "battery,polling_type", + &pdata->polling_type); + if (ret) + pr_info("%s : Polling type is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,monitor_initial_count", + &pdata->monitor_initial_count); + if (ret) + pr_info("%s : Monitor initial count is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,battery_check_type", + &pdata->battery_check_type); + if (ret) + pr_info("%s : Battery check type is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,check_count", + &pdata->check_count); + if (ret) + pr_info("%s : Check count is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,check_adc_max", + &pdata->check_adc_max); + if (ret) + pr_info("%s : Check adc max is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,check_adc_min", + &pdata->check_adc_min); + if (ret) + pr_info("%s : Check adc min is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,ovp_uvlo_check_type", + &pdata->ovp_uvlo_check_type); + if (ret) + pr_info("%s : Ovp Uvlo check type is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,overheatlimit_threshold", &temp); + battery->overheatlimit_threshold = (int)temp; + if (ret) { + pr_info("%s : overheatlimit_threshold is Empty\n", __func__); + battery->overheatlimit_threshold = 700; + } + + ret = of_property_read_u32(np, "battery,overheatlimit_recovery", &temp); + battery->overheatlimit_recovery = (int)temp; + if (ret) { + pr_info("%s : overheatlimit_recovery is Empty\n", __func__); + battery->overheatlimit_recovery = 680; + } + + ret = of_property_read_u32(np, "battery,usb_protection_temp", &temp); + battery->usb_protection_temp = (int)temp; + if (ret) { + pr_info("%s : usb protection temp value is Empty\n", __func__); + battery->usb_protection_temp = 610; + } + + ret = of_property_read_u32(np, "battery,temp_gap_bat_usb", &temp); + battery->temp_gap_bat_usb = (int)temp; + if (ret) { + pr_info("%s : temp gap value is Empty\n", __func__); + battery->temp_gap_bat_usb = 200; + } + + ret = of_property_read_u32(np, "battery,wire_warm_overheat_thresh", &temp); + pdata->wire_warm_overheat_thresh = (int)temp; + if (ret) { + pr_info("%s : wire_warm_overheat_thresh is Empty\n", __func__); + pdata->wire_warm_overheat_thresh = 500; + } + + ret = of_property_read_u32(np, "battery,wire_normal_warm_thresh", &temp); + pdata->wire_normal_warm_thresh = (int)temp; + if (ret) { + pr_info("%s : wire_normal_warm_thresh is Empty\n", __func__); + pdata->wire_normal_warm_thresh = 420; + } + + ret = of_property_read_u32(np, "battery,wire_cool1_normal_thresh", &temp); + pdata->wire_cool1_normal_thresh = (int)temp; + if (ret) { + pr_info("%s : wire_cool1_normal_thresh is Empty\n", __func__); + pdata->wire_cool1_normal_thresh = 180; + } + + ret = of_property_read_u32(np, "battery,wire_cool2_cool1_thresh", &temp); + pdata->wire_cool2_cool1_thresh = (int)temp; + if (ret) { + pr_info("%s : wire_cool2_cool1_thresh is Empty\n", __func__); + pdata->wire_cool2_cool1_thresh = 150; + } + + ret = of_property_read_u32(np, "battery,wire_cool3_cool2_thresh", &temp); + pdata->wire_cool3_cool2_thresh = (int)temp; + if (ret) { + pr_info("%s : wire_cool3_cool2_thresh is Empty\n", __func__); + pdata->wire_cool3_cool2_thresh = 50; + } + + ret = of_property_read_u32(np, "battery,wire_cold_cool3_thresh", &temp); + pdata->wire_cold_cool3_thresh = (int)temp; + if (ret) { + pr_info("%s : wire_cold_cool3_thresh is Empty\n", __func__); + pdata->wire_cold_cool3_thresh = 0; + } + + ret = of_property_read_u32(np, "battery,wireless_warm_overheat_thresh", &temp); + pdata->wireless_warm_overheat_thresh = (int)temp; + if (ret) { + pr_info("%s : wireless_warm_overheat_thresh is Empty\n", __func__); + pdata->wireless_warm_overheat_thresh = 450; + } + + ret = of_property_read_u32(np, "battery,wireless_normal_warm_thresh", &temp); + pdata->wireless_normal_warm_thresh = (int)temp; + if (ret) { + pr_info("%s : wireless_normal_warm_thresh is Empty\n", __func__); + pdata->wireless_normal_warm_thresh = 410; + } + + ret = of_property_read_u32(np, "battery,wireless_cool1_normal_thresh", &temp); + pdata->wireless_cool1_normal_thresh = (int)temp; + if (ret) { + pr_info("%s : wireless_cool1_normal_thresh is Empty\n", __func__); + pdata->wireless_cool1_normal_thresh = 180; + } + + ret = of_property_read_u32(np, "battery,wireless_cool2_cool1_thresh", &temp); + pdata->wireless_cool2_cool1_thresh = (int)temp; + if (ret) { + pr_info("%s : wireless_cool2_cool1_thresh is Empty\n", __func__); + pdata->wireless_cool2_cool1_thresh = 150; + } + + ret = of_property_read_u32(np, "battery,wireless_cool3_cool2_thresh", &temp); + pdata->wireless_cool3_cool2_thresh = (int)temp; + if (ret) { + pr_info("%s : wireless_cool3_cool2_thresh is Empty\n", __func__); + pdata->wireless_cool3_cool2_thresh = 50; + } + + ret = of_property_read_u32(np, "battery,wireless_cold_cool3_thresh", &temp); + pdata->wireless_cold_cool3_thresh = (int)temp; + if (ret) { + pr_info("%s : wireless_cold_cool3_thresh is Empty\n", __func__); + pdata->wireless_cold_cool3_thresh = 0; + } + + ret = of_property_read_u32(np, "battery,wire_warm_current", &temp); + pdata->wire_warm_current = (int)temp; + if (ret) { + pr_info("%s : wire_warm_current is Empty\n", __func__); + pdata->wire_warm_current = 500; + } + + ret = of_property_read_u32(np, "battery,wire_cool1_current", &temp); + pdata->wire_cool1_current = (int)temp; + if (ret) { + pr_info("%s : wire_cool1_current is Empty\n", __func__); + pdata->wire_cool1_current = 500; + } + + ret = of_property_read_u32(np, "battery,wire_cool2_current", &temp); + pdata->wire_cool2_current = (int)temp; + if (ret) { + pr_info("%s : wire_cool2_current is Empty\n", __func__); + pdata->wire_cool2_current = 500; + } + + ret = of_property_read_u32(np, "battery,wire_cool3_current", &temp); + pdata->wire_cool3_current = (int)temp; + if (ret) { + pr_info("%s : wire_cool3_current is Empty\n", __func__); + pdata->wire_cool3_current = 500; + } + + ret = of_property_read_u32(np, "battery,wireless_warm_current", &temp); + pdata->wireless_warm_current = (int)temp; + if (ret) { + pr_info("%s : wireless_warm_current is Empty\n", __func__); + pdata->wireless_warm_current = 500; + } + + ret = of_property_read_u32(np, "battery,wireless_cool1_current", &temp); + pdata->wireless_cool1_current = (int)temp; + if (ret) { + pr_info("%s : wireless_cool1_current is Empty\n", __func__); + pdata->wireless_cool1_current = 500; + } + + ret = of_property_read_u32(np, "battery,wireless_cool2_current", &temp); + pdata->wireless_cool2_current = (int)temp; + if (ret) { + pr_info("%s : wireless_cool2_current is Empty\n", __func__); + pdata->wireless_cool2_current = 500; + } + + ret = of_property_read_u32(np, "battery,wireless_cool3_current", &temp); + pdata->wireless_cool3_current = (int)temp; + if (ret) { + pr_info("%s : wireless_cool3_current is Empty\n", __func__); + pdata->wireless_cool3_current = 500; + } + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + ret = of_property_read_u32(np, "battery,limiter_main_warm_current", + &pdata->limiter_main_warm_current); + if (ret) + pr_info("%s: limiter_main_warm_current is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,limiter_sub_warm_current", + &pdata->limiter_sub_warm_current); + if (ret) + pr_info("%s: limiter_sub_warm_current is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,limiter_main_cool1_current", + &pdata->limiter_main_cool1_current); + if (ret) + pr_info("%s: limiter_main_cool1_current is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,limiter_sub_cool1_current", + &pdata->limiter_sub_cool1_current); + if (ret) + pr_info("%s: limiter_sub_cool1_current is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,limiter_main_cool2_current", + &pdata->limiter_main_cool2_current); + if (ret) + pr_info("%s: limiter_main_cool2_current is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,limiter_sub_cool2_current", + &pdata->limiter_sub_cool2_current); + if (ret) + pr_info("%s: limiter_sub_cool2_current is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,limiter_main_cool3_current", + &pdata->limiter_main_cool3_current); + if (ret) + pr_info("%s: limiter_main_cool3_current is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,limiter_sub_cool3_current", + &pdata->limiter_sub_cool3_current); + if (ret) + pr_info("%s: limiter_sub_cool3_current is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,limiter_aging_float_offset", + &pdata->limiter_aging_float_offset); + if (ret) + pr_info("%s: limiter_aging_float_offset is Empty\n", __func__); +#endif + + ret = of_property_read_u32(np, "battery,high_temp_float", &temp); + pdata->high_temp_float = (int)temp; + if (ret) { + pr_info("%s : high_temp_float is Empty\n", __func__); + pdata->high_temp_float = 4150; + } + + ret = of_property_read_u32(np, "battery,low_temp_float", &temp); + pdata->low_temp_float = (int)temp; + if (ret) { + pr_info("%s : low_temp_float is Empty\n", __func__); + pdata->low_temp_float = 4350; + } + + ret = of_property_read_u32(np, "battery,low_temp_cool3_float", &temp); + pdata->low_temp_cool3_float = (int)temp; + if (ret) { + pr_info("%s : low_temp_cool3_float is Empty\n", __func__); + pdata->low_temp_cool3_float = pdata->low_temp_float; + } + + ret = of_property_read_u32(np, "battery,buck_recovery_margin", &temp); + pdata->buck_recovery_margin = (int)temp; + if (ret) { + pr_info("%s : buck_recovery_margin is Empty\n", __func__); + pdata->buck_recovery_margin = 50; + } + + ret = of_property_read_u32(np, "battery,recharge_condition_vcell", &pdata->recharge_condition_vcell); + if (ret) { + pr_info("%s : recharge_condition_vcell is Empty\n", __func__); + pdata->recharge_condition_vcell = 4270; + } + + ret = of_property_read_u32(np, "battery,swelling_high_rechg_voltage", &temp); + pdata->swelling_high_rechg_voltage = (int)temp; + if (ret) { + pr_info("%s : swelling_high_rechg_voltage is Empty\n", __func__); + pdata->swelling_high_rechg_voltage = 4000; + } + + ret = of_property_read_u32(np, "battery,swelling_low_rechg_voltage", &temp); + pdata->swelling_low_rechg_voltage = (int)temp; + if (ret) { + pr_info("%s : swelling_low_rechg_voltage is Empty\n", __func__); + pdata->swelling_low_rechg_voltage = 4200; + } + + ret = of_property_read_u32(np, "battery,swelling_low_cool3_rechg_voltage", &temp); + pdata->swelling_low_cool3_rechg_voltage = (int)temp; + if (ret) { + pr_info("%s : swelling_low_cool3_rechg_voltage is Empty\n", __func__); + pdata->swelling_low_cool3_rechg_voltage = pdata->swelling_low_rechg_voltage; + } + + pdata->chgen_over_swell_rechg_vol = of_property_read_bool(np, "battery,chgen_over_swell_rechg_vol"); + pr_info("%s: chgen_over_swell_rechg_vol %s.\n", __func__, + pdata->chgen_over_swell_rechg_vol ? "Enabled" : "Disabled"); + + ret = of_property_read_u32(np, "battery,tx_high_threshold", + &temp); + pdata->tx_high_threshold = (int)temp; + if (ret) { + pr_info("%s : tx_high_threshold is Empty\n", __func__); + pdata->tx_high_threshold = 450; + } + + ret = of_property_read_u32(np, "battery,tx_high_recovery", + &temp); + pdata->tx_high_recovery = (int)temp; + if (ret) { + pr_info("%s : tx_high_recovery is Empty\n", __func__); + pdata->tx_high_recovery = 400; + } + + ret = of_property_read_u32(np, "battery,tx_low_threshold", + &temp); + pdata->tx_low_threshold = (int)temp; + if (ret) { + pr_info("%s : tx_low_threshold is Empty\n", __func__); + pdata->tx_low_recovery = 0; + } + ret = of_property_read_u32(np, "battery,tx_low_recovery", + &temp); + pdata->tx_low_recovery = (int)temp; + if (ret) { + pr_info("%s : tx_low_recovery is Empty\n", __func__); + pdata->tx_low_recovery = 50; + } + + ret = of_property_read_u32(np, "battery,icl_by_tx_gear", + &pdata->icl_by_tx_gear); + if (ret) + pr_info("%s : icl_by_tx_gear is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,fcc_by_tx", + &pdata->fcc_by_tx); + if (ret) + pr_info("%s : fcc_by_tx is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,fcc_by_tx_gear", + &pdata->fcc_by_tx_gear); + if (ret) + pr_info("%s : fcc_by_tx_gear is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,wpc_input_limit_by_tx_check", + &pdata->wpc_input_limit_by_tx_check); + if (ret) + pr_info("%s : wpc_input_limit_by_tx_check is Empty\n", __func__); + + if (pdata->wpc_input_limit_by_tx_check) { + ret = of_property_read_u32(np, "battery,wpc_input_limit_current_by_tx", + &pdata->wpc_input_limit_current_by_tx); + if (ret) { + pr_info("%s : wpc_input_limit_current_by_tx is Empty\n", __func__); + pdata->wpc_input_limit_current_by_tx = + pdata->wpc_input_limit_current; + } + } + + ret = of_property_read_u32(np, "battery,full_check_type", + &pdata->full_check_type); + if (ret) + pr_info("%s : Full check type is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,full_check_type_2nd", + &pdata->full_check_type_2nd); + if (ret) + pr_info("%s : Full check type 2nd is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,full_check_count", + &pdata->full_check_count); + if (ret) + pr_info("%s : Full check count is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,chg_gpio_full_check", + &pdata->chg_gpio_full_check); + if (ret) + pr_info("%s : Chg gpio full check is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,chg_polarity_full_check", + &pdata->chg_polarity_full_check); + if (ret) + pr_info("%s : Chg polarity full check is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,full_condition_type", + &pdata->full_condition_type); + if (ret) + pr_info("%s : Full condition type is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,full_condition_soc", + &pdata->full_condition_soc); + if (ret) + pr_info("%s : Full condition soc is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,full_condition_vcell", + &pdata->full_condition_vcell); + if (ret) + pr_info("%s : Full condition vcell is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,recharge_check_count", + &pdata->recharge_check_count); + if (ret) + pr_info("%s : Recharge check count is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,recharge_condition_type", + &pdata->recharge_condition_type); + if (ret) + pr_info("%s : Recharge condition type is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,recharge_condition_soc", + &pdata->recharge_condition_soc); + if (ret) + pr_info("%s : Recharge condition soc is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,charging_reset_time", + (unsigned int *)&pdata->charging_reset_time); + if (ret) + pr_info("%s : Charging reset time is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,chg_float_voltage", + (unsigned int *)&pdata->chg_float_voltage); + if (ret) { + pr_info("%s: chg_float_voltage is Empty\n", __func__); + pdata->chg_float_voltage = 43500; + } + + ret = of_property_read_u32(np, "battery,chg_float_voltage_conv", + &pdata->chg_float_voltage_conv); + if (ret) { + pr_info("%s: chg_float_voltage_conv is Empty\n", __func__); + pdata->chg_float_voltage_conv = 1; + } + + ret = of_property_read_u32(np, "battery,max_charging_current", + &pdata->max_charging_current); + if (ret) { + pr_err("%s: max_charging_current is Empty\n", __func__); + pdata->max_charging_current = 3000; + } + + sec_bat_parse_age_data(np, battery->pdata); + + sec_bat_parse_health_condition(np, battery->pdata); + + sec_bat_parse_dt_siop(battery, np); + + ret = of_property_read_u32(np, "battery,wireless_otg_input_current", + &pdata->wireless_otg_input_current); + if (ret) + pdata->wireless_otg_input_current = WIRELESS_OTG_INPUT_CURRENT; + + ret = of_property_read_u32(np, "battery,max_input_voltage", + &pdata->max_input_voltage); + if (ret) + pdata->max_input_voltage = 20000; + + ret = of_property_read_u32(np, "battery,max_input_current", + &pdata->max_input_current); + if (ret) + pdata->max_input_current = 3000; + + ret = of_property_read_u32(np, "battery,pd_charging_charge_power", + &pdata->pd_charging_charge_power); + if (ret) { + pr_err("%s: pd_charging_charge_power is Empty\n", __func__); + pdata->pd_charging_charge_power = 15000; + } + + pdata->support_fpdo_dc = of_property_read_bool(np, "battery,support_fpdo_dc"); + if (pdata->support_fpdo_dc) { + ret = of_property_read_u32(np, "battery,fpdo_dc_charge_power", + &pdata->fpdo_dc_charge_power); + if (ret) { + pr_err("%s: fpdo_dc_charge_power is Empty\n", __func__); + pdata->fpdo_dc_charge_power = 15000; + } + } + + ret = of_property_read_u32(np, "battery,rp_current_rp1", + &pdata->rp_current_rp1); + if (ret) { + pr_err("%s: rp_current_rp1 is Empty\n", __func__); + pdata->rp_current_rp1 = 500; + } + + ret = of_property_read_u32(np, "battery,rp_current_rp2", + &pdata->rp_current_rp2); + if (ret) { + pr_err("%s: rp_current_rp2 is Empty\n", __func__); + pdata->rp_current_rp2 = 1500; + } + + ret = of_property_read_u32(np, "battery,rp_current_rp3", + &pdata->rp_current_rp3); + if (ret) { + pr_err("%s: rp_current_rp3 is Empty\n", __func__); + pdata->rp_current_rp3 = 3000; + } + + ret = of_property_read_u32(np, "battery,rp_current_rdu_rp3", + &pdata->rp_current_rdu_rp3); + if (ret) { + pr_err("%s: rp_current_rdu_rp3 is Empty\n", __func__); + pdata->rp_current_rdu_rp3 = 2100; + } + + ret = of_property_read_u32(np, "battery,rp_current_abnormal_rp3", + &pdata->rp_current_abnormal_rp3); + if (ret) { + pr_err("%s: rp_current_abnormal_rp3 is Empty\n", __func__); + pdata->rp_current_abnormal_rp3 = 1800; + } + + ret = of_property_read_u32(np, "battery,nv_charge_power", + &pdata->nv_charge_power); + if (ret) { + pr_err("%s: nv_charge_power is Empty\n", __func__); + pdata->nv_charge_power = mW_by_mVmA(SEC_INPUT_VOLTAGE_5V, pdata->default_input_current); + } + + ret = of_property_read_u32(np, "battery,tx_minduty_default", + &pdata->tx_minduty_default); + if (ret) { + pdata->tx_minduty_default = 20; + pr_err("%s: tx minduty is Empty. set %d\n", __func__, pdata->tx_minduty_default); + } + + ret = of_property_read_u32(np, "battery,tx_minduty_5V", + &pdata->tx_minduty_5V); + if (ret) { + pdata->tx_minduty_5V = 50; + pr_err("%s: tx minduty 5V is Empty. set %d\n", __func__, pdata->tx_minduty_5V); + } + + ret = of_property_read_u32(np, "battery,tx_ping_duty_default", + &pdata->tx_ping_duty_default); + if (ret) { + pdata->tx_ping_duty_default = 0; + pr_err("%s: tx ping duty default is not changed (disabled) %d\n", + __func__, pdata->tx_ping_duty_default); + } + + ret = of_property_read_u32(np, "battery,tx_ping_duty_no_ta", + &pdata->tx_ping_duty_no_ta); + if (ret) { + pdata->tx_ping_duty_no_ta = pdata->tx_ping_duty_default; + pr_err("%s: tx ping duty no TA is default %d\n", __func__, pdata->tx_ping_duty_no_ta); + } + if (pdata->tx_ping_duty_default) + pr_info("%s : tx_ping_duty_default: %d, tx_ping_duty_no_ta: %d\n", + __func__, pdata->tx_ping_duty_default, pdata->tx_ping_duty_no_ta); + + ret = of_property_read_u32(np, "battery,tx_uno_vout", + &pdata->tx_uno_vout); + if (ret) { + pdata->tx_uno_vout = WC_TX_VOUT_7500MV; + pr_err("%s: tx uno vout is Empty. set %d\n", __func__, pdata->tx_uno_vout); + } + + ret = of_property_read_u32(np, "battery,tx_ping_vout", + &pdata->tx_ping_vout); + if (ret) { + pdata->tx_ping_vout = WC_TX_VOUT_5000MV; + pr_err("%s: tx ping vout is Empty. set %d\n", __func__, pdata->tx_ping_vout); + } + + ret = of_property_read_u32(np, "battery,tx_gear_vout", + &pdata->tx_gear_vout); + if (ret) { + pdata->tx_gear_vout = WC_TX_VOUT_5000MV; + pr_info("%s : tx gear vout is Empty. set %d\n", __func__, pdata->tx_gear_vout); + } + + ret = of_property_read_u32(np, "battery,tx_buds_vout", + &pdata->tx_buds_vout); + if (ret) { + pdata->tx_buds_vout = pdata->tx_uno_vout; // battery,tx_buds_vout in dt is not mandatory + pr_info("%s : tx buds vout is Empty. set %d\n", __func__, pdata->tx_buds_vout); + } + + ret = of_property_read_u32(np, "battery,tx_uno_iout", + &pdata->tx_uno_iout); + if (ret) { + pdata->tx_uno_iout = 1500; + pr_err("%s: tx uno iout is Empty. set %d\n", __func__, pdata->tx_uno_iout); + } + + ret = of_property_read_u32(np, "battery,tx_uno_iout_gear", + &pdata->tx_uno_iout_gear); + if (ret) { + pdata->tx_uno_iout_gear = pdata->tx_uno_iout; + pr_err("%s: tx_uno_iout_gear is Empty. set %d\n", __func__, pdata->tx_uno_iout); + } + + ret = of_property_read_u32(np, "battery,tx_uno_iout_aov_gear", + &pdata->tx_uno_iout_aov_gear); + if (ret) { + pdata->tx_uno_iout_aov_gear = pdata->tx_uno_iout_gear; + pr_err("%s: tx_uno_iout_aov_gear is Empty. set %d\n", + __func__, pdata->tx_uno_iout_gear); + } + + ret = of_property_read_u32(np, "battery,tx_mfc_iout_gear", + &pdata->tx_mfc_iout_gear); + if (ret) { + pdata->tx_mfc_iout_gear = 1500; + pr_err("%s: tx mfc iout gear is Empty. set %d\n", __func__, pdata->tx_mfc_iout_gear); + } + + ret = of_property_read_u32(np, "battery,tx_mfc_iout_aov_gear", + &pdata->tx_mfc_iout_aov_gear); + if (ret) { + pdata->tx_mfc_iout_aov_gear = pdata->tx_mfc_iout_gear; + pr_err("%s: tx_mfc_iout_aov_gear is Empty. set %d\n", + __func__, pdata->tx_mfc_iout_gear); + } + + ret = of_property_read_u32(np, "battery,tx_mfc_iout_phone", + &pdata->tx_mfc_iout_phone); + if (ret) { + pdata->tx_mfc_iout_phone = 1100; + pr_err("%s: tx mfc iout phone is Empty. set %d\n", __func__, pdata->tx_mfc_iout_phone); + } + + ret = of_property_read_u32(np, "battery,tx_mfc_iout_phone_5v", + &pdata->tx_mfc_iout_phone_5v); + if (ret) { + pdata->tx_mfc_iout_phone_5v = 300; + pr_err("%s: tx mfc iout phone 5v is Empty. set %d\n", __func__, pdata->tx_mfc_iout_phone_5v); + } + + ret = of_property_read_u32(np, "battery,tx_mfc_iout_lcd_on", + &pdata->tx_mfc_iout_lcd_on); + if (ret) { + pdata->tx_mfc_iout_lcd_on = 900; + pr_err("%s: tx mfc iout lcd on is Empty. set %d\n", __func__, pdata->tx_mfc_iout_lcd_on); + } + + pdata->tx_5v_disable = of_property_read_bool(np, "battery,tx_5v_disable"); + pr_info("%s: 5V TA power sharing is %s.\n", __func__, + pdata->tx_5v_disable ? "Disabled" : "Enabled"); + + ret = of_property_read_u32(np, "battery,phm_vout_ctrl_dev", + &pdata->phm_vout_ctrl_dev); + if (ret < 0) { + pr_info("%s: fail to read phm_vout_ctrl_dev\n", __func__); + pdata->phm_vout_ctrl_dev = 0; + } + pr_info("%s: phm_vout_ctrl_dev = %d\n", __func__, pdata->phm_vout_ctrl_dev); + + ret = of_property_read_u32(np, "battery,tx_aov_start_vout", + &pdata->tx_aov_start_vout); + if (ret) { + pdata->tx_aov_start_vout = WC_TX_VOUT_6000MV; + pr_err("%s: tx aov start vout is Empty. set %d\n", __func__, pdata->tx_aov_start_vout); + } + + ret = of_property_read_u32(np, "battery,tx_aov_freq_low", + &pdata->tx_aov_freq_low); + if (ret) { + pdata->tx_aov_freq_low = 125; + pr_err("%s: tx aov freq low is Empty. set %d\n", __func__, pdata->tx_aov_freq_low); + } + + ret = of_property_read_u32(np, "battery,tx_aov_freq_high", + &pdata->tx_aov_freq_high); + if (ret) { + pdata->tx_aov_freq_high = 147; + pr_err("%s: tx aov freq high is Empty. set %d\n", __func__, pdata->tx_aov_freq_high); + } + + ret = of_property_read_u32(np, "battery,tx_aov_delay", + &pdata->tx_aov_delay); + if (ret) { + pdata->tx_aov_delay = 3000; + pr_err("%s: tx aov dealy is Empty. set %d\n", __func__, pdata->tx_aov_delay); + } + + ret = of_property_read_u32(np, "battery,tx_aov_delay_phm_escape", + &pdata->tx_aov_delay_phm_escape); + if (ret) { + pdata->tx_aov_delay_phm_escape = 4000; + pr_err("%s: tx aov dealy phm escape is Empty. set %d\n", __func__, pdata->tx_aov_delay_phm_escape); + } + + pdata->wpc_warm_fod = of_property_read_bool(np, "battery,wpc_warm_fod"); + pr_info("%s: WPC Warm FOD %s.\n", __func__, + pdata->wpc_warm_fod ? "Enabled" : "Disabled"); + + /* Default setting 100mA */ + if (pdata->wpc_warm_fod) { + ret = of_property_read_u32(np, "battery,wpc_warm_fod_icc", + &pdata->wpc_warm_fod_icc); + if (ret) + pdata->wpc_warm_fod_icc = 100; + } + + ret = of_property_read_u32(np, "battery,wc21_icl", &pdata->wc21_icl); + if (ret) + pr_err("%s: wc21_icl is Empty\n", __func__); + + pdata->lr_enable = of_property_read_bool(np, "battery,lr_enable"); + if (pdata->lr_enable) { + ret = of_property_read_u32(np, "battery,lr_param_bat_thm", + &pdata->lr_param_bat_thm); + if (ret) + pdata->lr_param_bat_thm = 420; + + ret = of_property_read_u32(np, "battery,lr_param_sub_bat_thm", + &pdata->lr_param_sub_bat_thm); + if (ret) + pdata->lr_param_sub_bat_thm = 580; + + ret = of_property_read_u32(np, "battery,lr_delta", + &pdata->lr_delta); + if (ret) + pdata->lr_delta = 16; + + ret = of_property_read_u32(np, "battery,lr_param_init_bat_thm", + &pdata->lr_param_init_bat_thm); + if (ret) + pdata->lr_param_init_bat_thm = 70; + + ret = of_property_read_u32(np, "battery,lr_param_init_sub_bat_thm", + &pdata->lr_param_init_sub_bat_thm); + if (ret) + pdata->lr_param_init_sub_bat_thm = 30; + + ret = of_property_read_u32(np, "battery,lr_round_off", + &pdata->lr_round_off); + if (ret) + pdata->lr_round_off = 500; + } + + pr_info("%s: vendor : %s, technology : %d, cable_check_type : %d\n" + "cable_source_type : %d, polling_type: %d\n" + "initial_count : %d, check_count : %d\n" + "battery_check_type : %d, check_adc_max : %d, check_adc_min : %d\n" + "ovp_uvlo_check_type : %d, thermal_source : %d\n" + "temp_check_type : %d, temp_check_count : %d, nv_charge_power : %d\n" + "full_condition_type : %d, recharge_condition_type : %d, full_check_type : %d\n", + __func__, + pdata->vendor, pdata->technology,pdata->cable_check_type, + pdata->cable_source_type, pdata->polling_type, + pdata->monitor_initial_count, pdata->check_count, + pdata->battery_check_type, pdata->check_adc_max, pdata->check_adc_min, + pdata->ovp_uvlo_check_type, pdata->bat_thm_info.source, + pdata->bat_thm_info.check_type, pdata->temp_check_count, pdata->nv_charge_power, + pdata->full_condition_type, pdata->recharge_condition_type, pdata->full_check_type + ); + + ret = of_property_read_u32(np, "battery,batt_temp_adj_gap_inc", + &pdata->batt_temp_adj_gap_inc); + if (ret) { + pr_err("%s: batt_temp_adj_gap_inc is Empty\n", __func__); + pdata->batt_temp_adj_gap_inc = 0; + } + + ret = of_property_read_u32(np, "battery,change_FV_after_full", + &pdata->change_FV_after_full); + if (ret) { + pr_err("%s: change_FV_after_full is Empty\n", __func__); + pdata->change_FV_after_full = 0; + } + + pdata->loosened_unknown_temp = of_property_read_bool(np, "battery,loosened_unknown_temp"); + + pdata->pogo_chgin = of_property_read_bool(np, "battery,pogo_chgin"); + +#if defined(CONFIG_STEP_CHARGING) + sec_step_charging_init(battery, dev); +#endif + + ret = of_property_read_u32(np, "battery,max_charging_charge_power", + &pdata->max_charging_charge_power); + if (ret) { + pr_err("%s: max_charging_charge_power is Empty\n", __func__); + pdata->max_charging_charge_power = 25000; + } + + ret = of_property_read_u32(np, "battery,apdo_max_volt", + &pdata->apdo_max_volt); + if (ret) { + pr_err("%s: apdo_max_volt is Empty\n", __func__); + pdata->apdo_max_volt = 11000; /* 11v */ + } + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + ret = of_property_read_string(np, + "battery,dual_battery_name", (char const **)&pdata->dual_battery_name); + if (ret) + pr_info("%s: Dual battery name is Empty\n", __func__); + + np = of_find_node_by_name(NULL, "sec-dual-battery"); + if (!np) { + pr_info("%s: np NULL\n", __func__); + } else { + /* zone1 current ratio, 0C ~ 0.4C */ + ret = of_property_read_u32(np, "battery,zone1_limiter_current", + &pdata->zone1_limiter_current); + if (ret) { + pr_err("%s: zone1_limiter_current is Empty\n", __func__); + pdata->zone1_limiter_current = 100; + } + ret = of_property_read_u32(np, "battery,main_zone1_current_rate", + &pdata->main_zone1_current_rate); + if (ret) { + pr_err("%s: main_zone1_current_rate is Empty\n", __func__); + pdata->main_zone1_current_rate = 50; + } + ret = of_property_read_u32(np, "battery,sub_zone1_current_rate", + &pdata->sub_zone1_current_rate); + if (ret) { + pr_err("%s: sub_zone1_current_rate is Empty\n", __func__); + pdata->sub_zone1_current_rate = 60; + } + /* zone2 current ratio, 0.4C ~ 1.1C */ + ret = of_property_read_u32(np, "battery,zone2_limiter_current", + &pdata->zone2_limiter_current); + if (ret) { + pr_err("%s: zone2_limiter_current is Empty\n", __func__); + pdata->zone2_limiter_current = 1200; + } + ret = of_property_read_u32(np, "battery,main_zone2_current_rate", + &pdata->main_zone2_current_rate); + if (ret) { + pr_err("%s: main_zone2_current_rate is Empty\n", __func__); + pdata->main_zone2_current_rate = 50; + } + ret = of_property_read_u32(np, "battery,sub_zone2_current_rate", + &pdata->sub_zone2_current_rate); + if (ret) { + pr_err("%s: sub_zone2_current_rate is Empty\n", __func__); + pdata->sub_zone2_current_rate = 60; + } + /* zone3 current ratio, 1.1C ~ MAX */ + ret = of_property_read_u32(np, "battery,zone3_limiter_current", + &pdata->zone3_limiter_current); + if (ret) { + pr_err("%s: zone3_limiter_current is Empty\n", __func__); + pdata->zone3_limiter_current = 3000; + } + ret = of_property_read_u32(np, "battery,main_zone3_current_rate", + &pdata->main_zone3_current_rate); + if (ret) { + pr_err("%s: main_zone3_current_rate is Empty\n", __func__); + pdata->main_zone3_current_rate = pdata->main_zone2_current_rate; + } + ret = of_property_read_u32(np, "battery,sub_zone3_current_rate", + &pdata->sub_zone3_current_rate); + if (ret) { + pr_err("%s: sub_zone3_current_rate is Empty\n", __func__); + pdata->sub_zone3_current_rate = pdata->sub_zone2_current_rate; + } + ret = of_property_read_u32(np, "battery,force_recharge_margin", + &pdata->force_recharge_margin); + if (ret) { + pr_err("%s: force_recharge_margin is Empty\n", __func__); + pdata->force_recharge_margin = 150; + } + ret = of_property_read_u32(np, "battery,max_main_limiter_current", + &pdata->max_main_limiter_current); + if (ret) { + pr_err("%s: max_main_limiter_current is Empty\n", __func__); + pdata->max_main_limiter_current = 1550; + } + ret = of_property_read_u32(np, "battery,min_main_limiter_current", + &pdata->min_main_limiter_current); + if (ret) { + pr_err("%s: min_main_limiter_current is Empty\n", __func__); + pdata->min_main_limiter_current = 450; + } + ret = of_property_read_u32(np, "battery,max_sub_limiter_current", + &pdata->max_sub_limiter_current); + if (ret) { + pr_err("%s: max_sub_limiter_current is Empty\n", __func__); + pdata->max_sub_limiter_current = 1300; + } + ret = of_property_read_u32(np, "battery,min_sub_limiter_current", + &pdata->min_sub_limiter_current); + if (ret) { + pr_err("%s: min_sub_limiter_current is Empty\n", __func__); + pdata->min_sub_limiter_current = 450; + } + pdata->main_fto = of_property_read_bool(np, "battery,main_fto"); + pdata->sub_fto = of_property_read_bool(np, "battery,sub_fto"); + + if (pdata->main_fto) { + ret = of_property_read_u32(np, "battery,main_fto_current_thresh", + &pdata->main_fto_current_thresh); + if (ret) { + pr_err("%s: main_fto_current_thresh is Empty\n", __func__); + pdata->main_fto_current_thresh = pdata->zone3_limiter_current; + } + } + if (pdata->sub_fto) { + ret = of_property_read_u32(np, "battery,sub_fto_current_thresh", + &pdata->sub_fto_current_thresh); + if (ret) { + pr_err("%s: sub_fto_current_thresh is Empty\n", __func__); + pdata->sub_fto_current_thresh = pdata->zone3_limiter_current; + } + } + + pr_info("%s : main ratio:%d(zn1) %d(zn2) %d(zn3), sub ratio:%d(zn1) %d(zn2) %d(zn3), recharge marging:%d, " + "max main curr:%d, min main curr:%d, max sub curr:%d, min sub curr:%d, main_fto:%d, sub_fto:%d, " + "main_fto_curr:%d, sub_fto_curr:%d\n", + __func__, pdata->main_zone1_current_rate, pdata->main_zone2_current_rate, pdata->main_zone3_current_rate, + pdata->sub_zone1_current_rate, pdata->sub_zone2_current_rate, pdata->sub_zone3_current_rate, + pdata->force_recharge_margin, pdata->max_main_limiter_current, pdata->min_main_limiter_current, + pdata->max_sub_limiter_current, pdata->min_sub_limiter_current, pdata->main_fto, pdata->sub_fto, + pdata->main_fto_current_thresh, pdata->sub_fto_current_thresh); + + ret = of_property_read_string(np, "battery,main_current_limiter", + (char const **)&battery->pdata->main_limiter_name); + if (ret) + pr_err("%s: main_current_limiter is Empty\n", __func__); + else { + np = of_find_node_by_name(NULL, battery->pdata->main_limiter_name); + if (!np) { + pr_info("%s: main_limiter_name is Empty\n", __func__); + } else { + /* MAIN_BATTERY_SW_EN */ + ret = pdata->main_bat_enb_gpio = of_get_named_gpio(np, "limiter,main_bat_enb_gpio", 0); + if (ret < 0) + pr_info("%s : can't get main_bat_enb_gpio\n", __func__); + + /* MAIN_BATTERY_SW_EN2 */ + ret = pdata->main_bat_enb2_gpio = of_get_named_gpio(np, "limiter,main_bat_enb2_gpio", 0); + if (ret < 0) + pr_info("%s : can't get main_bat_enb2_gpio\n", __func__); + } + } + np = of_find_node_by_name(NULL, "sec-dual-battery"); + + ret = of_property_read_string(np, "battery,sub_current_limiter", + (char const **)&battery->pdata->sub_limiter_name); + if (ret) + pr_err("%s: sub_current_limiter is Empty\n", __func__); + else { + np = of_find_node_by_name(NULL, battery->pdata->sub_limiter_name); + if (!np) { + pr_info("%s: sub_limiter_name is Empty\n", __func__); + } else { + /* SUB_BATTERY_SW_EN */ + ret = pdata->sub_bat_enb_gpio = of_get_named_gpio(np, "limiter,sub_bat_enb_gpio", 0); + if (ret < 0) + pr_info("%s : can't get sub_bat_enb_gpio\n", __func__); + } + } + } + np = of_find_node_by_name(NULL, "battery"); +#if IS_ENABLED(CONFIG_DUAL_FUELGAUGE) + ret = of_property_read_string(np, + "battery,dual_fuelgauge_name", (char const **)&pdata->dual_fuelgauge_name); + if (ret) + pr_info("%s: Dual fuelgauge name is Empty\n", __func__); + + np = of_find_node_by_name(NULL, "sec-dual-fuelgauge"); + if (!np) { + pr_info("%s: np NULL\n", __func__); + } else { + ret = of_property_read_u32(np, "battery,main_design_capacity", + &pdata->main_design_capacity); + if (ret) + pr_err("%s: main_design_capacity is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,sub_design_capacity", + &pdata->sub_design_capacity); + if (ret) + pr_err("%s: sub_design_capacity is Empty\n", __func__); + } + ret = of_property_read_string(np, "battery,main_fuelgauge_name", + (char const **)&battery->pdata->main_fuelgauge_name); + if (ret) + pr_err("%s: main_fuelgauge_name is Empty\n", __func__); + + ret = of_property_read_string(np, "battery,sub_fuelgauge_name", + (char const **)&battery->pdata->sub_fuelgauge_name); + if (ret) + pr_err("%s: sub_fuelgauge_name is Empty\n", __func__); + + np = of_find_node_by_name(NULL, "battery"); +#endif +#endif + + p = of_get_property(np, "battery,ignore_cisd_index", &len); + pdata->ignore_cisd_index = kzalloc(sizeof(*pdata->ignore_cisd_index) * 2, GFP_KERNEL); + if (p) { + len = len / sizeof(u32); + ret = of_property_read_u32_array(np, "battery,ignore_cisd_index", + pdata->ignore_cisd_index, len); + if (ret) + pr_err("%s failed to read ignore_cisd_index: %d\n", + __func__, ret); + } else { + pr_info("%s : battery,ignore_cisd_index is Empty\n", __func__); + } + + p = of_get_property(np, "battery,ignore_cisd_index_d", &len); + pdata->ignore_cisd_index_d = kzalloc(sizeof(*pdata->ignore_cisd_index_d) * 2, GFP_KERNEL); + if (p) { + len = len / sizeof(u32); + ret = of_property_read_u32_array(np, "battery,ignore_cisd_index_d", + pdata->ignore_cisd_index_d, len); + if (ret) + pr_err("%s failed to read ignore_cisd_index_d: %d\n", + __func__, ret); + } else { + pr_info("%s : battery,ignore_cisd_index_d is Empty\n", __func__); + } + + pdata->support_usb_conn_check = of_property_read_bool(np, + "battery,support_usb_conn_check"); + pr_info("%s: support_usb_conn_check(%d)\n", __func__, pdata->support_usb_conn_check); + + ret = of_property_read_u32(np, "battery,usb_conn_slope_avg", + &pdata->usb_conn_slope_avg); + if (ret) { + pdata->usb_conn_slope_avg = 9; /* 0.9 degrees */ + pr_err("%s: usb_conn_slope_avg is default: %d\n", __func__, pdata->usb_conn_slope_avg); + } + + pdata->support_spsn_ctrl = of_property_read_bool(np, + "battery,support_spsn_ctrl"); + pr_info("%s: support_spsn_ctrl(%d)\n", __func__, pdata->support_spsn_ctrl); + +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + battery->disable_mfc = of_property_read_bool(np, + "battery,disable_mfc"); + pr_info("%s: disable_mfc(%d)\n", __func__, battery->disable_mfc); +#endif + return 0; +} +EXPORT_SYMBOL(sec_bat_parse_dt); + +void sec_bat_parse_mode_dt(struct sec_battery_info *battery) +{ + struct device_node *np; + sec_battery_platform_data_t *pdata = battery->pdata; + int ret = 0; + u32 temp = 0; + + np = of_find_node_by_name(NULL, "battery"); + if (!np) { + pr_err("%s np NULL\n", __func__); + return; + } + + if (battery->store_mode) { + ret = of_property_read_u32(np, "battery,store_mode_max_input_power", + &pdata->store_mode_max_input_power); + if (ret) { + pr_info("%s : store_mode_max_input_power is Empty\n", __func__); + pdata->store_mode_max_input_power = 4000; + } + + if (pdata->wpc_thm_info.check_type) { + ret = of_property_read_u32(np, "battery,wpc_store_high_temp", + &temp); + if (!ret) { + pdata->wpc_high_temp = temp; + pdata->wpc_high_temp_12w = temp; + pdata->wpc_high_temp_15w = temp; + } + + ret = of_property_read_u32(np, "battery,wpc_store_high_temp_recovery", + &temp); + if (!ret) { + pdata->wpc_high_temp_recovery = temp; + pdata->wpc_high_temp_recovery_12w = temp; + pdata->wpc_high_temp_recovery_15w = temp; + } + + ret = of_property_read_u32(np, "battery,wpc_store_charging_limit_current", + &temp); + if (!ret) + pdata->wpc_input_limit_current = temp; + + ret = of_property_read_u32(np, "battery,wpc_store_lcd_on_high_temp", + &temp); + if (!ret) { + pdata->wpc_lcd_on_high_temp = (int)temp; + pdata->wpc_lcd_on_high_temp_12w = (int)temp; + pdata->wpc_lcd_on_high_temp_15w = (int)temp; + } + + ret = of_property_read_u32(np, "battery,wpc_store_lcd_on_high_temp_rec", + &temp); + if (!ret) { + pdata->wpc_lcd_on_high_temp_rec = (int)temp; + pdata->wpc_lcd_on_high_temp_rec_12w = (int)temp; + pdata->wpc_lcd_on_high_temp_rec_15w = (int)temp; + } + + ret = of_property_read_u32(np, "battery,wpc_store_lcd_on_charging_limit_current", + &temp); + if (!ret) + pdata->wpc_lcd_on_input_limit_current = (int)temp; + + pr_info("%s: update store_mode - wpc high_temp(t:%d/%d/%d, r:%d/%d/%d), " + "lcd_on_high_temp(t:%d/%d%d, r:%d/%d/%d), curr(%d, %d)\n", __func__, + pdata->wpc_high_temp, pdata->wpc_high_temp_12w, pdata->wpc_high_temp_15w, + pdata->wpc_high_temp_recovery, pdata->wpc_high_temp_recovery_12w, pdata->wpc_high_temp_recovery_15w, + pdata->wpc_lcd_on_high_temp, pdata->wpc_lcd_on_high_temp_12w, pdata->wpc_lcd_on_high_temp_12w, + pdata->wpc_lcd_on_high_temp_rec, pdata->wpc_lcd_on_high_temp_rec_12w, pdata->wpc_lcd_on_high_temp_rec_12w, + pdata->wpc_input_limit_current, pdata->wpc_lcd_on_input_limit_current); + } + + ret = of_property_read_u32(np, "battery,siop_store_hv_wpc_icl", + &temp); + if (!ret) + pdata->siop_hv_wpc_icl = temp; + else + pdata->siop_hv_wpc_icl = SIOP_STORE_HV_WIRELESS_CHARGING_LIMIT_CURRENT; + pr_info("%s: update siop_hv_wpc_icl(%d)\n", + __func__, pdata->siop_hv_wpc_icl); + pdata->store_mode_buckoff = of_property_read_bool(np, "battery,store_mode_buckoff"); + pr_info("%s : battery,store_mode_buckoff: %d\n", __func__, pdata->store_mode_buckoff); + } +} +EXPORT_SYMBOL_KUNIT(sec_bat_parse_mode_dt); + +void sec_bat_parse_mode_dt_work(struct work_struct *work) +{ + struct sec_battery_info *battery = container_of(work, + struct sec_battery_info, parse_mode_dt_work.work); + + sec_bat_parse_mode_dt(battery); + + if (is_hv_wire_type(battery->cable_type) || + is_hv_wireless_type(battery->cable_type)) + sec_bat_set_charging_current(battery); + + __pm_relax(battery->parse_mode_dt_ws); +} +EXPORT_SYMBOL(sec_bat_parse_mode_dt_work); +#endif diff --git a/drivers/battery/common/sec_battery_dt.h b/drivers/battery/common/sec_battery_dt.h new file mode 100644 index 000000000000..3f8ef8b9068b --- /dev/null +++ b/drivers/battery/common/sec_battery_dt.h @@ -0,0 +1,27 @@ +/* + * sec_battery_dt.h + * Samsung Mobile Battery Header + * + * + * Copyright (C) 2018 Samsung Electronics, 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 __SEC_BATTERY_DT_H +#define __SEC_BATTERY_DT_H __FILE__ + +int sec_bat_parse_dt(struct device *dev, struct sec_battery_info *battery); +void sec_bat_parse_mode_dt(struct sec_battery_info *battery); +void sec_bat_parse_mode_dt_work(struct work_struct *work); + +#endif /* __SEC_BATTERY_DT_H */ diff --git a/drivers/battery/common/sec_battery_misc.c b/drivers/battery/common/sec_battery_misc.c new file mode 100644 index 000000000000..d6784fdb010b --- /dev/null +++ b/drivers/battery/common/sec_battery_misc.c @@ -0,0 +1,353 @@ + /* + * sec_battery_misc.c + * Samsung Mobile Battery Misc Driver + * Author: Yeongmi Ha + * Copyright (C) 2018 Samsung Electronics + * + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sec_battery.h" +#include "sec_battery_misc.h" + +static struct sec_bat_misc_dev *c_dev; + +#define SEC_BATT_MISC_DBG 1 +#define MAX_BUF 4095 +#define NODE_OF_MISC "batt_misc" +#define BATT_IOCTL_SWAM _IOWR('B', 0, struct swam_data) + +#define misc_log(str, ...) pr_info("%s:%s: "str, WC_AUTH_MSG, __func__, ##__VA_ARGS__) + +#if SEC_BATT_MISC_DBG +static void print_message(u8* buf, int size) +{ + int start_idx = 0; + + do { + char temp_buf[1024] = {0, }; + int size_temp = 0, str_len = 1024; + int old_idx = start_idx; + + size_temp = ((start_idx + 0x7F) > size) ? size : (start_idx + 0x7F); + for (; start_idx < size_temp; start_idx++) { + snprintf(temp_buf + strlen(temp_buf), str_len, "0x%02x ", buf[start_idx]); + str_len = 1024 - strlen(temp_buf); + } + misc_log("(%04d ~ %04d) %s\n", old_idx, start_idx - 1, temp_buf); + } while (start_idx < size); +} +#endif + +static inline int _lock(atomic_t *excl) +{ + if (atomic_inc_return(excl) == 1) + return 0; + + atomic_dec(excl); + return -1; +} + +static inline void _unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +static int sec_bat_misc_open(struct inode *inode, struct file *file) +{ + int ret = 0; + + misc_log("open success\n"); + if (!c_dev) { + misc_log("error : c_dev is NULL\n"); + ret = -ENODEV; + goto err; + } + + if (_lock(&c_dev->open_excl)) { + misc_log("error : device busy\n"); + ret = -EBUSY; + goto err; + } + misc_log("open success\n"); + return 0; +err: + return ret; +} + +static int sec_bat_misc_close(struct inode *inode, struct file *file) +{ + if (c_dev) + _unlock(&c_dev->open_excl); + misc_log("close success\n"); + return 0; +} + +static int send_swam_message(void *data, int size) +{ + int ret = c_dev->swam_write(data, size); + + misc_log("size : %d, ret : %d\n", size, ret); + + return ret; +} + +static int receive_swam_message(void *data, int size) +{ + int ret = c_dev->swam_read(data); + + misc_log("size : %d, ret : %d\n", size, ret); + + return ret; +} + +static long +sec_bat_misc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int ret = 0; + void *buf = NULL; + + if (_lock(&c_dev->ioctl_excl)) { + misc_log("error : ioctl busy - cmd : %d\n", cmd); + return -EBUSY; + } + + switch (cmd) { + case BATT_IOCTL_SWAM: + misc_log("BATT_IOCTL_SWAM cmd\n"); + if (copy_from_user(&c_dev->u_data, (void __user *) arg, + sizeof(struct swam_data))) { + ret = -EIO; + misc_log("copy_from_user error\n"); + goto err; + } + buf = kzalloc(MAX_BUF, GFP_KERNEL); + if (!buf) + return -EINVAL; + if (c_dev->u_data.size > MAX_BUF) { + ret = -ENOMEM; + misc_log("user data size is %d error\n", c_dev->u_data.size); + goto err; + } + + if (c_dev->u_data.dir == DIR_OUT) { + if (copy_from_user(buf, c_dev->u_data.pData, c_dev->u_data.size)) { + ret = -EIO; + misc_log("copy_from_user error\n"); + goto err; + } +#if SEC_BATT_MISC_DBG + misc_log("send_swam_message - size : %d\n", c_dev->u_data.size); + print_message(buf, c_dev->u_data.size); +#endif + ret = send_swam_message(buf, c_dev->u_data.size); + if (ret < 0) { + misc_log("send_swam_message error\n"); + ret = -EINVAL; + goto err; + } + } else { +#if SEC_BATT_MISC_DBG + misc_log("received_swam_message - size : %d\n", c_dev->u_data.size); +#endif + ret = receive_swam_message(buf, c_dev->u_data.size); + if (ret < 0) { + misc_log("receive_swam_message error\n"); + ret = -EINVAL; + goto err; + } +#if SEC_BATT_MISC_DBG + misc_log("received_swam_message - ret : %d\n", ret); + print_message(buf, ret); +#endif + if (copy_to_user((void __user *)c_dev->u_data.pData, + buf, ret)) { + ret = -EIO; + misc_log("copy_to_user error\n"); + goto err; + } + } + break; + + default: + misc_log("unknown ioctl cmd : %d\n", cmd); + ret = -ENOIOCTLCMD; + goto err; + } +err: + kfree(buf); + _unlock(&c_dev->ioctl_excl); + return ret; +} + +#ifdef CONFIG_COMPAT +static long +sec_bat_misc_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int ret = 0; + + misc_log("cmd : %d\n", cmd); + ret = sec_bat_misc_ioctl(file, cmd, arg); + + return ret; +} +#endif + +int sec_bat_swam_out_request_message(void *data, int size) +{ + union power_supply_propval value = {0, }; + u8 *p_data; + + misc_log("auth service writes data\n"); + + if (data == NULL) { + misc_log("given data is not valid !\n"); + return -EINVAL; + } + misc_log("size = %d\n", size); + + /* clear received event */ + value.intval = WIRELESS_AUTH_SENT; + psy_do_property("wireless", set, + POWER_SUPPLY_EXT_PROP_WIRELESS_AUTH_ADT_STATUS, value); + + p_data = (u8 *)data; + + if (size > 1 ) { + /* set data size first */ + value.intval = size; + psy_do_property("mfc-charger", set, + POWER_SUPPLY_EXT_PROP_WIRELESS_AUTH_ADT_SIZE, value); + + value.strval = (u8 *)data; + /* set data */ + psy_do_property("mfc-charger", set, + POWER_SUPPLY_EXT_PROP_WIRELESS_AUTH_ADT_DATA, value); + } else if (size == 1 ) { + if (p_data[0] == 0x1) { + misc_log("auth has been passed\n"); + value.intval = WIRELESS_AUTH_PASS; + psy_do_property("mfc-charger", set, + POWER_SUPPLY_EXT_PROP_WIRELESS_AUTH_ADT_STATUS, value); + } else if (p_data[0] == 0x2) { + misc_log("auth has been failed\n"); + value.intval = WIRELESS_AUTH_FAIL; + psy_do_property("mfc-charger", set, + POWER_SUPPLY_EXT_PROP_WIRELESS_AUTH_ADT_STATUS, value); + } else + misc_log("invalid arg %d \n", p_data[0]); + } + return size; +} + +void sec_bat_swam_copy_data(u8 *src, u8 *dest, int size) +{ + int i = 0; + + for (i = 0; i < size; i++) + dest[i] = src[i]; + +#if SEC_BATT_MISC_DBG + print_message(dest, size); +#endif +} + +int sec_bat_swam_in_request_message(void *data) +{ + union power_supply_propval value = {0, }; + int size = 0; + + misc_log("auth service reads data\n"); + + if (data == NULL) { + misc_log("given data is not valid !\n"); + return -EINVAL; + } + misc_log("\n"); + + /* get data size first */ + psy_do_property("mfc-charger", get, + POWER_SUPPLY_EXT_PROP_WIRELESS_AUTH_ADT_SIZE, value); + size = value.intval; + /* get data */ + psy_do_property("mfc-charger", get, + POWER_SUPPLY_EXT_PROP_WIRELESS_AUTH_ADT_DATA, value); + + if (value.intval == 0) { + misc_log("data hasn't been received yet!\n"); + return -EINVAL; + } + sec_bat_swam_copy_data((u8 *)value.strval, data, size); + + return size; +} + +static const struct file_operations sec_bat_misc_fops = { + .owner = THIS_MODULE, + .open = sec_bat_misc_open, + .release = sec_bat_misc_close, + .llseek = no_llseek, + .unlocked_ioctl = sec_bat_misc_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = sec_bat_misc_compat_ioctl, +#endif +}; + +static struct miscdevice sec_bat_misc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = NODE_OF_MISC, + .fops = &sec_bat_misc_fops, +}; + +int sec_bat_misc_init(struct sec_battery_info *battery) +{ + int ret = 0; + + ret = misc_register(&sec_bat_misc_device); + if (ret) { + misc_log("return error : %d\n", ret); + goto err; + } + + c_dev = kzalloc(sizeof(struct sec_bat_misc_dev), GFP_KERNEL); + if (!c_dev) { + ret = -ENOMEM; + misc_log("kzalloc failed : %d\n", ret); + goto err1; + } + atomic_set(&c_dev->open_excl, 0); + atomic_set(&c_dev->ioctl_excl, 0); + + battery->misc_dev = c_dev; + c_dev->swam_read = sec_bat_swam_in_request_message; + c_dev->swam_write = sec_bat_swam_out_request_message; + + misc_log("register success\n"); + return 0; +err1: + misc_deregister(&sec_bat_misc_device); +err: + return ret; +} +EXPORT_SYMBOL(sec_bat_misc_init); + +void sec_bat_misc_exit(void) +{ + misc_log("called\n"); + if (!c_dev) + return; + kfree(c_dev); + misc_deregister(&sec_bat_misc_device); +} +EXPORT_SYMBOL(sec_bat_misc_exit); diff --git a/drivers/battery/common/sec_battery_misc.h b/drivers/battery/common/sec_battery_misc.h new file mode 100644 index 000000000000..534283b87e35 --- /dev/null +++ b/drivers/battery/common/sec_battery_misc.h @@ -0,0 +1,48 @@ +/* +* sec_battery_misc.h +* Samsung Mobile Battery Misc Driver +* Author: Yeongmi Ha +* Copyright (C) 2018 Samsung Electronics +* +* +* 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. +*/ + +#ifndef __LINUX_SEC_BATTERY_MISC_H__ +#define __LINUX_SEC_BATTERY_MISC_H__ + +// Samsung Wireless Authentication Message +enum swam_data_type { + TYPE_SHORT = 0, + TYPE_LONG, +}; + +enum swam_direction_type { + DIR_OUT = 0, + DIR_IN, +}; + +struct swam_data { + unsigned short pid; /* Product ID */ + char type; /* swam_data_type */ + char dir; /* swam_direction_type */ + unsigned int size; /* data size */ + void __user *pData; /* data pointer */ +}; + +struct sec_bat_misc_dev { + struct swam_data u_data; + atomic_t open_excl; + atomic_t ioctl_excl; + int (*swam_write)(void *data, int size); + int (*swam_read)(void *data); + int (*swam_ready)(void); + void (*swam_close)(void); +}; + +#define sec_bat_misc_dev_t \ + struct sec_bat_misc_dev + +#endif diff --git a/drivers/battery/common/sec_battery_sysfs.c b/drivers/battery/common/sec_battery_sysfs.c new file mode 100644 index 000000000000..bb2780e93060 --- /dev/null +++ b/drivers/battery/common/sec_battery_sysfs.c @@ -0,0 +1,4537 @@ +/* + * sec_battery_sysfs.c + * Samsung Mobile Battery Driver + * + * Copyright (C) 2018 Samsung Electronics + * + * + * 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. + */ +#include "sec_battery.h" +#include "sec_battery_sysfs.h" +#if IS_ENABLED(CONFIG_SEC_ABC) +#include +#endif + +static struct device_attribute sec_battery_attrs[] = { + SEC_BATTERY_ATTR(batt_reset_soc), + SEC_BATTERY_ATTR(batt_read_raw_soc), + SEC_BATTERY_ATTR(batt_read_adj_soc), + SEC_BATTERY_ATTR(batt_type), + SEC_BATTERY_ATTR(batt_vfocv), + SEC_BATTERY_ATTR(batt_vol_adc), + SEC_BATTERY_ATTR(batt_vol_adc_cal), + SEC_BATTERY_ATTR(batt_vol_aver), + SEC_BATTERY_ATTR(batt_vol_adc_aver), + SEC_BATTERY_ATTR(batt_voltage_now), + SEC_BATTERY_ATTR(batt_current_ua_now), + SEC_BATTERY_ATTR(batt_current_ua_avg), + SEC_BATTERY_ATTR(batt_filter_cfg), + SEC_BATTERY_ATTR(batt_temp), + SEC_BATTERY_ATTR(batt_temp_raw), + SEC_BATTERY_ATTR(batt_temp_adc), + SEC_BATTERY_ATTR(batt_temp_aver), + SEC_BATTERY_ATTR(batt_temp_adc_aver), + SEC_BATTERY_ATTR(usb_temp), + SEC_BATTERY_ATTR(usb_temp_adc), + SEC_BATTERY_ATTR(chg_temp), + SEC_BATTERY_ATTR(chg_temp_adc), + SEC_BATTERY_ATTR(sub_bat_temp), + SEC_BATTERY_ATTR(sub_bat_temp_adc), + SEC_BATTERY_ATTR(sub_chg_temp), + SEC_BATTERY_ATTR(sub_chg_temp_adc), +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + SEC_BATTERY_ATTR(dchg_adc_mode_ctrl), + SEC_BATTERY_ATTR(dchg_temp), + SEC_BATTERY_ATTR(dchg_temp_adc), + SEC_BATTERY_ATTR(dchg_read_batp_batn), +#endif + SEC_BATTERY_ATTR(blkt_temp), + SEC_BATTERY_ATTR(blkt_temp_adc), + SEC_BATTERY_ATTR(batt_vf_adc), + SEC_BATTERY_ATTR(batt_slate_mode), + + SEC_BATTERY_ATTR(batt_lp_charging), + SEC_BATTERY_ATTR(siop_activated), + SEC_BATTERY_ATTR(siop_level), + SEC_BATTERY_ATTR(siop_event), + SEC_BATTERY_ATTR(batt_charging_source), + SEC_BATTERY_ATTR(fg_reg_dump), + SEC_BATTERY_ATTR(fg_reset_cap), + SEC_BATTERY_ATTR(fg_capacity), + SEC_BATTERY_ATTR(fg_asoc), + SEC_BATTERY_ATTR(auth), + SEC_BATTERY_ATTR(chg_current_adc), + SEC_BATTERY_ATTR(wc_adc), + SEC_BATTERY_ATTR(wc_status), + SEC_BATTERY_ATTR(wc_enable), + SEC_BATTERY_ATTR(wc_control), + SEC_BATTERY_ATTR(wc_control_cnt), + SEC_BATTERY_ATTR(led_cover), + SEC_BATTERY_ATTR(hv_charger_status), + SEC_BATTERY_ATTR(hv_wc_charger_status), + SEC_BATTERY_ATTR(hv_charger_set), + SEC_BATTERY_ATTR(factory_mode), + SEC_BATTERY_ATTR(store_mode), + SEC_BATTERY_ATTR(update), + SEC_BATTERY_ATTR(test_mode), + + SEC_BATTERY_ATTR(call), + SEC_BATTERY_ATTR(2g_call), + SEC_BATTERY_ATTR(talk_gsm), + SEC_BATTERY_ATTR(3g_call), + SEC_BATTERY_ATTR(talk_wcdma), + SEC_BATTERY_ATTR(music), + SEC_BATTERY_ATTR(video), + SEC_BATTERY_ATTR(browser), + SEC_BATTERY_ATTR(hotspot), + SEC_BATTERY_ATTR(camera), + SEC_BATTERY_ATTR(camcorder), + SEC_BATTERY_ATTR(data_call), + SEC_BATTERY_ATTR(wifi), + SEC_BATTERY_ATTR(wibro), + SEC_BATTERY_ATTR(lte), + SEC_BATTERY_ATTR(lcd), +#if defined(CONFIG_ISDB_CHARGING_CONTROL) + SEC_BATTERY_ATTR(batt_event_isdb), +#endif + SEC_BATTERY_ATTR(gps), + SEC_BATTERY_ATTR(event), + SEC_BATTERY_ATTR(batt_temp_table), + SEC_BATTERY_ATTR(batt_high_current_usb), +#if defined(CONFIG_ENG_BATTERY_CONCEPT) + SEC_BATTERY_ATTR(test_charge_current), +#if defined(CONFIG_STEP_CHARGING) + SEC_BATTERY_ATTR(test_step_condition), +#endif +#endif + SEC_BATTERY_ATTR(set_stability_test), + SEC_BATTERY_ATTR(batt_capacity_max), + SEC_BATTERY_ATTR(batt_repcap_1st), + SEC_BATTERY_ATTR(batt_inbat_voltage), + SEC_BATTERY_ATTR(batt_inbat_voltage_ocv), + SEC_BATTERY_ATTR(batt_inbat_voltage_adc), + SEC_BATTERY_ATTR(vbyp_voltage), + SEC_BATTERY_ATTR(check_sub_chg), + SEC_BATTERY_ATTR(batt_inbat_wireless_cs100), + SEC_BATTERY_ATTR(hmt_ta_connected), + SEC_BATTERY_ATTR(hmt_ta_charge), +#if defined(CONFIG_SEC_FACTORY) + SEC_BATTERY_ATTR(afc_test_fg_mode), +#endif + SEC_BATTERY_ATTR(fg_cycle), + SEC_BATTERY_ATTR(fg_full_voltage), + SEC_BATTERY_ATTR(fg_fullcapnom), + SEC_BATTERY_ATTR(battery_cycle), +#if defined(CONFIG_BATTERY_AGE_FORECAST_DETACHABLE) + SEC_BATTERY_ATTR(batt_after_manufactured), +#endif + SEC_BATTERY_ATTR(battery_cycle_test), + SEC_BATTERY_ATTR(batt_wpc_temp), + SEC_BATTERY_ATTR(batt_wpc_temp_adc), + SEC_BATTERY_ATTR(mst_switch_test), /* MFC MST switch test */ +#if defined(CONFIG_WIRELESS_FIRMWARE_UPDATE) + SEC_BATTERY_ATTR(batt_wireless_firmware_update), + SEC_BATTERY_ATTR(otp_firmware_result), + SEC_BATTERY_ATTR(wc_ic_grade), + SEC_BATTERY_ATTR(wc_ic_chip_id), + SEC_BATTERY_ATTR(otp_firmware_ver_bin), + SEC_BATTERY_ATTR(otp_firmware_ver), +#endif + SEC_BATTERY_ATTR(wc_phm_ctrl), + SEC_BATTERY_ATTR(wc_vout), + SEC_BATTERY_ATTR(wc_vrect), + SEC_BATTERY_ATTR(wc_tx_en), + SEC_BATTERY_ATTR(wc_tx_vout), + SEC_BATTERY_ATTR(batt_hv_wireless_status), + SEC_BATTERY_ATTR(batt_hv_wireless_pad_ctrl), + SEC_BATTERY_ATTR(wc_tx_id), + SEC_BATTERY_ATTR(wc_op_freq), + SEC_BATTERY_ATTR(wc_cmd_info), + SEC_BATTERY_ATTR(wc_rx_connected), + SEC_BATTERY_ATTR(wc_rx_connected_dev), + SEC_BATTERY_ATTR(wc_tx_mfc_vin_from_uno), + SEC_BATTERY_ATTR(wc_tx_mfc_iin_from_uno), +#if defined(CONFIG_WIRELESS_TX_MODE) + SEC_BATTERY_ATTR(wc_tx_avg_curr), + SEC_BATTERY_ATTR(wc_tx_total_pwr), +#endif + SEC_BATTERY_ATTR(wc_tx_stop_capacity), + SEC_BATTERY_ATTR(wc_tx_timer_en), +#if defined(CONFIG_ENG_BATTERY_CONCEPT) + SEC_BATTERY_ATTR(batt_tune_float_voltage), + SEC_BATTERY_ATTR(batt_tune_input_charge_current), + SEC_BATTERY_ATTR(batt_tune_fast_charge_current), + SEC_BATTERY_ATTR(batt_tune_wireless_vout_current), + SEC_BATTERY_ATTR(batt_tune_ui_term_cur_1st), + SEC_BATTERY_ATTR(batt_tune_ui_term_cur_2nd), + SEC_BATTERY_ATTR(batt_tune_temp_high_normal), + SEC_BATTERY_ATTR(batt_tune_temp_high_rec_normal), + SEC_BATTERY_ATTR(batt_tune_temp_low_normal), + SEC_BATTERY_ATTR(batt_tune_temp_low_rec_normal), + SEC_BATTERY_ATTR(batt_tune_chg_temp_high), + SEC_BATTERY_ATTR(batt_tune_chg_temp_rec), + SEC_BATTERY_ATTR(batt_tune_chg_limit_cur), + SEC_BATTERY_ATTR(batt_tune_lrp_temp_high_lcdon), + SEC_BATTERY_ATTR(batt_tune_lrp_temp_high_lcdoff), + SEC_BATTERY_ATTR(batt_tune_coil_temp_high), + SEC_BATTERY_ATTR(batt_tune_coil_temp_rec), + SEC_BATTERY_ATTR(batt_tune_coil_limit_cur), + SEC_BATTERY_ATTR(batt_tune_wpc_temp_high), + SEC_BATTERY_ATTR(batt_tune_wpc_temp_high_rec), + SEC_BATTERY_ATTR(batt_tune_dchg_temp_high), + SEC_BATTERY_ATTR(batt_tune_dchg_temp_high_rec), + SEC_BATTERY_ATTR(batt_tune_dchg_batt_temp_high), + SEC_BATTERY_ATTR(batt_tune_dchg_batt_temp_high_rec), + SEC_BATTERY_ATTR(batt_tune_dchg_limit_input_cur), + SEC_BATTERY_ATTR(batt_tune_dchg_limit_chg_cur), +#if defined(CONFIG_WIRELESS_TX_MODE) + SEC_BATTERY_ATTR(batt_tune_tx_mfc_iout_gear), + SEC_BATTERY_ATTR(batt_tune_tx_mfc_iout_phone), +#endif +#endif +#if defined(CONFIG_UPDATE_BATTERY_DATA) + SEC_BATTERY_ATTR(batt_update_data), +#endif + SEC_BATTERY_ATTR(batt_misc_event), + SEC_BATTERY_ATTR(batt_tx_event), + SEC_BATTERY_ATTR(batt_ext_dev_chg), + SEC_BATTERY_ATTR(batt_wdt_control), + SEC_BATTERY_ATTR(mode), + SEC_BATTERY_ATTR(check_ps_ready), + SEC_BATTERY_ATTR(batt_chip_id), + SEC_BATTERY_ATTR(error_cause), + SEC_BATTERY_ATTR(cisd_fullcaprep_max), + SEC_BATTERY_ATTR(cisd_data), + SEC_BATTERY_ATTR(cisd_data_json), + SEC_BATTERY_ATTR(cisd_data_d_json), + SEC_BATTERY_ATTR(cisd_wire_count), + SEC_BATTERY_ATTR(cisd_wc_data), + SEC_BATTERY_ATTR(cisd_wc_data_json), + SEC_BATTERY_ATTR(cisd_power_data), + SEC_BATTERY_ATTR(cisd_power_data_json), + SEC_BATTERY_ATTR(cisd_pd_data), + SEC_BATTERY_ATTR(cisd_pd_data_json), + SEC_BATTERY_ATTR(cisd_cable_data), + SEC_BATTERY_ATTR(cisd_cable_data_json), + SEC_BATTERY_ATTR(cisd_tx_data), + SEC_BATTERY_ATTR(cisd_tx_data_json), + SEC_BATTERY_ATTR(cisd_event_data), + SEC_BATTERY_ATTR(cisd_event_data_json), + SEC_BATTERY_ATTR(prev_battery_data), + SEC_BATTERY_ATTR(prev_battery_info), + SEC_BATTERY_ATTR(safety_timer_set), + SEC_BATTERY_ATTR(batt_swelling_control), + SEC_BATTERY_ATTR(batt_battery_id), +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + SEC_BATTERY_ATTR(batt_sub_battery_id), +#endif + SEC_BATTERY_ATTR(batt_temp_control_test), + SEC_BATTERY_ATTR(safety_timer_info), + SEC_BATTERY_ATTR(batt_shipmode_test), + SEC_BATTERY_ATTR(batt_misc_test), + SEC_BATTERY_ATTR(batt_temp_test), + SEC_BATTERY_ATTR(batt_current_event), + SEC_BATTERY_ATTR(batt_jig_gpio), + SEC_BATTERY_ATTR(cc_info), +#if defined(CONFIG_WIRELESS_AUTH) + SEC_BATTERY_ATTR(wc_auth_adt_sent), +#endif + SEC_BATTERY_ATTR(wc_duo_rx_power), +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + SEC_BATTERY_ATTR(batt_main_voltage), + SEC_BATTERY_ATTR(batt_sub_voltage), + SEC_BATTERY_ATTR(batt_main_vcell), + SEC_BATTERY_ATTR(batt_sub_vcell), + SEC_BATTERY_ATTR(batt_main_current_ma), + SEC_BATTERY_ATTR(batt_sub_current_ma), + SEC_BATTERY_ATTR(batt_main_con_det), + SEC_BATTERY_ATTR(batt_sub_con_det), +#if IS_ENABLED(CONFIG_LIMITER_S2ASL01) + SEC_BATTERY_ATTR(batt_main_vchg), + SEC_BATTERY_ATTR(batt_sub_vchg), + SEC_BATTERY_ATTR(batt_main_enb), + SEC_BATTERY_ATTR(batt_main_enb2), + SEC_BATTERY_ATTR(batt_sub_enb), + SEC_BATTERY_ATTR(batt_sub_pwr_mode2), +#else + SEC_BATTERY_ATTR(batt_main_shipmode), + SEC_BATTERY_ATTR(batt_sub_shipmode), +#endif +#if IS_ENABLED(CONFIG_DUAL_FUELGAUGE) + SEC_BATTERY_ATTR(batt_main_soc), + SEC_BATTERY_ATTR(batt_sub_soc), + SEC_BATTERY_ATTR(batt_main_repcap), + SEC_BATTERY_ATTR(batt_sub_repcap), + SEC_BATTERY_ATTR(batt_main_fullcaprep), + SEC_BATTERY_ATTR(batt_sub_fullcaprep), +#endif +#endif + SEC_BATTERY_ATTR(ext_event), + SEC_BATTERY_ATTR(direct_charging_status), +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + SEC_BATTERY_ATTR(direct_charging_step), + SEC_BATTERY_ATTR(direct_charging_iin), + SEC_BATTERY_ATTR(direct_charging_chg_status), + SEC_BATTERY_ATTR(switch_charging_source), +#endif + SEC_BATTERY_ATTR(charging_type), + SEC_BATTERY_ATTR(batt_factory_mode), + SEC_BATTERY_ATTR(boot_completed), + SEC_BATTERY_ATTR(pd_disable), + SEC_BATTERY_ATTR(factory_mode_relieve), + SEC_BATTERY_ATTR(factory_mode_bypass), + SEC_BATTERY_ATTR(normal_mode_bypass), + SEC_BATTERY_ATTR(factory_voltage_regulation), + SEC_BATTERY_ATTR(factory_mode_disable), + SEC_BATTERY_ATTR(usb_conf_test), + SEC_BATTERY_ATTR(charge_otg_control), + SEC_BATTERY_ATTR(charge_uno_control), + SEC_BATTERY_ATTR(charge_counter_shadow), + SEC_BATTERY_ATTR(voter_status), +#if defined(CONFIG_WIRELESS_IC_PARAM) + SEC_BATTERY_ATTR(wc_param_info), +#endif + SEC_BATTERY_ATTR(chg_info), + SEC_BATTERY_ATTR(lrp), + SEC_BATTERY_ATTR(hp_d2d), + SEC_BATTERY_ATTR(charger_ic_name), + SEC_BATTERY_ATTR(dc_rb_en), + SEC_BATTERY_ATTR(dc_op_mode), + SEC_BATTERY_ATTR(dc_adc_mode), + SEC_BATTERY_ATTR(dc_vbus), + SEC_BATTERY_ATTR(chg_type), + SEC_BATTERY_ATTR(mst_en), + SEC_BATTERY_ATTR(spsn_test), + SEC_BATTERY_ATTR(chg_soc_lim), + SEC_BATTERY_ATTR(mag_cover), + SEC_BATTERY_ATTR(mag_cloak), + SEC_BATTERY_ATTR(ari_cnt), +#if IS_ENABLED(CONFIG_SBP_FG) + SEC_BATTERY_ATTR(state_of_health), +#endif +}; + +static struct device_attribute sec_pogo_attrs[] = { + SEC_POGO_ATTR(sec_type), +}; + +static struct device_attribute sec_otg_attrs[] = { + SEC_OTG_ATTR(sec_type), +}; + +ssize_t sec_bat_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_battery_info *battery = power_supply_get_drvdata(psy); + const ptrdiff_t offset = attr - sec_battery_attrs; + union power_supply_propval value = {0, }; + int i = 0; + int ret = 0; + + switch (offset) { + case BATT_RESET_SOC: + break; + case BATT_READ_RAW_SOC: + { + value.intval = + SEC_FUELGAUGE_CAPACITY_TYPE_RAW; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CAPACITY, value); + + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_READ_ADJ_SOC: + break; + case BATT_TYPE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", + battery->batt_type); + break; + case BATT_VFOCV: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->voltage_ocv); + break; + case BATT_VOL_ADC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->inbat_adc); + break; + case BATT_VOL_ADC_CAL: + break; + case BATT_VOL_AVER: + break; + case BATT_VOL_ADC_AVER: + break; + case BATT_VOLTAGE_NOW: + { + value.intval = SEC_BATTERY_VOLTAGE_MV; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_VOLTAGE_NOW, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval * 1000); + } + break; + case BATT_CURRENT_UA_NOW: + { + value.intval = SEC_BATTERY_CURRENT_UA; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CURRENT_NOW, value); +#if defined(CONFIG_SEC_FACTORY) + pr_err("%s: batt_current_ua_now (%d)\n", + __func__, value.intval); +#endif + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_CURRENT_UA_AVG: + { + value.intval = SEC_BATTERY_CURRENT_UA; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CURRENT_AVG, value); + + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_FILTER_CFG: + { + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_EXT_PROP_FILTER_CFG, value); + + i += scnprintf(buf + i, PAGE_SIZE - i, "%x\n", + value.intval); + } + break; + case BATT_TEMP: + value.intval = sec_bat_get_temperature(battery->dev, + &battery->pdata->bat_thm_info, 0, + battery->pdata->charger_name, battery->pdata->fuelgauge_name, + battery->pdata->adc_read_type); +#if !defined(CONFIG_SEC_FACTORY) + if (battery->pdata->lr_enable) { + int sub_bat_temp = sec_bat_get_temperature(battery->dev, + &battery->pdata->sub_bat_thm_info, 0, + battery->pdata->charger_name, battery->pdata->fuelgauge_name, + battery->pdata->adc_read_type); + value.intval = adjust_bat_temp(battery, value.intval, sub_bat_temp); + } +#endif + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + break; + case BATT_TEMP_RAW: + value.intval = sec_bat_get_temperature(battery->dev, + &battery->pdata->bat_thm_info, 0, + battery->pdata->charger_name, battery->pdata->fuelgauge_name, + battery->pdata->adc_read_type); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + break; + case BATT_TEMP_ADC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->pdata->bat_thm_info.adc); + break; + case BATT_TEMP_AVER: + break; + case BATT_TEMP_ADC_AVER: + break; + case USB_TEMP: + value.intval = sec_bat_get_temperature(battery->dev, &battery->pdata->usb_thm_info, 0, + battery->pdata->charger_name, battery->pdata->fuelgauge_name, + battery->pdata->adc_read_type); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + break; + case USB_TEMP_ADC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->pdata->usb_thm_info.adc); + break; + case BATT_CHG_TEMP: + value.intval = sec_bat_get_temperature(battery->dev, + &battery->pdata->chg_thm_info, 0, + battery->pdata->charger_name, battery->pdata->fuelgauge_name, + battery->pdata->adc_read_type); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + break; + case BATT_CHG_TEMP_ADC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->pdata->chg_thm_info.adc); + break; + case SUB_BAT_TEMP: + value.intval = sec_bat_get_temperature(battery->dev, &battery->pdata->sub_bat_thm_info, 0, + battery->pdata->charger_name, battery->pdata->fuelgauge_name, + battery->pdata->adc_read_type); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + break; + case SUB_BAT_TEMP_ADC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->pdata->sub_bat_thm_info.adc); + break; +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + case DCHG_ADC_MODE_CTRL: + break; + case DCHG_TEMP: + { + if (battery->pdata->dctp_by_cgtp) + battery->dchg_temp = battery->chg_temp; + else + battery->dchg_temp = sec_bat_get_temperature(battery->dev, + &battery->pdata->dchg_thm_info, 0, + battery->pdata->charger_name, battery->pdata->fuelgauge_name, + battery->pdata->adc_read_type); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->dchg_temp); + } + break; + case DCHG_TEMP_ADC: + { + switch (battery->pdata->dchg_thm_info.source) { + case SEC_BATTERY_THERMAL_SOURCE_CHG_ADC: + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_PROP_TEMP, value); + break; + default: + value.intval = -1; + break; + } + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case DCHG_READ_BATP_BATN: + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_DCHG_READ_BATP_BATN, value); + pr_info("%s: DCHG_READ_BATP_BATN(%dmV) ", __func__, value.intval / 1000); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", value.intval / 1000); + break; +#endif + case BLKT_TEMP: + value.intval = sec_bat_get_temperature(battery->dev, &battery->pdata->blk_thm_info, 0, + battery->pdata->charger_name, battery->pdata->fuelgauge_name, + battery->pdata->adc_read_type); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + break; + case BLKT_TEMP_ADC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->pdata->blk_thm_info.adc); + break; + case BATT_VF_ADC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->check_adc_value); + break; + case BATT_SLATE_MODE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + is_slate_mode(battery)); + break; + + case BATT_LP_CHARGING: + if (sec_bat_get_lpmode()) + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", sec_bat_get_lpmode()); + break; + case SIOP_ACTIVATED: + break; + case SIOP_LEVEL: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->siop_level); + break; + case SIOP_EVENT: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + 0); + break; + case BATT_CHARGING_SOURCE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->cable_type); + break; + case FG_REG_DUMP: + break; + case FG_RESET_CAP: + break; + case FG_CAPACITY: + { + value.intval = + SEC_BATTERY_CAPACITY_DESIGNED; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_ENERGY_NOW, value); + + i += scnprintf(buf + i, PAGE_SIZE - i, "0x%04x ", + value.intval); + + value.intval = + SEC_BATTERY_CAPACITY_ABSOLUTE; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_ENERGY_NOW, value); + + i += scnprintf(buf + i, PAGE_SIZE - i, "0x%04x ", + value.intval); + + value.intval = + SEC_BATTERY_CAPACITY_TEMPERARY; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_ENERGY_NOW, value); + + i += scnprintf(buf + i, PAGE_SIZE - i, "0x%04x ", + value.intval); + + value.intval = + SEC_BATTERY_CAPACITY_CURRENT; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_ENERGY_NOW, value); + + i += scnprintf(buf + i, PAGE_SIZE - i, "0x%04x\n", + value.intval); + } + break; + case FG_ASOC: + value.intval = -1; + { + struct power_supply *psy_fg = NULL; + psy_fg = get_power_supply_by_name(battery->pdata->fuelgauge_name); + if (!psy_fg) { + pr_err("%s: Fail to get psy (%s)\n", + __func__, battery->pdata->fuelgauge_name); + } else { + if (psy_fg->desc->get_property != NULL) { + ret = psy_fg->desc->get_property(psy_fg, + POWER_SUPPLY_PROP_ENERGY_FULL, &value); + if (ret < 0) { + pr_err("%s: Fail to %s get (%d=>%d)\n", + __func__, battery->pdata->fuelgauge_name, + POWER_SUPPLY_PROP_ENERGY_FULL, ret); + } +#if IS_ENABLED(CONFIG_SEC_ABC) && !defined(CONFIG_SEC_FACTORY) + if (!value.intval) + sec_abc_send_event("MODULE=battery@WARN=show_fg_asoc0"); +#endif + } + } + } + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + break; + case AUTH: + break; + case CHG_CURRENT_ADC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->current_adc); + break; + case WC_ADC: + break; + case WC_STATUS: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + is_wireless_type(battery->cable_type) ? 1: 0); + break; + case WC_ENABLE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->wc_enable); + break; + case WC_CONTROL: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->wc_enable); + break; + case WC_CONTROL_CNT: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->wc_enable_cnt_value); + break; + case LED_COVER: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->led_cover); + break; + case HV_CHARGER_STATUS: + { + int check_val = 0; + + check_val = get_chg_power_type( + battery->cable_type, battery->wire_status, + battery->pd_max_charge_power, + battery->max_charge_power); + pr_info("%s : HV_CHARGER_STATUS(%d), max_charger_power(%d), pd_max charge power(%d)\n", + __func__, check_val, battery->max_charge_power, battery->pd_max_charge_power); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", check_val); + } + break; + case HV_WC_CHARGER_STATUS: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + is_hv_wireless_type(battery->cable_type) ? 1 : 0); + break; + case HV_CHARGER_SET: + break; + case FACTORY_MODE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->factory_mode); + break; + case STORE_MODE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->store_mode); + break; + case UPDATE: + break; + case TEST_MODE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->test_mode); + break; + case BATT_EVENT_CALL: + break; + case BATT_EVENT_2G_CALL: + break; + case BATT_EVENT_TALK_GSM: + break; + case BATT_EVENT_3G_CALL: + break; + case BATT_EVENT_TALK_WCDMA: + break; + case BATT_EVENT_MUSIC: + break; + case BATT_EVENT_VIDEO: + break; + case BATT_EVENT_BROWSER: + break; + case BATT_EVENT_HOTSPOT: + break; + case BATT_EVENT_CAMERA: + break; + case BATT_EVENT_CAMCORDER: + break; + case BATT_EVENT_DATA_CALL: + break; + case BATT_EVENT_WIFI: + break; + case BATT_EVENT_WIBRO: + break; + case BATT_EVENT_LTE: + break; + case BATT_EVENT_LCD: + break; +#if defined(CONFIG_ISDB_CHARGING_CONTROL) + case BATT_EVENT_ISDB: + break; +#endif + case BATT_EVENT_GPS: + break; + case BATT_EVENT: + break; + case BATT_HIGH_CURRENT_USB: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->is_hc_usb); + break; +#if defined(CONFIG_ENG_BATTERY_CONCEPT) + case TEST_CHARGE_CURRENT: + { + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; +#if defined(CONFIG_STEP_CHARGING) + case TEST_STEP_CONDITION: + { + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->test_step_condition); + } + break; +#endif +#endif + case SET_STABILITY_TEST: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->stability_test); + break; + case BATT_CAPACITY_MAX: + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", value.intval); + break; + case BATT_REPCAP_1ST: + if (battery->pdata->soc_by_repcap_en) { + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_EXT_PROP_CHARGE_FULL_REPCAP, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", value.intval); + } else + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", -1); + break; + case BATT_INBAT_VOLTAGE: + case BATT_INBAT_VOLTAGE_OCV: + ret = sec_bat_get_inbat_vol_ocv(battery); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + ret); + break; + case BATT_INBAT_VOLTAGE_ADC: + /* run twice */ + ret = (sec_bat_get_inbat_vol_by_adc(battery) +\ + sec_bat_get_inbat_vol_by_adc(battery)) / 2; + dev_info(battery->dev, "in-battery voltage adc(%d)\n", ret); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + ret); + break; + case BATT_VBYP_VOLTAGE: + value.intval = SEC_BATTERY_VBYP; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_EXT_PROP_MEASURE_INPUT, value); + pr_info("%s: vbyp(%d)mV\n", __func__, value.intval); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + break; + case CHECK_SUB_CHG: + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_CHECK_SUB_CHG_I2C, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + pr_info("%s : CHECK_SUB_CHG=%d\n",__func__,value.intval); + break; + case BATT_INBAT_WIRELESS_CS100: + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_PROP_STATUS, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", value.intval); + break; + case HMT_TA_CONNECTED: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->cable_type == SEC_BATTERY_CABLE_HMT_CONNECTED) ? 1 : 0); + break; + case HMT_TA_CHARGE: +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) || IS_ENABLED(CONFIG_CCIC_NOTIFIER) + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->current_event & SEC_BAT_CURRENT_EVENT_CHARGE_DISABLE) ? 0 : 1); +#else + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + (battery->cable_type == SEC_BATTERY_CABLE_HMT_CHARGE) ? 1 : 0); +#endif + break; +#if defined(CONFIG_SEC_FACTORY) + case AFC_TEST_FG_MODE: + break; +#endif + case FG_CYCLE: + value.intval = SEC_BATTERY_CAPACITY_CYCLE; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_ENERGY_NOW, value); + value.intval = value.intval / 100; + dev_info(battery->dev, "fg cycle(%d)\n", value.intval); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", value.intval); + break; + case FG_FULL_VOLTAGE: + { + int recharging_voltage = battery->pdata->recharge_condition_vcell; + + if (battery->current_event & SEC_BAT_CURRENT_EVENT_SWELLING_MODE) { + if (battery->current_event & SEC_BAT_CURRENT_EVENT_HIGH_TEMP_SWELLING) + recharging_voltage = battery->pdata->swelling_high_rechg_voltage; + else if (battery->current_event & SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING_COOL3) + recharging_voltage = battery->pdata->swelling_low_cool3_rechg_voltage; + else /* cool1 cool2 */ + recharging_voltage = battery->pdata->swelling_low_rechg_voltage; + } + + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d %d\n", + value.intval, recharging_voltage); + break; + } + case FG_FULLCAPNOM: + value.intval = + SEC_BATTERY_CAPACITY_AGEDCELL; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_ENERGY_NOW, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", value.intval); + break; +#if defined(CONFIG_BATTERY_AGE_FORECAST_DETACHABLE) + case BATT_AFTER_MANUFACTURED: +#endif + case BATTERY_CYCLE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", battery->batt_cycle); + break; + case BATTERY_CYCLE_TEST: + break; + case BATT_WPC_TEMP: + value.intval = sec_bat_get_temperature(battery->dev, &battery->pdata->wpc_thm_info, 0, + battery->pdata->charger_name, battery->pdata->fuelgauge_name, + battery->pdata->adc_read_type); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + break; + case BATT_WPC_TEMP_ADC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->pdata->wpc_thm_info.adc); + break; + case BATT_WIRELESS_MST_SWITCH_TEST: + value.intval = SEC_WIRELESS_MST_SWITCH_VERIFY; + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_PROP_MANUFACTURER, value); + pr_info("%s MST switch verify. result: %x\n", __func__, value.intval); + i += scnprintf(buf + i, PAGE_SIZE - i, "%x\n", value.intval); + break; +#if defined(CONFIG_WIRELESS_FIRMWARE_UPDATE) + case BATT_WIRELESS_FIRMWARE_UPDATE: + value.intval = SEC_WIRELESS_OTP_FIRM_VERIFY; + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_PROP_MANUFACTURER, value); + pr_info("%s RX firmware verify. result: %d\n", __func__, value.intval); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", value.intval); + break; + case OTP_FIRMWARE_RESULT: + value.intval = SEC_WIRELESS_OTP_FIRM_RESULT; + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_PROP_MANUFACTURER, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", value.intval); + break; + case WC_IC_GRADE: + value.intval = SEC_WIRELESS_IC_GRADE; + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_PROP_MANUFACTURER, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "0x%x ", value.intval); + + value.intval = SEC_WIRELESS_IC_REVISION; + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_PROP_MANUFACTURER, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "0x%x\n", value.intval); + break; + case WC_IC_CHIP_ID: + value.intval = SEC_WIRELESS_IC_CHIP_ID; + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_PROP_MANUFACTURER, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%x\n", value.intval); + break; + case OTP_FIRMWARE_VER_BIN: + value.intval = SEC_WIRELESS_OTP_FIRM_VER_BIN; + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_PROP_MANUFACTURER, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%x\n", value.intval); + break; + case OTP_FIRMWARE_VER: + value.intval = SEC_WIRELESS_OTP_FIRM_VER; + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_PROP_MANUFACTURER, value); + + i += scnprintf(buf + i, PAGE_SIZE - i, "%x\n", value.intval); + break; +#endif + case WC_PHM_CTRL: + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_RX_PHM, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", value.intval); + break; + case WC_VOUT: + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_PROP_ENERGY_NOW, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", value.intval); + break; + case WC_VRECT: + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_PROP_ENERGY_AVG, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", value.intval); + break; + + case WC_TX_EN: + pr_info("%s wc tx enable(%d)",__func__, battery->wc_tx_enable); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->wc_tx_enable); + break; + case WC_TX_VOUT: + pr_info("%s wc tx vout(%d)",__func__, battery->wc_tx_vout); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->wc_tx_vout); + break; + + case BATT_HV_WIRELESS_STATUS: + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_INPUT_VOLTAGE_REGULATION, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", value.intval); + break; + case BATT_HV_WIRELESS_PAD_CTRL: + break; + case WC_TX_ID: + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ID, value); + + pr_info("%s TX ID (%d)",__func__, value.intval); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", value.intval); + break; + case WC_OP_FREQ: + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_WIRELESS_OP_FREQ, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", value.intval); + break; + case WC_CMD_INFO: + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_WIRELESS_TRX_CMD, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "0x%02x ", + value.intval); + + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_WIRELESS_TRX_VAL, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "0x%02x ", + value.intval); + break; + case WC_RX_CONNECTED: + pr_info("%s RX Connected (%d)",__func__, battery->wc_rx_connected); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", battery->wc_rx_connected); + break; + case WC_RX_CONNECTED_DEV: + pr_info("%s RX Type (%d)",__func__, battery->wc_rx_type); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", battery->wc_rx_type); + break; + case WC_TX_MFC_VIN_FROM_UNO: + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_UNO_VIN, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + break; + case WC_TX_MFC_IIN_FROM_UNO: + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_UNO_IIN, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + break; +#if defined(CONFIG_WIRELESS_TX_MODE) + case WC_TX_AVG_CURR: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->tx_avg_curr); + break; + case WC_TX_TOTAL_PWR: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->tx_total_power); + /* If PMS read this value, average Tx current will be reset */ + battery->tx_time_cnt = 0; + battery->tx_avg_curr = 0; + battery->tx_total_power = 0; + break; +#endif + case WC_TX_STOP_CAPACITY: + ret = battery->pdata->tx_stop_capacity; + pr_info("%s tx stop capacity = %d%%", __func__, ret); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + ret); + break; + case WC_TX_TIMER_EN: + break; +#if defined(CONFIG_ENG_BATTERY_CONCEPT) + case BATT_TUNE_FLOAT_VOLTAGE: + ret = get_sec_vote_result(battery->fv_vote); + pr_info("%s float voltage = %d mA",__func__, ret); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + ret); + break; + case BATT_TUNE_INPUT_CHARGE_CURRENT: + ret = battery->pdata->charging_current[i].input_current_limit; + pr_info("%s input charge current = %d mA",__func__, ret); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + ret); + break; + case BATT_TUNE_FAST_CHARGE_CURRENT: + ret = battery->pdata->charging_current[i].fast_charging_current; + pr_info("%s fast charge current = %d mA",__func__, ret); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + ret); + break; + case BATT_TUNE_WIRELESS_VOUT_CURRENT: +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + ret = battery->wc20_vout; + pr_info("%s vout(%d) input_current(%d)",__func__, ret, + (battery->wc20_vout == 0) ? -1 : (battery->wc20_rx_power / battery->wc20_vout)); + + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + ret); +#endif + break; + case BATT_TUNE_UI_TERM_CURRENT_1ST: + ret = battery->pdata->full_check_current_1st; + pr_info("%s ui term current = %d mA",__func__, ret); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + ret); + break; + case BATT_TUNE_UI_TERM_CURRENT_2ND: + ret = battery->pdata->full_check_current_2nd; + pr_info("%s ui term current = %d mA",__func__, ret); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + ret); + break; + case BATT_TUNE_CHG_TEMP_HIGH: + ret = battery->pdata->chg_high_temp; + pr_info("%s chg_high_temp = %d ",__func__, ret); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + ret); + break; + case BATT_TUNE_CHG_TEMP_REC: + ret = battery->pdata->chg_high_temp_recovery; + pr_info("%s chg_high_temp_recovery = %d ",__func__, ret); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + ret); + break; + case BATT_TUNE_CHG_LIMIT_CUR: + ret = battery->pdata->chg_charging_limit_current; + pr_info("%s chg_charging_limit_current = %d ",__func__, ret); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + ret); + break; + case BATT_TUNE_LRP_TEMP_HIGH_LCDON: + { + char temp_buf[1024] = {0,}; + int j = 0; + int size = 0; + + snprintf(temp_buf, sizeof(temp_buf), "%d %d %d %d\n", + battery->pdata->lrp_temp[LRP_NORMAL].trig[ST2][LCD_ON], + battery->pdata->lrp_temp[LRP_NORMAL].recov[ST2][LCD_ON], + battery->pdata->lrp_temp[LRP_NORMAL].trig[ST1][LCD_ON], + battery->pdata->lrp_temp[LRP_NORMAL].recov[ST1][LCD_ON]); + size = sizeof(temp_buf) - strlen(temp_buf); + + for (j = LRP_NORMAL + 1; j < LRP_MAX; j++) { + snprintf(temp_buf+strlen(temp_buf), + size, "%d %d %d %d\n", + battery->pdata->lrp_temp[j].trig[ST2][LCD_ON], + battery->pdata->lrp_temp[j].recov[ST2][LCD_ON], + battery->pdata->lrp_temp[j].trig[ST1][LCD_ON], + battery->pdata->lrp_temp[j].recov[ST1][LCD_ON]); + size = sizeof(temp_buf) - strlen(temp_buf); + } + + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", temp_buf); + } + break; + case BATT_TUNE_LRP_TEMP_HIGH_LCDOFF: + { + char temp_buf[1024] = {0,}; + int j = 0; + int size = 0; + + snprintf(temp_buf, sizeof(temp_buf), "%d %d %d %d\n", + battery->pdata->lrp_temp[LRP_NORMAL].trig[ST2][LCD_OFF], + battery->pdata->lrp_temp[LRP_NORMAL].recov[ST2][LCD_OFF], + battery->pdata->lrp_temp[LRP_NORMAL].trig[ST1][LCD_OFF], + battery->pdata->lrp_temp[LRP_NORMAL].recov[ST1][LCD_OFF]); + size = sizeof(temp_buf) - strlen(temp_buf); + + for (j = LRP_NORMAL + 1; j < LRP_MAX; j++) { + snprintf(temp_buf+strlen(temp_buf), + size, "%d %d %d %d\n", + battery->pdata->lrp_temp[j].trig[ST2][LCD_OFF], + battery->pdata->lrp_temp[j].recov[ST2][LCD_OFF], + battery->pdata->lrp_temp[j].trig[ST1][LCD_OFF], + battery->pdata->lrp_temp[j].recov[ST1][LCD_OFF]); + size = sizeof(temp_buf) - strlen(temp_buf); + } + + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", temp_buf); + } + break; + case BATT_TUNE_COIL_TEMP_HIGH: + break; + case BATT_TUNE_COIL_TEMP_REC: + break; + case BATT_TUNE_COIL_LIMIT_CUR: + break; + case BATT_TUNE_WPC_TEMP_HIGH: + ret = battery->pdata->wpc_high_temp; + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + ret); + break; + case BATT_TUNE_WPC_TEMP_HIGH_REC: + ret = battery->pdata->wpc_high_temp_recovery; + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + ret); + break; + case BATT_TUNE_DCHG_TEMP_HIGH: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d %d %d %d\n", + battery->pdata->dchg_high_temp[0], + battery->pdata->dchg_high_temp[1], + battery->pdata->dchg_high_temp[2], + battery->pdata->dchg_high_temp[3]); + break; + case BATT_TUNE_DCHG_TEMP_HIGH_REC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d %d %d %d\n", + battery->pdata->dchg_high_temp_recovery[0], + battery->pdata->dchg_high_temp_recovery[1], + battery->pdata->dchg_high_temp_recovery[2], + battery->pdata->dchg_high_temp_recovery[3]); + break; + case BATT_TUNE_DCHG_BATT_TEMP_HIGH: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d %d %d %d\n", + battery->pdata->dchg_high_batt_temp[0], + battery->pdata->dchg_high_batt_temp[1], + battery->pdata->dchg_high_batt_temp[2], + battery->pdata->dchg_high_batt_temp[3]); + break; + case BATT_TUNE_DCHG_BATT_TEMP_HIGH_REC: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d %d %d %d\n", + battery->pdata->dchg_high_batt_temp_recovery[0], + battery->pdata->dchg_high_batt_temp_recovery[1], + battery->pdata->dchg_high_batt_temp_recovery[2], + battery->pdata->dchg_high_batt_temp_recovery[3]); + break; + case BATT_TUNE_DCHG_LIMIT_INPUT_CUR: + ret = battery->pdata->dchg_input_limit_current; + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + ret); + break; + case BATT_TUNE_DCHG_LIMIT_CHG_CUR: + ret = battery->pdata->dchg_charging_limit_current; + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + ret); + break; +#if defined(CONFIG_WIRELESS_TX_MODE) + case BATT_TUNE_TX_MFC_IOUT_GEAR: + ret = battery->pdata->tx_mfc_iout_gear; + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + ret); + break; + case BATT_TUNE_TX_MFC_IOUT_PHONE: + ret = battery->pdata->tx_mfc_iout_phone; + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + ret); + break; +#endif +#endif +#if defined(CONFIG_UPDATE_BATTERY_DATA) + case BATT_UPDATE_DATA: + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", + battery->data_path ? "OK" : "NOK"); + break; +#endif + case BATT_MISC_EVENT: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->misc_event); + break; + case BATT_TX_EVENT: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->tx_event); + if (battery->tx_event & BATT_TX_EVENT_WIRELESS_TX_ERR) { + /* clear tx all event */ + sec_bat_set_tx_event(battery, 0, BATT_TX_EVENT_WIRELESS_ALL_MASK); + } + break; + case BATT_EXT_DEV_CHG: + break; + case BATT_WDT_CONTROL: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->wdt_kick_disable); + break; + case MODE: + value.strval = NULL; + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_MULTI_CHARGER_MODE, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", + (value.strval) ? value.strval : "main"); + break; + case CHECK_PS_READY: + value.intval = battery->pdic_ps_rdy; + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + pr_info("%s : CHECK_PS_READY=%d\n",__func__,value.intval); + break; + case BATT_CHIP_ID: + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_CHIP_ID, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + break; + case ERROR_CAUSE: + { + int error_cause = SEC_BAT_ERROR_CAUSE_NONE; + + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_EXT_PROP_ERROR_CAUSE, value); + error_cause |= value.intval; + pr_info("%s: ERROR_CAUSE = 0x%X ",__func__, error_cause); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + error_cause); + } + break; + case CISD_FULLCAPREP_MAX: + { + union power_supply_propval fullcaprep_val; + + fullcaprep_val.intval = SEC_BATTERY_CAPACITY_FULL; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_ENERGY_NOW, fullcaprep_val); + + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + fullcaprep_val.intval); + } + break; + case CISD_DATA: + { + struct cisd *pcisd = &battery->cisd; + char temp_buf[1024] = {0,}; + int j = 0; + int size = 0; + + snprintf(temp_buf, sizeof(temp_buf), "%d", pcisd->data[CISD_DATA_RESET_ALG]); + size = sizeof(temp_buf) - strlen(temp_buf); + + for (j = CISD_DATA_RESET_ALG + 1; j < CISD_DATA_MAX_PER_DAY; j++) { + snprintf(temp_buf+strlen(temp_buf), size, " %d", pcisd->data[j]); + size = sizeof(temp_buf) - strlen(temp_buf); + } + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", temp_buf); + } + break; + case CISD_DATA_JSON: + { + struct cisd *pcisd = &battery->cisd; + char temp_buf[1024] = {0,}; + int j = 0; + int size = 0; + + snprintf(temp_buf, sizeof(temp_buf), "\"%s\":\"%d\"", + cisd_data_str[CISD_DATA_RESET_ALG], pcisd->data[CISD_DATA_RESET_ALG]); + size = sizeof(temp_buf) - strlen(temp_buf); + + for (j = CISD_DATA_RESET_ALG + 1; j < CISD_DATA_MAX; j++) { + if (battery->pdata->ignore_cisd_index[j / 32] & (0x1 << (j % 32))) + continue; + snprintf(temp_buf+strlen(temp_buf), size, ",\"%s\":\"%d\"", cisd_data_str[j], pcisd->data[j]); + size = sizeof(temp_buf) - strlen(temp_buf); + } + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", temp_buf); + } + break; + case CISD_DATA_D_JSON: + { + struct cisd *pcisd = &battery->cisd; + char temp_buf[1024] = {0,}; + int j = 0; + int size = 0; + + snprintf(temp_buf, sizeof(temp_buf), "\"%s\":\"%d\"", + cisd_data_str_d[CISD_DATA_FULL_COUNT_PER_DAY-CISD_DATA_MAX], + pcisd->data[CISD_DATA_FULL_COUNT_PER_DAY]); + size = sizeof(temp_buf) - strlen(temp_buf); + + for (j = CISD_DATA_FULL_COUNT_PER_DAY + 1; j < CISD_DATA_MAX_PER_DAY; j++) { + if (battery->pdata->ignore_cisd_index_d[(j - CISD_DATA_FULL_COUNT_PER_DAY) / 32] & (0x1 << ((j - CISD_DATA_FULL_COUNT_PER_DAY) % 32))) + continue; + snprintf(temp_buf+strlen(temp_buf), size, ",\"%s\":\"%d\"", + cisd_data_str_d[j-CISD_DATA_MAX], pcisd->data[j]); + size = sizeof(temp_buf) - strlen(temp_buf); + } + + /* Clear Daily Data */ + for (j = CISD_DATA_FULL_COUNT_PER_DAY; j < CISD_DATA_MAX_PER_DAY; j++) + pcisd->data[j] = 0; + + pcisd->data[CISD_DATA_FULL_COUNT_PER_DAY] = 1; + pcisd->data[CISD_DATA_BATT_TEMP_MAX_PER_DAY] = -300; + pcisd->data[CISD_DATA_SUB_BATT_TEMP_MAX_PER_DAY] = -300; + pcisd->data[CISD_DATA_CHG_TEMP_MAX_PER_DAY] = -300; + pcisd->data[CISD_DATA_USB_TEMP_MAX_PER_DAY] = -300; + pcisd->data[CISD_DATA_BATT_TEMP_MIN_PER_DAY] = 1000; + pcisd->data[CISD_DATA_SUB_BATT_TEMP_MIN_PER_DAY] = 1000; + pcisd->data[CISD_DATA_CHG_TEMP_MIN_PER_DAY] = 1000; + pcisd->data[CISD_DATA_USB_TEMP_MIN_PER_DAY] = 1000; + + pcisd->data[CISD_DATA_CHG_BATT_TEMP_MAX_PER_DAY] = -300; + pcisd->data[CISD_DATA_CHG_SUB_BATT_TEMP_MAX_PER_DAY] = -300; + pcisd->data[CISD_DATA_CHG_CHG_TEMP_MAX_PER_DAY] = -300; + pcisd->data[CISD_DATA_CHG_USB_TEMP_MAX_PER_DAY] = -300; + pcisd->data[CISD_DATA_CHG_BATT_TEMP_MIN_PER_DAY] = 1000; + pcisd->data[CISD_DATA_CHG_SUB_BATT_TEMP_MIN_PER_DAY] = 1000; + pcisd->data[CISD_DATA_CHG_CHG_TEMP_MIN_PER_DAY] = 1000; + pcisd->data[CISD_DATA_CHG_USB_TEMP_MIN_PER_DAY] = 1000; + pcisd->data[CISD_DATA_CHG_RETENTION_TIME_PER_DAY] = 0; + pcisd->data[CISD_DATA_TOTAL_CHG_RETENTION_TIME_PER_DAY] = 0; + + pcisd->data[CISD_DATA_CAP_MIN_PER_DAY] = 0xFFFF; + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", temp_buf); + } + break; + case CISD_WIRE_COUNT: + { + struct cisd *pcisd = &battery->cisd; + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + pcisd->data[CISD_DATA_WIRE_COUNT]); + } + break; + case CISD_WC_DATA: + { + struct cisd *pcisd = &battery->cisd; + struct pad_data *pad_data = NULL; + char temp_buf[1024] = {0,}; + int j = 0, size = 1024; + + mutex_lock(&pcisd->padlock); + pad_data = pcisd->pad_array; + snprintf(temp_buf, size, "%d", pcisd->pad_count); + while ((pad_data != NULL) && ((pad_data = pad_data->next) != NULL) && + (pad_data->id < MAX_PAD_ID) && (j++ < pcisd->pad_count)) { + snprintf(temp_buf+strlen(temp_buf), size, " 0x%02x:%d", pad_data->id, pad_data->count); + size = sizeof(temp_buf) - strlen(temp_buf); + } + mutex_unlock(&pcisd->padlock); + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", temp_buf); + } + break; + case CISD_WC_DATA_JSON: + { + struct cisd *pcisd = &battery->cisd; + struct pad_data *pad_data = NULL; + char temp_buf[1024] = {0,}; + int j = 0, size = 1024; + + mutex_lock(&pcisd->padlock); + pad_data = pcisd->pad_array; + snprintf(temp_buf+strlen(temp_buf), size, "\"%s\":\"%d\"", + PAD_INDEX_STRING, pcisd->pad_count); + while ((pad_data != NULL) && ((pad_data = pad_data->next) != NULL) && + (pad_data->id < MAX_PAD_ID) && (j++ < pcisd->pad_count)) { + snprintf(temp_buf+strlen(temp_buf), size, ",\"%s%02x\":\"%d\"", + PAD_JSON_STRING, pad_data->id, pad_data->count); + size = sizeof(temp_buf) - strlen(temp_buf); + } + mutex_unlock(&pcisd->padlock); + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", temp_buf); + } + break; + case CISD_POWER_DATA: + { + struct cisd *pcisd = &battery->cisd; + struct power_data *power_data = NULL; + char temp_buf[1024] = {0,}; + int j = 0, size = 1024; + + mutex_lock(&pcisd->powerlock); + power_data = pcisd->power_array; + snprintf(temp_buf+strlen(temp_buf), size, "%d", pcisd->power_count); + while ((power_data != NULL) && ((power_data = power_data->next) != NULL) && + (power_data->power < MAX_CHARGER_POWER) && (j++ < pcisd->power_count)) { + snprintf(temp_buf+strlen(temp_buf), size, " %d:%d", power_data->power, power_data->count); + size = sizeof(temp_buf) - strlen(temp_buf); + } + mutex_unlock(&pcisd->powerlock); + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", temp_buf); + } + break; + case CISD_POWER_DATA_JSON: + { + struct cisd *pcisd = &battery->cisd; + struct power_data *power_data = NULL; + char temp_buf[1024] = {0,}; + int j = 0, size = 1024; + + mutex_lock(&pcisd->powerlock); + power_data = pcisd->power_array; + snprintf(temp_buf+strlen(temp_buf), size, "\"%s\":\"%d\"", + POWER_COUNT_JSON_STRING, pcisd->power_count); + while ((power_data != NULL) && ((power_data = power_data->next) != NULL) && + (power_data->power < MAX_CHARGER_POWER) && (j++ < pcisd->power_count)) { + snprintf(temp_buf+strlen(temp_buf), size, ",\"%s%d\":\"%d\"", + POWER_JSON_STRING, power_data->power, power_data->count); + size = sizeof(temp_buf) - strlen(temp_buf); + } + mutex_unlock(&pcisd->powerlock); + + /* clear daily power data */ + init_cisd_power_data(&battery->cisd); + + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", temp_buf); + } + break; + case CISD_PD_DATA: + { + struct cisd *pcisd = &battery->cisd; + struct pd_data *pd_data = NULL; + char temp_buf[1024] = {0,}; + int j = 0, size = 1024; + + mutex_lock(&pcisd->pdlock); + pd_data = pcisd->pd_array; + snprintf(temp_buf+strlen(temp_buf), size, "%d", pcisd->pd_count); + while ((pd_data != NULL) && ((pd_data = pd_data->next) != NULL) && + (pd_data->pid < MAX_SS_PD_PID) && (j++ < pcisd->pd_count)) { + snprintf(temp_buf+strlen(temp_buf), size, " 0x%04x:%d", pd_data->pid, pd_data->count); + size = sizeof(temp_buf) - strlen(temp_buf); + } + mutex_unlock(&pcisd->pdlock); + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", temp_buf); + } + break; + case CISD_PD_DATA_JSON: + { + struct cisd *pcisd = &battery->cisd; + struct pd_data *pd_data = NULL; + char temp_buf[1024] = {0,}; + int j = 0, size = 1024; + + mutex_lock(&pcisd->pdlock); + pd_data = pcisd->pd_array; + snprintf(temp_buf+strlen(temp_buf), size, "\"%s\":\"%d\"", + PD_COUNT_JSON_STRING, pcisd->pd_count); + while ((pd_data != NULL) && ((pd_data = pd_data->next) != NULL) && + (pd_data->pid < MAX_SS_PD_PID) && (j++ < pcisd->pd_count)) { + if (pd_data->pid == 0x0) + snprintf(temp_buf+strlen(temp_buf), size, ",\"PID_OTHER\":\"%d\"", + pd_data->count); + else + snprintf(temp_buf+strlen(temp_buf), size, ",\"%s%04x\":\"%d\"", + PD_JSON_STRING, pd_data->pid, pd_data->count); + size = sizeof(temp_buf) - strlen(temp_buf); + } + mutex_unlock(&pcisd->pdlock); + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", temp_buf); + } + break; + case CISD_CABLE_DATA: + { + struct cisd *pcisd = &battery->cisd; + char temp_buf[1024] = {0,}; + int j = 0; + int size = 0; + + snprintf(temp_buf, sizeof(temp_buf), "%d", pcisd->cable_data[CISD_CABLE_TA]); + size = sizeof(temp_buf) - strlen(temp_buf); + + for (j = CISD_CABLE_TA + 1; j < CISD_CABLE_TYPE_MAX; j++) { + snprintf(temp_buf+strlen(temp_buf), size, " %d", pcisd->cable_data[j]); + size = sizeof(temp_buf) - strlen(temp_buf); + } + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", temp_buf); + + } + break; + + case CISD_CABLE_DATA_JSON: + { + struct cisd *pcisd = &battery->cisd; + char temp_buf[1024] = {0,}; + int j = 0; + int size = 0; + + snprintf(temp_buf, sizeof(temp_buf), "\"%s\":\"%d\"", + cisd_cable_data_str[CISD_CABLE_TA], pcisd->cable_data[CISD_CABLE_TA]); + size = sizeof(temp_buf) - strlen(temp_buf); + + for (j = CISD_CABLE_TA + 1; j < CISD_CABLE_TYPE_MAX; j++) { + snprintf(temp_buf+strlen(temp_buf), size, ",\"%s\":\"%d\"", + cisd_cable_data_str[j], pcisd->cable_data[j]); + size = sizeof(temp_buf) - strlen(temp_buf); + } + + /* Clear Daily Cable Data */ + for (j = CISD_CABLE_TA; j < CISD_CABLE_TYPE_MAX; j++) + pcisd->cable_data[j] = 0; + + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", temp_buf); + } + break; + case CISD_TX_DATA: + { + struct cisd *pcisd = &battery->cisd; + char temp_buf[1024] = {0,}; + int j = 0; + int size = 0; + + snprintf(temp_buf, sizeof(temp_buf), "%d", pcisd->tx_data[TX_ON]); + size = sizeof(temp_buf) - strlen(temp_buf); + + for (j = TX_ON + 1; j < TX_DATA_MAX; j++) { + snprintf(temp_buf+strlen(temp_buf), size, " %d", pcisd->tx_data[j]); + size = sizeof(temp_buf) - strlen(temp_buf); + } + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", temp_buf); + } + break; + case CISD_TX_DATA_JSON: + { + struct cisd *pcisd = &battery->cisd; + char temp_buf[1024] = {0,}; + int j = 0; + int size = 0; + + snprintf(temp_buf, sizeof(temp_buf), "\"%s\":\"%d\"", + cisd_tx_data_str[TX_ON], pcisd->tx_data[TX_ON]); + size = sizeof(temp_buf) - strlen(temp_buf); + + for (j = TX_ON + 1; j < TX_DATA_MAX; j++) { + snprintf(temp_buf+strlen(temp_buf), size, ",\"%s\":\"%d\"", + cisd_tx_data_str[j], pcisd->tx_data[j]); + size = sizeof(temp_buf) - strlen(temp_buf); + } + + /* Clear Daily Tx Data */ + for (j = TX_ON; j < TX_DATA_MAX; j++) + pcisd->tx_data[j] = 0; + + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", temp_buf); + } + break; + case CISD_EVENT_DATA: + { + struct cisd *pcisd = &battery->cisd; + char temp_buf[1024] = {0,}; + int j = 0; + int size = 0; + + snprintf(temp_buf, sizeof(temp_buf), "%d", pcisd->event_data[EVENT_DC_ERR]); + size = sizeof(temp_buf) - strlen(temp_buf); + + for (j = EVENT_DC_ERR + 1; j < EVENT_DATA_MAX; j++) { + snprintf(temp_buf+strlen(temp_buf), size, " %d", pcisd->event_data[j]); + size = sizeof(temp_buf) - strlen(temp_buf); + } + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", temp_buf); + } + break; + case CISD_EVENT_DATA_JSON: + { + struct cisd *pcisd = &battery->cisd; + char temp_buf[1024] = {0,}; + int j = 0; + int size = 0; + + snprintf(temp_buf, sizeof(temp_buf), "\"%s\":\"%d\"", + cisd_event_data_str[EVENT_DC_ERR], pcisd->event_data[EVENT_DC_ERR]); + size = sizeof(temp_buf) - strlen(temp_buf); + + for (j = EVENT_DC_ERR + 1; j < EVENT_DATA_MAX; j++) { + snprintf(temp_buf+strlen(temp_buf), size, ",\"%s\":\"%d\"", + cisd_event_data_str[j], pcisd->event_data[j]); + size = sizeof(temp_buf) - strlen(temp_buf); + } + + /* Clear Daily Event Data */ + for (j = EVENT_DC_ERR; j < EVENT_DATA_MAX; j++) + pcisd->event_data[j] = 0; + + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", temp_buf); + } + break; + case PREV_BATTERY_DATA: + { + if (battery->enable_update_data) + i += scnprintf(buf + i, PAGE_SIZE - i, "%d, %d, %d, %d\n", + battery->voltage_now, battery->temperature, battery->is_jig_on, + (battery->charger_mode == SEC_BAT_CHG_MODE_CHARGING) ? 1 : 0); + } + break; + case PREV_BATTERY_INFO: + { + i += scnprintf(buf + i, PAGE_SIZE - i, "%d,%d,%d,%d\n", + battery->prev_volt, battery->prev_temp, + battery->prev_jig_on, battery->prev_chg_on); + pr_info("%s: Read Prev Battery Info : %d, %d, %d, %d\n", __func__, + battery->prev_volt, battery->prev_temp, + battery->prev_jig_on, battery->prev_chg_on); + } + break; + case SAFETY_TIMER_SET: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->safety_timer_set); + break; + case BATT_SWELLING_CONTROL: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->skip_swelling); + break; + case BATT_BATTERY_ID: + value.intval = 0; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_EXT_PROP_BATTERY_ID, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + break; +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + case BATT_SUB_BATTERY_ID: + value.intval = SEC_DUAL_BATTERY_SUB; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_EXT_PROP_BATTERY_ID, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + break; +#endif + case BATT_TEMP_CONTROL_TEST: + { + int temp_ctrl_t = 0; + + if (battery->current_event & SEC_BAT_CURRENT_EVENT_TEMP_CTRL_TEST) + temp_ctrl_t = 1; + else + temp_ctrl_t = 0; + + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + temp_ctrl_t); + } + break; + case SAFETY_TIMER_INFO: + i += scnprintf(buf + i, PAGE_SIZE - i, "%ld\n", + battery->cal_safety_time); + break; + case BATT_SHIPMODE_TEST: + value.intval = 0; + ret = psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_SHIPMODE_TEST, value); + if (ret < 0) { + pr_info("%s: not support BATT_SHIPMODE_TEST\n", __func__); + value.intval = 0; + } else { + pr_info("%s: show BATT_SHIPMODE_TEST(%d)\n", __func__, value.intval); + } + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", value.intval); + break; + case BATT_MISC_TEST: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->display_test); + break; +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + case BATT_TEMP_TEST: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d %d %d %d %d\n", + battery->pdata->bat_thm_info.test, + battery->pdata->usb_thm_info.test, + battery->pdata->wpc_thm_info.test, + battery->pdata->chg_thm_info.test, + battery->pdata->sub_bat_thm_info.test); + break; +#else + case BATT_TEMP_TEST: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d %d %d %d %d %d %d\n", + battery->pdata->bat_thm_info.test, + battery->pdata->usb_thm_info.test, + battery->pdata->wpc_thm_info.test, + battery->pdata->chg_thm_info.test, + battery->pdata->dchg_thm_info.test, + battery->pdata->blk_thm_info.test, + battery->lrp_test); + break; +#endif + case BATT_CURRENT_EVENT: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->current_event); + break; + case BATT_JIG_GPIO: + value.intval = 0; + ret = psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_EXT_PROP_JIG_GPIO, value); + if (value.intval < 0 || ret < 0) { + value.intval = -1; + pr_info("%s: does not support JIG GPIO PIN READ\n", __func__); + } + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + break; + case CC_INFO: + { + union power_supply_propval cc_val; + + cc_val.intval = SEC_BATTERY_CAPACITY_QH; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_ENERGY_NOW, cc_val); + + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + cc_val.intval); + } + break; +#if defined(CONFIG_WIRELESS_AUTH) + case WC_AUTH_ADT_SENT: + { + //union power_supply_propval val = {0, }; + u8 auth_mode; + + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_WIRELESS_AUTH_ADT_STATUS, value); + auth_mode = value.intval; + if (auth_mode == WIRELESS_AUTH_WAIT) + value.strval = "None"; + else if (auth_mode == WIRELESS_AUTH_START) + value.strval = "Start"; + else if (auth_mode == WIRELESS_AUTH_SENT) + value.strval = "Sent"; + else if (auth_mode == WIRELESS_AUTH_RECEIVED) + value.strval = "Received"; + else if (auth_mode == WIRELESS_AUTH_FAIL) + value.strval = "Fail"; + else if (auth_mode == WIRELESS_AUTH_PASS) + value.strval = "Pass"; + else + value.strval = "None"; + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", value.strval); + } + break; +#endif + case WC_DUO_RX_POWER: + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_WIRELESS_RX_POWER, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + break; +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + case BATT_MAIN_VOLTAGE: + { + value.intval = SEC_DUAL_BATTERY_MAIN; + psy_do_property(battery->pdata->dual_battery_name, get, + POWER_SUPPLY_PROP_VOLTAGE_AVG, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_SUB_VOLTAGE: + { + value.intval = SEC_DUAL_BATTERY_SUB; + psy_do_property(battery->pdata->dual_battery_name, get, + POWER_SUPPLY_PROP_VOLTAGE_AVG, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_MAIN_VCELL: + { + value.intval = SEC_DUAL_BATTERY_MAIN; + psy_do_property(battery->pdata->dual_battery_name, get, + POWER_SUPPLY_PROP_VOLTAGE_NOW, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_SUB_VCELL: + { + value.intval = SEC_DUAL_BATTERY_SUB; + psy_do_property(battery->pdata->dual_battery_name, get, + POWER_SUPPLY_PROP_VOLTAGE_NOW, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_MAIN_CURRENT_MA: + { + value.intval = SEC_DUAL_BATTERY_MAIN; + psy_do_property(battery->pdata->dual_battery_name, get, + POWER_SUPPLY_PROP_CURRENT_NOW, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_SUB_CURRENT_MA: + { + value.intval = SEC_DUAL_BATTERY_SUB; + psy_do_property(battery->pdata->dual_battery_name, get, + POWER_SUPPLY_PROP_CURRENT_NOW, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_MAIN_CON_DET: + { + value.intval = SEC_DUAL_BATTERY_MAIN; + psy_do_property(battery->pdata->dual_battery_name, get, + POWER_SUPPLY_EXT_PROP_DUAL_BAT_DET, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_SUB_CON_DET: + { + value.intval = SEC_DUAL_BATTERY_SUB; + psy_do_property(battery->pdata->dual_battery_name, get, + POWER_SUPPLY_EXT_PROP_DUAL_BAT_DET, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; +#if IS_ENABLED(CONFIG_LIMITER_S2ASL01) + case BATT_MAIN_VCHG: + { + value.intval = SEC_BATTERY_VOLTAGE_MV; + psy_do_property(battery->pdata->main_limiter_name, get, + POWER_SUPPLY_EXT_PROP_CHG_VOLTAGE, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_SUB_VCHG: + { + value.intval = SEC_BATTERY_VOLTAGE_MV; + psy_do_property(battery->pdata->sub_limiter_name, get, + POWER_SUPPLY_EXT_PROP_CHG_VOLTAGE, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_MAIN_ENB: /* This pin is reversed by FET */ + { + if (battery->pdata->main_bat_enb_gpio) + value.intval = !gpio_get_value(battery->pdata->main_bat_enb_gpio); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_MAIN_ENB2: + { + if (battery->pdata->main_bat_enb2_gpio) + value.intval = gpio_get_value(battery->pdata->main_bat_enb2_gpio); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_SUB_ENB: + { + if (battery->pdata->sub_bat_enb_gpio) + value.intval = gpio_get_value(battery->pdata->sub_bat_enb_gpio); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_SUB_PWR_MODE2: + { + psy_do_property(battery->pdata->sub_limiter_name, get, + POWER_SUPPLY_EXT_PROP_POWER_MODE2, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; +#else /* max17333 */ + case BATT_MAIN_SHIPMODE: + { + value.intval = 0; + ret = psy_do_property(battery->pdata->main_limiter_name, get, + POWER_SUPPLY_EXT_PROP_LIMITER_SHIPMODE, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_SUB_SHIPMODE: + { + value.intval = 0; + ret = psy_do_property(battery->pdata->sub_limiter_name, get, + POWER_SUPPLY_EXT_PROP_LIMITER_SHIPMODE, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; +#endif +#if IS_ENABLED(CONFIG_DUAL_FUELGAUGE) + case BATT_MAIN_SOC: + { + value.intval = battery->main_capacity; + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_SUB_SOC: + { + value.intval = battery->sub_capacity; + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_MAIN_REPCAP: + { + value.intval = SEC_DUAL_BATTERY_MAIN; + psy_do_property(battery->pdata->dual_fuelgauge_name, get, + POWER_SUPPLY_EXT_PROP_REPCAP, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_SUB_REPCAP: + { + value.intval = SEC_DUAL_BATTERY_SUB; + psy_do_property(battery->pdata->dual_fuelgauge_name, get, + POWER_SUPPLY_EXT_PROP_REPCAP, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_MAIN_FULLCAPREP: + { + value.intval = SEC_DUAL_BATTERY_MAIN; + psy_do_property(battery->pdata->dual_fuelgauge_name, get, + POWER_SUPPLY_EXT_PROP_FULLCAPREP, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; + case BATT_SUB_FULLCAPREP: + { + value.intval = SEC_DUAL_BATTERY_SUB; + psy_do_property(battery->pdata->dual_fuelgauge_name, get, + POWER_SUPPLY_EXT_PROP_FULLCAPREP, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } + break; +#endif +#endif + case EXT_EVENT: + break; +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + case DIRECT_CHARGING_STATUS: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->pd_list.now_isApdo); + break; + case DIRECT_CHARGING_STEP: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->step_chg_status); + break; + case DIRECT_CHARGING_IIN: + if (is_pd_apdo_wire_type(battery->wire_status)) { + value.intval = SEC_BATTERY_IIN_UA; + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_MEASURE_INPUT, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + value.intval); + } else { + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + 0); + } + break; + case DIRECT_CHARGING_CHG_STATUS: + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_DIRECT_CHARGER_CHG_STATUS, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", + value.strval); + break; + case SWITCH_CHARGING_SOURCE: + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_CHANGE_CHARGING_SOURCE, value); + pr_info("%s Test Charging Source(%d) ",__func__, value.intval); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", value.intval); + break; +#else + case DIRECT_CHARGING_STATUS: + ret = -1; /* DC not supported model returns -1 */ + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", ret); + break; +#endif + case CHARGING_TYPE: + { + if (battery->cable_type > 0 && battery->cable_type < SEC_BATTERY_CABLE_MAX) { + value.strval = sb_get_ct_str(battery->cable_type); +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + if (is_pd_apdo_wire_type(battery->cable_type) && + battery->current_event & SEC_BAT_CURRENT_EVENT_DC_ERR) + value.strval = "PDIC"; +#endif + } else + value.strval = "UNKNOWN"; + pr_info("%s: CHARGING_TYPE = %s\n",__func__, value.strval); + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", value.strval); + } + break; + case BATT_FACTORY_MODE: +#if IS_ENABLED(CONFIG_USB_FACTORY_MODE) + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->usb_factory_init ? battery->usb_factory_mode : sec_bat_get_facmode()); +#else + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", sec_bat_get_facmode()); +#endif + break; + case PD_DISABLE: + if (battery->pd_disable) + value.strval = "PD Disabled"; + else + value.strval = "PD Enabled"; + pr_info("%s: PD = %s\n",__func__, value.strval); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", battery->pd_disable); + break; + case FACTORY_MODE_RELIEVE: + break; + case FACTORY_MODE_BYPASS: + break; + case NORMAL_MODE_BYPASS: + break; + case FACTORY_VOLTAGE_REGULATION: + break; + case FACTORY_MODE_DISABLE: + break; + case CHARGE_OTG_CONTROL: + break; + case CHARGE_UNO_CONTROL: + break; + case CHARGE_COUNTER_SHADOW: + psy_do_property("battery", get, + POWER_SUPPLY_EXT_PROP_CHARGE_COUNTER_SHADOW, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", value.intval); + break; + case VOTER_STATUS: + i = show_sec_vote_status(buf, PAGE_SIZE); + break; +#if defined(CONFIG_WIRELESS_IC_PARAM) + case WC_PARAM_INFO: + ret = psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_WIRELESS_PARAM_INFO, value); + if (ret < 0) { + i = -EINVAL; + } else { + pr_info("%s: WC_PARAM_INFO(0x%08x)\n", __func__, value.intval); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", value.intval); + } + break; +#endif + case CHG_INFO: + { + unsigned short vid = 0, pid = 0; + unsigned int xid = 0; + + sec_pd_get_vid_pid(&vid, &pid, &xid); + i += scnprintf(buf + i, PAGE_SIZE - i, "%04x %04x %08x\n", vid, pid, xid); + } + break; + case LRP: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", battery->lrp); + break; + case HP_D2D: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", battery->hp_d2d); + break; + case CHARGER_IC_NAME: + ret = psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_CHARGER_IC_NAME, value); + if (ret < 0) { + pr_info("%s: read fail\n", __func__); + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", "NONAME"); + } else { + pr_info("%s: CHARGER_IC_NAME: %s\n", __func__, value.strval); + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", value.strval); + } + break; + case DC_RB_EN: + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_D2D_REVERSE_VOLTAGE, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", value.intval); + break; + case DC_OP_MODE: + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_DC_OP_MODE, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%x\n", value.intval); + break; + case DC_ADC_MODE: + break; + case DC_VBUS: + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_D2D_REVERSE_VBUS, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", value.intval); + break; + case CHG_TYPE: + { + char temp_buf[20] = {0,}; + + value.strval = sb_get_ct_str(battery->cable_type); + strncpy(temp_buf, value.strval, sizeof(temp_buf) - 1); + if (is_pd_apdo_wire_type(battery->cable_type)) + snprintf(temp_buf+strlen(temp_buf), sizeof(temp_buf), "_%d", battery->pd_rated_power); + pr_info("%s: CHG_TYPE = %s\n", __func__, temp_buf); + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", temp_buf); + } + break; + case MST_EN: + psy_do_property("battery", get, + POWER_SUPPLY_EXT_PROP_MST_EN, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", value.intval); + break; + case SPSN_TEST: + /* Only MD15 supports this function (2022y 10m 12d) */ + ret = psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_SPSN_TEST, value); + if (ret < 0) { + pr_info("%s: Does not support SPSN_TEST(%d)\n", __func__, ret); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", ret); + } else { + pr_info("%s: SPSN_DTLS: 0x%x\n", __func__, value.intval); + i += scnprintf(buf + i, PAGE_SIZE - i, "%x\n", value.intval); + } + break; + case CHG_SOC_LIM: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d %d\n", + battery->pdata->store_mode_charging_min, + battery->pdata->store_mode_charging_max); + break; + case MAG_COVER: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + battery->mag_cover); + break; + case MAG_CLOAK: + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", "None"); + break; + case ARI_CNT: + i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", "None"); + break; +#if IS_ENABLED(CONFIG_SBP_FG) + case STATE_OF_HEALTH: + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_HEALTH, value); + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", value.intval); + break; +#endif + default: + i = -EINVAL; + break; + } + + return i; +} + +ssize_t sec_bat_store_attrs( + struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct sec_battery_info *battery = power_supply_get_drvdata(psy); + const ptrdiff_t offset = attr - sec_battery_attrs; + int ret = -EINVAL; + int x = 0, y = 0; + int i = 0; +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + char direct_charging_source_status[2] = {0, }; +#endif + + union power_supply_propval value = {0, }; + + switch (offset) { + case BATT_RESET_SOC: + /* Do NOT reset fuel gauge in charging mode */ +#if IS_ENABLED(CONFIG_USB_FACTORY_MODE) + if (battery->is_jig_on || battery->batt_f_mode != NO_MODE) { +#else + if (battery->is_jig_on) { +#endif + sec_bat_set_misc_event(battery, BATT_MISC_EVENT_BATT_RESET_SOC, BATT_MISC_EVENT_BATT_RESET_SOC); + + value.intval = + SEC_FUELGAUGE_CAPACITY_TYPE_RESET; + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_PROP_CAPACITY, value); + dev_info(battery->dev,"do reset SOC\n"); + /* update battery info */ + sec_bat_get_battery_info(battery); + } + ret = count; + break; + case BATT_READ_RAW_SOC: + break; + case BATT_READ_ADJ_SOC: + break; + case BATT_TYPE: + strncpy(battery->batt_type, buf, sizeof(battery->batt_type) - 1); + battery->batt_type[sizeof(battery->batt_type)-1] = '\0'; + ret = count; + break; + case BATT_VFOCV: + break; + case BATT_VOL_ADC: + break; + case BATT_VOL_ADC_CAL: + break; + case BATT_VOL_AVER: + break; + case BATT_VOL_ADC_AVER: + break; + case BATT_VOLTAGE_NOW: + break; + case BATT_CURRENT_UA_NOW: + break; + case BATT_CURRENT_UA_AVG: + break; + case BATT_FILTER_CFG: + if (sscanf(buf, "%10d\n", &x) == 1) { + value.intval = x; + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_EXT_PROP_FILTER_CFG, value); + ret = count; + } + break; + case BATT_TEMP: +#if defined(CONFIG_ENG_BATTERY_CONCEPT) || defined(CONFIG_SEC_FACTORY) + if (sscanf(buf, "%10d\n", &x) == 1) { + dev_info(battery->dev, "%s: skip check thermal mode %s\n", + __func__, (x ? "enable" : "disable")); + if (x == 0) + battery->skip_swelling = true; + else + battery->skip_swelling = false; + ret = count; + } +#endif + break; + case BATT_TEMP_RAW: + break; + case BATT_TEMP_ADC: + break; + case BATT_TEMP_AVER: + break; + case BATT_TEMP_ADC_AVER: + break; + case USB_TEMP: + break; + case USB_TEMP_ADC: + break; + case BATT_CHG_TEMP: + break; + case BATT_CHG_TEMP_ADC: + break; + case SUB_BAT_TEMP: + break; + case SUB_BAT_TEMP_ADC: + break; + case SUB_CHG_TEMP: + break; + case SUB_CHG_TEMP_ADC: + break; +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + case DCHG_ADC_MODE_CTRL: + if (sscanf(buf, "%10d\n", &x) == 1) { + dev_info(battery->dev, + "%s : direct charger adc mode cntl : %d\n", __func__, x); + value.intval = x; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_DIRECT_ADC_CTRL, value); + ret = count; + } + break; + case DCHG_TEMP: + case DCHG_TEMP_ADC: + case DCHG_READ_BATP_BATN: + break; +#endif + case BLKT_TEMP: + break; + case BLKT_TEMP_ADC: + break; + case BATT_VF_ADC: + break; + case BATT_SLATE_MODE: + if (sscanf(buf, "%10d\n", &x) == 1) { + if (x == SEC_SMART_SWITCH_SLATE) { + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_SLATE, SEC_BAT_CURRENT_EVENT_SLATE); + sec_vote(battery->chgen_vote, VOTER_SMART_SLATE, true, SEC_BAT_CHG_MODE_BUCK_OFF); + sec_bat_set_mfc_off(battery, WPC_EN_SLATE, false); +#if IS_ENABLED(CONFIG_USB_FACTORY_MODE) && defined(CONFIG_SEC_FACTORY) + battery->usb_factory_slate_mode = true; +#endif + dev_info(battery->dev, + "%s: enable smart switch slate mode : %d\n", __func__, x); + } else if (x == SEC_SLATE_MODE) { + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_SLATE, SEC_BAT_CURRENT_EVENT_SLATE); + sec_vote(battery->chgen_vote, VOTER_SLATE, true, SEC_BAT_CHG_MODE_BUCK_OFF); + dev_info(battery->dev, + "%s: enable slate mode : %d\n", __func__, x); + } else if (x == SEC_SLATE_OFF) { + sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_SLATE); + sec_vote(battery->chgen_vote, VOTER_SLATE, false, 0); + sec_vote(battery->chgen_vote, VOTER_SMART_SLATE, false, 0); + sec_bat_set_mfc_on(battery, WPC_EN_SLATE); + /* recover smart switch src cap max current to 500mA */ + sec_bat_smart_sw_src(battery, false, 500); + dev_info(battery->dev, + "%s: disable slate mode : %d\n", __func__, x); + } else if (x == SEC_SMART_SWITCH_SRC) { + /* reduce smart switch src cap max current */ + sec_bat_smart_sw_src(battery, true, 0); + } else { + dev_info(battery->dev, + "%s: SLATE MODE unknown command\n", __func__); + return -EINVAL; + } + __pm_stay_awake(battery->cable_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->cable_work, 0); + ret = count; + } + break; + case BATT_LP_CHARGING: + break; + case SIOP_ACTIVATED: + break; + case SIOP_LEVEL: + if (sscanf(buf, "%10d\n", &x) == 1) { + dev_info(battery->dev, + "%s: siop level: %d\n", __func__, x); + + battery->wc_heating_start_time = 0; + if (x == battery->siop_level) { + dev_info(battery->dev, + "%s: skip same siop level: %d\n", __func__, x); + return count; + } else if (x >= 0 && x <= 100 && battery->pdata->bat_thm_info.check_type) { + battery->siop_level = x; + if (battery->siop_level == 0) + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_SIOP_LIMIT, + SEC_BAT_CURRENT_EVENT_SIOP_LIMIT); + else + sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_SIOP_LIMIT); + } else { + battery->siop_level = 100; + } + + __pm_stay_awake(battery->siop_level_ws); +#if !defined(CONFIG_SEC_FACTORY) + if (battery->siop_level == 100) + if (!is_nocharge_type(battery->cable_type)) + sec_bat_check_temp_ctrl_by_cable(battery); +#endif + queue_delayed_work(battery->monitor_wqueue, &battery->siop_level_work, 0); + + ret = count; + } + break; + case SIOP_EVENT: + if (sscanf(buf, "%10d\n", &x) == 1) { + ret = count; + } + break; + case BATT_CHARGING_SOURCE: + break; + case FG_REG_DUMP: + break; + case FG_RESET_CAP: + break; + case FG_CAPACITY: + break; + case FG_ASOC: + if (sscanf(buf, "%d\n", &x) == 1) { + if (x >= 0 && x <= 100) { + dev_info(battery->dev, "%s: batt_asoc(%d)\n", __func__, x); +#if IS_ENABLED(CONFIG_SEC_ABC) && !defined(CONFIG_SEC_FACTORY) + if (!x) + sec_abc_send_event("MODULE=battery@WARN=store_fg_asoc0"); +#endif + battery->batt_asoc = x; + battery->cisd.data[CISD_DATA_ASOC] = x; + sec_bat_check_battery_health(battery); + } + ret = count; + } + break; + case AUTH: + break; + case CHG_CURRENT_ADC: + break; + case WC_ADC: + break; + case WC_STATUS: + break; + case WC_ENABLE: + if (sscanf(buf, "%10d\n", &x) == 1) { + if (x == 0) { + mutex_lock(&battery->wclock); + battery->wc_enable = false; + battery->wc_enable_cnt = 0; + mutex_unlock(&battery->wclock); + } else if (x == 1) { + mutex_lock(&battery->wclock); + battery->wc_enable = true; + battery->wc_enable_cnt = 0; + mutex_unlock(&battery->wclock); + } else { + dev_info(battery->dev, + "%s: WPC ENABLE unknown command\n", + __func__); + return -EINVAL; + } + __pm_stay_awake(battery->cable_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->cable_work, 0); + ret = count; + } + break; + case WC_CONTROL: + if (sscanf(buf, "%10d\n", &x) == 1) { + char wpc_en_status[2]; + + wpc_en_status[0] = WPC_EN_SYSFS; + if (x == 0) { + mutex_lock(&battery->wclock); + battery->wc_enable = false; + battery->wc_enable_cnt = 0; + value.intval = 0; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WC_CONTROL, value); + + wpc_en_status[1] = false; + value.strval= wpc_en_status; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WPC_EN, value); + pr_info("@DIS_MFC %s: WC CONTROL: Disable\n", __func__); + mutex_unlock(&battery->wclock); + } else if (x == 1) { + mutex_lock(&battery->wclock); + battery->wc_enable = true; + battery->wc_enable_cnt = 0; + value.intval = 1; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WC_CONTROL, value); + wpc_en_status[1] = true; + value.strval= wpc_en_status; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WPC_EN, value); + + pr_info("@DIS_MFC %s: WC CONTROL: Enable\n", __func__); + mutex_unlock(&battery->wclock); + } else { + dev_info(battery->dev, + "%s: WC CONTROL unknown command\n", + __func__); + return -EINVAL; + } + ret = count; + } + break; + case WC_CONTROL_CNT: + if (sscanf(buf, "%10d\n", &x) == 1) { + battery->wc_enable_cnt_value = x; + ret = count; + } + break; + case LED_COVER: + if (sscanf(buf, "%10d\n", &x) == 1) { + pr_info("%s: MFC, LED_COVER(%d)\n", __func__, x); + battery->led_cover = x; + value.intval = battery->led_cover; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_FILTER_CFG, value); + ret = count; + } + break; + case HV_CHARGER_STATUS: + break; + case HV_WC_CHARGER_STATUS: + break; + case HV_CHARGER_SET: + if (sscanf(buf, "%10d\n", &x) == 1) { + dev_info(battery->dev, + "%s: HV_CHARGER_SET(%d)\n", __func__, x); + if (x == 1) { + battery->wire_status = SEC_BATTERY_CABLE_9V_TA; + __pm_stay_awake(battery->cable_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->cable_work, 0); + } else { + battery->wire_status = SEC_BATTERY_CABLE_NONE; + __pm_stay_awake(battery->cable_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->cable_work, 0); + } + ret = count; + } + break; + case FACTORY_MODE: + if (sscanf(buf, "%10d\n", &x) == 1) { + battery->factory_mode = x ? true : false; + ret = count; + } + break; + case STORE_MODE: + if (sscanf(buf, "%10d\n", &x) == 1) { +#if !defined(CONFIG_SEC_FACTORY) + if (x) { + battery->store_mode = true; + __pm_stay_awake(battery->parse_mode_dt_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->parse_mode_dt_work, 0); +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + direct_charging_source_status[0] = SEC_STORE_MODE; + direct_charging_source_status[1] = SEC_CHARGING_SOURCE_SWITCHING; + value.strval = direct_charging_source_status; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_CHANGE_CHARGING_SOURCE, value); +#endif + } +#endif + ret = count; + } + break; + case UPDATE: + if (sscanf(buf, "%10d\n", &x) == 1) { + /* update battery info */ + sec_bat_get_battery_info(battery); + ret = count; + } + break; + case TEST_MODE: + if (sscanf(buf, "%10d\n", &x) == 1) { + battery->test_mode = x; + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->monitor_work, 0); + ret = count; + } + break; + + case BATT_EVENT_CALL: + case BATT_EVENT_2G_CALL: + case BATT_EVENT_TALK_GSM: + case BATT_EVENT_3G_CALL: + case BATT_EVENT_TALK_WCDMA: + if (sscanf(buf, "%10d\n", &x) == 1) { +#if defined(CONFIG_LIMIT_CHARGING_DURING_CALL) + if (x) + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_CALL, SEC_BAT_CURRENT_EVENT_CALL); + else + sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_CALL); + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); +#endif + ret = count; + } + break; + case BATT_EVENT_MUSIC: + if (sscanf(buf, "%10d\n", &x) == 1) { + ret = count; + } + break; + case BATT_EVENT_VIDEO: + if (sscanf(buf, "%10d\n", &x) == 1) { + ret = count; + } + break; + case BATT_EVENT_BROWSER: + if (sscanf(buf, "%10d\n", &x) == 1) { + ret = count; + } + break; + case BATT_EVENT_HOTSPOT: + if (sscanf(buf, "%10d\n", &x) == 1) { + ret = count; + } + break; + case BATT_EVENT_CAMERA: + if (sscanf(buf, "%10d\n", &x) == 1) { + ret = count; + } + break; + case BATT_EVENT_CAMCORDER: + if (sscanf(buf, "%10d\n", &x) == 1) { + ret = count; + } + break; + case BATT_EVENT_DATA_CALL: + if (sscanf(buf, "%10d\n", &x) == 1) { + ret = count; + } + break; + case BATT_EVENT_WIFI: + if (sscanf(buf, "%10d\n", &x) == 1) { + ret = count; + } + break; + case BATT_EVENT_WIBRO: + if (sscanf(buf, "%10d\n", &x) == 1) { + ret = count; + } + break; + case BATT_EVENT_LTE: + if (sscanf(buf, "%10d\n", &x) == 1) { + ret = count; + } + break; + case BATT_EVENT_LCD: + if (sscanf(buf, "%10d\n", &x) == 1) { +#if !defined(CONFIG_SEC_FACTORY) + struct timespec64 ts; + ts = ktime_to_timespec64(ktime_get_boottime()); + if (x) { + battery->lcd_status = true; + } else { + battery->lcd_status = false; + } + pr_info("%s : lcd_status (%d)\n", __func__, battery->lcd_status); + + if (battery->wc_tx_enable || battery->pdata->wpc_vout_ctrl_lcd_on || + (battery->d2d_auth == D2D_AUTH_SRC)) { + battery->polling_short = false; + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->monitor_work, 0); + } +#endif + ret = count; + } + break; +#if defined(CONFIG_ISDB_CHARGING_CONTROL) + case BATT_EVENT_ISDB: + if (sscanf(buf, "%10d\n", &x) == 1) { + dev_info(battery->dev, + "%s: ISDB EVENT %d\n", __func__, x); + if (x) { + pr_info("%s: ISDB ON\n", __func__); + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_ISDB, + SEC_BAT_CURRENT_EVENT_ISDB); + if (is_hv_wireless_type(battery->cable_type)) { + pr_info("%s: set vout 5.5V with ISDB\n", __func__); + value.intval = WIRELESS_VOUT_5_5V_STEP; // 5.5V + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_INPUT_VOLTAGE_REGULATION, value); + sec_bat_set_charging_current(battery); + } else if (is_hv_wire_type(battery->cable_type) || + (is_pd_wire_type(battery->cable_type) && + battery->pd_max_charge_power >= HV_CHARGER_STATUS_STANDARD1 && + battery->pdic_info.sink_status.available_pdo_num > 1) || + battery->max_charge_power >= HV_CHARGER_STATUS_STANDARD1) + sec_bat_set_charging_current(battery); + } else { + pr_info("%s: ISDB OFF\n", __func__); + sec_bat_set_current_event(battery, 0, + SEC_BAT_CURRENT_EVENT_ISDB); + if (is_hv_wireless_type(battery->cable_type)) { + pr_info("%s: recover vout 10V with ISDB\n", __func__); + value.intval = WIRELESS_VOUT_10V; // 10V + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_INPUT_VOLTAGE_REGULATION, value); + sec_bat_set_charging_current(battery); + } else if (is_hv_wire_type(battery->cable_type)) + sec_bat_set_charging_current(battery); + } + ret = count; + } + break; +#endif + case BATT_EVENT_GPS: + if (sscanf(buf, "%10d\n", &x) == 1) { + ret = count; + } + break; + case BATT_HIGH_CURRENT_USB: + if (sscanf(buf, "%10d\n", &x) == 1) { + battery->is_hc_usb = x ? true : false; + value.intval = battery->is_hc_usb; + + pr_info("%s: is_hc_usb (%d)\n", __func__, battery->is_hc_usb); + ret = count; + } + break; +#if defined(CONFIG_ENG_BATTERY_CONCEPT) + case TEST_CHARGE_CURRENT: + if (sscanf(buf, "%10d\n", &x) == 1) { + if (x >= 0 && x <= 2000) { + dev_err(battery->dev, + "%s: BATT_TEST_CHARGE_CURRENT(%d)\n", __func__, x); + battery->pdata->charging_current[ + SEC_BATTERY_CABLE_USB].input_current_limit = x; + battery->pdata->charging_current[ + SEC_BATTERY_CABLE_USB].fast_charging_current = x; + if (x > 500) { + battery->eng_not_full_status = true; + battery->pdata->bat_thm_info.check_type = + SEC_BATTERY_TEMP_CHECK_NONE; + } + if (battery->cable_type == SEC_BATTERY_CABLE_USB) { + value.intval = x; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + value); + } + } + ret = count; + } + break; +#if defined(CONFIG_STEP_CHARGING) + case TEST_STEP_CONDITION: + if (sscanf(buf, "%10d\n", &x) == 1) { + if (x >= 0 && x <= 100) { + dev_err(battery->dev, + "%s: TEST_STEP_CONDITION(%d)\n", __func__, x); + battery->test_step_condition = x; + } + ret = count; + } + break; +#endif +#endif + case SET_STABILITY_TEST: + if (sscanf(buf, "%10d\n", &x) == 1) { + dev_err(battery->dev, + "%s: BATT_STABILITY_TEST(%d)\n", __func__, x); + if (x) { + battery->stability_test = true; + battery->eng_not_full_status = true; + } + else { + battery->stability_test = false; + battery->eng_not_full_status = false; + } + ret = count; + } + break; + case BATT_CAPACITY_MAX: + if (sscanf(buf, "%10d\n", &x) == 1) { + dev_err(battery->dev, + "%s: BATT_CAPACITY_MAX(%d), fg_reset(%d)\n", __func__, x, sec_bat_get_fgreset()); + if (!sec_bat_get_fgreset() && !battery->store_mode) { + value.intval = x; + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, value); + + /* update soc */ + value.intval = 0; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CAPACITY, value); + battery->capacity = value.intval; + } else { +#if !defined(CONFIG_SAMSUNG_PRODUCT_SHIP) + battery->fg_reset = 1; +#endif + } + ret = count; + } + break; + case BATT_REPCAP_1ST: + if ((sscanf(buf, "%10d\n", &x) == 1) && (battery->pdata->soc_by_repcap_en)) { + dev_info(battery->dev, + "%s: BATT_REPCAP(%d), fg_reset(%d)\n", __func__, x, sec_bat_get_fgreset()); + /* Maximum value check should be added in FG driver file */ + if (!sec_bat_get_fgreset() && !battery->store_mode && x >= 0) { + value.intval = x; + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_EXT_PROP_CHARGE_FULL_REPCAP, value); + + /* update soc */ + value.intval = 0; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CAPACITY, value); + battery->capacity = value.intval; + } else { +#if !defined(CONFIG_SAMSUNG_PRODUCT_SHIP) + battery->fg_reset = 1; +#endif + } + ret = count; + } + break; + case BATT_INBAT_VOLTAGE: + break; + case BATT_INBAT_VOLTAGE_OCV: + break; + case BATT_VBYP_VOLTAGE: + break; + case CHECK_SUB_CHG: + break; + case BATT_INBAT_WIRELESS_CS100: + if (sscanf(buf, "%10d\n", &x) == 1) { + pr_info("%s send cs100 command\n", __func__); + value.intval = POWER_SUPPLY_STATUS_FULL; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_PROP_STATUS, value); + ret = count; + } + break; + case HMT_TA_CONNECTED: + if (sscanf(buf, "%10d\n", &x) == 1) { +#if !IS_ENABLED(CONFIG_PDIC_NOTIFIER) && !IS_ENABLED(CONFIG_CCIC_NOTIFIER) + dev_info(battery->dev, + "%s: HMT_TA_CONNECTED(%d)\n", __func__, x); + if (x) { + value.intval = false; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL, + value); + dev_info(battery->dev, + "%s: changed to OTG cable detached\n", __func__); + + battery->wire_status = SEC_BATTERY_CABLE_HMT_CONNECTED; + __pm_stay_awake(battery->cable_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->cable_work, 0); + } else { + value.intval = true; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL, + value); + dev_info(battery->dev, + "%s: changed to OTG cable attached\n", __func__); + + battery->wire_status = SEC_BATTERY_CABLE_OTG; + __pm_stay_awake(battery->cable_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->cable_work, 0); + } +#endif + ret = count; + } + break; + case HMT_TA_CHARGE: + if (sscanf(buf, "%10d\n", &x) == 1) { +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) || IS_ENABLED(CONFIG_CCIC_NOTIFIER) + dev_info(battery->dev, + "%s: HMT_TA_CHARGE(%d)\n", __func__, x); + + /* do not charge off without cable type, since wdt could be expired */ + if (x) { + sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_CHARGE_DISABLE); + sec_vote(battery->chgen_vote, VOTER_HMT, false, 0); + } else if (!x && !is_nocharge_type(battery->cable_type)) { + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_CHARGE_DISABLE, + SEC_BAT_CURRENT_EVENT_CHARGE_DISABLE); + sec_vote(battery->chgen_vote, VOTER_HMT, true, SEC_BAT_CHG_MODE_CHARGING_OFF); + } else + dev_info(battery->dev, "%s: Wrong HMT control\n", __func__); + + ret = count; +#else + dev_info(battery->dev, + "%s: HMT_TA_CHARGE(%d)\n", __func__, x); + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL, value); + if (value.intval) { + dev_info(battery->dev, + "%s: ignore HMT_TA_CHARGE(%d)\n", __func__, x); + } else { + if (x) { + value.intval = false; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL, + value); + dev_info(battery->dev, + "%s: changed to OTG cable detached\n", __func__); + battery->wire_status = SEC_BATTERY_CABLE_HMT_CHARGE; + __pm_stay_awake(battery->cable_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->cable_work, 0); + } else { + value.intval = false; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL, + value); + dev_info(battery->dev, + "%s: changed to OTG cable detached\n", __func__); + battery->wire_status = SEC_BATTERY_CABLE_HMT_CONNECTED; + __pm_stay_awake(battery->cable_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->cable_work, 0); + } + } + ret = count; +#endif + } + break; +#if defined(CONFIG_SEC_FACTORY) + case AFC_TEST_FG_MODE: + if (sscanf(buf, "%10d\n", &x) == 1) { + value.intval = x; + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_EXT_PROP_AFC_TEST_FG_MODE, value); + ret = count; + } + break; +#endif + case FG_CYCLE: + break; + case FG_FULL_VOLTAGE: + break; + case FG_FULLCAPNOM: + break; +#if defined(CONFIG_BATTERY_AGE_FORECAST_DETACHABLE) + case BATT_AFTER_MANUFACTURED: +#else + case BATTERY_CYCLE: +#endif + if (sscanf(buf, "%10d %10d\n", &x, &y) > 0) { + dev_info(battery->dev, "%s: %s(%d), BATT_FULL_STATUS(%d)\n", __func__, + (offset == BATTERY_CYCLE) ? "BATTERY_CYCLE" : "BATTERY_CYCLE(W)", x, y); + if (x >= 0) { + int prev_battery_cycle = battery->batt_cycle; + battery->batt_cycle = x; + if(y >= 0) + battery->batt_full_status_usage = y; + battery->cisd.data[CISD_DATA_CYCLE] = x; + if (prev_battery_cycle < 0) { + sec_bat_aging_check(battery); + } + sec_bat_check_battery_health(battery); + } + ret = count; + } + break; + case BATTERY_CYCLE_TEST: + sec_bat_aging_check(battery); + break; + case BATT_WPC_TEMP: + break; + case BATT_WPC_TEMP_ADC: + break; +#if defined(CONFIG_WIRELESS_FIRMWARE_UPDATE) + case BATT_WIRELESS_FIRMWARE_UPDATE: + if (sscanf(buf, "%10d\n", &x) == 1) { + if (sec_bat_check_boost_mfc_condition(battery, x)) { + if (x == SEC_WIRELESS_FW_UPDATE_SDCARD_MODE) { + pr_info("%s fw mode is SDCARD\n", __func__); + sec_bat_fw_update(battery, x); + } else if (x == SEC_WIRELESS_FW_UPDATE_BUILTIN_MODE) { + pr_info("%s fw mode is BUILD IN\n", __func__); + sec_bat_fw_update(battery, x); + } else if (x == SEC_WIRELESS_FW_UPDATE_SPU_MODE) { + pr_info("%s fw mode is SPU\n", __func__); + sec_bat_fw_update(battery, x); + } else if (x == SEC_WIRELESS_FW_UPDATE_SPU_MODE) { + pr_info("%s fw mode is SPU VERIFY\n", __func__); + sec_bat_fw_update(battery, x); + } else { + dev_info(battery->dev, "%s: wireless firmware unknown command\n", __func__); + return -EINVAL; + } + } else + pr_info("%s: skip fw update at this time\n", __func__); + ret = count; + } + break; + case OTP_FIRMWARE_RESULT: + if (sscanf(buf, "%10d\n", &x) == 1) { + if (x == 2) { + value.intval = x; + pr_info("%s RX firmware update ready!\n", __func__); + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_PROP_MANUFACTURER, value); + } else { + dev_info(battery->dev, "%s: firmware unknown command\n", __func__); + return -EINVAL; + } + ret = count; + } + break; + case WC_IC_GRADE: + break; + case WC_IC_CHIP_ID: + break; + case OTP_FIRMWARE_VER_BIN: + break; + case OTP_FIRMWARE_VER: + break; +#endif + case WC_PHM_CTRL: + if (sscanf(buf, "%10d\n", &x) == 1) { + pr_info("%s : phm ctrl %d\n", __func__, x); + value.intval = x; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_RX_PHM, value); + ret = count; + } + break; + case WC_VOUT: + break; + case WC_VRECT: + break; + case WC_RX_CONNECTED: + break; + case WC_RX_CONNECTED_DEV: + break; + case WC_TX_MFC_VIN_FROM_UNO: + break; + case WC_TX_MFC_IIN_FROM_UNO: + break; +#if defined(CONFIG_WIRELESS_TX_MODE) + case WC_TX_AVG_CURR: + break; + case WC_TX_TOTAL_PWR: + break; +#endif + case WC_TX_STOP_CAPACITY: + if (sscanf(buf, "%10d\n", &x) == 1) { + pr_info("%s : tx stop capacity (%d)%%\n", __func__, x); + if (x >= 0 && x <= 100) + battery->pdata->tx_stop_capacity = x; + ret = count; + } + break; + case WC_TX_TIMER_EN: + if (sscanf(buf, "%10d\n", &x) == 1) { + pr_info("%s : tx receiver detecting timer (%d)%%\n", __func__, x); + value.intval = x; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_TIMER_ON, value); + ret = count; + } + break; + case WC_TX_EN: + if (sscanf(buf, "%10d\n", &x) == 1) { + if (battery->mfc_fw_update) { + pr_info("@Tx_Mode %s : skip Tx by mfc_fw_update\n", __func__); + return count; + } + +#if defined(CONFIG_WIRELESS_TX_MODE) + /* x value is written by ONEUI 2.5 PMS when tx_event is changed */ + if (x && is_wireless_all_type(battery->cable_type)) { + pr_info("@Tx_Mode %s : Can't enable Tx mode during wireless charging\n", __func__); + return count; + } else { + pr_info("@Tx_Mode %s: Set TX Enable (%d)\n", __func__, x); + sec_wireless_set_tx_enable(battery, x); + if (!x) { + /* clear tx all event */ + sec_bat_set_tx_event(battery, 0, BATT_TX_EVENT_WIRELESS_ALL_MASK); + } + if (x) + battery->cisd.tx_data[TX_ON]++; + } +#endif + ret = count; + } + break; + case WC_TX_VOUT: + if (sscanf(buf, "%10d\n", &x) == 1) { + pr_info("@Tx_Mode %s: Set TX Vout (%d)\n", __func__, x); + battery->wc_tx_vout = value.intval = x; + if (battery->wc_tx_enable) { + pr_info("@Tx_Mode %s: set TX Vout (%d)\n", __func__, value.intval); + psy_do_property("otg", set, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_VOUT, value); + } else { + pr_info("@Tx_Mode %s: TX mode turned off now\n", __func__); + } + ret = count; + } + break; + case BATT_HV_WIRELESS_STATUS: + if (sscanf(buf, "%10d\n", &x) == 1) { + if (x == 1 && is_hv_wireless_type(battery->cable_type)) { +#ifdef CONFIG_SEC_FACTORY + int input_current, charging_current; + pr_info("%s change cable type HV WIRELESS -> WIRELESS\n", __func__); + battery->wc_status = battery->cable_type = SEC_BATTERY_CABLE_WIRELESS; + input_current = battery->pdata->charging_current[battery->cable_type].input_current_limit; + charging_current = battery->pdata->charging_current[battery->cable_type].fast_charging_current; + sec_vote(battery->fcc_vote, VOTER_SLEEP_MODE, true, charging_current); + sec_vote(battery->input_vote, VOTER_SLEEP_MODE, true, input_current); +#endif + pr_info("%s HV_WIRELESS_STATUS set to 1. Vout set to 5V.\n", __func__); + value.intval = WIRELESS_VOUT_5V; + __pm_stay_awake(battery->cable_ws); + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_INPUT_VOLTAGE_REGULATION, value); + __pm_relax(battery->cable_ws); + } else if (x == 3 && is_hv_wireless_type(battery->cable_type)) { + pr_info("%s HV_WIRELESS_STATUS set to 3. Vout set to 10V.\n", __func__); + value.intval = WIRELESS_VOUT_10V; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_INPUT_VOLTAGE_REGULATION, value); + } else { + dev_info(battery->dev, "%s: HV_WIRELESS_STATUS unknown command\n", __func__); + return -EINVAL; + } + ret = count; + } + break; + case BATT_HV_WIRELESS_PAD_CTRL: + if (sscanf(buf, "%10d\n", &x) == 1) { + pr_err("%s: x : %d\n", __func__, x); + + if (x == 1) { + pr_info("%s: hv wireless charging is disabled\n", __func__); + battery->sleep_mode = true; + value.intval = battery->sleep_mode; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_SLEEP_MODE, value); + + if (is_hv_wireless_type(battery->cable_type)) + sec_vote(battery->input_vote, VOTER_SLEEP_MODE, true, battery->pdata->sleep_mode_limit_current); + } else if (x == 2) { + pr_info("%s: hv wireless charging is enabled\n", __func__); + battery->auto_mode = false; + battery->sleep_mode = false; + value.intval = battery->sleep_mode; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_SLEEP_MODE, value); + + value.intval = WIRELESS_SLEEP_MODE_DISABLE; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_RX_CONTROL, value); + + sec_vote(battery->input_vote, VOTER_SLEEP_MODE, false, 0); + } else if (x == 3) { + pr_info("%s led off\n", __func__); + value.intval = WIRELESS_PAD_LED_OFF; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_RX_CONTROL, value); + } else if (x == 4) { + pr_info("%s led on\n", __func__); + value.intval = WIRELESS_PAD_LED_ON; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_RX_CONTROL, value); + } else if ((x == 5) || (x == 6)) { + if (battery->pdata->wpc_vout_ctrl_lcd_on) { + battery->wpc_vout_ctrl_mode = (x == 5) ? true : false; + pr_info("%s: %s display flicker wa\n", + __func__, (x == 5) ? "enable" : "disable"); + } + battery->polling_short = false; + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->monitor_work, 0); + } else { + dev_info(battery->dev, "%s: BATT_HV_WIRELESS_PAD_CTRL unknown command\n", __func__); + return -EINVAL; + } + ret = count; + } + break; + case WC_TX_ID: + break; +#if defined(CONFIG_ENG_BATTERY_CONCEPT) + case BATT_TUNE_FLOAT_VOLTAGE: + sscanf(buf, "%10d\n", &x); + pr_info("%s float voltage = %d mV",__func__, x); + sec_vote(battery->fv_vote, VOTER_CABLE, true, x); + break; + case BATT_TUNE_INPUT_CHARGE_CURRENT: + sscanf(buf, "%10d\n", &x); + pr_info("%s input charge current = %d mA",__func__, x); + if (x >= 0 && x <= 4000 ){ + battery->test_max_current = true; + for (i = 0; i < SEC_BATTERY_CABLE_MAX; i++) { + if (i != SEC_BATTERY_CABLE_USB) + battery->pdata->charging_current[i].input_current_limit = x; + pr_info("%s [%d] = %d mA",__func__, i, battery->pdata->charging_current[i].input_current_limit); + } + + if (battery->cable_type != SEC_BATTERY_CABLE_USB) { + value.intval = x; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, value); + } + } + break; + case BATT_TUNE_FAST_CHARGE_CURRENT: + sscanf(buf, "%10d\n", &x); + pr_info("%s fast charge current = %d mA",__func__, x); + if (x >= 0 && x <= 4000 ){ + battery->test_charge_current = true; + for (i = 0; i < SEC_BATTERY_CABLE_MAX; i++) { + if (i != SEC_BATTERY_CABLE_USB) + battery->pdata->charging_current[i].fast_charging_current = x; + pr_info("%s [%d] = %d mA",__func__, i, battery->pdata->charging_current[i].fast_charging_current); + } + + if (battery->cable_type != SEC_BATTERY_CABLE_USB) { + value.intval = x; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, value); + } + } + break; + case BATT_TUNE_WIRELESS_VOUT_CURRENT: +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + { + int vout, input_current, offset; + + sscanf(buf, "%10d %10d\n", &offset, &input_current); + switch (offset) { + case 5500: + vout = WIRELESS_VOUT_5V; + break; + case 9000: + vout = WIRELESS_VOUT_9V; + break; + case 10000: + vout = WIRELESS_VOUT_10V; + break; + case 11000: + vout = WIRELESS_VOUT_11V; + break; + case 12000: + vout = WIRELESS_VOUT_12V; + break; + case 12500: + vout = WIRELESS_VOUT_12_5V; + break; + default: + pr_info("%s vout(%d) you entered is not supported\n", __func__, offset); + vout = 0; + break; + } + + pr_info("%s vout(%d, %d) input_current(%d)",__func__, offset, vout, input_current); + battery->wc20_vout = offset; + battery->wc20_rx_power = offset * input_current; + __pm_stay_awake(battery->wc20_current_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->wc20_current_work, + msecs_to_jiffies(0)); + } +#endif + break; + case BATT_TUNE_UI_TERM_CURRENT_1ST: + sscanf(buf, "%10d\n", &x); + pr_info("%s ui term current = %d mA",__func__, x); + + if (x > 0 && x < 1000 ){ + battery->pdata->full_check_current_1st = x; + } + break; + case BATT_TUNE_UI_TERM_CURRENT_2ND: + sscanf(buf, "%10d\n", &x); + pr_info("%s ui term current = %d mA",__func__, x); + + if (x > 0 && x < 1000 ){ + battery->pdata->full_check_current_2nd = x; + } + break; + case BATT_TUNE_CHG_TEMP_HIGH: + sscanf(buf, "%10d\n", &x); + pr_info("%s chg_high_temp = %d ",__func__, x); + if (x < 1000 && x >= -200) + battery->pdata->chg_high_temp = x; + break; + case BATT_TUNE_CHG_TEMP_REC: + sscanf(buf, "%10d\n", &x); + pr_info("%s chg_high_temp_recovery = %d ",__func__, x); + if (x < 1000 && x >= -200) + battery->pdata->chg_high_temp_recovery = x; + break; + case BATT_TUNE_CHG_LIMIT_CUR: + sscanf(buf, "%10d\n", &x); + pr_info("%s chg_charging_limit_current = %d ",__func__, x); + if (x < 3000 && x > 0) + { + battery->pdata->chg_charging_limit_current = x; + battery->pdata->charging_current[SEC_BATTERY_CABLE_9V_ERR].input_current_limit= x; + battery->pdata->charging_current[SEC_BATTERY_CABLE_9V_UNKNOWN].input_current_limit= x; + battery->pdata->charging_current[SEC_BATTERY_CABLE_9V_TA].input_current_limit= x; + } + break; + case BATT_TUNE_LRP_TEMP_HIGH_LCDON: + { + int lrp_m = 0, lrp_t[4] = {0, }; + int lrp_pt = LRP_NORMAL; + + if (sscanf(buf, "%10d %10d %10d %10d %10d\n", + &lrp_m, &lrp_t[0], &lrp_t[1], &lrp_t[2], &lrp_t[3]) == 5) { + pr_info("%s : lrp_high_temp_lcd on lrp_m: %c, temp: %d %d %d %d\n", + __func__, lrp_m, lrp_t[0], lrp_t[1], lrp_t[2], lrp_t[3]); + + if (lrp_m == 45) + lrp_pt = LRP_45W; + else if (lrp_m == 25) + lrp_pt = LRP_25W; + + if (x < 1000 && x >= -200) { + battery->pdata->lrp_temp[lrp_pt].trig[ST2][LCD_ON] = lrp_t[0]; + battery->pdata->lrp_temp[lrp_pt].recov[ST2][LCD_ON] = lrp_t[1]; + battery->pdata->lrp_temp[lrp_pt].trig[ST1][LCD_ON] = lrp_t[2]; + battery->pdata->lrp_temp[lrp_pt].recov[ST1][LCD_ON] = lrp_t[3]; + } + } + break; + } + case BATT_TUNE_LRP_TEMP_HIGH_LCDOFF: + { + int lrp_m = 0, lrp_t[4] = {0, }; + int lrp_pt = LRP_NORMAL; + + if (sscanf(buf, "%10d %10d %10d %10d %10d\n", + &lrp_m, &lrp_t[0], &lrp_t[1], &lrp_t[2], &lrp_t[3]) == 5) { + pr_info("%s : lrp_high_temp_lcd off lrp_m: %dW, temp: %d %d %d %d\n", + __func__, lrp_m, lrp_t[0], lrp_t[1], lrp_t[2], lrp_t[3]); + + if (lrp_m == 45) + lrp_pt = LRP_45W; + else if (lrp_m == 25) + lrp_pt = LRP_25W; + + if (x < 1000 && x >= -200) { + battery->pdata->lrp_temp[lrp_pt].trig[ST2][LCD_OFF] = lrp_t[0]; + battery->pdata->lrp_temp[lrp_pt].recov[ST2][LCD_OFF] = lrp_t[1]; + battery->pdata->lrp_temp[lrp_pt].trig[ST1][LCD_OFF] = lrp_t[2]; + battery->pdata->lrp_temp[lrp_pt].recov[ST1][LCD_OFF] = lrp_t[3]; + } + } + break; + } + case BATT_TUNE_COIL_TEMP_HIGH: + break; + case BATT_TUNE_COIL_TEMP_REC: + break; + case BATT_TUNE_COIL_LIMIT_CUR: + sscanf(buf, "%10d\n", &x); + pr_info("%s wpc_input_limit_current = %d ",__func__, x); + if (x < 3000 && x > 0) + { + battery->pdata->charging_current[SEC_BATTERY_CABLE_9V_ERR].input_current_limit= x; + battery->pdata->charging_current[SEC_BATTERY_CABLE_9V_UNKNOWN].input_current_limit= x; + battery->pdata->charging_current[SEC_BATTERY_CABLE_9V_TA].input_current_limit= x; + } + break; + case BATT_TUNE_WPC_TEMP_HIGH: + sscanf(buf, "%10d\n", &x); + pr_info("%s wpc_high_temp = %d ",__func__, x); + battery->pdata->wpc_high_temp = x; + break; + case BATT_TUNE_WPC_TEMP_HIGH_REC: + sscanf(buf, "%10d\n", &x); + pr_info("%s wpc_high_temp_recovery = %d ",__func__, x); + battery->pdata->wpc_high_temp_recovery = x; + break; + case BATT_TUNE_DCHG_TEMP_HIGH: + { + int dchg_t[4] = {0, }; + + sscanf(buf, "%10d %10d %10d %10d\n", + &dchg_t[0], &dchg_t[1], &dchg_t[2], &dchg_t[3]); + pr_info("%s dchg_high_temp = %d %d %d %d", __func__, + dchg_t[0], dchg_t[1], dchg_t[2], dchg_t[3]); + for (i = 0; i < 4; i++) { + battery->pdata->dchg_high_temp[i] = dchg_t[i]; + } + break; + } + case BATT_TUNE_DCHG_TEMP_HIGH_REC: + { + int dchg_t[4] = {0, }; + + sscanf(buf, "%10d %10d %10d %10d\n", + &dchg_t[0], &dchg_t[1], &dchg_t[2], &dchg_t[3]); + pr_info("%s dchg_high_temp_recovery = %d %d %d %d", __func__, + dchg_t[0], dchg_t[1], dchg_t[2], dchg_t[3]); + for (i = 0; i < 4; i++) { + battery->pdata->dchg_high_temp_recovery[i] = dchg_t[i]; + } + break; + } + case BATT_TUNE_DCHG_BATT_TEMP_HIGH: + { + int dchg_t[4] = {0, }; + + sscanf(buf, "%10d %10d %10d %10d\n", + &dchg_t[0], &dchg_t[1], &dchg_t[2], &dchg_t[3]); + pr_info("%s dchg_high_batt_temp = %d %d %d %d", __func__, + dchg_t[0], dchg_t[1], dchg_t[2], dchg_t[3]); + for (i = 0; i < 4; i++) { + battery->pdata->dchg_high_batt_temp[i] = dchg_t[i]; + } + break; + } + case BATT_TUNE_DCHG_BATT_TEMP_HIGH_REC: + { + int dchg_t[4] = {0, }; + + sscanf(buf, "%10d %10d %10d %10d\n", + &dchg_t[0], &dchg_t[1], &dchg_t[2], &dchg_t[3]); + pr_info("%s dchg_high_batt_temp_recovery = %d %d %d %d", __func__, + dchg_t[0], dchg_t[1], dchg_t[2], dchg_t[3]); + for (i = 0; i < 4; i++) { + battery->pdata->dchg_high_batt_temp_recovery[i] = dchg_t[i]; + } + break; + } + case BATT_TUNE_DCHG_LIMIT_INPUT_CUR: + sscanf(buf, "%10d\n", &x); + pr_info("%s dchg_input_limit_current = %d ",__func__, x); + battery->pdata->dchg_input_limit_current = x; + break; + case BATT_TUNE_DCHG_LIMIT_CHG_CUR: + sscanf(buf, "%10d\n", &x); + pr_info("%s dchg_charging_limit_current = %d ",__func__, x); + battery->pdata->dchg_charging_limit_current = x; + break; +#if defined(CONFIG_WIRELESS_TX_MODE) + case BATT_TUNE_TX_MFC_IOUT_GEAR: + sscanf(buf, "%10d\n", &x); + pr_info("%s tx_mfc_iout_gear = %d", __func__, x); + battery->pdata->tx_mfc_iout_gear = x; + break; + case BATT_TUNE_TX_MFC_IOUT_PHONE: + sscanf(buf, "%10d\n", &x); + pr_info("%s tx_mfc_iout_phone = %d", __func__, x); + battery->pdata->tx_mfc_iout_phone = x; + break; +#endif +#endif +#if defined(CONFIG_UPDATE_BATTERY_DATA) + case BATT_UPDATE_DATA: + if (!battery->data_path && (count * sizeof(char)) < 256) { + battery->data_path = kzalloc((count * sizeof(char) + 1), GFP_KERNEL); + if (battery->data_path) { + sscanf(buf, "%s\n", battery->data_path); + cancel_delayed_work(&battery->batt_data_work); + __pm_stay_awake(battery->batt_data_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->batt_data_work, msecs_to_jiffies(100)); + } else { + pr_info("%s: failed to alloc data_path buffer\n", __func__); + } + } + ret = count; + break; +#endif + case BATT_MISC_EVENT: + if (sscanf(buf, "%10d\n", &x) == 1) { + pr_info("%s: PMS sevice hiccup read done : %d ", __func__, x); + if (battery->misc_event & + (BATT_MISC_EVENT_WATER_HICCUP_TYPE | + BATT_MISC_EVENT_TEMP_HICCUP_TYPE)) { + if (!battery->hiccup_status) { + sec_bat_set_misc_event(battery, + 0, (BATT_MISC_EVENT_WATER_HICCUP_TYPE | + BATT_MISC_EVENT_TEMP_HICCUP_TYPE)); + } else { + battery->hiccup_clear = true; + pr_info("%s : Hiccup event doesn't clear. Hiccup clear bit set (%d)\n", + __func__, battery->hiccup_clear); + } + } + } + ret = count; + break; + case BATT_EXT_DEV_CHG: + if (sscanf(buf, "%10d\n", &x) == 1) { + pr_info("%s: Connect Ext Device : %d ",__func__, x); + + switch (x) { + case EXT_DEV_NONE: + battery->wire_status = SEC_BATTERY_CABLE_NONE; + value.intval = 0; + break; + case EXT_DEV_GAMEPAD_CHG: + battery->wire_status = SEC_BATTERY_CABLE_TA; + value.intval = 0; + break; + case EXT_DEV_GAMEPAD_OTG: + battery->wire_status = SEC_BATTERY_CABLE_OTG; + value.intval = 1; + break; + default: + break; + } + + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL, + value); + + queue_delayed_work(battery->monitor_wqueue, + &battery->cable_work, 0); + ret = count; + } + break; + case BATT_WDT_CONTROL: + if (sscanf(buf, "%10d\n", &x) == 1) { + pr_info("%s: Charger WDT Set : %d\n", __func__, x); + battery->wdt_kick_disable = x; +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + value.intval = x; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_DIRECT_WDT_CONTROL, value); +#endif + } + ret = count; + break; + case MODE: + if (sscanf(buf, "%10d\n", &x) == 1) { + value.intval = x; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_MULTI_CHARGER_MODE, value); + ret = count; + } + break; + case CHECK_PS_READY: + break; + case BATT_CHIP_ID: + break; + case ERROR_CAUSE: + break; + case CISD_FULLCAPREP_MAX: + break; + case CISD_DATA: + { + struct cisd *pcisd = &battery->cisd; + int temp_data[CISD_DATA_MAX_PER_DAY] = {0,}; + + sscanf(buf, "%10d\n", &temp_data[0]); + + if (temp_data[CISD_DATA_RESET_ALG] > 0) { + if (sscanf(buf, "%10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d, %10d\n", + &temp_data[0], &temp_data[1], &temp_data[2], + &temp_data[3], &temp_data[4], &temp_data[5], + &temp_data[6], &temp_data[7], &temp_data[8], + &temp_data[9], &temp_data[10], &temp_data[11], + &temp_data[12], &temp_data[13], &temp_data[14], + &temp_data[15], &temp_data[16], &temp_data[17], + &temp_data[18], &temp_data[19], &temp_data[20], + &temp_data[21], &temp_data[22], &temp_data[23], + &temp_data[24], &temp_data[25], &temp_data[26], + &temp_data[27], &temp_data[28], &temp_data[29], + &temp_data[30], &temp_data[31], &temp_data[32], + &temp_data[33], &temp_data[34], &temp_data[35], + &temp_data[36], &temp_data[37], &temp_data[38], + &temp_data[39], &temp_data[40], &temp_data[41], + &temp_data[42], &temp_data[43], &temp_data[44], + &temp_data[45], &temp_data[46], &temp_data[47], + &temp_data[48], &temp_data[49], &temp_data[50], + &temp_data[51], &temp_data[52], &temp_data[53], + &temp_data[54], &temp_data[55], &temp_data[56], + &temp_data[57], &temp_data[58], &temp_data[59], + &temp_data[60], &temp_data[61], &temp_data[62], + &temp_data[63], &temp_data[64], &temp_data[65], + &temp_data[66], &temp_data[67], &temp_data[68], + &temp_data[69], &temp_data[70], &temp_data[71], + &temp_data[72], &temp_data[73], &temp_data[74], + &temp_data[75], &temp_data[76]) <= CISD_DATA_MAX_PER_DAY) { + for (i = 0; i < CISD_DATA_MAX_PER_DAY; i++) + pcisd->data[i] = 0; + + pcisd->data[CISD_DATA_ALG_INDEX] = battery->pdata->cisd_alg_index; + pcisd->data[CISD_DATA_FULL_COUNT] = temp_data[0]; + pcisd->data[CISD_DATA_CAP_MAX] = temp_data[1]; + pcisd->data[CISD_DATA_CAP_MIN] = temp_data[2]; + pcisd->data[CISD_DATA_VALERT_COUNT] = temp_data[16]; + pcisd->data[CISD_DATA_CYCLE] = temp_data[17]; + pcisd->data[CISD_DATA_WIRE_COUNT] = temp_data[18]; + pcisd->data[CISD_DATA_WIRELESS_COUNT] = temp_data[19]; + pcisd->data[CISD_DATA_HIGH_TEMP_SWELLING] = temp_data[20]; + pcisd->data[CISD_DATA_LOW_TEMP_SWELLING] = temp_data[21]; + pcisd->data[CISD_DATA_WC_HIGH_TEMP_SWELLING] = temp_data[22]; + pcisd->data[CISD_DATA_AICL_COUNT] = temp_data[26]; + pcisd->data[CISD_DATA_BATT_TEMP_MAX] = temp_data[27]; + pcisd->data[CISD_DATA_BATT_TEMP_MIN] = temp_data[28]; + pcisd->data[CISD_DATA_CHG_TEMP_MAX] = temp_data[29]; + pcisd->data[CISD_DATA_CHG_TEMP_MIN] = temp_data[30]; + pcisd->data[CISD_DATA_WPC_TEMP_MAX] = temp_data[31]; + pcisd->data[CISD_DATA_WPC_TEMP_MIN] = temp_data[32]; + pcisd->data[CISD_DATA_UNSAFETY_VOLTAGE] = temp_data[33]; + pcisd->data[CISD_DATA_UNSAFETY_TEMPERATURE] = temp_data[34]; + pcisd->data[CISD_DATA_SAFETY_TIMER] = temp_data[35]; + pcisd->data[CISD_DATA_VSYS_OVP] = temp_data[36]; + pcisd->data[CISD_DATA_VBAT_OVP] = temp_data[37]; + } + } else { + const char *p = buf; + + pr_info("%s: %s\n", __func__, buf); + for (i = CISD_DATA_RESET_ALG; i < CISD_DATA_MAX_PER_DAY; i++) { + if (sscanf(p, "%10d%n", &pcisd->data[i], &x) > 0) { + p += (size_t)x; + if (pcisd->data[CISD_DATA_ALG_INDEX] != battery->pdata->cisd_alg_index) { + pr_info("%s: ALG_INDEX is changed %d -> %d\n", __func__, + pcisd->data[CISD_DATA_ALG_INDEX], battery->pdata->cisd_alg_index); + temp_data[CISD_DATA_RESET_ALG] = -1; + break; + } + } else { + pr_info("%s: NO DATA (cisd_data)\n", __func__); + temp_data[CISD_DATA_RESET_ALG] = -1; + break; + } + } + + pr_info("%s: %s cisd data\n", __func__, + ((temp_data[CISD_DATA_RESET_ALG] < 0 || battery->fg_reset) ? + "init" : "update")); + + if (temp_data[CISD_DATA_RESET_ALG] < 0 || battery->fg_reset) { + /* initialize data */ + for (i = CISD_DATA_RESET_ALG; i < CISD_DATA_MAX_PER_DAY; i++) + pcisd->data[i] = 0; + + battery->fg_reset = 0; + + pcisd->data[CISD_DATA_ALG_INDEX] = battery->pdata->cisd_alg_index; + + pcisd->data[CISD_DATA_FULL_COUNT] = 1; + pcisd->data[CISD_DATA_BATT_TEMP_MAX] = -300; + pcisd->data[CISD_DATA_CHG_TEMP_MAX] = -300; + pcisd->data[CISD_DATA_WPC_TEMP_MAX] = -300; + pcisd->data[CISD_DATA_USB_TEMP_MAX] = -300; + pcisd->data[CISD_DATA_BATT_TEMP_MIN] = 1000; + pcisd->data[CISD_DATA_CHG_TEMP_MIN] = 1000; + pcisd->data[CISD_DATA_WPC_TEMP_MIN] = 1000; + pcisd->data[CISD_DATA_USB_TEMP_MIN] = 1000; + pcisd->data[CISD_DATA_CAP_MIN] = 0xFFFF; + + pcisd->data[CISD_DATA_FULL_COUNT_PER_DAY] = 1; + pcisd->data[CISD_DATA_BATT_TEMP_MAX_PER_DAY] = -300; + pcisd->data[CISD_DATA_SUB_BATT_TEMP_MAX_PER_DAY] = -300; + pcisd->data[CISD_DATA_CHG_TEMP_MAX_PER_DAY] = -300; + pcisd->data[CISD_DATA_USB_TEMP_MAX_PER_DAY] = -300; + pcisd->data[CISD_DATA_BATT_TEMP_MIN_PER_DAY] = 1000; + pcisd->data[CISD_DATA_SUB_BATT_TEMP_MIN_PER_DAY] = 1000; + pcisd->data[CISD_DATA_CHG_TEMP_MIN_PER_DAY] = 1000; + pcisd->data[CISD_DATA_USB_TEMP_MIN_PER_DAY] = 1000; + + pcisd->data[CISD_DATA_CHG_BATT_TEMP_MAX] = -300; + pcisd->data[CISD_DATA_CHG_CHG_TEMP_MAX] = -300; + pcisd->data[CISD_DATA_CHG_WPC_TEMP_MAX] = -300; + pcisd->data[CISD_DATA_CHG_USB_TEMP_MAX] = -300; + pcisd->data[CISD_DATA_CHG_BATT_TEMP_MIN] = 1000; + pcisd->data[CISD_DATA_CHG_CHG_TEMP_MIN] = 1000; + pcisd->data[CISD_DATA_CHG_WPC_TEMP_MIN] = 1000; + pcisd->data[CISD_DATA_CHG_USB_TEMP_MIN] = 1000; + + pcisd->data[CISD_DATA_CHG_BATT_TEMP_MAX_PER_DAY] = -300; + pcisd->data[CISD_DATA_CHG_SUB_BATT_TEMP_MAX_PER_DAY] = -300; + pcisd->data[CISD_DATA_CHG_CHG_TEMP_MAX_PER_DAY] = -300; + pcisd->data[CISD_DATA_CHG_USB_TEMP_MAX_PER_DAY] = -300; + pcisd->data[CISD_DATA_CHG_BATT_TEMP_MIN_PER_DAY] = 1000; + pcisd->data[CISD_DATA_CHG_SUB_BATT_TEMP_MIN_PER_DAY] = 1000; + pcisd->data[CISD_DATA_CHG_CHG_TEMP_MIN_PER_DAY] = 1000; + pcisd->data[CISD_DATA_CHG_USB_TEMP_MIN_PER_DAY] = 1000; + + pcisd->data[CISD_DATA_CAP_MIN_PER_DAY] = 0xFFFF; + pcisd->data[CISD_DATA_USB_OVERHEAT_RAPID_CHANGE] = 0; + pcisd->data[CISD_DATA_USB_OVERHEAT_RAPID_CHANGE_PER_DAY] = 0; + pcisd->data[CISD_DATA_USB_OVERHEAT_ALONE] = 0; + + /* initialize pad data */ + init_cisd_pad_data(&battery->cisd); + + /* initialize power data */ + init_cisd_power_data(&battery->cisd); + + /* initialize pd data */ + init_cisd_pd_data(&battery->cisd); + } + } + ret = count; + __pm_stay_awake(battery->monitor_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); + } + break; + case CISD_DATA_JSON: + { + char tc; + struct cisd *pcisd = &battery->cisd; + + if (sscanf(buf, "%1c\n", &tc) == 1) { + if (tc == 'c') { + for (i = 0; i < CISD_DATA_MAX; i++) + pcisd->data[i] = 0; + + pcisd->data[CISD_DATA_FULL_COUNT] = 1; + pcisd->data[CISD_DATA_BATT_TEMP_MAX] = -300; + pcisd->data[CISD_DATA_CHG_TEMP_MAX] = -300; + pcisd->data[CISD_DATA_WPC_TEMP_MAX] = -300; + pcisd->data[CISD_DATA_USB_TEMP_MAX] = -300; + pcisd->data[CISD_DATA_BATT_TEMP_MIN] = 1000; + pcisd->data[CISD_DATA_CHG_TEMP_MIN] = 1000; + pcisd->data[CISD_DATA_WPC_TEMP_MIN] = 1000; + pcisd->data[CISD_DATA_USB_TEMP_MIN] = 1000; + pcisd->data[CISD_DATA_CAP_MIN] = 0xFFFF; + + pcisd->data[CISD_DATA_FULL_COUNT_PER_DAY] = 1; + pcisd->data[CISD_DATA_BATT_TEMP_MAX_PER_DAY] = -300; + pcisd->data[CISD_DATA_SUB_BATT_TEMP_MAX_PER_DAY] = -300; + pcisd->data[CISD_DATA_CHG_TEMP_MAX_PER_DAY] = -300; + pcisd->data[CISD_DATA_USB_TEMP_MAX_PER_DAY] = -300; + pcisd->data[CISD_DATA_BATT_TEMP_MIN_PER_DAY] = 1000; + pcisd->data[CISD_DATA_SUB_BATT_TEMP_MIN_PER_DAY] = 1000; + pcisd->data[CISD_DATA_CHG_TEMP_MIN_PER_DAY] = 1000; + pcisd->data[CISD_DATA_USB_TEMP_MIN_PER_DAY] = 1000; + + pcisd->data[CISD_DATA_CHG_BATT_TEMP_MAX] = -300; + pcisd->data[CISD_DATA_CHG_CHG_TEMP_MAX] = -300; + pcisd->data[CISD_DATA_CHG_WPC_TEMP_MAX] = -300; + pcisd->data[CISD_DATA_CHG_USB_TEMP_MAX] = -300; + pcisd->data[CISD_DATA_CHG_BATT_TEMP_MIN] = 1000; + pcisd->data[CISD_DATA_CHG_CHG_TEMP_MIN] = 1000; + pcisd->data[CISD_DATA_CHG_WPC_TEMP_MIN] = 1000; + pcisd->data[CISD_DATA_CHG_USB_TEMP_MIN] = 1000; + + pcisd->data[CISD_DATA_CHG_BATT_TEMP_MAX_PER_DAY] = -300; + pcisd->data[CISD_DATA_CHG_SUB_BATT_TEMP_MAX_PER_DAY] = -300; + pcisd->data[CISD_DATA_CHG_CHG_TEMP_MAX_PER_DAY] = -300; + pcisd->data[CISD_DATA_CHG_USB_TEMP_MAX_PER_DAY] = -300; + pcisd->data[CISD_DATA_CHG_BATT_TEMP_MIN_PER_DAY] = 1000; + pcisd->data[CISD_DATA_CHG_SUB_BATT_TEMP_MIN_PER_DAY] = 1000; + pcisd->data[CISD_DATA_CHG_CHG_TEMP_MIN_PER_DAY] = 1000; + pcisd->data[CISD_DATA_CHG_USB_TEMP_MIN_PER_DAY] = 1000; + + pcisd->data[CISD_DATA_CAP_MIN_PER_DAY] = 0xFFFF; + pcisd->data[CISD_DATA_USB_OVERHEAT_RAPID_CHANGE] = 0; + pcisd->data[CISD_DATA_USB_OVERHEAT_RAPID_CHANGE_PER_DAY] = 0; + pcisd->data[CISD_DATA_USB_OVERHEAT_ALONE] = 0; + } + } + ret = count; + } + break; + case CISD_DATA_D_JSON: + break; + case CISD_WIRE_COUNT: + if (sscanf(buf, "%10d\n", &x) == 1) { + struct cisd *pcisd = &battery->cisd; + pr_info("%s: Wire Count : %d\n", __func__, x); + pcisd->data[CISD_DATA_WIRE_COUNT] = x; + pcisd->data[CISD_DATA_WIRE_COUNT_PER_DAY]++; + } + ret = count; + break; + case CISD_WC_DATA: + set_cisd_pad_data(battery, buf); + ret = count; + break; + case CISD_WC_DATA_JSON: + break; + case CISD_POWER_DATA: + set_cisd_power_data(battery, buf); + ret = count; + break; + case CISD_POWER_DATA_JSON: + break; + case CISD_PD_DATA: + set_cisd_pd_data(battery, buf); + ret = count; + break; + case CISD_PD_DATA_JSON: + break; + case CISD_CABLE_DATA: + { + struct cisd *pcisd = &battery->cisd; + const char *p = buf; + + pr_info("%s: %s\n", __func__, buf); + for (i = CISD_CABLE_TA; i < CISD_CABLE_TYPE_MAX; i++) { + if (sscanf(p, "%10d%n", &pcisd->cable_data[i], &x) > 0) { + p += (size_t)x; + } else { + pr_info("%s: NO DATA (CISD_CABLE_TYPE)\n", __func__); + pcisd->cable_data[i] = 0; + break; + } + } + } + ret = count; + break; + + case CISD_CABLE_DATA_JSON: + break; + case CISD_TX_DATA: + { + struct cisd *pcisd = &battery->cisd; + const char *p = buf; + + pr_info("%s: %s\n", __func__, buf); + for (i = TX_ON; i < TX_DATA_MAX; i++) { + if (sscanf(p, "%10d%n", &pcisd->tx_data[i], &x) > 0) { + p += (size_t)x; + } else { + pr_info("%s: NO DATA (CISD_TX_DATA)\n", __func__); + pcisd->tx_data[i] = 0; + break; + } + } + } + ret = count; + break; + case CISD_TX_DATA_JSON: + break; + case CISD_EVENT_DATA: + { + struct cisd *pcisd = &battery->cisd; + const char *p = buf; + + pr_info("%s: %s\n", __func__, buf); + for (i = EVENT_DC_ERR; i < EVENT_DATA_MAX; i++) { + if (sscanf(p, "%10d%n", &pcisd->event_data[i], &x) > 0) { + p += (size_t)x; + } else { + pr_info("%s: NO DATA (CISD_EVENT_DATA)\n", __func__); + pcisd->event_data[i] = 0; + break; + } + } + } + ret = count; + break; + case CISD_EVENT_DATA_JSON: + break; + case PREV_BATTERY_DATA: + if (sscanf(buf, "%10d, %10d, %10d, %10d\n", + &battery->prev_volt, &battery->prev_temp, + &battery->prev_jig_on, &battery->prev_chg_on) >= 4) { + pr_info("%s: prev voltage : %d, prev_temp : %d, prev_jig_on : %d, prev_chg_on : %d\n", + __func__, battery->prev_volt, battery->prev_temp, + battery->prev_jig_on, battery->prev_chg_on); + + if (battery->prev_volt >= 3700 && battery->prev_temp >= 150 && + !battery->prev_jig_on && battery->fg_reset) + pr_info("%s: Battery have been Removed\n", __func__); + + ret = count; + } + battery->enable_update_data = 1; + break; + case PREV_BATTERY_INFO: + break; + case SAFETY_TIMER_SET: + if (sscanf(buf, "%10d\n", &x) == 1) { + if (x) { + battery->safety_timer_set = true; + } else { + battery->safety_timer_set = false; + } + ret = count; + } + break; + case BATT_SWELLING_CONTROL: + if (sscanf(buf, "%10d\n", &x) == 1) { + if (x) { + pr_info("%s : 15TEST START!! SWELLING MODE DISABLE\n", __func__); + battery->skip_swelling = true; + } else { + pr_info("%s : 15TEST END!! SWELLING MODE END\n", __func__); + battery->skip_swelling = false; + } + ret = count; + } + break; + case BATT_BATTERY_ID: +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + case BATT_SUB_BATTERY_ID: +#endif + break; + case BATT_TEMP_CONTROL_TEST: + if (sscanf(buf, "%10d\n", &x) == 1) { + if (x) + sec_bat_set_temp_control_test(battery, true); + else + sec_bat_set_temp_control_test(battery, false); + + ret = count; + } + break; + case SAFETY_TIMER_INFO: + break; + case BATT_SHIPMODE_TEST: + if (sscanf(buf, "%10d\n", &x) == 1) { + pr_info("%s ship mode test %d\n", __func__, x); + value.intval = x; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_SHIPMODE_TEST, value); + ret = count; + } + break; + case BATT_MISC_TEST: + if (sscanf(buf, "%10d\n", &x) == 1) { + pr_info("%s batt_misc_test %d\n", __func__, x); + switch (x) { + case MISC_TEST_RESET: + pr_info("%s RESET MISC_TEST command\n", __func__); + battery->display_test = false; + battery->store_mode = false; + battery->skip_swelling = false; + battery->pdata->bat_thm_info.check_type = SEC_BATTERY_TEMP_CHECK_TEMP; + battery->pdata->usb_thm_info.check_type = SEC_BATTERY_TEMP_CHECK_TEMP; + battery->pdata->chg_thm_info.check_type = SEC_BATTERY_TEMP_CHECK_TEMP; + battery->pdata->wpc_thm_info.check_type = SEC_BATTERY_TEMP_CHECK_TEMP; + battery->pdata->sub_bat_thm_info.check_type = SEC_BATTERY_TEMP_CHECK_TEMP; + battery->pdata->blk_thm_info.check_type = SEC_BATTERY_TEMP_CHECK_TEMP; +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + battery->pdata->dchg_thm_info.check_type = SEC_BATTERY_TEMP_CHECK_TEMP; +#endif + break; + case MISC_TEST_DISPLAY: + pr_info("%s START DISPLAY_TEST command\n", __func__); + battery->display_test = true; /* block for display test */ + battery->store_mode = true; /* enter store mode for prevent 100% full charge */ + battery->skip_swelling = true; /* restore thermal_zone to NORMAL */ + battery->pdata->bat_thm_info.check_type = SEC_BATTERY_TEMP_CHECK_NONE; + battery->pdata->usb_thm_info.check_type = SEC_BATTERY_TEMP_CHECK_NONE; + battery->pdata->chg_thm_info.check_type = SEC_BATTERY_TEMP_CHECK_NONE; + battery->pdata->wpc_thm_info.check_type = SEC_BATTERY_TEMP_CHECK_NONE; + battery->pdata->sub_bat_thm_info.check_type = SEC_BATTERY_TEMP_CHECK_NONE; + battery->pdata->blk_thm_info.check_type = SEC_BATTERY_TEMP_CHECK_NONE; +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + battery->pdata->dchg_thm_info.check_type = SEC_BATTERY_TEMP_CHECK_NONE; +#endif + break; + case MISC_TEST_EPT_UNKNOWN: +#if defined(CONFIG_SEC_FACTORY) && IS_ENABLED(CONFIG_WIRELESS_CHARGING) + pr_info("%s START EPT_UNKNOWN command\n", __func__); + value.intval = 1; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WC_EPT_UNKNOWN, value); +#else + pr_info("%s not support EPT_UNKNOWN command\n", __func__); +#endif + break; + case MISC_TEST_MAX: + default: + pr_info("%s Wrong MISC_TEST command\n", __func__); + break; + } + ret = count; + } + break; + case BATT_TEMP_TEST: + { + char tc; + if (sscanf(buf, "%c %10d\n", &tc, &x) == 2) { + pr_info("%s : temperature t: %c, temp: %d\n", __func__, tc, x); + if (tc == 'u') { + if (x > 900) + battery->pdata->usb_thm_info.check_type = 0; + else + battery->pdata->usb_thm_info.test = x; + } else if (tc == 'w') { + if (x > 900) + battery->pdata->wpc_thm_info.check_type = 0; + else + battery->pdata->wpc_thm_info.test = x; + } else if (tc == 'b') { + if (x > 900) + battery->pdata->bat_thm_info.check_type = 0; + else + battery->pdata->bat_thm_info.test = x; + } else if (tc == 'c') { + if (x > 900) + battery->pdata->chg_thm_info.check_type = 0; + else + battery->pdata->chg_thm_info.test = x; + } else if (tc == 's') { + if (x > 900) + battery->pdata->sub_bat_thm_info.check_type = 0; + else + battery->pdata->sub_bat_thm_info.test = x; +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + } else if (tc == 'd') { + if (x > 900) + battery->pdata->dchg_thm_info.check_type = 0; + else + battery->pdata->dchg_thm_info.test = x; +#endif + } else if (tc == 'k') { + battery->pdata->blk_thm_info.test = x; + } else if (tc == 'r') { + battery->lrp_test = x; + battery->lrp = x; + } + ret = count; + } + break; + } + case BATT_CURRENT_EVENT: + break; + case BATT_JIG_GPIO: + break; + case CC_INFO: + break; +#if defined(CONFIG_WIRELESS_AUTH) + case WC_AUTH_ADT_SENT: + break; +#endif +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + case BATT_MAIN_VOLTAGE: + case BATT_SUB_VOLTAGE: + case BATT_MAIN_VCELL: + case BATT_SUB_VCELL: + case BATT_MAIN_CURRENT_MA: + case BATT_SUB_CURRENT_MA: + case BATT_MAIN_CON_DET: + case BATT_SUB_CON_DET: + break; +#if IS_ENABLED(CONFIG_LIMITER_S2ASL01) + case BATT_MAIN_VCHG: + case BATT_SUB_VCHG: + break; + case BATT_MAIN_ENB: /* Can control This pin with 523k jig only, high active pin because it is reversed */ + if (sscanf(buf, "%10d\n", &x) == 1) { + if (battery->pdata->main_bat_enb_gpio) { + pr_info("%s main battery enb = %d\n", __func__, x); + if (x == 0) { + union power_supply_propval value = {0, }; + /* activate main limiter */ + gpio_direction_output(battery->pdata->main_bat_enb_gpio, 1); + msleep(100); + value.intval = 1; + psy_do_property(battery->pdata->main_limiter_name, set, + POWER_SUPPLY_EXT_PROP_POWERMETER_ENABLE, value); + } else if (x == 1) { + /* deactivate main limiter */ + gpio_direction_output(battery->pdata->main_bat_enb_gpio, 0); + } + pr_info("%s main enb = %d, main enb2 = %d, sub enb = %d\n", + __func__, + gpio_get_value(battery->pdata->main_bat_enb_gpio), + gpio_get_value(battery->pdata->main_bat_enb2_gpio), + gpio_get_value(battery->pdata->sub_bat_enb_gpio)); + } + ret = count; + } + break; + case BATT_MAIN_ENB2: /* Low active pin */ + if (sscanf(buf, "%10d\n", &x) == 1) { + if (battery->pdata->main_bat_enb2_gpio) { + pr_info("%s main battery enb2 = %d\n", __func__, x); + if (x == 0) { + union power_supply_propval value = {0, }; + /* activate main limiter */ + gpio_direction_output(battery->pdata->main_bat_enb2_gpio, 0); + msleep(100); + value.intval = 1; + psy_do_property(battery->pdata->main_limiter_name, set, + POWER_SUPPLY_EXT_PROP_POWERMETER_ENABLE, value); + } else if (x == 1) { + /* deactivate main limiter */ + gpio_direction_output(battery->pdata->main_bat_enb2_gpio, 1); + } + pr_info("%s main enb = %d, main enb2 = %d, sub enb = %d\n", + __func__, + gpio_get_value(battery->pdata->main_bat_enb_gpio), + gpio_get_value(battery->pdata->main_bat_enb2_gpio), + gpio_get_value(battery->pdata->sub_bat_enb_gpio)); + } + ret = count; + } + break; + case BATT_SUB_ENB: /* Low active pin */ + if (sscanf(buf, "%10d\n", &x) == 1) { + if (battery->pdata->sub_bat_enb_gpio) { + pr_info("%s sub battery enb = %d\n", __func__, x); + if (x == 0) { + union power_supply_propval value = {0, }; + /* activate sub limiter */ + gpio_direction_output(battery->pdata->sub_bat_enb_gpio, 0); + msleep(100); + value.intval = 1; + psy_do_property(battery->pdata->sub_limiter_name, set, + POWER_SUPPLY_EXT_PROP_POWERMETER_ENABLE, value); + } else if (x == 1) { + /* deactivate sub limiter */ + gpio_direction_output(battery->pdata->sub_bat_enb_gpio, 1); + } + pr_info("%s main enb = %d, sub enb = %d\n", + __func__, + gpio_get_value(battery->pdata->main_bat_enb_gpio), + gpio_get_value(battery->pdata->sub_bat_enb_gpio)); + } + ret = count; + } + break; + case BATT_SUB_PWR_MODE2: + if (sscanf(buf, "%10d\n", &x) == 1) { + union power_supply_propval value = {0, }; + pr_info("%s sub pwr mode2 = %d\n", __func__, x); + if (x == 0) { + value.intval = 0; + psy_do_property(battery->pdata->sub_limiter_name, set, + POWER_SUPPLY_EXT_PROP_POWER_MODE2, value); + } else if (x == 1) { + value.intval = 1; + psy_do_property(battery->pdata->sub_limiter_name, set, + POWER_SUPPLY_EXT_PROP_POWER_MODE2, value); + } + ret = count; + } + break; +#else /* max17333 */ + case BATT_MAIN_SHIPMODE: + if (sscanf(buf, "%10d\n", &x) == 1) { + union power_supply_propval value = {0, }; + pr_info("%s main limiter shipmode = %d\n", __func__, x); + if (x == 1) { + value.intval = 1; + psy_do_property(battery->pdata->main_limiter_name, set, + POWER_SUPPLY_EXT_PROP_LIMITER_SHIPMODE, value); + } else { + pr_info("%s wrong option for main limiter shipmode\n", __func__); + } + ret = count; + } + break; + case BATT_SUB_SHIPMODE: + if (sscanf(buf, "%10d\n", &x) == 1) { + union power_supply_propval value = {0, }; + pr_info("%s sub limiter shipmode = %d\n", __func__, x); + if (x == 1) { + value.intval = 1; + psy_do_property(battery->pdata->sub_limiter_name, set, + POWER_SUPPLY_EXT_PROP_LIMITER_SHIPMODE, value); + } else { + pr_info("%s wrong option for sub limiter shipmode\n", __func__); + } + ret = count; + } + break; +#endif +#if IS_ENABLED(CONFIG_DUAL_FUELGAUGE) + case BATT_MAIN_SOC: + case BATT_SUB_SOC: + case BATT_MAIN_REPCAP: + case BATT_SUB_REPCAP: + case BATT_MAIN_FULLCAPREP: + case BATT_SUB_FULLCAPREP: + break; +#endif +#endif /* CONFIG_DUAL_BATTERY */ + case EXT_EVENT: +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + if (sscanf(buf, "%10d\n", &x) == 1) { + pr_info("%s: ext event 0x%x\n", __func__, x); + battery->ext_event = x; + __pm_stay_awake(battery->ext_event_ws); + queue_delayed_work(battery->monitor_wqueue, &battery->ext_event_work, 0); + ret = count; + } +#endif + break; +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + case DIRECT_CHARGING_STATUS: + break; + case DIRECT_CHARGING_STEP: + break; + case DIRECT_CHARGING_IIN: + break; + case DIRECT_CHARGING_CHG_STATUS: + break; + case SWITCH_CHARGING_SOURCE: + if (sscanf(buf, "%10d\n", &x) == 1) { + if (is_pd_apdo_wire_type(battery->cable_type)) { + dev_info(battery->dev, "%s: Request Change Charging Source : %s\n", + __func__, x == 0 ? "Switch Charger" : "Direct Charger"); + direct_charging_source_status[0] = SEC_TEST_MODE; + direct_charging_source_status[1] = + (x == 0) ? SEC_CHARGING_SOURCE_SWITCHING : SEC_CHARGING_SOURCE_DIRECT; + if (battery->current_event & SEC_BAT_CURRENT_EVENT_HIGH_TEMP_SWELLING) { + direct_charging_source_status[1] = SEC_CHARGING_SOURCE_SWITCHING; + pr_info("%s : Change Charging Source to S/C because of Swelling\n", __func__); + } + value.strval = direct_charging_source_status; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_CHANGE_CHARGING_SOURCE, value); + } + ret = count; + } + break; +#endif + case CHARGING_TYPE: + break; + case BATT_FACTORY_MODE: +#if IS_ENABLED(CONFIG_USB_FACTORY_MODE) + if (sscanf(buf, "%10d\n", &x) == 1) { + value.intval = x; + battery->usb_factory_mode = value.intval; + battery->usb_factory_init = true; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_BATT_F_MODE, value); + ret = count; + } +#endif + break; + case BOOT_COMPLETED: + if (sscanf(buf, "%10d\n", &x) == 1) { + dev_info(battery->dev, + "%s: boot completed(%d)\n", __func__, x); + ret = count; + } + break; + case PD_DISABLE: + if (sscanf(buf, "%10d\n", &x) == 1) { + pr_info("%s: PD wired charging mode is %s\n", + __func__, ((x == 1) ? "disabled" : "enabled")); + + if (x == 1) { + battery->pd_disable = true; + sec_bat_set_current_event(battery, + SEC_BAT_CURRENT_EVENT_HV_DISABLE, SEC_BAT_CURRENT_EVENT_HV_DISABLE); + + if (is_pd_wire_type(battery->cable_type)) { + battery->update_pd_list = true; + pr_info("%s: update pd list\n", __func__); +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + if (is_pd_apdo_wire_type(battery->cable_type)) + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_REFRESH_CHARGING_SOURCE, value); +#endif + sec_vote_refresh(battery->iv_vote); + } + } else { + battery->pd_disable = false; + sec_bat_set_current_event(battery, + 0, SEC_BAT_CURRENT_EVENT_HV_DISABLE); + + if (is_pd_wire_type(battery->cable_type)) { + battery->update_pd_list = true; + pr_info("%s: update pd list\n", __func__); +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + if (is_pd_apdo_wire_type(battery->cable_type)) + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_REFRESH_CHARGING_SOURCE, value); +#endif + sec_vote_refresh(battery->iv_vote); + } + } + ret = count; + } + break; + case FACTORY_MODE_RELIEVE: + if (sscanf(buf, "%10d\n", &x) == 1) { + value.intval = x; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_INPUT_VOLTAGE_REGULATION, value); + ret = count; + } + break; + case FACTORY_MODE_BYPASS: + pr_info("%s: factory mode bypass\n", __func__); + if (sscanf(buf, "%10d\n", &x) == 1) { + value.intval = x; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_PROP_AUTHENTIC, value); + ret = count; + } + break; + case NORMAL_MODE_BYPASS: + pr_info("%s: normal mode bypass for current measure\n", __func__); + if (sscanf(buf, "%10d\n", &x) == 1) { +// if (battery->pdata->detect_moisture && x) { +// sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_DISCHARGING); +// sec_bat_set_charge(battery, SEC_BAT_CHG_MODE_BUCK_OFF); +// } + + value.intval = x; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_CURRENT_MEASURE, value); + ret = count; + } + break; + case FACTORY_VOLTAGE_REGULATION: + { + sscanf(buf, "%10d\n", &x); + value.intval = x; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_FACTORY_VOLTAGE_REGULATION, value); + + value.intval = + SEC_FUELGAUGE_CAPACITY_TYPE_RESET; + psy_do_property(battery->pdata->fuelgauge_name, set, + POWER_SUPPLY_PROP_CAPACITY, value); + dev_info(battery->dev,"do reset SOC\n"); + /* update battery info */ + sec_bat_get_battery_info(battery); + } + ret = count; + break; + case FACTORY_MODE_DISABLE: + if (sscanf(buf, "%10d\n", &x) == 1) { + value.intval = x; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_DISABLE_FACTORY_MODE, value); + ret = count; + } + break; + case USB_CONF: + if (sscanf(buf, "%10d\n", &x) == 1) { + value.intval = x; + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_USB_CONFIGURE, value); + ret = count; + } + break; + case CHARGE_OTG_CONTROL: + if (sscanf(buf, "%10d\n", &x) == 1) { + value.intval = x; + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL, value); + ret = count; + } + break; + case CHARGE_UNO_CONTROL: + if (sscanf(buf, "%10d\n", &x) == 1) { + if (x && (is_hv_wire_type(battery->cable_type) || + is_hv_pdo_wire_type(battery->cable_type, battery->hv_pdo))) { + pr_info("%s: Skip uno control during HV wired charging\n", __func__); + break; + } + value.intval = x; + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_CHARGE_UNO_CONTROL, value); + ret = count; + } + break; + case CHARGE_COUNTER_SHADOW: + break; + case VOTER_STATUS: + break; +#if defined(CONFIG_WIRELESS_IC_PARAM) + case WC_PARAM_INFO: + break; +#endif + case LRP: + if (sscanf(buf, "%10d\n", &x) == 1) { + dev_info(battery->dev, "%s: LRP(%d)\n", __func__, x); + if ((x >= -200 && x <= 900) && (battery->lrp_test == 0)) + battery->lrp = x; + ret = count; + } + break; + case HP_D2D: + if (sscanf(buf, "%10d\n", &x) == 1) { + dev_info(battery->dev, "%s: set high power d2d(%d)\n", __func__, x); + battery->hp_d2d = x; + ret = count; + } + break; + case DC_RB_EN: + if (sscanf(buf, "%10d\n", &x) == 1) { + value.intval = x; + pr_info("%s: en reverse boost(%d)\n", __func__, value.intval); + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL, value); + ret = count; + } + break; + case DC_OP_MODE: + if (sscanf(buf, "%10d\n", &x) == 1) { + value.intval = x; + pr_info("%s: en dc op mode(%d)\n", __func__, value.intval); + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_DC_OP_MODE, value); + ret = count; + } + + break; + case DC_ADC_MODE: + if (sscanf(buf, "%10d\n", &x) == 1) { + value.intval = x; + pr_info("%s: en adc mode(%d)\n", __func__, value.intval); + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_ADC_MODE, value); + ret = count; + } + break; + case DC_VBUS: + break; + case CHG_TYPE: + break; + case MST_EN: + if (sscanf(buf, "%10d\n", &x) == 1) { + value.intval = x; + pr_info("%s: mst en(%d)\n", __func__, value.intval); + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_MST_EN, value); + ret = count; + } + break; + case SPSN_TEST: + break; + case CHG_SOC_LIM: + { +#if defined(CONFIG_SEC_FACTORY) + int y = 0; + + if (sscanf(buf, "%10d %10d\n", &x, &y) == 2) { + if (x >= y) { + pr_info("%s: min SOC (%d) higher/equal to max SOC (%d)\n", + __func__, x, y); + } else if (x >= 0 && y >= 0 && x <= 100 && y <= 100) { + battery->pdata->store_mode_charging_min = x; + battery->pdata->store_mode_charging_max = y; + } else { + pr_info("%s: Invalid min/max SOC (%d/%d)\n", __func__, x, y); + } + ret = count; + } +#endif + break; + } + case MAG_COVER: + if (sscanf(buf, "%10d\n", &x) == 1) { + pr_info("@MPP %s: update mag_cover(%d)\n", __func__, x); + battery->mag_cover = x; + value.intval = battery->mag_cover; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_MPP_COVER, value); + } + break; + case MAG_CLOAK: + if (sscanf(buf, "%10d\n", &x) == 1) { + pr_info("@MPP %s: update mag_cloak(%d)\n", __func__, x); + value.intval = x; + psy_do_property("wireless", set, + POWER_SUPPLY_EXT_PROP_MPP_CLOAK, value); + } + break; + case ARI_CNT: + if (sscanf(buf, "%10d\n", &x) == 1) { + pr_info("@ARI %s: (%d)\n", __func__, x); + value.intval = x; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_ARI_CNT, value); + } + ret = count; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +int sec_bat_create_attrs(struct device *dev) +{ + unsigned long i = 0; + int rc = 0; + + for (i = 0; i < ARRAY_SIZE(sec_battery_attrs); i++) { + rc = device_create_file(dev, &sec_battery_attrs[i]); + if (rc) + goto create_attrs_failed; + } + goto create_attrs_succeed; + +create_attrs_failed: + while (i--) + device_remove_file(dev, &sec_battery_attrs[i]); +create_attrs_succeed: + return rc; +} +EXPORT_SYMBOL(sec_bat_create_attrs); + +ssize_t sec_pogo_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const ptrdiff_t offset = attr - sec_pogo_attrs; + int i = 0; + + switch (offset) { + case POGO_SEC_TYPE: + i += scnprintf(buf + i, PAGE_SIZE - i, "POGO\n"); + break; + default: + i = -EINVAL; + break; + } + + return i; +} + +int sec_pogo_create_attrs(struct device *dev) +{ + unsigned long i = 0; + int rc = 0; + + for (i = 0; i < ARRAY_SIZE(sec_pogo_attrs); i++) { + rc = device_create_file(dev, &sec_pogo_attrs[i]); + if (rc) + goto create_attrs_failed; + } + goto create_attrs_succeed; + +create_attrs_failed: + while (i--) + device_remove_file(dev, &sec_pogo_attrs[i]); +create_attrs_succeed: + return rc; +} +EXPORT_SYMBOL(sec_pogo_create_attrs); + +ssize_t sec_otg_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const ptrdiff_t offset = attr - sec_otg_attrs; + int i = 0; + + switch (offset) { + case OTG_SEC_TYPE: + i += scnprintf(buf + i, PAGE_SIZE - i, "OTG\n"); + break; + default: + i = -EINVAL; + break; + } + + return i; +} + +int sec_otg_create_attrs(struct device *dev) +{ + unsigned long i = 0; + int rc = 0; + + for (i = 0; i < ARRAY_SIZE(sec_otg_attrs); i++) { + rc = device_create_file(dev, &sec_otg_attrs[i]); + if (rc) + goto create_attrs_failed; + } + goto create_attrs_succeed; + +create_attrs_failed: + while (i--) + device_remove_file(dev, &sec_otg_attrs[i]); +create_attrs_succeed: + return rc; +} +EXPORT_SYMBOL(sec_otg_create_attrs); diff --git a/drivers/battery/common/sec_battery_sysfs.h b/drivers/battery/common/sec_battery_sysfs.h new file mode 100644 index 000000000000..91aad02cae29 --- /dev/null +++ b/drivers/battery/common/sec_battery_sysfs.h @@ -0,0 +1,370 @@ +/* + * sec_battery_sysfs.h + * Samsung Mobile Battery Header + * + * + * Copyright (C) 2018 Samsung Electronics, 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 __SEC_BATTERY_SYSFS_H +#define __SEC_BATTERY_SYSFS_H __FILE__ + +ssize_t sec_bat_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf); + +ssize_t sec_bat_store_attrs(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +int sec_bat_create_attrs(struct device *dev); + +#define SEC_BATTERY_ATTR(_name) \ +{ \ + .attr = {.name = #_name, .mode = 0664}, \ + .show = sec_bat_show_attrs, \ + .store = sec_bat_store_attrs, \ +} + +enum sec_bat_attrs { + BATT_RESET_SOC = 0, + BATT_READ_RAW_SOC, + BATT_READ_ADJ_SOC, + BATT_TYPE, + BATT_VFOCV, + BATT_VOL_ADC, + BATT_VOL_ADC_CAL, + BATT_VOL_AVER, + BATT_VOL_ADC_AVER, + BATT_VOLTAGE_NOW, + BATT_CURRENT_UA_NOW, + BATT_CURRENT_UA_AVG, + BATT_FILTER_CFG, + BATT_TEMP, + BATT_TEMP_RAW, + BATT_TEMP_ADC, + BATT_TEMP_AVER, + BATT_TEMP_ADC_AVER, + USB_TEMP, + USB_TEMP_ADC, + BATT_CHG_TEMP, + BATT_CHG_TEMP_ADC, + SUB_BAT_TEMP, + SUB_BAT_TEMP_ADC, + SUB_CHG_TEMP, + SUB_CHG_TEMP_ADC, +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + DCHG_ADC_MODE_CTRL, + DCHG_TEMP, + DCHG_TEMP_ADC, + DCHG_READ_BATP_BATN, +#endif + BLKT_TEMP, + BLKT_TEMP_ADC, + BATT_VF_ADC, + BATT_SLATE_MODE, + + BATT_LP_CHARGING, + SIOP_ACTIVATED, + SIOP_LEVEL, + SIOP_EVENT, + BATT_CHARGING_SOURCE, + FG_REG_DUMP, + FG_RESET_CAP, + FG_CAPACITY, + FG_ASOC, + AUTH, + CHG_CURRENT_ADC, + WC_ADC, + WC_STATUS, + WC_ENABLE, + WC_CONTROL, + WC_CONTROL_CNT, + LED_COVER, + HV_CHARGER_STATUS, + HV_WC_CHARGER_STATUS, + HV_CHARGER_SET, + FACTORY_MODE, + STORE_MODE, + UPDATE, + TEST_MODE, + + BATT_EVENT_CALL, + BATT_EVENT_2G_CALL, + BATT_EVENT_TALK_GSM, + BATT_EVENT_3G_CALL, + BATT_EVENT_TALK_WCDMA, + BATT_EVENT_MUSIC, + BATT_EVENT_VIDEO, + BATT_EVENT_BROWSER, + BATT_EVENT_HOTSPOT, + BATT_EVENT_CAMERA, + BATT_EVENT_CAMCORDER, + BATT_EVENT_DATA_CALL, + BATT_EVENT_WIFI, + BATT_EVENT_WIBRO, + BATT_EVENT_LTE, + BATT_EVENT_LCD, +#if defined(CONFIG_ISDB_CHARGING_CONTROL) + BATT_EVENT_ISDB, +#endif + BATT_EVENT_GPS, + BATT_EVENT, + BATT_TEMP_TABLE, + BATT_HIGH_CURRENT_USB, +#if defined(CONFIG_ENG_BATTERY_CONCEPT) + TEST_CHARGE_CURRENT, +#if defined(CONFIG_STEP_CHARGING) + TEST_STEP_CONDITION, +#endif +#endif + SET_STABILITY_TEST, + BATT_CAPACITY_MAX, + BATT_REPCAP_1ST, + BATT_INBAT_VOLTAGE, + BATT_INBAT_VOLTAGE_OCV, + BATT_INBAT_VOLTAGE_ADC, + BATT_VBYP_VOLTAGE, + CHECK_SUB_CHG, + BATT_INBAT_WIRELESS_CS100, + HMT_TA_CONNECTED, + HMT_TA_CHARGE, +#if defined(CONFIG_SEC_FACTORY) + AFC_TEST_FG_MODE, +#endif + FG_CYCLE, + FG_FULL_VOLTAGE, + FG_FULLCAPNOM, + BATTERY_CYCLE, +#if defined(CONFIG_BATTERY_AGE_FORECAST_DETACHABLE) + BATT_AFTER_MANUFACTURED, +#endif + BATTERY_CYCLE_TEST, + BATT_WPC_TEMP, + BATT_WPC_TEMP_ADC, + BATT_WIRELESS_MST_SWITCH_TEST, +#if defined(CONFIG_WIRELESS_FIRMWARE_UPDATE) + BATT_WIRELESS_FIRMWARE_UPDATE, + OTP_FIRMWARE_RESULT, + WC_IC_GRADE, + WC_IC_CHIP_ID, + OTP_FIRMWARE_VER_BIN, + OTP_FIRMWARE_VER, +#endif + WC_PHM_CTRL, + WC_VOUT, + WC_VRECT, + WC_TX_EN, + WC_TX_VOUT, + BATT_HV_WIRELESS_STATUS, + BATT_HV_WIRELESS_PAD_CTRL, + WC_TX_ID, + WC_OP_FREQ, + WC_CMD_INFO, + WC_RX_CONNECTED, + WC_RX_CONNECTED_DEV, + WC_TX_MFC_VIN_FROM_UNO, + WC_TX_MFC_IIN_FROM_UNO, +#if defined(CONFIG_WIRELESS_TX_MODE) + WC_TX_AVG_CURR, + WC_TX_TOTAL_PWR, +#endif + WC_TX_STOP_CAPACITY, + WC_TX_TIMER_EN, +#if defined(CONFIG_ENG_BATTERY_CONCEPT) + BATT_TUNE_FLOAT_VOLTAGE, + BATT_TUNE_INPUT_CHARGE_CURRENT, + BATT_TUNE_FAST_CHARGE_CURRENT, + BATT_TUNE_WIRELESS_VOUT_CURRENT, + BATT_TUNE_UI_TERM_CURRENT_1ST, + BATT_TUNE_UI_TERM_CURRENT_2ND, + BATT_TUNE_TEMP_HIGH_NORMAL, + BATT_TUNE_TEMP_HIGH_REC_NORMAL, + BATT_TUNE_TEMP_LOW_NORMAL, + BATT_TUNE_TEMP_LOW_REC_NORMAL, + BATT_TUNE_CHG_TEMP_HIGH, + BATT_TUNE_CHG_TEMP_REC, + BATT_TUNE_CHG_LIMIT_CUR, + BATT_TUNE_LRP_TEMP_HIGH_LCDON, + BATT_TUNE_LRP_TEMP_HIGH_LCDOFF, + BATT_TUNE_COIL_TEMP_HIGH, + BATT_TUNE_COIL_TEMP_REC, + BATT_TUNE_COIL_LIMIT_CUR, + BATT_TUNE_WPC_TEMP_HIGH, + BATT_TUNE_WPC_TEMP_HIGH_REC, + BATT_TUNE_DCHG_TEMP_HIGH, + BATT_TUNE_DCHG_TEMP_HIGH_REC, + BATT_TUNE_DCHG_BATT_TEMP_HIGH, + BATT_TUNE_DCHG_BATT_TEMP_HIGH_REC, + BATT_TUNE_DCHG_LIMIT_INPUT_CUR, + BATT_TUNE_DCHG_LIMIT_CHG_CUR, +#if defined(CONFIG_WIRELESS_TX_MODE) + BATT_TUNE_TX_MFC_IOUT_GEAR, + BATT_TUNE_TX_MFC_IOUT_PHONE, +#endif +#endif +#if defined(CONFIG_UPDATE_BATTERY_DATA) + BATT_UPDATE_DATA, +#endif + BATT_MISC_EVENT, + BATT_TX_EVENT, + BATT_EXT_DEV_CHG, + BATT_WDT_CONTROL, + MODE, + CHECK_PS_READY, + BATT_CHIP_ID, + ERROR_CAUSE, + CISD_FULLCAPREP_MAX, + CISD_DATA, + CISD_DATA_JSON, + CISD_DATA_D_JSON, + CISD_WIRE_COUNT, + CISD_WC_DATA, + CISD_WC_DATA_JSON, + CISD_POWER_DATA, + CISD_POWER_DATA_JSON, + CISD_PD_DATA, + CISD_PD_DATA_JSON, + CISD_CABLE_DATA, + CISD_CABLE_DATA_JSON, + CISD_TX_DATA, + CISD_TX_DATA_JSON, + CISD_EVENT_DATA, + CISD_EVENT_DATA_JSON, + PREV_BATTERY_DATA, + PREV_BATTERY_INFO, + SAFETY_TIMER_SET, + BATT_SWELLING_CONTROL, + BATT_BATTERY_ID, +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + BATT_SUB_BATTERY_ID, +#endif + BATT_TEMP_CONTROL_TEST, + SAFETY_TIMER_INFO, + BATT_SHIPMODE_TEST, + BATT_MISC_TEST, + BATT_TEMP_TEST, + BATT_CURRENT_EVENT, + BATT_JIG_GPIO, + CC_INFO, +#if defined(CONFIG_WIRELESS_AUTH) + WC_AUTH_ADT_SENT, +#endif + WC_DUO_RX_POWER, +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + BATT_MAIN_VOLTAGE, + BATT_SUB_VOLTAGE, + BATT_MAIN_VCELL, + BATT_SUB_VCELL, + BATT_MAIN_CURRENT_MA, + BATT_SUB_CURRENT_MA, + BATT_MAIN_CON_DET, + BATT_SUB_CON_DET, +#if IS_ENABLED(CONFIG_LIMITER_S2ASL01) + BATT_MAIN_VCHG, + BATT_SUB_VCHG, + BATT_MAIN_ENB, + BATT_MAIN_ENB2, + BATT_SUB_ENB, + BATT_SUB_PWR_MODE2, +#else + BATT_MAIN_SHIPMODE, + BATT_SUB_SHIPMODE, +#endif +#if IS_ENABLED(CONFIG_DUAL_FUELGAUGE) + BATT_MAIN_SOC, + BATT_SUB_SOC, + BATT_MAIN_REPCAP, + BATT_SUB_REPCAP, + BATT_MAIN_FULLCAPREP, + BATT_SUB_FULLCAPREP, +#endif +#endif + EXT_EVENT, + DIRECT_CHARGING_STATUS, +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + DIRECT_CHARGING_STEP, + DIRECT_CHARGING_IIN, + DIRECT_CHARGING_CHG_STATUS, + SWITCH_CHARGING_SOURCE, +#endif + CHARGING_TYPE, + BATT_FACTORY_MODE, + BOOT_COMPLETED, + PD_DISABLE, + FACTORY_MODE_RELIEVE, + FACTORY_MODE_BYPASS, + NORMAL_MODE_BYPASS, + FACTORY_VOLTAGE_REGULATION, + FACTORY_MODE_DISABLE, + USB_CONF, + CHARGE_OTG_CONTROL, + CHARGE_UNO_CONTROL, + CHARGE_COUNTER_SHADOW, + VOTER_STATUS, +#if defined(CONFIG_WIRELESS_IC_PARAM) + WC_PARAM_INFO, +#endif + CHG_INFO, + LRP, + HP_D2D, + CHARGER_IC_NAME, + DC_RB_EN, + DC_OP_MODE, + DC_ADC_MODE, + DC_VBUS, + CHG_TYPE, + MST_EN, + SPSN_TEST, + CHG_SOC_LIM, + MAG_COVER, + MAG_CLOAK, + ARI_CNT, +#if IS_ENABLED(CONFIG_SBP_FG) + STATE_OF_HEALTH, +#endif +}; + +enum sec_pogo_attrs { + POGO_SEC_TYPE = 0, +}; + +ssize_t sec_pogo_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf); + +int sec_pogo_create_attrs(struct device *dev); + +#define SEC_POGO_ATTR(_name) \ +{ \ + .attr = {.name = #_name, .mode = 0444}, \ + .show = sec_pogo_show_attrs, \ + .store = NULL, \ +} + +enum sec_otg_attrs { + OTG_SEC_TYPE = 0, +}; + +ssize_t sec_otg_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf); + +int sec_otg_create_attrs(struct device *dev); + +#define SEC_OTG_ATTR(_name) \ +{ \ + .attr = {.name = #_name, .mode = 0444}, \ + .show = sec_otg_show_attrs, \ + .store = NULL, \ +} + +#endif /* __SEC_BATTERY_SYSFS_H */ diff --git a/drivers/battery/common/sec_battery_thermal.c b/drivers/battery/common/sec_battery_thermal.c new file mode 100644 index 000000000000..00d557eed4fd --- /dev/null +++ b/drivers/battery/common/sec_battery_thermal.c @@ -0,0 +1,1898 @@ +/* + * sec_battery_thermal.c + * Samsung Mobile Battery Driver + * + * Copyright (C) 2021 Samsung Electronics + * + * + * 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. + */ + +#include "sec_battery.h" +#include "battery_logger.h" +#include "sb_full_soc.h" + +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) && !defined(CONFIG_SEC_FACTORY) +extern int muic_set_hiccup_mode(int on_off); +#endif +#if defined(CONFIG_SEC_KUNIT) +#include +#else +#define __visible_for_testing static +#endif + +char *sec_bat_thermal_zone[] = { + "COLD", + "COOL3", + "COOL2", + "COOL1", + "NORMAL", + "WARM", + "OVERHEAT", + "OVERHEATLIMIT", +}; + +#define THERMAL_HYSTERESIS_2 19 + +const char *sec_usb_conn_str(int usb_conn_sts) +{ + switch (usb_conn_sts) { + case USB_CONN_NORMAL: + return "NORMAL"; + case USB_CONN_SLOPE_OVER: + return "SLOPE_OVER"; + case USB_CONN_GAP_OVER1: + return "GAP_OVER1"; + case USB_CONN_GAP_OVER2: + return "GAP_OVER2"; + default: + return "UNKNOWN"; + } +} + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) +int sec_bat_get_high_priority_temp(struct sec_battery_info *battery) +{ + int priority_temp = battery->temperature; + int standard_temp = 250; + + if (battery->pdata->sub_bat_thm_info.check_type == SEC_BATTERY_TEMP_CHECK_NONE) + return battery->temperature; + + if ((battery->temperature > standard_temp) && (battery->sub_bat_temp > standard_temp)) { + if (battery->temperature < battery->sub_bat_temp) + priority_temp = battery->sub_bat_temp; + } else { + if (battery->temperature > battery->sub_bat_temp) + priority_temp = battery->sub_bat_temp; + } + + pr_info("%s priority_temp = %d\n", __func__, priority_temp); + return priority_temp; +} +#endif + +/* trigger mix limit */ +void sec_bat_check_mix_temp_v2_trigger(struct sec_battery_info *battery) +{ + sec_battery_platform_data_t *p = battery->pdata; + + int max_icl = p->full_check_current_1st + 50; + int mix_icl = ((p->chg_float_voltage / p->chg_float_voltage_conv) * max_icl) / + ((battery->input_voltage * 9) / 10); + /* input current = float voltage * (topoff_current_1st + 50mA(margin)) / (vbus_level * 0.9) */ + + if (mix_icl > max_icl) + mix_icl = max_icl; + + /* skip other heating control */ + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_SKIP_HEATING_CONTROL, + SEC_BAT_CURRENT_EVENT_SKIP_HEATING_CONTROL); + + sec_vote(battery->input_vote, VOTER_MIX_LIMIT, true, mix_icl); + +#if IS_ENABLED(CONFIG_WIRELESS_TX_MODE) + if (battery->wc_tx_enable) { + pr_info("%s @Tx_Mode enter mix_temp_limit, TX mode should turn off\n", __func__); + sec_bat_set_tx_event(battery, BATT_TX_EVENT_WIRELESS_TX_HIGH_TEMP, + BATT_TX_EVENT_WIRELESS_TX_HIGH_TEMP); + battery->tx_retry_case |= SEC_BAT_TX_RETRY_MIX_TEMP; + sec_wireless_set_tx_enable(battery, false); + } +#endif +} + +/* recovery mix limit */ +void sec_bat_check_mix_temp_v2_recovery(struct sec_battery_info *battery) +{ + battery->mix_limit = false; + /* for checking charging source (SC -> DC) */ + sec_vote_refresh(battery->fcc_vote); + sec_vote(battery->input_vote, VOTER_MIX_LIMIT, false, 0); + + if (battery->tx_retry_case & SEC_BAT_TX_RETRY_MIX_TEMP) { + pr_info("%s @Tx_Mode recovery mix_temp_limit, TX mode should be retried\n", __func__); + if ((battery->tx_retry_case & ~SEC_BAT_TX_RETRY_MIX_TEMP) == 0) + sec_bat_set_tx_event(battery, BATT_TX_EVENT_WIRELESS_TX_RETRY, + BATT_TX_EVENT_WIRELESS_TX_RETRY); + battery->tx_retry_case &= ~SEC_BAT_TX_RETRY_MIX_TEMP; + } +} + +void sec_bat_check_mix_temp_v2(struct sec_battery_info *battery) +{ + sec_battery_platform_data_t *p = battery->pdata; + + int ct = battery->cable_type; + + int lrpst = battery->lrp; + int bat_temp = battery->temperature; + int chg_temp = battery->chg_temp; +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + int dchg_temp = battery->dchg_temp; +#else + int dchg_temp = 0; +#endif + + if (battery->pdata->lrp_temp_check_type == SEC_BATTERY_TEMP_CHECK_NONE) + return; + + if (battery->lcd_status || !is_wired_type(ct)) { + pr_info("%s: clear mix temp(%d), lcd(%d), ct(%d)\n", __func__, + battery->mix_limit, battery->lcd_status, ct); + + if (battery->mix_limit) /* recovery mix limit */ + sec_bat_check_mix_temp_v2_recovery(battery); + + return; + } + + if (battery->mix_limit) { /* mix limit enabled status */ + pr_info("%s: lrpst(%d), lrp mix temp recovery condition(%d)\n", __func__, lrpst, p->mix_v2_lrp_recov); + if (lrpst > p->mix_v2_lrp_recov) { /* maintain mix limit */ + sec_bat_check_mix_temp_v2_trigger(battery); + + store_battery_log( + "Mix:%d%%,%dmV,mix_lim(%d),lrpst(%d),tbat(%d), tchg(%d), tdchg(%d), icurr(%d),ct(%s)", + battery->capacity, battery->voltage_now, true, + lrpst, bat_temp, chg_temp, dchg_temp, + get_sec_vote_result(battery->input_vote), sb_get_ct_str(battery->cable_type)); + } else { /* recovery mix limit */ + sec_bat_check_mix_temp_v2_recovery(battery); + + store_battery_log( + "Mix:%d%%,%dmV,mix_lim(%d),lrpst(%d),tbat(%d), tchg(%d), tdchg(%d), icurr(%d),ct(%s)", + battery->capacity, battery->voltage_now, false, + lrpst, bat_temp, chg_temp, dchg_temp, + get_sec_vote_result(battery->input_vote), sb_get_ct_str(battery->cable_type)); + } + } else { /* mix limit disabled status, check mix limit */ + pr_info("%s: lrp:%d/%d, bat:%d/%d, chg:%d/%d, dchg:%d/%d\n", __func__, + lrpst, p->mix_v2_lrp_cond, bat_temp, p->mix_v2_bat_cond, + chg_temp, p->mix_v2_chg_cond, dchg_temp, p->mix_v2_dchg_cond); + + if ((lrpst >= p->mix_v2_lrp_cond) && (bat_temp >= p->mix_v2_bat_cond) && + (chg_temp >= p->mix_v2_chg_cond) && dchg_temp >= p->mix_v2_dchg_cond) { + /* for checking charging source (DC -> SC) */ + if (is_pd_apdo_wire_type(ct) && battery->pd_list.now_isApdo) { + battery->mix_limit = true; + sec_vote_refresh(battery->fcc_vote); + return; + } + + sec_bat_check_mix_temp_v2_trigger(battery); + + store_battery_log( + "Mix:%d%%,%dmV,mix_lim(%d),lrpst(%d),tbat(%d), tchg(%d), tdchg(%d), icurr(%d),ct(%s)", + battery->capacity, battery->voltage_now, true, + lrpst, bat_temp, chg_temp, dchg_temp, + get_sec_vote_result(battery->input_vote), sb_get_ct_str(battery->cable_type)); + + battery->mix_limit = true; + + } + } +} +EXPORT_SYMBOL_KUNIT(sec_bat_check_mix_temp_v2); + +void sec_bat_check_mix_temp(struct sec_battery_info *battery, int ct, int siop_level, bool is_apdo) +{ + int temperature = battery->temperature; + int chg_temp; + int input_current = 0; + + if (battery->pdata->bat_thm_info.check_type == SEC_BATTERY_TEMP_CHECK_NONE || + battery->pdata->chg_thm_info.check_type == SEC_BATTERY_TEMP_CHECK_NONE) + return; + + if (battery->pdata->blk_thm_info.check_type) + temperature = battery->blkt_temp; + +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + if (is_apdo) + chg_temp = battery->dchg_temp; + else + chg_temp = battery->chg_temp; +#else + chg_temp = battery->chg_temp; +#endif + + if (siop_level >= 100 && !battery->lcd_status && !is_wireless_all_type(ct)) { + if ((!battery->mix_limit && (temperature >= battery->pdata->mix_high_temp) && + (chg_temp >= battery->pdata->mix_high_chg_temp)) || + (battery->mix_limit && (temperature > battery->pdata->mix_high_temp_recovery))) { + int max_input_current = battery->pdata->full_check_current_1st + 50; + + if (!battery->mix_limit) + store_battery_log( + "Mix:%d%%,%dmV,mix_lim(%d),tbat(%d),tchg(%d),icurr(%d),ct(%s)", + battery->capacity, battery->voltage_now, true, + temperature, chg_temp, input_current, sb_get_ct_str(battery->cable_type)); + + /* for checking charging source (DC -> SC) */ + if (is_apdo) { + battery->mix_limit = true; + sec_vote_refresh(battery->fcc_vote); + return; + } + /* input current = float voltage * (topoff_current_1st + 50mA(margin)) / (vbus_level * 0.9) */ + input_current = ((battery->pdata->chg_float_voltage / battery->pdata->chg_float_voltage_conv) * + max_input_current) / ((battery->input_voltage * 9) / 10); + + if (input_current > max_input_current) + input_current = max_input_current; + + /* skip other heating control */ + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_SKIP_HEATING_CONTROL, + SEC_BAT_CURRENT_EVENT_SKIP_HEATING_CONTROL); + sec_vote(battery->input_vote, VOTER_MIX_LIMIT, true, input_current); + +#if IS_ENABLED(CONFIG_WIRELESS_TX_MODE) + if (battery->wc_tx_enable) { + pr_info("%s @Tx_Mode enter mix_temp_limit, TX mode should turn off\n", __func__); + sec_bat_set_tx_event(battery, BATT_TX_EVENT_WIRELESS_TX_HIGH_TEMP, + BATT_TX_EVENT_WIRELESS_TX_HIGH_TEMP); + battery->tx_retry_case |= SEC_BAT_TX_RETRY_MIX_TEMP; + sec_wireless_set_tx_enable(battery, false); + } +#endif + battery->mix_limit = true; + } else if (battery->mix_limit) { + battery->mix_limit = false; + /* for checking charging source (SC -> DC) */ + sec_vote_refresh(battery->fcc_vote); + sec_vote(battery->input_vote, VOTER_MIX_LIMIT, false, 0); + + if (battery->tx_retry_case & SEC_BAT_TX_RETRY_MIX_TEMP) { + pr_info("%s @Tx_Mode recovery mix_temp_limit, TX mode should be retried\n", __func__); + if ((battery->tx_retry_case & ~SEC_BAT_TX_RETRY_MIX_TEMP) == 0) + sec_bat_set_tx_event(battery, BATT_TX_EVENT_WIRELESS_TX_RETRY, + BATT_TX_EVENT_WIRELESS_TX_RETRY); + battery->tx_retry_case &= ~SEC_BAT_TX_RETRY_MIX_TEMP; + } + store_battery_log( + "Mix:%d%%,%dmV,mix_lim(%d),tbat(%d),tchg(%d),icurr(%d),ct(%s)", + battery->capacity, battery->voltage_now, battery->mix_limit, + temperature, chg_temp, get_sec_vote_result(battery->input_vote), + sb_get_ct_str(battery->cable_type)); + } + + pr_info("%s: mix_limit(%d), temp(%d), chg_temp(%d), input_current(%d)\n", + __func__, battery->mix_limit, temperature, chg_temp, get_sec_vote_result(battery->input_vote)); + } else { + if (battery->mix_limit) { + battery->mix_limit = false; + /* for checking charging source (SC -> DC) */ + sec_vote_refresh(battery->fcc_vote); + sec_vote(battery->input_vote, VOTER_MIX_LIMIT, false, 0); + } + } +} +EXPORT_SYMBOL_KUNIT(sec_bat_check_mix_temp); + +int sec_bat_get_temp_by_temp_control_source(struct sec_battery_info *battery, int tcs) +{ + switch (tcs) { + case TEMP_CONTROL_SOURCE_CHG_THM: + return battery->chg_temp; + case TEMP_CONTROL_SOURCE_USB_THM: + return battery->usb_temp; + case TEMP_CONTROL_SOURCE_WPC_THM: + return battery->wpc_temp; + case TEMP_CONTROL_SOURCE_NONE: + case TEMP_CONTROL_SOURCE_BAT_THM: + default: + return battery->temperature; + } +} +EXPORT_SYMBOL_KUNIT(sec_bat_get_temp_by_temp_control_source); + +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) +__visible_for_testing int sec_bat_check_wpc_step_limit(struct sec_battery_info *battery, unsigned int step_sz, + unsigned int *step_limit_temp, unsigned int rx_power, int temp) +{ + int i; + int fcc = 0; + + for (i = 0; i < step_sz; ++i) { + if (temp > step_limit_temp[i]) { + if (rx_power >= SEC_WIRELESS_RX_POWER_15W) + fcc = battery->pdata->wpc_step_limit_fcc_15w[i]; + else if (rx_power >= SEC_WIRELESS_RX_POWER_12W) + fcc = battery->pdata->wpc_step_limit_fcc_12w[i]; + else + fcc = battery->pdata->wpc_step_limit_fcc[i]; + pr_info("%s: wc20_rx_power(%d), wpc_step_limit[%d] temp:%d, fcc:%d\n", + __func__, rx_power, i, temp, fcc); + break; + } + } + return fcc; +} +EXPORT_SYMBOL_KUNIT(sec_bat_check_wpc_step_limit); + +__visible_for_testing void sec_bat_check_wpc_condition(struct sec_battery_info *battery, bool lcd_off, int ct, + unsigned int rx_power, int *wpc_high_temp, int *wpc_high_temp_recovery) +{ + if (lcd_off) { + if ((ct == SEC_BATTERY_CABLE_HV_WIRELESS_20) || (ct == SEC_BATTERY_CABLE_WIRELESS_EPP)) { + if (rx_power >= SEC_WIRELESS_RX_POWER_15W) { + *wpc_high_temp = battery->pdata->wpc_high_temp_15w; + *wpc_high_temp_recovery = battery->pdata->wpc_high_temp_recovery_15w; + } else if (rx_power >= SEC_WIRELESS_RX_POWER_12W) { + *wpc_high_temp = battery->pdata->wpc_high_temp_12w; + *wpc_high_temp_recovery = battery->pdata->wpc_high_temp_recovery_12w; + } + } else { + *wpc_high_temp = battery->pdata->wpc_high_temp; + *wpc_high_temp_recovery = battery->pdata->wpc_high_temp_recovery; + } + } else { + if ((ct == SEC_BATTERY_CABLE_HV_WIRELESS_20) || (ct == SEC_BATTERY_CABLE_WIRELESS_EPP)) { + if (rx_power >= SEC_WIRELESS_RX_POWER_15W) { + *wpc_high_temp = battery->pdata->wpc_lcd_on_high_temp_15w; + *wpc_high_temp_recovery = battery->pdata->wpc_lcd_on_high_temp_rec_15w; + } else if (rx_power >= SEC_WIRELESS_RX_POWER_12W) { + *wpc_high_temp = battery->pdata->wpc_lcd_on_high_temp_12w; + *wpc_high_temp_recovery = battery->pdata->wpc_lcd_on_high_temp_rec_12w; + } + } else { + *wpc_high_temp = battery->pdata->wpc_lcd_on_high_temp; + *wpc_high_temp_recovery = battery->pdata->wpc_lcd_on_high_temp_rec; + } + } +} + +__visible_for_testing int sec_bat_check_wpc_chg_limit(struct sec_battery_info *battery, bool lcd_off, int ct, + int chg_limit, int *step_limit_fcc) +{ + int wpc_high_temp = battery->pdata->wpc_high_temp; + int wpc_high_temp_recovery = battery->pdata->wpc_high_temp_recovery; + int thermal_source = battery->pdata->wpc_temp_control_source; + int temp; + + if (!lcd_off) + thermal_source = battery->pdata->wpc_temp_lcd_on_control_source; + + temp = sec_bat_get_temp_by_temp_control_source(battery, thermal_source); + sec_bat_check_wpc_condition(battery, + lcd_off, ct, battery->wc20_rx_power, &wpc_high_temp, &wpc_high_temp_recovery); + + if (temp >= wpc_high_temp) + chg_limit = true; + else if (temp <= wpc_high_temp_recovery) + chg_limit = false; + + if (!chg_limit && (temp >= wpc_high_temp_recovery)) { + *step_limit_fcc = sec_bat_check_wpc_step_limit(battery, battery->pdata->wpc_step_limit_size, + battery->pdata->wpc_step_limit_temp, battery->wc20_rx_power, temp); + } + return chg_limit; +} + +void sec_bat_check_wpc_temp(struct sec_battery_info *battery, int ct, int siop_level) +{ + int step_limit_fcc = 0; + int chg_limit = battery->chg_limit; + bool lcd_off = !battery->lcd_status; + int icl = battery->pdata->wpc_input_limit_current; + int fcc = battery->pdata->wpc_charging_limit_current; + + /* nv wc temp control is not necessary when nv wc icl has same value with hv limited icl. + That is why nv_wc_temp_ctrl_skip has true when nv wc icl has same value with hv limited icl. + Otherwise wc temp control is necessary with all kinds of wireless charger types when when nv wc icl is bigger than hv limited icl. + For example, 5.5V/800mA(NV) and 5.5V/800mA(HV) same power so that nv wc type can skip nv wc temp control, + but in case of 5.5V/800mA(NV) and 5.5V/700mA(HV) need wc temp control with nv wc type. */ + if (battery->pdata->wpc_thm_info.check_type == SEC_BATTERY_TEMP_CHECK_NONE || + (battery->nv_wc_temp_ctrl_skip && !is_hv_wireless_type(ct) && (ct != SEC_BATTERY_CABLE_WIRELESS_TX)) || + (!battery->nv_wc_temp_ctrl_skip && !is_wireless_type(ct))) + return; + + chg_limit = sec_bat_check_wpc_chg_limit(battery, lcd_off, ct, chg_limit, &step_limit_fcc); + battery->wpc_vout_level = sec_bat_check_wpc_vout(battery, ct, chg_limit, + battery->wpc_vout_level, battery->current_event); + + pr_info("%s: vout_level: %d, chg_limit: %d, step_limit: %d\n", + __func__, battery->wpc_vout_level, chg_limit, step_limit_fcc); + battery->chg_limit = chg_limit; + if (chg_limit) { + if (!lcd_off) + icl = battery->pdata->wpc_lcd_on_input_limit_current; + if (ct == SEC_BATTERY_CABLE_WIRELESS_TX && + battery->pdata->wpc_input_limit_by_tx_check) + icl = battery->pdata->wpc_input_limit_current_by_tx; + sec_vote(battery->input_vote, VOTER_CHG_TEMP, true, icl); + sec_vote(battery->input_vote, VOTER_CABLE, false, 0); /* 10(V)/ICL(mA) -> 5.5(V)/wpc_input_limit_current(mA) */ + sec_vote(battery->fcc_vote, VOTER_CHG_TEMP, true, fcc); + } else { + if (step_limit_fcc) + sec_vote(battery->fcc_vote, VOTER_CHG_TEMP, true, step_limit_fcc); + else + sec_vote(battery->fcc_vote, VOTER_CHG_TEMP, false, fcc); + sec_vote(battery->input_vote, VOTER_CABLE, true, + battery->pdata->charging_current[ct].input_current_limit); /* 5.5(V)/wpc_input_limit_current(mA) -> 10(V)/ICL(mA) */ + sec_vote(battery->input_vote, VOTER_CHG_TEMP, false, icl); + } +} +EXPORT_SYMBOL_KUNIT(sec_bat_check_wpc_temp); + +/* concept: nv_wireless_type does not control */ +/* concept: no need check LCD ON */ +/* concept: no need step fcc control */ +void sec_bat_check_wpc_temp_v2(struct sec_battery_info *battery) +{ + int ct = battery->cable_type; + int wpc_temp = battery->wpc_temp; + int temp_trigger = battery->pdata->wpc_high_temp; + int temp_recovery = battery->pdata->wpc_high_temp_recovery; + int temp_reset_condition = battery->pdata->wpc_temp_v2_cond; + int chg_limit = battery->chg_limit; + bool using_lrp = false; + + /* exception handling */ + if (!is_hv_wireless_type(ct) || + battery->pdata->wpc_thm_info.check_type == SEC_BATTERY_TEMP_CHECK_NONE) { + battery->wpc_temp_v2_offset = 0; + return; + } + + if (battery->pdata->wpc_high_check_using_lrp && !sec_bat_get_lpmode() && battery->lcd_status) { + if (battery->pdata->lrp_temp_check_type == SEC_BATTERY_TEMP_CHECK_NONE) { + battery->wpc_temp_v2_offset = 0; + return; + } + using_lrp = true; + wpc_temp = battery->lrp; + + /* operating temperature standard settings */ + if ((ct == SEC_BATTERY_CABLE_HV_WIRELESS_20) || (ct == SEC_BATTERY_CABLE_WIRELESS_EPP)) { + if (battery->wc20_rx_power >= SEC_WIRELESS_RX_POWER_15W) { + temp_trigger = battery->pdata->wpc_lrp_high_temp_15w; + temp_recovery = battery->pdata->wpc_lrp_high_temp_recovery_15w; + temp_reset_condition = battery->pdata->wpc_lrp_temp_v2_cond_15w; + } else if (battery->wc20_rx_power >= SEC_WIRELESS_RX_POWER_12W) { + temp_trigger = battery->pdata->wpc_lrp_high_temp_12w; + temp_recovery = battery->pdata->wpc_lrp_high_temp_recovery_12w; + temp_reset_condition = battery->pdata->wpc_lrp_temp_v2_cond_12w; + } + } else { + temp_trigger = battery->pdata->wpc_lrp_high_temp; + temp_recovery = battery->pdata->wpc_lrp_high_temp_recovery; + temp_reset_condition = battery->pdata->wpc_lrp_temp_v2_cond; + } + } else { + /* operating temperature standard settings */ + if ((ct == SEC_BATTERY_CABLE_HV_WIRELESS_20) || (ct == SEC_BATTERY_CABLE_WIRELESS_EPP)) { + if (battery->wc20_rx_power >= SEC_WIRELESS_RX_POWER_15W) { + temp_trigger = battery->pdata->wpc_high_temp_15w; + temp_recovery = battery->pdata->wpc_high_temp_recovery_15w; + temp_reset_condition = battery->pdata->wpc_temp_v2_cond_15w; + } else if (battery->wc20_rx_power >= SEC_WIRELESS_RX_POWER_12W) { + temp_trigger = battery->pdata->wpc_high_temp_12w; + temp_recovery = battery->pdata->wpc_high_temp_recovery_12w; + temp_reset_condition = battery->pdata->wpc_temp_v2_cond_12w; + } + } /* else initial value */ + } + + /* check chg_limit condition */ + if (wpc_temp >= temp_trigger) + chg_limit = true; + else if (wpc_temp <= temp_recovery) + chg_limit = false; + /* else initial value, remain prev chg_limit data */ + + /* check vout_level condition */ + battery->wpc_vout_level = sec_bat_check_wpc_vout(battery, ct, chg_limit, + battery->wpc_vout_level, battery->current_event); + + pr_info("%s: vout_level: %d, chg_limit: %d, %s_temp(%d), trc(%d/%d/%d)\n", __func__, + battery->wpc_vout_level, chg_limit, using_lrp ? "lrp" : "wpc", wpc_temp, + temp_trigger, temp_recovery, temp_reset_condition); + + if (chg_limit) { + /* icl min limit = 500mA */ + int offset_limit = battery->pdata->charging_current[ct].input_current_limit - 500; + + /* 10(V)/ICL(mA) -> 5.5(V)/wpc_input_limit_current(mA) */ + sec_vote(battery->input_vote, VOTER_CHG_TEMP, true, battery->pdata->wpc_input_limit_current); + sec_vote(battery->input_vote, VOTER_CABLE, false, 0); + sec_vote(battery->fcc_vote, VOTER_CHG_TEMP, true, battery->pdata->wpc_charging_limit_current); + + /* work only once for offset change */ + if (battery->chg_limit != chg_limit) { + battery->wpc_temp_v2_offset += 50; + if (battery->wpc_temp_v2_offset > offset_limit) + battery->wpc_temp_v2_offset = offset_limit; + pr_info("%s: offset++, wpc_temp_v2_offset: (%d), limit(%d)\n", __func__, + battery->wpc_temp_v2_offset, offset_limit); + } + } else { + int icl_max = battery->pdata->charging_current[ct].input_current_limit; + + if (wpc_temp <= temp_reset_condition) { + battery->wpc_temp_v2_offset -= 50; + if (battery->wpc_temp_v2_offset < 0) + battery->wpc_temp_v2_offset = 0; + pr_info("%s: offset--, wpc_temp_v2_offset: (%d)\n", __func__, battery->wpc_temp_v2_offset); + } + + sec_vote(battery->fcc_vote, VOTER_CHG_TEMP, false, 0); + /* 5.5(V)/wpc_input_limit_current(mA) -> 10(V)/ICL(mA) */ + sec_vote(battery->input_vote, VOTER_CABLE, true, icl_max - battery->wpc_temp_v2_offset); + sec_vote(battery->input_vote, VOTER_CHG_TEMP, false, 0); + } + battery->chg_limit = chg_limit; +} +EXPORT_SYMBOL_KUNIT(sec_bat_check_wpc_temp_v2); + +void sec_bat_thermal_warm_wc_fod(struct sec_battery_info *battery, bool is_charging) +{ + union power_supply_propval value = {0, }; + + if (!battery->pdata->wpc_warm_fod) + return; + + if (!is_wireless_all_type(battery->cable_type)) + return; + + value.intval = is_charging; + if (!is_charging) + sec_vote(battery->input_vote, VOTER_SWELLING, true, battery->pdata->wpc_warm_fod_icc); + + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WARM_FOD, value); +} +#else +void sec_bat_check_wpc_temp(struct sec_battery_info *battery, int ct, int siop_level) {} +void sec_bat_check_wpc_temp_v2(struct sec_battery_info *battery) {} +void sec_bat_thermal_warm_wc_fod(struct sec_battery_info *battery, bool is_charging) {} +#endif + +#if defined(CONFIG_WIRELESS_TX_MODE) +void sec_bat_check_tx_temperature(struct sec_battery_info *battery) +{ + int bat_temp = battery->temperature; + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + bat_temp = sec_bat_get_high_priority_temp(battery); +#endif + if (battery->wc_tx_enable) { + if (bat_temp >= battery->pdata->tx_high_threshold) { + pr_info("@Tx_Mode : %s: Battery temperature is too high. Tx mode should turn off\n", __func__); + /* set tx event */ + sec_bat_set_tx_event(battery, BATT_TX_EVENT_WIRELESS_TX_HIGH_TEMP, BATT_TX_EVENT_WIRELESS_TX_HIGH_TEMP); + battery->tx_retry_case |= SEC_BAT_TX_RETRY_HIGH_TEMP; + sec_wireless_set_tx_enable(battery, false); + } else if (bat_temp <= battery->pdata->tx_low_threshold) { + pr_info("@Tx_Mode : %s: Battery temperature is too low. Tx mode should turn off\n", __func__); + /* set tx event */ + sec_bat_set_tx_event(battery, BATT_TX_EVENT_WIRELESS_TX_LOW_TEMP, BATT_TX_EVENT_WIRELESS_TX_LOW_TEMP); + battery->tx_retry_case |= SEC_BAT_TX_RETRY_LOW_TEMP; + sec_wireless_set_tx_enable(battery, false); + } + } else if (battery->tx_retry_case & SEC_BAT_TX_RETRY_HIGH_TEMP) { + if (bat_temp <= battery->pdata->tx_high_recovery) { + pr_info("@Tx_Mode : %s: Battery temperature goes to normal(High). Retry TX mode\n", __func__); + battery->tx_retry_case &= ~SEC_BAT_TX_RETRY_HIGH_TEMP; + if (!battery->tx_retry_case) + sec_bat_set_tx_event(battery, BATT_TX_EVENT_WIRELESS_TX_RETRY, BATT_TX_EVENT_WIRELESS_TX_RETRY); + } + } else if (battery->tx_retry_case & SEC_BAT_TX_RETRY_LOW_TEMP) { + if (bat_temp >= battery->pdata->tx_low_recovery) { + pr_info("@Tx_Mode : %s: Battery temperature goes to normal(Low). Retry TX mode\n", __func__); + battery->tx_retry_case &= ~SEC_BAT_TX_RETRY_LOW_TEMP; + if (!battery->tx_retry_case) + sec_bat_set_tx_event(battery, BATT_TX_EVENT_WIRELESS_TX_RETRY, BATT_TX_EVENT_WIRELESS_TX_RETRY); + } + } +} +#endif + +int sec_bat_check_power_type( + int max_chg_pwr, int pd_max_chg_pwr, int ct, int ws, int is_apdo) +{ + if (is_pd_wire_type(ct) && is_apdo) { + if (get_chg_power_type(ct, ws, pd_max_chg_pwr, max_chg_pwr) == SFC_45W) + return SFC_45W; + else + return SFC_25W; + } else { + return NORMAL_TA; + } +} +EXPORT_SYMBOL_KUNIT(sec_bat_check_power_type); + +int sec_bat_check_lrp_temp_cond(int prev_step, + int temp, int trig, int recov) +{ + if (trig <= temp) + prev_step++; + else if (recov >= temp) + prev_step--; + + if (prev_step < LRP_NONE) + prev_step = LRP_NONE; + else if (prev_step > LRP_STEP2) + prev_step = LRP_STEP2; + + return prev_step; +} + +int sec_bat_check_lrp_step( + struct sec_battery_info *battery, int temp, int pt, bool lcd_sts) +{ + int step = LRP_NONE; + int lcd_st = LCD_OFF; + int lrp_pt = LRP_NORMAL; + int lrp_high_temp_st1 = battery->pdata->lrp_temp[LRP_NORMAL].trig[ST1][LCD_OFF]; + int lrp_high_temp_st2 = battery->pdata->lrp_temp[LRP_NORMAL].trig[ST2][LCD_OFF]; + int lrp_high_temp_recov_st1 = battery->pdata->lrp_temp[LRP_NORMAL].recov[ST1][LCD_OFF]; + int lrp_high_temp_recov_st2 = battery->pdata->lrp_temp[LRP_NORMAL].recov[ST2][LCD_OFF]; + + if (lcd_sts) + lcd_st = LCD_ON; + + if (pt == SFC_45W) + lrp_pt = LRP_45W; + else if (pt == SFC_25W) + lrp_pt = LRP_25W; + + lrp_high_temp_st1 = battery->pdata->lrp_temp[lrp_pt].trig[ST1][lcd_st]; + lrp_high_temp_st2 = battery->pdata->lrp_temp[lrp_pt].trig[ST2][lcd_st]; + lrp_high_temp_recov_st1 = battery->pdata->lrp_temp[lrp_pt].recov[ST1][lcd_st]; + lrp_high_temp_recov_st2 = battery->pdata->lrp_temp[lrp_pt].recov[ST2][lcd_st]; + + pr_info("%s: st1(%d), st2(%d), recv_st1(%d), recv_st2(%d), lrp(%d)\n", __func__, + lrp_high_temp_st1, lrp_high_temp_st2, + lrp_high_temp_recov_st1, lrp_high_temp_recov_st2, temp); + + switch (battery->lrp_step) { + case LRP_STEP2: + step = sec_bat_check_lrp_temp_cond(battery->lrp_step, + temp, 900, lrp_high_temp_recov_st2); + break; + case LRP_STEP1: + step = sec_bat_check_lrp_temp_cond(battery->lrp_step, + temp, lrp_high_temp_st2, lrp_high_temp_recov_st1); + break; + case LRP_NONE: + step = sec_bat_check_lrp_temp_cond(battery->lrp_step, + temp, lrp_high_temp_st1, -200); + break; + default: + break; + } + + if ((battery->lrp_step != LRP_STEP1) && (step == LRP_STEP1)) + step = sec_bat_check_lrp_temp_cond(step, + temp, lrp_high_temp_st2, lrp_high_temp_recov_st1); + + return step; +} + +#if defined(CONFIG_SUPPORT_HV_CTRL) && !defined(CONFIG_SEC_FACTORY) +bool sec_bat_temp_vbus_condition(int ct, unsigned int evt) +{ + if (!(is_hv_wire_type(ct) || is_pd_wire_type(ct)) || + (ct == SEC_BATTERY_CABLE_QC30)) + return false; + + if ((evt & SEC_BAT_CURRENT_EVENT_AFC) || + (evt & SEC_BAT_CURRENT_EVENT_SELECT_PDO)) + return false; + return true; +} + +bool sec_bat_change_temp_vbus(struct sec_battery_info *battery, + int ct, unsigned int evt, int lrp_step, int vote_evt) +{ + if (battery->pdata->chg_thm_info.check_type == SEC_BATTERY_TEMP_CHECK_NONE || + battery->store_mode) + return false; + if (!sec_bat_temp_vbus_condition(ct, evt)) + return false; + + if (lrp_step <= LRP_STEP1) + sec_vote(battery->iv_vote, vote_evt, false, 0); + else { + sec_vote(battery->iv_vote, vote_evt, true, SEC_INPUT_VOLTAGE_5V); + pr_info("%s: vbus set 5V by lrp, Cable(%d, %d, %d)\n", + __func__, ct, battery->muic_cable_type, battery->pd_usb_attached); + return true; + } + return false; +} +#endif + +void sec_bat_check_lrp_temp( + struct sec_battery_info *battery, int ct, int ws, int siop_level, bool lcd_sts) +{ + int input_current = 0, charging_current = 0; + int lrp_step = LRP_NONE; + bool is_apdo = false; + int power_type = NORMAL_TA; + bool hv_ctrl = false; +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + unsigned int cur_lrp_chg_src = battery->lrp_chg_src; +#endif + if (battery->pdata->lrp_temp_check_type == SEC_BATTERY_TEMP_CHECK_NONE) + return; + +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + is_apdo = (is_pd_apdo_wire_type(ct) && battery->pd_list.now_isApdo) ? 1 : 0; +#endif + power_type = sec_bat_check_power_type(battery->max_charge_power, + battery->pd_max_charge_power, ct, ws, is_apdo); + + lrp_step = sec_bat_check_lrp_step(battery, battery->lrp, power_type, lcd_sts); + +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + /* trigger from DC to SC for lcd on at LRP_STEP2 */ + if (battery->pdata->sc_LRP_25W) { + if (power_type == SFC_25W || battery->lrp_chg_src == SEC_CHARGING_SOURCE_SWITCHING) { + if (lcd_sts && lrp_step >= LRP_STEP2) + cur_lrp_chg_src = SEC_CHARGING_SOURCE_SWITCHING; + else + cur_lrp_chg_src = SEC_CHARGING_SOURCE_DIRECT; + } + } + + /* FPDO DC concept */ + if (battery->cable_type == SEC_BATTERY_CABLE_FPDO_DC && lrp_step != LRP_NONE) { + union power_supply_propval value = {0, }; + + battery->lrp_limit = true; + cur_lrp_chg_src = SEC_CHARGING_SOURCE_SWITCHING; + + value.intval = 0; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_REFRESH_CHARGING_SOURCE, value); + + } +#endif + + if ((lrp_step == LRP_STEP2) || (lrp_step == LRP_STEP1)) { + if (is_pd_wire_type(ct) || is_hv_wire_type(ct)) { + if (power_type == SFC_45W) { + input_current = battery->pdata->lrp_curr[LRP_45W].st_icl[lrp_step - 1]; + charging_current = battery->pdata->lrp_curr[LRP_45W].st_fcc[lrp_step - 1]; + } else if (power_type == SFC_25W) { + input_current = battery->pdata->lrp_curr[LRP_25W].st_icl[lrp_step - 1]; + charging_current = battery->pdata->lrp_curr[LRP_25W].st_fcc[lrp_step - 1]; + } else { +#if defined(CONFIG_SUPPORT_HV_CTRL) && !defined(CONFIG_SEC_FACTORY) + if (battery->misc_event & BATT_MISC_EVENT_HV_BY_AICL) + hv_ctrl = false; + else + hv_ctrl = sec_bat_change_temp_vbus(battery, ct, + battery->current_event, lrp_step, VOTER_LRP_TEMP); +#endif + if (lrp_step == LRP_STEP1) { /* 9W */ + input_current = + battery->pdata->lrp_curr[LRP_NORMAL].st_icl[lrp_step - 1]; + } else { /* 6W */ + input_current = hv_ctrl ? + battery->pdata->lrp_curr[LRP_NORMAL].st_icl[lrp_step - 1] : + mA_by_mWmV(battery->pdata->power_value, battery->input_voltage); + } + charging_current = battery->pdata->lrp_curr[LRP_NORMAL].st_fcc[lrp_step - 1]; + } +#if defined(CONFIG_USE_POGO) + } else if (ct == SEC_BATTERY_CABLE_POGO_9V) { + if (lcd_sts) { + input_current = battery->pdata->siop_hv_icl; + charging_current = battery->pdata->siop_hv_fcc; + } else { + input_current = battery->pdata->chg_input_limit_current; + charging_current = battery->pdata->chg_charging_limit_current; + } +#endif + } else { + if (lcd_sts) { + input_current = battery->pdata->siop_icl; + charging_current = battery->pdata->siop_fcc; + } else { + input_current = battery->pdata->default_input_current; + charging_current = battery->pdata->default_charging_current; + } + } + sec_vote(battery->fcc_vote, VOTER_LRP_TEMP, true, charging_current); + sec_vote(battery->input_vote, VOTER_LRP_TEMP, true, input_current); +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + if (battery->lrp_chg_src != cur_lrp_chg_src) { + battery->lrp_chg_src = cur_lrp_chg_src; + sec_vote_refresh(battery->fcc_vote); + } +#endif + if (battery->lrp_step != lrp_step) + store_battery_log( + "LRP:SOC(%d),Vnow(%d),lrp_step(%d),lcd(%d),tlrp(%d),icl(%d),fcc(%d),ct(%d),is_apdo(%d),mcp(%d,%d)", + battery->capacity, battery->voltage_now, lrp_step, lcd_sts, + battery->lrp, input_current, charging_current, battery->cable_type, + is_apdo, battery->pd_max_charge_power, battery->max_charge_power); + battery->lrp_limit = true; + } else if ((battery->lrp_limit == true) && (lrp_step == LRP_NONE)) { + sec_vote(battery->iv_vote, VOTER_LRP_TEMP, false, 0); + sec_vote(battery->fcc_vote, VOTER_LRP_TEMP, false, 0); + sec_vote(battery->input_vote, VOTER_LRP_TEMP, false, 0); + battery->lrp_limit = false; +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + battery->lrp_chg_src = SEC_CHARGING_SOURCE_DIRECT; +#endif + store_battery_log( + "LRP:%d%%,%dmV,lrp_lim(%d),tlrp(%d),icurr(%d),ocurr(%d),ct(%s)", + battery->capacity, battery->voltage_now, battery->lrp_limit, + battery->lrp, get_sec_vote_result(battery->input_vote), + get_sec_vote_result(battery->fcc_vote), sb_get_ct_str(battery->cable_type)); + } + battery->lrp_step = lrp_step; + + pr_info("%s: cable_type(%d), lrp_step(%d), lrp(%d)\n", __func__, + ct, battery->lrp_step, battery->lrp); +} +EXPORT_SYMBOL_KUNIT(sec_bat_check_lrp_temp); + +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) +static int sec_bat_check_lpm_power(int lpm, int pt) +{ + int ret = 0; + + if (pt == SFC_25W) + ret |= 0x02; + + if (lpm) + ret |= 0x01; + + return ret; +} + +int sec_bat_set_dchg_current(struct sec_battery_info *battery, int power_type, int pt) +{ + int input_current = 0, charging_current = 0; + + /* skip power_type check for non-use lrp_temp_check models */ + if (battery->pdata->lrp_temp_check_type == SEC_BATTERY_TEMP_CHECK_NONE) + power_type = NORMAL_TA; + + if (power_type == SFC_45W) { + if (pt & 0x01) { + input_current = battery->pdata->lrp_curr[LRP_45W].st_icl[ST1]; + charging_current = battery->pdata->lrp_curr[LRP_45W].st_fcc[ST1]; + } else { + input_current = battery->pdata->lrp_curr[LRP_45W].st_icl[ST2]; + charging_current = battery->pdata->lrp_curr[LRP_45W].st_fcc[ST2]; + } + } else if (power_type == SFC_25W) { + if (pt & 0x01) { + input_current = battery->pdata->lrp_curr[LRP_25W].st_icl[ST1]; + charging_current = battery->pdata->lrp_curr[LRP_25W].st_fcc[ST1]; + } else { + input_current = battery->pdata->lrp_curr[LRP_25W].st_icl[ST2]; + charging_current = battery->pdata->lrp_curr[LRP_25W].st_fcc[ST2]; + } + } else { + input_current = battery->pdata->dchg_input_limit_current; + charging_current = battery->pdata->dchg_charging_limit_current; + } + + sec_vote(battery->fcc_vote, VOTER_CHG_TEMP, true, charging_current); + sec_vote(battery->input_vote, VOTER_CHG_TEMP, true, input_current); + + return charging_current; +} + +void sec_bat_check_direct_chg_temp(struct sec_battery_info *battery, int siop_level) +{ + int input_current = 0, charging_current = 0, pt = 0; + int ct = battery->cable_type, ws = battery->wire_status; + bool is_apdo = false; + int power_type = NORMAL_TA; + + if (battery->pdata->dchg_thm_info.check_type == SEC_BATTERY_TEMP_CHECK_NONE) { + return; + } else if (battery->pdata->dctp_bootmode_en) { + battery->chg_limit = false; + sec_vote(battery->fcc_vote, VOTER_CHG_TEMP, false, 0); + sec_vote(battery->input_vote, VOTER_CHG_TEMP, false, 0); + + return; + } + + is_apdo = (is_pd_apdo_wire_type(ct) && battery->pd_list.now_isApdo) ? 1 : 0; + power_type = sec_bat_check_power_type(battery->max_charge_power, + battery->pd_max_charge_power, ct, ws, is_apdo); + pt = sec_bat_check_lpm_power(sec_bat_get_lpmode(), power_type); + + if (siop_level >= 100) { + if (!battery->chg_limit && + ((battery->dchg_temp >= battery->pdata->dchg_high_temp[pt]) || + (battery->temperature >= battery->pdata->dchg_high_batt_temp[pt]))) { + battery->chg_limit = true; + charging_current = sec_bat_set_dchg_current(battery, power_type, pt); + input_current = charging_current / 2; + store_battery_log( + "Dchg:%d%%,%dmV,chg_lim(%d),tbat(%d),tdchg(%d),icurr(%d),ocurr(%d),ct(%s)", + battery->capacity, battery->voltage_now, battery->chg_limit, + battery->temperature, battery->dchg_temp, input_current, charging_current, + sb_get_ct_str(battery->cable_type)); + } else if (battery->chg_limit) { + if ((battery->dchg_temp <= battery->pdata->dchg_high_temp_recovery[pt]) && + (battery->temperature <= battery->pdata->dchg_high_batt_temp_recovery[pt])) { + battery->chg_limit = false; + sec_vote(battery->fcc_vote, VOTER_CHG_TEMP, false, 0); + sec_vote(battery->input_vote, VOTER_CHG_TEMP, false, 0); + store_battery_log( + "Dchg:%d%%,%dmV,chg_lim(%d),tbat(%d),tdchg(%d),icurr(%d),ocurr(%d),ct(%s)", + battery->capacity, battery->voltage_now, battery->chg_limit, + battery->temperature, battery->dchg_temp, + get_sec_vote_result(battery->input_vote), + get_sec_vote_result(battery->fcc_vote), sb_get_ct_str(battery->cable_type)); + } else { + battery->chg_limit = true; + sec_bat_set_dchg_current(battery, power_type, pt); + } + } + pr_info("%s: chg_limit(%d)\n", __func__, battery->chg_limit); + } else if (battery->chg_limit) { + battery->chg_limit = false; + sec_vote(battery->fcc_vote, VOTER_CHG_TEMP, false, 0); + sec_vote(battery->input_vote, VOTER_CHG_TEMP, false, 0); + } +} +EXPORT_SYMBOL_KUNIT(sec_bat_check_direct_chg_temp); +#else +void sec_bat_check_direct_chg_temp(struct sec_battery_info *battery, int siop_level) {} +#endif + +void sec_bat_check_pdic_temp(struct sec_battery_info *battery, int siop_level) +{ + int input_current = 0, charging_current = 0; + bool pre_chg_limit = battery->chg_limit; + + if (battery->pdata->chg_thm_info.check_type == SEC_BATTERY_TEMP_CHECK_NONE) + return; + + if (battery->pdic_ps_rdy && siop_level >= 100) { + if ((!battery->chg_limit && (battery->chg_temp >= battery->pdata->chg_high_temp)) || + (battery->chg_limit && (battery->chg_temp >= battery->pdata->chg_high_temp_recovery))) { + input_current = + (battery->pdata->chg_input_limit_current * SEC_INPUT_VOLTAGE_9V) / battery->input_voltage; + charging_current = battery->pdata->chg_charging_limit_current; + sec_vote(battery->fcc_vote, VOTER_CHG_TEMP, true, charging_current); + sec_vote(battery->input_vote, VOTER_CHG_TEMP, true, input_current); + if (!battery->chg_limit) + store_battery_log( + "Pdic:%d%%,%dmV,chg_lim(%d),tchg(%d),icurr(%d),ocurr(%d),ct(%s)", + battery->capacity, battery->voltage_now, true, + battery->chg_temp, input_current, charging_current, + sb_get_ct_str(battery->cable_type)); + battery->chg_limit = true; + } else if (battery->chg_limit && battery->chg_temp <= battery->pdata->chg_high_temp_recovery) { + battery->chg_limit = false; + sec_vote(battery->fcc_vote, VOTER_CHG_TEMP, false, 0); + sec_vote(battery->input_vote, VOTER_CHG_TEMP, false, 0); + store_battery_log( + "Pdic:%d%%,%dmV,chg_lim(%d),tchg(%d),icurr(%d),ocurr(%d),ct(%s)", + battery->capacity, battery->voltage_now, battery->chg_limit, + battery->chg_temp, get_sec_vote_result(battery->input_vote), + get_sec_vote_result(battery->fcc_vote), sb_get_ct_str(battery->cable_type)); + } + pr_info("%s: chg_limit(%d)\n", __func__, battery->chg_limit); + } else if (battery->chg_limit) { + battery->chg_limit = false; + sec_vote(battery->fcc_vote, VOTER_CHG_TEMP, false, 0); + sec_vote(battery->input_vote, VOTER_CHG_TEMP, false, 0); + } + + /* FPDO DC concept */ + if (battery->cable_type == SEC_BATTERY_CABLE_FPDO_DC && battery->chg_limit != pre_chg_limit) { + union power_supply_propval value = {0, }; + + value.intval = 0; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_REFRESH_CHARGING_SOURCE, value); + + } +} +EXPORT_SYMBOL_KUNIT(sec_bat_check_pdic_temp); + +void sec_bat_check_afc_temp(struct sec_battery_info *battery, int siop_level) +{ + int input_current = 0, charging_current = 0; + int ct = battery->cable_type; + + if (battery->pdata->chg_thm_info.check_type == SEC_BATTERY_TEMP_CHECK_NONE) + return; + + if (siop_level >= 100) { +#if defined(CONFIG_SUPPORT_HV_CTRL) + if (!battery->chg_limit && is_hv_wire_type(ct) && + (battery->chg_temp >= battery->pdata->chg_high_temp)) { + input_current = battery->pdata->chg_input_limit_current; + charging_current = battery->pdata->chg_charging_limit_current; + battery->chg_limit = true; + sec_vote(battery->fcc_vote, VOTER_CHG_TEMP, true, charging_current); + sec_vote(battery->input_vote, VOTER_CHG_TEMP, true, input_current); + store_battery_log( + "Afc:%d%%,%dmV,chg_lim(%d),tchg(%d),icurr(%d),ocurr(%d),ct(%s)", + battery->capacity, battery->voltage_now, battery->chg_limit, + battery->chg_temp, input_current, charging_current, + sb_get_ct_str(ct)); + } else if (!battery->chg_limit && battery->max_charge_power >= + (battery->pdata->pd_charging_charge_power - 500) && + (battery->chg_temp >= battery->pdata->chg_high_temp)) { + input_current = battery->pdata->default_input_current; + charging_current = battery->pdata->default_charging_current; + battery->chg_limit = true; + sec_vote(battery->fcc_vote, VOTER_CHG_TEMP, true, charging_current); + sec_vote(battery->input_vote, VOTER_CHG_TEMP, true, input_current); + store_battery_log( + "Afc:%d%%,%dmV,chg_lim(%d),tchg(%d),icurr(%d),ocurr(%d),ct(%s)", + battery->capacity, battery->voltage_now, battery->chg_limit, + battery->chg_temp, input_current, charging_current, + sb_get_ct_str(battery->cable_type)); + } else if (battery->chg_limit && is_hv_wire_type(ct)) { + if (battery->chg_temp <= battery->pdata->chg_high_temp_recovery) { + battery->chg_limit = false; + sec_vote(battery->fcc_vote, VOTER_CHG_TEMP, false, 0); + sec_vote(battery->input_vote, VOTER_CHG_TEMP, false, 0); + store_battery_log( + "Afc:%d%%,%dmV,chg_lim(%d),tchg(%d),icurr(%d),ocurr(%d),ct(%s)", + battery->capacity, battery->voltage_now, battery->chg_limit, + battery->chg_temp, get_sec_vote_result(battery->input_vote), + get_sec_vote_result(battery->fcc_vote), sb_get_ct_str(ct)); + } + } else if (battery->chg_limit && battery->max_charge_power >= + (battery->pdata->pd_charging_charge_power - 500)) { + if (battery->chg_temp <= battery->pdata->chg_high_temp_recovery) { + battery->chg_limit = false; + sec_vote(battery->fcc_vote, VOTER_CHG_TEMP, false, 0); + sec_vote(battery->input_vote, VOTER_CHG_TEMP, false, 0); + store_battery_log( + "Afc:%d%%,%dmV,chg_lim(%d),tchg(%d),icurr(%d),ocurr(%d),ct(%s)", + battery->capacity, battery->voltage_now, battery->chg_limit, + battery->chg_temp, get_sec_vote_result(battery->input_vote), + get_sec_vote_result(battery->fcc_vote), sb_get_ct_str(ct)); + } + } + pr_info("%s: cable_type(%d), chg_limit(%d)\n", __func__, + ct, battery->chg_limit); +#else + if ((!battery->chg_limit && is_hv_wire_type(ct) && + (battery->chg_temp >= battery->pdata->chg_high_temp)) || + (battery->chg_limit && is_hv_wire_type(ct) && + (battery->chg_temp > battery->pdata->chg_high_temp_recovery))) { + if (!battery->chg_limit) { + input_current = battery->pdata->chg_input_limit_current; + charging_current = battery->pdata->chg_charging_limit_current; + battery->chg_limit = true; + sec_vote(battery->fcc_vote, VOTER_CHG_TEMP, true, charging_current); + sec_vote(battery->input_vote, VOTER_CHG_TEMP, true, input_current); + store_battery_log( + "Afc:%d%%,%dmV,chg_lim(%d),tchg(%d),icurr(%d),ocurr(%d),ct(%s)", + battery->capacity, battery->voltage_now, battery->chg_limit, + battery->chg_temp, input_current, charging_current, + sb_get_ct_str(ct)); + } + } else if (battery->chg_limit && is_hv_wire_type(ct) && + (battery->chg_temp <= battery->pdata->chg_high_temp_recovery)) { + battery->chg_limit = false; + sec_vote(battery->fcc_vote, VOTER_CHG_TEMP, false, 0); + sec_vote(battery->input_vote, VOTER_CHG_TEMP, false, 0); + store_battery_log( + "Afc:%d%%,%dmV,chg_lim(%d),tchg(%d),icurr(%d),ocurr(%d),ct(%s)", + battery->capacity, battery->voltage_now, battery->chg_limit, + battery->chg_temp, get_sec_vote_result(battery->input_vote), + get_sec_vote_result(battery->fcc_vote), sb_get_ct_str(ct)); + } +#endif + } else if (battery->chg_limit) { + battery->chg_limit = false; + sec_vote(battery->fcc_vote, VOTER_CHG_TEMP, false, 0); + sec_vote(battery->input_vote, VOTER_CHG_TEMP, false, 0); + } +} +EXPORT_SYMBOL_KUNIT(sec_bat_check_afc_temp); + +void sec_bat_set_threshold(struct sec_battery_info *battery, int cable_type) +{ + if (is_wireless_all_type(cable_type)) { + battery->cold_cool3_thresh = battery->pdata->wireless_cold_cool3_thresh; + battery->cool3_cool2_thresh = battery->pdata->wireless_cool3_cool2_thresh; + battery->cool2_cool1_thresh = battery->pdata->wireless_cool2_cool1_thresh; + battery->cool1_normal_thresh = battery->pdata->wireless_cool1_normal_thresh; + battery->normal_warm_thresh = battery->pdata->wireless_normal_warm_thresh; + battery->warm_overheat_thresh = battery->pdata->wireless_warm_overheat_thresh; + } else { + battery->cold_cool3_thresh = battery->pdata->wire_cold_cool3_thresh; + battery->cool3_cool2_thresh = battery->pdata->wire_cool3_cool2_thresh; + battery->cool2_cool1_thresh = battery->pdata->wire_cool2_cool1_thresh; + battery->cool1_normal_thresh = battery->pdata->wire_cool1_normal_thresh; + battery->normal_warm_thresh = battery->pdata->wire_normal_warm_thresh; + battery->warm_overheat_thresh = battery->pdata->wire_warm_overheat_thresh; + + } + + switch (battery->thermal_zone) { + case BAT_THERMAL_OVERHEATLIMIT: + battery->warm_overheat_thresh -= THERMAL_HYSTERESIS_2; + battery->normal_warm_thresh -= THERMAL_HYSTERESIS_2; + break; + case BAT_THERMAL_OVERHEAT: + battery->warm_overheat_thresh -= THERMAL_HYSTERESIS_2; + battery->normal_warm_thresh -= THERMAL_HYSTERESIS_2; + break; + case BAT_THERMAL_WARM: + battery->normal_warm_thresh -= THERMAL_HYSTERESIS_2; + break; + case BAT_THERMAL_COOL1: + battery->cool1_normal_thresh += THERMAL_HYSTERESIS_2; + break; + case BAT_THERMAL_COOL2: + battery->cool2_cool1_thresh += THERMAL_HYSTERESIS_2; + battery->cool1_normal_thresh += THERMAL_HYSTERESIS_2; + break; + case BAT_THERMAL_COOL3: + battery->cool3_cool2_thresh += THERMAL_HYSTERESIS_2; + battery->cool2_cool1_thresh += THERMAL_HYSTERESIS_2; + battery->cool1_normal_thresh += THERMAL_HYSTERESIS_2; + break; + case BAT_THERMAL_COLD: + battery->cold_cool3_thresh += THERMAL_HYSTERESIS_2; + battery->cool3_cool2_thresh += THERMAL_HYSTERESIS_2; + battery->cool2_cool1_thresh += THERMAL_HYSTERESIS_2; + battery->cool1_normal_thresh += THERMAL_HYSTERESIS_2; + break; + case BAT_THERMAL_NORMAL: + default: + break; + } +} + +int sec_usb_conn_gap_check(struct sec_battery_info *battery, int thm1_temp, int thm2_temp) +{ + int gap = 0; + + if (thm1_temp > thm2_temp) + gap = thm1_temp - thm2_temp; + + pr_info("%s: thm1_temp(%d), thm2_temp(%d), gap(%d)\n", + __func__, thm1_temp, thm2_temp, gap); + + if ((thm1_temp >= battery->usb_protection_temp) && + (gap >= battery->temp_gap_bat_usb)) { + pr_info("%s: USB_CONN_GAP_OVER1 detected\n", __func__); + if (battery->run_usb_conn_check && battery->usb_conn_check_cnt > 0) { + battery->cisd.data[CISD_DATA_USB_OVERHEAT_RAPID_CHANGE]++; + battery->cisd.data[CISD_DATA_USB_OVERHEAT_RAPID_CHANGE_PER_DAY]++; + } else { + battery->cisd.data[CISD_DATA_USB_OVERHEAT_ALONE]++; + battery->cisd.data[CISD_DATA_USB_OVERHEAT_ALONE_PER_DAY]++; + } + return USB_CONN_GAP_OVER1; + } + + return USB_CONN_NORMAL; +} + +int sec_usb_conn_slope_check(struct sec_battery_info *battery, int cur_usb_temp, int prev_usb_temp) +{ + int usb_temp_gap = 0, usb_temp_gap_avg = 0; + static int usb_temp_sum; + static unsigned int valid_temp_cnt; + + if (battery->usb_conn_check_cnt == 1) { + usb_temp_sum = 0; + valid_temp_cnt = 0; + } + + usb_temp_gap = cur_usb_temp - prev_usb_temp; + pr_info("%s: prev_usb_temp(%d) -> cur_usb_temp(%d), usb_temp_gap(%d)\n", + __func__, prev_usb_temp, cur_usb_temp, usb_temp_gap); + + if (cur_usb_temp >= 100 && usb_temp_gap < 50) { + usb_temp_sum += usb_temp_gap; + valid_temp_cnt++; + pr_info("%s: usb_temp_sum(%d), valid_temp_cnt(%d)\n", __func__, + usb_temp_sum, valid_temp_cnt); + if (usb_temp_sum > 0 && battery->usb_conn_check_cnt == MAX_USB_CONN_CHECK_CNT) { + usb_temp_gap_avg = usb_temp_sum / valid_temp_cnt; + pr_info("%s: usb_temp_gap_avg(%d)\n", __func__, usb_temp_gap_avg); + if (usb_temp_gap_avg >= battery->pdata->usb_conn_slope_avg) { + pr_info("%s: USB_CONN_SLOPE_OVER detected\n", __func__); + battery->cisd.data[CISD_DATA_USB_OVERHEAT_RAPID_CHANGE]++; + battery->cisd.data[CISD_DATA_USB_OVERHEAT_RAPID_CHANGE_PER_DAY]++; + return USB_CONN_SLOPE_OVER; + } + } + } + + return USB_CONN_NORMAL; +} + +void sec_usb_conn_protection(struct sec_battery_info *battery) +{ + int thm1_temp = battery->usb_temp; // default for flagship models, THM1 = USB_THM + int thm2_temp = battery->temperature; // default for flagship models, THM2 = BAT_THM + + if (battery->pdata->usb_thm_info.check_type == SEC_BATTERY_TEMP_CHECK_NONE) { + // there is no USB_THM, mass models, THM1 = BAT_THM, THM2 = CHG_THM + battery->usb_temp = battery->temperature; + thm1_temp = battery->temperature; + thm2_temp = battery->chg_temp; + } else if (battery->pdata->mass_with_usb_thm) { + // there is USB_THM, mass models, THM1 = USB_THM, THM2 = CHG_THM + thm1_temp = battery->usb_temp; + thm2_temp = battery->chg_temp; + } + + battery->usb_conn_status = sec_usb_conn_gap_check(battery, thm1_temp, thm2_temp); + if (battery->usb_conn_status != USB_CONN_NORMAL) { + battery->run_usb_conn_check = false; + battery->usb_conn_check_cnt = 0; + __pm_relax(battery->usb_conn_check_ws); + cancel_delayed_work(&battery->usb_conn_check_work); + } +} + +int sec_usb_conn_check(struct sec_battery_info *battery) +{ + int cur_bat_temp = 0; + int cur_sub_bat_temp = 0; + int cur_usb_temp = 0; + int cur_chg_temp = 0; + int prev_usb_temp = battery->usb_temp; + int thm1_temp = 0, thm2_temp = 0; + battery->usb_conn_status = USB_CONN_NORMAL; + + if (battery->pdata->usb_thm_info.check_type != SEC_BATTERY_TEMP_CHECK_NONE) { + cur_usb_temp = sec_bat_get_temperature(battery->dev, &battery->pdata->usb_thm_info, cur_usb_temp, + battery->pdata->charger_name, battery->pdata->fuelgauge_name, battery->pdata->adc_read_type); + thm1_temp = cur_usb_temp; + if (battery->pdata->mass_with_usb_thm) { + // there is USB_THM, mass models, THM1 = USB_THM, THM2 = CHG_THM + cur_chg_temp = sec_bat_get_temperature(battery->dev, &battery->pdata->chg_thm_info, cur_chg_temp, + battery->pdata->charger_name, battery->pdata->fuelgauge_name, + battery->pdata->adc_read_type); + thm2_temp = cur_chg_temp; + } else { + // there is USB_THM, flagship models, THM1 = USB_THM, THM2 = SUB_THM + if (battery->pdata->lr_enable) { + cur_sub_bat_temp = sec_bat_get_temperature(battery->dev, &battery->pdata->sub_bat_thm_info, cur_sub_bat_temp, + battery->pdata->charger_name, battery->pdata->fuelgauge_name, + battery->pdata->adc_read_type); + thm2_temp = cur_sub_bat_temp; + } else { + // there is USB_THM, flagship models, THM1 = USB_THM, THM2 = BAT_THM + cur_bat_temp = sec_bat_get_temperature(battery->dev, &battery->pdata->bat_thm_info, cur_bat_temp, + battery->pdata->charger_name, battery->pdata->fuelgauge_name, + battery->pdata->adc_read_type); + thm2_temp = cur_bat_temp; + } + } + } else { + pr_err("%s: USB_THM, Invalid Temp Check Type, usb_thm <- bat_thm\n", __func__); + // there is no USB_THM, mass models, THM1 = BAT_THM, THM2 = CHG_THM + cur_bat_temp = sec_bat_get_temperature(battery->dev, &battery->pdata->bat_thm_info, cur_bat_temp, + battery->pdata->charger_name, battery->pdata->fuelgauge_name, battery->pdata->adc_read_type); + cur_chg_temp = sec_bat_get_temperature(battery->dev, &battery->pdata->chg_thm_info, cur_chg_temp, + battery->pdata->charger_name, battery->pdata->fuelgauge_name, battery->pdata->adc_read_type); + cur_usb_temp = cur_bat_temp; + thm1_temp = cur_bat_temp; + thm2_temp = cur_chg_temp; + } + + battery->usb_conn_status = sec_usb_conn_slope_check(battery, cur_usb_temp, prev_usb_temp); + if (battery->usb_conn_status == USB_CONN_NORMAL) + battery->usb_conn_status = sec_usb_conn_gap_check(battery, thm1_temp, thm2_temp); + + battery->usb_temp = cur_usb_temp; + + return battery->usb_conn_status; +} + +void sec_bat_thermal_charging_status(struct sec_battery_info *battery) +{ + if (is_full_capacity(battery->fs) && (battery->misc_event & BATT_MISC_EVENT_FULL_CAPACITY)) { + pr_info("%s: prevent the status during is_full_cap\n", __func__); + return; + } + + if ((battery->status == POWER_SUPPLY_STATUS_NOT_CHARGING) && + ((battery->health == POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE) || \ + (battery->health == POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE))) + return; + +#if defined(CONFIG_ENABLE_FULL_BY_SOC) + if ((battery->capacity >= 100) || (battery->status == POWER_SUPPLY_STATUS_FULL)) + sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_FULL); + else + sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_CHARGING); +#else + if (battery->status != POWER_SUPPLY_STATUS_FULL) + sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_CHARGING); +#endif +} + +void sec_bat_thermal_charging_health(struct sec_battery_info *battery) +{ + if ((battery->status == POWER_SUPPLY_STATUS_NOT_CHARGING) && + ((battery->health == POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE) || \ + (battery->health == POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE))) + return; + + sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_GOOD); +} + +int sec_usb_temp_gap_check(struct sec_battery_info *battery, + bool tmp_chk, int usb_temp) +{ + int gap = 0; + int bat_thm = battery->temperature; + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + /* select low temp thermistor */ + if (battery->temperature > battery->sub_bat_temp) + bat_thm = battery->sub_bat_temp; +#endif + if (usb_temp > bat_thm) + gap = usb_temp - bat_thm; + + if (tmp_chk) { /* check usb temp gap */ + if ((usb_temp >= battery->usb_protection_temp) && + (gap >= battery->temp_gap_bat_usb)) { + pr_info("%s: Temp gap between Usb temp and Bat temp : %d\n", __func__, gap); + return USB_CONN_GAP_OVER1; + } else if ((usb_temp >= battery->overheatlimit_threshold) && + (gap >= (battery->temp_gap_bat_usb - 100))) { + pr_info("%s: Usb Temp (%d) over and gap between Usb temp and Bat temp : %d\n", + __func__, usb_temp, gap); + return USB_CONN_GAP_OVER2; + } + } else { /* recover check */ + if ((battery->usb_conn_status == USB_CONN_GAP_OVER1) && + (usb_temp >= battery->usb_protection_temp)) { + return USB_CONN_GAP_OVER1; + + } else if ((battery->usb_conn_status == USB_CONN_GAP_OVER2) && + (usb_temp >= battery->overheatlimit_threshold)) { + return USB_CONN_GAP_OVER2; + } + } + pr_info("%s: %s -> NORMAL\n", __func__, sec_usb_conn_str(battery->usb_conn_status)); + + return USB_CONN_NORMAL; +} + +void sec_usb_thm_overheatlimit(struct sec_battery_info *battery) +{ + bool use_usb_temp = (battery->pdata->usb_thm_info.check_type != SEC_BATTERY_TEMP_CHECK_NONE); + int usb_temp = battery->usb_temp; + + if (!use_usb_temp) { + pr_err("%s: USB_THM, Invalid Temp Check Type, usb_thm <- bat_thm\n", __func__); + battery->usb_temp = battery->temperature; + usb_temp = battery->temperature; + } +#if defined(CONFIG_SEC_FACTORY) + use_usb_temp = false; +#endif + + switch (battery->usb_conn_status) { + case USB_CONN_NORMAL: + if ((usb_temp >= battery->overheatlimit_threshold) && + !use_usb_temp) { + pr_info("%s: Usb Temp over than %d (usb_thm : %d)\n", __func__, + battery->overheatlimit_threshold, usb_temp); + battery->usb_conn_status = USB_CONN_OVERHEATLIMIT; + } else { + if (use_usb_temp) + battery->usb_conn_status = sec_usb_temp_gap_check(battery, true, usb_temp); + } + break; + case USB_CONN_OVERHEATLIMIT: + if (usb_temp <= battery->overheatlimit_recovery) + battery->usb_conn_status = USB_CONN_NORMAL; + break; + case USB_CONN_GAP_OVER1: + case USB_CONN_GAP_OVER2: + battery->usb_conn_status = sec_usb_temp_gap_check(battery, false, usb_temp); + break; + default: + break; + } +} + +int sec_usb_protection_gap_check(struct sec_battery_info *battery, bool tmp_chk, int thm1_temp, int thm2_temp) +{ + int gap = 0; + + if (thm1_temp > thm2_temp) + gap = thm1_temp - thm2_temp; + + if (tmp_chk) { /* check usb temp gap */ + if ((thm1_temp >= battery->usb_protection_temp) && + (gap >= battery->temp_gap_bat_usb)) { + pr_info("%s: Temp gap between THM1 temp and THM2 temp : %d\n", __func__, gap); + return USB_CONN_GAP_OVER1; + } else if ((thm1_temp >= battery->overheatlimit_threshold) && + (gap >= (battery->temp_gap_bat_usb - 100))) { + pr_info("%s: THM1 Temp (%d) over and gap between THM1 temp and THM2 temp : %d\n", + __func__, thm1_temp, gap); + return USB_CONN_GAP_OVER2; + } + } else { /* recover check */ + if ((battery->usb_conn_status == USB_CONN_GAP_OVER1) && + (thm1_temp >= battery->usb_protection_temp)) { + return USB_CONN_GAP_OVER1; + + } else if ((battery->usb_conn_status == USB_CONN_GAP_OVER2) && + (thm1_temp >= battery->overheatlimit_threshold)) { + return USB_CONN_GAP_OVER2; + } + } + pr_info("%s: %s -> NORMAL\n", __func__, sec_usb_conn_str(battery->usb_conn_status)); + + return USB_CONN_NORMAL; +} + +void sec_usb_protection(struct sec_battery_info *battery) +{ + // gap = THM1_temperature - THM2_temperature + int thm1_temp = battery->usb_temp; // default for flagship models, THM1 = USB_THM + int thm2_temp = battery->temperature; // default for flagship models, THM2 = BAT_THM + + if (battery->pdata->usb_thm_info.check_type == SEC_BATTERY_TEMP_CHECK_NONE) { + // there is no USB_THM, mass models, THM1 = BAT_THM, THM2 = CHG_THM + battery->usb_temp = battery->temperature; + thm1_temp = battery->temperature; + thm2_temp = battery->chg_temp; + } else if (battery->pdata->mass_with_usb_thm) { + // there is USB_THM, mass models, THM1 = USB_THM, THM2 = CHG_THM + thm1_temp = battery->usb_temp; + thm2_temp = battery->chg_temp; + } +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + /* select low temp thermistor */ + if (thm2_temp > battery->sub_bat_temp) + thm2_temp = battery->sub_bat_temp; +#endif + + switch (battery->usb_conn_status) { + case USB_CONN_NORMAL: + battery->usb_conn_status = sec_usb_protection_gap_check(battery, true, thm1_temp, thm2_temp); + break; + case USB_CONN_GAP_OVER1: + case USB_CONN_GAP_OVER2: + battery->usb_conn_status = sec_usb_protection_gap_check(battery, false, thm1_temp, thm2_temp); + break; + default: + pr_info("%s: Unknown usb_conn_status(%d), forced set to NORMAL\n", __func__, battery->usb_conn_status); + battery->usb_conn_status = USB_CONN_NORMAL; + break; + } +} + +void sec_bat_thermal_check(struct sec_battery_info *battery) +{ + int bat_thm = battery->temperature; + int pre_thermal_zone = battery->thermal_zone; + int voter_status = SEC_BAT_CHG_MODE_CHARGING; + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + union power_supply_propval val = {0, }; + + bat_thm = sec_bat_get_high_priority_temp(battery); +#endif + + pr_err("%s: co_c3: %d, c3_c2: %d, c2_c1: %d, c1_no: %d, no_wa: %d, wa_ov: %d, tz(%s)\n", __func__, + battery->cold_cool3_thresh, battery->cool3_cool2_thresh, battery->cool2_cool1_thresh, + battery->cool1_normal_thresh, battery->normal_warm_thresh, battery->warm_overheat_thresh, + sec_bat_thermal_zone[battery->thermal_zone]); + + if ((battery->status == POWER_SUPPLY_STATUS_DISCHARGING && battery->usb_conn_status == USB_CONN_NORMAL) || +#if defined(CONFIG_BC12_DEVICE) && defined(CONFIG_SEC_FACTORY) + battery->vbat_adc_open || +#endif + battery->skip_swelling) { + battery->health_change = false; + pr_debug("%s: DISCHARGING or 15 test mode. stop thermal check\n", __func__); + battery->thermal_zone = BAT_THERMAL_NORMAL; + battery->usb_conn_status = USB_CONN_NORMAL; + sec_vote(battery->topoff_vote, VOTER_SWELLING, false, 0); + sec_vote(battery->fcc_vote, VOTER_SWELLING, false, 0); + sec_vote(battery->fv_vote, VOTER_SWELLING, false, 0); + sec_vote(battery->input_vote, VOTER_SWELLING, false, 0); + if (battery->dchg_dc_in_swelling) + sec_vote(battery->dc_fv_vote, VOTER_SWELLING, false, 0); + sec_vote(battery->chgen_vote, VOTER_SWELLING, false, 0); + sec_vote(battery->chgen_vote, VOTER_CHANGE_CHGMODE, false, 0); + sec_vote(battery->iv_vote, VOTER_CHANGE_CHGMODE, false, 0); + sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_SWELLING_MODE); + sec_bat_set_threshold(battery, battery->cable_type); + return; + } + + if (battery->pdata->bat_thm_info.check_type == SEC_BATTERY_TEMP_CHECK_NONE) { + pr_err("%s: BAT_THM, Invalid Temp Check Type\n", __func__); + return; + } else { + /* COLD - COOL3 - COOL2 - COOL1 - NORMAL - WARM - OVERHEAT - OVERHEATLIMIT*/ + if (battery->pdata->support_usb_conn_check) { + if (battery->usb_conn_status == USB_CONN_NORMAL) /* 2023.03.20 new concept */ + sec_usb_conn_protection(battery); + } else if (battery->pdata->usb_protection) /* 2022.03.03 new concept */ + sec_usb_protection(battery); + else /* original usb protection concept */ + sec_usb_thm_overheatlimit(battery); + + /* Fix usb_conn_status at the request of Reliability Group Test */ + if (battery->current_event & SEC_BAT_CURRENT_EVENT_TEMP_CTRL_TEST) + battery->usb_conn_status = USB_CONN_NORMAL; + + if (battery->usb_conn_status != USB_CONN_NORMAL) { + battery->thermal_zone = BAT_THERMAL_OVERHEATLIMIT; + } else if (bat_thm >= battery->normal_warm_thresh) { + if (bat_thm >= battery->warm_overheat_thresh) { + battery->thermal_zone = BAT_THERMAL_OVERHEAT; + } else { + battery->thermal_zone = BAT_THERMAL_WARM; + } + } else if (bat_thm <= battery->cool1_normal_thresh) { + if (bat_thm <= battery->cold_cool3_thresh) { + battery->thermal_zone = BAT_THERMAL_COLD; + } else if (bat_thm <= battery->cool3_cool2_thresh) { + battery->thermal_zone = BAT_THERMAL_COOL3; + } else if (bat_thm <= battery->cool2_cool1_thresh) { + battery->thermal_zone = BAT_THERMAL_COOL2; + } else { + battery->thermal_zone = BAT_THERMAL_COOL1; + } + } else { + battery->thermal_zone = BAT_THERMAL_NORMAL; + } + } + + if (pre_thermal_zone != battery->thermal_zone) { + battery->bat_thm_count++; + + if (battery->bat_thm_count < battery->pdata->temp_check_count) { + pr_info("%s : bat_thm_count %d/%d\n", __func__, + battery->bat_thm_count, battery->pdata->temp_check_count); + battery->thermal_zone = pre_thermal_zone; + return; + } + + /* FPDO DC concept */ + if (battery->cable_type == SEC_BATTERY_CABLE_FPDO_DC && battery->thermal_zone != BAT_THERMAL_NORMAL) { + union power_supply_propval value = {0, }; + + value.intval = 0; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_REFRESH_CHARGING_SOURCE, value); + + } + + pr_info("%s: thermal zone update (%s -> %s), bat_thm(%d), usb_thm(%d)\n", __func__, + sec_bat_thermal_zone[pre_thermal_zone], + sec_bat_thermal_zone[battery->thermal_zone], bat_thm, battery->usb_temp); + battery->health_change = true; + battery->bat_thm_count = 0; + + pr_info("%s : SAFETY TIME RESET!\n", __func__); + battery->expired_time = battery->pdata->expired_time; + battery->prev_safety_time = 0; + + sec_bat_set_threshold(battery, battery->cable_type); + sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_SWELLING_MODE); + + switch (battery->thermal_zone) { + case BAT_THERMAL_OVERHEATLIMIT: + if (battery->status != POWER_SUPPLY_STATUS_DISCHARGING) { + sec_bat_set_health(battery, POWER_SUPPLY_EXT_HEALTH_OVERHEATLIMIT); + sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_NOT_CHARGING); + sec_vote(battery->chgen_vote, VOTER_SWELLING, true, SEC_BAT_CHG_MODE_BUCK_OFF); + battery->cisd.data[CISD_DATA_UNSAFETY_TEMPERATURE]++; + battery->cisd.data[CISD_DATA_UNSAFE_TEMPERATURE_PER_DAY]++; + sec_vote(battery->iv_vote, VOTER_MUIC_ABNORMAL, true, SEC_INPUT_VOLTAGE_5V); +#if !defined(CONFIG_SEC_FACTORY) + if (!sec_bat_get_lpmode()) { +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + muic_set_hiccup_mode(1); +#endif + if (is_pd_wire_type(battery->cable_type) || battery->pdata->mass_with_usb_thm) + sec_pd_manual_ccopen_req(1); + } + } else { // if already in discharging, just set misc_event + pr_info("%s: Set BATT_MISC_EVENT_TEMP_HICCUP_TYPE for discharging\n", __func__); + sec_bat_set_misc_event(battery, + BATT_MISC_EVENT_TEMP_HICCUP_TYPE, BATT_MISC_EVENT_TEMP_HICCUP_TYPE); + battery->usb_conn_status = USB_CONN_NORMAL; +#endif + } + store_battery_log( + "OHL:%d%%,%dmV,usb_conn_st(%d),tbat(%d),tusb(%d),ct(%s)", + battery->capacity, battery->voltage_now, + battery->usb_conn_status, bat_thm, + battery->usb_temp, sb_get_ct_str(battery->cable_type)); + break; + case BAT_THERMAL_OVERHEAT: + battery->usb_conn_status = USB_CONN_NORMAL; + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_HIGH_TEMP_SWELLING, + SEC_BAT_CURRENT_EVENT_SWELLING_MODE); + if (battery->voltage_now > battery->pdata->high_temp_float) { +#if defined(CONFIG_WIRELESS_TX_MODE) + if (get_sec_vote_result(battery->iv_vote) > SEC_INPUT_VOLTAGE_5V) { + sec_vote(battery->iv_vote, VOTER_CHANGE_CHGMODE, true, SEC_INPUT_VOLTAGE_5V); + sec_vote(battery->chgen_vote, VOTER_CHANGE_CHGMODE, true, SEC_BAT_CHG_MODE_NOT_SET); + } +#endif + sec_vote(battery->chgen_vote, VOTER_SWELLING, true, SEC_BAT_CHG_MODE_BUCK_OFF); + } else { + sec_vote(battery->chgen_vote, VOTER_SWELLING, true, SEC_BAT_CHG_MODE_CHARGING_OFF); + sec_vote(battery->iv_vote, VOTER_CHANGE_CHGMODE, false, 0); + } + sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_OVERHEAT); + sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_NOT_CHARGING); + battery->cisd.data[CISD_DATA_UNSAFETY_TEMPERATURE]++; + battery->cisd.data[CISD_DATA_UNSAFE_TEMPERATURE_PER_DAY]++; + store_battery_log( + "OH:%d%%,%dmV,tbat(%d),ct(%s)", + battery->capacity, battery->voltage_now, + bat_thm, sb_get_ct_str(battery->cable_type)); + break; + case BAT_THERMAL_WARM: + battery->usb_conn_status = USB_CONN_NORMAL; + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_HIGH_TEMP_SWELLING, + SEC_BAT_CURRENT_EVENT_SWELLING_MODE); + sec_bat_thermal_charging_status(battery); + if (battery->voltage_now > battery->pdata->high_temp_float) { + if ((battery->wc_tx_enable || battery->uno_en) && + (is_hv_wire_type(battery->cable_type) || + is_pd_wire_type(battery->cable_type))) { + sec_vote(battery->chgen_vote, VOTER_SWELLING, true, + SEC_BAT_CHG_MODE_CHARGING_OFF); + } else { + sec_vote(battery->chgen_vote, VOTER_SWELLING, + true, SEC_BAT_CHG_MODE_BUCK_OFF); + } + sec_bat_thermal_warm_wc_fod(battery, false); + } else if ((battery->voltage_now > battery->pdata->swelling_high_rechg_voltage) && + !battery->pdata->chgen_over_swell_rechg_vol) { + sec_vote(battery->chgen_vote, VOTER_SWELLING, true, SEC_BAT_CHG_MODE_CHARGING_OFF); + sec_bat_thermal_warm_wc_fod(battery, false); + } else { + sec_vote(battery->chgen_vote, VOTER_SWELLING, true, SEC_BAT_CHG_MODE_CHARGING); + sec_vote(battery->input_vote, VOTER_SWELLING, false, 0); + sec_bat_thermal_warm_wc_fod(battery, true); + } + + if (is_wireless_all_type(battery->cable_type)) { + sec_vote(battery->fcc_vote, VOTER_SWELLING, true, battery->pdata->wireless_warm_current); + battery->cisd.data[CISD_DATA_WC_HIGH_TEMP_SWELLING]++; + battery->cisd.data[CISD_DATA_WC_HIGH_TEMP_SWELLING_PER_DAY]++; + } else { + sec_vote(battery->fcc_vote, VOTER_SWELLING, true, battery->pdata->wire_warm_current); + battery->cisd.data[CISD_DATA_HIGH_TEMP_SWELLING]++; + battery->cisd.data[CISD_DATA_HIGH_TEMP_SWELLING_PER_DAY]++; + } + sec_vote(battery->fv_vote, VOTER_SWELLING, true, battery->pdata->high_temp_float); + if (battery->dchg_dc_in_swelling) + sec_vote(battery->dc_fv_vote, VOTER_SWELLING, true, battery->pdata->high_temp_float); + sec_vote(battery->topoff_vote, VOTER_SWELLING, true, battery->pdata->full_check_current_2nd); + sec_vote(battery->iv_vote, VOTER_CHANGE_CHGMODE, false, 0); + + sec_bat_thermal_charging_health(battery); + store_battery_log( + "THM_W:%d%%,%dmV,tbat(%d),ct(%s)", + battery->capacity, battery->voltage_now, + bat_thm, sb_get_ct_str(battery->cable_type)); + break; + case BAT_THERMAL_COOL1: + battery->usb_conn_status = USB_CONN_NORMAL; + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING_COOL1, + SEC_BAT_CURRENT_EVENT_SWELLING_MODE); + sec_bat_thermal_charging_status(battery); + if (is_wireless_all_type(battery->cable_type)) { + sec_vote(battery->fcc_vote, VOTER_SWELLING, true, battery->pdata->wireless_cool1_current); + } else { + sec_vote(battery->fcc_vote, VOTER_SWELLING, true, battery->pdata->wire_cool1_current); + } + sec_vote(battery->fv_vote, VOTER_SWELLING, true, battery->pdata->low_temp_float); + if (battery->dchg_dc_in_swelling) + sec_vote(battery->dc_fv_vote, VOTER_SWELLING, true, battery->pdata->low_temp_float); + sec_vote(battery->input_vote, VOTER_SWELLING, false, 0); + sec_vote(battery->topoff_vote, VOTER_SWELLING, false, 0); + sec_vote(battery->chgen_vote, VOTER_SWELLING, true, SEC_BAT_CHG_MODE_CHARGING); + sec_vote(battery->iv_vote, VOTER_CHANGE_CHGMODE, false, 0); + sec_bat_thermal_charging_health(battery); + store_battery_log( + "THM_C1:%d%%,%dmV,tbat(%d),ct(%s)", + battery->capacity, battery->voltage_now, + bat_thm, sb_get_ct_str(battery->cable_type)); + break; + case BAT_THERMAL_COOL2: + battery->usb_conn_status = USB_CONN_NORMAL; + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING_COOL2, + SEC_BAT_CURRENT_EVENT_SWELLING_MODE); + sec_bat_thermal_charging_status(battery); + if (is_wireless_all_type(battery->cable_type)) { + sec_vote(battery->fcc_vote, VOTER_SWELLING, true, battery->pdata->wireless_cool2_current); + } else { + sec_vote(battery->fcc_vote, VOTER_SWELLING, true, battery->pdata->wire_cool2_current); + } + sec_vote(battery->fv_vote, VOTER_SWELLING, true, battery->pdata->low_temp_float); + if (battery->dchg_dc_in_swelling) + sec_vote(battery->dc_fv_vote, VOTER_SWELLING, true, battery->pdata->low_temp_float); + sec_vote(battery->input_vote, VOTER_SWELLING, false, 0); + sec_vote(battery->topoff_vote, VOTER_SWELLING, false, 0); + sec_vote(battery->chgen_vote, VOTER_SWELLING, true, SEC_BAT_CHG_MODE_CHARGING); + sec_vote(battery->iv_vote, VOTER_CHANGE_CHGMODE, false, 0); + sec_bat_thermal_charging_health(battery); + store_battery_log( + "THM_C2:%d%%,%dmV,tbat(%d),ct(%s)", + battery->capacity, battery->voltage_now, + bat_thm, sb_get_ct_str(battery->cable_type)); + break; + case BAT_THERMAL_COOL3: + battery->usb_conn_status = USB_CONN_NORMAL; + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING_COOL3, + SEC_BAT_CURRENT_EVENT_SWELLING_MODE); + sec_bat_thermal_charging_status(battery); + if (is_wireless_all_type(battery->cable_type)) { + sec_vote(battery->fcc_vote, VOTER_SWELLING, true, battery->pdata->wireless_cool3_current); + } else { + sec_vote(battery->fcc_vote, VOTER_SWELLING, true, battery->pdata->wire_cool3_current); + } + sec_vote(battery->fv_vote, VOTER_SWELLING, true, battery->pdata->low_temp_cool3_float); + if (battery->dchg_dc_in_swelling) + sec_vote(battery->dc_fv_vote, VOTER_SWELLING, true, + battery->pdata->low_temp_cool3_float); + sec_vote(battery->input_vote, VOTER_SWELLING, false, 0); + sec_vote(battery->topoff_vote, VOTER_SWELLING, true, battery->pdata->full_check_current_2nd); + sec_vote(battery->chgen_vote, VOTER_SWELLING, true, SEC_BAT_CHG_MODE_CHARGING); + sec_vote(battery->iv_vote, VOTER_CHANGE_CHGMODE, false, 0); + sec_bat_thermal_charging_health(battery); + store_battery_log( + "THM_C3:%d%%,%dmV,tbat(%d),ct(%s)", + battery->capacity, battery->voltage_now, + bat_thm, sb_get_ct_str(battery->cable_type)); + break; + case BAT_THERMAL_COLD: + battery->usb_conn_status = USB_CONN_NORMAL; + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING_COOL3, + SEC_BAT_CURRENT_EVENT_SWELLING_MODE); + sec_vote(battery->input_vote, VOTER_SWELLING, false, 0); + sec_vote(battery->chgen_vote, VOTER_SWELLING, true, SEC_BAT_CHG_MODE_CHARGING_OFF); + sec_vote(battery->iv_vote, VOTER_CHANGE_CHGMODE, false, 0); + sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_COLD); + sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_NOT_CHARGING); + battery->cisd.data[CISD_DATA_UNSAFETY_TEMPERATURE]++; + battery->cisd.data[CISD_DATA_UNSAFE_TEMPERATURE_PER_DAY]++; + store_battery_log( + "THM_C:%d%%,%dmV,tbat(%d),ct(%s)", + battery->capacity, battery->voltage_now, + bat_thm, sb_get_ct_str(battery->cable_type)); + break; + case BAT_THERMAL_NORMAL: + default: + battery->usb_conn_status = USB_CONN_NORMAL; + sec_bat_thermal_charging_status(battery); + sec_vote(battery->fcc_vote, VOTER_SWELLING, false, 0); + sec_vote(battery->fv_vote, VOTER_SWELLING, false, 0); + if (battery->dchg_dc_in_swelling) + sec_vote(battery->dc_fv_vote, VOTER_SWELLING, false, 0); + sec_vote(battery->topoff_vote, VOTER_SWELLING, false, 0); + sec_vote(battery->chgen_vote, VOTER_SWELLING, false, 0); + sec_vote(battery->input_vote, VOTER_SWELLING, false, 0); + sec_vote(battery->iv_vote, VOTER_CHANGE_CHGMODE, false, 0); + sec_bat_thermal_charging_health(battery); + store_battery_log( + "THM_N:%d%%,%dmV,tbat(%d),ct(%s)", + battery->capacity, battery->voltage_now, + bat_thm, sb_get_ct_str(battery->cable_type)); + break; + } + } else { /* pre_thermal_zone == battery->thermal_zone */ + battery->health_change = false; + + switch (battery->thermal_zone) { + case BAT_THERMAL_OVERHEAT: + /* does not need buck control at low temperatures */ + if (battery->health == POWER_SUPPLY_HEALTH_OVERHEAT) { + int v_ref = battery->pdata->high_temp_float - battery->pdata->buck_recovery_margin; + + if (get_sec_voter_status(battery->chgen_vote, VOTER_SWELLING, &voter_status) < 0) + pr_err("%s: INVALID VOTER ID\n", __func__); + pr_info("%s: voter_status: %d\n", __func__, voter_status); + if ((voter_status == SEC_BAT_CHG_MODE_BUCK_OFF) && + (battery->voltage_now < v_ref)) { + pr_info("%s: Vnow(%dmV) < %dmV, buck on\n", __func__, + battery->voltage_now, v_ref); + sec_vote(battery->chgen_vote, VOTER_SWELLING, true, + SEC_BAT_CHG_MODE_CHARGING_OFF); + } + sec_bat_thermal_warm_wc_fod(battery, false); + } + break; + case BAT_THERMAL_WARM: + if (battery->health == POWER_SUPPLY_HEALTH_GOOD) { + int v_ref = battery->pdata->high_temp_float - battery->pdata->buck_recovery_margin; + int voltage = battery->voltage_now; +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + voltage = max(battery->voltage_avg_main, battery->voltage_avg_sub); +#endif + + if (get_sec_voter_status(battery->chgen_vote, VOTER_SWELLING, &voter_status) < 0) + pr_err("%s: INVALID VOTER ID\n", __func__); + pr_info("%s: voter_status: %d\n", __func__, voter_status); + if (voter_status == SEC_BAT_CHG_MODE_CHARGING) { + if (sec_bat_check_fullcharged(battery)) { + pr_info("%s: battery thermal zone WARM. Full charged.\n", __func__); + sec_bat_thermal_warm_wc_fod(battery, false); + sec_vote(battery->chgen_vote, VOTER_SWELLING, true, + SEC_BAT_CHG_MODE_CHARGING_OFF); +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + /* Enable supplement mode for swelling full charging done, should cut off charger then limiter sequence */ + val.intval = 1; + psy_do_property(battery->pdata->dual_battery_name, set, + POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED, val); +#endif + } + } else if ((voter_status == SEC_BAT_CHG_MODE_CHARGING_OFF || + voter_status == SEC_BAT_CHG_MODE_BUCK_OFF) && + (voltage <= battery->pdata->swelling_high_rechg_voltage)) { + pr_info("%s: thermal zone WARM. charging recovery. Voltage: %d\n", + __func__, voltage); + battery->expired_time = battery->pdata->expired_time; + battery->prev_safety_time = 0; + sec_vote(battery->input_vote, VOTER_SWELLING, false, 0); + sec_bat_thermal_warm_wc_fod(battery, true); + sec_vote(battery->fv_vote, VOTER_SWELLING, true, + battery->pdata->high_temp_float); + if (battery->dchg_dc_in_swelling) + sec_vote(battery->dc_fv_vote, VOTER_SWELLING, true, + battery->pdata->high_temp_float); + sec_vote(battery->chgen_vote, VOTER_FULL_CHARGE, false, 0); + sec_vote(battery->chgen_vote, VOTER_SWELLING, true, + SEC_BAT_CHG_MODE_CHARGING); + } else if (voter_status == SEC_BAT_CHG_MODE_BUCK_OFF && voltage < v_ref) { + pr_info("%s: Voltage(%dmV) < %dmV, buck on\n", __func__, + voltage, v_ref); + sec_bat_thermal_warm_wc_fod(battery, false); + sec_vote(battery->chgen_vote, VOTER_SWELLING, true, + SEC_BAT_CHG_MODE_CHARGING_OFF); + } + } + break; + default: + break; + } + } + + return; +} +EXPORT_SYMBOL_KUNIT(sec_bat_thermal_check); diff --git a/drivers/battery/common/sec_battery_ttf.c b/drivers/battery/common/sec_battery_ttf.c new file mode 100644 index 000000000000..69a0547d6a42 --- /dev/null +++ b/drivers/battery/common/sec_battery_ttf.c @@ -0,0 +1,413 @@ +/* + * sec_battery_ttf.c + * Samsung Mobile Battery Driver + * + * Copyright (C) 2019 Samsung Electronics + * + * + * 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. + */ +#include "sec_battery.h" +#include "sec_battery_ttf.h" + +#define is_ttf_thermal_zone(thermal_zone) ( \ + thermal_zone == BAT_THERMAL_NORMAL || \ + thermal_zone == BAT_THERMAL_COOL1 || \ + thermal_zone == BAT_THERMAL_COOL2) + +static bool skip_ttf_event(unsigned int misc_event) +{ + return (misc_event & BATT_MISC_EVENT_PASS_THROUGH); +} + +static bool check_ttf_state(unsigned int capacity, int bat_sts) +{ + return ((bat_sts == POWER_SUPPLY_STATUS_CHARGING) || + (bat_sts == POWER_SUPPLY_STATUS_FULL && capacity != 100)); +} + +static int get_cc_cv_time(struct sec_battery_info * battery, int ttf_curr, int soc, bool minimum) +{ + struct sec_cv_slope *cv_data = battery->ttf_d->cv_data; + int i, design_cap = battery->ttf_d->ttf_capacity; + int cc_time = 0, cv_time = 0; + int minimum_time = 0; + + for (i = 0; i < battery->ttf_d->cv_data_length; i++) { + if (ttf_curr >= cv_data[i].fg_current) + break; + } + i = i >= battery->ttf_d->cv_data_length ? battery->ttf_d->cv_data_length - 1 : i; + if (cv_data[i].soc < soc) { + for (i = 0; i < battery->ttf_d->cv_data_length; i++) { + if (soc <= cv_data[i].soc) + break; + } + cv_time = + ((cv_data[i - 1].time - cv_data[i].time) * (cv_data[i].soc - soc) + / (cv_data[i].soc - cv_data[i - 1].soc)) + cv_data[i].time; + } else { /* CC mode || NONE */ + cv_time = cv_data[i].time; + cc_time = + design_cap * (cv_data[i].soc - soc) / ttf_curr * 3600 / 1000; + pr_debug("%s: cc_time: %d\n", __func__, cc_time); + if (cc_time < 0) + cc_time = 0; + } + + pr_info("%s: cap: %d, soc: %4d, T: %6d, avg: %4d, cv soc: %4d, i: %4d, val: %d, minimum:%d\n", + __func__, design_cap, soc, cv_time + cc_time, + battery->current_avg, cv_data[i].soc, i, ttf_curr, minimum); + + if (minimum) + minimum_time = 60; + + return ((cc_time + cv_time >= 0) ? (cc_time + cv_time + minimum_time) : minimum_time); +} + +static int get_current_soc( char *name) +{ + union power_supply_propval value = {0, }; + + value.intval = SEC_FUELGAUGE_CAPACITY_TYPE_DYNAMIC_SCALE; + psy_do_property(name, get, + POWER_SUPPLY_PROP_CAPACITY, value); + + return value.intval; +} + +int sec_calc_ttf(struct sec_battery_info * battery, unsigned int ttf_curr) +{ + struct sec_cv_slope *cv_data = battery->ttf_d->cv_data; + int total_time; + + if (!cv_data || (ttf_curr <= 0)) { + pr_info("%s: no cv_data or val: %d\n", __func__, ttf_curr); + return -1; + } + + total_time = get_cc_cv_time(battery, ttf_curr, get_current_soc(battery->pdata->fuelgauge_name), true); + if (is_full_capacity(battery->fs)) { + int now_full_cap = get_full_capacity(battery->fs); + + pr_info("%s: time to %d percent\n", __func__, now_full_cap); + total_time -= get_cc_cv_time(battery, ttf_curr, (now_full_cap * 10), false); + } + + return total_time; +} + +int sec_get_ttf_standard_curr(struct sec_battery_info *battery) +{ + int charge = 0; + + if (is_hv_wire_12v_type(battery->cable_type)) { + charge = battery->ttf_d->ttf_hv_12v_charge_current; +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + } else if (battery->cable_type == SEC_BATTERY_CABLE_WIRELESS_EPP || + battery->cable_type == SEC_BATTERY_CABLE_WIRELESS_EPP_FAKE) { + if (battery->wc20_rx_power >= WFC21_WIRELESS_POWER) // need to fix hardcoding + charge = battery->ttf_d->ttf_wc21_wireless_charge_current; + else if (battery->wc20_rx_power >= WFC20_WIRELESS_POWER) + charge = battery->ttf_d->ttf_wc20_wireless_charge_current; + else if (battery->wc20_rx_power >= WFC10_WIRELESS_POWER) + charge = battery->ttf_d->ttf_hv_wireless_charge_current; + else + charge = battery->ttf_d->ttf_wireless_charge_current; + } else if (is_hv_wireless_type(battery->cable_type) || + battery->cable_type == SEC_BATTERY_CABLE_PREPARE_WIRELESS_HV || + battery->cable_type == SEC_BATTERY_CABLE_PREPARE_WIRELESS_20) { + unsigned int wc_budg_pwr; + union power_supply_propval value = {0, }; + + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_TX_PWR_BUDG, value); + wc_budg_pwr = value.intval; + pr_info("%s : POWER_SUPPLY_EXT_PROP_TX_PWR_BUDG(%d)\n", + __func__, wc_budg_pwr); + + if (sec_bat_hv_wc_normal_mode_check(battery)) + charge = battery->ttf_d->ttf_wireless_charge_current; + else if ((battery->cable_type == SEC_BATTERY_CABLE_PREPARE_WIRELESS_20 && !sec_bat_get_lpmode()) || + battery->cable_type == SEC_BATTERY_CABLE_HV_WIRELESS_20) { + if (battery->wc20_rx_power >= WFC21_WIRELESS_POWER || wc_budg_pwr >= RX_POWER_15W) + charge = battery->ttf_d->ttf_wc21_wireless_charge_current; + else if (battery->wc20_rx_power >= WFC20_WIRELESS_POWER || wc_budg_pwr >= RX_POWER_12W) + charge = battery->ttf_d->ttf_wc20_wireless_charge_current; + else if (battery->wc20_rx_power >= WFC10_WIRELESS_POWER || wc_budg_pwr >= RX_POWER_7_5W) + charge = battery->ttf_d->ttf_hv_wireless_charge_current; + else + charge = battery->ttf_d->ttf_wireless_charge_current; + } + else + charge = battery->ttf_d->ttf_hv_wireless_charge_current; + } else if (is_nv_wireless_type(battery->cable_type)) { + charge = battery->ttf_d->ttf_wireless_charge_current; +#endif + } else if (is_hv_wire_type(battery->cable_type)) { + charge = battery->ttf_d->ttf_hv_charge_current; + } else if (is_pd_apdo_wire_type(battery->cable_type) || + (is_pd_fpdo_wire_type(battery->cable_type) && battery->hv_pdo)) { + if (battery->pd_max_charge_power > HV_CHARGER_STATUS_STANDARD4) { + charge = battery->ttf_d->ttf_dc45_charge_current; + } else if (battery->pd_max_charge_power > HV_CHARGER_STATUS_STANDARD3) { + charge = battery->ttf_d->ttf_dc25_charge_current; + } else if (battery->pd_max_charge_power <= battery->pdata->pd_charging_charge_power && + battery->pdata->charging_current[battery->cable_type].fast_charging_current >= \ + battery->pdata->max_charging_current) { /* same PD power with AFC */ + charge = battery->ttf_d->ttf_hv_charge_current; + } else { /* other PD charging */ + charge = (battery->pd_max_charge_power / 5) > battery->pdata->charging_current[battery->cable_type].fast_charging_current ? + battery->pdata->charging_current[battery->cable_type].fast_charging_current : (battery->pd_max_charge_power / 5); + } + } else { + charge = (battery->max_charge_power / 5) > battery->pdata->charging_current[battery->cable_type].fast_charging_current ? + battery->pdata->charging_current[battery->cable_type].fast_charging_current : (battery->max_charge_power / 5); + } + + if (battery->cable_type == SEC_BATTERY_CABLE_FPDO_DC) + charge = battery->ttf_d->ttf_fpdo_dc_charge_current; + + return charge; +} +EXPORT_SYMBOL_KUNIT(sec_get_ttf_standard_curr); + +static int check_ttf_valid(struct sec_ttf_data *ttf_d, int ttf) +{ + if (ttf_d->timetofull < 0) { + ttf_d->old_timetofull = ttf; + return ttf; + } + + if (ttf > ttf_d->timetofull) { + if (ttf > ttf_d->old_timetofull) { + int ret = ttf_d->old_timetofull; + + ttf_d->old_timetofull = ttf; + return ret; + } + } + + ttf_d->old_timetofull = ttf; + return ttf; +} + +static void init_ttf(struct sec_ttf_data *ttf_d) +{ + ttf_d->timetofull = -1; + ttf_d->old_timetofull = -1; +} + +void sec_bat_calc_time_to_full(struct sec_battery_info * battery) +{ + if (delayed_work_pending(&battery->ttf_d->timetofull_work)) { + pr_info("%s: keep time_to_full(%5d sec)\n", __func__, battery->ttf_d->timetofull); + } else if (check_ttf_state(battery->capacity, battery->status) && + !battery->wc_tx_enable && !skip_ttf_event(battery->misc_event)) { + int charge = 0, ttf = 0; + + charge = sec_get_ttf_standard_curr(battery); + ttf = sec_calc_ttf(battery, charge); + battery->ttf_d->timetofull = check_ttf_valid(battery->ttf_d, ttf); + dev_info(battery->dev, "%s: T: %5d|%5d sec, passed time: %5ld, current: %d\n", + __func__, battery->ttf_d->timetofull, ttf, battery->charging_passed_time, charge); + } else { + init_ttf(battery->ttf_d); + } +} + +int sec_ttf_parse_dt(struct sec_battery_info *battery) +{ + struct device_node *np; + struct sec_ttf_data *pdata = battery->ttf_d; + sec_battery_platform_data_t *bpdata = battery->pdata; + int ret = 0, len = 0; + const u32 *p; + + pdata->pdev = battery; + np = of_find_node_by_name(NULL, "battery"); + if (!np) { + pr_info("%s: np NULL\n", __func__); + return 1; + } + + ret = of_property_read_u32(np, "battery,ttf_hv_12v_charge_current", + &pdata->ttf_hv_12v_charge_current); + if (ret) { + pdata->ttf_hv_12v_charge_current = + bpdata->charging_current[SEC_BATTERY_CABLE_12V_TA].fast_charging_current; + pr_info("%s: ttf_hv_12v_charge_current is Empty, Default value %d\n", + __func__, pdata->ttf_hv_12v_charge_current); + } + ret = of_property_read_u32(np, "battery,ttf_hv_charge_current", + &pdata->ttf_hv_charge_current); + if (ret) { + pdata->ttf_hv_charge_current = + bpdata->charging_current[SEC_BATTERY_CABLE_9V_TA].fast_charging_current; + pr_info("%s: ttf_hv_charge_current is Empty, Default value %d\n", + __func__, pdata->ttf_hv_charge_current); + } + + ret = of_property_read_u32(np, "battery,ttf_hv_wireless_charge_current", + &pdata->ttf_hv_wireless_charge_current); + if (ret) { + pdata->ttf_hv_wireless_charge_current = + bpdata->charging_current[SEC_BATTERY_CABLE_HV_WIRELESS].fast_charging_current - 300; + pr_info("%s: ttf_hv_wireless_charge_current is Empty, Default value %d\n", + __func__, pdata->ttf_hv_wireless_charge_current); + } + + ret = of_property_read_u32(np, "battery,ttf_wc20_wireless_charge_current", + &pdata->ttf_wc20_wireless_charge_current); + if (ret) { + pdata->ttf_wc20_wireless_charge_current = + bpdata->charging_current[SEC_BATTERY_CABLE_HV_WIRELESS_20].fast_charging_current - 300; + pr_info("%s: ttf_wc20_wireless_charge_current is Empty, Default value %d\n", + __func__, pdata->ttf_wc20_wireless_charge_current); + } + + ret = of_property_read_u32(np, "battery,ttf_wc21_wireless_charge_current", + &pdata->ttf_wc21_wireless_charge_current); + if (ret) { + pdata->ttf_wc21_wireless_charge_current = + bpdata->charging_current[SEC_BATTERY_CABLE_HV_WIRELESS_20].fast_charging_current - 300; + pr_info("%s: ttf_wc21_wireless_charge_current is Empty, Default value %d\n", + __func__, pdata->ttf_wc21_wireless_charge_current); + } + + ret = of_property_read_u32(np, "battery,ttf_wireless_charge_current", + &pdata->ttf_wireless_charge_current); + if (ret) { + pdata->ttf_wireless_charge_current = + bpdata->charging_current[SEC_BATTERY_CABLE_WIRELESS].input_current_limit; + pr_info("%s: ttf_wireless_charge_current is Empty, Default value %d\n", + __func__, pdata->ttf_wireless_charge_current); + } + + ret = of_property_read_u32(np, "battery,ttf_dc25_charge_current", + &pdata->ttf_dc25_charge_current); + if (ret) { + pdata->ttf_dc25_charge_current = + bpdata->charging_current[SEC_BATTERY_CABLE_9V_TA].fast_charging_current; + pr_info("%s: ttf_dc25_charge_current is Empty, Default value %d\n", + __func__, pdata->ttf_dc25_charge_current); + } + + ret = of_property_read_u32(np, "battery,ttf_dc45_charge_current", + &pdata->ttf_dc45_charge_current); + if (ret) { + pdata->ttf_dc45_charge_current = pdata->ttf_dc25_charge_current; + pr_info("%s: ttf_dc45_charge_current is Empty, Default value %d \n", + __func__, pdata->ttf_dc45_charge_current); + } + + ret = of_property_read_u32(np, "battery,ttf_fpdo_dc_charge_current", + &pdata->ttf_fpdo_dc_charge_current); + if (ret) { + pdata->ttf_fpdo_dc_charge_current = pdata->ttf_hv_charge_current; + pr_info("%s: ttf_fpdo_dc_charge_current is Empty, Default value %d\n", + __func__, pdata->ttf_fpdo_dc_charge_current); + } + + ret = of_property_read_u32(np, "battery,ttf_capacity", + &pdata->ttf_capacity); + if (ret < 0) { + pr_err("%s error reading capacity_calculation_type %d\n", __func__, ret); + pdata->ttf_capacity = bpdata->battery_full_capacity; + } + + p = of_get_property(np, "battery,cv_data", &len); + if (p) { + pdata->cv_data = kzalloc(len, GFP_KERNEL); + pdata->cv_data_length = len / sizeof(struct sec_cv_slope); + pr_err("%s: len= %ld, length= %d, %d\n", __func__, + sizeof(int) * len, len, pdata->cv_data_length); + ret = of_property_read_u32_array(np, "battery,cv_data", + (u32 *)pdata->cv_data, len / sizeof(u32)); + if (ret) { + pr_err("%s: failed to read battery->cv_data: %d\n", + __func__, ret); + kfree(pdata->cv_data); + pdata->cv_data = NULL; + } + } else { + pr_err("%s: there is not cv_data\n", __func__); + } + return 0; +} + +void sec_bat_time_to_full_work(struct work_struct *work) +{ + struct sec_ttf_data *dev = container_of(work, + struct sec_ttf_data, timetofull_work.work); + struct sec_battery_info *battery = dev->pdev; + union power_supply_propval value = {0, }; + + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, value); + battery->current_max = value.intval; + + value.intval = SEC_BATTERY_CURRENT_MA; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CURRENT_NOW, value); + battery->current_now = value.intval; + + value.intval = SEC_BATTERY_CURRENT_MA; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CURRENT_AVG, value); + battery->current_avg = value.intval; + + sec_bat_calc_time_to_full(battery); + dev_info(battery->dev, "%s:\n",__func__); + if (battery->voltage_now > 0) + battery->voltage_now--; + + power_supply_changed(battery->psy_bat); +} + +void ttf_work_start(struct sec_battery_info *battery) +{ + if (sec_bat_get_lpmode()) { + cancel_delayed_work(&battery->ttf_d->timetofull_work); + if (battery->current_event & SEC_BAT_CURRENT_EVENT_AFC) { + int work_delay = 0; + + if (!is_wireless_type(battery->cable_type)) { + work_delay = battery->pdata->pre_afc_work_delay; + } else { + work_delay = battery->pdata->pre_wc_afc_work_delay; + } + queue_delayed_work(battery->monitor_wqueue, + &battery->ttf_d->timetofull_work, msecs_to_jiffies(work_delay)); + } + } +} + +int ttf_display(unsigned int capacity, int bat_sts, int thermal_zone, int time) +{ + if (capacity == 100) + return 0; + + if (check_ttf_state(capacity, bat_sts) && + is_ttf_thermal_zone(thermal_zone)) + return time; + + return 0; +} +EXPORT_SYMBOL_KUNIT(ttf_display); + +void ttf_init(struct sec_battery_info *battery) +{ + battery->ttf_d = kzalloc(sizeof(struct sec_ttf_data), + GFP_KERNEL); + if (!battery->ttf_d) { + pr_err("Failed to allocate memory\n"); + } + sec_ttf_parse_dt(battery); + init_ttf(battery->ttf_d); + + INIT_DELAYED_WORK(&battery->ttf_d->timetofull_work, sec_bat_time_to_full_work); +} +EXPORT_SYMBOL_KUNIT(ttf_init); diff --git a/drivers/battery/common/sec_battery_ttf.h b/drivers/battery/common/sec_battery_ttf.h new file mode 100644 index 000000000000..cc60776de483 --- /dev/null +++ b/drivers/battery/common/sec_battery_ttf.h @@ -0,0 +1,64 @@ +/* + * sec_battery.h + * Samsung Mobile Battery Header + * + * + * Copyright (C) 2012 Samsung Electronics, 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 __SEC_BATTERY_TTF_H +#define __SEC_BATTERY_TTF_H __FILE__ + +struct sec_cv_slope { + int fg_current; + int soc; + int time; +}; + +struct sec_battery_info; + +struct sec_ttf_data { + void *pdev; + int timetofull; + int old_timetofull; + + unsigned int ttf_hv_12v_charge_current; + unsigned int ttf_hv_charge_current; + unsigned int ttf_hv_wireless_charge_current; + unsigned int ttf_wireless_charge_current; + unsigned int ttf_dc25_charge_current; + unsigned int ttf_dc45_charge_current; + unsigned int ttf_wc20_wireless_charge_current; + unsigned int ttf_wc21_wireless_charge_current; + unsigned int ttf_fpdo_dc_charge_current; + + struct sec_cv_slope *cv_data; + int cv_data_length; + unsigned int ttf_capacity; + + struct delayed_work timetofull_work; +}; + +int sec_calc_ttf(struct sec_battery_info * battery, unsigned int ttf_curr); +extern void sec_bat_calc_time_to_full(struct sec_battery_info * battery); +int sec_get_ttf_standard_curr(struct sec_battery_info *battery); +extern void sec_bat_time_to_full_work(struct work_struct *work); +extern void ttf_init(struct sec_battery_info *battery); +extern void ttf_work_start(struct sec_battery_info *battery); +extern int ttf_display(unsigned int capacity, int bat_sts, int thermal_zone, int time); +#ifdef CONFIG_OF +int sec_ttf_parse_dt(struct sec_battery_info *battery); +#endif + +#endif /* __SEC_BATTERY_H */ diff --git a/drivers/battery/common/sec_battery_vote.c b/drivers/battery/common/sec_battery_vote.c new file mode 100644 index 000000000000..3b69ecbf6437 --- /dev/null +++ b/drivers/battery/common/sec_battery_vote.c @@ -0,0 +1,593 @@ + +#include "sec_battery_vote.h" +#include +#include +#include + +static struct dentry *debug_root; +static struct dentry *status_all; +static LIST_HEAD(vote_list); +static DEFINE_MUTEX(vote_lock); +struct sec_voter { + int enable; + int value; + int pri; +}; +struct sec_vote { + const char * name; + int type; + int num; + struct sec_voter *voter; + const char ** voter_name; + int id; + int res; + int init_val; + struct mutex lock; + void * data; + int(*cb)(void *data, int value); + struct list_head list; + + struct dentry * root; + struct dentry * status_ent; + int force_set; + int force_val; + struct dentry * force_set_ent; +}; + +const char * none_str = "None"; +const char * force_str = "Force"; + +static int select_min(struct sec_voter *voter, int max, int *id, int *res) +{ + int i; + int pri = INT_MIN; + *res = INT_MAX; + *id = -EINVAL; + for (i = 0; i < max; i++) { + if (voter[i].enable) { + if (pri < voter[i].pri) { + *res = voter[i].value; + pri = voter[i].pri; + *id = i; + } else if (pri > voter[i].pri) { + continue; + } else if (*res > voter[i].value) { + *res = voter[i].value; + *id = i; + } + } + } + return 0; +} +static int select_max(struct sec_voter *voter, int max, int *id, int *res) +{ + int i; + int pri = INT_MIN; + *res = INT_MIN; + *id = -EINVAL; + for (i = 0; i < max; i++) { + if (voter[i].enable) { + if (pri < voter[i].pri) { + *res = voter[i].value; + pri = voter[i].pri; + *id = i; + } else if (pri > voter[i].pri) { + continue; + } else if (*res < voter[i].value) { + *res = voter[i].value; + *id = i; + } + } + } + return 0; +} +static int select_enable(struct sec_voter * voter, int max, int * id, int * res) +{ + int i; + *res = 0; + *id = -EINVAL; + for (i = 0; i < max; i++) { + if (voter[i].enable) { + *res = voter[i].enable; + *id = i; + break; + } + } + return 0; +} + +static int select_vote_value(struct sec_vote * vote, int * id, int * res) +{ + int ret = 0; + + switch (vote->type) { + case SEC_VOTE_MIN: + select_min(vote->voter, vote->num, id, res); + if (*res == INT_MAX) + *res = vote->init_val; + break; + case SEC_VOTE_MAX: + select_max(vote->voter, vote->num, id, res); + if (*res == INT_MIN) + *res = vote->init_val; + break; + case SEC_VOTE_EN: + select_enable(vote->voter, vote->num, id, res); + break; + default: + pr_err("%s type invalid\n", __func__); + ret = -EINVAL; + } + + return ret; +} + +int get_sec_vote(struct sec_vote * vote, const char ** name, int * value) +{ + mutex_lock(&vote->lock); + if (vote->id >= 0) { + *name = vote->voter_name[vote->id]; + } + else { + *name = none_str; + } + *value = vote->res; + mutex_unlock(&vote->lock); + return 0; +} +EXPORT_SYMBOL(get_sec_vote); +int get_sec_vote_result(struct sec_vote *vote) +{ + int v; + mutex_lock(&vote->lock); + if (vote->force_set) + v = vote->force_val; + else + v = vote->res; + mutex_unlock(&vote->lock); + return v; +} +EXPORT_SYMBOL(get_sec_vote_result); + +const char* get_sec_keyvoter_name(struct sec_vote *vote) +{ + const char * str; + mutex_lock(&vote->lock); + if (vote->force_set) + str = force_str; + else + str = (vote->id >= 0)?vote->voter_name[vote->id]: none_str; + mutex_unlock(&vote->lock); + return str; +} +EXPORT_SYMBOL(get_sec_keyvoter_name); + +int get_sec_voter_status(struct sec_vote *vote, int id, int * v) +{ + if (id >= vote->num || id < 0) + return -EINVAL; + + mutex_lock(&vote->lock); + if (vote->type == SEC_VOTE_EN) + *v = vote->voter[id].enable; + else if (vote->voter[id].enable) + *v = vote->voter[id].value; + else + *v = INT_MIN; + mutex_unlock(&vote->lock); + return (*v == INT_MIN) ? -EINVAL : 0; +} +EXPORT_SYMBOL(get_sec_voter_status); + +int show_sec_vote_status(char *buf, unsigned int p_size) +{ + struct sec_vote *vote; + int i, j = 0; + char *type_str = "Unkonwn"; + + if (list_empty(&vote_list)) { + j += scnprintf(buf + j, p_size - j, "No vote\n"); + return j; + } + + mutex_lock(&vote_lock); + list_for_each_entry(vote, &vote_list, list) { + mutex_lock(&vote->lock); + for (i = 0; i < vote->num; i++) { + if (vote->voter[i].enable) { + j += scnprintf(buf + j, p_size - j, "%s: %s:\t\t\ten=%d v=%d p=%d\n", + vote->name, + vote->voter_name[i], + vote->voter[i].enable, + vote->voter[i].value, + vote->voter[i].pri); + } + } + switch (vote->type) { + case SEC_VOTE_MIN: + type_str = "Min"; + break; + case SEC_VOTE_MAX: + type_str = "Max"; + break; + case SEC_VOTE_EN: + type_str = "Set_any"; + break; + default: + type_str = "Invalid"; + } + j += scnprintf(buf + j, p_size - j, "%s: INIT: v=%d\n", + vote->name, vote->init_val); + if (vote->force_set) { + j += scnprintf(buf + j, p_size - j, "%s: voter=%s type=%s v=%d\n", + vote->name, force_str, type_str, vote->force_val); + } else { + j += scnprintf(buf + j, p_size - j, "%s: voter=%s type=%s v=%d\n", + vote->name, + (vote->id >= 0) ? vote->voter_name[vote->id] : none_str, + type_str, vote->res); + } + mutex_unlock(&vote->lock); + } + mutex_unlock(&vote_lock); + + return j; +} +EXPORT_SYMBOL(show_sec_vote_status); + +static int show_vote_clients(struct seq_file *m, void *data) +{ + struct sec_vote *vote = m->private; + int i; + char *type_str = "Unkonwn"; + + mutex_lock(&vote->lock); + for (i = 0; i < vote->num; i++) { + if (vote->voter[i].enable) { + seq_printf(m, "%s: %s:\t\t\ten=%d v=%d p=%d\n", + vote->name, + vote->voter_name[i], + vote->voter[i].enable, + vote->voter[i].value, + vote->voter[i].pri); + } + } + switch (vote->type) { + case SEC_VOTE_MIN: + type_str = "Min"; + break; + case SEC_VOTE_MAX: + type_str = "Max"; + break; + case SEC_VOTE_EN: + type_str = "Set_any"; + break; + default: + type_str = "Invalid"; + } + seq_printf(m, "%s: INIT: v=%d\n", + vote->name, vote->init_val); + if (vote->force_set) { + seq_printf(m, "%s: voter=%s type=%s v=%d\n", + vote->name, force_str, type_str, vote->force_val); + } else { + seq_printf(m, "%s: voter=%s type=%s v=%d\n", + vote->name, + (vote->id >= 0)?vote->voter_name[vote->id]: none_str, + type_str, vote->res); + } + mutex_unlock(&vote->lock); + + return 0; +} + +static int vote_status_open(struct inode *inode, struct file *file) +{ + struct sec_vote *vote = inode->i_private; + + return single_open(file, show_vote_clients, vote); +} + +static const struct file_operations vote_status_ops = { + .owner = THIS_MODULE, + .open = vote_status_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int show_all_clients(struct seq_file *m, void *data) +{ + struct sec_vote *vote; + int i; + char *type_str = "Unkonwn"; + + if (list_empty(&vote_list)) { + seq_printf(m, "No vote\n"); + return 0; + } + + mutex_lock(&vote_lock); + list_for_each_entry(vote, &vote_list, list) { + mutex_lock(&vote->lock); + for (i = 0; i < vote->num; i++) { + if (vote->voter[i].enable) { + seq_printf(m, "%s: %s:\t\t\ten=%d v=%d p=%d\n", + vote->name, + vote->voter_name[i], + vote->voter[i].enable, + vote->voter[i].value, + vote->voter[i].pri); + } + } + switch (vote->type) { + case SEC_VOTE_MIN: + type_str = "Min"; + break; + case SEC_VOTE_MAX: + type_str = "Max"; + break; + case SEC_VOTE_EN: + type_str = "Set_any"; + break; + default: + type_str = "Invalid"; + } + seq_printf(m, "%s: INIT: v=%d\n", + vote->name, vote->init_val); + if (vote->force_set) { + seq_printf(m, "%s: voter=%s type=%s v=%d\n", + vote->name, force_str, type_str, vote->force_val); + } else { + seq_printf(m, "%s: voter=%s type=%s v=%d\n", + vote->name, + (vote->id >= 0)?vote->voter_name[vote->id]: none_str, + type_str, vote->res); + } + mutex_unlock(&vote->lock); + } + mutex_unlock(&vote_lock); + + return 0; +} + +static int vote_status_all_open(struct inode *inode, struct file *file) +{ + + return single_open(file, show_all_clients, NULL); +} + +static const struct file_operations vote_status_all_ops = { + .owner = THIS_MODULE, + .open = vote_status_all_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int force_get(void *data, u64 *val) +{ + struct sec_vote *vote = data; + + *val = vote->force_set; + + return 0; +} + +static int force_set(void *data, u64 val) +{ + struct sec_vote *vote = data; + + mutex_lock(&vote->lock); + vote->force_set = val; + + if (!vote->cb) + goto out; + + if (vote->force_set) { + vote->res = vote->cb(vote->data, vote->force_val); + } else { + vote->res = vote->cb(vote->data, vote->res); + } +out: + mutex_unlock(&vote->lock); + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(vote_force_ops, force_get, force_set, "%lld\n"); +struct sec_vote *find_vote(const char *name) +{ + struct sec_vote *vote; + + list_for_each_entry(vote, &vote_list, list) { + if (strcmp(vote->name, name) == 0) { + return vote; + } + } + return NULL; +} +EXPORT_SYMBOL(find_vote); + +struct sec_vote *sec_vote_init(const char *name, int type, int num, int init_val, + const char **voter_name, int(*cb)(void *data, int value), void *data) +{ + struct sec_vote * vote = NULL; + struct sec_voter * voter = NULL; + + mutex_lock(&vote_lock); + vote = find_vote(name); + if (vote) { + pr_info("%s: %s exist \n", __func__, name); + goto err; + } + if (voter_name == NULL) { + pr_info("%s: Please add voter name list \n", __func__); + goto err; + } + + vote = kzalloc(sizeof(struct sec_vote), GFP_KERNEL); + if (!vote) { + pr_info("%s: mem aloocate fail \n", __func__); + goto err; + } + vote->name = name; + vote->type = type; + voter = kzalloc(sizeof(struct sec_voter) * num, GFP_KERNEL); + if (!voter) { + pr_info("%s: mem aloocate fail \n", __func__); + kfree(vote); + goto err; + } + vote->voter = voter; + vote->num = num; + vote->voter_name = voter_name; + vote->init_val = init_val; + vote->cb = cb; + vote->id = -EINVAL; + vote->res = -EINVAL; + vote->data = data; + mutex_init(&vote->lock); + + if (debug_root == NULL) { + debug_root = debugfs_create_dir("sec-vote", NULL); + if (!debug_root) { + pr_err("Couldn't create debug dir\n"); + } else { + status_all = debugfs_create_file("status_all", + S_IFREG | 0444, + debug_root, NULL, + &vote_status_all_ops); + if (!status_all) { + pr_err("Couldn't create status_all dbg file \n"); + } + } + } + if (debug_root) + vote->root = debugfs_create_dir(name, debug_root); + if (!vote->root) { + pr_err("Couldn't create debug dir %s\n", name); + } else { + vote->status_ent = debugfs_create_file("status", S_IFREG | 0444, + vote->root, vote, + &vote_status_ops); + if (!vote->status_ent) { + pr_err("Couldn't create status dbg file for %s\n", name); + } + + debugfs_create_u32("force_val", S_IFREG | 0644, + vote->root, &(vote->force_val)); + + vote->force_set_ent = debugfs_create_file("force_set", + S_IFREG | 0444, + vote->root, vote, + &vote_force_ops); + if (!vote->force_set_ent) { + pr_err("Couldn't create force_set dbg file for %s\n", name); + } + } + pr_info("%s: %s \n", __func__, name); + list_add(&vote->list, &vote_list); + mutex_unlock(&vote_lock); + return vote; +err: + mutex_unlock(&vote_lock); + return NULL; +} +EXPORT_SYMBOL(sec_vote_init); + +void sec_vote_destroy(struct sec_vote *vote) +{ + pr_info("%s: %s\n", __func__, vote->name); + list_del(&vote->list); + kfree(vote->voter); + debugfs_remove_recursive(vote->root); + mutex_destroy(&vote->lock); + kfree(vote); +} +EXPORT_SYMBOL(sec_vote_destroy); + +void change_sec_voter_pri(struct sec_vote *vote, int event, int pri) +{ + if (event >= vote->num) { + pr_info("%s id Error(%d)\n", __func__, event); + return; + } + mutex_lock(&vote->lock); + vote->voter[event].pri = pri; + mutex_unlock(&vote->lock); +} +EXPORT_SYMBOL(change_sec_voter_pri); + +void _sec_vote(struct sec_vote *vote, int event, int en, int value, const char *fname, int line) +{ + int id, res, ret; + + if (event >= vote->num) { + pr_info("%s id Error(%d)\n", __func__, event); + return; + } + mutex_lock(&vote->lock); + pr_debug("%s, %s en: %d->%d, v: %d->%d\n", vote->name,vote->voter_name[event], + vote->voter[event].enable, en, vote->voter[event].value, value); + + if ((vote->voter[event].enable == en) && + (((vote->voter[event].value == value) || !en))) + goto out; + + vote->voter[event].enable = en; + vote->voter[event].value = value; + + ret = select_vote_value(vote, &id, &res); + if (ret < 0) + goto out; + + pr_info("%s(%s:%d): %s (%s, %d) -> (%s, %d)\n", __func__, fname, line, vote->name, + (vote->id >= 0) ? vote->voter_name[vote->id] : none_str, vote->res, + (id >= 0) ? vote->voter_name[id] : none_str, res); + + if (res != vote->res) { + vote->id = id; + vote->res = res; + if (vote->force_set) + pr_err("%s skip by force_set\n", __func__); + else + vote->res = vote->cb(vote->data, res); + } else if (!en && (vote->id == event)) { + vote->id = id; + } + +out: + mutex_unlock(&vote->lock); +} +EXPORT_SYMBOL(_sec_vote); + +void sec_vote_refresh(struct sec_vote *vote) +{ + mutex_lock(&vote->lock); + if (vote->res == -EINVAL && vote->id == -EINVAL) { + pr_info("%s: skip. not used before\n", __func__); + } else { + if (vote->force_set) { + pr_info("%s: refresh (%s, %d)\n", vote->name, force_str, vote->force_val); + vote->res = vote->cb(vote->data, vote->force_val); + } else { + int id, res, ret; + + ret = select_vote_value(vote, &id, &res); + pr_info("%s: refresh (%s, %d, %d)\n", vote->name, + (id >= 0) ? vote->voter_name[id] : none_str, res, ret); + if (ret < 0) + goto out; + vote->res = vote->cb(vote->data, res); + } + } +out: + mutex_unlock(&vote->lock); +} +EXPORT_SYMBOL(sec_vote_refresh); + +const char *get_sec_vote_name(struct sec_vote *vote) +{ + return vote->name; +} +EXPORT_SYMBOL(get_sec_vote_name); + diff --git a/drivers/battery/common/sec_battery_vote.h b/drivers/battery/common/sec_battery_vote.h new file mode 100644 index 000000000000..3a3ba2acdc9e --- /dev/null +++ b/drivers/battery/common/sec_battery_vote.h @@ -0,0 +1,127 @@ + +#ifndef __SEC_VOTER_H +#define __SEC_VOTER_H __FILE__ + +#define FOREACH_VOTER(GENERATE) \ + GENERATE(VOTER_VBUS_CHANGE) \ + GENERATE(VOTER_USB_100MA) \ + GENERATE(VOTER_CHG_LIMIT) \ + GENERATE(VOTER_AICL) \ + GENERATE(VOTER_SELECT_PDO) \ + GENERATE(VOTER_CABLE) \ + GENERATE(VOTER_MIX_LIMIT) \ + GENERATE(VOTER_CHG_TEMP) \ + GENERATE(VOTER_LRP_TEMP) \ + GENERATE(VOTER_STORE_MODE) \ + GENERATE(VOTER_SIOP) \ + GENERATE(VOTER_WPC_CUR) \ + GENERATE(VOTER_SWELLING) \ + GENERATE(VOTER_OTG) \ + GENERATE(VOTER_SLEEP_MODE) \ + GENERATE(VOTER_USER) \ + GENERATE(VOTER_STEP) \ + GENERATE(VOTER_AGING_STEP) \ + GENERATE(VOTER_VBUS_OVP) \ + GENERATE(VOTER_FULL_CHARGE) \ + GENERATE(VOTER_TEST_MODE) \ + GENERATE(VOTER_TIME_EXPIRED) \ + GENERATE(VOTER_MUIC_ABNORMAL) \ + GENERATE(VOTER_WC_TX) \ + GENERATE(VOTER_SLATE) \ + GENERATE(VOTER_SMART_SLATE) \ + GENERATE(VOTER_SUSPEND) \ + GENERATE(VOTER_SYSOVLO) \ + GENERATE(VOTER_VBAT_OVP) \ + GENERATE(VOTER_STEP_CHARGE) \ + GENERATE(VOTER_WPC_STEP_CHARGE) \ + GENERATE(VOTER_DC_STEP_CHARGE) \ + GENERATE(VOTER_TOPOFF_CHANGE) \ + GENERATE(VOTER_HMT) \ + GENERATE(VOTER_DC_ERR) \ + GENERATE(VOTER_DC_MODE) \ + GENERATE(VOTER_FULL_CAPACITY) \ + GENERATE(VOTER_WDT_EXPIRE) \ + GENERATE(VOTER_BATTERY) \ + GENERATE(VOTER_IFCON_WA) \ + GENERATE(VOTER_USB_FAC_100MA) \ + GENERATE(VOTER_PASS_THROUGH) \ + GENERATE(VOTER_NO_BATTERY) \ + GENERATE(VOTER_D2D_WIRE) \ + GENERATE(VOTER_CHANGE_CHGMODE) \ + GENERATE(VOTER_FLASH) \ + GENERATE(VOTER_MST) \ + GENERATE(VOTER_SRCCAP_TRANSIT) \ + GENERATE(VOTER_FW) \ + GENERATE(VOTER_WL_TO_W) \ + GENERATE(VOTER_ABNORMAL_TA) \ + GENERATE(VOTER_PHM) \ + GENERATE(VOTER_MAX) + +#define GENERATE_ENUM(ENUM) ENUM, +#define GENERATE_STRING(STRING) #STRING, + +enum VOTER_ENUM { + FOREACH_VOTER(GENERATE_ENUM) +}; + +enum { + SEC_VOTE_MIN, + SEC_VOTE_MAX, + SEC_VOTE_EN, +}; + +enum { + VOTE_PRI_0 = 0, + VOTE_PRI_1, + VOTE_PRI_2, + VOTE_PRI_3, + VOTE_PRI_4, + VOTE_PRI_5, + VOTE_PRI_6, + VOTE_PRI_7, + VOTE_PRI_8, + VOTE_PRI_9, + VOTE_PRI_10, +}; +struct sec_vote; + +extern int get_sec_vote(struct sec_vote *vote, const char **name, int *value); +extern struct sec_vote *find_vote(const char *name); +extern struct sec_vote *sec_vote_init(const char *name, int type, int num, int init_val, + const char **voter_name, int(*cb)(void *data, int value), void *data); +extern void sec_vote_destroy(struct sec_vote *vote); +extern void _sec_vote(struct sec_vote *vote, int event, int en, int value, const char *fname, int line); +extern void sec_vote_refresh(struct sec_vote *vote); +extern const char *get_sec_keyvoter_name(struct sec_vote *vote); +extern int get_sec_vote_result(struct sec_vote *vote); +extern int get_sec_voter_status(struct sec_vote *vote, int id, int *v); +extern int show_sec_vote_status(char *buf, unsigned int p_size); +extern void change_sec_voter_pri(struct sec_vote *vote, int event, int pri); + +#define sec_vote(vote, event, en, value) _sec_vote(vote, event, en, value, __func__, __LINE__) +#define sec_votef(name, event, en, value) \ +do { \ + struct sec_vote *vote = find_vote(name); \ +\ + if (!vote) { \ + pr_err("%s: failed to find vote(%s)\n", __func__, (name)); \ + break; \ + } \ + _sec_vote(vote, event, en, value, __func__, __LINE__); \ +} while (0) + +#define get_sec_voter_statusf(name, id, value) ({ \ +int ret; \ +do { \ + struct sec_vote *vote = find_vote(name); \ +\ + if (!vote) { \ + pr_err("%s: failed to find vote(%s)\n", __func__, (name)); \ + ret = -EINVAL; \ + break; \ + } \ + ret = get_sec_voter_status(vote, id, value); \ +} while (0); \ +ret; \ +}) +#endif diff --git a/drivers/battery/common/sec_battery_wc.c b/drivers/battery/common/sec_battery_wc.c new file mode 100644 index 000000000000..1a4fe1d2bace --- /dev/null +++ b/drivers/battery/common/sec_battery_wc.c @@ -0,0 +1,1698 @@ +/* + * sec_battery_wc.c + * Samsung Mobile Battery Driver + * + * Copyright (C) 2020 Samsung Electronics + * + * + * 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. + */ +#include "sec_battery.h" +#include "sb_tx.h" + +#if defined(CONFIG_SEC_KUNIT) +#include +#else +#define __visible_for_testing static +#endif + +#if defined(CONFIG_WIRELESS_FIRMWARE_UPDATE) +bool sec_bat_check_boost_mfc_condition(struct sec_battery_info *battery, int mode) +{ + union power_supply_propval value = {0, }; + int boost_status = 0, wpc_det = 0, mst_pwr_en = 0; + + pr_info("%s\n", __func__); + + if (mode == SEC_WIRELESS_FW_UPDATE_AUTO_MODE) { + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_WIRELESS_INITIAL_WC_CHECK, value); + wpc_det = value.intval; + } + + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_WIRELESS_MST_PWR_EN, value); + mst_pwr_en = value.intval; + + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_CHARGE_BOOST, value); + boost_status = value.intval; + + pr_info("%s wpc_det(%d), mst_pwr_en(%d), boost_status(%d)\n", + __func__, wpc_det, mst_pwr_en, boost_status); + + if (!boost_status && !wpc_det && !mst_pwr_en) + return true; + return false; +} + +void sec_bat_fw_update(struct sec_battery_info *battery, int mode) +{ + union power_supply_propval value = {0, }; + int ret = 0; + + pr_info("%s\n", __func__); + + __pm_wakeup_event(battery->vbus_ws, jiffies_to_msecs(HZ * 10)); + + switch (mode) { + case SEC_WIRELESS_FW_UPDATE_SDCARD_MODE: + case SEC_WIRELESS_FW_UPDATE_BUILTIN_MODE: + case SEC_WIRELESS_FW_UPDATE_AUTO_MODE: + case SEC_WIRELESS_FW_UPDATE_SPU_MODE: + case SEC_WIRELESS_FW_UPDATE_SPU_VERIFY_MODE: + battery->mfc_fw_update = true; + sec_vote(battery->chgen_vote, VOTER_FW, true, SEC_BAT_CHG_MODE_BUCK_OFF); + msleep(500); + sec_vote(battery->iv_vote, VOTER_FW, true, SEC_INPUT_VOLTAGE_5V); +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) +#if !IS_ENABLED(CONFIG_MTK_CHARGER) || !IS_ENABLED(CONFIG_AFC_CHARGER) + muic_afc_request_voltage(AFC_REQUEST_MFC, SEC_INPUT_VOLTAGE_5V / 1000); +#endif +#endif + msleep(2000); + value.intval = mode; + ret = psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_CHARGE_POWERED_OTG_CONTROL, value); + if (ret < 0) { + battery->mfc_fw_update = false; + sec_vote(battery->chgen_vote, VOTER_FW, false, 0); + sec_vote(battery->iv_vote, VOTER_FW, false, 0); +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) +#if !IS_ENABLED(CONFIG_MTK_CHARGER) || !IS_ENABLED(CONFIG_AFC_CHARGER) + muic_afc_request_voltage(AFC_REQUEST_MFC, SEC_INPUT_VOLTAGE_9V / 1000); +#endif +#endif + } + break; + default: + break; + } +} +#endif + +int sec_bat_check_wpc_vout(struct sec_battery_info *battery, int ct, unsigned int chg_limit, + int pre_vout, unsigned int evt) +{ + union power_supply_propval value = {0, }; + int vout = 0; + bool check_flicker_wa = false; + + if (!is_hv_wireless_type(ct)) + return 0; + + if ((ct == SEC_BATTERY_CABLE_HV_WIRELESS_20) || (ct == SEC_BATTERY_CABLE_WIRELESS_EPP)) + vout = battery->wpc_max_vout_level; + else + vout = WIRELESS_VOUT_10V; + + mutex_lock(&battery->voutlock); + if (battery->pdata->wpc_vout_ctrl_lcd_on) { + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ID, value); + if ((value.intval != WC_PAD_ID_UNKNOWN) && + (value.intval != WC_PAD_ID_SNGL_DREAM) && + (value.intval != WC_PAD_ID_STAND_DREAM)) { + if (battery->wpc_vout_ctrl_mode && battery->lcd_status) { + pr_info("%s: trigger flicker wa\n", __func__); + check_flicker_wa = true; + } else { + value.intval = 0; + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_PAD_VOLT_CTRL, value); + if (!value.intval) { + pr_info("%s: recover flicker wa\n", __func__); + value.intval = battery->lcd_status; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_PAD_VOLT_CTRL, value); + } + } + } + } + + /* get vout level */ + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_WIRELESS_RX_VOUT, value); + + if (value.intval == WIRELESS_VOUT_5_5V_STEP) + pre_vout = WIRELESS_VOUT_5_5V_STEP; + + if ((evt & (SEC_BAT_CURRENT_EVENT_HIGH_TEMP_SWELLING | SEC_BAT_CURRENT_EVENT_ISDB)) || + battery->sleep_mode || chg_limit || check_flicker_wa) + vout = WIRELESS_VOUT_5_5V_STEP; + + pr_info("%s: prev_vout(%d) => vout(%d)\n", __func__, pre_vout, vout); + if (vout != pre_vout) { + if (evt & SEC_BAT_CURRENT_EVENT_WPC_VOUT_LOCK) { + vout = pre_vout; + pr_info("%s: block to set wpc vout level(%d) because otg on\n", + __func__, vout); + } else { + value.intval = vout; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_INPUT_VOLTAGE_REGULATION, value); + pr_info("%s: change vout level(%d)", __func__, vout); + sec_vote(battery->input_vote, VOTER_AICL, false, 0); + } + } else if ((vout == WIRELESS_VOUT_10V || + vout == battery->wpc_max_vout_level)) { + /* reset aicl current to recover current for unexpected aicl during */ + /* before vout boosting completion */ + sec_vote(battery->input_vote, VOTER_AICL, false, 0); + } + mutex_unlock(&battery->voutlock); + return vout; +} +EXPORT_SYMBOL_KUNIT(sec_bat_check_wpc_vout); + +void sec_wireless_otg_vout_control(struct sec_battery_info *battery, int enable) +{ + union power_supply_propval value = {0, }; + + if (enable) { + sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_WPC_VOUT_LOCK, + SEC_BAT_CURRENT_EVENT_WPC_VOUT_LOCK); + } else { + sec_bat_set_current_event(battery, 0, + SEC_BAT_CURRENT_EVENT_WPC_VOUT_LOCK); + } + + value.intval = enable; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL, value); + + if (enable) { + if (battery->wc_tx_enable) { + /* TX power should turn off during otg on */ + pr_info("@Tx_Mode %s: OTG is going to work, TX power should off\n", __func__); + /* set tx event */ + sec_bat_set_tx_event(battery, BATT_TX_EVENT_WIRELESS_TX_OTG_ON, BATT_TX_EVENT_WIRELESS_TX_OTG_ON); + sec_wireless_set_tx_enable(battery, false); + } else { + battery->wpc_vout_level = WIRELESS_VOUT_5V; + } + } else if (is_wireless_all_type(battery->cable_type)) { + if ((battery->status == POWER_SUPPLY_STATUS_FULL) && + ((battery->charging_mode == SEC_BATTERY_CHARGING_2ND) || battery->is_recharging)) { + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_INPUT_VOLTAGE_REGULATION, value); + + if (value.intval >= MFC_VOUT_5_5V) + battery->wpc_vout_level = WIRELESS_VOUT_5_5V_STEP; + else + battery->wpc_vout_level = WIRELESS_VOUT_CC_CV_VOUT; + value.intval = WIRELESS_VOUT_CC_CV_VOUT; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_1ST_DONE, value); + } else { + battery->wpc_vout_level = sec_bat_check_wpc_vout(battery, battery->cable_type, battery->chg_limit, + battery->wpc_vout_level, battery->current_event); + } + sec_wireless_otg_icl_control(battery); + } +} + +unsigned int get_wc20_vout(unsigned int vout) +{ + unsigned int ret = 0; + + switch (vout) { + case WIRELESS_VOUT_5V: + ret = 5000; + break; + case WIRELESS_VOUT_5_5V: + ret = 5500; + break; + case WIRELESS_VOUT_9V: + ret = 9000; + break; + case WIRELESS_VOUT_10V: + ret = 10000; + break; + case WIRELESS_VOUT_11V: + ret = 11000; + break; + case WIRELESS_VOUT_12V: + ret = 12000; + break; + default: + pr_err("%s vout is not supported\n", __func__); + break; + } + + pr_info("%s vout(%d) - idx(%d)\n", __func__, vout, ret); + return ret; +} + +void sec_bat_set_wc20_current(struct sec_battery_info *battery) +{ + int icl = 0, fcc = 0; + pr_info("%s: wc_status(%d), rx_power(%d), vout(%d)\n", __func__, + battery->wc_status, battery->wc20_rx_power, battery->wc20_vout); + + if (is_pwr_nego_wireless_type(battery->wc_status)) { + icl = (battery->wc20_rx_power / battery->wc20_vout); + fcc = battery->pdata->charging_current[battery->wc_status].fast_charging_current; + + if (battery->wc20_rx_power <= SEC_WIRELESS_RX_POWER_5W) { + battery->wc20_power_class = 0; + } else if (battery->wc20_rx_power <= SEC_WIRELESS_RX_POWER_7_5W) { + battery->wc20_power_class = SEC_WIRELESS_RX_POWER_CLASS_1; + } else if (battery->wc20_rx_power <= SEC_WIRELESS_RX_POWER_12W) { + battery->wc20_power_class = SEC_WIRELESS_RX_POWER_CLASS_2; + } else if (battery->wc20_rx_power <= SEC_WIRELESS_RX_POWER_20W) { + battery->wc20_power_class = SEC_WIRELESS_RX_POWER_CLASS_3; + if (battery->pdata->wc21_icl > 0) + icl = battery->pdata->wc21_icl; + } else { + battery->wc20_power_class = SEC_WIRELESS_RX_POWER_CLASS_4; + } + + sec_bat_change_default_current(battery, battery->wc_status, icl, fcc); + sec_vote(battery->input_vote, VOTER_CABLE, true, icl); + sec_vote(battery->fcc_vote, VOTER_CABLE, true, fcc); + + if (is_wired_type(battery->cable_type)) { + int wl_power = battery->wc20_rx_power; + + pr_info("%s: check power(%d <--> %d)\n", + __func__, battery->max_charge_power, wl_power); + if (battery->max_charge_power < wl_power) { + __pm_stay_awake(battery->cable_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->cable_work, 0); + } + } else { + sec_bat_set_charging_current(battery); + } + } +} + +void sec_wireless_otg_icl_control(struct sec_battery_info *battery) +{ + bool need_vote_refresh = false; + + pr_info("%s: is_otg_on=%s\n", __func__, battery->is_otg_on ? "ON" : "OFF"); + + if (battery->is_otg_on) { + if (get_sec_vote_result(battery->input_vote) <= battery->pdata->wireless_otg_input_current) + need_vote_refresh = true; + sec_vote(battery->input_vote, VOTER_OTG, true, battery->pdata->wireless_otg_input_current); + if (need_vote_refresh) + sec_vote_refresh(battery->input_vote); + } else { + sec_vote(battery->input_vote, VOTER_OTG, false, 0); + } + sec_vote(battery->input_vote, VOTER_AICL, false, 0); +} + +void sec_bat_set_mfc_off(struct sec_battery_info *battery, char flag, bool need_ept) +{ + union power_supply_propval value = {0, }; + char wpc_en_status[2]; + + if (need_ept) { + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_PROP_ONLINE, value); + + msleep(300); + } + + wpc_en_status[0] = flag; + wpc_en_status[1] = false; + value.strval = wpc_en_status; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WPC_EN, value); + + pr_info("@DIS_MFC %s: WC CONTROL: Disable %d\n", __func__, flag); +} + +void sec_bat_set_mfc_on(struct sec_battery_info *battery, char flag) +{ + union power_supply_propval value = {0, }; + char wpc_en_status[2]; + + wpc_en_status[0] = flag; + wpc_en_status[1] = true; + value.strval = wpc_en_status; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WPC_EN, value); + + pr_info("%s: WC CONTROL: Enable %d\n", __func__, flag); +} + +void sec_bat_mfc_ldo_cntl(struct sec_battery_info *battery, bool en) +{ + union power_supply_propval value = {0, }; + + battery->wc_need_ldo_on = !en; + value.intval = en; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_PROP_CHARGE_EMPTY, value); + if (en) { + battery->wpc_vout_level = WIRELESS_VOUT_5V; + } else { + battery->wpc_vout_level = sec_bat_check_wpc_vout(battery, battery->cable_type, battery->chg_limit, + battery->wpc_vout_level, battery->current_event); + } + + if (battery->disable_mfc) { + pr_info("%s : set mfc %s\n", __func__, (en ? "on" : "off")); + if (en) + sec_bat_set_mfc_on(battery, WPC_EN_CHARGING); + else + sec_bat_set_mfc_off(battery, WPC_EN_CHARGING, false); + } +} + +__visible_for_testing int sec_bat_get_wire_power(struct sec_battery_info *battery, int wr_sts) +{ + int wr_pwr = 0; + int wr_icl = 0, wr_vol = 0; + + if (!is_wired_type(wr_sts)) + return 0; + if (is_pd_wire_type(wr_sts)) + return battery->pd_max_charge_power; + + wr_icl = (wr_sts == SEC_BATTERY_CABLE_PREPARE_TA ? + battery->pdata->charging_current[SEC_BATTERY_CABLE_TA].input_current_limit : + battery->pdata->charging_current[wr_sts].input_current_limit); + wr_vol = is_hv_wire_type(wr_sts) ? + (wr_sts == SEC_BATTERY_CABLE_12V_TA ? SEC_INPUT_VOLTAGE_12V : SEC_INPUT_VOLTAGE_9V) + : SEC_INPUT_VOLTAGE_5V; + + wr_pwr = mW_by_mVmA(wr_vol, wr_icl); + pr_info("%s: wr_power(%d), wire_cable_type(%d)\n", __func__, wr_pwr, wr_sts); + + return wr_pwr; +} + +__visible_for_testing int sec_bat_get_wireless_power(struct sec_battery_info *battery, int wrl_sts) +{ + int wrl_icl = 0, wrl_pwr = 0; + + if (wrl_sts == SEC_BATTERY_CABLE_PREPARE_WIRELESS_HV) + wrl_sts = SEC_BATTERY_CABLE_HV_WIRELESS; + else if (wrl_sts == SEC_BATTERY_CABLE_PREPARE_WIRELESS_20) + wrl_sts = SEC_BATTERY_CABLE_HV_WIRELESS_20; + + wrl_icl = battery->pdata->charging_current[wrl_sts].input_current_limit; + + if (battery->sleep_mode) + wrl_pwr = mW_by_mVmA(SEC_INPUT_VOLTAGE_5_5V, battery->pdata->sleep_mode_limit_current); + else if (is_nv_wireless_type(wrl_sts) || (wrl_sts == SEC_BATTERY_CABLE_WIRELESS_FAKE)) + wrl_pwr = mW_by_mVmA(SEC_INPUT_VOLTAGE_5_5V, wrl_icl); + else if (is_pwr_nego_wireless_type(wrl_sts) || (wrl_sts == SEC_BATTERY_CABLE_WIRELESS_EPP_FAKE)) + wrl_pwr = mW_by_mVmA(battery->wc20_vout, wrl_icl); + else + wrl_pwr = mW_by_mVmA(SEC_INPUT_VOLTAGE_10V, wrl_icl); + + return wrl_pwr; +} + +__visible_for_testing void sec_bat_switch_to_wr(struct sec_battery_info *battery, int wrl_sts, int prev_ct) +{ + union power_supply_propval val = {0, }; + + pr_info("%s\n", __func__); + if (wrl_sts == SEC_BATTERY_CABLE_PREPARE_WIRELESS_20) + wrl_sts = SEC_BATTERY_CABLE_HV_WIRELESS_20; + /* limit charging current before change path between chgin and wcin */ + if (is_pwr_nego_wireless_type(prev_ct) && + is_pwr_nego_wireless_type(wrl_sts)) { + /* limit charging current before change path between chgin and wcin */ + pr_info("%s: set charging current %dmA for a moment in case of TA OCP\n", + __func__, battery->pdata->wpc_charging_limit_current); + sec_vote(battery->fcc_vote, VOTER_CABLE, true, battery->pdata->wpc_charging_limit_current); + msleep(100); + } + + sec_bat_mfc_ldo_cntl(battery, MFC_LDO_OFF); + /* Turn off TX to charge by cable charging having more power */ + if (wrl_sts == SEC_BATTERY_CABLE_WIRELESS_TX) { + pr_info("@Tx_Mode %s: RX device with TA, notify TX device of this info\n", + __func__); + val.intval = true; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_SWITCH, val); + } +} + +__visible_for_testing void sec_bat_switch_to_wrl(struct sec_battery_info *battery, int wr_sts, int prev_ct) +{ + pr_info("%s\n", __func__); + if (!is_wireless_type(prev_ct)) { + /* limit charging current before change path between chgin and wcin */ + pr_info("%s: set charging current %dmA for a moment in case of TA OCP\n", + __func__, battery->pdata->wpc_charging_limit_current); + sec_vote(battery->fcc_vote, VOTER_CABLE, true, battery->pdata->wpc_charging_limit_current); + msleep(100); + } + /* turn on ldo when ldo was off because of TA, */ + /* ldo is supposed to turn on automatically except force off by sw. */ + /* do not turn on ldo every wireless connection just in case ldo re-toggle by ic */ + if (!is_nocharge_type(wr_sts)) { + sec_bat_mfc_ldo_cntl(battery, MFC_LDO_ON); + } +} + +int sec_bat_choose_cable_type(struct sec_battery_info *battery) +{ + int wr_sts = battery->wire_status; + int cur_ct = wr_sts; + int prev_ct = battery->cable_type; + int wrl_sts = battery->wc_status; + union power_supply_propval value = {0, }; + + if (wrl_sts != SEC_BATTERY_CABLE_NONE) { + cur_ct = wrl_sts; + if (!is_nocharge_type(wr_sts)) { + int wrl_pwr, wr_pwr; + + wr_pwr = sec_bat_get_wire_power(battery, wr_sts); + wrl_pwr = sec_bat_get_wireless_power(battery, wrl_sts); + if (wrl_pwr <= wr_pwr) + cur_ct = wr_sts; + pr_info("%s: wrl_pwr(%d), wr_pwr(%d), wc_sts(%d), wr_sts(%d), cur_ct(%d)\n", + __func__, wrl_pwr, wr_pwr, wrl_sts, wr_sts, cur_ct); + if (is_wireless_type(cur_ct)) + sec_bat_switch_to_wrl(battery, wr_sts, prev_ct); + else { + if (is_hv_wireless_type(wrl_sts)) { + value.intval = WIRELESS_VOUT_FORCE_9V; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_INPUT_VOLTAGE_REGULATION, value); + msleep(200); + value.intval = WIRELESS_VOUT_5V; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_INPUT_VOLTAGE_REGULATION, value); + } + if (is_wireless_all_type(prev_ct)) { + msleep(200); + sec_vote(battery->fcc_vote, VOTER_WL_TO_W, true, 300); + msleep(200); + sec_vote(battery->fcc_vote, VOTER_WL_TO_W, true, 100); + msleep(200); + sec_vote(battery->chgen_vote, VOTER_WL_TO_W, true, SEC_BAT_CHG_MODE_BUCK_OFF); + value.intval = WL_TO_W; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_CHGINSEL, value); + } + sec_bat_switch_to_wr(battery, wrl_sts, prev_ct); + sec_vote(battery->input_vote, VOTER_WPC_CUR, false, 0); + sec_vote(battery->fcc_vote, VOTER_WL_TO_W, false, 0); + sec_vote(battery->chgen_vote, VOTER_WL_TO_W, false, 0); + } + } else { + if (battery->wc_need_ldo_on) + sec_bat_mfc_ldo_cntl(battery, MFC_LDO_ON); + } + } else if (is_nocharge_type(wr_sts) && battery->disable_mfc) { + pr_info("%s : sec_bat_set_mfc_on because of CABLE_NONE\n", __func__); + sec_bat_set_mfc_on(battery, WPC_EN_CHARGING); + } + + return cur_ct; +} + +void sec_bat_get_wireless_current(struct sec_battery_info *battery) +{ + int incurr = INT_MAX; + union power_supply_propval value = {0, }; + + /* WPC_SLEEP_MODE */ + if (is_hv_wireless_type(battery->cable_type) && battery->sleep_mode) { + if (incurr > battery->pdata->sleep_mode_limit_current) + incurr = battery->pdata->sleep_mode_limit_current; + pr_info("%s: sleep_mode = %d, chg_limit = %d, in_curr = %d\n", + __func__, battery->sleep_mode, battery->chg_limit, incurr); + + if (!battery->auto_mode) { + /* send cmd once */ + battery->auto_mode = true; + value.intval = WIRELESS_SLEEP_MODE_ENABLE; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_RX_CONTROL, value); + } + } + + /* WPC_TEMP_MODE */ + if (is_wireless_type(battery->cable_type) && battery->chg_limit) { + if ((battery->siop_level >= 100 && !battery->lcd_status) && + (incurr > battery->pdata->wpc_input_limit_current)) { + if (battery->cable_type == SEC_BATTERY_CABLE_WIRELESS_TX && + battery->pdata->wpc_input_limit_by_tx_check) + incurr = battery->pdata->wpc_input_limit_current_by_tx; + else + incurr = battery->pdata->wpc_input_limit_current; + } else if ((battery->siop_level < 100 || battery->lcd_status) && + (incurr > battery->pdata->wpc_lcd_on_input_limit_current)) + incurr = battery->pdata->wpc_lcd_on_input_limit_current; + } + + /* Display flicker W/A */ + if (battery->pdata->wpc_vout_ctrl_lcd_on && battery->wpc_vout_ctrl_mode && battery->lcd_status) { + if (is_wireless_type(battery->cable_type) && battery->pdata->wpc_flicker_wa_input_limit_current) { + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ID, value); + if ((value.intval != WC_PAD_ID_UNKNOWN) && + (value.intval != WC_PAD_ID_SNGL_DREAM) && + (value.intval != WC_PAD_ID_STAND_DREAM)) { + pr_info("%s: trigger flicker wa\n", __func__); + if (incurr > battery->pdata->wpc_flicker_wa_input_limit_current) + incurr = battery->pdata->wpc_flicker_wa_input_limit_current; + } + } + } + + /* Full-Additional state */ + if (battery->status == POWER_SUPPLY_STATUS_FULL) { + if ((incurr > battery->pdata->siop_hv_wpc_icl) && + (battery->charging_mode == SEC_BATTERY_CHARGING_2ND)) + incurr = battery->pdata->siop_hv_wpc_icl; + if ((incurr > battery->pdata->rechg_hv_wpc_icl) && battery->is_recharging) + incurr = battery->pdata->rechg_hv_wpc_icl; + } + + /* Hero Stand Pad CV */ + if (battery->capacity >= battery->pdata->wc_hero_stand_cc_cv) { + if (battery->cable_type == SEC_BATTERY_CABLE_WIRELESS_STAND) { + if (incurr > battery->pdata->wc_hero_stand_cv_current) + incurr = battery->pdata->wc_hero_stand_cv_current; + } else if (battery->cable_type == SEC_BATTERY_CABLE_WIRELESS_HV_STAND) { + if (battery->chg_limit && + incurr > battery->pdata->wc_hero_stand_cv_current) { + incurr = battery->pdata->wc_hero_stand_cv_current; + } else if (!battery->chg_limit && + incurr > battery->pdata->wc_hero_stand_hv_cv_current) { + incurr = battery->pdata->wc_hero_stand_hv_cv_current; + } + } + } + + /* Full-None state && SIOP_LEVEL 100 */ + if ((battery->siop_level >= 100 && !battery->lcd_status) && + battery->status == POWER_SUPPLY_STATUS_FULL && battery->charging_mode == SEC_BATTERY_CHARGING_NONE) { + incurr = battery->pdata->wc_full_input_limit_current; + } + + if (incurr != INT_MAX) + sec_vote(battery->input_vote, VOTER_WPC_CUR, true, incurr); + else + sec_vote(battery->input_vote, VOTER_WPC_CUR, false, incurr); +} + +int sec_bat_check_wc_available(struct sec_battery_info *battery) +{ + mutex_lock(&battery->wclock); + if (!battery->wc_enable) { + pr_info("%s: wc_enable(%d), cnt(%d)\n", __func__, battery->wc_enable, battery->wc_enable_cnt); + if (battery->wc_enable_cnt > battery->wc_enable_cnt_value) { + union power_supply_propval val = {0, }; + char wpc_en_status[2]; + + battery->wc_enable = true; + battery->wc_enable_cnt = 0; + wpc_en_status[0] = WPC_EN_SYSFS; + wpc_en_status[1] = true; + val.strval = wpc_en_status; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WPC_EN, val); + pr_info("%s: WC CONTROL: Enable\n", __func__); + } + battery->wc_enable_cnt++; + } + mutex_unlock(&battery->wclock); + + return 0; +} + +/* OTG during HV wireless charging or sleep mode have 4.5W normal wireless charging UI */ +bool sec_bat_hv_wc_normal_mode_check(struct sec_battery_info *battery) +{ + union power_supply_propval value = {0, }; + + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL, value); + if (value.intval || battery->sleep_mode) { + pr_info("%s: otg(%d), sleep_mode(%d)\n", __func__, value.intval, battery->sleep_mode); + return true; + } + return false; +} +EXPORT_SYMBOL_KUNIT(sec_bat_hv_wc_normal_mode_check); + +void sec_bat_ext_event_work_content(struct sec_battery_info *battery) +{ + union power_supply_propval value = {0, }; + + if (battery->wc_tx_enable) { /* TX ON state */ + if (battery->ext_event & BATT_EXT_EVENT_CAMERA) { + pr_info("@Tx_Mode %s: Camera ON, TX OFF\n", __func__); + sec_bat_set_tx_event(battery, + BATT_TX_EVENT_WIRELESS_TX_CAMERA_ON, BATT_TX_EVENT_WIRELESS_TX_CAMERA_ON); + sec_wireless_set_tx_enable(battery, false); + } else if (battery->ext_event & BATT_EXT_EVENT_DEX) { + pr_info("@Tx_Mode %s: Dex ON, TX OFF\n", __func__); + sec_bat_set_tx_event(battery, + BATT_TX_EVENT_WIRELESS_TX_OTG_ON, BATT_TX_EVENT_WIRELESS_TX_OTG_ON); + sec_wireless_set_tx_enable(battery, false); + } else if (battery->ext_event & BATT_EXT_EVENT_CALL) { + pr_info("@Tx_Mode %s: Call ON, TX OFF\n", __func__); + battery->tx_retry_case |= SEC_BAT_TX_RETRY_CALL; + /* clear tx all event */ + sec_bat_set_tx_event(battery, 0, BATT_TX_EVENT_WIRELESS_ALL_MASK); + sec_wireless_set_tx_enable(battery, false); + } + } else { /* TX OFF state, it has only call scenario */ + if (battery->ext_event & BATT_EXT_EVENT_CALL) { + pr_info("@Tx_Mode %s: Call ON\n", __func__); + + value.intval = BATT_EXT_EVENT_CALL; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_CALL_EVENT, value); + +#if !defined(CONFIG_WIRELESS_RX_PHM_CTRL) + if (battery->cable_type == SEC_BATTERY_CABLE_WIRELESS_PACK || + battery->cable_type == SEC_BATTERY_CABLE_WIRELESS_HV_PACK || + battery->cable_type == SEC_BATTERY_CABLE_WIRELESS_TX) { + pr_info("%s: Call is on during Wireless Pack or TX\n", __func__); + battery->wc_rx_phm_mode = true; + } +#endif + if (battery->tx_retry_case != SEC_BAT_TX_RETRY_NONE) { + pr_info("@Tx_Mode %s: TX OFF because of other reason(retry:0x%x), save call retry case\n", + __func__, battery->tx_retry_case); + battery->tx_retry_case |= SEC_BAT_TX_RETRY_CALL; + } + } else if (!(battery->ext_event & BATT_EXT_EVENT_CALL)) { + pr_info("@Tx_Mode %s: Call OFF\n", __func__); + + value.intval = BATT_EXT_EVENT_NONE; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_CALL_EVENT, value); + + /* check the diff between current and previous ext_event state */ + if (battery->tx_retry_case & SEC_BAT_TX_RETRY_CALL) { + battery->tx_retry_case &= ~SEC_BAT_TX_RETRY_CALL; + if (!battery->tx_retry_case) { + pr_info("@Tx_Mode %s: Call OFF, TX Retry\n", __func__); + sec_bat_set_tx_event(battery, + BATT_TX_EVENT_WIRELESS_TX_RETRY, + BATT_TX_EVENT_WIRELESS_TX_RETRY); + } + } +#if !defined(CONFIG_WIRELESS_RX_PHM_CTRL) + /* process escape phm */ + if (battery->wc_rx_phm_mode) { + pr_info("%s: ESCAPE PHM STEP 1\n", __func__); + sec_bat_set_mfc_on(battery, WPC_EN_CHARGING); + msleep(100); + + pr_info("%s: ESCAPE PHM STEP 2\n", __func__); + sec_bat_set_mfc_off(battery, WPC_EN_CHARGING, false); + msleep(510); + + pr_info("%s: ESCAPE PHM STEP 3\n", __func__); + sec_bat_set_mfc_on(battery, WPC_EN_CHARGING); + } + battery->wc_rx_phm_mode = false; +#endif + } + } + + __pm_relax(battery->ext_event_ws); +} + +void sec_bat_wireless_minduty_cntl(struct sec_battery_info *battery, unsigned int duty_val) +{ + union power_supply_propval value = {0, }; + + if (duty_val != battery->tx_minduty) { + value.intval = duty_val; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_MIN_DUTY, value); + + pr_info("@Tx_Mode %s: Min duty changed (%d -> %d)\n", __func__, battery->tx_minduty, duty_val); + battery->tx_minduty = duty_val; + } +} + +void sec_bat_wireless_set_ping_duty(struct sec_battery_info *battery, unsigned int ping_duty_val) +{ + union power_supply_propval value = {0, }; + + if (ping_duty_val != battery->tx_ping_duty) { + value.intval = ping_duty_val; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_PING_DUTY, value); + + pr_info("@Tx_Mode %s: Ping duty changed (%d -> %d)\n", + __func__, battery->tx_ping_duty, ping_duty_val); + battery->tx_ping_duty = ping_duty_val; + } +} + +void sec_bat_wireless_uno_cntl(struct sec_battery_info *battery, bool en) +{ + union power_supply_propval value = {0, }; + + value.intval = en; + battery->uno_en = en; + pr_info("@Tx_Mode %s: Uno control %d\n", __func__, en); + + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ENABLE, value); + + sb_tx_set_enable(en, 0); +} + +void sec_bat_wireless_iout_cntl(struct sec_battery_info *battery, int uno_iout, int mfc_iout) +{ + union power_supply_propval value = {0, }; + + if (battery->tx_uno_iout != uno_iout) { + pr_info("@Tx_Mode %s: set uno iout(%d) -> (%d)\n", __func__, battery->tx_uno_iout, uno_iout); + value.intval = battery->tx_uno_iout = uno_iout; + psy_do_property("otg", set, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_IOUT, value); + } else { + pr_info("@Tx_Mode %s: Already set Uno Iout(%d == %d)\n", __func__, battery->tx_uno_iout, uno_iout); + } + +#if !defined(CONFIG_SEC_FACTORY) + if (battery->lcd_status && (mfc_iout == battery->pdata->tx_mfc_iout_phone)) { + pr_info("@Tx_Mode %s: Reduce Tx MFC Iout. LCD ON\n", __func__); + mfc_iout = battery->pdata->tx_mfc_iout_lcd_on; + } +#endif + + if (battery->tx_mfc_iout != mfc_iout) { + pr_info("@Tx_Mode %s: set mfc iout(%d) -> (%d)\n", __func__, battery->tx_mfc_iout, mfc_iout); + value.intval = battery->tx_mfc_iout = mfc_iout; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_IOUT, value); + } else { + pr_info("@Tx_Mode %s: Already set MFC Iout(%d == %d)\n", __func__, battery->tx_mfc_iout, mfc_iout); + } +} + +void sec_bat_wireless_vout_cntl(struct sec_battery_info *battery, int vout_now) +{ + union power_supply_propval value = {0, }; + + pr_info("@Tx_Mode %s: set uno & mfc vout (%dmV -> %dmV)\n", __func__, battery->wc_tx_vout, vout_now); + + if (battery->wc_tx_vout >= vout_now) { + battery->wc_tx_vout = value.intval = vout_now; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_VOUT, value); + psy_do_property("otg", set, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_VOUT, value); + } else if (vout_now > battery->wc_tx_vout) { + battery->wc_tx_vout = value.intval = vout_now; + psy_do_property("otg", set, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_VOUT, value); + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_VOUT, value); + } + +} + +#if defined(CONFIG_WIRELESS_TX_MODE) +#if !defined(CONFIG_SEC_FACTORY) +static void sec_bat_check_tx_battery_drain(struct sec_battery_info *battery) +{ + if (battery->capacity <= battery->pdata->tx_stop_capacity && + is_nocharge_type(battery->cable_type)) { + pr_info("@Tx_Mode %s: battery level is drained, TX mode should turn off\n", __func__); + /* set tx event */ + sec_bat_set_tx_event(battery, BATT_TX_EVENT_WIRELESS_TX_SOC_DRAIN, BATT_TX_EVENT_WIRELESS_TX_SOC_DRAIN); + sec_wireless_set_tx_enable(battery, false); + } +} + +static void sec_bat_check_tx_current(struct sec_battery_info *battery) +{ + if (battery->lcd_status && (battery->tx_mfc_iout > battery->pdata->tx_mfc_iout_lcd_on)) { + sec_bat_wireless_iout_cntl(battery, battery->pdata->tx_uno_iout, battery->pdata->tx_mfc_iout_lcd_on); + pr_info("@Tx_Mode %s: Reduce Tx MFC Iout. LCD ON\n", __func__); + } else if (!battery->lcd_status && (battery->tx_mfc_iout == battery->pdata->tx_mfc_iout_lcd_on)) { + union power_supply_propval value = {0, }; + + sec_bat_wireless_iout_cntl(battery, battery->pdata->tx_uno_iout, battery->pdata->tx_mfc_iout_phone); + pr_info("@Tx_Mode %s: Recovery Tx MFC Iout. LCD OFF\n", __func__); + + value.intval = true; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_SEND_FSK, value); + } +} +#endif + +static void sec_bat_check_tx_switch_mode(struct sec_battery_info *battery) +{ + union power_supply_propval value = {0, }; + + /* temporary mode */ + if (battery->tx_switch_mode == TX_SWITCH_GEAR_PPS) { + pr_info("@Tx_mode %s: skip routine in gear pps mode.\n", __func__); + return; + } + + if (battery->current_event & SEC_BAT_CURRENT_EVENT_AFC) { + pr_info("@Tx_mode %s: Do not switch switch mode! AFC Event set\n", __func__); + return; + } + + value.intval = SEC_FUELGAUGE_CAPACITY_TYPE_CAPACITY_POINT; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CAPACITY, value); + + if ((battery->tx_switch_mode == TX_SWITCH_UNO_ONLY) && (!battery->buck_cntl_by_tx)) { + battery->buck_cntl_by_tx = true; + sec_vote(battery->chgen_vote, VOTER_WC_TX, true, SEC_BAT_CHG_MODE_BUCK_OFF); + + sec_bat_wireless_iout_cntl(battery, battery->pdata->tx_uno_iout, battery->pdata->tx_mfc_iout_phone); + sec_bat_wireless_vout_cntl(battery, battery->pdata->tx_uno_vout); + sec_bat_wireless_minduty_cntl(battery, battery->pdata->tx_minduty_default); + } else if ((battery->tx_switch_mode == TX_SWITCH_CHG_ONLY) && (battery->buck_cntl_by_tx)) { + sec_bat_wireless_iout_cntl(battery, battery->pdata->tx_uno_iout, battery->pdata->tx_mfc_iout_phone_5v); + sec_bat_wireless_vout_cntl(battery, battery->pdata->tx_ping_vout); + sec_bat_wireless_minduty_cntl(battery, battery->pdata->tx_minduty_5V); + + battery->buck_cntl_by_tx = false; + sec_vote(battery->chgen_vote, VOTER_WC_TX, false, 0); + } + + if (battery->status == POWER_SUPPLY_STATUS_FULL) { + if (battery->charging_mode == SEC_BATTERY_CHARGING_NONE) { + if (battery->tx_switch_mode == TX_SWITCH_CHG_ONLY) + battery->tx_switch_mode_change = true; + } else { + if (battery->tx_switch_mode == TX_SWITCH_UNO_ONLY) { + if (battery->tx_switch_start_soc >= 100) { + if (battery->capacity < 99 || (battery->capacity == 99 && value.intval <= 1)) + battery->tx_switch_mode_change = true; + } else { + if ((battery->capacity == battery->tx_switch_start_soc && value.intval <= 1) || + (battery->capacity < battery->tx_switch_start_soc)) + battery->tx_switch_mode_change = true; + } + } else if (battery->tx_switch_mode == TX_SWITCH_CHG_ONLY) { + if (battery->capacity >= 100) + battery->tx_switch_mode_change = true; + } + } + } else { + if (battery->tx_switch_mode == TX_SWITCH_UNO_ONLY) { + if (((battery->capacity == battery->tx_switch_start_soc) && (value.intval <= 1)) || + (battery->capacity < battery->tx_switch_start_soc)) + battery->tx_switch_mode_change = true; + + } else if (battery->tx_switch_mode == TX_SWITCH_CHG_ONLY) { + if (((battery->capacity == (battery->tx_switch_start_soc + 1)) && (value.intval >= 8)) || + (battery->capacity > (battery->tx_switch_start_soc + 1))) + battery->tx_switch_mode_change = true; + } + } + pr_info("@Tx_mode Tx mode(%d) tx_switch_mode_change(%d) start soc(%d) now soc(%d.%d)\n", + battery->tx_switch_mode, battery->tx_switch_mode_change, + battery->tx_switch_start_soc, battery->capacity, value.intval); +} +#endif + +void sec_bat_txpower_calc(struct sec_battery_info *battery) +{ + if (delayed_work_pending(&battery->wpc_txpower_calc_work)) { + pr_info("%s: keep average tx power(%5d mA)\n", __func__, battery->tx_avg_curr); + } else if (battery->wc_tx_enable) { + int tx_vout = 0, tx_iout = 0, vbatt = 0; + union power_supply_propval value = {0, }; + + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_UNO_VIN, value); + tx_vout = value.intval; + + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_UNO_IIN, value); + tx_iout = value.intval; + + value.intval = SEC_BATTERY_VOLTAGE_MV; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_VOLTAGE_NOW, value); + vbatt = value.intval; + + battery->tx_time_cnt++; + + /* AVG curr will be calculated only when the battery is discharged */ + if (battery->current_avg <= 0 && vbatt > 0 && tx_vout > 0 && tx_iout > 0) + tx_iout = (tx_vout / vbatt) * tx_iout; + else + tx_iout = 0; + + /* monitor work will be scheduled every 10s when wc_tx_enable is true */ + battery->tx_avg_curr = + ((battery->tx_avg_curr * battery->tx_time_cnt) + tx_iout) / (battery->tx_time_cnt + 1); + battery->tx_total_power = + (battery->tx_avg_curr * battery->tx_time_cnt) / (60 * 60 / 10); + + if (battery->tx_total_power > ((battery->pdata->battery_full_capacity * 7) / 10)) { + pr_info("%s: tx_total_power(%dmAh) is wrong\n", __func__, battery->tx_total_power); + battery->tx_total_power = (battery->pdata->battery_full_capacity * 7) / 10; + } + + pr_info("%s: tx_time_cnt(%ds), UNO_Vin(%dV), UNO_Iin(%dmA), tx_avg_curr(%dmA), tx_total_power(%dmAh)\n", + __func__, battery->tx_time_cnt * 10, tx_vout, tx_iout, battery->tx_avg_curr, battery->tx_total_power); + } +} + +void sec_bat_tx_off_by_misalign_ocp(struct sec_battery_info *battery) +{ + battery->tx_retry_case &= ~(SEC_BAT_TX_RETRY_MISALIGN | SEC_BAT_TX_RETRY_OCP); + battery->tx_misalign_start_time = 0; + battery->tx_misalign_cnt = 0; + battery->tx_ocp_start_time = 0; + battery->tx_ocp_cnt = 0; +} + +void sec_bat_handle_tx_misalign(struct sec_battery_info *battery, bool trigger_misalign) +{ + struct timespec64 ts = {0, }; + + if (trigger_misalign && battery->wc_tx_enable) { + if (battery->tx_misalign_start_time == 0) { + ts = ktime_to_timespec64(ktime_get_boottime()); + battery->tx_misalign_start_time = ts.tv_sec; + } + pr_info("@Tx_Mode %s: misalign is triggered!!(%d)\n", __func__, ++battery->tx_misalign_cnt); + /* Attention!! in this case, 0x00(TX_OFF) is sent first */ + /* and then 0x8000(RETRY) is sent */ + if (battery->tx_misalign_cnt < MISALIGN_TX_TRY_CNT) { + battery->tx_retry_case |= SEC_BAT_TX_RETRY_MISALIGN; + sec_wireless_set_tx_enable(battery, false); + msleep(1000); + /* clear tx all event */ + sec_bat_set_tx_event(battery, 0, BATT_TX_EVENT_WIRELESS_ALL_MASK); + sec_bat_set_tx_event(battery, + BATT_TX_EVENT_WIRELESS_TX_RETRY, BATT_TX_EVENT_WIRELESS_TX_RETRY); + msleep(1000); + } else { + pr_info("@Tx_Mode %s: Misalign over %d times, TX OFF (cancel misalign)\n", + __func__, MISALIGN_TX_TRY_CNT); + sec_bat_tx_off_by_misalign_ocp(battery); + sec_bat_set_tx_event(battery, + BATT_TX_EVENT_WIRELESS_TX_MISALIGN, BATT_TX_EVENT_WIRELESS_TX_MISALIGN); + sec_wireless_set_tx_enable(battery, false); + } + } else if (battery->tx_retry_case & SEC_BAT_TX_RETRY_MISALIGN) { + ts = ktime_to_timespec64(ktime_get_boottime()); + if (ts.tv_sec >= battery->tx_misalign_start_time) { + battery->tx_misalign_passed_time = ts.tv_sec - battery->tx_misalign_start_time; + } else { + battery->tx_misalign_passed_time = 0xFFFFFFFF - battery->tx_misalign_start_time + + ts.tv_sec; + } + pr_info("@Tx_Mode %s: already misaligned, passed time(%ld)\n", + __func__, battery->tx_misalign_passed_time); + + if (battery->tx_misalign_passed_time >= 60) { + pr_info("@Tx_Mode %s: after 1min\n", __func__); + if (battery->wc_tx_enable) { + if (battery->wc_rx_connected) { + pr_info("@Tx_Mode %s: RX Dev, Keep TX ON status (cancel misalign)\n", __func__); + } else { + pr_info("@Tx_Mode %s: NO RX Dev, TX OFF (cancel misalign)\n", __func__); + sec_bat_tx_off_by_misalign_ocp(battery); + sec_bat_set_tx_event(battery, + BATT_TX_EVENT_WIRELESS_TX_MISALIGN, BATT_TX_EVENT_WIRELESS_TX_MISALIGN); + sec_wireless_set_tx_enable(battery, false); + } + } else { + pr_info("@Tx_Mode %s: Keep TX OFF status (cancel misalign)\n", __func__); + } + battery->tx_retry_case &= ~SEC_BAT_TX_RETRY_MISALIGN; + battery->tx_misalign_start_time = 0; + battery->tx_misalign_cnt = 0; + } + } +} +EXPORT_SYMBOL_KUNIT(sec_bat_handle_tx_misalign); + +void sec_bat_handle_tx_ocp(struct sec_battery_info *battery, bool trigger_ocp) +{ + struct timespec64 ts = {0, }; + + if (trigger_ocp && battery->wc_tx_enable) { + if (battery->tx_ocp_start_time == 0) { + ts = ktime_to_timespec64(ktime_get_boottime()); + battery->tx_ocp_start_time = ts.tv_sec; + } + pr_info("@Tx_Mode %s: ocp is triggered!!(%d)\n", __func__, ++battery->tx_ocp_cnt); + /* Attention!! in this case, 0x00(TX_OFF) is sent first */ + /* and then 0x8000(RETRY) is sent */ + if (battery->tx_ocp_cnt < 3) { + battery->tx_retry_case |= SEC_BAT_TX_RETRY_OCP; + sec_wireless_set_tx_enable(battery, false); + msleep(1000); + /* clear tx all event */ + sec_bat_set_tx_event(battery, 0, BATT_TX_EVENT_WIRELESS_ALL_MASK); + sec_bat_set_tx_event(battery, + BATT_TX_EVENT_WIRELESS_TX_RETRY, BATT_TX_EVENT_WIRELESS_TX_RETRY); + msleep(1000); + } else { + pr_info("@Tx_Mode %s: ocp over 3 times, TX OFF (cancel ocp)\n", __func__); + sec_bat_tx_off_by_misalign_ocp(battery); + sec_bat_set_tx_event(battery, + BATT_TX_EVENT_WIRELESS_TX_OCP, BATT_TX_EVENT_WIRELESS_TX_OCP); + sec_wireless_set_tx_enable(battery, false); + } + } else if (battery->tx_retry_case & SEC_BAT_TX_RETRY_OCP) { + ts = ktime_to_timespec64(ktime_get_boottime()); + if (ts.tv_sec >= battery->tx_ocp_start_time) { + battery->tx_ocp_passed_time = ts.tv_sec - battery->tx_ocp_start_time; + } else { + battery->tx_ocp_passed_time = 0xFFFFFFFF - battery->tx_ocp_start_time + + ts.tv_sec; + } + pr_info("@Tx_Mode %s: already ocp, passed time(%ld)\n", + __func__, battery->tx_ocp_passed_time); + + if (battery->tx_ocp_passed_time >= 60) { + pr_info("@Tx_Mode %s: after 1min\n", __func__); + if (battery->wc_tx_enable) { + if (battery->wc_rx_connected) { + pr_info("@Tx_Mode %s: RX Dev, Keep TX ON status (cancel ocp)\n", __func__); + } else { + pr_info("@Tx_Mode %s: NO RX Dev, TX OFF (cancel ocp)\n", __func__); + sec_bat_tx_off_by_misalign_ocp(battery); + sec_bat_set_tx_event(battery, + BATT_TX_EVENT_WIRELESS_TX_OCP, BATT_TX_EVENT_WIRELESS_TX_OCP); + sec_wireless_set_tx_enable(battery, false); + } + } else { + pr_info("@Tx_Mode %s: Keep TX OFF status (cancel ocp)\n", __func__); + } + battery->tx_retry_case &= ~SEC_BAT_TX_RETRY_OCP; + battery->tx_ocp_start_time = 0; + battery->tx_ocp_cnt = 0; + } + } +} + +void sec_bat_check_wc_re_auth(struct sec_battery_info *battery) +{ + union power_supply_propval value = {0, }; + + int tx_id = 0, auth_stat = 0; + + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ID, value); + tx_id = value.intval; + + psy_do_property(battery->pdata->wireless_charger_name, get, + POWER_SUPPLY_EXT_PROP_WIRELESS_AUTH_ADT_STATUS, value); + auth_stat = value.intval; + + pr_info("%s %s: tx_id(0x%x), cable(%d), soc(%d), auth_stat(%d)\n", WC_AUTH_MSG, __func__, + tx_id, battery->cable_type, battery->capacity, auth_stat); + + if ((auth_stat == WIRELESS_AUTH_FAIL) && (battery->capacity >= 5)) { + pr_info("%s %s: EPT Unknown for re-auth\n", WC_AUTH_MSG, __func__); + value.intval = 1; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WC_EPT_UNKNOWN, value); + battery->wc_auth_retried = true; + } else if (auth_stat == WIRELESS_AUTH_PASS) { + pr_info("%s %s: auth success\n", WC_AUTH_MSG, __func__); + battery->wc_auth_retried = true; + } else if (((tx_id != 0) && (tx_id < WC_PAD_ID_AUTH_PAD)) + || (tx_id > WC_PAD_ID_AUTH_PAD_END) + || ((tx_id == 0) && is_hv_wireless_type(battery->cable_type))) { + pr_info("%s %s: re-auth is unnecessary\n", WC_AUTH_MSG, __func__); + battery->wc_auth_retried = true; + } +} + +#if defined(CONFIG_WIRELESS_TX_MODE) +void sec_bat_check_tx_mode(struct sec_battery_info *battery) +{ + + if (battery->wc_tx_enable) { + pr_info("@Tx_Mode %s: tx_retry(0x%x), tx_switch(0x%x)", + __func__, battery->tx_retry_case, battery->tx_switch_mode); +#if !defined(CONFIG_SEC_FACTORY) + sec_bat_check_tx_battery_drain(battery); + sec_bat_check_tx_temperature(battery); + + if ((battery->wc_rx_type == SS_PHONE) || + (battery->wc_rx_type == OTHER_DEV) || + (battery->wc_rx_type == SS_BUDS)) + sec_bat_check_tx_current(battery); +#endif + sec_bat_txpower_calc(battery); + sec_bat_handle_tx_misalign(battery, false); + sec_bat_handle_tx_ocp(battery, false); + battery->tx_retry_case &= ~SEC_BAT_TX_RETRY_AC_MISSING; + + if (battery->tx_switch_mode != TX_SWITCH_MODE_OFF && battery->tx_switch_start_soc != 0) + sec_bat_check_tx_switch_mode(battery); + + } else if (battery->tx_retry_case != SEC_BAT_TX_RETRY_NONE) { + pr_info("@Tx_Mode %s: tx_retry(0x%x)", __func__, battery->tx_retry_case); +#if !defined(CONFIG_SEC_FACTORY) + sec_bat_check_tx_temperature(battery); +#endif + sec_bat_handle_tx_misalign(battery, false); + sec_bat_handle_tx_ocp(battery, false); + battery->tx_retry_case &= ~SEC_BAT_TX_RETRY_AC_MISSING; + } +} +#endif + +void sec_bat_wc_cv_mode_check(struct sec_battery_info *battery) +{ + union power_supply_propval value = {0, }; + int is_otg_on = 0; + + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL, value); + is_otg_on = value.intval; + + pr_info("%s: battery->wc_cv_mode = %d, otg(%d), cable_type(%d)\n", __func__, + battery->wc_cv_mode, is_otg_on, battery->cable_type); + + if (battery->capacity >= battery->pdata->wireless_cc_cv && !is_otg_on) { + battery->wc_cv_mode = true; + if (is_nv_wireless_type(battery->cable_type)) { + if ((battery->cable_type == SEC_BATTERY_CABLE_WIRELESS || + battery->cable_type == SEC_BATTERY_CABLE_WIRELESS_STAND || + battery->cable_type == SEC_BATTERY_CABLE_WIRELESS_TX)) { + value.intval = WIRELESS_CLAMP_ENABLE; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_RX_CONTROL, value); + } + } + /* Change FOD values for CV mode */ + value.intval = POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_PROP_STATUS, value); + + /* Change Vrect headroom for CV mode */ + if (battery->pdata->p2p_cv_headroom && battery->cable_type == SEC_BATTERY_CABLE_WIRELESS_TX) { + value.intval = WIRELESS_VRECT_ADJ_ROOM_2; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_RX_CONTROL, value); + } + } +} +EXPORT_SYMBOL_KUNIT(sec_bat_wc_cv_mode_check); + +static int is_5v_charger(struct sec_battery_info *battery, int wr_sts) +{ + if ((is_pd_wire_type(wr_sts) && battery->pd_list.max_pd_count > 1) + || is_hv_wire_12v_type(wr_sts) + || is_hv_wire_type(wr_sts) + || (wr_sts == SEC_BATTERY_CABLE_HV_TA_CHG_LIMIT) + || (wr_sts == SEC_BATTERY_CABLE_PREPARE_TA) + || (battery->current_event & SEC_BAT_CURRENT_EVENT_AFC)) + return false; + else if (is_wired_type(wr_sts)) + return true; + + return false; +} + +void sec_bat_run_wpc_tx_work(struct sec_battery_info *battery, int work_delay) +{ + if (delayed_work_pending(&battery->wpc_tx_work)) { + pr_info("%s: did not push the tx_work because of already in queue.\n", __func__); + return; + } + + cancel_delayed_work(&battery->wpc_tx_work); + __pm_stay_awake(battery->wpc_tx_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->wpc_tx_work, msecs_to_jiffies(work_delay)); +} + +static void sec_bat_wpc_tx_iv(struct sec_vote *target_vote, int target_voltage, bool need_refresh) +{ + int voter_val = get_sec_vote_result(target_vote); + + pr_info("@Tx_mode %s : present iv = %d. target iv = %d\n", __func__, voter_val, target_voltage); + + sec_vote(target_vote, VOTER_WC_TX, true, target_voltage); + + if (need_refresh && (voter_val == target_voltage)) + sec_vote_refresh(target_vote); +} + +static void sec_bat_tx_work_nodev(struct sec_battery_info *battery) +{ + if (battery->pdata->fcc_by_tx) + sec_vote(battery->fcc_vote, VOTER_WC_TX, true, + battery->pdata->fcc_by_tx); + + if (is_hv_wire_type(battery->wire_status) || + (is_pd_wire_type(battery->wire_status) && battery->hv_pdo)) { + sec_bat_wpc_tx_iv(battery->iv_vote, SEC_INPUT_VOLTAGE_5V, true); + return; + } + battery->buck_cntl_by_tx = true; + sec_vote(battery->chgen_vote, VOTER_WC_TX, true, SEC_BAT_CHG_MODE_BUCK_OFF); + if (!battery->uno_en) + sec_bat_wireless_uno_cntl(battery, true); + + sec_bat_wireless_vout_cntl(battery, battery->pdata->tx_ping_vout); + sec_bat_wireless_iout_cntl(battery, battery->pdata->tx_uno_iout_aov_gear, battery->pdata->tx_mfc_iout_aov_gear); + sec_bat_wireless_minduty_cntl(battery, battery->pdata->tx_minduty_default); +} + +static void sec_bat_tx_work_with_nv_charger(struct sec_battery_info *battery) +{ + union power_supply_propval value = {0, }; + + if (battery->pdata->tx_5v_disable) + return; + + if (battery->current_event & SEC_BAT_CURRENT_EVENT_AFC) { + if (!battery->buck_cntl_by_tx) { + battery->buck_cntl_by_tx = true; + sec_vote(battery->chgen_vote, VOTER_WC_TX, true, SEC_BAT_CHG_MODE_BUCK_OFF); + } + battery->tx_switch_mode = TX_SWITCH_MODE_OFF; + battery->tx_switch_start_soc = 0; + battery->tx_switch_mode_change = false; + + sec_bat_wireless_iout_cntl(battery, + battery->pdata->tx_uno_iout, battery->pdata->tx_mfc_iout_phone); + sec_bat_wireless_vout_cntl(battery, battery->pdata->tx_uno_vout); + sec_bat_wireless_minduty_cntl(battery, battery->pdata->tx_minduty_default); + } else if (battery->tx_switch_mode == TX_SWITCH_MODE_OFF) { + battery->tx_switch_mode = TX_SWITCH_UNO_ONLY; + battery->tx_switch_start_soc = battery->capacity; + if (!battery->buck_cntl_by_tx) { + battery->buck_cntl_by_tx = true; + sec_vote(battery->chgen_vote, VOTER_WC_TX, true, SEC_BAT_CHG_MODE_BUCK_OFF); + } + sec_bat_wireless_iout_cntl(battery, + battery->pdata->tx_uno_iout, battery->pdata->tx_mfc_iout_phone); + sec_bat_wireless_vout_cntl(battery, battery->pdata->tx_uno_vout); + sec_bat_wireless_minduty_cntl(battery, battery->pdata->tx_minduty_default); + + } else if (battery->tx_switch_mode_change == true) { + battery->tx_switch_start_soc = battery->capacity; + + pr_info("@Tx_mode: Switch Mode Change(%d -> %d)\n", + battery->tx_switch_mode, + battery->tx_switch_mode == TX_SWITCH_UNO_ONLY ? + TX_SWITCH_CHG_ONLY : TX_SWITCH_UNO_ONLY); + + if (battery->tx_switch_mode == TX_SWITCH_UNO_ONLY) { + battery->tx_switch_mode = TX_SWITCH_CHG_ONLY; + + sec_bat_wireless_iout_cntl(battery, + battery->pdata->tx_uno_iout, + battery->pdata->tx_mfc_iout_phone_5v); + sec_bat_wireless_vout_cntl(battery, battery->pdata->tx_ping_vout); + sec_bat_wireless_minduty_cntl(battery, battery->pdata->tx_minduty_5V); + + if (battery->buck_cntl_by_tx) { + battery->buck_cntl_by_tx = false; + sec_vote(battery->chgen_vote, VOTER_WC_TX, false, 0); + } + + } else if (battery->tx_switch_mode == TX_SWITCH_CHG_ONLY) { + battery->tx_switch_mode = TX_SWITCH_UNO_ONLY; + + if (!battery->buck_cntl_by_tx) { + battery->buck_cntl_by_tx = true; + sec_vote(battery->chgen_vote, + VOTER_WC_TX, true, SEC_BAT_CHG_MODE_BUCK_OFF); + } + sec_bat_wireless_iout_cntl(battery, + battery->pdata->tx_uno_iout, + battery->pdata->tx_mfc_iout_phone); + sec_bat_wireless_vout_cntl(battery, battery->pdata->tx_uno_vout); + sec_bat_wireless_minduty_cntl(battery, battery->pdata->tx_minduty_default); + + value.intval = true; + psy_do_property(battery->pdata->wireless_charger_name, set, + POWER_SUPPLY_EXT_PROP_WIRELESS_SEND_FSK, value); + } + battery->tx_switch_mode_change = false; + } +} + +void sec_bat_wireless_proc_ping_duty(struct sec_battery_info *battery, int wr_sts) +{ + if (wr_sts != SEC_BATTERY_CABLE_NONE) + sec_bat_wireless_set_ping_duty(battery, battery->pdata->tx_ping_duty_default); + else + sec_bat_wireless_set_ping_duty(battery, battery->pdata->tx_ping_duty_no_ta); +} + +void sec_bat_wpc_tx_work_content(struct sec_battery_info *battery) +{ + int wr_sts = battery->wire_status; + unsigned int tx_uno_vout = battery->pdata->tx_uno_vout; + + if (battery->wc_rx_type == SS_GEAR) + tx_uno_vout = battery->pdata->tx_gear_vout; + else if (battery->wc_rx_type == SS_BUDS) + tx_uno_vout = battery->pdata->tx_buds_vout; + + dev_info(battery->dev, "@Tx_Mode %s: Start: %d\n", __func__, battery->wc_rx_type); + if (!battery->wc_tx_enable) { + pr_info("@Tx_Mode %s : exit wpc_tx_work. Because Tx is already off\n", __func__); + goto end_of_tx_work; + } + if (battery->pdata->tx_5v_disable && is_5v_charger(battery, wr_sts)) { + pr_info("@Tx_Mode %s : 5V charger(%d) connected, disable TX\n", __func__, battery->cable_type); + sec_bat_set_tx_event(battery, BATT_TX_EVENT_WIRELESS_TX_5V_TA, BATT_TX_EVENT_WIRELESS_TX_5V_TA); + sec_wireless_set_tx_enable(battery, false); + goto end_of_tx_work; + } + + if (battery->uno_en) { + sec_bat_wireless_proc_ping_duty(battery, wr_sts); + } + + switch (battery->wc_rx_type) { + case NO_DEV: + sec_bat_tx_work_nodev(battery); + sec_bat_wireless_proc_ping_duty(battery, wr_sts); + sb_tx_init_aov(); + break; + case SS_GEAR: + { + if (battery->pdata->icl_by_tx_gear) + sec_vote(battery->input_vote, VOTER_WC_TX, true, + battery->pdata->icl_by_tx_gear); + if (battery->pdata->fcc_by_tx_gear) + sec_vote(battery->fcc_vote, VOTER_WC_TX, true, + battery->pdata->fcc_by_tx_gear); + + if (sb_tx_is_aov_enabled(battery->wire_status)) { + sb_tx_monitor_aov(battery->wc_tx_vout, battery->wc_tx_phm_mode); + battery->buck_cntl_by_tx = false; + + battery->tx_switch_mode = TX_SWITCH_GEAR_PPS; + battery->tx_switch_mode_change = true; + + sec_bat_wireless_iout_cntl(battery, battery->pdata->tx_uno_iout_aov_gear, + battery->pdata->tx_mfc_iout_aov_gear); + + if (delayed_work_pending(&battery->wpc_tx_work)) + return; + break; + } else { + if (battery->pdata->phm_vout_ctrl_dev & SEC_WIRELESS_PHM_VOUT_CTRL_GEAR) { + if (battery->wc_tx_phm_mode) { + if (is_hv_wire_type(battery->wire_status) || + (is_pd_wire_type(battery->wire_status) && battery->hv_pdo)) { + pr_info("@Tx_Mode %s : change iv(9V -> 5V).\n", __func__); + sec_bat_wireless_vout_cntl(battery, WC_TX_VOUT_5000MV); + sec_bat_wpc_tx_iv(battery->iv_vote, SEC_INPUT_VOLTAGE_5V, true); + break; + } + } else { + if ((battery->wire_status == SEC_BATTERY_CABLE_HV_TA_CHG_LIMIT) || + (is_pd_wire_type(battery->wire_status) && !battery->hv_pdo)) { + pr_info("@Tx_Mode %s : charging voltage change(5V -> 9V)\n", __func__); + /* prevent ocp */ + if (!battery->buck_cntl_by_tx) { + sec_bat_wireless_vout_cntl(battery, WC_TX_VOUT_5000MV); + sec_bat_wireless_iout_cntl(battery, battery->pdata->tx_uno_iout_gear, 1000); + battery->buck_cntl_by_tx = true; + sec_vote(battery->chgen_vote, VOTER_WC_TX, + true, SEC_BAT_CHG_MODE_BUCK_OFF); + sec_bat_run_wpc_tx_work(battery, 500); + return; + } else if (battery->wc_tx_vout < WC_TX_VOUT_8500MV) { + sec_bat_wireless_vout_cntl(battery, + battery->wc_tx_vout + WC_TX_VOUT_STEP_AOV); + sec_bat_run_wpc_tx_work(battery, 500); + return; + } + sec_bat_wpc_tx_iv(battery->iv_vote, SEC_INPUT_VOLTAGE_9V, true); + break; + } + } + } else { + if (is_hv_wire_type(battery->wire_status) || + (is_pd_wire_type(battery->wire_status) && battery->hv_pdo)) { + pr_info("@Tx_Mode %s : change iv(9V -> 5V).\n", __func__); + sec_bat_wireless_vout_cntl(battery, WC_TX_VOUT_5000MV); + sec_bat_wpc_tx_iv(battery->iv_vote, SEC_INPUT_VOLTAGE_5V, true); + break; + } + } + } + + if (is_wired_type(battery->wire_status) && battery->buck_cntl_by_tx) { + battery->buck_cntl_by_tx = false; + sec_vote(battery->chgen_vote, VOTER_WC_TX, false, 0); + } else if ((battery->wire_status == SEC_BATTERY_CABLE_NONE) && (!battery->buck_cntl_by_tx)) { + battery->buck_cntl_by_tx = true; + sec_vote(battery->chgen_vote, VOTER_WC_TX, true, SEC_BAT_CHG_MODE_BUCK_OFF); + } + + sec_bat_wireless_vout_cntl(battery, tx_uno_vout); + sec_bat_wireless_iout_cntl(battery, battery->pdata->tx_uno_iout_gear, + battery->pdata->tx_mfc_iout_gear); + } + break; + default: /* SS_BUDS, SS_PHONE, OTHER_DEV */ + if (battery->pdata->phm_vout_ctrl_dev & SEC_WIRELESS_PHM_VOUT_CTRL_BUDS) { + if (battery->wc_tx_phm_mode) { + pr_info("@Tx_Mode %s : phm on status.\n", __func__); + + sec_bat_wireless_vout_cntl(battery, WC_TX_VOUT_5000MV); + if (is_hv_wire_type(battery->wire_status) || + (is_pd_wire_type(battery->wire_status) && battery->hv_pdo)) { + pr_info("@Tx_Mode %s : change iv(9V -> 5V).\n", __func__); + sec_bat_wpc_tx_iv(battery->iv_vote, SEC_INPUT_VOLTAGE_5V, true); + } else { + pr_info("@Tx_Mode %s : change iv(%dV -> 5V).\n", + __func__, battery->pdata->tx_uno_vout); + } + battery->prev_tx_phm_mode = battery->wc_tx_phm_mode; + break; + } else { + if (battery->prev_tx_phm_mode) { + pr_info("@Tx_Mode %s : keep phm off status concept before tx off\n", __func__); + break; + } + } + } + + if ((battery->wire_status == SEC_BATTERY_CABLE_HV_TA_CHG_LIMIT) || + (is_pd_wire_type(battery->wire_status) && !battery->hv_pdo)) { + if (battery->wc_tx_vout < tx_uno_vout) { + sec_bat_wireless_vout_cntl(battery, tx_uno_vout); + if (!battery->buck_cntl_by_tx) { + battery->buck_cntl_by_tx = true; + sec_vote(battery->chgen_vote, VOTER_WC_TX, + true, SEC_BAT_CHG_MODE_BUCK_OFF); + } + sec_bat_run_wpc_tx_work(battery, 500); + return; + } + pr_info("@Tx_Mode %s : change iv(5V -> 9V)\n", __func__); + sec_bat_wpc_tx_iv(battery->iv_vote, SEC_INPUT_VOLTAGE_9V, true); + break; + } + + if (battery->wire_status == SEC_BATTERY_CABLE_NONE) { + battery->tx_switch_mode = TX_SWITCH_MODE_OFF; + battery->tx_switch_start_soc = 0; + battery->tx_switch_mode_change = false; + + if (!battery->buck_cntl_by_tx) { + battery->buck_cntl_by_tx = true; + sec_vote(battery->chgen_vote, VOTER_WC_TX, true, SEC_BAT_CHG_MODE_BUCK_OFF); + } + sec_bat_wireless_vout_cntl(battery, tx_uno_vout); + sec_bat_wireless_iout_cntl(battery, + battery->pdata->tx_uno_iout, battery->pdata->tx_mfc_iout_phone); + sec_bat_wireless_minduty_cntl(battery, battery->pdata->tx_minduty_default); + } else if (is_hv_wire_type(battery->wire_status) || + (is_pd_wire_type(battery->wire_status) && battery->hv_pdo)) { + battery->tx_switch_mode = TX_SWITCH_MODE_OFF; + battery->tx_switch_start_soc = 0; + battery->tx_switch_mode_change = false; + + if (battery->buck_cntl_by_tx) { + battery->buck_cntl_by_tx = false; + sec_vote(battery->chgen_vote, VOTER_WC_TX, false, 0); + } + sec_bat_wireless_iout_cntl(battery, + battery->pdata->tx_uno_iout, battery->pdata->tx_mfc_iout_phone); + sec_bat_wireless_minduty_cntl(battery, battery->pdata->tx_minduty_default); + } else if (is_pd_wire_type(battery->wire_status) && battery->hv_pdo) { + pr_info("@Tx_Mode %s: PD cable attached. HV PDO(%d)\n", __func__, battery->hv_pdo); + + battery->tx_switch_mode = TX_SWITCH_MODE_OFF; + battery->tx_switch_start_soc = 0; + battery->tx_switch_mode_change = false; + + if (battery->buck_cntl_by_tx) { + battery->buck_cntl_by_tx = false; + sec_vote(battery->chgen_vote, VOTER_WC_TX, false, 0); + } + sec_bat_wireless_iout_cntl(battery, + battery->pdata->tx_uno_iout, battery->pdata->tx_mfc_iout_phone); + sec_bat_wireless_minduty_cntl(battery, battery->pdata->tx_minduty_default); + } else if (is_wired_type(battery->wire_status) && !is_hv_wire_type(battery->wire_status) && + (battery->wire_status != SEC_BATTERY_CABLE_HV_TA_CHG_LIMIT)) { + sec_bat_tx_work_with_nv_charger(battery); + } + break; + } +end_of_tx_work: + dev_info(battery->dev, "@Tx_Mode %s End\n", __func__); + __pm_relax(battery->wpc_tx_ws); +} + +#define EN_RETRY_DELAY 100 +#define EN_RETRY_CNT 20 +void sec_bat_wpc_tx_en_work_content(struct sec_battery_info *battery) +{ + int wr_sts = battery->wire_status; + int selected_pdo; + static unsigned int cnt; + + pr_info("@Tx_Mode %s: tx %s\n", __func__, + battery->wc_tx_enable ? "on" : "off"); + + battery->tx_minduty = battery->pdata->tx_minduty_default; + battery->tx_switch_mode = TX_SWITCH_MODE_OFF; + battery->tx_switch_start_soc = 0; + battery->tx_switch_mode_change = false; + battery->tx_ping_duty = 0; + + if (battery->wc_tx_enable) { + /* set tx event */ + sec_bat_set_tx_event(battery, BATT_TX_EVENT_WIRELESS_TX_STATUS, + (BATT_TX_EVENT_WIRELESS_TX_STATUS | BATT_TX_EVENT_WIRELESS_TX_RETRY)); + + if (is_pd_wire_type(wr_sts)) { + sec_pd_get_selected_pdo(&selected_pdo); + if (selected_pdo == battery->sink_status.current_pdo_num) { + if (battery->sink_status.power_list[battery->sink_status.current_pdo_num].max_voltage + == SEC_INPUT_VOLTAGE_5V) { + pr_info("@Tx_Mode %s: 5V PDO. Tx Start\n", __func__); + sec_bat_wpc_tx_iv(battery->iv_vote, SEC_INPUT_VOLTAGE_5V, false); + sec_bat_run_wpc_tx_work(battery, 0); + } else { + pr_info("@Tx_Mode %s: Wait to decrease 5V (%d / %d)\n", + __func__, ++cnt, EN_RETRY_CNT); + sec_bat_wpc_tx_iv(battery->iv_vote, SEC_INPUT_VOLTAGE_5V, false); + __pm_stay_awake(battery->wpc_tx_en_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->wpc_tx_en_work, msecs_to_jiffies(EN_RETRY_DELAY)); + return; + } + } else { + if (cnt >= EN_RETRY_CNT) { + pr_info("@Tx_Mode %s: No response for 5V. Error case\n", __func__); + cnt = 0; + } else { + pr_info("@Tx_Mode %s: Wait to decrease 5V (%d / %d)\n", + __func__, ++cnt, EN_RETRY_CNT); + __pm_stay_awake(battery->wpc_tx_en_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->wpc_tx_en_work, msecs_to_jiffies(EN_RETRY_DELAY)); + return; + } + } + } else if (is_hv_wire_type(wr_sts)) { + if (cnt >= EN_RETRY_CNT) { + pr_info("@Tx_Mode %s: No response for 5V. Error case\n", __func__); + cnt = 0; + } else { + pr_info("@Tx_Mode %s: Wait to decrease 5V (%d / %d)\n", + __func__, ++cnt, EN_RETRY_CNT); + sec_bat_wpc_tx_iv(battery->iv_vote, SEC_INPUT_VOLTAGE_5V, false); + __pm_stay_awake(battery->wpc_tx_en_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->wpc_tx_en_work, msecs_to_jiffies(EN_RETRY_DELAY)); + return; + } + } else { + pr_info("@Tx_Mode %s: 5V AFC. Tx Start\n", __func__); + sec_bat_wpc_tx_iv(battery->iv_vote, SEC_INPUT_VOLTAGE_5V, false); + sec_bat_run_wpc_tx_work(battery, 0); + } + + pr_info("@Tx_Mode %s: TX Power Calculation start.\n", __func__); + queue_delayed_work(battery->monitor_wqueue, + &battery->wpc_txpower_calc_work, 0); + } else { + cancel_delayed_work(&battery->wpc_tx_work); + cancel_delayed_work(&battery->wpc_txpower_calc_work); + __pm_relax(battery->wpc_tx_ws); + + sec_bat_wireless_minduty_cntl(battery, battery->pdata->tx_minduty_default); + battery->wc_rx_type = NO_DEV; + battery->wc_rx_connected = false; + battery->tx_uno_iout = 0; + battery->tx_mfc_iout = 0; + battery->buck_cntl_by_tx = false; + sec_bat_wireless_uno_cntl(battery, false); + sec_bat_wireless_vout_cntl(battery, WC_TX_VOUT_5000MV); + sec_vote(battery->chgen_vote, VOTER_WC_TX, false, 0); + sec_vote(battery->fcc_vote, VOTER_WC_TX, false, 0); + sec_vote(battery->input_vote, VOTER_WC_TX, false, 0); + /* for 1) not supporting DC and charging bia PD20/DC on Tx */ + /* 2) supporting DC and charging bia PD20 on Tx */ + /* 3) During retrying, maintain iv_vote */ + if (battery->tx_retry_case == SEC_BAT_TX_RETRY_NONE) + sec_vote(battery->iv_vote, VOTER_WC_TX, false, 0); + cnt = 0; + } + + pr_info("@Tx_Mode %s Done\n", __func__); + __pm_relax(battery->wpc_tx_en_ws); +} + +void sec_wireless_set_tx_enable(struct sec_battery_info *battery, bool wc_tx_enable) +{ + int wr_sts = battery->wire_status; + pr_info("@Tx_Mode %s: TX Power enable ? (%d)\n", __func__, wc_tx_enable); + + if (battery->pdata->tx_5v_disable && wc_tx_enable && is_5v_charger(battery, wr_sts)) { + pr_info("@Tx_Mode %s : 5V charger(%d) connected, do not turn on TX\n", __func__, wr_sts); + sec_bat_set_tx_event(battery, BATT_TX_EVENT_WIRELESS_TX_5V_TA, BATT_TX_EVENT_WIRELESS_TX_5V_TA); + return; + } + + /* temporary code */ + sb_tx_init(battery, battery->pdata->wireless_charger_name); + sb_tx_init_aov(); + + battery->wc_tx_enable = wc_tx_enable; + battery->wc_tx_phm_mode = false; + battery->prev_tx_phm_mode = false; + + /* FPDO DC concept */ + if (wr_sts == SEC_BATTERY_CABLE_FPDO_DC && battery->wc_tx_enable) { + union power_supply_propval value = {0, }; + + value.intval = 0; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_REFRESH_CHARGING_SOURCE, value); + + } + + cancel_delayed_work(&battery->wpc_tx_en_work); + __pm_stay_awake(battery->wpc_tx_en_ws); + queue_delayed_work(battery->monitor_wqueue, + &battery->wpc_tx_en_work, 0); +} +EXPORT_SYMBOL(sec_wireless_set_tx_enable); diff --git a/drivers/battery/common/sec_charging_common.h b/drivers/battery/common/sec_charging_common.h new file mode 100644 index 000000000000..4cfe511bee75 --- /dev/null +++ b/drivers/battery/common/sec_charging_common.h @@ -0,0 +1,654 @@ +/* + * sec_charging_common.h + * Samsung Mobile Charging Common Header + * + * Copyright (C) 2012 Samsung Electronics, 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 __SEC_CHARGING_COMMON_H +#define __SEC_CHARGING_COMMON_H __FILE__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sec_battery_vote.h" +#include "sec_charging_modprobe.h" + +/* definitions */ +#define SEC_BATTERY_CABLE_HV_WIRELESS_ETX 100 +#define SEC_BATTERY_CABLE_SILENT_TYPE 99 + +#define WC_AUTH_MSG "@WC_AUTH " +#define WC_TX_MSG "@Tx_Mode " + +#define MFC_LDO_ON 1 +#define MFC_LDO_OFF 0 + +#define TX_ID_CHECK_CNT 3 +#define MISALIGN_TX_TRY_CNT 3 + +#define WL_TO_W 99 + +#if IS_ENABLED(CONFIG_USB_FACTORY_MODE) +#define FOREACH_BOOT_MODE(GEN_BOOT_MODE) \ + GEN_BOOT_MODE(NO_MODE) \ + GEN_BOOT_MODE(OB_MODE) \ + GEN_BOOT_MODE(IB_MODE) + +#define GENERATE_BOOT_MODE_ENUM(ENUM) ENUM, +#define GENERATE_BOOT_MODE_STRING(STRING) #STRING, + +enum BOOT_MODE_ENUM { + FOREACH_BOOT_MODE(GENERATE_BOOT_MODE_ENUM) +}; + +static const char * const BOOT_MODE_STRING[] = { + FOREACH_BOOT_MODE(GENERATE_BOOT_MODE_STRING) +}; +#endif + +enum battery_thermal_zone { + BAT_THERMAL_COLD = 0, + BAT_THERMAL_COOL3, + BAT_THERMAL_COOL2, + BAT_THERMAL_COOL1, + BAT_THERMAL_NORMAL, + BAT_THERMAL_WARM, + BAT_THERMAL_OVERHEAT, + BAT_THERMAL_OVERHEATLIMIT, +}; + +enum sb_wireless_mode { + SB_WRL_NONE = 0, + SB_WRL_RX_MODE = 1, + SB_WRL_TX_MODE = 2, +}; + +enum rx_device_type { + NO_DEV = 0, + OTHER_DEV, + SS_GEAR, + SS_PHONE, + SS_BUDS, +}; + +enum power_supply_ext_health { + POWER_SUPPLY_EXT_HEALTH_MIN = 20, + POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE = POWER_SUPPLY_EXT_HEALTH_MIN, + POWER_SUPPLY_EXT_HEALTH_OVERHEATLIMIT, + POWER_SUPPLY_EXT_HEALTH_VSYS_OVP, + POWER_SUPPLY_EXT_HEALTH_VBAT_OVP, + POWER_SUPPLY_EXT_HEALTH_DC_ERR, + POWER_SUPPLY_EXT_HEALTH_MAX, +}; + +enum sec_battery_current_type { + /* uA */ + SEC_BATTERY_CURRENT_UA = 0, + /* mA */ + SEC_BATTERY_CURRENT_MA, +}; + +enum sec_battery_voltage_type { + /* uA */ + SEC_BATTERY_VOLTAGE_UV = 0, + /* mA */ + SEC_BATTERY_VOLTAGE_MV, +}; + +enum sec_battery_temp_type { + /* temp */ + SEC_BATTERY_TEMP_TEMP = 0, + /* adc */ + SEC_BATTERY_TEMP_ADC, +}; + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) +enum sec_battery_dual_mode { + SEC_DUAL_BATTERY_MAIN = 0, + SEC_DUAL_BATTERY_SUB, + SEC_DUAL_BATTERY_TOTAL, +}; +#endif + +enum sec_battery_capacity_mode { + /* designed capacity */ + SEC_BATTERY_CAPACITY_DESIGNED = 0, + /* absolute capacity by fuel gauge */ + SEC_BATTERY_CAPACITY_ABSOLUTE, + /* temperary capacity in the time */ + SEC_BATTERY_CAPACITY_TEMPERARY, + /* current capacity now */ + SEC_BATTERY_CAPACITY_CURRENT, + /* cell aging information */ + SEC_BATTERY_CAPACITY_AGEDCELL, + /* charge count */ + SEC_BATTERY_CAPACITY_CYCLE, + /* full capacity rep */ + SEC_BATTERY_CAPACITY_FULL, + /* QH capacity */ + SEC_BATTERY_CAPACITY_QH, + /* vfsoc */ + SEC_BATTERY_CAPACITY_VFSOC, + /* rcomp0 */ + SEC_BATTERY_CAPACITY_RC0, +}; + +enum sec_wireless_info_mode { + SEC_WIRELESS_OTP_FIRM_RESULT = 0, + SEC_WIRELESS_IC_REVISION, + SEC_WIRELESS_IC_GRADE, + SEC_WIRELESS_IC_CHIP_ID, + SEC_WIRELESS_OTP_FIRM_VER_BIN, + SEC_WIRELESS_OTP_FIRM_VER, + SEC_WIRELESS_OTP_FIRM_VERIFY, + SEC_WIRELESS_MST_SWITCH_VERIFY, +}; + +enum sec_wireless_firmware_update_mode { + SEC_WIRELESS_FW_UPDATE_SDCARD_MODE = 0, /* manual update mode , firmware file must be in sdcard */ + SEC_WIRELESS_FW_UPDATE_BUILTIN_MODE, /* factory line update mode, MSP wirtes only this mode */ + SEC_WIRELESS_FW_UPDATE_AUTO_MODE, /* auto update mode, it works during kernel on, very similar to BUILTIN MODE */ + SEC_WIRELESS_FW_UPDATE_SPU_MODE, /* spu update mode */ + SEC_WIRELESS_FW_UPDATE_SPU_VERIFY_MODE, /* for automation test */ +}; + +enum sec_tx_sharing_mode { + SEC_TX_OFF = 0, + SEC_TX_STANDBY, + SEC_TX_POWER_TRANSFER, + SEC_TX_ERROR, +}; + +enum sec_wireless_auth_mode { + WIRELESS_AUTH_WAIT = 0, + WIRELESS_AUTH_START, + WIRELESS_AUTH_SENT, + WIRELESS_AUTH_RECEIVED, + WIRELESS_AUTH_FAIL, + WIRELESS_AUTH_PASS, +}; + +enum sec_wireless_pad_id { + WC_PAD_ID_UNKNOWN = 0x00, + /* 0x01~1F : Single Port */ + WC_PAD_ID_SNGL_NOBLE = 0x10, + WC_PAD_ID_SNGL_VEHICLE, + WC_PAD_ID_SNGL_MINI, + WC_PAD_ID_SNGL_ZERO, + WC_PAD_ID_SNGL_DREAM, + /* 0x20~2F : Multi Port */ + /* 0x30~3F : Stand Type */ + WC_PAD_ID_STAND_HERO = 0x30, + WC_PAD_ID_STAND_DREAM, + /* 0x40~4F : External Battery Pack */ + WC_PAD_ID_EXT_BATT_PACK = 0x40, + WC_PAD_ID_EXT_BATT_PACK_TA, + /* 0x50~6F : Reserved */ + WC_PAD_ID_UNO_TX = 0x72, + WC_PAD_ID_UNO_TX_B0 = 0x80, + WC_PAD_ID_UNO_TX_B1, + WC_PAD_ID_UNO_TX_B2, + WC_PAD_ID_UNO_TX_MAX = 0x9F, + WC_PAD_ID_AUTH_PAD = 0xA0, + WC_PAD_ID_DAVINCI_PAD_V, + WC_PAD_ID_DAVINCI_PAD_H, + WC_PAD_ID_AUTH_PAD_ACLASS_END = 0xAF, + WC_PAD_ID_AUTH_PAD_END = 0xBF, + /* reserved 0xA1 ~ 0xBF for auth pad */ + WC_PAD_ID_MAX = 0xFF, +}; + +enum sec_battery_adc_channel { + SEC_BAT_ADC_CHANNEL_CABLE_CHECK = 0, + SEC_BAT_ADC_CHANNEL_BATID_CHECK, + SEC_BAT_ADC_CHANNEL_TEMP, + SEC_BAT_ADC_CHANNEL_TEMP_AMBIENT, + SEC_BAT_ADC_CHANNEL_FULL_CHECK, + SEC_BAT_ADC_CHANNEL_VOLTAGE_NOW, + SEC_BAT_ADC_CHANNEL_CHG_TEMP, + SEC_BAT_ADC_CHANNEL_INBAT_VOLTAGE, + SEC_BAT_ADC_CHANNEL_DISCHARGING_CHECK, + SEC_BAT_ADC_CHANNEL_DISCHARGING_NTC, + SEC_BAT_ADC_CHANNEL_WPC_TEMP, + SEC_BAT_ADC_CHANNEL_SUB_CHG_TEMP, + SEC_BAT_ADC_CHANNEL_USB_TEMP, + SEC_BAT_ADC_CHANNEL_SUB_BAT_TEMP, + SEC_BAT_ADC_CHANNEL_BLKT_TEMP, + SEC_BAT_ADC_CHANNEL_NUM, +}; + +enum sec_battery_charge_mode { + SEC_BAT_CHG_MODE_BUCK_OFF = 0, /* buck, chg off */ + SEC_BAT_CHG_MODE_CHARGING_OFF, + SEC_BAT_CHG_MODE_PASS_THROUGH, + SEC_BAT_CHG_MODE_CHARGING, /* buck, chg on */ + SEC_BAT_CHG_MODE_OTG_ON, + SEC_BAT_CHG_MODE_OTG_OFF, + SEC_BAT_CHG_MODE_UNO_ON, + SEC_BAT_CHG_MODE_UNO_OFF, + SEC_BAT_CHG_MODE_UNO_ONLY, + SEC_BAT_CHG_MODE_NOT_SET, + SEC_BAT_CHG_MODE_MAX, +}; + +/* charging mode */ +enum sec_battery_charging_mode { + /* no charging */ + SEC_BATTERY_CHARGING_NONE = 0, + /* 1st charging */ + SEC_BATTERY_CHARGING_1ST, + /* 2nd charging */ + SEC_BATTERY_CHARGING_2ND, + /* recharging */ + SEC_BATTERY_CHARGING_RECHARGING, +}; + +/* POWER_SUPPLY_EXT_PROP_MEASURE_SYS */ +enum sec_battery_measure_sys { + SEC_BATTERY_ISYS_MA = 0, + SEC_BATTERY_ISYS_UA, + SEC_BATTERY_ISYS_AVG_MA, + SEC_BATTERY_ISYS_AVG_UA, + SEC_BATTERY_VSYS, +}; + +/* POWER_SUPPLY_EXT_PROP_MEASURE_INPUT */ +enum sec_battery_measure_input { + SEC_BATTERY_IIN_MA = 0, + SEC_BATTERY_IIN_UA, + SEC_BATTERY_VBYP, + SEC_BATTERY_VIN_MA, + SEC_BATTERY_VIN_UA, +}; + +enum sec_battery_direct_charging_source_ctrl { + SEC_TEST_MODE = 0x1, + SEC_SEND_UVDM = 0x2, + SEC_STORE_MODE = 0x4, +}; + +enum sec_battery_slate_mode { + SEC_SLATE_OFF = 0, + SEC_SLATE_MODE, + SEC_SMART_SWITCH_SLATE, + SEC_SMART_SWITCH_SRC, +}; + +extern const char *sb_rx_type_str(int type); +extern const char *sb_vout_ctr_mode_str(int vout_mode); +extern const char *sb_rx_vout_str(int vout); + +/* tx_event */ +#define BATT_TX_EVENT_WIRELESS_TX_STATUS 0x00000001 +#define BATT_TX_EVENT_WIRELESS_RX_CONNECT 0x00000002 +#define BATT_TX_EVENT_WIRELESS_TX_FOD 0x00000004 +#define BATT_TX_EVENT_WIRELESS_TX_HIGH_TEMP 0x00000008 +#define BATT_TX_EVENT_WIRELESS_RX_UNSAFE_TEMP 0x00000010 +#define BATT_TX_EVENT_WIRELESS_RX_CHG_SWITCH 0x00000020 +#define BATT_TX_EVENT_WIRELESS_RX_CS100 0x00000040 +#define BATT_TX_EVENT_WIRELESS_TX_OTG_ON 0x00000080 +#define BATT_TX_EVENT_WIRELESS_TX_LOW_TEMP 0x00000100 +#define BATT_TX_EVENT_WIRELESS_TX_SOC_DRAIN 0x00000200 +#define BATT_TX_EVENT_WIRELESS_TX_CRITICAL_EOC 0x00000400 +#define BATT_TX_EVENT_WIRELESS_TX_CAMERA_ON 0x00000800 +#define BATT_TX_EVENT_WIRELESS_TX_OCP 0x00001000 +#define BATT_TX_EVENT_WIRELESS_TX_MISALIGN 0x00002000 +#define BATT_TX_EVENT_WIRELESS_TX_ETC 0x00004000 +#define BATT_TX_EVENT_WIRELESS_TX_RETRY 0x00008000 +#define BATT_TX_EVENT_WIRELESS_TX_5V_TA 0x00010000 +#define BATT_TX_EVENT_WIRELESS_TX_AC_MISSING 0x00020000 +#define BATT_TX_EVENT_WIRELESS_ALL_MASK 0x0003ffff +#define BATT_TX_EVENT_WIRELESS_TX_ERR (BATT_TX_EVENT_WIRELESS_TX_FOD | \ + BATT_TX_EVENT_WIRELESS_TX_HIGH_TEMP | BATT_TX_EVENT_WIRELESS_RX_UNSAFE_TEMP | \ + BATT_TX_EVENT_WIRELESS_RX_CHG_SWITCH | BATT_TX_EVENT_WIRELESS_RX_CS100 | \ + BATT_TX_EVENT_WIRELESS_TX_OTG_ON | BATT_TX_EVENT_WIRELESS_TX_LOW_TEMP | \ + BATT_TX_EVENT_WIRELESS_TX_SOC_DRAIN | BATT_TX_EVENT_WIRELESS_TX_CRITICAL_EOC | \ + BATT_TX_EVENT_WIRELESS_TX_CAMERA_ON | BATT_TX_EVENT_WIRELESS_TX_OCP | \ + BATT_TX_EVENT_WIRELESS_TX_MISALIGN | BATT_TX_EVENT_WIRELESS_TX_ETC | \ + BATT_TX_EVENT_WIRELESS_TX_5V_TA | BATT_TX_EVENT_WIRELESS_TX_AC_MISSING) + +#define SEC_BAT_TX_RETRY_NONE 0x0000 +#define SEC_BAT_TX_RETRY_MISALIGN 0x0001 +#define SEC_BAT_TX_RETRY_CAMERA 0x0002 +#define SEC_BAT_TX_RETRY_CALL 0x0004 +#define SEC_BAT_TX_RETRY_MIX_TEMP 0x0008 +#define SEC_BAT_TX_RETRY_HIGH_TEMP 0x0010 +#define SEC_BAT_TX_RETRY_LOW_TEMP 0x0020 +#define SEC_BAT_TX_RETRY_OCP 0x0040 +#define SEC_BAT_TX_RETRY_AC_MISSING 0x0080 + +/* ext_event */ +#define BATT_EXT_EVENT_NONE 0x00000000 +#define BATT_EXT_EVENT_CAMERA 0x00000001 +#define BATT_EXT_EVENT_DEX 0x00000002 +#define BATT_EXT_EVENT_CALL 0x00000004 + +#define SEC_BAT_ERROR_CAUSE_NONE 0x0000 +#define SEC_BAT_ERROR_CAUSE_FG_INIT_FAIL 0x0001 +#define SEC_BAT_ERROR_CAUSE_I2C_FAIL 0xFFFFFFFF + +/* monitor activation */ +enum sec_battery_polling_time_type { + /* same order with power supply status */ + SEC_BATTERY_POLLING_TIME_BASIC = 0, + SEC_BATTERY_POLLING_TIME_CHARGING, + SEC_BATTERY_POLLING_TIME_DISCHARGING, + SEC_BATTERY_POLLING_TIME_NOT_CHARGING, + SEC_BATTERY_POLLING_TIME_SLEEP, +}; + +/* BATT_INBAT_VOLTAGE */ +enum sec_battery_fgsrc_switching { + SEC_BAT_INBAT_FGSRC_SWITCHING_VBAT = 0, + SEC_BAT_INBAT_FGSRC_SWITCHING_VSYS, + SEC_BAT_FGSRC_SWITCHING_VBAT, + SEC_BAT_FGSRC_SWITCHING_VSYS +}; + +enum ta_alert_mode { + OCP_NONE = 0, + OCP_DETECT, + OCP_WA_ACTIVE, +}; + +enum tx_switch_mode_state { + TX_SWITCH_MODE_OFF = 0, + TX_SWITCH_CHG_ONLY, + TX_SWITCH_UNO_ONLY, + TX_SWITCH_GEAR_PPS, /* temporary mode */ +}; + +enum d2d_auth_type { + D2D_AUTH_NONE = 0, + D2D_AUTH_SRC, + D2D_AUTH_SNK, +}; + +enum d2d_mode { + HP_D2D_NONE = 0, + HP_D2D_ON, + HP_D2D_BATT_TMP, + HP_D2D_LRP_TMP, + HP_D2D_OCP, + HP_D2D_SOC, + HP_D2D_LCD, +}; + +enum { + RX_POWER_NONE, + RX_POWER_5W, + RX_POWER_7_5W, + RX_POWER_12W, + RX_POWER_15W, +}; + +enum mfc_phm_state { + NONE_PHM = 0, + EXIT_PHM, + ENTER_PHM, + FAILED_PHM, + END_PHM, +}; + +/* full check condition type (can be used overlapped) */ +#define sec_battery_full_condition_t unsigned int + +/* battery check : POWER_SUPPLY_PROP_PRESENT */ +enum sec_battery_check { + /* No Check for internal battery */ + SEC_BATTERY_CHECK_NONE, + /* by ADC */ + SEC_BATTERY_CHECK_ADC, + /* by callback function (battery certification by 1 wired)*/ + SEC_BATTERY_CHECK_CALLBACK, + /* by PMIC */ + SEC_BATTERY_CHECK_PMIC, + /* by fuel gauge */ + SEC_BATTERY_CHECK_FUELGAUGE, + /* by charger */ + SEC_BATTERY_CHECK_CHARGER, + /* by interrupt (use check_battery_callback() to check battery) */ + SEC_BATTERY_CHECK_INT, +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + /* by dual battery */ + SEC_BATTERY_CHECK_DUAL_BAT_GPIO, +#endif +}; +#define sec_battery_check_t \ + enum sec_battery_check + +/* cable check (can be used overlapped) */ +#define sec_battery_cable_check_t unsigned int + +/* check cable source (can be used overlapped) */ +#define sec_battery_cable_source_t unsigned int + +/* capacity calculation type (can be used overlapped) */ +#define sec_fuelgauge_capacity_type_t int + +/* SEC_FUELGAUGE_CAPACITY_TYPE_RESET + * use capacity information to reset fuel gauge + * (only for driver algorithm, can NOT be set by user) + */ +#define SEC_FUELGAUGE_CAPACITY_TYPE_RESET (-1) +/* SEC_FUELGAUGE_CAPACITY_TYPE_RAW + * use capacity information from fuel gauge directly + */ +#define SEC_FUELGAUGE_CAPACITY_TYPE_RAW 0x1 +/* SEC_FUELGAUGE_CAPACITY_TYPE_SCALE + * rescale capacity by scaling, need min and max value for scaling + */ +#define SEC_FUELGAUGE_CAPACITY_TYPE_SCALE 0x2 +/* SEC_FUELGAUGE_CAPACITY_TYPE_DYNAMIC_SCALE + * change only maximum capacity dynamically + * to keep time for every SOC unit + */ +#define SEC_FUELGAUGE_CAPACITY_TYPE_DYNAMIC_SCALE 0x4 +/* SEC_FUELGAUGE_CAPACITY_TYPE_ATOMIC + * change capacity value by only -1 or +1 + * no sudden change of capacity + */ +#define SEC_FUELGAUGE_CAPACITY_TYPE_ATOMIC 0x8 +/* SEC_FUELGAUGE_CAPACITY_TYPE_SKIP_ABNORMAL + * skip current capacity value + * if it is abnormal value + */ +#define SEC_FUELGAUGE_CAPACITY_TYPE_SKIP_ABNORMAL 0x10 + +#define SEC_FUELGAUGE_CAPACITY_TYPE_CAPACITY_POINT 0x20 + +#define SEC_FUELGAUGE_CAPACITY_TYPE_LOST_SOC 0x40 + +#define SEC_FUELGAUGE_CAPACITY_TYPE_REPCAP 0x80 + +/* charger function settings (can be used overlapped) */ +/* SEC_CHARGER_NO_GRADUAL_CHARGING_CURRENT + * disable gradual charging current setting + * SUMMIT:AICL, MAXIM:regulation loop + */ +#define SEC_CHARGER_NO_GRADUAL_CHARGING_CURRENT 1 + +/* SEC_CHARGER_MINIMUM_SIOP_CHARGING_CURRENT + * charging current should be over than USB charging current + */ +#define SEC_CHARGER_MINIMUM_SIOP_CHARGING_CURRENT 2 + +#define SEC_BATTERY_CABLE_TYPE_FROM_MTK 1 + +#if defined(CONFIG_TABLET_MODEL_CONCEPT) && !defined(CONFIG_SEC_FACTORY) +#define SLOW_CHARGING_CURRENT_STANDARD 1000 +#else +#define SLOW_CHARGING_CURRENT_STANDARD 400 +#endif + +typedef struct sec_age_data { + unsigned int cycle; + unsigned int float_voltage; + unsigned int recharge_condition_vcell; + unsigned int full_condition_vcell; + unsigned int full_condition_soc; +#if defined(CONFIG_BATTERY_AGE_FORECAST_B2B) + unsigned int max_charging_current; +#endif +} sec_age_data_t; + +typedef struct { + unsigned int cycle; + unsigned int asoc; +} battery_health_condition; + +#define is_ppde_wireless_type(cable_type) ( \ + cable_type == SEC_BATTERY_CABLE_PREPARE_WIRELESS_20 || \ + cable_type == SEC_BATTERY_CABLE_HV_WIRELESS_20_LIMIT || \ + cable_type == SEC_BATTERY_CABLE_HV_WIRELESS_20) + +#define is_pwr_nego_wireless_type(cable_type) ( \ + cable_type == SEC_BATTERY_CABLE_HV_WIRELESS_20 || \ + cable_type == SEC_BATTERY_CABLE_WIRELESS_EPP) + +#define is_hv_wireless_type(cable_type) ( \ + cable_type == SEC_BATTERY_CABLE_HV_WIRELESS || \ + cable_type == SEC_BATTERY_CABLE_WIRELESS_HV_STAND || \ + cable_type == SEC_BATTERY_CABLE_HV_WIRELESS_20 || \ + cable_type == SEC_BATTERY_CABLE_HV_WIRELESS_20_LIMIT || \ + cable_type == SEC_BATTERY_CABLE_WIRELESS_HV_VEHICLE || \ + cable_type == SEC_BATTERY_CABLE_WIRELESS_HV_PACK || \ + cable_type == SEC_BATTERY_CABLE_WIRELESS_EPP) + +#define is_nv_wireless_type(cable_type) ( \ + cable_type == SEC_BATTERY_CABLE_WIRELESS || \ + cable_type == SEC_BATTERY_CABLE_PMA_WIRELESS || \ + cable_type == SEC_BATTERY_CABLE_WIRELESS_PACK || \ + cable_type == SEC_BATTERY_CABLE_WIRELESS_STAND || \ + cable_type == SEC_BATTERY_CABLE_WIRELESS_VEHICLE || \ + cable_type == SEC_BATTERY_CABLE_PREPARE_WIRELESS_HV || \ + cable_type == SEC_BATTERY_CABLE_PREPARE_WIRELESS_20 || \ + cable_type == SEC_BATTERY_CABLE_WIRELESS_TX || \ + cable_type == SEC_BATTERY_CABLE_WIRELESS_EPP_NV) + +#define is_wireless_type(cable_type) ( \ + is_hv_wireless_type(cable_type) || \ + is_nv_wireless_type(cable_type)) + +#define is_wireless_fake_type(cable_type) ( \ + cable_type == SEC_BATTERY_CABLE_WIRELESS_FAKE || \ + cable_type == SEC_BATTERY_CABLE_WIRELESS_EPP_FAKE) + +#define is_wireless_all_type(cable_type) ( \ + is_wireless_type(cable_type) || \ + is_wireless_fake_type(cable_type)) + +#define is_not_wireless_type(cable_type) ( \ + cable_type != SEC_BATTERY_CABLE_WIRELESS && \ + cable_type != SEC_BATTERY_CABLE_PMA_WIRELESS && \ + cable_type != SEC_BATTERY_CABLE_WIRELESS_PACK && \ + cable_type != SEC_BATTERY_CABLE_WIRELESS_STAND && \ + cable_type != SEC_BATTERY_CABLE_HV_WIRELESS && \ + cable_type != SEC_BATTERY_CABLE_PREPARE_WIRELESS_HV && \ + cable_type != SEC_BATTERY_CABLE_WIRELESS_HV_STAND && \ + cable_type != SEC_BATTERY_CABLE_WIRELESS_VEHICLE && \ + cable_type != SEC_BATTERY_CABLE_WIRELESS_HV_VEHICLE && \ + cable_type != SEC_BATTERY_CABLE_WIRELESS_TX && \ + cable_type != SEC_BATTERY_CABLE_PREPARE_WIRELESS_20 && \ + cable_type != SEC_BATTERY_CABLE_HV_WIRELESS_20 && \ + cable_type != SEC_BATTERY_CABLE_HV_WIRELESS_20_LIMIT && \ + cable_type != SEC_BATTERY_CABLE_WIRELESS_HV_PACK && \ + cable_type != SEC_BATTERY_CABLE_WIRELESS_EPP && \ + cable_type != SEC_BATTERY_CABLE_WIRELESS_EPP_NV) + +#define is_wired_type(cable_type) \ + (is_not_wireless_type(cable_type) && (cable_type != SEC_BATTERY_CABLE_NONE) && \ + (cable_type != SEC_BATTERY_CABLE_OTG)) + +#define is_hv_qc_wire_type(cable_type) ( \ + cable_type == SEC_BATTERY_CABLE_QC20 || \ + cable_type == SEC_BATTERY_CABLE_QC30) + +#define is_hv_afc_wire_type(cable_type) ( \ + cable_type == SEC_BATTERY_CABLE_9V_ERR || \ + cable_type == SEC_BATTERY_CABLE_9V_TA || \ + cable_type == SEC_BATTERY_CABLE_9V_UNKNOWN || \ + cable_type == SEC_BATTERY_CABLE_12V_TA) + +#define is_hv_wire_9v_type(cable_type) ( \ + cable_type == SEC_BATTERY_CABLE_9V_ERR || \ + cable_type == SEC_BATTERY_CABLE_9V_TA || \ + cable_type == SEC_BATTERY_CABLE_9V_UNKNOWN || \ + cable_type == SEC_BATTERY_CABLE_POGO_9V || \ + cable_type == SEC_BATTERY_CABLE_QC20) + +#define is_hv_wire_12v_type(cable_type) ( \ + cable_type == SEC_BATTERY_CABLE_12V_TA || \ + cable_type == SEC_BATTERY_CABLE_QC30) + +#define is_hv_wire_type(cable_type) ( \ + is_hv_afc_wire_type(cable_type) || is_hv_qc_wire_type(cable_type)) + +#define is_nocharge_type(cable_type) ( \ + cable_type == SEC_BATTERY_CABLE_NONE || \ + cable_type == SEC_BATTERY_CABLE_OTG || \ + cable_type == SEC_BATTERY_CABLE_POWER_SHARING) + + +#define chg_can_sleep_type(cable_type) ( \ + !is_wired_type(cable_type) || cable_type == SEC_BATTERY_CABLE_TIMEOUT) + +#define is_slate_mode(battery) ((battery->current_event & SEC_BAT_CURRENT_EVENT_SLATE) \ + == SEC_BAT_CURRENT_EVENT_SLATE) + +#define can_usb_suspend_type(cable_type) ( \ + cable_type == SEC_BATTERY_CABLE_PDIC || \ + cable_type == SEC_BATTERY_CABLE_FPDO_DC || \ + cable_type == SEC_BATTERY_CABLE_PDIC_APDO || \ + cable_type == SEC_BATTERY_CABLE_USB || \ + cable_type == SEC_BATTERY_CABLE_USB_CDP) + +#define is_pd_wire_type(cable_type) ( \ + cable_type == SEC_BATTERY_CABLE_PDIC || \ + cable_type == SEC_BATTERY_CABLE_FPDO_DC || \ + cable_type == SEC_BATTERY_CABLE_PDIC_APDO) + +#define is_pd_apdo_wire_type(cable_type) ( \ + cable_type == SEC_BATTERY_CABLE_FPDO_DC || \ + cable_type == SEC_BATTERY_CABLE_PDIC_APDO) + +#define is_pd_fpdo_wire_type(cable_type) ( \ + cable_type == SEC_BATTERY_CABLE_PDIC) + +#define is_hv_pdo_wire_type(cable_type, hv_pdo) ( \ + (cable_type == SEC_BATTERY_CABLE_PDIC || \ + cable_type == SEC_BATTERY_CABLE_FPDO_DC || \ + cable_type == SEC_BATTERY_CABLE_PDIC_APDO) && \ + hv_pdo) + +#define is_pogo_wire_type(cable_type) ( \ + cable_type == SEC_BATTERY_CABLE_POGO || \ + cable_type == SEC_BATTERY_CABLE_POGO_9V) + +#define is_wcin_type(cable_type) ( \ + is_pogo_wire_type(cable_type) || is_wireless_type(cable_type)) +#endif /* __SEC_CHARGING_COMMON_H */ diff --git a/drivers/battery/common/sec_charging_modprobe.c b/drivers/battery/common/sec_charging_modprobe.c new file mode 100644 index 000000000000..ab8957bced9b --- /dev/null +++ b/drivers/battery/common/sec_charging_modprobe.c @@ -0,0 +1,75 @@ +/* + * sec_charging_modprobe.c + * Samsung Mobile Battery Driver + * + * Copyright (C) 2021 Samsung Electronics + * + * + * 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. + */ + +#include "sec_charging_modprobe.h" + +#define MODPROB_TIMEOUT 20000 + +#if IS_MODULE(CONFIG_BATTERY_SAMSUNG) +static struct dev_init_info gdev_init; + +void sec_chg_init_gdev(void) +{ + gdev_init.dev = 0; + init_waitqueue_head(&gdev_init.dev_wait); +} + +int sec_chg_set_dev_init(unsigned int dev) +{ + gdev_init.dev |= dev; + wake_up(&gdev_init.dev_wait); + + return 0; +} +EXPORT_SYMBOL(sec_chg_set_dev_init); + +void sec_chg_check_modprobe(void) +{ + unsigned int check_dev = 0; + + check_dev |= SC_DEV_FG | SC_DEV_MAIN_CHG; +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + check_dev |= SC_DEV_MAIN_LIM | SC_DEV_SUB_LIM; +#endif +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + check_dev |= SC_DEV_DIR_CHG | SC_DEV_SEC_DIR_CHG; +#endif +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + check_dev |= SC_DEV_WRL_CHG; +#if IS_ENABLED(CONFIG_SB_MFC) + check_dev |= SC_DEV_SB_MFC; +#endif +#endif + + if (!wait_event_timeout(gdev_init.dev_wait, + gdev_init.dev == check_dev, msecs_to_jiffies(MODPROB_TIMEOUT))) + pr_info("%s: dev_init timeout(0x%x)\n", __func__, gdev_init.dev); + else + pr_info("%s: takes time to wait(0x%x)\n", __func__, gdev_init.dev); +} +EXPORT_SYMBOL(sec_chg_check_modprobe); + +void sec_chg_check_dev_modprobe(unsigned int dev) +{ + if (!wait_event_timeout(gdev_init.dev_wait, + gdev_init.dev & dev, msecs_to_jiffies(MODPROB_TIMEOUT))) + pr_info("%s: dev_init timeout(0x%x)\n", __func__, dev); + else + pr_info("%s: takes time to wait(0x%x)\n", __func__, dev); +} +EXPORT_SYMBOL(sec_chg_check_dev_modprobe); +#else +void sec_chg_init_gdev(void) { } +int sec_chg_set_dev_init(unsigned int dev) { return 0; } +void sec_chg_check_modprobe(void) { } +void sec_chg_check_dev_modprobe(unsigned int dev) { } +#endif diff --git a/drivers/battery/common/sec_charging_modprobe.h b/drivers/battery/common/sec_charging_modprobe.h new file mode 100644 index 000000000000..d64e8fed29aa --- /dev/null +++ b/drivers/battery/common/sec_charging_modprobe.h @@ -0,0 +1,43 @@ +/* + * sec_charging_modprobe.h + * Samsung Mobile Battery Header + * + * Copyright (C) 2021 Samsung Electronics + * + * + * 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. + */ + +#ifndef __SEC_CHARGING_MODPROBE_H +#define __SEC_CHARGING_MODPROBE_H __FILE__ + +#include +#include +#include +#include +#include + +enum sec_chg_dev_info { + SC_DEV_FG = 0x1, + SC_DEV_MAIN_CHG = 0x2, + SC_DEV_DIR_CHG = 0x4, + SC_DEV_SEC_DIR_CHG = 0x8, + SC_DEV_WRL_CHG = 0x10, + SC_DEV_SB_MFC = 0x20, + SC_DEV_MAIN_LIM = 0x40, + SC_DEV_SUB_LIM = 0x80, +}; + +struct dev_init_info { + wait_queue_head_t dev_wait; + unsigned int dev; +}; + +extern int sec_chg_set_dev_init(unsigned int dev); +extern void sec_chg_check_modprobe(void); +extern void sec_chg_check_dev_modprobe(unsigned int dev); +extern void sec_chg_init_gdev(void); + +#endif /* __SEC_CHARGING_MODPROBE_H */ diff --git a/drivers/battery/common/sec_cisd.c b/drivers/battery/common/sec_cisd.c new file mode 100644 index 000000000000..bbae97179a26 --- /dev/null +++ b/drivers/battery/common/sec_cisd.c @@ -0,0 +1,856 @@ +/* + * sec_cisd.c + * Samsung Mobile Battery Driver + * + * Copyright (C) 2012 Samsung Electronics + * + * + * 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. + */ +#include "sec_battery.h" +#include "sec_cisd.h" + +#if IS_ENABLED(CONFIG_SEC_ABC) +#include +#endif + +const char *cisd_data_str[] = { + "RESET_ALG", "ALG_INDEX", "FULL_CNT", "CAP_MAX", "CAP_MIN", "RECHARGING_CNT", "VALERT_CNT", + "BATT_CYCLE", "WIRE_CNT", "WIRELESS_CNT", "HIGH_SWELLING_CNT", "LOW_SWELLING_CNT", + "WC_HIGH_SWELLING_CNT", "SWELLING_FULL_CNT", "SWELLING_RECOVERY_CNT", "AICL_CNT", "BATT_THM_MAX", + "BATT_THM_MIN", "CHG_THM_MAX", "CHG_THM_MIN", "WPC_THM_MAX", "WPC_THM_MIN", "USB_THM_MAX", "USB_THM_MIN", + "CHG_BATT_THM_MAX", "CHG_BATT_THM_MIN", "CHG_CHG_THM_MAX", "CHG_CHG_THM_MIN", "CHG_WPC_THM_MAX", + "CHG_WPC_THM_MIN", "CHG_USB_THM_MAX", "CHG_USB_THM_MIN", "USB_OVERHEAT_CHARGING", "UNSAFETY_VOLT", + "UNSAFETY_TEMP", "SAFETY_TIMER", "VSYS_OVP", "VBAT_OVP", "USB_OVERHEAT_RAPID_CHANGE", "ASOC", + "USB_OVERHEAT_ALONE", "CAP_NOM", "RC0" +}; +EXPORT_SYMBOL(cisd_data_str); + +const char *cisd_data_str_d[] = { + "FULL_CNT_D", "CAP_MAX_D", "CAP_MIN_D", "RECHARGING_CNT_D", "VALERT_CNT_D", "WIRE_CNT_D", "WIRELESS_CNT_D", + "HIGH_SWELLING_CNT_D", "LOW_SWELLING_CNT_D", "WC_HIGH_SWELLING_CNT_D", "SWELLING_FULL_CNT_D", + "SWELLING_RECOVERY_CNT_D", "AICL_CNT_D", "BATT_THM_MAX_D", "BATT_THM_MIN_D", "SUB_BATT_THM_MAX_D", + "SUB_BATT_THM_MIN_D", "CHG_THM_MAX_D", "CHG_THM_MIN_D", "USB_THM_MAX_D", "USB_THM_MIN_D", + "CHG_BATT_THM_MAX_D", "CHG_BATT_THM_MIN_D", "CHG_SUB_BATT_THM_MAX_D", "CHG_SUB_BATT_THM_MIN_D", + "CHG_CHG_THM_MAX_D", "CHG_CHG_THM_MIN_D", "CHG_USB_THM_MAX_D", "CHG_USB_THM_MIN_D", + "USB_OVERHEAT_CHARGING_D", "UNSAFETY_VOLT_D", "UNSAFETY_TEMP_D", + "SAFETY_TIMER_D", "VSYS_OVP_D", "VBAT_OVP_D", "USB_OVERHEAT_RAPID_CHANGE_D", "BUCK_OFF_D", + "USB_OVERHEAT_ALONE_D", "DROP_SENSOR_D", "CHG_TIME_D", "TOTAL_CHG_TIME_D" +}; +EXPORT_SYMBOL(cisd_data_str_d); + +const char *cisd_cable_data_str[] = {"TA", "AFC", "AFC_FAIL", "QC", "QC_FAIL", "PD", "PD_HIGH", "HV_WC_20", "FPDO_DC"}; +EXPORT_SYMBOL(cisd_cable_data_str); +const char *cisd_tx_data_str[] = {"ON", "OTHER", "GEAR", "PHONE", "BUDS"}; +EXPORT_SYMBOL(cisd_tx_data_str); +#if IS_ENABLED(CONFIG_DUAL_BATTERY) +const char *cisd_event_data_str[] = {"DC_ERR", "TA_OCP_DET", "TA_OCP_ON", "OVP_EVENT_POWER", "OVP_EVENT_SIGNAL", "OTG", "D2D", "MAIN_BAT_ERR", "SUB_BAT_ERR", "WA_ERR"}; +#else +const char *cisd_event_data_str[] = {"DC_ERR", "TA_OCP_DET", "TA_OCP_ON", "OVP_EVENT_POWER", "OVP_EVENT_SIGNAL", "OTG", "D2D"}; +#endif +EXPORT_SYMBOL(cisd_event_data_str); + +bool sec_bat_cisd_check(struct sec_battery_info *battery) +{ + union power_supply_propval val = {0, }; + struct cisd *pcisd = &battery->cisd; + bool ret = false; + int voltage = battery->voltage_now; + + if (battery->factory_mode || battery->is_jig_on || battery->skip_cisd) { + dev_info(battery->dev, "%s: No need to check in factory mode\n", + __func__); + return ret; + } + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + voltage = max(battery->voltage_avg_main, battery->voltage_avg_sub); +#endif + + if ((battery->status == POWER_SUPPLY_STATUS_CHARGING) || + (battery->status == POWER_SUPPLY_STATUS_FULL)) { + + /* check abnormal vbat */ + pcisd->ab_vbat_check_count = voltage > pcisd->max_voltage_thr ? + pcisd->ab_vbat_check_count + 1 : 0; + + if ((pcisd->ab_vbat_check_count >= pcisd->ab_vbat_max_count) && + !(pcisd->state & CISD_STATE_OVER_VOLTAGE)) { + dev_info(battery->dev, "%s : [CISD] Battery Over Voltage Protection !! vbat(%d)mV\n", + __func__, voltage); + val.intval = true; + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_VBAT_OVP, + val); + pcisd->data[CISD_DATA_VBAT_OVP]++; + pcisd->data[CISD_DATA_VBAT_OVP_PER_DAY]++; + pcisd->state |= CISD_STATE_OVER_VOLTAGE; +#if IS_ENABLED(CONFIG_SEC_ABC) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=battery@INFO=over_voltage"); +#else + sec_abc_send_event("MODULE=battery@WARN=over_voltage"); +#endif +#endif + } + + if (battery->temperature > pcisd->data[CISD_DATA_CHG_BATT_TEMP_MAX]) + pcisd->data[CISD_DATA_CHG_BATT_TEMP_MAX] = battery->temperature; + if (battery->temperature < pcisd->data[CISD_DATA_CHG_BATT_TEMP_MIN]) + pcisd->data[CISD_DATA_CHG_BATT_TEMP_MIN] = battery->temperature; + + if (battery->chg_temp > pcisd->data[CISD_DATA_CHG_CHG_TEMP_MAX]) + pcisd->data[CISD_DATA_CHG_CHG_TEMP_MAX] = battery->chg_temp; + if (battery->chg_temp < pcisd->data[CISD_DATA_CHG_CHG_TEMP_MIN]) + pcisd->data[CISD_DATA_CHG_CHG_TEMP_MIN] = battery->chg_temp; + + if (battery->wpc_temp > pcisd->data[CISD_DATA_CHG_WPC_TEMP_MAX]) + pcisd->data[CISD_DATA_CHG_WPC_TEMP_MAX] = battery->wpc_temp; + if (battery->wpc_temp < pcisd->data[CISD_DATA_CHG_WPC_TEMP_MIN]) + pcisd->data[CISD_DATA_CHG_WPC_TEMP_MIN] = battery->wpc_temp; + + if (battery->usb_temp > pcisd->data[CISD_DATA_CHG_USB_TEMP_MAX]) + pcisd->data[CISD_DATA_CHG_USB_TEMP_MAX] = battery->usb_temp; + if (battery->usb_temp < pcisd->data[CISD_DATA_CHG_USB_TEMP_MIN]) + pcisd->data[CISD_DATA_CHG_USB_TEMP_MIN] = battery->usb_temp; + + if (battery->temperature > pcisd->data[CISD_DATA_CHG_BATT_TEMP_MAX_PER_DAY]) + pcisd->data[CISD_DATA_CHG_BATT_TEMP_MAX_PER_DAY] = battery->temperature; + if (battery->temperature < pcisd->data[CISD_DATA_CHG_BATT_TEMP_MIN_PER_DAY]) + pcisd->data[CISD_DATA_CHG_BATT_TEMP_MIN_PER_DAY] = battery->temperature; + + if (battery->sub_bat_temp > pcisd->data[CISD_DATA_CHG_SUB_BATT_TEMP_MAX_PER_DAY]) + pcisd->data[CISD_DATA_CHG_SUB_BATT_TEMP_MAX_PER_DAY] = battery->sub_bat_temp; + if (battery->sub_bat_temp < pcisd->data[CISD_DATA_CHG_SUB_BATT_TEMP_MIN_PER_DAY]) + pcisd->data[CISD_DATA_CHG_SUB_BATT_TEMP_MIN_PER_DAY] = battery->sub_bat_temp; + + if (battery->chg_temp > pcisd->data[CISD_DATA_CHG_CHG_TEMP_MAX_PER_DAY]) + pcisd->data[CISD_DATA_CHG_CHG_TEMP_MAX_PER_DAY] = battery->chg_temp; + if (battery->chg_temp < pcisd->data[CISD_DATA_CHG_CHG_TEMP_MIN_PER_DAY]) + pcisd->data[CISD_DATA_CHG_CHG_TEMP_MIN_PER_DAY] = battery->chg_temp; + + if (battery->usb_temp > pcisd->data[CISD_DATA_CHG_USB_TEMP_MAX_PER_DAY]) + pcisd->data[CISD_DATA_CHG_USB_TEMP_MAX_PER_DAY] = battery->usb_temp; + if (battery->usb_temp < pcisd->data[CISD_DATA_CHG_USB_TEMP_MIN_PER_DAY]) + pcisd->data[CISD_DATA_CHG_USB_TEMP_MIN_PER_DAY] = battery->usb_temp; + + if (battery->usb_temp > 800 && !battery->usb_overheat_check) { + battery->cisd.data[CISD_DATA_USB_OVERHEAT_CHARGING]++; + battery->cisd.data[CISD_DATA_USB_OVERHEAT_CHARGING_PER_DAY]++; + battery->usb_overheat_check = true; + } + } else { + /* discharging */ + if (battery->status == POWER_SUPPLY_STATUS_NOT_CHARGING) { + /* check abnormal vbat */ + pcisd->ab_vbat_check_count = voltage > pcisd->max_voltage_thr ? + pcisd->ab_vbat_check_count + 1 : 0; + + if ((pcisd->ab_vbat_check_count >= pcisd->ab_vbat_max_count) && + !(pcisd->state & CISD_STATE_OVER_VOLTAGE)) { + pcisd->data[CISD_DATA_VBAT_OVP]++; + pcisd->data[CISD_DATA_VBAT_OVP_PER_DAY]++; + pcisd->state |= CISD_STATE_OVER_VOLTAGE; +#if IS_ENABLED(CONFIG_SEC_ABC) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=battery@INFO=over_voltage"); +#else + sec_abc_send_event("MODULE=battery@WARN=over_voltage"); +#endif +#endif + } + } + + val.intval = SEC_BATTERY_CAPACITY_FULL; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_ENERGY_NOW, val); + if (val.intval == -1) { + dev_info(battery->dev, "%s: [CISD] FG I2C fail. skip cisd check\n", __func__); + return ret; + } + + if (val.intval > pcisd->data[CISD_DATA_CAP_MAX]) + pcisd->data[CISD_DATA_CAP_MAX] = val.intval; + if (val.intval < pcisd->data[CISD_DATA_CAP_MIN]) + pcisd->data[CISD_DATA_CAP_MIN] = val.intval; + + if (val.intval > pcisd->data[CISD_DATA_CAP_MAX_PER_DAY]) + pcisd->data[CISD_DATA_CAP_MAX_PER_DAY] = val.intval; + if (val.intval < pcisd->data[CISD_DATA_CAP_MIN_PER_DAY]) + pcisd->data[CISD_DATA_CAP_MIN_PER_DAY] = val.intval; + + val.intval = SEC_BATTERY_CAPACITY_AGEDCELL; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_ENERGY_NOW, val); + if (val.intval == -1) { + dev_info(battery->dev, "%s: [CISD] FG I2C fail. skip cisd check\n", __func__); + return ret; + } + pcisd->data[CISD_DATA_CAP_NOM] = val.intval; + dev_info(battery->dev, "%s: [CISD] CAP_NOM %dmAh\n", __func__, pcisd->data[CISD_DATA_CAP_NOM]); + + val.intval = SEC_BATTERY_CAPACITY_RC0; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_ENERGY_NOW, val); + if (val.intval == -1) { + dev_info(battery->dev, "%s: [CISD] FG I2C fail. skip cisd check\n", __func__); + return ret; + } + pcisd->data[CISD_DATA_RC0] = val.intval; + dev_info(battery->dev, "%s: [CISD] RC0 0x%x\n", __func__, pcisd->data[CISD_DATA_RC0]); + } + + if (battery->temperature > pcisd->data[CISD_DATA_BATT_TEMP_MAX]) + pcisd->data[CISD_DATA_BATT_TEMP_MAX] = battery->temperature; + if (battery->temperature < battery->cisd.data[CISD_DATA_BATT_TEMP_MIN]) + pcisd->data[CISD_DATA_BATT_TEMP_MIN] = battery->temperature; + + if (battery->chg_temp > pcisd->data[CISD_DATA_CHG_TEMP_MAX]) + pcisd->data[CISD_DATA_CHG_TEMP_MAX] = battery->chg_temp; + if (battery->chg_temp < pcisd->data[CISD_DATA_CHG_TEMP_MIN]) + pcisd->data[CISD_DATA_CHG_TEMP_MIN] = battery->chg_temp; + + if (battery->wpc_temp > pcisd->data[CISD_DATA_WPC_TEMP_MAX]) + pcisd->data[CISD_DATA_WPC_TEMP_MAX] = battery->wpc_temp; + if (battery->wpc_temp < battery->cisd.data[CISD_DATA_WPC_TEMP_MIN]) + pcisd->data[CISD_DATA_WPC_TEMP_MIN] = battery->wpc_temp; + + if (battery->usb_temp > pcisd->data[CISD_DATA_USB_TEMP_MAX]) + pcisd->data[CISD_DATA_USB_TEMP_MAX] = battery->usb_temp; + if (battery->usb_temp < pcisd->data[CISD_DATA_USB_TEMP_MIN]) + pcisd->data[CISD_DATA_USB_TEMP_MIN] = battery->usb_temp; + + if (battery->temperature > pcisd->data[CISD_DATA_BATT_TEMP_MAX_PER_DAY]) + pcisd->data[CISD_DATA_BATT_TEMP_MAX_PER_DAY] = battery->temperature; + if (battery->temperature < pcisd->data[CISD_DATA_BATT_TEMP_MIN_PER_DAY]) + pcisd->data[CISD_DATA_BATT_TEMP_MIN_PER_DAY] = battery->temperature; + + if (battery->sub_bat_temp > pcisd->data[CISD_DATA_SUB_BATT_TEMP_MAX_PER_DAY]) + pcisd->data[CISD_DATA_SUB_BATT_TEMP_MAX_PER_DAY] = battery->sub_bat_temp; + if (battery->sub_bat_temp < pcisd->data[CISD_DATA_SUB_BATT_TEMP_MIN_PER_DAY]) + pcisd->data[CISD_DATA_SUB_BATT_TEMP_MIN_PER_DAY] = battery->sub_bat_temp; + + if (battery->chg_temp > pcisd->data[CISD_DATA_CHG_TEMP_MAX_PER_DAY]) + pcisd->data[CISD_DATA_CHG_TEMP_MAX_PER_DAY] = battery->chg_temp; + if (battery->chg_temp < pcisd->data[CISD_DATA_CHG_TEMP_MIN_PER_DAY]) + pcisd->data[CISD_DATA_CHG_TEMP_MIN_PER_DAY] = battery->chg_temp; + + if (battery->usb_temp > pcisd->data[CISD_DATA_USB_TEMP_MAX_PER_DAY]) + pcisd->data[CISD_DATA_USB_TEMP_MAX_PER_DAY] = battery->usb_temp; + if (battery->usb_temp < pcisd->data[CISD_DATA_USB_TEMP_MIN_PER_DAY]) + pcisd->data[CISD_DATA_USB_TEMP_MIN_PER_DAY] = battery->usb_temp; + + return ret; +} +EXPORT_SYMBOL(sec_bat_cisd_check); + +static irqreturn_t cisd_irq_thread(int irq, void *data) +{ + struct cisd *pcisd = data; + + pr_info("%s: irq(%d)\n", __func__, irq); + if (irq == pcisd->irq_ovp_power && + !gpio_get_value(pcisd->gpio_ovp_power)) + pcisd->event_data[EVENT_OVP_POWER]++; + + if (irq == pcisd->irq_ovp_signal && + !gpio_get_value(pcisd->gpio_ovp_signal)) + pcisd->event_data[EVENT_OVP_SIGNAL]++; + + return IRQ_HANDLED; +} + +#ifdef CONFIG_OF +static void sec_cisd_parse_dt(struct cisd *pcisd) +{ + struct device_node *np; + int ret = 0; + + np = of_find_node_by_name(NULL, "sec-cisd"); + if (!np) { + pr_err("%s: np NULL\n", __func__); + return; + } + + ret = of_get_named_gpio(np, "ovp_power", 0); + if (ret >= 0) { + pcisd->gpio_ovp_power = ret; + pr_info("%s: set ovp_power gpio(%d)\n", __func__, pcisd->gpio_ovp_power); + pcisd->irq_ovp_power = gpio_to_irq(pcisd->gpio_ovp_power); + ret = request_threaded_irq(pcisd->irq_ovp_power, NULL, + cisd_irq_thread, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "cisd-ovp-power", pcisd); + if (ret < 0) { + pr_err("%s: failed to request ovp_power irq(ret = %d)\n", + __func__, ret); + pcisd->irq_ovp_power = 0; + } else + pr_info("%s: set irq_ovp_power(%d)\n", __func__, pcisd->irq_ovp_power); + } else + pr_err("%s: failed to get ovp_power\n", __func__); + + ret = of_get_named_gpio(np, "ovp_signal", 0); + if (ret >= 0) { + pcisd->gpio_ovp_signal = ret; + pr_info("%s: set ovp_signal gpio(%d)\n", __func__, pcisd->gpio_ovp_signal); + pcisd->irq_ovp_signal = gpio_to_irq(pcisd->gpio_ovp_signal); + ret = request_threaded_irq(pcisd->irq_ovp_signal, NULL, + cisd_irq_thread, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "cisd-ovp-signal", pcisd); + if (ret < 0) { + pr_err("%s: failed to request ovp_signal irq(ret = %d)\n", + __func__, ret); + pcisd->irq_ovp_signal = 0; + } else + pr_info("%s: set irq_ovp_signal(%d)\n", __func__, pcisd->irq_ovp_signal); + } else + pr_err("%s: failed to get ovp_signal\n", __func__); +} +#else +static void sec_cisd_parse_dt(struct cisd *pcisd) +{ +} +#endif + +struct cisd *gcisd; +EXPORT_SYMBOL(gcisd); +void sec_battery_cisd_init(struct sec_battery_info *battery) +{ + /* parse dt */ + sec_cisd_parse_dt(&battery->cisd); + + /* init cisd data */ + battery->cisd.state = CISD_STATE_NONE; + + battery->cisd.data[CISD_DATA_ALG_INDEX] = battery->pdata->cisd_alg_index; + battery->cisd.data[CISD_DATA_FULL_COUNT] = 1; + battery->cisd.data[CISD_DATA_BATT_TEMP_MAX] = -300; + battery->cisd.data[CISD_DATA_CHG_TEMP_MAX] = -300; + battery->cisd.data[CISD_DATA_WPC_TEMP_MAX] = -300; + battery->cisd.data[CISD_DATA_USB_TEMP_MAX] = -300; + battery->cisd.data[CISD_DATA_BATT_TEMP_MIN] = 1000; + battery->cisd.data[CISD_DATA_CHG_TEMP_MIN] = 1000; + battery->cisd.data[CISD_DATA_WPC_TEMP_MIN] = 1000; + battery->cisd.data[CISD_DATA_USB_TEMP_MIN] = 1000; + battery->cisd.data[CISD_DATA_CHG_BATT_TEMP_MAX] = -300; + battery->cisd.data[CISD_DATA_CHG_CHG_TEMP_MAX] = -300; + battery->cisd.data[CISD_DATA_CHG_WPC_TEMP_MAX] = -300; + battery->cisd.data[CISD_DATA_CHG_USB_TEMP_MAX] = -300; + battery->cisd.data[CISD_DATA_CHG_BATT_TEMP_MIN] = 1000; + battery->cisd.data[CISD_DATA_CHG_CHG_TEMP_MIN] = 1000; + battery->cisd.data[CISD_DATA_CHG_WPC_TEMP_MIN] = 1000; + battery->cisd.data[CISD_DATA_CHG_USB_TEMP_MIN] = 1000; + battery->cisd.data[CISD_DATA_CAP_MIN] = 0xFFFF; + battery->cisd.data[CISD_DATA_ASOC] = 100; + + battery->cisd.data[CISD_DATA_FULL_COUNT_PER_DAY] = 1; + battery->cisd.data[CISD_DATA_BATT_TEMP_MAX_PER_DAY] = -300; + battery->cisd.data[CISD_DATA_SUB_BATT_TEMP_MAX_PER_DAY] = -300; + battery->cisd.data[CISD_DATA_CHG_TEMP_MAX_PER_DAY] = -300; + battery->cisd.data[CISD_DATA_USB_TEMP_MAX_PER_DAY] = -300; + battery->cisd.data[CISD_DATA_BATT_TEMP_MIN_PER_DAY] = 1000; + battery->cisd.data[CISD_DATA_SUB_BATT_TEMP_MIN_PER_DAY] = 1000; + battery->cisd.data[CISD_DATA_CHG_TEMP_MIN_PER_DAY] = 1000; + battery->cisd.data[CISD_DATA_USB_TEMP_MIN_PER_DAY] = 1000; + + battery->cisd.data[CISD_DATA_CHG_BATT_TEMP_MAX_PER_DAY] = -300; + battery->cisd.data[CISD_DATA_CHG_SUB_BATT_TEMP_MAX_PER_DAY] = -300; + battery->cisd.data[CISD_DATA_CHG_CHG_TEMP_MAX_PER_DAY] = -300; + battery->cisd.data[CISD_DATA_CHG_USB_TEMP_MAX_PER_DAY] = -300; + battery->cisd.data[CISD_DATA_CHG_BATT_TEMP_MIN_PER_DAY] = 1000; + battery->cisd.data[CISD_DATA_CHG_SUB_BATT_TEMP_MIN_PER_DAY] = 1000; + battery->cisd.data[CISD_DATA_CHG_CHG_TEMP_MIN_PER_DAY] = 1000; + battery->cisd.data[CISD_DATA_CHG_USB_TEMP_MIN_PER_DAY] = 1000; + battery->cisd.data[CISD_DATA_CHG_RETENTION_TIME_PER_DAY] = 0; + battery->cisd.data[CISD_DATA_TOTAL_CHG_RETENTION_TIME_PER_DAY] = 0; + + battery->cisd.ab_vbat_max_count = 2; /* should be 2 */ + battery->cisd.ab_vbat_check_count = 0; + battery->cisd.max_voltage_thr = battery->pdata->max_voltage_thr; + + /* set cisd pointer */ + gcisd = &battery->cisd; + + /* initialize pad data */ + mutex_init(&battery->cisd.padlock); + mutex_init(&battery->cisd.powerlock); + mutex_init(&battery->cisd.pdlock); + init_cisd_pad_data(&battery->cisd); + init_cisd_power_data(&battery->cisd); + init_cisd_pd_data(&battery->cisd); +} +EXPORT_SYMBOL(sec_battery_cisd_init); + +static struct pad_data *create_pad_data(unsigned int pad_id, unsigned int pad_count) +{ + struct pad_data *temp_data; + + temp_data = kzalloc(sizeof(struct pad_data), GFP_KERNEL); + if (temp_data == NULL) + return NULL; + + temp_data->id = pad_id; + temp_data->count = pad_count; + temp_data->prev = temp_data->next = NULL; + + return temp_data; +} + +static struct pad_data *find_pad_data_by_id(struct cisd *cisd, unsigned int pad_id) +{ + struct pad_data *temp_data = cisd->pad_array->next; + + if (cisd->pad_count <= 0 || temp_data == NULL) + return NULL; + + while ((temp_data->id != pad_id) && + ((temp_data = temp_data->next) != NULL)); + + return temp_data; +} + +static void add_pad_data(struct cisd *cisd, unsigned int pad_id, unsigned int pad_count) +{ + struct pad_data *temp_data = cisd->pad_array->next; + struct pad_data *pad_data; + + if (pad_id >= MAX_PAD_ID) + return; + + pad_data = create_pad_data(pad_id, pad_count); + if (pad_data == NULL) + return; + + pr_info("%s: id(0x%x), count(%d)\n", __func__, pad_id, pad_count); + while (temp_data) { + if (temp_data->id > pad_id) { + temp_data->prev->next = pad_data; + pad_data->prev = temp_data->prev; + pad_data->next = temp_data; + temp_data->prev = pad_data; + cisd->pad_count++; + return; + } + temp_data = temp_data->next; + } + + pr_info("%s: failed to add pad_data(%d, %d)\n", + __func__, pad_id, pad_count); + kfree(pad_data); +} + +void init_cisd_pad_data(struct cisd *cisd) +{ + struct pad_data *temp_data = NULL; + + mutex_lock(&cisd->padlock); + temp_data = cisd->pad_array; + while (temp_data) { + struct pad_data *next_data = temp_data->next; + + kfree(temp_data); + temp_data = next_data; + } + + /* create dummy data */ + cisd->pad_array = create_pad_data(0, 0); + if (cisd->pad_array == NULL) + goto err_create_dummy_data; + temp_data = create_pad_data(MAX_PAD_ID, 0); + if (temp_data == NULL) { + kfree(cisd->pad_array); + cisd->pad_array = NULL; + goto err_create_dummy_data; + } + cisd->pad_count = 0; + cisd->pad_array->next = temp_data; + temp_data->prev = cisd->pad_array; + +err_create_dummy_data: + mutex_unlock(&cisd->padlock); +} +EXPORT_SYMBOL(init_cisd_pad_data); + +void count_cisd_pad_data(struct cisd *cisd, unsigned int pad_id) +{ + struct pad_data *pad_data; + + if (cisd->pad_array == NULL) { + pr_info("%s: can't update the connected count of pad_id(0x%x) because of null\n", + __func__, pad_id); + return; + } + + mutex_lock(&cisd->padlock); + pad_data = find_pad_data_by_id(cisd, pad_id); + if (pad_data != NULL) + pad_data->count++; + else + add_pad_data(cisd, pad_id, 1); + mutex_unlock(&cisd->padlock); +} +EXPORT_SYMBOL(count_cisd_pad_data); + +void set_cisd_pad_data(struct sec_battery_info *battery, const char *buf) +{ + struct cisd *pcisd = &battery->cisd; + unsigned int pad_total_count, pad_id, pad_count; + struct pad_data *pad_data; + int i, x; + + pr_info("%s: %s\n", __func__, buf); + if (pcisd->pad_count > 0) + init_cisd_pad_data(pcisd); + + if (pcisd->pad_array == NULL) { + pr_info("%s: can't set the pad data because of null\n", __func__); + return; + } + + if (sscanf(buf, "%10u %n", &pad_total_count, &x) <= 0) { + pr_info("%s: failed to read pad index\n", __func__); + return; + } + buf += (size_t)x; + pr_info("%s: add pad data(count: %d)\n", __func__, pad_total_count); + for (i = 0; i < pad_total_count; i++) { + if (sscanf(buf, "0x%02x:%10u %n", &pad_id, &pad_count, &x) != 2) { + pr_info("%s: failed to read pad data(0x%x, %d, %d)!!!re-init pad data\n", + __func__, pad_id, pad_count, x); + init_cisd_pad_data(pcisd); + break; + } + buf += (size_t)x; + mutex_lock(&pcisd->padlock); + pad_data = find_pad_data_by_id(pcisd, pad_id); + if (pad_data != NULL) + pad_data->count = pad_count; + else + add_pad_data(pcisd, pad_id, pad_count); + mutex_unlock(&pcisd->padlock); + } +} +EXPORT_SYMBOL(set_cisd_pad_data); + +static struct power_data *create_power_data(unsigned int power, unsigned int power_count) +{ + struct power_data *temp_data; + + temp_data = kzalloc(sizeof(struct power_data), GFP_KERNEL); + if (temp_data == NULL) + return NULL; + + temp_data->power = power; + temp_data->count = power_count; + temp_data->prev = temp_data->next = NULL; + + return temp_data; +} + +static struct power_data *find_data_by_power(struct cisd *cisd, unsigned int power) +{ + struct power_data *temp_data = cisd->power_array->next; + + if (cisd->power_count <= 0 || temp_data == NULL) + return NULL; + + while ((temp_data->power != power) && + ((temp_data = temp_data->next) != NULL)); + + return temp_data; +} + +static void add_power_data(struct cisd *cisd, unsigned int power, unsigned int power_count) +{ + struct power_data *temp_data = cisd->power_array->next; + struct power_data *power_data; + + power_data = create_power_data(power, power_count); + if (power_data == NULL) + return; + + pr_info("%s: power(%d), count(%d)\n", __func__, power, power_count); + while (temp_data) { + if (temp_data->power > power) { + temp_data->prev->next = power_data; + power_data->prev = temp_data->prev; + power_data->next = temp_data; + temp_data->prev = power_data; + cisd->power_count++; + return; + } + temp_data = temp_data->next; + } + + pr_info("%s: failed to add pad_data(%d, %d)\n", + __func__, power, power_count); + kfree(power_data); +} + +void init_cisd_power_data(struct cisd *cisd) +{ + struct power_data *temp_data = NULL; + + mutex_lock(&cisd->powerlock); + temp_data = cisd->power_array; + while (temp_data) { + struct power_data *next_data = temp_data->next; + + kfree(temp_data); + temp_data = next_data; + } + + /* create dummy data */ + cisd->power_array = create_power_data(0, 0); + if (cisd->power_array == NULL) + goto err_create_dummy_data; + temp_data = create_power_data(MAX_CHARGER_POWER, 0); + if (temp_data == NULL) { + kfree(cisd->power_array); + cisd->power_array = NULL; + goto err_create_dummy_data; + } + cisd->power_count = 0; + cisd->power_array->next = temp_data; + temp_data->prev = cisd->power_array; + +err_create_dummy_data: + mutex_unlock(&cisd->powerlock); +} +EXPORT_SYMBOL(init_cisd_power_data); + +#define FIND_MAX_POWER 45000 +#define FIND_POWER_STEP 10000 +#define POWER_MARGIN 1000 +void count_cisd_power_data(struct cisd *cisd, int power) +{ + struct power_data *power_data; + int power_index = 0; + + pr_info("%s: power value : %d\n", __func__, power); + if (cisd->power_array == NULL || power < 15000) { + pr_info("%s: can't update the connected count of power(%d) because of null\n", + __func__, power); + return; + } + + power_index = FIND_MAX_POWER; + while (power_index >= 14000) { + if (power + POWER_MARGIN - power_index >= 0) { + power_index /= 1000; + break; + } + + power_index -= FIND_POWER_STEP; + } + + mutex_lock(&cisd->powerlock); + power_data = find_data_by_power(cisd, power_index); + if (power_data != NULL) + power_data->count++; + else + add_power_data(cisd, power_index, 1); + mutex_unlock(&cisd->powerlock); +} +EXPORT_SYMBOL(count_cisd_power_data); + +void set_cisd_power_data(struct sec_battery_info *battery, const char *buf) +{ + struct cisd *pcisd = &battery->cisd; + unsigned int power_total_count, power_id, power_count; + struct power_data *power_data; + int i, x; + + pr_info("%s: %s\n", __func__, buf); + if (pcisd->power_count > 0) + init_cisd_power_data(pcisd); + + if (pcisd->power_array == NULL) { + pr_info("%s: can't set the power data because of null\n", __func__); + return; + } + + if (sscanf(buf, "%10u %n", &power_total_count, &x) <= 0) + return; + + buf += (size_t)x; + pr_info("%s: add power data(count: %d)\n", __func__, power_total_count); + for (i = 0; i < power_total_count; i++) { + if (sscanf(buf, "%10u:%10u %n", &power_id, &power_count, &x) != 2) { + pr_info("%s: failed to read power data(%d, %d, %d)!!!re-init power data\n", + __func__, power_id, power_count, x); + init_cisd_power_data(pcisd); + break; + } + buf += (size_t)x; + mutex_lock(&pcisd->powerlock); + power_data = find_data_by_power(pcisd, power_id); + if (power_data != NULL) + power_data->count = power_count; + else + add_power_data(pcisd, power_id, power_count); + mutex_unlock(&pcisd->powerlock); + } +} +EXPORT_SYMBOL(set_cisd_power_data); + +static struct pd_data *create_pd_data(unsigned short pid, unsigned int pd_count) +{ + struct pd_data *temp_data; + + temp_data = kzalloc(sizeof(struct pd_data), GFP_KERNEL); + if (temp_data == NULL) + return NULL; + + temp_data->pid = pid; + temp_data->count = pd_count; + temp_data->prev = temp_data->next = NULL; + + return temp_data; +} + +static struct pd_data *find_data_by_pd(struct cisd *cisd, unsigned short pid) +{ + struct pd_data *temp_data = cisd->pd_array->next; + + if (cisd->pd_count <= 0 || temp_data == NULL) + return NULL; + + while ((temp_data->pid != pid) && + ((temp_data = temp_data->next) != NULL)) + ; + + return temp_data; +} + +static void add_pd_data(struct cisd *cisd, unsigned short pid, unsigned int pd_count) +{ + struct pd_data *temp_data = cisd->pd_array->next; + struct pd_data *pd_data; + + pd_data = create_pd_data(pid, pd_count); + if (pd_data == NULL) + return; + + pr_info("%s: pid(0x%04x), count(%d)\n", __func__, pid, pd_count); + while (temp_data) { + if (temp_data->pid > pid) { + temp_data->prev->next = pd_data; + pd_data->prev = temp_data->prev; + pd_data->next = temp_data; + temp_data->prev = pd_data; + cisd->pd_count++; + return; + } + temp_data = temp_data->next; + } + + pr_info("%s: failed to add pd_data(0x%04x, %d)\n", + __func__, pid, pd_count); + kfree(pd_data); +} + +void init_cisd_pd_data(struct cisd *cisd) +{ + struct pd_data *temp_data = NULL; + + mutex_lock(&cisd->pdlock); + temp_data = cisd->pd_array; + while (temp_data) { + struct pd_data *next_data = temp_data->next; + + kfree(temp_data); + temp_data = next_data; + } + + /* create dummy data */ + cisd->pd_array = create_pd_data(0, 0); + if (cisd->pd_array == NULL) + goto err_create_dummy_data; + temp_data = create_pd_data(MAX_SS_PD_PID, 0); + if (temp_data == NULL) { + kfree(cisd->pd_array); + cisd->pd_array = NULL; + goto err_create_dummy_data; + } + cisd->pd_count = 0; + cisd->pd_array->next = temp_data; + temp_data->prev = cisd->pd_array; + +err_create_dummy_data: + mutex_unlock(&cisd->pdlock); +} +EXPORT_SYMBOL(init_cisd_pd_data); + +void count_cisd_pd_data(unsigned short vid, unsigned short pid) +{ + struct power_supply *psy = power_supply_get_by_name("battery"); + struct sec_battery_info *battery = power_supply_get_drvdata(psy); + struct cisd *cisd = &battery->cisd; + struct pd_data *pd_data; + + pr_info("%s: vid : 0x%04x, pid : 0x%04x\n", __func__, vid, pid); + if (cisd->pd_array == NULL) { + pr_info("%s: can't update count of pid(0x%04x) because of null\n", + __func__, pid); + return; + } + + if ((vid != SS_PD_VID) || ((vid == SS_PD_VID) + && (pid < MIN_SS_PD_PID || pid > MAX_SS_PD_PID))) { + pr_info("%s: other pd ta(vid_0x%04x, pid_0x%04x). change pid to 0x0000\n", + __func__, vid, pid); + pid = 0x0; + } + + mutex_lock(&cisd->pdlock); + pd_data = find_data_by_pd(cisd, pid); + if (pd_data != NULL) + pd_data->count++; + else + add_pd_data(cisd, pid, 1); + mutex_unlock(&cisd->pdlock); +} +EXPORT_SYMBOL(count_cisd_pd_data); + +void set_cisd_pd_data(struct sec_battery_info *battery, const char *buf) +{ + struct cisd *pcisd = &battery->cisd; + unsigned int pd_total_count, pid, pd_count; + struct pd_data *pd_data; + int i, x; + + pr_info("%s: %s\n", __func__, buf); + if (pcisd->pd_count > 0) + init_cisd_pd_data(pcisd); + + if (pcisd->pd_array == NULL) { + pr_info("%s: can't set the pd data because of null\n", __func__); + return; + } + + if (sscanf(buf, "%10u %n", &pd_total_count, &x) <= 0) + return; + + buf += (size_t)x; + pr_info("%s: add pd data(count: %d)\n", __func__, pd_total_count); + for (i = 0; i < pd_total_count; i++) { + if (sscanf(buf, "0x%04x:%10u %n", &pid, &pd_count, &x) != 2) { + pr_info("%s: failed to read pd data(0x%04x, %d, %d)!!!re-init pd data\n", + __func__, pid, pd_count, x); + init_cisd_pd_data(pcisd); + break; + } + buf += (size_t)x; + mutex_lock(&pcisd->pdlock); + pd_data = find_data_by_pd(pcisd, pid); + if (pd_data != NULL) + pd_data->count = pd_count; + else + add_pd_data(pcisd, pid, pd_count); + mutex_unlock(&pcisd->pdlock); + } +} +EXPORT_SYMBOL(set_cisd_pd_data); + diff --git a/drivers/battery/common/sec_cisd.h b/drivers/battery/common/sec_cisd.h new file mode 100644 index 000000000000..11e97c02f949 --- /dev/null +++ b/drivers/battery/common/sec_cisd.h @@ -0,0 +1,272 @@ +/* + * sec_cisd.h + * Samsung Mobile Charger Header + * + * Copyright (C) 2015 Samsung Electronics, 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 __SEC_CISD_H +#define __SEC_CISD_H __FILE__ + +#define CISD_STATE_NONE 0x00 +#define CISD_STATE_OVER_VOLTAGE 0x01 + +#define is_cisd_check_type(cable_type) ( \ + cable_type == SEC_BATTERY_CABLE_TA || \ + cable_type == SEC_BATTERY_CABLE_9V_TA || \ + cable_type == SEC_BATTERY_CABLE_9V_UNKNOWN || \ + cable_type == SEC_BATTERY_CABLE_9V_ERR || \ + cable_type == SEC_BATTERY_CABLE_PDIC) + +enum cisd_data { + CISD_DATA_RESET_ALG = 0, + + CISD_DATA_ALG_INDEX, + CISD_DATA_FULL_COUNT, + CISD_DATA_CAP_MAX, + CISD_DATA_CAP_MIN, + CISD_DATA_RECHARGING_COUNT, + CISD_DATA_VALERT_COUNT, + CISD_DATA_CYCLE, + CISD_DATA_WIRE_COUNT, + CISD_DATA_WIRELESS_COUNT, + CISD_DATA_HIGH_TEMP_SWELLING, + + CISD_DATA_LOW_TEMP_SWELLING, + CISD_DATA_WC_HIGH_TEMP_SWELLING, + CISD_DATA_SWELLING_FULL_CNT, + CISD_DATA_SWELLING_RECOVERY_CNT, + CISD_DATA_AICL_COUNT, + CISD_DATA_BATT_TEMP_MAX, + CISD_DATA_BATT_TEMP_MIN, + CISD_DATA_CHG_TEMP_MAX, + CISD_DATA_CHG_TEMP_MIN, + CISD_DATA_WPC_TEMP_MAX, + + CISD_DATA_WPC_TEMP_MIN, + CISD_DATA_USB_TEMP_MAX, + CISD_DATA_USB_TEMP_MIN, + CISD_DATA_CHG_BATT_TEMP_MAX, + CISD_DATA_CHG_BATT_TEMP_MIN, + CISD_DATA_CHG_CHG_TEMP_MAX, + CISD_DATA_CHG_CHG_TEMP_MIN, + CISD_DATA_CHG_WPC_TEMP_MAX, + CISD_DATA_CHG_WPC_TEMP_MIN, + CISD_DATA_CHG_USB_TEMP_MAX, + + CISD_DATA_CHG_USB_TEMP_MIN, + CISD_DATA_USB_OVERHEAT_CHARGING, /* 32 */ + CISD_DATA_UNSAFETY_VOLTAGE, + CISD_DATA_UNSAFETY_TEMPERATURE, + CISD_DATA_SAFETY_TIMER, + CISD_DATA_VSYS_OVP, + CISD_DATA_VBAT_OVP, + CISD_DATA_USB_OVERHEAT_RAPID_CHANGE, + CISD_DATA_ASOC, + CISD_DATA_USB_OVERHEAT_ALONE, + + CISD_DATA_CAP_NOM, + CISD_DATA_RC0, + + CISD_DATA_MAX, +}; + +enum cisd_data_per_day { + CISD_DATA_FULL_COUNT_PER_DAY = CISD_DATA_MAX, + + CISD_DATA_CAP_MAX_PER_DAY, + CISD_DATA_CAP_MIN_PER_DAY, + CISD_DATA_RECHARGING_COUNT_PER_DAY, + CISD_DATA_VALERT_COUNT_PER_DAY, + CISD_DATA_WIRE_COUNT_PER_DAY, + CISD_DATA_WIRELESS_COUNT_PER_DAY, + CISD_DATA_HIGH_TEMP_SWELLING_PER_DAY, + CISD_DATA_LOW_TEMP_SWELLING_PER_DAY, + CISD_DATA_WC_HIGH_TEMP_SWELLING_PER_DAY, + CISD_DATA_SWELLING_FULL_CNT_PER_DAY, + + CISD_DATA_SWELLING_RECOVERY_CNT_PER_DAY, + CISD_DATA_AICL_COUNT_PER_DAY, + CISD_DATA_BATT_TEMP_MAX_PER_DAY, + CISD_DATA_BATT_TEMP_MIN_PER_DAY, + CISD_DATA_SUB_BATT_TEMP_MAX_PER_DAY, + CISD_DATA_SUB_BATT_TEMP_MIN_PER_DAY, + CISD_DATA_CHG_TEMP_MAX_PER_DAY, + CISD_DATA_CHG_TEMP_MIN_PER_DAY, + CISD_DATA_USB_TEMP_MAX_PER_DAY, + CISD_DATA_USB_TEMP_MIN_PER_DAY, + + CISD_DATA_CHG_BATT_TEMP_MAX_PER_DAY, + CISD_DATA_CHG_BATT_TEMP_MIN_PER_DAY, + CISD_DATA_CHG_SUB_BATT_TEMP_MAX_PER_DAY, + CISD_DATA_CHG_SUB_BATT_TEMP_MIN_PER_DAY, + CISD_DATA_CHG_CHG_TEMP_MAX_PER_DAY, + CISD_DATA_CHG_CHG_TEMP_MIN_PER_DAY, + CISD_DATA_CHG_USB_TEMP_MAX_PER_DAY, + CISD_DATA_CHG_USB_TEMP_MIN_PER_DAY, + CISD_DATA_USB_OVERHEAT_CHARGING_PER_DAY, + CISD_DATA_UNSAFE_VOLTAGE_PER_DAY, + + CISD_DATA_UNSAFE_TEMPERATURE_PER_DAY, + CISD_DATA_SAFETY_TIMER_PER_DAY, /* 32 */ + CISD_DATA_VSYS_OVP_PER_DAY, + CISD_DATA_VBAT_OVP_PER_DAY, + CISD_DATA_USB_OVERHEAT_RAPID_CHANGE_PER_DAY, + CISD_DATA_BUCK_OFF_PER_DAY, + CISD_DATA_USB_OVERHEAT_ALONE_PER_DAY, + CISD_DATA_DROP_VALUE_PER_DAY, + CISD_DATA_CHG_RETENTION_TIME_PER_DAY, + CISD_DATA_TOTAL_CHG_RETENTION_TIME_PER_DAY, + + CISD_DATA_MAX_PER_DAY, +}; + +enum { + CISD_CABLE_TA = 0, + CISD_CABLE_AFC, + CISD_CABLE_AFC_FAIL, + CISD_CABLE_QC, + CISD_CABLE_QC_FAIL, + CISD_CABLE_PD, + CISD_CABLE_PD_HIGH, + CISD_CABLE_HV_WC_20, + CISD_CABLE_FPDO_DC, + + CISD_CABLE_TYPE_MAX, +}; + +enum { + TX_ON = 0, + TX_OTHER, + TX_GEAR, + TX_PHONE, + TX_BUDS, + TX_DATA_MAX, +}; + +enum { + EVENT_DC_ERR = 0, + EVENT_TA_OCP_DET, + EVENT_TA_OCP_ON, + EVENT_OVP_POWER, + EVENT_OVP_SIGNAL, + EVENT_OTG, + EVENT_D2D, +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + EVENT_MAIN_BAT_ERR, + EVENT_SUB_BAT_ERR, + EVENT_BAT_WA_ERR, +#endif + EVENT_DATA_MAX, +}; + +extern const char *cisd_data_str[]; +extern const char *cisd_data_str_d[]; +extern const char *cisd_cable_data_str[]; +extern const char *cisd_tx_data_str[]; +extern const char *cisd_event_data_str[]; + +#define PAD_INDEX_STRING "INDEX" +#define PAD_JSON_STRING "PAD_0x" +#define MAX_PAD_ID 0xFF +#define MAX_CHARGER_POWER 100 +#define POWER_JSON_STRING "POWER_" +#define POWER_COUNT_JSON_STRING "COUNT" +#define SS_PD_VID 0x04E8 +#define MIN_SS_PD_PID 0x3000 +#define MAX_SS_PD_PID 0x30FF +#define PD_JSON_STRING "PID_0x" +#define PD_COUNT_JSON_STRING "PID" + +struct pad_data { + unsigned int id; + unsigned int count; + + struct pad_data* prev; + struct pad_data* next; +}; + +struct power_data { + unsigned int power; + unsigned int count; + + struct power_data* prev; + struct power_data* next; +}; + +struct pd_data { + unsigned short pid; + unsigned int count; + + struct pd_data *prev; + struct pd_data *next; +}; + +struct cisd { + unsigned int cisd_alg_index; + unsigned int state; + + unsigned int ab_vbat_max_count; + unsigned int ab_vbat_check_count; + int max_voltage_thr; + + unsigned int gpio_ovp_power; + unsigned int irq_ovp_power; + unsigned int gpio_ovp_signal; + unsigned int irq_ovp_signal; + + /* Big Data Field */ + int data[CISD_DATA_MAX_PER_DAY]; + int cable_data[CISD_CABLE_TYPE_MAX]; + unsigned int tx_data[TX_DATA_MAX]; + unsigned int event_data[EVENT_DATA_MAX]; + + struct mutex padlock; + struct mutex powerlock; + struct mutex pdlock; + struct pad_data* pad_array; + struct power_data* power_array; + struct pd_data *pd_array; + unsigned int pad_count; + unsigned int power_count; + unsigned int pd_count; +}; + +extern struct cisd *gcisd; +static inline void set_cisd_data(int type, int value) +{ + if (gcisd && (type >= CISD_DATA_RESET_ALG && type < CISD_DATA_MAX_PER_DAY)) + gcisd->data[type] = value; +} +static inline int get_cisd_data(int type) +{ + if (!gcisd || (type < CISD_DATA_RESET_ALG || type >= CISD_DATA_MAX_PER_DAY)) + return -1; + + return gcisd->data[type]; +} +static inline void increase_cisd_count(int type) +{ + if (gcisd && (type >= CISD_DATA_RESET_ALG && type < CISD_DATA_MAX_PER_DAY)) + gcisd->data[type]++; +} + +void init_cisd_pad_data(struct cisd *cisd); +void count_cisd_pad_data(struct cisd *cisd, unsigned int pad_id); + +void init_cisd_power_data(struct cisd* cisd); +void count_cisd_power_data(struct cisd* cisd, int power); + +void init_cisd_pd_data(struct cisd *cisd); +void count_cisd_pd_data(unsigned short vid, unsigned short pid); +#endif /* __SEC_CISD_H */ diff --git a/drivers/battery/common/sec_direct_charger.c b/drivers/battery/common/sec_direct_charger.c new file mode 100644 index 000000000000..c71ff8aaf510 --- /dev/null +++ b/drivers/battery/common/sec_direct_charger.c @@ -0,0 +1,1203 @@ +/* + * sec_direct_charger.c + * Samsung Mobile Charger Driver + * + * Copyright (C) 2020 Samsung Electronics + * + * + * 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. + */ +#define DEBUG + +#include "sec_direct_charger.h" +#include "battery_logger.h" + +#if IS_ENABLED(CONFIG_SEC_ABC) +#include +#endif + +char *sec_direct_chg_mode_str[] = { + "OFF", //SEC_DIRECT_CHG_MODE_DIRECT_OFF + "CHECK_VBAT", //SEC_DIRECT_CHG_MODE_DIRECT_CHECK_VBAT + "PRESET", //SEC_DIRECT_CHG_MODE_DIRECT_PRESET + "ON_ADJUST", // SEC_DIRECT_CHG_MODE_DIRECT_ON_ADJUST + "ON", //SEC_DIRECT_CHG_MODE_DIRECT_ON + "DONE", //SEC_DIRECT_CHG_MODE_DIRECT_DONE + "BYPASS", //SEC_DIRECT_CHG_MODE_DIRECT_BYPASS +}; + +char *sec_direct_charger_mode_str[] = { + "Buck-Off", + "Charging-Off", + "Pass-Through", + "Charging-On", + "OTG-On", + "OTG-Off", + "UNO-On", + "UNO-Off", + "UNO-Only", + "Not-Set", + "Max", +}; + +#if IS_ENABLED(CONFIG_SEC_ABC) +void sec_direct_abc_check(struct sec_direct_charger_info *charger) +{ + if ((charger->charging_source != SEC_CHARGING_SOURCE_DIRECT) || + !is_pd_apdo_wire_type(charger->cable_type) || !charger->now_isApdo) { + charger->abc_dc_current_cnt = 0; + + return; + } + + if (charger->dc_input_current < 900) { + if (charger->abc_dc_current_cnt <= ABC_DC_CNT) + charger->abc_dc_current_cnt++; + if (charger->abc_dc_current_cnt == ABC_DC_CNT) + sec_abc_send_event("MODULE=battery@WARN=dc_current"); + } else { + charger->abc_dc_current_cnt = 0; + } +} +#else +void sec_direct_abc_check(struct sec_direct_charger_info *charger) {} +#endif + +void sec_direct_chg_monitor(struct sec_direct_charger_info *charger) +{ + int ret = 0; + union power_supply_propval dc_state = {0, }; + + dc_state.strval = "NO_CHARGING"; + ret = psy_do_property(charger->pdata->direct_charger_name, get, + POWER_SUPPLY_EXT_PROP_DIRECT_CHARGER_CHG_STATUS, dc_state); + + if (ret < 0) { + pr_info("%s: Failed to get dc_chg status", __func__); + } else if (charger->charging_source == SEC_CHARGING_SOURCE_DIRECT) { + pr_info("%s: Src(%s), direct(%s), switching(%s), Imax(%dmA), Ichg(%dmA), dc_input(%dmA), dc_state(%s)\n", + __func__, charger->charging_source ? "DIRECT" : "SWITCHING", + sec_direct_charger_mode_str[charger->charger_mode_direct], + sec_direct_charger_mode_str[charger->charger_mode_main], + charger->input_current, charger->charging_current, charger->dc_input_current, dc_state.strval); + } + sec_direct_abc_check(charger); + + sb_pt_monitor(charger->pt, charger->charging_source); +} + +static bool sec_direct_chg_set_direct_charge( + struct sec_direct_charger_info *charger, unsigned int charger_mode) +{ + union power_supply_propval value = {0,}; + + if (charger->ta_alert_wa) { + psy_do_property("battery", get, + POWER_SUPPLY_EXT_PROP_DIRECT_TA_ALERT, value); + charger->ta_alert_mode = value.intval; + } + + if (charger->charger_mode_direct == charger_mode && !(charger->dc_retry_cnt) && + (charger->ta_alert_mode == OCP_NONE)) { + pr_info("%s: charger_mode is same(%s)\n", __func__, + sec_direct_charger_mode_str[charger->charger_mode_direct]); + return false; + } + + pr_info("%s: charger_mode(%s->%s)\n", __func__, + sec_direct_charger_mode_str[charger->charger_mode_direct], + sec_direct_charger_mode_str[charger_mode]); + charger->charger_mode_direct = charger_mode; + + if (charger_mode == SEC_BAT_CHG_MODE_CHARGING || + charger_mode == SEC_BAT_CHG_MODE_PASS_THROUGH) + value.intval = true; + else + value.intval = false; + + psy_do_property(charger->pdata->direct_charger_name, set, + POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED, value); + + return true; +} + +static bool sec_direct_chg_set_switching_charge( + struct sec_direct_charger_info *charger, unsigned int charger_mode) +{ + union power_supply_propval value = {0,}; + + pr_info("%s: charger_mode(%s->%s)\n", __func__, + sec_direct_charger_mode_str[charger->charger_mode_main], + sec_direct_charger_mode_str[charger_mode]); + + if (charger_mode == SEC_BAT_CHG_MODE_PASS_THROUGH) + charger_mode = SEC_BAT_CHG_MODE_CHARGING_OFF; + charger->charger_mode_main = charger_mode; + + value.intval = charger_mode; + psy_do_property(charger->pdata->main_charger_name, set, + POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED, value); + + return true; +} +static bool sec_direct_chg_check_temp(struct sec_direct_charger_info *charger) +{ + union power_supply_propval value = {0,}; + int batt_temp = 0, mix_limit = 0; +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + int sub_batt_temp = 0; +#endif + + /* check mix limit */ + psy_do_property("battery", get, POWER_SUPPLY_EXT_PROP_MIX_LIMIT, value); + mix_limit = value.intval; + if (mix_limit) { + pr_info("%s: S/C was selected! mix_limit(%d)\n", __func__, value.intval); + return true; + } + + if (charger->pdata->dchg_dc_in_swelling) { + /* do not check batt temp for DC */ + return false; + } + + value.intval = THM_INFO_BAT; + psy_do_property("battery", get, POWER_SUPPLY_EXT_PROP_TEMP_CHECK_TYPE, value); + if (value.intval) { + /* check Tbat temperature */ + psy_do_property("battery", get, POWER_SUPPLY_PROP_TEMP, value); + batt_temp = value.intval; + if (batt_temp <= charger->pdata->dchg_temp_low_threshold || + batt_temp >= charger->pdata->dchg_temp_high_threshold) { + pr_info("%s: S/C was selected! Tbat(%d)\n", __func__, batt_temp); + return true; + } + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + /* check Tsub temperature */ + psy_do_property("battery", get, POWER_SUPPLY_EXT_PROP_SUB_TEMP, value); + sub_batt_temp = value.intval; + if (sub_batt_temp <= charger->pdata->dchg_temp_low_threshold || + sub_batt_temp >= charger->pdata->dchg_temp_high_threshold) { + pr_info("%s: S/C was selected! Tsub(%d)\n", __func__, sub_batt_temp); + return true; + } +#endif + } else { + pr_info("%s: Temperature Control Disabled!\n", __func__); + } + return false; +} + +static bool sec_direct_chg_check_event( + struct sec_direct_charger_info *charger, unsigned int current_event, unsigned int tx_retry_case) +{ + union power_supply_propval value = {0,}; + int batt_volt = 0; + int dc_status = POWER_SUPPLY_STATUS_DISCHARGING; + + if (charger->pdata->dchg_dc_in_swelling) { + if (current_event & SEC_BAT_CURRENT_EVENT_HIGH_TEMP_SWELLING) { + /* check Tbat temperature */ + psy_do_property("battery", get, POWER_SUPPLY_PROP_VOLTAGE_NOW, value); + batt_volt = value.intval / 1000; + psy_do_property(charger->pdata->direct_charger_name, get, + POWER_SUPPLY_PROP_STATUS, value); + dc_status = value.intval; + if ((batt_volt >= charger->pdata->swelling_high_rechg_voltage) && + (dc_status != POWER_SUPPLY_STATUS_CHARGING) && + !charger->pdata->chgen_over_swell_rechg_vol) { + pr_info("%s : volt(%d) rechg_voltage(%d) dc_status(%d)\n", __func__, + batt_volt, charger->pdata->swelling_high_rechg_voltage, dc_status); + return true; + } + if (charger->dc_rcp) { + pr_info("%s : swelling and rcp(%d)\n", __func__, + charger->dc_rcp); + return true; + } + } else + charger->dc_rcp = false; + if (current_event & SEC_BAT_CURRENT_EVENT_LOW_TEMP_MODE) + return true; + } else { + if (current_event & SEC_BAT_CURRENT_EVENT_SWELLING_MODE) + return true; + } + if (current_event & SEC_BAT_CURRENT_EVENT_HV_DISABLE || + current_event & SEC_BAT_CURRENT_EVENT_SIOP_LIMIT || + current_event & SEC_BAT_CURRENT_EVENT_SEND_UVDM || + (current_event & SEC_BAT_CURRENT_EVENT_DC_ERR && charger->ta_alert_mode == OCP_NONE)) + return true; + + if (tx_retry_case & SEC_BAT_TX_RETRY_MISALIGN || + tx_retry_case & SEC_BAT_TX_RETRY_OCP) + return true; + + return false; +} + +static bool sec_direct_fpdo_dc_check(struct sec_direct_charger_info *charger) +{ + union power_supply_propval value = {0,}; + int voltage = 0; + + /* Works only in FPDO DC */ + if (charger->cable_type != SEC_BATTERY_CABLE_FPDO_DC) + return false; + + /* check fdpo dc start vbat condition */ + psy_do_property("battery", get, POWER_SUPPLY_PROP_VOLTAGE_AVG, value); + voltage = value.intval / 1000; + if (voltage < charger->pdata->fpdo_dc_min_vbat) { + pr_info("%s: FPDO DC, S/C was selected! low vbat(%dmV)\n", __func__, voltage); + return true; + } + + if (charger->charging_source == SEC_CHARGING_SOURCE_SWITCHING) { + /* check fdpo dc vbat max condition */ +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + psy_do_property("battery", get, POWER_SUPPLY_EXT_PROP_VOLTAGE_PACK_MAIN, value); + voltage = value.intval; + if (voltage >= charger->pdata->fpdo_dc_max_main_vbat) { + pr_info("%s: FPDO DC, S/C was selected! high main vbat(%dmV/%dmV)\n", __func__, + voltage, charger->pdata->fpdo_dc_max_main_vbat); + return true; + } + + psy_do_property("battery", get, POWER_SUPPLY_EXT_PROP_VOLTAGE_PACK_SUB, value); + voltage = value.intval; + if (voltage >= charger->pdata->fpdo_dc_max_sub_vbat) { + pr_info("%s: FPDO DC, S/C was selected! high sub vbat(%dmV/%dmV)\n", __func__, + voltage, charger->pdata->fpdo_dc_max_sub_vbat); + return true; + } +#else + psy_do_property("battery", get, POWER_SUPPLY_PROP_VOLTAGE_NOW, value); + voltage = value.intval / 1000; + if (voltage >= charger->pdata->fpdo_dc_max_vbat) { + pr_info("%s: FPDO DC, S/C was selected! high vbat(%dmV)\n", __func__, voltage); + return true; + } +#endif + } + + /* check fpdo dc thermal condition check */ + psy_do_property("battery", get, POWER_SUPPLY_EXT_PROP_FPDO_DC_THERMAL_CHECK, value); + if (value.intval) { + pr_info("%s: S/C was selected! FPDO_DC_THERMAL_CHECK(%d)\n", __func__, value.intval); + return true; + } + + return false; +} + +static int sec_direct_chg_check_charging_source(struct sec_direct_charger_info *charger) +{ + union power_supply_propval value = {0,}; + int ret = SEC_CHARGING_SOURCE_SWITCHING; + int has_apdo = 0, cable_type = 0, voltage_avg = 0; + unsigned int current_event = 0, lrp_chg_src = SEC_CHARGING_SOURCE_DIRECT, tx_retry_case = 0; + int flash_state = 0, mst_en = 0, abnormal_ta = 0; +#if IS_ENABLED(CONFIG_MTK_CHARGER) + int mtk_fg_init = 0; +#endif + + pr_info("%s: dc_retry_cnt(%d)\n", __func__, charger->dc_retry_cnt); + + if (charger->dc_err) { + if (charger->ta_alert_wa) { + psy_do_property("battery", get, + POWER_SUPPLY_EXT_PROP_DIRECT_TA_ALERT, value); + charger->ta_alert_mode = value.intval; + } + + pr_info("%s: dc_err(%d), ta_alert_mode(%d)\n", __func__, charger->dc_err, charger->ta_alert_mode); + value.intval = SEC_BAT_CURRENT_EVENT_DC_ERR; + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_CURRENT_EVENT, value); + if (!charger->ta_alert_wa || (charger->ta_alert_mode == OCP_NONE)) { + pr_info("%s: S/C was selected! ta_alert_mode(%d)\n", __func__, charger->ta_alert_mode); + goto end_chg_src; + } + } + if ((charger->charger_mode != SEC_BAT_CHG_MODE_CHARGING) && + (charger->charger_mode != SEC_BAT_CHG_MODE_PASS_THROUGH)) { + pr_info("%s: S/C was selected! charger_mode(%d)\n", __func__, charger->charger_mode); + goto end_chg_src; + } + +#if defined(CONFIG_WIRELESS_TX_MODE) + /* check TX enable*/ + psy_do_property("battery", get, POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ENABLE, value); + charger->wc_tx_enable = value.intval; + if (charger->wc_tx_enable) { + pr_info("@TX_Mode %s: Source Switching charger during Tx mode\n", __func__); + goto end_chg_src; + } +#endif + + if (sec_direct_chg_check_temp(charger)) + goto end_chg_src; + + psy_do_property("battery", get, POWER_SUPPLY_EXT_PROP_LRP_CHG_SRC, value); + lrp_chg_src = value.intval; + if (lrp_chg_src == SEC_CHARGING_SOURCE_SWITCHING) { + pr_info("%s: S/C was selected! lrp_chg_src is S/C\n", __func__); + goto end_chg_src; + } + + /* check current event */ + psy_do_property("battery", get, POWER_SUPPLY_EXT_PROP_CURRENT_EVENT, value); + current_event = value.intval; + psy_do_property("wireless", get, POWER_SUPPLY_EXT_PROP_WIRELESS_TX_RETRY_CASE, value); + tx_retry_case = value.intval; + if (sec_direct_chg_check_event(charger, current_event, tx_retry_case)) { + pr_info("%s: S/C was selected! current_event(0x%x), tx_retry_case(0x%x)\n", + __func__, current_event, tx_retry_case); + goto end_chg_src; + } + + /* check test mode */ + if (charger->test_mode_source == SEC_CHARGING_SOURCE_SWITCHING) { + pr_info("%s: S/C was selected! tese_mode_source(%d)\n", __func__, charger->test_mode_source); + goto end_chg_src; + } + + /* check apdo */ + psy_do_property("battery", get, POWER_SUPPLY_PROP_ONLINE, value); + cable_type = value.intval; + if (!is_pd_apdo_wire_type(charger->cable_type) || !is_pd_apdo_wire_type(cable_type)) { + pr_info("%s: S/C was selected! Not APDO(%d, %d)\n", + __func__, charger->cable_type, cable_type); + goto end_chg_src; + } + + /* check battery->status */ + psy_do_property("battery", get, POWER_SUPPLY_PROP_STATUS, value); + charger->batt_status = value.intval; + if (charger->batt_status == POWER_SUPPLY_STATUS_FULL || + charger->batt_status == POWER_SUPPLY_STATUS_NOT_CHARGING || + charger->batt_status == POWER_SUPPLY_STATUS_DISCHARGING) { + pr_info("%s: S/C was selected! battery->status(%d)\n", + __func__, charger->batt_status); + goto end_chg_src; + } + + /* check charging status */ + psy_do_property("battery", get, POWER_SUPPLY_EXT_PROP_DIRECT_HAS_APDO, value); + has_apdo = value.intval; + if (charger->cable_type == SEC_BATTERY_CABLE_FPDO_DC) + has_apdo = 1; + psy_do_property("battery", get, POWER_SUPPLY_EXT_PROP_FLASH_STATE, value); + flash_state = value.intval; /* check only for MTK */ + psy_do_property("battery", get, POWER_SUPPLY_EXT_PROP_MST_EN, value); + mst_en = value.intval; /* check only for MTK */ + psy_do_property("battery", get, POWER_SUPPLY_EXT_PROP_ABNORMAL_TA, value); + abnormal_ta = value.intval; + +#if IS_ENABLED(CONFIG_MTK_CHARGER) + psy_do_property("battery", get, POWER_SUPPLY_EXT_PROP_MTK_FG_INIT, value); + mtk_fg_init = value.intval; /* check only for MTK */ +#endif + + psy_do_property("battery", get, POWER_SUPPLY_PROP_CAPACITY, value); + charger->capacity = value.intval; + if (charger->direct_chg_done || (charger->capacity >= charger->pdata->dchg_end_soc) + || !has_apdo || charger->store_mode || flash_state || mst_en || abnormal_ta +#if IS_ENABLED(CONFIG_MTK_CHARGER) + || !mtk_fg_init +#endif + ) { + pr_info("%s: S/C was selected! dc_done(%s), SoC(%d), has_apdo(%d) mst_en(%d) abnormal_ta(%d)\n", + __func__, charger->direct_chg_done ? "TRUE" : "FALSE", + charger->capacity, has_apdo, mst_en, abnormal_ta); + goto end_chg_src; + } + + if (charger->vbat_min_src != LOW_VBAT_OFF) { + psy_do_property("battery", get, + POWER_SUPPLY_PROP_VOLTAGE_AVG, value); + voltage_avg = value.intval / 1000; + if (voltage_avg < charger->pdata->dchg_min_vbat) { + pr_info("%s: S/C was selected! low vbat(%dmV)\n", + __func__, voltage_avg); + charger->vbat_min_src = LOW_VBAT_SET; + goto end_chg_src; + } + charger->vbat_min_src = LOW_VBAT_OFF; + } + + if (sec_direct_fpdo_dc_check(charger)) + goto end_chg_src; + + ret = SEC_CHARGING_SOURCE_DIRECT; + +end_chg_src: + if (charger->charging_source != ret) { + store_battery_log("CHG_SRC:SOC(%d),BATT_ST(%d),VOLT_AVG(%d),CHG_MODE(%d)", + charger->capacity, charger->batt_status, voltage_avg, charger->charger_mode); + store_battery_log("CHG_SRC:SRC(%s),CT(%d,%d),CURR_EV(0x%x),DC_ERR(%d),TX(%d),HAS_APDO(%d),DC_DONE(%d)", + ret ? "DIRECT" : "SWITCHING", cable_type, charger->cable_type, current_event, charger->dc_err, + charger->wc_tx_enable, has_apdo, charger->direct_chg_done); + } + + return sb_pt_check_chg_src(charger->pt, ret); +} + +static int sec_direct_chg_set_charging_source(struct sec_direct_charger_info *charger, + unsigned int charger_mode, int charging_source) +{ + union power_supply_propval value = {0,}; + + mutex_lock(&charger->charger_mutex); + if (charging_source == SEC_CHARGING_SOURCE_DIRECT) { + sec_direct_chg_set_switching_charge(charger, SEC_BAT_CHG_MODE_BUCK_OFF); + sec_direct_chg_set_direct_charge(charger, charger_mode); + + value.intval = SEC_INPUT_VOLTAGE_APDO; + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_DIRECT_FIXED_PDO, value); + } else { + if (charger->ta_alert_wa) { + psy_do_property("battery", get, + POWER_SUPPLY_EXT_PROP_DIRECT_TA_ALERT, value); + charger->ta_alert_mode = value.intval; + } + + /* Must Charging-off the DC charger before changing voltage */ + /* to prevent reverse-current into TA */ + sec_direct_chg_set_direct_charge(charger, SEC_BAT_CHG_MODE_CHARGING_OFF); + + if (charger->cable_type == SEC_BATTERY_CABLE_FPDO_DC && + charger->charging_source == SEC_CHARGING_SOURCE_DIRECT) + msleep(100); + + value.intval = SEC_INPUT_VOLTAGE_9V; + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_DIRECT_FIXED_PDO, value); + + sec_direct_chg_set_switching_charge(charger, charger_mode); + } + + charger->charging_source = charging_source; + mutex_unlock(&charger->charger_mutex); + + return 0; +} + +static void sec_direct_chg_set_charge(struct sec_direct_charger_info *charger, unsigned int charger_mode) +{ + int charging_source; + + charger->charger_mode = charger_mode; + + switch (charger->charger_mode) { + case SEC_BAT_CHG_MODE_BUCK_OFF: + case SEC_BAT_CHG_MODE_CHARGING_OFF: + case SEC_BAT_CHG_MODE_PASS_THROUGH: + charger->is_charging = false; + break; + case SEC_BAT_CHG_MODE_CHARGING: + charger->is_charging = true; + break; + } + + charging_source = sec_direct_chg_check_charging_source(charger); + sec_direct_chg_set_charging_source(charger, charger_mode, charging_source); +} + +static void sec_direct_chg_do_dc_fullcharged(struct sec_direct_charger_info *charger) { + int charging_source; + + pr_info("%s: called\n", __func__); + charger->direct_chg_done = true; + + charging_source = sec_direct_chg_check_charging_source(charger); + sec_direct_chg_set_charging_source(charger, charger->charger_mode, charging_source); +} + +static int sec_direct_chg_set_input_current(struct sec_direct_charger_info *charger, + enum power_supply_property psp, int input_current) { + union power_supply_propval value = {0,}; + + pr_info("%s: called(%dmA)\n", __func__, input_current); + + value.intval = input_current; + psy_do_property(charger->pdata->main_charger_name, set, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, value); + + /* direct charger input current is based on charging current */ + return 0; +} + +static int sec_direct_chg_set_charging_current(struct sec_direct_charger_info *charger, + enum power_supply_property psp, int charging_current) { + union power_supply_propval value = {0,}; + int charging_source, cable_type; + + psy_do_property("battery", get, + POWER_SUPPLY_EXT_PROP_DIRECT_CHARGER_MODE, value); + charger->now_isApdo = value.intval; + + psy_do_property("battery", get, + POWER_SUPPLY_PROP_ONLINE, value); + cable_type = value.intval; + + pr_info("%s: called(%dmA) now_isApdo(%d) cable_type(%d)\n", + __func__, charging_current, charger->now_isApdo, cable_type); + + /* main charger */ + value.intval = charging_current; + psy_do_property(charger->pdata->main_charger_name, set, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, value); + + charger->dc_charging_current = charging_current; + charger->dc_input_current = charger->dc_charging_current / 2; + + charging_source = sec_direct_chg_check_charging_source(charger); + value.intval = charger->dc_input_current; + + /* direct charger */ + if (is_pd_apdo_wire_type(cable_type)) { + psy_do_property(charger->pdata->direct_charger_name, set, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, value); + sec_direct_chg_set_charging_source(charger, charger->charger_mode, charging_source); + } + + return 0; +} + +static void sec_direct_chg_set_initial_status(struct sec_direct_charger_info *charger) +{ + union power_supply_propval value = {0,}; + + if (charger->dc_err) { + value.intval = SEC_BAT_CURRENT_EVENT_DC_ERR; + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_CURRENT_EVENT_CLEAR, value); + } + charger->direct_chg_done = false; + + charger->dc_charging_current = charger->pdata->dchg_min_current; + charger->dc_input_current = charger->dc_charging_current / 2; + charger->dc_err = false; + charger->dc_retry_cnt = 0; + charger->dc_rcp = false; + charger->test_mode_source = SEC_CHARGING_SOURCE_DIRECT; + charger->vbat_min_src = LOW_VBAT_NONE; +} + +static int sec_direct_chg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sec_direct_charger_info *charger = power_supply_get_drvdata(psy); + enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; + union power_supply_propval value = {0,}; + int ret = 0; + + ret = sb_pt_psy_get_property(charger->pt, psp, val); + if (ret) { + pr_info("%s: prevent event for pt(ret = %d)", __func__, ret); + return 0; + } + + value.intval = val->intval; + switch ((int)psp) { + case POWER_SUPPLY_PROP_STATUS: + if (charger->charging_source == SEC_CHARGING_SOURCE_DIRECT) { + psy_do_property(charger->pdata->direct_charger_name, get, psp, value); + } else { + psy_do_property(charger->pdata->main_charger_name, get, psp, value); + } + val->intval = value.intval; + break; + case POWER_SUPPLY_PROP_HEALTH: + if (charger->charging_source == SEC_CHARGING_SOURCE_DIRECT) { + psy_do_property(charger->pdata->direct_charger_name, get, psp, value); + if (value.intval == POWER_SUPPLY_EXT_HEALTH_DC_ERR) { + charger->dc_retry_cnt++; + if (charger->dc_retry_cnt > 2) { + charger->dc_err = true; + } else + charger->dc_err = false; + } else { + charger->dc_err = false; + charger->dc_retry_cnt = 0; + } + } else { + psy_do_property(charger->pdata->main_charger_name, get, psp, value); + charger->dc_retry_cnt = 0; + } + val->intval = value.intval; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: /* get input current which was set */ + psy_do_property(charger->pdata->main_charger_name, get, psp, value); + if (is_direct_chg_mode_on(charger->direct_chg_mode)) { + // NEED to CHECK + val->intval = charger->input_current; + } else { + val->intval = value.intval; + } + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: /* get charge current which was set */ + psy_do_property(charger->pdata->main_charger_name, get, psp, value); + if (is_direct_chg_mode_on(charger->direct_chg_mode)) { + // NEED to CHECK + val->intval = charger->charging_current; + } else { + val->intval = value.intval; + } + break; + case POWER_SUPPLY_PROP_TEMP: + psy_do_property(charger->pdata->direct_charger_name, get, psp, value); + val->intval = value.intval; + break; + case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: + switch (ext_psp) { + case POWER_SUPPLY_EXT_PROP_MONITOR_WORK: + psy_do_property(charger->pdata->main_charger_name, get, ext_psp, value); + if (is_pd_apdo_wire_type(charger->cable_type)) { + psy_do_property(charger->pdata->direct_charger_name, get, ext_psp, value); + val->intval = charger->vbat_min_src; + } else + val->intval = LOW_VBAT_NONE; + sec_direct_chg_monitor(charger); + break; + case POWER_SUPPLY_EXT_PROP_DIRECT_CHARGER_MODE: + val->intval = charger->direct_chg_mode; + break; + case POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED_DC: + psy_do_property(charger->pdata->main_charger_name, get, + POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED, value); + if (value.intval == SEC_BAT_CHG_MODE_CHARGING) + val->intval = true; + else + val->intval = false; + break; + case POWER_SUPPLY_EXT_PROP_DIRECT_DONE: + val->intval = charger->direct_chg_done; + break; + case POWER_SUPPLY_EXT_PROP_MEASURE_INPUT: + psy_do_property(charger->pdata->direct_charger_name, get, ext_psp, value); + val->intval = value.intval; + break; + case POWER_SUPPLY_EXT_PROP_DIRECT_CHARGER_CHG_STATUS: + ret = psy_do_property(charger->pdata->direct_charger_name, get, ext_psp, value); + val->strval = value.strval; + break; + case POWER_SUPPLY_EXT_PROP_CHANGE_CHARGING_SOURCE: + val->intval = charger->test_mode_source; + break; + case POWER_SUPPLY_EXT_PROP_DIRECT_CONSTANT_CHARGE_VOLTAGE: + psy_do_property(charger->pdata->direct_charger_name, get, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, value); + val->intval = value.intval; + break; + case POWER_SUPPLY_EXT_PROP_PASS_THROUGH_MODE: + case POWER_SUPPLY_EXT_PROP_PASS_THROUGH_MODE_TA_VOL: + ret = psy_do_property(charger->pdata->direct_charger_name, get, ext_psp, value); + val->intval = value.intval; + break; + case POWER_SUPPLY_EXT_PROP_D2D_REVERSE_VOLTAGE: + ret = psy_do_property(charger->pdata->direct_charger_name, get, + ext_psp, value); + val->intval = value.intval; + break; + case POWER_SUPPLY_EXT_PROP_CHARGER_IC_NAME: + psy_do_property(charger->pdata->main_charger_name, get, ext_psp, value); + pr_info("%s: CHARGER_IC_NAME: %s\n", __func__, value.strval); + val->strval = value.strval; + break; + case POWER_SUPPLY_EXT_PROP_D2D_REVERSE_OCP: + ret = psy_do_property(charger->pdata->direct_charger_name, get, + ext_psp, value); + val->intval = value.intval; + break; + case POWER_SUPPLY_EXT_PROP_DC_OP_MODE: + case POWER_SUPPLY_EXT_PROP_D2D_REVERSE_VBUS: + ret = psy_do_property(charger->pdata->direct_charger_name, get, + ext_psp, value); + val->intval = value.intval; + break; + case POWER_SUPPLY_EXT_PROP_CHARGER_MODE_DIRECT: + val->intval = charger->charger_mode_direct; + break; + case POWER_SUPPLY_EXT_PROP_DCHG_READ_BATP_BATN: + ret = psy_do_property(charger->pdata->direct_charger_name, get, + ext_psp, value); + val->intval = value.intval; + break; + default: + ret = psy_do_property(charger->pdata->main_charger_name, get, ext_psp, value); + val->intval = value.intval; + return ret; + } + break; + default: + ret = psy_do_property(charger->pdata->main_charger_name, get, psp, value); + val->intval = value.intval; + return ret; + } + + return ret; +} + +static int sec_direct_chg_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct sec_direct_charger_info *charger = power_supply_get_drvdata(psy); + enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; + union power_supply_propval value = {0,}; + int prev_val; + int ret = 0; + + ret = sb_pt_psy_set_property(charger->pt, psp, val); + if (ret) { + pr_info("%s: prevent event for pt(ret = %d)", __func__, ret); + return 0; + } + + value.intval = val->intval; + switch ((int)psp) { + case POWER_SUPPLY_PROP_STATUS: + psy_do_property(charger->pdata->main_charger_name, set, + psp, value); + charger->batt_status = val->intval; + pr_info("%s: batt status(%d)\n", __func__, charger->batt_status); + break; + case POWER_SUPPLY_PROP_ONLINE: + prev_val = charger->cable_type; + charger->cable_type = val->intval; + + if (charger->cable_type == SEC_BATTERY_CABLE_NONE) { + sec_direct_chg_set_initial_status(charger); + } + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + /* Dual Battery featured model turn on the ADC block during all charging not only DC */ + value.intval = (charger->cable_type == SEC_BATTERY_CABLE_NONE) ? 0 : 1; + psy_do_property(charger->pdata->direct_charger_name, set, + POWER_SUPPLY_EXT_PROP_DIRECT_ADC_CTRL, value); +#endif + + /* main charger */ + value.intval = val->intval; + psy_do_property(charger->pdata->main_charger_name, set, + psp, value); + + /* direct charger */ + if (is_pd_apdo_wire_type(charger->cable_type)) { + charger->direct_chg_mode = SEC_DIRECT_CHG_MODE_DIRECT_CHECK_VBAT; + value.intval = 1; + psy_do_property(charger->pdata->direct_charger_name, set, + psp, value); + } else { + value.intval = 0; + psy_do_property(charger->pdata->direct_charger_name, set, + psp, value); + } + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + charger->input_current = val->intval; + sec_direct_chg_set_input_current(charger, psp, charger->input_current); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + charger->charging_current = val->intval; + sec_direct_chg_set_charging_current(charger, psp, charger->charging_current); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + charger->float_voltage = val->intval; + psy_do_property(charger->pdata->main_charger_name, set, + psp, value); + break; + case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: + switch (ext_psp) { + case POWER_SUPPLY_EXT_PROP_DIRECT_CHARGER_MODE: + if (val->intval >= SEC_DIRECT_CHG_MODE_MAX) { + pr_info("%s: abnormal direct_chg_mode(%d)\n", __func__, val->intval); + } else { + if (!charger->direct_chg_done) { + pr_info("%s: direct_chg_mode:%s(%d)->%s(%d)\n", __func__, + sec_direct_chg_mode_str[charger->direct_chg_mode], charger->direct_chg_mode, + sec_direct_chg_mode_str[val->intval], val->intval); + charger->direct_chg_mode = val->intval; + if (charger->direct_chg_mode == SEC_DIRECT_CHG_MODE_DIRECT_OFF) + charger->charger_mode_direct = SEC_BAT_CHG_MODE_CHARGING_OFF; + } + } + break; + case POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED_DC: +#if 0 + if (val->intval) + sec_direct_chg_check_set_charge(charger, charger->charger_mode, + SEC_BAT_CHG_MODE_BUCK_OFF, SEC_BAT_CHG_MODE_CHARGING); + else + sec_direct_chg_check_set_charge(charger, charger->charger_mode, + SEC_BAT_CHG_MODE_CHARGING, SEC_BAT_CHG_MODE_CHARGING_OFF); +#endif + break; + case POWER_SUPPLY_EXT_PROP_DIRECT_DONE: + pr_info("%s: POWER_SUPPLY_EXT_PROP_DIRECT_DONE(%d)\n", __func__, val->intval); + if (val->intval) + sec_direct_chg_do_dc_fullcharged(charger); + break; + case POWER_SUPPLY_EXT_PROP_CURRENT_MEASURE: + psy_do_property(charger->pdata->main_charger_name, set, + ext_psp, value); + break; + case POWER_SUPPLY_EXT_PROP_DIRECT_WDT_CONTROL: + psy_do_property(charger->pdata->direct_charger_name, set, + ext_psp, value); + break; + case POWER_SUPPLY_EXT_PROP_DIRECT_CONSTANT_CHARGE_VOLTAGE: + psy_do_property(charger->pdata->direct_charger_name, set, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, value); + break; + case POWER_SUPPLY_EXT_PROP_DIRECT_CURRENT_MAX: + psy_do_property(charger->pdata->direct_charger_name, set, + ext_psp, value); + break; + case POWER_SUPPLY_EXT_PROP_DIRECT_CONSTANT_CHARGE_VOLTAGE_MAX: + psy_do_property(charger->pdata->direct_charger_name, set, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, value); + break; + case POWER_SUPPLY_EXT_PROP_DIRECT_ADC_CTRL: + psy_do_property(charger->pdata->direct_charger_name, set, + ext_psp, value); + break; + case POWER_SUPPLY_EXT_PROP_DIRECT_CLEAR_ERR: + /* If SRCCAP is changed by Src, clear DC err variables */ + charger->dc_err = false; + charger->dc_retry_cnt = 0; + if (val->intval) { + value.intval = SEC_BAT_CURRENT_EVENT_DC_ERR; + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_CURRENT_EVENT_CLEAR, value); + } + pr_info("%s: POWER_SUPPLY_EXT_PROP_DIRECT_CLEAR_ERR\n", + __func__); + break; + case POWER_SUPPLY_EXT_PROP_CHANGE_CHARGING_SOURCE: + pr_info("%s: POWER_SUPPLY_EXT_PROP_CHANGE_CHARGING_SOURCE(%d, %d)\n", + __func__, val->strval[0], val->strval[1]); + if (val->strval[0] == SEC_STORE_MODE) + charger->store_mode = true; + if (is_pd_apdo_wire_type(charger->cable_type)) { + charger->test_mode_source = val->strval[1]; + + if (charger->test_mode_source == SEC_CHARGING_SOURCE_DIRECT) + charger->test_mode_source = sec_direct_chg_check_charging_source(charger); + + sec_direct_chg_set_charging_source(charger, charger->charger_mode, charger->test_mode_source); + } else { + pr_info("%s: block to set charging_source (cable:%d, mode:%d, test:%d, store:%d)\n", + __func__, charger->cable_type, charger->charger_mode, + charger->test_mode_source, charger->store_mode); + } + break; + case POWER_SUPPLY_EXT_PROP_REFRESH_CHARGING_SOURCE: + if (is_pd_apdo_wire_type(charger->cable_type)) { + int charging_source; + + charging_source = sec_direct_chg_check_charging_source(charger); + sec_direct_chg_set_charging_source(charger, charger->charger_mode, charging_source); + } + break; + case POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED: + sec_direct_chg_set_charge(charger, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_DC_INITIALIZE: + sec_direct_chg_set_initial_status(charger); + break; + case POWER_SUPPLY_EXT_PROP_PASS_THROUGH_MODE: + case POWER_SUPPLY_EXT_PROP_PASS_THROUGH_MODE_TA_VOL: + ret = psy_do_property(charger->pdata->direct_charger_name, set, ext_psp, value); + break; + case POWER_SUPPLY_EXT_PROP_D2D_REVERSE_VOLTAGE: + pr_info("%s: POWER_SUPPLY_EXT_PROP_D2D_REVERSE_VOLTAGE\n", __func__); + psy_do_property(charger->pdata->direct_charger_name, set, + psp, value); + break; + case POWER_SUPPLY_EXT_PROP_DC_OP_MODE: + case POWER_SUPPLY_EXT_PROP_ADC_MODE: + ret = psy_do_property(charger->pdata->direct_charger_name, set, ext_psp, value); + break; + case POWER_SUPPLY_EXT_PROP_OTG_VBUS_CTRL: + pr_info("%s: OTG_CONTROL(%d)\n", __func__, val->intval); + if (val->intval) { + value.intval = 1000000;/* 1000mA */ + psy_do_property(charger->pdata->direct_charger_name, set, + POWER_SUPPLY_EXT_PROP_DC_VIN_OVERCURRENT, value); + value.intval = POWER_SUPPLY_DC_REVERSE_BYP;/* Reverse bypass mode */ + psy_do_property(charger->pdata->direct_charger_name, set, + POWER_SUPPLY_EXT_PROP_DC_REVERSE_MODE, value); + } else { + value.intval = POWER_SUPPLY_DC_REVERSE_STOP;/* Stop reverse mode */ + psy_do_property(charger->pdata->direct_charger_name, set, + POWER_SUPPLY_EXT_PROP_DC_REVERSE_MODE, value); + } + break; + case POWER_SUPPLY_EXT_PROP_DC_RCP: + charger->dc_rcp = val->intval; + break; + default: + ret = psy_do_property(charger->pdata->main_charger_name, set, ext_psp, value); + return ret; + } + break; + default: + ret = psy_do_property(charger->pdata->main_charger_name, set, psp, value); + return ret; + } + + return ret; +} + +#ifdef CONFIG_OF +static int sec_direct_charger_parse_dt(struct device *dev, + struct sec_direct_charger_info *charger) +{ + struct device_node *np = dev->of_node; + + if (!np) { + pr_err("%s: np NULL\n", __func__); + return 1; + } + sb_of_parse_str_dt(np, "charger,battery_name", charger->pdata, battery_name); + sb_of_parse_str_dt(np, "charger,main_charger", charger->pdata, main_charger_name); + sb_of_parse_str_dt(np, "charger,direct_charger", charger->pdata, direct_charger_name); + sb_of_parse_u32_dt(np, "charger,dchg_min_current", charger->pdata, dchg_min_current, SEC_DIRECT_CHG_MIN_IOUT); + sb_of_parse_u32_dt(np, "charger,dchg_min_vbat", charger->pdata, dchg_min_vbat, SEC_DIRECT_CHG_MIN_VBAT); + sb_of_parse_u32_dt(np, "charger,fpdo_dc_min_vbat", charger->pdata, fpdo_dc_min_vbat, FPDO_DC_MIN_VBAT); + sb_of_parse_u32_dt(np, "charger,fpdo_dc_max_vbat", charger->pdata, fpdo_dc_max_vbat, FPDO_DC_MAX_VBAT); +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + sb_of_parse_u32_dt(np, "charger,fpdo_dc_max_main_vbat", + charger->pdata, fpdo_dc_max_main_vbat, FPDO_DC_MAX_VBAT); + sb_of_parse_u32_dt(np, "charger,fpdo_dc_max_sub_vbat", charger->pdata, fpdo_dc_max_sub_vbat, FPDO_DC_MAX_VBAT); +#endif + sb_of_parse_u32_dt(np, "charger,end_soc", charger->pdata, dchg_end_soc, 95); + sb_of_parse_bool_dt(np, "charger,ta_alert_wa", charger, ta_alert_wa); + + np = of_find_node_by_name(NULL, "battery"); + if (!np) { + pr_info("%s: np NULL\n", __func__); + return 1; + } + sb_of_parse_bool_dt(np, "battery,dchg_dc_in_swelling", charger->pdata, dchg_dc_in_swelling); + sb_of_parse_u32_dt(np, "battery,wire_normal_warm_thresh", + charger->pdata, dchg_temp_high_threshold, 420); + sb_of_parse_u32_dt(np, "battery,wire_cool1_normal_thresh", + charger->pdata, dchg_temp_low_threshold, 180); + sb_of_parse_u32_dt(np, "battery,swelling_high_rechg_voltage", + charger->pdata, swelling_high_rechg_voltage, 4050); + sb_of_parse_bool_dt(np, "battery,chgen_over_swell_rechg_vol", charger->pdata, chgen_over_swell_rechg_vol); + + return 0; +} +#else +static int sec_direct_charger_parse_dt(struct device *dev, + struct sec_direct_charger_info *charger) +{ + return 0; +} +#endif /* CONFIG_OF */ + +static enum power_supply_property sec_direct_charger_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static const struct power_supply_desc sec_direct_charger_power_supply_desc = { + .name = "sec-direct-charger", + .type = POWER_SUPPLY_TYPE_UNKNOWN, + .properties = sec_direct_charger_props, + .num_properties = ARRAY_SIZE(sec_direct_charger_props), + .get_property = sec_direct_chg_get_property, + .set_property = sec_direct_chg_set_property, +}; + +static int sec_direct_charger_probe(struct platform_device *pdev) +{ + struct sec_direct_charger_info *charger; + struct sec_direct_charger_platform_data *pdata = NULL; + struct power_supply_config direct_charger_cfg = {}; + int ret = 0; + + pr_info("%s: SEC Direct-Charger Driver Loading\n", __func__); + + charger = kzalloc(sizeof(*charger), GFP_KERNEL); + if (!charger) + return -ENOMEM; + + if (pdev->dev.of_node) { + pdata = devm_kzalloc(&pdev->dev, + sizeof(struct sec_direct_charger_platform_data), + GFP_KERNEL); + if (!pdata) { + dev_err(&pdev->dev, "Failed to allocate memory\n"); + ret = -ENOMEM; + goto err_charger_free; + } + + charger->pdata = pdata; + if (sec_direct_charger_parse_dt(&pdev->dev, charger)) { + dev_err(&pdev->dev, + "%s: Failed to get sec-direct-charger dt\n", __func__); + ret = -EINVAL; + goto err_pdata_free; + } + } else { + pdata = dev_get_platdata(&pdev->dev); + charger->pdata = pdata; + } + + /* init direct charger variables */ + charger->direct_chg_done = false; + charger->direct_chg_mode = SEC_DIRECT_CHG_MODE_DIRECT_OFF; + charger->cable_type = SEC_BATTERY_CABLE_NONE; + + charger->charger_mode = SEC_BAT_CHG_MODE_CHARGING_OFF; + charger->charger_mode_direct = SEC_BAT_CHG_MODE_CHARGING_OFF; + charger->charger_mode_main = SEC_BAT_CHG_MODE_CHARGING_OFF; + charger->test_mode_source = SEC_CHARGING_SOURCE_DIRECT; + + charger->wc_tx_enable = false; + charger->now_isApdo = false; + charger->store_mode = false; + charger->vbat_min_src = LOW_VBAT_NONE; +#if IS_ENABLED(CONFIG_SEC_ABC) + charger->abc_dc_current_cnt = 0; +#endif + + platform_set_drvdata(pdev, charger); + charger->dev = &pdev->dev; + direct_charger_cfg.drv_data = charger; + charger->ta_alert_mode = OCP_NONE; + + mutex_init(&charger->charger_mutex); + + charger->pt = sb_pt_init(charger->dev); + if (IS_ERR(charger->pt)) { + ret = PTR_ERR(charger->pt); + dev_info(charger->dev, "%s: unused pass through (ret = %d)\n", __func__, ret); + charger->pt = NULL; + } + + charger->psy_chg = power_supply_register(&pdev->dev, + &sec_direct_charger_power_supply_desc, &direct_charger_cfg); + if (IS_ERR(charger->psy_chg)) { + ret = PTR_ERR(charger->psy_chg); + dev_err(charger->dev, + "%s: Failed to Register psy_chg(%d)\n", __func__, ret); + goto err_power_supply_register; + } + sec_chg_set_dev_init(SC_DEV_SEC_DIR_CHG); + + pr_info("%s: SEC Direct-Charger Driver Loaded(%s, %s)\n", + __func__, charger->pdata->main_charger_name, charger->pdata->direct_charger_name); + return 0; + +err_power_supply_register: + mutex_destroy(&charger->charger_mutex); +err_pdata_free: + kfree(pdata); +err_charger_free: + kfree(charger); + + return ret; +} + +static int sec_direct_charger_remove(struct platform_device *pdev) +{ + struct sec_direct_charger_info *charger = platform_get_drvdata(pdev); + + pr_info("%s: ++\n", __func__); + + power_supply_unregister(charger->psy_chg); + mutex_destroy(&charger->charger_mutex); + + dev_dbg(charger->dev, "%s: End\n", __func__); + + kfree(charger->pdata); + kfree(charger); + + pr_info("%s: --\n", __func__); + + return 0; +} + +static int sec_direct_charger_suspend(struct device *dev) +{ + return 0; +} + +static int sec_direct_charger_resume(struct device *dev) +{ + return 0; +} + +static void sec_direct_charger_shutdown(struct platform_device *pdev) +{ + struct sec_direct_charger_info *charger = platform_get_drvdata(pdev); + union power_supply_propval value = {0,}; + + pr_info("%s: ++\n", __func__); + + value.intval = false; + psy_do_property(charger->pdata->direct_charger_name, set, + POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED, value); + + value.intval = SEC_INPUT_VOLTAGE_5V; + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_DIRECT_FIXED_PDO, value); + + pr_info("%s: --\n", __func__); +} + +#ifdef CONFIG_OF +static struct of_device_id sec_direct_charger_dt_ids[] = { + { .compatible = "samsung,sec-direct-charger" }, + { } +}; +MODULE_DEVICE_TABLE(of, sec_direct_charger_dt_ids); +#endif /* CONFIG_OF */ + +static const struct dev_pm_ops sec_direct_charger_pm_ops = { + .suspend = sec_direct_charger_suspend, + .resume = sec_direct_charger_resume, +}; + +static struct platform_driver sec_direct_charger_driver = { + .driver = { + .name = "sec-direct-charger", + .owner = THIS_MODULE, + .pm = &sec_direct_charger_pm_ops, +#ifdef CONFIG_OF + .of_match_table = sec_direct_charger_dt_ids, +#endif + }, + .probe = sec_direct_charger_probe, + .remove = sec_direct_charger_remove, + .shutdown = sec_direct_charger_shutdown, +}; + +static int __init sec_direct_charger_init(void) +{ + pr_info("%s: \n", __func__); + return platform_driver_register(&sec_direct_charger_driver); +} + +static void __exit sec_direct_charger_exit(void) +{ + platform_driver_unregister(&sec_direct_charger_driver); +} + +device_initcall_sync(sec_direct_charger_init); +module_exit(sec_direct_charger_exit); + +MODULE_DESCRIPTION("Samsung Direct Charger Driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/battery/common/sec_direct_charger.h b/drivers/battery/common/sec_direct_charger.h new file mode 100644 index 000000000000..ab29405339b7 --- /dev/null +++ b/drivers/battery/common/sec_direct_charger.h @@ -0,0 +1,131 @@ +/* + * sec_direct_charger.h + * Samsung Mobile Charger Header + * + * Copyright (C) 2018 Samsung Electronics, 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 __SEC_DIRECT_CHARGER_H +#define __SEC_DIRECT_CHARGER_H __FILE__ + +#include "sec_battery.h" +#include "sec_charging_common.h" +#include "sb_pass_through.h" + +#define SEC_DIRECT_CHG_MIN_IOUT 2000 +#define SEC_DIRECT_CHG_MIN_VBAT 3500 +#define SEC_DIRECT_CHG_MAX_VBAT 4200 +#define FPDO_DC_MIN_VBAT 3500 +#define FPDO_DC_MAX_VBAT 5000 +#if IS_ENABLED(CONFIG_SEC_ABC) +#define ABC_DC_CNT 5 +#endif + +typedef enum _sec_direct_chg_src { + SEC_CHARGING_SOURCE_SWITCHING = 0, + SEC_CHARGING_SOURCE_DIRECT, +} sec_direct_chg_src_t; + +typedef enum _sec_direct_chg_mode { + SEC_DIRECT_CHG_MODE_DIRECT_OFF = 0, + SEC_DIRECT_CHG_MODE_DIRECT_CHECK_VBAT, + SEC_DIRECT_CHG_MODE_DIRECT_PRESET, + SEC_DIRECT_CHG_MODE_DIRECT_ON_ADJUST, + SEC_DIRECT_CHG_MODE_DIRECT_ON, + SEC_DIRECT_CHG_MODE_DIRECT_DONE, + SEC_DIRECT_CHG_MODE_DIRECT_BYPASS, + SEC_DIRECT_CHG_MODE_MAX, +} sec_direct_chg_mode_t; + +enum { + LOW_VBAT_SET = 0, + LOW_VBAT_NONE, + LOW_VBAT_OFF, +}; + +enum { + DC_NORMAL_MODE = 0, + DC_BYPASS_MODE, +}; + +#define is_direct_chg_mode_on(direct_chg_mode) ( \ + direct_chg_mode == SEC_DIRECT_CHG_MODE_DIRECT_PRESET || \ + direct_chg_mode == SEC_DIRECT_CHG_MODE_DIRECT_ON) || \ + direct_chg_mode == SEC_DIRECT_CHG_MODE_DIRECT_ON_ADJUST + +struct sec_direct_charger_platform_data { + char *battery_name; + char *main_charger_name; + char *direct_charger_name; + char *direct_sub_charger_name; + + int dchg_min_current; + int dchg_min_vbat; + int dchg_temp_low_threshold; + int dchg_temp_high_threshold; + int dchg_end_soc; + int dchg_dc_in_swelling; + int swelling_high_rechg_voltage; + int fpdo_dc_min_vbat; + int fpdo_dc_max_vbat; +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + int fpdo_dc_max_main_vbat; + int fpdo_dc_max_sub_vbat; +#endif + bool chgen_over_swell_rechg_vol; +}; + +struct sec_direct_charger_info { + struct device *dev; + struct sec_direct_charger_platform_data *pdata; + struct power_supply* psy_chg; + struct mutex charger_mutex; + + struct sb_pt *pt; + + sec_direct_chg_mode_t direct_chg_mode; + unsigned int charger_mode; + unsigned int charger_mode_main; + unsigned int charger_mode_direct; + unsigned int dc_retry_cnt; + + int cable_type; + int input_current; + int charging_current; + int topoff_current; + int float_voltage; + bool dc_err; + bool ta_alert_wa; + int ta_alert_mode; + bool is_charging; + int batt_status; + int capacity; + bool direct_chg_done; + bool wc_tx_enable; + bool now_isApdo; + bool store_mode; + int vbat_min_src; + bool dc_rcp; + + int bat_temp; + + sec_direct_chg_src_t charging_source; + int fpdo_pos; + int dc_input_current; + int dc_charging_current; + int test_mode_source; +#if IS_ENABLED(CONFIG_SEC_ABC) + int abc_dc_current_cnt; +#endif +}; +#endif /* __SEC_DIRECT_CHARGER_H */ diff --git a/drivers/battery/common/sec_pd.c b/drivers/battery/common/sec_pd.c new file mode 100644 index 000000000000..2863f413a7f4 --- /dev/null +++ b/drivers/battery/common/sec_pd.c @@ -0,0 +1,448 @@ +/* + * Copyrights (C) 2017 Samsung Electronics, 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. + */ + +#include +#include +#include + +#if defined(CONFIG_SEC_KUNIT) +#include + +SEC_PD_SINK_STATUS *g_psink_status; +EXPORT_SYMBOL(g_psink_status); +#else +static SEC_PD_SINK_STATUS *g_psink_status; +#endif + +#if defined(CONFIG_ARCH_MTK_PROJECT) || IS_ENABLED(CONFIG_SEC_MTK_CHARGER) +struct pdic_notifier_struct pd_noti; +EXPORT_SYMBOL(pd_noti); +#endif + +const char* sec_pd_pdo_type_str(int pdo_type) +{ + switch (pdo_type) { + case FPDO_TYPE: + return "FIXED"; + case APDO_TYPE: + return "APDO"; + case VPDO_TYPE: + return "VPDO"; + default: + return "UNKNOWN"; + } +} +EXPORT_SYMBOL(sec_pd_pdo_type_str); + +int sec_pd_select_pdo(int num) +{ + if (!g_psink_status) { + pr_err("%s: g_psink_status is NULL\n", __func__); + return -1; + } + + if (!g_psink_status->fp_sec_pd_select_pdo) { + pr_err("%s: not exist\n", __func__); + return -1; + } + + g_psink_status->fp_sec_pd_select_pdo(num); + + return 0; +} +EXPORT_SYMBOL(sec_pd_select_pdo); + +int sec_pd_select_pps(int num, int ppsVol, int ppsCur) +{ + if (!g_psink_status) { + pr_err("%s: g_psink_status is NULL\n", __func__); + return -1; + } + + if (!g_psink_status->fp_sec_pd_select_pps) { + pr_err("%s: not exist\n", __func__); + return -1; + } + + return g_psink_status->fp_sec_pd_select_pps(num, ppsVol, ppsCur); +} +EXPORT_SYMBOL(sec_pd_select_pps); + +int sec_pd_get_current_pdo(unsigned int *pdo) +{ + if (pdo == NULL) { + pr_err("%s: invalid argument\n", __func__); + return -1; + } + + if (!g_psink_status) { + pr_err("%s: g_psink_status is NULL\n", __func__); + return -1; + } + + *pdo = g_psink_status->current_pdo_num; + return 0; +} +EXPORT_SYMBOL(sec_pd_get_current_pdo); + +int sec_pd_get_selected_pdo(unsigned int *pdo) +{ + if (pdo == NULL) { + pr_err("%s: invalid argument\n", __func__); + return -1; + } + + if (!g_psink_status) { + pr_err("%s: g_psink_status is NULL\n", __func__); + return -1; + } + + *pdo = g_psink_status->selected_pdo_num; + return 0; +} +EXPORT_SYMBOL(sec_pd_get_selected_pdo); + +int sec_pd_is_apdo(unsigned int pdo) +{ + if (!g_psink_status) { + pr_err("%s: g_psink_status is NULL\n", __func__); + return -1; + } + + if (pdo > g_psink_status->available_pdo_num) { + pr_err("%s: invalid argument(%d)\n", __func__, pdo); + return -EINVAL; + } + + return ((g_psink_status->power_list[pdo].pdo_type == APDO_TYPE) ? true : false); +} +EXPORT_SYMBOL(sec_pd_is_apdo); + +int sec_pd_detach_with_cc(int state) +{ + if (!g_psink_status) { + pr_err("%s: g_psink_status is NULL\n", __func__); + return -1; + } + + if (!g_psink_status->fp_sec_pd_detach_with_cc) { + pr_err("%s: not exist\n", __func__); + return -1; + } + + g_psink_status->fp_sec_pd_detach_with_cc(state); + + return 0; +} +EXPORT_SYMBOL(sec_pd_detach_with_cc); + +static int sec_pd_check_pdo(unsigned int pdo, unsigned int min_volt, unsigned int max_volt, unsigned int max_curr) +{ + POWER_LIST *pwr = &g_psink_status->power_list[pdo]; + + if (pwr->pdo_type == APDO_TYPE) { + if (min_volt > 0) { + if (min_volt < pwr->min_voltage) + return -1; + + if (min_volt > pwr->max_voltage) + return -1; + } + + if (max_volt > 0) { + if (max_volt > pwr->max_voltage) + return -1; + + if (max_volt < pwr->min_voltage) + return -1; + } + } else { + if (max_volt != pwr->max_voltage) + return -1; + } + + if ((max_curr > 0) && (max_curr > pwr->max_current)) + return -1; + + return sec_pd_get_max_power(pwr->pdo_type, + pwr->min_voltage, pwr->max_voltage, pwr->max_current); +} + +#define PROG_0V 0 +#define PROG_5V 5900 +#define PROG_9V 11000 +#define PROG_15V 16000 +#define PROG_20V 21000 +#define PROG_MIN 3300 +int sec_pd_get_apdo_prog_volt(unsigned int pdo_type, unsigned int max_volt) +{ + if (pdo_type != APDO_TYPE) + return max_volt; + + switch (max_volt) { + case PROG_0V ... (PROG_5V - 1): + return 0; + case PROG_5V ... (PROG_9V - 1): + return 5000; + case PROG_9V ... (PROG_15V - 1): + return 9000; + case PROG_15V ... (PROG_20V - 1): + return 15000; + } + return 20000; +} +EXPORT_SYMBOL(sec_pd_get_apdo_prog_volt); + +int sec_pd_get_max_power(unsigned int pdo_type, unsigned int min_volt, unsigned int max_volt, unsigned int max_curr) +{ + return sec_pd_get_apdo_prog_volt(pdo_type, max_volt) * max_curr; +} +EXPORT_SYMBOL(sec_pd_get_max_power); + +int sec_pd_get_pdo_power(unsigned int *pdo, unsigned int *min_volt, unsigned int *max_volt, unsigned int *max_curr) +{ + unsigned int npdo = 0, nmin_volt = 0, nmax_volt = 0, ncurr = 0; + int nidx = 0, npwr = 0; + POWER_LIST *pwr; + + if (!g_psink_status) { + pr_err("%s: g_psink_status is NULL\n", __func__); + return -1; + } + + npdo = (pdo != NULL) ? (*pdo) : 0; + nmin_volt = (min_volt != NULL) ? (*min_volt) : 0; + nmax_volt = (max_volt != NULL) ? (*max_volt) : 0; + ncurr = (max_curr != NULL) ? (*max_curr) : 0; + + if (npdo > g_psink_status->available_pdo_num) { + pr_err("%s: invalid argument(%d)\n", __func__, npdo); + return -EINVAL; + } + + if (npdo != 0) { + npwr = sec_pd_check_pdo(npdo, nmin_volt, nmax_volt, ncurr); + nidx = npdo; + } else { + int i, anidx = 0, anpwr = 0, tpwr = 0; + + for (i = 1; i <= g_psink_status->available_pdo_num; i++) { + pwr = &g_psink_status->power_list[i]; + + tpwr = sec_pd_check_pdo(i, nmin_volt, nmax_volt, ncurr); + if (pwr->pdo_type == APDO_TYPE) { + if (anpwr < tpwr) { + anidx = i; + anpwr = tpwr; + } + } else { + if (npwr < tpwr) { + nidx = i; + npwr = tpwr; + } + } + } + + if (!npwr) { + nidx = anidx; + npwr = anpwr; + } + } + + if ((nidx <= 0) || (npwr <= 0)) { + pr_err("%s: failed to find available pdo(%d)\n", __func__, npwr); + return npwr; + } + + /* update values */ + pwr = &g_psink_status->power_list[nidx]; + + if (pdo != NULL) + *pdo = nidx; + if (min_volt != NULL) + *min_volt = pwr->min_voltage; + if (max_volt != NULL) + *max_volt = pwr->max_voltage; + if (max_curr != NULL) + *max_curr = pwr->max_current; + + pr_info("%s: success to find pdo idx = %d, pwr = %d\n", __func__, nidx, npwr); + return npwr; +} +EXPORT_SYMBOL(sec_pd_get_pdo_power); + +int sec_pd_vpdo_auth(int auth, int d2d_type) +{ + if (!g_psink_status) { + pr_err("%s: g_psink_status is NULL\n", __func__); + return -1; + } + + if (!g_psink_status->fp_sec_pd_vpdo_auth) { + pr_err("%s: not exist\n", __func__); + return -1; + } + + g_psink_status->fp_sec_pd_vpdo_auth(auth, d2d_type); + + return 0; +} +EXPORT_SYMBOL(sec_pd_vpdo_auth); + +int sec_pd_change_src(int max_cur) +{ + if (!g_psink_status) { + pr_err("%s: g_psink_status is NULL\n", __func__); + return -1; + } + + if (!g_psink_status->fp_sec_pd_change_src) { + pr_err("%s: not exist\n", __func__); + return -1; + } + + g_psink_status->fp_sec_pd_change_src(max_cur); + + return 0; +} +EXPORT_SYMBOL(sec_pd_change_src); + +int sec_pd_get_apdo_max_power(unsigned int *pdo_pos, unsigned int *taMaxVol, unsigned int *taMaxCur, unsigned int *taMaxPwr) +{ + int i; + int fpdo_max_power = 0; + + if (!g_psink_status) { + pr_err("%s: g_psink_status is NULL\n", __func__); + return -1; + } + + if (!g_psink_status->has_apdo) { + pr_info("%s: pd don't have apdo\n", __func__); + return -1; + } + + for (i = 1; i <= g_psink_status->available_pdo_num; i++) { + if (g_psink_status->power_list[i].pdo_type != APDO_TYPE) { + fpdo_max_power = + (g_psink_status->power_list[i].max_voltage * g_psink_status->power_list[i].max_current) > fpdo_max_power ? + (g_psink_status->power_list[i].max_voltage * g_psink_status->power_list[i].max_current) : fpdo_max_power; + } + } + + if (*pdo_pos == 0) { + /* min(max power of all fpdo, max power of selected apdo) */ + for (i = 1; i <= g_psink_status->available_pdo_num; i++) { + if ((g_psink_status->power_list[i].pdo_type == APDO_TYPE) && + g_psink_status->power_list[i].accept && + (g_psink_status->power_list[i].max_voltage >= *taMaxVol)) { + *pdo_pos = i; + *taMaxVol = g_psink_status->power_list[i].max_voltage; + *taMaxCur = g_psink_status->power_list[i].max_current; + *taMaxPwr = min(fpdo_max_power, + (g_psink_status->power_list[i].max_voltage * g_psink_status->power_list[i].max_current)); + + pr_info("%s : *pdo_pos(%d), *taMaxVol(%d), *maxCur(%d), *maxPwr(%d)\n", + __func__, *pdo_pos, *taMaxVol, *taMaxCur, *taMaxPwr); + + return 0; + } + } + } else { + /* If we already have pdo object position, we don't need to search max current */ + return -ENOTSUPP; + } + + pr_info("mv (%d) and ma (%d) out of range of APDO\n", *taMaxVol, *taMaxCur); + + return -EINVAL; +} +EXPORT_SYMBOL(sec_pd_get_apdo_max_power); + +void sec_pd_init_data(SEC_PD_SINK_STATUS* psink_status) +{ + g_psink_status = psink_status; + if (g_psink_status) + pr_info("%s: done.\n", __func__); + else + pr_err("%s: g_psink_status is NULL\n", __func__); +} +EXPORT_SYMBOL(sec_pd_init_data); + +int sec_pd_register_chg_info_cb(void *cb) +{ + if (!g_psink_status) { + pr_err("%s: g_psink_status is NULL\n", __func__); + return -1; + } + g_psink_status->fp_sec_pd_ext_cb = cb; + + return 0; +} +EXPORT_SYMBOL(sec_pd_register_chg_info_cb); + +void sec_pd_get_vid_pid(unsigned short *vid, unsigned short *pid, unsigned int *xid) +{ + if (!g_psink_status) { + pr_err("%s: g_psink_status is NULL\n", __func__); + return; + } + *vid = g_psink_status->vid; + *pid = g_psink_status->pid; + *xid = g_psink_status->xid; +} +EXPORT_SYMBOL(sec_pd_get_vid_pid); + +void sec_pd_manual_ccopen_req(int is_on) +{ + if (!g_psink_status) { + pr_err("%s: g_psink_status is NULL\n", __func__); + return; + } + + if (!g_psink_status->fp_sec_pd_manual_ccopen_req) { + pr_err("%s: not exist\n", __func__); + return; + } + + g_psink_status->fp_sec_pd_manual_ccopen_req(is_on); +} +EXPORT_SYMBOL(sec_pd_manual_ccopen_req); + +void sec_pd_manual_jig_ctrl(bool mode) +{ + if (!g_psink_status) { + pr_err("%s: g_psink_status is NULL\n", __func__); + return; + } + + if (!g_psink_status->fp_sec_pd_manual_jig_ctrl) { + pr_err("%s: not exist\n", __func__); + return; + } + + g_psink_status->fp_sec_pd_manual_jig_ctrl(mode); +} +EXPORT_SYMBOL(sec_pd_manual_jig_ctrl); + +static int __init sec_pd_init(void) +{ + pr_info("%s: \n", __func__); + return 0; +} + +module_init(sec_pd_init); +MODULE_DESCRIPTION("Samsung PD control"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/battery/common/sec_step_charging.c b/drivers/battery/common/sec_step_charging.c new file mode 100644 index 000000000000..59b462645b1b --- /dev/null +++ b/drivers/battery/common/sec_step_charging.c @@ -0,0 +1,1774 @@ +/* + * sec_step_charging.c + * Samsung Mobile Battery Driver + * + * Copyright (C) 2018 Samsung Electronics + * + * + * 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. + */ +#include "sec_battery.h" + +#define STEP_CHARGING_CONDITION_VOLTAGE 0x01 +#define STEP_CHARGING_CONDITION_SOC 0x02 +#define STEP_CHARGING_CONDITION_CHARGE_POWER 0x04 +#define STEP_CHARGING_CONDITION_ONLINE 0x08 +#define STEP_CHARGING_CONDITION_CURRENT_NOW 0x10 +#define STEP_CHARGING_CONDITION_FLOAT_VOLTAGE 0x20 +#define STEP_CHARGING_CONDITION_INPUT_CURRENT 0x40 +#define STEP_CHARGING_CONDITION_SOC_INIT_ONLY 0x80 /* use this to consider SOC to decide starting step only */ +#define STEP_CHARGING_CONDITION_FORCE_SOC 0x100 +#define STEP_CHARGING_CONDITION_FG_CURRENT 0x200 + +#define STEP_CHARGING_CONDITION_DC_INIT (STEP_CHARGING_CONDITION_VOLTAGE | STEP_CHARGING_CONDITION_SOC | STEP_CHARGING_CONDITION_SOC_INIT_ONLY) + +#define DIRECT_CHARGING_FLOAT_VOLTAGE_MARGIN 20 +#define DIRECT_CHARGING_FORCE_SOC_MARGIN 10 + +void sec_bat_reset_step_charging(struct sec_battery_info *battery) +{ + pr_info("%s\n", __func__); + battery->step_chg_status = -1; +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + battery->wpc_step_chg_status = -1; +#endif +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + battery->dc_float_voltage_set = false; +#endif +} +EXPORT_SYMBOL(sec_bat_reset_step_charging); + +void sec_bat_exit_step_charging(struct sec_battery_info *battery) +{ + sec_vote(battery->fcc_vote, VOTER_STEP_CHARGE, false, 0); + if (battery->step_chg_type & STEP_CHARGING_CONDITION_FLOAT_VOLTAGE) + sec_vote(battery->fv_vote, VOTER_STEP_CHARGE, false, 0); + sec_bat_reset_step_charging(battery); +} +EXPORT_SYMBOL(sec_bat_exit_step_charging); + +void sec_bat_exit_wpc_step_charging(struct sec_battery_info *battery) +{ + sec_vote(battery->fcc_vote, VOTER_WPC_STEP_CHARGE, false, 0); + if (battery->step_chg_type & STEP_CHARGING_CONDITION_FLOAT_VOLTAGE) + sec_vote(battery->fv_vote, VOTER_WPC_STEP_CHARGE, false, 0); + sec_bat_reset_step_charging(battery); +} +EXPORT_SYMBOL(sec_bat_exit_wpc_step_charging); + +/* + * true: step is changed + * false: not changed + */ +bool sec_bat_check_step_charging(struct sec_battery_info *battery) +{ + int i = 0, value = 0, step_condition = 0, lcd_status = 0; +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + int value_sub = 0, step_condition_sub = 0; +#endif + static int curr_cnt; + static bool skip_lcd_on_changed; + int age_step = battery->pdata->age_step; + union power_supply_propval val = {0, }; + int fpdo_sc = 0; + +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + if (is_wireless_all_type(battery->cable_type)) { + sec_vote(battery->fv_vote, VOTER_STEP_CHARGE, false, 0); + sec_vote(battery->fcc_vote, VOTER_STEP_CHARGE, false, 0); + return false; + } +#endif +#if defined(CONFIG_SEC_FACTORY) + if (!battery->step_chg_en_in_factory) + return false; +#endif + if (!battery->step_chg_type) + return false; +#if defined(CONFIG_ENG_BATTERY_CONCEPT) + if (battery->test_charge_current) + return false; + if (battery->test_step_condition <= 4500) + battery->pdata->step_chg_cond[0][0] = battery->test_step_condition; +#endif + + if (battery->siop_level < 100 || battery->lcd_status) + lcd_status = 1; + else + lcd_status = 0; + + if (battery->cable_type == SEC_BATTERY_CABLE_FPDO_DC) { + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED_DC, val); + fpdo_sc = val.intval; + pr_info("%s: SC for FPDO_DC(%d)", __func__, fpdo_sc); + + if (!fpdo_sc && battery->step_chg_status >= 0) + sec_bat_reset_step_charging(battery); + } + + if (battery->step_chg_type & STEP_CHARGING_CONDITION_ONLINE) { +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + if ((is_pd_apdo_wire_type(battery->cable_type) && !fpdo_sc) && + !((battery->current_event & SEC_BAT_CURRENT_EVENT_DC_ERR) && + (battery->ta_alert_mode == OCP_NONE))) { + sec_vote(battery->fv_vote, VOTER_STEP_CHARGE, false, 0); + sec_vote(battery->fcc_vote, VOTER_STEP_CHARGE, false, 0); + return false; + } + + if (((is_pd_apdo_wire_type(battery->cable_type) || is_pd_apdo_wire_type(battery->wire_status)) && + !fpdo_sc) && + (battery->sink_status.rp_currentlvl == RP_CURRENT_LEVEL3)) { + pr_info("%s: This cable type should be checked in dc step check\n", __func__); + sec_vote(battery->fv_vote, VOTER_STEP_CHARGE, false, 0); + sec_vote(battery->fcc_vote, VOTER_STEP_CHARGE, false, 0); + return false; + } +#endif + if (!is_hv_wire_type(battery->cable_type) && !is_pd_wire_type(battery->cable_type) && + (battery->sink_status.rp_currentlvl != RP_CURRENT_LEVEL3)) { + sec_vote(battery->fv_vote, VOTER_STEP_CHARGE, false, 0); + sec_vote(battery->fcc_vote, VOTER_STEP_CHARGE, false, 0); + return false; + } + } + + pr_info("%s\n", __func__); + + if (battery->step_chg_type & STEP_CHARGING_CONDITION_CHARGE_POWER) { + if (battery->max_charge_power < battery->step_chg_charge_power) { + /* In case of max_charge_power falling by AICL during step-charging ongoing */ + sec_bat_exit_step_charging(battery); + return false; + } + } + + if (battery->step_charging_skip_lcd_on && lcd_status) { + if (!skip_lcd_on_changed) { + if (battery->step_chg_status != (battery->step_chg_step - 1)) { + sec_vote(battery->fcc_vote, VOTER_STEP_CHARGE, true, + battery->pdata->step_chg_curr[age_step][battery->step_chg_step - 1]); + + if (battery->step_chg_type & STEP_CHARGING_CONDITION_FLOAT_VOLTAGE) { + pr_info("%s : float voltage = %d\n", __func__, + battery->pdata->step_chg_vfloat[age_step][battery->step_chg_step - 1]); + sec_vote(battery->fv_vote, VOTER_STEP_CHARGE, true, + battery->pdata->step_chg_vfloat[age_step][battery->step_chg_step - 1]); + } + pr_info("%s : skip step charging because lcd on\n", __func__); + skip_lcd_on_changed = true; + return true; + } + } + return false; + } + + if (battery->step_chg_status < 0) { + i = 0; + + /* this is only for step enter condition and do not use STEP_CHARGING_CONDITION_SOC at the same time */ + if (battery->step_chg_type & STEP_CHARGING_CONDITION_SOC_INIT_ONLY) { + int soc_condition; + + value = battery->capacity; + while (i < battery->step_chg_step - 1) { + soc_condition = battery->pdata->step_chg_cond_soc[age_step][i]; + if (value < soc_condition) + break; + i++; + } + + pr_info("%s : set initial step(%d) by soc\n", __func__, i); + goto check_step_change; + } + } else + i = battery->step_chg_status; + + step_condition = battery->pdata->step_chg_cond[age_step][i]; + + if (battery->step_chg_type & STEP_CHARGING_CONDITION_VOLTAGE) { +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + step_condition_sub = battery->pdata->step_chg_cond_sub[age_step][i]; + value = battery->voltage_avg_main; + value_sub = battery->voltage_avg_sub; +#else + value = battery->voltage_avg; +#endif + } else if (battery->step_chg_type & STEP_CHARGING_CONDITION_SOC) { + value = battery->capacity; + if (lcd_status) { + step_condition = battery->pdata->step_chg_cond[age_step][i] + 15; + curr_cnt = 0; + } + } else { + return false; + } + + while (i < battery->step_chg_step - 1) { +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + if (battery->step_chg_type & STEP_CHARGING_CONDITION_VOLTAGE) { + if ((value < step_condition) && (value_sub < step_condition_sub)) + break; + } else { + if (value < step_condition) + break; + } +#else + if (value < step_condition) + break; +#endif + i++; + + if ((battery->step_chg_type & STEP_CHARGING_CONDITION_SOC) && + lcd_status) + step_condition = battery->pdata->step_chg_cond[age_step][i] + 15; + else { + step_condition = battery->pdata->step_chg_cond[age_step][i]; +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + if (battery->step_chg_type & STEP_CHARGING_CONDITION_VOLTAGE) + step_condition_sub = battery->pdata->step_chg_cond_sub[age_step][i]; +#endif + } + if (battery->step_chg_status != -1) + break; + } + +check_step_change: + if ((i != battery->step_chg_status) || skip_lcd_on_changed) { + /* this is only for no consuming current */ + if ((battery->step_chg_type & STEP_CHARGING_CONDITION_CURRENT_NOW) && + !lcd_status && + battery->step_chg_status >= 0) { + int condition_curr; + condition_curr = max(battery->current_avg, battery->current_now); + if (condition_curr < battery->pdata->step_chg_cond_curr[battery->step_chg_status]) { + curr_cnt++; + pr_info("%s : cnt = %d, curr(%d)mA < curr cond(%d)mA\n", + __func__, curr_cnt, condition_curr, + battery->pdata->step_chg_cond_curr[battery->step_chg_status]); + if (curr_cnt < 3) + return false; + } else { + pr_info("%s : clear cnt, curr(%d)mA >= curr cond(%d)mA or < 0mA\n", + __func__, condition_curr, + battery->pdata->step_chg_cond_curr[battery->step_chg_status]); + curr_cnt = 0; + return false; + } + } + + pr_info("%s : prev=%d, new=%d, value=%d, current=%d, curr_cnt=%d\n", __func__, + battery->step_chg_status, i, value, + battery->pdata->step_chg_curr[age_step][i], curr_cnt); + battery->step_chg_status = i; + skip_lcd_on_changed = false; + sec_vote(battery->fcc_vote, VOTER_STEP_CHARGE, true, + battery->pdata->step_chg_curr[age_step][i]); + + if (battery->step_chg_type & STEP_CHARGING_CONDITION_FLOAT_VOLTAGE) { + pr_info("%s : float voltage = %d\n", __func__, + battery->pdata->step_chg_vfloat[age_step][i]); + sec_vote(battery->fv_vote, VOTER_STEP_CHARGE, true, + battery->pdata->step_chg_vfloat[age_step][i]); + } + return true; + } + return false; +} +EXPORT_SYMBOL(sec_bat_check_step_charging); + +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) +bool sec_bat_check_wpc_step_charging(struct sec_battery_info *battery) +{ + int i = 0, value = 0, step_condition = 0, lcd_status = 0; + static int curr_cnt; + static bool skip_lcd_on_changed; + int age_step = battery->pdata->age_step; + +#if defined(CONFIG_SEC_FACTORY) + if (!battery->step_chg_en_in_factory) + return false; +#endif + if (!battery->wpc_step_chg_type) + return false; + if (is_not_wireless_type(battery->cable_type)) { + sec_vote(battery->fv_vote, VOTER_WPC_STEP_CHARGE, false, 0); + sec_vote(battery->fcc_vote, VOTER_WPC_STEP_CHARGE, false, 0); + + return false; + } +#if defined(CONFIG_ENG_BATTERY_CONCEPT) + if (battery->test_charge_current) + return false; + if (battery->test_step_condition <= 4500) + battery->pdata->wpc_step_chg_cond[0][0] = battery->test_step_condition; +#endif + + if (battery->siop_level < 100 || battery->lcd_status) + lcd_status = 1; + else + lcd_status = 0; + + if (battery->wpc_step_chg_type & STEP_CHARGING_CONDITION_CHARGE_POWER) { + if (battery->max_charge_power < battery->wpc_step_chg_charge_power) { + /* In case of max_charge_power falling by AICL during step-charging ongoing */ + sec_bat_exit_wpc_step_charging(battery); + return false; + } + } + + if (battery->step_charging_skip_lcd_on && lcd_status) { + if (!skip_lcd_on_changed) { + if (battery->wpc_step_chg_status != (battery->wpc_step_chg_step - 1)) { + sec_vote(battery->fcc_vote, VOTER_WPC_STEP_CHARGE, true, + battery->pdata->wpc_step_chg_curr[age_step][battery->wpc_step_chg_step - 1]); + + if (battery->wpc_step_chg_type & STEP_CHARGING_CONDITION_FLOAT_VOLTAGE) { + pr_info("%s : float voltage = %d\n", __func__, + battery->pdata->wpc_step_chg_vfloat[age_step][battery->wpc_step_chg_step - 1]); + sec_vote(battery->fv_vote, VOTER_WPC_STEP_CHARGE, true, + battery->pdata->wpc_step_chg_vfloat[age_step][battery->wpc_step_chg_step - 1]); + } + pr_info("%s : skip step charging because lcd on\n", __func__); + skip_lcd_on_changed = true; + return true; + } + } + return false; + } + + if (battery->wpc_step_chg_status < 0) + i = 0; + else + i = battery->wpc_step_chg_status; + + step_condition = battery->pdata->wpc_step_chg_cond[age_step][i]; + + if (battery->wpc_step_chg_type & STEP_CHARGING_CONDITION_VOLTAGE) { + value = battery->voltage_avg; + } else if (battery->wpc_step_chg_type & STEP_CHARGING_CONDITION_SOC) { + value = battery->capacity; + if (lcd_status) { + step_condition = battery->pdata->wpc_step_chg_cond[age_step][i] + 15; + curr_cnt = 0; + } + } else { + return false; + } + + while (i < battery->wpc_step_chg_step - 1) { + if (value < step_condition) + break; + i++; + + if ((battery->wpc_step_chg_type & STEP_CHARGING_CONDITION_SOC) && + lcd_status) + step_condition = battery->pdata->wpc_step_chg_cond[age_step][i] + 15; + else { + step_condition = battery->pdata->wpc_step_chg_cond[age_step][i]; + } + if (battery->wpc_step_chg_status != -1) + break; + } + + /* this is only for no consuming current */ + if ((battery->wpc_step_chg_type & STEP_CHARGING_CONDITION_CURRENT_NOW) && + !lcd_status && + battery->wpc_step_chg_status >= 0) { + int condition_curr; + condition_curr = max(battery->current_avg, battery->current_now); + if (condition_curr < battery->pdata->wpc_step_chg_cond_curr[battery->wpc_step_chg_status]) { + curr_cnt++; + pr_info("%s : cnt = %d, curr(%d)mA < curr cond(%d)mA\n", + __func__, curr_cnt, condition_curr, + battery->pdata->wpc_step_chg_cond_curr[battery->wpc_step_chg_status]); + if (curr_cnt < 3) + return false; + } else { + pr_info("%s : clear cnt, curr(%d)mA >= curr cond(%d)mA or < 0mA\n", + __func__, condition_curr, + battery->pdata->wpc_step_chg_cond_curr[battery->wpc_step_chg_status]); + curr_cnt = 0; + return false; + } + } + + pr_info("%s : prev=%d, new=%d, value=%d, current=%d, curr_cnt=%d\n", __func__, + battery->wpc_step_chg_status, i, value, + battery->pdata->wpc_step_chg_curr[age_step][i], curr_cnt); + battery->wpc_step_chg_status = i; + skip_lcd_on_changed = false; + sec_vote(battery->fcc_vote, VOTER_WPC_STEP_CHARGE, true, + battery->pdata->wpc_step_chg_curr[age_step][i]); + + if (battery->wpc_step_chg_type & STEP_CHARGING_CONDITION_FLOAT_VOLTAGE) { + pr_info("%s : float voltage = %d\n", __func__, + battery->pdata->wpc_step_chg_vfloat[age_step][i]); + sec_vote(battery->fv_vote, VOTER_WPC_STEP_CHARGE, true, + battery->pdata->wpc_step_chg_vfloat[age_step][i]); + } + + return true; +} +EXPORT_SYMBOL(sec_bat_check_wpc_step_charging); +#endif + +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) +bool skip_check_dc_step(struct sec_battery_info *battery) +{ + if (battery->dchg_dc_in_swelling) { + if (battery->current_event & SEC_BAT_CURRENT_EVENT_LOW_TEMP_MODE) + return true; + } else { + if (battery->current_event & SEC_BAT_CURRENT_EVENT_SWELLING_MODE) + return true; + } + + if (battery->current_event & SEC_BAT_CURRENT_EVENT_HV_DISABLE || + ((battery->current_event & SEC_BAT_CURRENT_EVENT_DC_ERR) && + (battery->ta_alert_mode == OCP_NONE)) || + battery->current_event & SEC_BAT_CURRENT_EVENT_SIOP_LIMIT || + battery->wc_tx_enable || + battery->uno_en || + battery->mix_limit || + battery->lrp_chg_src == SEC_CHARGING_SOURCE_SWITCHING) + return true; + else + return false; +} + +bool sec_bat_check_dc_step_charging(struct sec_battery_info *battery) +{ + int i, value; + int step = -1, step_vol = -1, step_input = -1, step_soc = -1, soc_condition = 0; + int force_step_soc = 0, step_fg_current = -1; + bool force_change_step = false; + union power_supply_propval val = {0, }; + int age_step = battery->pdata->age_step; + unsigned int dc_step_chg_type; + + if (battery->cable_type == SEC_BATTERY_CABLE_FPDO_DC) { + sec_vote(battery->dc_fv_vote, VOTER_DC_STEP_CHARGE, false, 0); + sec_vote(battery->fcc_vote, VOTER_CABLE, true, + battery->pdata->charging_current[SEC_BATTERY_CABLE_FPDO_DC].fast_charging_current); + sec_vote_refresh(battery->fcc_vote); + + return false; + } + + i = (battery->step_chg_status < 0 ? 0 : battery->step_chg_status); + dc_step_chg_type = battery->dc_step_chg_type[i]; + + if (!dc_step_chg_type) { + sec_vote(battery->dc_fv_vote, VOTER_DC_STEP_CHARGE, false, 0); + return false; + } + + if (dc_step_chg_type & STEP_CHARGING_CONDITION_CHARGE_POWER) + if (battery->charge_power < battery->dc_step_chg_charge_power) { + sec_vote(battery->dc_fv_vote, VOTER_DC_STEP_CHARGE, false, 0); + return false; + } + + if (dc_step_chg_type & STEP_CHARGING_CONDITION_ONLINE) { + if (!is_pd_apdo_wire_type(battery->cable_type)) { + sec_vote(battery->dc_fv_vote, VOTER_DC_STEP_CHARGE, false, 0); + return false; + } + } + if (skip_check_dc_step(battery)) { + if (battery->step_chg_status >= 0) + sec_bat_reset_step_charging(battery); + sec_vote(battery->dc_fv_vote, VOTER_DC_STEP_CHARGE, false, 0); + return false; + } + + if (!(dc_step_chg_type & STEP_CHARGING_CONDITION_DC_INIT)) { + pr_info("%s : cond_vol and cond_soc are both empty\n", __func__); + sec_vote(battery->dc_fv_vote, VOTER_DC_STEP_CHARGE, false, 0); + return false; + } + + /* this is only for step enter condition and do not use STEP_CHARGING_CONDITION_SOC at the same time */ + if (dc_step_chg_type & STEP_CHARGING_CONDITION_SOC_INIT_ONLY) { + if (battery->step_chg_status < 0) { + step_soc = i; + value = battery->capacity; + while (step_soc < battery->dc_step_chg_step - 1) { + soc_condition = battery->pdata->dc_step_chg_cond_soc[age_step][step_soc]; + if (value < soc_condition) + break; + step_soc++; + } + + if ((step_soc < step) || (step < 0)) + step = step_soc; + + pr_info("%s : set initial step(%d) by soc\n", __func__, step_soc); + goto check_dc_step_change; + } else + step_soc = battery->dc_step_chg_step - 1; + } + + if (dc_step_chg_type & STEP_CHARGING_CONDITION_SOC) { + step_soc = i; + value = battery->capacity; + while (step_soc < battery->dc_step_chg_step - 1) { + soc_condition = battery->pdata->dc_step_chg_cond_soc[age_step][step_soc]; + if (battery->step_chg_status >= 0 && + (battery->siop_level < 100 || battery->lcd_status)) { + soc_condition += DIRECT_CHARGING_FORCE_SOC_MARGIN; + force_change_step = true; + } + if (value < soc_condition) + break; + step_soc++; + if (battery->step_chg_status >= 0) + break; + } + + if ((step_soc < step) || (step < 0)) + step = step_soc; + + if (battery->step_chg_status < 0) { + pr_info("%s : set initial step(%d) by soc\n", __func__, step_soc); + goto check_dc_step_change; + } + if (force_change_step) { + pr_info("%s : force check step(%d) by soc\n", __func__, step_soc); + step_vol = step_input = step_soc; + battery->dc_step_chg_iin_cnt = battery->pdata->dc_step_chg_iin_check_cnt; + goto check_dc_step_change; + } + } else + step_soc = battery->dc_step_chg_step - 1; + + if (dc_step_chg_type & STEP_CHARGING_CONDITION_VOLTAGE) { + step_vol = i; + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + value = max((battery->voltage_avg_main - battery->pdata->dc_step_cond_v_margin_main), + (battery->voltage_avg_sub - battery->pdata->dc_step_cond_v_margin_sub)); + /* (charging current)step down when main or sub voltage condition meets */ + while (step_vol < battery->dc_step_chg_step - 1) { + if (battery->voltage_avg_main < battery->pdata->dc_step_chg_cond_vol[age_step][step_vol] && + battery->voltage_avg_sub < battery->pdata->dc_step_chg_cond_vol_sub[age_step][step_vol]) + break; + step_vol++; + if (battery->step_chg_status >= 0) + break; + } +#else + if (dc_step_chg_type & STEP_CHARGING_CONDITION_FLOAT_VOLTAGE) + value = battery->voltage_now + battery->pdata->dc_step_chg_cond_v_margin; + else + value = battery->voltage_avg; + while (step_vol < battery->dc_step_chg_step - 1) { + if (value < battery->pdata->dc_step_chg_cond_vol[age_step][step_vol]) + break; + step_vol++; + if (battery->step_chg_status >= 0) + break; + } +#endif + if ((step_vol < step) || (step < 0)) + step = step_vol; + + if (battery->step_chg_status < 0) { + pr_info("%s : set initial step(%d) by vol\n", __func__, step_vol); + goto check_dc_step_change; + } + } else + step_vol = battery->dc_step_chg_step - 1; + + if (dc_step_chg_type & STEP_CHARGING_CONDITION_INPUT_CURRENT) { + step_input = i; + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_DIRECT_CHARGER_MODE, val); + if (val.intval != SEC_DIRECT_CHG_MODE_DIRECT_ON) { + pr_info("%s : dc no charging status = %d\n", __func__, val.intval); + battery->dc_step_chg_iin_cnt = 0; + return false; + } else if (battery->siop_level >= 100 && !battery->lcd_status) { + val.intval = SEC_BATTERY_IIN_MA; + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_MEASURE_INPUT, val); + value = val.intval; + + while (step_input < battery->dc_step_chg_step - 1) { + if (value > battery->pdata->dc_step_chg_cond_iin[step_input]) + break; + step_input++; + + if (battery->step_chg_status >= 0) { + battery->dc_step_chg_iin_cnt++; + break; + } else { + battery->dc_step_chg_iin_cnt = 0; + } + } + } else { + /* + * Do not check input current when lcd is on or siop is not 100 + * since there might be quite big system current + */ + step_input = battery->dc_step_chg_step - 1; + } + + if ((step_input < step) || (step < 0)) + step = step_input; + } else + step_input = battery->dc_step_chg_step - 1; + + if (dc_step_chg_type & STEP_CHARGING_CONDITION_FG_CURRENT) { + step_fg_current = i; + psy_do_property(battery->pdata->charger_name, get, + POWER_SUPPLY_EXT_PROP_DIRECT_CHARGER_MODE, val); + if (val.intval != SEC_DIRECT_CHG_MODE_DIRECT_ON) { + pr_info("%s : dc no charging status = %d\n", __func__, val.intval); + battery->dc_step_chg_iin_cnt = 0; + return false; + } else if (battery->siop_level >= 100 && !battery->lcd_status) { + int current_now, current_avg; + + val.intval = SEC_BATTERY_CURRENT_MA; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CURRENT_NOW, val); + current_now = val.intval; + val.intval = SEC_BATTERY_CURRENT_MA; + psy_do_property(battery->pdata->fuelgauge_name, get, + POWER_SUPPLY_PROP_CURRENT_AVG, val); + current_avg = val.intval; + value = max(current_now, current_avg) / 2; + + while (step_fg_current < battery->dc_step_chg_step - 1) { + if (value > battery->pdata->dc_step_chg_cond_iin[step_fg_current]) + break; + step_fg_current++; + + if (battery->step_chg_status >= 0) { + battery->dc_step_chg_iin_cnt++; + break; + } + battery->dc_step_chg_iin_cnt = 0; + } + } else { + /* + * Do not check input current when lcd is on or siop is not 100 + * since there might be quite big system current + */ + step_fg_current = battery->dc_step_chg_step - 1; + } + + if ((step_fg_current < step) || (step < 0)) + step = step_fg_current; + } else + step_fg_current = battery->dc_step_chg_step - 1; + + + if (dc_step_chg_type & STEP_CHARGING_CONDITION_FORCE_SOC) { + force_step_soc = i; + if (battery->capacity >= battery->pdata->dc_step_chg_cond_soc[age_step][i]) { + if (++force_step_soc > step) + step = force_step_soc; + pr_info("%s : SOC(%d) cond_soc(%d) step(%d) force_step_soc(%d)\n", __func__, + battery->capacity, battery->pdata->dc_step_chg_cond_soc[age_step][i], + step, force_step_soc); + } else + force_step_soc = 0; + } else + force_step_soc = 0; + + +check_dc_step_change: + pr_info("%s : curr_step(%d), step_vol(%d), step_soc(%d), step_input(%d, %d), curr_cnt(%d/%d) force_step_soc(%d)\n", + __func__, step, step_vol, step_soc, step_input, step_fg_current, + battery->dc_step_chg_iin_cnt, battery->pdata->dc_step_chg_iin_check_cnt, force_step_soc); + + if (battery->step_chg_status < 0 || force_step_soc || + (step != battery->step_chg_status && + step == min(min(step_vol, step_soc), min(step_input, step_fg_current)))) { + if ((dc_step_chg_type & + (STEP_CHARGING_CONDITION_INPUT_CURRENT | STEP_CHARGING_CONDITION_FG_CURRENT)) && + (battery->step_chg_status >= 0)) { + if ((battery->dc_step_chg_iin_cnt < battery->pdata->dc_step_chg_iin_check_cnt) && + (battery->siop_level >= 100 && !battery->lcd_status) && !force_step_soc) { + pr_info("%s : keep step(%d), curr_cnt(%d/%d)\n", + __func__, battery->step_chg_status, + battery->dc_step_chg_iin_cnt, battery->pdata->dc_step_chg_iin_check_cnt); + return false; + } + } + + pr_info("%s : cable(%d), soc(%d), step changed(%d->%d), current(%dmA) force_step_soc(%d)\n", + __func__, battery->cable_type, battery->capacity, battery->step_chg_status, step, + battery->pdata->dc_step_chg_val_iout[age_step][step], force_step_soc); + /* set charging current */ + battery->pdata->charging_current[battery->cable_type].fast_charging_current = + battery->pdata->dc_step_chg_val_iout[age_step][step]; + + if (dc_step_chg_type & STEP_CHARGING_CONDITION_FLOAT_VOLTAGE) { + if (battery->step_chg_status < 0) { + pr_info("%s : step float voltage = %d\n", __func__, + battery->pdata->dc_step_chg_val_vfloat[age_step][step]); + sec_vote(battery->dc_fv_vote, VOTER_DC_STEP_CHARGE, true, + battery->pdata->dc_step_chg_val_vfloat[age_step][step]); + } + battery->dc_float_voltage_set = true; + } + + if (battery->step_chg_status < 0) { + pr_info("%s : step input current = %d\n", __func__, + battery->pdata->dc_step_chg_val_iout[age_step][step] / 2); + val.intval = battery->pdata->dc_step_chg_val_iout[age_step][step] / 2; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_DIRECT_CURRENT_MAX, val); + } + + battery->step_chg_status = step; + battery->dc_step_chg_iin_cnt = 0; + + sec_vote(battery->fcc_vote, VOTER_CABLE, true, + battery->pdata->dc_step_chg_val_iout[age_step][step]); + sec_vote_refresh(battery->fcc_vote); + + return true; + } else { + battery->dc_step_chg_iin_cnt = 0; + } + + return false; +} +EXPORT_SYMBOL(sec_bat_check_dc_step_charging); + +int sec_dc_step_charging_dt(struct sec_battery_info *battery, struct device *dev) +{ + struct device_node *np = dev->of_node; + int ret = 0, len = 0; + sec_battery_platform_data_t *pdata = battery->pdata; + unsigned int i = 0, j = 0, dc_step_chg_type = 0; + const u32 *p; + char str[128] = {0,}; + u32 *soc_cond_temp, *vol_cond_temp, *vfloat_temp, *iout_temp; + int age_step = battery->pdata->age_step; + int num_age_step = battery->pdata->num_age_step; + battery->dchg_dc_in_swelling = of_property_read_bool(np, + "battery,dchg_dc_in_swelling"); + pr_info("%s: dchg_dc_in_swelling(%d)\n", __func__, battery->dchg_dc_in_swelling); + + ret = of_property_read_u32(np, "battery,dc_step_chg_step", + &battery->dc_step_chg_step); + if (ret) { + pr_err("%s: dc_step_chg_step is Empty\n", __func__); + battery->dc_step_chg_step = 0; + goto dc_step_charging_dt_error; + } else { + pr_err("%s: dc_step_chg_step is %d\n", + __func__, battery->dc_step_chg_step); + } + + battery->dc_step_chg_type = kcalloc(battery->dc_step_chg_step, sizeof(u32), GFP_KERNEL); + p = of_get_property(np, "battery,dc_step_chg_type", &len); + if (!p) { + pr_info("%s: dc_step_chg_type is Empty\n", __func__); + return -1; + } + len = len / sizeof(u32); + ret = of_property_read_u32_array(np, "battery,dc_step_chg_type", + battery->dc_step_chg_type, len); + if (len != battery->dc_step_chg_step) { + pr_err("%s not match size of dc_step_chg_type: %d\n", __func__, len); + for (i = 1; i < battery->dc_step_chg_step; i++) + battery->dc_step_chg_type[i] = battery->dc_step_chg_type[0]; + dc_step_chg_type = battery->dc_step_chg_type[0]; + } else { + for (i = 0; i < battery->dc_step_chg_step; i++) + dc_step_chg_type |= battery->dc_step_chg_type[i]; + } + + memset(str, 0x0, sizeof(str)); + sprintf(str + strlen(str), "dc_step_chg_type arr :"); + for (i = 0; i < battery->dc_step_chg_step; i++) + sprintf(str + strlen(str), " 0x%x", battery->dc_step_chg_type[i]); + pr_info("%s: %s 0x%x\n", __func__, str, dc_step_chg_type); + + ret = of_property_read_u32(np, "battery,dc_step_chg_charge_power", + &battery->dc_step_chg_charge_power); + if (ret) { + pr_err("%s: dc_step_chg_charge_power is Empty\n", __func__); + battery->dc_step_chg_charge_power = 20000; + } + + if (dc_step_chg_type & STEP_CHARGING_CONDITION_VOLTAGE) { + p = of_get_property(np, "battery,dc_step_chg_cond_vol", &len); + if (!p) { + pr_err("%s: dc_step_chg_cond_vol is Empty, type(0x%X->0x%X)\n", + __func__, dc_step_chg_type, + dc_step_chg_type & ~STEP_CHARGING_CONDITION_VOLTAGE); + for (i = 0; i < battery->dc_step_chg_step; i++) + battery->dc_step_chg_type[i] &= ~STEP_CHARGING_CONDITION_VOLTAGE; + } else { + len = len / sizeof(u32); + pr_info("%s: step(%d) * age_step(%d), dc_step_chg_cond_vol len(%d)\n", + __func__, battery->dc_step_chg_step, num_age_step, len); + + vol_cond_temp = kcalloc(battery->dc_step_chg_step * num_age_step, sizeof(u32), GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,dc_step_chg_cond_vol", + vol_cond_temp, battery->dc_step_chg_step * num_age_step); + + /* copy buff to 2d arr */ + pdata->dc_step_chg_cond_vol = kcalloc(num_age_step, sizeof(u32 *), GFP_KERNEL); + for (i = 0; i < num_age_step; i++) { + pdata->dc_step_chg_cond_vol[i] = + kcalloc(battery->dc_step_chg_step, sizeof(u32), GFP_KERNEL); + for (j = 0; j < battery->dc_step_chg_step; j++) + pdata->dc_step_chg_cond_vol[i][j] = + vol_cond_temp[i*battery->dc_step_chg_step + j]; + } + + /* if there are only 1 dimentional array of value, get the same value */ + if (battery->dc_step_chg_step * num_age_step != len) { + pr_err("%s: len of dc_step_chg_cond_vol is not matched\n", __func__); + + ret = of_property_read_u32_array(np, "battery,dc_step_chg_cond_vol", + *pdata->dc_step_chg_cond_vol, battery->dc_step_chg_step); + + for (i = 1; i < num_age_step; i++) { + for (j = 0; j < battery->dc_step_chg_step; j++) + pdata->dc_step_chg_cond_vol[i][j] = + pdata->dc_step_chg_cond_vol[0][j]; + } + } + + /* debug log */ + for (i = 0; i < num_age_step; i++) { + memset(str, 0x0, sizeof(str)); + sprintf(str + strlen(str), "vol arr[%d]:", i); + for (j = 0; j < battery->dc_step_chg_step; j++) + sprintf(str + strlen(str), " %d", pdata->dc_step_chg_cond_vol[i][j]); + pr_info("%s: %s\n", __func__, str); + } + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + len = len / sizeof(u32); + pr_info("%s: step(%d) * age_step(%d), dc_step_chg_cond_vol_sub len(%d)\n", + __func__, battery->dc_step_chg_step, num_age_step, len); + + vol_cond_temp = kcalloc(battery->dc_step_chg_step * num_age_step, sizeof(u32), GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,dc_step_chg_cond_vol_sub", + vol_cond_temp, battery->dc_step_chg_step * num_age_step); + + /* copy buff to 2d arr */ + pdata->dc_step_chg_cond_vol_sub = kcalloc(num_age_step, sizeof(u32 *), GFP_KERNEL); + for (i = 0; i < num_age_step; i++) { + pdata->dc_step_chg_cond_vol_sub[i] = + kcalloc(battery->dc_step_chg_step, sizeof(u32), GFP_KERNEL); + for (j = 0; j < battery->dc_step_chg_step; j++) + pdata->dc_step_chg_cond_vol_sub[i][j] = + vol_cond_temp[i*battery->dc_step_chg_step + j]; + } + + /* if there are only 1 dimentional array of value, get the same value */ + if (battery->dc_step_chg_step * num_age_step != len) { + pr_err("%s: len of dc_step_chg_cond_vol_sub is not matched\n", __func__); + + ret = of_property_read_u32_array(np, "battery,dc_step_chg_cond_vol_sub", + *pdata->dc_step_chg_cond_vol_sub, battery->dc_step_chg_step); + + for (i = 1; i < num_age_step; i++) { + for (j = 0; j < battery->dc_step_chg_step; j++) + pdata->dc_step_chg_cond_vol_sub[i][j] = + pdata->dc_step_chg_cond_vol_sub[0][j]; + } + } + + /* debug log */ + for (i = 0; i < num_age_step; i++) { + memset(str, 0x0, sizeof(str)); + sprintf(str + strlen(str), "vol_sub arr[%d]:", i); + for (j = 0; j < battery->dc_step_chg_step; j++) + sprintf(str + strlen(str), " %d", pdata->dc_step_chg_cond_vol_sub[i][j]); + pr_info("%s: %s\n", __func__, str); + } +#endif + if (ret) { + pr_info("%s : dc_step_chg_cond_vol read fail\n", __func__); + for (i = 0; i < battery->dc_step_chg_step; i++) + battery->dc_step_chg_type[i] &= ~STEP_CHARGING_CONDITION_VOLTAGE; + } + kfree(vol_cond_temp); + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + ret = of_property_read_u32(np, "battery,dc_step_cond_v_margin_main", + &battery->pdata->dc_step_cond_v_margin_main); + if (ret) + battery->pdata->dc_step_cond_v_margin_main = 0; + + ret = of_property_read_u32(np, "battery,dc_step_cond_v_margin_sub", + &battery->pdata->dc_step_cond_v_margin_sub); + if (ret) + battery->pdata->dc_step_cond_v_margin_sub = 0; + + ret = of_property_read_u32(np, "battery,sc_vbat_thresh_main", + &battery->pdata->sc_vbat_thresh_main); + if (ret) + battery->pdata->sc_vbat_thresh_main = 4420; + + ret = of_property_read_u32(np, "battery,sc_vbat_thresh_sub", + &battery->pdata->sc_vbat_thresh_sub); + if (ret) + battery->pdata->sc_vbat_thresh_sub = battery->pdata->sc_vbat_thresh_main; +#endif + } + } + + if (dc_step_chg_type & STEP_CHARGING_CONDITION_SOC || + dc_step_chg_type & STEP_CHARGING_CONDITION_SOC_INIT_ONLY) { + p = of_get_property(np, "battery,dc_step_chg_cond_soc", &len); + if (!p) { + pr_err("%s: dc_step_chg_cond_soc is Empty, type(0x%X->0x%x)\n", + __func__, dc_step_chg_type, + dc_step_chg_type & ~(STEP_CHARGING_CONDITION_SOC | + STEP_CHARGING_CONDITION_SOC_INIT_ONLY)); + for (i = 0; i < battery->dc_step_chg_step; i++) + battery->dc_step_chg_type[i] &= ~(STEP_CHARGING_CONDITION_SOC | + STEP_CHARGING_CONDITION_SOC_INIT_ONLY); + } else { + len = len / sizeof(u32); + pr_info("%s: step(%d) * age_step(%d), dc_step_chg_cond_soc len(%d)\n", + __func__, battery->dc_step_chg_step, num_age_step, len); + + /* get dt to buff */ + soc_cond_temp = kcalloc(battery->dc_step_chg_step * num_age_step, sizeof(u32), GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,dc_step_chg_cond_soc", + soc_cond_temp, battery->dc_step_chg_step * num_age_step); + + /* copy buff to 2d arr */ + pdata->dc_step_chg_cond_soc = kcalloc(num_age_step, sizeof(u32 *), GFP_KERNEL); + for (i = 0; i < num_age_step; i++) { + pdata->dc_step_chg_cond_soc[i] = + kcalloc(battery->dc_step_chg_step, sizeof(u32), GFP_KERNEL); + for (j = 0; j < battery->dc_step_chg_step; j++) + pdata->dc_step_chg_cond_soc[i][j] = soc_cond_temp[i*battery->dc_step_chg_step + j]; + } + + /* if there are only 1 dimentional array of value, get the same value */ + if (battery->dc_step_chg_step * num_age_step != len) { + pr_err("%s: len of dc_step_chg_cond_soc is not matched\n", __func__); + + ret = of_property_read_u32_array(np, "battery,dc_step_chg_cond_soc", + *pdata->dc_step_chg_cond_soc, battery->dc_step_chg_step); + + for (i = 1; i < num_age_step; i++) { + for (j = 0; j < battery->dc_step_chg_step; j++) + pdata->dc_step_chg_cond_soc[i][j] = pdata->dc_step_chg_cond_soc[0][j]; + } + } + + /* debug log */ + for (i = 0; i < num_age_step; i++) { + memset(str, 0x0, sizeof(str)); + sprintf(str + strlen(str), "soc arr[%d]:", i); + for (j = 0; j < battery->dc_step_chg_step; j++) + sprintf(str + strlen(str), " %d", pdata->dc_step_chg_cond_soc[i][j]); + pr_info("%s: %s\n", __func__, str); + } + + if (ret) { + pr_info("%s : dc_step_chg_cond_soc read fail\n", __func__); + for (i = 0; i < battery->dc_step_chg_step; i++) + battery->dc_step_chg_type[i] &= ~STEP_CHARGING_CONDITION_SOC; + } + + kfree(soc_cond_temp); + + if (dc_step_chg_type & STEP_CHARGING_CONDITION_SOC && + dc_step_chg_type & STEP_CHARGING_CONDITION_SOC_INIT_ONLY) { + pr_info("%s : do not set SOC and SOC_INIT_ONLY at the same time\n", __func__); + for (i = 0; i < battery->dc_step_chg_step; i++) + battery->dc_step_chg_type[i] &= ~STEP_CHARGING_CONDITION_SOC; + } + } + } + + if (dc_step_chg_type & STEP_CHARGING_CONDITION_FLOAT_VOLTAGE) { + p = of_get_property(np, "battery,dc_step_chg_val_vfloat", &len); + if (!p) { + pr_err("%s: dc_step_chg_val_vfloat is Empty, type(0x%X->0x%x)\n", + __func__, dc_step_chg_type, + dc_step_chg_type & ~STEP_CHARGING_CONDITION_FLOAT_VOLTAGE); + for (i = 0; i < battery->dc_step_chg_step; i++) + battery->dc_step_chg_type[i] &= ~STEP_CHARGING_CONDITION_FLOAT_VOLTAGE; + } else { + ret = of_property_read_u32(np, "battery,dc_step_chg_cond_v_margin", + &battery->pdata->dc_step_chg_cond_v_margin); + if (ret) + battery->pdata->dc_step_chg_cond_v_margin = DIRECT_CHARGING_FLOAT_VOLTAGE_MARGIN; + + pr_err("%s: dc_step_chg_cond_v_margin is %d\n", + __func__, battery->pdata->dc_step_chg_cond_v_margin); + + len = len / sizeof(u32); + pr_info("%s: step(%d) * age_step(%d), dc_step_chg_val_vfloat len(%d)\n", + __func__, battery->dc_step_chg_step, num_age_step, len); + + vfloat_temp = kcalloc(battery->dc_step_chg_step * num_age_step, sizeof(u32), GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,dc_step_chg_val_vfloat", + vfloat_temp, battery->dc_step_chg_step * num_age_step); + + /* copy buff to 2d arr */ + pdata->dc_step_chg_val_vfloat = kcalloc(num_age_step, sizeof(u32 *), GFP_KERNEL); + for (i = 0; i < num_age_step; i++) { + pdata->dc_step_chg_val_vfloat[i] = + kcalloc(battery->dc_step_chg_step, sizeof(u32), GFP_KERNEL); + for (j = 0; j < battery->dc_step_chg_step; j++) + pdata->dc_step_chg_val_vfloat[i][j] = + vfloat_temp[i*battery->dc_step_chg_step + j]; + } + + /* if there are only 1 dimentional array of value, get the same value */ + if (battery->dc_step_chg_step * num_age_step != len) { + pr_err("%s: len of dc_step_chg_val_vfloat is not matched\n", __func__); + + ret = of_property_read_u32_array(np, "battery,dc_step_chg_val_vfloat", + *pdata->dc_step_chg_val_vfloat, battery->dc_step_chg_step); + + for (i = 1; i < num_age_step; i++) { + for (j = 0; j < battery->dc_step_chg_step; j++) + pdata->dc_step_chg_val_vfloat[i][j] = + pdata->dc_step_chg_val_vfloat[0][j]; + } + } + /* debug log */ + for (i = 0; i < num_age_step; i++) { + memset(str, 0x0, sizeof(str)); + sprintf(str + strlen(str), "vfloat arr[%d]:", i); + for (j = 0; j < battery->dc_step_chg_step; j++) + sprintf(str + strlen(str), " %d", pdata->dc_step_chg_val_vfloat[i][j]); + pr_info("%s: %s\n", __func__, str); + } + + if (ret) { + pr_info("%s : dc_step_chg_val_vfloat read fail\n", __func__); + for (i = 0; i < battery->dc_step_chg_step; i++) + battery->dc_step_chg_type[i] &= ~STEP_CHARGING_CONDITION_FLOAT_VOLTAGE; + } + kfree(vfloat_temp); + + pdata->dc_step_chg_vol_offset = kcalloc(battery->dc_step_chg_step, sizeof(u32), GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,dc_step_chg_vol_offset", + pdata->dc_step_chg_vol_offset, battery->dc_step_chg_step); + if (ret) { + pr_info("%s: dc_step_chg_vol_offset is empty\n", __func__); + /* Fill-up use one-dimensional offset table */ + for (j = 0; j < battery->dc_step_chg_step; j++) + if (pdata->dc_step_chg_val_vfloat[0][j] > battery->pdata->chg_float_voltage) + pdata->dc_step_chg_vol_offset[j] = + pdata->dc_step_chg_val_vfloat[0][j] - + battery->pdata->chg_float_voltage; + } + + memset(str, 0x0, sizeof(str)); + sprintf(str + strlen(str), "dc_step_chg_vol_offset arr :"); + for (i = 0; i < battery->dc_step_chg_step; i++) + sprintf(str + strlen(str), " %d", pdata->dc_step_chg_vol_offset[i]); + pr_info("%s: %s\n", __func__, str); + } + } + + p = of_get_property(np, "battery,dc_step_chg_val_iout", &len); + if (!p) { + pr_err("%s: dc_step_chg_val_iout is Empty\n", __func__); + for (i = 0; i < battery->dc_step_chg_step; i++) + battery->dc_step_chg_type[i] = 0; + return -1; + } else { + len = len / sizeof(u32); + pr_info("%s: step(%d) * age_step(%d), dc_step_chg_val_iout len(%d)\n", + __func__, battery->dc_step_chg_step, num_age_step, len); + + iout_temp = kcalloc(battery->dc_step_chg_step * num_age_step, sizeof(u32), GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,dc_step_chg_val_iout", + iout_temp, battery->dc_step_chg_step * num_age_step); + + /* copy buff to 2d arr */ + pdata->dc_step_chg_val_iout = kcalloc(num_age_step, sizeof(u32 *), GFP_KERNEL); + for (i = 0; i < num_age_step; i++) { + pdata->dc_step_chg_val_iout[i] = + kcalloc(battery->dc_step_chg_step, sizeof(u32), GFP_KERNEL); + for (j = 0; j < battery->dc_step_chg_step; j++) + pdata->dc_step_chg_val_iout[i][j] = iout_temp[i*battery->dc_step_chg_step + j]; + } + + /* if there are only 1 dimentional array of value, get the same value */ + if (battery->dc_step_chg_step * num_age_step != len) { + pr_err("%s: len of dc_step_chg_val_iout is not matched\n", __func__); + + ret = of_property_read_u32_array(np, "battery,dc_step_chg_val_iout", + *pdata->dc_step_chg_val_iout, battery->dc_step_chg_step); + + for (i = 1; i < num_age_step; i++) { + for (j = 0; j < battery->dc_step_chg_step; j++) + pdata->dc_step_chg_val_iout[i][j] = pdata->dc_step_chg_val_iout[0][j]; + } + } + + /* debug log */ + for (i = 0; i < num_age_step; i++) { + memset(str, 0x0, sizeof(str)); + sprintf(str + strlen(str), "iout arr[%d]:", i); + for (j = 0; j < battery->dc_step_chg_step; j++) + sprintf(str + strlen(str), " %d", pdata->dc_step_chg_val_iout[i][j]); + pr_info("%s: %s\n", __func__, str); + } + + if (ret) { + pr_info("%s : dc_step_chg_val_iout read fail\n", __func__); + } + kfree(iout_temp); + } + + if ((dc_step_chg_type & STEP_CHARGING_CONDITION_INPUT_CURRENT) || + (dc_step_chg_type & STEP_CHARGING_CONDITION_FG_CURRENT)) { + p = of_get_property(np, "battery,dc_step_chg_cond_iin", &len); + if (!p) { + pr_info("%s: dc_step_chg_cond_iin is Empty, set default (Iout / 2)\n", __func__); + pdata->dc_step_chg_cond_iin = + kcalloc(battery->dc_step_chg_step, sizeof(u32), GFP_KERNEL); + for (i = 0; i < (battery->dc_step_chg_step - 1); i++) { + pdata->dc_step_chg_cond_iin[i] = pdata->dc_step_chg_val_iout[age_step][i+1] / 2; + pr_info("%s: Condition Iin [step %d] %dmA", + __func__, i, pdata->dc_step_chg_cond_iin[i]); + } + pdata->dc_step_chg_cond_iin[i] = 0; + } else { + len = len / sizeof(u32); + + if (len != battery->dc_step_chg_step) { +/* [dchg] TODO: do some error handling */ + pr_err("%s: len of dc_step_chg_cond_iin is not matched, len(%d/%d)\n", + __func__, len, battery->dc_step_chg_step); + } + + pdata->dc_step_chg_cond_iin = kcalloc(len, sizeof(u32), GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,dc_step_chg_cond_iin", + pdata->dc_step_chg_cond_iin, len); + if (ret) { + pr_info("%s : dc_step_chg_cond_iin read fail\n", __func__); + for (i = 0; i < battery->dc_step_chg_step; i++) + battery->dc_step_chg_type[i] &= ~STEP_CHARGING_CONDITION_INPUT_CURRENT; + } + } + + ret = of_property_read_u32(np, "battery,dc_step_chg_iin_check_cnt", + &battery->pdata->dc_step_chg_iin_check_cnt); + if (ret) { + pr_err("%s: dc_step_chg_iin_check_cnt is Empty\n", __func__); + battery->pdata->dc_step_chg_iin_check_cnt = 2; + } else { + pr_err("%s: dc_step_chg_iin_check_cnt is %d\n", + __func__, battery->pdata->dc_step_chg_iin_check_cnt); + } + } + + // print dc step charging information + for (i = 0; i < battery->dc_step_chg_step; i++) { + memset(str, 0x0, sizeof(str)); + if (battery->dc_step_chg_type[i] & STEP_CHARGING_CONDITION_VOLTAGE) + sprintf(str + strlen(str), "cond_vol: %dmV, ", pdata->dc_step_chg_cond_vol[age_step][i]); + if (battery->dc_step_chg_type[i] & STEP_CHARGING_CONDITION_SOC) + sprintf(str + strlen(str), "cond_soc: %d%%, ", pdata->dc_step_chg_cond_soc[age_step][i]); + if (battery->dc_step_chg_type[i] & STEP_CHARGING_CONDITION_INPUT_CURRENT) + sprintf(str + strlen(str), "cond_iin: %dmA, ", pdata->dc_step_chg_cond_iin[i]); + if (battery->dc_step_chg_type[i] & STEP_CHARGING_CONDITION_FLOAT_VOLTAGE) + sprintf(str + strlen(str), "vfloat: %dmV, ", pdata->dc_step_chg_val_vfloat[age_step][i]); + + sprintf(str + strlen(str), "iout: %dmA,", pdata->dc_step_chg_val_iout[age_step][i]); + pr_info("%s : step [%d] %s\n", __func__, i, str); + } + + return 0; + +dc_step_charging_dt_error: + return -1; +} /* sec_dc_step_charging_dt */ +#endif + +void sec_bat_set_aging_info_step_charging(struct sec_battery_info *battery) +{ +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + union power_supply_propval val; + int i = 0; + unsigned int max_fv = 0; + int float_volt; +#endif + int age_step = battery->pdata->age_step; + +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + i = (battery->step_chg_status < 0 ? 0 : battery->step_chg_status); + if (!battery->dc_step_chg_type[i]) { + pr_info("%s : invalid dc step chg type\n", __func__); + return; + } +#endif + + if (battery->step_chg_type) { + if (battery->step_chg_type & STEP_CHARGING_CONDITION_FLOAT_VOLTAGE) + battery->pdata->step_chg_vfloat[age_step][battery->step_chg_step-1] = + battery->pdata->chg_float_voltage; + + dev_info(battery->dev, "%s: float_v(%d)\n", + __func__, battery->pdata->step_chg_vfloat[age_step][battery->step_chg_step-1]); + } +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + for (i = 0; i < battery->dc_step_chg_step; i++) { + float_volt = battery->pdata->dc_step_chg_vol_offset[i] + battery->pdata->chg_float_voltage; + + if (battery->dc_step_chg_type[i] & STEP_CHARGING_CONDITION_FLOAT_VOLTAGE) + if (battery->pdata->dc_step_chg_val_vfloat[age_step][i] > float_volt) + battery->pdata->dc_step_chg_val_vfloat[age_step][i] = float_volt; + max_fv = max(max_fv, battery->pdata->dc_step_chg_val_vfloat[age_step][i]); + + if (battery->dc_step_chg_type[i] & STEP_CHARGING_CONDITION_VOLTAGE) + if (battery->pdata->dc_step_chg_cond_vol[age_step][i] > float_volt) + battery->pdata->dc_step_chg_cond_vol[age_step][i] = float_volt; + } + + for (i = 0; i < battery->dc_step_chg_step; i++) { + dev_info(battery->dev, "%s: cond_vol: %dmV, vfloat: %dmV, cond_iin: %dmA, iout: %dmA\n", __func__, + battery->dc_step_chg_type[i] & STEP_CHARGING_CONDITION_VOLTAGE ? + battery->pdata->dc_step_chg_cond_vol[age_step][i] : 0, + battery->dc_step_chg_type[i] & STEP_CHARGING_CONDITION_FLOAT_VOLTAGE ? + battery->pdata->dc_step_chg_val_vfloat[age_step][i] : 0, + battery->dc_step_chg_type[i] & STEP_CHARGING_CONDITION_INPUT_CURRENT ? + battery->pdata->dc_step_chg_cond_iin[i] : 0, + battery->pdata->dc_step_chg_val_iout[age_step][i]); + } + + i = (battery->step_chg_status < 0 ? 0 : battery->step_chg_status); + if (battery->dc_step_chg_type[i] & STEP_CHARGING_CONDITION_FLOAT_VOLTAGE) { + val.intval = battery->pdata->dc_step_chg_val_vfloat[age_step][battery->dc_step_chg_step-1]; + psy_do_property(battery->pdata->charger_name, set, + POWER_SUPPLY_EXT_PROP_DIRECT_CONSTANT_CHARGE_VOLTAGE_MAX, val); + } + + sec_vote(battery->dc_fv_vote, VOTER_AGING_STEP, true, max_fv); + + sec_bat_reset_step_charging(battery); + sec_bat_check_dc_step_charging(battery); +#endif +} +EXPORT_SYMBOL(sec_bat_set_aging_info_step_charging); + +void sec_step_charging_dt(struct sec_battery_info *battery, struct device *dev) +{ + struct device_node *np = dev->of_node; + int ret, len; + sec_battery_platform_data_t *pdata = battery->pdata; + unsigned int i = 0, j = 0; + const u32 *p; + char str[128] = {0,}; + u32 *soc_cond_temp, *vfloat_temp, *curr_temp; + int num_age_step = battery->pdata->num_age_step; + + battery->step_charging_skip_lcd_on = of_property_read_bool(np, + "battery,step_charging_skip_lcd_on"); + + battery->step_chg_en_in_factory = of_property_read_bool(np, + "battery,step_chg_en_in_factory"); + + ret = of_property_read_u32(np, "battery,step_chg_step", + &battery->step_chg_step); + if (ret) { + pr_err("%s: step_chg_step is Empty\n", __func__); + battery->step_chg_step = 0; + } else { + pr_err("%s: step_chg_step is %d\n", + __func__, battery->step_chg_step); + } + + ret = of_property_read_u32(np, "battery,step_chg_charge_power", + &battery->step_chg_charge_power); + if (ret) { + pr_err("%s: step_chg_charge_power is Empty\n", __func__); + battery->step_chg_charge_power = 20000; + } + + p = of_get_property(np, "battery,step_chg_cond", &len); + if (!p) { + battery->step_chg_step = 0; + } else { + len = len / sizeof(u32); + pr_info("%s: step(%d) * age_step(%d), step_chg_cond len(%d)\n", + __func__, battery->step_chg_step, num_age_step, len); + + /* get dt to buff */ + soc_cond_temp = kcalloc(battery->step_chg_step * num_age_step, sizeof(u32), GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,step_chg_cond", + soc_cond_temp, battery->step_chg_step * num_age_step); + + /* copy buff to 2d arr */ + pdata->step_chg_cond = kcalloc(num_age_step, sizeof(u32 *), GFP_KERNEL); + for (i = 0; i < num_age_step; i++) { + pdata->step_chg_cond[i] = + kcalloc(battery->step_chg_step, sizeof(u32), GFP_KERNEL); + for (j = 0; j < battery->step_chg_step; j++) + pdata->step_chg_cond[i][j] = soc_cond_temp[i*battery->step_chg_step + j]; + } + + /* if there are only 1 dimentional array of value, get the same value */ + if (battery->step_chg_step * num_age_step != len) { + ret = of_property_read_u32_array(np, "battery,step_chg_cond", + *pdata->step_chg_cond, battery->step_chg_step); + for (i = 0; i < num_age_step; i++) { + for (j = 0; j < battery->step_chg_step; j++) + pdata->step_chg_cond[i][j] = pdata->step_chg_cond[0][j]; + } + } + + /* debug log */ + for (i = 0; i < num_age_step; i++) { + memset(str, 0x0, sizeof(str)); + sprintf(str + strlen(str), "step_chg_cond arr[%d]:", i); + for (j = 0; j < battery->step_chg_step; j++) + sprintf(str + strlen(str), " %d", pdata->step_chg_cond[i][j]); + pr_info("%s: %s\n", __func__, str); + } + + if (ret) { + pr_info("%s : step_chg_cond read fail\n", __func__); + battery->step_chg_step = 0; + } + + kfree(soc_cond_temp); + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + if (battery->step_chg_type & STEP_CHARGING_CONDITION_VOLTAGE) { + /* get dt to buff */ + soc_cond_temp = kcalloc(battery->step_chg_step * num_age_step, sizeof(u32), GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,step_chg_cond_sub", + soc_cond_temp, battery->step_chg_step * num_age_step); + + /* copy buff to 2d arr */ + pdata->step_chg_cond_sub = kcalloc(num_age_step, sizeof(u32 *), GFP_KERNEL); + for (i = 0; i < num_age_step; i++) { + pdata->step_chg_cond_sub[i] = + kcalloc(battery->step_chg_step, sizeof(u32), GFP_KERNEL); + for (j = 0; j < battery->step_chg_step; j++) + pdata->step_chg_cond_sub[i][j] = soc_cond_temp[i*battery->step_chg_step + j]; + } + + /* if there are only 1 dimentional array of value, get the same value */ + if (battery->step_chg_step * num_age_step != len) { + ret = of_property_read_u32_array(np, "battery,step_chg_cond", + *pdata->step_chg_cond_sub, battery->step_chg_step); + for (i = 0; i < num_age_step; i++) { + for (j = 0; j < battery->step_chg_step; j++) + pdata->step_chg_cond_sub[i][j] = pdata->step_chg_cond[0][j]; + } + } + + /* debug log */ + for (i = 0; i < num_age_step; i++) { + memset(str, 0x0, sizeof(str)); + sprintf(str + strlen(str), "step_chg_cond_sub arr[%d]:", i); + for (j = 0; j < battery->step_chg_step; j++) + sprintf(str + strlen(str), " %d", pdata->step_chg_cond_sub[i][j]); + pr_info("%s: %s\n", __func__, str); + } + + if (ret) + pr_info("%s : step_chg_cond_sub read fail\n", __func__); + + kfree(soc_cond_temp); + } +#endif + + p = of_get_property(np, "battery,step_chg_cond_curr", &len); + if (!p) { + pr_err("%s: step_chg_cond_curr is Empty\n", __func__); + } else { + len = len / sizeof(u32); + pdata->step_chg_cond_curr = kcalloc(len, sizeof(u32), GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,step_chg_cond_curr", + pdata->step_chg_cond_curr, len); + if (ret) { + pr_info("%s : step_chg_cond_curr read fail\n", __func__); + battery->step_chg_step = 0; + } + } + + p = of_get_property(np, "battery,step_chg_vfloat", &len); + if (!p) { + pr_err("%s: step_chg_vfloat is Empty\n", __func__); + } else { + len = len / sizeof(u32); + pr_info("%s: step(%d) * age_step(%d), step_chg_vfloat len(%d)\n", + __func__, battery->step_chg_step, num_age_step, len); + + vfloat_temp = kcalloc(battery->step_chg_step * num_age_step, sizeof(u32), GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,step_chg_vfloat", + vfloat_temp, battery->step_chg_step * num_age_step); + + /* copy buff to 2d arr */ + pdata->step_chg_vfloat = kcalloc(num_age_step, sizeof(u32 *), GFP_KERNEL); + for (i = 0; i < num_age_step; i++) { + pdata->step_chg_vfloat[i] = + kcalloc(battery->step_chg_step, sizeof(u32), GFP_KERNEL); + for (j = 0; j < battery->step_chg_step; j++) + pdata->step_chg_vfloat[i][j] = + vfloat_temp[i*battery->step_chg_step + j]; + } + + /* if there are only 1 dimentional array of value, get the same value */ + if (battery->step_chg_step * num_age_step != len) { + ret = of_property_read_u32_array(np, "battery,step_chg_vfloat", + *pdata->step_chg_vfloat, battery->step_chg_step); + + for (i = 1; i < num_age_step; i++) { + for (j = 0; j < battery->step_chg_step; j++) + pdata->step_chg_vfloat[i][j] = pdata->step_chg_vfloat[0][j]; + } + } + + /* debug log */ + for (i = 0; i < num_age_step; i++) { + memset(str, 0x0, sizeof(str)); + sprintf(str + strlen(str), "step_chg_vfloat arr[%d]:", i); + for (j = 0; j < battery->step_chg_step; j++) + sprintf(str + strlen(str), " %d", pdata->step_chg_vfloat[i][j]); + pr_info("%s: %s\n", __func__, str); + } + + if (ret) + pr_info("%s : step_chg_vfloat read fail\n", __func__); + + kfree(vfloat_temp); + } + + p = of_get_property(np, "battery,step_chg_curr", &len); + if (!p) { + pr_err("%s: step_chg_curr is Empty\n", __func__); + } else { + len = len / sizeof(u32); + pr_info("%s: step(%d) * age_step(%d), step_chg_curr len(%d)\n", + __func__, battery->step_chg_step, num_age_step, len); + + curr_temp = kcalloc(battery->step_chg_step * num_age_step, sizeof(u32), GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,step_chg_curr", + curr_temp, battery->step_chg_step * num_age_step); + + /* copy buff to 2d arr */ + pdata->step_chg_curr = kcalloc(num_age_step, sizeof(u32 *), GFP_KERNEL); + for (i = 0; i < num_age_step; i++) { + pdata->step_chg_curr[i] = + kcalloc(battery->step_chg_step, sizeof(u32), GFP_KERNEL); + for (j = 0; j < battery->step_chg_step; j++) + pdata->step_chg_curr[i][j] = curr_temp[i*battery->step_chg_step + j]; + } + + /* if there are only 1 dimentional array of value, get the same value */ + if (battery->step_chg_step * num_age_step != len) { + ret = of_property_read_u32_array(np, "battery,step_chg_curr", + *pdata->step_chg_curr, battery->step_chg_step); + + for (i = 1; i < num_age_step; i++) { + for (j = 0; j < battery->step_chg_step; j++) + pdata->step_chg_curr[i][j] = pdata->step_chg_curr[0][j]; + } + } + + /* debug log */ + for (i = 0; i < num_age_step; i++) { + memset(str, 0x0, sizeof(str)); + sprintf(str + strlen(str), "step_chg_curr arr[%d]:", i); + for (j = 0; j < battery->step_chg_step; j++) + sprintf(str + strlen(str), " %d", pdata->step_chg_curr[i][j]); + pr_info("%s: %s\n", __func__, str); + } + + if (ret) + pr_info("%s : step_chg_curr read fail\n", __func__); + + kfree(curr_temp); + } + + p = of_get_property(np, "battery,step_chg_cond_soc", &len); + if (!p) { + pr_err("%s: step_chg_cond_soc is Empty\n", __func__); + battery->step_chg_type = + (battery->step_chg_type) & (~STEP_CHARGING_CONDITION_SOC_INIT_ONLY); + } else { + len = len / sizeof(u32); + pr_info("%s: step(%d) * age_step(%d), step_chg_soc len(%d)\n", + __func__, battery->step_chg_step, num_age_step, len); + + if (battery->step_chg_step * num_age_step != len) { + pr_err("%s: mis-match len!!\n", __func__); + battery->step_chg_type = + (battery->step_chg_type) & (~STEP_CHARGING_CONDITION_SOC_INIT_ONLY); + goto err_soc; + } + + curr_temp = kcalloc(battery->step_chg_step * num_age_step, sizeof(u32), GFP_KERNEL); + if (!curr_temp) + goto err_soc; + + ret = of_property_read_u32_array(np, "battery,step_chg_cond_soc", + curr_temp, battery->step_chg_step * num_age_step); + if (ret) { + pr_err("%s: failed to read chg_cond_soc(ret = %d)\n", __func__, ret); + kfree(curr_temp); + battery->step_chg_type = + (battery->step_chg_type) & (~STEP_CHARGING_CONDITION_SOC_INIT_ONLY); + goto err_soc; + } + + pdata->step_chg_cond_soc = kcalloc(num_age_step, sizeof(u32 *), GFP_KERNEL); + for (i = 0; i < num_age_step; i++) { + pdata->step_chg_cond_soc[i] = + kcalloc(battery->step_chg_step, sizeof(u32), GFP_KERNEL); + + for (j = 0; j < battery->step_chg_step; j++) + pdata->step_chg_cond_soc[i][j] = curr_temp[i*battery->step_chg_step + j]; + } + + for (i = 0; i < num_age_step; i++) { + memset(str, 0x0, sizeof(str)); + sprintf(str + strlen(str), "step_chg_cond_soc arr[%d]:", i); + for (j = 0; j < battery->step_chg_step; j++) + sprintf(str + strlen(str), " %d", pdata->step_chg_cond_soc[i][j]); + pr_info("%s: %s\n", __func__, str); + } +err_soc: + pr_info("%s: step_chg_soc end\n", __func__); + } + } +} + +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) +void sec_wpc_step_charging_dt(struct sec_battery_info *battery, struct device *dev) +{ + struct device_node *np = dev->of_node; + int ret, len; + sec_battery_platform_data_t *pdata = battery->pdata; + unsigned int i = 0, j = 0; + const u32 *p; + char str[128] = {0,}; + u32 *soc_cond_temp, *vfloat_temp, *curr_temp; + int num_age_step = battery->pdata->num_age_step; + + ret = of_property_read_u32(np, "battery,wpc_step_chg_step", + &battery->wpc_step_chg_step); + if (ret) { + pr_err("%s: wpc_step_chg_step is Empty\n", __func__); + battery->wpc_step_chg_step = 0; + } else { + pr_err("%s: wpc_step_chg_step is %d\n", + __func__, battery->wpc_step_chg_step); + } + + ret = of_property_read_u32(np, "battery,wpc_step_chg_charge_power", + &battery->wpc_step_chg_charge_power); + if (ret) { + pr_err("%s: wpc_step_chg_charge_power is Empty\n", __func__); + battery->wpc_step_chg_charge_power = 7500; + } + + p = of_get_property(np, "battery,wpc_step_chg_cond", &len); + if (!p) { + battery->wpc_step_chg_step = 0; + } else { + len = len / sizeof(u32); + pr_info("%s: step(%d) * age_step(%d), step_chg_cond len(%d)\n", + __func__, battery->wpc_step_chg_step, num_age_step, len); + + /* get dt to buff */ + soc_cond_temp = kcalloc(battery->wpc_step_chg_step * num_age_step, sizeof(u32), GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,wpc_step_chg_cond", + soc_cond_temp, battery->wpc_step_chg_step * num_age_step); + + /* copy buff to 2d arr */ + pdata->wpc_step_chg_cond = kcalloc(num_age_step, sizeof(u32 *), GFP_KERNEL); + for (i = 0; i < num_age_step; i++) { + pdata->wpc_step_chg_cond[i] = + kcalloc(battery->wpc_step_chg_step, sizeof(u32), GFP_KERNEL); + for (j = 0; j < battery->wpc_step_chg_step; j++) + pdata->wpc_step_chg_cond[i][j] = soc_cond_temp[i*battery->wpc_step_chg_step + j]; + } + + /* if there are only 1 dimentional array of value, get the same value */ + if (battery->wpc_step_chg_step * num_age_step != len) { + ret = of_property_read_u32_array(np, "battery,wpc_step_chg_cond", + *pdata->wpc_step_chg_cond, battery->wpc_step_chg_step); + for (i = 0; i < num_age_step; i++) { + for (j = 0; j < battery->wpc_step_chg_step; j++) + pdata->wpc_step_chg_cond[i][j] = pdata->wpc_step_chg_cond[0][j]; + } + } + + /* debug log */ + for (i = 0; i < num_age_step; i++) { + memset(str, 0x0, sizeof(str)); + sprintf(str + strlen(str), "wpc_step_chg_cond arr[%d]:", i); + for (j = 0; j < battery->wpc_step_chg_step; j++) + sprintf(str + strlen(str), " %d", pdata->wpc_step_chg_cond[i][j]); + pr_info("%s: %s\n", __func__, str); + } + + if (ret) { + pr_info("%s : wpc_step_chg_cond read fail\n", __func__); + battery->wpc_step_chg_step = 0; + } + + kfree(soc_cond_temp); + + p = of_get_property(np, "battery,wpc_step_chg_cond_curr", &len); + if (!p) { + pr_err("%s: wpc_step_chg_cond_curr is Empty\n", __func__); + } else { + len = len / sizeof(u32); + pdata->wpc_step_chg_cond_curr = kcalloc(len, sizeof(u32), GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,wpc_step_chg_cond_curr", + pdata->wpc_step_chg_cond_curr, len); + if (ret) { + pr_info("%s : wpc_step_chg_cond_curr read fail\n", __func__); + battery->wpc_step_chg_step = 0; + } + } + + p = of_get_property(np, "battery,wpc_step_chg_vfloat", &len); + if (!p) { + pr_err("%s: wpc_step_chg_vfloat is Empty\n", __func__); + } else { + len = len / sizeof(u32); + pr_info("%s: step(%d) * age_step(%d), wpc_step_chg_vfloat len(%d)\n", + __func__, battery->wpc_step_chg_step, num_age_step, len); + + vfloat_temp = kcalloc(battery->wpc_step_chg_step * num_age_step, sizeof(u32), GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,wpc_step_chg_vfloat", + vfloat_temp, battery->wpc_step_chg_step * num_age_step); + + /* copy buff to 2d arr */ + pdata->wpc_step_chg_vfloat = kcalloc(num_age_step, sizeof(u32 *), GFP_KERNEL); + for (i = 0; i < num_age_step; i++) { + pdata->wpc_step_chg_vfloat[i] = + kcalloc(battery->wpc_step_chg_step, sizeof(u32), GFP_KERNEL); + for (j = 0; j < battery->wpc_step_chg_step; j++) + pdata->wpc_step_chg_vfloat[i][j] = + vfloat_temp[i*battery->wpc_step_chg_step + j]; + } + + /* if there are only 1 dimentional array of value, get the same value */ + if (battery->wpc_step_chg_step * num_age_step != len) { + ret = of_property_read_u32_array(np, "battery,wpc_step_chg_vfloat", + *pdata->wpc_step_chg_vfloat, battery->wpc_step_chg_step); + + for (i = 1; i < num_age_step; i++) { + for (j = 0; j < battery->wpc_step_chg_step; j++) + pdata->wpc_step_chg_vfloat[i][j] = pdata->wpc_step_chg_vfloat[0][j]; + } + } + + /* debug log */ + for (i = 0; i < num_age_step; i++) { + memset(str, 0x0, sizeof(str)); + sprintf(str + strlen(str), "wpc_step_chg_vfloat arr[%d]:", i); + for (j = 0; j < battery->wpc_step_chg_step; j++) + sprintf(str + strlen(str), " %d", pdata->wpc_step_chg_vfloat[i][j]); + pr_info("%s: %s\n", __func__, str); + } + + if (ret) + pr_info("%s : wpc_step_chg_vfloat read fail\n", __func__); + + kfree(vfloat_temp); + } + + p = of_get_property(np, "battery,wpc_step_chg_curr", &len); + if (!p) { + pr_err("%s: wpc_step_chg_curr is Empty\n", __func__); + } else { + len = len / sizeof(u32); + pr_info("%s: step(%d) * age_step(%d), wpc_step_chg_curr len(%d)\n", + __func__, battery->wpc_step_chg_step, num_age_step, len); + + curr_temp = kcalloc(battery->wpc_step_chg_step * num_age_step, sizeof(u32), GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,wpc_step_chg_curr", + curr_temp, battery->wpc_step_chg_step * num_age_step); + + /* copy buff to 2d arr */ + pdata->wpc_step_chg_curr = kcalloc(num_age_step, sizeof(u32 *), GFP_KERNEL); + for (i = 0; i < num_age_step; i++) { + pdata->wpc_step_chg_curr[i] = + kcalloc(battery->wpc_step_chg_step, sizeof(u32), GFP_KERNEL); + for (j = 0; j < battery->wpc_step_chg_step; j++) + pdata->wpc_step_chg_curr[i][j] = curr_temp[i*battery->wpc_step_chg_step + j]; + } + + /* if there are only 1 dimentional array of value, get the same value */ + if (battery->wpc_step_chg_step * num_age_step != len) { + ret = of_property_read_u32_array(np, "battery,wpc_step_chg_curr", + *pdata->wpc_step_chg_curr, battery->wpc_step_chg_step); + + for (i = 1; i < num_age_step; i++) { + for (j = 0; j < battery->wpc_step_chg_step; j++) + pdata->wpc_step_chg_curr[i][j] = pdata->wpc_step_chg_curr[0][j]; + } + } + + /* debug log */ + for (i = 0; i < num_age_step; i++) { + memset(str, 0x0, sizeof(str)); + sprintf(str + strlen(str), "wpc_step_chg_curr arr[%d]:", i); + for (j = 0; j < battery->wpc_step_chg_step; j++) + sprintf(str + strlen(str), " %d", pdata->wpc_step_chg_curr[i][j]); + pr_info("%s: %s\n", __func__, str); + } + + if (ret) + pr_info("%s : wpc_step_chg_curr read fail\n", __func__); + + kfree(curr_temp); + } + } +} +#endif + +void sec_step_charging_init(struct sec_battery_info *battery, struct device *dev) +{ + struct device_node *np = dev->of_node; + int ret; + + battery->step_chg_status = -1; + + ret = of_property_read_u32(np, "battery,step_chg_type", + &battery->step_chg_type); + pr_err("%s: step_chg_type 0x%x\n", __func__, battery->step_chg_type); + if (ret) { + pr_err("%s: step_chg_type is Empty\n", __func__); + battery->step_chg_type = 0; + } + + if (battery->step_chg_type) + sec_step_charging_dt(battery, dev); +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) + ret = of_property_read_u32(np, "battery,wpc_step_chg_type", + &battery->wpc_step_chg_type); + pr_err("%s: wpc_step_chg_type 0x%x\n", __func__, battery->wpc_step_chg_type); + if (ret) { + pr_err("%s: wpc_step_chg_type is Empty\n", __func__); + battery->wpc_step_chg_type = 0; + } + + if (battery->wpc_step_chg_type) + sec_wpc_step_charging_dt(battery, dev); +#endif +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + sec_dc_step_charging_dt(battery, dev); +#endif +} +EXPORT_SYMBOL(sec_step_charging_init); diff --git a/drivers/battery/core/Kconfig b/drivers/battery/core/Kconfig new file mode 100644 index 000000000000..2456509bfb45 --- /dev/null +++ b/drivers/battery/core/Kconfig @@ -0,0 +1,93 @@ +# +# SB Core +# + +menu "SB Core configs" + +config SB_CORE + tristate "sb-core" + default n + help + Say Y here to enable. + support for sec battery core API. + (module: pq/notify/sysfs/vote) + refer to API headers. + +config SB_PQUEUE + bool "sb-pqueue" + default n + help + Say Y here to enable. + support for priority queue. + (api: push/pop/remove) + If you use the data type, + you should set the compare function. + refer to sb_pqueue.h + +config SB_NOTIFY + bool "sb-notify" + default n + help + Say Y here to enable. + support for notifier. + (type: probe/exit/external/others event) + refer to sb_notify.h + +config SB_SYSFS + bool "sb-sysfs" + default n + depends on SYSFS + help + Say Y here to enable. + support for common sysfs. + If you want to make sysfs node in battery folder, + please use this API to work. + refer to sb_sysfs.h + +config SB_VOTE + bool "sb-vote" + default n + depends on SB_PQUEUE + help + Say Y here to enable. + support for vote. + (set/get/find) + refer to sb_voter.h + +config SB_PQUEUE_TEST + tristate "KUnit test for sb_pqueue_test" + depends on SEC_KUNIT + help + Say Y here to enable + support to kunit test. + for kunitest + for sb_pqueue_test + +config SB_NOTIFY_TEST + tristate "KUnit test for sb_notify_test" + depends on SEC_KUNIT + help + Say Y here to enable + support to kunit test. + for kunitest + for sb_notify_test + +config SB_SYSFS_TEST + tristate "KUnit test for sb_sysfs_test" + depends on SEC_KUNIT + help + Say Y here to enable + support to kunit test. + for kunitest + for sb_sysfs_test + +config SB_VOTE_TEST + tristate "KUnit test for sb_vote_test" + depends on SEC_KUNIT + help + Say Y here to enable + support to kunit test. + for kunitest + for sb_vote_test + +endmenu diff --git a/drivers/battery/core/Makefile b/drivers/battery/core/Makefile new file mode 100644 index 000000000000..40f832329913 --- /dev/null +++ b/drivers/battery/core/Makefile @@ -0,0 +1,15 @@ +obj-$(CONFIG_SB_CORE) += sb-core.o +sb-core-$(CONFIG_SB_CORE) += sb_core.o +sb-core-$(CONFIG_SB_PQUEUE) += sb_pqueue.o +sb-core-$(CONFIG_SB_NOTIFY) += sb_notify.o +sb-core-$(CONFIG_SB_SYSFS) += sb_sysfs.o +sb-core-$(CONFIG_SB_VOTE) += sb_vote.o + +ifeq ($(CONFIG_SEC_KUNIT), y) + GCOV_PROFILE_sb_pqueue.o := $(CONFIG_SEC_KUNIT) + GCOV_PROFILE_sb_notify.o := $(CONFIG_SEC_KUNIT) + GCOV_PROFILE_sb_sysfs.o := $(CONFIG_SEC_KUNIT) + GCOV_PROFILE_sb_vote.o := $(CONFIG_SEC_KUNIT) +endif + +ccflags-y := -Wformat diff --git a/drivers/battery/core/sb_core.c b/drivers/battery/core/sb_core.c new file mode 100644 index 000000000000..986c412c3080 --- /dev/null +++ b/drivers/battery/core/sb_core.c @@ -0,0 +1,30 @@ +/* + * sb_core.c + * Samsung Mobile Battery Core + * + * Copyright (C) 2021 Samsung Electronics + * + * + * 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. + */ + +#include +#include + +static int __init sb_core_init(void) +{ + pr_info("%s:\n", __func__); + return 0; +} +module_init(sb_core_init); + +static void __exit sb_core_exit(void) +{ +} +module_exit(sb_core_exit); + +MODULE_DESCRIPTION("Samsung Battery Core"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/battery/core/sb_notify.c b/drivers/battery/core/sb_notify.c new file mode 100644 index 000000000000..d4959ded84df --- /dev/null +++ b/drivers/battery/core/sb_notify.c @@ -0,0 +1,218 @@ +/* + * sb_notify.c + * Samsung Mobile Battery NotifY + * + * Copyright (C) 2021 Samsung Electronics + * + * + * 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. + */ + +#include +#include +#include +#include + +#include + +#define SB_NOTIFY_NAME "sb-notify" + +struct sbn_dev { + const char *name; + enum sb_dev_type type; + + struct notifier_block *nb; + struct list_head list; +}; + +struct sbn { + struct blocking_notifier_head nb_head; + + struct list_head dev_list; + unsigned int dev_count; +}; + +static struct sbn sb_notify; + +struct device *sb_device; +EXPORT_SYMBOL(sb_device); + +static DEFINE_MUTEX(noti_lock); + +static struct sbn_dev *sbn_create_device(const char *name, enum sb_dev_type type) +{ + struct sbn_dev *ndev; + + ndev = kzalloc(sizeof(struct sbn_dev), GFP_KERNEL); + if (!ndev) + return NULL; + + ndev->name = name; + ndev->type = type; + return ndev; +} + +static struct sbn_dev *sbn_find_device(struct notifier_block *nb) +{ + struct sbn_dev *ndev = NULL; + + if (!nb) + return NULL; + + mutex_lock(¬i_lock); + + list_for_each_entry(ndev, &sb_notify.dev_list, list) { + if (ndev->nb == nb) + break; + } + + mutex_unlock(¬i_lock); + + return ndev; +} + +static int sb_notify_init(void) +{ + static bool init_state; + int ret = 0; + + mutex_lock(¬i_lock); + + if (init_state) + goto skip_init; + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + sb_device = sec_device_create(NULL, SB_NOTIFY_NAME); + if (IS_ERR(sb_device)) { + pr_err("%s Failed to create device(%s)!\n", __func__, SB_NOTIFY_NAME); + ret = -ENODEV; + } +#endif + + BLOCKING_INIT_NOTIFIER_HEAD(&(sb_notify.nb_head)); + + INIT_LIST_HEAD(&sb_notify.dev_list); + + init_state = true; + pr_info("%s: init = %d\n", __func__, init_state); + +skip_init: + mutex_unlock(¬i_lock); + return ret; +} + +int sb_notify_call(enum sbn_type ntype, sb_data *ndata) +{ + int ret = 0; + + if (ntype <= SB_NOTIFY_UNKNOWN || ntype >= SB_NOTIFY_MAX) + return -EINVAL; + + mutex_lock(¬i_lock); + + ret = blocking_notifier_call_chain(&(sb_notify.nb_head), + ntype, ndata); + + mutex_unlock(¬i_lock); + + return 0; +} +EXPORT_SYMBOL(sb_notify_call); + +int sb_notify_register(struct notifier_block *nb, notifier_fn_t notifier, + const char *name, enum sb_dev_type type) +{ + struct sbn_dev *ndev; + int ret = 0; + + if (!nb || !notifier || !name) + return -EINVAL; + + if (sb_notify_init() < 0) + return -ENOENT; + + ndev = sbn_create_device(name, type); + if (!ndev) + return -ENOMEM; + + nb->notifier_call = notifier; + nb->priority = 0; + ndev->nb = nb; + + ret = sb_notify_call(SB_NOTIFY_DEV_PROBE, cast_to_sb_pdata(name)); + if (ret < 0) + pr_err("%s: failed to broadcast dev_probe, ret = %d\n", + __func__, ret); + + mutex_lock(¬i_lock); + + ret = blocking_notifier_chain_register(&(sb_notify.nb_head), nb); + if (ret < 0) { + pr_err("%s: failed to register nb(%s, %d)", + __func__, name, ret); + goto skip_register; + } + + if (sb_notify.dev_count > 0) { + struct sbn_dev_list dev_list; + struct sbn_dev *tmp_dev; + + dev_list.list = kcalloc(sb_notify.dev_count, sizeof(char *), GFP_KERNEL); + if (dev_list.list) { + int i = 0; + + list_for_each_entry(tmp_dev, &sb_notify.dev_list, list) { + dev_list.list[i++] = tmp_dev->name; + } + + dev_list.count = sb_notify.dev_count; + nb->notifier_call(nb, SB_NOTIFY_DEV_LIST, cast_to_sb_pdata(&dev_list)); + + kfree(dev_list.list); + } + } + + list_add(&ndev->list, &sb_notify.dev_list); + sb_notify.dev_count++; + + mutex_unlock(¬i_lock); + return ret; + +skip_register: + mutex_unlock(¬i_lock); + kfree(ndev); + return ret; +} +EXPORT_SYMBOL(sb_notify_register); + +int sb_notify_unregister(struct notifier_block *nb) +{ + struct sbn_dev *ndev; + int ret = 0; + + ndev = sbn_find_device(nb); + if (!ndev) + return -ENODEV; + + mutex_lock(¬i_lock); + + list_del(&ndev->list); + sb_notify.dev_count--; + + ret = blocking_notifier_chain_unregister(&sb_notify.nb_head, nb); + if (ret < 0) + pr_err("%s: failed to unregister nb(%s, %d)", + __func__, ndev->name, ret); + + nb->notifier_call = NULL; + nb->priority = -1; + + mutex_unlock(¬i_lock); + + ret = sb_notify_call(SB_NOTIFY_DEV_SHUTDOWN, (void *)ndev->name); + kfree(ndev); + return ret; +} +EXPORT_SYMBOL(sb_notify_unregister); diff --git a/drivers/battery/core/sb_pqueue.c b/drivers/battery/core/sb_pqueue.c new file mode 100644 index 000000000000..8389b742f8f7 --- /dev/null +++ b/drivers/battery/core/sb_pqueue.c @@ -0,0 +1,397 @@ +/* + * sb_pqueue.c + * Samsung Mobile Priority Queue + * + * Copyright (C) 2021 Samsung Electronics + * + * + * 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. + */ + +#include +#include +#include + +#define MAX_SIZE 1024 +#define PQF_USING_ARRAY (PQF_REMOVE | PQF_PRIORITY) + +struct sb_pq_data { + unsigned index : 32; + signed priority : 31; + unsigned enable : 1; + + sb_data *data; +}; + +struct sb_pqueue { + struct mutex lock; + + unsigned int flag; + + struct sb_pq_data **heap; + struct sb_pq_data *data; + unsigned int size; + unsigned int count; + + cmp_func cmp; +}; + +static bool tmp_cmp(sb_data *data1, sb_data *data2) { return true; } +struct sb_pqueue *sb_pq_create(unsigned int flag, unsigned int size, cmp_func cmp) +{ + struct sb_pqueue *pq; + int ret = -ENOMEM; + + if (size > MAX_SIZE) + return ERR_PTR(-EINVAL); + + if (!(flag & PQF_USING_ARRAY) && (cmp == NULL)) + return ERR_PTR(-EINVAL); + + pq = kzalloc(sizeof(struct sb_pqueue), GFP_KERNEL); + if (!pq) + goto err_alloc_pq; + + pq->heap = kcalloc(size, sizeof(struct sb_pq_data *), GFP_KERNEL); + if (!pq->heap) + goto err_alloc_heap; + + pq->data = kcalloc(size, sizeof(struct sb_pq_data), GFP_KERNEL); + if (!pq->data) + goto err_alloc_data; + + pq->flag = flag; + pq->size = size; + pq->count = 0; + pq->cmp = (cmp) ? cmp : tmp_cmp; + + mutex_init(&pq->lock); + + return pq; + +err_alloc_data: + kfree(pq->heap); +err_alloc_heap: + kfree(pq); +err_alloc_pq: + return ERR_PTR(ret); +} +EXPORT_SYMBOL(sb_pq_create); + +void sb_pq_destroy(struct sb_pqueue *pq) +{ + if (pq == NULL) + return; + + mutex_destroy(&pq->lock); + + if (pq->size > 0) { + kfree(pq->data); + kfree(pq->heap); + } + + kfree(pq); +} +EXPORT_SYMBOL(sb_pq_destroy); + +static void swap_pq_data(struct sb_pq_data **heap, unsigned int idx1, unsigned int idx2) +{ + struct sb_pq_data *tmp = heap[idx1]; + + heap[idx1] = heap[idx2]; + heap[idx2] = tmp; + + heap[idx1]->index = idx1; + heap[idx2]->index = idx2; +} + +static bool cmp_pq_data(struct sb_pqueue *pq, unsigned int idx1, unsigned int idx2) +{ + struct sb_pq_data *pd1 = pq->heap[idx1], *pd2 = pq->heap[idx2]; + + if (pd1->priority == pd2->priority) + return pq->cmp(pd1->data, pd2->data); + + return (pd1->priority > pd2->priority); +} + +static int _sb_pq_up(struct sb_pqueue *pq, unsigned int pos) +{ + unsigned int idx, prt_idx; + + idx = pos; + prt_idx = (pos - 1) / 2; + + while ((idx > 0) && cmp_pq_data(pq, idx, prt_idx)) { + swap_pq_data(pq->heap, idx, prt_idx); + + idx = prt_idx; + prt_idx = (idx - 1) / 2; + } + + return idx; +} + +static int _sb_pq_down(struct sb_pqueue *pq, unsigned int pos) +{ + unsigned int idx, chd_idx; + + idx = pos; + chd_idx = (pos * 2) + 1; + + while (chd_idx < pq->count) { + int chd; + + if (chd_idx + 1 == pq->count) + chd = chd_idx; + else + chd = (cmp_pq_data(pq, chd_idx, chd_idx + 1)) ? + (chd_idx) : (chd_idx + 1); + + if (cmp_pq_data(pq, idx, chd)) + break; + + swap_pq_data(pq->heap, idx, chd); + + idx = chd; + chd_idx = (idx * 2) + 1; + } + + return idx; +} + +static sb_data *_sb_pq_pop(struct sb_pqueue *pq) +{ + struct sb_pq_data *pd = NULL; + + if (pq->count <= 0) + return ERR_PTR(-ENOENT); + + pd = pq->heap[0]; + swap_pq_data(pq->heap, 0, --pq->count); + _sb_pq_down(pq, 0); + + pd->enable = false; + return pd->data; +} + +static int _sb_pq_push(struct sb_pqueue *pq, unsigned int idx, sb_data *data) +{ + struct sb_pq_data *pd = NULL; + + if (pq->size <= pq->count) + return -ENOMEM; + + pd = &pq->data[idx]; + pd->data = data; + pd->enable = true; + + pq->heap[pq->count] = pd; + pd->index = pq->count; + + return _sb_pq_up(pq, pq->count++); +} + +static int _sb_pq_remove(struct sb_pqueue *pq, unsigned int idx) +{ + struct sb_pq_data *pd = NULL; + int ret = 0; + + if (pq->count <= idx) + return -EINVAL; + + ret = idx; + if (idx == 0) { + _sb_pq_pop(pq); + return 0; + } + + if (idx == pq->count - 1) { + pd = pq->heap[--pq->count]; + pd->enable = false; + return 0; + } + + pd = pq->heap[idx]; + pd->enable = false; + swap_pq_data(pq->heap, idx, --pq->count); + + ret = _sb_pq_down(pq, idx); + ret = _sb_pq_up(pq, idx); + return ret; +} + + +sb_data *sb_pq_pop(struct sb_pqueue *pq) +{ + sb_data *data = NULL; + + if (pq == NULL) + return ERR_PTR(-EINVAL); + + mutex_lock(&pq->lock); + data = _sb_pq_pop(pq); + mutex_unlock(&pq->lock); + + return data; +} +EXPORT_SYMBOL(sb_pq_pop); + +int sb_pq_push(struct sb_pqueue *pq, unsigned int idx, sb_data *data) +{ + int ret = -ENOMEM; + + if ((pq == NULL) || + (idx >= pq->size) || + (data == NULL)) + return -EINVAL; + + mutex_lock(&pq->lock); + if (!(pq->flag & PQF_USING_ARRAY)) { + idx = 0; + while ((idx < pq->size) && (pq->data[idx].enable)) + idx++; + } + + ret = _sb_pq_push(pq, idx, data); + mutex_unlock(&pq->lock); + + return ret; +} +EXPORT_SYMBOL(sb_pq_push); + +int sb_pq_get_en(struct sb_pqueue *pq, unsigned int idx) +{ + if ((pq == NULL) || (idx >= pq->size)) + return -EINVAL; + + if (!(pq->flag & PQF_USING_ARRAY)) + return -EPERM; + + return pq->data[idx].enable; +} +EXPORT_SYMBOL(sb_pq_get_en); + +int sb_pq_set_pri(struct sb_pqueue *pq, unsigned int idx, int pri) +{ + struct sb_pq_data *pd = NULL; + int ret = 0; + + if ((pq == NULL) || (idx >= pq->size)) + return -EINVAL; + + if (!(pq->flag & PQF_PRIORITY)) + return -EPERM; + + mutex_lock(&pq->lock); + + pd = &pq->data[idx]; + if (!pd->enable) { + pd->priority = pri; + goto skip_set_pri; + } else if (pd->priority == pri) { + goto skip_set_pri; + } + pd->priority = pri; + + ret = _sb_pq_remove(pq, pd->index); + ret = _sb_pq_push(pq, idx, pd->data); + +skip_set_pri: + mutex_unlock(&pq->lock); + return ret; +} +EXPORT_SYMBOL(sb_pq_set_pri); + +int sb_pq_get_pri(struct sb_pqueue *pq, unsigned int idx) +{ + if ((pq == NULL) || (idx >= pq->size)) + return -EINVAL; + + if (!(pq->flag & PQF_PRIORITY)) + return -EPERM; + + return pq->data[idx].priority; +} +EXPORT_SYMBOL(sb_pq_get_pri); + +int sb_pq_set_data(struct sb_pqueue *pq, unsigned int idx, sb_data *data) +{ + struct sb_pq_data *pd = NULL; + int ret = 0; + + if ((pq == NULL) || (idx >= pq->size)) + return -EINVAL; + + if (!(pq->flag & PQF_USING_ARRAY)) + return -EPERM; + + mutex_lock(&pq->lock); + + pd = &pq->data[idx]; + pd->data = data; + + if (!pd->enable) + goto skip_set_data; + + ret = _sb_pq_remove(pq, pd->index); + ret = _sb_pq_push(pq, idx, pd->data); + +skip_set_data: + mutex_unlock(&pq->lock); + return ret; +} +EXPORT_SYMBOL(sb_pq_set_data); + +sb_data *sb_pq_get_data(struct sb_pqueue *pq, unsigned int idx) +{ + if ((pq == NULL) || (idx >= pq->size)) + return ERR_PTR(-EINVAL); + + if (!(pq->flag & PQF_USING_ARRAY)) + return ERR_PTR(-EPERM); + + return pq->data[idx].data; +} +EXPORT_SYMBOL(sb_pq_get_data); + +sb_data *sb_pq_top(struct sb_pqueue *pq) +{ + sb_data *data = NULL; + + if (pq == NULL) + return ERR_PTR(-EINVAL); + + mutex_lock(&pq->lock); + + if (pq->count > 0) + data = pq->heap[0]->data; + else + data = ERR_PTR(-ENOENT); + + mutex_unlock(&pq->lock); + + return data; +} +EXPORT_SYMBOL(sb_pq_top); + +int sb_pq_remove(struct sb_pqueue *pq, unsigned int idx) +{ + int ret = 0; + + if ((pq == NULL) || (idx >= pq->size)) + return -EINVAL; + + if (!(pq->flag & PQF_REMOVE)) + return -EPERM; + + mutex_lock(&pq->lock); + ret = _sb_pq_remove(pq, pq->data[idx].index); + mutex_unlock(&pq->lock); + + return ret; +} +EXPORT_SYMBOL(sb_pq_remove); + diff --git a/drivers/battery/core/sb_sysfs.c b/drivers/battery/core/sb_sysfs.c new file mode 100644 index 000000000000..1275088fd514 --- /dev/null +++ b/drivers/battery/core/sb_sysfs.c @@ -0,0 +1,152 @@ +/* + * sb_sysfs.c + * Samsung Mobile SysFS + * + * Copyright (C) 2021 Samsung Electronics + * + * + * 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. + */ + +#include +#include +#include +#include + +struct sb_sysfs_mod { + const char *name; + void *pdata; + struct device *parent; + + struct device_attribute *attr; + unsigned long size; + unsigned long state; + + struct list_head list; +}; + +static LIST_HEAD(sysfs_list); +static DEFINE_MUTEX(sysfs_lock); + +int sb_sysfs_add_attrs(const char *name, void *pdata, struct device_attribute *attr, unsigned long size) +{ + struct sb_sysfs_mod *sysfs_mod; + + if ((name == NULL) || (pdata == NULL) || + (attr == NULL) || (size <= 0)) + return -EINVAL; + + sysfs_mod = kzalloc(sizeof(struct sb_sysfs_mod), GFP_KERNEL); + if (!sysfs_mod) + return -ENOMEM; + + sysfs_mod->name = name; + sysfs_mod->pdata = pdata; + sysfs_mod->attr = attr; + sysfs_mod->size = size; + + sysfs_mod->parent = NULL; + sysfs_mod->state = 0; + + mutex_lock(&sysfs_lock); + + list_add(&sysfs_mod->list, &sysfs_list); + + mutex_unlock(&sysfs_lock); + + return 0; +} +EXPORT_SYMBOL(sb_sysfs_add_attrs); + +int sb_sysfs_remove_attrs(const char *name) +{ + struct sb_sysfs_mod *sysfs_mod; + bool is_found = false; + + if (name == NULL) + return -EINVAL; + + mutex_lock(&sysfs_lock); + + list_for_each_entry(sysfs_mod, &sysfs_list, list) { + is_found = !strcmp(name, sysfs_mod->name); + if (is_found) + goto skip_loop; + } + +skip_loop: + if (is_found) { + if ((sysfs_mod->parent != NULL) && + (sysfs_mod->state > 0)) { + while (sysfs_mod->state--) + device_remove_file(sysfs_mod->parent, + &sysfs_mod->attr[sysfs_mod->state]); + } + + list_del(&sysfs_mod->list); + kfree(sysfs_mod); + } + + mutex_unlock(&sysfs_lock); + return (is_found) ? 0 : -ENODEV; +} +EXPORT_SYMBOL(sb_sysfs_remove_attrs); + +void *sb_sysfs_get_pdata(const char *name) +{ + struct sb_sysfs_mod *sysfs_mod; + bool is_found = false; + + if (name == NULL) + return ERR_PTR(-EINVAL); + + mutex_lock(&sysfs_lock); + + list_for_each_entry(sysfs_mod, &sysfs_list, list) { + is_found = !strcmp(name, sysfs_mod->name); + if (is_found) + goto skip_loop; + } + +skip_loop: + mutex_unlock(&sysfs_lock); + return (is_found) ? sysfs_mod->pdata : ERR_PTR(-ENODEV); +} +EXPORT_SYMBOL(sb_sysfs_get_pdata); + +int sb_sysfs_create_attrs(struct device *dev) +{ + struct sb_sysfs_mod *sysfs_mod; + int ret = 0; + + if (dev == NULL) + return -EINVAL; + + list_for_each_entry(sysfs_mod, &sysfs_list, list) { + unsigned long i = 0; + + if (sysfs_mod->state == sysfs_mod->size) + continue; + + sysfs_mod->parent = dev; + for (i = sysfs_mod->state; i < sysfs_mod->size; i++) { + if (device_create_file(dev, &sysfs_mod->attr[i])) { + while (i--) + device_remove_file(dev, &sysfs_mod->attr[i]); + + sysfs_mod->state = 0; + break; + } + + sysfs_mod->state++; + } + + ret += sysfs_mod->state; + } + + return ret; +} +EXPORT_SYMBOL(sb_sysfs_create_attrs); + diff --git a/drivers/battery/core/sb_vote.c b/drivers/battery/core/sb_vote.c new file mode 100644 index 000000000000..9521fb133f68 --- /dev/null +++ b/drivers/battery/core/sb_vote.c @@ -0,0 +1,540 @@ +/* + * sb_vote.c + * Samsung Mobile Battery Vote + * + * Copyright (C) 2021 Samsung Electronics + * + * + * 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. + */ + +#include +#include +#include +#include +#include +#include + +enum { + VOTER_INIT = 0, + VOTER_FORCE, + VOTER_MAX, +}; + +struct sb_voter { + const char *name; + sb_data data; + + bool enable; +}; + +struct sb_vote { + const char *name; + int type; + + struct sb_voter *vlist; + int vnum; + + cb_vote cb; + cmp_vote cmp; + + void *pdata; + + struct sb_pqueue *pq; + + struct mutex lock; + struct list_head list; + + struct dentry *root; + struct dentry *status_ent; + struct dentry *force_set_ent; +}; + +static struct dentry *debug_root; +static struct dentry *status_all; +static LIST_HEAD(vote_list); +static DEFINE_MUTEX(vote_lock); + +static void set_voter(struct sb_voter *voter, const char *name, bool en, sb_data data) +{ + voter->name = name; + voter->enable = en; + voter->data = data; +} + +static sb_data check_vote_data(struct sb_vote *vote, sb_data *data) +{ + if (IS_ERR_OR_NULL(data)) + return vote->vlist[vote->vnum - VOTER_MAX + VOTER_INIT].data; + + return *data; +} + +static bool cmp_min(sb_data *vdata1, sb_data *vdata2) +{ + return (cast_sb_pdata(int, vdata1) < cast_sb_pdata(int, vdata2)); +} + +static bool cmp_max(sb_data *vdata1, sb_data *vdata2) +{ + return (cast_sb_pdata(int, vdata1) > cast_sb_pdata(int, vdata2)); +} + +static bool check_vote_cfg(const struct sb_vote_cfg *cfg) +{ + return (cfg != NULL) && + (cfg->voter_list != NULL) && + (cfg->voter_num > 0) && + (cfg->cb != NULL) && + (cfg->type >= SB_VOTE_MIN) && + (cfg->type <= SB_VOTE_DATA) && + ((cfg->type != SB_VOTE_DATA) || (cfg->cmp != NULL)); +} + +static void sb_vote_init_debug(struct sb_vote *vote); +struct sb_vote *sb_vote_create(const struct sb_vote_cfg *vote_cfg, void *pdata, sb_data init_data) +{ + struct sb_vote *vote = NULL; + int ret, i; + + if (!check_vote_cfg(vote_cfg) || (pdata == NULL)) + return ERR_PTR(-EINVAL); + + vote = sb_vote_find(vote_cfg->name); + if (!IS_ERR_OR_NULL(vote)) + return ERR_PTR(-EEXIST); + + mutex_lock(&vote_lock); + + ret = -ENOMEM; + vote = kzalloc(sizeof(struct sb_vote), GFP_KERNEL); + if (!vote) + goto err_vote; + + vote->name = vote_cfg->name; + vote->cb = vote_cfg->cb; + vote->pdata = pdata; + vote->type = vote_cfg->type; + + vote->vnum = vote_cfg->voter_num + VOTER_MAX; + vote->vlist = kcalloc(vote->vnum, sizeof(struct sb_voter), GFP_KERNEL); + if (!vote->vlist) + goto err_vlist; + + switch (vote->type) { + case SB_VOTE_MIN: + vote->cmp = cmp_min; + break; + case SB_VOTE_MAX: + vote->cmp = cmp_max; + break; + case SB_VOTE_EN: + vote->cmp = NULL; + break; + case SB_VOTE_DATA: + vote->cmp = vote_cfg->cmp; + break; + } + + vote->pq = sb_pq_create((PQF_REMOVE | PQF_PRIORITY), vote->vnum, vote->cmp); + if (IS_ERR_OR_NULL(vote->pq)) { + ret = (int)PTR_ERR(vote->pq); + goto err_pq; + } + + /* init voter */ + for (i = 0; i < vote_cfg->voter_num; i++) { + set_voter(&vote->vlist[i], vote_cfg->voter_list[i], false, init_data); + sb_pq_set_pri(vote->pq, i, VOTE_PRI_MIN); + } + + /* i = vote->vnum - VOTER_MAX; */ + i = vote_cfg->voter_num + VOTER_INIT; + set_voter(&vote->vlist[i], "Init", true, init_data); + sb_pq_set_pri(vote->pq, i, VOTE_PRI_MIN - 1); + sb_pq_push(vote->pq, i, &vote->vlist[i].data); + + i = vote_cfg->voter_num + VOTER_FORCE; + set_voter(&vote->vlist[i], "Force", false, init_data); + sb_pq_set_pri(vote->pq, i, VOTE_PRI_MAX + 1); + + mutex_init(&vote->lock); + sb_vote_init_debug(vote); + + /* call cb */ + vote->cb(vote->pdata, check_vote_data(vote, sb_pq_top(vote->pq))); + + pr_info("%s: %s\n", __func__, vote->name); + list_add(&vote->list, &vote_list); + mutex_unlock(&vote_lock); + + return vote; + +err_pq: + kfree(vote->vlist); +err_vlist: + kfree(vote); +err_vote: + mutex_unlock(&vote_lock); + return ERR_PTR(ret); +} +EXPORT_SYMBOL(sb_vote_create); + +void sb_vote_destroy(struct sb_vote *vote) +{ + mutex_lock(&vote_lock); + + pr_info("%s: %s\n", __func__, vote->name); + list_del(&vote->list); + sb_pq_destroy(vote->pq); + kfree(vote->vlist); + debugfs_remove_recursive(vote->root); + mutex_destroy(&vote->lock); + kfree(vote); + + mutex_unlock(&vote_lock); +} +EXPORT_SYMBOL(sb_vote_destroy); + +struct sb_vote *sb_vote_find(const char *name) +{ + struct sb_vote *vote = NULL; + + if (name == NULL) + return NULL; + + mutex_lock(&vote_lock); + list_for_each_entry(vote, &vote_list, list) { + if (strcmp(vote->name, name) == 0) { + mutex_unlock(&vote_lock); + return vote; + } + } + mutex_unlock(&vote_lock); + + return NULL; +} +EXPORT_SYMBOL(sb_vote_find); + +int sb_vote_get(struct sb_vote *vote, int event, sb_data *data) +{ + struct sb_voter *voter; + + if ((vote == NULL) || + (event < 0) || (event >= vote->vnum - VOTER_MAX) || + (data == NULL)) + return -EINVAL; + + mutex_lock(&vote->lock); + + voter = &vote->vlist[event]; + *data = voter->data; + + mutex_unlock(&vote->lock); + + return 0; +} +EXPORT_SYMBOL(sb_vote_get); + +int sb_vote_get_result(struct sb_vote *vote, sb_data *data) +{ + sb_data *ret; + + if ((vote == NULL) || (data == NULL)) + return -EINVAL; + + mutex_lock(&vote->lock); + + ret = sb_pq_top(vote->pq); + if (!IS_ERR_OR_NULL(ret)) + *data = *ret; + + mutex_unlock(&vote->lock); + + return IS_ERR_OR_NULL(ret) ? (PTR_ERR(ret)) : 0; +} +EXPORT_SYMBOL(sb_vote_get_result); + +static bool check_vote_changed(sb_data *old_data, sb_data *new_data) +{ + return ((*old_data) != (*new_data)); +} + +int _sb_vote_set(struct sb_vote *vote, int event, bool en, sb_data data, const char *fname, int line) +{ + struct sb_voter *voter; + sb_data *old_data, *new_data; + int ret = 0; + + if ((vote == NULL) || + (event < 0) || (event >= vote->vnum - VOTER_MAX)) + return -EINVAL; + + mutex_lock(&vote->lock); + + voter = &vote->vlist[event]; + if ((voter->enable == en) && + (voter->data == data)) + goto skip_set; + + old_data = sb_pq_top(vote->pq); + + if (voter->enable) + sb_pq_remove(vote->pq, event); + + voter->enable = en; + voter->data = data; + if (voter->enable) + sb_pq_push(vote->pq, event, &voter->data); + + new_data = sb_pq_top(vote->pq); + + if (check_vote_changed(old_data, new_data)) + ret = vote->cb(vote->pdata, check_vote_data(vote, new_data)); + +skip_set: + mutex_unlock(&vote->lock); + + return ret; +} +EXPORT_SYMBOL(_sb_vote_set); + +int sb_vote_set_pri(struct sb_vote *vote, int event, int pri) +{ + sb_data *old_data, *new_data; + int ret = 0; + + if ((vote == NULL) || + (event < 0) || (event >= vote->vnum - VOTER_MAX) || + (pri < VOTE_PRI_MIN) || (pri > VOTE_PRI_MAX)) + return -EINVAL; + else if (vote->type == SB_VOTE_DATA) + return -EPERM; + + mutex_lock(&vote->lock); + + if (sb_pq_get_pri(vote->pq, event) == pri) + goto skip_pri; + + old_data = sb_pq_top(vote->pq); + + sb_pq_set_pri(vote->pq, event, pri); + + new_data = sb_pq_top(vote->pq); + + if (check_vote_changed(old_data, new_data)) + ret = vote->cb(vote->pdata, check_vote_data(vote, new_data)); + +skip_pri: + mutex_unlock(&vote->lock); + + return ret; +} +EXPORT_SYMBOL(sb_vote_set_pri); + +int sb_vote_refresh(struct sb_vote *vote) +{ + sb_data *data = NULL; + int ret = 0; + + if (vote == NULL) + return -EINVAL; + + mutex_lock(&vote->lock); + + data = sb_pq_top(vote->pq); + if (!IS_ERR_OR_NULL(data)) + ret = vote->cb(vote->pdata, *data); + + mutex_unlock(&vote->lock); + + return ret; +} +EXPORT_SYMBOL(sb_vote_refresh); + +static void show_vote(struct seq_file *m, struct sb_vote *vote) +{ + char *type_str = "Unkonwn"; + bool val_type = true; + struct sb_voter *voter; + sb_data *data; + int i; + + mutex_lock(&vote->lock); + + switch (vote->type) { + case SB_VOTE_MIN: + type_str = "Min"; + break; + case SB_VOTE_MAX: + type_str = "Max"; + break; + case SB_VOTE_EN: + type_str = "En"; + break; + case SB_VOTE_DATA: + default: + type_str = "Data"; + val_type = false; + break; + } + + for (i = 0; i < vote->vnum; i++) { + voter = &vote->vlist[i]; + + if (voter->enable) + seq_printf(m, "%s: %s:\t\t\ten=%d v=%d p=%d\n", + vote->name, + voter->name, + voter->enable, + ((val_type) ? cast_sb_data(int, voter->data) : 0), + sb_pq_get_pri(vote->pq, i)); + } + + data = sb_pq_top(vote->pq); + if (!IS_ERR_OR_NULL(voter)) { + voter = container_of(data, struct sb_voter, data); + + seq_printf(m, "%s: voter=%s type=%s v=%d\n", + vote->name, voter->name, type_str, ((val_type) ? cast_sb_data(int, voter->data) : 0)); + } + + mutex_unlock(&vote->lock); +} + +static int show_all_clients(struct seq_file *m, void *data) +{ + struct sb_vote *vote; + + if (list_empty(&vote_list)) { + seq_puts(m, "No vote\n"); + return 0; + } + + mutex_lock(&vote_lock); + + list_for_each_entry(vote, &vote_list, list) { + show_vote(m, vote); + } + + mutex_unlock(&vote_lock); + + return 0; +} + +static int vote_status_all_open(struct inode *inode, struct file *file) +{ + + return single_open(file, show_all_clients, NULL); +} + +static const struct file_operations vote_status_all_ops = { + .owner = THIS_MODULE, + .open = vote_status_all_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int show_vote_clients(struct seq_file *m, void *data) +{ + struct sb_vote *vote = m->private; + + show_vote(m, vote); + return 0; +} + +static int vote_status_open(struct inode *inode, struct file *file) +{ + struct sb_vote *vote = inode->i_private; + + return single_open(file, show_vote_clients, vote); +} + +static const struct file_operations vote_status_ops = { + .owner = THIS_MODULE, + .open = vote_status_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int force_get(void *data, u64 *val) +{ + struct sb_vote *vote = data; + struct sb_voter *voter; + + voter = &vote->vlist[vote->vnum - VOTER_MAX + VOTER_FORCE]; + *val = voter->enable; + + return 0; +} + +static int force_set(void *data, u64 val) +{ + struct sb_vote *vote = data; + bool en = !!(val); + struct sb_voter *voter; + int idx = 0; + + mutex_lock(&vote->lock); + + idx = vote->vnum - VOTER_MAX + VOTER_FORCE; + voter = &vote->vlist[idx]; + if (voter->enable != en) { + if (en) + sb_pq_push(vote->pq, idx, &voter->data); + else + sb_pq_pop(vote->pq); + } + voter->enable = en; + vote->cb(vote->pdata, check_vote_data(vote, sb_pq_top(vote->pq))); + + mutex_unlock(&vote->lock); + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(vote_force_ops, force_get, force_set, "%lld\n"); + +static void sb_vote_init_debug(struct sb_vote *vote) +{ + if (debug_root == NULL) { + debug_root = debugfs_create_dir("sb-vote", NULL); + if (!debug_root) { + pr_err("Couldn't create debug dir\n"); + } else { + status_all = debugfs_create_file("status_all", + S_IFREG | 0444, + debug_root, NULL, + &vote_status_all_ops); + if (!status_all) + pr_err("Couldn't create status_all dbg file\n"); + } + } + if (debug_root) + vote->root = debugfs_create_dir(vote->name, debug_root); + if (!vote->root) { + pr_err("Couldn't create debug dir %s\n", vote->name); + } else { + struct sb_voter *voter; + + vote->status_ent = debugfs_create_file("status", S_IFREG | 0444, + vote->root, vote, + &vote_status_ops); + if (!vote->status_ent) + pr_err("Couldn't create status dbg file for %s\n", vote->name); + + voter = &vote->vlist[vote->vnum - VOTER_MAX + VOTER_FORCE]; + debugfs_create_u32("force_val", S_IFREG | 0644, + vote->root, cast_sb_data_ptr(int, &voter->data)); + + vote->force_set_ent = debugfs_create_file("force_set", + S_IFREG | 0444, + vote->root, vote, + &vote_force_ops); + if (!vote->force_set_ent) + pr_err("Couldn't create force_set dbg file for %s\n", vote->name); + } +} + diff --git a/drivers/battery/fuelgauge/max77705_fuelgauge/Kconfig b/drivers/battery/fuelgauge/max77705_fuelgauge/Kconfig new file mode 100644 index 000000000000..99d3e8af8067 --- /dev/null +++ b/drivers/battery/fuelgauge/max77705_fuelgauge/Kconfig @@ -0,0 +1,49 @@ +menu "Fuelgauge drivers" + +config FUELGAUGE_DUMMY + bool "dummy fuel gauge driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y here, to enable + support for dummy fuel gauge driver. + This driver source code implemented + skeleton source code for fuel gauge functions. + +config FUELGAUGE_MAX77705 + tristate "MAX77705 fuel gauge driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y or M here, to enable + support for MAXIM MAX77705 fuel gauge driver. + This is fuel-gauge systems for monitoring batteries. + This fuel-gauge can be used in coulomb-counting mode. + +config EN_OOPS + bool "enable oops filter" + default n + help + Say Y here to enable + support for FUELGAUGE_MAX77705 enable oops filter. + MAXIM fuel-gauge only support this option. + some battery data values should be defined. + +config ID_USING_BAT_SUBBAT + bool "battery id using sub bat" + default n + depends on DUAL_BATTERY + help + Say Y here to enable + This is to calculate bat_id using main_bat_id & sub_bat_id. + +config UI_SOC_PROLONGING + bool "Ui Soc 100% Prolonging" + default n + help + Say Y here to enable + Support for UI Soc prolonging. + This is to enable UI Soc prolonging concept. + +endmenu + diff --git a/drivers/battery/fuelgauge/max77705_fuelgauge/Makefile b/drivers/battery/fuelgauge/max77705_fuelgauge/Makefile new file mode 100644 index 000000000000..9c427c8fa427 --- /dev/null +++ b/drivers/battery/fuelgauge/max77705_fuelgauge/Makefile @@ -0,0 +1,7 @@ +obj-$(CONFIG_FUELGAUGE_MAX77705) += max77705-fuelgauge.o +max77705-fuelgauge-$(CONFIG_FUELGAUGE_MAX77705) += max77705_fuelgauge.o + +ifeq ($(CONFIG_SEC_KUNIT), y) +GCOV_PROFILE_max77705_fuelgauge.o := $(CONFIG_SEC_KUNIT) +endif +ccflags-y := -Wformat diff --git a/drivers/battery/fuelgauge/max77705_fuelgauge/max77705_fuelgauge.bzl b/drivers/battery/fuelgauge/max77705_fuelgauge/max77705_fuelgauge.bzl new file mode 100644 index 000000000000..16aab12da0e9 --- /dev/null +++ b/drivers/battery/fuelgauge/max77705_fuelgauge/max77705_fuelgauge.bzl @@ -0,0 +1,10 @@ +ko_list = [ + { + "ko_names" : [ + "drivers/battery/fuelgauge/max77705_fuelgauge/max77705-fuelgauge.ko" + ], + "kunit_test" : [ + "drivers/battery/fuelgauge/max77705_fuelgauge/kunit_test/max77705_fuelgauge_test.ko", + ] + } +] diff --git a/drivers/battery/fuelgauge/max77705_fuelgauge/max77705_fuelgauge.c b/drivers/battery/fuelgauge/max77705_fuelgauge/max77705_fuelgauge.c new file mode 100644 index 000000000000..97ff778df3ee --- /dev/null +++ b/drivers/battery/fuelgauge/max77705_fuelgauge/max77705_fuelgauge.c @@ -0,0 +1,3350 @@ +/* + * max77705_fuelgauge.c + * Samsung max77705 Fuel Gauge Driver + * + * Copyright (C) 2015 Samsung Electronics + * + * + * 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. + */ + +#define DEBUG +/* #define BATTERY_LOG_MESSAGE */ + +#include +#include +#include +#include +#include "max77705_fuelgauge.h" + +#if defined(CONFIG_SEC_KUNIT) +#define __visible_for_testing +#else +#define __visible_for_testing static +#endif + +#ifndef EXPORT_SYMBOL_KUNIT +#define EXPORT_SYMBOL_KUNIT(sym) /* nothing */ +#endif + +static unsigned int __read_mostly lpcharge; +module_param(lpcharge, uint, 0444); + +static enum power_supply_property max77705_fuelgauge_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +bool max77705_fg_fuelalert_init(struct max77705_fuelgauge_data *fuelgauge, + int soc); +static void max77705_fg_periodic_read_power( + struct max77705_fuelgauge_data *fuelgauge); +static void max77705_reset_bat_id(struct max77705_fuelgauge_data *fuelgauge); + +static struct device_attribute max77705_fg_attrs[] = { + MAX77705_FG_ATTR(fg_data), +}; + +static unsigned int max77705_get_lpmode(void) { return lpcharge; } +static int max77705_fg_read_vfsoc(struct max77705_fuelgauge_data *fuelgauge); + +#if !defined(CONFIG_SEC_FACTORY) +static void max77705_fg_adaptation_wa(struct max77705_fuelgauge_data *fuelgauge) +{ + u32 rcomp0; + u32 fullcapnom, fullcaprep, designcap; + u32 temp; + u8 data[2]; + int vfsoc; + struct fg_reset_wa *fg_reset_data = fuelgauge->fg_reset_data; + + if (!fg_reset_data) + return; + + /* check RCOMP0 */ + rcomp0 = max77705_read_word(fuelgauge->i2c, RCOMP_REG); + if ((rcomp0 > (fg_reset_data->rcomp0 * 14 / 10)) || (rcomp0 < (fg_reset_data->rcomp0 * 7 / 10))) { + pr_err("%s: abnormal RCOMP0 (0x%x / 0x%x)\n", __func__, rcomp0, fg_reset_data->rcomp0); + goto set_default_value; + } + + /* check TEMPCO */ + if (max77705_bulk_read(fuelgauge->i2c, TEMPCO_REG, + 2, data) < 0) { + pr_err("%s: Failed to read TEMPCO\n", __func__); + return; + } + /* tempcohot = data[1]; tempcocold = data[0]; */ + temp = (fg_reset_data->tempco & 0xFF00) >> 8; + if ((data[1] > (temp * 14 / 10)) || (data[1] < (temp * 7 / 10))) { + pr_err("%s: abnormal TempCoHot (0x%x / 0x%x)\n", __func__, data[1], temp); + goto set_default_value; + } + + temp = fg_reset_data->tempco & 0x00FF; + if ((data[0] > (temp * 14 / 10)) || (data[0] < (temp * 7 / 10))) { + pr_err("%s: abnormal TempCoCold (0x%x / 0x%x)\n", __func__, data[0], temp); + goto set_default_value; + } + + /* get DESIGNCAP */ + designcap = max77705_read_word(fuelgauge->i2c, DESIGNCAP_REG); + + /* check FULLCAPNOM */ + fullcapnom = max77705_read_word(fuelgauge->i2c, FULLCAP_NOM_REG); + if (fullcapnom > (designcap * 11 / 10)) { + pr_err("%s: abnormal fullcapnom (0x%x / 0x%x)\n", __func__, fullcapnom, designcap); + goto re_calculation_fullcap_nom; + } + + /* check FULLCAPREP */ + fullcaprep = max77705_read_word(fuelgauge->i2c, FULLCAP_REP_REG); + if (fullcaprep > (designcap * 115 / 100)) { + pr_err("%s: abnormal fullcaprep (0x%x / 0x%x)\n", __func__, fullcaprep, designcap); + goto re_calculation_fullcap_rep; + } + + return; + +re_calculation_fullcap_nom: + pr_err("%s: enter re_calculation fullcapnom\n", __func__); + max77705_write_word(fuelgauge->i2c, DPACC_REG, fg_reset_data->dPacc); + max77705_write_word(fuelgauge->i2c, DQACC_REG, fg_reset_data->dQacc); + max77705_write_word(fuelgauge->i2c, FULLCAP_NOM_REG, fg_reset_data->fullcapnom); + temp = max77705_read_word(fuelgauge->i2c, LEARN_CFG_REG); + temp &= 0xFF0F; + max77705_write_word(fuelgauge->i2c, LEARN_CFG_REG, temp); + max77705_write_word(fuelgauge->i2c, CYCLES_REG, 0); +re_calculation_fullcap_rep: + pr_err("%s: enter re_calculation fullcaprep\n", __func__); + vfsoc = max77705_fg_read_vfsoc(fuelgauge); + max77705_write_word(fuelgauge->i2c, REMCAP_REP_REG, vfsoc * fg_reset_data->fullcapnom / 1000); + msleep(200); + max77705_write_word(fuelgauge->i2c, FULLCAP_REP_REG, fg_reset_data->fullcapnom); + fuelgauge->repcap_1st = -1; + fuelgauge->err_cnt++; +set_default_value: + pr_err("%s: enter set_default_value\n", __func__); + max77705_write_word(fuelgauge->i2c, RCOMP_REG, fg_reset_data->rcomp0); + max77705_write_word(fuelgauge->i2c, TEMPCO_REG, fg_reset_data->tempco); + + return; +} + +static void max77705_fg_periodic_read(struct max77705_fuelgauge_data *fuelgauge) +{ + u8 reg; + int i, data[0x10]; + char *str = NULL; + unsigned int v_cnt = 0; /* verify count */ + unsigned int err_cnt = 0; + struct verify_reg *fg_verify = fuelgauge->verify_selected_reg; + + str = kzalloc(sizeof(char) * 1024, GFP_KERNEL); + if (!str) + return; + + for (i = 0; i < 16; i++) { + if (i == 5) + i = 11; + else if (i == 12) + i = 13; + for (reg = 0; reg < 0x10; reg++) { + data[reg] = max77705_read_word(fuelgauge->i2c, reg + i * 0x10); + + if (data[reg] < 0) { + kfree(str); + return; + } + + /* verify fg reg */ + if (!fuelgauge->skip_fg_verify && fg_verify && v_cnt < fuelgauge->verify_selected_reg_length) { + if (fg_verify[v_cnt].addr == reg + i * 0x10) { + if (fg_verify[v_cnt].data != data[reg]) { + pr_err("%s:[%d] addr(0x%x 0x%x) data(0x%x 0x%x) read veryfy error! not matched!\n", + __func__, v_cnt, + fg_verify[v_cnt].addr, reg + i * 0x10, + fg_verify[v_cnt].data, data[reg]); + err_cnt++; + } + v_cnt++; + } + } + } + sprintf(str + strlen(str), + "%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,", + data[0x00], data[0x01], data[0x02], data[0x03], + data[0x04], data[0x05], data[0x06], data[0x07]); + sprintf(str + strlen(str), + "%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,", + data[0x08], data[0x09], data[0x0a], data[0x0b], + data[0x0c], data[0x0d], data[0x0e], data[0x0f]); + + if (!fuelgauge->initial_update_of_soc) + msleep(1); + } + + pr_info("[%d][FG] %s\n", fuelgauge->battery_data->battery_id, str); + + max77705_fg_adaptation_wa(fuelgauge); + + kfree(str); +} +#endif + +static int max77705_fg_read_vcell(struct max77705_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + u32 vcell, temp; + u16 w_data; + + if (max77705_bulk_read(fuelgauge->i2c, VCELL_REG, 2, data) < 0) { + pr_err("%s: Failed to read VCELL_REG\n", __func__); + return -1; + } + + w_data = (data[1] << 8) | data[0]; + + temp = (w_data & 0xFFF) * 78125; + vcell = temp / 1000000; + + temp = ((w_data & 0xF000) >> 4) * 78125; + temp /= 1000000; + vcell += temp << 4; + + if (!(fuelgauge->info.pr_cnt++ % PRINT_COUNT)) { + fuelgauge->info.pr_cnt = 1; + pr_info("%s: VCELL(%d)mV, data(0x%04x)\n", + __func__, vcell, (data[1] << 8) | data[0]); + } + + if ((fuelgauge->vempty_mode == VEMPTY_MODE_SW_VALERT) && + (vcell >= fuelgauge->battery_data->sw_v_empty_recover_vol)) { + fuelgauge->vempty_mode = VEMPTY_MODE_SW_RECOVERY; + max77705_fg_fuelalert_init(fuelgauge, fuelgauge->pdata->fuel_alert_soc); + pr_info("%s: Recoverd from SW V EMPTY Activation\n", __func__); + if (fuelgauge->valert_count_flag) { + pr_info("%s: Vcell(%d) release CISD VALERT COUNT check\n", + __func__, vcell); + fuelgauge->valert_count_flag = false; + } + } + + return vcell; +} + +void check_learncfg(struct max77705_fuelgauge_data *fuelgauge) +{ + u32 learncfg; + + learncfg = max77705_read_word(fuelgauge->i2c, LEARN_CFG_REG); + if (learncfg & MAX77705_ELRN) { + pr_info("%s: LearnCFG= %d -> LSB is set\n", __func__, learncfg); + learncfg &= ~MAX77705_ELRN; + if ((learncfg & MAX77705_FILT_EMPTY) == 0) + learncfg |= MAX77705_FILT_EMPTY; + /* write learncfg */ + max77705_write_word(fuelgauge->i2c, LEARN_CFG_REG, learncfg); + if (fuelgauge->verify_selected_reg != NULL) { + /* write Qrtable00 ~ 30 */ + max77705_write_word(fuelgauge->i2c, QRTABLE00_REG, fuelgauge->q_res_table[0]); + max77705_write_word(fuelgauge->i2c, QRTABLE10_REG, fuelgauge->q_res_table[1]); + max77705_write_word(fuelgauge->i2c, QRTABLE20_REG, fuelgauge->q_res_table[2]); + max77705_write_word(fuelgauge->i2c, QRTABLE30_REG, fuelgauge->q_res_table[3]); + } + } +} + +static int max77705_fg_read_vfocv(struct max77705_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + u32 vfocv = 0, temp; + u16 w_data; + + if (max77705_bulk_read(fuelgauge->i2c, VFOCV_REG, 2, data) < 0) { + pr_err("%s: Failed to read VFOCV_REG\n", __func__); + return -1; + } + + w_data = (data[1] << 8) | data[0]; + + temp = (w_data & 0xFFF) * 78125; + vfocv = temp / 1000000; + + temp = ((w_data & 0xF000) >> 4) * 78125; + temp /= 1000000; + vfocv += (temp << 4); + + max77705_fg_periodic_read_power(fuelgauge); + + check_learncfg(fuelgauge); + fuelgauge->bd_vfocv = vfocv; + return vfocv; +} + +static int max77705_fg_read_avg_vcell(struct max77705_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + u32 avg_vcell = 0, temp; + u16 w_data; + + if (max77705_bulk_read(fuelgauge->i2c, AVR_VCELL_REG, 2, data) < 0) { + pr_err("%s: Failed to read AVR_VCELL_REG\n", __func__); + return -1; + } + + w_data = (data[1] << 8) | data[0]; + + temp = (w_data & 0xFFF) * 78125; + avg_vcell = temp / 1000000; + + temp = ((w_data & 0xF000) >> 4) * 78125; + temp /= 1000000; + avg_vcell += (temp << 4); + + return avg_vcell; +} + +static int max77705_fg_check_battery_present( + struct max77705_fuelgauge_data *fuelgauge) +{ + u8 status_data[2]; + int ret = 1; + + /* 1. Check Bst bit */ + if (max77705_bulk_read(fuelgauge->i2c, STATUS_REG, 2, status_data) < 0) { + pr_err("%s: Failed to read STATUS_REG\n", __func__); + return 0; + } + + if (status_data[0] & (0x1 << 3)) { + pr_info("%s: addr(0x00), data(0x%04x)\n", __func__, + (status_data[1] << 8) | status_data[0]); + pr_info("%s: battery is absent!!\n", __func__); + ret = 0; + } + + return ret; +} + +static void max77705_fg_set_vempty(struct max77705_fuelgauge_data *fuelgauge, + int vempty_mode) +{ + u16 data = 0; + u8 valrt_data[2] = { 0, }; + + if (!fuelgauge->using_temp_compensation) { + pr_info("%s: does not use temp compensation, default hw vempty\n", + __func__); + vempty_mode = VEMPTY_MODE_HW; + } + + fuelgauge->vempty_mode = vempty_mode; + switch (vempty_mode) { + case VEMPTY_MODE_SW: + /* HW Vempty Disable */ + max77705_write_word(fuelgauge->i2c, VEMPTY_REG, + fuelgauge->battery_data->V_empty_origin); + /* Reset VALRT Threshold setting (enable) */ + valrt_data[1] = 0xFF; + valrt_data[0] = fuelgauge->battery_data->sw_v_empty_vol / 20; + if (max77705_bulk_write(fuelgauge->i2c, VALRT_THRESHOLD_REG, + 2, valrt_data) < 0) { + pr_info("%s: Failed to write VALRT_THRESHOLD_REG\n", __func__); + return; + } + data = max77705_read_word(fuelgauge->i2c, VALRT_THRESHOLD_REG); + pr_info("%s: HW V EMPTY Disable, SW V EMPTY Enable with %d mV (%d)\n", + __func__, fuelgauge->battery_data->sw_v_empty_vol, (data & 0x00ff) * 20); + break; + default: + /* HW Vempty Enable */ + max77705_write_word(fuelgauge->i2c, VEMPTY_REG, + fuelgauge->battery_data->V_empty); + /* Reset VALRT Threshold setting (disable) */ + valrt_data[1] = 0xFF; + valrt_data[0] = fuelgauge->battery_data->sw_v_empty_vol_cisd / 20; + if (max77705_bulk_write(fuelgauge->i2c, VALRT_THRESHOLD_REG, + 2, valrt_data) < 0) { + pr_info("%s: Failed to write VALRT_THRESHOLD_REG\n", __func__); + return; + } + data = max77705_read_word(fuelgauge->i2c, VALRT_THRESHOLD_REG); + pr_info("%s: HW V EMPTY Enable, SW V EMPTY Disable %d mV (%d)\n", + __func__, 0, (data & 0x00ff) * 20); + break; + } +} + +static int max77705_fg_write_temp(struct max77705_fuelgauge_data *fuelgauge, + int temperature) +{ + u8 data[2]; + + data[0] = (temperature % 10) * 1000 / 39; + data[1] = temperature / 10; + max77705_bulk_write(fuelgauge->i2c, TEMPERATURE_REG, 2, data); + + pr_debug("%s: temperature to (%d, 0x%02x%02x)\n", + __func__, temperature, data[1], data[0]); + + fuelgauge->temperature = temperature; + if (!fuelgauge->vempty_init_flag) + fuelgauge->vempty_init_flag = true; + + return temperature; +} + +static int max77705_fg_read_temp(struct max77705_fuelgauge_data *fuelgauge) +{ + u8 data[2] = { 0, 0 }; + int temper = 0; + + if (max77705_fg_check_battery_present(fuelgauge)) { + if (max77705_bulk_read(fuelgauge->i2c, TEMPERATURE_REG, 2, data) < 0) { + pr_err("%s: Failed to read TEMPERATURE_REG\n", __func__); + return -1; + } + + if (data[1] & (0x1 << 7)) { + temper = ((~(data[1])) & 0xFF) + 1; + temper *= (-1000); + temper -= ((~((int)data[0])) + 1) * 39 / 10; + } else { + temper = data[1] & 0x7f; + temper *= 1000; + temper += data[0] * 39 / 10; + } + } else { + temper = 20000; + } + + if (!(fuelgauge->info.pr_cnt % PRINT_COUNT)) + pr_info("%s: TEMPERATURE(%d), data(0x%04x)\n", + __func__, temper, (data[1] << 8) | data[0]); + + return temper / 100; +} + +/* soc should be 0.1% unit */ +static int max77705_fg_read_vfsoc(struct max77705_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int soc; + + if (max77705_bulk_read(fuelgauge->i2c, VFSOC_REG, 2, data) < 0) { + pr_err("%s: Failed to read VFSOC_REG\n", __func__); + return -1; + } + soc = ((data[1] * 100) + (data[0] * 100 / 256)) / 10; + + return min(soc, 1000); +} + +/* soc should be 0.001% unit */ +static int max77705_fg_read_qh_vfsoc(struct max77705_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int soc; + + if (max77705_bulk_read(fuelgauge->i2c, VFSOC_REG, 2, data) < 0) { + pr_err("%s: Failed to read VFSOC_REG\n", __func__); + return -1; + } + soc = ((data[1] * 10000) + (data[0] * 10000 / 256)) / 10; + + return soc; +} + +static int max77705_fg_read_qh(struct max77705_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + u32 temp, sign; + s32 qh; + + if (max77705_bulk_read(fuelgauge->i2c, QH_REG, 2, data) < 0) { + pr_err("%s: Failed to read QH_REG\n", __func__); + return -1; + } + + temp = ((data[1] << 8) | data[0]) & 0xFFFF; + if (temp & (0x1 << 15)) { + sign = NEGATIVE; + temp = (~temp & 0xFFFF) + 1; + } else { + sign = POSITIVE; + } + + qh = temp * 1000 * fuelgauge->fg_resistor / 2; + + if (sign) + qh *= -1; + + pr_info("%s : QH(%d)\n", __func__, qh); + + return qh; +} + +/* soc should be 0.1% unit */ +static int max77705_fg_read_avsoc(struct max77705_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int soc; + + if (max77705_bulk_read(fuelgauge->i2c, SOCAV_REG, 2, data) < 0) { + pr_err("%s: Failed to read SOCAV_REG\n", __func__); + return -1; + } + soc = ((data[1] * 100) + (data[0] * 100 / 256)) / 10; + + return min(soc, 1000); +} + +/* soc should be 0.1% unit */ +static int max77705_fg_read_soc(struct max77705_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int soc; + + if (max77705_bulk_read(fuelgauge->i2c, SOCREP_REG, 2, data) < 0) { + pr_err("%s: Failed to read SOCREP_REG\n", __func__); + return -1; + } + soc = ((data[1] * 100) + (data[0] * 100 / 256)) / 10; + +#ifdef BATTERY_LOG_MESSAGE + pr_debug("%s: raw capacity (%d)\n", __func__, soc); + + if (!(fuelgauge->info.pr_cnt % PRINT_COUNT)) { + pr_debug("%s: raw capacity (%d), data(0x%04x)\n", + __func__, soc, (data[1] << 8) | data[0]); + pr_debug("%s: REPSOC (%d), VFSOC (%d), data(0x%04x)\n", + __func__, soc / 10, + max77705_fg_read_vfsoc(fuelgauge) / 10, + (data[1] << 8) | data[0]); + } +#endif + + return min(soc, 1000); +} + +/* soc should be 0.01% unit */ +static int max77705_fg_read_rawsoc(struct max77705_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int soc; + + if (max77705_bulk_read(fuelgauge->i2c, SOCREP_REG, 2, data) < 0) { + pr_err("%s: Failed to read SOCREP_REG\n", __func__); + return -1; + } + soc = (data[1] * 100) + (data[0] * 100 / 256); + + pr_debug("%s: raw capacity (0.01%%) (%d)\n", __func__, soc); + + if (!(fuelgauge->info.pr_cnt % PRINT_COUNT)) + pr_debug("%s: raw capacity (%d), data(0x%04x)\n", + __func__, soc, (data[1] << 8) | data[0]); + + return min(soc, 10000); +} + +static int max77705_fg_read_fullcap(struct max77705_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int ret; + + if (max77705_bulk_read(fuelgauge->i2c, FULLCAP_REG, 2, data) < 0) { + pr_err("%s: Failed to read FULLCAP_REG\n", __func__); + return -1; + } + ret = (data[1] << 8) + data[0]; + + return ret * fuelgauge->fg_resistor / 2; +} + +static int max77705_fg_read_fullcaprep(struct max77705_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int ret; + + if (max77705_bulk_read(fuelgauge->i2c, FULLCAP_REP_REG, 2, data) < 0) { + pr_err("%s: Failed to read FULLCAP_REP_REG\n", __func__); + return -1; + } + ret = (data[1] << 8) + data[0]; + + return ret * fuelgauge->fg_resistor / 2; +} + +static int max77705_fg_read_fullcapnom(struct max77705_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int ret; + + if (max77705_bulk_read(fuelgauge->i2c, FULLCAP_NOM_REG, 2, data) < 0) { + pr_err("%s: Failed to read FULLCAP_NOM_REG\n", __func__); + return -1; + } + ret = (data[1] << 8) + data[0]; + + return ret * fuelgauge->fg_resistor / 2; +} + +static int max77705_fg_read_mixcap(struct max77705_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int ret; + + if (max77705_bulk_read(fuelgauge->i2c, REMCAP_MIX_REG, 2, data) < 0) { + pr_err("%s: Failed to read REMCAP_MIX_REG\n", __func__); + return -1; + } + ret = (data[1] << 8) + data[0]; + + return ret * fuelgauge->fg_resistor / 2; +} + +static int max77705_fg_read_avcap(struct max77705_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int ret; + + if (max77705_bulk_read(fuelgauge->i2c, REMCAP_AV_REG, 2, data) < 0) { + pr_err("%s: Failed to read REMCAP_AV_REG\n", __func__); + return -1; + } + ret = (data[1] << 8) + data[0]; + + return ret * fuelgauge->fg_resistor / 2; +} + +static int max77705_fg_read_repcap(struct max77705_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int ret; + + if (max77705_bulk_read(fuelgauge->i2c, REMCAP_REP_REG, 2, data) < 0) { + pr_err("%s: Failed to read REMCAP_REP_REG\n", __func__); + return -1; + } + ret = (data[1] << 8) + data[0]; + + return ret * fuelgauge->fg_resistor / 2; +} + +static int max77705_fg_read_current(struct max77705_fuelgauge_data *fuelgauge, + int unit) +{ + u8 data1[2]; + u32 temp, sign; + s32 i_current; + + if (max77705_bulk_read(fuelgauge->i2c, CURRENT_REG, 2, data1) < 0) { + pr_err("%s: Failed to read CURRENT_REG\n", __func__); + return -1; + } + + temp = ((data1[1] << 8) | data1[0]) & 0xFFFF; + /* Debug log for abnormal current case */ + if (temp & (0x1 << 15)) { + sign = NEGATIVE; + temp = (~temp & 0xFFFF) + 1; + } else { + sign = POSITIVE; + } + + /* 1.5625uV/0.01Ohm(Rsense) = 156.25uA */ + /* 1.5625uV/0.005Ohm(Rsense) = 312.5uA */ + switch (unit) { + case SEC_BATTERY_CURRENT_UA: + i_current = temp * 15625 * fuelgauge->fg_resistor / 100; + break; + case SEC_BATTERY_CURRENT_MA: + default: + i_current = temp * 15625 * fuelgauge->fg_resistor / 100000; + break; + } + + if (sign) + i_current *= -1; + + pr_debug("%s: current=%d%s\n", __func__, i_current, + (unit == SEC_BATTERY_CURRENT_UA)? "uA" : "mA"); + + return i_current; +} + +static int max77705_fg_read_avg_current(struct max77705_fuelgauge_data *fuelgauge, + int unit) +{ + static int cnt; + u8 data2[2]; + u32 temp, sign; + s32 avg_current; + int vcell; + + if (max77705_bulk_read(fuelgauge->i2c, AVG_CURRENT_REG, 2, data2) < 0) { + pr_err("%s: Failed to read AVG_CURRENT_REG\n", __func__); + return -1; + } + + temp = ((data2[1] << 8) | data2[0]) & 0xFFFF; + if (temp & (0x1 << 15)) { + sign = NEGATIVE; + temp = (~temp & 0xFFFF) + 1; + } else { + sign = POSITIVE; + } + + /* 1.5625uV/0.01Ohm(Rsense) = 156.25uA */ + /* 1.5625uV/0.005Ohm(Rsense) = 312.5uA */ + switch (unit) { + case SEC_BATTERY_CURRENT_UA: + avg_current = temp * 15625 * fuelgauge->fg_resistor / 100; + break; + case SEC_BATTERY_CURRENT_MA: + default: + avg_current = temp * 15625 * fuelgauge->fg_resistor / 100000; + break; + } + + if (sign) + avg_current *= -1; + + vcell = max77705_fg_read_vcell(fuelgauge); + if ((vcell < 3500) && (cnt < 10) && (avg_current < 0) && fuelgauge->is_charging) { + avg_current = 1; + cnt++; + } + + pr_debug("%s: avg_current=%d%s\n", __func__, avg_current, + (unit == SEC_BATTERY_CURRENT_UA)? "uA" : "mA"); + + return avg_current; +} + +static int max77705_fg_read_isys(struct max77705_fuelgauge_data *fuelgauge, + int unit) +{ + u8 data1[2]; + u32 temp = 0; + s32 i_current = 0; + s32 inow = 0, inow_comp = 0; + u32 unit_type = 0; + + if (max77705_bulk_read(fuelgauge->i2c, ISYS_REG, 2, data1) < 0) { + pr_err("%s: Failed to read ISYS_REG\n", __func__); + return -1; + } + temp = ((data1[1] << 8) | data1[0]) & 0xFFFF; + + /* standard value is 2 whitch means 5mOhm */ + if (fuelgauge->fg_resistor != 2) { + inow = max77705_fg_read_current(fuelgauge, unit); + } + + if (unit == SEC_BATTERY_CURRENT_UA) + unit_type = 1; + else + unit_type = 1000; + + i_current = temp * 3125 / 10 / unit_type; + + if (fuelgauge->fg_resistor != 2 && i_current != 0) { + /* inow_comp = 60% x inow if 0.002Ohm */ + inow_comp = (int)((fuelgauge->fg_resistor - 2) * 10 / fuelgauge->fg_resistor) * inow / 10; + /* i_current must have a value of inow compensated */ + i_current = i_current - inow_comp; + } + if (!(fuelgauge->info.pr_cnt % PRINT_COUNT)) + pr_info("%s: isys_current=%d%s\n", __func__, i_current, + (unit == SEC_BATTERY_CURRENT_UA)? "uA" : "mA"); + + return i_current; +} + +static int max77705_fg_read_isys_avg(struct max77705_fuelgauge_data *fuelgauge, + int unit) +{ + u8 data2[2]; + u32 temp = 0; + s32 avg_current = 0; + s32 avg_inow = 0, avg_inow_comp = 0; + u32 unit_type = 0; + + if (max77705_bulk_read(fuelgauge->i2c, AVGISYS_REG, 2, data2) < 0) { + pr_err("%s: Failed to read AVGISYS_REG\n", __func__); + return -1; + } + temp = ((data2[1] << 8) | data2[0]) & 0xFFFF; + + /* standard value is 2 whitch means 5mOhm */ + if (fuelgauge->fg_resistor != 2) { + avg_inow = max77705_fg_read_avg_current(fuelgauge, unit); + } + + if (unit == SEC_BATTERY_CURRENT_UA) + unit_type = 1; + else + unit_type = 1000; + + avg_current = temp * 3125 / 10 / unit_type; + + if (fuelgauge->fg_resistor != 2 && avg_current != 0) { + /* inow_comp = 60% x inow if 0.002Ohm */ + avg_inow_comp = (int)((fuelgauge->fg_resistor - 2) * 10 / fuelgauge->fg_resistor) * avg_inow / 10; + /* i_current must have a value of inow compensated */ + avg_current = avg_current - avg_inow_comp; + } + if (!(fuelgauge->info.pr_cnt % PRINT_COUNT)) + pr_info("%s: isys_avg_current=%d%s\n", __func__, avg_current, + (unit == SEC_BATTERY_CURRENT_UA)? "uA" : "mA"); + + return avg_current; +} + +static int max77705_fg_read_iin(struct max77705_fuelgauge_data *fuelgauge, + int unit) +{ + u8 data1[2]; + u32 temp; + s32 i_current; + + if (max77705_bulk_read(fuelgauge->i2c, IIN_REG, 2, data1) < 0) { + pr_err("%s: Failed to read IIN_REG\n", __func__); + return -1; + } + + temp = ((data1[1] << 8) | data1[0]) & 0xFFFF; + + /* LSB 0.125mA */ + switch (unit) { + case SEC_BATTERY_CURRENT_UA: + i_current = temp * 125; + break; + case SEC_BATTERY_CURRENT_MA: + default: + i_current = temp * 125 / 1000; + break; + } + + if (!(fuelgauge->info.pr_cnt % PRINT_COUNT)) + pr_debug("%s: iin_current=%d%s\n", __func__, i_current, + (unit == SEC_BATTERY_CURRENT_UA)? "uA" : "mA"); + + return i_current; +} + +static int max77705_fg_read_vbyp(struct max77705_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + u32 vbyp, temp; + u16 w_data; + + if (max77705_bulk_read(fuelgauge->i2c, VBYP_REG, 2, data) < 0) { + pr_err("%s: Failed to read VBYP_REG\n", __func__); + return -1; + } + + w_data = (data[1] << 8) | data[0]; + + temp = (w_data & 0xFFF) * 427246; + vbyp = temp / 1000000; + + temp = ((w_data & 0xF000) >> 4) * 427246; + temp /= 1000000; + vbyp += (temp << 4); + + if (!(fuelgauge->info.pr_cnt % PRINT_COUNT)) + pr_info("%s: VBYP(%d), data(0x%04x)\n", + __func__, vbyp, (data[1] << 8) | data[0]); + + return vbyp; +} + +static int max77705_fg_read_vsys(struct max77705_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + u32 vsys, temp; + u16 w_data; + + if (max77705_bulk_read(fuelgauge->i2c, VSYS_REG, 2, data) < 0) { + pr_err("%s: Failed to read VSYS_REG\n", __func__); + return -1; + } + + w_data = (data[1] << 8) | data[0]; + + temp = (w_data & 0xFFF) * 15625; + vsys = temp / 100000; + + temp = ((w_data & 0xF000) >> 4) * 15625; + temp /= 100000; + vsys += (temp << 4); + + if (!(fuelgauge->info.pr_cnt % PRINT_COUNT)) + pr_info("%s: VSYS(%d), data(0x%04x)\n", + __func__, vsys, (data[1] << 8) | data[0]); + + return vsys; +} + +static int max77705_fg_read_cycle(struct max77705_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int ret; + + if (max77705_bulk_read(fuelgauge->i2c, CYCLES_REG, 2, data) < 0) { + pr_err("%s: Failed to read CYCLES_REG\n", __func__); + return -1; + } + ret = (data[1] << 8) + data[0]; + + return ret; +} + +static bool max77705_check_jig_status(struct max77705_fuelgauge_data *fuelgauge) +{ + bool ret = false; + + if (fuelgauge->pdata->jig_gpio) { + if (fuelgauge->pdata->jig_low_active) + ret = !gpio_get_value(fuelgauge->pdata->jig_gpio); + else + ret = gpio_get_value(fuelgauge->pdata->jig_gpio); + } + pr_info("%s: ret(%d)\n", __func__, ret); + + return ret; +} + +int max77705_fg_reset_soc(struct max77705_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int vfocv, fullcap; + + /* delay for current stablization */ + msleep(500); + + pr_info("%s: Before quick-start - VCELL(%d), VFOCV(%d), VfSOC(%d), RepSOC(%d)\n", + __func__, max77705_fg_read_vcell(fuelgauge), + max77705_fg_read_vfocv(fuelgauge), + max77705_fg_read_vfsoc(fuelgauge), + max77705_fg_read_soc(fuelgauge)); + pr_info("%s: Before quick-start - current(%d), avg current(%d)\n", __func__, + max77705_fg_read_current(fuelgauge, SEC_BATTERY_CURRENT_MA), + max77705_fg_read_avg_current(fuelgauge, SEC_BATTERY_CURRENT_MA)); + + if (!max77705_check_jig_status(fuelgauge)) { + pr_info("%s : Return by No JIG_ON signal\n", __func__); + return 0; + } + + max77705_write_word(fuelgauge->i2c, CYCLES_REG, 0); + + if (max77705_bulk_read(fuelgauge->i2c, MISCCFG_REG, 2, data) < 0) { + pr_err("%s: Failed to read MISCCFG_REG\n", __func__); + return -1; + } + + data[1] |= (0x1 << 2); + if (max77705_bulk_write(fuelgauge->i2c, MISCCFG_REG, 2, data) < 0) { + pr_info("%s: Failed to write MISCCFG_REG\n", __func__); + return -1; + } + + msleep(250); + max77705_write_word(fuelgauge->i2c, FULLCAP_REG, + fuelgauge->battery_data->Capacity); + max77705_write_word(fuelgauge->i2c, FULLCAP_REP_REG, + fuelgauge->battery_data->Capacity); + msleep(500); + + pr_info("%s: After quick-start - VCELL(%d), VFOCV(%d), VfSOC(%d), RepSOC(%d)\n", + __func__, max77705_fg_read_vcell(fuelgauge), + max77705_fg_read_vfocv(fuelgauge), + max77705_fg_read_vfsoc(fuelgauge), + max77705_fg_read_soc(fuelgauge)); + pr_info("%s: After quick-start - current(%d), avg current(%d)\n", __func__, + max77705_fg_read_current(fuelgauge, SEC_BATTERY_CURRENT_MA), + max77705_fg_read_avg_current(fuelgauge, SEC_BATTERY_CURRENT_MA)); + + max77705_write_word(fuelgauge->i2c, CYCLES_REG, 0x00a0); + +/* P8 is not turned off by Quickstart @3.4V + * (It's not a problem, depend on mode data) + * Power off for factory test(File system, etc..) + */ + vfocv = max77705_fg_read_vfocv(fuelgauge); + if (vfocv < POWER_OFF_VOLTAGE_LOW_MARGIN) { + pr_info("%s: Power off condition(%d)\n", __func__, vfocv); + fullcap = max77705_read_word(fuelgauge->i2c, FULLCAP_REG); + + /* FullCAP * 0.009 */ + max77705_write_word(fuelgauge->i2c, REMCAP_REP_REG, + (u16) (fullcap * 9 / 1000)); + msleep(200); + pr_info("%s: new soc=%d, vfocv=%d\n", __func__, + max77705_fg_read_soc(fuelgauge), vfocv); + } + + pr_info("%s: Additional step - VfOCV(%d), VfSOC(%d), RepSOC(%d)\n", + __func__, max77705_fg_read_vfocv(fuelgauge), + max77705_fg_read_vfsoc(fuelgauge), + max77705_fg_read_soc(fuelgauge)); + + return 0; +} + +int max77705_fg_reset_capacity_by_jig_connection( + struct max77705_fuelgauge_data *fuelgauge) +{ + union power_supply_propval val; + + val.intval = SEC_BAT_FGSRC_SWITCHING_VSYS; + psy_do_property("max77705-charger", set, + POWER_SUPPLY_EXT_PROP_FGSRC_SWITCHING, val); + + val.intval = 1; + psy_do_property("battery", set, POWER_SUPPLY_PROP_ENERGY_NOW, val); + pr_info("%s: DesignCap = Capacity - 1 (Jig Connection)\n", __func__); + + return max77705_write_word(fuelgauge->i2c, DESIGNCAP_REG, + fuelgauge->battery_data->Capacity - 1); +} + +static int max77705_fg_check_status_reg(struct max77705_fuelgauge_data *fuelgauge) +{ + u8 status_data[2]; + int ret = 0; + + /* 1. Check Smn was generatedread */ + if (max77705_bulk_read(fuelgauge->i2c, STATUS_REG, 2, status_data) < 0) { + pr_err("%s: Failed to read STATUS_REG\n", __func__); + return -1; + } +#ifdef BATTERY_LOG_MESSAGE + pr_info("%s: addr(0x00), data(0x%04x)\n", __func__, + (status_data[1] << 8) | status_data[0]); +#endif + + if (status_data[1] & (0x1 << 2)) + ret = 1; + + /* 2. clear Status reg */ + status_data[1] = 0; + if (max77705_bulk_write(fuelgauge->i2c, STATUS_REG, 2, status_data) < 0) { + pr_info("%s: Failed to write STATUS_REG\n", __func__); + return -1; + } + + return ret; +} + +int max77705_get_fuelgauge_value(struct max77705_fuelgauge_data *fuelgauge, + int data) +{ + int ret; + + switch (data) { + case FG_LEVEL: + ret = max77705_fg_read_soc(fuelgauge); + break; + case FG_TEMPERATURE: + ret = max77705_fg_read_temp(fuelgauge); + break; + case FG_VOLTAGE: + ret = max77705_fg_read_vcell(fuelgauge); + break; + case FG_CURRENT: + ret = max77705_fg_read_current(fuelgauge, SEC_BATTERY_CURRENT_MA); + break; + case FG_CURRENT_AVG: + ret = max77705_fg_read_avg_current(fuelgauge, SEC_BATTERY_CURRENT_MA); + break; + case FG_CHECK_STATUS: + ret = max77705_fg_check_status_reg(fuelgauge); + break; + case FG_RAW_SOC: + ret = max77705_fg_read_rawsoc(fuelgauge); + fuelgauge->bd_raw_soc = ret; + break; + case FG_VF_SOC: + ret = max77705_fg_read_vfsoc(fuelgauge); + break; + case FG_AV_SOC: + ret = max77705_fg_read_avsoc(fuelgauge); + break; + case FG_FULLCAP: + ret = max77705_fg_read_fullcap(fuelgauge); + if (ret == -1) + ret = max77705_fg_read_fullcap(fuelgauge); + break; + case FG_FULLCAPNOM: + ret = max77705_fg_read_fullcapnom(fuelgauge); + if (ret == -1) + ret = max77705_fg_read_fullcapnom(fuelgauge); + break; + case FG_FULLCAPREP: + ret = max77705_fg_read_fullcaprep(fuelgauge); + if (ret == -1) + ret = max77705_fg_read_fullcaprep(fuelgauge); + break; + case FG_MIXCAP: + ret = max77705_fg_read_mixcap(fuelgauge); + break; + case FG_AVCAP: + ret = max77705_fg_read_avcap(fuelgauge); + break; + case FG_REPCAP: + ret = max77705_fg_read_repcap(fuelgauge); + break; + case FG_CYCLE: + ret = max77705_fg_read_cycle(fuelgauge); + break; + case FG_QH: + ret = max77705_fg_read_qh(fuelgauge); + break; + case FG_QH_VF_SOC: + ret = max77705_fg_read_qh_vfsoc(fuelgauge); + break; + case FG_ISYS: + ret = max77705_fg_read_isys(fuelgauge, SEC_BATTERY_CURRENT_MA); + break; + case FG_ISYS_AVG: + ret = max77705_fg_read_isys_avg(fuelgauge, SEC_BATTERY_CURRENT_MA); + break; + case FG_VSYS: + ret = max77705_fg_read_vsys(fuelgauge); + break; + case FG_IIN: + ret = max77705_fg_read_iin(fuelgauge, SEC_BATTERY_CURRENT_MA); + break; + case FG_VBYP: + ret = max77705_fg_read_vbyp(fuelgauge); + break; + default: + ret = -1; + break; + } + + return ret; +} + +static void max77705_fg_periodic_read_power( + struct max77705_fuelgauge_data *fuelgauge) +{ + int isys, isys_avg, vsys, iin, vbyp, qh; + + isys = max77705_get_fuelgauge_value(fuelgauge, FG_ISYS); + isys_avg = max77705_get_fuelgauge_value(fuelgauge, FG_ISYS_AVG); + vsys = max77705_get_fuelgauge_value(fuelgauge, FG_VSYS); + iin = max77705_get_fuelgauge_value(fuelgauge, FG_IIN); + vbyp = max77705_get_fuelgauge_value(fuelgauge, FG_VBYP); + qh = max77705_get_fuelgauge_value(fuelgauge, FG_QH); + + pr_info("[FG power] ISYS(%dmA),ISYSAVG(%dmA),VSYS(%dmV),IIN(%dmA),VBYP(%dmV),QH(%d uah),WA(%d)\n", + isys, isys_avg, vsys, iin, vbyp, qh, fuelgauge->err_cnt); +} + +static void max77705_fg_read_power_log( + struct max77705_fuelgauge_data *fuelgauge) +{ + int vnow, inow; + + vnow = max77705_get_fuelgauge_value(fuelgauge, FG_VOLTAGE); + inow = max77705_get_fuelgauge_value(fuelgauge, FG_CURRENT); + + pr_info("[FG info] VNOW(%dmV),INOW(%dmA)\n", vnow, inow); +} + +int max77705_fg_alert_init(struct max77705_fuelgauge_data *fuelgauge, int soc) +{ + u8 misccgf_data[2], salrt_data[2], config_data[2], talrt_data[2]; + u16 read_data = 0; + + fuelgauge->is_fuel_alerted = false; + + /* Using RepSOC */ + if (max77705_bulk_read(fuelgauge->i2c, MISCCFG_REG, 2, misccgf_data) < 0) { + pr_err("%s: Failed to read MISCCFG_REG\n", __func__); + return -1; + } + misccgf_data[0] = misccgf_data[0] & ~(0x03); + + if (max77705_bulk_write(fuelgauge->i2c, MISCCFG_REG, 2, misccgf_data) < 0) { + pr_info("%s: Failed to write MISCCFG_REG\n", __func__); + return -1; + } + + /* SALRT Threshold setting */ + salrt_data[1] = 0xff; + salrt_data[0] = soc; + if (max77705_bulk_write(fuelgauge->i2c, SALRT_THRESHOLD_REG, 2, salrt_data) < 0) { + pr_info("%s: Failed to write SALRT_THRESHOLD_REG\n", __func__); + return -1; + } + + /* Reset TALRT Threshold setting (disable) */ + talrt_data[1] = 0x7F; + talrt_data[0] = 0x80; + if (max77705_bulk_write(fuelgauge->i2c, TALRT_THRESHOLD_REG, 2, talrt_data) < 0) { + pr_info("%s: Failed to write TALRT_THRESHOLD_REG\n", __func__); + return -1; + } + + read_data = max77705_read_word(fuelgauge->i2c, TALRT_THRESHOLD_REG); + if (read_data != 0x7f80) + pr_err("%s: TALRT_THRESHOLD_REG is not valid (0x%x)\n", + __func__, read_data); + + /* Enable SOC alerts */ + if (max77705_bulk_read(fuelgauge->i2c, CONFIG_REG, 2, config_data) < 0) { + pr_err("%s: Failed to read CONFIG_REG\n", __func__); + return -1; + } + config_data[0] = config_data[0] | (0x1 << 2); + + if (max77705_bulk_write(fuelgauge->i2c, CONFIG_REG, 2, config_data) < 0) { + pr_info("%s: Failed to write CONFIG_REG\n", __func__); + return -1; + } + + max77705_update_reg(fuelgauge->pmic, MAX77705_PMIC_REG_INTSRC_MASK, + ~MAX77705_IRQSRC_FG, MAX77705_IRQSRC_FG); + + pr_info("[%s] SALRT(0x%02x%02x), CONFIG(0x%02x%02x)\n", __func__, + salrt_data[1], salrt_data[0], config_data[1], config_data[0]); + + return 1; +} + +static int max77705_get_fg_soc(struct max77705_fuelgauge_data *fuelgauge) +{ + int fg_soc = 0, fg_vcell, avg_current; + + fg_soc = max77705_get_fuelgauge_value(fuelgauge, FG_LEVEL); + if (fg_soc < 0) { + pr_info("Can't read soc!!!"); + fg_soc = fuelgauge->info.soc; + } + + fg_vcell = max77705_get_fuelgauge_value(fuelgauge, FG_VOLTAGE); + avg_current = max77705_get_fuelgauge_value(fuelgauge, FG_CURRENT_AVG); + + if (fuelgauge->info.is_first_check) + fuelgauge->info.is_first_check = false; + + fuelgauge->info.soc = fg_soc; + pr_debug("%s: soc(%d)\n", __func__, fuelgauge->info.soc); + + return fg_soc; +} + +static void max77705_offset_leakage( + struct max77705_fuelgauge_data *fuelgauge) +{ + // offset coffset + if (fuelgauge->battery_data->coff_origin != fuelgauge->battery_data->coff_charging) { + pr_info("%s: modify coffset\n", __func__); + if (fuelgauge->is_charging) { + max77705_write_word(fuelgauge->i2c, COFFSET_REG, + fuelgauge->battery_data->coff_charging); + pr_info("%s: modify coffset 0x%x\n", __func__, fuelgauge->battery_data->coff_charging); + } else { + max77705_write_word(fuelgauge->i2c, COFFSET_REG, + fuelgauge->battery_data->coff_origin); + pr_info("%s: modify coffset 0x%x\n", __func__, fuelgauge->battery_data->coff_origin); + } + } + + // offset cgain + if (fuelgauge->battery_data->cgain_origin) { + pr_info("%s: modify cgain\n", __func__); + if (fuelgauge->is_charging) { + max77705_write_word(fuelgauge->i2c, CGAIN_REG, + fuelgauge->battery_data->cgain_charging); + pr_info("%s: modify cgain 0x%x\n", __func__, fuelgauge->battery_data->cgain_charging); + } else { + max77705_write_word(fuelgauge->i2c, CGAIN_REG, + fuelgauge->battery_data->cgain_origin); + pr_info("%s: modify cgain 0x%x\n", __func__, fuelgauge->battery_data->cgain_origin); + } + } +} + +static void max77705_offset_leakage_default( + struct max77705_fuelgauge_data *fuelgauge) +{ + // offset coffset + if (fuelgauge->battery_data->coff_charging) { + pr_info("%s: modify coffset\n", __func__); + max77705_write_word(fuelgauge->i2c, COFFSET_REG, + fuelgauge->battery_data->coff_charging); + pr_info("%s: modify coffset 0x%x\n", __func__, fuelgauge->battery_data->coff_charging); + } + + // offset cgain + if (fuelgauge->battery_data->cgain_origin) { + pr_info("%s: modify cgain\n", __func__); + max77705_write_word(fuelgauge->i2c, CGAIN_REG, + fuelgauge->battery_data->cgain_origin); + pr_info("%s: modify cgain 0x%x\n", __func__, fuelgauge->battery_data->cgain_origin); + } +} + +static irqreturn_t max77705_jig_irq_thread(int irq, void *irq_data) +{ + struct max77705_fuelgauge_data *fuelgauge = irq_data; + + pr_info("%s\n", __func__); + + if (max77705_check_jig_status(fuelgauge)) + max77705_fg_reset_capacity_by_jig_connection(fuelgauge); + else + pr_info("%s: jig removed\n", __func__); + + return IRQ_HANDLED; +} + +bool max77705_fg_init(struct max77705_fuelgauge_data *fuelgauge) +{ + ktime_t current_time; + struct timespec64 ts; + u8 data[2] = { 0, 0 }; + +#if defined(ANDROID_ALARM_ACTIVATED) + current_time = alarm_get_elapsed_realtime(); +#else + current_time = ktime_get_boottime(); +#endif + ts = ktime_to_timespec64(ktime_get_boottime()); + + fuelgauge->info.fullcap_check_interval = ts.tv_sec; + fuelgauge->info.is_first_check = true; + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + /* clear vex bit */ + if (max77705_bulk_read(fuelgauge->i2c, MISCCFG_REG, 2, data) < 0) { + pr_err("%s: Failed to read MISCCFG_REG\n", __func__); + return -1; + } + + data[0] &= 0xFB; + if (max77705_bulk_write(fuelgauge->i2c, MISCCFG_REG, 2, data) < 0) { + pr_info("%s: Failed to write MISCCFG_REG\n", __func__); + return -1; + } +#endif + + if (max77705_bulk_read(fuelgauge->i2c, CONFIG2_REG, 2, data) < 0) { + pr_err("%s: Failed to read CONFIG2_REG\n", __func__); + } else if ((data[0] & 0x0F) != 0x05) { + data[0] &= ~0x2F; + data[0] |= (0x5 & 0xF); /* ISysNCurr: 11.25 */ + max77705_bulk_write(fuelgauge->i2c, CONFIG2_REG, 2, data); + } + + /* 0xB2 == modeldata_ver reg */ + if (max77705_read_word(fuelgauge->i2c, 0xB2) != fuelgauge->data_ver) { + pr_err("%s: fg data_ver miss match. skip verify fg reg\n", __func__); + fuelgauge->skip_fg_verify = true; + } else { + pr_err("%s: fg data_ver match!(0x%x)\n", __func__, fuelgauge->data_ver); + fuelgauge->skip_fg_verify = false; + } + + /* NOT using FG for temperature */ + if (fuelgauge->pdata->thermal_source != SEC_BATTERY_THERMAL_SOURCE_FG) { + if (max77705_bulk_read(fuelgauge->i2c, CONFIG_REG, 2, data) < 0) { + pr_err("%s : Failed to read CONFIG_REG\n", __func__); + return false; + } + data[1] |= 0x1; + + if (max77705_bulk_write(fuelgauge->i2c, CONFIG_REG, 2, data) < 0) { + pr_info("%s : Failed to write CONFIG_REG\n", __func__); + return false; + } + } else { + if (max77705_bulk_read(fuelgauge->i2c, CONFIG_REG, 2, data) < 0) { + pr_err("%s : Failed to read CONFIG_REG\n", __func__); + return false; + } + data[1] &= 0xFE; + data[0] |= 0x10; + + if (max77705_bulk_write(fuelgauge->i2c, CONFIG_REG, 2, data) < 0) { + pr_info("%s : Failed to write CONFIG_REG\n", __func__); + return false; + } + } + max77705_offset_leakage(fuelgauge); + + return true; +} + +bool max77705_fg_fuelalert_init(struct max77705_fuelgauge_data *fuelgauge, + int soc) +{ + /* 1. Set max77705 alert configuration. */ + if (max77705_fg_alert_init(fuelgauge, soc) > 0) + return true; + else + return false; +} + +void max77705_fg_fuelalert_set(struct max77705_fuelgauge_data *fuelgauge, + int enable) +{ + u8 config_data[2], status_data[2]; + + if (max77705_bulk_read(fuelgauge->i2c, CONFIG_REG, 2, config_data) < 0) + pr_err("%s: Failed to read CONFIG_REG\n", __func__); + + if (enable) + config_data[0] |= ALERT_EN; + else + config_data[0] &= ~ALERT_EN; + + pr_info("%s: CONFIG(0x%02x%02x)\n", __func__, config_data[1], config_data[0]); + + if (max77705_bulk_write(fuelgauge->i2c, CONFIG_REG, 2, config_data) < 0) + pr_info("%s: Failed to write CONFIG_REG\n", __func__); + + if (max77705_bulk_read(fuelgauge->i2c, STATUS_REG, 2, status_data) < 0) + pr_err("%s: Failed to read STATUS_REG\n", __func__); + + if ((status_data[1] & 0x01) && !max77705_get_lpmode() && !fuelgauge->is_charging) { + pr_info("%s: Battery Voltage is Very Low!! V EMPTY(%d)\n", + __func__, fuelgauge->vempty_mode); + + if (fuelgauge->vempty_mode != VEMPTY_MODE_HW) + fuelgauge->vempty_mode = VEMPTY_MODE_SW_VALERT; + else if (!fuelgauge->valert_count_flag) { + union power_supply_propval value; + + value.intval = fuelgauge->vempty_mode; + psy_do_property("battery", set, + POWER_SUPPLY_PROP_VOLTAGE_MIN, value); + fuelgauge->valert_count_flag = true; + } + } +} + +bool max77705_fg_fuelalert_process(void *irq_data) +{ + struct max77705_fuelgauge_data *fuelgauge = + (struct max77705_fuelgauge_data *)irq_data; + + if (fuelgauge->initial_update_of_alert) + max77705_fg_fuelalert_set(fuelgauge, 0); + + return true; +} + +bool max77705_fg_reset(struct max77705_fuelgauge_data *fuelgauge) +{ + if (!max77705_fg_reset_soc(fuelgauge)) + return true; + else + return false; +} + +static int max77705_fg_check_capacity_max( + struct max77705_fuelgauge_data *fuelgauge, int capacity_max) +{ + int cap_max, cap_min; + + cap_max = fuelgauge->pdata->capacity_max; + cap_min = + (fuelgauge->pdata->capacity_max - fuelgauge->pdata->capacity_max_margin); + + return (capacity_max < cap_min) ? cap_min : + ((capacity_max >= cap_max) ? cap_max : capacity_max); +} + +static int max77705_fg_check_repcap_to_save( + struct max77705_fuelgauge_data *fuelgauge, int repcap_to_be) +{ + int repcap_min = 0, val = 0; + + val = max77705_get_fuelgauge_value(fuelgauge, FG_FULLCAPREP); + if (val < 0) { + pr_info("%s: Failed to read FG_FULLCAPREP\n", __func__); + return -1; + } + pr_info("%s: fg_fullcaprep(%d) ,repcap_min:(%d) ,repcap_to_be:(%d)\n", + __func__, val, repcap_min, repcap_to_be); + repcap_min = 95 * val / 100; + + return (repcap_to_be < repcap_min) ? repcap_min : + ((repcap_to_be > val) ? val : repcap_to_be); +} + +#if defined(CONFIG_UI_SOC_PROLONGING) +static void max77705_fg_adjust_capacity_max( + struct max77705_fuelgauge_data *fuelgauge, int curr_raw_soc) +{ + int diff = 0; + + if (fuelgauge->is_charging && fuelgauge->capacity_max_conv) { + diff = curr_raw_soc - fuelgauge->prev_raw_soc; + + if ((diff >= 1) && (fuelgauge->capacity_max < fuelgauge->g_capacity_max)) { + fuelgauge->capacity_max++; + } else if ((fuelgauge->capacity_max >= fuelgauge->g_capacity_max) || (curr_raw_soc == 1000)) { + fuelgauge->g_capacity_max = 0; + fuelgauge->capacity_max_conv = false; + } + pr_info("%s: curr_raw_soc(%d) prev_raw_soc(%d) capacity_max_conv(%d) Capacity Max(%d | %d)\n", + __func__, curr_raw_soc, fuelgauge->prev_raw_soc, fuelgauge->capacity_max_conv, + fuelgauge->capacity_max, fuelgauge->g_capacity_max); + } + + fuelgauge->prev_raw_soc = curr_raw_soc; +} +#else +static void max77705_fg_adjust_capacity_max( + struct max77705_fuelgauge_data *fuelgauge) +{ + struct timespec64 c_ts = {0, }; + static struct timespec64 old_ts = {0, }; + + if (fuelgauge->capacity_max_conv) { + c_ts = ktime_to_timespec64(ktime_get_boottime()); + pr_info("%s: capacit max conv time(%llu)\n", + __func__, c_ts.tv_sec - old_ts.tv_sec); + + if ((fuelgauge->capacity_max < fuelgauge->g_capacity_max) && + ((unsigned long)(c_ts.tv_sec - old_ts.tv_sec) >= 60)) { + fuelgauge->capacity_max++; + old_ts = c_ts; + } else if (fuelgauge->capacity_max >= fuelgauge->g_capacity_max) { + fuelgauge->g_capacity_max = 0; + fuelgauge->capacity_max_conv = false; + } + pr_info("%s: capacity_max_conv(%d) Capacity Max(%d | %d)\n", + __func__, fuelgauge->capacity_max_conv, + fuelgauge->capacity_max, fuelgauge->g_capacity_max); + } +} +#endif + +static unsigned int max77705_fg_get_scaled_capacity( + struct max77705_fuelgauge_data *fuelgauge, unsigned int soc) +{ + int raw_soc = soc; + + soc = (soc < fuelgauge->pdata->capacity_min) ? + 0 : ((soc - fuelgauge->pdata->capacity_min) * 1000 / + (fuelgauge->capacity_max - fuelgauge->pdata->capacity_min)); + + pr_info("%s : capacity_max (%d) scaled capacity(%d.%d), raw_soc(%d.%d)\n", + __func__, fuelgauge->capacity_max, soc / 10, soc % 10, + raw_soc / 10, raw_soc % 10); + + return soc; +} + + +static unsigned int max77705_fg_get_capacity_by_repcap( + struct max77705_fuelgauge_data *fuelgauge, int fg_soc) +{ + int val = 0, ret = 0; + + if (fuelgauge->repcap_1st <= 0) { + pr_info("%s: repcap_1st(%d)\n", __func__, fuelgauge->repcap_1st); + return fg_soc; + } + + ret = max77705_get_fuelgauge_value(fuelgauge, FG_REPCAP); + if (ret < 0) { + pr_info("%s: Failed to get FG_REPCAP\n", __func__); + return fg_soc; + } + val = (ret * 1000) / fuelgauge->repcap_1st; + pr_info("%s: FG_REPCAP(%d), repcap_1st(%d), repcap scaled capacity(%d)\n", + __func__, ret, fuelgauge->repcap_1st, val); + + if (val < 10 && fg_soc % 10 >= 1) { + val = 10; + pr_info("%s: make repcap scaled capacity 10\n", __func__); + } + + return val; +} + +/* capacity is integer */ +static unsigned int max77705_fg_get_atomic_capacity( + struct max77705_fuelgauge_data *fuelgauge, unsigned int soc) +{ + pr_info("%s : NOW(%d), OLD(%d)\n", + __func__, soc, fuelgauge->capacity_old); + + if (fuelgauge->pdata->capacity_calculation_type & + SEC_FUELGAUGE_CAPACITY_TYPE_ATOMIC) { + if (fuelgauge->capacity_old < soc) + soc = fuelgauge->capacity_old + 1; + else if (fuelgauge->capacity_old > soc) + soc = fuelgauge->capacity_old - 1; + } + + /* keep SOC stable in abnormal status */ + if (fuelgauge->pdata->capacity_calculation_type & + SEC_FUELGAUGE_CAPACITY_TYPE_SKIP_ABNORMAL) { + if (!fuelgauge->is_charging && + fuelgauge->capacity_old < soc) { + pr_err("%s: capacity (old %d : new %d)\n", + __func__, fuelgauge->capacity_old, soc); + soc = fuelgauge->capacity_old; + } + } + + /* updated old capacity */ + fuelgauge->capacity_old = soc; + + return soc; +} + +#if defined(CONFIG_UI_SOC_PROLONGING) +static void max77705_fg_calculate_dynamic_scale( + struct max77705_fuelgauge_data *fuelgauge, int capacity, bool scale_by_full) +{ + union power_supply_propval raw_soc_val; + int min_cap = fuelgauge->pdata->capacity_max - fuelgauge->pdata->capacity_max_margin; + int scaling_factor = 1; + + if ((capacity > 100) || ((capacity * 10) < min_cap)) { + pr_err("%s: invalid capacity(%d)\n", __func__, capacity); + return; + } + + raw_soc_val.intval = max77705_get_fuelgauge_value(fuelgauge, FG_RAW_SOC); + if (raw_soc_val.intval < 0) { + pr_info("%s: failed to get raw soc\n", __func__); + return; + } + + raw_soc_val.intval = raw_soc_val.intval / 10; + + if (capacity < 100) + fuelgauge->capacity_max_conv = false; //Force full sequence , need to decrease capacity_max + + if ((raw_soc_val.intval < min_cap) || (fuelgauge->capacity_max_conv)) { + pr_info("%s: skip routine - raw_soc(%d), min_cap(%d), cap_max_conv(%d)\n", + __func__, raw_soc_val.intval, min_cap, fuelgauge->capacity_max_conv); + return; + } + + if (capacity == 100) + scaling_factor = 2; + + fuelgauge->capacity_max = (raw_soc_val.intval * 100 / (capacity + scaling_factor)); + fuelgauge->capacity_old = capacity; + + fuelgauge->capacity_max = + max77705_fg_check_capacity_max(fuelgauge, fuelgauge->capacity_max); + + pr_info("%s: %d is used for capacity_max, capacity(%d)\n", + __func__, fuelgauge->capacity_max, capacity); + if ((capacity == 100) && !fuelgauge->capacity_max_conv && scale_by_full) { + fuelgauge->capacity_max_conv = true; + fuelgauge->g_capacity_max = 990; + pr_info("%s: Goal capacity max %d\n", __func__, fuelgauge->g_capacity_max); + } +} +#else +static void max77705_fg_calculate_dynamic_scale( + struct max77705_fuelgauge_data *fuelgauge, int capacity, bool scale_by_full) +{ + union power_supply_propval raw_soc_val; + int min_cap = fuelgauge->pdata->capacity_max - fuelgauge->pdata->capacity_max_margin; + + if ((capacity > 100) || ((capacity * 10) < min_cap)) { + pr_err("%s: invalid capacity(%d)\n", __func__, capacity); + return; + } + + raw_soc_val.intval = max77705_get_fuelgauge_value(fuelgauge, FG_RAW_SOC); + if (raw_soc_val.intval < 0) { + pr_info("%s: failed to get raw soc\n", __func__); + return; + } + + raw_soc_val.intval = raw_soc_val.intval / 10; + if ((raw_soc_val.intval < min_cap) || (fuelgauge->capacity_max_conv)) { + pr_info("%s: skip routine - raw_soc(%d), min_cap(%d), cap_max_conv(%d)\n", + __func__, raw_soc_val.intval, min_cap, fuelgauge->capacity_max_conv); + return; + } + + fuelgauge->capacity_max = (raw_soc_val.intval * 100 / (capacity + 1)); + fuelgauge->capacity_old = capacity; + + fuelgauge->capacity_max = + max77705_fg_check_capacity_max(fuelgauge, fuelgauge->capacity_max); + + pr_info("%s: %d is used for capacity_max, capacity(%d)\n", + __func__, fuelgauge->capacity_max, capacity); + if ((capacity == 100) && !fuelgauge->capacity_max_conv && scale_by_full) { + fuelgauge->capacity_max_conv = true; + fuelgauge->g_capacity_max = min(990, raw_soc_val.intval); + pr_info("%s: Goal capacity max %d\n", __func__, fuelgauge->g_capacity_max); + } +} +#endif + +static void max77705_fg_calculate_new_repcap_1st(struct max77705_fuelgauge_data *fuelgauge) +{ + int current_repcap = 0, val = 0; + + val = max77705_get_fuelgauge_value(fuelgauge, FG_REPCAP); + if (val < 0) { + pr_info("%s: Failed to read FG_REPCAP\n", __func__); + return; + } + current_repcap = max77705_fg_check_repcap_to_save(fuelgauge, val); + if (current_repcap < 0) { + pr_info("%s: Failed to save repcap, use previous repcap\n", __func__); + return; + } + pr_info("%s: current_repcap=(%d)\n", __func__, current_repcap); + fuelgauge->repcap_1st = current_repcap; +} + +__visible_for_testing void max77705_lost_soc_reset(struct max77705_fuelgauge_data *fuelgauge) +{ + fuelgauge->lost_soc.ing = false; + fuelgauge->lost_soc.prev_raw_soc = -1; + fuelgauge->lost_soc.prev_remcap = 0; + fuelgauge->lost_soc.prev_qh = 0; + fuelgauge->lost_soc.lost_cap = 0; + fuelgauge->lost_soc.weight = 0; +} +EXPORT_SYMBOL_KUNIT(max77705_lost_soc_reset); + +__visible_for_testing void max77705_lost_soc_check_trigger_cond( + struct max77705_fuelgauge_data *fuelgauge, int raw_soc, int d_raw_soc, int d_remcap, int d_qh) +{ + if (fuelgauge->lost_soc.prev_raw_soc >= fuelgauge->lost_soc.trig_soc || + d_raw_soc <= 0 || d_qh < 0) + return; + + /* + * raw soc is jumped over gap_soc + * and remcap is decreased more than trig_scale of qh + */ + if (d_raw_soc >= fuelgauge->lost_soc.trig_d_soc && + d_remcap >= (d_qh * fuelgauge->lost_soc.trig_scale)) { + fuelgauge->lost_soc.ing = true; + fuelgauge->lost_soc.lost_cap += d_remcap; + + /* calc weight */ + fuelgauge->lost_soc.weight += d_raw_soc * 10 / fuelgauge->lost_soc.guarantee_soc; + if (fuelgauge->lost_soc.weight < fuelgauge->lost_soc.min_weight) + fuelgauge->lost_soc.weight = fuelgauge->lost_soc.min_weight; + + pr_info("%s: trigger: raw_soc(%d->%d), d_raw_soc(%d), d_remcap(%d), d_qh(%d), weight(%d.%d)\n", + __func__, fuelgauge->lost_soc.prev_raw_soc, raw_soc, d_raw_soc, d_remcap, + d_qh, fuelgauge->lost_soc.weight / 10, fuelgauge->lost_soc.weight % 10); + } +} +EXPORT_SYMBOL_KUNIT(max77705_lost_soc_check_trigger_cond); + +__visible_for_testing int max77705_lost_soc_calc_soc( + struct max77705_fuelgauge_data *fuelgauge, int request_soc, int d_qh, int d_remcap) +{ + int lost_soc = 0, gap_cap = 0; + int vavg = 0, fullcaprep = 0, onecap = 0; + + vavg = max77705_fg_read_avg_vcell(fuelgauge); + fullcaprep = max77705_fg_read_fullcaprep(fuelgauge); + if (fullcaprep < 0) { + fullcaprep = fuelgauge->battery_data->Capacity * fuelgauge->fg_resistor / 2; + pr_info("%s: ing: fullcaprep is replaced\n", __func__); + } + onecap = (fullcaprep / 100) + 1; + + if (d_qh < 0) { + /* charging status, recover capacity is delta of remcap */ + if (d_remcap < 0) + gap_cap = d_remcap * (-1); + else + gap_cap = d_remcap; + } else if (d_qh == 0) { + gap_cap = 1; + } else { + gap_cap = (d_qh * fuelgauge->lost_soc.weight / 10); + } + + if ((vavg < fuelgauge->lost_soc.min_vol) && (vavg > 0) && (gap_cap < onecap)) { + gap_cap = onecap; /* reduce 1% */ + pr_info("%s: ing: vavg(%d) is under min_vol(%d), reduce cap more(%d)\n", + __func__, vavg, fuelgauge->lost_soc.min_vol, (fullcaprep / 100)); + } + + fuelgauge->lost_soc.lost_cap -= gap_cap; + + if (fuelgauge->lost_soc.lost_cap > 0) { + lost_soc = (fuelgauge->lost_soc.lost_cap * 1000) / fullcaprep; + pr_info("%s: ing: calc_soc(%d), lost_soc(%d), lost_cap(%d), d_qh(%d), d_remcap(%d), weight(%d.%d)\n", + __func__, request_soc + lost_soc, lost_soc, fuelgauge->lost_soc.lost_cap, + d_qh, d_remcap, fuelgauge->lost_soc.weight / 10, fuelgauge->lost_soc.weight % 10); + } else { + lost_soc = 0; + max77705_lost_soc_reset(fuelgauge); + pr_info("%s: done: request_soc(%d), lost_soc(%d), lost_cap(%d)\n", + __func__, request_soc, lost_soc, fuelgauge->lost_soc.lost_cap); + } + + return lost_soc; +} +EXPORT_SYMBOL_KUNIT(max77705_lost_soc_calc_soc); + +static int max77705_lost_soc_get(struct max77705_fuelgauge_data *fuelgauge, int request_soc) +{ + int raw_soc, remcap, qh; /* now values */ + int d_raw_soc, d_remcap, d_qh; /* delta between prev values */ + int report_soc; + + /* get current values */ + raw_soc = max77705_get_fuelgauge_value(fuelgauge, FG_RAW_SOC) / 10; + remcap = max77705_get_fuelgauge_value(fuelgauge, FG_REPCAP); + qh = max77705_get_fuelgauge_value(fuelgauge, FG_QH) / 1000; + + if (fuelgauge->lost_soc.prev_raw_soc < 0) { + fuelgauge->lost_soc.prev_raw_soc = raw_soc; + fuelgauge->lost_soc.prev_remcap = remcap; + fuelgauge->lost_soc.prev_qh = qh; + fuelgauge->lost_soc.lost_cap = 0; + pr_info("%s: init: raw_soc(%d), remcap(%d), qh(%d)\n", + __func__, raw_soc, remcap, qh); + + return request_soc; + } + + /* get diff values with prev */ + d_raw_soc = fuelgauge->lost_soc.prev_raw_soc - raw_soc; + d_remcap = fuelgauge->lost_soc.prev_remcap - remcap; + d_qh = fuelgauge->lost_soc.prev_qh - qh; + + max77705_lost_soc_check_trigger_cond(fuelgauge, raw_soc, d_raw_soc, d_remcap, d_qh); + + /* backup prev values */ + fuelgauge->lost_soc.prev_raw_soc = raw_soc; + fuelgauge->lost_soc.prev_remcap = remcap; + fuelgauge->lost_soc.prev_qh = qh; + + if (!fuelgauge->lost_soc.ing) + return request_soc; + + report_soc = request_soc + max77705_lost_soc_calc_soc(fuelgauge, request_soc, d_qh, d_remcap); + + if (report_soc > 1000) + report_soc = 1000; + if (report_soc < 0) + report_soc = 0; + + return report_soc; +} + +static bool max77705_fg_check_vempty_recover_time(struct max77705_fuelgauge_data *fuelgauge) +{ + struct timespec64 c_ts = {0, }; + unsigned long vempty_time = 0; + + c_ts = ktime_to_timespec64(ktime_get_boottime()); + if (!fuelgauge->vempty_time) + fuelgauge->vempty_time = (c_ts.tv_sec) ? c_ts.tv_sec : 1; + else if (c_ts.tv_sec >= fuelgauge->vempty_time) + vempty_time = c_ts.tv_sec - fuelgauge->vempty_time; + else + vempty_time = 0xFFFFFFFF - fuelgauge->vempty_time + c_ts.tv_sec; + + pr_info("%s: check vempty time(%ld, %ld)\n", + __func__, fuelgauge->vempty_time, vempty_time); + return (vempty_time >= fuelgauge->vempty_recover_time); +} + +static unsigned int max77705_check_vempty_status(struct max77705_fuelgauge_data *fuelgauge, unsigned int soc) +{ + if (!fuelgauge->using_hw_vempty || !fuelgauge->vempty_init_flag) { + fuelgauge->vempty_time = 0; + return soc; + } + + /* SW/HW V Empty setting */ + if (fuelgauge->temperature <= (int)fuelgauge->low_temp_limit) { + if (fuelgauge->raw_capacity <= 50) { + if ((fuelgauge->vempty_mode != VEMPTY_MODE_HW) && + max77705_fg_check_vempty_recover_time(fuelgauge)) { + max77705_fg_set_vempty(fuelgauge, VEMPTY_MODE_HW); + fuelgauge->vempty_time = 0; + } + } else { + if (fuelgauge->vempty_mode == VEMPTY_MODE_HW) + max77705_fg_set_vempty(fuelgauge, VEMPTY_MODE_SW); + + fuelgauge->vempty_time = 0; + } + } else if (fuelgauge->vempty_mode != VEMPTY_MODE_HW && + max77705_fg_check_vempty_recover_time(fuelgauge)) { + max77705_fg_set_vempty(fuelgauge, VEMPTY_MODE_HW); + fuelgauge->vempty_time = 0; + } + + if (!fuelgauge->is_charging && !max77705_get_lpmode() && + fuelgauge->vempty_mode == VEMPTY_MODE_SW_VALERT) { + if (!fuelgauge->vempty_time) { + pr_info("%s : SW V EMPTY. Decrease SOC\n", __func__); + if (fuelgauge->capacity_old > 0) + soc = fuelgauge->capacity_old - 1; + else + soc = 0; + } + } else if ((fuelgauge->vempty_mode == VEMPTY_MODE_SW_RECOVERY) + && (soc == fuelgauge->capacity_old)) { + fuelgauge->vempty_mode = VEMPTY_MODE_SW; + } + + return soc; +} + +static int max77705_get_fg_ui_soc(struct max77705_fuelgauge_data *fuelgauge, union power_supply_propval *val) +{ +#if defined(CONFIG_UI_SOC_PROLONGING) + int scale_to = 1020; +#else + int scale_to = 1010; +#endif + if (val->intval == SEC_FUELGAUGE_CAPACITY_TYPE_RAW) { + val->intval = max77705_get_fuelgauge_value(fuelgauge, FG_RAW_SOC); + } else if (val->intval == SEC_FUELGAUGE_CAPACITY_TYPE_CAPACITY_POINT) { + val->intval = fuelgauge->raw_capacity % 10; + } else if (val->intval == SEC_FUELGAUGE_CAPACITY_TYPE_DYNAMIC_SCALE) { + val->intval = fuelgauge->raw_capacity; + } else { + val->intval = max77705_get_fg_soc(fuelgauge); + + if (fuelgauge->pdata->capacity_calculation_type & + (SEC_FUELGAUGE_CAPACITY_TYPE_SCALE | + SEC_FUELGAUGE_CAPACITY_TYPE_DYNAMIC_SCALE)) { + +#if defined(CONFIG_UI_SOC_PROLONGING) + max77705_fg_adjust_capacity_max(fuelgauge, val->intval); +#else + max77705_fg_adjust_capacity_max(fuelgauge); +#endif + val->intval = max77705_fg_get_scaled_capacity(fuelgauge, val->intval); + + if (val->intval > scale_to) { + pr_info("%s: scaled capacity (%d)\n", __func__, val->intval); + max77705_fg_calculate_dynamic_scale(fuelgauge, 100, false); + } + } + + if (fuelgauge->pdata->capacity_calculation_type & + SEC_FUELGAUGE_CAPACITY_TYPE_REPCAP) { + val->intval = max77705_fg_get_capacity_by_repcap(fuelgauge, val->intval); + } + /* capacity should be between 0% and 100% + * (0.1% degree) + */ + if (val->intval > 1000) + val->intval = 1000; + if (val->intval < 0) + val->intval = 0; + + fuelgauge->raw_capacity = val->intval; + + if (fuelgauge->pdata->capacity_calculation_type & + SEC_FUELGAUGE_CAPACITY_TYPE_LOST_SOC) { + val->intval = max77705_lost_soc_get(fuelgauge, fuelgauge->raw_capacity); + } + + /* get only integer part */ + val->intval /= 10; + + /* check out vempty status and get new soc */ + val->intval = max77705_check_vempty_status(fuelgauge, val->intval); + + /* check whether doing the __pm_relax */ + if ((val->intval > fuelgauge->pdata->fuel_alert_soc) && + fuelgauge->is_fuel_alerted) { + max77705_fg_fuelalert_init(fuelgauge, + fuelgauge->pdata->fuel_alert_soc); + } + +#if defined(CONFIG_UI_SOC_PROLONGING) + /* Check UI soc reached 100% from 99% , no need to adjust now */ + if ((val->intval == 100) && (fuelgauge->capacity_old < 100) && + (fuelgauge->capacity_max_conv == true)) + fuelgauge->capacity_max_conv = false; +#endif + + /* (Only for atomic capacity) + * In initial time, capacity_old is 0. + * and in resume from sleep, + * capacity_old is too different from actual soc. + * should update capacity_old + * by val->intval in booting or resume. + */ + if (fuelgauge->initial_update_of_soc) { + fuelgauge->initial_update_of_soc = false; + if (fuelgauge->vempty_mode != VEMPTY_MODE_SW_VALERT) { + /* updated old capacity */ + fuelgauge->capacity_old = val->intval; + if (fuelgauge->initial_update_of_alert == false) + fuelgauge->initial_update_of_alert = true; + + return val->intval; + } + } + + if (fuelgauge->sleep_initial_update_of_soc) { + /* updated old capacity in case of resume */ + if (fuelgauge->is_charging || + ((!fuelgauge->is_charging) && (fuelgauge->capacity_old >= val->intval))) { + fuelgauge->capacity_old = val->intval; + fuelgauge->sleep_initial_update_of_soc = false; + + return val->intval; + } + } + + if (fuelgauge->pdata->capacity_calculation_type & + (SEC_FUELGAUGE_CAPACITY_TYPE_ATOMIC | + SEC_FUELGAUGE_CAPACITY_TYPE_SKIP_ABNORMAL)) { + val->intval = max77705_fg_get_atomic_capacity(fuelgauge, val->intval); + } + } + + return val->intval; +} + +#if defined(CONFIG_EN_OOPS) +static void max77705_set_full_value(struct max77705_fuelgauge_data *fuelgauge, + int cable_type) +{ + u16 ichgterm, misccfg, fullsocthr; + + if (is_hv_wireless_type(cable_type) || is_hv_wire_type(cable_type)) { + ichgterm = fuelgauge->battery_data->ichgterm_2nd; + misccfg = fuelgauge->battery_data->misccfg_2nd; + fullsocthr = fuelgauge->battery_data->fullsocthr_2nd; + } else { + ichgterm = fuelgauge->battery_data->ichgterm; + misccfg = fuelgauge->battery_data->misccfg; + fullsocthr = fuelgauge->battery_data->fullsocthr; + } + + max77705_write_word(fuelgauge->i2c, ICHGTERM_REG, ichgterm); + max77705_write_word(fuelgauge->i2c, MISCCFG_REG, misccfg); + max77705_write_word(fuelgauge->i2c, FULLSOCTHR_REG, fullsocthr); + + pr_info("%s : ICHGTERM(0x%04x) FULLSOCTHR(0x%04x), MISCCFG(0x%04x)\n", + __func__, ichgterm, misccfg, fullsocthr); +} +#endif + +static int max77705_fg_check_initialization_result( + struct max77705_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + + if (max77705_bulk_read(fuelgauge->i2c, FG_INIT_RESULT_REG, 2, data) < 0) { + pr_err("%s: Failed to read FG_INIT_RESULT_REG\n", __func__); + return SEC_BAT_ERROR_CAUSE_I2C_FAIL; + } + + if (data[1] == 0xFF) { + pr_info("%s : initialization is failed.(0x%02X:0x%04X)\n", + __func__, FG_INIT_RESULT_REG, data[1] << 8 | data[0]); + return SEC_BAT_ERROR_CAUSE_FG_INIT_FAIL; + } else { + pr_info("%s : initialization is succeed.(0x%02X:0x%04X)\n", + __func__, FG_INIT_RESULT_REG, data[1] << 8 | data[0]); + } + + return SEC_BAT_ERROR_CAUSE_NONE; +} + +static int max77705_fg_create_attrs(struct device *dev) +{ + int i, rc; + + for (i = 0; i < (int)ARRAY_SIZE(max77705_fg_attrs); i++) { + rc = device_create_file(dev, &max77705_fg_attrs[i]); + if (rc) + goto create_attrs_failed; + } + return rc; + +create_attrs_failed: + dev_err(dev, "%s: failed (%d)\n", __func__, rc); + while (i--) + device_remove_file(dev, &max77705_fg_attrs[i]); + return rc; +} + +ssize_t max77705_fg_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct max77705_fuelgauge_data *fuelgauge = power_supply_get_drvdata(psy); + const ptrdiff_t offset = attr - max77705_fg_attrs; + int i = 0, j = 0; + u8 addr = 0; + u16 data = 0; + + dev_info(fuelgauge->dev, "%s: (%ld)\n", __func__, offset); + + switch (offset) { + case FG_DATA: + for (j = 0; j <= 0x0F; j++) { + for (addr = 0; addr < 0x10; addr++) { + data = max77705_read_word(fuelgauge->i2c, addr + j * 0x10); + i += scnprintf(buf + i, PAGE_SIZE - i, + "0x%02x:\t0x%04x\n", addr + j * 0x10, data); + } + if (j == 5) + j = 0x0A; + } + break; + default: + return -EINVAL; + } + return i; +} + +ssize_t max77705_fg_store_attrs(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct max77705_fuelgauge_data *fuelgauge = power_supply_get_drvdata(psy); + const ptrdiff_t offset = attr - max77705_fg_attrs; + int ret = 0, x, y; + + dev_info(fuelgauge->dev, "%s: (%ld)\n", __func__, offset); + + switch (offset) { + case FG_DATA: + if (sscanf(buf, "0x%8x 0x%8x", &x, &y) == 2) { + if (x >= VALRT_THRESHOLD_REG && x <= VFSOC_REG) { + u8 addr = x; + u16 data = y; + + if (max77705_write_word(fuelgauge->i2c, addr, data) < 0) + dev_info(fuelgauge->dev,"%s: addr: 0x%x write fail\n", + __func__, addr); + } else { + dev_info(fuelgauge->dev,"%s: addr: 0x%x is wrong\n", + __func__, x); + } + } + ret = count; + break; + default: + ret = -EINVAL; + } + return ret; +} + +static void max77705_fg_bd_log(struct max77705_fuelgauge_data *fuelgauge) +{ + memset(fuelgauge->d_buf, 0x0, sizeof(fuelgauge->d_buf)); + + snprintf(fuelgauge->d_buf, sizeof(fuelgauge->d_buf), "%d,%d,%d", + fuelgauge->bd_vfocv, fuelgauge->bd_raw_soc, fuelgauge->capacity_max); +} + +static int max77705_fg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max77705_fuelgauge_data *fuelgauge = power_supply_get_drvdata(psy); + enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; + u8 data[2] = { 0, 0 }; + + if (atomic_read(&fuelgauge->shutdown_cnt) > 0) { + dev_info(fuelgauge->dev, "%s: fuelgauge already shutdown\n", __func__); + return -EINVAL; + } + + switch ((int)psp) { + /* Cell voltage (VCELL, mV) */ + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = max77705_get_fuelgauge_value(fuelgauge, FG_VOLTAGE); + break; + /* Additional Voltage Information (mV) */ + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + val->intval = max77705_fg_read_avg_vcell(fuelgauge); + break; + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + val->intval = max77705_fg_read_vfocv(fuelgauge); + break; + /* Current */ + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = max77705_fg_read_current(fuelgauge, val->intval); + break; + /* Average Current */ + case POWER_SUPPLY_PROP_CURRENT_AVG: + switch (val->intval) { + case SEC_BATTERY_CURRENT_UA: + val->intval = max77705_fg_read_avg_current(fuelgauge, + SEC_BATTERY_CURRENT_UA); + break; + case SEC_BATTERY_CURRENT_MA: + default: + fuelgauge->current_avg = val->intval = + max77705_fg_read_avg_current(fuelgauge, + SEC_BATTERY_CURRENT_MA); + break; + } + break; + /* Full Capacity */ + case POWER_SUPPLY_PROP_ENERGY_NOW: + switch (val->intval) { + case SEC_BATTERY_CAPACITY_DESIGNED: + val->intval = max77705_get_fuelgauge_value(fuelgauge, FG_FULLCAP); + break; + case SEC_BATTERY_CAPACITY_ABSOLUTE: + val->intval = max77705_get_fuelgauge_value(fuelgauge, FG_MIXCAP); + break; + case SEC_BATTERY_CAPACITY_TEMPERARY: + val->intval = max77705_get_fuelgauge_value(fuelgauge, FG_AVCAP); + break; + case SEC_BATTERY_CAPACITY_CURRENT: + val->intval = max77705_get_fuelgauge_value(fuelgauge, FG_REPCAP); + break; + case SEC_BATTERY_CAPACITY_AGEDCELL: + val->intval = max77705_get_fuelgauge_value(fuelgauge, FG_FULLCAPNOM); + break; + case SEC_BATTERY_CAPACITY_CYCLE: + val->intval = max77705_get_fuelgauge_value(fuelgauge, FG_CYCLE); + break; + case SEC_BATTERY_CAPACITY_FULL: + val->intval = max77705_get_fuelgauge_value(fuelgauge, FG_FULLCAPREP); + break; + case SEC_BATTERY_CAPACITY_QH: + val->intval = max77705_get_fuelgauge_value(fuelgauge, FG_QH); + break; + case SEC_BATTERY_CAPACITY_VFSOC: + val->intval = max77705_get_fuelgauge_value(fuelgauge, FG_QH_VF_SOC); + break; + case SEC_BATTERY_CAPACITY_RC0: + val->intval = max77705_read_word(fuelgauge->i2c, RCOMP_REG); + break; + } + break; + /* SOC (%) */ + case POWER_SUPPLY_PROP_CAPACITY: + mutex_lock(&fuelgauge->fg_lock); + val->intval = max77705_get_fg_ui_soc(fuelgauge, val); + mutex_unlock(&fuelgauge->fg_lock); + break; + /* Battery Temperature */ + case POWER_SUPPLY_PROP_TEMP: + /* Target Temperature */ + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + val->intval = max77705_get_fuelgauge_value(fuelgauge, FG_TEMPERATURE); + break; +#if defined(CONFIG_EN_OOPS) + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + return -ENODATA; +#endif + case POWER_SUPPLY_PROP_ENERGY_FULL: + { + int fullcap = max77705_get_fuelgauge_value(fuelgauge, FG_FULLCAPNOM); + + val->intval = fullcap * 100 / + (fuelgauge->battery_data->Capacity * fuelgauge->fg_resistor / 2); + pr_info("%s: asoc(%d), fullcap(%d)\n", __func__, + val->intval, fullcap); + } + break; + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + val->intval = fuelgauge->capacity_max; + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + return -ENODATA; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + val->intval = fuelgauge->raw_capacity * + (fuelgauge->battery_data->Capacity * fuelgauge->fg_resistor / 2); + break; + case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: + switch (ext_psp) { + case POWER_SUPPLY_EXT_PROP_FUELGAUGE_LOG: + max77705_fg_read_power_log(fuelgauge); + break; + case POWER_SUPPLY_EXT_PROP_MONITOR_WORK: +#if !defined(CONFIG_SEC_FACTORY) + max77705_fg_periodic_read(fuelgauge); +#endif + break; + case POWER_SUPPLY_EXT_PROP_ERROR_CAUSE: + val->intval = max77705_fg_check_initialization_result(fuelgauge); + break; + case POWER_SUPPLY_EXT_PROP_MEASURE_SYS: + switch (val->intval) { + case SEC_BATTERY_VSYS: + val->intval = max77705_fg_read_vsys(fuelgauge); + break; + case SEC_BATTERY_ISYS_AVG_UA: + val->intval = max77705_fg_read_isys_avg(fuelgauge, + SEC_BATTERY_CURRENT_UA); + break; + case SEC_BATTERY_ISYS_AVG_MA: + val->intval = max77705_fg_read_isys_avg(fuelgauge, + SEC_BATTERY_CURRENT_MA); + break; + case SEC_BATTERY_ISYS_UA: + val->intval = max77705_fg_read_isys(fuelgauge, + SEC_BATTERY_CURRENT_UA); + break; + case SEC_BATTERY_ISYS_MA: + default: + val->intval = max77705_fg_read_isys(fuelgauge, + SEC_BATTERY_CURRENT_MA); + break; + } + break; + case POWER_SUPPLY_EXT_PROP_MEASURE_INPUT: + switch (val->intval) { + case SEC_BATTERY_VBYP: + val->intval = max77705_fg_read_vbyp(fuelgauge); + break; + case SEC_BATTERY_IIN_UA: + val->intval = max77705_fg_read_iin(fuelgauge, + SEC_BATTERY_CURRENT_UA); + break; + case SEC_BATTERY_IIN_MA: + default: + val->intval = max77705_fg_read_iin(fuelgauge, + SEC_BATTERY_CURRENT_MA); + break; + } + break; + case POWER_SUPPLY_EXT_PROP_JIG_GPIO: + if (fuelgauge->pdata->jig_gpio) + val->intval = gpio_get_value(fuelgauge->pdata->jig_gpio); + else + val->intval = -1; + pr_info("%s: jig gpio = %d \n", __func__, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_FILTER_CFG: + max77705_bulk_read(fuelgauge->i2c, FILTER_CFG_REG, 2, data); + val->intval = data[1] << 8 | data[0]; + pr_debug("%s: FilterCFG=0x%04X\n", __func__, data[1] << 8 | data[0]); + break; + case POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED: + val->intval = fuelgauge->is_charging; + break; + case POWER_SUPPLY_EXT_PROP_BATTERY_ID: + if (!val->intval) { + if (fuelgauge->pdata->bat_gpio_cnt > 0) + max77705_reset_bat_id(fuelgauge); +#if defined(CONFIG_ID_USING_BAT_SUBBAT) + val->intval = fuelgauge->battery_data->main_battery_id; +#else + val->intval = fuelgauge->battery_data->battery_id; +#endif + pr_info("%s: bat_id_gpio = %d \n", __func__, val->intval); + } +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + else if (val->intval == SEC_DUAL_BATTERY_SUB) { + if (fuelgauge->pdata->sub_bat_gpio_cnt > 0) + max77705_reset_bat_id(fuelgauge); + val->intval = fuelgauge->battery_data->sub_battery_id; + pr_info("%s: sub_bat_id_gpio = %d \n", __func__, val->intval); + } +#endif + break; + case POWER_SUPPLY_EXT_PROP_BATT_DUMP: + max77705_fg_bd_log(fuelgauge); + val->strval = fuelgauge->d_buf; + break; + case POWER_SUPPLY_EXT_PROP_CHARGE_FULL_REPCAP: + val->intval = fuelgauge->repcap_1st; + break; + case POWER_SUPPLY_EXT_PROP_STATUS_FULL_REPCAP: + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + return 0; +} + +#if defined(CONFIG_UPDATE_BATTERY_DATA) +static int max77705_fuelgauge_parse_dt(struct max77705_fuelgauge_data *fuelgauge); +#endif +static int max77705_fg_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max77705_fuelgauge_data *fuelgauge = power_supply_get_drvdata(psy); + enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; + static bool low_temp_wa; + u8 data[2] = { 0, 0 }; + u16 reg_data; + + if (atomic_read(&fuelgauge->shutdown_cnt) > 0) { + dev_info(fuelgauge->dev, "%s: fuelgauge already shutdown\n", __func__); + return -EINVAL; + } + + switch ((int)psp) { + case POWER_SUPPLY_PROP_STATUS: + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + if (fuelgauge->pdata->capacity_calculation_type & + SEC_FUELGAUGE_CAPACITY_TYPE_DYNAMIC_SCALE) + max77705_fg_calculate_dynamic_scale(fuelgauge, val->intval, true); + break; +#if defined(CONFIG_EN_OOPS) + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + max77705_set_full_value(fuelgauge, val->intval); + break; +#endif + case POWER_SUPPLY_PROP_ONLINE: + fuelgauge->cable_type = val->intval; + if (!is_nocharge_type(fuelgauge->cable_type)) { + /* enable alert */ + if (fuelgauge->vempty_mode >= VEMPTY_MODE_SW_VALERT) { + max77705_fg_set_vempty(fuelgauge, VEMPTY_MODE_HW); + fuelgauge->initial_update_of_soc = true; + max77705_fg_fuelalert_init(fuelgauge, + fuelgauge->pdata->fuel_alert_soc); + } + } + break; + /* Battery Temperature */ + case POWER_SUPPLY_PROP_CAPACITY: + if (val->intval == SEC_FUELGAUGE_CAPACITY_TYPE_RESET) { + if (!max77705_fg_reset(fuelgauge)) + return -EINVAL; + fuelgauge->initial_update_of_soc = true; + if (fuelgauge->pdata->capacity_calculation_type & + SEC_FUELGAUGE_CAPACITY_TYPE_LOST_SOC) + max77705_lost_soc_reset(fuelgauge); + } + break; + case POWER_SUPPLY_PROP_TEMP: + if (val->intval < 0) { + reg_data = max77705_read_word(fuelgauge->i2c, DESIGNCAP_REG); + if (reg_data == fuelgauge->battery_data->Capacity) { + max77705_write_word(fuelgauge->i2c, DESIGNCAP_REG, + fuelgauge->battery_data->Capacity + 3); + pr_info("%s: set the low temp reset! temp : %d, capacity : 0x%x, original capacity : 0x%x\n", + __func__, val->intval, reg_data, + fuelgauge->battery_data->Capacity); + } + } + + if (val->intval < 0 && !low_temp_wa) { + low_temp_wa = true; + max77705_write_word(fuelgauge->i2c, 0x29, 0xCEA7); + pr_info("%s: FilterCFG(0x%0x)\n", __func__, + max77705_read_word(fuelgauge->i2c, 0x29)); + } else if (val->intval > 30 && low_temp_wa) { + low_temp_wa = false; + max77705_write_word(fuelgauge->i2c, 0x29, 0xCEA4); + pr_info("%s: FilterCFG(0x%0x)\n", __func__, + max77705_read_word(fuelgauge->i2c, 0x29)); + } + max77705_fg_write_temp(fuelgauge, val->intval); + break; + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + break; + case POWER_SUPPLY_PROP_ENERGY_NOW: + pr_info("%s: POWER_SUPPLY_PROP_ENERGY_NOW\n", __func__); + max77705_fg_reset_capacity_by_jig_connection(fuelgauge); + break; + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + mutex_lock(&fuelgauge->fg_lock); + pr_info("%s: capacity_max changed, %d -> %d\n", + __func__, fuelgauge->capacity_max, val->intval); + fuelgauge->capacity_max = + max77705_fg_check_capacity_max(fuelgauge, val->intval); + fuelgauge->initial_update_of_soc = true; +#if defined(CONFIG_UI_SOC_PROLONGING) + fuelgauge->g_capacity_max = 990; + fuelgauge->capacity_max_conv = true; +#endif + mutex_unlock(&fuelgauge->fg_lock); + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + { + u16 reg_fullsocthr; + int val_soc = val->intval; + + if (val->intval > fuelgauge->pdata->full_condition_soc + || val->intval <= (fuelgauge->pdata->full_condition_soc - 10)) { + pr_info("%s: abnormal value(%d). so thr is changed to default(%d)\n", + __func__, val->intval, fuelgauge->pdata->full_condition_soc); + val_soc = fuelgauge->pdata->full_condition_soc; + } + + reg_fullsocthr = val_soc << 8; + if (max77705_write_word(fuelgauge->i2c, FULLSOCTHR_REG, + reg_fullsocthr) < 0) { + pr_info("%s: Failed to write FULLSOCTHR_REG\n", __func__); + } + else { + reg_fullsocthr = + max77705_read_word(fuelgauge->i2c, FULLSOCTHR_REG); + pr_info("%s: FullSOCThr %d%%(0x%04X)\n", + __func__, val_soc, reg_fullsocthr); + } + } + break; + case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: + switch (ext_psp) { + case POWER_SUPPLY_EXT_PROP_FILTER_CFG: + /* Set FilterCFG */ + max77705_bulk_read(fuelgauge->i2c, FILTER_CFG_REG, 2, data); + pr_debug("%s: FilterCFG=0x%04X\n", __func__, data[1] << 8 | data[0]); + data[0] &= ~0xF; + data[0] |= (val->intval & 0xF); + max77705_bulk_write(fuelgauge->i2c, FILTER_CFG_REG, 2, data); + + max77705_bulk_read(fuelgauge->i2c, FILTER_CFG_REG, 2, data); + pr_debug("%s: FilterCFG=0x%04X\n", __func__, data[1] << 8 | data[0]); + break; + case POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED: + switch (val->intval) { + case SEC_BAT_CHG_MODE_BUCK_OFF: + case SEC_BAT_CHG_MODE_CHARGING_OFF: + fuelgauge->is_charging = false; + max77705_offset_leakage(fuelgauge); + break; + case SEC_BAT_CHG_MODE_CHARGING: + case SEC_BAT_CHG_MODE_PASS_THROUGH: + fuelgauge->is_charging = true; + max77705_offset_leakage(fuelgauge); + break; + }; + break; +#if defined(CONFIG_UPDATE_BATTERY_DATA) + case POWER_SUPPLY_EXT_PROP_POWER_DESIGN: + max77705_fuelgauge_parse_dt(fuelgauge); + break; +#endif + case POWER_SUPPLY_EXT_PROP_CHARGE_FULL_REPCAP: + { + int ret = 0; + + if (fuelgauge->pdata->capacity_calculation_type & + SEC_FUELGAUGE_CAPACITY_TYPE_REPCAP) { + mutex_lock(&fuelgauge->fg_lock); + pr_info("%s: repcap_1st to be changed, %d -> %d\n", + __func__, fuelgauge->repcap_1st, val->intval); + ret = max77705_fg_check_repcap_to_save(fuelgauge, val->intval); + if (ret < 0) + pr_info("%s: failed to save repcap\n", __func__); + else { + fuelgauge->repcap_1st = ret; + pr_info("%s: saved repcap_1st as, %d\n", + __func__, fuelgauge->repcap_1st); + } + fuelgauge->initial_update_of_soc = true; + mutex_unlock(&fuelgauge->fg_lock); + } + } + break; + case POWER_SUPPLY_EXT_PROP_STATUS_FULL_REPCAP: + if (fuelgauge->pdata->capacity_calculation_type & + SEC_FUELGAUGE_CAPACITY_TYPE_REPCAP) + max77705_fg_calculate_new_repcap_1st(fuelgauge); + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + return 0; +} + +static void max77705_fg_isr_work(struct work_struct *work) +{ + struct max77705_fuelgauge_data *fuelgauge = + container_of(work, struct max77705_fuelgauge_data, isr_work.work); + + /* process for fuel gauge chip */ + max77705_fg_fuelalert_process(fuelgauge); + + __pm_relax(fuelgauge->fuel_alert_ws); +} + +static irqreturn_t max77705_fg_irq_thread(int irq, void *irq_data) +{ + struct max77705_fuelgauge_data *fuelgauge = irq_data; + + pr_info("%s\n", __func__); + + max77705_update_reg(fuelgauge->pmic, MAX77705_PMIC_REG_INTSRC_MASK, + MAX77705_IRQSRC_FG, MAX77705_IRQSRC_FG); + if (fuelgauge->is_fuel_alerted) + return IRQ_HANDLED; + + __pm_stay_awake(fuelgauge->fuel_alert_ws); + fuelgauge->is_fuel_alerted = true; + schedule_delayed_work(&fuelgauge->isr_work, 0); + + return IRQ_HANDLED; +} + +#ifdef CONFIG_OF +#define PROPERTY_NAME_SIZE 128 +static void max77705_fuelgauge_parse_dt_lost_soc( + struct max77705_fuelgauge_data *fuelgauge, struct device_node *np) +{ + int ret; + + ret = of_property_read_u32(np, "fuelgauge,lost_soc_trig_soc", + &fuelgauge->lost_soc.trig_soc); + if (ret < 0) + fuelgauge->lost_soc.trig_soc = 1000; /* 100% */ + + ret = of_property_read_u32(np, "fuelgauge,lost_soc_trig_d_soc", + &fuelgauge->lost_soc.trig_d_soc); + if (ret < 0) + fuelgauge->lost_soc.trig_d_soc = 20; /* 2% */ + + ret = of_property_read_u32(np, "fuelgauge,lost_soc_trig_scale", + &fuelgauge->lost_soc.trig_scale); + if (ret < 0) + fuelgauge->lost_soc.trig_scale = 2; /* 2x */ + + ret = of_property_read_u32(np, "fuelgauge,lost_soc_guarantee_soc", + &fuelgauge->lost_soc.guarantee_soc); + if (ret < 0) + fuelgauge->lost_soc.guarantee_soc = 20; /* 2% */ + + ret = of_property_read_u32(np, "fuelgauge,lost_soc_min_vol", + &fuelgauge->lost_soc.min_vol); + if (ret < 0) + fuelgauge->lost_soc.min_vol = 3200; /* 3.2V */ + + ret = of_property_read_u32(np, "fuelgauge,lost_soc_min_weight", + &fuelgauge->lost_soc.min_weight); + if (ret < 0 || fuelgauge->lost_soc.min_weight <= 10) + fuelgauge->lost_soc.min_weight = 20; /* 2.0 */ + + pr_info("%s: trigger soc(%d), d_soc(%d), scale(%d), guarantee_soc(%d), min_vol(%d), min_weight(%d)\n", + __func__, fuelgauge->lost_soc.trig_soc, fuelgauge->lost_soc.trig_d_soc, + fuelgauge->lost_soc.trig_scale, fuelgauge->lost_soc.guarantee_soc, + fuelgauge->lost_soc.min_vol, fuelgauge->lost_soc.min_weight); +} + +__visible_for_testing int max77705_get_bat_id(int bat_id[], int bat_gpio_cnt) +{ + int battery_id = 0; + int i = 0; + + for (i = (bat_gpio_cnt - 1); i >= 0; i--) + battery_id += bat_id[i] << i; + + return battery_id; +} +EXPORT_SYMBOL_KUNIT(max77705_get_bat_id); + +static void max77705_reset_bat_id(struct max77705_fuelgauge_data *fuelgauge) +{ + int bat_id[BAT_GPIO_NO] = {0, }; + int i = 0; + + for (i = 0; i < fuelgauge->pdata->bat_gpio_cnt; i++) + bat_id[i] = gpio_get_value(fuelgauge->pdata->bat_id_gpio[i]); + + fuelgauge->battery_data->battery_id = + max77705_get_bat_id(bat_id, fuelgauge->pdata->bat_gpio_cnt); + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + for (i = 0; i < fuelgauge->pdata->sub_bat_gpio_cnt; i++) + bat_id[i] = gpio_get_value(fuelgauge->pdata->sub_bat_id_gpio[i]); + + fuelgauge->battery_data->sub_battery_id = + max77705_get_bat_id(bat_id, fuelgauge->pdata->sub_bat_gpio_cnt); +#if defined(CONFIG_ID_USING_BAT_SUBBAT) + fuelgauge->battery_data->main_battery_id = fuelgauge->battery_data->battery_id; + fuelgauge->battery_data->battery_id = + (fuelgauge->battery_data->battery_id << fuelgauge->pdata->sub_bat_gpio_cnt ) | fuelgauge->battery_data->sub_battery_id; +#endif +#endif +} + +static int max77705_fuelgauge_parse_dt(struct max77705_fuelgauge_data *fuelgauge) +{ + struct device_node *np = of_find_node_by_name(NULL, "max77705-fuelgauge"); + max77705_fuelgauge_platform_data_t *pdata = fuelgauge->pdata; + int ret, len; + u8 battery_id = 0; + int i = 0; + int bat_id[BAT_GPIO_NO] = {0, }; + const u32 *p; + + /* reset, irq gpio info */ + if (np == NULL) { + pr_err("%s: np NULL\n", __func__); + } else { + ret = of_property_read_u32(np, "fuelgauge,capacity_max", + &pdata->capacity_max); + if (ret < 0) + pr_err("%s: error reading capacity_max %d\n", __func__, ret); + + ret = of_property_read_u32(np, "fuelgauge,capacity_max_margin", + &pdata->capacity_max_margin); + if (ret < 0) { + pr_err("%s: error reading capacity_max_margin %d\n", + __func__, ret); + pdata->capacity_max_margin = 300; + } + + ret = of_property_read_u32(np, "fuelgauge,capacity_min", + &pdata->capacity_min); + if (ret < 0) + pr_err("%s: error reading capacity_min %d\n", __func__, ret); + + ret = of_property_read_u32(np, "fuelgauge,capacity_calculation_type", + &pdata->capacity_calculation_type); + if (ret < 0) + pr_err("%s: error reading capacity_calculation_type %d\n", + __func__, ret); + + ret = of_property_read_u32(np, "fuelgauge,fuel_alert_soc", + &pdata->fuel_alert_soc); + if (ret < 0) + pr_err("%s: error reading pdata->fuel_alert_soc %d\n", + __func__, ret); + + pdata->repeated_fuelalert = of_property_read_bool(np, + "fuelgauge,repeated_fuelalert"); + + fuelgauge->using_temp_compensation = of_property_read_bool(np, + "fuelgauge,using_temp_compensation"); + if (fuelgauge->using_temp_compensation) { + ret = of_property_read_u32(np, "fuelgauge,low_temp_limit", + &fuelgauge->low_temp_limit); + if (ret < 0) { + pr_err("%s: error reading low temp limit %d\n", + __func__, ret); + fuelgauge->low_temp_limit = 0; /* Default: 0'C */ + } + + ret = of_property_read_u32(np, + "fuelgauge,vempty_recover_time", &fuelgauge->vempty_recover_time); + if (ret < 0) { + pr_err("%s: error reading low temp limit %d\n", + __func__, ret); + fuelgauge->vempty_recover_time = 0; /* Default: 0 */ + } + } + + fuelgauge->using_hw_vempty = of_property_read_bool(np, + "fuelgauge,using_hw_vempty"); + if (fuelgauge->using_hw_vempty) { + ret = of_property_read_u32(np, "fuelgauge,sw_v_empty_voltage", + &fuelgauge->battery_data->sw_v_empty_vol); + if (ret < 0) + pr_err("%s: error reading sw_v_empty_default_vol %d\n", + __func__, ret); + + ret = of_property_read_u32(np, "fuelgauge,sw_v_empty_voltage_cisd", + &fuelgauge->battery_data->sw_v_empty_vol_cisd); + if (ret < 0) { + pr_err("%s: error reading sw_v_empty_default_vol_cise %d\n", + __func__, ret); + fuelgauge->battery_data->sw_v_empty_vol_cisd = 3100; + } + + ret = of_property_read_u32(np, "fuelgauge,sw_v_empty_recover_voltage", + &fuelgauge->battery_data->sw_v_empty_recover_vol); + if (ret < 0) + pr_err("%s: error reading sw_v_empty_recover_vol %d\n", + __func__, ret); + + pr_info("%s : SW V Empty (%d)mV, SW V Empty recover (%d)mV\n", + __func__, fuelgauge->battery_data->sw_v_empty_vol, + fuelgauge->battery_data->sw_v_empty_recover_vol); + } + + pdata->jig_gpio = of_get_named_gpio(np, "fuelgauge,jig_gpio", 0); + if (pdata->jig_gpio >= 0) { + pdata->jig_irq = gpio_to_irq(pdata->jig_gpio); + pdata->jig_low_active = of_property_read_bool(np, + "fuelgauge,jig_low_active"); + } else { + pr_err("%s: error reading jig_gpio = %d\n", + __func__, pdata->jig_gpio); + pdata->jig_gpio = 0; + } + + ret = of_property_read_u32(np, "fuelgauge,fg_resistor", + &fuelgauge->fg_resistor); + if (ret < 0) { + pr_err("%s: error reading fg_resistor %d\n", __func__, ret); + fuelgauge->fg_resistor = 1; + } +#if defined(CONFIG_EN_OOPS) + ret = of_property_read_u32(np, "fuelgauge,ichgterm", + &fuelgauge->battery_data->ichgterm); + if (ret < 0) + pr_err("%s: error reading ichgterm %d\n", __func__, ret); + + ret = of_property_read_u32(np, "fuelgauge,ichgterm_2nd", + &fuelgauge->battery_data->ichgterm_2nd); + if (ret < 0) + pr_err("%s: error reading ichgterm_2nd %d\n", __func__, ret); + + ret = of_property_read_u32(np, "fuelgauge,misccfg", + &fuelgauge->battery_data->misccfg); + if (ret < 0) + pr_err("%s: error reading misccfg %d\n", __func__, ret); + + ret = of_property_read_u32(np, "fuelgauge,misccfg_2nd", + &fuelgauge->battery_data->misccfg_2nd); + if (ret < 0) + pr_err("%s: error reading misccfg_2nd %d\n", __func__, ret); + + ret = of_property_read_u32(np, "fuelgauge,fullsocthr", + &fuelgauge->battery_data->fullsocthr); + if (ret < 0) + pr_err("%s: error reading fullsocthr %d\n", __func__, ret); + + ret = of_property_read_u32(np, "fuelgauge,fullsocthr_2nd", + &fuelgauge->battery_data->fullsocthr_2nd); + if (ret < 0) + pr_err("%s: error reading fullsocthr_2nd %d\n", __func__, ret); +#endif + + pdata->bat_gpio_cnt = of_gpio_named_count(np, "fuelgauge,bat_id_gpio"); + /* not run if gpio gpio cnt is less than 1 */ + if (pdata->bat_gpio_cnt > 0) { + pr_info("%s: Has %d bat_id_gpios\n", __func__, pdata->bat_gpio_cnt); + if (pdata->bat_gpio_cnt > BAT_GPIO_NO) { + pr_err("%s: bat_id_gpio, catch out-of bounds array read\n", + __func__); + pdata->bat_gpio_cnt = BAT_GPIO_NO; + } + for (i = 0; i < pdata->bat_gpio_cnt; i++) { + pdata->bat_id_gpio[i] = of_get_named_gpio(np, "fuelgauge,bat_id_gpio", i); + if (pdata->bat_id_gpio[i] >= 0) { + bat_id[i] = gpio_get_value(pdata->bat_id_gpio[i]); + } else { + pr_err("%s: error reading bat_id_gpio = %d\n", + __func__, pdata->bat_id_gpio[i]); + bat_id[i] = 0; + } + } + fuelgauge->battery_data->battery_id = + max77705_get_bat_id(bat_id, pdata->bat_gpio_cnt); + } else + fuelgauge->battery_data->battery_id = 0; + + battery_id = fuelgauge->battery_data->battery_id; + pr_info("%s: battery_id(batt_id:%d) = %d\n", __func__, fuelgauge->battery_data->battery_id, battery_id); + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + pdata->sub_bat_gpio_cnt = of_gpio_named_count(np, "fuelgauge,sub_bat_id_gpio"); + /* not run if gpio gpio cnt is less than 1 */ + if (pdata->sub_bat_gpio_cnt > 0) { + pr_info("%s: Has %d sub_bat_id_gpios\n", __func__, pdata->sub_bat_gpio_cnt); + if (pdata->sub_bat_gpio_cnt > BAT_GPIO_NO) { + pr_err("%s: sub_bat_id_gpio, catch out-of bounds array read\n", + __func__); + pdata->sub_bat_gpio_cnt = BAT_GPIO_NO; + } + for (i = 0; i < pdata->sub_bat_gpio_cnt; i++) { + pdata->sub_bat_id_gpio[i] = of_get_named_gpio(np, "fuelgauge,sub_bat_id_gpio", i); + if (pdata->sub_bat_id_gpio[i] >= 0) { + bat_id[i] = gpio_get_value(pdata->sub_bat_id_gpio[i]); + } else { + pr_err("%s: error reading sub_bat_id_gpio = %d\n", + __func__, pdata->sub_bat_id_gpio[i]); + bat_id[i] = 0; + } + } + fuelgauge->battery_data->sub_battery_id = + max77705_get_bat_id(bat_id, pdata->sub_bat_gpio_cnt); + } else + fuelgauge->battery_data->sub_battery_id = 0; + + pr_info("%s: sub_battery_id = %d\n", __func__, fuelgauge->battery_data->sub_battery_id); +#if defined (CONFIG_ID_USING_BAT_SUBBAT) + fuelgauge->battery_data->main_battery_id = fuelgauge->battery_data->battery_id; + fuelgauge->battery_data->battery_id = + (fuelgauge->battery_data->battery_id << pdata->sub_bat_gpio_cnt ) | fuelgauge->battery_data->sub_battery_id; + battery_id = fuelgauge->battery_data->battery_id; + pr_info("%s: Effective battery_id(batt_id:%d) = %d\n", __func__, fuelgauge->battery_data->battery_id, battery_id); +#endif +#endif + if (fuelgauge->pdata->capacity_calculation_type & + SEC_FUELGAUGE_CAPACITY_TYPE_LOST_SOC) + max77705_fuelgauge_parse_dt_lost_soc(fuelgauge, np); + + /* get battery_params node */ + np = of_find_node_by_name(of_node_get(np), "battery_params"); + if (np == NULL) { + pr_err("%s: Cannot find child node \"battery_params\"\n", __func__); + return -EINVAL; + } else { + char prop_name[PROPERTY_NAME_SIZE]; + + snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", + battery_id, "v_empty"); + ret = of_property_read_u32(np, prop_name, + &fuelgauge->battery_data->V_empty); + if (ret < 0) { + pr_err("%s: error reading v_empty %d\n", __func__, ret); + pr_err("%s: battery data: %d does not exist\n", __func__, battery_id); + battery_id = 0; + snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", + battery_id, "v_empty"); + ret = of_property_read_u32(np, prop_name, + &fuelgauge->battery_data->V_empty); + if (ret < 0) + pr_err("%s: error reading v_empty of default battery data %d\n", + __func__, ret); + } + + snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", + battery_id, "v_empty_origin"); + ret = of_property_read_u32(np, prop_name, + &fuelgauge->battery_data->V_empty_origin); + if (ret < 0) + pr_err("%s: error reading v_empty_origin %d\n", __func__, ret); + + snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", + battery_id, "capacity"); + ret = of_property_read_u32(np, prop_name, + &fuelgauge->battery_data->Capacity); + if (ret < 0) + pr_err("%s: error reading capacity %d\n", __func__, ret); + + snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", + battery_id, "fg_reset_wa_data"); + len = of_property_count_u32_elems(np, prop_name); + if (len != FG_RESET_DATA_COUNT) { + pr_err("%s fg_reset_wa_data is %d < %d, need more data\n", + __func__, len, FG_RESET_DATA_COUNT); + fuelgauge->fg_reset_data = NULL; + } else { + fuelgauge->fg_reset_data = kzalloc(sizeof(struct fg_reset_wa), GFP_KERNEL); + ret = of_property_read_u32_array(np, prop_name, + (u32 *) fuelgauge->fg_reset_data, FG_RESET_DATA_COUNT); + if (ret < 0) { + pr_err("%s failed to read fuelgauge->fg_reset_wa_data: %d\n", + __func__, ret); + + kfree(fuelgauge->fg_reset_data); + fuelgauge->fg_reset_data = NULL; + } + } + pr_info("%s: V_empty(0x%04x), v_empty_origin(0x%04x), capacity(0x%04x)\n", + __func__, fuelgauge->battery_data->V_empty, + fuelgauge->battery_data->V_empty_origin, fuelgauge->battery_data->Capacity); + + snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", + battery_id, "data_ver"); + ret = of_property_read_u32(np, prop_name, + &fuelgauge->data_ver); + if (ret < 0) { + pr_err("%s: error reading data_ver %d\n", __func__, ret); + fuelgauge->data_ver = 0xff; + } + pr_info("%s: fg data_ver (%x)\n", __func__, fuelgauge->data_ver); + + snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", + battery_id, "selected_reg"); + + p = of_get_property(np, prop_name, &len); + if (p) { + fuelgauge->verify_selected_reg = kzalloc(len, GFP_KERNEL); + fuelgauge->verify_selected_reg_length = len / (int)sizeof(struct verify_reg); + pr_err("%s: len= %ld, length= %d, %d\n", __func__, + sizeof(int) * len, len, fuelgauge->verify_selected_reg_length); + ret = of_property_read_u32_array(np, prop_name, + (u32 *)fuelgauge->verify_selected_reg, len / sizeof(u32)); + if (ret) { + pr_err("%s: failed to read fuelgauge->verify_selected_reg: %d\n", + __func__, ret); + kfree(fuelgauge->verify_selected_reg); + fuelgauge->verify_selected_reg = NULL; + } else { + for (i = 0; i < fuelgauge->verify_selected_reg_length; i++) { + if (fuelgauge->verify_selected_reg[i].addr == QRTABLE00_REG) + fuelgauge->q_res_table[0] = fuelgauge->verify_selected_reg[i].data; + else if (fuelgauge->verify_selected_reg[i].addr == QRTABLE10_REG) + fuelgauge->q_res_table[1] = fuelgauge->verify_selected_reg[i].data; + else if (fuelgauge->verify_selected_reg[i].addr == QRTABLE20_REG) + fuelgauge->q_res_table[2] = fuelgauge->verify_selected_reg[i].data; + else if (fuelgauge->verify_selected_reg[i].addr == QRTABLE30_REG) + fuelgauge->q_res_table[3] = fuelgauge->verify_selected_reg[i].data; + } + } + } else { + pr_err("%s: there is not selected_reg\n", __func__); + fuelgauge->verify_selected_reg = NULL; + } + + snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", + battery_id, "coff_origin"); + ret = of_property_read_u32(np, prop_name, + &fuelgauge->battery_data->coff_origin); + if (ret < 0) + pr_err("%s: error reading coff_origin %d\n", __func__, ret); + + snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", + battery_id, "coff_charging"); + ret = of_property_read_u32(np, prop_name, + &fuelgauge->battery_data->coff_charging); + if (ret < 0) + pr_err("%s: error reading coff_charging %d\n", __func__, ret); + + snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", + battery_id, "cgain_origin"); + ret = of_property_read_u32(np, prop_name, + &fuelgauge->battery_data->cgain_origin); + if (ret < 0) + pr_err("%s: error reading cgain_origin %d\n", __func__, ret); + + snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", + battery_id, "cgain_charging"); + ret = of_property_read_u32(np, prop_name, + &fuelgauge->battery_data->cgain_charging); + if (ret < 0) + pr_err("%s: error reading cgain_charging %d\n", __func__, ret); + + pr_info("%s: V_empty(0x%04x), v_empty_origin(0x%04x), capacity(0x%04x)," + "coff_origin(0x%04x), coff_charging(0x%04x)," + "cgain_origin(0x%04x), cgain_charging(0x%04x)\n", + __func__, fuelgauge->battery_data->V_empty, fuelgauge->battery_data->V_empty_origin, fuelgauge->battery_data->Capacity, + fuelgauge->battery_data->coff_origin, fuelgauge->battery_data->coff_charging, + fuelgauge->battery_data->cgain_origin, fuelgauge->battery_data->cgain_charging); + } + + np = of_find_node_by_name(NULL, "battery"); + ret = of_property_read_u32(np, "battery,thermal_source", + &pdata->thermal_source); + if (ret < 0) + pr_err("%s: error reading pdata->thermal_source %d\n", + __func__, ret); + + ret = of_property_read_u32(np, "battery,full_condition_soc", + &pdata->full_condition_soc); + if (ret) { + pdata->full_condition_soc = 93; + pr_info("%s: Full condition soc is Empty\n", __func__); + } + + pr_info("%s: thermal: %d, jig_gpio: %d, capacity_max: %d\n" + "capacity_max_margin: %d, capacity_min: %d\n" + "calculation_type: 0x%x, fuel_alert_soc: %d,\n" + "repeated_fuelalert: %d, fg_resistor : %d\n", + __func__, pdata->thermal_source, pdata->jig_gpio, pdata->capacity_max, + pdata->capacity_max_margin, pdata->capacity_min, + pdata->capacity_calculation_type, pdata->fuel_alert_soc, + pdata->repeated_fuelalert, fuelgauge->fg_resistor); + } + pr_info("%s: (%d)\n", __func__, fuelgauge->battery_data->Capacity); + + return 0; +} +#endif + +static const struct power_supply_desc max77705_fuelgauge_power_supply_desc = { + .name = "max77705-fuelgauge", + .type = POWER_SUPPLY_TYPE_UNKNOWN, + .properties = max77705_fuelgauge_props, + .num_properties = ARRAY_SIZE(max77705_fuelgauge_props), + .get_property = max77705_fg_get_property, + .set_property = max77705_fg_set_property, + .no_thermal = true, +}; + +static int max77705_fuelgauge_probe(struct platform_device *pdev) +{ + struct max77705_dev *max77705 = dev_get_drvdata(pdev->dev.parent); + struct max77705_platform_data *pdata = dev_get_platdata(max77705->dev); + max77705_fuelgauge_platform_data_t *fuelgauge_data; + struct max77705_fuelgauge_data *fuelgauge; + struct power_supply_config fuelgauge_cfg = { }; + union power_supply_propval raw_soc_val; + int ret = 0; + + pr_info("%s: max77705 Fuelgauge Driver Loading\n", __func__); + + fuelgauge = kzalloc(sizeof(*fuelgauge), GFP_KERNEL); + if (!fuelgauge) + return -ENOMEM; + + fuelgauge_data = kzalloc(sizeof(max77705_fuelgauge_platform_data_t), GFP_KERNEL); + if (!fuelgauge_data) { + ret = -ENOMEM; + goto err_free; + } + + mutex_init(&fuelgauge->fg_lock); + + fuelgauge->dev = &pdev->dev; + fuelgauge->pdata = fuelgauge_data; + fuelgauge->i2c = max77705->fuelgauge; + fuelgauge->pmic = max77705->i2c; + fuelgauge->max77705_pdata = pdata; +#if defined(CONFIG_OF) + fuelgauge->battery_data = kzalloc(sizeof(struct battery_data_t), GFP_KERNEL); + if (!fuelgauge->battery_data) { + pr_err("Failed to allocate memory\n"); + ret = -ENOMEM; + goto err_pdata_free; + } + ret = max77705_fuelgauge_parse_dt(fuelgauge); + if (ret < 0) { + pr_err("%s not found fg dt! ret[%d]\n", __func__, ret); + goto err_data_free; + } +#endif + + fuelgauge->capacity_max = fuelgauge->pdata->capacity_max; +#if !defined(CONFIG_UI_SOC_PROLONGING) + fuelgauge->g_capacity_max = 0; + fuelgauge->capacity_max_conv = false; +#endif + max77705_lost_soc_reset(fuelgauge); + + raw_soc_val.intval = max77705_get_fuelgauge_value(fuelgauge, FG_RAW_SOC) / 10; + + if (raw_soc_val.intval > fuelgauge->capacity_max) + max77705_fg_calculate_dynamic_scale(fuelgauge, 100, false); + + fuelgauge->repcap_1st = 0; + + if (!max77705_fg_init(fuelgauge)) { + pr_err("%s: Failed to Initialize Fuelgauge\n", __func__); + ret = -ENODEV; + goto err_data_free; + } + + fuelgauge->initial_update_of_alert = false; + /* SW/HW init code. SW/HW V Empty mode must be opposite ! */ + fuelgauge->vempty_init_flag = false; /* default value */ + pr_info("%s: SW/HW V empty init\n", __func__); + max77705_fg_set_vempty(fuelgauge, VEMPTY_MODE_SW); + + fuelgauge_cfg.drv_data = fuelgauge; + + fuelgauge->psy_fg = power_supply_register(&pdev->dev, + &max77705_fuelgauge_power_supply_desc, + &fuelgauge_cfg); + if (IS_ERR(fuelgauge->psy_fg)) { + ret = PTR_ERR(fuelgauge->psy_fg); + pr_err("%s: Failed to Register psy_fg(%d)\n", __func__, ret); + goto err_data_free; + } + + fuelgauge->fg_irq = pdata->irq_base + MAX77705_FG_IRQ_ALERT; + pr_info("[%s]IRQ_BASE(%d) FG_IRQ(%d)\n", + __func__, pdata->irq_base, fuelgauge->fg_irq); + + fuelgauge->is_fuel_alerted = false; + if (fuelgauge->pdata->fuel_alert_soc >= 0) { + if (max77705_fg_fuelalert_init(fuelgauge, + fuelgauge->pdata->fuel_alert_soc)) { + fuelgauge->fuel_alert_ws = wakeup_source_register(&pdev->dev, "fuel_alerted"); + if (fuelgauge->fg_irq) { + INIT_DELAYED_WORK(&fuelgauge->isr_work, + max77705_fg_isr_work); + + ret = request_threaded_irq(fuelgauge->fg_irq, NULL, + max77705_fg_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "fuelgauge-irq", fuelgauge); + if (ret) { + pr_err("%s: Failed to Request IRQ (fg_irq)\n", __func__); + goto err_supply_unreg; + } + } + } else { + pr_err("%s: Failed to Initialize Fuel-alert\n", __func__); + goto err_supply_unreg; + } + } + + if (fuelgauge->pdata->jig_gpio) { + ret = request_threaded_irq(fuelgauge->pdata->jig_irq, NULL, + max77705_jig_irq_thread, + (fuelgauge->pdata->jig_low_active ? + IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING) + | IRQF_ONESHOT, + "jig-irq", fuelgauge); + if (ret) { + pr_info("%s: Failed to Request IRQ (jig_gpio)\n", __func__); + //goto err_supply_unreg; + } + + /* initial check for the JIG */ + if (max77705_check_jig_status(fuelgauge)) + max77705_fg_reset_capacity_by_jig_connection(fuelgauge); + } + + ret = max77705_fg_create_attrs(&fuelgauge->psy_fg->dev); + if (ret) { + dev_err(fuelgauge->dev,"%s : Failed to create_attrs\n", __func__); + goto err_irq; + } + + fuelgauge->err_cnt = 0; + fuelgauge->sleep_initial_update_of_soc = false; + fuelgauge->initial_update_of_soc = true; + fuelgauge->valert_count_flag = false; + atomic_set(&fuelgauge->shutdown_cnt, 0); + platform_set_drvdata(pdev, fuelgauge); + + sec_chg_set_dev_init(SC_DEV_FG); + + pr_info("%s: max77705 Fuelgauge Driver Loaded\n", __func__); + return 0; + +err_irq: + free_irq(fuelgauge->fg_irq, fuelgauge); + free_irq(fuelgauge->pdata->jig_irq, fuelgauge); +err_supply_unreg: + power_supply_unregister(fuelgauge->psy_fg); + wakeup_source_unregister(fuelgauge->fuel_alert_ws); +err_data_free: +#if defined(CONFIG_OF) + kfree(fuelgauge->battery_data); +#endif +err_pdata_free: + kfree(fuelgauge_data); + mutex_destroy(&fuelgauge->fg_lock); +err_free: + kfree(fuelgauge); + + return ret; +} + +static int max77705_fuelgauge_remove(struct platform_device *pdev) +{ + struct max77705_fuelgauge_data *fuelgauge = platform_get_drvdata(pdev); + + pr_info("%s: ++\n", __func__); + + if (fuelgauge) { + if (fuelgauge->psy_fg) + power_supply_unregister(fuelgauge->psy_fg); + + free_irq(fuelgauge->fg_irq, fuelgauge); + free_irq(fuelgauge->pdata->jig_irq, fuelgauge); + wakeup_source_unregister(fuelgauge->fuel_alert_ws); +#if defined(CONFIG_OF) + kfree(fuelgauge->battery_data); +#endif + kfree(fuelgauge->pdata); + mutex_destroy(&fuelgauge->fg_lock); + kfree(fuelgauge); + } + + pr_info("%s: --\n", __func__); + + return 0; +} + +static int max77705_fuelgauge_suspend(struct device *dev) +{ + struct max77705_fuelgauge_data *fuelgauge = dev_get_drvdata(dev); + + pr_debug("%s: ++\n", __func__); + + if (fuelgauge->pdata->jig_irq) { + disable_irq(fuelgauge->pdata->jig_irq); + enable_irq_wake(fuelgauge->pdata->jig_irq); + } + + return 0; +} + +static int max77705_fuelgauge_resume(struct device *dev) +{ + struct max77705_fuelgauge_data *fuelgauge = dev_get_drvdata(dev); + + fuelgauge->sleep_initial_update_of_soc = true; + + pr_debug("%s: ++\n", __func__); + + if (fuelgauge->pdata->jig_irq) { + disable_irq_wake(fuelgauge->pdata->jig_irq); + enable_irq(fuelgauge->pdata->jig_irq); + } + + return 0; +} + +static void max77705_fuelgauge_shutdown(struct platform_device *pdev) +{ + struct max77705_fuelgauge_data *fuelgauge = platform_get_drvdata(pdev); + + pr_info("%s: ++\n", __func__); + + if (fuelgauge) { + atomic_inc(&fuelgauge->shutdown_cnt); + + if (fuelgauge->i2c) { + max77705_offset_leakage_default(fuelgauge); + } + if (fuelgauge->fg_irq) + free_irq(fuelgauge->fg_irq, fuelgauge); + if (fuelgauge->pdata->jig_gpio) + free_irq(fuelgauge->pdata->jig_irq, fuelgauge); + + cancel_delayed_work(&fuelgauge->isr_work); + } + + pr_info("%s: --\n", __func__); +} + +static SIMPLE_DEV_PM_OPS(max77705_fuelgauge_pm_ops, max77705_fuelgauge_suspend, + max77705_fuelgauge_resume); + +static struct platform_driver max77705_fuelgauge_driver = { + .driver = { + .name = "max77705-fuelgauge", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &max77705_fuelgauge_pm_ops, +#endif + }, + .probe = max77705_fuelgauge_probe, + .remove = max77705_fuelgauge_remove, + .shutdown = max77705_fuelgauge_shutdown, +}; + +static int __init max77705_fuelgauge_init(void) +{ + pr_info("%s:\n", __func__); + return platform_driver_register(&max77705_fuelgauge_driver); +} + +static void __exit max77705_fuelgauge_exit(void) +{ + platform_driver_unregister(&max77705_fuelgauge_driver); +} + +module_init(max77705_fuelgauge_init); +module_exit(max77705_fuelgauge_exit); + +MODULE_DESCRIPTION("Samsung max77705 Fuel Gauge Driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/battery/fuelgauge/max77705_fuelgauge/max77705_fuelgauge.dtsi b/drivers/battery/fuelgauge/max77705_fuelgauge/max77705_fuelgauge.dtsi new file mode 100644 index 000000000000..40a97496b3d1 --- /dev/null +++ b/drivers/battery/fuelgauge/max77705_fuelgauge/max77705_fuelgauge.dtsi @@ -0,0 +1,178 @@ +&smd { + max77705_fuelgauge: max77705-fuelgauge { + /* for 4500mAh battery */ + status = "okay"; + fuelgauge,fuel_alert_soc = <1>; + fuelgauge,jig_gpio = ; /* IFC_SENSE_INT_AP */ + fuelgauge,jig_low_active; + fuelgauge,capacity_max = <1000>; + fuelgauge,capacity_max_margin = <300>; + fuelgauge,capacity_min = <0>; + fuelgauge,capacity_calculation_type = <28>; + fuelgauge,repeated_fuelalert; + fuelgauge,using_temp_compensation; + fuelgauge,low_temp_limit = <100>; + fuelgauge,vempty_recover_time = <180>; /* 3 mins */ + fuelgauge,using_hw_vempty; + fuelgauge,sw_v_empty_voltage = <3200>; + fuelgauge,sw_v_empty_voltage_cisd = <3100>; + fuelgauge,sw_v_empty_recover_voltage = <3480>; + fuelgauge,fg_resistor = <2>; +#if 1 + fuelgauge,bat_id_gpio = < +#if 1 + SEC_GPIO_REF(AP,tlmm,168) 0 /* BAT_ID_GPIO 1 */ +#endif + SEC_GPIO_REF(AP,tlmm,167) 0 /* BAT_ID_GPIO 0 */ + >; +#endif +#if 0 + fuelgauge,sub_bat_id_gpio = < +#if 0 + SEC_GPIO_REF(${sub_bat_id_gpio_2}) 0 /* SUB_BAT_ID_GPIO 1 */ +#endif + SEC_GPIO_REF(${sub_bat_id_gpio}) 0 /* SUB_BAT_ID_GPIO 0 */ + >; +#endif + }; +}; + +/* /home/dpi/qb5_8814/workspace/P4_1716/android/kernel_platform/kmodule/battery/stable/eureka/fuelgauge/max77705/e3q/max77705_fuelgauge.dtsi */ +&max77705_fuelgauge { + fuelgauge,capacity_calculation_type = <0x5C>; + fuelgauge,lost_soc_trig_soc = <1000>; /* 100.0% */ + fuelgauge,lost_soc_trig_d_soc = <20>; /* 2% */ + fuelgauge,lost_soc_trig_scale = <2>; /* 2x */ + fuelgauge,lost_soc_guarantee_soc = <30>; /* 3.0% */ + fuelgauge,lost_soc_min_vol = <3200>; /* 3200mV */ + fuelgauge,fg_resistor = <5>; /* 2 milliohm */ + fuelgauge,scale_to_102; + + battery_params { + /* + * BAT_ID_1/BAT_ID_2 + * 11: SDI, 10: ATL + * 01: TBD, 00: TBD + */ + + /* 00 : battery data */ + battery0,v_empty = <0xA561>; /* Empty: 3400mV, Recover: 4000mV */ + battery0,v_empty_origin = <0x7D54>; /* Empty: 2500mV, Recover: 3360mV */ + battery0,capacity = <0x076B>; + /* fullcapnom dPacc dQacc RCOMP0 TempCo */ + battery0,fg_reset_wa_data = <0x076A 0x3200 0x01DA 0x002F 0x171F>; + + battery0,data_ver = <0x2>; + + battery0,selected_reg = < + 0x02 0x7F80 /* default */ + 0x12 0x3C00 /* QResidual00 */ + 0x1E 0x026C /* ICHGTerm */ + 0x21 0x6200 /* default */ + 0x22 0x1D00 /* QResidual10 */ + 0x2A 0x023C /* RelaxCFG */ + 0x2C 0xE3E1 /* TGAIN */ + 0x2D 0x290E /* TOFF */ + 0x2E 0x0400 /* CGAIN */ + 0x2F 0x0001 /* COFF */ + 0x32 0x0D80 /* QResidual20 */ + 0x33 0xFFFF /* default */ + 0x37 0x05E0 /* default */ + 0x42 0x0B00 /* QResidual30 */ + 0xB4 0x7F80 /* default */ + 0xB8 0x0000 /* default */ + 0xB9 0x006B /* default */ + 0xBA 0x090C /* default */ + >; + + /* 01 : battery data */ + battery1,v_empty = <0xA561>; /* Empty: 3400mV, Recover: 4000mV */ + battery1,v_empty_origin = <0x7D54>; /* Empty: 2500mV, Recover: 3360mV */ + battery1,capacity = <0x076B>; + /* fullcapnom dPacc dQacc RCOMP0 TempCo */ + battery1,fg_reset_wa_data = <0x076A 0x3200 0x01DA 0x002F 0x171F>; + + battery1,data_ver = <0x2>; + + battery1,selected_reg = < + 0x02 0x7F80 /* default */ + 0x12 0x3C00 /* QResidual00 */ + 0x1E 0x026C /* ICHGTerm */ + 0x21 0x6200 /* default */ + 0x22 0x1D00 /* QResidual10 */ + 0x2A 0x023C /* RelaxCFG */ + 0x2C 0xE3E1 /* TGAIN */ + 0x2D 0x290E /* TOFF */ + 0x2E 0x0400 /* CGAIN */ + 0x2F 0x0001 /* COFF */ + 0x32 0x0D80 /* QResidual20 */ + 0x33 0xFFFF /* default */ + 0x37 0x05E0 /* default */ + 0x42 0x0B00 /* QResidual30 */ + 0xB4 0x7F80 /* default */ + 0xB8 0x0000 /* default */ + 0xB9 0x006B /* default */ + 0xBA 0x090C /* default */ + >; + + /* 10 : ATL battery data */ + battery2,v_empty = <0xA561>; /* Empty: 3400mV, Recover: 4000mV */ + battery2,v_empty_origin = <0x7D54>; /* Empty: 2500mV, Recover: 3360mV */ + battery2,capacity = <0x077A>; + /* fullcapnom dPacc dQacc RCOMP0 TempCo */ + battery2,fg_reset_wa_data = <0x077A 0x3200 0x01DE 0x0025 0x161F>; + + battery2,data_ver = <0x3>; + + battery2,selected_reg = < + 0x02 0x7F80 /* default */ + 0x12 0x3400 /* QResidual00 */ + 0x1E 0x026C /* ICHGTerm */ + 0x21 0x6200 /* default */ + 0x22 0x1A80 /* QResidual10 */ + 0x2A 0x023C /* RelaxCFG */ + 0x2C 0xE3E1 /* TGAIN */ + 0x2D 0x290E /* TOFF */ + 0x2E 0x0400 /* CGAIN */ + 0x2F 0x0001 /* COFF */ + 0x32 0x0D80 /* QResidual20 */ + 0x33 0xFFFF /* default */ + 0x37 0x05E0 /* default */ + 0x42 0x0900 /* QResidual30 */ + 0xB4 0x7F80 /* default */ + 0xB8 0x0000 /* default */ + 0xB9 0x006B /* default */ + 0xBA 0x090C /* default */ + >; + + /* 11 : SDI battery data */ + battery3,v_empty = <0xA561>; /* Empty: 3400mV, Recover: 4000mV */ + battery3,v_empty_origin = <0x7D54>; /* Empty: 2500mV, Recover: 3360mV */ + battery3,capacity = <0x076B>; + /* fullcapnom dPacc dQacc RCOMP0 TempCo */ + battery3,fg_reset_wa_data = <0x076A 0x3200 0x01DA 0x002F 0x171F>; + + battery3,data_ver = <0x2>; + + battery3,selected_reg = < + 0x02 0x7F80 /* default */ + 0x12 0x3C00 /* QResidual00 */ + 0x1E 0x026C /* ICHGTerm */ + 0x21 0x6200 /* default */ + 0x22 0x1D00 /* QResidual10 */ + 0x2A 0x023C /* RelaxCFG */ + 0x2C 0xE3E1 /* TGAIN */ + 0x2D 0x290E /* TOFF */ + 0x2E 0x0400 /* CGAIN */ + 0x2F 0x0001 /* COFF */ + 0x32 0x0D80 /* QResidual20 */ + 0x33 0xFFFF /* default */ + 0x37 0x05E0 /* default */ + 0x42 0x0B00 /* QResidual30 */ + 0xB4 0x7F80 /* default */ + 0xB8 0x0000 /* default */ + 0xB9 0x006B /* default */ + 0xBA 0x090C /* default */ + >; + }; +}; diff --git a/drivers/battery/fuelgauge/max77705_fuelgauge/max77705_fuelgauge.h b/drivers/battery/fuelgauge/max77705_fuelgauge/max77705_fuelgauge.h new file mode 100644 index 000000000000..8e0ed8a0cbc4 --- /dev/null +++ b/drivers/battery/fuelgauge/max77705_fuelgauge/max77705_fuelgauge.h @@ -0,0 +1,308 @@ +/* + * max77705_fuelgauge.h + * Samsung max77705 Fuel Gauge Header + * + * Copyright (C) 2015 Samsung Electronics, Inc. + * + * This software is 77854 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 __MAX77705_FUELGAUGE_H +#define __MAX77705_FUELGAUGE_H __FILE__ + +#include +#include +#include +#include +#include +#include "../../common/sec_charging_common.h" +#include + +/* Client address should be shifted to the right 1bit. + * R/W bit should NOT be included. + */ + +#define PRINT_COUNT 10 + +#define ALERT_EN 0x04 +#define CAPACITY_SCALE_DEFAULT_CURRENT 1000 +#define CAPACITY_SCALE_HV_CURRENT 600 + +#define FG_BATT_DUMP_SIZE 128 + +enum max77705_vempty_mode { + VEMPTY_MODE_HW = 0, + VEMPTY_MODE_SW, + VEMPTY_MODE_SW_VALERT, + VEMPTY_MODE_SW_RECOVERY, +}; + +enum { + FG_DATA, +}; + +ssize_t max77705_fg_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf); + +ssize_t max77705_fg_store_attrs(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +#define MAX77705_FG_ATTR(_name) \ +{ \ + .attr = {.name = #_name, .mode = 0660}, \ + .show = max77705_fg_show_attrs, \ + .store = max77705_fg_store_attrs, \ +} + +struct sec_fg_info { + /* test print count */ + int pr_cnt; + /* full charge comp */ + struct delayed_work full_comp_work; + + /* battery info */ + u32 soc; + + /* miscellaneous */ + unsigned long fullcap_check_interval; + int full_check_flag; + bool is_first_check; +}; + +enum { + FG_LEVEL = 0, + FG_TEMPERATURE, + FG_VOLTAGE, + FG_CURRENT, + FG_CURRENT_AVG, + FG_CHECK_STATUS, + FG_RAW_SOC, + FG_VF_SOC, + FG_AV_SOC, + FG_FULLCAP, + FG_FULLCAPNOM, + FG_FULLCAPREP, + FG_MIXCAP, + FG_AVCAP, + FG_REPCAP, + FG_CYCLE, + FG_QH, + FG_QH_VF_SOC, + FG_ISYS, + FG_ISYS_AVG, + FG_VSYS, + FG_IIN, + FG_VBYP, +}; + +enum { + POSITIVE = 0, + NEGATIVE, +}; + +enum { + RANGE = 0, + SLOPE, + OFFSET, + TABLE_MAX +}; + +#define CURRENT_RANGE_MAX_NUM 5 + +struct battery_data_t { + u8 battery_id; +#if IS_ENABLED(CONFIG_DUAL_BATTERY) +#if defined(CONFIG_ID_USING_BAT_SUBBAT) + u8 main_battery_id; +#endif + u8 sub_battery_id; +#endif + u32 V_empty; + u32 V_empty_origin; + u32 sw_v_empty_vol; + u32 sw_v_empty_vol_cisd; + u32 sw_v_empty_recover_vol; + u32 Capacity; + u8 *type_str; + u32 ichgterm; + u32 misccfg; + u32 fullsocthr; + u32 ichgterm_2nd; + u32 misccfg_2nd; + u32 fullsocthr_2nd; + u32 coff_origin; + u32 coff_charging; + u32 cgain_origin; + u32 cgain_charging; +}; + +/* FullCap learning setting */ +#define VFFULLCAP_CHECK_INTERVAL 300 /* sec */ +/* soc should be 0.1% unit */ +#define VFSOC_FOR_FULLCAP_LEARNING 950 +#define LOW_CURRENT_FOR_FULLCAP_LEARNING 20 +#define HIGH_CURRENT_FOR_FULLCAP_LEARNING 120 +#define LOW_AVGCURRENT_FOR_FULLCAP_LEARNING 20 +#define HIGH_AVGCURRENT_FOR_FULLCAP_LEARNING 100 + +/* power off margin */ +/* soc should be 0.1% unit */ +#define POWER_OFF_SOC_HIGH_MARGIN 20 +#define POWER_OFF_VOLTAGE_HIGH_MARGIN 3500 +#define POWER_OFF_VOLTAGE_LOW_MARGIN 3400 + +#define LEARNING_QRTABLE 0x0001 + +/* Need to be increased if there are more than 2 BAT ID GPIOs */ +#define BAT_GPIO_NO 2 + +typedef struct max77705_fuelgauge_platform_data { + int jig_irq; + int jig_gpio; + int jig_low_active; + + int bat_id_gpio[BAT_GPIO_NO]; + int bat_gpio_cnt; +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + int sub_bat_id_gpio[BAT_GPIO_NO]; + int sub_bat_gpio_cnt; +#endif + int thermal_source; + + /* fuel alert SOC (-1: not use) */ + int fuel_alert_soc; + int fuel_alert_vol; + /* fuel alert can be repeated */ + bool repeated_fuelalert; + int capacity_calculation_type; + /* soc should be soc x 10 (0.1% degree) + * only for scaling + */ + int capacity_max; + int capacity_max_margin; + int capacity_min; + unsigned int full_condition_soc; +} max77705_fuelgauge_platform_data_t; + +#define FG_RESET_DATA_COUNT 5 + +struct verify_reg { + u16 addr; + u32 data; +}; + +struct fg_reset_wa { + u32 fullcapnom; + u32 dPacc; + u32 dQacc; + u32 rcomp0; + u32 tempco; +}; + +struct lost_soc_data { + /* dt data */ + int trig_soc; /* default 10% */ + int trig_d_soc; /* delta soc, default 2% */ + int trig_scale; /* default 2x */ + int guarantee_soc; /* default 2% */ + int min_vol; /* default 3200mV */ + int min_weight; /* default 2.0 */ + + /* data */ + bool ing; + int prev_raw_soc; + int prev_remcap; + int prev_qh; + int lost_cap; + int weight; +}; + +struct max77705_fuelgauge_data { + struct device *dev; + struct i2c_client *i2c; + struct i2c_client *pmic; + struct mutex fuelgauge_mutex; + struct max77705_platform_data *max77705_pdata; + max77705_fuelgauge_platform_data_t *pdata; + struct power_supply *psy_fg; + struct delayed_work isr_work; + + atomic_t shutdown_cnt; + + int cable_type; + bool is_charging; + + /* HW-dedicated fuel gauge info structure + * used in individual fuel gauge file only + * (ex. dummy_fuelgauge.c) + */ + struct sec_fg_info info; + struct battery_data_t *battery_data; + + bool is_fuel_alerted; + struct wakeup_source *fuel_alert_ws; + + unsigned int capacity_old; /* only for atomic calculation */ + unsigned int capacity_max; /* only for dynamic calculation */ + unsigned int g_capacity_max; /* only for dynamic calculation */ + unsigned int standard_capacity; + + bool capacity_max_conv; + bool initial_update_of_soc; + bool initial_update_of_alert; + bool sleep_initial_update_of_soc; + struct mutex fg_lock; + + /* register programming */ + int reg_addr; + u8 reg_data[2]; + + int fg_irq; + + int raw_capacity; + int current_now; + int current_avg; + + int repcap_1st; +#if defined(CONFIG_UI_SOC_PROLONGING) + int prev_raw_soc; +#endif + + bool using_temp_compensation; + bool using_hw_vempty; + unsigned int vempty_mode; + int temperature; + bool vempty_init_flag; + + int low_temp_limit; + + int vempty_recover_time; + unsigned long vempty_time; + + u32 fg_resistor; + + struct fg_reset_wa *fg_reset_data; + struct verify_reg *verify_selected_reg; + unsigned int verify_selected_reg_length; + u32 data_ver; + bool skip_fg_verify; + u32 err_cnt; + u32 q_res_table[4]; /* QResidual Table */ + + bool valert_count_flag; + struct lost_soc_data lost_soc; + char d_buf[128]; + int bd_vfocv; + int bd_raw_soc; +}; + +#endif /* __MAX77705_FUELGAUGE_H */ diff --git a/drivers/battery/fuelgauge/max77775_fuelgauge/Kconfig b/drivers/battery/fuelgauge/max77775_fuelgauge/Kconfig new file mode 100644 index 000000000000..cf5e7f0e9ace --- /dev/null +++ b/drivers/battery/fuelgauge/max77775_fuelgauge/Kconfig @@ -0,0 +1,49 @@ +menu "Fuelgauge drivers" + +config FUELGAUGE_DUMMY + bool "dummy fuel gauge driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y here, to enable + support for dummy fuel gauge driver. + This driver source code implemented + skeleton source code for fuel gauge functions. + +config FUELGAUGE_MAX77775 + tristate "MAX77775 fuel gauge driver" + default n + depends on BATTERY_SAMSUNG + help + Say Y or M here, to enable + support for MAXIM MAX77775 fuel gauge driver. + This is fuel-gauge systems for monitoring batteries. + This fuel-gauge can be used in coulomb-counting mode. + +config EN_OOPS + bool "enable oops filter" + default n + help + Say Y here to enable + support for FUELGAUGE_MAX77775 enable oops filter. + MAXIM fuel-gauge only support this option. + some battery data values should be defined. + +config ID_USING_BAT_SUBBAT + bool "battery id using sub bat" + default n + depends on DUAL_BATTERY + help + Say Y here to enable + This is to calculate bat_id using main_bat_id & sub_bat_id. + +config UI_SOC_PROLONGING + bool "Ui Soc 100% Prolonging" + default n + help + Say Y here to enable + Support for UI Soc prolonging. + This is to enable UI Soc prolonging concept. + +endmenu + diff --git a/drivers/battery/fuelgauge/max77775_fuelgauge/Makefile b/drivers/battery/fuelgauge/max77775_fuelgauge/Makefile new file mode 100644 index 000000000000..df1dfbeef3da --- /dev/null +++ b/drivers/battery/fuelgauge/max77775_fuelgauge/Makefile @@ -0,0 +1,7 @@ +obj-$(CONFIG_FUELGAUGE_MAX77775) += max77775-fuelgauge.o +max77775-fuelgauge-$(CONFIG_FUELGAUGE_MAX77775) += max77775_fuelgauge.o + +ifeq ($(CONFIG_SEC_KUNIT), y) +GCOV_PROFILE_max77775_fuelgauge.o := $(CONFIG_SEC_KUNIT) +endif +ccflags-y := -Wformat diff --git a/drivers/battery/fuelgauge/max77775_fuelgauge/max77775_fuelgauge.bzl b/drivers/battery/fuelgauge/max77775_fuelgauge/max77775_fuelgauge.bzl new file mode 100644 index 000000000000..13e8cc254121 --- /dev/null +++ b/drivers/battery/fuelgauge/max77775_fuelgauge/max77775_fuelgauge.bzl @@ -0,0 +1,7 @@ +ko_list = [ + { + "ko_names" : [ + "drivers/battery/fuelgauge/max77775_fuelgauge/max77775-fuelgauge.ko" + ] + } +] diff --git a/drivers/battery/fuelgauge/max77775_fuelgauge/max77775_fuelgauge.c b/drivers/battery/fuelgauge/max77775_fuelgauge/max77775_fuelgauge.c new file mode 100644 index 000000000000..fdb757994694 --- /dev/null +++ b/drivers/battery/fuelgauge/max77775_fuelgauge/max77775_fuelgauge.c @@ -0,0 +1,3275 @@ +/* + * max77775_fuelgauge.c + * Samsung max77775 Fuel Gauge Driver + * + * Copyright (C) 2015 Samsung Electronics + * + * + * 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. + */ + +#define DEBUG +/* #define BATTERY_LOG_MESSAGE */ + +#include +#include +#include +#include +#include "max77775_fuelgauge.h" + +#if defined(CONFIG_SEC_KUNIT) +#define __visible_for_testing +#else +#define __visible_for_testing static +#endif + +static unsigned int __read_mostly lpcharge; +module_param(lpcharge, uint, 0444); + +static enum power_supply_property max77775_fuelgauge_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +bool max77775_fg_fuelalert_init(struct max77775_fuelgauge_data *fuelgauge, + int soc); +static void max77775_fg_periodic_read_power( + struct max77775_fuelgauge_data *fuelgauge); +static void max77775_reset_bat_id(struct max77775_fuelgauge_data *fuelgauge); + +static struct device_attribute max77775_fg_attrs[] = { + MAX77775_FG_ATTR(fg_data), +}; + +static unsigned int max77775_get_lpmode(void) { return lpcharge; } +static int max77775_fg_read_vfsoc(struct max77775_fuelgauge_data *fuelgauge); + +#if !defined(CONFIG_SEC_FACTORY) +static void max77775_fg_adaptation_wa(struct max77775_fuelgauge_data *fuelgauge) +{ + u32 rcomp0; + u32 fullcapnom, fullcaprep, designcap; + u32 temp; + u8 data[2]; + int vfsoc; + struct fg_reset_wa *fg_reset_data = fuelgauge->fg_reset_data; + +#if IS_ENABLED(CONFIG_DUAL_FUELGAUGE) + return; +#endif + + if (!fg_reset_data) + return; + + /* check RCOMP0 */ + rcomp0 = max77775_read_word(fuelgauge->i2c, MAX77775_FG_REG_RCOMP0); + if ((rcomp0 > (fg_reset_data->rcomp0 * 14 / 10)) || (rcomp0 < (fg_reset_data->rcomp0 * 7 / 10))) { + pr_err("%s: abnormal RCOMP0 (0x%x / 0x%x)\n", __func__, rcomp0, fg_reset_data->rcomp0); + goto set_default_value; + } + + /* check TEMPCO */ + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_TEMPCO, + 2, data) < 0) { + pr_err("%s: Failed to read TEMPCO\n", __func__); + return; + } + /* tempcohot = data[1]; tempcocold = data[0]; */ + temp = (fg_reset_data->tempco & 0xFF00) >> 8; + if ((data[1] > (temp * 14 / 10)) || (data[1] < (temp * 7 / 10))) { + pr_err("%s: abnormal TempCoHot (0x%x / 0x%x)\n", __func__, data[1], temp); + goto set_default_value; + } + + temp = fg_reset_data->tempco & 0x00FF; + if ((data[0] > (temp * 14 / 10)) || (data[0] < (temp * 7 / 10))) { + pr_err("%s: abnormal TempCoCold (0x%x / 0x%x)\n", __func__, data[0], temp); + goto set_default_value; + } + + /* get DESIGNCAP */ + designcap = max77775_read_word(fuelgauge->i2c, MAX77775_FG_REG_DESIGNCAP); + + /* check FULLCAPNOM */ + fullcapnom = max77775_read_word(fuelgauge->i2c, MAX77775_FG_REG_FULLCAPNOM); + if (fullcapnom > (designcap * 11 / 10)) { + pr_err("%s: abnormal fullcapnom (0x%x / 0x%x)\n", __func__, fullcapnom, designcap); + goto re_calculation_fullcap_nom; + } + + /* check FULLCAPREP */ + fullcaprep = max77775_read_word(fuelgauge->i2c, MAX77775_FG_REG_FULLCAPREP); + if (fullcaprep > (designcap * 115 / 100)) { + pr_err("%s: abnormal fullcaprep (0x%x / 0x%x)\n", __func__, fullcaprep, designcap); + goto re_calculation_fullcap_rep; + } + + return; + +re_calculation_fullcap_nom: + pr_err("%s: enter re_calculation fullcapnom\n", __func__); + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_DPACC, fg_reset_data->dPacc); + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_DQACC, fg_reset_data->dQacc); + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_FULLCAPNOM, fg_reset_data->fullcapnom); + temp = max77775_read_word(fuelgauge->i2c, MAX77775_FG_REG_LEARNCFG); + temp &= 0xFF0F; + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_LEARNCFG, temp); + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_CYCLES, 0); +re_calculation_fullcap_rep: + pr_err("%s: enter re_calculation fullcaprep\n", __func__); + vfsoc = max77775_fg_read_vfsoc(fuelgauge); + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_REPCAP, vfsoc * fg_reset_data->fullcapnom / 1000); + msleep(200); + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_FULLCAPREP, fg_reset_data->fullcapnom); + fuelgauge->err_cnt++; +set_default_value: + pr_err("%s: enter set_default_value\n", __func__); + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_RCOMP0, fg_reset_data->rcomp0); + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_TEMPCO, fg_reset_data->tempco); + + return; +} + +static void max77775_fg_periodic_read(struct max77775_fuelgauge_data *fuelgauge) +{ + u8 reg; + int i, data[0x10]; + char *str = NULL; + unsigned int v_cnt = 0; /* verify count */ + unsigned int err_cnt = 0; + struct verify_reg *fg_verify = fuelgauge->verify_selected_reg; + + str = kzalloc(sizeof(char) * 1024, GFP_KERNEL); + if (!str) + return; + + for (i = 0; i < 16; i++) { + if (i == 5) + i = 11; + else if (i == 12) + i = 13; + for (reg = 0; reg < 0x10; reg++) { + data[reg] = max77775_read_word(fuelgauge->i2c, reg + i * 0x10); + + if (data[reg] < 0) { + kfree(str); + return; + } + + /* verify fg reg */ + if (!fuelgauge->skip_fg_verify && fg_verify && v_cnt < fuelgauge->verify_selected_reg_length) { + if (fg_verify[v_cnt].addr == reg + i * 0x10) { + if (fg_verify[v_cnt].data != data[reg]) { + pr_err("%s:[%d] addr(0x%x 0x%x) data(0x%x 0x%x) read veryfy error! not matched!\n", + __func__, v_cnt, + fg_verify[v_cnt].addr, reg + i * 0x10, + fg_verify[v_cnt].data, data[reg]); + err_cnt++; + } + v_cnt++; + } + } + } + sprintf(str + strlen(str), + "%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,", + data[0x00], data[0x01], data[0x02], data[0x03], + data[0x04], data[0x05], data[0x06], data[0x07]); + sprintf(str + strlen(str), + "%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,%04xh,", + data[0x08], data[0x09], data[0x0a], data[0x0b], + data[0x0c], data[0x0d], data[0x0e], data[0x0f]); + + if (!fuelgauge->initial_update_of_soc) + msleep(1); + } + + pr_info("[%d][FG] %s\n", fuelgauge->battery_data->battery_id, str); + + max77775_fg_adaptation_wa(fuelgauge); + + kfree(str); +} +#endif + +static int max77775_fg_read_vcell(struct max77775_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + u32 vcell, temp; + u16 w_data; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_VCELL, 2, data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_VCELL\n", __func__); + return -1; + } + + w_data = (data[1] << 8) | data[0]; + + temp = (w_data & 0xFFF) * 78125; + vcell = temp / 1000000; + + temp = ((w_data & 0xF000) >> 4) * 78125; + temp /= 1000000; + vcell += temp << 4; + +#if !IS_ENABLED(CONFIG_DUAL_FUELGAUGE) + if (!(fuelgauge->info.pr_cnt++ % PRINT_COUNT)) { + fuelgauge->info.pr_cnt = 1; + pr_info("%s: VCELL(%d)mV, data(0x%04x)\n", + __func__, vcell, (data[1] << 8) | data[0]); + } + + if ((fuelgauge->vempty_mode == VEMPTY_MODE_SW_VALERT) && + (vcell >= fuelgauge->battery_data->sw_v_empty_recover_vol)) { + fuelgauge->vempty_mode = VEMPTY_MODE_SW_RECOVERY; + max77775_fg_fuelalert_init(fuelgauge, fuelgauge->pdata->fuel_alert_soc); + pr_info("%s: Recoverd from SW V EMPTY Activation\n", __func__); + if (fuelgauge->valert_count_flag) { + pr_info("%s: Vcell(%d) release CISD VALERT COUNT check\n", + __func__, vcell); + fuelgauge->valert_count_flag = false; + } + } +#endif + return vcell; +} + +void check_learncfg(struct max77775_fuelgauge_data *fuelgauge) +{ + u32 learncfg; + + learncfg = max77775_read_word(fuelgauge->i2c, MAX77775_FG_REG_LEARNCFG); + if (learncfg & MAX77775_ELRN) { + pr_info("%s: LearnCFG= %d -> LSB is set\n", __func__, learncfg); + learncfg &= ~MAX77775_ELRN; + if ((learncfg & MAX77775_FILT_EMPTY) == 0) + learncfg |= MAX77775_FILT_EMPTY; + /* write learncfg */ + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_LEARNCFG, learncfg); + if (fuelgauge->verify_selected_reg != NULL) { + /* write Qrtable00 ~ 30 */ + max77775_write_word(fuelgauge->i2c, MAX77775_FG_QRTABLE00, fuelgauge->q_res_table[0]); + max77775_write_word(fuelgauge->i2c, MAX77775_FG_QRTABLE10, fuelgauge->q_res_table[1]); + max77775_write_word(fuelgauge->i2c, MAX77775_FG_QRTABLE20, fuelgauge->q_res_table[2]); + max77775_write_word(fuelgauge->i2c, MAX77775_FG_QRTABLE30, fuelgauge->q_res_table[3]); + } + } +} + +static int max77775_fg_read_vfocv(struct max77775_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + u32 vfocv = 0, temp; + u16 w_data; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_VFOCV, 2, data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_VFOCV\n", __func__); + return -1; + } + + w_data = (data[1] << 8) | data[0]; + + temp = (w_data & 0xFFF) * 78125; + vfocv = temp / 1000000; + + temp = ((w_data & 0xF000) >> 4) * 78125; + temp /= 1000000; + vfocv += (temp << 4); + +#if !defined(CONFIG_SEC_FACTORY) + max77775_fg_periodic_read(fuelgauge); +#endif + max77775_fg_periodic_read_power(fuelgauge); + + check_learncfg(fuelgauge); + fuelgauge->bd_vfocv = vfocv; + return vfocv; +} + +static int max77775_fg_read_avg_vcell(struct max77775_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + u32 avg_vcell = 0, temp; + u16 w_data; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_AVGVCELL, 2, data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_AVGVCELL\n", __func__); + return -1; + } + + w_data = (data[1] << 8) | data[0]; + + temp = (w_data & 0xFFF) * 78125; + avg_vcell = temp / 1000000; + + temp = ((w_data & 0xF000) >> 4) * 78125; + temp /= 1000000; + avg_vcell += (temp << 4); + + return avg_vcell; +} + +static int max77775_fg_check_battery_present( + struct max77775_fuelgauge_data *fuelgauge) +{ + u8 status_data[2]; + int ret = 1; + + /* 1. Check Bst bit */ + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_STATUS, 2, status_data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_STATUS\n", __func__); + return 0; + } + + if (status_data[0] & (0x1 << 3)) { + pr_info("%s: addr(0x00), data(0x%04x)\n", __func__, + (status_data[1] << 8) | status_data[0]); + pr_info("%s: battery is absent!!\n", __func__); + ret = 0; + } + + return ret; +} + +static void max77775_fg_set_vempty(struct max77775_fuelgauge_data *fuelgauge, + int vempty_mode) +{ + u16 data = 0; + u8 valrt_data[2] = { 0, }; + + if (!fuelgauge->using_temp_compensation) { + pr_info("%s: does not use temp compensation, default hw vempty\n", + __func__); + vempty_mode = VEMPTY_MODE_HW; + } + + fuelgauge->vempty_mode = vempty_mode; + switch (vempty_mode) { + case VEMPTY_MODE_SW: + /* HW Vempty Disable */ + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_VEMPTY, + fuelgauge->battery_data->V_empty_origin); + /* Reset VALRT Threshold setting (enable) */ + valrt_data[1] = 0xFF; + valrt_data[0] = fuelgauge->battery_data->sw_v_empty_vol / 20; + if (max77775_bulk_write(fuelgauge->i2c, MAX77775_FG_REG_VALRTTH, + 2, valrt_data) < 0) { + pr_info("%s: Failed to write MAX77775_FG_REG_VALRTTH\n", __func__); + return; + } + data = max77775_read_word(fuelgauge->i2c, MAX77775_FG_REG_VALRTTH); + pr_info("%s: HW V EMPTY Disable, SW V EMPTY Enable with %d mV (%d)\n", + __func__, fuelgauge->battery_data->sw_v_empty_vol, (data & 0x00ff) * 20); + break; + default: + /* HW Vempty Enable */ + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_VEMPTY, + fuelgauge->battery_data->V_empty); + /* Reset VALRT Threshold setting (disable) */ + valrt_data[1] = 0xFF; + valrt_data[0] = fuelgauge->battery_data->sw_v_empty_vol_cisd / 20; + if (max77775_bulk_write(fuelgauge->i2c, MAX77775_FG_REG_VALRTTH, + 2, valrt_data) < 0) { + pr_info("%s: Failed to write MAX77775_FG_REG_VALRTTH\n", __func__); + return; + } + data = max77775_read_word(fuelgauge->i2c, MAX77775_FG_REG_VALRTTH); + pr_info("%s: HW V EMPTY Enable, SW V EMPTY Disable %d mV (%d)\n", + __func__, 0, (data & 0x00ff) * 20); + break; + } +} + +static int max77775_fg_write_temp(struct max77775_fuelgauge_data *fuelgauge, + int temperature) +{ + u8 data[2]; + + data[0] = (temperature % 10) * 1000 / 39; + data[1] = temperature / 10; + max77775_bulk_write(fuelgauge->i2c, MAX77775_FG_REG_TEMP, 2, data); + + pr_debug("%s: temperature to (%d, 0x%02x%02x)\n", + __func__, temperature, data[1], data[0]); + + fuelgauge->temperature = temperature; + if (!fuelgauge->vempty_init_flag) + fuelgauge->vempty_init_flag = true; + + return temperature; +} + +static int max77775_fg_read_temp(struct max77775_fuelgauge_data *fuelgauge) +{ + u8 data[2] = { 0, 0 }; + int temper = 0; + + if (max77775_fg_check_battery_present(fuelgauge)) { + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_TEMP, 2, data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_TEMP\n", __func__); + return -1; + } + + if (data[1] & (0x1 << 7)) { + temper = ((~(data[1])) & 0xFF) + 1; + temper *= (-1000); + temper -= ((~((int)data[0])) + 1) * 39 / 10; + } else { + temper = data[1] & 0x7f; + temper *= 1000; + temper += data[0] * 39 / 10; + } + } else { + temper = 20000; + } + + if (!(fuelgauge->info.pr_cnt % PRINT_COUNT)) + pr_info("%s: TEMPERATURE(%d), data(0x%04x)\n", + __func__, temper, (data[1] << 8) | data[0]); + + return temper / 100; +} + +/* soc should be 0.1% unit */ +static int max77775_fg_read_vfsoc(struct max77775_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int soc; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_VFSOC, 2, data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_VFSOC\n", __func__); + return -1; + } + soc = ((data[1] * 100) + (data[0] * 100 / 256)) / 10; + + return min(soc, 1000); +} + +/* soc should be 0.001% unit */ +static int max77775_fg_read_qh_vfsoc(struct max77775_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int soc; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_VFSOC, 2, data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_VFSOC\n", __func__); + return -1; + } + soc = ((data[1] * 10000) + (data[0] * 10000 / 256)) / 10; + + return soc; +} + +static int max77775_fg_read_qh(struct max77775_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + u32 temp, sign; + s32 qh; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_QH, 2, data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_QH\n", __func__); + return -1; + } + + temp = ((data[1] << 8) | data[0]) & 0xFFFF; + if (temp & (0x1 << 15)) { + sign = NEGATIVE; + temp = (~temp & 0xFFFF) + 1; + } else { + sign = POSITIVE; + } + + qh = temp * 1000 * fuelgauge->fg_resistor / 2; + + if (sign) + qh *= -1; + + pr_info("%s : QH(%d)\n", __func__, qh); + + return qh; +} + +/* soc should be 0.1% unit */ +static int max77775_fg_read_avsoc(struct max77775_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int soc; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_AVSOC, 2, data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_AVSOC\n", __func__); + return -1; + } + soc = ((data[1] * 100) + (data[0] * 100 / 256)) / 10; + + return min(soc, 1000); +} + +/* soc should be 0.1% unit */ +static int max77775_fg_read_soc(struct max77775_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int soc; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_REPSOC, 2, data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_REPSOC\n", __func__); + return -1; + } + soc = ((data[1] * 100) + (data[0] * 100 / 256)) / 10; + +#ifdef BATTERY_LOG_MESSAGE + pr_debug("%s: raw capacity (%d)\n", __func__, soc); + + if (!(fuelgauge->info.pr_cnt % PRINT_COUNT)) { + pr_debug("%s: raw capacity (%d), data(0x%04x)\n", + __func__, soc, (data[1] << 8) | data[0]); + pr_debug("%s: REPSOC (%d), VFSOC (%d), data(0x%04x)\n", + __func__, soc / 10, + max77775_fg_read_vfsoc(fuelgauge) / 10, + (data[1] << 8) | data[0]); + } +#endif + +#if IS_ENABLED(CONFIG_DUAL_FUELGAUGE) + return soc; +#else + return min(soc, 1000); +#endif +} + +/* soc should be 0.01% unit */ +static int max77775_fg_read_rawsoc(struct max77775_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int soc; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_REPSOC, 2, data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_REPSOC\n", __func__); + return -1; + } + soc = (data[1] * 100) + (data[0] * 100 / 256); + + pr_debug("%s: raw capacity (0.01%%) (%d)\n", __func__, soc); + + if (!(fuelgauge->info.pr_cnt % PRINT_COUNT)) + pr_debug("%s: raw capacity (%d), data(0x%04x)\n", + __func__, soc, (data[1] << 8) | data[0]); + + return min(soc, 10000); +} + +static int max77775_fg_read_fullcap(struct max77775_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int ret; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_FULLCAP, 2, data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_FULLCAP\n", __func__); + return -1; + } + ret = (data[1] << 8) + data[0]; + + return ret * fuelgauge->fg_resistor / 2; +} + +static int max77775_fg_read_fullcaprep(struct max77775_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int ret; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_FULLCAPREP, 2, data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_FULLCAPREP\n", __func__); + return -1; + } + ret = (data[1] << 8) + data[0]; + + return ret * fuelgauge->fg_resistor / 2; +} + +static int max77775_fg_read_fullcapnom(struct max77775_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int ret; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_FULLCAPNOM, 2, data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_FULLCAPNOM\n", __func__); + return -1; + } + ret = (data[1] << 8) + data[0]; + + return ret * fuelgauge->fg_resistor / 2; +} + +static int max77775_fg_read_mixcap(struct max77775_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int ret; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_MIXCAP, 2, data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_MIXCAP\n", __func__); + return -1; + } + ret = (data[1] << 8) + data[0]; + + return ret * fuelgauge->fg_resistor / 2; +} + +static int max77775_fg_read_avcap(struct max77775_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int ret; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_REMCAPAV, 2, data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_REMCAPAV\n", __func__); + return -1; + } + ret = (data[1] << 8) + data[0]; + + return ret * fuelgauge->fg_resistor / 2; +} + +static int max77775_fg_read_repcap(struct max77775_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int ret; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_REPCAP, 2, data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_REPCAP\n", __func__); + return -1; + } + ret = (data[1] << 8) + data[0]; + + return ret * fuelgauge->fg_resistor / 2; +} + +static int max77775_fg_read_current(struct max77775_fuelgauge_data *fuelgauge, + int unit) +{ + u8 data1[2]; + u32 temp, sign; + s32 i_current; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_CURRENT, 2, data1) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_CURRENT\n", __func__); + return -1; + } + + temp = ((data1[1] << 8) | data1[0]) & 0xFFFF; + /* Debug log for abnormal current case */ + if (temp & (0x1 << 15)) { + sign = NEGATIVE; + temp = (~temp & 0xFFFF) + 1; + } else { + sign = POSITIVE; + } + + /* 1.5625uV/0.01Ohm(Rsense) = 156.25uA */ + /* 1.5625uV/0.005Ohm(Rsense) = 312.5uA */ + switch (unit) { + case SEC_BATTERY_CURRENT_UA: + i_current = temp * 15625 * fuelgauge->fg_resistor / 100; + break; + case SEC_BATTERY_CURRENT_MA: + default: + i_current = temp * 15625 * fuelgauge->fg_resistor / 100000; + break; + } + + if (sign) + i_current *= -1; + +#if !IS_ENABLED(CONFIG_DUAL_FUELGAUGE) + pr_debug("%s: current=%d%s\n", __func__, i_current, + (unit == SEC_BATTERY_CURRENT_UA)? "uA" : "mA"); +#endif + return i_current; +} + +static int max77775_fg_read_avg_current(struct max77775_fuelgauge_data *fuelgauge, + int unit) +{ + static int cnt; + u8 data2[2]; + u32 temp, sign; + s32 avg_current; + int vcell; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_AVGCURRENT, 2, data2) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_AVGCURRENT\n", __func__); + return -1; + } + + temp = ((data2[1] << 8) | data2[0]) & 0xFFFF; + if (temp & (0x1 << 15)) { + sign = NEGATIVE; + temp = (~temp & 0xFFFF) + 1; + } else { + sign = POSITIVE; + } + + /* 1.5625uV/0.01Ohm(Rsense) = 156.25uA */ + /* 1.5625uV/0.005Ohm(Rsense) = 312.5uA */ + switch (unit) { + case SEC_BATTERY_CURRENT_UA: + avg_current = temp * 15625 * fuelgauge->fg_resistor / 100; + break; + case SEC_BATTERY_CURRENT_MA: + default: + avg_current = temp * 15625 * fuelgauge->fg_resistor / 100000; + break; + } + + if (sign) + avg_current *= -1; + + vcell = max77775_fg_read_vcell(fuelgauge); + if ((vcell < 3500) && (cnt < 10) && (avg_current < 0) && fuelgauge->is_charging) { + avg_current = 1; + cnt++; + } + +#if !IS_ENABLED(CONFIG_DUAL_FUELGAUGE) + pr_debug("%s: avg_current=%d%s\n", __func__, avg_current, + (unit == SEC_BATTERY_CURRENT_UA)? "uA" : "mA"); +#endif + return avg_current; +} + +static int max77775_fg_read_isys(struct max77775_fuelgauge_data *fuelgauge, + int unit) +{ + u8 data1[2]; + u32 temp = 0; + s32 i_current = 0; + s32 inow = 0, inow_comp = 0; + u32 unit_type = 0; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_ISYS, 2, data1) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_ISYS\n", __func__); + return -1; + } + temp = ((data1[1] << 8) | data1[0]) & 0xFFFF; + + /* standard value is 2 whitch means 5mOhm */ + if (fuelgauge->fg_resistor != 2) { + inow = max77775_fg_read_current(fuelgauge, unit); + } + + if (unit == SEC_BATTERY_CURRENT_UA) + unit_type = 1; + else + unit_type = 1000; + + i_current = temp * 3125 / 10 / unit_type; + + if (fuelgauge->fg_resistor != 2 && i_current != 0) { + /* inow_comp = 60% x inow if 0.002Ohm */ + inow_comp = (int)((fuelgauge->fg_resistor - 2) * 10 / fuelgauge->fg_resistor) * inow / 10; + /* i_current must have a value of inow compensated */ + i_current = i_current - inow_comp; + } + if (!(fuelgauge->info.pr_cnt % PRINT_COUNT)) + pr_info("%s: isys_current=%d%s\n", __func__, i_current, + (unit == SEC_BATTERY_CURRENT_UA)? "uA" : "mA"); + + return i_current; +} + +static int max77775_fg_read_isys_avg(struct max77775_fuelgauge_data *fuelgauge, + int unit) +{ + u8 data2[2]; + u32 temp = 0; + s32 avg_current = 0; + s32 avg_inow = 0, avg_inow_comp = 0; + u32 unit_type = 0; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_AVGISYS, 2, data2) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_AVGISYS\n", __func__); + return -1; + } + temp = ((data2[1] << 8) | data2[0]) & 0xFFFF; + + /* standard value is 2 whitch means 5mOhm */ + if (fuelgauge->fg_resistor != 2) { + avg_inow = max77775_fg_read_avg_current(fuelgauge, unit); + } + + if (unit == SEC_BATTERY_CURRENT_UA) + unit_type = 1; + else + unit_type = 1000; + + avg_current = temp * 3125 / 10 / unit_type; + + if (fuelgauge->fg_resistor != 2 && avg_current != 0) { + /* inow_comp = 60% x inow if 0.002Ohm */ + avg_inow_comp = (int)((fuelgauge->fg_resistor - 2) * 10 / fuelgauge->fg_resistor) * avg_inow / 10; + /* i_current must have a value of inow compensated */ + avg_current = avg_current - avg_inow_comp; + } + if (!(fuelgauge->info.pr_cnt % PRINT_COUNT)) + pr_info("%s: isys_avg_current=%d%s\n", __func__, avg_current, + (unit == SEC_BATTERY_CURRENT_UA)? "uA" : "mA"); + + return avg_current; +} + +static int max77775_fg_read_iin(struct max77775_fuelgauge_data *fuelgauge, + int unit) +{ + u8 data1[2]; + u32 temp; + s32 i_current; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_IIN, 2, data1) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_IIN\n", __func__); + return -1; + } + + temp = ((data1[1] << 8) | data1[0]) & 0xFFFF; + + /* LSB 0.125mA */ + switch (unit) { + case SEC_BATTERY_CURRENT_UA: + i_current = temp * 125; + break; + case SEC_BATTERY_CURRENT_MA: + default: + i_current = temp * 125 / 1000; + break; + } + + if (!(fuelgauge->info.pr_cnt % PRINT_COUNT)) + pr_debug("%s: iin_current=%d%s\n", __func__, i_current, + (unit == SEC_BATTERY_CURRENT_UA)? "uA" : "mA"); + + return i_current; +} + +static int max77775_fg_read_vbyp(struct max77775_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + u32 vbyp, temp; + u16 w_data; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_VBYP, 2, data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_VBYP\n", __func__); + return -1; + } + + w_data = (data[1] << 8) | data[0]; + + temp = (w_data & 0xFFF) * 427246; + vbyp = temp / 1000000; + + temp = ((w_data & 0xF000) >> 4) * 427246; + temp /= 1000000; + vbyp += (temp << 4); + + if (!(fuelgauge->info.pr_cnt % PRINT_COUNT)) + pr_info("%s: VBYP(%d), data(0x%04x)\n", + __func__, vbyp, (data[1] << 8) | data[0]); + + return vbyp; +} + +static int max77775_fg_read_vsys(struct max77775_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + u32 vsys, temp; + u16 w_data; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_VSYS, 2, data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_VSYS\n", __func__); + return -1; + } + + w_data = (data[1] << 8) | data[0]; + + temp = (w_data & 0xFFF) * 15625; + vsys = temp / 100000; + + temp = ((w_data & 0xF000) >> 4) * 15625; + temp /= 100000; + vsys += (temp << 4); + + if (!(fuelgauge->info.pr_cnt % PRINT_COUNT)) + pr_info("%s: VSYS(%d), data(0x%04x)\n", + __func__, vsys, (data[1] << 8) | data[0]); + + return vsys; +} + +static int max77775_fg_read_cycle(struct max77775_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int ret; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_CYCLES, 2, data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_CYCLES\n", __func__); + return -1; + } + ret = (data[1] << 8) + data[0]; + + return ret; +} + +static bool max77775_check_jig_status(struct max77775_fuelgauge_data *fuelgauge) +{ + bool ret = false; + + if (fuelgauge->pdata->jig_gpio) { + if (fuelgauge->pdata->jig_low_active) + ret = !gpio_get_value(fuelgauge->pdata->jig_gpio); + else + ret = gpio_get_value(fuelgauge->pdata->jig_gpio); + } + pr_info("%s: ret(%d)\n", __func__, ret); + + return ret; +} + +int max77775_fg_reset_soc(struct max77775_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + int vfocv, fullcap; + + /* delay for current stablization */ + msleep(200); + + pr_info("%s: Before quick-start - VCELL(%d), VFOCV(%d), VfSOC(%d), RepSOC(%d)\n", + __func__, max77775_fg_read_vcell(fuelgauge), + max77775_fg_read_vfocv(fuelgauge), + max77775_fg_read_vfsoc(fuelgauge), + max77775_fg_read_soc(fuelgauge)); + pr_info("%s: Before quick-start - current(%d), avg current(%d)\n", __func__, + max77775_fg_read_current(fuelgauge, SEC_BATTERY_CURRENT_MA), + max77775_fg_read_avg_current(fuelgauge, SEC_BATTERY_CURRENT_MA)); + + if (!max77775_check_jig_status(fuelgauge)) { + pr_info("%s : Return by No JIG_ON signal\n", __func__); + return 0; + } + + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_CYCLES, 0); + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_MISCCFG, 2, data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_MISCCFG\n", __func__); + return -1; + } + + data[1] |= (0x1 << 2); // InitVFG (fuelgauge to re-initialize) + if (max77775_bulk_write(fuelgauge->i2c, MAX77775_FG_REG_MISCCFG, 2, data) < 0) { + pr_info("%s: Failed to write MAX77775_FG_REG_MISCCFG\n", __func__); + return -1; + } + + msleep(1000); + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_FULLCAP, + fuelgauge->battery_data->Capacity); + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_FULLCAPREP, + fuelgauge->battery_data->Capacity); + msleep(200); + + pr_info("%s: After quick-start - VCELL(%d), VFOCV(%d), VfSOC(%d), RepSOC(%d)\n", + __func__, max77775_fg_read_vcell(fuelgauge), + max77775_fg_read_vfocv(fuelgauge), + max77775_fg_read_vfsoc(fuelgauge), + max77775_fg_read_soc(fuelgauge)); + pr_info("%s: After quick-start - current(%d), avg current(%d)\n", __func__, + max77775_fg_read_current(fuelgauge, SEC_BATTERY_CURRENT_MA), + max77775_fg_read_avg_current(fuelgauge, SEC_BATTERY_CURRENT_MA)); + + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_CYCLES, 0x00a0); + +/* P8 is not turned off by Quickstart @3.4V + * (It's not a problem, depend on mode data) + * Power off for factory test(File system, etc..) + */ + vfocv = max77775_fg_read_vfocv(fuelgauge); + if (vfocv < POWER_OFF_VOLTAGE_LOW_MARGIN) { + pr_info("%s: Power off condition(%d)\n", __func__, vfocv); + fullcap = max77775_read_word(fuelgauge->i2c, MAX77775_FG_REG_FULLCAP); + + /* FullCAP * 0.009 */ + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_REPCAP, + (u16) (fullcap * 9 / 1000)); + msleep(200); + pr_info("%s: new soc=%d, vfocv=%d\n", __func__, + max77775_fg_read_soc(fuelgauge), vfocv); + } + + pr_info("%s: Additional step - VfOCV(%d), VfSOC(%d), RepSOC(%d)\n", + __func__, max77775_fg_read_vfocv(fuelgauge), + max77775_fg_read_vfsoc(fuelgauge), + max77775_fg_read_soc(fuelgauge)); + + return 0; +} + +int max77775_fg_reset_capacity_by_jig_connection( + struct max77775_fuelgauge_data *fuelgauge) +{ + union power_supply_propval val; + + psy_do_property("max77775-charger", set, + POWER_SUPPLY_EXT_PROP_FACTORY_MODE, val); + pr_err("%s : FACTORY MODE TEST!\n", __func__); + + val.intval = 1; + psy_do_property("battery", set, POWER_SUPPLY_PROP_ENERGY_NOW, val); + pr_info("%s: DesignCap = Capacity - 1 (Jig Connection)\n", __func__); + + return max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_DESIGNCAP, + fuelgauge->battery_data->Capacity - 1); +} + +static int max77775_fg_check_status_reg(struct max77775_fuelgauge_data *fuelgauge) +{ + u8 status_data[2]; + int ret = 0; + + /* 1. Check Smn was generatedread */ + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_STATUS, 2, status_data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_STATUS\n", __func__); + return -1; + } +#ifdef BATTERY_LOG_MESSAGE + pr_info("%s: addr(0x00), data(0x%04x)\n", __func__, + (status_data[1] << 8) | status_data[0]); +#endif + + if (status_data[1] & (0x1 << 2)) + ret = 1; + + /* 2. clear Status reg */ + status_data[1] = 0; + if (max77775_bulk_write(fuelgauge->i2c, MAX77775_FG_REG_STATUS, 2, status_data) < 0) { + pr_info("%s: Failed to write MAX77775_FG_REG_STATUS\n", __func__); + return -1; + } + + return ret; +} + +int max77775_get_fuelgauge_value(struct max77775_fuelgauge_data *fuelgauge, + int data) +{ + int ret; + + switch (data) { + case FG_LEVEL: + ret = max77775_fg_read_soc(fuelgauge); + break; + case FG_TEMPERATURE: + ret = max77775_fg_read_temp(fuelgauge); + break; + case FG_VOLTAGE: + ret = max77775_fg_read_vcell(fuelgauge); + break; + case FG_CURRENT: + ret = max77775_fg_read_current(fuelgauge, SEC_BATTERY_CURRENT_MA); + break; + case FG_CURRENT_AVG: + ret = max77775_fg_read_avg_current(fuelgauge, SEC_BATTERY_CURRENT_MA); + break; + case FG_CHECK_STATUS: + ret = max77775_fg_check_status_reg(fuelgauge); + break; + case FG_RAW_SOC: + ret = max77775_fg_read_rawsoc(fuelgauge); + fuelgauge->bd_raw_soc = ret; + break; + case FG_VF_SOC: + ret = max77775_fg_read_vfsoc(fuelgauge); + break; + case FG_AV_SOC: + ret = max77775_fg_read_avsoc(fuelgauge); + break; + case FG_FULLCAP: + ret = max77775_fg_read_fullcap(fuelgauge); + if (ret == -1) + ret = max77775_fg_read_fullcap(fuelgauge); + break; + case FG_FULLCAPNOM: + ret = max77775_fg_read_fullcapnom(fuelgauge); + if (ret == -1) + ret = max77775_fg_read_fullcapnom(fuelgauge); + break; + case FG_FULLCAPREP: + ret = max77775_fg_read_fullcaprep(fuelgauge); + if (ret == -1) + ret = max77775_fg_read_fullcaprep(fuelgauge); + break; + case FG_MIXCAP: + ret = max77775_fg_read_mixcap(fuelgauge); + break; + case FG_AVCAP: + ret = max77775_fg_read_avcap(fuelgauge); + break; + case FG_REPCAP: + ret = max77775_fg_read_repcap(fuelgauge); + break; + case FG_CYCLE: + ret = max77775_fg_read_cycle(fuelgauge); + break; + case FG_QH: + ret = max77775_fg_read_qh(fuelgauge); + break; + case FG_QH_VF_SOC: + ret = max77775_fg_read_qh_vfsoc(fuelgauge); + break; + case FG_ISYS: + ret = max77775_fg_read_isys(fuelgauge, SEC_BATTERY_CURRENT_MA); + break; + case FG_ISYS_AVG: + ret = max77775_fg_read_isys_avg(fuelgauge, SEC_BATTERY_CURRENT_MA); + break; + case FG_VSYS: + ret = max77775_fg_read_vsys(fuelgauge); + break; + case FG_IIN: + ret = max77775_fg_read_iin(fuelgauge, SEC_BATTERY_CURRENT_MA); + break; + case FG_VBYP: + ret = max77775_fg_read_vbyp(fuelgauge); + break; + default: + ret = -1; + break; + } + + return ret; +} + +static void max77775_fg_periodic_read_power( + struct max77775_fuelgauge_data *fuelgauge) +{ + int isys, isys_avg, vsys, iin, vbyp, qh; + + isys = max77775_get_fuelgauge_value(fuelgauge, FG_ISYS); + isys_avg = max77775_get_fuelgauge_value(fuelgauge, FG_ISYS_AVG); + vsys = max77775_get_fuelgauge_value(fuelgauge, FG_VSYS); + iin = max77775_get_fuelgauge_value(fuelgauge, FG_IIN); + vbyp = max77775_get_fuelgauge_value(fuelgauge, FG_VBYP); + qh = max77775_get_fuelgauge_value(fuelgauge, FG_QH); + + pr_info("[FG power] ISYS(%dmA),ISYSAVG(%dmA),VSYS(%dmV),IIN(%dmA),VBYP(%dmV),QH(%d uah),WA(%d)\n", + isys, isys_avg, vsys, iin, vbyp, qh, fuelgauge->err_cnt); +} + +static void max77775_fg_read_power_log( + struct max77775_fuelgauge_data *fuelgauge) +{ + int vnow, inow; + + vnow = max77775_get_fuelgauge_value(fuelgauge, FG_VOLTAGE); + inow = max77775_get_fuelgauge_value(fuelgauge, FG_CURRENT); + + pr_info("[FG info] VNOW(%dmV),INOW(%dmA)\n", vnow, inow); +} + +int max77775_fg_alert_init(struct max77775_fuelgauge_data *fuelgauge, int soc) +{ + u8 misccgf_data[2], salrt_data[2], config_data[2], talrt_data[2]; + u16 read_data = 0; + + fuelgauge->is_fuel_alerted = false; + + /* Using RepSOC */ + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_MISCCFG, 2, misccgf_data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_MISCCFG\n", __func__); + return -1; + } + misccgf_data[0] = misccgf_data[0] & ~(0x03); + + if (max77775_bulk_write(fuelgauge->i2c, MAX77775_FG_REG_MISCCFG, 2, misccgf_data) < 0) { + pr_info("%s: Failed to write MAX77775_FG_REG_MISCCFG\n", __func__); + return -1; + } + + /* SALRT Threshold setting */ + salrt_data[1] = 0xff; + salrt_data[0] = soc; + if (max77775_bulk_write(fuelgauge->i2c, MAX77775_FG_REG_SALRTTH, 2, salrt_data) < 0) { + pr_info("%s: Failed to write MAX77775_FG_REG_SALRTTH\n", __func__); + return -1; + } + + /* Reset TALRT Threshold setting (disable) */ + talrt_data[1] = 0x7F; + talrt_data[0] = 0x80; + if (max77775_bulk_write(fuelgauge->i2c, MAX77775_FG_REG_TALRTTH, 2, talrt_data) < 0) { + pr_info("%s: Failed to write MAX77775_FG_REG_TALRTTH\n", __func__); + return -1; + } + + read_data = max77775_read_word(fuelgauge->i2c, MAX77775_FG_REG_TALRTTH); + if (read_data != 0x7f80) + pr_err("%s: MAX77775_FG_REG_TALRTTH is not valid (0x%x)\n", + __func__, read_data); + + /* Enable SOC alerts */ + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_CONFIG, 2, config_data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_CONFIG\n", __func__); + return -1; + } + config_data[0] = config_data[0] | (0x1 << 2); + + if (max77775_bulk_write(fuelgauge->i2c, MAX77775_FG_REG_CONFIG, 2, config_data) < 0) { + pr_info("%s: Failed to write MAX77775_FG_REG_CONFIG\n", __func__); + return -1; + } + + max77775_update_reg(fuelgauge->pmic, MAX77775_PMIC_REG_INTSRC_MASK, + ~MAX77775_IRQSRC_FG, MAX77775_IRQSRC_FG); + + pr_info("[%s] SALRT(0x%02x%02x), CONFIG(0x%02x%02x)\n", __func__, + salrt_data[1], salrt_data[0], config_data[1], config_data[0]); + + return 1; +} + +static int max77775_get_fg_soc(struct max77775_fuelgauge_data *fuelgauge) +{ + int fg_soc = 0, fg_vcell, avg_current; + + fg_soc = max77775_get_fuelgauge_value(fuelgauge, FG_LEVEL); + if (fg_soc < 0) { + pr_info("Can't read soc!!!"); + fg_soc = fuelgauge->info.soc; + } + + fg_vcell = max77775_get_fuelgauge_value(fuelgauge, FG_VOLTAGE); + avg_current = max77775_get_fuelgauge_value(fuelgauge, FG_CURRENT_AVG); + + if (fuelgauge->info.is_first_check) + fuelgauge->info.is_first_check = false; + + fuelgauge->info.soc = fg_soc; + pr_debug("%s: soc(%d)\n", __func__, fuelgauge->info.soc); + + return fg_soc; +} + +static void max77775_offset_leakage( + struct max77775_fuelgauge_data *fuelgauge) +{ + // offset coffset + if (fuelgauge->battery_data->coff_origin != fuelgauge->battery_data->coff_charging) { + pr_info("%s: modify coffset\n", __func__); + if (fuelgauge->is_charging) { + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_COFF, + fuelgauge->battery_data->coff_charging); + pr_info("%s: modify coffset 0x%x\n", __func__, fuelgauge->battery_data->coff_charging); + } else { + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_COFF, + fuelgauge->battery_data->coff_origin); + pr_info("%s: modify coffset 0x%x\n", __func__, fuelgauge->battery_data->coff_origin); + } + } + + // offset cgain + if (fuelgauge->battery_data->cgain_origin) { + pr_info("%s: modify cgain\n", __func__); + if (fuelgauge->is_charging) { + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_CGAIN, + fuelgauge->battery_data->cgain_charging); + pr_info("%s: modify cgain 0x%x\n", __func__, fuelgauge->battery_data->cgain_charging); + } else { + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_CGAIN, + fuelgauge->battery_data->cgain_origin); + pr_info("%s: modify cgain 0x%x\n", __func__, fuelgauge->battery_data->cgain_origin); + } + } +} + +static void max77775_offset_leakage_default( + struct max77775_fuelgauge_data *fuelgauge) +{ + // offset coffset + if (fuelgauge->battery_data->coff_charging) { + pr_info("%s: modify coffset\n", __func__); + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_COFF, + fuelgauge->battery_data->coff_charging); + pr_info("%s: modify coffset 0x%x\n", __func__, fuelgauge->battery_data->coff_charging); + } + + // offset cgain + if (fuelgauge->battery_data->cgain_origin) { + pr_info("%s: modify cgain\n", __func__); + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_CGAIN, + fuelgauge->battery_data->cgain_origin); + pr_info("%s: modify cgain 0x%x\n", __func__, fuelgauge->battery_data->cgain_origin); + } +} + +static irqreturn_t max77775_jig_irq_thread(int irq, void *irq_data) +{ + struct max77775_fuelgauge_data *fuelgauge = irq_data; + + pr_info("%s\n", __func__); + + if (max77775_check_jig_status(fuelgauge)) + max77775_fg_reset_capacity_by_jig_connection(fuelgauge); + else + pr_info("%s: jig removed\n", __func__); + + return IRQ_HANDLED; +} + +bool max77775_fg_init(struct max77775_fuelgauge_data *fuelgauge) +{ + ktime_t current_time; + struct timespec64 ts; + u8 data[2] = { 0, 0 }; + +#if defined(ANDROID_ALARM_ACTIVATED) + current_time = alarm_get_elapsed_realtime(); +#else + current_time = ktime_get_boottime(); +#endif + ts = ktime_to_timespec64(ktime_get_boottime()); + + fuelgauge->info.fullcap_check_interval = ts.tv_sec; + fuelgauge->info.is_first_check = true; + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) // will remove + /* clear vex bit */ + if (max77775_bulk_read(fuelgauge->i2c, 0x2B, 2, data) < 0) { + pr_err("%s: Failed to read MISCCFG_REG\n", __func__); + return -1; + } + + data[0] &= 0xFB; + if (max77775_bulk_write(fuelgauge->i2c, 0x2B, 2, data) < 0) { + pr_info("%s: Failed to write MISCCFG_REG\n", __func__); + return -1; + } +#endif + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_CONFIG2, 2, data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_CONFIG2\n", __func__); + } else if ((data[0] & 0x0F) != 0x05) { + data[0] &= ~0x2F; + data[0] |= (0x5 & 0xF); /* ISysNCurr: 11.25 */ + max77775_bulk_write(fuelgauge->i2c, MAX77775_FG_REG_CONFIG2, 2, data); + } + + /* 0xB2 == modeldata_ver reg */ + if (max77775_read_word(fuelgauge->i2c, 0xB2) != fuelgauge->data_ver) { + pr_err("%s: fg data_ver miss match. skip verify fg reg\n", __func__); + fuelgauge->skip_fg_verify = true; + } else { + pr_err("%s: fg data_ver match!(0x%x)\n", __func__, fuelgauge->data_ver); + fuelgauge->skip_fg_verify = false; + } + + /* NOT using FG for temperature */ + if (fuelgauge->pdata->thermal_source != SEC_BATTERY_THERMAL_SOURCE_FG) { + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_CONFIG, 2, data) < 0) { + pr_err("%s : Failed to read MAX77775_FG_REG_CONFIG\n", __func__); + return false; + } + data[1] |= 0x1; + + if (max77775_bulk_write(fuelgauge->i2c, MAX77775_FG_REG_CONFIG, 2, data) < 0) { + pr_info("%s : Failed to write MAX77775_FG_REG_CONFIG\n", __func__); + return false; + } + } else { + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_CONFIG, 2, data) < 0) { + pr_err("%s : Failed to read MAX77775_FG_REG_CONFIG\n", __func__); + return false; + } + data[1] &= 0xFE; + data[0] |= 0x10; + + if (max77775_bulk_write(fuelgauge->i2c, MAX77775_FG_REG_CONFIG, 2, data) < 0) { + pr_info("%s : Failed to write MAX77775_FG_REG_CONFIG\n", __func__); + return false; + } + } + max77775_offset_leakage(fuelgauge); + + return true; +} + +bool max77775_fg_fuelalert_init(struct max77775_fuelgauge_data *fuelgauge, + int soc) +{ + /* 1. Set max77775 alert configuration. */ + if (max77775_fg_alert_init(fuelgauge, soc) > 0) + return true; + else + return false; +} + +void max77775_fg_fuelalert_set(struct max77775_fuelgauge_data *fuelgauge, + int enable) +{ + u8 config_data[2], status_data[2]; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_CONFIG, 2, config_data) < 0) + pr_err("%s: Failed to read MAX77775_FG_REG_CONFIG\n", __func__); + + if (enable) + config_data[0] |= ALERT_EN; + else + config_data[0] &= ~ALERT_EN; + + pr_info("%s: CONFIG(0x%02x%02x)\n", __func__, config_data[1], config_data[0]); + + if (max77775_bulk_write(fuelgauge->i2c, MAX77775_FG_REG_CONFIG, 2, config_data) < 0) + pr_info("%s: Failed to write MAX77775_FG_REG_CONFIG\n", __func__); + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_STATUS, 2, status_data) < 0) + pr_err("%s: Failed to read MAX77775_FG_REG_STATUS\n", __func__); + + if ((status_data[1] & 0x01) && !max77775_get_lpmode() && !fuelgauge->is_charging) { + pr_info("%s: Battery Voltage is Very Low!! V EMPTY(%d)\n", + __func__, fuelgauge->vempty_mode); + + if (fuelgauge->vempty_mode != VEMPTY_MODE_HW) + fuelgauge->vempty_mode = VEMPTY_MODE_SW_VALERT; + else if (!fuelgauge->valert_count_flag) { + union power_supply_propval value; + + value.intval = fuelgauge->vempty_mode; + psy_do_property("battery", set, + POWER_SUPPLY_PROP_VOLTAGE_MIN, value); + fuelgauge->valert_count_flag = true; + } + } +} + +bool max77775_fg_fuelalert_process(void *irq_data) +{ + struct max77775_fuelgauge_data *fuelgauge = + (struct max77775_fuelgauge_data *)irq_data; + + max77775_fg_fuelalert_set(fuelgauge, 0); + + return true; +} + +bool max77775_fg_reset(struct max77775_fuelgauge_data *fuelgauge) +{ + if (!max77775_fg_reset_soc(fuelgauge)) + return true; + else + return false; +} + +static int max77775_fg_check_capacity_max( + struct max77775_fuelgauge_data *fuelgauge, int capacity_max) +{ + int cap_max, cap_min; + + cap_max = fuelgauge->pdata->capacity_max; + cap_min = + (fuelgauge->pdata->capacity_max - fuelgauge->pdata->capacity_max_margin); + + return (capacity_max < cap_min) ? cap_min : + ((capacity_max >= cap_max) ? cap_max : capacity_max); +} + + +#if defined(CONFIG_UI_SOC_PROLONGING) +static void max77775_fg_adjust_capacity_max( + struct max77775_fuelgauge_data *fuelgauge, int curr_raw_soc) +{ + int diff = 0; + + if (fuelgauge->is_charging && fuelgauge->capacity_max_conv) { + diff = curr_raw_soc - fuelgauge->prev_raw_soc; + + if ((diff >= 1) && (fuelgauge->capacity_max < fuelgauge->g_capacity_max)) { + fuelgauge->capacity_max++; + } else if ((fuelgauge->capacity_max >= fuelgauge->g_capacity_max) || (curr_raw_soc == 1000)) { + fuelgauge->g_capacity_max = 0; + fuelgauge->capacity_max_conv = false; + } + pr_info("%s: curr_raw_soc(%d) prev_raw_soc(%d) capacity_max_conv(%d) Capacity Max(%d | %d)\n", + __func__, curr_raw_soc, fuelgauge->prev_raw_soc, fuelgauge->capacity_max_conv, + fuelgauge->capacity_max, fuelgauge->g_capacity_max); + } + + fuelgauge->prev_raw_soc = curr_raw_soc; +} +#else +static void max77775_fg_adjust_capacity_max( + struct max77775_fuelgauge_data *fuelgauge) +{ + struct timespec64 c_ts = {0, }; + static struct timespec64 old_ts = {0, }; + + if (fuelgauge->capacity_max_conv) { + c_ts = ktime_to_timespec64(ktime_get_boottime()); + pr_info("%s: capacit max conv time(%llu)\n", + __func__, c_ts.tv_sec - old_ts.tv_sec); + + if ((fuelgauge->capacity_max < fuelgauge->g_capacity_max) && + ((unsigned long)(c_ts.tv_sec - old_ts.tv_sec) >= 60)) { + fuelgauge->capacity_max++; + old_ts = c_ts; + } else if (fuelgauge->capacity_max >= fuelgauge->g_capacity_max) { + fuelgauge->g_capacity_max = 0; + fuelgauge->capacity_max_conv = false; + } + pr_info("%s: capacity_max_conv(%d) Capacity Max(%d | %d)\n", + __func__, fuelgauge->capacity_max_conv, + fuelgauge->capacity_max, fuelgauge->g_capacity_max); + } +} +#endif + +static unsigned int max77775_fg_get_scaled_capacity( + struct max77775_fuelgauge_data *fuelgauge, unsigned int soc) +{ + int raw_soc = soc; + + soc = (soc < fuelgauge->pdata->capacity_min) ? + 0 : ((soc - fuelgauge->pdata->capacity_min) * 1000 / + (fuelgauge->capacity_max - fuelgauge->pdata->capacity_min)); + + pr_info("%s : capacity_max (%d) scaled capacity(%d.%d), raw_soc(%d.%d)\n", + __func__, fuelgauge->capacity_max, soc / 10, soc % 10, + raw_soc / 10, raw_soc % 10); + + return soc; +} + +/* capacity is integer */ +static unsigned int max77775_fg_get_atomic_capacity( + struct max77775_fuelgauge_data *fuelgauge, unsigned int soc) +{ + pr_info("%s : NOW(%d), OLD(%d)\n", + __func__, soc, fuelgauge->capacity_old); + + if (fuelgauge->pdata->capacity_calculation_type & + SEC_FUELGAUGE_CAPACITY_TYPE_ATOMIC) { + if (fuelgauge->capacity_old < soc) { + soc = fuelgauge->capacity_old + 1; + } else if (fuelgauge->capacity_old > soc){ + if(fuelgauge->capacity_old > 0) + soc = fuelgauge->capacity_old - 1; + else + soc = 0; + } + } + + /* keep SOC stable in abnormal status */ + if (fuelgauge->pdata->capacity_calculation_type & + SEC_FUELGAUGE_CAPACITY_TYPE_SKIP_ABNORMAL) { + if (!fuelgauge->is_charging && + fuelgauge->capacity_old < soc) { + pr_err("%s: capacity (old %d : new %d)\n", + __func__, fuelgauge->capacity_old, soc); + soc = fuelgauge->capacity_old; + } + } + + /* updated old capacity */ + fuelgauge->capacity_old = soc; + + return soc; +} + +#if defined(CONFIG_UI_SOC_PROLONGING) +static void max77775_fg_calculate_dynamic_scale( + struct max77775_fuelgauge_data *fuelgauge, int capacity, bool scale_by_full) +{ + union power_supply_propval raw_soc_val; + int min_cap = fuelgauge->pdata->capacity_max - fuelgauge->pdata->capacity_max_margin; + int scaling_factor = 1; + + if ((capacity > 100) || ((capacity * 10) < min_cap)) { + pr_err("%s: invalid capacity(%d)\n", __func__, capacity); + return; + } + + raw_soc_val.intval = max77775_get_fuelgauge_value(fuelgauge, FG_RAW_SOC); + if (raw_soc_val.intval < 0) { + pr_info("%s: failed to get raw soc\n", __func__); + return; + } + + raw_soc_val.intval = raw_soc_val.intval / 10; + + if (capacity < 100) + fuelgauge->capacity_max_conv = false; //Force full sequence , need to decrease capacity_max + + if ((raw_soc_val.intval < min_cap) || (fuelgauge->capacity_max_conv)) { + pr_info("%s: skip routine - raw_soc(%d), min_cap(%d), cap_max_conv(%d)\n", + __func__, raw_soc_val.intval, min_cap, fuelgauge->capacity_max_conv); + return; + } + + if (capacity == 100) + scaling_factor = 2; + + fuelgauge->capacity_max = (raw_soc_val.intval * 100 / (capacity + scaling_factor)); + fuelgauge->capacity_old = capacity; + + fuelgauge->capacity_max = + max77775_fg_check_capacity_max(fuelgauge, fuelgauge->capacity_max); + + pr_info("%s: %d is used for capacity_max, capacity(%d)\n", + __func__, fuelgauge->capacity_max, capacity); + if ((capacity == 100) && !fuelgauge->capacity_max_conv && scale_by_full) { + fuelgauge->capacity_max_conv = true; + fuelgauge->g_capacity_max = 990; + pr_info("%s: Goal capacity max %d\n", __func__, fuelgauge->g_capacity_max); + } +} +#else +static void max77775_fg_calculate_dynamic_scale( + struct max77775_fuelgauge_data *fuelgauge, int capacity, bool scale_by_full) +{ + union power_supply_propval raw_soc_val; + int min_cap = fuelgauge->pdata->capacity_max - fuelgauge->pdata->capacity_max_margin; + + if ((capacity > 100) || ((capacity * 10) < min_cap)) { + pr_err("%s: invalid capacity(%d)\n", __func__, capacity); + return; + } + + raw_soc_val.intval = max77775_get_fuelgauge_value(fuelgauge, FG_RAW_SOC); + if (raw_soc_val.intval < 0) { + pr_info("%s: failed to get raw soc\n", __func__); + return; + } + + raw_soc_val.intval = raw_soc_val.intval / 10; + if ((raw_soc_val.intval < min_cap) || (fuelgauge->capacity_max_conv)) { + pr_info("%s: skip routine - raw_soc(%d), min_cap(%d), cap_max_conv(%d)\n", + __func__, raw_soc_val.intval, min_cap, fuelgauge->capacity_max_conv); + return; + } + + fuelgauge->capacity_max = (raw_soc_val.intval * 100 / (capacity + 1)); + fuelgauge->capacity_old = capacity; + + fuelgauge->capacity_max = + max77775_fg_check_capacity_max(fuelgauge, fuelgauge->capacity_max); + + pr_info("%s: %d is used for capacity_max, capacity(%d)\n", + __func__, fuelgauge->capacity_max, capacity); + if ((capacity == 100) && !fuelgauge->capacity_max_conv && scale_by_full) { + fuelgauge->capacity_max_conv = true; + fuelgauge->g_capacity_max = min(990, raw_soc_val.intval); + pr_info("%s: Goal capacity max %d\n", __func__, fuelgauge->g_capacity_max); + } +} +#endif + +__visible_for_testing void max77775_lost_soc_reset(struct max77775_fuelgauge_data *fuelgauge) +{ + fuelgauge->lost_soc.ing = false; + fuelgauge->lost_soc.prev_raw_soc = -1; + fuelgauge->lost_soc.prev_remcap = 0; + fuelgauge->lost_soc.prev_qh = 0; + fuelgauge->lost_soc.lost_cap = 0; + fuelgauge->lost_soc.weight = 0; +} + +__visible_for_testing void max77775_lost_soc_check_trigger_cond( + struct max77775_fuelgauge_data *fuelgauge, int raw_soc, int d_raw_soc, int d_remcap, int d_qh) +{ + if (fuelgauge->lost_soc.prev_raw_soc >= fuelgauge->lost_soc.trig_soc || + d_raw_soc <= 0 || d_qh < 0) + return; + + /* + * raw soc is jumped over gap_soc + * and remcap is decreased more than trig_scale of qh + */ + if (d_raw_soc >= fuelgauge->lost_soc.trig_d_soc && + d_remcap >= (d_qh * fuelgauge->lost_soc.trig_scale)) { + fuelgauge->lost_soc.ing = true; + fuelgauge->lost_soc.lost_cap += d_remcap; + + /* calc weight */ + fuelgauge->lost_soc.weight += d_raw_soc * 10 / fuelgauge->lost_soc.guarantee_soc; + if (fuelgauge->lost_soc.weight < fuelgauge->lost_soc.min_weight) + fuelgauge->lost_soc.weight = fuelgauge->lost_soc.min_weight; + + pr_info("%s: trigger: raw_soc(%d->%d), d_raw_soc(%d), d_remcap(%d), d_qh(%d), weight(%d.%d)\n", + __func__, fuelgauge->lost_soc.prev_raw_soc, raw_soc, d_raw_soc, d_remcap, + d_qh, fuelgauge->lost_soc.weight / 10, fuelgauge->lost_soc.weight % 10); + } +} + +__visible_for_testing int max77775_lost_soc_calc_soc( + struct max77775_fuelgauge_data *fuelgauge, int request_soc, int d_qh, int d_remcap) +{ + int lost_soc = 0, gap_cap = 0; + int vavg = 0, fullcaprep = 0, onecap = 0; + + vavg = max77775_fg_read_avg_vcell(fuelgauge); + fullcaprep = max77775_fg_read_fullcaprep(fuelgauge); + if (fullcaprep < 0) { + fullcaprep = fuelgauge->battery_data->Capacity * fuelgauge->fg_resistor / 2; + pr_info("%s: ing: fullcaprep is replaced\n", __func__); + } + onecap = (fullcaprep / 100) + 1; + + if (d_qh < 0) { + /* charging status, recover capacity is delta of remcap */ + if (d_remcap < 0) + gap_cap = d_remcap * (-1); + else + gap_cap = d_remcap; + } else if (d_qh == 0) { + gap_cap = 1; + } else { + gap_cap = (d_qh * fuelgauge->lost_soc.weight / 10); + } + + if ((vavg < fuelgauge->lost_soc.min_vol) && (vavg > 0) && (gap_cap < onecap)) { + gap_cap = onecap; /* reduce 1% */ + pr_info("%s: ing: vavg(%d) is under min_vol(%d), reduce cap more(%d)\n", + __func__, vavg, fuelgauge->lost_soc.min_vol, (fullcaprep / 100)); + } + + fuelgauge->lost_soc.lost_cap -= gap_cap; + + if (fuelgauge->lost_soc.lost_cap > 0) { + lost_soc = (fuelgauge->lost_soc.lost_cap * 1000) / fullcaprep; + pr_info("%s: ing: calc_soc(%d), lost_soc(%d), lost_cap(%d), d_qh(%d), d_remcap(%d), weight(%d.%d)\n", + __func__, request_soc + lost_soc, lost_soc, fuelgauge->lost_soc.lost_cap, + d_qh, d_remcap, fuelgauge->lost_soc.weight / 10, fuelgauge->lost_soc.weight % 10); + } else { + lost_soc = 0; + max77775_lost_soc_reset(fuelgauge); + pr_info("%s: done: request_soc(%d), lost_soc(%d), lost_cap(%d)\n", + __func__, request_soc, lost_soc, fuelgauge->lost_soc.lost_cap); + } + + return lost_soc; +} + +static int max77775_lost_soc_get(struct max77775_fuelgauge_data *fuelgauge, int request_soc) +{ + int raw_soc, remcap, qh; /* now values */ + int d_raw_soc, d_remcap, d_qh; /* delta between prev values */ + int report_soc; + + /* get current values */ + raw_soc = max77775_get_fuelgauge_value(fuelgauge, FG_RAW_SOC) / 10; + remcap = max77775_get_fuelgauge_value(fuelgauge, FG_REPCAP); + qh = max77775_get_fuelgauge_value(fuelgauge, FG_QH) / 1000; + + if (fuelgauge->lost_soc.prev_raw_soc < 0) { + fuelgauge->lost_soc.prev_raw_soc = raw_soc; + fuelgauge->lost_soc.prev_remcap = remcap; + fuelgauge->lost_soc.prev_qh = qh; + fuelgauge->lost_soc.lost_cap = 0; + pr_info("%s: init: raw_soc(%d), remcap(%d), qh(%d)\n", + __func__, raw_soc, remcap, qh); + + return request_soc; + } + + /* get diff values with prev */ + d_raw_soc = fuelgauge->lost_soc.prev_raw_soc - raw_soc; + d_remcap = fuelgauge->lost_soc.prev_remcap - remcap; + d_qh = fuelgauge->lost_soc.prev_qh - qh; + + max77775_lost_soc_check_trigger_cond(fuelgauge, raw_soc, d_raw_soc, d_remcap, d_qh); + + /* backup prev values */ + fuelgauge->lost_soc.prev_raw_soc = raw_soc; + fuelgauge->lost_soc.prev_remcap = remcap; + fuelgauge->lost_soc.prev_qh = qh; + + if (!fuelgauge->lost_soc.ing) + return request_soc; + + report_soc = request_soc + max77775_lost_soc_calc_soc(fuelgauge, request_soc, d_qh, d_remcap); + + if (report_soc > 1000) + report_soc = 1000; + if (report_soc < 0) + report_soc = 0; + + return report_soc; +} + +static bool max77775_fg_check_vempty_recover_time(struct max77775_fuelgauge_data *fuelgauge) +{ + struct timespec64 c_ts = {0, }; + unsigned long vempty_time = 0; + + c_ts = ktime_to_timespec64(ktime_get_boottime()); + if (!fuelgauge->vempty_time) + fuelgauge->vempty_time = (c_ts.tv_sec) ? c_ts.tv_sec : 1; + else if (c_ts.tv_sec >= fuelgauge->vempty_time) + vempty_time = c_ts.tv_sec - fuelgauge->vempty_time; + else + vempty_time = 0xFFFFFFFF - fuelgauge->vempty_time + c_ts.tv_sec; + + pr_info("%s: check vempty time(%ld, %ld)\n", + __func__, fuelgauge->vempty_time, vempty_time); + return (vempty_time >= fuelgauge->vempty_recover_time); +} + +static unsigned int max77775_check_vempty_status(struct max77775_fuelgauge_data *fuelgauge, unsigned int soc) +{ + if (!fuelgauge->using_hw_vempty || !fuelgauge->vempty_init_flag) { + fuelgauge->vempty_time = 0; + return soc; + } + + /* SW/HW V Empty setting */ + if (fuelgauge->temperature <= (int)fuelgauge->low_temp_limit) { + if (fuelgauge->raw_capacity <= 50) { + if ((fuelgauge->vempty_mode != VEMPTY_MODE_HW) && + max77775_fg_check_vempty_recover_time(fuelgauge)) { + max77775_fg_set_vempty(fuelgauge, VEMPTY_MODE_HW); + fuelgauge->vempty_time = 0; + } + } else { + if (fuelgauge->vempty_mode == VEMPTY_MODE_HW) + max77775_fg_set_vempty(fuelgauge, VEMPTY_MODE_SW); + + fuelgauge->vempty_time = 0; + } + } else if (fuelgauge->vempty_mode != VEMPTY_MODE_HW && + max77775_fg_check_vempty_recover_time(fuelgauge)) { + max77775_fg_set_vempty(fuelgauge, VEMPTY_MODE_HW); + fuelgauge->vempty_time = 0; + } + + if (!fuelgauge->is_charging && !max77775_get_lpmode() && + fuelgauge->vempty_mode == VEMPTY_MODE_SW_VALERT) { + if (!fuelgauge->vempty_time) { + pr_info("%s : SW V EMPTY. Decrease SOC\n", __func__); + if (fuelgauge->capacity_old > 0) + soc = fuelgauge->capacity_old - 1; + else + soc = 0; + } + } else if ((fuelgauge->vempty_mode == VEMPTY_MODE_SW_RECOVERY) + && (soc == fuelgauge->capacity_old)) { + fuelgauge->vempty_mode = VEMPTY_MODE_SW; + } + + return soc; +} + +static int max77775_get_fg_ui_soc(struct max77775_fuelgauge_data *fuelgauge, union power_supply_propval *val) +{ +#if defined(CONFIG_UI_SOC_PROLONGING) + int scale_to = 1020; +#else + int scale_to = 1010; +#endif + if (val->intval == SEC_FUELGAUGE_CAPACITY_TYPE_RAW) { + val->intval = max77775_get_fuelgauge_value(fuelgauge, FG_RAW_SOC); + } else if (val->intval == SEC_FUELGAUGE_CAPACITY_TYPE_CAPACITY_POINT) { + val->intval = fuelgauge->raw_capacity % 10; + } else if (val->intval == SEC_FUELGAUGE_CAPACITY_TYPE_DYNAMIC_SCALE) { + val->intval = fuelgauge->raw_capacity; + } else { + val->intval = max77775_get_fg_soc(fuelgauge); + + if (fuelgauge->pdata->capacity_calculation_type & + (SEC_FUELGAUGE_CAPACITY_TYPE_SCALE | + SEC_FUELGAUGE_CAPACITY_TYPE_DYNAMIC_SCALE)) { + +#if defined(CONFIG_UI_SOC_PROLONGING) + max77775_fg_adjust_capacity_max(fuelgauge, val->intval); +#else + max77775_fg_adjust_capacity_max(fuelgauge); +#endif + val->intval = max77775_fg_get_scaled_capacity(fuelgauge, val->intval); + + if (val->intval > scale_to) { + pr_info("%s: scaled capacity (%d)\n", __func__, val->intval); + max77775_fg_calculate_dynamic_scale(fuelgauge, 100, false); + } + } + + /* capacity should be between 0% and 100% + * (0.1% degree) + */ + if (val->intval > 1000) + val->intval = 1000; + if (val->intval < 0) + val->intval = 0; + + fuelgauge->raw_capacity = val->intval; + + if (fuelgauge->pdata->capacity_calculation_type & + SEC_FUELGAUGE_CAPACITY_TYPE_LOST_SOC) { + val->intval = max77775_lost_soc_get(fuelgauge, fuelgauge->raw_capacity); + } + + /* get only integer part */ + val->intval /= 10; + + /* check out vempty status and get new soc */ + val->intval = max77775_check_vempty_status(fuelgauge, val->intval); + + /* check whether doing the __pm_relax */ + if ((val->intval > fuelgauge->pdata->fuel_alert_soc) && + fuelgauge->is_fuel_alerted) { + max77775_fg_fuelalert_init(fuelgauge, + fuelgauge->pdata->fuel_alert_soc); + } + +#if defined(CONFIG_UI_SOC_PROLONGING) + /* Check UI soc reached 100% from 99% , no need to adjust now */ + if ((val->intval == 100) && (fuelgauge->capacity_old < 100) && + (fuelgauge->capacity_max_conv == true)) + fuelgauge->capacity_max_conv = false; +#endif + + /* (Only for atomic capacity) + * In initial time, capacity_old is 0. + * and in resume from sleep, + * capacity_old is too different from actual soc. + * should update capacity_old + * by val->intval in booting or resume. + */ + if (fuelgauge->initial_update_of_soc) { + fuelgauge->initial_update_of_soc = false; + if (fuelgauge->vempty_mode != VEMPTY_MODE_SW_VALERT) { + /* updated old capacity */ + fuelgauge->capacity_old = val->intval; + + return val->intval; + } + } + + if (fuelgauge->sleep_initial_update_of_soc) { + /* updated old capacity in case of resume */ + if (fuelgauge->is_charging || + ((!fuelgauge->is_charging) && (fuelgauge->capacity_old >= val->intval))) { + fuelgauge->capacity_old = val->intval; + fuelgauge->sleep_initial_update_of_soc = false; + + return val->intval; + } + } + + if (fuelgauge->pdata->capacity_calculation_type & + (SEC_FUELGAUGE_CAPACITY_TYPE_ATOMIC | + SEC_FUELGAUGE_CAPACITY_TYPE_SKIP_ABNORMAL)) { + val->intval = max77775_fg_get_atomic_capacity(fuelgauge, val->intval); + } + } + + return val->intval; +} + +#if defined(CONFIG_EN_OOPS) +static void max77775_set_full_value(struct max77775_fuelgauge_data *fuelgauge, + int cable_type) +{ + u16 ichgterm, misccfg, fullsocthr; + + if (is_hv_wireless_type(cable_type) || is_hv_wire_type(cable_type)) { + ichgterm = fuelgauge->battery_data->ichgterm_2nd; + misccfg = fuelgauge->battery_data->misccfg_2nd; + fullsocthr = fuelgauge->battery_data->fullsocthr_2nd; + } else { + ichgterm = fuelgauge->battery_data->ichgterm; + misccfg = fuelgauge->battery_data->misccfg; + fullsocthr = fuelgauge->battery_data->fullsocthr; + } + + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_ICHGTERM, ichgterm); + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_MISCCFG, misccfg); + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_FULLSOCTHR, fullsocthr); + + pr_info("%s : ICHGTERM(0x%04x) FULLSOCTHR(0x%04x), MISCCFG(0x%04x)\n", + __func__, ichgterm, misccfg, fullsocthr); +} +#endif + +static int max77775_fg_check_initialization_result( + struct max77775_fuelgauge_data *fuelgauge) +{ + u8 data[2]; + + if (max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_TALRTTH2, 2, data) < 0) { + pr_err("%s: Failed to read MAX77775_FG_REG_TALRTTH2\n", __func__); + return SEC_BAT_ERROR_CAUSE_I2C_FAIL; + } + + if (data[1] == 0xFF) { + pr_info("%s : initialization is failed.(0x%02X:0x%04X)\n", + __func__, MAX77775_FG_REG_TALRTTH2, data[1] << 8 | data[0]); + return SEC_BAT_ERROR_CAUSE_FG_INIT_FAIL; + } else { + pr_info("%s : initialization is succeed.(0x%02X:0x%04X)\n", + __func__, MAX77775_FG_REG_TALRTTH2, data[1] << 8 | data[0]); + } + + return SEC_BAT_ERROR_CAUSE_NONE; +} + +static int max77775_fg_create_attrs(struct device *dev) +{ + int i, rc; + + for (i = 0; i < (int)ARRAY_SIZE(max77775_fg_attrs); i++) { + rc = device_create_file(dev, &max77775_fg_attrs[i]); + if (rc) + goto create_attrs_failed; + } + return rc; + +create_attrs_failed: + dev_err(dev, "%s: failed (%d)\n", __func__, rc); + while (i--) + device_remove_file(dev, &max77775_fg_attrs[i]); + return rc; +} + +ssize_t max77775_fg_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct max77775_fuelgauge_data *fuelgauge = power_supply_get_drvdata(psy); + const ptrdiff_t offset = attr - max77775_fg_attrs; + int i = 0, j = 0; + u8 addr = 0; + u16 data = 0; + + dev_info(fuelgauge->dev, "%s: (%ld)\n", __func__, offset); + + switch (offset) { + case FG_DATA: + for (j = 0; j <= 0x0F; j++) { + for (addr = 0; addr < 0x10; addr++) { + data = max77775_read_word(fuelgauge->i2c, addr + j * 0x10); + i += scnprintf(buf + i, PAGE_SIZE - i, + "0x%02x:\t0x%04x\n", addr + j * 0x10, data); + } + if (j == 5) + j = 0x0A; + } + break; + default: + return -EINVAL; + } + return i; +} + +ssize_t max77775_fg_store_attrs(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct max77775_fuelgauge_data *fuelgauge = power_supply_get_drvdata(psy); + const ptrdiff_t offset = attr - max77775_fg_attrs; + int ret = 0, x, y; + + dev_info(fuelgauge->dev, "%s: (%ld)\n", __func__, offset); + + switch (offset) { + case FG_DATA: + if (sscanf(buf, "0x%8x 0x%8x", &x, &y) == 2) { + if (x >= MAX77775_FG_REG_VALRTTH && x <= MAX77775_FG_REG_VFSOC) { + u8 addr = x; + u16 data = y; + + if (max77775_write_word(fuelgauge->i2c, addr, data) < 0) + dev_info(fuelgauge->dev,"%s: addr: 0x%x write fail\n", + __func__, addr); + } else { + dev_info(fuelgauge->dev,"%s: addr: 0x%x is wrong\n", + __func__, x); + } + } + ret = count; + break; + default: + ret = -EINVAL; + } + return ret; +} + +static void max77775_fg_bd_log(struct max77775_fuelgauge_data *fuelgauge) +{ + memset(fuelgauge->d_buf, 0x0, sizeof(fuelgauge->d_buf)); + + snprintf(fuelgauge->d_buf, sizeof(fuelgauge->d_buf), + "%d,%d,%d", + fuelgauge->bd_vfocv, + fuelgauge->bd_raw_soc, + fuelgauge->capacity_max); +} + +static int max77775_fg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max77775_fuelgauge_data *fuelgauge = power_supply_get_drvdata(psy); + enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; + u8 data[2] = { 0, 0 }; + + if (atomic_read(&fuelgauge->shutdown_cnt) > 0) { + dev_info(fuelgauge->dev, "%s: fuelgauge already shutdown\n", __func__); + return -EINVAL; + } + + switch ((int)psp) { + /* Cell voltage (VCELL, mV) */ + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = max77775_get_fuelgauge_value(fuelgauge, FG_VOLTAGE); + break; + /* Additional Voltage Information (mV) */ + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + val->intval = max77775_fg_read_avg_vcell(fuelgauge); + break; + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + val->intval = max77775_fg_read_vfocv(fuelgauge); + break; + /* Current */ + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = max77775_fg_read_current(fuelgauge, val->intval); + break; + /* Average Current */ + case POWER_SUPPLY_PROP_CURRENT_AVG: + switch (val->intval) { + case SEC_BATTERY_CURRENT_UA: + val->intval = max77775_fg_read_avg_current(fuelgauge, + SEC_BATTERY_CURRENT_UA); + break; + case SEC_BATTERY_CURRENT_MA: + default: + fuelgauge->current_avg = val->intval = + max77775_fg_read_avg_current(fuelgauge, + SEC_BATTERY_CURRENT_MA); + break; + } + break; + /* Full Capacity */ + case POWER_SUPPLY_PROP_ENERGY_NOW: + switch (val->intval) { + case SEC_BATTERY_CAPACITY_DESIGNED: + val->intval = max77775_get_fuelgauge_value(fuelgauge, FG_FULLCAP); + break; + case SEC_BATTERY_CAPACITY_ABSOLUTE: + val->intval = max77775_get_fuelgauge_value(fuelgauge, FG_MIXCAP); + break; + case SEC_BATTERY_CAPACITY_TEMPERARY: + val->intval = max77775_get_fuelgauge_value(fuelgauge, FG_AVCAP); + break; + case SEC_BATTERY_CAPACITY_CURRENT: + val->intval = max77775_get_fuelgauge_value(fuelgauge, FG_REPCAP); + break; + case SEC_BATTERY_CAPACITY_AGEDCELL: + val->intval = max77775_get_fuelgauge_value(fuelgauge, FG_FULLCAPNOM); + break; + case SEC_BATTERY_CAPACITY_CYCLE: + val->intval = max77775_get_fuelgauge_value(fuelgauge, FG_CYCLE); + break; + case SEC_BATTERY_CAPACITY_FULL: + val->intval = max77775_get_fuelgauge_value(fuelgauge, FG_FULLCAPREP); + break; + case SEC_BATTERY_CAPACITY_QH: + val->intval = max77775_get_fuelgauge_value(fuelgauge, FG_QH); + break; + case SEC_BATTERY_CAPACITY_VFSOC: + val->intval = max77775_get_fuelgauge_value(fuelgauge, FG_QH_VF_SOC); + break; + } + break; + /* SOC (%) */ + case POWER_SUPPLY_PROP_CAPACITY: + mutex_lock(&fuelgauge->fg_lock); + val->intval = max77775_get_fg_ui_soc(fuelgauge, val); + mutex_unlock(&fuelgauge->fg_lock); + break; + /* Battery Temperature */ + case POWER_SUPPLY_PROP_TEMP: + /* Target Temperature */ + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + val->intval = max77775_get_fuelgauge_value(fuelgauge, FG_TEMPERATURE); + break; +#if defined(CONFIG_EN_OOPS) + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + return -ENODATA; +#endif + case POWER_SUPPLY_PROP_ENERGY_FULL: + { + int fullcap = max77775_get_fuelgauge_value(fuelgauge, FG_FULLCAPNOM); + + val->intval = fullcap * 100 / + (fuelgauge->battery_data->Capacity * fuelgauge->fg_resistor / 2); + pr_info("%s: asoc(%d), fullcap(%d)\n", __func__, + val->intval, fullcap); +#if !defined(CONFIG_SEC_FACTORY) + max77775_fg_periodic_read(fuelgauge); +#endif + } + break; + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + val->intval = fuelgauge->capacity_max; + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + return -ENODATA; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + val->intval = fuelgauge->raw_capacity * + (fuelgauge->battery_data->Capacity * fuelgauge->fg_resistor / 2); + break; + case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: + switch (ext_psp) { + case POWER_SUPPLY_EXT_PROP_REPSOC: + val->intval = max77775_fg_read_soc(fuelgauge); + break; + case POWER_SUPPLY_EXT_PROP_REPCAP: + val->intval = max77775_fg_read_repcap(fuelgauge); + break; + case POWER_SUPPLY_EXT_PROP_FULLCAPREP: + val->intval = max77775_fg_read_fullcaprep(fuelgauge); + break; + case POWER_SUPPLY_EXT_PROP_FULLCAPNOM: + val->intval = max77775_fg_read_fullcapnom(fuelgauge); + break; + case POWER_SUPPLY_EXT_PROP_CYCLES: + val->intval = max77775_fg_read_cycle(fuelgauge); + break; + case POWER_SUPPLY_EXT_PROP_FUELGAUGE_LOG: + max77775_fg_read_power_log(fuelgauge); + break; + case POWER_SUPPLY_EXT_PROP_MONITOR_WORK: + break; + case POWER_SUPPLY_EXT_PROP_ERROR_CAUSE: + val->intval = max77775_fg_check_initialization_result(fuelgauge); + break; + case POWER_SUPPLY_EXT_PROP_MEASURE_SYS: + switch (val->intval) { + case SEC_BATTERY_VSYS: + val->intval = max77775_fg_read_vsys(fuelgauge); + break; + case SEC_BATTERY_ISYS_AVG_UA: + val->intval = max77775_fg_read_isys_avg(fuelgauge, + SEC_BATTERY_CURRENT_UA); + break; + case SEC_BATTERY_ISYS_AVG_MA: + val->intval = max77775_fg_read_isys_avg(fuelgauge, + SEC_BATTERY_CURRENT_MA); + break; + case SEC_BATTERY_ISYS_UA: + val->intval = max77775_fg_read_isys(fuelgauge, + SEC_BATTERY_CURRENT_UA); + break; + case SEC_BATTERY_ISYS_MA: + default: + val->intval = max77775_fg_read_isys(fuelgauge, + SEC_BATTERY_CURRENT_MA); + break; + } + break; + case POWER_SUPPLY_EXT_PROP_MEASURE_INPUT: + switch (val->intval) { + case SEC_BATTERY_VBYP: + val->intval = max77775_fg_read_vbyp(fuelgauge); + break; + case SEC_BATTERY_IIN_UA: + val->intval = max77775_fg_read_iin(fuelgauge, + SEC_BATTERY_CURRENT_UA); + break; + case SEC_BATTERY_IIN_MA: + default: + val->intval = max77775_fg_read_iin(fuelgauge, + SEC_BATTERY_CURRENT_MA); + break; + } + break; + case POWER_SUPPLY_EXT_PROP_JIG_GPIO: + if (fuelgauge->pdata->jig_gpio) + val->intval = gpio_get_value(fuelgauge->pdata->jig_gpio); + else + val->intval = -1; + pr_info("%s: jig gpio = %d \n", __func__, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_FILTER_CFG: + max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_FILTERCFG, 2, data); + val->intval = data[1] << 8 | data[0]; + pr_debug("%s: FilterCFG=0x%04X\n", __func__, data[1] << 8 | data[0]); + break; + case POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED: + val->intval = fuelgauge->is_charging; + break; + case POWER_SUPPLY_EXT_PROP_BATTERY_ID: + if (!val->intval) { + if (fuelgauge->pdata->bat_gpio_cnt > 0) + max77775_reset_bat_id(fuelgauge); +#if defined(CONFIG_ID_USING_BAT_SUBBAT) + val->intval = fuelgauge->battery_data->main_battery_id; +#else + val->intval = fuelgauge->battery_data->battery_id; +#endif + pr_info("%s: bat_id_gpio = %d \n", __func__, val->intval); + } +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + else if (val->intval == SEC_DUAL_BATTERY_SUB) { + if (fuelgauge->pdata->sub_bat_gpio_cnt > 0) + max77775_reset_bat_id(fuelgauge); + val->intval = fuelgauge->battery_data->sub_battery_id; + pr_info("%s: sub_bat_id_gpio = %d \n", __func__, val->intval); + } +#endif + break; + case POWER_SUPPLY_EXT_PROP_DESIGNCAP: + val->intval = max77775_read_word(fuelgauge->i2c, MAX77775_FG_REG_DESIGNCAP); + break; + case POWER_SUPPLY_EXT_PROP_BATT_DUMP: + max77775_fg_bd_log(fuelgauge); + val->strval = fuelgauge->d_buf; + break; + default: + return -EINVAL; + } + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = fuelgauge->cable_type; + pr_info("%s: cable_type = %d \n", __func__, val->intval); + break; + default: + return -EINVAL; + } + return 0; +} + +#if defined(CONFIG_UPDATE_BATTERY_DATA) +static int max77775_fuelgauge_parse_dt(struct max77775_fuelgauge_data *fuelgauge); +#endif +static int max77775_fg_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max77775_fuelgauge_data *fuelgauge = power_supply_get_drvdata(psy); + enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; + static bool low_temp_wa; + u8 data[2] = { 0, 0 }; + u16 reg_data; + + if (atomic_read(&fuelgauge->shutdown_cnt) > 0) { + dev_info(fuelgauge->dev, "%s: fuelgauge already shutdown\n", __func__); + return -EINVAL; + } + + switch ((int)psp) { + case POWER_SUPPLY_PROP_STATUS: + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + if (fuelgauge->pdata->capacity_calculation_type & + SEC_FUELGAUGE_CAPACITY_TYPE_DYNAMIC_SCALE) + max77775_fg_calculate_dynamic_scale(fuelgauge, val->intval, true); + break; +#if defined(CONFIG_EN_OOPS) + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + max77775_set_full_value(fuelgauge, val->intval); + break; +#endif + case POWER_SUPPLY_PROP_ONLINE: + fuelgauge->cable_type = val->intval; + if (!is_nocharge_type(fuelgauge->cable_type)) { + /* enable alert */ + if (fuelgauge->vempty_mode >= VEMPTY_MODE_SW_VALERT) { + max77775_fg_set_vempty(fuelgauge, VEMPTY_MODE_HW); + fuelgauge->initial_update_of_soc = true; + max77775_fg_fuelalert_init(fuelgauge, + fuelgauge->pdata->fuel_alert_soc); + } + } + break; + /* Battery Temperature */ + case POWER_SUPPLY_PROP_CAPACITY: + if (val->intval == SEC_FUELGAUGE_CAPACITY_TYPE_RESET) { + if (!max77775_fg_reset(fuelgauge)) + return -EINVAL; + fuelgauge->initial_update_of_soc = true; + if (fuelgauge->pdata->capacity_calculation_type & + SEC_FUELGAUGE_CAPACITY_TYPE_LOST_SOC) + max77775_lost_soc_reset(fuelgauge); + } + break; + case POWER_SUPPLY_PROP_TEMP: + if (val->intval < 0) { + reg_data = max77775_read_word(fuelgauge->i2c, MAX77775_FG_REG_DESIGNCAP); + if (reg_data == fuelgauge->battery_data->Capacity) { + max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_DESIGNCAP, + fuelgauge->battery_data->Capacity + 3); + pr_info("%s: set the low temp reset! temp : %d, capacity : 0x%x, original capacity : 0x%x\n", + __func__, val->intval, reg_data, + fuelgauge->battery_data->Capacity); + } + } + + if (val->intval < 0 && !low_temp_wa) { + low_temp_wa = true; + max77775_write_word(fuelgauge->i2c, 0x29, 0xCEA7); + pr_info("%s: FilterCFG(0x%0x)\n", __func__, + max77775_read_word(fuelgauge->i2c, 0x29)); + } else if (val->intval > 30 && low_temp_wa) { + low_temp_wa = false; + max77775_write_word(fuelgauge->i2c, 0x29, 0xCEA4); + pr_info("%s: FilterCFG(0x%0x)\n", __func__, + max77775_read_word(fuelgauge->i2c, 0x29)); + } + max77775_fg_write_temp(fuelgauge, val->intval); + break; + case POWER_SUPPLY_PROP_TEMP_AMBIENT: + break; + case POWER_SUPPLY_PROP_ENERGY_NOW: + pr_info("%s: POWER_SUPPLY_PROP_ENERGY_NOW\n", __func__); + max77775_fg_reset_capacity_by_jig_connection(fuelgauge); + break; + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + mutex_lock(&fuelgauge->fg_lock); + pr_info("%s: capacity_max changed, %d -> %d\n", + __func__, fuelgauge->capacity_max, val->intval); + fuelgauge->capacity_max = + max77775_fg_check_capacity_max(fuelgauge, val->intval); + fuelgauge->initial_update_of_soc = true; +#if defined(CONFIG_UI_SOC_PROLONGING) + fuelgauge->g_capacity_max = 990; + fuelgauge->capacity_max_conv = true; +#endif + mutex_unlock(&fuelgauge->fg_lock); + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + { + u16 reg_fullsocthr; + int val_soc = val->intval; + + if (val->intval > fuelgauge->pdata->full_condition_soc + || val->intval <= (fuelgauge->pdata->full_condition_soc - 10)) { + pr_info("%s: abnormal value(%d). so thr is changed to default(%d)\n", + __func__, val->intval, fuelgauge->pdata->full_condition_soc); + val_soc = fuelgauge->pdata->full_condition_soc; + } + + reg_fullsocthr = val_soc << 8; + if (max77775_write_word(fuelgauge->i2c, MAX77775_FG_REG_FULLSOCTHR, + reg_fullsocthr) < 0) { + pr_info("%s: Failed to write MAX77775_FG_REG_FULLSOCTHR\n", __func__); + } else { + reg_fullsocthr = + max77775_read_word(fuelgauge->i2c, MAX77775_FG_REG_FULLSOCTHR); + pr_info("%s: FullSOCThr %d%%(0x%04X)\n", + __func__, val_soc, reg_fullsocthr); + } + } + break; + case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: + switch (ext_psp) { + case POWER_SUPPLY_EXT_PROP_FILTER_CFG: + /* Set FilterCFG */ + max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_FILTERCFG, 2, data); + pr_debug("%s: FilterCFG=0x%04X\n", __func__, data[1] << 8 | data[0]); + data[0] &= ~0xF; + data[0] |= (val->intval & 0xF); + max77775_bulk_write(fuelgauge->i2c, MAX77775_FG_REG_FILTERCFG, 2, data); + + max77775_bulk_read(fuelgauge->i2c, MAX77775_FG_REG_FILTERCFG, 2, data); + pr_debug("%s: FilterCFG=0x%04X\n", __func__, data[1] << 8 | data[0]); + break; + case POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED: + switch (val->intval) { + case SEC_BAT_CHG_MODE_BUCK_OFF: + case SEC_BAT_CHG_MODE_CHARGING_OFF: + fuelgauge->is_charging = false; + max77775_offset_leakage(fuelgauge); + break; + case SEC_BAT_CHG_MODE_CHARGING: + case SEC_BAT_CHG_MODE_PASS_THROUGH: + fuelgauge->is_charging = true; + max77775_offset_leakage(fuelgauge); + break; + }; + break; +#if defined(CONFIG_UPDATE_BATTERY_DATA) + case POWER_SUPPLY_EXT_PROP_POWER_DESIGN: + max77775_fuelgauge_parse_dt(fuelgauge); + break; +#endif + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + return 0; +} + +static void max77775_fg_isr_work(struct work_struct *work) +{ + struct max77775_fuelgauge_data *fuelgauge = + container_of(work, struct max77775_fuelgauge_data, isr_work.work); + + /* process for fuel gauge chip */ + max77775_fg_fuelalert_process(fuelgauge); + + __pm_relax(fuelgauge->fuel_alert_ws); +} + +static irqreturn_t max77775_fg_irq_thread(int irq, void *irq_data) +{ + struct max77775_fuelgauge_data *fuelgauge = irq_data; + + pr_info("%s\n", __func__); + + max77775_update_reg(fuelgauge->pmic, MAX77775_PMIC_REG_INTSRC_MASK, + MAX77775_IRQSRC_FG, MAX77775_IRQSRC_FG); + if (fuelgauge->is_fuel_alerted) + return IRQ_HANDLED; + + __pm_stay_awake(fuelgauge->fuel_alert_ws); + fuelgauge->is_fuel_alerted = true; + schedule_delayed_work(&fuelgauge->isr_work, 0); + + return IRQ_HANDLED; +} + +#ifdef CONFIG_OF +#define PROPERTY_NAME_SIZE 128 +static void max77775_fuelgauge_parse_dt_lost_soc( + struct max77775_fuelgauge_data *fuelgauge, struct device_node *np) +{ + int ret; + + ret = of_property_read_u32(np, "fuelgauge,lost_soc_trig_soc", + &fuelgauge->lost_soc.trig_soc); + if (ret < 0) + fuelgauge->lost_soc.trig_soc = 1000; /* 100% */ + + ret = of_property_read_u32(np, "fuelgauge,lost_soc_trig_d_soc", + &fuelgauge->lost_soc.trig_d_soc); + if (ret < 0) + fuelgauge->lost_soc.trig_d_soc = 20; /* 2% */ + + ret = of_property_read_u32(np, "fuelgauge,lost_soc_trig_scale", + &fuelgauge->lost_soc.trig_scale); + if (ret < 0) + fuelgauge->lost_soc.trig_scale = 2; /* 2x */ + + ret = of_property_read_u32(np, "fuelgauge,lost_soc_guarantee_soc", + &fuelgauge->lost_soc.guarantee_soc); + if (ret < 0) + fuelgauge->lost_soc.guarantee_soc = 20; /* 2% */ + + ret = of_property_read_u32(np, "fuelgauge,lost_soc_min_vol", + &fuelgauge->lost_soc.min_vol); + if (ret < 0) + fuelgauge->lost_soc.min_vol = 3200; /* 3.2V */ + + ret = of_property_read_u32(np, "fuelgauge,lost_soc_min_weight", + &fuelgauge->lost_soc.min_weight); + if (ret < 0 || fuelgauge->lost_soc.min_weight <= 10) + fuelgauge->lost_soc.min_weight = 20; /* 2.0 */ + + pr_info("%s: trigger soc(%d), d_soc(%d), scale(%d), guarantee_soc(%d), min_vol(%d), min_weight(%d)\n", + __func__, fuelgauge->lost_soc.trig_soc, fuelgauge->lost_soc.trig_d_soc, + fuelgauge->lost_soc.trig_scale, fuelgauge->lost_soc.guarantee_soc, + fuelgauge->lost_soc.min_vol, fuelgauge->lost_soc.min_weight); +} + +__visible_for_testing int max77775_get_bat_id(int bat_id[], int bat_gpio_cnt) +{ + int battery_id = 0; + int i = 0; + + for (i = (bat_gpio_cnt - 1); i >= 0; i--) + battery_id += bat_id[i] << i; + + return battery_id; +} + +static void max77775_reset_bat_id(struct max77775_fuelgauge_data *fuelgauge) +{ + int bat_id[BAT_GPIO_NO] = {0, }; + int i = 0; + + for (i = 0; i < fuelgauge->pdata->bat_gpio_cnt; i++) + bat_id[i] = gpio_get_value(fuelgauge->pdata->bat_id_gpio[i]); + + fuelgauge->battery_data->battery_id = + max77775_get_bat_id(bat_id, fuelgauge->pdata->bat_gpio_cnt); + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + for (i = 0; i < fuelgauge->pdata->sub_bat_gpio_cnt; i++) + bat_id[i] = gpio_get_value(fuelgauge->pdata->sub_bat_id_gpio[i]); + + fuelgauge->battery_data->sub_battery_id = + max77775_get_bat_id(bat_id, fuelgauge->pdata->sub_bat_gpio_cnt); +#if defined(CONFIG_ID_USING_BAT_SUBBAT) + fuelgauge->battery_data->main_battery_id = fuelgauge->battery_data->battery_id; + fuelgauge->battery_data->battery_id = + (fuelgauge->battery_data->battery_id << fuelgauge->pdata->sub_bat_gpio_cnt ) | fuelgauge->battery_data->sub_battery_id; +#endif +#endif +} + +static int max77775_fuelgauge_parse_dt(struct max77775_fuelgauge_data *fuelgauge) +{ + struct device_node *np = of_find_node_by_name(NULL, "max77775-fuelgauge"); + max77775_fuelgauge_platform_data_t *pdata = fuelgauge->pdata; + int ret, len; + u8 battery_id = 0; + int i = 0; + int bat_id[BAT_GPIO_NO] = {0, }; + const u32 *p; + + /* reset, irq gpio info */ + if (np == NULL) { + pr_err("%s: np NULL\n", __func__); + } else { + ret = of_property_read_u32(np, "fuelgauge,capacity_max", + &pdata->capacity_max); + if (ret < 0) + pr_err("%s: error reading capacity_max %d\n", __func__, ret); + + ret = of_property_read_u32(np, "fuelgauge,capacity_max_margin", + &pdata->capacity_max_margin); + if (ret < 0) { + pr_err("%s: error reading capacity_max_margin %d\n", + __func__, ret); + pdata->capacity_max_margin = 300; + } + + ret = of_property_read_u32(np, "fuelgauge,capacity_min", + &pdata->capacity_min); + if (ret < 0) + pr_err("%s: error reading capacity_min %d\n", __func__, ret); + + ret = of_property_read_u32(np, "fuelgauge,capacity_calculation_type", + &pdata->capacity_calculation_type); + if (ret < 0) + pr_err("%s: error reading capacity_calculation_type %d\n", + __func__, ret); + + ret = of_property_read_u32(np, "fuelgauge,fuel_alert_soc", + &pdata->fuel_alert_soc); + if (ret < 0) + pr_err("%s: error reading pdata->fuel_alert_soc %d\n", + __func__, ret); + + pdata->repeated_fuelalert = of_property_read_bool(np, + "fuelgauge,repeated_fuelalert"); + + fuelgauge->using_temp_compensation = of_property_read_bool(np, + "fuelgauge,using_temp_compensation"); + if (fuelgauge->using_temp_compensation) { + ret = of_property_read_u32(np, "fuelgauge,low_temp_limit", + &fuelgauge->low_temp_limit); + if (ret < 0) { + pr_err("%s: error reading low temp limit %d\n", + __func__, ret); + fuelgauge->low_temp_limit = 0; /* Default: 0'C */ + } + + ret = of_property_read_u32(np, + "fuelgauge,vempty_recover_time", &fuelgauge->vempty_recover_time); + if (ret < 0) { + pr_err("%s: error reading low temp limit %d\n", + __func__, ret); + fuelgauge->vempty_recover_time = 0; /* Default: 0 */ + } + } + + fuelgauge->using_hw_vempty = of_property_read_bool(np, + "fuelgauge,using_hw_vempty"); + if (fuelgauge->using_hw_vempty) { + ret = of_property_read_u32(np, "fuelgauge,sw_v_empty_voltage", + &fuelgauge->battery_data->sw_v_empty_vol); + if (ret < 0) + pr_err("%s: error reading sw_v_empty_default_vol %d\n", + __func__, ret); + + ret = of_property_read_u32(np, "fuelgauge,sw_v_empty_voltage_cisd", + &fuelgauge->battery_data->sw_v_empty_vol_cisd); + if (ret < 0) { + pr_err("%s: error reading sw_v_empty_default_vol_cisd %d\n", + __func__, ret); + fuelgauge->battery_data->sw_v_empty_vol_cisd = 3100; + } + + ret = of_property_read_u32(np, "fuelgauge,sw_v_empty_recover_voltage", + &fuelgauge->battery_data->sw_v_empty_recover_vol); + if (ret < 0) + pr_err("%s: error reading sw_v_empty_recover_vol %d\n", + __func__, ret); + + pr_info("%s : SW V Empty (%d)mV, SW V Empty recover (%d)mV\n", + __func__, fuelgauge->battery_data->sw_v_empty_vol, + fuelgauge->battery_data->sw_v_empty_recover_vol); + } + + pdata->jig_gpio = of_get_named_gpio(np, "fuelgauge,jig_gpio", 0); + if (pdata->jig_gpio >= 0) { + pdata->jig_irq = gpio_to_irq(pdata->jig_gpio); + pdata->jig_low_active = of_property_read_bool(np, + "fuelgauge,jig_low_active"); + } else { + pr_err("%s: error reading jig_gpio = %d\n", + __func__, pdata->jig_gpio); + pdata->jig_gpio = 0; + } + + ret = of_property_read_u32(np, "fuelgauge,fg_resistor", + &fuelgauge->fg_resistor); + if (ret < 0) { + pr_err("%s: error reading fg_resistor %d\n", __func__, ret); + fuelgauge->fg_resistor = 1; + } +#if defined(CONFIG_EN_OOPS) + ret = of_property_read_u32(np, "fuelgauge,ichgterm", + &fuelgauge->battery_data->ichgterm); + if (ret < 0) + pr_err("%s: error reading ichgterm %d\n", __func__, ret); + + ret = of_property_read_u32(np, "fuelgauge,ichgterm_2nd", + &fuelgauge->battery_data->ichgterm_2nd); + if (ret < 0) + pr_err("%s: error reading ichgterm_2nd %d\n", __func__, ret); + + ret = of_property_read_u32(np, "fuelgauge,misccfg", + &fuelgauge->battery_data->misccfg); + if (ret < 0) + pr_err("%s: error reading misccfg %d\n", __func__, ret); + + ret = of_property_read_u32(np, "fuelgauge,misccfg_2nd", + &fuelgauge->battery_data->misccfg_2nd); + if (ret < 0) + pr_err("%s: error reading misccfg_2nd %d\n", __func__, ret); + + ret = of_property_read_u32(np, "fuelgauge,fullsocthr", + &fuelgauge->battery_data->fullsocthr); + if (ret < 0) + pr_err("%s: error reading fullsocthr %d\n", __func__, ret); + + ret = of_property_read_u32(np, "fuelgauge,fullsocthr_2nd", + &fuelgauge->battery_data->fullsocthr_2nd); + if (ret < 0) + pr_err("%s: error reading fullsocthr_2nd %d\n", __func__, ret); +#endif + + pdata->bat_gpio_cnt = of_gpio_named_count(np, "fuelgauge,bat_id_gpio"); + /* not run if gpio gpio cnt is less than 1 */ + if (pdata->bat_gpio_cnt > 0) { + pr_info("%s: Has %d bat_id_gpios\n", __func__, pdata->bat_gpio_cnt); + if (pdata->bat_gpio_cnt > BAT_GPIO_NO) { + pr_err("%s: bat_id_gpio, catch out-of bounds array read\n", + __func__); + pdata->bat_gpio_cnt = BAT_GPIO_NO; + } + for (i = 0; i < pdata->bat_gpio_cnt; i++) { + pdata->bat_id_gpio[i] = of_get_named_gpio(np, "fuelgauge,bat_id_gpio", i); + if (pdata->bat_id_gpio[i] >= 0) { + bat_id[i] = gpio_get_value(pdata->bat_id_gpio[i]); + } else { + pr_err("%s: error reading bat_id_gpio = %d\n", + __func__, pdata->bat_id_gpio[i]); + bat_id[i] = 0; + } + } + fuelgauge->battery_data->battery_id = + max77775_get_bat_id(bat_id, pdata->bat_gpio_cnt); + } else + fuelgauge->battery_data->battery_id = 0; + + battery_id = fuelgauge->battery_data->battery_id; + pr_info("%s: battery_id(batt_id:%d) = %d\n", __func__, fuelgauge->battery_data->battery_id, battery_id); + +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + pdata->sub_bat_gpio_cnt = of_gpio_named_count(np, "fuelgauge,sub_bat_id_gpio"); + /* not run if gpio gpio cnt is less than 1 */ + if (pdata->sub_bat_gpio_cnt > 0) { + pr_info("%s: Has %d sub_bat_id_gpios\n", __func__, pdata->sub_bat_gpio_cnt); + if (pdata->sub_bat_gpio_cnt > BAT_GPIO_NO) { + pr_err("%s: sub_bat_id_gpio, catch out-of bounds array read\n", + __func__); + pdata->sub_bat_gpio_cnt = BAT_GPIO_NO; + } + for (i = 0; i < pdata->sub_bat_gpio_cnt; i++) { + pdata->sub_bat_id_gpio[i] = of_get_named_gpio(np, "fuelgauge,sub_bat_id_gpio", i); + if (pdata->sub_bat_id_gpio[i] >= 0) { + bat_id[i] = gpio_get_value(pdata->sub_bat_id_gpio[i]); + } else { + pr_err("%s: error reading sub_bat_id_gpio = %d\n", + __func__, pdata->sub_bat_id_gpio[i]); + bat_id[i] = 0; + } + } + fuelgauge->battery_data->sub_battery_id = + max77775_get_bat_id(bat_id, pdata->sub_bat_gpio_cnt); + } else + fuelgauge->battery_data->sub_battery_id = 0; + + pr_info("%s: sub_battery_id = %d\n", __func__, fuelgauge->battery_data->sub_battery_id); +#if defined (CONFIG_ID_USING_BAT_SUBBAT) + fuelgauge->battery_data->main_battery_id = fuelgauge->battery_data->battery_id; + fuelgauge->battery_data->battery_id = + (fuelgauge->battery_data->battery_id << pdata->sub_bat_gpio_cnt ) | fuelgauge->battery_data->sub_battery_id; + battery_id = fuelgauge->battery_data->battery_id; + pr_info("%s: Effective battery_id(batt_id:%d) = %d\n", __func__, fuelgauge->battery_data->battery_id, battery_id); +#endif +#endif + if (fuelgauge->pdata->capacity_calculation_type & + SEC_FUELGAUGE_CAPACITY_TYPE_LOST_SOC) + max77775_fuelgauge_parse_dt_lost_soc(fuelgauge, np); + + /* get battery_params node */ + np = of_find_node_by_name(of_node_get(np), "battery_params"); + if (np == NULL) { + pr_err("%s: Cannot find child node \"battery_params\"\n", __func__); + return -EINVAL; + } else { + char prop_name[PROPERTY_NAME_SIZE]; + + snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", + battery_id, "v_empty"); + ret = of_property_read_u32(np, prop_name, + &fuelgauge->battery_data->V_empty); + if (ret < 0) { + pr_err("%s: error reading v_empty %d\n", __func__, ret); + pr_err("%s: battery data: %d does not exist\n", __func__, battery_id); + battery_id = 0; + snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", + battery_id, "v_empty"); + ret = of_property_read_u32(np, prop_name, + &fuelgauge->battery_data->V_empty); + if (ret < 0) + pr_err("%s: error reading v_empty of default battery data %d\n", + __func__, ret); + } + + snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", + battery_id, "v_empty_origin"); + ret = of_property_read_u32(np, prop_name, + &fuelgauge->battery_data->V_empty_origin); + if (ret < 0) + pr_err("%s: error reading v_empty_origin %d\n", __func__, ret); + + snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", + battery_id, "capacity"); + ret = of_property_read_u32(np, prop_name, + &fuelgauge->battery_data->Capacity); + if (ret < 0) + pr_err("%s: error reading capacity %d\n", __func__, ret); + + snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", + battery_id, "fg_reset_wa_data"); + len = of_property_count_u32_elems(np, prop_name); + if (len != FG_RESET_DATA_COUNT) { + pr_err("%s fg_reset_wa_data is %d < %d, need more data\n", + __func__, len, FG_RESET_DATA_COUNT); + fuelgauge->fg_reset_data = NULL; + } else { + fuelgauge->fg_reset_data = kzalloc(sizeof(struct fg_reset_wa), GFP_KERNEL); + ret = of_property_read_u32_array(np, prop_name, + (u32 *) fuelgauge->fg_reset_data, FG_RESET_DATA_COUNT); + if (ret < 0) { + pr_err("%s failed to read fuelgauge->fg_reset_wa_data: %d\n", + __func__, ret); + + kfree(fuelgauge->fg_reset_data); + fuelgauge->fg_reset_data = NULL; + } + } + pr_info("%s: V_empty(0x%04x), v_empty_origin(0x%04x), capacity(0x%04x)\n", + __func__, fuelgauge->battery_data->V_empty, + fuelgauge->battery_data->V_empty_origin, fuelgauge->battery_data->Capacity); + + snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", + battery_id, "data_ver"); + ret = of_property_read_u32(np, prop_name, + &fuelgauge->data_ver); + if (ret < 0) { + pr_err("%s: error reading data_ver %d\n", __func__, ret); + fuelgauge->data_ver = 0xff; + } + pr_info("%s: fg data_ver (%x)\n", __func__, fuelgauge->data_ver); + + snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", + battery_id, "selected_reg"); + + p = of_get_property(np, prop_name, &len); + if (p) { + fuelgauge->verify_selected_reg = kzalloc(len, GFP_KERNEL); + fuelgauge->verify_selected_reg_length = len / (int)sizeof(struct verify_reg); + pr_err("%s: len= %ld, length= %d, %d\n", __func__, + sizeof(int) * len, len, fuelgauge->verify_selected_reg_length); + ret = of_property_read_u32_array(np, prop_name, + (u32 *)fuelgauge->verify_selected_reg, len / sizeof(u32)); + if (ret) { + pr_err("%s: failed to read fuelgauge->verify_selected_reg: %d\n", + __func__, ret); + kfree(fuelgauge->verify_selected_reg); + fuelgauge->verify_selected_reg = NULL; + } else { + for (i = 0; i < fuelgauge->verify_selected_reg_length; i++) { + if (fuelgauge->verify_selected_reg[i].addr == MAX77775_FG_QRTABLE00) + fuelgauge->q_res_table[0] = fuelgauge->verify_selected_reg[i].data; + else if (fuelgauge->verify_selected_reg[i].addr == MAX77775_FG_QRTABLE10) + fuelgauge->q_res_table[1] = fuelgauge->verify_selected_reg[i].data; + else if (fuelgauge->verify_selected_reg[i].addr == MAX77775_FG_QRTABLE20) + fuelgauge->q_res_table[2] = fuelgauge->verify_selected_reg[i].data; + else if (fuelgauge->verify_selected_reg[i].addr == MAX77775_FG_QRTABLE30) + fuelgauge->q_res_table[3] = fuelgauge->verify_selected_reg[i].data; + } + } + } else { + pr_err("%s: there is not selected_reg\n", __func__); + fuelgauge->verify_selected_reg = NULL; + } + + snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", + battery_id, "coff_origin"); + ret = of_property_read_u32(np, prop_name, + &fuelgauge->battery_data->coff_origin); + if (ret < 0) + pr_err("%s: error reading coff_origin %d\n", __func__, ret); + + snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", + battery_id, "coff_charging"); + ret = of_property_read_u32(np, prop_name, + &fuelgauge->battery_data->coff_charging); + if (ret < 0) + pr_err("%s: error reading coff_charging %d\n", __func__, ret); + + snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", + battery_id, "cgain_origin"); + ret = of_property_read_u32(np, prop_name, + &fuelgauge->battery_data->cgain_origin); + if (ret < 0) + pr_err("%s: error reading cgain_origin %d\n", __func__, ret); + + snprintf(prop_name, PROPERTY_NAME_SIZE, "battery%d,%s", + battery_id, "cgain_charging"); + ret = of_property_read_u32(np, prop_name, + &fuelgauge->battery_data->cgain_charging); + if (ret < 0) + pr_err("%s: error reading cgain_charging %d\n", __func__, ret); + + pr_info("%s: V_empty(0x%04x), v_empty_origin(0x%04x), capacity(0x%04x)," + "coff_origin(0x%04x), coff_charging(0x%04x)," + "cgain_origin(0x%04x), cgain_charging(0x%04x)\n", + __func__, fuelgauge->battery_data->V_empty, fuelgauge->battery_data->V_empty_origin, fuelgauge->battery_data->Capacity, + fuelgauge->battery_data->coff_origin, fuelgauge->battery_data->coff_charging, + fuelgauge->battery_data->cgain_origin, fuelgauge->battery_data->cgain_charging); + } + + np = of_find_node_by_name(NULL, "battery"); + ret = of_property_read_u32(np, "battery,thermal_source", + &pdata->thermal_source); + if (ret < 0) + pr_err("%s: error reading pdata->thermal_source %d\n", + __func__, ret); + + ret = of_property_read_u32(np, "battery,full_condition_soc", + &pdata->full_condition_soc); + if (ret) { + pdata->full_condition_soc = 93; + pr_info("%s: Full condition soc is Empty\n", __func__); + } + pr_info("%s: thermal: %d, jig_gpio: %d, capacity_max: %d," + "capacity_max_margin: %d, capacity_min: %d," + "calculation_type: 0x%x, fuel_alert_soc: %d," + "repeated_fuelalert: %d, fg_resistor : %d\n", + __func__, pdata->thermal_source, pdata->jig_gpio, pdata->capacity_max, + pdata->capacity_max_margin, pdata->capacity_min, + pdata->capacity_calculation_type, pdata->fuel_alert_soc, + pdata->repeated_fuelalert, fuelgauge->fg_resistor); + } + pr_info("%s: (%d)\n", __func__, fuelgauge->battery_data->Capacity); + + return 0; +} +#endif + +static const struct power_supply_desc max77775_fuelgauge_power_supply_desc = { + .name = "max77775-fuelgauge", + .type = POWER_SUPPLY_TYPE_UNKNOWN, + .properties = max77775_fuelgauge_props, + .num_properties = ARRAY_SIZE(max77775_fuelgauge_props), + .get_property = max77775_fg_get_property, + .set_property = max77775_fg_set_property, + .no_thermal = true, +}; + +static int max77775_fuelgauge_probe(struct platform_device *pdev) +{ + struct max77775_dev *max77775 = dev_get_drvdata(pdev->dev.parent); + struct max77775_platform_data *pdata = dev_get_platdata(max77775->dev); + max77775_fuelgauge_platform_data_t *fuelgauge_data; + struct max77775_fuelgauge_data *fuelgauge; + struct power_supply_config fuelgauge_cfg = { }; + union power_supply_propval raw_soc_val; + int ret = 0; + + pr_info("%s: max77775 Fuelgauge Driver Loading\n", __func__); + + fuelgauge = kzalloc(sizeof(*fuelgauge), GFP_KERNEL); + if (!fuelgauge) + return -ENOMEM; + + fuelgauge_data = kzalloc(sizeof(max77775_fuelgauge_platform_data_t), GFP_KERNEL); + if (!fuelgauge_data) { + ret = -ENOMEM; + goto err_free; + } + + mutex_init(&fuelgauge->fg_lock); + + fuelgauge->dev = &pdev->dev; + fuelgauge->pdata = fuelgauge_data; + fuelgauge->i2c = max77775->fuelgauge; + fuelgauge->pmic = max77775->i2c; + fuelgauge->max77775_pdata = pdata; +#if defined(CONFIG_OF) + fuelgauge->battery_data = kzalloc(sizeof(struct battery_data_t), GFP_KERNEL); + if (!fuelgauge->battery_data) { + pr_err("Failed to allocate memory\n"); + ret = -ENOMEM; + goto err_pdata_free; + } + ret = max77775_fuelgauge_parse_dt(fuelgauge); + if (ret < 0) { + pr_err("%s not found fg dt! ret[%d]\n", __func__, ret); + goto err_data_free; + } +#endif + + fuelgauge->capacity_max = fuelgauge->pdata->capacity_max; +#if !defined(CONFIG_UI_SOC_PROLONGING) + fuelgauge->g_capacity_max = 0; + fuelgauge->capacity_max_conv = false; +#endif + max77775_lost_soc_reset(fuelgauge); + + raw_soc_val.intval = max77775_get_fuelgauge_value(fuelgauge, FG_RAW_SOC) / 10; + + if (raw_soc_val.intval > fuelgauge->capacity_max) + max77775_fg_calculate_dynamic_scale(fuelgauge, 100, false); + + if (!max77775_fg_init(fuelgauge)) { + pr_err("%s: Failed to Initialize Fuelgauge\n", __func__); + ret = -ENODEV; + goto err_data_free; + } + +#if !IS_ENABLED(CONFIG_DUAL_FUELGAUGE) + /* SW/HW init code. SW/HW V Empty mode must be opposite ! */ + fuelgauge->vempty_init_flag = false; /* default value */ + pr_info("%s: SW/HW V empty init\n", __func__); + max77775_fg_set_vempty(fuelgauge, VEMPTY_MODE_SW); +#endif + fuelgauge_cfg.drv_data = fuelgauge; + + fuelgauge->psy_fg = power_supply_register(&pdev->dev, + &max77775_fuelgauge_power_supply_desc, + &fuelgauge_cfg); + if (IS_ERR(fuelgauge->psy_fg)) { + ret = PTR_ERR(fuelgauge->psy_fg); + pr_err("%s: Failed to Register psy_fg(%d)\n", __func__, ret); + goto err_data_free; + } + + fuelgauge->fg_irq = pdata->irq_base + MAX77775_FG_IRQ_ALERT; + pr_info("[%s]IRQ_BASE(%d) FG_IRQ(%d)\n", + __func__, pdata->irq_base, fuelgauge->fg_irq); + + fuelgauge->is_fuel_alerted = false; + if (fuelgauge->pdata->fuel_alert_soc >= 0) { + if (max77775_fg_fuelalert_init(fuelgauge, + fuelgauge->pdata->fuel_alert_soc)) { + fuelgauge->fuel_alert_ws = wakeup_source_register(&pdev->dev, "fuel_alerted"); + if (fuelgauge->fg_irq) { + INIT_DELAYED_WORK(&fuelgauge->isr_work, + max77775_fg_isr_work); + + ret = request_threaded_irq(fuelgauge->fg_irq, NULL, + max77775_fg_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "fuelgauge-irq", fuelgauge); + if (ret) { + pr_err("%s: Failed to Request IRQ (fg_irq)\n", __func__); + goto err_supply_unreg; + } + } + } else { + pr_err("%s: Failed to Initialize Fuel-alert\n", __func__); + goto err_supply_unreg; + } + } + + if (fuelgauge->pdata->jig_gpio) { + ret = request_threaded_irq(fuelgauge->pdata->jig_irq, NULL, + max77775_jig_irq_thread, + (fuelgauge->pdata->jig_low_active ? + IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING) + | IRQF_ONESHOT, + "jig-irq", fuelgauge); + if (ret) { + pr_info("%s: Failed to Request IRQ (jig_gpio)\n", __func__); + //goto err_supply_unreg; + } + + /* initial check for the JIG */ + if (max77775_check_jig_status(fuelgauge)) + max77775_fg_reset_capacity_by_jig_connection(fuelgauge); + } + + ret = max77775_fg_create_attrs(&fuelgauge->psy_fg->dev); + if (ret) { + dev_err(fuelgauge->dev,"%s : Failed to create_attrs\n", __func__); + goto err_irq; + } + + fuelgauge->err_cnt = 0; + fuelgauge->sleep_initial_update_of_soc = false; + fuelgauge->initial_update_of_soc = true; + fuelgauge->valert_count_flag = false; + atomic_set(&fuelgauge->shutdown_cnt, 0); + platform_set_drvdata(pdev, fuelgauge); + + sec_chg_set_dev_init(SC_DEV_FG); + + pr_info("%s: max77775 Fuelgauge Driver Loaded\n", __func__); + return 0; + +err_irq: + free_irq(fuelgauge->fg_irq, fuelgauge); + free_irq(fuelgauge->pdata->jig_irq, fuelgauge); +err_supply_unreg: + power_supply_unregister(fuelgauge->psy_fg); + wakeup_source_unregister(fuelgauge->fuel_alert_ws); +err_data_free: +#if defined(CONFIG_OF) + kfree(fuelgauge->battery_data); +#endif +err_pdata_free: + kfree(fuelgauge_data); + mutex_destroy(&fuelgauge->fg_lock); +err_free: + kfree(fuelgauge); + + return ret; +} + +static int max77775_fuelgauge_remove(struct platform_device *pdev) +{ + struct max77775_fuelgauge_data *fuelgauge = platform_get_drvdata(pdev); + + pr_info("%s: ++\n", __func__); + + if (fuelgauge) { + if (fuelgauge->psy_fg) + power_supply_unregister(fuelgauge->psy_fg); + + free_irq(fuelgauge->fg_irq, fuelgauge); + free_irq(fuelgauge->pdata->jig_irq, fuelgauge); + wakeup_source_unregister(fuelgauge->fuel_alert_ws); +#if defined(CONFIG_OF) + kfree(fuelgauge->battery_data); +#endif + kfree(fuelgauge->pdata); + mutex_destroy(&fuelgauge->fg_lock); + kfree(fuelgauge); + } + + pr_info("%s: --\n", __func__); + + return 0; +} + +static int max77775_fuelgauge_suspend(struct device *dev) +{ + struct max77775_fuelgauge_data *fuelgauge = dev_get_drvdata(dev); + + pr_debug("%s: ++\n", __func__); + + if (fuelgauge->pdata->jig_irq) { + disable_irq(fuelgauge->pdata->jig_irq); + enable_irq_wake(fuelgauge->pdata->jig_irq); + } + + return 0; +} + +static int max77775_fuelgauge_resume(struct device *dev) +{ + struct max77775_fuelgauge_data *fuelgauge = dev_get_drvdata(dev); + + fuelgauge->sleep_initial_update_of_soc = true; + + pr_debug("%s: ++\n", __func__); + + if (fuelgauge->pdata->jig_irq) { + disable_irq_wake(fuelgauge->pdata->jig_irq); + enable_irq(fuelgauge->pdata->jig_irq); + } + + return 0; +} + +static void max77775_fuelgauge_shutdown(struct platform_device *pdev) +{ + struct max77775_fuelgauge_data *fuelgauge = platform_get_drvdata(pdev); + + pr_info("%s: ++\n", __func__); + + if (fuelgauge) { + atomic_inc(&fuelgauge->shutdown_cnt); + + if (fuelgauge->i2c) { + max77775_offset_leakage_default(fuelgauge); + } + if (fuelgauge->fg_irq) + free_irq(fuelgauge->fg_irq, fuelgauge); + if (fuelgauge->pdata->jig_gpio) + free_irq(fuelgauge->pdata->jig_irq, fuelgauge); + + cancel_delayed_work(&fuelgauge->isr_work); + } + + pr_info("%s: --\n", __func__); +} + +static SIMPLE_DEV_PM_OPS(max77775_fuelgauge_pm_ops, max77775_fuelgauge_suspend, + max77775_fuelgauge_resume); + +static struct platform_driver max77775_fuelgauge_driver = { + .driver = { + .name = "max77775-fuelgauge", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &max77775_fuelgauge_pm_ops, +#endif + }, + .probe = max77775_fuelgauge_probe, + .remove = max77775_fuelgauge_remove, + .shutdown = max77775_fuelgauge_shutdown, +}; + +static int __init max77775_fuelgauge_init(void) +{ + pr_info("%s:\n", __func__); + return platform_driver_register(&max77775_fuelgauge_driver); +} + +static void __exit max77775_fuelgauge_exit(void) +{ + platform_driver_unregister(&max77775_fuelgauge_driver); +} + +module_init(max77775_fuelgauge_init); +module_exit(max77775_fuelgauge_exit); + +MODULE_DESCRIPTION("Samsung max77775 Fuel Gauge Driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/battery/fuelgauge/max77775_fuelgauge/max77775_fuelgauge.dtsi b/drivers/battery/fuelgauge/max77775_fuelgauge/max77775_fuelgauge.dtsi new file mode 100644 index 000000000000..cf9f4ab020e7 --- /dev/null +++ b/drivers/battery/fuelgauge/max77775_fuelgauge/max77775_fuelgauge.dtsi @@ -0,0 +1,117 @@ +&smd { + max77775_fuelgauge: max77775-fuelgauge { + /* for 4500mAh battery */ + status = "okay"; + fuelgauge,fuel_alert_soc = <1>; + fuelgauge,jig_gpio = ; /* IFC_SENSE_INT_AP */ + fuelgauge,jig_low_active; + fuelgauge,capacity_max = <1000>; + fuelgauge,capacity_max_margin = <300>; + fuelgauge,capacity_min = <0>; + fuelgauge,capacity_calculation_type = <28>; + fuelgauge,repeated_fuelalert; + fuelgauge,using_temp_compensation; + fuelgauge,low_temp_limit = <100>; + fuelgauge,vempty_recover_time = <180>; /* 3 mins */ + fuelgauge,using_hw_vempty; + fuelgauge,sw_v_empty_voltage = <3200>; + fuelgauge,sw_v_empty_voltage_cisd = <3100>; + fuelgauge,sw_v_empty_recover_voltage = <3480>; + fuelgauge,fg_resistor = <2>; +#if 1 + fuelgauge,bat_id_gpio = < +#if 0 + SEC_GPIO_REF(${bat_id_gpio_2}) 0 /* BAT_ID_GPIO 1 */ +#endif + SEC_GPIO_REF(AP,tlmm,122) 0 /* BAT_ID_GPIO 0 */ + >; +#endif +#if 0 + fuelgauge,sub_bat_id_gpio = < +#if 0 + SEC_GPIO_REF(${sub_bat_id_gpio_2}) 0 /* SUB_BAT_ID_GPIO 1 */ +#endif + SEC_GPIO_REF(${sub_bat_id_gpio}) 0 /* SUB_BAT_ID_GPIO 0 */ + >; +#endif + }; +}; + +/* /home/dpi/qb5_8814/workspace/P4_1716/android/kernel_platform/kmodule/battery/stable/eureka/fuelgauge/max77775/e3q/max77775_fuelgauge.dtsi */ +&max77775_fuelgauge { + fuelgauge,capacity_calculation_type = <0x5C>; + fuelgauge,lost_soc_trig_soc = <1000>; /* 100.0% */ + fuelgauge,lost_soc_trig_d_soc = <20>; /* 2% */ + fuelgauge,lost_soc_trig_scale = <2>; /* 2x */ + fuelgauge,lost_soc_guarantee_soc = <30>; /* 3.0% */ + fuelgauge,lost_soc_min_vol = <3200>; /* 3200mV */ + fuelgauge,fg_resistor = <5>; /* 2 milliohm */ + fuelgauge,scale_to_102; + + battery_params { + /* + * BAT_ID_1/BAT_ID_2 + * 01: ATL, 00: SDI + */ + + /* 00 : SDI battery data */ + battery0,v_empty = <0xA561>; /* Empty: 3400mV, Recover: 4000mV */ + battery0,v_empty_origin = <0x7D54>; /* Empty: 2500mV, Recover: 3360mV */ + battery0,capacity = <0x07AB>; + /* fullcapnom dPacc dQacc RCOMP0 TempCo */ + battery0,fg_reset_wa_data = <0x07AA 0x3200 0x01EA 0x0560 0x0F02>; + + battery0,data_ver = <0x1>; + + battery0,selected_reg = < + 0x02 0x7F80 /* default */ + 0x12 0x2A00 /* QResidual00 */ + 0x1E 0x0266 /* ICHGTerm */ + 0x21 0x6200 /* default */ + 0x22 0x1480 /* QResidual10 */ + 0x2A 0x023E /* RelaxCFG */ + 0x2C 0xE3E1 /* TGAIN */ + 0x2D 0x290E /* TOFF */ + 0x2E 0x0400 /* CGAIN */ + 0x2F 0x0001 /* COFF */ + 0x32 0x0A00 /* QResidual20 */ + 0x33 0xFFFF /* default */ + 0x37 0x05E0 /* default */ + 0x42 0x0900 /* QResidual30 */ + 0xB4 0x7F80 /* default */ + 0xB8 0x0000 /* default */ + 0xB9 0x006B /* default */ + 0xBA 0x090C /* default */ + >; + + /* 01 : ATL battery data */ + battery1,v_empty = <0xA561>; /* Empty: 3400mV, Recover: 4000mV */ + battery1,v_empty_origin = <0x7D54>; /* Empty: 2500mV, Recover: 3360mV */ + battery1,capacity = <0x07B7>; + /* fullcapnom dPacc dQacc RCOMP0 TempCo */ + battery1,fg_reset_wa_data = <0x07B7 0x3200 0x01ED 0x0560 0x0E02>; + + battery1,data_ver = <0x1>; + + battery1,selected_reg = < + 0x02 0x7F80 /* default */ + 0x12 0x2A00 /* QResidual00 */ + 0x1E 0x0266 /* ICHGTerm */ + 0x21 0x6200 /* default */ + 0x22 0x1400 /* QResidual10 */ + 0x2A 0x023E /* RelaxCFG */ + 0x2C 0xE3E1 /* TGAIN */ + 0x2D 0x290E /* TOFF */ + 0x2E 0x0400 /* CGAIN */ + 0x2F 0x0001 /* COFF */ + 0x32 0x0900 /* QResidual20 */ + 0x33 0xFFFF /* default */ + 0x37 0x05E0 /* default */ + 0x42 0x0680 /* QResidual30 */ + 0xB4 0x7F80 /* default */ + 0xB8 0x0000 /* default */ + 0xB9 0x006B /* default */ + 0xBA 0x090C /* default */ + >; + }; +}; diff --git a/drivers/battery/fuelgauge/max77775_fuelgauge/max77775_fuelgauge.h b/drivers/battery/fuelgauge/max77775_fuelgauge/max77775_fuelgauge.h new file mode 100644 index 000000000000..e80d326e1ada --- /dev/null +++ b/drivers/battery/fuelgauge/max77775_fuelgauge/max77775_fuelgauge.h @@ -0,0 +1,306 @@ +/* + * max77775_fuelgauge.h + * Samsung max77775 Fuel Gauge Header + * + * Copyright (C) 2015 Samsung Electronics, Inc. + * + * This software is 77854 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 __MAX77775_FUELGAUGE_H +#define __MAX77775_FUELGAUGE_H __FILE__ + +#include +#include +#include +#include +#include +#include "../../common/sec_charging_common.h" +#include + +/* Client address should be shifted to the right 1bit. + * R/W bit should NOT be included. + */ + +#define PRINT_COUNT 10 + +#define ALERT_EN 0x04 +#define CAPACITY_SCALE_DEFAULT_CURRENT 1000 +#define CAPACITY_SCALE_HV_CURRENT 600 + +#define FG_BATT_DUMP_SIZE 128 + +enum max77775_vempty_mode { + VEMPTY_MODE_HW = 0, + VEMPTY_MODE_SW, + VEMPTY_MODE_SW_VALERT, + VEMPTY_MODE_SW_RECOVERY, +}; + +enum { + FG_DATA, +}; + +ssize_t max77775_fg_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf); + +ssize_t max77775_fg_store_attrs(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +#define MAX77775_FG_ATTR(_name) \ +{ \ + .attr = {.name = #_name, .mode = 0660}, \ + .show = max77775_fg_show_attrs, \ + .store = max77775_fg_store_attrs, \ +} + +struct sec_fg_info { + /* test print count */ + int pr_cnt; + /* full charge comp */ + struct delayed_work full_comp_work; + + /* battery info */ + u32 soc; + + /* miscellaneous */ + unsigned long fullcap_check_interval; + int full_check_flag; + bool is_first_check; +}; + +enum { + FG_LEVEL = 0, + FG_TEMPERATURE, + FG_VOLTAGE, + FG_CURRENT, + FG_CURRENT_AVG, + FG_CHECK_STATUS, + FG_RAW_SOC, + FG_VF_SOC, + FG_AV_SOC, + FG_FULLCAP, + FG_FULLCAPNOM, + FG_FULLCAPREP, + FG_MIXCAP, + FG_AVCAP, + FG_REPCAP, + FG_CYCLE, + FG_QH, + FG_QH_VF_SOC, + FG_ISYS, + FG_ISYS_AVG, + FG_VSYS, + FG_IIN, + FG_VBYP, +}; + +enum { + POSITIVE = 0, + NEGATIVE, +}; + +enum { + RANGE = 0, + SLOPE, + OFFSET, + TABLE_MAX +}; + +#define CURRENT_RANGE_MAX_NUM 5 + +struct battery_data_t { + u8 battery_id; +#if IS_ENABLED(CONFIG_DUAL_BATTERY) +#if defined(CONFIG_ID_USING_BAT_SUBBAT) + u8 main_battery_id; +#endif + u8 sub_battery_id; +#endif + u32 V_empty; + u32 V_empty_origin; + u32 sw_v_empty_vol; + u32 sw_v_empty_vol_cisd; + u32 sw_v_empty_recover_vol; + u32 Capacity; + u8 *type_str; + u32 ichgterm; + u32 misccfg; + u32 fullsocthr; + u32 ichgterm_2nd; + u32 misccfg_2nd; + u32 fullsocthr_2nd; + u32 coff_origin; + u32 coff_charging; + u32 cgain_origin; + u32 cgain_charging; +}; + +/* FullCap learning setting */ +#define VFFULLCAP_CHECK_INTERVAL 300 /* sec */ +/* soc should be 0.1% unit */ +#define VFSOC_FOR_FULLCAP_LEARNING 950 +#define LOW_CURRENT_FOR_FULLCAP_LEARNING 20 +#define HIGH_CURRENT_FOR_FULLCAP_LEARNING 120 +#define LOW_AVGCURRENT_FOR_FULLCAP_LEARNING 20 +#define HIGH_AVGCURRENT_FOR_FULLCAP_LEARNING 100 + +/* power off margin */ +/* soc should be 0.1% unit */ +#define POWER_OFF_SOC_HIGH_MARGIN 20 +#define POWER_OFF_VOLTAGE_HIGH_MARGIN 3500 +#define POWER_OFF_VOLTAGE_LOW_MARGIN 3400 + +#define LEARNING_QRTABLE 0x0001 + +/* Need to be increased if there are more than 2 BAT ID GPIOs */ +#define BAT_GPIO_NO 2 + +typedef struct max77775_fuelgauge_platform_data { + int jig_irq; + int jig_gpio; + int jig_low_active; + + int bat_id_gpio[BAT_GPIO_NO]; + int bat_gpio_cnt; +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + int sub_bat_id_gpio[BAT_GPIO_NO]; + int sub_bat_gpio_cnt; +#endif + int thermal_source; + + /* fuel alert SOC (-1: not use) */ + int fuel_alert_soc; + int fuel_alert_vol; + /* fuel alert can be repeated */ + bool repeated_fuelalert; + int capacity_calculation_type; + /* soc should be soc x 10 (0.1% degree) + * only for scaling + */ + int capacity_max; + int capacity_max_margin; + int capacity_min; + unsigned int full_condition_soc; +} max77775_fuelgauge_platform_data_t; + +#define FG_RESET_DATA_COUNT 5 + +struct verify_reg { + u16 addr; + u32 data; +}; + +struct fg_reset_wa { + u32 fullcapnom; + u32 dPacc; + u32 dQacc; + u32 rcomp0; + u32 tempco; +}; + +struct lost_soc_data { + /* dt data */ + int trig_soc; /* default 10% */ + int trig_d_soc; /* delta soc, default 2% */ + int trig_scale; /* default 2x */ + int guarantee_soc; /* default 2% */ + int min_vol; /* default 3200mV */ + int min_weight; /* default 2.0 */ + + /* data */ + bool ing; + int prev_raw_soc; + int prev_remcap; + int prev_qh; + int lost_cap; + int weight; +}; + +struct max77775_fuelgauge_data { + struct device *dev; + struct i2c_client *i2c; + struct i2c_client *pmic; + struct mutex fuelgauge_mutex; + struct max77775_platform_data *max77775_pdata; + max77775_fuelgauge_platform_data_t *pdata; + struct power_supply *psy_fg; + struct delayed_work isr_work; + + atomic_t shutdown_cnt; + + int cable_type; + bool is_charging; + + /* HW-dedicated fuel gauge info structure + * used in individual fuel gauge file only + * (ex. dummy_fuelgauge.c) + */ + struct sec_fg_info info; + struct battery_data_t *battery_data; + + bool is_fuel_alerted; + struct wakeup_source *fuel_alert_ws; + + unsigned int capacity_old; /* only for atomic calculation */ + unsigned int capacity_max; /* only for dynamic calculation */ + unsigned int g_capacity_max; /* only for dynamic calculation */ + unsigned int standard_capacity; + + bool capacity_max_conv; + bool initial_update_of_soc; + bool sleep_initial_update_of_soc; + struct mutex fg_lock; + + /* register programming */ + int reg_addr; + u8 reg_data[2]; + + int fg_irq; + + int raw_capacity; + int current_now; + int current_avg; + +#if defined(CONFIG_UI_SOC_PROLONGING) + int prev_raw_soc; +#endif + + bool using_temp_compensation; + bool using_hw_vempty; + unsigned int vempty_mode; + int temperature; + bool vempty_init_flag; + + int low_temp_limit; + + int vempty_recover_time; + unsigned long vempty_time; + + u32 fg_resistor; + + struct fg_reset_wa *fg_reset_data; + struct verify_reg *verify_selected_reg; + unsigned int verify_selected_reg_length; + u32 data_ver; + bool skip_fg_verify; + u32 err_cnt; + u32 q_res_table[4]; /* QResidual Table */ + + bool valert_count_flag; + struct lost_soc_data lost_soc; + char d_buf[128]; + int bd_vfocv; + int bd_raw_soc; +}; + +#endif /* __MAX77775_FUELGAUGE_H */ diff --git a/drivers/battery/wireless/Kconfig b/drivers/battery/wireless/Kconfig new file mode 100644 index 000000000000..0fdeaf6280b3 --- /dev/null +++ b/drivers/battery/wireless/Kconfig @@ -0,0 +1,118 @@ +menu "Wireless Charger drivers" + +config WIRELESS_CHARGING + tristate "support for wireless charging" + default n + help + Say Y to include support for wireless charging. + Wireless charging models should enable this charging option. + it include various scenario for wireless charging models only. + +config WIRELESS_CHARGER_NU1668 + tristate "MFC IC NU1668 charger support" + select WIRELESS_CHARGING + depends on BATTERY_SAMSUNG && I2C + help + Say Y here, to enable + support for the NU1668 MFC IC + NU1668 MFC include wireless charger driver. + It is for NuVoltatechnology. + +config WIRELESS_NO_HV + bool "wireless no hv" + depends on BATTERY_SAMSUNG && I2C + help + Say Y here to enable + support for disable HV wireless charging. + If you are unsure, say N. + some models need this for not support high voltage wireless charging. + +config WIRELESS_AUTH + bool "support for samsung wireless authentication massage" + default n + depends on WIRELESS_CHARGING + help + Say Y here to enable + support for wireless authentication message. + It is for samsung wireless authentication. + wireless charging flagship models should enable this option. + +config WIRELESS_CHARGER_HIGH_VOLTAGE + bool "high voltage wireless charger" + default n + depends on WIRELESS_CHARGING + help + Say Y here to enable + support for wireless fast charging. + It is for fast wireless charging. + wireless charging flagship models should enable this option. + +config WIRELESS_TX_MODE + bool "wireless power sharing support in sec battery driver" + default n + depends on WIRELESS_CHARGING + help + Say Y here to enable + support for wireless tx mode. + It is for wireless power sharing. + wireless tx support models should enable this option. + +config WIRELESS_FIRMWARE_UPDATE + bool "WIRELESS IC firmware update support" + default n + depends on WIRELESS_CHARGING + help + Say Y here to enable + support for the wireless charger IC firmware update. + it need firmware bin in firmware folder. + It is for MFC IC. + +config WIRELESS_IC_PARAM + bool "WIRELESS IC info param support" + depends on WIRELESS_CHARGING + help + Say Y here to enable + support for the WIRELESS IC info param. + It is for wireless ic dualization. + such as use of IDT and LSI. + +config TX_GEAR_PHM_VOUT_CTRL + bool "control vout when gear`s phm" + default n + depends on WIRELESS_TX_MODE + help + Say Y to enable CONFIG_TX_GEAR_PHM_VOUT_CTRL + Control vout 5v when enter gear`s phm with high voltage wired charging + Recover vout when escape gear`s phm + This is considered only if high vout with gear. + +config WIRELESS_RX_PHM_CTRL + bool "control phm rx itself" + default n + depends on WIRELESS_CHARGING + help + Say Y to enable CONFIG_WIRELESS_RX_PHM_CTRL + Control rx phm itself during wireless charging. + +config TX_GEAR_AOV + bool "control adaptive vout when tx gear" + default n + depends on WIRELESS_TX_MODE + help + Say Y to enable CONFIG_TX_GEAR_AOV + AOV : Adaptive Operation Voltage + When tx gear, control vout adaptively. + This is for tx gear with thick cover. + +config ENABLE_WIRELESS_IRQ_IN_SLEEP + bool "support for enable irq in sleep" + default n + depends on WIRELESS_CHARGING + help + Say Y here to enable + support for enable irq in sleep + In Qualcomm AP, wireless charging does not work, + when irq is disabled in sleep . + +endmenu + diff --git a/drivers/battery/wireless/Makefile b/drivers/battery/wireless/Makefile new file mode 100644 index 000000000000..8fd441e6df96 --- /dev/null +++ b/drivers/battery/wireless/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_WIRELESS_CHARGER_NU1668) += nu1668-charger.o + +nu1668-charger-$(CONFIG_WIRELESS_CHARGER_NU1668) += nu1668_fod.o nu1668_cmfet.o nu1668_charger.o + +ccflags-y := -Wformat diff --git a/drivers/battery/wireless/nu1668_charger.bzl b/drivers/battery/wireless/nu1668_charger.bzl new file mode 100644 index 000000000000..2c293192f7c6 --- /dev/null +++ b/drivers/battery/wireless/nu1668_charger.bzl @@ -0,0 +1,7 @@ +ko_list = [ + { + "ko_names" : [ + "drivers/battery/wireless/nu1668-charger.ko" + ] + } +] diff --git a/drivers/battery/wireless/nu1668_charger.c b/drivers/battery/wireless/nu1668_charger.c new file mode 100755 index 000000000000..648a53769d56 --- /dev/null +++ b/drivers/battery/wireless/nu1668_charger.c @@ -0,0 +1,7905 @@ +/* + * nu1668_charger.c + * Samsung NU1668 IC Charger Driver + * + * Copyright (C) 2022 Samsung Electronics + * + * 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 "nu1668_charger.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_SPU_VERIFY) +#include +#endif + +#define ENABLE 1 +#define DISABLE 0 +#define CMD_CNT 3 +#define ISR_CNT 10 +#define MAX_I2C_ERROR_COUNT 30 +#define MAX_MTP_PGM_CNT 3 + +#define MAX_BUF 4095 +#define SENDSZ 16 + +#define VALID_VRECT_LEVEL 2700 + +static char * __read_mostly carrierid; +module_param(carrierid, charp, 0444); + +#if defined(CONFIG_WIRELESS_IC_PARAM) +static unsigned int __read_mostly wireless_ic; +module_param(wireless_ic, uint, 0444); +#endif +static bool is_shutdn; + +#define ADT_DATA_BUFFER_SIZE 84 +static u8 temp_buffer_rdata[100] = {0, }; +static u8 ADT_buffer_rdata[MAX_BUF] = {0, }; +static int adt_readSize,total_adt_dataSize; +static u8 auth_read_data_step = 0; + +typedef struct _sgf_data { + unsigned int size; + unsigned int type; + char *data; +} sgf_data; + +//set vout voltage +// Vout = value * 0.1 (V) + 3.5 (V). Range : 3.5V ~ 20V +static const u16 mfc_nu1668_vout_val16[] = { + 0x000A, /* MFC_VOUT_4_5V */ + 0x000C, /* MFC_VOUT_4_7V */ + 0x000D, /* MFC_VOUT_4_8V */ + 0x000E, /* MFC_VOUT_4_9V */ + 0x000F, /* MFC_VOUT_5V */ + 0x0014, /* MFC_VOUT_5_5V */ + 0x0019, /* MFC_VOUT_6V */ + 0x0023, /* MFC_VOUT_7V */ + 0x002D, /* MFC_VOUT_8V */ + 0x0037, /* MFC_VOUT_9V */ + 0x0041, /* MFC_VOUT_10V */ + 0x004B, /* MFC_VOUT_11V */ + 0x0055, /* MFC_VOUT_12V */ + 0x005A, /* MFC_VOUT_12_5V */ + 0x000C, /* MFC_VOUT_OTG, 4.7V */ +}; + +//used for adb debug +static struct device_attribute mfc_attrs[] = { + NU1668_ATTR(mfc_addr), + NU1668_ATTR(mfc_size), + NU1668_ATTR(mfc_data), + NU1668_ATTR(mfc_packet), +}; + +static enum power_supply_property mfc_charger_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static irqreturn_t mfc_wpc_det_irq_thread(int irq, void *irq_data); +static irqreturn_t mfc_wpc_irq_thread(int irq, void *irq_data); +static int mfc_reg_multi_write_verify(struct i2c_client *client, u16 reg, const u8 *val, int size); +static void mfc_set_tx_fod_thresh1(struct i2c_client *client, u32 fod_thresh1); +static void mfc_set_tx_fod_ta_thresh(struct i2c_client *client, u32 fod_thresh); +static void mfc_deactivate_work_content(struct mfc_charger_data *charger); +static void mfc_mpp_epp_nego_done(struct mfc_charger_data *charger); + +#if defined(CONFIG_WIRELESS_IC_PARAM) +static unsigned int mfc_get_wrlic(void) { return wireless_ic; } +#endif + +static bool carrierid_is(char *str) +{ + if (carrierid == NULL) + return false; + + pr_info("%s: %s\n", __func__, carrierid); + + return !strncmp(carrierid, str, 3); +} + +static void mfc_check_i2c_error(struct mfc_charger_data *charger, bool is_error) +{ + //charger->pdata->wpc_det seems is a gpio pin connect with AP, if it is indicate IIC is normal, can be access by AP? can't use INTA Pin to aviod impact INT function. + charger->i2c_error_count = + (charger->det_state && gpio_get_value(charger->pdata->wpc_det) && is_error) ? + (charger->i2c_error_count + 1) : 0; + + if (charger->i2c_error_count > MAX_I2C_ERROR_COUNT) { + charger->i2c_error_count = 0; + queue_delayed_work(charger->wqueue, &charger->wpc_i2c_error_work, 0); + } +} + +static int nu1668_get_op_mode(void *pdata) +{ + struct mfc_charger_data *charger = pdata; + + if (!charger->det_state) + return WPC_OP_MODE_NONE; + + switch (charger->rx_op_mode) { + case PAD_RX_MODE_WPC_BPP: + if (is_ppde_wireless_type(charger->pdata->cable_type)) + return WPC_OP_MODE_PPDE; + return WPC_OP_MODE_BPP; + case PAD_RX_MODE_WPC_EPP: + case PAD_RX_MODE_WPC_EPP_NEGO: + return WPC_OP_MODE_EPP; + case PAD_RX_MODE_WPC_MPP_RESTRICT: + case PAD_RX_MODE_WPC_MPP_FULL: + case PAD_RX_MODE_WPC_MPP_CLOAK: + case PAD_RX_MODE_WPC_MPP_NEGO: + return WPC_OP_MODE_MPP; + } + + return WPC_OP_MODE_NONE; +} + +static int nu1668_get_qi_ver(void *pdata) +{ + struct mfc_charger_data *charger = pdata; + + if (!charger->det_state) + return 0; + + return (charger->mpp_epp_tx_id >> 16); +} + +static int nu1668_get_auth_mode(void *pdata) +{ + struct mfc_charger_data *charger = pdata; + + if (!charger->det_state) + return WPC_AUTH_MODE_NONE; + + if (mpp_mode(charger->rx_op_mode)) + return WPC_AUTH_MODE_MPP; + + if (is_ppde_wireless_type(charger->pdata->cable_type)) + return WPC_AUTH_MODE_PPDE; + + if (epp_mode(charger->rx_op_mode)) + return WPC_AUTH_MODE_EPP; + + return WPC_AUTH_MODE_BPP; +} + +static const struct sb_wireless_op nu1668_sbw_op = { + .get_op_mode = nu1668_get_op_mode, + .get_qi_ver = nu1668_get_qi_ver, + .get_auth_mode = nu1668_get_auth_mode, +}; + +static bool is_no_hv(struct mfc_charger_data *charger) +{ + return (charger->pdata->no_hv == 1); +} + +static bool is_samsung_pad(u8 vendor_id) +{ + return (vendor_id == 0x42); +} + +static bool is_3rd_pad(u16 vendor_id) +{ + return (vendor_id == 0x6E00) || (vendor_id == 0x0066); +} + +static bool is_phm_supported_pad(struct mfc_charger_data *charger) +{ + pr_info("%s: tx_id_cnt(%d) tx_id(0x%x)\n", __func__, charger->tx_id_cnt, charger->tx_id); + + if (charger->tx_id == TX_ID_UNKNOWN || + charger->tx_id == TX_ID_N3300_V_PAD || + charger->tx_id == TX_ID_N3300_H_PAD || + charger->tx_id == TX_ID_N5200_V_PAD || + charger->tx_id == TX_ID_N5200_H_PAD || + charger->tx_id == TX_ID_BATT_PACK_U1200 || + charger->tx_id == TX_ID_BATT_PACK_U3300) + return false; + return true; +} + +static int mfc_reg_read(struct i2c_client *client, u16 reg, u8 *val) +{ + struct mfc_charger_data *charger = i2c_get_clientdata(client); + int ret; + struct i2c_msg msg[2]; + u8 wbuf[2]; + u8 rbuf[2]; + + if (charger->reg_access_lock) { + pr_err("%s: can not access to reg during fw update\n", __func__); + return -1; + } + + msg[0].addr = client->addr; + msg[0].flags = client->flags & I2C_M_TEN; + msg[0].len = 2; + msg[0].buf = wbuf; + + wbuf[0] = (reg & 0xFF00) >> 8; + wbuf[1] = (reg & 0xFF); + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = 1; + msg[1].buf = rbuf; + + mutex_lock(&charger->io_lock); + ret = i2c_transfer(client->adapter, msg, 2); + //if don't have IIC detect pin, then need to remove below code, don't detect IIC error. + mfc_check_i2c_error(charger, (ret < 0)); + mutex_unlock(&charger->io_lock); + if (ret < 0) { + pr_err("%s: i2c read error, reg: 0x%x, ret: %d (called by %ps)\n", + __func__, reg, ret, __builtin_return_address(0)); + return -1; + } + + *val = rbuf[0]; + + return ret; +} + +static int mfc_reg_multi_read(struct i2c_client *client, u16 reg, u8 *val, int size) +{ + struct mfc_charger_data *charger = i2c_get_clientdata(client); + int ret; + struct i2c_msg msg[2]; + u8 wbuf[2]; + + if (charger->reg_access_lock) { + pr_err("%s: can not access to reg during fw update\n", __func__); + return -1; + } + + msg[0].addr = client->addr; + msg[0].flags = client->flags & I2C_M_TEN; + msg[0].len = 2; + msg[0].buf = wbuf; + + wbuf[0] = (reg & 0xFF00) >> 8; + wbuf[1] = (reg & 0xFF); + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = size; + msg[1].buf = val; + + mutex_lock(&charger->io_lock); + ret = i2c_transfer(client->adapter, msg, 2); + mfc_check_i2c_error(charger, (ret < 0)); + mutex_unlock(&charger->io_lock); + if (ret < 0) { + pr_err("%s: i2c transfer fail", __func__); + return -1; + } + + return ret; +} + +static int mfc_reg_write(struct i2c_client *client, u16 reg, u8 val) +{ + struct mfc_charger_data *charger = i2c_get_clientdata(client); + int ret; + unsigned char data[3] = { reg >> 8, reg & 0xff, val }; + + if (charger->reg_access_lock) { + pr_err("%s: can not access to reg during fw update\n", __func__); + return -1; + } + + mutex_lock(&charger->io_lock); + ret = i2c_master_send(client, data, 3); + mfc_check_i2c_error(charger, (ret < 3)); + mutex_unlock(&charger->io_lock); + if (ret < 3) { + pr_err("%s: i2c write error, reg: 0x%x, ret: %d (called by %ps)\n", + __func__, reg, ret, __builtin_return_address(0)); + return ret < 0 ? ret : -EIO; + } + + return 0; +} + +//read IIC register data and opration its bit value. +static int mfc_reg_update(struct i2c_client *client, u16 reg, u8 val, u8 mask) +{ + //val = 0b 0000 0001 + //ms = 0b 1111 1110 + struct mfc_charger_data *charger = i2c_get_clientdata(client); + unsigned char data[3] = {reg >> 8, reg & 0xff, val}; + u8 data2; + int ret; + + if (charger->reg_access_lock) { + pr_err("%s: can not access to reg during fw update\n", __func__); + return -1; + } + + ret = mfc_reg_read(client, reg, &data2); + if (ret >= 0) { + u8 old_val = data2 & 0xff; + u8 new_val = (val & mask) | (old_val & (~mask)); + + data[2] = new_val; + mutex_lock(&charger->io_lock); + ret = i2c_master_send(client, data, 3); + mfc_check_i2c_error(charger, (ret < 3)); + mutex_unlock(&charger->io_lock); + if (ret < 3) { + pr_err("%s: i2c write error, reg: 0x%x, ret: %d\n", + __func__, reg, ret); + return ret < 0 ? ret : -EIO; + } + } + mfc_reg_read(client, reg, &data2); + + return ret; +} + +static int mfc_get_firmware_version(struct mfc_charger_data *charger, int firm_mode) +{ + int ver_major = -1, ver_minor = -1; + int ret; + u8 fw_major[2] = {0, }; + u8 fw_minor[2] = {0, }; + + pr_info("%s: called by (%ps)\n", __func__, __builtin_return_address(0)); + switch (firm_mode) { + case MFC_RX_FIRMWARE: + ret = mfc_reg_read(charger->client, MFC_FW_MAJOR_REV_L_REG, &fw_major[0]); + if (ret < 0) + break; + + ret = mfc_reg_read(charger->client, MFC_FW_MAJOR_REV_H_REG, &fw_major[1]); + if (ret < 0) + break; + + ver_major = fw_major[0] | (fw_major[1] << 8); + pr_info("%s: rx major firmware version 0x%x\n", __func__, ver_major); + + ret = mfc_reg_read(charger->client, MFC_FW_MINOR_REV_L_REG, &fw_minor[0]); + if (ret < 0) + break; + + ret = mfc_reg_read(charger->client, MFC_FW_MINOR_REV_H_REG, &fw_minor[1]); + if (ret < 0) + break; + + ver_minor = fw_minor[0] | (fw_minor[1] << 8); + pr_info("%s: rx minor firmware version 0x%x\n", __func__, ver_minor); + break; + default: + pr_err("%s: Wrong firmware mode\n", __func__); + ver_major = -1; + ver_minor = -1; + break; + } + +#if defined(CONFIG_WIRELESS_IC_PARAM) + if (ver_minor > 0 && charger->wireless_fw_ver_param != ver_minor) { + pr_info("%s: fw_ver (param:0x%04X, version:0x%04X)\n", + __func__, charger->wireless_fw_ver_param, ver_minor); + charger->wireless_fw_ver_param = ver_minor; + charger->wireless_param_info &= 0xFF0000FF; + charger->wireless_param_info |= (charger->wireless_fw_ver_param & 0xFFFF) << 8; + pr_info("%s: wireless_param_info (0x%08X)\n", __func__, charger->wireless_param_info); + } +#endif + + return ver_minor; +} + + +static bool mfc_check_customer_id(struct mfc_charger_data *charger) +{ + u8 customer_id = 0; + int ret = 0; + + ret = mfc_reg_read(charger->client, MFC_CUSTOMER_ID_REG, &customer_id); + if (ret < 0) + return false; + + pr_info("%s: Customer ID: 0x%x\n", __func__, customer_id); + + if (customer_id == 0x01) + return true; + + return false; +} + + +static bool mfc_check_chip_id(struct mfc_charger_data *charger) +{ + u8 chip_id_l = 0, chip_id_h = 0; + int ret = 0; + + ret = mfc_reg_read(charger->client, MFC_CHIP_ID_L_REG, &chip_id_l); + if (ret < 0) + return false; + + ret = mfc_reg_read(charger->client, MFC_CHIP_ID_H_REG, &chip_id_h); + if (ret < 0) + return false; + + pr_info("%s: CHIP_L(0x%x), CHIP_H(0x%x)\n", __func__, chip_id_l, chip_id_h); + + if ((chip_id_l == 0x68) && (chip_id_h == 0x16)) + return true; + + return false; +} + +static int mfc_get_chip_id(struct mfc_charger_data *charger) +{ + int ret = 0; + u8 chip_id = 0, chip_id_h = 0; + + ret = mfc_reg_read(charger->client, MFC_CHIP_ID_L_REG, &chip_id); + ret = mfc_reg_read(charger->client, MFC_CHIP_ID_H_REG, &chip_id_h); + if (ret >= 0) { + pr_info("%s: CHIP_L(0x%x), CHIP_H(0x%x)\n", __func__, chip_id, chip_id_h); + charger->chip_id = MFC_CHIP_NUVOLTA; + } else + return -1; + +#if defined(CONFIG_WIRELESS_IC_PARAM) + if (chip_id > 0 && charger->wireless_chip_id_param != chip_id) { + pr_info("%s: chip_id (param:0x%02X, chip_id:0x%02X)\n", + __func__, charger->wireless_chip_id_param, chip_id); + charger->wireless_chip_id_param = chip_id; + charger->wireless_param_info &= 0x00FFFFFF; + charger->wireless_param_info |= (charger->wireless_chip_id_param & 0xFF) << 24; + pr_info("%s: wireless_param_info (0x%08X)\n", __func__, charger->wireless_param_info); + } +#endif + + return charger->chip_id; +} + +static int mfc_get_ic_revision(struct mfc_charger_data *charger, int read_mode) +{ + u8 temp; + int ret = -1; + + pr_info("%s: called by (%ps)\n", __func__, __builtin_return_address(0)); + + switch (read_mode) { + case MFC_IC_REVISION: + ret = mfc_reg_read(charger->client, MFC_CHIP_REVISION_REG, &temp); + if (ret >= 0) { + temp &= MFC_CHIP_REVISION_MASK; + pr_info("%s: ic revision = %d\n", __func__, temp); + ret = temp; + } + break; +//1668 don't have font version +/* + case MFC_IC_FONT: + ret = mfc_reg_read(charger->client, MFC_CHIP_REVISION_REG, &temp); + if (ret >= 0) { + temp &= MFC_CHIP_FONT_MASK; + pr_info("%s: ic font = %d\n", __func__, temp); + ret = temp; + } + break; +*/ + default: + break; + } + return ret; +} + +static int mfc_get_adc(struct mfc_charger_data *charger, int adc_type) +{ + int ret = 0; + u8 data[2] = {0,}; + + switch (adc_type) { + case MFC_ADC_VOUT: + ret = mfc_reg_read(charger->client, MFC_ADC_VOUT_L_REG, &data[0]); + if (ret < 0) + break; + ret = mfc_reg_read(charger->client, MFC_ADC_VOUT_H_REG, &data[1]); + if (ret < 0) + break; + ret = (data[0] | (data[1] << 8)); + charger->mfc_adc_vout = ret; + break; + case MFC_ADC_VRECT: + ret = mfc_reg_read(charger->client, MFC_ADC_VRECT_L_REG, &data[0]); + if (ret < 0) + break; + ret = mfc_reg_read(charger->client, MFC_ADC_VRECT_H_REG, &data[1]); + if (ret < 0) + break; + ret = (data[0] | (data[1] << 8)); + charger->mfc_adc_vrect = ret; + break; + case MFC_ADC_RX_IOUT: + ret = mfc_reg_read(charger->client, MFC_ADC_IOUT_L_REG, &data[0]); + if (ret < 0) + break; + ret = mfc_reg_read(charger->client, MFC_ADC_IOUT_H_REG, &data[1]); + if (ret < 0) + break; + ret = (data[0] | (data[1] << 8)); + charger->mfc_adc_rx_iout = ret; + break; + case MFC_ADC_DIE_TEMP: // Unit : Celsius + ret = mfc_reg_read(charger->client, MFC_ADC_DIE_TEMP_L_REG, &data[0]); + if (ret < 0) + break; + ret = mfc_reg_read(charger->client, MFC_ADC_DIE_TEMP_H_REG, &data[1]); + if (ret < 0) + break; + ret = (data[0] | (data[1] << 8)); + pr_info("[nu1668] [%s] die_temp = %d\n", __func__, ret); + break; + case MFC_ADC_OP_FRQ: /* kHz */ // Unit : kHz, Frequency + ret = mfc_reg_read(charger->client, MFC_TRX_OP_FREQ_L_REG, &data[0]); + if (ret < 0) + break; + ret = mfc_reg_read(charger->client, MFC_TRX_OP_FREQ_H_REG, &data[1]); + if (ret < 0) + break; + ret = (data[0] | (data[1] << 8)); + charger->mfc_adc_op_frq = ret; + break; + case MFC_ADC_TX_MAX_OP_FRQ: // Frequency = 96MHz / value, default is 148kHz ( 96MHz / 0x288 = 148kHz ) + ret = mfc_reg_read(charger->client, MFC_TX_MAX_OP_FREQ_L_REG, &data[0]); + if (ret < 0) + break; + ret = mfc_reg_read(charger->client, MFC_TX_MAX_OP_FREQ_H_REG, &data[1]); + if (ret < 0) + break; + ret = (u16)(96000/(data[0] | (data[1] << 8))); + charger->mfc_adc_tx_max_op_frq = ret; + break; + case MFC_ADC_TX_MIN_OP_FRQ: // Frequency = 96MHz / value, default is 113kHz ( 96MHz / 0x849 = 113kHz ) + ret = mfc_reg_read(charger->client, MFC_TX_MIN_OP_FREQ_L_REG, &data[0]); + if (ret < 0) + break; + ret = mfc_reg_read(charger->client, MFC_TX_MIN_OP_FREQ_H_REG, &data[1]); + if (ret < 0) + break; + ret = (u16)(96000/(data[0] | (data[1] << 8))); + charger->mfc_adc_tx_min_op_frq = ret; + break; + case MFC_ADC_PING_FRQ: // Frequency = 96MHz / value, default is 145kHz ( 96MHz / 0x296 = 145kHz ) + ret = mfc_reg_read(charger->client, MFC_TX_PING_FREQ_L_REG, &data[0]); + if (ret < 0) + break; + ret = mfc_reg_read(charger->client, MFC_TX_PING_FREQ_H_REG, &data[1]); + if (ret < 0) + break; + ret = (u16)(96000/(data[0] | (data[1] << 8))); + charger->mfc_adc_ping_frq = ret; + break; + case MFC_ADC_TX_VOUT: + ret = mfc_reg_read(charger->client, MFC_ADC_VOUT_L_REG, &data[0]); + if (ret < 0) + break; + ret = mfc_reg_read(charger->client, MFC_ADC_VOUT_H_REG, &data[1]); + if (ret < 0) + break; + ret = (data[0] | (data[1] << 8)); + charger->mfc_adc_tx_vout = ret; + break; + case MFC_ADC_TX_IOUT: + if (charger->wc_tx_enable) { + ret = mfc_reg_read(charger->client, MFC_ADC_TX_ISENSE_L_REG, &data[0]); + if (ret < 0) + break; + ret = mfc_reg_read(charger->client, MFC_ADC_TX_ISENSE_H_REG, &data[1]); + if (ret < 0) + break; + } else { + ret = mfc_reg_read(charger->client, MFC_ADC_IOUT_L_REG, &data[0]); + if (ret < 0) + break; + ret = mfc_reg_read(charger->client, MFC_ADC_IOUT_H_REG, &data[1]); + if (ret < 0) + break; + } + ret = (data[0] | (data[1] << 8)); + charger->mfc_adc_tx_iout = ret; + break; + default: + break; + } + + return ret; +} + +static void mfc_set_wpc_en(struct mfc_charger_data *charger, char flag, char on) +{ + union power_supply_propval value = {0, }; + int enable = 0, temp = charger->wpc_en_flag; + int old_val = 0, new_val = 0; + + psy_do_property("battery", get, + POWER_SUPPLY_PROP_STATUS, value); + + mutex_lock(&charger->wpc_en_lock); + + if (on) { + if (value.intval == POWER_SUPPLY_STATUS_DISCHARGING) + charger->wpc_en_flag |= WPC_EN_CHARGING; + charger->wpc_en_flag |= flag; + } else { + charger->wpc_en_flag &= ~flag; + } + + if (charger->wpc_en_flag & WPC_EN_FW) + enable = 1; + else if (!(charger->wpc_en_flag & WPC_EN_SYSFS) || !(charger->wpc_en_flag & WPC_EN_CCIC)) + enable = 0; + else if (!(charger->wpc_en_flag & (WPC_EN_CHARGING | WPC_EN_MST | WPC_EN_TX))) + enable = 0; + else + enable = 1; + + if (charger->pdata->wpc_en >= 0) { + old_val = gpio_get_value(charger->pdata->wpc_en); + if (enable) + gpio_direction_output(charger->pdata->wpc_en, 0); + else + gpio_direction_output(charger->pdata->wpc_en, 1); + new_val = gpio_get_value(charger->pdata->wpc_en); + + pr_info("@DIS_MFC %s: before(0x%x), after(0x%x), en(%d), val(%d->%d)\n", + __func__, temp, charger->wpc_en_flag, enable, old_val, new_val); + + mutex_unlock(&charger->wpc_en_lock); + if (old_val != new_val) { + value.intval = enable; + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_WPC_EN, value); + } + } else { + pr_info("@DIS_MFC %s: there`s no wpc_en\n", __func__); + mutex_unlock(&charger->wpc_en_lock); + } +} + +static void mfc_set_coil_sw_en(struct mfc_charger_data *charger, int enable) +{ +#if defined(CONFIG_SEC_FACTORY) + pr_info("@Tx_Mode %s: do not set with factory bin\n", __func__); + return; +#endif + + if (charger->pdata->coil_sw_en < 0) { + pr_info("@Tx_Mode %s: no coil_sw_en\n", __func__); + return; + } + + if (gpio_get_value(charger->pdata->coil_sw_en) == enable) { + pr_info("@Tx_Mode %s: Skip to set same config, val(%d)\n", + __func__, gpio_get_value(charger->pdata->coil_sw_en)); + return; + } + + gpio_direction_output(charger->pdata->coil_sw_en, enable); + pr_info("@Tx_Mode %s: en(%d), val(%d)\n", __func__, + enable, gpio_get_value(charger->pdata->coil_sw_en)); +} + +static void mfc_set_force_vout(struct mfc_charger_data *charger, int vout) +{ + u8 data[2] = {0,}; + + if ((charger->pdata->cable_type == SEC_BATTERY_CABLE_WIRELESS_EPP_FAKE) && \ + (charger->rx_op_mode == PAD_RX_MODE_WPC_EPP_NEGO)) { + pr_info("%s: Escape MPP and EPP Calibration step\n", __func__); + return; + } + + if (charger->wc_ldo_status == MFC_LDO_OFF) { + pr_info("%s: vout updated to 5V becauseof wc ldo\n", __func__); + vout = MFC_VOUT_5V; + } + + // Vout = value * 0.1 (V) + 3.5 (V). + data[0] = mfc_nu1668_vout_val16[vout] & 0xff; + data[1] = (mfc_nu1668_vout_val16[vout] & 0xff00) >> 8; + mfc_reg_write(charger->client, MFC_VOUT_SET_L_REG, data[0]); + //mfc_reg_write(charger->client, MFC_VOUT_SET_H_REG, data[1]); + + mfc_fod_set_vout(charger->fod, (3500 + (mfc_nu1668_vout_val16[vout] * 100))); + mfc_cmfet_set_vout(charger->cmfet, (3500 + (mfc_nu1668_vout_val16[vout] * 100))); + msleep(100); + + pr_info("%s: set vout(%s, 0x%04X) read = %d mV\n", __func__, + sb_rx_vout_str(vout), (data[0] | (data[1] << 8)), mfc_get_adc(charger, MFC_ADC_VOUT)); + charger->pdata->vout_status = vout; +} + +static void mfc_set_vout(struct mfc_charger_data *charger, int vout) +{ + u8 data[2] = {0,}; + + if ((charger->pdata->cable_type == SEC_BATTERY_CABLE_WIRELESS_EPP_FAKE) && \ + (charger->rx_op_mode == PAD_RX_MODE_WPC_EPP_NEGO)) { + pr_info("%s: Escape MPP and EPP Calibration step\n", __func__); + return; + } + + if (charger->wc_ldo_status == MFC_LDO_OFF) { + pr_info("%s: vout updated to 5V becauseof wc ldo\n", __func__); + vout = MFC_VOUT_5V; + } + + if (charger->is_otg_on && (vout > MFC_VOUT_5V)) { + pr_info("%s: skip set vout in otg case\n", __func__); + return; + } + + // Vout = value * 0.1 (V) + 3.5 (V). + data[0] = mfc_nu1668_vout_val16[vout] & 0xff; + data[1] = (mfc_nu1668_vout_val16[vout] & 0xff00) >> 8; + mfc_reg_write(charger->client, MFC_VOUT_SET_L_REG, data[0]); + //mfc_reg_write(charger->client, MFC_VOUT_SET_H_REG, data[1]); + + mfc_fod_set_vout(charger->fod, (3500 + (mfc_nu1668_vout_val16[vout] * 100))); + mfc_cmfet_set_vout(charger->cmfet, (3500 + (mfc_nu1668_vout_val16[vout] * 100))); + msleep(100); + + pr_info("%s: set vout(%s, 0x%04X) read = %d mV\n", __func__, + sb_rx_vout_str(vout), (data[0] | (data[1] << 8)), mfc_get_adc(charger, MFC_ADC_VOUT)); + charger->pdata->vout_status = vout; +} + +static int mfc_get_vout(struct mfc_charger_data *charger) +{ + u8 data[2] = {0,}; + int ret; + + ret = mfc_reg_read(charger->client, MFC_VOUT_SET_L_REG, &data[0]); + //ret = mfc_reg_read(charger->client, MFC_VOUT_SET_H_REG, &data[1]); + + if (ret < 0) { + pr_err("%s: fail to read vout. (%d)\n", __func__, ret); + } else { + ret = (data[0] | (data[1] << 8)); + pr_info("%s: vout(0x%04x)\n", __func__, ret); + } + + return ret; +} + +static void mfc_uno_on(struct mfc_charger_data *charger, bool on) +{ + union power_supply_propval value = {0, }; + + if (charger->wc_tx_enable && on) { /* UNO ON */ + value.intval = SEC_BAT_CHG_MODE_UNO_ONLY; + psy_do_property("otg", set, + POWER_SUPPLY_EXT_PROP_CHARGE_UNO_CONTROL, value); + pr_info("%s: UNO ON\n", __func__); + } else if (on) { /* UNO ON */ + value.intval = 1; + psy_do_property("otg", set, + POWER_SUPPLY_EXT_PROP_CHARGE_UNO_CONTROL, value); + pr_info("%s: UNO ON\n", __func__); + } else { /* UNO OFF */ + value.intval = 0; + psy_do_property("otg", set, + POWER_SUPPLY_EXT_PROP_CHARGE_UNO_CONTROL, value); + + if (delayed_work_pending(&charger->wpc_tx_duty_min_work)) { + __pm_relax(charger->wpc_tx_duty_min_ws); + cancel_delayed_work(&charger->wpc_tx_duty_min_work); + } + pr_info("%s: UNO OFF\n", __func__); + } +} +//RPP scale: 256---1,128---0.5,64--0.25 +static void mfc_rpp_set(struct mfc_charger_data *charger, u8 val) +{ + u8 data; + int ret; + + pr_info("%s: Scale Factor %d\n", __func__, val); + mfc_reg_write(charger->client, MFC_RPP_SCALE_COEF_REG, val); + usleep_range(5000, 6000); + ret = mfc_reg_read(charger->client, MFC_RPP_SCALE_COEF_REG, &data); + if (ret < 0) + pr_err("%s: fail to read RPP scaling coefficient. (%d)\n", __func__, ret); + else + pr_info("%s: RPP scaling coefficient(0x%x)\n", __func__, data); +} + +static void mfc_set_tx_conflict_current(struct mfc_charger_data *charger, int tx_cf_current) +{ + u8 data = (u8)(tx_cf_current / 100); + + //mfc_reg_write(charger->client, MFC_TX_CONFLICT_CURRENT_REG, data); + pr_info("%s: current = %d, data = 0x%x\n", __func__, tx_cf_current, data); +} + +static void mfc_set_tx_min_op_freq(struct mfc_charger_data *charger, unsigned int op_freq) +{ + u8 data[2] = {0,}; + + pr_info("%s: tx_min_op_freq = %d KHz\n", __func__, op_freq / 10); + + data[0] = op_freq & 0xFF; + data[1] = (op_freq & 0xFF00) >> 8; + mfc_reg_write(charger->client, MFC_TX_MIN_OP_FREQ_L_REG, data[0]); + mfc_reg_write(charger->client, MFC_TX_MIN_OP_FREQ_H_REG, data[1]); +} + +static void mfc_set_tx_max_op_freq(struct mfc_charger_data *charger, unsigned int op_freq) +{ + u8 data[2] = {0,}; + + pr_info("%s: tx_max_op_freq = %d KHz\n", __func__, op_freq / 10); + + data[0] = op_freq & 0xFF; + data[1] = (op_freq & 0xFF00) >> 8; + mfc_reg_write(charger->client, MFC_TX_MAX_OP_FREQ_L_REG, data[0]); + mfc_reg_write(charger->client, MFC_TX_MAX_OP_FREQ_H_REG, data[1]); +} + +static void mfc_set_min_duty(struct mfc_charger_data *charger, unsigned int duty) +{ + u8 data = 0; + + data = (duty * 256) / 100; + pr_info("%s: min duty = %d (0x%x)\n", __func__, duty, data); + /* Duty = Min_Duty [7:0] in decimal / 256, 1 is 100% */ + mfc_reg_write(charger->client, MFC_TX_MIN_DUTY_SETTING_REG, data); +} + +static void mfc_set_cmd_l_reg(struct mfc_charger_data *charger, u8 val, u8 mask) +{ + u8 temp = 0; + int ret = 0, i = 0; + + do { + pr_info("%s: value : %d\n", __func__, val); + ret = mfc_reg_update(charger->client, MFC_AP2MFC_CMD_L_REG, val, mask); // command + if (ret >= 0) { + usleep_range(10000, 11000); + ret = mfc_reg_read(charger->client, MFC_AP2MFC_CMD_L_REG, &temp); // check out set bit exists + if (temp != 0) + pr_info("%s: CMD is not clear yet, cnt = %d\n", __func__, i); + if (ret < 0 || i > 3) + break; + } + i++; + } while ((temp != 0) && (i < 3)); +} + +static void mfc_set_cmd_h_reg(struct mfc_charger_data *charger, u8 val, u8 mask) +{ + u8 temp = 0; + int ret = 0, i = 0; + + do { + pr_info("%s\n", __func__); + ret = mfc_reg_update(charger->client, MFC_AP2MFC_CMD_H_REG, val, mask); // command + if (ret >= 0) { + usleep_range(10000, 11000); + ret = mfc_reg_read(charger->client, MFC_AP2MFC_CMD_H_REG, &temp); // check out set bit exists + if (temp != 0) + pr_info("%s: CMD is not clear yet, cnt = %d\n", __func__, i); + if (ret < 0 || i > 3) + break; + } + i++; + } while ((temp != 0) && (i < 3)); +} + +//need power on wireless IC +static void mfc_mpp_exit_cloak(struct mfc_charger_data *charger) +{ + //mfc_set_cmd_h_reg(charger, MFC_CMD2_MPP_EXIT_CLOAK_MASK, MFC_CMD2_MPP_EXIT_CLOAK_MASK); + //pr_info("@MPP %s\n", __func__); + + charger->mpp_cloak = 0; +#if 0 + if (gpio_get_value(charger->pdata->mpp_sw)) { + gpio_direction_output(charger->pdata->mpp_sw, 0); + } +#endif +#if 0 + pr_info("@MPP %s: coil sw (%d)\n", __func__, gpio_get_value(charger->pdata->mpp_sw)); +#else + pr_info("@MPP %s\n", __func__); +#endif +} + +static void mfc_mpp_enter_cloak(struct mfc_charger_data *charger, u8 cloak_reason) +{ + int ret = 0; + + charger->mpp_cloak = cloak_reason; + //pr_info("@MPP %s: coil sw (%d)\n", __func__, gpio_get_value(charger->pdata->mpp_sw)); +#if 0 + if (!gpio_get_value(charger->pdata->mpp_sw)) { + gpio_direction_output(charger->pdata->mpp_sw, 1); + } +#endif + ret = mfc_reg_write(charger->client, MFC_CLOAK_REASON_REG, cloak_reason); + if (ret < 0) + return; + + mfc_set_cmd_h_reg(charger, MFC_CMD2_MPP_ENTER_CLOAK_MASK, MFC_CMD2_MPP_ENTER_CLOAK_MASK); +#if 0 + pr_info("@MPP %s: enter cloak and reason is %d coil sw (%d)\n", __func__, + charger->mpp_cloak, gpio_get_value(charger->pdata->mpp_sw)); +#else + pr_info("@MPP %s: enter cloak and reason is %d\n", __func__, charger->mpp_cloak); +#endif +} + + +#if 0 +static void mfc_mpp_full_mode_enable(struct mfc_charger_data *charger) +{ + mfc_set_cmd_h_reg(charger, MFC_CMD2_MPP_FULL_MODE_SHIFT, MFC_CMD2_MPP_FULL_MODE_SHIFT); + pr_info("%s: restrict mode to full mode\n", __func__); +} + +//if trans_type = MFC_RX_MPP_FULL_MODE_TRAN_POWER_RESET, need disable restrict mode and enable full mode after wireless chip restart +static void mfc_mpp_full_mode_trans(struct mfc_charger_data *charger, u8 trans_type) +{ + int ret = 0; + + ret = mfc_reg_write(charger->client, MFC_MPP_FULL_MODE_TRANS_TYPE_REG, trans_type); + if (ret < 0) + return; + + pr_info("%s: mpp full mode transfer %d\n", __func__, trans_type); +} + +//set negotiate power with Tx, set before negotiation phase. +static void mfc_mpp_epp_nego_power_set(struct mfc_charger_data *charger, u8 nego_load_power) +{ + int ret = 0; +#if 0 + u8 reg_data = 0; + + ret = mfc_reg_read(charger->client, MFC_INT_B_ENABLE_L_REG, ®_data); + if ((ret >= 0) && !(reg_data & MFC_INTB_L_INCREASE_POWER_MASK)) { + pr_info("@MPP @EPP %s: Skip set negotiate power by high temp\n", __func__); + return; + } +#endif + ret = mfc_reg_write(charger->client, MFC_POWER_LEVEL_SETTING_REG, (nego_load_power << 1)); + if (ret < 0) + return; + + pr_info("@MPP @EPP %s: MPP or EPP set negotiate power %dW\n", __func__, nego_load_power); +} +#endif +static void mfc_mpp_inc_int_ctrl(struct mfc_charger_data *charger, u32 enable) +{ + int ret = 0; + + ret = mfc_reg_update(charger->client, MFC_INT_A_ENABLE_L_REG1, + (enable << MFC_INTA1_L_INCREASE_POWER_SHIFT), MFC_INTA1_L_INCREASE_POWER_MASK); + if (ret < 0) + return; + + pr_info("@MPP %s: enable(%d)\n", __func__, enable); +} +#if 0 +static void mfc_mpp_thermal_ctrl(struct mfc_charger_data *charger, u32 enable) +{ + int ret = 0; + + ret = mfc_reg_write(charger->client, MFC_MPP_THERMAL_CTRL_REG, enable); + if (ret < 0) + return; + + pr_info("@MPP %s: enable(%d)\n", __func__, enable); +} +#endif +static void mfc_epp_enable(struct mfc_charger_data *charger, u32 enable) +{ + gpio_direction_output(charger->pdata->mag_det, enable); + + if (charger->pdata->pdrc_vrect_clear) + gpio_direction_output(charger->pdata->wpc_pdrc, enable); + + pr_info("@EPP %s: enable(%d <-> %d, %d)\n", + __func__, enable, + gpio_get_value(charger->pdata->mag_det), + gpio_get_value(charger->pdata->wpc_pdrc)); +} + +static void mfc_set_epp_count(struct mfc_charger_data *charger, unsigned int count) +{ + if (delayed_work_pending(&charger->epp_count_work)) { + __pm_relax(charger->epp_count_ws); + cancel_delayed_work(&charger->epp_count_work); + } + + charger->epp_count = count; + pr_info("%s: %d\n", __func__, count); + if (count <= 0) + return; + + __pm_stay_awake(charger->epp_count_ws); + queue_delayed_work(charger->wqueue, &charger->epp_count_work, msecs_to_jiffies(6000)); +} + +static void mfc_epp_count_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, epp_count_work.work); + + pr_info("%s: %d\n", __func__, charger->epp_count); + charger->epp_count = 0; + __pm_relax(charger->epp_count_ws); +} + +static void mfc_epp_clear_timer_work_start(struct mfc_charger_data *charger, int work_delay) +{ + if (delayed_work_pending(&charger->epp_clear_timer_work)) { + __pm_relax(charger->epp_clear_ws); + cancel_delayed_work(&charger->epp_clear_timer_work); + } + + if (work_delay < 0) + return; + + if ((charger->epp_count > 0) && + (charger->epp_count >= charger->pdata->mpp_epp_max_count)) { + work_delay = 6000; + mfc_set_epp_count(charger, 0); + } + pr_info("%s: set delay(%d), count(%d, %d)\n", + __func__, work_delay, charger->epp_count, charger->pdata->mpp_epp_max_count); + __pm_stay_awake(charger->epp_clear_ws); + queue_delayed_work(charger->wqueue, &charger->epp_clear_timer_work, msecs_to_jiffies(work_delay)); +} + +#if 0 +static u32 mfc_mpp_epp_get_nego_done_power(struct mfc_charger_data *charger) +{ + u32 data = 0; + u8 temp = 0; + + mfc_reg_read(charger->client, MFC_MPP_EPP_NEGO_DONE_POWER_L_REG, &temp); + data = temp; + mfc_reg_read(charger->client, MFC_MPP_EPP_NEGO_DONE_POWER_H_REG, &temp); + data |= (temp << 8); + data = data * 100; + pr_info("@MPP @EPP %s: mpp or epp negotiated power %dmW\n", __func__, data); + return data; +} + +static u8 mfc_mpp_get_gp_state(struct mfc_charger_data *charger) +{ + u8 data = 0; + + mfc_reg_read(charger->client, MFC_MPP_GP_STATE_REG, &data); + pr_info("%s: mpp gp state %02X\n", __func__, data); + return data; +} +#endif + +static void mfc_send_eop(struct mfc_charger_data *charger, int health_mode) +{ + int i = 0; + int ret = 0; + + pr_info("%s: health_mode(0x%x), cable_type(%d)\n", + __func__, health_mode, charger->pdata->cable_type); + + switch (health_mode) { + case POWER_SUPPLY_HEALTH_OVERHEAT: + case POWER_SUPPLY_EXT_HEALTH_OVERHEATLIMIT: + case POWER_SUPPLY_HEALTH_COLD: + pr_info("%s: ept-ot\n", __func__); + if (charger->pdata->cable_type == SEC_BATTERY_CABLE_PMA_WIRELESS) { + for (i = 0; i < CMD_CNT; i++) { + ret = mfc_reg_write(charger->client, MFC_EPT_REG, MFC_WPC_EPT_END_OF_CHG); + if (ret < 0) + break; + + mfc_set_cmd_l_reg(charger, MFC_CMD_SEND_EOP_MASK, MFC_CMD_SEND_EOP_MASK); + msleep(250); + } + } else { + for (i = 0; i < CMD_CNT; i++) { + ret = mfc_reg_write(charger->client, MFC_EPT_REG, MFC_WPC_EPT_OVER_TEMP); + if (ret < 0) + break; + + mfc_set_cmd_l_reg(charger, MFC_CMD_SEND_EOP_MASK, MFC_CMD_SEND_EOP_MASK); + msleep(250); + } + } + break; + default: + break; + } +} + +static void mfc_packet_assist(struct mfc_charger_data *charger) +{ + u8 dummy; + + mfc_reg_read(charger->client, MFC_WPC_RX_DATA_COM_REG, &dummy); + mfc_reg_read(charger->client, MFC_WPC_RX_DATA_VALUE0_REG, &dummy); + mfc_reg_read(charger->client, MFC_AP2MFC_CMD_L_REG, &dummy); +} + + +static void mfc_send_packet(struct mfc_charger_data *charger, u8 header, u8 rx_data_com, u8 *data_val, int data_size) +{ + int i; + + mfc_reg_write(charger->client, MFC_WPC_PCKT_HEADER_REG, header); + mfc_reg_write(charger->client, MFC_WPC_RX_DATA_COM_REG, rx_data_com); + + for (i = 0; i < data_size; i++) + mfc_reg_write(charger->client, MFC_WPC_RX_DATA_VALUE0_REG + i, data_val[i]); + mfc_set_cmd_l_reg(charger, MFC_CMD_SEND_TRX_DATA_MASK, MFC_CMD_SEND_TRX_DATA_MASK); +} + +static int mfc_send_cs100(struct mfc_charger_data *charger) +{ + int i = 0; + int ret = 0; + + for (i = 0; i < CMD_CNT; i++) { + ret = mfc_reg_write(charger->client, MFC_BATTERY_CHG_STATUS_REG, 100); + if (ret >= 0) { + mfc_set_cmd_l_reg(charger, MFC_CMD_SEND_CHG_STS_MASK, MFC_CMD_SEND_CHG_STS_MASK); + msleep(250); + ret = 1; + } else { + ret = -1; + break; + } + } + return ret; +} + +static void mfc_send_ept_unknown(struct mfc_charger_data *charger) +{ + pr_info("%s: EPT-Unknown\n", __func__); + mfc_reg_write(charger->client, MFC_EPT_REG, MFC_WPC_EPT_UNKNOWN); + mfc_set_cmd_l_reg(charger, MFC_CMD_SEND_EOP_MASK, MFC_CMD_SEND_EOP_MASK); +} + +static void mfc_send_command(struct mfc_charger_data *charger, int cmd_mode) +{ + u8 data_val[4]; + u8 cmd = 0; + u8 i; + + switch (cmd_mode) { + case MFC_AFC_CONF_5V: + pr_info("%s: WPC/PMA set 5V\n", __func__); + cmd = WPC_COM_AFC_SET; + data_val[0] = 0x05; /* Value for WPC AFC_SET 5V */ + mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1); // 0x28 0x06 0x05 + msleep(120); + + charger->vout_mode = WIRELESS_VOUT_5V; + cancel_delayed_work(&charger->wpc_vout_mode_work); + __pm_stay_awake(charger->wpc_vout_mode_ws); + queue_delayed_work(charger->wqueue, &charger->wpc_vout_mode_work, 0); + + pr_info("%s: vout read = %d\n", __func__, mfc_get_adc(charger, MFC_ADC_VOUT)); + mfc_packet_assist(charger); + break; + case MFC_AFC_CONF_10V: + pr_info("%s: WPC set 10V\n", __func__); + /* trigger 10V vout work after 8sec */ + __pm_stay_awake(charger->wpc_afc_vout_ws); +#if defined(CONFIG_SEC_FACTORY) + queue_delayed_work(charger->wqueue, &charger->wpc_afc_vout_work, msecs_to_jiffies(3000)); +#else + queue_delayed_work(charger->wqueue, &charger->wpc_afc_vout_work, msecs_to_jiffies(8000)); +#endif + break; + case MFC_AFC_CONF_5V_TX: + for (i = 0; i < CMD_CNT; i++) { + cmd = WPC_COM_AFC_SET; + data_val[0] = RX_DATA_VAL2_5V; /* Value for WPC AFC_SET 5V */ + pr_info("%s: set 5V to TX, cnt = %d\n", __func__, i); + mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1); // 28 06 05 + mfc_packet_assist(charger); + msleep(100); + } + mfc_rpp_set(charger, 255); + break; + case MFC_AFC_CONF_10V_TX: + for (i = 0; i < CMD_CNT; i++) { + cmd = WPC_COM_AFC_SET; + data_val[0] = RX_DATA_VAL2_10V; /* Value for WPC AFC_SET 10V */ + pr_info("%s: set 10V to TX, cnt = %d\n", __func__, i); + mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1); // 28 06 2c + mfc_packet_assist(charger); + msleep(100); + } + mfc_rpp_set(charger, 128); + break; + case MFC_AFC_CONF_12V_TX: + for (i = 0; i < CMD_CNT; i++) { + cmd = WPC_COM_AFC_SET; + data_val[0] = RX_DATA_VAL2_12V; /* Value for WPC AFC_SET 12V */ + pr_info("%s: set 12V to TX, cnt = %d\n", __func__, i); + mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1); + mfc_packet_assist(charger); + msleep(100); + } + mfc_rpp_set(charger, 128); + break; + case MFC_AFC_CONF_12_5V_TX: + disable_irq(charger->pdata->irq_wpc_int); /* disable int in order not to have rx power int before this work done */ + for (i = 0; i < CMD_CNT; i++) { + cmd = WPC_COM_AFC_SET; + data_val[0] = RX_DATA_VAL2_12_5V; /* Value for WPC AFC_SET 12V */ + pr_info("%s: set 12.5V to TX, cnt = %d\n", __func__, i); + mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1); + mfc_packet_assist(charger); + msleep(100); + } + mfc_rpp_set(charger, 128); + enable_irq(charger->pdata->irq_wpc_int); + + if (charger->pdata->cable_type == SEC_BATTERY_CABLE_HV_WIRELESS_20) { + if (delayed_work_pending(&charger->wpc_rx_power_trans_fail_work)) { + __pm_relax(charger->wpc_rx_power_trans_fail_ws); + cancel_delayed_work(&charger->wpc_rx_power_trans_fail_work); + } + charger->check_rx_power = true; + __pm_stay_awake(charger->wpc_rx_power_trans_fail_ws); + queue_delayed_work(charger->wqueue, + &charger->wpc_rx_power_trans_fail_work, msecs_to_jiffies(3000)); /* this work is for in case of missing rx power intterupt */ + } + break; + case MFC_AFC_CONF_20V_TX: + for (i = 0; i < CMD_CNT; i++) { + cmd = WPC_COM_AFC_SET; + data_val[0] = RX_DATA_VAL2_20V; /* Value for WPC AFC_SET 20V */ + pr_info("%s: set 20V to TX, cnt = %d\n", __func__, i); + mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1); + mfc_packet_assist(charger); + msleep(100); + } + mfc_rpp_set(charger, 64); + break; + case MFC_LED_CONTROL_ON: + pr_info("%s: led on\n", __func__); + cmd = WPC_COM_LED_CONTROL; + data_val[0] = 0x00; /* Value for WPC LED ON */ + mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1); + msleep(100); + break; + case MFC_LED_CONTROL_OFF: + pr_info("%s: led off\n", __func__); + cmd = WPC_COM_LED_CONTROL; + data_val[0] = 0xff; /* Value for WPC LED OFF */ + mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1); + msleep(100); + break; + case MFC_LED_CONTROL_DIMMING: + pr_info("%s: led dimming\n", __func__); + cmd = WPC_COM_LED_CONTROL; + data_val[0] = 0x55; /* Value for WPC LED DIMMING */ + mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1); + msleep(100); + break; + case MFC_FAN_CONTROL_ON: + pr_info("%s: fan on\n", __func__); + cmd = WPC_COM_COOLING_CTRL; + data_val[0] = 0x00; /* Value for WPC FAN ON */ + mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1); + msleep(100); + break; + case MFC_FAN_CONTROL_OFF: + pr_info("%s: fan off\n", __func__); + cmd = WPC_COM_COOLING_CTRL; + data_val[0] = 0xff; /* Value for WPC FAN OFF */ + mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1); + msleep(100); + break; + case MFC_REQUEST_AFC_TX: + pr_info("%s: request afc tx, cable(0x%x)\n", __func__, charger->pdata->cable_type); + cmd = WPC_COM_REQ_AFC_TX; + data_val[0] = 0x00; /* Value for WPC Request AFC_TX */ + mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1); + break; + case MFC_REQUEST_TX_ID: + pr_info("%s: request TX ID\n", __func__); + cmd = WPC_COM_TX_ID; + data_val[0] = 0x00; /* Value for WPC TX ID */ + mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1); + break; + case MFC_DISABLE_TX: + pr_info("%s: Disable TX\n", __func__); + cmd = WPC_COM_DISABLE_TX; + data_val[0] = 0x55; /* Value for Disable TX */ + mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1); + break; + case MFC_PHM_ON: + pr_info("%s: Enter PHM\n", __func__); + cmd = WPC_COM_ENTER_PHM; + data_val[0] = 0x01; /* Enter PHM */ + mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1); + break; + case MFC_SET_OP_FREQ: + pr_info("%s: set tx op freq\n", __func__); + cmd = WPC_COM_OP_FREQ_SET; + data_val[0] = 0x69; /* Value for OP FREQ 120.5kHz */ + mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1); + break; + case MFC_TX_UNO_OFF: + pr_info("%s: UNO off TX\n", __func__); + cmd = WPC_COM_DISABLE_TX; + data_val[0] = 0xFF; /* Value for Uno off */ + mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1); + break; + case MFC_REQ_TX_PWR_BUDG: + pr_info("%s: request PWR BUDG\n", __func__); + cmd = WPC_COM_REQ_PWR_BUDG; + data_val[0] = 0x00; + mfc_send_packet(charger, MFC_HEADER_AFC_CONF, cmd, data_val, 1); + break; + default: + break; + } +} + +static void mfc_send_fsk(struct mfc_charger_data *charger, u8 tx_data_com, u8 data_val) +{ + int i; + + for (i = 0; i < 3; i++) { + mfc_reg_write(charger->client, MFC_WPC_TX_DATA_COM_REG, tx_data_com); + mfc_reg_write(charger->client, MFC_WPC_TX_DATA_VALUE0_REG, data_val); + mfc_set_cmd_l_reg(charger, MFC_CMD_SEND_TRX_DATA_MASK, MFC_CMD_SEND_TRX_DATA_MASK); + msleep(250); + } +} + +static void mfc_led_control(struct mfc_charger_data *charger, int state) +{ + int i = 0; + + for (i = 0; i < CMD_CNT; i++) + mfc_send_command(charger, state); +} + +static void mfc_fan_control(struct mfc_charger_data *charger, bool on) +{ + int i = 0; + + if (on) { + for (i = 0; i < CMD_CNT - 1; i++) + mfc_send_command(charger, MFC_FAN_CONTROL_ON); + } else { + for (i = 0; i < CMD_CNT - 1; i++) + mfc_send_command(charger, MFC_FAN_CONTROL_OFF); + } +} + +// need to get the value for the each case +static void mfc_set_vrect_adjust(struct mfc_charger_data *charger, int set) +{ + if (charger->vout_mode == charger->pdata->wpc_vout_ctrl_full) + return; + + pr_info("%s Enter, %d\n", __func__, set); + + switch (set) { + case MFC_HEADROOM_0: + mfc_reg_write(charger->client, MFC_VRECT_ADJ_REG, 0x0); + break; + case MFC_HEADROOM_1: + mfc_reg_write(charger->client, MFC_VRECT_ADJ_REG, 0x23); // 273mV + break; + case MFC_HEADROOM_2: + mfc_reg_write(charger->client, MFC_VRECT_ADJ_REG, 0x40); // 500mV + break; + case MFC_HEADROOM_3: + mfc_reg_write(charger->client, MFC_VRECT_ADJ_REG, 0x53); // 648mV + break; + case MFC_HEADROOM_4: + mfc_reg_write(charger->client, MFC_VRECT_ADJ_REG, 0x04); // 31mV + break; + case MFC_HEADROOM_5: + mfc_reg_write(charger->client, MFC_VRECT_ADJ_REG, 0x0A); // 78mV + break; + case MFC_HEADROOM_6: + mfc_reg_write(charger->client, MFC_VRECT_ADJ_REG, 0x0C); // 93mV + break; + case MFC_HEADROOM_7: + mfc_reg_write(charger->client, MFC_VRECT_ADJ_REG, 0xB3); // -601mV + break; + default: + pr_info("%s no headroom mode\n", __func__); + break; + } +} + +static void mfc_set_vout_ctrl_1st_full(struct mfc_charger_data *charger) +{ + if (!volt_ctrl_pad(charger->tx_id)) + return; + + charger->vout_mode = WIRELESS_VOUT_CC_CV_VOUT; + cancel_delayed_work(&charger->wpc_vout_mode_work); + __pm_stay_awake(charger->wpc_vout_mode_ws); + queue_delayed_work(charger->wqueue, + &charger->wpc_vout_mode_work, msecs_to_jiffies(250)); +} + +static void mfc_set_vout_ctrl_2nd_full(struct mfc_charger_data *charger) +{ + if (!charger->pdata->wpc_vout_ctrl_full + || !volt_ctrl_pad(charger->tx_id)) + return; + + if (charger->pdata->wpc_headroom_ctrl_full) + mfc_set_vrect_adjust(charger, MFC_HEADROOM_7); + charger->vout_mode = charger->pdata->wpc_vout_ctrl_full; + cancel_delayed_work(&charger->wpc_vout_mode_work); + __pm_stay_awake(charger->wpc_vout_mode_ws); + queue_delayed_work(charger->wqueue, + &charger->wpc_vout_mode_work, msecs_to_jiffies(250)); + pr_info("%s: 2nd wireless charging done! vout set %s & headroom offset %dmV!\n", + __func__, sb_vout_ctr_mode_str(charger->vout_mode), + charger->pdata->wpc_headroom_ctrl_full ? -600 : 0); +} + +static void mfc_set_cep_timeout(struct mfc_charger_data *charger, u32 timeout) +{ + if (timeout) { + pr_info("%s: timeout = %dmsec\n", __func__, timeout); + timeout = timeout/100; + mfc_reg_write(charger->client, MFC_CEP_TIME_OUT_REG, timeout); + } +} + +/* these pads can control fans for sleep/auto mode */ +static bool is_sleep_mode_active(int pad_id) +{ + /* standard fw, hv pad, no multiport pad, non hv pad and PG950 pad */ + if (fan_ctrl_pad(pad_id)) { + pr_info("%s: this pad can control fan for auto mode\n", __func__); + return 1; + } + + pr_info("%s: this pad cannot control fan\n", __func__); + return 0; +} + +static void mfc_mis_align(struct mfc_charger_data *charger) +{ + if (charger->wc_checking_align) { + pr_info("%s: skip Reset M0\n", __func__); + return; + } + + pr_info("%s: Reset M0\n", __func__); + /* reset MCU of MFC IC */ + mfc_set_cmd_l_reg(charger, MFC_CMD_MCU_RESET_MASK, MFC_CMD_MCU_RESET_MASK); +} + +static bool mfc_tx_function_check(struct mfc_charger_data *charger) +{ + u8 reg_f2, reg_f4; + + mfc_reg_read(charger->client, MFC_TX_RXID1_READ_REG, ®_f2); + mfc_reg_read(charger->client, MFC_TX_RXID3_READ_REG, ®_f4); + + pr_info("@Tx_Mode %s: 0x%x 0x%x\n", __func__, reg_f2, reg_f4); + + if ((reg_f2 == SS_ID) && (reg_f4 == SS_CODE)) + return true; + else + return false; +} + +static void mfc_set_tx_ioffset(struct mfc_charger_data *charger, int ioffset) +{ + u8 ioffset_data_l = 0, ioffset_data_h = 0; + + ioffset_data_l = 0xFF & ioffset; + ioffset_data_h = 0xFF & (ioffset >> 8); + + mfc_reg_write(charger->client, MFC_TX_IUNO_OFFSET_L_REG, ioffset_data_l); + mfc_reg_write(charger->client, MFC_TX_IUNO_OFFSET_H_REG, ioffset_data_h); + + pr_info("@Tx_Mode %s: Tx Iout set %d(0x%x 0x%x)\n", __func__, ioffset, ioffset_data_h, ioffset_data_l); +} + +static void mfc_set_tx_iout(struct mfc_charger_data *charger, unsigned int iout) +{ + u8 iout_data_l = 0, iout_data_h = 0; + + iout_data_l = 0xFF & iout; + iout_data_h = 0xFF & (iout >> 8); + + mfc_reg_write(charger->client, MFC_TX_IUNO_LIMIT_L_REG, iout_data_l); + mfc_reg_write(charger->client, MFC_TX_IUNO_LIMIT_H_REG, iout_data_h); + + pr_info("@Tx_Mode %s: Tx Iout set %d(0x%x 0x%x)\n", __func__, iout, iout_data_h, iout_data_l); + + if (iout == 1100) + mfc_set_tx_ioffset(charger, 100); + else + mfc_set_tx_ioffset(charger, 150); +} + +static void mfc_test_read(struct mfc_charger_data *charger) +{ + u8 reg_data; + + if (!charger->wc_tx_enable) + return; + + if (mfc_reg_read(charger->client, MFC_STARTUP_EPT_COUNTER, ®_data) <= 0) + return; + + pr_info("%s: [AOV] ept-counter = %d\n", __func__, reg_data); +} + +static void mfc_set_tx_vout(struct mfc_charger_data *charger, unsigned int vout) +{ + unsigned int vout_val; + u8 vout_data_l = 0, vout_data_h = 0; + + if (vout < WC_TX_VOUT_MIN) { + pr_err("%s: vout(%d) is lower than min\n", __func__, vout); + vout = WC_TX_VOUT_MIN; + } else if (vout > WC_TX_VOUT_MAX) { + pr_err("%s: vout(%d) is higher than max\n", __func__, vout); + vout = WC_TX_VOUT_MAX; + } + + mfc_test_read(charger); + + vout_val = (u16)((vout-3500)/100); + vout_data_l = 0xFF & vout_val; + vout_data_h = 0xFF & (vout_val >> 8); + + mfc_reg_write(charger->client, MFC_VOUT_SET_L_REG, vout_data_l); + //mfc_reg_write(charger->client, MFC_VOUT_SET_H_REG, vout_data_h); + + pr_info("@Tx_Mode %s: Tx Vout set %d(0x%x 0x%x)\n", __func__, vout, vout_data_h, vout_data_l); +} + +static void mfc_print_buffer(struct mfc_charger_data *charger, u8 *buffer, int size) +{ + int start_idx = 0; + + do { + char temp_buf[1024] = {0, }; + int size_temp = 0, str_len = 1024; + int old_idx = start_idx; + + size_temp = ((start_idx + 0x7F) > size) ? size : (start_idx + 0x7F); + for (; start_idx < size_temp; start_idx++) { + snprintf(temp_buf + strlen(temp_buf), str_len, "0x%02x ", buffer[start_idx]); + str_len = 1024 - strlen(temp_buf); + } + pr_info("%s: (%04d ~ %04d) %s\n", __func__, old_idx, start_idx - 1, temp_buf); + } while (start_idx < size); +} + +static int mfc_set_psy_wrl(struct mfc_charger_data *charger, int prop, int data) +{ + union power_supply_propval value = { data, }; + + return psy_do_property("wireless", set, prop, value); +} + +static void mfc_auth_send_adt_status(struct mfc_charger_data *charger, u8 adt_status) +{ + charger->adt_transfer_status = adt_status; + mfc_set_psy_wrl(charger, POWER_SUPPLY_EXT_PROP_WIRELESS_AUTH_ADT_STATUS, adt_status); +} + +static void mfc_set_online(struct mfc_charger_data *charger, int type) +{ + charger->pdata->cable_type = type; + mfc_set_psy_wrl(charger, POWER_SUPPLY_PROP_ONLINE, type); +} + +static void mfc_set_tx_phm(struct mfc_charger_data *charger, bool enable) +{ + charger->tx_device_phm = enable; + mfc_set_psy_wrl(charger, POWER_SUPPLY_EXT_PROP_GEAR_PHM_EVENT, enable); +} + +static void mfc_set_rx_power(struct mfc_charger_data *charger, unsigned int rx_power) +{ + if (rx_power > TX_RX_POWER_20W * 100000) + rx_power = TX_RX_POWER_12W * 100000; + + mfc_set_psy_wrl(charger, POWER_SUPPLY_EXT_PROP_WIRELESS_RX_POWER, rx_power); +} + +static int mfc_auth_adt_read(struct mfc_charger_data *charger, u8 *readData) +{ + int buffAddr = MFC_ADT_BUFFER_ADT_PARAM_REG; + int i = 0, size = 0; + int ret = 0; + u8 size_temp[2] = {0, 0}; + + ret = mfc_reg_multi_read(charger->client, MFC_ADT_BUFFER_ADT_TYPE_REG, size_temp, 2); + if (ret < 0) { + pr_err("%s: failed to read adt type (ret = %d)\n", __func__, ret); + return ret; + } + + adt_readSize = ((size_temp[0] & 0x07) << 8) | (size_temp[1]); + pr_info("%s %s: (size_temp = 0x%x, readSize = %d bytes)\n", + WC_AUTH_MSG, __func__, (size_temp[0] | size_temp[1]), adt_readSize); + if (adt_readSize <= 0) { + pr_err("%s %s: failed to read adt data size\n", WC_AUTH_MSG, __func__); + return -EINVAL; + } + + if(auth_read_data_step == 0) { + total_adt_dataSize = adt_readSize; + pr_err("%s %s: total_adt_dataSize = %d \n", WC_AUTH_MSG, __func__, total_adt_dataSize); + } + + size = adt_readSize; + if (size > ADT_DATA_BUFFER_SIZE) {//84 + size = ADT_DATA_BUFFER_SIZE; + } + if ((buffAddr + size) - 1 > MFC_ADT_BUFFER_ADT_PARAM_MAX_REG) { + pr_err("%s %s: failed to read adt stream, too large data\n", WC_AUTH_MSG, __func__); + return -EINVAL; + } + + if (total_adt_dataSize <= 2) { + pr_err("%s %s: data from mcu has invalid size\n", WC_AUTH_MSG, __func__); + /* notify auth service to send TX PAD a request key */ + mfc_set_psy_wrl(charger, + POWER_SUPPLY_EXT_PROP_WIRELESS_AUTH_ADT_STATUS, WIRELESS_AUTH_FAIL); + return -EINVAL; + } + + while (size > SENDSZ) { + ret = mfc_reg_multi_read(charger->client, buffAddr, readData + i, SENDSZ); + if (ret < 0) { + pr_err("%s %s: failed to read adt stream (%d, buff %d)\n", + WC_AUTH_MSG, __func__, ret, buffAddr); + return ret; + } + i += SENDSZ; + buffAddr += SENDSZ; + size = size - SENDSZ; + pr_info("%s %s: 0x%x %d %d\n", WC_AUTH_MSG, __func__, i, buffAddr, size); + } + + if (size > 0) { + ret = mfc_reg_multi_read(charger->client, buffAddr, readData + i, size); + if (ret < 0) { + pr_err("%s %s: failed to read adt stream (%d, buff %d)\n", + WC_AUTH_MSG, __func__, ret, buffAddr); + return ret; + } + } + + if (adt_readSize > ADT_DATA_BUFFER_SIZE) { + mfc_print_buffer(charger, readData, ADT_DATA_BUFFER_SIZE); + } else { + mfc_print_buffer(charger, readData, adt_readSize); + mfc_auth_send_adt_status(charger, WIRELESS_AUTH_RECEIVED); + } + + pr_info("%s %s: succeeded to read ADT\n", WC_AUTH_MSG, __func__); + + return 0; +} + +static int mfc_auth_adt_write(struct mfc_charger_data *charger, u8 *srcData, int srcSize) +{ + int buffAddr = MFC_ADT_BUFFER_ADT_PARAM_REG;//0x12AA + u8 wdata = 0; + u8 sBuf[144] = {0,}; + int ret; + + pr_info("%s %s: start to write ADT\n", WC_AUTH_MSG, __func__); + + if ((buffAddr + srcSize) - 1 > MFC_ADT_BUFFER_ADT_PARAM_MAX_REG) {//0x12FF + pr_err("%s %s: failed to write adt stream, too large data.\n", WC_AUTH_MSG, __func__); + return -EINVAL; + } + + if (charger->rx_op_mode == PAD_RX_MODE_WPC_MPP_FULL) { + wdata = 0x20; /* MPP Authentication */ + } else if (charger->rx_op_mode == PAD_RX_MODE_WPC_EPP) { + if (is_samsung_pad((charger->mpp_epp_tx_id & 0xFF))) { + wdata = 0x08; /* Authentication purpose */ + } else { + wdata = 0x10; /* EPP Authentication */ + } + } else if (charger->rx_op_mode == PAD_RX_MODE_WPC_BPP) { + wdata = 0x08; /* Authentication purpose */ + } else { + pr_info("%s %s: error Rx mode%d\n", WC_AUTH_MSG, __func__, charger->rx_op_mode); + return -EINVAL; + } + + pr_info("%s %s: wdata(0x%x)\n", WC_AUTH_MSG, __func__, wdata); + wdata = wdata | ((srcSize >> 8) & 0x07); + mfc_reg_write(charger->client, MFC_ADT_BUFFER_ADT_TYPE_REG, wdata); + pr_info("%s %s: ADT_TYPE:0x%x \n", WC_AUTH_MSG, __func__,wdata); + + wdata = srcSize; /* need to check */ + mfc_reg_write(charger->client, MFC_ADT_BUFFER_ADT_MSG_SIZE_REG, wdata); + pr_info("%s %s: srcSize:0x%x \n", WC_AUTH_MSG, __func__,wdata); + + buffAddr = MFC_ADT_BUFFER_ADT_PARAM_REG; + + if (srcSize < 85) {//ADT_MAX_DATA_LENGTH + memcpy(sBuf, srcData, srcSize); + ret = mfc_reg_multi_write_verify(charger->client, buffAddr, sBuf, srcSize); + if (ret < 0) { + pr_err("%s %s: failed to write adt stream (%d, buff %d)\n", + WC_AUTH_MSG, __func__, ret, buffAddr); + return ret; + } + } else + pr_info("%s %s: srcSize:%d \n", WC_AUTH_MSG, __func__,srcSize); + + pr_info("%s %s: succeeded to write ADT\n", WC_AUTH_MSG, __func__); + return 0; +} + +static void mfc_auth_adt_send(struct mfc_charger_data *charger, u8 *srcData, int srcSize) +{ + u8 temp; + int ret = 0; + + charger->adt_transfer_status = WIRELESS_AUTH_SENT; + ret = mfc_auth_adt_write(charger, srcData, srcSize); /* write buff fw datas to send fw datas to tx */ + + mfc_reg_read(charger->client, MFC_AP2MFC_CMD_H_REG, &temp); // check out set bit exists + pr_info("%s %s: MFC_CMD_H_REG(0x%x)\n", WC_AUTH_MSG, __func__, temp); + temp |= 0x02; + pr_info("%s %s: MFC_CMD_H_REG(0x%x)\n", WC_AUTH_MSG, __func__, temp); + mfc_reg_write(charger->client, MFC_AP2MFC_CMD_H_REG, temp); + + mfc_reg_read(charger->client, MFC_AP2MFC_CMD_H_REG, &temp); // check out set bit exists + pr_info("%s %s: MFC_CMD_H_REG(0x%x)\n", WC_AUTH_MSG, __func__, temp); + + mfc_reg_update(charger->client, MFC_INT_A_ENABLE_H_REG, + MFC_STAT_H_ADT_SENT_MASK, MFC_STAT_H_ADT_SENT_MASK); +} + +#define AUTH_READY 0 +#define AUTH_COMPLETE 1 +#define AUTH_OPFREQ 140 +static void mfc_auth_set_configs(struct mfc_charger_data *charger, int opt) +{ +// u8 data = 0; + + if (opt == AUTH_READY) { + int op_freq = mfc_get_adc(charger, MFC_ADC_OP_FRQ); + + pr_info("%s: op_freq(%d)\n", __func__, op_freq); +// mfc_cmfet_set_auth(charger->cmfet, true); + if ((charger->tx_id == TX_ID_P5200_PAD) && (op_freq >= AUTH_OPFREQ)) { +// mfc_reg_write(charger->client, MFC_RECT_MODE_AP_CTRL, 0x01); +// mfc_reg_update(charger->client, MFC_RECTMODE_REG, 0x40, 0xC0); +// mfc_reg_read(charger->client, MFC_RECTMODE_REG, &data); + } + } else if (opt == AUTH_COMPLETE) { + if (charger->tx_id == TX_ID_P5200_PAD) { +// mfc_reg_write(charger->client, MFC_RECT_MODE_AP_CTRL, 0x00); +// mfc_reg_read(charger->client, MFC_RECTMODE_REG, &data); + } +// mfc_cmfet_set_auth(charger->cmfet, false); + } else { + pr_info("%s: undefined cmd(%d)\n", __func__, opt); + } +} + +/* uno on/off control function */ +static void mfc_set_tx_power(struct mfc_charger_data *charger, bool on) +{ + union power_supply_propval value = {0, }; + + charger->wc_tx_enable = on; + + if (on) { + u32 tx_max_op_freq = charger->pdata->tx_max_op_freq, + tx_min_op_freq = charger->pdata->tx_min_op_freq; + + pr_info("@Tx_Mode %s: Turn On TX Power\n", __func__); + mfc_set_coil_sw_en(charger, 1); + mfc_uno_on(charger, true); + msleep(200); + + charger->pdata->otp_firmware_ver = mfc_get_firmware_version(charger, MFC_RX_FIRMWARE); + charger->wc_rx_fod = false; + if (delayed_work_pending(&charger->wpc_tx_duty_min_work)) { + __pm_relax(charger->wpc_tx_duty_min_ws); + cancel_delayed_work(&charger->wpc_tx_duty_min_work); + } + + /* Set TX OP Freq (MAX/MIN) */ + if (tx_max_op_freq > 0) + mfc_set_tx_max_op_freq(charger, tx_max_op_freq); + if (tx_min_op_freq > 0) + mfc_set_tx_min_op_freq(charger, tx_min_op_freq); + } else { + pr_info("@Tx_Mode %s: Turn Off TX Power, and reset UNO config\n", __func__); + + mfc_uno_on(charger, false); + mfc_set_coil_sw_en(charger, 0); + + value.intval = WC_TX_VOUT_5000MV; + psy_do_property("otg", set, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_VOUT, value); + + charger->wc_rx_connected = false; + charger->wc_rx_type = NO_DEV; + charger->duty_min = MIN_DUTY_SETTING_20_DATA; + charger->tx_device_phm = 0; + + alarm_cancel(&charger->phm_alarm); + if (delayed_work_pending(&charger->wpc_rx_type_det_work)) { + cancel_delayed_work(&charger->wpc_rx_type_det_work); + __pm_relax(charger->wpc_rx_det_ws); + } + if (delayed_work_pending(&charger->wpc_rx_connection_work)) + cancel_delayed_work(&charger->wpc_rx_connection_work); + if (delayed_work_pending(&charger->wpc_tx_isr_work)) { + cancel_delayed_work(&charger->wpc_tx_isr_work); + __pm_relax(charger->wpc_tx_ws); + } + if (delayed_work_pending(&charger->wpc_tx_phm_work)) { + cancel_delayed_work(&charger->wpc_tx_phm_work); + __pm_relax(charger->wpc_tx_phm_ws); + } + if (delayed_work_pending(&charger->mode_change_work)) { + cancel_delayed_work(&charger->mode_change_work); + __pm_relax(charger->mode_change_ws); + } + + charger->tx_status = SEC_TX_OFF; + } +} + +/* determine rx connection status with tx sharing mode */ +static void mfc_wpc_rx_connection_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, wpc_rx_connection_work.work); + u8 data = 0; + + if (!charger->wc_tx_enable) { + pr_info("@Tx_Mode %s : wc_tx_enable(%d)\n", __func__, charger->wc_tx_enable); + charger->wc_rx_connected = false; + return; + } + + if (charger->wc_rx_connected) { + pr_info("@Tx_Mode %s: Rx(%s) connected\n", + __func__, mfc_tx_function_check(charger) ? "Samsung" : "Other"); + + mfc_reg_read(charger->client, MFC_TX_QAIR_L_REG, &data); + pr_info("@Tx_Mode %s: Qair_l(0x%x)\n", __func__, data); + mfc_reg_read(charger->client, MFC_TX_QAIR_H_REG, &data); + pr_info("@Tx_Mode %s: Qair_h(0x%x)\n", __func__, data); + mfc_reg_read(charger->client, MFC_TX_Q_L_REG, &data); + pr_info("@Tx_Mode %s: Q_l(0x%x)\n", __func__, data); + mfc_reg_read(charger->client, MFC_TX_Q_H_REG, &data); + pr_info("@Tx_Mode %s: Q_h(0x%x)\n", __func__, data); + + cancel_delayed_work(&charger->wpc_rx_type_det_work); + __pm_stay_awake(charger->wpc_rx_det_ws); + queue_delayed_work(charger->wqueue, &charger->wpc_rx_type_det_work, msecs_to_jiffies(1000)); + + __pm_stay_awake(charger->wpc_tx_duty_min_ws); + queue_delayed_work(charger->wqueue, &charger->wpc_tx_duty_min_work, msecs_to_jiffies(5000)); + + pr_info("%s: tx op freq = %dKhz\n", __func__, mfc_get_adc(charger, MFC_ADC_TX_MAX_OP_FRQ)); + charger->duty_min = MIN_DUTY_SETTING_30_DATA; + mfc_set_min_duty(charger, MIN_DUTY_SETTING_30_DATA); + } else { + pr_info("@Tx_Mode %s: Rx disconnected\n", __func__); + + mfc_set_coil_sw_en(charger, 1); + charger->wc_rx_fod = false; + charger->wc_rx_type = NO_DEV; + charger->tx_device_phm = 0; + + if (delayed_work_pending(&charger->wpc_rx_type_det_work)) { + __pm_relax(charger->wpc_rx_det_ws); + cancel_delayed_work(&charger->wpc_rx_type_det_work); + } + + if (delayed_work_pending(&charger->wpc_tx_duty_min_work)) { + __pm_relax(charger->wpc_tx_duty_min_ws); + cancel_delayed_work(&charger->wpc_tx_duty_min_work); + } + } + + /* set rx connection condition */ + mfc_set_psy_wrl(charger, POWER_SUPPLY_EXT_PROP_WIRELESS_RX_CONNECTED, charger->wc_rx_connected); +} + +/* + * determine rx connection status with tx sharing mode, + * this TX device has MFC_INTA_H_TRX_DATA_RECEIVED_MASK irq and RX device is connected HV wired cable + */ +static void mfc_tx_handle_rx_packet(struct mfc_charger_data *charger) +{ + u8 cmd_data = 0, val_data = 0, val2_data = 0; + union power_supply_propval value = {0, }; + + mfc_reg_read(charger->client, MFC_WPC_TRX_DATA2_COM_REG, &cmd_data); + mfc_reg_read(charger->client, MFC_WPC_TRX_DATA2_VALUE0_REG, &val_data); + mfc_reg_read(charger->client, MFC_WPC_TRX_DATA2_VALUE1_REG, &val2_data); + charger->pdata->trx_data_cmd = cmd_data; + charger->pdata->trx_data_val = val_data; + + pr_info("@Tx_Mode %s: CMD : 0x%x, DATA : 0x%x, DATA2 : 0x%x\n", + __func__, cmd_data, val_data, val2_data); + + /* When RX device has got a AFC TA, this TX device should turn off TX power sharing(uno) */ + if (cmd_data == MFC_HEADER_AFC_CONF) { /* 0x28 */ + switch (val_data) { + case WPC_COM_DISABLE_TX: /* 0x19 Turn off UNO of TX */ + if (val2_data == RX_DATA_VAL2_MISALIGN) { + pr_info("@Tx_Mode %s: RX send TX off packet(Misalign or darkzone)\n", __func__); + value.intval = BATT_TX_EVENT_WIRELESS_TX_MISALIGN; + psy_do_property("wireless", set, POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ERR, value); + } else if (val2_data == RX_DATA_VAL2_TA_CONNECT_DURING_WC) { + pr_info("@Tx_Mode %s: TX dev should turn off TX(uno), RX dev has AFC TA\n", __func__); + value.intval = BATT_TX_EVENT_WIRELESS_RX_CHG_SWITCH; + psy_do_property("wireless", set, POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ERR, value); + } + break; + case WPC_COM_ENTER_PHM: /* 0x18 Received PHM, GEAR sent PHM packet to TX */ + if (val2_data == RX_DATA_VAL2_ENABLE) + pr_info("@Tx_Mode %s: Received PHM\n", __func__); + break; + case WPC_COM_RX_ID: /* 0x0E Received RX ID */ + if (val2_data >= RX_DATA_VAL2_RXID_ACC_BUDS && val2_data <= RX_DATA_VAL2_RXID_ACC_BUDS_MAX) { + charger->wc_rx_type = SS_BUDS; + pr_info("@Tx_Mode %s: Buds connected\n", __func__); + mfc_set_tx_fod_thresh1(charger->client, charger->pdata->buds_fod_thresh1); + mfc_set_tx_fod_ta_thresh(charger->client, charger->pdata->buds_fod_ta_thresh); + value.intval = charger->wc_rx_type; + psy_do_property("wireless", set, + POWER_SUPPLY_EXT_PROP_WIRELESS_RX_TYPE, value); + } + break; + default: + pr_info("@Tx_Mode %s: unsupport : 0x%x", __func__, val_data); + break; + } + } else if (cmd_data == MFC_HEADER_END_CHARGE_STATUS) { + if (val_data == MFC_ECS_CS100) { /* CS 100 */ + pr_info("@Tx_Mode %s: CS100 Received, TX power off\n", __func__); + value.intval = BATT_TX_EVENT_WIRELESS_RX_CS100; + psy_do_property("wireless", set, POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ERR, value); + } + } else if (cmd_data == MFC_HEADER_END_POWER_TRANSFER) { + if (val_data == MFC_EPT_CODE_OVER_TEMPERATURE) { + pr_info("@Tx_Mode %s: EPT-OT Received, TX power off\n", __func__); + value.intval = BATT_TX_EVENT_WIRELESS_RX_UNSAFE_TEMP; + psy_do_property("wireless", set, POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ERR, value); + } else if (val_data == MFC_EPT_CODE_BATTERY_FAILURE) { + if (charger->wc_rx_type != SS_GEAR) { + pr_info("@Tx_Mode %s: EPT06 Received, TX power off and retry\n", __func__); + value.intval = BATT_TX_EVENT_WIRELESS_TX_RETRY; + psy_do_property("wireless", set, POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ERR, value); + } + } + } else if (cmd_data == MFC_HEADER_PROPRIETARY_1_BYTE) { + if (val_data == WPC_COM_WDT_ERR) { + pr_info("@Tx_Mode %s: WDT Error Received, TX power off\n", __func__); + value.intval = BATT_TX_EVENT_WIRELESS_TX_ETC; + psy_do_property("wireless", set, POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ERR, value); + } + } +} + +static int datacmp(const char *cs, const char *ct, int count) +{ + unsigned char c1, c2; + + while (count) { + c1 = *cs++; + c2 = *ct++; + if (c1 != c2) { + pr_err("%s: cnt %d\n", __func__, count); + return c1 < c2 ? -1 : 1; + } + count--; + } + return 0; +} + +static int mfc_reg_multi_write_verify(struct i2c_client *client, u16 reg, const u8 *val, int size) +{ + int ret = 0; + int cnt = 0; + int retry_cnt = 0; + unsigned char data[SENDSZ + 2]; + u8 rdata[SENDSZ + 2]; + struct mfc_charger_data *charger = i2c_get_clientdata(client); + + if (charger->reg_access_lock) { + pr_err("%s: can not access to reg during fw update\n", __func__); + return -1; + } + + while (size > SENDSZ) { + data[0] = (reg + cnt) >> 8; + data[1] = (reg + cnt) & 0xFF; + memcpy(data + 2, val + cnt, SENDSZ); +// dev_dbg(&client->dev, "%s: addr: 0x%x, cnt: 0x%x\n", __func__, reg + cnt, cnt); + ret = i2c_master_send(client, data, SENDSZ + 2); + if (ret < SENDSZ + 2) { + pr_err("%s: i2c write error, reg: 0x%x\n", __func__, reg); + return ret < 0 ? ret : -EIO; + } + if (mfc_reg_multi_read(client, reg + cnt, rdata, SENDSZ) < 0) { + pr_err("%s: read failed(%d)\n", __func__, reg + cnt); + return -1; + } + if (datacmp(val + cnt, rdata, SENDSZ)) { + pr_err("%s: data is not matched. retry(%d)\n", __func__, retry_cnt); + retry_cnt++; + if (retry_cnt > 4) { + pr_err("%s: data is not matched. write failed\n", __func__); + retry_cnt = 0; + return -1; + } + continue; + } +// pr_debug("%s: data is matched!\n", __func__); + cnt += SENDSZ; + size -= SENDSZ; + retry_cnt = 0; + } + while (size > 0) { + data[0] = (reg + cnt) >> 8; + data[1] = (reg + cnt) & 0xFF; + memcpy(data + 2, val + cnt, size); +// dev_dbg(&client->dev, "%s: addr: 0x%x, cnt: 0x%x, size: 0x%x\n", __func__, reg + cnt, cnt, size); + ret = i2c_master_send(client, data, size + 2); + if (ret < size + 2) { + pr_err("%s: i2c write error, reg: 0x%x\n", __func__, reg); + return ret < 0 ? ret : -EIO; + } + if (mfc_reg_multi_read(client, reg + cnt, rdata, size) < 0) { + pr_err("%s: read failed(%d)\n", __func__, reg + cnt); + return -1; + } + if (datacmp(val + cnt, rdata, size)) { + pr_err("%s: data is not matched. retry(%d)\n", __func__, retry_cnt); + retry_cnt++; + if (retry_cnt > 4) { + pr_err("%s: data is not matched. write failed\n", __func__); + retry_cnt = 0; + return -1; + } + continue; + } + size = 0; + pr_err("%s: data is matched!\n", __func__); + } + return ret; +} + +#if 0 +static int mfc_reg_multi_write(struct i2c_client *client, u16 reg, const u8 *val, int size) +{ + struct mfc_charger_data *charger = i2c_get_clientdata(client); + int ret = 0; + unsigned char data[SENDSZ + 2]; + int cnt = 0; + + pr_err("%s: size: 0x%x\n", __func__, size); + while (size > SENDSZ) { + data[0] = (reg + cnt) >> 8; + data[1] = (reg + cnt) & 0xff; + memcpy(data + 2, val + cnt, SENDSZ); + mutex_lock(&charger->io_lock); + ret = i2c_master_send(client, data, SENDSZ + 2); + mutex_unlock(&charger->io_lock); + if (ret < SENDSZ + 2) { + pr_err("%s: i2c write error, reg: 0x%x\n", __func__, reg); + return ret < 0 ? ret : -EIO; + } + cnt = cnt + SENDSZ; + size = size - SENDSZ; + } + if (size > 0) { + data[0] = (reg + cnt) >> 8; + data[1] = (reg + cnt) & 0xff; + memcpy(data + 2, val + cnt, size); + mutex_lock(&charger->io_lock); + ret = i2c_master_send(client, data, size + 2); + mutex_unlock(&charger->io_lock); + if (ret < size + 2) { + pr_info("%s: i2c write error, reg: 0x%x\n", __func__, reg); + return ret < 0 ? ret : -EIO; + } + } + + return ret; +} +#endif + + +/*************update FW**************/ +static int mfc_nu1668_reg_read(struct i2c_client *client, u16 reg, u8 *val) +{ + struct mfc_charger_data *charger = i2c_get_clientdata(client); + int ret; + struct i2c_msg msg[2]; + u8 wbuf[2]; + u8 rbuf[2]; + + msg[0].addr = client->addr; + msg[0].flags = client->flags & I2C_M_TEN; + msg[0].len = 2; + msg[0].buf = wbuf; + + wbuf[0] = (reg & 0xFF00) >> 8; + wbuf[1] = (reg & 0xFF); + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = 1; + msg[1].buf = rbuf; + + mutex_lock(&charger->io_lock); + ret = i2c_transfer(client->adapter, msg, 2); + //if don't have IIC detect pin, then need to remove below code, don't detect IIC error. + mfc_check_i2c_error(charger, (ret < 0)); + mutex_unlock(&charger->io_lock); + if (ret < 0) { + pr_err("%s: i2c read error, reg: 0x%x, ret: %d (called by %ps)\n", + __func__, reg, ret, __builtin_return_address(0)); + return -1; + } + + *val = rbuf[0]; + + return ret; +} + +static int mfc_nu1668_reg_multi_write(struct i2c_client *client, u16 reg, const u8 *val, int size) +{ + int ret = 0; + unsigned char data[10]; + struct mfc_charger_data *charger = i2c_get_clientdata(client); + + data[0] = reg >> 8; + data[1] = reg & 0xFF; + memcpy(data + 2, val, size); +// dev_dbg(&client->dev, "%s: addr: 0x%x, size: 0x%x\n", __func__, reg, size); + mutex_lock(&charger->io_lock); + ret = i2c_master_send(client, data, (size + 2)); + mfc_check_i2c_error(charger, (ret < (size + 2))); + mutex_unlock(&charger->io_lock); + if (ret < (size + 2)) { + pr_err("%s: i2c write error, reg: 0x%x\n", __func__, reg); + return ret < 0 ? ret : -EIO; + } + + return ret; +} + +static int nu1668_mtp_unlock(struct mfc_charger_data *charger) +{ + int ret; + + pr_info("[nu1668] [%s]\n", __func__); + + ret = mfc_reg_write(charger->client, NU1668_KEY_OPEN_REG, 0x00); + if (ret < 0) { + return -EINVAL; + } + + ret = mfc_reg_write(charger->client, NU1668_KEY_OPEN_REG, 0x2d); + if (ret < 0) { + return -EINVAL; + } + + ret = mfc_reg_write(charger->client, NU1668_KEY_OPEN_REG, 0xd2); + if (ret < 0) { + return -EINVAL; + } + + ret = mfc_reg_write(charger->client, NU1668_KEY_OPEN_REG, 0x22); + if (ret < 0) { + return -EINVAL; + } + + ret = mfc_reg_write(charger->client, NU1668_KEY_OPEN_REG, 0xdd); + if (ret < 0) { + return -EINVAL; + } + + ret = mfc_reg_write(charger->client, NU1668_KEY0_REG, 0x00); + if (ret < 0) { + return -EINVAL; + } + + ret = mfc_reg_write(charger->client, NU1668_KEY1_REG, 0x00); + if (ret < 0) { + return -EINVAL; + } + + return ret; +} + + +static int nu1668_mtp_enter_test_mode(struct mfc_charger_data *charger) +{ + int ret; + + pr_info("[nu1668] [%s]\n", __func__); + + ret = mfc_reg_write(charger->client, NU1668_SP_CTRL0_REG, 0x41); + if (ret < 0) { + return -EINVAL; + } + + return ret; +} + +static int nu1668_init_mtp_read_function(struct mfc_charger_data *charger) +{ + int ret; + + pr_info("[nu1668] [%s]\n", __func__); + + ret = mfc_reg_write(charger->client, NU1668_TEST_MODE_CTRL_0_REG, 0x80); + if (ret < 0) { + return -EINVAL; + } + + ret = mfc_reg_write(charger->client, NU1668_MTP_SECTOR_REG, 0xff); + if (ret < 0) { + return -EINVAL; + } + + ret = mfc_reg_write(charger->client, NU1668_MTP_ADDR_0_REG, 0x1F); + if (ret < 0) { + return -EINVAL; + } + + ret = mfc_reg_write(charger->client, NU1668_MTP_ADDR_1_REG, 0xfe); + if (ret < 0) { + return -EINVAL; + } + + ret = mfc_reg_write(charger->client, NU1668_MTP_CTRL_0_REG, 0x0c); + if (ret < 0) { + return -EINVAL; + } + + ret = mfc_reg_write(charger->client, NU1668_MTP_CTRL_OP_REG, 0xa5); + if (ret < 0) { + return -EINVAL; + } + + return ret; +} + + +static int nu1668_mtp_read_end(struct mfc_charger_data *charger) +{ + int ret; + + pr_info("[nu1668] [%s]\n", __func__); + + ret = mfc_reg_write(charger->client, NU1668_MTP_CTRL_OP_REG, 0x00); + if (ret < 0) { + return -EINVAL; + } + + ret = mfc_reg_write(charger->client, NU1668_TM_EN_ANA_REG, 0x00); + if (ret < 0) { + return -EINVAL; + } + + ret = mfc_reg_write(charger->client, NU1668_KEY_OPEN_REG, 0x00); + if (ret < 0) { + return -EINVAL; + } + + return ret; +} + + +static int nu1668_mtp_disable_mcu(struct mfc_charger_data *charger) +{ + int ret; + + pr_info("[nu1668] [%s]\n", __func__); + + ret = mfc_reg_write(charger->client, NU1668_TEST_MODE_CTRL_0_REG, 0x90); + if (ret < 0) + return -EINVAL; + + ret = mfc_reg_write(charger->client, NU1668_GEN_OPT0_BYTE2_REG, 0x10); + if (ret < 0) + return -EINVAL; + + return ret; +} + +static int nu1668_write_mtp_addr(struct mfc_charger_data *charger) +{ + int ret; + + pr_info("[nu1668] [%s] enter\n", __func__); + + ret = mfc_reg_write(charger->client, NU1668_MTP_SECTOR_REG, 0xff); + if (ret < 0) + return -EINVAL; + + return ret; +} + + +static int nu1668_mtp_write_end(struct mfc_charger_data *charger) +{ + int ret; + + pr_info("[nu1668] [%s] enter\n", __func__); + + ret = mfc_reg_write(charger->client, NU1668_MTP_CTRL_OP_REG, 0x00); + if (ret < 0) + return -EINVAL; + + ret = mfc_reg_write(charger->client, NU1668_GENERAL_REG_000F, 0x5A); + if (ret < 0) + return -EINVAL; + + ret = mfc_reg_write(charger->client, NU1668_TEST_MODE_CTRL_0_REG, 0x00); + if (ret < 0) + return -EINVAL; + + ret = mfc_reg_write(charger->client, NU1668_TEST_MODE_VECTOR_1_REG, 0x20); + if (ret < 0) + return -EINVAL; + + usleep_range(10000, 11000); + + ret = mfc_reg_write(charger->client, NU1668_TEST_MODE_VECTOR_1_REG, 0x00); + if (ret < 0) + return -EINVAL; + + ret = mfc_reg_write(charger->client, NU1668_TM_EN_ANA_REG, 0x00); + if (ret < 0) + return -EINVAL; + + + ret = mfc_reg_write(charger->client, NU1668_KEY0_REG, 0x00); + if (ret < 0) + return -EINVAL; + + + ret = mfc_reg_write(charger->client, NU1668_KEY1_REG, 0x00); + if (ret < 0) + return -EINVAL; + + ret = mfc_reg_write(charger->client, NU1668_KEY_OPEN_REG, 0x00); + if (ret < 0) + return -EINVAL; + + return ret; +} + + +static int nu1668_mtp_init(struct mfc_charger_data *charger, const u8 *data) +{ + int ret; + + pr_info("[nu1668] [%s] enter: 0x%x, 0x%x, 0x%x, 0x%x\n", __func__, + 0xff&(~data[3]), 0xff&(~data[2]), 0xff&(~data[1]), 0xff&(~data[0])); + + /* initialize */ + ret = mfc_reg_write(charger->client, NU1668_MTP_CTRL_0_REG, 0x01); + if (ret < 0) + return -EINVAL; + + ret = mfc_reg_write(charger->client, NU1668_MTP_WDATA_0_REG, (0xff&(~data[3]))); + if (ret < 0) + return -EINVAL; + + ret = mfc_reg_write(charger->client, NU1668_MTP_WDATA_1_REG, (0xff&(~data[2]))); + if (ret < 0) + return -EINVAL; + + ret = mfc_reg_write(charger->client, NU1668_MTP_WDATA_2_REG, (0xff&(~data[1]))); + if (ret < 0) + return -EINVAL; + + ret = mfc_reg_write(charger->client, NU1668_MTP_WDATA_3_REG, (0xff&(~data[0]))); + if (ret < 0) + return -EINVAL; + + ret = mfc_reg_write(charger->client, NU1668_MTP_CTRL_2_REG, 0x01); + if (ret < 0) + return -EINVAL; + + ret = mfc_reg_write(charger->client, NU1668_MTP_CTRL_2_REG, 0x00); + if (ret < 0) + return -EINVAL; + + ret = mfc_reg_write(charger->client, NU1668_MTP_CTRL_OP_REG, 0x5a); + if (ret < 0) + return -EINVAL; + + return ret; +} + + +static int nu1668_mtp_write_data(struct mfc_charger_data *charger, const u8 *fw_data, int length) +{ + int ret; + u8 busy, wrfail; + int i = 0; + int j = 0; + u8 read_data = 0; + u8 data_length = 0; + u8 sBuf[4] = {0, }; + + /* write firmware data to mtp */ + for (i = 0; i < length; i += 4) { + if (i%4096 == 0) { //Each sector has 4090 bytes of data, and the delay between sectors is 20ms + msleep(20); + pr_info("[nu1668] i=%d\n", i); + } + + sBuf[0] = (0xff&(~fw_data[i+3])); + sBuf[1] = (0xff&(~fw_data[i+2])); + sBuf[2] = (0xff&(~fw_data[i+1])); + sBuf[3] = (0xff&(~fw_data[i+0])); + data_length = 4; + ret = mfc_nu1668_reg_multi_write(charger->client, NU1668_MTP_WDATA_0_REG, sBuf, data_length); + if (ret < 0) { + pr_err("%s: failed to multi_write (ret=%d, i=%d)\n", __func__, ret, i); + return -EINVAL; + } + + for (j = 0; j < 120; j++) {//the maximum delay time is 12ms (100us*120) + ret = mfc_nu1668_reg_read(charger->client, NU1668_MTP_STATUS_REG, &read_data); + if (ret < 0) + return -EINVAL; + + busy = (read_data & 0x80) >> 7; + if (busy == 0) { + wrfail = (read_data & 0x40) >> 6; + if (wrfail == 1) { + pr_info("[nu1668] j=%d.......wrfail..\n", j); + return -EINVAL; + } else { + break; + } + } + usleep_range(100, 110); + } + } /* write firmware data to mtp */ + + return ret; +} + +bool nu1668_read_fw_version_and_checksum(struct mfc_charger_data *charger, u8 *fw_info) +{ + int ret = 0; + u8 read_data[8] = {0, }; + + pr_info("[nu1668] [%s] enter\n", __func__); + + ret = nu1668_mtp_unlock(charger); + if (ret < 0) + return false; + + ret = nu1668_mtp_enter_test_mode(charger); + if (ret < 0) + return false; + + ret = nu1668_init_mtp_read_function(charger); + if (ret < 0) + return false; + + msleep(20); + + ret = mfc_reg_read(charger->client, NU1668_MTP_DATA_0_REG, &read_data[0]); + if (ret < 0) + return false; + + pr_info("[nu1668] [%s] read_data[0] = 0x%x\n", __func__, read_data[0]); + + ret = mfc_reg_read(charger->client, NU1668_MTP_DATA_1_REG, &read_data[1]); + if (ret < 0) + return false; + + pr_info("[nu1668] [%s] read_data[1] = 0x%x\n", __func__, read_data[1]); + + ret = mfc_reg_read(charger->client, NU1668_MTP_DATA_2_REG, &read_data[2]); + if (ret < 0) + return false; + + pr_info("[nu1668] [%s] read_data[2] = 0x%x\n", __func__, read_data[2]); + + ret = mfc_reg_read(charger->client, NU1668_MTP_DATA_3_REG, &read_data[3]); + if (ret < 0) + return false; + + pr_info("[nu1668] [%s] read_data[3] = 0x%x\n", __func__, read_data[3]); + + read_data[0] = 0xff & (~read_data[0]); + read_data[1] = 0xff & (~read_data[1]); + read_data[2] = 0xff & (~read_data[2]); + read_data[3] = 0xff & (~read_data[3]); + + fw_info[0] = read_data[3]; + fw_info[1] = read_data[2]; + fw_info[2] = read_data[1]; + fw_info[3] = read_data[0]; + pr_info("[nu1668] [%s] mtp_fw_ver--fw_info[0~3]: %x %x %x %x\n", + __func__, fw_info[0], fw_info[1], fw_info[2], fw_info[3]); + + msleep(20); + + ret = mfc_reg_read(charger->client, NU1668_MTP_DATA_0_REG, &read_data[4]); + if (ret < 0) + return false; + + pr_info("[nu1668] [%s] read_data[4] = 0x%x\n", __func__, read_data[4]); + + ret = mfc_reg_read(charger->client, NU1668_MTP_DATA_1_REG, &read_data[5]); + if (ret < 0) + return false; + + pr_info("[nu1668] [%s] read_data[5] = 0x%x\n", __func__, read_data[5]); + + ret = mfc_reg_read(charger->client, NU1668_MTP_DATA_2_REG, &read_data[6]); + if (ret < 0) + return false; + + pr_info("[nu1668] [%s] read_data[6] = 0x%x\n", __func__, read_data[6]); + + ret = mfc_reg_read(charger->client, NU1668_MTP_DATA_3_REG, &read_data[7]); + if (ret < 0) + return false; + + pr_info("[nu1668] [%s] read_data[7] = 0x%x\n", __func__, read_data[7]); + + read_data[4] = 0xff & (~read_data[4]); + read_data[5] = 0xff & (~read_data[5]); + read_data[6] = 0xff & (~read_data[6]); + read_data[7] = 0xff & (~read_data[7]); + + fw_info[4] = read_data[7]; + fw_info[5] = read_data[6]; + fw_info[6] = read_data[5]; + fw_info[7] = read_data[4]; + pr_info("[nu1668] [%s] mtp_checksum--fw_info[4-7]: %x %x %x %x\n", + __func__, fw_info[4], fw_info[5], fw_info[6], fw_info[7]); + + ret = nu1668_mtp_read_end(charger); + if (ret < 0) + return false; + + return true; +} + + +bool nu1668_download_fw_content(struct mfc_charger_data *charger, const u8 *wdata, int size) +{ + u32 fw_data_length = 0; + int ret; + + pr_info("[nu1668] [%s] enter\n", __func__); + + fw_data_length = size; + + pr_info("[nu1668] [%s] fw_data_length = %d\n", __func__, fw_data_length); + + ret = nu1668_mtp_unlock(charger); + if (ret < 0) + return false; + + + ret = nu1668_mtp_enter_test_mode(charger); + if (ret < 0) + return false; + + + ret = nu1668_mtp_disable_mcu(charger); + if (ret < 0) + return false; + + ret = nu1668_write_mtp_addr(charger); + if (ret < 0) + return false; + + msleep(20); + pr_info("[nu1668] [%s] delay 20ms\n", __func__); + + ret = nu1668_mtp_init(charger, wdata); + if (ret < 0) + return false; + + /* + * block to access regisgter map during fw update + * register-map is not available since SRAM is usued as buffer or bootloader + */ + charger->reg_access_lock = true; + + ret = nu1668_mtp_write_data(charger, wdata, fw_data_length); + if (ret < 0) { + charger->reg_access_lock = false; + return false; + } + + msleep(100); + charger->reg_access_lock = false; + + ret = nu1668_mtp_write_end(charger); + if (ret < 0) { + pr_info("[nu1668] [%s] nu1668_mtp_write_end failed!\n", __func__); + return false; + } + + return true; +} + + +bool nu1668_check_fw_info(struct mfc_charger_data *charger, u8 *fw_info) +{ + u8 read_buf[5] = {0, }; + int ret = 0; + u8 read_data[2] = {0, }; + + pr_info("[nu1668] [%s] enter\n", __func__); + + ret = mfc_reg_read(charger->client, NU1668_GEN_REPORT_4_REG, &read_buf[0]); + if (ret < 0) + return false; + pr_info("[nu1668] [%s] reg24 = 0x%x\n", __func__, read_buf[0]); + + ret = mfc_reg_read(charger->client, NU1668_GEN_REPORT_5_REG, &read_buf[1]); + if (ret < 0) + return false; + pr_info("[nu1668] [%s] reg25 = 0x%x\n", __func__, read_buf[1]); + + ret = mfc_reg_read(charger->client, NU1668_GEN_REPORT_6_REG, &read_buf[2]); + if (ret < 0) + return false; + pr_info("[nu1668] [%s] reg26 = 0x%x\n", __func__, read_buf[2]); + + ret = mfc_reg_read(charger->client, NU1668_GEN_REPORT_7_REG, &read_buf[3]); + if (ret < 0) + return false; + pr_info("[nu1668] [%s] reg27 = 0x%x\n", __func__, read_buf[3]); + + ret = mfc_reg_read(charger->client, NU1668_GEN_REPORT_8_REG, &read_buf[4]); + if (ret < 0) + return false; + pr_info("[nu1668] [%s] reg28 = 0x%x\n", __func__, read_buf[4]); + + //check if the fw calculate checksum is the same with content's record? + if ((read_buf[0] != fw_info[7]) || (read_buf[1] != fw_info[6]) || + (read_buf[2] != fw_info[5]) || (read_buf[3] != fw_info[4])) { + pr_info("[nu1668] [%s] (bin file) checksum data = 0x%x 0x%x 0x%x 0x%x\n", __func__, + fw_info[7], fw_info[6], fw_info[5], fw_info[4]); + pr_info("[nu1668] [%s] (new) checksum data = 0x%x 0x%x 0x%x 0x%x\n", __func__, + read_buf[0], read_buf[1], read_buf[2], read_buf[3]); + return false; + } + + //check if FW download is successful? + if (read_buf[4] != 0x66) + return false; + + //check if the FW version is equal with the content's record? + ret = mfc_reg_read(charger->client, MFC_FW_MINOR_REV_L_REG, &read_data[0]); + if (ret < 0) + return false; + + ret = mfc_reg_read(charger->client, MFC_FW_MINOR_REV_H_REG, &read_data[1]); + if (ret < 0) + return false; + + + //fw_info[3] = srcData[BIN_FILE_TOTAL_LENGTH-5];//2 read_data[1]=2 + //fw_info[2] = srcData[BIN_FILE_TOTAL_LENGTH-6];//17 read_data[0]=8 + //fw_info[1] = srcData[BIN_FILE_TOTAL_LENGTH-7];//0 + //fw_info[0] = srcData[BIN_FILE_TOTAL_LENGTH-8];//0 + if ((read_data[0] != fw_info[2]) || (read_data[1] != fw_info[3])) { + pr_info("[nu1668] [%s] (bin file) fw_minor: 0x%x 0x%x\n", __func__, fw_info[3], fw_info[2]); + pr_info("[nu1668] [%s] (new) fw_minor: 0x%x 0x%x\n", __func__, read_data[1], read_data[0]); + return false; + } + + pr_info("[nu1668] [%s] (bin file) fw_minor: 0x%x 0x%x\n", __func__, fw_info[3], fw_info[2]); + pr_info("[nu1668] [%s] (new) fw_minor: 0x%x 0x%x\n", __func__, read_data[1], read_data[0]); + + return true; +} + +static int PgmOTPwRAM_NU1668(struct mfc_charger_data *charger, unsigned short OtpAddr, + const u8 *srcData, int srcOffs, int size) +{ + bool status = false; + u8 fw_info[8] = {0, }; + u16 mtp_fwver_minor = 0; + u16 bin_fwver_minor = 0; + + pr_info("[nu1668] [%s] enter\n", __func__); + + status = nu1668_read_fw_version_and_checksum(charger, &fw_info[0]); + if (!status) { + pr_info("nu1668_read_firmware version mtp fail!!!\n"); + return -1; + } + + pr_info("[nu1668] [%s] fw_info[0]:%d, srcData[32768-8]:%d\n", __func__, fw_info[0], srcData[BIN_FILE_TOTAL_LENGTH-8]); + pr_info("[nu1668] [%s] fw_info[1]:%d, srcData[32768-7]:%d\n", __func__, fw_info[1], srcData[BIN_FILE_TOTAL_LENGTH-7]); + pr_info("[nu1668] [%s] fw_info[2]:%d, srcData[32768-6]:%d\n", __func__, fw_info[2], srcData[BIN_FILE_TOTAL_LENGTH-6]); + pr_info("[nu1668] [%s] fw_info[3]:%d, srcData[32768-5]:%d\n", __func__, fw_info[3], srcData[BIN_FILE_TOTAL_LENGTH-5]); + pr_info("[nu1668] [%s] fw_info[4]:%d, srcData[32768-4]:%d\n", __func__, fw_info[4], srcData[BIN_FILE_TOTAL_LENGTH-4]); + pr_info("[nu1668] [%s] fw_info[5]:%d, srcData[32768-3]:%d\n", __func__, fw_info[5], srcData[BIN_FILE_TOTAL_LENGTH-3]); + pr_info("[nu1668] [%s] fw_info[6]:%d, srcData[32768-2]:%d\n", __func__, fw_info[6], srcData[BIN_FILE_TOTAL_LENGTH-2]); + pr_info("[nu1668] [%s] fw_info[7]:%d, srcData[32768-1]:%d\n", __func__, fw_info[7], srcData[BIN_FILE_TOTAL_LENGTH-1]); + + pr_info("[nu1668] [%s] fw_major = 0x%02x 0x%02x, fw_minor = 0x%02x 0x%02x\n", __func__, fw_info[1], fw_info[0], fw_info[3], fw_info[2]); + pr_info("[nu1668] [%s] mtp_checksum = 0x%x 0x%x 0x%x 0x%x\n", __func__, fw_info[4], fw_info[5], fw_info[6], fw_info[7]); + + mtp_fwver_minor = fw_info[2] | (fw_info[3] << 8); + bin_fwver_minor = srcData[BIN_FILE_TOTAL_LENGTH-6] | (srcData[BIN_FILE_TOTAL_LENGTH-5] << 8); + pr_info("[nu1668] [%s] mtp_fwver_minor = 0x%x; bin_fwver_minor = 0x%x\n", __func__, mtp_fwver_minor, bin_fwver_minor); + + /******Check whether the mtp fw is latest version******/ + if (mtp_fwver_minor > bin_fwver_minor) + pr_info("[nu1668] [%s] mtp is latest fw!!!\n", __func__); + /******Check whether the mtp fw is latest version******/ + + pr_info("[nu1668] [%s] : nu1668_download_fw_content enter\n", __func__); + + status = nu1668_download_fw_content(charger, srcData, size); + if (!status) { + pr_info("[nu1668] [%s] nu1668_download_fw_content fail!!!\n", __func__); + return -1; + } + + pr_info("[nu1668] [%s] : nu1668_download_fw_content exit\n", __func__); + + fw_info[7] = srcData[BIN_FILE_TOTAL_LENGTH-1]; + fw_info[6] = srcData[BIN_FILE_TOTAL_LENGTH-2]; + fw_info[5] = srcData[BIN_FILE_TOTAL_LENGTH-3]; + fw_info[4] = srcData[BIN_FILE_TOTAL_LENGTH-4]; + fw_info[3] = srcData[BIN_FILE_TOTAL_LENGTH-5]; + fw_info[2] = srcData[BIN_FILE_TOTAL_LENGTH-6]; + fw_info[1] = srcData[BIN_FILE_TOTAL_LENGTH-7]; + fw_info[0] = srcData[BIN_FILE_TOTAL_LENGTH-8]; + + pr_info("[nu1668] [%s]----------power reset------------\n", __func__); + + msleep(600); + + status = nu1668_check_fw_info(charger, &fw_info[0]); + if (!status) { + pr_info("[nu1668] [%s] nu1668_check_fw_info fail!!!\n", __func__); + return -1; + } + + return 1; +} +/*************updata FW**************/ + +static void mfc_reset_rx_power(struct mfc_charger_data *charger, u8 rx_power) +{ +#if defined(CONFIG_SEC_FACTORY) + if (delayed_work_pending(&charger->wpc_rx_power_work)) + cancel_delayed_work(&charger->wpc_rx_power_work); +#endif + + if (charger->adt_transfer_status == WIRELESS_AUTH_PASS) + mfc_set_rx_power(charger, rx_power * 100000); + else + pr_info("%s %s: undefine rx power scenario, It is auth failed case how dose it get rx power?\n", + WC_AUTH_MSG, __func__); +} + +static void mfc_wpc_rx_power_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, wpc_rx_power_work.work); + + pr_info("%s: rx power = %d, This W/A is only for Factory\n", __func__, charger->max_power_by_txid); + mfc_set_rx_power(charger, charger->max_power_by_txid); +} + +static void mfc_set_pad_hv(struct mfc_charger_data *charger, bool set_hv) +{ + if (!is_hv_wireless_type(charger->pdata->cable_type) || + (charger->pdata->cable_type == SEC_BATTERY_CABLE_WIRELESS_EPP)) + return; + + if (set_hv && charger->is_full_status) + return; + + if (set_hv) { + if (charger->pdata->cable_type == SEC_BATTERY_CABLE_HV_WIRELESS_20) + mfc_send_command(charger, charger->vrect_by_txid); + else + mfc_send_command(charger, MFC_AFC_CONF_10V_TX); + + } else { + mfc_send_command(charger, MFC_AFC_CONF_5V_TX); + } + charger->is_afc_tx = set_hv; +} + +static void mfc_recover_vout_by_pad(struct mfc_charger_data *charger) +{ + if (!is_hv_wireless_type(charger->pdata->cable_type)) + return; + + if (charger->is_full_status) + return; + + if (charger->vout_mode != WIRELESS_VOUT_5V && + charger->vout_mode != WIRELESS_VOUT_5V_STEP && + charger->vout_mode != WIRELESS_VOUT_5_5V_STEP && + charger->vout_mode != WIRELESS_VOUT_OTG) { + // need to fix here + if ((charger->pdata->cable_type == SEC_BATTERY_CABLE_HV_WIRELESS_20) || + (charger->pdata->cable_type == SEC_BATTERY_CABLE_WIRELESS_EPP)) + charger->vout_mode = charger->vout_by_txid; + else + charger->vout_mode = WIRELESS_VOUT_10V; + + if (!charger->is_otg_on) { + cancel_delayed_work(&charger->wpc_vout_mode_work); + __pm_stay_awake(charger->wpc_vout_mode_ws); + queue_delayed_work(charger->wqueue, &charger->wpc_vout_mode_work, 0); + + if ((charger->pdata->cable_type == SEC_BATTERY_CABLE_HV_WIRELESS_20) && + (charger->tx_id == TX_ID_FG_PAD)) { + pr_info("%s: set power = %d\n", __func__, charger->current_rx_power); + mfc_reset_rx_power(charger, charger->current_rx_power); + } + } + } else if (charger->sleep_mode && (charger->nego_done_power >= TX_RX_POWER_8W)) { + cancel_delayed_work(&charger->wpc_vout_mode_work); + __pm_stay_awake(charger->wpc_vout_mode_ws); + queue_delayed_work(charger->wqueue, &charger->wpc_vout_mode_work, 0); + } +} + +static void mfc_wpc_afc_vout_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, wpc_afc_vout_work.work); + + pr_info("%s: start, current cable(%d)\n", __func__, charger->pdata->cable_type); + + /* change cable type */ + if (charger->pdata->cable_type == SEC_BATTERY_CABLE_PREPARE_WIRELESS_HV) + charger->pdata->cable_type = SEC_BATTERY_CABLE_HV_WIRELESS; + + mfc_set_online(charger, charger->pdata->cable_type); + + pr_info("%s: check OTG(%d), full(%d) tx_id(0x%x)\n", + __func__, charger->is_otg_on, charger->is_full_status, charger->tx_id); + + if (charger->is_full_status && volt_ctrl_pad(charger->tx_id)) { + pr_info("%s: skip voltgate set to pad, full status with PG950 pad\n", __func__); + goto skip_set_afc_vout; + } + // need to fix here to get vout setting + mfc_set_pad_hv(charger, true); + mfc_recover_vout_by_pad(charger); + pr_info("%s: is_afc_tx = %d vout read = %d\n", __func__, + charger->is_afc_tx, mfc_get_adc(charger, MFC_ADC_VOUT)); +skip_set_afc_vout: + __pm_relax(charger->wpc_afc_vout_ws); +} + +static void mfc_wpc_fw_update_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, wpc_fw_update_work.work); + union power_supply_propval value = {0, }; + int ret = 0; + int i = 0; + bool is_changed = false; + char fwtime[8] = {32, 32, 32, 32, 32, 32, 32, 32}; + char fwdate[11] = {32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32}; + u8 data = 32; /* ascii space */ + const char *fw_path = MFC_FLASH_FW_HEX_NU1668_PATH; + int fw_size; + u8 pgmCnt = 0; + bool repairEn = false; + + pr_info("%s: firmware update mode is = %d\n", __func__, charger->fw_cmd); + + if (gpio_get_value(charger->pdata->wpc_en)) { + pr_info("%s: wpc_en disabled\n", __func__); + goto end_of_fw_work; + } + mfc_set_wpc_en(charger, WPC_EN_FW, true); /* keep the wpc_en low during fw update */ + + switch (charger->fw_cmd) { + case SEC_WIRELESS_FW_UPDATE_SPU_MODE: + case SEC_WIRELESS_FW_UPDATE_SPU_VERIFY_MODE: + case SEC_WIRELESS_FW_UPDATE_SDCARD_MODE: + case SEC_WIRELESS_FW_UPDATE_AUTO_MODE: + case SEC_WIRELESS_FW_UPDATE_BUILTIN_MODE: + mfc_uno_on(charger, true); + msleep(200); + if (!mfc_check_chip_id(charger)) { + pr_info("%s: current IC's chip_id is not matching to driver's chip_id(0x%x)\n", + __func__, MFC_CHIP_NUVOLTA); + break; + } + if (charger->fw_cmd == SEC_WIRELESS_FW_UPDATE_AUTO_MODE) { + if (mfc_check_customer_id(charger)) { + charger->pdata->otp_firmware_ver = mfc_get_firmware_version(charger, MFC_RX_FIRMWARE); + } else { + pr_info("%s, invalid firmware\n", __func__); + charger->pdata->otp_firmware_ver = 0; + } + pr_info("%s: current version (0x%x) ------ BIN_VERSION by SPU(0x%x)\n", + __func__, charger->pdata->otp_firmware_ver, MFC_FW_BIN_VERSION); +#if defined(CONFIG_WIRELESS_IC_PARAM) + if (charger->wireless_fw_mode_param == SEC_WIRELESS_FW_UPDATE_SPU_MODE && + charger->pdata->otp_firmware_ver > MFC_FW_BIN_VERSION) { + pr_info("%s: current version (0x%x) is higher than BIN_VERSION by SPU(0x%x)\n", + __func__, charger->pdata->otp_firmware_ver, MFC_FW_BIN_VERSION); + break; + } +#endif + if (charger->pdata->otp_firmware_ver == MFC_FW_BIN_VERSION) { + pr_info("%s: current version (0x%x) is same to BIN_VERSION (0x%x)\n", + __func__, charger->pdata->otp_firmware_ver, MFC_FW_BIN_VERSION); + break; + } + } + charger->pdata->otp_firmware_result = MFC_FWUP_ERR_RUNNING; + is_changed = true; + + pr_info("%s, request_firmware\n", __func__); + + if (charger->fw_cmd == SEC_WIRELESS_FW_UPDATE_SPU_MODE || + charger->fw_cmd == SEC_WIRELESS_FW_UPDATE_SPU_VERIFY_MODE) + fw_path = MFC_FW_SPU_BIN_PATH; + else if (charger->fw_cmd == SEC_WIRELESS_FW_UPDATE_SDCARD_MODE) + fw_path = MFC_FW_SDCARD_BIN_PATH; + else + fw_path = MFC_FLASH_FW_HEX_NU1668_PATH; + + ret = request_firmware(&charger->firm_data_bin, fw_path, &charger->client->dev); + if (ret < 0) { + pr_err("%s: failed to request firmware %s (%d)\n", __func__, fw_path, ret); + charger->pdata->otp_firmware_result = MFC_FWUP_ERR_REQUEST_FW_BIN; + goto fw_err; + } + fw_size = (int)charger->firm_data_bin->size; + +#if IS_ENABLED(CONFIG_SPU_VERIFY) + if (charger->fw_cmd == SEC_WIRELESS_FW_UPDATE_SPU_MODE || + charger->fw_cmd == SEC_WIRELESS_FW_UPDATE_SPU_VERIFY_MODE) { + if (spu_firmware_signature_verify("MFC", charger->firm_data_bin->data, charger->firm_data_bin->size) == + (fw_size - SPU_METADATA_SIZE(MFC))) { + pr_err("%s: spu_firmware_signature_verify success\n", __func__); + fw_size -= SPU_METADATA_SIZE(MFC); + if (charger->fw_cmd == SEC_WIRELESS_FW_UPDATE_SPU_VERIFY_MODE) { + charger->pdata->otp_firmware_result = MFC_FW_RESULT_PASS; + goto fw_err; + } + } else { + pr_err("%s: spu_firmware_signature_verify failed\n", __func__); + goto fw_err; + } + } +#endif + + disable_irq(charger->pdata->irq_wpc_int); + disable_irq(charger->pdata->irq_wpc_det); + if (charger->pdata->irq_wpc_pdrc) + disable_irq(charger->pdata->irq_wpc_pdrc); + if (charger->pdata->irq_wpc_pdet_b) + disable_irq(charger->pdata->irq_wpc_pdet_b); + pr_info("%s data size = %ld\n", __func__, (long)fw_size); + + do { + if (repairEn == true) { + if (charger->pdata->wpc_en >= 0) + gpio_direction_output(charger->pdata->wpc_en, 1); + mfc_uno_on(charger, false); + msleep(1000); + mfc_uno_on(charger, true); + if (charger->pdata->wpc_en >= 0) + gpio_direction_output(charger->pdata->wpc_en, 0); + msleep(300); + } + + ret = PgmOTPwRAM_NU1668(charger, 0, charger->firm_data_bin->data, 0, fw_size); + + if (ret != MFC_FWUP_ERR_SUCCEEDED) + repairEn = true; + else + repairEn = false; + + pgmCnt++; + + pr_info("%s %s: repairEn(%d), pgmCnt(%d), ret(%d)\n", + MFC_FW_MSG, __func__, repairEn, pgmCnt, ret); + } while ((ret != MFC_FWUP_ERR_SUCCEEDED) && (pgmCnt < MAX_MTP_PGM_CNT)); + + release_firmware(charger->firm_data_bin); + + for (i = 0; i < 11; i++) { + if (mfc_reg_read(charger->client, MFC_FW_DATE_CODE_0+i, &data) > 0) + fwdate[i] = (char)data; + } + for (i = 0; i < 8; i++) { + if (mfc_reg_read(charger->client, MFC_FW_TIMER_CODE_0+i, &data) > 0) + fwtime[i] = (char)data; + } + pr_info("%s: %d%d%d%d%d%d%d%d%d%d%d, %d%d%d%d%d%d%d%d\n", __func__, + fwdate[0], fwdate[1], fwdate[2], fwdate[3], fwdate[4], fwdate[5], fwdate[6], fwdate[7],fwdate[8], fwdate[9], fwdate[10], + fwtime[0], fwtime[1], fwtime[2], fwtime[3], fwtime[4], fwtime[5], fwtime[6], fwtime[7]); + + charger->pdata->otp_firmware_ver = mfc_get_firmware_version(charger, MFC_RX_FIRMWARE); + charger->pdata->wc_ic_rev = mfc_get_ic_revision(charger, MFC_IC_REVISION); + + for (i = 0; i < 11; i++) { + if (mfc_reg_read(charger->client, MFC_FW_DATE_CODE_0+i, &data) > 0) + fwdate[i] = (char)data; + } + for (i = 0; i < 8; i++) { + if (mfc_reg_read(charger->client, MFC_FW_TIMER_CODE_0+i, &data) > 0) + fwtime[i] = (char)data; + } + pr_info("%s: %d%d%d%d%d%d%d%d%d%d%d, %d%d%d%d%d%d%d%d\n", __func__, + fwdate[0], fwdate[1], fwdate[2], fwdate[3], fwdate[4], fwdate[5], fwdate[6], fwdate[7],fwdate[8], fwdate[9], fwdate[10], + fwtime[0], fwtime[1], fwtime[2], fwtime[3], fwtime[4], fwtime[5], fwtime[6], fwtime[7]); + + enable_irq(charger->pdata->irq_wpc_int); + enable_irq(charger->pdata->irq_wpc_det); + if (charger->pdata->irq_wpc_pdrc) + enable_irq(charger->pdata->irq_wpc_pdrc); + if (charger->pdata->irq_wpc_pdet_b) + enable_irq(charger->pdata->irq_wpc_pdet_b); + break; + default: + break; + } + + msleep(200); + mfc_uno_on(charger, false); + pr_info("%s---------------------------------------------------------------\n", __func__); + + if (is_changed) { + if (ret == MFC_FWUP_ERR_SUCCEEDED) { + charger->pdata->otp_firmware_result = MFC_FW_RESULT_PASS; +#if defined(CONFIG_WIRELESS_IC_PARAM) + charger->wireless_fw_mode_param = charger->fw_cmd & 0xF; + pr_info("%s: succeed. fw_mode(0x%01X)\n", + __func__, charger->wireless_fw_mode_param); + charger->wireless_param_info &= 0xFFFFFF0F; + charger->wireless_param_info |= (charger->wireless_fw_mode_param & 0xF) << 4; + pr_info("%s: wireless_param_info (0x%08X)\n", __func__, charger->wireless_param_info); +#endif + } else { + charger->pdata->otp_firmware_result = ret; + } + } + value.intval = false; + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_MFC_FW_UPDATE, value); + + mfc_set_wpc_en(charger, WPC_EN_FW, false); + __pm_relax(charger->wpc_update_ws); + return; +fw_err: + mfc_uno_on(charger, false); + mfc_set_wpc_en(charger, WPC_EN_FW, false); +end_of_fw_work: + value.intval = false; + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_MFC_FW_UPDATE, value); + __pm_relax(charger->wpc_update_ws); +} + +static void mfc_set_tx_data(struct mfc_charger_data *charger, int idx) +{ + if (idx < 0) + idx = 0; + else if (idx >= charger->pdata->len_wc20_list) + idx = charger->pdata->len_wc20_list - 1; + + charger->vout_by_txid = charger->pdata->wireless20_vout_list[idx]; + charger->vrect_by_txid = charger->pdata->wireless20_vrect_list[idx]; + charger->max_power_by_txid = charger->pdata->wireless20_max_power_list[idx]; +} + +static int mfc_set_sgf_data(struct mfc_charger_data *charger, sgf_data *pdata) +{ + pr_info("%s: size = %d, type = %d\n", __func__, pdata->size, pdata->type); + + switch (pdata->type) { + case WPC_TX_COM_RX_POWER: + { + union power_supply_propval value = {0, }; + int tx_power = *(int *)pdata->data; + + switch (tx_power) { + case TX_RX_POWER_7_5W: + pr_info("%s : TX Power is 7.5W\n", __func__); + charger->current_rx_power = TX_RX_POWER_7_5W; + mfc_set_tx_data(charger, 0); + break; + case TX_RX_POWER_12W: + pr_info("%s : TX Power is 12W\n", __func__); + charger->current_rx_power = TX_RX_POWER_12W; + mfc_set_tx_data(charger, 1); + break; + case TX_RX_POWER_15W: + pr_info("%s : TX Power is 15W\n", __func__); + charger->current_rx_power = TX_RX_POWER_15W; + mfc_set_tx_data(charger, 2); + break; + case TX_RX_POWER_17_5W: + pr_info("%s : TX Power is 17.5W\n", __func__); + charger->current_rx_power = TX_RX_POWER_17_5W; + mfc_set_tx_data(charger, 3); + break; + case TX_RX_POWER_20W: + pr_info("%s : TX Power is 20W\n", __func__); + charger->current_rx_power = TX_RX_POWER_20W; + mfc_set_tx_data(charger, 4); + break; + default: + pr_info("%s : Undefined TX Power(%d)\n", __func__, tx_power); + return -EINVAL; + } + charger->adt_transfer_status = WIRELESS_AUTH_PASS; + charger->pdata->cable_type = value.intval = SEC_BATTERY_CABLE_HV_WIRELESS_20; + pr_info("%s: change cable type to WPC HV 2.0\n", __func__); + + __pm_stay_awake(charger->wpc_afc_vout_ws); + queue_delayed_work(charger->wqueue, &charger->wpc_afc_vout_work, msecs_to_jiffies(0)); + } + break; + default: + return -EINVAL; + } + return 0; +} + +#if defined(CONFIG_MST_V2) +static void mfc_send_mst_cmd(int cmd, struct mfc_charger_data *charger, u8 irq_src_l, u8 irq_src_h) +{ + switch (cmd) { + case MST_MODE_ON: + /* clear interrupt */ + mfc_reg_write(charger->client, MFC_INT_A_CLEAR_L_REG, irq_src_l); // clear int + mfc_reg_write(charger->client, MFC_INT_A_CLEAR_H_REG, irq_src_h); // clear int + mfc_set_cmd_l_reg(charger, 0x20, MFC_CMD_CLEAR_INT_MASK); // command + + mfc_reg_write(charger->client, MFC_MST_MODE_SEL_REG, 0x02); /* set MST mode2 */ + pr_info("%s 2AC Missing ! : MST on REV : %d\n", __func__, charger->pdata->wc_ic_rev); + + /* clear interrupt */ + mfc_reg_write(charger->client, MFC_INT_A_CLEAR_L_REG, irq_src_l); // clear int + mfc_reg_write(charger->client, MFC_INT_A_CLEAR_H_REG, irq_src_h); // clear int + mfc_set_cmd_l_reg(charger, 0x20, MFC_CMD_CLEAR_INT_MASK); // command + + usleep_range(10000, 11000); + break; + case MST_MODE_OFF: + pr_info("%s: set MST mode off\n", __func__); + break; + default: + break; + } +} + +static int mfc_get_mst_mode(struct mfc_charger_data *charger) +{ + u8 mst_mode, reg_data; + int ret; + + ret = mfc_reg_read(charger->client, MFC_MST_MODE_SEL_REG, &mst_mode); + if (ret < 0) { + pr_info("%s mst mode(0x2) i2c write failed, ret = %d\n", + __func__, ret); + return ret; + } + + ret = mfc_reg_read(charger->client, MFC_SYS_OP_MODE_REG, ®_data); + if (ret < 0) { + pr_info("%s mst mode change irq(0x4) read failed, ret = %d\n", + __func__, ret); + return ret; + } + reg_data &= 0x0C; /* use only [3:2]bit of sys_op_mode register for MST */ + + pr_info("%s mst mode check: mst_mode = %d, reg_data = %d\n", + __func__, mst_mode, reg_data); + + ret = 0; + if (reg_data == 0x4) + ret = mst_mode; + + return ret; +} +#endif + +#define ALIGN_WORK_CHK_CNT 5 +#define ALIGN_WORK_DELAY 500 +#define ALIGN_CHK_PERIOD 1000 +#define ALIGN_WORK_CHK_PERIOD 100 +#define MISALIGN_TX_OFF_TIME 10 + +static int mfc_get_target_vout(struct mfc_charger_data *charger) +{ + if (charger->vout_strength == 0) + return (charger->pdata->mis_align_target_vout + charger->pdata->mis_align_offset); + else + return charger->pdata->mis_align_target_vout; // falling uvlo +} + +static int mfc_unsafe_vout_check(struct mfc_charger_data *charger) +{ + int vout; + int target_vout; + + if (charger->pdata->wpc_vout_ctrl_full && charger->is_full_status) + return 0; + + vout = mfc_get_adc(charger, MFC_ADC_VOUT); + target_vout = mfc_get_target_vout(charger); + + pr_info("%s: vout(%d) target_vout(%d)\n", __func__, vout, target_vout); + if (vout < target_vout) + return 1; + return 0; +} + +static bool mfc_check_wire_status(void) +{ + union power_supply_propval value = {0, }; + int wire_type = SEC_BATTERY_CABLE_NONE; + + psy_do_property("battery", get, POWER_SUPPLY_EXT_PROP_CHARGE_COUNTER_SHADOW, value); + wire_type = value.intval; + + if (is_wired_type(wire_type) || (wire_type == SEC_BATTERY_CABLE_OTG)) { + pr_info("%s: return misalign check, cable_type(%d)\n", + __func__, wire_type); + return true; + } + + return false; +} + +static void mfc_wpc_align_check_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, align_check_work.work); + struct timespec64 current_ts = {0, }; + union power_supply_propval value = {0, }; + long checking_time = 0; + int vout = 0, vout_avr = 0, i = 0; + static int vout_sum, align_work_cnt; + + if (!charger->det_state) + goto end_align_work; + + if (charger->pdata->wpc_vout_ctrl_full && charger->is_full_status) + goto end_align_work; + + if (charger->wc_align_check_start.tv_sec == 0) { + charger->wc_align_check_start = ktime_to_timespec64(ktime_get_boottime()); + align_work_cnt = 0; + vout_sum = 0; + } + current_ts = ktime_to_timespec64(ktime_get_boottime()); + checking_time = current_ts.tv_sec - charger->wc_align_check_start.tv_sec; + + vout = mfc_get_adc(charger, MFC_ADC_VOUT); + vout_sum += vout; + align_work_cnt++; + vout_avr = vout_sum / align_work_cnt; + + pr_info("%s: vout(%d), vout_avr(%d), work_cnt(%d), checking_time(%ld)\n", + __func__, vout, vout_avr, align_work_cnt, checking_time); + + if (align_work_cnt < ALIGN_WORK_CHK_CNT) { + queue_delayed_work(charger->wqueue, + &charger->align_check_work, msecs_to_jiffies(ALIGN_WORK_CHK_PERIOD)); + + return; + } + + if (vout_avr >= mfc_get_target_vout(charger)) { + value.intval = charger->vout_strength = 100; + psy_do_property("battery", + set, POWER_SUPPLY_EXT_PROP_WPC_FREQ_STRENGTH, value); + psy_do_property("wireless", set, POWER_SUPPLY_PROP_CURRENT_MAX, value); + pr_info("%s: Finish to check (Align)\n", __func__); + goto end_align_work; + } else if (checking_time >= MISALIGN_TX_OFF_TIME) { + pr_info("%s: %s to check (Timer cnt :%d)\n", + __func__, charger->mis_align_tx_try_cnt == MISALIGN_TX_TRY_CNT ? "Finish" : "Retry", + charger->mis_align_tx_try_cnt); + + for (i = 0; i < 30; i++) { + pr_info("%s: Send a packet to TX device to stop power sharing\n", + __func__); + mfc_send_command(charger, MFC_TX_UNO_OFF); + if (mfc_get_adc(charger, MFC_ADC_VRECT) <= 0) + break; + } + charger->mis_align_tx_try_cnt++; + mfc_set_wpc_en(charger, WPC_EN_CHARGING, false); + goto end_algin_work_by_retry; + } else if (checking_time >= MISALIGN_TX_OFF_TIME * MISALIGN_TX_TRY_CNT) { + pr_info("%s: Finish to check (Timer expired %d secs)\n", + __func__, MISALIGN_TX_OFF_TIME * MISALIGN_TX_TRY_CNT); + goto end_align_work; + } else { + if (mfc_check_wire_status()) + goto end_align_work; + + pr_info("%s: Continue to check until %d secs (Misalign)\n", + __func__, MISALIGN_TX_OFF_TIME * MISALIGN_TX_TRY_CNT); + value.intval = charger->vout_strength = 0; + psy_do_property("battery", + set, POWER_SUPPLY_EXT_PROP_WPC_FREQ_STRENGTH, value); + + align_work_cnt = 0; + vout_sum = 0; + queue_delayed_work(charger->wqueue, + &charger->align_check_work, msecs_to_jiffies(ALIGN_CHK_PERIOD)); + } + + return; + +end_align_work: + mfc_set_wpc_en(charger, WPC_EN_CHARGING, true); + charger->mis_align_tx_try_cnt = 1; + charger->wc_checking_align = false; + charger->wc_align_check_start.tv_sec = 0; +end_algin_work_by_retry: + __pm_relax(charger->align_check_ws); +} + +static void mfc_wpc_align_check(struct mfc_charger_data *charger, unsigned int work_delay) +{ + if (!charger->pdata->mis_align_guide) + return; + + if (mfc_check_wire_status()) + return; + + if (charger->wc_checking_align) { + pr_info("%s: return, wc_checking_align(%d)\n", __func__, charger->wc_checking_align); + return; + } + + if (!charger->pdata->is_charging) { + pr_info("%s: return, is_charging(%d)\n", + __func__, charger->pdata->is_charging); + return; + } + + if (charger->vout_strength >= 100) { + if (!mfc_unsafe_vout_check(charger)) { + pr_info("%s: return, safe vout\n", __func__); + return; + } + } + + pr_info("%s: start\n", __func__); + __pm_stay_awake(charger->align_check_ws); + charger->wc_checking_align = true; + queue_delayed_work(charger->wqueue, &charger->align_check_work, msecs_to_jiffies(work_delay)); +} + +static void mfc_start_wpc_tx_id_work(struct mfc_charger_data *charger, unsigned int delay) +{ + __pm_stay_awake(charger->wpc_tx_id_ws); + queue_delayed_work(charger->wqueue, &charger->wpc_tx_id_work, msecs_to_jiffies(delay)); +} + +static bool mfc_check_to_start_afc_tx(struct mfc_charger_data *charger) +{ + int vrect_level, vout_level; + + vrect_level = mfc_get_adc(charger, MFC_ADC_VRECT); + vout_level = mfc_get_adc(charger, MFC_ADC_VOUT); + pr_info("%s: read vrect(%dmV), vout(%dmV)\n", __func__, vrect_level, vout_level); + + return (vrect_level < 8500 || vout_level < 8500); +} + +static void mfc_start_bpp_mode(struct mfc_charger_data *charger) +{ + if (!charger->is_full_status) { + /* send request afc_tx , request afc is mandatory */ + msleep(charger->req_afc_delay); + mfc_send_command(charger, MFC_REQUEST_AFC_TX); + __pm_stay_awake(charger->wpc_tx_pwr_budg_ws); + queue_delayed_work(charger->wqueue, + &charger->wpc_tx_pwr_budg_work, msecs_to_jiffies(1200)); + } +} + +static void mfc_set_epp_mode(struct mfc_charger_data *charger, int nego_power) +{ + mfc_set_psy_wrl(charger, + POWER_SUPPLY_EXT_PROP_WIRELESS_MAX_VOUT, + charger->vout_by_txid); + + /* Update max power */ + charger->max_power_by_txid = nego_power * 100000; + mfc_set_rx_power(charger, charger->max_power_by_txid); + charger->current_rx_power = nego_power; +} + +static void mfc_set_epp_nv_mode(struct mfc_charger_data *charger) +{ + mfc_set_psy_wrl(charger, + POWER_SUPPLY_EXT_PROP_WIRELESS_MAX_VOUT, + WIRELESS_VOUT_5_5V); + + mfc_set_rx_power(charger, charger->max_power_by_txid); +} + +static bool is_wpc_auth_support(struct mfc_charger_data *charger) +{ + u8 reg_data = 0; + + if (mfc_reg_read(charger->client, MFC_TX_WPC_AUTH_SUPPORT_REG, ®_data) >= 0) { + if ((reg_data & 0x40) == 0x00) { + pr_info("@EPP %s: wpc auth not support (0x%x)\n", __func__, reg_data); + return false; + } + } + + return true; +} + +static void mfc_wpc_mode_change_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, mode_change_work.work); + u8 op_mode = 0; + + pr_info("%s: start\n", __func__); + if (mfc_reg_read(charger->client, MFC_SYS_OP_MODE_REG, &op_mode) <= 0) + goto end_work; + + charger->rx_op_mode = (op_mode >> 5); + pr_info("%s: Rx op mode0x%02x!\n", __func__, op_mode); + + //Enable authentication, but please note that re-nego or cloak will enter MPP or EPP again + switch (charger->rx_op_mode) { + case MFC_RX_MODE_AC_MISSING: + //pr_info("%s: MFC_RX_MODE_AC_MISSING\n", __func__); + break; + case PAD_RX_MODE_WPC_BPP: + pr_info("%s: MFC_RX_MODE_WPC_BPP\n", __func__); + /* mfc_epp_enable(charger, 1); */ + break; + case PAD_RX_MODE_WPC_EPP: + pr_info("@EPP %s: MFC_RX_MODE_WPC_EPP\n", __func__); + if (!is_3rd_pad((charger->mpp_epp_tx_id & 0xFFFF))) + mfc_epp_enable(charger, 1); + mfc_set_epp_count(charger, 0); + + mfc_epp_clear_timer_work_start(charger, -1); + if (is_samsung_pad((charger->mpp_epp_tx_id & 0xFF))) { + if (charger->is_full_status || charger->sleep_mode) { + cancel_delayed_work(&charger->wpc_vout_mode_work); + __pm_stay_awake(charger->wpc_vout_mode_ws); + queue_delayed_work(charger->wqueue, &charger->wpc_vout_mode_work, 0); + } + mfc_start_wpc_tx_id_work(charger, 1000); + } else { + cancel_delayed_work(&charger->wpc_vout_mode_work); + __pm_stay_awake(charger->wpc_vout_mode_ws); + queue_delayed_work(charger->wqueue, &charger->wpc_vout_mode_work, 0); + if (charger->nego_done_power < TX_RX_POWER_8W) { + mfc_set_online(charger, SEC_BATTERY_CABLE_WIRELESS_EPP_NV); + mfc_set_epp_nv_mode(charger); + break; + } + mfc_set_online(charger, SEC_BATTERY_CABLE_WIRELESS_EPP); + +#if defined(CONFIG_SEC_FACTORY) + charger->adt_transfer_status = WIRELESS_AUTH_PASS; +#endif + if (charger->adt_transfer_status == WIRELESS_AUTH_PASS) { + mfc_set_epp_mode(charger, charger->nego_done_power); + break; + } + + if (charger->adt_transfer_status != WIRELESS_AUTH_WAIT) + break; + + if (!is_wpc_auth_support(charger)) { + mfc_set_epp_mode(charger, TX_RX_POWER_8W); + break; + } + + mfc_auth_set_configs(charger, AUTH_READY); + /* notify auth service to send TX PAD a request key */ + mfc_auth_send_adt_status(charger, WIRELESS_AUTH_START); + } + break; + case PAD_RX_MODE_WPC_MPP_RESTRICT: + pr_info("@MPP %s: MFC_RX_MODE_WPC_MPP_RESTRICT\n", __func__); + break; + case PAD_RX_MODE_WPC_MPP_FULL: + pr_info("@MPP %s: MFC_RX_MODE_WPC_MPP_FULL\n", __func__); +#if defined(CONFIG_SEC_FACTORY) + charger->adt_transfer_status = WIRELESS_AUTH_PASS; +#endif + if (charger->adt_transfer_status == WIRELESS_AUTH_PASS) { + mfc_set_epp_mode(charger, charger->nego_done_power); + break; + } + + if (charger->adt_transfer_status != WIRELESS_AUTH_WAIT) + break; + + if (!is_wpc_auth_support(charger)) { + mfc_set_epp_mode(charger, TX_RX_POWER_8W); + break; + } + + mfc_auth_set_configs(charger, AUTH_READY); + /* notify auth service to send TX PAD a request key */ + mfc_auth_send_adt_status(charger, WIRELESS_AUTH_START); + break; + case PAD_RX_MODE_WPC_MPP_CLOAK: + pr_info("@MPP %s: MFC_RX_MODE_WPC_MPP_CLOAK\n", __func__); + break; + case PAD_RX_MODE_WPC_MPP_NEGO: + pr_info("@MPP %s: MFC_RX_MODE_WPC_MPP_NEGO\n", __func__); + //mfc_mpp_epp_nego_power_set(charger, MFC_RX_MPP_NEGO_POWER_15W); // need to add this in dtsi + break; + case PAD_RX_MODE_WPC_EPP_NEGO: + pr_info("@EPP %s: MFC_RX_MODE_WPC_EPP_NEGO\n", __func__); + //mfc_mpp_epp_nego_power_set(charger, MFC_RX_MPP_NEGO_POWER_15W); // need to add this in dtsi + break; + } + + if (!charger->wc_tx_enable) + goto end_work; + + op_mode = op_mode & 0xF; + pr_info("%s: Tx op_mode = 0x%x\n", __func__, op_mode); + + if (op_mode == MFC_TX_MODE_TX_PWR_HOLD) { + if (charger->wc_rx_type == SS_GEAR) { + /* start 3min alarm timer */ + pr_info("@Tx_Mode %s: Received PHM and start PHM disable alarm by 3min\n", __func__); + alarm_start(&charger->phm_alarm, + ktime_add(ktime_get_boottime(), ktime_set(180, 0))); + } else { + pr_info("%s: TX entered PHM but no PHM disable 3min timer\n", __func__); + } + + mfc_set_tx_phm(charger, true); + } else { + mfc_test_read(charger); + + if (charger->tx_device_phm) { + pr_info("@Tx_Mode %s: Ended PHM\n", __func__); + mfc_set_tx_phm(charger, false); + } + if (charger->phm_alarm.state & ALARMTIMER_STATE_ENQUEUED) { + pr_info("@Tx_Mode %s: escape PHM mode, cancel PHM alarm\n", __func__); + cancel_delayed_work(&charger->wpc_tx_phm_work); + __pm_relax(charger->wpc_tx_phm_ws); + alarm_cancel(&charger->phm_alarm); + } + } + +end_work: + __pm_relax(charger->mode_change_ws); +} + +static void nu1668_adt_transfer_result(struct mfc_charger_data *charger, int adt_state) +{ +#if !defined(CONFIG_SEC_FACTORY) + if ((charger->pdata->cable_type == SEC_BATTERY_CABLE_NONE) || + (charger->adt_transfer_status == WIRELESS_AUTH_WAIT)) { + pr_info("%s %s: auth service sent wrong cmd(%d)\n", WC_AUTH_MSG, __func__, adt_state); + return; + } else if (charger->adt_transfer_status == adt_state) { + pr_info("%s %s: skip a same PASS/FAIL result\n", WC_AUTH_MSG, __func__); + return; + } else if ((adt_state != WIRELESS_AUTH_PASS) && (adt_state != WIRELESS_AUTH_FAIL)) { + pr_info("%s %s: undefined PASS/FAIL result(%d)\n", WC_AUTH_MSG, __func__, adt_state); + charger->adt_transfer_status = adt_state; + goto end_adt; + } + + charger->adt_transfer_status = adt_state; + switch (nu1668_get_auth_mode(charger)) { + case WPC_AUTH_MODE_EPP: + mfc_set_epp_mode(charger, + (adt_state != WIRELESS_AUTH_PASS) ? TX_RX_POWER_8W : charger->nego_done_power); + break; + case WPC_AUTH_MODE_MPP: + break; + case WPC_AUTH_MODE_PPDE: + if (adt_state == WIRELESS_AUTH_PASS) { + mfc_fod_set_op_mode(charger->fod, WPC_OP_MODE_PPDE); + charger->pdata->cable_type = SEC_BATTERY_CABLE_HV_WIRELESS_20; + __pm_stay_awake(charger->wpc_afc_vout_ws); + queue_delayed_work(charger->wqueue, + &charger->wpc_afc_vout_work, msecs_to_jiffies(0)); + pr_info("%s %s: PASS! type = %d\n", WC_AUTH_MSG, + __func__, charger->pdata->cable_type); + } else { + if (epp_mode(charger->rx_op_mode) || + charger->afc_tx_done) { + charger->pdata->cable_type = SEC_BATTERY_CABLE_HV_WIRELESS; + __pm_stay_awake(charger->wpc_afc_vout_ws); + queue_delayed_work(charger->wqueue, + &charger->wpc_afc_vout_work, msecs_to_jiffies(0)); + } else { + mfc_set_online(charger, SEC_BATTERY_CABLE_WIRELESS); + } + } + break; + case WPC_AUTH_MODE_BPP: + default: + break; + } + +end_adt: + mfc_auth_set_configs(charger, AUTH_COMPLETE); +#endif +} + +static const char *mfc_bd_log(struct mfc_charger_data *charger, int wrl_mode) +{ + memset(charger->d_buf, 0, MFC_BAT_DUMP_SIZE); + + if (wrl_mode == SB_WRL_TX_MODE) { + snprintf(charger->d_buf, MFC_BAT_DUMP_SIZE, "%d,%d,%d,%d,%d,%d,%s,%x,0x%x,", + charger->mfc_adc_tx_vout, + charger->mfc_adc_tx_iout, + charger->mfc_adc_ping_frq, + charger->mfc_adc_tx_min_op_frq, + charger->mfc_adc_tx_max_op_frq, + charger->tx_device_phm, + sb_rx_type_str(charger->wc_rx_type), + charger->pdata->otp_firmware_ver, + charger->pdata->wc_ic_rev); + } else if (wrl_mode == SB_WRL_RX_MODE) { + snprintf(charger->d_buf, MFC_BAT_DUMP_SIZE, "%d,%d,%d,%d,0x%x,%x,0x%x,0x%x,%016llX,%016llX,", + charger->mfc_adc_vout, + charger->mfc_adc_vrect, + charger->mfc_adc_rx_iout, + charger->mfc_adc_op_frq, + charger->tx_id, + charger->pdata->otp_firmware_ver, + charger->pdata->wc_ic_rev, + charger->rx_op_mode, + charger->now_cmfet_state.value, + charger->now_fod_state.value); + } + + return charger->d_buf; +} + +static void nu1668_monitor_work(struct mfc_charger_data *charger) +{ + union power_supply_propval value = { 0, }; + struct sec_vote *chgen_vote = NULL; + int pdet_b = 1, wpc_det = 0; + int thermal_zone = BAT_THERMAL_NORMAL, capacity = 50, chgen = SEC_BAT_CHG_MODE_CHARGING; + int ret = 0; + + if (gpio_get_value(charger->pdata->wpc_en)) + pr_info("@DIS_MFC %s: charger->wpc_en_flag(0x%x)\n", __func__, charger->wpc_en_flag); + + if (charger->pdata->wpc_pdet_b >= 0) + pdet_b = gpio_get_value(charger->pdata->wpc_pdet_b); + + if (charger->pdata->wpc_det >= 0) + wpc_det = gpio_get_value(charger->pdata->wpc_det); + + if (!wpc_det) { + if (!pdet_b) + pr_info("%s: now phm!\n", __func__); + return; + } + + ret = psy_do_property("battery", get, + POWER_SUPPLY_EXT_PROP_THERMAL_ZONE, value); + if (!ret) + thermal_zone = value.intval; + + ret = psy_do_property("battery", get, + POWER_SUPPLY_PROP_CAPACITY, value); + if (!ret) + capacity = value.intval; + + chgen_vote = find_vote("CHGEN"); + if (chgen_vote) { + ret = get_sec_voter_status(chgen_vote, VOTER_SWELLING, &chgen); + if (ret < 0) + chgen = SEC_BAT_CHG_MODE_CHARGING; + } + + switch (thermal_zone) { + case BAT_THERMAL_OVERHEATLIMIT: + case BAT_THERMAL_OVERHEAT: + case BAT_THERMAL_WARM: + mfc_cmfet_set_high_swell(charger->cmfet, true); + mfc_fod_set_high_swell(charger->fod, true); + if ((chgen == SEC_BAT_CHG_MODE_CHARGING_OFF) || + (chgen == SEC_BAT_CHG_MODE_BUCK_OFF)) { + mfc_cmfet_set_chg_done(charger->cmfet, true); + mfc_fod_set_bat_state(charger->fod, MFC_FOD_BAT_STATE_FULL); + } else if (chgen == SEC_BAT_CHG_MODE_CHARGING) { + mfc_cmfet_set_chg_done(charger->cmfet, false); + } + break; + default: + mfc_cmfet_set_high_swell(charger->cmfet, false); + mfc_fod_set_high_swell(charger->fod, false); + if (chgen == SEC_BAT_CHG_MODE_CHARGING) + mfc_cmfet_set_chg_done(charger->cmfet, false); + break; + } + mfc_cmfet_set_bat_cap(charger->cmfet, capacity); + mfc_fod_set_bat_cap(charger->fod, capacity); + + pr_info("%s: check thermal_zone = %d, capacity = %d, chgen = %d\n", + __func__, thermal_zone, capacity, chgen); +} + +static int nu1668_chg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct mfc_charger_data *charger = power_supply_get_drvdata(psy); + enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; +// union power_supply_propval value; + + switch ((int)psp) { + case POWER_SUPPLY_PROP_STATUS: + pr_info("%s: charger->pdata->cs100_status %d\n", __func__, charger->pdata->cs100_status); + val->intval = charger->pdata->cs100_status; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + case POWER_SUPPLY_PROP_HEALTH: + return -ENODATA; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + if (charger->pdata->is_charging) + val->intval = mfc_get_vout(charger); + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + return -ENODATA; + case POWER_SUPPLY_PROP_ONLINE: + pr_info("%s: cable_type =%d\n", __func__, charger->pdata->cable_type); + val->intval = charger->pdata->cable_type; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + pr_info("%s: POWER_SUPPLY_PROP_MANUFACTURER, intval(0x%x), called by(%ps)\n", + __func__, val->intval, __builtin_return_address(0)); + if (val->intval == SEC_WIRELESS_OTP_FIRM_RESULT) { + pr_info("%s: otp firmware result = %d\n", __func__, charger->pdata->otp_firmware_result); + val->intval = charger->pdata->otp_firmware_result; + } else if (val->intval == SEC_WIRELESS_IC_REVISION) { + pr_info("%s: check ic revision\n", __func__); + val->intval = mfc_get_ic_revision(charger, MFC_IC_REVISION); + } else if (val->intval == SEC_WIRELESS_IC_CHIP_ID) { + pr_info("%s: check ic chip_id(0x%02X)\n", __func__, charger->chip_id); + val->intval = charger->chip_id; + } else if (val->intval == SEC_WIRELESS_OTP_FIRM_VER_BIN) { + /* update latest kernl f/w version */ + val->intval = MFC_FW_BIN_VERSION; + } else if (val->intval == SEC_WIRELESS_OTP_FIRM_VER) { + val->intval = mfc_get_firmware_version(charger, MFC_RX_FIRMWARE); + pr_info("%s: check f/w revision (0x%x)\n", __func__, val->intval); + if (val->intval < 0 && charger->pdata->otp_firmware_ver > 0) + val->intval = charger->pdata->otp_firmware_ver; + } else if (val->intval == SEC_WIRELESS_OTP_FIRM_VERIFY) { + pr_info("%s: NU1668 FIRM_VERIFY is not implemented\n", __func__); + val->intval = 1; + } else { + val->intval = -ENODATA; + pr_err("%s: wrong mode\n", __func__); + } + break; + case POWER_SUPPLY_PROP_ENERGY_NOW: /* vout */ + if (charger->pdata->is_charging) { + val->intval = mfc_get_adc(charger, MFC_ADC_VOUT); + pr_info("%s: wc vout (%d)\n", __func__, val->intval); + } else { + val->intval = 0; + } + break; + case POWER_SUPPLY_PROP_ENERGY_AVG: /* vrect */ + if (charger->pdata->is_charging) + val->intval = mfc_get_adc(charger, MFC_ADC_VRECT); + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: + val->intval = charger->vrect_by_txid; + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = mfc_get_adc(charger, val->intval); + break; + case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: + break; + case POWER_SUPPLY_PROP_CHARGE_EMPTY: + val->intval = charger->wc_ldo_status; + break; + case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: + switch (ext_psp) { + case POWER_SUPPLY_EXT_PROP_WIRELESS_OP_FREQ: + val->intval = mfc_get_adc(charger, MFC_ADC_OP_FRQ); + pr_info("%s: Operating FQ %dkHz\n", __func__, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_OP_FREQ_STRENGTH: + val->intval = charger->vout_strength; + pr_info("%s: vout strength = (%d)\n", + __func__, charger->vout_strength); + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_TRX_CMD: + val->intval = charger->pdata->trx_data_cmd; + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_TRX_VAL: + val->intval = charger->pdata->trx_data_val; + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ID: + val->intval = charger->tx_id; + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ID_CNT: + val->intval = charger->tx_id_cnt; + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_RX_CONNECTED: + val->intval = charger->wc_rx_connected; + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_RX_TYPE: + val->intval = charger->wc_rx_type; + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_UNO_VIN: + val->intval = mfc_get_adc(charger, MFC_ADC_TX_VOUT); + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_UNO_IIN: + val->intval = mfc_get_adc(charger, MFC_ADC_TX_IOUT); + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_RX_POWER: + val->intval = charger->current_rx_power; + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_AUTH_ADT_STATUS: + val->intval = charger->adt_transfer_status; + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_AUTH_ADT_DATA: + { + //int i = 0; + //u8 *p_data; + + if (charger->adt_transfer_status == WIRELESS_AUTH_RECEIVED) { + pr_info("%s %s: MFC_ADT_RECEIVED %d bytes (%d)\n", + WC_AUTH_MSG, __func__, total_adt_dataSize, charger->adt_transfer_status); + val->strval = (u8 *)ADT_buffer_rdata; + //p_data = ADT_buffer_rdata; + adt_readSize = total_adt_dataSize; + //for (i = 0; i < adt_readSize; i++) + // pr_info("%s: auth read data = %x", __func__, p_data[i]); + //pr_info("\n", __func__); + } else { + pr_info("%s: data hasn't been received yet\n", __func__); + return -ENODATA; + } + } + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_AUTH_ADT_SIZE: + adt_readSize = total_adt_dataSize; + val->intval = adt_readSize; + pr_info("%s %s: MFC_ADT_RECEIVED (%d), DATA SIZE(%d)\n", + WC_AUTH_MSG, __func__, charger->adt_transfer_status, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_RX_VOUT: + val->intval = charger->vout_mode; + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_INITIAL_WC_CHECK: + val->intval = charger->initial_wc_check; + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_CHECK_FW_VER: +#if defined(CONFIG_WIRELESS_IC_PARAM) + pr_info("%s: fw_ver (param:0x%04X, build:0x%04X)\n", + __func__, charger->wireless_fw_ver_param, MFC_FW_BIN_VERSION); + if (charger->wireless_fw_ver_param == MFC_FW_BIN_VERSION) + val->intval = 1; + else + val->intval = 0; +#else + val->intval = 0; +#endif + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_MST_PWR_EN: + if (gpio_is_valid(charger->pdata->mst_pwr_en)) { + val->intval = gpio_get_value(charger->pdata->mst_pwr_en); + } else { + pr_info("%s: invalid gpio(mst_pwr_en)\n", __func__); + val->intval = 0; + } + break; + case POWER_SUPPLY_EXT_PROP_PAD_VOLT_CTRL: + val->intval = charger->is_afc_tx; + break; + case POWER_SUPPLY_EXT_PROP_WPC_EN: + val->intval = gpio_get_value(charger->pdata->wpc_en); + break; +#if defined(CONFIG_MST_V2) + case POWER_SUPPLY_EXT_PROP_MST_MODE: + val->intval = mfc_get_mst_mode(charger); + break; + case POWER_SUPPLY_EXT_PROP_MST_DELAY: + val->intval = DELAY_FOR_MST; + break; +#endif + case POWER_SUPPLY_EXT_PROP_MONITOR_WORK: + nu1668_monitor_work(charger); + break; + case POWER_SUPPLY_EXT_PROP_GEAR_PHM_EVENT: + val->intval = charger->tx_device_phm; + break; + case POWER_SUPPLY_EXT_PROP_RX_PHM: + val->intval = charger->rx_phm_status; + break; + case POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL: + case POWER_SUPPLY_EXT_PROP_CHARGE_POWERED_OTG_CONTROL: + return -ENODATA; + case POWER_SUPPLY_EXT_PROP_INPUT_VOLTAGE_REGULATION: + val->intval = charger->pdata->vout_status; + break; +#if defined(CONFIG_WIRELESS_IC_PARAM) + case POWER_SUPPLY_EXT_PROP_WIRELESS_PARAM_INFO: + val->intval = charger->wireless_param_info; + break; +#endif + case POWER_SUPPLY_EXT_PROP_WIRELESS_SGF: + { + sgf_data data; + + data.size = *(int *)val->strval; + data.type = *(int *)(val->strval + 4); + data.data = (char *)(val->strval + 8); + mfc_set_sgf_data(charger, &data); + } + break; + case POWER_SUPPLY_EXT_PROP_WPC_FREQ_STRENGTH: + pr_info("%s: vout_strength = %d\n", + __func__, charger->vout_strength); + val->intval = charger->vout_strength; + break; + case POWER_SUPPLY_EXT_PROP_BATT_DUMP: + val->strval = mfc_bd_log(charger, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_TX_PWR_BUDG: + switch (charger->tx_pwr_budg) { + case MFC_TX_PWR_BUDG_2W: + case MFC_TX_PWR_BUDG_5W: + val->intval = RX_POWER_5W; + break; + case MFC_TX_PWR_BUDG_7_5W: + val->intval = RX_POWER_7_5W; + break; + case MFC_TX_PWR_BUDG_12W: + val->intval = RX_POWER_12W; + break; + case MFC_TX_PWR_BUDG_15W: + val->intval = RX_POWER_15W; + break; + default: + val->intval = RX_POWER_NONE; + } + break; + // TODO: Need to add for EPP nego_done_power + // TODO: Need to add for EPP potential_load_power + // TODO: Need to add for EPP negotiable_load_power + case POWER_SUPPLY_EXT_PROP_WIRELESS_OP_MODE: + val->intval = charger->rx_op_mode; + break; + default: + return -ENODATA; + } + break; + default: + return -ENODATA; + } + return 0; +} + +static void mfc_wpc_vout_mode_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, wpc_vout_mode_work.work); + int vout_step = charger->pdata->vout_status; + int vout = MFC_VOUT_10V; + int wpc_vout_ctrl_lcd_on = 0; + union power_supply_propval value = {0, }; + + if (is_shutdn) { + pr_err("%s: Escape by shtudown\n", __func__); + return; + } + pr_info("%s: start - vout_mode(%s), vout_status(%s)\n", + __func__, sb_vout_ctr_mode_str(charger->vout_mode), sb_rx_vout_str(charger->pdata->vout_status)); + switch (charger->vout_mode) { + case WIRELESS_VOUT_4_5V: + mfc_set_vout(charger, MFC_VOUT_4_5V); + break; + case WIRELESS_VOUT_5V: + mfc_set_vout(charger, MFC_VOUT_5V); + break; + case WIRELESS_VOUT_5_5V: + mfc_set_vout(charger, MFC_VOUT_5_5V); + break; + case WIRELESS_VOUT_9V: + mfc_set_vout(charger, MFC_VOUT_9V); + break; + case WIRELESS_VOUT_10V: + mfc_set_vout(charger, MFC_VOUT_10V); + /* reset AICL */ + psy_do_property("wireless", set, POWER_SUPPLY_PROP_CURRENT_MAX, value); + break; + case WIRELESS_VOUT_11V: + mfc_set_vout(charger, MFC_VOUT_11V); + /* reset AICL */ + psy_do_property("wireless", set, POWER_SUPPLY_PROP_CURRENT_MAX, value); + break; + case WIRELESS_VOUT_12V: + mfc_set_vout(charger, MFC_VOUT_12V); + /* reset AICL */ + psy_do_property("wireless", set, POWER_SUPPLY_PROP_CURRENT_MAX, value); + break; + case WIRELESS_VOUT_12_5V: + mfc_set_vout(charger, MFC_VOUT_12_5V); + /* reset AICL */ + psy_do_property("wireless", set, POWER_SUPPLY_PROP_CURRENT_MAX, value); + break; + case WIRELESS_VOUT_5V_STEP: + vout_step--; + if (vout_step >= MFC_VOUT_5V) { + mfc_set_vout(charger, vout_step); + cancel_delayed_work(&charger->wpc_vout_mode_work); + queue_delayed_work(charger->wqueue, &charger->wpc_vout_mode_work, msecs_to_jiffies(250)); + return; + } + break; + case WIRELESS_VOUT_5_5V_STEP: + psy_do_property("battery", get, POWER_SUPPLY_EXT_PROP_LCD_FLICKER, value); + wpc_vout_ctrl_lcd_on = value.intval; + vout_step--; + if (vout_step < MFC_VOUT_5_5V) { + if (wpc_vout_ctrl_lcd_on && opfreq_ctrl_pad(charger->tx_id)) { + pr_info("%s: tx id = 0x%x , set op freq\n", __func__, charger->tx_id); + mfc_send_command(charger, MFC_SET_OP_FREQ); + msleep(500); + } + break; + } + + if (wpc_vout_ctrl_lcd_on) { + psy_do_property("battery", get, POWER_SUPPLY_EXT_PROP_PAD_VOLT_CTRL, value); + if (value.intval && charger->is_afc_tx) { + if (vout_step == charger->flicker_vout_threshold) { + mfc_set_vout(charger, vout_step); + cancel_delayed_work(&charger->wpc_vout_mode_work); + queue_delayed_work(charger->wqueue, + &charger->wpc_vout_mode_work, + msecs_to_jiffies(charger->flicker_delay)); + return; + } else if (vout_step < charger->flicker_vout_threshold) { + pr_info("%s: set TX 5V because LCD ON\n", __func__); + mfc_set_pad_hv(charger, false); + charger->pad_ctrl_by_lcd = true; + } + } + } + mfc_set_vout(charger, vout_step); + cancel_delayed_work(&charger->wpc_vout_mode_work); + queue_delayed_work(charger->wqueue, + &charger->wpc_vout_mode_work, msecs_to_jiffies(250)); + return; + case WIRELESS_VOUT_4_5V_STEP: + vout_step--; + if (vout_step == MFC_VOUT_4_9V) + vout_step = MFC_VOUT_4_5V; + if (vout_step >= MFC_VOUT_4_5V) { + mfc_set_vout(charger, vout_step); + cancel_delayed_work(&charger->wpc_vout_mode_work); + queue_delayed_work(charger->wqueue, + &charger->wpc_vout_mode_work, msecs_to_jiffies(250)); + return; + } + break; + case WIRELESS_VOUT_9V_STEP: + vout = MFC_VOUT_9V; + fallthrough; + case WIRELESS_VOUT_10V_STEP: + vout_step++; + if (vout_step <= vout) { + mfc_set_vout(charger, vout_step); + cancel_delayed_work(&charger->wpc_vout_mode_work); + queue_delayed_work(charger->wqueue, + &charger->wpc_vout_mode_work, msecs_to_jiffies(250)); + return; + } + break; + case WIRELESS_VOUT_CC_CV_VOUT: + mfc_set_vout(charger, MFC_VOUT_5_5V); + break; + case WIRELESS_VOUT_OTG: + mfc_set_vout(charger, MFC_VOUT_OTG); + break; + default: + break; + } +#if !defined(CONFIG_SEC_FACTORY) + if (charger->pdata->vout_status <= MFC_VOUT_5_5V && + (charger->is_full_status || charger->sleep_mode || (charger->tx_id == TX_ID_BATT_PACK_U1200))) + mfc_set_pad_hv(charger, false); +#endif + pr_info("%s: finish - vout_mode(%s), vout_status(%s)\n", + __func__, sb_vout_ctr_mode_str(charger->vout_mode), sb_rx_vout_str(charger->pdata->vout_status)); + __pm_relax(charger->wpc_vout_mode_ws); +} + +static void mfc_wpc_i2c_error_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, wpc_i2c_error_work.work); + + if (charger->det_state && + gpio_get_value(charger->pdata->wpc_det)) { + union power_supply_propval value; + + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_WC_CONTROL, value); + } +} + +static void mfc_set_tx_fod_common(struct mfc_charger_data *charger) +{ + return; + + pr_info("@Tx_Mode %s\n", __func__); + + /* Gain */ + mfc_reg_write(charger->client, MFC_TX_FOD_GAIN_REG, 0x08);//0x13 + /* Offset */ + mfc_reg_write(charger->client, MFC_TX_FOD_OFFSET_L_REG, 0xC8);//0x64 + //mfc_reg_write(charger->client, MFC_TX_FOD_OFFSET_H_REG, 0x0);//0x00 +} + +static void mfc_set_tx_fod_thresh1(struct i2c_client *client, u32 fod_thresh1) +{ + u8 data[2] = {0,}; + + return; + + /* Thresh1 */ + data[0] = fod_thresh1 & 0xff; + data[1] = (fod_thresh1 & 0xff00) >> 8; + + pr_info("%s: fod_thresh1(0x%x, 0x%x)\n", __func__, data[1], data[0]); + mfc_reg_write(client, MFC_TX_FOD_THRESH1_L_REG, data[0]); + //mfc_reg_write(client, MFC_TX_FOD_THRESH1_H_REG, data[1]); +} + +static void mfc_set_tx_fod_ta_thresh(struct i2c_client *client, u32 fod_thresh) +{ + u8 data[2] = {0,}; + + /* TA Thresh */ + data[0] = fod_thresh & 0xff; + data[1] = (fod_thresh & 0xff00) >> 8; + pr_info("%s: fod_thresh1(0x%x, 0x%x)\n", __func__, data[1], data[0]); + //mfc_reg_write(client, MFC_TX_FOD_TA_THRESH_L_REG, data[0]); + //mfc_reg_write(client, MFC_TX_FOD_TA_THRESH_H_REG, data[1]); +} + +static void mfc_wpc_rx_type_det_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, wpc_rx_type_det_work.work); + u8 reg_data, prmc_id; + union power_supply_propval value; + + if (!charger->wc_tx_enable) { + __pm_relax(charger->wpc_rx_det_ws); + return; + } + + mfc_reg_read(charger->client, MFC_STARTUP_EPT_COUNTER, ®_data); + mfc_reg_read(charger->client, MFC_TX_RXID1_READ_REG, &prmc_id); + + pr_info("@Tx_Mode %s: prmc_id 0x%x\n", __func__, prmc_id); + + mfc_set_tx_fod_common(charger); + if (prmc_id == 0x42 && reg_data >= 1) { + pr_info("@Tx_Mode %s: Samsung Gear Connected\n", __func__); + charger->wc_rx_type = SS_GEAR; + } else if (prmc_id == 0x42) { + pr_info("@Tx_Mode %s: Samsung Phone Connected\n", __func__); + charger->wc_rx_type = SS_PHONE; + mfc_set_coil_sw_en(charger, 0); + mfc_set_tx_fod_thresh1(charger->client, charger->pdata->phone_fod_thresh1); + } else { + pr_info("@Tx_Mode %s: Unknown device connected\n", __func__); + charger->wc_rx_type = OTHER_DEV; + } + value.intval = charger->wc_rx_type; + psy_do_property("wireless", set, POWER_SUPPLY_EXT_PROP_WIRELESS_RX_TYPE, value); + + __pm_relax(charger->wpc_rx_det_ws); +} + +static void mfc_tx_duty_min_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, wpc_tx_duty_min_work.work); + + charger->duty_min = MIN_DUTY_SETTING_20_DATA; + /* recover min duty */ + mfc_set_min_duty(charger, MIN_DUTY_SETTING_20_DATA); + pr_info("%s: tx op freq = %dKhz\n", __func__, mfc_get_adc(charger, MFC_ADC_TX_MAX_OP_FRQ)); + + __pm_relax(charger->wpc_tx_duty_min_ws); +} + +static void mfc_cs100_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, wpc_cs100_work.work); + + charger->pdata->cs100_status = mfc_send_cs100(charger); + __pm_relax(charger->wpc_cs100_ws); +} + +static void mfc_rx_power_trans_fail_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, wpc_rx_power_trans_fail_work.work); + + pr_info("%s %d\n", __func__, charger->check_rx_power); + if (charger->check_rx_power) { + pr_info("%s: set 7.5W\n", __func__); + mfc_reset_rx_power(charger, TX_RX_POWER_7_5W); + charger->current_rx_power = TX_RX_POWER_7_5W; + } + __pm_relax(charger->wpc_rx_power_trans_fail_ws); +} + +static void mfc_tx_phm_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, wpc_tx_phm_work.work); + + pr_info("@Tx_Mode %s\n", __func__); + mfc_set_cmd_l_reg(charger, MFC_CMD_TOGGLE_PHM_MASK, MFC_CMD_TOGGLE_PHM_MASK); + + if (charger->tx_device_phm) + mfc_set_tx_phm(charger, false); + + charger->skip_phm_work_in_sleep = false; + __pm_relax(charger->wpc_tx_phm_ws); +} + +static void mfc_wpc_init_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, wpc_init_work.work); + + pr_info("%s\n", __func__); + if (charger->pdata->cable_type != SEC_BATTERY_CABLE_NONE) { + mfc_set_online(charger, charger->pdata->cable_type); + pr_info("%s: Reset M0\n", __func__); + /* reset MCU of MFC IC */ + mfc_set_cmd_l_reg(charger, MFC_CMD_MCU_RESET_MASK, MFC_CMD_MCU_RESET_MASK); + } + if (charger->is_otg_on) { + union power_supply_propval value = {0, }; + + psy_do_property("wireless", set, + POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL, value); + } +} + +static void mfc_cancel_wpc_vrect_check_work(struct mfc_charger_data *charger) +{ + __pm_relax(charger->wpc_vrect_check_ws); + cancel_delayed_work(&charger->wpc_vrect_check_work); +} + +static void mfc_start_wpc_vrect_check_work(struct mfc_charger_data *charger, unsigned int work_delay) +{ + if ((charger->det_state) || (charger->rx_phm_status)) { + pr_info("%s: prevent work after det_irq: %d, %d\n", + __func__, charger->det_state, charger->rx_phm_status); + return; + } + + __pm_stay_awake(charger->wpc_vrect_check_ws); + queue_delayed_work(charger->wqueue, + &charger->wpc_vrect_check_work, msecs_to_jiffies(work_delay)); +} + +static bool mfc_wpc_check_phm_exit_state(struct mfc_charger_data *charger) +{ + int det_state, vrect, i, ret; + + for (i = 0; i < 8; i++) { + u8 status_l = 0; + + msleep(250); + + det_state = gpio_get_value(charger->pdata->wpc_det); + vrect = mfc_get_adc(charger, MFC_ADC_VRECT); + ret = mfc_reg_read(charger->client, MFC_STATUS_L_REG, &status_l); + pr_info("%s: i(%d), det(%d), vrect(%d), status(%d, 0x%x)\n", + __func__, i, det_state, vrect, ret, status_l); + + if (det_state) + return true; + + if ((status_l & MFC_INTA_L_STAT_VRECT_MASK) && + (vrect >= VALID_VRECT_LEVEL)) + return true; + } + + return false; +} + +static void mfc_wpc_phm_exit_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, wpc_phm_exit_work.work); + int i, det_state, vrect; + + /* init basic info. */ + charger->tx_id = TX_ID_UNKNOWN; + charger->tx_id_done = false; + charger->req_tx_id = false; + charger->tx_id_cnt = 0; + charger->pdata->cable_type = SEC_BATTERY_CABLE_NONE; + if ((charger->adt_transfer_status != WIRELESS_AUTH_PASS) && + (charger->adt_transfer_status != WIRELESS_AUTH_FAIL)) + charger->adt_transfer_status = WIRELESS_AUTH_WAIT; + + det_state = gpio_get_value(charger->pdata->wpc_det); + vrect = mfc_get_adc(charger, MFC_ADC_VRECT); + pr_info("%s: phm(%d), det(%d), vrect(%d)\n", + __func__, charger->rx_phm_status, det_state, vrect); + if (det_state) + goto clear_phm; + + for (i = 0; i < 2; i++) { + mfc_set_wpc_en(charger, WPC_EN_CHARGING, false); + msleep(510); + mfc_set_wpc_en(charger, WPC_EN_CHARGING, true); + + if (mfc_wpc_check_phm_exit_state(charger)) + goto clear_phm; + } + + if (!mfc_wpc_check_phm_exit_state(charger)) { + /* reset rx ic and tx pad for phm exit */ + mfc_set_wpc_en(charger, WPC_EN_CHARGING, false); + msleep(750); + mfc_cancel_wpc_vrect_check_work(charger); + mfc_deactivate_work_content(charger); + msleep(750); + mfc_set_wpc_en(charger, WPC_EN_CHARGING, true); + } + +clear_phm: + charger->rx_phm_status = false; + gpio_direction_output(charger->pdata->ping_nen, 1); + mfc_set_psy_wrl(charger, POWER_SUPPLY_EXT_PROP_RX_PHM, false); + + __pm_relax(charger->wpc_phm_exit_ws); +} + +static void mfc_epp_clear_timer_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, epp_clear_timer_work.work); + + pr_info("%s : bpp -> epp gpio\n", __func__); + mfc_epp_enable(charger, 1); + + __pm_relax(charger->epp_clear_ws); +} + +static void mfc_set_afc_vout_control(struct mfc_charger_data *charger, int vout_mode) +{ + if (is_shutdn || charger->pad_ctrl_by_lcd) { + pr_info("%s: block to set high vout level(vs=%s) because shutdn(%d)\n", + __func__, sb_rx_vout_str(charger->pdata->vout_status), is_shutdn); + return; + } + + if (charger->is_full_status) { + pr_info("%s: block to set high vout level(vs=%s) because full status(%d)\n", + __func__, sb_rx_vout_str(charger->pdata->vout_status), + charger->is_full_status); + return; + } + + if (!charger->is_afc_tx) { + pr_info("%s: need to set afc tx before vout control\n", __func__); + mfc_set_pad_hv(charger, true); + pr_info("%s: is_afc_tx = %d vout read = %d\n", __func__, + charger->is_afc_tx, mfc_get_adc(charger, MFC_ADC_VOUT)); + } + charger->vout_mode = vout_mode; + cancel_delayed_work(&charger->wpc_vout_mode_work); + __pm_stay_awake(charger->wpc_vout_mode_ws); + queue_delayed_work(charger->wqueue, + &charger->wpc_vout_mode_work, msecs_to_jiffies(250)); +} + +static void mfc_recover_vout(struct mfc_charger_data *charger) +{ + int ct = charger->pdata->cable_type; + + pr_info("%s: cable_type =%d\n", __func__, ct); + + if (is_hv_wireless_type(ct) && !is_pwr_nego_wireless_type(ct)) + mfc_set_vout(charger, MFC_VOUT_10V); +} + +#define RX_PHM_CMD_CNT 5 +static void mfc_rx_phm_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, wpc_rx_phm_work.work); + + union power_supply_propval value = {0, }; + u8 pdet_b = 0, wpc_det = 0; + u8 count = RX_PHM_CMD_CNT; + + if (charger->pdata->ping_nen < 0 || charger->pdata->wpc_pdet_b < 0) { + charger->rx_phm_state = NONE_PHM; + __pm_relax(charger->wpc_rx_phm_ws); + return; + } + if (charger->rx_phm_state == ENTER_PHM && !is_phm_supported_pad(charger)) { + pr_info("%s: rx_phm unsupported\n", __func__); + charger->rx_phm_state = NONE_PHM; + __pm_relax(charger->wpc_rx_phm_ws); + return; + } + + switch (charger->rx_phm_state) { + case ENTER_PHM: + pr_info("%s: set ping_nen low, enter phm\n", __func__); + charger->rx_phm_status = true; + gpio_direction_output(charger->pdata->ping_nen, 0); + value.intval = charger->rx_phm_status; + psy_do_property("wireless", set, + POWER_SUPPLY_EXT_PROP_RX_PHM, value); + + while (count-- > 0) { + mfc_send_command(charger, MFC_PHM_ON); + msleep(300); + } + pdet_b = gpio_get_value(charger->pdata->wpc_pdet_b); + wpc_det = gpio_get_value(charger->pdata->wpc_det); + pr_info("%s: check pdet_b = %d, wpc_det = %d for fail case\n", __func__, pdet_b, wpc_det); + if (pdet_b || wpc_det) { + pr_info("%s: set ping_nen high, phm fail case\n", __func__); + charger->rx_phm_status = false; + gpio_direction_output(charger->pdata->ping_nen, 1); + value.intval = charger->rx_phm_status; + psy_do_property("wireless", set, + POWER_SUPPLY_EXT_PROP_RX_PHM, value); + msleep(50); + } else { + value.intval = 1; + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_RX_PHM, value); + } + break; + case EXIT_PHM: + if (charger->rx_phm_status) { + pr_info("%s: set ping_nen high, exit phm\n", __func__); + __pm_stay_awake(charger->wpc_phm_exit_ws); + queue_delayed_work(charger->wqueue, &charger->wpc_phm_exit_work, 0); + } else + pr_info("%s: skip exit phm\n", __func__); + break; + case END_PHM: + if (charger->rx_phm_status) { + pr_info("%s: set ping_nen high, end phm with detach\n", __func__); + charger->rx_phm_status = false; + gpio_direction_output(charger->pdata->ping_nen, 1); + value.intval = charger->rx_phm_status; + psy_do_property("wireless", set, + POWER_SUPPLY_EXT_PROP_RX_PHM, value); + mfc_deactivate_work_content(charger); + } else + pr_info("%s: skip end phm\n", __func__); + break; + case FAILED_PHM: + break; + default: + break; + } + __pm_relax(charger->wpc_rx_phm_ws); +} + +static void print_fod_log(struct mfc_charger_data *charger, union mfc_fod_state *state) +{ + u8 fod_data[MFC_NUM_FOD_REG]; + char str[512] = { 0, }; + int i, ret = 0, str_size = 512; + + snprintf(str, str_size, "[0x%llX][%s]", state->value, sb_wrl_op_mode_str(state->fake_op_mode)); + str_size = sizeof(str) - strlen(str); + + for (i = 0; i < MFC_NUM_FOD_REG; i++) { + ret = mfc_reg_read(charger->client, MFC_WPC_FOD_0A_REG + i, &fod_data[i]); + if (ret < 0) { + pr_info("%s: %s failed to read fod reg ret(%d)\n", __func__, str, ret); + return; + } + + snprintf(str + strlen(str), str_size, " 0x%02X:0x%02X", MFC_WPC_FOD_0A_REG + i, fod_data[i]); + str_size = sizeof(str) - strlen(str); + } + + pr_info("%s: %s\n", __func__, str); +} + +static int nu1668_set_fod(struct device *dev, union mfc_fod_state *state, fod_data_t *data) +{ + struct mfc_charger_data *charger = dev_get_drvdata(dev); + int i, ret = 0; + + if (charger->pdata->cable_type == SEC_BATTERY_CABLE_NONE) + return 0; + if (data == NULL) + return 0; + + for (i = 0; i < MFC_NUM_FOD_REG; i++) { + ret = mfc_reg_write(charger->client, MFC_WPC_FOD_0A_REG + i, data[i]); + if (ret < 0) + goto err_write_reg; + } + + print_fod_log(charger, state); + return 0; + +err_write_reg: + pr_err("%s: failed to write fod reg(ret = %d)\n", __func__, ret); + return ret; +} + +static void nu1668_print_fod(struct mfc_charger_data *charger) +{ + union mfc_fod_state now_state = { 0, }; + + mfc_fod_get_state(charger->fod, &now_state); + charger->now_fod_state.value = now_state.value; + + print_fod_log(charger, &now_state); +} + +static int nu1668_set_cmfet(struct device *dev, union mfc_cmfet_state *state, bool cma, bool cmb) +{ + struct mfc_charger_data *charger = dev_get_drvdata(dev); + int ret = 0; + u8 data; + + if (!charger->det_state) { + pr_info("%s: wireless is disconnected, state(%lld)\n", __func__, state->value); + return 0; + } + + data = ((cma) ? 0xC0 : 0x00) | ((cmb) ? 0x30 : 0x00);//cma1_on:bit7,cma2_on:bit6; cmb1_on:bit5,cma2_on:bit4; + ret = mfc_reg_write(charger->client, MFC_CMFET_CTRL_REG, data); + mfc_reg_read(charger->client, MFC_CMFET_CTRL_REG, &data); + pr_info("%s: state(0x%llX), ret(%d), data(0x%X)\n", __func__, state->value, ret, data); + return ret; +} + +static void nu1668_print_cmfet(struct mfc_charger_data *charger) +{ + union mfc_cmfet_state state = { 0, }; + int ret = 0; + u8 data = 0; + + mfc_cmfet_get_state(charger->cmfet, &state); + charger->now_cmfet_state.value = state.value; + + ret = mfc_reg_read(charger->client, MFC_CMFET_CTRL_REG, &data); + pr_info("%s: [0x%llX] ret = %d, data = 0x%X\n", __func__, state.value, ret, data); +} + +static bool nu1668_set_force_vout(struct mfc_charger_data *charger, int vout) +{ + bool ret = true; + + switch (vout) { + case WIRELESS_VOUT_FORCE_9V: + mfc_set_force_vout(charger, MFC_VOUT_9V); + break; + case WIRELESS_VOUT_FORCE_5V: + mfc_set_force_vout(charger, MFC_VOUT_5V); + break; + case WIRELESS_VOUT_FORCE_4_7V: + mfc_set_force_vout(charger, MFC_VOUT_4_7V); + break; + case WIRELESS_VOUT_FORCE_4_8V: + mfc_set_force_vout(charger, MFC_VOUT_4_8V); + break; + case WIRELESS_VOUT_FORCE_4_9V: + mfc_set_force_vout(charger, MFC_VOUT_4_9V); + break; + default: + ret = false; + break; + } + + return ret; +} + +static bool nu1668_check_detach_status(struct mfc_charger_data *charger) +{ + int pdet_b = 0, wpc_det = 0, vrect = 0; + + if (charger->pdata->irq_wpc_pdet_b) + pdet_b = gpio_get_value(charger->pdata->wpc_pdet_b); + wpc_det = gpio_get_value(charger->pdata->wpc_det); + vrect = mfc_get_adc(charger, MFC_ADC_VRECT); + + if (pdet_b && !wpc_det && (vrect < 0)) + return true; + + return false; +} + +#if defined(CONFIG_UPDATE_BATTERY_DATA) +static int mfc_chg_parse_dt(struct device *dev, mfc_charger_platform_data_t *pdata); +#endif +static int nu1668_chg_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct mfc_charger_data *charger = power_supply_get_drvdata(psy); + enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; + int i = 0; + u8 tmp = 0; + + pr_info("%s: switch(%x), Value(%d)\n", __func__, (int)psp, val->intval); + + switch ((int)psp) { + case POWER_SUPPLY_PROP_STATUS: + if (val->intval == POWER_SUPPLY_STATUS_FULL) { + pr_info("%s: full status\n", __func__); + charger->is_full_status = 1; + if (!is_wireless_fake_type(charger->pdata->cable_type)) { + mfc_fod_set_bat_state(charger->fod, MFC_FOD_BAT_STATE_FULL); + __pm_stay_awake(charger->wpc_cs100_ws); + queue_delayed_work(charger->wqueue, &charger->wpc_cs100_work, msecs_to_jiffies(0)); + } + } else if (val->intval == POWER_SUPPLY_STATUS_NOT_CHARGING) { + if (nu1668_check_detach_status(charger)) { + __pm_stay_awake(charger->wpc_det_ws); + queue_delayed_work(charger->wqueue, &charger->wpc_det_work, 0); + } else { + mfc_mis_align(charger); + } + } else if (val->intval == POWER_SUPPLY_STATUS_CHARGING || + val->intval == POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE) { + if (val->intval == POWER_SUPPLY_STATUS_CHARGING) { + charger->is_full_status = 0; + mfc_set_pad_hv(charger, true); + mfc_recover_vout_by_pad(charger); + } + pr_info("%s: CV status. tx_id(0x%x)\n", __func__, charger->tx_id); + } + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + queue_delayed_work(charger->wqueue, &charger->wpc_init_work, 0); + break; + case POWER_SUPPLY_PROP_HEALTH: + if (val->intval == POWER_SUPPLY_HEALTH_OVERHEAT || + val->intval == POWER_SUPPLY_EXT_HEALTH_OVERHEATLIMIT || + val->intval == POWER_SUPPLY_HEALTH_COLD) + mfc_send_eop(charger, val->intval); + break; + case POWER_SUPPLY_PROP_ONLINE: + pr_info("%s: ept-internal fault\n", __func__); + mfc_reg_write(charger->client, MFC_EPT_REG, MFC_WPC_EPT_INT_FAULT); + mfc_set_cmd_l_reg(charger, MFC_CMD_SEND_EOP_MASK, MFC_CMD_SEND_EOP_MASK); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + if (val->intval) { + charger->is_mst_on = MST_MODE_2; + pr_info("%s: set MST mode 2\n", __func__); + } else { +#if defined(CONFIG_MST_V2) + // it will send MST driver a message. + mfc_send_mst_cmd(MST_MODE_OFF, charger, 0, 0); +#endif + pr_info("%s: set MST mode off\n", __func__); + charger->is_mst_on = MST_MODE_0; + } + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + charger->pdata->otp_firmware_result = val->intval; + pr_info("%s: otp_firmware result initialize (%d)\n", __func__, + charger->pdata->otp_firmware_result); + break; + case POWER_SUPPLY_PROP_ENERGY_NOW: + if (!charger->rx_phm_status) { + if (charger->wc_tx_enable) { + pr_info("@Tx_Mode %s: FW Ver(0x%x) TX_VOUT(%dmV) TX_IOUT(%dmA), PHM(%d), %s connected\n", __func__, + charger->pdata->otp_firmware_ver, + mfc_get_adc(charger, MFC_ADC_TX_VOUT), + mfc_get_adc(charger, MFC_ADC_TX_IOUT), + charger->tx_device_phm, + sb_rx_type_str(charger->wc_rx_type)); + pr_info("@Tx_Mode %s: PING_FRQ(%dKHz) OP_FRQ(%dKHz) TX_MIN_FRQ(%dKHz) TX_MAX_FRQ(%dKHz)\n", + __func__, + mfc_get_adc(charger, MFC_ADC_PING_FRQ), + mfc_get_adc(charger, MFC_ADC_OP_FRQ), + mfc_get_adc(charger, MFC_ADC_TX_MIN_OP_FRQ), + mfc_get_adc(charger, MFC_ADC_TX_MAX_OP_FRQ)); + } else if (charger->pdata->cable_type != SEC_BATTERY_CABLE_NONE) { + pr_info("%s: FW Ver(%x) RX_VOUT(%dmV) RX_VRECT(%dmV) RX_IOUT(%dmA)\n", __func__, + charger->pdata->otp_firmware_ver, + mfc_get_adc(charger, MFC_ADC_VOUT), + mfc_get_adc(charger, MFC_ADC_VRECT), + mfc_get_adc(charger, MFC_ADC_RX_IOUT)); + pr_info("%s: OP_FRQ(%dKHz) TX ID(0x%x) IC Rev(0x%x)\n", __func__, + mfc_get_adc(charger, MFC_ADC_OP_FRQ), + charger->tx_id, + charger->pdata->wc_ic_rev); + + nu1668_print_fod(charger); + nu1668_print_cmfet(charger); + } + } + break; + case POWER_SUPPLY_PROP_CAPACITY: + break; + case POWER_SUPPLY_PROP_CHARGE_EMPTY: + { + int vout = 0, vrect = 0; + u8 is_vout_on = 0; + bool error = false; + + if (mfc_reg_read(charger->client, MFC_STATUS_L_REG, &is_vout_on) < 0) + error = true; + is_vout_on = is_vout_on >> 7; + vout = mfc_get_adc(charger, MFC_ADC_VOUT); + vrect = mfc_get_adc(charger, MFC_ADC_VRECT); + pr_info("%s: SET MFC LDO (%s), Current VOUT STAT (%d), RX_VOUT = %dmV, RX_VRECT = %dmV, error(%d)\n", + __func__, (val->intval == MFC_LDO_ON ? "ON" : "OFF"), is_vout_on, vout, vrect, error); + if ((val->intval == MFC_LDO_ON) && (!is_vout_on || error)) { /* LDO ON */ + pr_info("%s: MFC LDO ON toggle ------------ cable_work\n", __func__); + for (i = 0; i < 2; i++) { + mfc_reg_read(charger->client, MFC_STATUS_L_REG, &is_vout_on); + is_vout_on = is_vout_on >> 7; + if (!is_vout_on) + mfc_set_cmd_l_reg(charger, MFC_CMD_TOGGLE_LDO_MASK, MFC_CMD_TOGGLE_LDO_MASK); + else + break; + msleep(500); + mfc_reg_read(charger->client, MFC_STATUS_L_REG, &is_vout_on); + is_vout_on = is_vout_on >> 7; + if (is_vout_on) { + pr_info("%s: cnt = %d, LDO is ON -> MFC LDO STAT(%d)\n", + __func__, i, is_vout_on); + break; + } + msleep(1000); + vout = mfc_get_adc(charger, MFC_ADC_VOUT); + vrect = mfc_get_adc(charger, MFC_ADC_VRECT); + pr_info("%s: cnt = %d, LDO Should ON -> MFC LDO STAT(%d), RX_VOUT = %dmV, RX_VRECT = %dmV\n", + __func__, i, is_vout_on, vout, vrect); + } + charger->wc_ldo_status = MFC_LDO_ON; + mfc_recover_vout(charger); + } else if ((val->intval == MFC_LDO_OFF) && (is_vout_on || error)) { /* LDO OFF */ + pr_info("%s: MFC LDO OFF toggle ------------ cable_work\n", __func__); + mfc_set_vout(charger, MFC_VOUT_5V); + msleep(300); + mfc_set_cmd_l_reg(charger, MFC_CMD_TOGGLE_LDO_MASK, MFC_CMD_TOGGLE_LDO_MASK); + msleep(400); + mfc_reg_read(charger->client, MFC_STATUS_L_REG, &is_vout_on); + is_vout_on = is_vout_on >> 7; + vout = mfc_get_adc(charger, MFC_ADC_VOUT); + vrect = mfc_get_adc(charger, MFC_ADC_VRECT); + pr_info("%s: LDO Should OFF -> MFC LDO STAT(%d), RX_VOUT = %dmV, RX_VRECT = %dmV\n", + __func__, is_vout_on, vout, vrect); + charger->wc_ldo_status = MFC_LDO_OFF; + } + } + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + charger->input_current = val->intval; + pr_info("%s: input_current: %d\n", __func__, charger->input_current); + break; + case POWER_SUPPLY_PROP_SCOPE: + return -ENODATA; + case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: + switch (ext_psp) { + case POWER_SUPPLY_EXT_PROP_WC_CONTROL: + if (val->intval == 0) { + tmp = 0x01; + mfc_send_packet(charger, MFC_HEADER_AFC_CONF, + 0x20, &tmp, 1); + pr_info("%s: send command after wc control\n", __func__); + msleep(150); + } + break; + case POWER_SUPPLY_EXT_PROP_WC_EPT_UNKNOWN: + if (val->intval == 1) + mfc_send_ept_unknown(charger); + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_SWITCH: + /* + * It is a RX device , send a packet to TX device to stop power sharing. + * TX device will have MFC_INTA_H_TRX_DATA_RECEIVED_MASK irq + */ + if (charger->pdata->cable_type == SEC_BATTERY_CABLE_WIRELESS_TX) { + if (val->intval) { + pr_info("%s: It is a RX device , send a packet to TX device to stop power sharing\n", + __func__); + mfc_send_command(charger, MFC_DISABLE_TX); + } + } + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_SEND_FSK: + /* send fsk packet for rx device aicl reset */ + if (val->intval && (charger->wc_rx_type != SS_GEAR)) { + pr_info("@Tx_mode %s: Send FSK packet for Rx device aicl reset\n", __func__); + mfc_send_fsk(charger, WPC_TX_COM_WPS, WPS_AICL_RESET); + } + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ENABLE: + /* on/off tx power */ + mfc_set_tx_power(charger, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_RX_CONNECTED: + charger->wc_rx_connected = val->intval; + queue_delayed_work(charger->wqueue, &charger->wpc_rx_connection_work, 0); + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_AUTH_ADT_STATUS: /* it has only PASS and FAIL */ + nu1668_adt_transfer_result(charger, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_AUTH_ADT_DATA: /* data from auth service will be sent */ + if (charger->pdata->cable_type != SEC_BATTERY_CABLE_NONE) { + u8 *p_data; + + p_data = (u8 *)val->strval; + + for (i = 0; i < adt_readSize; i++) + pr_info("%s %s: p_data[%d] = %x\n", WC_AUTH_MSG, __func__, i, p_data[i]); + mfc_auth_adt_send(charger, p_data, adt_readSize); + + auth_read_data_step = 0; + } + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_AUTH_ADT_SIZE: + if (charger->pdata->cable_type != SEC_BATTERY_CABLE_NONE) + adt_readSize = val->intval; + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_RX_TYPE: + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_VOUT: + mfc_set_tx_vout(charger, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_IOUT: + mfc_set_tx_iout(charger, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_TIMER_ON: + pr_info("%s %s: TX receiver detecting timer enable(%d)\n", WC_AUTH_MSG, __func__, val->intval); + if (charger->wc_tx_enable) { + if (val->intval) { + pr_info("%s %s: enable TX OFF timer (90sec)", WC_AUTH_MSG, __func__); + mfc_reg_update(charger->client, MFC_INT_A_ENABLE_H_REG, (0x1 << 5), (0x1 << 5)); + } else { + pr_info("%s %s: disable TX OFF timer (90sec)", WC_AUTH_MSG, __func__); + mfc_reg_update(charger->client, MFC_INT_A_ENABLE_H_REG, 0x0, (0x1 << 5)); + } + } else { + pr_info("%s %s: Don't need to set TX 90sec timer, on TX OFF state\n", + WC_AUTH_MSG, __func__); + } + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_MIN_DUTY: + if (delayed_work_pending(&charger->wpc_tx_duty_min_work)) { + __pm_relax(charger->wpc_tx_duty_min_ws); + cancel_delayed_work(&charger->wpc_tx_duty_min_work); + } + charger->duty_min = val->intval; + mfc_set_min_duty(charger, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_CALL_EVENT: + if (val->intval & BATT_EXT_EVENT_CALL) { + charger->device_event |= BATT_EXT_EVENT_CALL; + +#if defined(CONFIG_WIRELESS_RX_PHM_CTRL) + charger->rx_phm_state = ENTER_PHM; + __pm_stay_awake(charger->wpc_rx_phm_ws); + queue_delayed_work(charger->wqueue, &charger->wpc_rx_phm_work, 0); +#else + /* call in is after wireless connection */ + if (charger->pdata->cable_type == SEC_BATTERY_CABLE_WIRELESS_PACK || + charger->pdata->cable_type == SEC_BATTERY_CABLE_WIRELESS_HV_PACK || + charger->pdata->cable_type == SEC_BATTERY_CABLE_WIRELESS_TX) { + union power_supply_propval value2; + + pr_info("%s %s: enter PHM\n", WC_TX_MSG, __func__); + /* notify "wireless" PHM status */ + value2.intval = 1; + psy_do_property("wireless", set, POWER_SUPPLY_EXT_PROP_CALL_EVENT, value2); + mfc_send_command(charger, MFC_PHM_ON); + msleep(250); + mfc_send_command(charger, MFC_PHM_ON); + } +#endif + } else if (val->intval == BATT_EXT_EVENT_NONE) { + if (charger->device_event & BATT_EXT_EVENT_CALL) { + charger->device_event &= ~BATT_EXT_EVENT_CALL; +#if defined(CONFIG_WIRELESS_RX_PHM_CTRL) + charger->rx_phm_state = EXIT_PHM; + __pm_stay_awake(charger->wpc_rx_phm_ws); + queue_delayed_work(charger->wqueue, &charger->wpc_rx_phm_work, 0); +#endif + } + } + break; + case POWER_SUPPLY_EXT_PROP_RX_PHM: + charger->rx_phm_state = val->intval; + if (charger->tx_id_done) + queue_delayed_work(charger->wqueue, &charger->wpc_rx_phm_work, 0); + else + queue_delayed_work(charger->wqueue, &charger->wpc_rx_phm_work, msecs_to_jiffies(20000)); + break; + case POWER_SUPPLY_EXT_PROP_SLEEP_MODE: + charger->sleep_mode = val->intval; + break; + case POWER_SUPPLY_EXT_PROP_PAD_VOLT_CTRL: + if (delayed_work_pending(&charger->wpc_vout_mode_work)) { + pr_info("%s: Already vout change. skip pad control\n", __func__); + return 0; + } + + if (!volt_ctrl_pad(charger->tx_id)) + break; + + if (val->intval && charger->is_afc_tx) { + pr_info("%s: set TX 5V because LCD ON\n", __func__); + mfc_set_pad_hv(charger, false); + charger->pad_ctrl_by_lcd = true; + } else if (!val->intval && !charger->is_afc_tx && charger->pad_ctrl_by_lcd) { + mfc_set_pad_hv(charger, true); + charger->pad_ctrl_by_lcd = false; + pr_info("%s: need to set afc tx because LCD OFF\n", __func__); + } + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_VOUT: + for (i = 0; i < charger->pdata->len_wc20_list; i++) + charger->pdata->wireless20_vout_list[i] = val->intval; + + pr_info("%s: vout(%d) len(%d)\n", __func__, val->intval, i - 1); + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_1ST_DONE: + /* 1st chg done ~ 2nd chg done : CMA+CMB (samsung pad only) */ + mfc_cmfet_set_full(charger->cmfet, true); + pr_info("%s: 1st wireless charging done! CMA CMB\n", __func__); + mfc_set_vout_ctrl_1st_full(charger); + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_2ND_DONE: + mfc_cmfet_set_chg_done(charger->cmfet, true); + pr_info("%s: 2nd wireless charging done! CMA only\n", __func__); + mfc_set_vout_ctrl_2nd_full(charger); + break; + case POWER_SUPPLY_EXT_PROP_WPC_EN: + mfc_set_wpc_en(charger, val->strval[0], val->strval[1]); + break; + case POWER_SUPPLY_EXT_PROP_WPC_EN_MST: + if (val->intval) + mfc_set_wpc_en(charger, WPC_EN_MST, true); + else + mfc_set_wpc_en(charger, WPC_EN_MST, false); + break; + case POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL: + if (val->intval) { + pr_info("%s: mfc otg on\n", __func__); + charger->is_otg_on = true; + } else { + pr_info("%s: mfc otg off\n", __func__); + charger->is_otg_on = false; + } + break; + case POWER_SUPPLY_EXT_PROP_CHARGE_POWERED_OTG_CONTROL: + { + unsigned int work_state; + + mutex_lock(&charger->fw_lock); + /* check delayed work state */ + work_state = work_busy(&charger->wpc_fw_update_work.work); + pr_info("%s: check fw_work state(0x%x)\n", __func__, work_state); + if (work_state & (WORK_BUSY_PENDING | WORK_BUSY_RUNNING)) { + pr_info("%s: skip update_fw!!\n", __func__); + } else { + charger->fw_cmd = val->intval; + __pm_stay_awake(charger->wpc_update_ws); + queue_delayed_work(charger->wqueue, &charger->wpc_fw_update_work, 0); + pr_info("%s: rx result = %d, tx result = %d\n", __func__, + charger->pdata->otp_firmware_result, + charger->pdata->tx_firmware_result); + } + mutex_unlock(&charger->fw_lock); + } + break; + case POWER_SUPPLY_EXT_PROP_INPUT_VOLTAGE_REGULATION: + if (val->intval == WIRELESS_VOUT_NORMAL_VOLTAGE) { + pr_info("%s: Wireless Vout forced set to 5V. + PAD CMD\n", __func__); + for (i = 0; i < CMD_CNT; i++) { + mfc_send_command(charger, MFC_AFC_CONF_5V); + msleep(250); + } + } else if (val->intval == WIRELESS_VOUT_HIGH_VOLTAGE) { + pr_info("%s: Wireless Vout forced set to 10V. + PAD CMD\n", __func__); + for (i = 0; i < CMD_CNT; i++) { + mfc_send_command(charger, MFC_AFC_CONF_10V); + msleep(250); + } + } else if (val->intval == WIRELESS_VOUT_CC_CV_VOUT) { + charger->vout_mode = val->intval; + cancel_delayed_work(&charger->wpc_vout_mode_work); + __pm_stay_awake(charger->wpc_vout_mode_ws); + queue_delayed_work(charger->wqueue, &charger->wpc_vout_mode_work, 0); + } else if (val->intval == WIRELESS_VOUT_5V || + val->intval == WIRELESS_VOUT_5V_STEP || + val->intval == WIRELESS_VOUT_5_5V_STEP || + val->intval == WIRELESS_VOUT_OTG) { + int def_delay = 0; + + charger->vout_mode = val->intval; + if (is_hv_wireless_type(charger->pdata->cable_type) && + val->intval != WIRELESS_VOUT_OTG) + def_delay = 250; + cancel_delayed_work(&charger->wpc_vout_mode_work); + __pm_stay_awake(charger->wpc_vout_mode_ws); + queue_delayed_work(charger->wqueue, + &charger->wpc_vout_mode_work, msecs_to_jiffies(def_delay)); + } else if (val->intval == WIRELESS_VOUT_9V || + val->intval == WIRELESS_VOUT_10V || + val->intval == WIRELESS_VOUT_11V || + val->intval == WIRELESS_VOUT_12V || + val->intval == WIRELESS_VOUT_12_5V || + val->intval == WIRELESS_VOUT_9V_STEP || + val->intval == WIRELESS_VOUT_10V_STEP) { + pr_info("%s: vout (%d)\n", __func__, val->intval); + mfc_set_afc_vout_control(charger, val->intval); + } else { + if (!nu1668_set_force_vout(charger, val->intval)) + pr_info("%s: Unknown Command(%d)\n", __func__, val->intval); + } + break; + case POWER_SUPPLY_EXT_PROP_WIRELESS_RX_CONTROL: + if (val->intval == WIRELESS_PAD_FAN_OFF) { + pr_info("%s: fan off\n", __func__); + mfc_fan_control(charger, 0); + } else if (val->intval == WIRELESS_PAD_FAN_ON) { + pr_info("%s: fan on\n", __func__); + mfc_fan_control(charger, 1); + } else if (val->intval == WIRELESS_PAD_LED_OFF) { + pr_info("%s: led off\n", __func__); + mfc_led_control(charger, MFC_LED_CONTROL_OFF); + } else if (val->intval == WIRELESS_PAD_LED_ON) { + pr_info("%s: led on\n", __func__); + mfc_led_control(charger, MFC_LED_CONTROL_ON); + } else if (val->intval == WIRELESS_PAD_LED_DIMMING) { + pr_info("%s: led dimming\n", __func__); + mfc_led_control(charger, MFC_LED_CONTROL_DIMMING); + } else if (val->intval == WIRELESS_VRECT_ADJ_ON) { + pr_info("%s: vrect adjust to have big headroom(default value)\n", __func__); + mfc_set_vrect_adjust(charger, MFC_HEADROOM_1); + } else if (val->intval == WIRELESS_VRECT_ADJ_OFF) { + pr_info("%s: vrect adjust to have small headroom\n", __func__); + mfc_set_vrect_adjust(charger, MFC_HEADROOM_0); + } else if (val->intval == WIRELESS_VRECT_ADJ_ROOM_0) { + pr_info("%s: vrect adjust to have headroom 0(0mV)\n", __func__); + mfc_set_vrect_adjust(charger, MFC_HEADROOM_0); + } else if (val->intval == WIRELESS_VRECT_ADJ_ROOM_1) { + pr_info("%s: vrect adjust to have headroom 1(277mV)\n", __func__); + mfc_set_vrect_adjust(charger, MFC_HEADROOM_1); + } else if (val->intval == WIRELESS_VRECT_ADJ_ROOM_2) { + pr_info("%s: vrect adjust to have headroom 2(497mV)\n", __func__); + mfc_set_vrect_adjust(charger, MFC_HEADROOM_2); + } else if (val->intval == WIRELESS_VRECT_ADJ_ROOM_3) { + pr_info("%s: vrect adjust to have headroom 3(650mV)\n", __func__); + mfc_set_vrect_adjust(charger, MFC_HEADROOM_3); + } else if (val->intval == WIRELESS_VRECT_ADJ_ROOM_4) { + pr_info("%s: vrect adjust to have headroom 4(30mV)\n", __func__); + mfc_set_vrect_adjust(charger, MFC_HEADROOM_4); + } else if (val->intval == WIRELESS_VRECT_ADJ_ROOM_5) { + pr_info("%s: vrect adjust to have headroom 5(82mV)\n", __func__); + mfc_set_vrect_adjust(charger, MFC_HEADROOM_5); + } else if (val->intval == WIRELESS_CLAMP_ENABLE) { + pr_info("%s: enable clamp1, clamp2 for WPC modulation\n", __func__); + } else if (val->intval == WIRELESS_SLEEP_MODE_ENABLE) { + if (is_sleep_mode_active(charger->tx_id)) { + pr_info("%s: sleep_mode enable\n", __func__); + msleep(500); + pr_info("%s: led dimming\n", __func__); + mfc_led_control(charger, MFC_LED_CONTROL_DIMMING); + msleep(500); + pr_info("%s: fan off\n", __func__); + mfc_fan_control(charger, 0); + } else { + pr_info("%s: sleep_mode inactive\n", __func__); + } + } else if (val->intval == WIRELESS_SLEEP_MODE_DISABLE) { + if (is_sleep_mode_active(charger->tx_id)) { + pr_info("%s: sleep_mode disable\n", __func__); + msleep(500); + pr_info("%s: led on\n", __func__); + mfc_led_control(charger, MFC_LED_CONTROL_ON); + msleep(500); + pr_info("%s: fan on\n", __func__); + mfc_fan_control(charger, 1); + } else { + pr_info("%s: sleep_mode inactive\n", __func__); + } + } else { + pr_info("%s: Unknown Command(%d)\n", __func__, val->intval); + } + break; +#if defined(CONFIG_UPDATE_BATTERY_DATA) + case POWER_SUPPLY_EXT_PROP_POWER_DESIGN: + mfc_chg_parse_dt(charger->dev, charger->pdata); + break; +#endif + case POWER_SUPPLY_EXT_PROP_FILTER_CFG: + charger->led_cover = val->intval; + pr_info("%s: LED_COVER(%d)\n", __func__, charger->led_cover); + break; + case POWER_SUPPLY_EXT_PROP_TX_PWR_BUDG: + break; + case POWER_SUPPLY_EXT_PROP_MPP_COVER: + pr_info("@MPP %s: MPP_COVER(%d)\n", __func__, val->intval); + //mfc_mpp_enable(charger, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_MPP_CLOAK: + pr_info("@MPP %s: MPP_CLOAK(%d)\n", __func__, val->intval); + if (val->intval == 1) + mfc_mpp_enter_cloak(charger, MFC_TRX_MPP_CLOAK_PTX_INITIATED); + else// if (val->intval == 0) + mfc_mpp_exit_cloak(charger); +#if 0 + else { + pr_info("@MPP %s: coil sw (%d)\n", __func__, gpio_get_value(charger->pdata->mpp_sw)); + if (gpio_get_value(charger->pdata->mpp_sw)) { + gpio_direction_output(charger->pdata->mpp_sw, 0); + } + pr_info("@MPP %s: exit cloak coil sw (%d)\n", __func__, gpio_get_value(charger->pdata->mpp_sw)); + } +#endif + break; + case POWER_SUPPLY_EXT_PROP_MPP_ICL_CTRL: + pr_info("@MPP %s: MPP_THERMAL_CTRL(%d)\n", __func__, val->intval); + //mfc_mpp_thermal_ctrl(charger, val->intval); + break; + case POWER_SUPPLY_EXT_PROP_MPP_INC_INT_CTRL: + pr_info("@MPP %s: %s INCREASE INT\n", __func__, val->intval ? "ENABLE" : "DISABLE"); +#if 0 + if (val->intval == 1) + mfc_mpp_epp_nego_power_set(charger, MFC_RX_MPP_NEGO_POWER_15W); // need delay? +#endif + mfc_mpp_inc_int_ctrl(charger, val->intval); + break; + // TODO: Need to add for EPP Q_Value + // TODO: Need to add for EPP Ref_Freq_Value + default: + return -ENODATA; + } + break; + default: + return -ENODATA; + } + + return 0; +} + +static u32 mfc_get_wireless20_vout_by_txid(struct mfc_charger_data *charger, u8 txid) +{ + u32 vout = 0, i = 0; + + if (txid < TX_ID_AUTH_PAD || txid > TX_ID_AUTH_PAD_END) { + pr_info("%s: wrong tx id(0x%x) for wireless 2.0\n", __func__, txid); + i = 0; /* defalut value is WIRELESS_VOUT_10V */ + } else if (txid >= TX_ID_AUTH_PAD + charger->pdata->len_wc20_list) { + pr_info("%s: undefined tx id(0x%x) of the device\n", __func__, txid); + i = charger->pdata->len_wc20_list - 1; + } else + i = txid - TX_ID_AUTH_PAD; + + vout = charger->pdata->wireless20_vout_list[i]; + pr_info("%s: vout = %d\n", __func__, vout); + return vout; +} + +static u32 mfc_get_wireless20_vrect_by_txid(struct mfc_charger_data *charger, u8 txid) +{ + u32 vrect = 0, i = 0; + + if (txid < TX_ID_AUTH_PAD || txid > TX_ID_AUTH_PAD_END) { + pr_info("%s: wrong tx id(0x%x) for wireless 2.0\n", __func__, txid); + i = 0; /* defalut value is MFC_AFC_CONF_10V_TX */ + } else if (txid >= TX_ID_AUTH_PAD + charger->pdata->len_wc20_list) { + pr_info("%s: undefined tx id(0x%x) of the device\n", __func__, txid); + i = charger->pdata->len_wc20_list - 1; + } else { + i = txid - TX_ID_AUTH_PAD; + } + + vrect = charger->pdata->wireless20_vrect_list[i]; + pr_info("%s: vrect = %d\n", __func__, vrect); + return vrect; +} + +static u32 mfc_get_wireless20_max_power_by_txid(struct mfc_charger_data *charger, u8 txid) +{ + u32 max_power = 0, i = 0; + + if (txid < TX_ID_AUTH_PAD || txid > TX_ID_AUTH_PAD_END) { + pr_info("%s: wrong tx id(0x%x) for wireless 2.0\n", __func__, txid); + i = 0; /* defalut value is SEC_WIRELESS_RX_POWER_12W */ + } else if (txid >= TX_ID_AUTH_PAD + charger->pdata->len_wc20_list) { + pr_info("%s: undefined tx id(0x%x) of the device\n", __func__, txid); + i = charger->pdata->len_wc20_list - 1; + } else { + i = txid - TX_ID_AUTH_PAD; + } + + max_power = charger->pdata->wireless20_max_power_list[i]; + pr_info("%s: max rx power = %d\n", __func__, max_power); + return max_power; +} + +static void mfc_recv_tx_pwr_budg(struct mfc_charger_data *charger, u8 data) +{ + switch (data) { + case MFC_TX_PWR_BUDG_2W: + pr_info("%s: TX POWER BUDGET 2W\n", __func__); + charger->tx_pwr_budg = MFC_TX_PWR_BUDG_2W; + break; + case MFC_TX_PWR_BUDG_5W: + pr_info("%s: TX POWER BUDGET 5W\n", __func__); + charger->tx_pwr_budg = MFC_TX_PWR_BUDG_5W; + break; + case MFC_TX_PWR_BUDG_7_5W: + pr_info("%s: TX POWER BUDGET 7.5W\n", __func__); + charger->tx_pwr_budg = MFC_TX_PWR_BUDG_7_5W; + break; + case MFC_TX_PWR_BUDG_12W: + pr_info("%s: TX POWER BUDGET 12W\n", __func__); + charger->tx_pwr_budg = MFC_TX_PWR_BUDG_12W; + break; + case MFC_TX_PWR_BUDG_15W: + pr_info("%s: TX POWER BUDGET 15W\n", __func__); + charger->tx_pwr_budg = MFC_TX_PWR_BUDG_15W; + break; + default: + pr_info("%s: NOT DEFINED TX POWER BUDGET(%d)\n", __func__, data); + charger->tx_pwr_budg = MFC_TX_PWR_BUDG_NONE; + break; + } +} + +static void mfc_activate_work_content(struct mfc_charger_data *charger) +{ + int cable_type; + u8 pad_mode; + u8 vrect; + + charger->initial_wc_check = true; + charger->pdata->otp_firmware_ver = mfc_get_firmware_version(charger, MFC_RX_FIRMWARE); + charger->pdata->wc_ic_rev = mfc_get_ic_revision(charger, MFC_IC_REVISION); + + charger->wc_tx_enable = false; + + /* enable Mode Change INT */ + mfc_reg_update(charger->client, MFC_INT_A_ENABLE_L_REG, + MFC_STAT_L_OP_MODE_MASK, MFC_STAT_L_OP_MODE_MASK); + mfc_reg_update(charger->client, MFC_INT_A_ENABLE_L_REG, + MFC_STAT_L_OVER_TEMP_MASK, MFC_STAT_L_OVER_TEMP_MASK); + + if (!charger->pdata->default_clamp_volt) { + /* SET OV 21V */ /*0 = 14V, 1 = 16V, 2 = 18V, 3 = 20V, 4=21V, 5= 22V, others: reserved.*/ + mfc_reg_write(charger->client, MFC_RX_OV_CLAMP_REG, 0x5); + } + + /* SET ILIM 1.5A */ /* ILim = value * 0.1(A) + 0.1(A). 1.5A = 14*0.1 + 0.1 */ + mfc_reg_write(charger->client, MFC_ILIM_SET_REG, 0x0E); + + /* read vrect adjust */ + mfc_reg_read(charger->client, MFC_VRECT_ADJ_REG, &vrect); + + pr_info("%s: wireless charger activated, set V_INT as PN\n", __func__); + + /* CMA/CMB refresh */ + mfc_cmfet_refresh(charger->cmfet); + + /* read pad mode */ + mfc_reg_read(charger->client, MFC_SYS_OP_MODE_REG, &pad_mode); + charger->rx_op_mode = pad_mode >> 5; + pr_info("%s: Rx op_mode (0x%x)\n", __func__, charger->rx_op_mode); + + cable_type = charger->pdata->cable_type; + charger->pdata->is_charging = 1; + if ((cable_type == SEC_BATTERY_CABLE_NONE) || + is_wireless_fake_type(cable_type)) { + if (bpp_mode(charger->rx_op_mode)) { + cable_type = SEC_BATTERY_CABLE_WIRELESS; + + if (!charger->is_otg_on) { + charger->pdata->vout_status = MFC_VOUT_5_5V; + mfc_set_vout(charger, charger->pdata->vout_status); + } + + if (!is_no_hv(charger)) { + if (mfc_check_to_start_afc_tx(charger)) { + mfc_start_bpp_mode(charger); + } else { + /* reboot status with previous hv voltage setting */ + /* re-set vout level */ + charger->pad_vout = PAD_VOUT_10V; + mfc_set_vout(charger, MFC_VOUT_10V); + + /* change cable type */ + cable_type = SEC_BATTERY_CABLE_HV_WIRELESS; + } + } + mfc_start_wpc_tx_id_work(charger, 2500); + mfc_set_online(charger, cable_type); + } else if (charger->rx_op_mode == PAD_RX_MODE_WPC_EPP) { + if ((cable_type != SEC_BATTERY_CABLE_WIRELESS_EPP) && + (cable_type != SEC_BATTERY_CABLE_WIRELESS_EPP_NV)) { + mfc_mpp_epp_nego_done(charger); + cancel_delayed_work(&charger->mode_change_work); + __pm_stay_awake(charger->mode_change_ws); + queue_delayed_work(charger->wqueue, &charger->mode_change_work, 0); + } + + if (is_samsung_pad((charger->mpp_epp_tx_id & 0xFF)) || + is_3rd_pad((charger->mpp_epp_tx_id & 0xFFFF))) + charger->epp_time = 6000; + } else if (charger->rx_op_mode == PAD_RX_MODE_WPC_EPP_NEGO) { + if (cable_type != SEC_BATTERY_CABLE_WIRELESS_EPP_FAKE) + mfc_mpp_epp_nego_done(charger); + + if (is_samsung_pad((charger->mpp_epp_tx_id & 0xFF)) || + is_3rd_pad((charger->mpp_epp_tx_id & 0xFFFF))) + charger->epp_time = 6000; + } + mfc_fod_set_op_mode(charger->fod, nu1668_get_op_mode(charger)); + } + mfc_fod_refresh(charger->fod); +} + +static void mfc_deactivate_work_content(struct mfc_charger_data *charger) +{ + union power_supply_propval value; + + /* Send last tx_id to battery to count tx_id */ + value.intval = charger->tx_id; + psy_do_property("wireless", set, POWER_SUPPLY_PROP_AUTHENTIC, value); + + charger->pdata->cable_type = SEC_BATTERY_CABLE_NONE; + charger->pdata->is_charging = 0; + charger->pdata->vout_status = MFC_VOUT_5V; + charger->pad_vout = PAD_VOUT_5V; + charger->pdata->opfq_cnt = 0; + charger->pdata->trx_data_cmd = 0; + charger->pdata->trx_data_val = 0; + charger->vout_mode = 0; + charger->is_full_status = 0; + charger->is_afc_tx = false; + charger->pad_ctrl_by_lcd = false; + charger->tx_id = TX_ID_UNKNOWN; + charger->i2c_error_count = 0; + charger->adt_transfer_status = WIRELESS_AUTH_WAIT; + charger->current_rx_power = TX_RX_POWER_0W; + charger->tx_id_cnt = 0; + charger->wc_ldo_status = MFC_LDO_ON; + charger->tx_id_done = false; + charger->req_tx_id = false; + charger->req_afc_delay = REQ_AFC_DLY; + charger->afc_tx_done = false; + charger->wc_align_check_start.tv_sec = 0; + charger->vout_strength = 100; + charger->rx_phm_state = NONE_PHM; + charger->check_rx_power = false; + charger->tx_pwr_budg = MFC_TX_PWR_BUDG_NONE; + //TODO: Need to check from here + //charger->epp_supported = false; + //charger->epp_nego_pass = false; + //charger->epp_nego_fail = false; + + //charger->mpp_supported = false; + //charger->mpp_nego_pass_360 = false; + //charger->mpp_power_inc = false; + //charger->mpp_power_dec = false; + //charger->mpp_exit_cloak = false; + + charger->nego_done_power = 0; + charger->potential_load_power = 0; + charger->negotiable_load_power = 0; + charger->mpp_epp_tx_id = 0; + charger->mpp_cloak = 0; + charger->rx_op_mode = 0; + //TODO: Need to check to here + + charger->vout_by_txid = 0; + charger->vrect_by_txid = 0; + charger->max_power_by_txid = 0; + + mfc_mpp_exit_cloak(charger); + + mfc_set_online(charger, SEC_BATTERY_CABLE_NONE); + mfc_fod_init_state(charger->fod); + mfc_cmfet_init_state(charger->cmfet); + + /* tx retry case for mis-align */ + if (charger->wc_checking_align) { + /* reset try cnt in real detach status */ + if (!gpio_get_value(charger->pdata->wpc_en)) + charger->mis_align_tx_try_cnt = 1; + else { + if (charger->mis_align_tx_try_cnt > MISALIGN_TX_TRY_CNT) + charger->mis_align_tx_try_cnt = 1; + msleep(1000); + mfc_set_wpc_en(charger, WPC_EN_CHARGING, true); + } + } else + charger->mis_align_tx_try_cnt = 1; + + charger->wc_checking_align = false; + + pr_info("%s: wpc deactivated, set V_INT as PD\n", __func__); + mfc_epp_clear_timer_work_start(charger, charger->epp_time); + charger->epp_time = 1000; + + if (delayed_work_pending(&charger->wpc_afc_vout_work)) { + __pm_relax(charger->wpc_afc_vout_ws); + cancel_delayed_work(&charger->wpc_afc_vout_work); + } + if (delayed_work_pending(&charger->wpc_vout_mode_work)) { + __pm_relax(charger->wpc_vout_mode_ws); + cancel_delayed_work(&charger->wpc_vout_mode_work); + } + if (delayed_work_pending(&charger->wpc_cs100_work)) { + __pm_relax(charger->wpc_cs100_ws); + cancel_delayed_work(&charger->wpc_cs100_work); + } + if (delayed_work_pending(&charger->wpc_rx_phm_work)) { + __pm_relax(charger->wpc_rx_phm_ws); + cancel_delayed_work(&charger->wpc_rx_phm_work); + } + if (delayed_work_pending(&charger->wpc_rx_power_trans_fail_work)) { + __pm_relax(charger->wpc_rx_power_trans_fail_ws); + cancel_delayed_work(&charger->wpc_rx_power_trans_fail_work); + } + + cancel_delayed_work(&charger->wpc_isr_work); + cancel_delayed_work(&charger->wpc_tx_isr_work); + cancel_delayed_work(&charger->wpc_tx_id_work); + cancel_delayed_work(&charger->wpc_tx_pwr_budg_work); + cancel_delayed_work(&charger->wpc_i2c_error_work); + cancel_delayed_work(&charger->align_check_work); + __pm_relax(charger->wpc_rx_ws); + __pm_relax(charger->wpc_tx_ws); + __pm_relax(charger->wpc_tx_id_ws); + __pm_relax(charger->wpc_tx_pwr_budg_ws); + __pm_relax(charger->align_check_ws); +} + +static void mfc_wpc_det_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, wpc_det_work.work); + + u8 pdet_b = 0, p_nen = 0; + + pr_info("%s : start\n", __func__); + + /* + * We don't have to handle the wpc detect handling, + * when it's the MST mode. + */ + if (charger->is_mst_on == MST_MODE_2) { + pr_info("%s: check det_state(%d - %d)\n", __func__, + charger->det_state, gpio_get_value(charger->pdata->wpc_det)); + + if (charger->det_state == 0) { + pr_info("%s: skip wpc_det_work for MST operation\n", __func__); + goto end_det_work; + } + } + + if (charger->pdata->wpc_pdet_b >= 0) + pdet_b = gpio_get_value(charger->pdata->wpc_pdet_b); + if (charger->pdata->ping_nen >= 0) + p_nen = gpio_get_value(charger->pdata->ping_nen); + pr_info("%s: det = %d, pdet_b = %d, ping_nen = %d\n", + __func__, gpio_get_value(charger->pdata->wpc_det), pdet_b, p_nen); + + if (charger->det_state) { + int i = 0; + + for (i = 0; i < 3; i++) { + if (mfc_get_chip_id(charger) >= 0) + break; + } + if (i == 3) + goto end_det_work; + + mfc_activate_work_content(charger); + } else { + if (charger->rx_phm_status && !pdet_b) { + pr_info("%s: phm status, skip deactivated\n", __func__); + charger->tx_id_cnt = 0; + goto end_det_work; + } + mfc_deactivate_work_content(charger); + } + +end_det_work: + mfc_cancel_wpc_vrect_check_work(charger); + + __pm_relax(charger->wpc_det_ws); +} + +static void mfc_wpc_pdrc_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, wpc_pdrc_work.work); + + if (charger->is_mst_on == MST_MODE_2 || charger->wc_tx_enable) { + pr_info("%s: Noise made false Vrect IRQ !\n", __func__); + if (charger->wc_tx_enable) { + union power_supply_propval value; + + value.intval = BATT_TX_EVENT_WIRELESS_TX_ETC; + psy_do_property("wireless", set, POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ERR, value); + } + __pm_relax(charger->wpc_pdrc_ws); + return; + } + + pr_info("%s : cable_type: %d, status: %d\n", __func__, + charger->pdata->cable_type, charger->pdrc_state); + + if (charger->pdata->cable_type == SEC_BATTERY_CABLE_NONE && (charger->pdrc_state == 0)) { + int i = 0; + + for (i = 0; i < 3; i++) { + if (mfc_get_chip_id(charger) >= 0) + break; + } + if (i == 3) { + __pm_relax(charger->wpc_pdrc_ws); + return; + } + + mfc_set_online(charger, SEC_BATTERY_CABLE_WIRELESS_FAKE); + } else if ((is_wireless_fake_type(charger->pdata->cable_type)) && (charger->pdrc_state == 1)) { + union power_supply_propval value = {0, }; + + charger->is_full_status = 0; + mfc_set_online(charger, SEC_BATTERY_CABLE_NONE); + mfc_fod_init_state(charger->fod); + mfc_cmfet_init_state(charger->cmfet); + value.intval = 0; + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_RX_PHM, value); + } + + __pm_relax(charger->wpc_pdrc_ws); +} + +static void mfc_vrect_check_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, wpc_vrect_check_work.work); + int vrect = 0; + u8 irq_src[2]; + union power_supply_propval value = {0, }; + + if (charger->pdata->cable_type == SEC_BATTERY_CABLE_NONE) { + /* Attach case */ + mfc_set_online(charger, SEC_BATTERY_CABLE_WIRELESS_FAKE); + + /* disable epp */ + mfc_epp_enable(charger, 0); + mfc_epp_clear_timer_work_start(charger, -1); + mfc_set_epp_count(charger, charger->epp_count + 1); + + /* To make sure forced detach if VOUT_GD is not rising within 3 seconds */ + mfc_start_wpc_vrect_check_work(charger, 3000); + return; + } + + vrect = mfc_get_adc(charger, MFC_ADC_VRECT); + pr_info("%s: vrect: %dmV\n", __func__, vrect); + if (vrect >= VALID_VRECT_LEVEL && !charger->is_mst_on) { + if (is_wireless_fake_type(charger->pdata->cable_type)) { + pr_info("%s: Temporary UVLO, FAKE. check again.\n", __func__); + mfc_start_wpc_vrect_check_work(charger, 300); + } else { + pr_info("%s: Temporary UVLO, hold on wireless charging\n", __func__); + __pm_relax(charger->wpc_vrect_check_ws); + } + return; + } + + /* Detach case */ + pr_info("%s: Vrect IRQ! wireless charging pad was removed!!\n", __func__); + + mfc_epp_clear_timer_work_start(charger, charger->epp_time); + charger->epp_time = 1000; + /* clear intterupt */ + if (is_wireless_fake_type(charger->pdata->cable_type) && + !gpio_get_value(charger->pdata->wpc_int)) { + mfc_reg_read(charger->client, MFC_INT_A_L_REG, &irq_src[0]); + mfc_reg_read(charger->client, MFC_INT_A_H_REG, &irq_src[1]); + mfc_reg_write(charger->client, MFC_INT_A_CLEAR_L_REG, irq_src[0]); // clear int + mfc_reg_write(charger->client, MFC_INT_A_CLEAR_H_REG, irq_src[1]); // clear int + mfc_set_cmd_l_reg(charger, MFC_CMD_CLEAR_INT_MASK, MFC_CMD_CLEAR_INT_MASK); // command + pr_info("%s wc_w_state_irq = %d\n", __func__, gpio_get_value(charger->pdata->wpc_int)); + } + charger->is_full_status = 0; + mfc_set_online(charger, SEC_BATTERY_CABLE_NONE); + mfc_fod_init_state(charger->fod); + mfc_cmfet_init_state(charger->cmfet); + value.intval = 0; + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_RX_PHM, value); + + __pm_relax(charger->wpc_vrect_check_ws); +} + +/* INT_A */ +static void mfc_wpc_tx_isr_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, wpc_tx_isr_work.work); + + u8 status_l = 0, status_h = 0; + int ret = 0; + + pr_info("@Tx_Mode %s\n", __func__); + + if (!charger->wc_tx_enable) { + __pm_relax(charger->wpc_tx_ws); + return; + } + + ret = mfc_reg_read(charger->client, MFC_STATUS_L_REG, &status_l); + ret = mfc_reg_read(charger->client, MFC_STATUS_H_REG, &status_h); + + pr_info("@Tx_Mode %s: DATA RECEIVED! status(0x%x)\n", __func__, status_h << 8 | status_l); + + mfc_tx_handle_rx_packet(charger); + + __pm_relax(charger->wpc_tx_ws); +} + +/* INT_A */ +static void mfc_wpc_isr_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, wpc_isr_work.work); + u8 cmd_data, val_data; + int wpc_vout_ctrl_lcd_on = 0; + union power_supply_propval value = {0, }; + + if (!charger->det_state) { + pr_info("%s: charger->det_state is 0. exit wpc_isr_work.\n", __func__); + __pm_relax(charger->wpc_rx_ws); + return; + } + + pr_info("%s: cable_type (%d)\n", __func__, charger->pdata->cable_type); + + mfc_reg_read(charger->client, MFC_WPC_TRX_DATA2_COM_REG, &cmd_data); + mfc_reg_read(charger->client, MFC_WPC_TRX_DATA2_VALUE0_REG, &val_data); + charger->pdata->trx_data_cmd = cmd_data; + charger->pdata->trx_data_val = val_data; + + pr_info("%s: WPC Interrupt Occurred, CMD : 0x%x, DATA : 0x%x\n", __func__, cmd_data, val_data); + + if (mpp_mode(charger->rx_op_mode)) { +#if 0 + if ((cmd_data == MFC_HEADER_CLOAK) && ((val_data & 0xF0) == 0)) { + pr_info("@MPP %s: MFC_HEADER_CLOAK\n", __func__); + value.intval = 1; + psy_do_property("wireless", set, + POWER_SUPPLY_EXT_PROP_MPP_CLOAK, value); + } +#endif + pr_info("@MPP %s: op_mode is MPP(%d), exit sfc\n", __func__, charger->rx_op_mode); + return; + } else if (epp_mode(charger->rx_op_mode) && !is_samsung_pad((charger->mpp_epp_tx_id & 0xFF))) { + pr_info("@EPP %s: op_mode is EPP(%d), exit sfc\n", __func__, charger->rx_op_mode); + return; + } + + /* None or RX mode */ + if (cmd_data == WPC_TX_COM_AFC_SET) { + switch (val_data) { + case TX_AFC_SET_5V: + pr_info("%s: data = 0x%x, might be 5V irq\n", __func__, val_data); + charger->pad_vout = PAD_VOUT_5V; + break; + case TX_AFC_SET_10V: + pr_info("%s: data = 0x%x, might be 10V irq\n", __func__, val_data); + charger->afc_tx_done = true; + if (!gpio_get_value(charger->pdata->wpc_det)) { + pr_err("%s: Wireless charging is paused during set high voltage.\n", __func__); + __pm_relax(charger->wpc_rx_ws); + return; + } + if (!charger->pdata->no_hv) { + if (is_hv_wireless_type(charger->pdata->cable_type) || + (charger->pdata->cable_type == SEC_BATTERY_CABLE_PREPARE_WIRELESS_HV)) { + pr_err("%s: Is is already HV wireless cable. No need to set again\n", __func__); + __pm_relax(charger->wpc_rx_ws); + return; + } + + /* send AFC_SET */ + mfc_send_command(charger, MFC_AFC_CONF_10V); + msleep(500); + + /* change cable type */ + mfc_set_online(charger, SEC_BATTERY_CABLE_PREPARE_WIRELESS_HV); + + charger->pad_vout = PAD_VOUT_10V; + mfc_fod_set_op_mode(charger->fod, WPC_OP_MODE_PPDE); + } + break; + case TX_AFC_SET_12V: + break; + case TX_AFC_SET_18V: + case TX_AFC_SET_19V: + case TX_AFC_SET_20V: + case TX_AFC_SET_24V: + break; + default: + pr_info("%s: unsupport : 0x%x", __func__, val_data); + break; + } + } else if (cmd_data == WPC_TX_COM_TX_ID) { + if (charger->req_tx_id) + charger->req_tx_id = false; + + if (!charger->tx_id_done) { + int capacity = 0; + bool skip_send_online = false; + + charger->tx_id = val_data; + mfc_fod_set_tx_id(charger->fod, val_data); + + psy_do_property("battery", get, POWER_SUPPLY_PROP_CAPACITY, value); + capacity = value.intval; + + psy_do_property("battery", get, POWER_SUPPLY_EXT_PROP_LCD_FLICKER, value); + wpc_vout_ctrl_lcd_on = value.intval; + + switch (val_data) { + case TX_ID_UNKNOWN: + value.intval = charger->pdata->cable_type; + break; + case TX_ID_VEHICLE_PAD: + if (charger->pad_vout == PAD_VOUT_10V) { + if (charger->pdata->cable_type == SEC_BATTERY_CABLE_PREPARE_WIRELESS_HV) { + charger->pdata->cable_type = SEC_BATTERY_CABLE_WIRELESS_HV_VEHICLE; + skip_send_online = true; + } else { + charger->pdata->cable_type = value.intval = + SEC_BATTERY_CABLE_WIRELESS_HV_VEHICLE; + } + } else { + charger->pdata->cable_type = value.intval = SEC_BATTERY_CABLE_WIRELESS_VEHICLE; + } + pr_info("%s: VEHICLE Wireless Charge PAD %s\n", __func__, + charger->pad_vout == PAD_VOUT_10V ? "HV" : ""); + + break; + case TX_ID_STAND_TYPE_START: + if (charger->pad_vout == PAD_VOUT_10V) { + if (charger->pdata->cable_type == SEC_BATTERY_CABLE_PREPARE_WIRELESS_HV) { + charger->pdata->cable_type = SEC_BATTERY_CABLE_WIRELESS_HV_STAND; + skip_send_online = true; + } else { + charger->pdata->cable_type = value.intval = SEC_BATTERY_CABLE_WIRELESS_HV_STAND; + } + } else { + charger->pdata->cable_type = value.intval = + SEC_BATTERY_CABLE_WIRELESS_STAND; + } + pr_info("%s: Cable(%d), STAND Wireless Charge PAD %s\n", __func__, + charger->pdata->cable_type, charger->pad_vout == PAD_VOUT_10V ? "HV" : ""); + break; + case TX_ID_MULTI_PORT_START ... TX_ID_MULTI_PORT_END: + value.intval = charger->pdata->cable_type; + pr_info("%s: MULTI PORT PAD : 0x%x\n", __func__, val_data); + break; + case TX_ID_BATT_PACK_U1200 ... TX_ID_BATT_PACK_END: + if (charger->pad_vout == PAD_VOUT_10V) { + if (charger->pdata->cable_type == SEC_BATTERY_CABLE_PREPARE_WIRELESS_HV) { + charger->pdata->cable_type = SEC_BATTERY_CABLE_WIRELESS_HV_PACK; + skip_send_online = true; + pr_info("%s: WIRELESS HV BATTERY PACK (PREP)\n", __func__); + } else { + charger->pdata->cable_type = value.intval = + SEC_BATTERY_CABLE_WIRELESS_HV_PACK; + pr_info("%s: WIRELESS HV BATTERY PACK\n", __func__); + } + } else { + charger->pdata->cable_type = value.intval = SEC_BATTERY_CABLE_WIRELESS_PACK; + pr_info("%s: WIRELESS BATTERY PACK\n", __func__); + } +#if !defined(CONFIG_WIRELESS_RX_PHM_CTRL) + if (charger->device_event & BATT_EXT_EVENT_CALL) { + union power_supply_propval value2; + + pr_info("%s: enter PHM\n", __func__); + /* notify "wireless" PHM status */ + value2.intval = 1; + psy_do_property("wireless", set, POWER_SUPPLY_EXT_PROP_CALL_EVENT, value2); + mfc_send_command(charger, MFC_PHM_ON); + msleep(250); + mfc_send_command(charger, MFC_PHM_ON); + } +#endif + break; + case TX_ID_UNO_TX: + case TX_ID_UNO_TX_B0 ... TX_ID_UNO_TX_MAX: + charger->pdata->cable_type = value.intval = SEC_BATTERY_CABLE_WIRELESS_TX; + pr_info("@Tx_Mode %s: TX by UNO\n", __func__); + if (capacity < 100) + mfc_wpc_align_check(charger, ALIGN_WORK_DELAY); +#if !defined(CONFIG_WIRELESS_RX_PHM_CTRL) + if (charger->device_event & BATT_EXT_EVENT_CALL) { + pr_info("%s: enter PHM\n", __func__); + mfc_send_command(charger, MFC_PHM_ON); + msleep(250); + mfc_send_command(charger, MFC_PHM_ON); + } +#endif + break; + case TX_ID_NON_AUTH_PAD ... TX_ID_NON_AUTH_PAD_END: + value.intval = charger->pdata->cable_type; + break; + case TX_ID_AUTH_PAD ... TX_ID_AUTH_PAD_END: + if (charger->pdata->no_hv) { + pr_info("%s: WIRELESS HV is disabled\n", __func__); + break; + } + if (epp_mode(charger->rx_op_mode) && (charger->tx_id == TX_ID_P2400_PAD)) + mfc_send_command(charger, MFC_AFC_CONF_10V_TX); + + charger->vout_by_txid = mfc_get_wireless20_vout_by_txid(charger, val_data); + charger->vrect_by_txid = mfc_get_wireless20_vrect_by_txid(charger, val_data); + charger->max_power_by_txid = mfc_get_wireless20_max_power_by_txid(charger, val_data); +#if !defined(CONFIG_SEC_FACTORY) + /* power on charging */ + if (charger->adt_transfer_status == WIRELESS_AUTH_WAIT) { + /* change first wireless type to prepare wc2.0 for auth mode */ + charger->pdata->cable_type = value.intval = SEC_BATTERY_CABLE_PREPARE_WIRELESS_20; + pr_info("%s %s: AUTH PAD for WIRELESS 2.0 : 0x%x\n", WC_AUTH_MSG, + __func__, val_data); + + mfc_auth_set_configs(charger, AUTH_READY); + if (delayed_work_pending(&charger->wpc_afc_vout_work)) { + __pm_relax(charger->wpc_afc_vout_ws); + cancel_delayed_work(&charger->wpc_afc_vout_work); + } + /* notify auth service to send TX PAD a request key */ + mfc_auth_send_adt_status(charger, WIRELESS_AUTH_START); + } else if (charger->adt_transfer_status == WIRELESS_AUTH_PASS) { + if (delayed_work_pending(&charger->wpc_afc_vout_work)) { + __pm_relax(charger->wpc_afc_vout_ws); + cancel_delayed_work(&charger->wpc_afc_vout_work); + } + charger->pdata->cable_type = value.intval = SEC_BATTERY_CABLE_HV_WIRELESS_20; + skip_send_online = true; + __pm_stay_awake(charger->wpc_afc_vout_ws); + queue_delayed_work(charger->wqueue, + &charger->wpc_afc_vout_work, msecs_to_jiffies(0)); + } else if (charger->adt_transfer_status == WIRELESS_AUTH_FAIL) { + charger->pdata->cable_type = SEC_BATTERY_CABLE_HV_WIRELESS; + value.intval = charger->pdata->cable_type; + skip_send_online = true; + if (charger->afc_tx_done) { + charger->pdata->cable_type = SEC_BATTERY_CABLE_HV_WIRELESS; + __pm_stay_awake(charger->wpc_afc_vout_ws); + queue_delayed_work(charger->wqueue, + &charger->wpc_afc_vout_work, msecs_to_jiffies(0)); + } else { + mfc_set_online(charger, SEC_BATTERY_CABLE_WIRELESS); + } + } else { + value.intval = charger->pdata->cable_type; + pr_info("%s %s: undefined auth TX-ID scenario, auth service works strange...\n", + WC_AUTH_MSG, __func__); + } +#else /* FACTORY MODE dose not process auth, set 12W right after */ + if (charger->adt_transfer_status == WIRELESS_AUTH_WAIT) { + if (delayed_work_pending(&charger->wpc_afc_vout_work)) { + __pm_relax(charger->wpc_afc_vout_ws); + cancel_delayed_work(&charger->wpc_afc_vout_work); + } + charger->adt_transfer_status = WIRELESS_AUTH_PASS; + charger->pdata->cable_type = value.intval = SEC_BATTERY_CABLE_HV_WIRELESS_20; + skip_send_online = true; + __pm_stay_awake(charger->wpc_afc_vout_ws); + queue_delayed_work(charger->wqueue, + &charger->wpc_afc_vout_work, msecs_to_jiffies(0)); + } + pr_info("%s %s: AUTH PAD for WIRELESS 2.0 but FACTORY MODE\n", WC_AUTH_MSG, __func__); +#endif + break; + case TX_ID_PG950_S_PAD: + value.intval = charger->pdata->cable_type; + pr_info("%s: PG950 PAD STAND : 0x%x\n", __func__, val_data); + break; + case TX_ID_PG950_D_PAD: + value.intval = charger->pdata->cable_type; + pr_info("%s: PG950 PAD DOWN : 0x%x\n", __func__, val_data); + break; + case TX_ID_P2400_PAD_NOAUTH: + value.intval = charger->pdata->cable_type; + if (epp_mode(charger->rx_op_mode)) + mfc_send_command(charger, MFC_AFC_CONF_10V_TX); + break; + default: + value.intval = charger->pdata->cable_type; + pr_info("%s: UNDEFINED PAD : 0x%x\n", __func__, val_data); + break; + } + + if (wpc_vout_ctrl_lcd_on && opfreq_ctrl_pad(charger->tx_id)) { + pr_info("%s: tx id = 0x%x , set op freq\n", __func__, val_data); + mfc_send_command(charger, MFC_SET_OP_FREQ); + msleep(500); + } + + if (epp_mode(charger->rx_op_mode)) { + if (charger->adt_transfer_status == WIRELESS_AUTH_WAIT) { + mfc_set_online(charger, SEC_BATTERY_CABLE_WIRELESS_EPP); + charger->max_power_by_txid = TX_RX_POWER_8W * 100000; + charger->current_rx_power = TX_RX_POWER_8W; + skip_send_online = true; + } + } + + if (!skip_send_online) { + pr_info("%s: change cable type\n", __func__); + mfc_set_online(charger, value.intval); + } + + /* send max vout informaion of this pad such as WIRELESS_VOUT_10V, WIRELESS_VOUT_12_5V */ + mfc_set_psy_wrl(charger, POWER_SUPPLY_EXT_PROP_WIRELESS_MAX_VOUT, charger->vout_by_txid); + + /* + * send rx power informaion of this pad such as + * SEC_WIRELESS_RX_POWER_12W, SEC_WIRELESS_RX_POWER_17_5W + */ + mfc_set_rx_power(charger, charger->max_power_by_txid); + +#if defined(CONFIG_SEC_FACTORY) + queue_delayed_work(charger->wqueue, &charger->wpc_rx_power_work, msecs_to_jiffies(3000)); +#endif + charger->tx_id_done = true; + pr_info("%s: TX_ID : 0x%x\n", __func__, val_data); + } else { + pr_err("%s: TX ID isr is already done\n", __func__); + } + } else if (cmd_data == WPC_TX_COM_CHG_ERR) { + switch (val_data) { + case TX_CHG_ERR_OTP: + case TX_CHG_ERR_OCP: + case TX_CHG_ERR_DARKZONE: + pr_info("%s: Received CHG error from the TX(0x%x)\n", __func__, val_data); + break; + /*case TX_CHG_ERR_FOD:*/ + case 0x20 ... 0x27: + pr_info("%s: Received FOD state from the TX(0x%x)\n", __func__, val_data); + value.intval = val_data; + psy_do_property("wireless", set, POWER_SUPPLY_EXT_PROP_WIRELESS_ERR, value); + break; + default: + pr_info("%s: Undefined Type(0x%x)\n", __func__, val_data); + break; + } + } else if (cmd_data == WPC_TX_COM_RX_POWER) { + charger->check_rx_power = false; + if (delayed_work_pending(&charger->wpc_rx_power_trans_fail_work)) { + __pm_relax(charger->wpc_rx_power_trans_fail_ws); + cancel_delayed_work(&charger->wpc_rx_power_trans_fail_work); + } + if (charger->current_rx_power != val_data) { + switch (val_data) { + case TX_RX_POWER_3W: + pr_info("%s %s: RX Power is 3W\n", WC_AUTH_MSG, __func__); + charger->current_rx_power = TX_RX_POWER_3W; + break; + case TX_RX_POWER_5W: + pr_info("%s %s: RX Power is 5W\n", WC_AUTH_MSG, __func__); + charger->current_rx_power = TX_RX_POWER_5W; + break; + case TX_RX_POWER_6_5W: + pr_info("%s %s: RX Power is 6.5W\n", WC_AUTH_MSG, __func__); + charger->current_rx_power = TX_RX_POWER_6_5W; + break; + case TX_RX_POWER_7_5W: + pr_info("%s %s: RX Power is 7.5W\n", WC_AUTH_MSG, __func__); + mfc_reset_rx_power(charger, TX_RX_POWER_7_5W); + charger->current_rx_power = TX_RX_POWER_7_5W; + break; + case TX_RX_POWER_10W: + pr_info("%s %s: RX Power is 10W\n", WC_AUTH_MSG, __func__); + charger->current_rx_power = TX_RX_POWER_10W; + break; + case TX_RX_POWER_12W: + pr_info("%s %s: RX Power is 12W\n", WC_AUTH_MSG, __func__); + mfc_reset_rx_power(charger, TX_RX_POWER_12W); + charger->current_rx_power = TX_RX_POWER_12W; + break; + case TX_RX_POWER_15W: + pr_info("%s %s: RX Power is 15W\n", WC_AUTH_MSG, __func__); + mfc_reset_rx_power(charger, TX_RX_POWER_15W); + charger->current_rx_power = TX_RX_POWER_15W; + break; + case TX_RX_POWER_17_5W: + pr_info("%s %s: RX Power is 17.5W\n", WC_AUTH_MSG, __func__); + mfc_reset_rx_power(charger, TX_RX_POWER_17_5W); + charger->current_rx_power = TX_RX_POWER_17_5W; + break; + case TX_RX_POWER_20W: + pr_info("%s %s: RX Power is 20W\n", WC_AUTH_MSG, __func__); + mfc_reset_rx_power(charger, TX_RX_POWER_20W); + charger->current_rx_power = TX_RX_POWER_20W; + break; + default: + pr_info("%s %s: Undefined RX Power(0x%x)\n", WC_AUTH_MSG, __func__, val_data); + break; + } + } else { + pr_info("%s: skip same RX Power\n", __func__); + } + } else if (cmd_data == WPC_TX_COM_WPS) { + switch (val_data) { + case WPS_AICL_RESET: + value.intval = 1; + pr_info("@Tx_mode %s: Rx devic AICL Reset\n", __func__); + psy_do_property("wireless", set, POWER_SUPPLY_PROP_CURRENT_MAX, value); + break; + default: + pr_info("%s %s: Undefined RX Power(0x%x)\n", WC_AUTH_MSG, __func__, val_data); + break; + } + } else if (cmd_data == WPC_TX_COM_TX_PWR_BUDG) { + pr_info("%s: WPC_TX_COM_TX_PWR_BUDG (0x%x)\n", __func__, val_data); + mfc_recv_tx_pwr_budg(charger, val_data); + } + __pm_relax(charger->wpc_rx_ws); +} + +static void mfc_set_unknown_pad_config(struct mfc_charger_data *charger) +{ + pr_info("%s: TX ID not Received, cable_type(%d)\n", + __func__, charger->pdata->cable_type); + + if (epp_mode(charger->rx_op_mode)) { + cancel_delayed_work(&charger->wpc_vout_mode_work); + __pm_stay_awake(charger->wpc_vout_mode_ws); + queue_delayed_work(charger->wqueue, &charger->wpc_vout_mode_work, 0); + if (charger->nego_done_power < TX_RX_POWER_8W) { + mfc_set_online(charger, SEC_BATTERY_CABLE_WIRELESS_EPP_NV); + mfc_set_epp_nv_mode(charger); + } else { + mfc_set_online(charger, SEC_BATTERY_CABLE_WIRELESS_EPP); + mfc_set_epp_mode(charger, TX_RX_POWER_8W); + } + return; + } + + if (!charger->pdata->default_clamp_volt) { + if (is_hv_wireless_type(charger->pdata->cable_type)) { + /* SET OV 16V */ + mfc_reg_write(charger->client, MFC_RX_OV_CLAMP_REG, 0x1); + } else { + /* SET OV 14V */ + mfc_reg_write(charger->client, MFC_RX_OV_CLAMP_REG, 0x0); + } + } +} + +static void mfc_wpc_tx_pwr_budg_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, wpc_tx_pwr_budg_work.work); + + mfc_send_command(charger, MFC_REQ_TX_PWR_BUDG); + __pm_relax(charger->wpc_tx_pwr_budg_ws); +} + +static void mfc_wpc_tx_id_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, wpc_tx_id_work.work); + union power_supply_propval value; + + pr_info("%s\n", __func__); + + if (!charger->tx_id) { + if (charger->tx_id_cnt < 10) { + mfc_send_command(charger, MFC_REQUEST_TX_ID); + charger->req_tx_id = true; + charger->tx_id_cnt++; + + pr_info("%s: request TX ID cnt (%d)\n", __func__, charger->tx_id_cnt); + queue_delayed_work(charger->wqueue, &charger->wpc_tx_id_work, msecs_to_jiffies(1500)); + return; + } + + mfc_set_unknown_pad_config(charger); + charger->req_tx_id = false; + } else { + pr_info("%s: TX ID (0x%x)\n", __func__, charger->tx_id); + if (charger->tx_id == TX_ID_JIG_PAD) { + value.intval = 1; + pr_info("%s: it is jig pad\n", __func__); + psy_do_property("wireless", set, POWER_SUPPLY_EXT_PROP_WIRELESS_JIG_PAD, value); + } else { + mfc_cmfet_set_tx_id(charger->cmfet, charger->tx_id); + } + } + __pm_relax(charger->wpc_tx_id_ws); +} + +static irqreturn_t mfc_wpc_det_irq_thread(int irq, void *irq_data) +{ + struct mfc_charger_data *charger = irq_data; + u8 det_state; + + pr_info("%s(%d)\n", __func__, irq); + + det_state = gpio_get_value(charger->pdata->wpc_det); + + if (charger->det_state != det_state) { + union power_supply_propval value = {0, }; + + value.intval = 0; + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_RX_PHM, value); + + pr_info("%s: det(%d to %d)\n", __func__, charger->det_state, det_state); + charger->det_state = det_state; + queue_delayed_work(charger->wqueue, &charger->wpc_det_work, 0); + + /* clear intterupt */ + if (is_wireless_fake_type(charger->pdata->cable_type)) { + u8 int_a_l; + + mfc_reg_read(charger->client, MFC_INT_A_L_REG, &int_a_l); + if (int_a_l & MFC_INTA_L_STAT_VRECT_MASK) { + mfc_reg_write(charger->client, + MFC_INT_A_CLEAR_L_REG, MFC_INTA_L_STAT_VRECT_MASK); // clear vrect irq + mfc_set_cmd_l_reg(charger, MFC_CMD_CLEAR_INT_MASK, MFC_CMD_CLEAR_INT_MASK); // command + } + pr_info("%s: clear irqs\n", __func__); + } + } else + __pm_relax(charger->wpc_det_ws); + + return IRQ_HANDLED; +} + +static irqreturn_t mfc_wpc_pdrc_irq_thread(int irq, void *irq_data) +{ + struct mfc_charger_data *charger = irq_data; + u8 pdrc_state; + + pr_info("%s(%d)\n", __func__, irq); + + pdrc_state = gpio_get_value(charger->pdata->wpc_pdrc); + + if (charger->pdrc_state != pdrc_state) { + pr_info("%s: pdrc(%d to %d)\n", __func__, charger->pdrc_state, pdrc_state); + charger->pdrc_state = pdrc_state; + if (charger->pdata->cable_type == SEC_BATTERY_CABLE_NONE) + queue_delayed_work(charger->wqueue, &charger->wpc_pdrc_work, msecs_to_jiffies(0)); + else if (is_wireless_fake_type(charger->pdata->cable_type)) + queue_delayed_work(charger->wqueue, &charger->wpc_pdrc_work, msecs_to_jiffies(900)); + else + __pm_relax(charger->wpc_pdrc_ws); + } else { + if (!delayed_work_pending(&charger->wpc_pdrc_work)) + __pm_relax(charger->wpc_pdrc_ws); + } + + return IRQ_HANDLED; +} + +static irqreturn_t mfc_wpc_pdet_b_irq_thread(int irq, void *irq_data) +{ + struct mfc_charger_data *charger = irq_data; + u8 pdetb_state = 0, det_state = 0; + + pdetb_state = gpio_get_value(charger->pdata->wpc_pdet_b); + det_state = gpio_get_value(charger->pdata->wpc_det); + + pr_info("%s : pdet_b = %d, wpc_det = %d\n", + __func__, pdetb_state, det_state); + + if (charger->rx_phm_status && !det_state && pdetb_state) { + pr_info("%s: wireless charger deactivated during phm\n", __func__); + charger->rx_phm_state = END_PHM; + __pm_stay_awake(charger->wpc_rx_phm_ws); + queue_delayed_work(charger->wqueue, &charger->wpc_rx_phm_work, 0); + } + __pm_relax(charger->wpc_pdet_b_ws); + + return IRQ_HANDLED; +} + +/* mfc_mst_routine : MST dedicated codes */ +static void mfc_mst_routine(struct mfc_charger_data *charger, u8 irq_src_l, u8 irq_src_h) +{ + u8 data = 0; + u8 index = 0; + u8 status_l = 0; + u8 status_h = 0; + + pr_info("%s\n", __func__); + + if (charger->is_mst_on == MST_MODE_2) { + charger->wc_tx_enable = false; +#if defined(CONFIG_MST_V2) + // it will send MST driver a message. + mfc_send_mst_cmd(MST_MODE_ON, charger, irq_src_l, irq_src_h); +#endif + } else if (charger->wc_tx_enable) { + mfc_reg_read(charger->client, MFC_STATUS_H_REG, &data); + data &= 0x4; /* AC MISSING DETECT */ + msleep(100); + pr_info("@Tx_Mode %s: Register AC Missing(%d)\n", __func__, data); + mfc_reg_write(charger->client, MFC_INT_A_CLEAR_L_REG, irq_src_l); // clear int + mfc_reg_write(charger->client, MFC_INT_A_CLEAR_H_REG, irq_src_h); // clear int + mfc_set_cmd_l_reg(charger, MFC_CMD_CLEAR_INT_MASK, MFC_CMD_CLEAR_INT_MASK); // command + mfc_reg_read(charger->client, MFC_STATUS_L_REG, &status_l); + mfc_reg_read(charger->client, MFC_STATUS_H_REG, &status_h); + pr_info("%s: status after clear irq, status(0x%x)\n", __func__, status_h << 8 | status_l); + if (data) { + /* initialize QFOD reg */ + pr_info("@Tx_Mode %s: enable_qfod_param(%d)\n", __func__, charger->pdata->enable_qfod_param); + if (charger->pdata->enable_qfod_param) { + index = 0; + mfc_reg_write(charger->client, MFC_TX_Q_SSPTH_REG, (sspth_value[index] & 0xff)); + mfc_reg_write(charger->client, MFC_TX_Q_BASE_L_REG, (qbase_value[index] & 0xff)); + //mfc_reg_write(charger->client, MFC_TX_Q_BASE_H_REG, (qbase_value[index] & 0xff00) >> 8); + mfc_reg_write(charger->client, MFC_TX_COEFF_QFOD_TH0_REG, (qfod_th0[index] & 0xff)); + mfc_reg_write(charger->client, MFC_TX_COEFF_QFOD_TH1_REG, (qfod_th1[index] & 0xff)); + mfc_reg_write(charger->client, MFC_TX_COEFF_QFOD_TH2_REG, (qfod_th2[index] & 0xff)); + mfc_reg_write(charger->client, MFC_TX_COEFF_QFOD_TH3_REG, (qfod_th3[index] & 0xff)); + + mfc_reg_write(charger->client, + MFC_TX_COEFF_QFOD_TWS_TH3_L_REG, (qfod_tws_th3[index] & 0xff)); + //mfc_reg_write(charger->client, + // MFC_TX_COEFF_QFOD_TWS_TH3_H_REG, (qfod_tws_th3[index] & 0xff00) >> 8); + mfc_reg_write(charger->client, + MFC_TX_COEFF_QFOD_PHONE_TH4_L_REG, (qfod_phone_th4[index] & 0xff)); + //mfc_reg_write(charger->client, + // MFC_TX_COEFF_QFOD_PHONE_TH4_H_REG, (qfod_phone_th4[index] & 0xff00) >> 8); + + pr_info("@Tx_Mode %s: sspth_value[index](0x%x)\n", __func__, + (sspth_value[index] & 0xff)); + pr_info("@Tx_Mode %s: qbase_value l(0x%x)\n", __func__, + (qbase_value[index] & 0xff)); + pr_info("@Tx_Mode %s: qbase_value h(0x%x)\n", __func__, + (qbase_value[index] & 0xff00) >> 8); + pr_info("@Tx_Mode %s: qfod_th0(0x%x)\n", __func__, + (qfod_th0[index] & 0xff)); + pr_info("@Tx_Mode %s: qfod_th1(0x%x)\n", __func__, + (qfod_th1[index] & 0xff)); + pr_info("@Tx_Mode %s: qfod_th2(0x%x)\n", __func__, + (qfod_th2[index] & 0xff)); + pr_info("@Tx_Mode %s: qfod_th3(0x%x)\n", __func__, + (qfod_th3[index] & 0xff)); + pr_info("@Tx_Mode %s: qfod_tws_th3 l(0x%x)\n", __func__, + (qfod_tws_th3[index] & 0xff)); + pr_info("@Tx_Mode %s: qfod_tws_th3 h(0x%x)\n", __func__, + (qfod_tws_th3[index] & 0xff00) >> 8); + pr_info("@Tx_Mode %s: qfod_phone_th4 l(0x%x)\n", __func__, + (qfod_phone_th4[index] & 0xff)); + pr_info("@Tx_Mode %s: qfod_phone_th4 h(0x%x)\n", __func__, + (qfod_phone_th4[index] & 0xff00) >> 8); + } + /* initialize control reg */ + // after checking the purpose of this function, will be implemented. + mfc_set_tx_conflict_current(charger, charger->pdata->tx_conflict_curr); + mfc_reg_write(charger->client, MFC_TX_IUNO_HYS_REG, 0x50); + mfc_reg_write(charger->client, MFC_DEMOD1_REG, 0x00); + mfc_reg_write(charger->client, MFC_DEMOD2_REG, 0x00); + mfc_reg_write(charger->client, MFC_MST_MODE_SEL_REG, 0x03); /* set TX-ON mode */ + mfc_set_cep_timeout(charger, charger->pdata->cep_timeout); // this is only for CAN + pr_info("@Tx_Mode %s: TX-ON Mode : %d\n", __func__, charger->pdata->wc_ic_rev); + } // ac missing is 0, ie, TX detected + } +} + +static void mfc_mpp_epp_nego_done(struct mfc_charger_data *charger) +{ + int ret = 0; + u8 buf_val = 0; + + mfc_reg_read(charger->client, MFC_SYS_OP_MODE_REG, &buf_val); + charger->rx_op_mode = buf_val >> 5; + pr_info("@MPP @EPP %s:Rx op_mode %d\n", __func__, charger->rx_op_mode); + + ret = mfc_reg_read(charger->client, MFC_NEGO_DONE_POWER_REG, &buf_val); + charger->nego_done_power = buf_val * 2; + + ret = mfc_reg_read(charger->client, MFC_PTX_EXTENDED_ID0_REG, &buf_val); + charger->mpp_epp_tx_id = (buf_val << 16); + ret = mfc_reg_read(charger->client, MFC_PTX_EXTENDED_ID1_REG, &buf_val); + charger->mpp_epp_tx_id |= (buf_val << 8); + ret = mfc_reg_read(charger->client, MFC_PTX_EXTENDED_ID2_REG, &buf_val); + charger->mpp_epp_tx_id |= buf_val; + + ret = mfc_reg_read(charger->client, MFC_POTENTIAL_LOAD_POWER_REG, &buf_val); + charger->potential_load_power = buf_val * 2; + + ret = mfc_reg_read(charger->client, MFC_NEGOTIABLE_LOAD_POWER_REG, &buf_val); + charger->negotiable_load_power = buf_val * 2; + pr_info("@MPP @EPP %s: TX ID:0x%x, NEGO_DONE_POWER:%dmW, POTENTIAL_LOAD_POWER:%dmW, NEGOTIABLE_LOAD_POWER:%dmW\n", + __func__, charger->mpp_epp_tx_id, charger->nego_done_power * 100, + charger->potential_load_power * 100, charger->negotiable_load_power * 100); + + mfc_fod_set_vendor_id(charger->fod, (charger->mpp_epp_tx_id & 0xFF)); + if (epp_mode(charger->rx_op_mode)) { + charger->max_power_by_txid = charger->pdata->mpp_epp_def_power; + charger->current_rx_power = charger->max_power_by_txid / 100000; + + /* check epp nego power */ + if ((charger->nego_done_power < TX_RX_POWER_8W) || charger->is_full_status) { + charger->vrect_by_txid = MFC_AFC_CONF_5V; + charger->vout_by_txid = WIRELESS_VOUT_5_5V; + charger->vout_mode = WIRELESS_VOUT_5_5V; + charger->pdata->vout_status = MFC_VOUT_5_5V; + } else { + charger->vrect_by_txid = MFC_AFC_CONF_12_5V_TX; + charger->vout_by_txid = charger->pdata->mpp_epp_vout; + charger->vout_mode = charger->pdata->mpp_epp_vout; + charger->pdata->vout_status = MFC_VOUT_12V; + if (charger->sleep_mode) + charger->vout_mode = WIRELESS_VOUT_5_5V_STEP; + } + + mfc_set_epp_mode(charger, charger->nego_done_power > TX_RX_POWER_8W ? + TX_RX_POWER_8W : charger->nego_done_power); + if ((charger->vout_mode == WIRELESS_VOUT_5_5V_STEP) || + (charger->vout_mode == WIRELESS_VOUT_5_5V)) + mfc_fod_set_vout(charger->fod, 5500); + else + mfc_fod_set_vout(charger->fod, 12000); + mfc_fod_set_op_mode(charger->fod, WPC_OP_MODE_EPP); + mfc_set_online(charger, SEC_BATTERY_CABLE_WIRELESS_EPP_FAKE); + } +} + +static irqreturn_t mfc_wpc_irq_handler(int irq, void *irq_data) +{ + struct mfc_charger_data *charger = irq_data; + + if (irq == charger->pdata->irq_wpc_int) + __pm_stay_awake(charger->wpc_ws); + else if (irq == charger->pdata->irq_wpc_det) + __pm_stay_awake(charger->wpc_det_ws); + else if (irq == charger->pdata->irq_wpc_pdrc) + __pm_stay_awake(charger->wpc_pdrc_ws); + else if (irq == charger->pdata->irq_wpc_pdet_b) + __pm_stay_awake(charger->wpc_pdet_b_ws); + +#if IS_ENABLED(CONFIG_ENABLE_WIRELESS_IRQ_IN_SLEEP) + return charger->is_suspend ? IRQ_HANDLED : IRQ_WAKE_THREAD; +#else + return IRQ_WAKE_THREAD; +#endif +} + +static bool mfc_check_wpc_release_by_vrect_int(struct mfc_charger_data *charger) +{ + u8 vrect_int = 0, vrect_status = 0; + int ret = 0; + + if (charger->pdata->irq_wpc_pdrc) + return false; + + if (!is_wireless_fake_type(charger->pdata->cable_type)) + return false; + + ret = mfc_reg_read(charger->client, MFC_INT_A_L_REG, &vrect_int); + if (ret < 0) + return true; + vrect_int = !!(vrect_int & MFC_INTA_L_STAT_VRECT_MASK); + + ret = mfc_reg_read(charger->client, MFC_STATUS_L_REG, &vrect_status); + if (ret < 0) + return true; + vrect_status = !!(vrect_status & MFC_STAT_L_STAT_VRECT_MASK); + + pr_info("%s: check vrect (int = %d, status = %d)\n", + __func__, vrect_int, vrect_status); + return (!vrect_int && !vrect_status); +} + +static irqreturn_t mfc_wpc_irq_thread(int irq, void *irq_data) +{ + struct mfc_charger_data *charger = irq_data; + u8 int_state; + int ret = 0; + u8 irq_src_l = 0, irq_src_h = 0, irq_src1_l = 0, irq_src1_h = 0; + u8 status_l = 0, status_h = 0, status1_l = 0, status1_h = 0; + bool end_irq = false; + union power_supply_propval value; + u8 fod_type = 0; + + pr_info("%s: start(%d)!\n", __func__, irq); + + int_state = gpio_get_value(charger->pdata->wpc_int); + pr_info("%s: int_state = %d\n", __func__, int_state); + + if (int_state == 1) { + if (mfc_check_wpc_release_by_vrect_int(charger)) { + pr_info("%s: Check vrect after 0.9 second to prevent reconnection by UVLO or Watch ping\n", __func__); + mfc_cancel_wpc_vrect_check_work(charger); + mfc_start_wpc_vrect_check_work(charger, 900); + } else { + pr_info("%s: Rising edge, End up ISR\n", __func__); + } + __pm_relax(charger->wpc_ws); + return IRQ_HANDLED; + } + + ret = mfc_reg_read(charger->client, MFC_INT_A_L_REG, &irq_src_l);//1270 + if (ret < 0) { + pr_err("%s: Failed to read INT_A_L_REG: %d\n", __func__, ret); + __pm_relax(charger->wpc_ws); + return IRQ_HANDLED; + } + ret = mfc_reg_read(charger->client, MFC_INT_A_H_REG, &irq_src_h);//1271 + if (ret < 0) { + pr_err("%s: Failed to read INT_A_H_REG: %d\n", __func__, ret); + __pm_relax(charger->wpc_ws); + return IRQ_HANDLED; + } + + ret = mfc_reg_read(charger->client, MFC_INT_A_L_REG1, &irq_src1_l);//1272 + if (ret < 0) { + pr_err("%s: Failed to read INT_A_L_REG1: %d\n", __func__, ret); + __pm_relax(charger->wpc_ws); + return IRQ_HANDLED; + } + ret = mfc_reg_read(charger->client, MFC_INT_A_H_REG1, &irq_src1_h);//1273 + if (ret < 0) { + pr_err("%s: Failed to read INT_A_H_REG1: %d\n", __func__, ret); + __pm_relax(charger->wpc_ws); + return IRQ_HANDLED; + } + + ret = mfc_reg_read(charger->client, MFC_STATUS_L_REG, &status_l);//1274 + if (ret < 0) { + pr_err("%s: Failed to read STATUS_L_REG: %d\n", __func__, ret); + __pm_relax(charger->wpc_ws); + return IRQ_HANDLED; + } + ret = mfc_reg_read(charger->client, MFC_STATUS_H_REG, &status_h);//1275 + if (ret < 0) { + pr_err("%s: Failed to read STATUS_H_REG: %d\n", __func__, ret); + __pm_relax(charger->wpc_ws); + return IRQ_HANDLED; + } + + ret = mfc_reg_read(charger->client, MFC_STATUS_L_REG1, &status1_l);//1276 + if (ret < 0) { + pr_err("%s: Failed to read STATUS_L_REG1: %d\n", __func__, ret); + __pm_relax(charger->wpc_ws); + return IRQ_HANDLED; + } + ret = mfc_reg_read(charger->client, MFC_STATUS_H_REG1, &status1_h);//1277 + if (ret < 0) { + pr_err("%s: Failed to read STATUS_H_REG1: %d\n", __func__, ret); + __pm_relax(charger->wpc_ws); + return IRQ_HANDLED; + } + + pr_info("%s: interrupt source(0x%x), status(0x%x), source1(0x%x), status1(0x%x)\n", + __func__, irq_src_h << 8 | irq_src_l, status_h << 8 | status_l, + irq_src1_h << 8 | irq_src1_l, status1_h << 8 | status1_l); + + if (irq_src_h & MFC_INTA_H_AC_MISSING_DET_MASK) { + pr_info("%s: 1AC Missing ! : MFC is on\n", __func__); + pr_info("%s: kernel version (0x%x)\n", __func__, MFC_NU1668_DRIVER_VERSION); + mfc_mst_routine(charger, irq_src_l, irq_src_h); + if (charger->wc_tx_enable) { + __pm_relax(charger->wpc_ws); + return IRQ_HANDLED; + } + } + + if ((irq_src_l & MFC_INTA_L_OVER_VOL_MASK) || (irq_src_l & MFC_INTA_L_OVER_CURR_MASK)) + pr_info("%s: ABNORMAL STAT IRQ !\n", __func__); + + if (irq_src_l & MFC_INTA_L_OVER_TEMP_MASK) { + pr_info("%s: OVER TEMP IRQ !\n", __func__); + if (charger->wc_tx_enable) { + value.intval = BATT_TX_EVENT_WIRELESS_RX_UNSAFE_TEMP; + end_irq = true; + goto INT_END; + } + } + + if (irq_src_l & MFC_INTA_L_STAT_VRECT_MASK) { + int epp_ref_qf = 0, epp_ref_rf = 0; + + mfc_fod_get_ext(charger->fod, MFC_FOD_EXT_EPP_REF_QF, &epp_ref_qf); + mfc_fod_get_ext(charger->fod, MFC_FOD_EXT_EPP_REF_RF, &epp_ref_rf); + pr_info("%s: Vrect IRQ (0x%x, 0x%x), Kernel version(0x%x)!\n", + __func__, epp_ref_qf, epp_ref_rf, MFC_NU1668_DRIVER_VERSION); + if (epp_ref_qf) + mfc_reg_write(charger->client, MFC_MPP_FOD_QF_REG, epp_ref_qf); + if (epp_ref_rf) + mfc_reg_write(charger->client, MFC_MPP_FOD_RF_REG, epp_ref_rf); + if (charger->is_mst_on == MST_MODE_2 || charger->wc_tx_enable) { + pr_info("%s: Noise made false Vrect IRQ!\n", __func__); + if (charger->wc_tx_enable) { + value.intval = BATT_TX_EVENT_WIRELESS_TX_ETC; + end_irq = true; + goto INT_END; + } + } else if (!charger->pdata->irq_wpc_pdrc) { + if (charger->pdata->cable_type == SEC_BATTERY_CABLE_NONE) { + irq_src_l = irq_src_l & (~(MFC_INTA_L_STAT_VRECT_MASK)); + mfc_start_wpc_vrect_check_work(charger, 0); + } else if (is_wireless_fake_type(charger->pdata->cable_type)) { + pr_info("%s: restart vrect_check_work(vrect = %d)\n", + __func__, mfc_get_adc(charger, MFC_ADC_VRECT)); + + irq_src_l = irq_src_l & (~(MFC_INTA_L_STAT_VRECT_MASK)); + mfc_cancel_wpc_vrect_check_work(charger); + mfc_start_wpc_vrect_check_work(charger, 3000); + } + } + } + + if (irq_src_l & MFC_INTA_L_OP_MODE_MASK) { + pr_info("%s: MODE CHANGE IRQ !\n", __func__); + __pm_stay_awake(charger->mode_change_ws); + queue_delayed_work(charger->wqueue, &charger->mode_change_work, 0); + } + + if (irq_src_l & MFC_INTA_L_TXCONFLICT_MASK) { + pr_info("@Tx_Mode %s: TXCONFLICT IRQ !\n", __func__); + if (charger->wc_tx_enable && (status_l & MFC_STAT_L_TXCONFLICT_MASK)) { + value.intval = BATT_TX_EVENT_WIRELESS_TX_ETC; + end_irq = true; + goto INT_END; + } + } + + if (irq_src_h & MFC_INTA_H_TRX_DATA_RECEIVED_MASK) { + pr_info("%s: TX RECEIVED IRQ !\n", __func__); + if (charger->wc_tx_enable && !delayed_work_pending(&charger->wpc_tx_isr_work)) { + __pm_stay_awake(charger->wpc_tx_ws); + queue_delayed_work(charger->wqueue, &charger->wpc_tx_isr_work, 0); + } else if (charger->pdata->cable_type == SEC_BATTERY_CABLE_WIRELESS_STAND || + charger->pdata->cable_type == SEC_BATTERY_CABLE_WIRELESS_HV_STAND) { + pr_info("%s: Don't run ISR_WORK for NO ACK !\n", __func__); + } else if (!delayed_work_pending(&charger->wpc_isr_work)) { + __pm_stay_awake(charger->wpc_rx_ws); + queue_delayed_work(charger->wqueue, &charger->wpc_isr_work, 0); + } + } + + if (irq_src_h & MFC_INTA_H_TX_CON_DISCON_MASK) { + if (charger->wc_tx_enable) { + pr_info("@Tx_Mode %s: TX CONNECT IRQ !\n", __func__); + charger->tx_status = SEC_TX_POWER_TRANSFER; + if (status_h & MFC_STAT_H_TX_CON_DISCON_MASK) { + /* determine rx connection status with tx sharing mode */ + if (!charger->wc_rx_connected) { + charger->wc_rx_connected = true; + queue_delayed_work(charger->wqueue, &charger->wpc_rx_connection_work, 0); + } + } else { + /* determine rx connection status with tx sharing mode */ + if (!charger->wc_rx_connected) { + pr_info("@Tx_Mode %s: Ignore IRQ!! already Rx disconnected!\n", __func__); + } else { + charger->wc_rx_connected = false; + queue_delayed_work(charger->wqueue, &charger->wpc_rx_connection_work, 0); + } + } + } else { + if (status_l & MFC_INTA_L_STAT_VRECT_MASK) + pr_info("%s: VRECT SIGNAL !\n", __func__); + } + } + + /* when rx is not detacted in 3 mins */ + if (irq_src_h & MFC_INTA_H_TX_MODE_RX_NOT_DET_MASK) { + if (!(irq_src_h & MFC_INTA_H_TX_CON_DISCON_MASK)) { + pr_info("@Tx_Mode %s: Receiver NOT DETECTED IRQ !\n", __func__); + if (charger->wc_tx_enable) { + value.intval = BATT_TX_EVENT_WIRELESS_TX_ETC; + end_irq = true; + goto INT_END; + } + } + } + + if (irq_src_h & MFC_STAT_H_ADT_RECEIVED_MASK) { + pr_info("%s %s: ADT Received IRQ !\n", WC_AUTH_MSG, __func__); + + mfc_auth_adt_read(charger, temp_buffer_rdata); + pr_info("%s %s: total_adt_dataSize=%d, adt_readSize=%d \n", WC_AUTH_MSG, __func__, total_adt_dataSize, adt_readSize); + if(adt_readSize > ADT_DATA_BUFFER_SIZE) + { + pr_info("%s %s: auth_read_data_step = %d \n", WC_AUTH_MSG, __func__, auth_read_data_step); + memcpy((ADT_buffer_rdata+ADT_DATA_BUFFER_SIZE*auth_read_data_step), temp_buffer_rdata, ADT_DATA_BUFFER_SIZE); + auth_read_data_step++; + } else { + memcpy((ADT_buffer_rdata+ADT_DATA_BUFFER_SIZE*auth_read_data_step), temp_buffer_rdata, adt_readSize); + auth_read_data_step = 0; + } + } + + if (irq_src_h & MFC_STAT_H_ADT_SENT_MASK) { + pr_info("%s %s: ADT Sent successful IRQ !\n", WC_AUTH_MSG, __func__); + mfc_reg_update(charger->client, MFC_INT_A_ENABLE_H_REG, 0, MFC_STAT_H_ADT_SENT_MASK); + } + + if (irq_src_h & MFC_INTA_H_TX_FOD_MASK) { + pr_info("%s: TX FOD IRQ !\n", __func__); + + ret = mfc_reg_read(charger->client, MFC_SYS_OP_MODE_REG, &fod_type); + pr_info("%s: fod_type = 0x%x\n", __func__, (fod_type & 0xFF)); + + // this code will move to wpc_tx_isr_work + if (charger->wc_tx_enable) { + if (status_h & MFC_STAT_H_TX_FOD_MASK) { + charger->wc_rx_fod = true; + value.intval = BATT_TX_EVENT_WIRELESS_TX_FOD; + end_irq = true; + goto INT_END; + } + } + } + + if (irq_src_h & MFC_INTA_H_TX_OCP_MASK) + pr_info("%s: TX Over Current IRQ !\n", __func__); + + // TODO: Need checking from here + if (irq_src1_l & MFC_INTA1_L_EPP_SUPPORT_MASK) { + pr_info("@EPP %s: EPP Suported IRQ !\n", __func__); + //charger->epp_supported = true; + } + + if (irq_src1_l & MFC_INTA1_L_EPP_NEGO_PASS_MASK) { + pr_info("@EPP %s: EPP NEGO PASS IRQ !\n", __func__); + //mfc_mpp_epp_nego_power_set(charger, MFC_RX_MPP_NEGO_POWER_15W); + mfc_mpp_epp_nego_done(charger); + } + + if (irq_src1_l & MFC_INTA1_L_EPP_NEGO_FAIL_MASK) { + pr_info("@EPP %s: EPP NEGO FAIL IRQ !\n", __func__); + //charger->epp_nego_fail = true; + } + + + if (irq_src1_l & MFC_INTA1_L_MPP_SUPPORT_MASK) { + pr_info("@MPP %s: MPP Suported IRQ !\n", __func__); + //charger->mpp_supported = true; + } + + if (irq_src1_l & MFC_INTA1_L_NEGO_PASS_360KHZ_MASK) { + pr_info("@MPP %s: MPP NEGO PASS 360 IRQ !\n", __func__); + //charger->mpp_nego_pass_360 = true; + mfc_mpp_epp_nego_done(charger); + } + + if (irq_src1_l & MFC_INTA1_L_INCREASE_POWER_MASK) { + pr_info("@MPP %s: MPP POWER INC IRQ !\n", __func__); + //charger->mpp_power_inc = true; + } + + if (irq_src1_l & MFC_INTA1_L_DECREASE_POWER_MASK) { + pr_info("@MPP %s: MPP POWER DEC IRQ !\n", __func__); + //charger->mpp_power_dec = true; + } + + if (irq_src1_l & MFC_INTA1_L_EXIT_CLOAK_MASK) { + u8 cloak_exit_reason; + //pr_info("@MPP %s: MPP EXIT CLOAK IRQ !\n", __func__); + //charger->mpp_exit_cloak = true; + + mfc_reg_read(charger->client, MFC_EXIT_CLOAK_REASON_REG, &cloak_exit_reason); + pr_info("@MPP %s: Exit Cloak IRQ - reason : 0x%x mpp_cloak(%d)\n", + __func__, cloak_exit_reason, charger->mpp_cloak); + + value.intval = 0; + psy_do_property("wireless", set, POWER_SUPPLY_EXT_PROP_MPP_CLOAK, value); + } + + // TODO: Need checking to here + +INT_END: + ret = mfc_reg_write(charger->client, MFC_INT_A_CLEAR_L_REG, irq_src_l); // clear int + ret = mfc_reg_write(charger->client, MFC_INT_A_CLEAR_H_REG, irq_src_h); // clear int + ret = mfc_reg_write(charger->client, MFC_INT_A_CLEAR_L_REG1, irq_src1_l); // clear int + ret = mfc_reg_write(charger->client, MFC_INT_A_CLEAR_H_REG1, irq_src1_h); // clear int + mfc_set_cmd_l_reg(charger, MFC_CMD_CLEAR_INT_MASK, MFC_CMD_CLEAR_INT_MASK); // command + ret = mfc_reg_read(charger->client, MFC_STATUS_L_REG, &status_l); + ret = mfc_reg_read(charger->client, MFC_STATUS_H_REG, &status_h); + ret = mfc_reg_read(charger->client, MFC_STATUS_L_REG1, &status1_l); + ret = mfc_reg_read(charger->client, MFC_STATUS_H_REG1, &status1_h); + pr_info("%s: status after clear irq, status(0x%x) status1(0x%x)\n", __func__, status_h << 8 | status_l, status1_h << 8 | status1_l); + + /* tx off should work having done i2c */ + if (end_irq) + psy_do_property("wireless", set, POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ERR, value); + + pr_info("%s: end!\n", __func__); + + __pm_relax(charger->wpc_ws); + + return IRQ_HANDLED; +} + +static void mfc_chg_parse_qfod_data(struct device_node *np, + mfc_charger_platform_data_t *pdata) +{ + struct device_node *child = NULL; + int ret = 0, idx = 0; + + np = of_find_node_by_name(np, "qfod_list"); + if (!np) { + pr_err("%s: qfod list is NULL!\n", __func__); + return; + } + + ret = of_property_read_u32(np, "number", &pdata->qfod_data_num); + if (ret < 0) { + pr_err("%s: qfod data size is NULL!\n", __func__); + return; + } + pr_info("%s: pdata->qfod_data_num=%d\n", __func__, pdata->qfod_data_num); + + pdata->qfod_list = kcalloc(pdata->qfod_data_num, sizeof(struct mfc_qfod_data), GFP_KERNEL); + if (pdata->qfod_list == NULL) { + pdata->qfod_data_num = 0; + return; + } + + for_each_child_of_node(np, child) { + struct mfc_qfod_data *pqfod_data = NULL; + + if (idx >= pdata->qfod_data_num) { + pr_err("%s: skip set qfod data because of overflow\n", __func__); + break; + } + pqfod_data = &pdata->qfod_list[idx]; + + if (sscanf(child->name, "rx_0x%X", &pqfod_data->rx_id) < 0) { + pr_err("%s: failed to get rx id of %s\n", __func__, child->name); + continue; + } + pr_info("%s: child->name: %s\n", __func__, child->name); + + ret = of_property_read_u32(child, "qfod_th0", &qfod_th0[idx]); + if (ret < 0) { + pr_err("%s: failed to get qfod_th0 of %s\n", __func__, child->name); + continue; + } + pr_info("%s: qfod_th0 = %d, idx = %d\n", __func__, qfod_th0[idx], idx); + + ret = of_property_read_u32(child, "qfod_th1", &qfod_th1[idx]); + if (ret < 0) { + pr_err("%s: failed to get qfod_th1 of %s\n", __func__, child->name); + continue; + } + pr_info("%s: qfod_th1 = %d, idx = %d\n", __func__, qfod_th1[idx], idx); + + ret = of_property_read_u32(child, "qfod_th2", &qfod_th2[idx]); + if (ret < 0) { + pr_err("%s: failed to get qfod_th2 of %s\n", __func__, child->name); + continue; + } + pr_info("%s: qfod_th2 = %d, idx = %d\n", __func__, qfod_th2[idx], idx); + + ret = of_property_read_u32(child, "qfod_th3", &qfod_th3[idx]); + if (ret < 0) { + pr_err("%s: failed to get qfod_th3 of %s\n", __func__, child->name); + continue; + } + pr_info("%s: qfod_th3 = %d, idx = %d\n", __func__, qfod_th3[idx], idx); + + ret = of_property_read_u32(child, "sspth_value", &sspth_value[idx]); + if (ret < 0) { + pr_err("%s: failed to get sspth_value of %s\n", __func__, child->name); + continue; + } + pr_info("%s: sspth_value = %d, idx = %d\n", __func__, sspth_value[idx], idx); + + ret = of_property_read_u32(child, "qbase_value", &qbase_value[idx]); + if (ret < 0) { + pr_err("%s: failed to get qbase_value of %s\n", __func__, child->name); + continue; + } + pr_info("%s: qbase_value = %d, idx = %d\n", __func__, qbase_value[idx], idx); + + ret = of_property_read_u32(child, "qfod_tws_th3", &qfod_tws_th3[idx]); + if (ret < 0) { + pr_err("%s: failed to get qfod_tws_th3 of %s\n", __func__, child->name); + continue; + } + pr_info("%s: qfod_tws_th3 = %d, idx = %d\n", __func__, qfod_tws_th3[idx], idx); + + ret = of_property_read_u32(child, "qfod_phone_th4", &qfod_phone_th4[idx]); + if (ret < 0) { + pr_err("%s: failed to get qfod_phone_th4 of %s\n", __func__, child->name); + continue; + } + pr_info("%s: qfod_phone_th4 = %d, idx = %d\n", __func__, qfod_phone_th4[idx], idx); + + idx++; + } +} + +#if defined(CONFIG_WIRELESS_IC_PARAM) +static void mfc_parse_param_value(struct mfc_charger_data *charger) +{ + unsigned int param_value = mfc_get_wrlic(); + + charger->wireless_param_info = param_value; + charger->wireless_chip_id_param = (param_value & 0xFF000000) >> 24; + charger->wireless_fw_ver_param = (param_value & 0x00FFFF00) >> 8; + charger->wireless_fw_mode_param = (param_value & 0x000000F0) >> 4; + + pr_info("%s: wireless_ic(0x%08X), chip_id(0x%02X), fw_ver(0x%04X), fw_mode(0x%01X)\n", + __func__, param_value, charger->wireless_chip_id_param, + charger->wireless_fw_ver_param, charger->wireless_fw_mode_param); +} +#endif + +static int mfc_chg_parse_dt(struct device *dev, + mfc_charger_platform_data_t *pdata) +{ + int ret = 0; + struct device_node *np = dev->of_node; + enum of_gpio_flags irq_gpio_flags; + int len, i; + const u32 *p; + + if (!np) { + pr_err("%s: np NULL\n", __func__); + return 1; + } + ret = of_property_read_string(np, + "battery,wireless_charger_name", (char const **)&pdata->wireless_charger_name); + if (ret < 0) + pr_info("%s: Wireless Charger name is Empty\n", __func__); + + ret = of_property_read_string(np, + "battery,charger_name", (char const **)&pdata->wired_charger_name); + if (ret < 0) + pr_info("%s: Charger name is Empty\n", __func__); + + ret = of_property_read_string(np, + "battery,fuelgauge_name", (char const **)&pdata->fuelgauge_name); + if (ret < 0) + pr_info("%s: Fuelgauge name is Empty\n", __func__); + + ret = of_property_read_u32(np, "battery,phone_fod_thresh1", + &pdata->phone_fod_thresh1); + if (ret < 0) { + pr_info("%s: fail to read phone_fod_thresh1\n", __func__); + pdata->phone_fod_thresh1 = 0x01F4; /* 0x01F4 :default 500mW *///0x07D0; /* default 2000mW */ + } + + ret = of_property_read_u32(np, "battery,buds_fod_thresh1", + &pdata->buds_fod_thresh1); + if (ret < 0) { + pr_info("%s: fail to read buds_fod_thresh1\n", __func__); + pdata->buds_fod_thresh1 = 0x01F4; /* 0x01F4 :default 500mW *///0x07D0; /* default 2000mW */ + } + + ret = of_property_read_u32(np, "battery,buds_fod_ta_thresh", + &pdata->buds_fod_ta_thresh); + if (ret < 0) { + pr_info("%s: fail to read buds_fod_ta_thresh\n", __func__); + pdata->buds_fod_ta_thresh = 0x0320; /* default 800mW */ + } + + ret = of_property_read_u32(np, "battery,tx_max_op_freq", + &pdata->tx_max_op_freq); + if (ret < 0) + pr_info("%s: fail to read tx_max_op_freq\n", __func__); + + ret = of_property_read_u32(np, "battery,tx_min_op_freq", + &pdata->tx_min_op_freq); + if (ret < 0) + pr_info("%s: fail to read tx_min_op_freq\n", __func__); + + if (carrierid_is("XAC")) { + ret = of_property_read_u32(np, "battery,cep_timeout_xac", + &pdata->cep_timeout); + } else { + ret = of_property_read_u32(np, "battery,cep_timeout", + &pdata->cep_timeout); + } + if (ret < 0) { + pr_info("%s: fail to read cep_timeout\n", __func__); + pdata->cep_timeout = 0; + } + pr_info("%s: cep_timeout %d\n", __func__, pdata->cep_timeout); + + ret = of_property_read_u32(np, "battery,tx_conflict_curr", + &pdata->tx_conflict_curr); + if (ret < 0) { + pr_info("%s: fail to read tx_conflict_curr\n", __func__); + pdata->tx_conflict_curr = 1200; + } + + /* wpc_det */ + ret = pdata->wpc_det = of_get_named_gpio_flags(np, "battery,wpc_det", + 0, &irq_gpio_flags); + if (ret < 0) { + pr_err("%s: can't get wpc_det\r\n", __func__); + } else { + pdata->irq_wpc_det = gpio_to_irq(pdata->wpc_det); + pr_info("%s: wpc_det = 0x%x, irq_wpc_det = 0x%x\n", + __func__, pdata->wpc_det, pdata->irq_wpc_det); + } + /* wpc_int (This GPIO means MFC_AP_INT) */ + ret = pdata->wpc_int = of_get_named_gpio_flags(np, "battery,wpc_int", + 0, &irq_gpio_flags); + if (ret < 0) { + pr_err("%s: can't wpc_int\r\n", __func__); + } else { + pdata->irq_wpc_int = gpio_to_irq(pdata->wpc_int); + pr_info("%s: wpc_int = 0x%x, irq_wpc_int = 0x%x\n", + __func__, pdata->wpc_int, pdata->irq_wpc_int); + } + + /* mst_pwr_en (MST PWR EN) */ + ret = pdata->mst_pwr_en = of_get_named_gpio_flags(np, "battery,mst_pwr_en", + 0, &irq_gpio_flags); + if (ret < 0) + pr_err("%s: can't mst_pwr_en\r\n", __func__); + + pdata->pdrc_vrect_clear = of_property_read_bool(np, "battery,pdrc_vrect_clear"); + /* wpc_pdrc (This GPIO means VRECT_INT) */ + ret = pdata->wpc_pdrc = of_get_named_gpio_flags(np, "battery,wpc_pdrc", + 0, &irq_gpio_flags); + if (ret < 0) { + pr_err("%s : can't wpc_pdrc\r\n", __func__); + } else if (!pdata->pdrc_vrect_clear) { + pdata->irq_wpc_pdrc = gpio_to_irq(pdata->wpc_pdrc); + pr_info("%s wpc_pdrc = 0x%x, irq_wpc_pdrc = 0x%x\n", + __func__, pdata->wpc_pdrc, pdata->irq_wpc_pdrc); + } + + /* wpc_pdet_b (PDET_B) */ + ret = pdata->wpc_pdet_b = of_get_named_gpio_flags(np, "battery,wpc_pdet_b", + 0, &irq_gpio_flags); + if (ret < 0) { + pr_err("%s : can't wpc_pdet_b\r\n", __func__); + } else { + pdata->irq_wpc_pdet_b = gpio_to_irq(pdata->wpc_pdet_b); + pr_info("%s wpc_pdet_b = 0x%x, irq_wpc_pdet_b = 0x%x\n", + __func__, pdata->wpc_pdet_b, pdata->irq_wpc_pdet_b); + } + + /* wpc_en (MFC EN) */ + ret = pdata->wpc_en = of_get_named_gpio_flags(np, "battery,wpc_en", + 0, &irq_gpio_flags); + if (ret < 0) + pr_err("%s: can't wpc_en\r\n", __func__); + + ret = pdata->mag_det = of_get_named_gpio_flags(np, "battery,wpc_mag_det", + 0, &irq_gpio_flags); + if (ret < 0) + dev_err(dev, "%s: can't wpc_mag_det\r\n", __func__); + + /* coil_sw_en (COIL SW EN N) */ + ret = pdata->coil_sw_en = of_get_named_gpio_flags(np, "battery,coil_sw_en", + 0, &irq_gpio_flags); + if (ret < 0) + pr_err("%s: can't coil_sw_en\r\n", __func__); + + /* ping_nen (PING nEN) */ + ret = pdata->ping_nen = of_get_named_gpio_flags(np, "battery,wpc_ping_nen", + 0, &irq_gpio_flags); + if (ret < 0) + pr_err("%s : can't wpc_ping_nen\r\n", __func__); + + p = of_get_property(np, "battery,wireless20_vout_list", &len); + if (p) { + len = len / sizeof(u32); + pdata->len_wc20_list = len; + pdata->wireless20_vout_list = kcalloc(len, sizeof(*pdata->wireless20_vout_list), GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,wireless20_vout_list", + pdata->wireless20_vout_list, len); + + for (i = 0; i < len; i++) + pr_info("%s: wireless20_vout_list = %d ", __func__, pdata->wireless20_vout_list[i]); + pr_info("%s: len_wc20_list = %d ", __func__, pdata->len_wc20_list); + } else { + pr_err("%s: there is no wireless20_vout_list\n", __func__); + } + + p = of_get_property(np, "battery,wireless20_vrect_list", &len); + if (p) { + len = len / sizeof(u32); + pdata->wireless20_vrect_list = + kcalloc(len, sizeof(*pdata->wireless20_vrect_list), GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,wireless20_vrect_list", + pdata->wireless20_vrect_list, len); + + for (i = 0; i < len; i++) + pr_info("%s: wireless20_vrect_list = %d ", __func__, pdata->wireless20_vrect_list[i]); + } else { + pr_err("%s: there is no wireless20_vrect_list\n", __func__); + } + + p = of_get_property(np, "battery,wireless20_max_power_list", &len); + if (p) { + len = len / sizeof(u32); + pdata->wireless20_max_power_list = + kcalloc(len, sizeof(*pdata->wireless20_max_power_list), GFP_KERNEL); + ret = of_property_read_u32_array(np, "battery,wireless20_max_power_list", + pdata->wireless20_max_power_list, len); + + for (i = 0; i < len; i++) + pr_info("%s: wireless20_max_power_list = %d\n", + __func__, pdata->wireless20_max_power_list[i]); + } else { + pr_err("%s: there is no wireless20_max_power_list\n", __func__); + } + pdata->no_hv = + of_property_read_bool(np, "battery,wireless_no_hv"); + + ret = of_property_read_u32(np, "battery,wpc_vout_ctrl_full", + &pdata->wpc_vout_ctrl_full); + if (ret) + pr_err("%s: wpc_vout_ctrl_full is Empty\n", __func__); + + if (pdata->wpc_vout_ctrl_full) + pdata->wpc_headroom_ctrl_full = of_property_read_bool(np, "battery,wpc_headroom_ctrl_full"); + + pdata->mis_align_guide = of_property_read_bool(np, "battery,mis_align_guide"); + if (pdata->mis_align_guide) { + ret = of_property_read_u32(np, "battery,mis_align_target_vout", + &pdata->mis_align_target_vout); + if (ret) + pdata->mis_align_target_vout = 5000; + + ret = of_property_read_u32(np, "battery,mis_align_offset", + &pdata->mis_align_offset); + if (ret) + pdata->mis_align_offset = 200; + pr_info("%s: mis_align_guide, vout:%d, offset:%d\n", __func__, + pdata->mis_align_target_vout, pdata->mis_align_offset); + } + pdata->unknown_cmb_ctrl = of_property_read_bool(np, "battery,unknown_cmb_ctrl"); + + pdata->default_clamp_volt = of_property_read_bool(np, "battery,default_clamp_volt"); + if (pdata->default_clamp_volt) + pr_info("%s: default_clamp_volt(%d)\n", __func__, pdata->default_clamp_volt); + + pdata->enable_qfod_param = of_property_read_bool(np, "battery,enable_qfod_param"); + if (pdata->enable_qfod_param) + pr_info("%s: enable_qfod_param(%d)\n", __func__, pdata->enable_qfod_param); + + ret = of_property_read_u32(np, "battery,mpp_epp_vout", + &pdata->mpp_epp_vout); + if (ret) { + pr_err("%s: mpp_epp_vout is Empty\n", __func__); + pdata->mpp_epp_vout = WIRELESS_VOUT_12V; + } + + ret = of_property_read_u32(np, "battery,mpp_epp_def_power", + &pdata->mpp_epp_def_power); + if (ret) { + pr_err("%s: mpp_epp_def_power is Empty\n", __func__); + pdata->mpp_epp_def_power = TX_RX_POWER_5W * 100000; + } + + ret = of_property_read_u32(np, "battery,mpp_epp_max_count", + &pdata->mpp_epp_max_count); + if (ret) { + pr_err("%s: mpp_epp_max_count is Empty\n", __func__); + pdata->mpp_epp_max_count = 3; + } + + mfc_chg_parse_qfod_data(np, pdata); + np = dev->of_node; + + return 0; +} + +static int mfc_create_attrs(struct device *dev) +{ + int i, rc; + + for (i = 0; i < (int)ARRAY_SIZE(mfc_attrs); i++) { + rc = device_create_file(dev, &mfc_attrs[i]); + if (rc) + goto create_attrs_failed; + } + return rc; + +create_attrs_failed: + pr_err("%s: failed (%d)\n", __func__, rc); + while (i--) + device_remove_file(dev, &mfc_attrs[i]); + return rc; +} + +ssize_t nu1668_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct mfc_charger_data *charger = power_supply_get_drvdata(psy); + + const ptrdiff_t offset = attr - mfc_attrs; + int i = 0; + + pr_info("[nu1668] %s\n", __func__); + + switch (offset) { + case MFC_ADDR: + i += scnprintf(buf + i, PAGE_SIZE - i, "0x%x\n", charger->addr); + break; + case MFC_SIZE: + i += scnprintf(buf + i, PAGE_SIZE - i, "0x%x\n", charger->size); + break; + case MFC_DATA: + if (charger->size == 0) { + charger->size = 1; + } else if (charger->size + charger->addr <= 0xFFFF) { + u8 data; + int j; + + for (j = 0; j < charger->size; j++) { + if (mfc_reg_read(charger->client, charger->addr + j, &data) < 0) { + pr_info("[nu1668] %s: read fail\n", __func__); + i += scnprintf(buf + i, PAGE_SIZE - i, "addr: 0x%x read fail\n", + charger->addr + j); + continue; + } + i += scnprintf(buf + i, PAGE_SIZE - i, "addr: 0x%x, data: 0x%x\n", + charger->addr + j, data); + } + } + break; + case MFC_PACKET: + break; + default: + return -EINVAL; + } + return i; +} + +ssize_t nu1668_store_attrs(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct mfc_charger_data *charger = power_supply_get_drvdata(psy); + const ptrdiff_t offset = attr - mfc_attrs; + int x, ret; + u8 header, data_com, data_val; + + pr_info("[nu1668] %s\n", __func__); + + switch (offset) { + case MFC_ADDR: + if (sscanf(buf, "0x%4x\n", &x) == 1) + charger->addr = x; + ret = count; + break; + case MFC_SIZE: + if (sscanf(buf, "%5d\n", &x) == 1) + charger->size = x; + ret = count; + break; + case MFC_DATA: + if (sscanf(buf, "0x%10x", &x) == 1) { + u8 data = x; + + if (mfc_reg_write(charger->client, charger->addr, data) < 0) + pr_info("[nu1668] %s: addr: 0x%x write fail\n", __func__, charger->addr); + } + ret = count; + break; + case MFC_PACKET: + { + u32 header_temp, data_com_temp, data_val_temp; + + if (sscanf(buf, "0x%4x 0x%4x 0x%4x\n", &header_temp, &data_com_temp, &data_val_temp) == 3) { + header = (u8)header_temp; + data_com = (u8)data_com_temp; + data_val = (u8)data_val_temp; + pr_info("[nu1668] %s 0x%x, 0x%x, 0x%x\n", __func__, header, data_com, data_val); + mfc_send_packet(charger, header, data_com, &data_val, 1); + } + ret = count; + } + break; + default: + ret = -EINVAL; + } + return ret; +} + +static const struct power_supply_desc mfc_charger_power_supply_desc = { + .name = "mfc-charger", + .type = POWER_SUPPLY_TYPE_UNKNOWN, + .properties = mfc_charger_props, + .num_properties = ARRAY_SIZE(mfc_charger_props), + .get_property = nu1668_chg_get_property, + .set_property = nu1668_chg_set_property, +}; + +static void mfc_wpc_int_req_work(struct work_struct *work) +{ + struct mfc_charger_data *charger = + container_of(work, struct mfc_charger_data, wpc_int_req_work.work); + + int ret = 0; + + pr_info("%s\n", __func__); + /* wpc_irq */ + if (charger->pdata->irq_wpc_int) { + int irq_flag = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; + + if (!charger->pdata->irq_wpc_pdrc) + irq_flag |= IRQF_TRIGGER_RISING; + + msleep(100); + ret = request_threaded_irq(charger->pdata->irq_wpc_int, + mfc_wpc_irq_handler, mfc_wpc_irq_thread, + irq_flag, "wpc-irq", charger); + if (ret) + pr_err("%s: Failed to Request IRQ\n", __func__); + } + if (ret < 0) + free_irq(charger->pdata->irq_wpc_det, NULL); +} + +static enum alarmtimer_restart mfc_phm_alarm( + struct alarm *alarm, ktime_t now) +{ + struct mfc_charger_data *charger = container_of(alarm, + struct mfc_charger_data, phm_alarm); + + pr_info("%s: forced escape to PHM\n", __func__); + __pm_stay_awake(charger->wpc_tx_phm_ws); + if (charger->is_suspend) + charger->skip_phm_work_in_sleep = true; + else + queue_delayed_work(charger->wqueue, &charger->wpc_tx_phm_work, 0); + + return ALARMTIMER_NORESTART; +} + +static int nu1668_charger_probe( + struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device_node *of_node = client->dev.of_node; + struct mfc_charger_data *charger; + mfc_charger_platform_data_t *pdata = client->dev.platform_data; + struct power_supply_config mfc_cfg = {}; + int ret = 0; + u8 int_state; + + pr_info("%s: MFC NU1668 Charger Driver Loading\n", __func__); + + if (of_node) { + pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + ret = mfc_chg_parse_dt(&client->dev, pdata); + if (ret < 0) + goto err_parse_dt; + } else { + pdata = client->dev.platform_data; + return -ENOMEM; + } + + charger = kcalloc(1, sizeof(*charger), GFP_KERNEL); + if (charger == NULL) { + ret = -ENOMEM; + goto err_wpc_nomem; + } + charger->dev = &client->dev; + + ret = i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_I2C_BLOCK); + if (!ret) { + ret = i2c_get_functionality(client->adapter); + pr_err("[nu1668] I2C functionality is not supported.\n"); + ret = -ENODEV; + goto err_i2cfunc_not_support; + } + + is_shutdn = false; + charger->client = client; + charger->pdata = pdata; + + pr_info("%s: %s\n", __func__, charger->pdata->wireless_charger_name); + + i2c_set_clientdata(client, charger); +#if defined(CONFIG_WIRELESS_IC_PARAM) + mfc_parse_param_value(charger); +#endif + + charger->fod = mfc_fod_init(charger->dev, MFC_NUM_FOD_REG, nu1668_set_fod); + if (IS_ERR(charger->fod)) + pr_err("%s: failed to init fod (ret = %ld)\n", __func__, PTR_ERR(charger->fod)); + charger->cmfet = mfc_cmfet_init(charger->dev, nu1668_set_cmfet); + if (IS_ERR(charger->cmfet)) + pr_err("%s: failed to init cmfet (ret = %ld)\n", __func__, PTR_ERR(charger->cmfet)); + + charger->pdata->cable_type = SEC_BATTERY_CABLE_NONE; + charger->pdata->is_charging = 0; + charger->tx_status = 0; + charger->pdata->cs100_status = 0; + charger->pdata->vout_status = MFC_VOUT_5V; + charger->pdata->opfq_cnt = 0; + charger->flicker_delay = 1000; + charger->flicker_vout_threshold = MFC_VOUT_8V; + charger->is_mst_on = MST_MODE_0; + charger->chip_id = MFC_CHIP_NUVOLTA; + charger->is_otg_on = false; + charger->led_cover = 0; + charger->vout_mode = WIRELESS_VOUT_OFF; + charger->is_full_status = 0; + charger->is_afc_tx = false; + charger->pad_ctrl_by_lcd = false; + charger->wc_tx_enable = false; + charger->initial_wc_check = false; + charger->wc_rx_connected = false; + charger->wc_rx_fod = false; + charger->adt_transfer_status = WIRELESS_AUTH_WAIT; + charger->current_rx_power = TX_RX_POWER_0W; + charger->tx_id = TX_ID_UNKNOWN; + charger->tx_id_cnt = 0; + charger->wc_ldo_status = MFC_LDO_ON; + charger->tx_id_done = false; + charger->wc_rx_type = NO_DEV; + charger->is_suspend = false; + charger->device_event = 0; + charger->duty_min = MIN_DUTY_SETTING_20_DATA; + charger->wpc_en_flag = (WPC_EN_SYSFS | WPC_EN_CHARGING | WPC_EN_CCIC); + charger->req_tx_id = false; + charger->afc_tx_done = false; + charger->sleep_mode = false; + charger->req_afc_delay = REQ_AFC_DLY; + charger->mis_align_tx_try_cnt = 1; + charger->wc_checking_align = false; + charger->wc_align_check_start.tv_sec = 0; + charger->vout_strength = 100; + charger->reg_access_lock = false; + charger->det_state = 0; + charger->pdrc_state = 1; + + // TODO: need to check this is right. + //charger->epp_supported = false; + //charger->epp_nego_pass = false; + //charger->epp_nego_fail = false; + + //charger->mpp_supported = false; + //charger->mpp_nego_pass_360 = false; + //charger->mpp_power_inc = false; + //charger->mpp_power_dec = false; + //charger->mpp_exit_cloak = false; + + charger->nego_done_power = 0; + charger->potential_load_power = 0; + charger->negotiable_load_power = 0; + charger->mpp_epp_tx_id = 0; + charger->mpp_cloak = 0; + charger->epp_time = 1000; + + mutex_init(&charger->io_lock); + mutex_init(&charger->wpc_en_lock); + mutex_init(&charger->fw_lock); + + charger->wqueue = create_singlethread_workqueue("mfc_workqueue"); + if (!charger->wqueue) { + pr_err("%s: Fail to Create Workqueue\n", __func__); + goto err_pdata_free; + } + + charger->wpc_ws = wakeup_source_register(&client->dev, "wpc_ws"); + charger->wpc_det_ws = wakeup_source_register(&client->dev, "wpc_det_ws"); + charger->wpc_rx_ws = wakeup_source_register(&client->dev, "wpc_rx_ws"); + charger->wpc_tx_ws = wakeup_source_register(&client->dev, "wpc_tx_ws"); + charger->wpc_update_ws = wakeup_source_register(&client->dev, "wpc_update_ws"); + charger->wpc_tx_duty_min_ws = wakeup_source_register(&client->dev, "wpc_tx_duty_min_ws"); + charger->wpc_afc_vout_ws = wakeup_source_register(&client->dev, "wpc_afc_vout_ws"); + charger->wpc_vout_mode_ws = wakeup_source_register(&client->dev, "wpc_vout_mode_ws"); + charger->wpc_rx_det_ws = wakeup_source_register(&client->dev, "wpc_rx_det_ws"); + charger->wpc_tx_phm_ws = wakeup_source_register(&client->dev, "wpc_tx_phm_ws"); + charger->wpc_tx_id_ws = wakeup_source_register(&client->dev, "wpc_tx_id_ws"); + charger->wpc_tx_pwr_budg_ws = wakeup_source_register(&client->dev, "wpc_tx_pwr_budg_ws"); + charger->wpc_pdrc_ws = wakeup_source_register(&client->dev, "wpc_pdrc_ws"); + charger->align_check_ws = wakeup_source_register(&client->dev, "align_check_ws"); + charger->mode_change_ws = wakeup_source_register(&client->dev, "mode_change_ws"); + charger->wpc_cs100_ws = wakeup_source_register(&client->dev, "wpc_cs100_ws"); + charger->wpc_pdet_b_ws = wakeup_source_register(&client->dev, "wpc_pdet_b_ws"); + charger->wpc_rx_phm_ws = wakeup_source_register(&client->dev, "wpc_rx_phm_ws"); + charger->wpc_rx_power_trans_fail_ws = wakeup_source_register(&client->dev, "wpc_rx_power_trans_fail_ws"); + charger->wpc_phm_exit_ws = wakeup_source_register(&client->dev, "wpc_phm_exit_ws"); + charger->wpc_vrect_check_ws = wakeup_source_register(&client->dev, "wpc_vrect_check_ws"); + charger->epp_clear_ws = wakeup_source_register(&client->dev, "epp_clear_ws"); + charger->epp_count_ws = wakeup_source_register(&client->dev, "epp_count_ws"); + + /* wpc_det */ + if (charger->pdata->irq_wpc_det) { + INIT_DELAYED_WORK(&charger->wpc_det_work, mfc_wpc_det_work); + } + + /* wpc_irq (INT_A) */ + if (charger->pdata->irq_wpc_int) { + INIT_DELAYED_WORK(&charger->wpc_isr_work, mfc_wpc_isr_work); + INIT_DELAYED_WORK(&charger->wpc_tx_isr_work, mfc_wpc_tx_isr_work); + INIT_DELAYED_WORK(&charger->wpc_tx_id_work, mfc_wpc_tx_id_work); + INIT_DELAYED_WORK(&charger->wpc_tx_pwr_budg_work, mfc_wpc_tx_pwr_budg_work); + INIT_DELAYED_WORK(&charger->wpc_int_req_work, mfc_wpc_int_req_work); + } + INIT_DELAYED_WORK(&charger->wpc_vout_mode_work, mfc_wpc_vout_mode_work); + INIT_DELAYED_WORK(&charger->wpc_afc_vout_work, mfc_wpc_afc_vout_work); + INIT_DELAYED_WORK(&charger->wpc_fw_update_work, mfc_wpc_fw_update_work); + INIT_DELAYED_WORK(&charger->wpc_i2c_error_work, mfc_wpc_i2c_error_work); + INIT_DELAYED_WORK(&charger->wpc_rx_type_det_work, mfc_wpc_rx_type_det_work); + INIT_DELAYED_WORK(&charger->wpc_rx_connection_work, mfc_wpc_rx_connection_work); + INIT_DELAYED_WORK(&charger->wpc_tx_duty_min_work, mfc_tx_duty_min_work); + INIT_DELAYED_WORK(&charger->wpc_tx_phm_work, mfc_tx_phm_work); + INIT_DELAYED_WORK(&charger->wpc_rx_power_work, mfc_wpc_rx_power_work); + INIT_DELAYED_WORK(&charger->wpc_init_work, mfc_wpc_init_work); + INIT_DELAYED_WORK(&charger->align_check_work, mfc_wpc_align_check_work); + INIT_DELAYED_WORK(&charger->mode_change_work, mfc_wpc_mode_change_work); + INIT_DELAYED_WORK(&charger->wpc_cs100_work, mfc_cs100_work); + INIT_DELAYED_WORK(&charger->wpc_rx_phm_work, mfc_rx_phm_work); + INIT_DELAYED_WORK(&charger->wpc_rx_power_trans_fail_work, mfc_rx_power_trans_fail_work); + INIT_DELAYED_WORK(&charger->wpc_phm_exit_work, mfc_wpc_phm_exit_work); + INIT_DELAYED_WORK(&charger->wpc_vrect_check_work, mfc_vrect_check_work); + INIT_DELAYED_WORK(&charger->epp_clear_timer_work, mfc_epp_clear_timer_work); + INIT_DELAYED_WORK(&charger->epp_count_work, mfc_epp_count_work); + + /* + * Default Idle voltage of the INT_A is LOW. + * Prevent the un-wanted INT_A Falling handling. + * This is a work-around, and will be fixed by the revision. + */ + //INIT_DELAYED_WORK(&charger->mst_off_work, mfc_mst_off_work); + + alarm_init(&charger->phm_alarm, ALARM_BOOTTIME, mfc_phm_alarm); + + mfc_cfg.drv_data = charger; + charger->psy_chg = power_supply_register(charger->dev, &mfc_charger_power_supply_desc, &mfc_cfg); + if (IS_ERR(charger->psy_chg)) { + ret = PTR_ERR(charger->psy_chg); + pr_err("%s: Failed to Register psy_chg(%d)\n", __func__, ret); + goto err_supply_unreg; + } + + ret = mfc_create_attrs(&charger->psy_chg->dev); + if (ret) { + pr_err("%s : Failed to create_attrs\n", __func__); + } + + /* Enable interrupts after battery driver load */ + /* wpc_det */ + if (charger->pdata->irq_wpc_det) { + ret = request_threaded_irq(charger->pdata->irq_wpc_det, + mfc_wpc_irq_handler, mfc_wpc_det_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | + IRQF_ONESHOT, + "wpc-det-irq", charger); + if (ret) { + pr_err("%s: Failed to Request IRQ\n", __func__); + goto err_irq_wpc_det; + } + } + + /* wpc_pdrc */ + if (charger->pdata->irq_wpc_pdrc) { + INIT_DELAYED_WORK(&charger->wpc_pdrc_work, mfc_wpc_pdrc_work); + ret = request_threaded_irq(charger->pdata->irq_wpc_pdrc, + mfc_wpc_irq_handler, mfc_wpc_pdrc_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | + IRQF_ONESHOT, + "wpc-pdrc-irq", charger); + if (ret) { + pr_err("%s: Failed to Request pdrc IRQ\n", __func__); + goto err_irq_wpc_det; + } + } + + /* wpc_pdet_b */ + if (charger->pdata->irq_wpc_pdet_b) { + ret = request_threaded_irq(charger->pdata->irq_wpc_pdet_b, + mfc_wpc_irq_handler, mfc_wpc_pdet_b_irq_thread, + IRQF_TRIGGER_RISING | + IRQF_ONESHOT, + "wpc-pdet-b-irq", charger); + if (ret) { + pr_err("%s: Failed to Request pdet_b IRQ\n", __func__); + goto err_irq_wpc_det; + } + } + + /* wpc_irq */ + queue_delayed_work(charger->wqueue, &charger->wpc_int_req_work, msecs_to_jiffies(100)); + + int_state = gpio_get_value(charger->pdata->wpc_int); + pr_info("%s: int_state = %d\n", __func__, int_state); + if (gpio_get_value(charger->pdata->wpc_det)) { + u8 irq_src[2]; + + pr_info("%s: Charger interrupt occurred during lpm\n", __func__); + charger->det_state = gpio_get_value(charger->pdata->wpc_det); + + mfc_reg_read(charger->client, MFC_INT_A_L_REG, &irq_src[0]); + mfc_reg_read(charger->client, MFC_INT_A_H_REG, &irq_src[1]); + /* clear interrupt */ + pr_info("%s: interrupt source(0x%x)\n", __func__, irq_src[1] << 8 | irq_src[0]); + mfc_reg_write(charger->client, MFC_INT_A_CLEAR_L_REG, irq_src[0]); // clear int + mfc_reg_write(charger->client, MFC_INT_A_CLEAR_H_REG, irq_src[1]); // clear int + mfc_set_cmd_l_reg(charger, MFC_CMD_CLEAR_INT_MASK, MFC_CMD_CLEAR_INT_MASK); // command + if (charger->pdata->wired_charger_name) { + union power_supply_propval value; + + ret = psy_do_property(charger->pdata->wired_charger_name, get, + POWER_SUPPLY_EXT_PROP_INPUT_CURRENT_LIMIT_WRL, value); + charger->input_current = (ret) ? 500 : value.intval; + pr_info("%s: updated input current (%d)\n", + __func__, charger->input_current); + } + charger->req_afc_delay = 0; + __pm_stay_awake(charger->wpc_det_ws); + queue_delayed_work(charger->wqueue, &charger->wpc_det_work, 0); + if (!int_state && !delayed_work_pending(&charger->wpc_isr_work)) { + __pm_stay_awake(charger->wpc_rx_ws); + queue_delayed_work(charger->wqueue, &charger->wpc_isr_work, msecs_to_jiffies(2000)); + } + } + + sec_chg_set_dev_init(SC_DEV_WRL_CHG); + ret = sb_wireless_set_op(charger, &nu1668_sbw_op); + pr_info("%s: MFC NU1668 Charger Driver Loaded(%d)\n", __func__, ret); + device_init_wakeup(charger->dev, 1); + return 0; + +err_irq_wpc_det: + power_supply_unregister(charger->psy_chg); +err_supply_unreg: + wakeup_source_unregister(charger->wpc_ws); + wakeup_source_unregister(charger->wpc_det_ws); + wakeup_source_unregister(charger->wpc_rx_ws); + wakeup_source_unregister(charger->wpc_tx_ws); + wakeup_source_unregister(charger->wpc_update_ws); + wakeup_source_unregister(charger->wpc_tx_duty_min_ws); + wakeup_source_unregister(charger->wpc_afc_vout_ws); + wakeup_source_unregister(charger->wpc_vout_mode_ws); + wakeup_source_unregister(charger->wpc_rx_det_ws); + wakeup_source_unregister(charger->wpc_tx_phm_ws); + wakeup_source_unregister(charger->wpc_tx_id_ws); + wakeup_source_unregister(charger->wpc_pdrc_ws); + wakeup_source_unregister(charger->wpc_cs100_ws); + wakeup_source_unregister(charger->wpc_pdet_b_ws); + wakeup_source_unregister(charger->wpc_rx_phm_ws); + wakeup_source_unregister(charger->wpc_phm_exit_ws); + wakeup_source_unregister(charger->wpc_vrect_check_ws); + wakeup_source_unregister(charger->epp_clear_ws); +err_pdata_free: + mutex_destroy(&charger->io_lock); + mutex_destroy(&charger->wpc_en_lock); + mutex_destroy(&charger->fw_lock); +err_i2cfunc_not_support: + kfree(charger); +err_wpc_nomem: +err_parse_dt: + devm_kfree(&client->dev, pdata); + + return ret; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) +static int nu1668_charger_remove(struct i2c_client *client) +#else +static void nu1668_charger_remove(struct i2c_client *client) +#endif +{ + struct mfc_charger_data *charger = i2c_get_clientdata(client); + + alarm_cancel(&charger->phm_alarm); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) + return 0; +#endif +} + +#if defined(CONFIG_PM) +static int mfc_charger_suspend(struct device *dev) +{ + struct mfc_charger_data *charger = dev_get_drvdata(dev); + + pr_info("%s: det(%d) int(%d) vrect_int(%d)\n", __func__, + gpio_get_value(charger->pdata->wpc_det), + gpio_get_value(charger->pdata->wpc_int), + gpio_get_value(charger->pdata->wpc_pdrc)); + + charger->is_suspend = true; + + if (device_may_wakeup(charger->dev)) { + enable_irq_wake(charger->pdata->irq_wpc_int); + enable_irq_wake(charger->pdata->irq_wpc_det); + if (charger->pdata->irq_wpc_pdrc) + enable_irq_wake(charger->pdata->irq_wpc_pdrc); + if (charger->pdata->irq_wpc_pdet_b) + enable_irq_wake(charger->pdata->irq_wpc_pdet_b); + } +#if !IS_ENABLED(CONFIG_ENABLE_WIRELESS_IRQ_IN_SLEEP) + disable_irq(charger->pdata->irq_wpc_int); + disable_irq(charger->pdata->irq_wpc_det); + if (charger->pdata->irq_wpc_pdrc) + disable_irq(charger->pdata->irq_wpc_pdrc); + if (charger->pdata->irq_wpc_pdet_b) + disable_irq(charger->pdata->irq_wpc_pdet_b); +#endif + + return 0; +} + +static int mfc_charger_resume(struct device *dev) +{ + struct mfc_charger_data *charger = dev_get_drvdata(dev); + + pr_info("%s: det(%d) int(%d) vrect_int(%d)\n", __func__, + gpio_get_value(charger->pdata->wpc_det), + gpio_get_value(charger->pdata->wpc_int), + gpio_get_value(charger->pdata->wpc_pdrc)); + + charger->is_suspend = false; + + if (device_may_wakeup(charger->dev)) { + disable_irq_wake(charger->pdata->irq_wpc_int); + disable_irq_wake(charger->pdata->irq_wpc_det); + if (charger->pdata->irq_wpc_pdrc) + disable_irq_wake(charger->pdata->irq_wpc_pdrc); + if (charger->pdata->irq_wpc_pdet_b) + disable_irq_wake(charger->pdata->irq_wpc_pdet_b); + } +#if !IS_ENABLED(CONFIG_ENABLE_WIRELESS_IRQ_IN_SLEEP) + enable_irq(charger->pdata->irq_wpc_int); + enable_irq(charger->pdata->irq_wpc_det); + if (charger->pdata->irq_wpc_pdrc) + enable_irq(charger->pdata->irq_wpc_pdrc); + if (charger->pdata->irq_wpc_pdet_b) + enable_irq(charger->pdata->irq_wpc_pdet_b); +#else + /* Level triggering makes infinite IRQ, Edge triggering is required */ + __pm_stay_awake(charger->wpc_ws); + __pm_stay_awake(charger->wpc_det_ws); + mfc_wpc_irq_thread(0, charger); + if (charger->pdata->irq_wpc_pdrc) { + __pm_stay_awake(charger->wpc_pdrc_ws); + mfc_wpc_pdrc_irq_thread(0, charger); + } + if (charger->pdata->irq_wpc_pdet_b) { + __pm_stay_awake(charger->wpc_pdet_b_ws); + mfc_wpc_pdet_b_irq_thread(0, charger); + } + mfc_wpc_det_irq_thread(0, charger); +#endif + + if (charger->skip_phm_work_in_sleep) + queue_delayed_work(charger->wqueue, &charger->wpc_tx_phm_work, 0); + + return 0; +} +#else +#define mfc_charger_suspend NULL +#define mfc_charger_resume NULL +#endif + +#if defined(CONFIG_WIRELESS_RX_PHM_CTRL) +static void mfc_disable_irq_nosync(int irq) +{ + struct irq_desc *desc; + + if (irq <= 0) + return; + + desc = irq_to_desc(irq); + if (desc->depth == 0) + disable_irq_nosync(irq); +} +#endif + +static void nu1668_charger_shutdown(struct i2c_client *client) +{ + struct mfc_charger_data *charger = i2c_get_clientdata(client); + + is_shutdn = true; + pr_info("%s\n", __func__); + + cancel_delayed_work(&charger->wpc_vout_mode_work); + alarm_cancel(&charger->phm_alarm); + + if (gpio_get_value(charger->pdata->wpc_det)) { + pr_info("%s: forced 5V Vout\n", __func__); + /* Prevent for unexpected FOD when reboot on morphie pad */ + mfc_set_vrect_adjust(charger, MFC_HEADROOM_6); + mfc_set_vout(charger, MFC_VOUT_5V); + mfc_set_pad_hv(charger, false); + } + cancel_delayed_work(&charger->wpc_tx_duty_min_work); + cancel_delayed_work(&charger->wpc_rx_phm_work); + +#if defined(CONFIG_WIRELESS_RX_PHM_CTRL) + if (charger->pdata->cable_type != SEC_BATTERY_CABLE_NONE) { + mfc_disable_irq_nosync(charger->pdata->irq_wpc_pdrc); + mfc_disable_irq_nosync(charger->pdata->irq_wpc_det); + mfc_disable_irq_nosync(charger->pdata->irq_wpc_pdet_b); + + /* reset rx ic and tx pad for phm exit */ + mfc_set_wpc_en(charger, WPC_EN_CHARGING, false); + msleep(1500); + } +#endif +} + +static const struct i2c_device_id nu1668_charger_id_table[] = { + { "nu1668-charger", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, nu1668_charger_id_table); + +#ifdef CONFIG_OF +static const struct of_device_id nu1668_charger_match_table[] = { + { .compatible = "nuvolta,nu1668-charger",}, + {}, +}; +MODULE_DEVICE_TABLE(of, nu1668_charger_match_table); +#else +#define nu1668_charger_match_table NULL +#endif + +const struct dev_pm_ops nu1668_pm = { + SET_SYSTEM_SLEEP_PM_OPS(mfc_charger_suspend, mfc_charger_resume) +}; + +static struct i2c_driver nu1668_charger_driver = { + .driver = { + .name = "nu1668-charger", + .owner = THIS_MODULE, +#if defined(CONFIG_PM) + .pm = &nu1668_pm, +#endif /* CONFIG_PM */ + .of_match_table = nu1668_charger_match_table, + }, + .shutdown = nu1668_charger_shutdown, + .probe = nu1668_charger_probe, + .remove = nu1668_charger_remove, + .id_table = nu1668_charger_id_table, +}; + +static int __init nu1668_charger_init(void) +{ + pr_info("%s\n", __func__); + return i2c_add_driver(&nu1668_charger_driver); +} + +static void __exit nu1668_charger_exit(void) +{ + pr_info("%s\n", __func__); + i2c_del_driver(&nu1668_charger_driver); +} + +module_init(nu1668_charger_init); +module_exit(nu1668_charger_exit); + +MODULE_DESCRIPTION("Samsung NU1668 Charger Driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/battery/wireless/nu1668_charger.dtsi b/drivers/battery/wireless/nu1668_charger.dtsi new file mode 100644 index 000000000000..f2a5cf0112d8 --- /dev/null +++ b/drivers/battery/wireless/nu1668_charger.dtsi @@ -0,0 +1,211 @@ +#include + +&tlmm { + nu_irq_default: nu_irq_default { + GPIO_CONFIG_PUD_DRV(AP,tlmm,84, FUNC_INPUT_WAKEUP, PULL_NONE, DRV_LV1); + }; +}; + +&pm8550_gpios { + nu_det_default: nu_det_default { + GPIO_CONFIG_PUD(PM,pm8550_gpios,9, FUNC_INPUT_WAKEUP, PULL_NONE); + }; +}; + +&tlmm { + nu_en_default: nu_en_default { + GPIO_CONFIG_PUD(AP,tlmm,82, FUNC_OUTPUT_LOW, PULL_NONE); + }; +}; + +&pm8550vs_g_gpios { + nu_pdrc_default: nu_pdrc_default { + GPIO_CONFIG_PUD(PM,pm8550vs_g_gpios,6, FUNC_INPUT_WAKEUP, PULL_NONE); + }; +}; + +#if 1 +&tlmm { + nu_ping_nen_default: nu_ping_nen_default { + GPIO_CONFIG_PUD(AP,tlmm,180, FUNC_OUTPUT_HIGH, PULL_NONE); + }; +}; +#endif + +#if 1 +&tlmm { + nu_pdet_b_default: nu_pdet_b_default { + GPIO_CONFIG_PUD(AP,tlmm,55, FUNC_INPUT_WAKEUP, PULL_NONE); + }; +}; +#endif + +#if 1 +&pm8550vs_g_gpios { + nu_mag_det_default: nu_mag_det_default { + GPIO_CONFIG_PUD(PM,pm8550vs_g_gpios,5, FUNC_OUTPUT_HIGH, PULL_NONE); + }; +}; +#endif + +&qupv3_hub_i2c7 { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + clock-frequency = <100000>; + + nu1668_charger: nu1668-charger@41 { + compatible = "nuvolta,nu1668-charger"; + reg = <0x41>; + status = "okay"; + + pinctrl-names = "default"; + pinctrl-0 = <&nu_irq_default &nu_det_default &nu_en_default &nu_pdrc_default +#if 1 + &nu_ping_nen_default +#endif +#if 1 + &nu_pdet_b_default +#endif +#if 1 + &nu_mag_det_default +#endif + >; + + battery,wpc_int = ; /* MFC_AP_INT */ + battery,wpc_det = ; /* WPC_DET */ +#if 0 + battery,mst_pwr_en = ; /* MST_PWR_EN */ +#endif +#if 1 + battery,wpc_ping_nen = ; /* PING_NEN */ +#endif +#if 1 + battery,wpc_pdet_b = ; /* PDET_B */ +#endif + battery,wpc_en = ; /* WPC_EN */ + battery,wpc_pdrc = ; /* VRECT_INT */ +#if 1 + battery,wpc_mag_det = ; /* MAG_DET */ +#endif + + battery,charger_name = "max77775-charger"; + battery,fuelgauge_name = "max77775-fuelgauge"; + battery,wireless_charger_name = "nu1668-charger"; + battery,wc_cover_rpp = <0x44>; + battery,phone_fod_threshold = <0x3b>; + battery,wireless20_vout_list = ; /* 0xA5 */ + battery,wireless20_vrect_list = ; + battery,wireless20_max_power_list = ; + + battery,buds_fod_ta_thresh = <0x0898>; /* 2200mW */ + battery,wpc_vout_ctrl_full = ; + battery,mis_align_guide; + battery,mis_align_target_vout = <5000>; + battery,enable_qfod_param; + battery,mpp_epp_vout = ; + + fod_list { + count = <1>; + + pad_0x00 { /* DEFAULT */ + bpp { /* DEFAULT OP MODE */ + flag = <(SET_FOD_CC(ADD) | SET_FOD_CV(USE_CC) | SET_FOD_FULL(ADD))>; + cc = <0x28 0x54 0x28 0x54 0x28 0x32 0x28 0x32 0x28 0x32 + 0x28 0x32 0x28 0x32 0x32 0x40 0x32 0x40 0x32 0x40>; + full = <0xFF 0x7F 0xFF 0x7F 0xFF 0x7F 0xFF 0x7F 0xFF 0x7F + 0xFF 0x7F 0xFF 0x7F 0xFF 0x7F 0xFF 0x7F 0xFF 0x7F>; + }; + + ppde { + flag = <(SET_FOD_CC(USE_DEF_OP) | SET_FOD_CV(USE_DEF_OP) | SET_FOD_FULL(USE_DEF_OP))>; + }; + + epp { + flag = <(SET_FOD_CC(USE_DEF_OP) | SET_FOD_CV(USE_DEF_OP) | SET_FOD_FULL(USE_DEF_OP))>; + }; + + mpp { + flag = <(SET_FOD_CC(USE_DEF_OP) | SET_FOD_CV(USE_DEF_OP) | SET_FOD_FULL(USE_DEF_OP))>; + }; + }; + }; + + qfod_list { + number = <1>; + + rx_0x00 { /* DEFAULT */ + sspth_value = <74>; + qbase_value = <1700>; + qfod_th0 = <51>; + qfod_th1 = <116>; + qfod_th2 = <51>; + qfod_th3 = <117>; + qfod_tws_th3 = <600>; + qfod_phone_th4 = <1100>; + }; + }; + }; +}; + +/* /home/dpi/qb5_8814/workspace/P4_1716/android/kernel_platform/kmodule/battery/stable/eureka/wireless/nu1668/nu1668_charger.e3q.dtsi */ +#include + +&nu_pdrc_default { + GPIO_CONFIG_PUD(PM,pm8550vs_g_gpios,6, FUNC_OUTPUT_HIGH, PULL_NONE); + power-source = <1>; /* need to set default MV gpio to LV */ +}; + +&nu_det_default { + power-source = <1>; /* need to set default MV gpio to LV */ +}; + +#if 1 +&nu_mag_det_default { + power-source = <1>; /* need to set default MV gpio to LV */ +}; +#endif + +&nu1668_charger { + battery,pdrc_vrect_clear; + battery,unknown_cmb_ctrl; + battery,default_clamp_volt; + battery,cep_timeout_xac = <900>; + + fod_list { + epp_ref_qf = <0x26>; + epp_ref_rf = <0x6C>; + + bpp_vout = <5500>; + + pad_0x00 { /* DEFAULT */ + bpp { /* DEFAULT OP MODE */ + flag = <(SET_FOD_CC(ADD) | SET_FOD_CV(ADD) | SET_FOD_FULL(ADD))>; + cc = <0x28 0x3C 0x28 0x31 0x3C 0x30 0x3C 0x1D 0x3C 0x1E 0x3C 0x46 0x32 0x6B 0x32 0x7B 0x32 0x40 0x32 0x40>; + cv = <0x28 0x5A 0x28 0x4F 0x3C 0x4E 0x3C 0x3B 0x3C 0x3C 0x3C 0x64 0x32 0x7F 0x32 0x7F 0x32 0x5E 0x32 0x5E>; + full = <0xFF 0x7F 0xFF 0x7F 0xFF 0x7F 0xFF 0x7F 0xFF 0x7F 0xFF 0x7F 0xFF 0x7F 0xFF 0x7F 0xFF 0x7F 0xFF 0x7F>; + }; + + epp { + flag = <(SET_FOD_CC(ADD) | SET_FOD_CV(ADD) | SET_FOD_FULL(USE_DEF_OP))>; + cc = <0x3C 0x64 0x3C 0x61 0x3C 0x61 0x3C 0x50 0x3C 0x50 0x3C 0x48 0x3C 0x48 0x3C 0x42 0x3C 0x42 0x3C 0x42>; + cv = <0x3C 0x7F 0x3C 0x7F 0x3C 0x7F 0x3C 0x6E 0x3C 0x6E 0x3C 0x66 0x3C 0x66 0x3C 0x60 0x3C 0x60 0x3C 0x60>; + }; + }; + }; +}; diff --git a/drivers/battery/wireless/nu1668_charger.h b/drivers/battery/wireless/nu1668_charger.h new file mode 100755 index 000000000000..93af15345f7f --- /dev/null +++ b/drivers/battery/wireless/nu1668_charger.h @@ -0,0 +1,1373 @@ +/* + * nu1668_charger.h + * Samsung NU1668 Charger Header + * + * Copyright (C) 2022 Samsung Electronics, 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 __WIRELESS_CHARGER_NU1668_H +#define __WIRELESS_CHARGER_NU1668_H __FILE__ + +#include +#include +#include +#include +#include +#include +#include "../common/sec_charging_common.h" +#include "nu1668_fod.h" +#include "nu1668_cmfet.h" + +#define MFC_NU1668_DRIVER_VERSION 0x001E + +#define MFC_FW_BIN_VERSION 0x0327 + +#define MFC_FLASH_FW_HEX_PATH "mfc/mfc_fw_flash.bin" +#define MFC_FW_SDCARD_BIN_PATH "wpc_fw_sdcard.bin" + +#define MFC_FLASH_FW_HEX_NU1668_PATH "mfc/mfc_fw_flash_nu1668.bin" + +/* for SPU FW update */ +#define MFC_FW_SPU_BIN_PATH "mfc/mfc_fw_spu_nu1668.bin" + +#define NU1668 1 +#define BIN_FILE_TOTAL_LENGTH 32768 + +#define NU1668_SRAM_BASE_ADDR 0x1200 + +/* REGISTER MAPS */ +/************only for E3************/ +#define MFC_CLOAK_REASON_REG 0x0000 +#define MFC_FULL_MODE_TRANS_TYPE_REG 0x0001 +#define MFC_POWER_LEVEL_SETTING_REG 0x0002 +#define MFC_GP_STATE_REG 0x0020 +#define MFC_NEGO_DONE_POWER_REG 0x0021 +#define MFC_POTENTIAL_LOAD_POWER_REG 0x0022 +#define MFC_NEGOTIABLE_LOAD_POWER_REG 0x0023 +#define MFC_PTX_EXTENDED_ID0_REG 0x002C +#define MFC_PTX_EXTENDED_ID1_REG 0x002D +#define MFC_PTX_EXTENDED_ID2_REG 0x002E +#define MFC_DC_CURR_MOD_BASE_LIGHT_LOAD_REG 0x0003 +#define MFC_DC_CURR_MOD_DEPTH_REG 0x0003 +#define MFC_EXIT_CLOAK_REASON_REG 0x002f +/************only for E3************/ + +#define MFC_CHIP_ID_L_REG 0x00F1// 0x00F1//0x1258 +#define MFC_CHIP_ID_H_REG 0x00F0// 0x00F0//0x1259 +#define MFC_CHIP_REVISION_REG 0x125A +#define MFC_CUSTOMER_ID_REG 0x125B +#define MFC_FW_MAJOR_REV_L_REG 0x125C +#define MFC_FW_MAJOR_REV_H_REG 0x125D +#define MFC_FW_MINOR_REV_L_REG 0x125E +#define MFC_FW_MINOR_REV_H_REG 0x125F +#define MFC_PRMC_ID_L_REG 0x1278 +#define MFC_PRMC_ID_H_REG 0x1279 + +/* RXID BIT[0:47] */ +#define MFC_WPC_RXID_0_REG 0x1288 +#define MFC_WPC_RXID_1_REG 0x1289 +#define MFC_WPC_RXID_2_REG 0x128A +#define MFC_WPC_RXID_3_REG 0x128B +#define MFC_WPC_RXID_4_REG 0x128C +#define MFC_WPC_RXID_5_REG 0x128D + +#define MFC_STATUS_L_REG 0x1274//E3:0x1274; Q5:0x1276 +#define MFC_STATUS_H_REG 0x1275//E3:0x1275; Q5:0x1277 +#define MFC_STATUS_L_REG1 0x1276//E3:0x1276; Q5:null-------- +#define MFC_STATUS_H_REG1 0x1277//E3:0x1277; Q5:null-------- + +#define MFC_INT_A_L_REG 0x1270//E3:0x1270; Q5:0x1274 +#define MFC_INT_A_H_REG 0x1271//E3:0x1271; Q5:0x1275 +#define MFC_INT_A_L_REG1 0x1272//E3:0x1272; Q5:null-------- +#define MFC_INT_A_H_REG1 0x1273//E3:0x1273; Q5:null-------- + +#define MFC_INT_A_ENABLE_L_REG 0x0004//E3:0x0004; Q5:0x1200 +#define MFC_INT_A_ENABLE_H_REG 0x0005//E3:0x0005; Q5:0x1201 +#define MFC_INT_A_ENABLE_L_REG1 0x0006//E3:0x0006; Q5:null-------- +#define MFC_INT_A_ENABLE_H_REG1 0x0007//E3:0x0007; Q5:null-------- + +#define MFC_INT_A_CLEAR_L_REG 0x1200//E3:0x1200; Q5:0x1202 +#define MFC_INT_A_CLEAR_H_REG 0x1201//E3:0x1201; Q5:0x1203 +#define MFC_INT_A_CLEAR_L_REG1 0x1202//E3:0x1202; Q5:null-------- +#define MFC_INT_A_CLEAR_H_REG1 0x1203//E3:0x1203; Q5:null-------- + +//#define MFC_CTRL_STS_REG 0x28 // no used in by Nuvolta + +#define MFC_SYS_OP_MODE_REG 0x1268//E3:0x1268; Q5:0x126B +#define MFC_BATTERY_CHG_STATUS_REG 0x0008//E3:0x0008; Q5:0x1214 +#define MFC_EPT_REG 0x0009//E3:0x0009; Q5:0x1215 /* EPT(End of Power Transfer) cases. PMA has only EOC case */ +#define MFC_ADC_VOUT_L_REG 0x127A +#define MFC_ADC_VOUT_H_REG 0x127B + +#define MFC_VOUT_SET_L_REG 0x000A//E3:0x000A; Q5:0x1216 +//#define MFC_VOUT_SET_H_REG 0x1217 +#define MFC_VRECT_ADJ_REG 0x000B//E3:0x000B; Q5:0x1218 +#define MFC_ADC_VRECT_L_REG 0x127C +#define MFC_ADC_VRECT_H_REG 0x127D + +#if NU1668 +#define MFC_ADC_TX_ISENSE_L_REG 0x1282 +#define MFC_ADC_TX_ISENSE_H_REG 0x1283 +#else // cps case +#define MFC_ADC_IRECT_L_REG 0x42 +#define MFC_ADC_IRECT_H_REG 0x43 +#endif + +#define MFC_TX_IUNO_LIMIT_L_REG 0x123C//E3:0x123C; Q5:0x1238 +#define MFC_TX_IUNO_LIMIT_H_REG 0x123D//E3:0x123D; Q5:0x1239 +#define MFC_ADC_IOUT_L_REG 0x127E +#define MFC_ADC_IOUT_H_REG 0x127F +#define MFC_ADC_DIE_TEMP_L_REG 0x1280 /* 8 LSB field is used, Celsius */ +#define MFC_ADC_DIE_TEMP_H_REG 0x1281 /* only 4 MSB[3:0] field is used, Celsius */ +#define MFC_TRX_OP_FREQ_L_REG 0x1284 /* kHZ */ +#define MFC_TRX_OP_FREQ_H_REG 0x1285 /* kHZ */ +#define MFC_RX_PING_FREQ_L_REG 0x1286 /* kHZ */ +#define MFC_RX_PING_FREQ_H_REG 0x1287 /* kHZ */ +#define MFC_ILIM_SET_REG 0x000C//E3:0x000C; Q5:0x1219 /* ILim = value * 0.1(A) + 0.1(A). 1.3A = 12*0.1 + 0.1 */ +//#define MFC_ILIM_ADJ_REG 0x121A // Unused + +#define MFC_AP2MFC_CMD_L_REG 0x1204 +#define MFC_AP2MFC_CMD_H_REG 0x1205 + +/********************************************************************************/ +/* Below register are functionally depends on the operation mode(TX or RX mode) */ +/* RX mode */ +#define MFC_WPC_PCKT_HEADER_REG 0x1208 +#define MFC_WPC_RX_DATA_COM_REG 0x1209 /* WPC Rx to Tx COMMAND */ +#define MFC_WPC_RX_DATA_VALUE0_REG 0x120A +#define MFC_WPC_RX_DATA_VALUE1_REG 0x120B +#define MFC_WPC_RX_DATA_VALUE2_REG 0x120C +#define MFC_WPC_RX_DATA_VALUE3_REG 0x120D +#define MFC_WPC_RX_DATA_VALUE4_REG 0x120E//only for E3--------- +#define MFC_WPC_RX_DATA_VALUE5_REG 0x120F//only for E3--------- +#define MFC_WPC_RX_DATA_VALUE6_REG 0x1210//only for E3--------- +#define MFC_WPC_RX_DATA_VALUE7_REG 0x1211//only for E3--------- + +/* TX mode */ +#define MFC_WPC_TX_DATA_COM_REG 0x1208 /* WPC Tx to Rx COMMAND */ +#define MFC_WPC_TX_DATA_VALUE0_REG 0x1209 +#define MFC_WPC_TX_DATA_VALUE1_REG 0x120A +#define MFC_WPC_TX_DATA_VALUE2_REG 0x120B + +/* Common */ +#define MFC_WPC_TRX_DATA2_COM_REG 0x1214//E3:0x1214; Q5:0x1210 +#define MFC_WPC_TRX_DATA2_VALUE0_REG 0x1215//E3:0x1215; Q5:0x1211 +#define MFC_WPC_TRX_DATA2_VALUE1_REG 0x1216//E3:0x1216; Q5:0x1212 +#define MFC_WPC_TRX_DATA2_VALUE2_REG 0x1217//only for E3--------- +#define MFC_WPC_TRX_DATA2_VALUE3_REG 0x1218//only for E3--------- +#define MFC_WPC_TRX_DATA2_VALUE4_REG 0x1219//only for E3--------- +#define MFC_WPC_TRX_DATA2_VALUE5_REG 0x121A//only for E3--------- +#define MFC_WPC_TRX_DATA2_VALUE6_REG 0x121B//only for E3--------- +#define MFC_WPC_TRX_DATA2_VALUE7_REG 0x121C//only for E3--------- +/********************************************************************************/ + +#define MFC_ADT_TIMEOUT_PKT_REG 0x1220//E3:0x1220; Q5:0x121B +#define MFC_ADT_TIMEOUT_STR_REG 0x1221//E3:0x1221; Q5:0x121C + +#define MFC_TX_IUNO_HYS_REG 0x123E//E3:0x123E; Q5:0x123A +#define MFC_TX_IUNO_OFFSET_L_REG 0x1240//E3:0x1240; Q5:0x123C +#define MFC_TX_IUNO_OFFSET_H_REG 0x1241//E3:0x1241; Q5:0x123D + +#define MFC_TX_OC_FOD1_LIMIT_L_REG 0x124F//E3:0x124F; Q5:0x124C +//#define MFC_TX_OC_FOD1_LIMIT_H_REG 0x124D +#define MFC_TX_OC_FOD2_LIMIT_L_REG 0x1250//E3:0x1250; Q5:0x124E +//#define MFC_TX_OC_FOD2_LIMIT_H_REG 0x124F + +#define MFC_STARTUP_EPT_COUNTER 0x128F + +#define MFC_TX_DUTY_CYCLE 0x128E /* default 0x80(50%) */ + +/* TX Max Operating Frequency = 96 MHz/value, default is 148kHz (96MHz/0x288=148KHz) */ +#define MFC_TX_MAX_OP_FREQ_L_REG 0x1248//E3:0x1248; Q5:0x1244 /* default 0x88 */ +#define MFC_TX_MAX_OP_FREQ_H_REG 0x1249//E3:0x1249; Q5:0x1245 /* default 0x02 */ +/* TX Min Operating Frequency = 96 MHz/value, default is 113kHz (96MHz/0x351=113KHz) */ +#define MFC_TX_MIN_OP_FREQ_L_REG 0x124A//E3:0x124A; Q5:0x1246 /* default 0x51 */ +#define MFC_TX_MIN_OP_FREQ_H_REG 0x124B//E3:0x124B; Q5:0x1247 /* default 0x03 */ +/* TX Digital Ping Frequency = 96 MHz/value, default is 145kHz (96MHz/0x296=145KHz) */ +#define MFC_TX_PING_FREQ_L_REG 0x124C//E3:0x124C; Q5:0x1248 /* default 0x96 */ +#define MFC_TX_PING_FREQ_H_REG 0x124D//E3:0x124D; Q5:0x1249 /* default 0x02 */ +/* TX Mode Minimum Duty Setting Register, Min_Duty, default is 20%. value/256 * 100(0x33=20) */ +#define MFC_TX_MIN_DUTY_SETTING_REG 0x124E//E3:0x124E; Q5:0x124A /* default 0x33 */ + +#define MFC_INVERTER_CTRL_REG 0x124B // not implemented. P2P HALF : 0x4B[7] +#define MFC_CMFET_CTRL_REG 0x1223//E3:0x1223; Q5:0x121E + +/* RX Mode Communication Modulation FET Ctrl */ +#define MFC_MST_MODE_SEL_REG 0x1255 +#define MFC_RX_OV_CLAMP_REG 0x1224//E3:0x1224; Q5:0x121F +//#define MFC_RX_COMM_MOD_FET_REG 0x1220 // unused in a code. Not used in Nuvolta + +#define MFC_RECTMODE_REG 0x1223 +#define MFC_START_EPT_COUNTER_REG 0x128F // In a code, MFC_START_EPT_COUNTER is used. need to check. +//#define MFC_CTRL_MODE_REG 0x6E // this is not used in a code. Need to check the operation fo this register from Samsung. +#define MFC_TX_FOD_CTRL_REG 0x1245//E3:0x1245; Q5:0x1241 // this ? +#define MFC_RC_PHM_PING_PERIOD_REG 0x1226//E3:0x1226; Q5:0x1221 //in RX mode. Not Implemented. + +#define MIN_DUTY_SETTING_20_DATA 20 +#define MIN_DUTY_SETTING_30_DATA 30 +#define MIN_DUTY_SETTING_50_DATA 50 + +#define MFC_WPC_FOD_0A_REG 0x1228//E3:0x1228; Q5:0x1224 +#define MFC_WPC_FOD_0B_REG 0x1229//E3:0x1229; Q5:0x1225 +#define MFC_WPC_FOD_1A_REG 0x122A//E3:0x122A; Q5:0x1226 +#define MFC_WPC_FOD_1B_REG 0x122B//E3:0x122B; Q5:0x1227 +#define MFC_WPC_FOD_2A_REG 0x122C//E3:0x122C; Q5:0x1228 +#define MFC_WPC_FOD_2B_REG 0x122D//E3:0x122D; Q5:0x1229 +#define MFC_WPC_FOD_3A_REG 0x122E//E3:0x122E; Q5:0x122A +#define MFC_WPC_FOD_3B_REG 0x122F//E3:0x122F; Q5:0x122B +#define MFC_WPC_FOD_4A_REG 0x1230//E3:0x1230; Q5:0x122C +#define MFC_WPC_FOD_4B_REG 0x1231//E3:0x1231; Q5:0x122D +#define MFC_WPC_FOD_5A_REG 0x1232//E3:0x1232; Q5:0x122E +#define MFC_WPC_FOD_5B_REG 0x1233//E3:0x1233; Q5:0x122F +#define MFC_WPC_FOD_6A_REG 0x1234//E3:0x1234; Q5:0x1230 +#define MFC_WPC_FOD_6B_REG 0x1235//E3:0x1235; Q5:0x1231 +#define MFC_WPC_FOD_7A_REG 0x1236//E3:0x1236; Q5:0x1232 +#define MFC_WPC_FOD_7B_REG 0x1237//E3:0x1237; Q5:0x1233 +#define MFC_WPC_FOD_8A_REG 0x1238//E3:0x1238; Q5:0x1234 +#define MFC_WPC_FOD_8B_REG 0x1239//E3:0x1239; Q5:0x1235 +#define MFC_WPC_FOD_9A_REG 0x123A//E3:0x123A; Q5:0x1236 +#define MFC_WPC_FOD_9B_REG 0x123B//E3:0x123B; Q5:0x1237 + +//#define MFC_WPC_PARA_MODE_REG 0x8C // not used in a code. Need to check the operation of this regiser from Samsung + +// used in a code. But, Nuvolta doesn't have these registers. Need to check from Samsung +#if 0 +#define MFC_WPC_FWC_FOD_0A_REG 0x100 +#define MFC_WPC_FWC_FOD_0B_REG 0x101 +#define MFC_WPC_FWC_FOD_1A_REG 0x102 +#define MFC_WPC_FWC_FOD_1B_REG 0x103 +#define MFC_WPC_FWC_FOD_2A_REG 0x104 +#define MFC_WPC_FWC_FOD_2B_REG 0x105 +#define MFC_WPC_FWC_FOD_3A_REG 0x106 +#define MFC_WPC_FWC_FOD_3B_REG 0x107 +#define MFC_WPC_FWC_FOD_4A_REG 0x108 +#define MFC_WPC_FWC_FOD_4B_REG 0x109 +#define MFC_WPC_FWC_FOD_5A_REG 0x10A +#define MFC_WPC_FWC_FOD_5B_REG 0x10B +#define MFC_WPC_FWC_FOD_6A_REG 0x10C +#define MFC_WPC_FWC_FOD_6B_REG 0x10D +#define MFC_WPC_FWC_FOD_7A_REG 0x10E +#define MFC_WPC_FWC_FOD_7B_REG 0x10F +#define MFC_WPC_FWC_FOD_8A_REG 0x100 +#define MFC_WPC_FWC_FOD_8B_REG 0x101 +#define MFC_WPC_FWC_FOD_9A_REG 0x102 +#define MFC_WPC_FWC_FOD_9B_REG 0x103 + +#define MFC_PMA_FOD_0A_REG 0x84 +#define MFC_PMA_FOD_0B_REG 0x85 +#define MFC_PMA_FOD_1A_REG 0x86 +#define MFC_PMA_FOD_1B_REG 0x87 +#define MFC_PMA_FOD_2A_REG 0x88 +#define MFC_PMA_FOD_2B_REG 0x89 +#define MFC_PMA_FOD_3A_REG 0x8A +#define MFC_PMA_FOD_3B_REG 0x8B +#endif +// used in a code. But, Nuvolta doesn't have these registers. Need to check from Samsung + +#define MFC_ADT_ERROR_CODE_REG 0x1297 + +#define MFC_TX_FOD_GAIN_REG 0x1253//E3:0x1253; Q5:0x1254 +#define MFC_TX_FOD_OFFSET_L_REG 0x1251//E3:0x1251; Q5:0x1250 +//#define MFC_TX_FOD_OFFSET_H_REG 0x1251 +#define MFC_TX_FOD_THRESH1_L_REG 0x1252//E3:0x1252; Q5:0x1252 +//#define MFC_TX_FOD_THRESH1_H_REG 0x1253 + +// for FOD threshold setting, there are two functions. +// mfc_set_tx_fod_thresh1() and mfc_set_tx_fod_ta_thresh(). buds uses two functions. phone uses mfc_set_tx_fod_thresh1(). +// Nuvolta doesn't have MFC_TX_FOD_TA_THRESH_L/H_REG. Need to check Samsung. +//#define MFC_TX_FOD_TA_THRESH_L_REG 0x98 +//#define MFC_TX_FOD_TA_THRESH_H_REG 0x99 + + +// MFX(Reference) or MFC(Nuvolta). need to check Samsung. +#define MFC_TX_ID_VALUE_L_REG 0x1246//E3:0x1246; Q5:0x1242 +#define MFC_TX_ID_VALUE_H_REG 0x1247//E3:0x1247; Q5:0x1243 + +#define MFC_DEMOD1_REG 0x1242//E3:0x1242; Q5:0x123E +#define MFC_DEMOD2_REG 0x1243//E3:0x1243; Q5:0x123F + +#define MFC_PWR_HOLD_INTERVAL_REG 0x1244//E3:0x1244; Q5:0x1240 +// Nuvolta doesn't have this register. Need to check Samsung. +// used in mfc_set_tx_conflict_current(). +//#define MFC_TX_CONFLICT_CURRENT_REG 0xA0 +//#define MFC_RECT_MODE_AP_CTRL 0xA2 + +#define MFC_CEP_TIME_OUT_REG 0x123F//E3:0x123F; Q5:0x123B + +// check if DATA is ok or DATE is ok from Samsung. +#define MFC_FW_DATE_CODE_0 0x1260 +#define MFC_FW_DATE_CODE_1 0x1261 +#define MFC_FW_DATE_CODE_2 0x1262 +#define MFC_FW_DATE_CODE_3 0x1263 +#define MFC_FW_DATE_CODE_4 0x1264 +#define MFC_FW_DATE_CODE_5 0x1265 +#define MFC_FW_DATE_CODE_6 0x1266 +#define MFC_FW_DATE_CODE_7 0x1267 +//#define MFC_FW_DATE_CODE_8 0x1268 +//#define MFC_FW_DATE_CODE_9 0x1269 +//#define MFC_FW_DATE_CODE_A 0x126A + +/* Timer code contains ASCII value. (ex. 31 means '1', 3A means ':') */ +#define MFC_FW_TIMER_CODE_0 0x1269//E3:0x1269; Q5:0x126C +#define MFC_FW_TIMER_CODE_1 0x126A//E3:0x126A; Q5:0x126D +#define MFC_FW_TIMER_CODE_2 0x126B//E3:0x126B; Q5:0x126E +#define MFC_FW_TIMER_CODE_3 0x126C//E3:0x126C; Q5:0x126F +#define MFC_FW_TIMER_CODE_4 0x126D//E3:0x126D; Q5:0x1270 +#define MFC_FW_TIMER_CODE_5 0x126E//E3:0x126E; Q5:0x1271 +//#define MFC_FW_TIMER_CODE_6 0x1272 +//#define MFC_FW_TIMER_CODE_7 0x1273 + +#define MFC_TX_WPC_AUTH_SUPPORT_REG 0x126F + +#define MFC_RX_PWR_L_REG 0x1290 +#define MFC_RX_PWR_H_REG 0x1291 + + +//#define MFC_TX_FOD_THRESH2_REG 0xE3 +//#define MFC_TX_DUTY_CYCLE_REG 0xE6 + + +// Not used in code. Need to check from Samsung if the function will be created for the usage of this register. +//#define MFC_TX_PWR_L_REG 0xEC +//#define MFC_TX_PWR_H_REG 0xED +#define MFC_TX_PWR_L_REG 0x1292 +#define MFC_TX_PWR_H_REG 0x1293 + +#define MFC_RPP_SCALE_COEF_REG 0x1227//E3:0x1227; Q5:0x1222 + + +//#define MFC_ACTIVE_LOAD_CONTROL_REG 0xF1 +/* Parameter 1: Major and Minor Version */ +#define MFC_TX_RXID1_READ_REG 0x1294 +/* Parameter 2~3: Manufacturer Code */ +#define MFC_TX_RXID2_READ_REG 0x1295 +#define MFC_TX_RXID3_READ_REG 0x1296 + +/* Parameter QFOD */ +#define MFC_TX_QAIR_L_REG 0x12A4 +#define MFC_TX_QAIR_H_REG 0x12A5 +#define MFC_TX_Q_L_REG 0x12A6 +#define MFC_TX_Q_H_REG 0x12A7 + +#define MFC_TX_Q_SSPTH_REG 0x000D//E3:0x000D; Q5:0x121A +#define MFC_TX_Q_BASE_L_REG 0x1254//E3:0x1254; Q5:0x129C +//#define MFC_TX_Q_BASE_H_REG 0x129D +#define MFC_TX_COEFF_QFOD_TH0_REG 0x1222//E3:0x1222; Q5:0x121D +#define MFC_TX_COEFF_QFOD_TH1_REG 0x1225//E3:0x1225; Q5:0x1220 +#define MFC_TX_COEFF_QFOD_TH2_REG 0x1256 +#define MFC_TX_COEFF_QFOD_TH3_REG 0x1257 +#define MFC_TX_COEFF_QFOD_TWS_TH3_L_REG 0x129D//E3:0x129D; Q5:0x129E +//#define MFC_TX_COEFF_QFOD_TWS_TH3_H_REG 0x129F +#define MFC_TX_COEFF_QFOD_PHONE_TH4_L_REG 0x129C//E3:0x129C; Q5:0x12A0 +//#define MFC_TX_COEFF_QFOD_PHONE_TH4_H_REG 0x12A1 +#define MFC_EPP_REF_FREQ_VALUE_REG 0x129E//NEW + +#define MFC_MPP_FOD_QF_REG MFC_TX_Q_BASE_L_REG +#define MFC_MPP_FOD_RF_REG MFC_EPP_REF_FREQ_VALUE_REG + +/* Parameter 4~10: Basic Device Identifier */ +//#define MFC_TX_RXID4_READ_REG 0xF5 +//#define MFC_TX_RXID5_READ_REG 0xF6 +//#define MFC_TX_RXID6_READ_REG 0xF7 +//#define MFC_TX_RXID7_READ_REG 0xF8 +//#define MFC_TX_RXID8_READ_REG 0xF9 +//#define MFC_TX_RXID9_READ_REG 0xFA +//#define MFC_TX_RXID10_READ_REG 0xFB + + +#define SS_ID 0x42 +#define SS_CODE 0x64 + +/* Cloak reason Register, CLOAK_REASON (0x0000) */ +#define MFC_TRX_MPP_CLOAK_GENERIC 0x0 +#define MFC_TRX_MPP_CLOAK_FORCED 0x1 +#define MFC_TRX_MPP_CLOAK_THERMALLY_CONSTRAINED 0x2 +#define MFC_TRX_MPP_CLOAK_INSUFFICIENT_POWER 0x3 +#define MFC_TRX_MPP_CLOAK_COEX_MITIGATION 0x4 +#define MFC_TRX_MPP_CLOAK_END_OF_CHARGE 0x5 +#define MFC_TRX_MPP_CLOAK_PTX_INITIATED 0x6 + +/* nego power level Register, POWER_LEVEL_SETTING (0x0002) */ +#define MFC_RX_MPP_NEGO_POWER_10W 10 +#define MFC_RX_MPP_NEGO_POWER_15W 15 + +//#define TX_ID_CHECK_CNT 10 + +/* Target Vrect is ReadOnly register, and updated by every 10ms + * Its default value is 0x1A90(6800mV). + * Target_Vrect (Iout,Vout) = {Vout + 0.05} + { Vrect(Iout,5V)-Vrect(1A,5V) } * 5/9 + */ + +// not used in code. Nuvolta doesn't have this register. Need to check from Samsung. +#if 0 +#define MFC_TARGET_VRECT_L_REG 0x0164 /* default 0x90 */ +#define MFC_TARGET_VRECT_H_REG 0x0165 /* default 0x1A */ + +#define MFC_RX_CEP_PACKET_COUNTER0 0x029C +#define MFC_RX_CEP_PACKET_COUNTER1 0x029D +#define MFC_RX_CEP_PACKET_COUNTER2 0x029E +#define MFC_RX_CEP_PACKET_COUNTER3 0x029F +#define MFC_RX_RPP_PACKET_COUNTER0 0x02A0 +#define MFC_RX_RPP_PACKET_COUNTER1 0x02A1 +#define MFC_RX_RPP_PACKET_COUNTER2 0x02A2 +#define MFC_RX_RPP_PACKET_COUNTER3 0x02A3 +#define MFC_RX_CSP_PACKET_COUNTER0 0x02A4 +#define MFC_RX_CSP_PACKET_COUNTER1 0x02A5 +#define MFC_RX_CSP_PACKET_COUNTER2 0x02A6 +#define MFC_RX_CSP_PACKET_COUNTER3 0x02A7 +#define MFC_RX_PPP_PACKET_COUNTER0 0x02A8 +#define MFC_RX_PPP_PACKET_COUNTER1 0x02A9 +#define MFC_RX_PPP_PACKET_COUNTER2 0x02AA +#define MFC_RX_PPP_PACKET_COUNTER3 0x02AB +#endif + +/* ADT Buffer Registers, (0x12A8 ~ 0x12FF) */ +#define MFC_ADT_BUFFER_ADT_TYPE_REG 0x12A8 +#define MFC_ADT_BUFFER_ADT_MSG_SIZE_REG 0x12A9 +#define MFC_ADT_BUFFER_ADT_PARAM_REG 0x12AA +#define MFC_ADT_BUFFER_ADT_PARAM_MAX_REG 0x12FF + +/* System Operating Mode Register, Sys_Op_Mode (0x126B) */ +#define PAD_RX_MODE_AC_MISSING 0 +#define PAD_RX_MODE_WPC_BPP 1 +#define PAD_RX_MODE_WPC_EPP 2 +#define PAD_RX_MODE_WPC_MPP_RESTRICT 3 +#define PAD_RX_MODE_WPC_MPP_FULL 4 +#define PAD_RX_MODE_WPC_MPP_CLOAK 5 +#define PAD_RX_MODE_WPC_MPP_NEGO 6 +#define PAD_RX_MODE_WPC_EPP_NEGO 7 + +#define PAD_TX_MODE_BACK_POWER_MISSING 0 +#define PAD_TX_MODE_TX_POWERLOSS_FOD 2 +#define PAD_TX_MODE_TX_PINGOC_FOD 3 +#define PAD_TX_MODE_DCR_MST_ON 4 +#define PAD_TX_MODE_PCR_MST_ON 5 +#define PAD_TX_MODE_TX_MODE_ON 8 +#define PAD_TX_MODE_TX_CONFLICT 9 +#define PAD_TX_MODE_TX_PH_MODE 10 +#define PAD_TX_MODE_TX_QTH0_FOD 11 +#define PAD_TX_MODE_TX_QTH2_FOD 12 +#define PAD_TX_MODE_TX_WATCH_ITH1_ITH2_FOD 13 +#define PAD_TX_MODE_TX_TWS_ITH3_FOD 14 +#define PAD_TX_MODE_TX_PHONE_ITH4_FOD 15 + +// below is used in the reference code. Need to change .c with the above code after review the function. +#define PAD_MODE_MISSING 0 +#define PAD_MODE_WPC_BASIC 1 +#define PAD_MODE_WPC_ADV 2 +#define PAD_MODE_PMA_SR1 3 +#define PAD_MODE_PMA_SR1E 4 + +#define PAD_MODE_UNKNOWN 5 // this is not used in the code + +#if 0 +/* MFC_RX_DATA_COM_REG (0x51) : RX Data Command VALUE of 0x19 PPP Heaader */ +#define WPC_COM_CLEAR_PACKET_COUNTING 0x01 +#define WPC_COM_START_PACKET_COUNTING 0x02 +#define WPC_COM_DISABLE_PACKET_COUNTING 0x03 +#endif + +/* RX Data Value1 Register (Data Sending), RX_Data_VALUE1_Out (0x51) : Function and Description : Reference Code */ +/* RX Data Command MFC_WPC_RX_DATA_COM_REG */ +#define WPC_COM_UNKNOWN 0x00 +#define WPC_COM_TX_ID 0x01 +#define WPC_COM_CHG_STATUS 0x05 +#define WPC_COM_AFC_SET 0x06 +#define WPC_COM_AFC_DEBOUNCE 0x07 /* Data Values [ 0~1000mV : 0x0000~0x03E8 ], 2 bytes*/ +#define WPC_COM_SID_TAG 0x08 +#define WPC_COM_SID_TOKEN 0x09 +#define WPC_COM_TX_STANDBY 0x0A +#define WPC_COM_LED_CONTROL 0x0B /* Data Value LED Enable(0x00), LED Disable(0xFF) */ +#define WPC_COM_REQ_AFC_TX 0x0C /* Data Value (0x00) */ +#define WPC_COM_COOLING_CTRL 0x0D /* Data Value ON(0x00), OFF(0xFF) */ +#define WPC_COM_RX_ID 0x0E /* Received RX ID */ +#define WPC_COM_CHG_LEVEL 0x0F /* Battery level */ +#define WPC_COM_ENTER_PHM 0x18 /* GEAR entered PHM */ +#define WPC_COM_DISABLE_TX 0x19 /* Turn off UNO of TX, OFF(0xFF) */ +#define WPC_COM_PAD_LED 0x20 /* PAD LED */ +#define WPC_COM_REQ_PWR_BUDG 0x21 +#define WPC_COM_OP_FREQ_SET 0xD1 +#define WPC_COM_WDT_ERR 0xE7 /* Data Value WDT Error */ + +/* RX Data Value 2~5 Register (Data Sending), RX_Data_Value2_5_Out : Function and Description */ +#define RX_DATA_VAL2_5V 0x05 +#define RX_DATA_VAL2_10V 0x2C +#define RX_DATA_VAL2_12V 0x4B +#define RX_DATA_VAL2_12_5V 0x69 +#define RX_DATA_VAL2_20V 0x9B +#define RX_DATA_VAL2_TA_CONNECT_DURING_WC 0x55 +#define RX_DATA_VAL2_MISALIGN 0xFF +#define RX_DATA_VAL2_ENABLE 0x01 + +#define RX_DATA_VAL2_RXID_ACC_BUDS 0x70 +#define RX_DATA_VAL2_RXID_ACC_BUDS_MAX 0x8F + +/* MFC_TX_DATA_COM_REG (0x58) : TX Command */ +#define WPC_TX_COM_UNKNOWN 0x00 +#define WPC_TX_COM_TX_ID 0x01 +#define WPC_TX_COM_AFC_SET 0x02 +#define WPC_TX_COM_ACK 0x03 +#define WPC_TX_COM_NAK 0x04 +#define WPC_TX_COM_CHG_ERR 0x05 +#define WPC_TX_COM_WPS 0x07 +#define WPC_TX_COM_RX_POWER 0x0A +#define WPC_TX_COM_TX_PWR_BUDG 0x0C + +/* value of WPC_TX_COM_AFC_SET(0x02) */ +#define TX_AFC_SET_5V 0x00 +#define TX_AFC_SET_10V 0x01 +#define TX_AFC_SET_12V 0x02 +#define TX_AFC_SET_18V 0x03 +#define TX_AFC_SET_19V 0x04 +#define TX_AFC_SET_20V 0x05 +#define TX_AFC_SET_24V 0x06 + +/* value of WPC_TX_COM_TX_ID(0x01) */ +#define TX_ID_UNKNOWN 0x00 +#define TX_ID_SNGL_PORT_START 0x01 +#define TX_ID_VEHICLE_PAD 0x11 +#define TX_ID_PG950_D_PAD 0x14 +#define TX_ID_P1100_PAD 0x16 +#define TX_ID_P1300_PAD 0x17 +#define TX_ID_P2400_PAD_NOAUTH 0x18 +#define TX_ID_SNGL_PORT_END 0x1F +#define TX_ID_MULTI_PORT_START 0x20 +#define TX_ID_P4300_PAD 0x25 +#define TX_ID_P5400_PAD_NOAUTH 0x27 +#define TX_ID_MULTI_PORT_END 0x2F +#define TX_ID_STAND_TYPE_START 0x30 +#define TX_ID_PG950_S_PAD 0x31 +#define TX_ID_STAR_STAND 0x32 +#define TX_ID_N3300_V_PAD 0x35 +#define TX_ID_N3300_H_PAD 0xF2 +#define TX_ID_STAND_TYPE_END 0x3F +#define TX_ID_BATT_PACK_TA 0x41 /* 0x40 ~ 0x41 is N/C*/ +#define TX_ID_BATT_PACK_U1200 0x42 +#define TX_ID_BATT_PACK_U3300 0x43 +#define TX_ID_BATT_PACK_END 0x4F /* reserved 0x40 ~ 0x4F for wireless battery pack */ +#define TX_ID_UNO_TX 0x72 +#define TX_ID_UNO_TX_B0 0x80 +#define TX_ID_UNO_TX_B1 0x81 +#define TX_ID_UNO_TX_B2 0x82 +#define TX_ID_UNO_TX_MAX 0x9F + +#define TX_ID_AUTH_PAD 0xA0 +#define TX_ID_P5200_PAD 0xA0 +#define TX_ID_N5200_V_PAD 0xA1 +#define TX_ID_N5200_H_PAD 0xA2 +#define TX_ID_P2400_PAD 0xA3 +#define TX_ID_P5400_PAD 0xA4 +#define TX_ID_AUTH_PAD_ACLASS_END 0xAF +#define TX_ID_AUTH_PAD_END 0xBF /* reserved 0xA1 ~ 0xBF for auth pad */ +#define TX_ID_JIG_PAD 0xED /* for factory */ +#define TX_ID_FG_PAD 0xEF /* Galaxy Friends */ +#define TX_ID_NON_AUTH_PAD 0xF0 +#define TX_ID_NON_AUTH_PAD_END 0xFF + +/* value of WPC_TX_COM_CHG_ERR(0x05) */ +#define TX_CHG_ERR_OTP 0x12 +#define TX_CHG_ERR_OCP 0x13 +#define TX_CHG_ERR_DARKZONE 0x14 +#define TX_CHG_ERR_FOD (0x20 ... 0x27) + +/* value of WPC_TX_COM_WPS 0x07) */ +#define WPS_AICL_RESET 0x01 + +/* value of WPC_TX_COM_RX_POWER(0x0A) */ +#define TX_RX_POWER_0W 0x0 +#define TX_RX_POWER_3W 0x1E +#define TX_RX_POWER_5W 0x32 +#define TX_RX_POWER_6W 0x3C +#define TX_RX_POWER_6_5W 0x41 +#define TX_RX_POWER_7_5W 0x4B +#define TX_RX_POWER_8W 0x50 +#define TX_RX_POWER_10W 0x64 +#define TX_RX_POWER_12W 0x78 +#define TX_RX_POWER_15W 0x96 +#define TX_RX_POWER_17_5W 0xAF +#define TX_RX_POWER_20W 0xC8 + +#define MFC_NUM_FOD_REG 20 + +/* BIT DEFINE of Command Register, COM_L(0x1204) */ // Done. Nu1668 +#define MFC_CMD_TOGGLE_PHM_SHIFT 7 +#define MFC_CMD_RESERVED6_SHIFT 6 +#define MFC_CMD_CLEAR_INT_SHIFT 5 +#define MFC_CMD_SEND_CHG_STS_SHIFT 4 +#define MFC_CMD_SEND_EOP_SHIFT 3 +#define MFC_CMD_MCU_RESET_SHIFT 2 +#define MFC_CMD_TOGGLE_LDO_SHIFT 1 +#define MFC_CMD_SEND_TRX_DATA_SHIFT 0 +#define MFC_CMD_TOGGLE_PHM_MASK (1 << MFC_CMD_TOGGLE_PHM_SHIFT) +#define MFC_CMD_RESERVED6_MASK (1 << MFC_CMD_RESERVED6_SHIFT) +#define MFC_CMD_CLEAR_INT_MASK (1 << MFC_CMD_CLEAR_INT_SHIFT) +#define MFC_CMD_SEND_CHG_STS_MASK (1 << MFC_CMD_SEND_CHG_STS_SHIFT) /* MFC MCU sends ChgStatus packet to TX */ +#define MFC_CMD_SEND_EOP_MASK (1 << MFC_CMD_SEND_EOP_SHIFT) +#define MFC_CMD_MCU_RESET_MASK (1 << MFC_CMD_MCU_RESET_SHIFT) +#define MFC_CMD_TOGGLE_LDO_MASK (1 << MFC_CMD_TOGGLE_LDO_SHIFT) +#define MFC_CMD_SEND_TRX_DATA_MASK (1 << MFC_CMD_SEND_TRX_DATA_SHIFT) + +/* Command Register, COM_H(0x1205) */ // Done. Nu1668 +#define MFC_CMD2_MPP_EXIT_CLOAK_SHIFT 4 +#define MFC_CMD2_MPP_EXIT_CLOAK_MASK (1 << MFC_CMD2_MPP_EXIT_CLOAK_SHIFT) +#define MFC_CMD2_MPP_ENTER_CLOAK_SHIFT 3 +#define MFC_CMD2_MPP_ENTER_CLOAK_MASK (1 << MFC_CMD2_MPP_ENTER_CLOAK_SHIFT) +#define MFC_CMD2_SEND_ADT_SHIFT 1 +#define MFC_CMD2_SEND_ADT_MASK (1 << MFC_CMD2_SEND_ADT_SHIFT) + +#if 0 +#define MFC_CMD2_WP_ON_SHIFT 0 +#define MFC_CMD2_WP_ON_MASK (1 << MFC_CMD2_WP_ON_SHIFT) + +#define MFC_CMD2_ADT_SENT_SHIFT 1 +#define MFC_CMD2_ADT_SENT_MASK (1 << MFC_CMD2_WP_ON_SHIFT) +#endif + +/* Chip Revision Register, Chip_Rev (0x125A) */ +#define MFC_CHIP_REVISION_MASK 0xFF +//#define MFC_CHIP_FONT_MASK 0x0f + + +/* BIT DEFINE of Status Registers, Status_L (0x20), Status_H (0x21) */ +#define MFC_STAT_L_STAT_VOUT_SHIFT 7 +#define MFC_STAT_L_STAT_VRECT_SHIFT 6 +#define MFC_STAT_L_OP_MODE_SHIFT 5 +#define MFC_STAT_L_OVER_VOL_SHIFT 4 +#define MFC_STAT_L_OVER_CURR_SHIFT 3 +#define MFC_STAT_L_OVER_TEMP_SHIFT 2 +#define MFC_STAT_L_TXCONFLICT_SHIFT 1 +#define MFC_STAT_L_ADT_ERROR_SHIFT 0 +#define MFC_STAT_L_STAT_VOUT_MASK (1 << MFC_STAT_L_STAT_VOUT_SHIFT) +#define MFC_STAT_L_STAT_VRECT_MASK (1 << MFC_STAT_L_STAT_VRECT_SHIFT) +#define MFC_STAT_L_OP_MODE_MASK (1 << MFC_STAT_L_OP_MODE_SHIFT) +#define MFC_STAT_L_OVER_VOL_MASK (1 << MFC_STAT_L_OVER_VOL_SHIFT) +#define MFC_STAT_L_OVER_CURR_MASK (1 << MFC_STAT_L_OVER_CURR_SHIFT) +#define MFC_STAT_L_OVER_TEMP_MASK (1 << MFC_STAT_L_OVER_TEMP_SHIFT) +#define MFC_STAT_L_TXCONFLICT_MASK (1 << MFC_STAT_L_TXCONFLICT_SHIFT) +#define MFC_STAT_L_ADT_ERROR_MASK (1 << MFC_STAT_L_ADT_ERROR_SHIFT) + +#define MFC_STAT_H_TRX_DATA_RECEIVED_SHIFT 7 +#define MFC_STAT_H_TX_OCP_SHIFT 6 +#define MFC_STAT_H_TX_MODE_RX_NOT_DET_SHIFT 5 +#define MFC_STAT_H_TX_FOD_SHIFT 4 +#define MFC_STAT_H_TX_CON_DISCON_SHIFT 3 +#define MFC_STAT_H_AC_MISSING_DET_SHIFT 2 +#define MFC_STAT_H_ADT_RECEIVED_SHIFT 1 +#define MFC_STAT_H_ADT_SENT_SHIFT 0 +#define MFC_STAT_H_TRX_DATA_RECEIVED_MASK (1 << MFC_STAT_H_TRX_DATA_RECEIVED_SHIFT) +#define MFC_STAT_H_TX_OCP_MASK (1 << MFC_STAT_H_TX_OCP_SHIFT) +#define MFC_STAT_H_TX_MODE_RX_NOT_DET_MASK (1 << MFC_STAT_H_TX_MODE_RX_NOT_DET_SHIFT) +#define MFC_STAT_H_TX_FOD_MASK (1 << MFC_STAT_H_TX_FOD_SHIFT) +#define MFC_STAT_H_TX_CON_DISCON_MASK (1 << MFC_STAT_H_TX_CON_DISCON_SHIFT) +#define MFC_STAT_H_AC_MISSING_DET_MASK (1 << MFC_STAT_H_AC_MISSING_DET_SHIFT) +#define MFC_STAT_H_ADT_RECEIVED_MASK (1 << MFC_STAT_H_ADT_RECEIVED_SHIFT) +#define MFC_STAT_H_ADT_SENT_MASK (1 << MFC_STAT_H_ADT_SENT_SHIFT) + +/* BIT DEFINE of Interrupt_A1 Registers, Status1_L (0x1276), Status1_H (0x1277) */ +//for E3 +#define MFC_STAT1_L_EPP_NEGO_FAIL_SHIFT 7 +#define MFC_STAT1_L_EPP_NEGO_FAIL_MASK (1 << MFC_STAT1_L_EPP_NEGO_FAIL_SHIFT) +#define MFC_STAT1_L_EPP_NEGO_PASS_SHIFT 6 +#define MFC_STAT1_L_EPP_NEGO_PASS_MASK (1 << MFC_STAT1_L_EPP_NEGO_PASS_SHIFT) +#define MFC_STAT1_L_EXIT_CLOAK_SHIFT 5 +#define MFC_STAT1_L_EXIT_CLOAK_MASK (1 << MFC_STAT1_L_EXIT_CLOAK_SHIFT) +#define MFC_STAT1_L_DECREASE_POWER_SHIFT 4 +#define MFC_STAT1_L_DECREASE_POWER_MASK (1 << MFC_STAT1_L_DECREASE_POWER_SHIFT) +#define MFC_STAT1_L_INCREASE_POWER_SHIFT 3 //Increase Power +#define MFC_STAT1_L_INCREASE_POWER_MASK (1 << MFC_STAT1_L_INCREASE_POWER_SHIFT) +#define MFC_STAT1_L_NEGO_PASS_360KHZ_SHIFT 2 +#define MFC_STAT1_L_NEGO_PASS_360KHZ_MASK (1 << MFC_STAT1_L_NEGO_PASS_360KHZ_SHIFT) +#define MFC_STAT1_L_EPP_SUPPORT_SHIFT 1 +#define MFC_STAT1_L_EPP_SUPPORT_MASK (1 << MFC_STAT1_L_EPP_SUPPORT_SHIFT) +#define MFC_STAT1_L_MPP_SUPPORT_SHIFT 0 +#define MFC_STAT1_L_MPP_SUPPORT_MASK (1 << MFC_STAT1_L_MPP_SUPPORT_SHIFT) + +/* BIT DEFINE of Interrupt_A Registers, INT_L (0x22), INT_H (0x23) */ +#define MFC_INTA_L_STAT_VOUT_SHIFT 7 +#define MFC_INTA_L_STAT_VRECT_SHIFT 6 +#define MFC_INTA_L_OP_MODE_SHIFT 5 +#define MFC_INTA_L_OVER_VOL_SHIFT 4 +#define MFC_INTA_L_OVER_CURR_SHIFT 3 +#define MFC_INTA_L_OVER_TEMP_SHIFT 2 +#define MFC_INTA_L_TXCONFLICT_SHIFT 1 +#define MFC_INTA_L_ADT_ERROR_SHIFT 0 +#define MFC_INTA_L_STAT_VOUT_MASK (1 << MFC_INTA_L_STAT_VOUT_SHIFT) +#define MFC_INTA_L_STAT_VRECT_MASK (1 << MFC_INTA_L_STAT_VRECT_SHIFT) +#define MFC_INTA_L_OP_MODE_MASK (1 << MFC_INTA_L_OP_MODE_SHIFT) +#define MFC_INTA_L_OVER_VOL_MASK (1 << MFC_INTA_L_OVER_VOL_SHIFT) +#define MFC_INTA_L_OVER_CURR_MASK (1 << MFC_STAT_L_OVER_CURR_SHIFT) +#define MFC_INTA_L_OVER_TEMP_MASK (1 << MFC_STAT_L_OVER_TEMP_SHIFT) +#define MFC_INTA_L_TXCONFLICT_MASK (1 << MFC_STAT_L_TXCONFLICT_SHIFT) +#define MFC_INTA_L_ADT_ERROR_MASK (1 << MFC_INTA_L_ADT_ERROR_SHIFT) + +#define MFC_INTA_H_TRX_DATA_RECEIVED_SHIFT 7 +#define MFC_INTA_H_TX_OCP_SHIFT 6 +#define MFC_INTA_H_TX_MODE_RX_NOT_DET 5 +#define MFC_INTA_H_TX_FOD_SHIFT 4 +#define MFC_INTA_H_TX_CON_DISCON_SHIFT 3 +#define MFC_INTA_H_AC_MISSING_DET_SHIFT 2 +#define MFC_INTA_H_ADT_RECEIVED_SHIFT 1 +#define MFC_INTA_H_ADT_SENT_SHIFT 0 +#define MFC_INTA_H_TRX_DATA_RECEIVED_MASK (1 << MFC_INTA_H_TRX_DATA_RECEIVED_SHIFT) +#define MFC_INTA_H_TX_OCP_MASK (1 << MFC_INTA_H_TX_OCP_SHIFT) +#define MFC_INTA_H_TX_MODE_RX_NOT_DET_MASK (1 << MFC_INTA_H_TX_MODE_RX_NOT_DET) +#define MFC_INTA_H_TX_FOD_MASK (1 << MFC_INTA_H_TX_FOD_SHIFT) +#define MFC_INTA_H_TX_CON_DISCON_MASK (1 << MFC_INTA_H_TX_CON_DISCON_SHIFT) +#define MFC_INTA_H_AC_MISSING_DET_MASK (1 << MFC_INTA_H_AC_MISSING_DET_SHIFT) +#define MFC_INTA_H_ADT_RECEIVED_MASK (1 << MFC_INTA_H_ADT_RECEIVED_SHIFT) +#define MFC_INTA_H_ADT_SENT_MASK (1 << MFC_INTA_H_ADT_SENT_SHIFT) + +/* BIT DEFINE of Interrupt_A1 Registers, INT1_L (0x1272), INT1_H (0x1273); */ +//for E3 +#define MFC_INTA1_L_EPP_NEGO_FAIL_SHIFT 7 +#define MFC_INTA1_L_EPP_NEGO_FAIL_MASK (1 << MFC_INTA1_L_EPP_NEGO_FAIL_SHIFT) +#define MFC_INTA1_L_EPP_NEGO_PASS_SHIFT 6 +#define MFC_INTA1_L_EPP_NEGO_PASS_MASK (1 << MFC_INTA1_L_EPP_NEGO_PASS_SHIFT) +#define MFC_INTA1_L_EXIT_CLOAK_SHIFT 5 +#define MFC_INTA1_L_EXIT_CLOAK_MASK (1 << MFC_INTA1_L_EXIT_CLOAK_SHIFT) +#define MFC_INTA1_L_DECREASE_POWER_SHIFT 4 +#define MFC_INTA1_L_DECREASE_POWER_MASK (1 << MFC_INTA1_L_DECREASE_POWER_SHIFT) +#define MFC_INTA1_L_INCREASE_POWER_SHIFT 3 //Increase Power +#define MFC_INTA1_L_INCREASE_POWER_MASK (1 << MFC_INTA1_L_INCREASE_POWER_SHIFT) +#define MFC_INTA1_L_NEGO_PASS_360KHZ_SHIFT 2 +#define MFC_INTA1_L_NEGO_PASS_360KHZ_MASK (1 << MFC_INTA1_L_NEGO_PASS_360KHZ_SHIFT) +#define MFC_INTA1_L_EPP_SUPPORT_SHIFT 1 +#define MFC_INTA1_L_EPP_SUPPORT_MASK (1 << MFC_INTA1_L_EPP_SUPPORT_SHIFT) +#define MFC_INTA1_L_MPP_SUPPORT_SHIFT 0 +#define MFC_INTA1_L_MPP_SUPPORT_MASK (1 << MFC_INTA1_L_MPP_SUPPORT_SHIFT) + +/* System Operating Mode Register, Sys_op_mode(0x2B) */ +/* RX MODE[7:5] */ +#define MFC_RX_MODE_AC_MISSING 0x0 +#define MFC_RX_MODE_WPC_BASIC 0x1 +#define MFC_RX_MODE_WPC_ADV 0x2 +#define MFC_RX_MODE_PMA_SR1 0x3 +#define MFC_RX_MODE_PMA_SR1E 0x4 +#define MFC_RX_MODE_RESERVED1 0x5 +#define MFC_RX_MODE_RESERVED2 0x6 +#define MFC_RX_MODE_UNKNOWN 0x7 + +/* TX MODE[3:0] */ +#define MFC_TX_MODE_RX_MODE 0x0 +#define MFC_TX_MODE_MST_MODE1 0x1 +#define MFC_TX_MODE_MST_MODE2 0x2 +#define MFC_TX_MODE_TX_MODE 0x3 +#define MFC_TX_MODE_MST_PCR_MODE1 0x7 +#define MFC_TX_MODE_MST_PCR_MODE2 0xF + +//#endif +/* TX MODE[3:0] */ +#define MFC_TX_MODE_BACK_PWR_MISSING 0x0 +#define MFC_TX_MODE_MST_ON 0x4 +#define MFC_TX_MODE_TX_MODE_ON 0x8 +#define MFC_TX_MODE_TX_ERROR 0x9 /* TX FOD, TX conflict */ +#define MFC_TX_MODE_TX_PWR_HOLD 0xA + +/* End of Power Transfer Register, EPT (0x3B) (RX only) */ +#define MFC_WPC_EPT_UNKNOWN 0 +#define MFC_WPC_EPT_END_OF_CHG 1 +#define MFC_WPC_EPT_INT_FAULT 2 +#define MFC_WPC_EPT_OVER_TEMP 3 +#define MFC_WPC_EPT_OVER_VOL 4 +#define MFC_WPC_EPT_OVER_CURR 5 +#define MFC_WPC_EPT_BATT_FAIL 6 +#define MFC_WPC_EPT_RECONFIG 7 +#define MFC_WPC_EPT_NO_RESPONSE 8 + +/* Proprietary Packet Header Register, PPP_Header VALUE(0x1208, MFC_WPC_PCKT_HEADER_REG) */ +#define MFC_HEADER_END_SIG_STRENGTH 0x01 /* Message Size 1 */ +#define MFC_HEADER_END_POWER_TRANSFER 0x02 /* Message Size 1 */ +#define MFC_HEADER_END_CTR_ERROR 0x03 /* Message Size 1 */ +#define MFC_HEADER_END_RECEIVED_POWER 0x04 /* Message Size 1 */ +#define MFC_HEADER_END_CHARGE_STATUS 0x05 /* Message Size 1 */ +#define MFC_HEADER_POWER_CTR_HOLD_OFF 0x06 /* Message Size 1 */ +#define MFC_HEADER_PROPRIETARY_1_BYTE 0x18 /* Message Size 1 */ +#define MFC_HEADER_PACKET_COUNTING 0x19 /* Message Size 1 */ +#define MFC_HEADER_AFC_CONF 0x28 /* Message Size 2 */ +#define MFC_HEADER_CONFIGURATION 0x51 /* Message Size 5 */ +#define MFC_HEADER_IDENTIFICATION 0x71 /* Message Size 7 */ +#define MFC_HEADER_EXTENDED_IDENT 0x81 /* Message Size 8 */ + +/* END CHARGE STATUS CODES IN WPC */ +#define MFC_ECS_CS100 0x64 /* CS 100 */ + +/* TX Data Command Register, TX Data_COM VALUE(0x50) */ +#define MFC_TX_DATA_COM_TX_ID 0x01 + +/* END POWER TRANSFER CODES IN WPC */ +#define MFC_EPT_CODE_UNKNOWN 0x00 +#define MFC_EPT_CODE_CHARGE_COMPLETE 0x01 +#define MFC_EPT_CODE_INTERNAL_FAULT 0x02 +#define MFC_EPT_CODE_OVER_TEMPERATURE 0x03 +#define MFC_EPT_CODE_OVER_VOLTAGE 0x04 +#define MFC_EPT_CODE_OVER_CURRENT 0x05 +#define MFC_EPT_CODE_BATTERY_FAILURE 0x06 +#define MFC_EPT_CODE_RECONFIGURE 0x07 +#define MFC_EPT_CODE_NO_RESPONSE 0x08 + +#define MFC_POWER_MODE_MASK (0x1 << 0) +#define MFC_SEND_USER_PKT_DONE_MASK (0x1 << 7) +#define MFC_SEND_USER_PKT_ERR_MASK (0x3 << 5) +#define MFC_SEND_ALIGN_MASK (0x1 << 3) +#define MFC_SEND_EPT_CC_MASK (0x1 << 0) +#define MFC_SEND_EOC_MASK (0x1 << 0) + +#define MFC_PTK_ERR_NO_ERR 0x00 +#define MFC_PTK_ERR_ERR 0x01 +#define MFC_PTK_ERR_ILLEGAL_HD 0x02 +#define MFC_PTK_ERR_NO_DEF 0x03 + +#define MFC_FW_RESULT_DOWNLOADING 2 +#define MFC_FW_RESULT_PASS 1 +#define MFC_FW_RESULT_FAIL 0 + +#define REQ_AFC_DLY 300 + +#define MFC_FW_MSG "@MFC_FW " + +/* value of TX POWER BUDGET */ +#define MFC_TX_PWR_BUDG_NONE 0x00 +#define MFC_TX_PWR_BUDG_2W 0x14 +#define MFC_TX_PWR_BUDG_5W 0x32 +#define MFC_TX_PWR_BUDG_7_5W 0x4B +#define MFC_TX_PWR_BUDG_10W 0x64 +#define MFC_TX_PWR_BUDG_12W 0x78 +#define MFC_TX_PWR_BUDG_15W 0x96 + +#define RUNNING 0x66 +#define PASS 0x55 +#define FAIL 0xAA +#define ILLEGAL 0x40 + +#if defined(CONFIG_MST_V2) +#define MST_MODE_ON 1 // ON Message to MFC ic +#define MST_MODE_OFF 0 // OFF Message to MFC ic +#define DELAY_FOR_MST 100 // S.LSI : 100 ms +#define MFC_MST_LDO_CONFIG_1 0x7400 +#define MFC_MST_LDO_CONFIG_2 0x7409 +#define MFC_MST_LDO_CONFIG_3 0x7418 +#define MFC_MST_LDO_CONFIG_4 0x3014 +#define MFC_MST_LDO_CONFIG_5 0x3405 +#define MFC_MST_LDO_CONFIG_6 0x3010 +#define MFC_MST_LDO_TURN_ON 0x301c +#define MFC_MST_LDO_CONFIG_8 0x343c +#define MFC_MST_OVER_TEMP_INT 0x0024 +#endif + + +/****************************************************************/ +/********************* SPECIAL REGISTER MAP *********************/ +/****************************************************************/ +#define NU1668_GEN_REQUEST_0_REG 0x0000 + +#define NU1668_GENERAL_REG_000F 0x000F + +#define NU1668_MTP_ADDR_0_REG 0x0010 +#define NU1668_MTP_ADDR_1_REG 0x0011 +#define NU1668_MTP_SECTOR_REG 0x0012 +#define NU1668_MTP_DATA_0_REG 0x0013 +#define NU1668_MTP_DATA_1_REG 0x0014 +#define NU1668_MTP_DATA_2_REG 0x0015 +#define NU1668_MTP_DATA_3_REG 0x0016 +#define NU1668_MTP_CTRL_0_REG 0x0017 +#define NU1668_MTP_CTRL_1_REG 0x0018 +#define NU1668_MTP_CTRL_2_REG 0x0019 +#define NU1668_MTP_CTRL_OP_REG 0x001A +#define NU1668_MTP_STATUS_REG 0x001B +#define NU1668_MTP_WDATA_0_REG 0x001C +#define NU1668_MTP_WDATA_1_REG 0x001D +#define NU1668_MTP_WDATA_2_REG 0x001E +#define NU1668_MTP_WDATA_3_REG 0x001F + +#define NU1668_GEN_REPORT_2_REG 0x0022 +#define NU1668_GEN_REPORT_3_REG 0x0023 +#define NU1668_GEN_REPORT_4_REG 0x0024 +#define NU1668_GEN_REPORT_5_REG 0x0025 +#define NU1668_GEN_REPORT_6_REG 0x0026 +#define NU1668_GEN_REPORT_7_REG 0x0027 +#define NU1668_GEN_REPORT_8_REG 0x0028 + +#define NU1668_SP_CTRL0_REG 0x0090 +#define NU1668_TEST_MODE_CTRL_0_REG 0x1000 +#define NU1668_TEST_MODE_VECTOR_1_REG 0x1002 +#define NU1668_GEN_OPT0_BYTE2_REG 0x1152 + +#define NU1668_KEY_OPEN_REG 0x2017 +#define NU1668_KEY0_REG 0x2018 +#define NU1668_KEY1_REG 0x2019 +#define NU1668_TM_EN_ANA_REG 0x2020 +/****************************************************************/ + +/* F/W Update & Verification ERROR CODES */ +enum { + MFC_FWUP_ERR_COMMON_FAIL = 0, + MFC_FWUP_ERR_SUCCEEDED, + MFC_FWUP_ERR_RUNNING, + + MFC_FWUP_ERR_REQUEST_FW_BIN, + + /* F/W update error */ + MFC_FWUP_ERR_WRITE_KEY_ERR, + MFC_FWUP_ERR_CLK_TIMING_ERR1, /* 5 */ + MFC_FWUP_ERR_CLK_TIMING_ERR2, + MFC_FWUP_ERR_CLK_TIMING_ERR3, + MFC_FWUP_ERR_CLK_TIMING_ERR4, + MFC_FWUP_ERR_INFO_PAGE_EMPTY, + MFC_FWUP_ERR_HALT_M0_ERR, /* 10 */ + MFC_FWUP_ERR_FAIL, + MFC_FWUP_ERR_ADDR_READ_FAIL, + MFC_FWUP_ERR_DATA_NOT_MATCH, + MFC_FWUP_ERR_OTP_LOADER_IN_RAM_ERR, + MFC_FWUP_ERR_CLR_MTP_STATUS_BYTE, /* 15 */ + MFC_FWUP_ERR_MAP_RAM_TO_OTP_ERR, + MFC_FWUP_ERR_WRITING_TO_OTP_BUFFER, + MFC_FWUP_ERR_OTF_BUFFER_VALIDATION, + MFC_FWUP_ERR_READING_OTP_BUFFER_STATUS, + MFC_FWUP_ERR_TIMEOUT_ON_BUFFER_TO_OTP, /* 20 */ + MFC_FWUP_ERR_MTP_WRITE_ERR, + MFC_FWUP_ERR_PKT_CHECKSUM_ERR, + MFC_FWUP_ERR_UNKNOWN_ERR, + MFC_FWUP_ERR_BUFFER_WRITE_IN_SECTOR, + MFC_FWUP_ERR_WRITING_FW_VERION, /* 25 */ + + /* F/W verification error */ + MFC_VERIFY_ERR_WRITE_KEY_ERR, + MFC_VERIFY_ERR_HALT_M0_ERR, + MFC_VERIFY_ERR_KZALLOC_ERR, + MFC_VERIFY_ERR_FAIL, + MFC_VERIFY_ERR_ADDR_READ_FAIL, /* 30 */ + MFC_VERIFY_ERR_DATA_NOT_MATCH, + MFC_VERIFY_ERR_MTP_VERIFIER_IN_RAM_ERR, + MFC_VERIFY_ERR_CLR_MTP_STATUS_BYTE, + MFC_VERIFY_ERR_MAP_RAM_TO_OTP_ERR, + MFC_VERIFY_ERR_UNLOCK_SYS_REG_ERR, /* 35 */ + MFC_VERIFY_ERR_LDO_CLK_2MHZ_ERR, + MFC_VERIFY_ERR_LDO_OUTPUT_5_5V_ERR, + MFC_VERIFY_ERR_ENABLE_LDO_ERR, + MFC_VERIFY_ERR_WRITING_TO_MTP_VERIFY_BUFFER, + MFC_VERIFY_ERR_START_MTP_VERIFY_ERR, /* 40 */ + MFC_VERIFY_ERR_READING_MTP_VERIFY_STATUS, + MFC_VERIFY_ERR_CRC_BUSY, + MFC_VERIFY_ERR_READING_MTP_VERIFY_PASS_FAIL, + MFC_VERIFY_ERR_CRC_ERROR, + MFC_VERIFY_ERR_UNKNOWN_ERR, /* 45 */ + MFC_VERIFY_ERR_BUFFER_WRITE_IN_SECTOR, + + MFC_REPAIR_ERR_HALT_M0_ERR, + MFC_REPAIR_ERR_MTP_REPAIR_IN_RAM, + MFC_REPAIR_ERR_CLR_MTP_STATUS_BYTE, + MFC_REPAIR_ERR_START_MTP_REPAIR_ERR, /* 50 */ + MFC_REPAIR_ERR_READING_MTP_REPAIR_STATUS, + MFC_REPAIR_ERR_READING_MTP_REPAIR_PASS_FAIL, + MFC_REPAIR_ERR_BUFFER_WRITE_IN_SECTOR, +}; + +/* PAD Vout */ +enum { + PAD_VOUT_5V = 0, + PAD_VOUT_9V, + PAD_VOUT_10V, + PAD_VOUT_12V, + PAD_VOUT_18V, + PAD_VOUT_19V, + PAD_VOUT_20V, + PAD_VOUT_24V, +}; + +enum { + MFC_ADC_VOUT = 0, + MFC_ADC_VRECT, + MFC_ADC_RX_IOUT, + MFC_ADC_DIE_TEMP, + MFC_ADC_OP_FRQ, + MFC_ADC_TX_MAX_OP_FRQ, + MFC_ADC_TX_MIN_OP_FRQ, + MFC_ADC_PING_FRQ, + MFC_ADC_TX_IOUT, + MFC_ADC_TX_VOUT, +}; + +enum { + MFC_ADDR = 0, + MFC_SIZE, + MFC_DATA, + MFC_PACKET, +}; + +ssize_t nu1668_show_attrs(struct device *dev, + struct device_attribute *attr, char *buf); + +ssize_t nu1668_store_attrs(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +#define NU1668_ATTR(_name) \ +{ \ + .attr = {.name = #_name, .mode = 0660}, \ + .show = nu1668_show_attrs, \ + .store = nu1668_store_attrs, \ +} + +enum mfc_irq { + MFC_IRQ_STAT_VOUT = 0, + MFC_IRQ_STAT_VRECT, + MFC_IRQ_MODE_CHANGE, + MFC_IRQ_TX_DATA_RECEIVED, + MFC_IRQ_OVER_VOLT, + MFC_IRQ_OVER_CURR, + MFC_IRQ_OVER_TEMP, + MFC_IRQ_TX_OVER_CURR, + MFC_IRQ_TX_OVER_TEMP, + MFC_IRQ_TX_FOD, + MFC_IRQ_TX_CONNECT, + MFC_IRQ_NR, +}; + +enum mfc_firmware_mode { + MFC_RX_FIRMWARE = 0, + MFC_TX_FIRMWARE, +}; + +enum mfc_ic_revision { + MFC_IC_REVISION = 0, + MFC_IC_FONT, +}; + +enum mfc_chip_id { + MFC_CHIP_IDT = 1, + MFC_CHIP_LSI, + MFC_CHIP_CPS, + MFC_CHIP_NUVOLTA, +}; + +enum mfc_headroom { + MFC_HEADROOM_0 = 0, + MFC_HEADROOM_1, /* 0.273V */ + MFC_HEADROOM_2, /* 0.500V */ + MFC_HEADROOM_3, /* 0.648V */ + MFC_HEADROOM_4, /* 0.031V */ + MFC_HEADROOM_5, /* 0.078V */ + MFC_HEADROOM_6, /* 0.093V */ + MFC_HEADROOM_7, /* -0.601V */ +}; + +# define RX_MAX_NUM 10 +struct mfc_qfod_data { + int rx_id; +}; +static int sspth_value[RX_MAX_NUM]; +static int qbase_value[RX_MAX_NUM]; +static int qfod_th0[RX_MAX_NUM]; +static int qfod_th1[RX_MAX_NUM]; +static int qfod_th2[RX_MAX_NUM]; +static int qfod_th3[RX_MAX_NUM]; +static int qfod_tws_th3[RX_MAX_NUM]; +static int qfod_phone_th4[RX_MAX_NUM]; + +#if defined(CONFIG_WIRELESS_IC_PARAM) +extern unsigned int wireless_fw_ver_param; +extern unsigned int wireless_chip_id_param; +extern unsigned int wireless_fw_mode_param; +#endif + +struct mfc_charger_platform_data { + int pad_mode; + int wpc_det; + int irq_wpc_det; + int wpc_int; + int mst_pwr_en; + int wpc_en; + int mag_det; + int coil_sw_en; + int wpc_pdrc; + int irq_wpc_pdrc; + int ping_nen; + int irq_wpc_int; + int wpc_pdet_b; + int irq_wpc_pdet_b; + int cs100_status; + int vout_status; + int siop_level; + int cable_type; + bool default_voreg; + int is_charging; + u32 *wireless20_vout_list; + u32 *wireless20_vrect_list; + u32 *wireless20_max_power_list; + u8 len_wc20_list; + bool ic_on_mode; + int hw_rev_changed; /* this is only for noble/zero2 */ + int otp_firmware_result; + int tx_firmware_result; + int wc_ic_grade; + int wc_ic_rev; + int otp_firmware_ver; + int tx_firmware_ver; + int vout; + int vrect; + u8 trx_data_cmd; + u8 trx_data_val; + char *wireless_charger_name; + char *wired_charger_name; + char *fuelgauge_name; + int opfq_cnt; + int mst_switch_delay; + int wc_cover_rpp; + int wc_hv_rpp; + u32 phone_fod_thresh1; + u32 buds_fod_thresh1; + u32 buds_fod_ta_thresh; + u32 tx_max_op_freq; + u32 tx_min_op_freq; + u32 cep_timeout; + int no_hv; + bool keep_tx_vout; + u32 wpc_vout_ctrl_full; + bool wpc_headroom_ctrl_full; + bool mis_align_guide; + bool unknown_cmb_ctrl; + bool enable_qfod_param; + bool default_clamp_volt; + u32 mis_align_target_vout; + u32 mis_align_offset; + struct mfc_qfod_data *qfod_list; + int qfod_data_num; + int tx_conflict_curr; + + u32 mpp_epp_vout; + u32 mpp_epp_def_power; + u32 mpp_epp_max_count; + + bool pdrc_vrect_clear; +}; + +#define mfc_charger_platform_data_t \ + struct mfc_charger_platform_data + +#define MST_MODE_0 0 +#define MST_MODE_2 1 + +#define MFC_BAT_DUMP_SIZE 256 + +struct mfc_charger_data { + struct i2c_client *client; + struct device *dev; + mfc_charger_platform_data_t *pdata; + struct mutex io_lock; + struct mutex wpc_en_lock; + struct mutex fw_lock; + const struct firmware *firm_data_bin; + + u8 det_state; /* ACTIVE HIGH */ + u8 pdrc_state; /* ACTIVE LOW */ + + struct power_supply *psy_chg; + struct wakeup_source *wpc_ws; + struct wakeup_source *wpc_det_ws; + struct wakeup_source *wpc_tx_ws; + struct wakeup_source *wpc_rx_ws; + struct wakeup_source *wpc_update_ws; + struct wakeup_source *wpc_tx_duty_min_ws; + struct wakeup_source *wpc_afc_vout_ws; + struct wakeup_source *wpc_vout_mode_ws; + struct wakeup_source *wpc_rx_det_ws; + struct wakeup_source *wpc_tx_phm_ws; + struct wakeup_source *wpc_tx_id_ws; + struct wakeup_source *wpc_tx_pwr_budg_ws; + struct wakeup_source *wpc_pdrc_ws; + struct wakeup_source *align_check_ws; + struct wakeup_source *mode_change_ws; + struct wakeup_source *wpc_cs100_ws; + struct wakeup_source *wpc_pdet_b_ws; + struct wakeup_source *wpc_rx_phm_ws; + struct wakeup_source *wpc_rx_power_trans_fail_ws; + struct wakeup_source *wpc_vrect_check_ws; + struct wakeup_source *wpc_phm_exit_ws; + struct wakeup_source *epp_clear_ws; + struct wakeup_source *epp_count_ws; + struct workqueue_struct *wqueue; + struct work_struct wcin_work; + struct delayed_work wpc_det_work; + struct delayed_work wpc_pdrc_work; + struct delayed_work wpc_isr_work; + struct delayed_work wpc_tx_isr_work; + struct delayed_work wpc_tx_id_work; + struct delayed_work wpc_tx_pwr_budg_work; + struct delayed_work mst_off_work; + struct delayed_work wpc_int_req_work; + struct delayed_work wpc_fw_update_work; + struct delayed_work wpc_afc_vout_work; + struct delayed_work wpc_fw_booting_work; + struct delayed_work wpc_vout_mode_work; + struct delayed_work wpc_i2c_error_work; + struct delayed_work wpc_rx_type_det_work; + struct delayed_work wpc_rx_connection_work; + struct delayed_work wpc_tx_op_freq_work; + struct delayed_work wpc_tx_duty_min_work; + struct delayed_work wpc_tx_phm_work; + struct delayed_work wpc_vrect_check_work; + struct delayed_work wpc_rx_power_work; + struct delayed_work wpc_cs100_work; + struct delayed_work wpc_init_work; + struct delayed_work align_check_work; + struct delayed_work mode_change_work; + struct delayed_work wpc_rx_phm_work; + struct delayed_work wpc_rx_power_trans_fail_work; + struct delayed_work wpc_phm_exit_work; + struct delayed_work epp_clear_timer_work; + struct delayed_work epp_count_work; + + struct alarm phm_alarm; + + struct mfc_fod *fod; + struct mfc_cmfet *cmfet; + + u16 addr; + int size; + int is_afc; + int pad_vout; + int is_mst_on; /* mst */ + int chip_id; + u32 rx_op_mode; + int fw_cmd; + int vout_mode; + u32 vout_by_txid; + u32 vrect_by_txid; + u32 max_power_by_txid; + int is_full_status; + int mst_off_lock; + bool is_otg_on; + int led_cover; + bool is_probed; + bool is_afc_tx; + bool pad_ctrl_by_lcd; + bool tx_id_done; + bool is_suspend; + int tx_id; + int tx_id_cnt; + bool rx_phm_status; + int rx_phm_state; + + int flicker_delay; + int flicker_vout_threshold; + + /* wireless tx */ + int tx_status; + bool initial_wc_check; + bool wc_tx_enable; + int wc_rx_type; + bool wc_rx_connected; + bool wc_rx_fod; + bool wc_ldo_status; + int non_sleep_mode_cnt; + u8 adt_transfer_status; + u8 current_rx_power; + u8 tx_pwr_budg; + u8 device_event; + int i2c_error_count; + int input_current; + int duty_min; + int wpc_en_flag; + bool tx_device_phm; + + bool req_tx_id; + bool afc_tx_done; + int req_afc_delay; + + bool sleep_mode; + bool wc_checking_align; + struct timespec64 wc_align_check_start; + int vout_strength; + u32 mis_align_tx_try_cnt; + bool skip_phm_work_in_sleep; + bool reg_access_lock; + bool check_rx_power; + + int mfc_adc_tx_vout; + int mfc_adc_tx_iout; + int mfc_adc_ping_frq; + int mfc_adc_tx_min_op_frq; + int mfc_adc_tx_max_op_frq; + int mfc_adc_vout; + int mfc_adc_vrect; + int mfc_adc_rx_iout; + int mfc_adc_op_frq; + union mfc_fod_state now_fod_state; + union mfc_cmfet_state now_cmfet_state; + +#if defined(CONFIG_WIRELESS_IC_PARAM) + unsigned int wireless_param_info; + unsigned int wireless_fw_ver_param; + unsigned int wireless_chip_id_param; + unsigned int wireless_fw_mode_param; +#endif + + /* EPP flag - T.B.D. */ + //bool epp_supported; + //bool epp_nego_pass; + //bool epp_nego_fail; + + /* MPP flag - T.B.D. */ + //bool mpp_supported; + //bool mpp_nego_pass_360; + //bool mpp_power_inc; + //bool mpp_power_dec; + //bool mpp_exit_cloak; + + // EPP & MPP NEGO data - T.B.D. + u32 mpp_epp_tx_id; + u8 nego_done_power; + u8 potential_load_power; + u8 negotiable_load_power; + u8 mpp_cloak; + + int epp_time; + int epp_count; + + char d_buf[MFC_BAT_DUMP_SIZE]; +}; + +#define fan_ctrl_pad(pad_id) (\ + (pad_id >= 0x14 && pad_id <= 0x1f) || \ + (pad_id >= 0x25 && pad_id <= 0x2f) || \ + (pad_id >= 0x30 && pad_id <= 0x3f) || \ + (pad_id >= 0x46 && pad_id <= 0x4f) || \ + (pad_id >= 0xa1 && pad_id <= 0xcf) || \ + (pad_id >= 0xd0 && pad_id <= 0xff)) + +#define opfreq_ctrl_pad(pad_id) (\ + ((pad_id >= TX_ID_NON_AUTH_PAD) && (pad_id <= TX_ID_NON_AUTH_PAD_END)) || \ + ((pad_id >= TX_ID_N5200_V_PAD) && (pad_id <= TX_ID_AUTH_PAD_ACLASS_END)) || \ + (pad_id == TX_ID_P1300_PAD) || \ + (pad_id == TX_ID_N3300_V_PAD) || \ + (pad_id == TX_ID_N3300_H_PAD) || \ + (pad_id == TX_ID_P4300_PAD)) + +#define volt_ctrl_pad(pad_id) (\ + (pad_id != TX_ID_PG950_S_PAD) && \ + (pad_id != TX_ID_PG950_D_PAD)) + +#define bpp_mode(op_mode) (\ + (op_mode == PAD_RX_MODE_WPC_BPP)) + +#define mpp_mode(op_mode) (\ + (op_mode == PAD_RX_MODE_WPC_MPP_RESTRICT) || \ + (op_mode == PAD_RX_MODE_WPC_MPP_FULL) || \ + (op_mode == PAD_RX_MODE_WPC_MPP_CLOAK) || \ + (op_mode == PAD_RX_MODE_WPC_MPP_NEGO)) + +#define epp_mode(op_mode) (\ + (op_mode == PAD_RX_MODE_WPC_EPP) || \ + (op_mode == PAD_RX_MODE_WPC_EPP_NEGO)) + +#endif /* __WIRELESS_CHARGER_NU1668_H */ diff --git a/drivers/battery/wireless/nu1668_cmfet.c b/drivers/battery/wireless/nu1668_cmfet.c new file mode 100644 index 000000000000..d1c001d10bce --- /dev/null +++ b/drivers/battery/wireless/nu1668_cmfet.c @@ -0,0 +1,335 @@ +/* + * mfc_cmfet.c + * Samsung Mobile MFC CMFET Module + * + * Copyright (C) 2023 Samsung Electronics + * + * + * 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. + */ + +#include +#include +#include +#include +#include + +#include "nu1668_charger.h" + +#define cmfet_log(str, ...) pr_info("[MFC-CMFET]:%s: "str, __func__, ##__VA_ARGS__) + +struct mfc_cmfet { + struct device *parent; + mfc_set_cmfet cb_func; + + struct mutex lock; + union mfc_cmfet_state state; + + /* threshold */ + bool unknown_cmb_ctrl; + unsigned int high_swell_cc_cv_thr; +}; + +static int mfc_cmfet_parse_dt(struct device_node *np, struct mfc_cmfet *cmfet) +{ + int ret = 0; + + cmfet->unknown_cmb_ctrl = of_property_read_bool(np, "battery,unknown_cmb_ctrl"); + + ret = of_property_read_u32(np, "high_swell_cc_cv_thr", &cmfet->high_swell_cc_cv_thr); + if (ret < 0) + cmfet->high_swell_cc_cv_thr = 70; + + cmfet_log("unknown_cmb_ctrl = %d, high_swell_cc_cv_thr = %d\n", + cmfet->unknown_cmb_ctrl, cmfet->high_swell_cc_cv_thr); + return 0; +} + +struct mfc_cmfet *mfc_cmfet_init(struct device *dev, mfc_set_cmfet cb_func) +{ + struct mfc_cmfet *cmfet; + int ret = 0; + + if (IS_ERR_OR_NULL(dev) || + (cb_func == NULL)) + return ERR_PTR(-EINVAL); + + cmfet = kzalloc(sizeof(struct mfc_cmfet), GFP_KERNEL); + if (!cmfet) + return ERR_PTR(-ENOMEM); + + ret = mfc_cmfet_parse_dt(dev->of_node, cmfet); + if (ret < 0) { + kfree(cmfet); + return ERR_PTR(ret); + } + + mutex_init(&cmfet->lock); + + cmfet->parent = dev; + cmfet->cb_func = cb_func; + cmfet->state.value = 0; + + cmfet_log("DONE!!\n"); + return cmfet; +} +EXPORT_SYMBOL(mfc_cmfet_init); + +int mfc_cmfet_init_state(struct mfc_cmfet *cmfet) +{ + if (IS_ERR(cmfet)) + return -EINVAL; + + mutex_lock(&cmfet->lock); + if (cmfet->state.value != 0) { + cmfet->state.value = 0; + + cmfet->cb_func(cmfet->parent, &cmfet->state, false, false); + } + mutex_unlock(&cmfet->lock); + return 0; +} +EXPORT_SYMBOL(mfc_cmfet_init_state); + +int mfc_cmfet_refresh(struct mfc_cmfet *cmfet) +{ + if (IS_ERR(cmfet)) + return -EINVAL; + + mutex_lock(&cmfet->lock); + if (cmfet->state.value != 0) + cmfet->cb_func(cmfet->parent, &cmfet->state, cmfet->state.cma, cmfet->state.cmb); + mutex_unlock(&cmfet->lock); + return 0; +} +EXPORT_SYMBOL(mfc_cmfet_refresh); + +static void set_init_state(union mfc_cmfet_state *state, bool cma, bool cmb) +{ + state->cma = cma; + state->cmb = cmb; +} + +static unsigned long long check_tx_default(struct mfc_cmfet *cmfet, union mfc_cmfet_state *state) +{ + if (state->high_swell) + state->cmb = (state->bat_cap > cmfet->high_swell_cc_cv_thr); + + if (state->full) + state->cmb = true; + + if (state->chg_done) + state->cmb = false; + + return state->value; +} + +static unsigned long long check_tx_unknown(struct mfc_cmfet *cmfet, union mfc_cmfet_state *state) +{ + set_init_state(state, true, (cmfet->unknown_cmb_ctrl)); + + return state->value; +} + +static unsigned long long check_tx_p1100(struct mfc_cmfet *cmfet, union mfc_cmfet_state *state) +{ + set_init_state(state, true, true); + + if (state->vout <= 5500) + state->cmb = false; + + if (state->chg_done) + state->cmb = false; + + return state->value; +} + +static unsigned long long check_tx_n5200(struct mfc_cmfet *cmfet, union mfc_cmfet_state *state) +{ + set_init_state(state, true, false); + + return check_tx_default(cmfet, state); +} + +static unsigned long long check_tx_p5200(struct mfc_cmfet *cmfet, union mfc_cmfet_state *state) +{ + set_init_state(state, true, true); + + if (state->auth) + state->cmb = false; + + return check_tx_default(cmfet, state); +} + +static unsigned long long check_cmfet_state(struct mfc_cmfet *cmfet, union mfc_cmfet_state *state) +{ + switch (state->tx_id) { + case TX_ID_UNKNOWN: + return check_tx_unknown(cmfet, state); + case TX_ID_P1100_PAD: + return check_tx_p1100(cmfet, state); + case TX_ID_N5200_V_PAD: + case TX_ID_N5200_H_PAD: + return check_tx_n5200(cmfet, state); + case TX_ID_P5200_PAD: + return check_tx_p5200(cmfet, state); + default: + break; + } + + set_init_state(state, true, true); + return check_tx_default(cmfet, state); +} + +static bool is_changed_state(union mfc_cmfet_state *state1, union mfc_cmfet_state *state2) +{ + return (state1->cma != state2->cma) || (state1->cmb != state2->cmb); +} + +static int update_cmfet_state(struct mfc_cmfet *cmfet, union mfc_cmfet_state *state) +{ + state->value = check_cmfet_state(cmfet, state); + + if (is_changed_state(state, &cmfet->state)) + cmfet->cb_func(cmfet->parent, state, state->cma, state->cmb); + + cmfet->state.value = state->value; + return 0; +} + +int mfc_cmfet_set_tx_id(struct mfc_cmfet *cmfet, int tx_id) +{ + if (IS_ERR(cmfet) || + (tx_id < 0) || (tx_id >= 256)) + return -EINVAL; + + mutex_lock(&cmfet->lock); + if (cmfet->state.tx_id != tx_id) { + union mfc_cmfet_state temp = { cmfet->state.value, }; + + temp.tx_id = tx_id; + update_cmfet_state(cmfet, &temp); + } + mutex_unlock(&cmfet->lock); + return 0; +} +EXPORT_SYMBOL(mfc_cmfet_set_tx_id); + +int mfc_cmfet_set_bat_cap(struct mfc_cmfet *cmfet, int bat_cap) +{ + if (IS_ERR(cmfet) || + (bat_cap < 0) || (bat_cap > 100)) + return -EINVAL; + + mutex_lock(&cmfet->lock); + if (cmfet->state.bat_cap != bat_cap) { + union mfc_cmfet_state temp = { cmfet->state.value, }; + + temp.bat_cap = bat_cap; + update_cmfet_state(cmfet, &temp); + } + mutex_unlock(&cmfet->lock); + return 0; +} +EXPORT_SYMBOL(mfc_cmfet_set_bat_cap); + +int mfc_cmfet_set_vout(struct mfc_cmfet *cmfet, int vout) +{ + if (IS_ERR(cmfet) || + (vout <= 0)) + return -EINVAL; + + mutex_lock(&cmfet->lock); + if (cmfet->state.vout != vout) { + union mfc_cmfet_state temp = { cmfet->state.value, }; + + temp.vout = vout; + update_cmfet_state(cmfet, &temp); + } + mutex_unlock(&cmfet->lock); + return 0; +} +EXPORT_SYMBOL(mfc_cmfet_set_vout); + +int mfc_cmfet_set_high_swell(struct mfc_cmfet *cmfet, bool state) +{ + if (IS_ERR(cmfet)) + return -EINVAL; + + mutex_lock(&cmfet->lock); + if (cmfet->state.high_swell != state) { + union mfc_cmfet_state temp = { cmfet->state.value, }; + + temp.high_swell = state; + update_cmfet_state(cmfet, &temp); + } + mutex_unlock(&cmfet->lock); + return 0; +} +EXPORT_SYMBOL(mfc_cmfet_set_high_swell); + +int mfc_cmfet_set_full(struct mfc_cmfet *cmfet, bool full) +{ + if (IS_ERR(cmfet)) + return -EINVAL; + + mutex_lock(&cmfet->lock); + if (cmfet->state.full != full) { + union mfc_cmfet_state temp = { cmfet->state.value, }; + + temp.full = full; + update_cmfet_state(cmfet, &temp); + } + mutex_unlock(&cmfet->lock); + return 0; +} +EXPORT_SYMBOL(mfc_cmfet_set_full); + +int mfc_cmfet_set_chg_done(struct mfc_cmfet *cmfet, bool chg_done) +{ + if (IS_ERR(cmfet)) + return -EINVAL; + + mutex_lock(&cmfet->lock); + if (cmfet->state.chg_done != chg_done) { + union mfc_cmfet_state temp = { cmfet->state.value, }; + + temp.chg_done = chg_done; + update_cmfet_state(cmfet, &temp); + } + mutex_unlock(&cmfet->lock); + return 0; +} +EXPORT_SYMBOL(mfc_cmfet_set_chg_done); + +int mfc_cmfet_set_auth(struct mfc_cmfet *cmfet, bool auth) +{ + if (IS_ERR(cmfet)) + return -EINVAL; + + mutex_lock(&cmfet->lock); + if (cmfet->state.auth != auth) { + union mfc_cmfet_state temp = { cmfet->state.value, }; + + temp.auth = auth; + update_cmfet_state(cmfet, &temp); + } + mutex_unlock(&cmfet->lock); + return 0; +} +EXPORT_SYMBOL(mfc_cmfet_set_auth); + +int mfc_cmfet_get_state(struct mfc_cmfet *cmfet, union mfc_cmfet_state *state) +{ + if (IS_ERR(cmfet) || + (state == NULL)) + return -EINVAL; + + mutex_lock(&cmfet->lock); + state->value = cmfet->state.value; + mutex_unlock(&cmfet->lock); + return 0; +} +EXPORT_SYMBOL(mfc_cmfet_get_state); diff --git a/drivers/battery/wireless/nu1668_cmfet.h b/drivers/battery/wireless/nu1668_cmfet.h new file mode 100644 index 000000000000..18ae2e4e4ed6 --- /dev/null +++ b/drivers/battery/wireless/nu1668_cmfet.h @@ -0,0 +1,90 @@ +/* + * mfc_cmfet.h + * Samsung Mobile MFC CMFET Header + * + * Copyright (C) 2023 Samsung Electronics, 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 __MFC_CMFET_H +#define __MFC_CMFET_H __FILE__ + +#include + +struct device; +struct mfc_cmfet; + +union mfc_cmfet_state { + unsigned long long value; + + struct { + unsigned cma : 1, + cmb : 1, + resv : 6, + tx_id : 8, + vout : 16, + bat_cap : 7, + full : 1, + chg_done : 1, + high_swell : 1, + auth : 1; + }; +}; + +typedef int (*mfc_set_cmfet)(struct device *dev, union mfc_cmfet_state *state, bool cma, bool cmb); + +#define MFC_CMFET_DISABLED (-ESRCH) +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) +struct mfc_cmfet *mfc_cmfet_init(struct device *dev, mfc_set_cmfet cb_func); + +int mfc_cmfet_init_state(struct mfc_cmfet *cmfet); +int mfc_cmfet_refresh(struct mfc_cmfet *cmfet); + +int mfc_cmfet_set_tx_id(struct mfc_cmfet *cmfet, int tx_id); +int mfc_cmfet_set_bat_cap(struct mfc_cmfet *cmfet, int bat_cap); +int mfc_cmfet_set_vout(struct mfc_cmfet *cmfet, int vout); +int mfc_cmfet_set_high_swell(struct mfc_cmfet *cmfet, bool state); +int mfc_cmfet_set_full(struct mfc_cmfet *cmfet, bool full); +int mfc_cmfet_set_chg_done(struct mfc_cmfet *cmfet, bool chg_done); +int mfc_cmfet_set_auth(struct mfc_cmfet *cmfet, bool auth); + +int mfc_cmfet_get_state(struct mfc_cmfet *cmfet, union mfc_cmfet_state *state); +#else +static inline struct mfc_cmfet *mfc_cmfet_init(struct device *dev, mfc_set_cmfet cb_func) +{ return ERR_PTR(MFC_CMFET_DISABLED); } + +static inline int mfc_cmfet_init_state(struct mfc_cmfet *cmfet) +{ return MFC_CMFET_DISABLED; } +static inline int mfc_cmfet_refresh(struct mfc_cmfet *cmfet) +{ return MFC_CMFET_DISABLED; } + +static inline int mfc_cmfet_set_tx_id(struct mfc_cmfet *cmfet, int tx_id) +{ return MFC_CMFET_DISABLED; } +static inline int mfc_cmfet_set_bat_cap(struct mfc_cmfet *cmfet, int bat_cap) +{ return MFC_CMFET_DISABLED; } +static inline int mfc_cmfet_set_vout(struct mfc_cmfet *cmfet, int vout) +{ return MFC_CMFET_DISABLED; } +static inline int mfc_cmfet_set_high_swell(struct mfc_cmfet *cmfet, bool state) +{ return MFC_CMFET_DISABLED; } +static inline int mfc_cmfet_set_full(struct mfc_cmfet *cmfet, bool full) +{ return MFC_CMFET_DISABLED; } +static inline int mfc_cmfet_set_chg_done(struct mfc_cmfet *cmfet, bool chg_done) +{ return MFC_CMFET_DISABLED; } +static inline int mfc_cmfet_set_auth(struct mfc_cmfet *cmfet, bool auth) +{ return MFC_CMFET_DISABLED; } + +static inline int mfc_cmfet_get_state(struct mfc_cmfet *cmfet, union mfc_cmfet_state *state) +{ return MFC_CMFET_DISABLED; } +#endif + +#endif /* __MFC_CMFET_H */ diff --git a/drivers/battery/wireless/nu1668_fod.c b/drivers/battery/wireless/nu1668_fod.c new file mode 100644 index 000000000000..72dc1117c1f6 --- /dev/null +++ b/drivers/battery/wireless/nu1668_fod.c @@ -0,0 +1,721 @@ +/* + * mfc_fod.c + * Samsung Mobile MFC FOD Module + * + * Copyright (C) 2023 Samsung Electronics + * + * + * 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. + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include "nu1668_fod.h" + +#define fod_log(str, ...) pr_info("[MFC-FOD]:%s: "str, __func__, ##__VA_ARGS__) +#define DEFAULT_TX_IDX 0 +#define DEFAULT_OP_MODE WPC_OP_MODE_BPP +#define DEFAULT_VENDOR_ID 0x42 + +struct mfc_fod_op { + unsigned int flag; + fod_data_t *data[MFC_FOD_BAT_STATE_MAX]; +}; + +struct mfc_fod_tx { + unsigned int id; + struct mfc_fod_op op[WPC_OP_MODE_MAX]; +}; + +struct mfc_fod { + struct device *parent; + mfc_set_fod cb_func; + + struct mutex lock; + union mfc_fod_state state; + + /* fod data */ + struct mfc_fod_tx *list; + unsigned int count; + unsigned int ext[MFC_FOD_EXT_MAX]; + + /* threshold */ + unsigned int bpp_vout; + unsigned int cc_cv_thr; + unsigned int high_swell_cc_cv_thr; + unsigned int vendor_id; +}; + +static int get_op_mode_by_str(const char *str) +{ + if (str == NULL) + return WPC_OP_MODE_NONE; + + if (!strncmp(str, "ppde", 4)) + return WPC_OP_MODE_PPDE; + + if (!strncmp(str, "epp", 3)) + return WPC_OP_MODE_EPP; + + if (!strncmp(str, "mpp", 3)) + return WPC_OP_MODE_MPP; + + return WPC_OP_MODE_BPP; +} + +static const char *get_ext_str(unsigned int ext_type) +{ + switch (ext_type) { + case MFC_FOD_EXT_EPP_REF_QF: + return "epp_ref_qf"; + case MFC_FOD_EXT_EPP_REF_RF: + return "epp_ref_rf"; + } + + return "none"; +} + +static const char *get_bat_state_str(unsigned int bat_state) +{ + switch (bat_state) { + case MFC_FOD_BAT_STATE_CC: + return "cc"; + case MFC_FOD_BAT_STATE_CV: + return "cv"; + case MFC_FOD_BAT_STATE_FULL: + return "full"; + } + + return "none"; +} + +static fod_data_t *mfc_fod_parse_data(struct device_node *np, int bat_state, unsigned int fod_size) +{ + fod_data_t *data; + const u32 *p; + int len = 0, t_size = 0, ret; + + p = of_get_property(np, get_bat_state_str(bat_state), &len); + if (!p) + return NULL; + + t_size = sizeof(fod_data_t); + data = kcalloc(fod_size, t_size, GFP_KERNEL); + if (!data) + return NULL; + + if (fod_size != (len / t_size)) { + fod_log("not match the size(%d, %d, %d)\n", fod_size, len, t_size); + goto err_size; + } + + switch (t_size) { + case sizeof(u64): + ret = of_property_read_u64_array(np, get_bat_state_str(bat_state), + (u64 *)data, fod_size); + break; + case sizeof(u32): + ret = of_property_read_u32_array(np, get_bat_state_str(bat_state), + (u32 *)data, fod_size); + break; + case sizeof(u16): + ret = of_property_read_u16_array(np, get_bat_state_str(bat_state), + (u16 *)data, fod_size); + break; + case sizeof(u8): + ret = of_property_read_u8_array(np, get_bat_state_str(bat_state), + (u8 *)data, fod_size); + break; + default: + fod_log("invalid t size(%d)\n", t_size); + goto err_size; + } + + if (ret < 0) { + fod_log("%s, failed to parse data (ret = %d)\n", get_bat_state_str(bat_state), ret); + goto err_size; + } + + return data; + +err_size: + kfree(data); + return NULL; +} + +static int mfc_fod_parse_op_node(struct device_node *np, + struct mfc_fod *fod, struct mfc_fod_tx *fod_tx, int op_mode, unsigned int fod_size) +{ + struct mfc_fod_op *fod_op = &fod_tx->op[op_mode]; + unsigned int flag = 0; + int ret = 0, i; + + ret = of_property_read_u32(np, "flag", &flag); + if (ret < 0) { + pr_err("%s: failed to get flag of %s\n", __func__, np->name); + return ret; + } + + for (i = MFC_FOD_BAT_STATE_CC; i < MFC_FOD_BAT_STATE_MAX; i++) { + switch ((flag >> (i * 4)) & 0xF) { + case FOD_FLAG_ADD: + fod_op->data[i] = mfc_fod_parse_data(np, i, fod_size); + if (fod_op->data[i] == NULL) { + ret = -1; + goto err_data; + } + break; + case FOD_FLAG_USE_CC: + if (fod_op->data[MFC_FOD_BAT_STATE_CC] == NULL) { + ret = -2; + goto err_data; + } + + fod_op->data[i] = fod_op->data[MFC_FOD_BAT_STATE_CC]; + break; + case FOD_FLAG_USE_CV: + if (fod_op->data[MFC_FOD_BAT_STATE_CV] == NULL) { + ret = -3; + goto err_data; + } + + fod_op->data[i] = fod_op->data[MFC_FOD_BAT_STATE_CV]; + break; + case FOD_FLAG_USE_FULL: + if (fod_op->data[MFC_FOD_BAT_STATE_FULL] == NULL) { + ret = -4; + goto err_data; + } + + fod_op->data[i] = fod_op->data[MFC_FOD_BAT_STATE_FULL]; + break; + case FOD_FLAG_USE_DEF_PAD: + { + struct mfc_fod_tx *def_tx = &fod->list[DEFAULT_TX_IDX]; + + if (def_tx->op[op_mode].data[i] == NULL) { + ret = -5; + goto err_data; + } + + fod_op->data[i] = def_tx->op[op_mode].data[i]; + } + break; + case FOD_FLAG_USE_DEF_OP: + { + struct mfc_fod_op *def_op = &fod_tx->op[DEFAULT_OP_MODE]; + + if (def_op->data[i] == NULL) { + ret = -6; + goto err_data; + } + + fod_op->data[i] = def_op->data[i]; + } + break; + case FOD_FLAG_NONE: + default: + fod_log("%s - %s is not set\n", np->name, get_bat_state_str(i)); + break; + } + } + + fod_op->flag = flag; + return 0; + +err_data: + for (; i >= 0; i--) { + if (((flag >> (i * 4)) & 0xF) == FOD_FLAG_ADD) + kfree(fod_op->data[i]); + } + + return ret; +} + +static int mfc_fod_init_ext_pad(struct mfc_fod_tx *fod_tx, struct mfc_fod_tx *def_tx) +{ + int i, j; + + if (fod_tx->id == DEFAULT_TX_IDX) + return 0; + + for (j = WPC_OP_MODE_NONE; j < WPC_OP_MODE_MAX; j++) { + if (def_tx->op[j].flag == 0) + continue; + + for (i = MFC_FOD_BAT_STATE_CC; i < MFC_FOD_BAT_STATE_MAX; i++) + fod_tx->op[j].data[i] = def_tx->op[j].data[i]; + + fod_tx->op[j].flag = + (SET_FOD_CC(USE_DEF_PAD) | SET_FOD_CV(USE_DEF_PAD) | SET_FOD_FULL(USE_DEF_PAD)); + } + + return 0; +} + +static int mfc_fod_parse_tx_node(struct device_node *np, struct mfc_fod *fod, unsigned int fod_size) +{ + struct device_node *tx_node = NULL; + int ret = 0, tx_idx = 0; + + for_each_child_of_node(np, tx_node) { + struct mfc_fod_tx *fod_tx = NULL; + struct device_node *op_node = NULL; + + if (tx_idx >= fod->count) { + fod_log("out of range(%d <--> %d)\n", tx_idx, fod->count); + break; + } + + fod_tx = &fod->list[tx_idx++]; + if (sscanf(tx_node->name, "pad_0x%X", &fod_tx->id) < 0) { + fod_log("failed to get tx id(%s)\n", tx_node->name); + continue; + } + + mfc_fod_init_ext_pad(fod_tx, &fod->list[DEFAULT_TX_IDX]); + + for_each_child_of_node(tx_node, op_node) { + int op_mode; + + op_mode = get_op_mode_by_str(op_node->name); + if (op_mode == WPC_OP_MODE_NONE) { + fod_log("%s, invalid op name\n", op_node->name); + continue; + } + + ret = mfc_fod_parse_op_node(op_node, fod, fod_tx, op_mode, fod_size); + if (ret < 0) + fod_log("%s, failed to parse data(ret = %d)\n", op_node->name, ret); + } + } + + return 0; +} + +static void mfc_fod_print_data(struct mfc_fod *fod, unsigned int fod_size) +{ + int x, y, z, k; + struct mfc_fod_tx *fod_tx; + + for (x = 0; x < fod->count; x++) { + fod_tx = &fod->list[x]; + + for (y = WPC_OP_MODE_NONE + 1; y < WPC_OP_MODE_MAX; y++) { + + for (z = MFC_FOD_BAT_STATE_CC; z < MFC_FOD_BAT_STATE_MAX; z++) { + char temp_buf[1024] = {0, }; + int size = 1024; + + if (fod_tx->op[y].data[z] == NULL) { + fod_log("PAD_0x%02X:%s:%s is null!!\n", + fod_tx->id, sb_wrl_op_mode_str(y), get_bat_state_str(z)); + continue; + } + + for (k = 0; k < fod_size; k++) { + snprintf(temp_buf + strlen(temp_buf), size, "0x%02X ", fod_tx->op[y].data[z][k]); + size = sizeof(temp_buf) - strlen(temp_buf); + } + + fod_log("PAD_0x%02X:%s:%s - %s\n", + fod_tx->id, sb_wrl_op_mode_str(y), get_bat_state_str(z), temp_buf); + } + } + } +} + +static void mfc_fod_print_ext(struct mfc_fod *fod) +{ + char ext_buf[1024] = {0, }; + int x, ext_size = 1024; + + for (x = 0; x < MFC_FOD_EXT_MAX; x++) { + snprintf(ext_buf + strlen(ext_buf), ext_size, "%02d:0x%X ", x, fod->ext[x]); + ext_size = sizeof(ext_buf) - strlen(ext_buf); + } + fod_log("EXT - %s\n", ext_buf); +} + +static int mfc_fod_parse_dt(struct device_node *np, struct mfc_fod *fod, unsigned int fod_size) +{ + int ret = 0, i; + + np = of_find_node_by_name(np, "fod_list"); + if (!np) { + fod_log("fod list is null!!!!\n"); + return -ENODEV; + } + + ret = of_property_read_u32(np, "count", &fod->count); + if (ret < 0) { + fod_log("count is null(ret = %d)\n", ret); + return ret; + } + + fod->list = kcalloc(fod->count, sizeof(struct mfc_fod_tx), GFP_KERNEL); + if (!fod->list) { + fod_log("failed to alloc fod list\n"); + return -ENOMEM; + } + + ret = mfc_fod_parse_tx_node(np, fod, fod_size); + if (ret < 0) { + kfree(fod->list); + return ret; + } + + /* parse ext */ + for (i = 0; i < MFC_FOD_EXT_MAX; i++) { + ret = of_property_read_u32(np, get_ext_str(i), (unsigned int *)&fod->ext[i]); + if (ret < 0) + fod_log("%s is null(ret = %d)!!\n", get_ext_str(i), ret); + } + + mfc_fod_print_data(fod, fod_size); + mfc_fod_print_ext(fod); + + return 0; +} + +static int mfc_fod_thr_parse_dt(struct device_node *np, struct mfc_fod *fod) +{ + int ret = 0; + + ret = of_property_read_u32(np, "bpp_vout", &fod->bpp_vout); + if (ret < 0) + fod->bpp_vout = 7000; + + ret = of_property_read_u32(np, "cc_cv_thr", &fod->cc_cv_thr); + if (ret < 0) + fod->cc_cv_thr = 85; + + ret = of_property_read_u32(np, "high_swell_cc_cv_thr", &fod->high_swell_cc_cv_thr); + if (ret < 0) + fod->high_swell_cc_cv_thr = 70; + + ret = of_property_read_u32(np, "vendor_id", (unsigned int *)&fod->vendor_id); + if (ret < 0) { + fod_log("vendor_id is null(ret = %d)!!\n", ret); + fod->vendor_id = DEFAULT_VENDOR_ID; + } + + fod_log("bpp_vout = %d, cc_cv_thr = %d, high_swell_cc_cv_thr = %d, vendor_id = 0x%x\n", + fod->bpp_vout, fod->cc_cv_thr, fod->high_swell_cc_cv_thr, fod->vendor_id); + return 0; +} + +static struct mfc_fod_tx *get_fod_tx(struct mfc_fod *fod, unsigned int tx_id) +{ + int i; + + for (i = 0; i < fod->count; i++) { + if (fod->list[i].id == tx_id) + return &fod->list[i]; + } + + return &fod->list[DEFAULT_TX_IDX]; +} + +static bool check_vendor_id_to_set_op_mode_by_vout(union mfc_fod_state *state, int vendor_id) +{ + return (state->vendor_id == vendor_id); +} + +static int check_op_mode_vout(struct mfc_fod *fod, union mfc_fod_state *state) +{ + if (!check_vendor_id_to_set_op_mode_by_vout(state, fod->vendor_id)) + return state->op_mode; + + switch (state->op_mode) { + case WPC_OP_MODE_EPP: + case WPC_OP_MODE_PPDE: + if (state->vout <= fod->bpp_vout) + return WPC_OP_MODE_BPP; + break; + case WPC_OP_MODE_MPP: + break; + default: + /* default op mode */ + return WPC_OP_MODE_BPP; + } + + return state->op_mode; +} + +static fod_data_t *mfc_fod_get_data(struct mfc_fod *fod) +{ + union mfc_fod_state *fod_state = &fod->state; + struct mfc_fod_tx *fod_tx; + + if (fod->count <= 0) + return NULL; + + fod_tx = get_fod_tx(fod, fod_state->tx_id); + fod_state->fake_op_mode = check_op_mode_vout(fod, fod_state); + return fod_tx->op[fod_state->fake_op_mode].data[fod_state->bat_state]; +} + +struct mfc_fod *mfc_fod_init(struct device *dev, unsigned int fod_size, mfc_set_fod cb_func) +{ + struct mfc_fod *fod; + int ret = 0; + + if (IS_ERR_OR_NULL(dev) || + (fod_size <= 0) || + (fod_size > MFC_FOD_MAX_SIZE) || + (cb_func == NULL)) + return ERR_PTR(-EINVAL); + + fod = kzalloc(sizeof(struct mfc_fod), GFP_KERNEL); + if (!fod) + return ERR_PTR(-ENOMEM); + + ret = mfc_fod_parse_dt(dev->of_node, fod, fod_size); + if (ret < 0) { + kfree(fod); + return ERR_PTR(ret); + } + mfc_fod_thr_parse_dt(dev->of_node, fod); + + mutex_init(&fod->lock); + + fod->parent = dev; + fod->cb_func = cb_func; + fod->state.value = 0; + + fod_log("DONE!!\n"); + return fod; +} +EXPORT_SYMBOL(mfc_fod_init); + +int mfc_fod_init_state(struct mfc_fod *fod) +{ + if (IS_ERR(fod)) + return -EINVAL; + + mutex_lock(&fod->lock); + if (fod->state.value != 0) { + fod->state.value = 0; + + fod->cb_func(fod->parent, &fod->state, mfc_fod_get_data(fod)); + } + mutex_unlock(&fod->lock); + return 0; +} +EXPORT_SYMBOL(mfc_fod_init_state); + +int mfc_fod_refresh(struct mfc_fod *fod) +{ + if (IS_ERR(fod)) + return -EINVAL; + + mutex_lock(&fod->lock); + if (fod->state.value != 0) + fod->cb_func(fod->parent, &fod->state, mfc_fod_get_data(fod)); + mutex_unlock(&fod->lock); + return 0; +} +EXPORT_SYMBOL(mfc_fod_refresh); + +int mfc_fod_set_op_mode(struct mfc_fod *fod, int op_mode) +{ + if (IS_ERR(fod) || + (op_mode < WPC_OP_MODE_NONE) || + (op_mode >= WPC_OP_MODE_MAX)) + return -EINVAL; + + mutex_lock(&fod->lock); + switch (fod->state.op_mode) { + case WPC_OP_MODE_EPP: + case WPC_OP_MODE_MPP: + case WPC_OP_MODE_PPDE: + fod_log("prevent op mode(%d)!!\n", op_mode); + break; + case WPC_OP_MODE_BPP: + case WPC_OP_MODE_NONE: + default: + if (fod->state.op_mode != op_mode) { + fod->state.op_mode = op_mode; + + fod->cb_func(fod->parent, &fod->state, mfc_fod_get_data(fod)); + } + break; + } + mutex_unlock(&fod->lock); + return 0; +} +EXPORT_SYMBOL(mfc_fod_set_op_mode); + +int mfc_fod_set_vendor_id(struct mfc_fod *fod, int vendor_id) +{ + if (IS_ERR(fod) || + (vendor_id < 0) || + (vendor_id > 0xFF)) + return -EINVAL; + + mutex_lock(&fod->lock); + if (vendor_id != fod->state.vendor_id) { + fod->state.vendor_id = vendor_id; + + fod->cb_func(fod->parent, &fod->state, mfc_fod_get_data(fod)); + } + mutex_unlock(&fod->lock); + return 0; +} +EXPORT_SYMBOL(mfc_fod_set_vendor_id); + +int mfc_fod_set_tx_id(struct mfc_fod *fod, int tx_id) +{ + if (IS_ERR(fod) || + (tx_id < 0) || (tx_id >= 256)) + return -EINVAL; + + mutex_lock(&fod->lock); + if (fod->state.tx_id != tx_id) { + fod->state.tx_id = tx_id; + + fod->cb_func(fod->parent, &fod->state, mfc_fod_get_data(fod)); + } + mutex_unlock(&fod->lock); + return 0; +} +EXPORT_SYMBOL(mfc_fod_set_tx_id); + +static int check_bat_state(struct mfc_fod *fod) +{ + if (fod->state.high_swell) + return (fod->state.bat_cap > fod->high_swell_cc_cv_thr) ? + MFC_FOD_BAT_STATE_CV : MFC_FOD_BAT_STATE_CC; + + return (fod->state.bat_cap > fod->cc_cv_thr) ? + MFC_FOD_BAT_STATE_CV : MFC_FOD_BAT_STATE_CC; +} + +static int set_bat_state(struct mfc_fod *fod, int bat_state) +{ + switch (fod->state.bat_state) { + case MFC_FOD_BAT_STATE_FULL: + fod_log("prevent bat state(%d)!!\n", bat_state); + break; + case MFC_FOD_BAT_STATE_CC: + case MFC_FOD_BAT_STATE_CV: + default: + if (fod->state.bat_state != bat_state) { + fod->state.bat_state = bat_state; + + fod->cb_func(fod->parent, &fod->state, mfc_fod_get_data(fod)); + } + break; + } + + return 0; +} + +int mfc_fod_set_bat_state(struct mfc_fod *fod, int bat_state) +{ + if (IS_ERR(fod) || + (bat_state < MFC_FOD_BAT_STATE_CC) || + (bat_state >= MFC_FOD_BAT_STATE_MAX)) + return -EINVAL; + + mutex_lock(&fod->lock); + set_bat_state(fod, bat_state); + mutex_unlock(&fod->lock); + return 0; +} +EXPORT_SYMBOL(mfc_fod_set_bat_state); + +int mfc_fod_set_bat_cap(struct mfc_fod *fod, int bat_cap) +{ + if (IS_ERR(fod) || + (bat_cap < 0) || (bat_cap > 100)) + return -EINVAL; + + mutex_lock(&fod->lock); + if (fod->state.bat_cap != bat_cap) { + fod->state.bat_cap = bat_cap; + + set_bat_state(fod, check_bat_state(fod)); + } + mutex_unlock(&fod->lock); + + return 0; +} +EXPORT_SYMBOL(mfc_fod_set_bat_cap); + +int mfc_fod_set_vout(struct mfc_fod *fod, int vout) +{ + if (IS_ERR(fod) || + (vout <= 0)) + return -EINVAL; + + mutex_lock(&fod->lock); + if (fod->state.vout != vout) { + int new_op_mode, old_op_mode; + + old_op_mode = check_op_mode_vout(fod, &fod->state); + + fod->state.vout = vout; + new_op_mode = check_op_mode_vout(fod, &fod->state); + + if (new_op_mode != old_op_mode) + fod->cb_func(fod->parent, &fod->state, mfc_fod_get_data(fod)); + } + mutex_unlock(&fod->lock); + return 0; +} +EXPORT_SYMBOL(mfc_fod_set_vout); + +int mfc_fod_set_high_swell(struct mfc_fod *fod, bool state) +{ + if (IS_ERR(fod)) + return -EINVAL; + + mutex_lock(&fod->lock); + if (fod->state.high_swell != state) { + fod->state.high_swell = state; + + set_bat_state(fod, check_bat_state(fod)); + } + mutex_unlock(&fod->lock); + return 0; +} +EXPORT_SYMBOL(mfc_fod_set_high_swell); + +int mfc_fod_get_state(struct mfc_fod *fod, union mfc_fod_state *state) +{ + if (IS_ERR(fod) || + (state == NULL)) + return -EINVAL; + + mutex_lock(&fod->lock); + state->value = fod->state.value; + mutex_unlock(&fod->lock); + return 0; +} +EXPORT_SYMBOL(mfc_fod_get_state); + +int mfc_fod_get_ext(struct mfc_fod *fod, int ext_type, int *data) +{ + if (IS_ERR(fod) || + (ext_type < MFC_FOD_EXT_EPP_REF_QF) || + (ext_type >= MFC_FOD_EXT_MAX) || + (data == NULL)) + return -EINVAL; + + mutex_lock(&fod->lock); + *data = fod->ext[ext_type]; + mutex_unlock(&fod->lock); + return 0; +} +EXPORT_SYMBOL(mfc_fod_get_ext); diff --git a/drivers/battery/wireless/nu1668_fod.h b/drivers/battery/wireless/nu1668_fod.h new file mode 100644 index 000000000000..d27df0879eaf --- /dev/null +++ b/drivers/battery/wireless/nu1668_fod.h @@ -0,0 +1,108 @@ +/* + * mfc_fod.h + * Samsung Mobile MFC FOD Header + * + * Copyright (C) 2023 Samsung Electronics, 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 __MFC_FOD_H +#define __MFC_FOD_H __FILE__ + +#include + +struct device; +struct mfc_fod; + +#define MFC_FOD_MAX_SIZE 256 + +enum { + MFC_FOD_BAT_STATE_CC = 0, + MFC_FOD_BAT_STATE_CV, + MFC_FOD_BAT_STATE_FULL, + MFC_FOD_BAT_STATE_MAX +}; + +union mfc_fod_state { + unsigned long long value; + + struct { + unsigned tx_id : 8, + vendor_id : 8, + op_mode : 8, + fake_op_mode : 8, + bat_state : 8, + bat_cap : 7, + high_swell : 1, + vout : 16; + }; +}; + +typedef unsigned int fod_data_t; +typedef int (*mfc_set_fod)(struct device *dev, union mfc_fod_state *state, fod_data_t *data); + +enum { + MFC_FOD_EXT_EPP_REF_QF = 0, + MFC_FOD_EXT_EPP_REF_RF, + + MFC_FOD_EXT_MAX +}; + +#define MFC_FOD_DISABLED (-ESRCH) +#if IS_ENABLED(CONFIG_WIRELESS_CHARGING) +struct mfc_fod *mfc_fod_init(struct device *dev, unsigned int fod_size, mfc_set_fod cb_func); + +int mfc_fod_init_state(struct mfc_fod *fod); +int mfc_fod_refresh(struct mfc_fod *fod); + +int mfc_fod_set_op_mode(struct mfc_fod *fod, int op_mode); +int mfc_fod_set_vendor_id(struct mfc_fod *fod, int vendor_id); +int mfc_fod_set_tx_id(struct mfc_fod *fod, int tx_id); +int mfc_fod_set_bat_state(struct mfc_fod *fod, int bat_state); +int mfc_fod_set_bat_cap(struct mfc_fod *fod, int bat_cap); +int mfc_fod_set_vout(struct mfc_fod *fod, int vout); +int mfc_fod_set_high_swell(struct mfc_fod *fod, bool state); + +int mfc_fod_get_state(struct mfc_fod *fod, union mfc_fod_state *state); +int mfc_fod_get_ext(struct mfc_fod *fod, int ext_type, int *data); +#else +static inline struct mfc_fod *mfc_fod_init(struct device *dev, unsigned int fod_size, mfc_set_fod cb_func) +{ return ERR_PTR(MFC_FOD_DISABLED); } + +static inline int mfc_fod_init_state(struct mfc_fod *fod) +{ return MFC_FOD_DISABLED; } +static inline int mfc_fod_refresh(struct mfc_fod *fod) +{ return MFC_FOD_DISABLED; } + +static inline int mfc_fod_set_op_mode(struct mfc_fod *fod, int op_mode) +{ return MFC_FOD_DISABLED; } +static inline int mfc_fod_set_vendor_id(struct mfc_fod *fod, int vendor_id) +{ return MFC_FOD_DISABLED; } +static inline int mfc_fod_set_tx_id(struct mfc_fod *fod, int tx_id) +{ return MFC_FOD_DISABLED; } +static inline int mfc_fod_set_bat_state(struct mfc_fod *fod, int bat_state) +{ return MFC_FOD_DISABLED; } +static inline int mfc_fod_set_bat_cap(struct mfc_fod *fod, int bat_cap) +{ return MFC_FOD_DISABLED; } +static inline int mfc_fod_set_vout(struct mfc_fod *fod, int vout) +{ return MFC_FOD_DISABLED; } +static inline int mfc_fod_set_high_swell(struct mfc_fod *fod, bool state) +{ return MFC_FOD_DISABLED; } + +static inline int mfc_fod_get_state(struct mfc_fod *fod, union mfc_fod_state *state) +{ return MFC_FOD_DISABLED; } +static inline int mfc_fod_get_ext(struct mfc_fod *fod, int ext_type, int *data) +{ return MFC_FOD_DISABLED; } +#endif + +#endif /* __MFC_FOD_H */ diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig index c93954613691..71734e773a7d 100644 --- a/drivers/cpufreq/Kconfig +++ b/drivers/cpufreq/Kconfig @@ -42,6 +42,11 @@ config CPU_FREQ_TIMES If in doubt, say N. +config CPU_FREQ_LIMIT + tristate "CPU frequency limit API" + help + This driver supports API to limit CPU frequency. + choice prompt "Default CPUFreq governor" default CPU_FREQ_DEFAULT_GOV_USERSPACE if ARM_SA1100_CPUFREQ || ARM_SA1110_CPUFREQ @@ -337,5 +342,11 @@ config QORIQ_CPUFREQ This adds the CPUFreq driver support for Freescale QorIQ SoCs which are capable of changing the CPU's frequency dynamically. +config QCOM_LMH_STAT + bool + default y + help + time tracking QCOM LMH clocks + endif endmenu diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index b34e73cbc64e..09e80918c06a 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -8,6 +8,9 @@ obj-$(CONFIG_CPU_FREQ_STAT) += cpufreq_stats.o # CPUfreq times obj-$(CONFIG_CPU_FREQ_TIMES) += cpufreq_times.o +# CPUfreq limit +obj-$(CONFIG_CPU_FREQ_LIMIT) += cpufreq_limit.o + # CPUfreq governors obj-$(CONFIG_CPU_FREQ_GOV_PERFORMANCE) += cpufreq_performance.o obj-$(CONFIG_CPU_FREQ_GOV_POWERSAVE) += cpufreq_powersave.o diff --git a/drivers/cpufreq/cpufreq_limit.c b/drivers/cpufreq/cpufreq_limit.c new file mode 100644 index 000000000000..f5a1854cad53 --- /dev/null +++ b/drivers/cpufreq/cpufreq_limit.c @@ -0,0 +1,1555 @@ +/* + * drivers/cpufreq/cpufreq_limit.c + * + * Remade according to cpufreq change + * (refer to commit df0eea4488081e0698b0b58ccd1e8c8823e22841 + * 18c49926c4bf4915e5194d1de3299c0537229f9f) + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_OF +#include +#endif +#include + +#define MAX_BUF_SIZE 1024 +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +/* adaptive boost from walt */ +extern int cpufreq_walt_set_adaptive_freq(unsigned int cpu, unsigned int adaptive_low_freq, + unsigned int adaptive_high_freq); +extern int cpufreq_walt_get_adaptive_freq(unsigned int cpu, unsigned int *adaptive_low_freq, + unsigned int *adaptive_high_freq); +extern int cpufreq_walt_reset_adaptive_freq(unsigned int cpu); + +static unsigned int __read_mostly lpcharge; +module_param(lpcharge, uint, 0444); + +/* voltage based freq table */ +#if IS_ENABLED(CONFIG_QTI_CPU_VOLTAGE_COOLING_DEVICE) +extern struct freq_voltage_base cflm_vbf; +#else +static struct freq_voltage_base cflm_vbf; +#endif + +static DEFINE_MUTEX(cflm_mutex); +#define LIMIT_RELEASE -1 + +/* boosted state */ +#define BOOSTED 1 +#define NOT_BOOSTED 0 + +#define NUM_CPUS 8 +static unsigned int cflm_req_init[NUM_CPUS]; +static struct freq_qos_request max_req[NUM_CPUS][CFLM_MAX_ITEM]; +static struct freq_qos_request min_req[NUM_CPUS][CFLM_MAX_ITEM]; +static struct kobject *cflm_kobj; + +struct freq_map { + unsigned int in; + unsigned int out; +}; + +/* adaptive boost threshold - high - low freq table */ +struct aboost_th_table { + int threshold; + int high; + int low; +}; + +/* input info: freq, time(TBD) */ +struct input_info { + int boosted; + int min; + int max; + u64 time_in_min_limit; + u64 time_in_max_limit; + u64 time_in_over_limit; + ktime_t last_min_limit_time; + ktime_t last_max_limit_time; + ktime_t last_over_limit_time; +}; +static struct input_info freq_input[CFLM_MAX_ITEM]; + +struct cflm_parameter { + /* to make virtual freq table */ + struct cpufreq_frequency_table *cpuftbl_L; + struct cpufreq_frequency_table *cpuftbl_b; + unsigned int unified_cpuftbl[50]; + unsigned int freq_count; + bool table_initialized; + + /* cpu info: silver/gold/prime */ + unsigned int s_first; + unsigned int s_fmin; + unsigned int s_fmax; + unsigned int g_first; + unsigned int g_fmin; + unsigned int g_fmax; + unsigned int p_first; + unsigned int p_fmin; + unsigned int p_fmax; + + /* 4 policy arch: sm8650, titanium, same clock table as gold */ + bool titanium; + unsigned int t_first; + unsigned int t_fmin; + unsigned int t_fmax; + + /* exceptional case */ + unsigned int g_fmin_up; /* fixed gold clock for performance */ + + /* in virtual table little(silver)/big(gold & prime) */ + unsigned int big_min_freq; + unsigned int big_max_freq; + unsigned int ltl_min_freq; + unsigned int ltl_max_freq; + + /* pre-defined value */ + struct freq_map *silver_boost_map; + unsigned int boost_map_size; + struct freq_map *silver_limit_map; + unsigned int limit_map_size; + unsigned int silver_divider; + + /* current freq in virtual table */ + unsigned int min_limit_val; + unsigned int max_limit_val; + + /* sched boost type */ + int sched_boost_type; + bool sched_boost_cond; + bool sched_boost_enabled; + + /* over limit */ + unsigned int over_limit; + + /* voltage based clock */ + bool vol_based_clk; + int vbf_offset; /* gold clock offset for perf */ + + /* adaptive boost */ + bool ab_enabled; + struct aboost_th_table *ab_table; +}; + + +/* TODO: move to dtsi? */ +static struct cflm_parameter param = { + .freq_count = 0, + .table_initialized = false, + + .s_first = 0, + .g_first = 2, + .p_first = 7, + + .titanium = 0, + .t_first = 5, + + .g_fmin_up = 0, /* fixed gold clock for performance */ + + .ltl_min_freq = 0, /* will be auto updated */ + .ltl_max_freq = 0, /* will be auto updated */ + .big_min_freq = 0, /* will be auto updated */ + .big_max_freq = 0, /* will be auto updated */ + + .boost_map_size = 0, + .limit_map_size = 0, + .silver_divider = 2, + + .min_limit_val = -1, + .max_limit_val = -1, + + .sched_boost_type = CONSERVATIVE_BOOST, + .sched_boost_cond = false, + .sched_boost_enabled = false, + + .over_limit = 0, + + .vol_based_clk = false, +}; + +static bool cflm_make_table(void) +{ + int i, count = 0; + int freq_count = 0; + unsigned int freq; + bool ret = false; + + /* big cluster table */ + if (!param.cpuftbl_b) + goto little; + + for (i = 0; param.cpuftbl_b[i].frequency != CPUFREQ_TABLE_END; i++) + count = i; + + for (i = count; i >= 0; i--) { + freq = param.cpuftbl_b[i].frequency; + + if (freq == CPUFREQ_ENTRY_INVALID) + continue; + + if (freq < param.big_min_freq || + freq > param.big_max_freq) + continue; + + param.unified_cpuftbl[freq_count++] = freq; + } + +little: + /* LITTLE cluster table */ + if (!param.cpuftbl_L) + goto done; + + for (i = 0; param.cpuftbl_L[i].frequency != CPUFREQ_TABLE_END; i++) + count = i; + + for (i = count; i >= 0; i--) { + freq = param.cpuftbl_L[i].frequency / param.silver_divider; + + if (freq == CPUFREQ_ENTRY_INVALID) + continue; + + if (freq < param.ltl_min_freq || + freq > param.ltl_max_freq) + continue; + + param.unified_cpuftbl[freq_count++] = freq; + } + +done: + if (freq_count) { + pr_debug("%s: unified table is made\n", __func__); + param.freq_count = freq_count; + ret = true; + } else { + pr_err("%s: cannot make unified table\n", __func__); + } + + return ret; +} + +/** + * cflm_set_table - cpufreq table from dt via qcom-cpufreq + */ +static void cflm_set_table(int cpu, struct cpufreq_frequency_table *ftbl) +{ + int i, count = 0; + unsigned int max_freq_b = 0, min_freq_b = UINT_MAX; + unsigned int max_freq_l = 0, min_freq_l = UINT_MAX; + + if (param.table_initialized) + return; + + if (cpu == param.s_first) + param.cpuftbl_L = ftbl; + else if (cpu == param.p_first) + param.cpuftbl_b = ftbl; + + if (!param.cpuftbl_L) + return; + + if (!param.cpuftbl_b) + return; + + pr_info("%s: freq table is ready, update config\n", __func__); + + /* update little config */ + for (i = 0; param.cpuftbl_L[i].frequency != CPUFREQ_TABLE_END; i++) + count = i; + + for (i = count; i >= 0; i--) { + if (param.cpuftbl_L[i].frequency == CPUFREQ_ENTRY_INVALID) + continue; + + if (param.cpuftbl_L[i].frequency < min_freq_l) + min_freq_l = param.cpuftbl_L[i].frequency; + + if (param.cpuftbl_L[i].frequency > max_freq_l) + max_freq_l = param.cpuftbl_L[i].frequency; + } + + if (!param.ltl_min_freq) + param.ltl_min_freq = min_freq_l / param.silver_divider; + if (!param.ltl_max_freq) + param.ltl_max_freq = max_freq_l / param.silver_divider; + + /* update big config */ + for (i = 0; param.cpuftbl_b[i].frequency != CPUFREQ_TABLE_END; i++) + count = i; + + for (i = count; i >= 0; i--) { + if (param.cpuftbl_b[i].frequency == CPUFREQ_ENTRY_INVALID) + continue; + + if ((param.cpuftbl_b[i].frequency < min_freq_b) && + (param.cpuftbl_b[i].frequency > param.ltl_max_freq)) + min_freq_b = param.cpuftbl_b[i].frequency; + + if (param.cpuftbl_b[i].frequency > max_freq_b) + max_freq_b = param.cpuftbl_b[i].frequency; + } + + if (!param.big_min_freq) + param.big_min_freq = min_freq_b; + if (!param.big_max_freq) + param.big_max_freq = max_freq_b; + + pr_info("%s: updated: little(%u-%u), big(%u-%u)\n", __func__, + param.ltl_min_freq, param.ltl_max_freq, + param.big_min_freq, param.big_max_freq); + + param.table_initialized = cflm_make_table(); +} + +/** + * cflm_get_table - fill the cpufreq table to support HMP + * @buf a buf that has been requested to fill the cpufreq table + */ +static ssize_t cflm_get_table(char *buf) +{ + ssize_t len = 0; + int i = 0; + + if (!param.freq_count) + return len; + + for (i = 0; i < param.freq_count; i++) + len += snprintf(buf + len, MAX_BUF_SIZE, "%u ", + param.unified_cpuftbl[i]); + + len--; + len += snprintf(buf + len, MAX_BUF_SIZE, "\n"); + + pr_info("%s: %s\n", __func__, buf); + + return len; +} + +static void cflm_update_boost(void) +{ + int i; + bool boost_condition = false; + + /* sched boost */ + param.sched_boost_cond = false; + for (i = 0; i < CFLM_MAX_ITEM; i++) { + if (freq_input[i].min > (int)param.ltl_max_freq) { + param.sched_boost_cond = true; + boost_condition = true; + + break; + } + } + + if (boost_condition) { + if (!param.sched_boost_enabled) { + pr_debug("%s: sched boost on, type(%d)\n", __func__, param.sched_boost_type); + sched_set_boost(param.sched_boost_type); + param.sched_boost_enabled = true; + } else { + pr_debug("%s: sched boost already on, do nothing\n", __func__); + } + } else { + if (param.sched_boost_enabled) { + pr_debug("%s: sched boost off(%d)\n", __func__, (param.sched_boost_type * -1)); + sched_set_boost(param.sched_boost_type * -1); + param.sched_boost_enabled = false; + } else { + pr_debug("%s: sched boost already off, do nothing\n", __func__); + } + } +} + +static s32 cflm_freq_qos_read_value(struct freq_constraints *qos, + enum freq_qos_req_type type) +{ + s32 ret; + + switch (type) { + case FREQ_QOS_MIN: + ret = IS_ERR_OR_NULL(qos) ? + FREQ_QOS_MIN_DEFAULT_VALUE : + READ_ONCE(qos->min_freq.target_value); + break; + case FREQ_QOS_MAX: + ret = IS_ERR_OR_NULL(qos) ? + FREQ_QOS_MAX_DEFAULT_VALUE : + READ_ONCE(qos->max_freq.target_value); + break; + default: + WARN_ON(1); + ret = 0; + } + + return ret; +} + +static void cflm_current_qos(void) +{ + struct cpufreq_policy *policy; + int s_min = 0, s_max = 0; + int g_min = 0, g_max = 0; + int p_min = 0, p_max = 0; + int t_min = 0, t_max = 0; + unsigned int a_low = 0, a_high = 0; + + policy = cpufreq_cpu_get(param.s_first); + if (policy) { + s_min = cflm_freq_qos_read_value(&policy->constraints, FREQ_QOS_MIN); + s_max = cflm_freq_qos_read_value(&policy->constraints, FREQ_QOS_MAX); + cpufreq_cpu_put(policy); + } + + if (param.ab_enabled) { + cpufreq_walt_get_adaptive_freq(param.s_first, &a_low, &a_high); + pr_cont("%s: s[%d(%d, %d)-%d]", __func__, s_min, a_low, a_high, s_max); + } else { + pr_cont("%s: s[%d-%d]", __func__, s_min, s_max); + } + + + policy = cpufreq_cpu_get(param.g_first); + if (policy) { + g_min = cflm_freq_qos_read_value(&policy->constraints, FREQ_QOS_MIN); + g_max = cflm_freq_qos_read_value(&policy->constraints, FREQ_QOS_MAX); + cpufreq_cpu_put(policy); + } + + if (param.ab_enabled) { + cpufreq_walt_get_adaptive_freq(param.g_first, &a_low, &a_high); + pr_cont(", g[%d(%d, %d)-%d]", g_min, a_low, a_high, g_max); + } else { + pr_cont(", g[%d-%d]", g_min, g_max); + } + + + /* now, not use adaptive boost for titanium and prime */ + if (param.titanium) { + policy = cpufreq_cpu_get(param.t_first); + if (policy) { + t_min = cflm_freq_qos_read_value(&policy->constraints, FREQ_QOS_MIN); + t_max = cflm_freq_qos_read_value(&policy->constraints, FREQ_QOS_MAX); + cpufreq_cpu_put(policy); + } + + pr_cont(", t[%d-%d]", t_min, t_max); + } + + + policy = cpufreq_cpu_get(param.p_first); + if (policy) { + p_min = cflm_freq_qos_read_value(&policy->constraints, FREQ_QOS_MIN); + p_max = cflm_freq_qos_read_value(&policy->constraints, FREQ_QOS_MAX); + cpufreq_cpu_put(policy); + } + + pr_cont(", p[%d-%d]", p_min, p_max); + + pr_cont("\n"); +} + +static bool cflm_max_lock_need_restore(void) +{ + if ((int)param.over_limit <= 0) + return false; + + if (freq_input[CFLM_USERSPACE].min > 0) { + if (freq_input[CFLM_USERSPACE].min > (int)param.ltl_max_freq) { + pr_debug("%s: userspace minlock (%d) > ltl max (%d)\n", + __func__, freq_input[CFLM_USERSPACE].min, param.ltl_max_freq); + return false; + } + } + + if (freq_input[CFLM_TOUCH].min > 0) { + if (freq_input[CFLM_TOUCH].min > (int)param.ltl_max_freq) { + pr_debug("%s: touch minlock (%d) > ltl max (%d)\n", + __func__, freq_input[CFLM_TOUCH].min, param.ltl_max_freq); + return false; + } + } + + return true; +} + +static bool cflm_high_pri_min_lock_required(void) +{ + if ((int)param.over_limit <= 0) + return false; + + if (freq_input[CFLM_USERSPACE].min > 0) { + if (freq_input[CFLM_USERSPACE].min > (int)param.ltl_max_freq) { + pr_debug("%s: userspace minlock (%d) > ltl max (%d)\n", + __func__, freq_input[CFLM_USERSPACE].min, param.ltl_max_freq); + return true; + } + } + + if (freq_input[CFLM_TOUCH].min > 0) { + if (freq_input[CFLM_TOUCH].min > (int)param.ltl_max_freq) { + pr_debug("%s: touch minlock (%d) > ltl max (%d)\n", + __func__, freq_input[CFLM_TOUCH].min, param.ltl_max_freq); + return true; + } + } + + return false; +} + +static unsigned int cflm_get_vol_matched_freq(unsigned int in_freq) +{ + int i; + unsigned int out_freq = in_freq; + + if (param.vbf_offset > cflm_vbf.count || param.vbf_offset < 0) { + pr_err("%s: bad condition(off(%d), cnt(%d))", + __func__, param.vbf_offset, cflm_vbf.count); + return out_freq; + } + + /* start from offset */ + for (i = param.vbf_offset; i < cflm_vbf.count; i++) { + if (cflm_vbf.table[PRIME_CPU][i] <= in_freq) { + out_freq = cflm_vbf.table[GOLD_CPU][i - param.vbf_offset]; + break; + } + } + + pr_debug("%s: in(%d), out(%d)\n", __func__, in_freq, out_freq); + + return out_freq; +} + +static int cflm_get_silver_boost(int freq) +{ + int i; + + for (i = 0; i < param.boost_map_size; i++) + if (freq >= param.silver_boost_map[i].in) + return param.silver_boost_map[i].out; + return freq * param.silver_divider; +} + +static int cflm_get_silver_limit(int freq) +{ + int i; + + /* prime limit condition */ + for (i = 0; i < param.limit_map_size; i++) + if (freq >= param.silver_limit_map[i].in) + return MIN(param.silver_limit_map[i].out, param.s_fmax); + + /* silver limit condition */ + return freq * param.silver_divider; +} + +static int cflm_adaptive_boost(int first_cpu, int min) +{ + struct cpufreq_policy *policy; + int cpu = 0; + int ret = 0; + int aboost_low; + + pr_debug("%s: cpu%d: %d\n", __func__, first_cpu, min); + + if (!param.ab_enabled) + return -EINVAL; + + if (!param.ab_table) + return -EINVAL; + + if (!param.ab_table[first_cpu].threshold) + return -EINVAL; + + policy = cpufreq_cpu_get(first_cpu); + if (!policy) { + pr_err("%s: no policy for cpu%d\n", __func__, first_cpu); + return -EFAULT; + } + + if (strcmp((policy->governor->name), "walt")) { + pr_err("%s: not supported gov(%s)\n", __func__, policy->governor->name); + return -EFAULT; + } + + if (min >= param.ab_table[first_cpu].threshold) + aboost_low = param.ab_table[first_cpu].high; + else + aboost_low = param.ab_table[first_cpu].low; + + if ((min > 0) && (min < aboost_low)) { + pr_err("%s: cpu%d boost min(%d) is lower than adaptive low(%d)\n", + __func__, first_cpu, min, aboost_low); + aboost_low = min; + } + + for_each_cpu(cpu, policy->related_cpus) { + if (min > 0) { + pr_debug("%s: set aboost: cpu%d: %d, %d\n", __func__, cpu, aboost_low, min); + ret = cpufreq_walt_set_adaptive_freq(cpu, aboost_low, min); + } else { + pr_debug("%s: clear aboost: cpu%d\n", __func__, cpu); + ret = cpufreq_walt_reset_adaptive_freq(cpu); + } + } + + cpufreq_cpu_put(policy); + + return ret; +} + +static void cflm_freq_decision(int type, int new_min, int new_max) +{ + int cpu = 0; + int s_min = param.s_fmin; + int s_max = param.s_fmax; + int g_min = param.g_fmin; + int g_max = param.g_fmax; + int p_min = param.p_fmin; + int p_max = param.p_fmax; + int t_max = param.t_fmax; + + bool need_update_user_max = false; + int new_user_max = FREQ_QOS_MAX_DEFAULT_VALUE; + + pr_info("%s: input: type(%d), min(%d), max(%d)\n", + __func__, type, new_min, new_max); + + /* update input freq */ + if (new_min != 0) { + freq_input[type].min = new_min; + if ((new_min == LIMIT_RELEASE || new_min == param.ltl_min_freq) && + freq_input[type].last_min_limit_time != 0) { + freq_input[type].time_in_min_limit += ktime_to_ms(ktime_get()- + freq_input[type].last_min_limit_time); + freq_input[type].last_min_limit_time = 0; + freq_input[type].boosted = NOT_BOOSTED; + pr_debug("%s: type(%d), released(%d)\n", __func__, type, freq_input[type].boosted); + } + if (new_min != LIMIT_RELEASE && new_min != param.ltl_min_freq && + freq_input[type].last_min_limit_time == 0) { + freq_input[type].last_min_limit_time = ktime_get(); + freq_input[type].boosted = BOOSTED; + pr_debug("%s: type(%d), boosted(%d)\n", __func__, type, freq_input[type].boosted); + } + } + + if (new_max != 0) { + freq_input[type].max = new_max; + if ((new_max == LIMIT_RELEASE || new_max == param.big_max_freq) && + freq_input[type].last_max_limit_time != 0) { + freq_input[type].time_in_max_limit += ktime_to_ms(ktime_get() - + freq_input[type].last_max_limit_time); + freq_input[type].last_max_limit_time = 0; + } + if (new_max != LIMIT_RELEASE && new_max != param.big_max_freq && + freq_input[type].last_max_limit_time == 0) { + freq_input[type].last_max_limit_time = ktime_get(); + } + } + + if (new_min > 0) { + if (new_min < param.ltl_min_freq) { + pr_err("%s: too low freq(%d), set to %d\n", + __func__, new_min, param.ltl_min_freq); + new_min = param.ltl_min_freq; + } + + pr_debug("%s: new_min=%d, ltl_max=%d, over_limit=%d\n", __func__, + new_min, param.ltl_max_freq, param.over_limit); + if ((type == CFLM_USERSPACE || type == CFLM_TOUCH) && + cflm_high_pri_min_lock_required()) { + if (freq_input[CFLM_USERSPACE].max > 0) { + need_update_user_max = true; + new_user_max = MAX((int)param.over_limit, freq_input[CFLM_USERSPACE].max); + pr_debug("%s: override new_max %d => %d, userspace_min=%d, touch_min=%d, ltl_max=%d\n", + __func__, freq_input[CFLM_USERSPACE].max, new_user_max, freq_input[CFLM_USERSPACE].min, + freq_input[CFLM_TOUCH].min, param.ltl_max_freq); + } + } + + /* boost @gold/prime */ + s_min = cflm_get_silver_boost(new_min); + if (new_min > param.ltl_max_freq) { + g_min = MIN(new_min, param.g_fmax); + p_min = MIN(new_min, param.p_fmax); + } else { + g_min = param.g_fmin; + p_min = param.p_fmin; + } + + if (cflm_adaptive_boost(param.s_first, s_min) < 0) + freq_qos_update_request(&min_req[param.s_first][type], s_min); /* prevent adaptive boost fail */ + + if (cflm_adaptive_boost(param.g_first, g_min) < 0) + freq_qos_update_request(&min_req[param.g_first][type], g_min); + + freq_qos_update_request(&min_req[param.p_first][type], p_min); + /* TEMP??: no boost for titanium + *if (param.titanium) + * freq_qos_update_request(&min_req[param.t_first][type], g_min); + */ + } else if (new_min == LIMIT_RELEASE) { + for_each_possible_cpu(cpu) { + freq_qos_update_request(&min_req[cpu][type], + FREQ_QOS_MIN_DEFAULT_VALUE); + } + + if (param.ab_enabled) { + int i; + int aggr_state = 0; + + for (i = 0; i < CFLM_MAX_ITEM; i++) + aggr_state += freq_input[i].boosted; + + if (aggr_state == 0) { + cflm_adaptive_boost(param.s_first, 0); + cflm_adaptive_boost(param.g_first, 0); + pr_debug("%s: aboost: clear\n", __func__); + } + } + + if ((type == CFLM_USERSPACE || type == CFLM_TOUCH) && + cflm_max_lock_need_restore()) { // if there is no high priority min lock and over limit is set + if (freq_input[CFLM_USERSPACE].max > 0) { + need_update_user_max = true; + new_user_max = freq_input[CFLM_USERSPACE].max; + pr_debug("%s: restore new_max => %d\n", + __func__, new_user_max); + } + } + } + + if (new_max > 0) { + if (new_max > param.big_max_freq) { + pr_err("%s: too high freq(%d), set to %d\n", + __func__, new_max, param.big_max_freq); + new_max = param.big_max_freq; + } + + if ((type == CFLM_USERSPACE) && // if userspace maxlock is being set + cflm_high_pri_min_lock_required()) { + need_update_user_max = true; + new_user_max = MAX((int)param.over_limit, freq_input[CFLM_USERSPACE].max); + pr_debug("%s: force up new_max %d => %d, userspace_min=%d, touch_min=%d, ltl_max=%d\n", + __func__, new_max, new_user_max, freq_input[CFLM_USERSPACE].min, + freq_input[CFLM_TOUCH].min, param.ltl_max_freq); + } + + s_max = cflm_get_silver_limit(new_max); + if (new_max < param.big_min_freq) { + /* if silver clock is limited as fmax, + * set promised clock for gold cluster + */ + if ((new_max == param.s_fmax / param.silver_divider) && (param.g_fmin_up > 0)) { + g_max = param.g_fmin_up; + t_max = param.g_fmin_up; + } else { + g_max = param.g_fmin; + t_max = param.t_fmin; + } + + p_max = param.p_fmin; + } else { + p_max = MIN(new_max, param.p_fmax); + g_max = MIN(new_max, param.g_fmax); + if (param.vol_based_clk == true && cflm_vbf.count > 0) + t_max = MIN(cflm_get_vol_matched_freq(p_max), param.t_fmax); + else + t_max = MIN(new_max, param.t_fmax); + } + + freq_qos_update_request(&max_req[param.s_first][type], s_max); + freq_qos_update_request(&max_req[param.g_first][type], g_max); + freq_qos_update_request(&max_req[param.p_first][type], p_max); + if (param.titanium) + freq_qos_update_request(&max_req[param.t_first][type], t_max); + } else if (new_max == LIMIT_RELEASE) { + for_each_possible_cpu(cpu) + freq_qos_update_request(&max_req[cpu][type], + FREQ_QOS_MAX_DEFAULT_VALUE); + } + + if ((freq_input[type].min <= (int)param.ltl_max_freq || new_user_max != (int)param.over_limit) && + freq_input[type].last_over_limit_time != 0) { + freq_input[type].time_in_over_limit += ktime_to_ms(ktime_get() - + freq_input[type].last_over_limit_time); + freq_input[type].last_over_limit_time = 0; + } + if (freq_input[type].min > (int)param.ltl_max_freq && new_user_max == (int)param.over_limit && + freq_input[type].last_over_limit_time == 0) { + freq_input[type].last_over_limit_time = ktime_get(); + } + + if (need_update_user_max) { + pr_debug("%s: update_user_max is true\n", __func__); + if (new_user_max > param.big_max_freq) { + pr_debug("%s: too high freq(%d), set to %d\n", + __func__, new_user_max, param.big_max_freq); + new_user_max = param.big_max_freq; + } + + s_max = cflm_get_silver_limit(new_user_max); + if (new_user_max < param.big_min_freq) { + /* if silver clock is limited as fmax, + * set promised clock for gold cluster + */ + if ((new_user_max == param.s_fmax / param.silver_divider) && (param.g_fmin_up > 0)) { + g_max = param.g_fmin_up; + t_max = param.g_fmin_up; /* use same freq with gold */ + } else { + g_max = param.g_fmin; + t_max = param.t_fmin; + } + + p_max = param.p_fmin; + } else { + p_max = MIN(new_user_max, param.p_fmax); + g_max = MIN(new_user_max, param.g_fmax); + if (param.vol_based_clk == true && cflm_vbf.count > 0) + t_max = MIN(cflm_get_vol_matched_freq(p_max), param.t_fmax); + else + t_max = MIN(new_user_max, param.t_fmax); + } + + pr_info("%s: freq_update_request : new userspace max %d %d %d %d\n", __func__, s_max, g_max, t_max, p_max); + freq_qos_update_request(&max_req[param.s_first][CFLM_USERSPACE], s_max); + freq_qos_update_request(&max_req[param.g_first][CFLM_USERSPACE], g_max); + freq_qos_update_request(&max_req[param.p_first][CFLM_USERSPACE], p_max); + if (param.titanium) + freq_qos_update_request(&max_req[param.t_first][CFLM_USERSPACE], g_max); + } + + cflm_update_boost(); + + cflm_current_qos(); +} + +static ssize_t cpufreq_table_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + ssize_t len = 0; + + len = cflm_get_table(buf); + + return len; +} + +static ssize_t cpufreq_max_limit_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return snprintf(buf, MAX_BUF_SIZE, "%d\n", param.max_limit_val); +} + +static ssize_t cpufreq_max_limit_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + int freq; + int ret = -EINVAL; + + ret = kstrtoint(buf, 10, &freq); + if (ret < 0) { + pr_err("%s: cflm: Invalid cpufreq format\n", __func__); + goto out; + } + + mutex_lock(&cflm_mutex); + + param.max_limit_val = freq; + cflm_freq_decision(CFLM_USERSPACE, 0, freq); + + mutex_unlock(&cflm_mutex); + ret = n; + +out: + return ret; +} + +static ssize_t cpufreq_min_limit_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return snprintf(buf, MAX_BUF_SIZE, "%d\n", param.min_limit_val); +} + +static ssize_t cpufreq_min_limit_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + int freq; + int ret = -EINVAL; + + ret = kstrtoint(buf, 10, &freq); + if (ret < 0) { + pr_err("%s: cflm: Invalid cpufreq format\n", __func__); + goto out; + } + + mutex_lock(&cflm_mutex); + + cflm_freq_decision(CFLM_USERSPACE, freq, 0); + param.min_limit_val = freq; + + mutex_unlock(&cflm_mutex); + ret = n; +out: + return ret; +} + +static ssize_t over_limit_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return snprintf(buf, MAX_BUF_SIZE, "%d\n", param.over_limit); +} + +static ssize_t over_limit_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + int freq; + int ret = -EINVAL; + + ret = kstrtoint(buf, 10, &freq); + if (ret < 0) { + pr_err("%s: cflm: Invalid cpufreq format\n", __func__); + goto out; + } + + mutex_lock(&cflm_mutex); + + if (param.over_limit != freq) { + param.over_limit = freq; + if ((int)param.max_limit_val > 0) + cflm_freq_decision(CFLM_USERSPACE, 0, param.max_limit_val); + } + + mutex_unlock(&cflm_mutex); + ret = n; +out: + return ret; +} + +static ssize_t limit_stat_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + + ssize_t len = 0; + int i, j = 0; + + mutex_lock(&cflm_mutex); + for (i = 0; i < CFLM_MAX_ITEM; i++) { + if (freq_input[i].last_min_limit_time != 0) { + freq_input[i].time_in_min_limit += ktime_to_ms(ktime_get() - + freq_input[i].last_min_limit_time); + freq_input[i].last_min_limit_time = ktime_get(); + } + + if (freq_input[i].last_max_limit_time != 0) { + freq_input[i].time_in_max_limit += ktime_to_ms(ktime_get() - + freq_input[i].last_max_limit_time); + freq_input[i].last_max_limit_time = ktime_get(); + } + + if (freq_input[i].last_over_limit_time != 0) { + freq_input[i].time_in_over_limit += ktime_to_ms(ktime_get() - + freq_input[i].last_over_limit_time); + freq_input[i].last_over_limit_time = ktime_get(); + } + } + + for (j = 0; j < CFLM_MAX_ITEM; j++) { + len += snprintf(buf + len, MAX_BUF_SIZE - len, "%llu %llu %llu\n", + freq_input[j].time_in_min_limit, freq_input[j].time_in_max_limit, + freq_input[j].time_in_over_limit); + } + + mutex_unlock(&cflm_mutex); + return len; +} + +static unsigned int cflm_get_table_freq(struct cpufreq_policy *policy, + unsigned int target_freq, unsigned int relation) +{ + unsigned int idx; + + target_freq = clamp_val(target_freq, policy->min, policy->max); + + if (!policy->freq_table) + return target_freq; + + idx = cpufreq_frequency_table_target(policy, target_freq, relation); + + return policy->freq_table[idx].frequency; +} + +static ssize_t vtable_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + ssize_t len = 0; + int i = 0; + struct cpufreq_policy *policy = cpufreq_cpu_get(param.g_first); + unsigned int virt_clk = 0; + + if (!cflm_vbf.count) + return len; + + if (param.vbf_offset > cflm_vbf.count) { + pr_err("%s: bad condition(off(%d), cnt(%d))", + __func__, param.vbf_offset, cflm_vbf.count); + return len; + } + + if (param.max_limit_val != LIMIT_RELEASE) + len += snprintf(buf + len, MAX_BUF_SIZE, "!!!) please, read table again when no limit state\n"); + + len += snprintf(buf + len, MAX_BUF_SIZE, "========================max===============================min================\n"); + len += snprintf(buf + len, MAX_BUF_SIZE, " virt | prime titan gold silver | prime titan gold silver\n"); + for (i = 0; i < param.freq_count; i++) { + virt_clk = param.unified_cpuftbl[i]; + + if (virt_clk > param.ltl_max_freq) { + len += snprintf(buf + len, MAX_BUF_SIZE, " %7u | %7u %7u %7u %7u | %7u %7u %7u %7u\n", + virt_clk, + + /* max = limit */ + virt_clk, + cflm_get_vol_matched_freq(virt_clk), + cflm_get_table_freq(policy, virt_clk, CPUFREQ_RELATION_H), + cflm_get_silver_limit(virt_clk), + + /* min = boost */ + virt_clk, + 0, + cflm_get_table_freq(policy, virt_clk, CPUFREQ_RELATION_L), + cflm_get_silver_boost(virt_clk)); + } else { + len += snprintf(buf + len, MAX_BUF_SIZE, " %7u | %7u %7u %7u %7u | %7u %7u %7u %7u\n", + virt_clk, + + /* max = limit */ + param.p_fmin, + param.t_fmin, + param.g_fmin, + cflm_get_silver_limit(virt_clk), + + /* min = boost */ + 0, + 0, + 0, + cflm_get_silver_boost(virt_clk)); + } + } + len += snprintf(buf + len, MAX_BUF_SIZE, "=============================================================================\n"); + + cpufreq_cpu_put(policy); + + pr_info("%s: %s\n", __func__, buf); + + return len; +} + +static ssize_t sched_boost_type_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return snprintf(buf, MAX_BUF_SIZE, "%d\n", param.sched_boost_type); +} + +static ssize_t sched_boost_type_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + int boost_type; + int ret = -EINVAL; + + ret = kstrtoint(buf, 10, &boost_type); + if (ret < 0) { + pr_err("%s: cflm: Invalid cpufreq format\n", __func__); + goto out; + } + + mutex_lock(&cflm_mutex); + + if ((param.sched_boost_enabled) && (param.sched_boost_type != boost_type)) { + pr_info("%s: sched boost is enabled(%d), reset(%d)\n", __func__, param.sched_boost_type, boost_type); + sched_set_boost(param.sched_boost_type * -1); + sched_set_boost(boost_type); + } + + param.sched_boost_type = boost_type; + pr_info("%s: sched boost type is changed to %d\n", __func__, param.sched_boost_type); + + mutex_unlock(&cflm_mutex); + ret = n; + +out: + return ret; +} + +/* sysfs in /sys/power */ +static struct kobj_attribute cpufreq_table = { + .attr = { + .name = "cpufreq_table", + .mode = 0444 + }, + .show = cpufreq_table_show, + .store = NULL, +}; + +static struct kobj_attribute cpufreq_min_limit = { + .attr = { + .name = "cpufreq_min_limit", + .mode = 0644 + }, + .show = cpufreq_min_limit_show, + .store = cpufreq_min_limit_store, +}; + +static struct kobj_attribute cpufreq_max_limit = { + .attr = { + .name = "cpufreq_max_limit", + .mode = 0644 + }, + .show = cpufreq_max_limit_show, + .store = cpufreq_max_limit_store, +}; + +static struct kobj_attribute over_limit = { + .attr = { + .name = "over_limit", + .mode = 0644 + }, + .show = over_limit_show, + .store = over_limit_store, +}; + +static struct kobj_attribute limit_stat = { + .attr = { + .name = "limit_stat", + .mode = 0644 + }, + .show = limit_stat_show, +}; + +static struct kobj_attribute vtable = { + .attr = { + .name = "vtable", + .mode = 0444 + }, + .show = vtable_show, + .store = NULL, +}; + +static struct kobj_attribute sched_boost_type = { + .attr = { + .name = "sched_boost_type", + .mode = 0644 + }, + .show = sched_boost_type_show, + .store = sched_boost_type_store, +}; + +int set_freq_limit(unsigned int id, unsigned int freq) +{ + if (lpcharge) { + pr_err("%s: not allowed in LPM\n", __func__); + return 0; + } + + mutex_lock(&cflm_mutex); + + pr_info("%s: cflm: id(%d) freq(%d)\n", __func__, (int)id, freq); + + cflm_freq_decision(id, freq, 0); + + mutex_unlock(&cflm_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(set_freq_limit); + +#define cflm_attr_rw(_name) \ +static struct kobj_attribute _name##_attr = \ +__ATTR(_name, 0644, show_##_name, store_##_name) + +#define show_one(file_name) \ +static ssize_t show_##file_name \ +(struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ +{ \ + return scnprintf(buf, PAGE_SIZE, "%u\n", param.file_name); \ +} + +#define store_one(file_name) \ +static ssize_t store_##file_name \ +(struct kobject *kobj, struct kobj_attribute *attr, \ +const char *buf, size_t count) \ +{ \ + int ret; \ + \ + ret = sscanf(buf, "%u", ¶m.file_name); \ + if (ret != 1) \ + return -EINVAL; \ + \ + return count; \ +} + +/* votlage based */ +show_one(vol_based_clk); +store_one(vol_based_clk); +cflm_attr_rw(vol_based_clk); +show_one(vbf_offset); +store_one(vbf_offset); +cflm_attr_rw(vbf_offset); + +/* adaptive boost */ +show_one(ab_enabled); +store_one(ab_enabled); +cflm_attr_rw(ab_enabled); + +static ssize_t show_cflm_info(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + ssize_t len = 0; + int i = 0; + + mutex_lock(&cflm_mutex); + + len += snprintf(buf, MAX_BUF_SIZE, "[basic info]\n"); + len += snprintf(buf + len, MAX_BUF_SIZE - len, + "real: silver(%d ~ %d), gold(%d ~ %d), prime(%d ~ %d)\n", + param.s_fmin, param.s_fmax, + param.g_fmin, param.g_fmax, + param.p_fmin, param.p_fmax); + + len += snprintf(buf + len, MAX_BUF_SIZE - len, + "virt: little(%d ~ %d), big(%d ~ %d)\n", + param.ltl_min_freq, param.ltl_max_freq, + param.big_min_freq, param.big_max_freq); + + len += snprintf(buf + len, MAX_BUF_SIZE - len, + "param: div(%d), sched boost(%d)\n", + param.silver_divider, param.sched_boost_type); + + len += snprintf(buf + len, MAX_BUF_SIZE - len, + "param: vbf(%d), offset(%d), aboost(%d)\n", + param.vol_based_clk, param.vbf_offset, param.ab_enabled); + + len += snprintf(buf + len, MAX_BUF_SIZE - len, "[requested info]\n"); + for (i = 0; i < CFLM_MAX_ITEM; i++) { + len += snprintf(buf + len, MAX_BUF_SIZE - len, + "requested: [%d] min(%d), max(%d)\n", + i, freq_input[i].min, freq_input[i].max); + } + + len += snprintf(buf + len, MAX_BUF_SIZE - len, "[aboost table]\n"); + if ((param.ab_enabled) && (param.ab_table)) { + for (i = 0; i < NUM_CPUS; i++) { + len += snprintf(buf + len, MAX_BUF_SIZE - len, "cpu%d: %d %d %d\n", + i, param.ab_table[i].threshold, + param.ab_table[i].high, param.ab_table[i].low); + } + } + + mutex_unlock(&cflm_mutex); + + return len; +} + +static struct kobj_attribute cflm_info = + __ATTR(info, 0444, show_cflm_info, NULL); + +static struct attribute *cflm_attributes[] = { + &cpufreq_table.attr, + &cpufreq_min_limit.attr, + &cpufreq_max_limit.attr, + &over_limit.attr, + &limit_stat.attr, + &cflm_info.attr, + &vtable.attr, + &sched_boost_type.attr, + &vol_based_clk_attr.attr, + &vbf_offset_attr.attr, + &ab_enabled_attr.attr, + NULL, +}; + +static struct attribute_group cflm_attr_group = { + .attrs = cflm_attributes, +}; + +#ifdef CONFIG_OF +static void cflm_parse_dt(struct platform_device *pdev) +{ + int size = 0; + + if (!pdev->dev.of_node) { + pr_info("%s: no device tree\n", __func__); + return; + } + + /* voltage based */ + param.vol_based_clk = of_property_read_bool(pdev->dev.of_node, "limit,vol_based_clk"); + of_property_read_u32(pdev->dev.of_node, "limit,vbf_offset", ¶m.vbf_offset); + pr_info("%s: param: voltage based clock: %s(offset %d)\n", + __func__, param.vol_based_clk ? "true" : "false", param.vbf_offset); + + /* boost table */ + of_get_property(pdev->dev.of_node, "limit,silver_boost_table", &size); + if (size) { + param.silver_boost_map = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); + of_property_read_u32_array(pdev->dev.of_node, "limit,silver_boost_table", + (u32 *)param.silver_boost_map, size / sizeof(u32)); + + param.boost_map_size = size / sizeof(*param.silver_boost_map); + } + pr_info("%s: param: boost map size(%d)\n", __func__, param.boost_map_size); + + /* limit table */ + size = 0; + of_get_property(pdev->dev.of_node, "limit,silver_limit_table", &size); + if (size) { + param.silver_limit_map = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); + of_property_read_u32_array(pdev->dev.of_node, "limit,silver_limit_table", + (u32 *)param.silver_limit_map, size / sizeof(u32)); + + param.limit_map_size = size / sizeof(*param.silver_limit_map); + } + pr_info("%s: param: limit map size(%d)\n", __func__, param.limit_map_size); + + /* adaptive boost */ + size = 0; + of_get_property(pdev->dev.of_node, "limit,ab_table", &size); + if (size) { + param.ab_table = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); + of_property_read_u32_array(pdev->dev.of_node, "limit,ab_table", + (u32 *)param.ab_table, size / sizeof(u32)); + + param.ab_enabled = 1; + pr_info("%s: param: aboost enabled(%d)\n", __func__, size); + } else { + param.ab_enabled = 0; + pr_info("%s: param: no aboost table(%d)\n", __func__, size); + } + + /* lowest freq */ + of_property_read_u32(pdev->dev.of_node, "limit,gold_fmin", ¶m.g_fmin); + of_property_read_u32(pdev->dev.of_node, "limit,titanium_fmin", ¶m.t_fmin); + of_property_read_u32(pdev->dev.of_node, "limit,prime_fmin", ¶m.p_fmin); + + /* etc */ + of_property_read_u32(pdev->dev.of_node, "limit,gold_fmin_up", ¶m.g_fmin_up); + of_property_read_u32(pdev->dev.of_node, "limit,little_max_freq", ¶m.ltl_max_freq); + of_property_read_u32(pdev->dev.of_node, "limit,big_min_freq", ¶m.big_min_freq); + + /* sm8650 4 cluster(silver, gold, titanium, prime) */ + param.titanium = of_property_read_bool(pdev->dev.of_node, "limit,support_titanium"); + + of_node_put(pdev->dev.of_node); + pr_info("%s: param: g_fmin_up(%d), ltl_max_freq(%d), big_min_freq(%d), titanium(%d)\n", __func__, + param.g_fmin_up, param.ltl_max_freq, param.big_min_freq, param.titanium); +}; +#endif + +int cflm_add_qos(void) +{ + struct cpufreq_policy *policy; + unsigned int i = 0; + unsigned int j = 0; + int ret = 0; + + for_each_possible_cpu(i) { + policy = cpufreq_cpu_get(i); + if (!policy) { + pr_err("no policy for cpu%d\n", i); + ret = -EPROBE_DEFER; + break; + } + + for (j = 0; j < CFLM_MAX_ITEM; j++) { + ret = freq_qos_add_request(&policy->constraints, + &min_req[i][j], + FREQ_QOS_MIN, policy->cpuinfo.min_freq); + if (ret < 0) { + pr_err("%s: failed to add min req(%d)\n", __func__, ret); + break; + } + cflm_req_init[i] |= BIT(j*2); + + ret = freq_qos_add_request(&policy->constraints, + &max_req[i][j], + FREQ_QOS_MAX, policy->cpuinfo.max_freq); + if (ret < 0) { + pr_err("%s: failed to add max req(%d)\n", __func__, ret); + break; + } + cflm_req_init[i] |= BIT(j*2+1); + } + if (ret < 0) { + cpufreq_cpu_put(policy); + break; + } + + if (i == param.s_first) { + if (!param.s_fmin) + param.s_fmin = policy->cpuinfo.min_freq; + param.s_fmax = policy->cpuinfo.max_freq; + } + if (i == param.g_first) { + if (!param.g_fmin) + param.g_fmin = policy->cpuinfo.min_freq; + param.g_fmax = policy->cpuinfo.max_freq; + } + if (i == param.p_first) { + if (!param.p_fmin) + param.p_fmin = policy->cpuinfo.min_freq; + param.p_fmax = policy->cpuinfo.max_freq; + } + if (i == param.t_first) { + if (!param.t_fmin) + param.t_fmin = policy->cpuinfo.min_freq; + param.t_fmax = policy->cpuinfo.max_freq; + } + + cflm_set_table(policy->cpu, policy->freq_table); + + cpufreq_cpu_put(policy); + } + + return ret; +} + +void cflm_remove_qos(void) +{ + unsigned int i = 0; + unsigned int j = 0; + int ret = 0; + + pr_info("%s\n", __func__); + for_each_possible_cpu(i) { + for (j = 0; j < CFLM_MAX_ITEM; j++) { + if (cflm_req_init[i] & BIT(j*2)) { + //pr_info("%s: try to remove min[%d][%d] req\n", __func__, i, j); + ret = freq_qos_remove_request(&min_req[i][j]); + if (ret < 0) + pr_err("%s: failed to remove min_req (%d)\n", __func__, ret); + } + if (cflm_req_init[i] & BIT(j*2+1)) { + //pr_info("%s: try to remove max[%d][%d] req\n", __func__, i, j); + ret = freq_qos_remove_request(&max_req[i][j]); + if (ret < 0) + pr_err("%s: failed to remove max_req (%d)\n", __func__, ret); + } + } + cflm_req_init[i] = 0U; + } +} + +int cflm_probe(struct platform_device *pdev) +{ + int ret; + + pr_info("%s\n", __func__); + + if (lpcharge) { + pr_info("%s: dummy for LPM\n", __func__); + + return 0; + } + +#ifdef CONFIG_OF + cflm_parse_dt(pdev); +#endif + + ret = cflm_add_qos(); + if (ret < 0) + goto policy_not_ready; + + cflm_kobj = kobject_create_and_add("cpufreq_limit", + &cpu_subsys.dev_root->kobj); + if (!cflm_kobj) { + pr_err("Unable to cread cflm_kobj\n"); + goto object_create_failed; + } + + ret = sysfs_create_group(cflm_kobj, &cflm_attr_group); + if (ret) { + pr_err("Unable to create cflm group\n"); + goto group_create_failed; + } + + pr_info("%s done\n", __func__); + return ret; + +group_create_failed: + kobject_put(cflm_kobj); +object_create_failed: + cflm_kobj = NULL; +policy_not_ready: + cflm_remove_qos(); + + return ret; +} + +static int cflm_remove(struct platform_device *pdev) +{ + pr_info("%s\n", __func__); + + if (!lpcharge && cflm_kobj) { + cflm_remove_qos(); + sysfs_remove_group(cflm_kobj, &cflm_attr_group); + kobject_put(cflm_kobj); + cflm_kobj = NULL; + } + + return 0; +} + +static const struct of_device_id cflm_match_table[] = { + { .compatible = "cpufreq_limit" }, + {} +}; + +static struct platform_driver cflm_driver = { + .driver = { + .name = "cpufreq_limit", + .of_match_table = cflm_match_table, + }, + .probe = cflm_probe, + .remove = cflm_remove, +}; + +static int __init cflm_init(void) +{ + return platform_driver_register(&cflm_driver); +} + +static void __exit cflm_exit(void) +{ + platform_driver_unregister(&cflm_driver); +} + +MODULE_AUTHOR("Sangyoung Son @@ -18,6 +18,16 @@ #include #include #include +#include + +#if IS_ENABLED(CONFIG_QCOM_LMH_STAT) +#include +#include +#endif + +#if IS_ENABLED(CONFIG_SEC_PM_LOG) +#include +#endif #define CREATE_TRACE_POINTS #include @@ -55,7 +65,6 @@ struct qcom_cpufreq_soc_data { u32 reg_current_vote; u32 reg_perf_state; u32 reg_cycle_cntr; - u32 lut_max_entries; u8 lut_row_size; bool accumulative_counter; bool turbo_ind_support; @@ -84,8 +93,152 @@ struct qcom_cpufreq_data { bool per_core_dcvs; unsigned long dcvsh_freq_limit; struct device_attribute freq_limit_attr; + +#if IS_ENABLED(CONFIG_QCOM_LMH_STAT) + unsigned long long last_time; + unsigned int state_num; + unsigned int max_state; + unsigned int last_index; + u64 *time_in_state; + unsigned int *freq_table; + struct device_attribute time_in_state_attr; +#endif + +#if IS_ENABLED(CONFIG_SEC_PM_LOG) + unsigned long lowest_freq; + bool limiting; + + ktime_t start_time; + ktime_t limited_time; + unsigned long accu_time; +#endif }; +#if IS_ENABLED(CONFIG_QCOM_LMH_STAT) +static int freq_table_get_index(struct qcom_cpufreq_data *data, unsigned int freq) +{ + int i; + + for (i = 0; i < data->max_state; i++) + if (data->freq_table[i] == freq) + return i; + + return -1; +} + +static int freq_table_highest_index(struct qcom_cpufreq_data *data) +{ + int i, high_idx = -1; + unsigned int high_freq = 0; + + if (data->max_state == 0) + return high_idx; + + for (i = 0; i < data->max_state; i++) { + if (data->freq_table[i] > high_freq) { + high_idx = i; + high_freq = data->freq_table[i]; + } + } + + return high_idx; +} + +static void throttle_stats_update(struct qcom_cpufreq_data *data, + unsigned long long time) +{ + unsigned long long cur_time = local_clock(); + + data->time_in_state[data->last_index] += cur_time - time; + data->last_time = cur_time; +} + +static void time_in_state_update(struct qcom_cpufreq_data *data, + unsigned long throttled_freq) +{ + int new_index, old_index; + + if (data->max_state == 0) + return; + + old_index = data->last_index; + new_index = freq_table_get_index(data, throttled_freq); + if (old_index == -1 || new_index == -1) + return; + + throttle_stats_update(data, data->last_time); + data->last_index = new_index; +} + +static ssize_t time_in_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct qcom_cpufreq_data *data = container_of(attr, struct qcom_cpufreq_data, + time_in_state_attr); + unsigned long long time; + ssize_t len = 0; + int i; + + if (data->max_state == 0) + return 0; + + for (i = 0; i < data->state_num; i++) { + time = data->time_in_state[i]; + if (i == data->last_index) + time += local_clock() - data->last_time; + + len += sprintf(buf + len, "%u %llu\n", + data->freq_table[i], div_u64(time, NSEC_PER_MSEC)); + } + + return len; +} + +static int time_in_state_attr_init(struct qcom_cpufreq_data *data, + struct device *cpu_dev) +{ + struct cpufreq_frequency_table *pos; + unsigned int i = 0, count; + unsigned int alloc_size; + + count = cpufreq_table_count_valid_entries(data->policy); + if (!count) + return -1; + + alloc_size = sizeof(u64) * count + sizeof(unsigned int) * count; + data->time_in_state = kzalloc(alloc_size, GFP_KERNEL); + if (!data->time_in_state) + return -1; + + data->freq_table = (unsigned int *)(data->time_in_state + count); + + cpufreq_for_each_valid_entry(pos, data->policy->freq_table) + if (freq_table_get_index(data, pos->frequency) == -1) + data->freq_table[i++] = pos->frequency; + data->state_num = i; + + sysfs_attr_init(&data->time_in_state_attr.attr); + data->time_in_state_attr.attr.name = "dcvsh_state_ms"; + data->time_in_state_attr.show = time_in_state_show; + data->time_in_state_attr.attr.mode = 0444; + if (device_create_file(cpu_dev, &data->time_in_state_attr)) { + kfree(data->time_in_state); + return -1; + } + + /* policy->cpuinfo.max_freq == 0 at this time */ + data->max_state = count; + data->last_index = freq_table_highest_index(data); + data->last_time = local_clock(); + + pr_err("%s : cpu[%d] max_state %lu, last_time %llu, last_index %d\n", + __func__, data->policy->cpu, + data->max_state, data->last_time, data->last_index); + + return 0; +} +#endif + static unsigned long cpu_hw_rate, xo_rate; static bool icc_scaling_enabled; @@ -191,6 +344,8 @@ u64 qcom_cpufreq_get_cpu_cycle_counter(int cpu) } EXPORT_SYMBOL(qcom_cpufreq_get_cpu_cycle_counter); +static void __cpufreq_hw_target_index_call_notifier_chain(struct cpufreq_policy *policy, unsigned int index); + static int qcom_cpufreq_hw_target_index(struct cpufreq_policy *policy, unsigned int index) { @@ -199,6 +354,8 @@ static int qcom_cpufreq_hw_target_index(struct cpufreq_policy *policy, unsigned long freq = policy->freq_table[index].frequency; unsigned int i; + __cpufreq_hw_target_index_call_notifier_chain(policy, index); + if (soc_data->perf_lock_support) { if (data->pdmem_base) writel_relaxed(index, data->pdmem_base); @@ -244,7 +401,7 @@ static unsigned int qcom_cpufreq_get_freq(unsigned int cpu) soc_data = data->soc_data; index = readl_relaxed(data->base + soc_data->reg_perf_state); - index = min(index, soc_data->lut_max_entries - 1); + index = min(index, LUT_MAX_ENTRIES - 1); return policy->freq_table[index].frequency; } @@ -296,7 +453,7 @@ static int qcom_cpufreq_hw_read_lut(struct device *cpu_dev, struct qcom_cpufreq_data *drv_data = policy->driver_data; const struct qcom_cpufreq_soc_data *soc_data = drv_data->soc_data; - table = kcalloc(soc_data->lut_max_entries + 1, sizeof(*table), GFP_KERNEL); + table = kcalloc(LUT_MAX_ENTRIES + 1, sizeof(*table), GFP_KERNEL); if (!table) return -ENOMEM; @@ -321,7 +478,7 @@ static int qcom_cpufreq_hw_read_lut(struct device *cpu_dev, icc_scaling_enabled = false; } - for (i = 0; i < soc_data->lut_max_entries; i++) { + for (i = 0; i < LUT_MAX_ENTRIES; i++) { data = readl_relaxed(drv_data->base + soc_data->reg_freq_lut + i * soc_data->lut_row_size); src = FIELD_GET(LUT_SRC, data); @@ -385,7 +542,7 @@ static int qcom_cpufreq_hw_read_lut(struct device *cpu_dev, table[i].frequency = CPUFREQ_TABLE_END; policy->freq_table = table; - for (i = 0; i < soc_data->lut_max_entries && table[i].frequency != CPUFREQ_TABLE_END; i++) { + for (i = 0; i < LUT_MAX_ENTRIES && table[i].frequency != CPUFREQ_TABLE_END; i++) { if (table[i].flags == CPUFREQ_BOOST_FREQ) break; @@ -435,6 +592,8 @@ static void qcom_lmh_dcvs_notify(struct qcom_cpufreq_data *data) struct device *dev = get_cpu_device(cpu); unsigned long freq_hz, throttled_freq, thermal_pressure; struct dev_pm_opp *opp; + unsigned long trace_freq; + char lmh_debug[8] = {0}; if (!dev) return; @@ -454,6 +613,7 @@ static void qcom_lmh_dcvs_notify(struct qcom_cpufreq_data *data) else dev_pm_opp_put(opp); + trace_freq = throttled_freq = thermal_pressure = freq_hz / HZ_PER_KHZ; /* @@ -469,10 +629,22 @@ static void qcom_lmh_dcvs_notify(struct qcom_cpufreq_data *data) * for, then stop polling and switch back to interrupt mechanism. */ if (throttled_freq >= qcom_cpufreq_get_freq(cpu)) { + trace_freq = thermal_pressure = policy->cpuinfo.max_freq; enable_irq(data->throttle_irq); trace_dcvsh_throttle(cpu, 0); +#if IS_ENABLED(CONFIG_SEC_PM_LOG) + if (data->limiting == true) { + data->limiting = false; + data->limited_time = (ktime_get() - data->start_time); + data->accu_time += ktime_to_ms(data->limited_time); + ss_thermal_print("Fin. lmh cpu%d, lowest %lu, f_lim %lu, dcvsh %lu, accu %d\n", + cpu, (data->lowest_freq / 1000), (throttled_freq / 1000), + (qcom_cpufreq_hw_get(cpu) / 1000), data->accu_time); + data->lowest_freq = UINT_MAX; + } +#endif } else { /* * If the frequency is at least the highest, non-boost @@ -483,6 +655,17 @@ static void qcom_lmh_dcvs_notify(struct qcom_cpufreq_data *data) if (throttled_freq >= data->last_non_boost_freq) thermal_pressure = policy->cpuinfo.max_freq; +#if IS_ENABLED(CONFIG_SEC_PM_LOG) + if (data->limiting == false) { + ss_thermal_print("Start lmh cpu%d @%lu\n", cpu, (thermal_pressure / 1000)); + data->lowest_freq = thermal_pressure; + data->limiting = true; + data->start_time = ktime_get(); + } else { + if (thermal_pressure < data->lowest_freq) + data->lowest_freq = thermal_pressure; + } +#endif mod_delayed_work(system_highpri_wq, &data->throttle_work, msecs_to_jiffies(10)); } @@ -493,6 +676,13 @@ static void qcom_lmh_dcvs_notify(struct qcom_cpufreq_data *data) arch_update_thermal_pressure(policy->related_cpus, thermal_pressure); data->dcvsh_freq_limit = thermal_pressure; + snprintf(lmh_debug, sizeof(lmh_debug), "lmh_%d", cpu); + trace_clock_set_rate(lmh_debug, trace_freq, raw_smp_processor_id()); + +#if IS_ENABLED(CONFIG_QCOM_LMH_STAT) + time_in_state_update(data, trace_freq); +#endif + out: mutex_unlock(&data->throttle_lock); } @@ -531,7 +721,6 @@ static const struct qcom_cpufreq_soc_data qcom_soc_data = { .reg_perf_state = 0x920, .reg_cycle_cntr = 0x9c0, .lut_row_size = 32, - .lut_max_entries = LUT_MAX_ENTRIES, .accumulative_counter = false, .turbo_ind_support = true, }; @@ -546,7 +735,6 @@ static const struct qcom_cpufreq_soc_data epss_soc_data = { .reg_perf_state = 0x320, .reg_cycle_cntr = 0x3c4, .lut_row_size = 4, - .lut_max_entries = LUT_MAX_ENTRIES, .accumulative_counter = true, .turbo_ind_support = false, .perf_lock_support = false, @@ -562,39 +750,6 @@ static const struct qcom_cpufreq_soc_data epss_pdmem_soc_data = { .reg_perf_state = 0x320, .reg_cycle_cntr = 0x3c4, .lut_row_size = 4, - .lut_max_entries = LUT_MAX_ENTRIES, - .accumulative_counter = true, - .turbo_ind_support = false, - .perf_lock_support = true, -}; - -static const struct qcom_cpufreq_soc_data rimps_soc_data = { - .reg_enable = 0x0, - .reg_domain_state = 0x20, - .reg_dcvs_ctrl = 0xb0, - .reg_freq_lut = 0x100, - .reg_volt_lut = 0x200, - .reg_intr_clr = 0x308, - .reg_perf_state = 0x320, - .reg_cycle_cntr = 0x3c4, - .lut_row_size = 4, - .lut_max_entries = 12, - .accumulative_counter = true, - .turbo_ind_support = false, - .perf_lock_support = false, -}; - -static const struct qcom_cpufreq_soc_data rimps_pdmem_soc_data = { - .reg_enable = 0x0, - .reg_domain_state = 0x20, - .reg_dcvs_ctrl = 0xb0, - .reg_freq_lut = 0x100, - .reg_volt_lut = 0x200, - .reg_intr_clr = 0x308, - .reg_perf_state = 0x320, - .reg_cycle_cntr = 0x3c4, - .lut_row_size = 4, - .lut_max_entries = 12, .accumulative_counter = true, .turbo_ind_support = false, .perf_lock_support = true, @@ -604,8 +759,6 @@ static const struct of_device_id qcom_cpufreq_hw_match[] = { { .compatible = "qcom,cpufreq-hw", .data = &qcom_soc_data }, { .compatible = "qcom,cpufreq-epss", .data = &epss_soc_data }, { .compatible = "qcom,cpufreq-epss-pdmem", .data = &epss_pdmem_soc_data }, - { .compatible = "qcom,cpufreq-rimps", .data = &rimps_soc_data }, - { .compatible = "qcom,cpufreq-rimps-pdmem", .data = &rimps_pdmem_soc_data }, {} }; MODULE_DEVICE_TABLE(of, qcom_cpufreq_hw_match); @@ -653,6 +806,15 @@ static int qcom_cpufreq_hw_lmh_init(struct cpufreq_policy *policy, int index, data->dcvsh_freq_limit = U32_MAX; device_create_file(cpu_dev, &data->freq_limit_attr); +#if IS_ENABLED(CONFIG_SEC_PM_LOG) + data->accu_time = 0; +#endif + +#if IS_ENABLED(CONFIG_QCOM_LMH_STAT) + if (time_in_state_attr_init(data, cpu_dev)) + pr_err("QCOM LMH clock state init error\n"); +#endif + return 0; } @@ -957,3 +1119,26 @@ module_exit(qcom_cpufreq_hw_exit); MODULE_DESCRIPTION("QCOM CPUFREQ HW Driver"); MODULE_LICENSE("GPL v2"); + +#if IS_ENABLED(CONFIG_SEC_QC_SMEM) +static ATOMIC_NOTIFIER_HEAD(target_index_notifier_list); + +int qcom_cpufreq_hw_target_index_register_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_register(&target_index_notifier_list, nb); +} +EXPORT_SYMBOL(qcom_cpufreq_hw_target_index_register_notifier); + +int qcom_cpufreq_hw_target_index_unregister_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_unregister(&target_index_notifier_list, nb); +} +EXPORT_SYMBOL(qcom_cpufreq_hw_target_index_unregister_notifier); + +static void __cpufreq_hw_target_index_call_notifier_chain(struct cpufreq_policy *policy, unsigned int index) +{ + atomic_notifier_call_chain(&target_index_notifier_list, index, policy); +} +#else +static void __cpufreq_hw_target_index_call_notifier_chain(struct cpufreq_policy *policy, unsigned int index) {} +#endif diff --git a/drivers/cpuidle/governors/Makefile b/drivers/cpuidle/governors/Makefile index 663620853fd8..ca96bbb58b48 100644 --- a/drivers/cpuidle/governors/Makefile +++ b/drivers/cpuidle/governors/Makefile @@ -14,5 +14,6 @@ obj-$(CONFIG_CPU_IDLE_GOV_QCOM_LPM) += qcom_lpm.o qcom_lpm-y += qcom-lpm.o qcom_lpm-y += qcom-cluster-lpm.o qcom_lpm-y += qcom-lpm-sysfs.o +qcom_lpm-$(CONFIG_SEC_QC_QCOM_WDT_CORE) += qcom-lpm-sec-extra.o obj-$(CONFIG_CPU_IDLE_GOV_TEO) += teo.o obj-$(CONFIG_CPU_IDLE_GOV_HALTPOLL) += haltpoll.o diff --git a/drivers/cpuidle/governors/qcom-lpm-sec-extra.c b/drivers/cpuidle/governors/qcom-lpm-sec-extra.c new file mode 100644 index 000000000000..3bd7830e1f89 --- /dev/null +++ b/drivers/cpuidle/governors/qcom-lpm-sec-extra.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2020-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include + +#include "qcom-lpm.h" + +#if IS_ENABLED(CONFIG_SEC_QC_QCOM_WDT_CORE) +void qcom_lpm_set_sleep_disabled(void) +{ + sleep_disabled = true; +} +EXPORT_SYMBOL(qcom_lpm_set_sleep_disabled); + +void qcom_lpm_unset_sleep_disabled(void) +{ + sleep_disabled = false; +} +EXPORT_SYMBOL(qcom_lpm_unset_sleep_disabled); +#endif diff --git a/drivers/dma-buf/heaps/Kconfig b/drivers/dma-buf/heaps/Kconfig index d7e0e9e34eee..da0868034775 100644 --- a/drivers/dma-buf/heaps/Kconfig +++ b/drivers/dma-buf/heaps/Kconfig @@ -126,3 +126,11 @@ config QCOM_DMABUF_HEAPS_SYSTEM_MOVABLE lend memory from the movable zone. If in doubt, say N here. +config RBIN + bool "DMA-BUF RBIN Samsung Heap" + depends on DMABUF_HEAPS && CLEANCACHE + default m + help + Choose this option to enable dma-buf rbin heap for samsung. + This heap supports both dmabuf allocation for camera and + operations for cleancache backend. diff --git a/drivers/dma-buf/heaps/Makefile b/drivers/dma-buf/heaps/Makefile index 081cbc6c0020..7e081d170062 100644 --- a/drivers/dma-buf/heaps/Makefile +++ b/drivers/dma-buf/heaps/Makefile @@ -17,3 +17,4 @@ qcom_dma_heaps-$(CONFIG_QCOM_DMABUF_HEAPS_BITSTREAM_CONTIG) += qcom_bitstream_co qcom_dma_heaps-$(CONFIG_QCOM_DMABUF_HEAPS_UBWCP) += qcom_ubwcp_heap.o qcom_dma_heaps-$(CONFIG_QCOM_DMABUF_HEAPS_TUI_CARVEOUT) += qcom_tui_carveout_heap.o qcom_dma_heaps-$(CONFIG_QCOM_DMABUF_HEAPS_SYSTEM_MOVABLE) += qcom_system_movable_heap.o +qcom_dma_heaps-$(CONFIG_RBIN) += rbin_heap.o rbincache.o rbinregion.o diff --git a/drivers/dma-buf/heaps/qcom_dma_heap.c b/drivers/dma-buf/heaps/qcom_dma_heap.c index 758f2a68c9a4..c22c9fc506f9 100644 --- a/drivers/dma-buf/heaps/qcom_dma_heap.c +++ b/drivers/dma-buf/heaps/qcom_dma_heap.c @@ -27,6 +27,10 @@ #define FOPS_INIT_VAL ERR_PTR(-EINVAL) static const struct file_operations *dma_buf_cached_fops; +#if defined(CONFIG_RBIN) +int add_rbin_heap(struct platform_heap *heap_data); +#endif + static int qcom_dma_heap_probe(struct platform_device *pdev) { int ret = 0; diff --git a/drivers/dma-buf/heaps/qcom_dt_parser.c b/drivers/dma-buf/heaps/qcom_dt_parser.c index 521981c4398b..d0fde2ddbbd6 100644 --- a/drivers/dma-buf/heaps/qcom_dt_parser.c +++ b/drivers/dma-buf/heaps/qcom_dt_parser.c @@ -112,6 +112,15 @@ static int heap_dt_init(struct device_node *mem_node, } heap->is_nomap = of_property_read_bool(mem_node, "no-map"); +#if defined(CONFIG_RBIN) + if (strncmp(rmem->name, "rbin", 4) == 0) { + if (!heap->base && !heap->size && rmem->base && rmem->size) { + heap->base = rmem->base; + heap->size = rmem->size; + } + } +#endif + return ret; } diff --git a/drivers/dma-buf/heaps/qcom_system_heap.c b/drivers/dma-buf/heaps/qcom_system_heap.c index 1eeeabe82184..316190eb953f 100644 --- a/drivers/dma-buf/heaps/qcom_system_heap.c +++ b/drivers/dma-buf/heaps/qcom_system_heap.c @@ -53,7 +53,7 @@ #include #include #include -#include +#include #include "qcom_dma_heap_secure_utils.h" #include "qcom_dynamic_page_pool.h" @@ -388,6 +388,7 @@ static void system_heap_buf_free(struct deferred_freelist_item *item, } } } + atomic_long_sub(buffer->len, &sys_heap->total_bytes); sg_free_table(table); kfree(buffer); } @@ -432,8 +433,6 @@ struct page *qcom_sys_heap_alloc_largest_available(struct dynamic_page_pool **po if (dynamic_pool_needs_refill(pools[i])) wake_up_process(pools[i]->refill_worker); - set_page_owner(page, pools[i]->order, GFP_KERNEL); - return page; } return NULL; @@ -525,6 +524,7 @@ static struct dma_buf *system_heap_allocate(struct dma_heap *heap, unsigned long fd_flags, unsigned long heap_flags) { + struct qcom_system_heap *sys_heap; struct qcom_sg_buffer *buffer; DEFINE_DMA_BUF_EXPORT_INFO(exp_info); struct dma_buf *dmabuf; @@ -534,6 +534,8 @@ static struct dma_buf *system_heap_allocate(struct dma_heap *heap, if (!buffer) return ERR_PTR(-ENOMEM); + sys_heap = dma_heap_get_drvdata(heap); + ret = system_qcom_sg_buffer_alloc(heap, buffer, len, false); if (ret) goto free_buf_struct; @@ -555,6 +557,7 @@ static struct dma_buf *system_heap_allocate(struct dma_heap *heap, goto free_vmperm; } + atomic_long_add(buffer->len, &sys_heap->total_bytes); return dmabuf; free_vmperm: @@ -574,6 +577,9 @@ static long get_pool_size_bytes(struct dma_heap *heap) int i; struct qcom_system_heap *sys_heap = dma_heap_get_drvdata(heap); + if (!strncmp(dma_heap_get_name(heap), "system", 6)) + return 0; + for (i = 0; i < NUM_ORDERS; i++) total_size += dynamic_page_pool_total(sys_heap->pool_list[i], true); @@ -585,6 +591,42 @@ static const struct dma_heap_ops system_heap_ops = { .get_pool_size = get_pool_size_bytes, }; +static long get_system_heap_total_kbytes(struct dma_heap *heap) +{ + struct qcom_system_heap *sys_heap; + + if (!heap) + return 0; + + sys_heap = dma_heap_get_drvdata(heap); + if (!sys_heap) + return 0; + + return atomic_long_read(&sys_heap->total_bytes) >> 10; +} + +static void qcom_system_heap_show_mem(void *data, unsigned int filter, nodemask_t *nodemask) +{ + struct dma_heap *heap = (struct dma_heap *)data; + long total_kbytes = get_system_heap_total_kbytes(heap); + + if (total_kbytes == 0) + return; + + pr_info("%s: %ld kB\n", dma_heap_get_name(heap), total_kbytes); +} + +static void qcom_system_heap_meminfo(void *data, struct seq_file *m) +{ + struct dma_heap *heap = (struct dma_heap *)data; + long total_kbytes = get_system_heap_total_kbytes(heap); + + if (total_kbytes == 0) + return; + + show_val_meminfo(m, dma_heap_get_name(heap), total_kbytes); +} + void qcom_system_heap_create(const char *name, const char *system_alias, bool uncached) { struct dma_heap_export_info exp_info; @@ -645,6 +687,8 @@ void qcom_system_heap_create(const char *name, const char *system_alias, bool un pr_info("%s: DMA-BUF Heap: Created '%s'\n", __func__, system_alias); } + register_trace_android_vh_show_mem(qcom_system_heap_show_mem, (void *)heap); + register_trace_android_vh_meminfo_proc_show(qcom_system_heap_meminfo, (void *)heap); return; stop_worker: diff --git a/drivers/dma-buf/heaps/qcom_system_heap.h b/drivers/dma-buf/heaps/qcom_system_heap.h index 551cd46bfe59..77c53d22c318 100644 --- a/drivers/dma-buf/heaps/qcom_system_heap.h +++ b/drivers/dma-buf/heaps/qcom_system_heap.h @@ -16,6 +16,7 @@ struct qcom_system_heap { int uncached; struct dynamic_page_pool **pool_list; + atomic_long_t total_bytes; }; #ifdef CONFIG_QCOM_DMABUF_HEAPS_SYSTEM diff --git a/drivers/dma-buf/heaps/rbin_heap.c b/drivers/dma-buf/heaps/rbin_heap.c new file mode 100644 index 000000000000..1483328a7fc2 --- /dev/null +++ b/drivers/dma-buf/heaps/rbin_heap.c @@ -0,0 +1,718 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * DMABUF Rbin heap exporter for Samsung + * + * Copyright (c) 2021 Samsung Electronics Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rbinregion.h" +#include "deferred-free-helper.h" + +#include "qcom_dt_parser.h" +#include "qcom_sg_ops.h" +/* page types we track in the pool */ +enum { + POOL_LOWPAGE, /* Clean lowmem pages */ + POOL_HIGHPAGE, /* Clean highmem pages */ + + POOL_TYPE_SIZE, +}; + +/** + * struct rbin_dmabuf_page_pool - pagepool struct + * @count[]: array of number of pages of that type in the pool + * @items[]: array of list of pages of the specific type + * @lock: lock protecting this struct and especially the count + * item list + * @gfp_mask: gfp_mask to use from alloc + * @order: order of pages in the pool + * @list: list node for list of pools + * + * Allows you to keep a pool of pre allocated pages to use + */ +struct rbin_dmabuf_page_pool { + int count[POOL_TYPE_SIZE]; + struct list_head items[POOL_TYPE_SIZE]; + spinlock_t lock; + gfp_t gfp_mask; + unsigned int order; + struct list_head list; +}; + +#define RBINHEAP_PREFIX "[RBIN-HEAP] " +#define perrfn(format, arg...) \ + pr_err(RBINHEAP_PREFIX "%s: " format "\n", __func__, ##arg) + +#define perrdev(dev, format, arg...) \ + dev_err(dev, RBINHEAP_PREFIX format "\n", ##arg) + +static struct dma_heap *rbin_cached_dma_heap; +static struct dma_heap *rbin_uncached_dma_heap; + +static const unsigned int orders[] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; +#define NUM_ORDERS ARRAY_SIZE(orders) + +static int order_to_index(unsigned int order) +{ + int i; + + for (i = 0; i < NUM_ORDERS; i++) + if (order == orders[i]) + return i; + BUG(); + return -1; +} + +struct rbin_heap { + struct task_struct *task; + struct task_struct *task_shrink; + bool task_run; + bool shrink_run; + wait_queue_head_t waitqueue; + unsigned long count; + struct rbin_dmabuf_page_pool *pools[NUM_ORDERS]; +}; + +static void rbin_page_pool_add(struct rbin_dmabuf_page_pool *pool, struct page *page) +{ + int index; + + if (PageHighMem(page)) + index = POOL_HIGHPAGE; + else + index = POOL_LOWPAGE; + + spin_lock(&pool->lock); + list_add_tail(&page->lru, &pool->items[index]); + pool->count[index]++; + spin_unlock(&pool->lock); +} + +static struct page *rbin_page_pool_remove(struct rbin_dmabuf_page_pool *pool, int index) +{ + struct page *page; + + spin_lock(&pool->lock); + page = list_first_entry_or_null(&pool->items[index], struct page, lru); + if (page) { + pool->count[index]--; + list_del(&page->lru); + } + spin_unlock(&pool->lock); + + return page; +} + +static struct page *rbin_page_pool_fetch(struct rbin_dmabuf_page_pool *pool) +{ + struct page *page = NULL; + + page = rbin_page_pool_remove(pool, POOL_HIGHPAGE); + if (!page) + page = rbin_page_pool_remove(pool, POOL_LOWPAGE); + + return page; +} + +static struct rbin_dmabuf_page_pool *rbin_page_pool_create(gfp_t gfp_mask, unsigned int order) +{ + struct rbin_dmabuf_page_pool *pool = kmalloc(sizeof(*pool), GFP_KERNEL); + int i; + + if (!pool) + return NULL; + + for (i = 0; i < POOL_TYPE_SIZE; i++) { + pool->count[i] = 0; + INIT_LIST_HEAD(&pool->items[i]); + } + pool->gfp_mask = gfp_mask | __GFP_COMP; + pool->order = order; + spin_lock_init(&pool->lock); + + return pool; +} + +static void rbin_page_pool_free(struct rbin_dmabuf_page_pool *pool, struct page *page) +{ + rbin_page_pool_add(pool, page); +} + +static struct page *alloc_rbin_page(unsigned long size, unsigned long last_size) +{ + struct page *page = ERR_PTR(-ENOMEM); + phys_addr_t paddr = -ENOMEM; + void *addr; + int order; + + order = min(get_order(last_size), get_order(size)); + for (; order >= 0; order--) { + size = min_t(unsigned long, size, PAGE_SIZE << order); + paddr = dmabuf_rbin_allocate(size); + if (paddr == -ENOMEM) + continue; + if (paddr == -EBUSY) + page = ERR_PTR(-EBUSY); + break; + } + + if (!IS_ERR_VALUE(paddr)) { + page = phys_to_page(paddr); + INIT_LIST_HEAD(&page->lru); + addr = page_address(page); + memset(addr, 0, size); + set_page_private(page, size); + } + return page; +} + +static inline void do_expand(struct rbin_heap *rbin_heap, + struct page *page, unsigned int nr_pages) +{ + unsigned int rem_nr_pages; + unsigned int order; + unsigned int total_nr_pages; + unsigned int free_nr_page; + struct page *free_page; + struct rbin_dmabuf_page_pool *pool; + + total_nr_pages = page_private(page) >> PAGE_SHIFT; + rem_nr_pages = total_nr_pages - nr_pages; + free_page = page + total_nr_pages; + + while (rem_nr_pages) { + order = ilog2(rem_nr_pages); + free_nr_page = 1 << order; + free_page -= free_nr_page; + set_page_private(free_page, free_nr_page << PAGE_SHIFT); + pool = rbin_heap->pools[order_to_index(order)]; + rbin_page_pool_free(pool, free_page); + rem_nr_pages -= free_nr_page; + } + set_page_private(page, nr_pages << PAGE_SHIFT); +} + +static struct page *alloc_rbin_page_from_pool(struct rbin_heap *rbin_heap, + unsigned long size) +{ + struct page *page = NULL; + unsigned int size_order = get_order(size); + unsigned int nr_pages = size >> PAGE_SHIFT; + int i; + + /* try the same or higher order */ + for (i = NUM_ORDERS - 1; i >= 0; i--) { + if (orders[i] < size_order) + continue; + page = rbin_page_pool_fetch(rbin_heap->pools[i]); + if (!page) + continue; + if (nr_pages < (1 << orders[i])) + do_expand(rbin_heap, page, nr_pages); + goto done; + } + + /* try lower order */ + for (i = 0; i < NUM_ORDERS; i++) { + if (orders[i] >= size_order) + continue; + page = rbin_page_pool_fetch(rbin_heap->pools[i]); + if (!page) + continue; + goto done; + } +done: + if (page) + atomic_sub(page_private(page) >> PAGE_SHIFT, &rbin_pool_pages); + return page; +} + +static void rbin_heap_free(struct qcom_sg_buffer *buffer) +{ + struct sg_table *table = &buffer->sg_table; + struct scatterlist *sg; + struct page *page; + int i; + + for_each_sg(table->sgl, sg, table->nents, i) { + page = sg_page(sg); + dmabuf_rbin_free(page_to_phys(page), page_private(page)); + } + atomic_sub(buffer->len >> PAGE_SHIFT, &rbin_allocated_pages); + sg_free_table(table); + kfree(buffer); +} + +static struct dma_buf *rbin_heap_allocate(struct dma_heap *heap, unsigned long len, + unsigned long fd_flags, unsigned long heap_flags, + bool uncached) +{ + struct rbin_heap *rbin_heap = dma_heap_get_drvdata(heap); + struct qcom_sg_buffer *buffer; + DEFINE_DMA_BUF_EXPORT_INFO(exp_info); + unsigned long size_remain; + unsigned long last_size; + unsigned long nr_free; + struct dma_buf *dmabuf; + struct sg_table *table; + struct scatterlist *sg; + struct list_head pages; + struct page *page, *tmp_page; + int i = 0; + int ret = -ENOMEM; + + size_remain = last_size = PAGE_ALIGN(len); + nr_free = rbin_heap->count - atomic_read(&rbin_allocated_pages); + if (size_remain > nr_free << PAGE_SHIFT) + return ERR_PTR(ret); + + buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); + if (!buffer) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&buffer->attachments); + mutex_init(&buffer->lock); + buffer->heap = heap; + buffer->len = len; + buffer->uncached = uncached; + buffer->free = rbin_heap_free; + + INIT_LIST_HEAD(&pages); + while (size_remain > 0) { + /* + * Avoid trying to allocate memory if the process + * has been killed by SIGKILL + */ + if (fatal_signal_pending(current)) { + perrfn("Fatal signal pending pid #%d", current->pid); + ret = -EINTR; + goto free_buffer; + } + + if (atomic_read(&rbin_pool_pages)) { + page = alloc_rbin_page_from_pool(rbin_heap, size_remain); + if (page) + goto got_pg; + } + + page = alloc_rbin_page(size_remain, last_size); + if (IS_ERR(page)) + goto free_buffer; + else + last_size = page_private(page); +got_pg: + list_add_tail(&page->lru, &pages); + size_remain -= page_private(page); + i++; + } + + table = &buffer->sg_table; + if (sg_alloc_table(table, i, GFP_KERNEL)) { + ret = PTR_ERR(buffer); + perrfn("sg_alloc_table failed %d\n", ret); + goto free_buffer; + } + + sg = table->sgl; + list_for_each_entry_safe(page, tmp_page, &pages, lru) { + sg_set_page(sg, page, page_private(page), 0); + sg = sg_next(sg); + list_del(&page->lru); + } + + /* + * For uncached buffers, we need to initially flush cpu cache, since + * the __GFP_ZERO on the allocation means the zeroing was done by the + * cpu and thus it is likely cached. Map (and implicitly flush) and + * unmap it now so we don't get corruption later on. + */ + if (buffer->uncached) { + dma_map_sgtable(dma_heap_get_dev(heap), table, DMA_BIDIRECTIONAL, 0); + dma_unmap_sgtable(dma_heap_get_dev(heap), table, DMA_BIDIRECTIONAL, 0); + } + + buffer->vmperm = mem_buf_vmperm_alloc(table); + + if (IS_ERR(buffer->vmperm)) { + ret = PTR_ERR(buffer->vmperm); + perrfn("vmperm error %d\n", ret); + goto free_sg; + } + + /* create the dmabuf */ + exp_info.exp_name = dma_heap_get_name(heap); + exp_info.size = buffer->len; + exp_info.flags = fd_flags; + exp_info.priv = buffer; + dmabuf = mem_buf_dma_buf_export(&exp_info, &qcom_sg_buf_ops); + + if (IS_ERR(dmabuf)) { + ret = PTR_ERR(dmabuf); + goto vmperm_release; + } + atomic_add(len >> PAGE_SHIFT, &rbin_allocated_pages); + + return dmabuf; + +vmperm_release: + mem_buf_vmperm_release(buffer->vmperm); +free_sg: + sg_free_table(table); +free_buffer: + list_for_each_entry_safe(page, tmp_page, &pages, lru) + dmabuf_rbin_free(page_to_phys(page), page_private(page)); + kfree(buffer); + + return ERR_PTR(ret); +} + +static struct rbin_heap *g_rbin_heap; + +void wake_dmabuf_rbin_heap_prereclaim(void) +{ + if (g_rbin_heap) { + g_rbin_heap->task_run = 1; + wake_up(&g_rbin_heap->waitqueue); + } +} + +void wake_dmabuf_rbin_heap_shrink(void) +{ + if (g_rbin_heap) { + g_rbin_heap->shrink_run = 1; + wake_up(&g_rbin_heap->waitqueue); + } +} + +static void dmabuf_rbin_heap_destroy_pools(struct rbin_dmabuf_page_pool **pools) +{ + int i; + + for (i = 0; i < NUM_ORDERS; i++) + kfree(pools[i]); +} + +static int dmabuf_rbin_heap_create_pools(struct rbin_dmabuf_page_pool **pools) +{ + int i; + + for (i = 0; i < NUM_ORDERS; i++) { + pools[i] = rbin_page_pool_create(GFP_KERNEL, orders[i]); + if (!pools[i]) + goto err_create_pool; + } + return 0; + +err_create_pool: + dmabuf_rbin_heap_destroy_pools(pools); + return -ENOMEM; +} + +#define RBIN_CORE_NUM_FIRST 0 +#define RBIN_CORE_NUM_LAST 3 +static struct cpumask rbin_cpumask; +static void init_rbin_cpumask(void) +{ + int i; + + cpumask_clear(&rbin_cpumask); + + for (i = RBIN_CORE_NUM_FIRST; i <= RBIN_CORE_NUM_LAST; i++) + cpumask_set_cpu(i, &rbin_cpumask); +} + +static int rbin_cpu_online(unsigned int cpu) +{ + if (cpumask_any_and(cpu_online_mask, &rbin_cpumask) < nr_cpu_ids) { + /* One of our CPUs online: restore mask */ + set_cpus_allowed_ptr(g_rbin_heap->task, &rbin_cpumask); + set_cpus_allowed_ptr(g_rbin_heap->task_shrink, &rbin_cpumask); + } + return 0; +} + +static int dmabuf_rbin_heap_prereclaim(void *data) +{ + struct rbin_heap *rbin_heap = data; + unsigned int order; + unsigned long size = PAGE_SIZE << orders[0]; + unsigned long last_size; + struct rbin_dmabuf_page_pool *pool; + struct page *page; + unsigned long jiffies_bstop; + + set_cpus_allowed_ptr(current, &rbin_cpumask); + while (true) { + wait_event_freezable(rbin_heap->waitqueue, rbin_heap->task_run); + jiffies_bstop = jiffies + (HZ / 10); + last_size = size; + while (true) { + page = alloc_rbin_page(size, last_size); + if (PTR_ERR(page) == -ENOMEM) + break; + if (PTR_ERR(page) == -EBUSY) { + if (time_is_after_jiffies(jiffies_bstop)) + continue; + else + break; + } + last_size = page_private(page); + order = get_order(page_private(page)); + pool = rbin_heap->pools[order_to_index(order)]; + rbin_page_pool_free(pool, page); + atomic_add(1 << order, &rbin_pool_pages); + } + rbin_heap->task_run = 0; + } + return 0; +} + +static int dmabuf_rbin_heap_shrink(void *data) +{ + struct rbin_heap *rbin_heap = data; + unsigned long size = PAGE_SIZE << orders[0]; + struct page *page; + + set_cpus_allowed_ptr(current, &rbin_cpumask); + while (true) { + wait_event_freezable(rbin_heap->waitqueue, rbin_heap->shrink_run); + while (true) { + page = alloc_rbin_page_from_pool(rbin_heap, size); + if (!page) + break; + dmabuf_rbin_free(page_to_phys(page), page_private(page)); + } + rbin_heap->shrink_run = 0; + } + return 0; +} + +/* Dummy function to be used until we can call coerce_mask_and_coherent */ +static struct dma_buf *rbin_heap_allocate_not_initialized(struct dma_heap *heap, + unsigned long len, + unsigned long fd_flags, + unsigned long heap_flags) +{ + return ERR_PTR(-EBUSY); +} + +static struct dma_buf *rbin_cached_heap_allocate(struct dma_heap *heap, + unsigned long len, + unsigned long fd_flags, + unsigned long heap_flags) +{ + return rbin_heap_allocate(heap, len, fd_flags, heap_flags, false); +} + +static struct dma_heap_ops rbin_cached_heap_ops = { + .allocate = rbin_heap_allocate_not_initialized, +}; + +static struct dma_buf *rbin_uncached_heap_allocate(struct dma_heap *heap, + unsigned long len, + unsigned long fd_flags, + unsigned long heap_flags) +{ + return rbin_heap_allocate(heap, len, fd_flags, heap_flags, true); +} + +static struct dma_heap_ops rbin_uncached_heap_ops = { + /* After rbin_heap_create is complete, we will swap this */ + .allocate = rbin_heap_allocate_not_initialized, +}; + +struct kobject *rbin_kobject; + +static void rbin_heap_show_mem(void *data, unsigned int filter, nodemask_t *nodemask) +{ + struct dma_heap *heap = (struct dma_heap *)data; + struct rbin_heap *rbin_heap; + + if (!heap) + return; + + rbin_heap = dma_heap_get_drvdata(heap); + if (!rbin_heap) + return; + + pr_info("rbintotal: %u kB rbinpool: %u kB rbinfree: %u kB rbincache: %u kB\n", + rbin_heap->count << (PAGE_SHIFT - 10), + atomic_read(&rbin_pool_pages) << (PAGE_SHIFT - 10), + atomic_read(&rbin_free_pages) << (PAGE_SHIFT - 10), + atomic_read(&rbin_cached_pages) << (PAGE_SHIFT - 10)); +} + +static void show_rbin_meminfo(void *data, struct seq_file *m) +{ + struct dma_heap *heap = (struct dma_heap *)data; + struct rbin_heap *rbin_heap; + u64 rbin_allocated_kb, rbin_pool_kb; + + if (!heap) + return; + + rbin_heap = dma_heap_get_drvdata(heap); + if (!rbin_heap) + return; + + rbin_allocated_kb = (u64)(atomic_read(&rbin_allocated_pages) << (PAGE_SHIFT - 10)); + rbin_pool_kb = (u64)(atomic_read(&rbin_pool_pages) << (PAGE_SHIFT - 10)); + + show_val_meminfo(m, "RbinTotal", rbin_heap->count << (PAGE_SHIFT - 10)); + show_val_meminfo(m, "RbinAlloced", rbin_allocated_kb + rbin_pool_kb); + show_val_meminfo(m, "RbinPool", rbin_pool_kb); + show_val_meminfo(m, "RbinFree", (u64)(atomic_read(&rbin_free_pages) << (PAGE_SHIFT - 10))); + show_val_meminfo(m, "RbinCached", (u64)(atomic_read(&rbin_cached_pages) << (PAGE_SHIFT - 10))); +} + +static void rbin_cache_adjust(void *data, unsigned long *cached) +{ + *cached += (unsigned long)atomic_read(&rbin_cached_pages); +} + +static void rbin_available_adjust(void *data, unsigned long *available) +{ + *available += (unsigned long)atomic_read(&rbin_cached_pages); + *available += (unsigned long)atomic_read(&rbin_free_pages); +} + +static void rbin_meminfo_adjust(void *data, unsigned long *totalram, + unsigned long *freeram) +{ + struct dma_heap *heap = (struct dma_heap *)data; + struct rbin_heap *rbin_heap; + + if (!heap) + return; + + rbin_heap = dma_heap_get_drvdata(heap); + if (!rbin_heap) + return; + + *totalram += rbin_heap->count; + *freeram += (unsigned long)atomic_read(&rbin_free_pages); +} + +int add_rbin_heap(struct platform_heap *heap_data) +{ + struct dma_heap_export_info exp_info; + struct rbin_heap *rbin_heap; + int ret = 0; + + if (!heap_data->base) { + perrdev(heap_data->dev, "memory-region has no base"); + ret = -ENODEV; + goto out; + } + + if (!heap_data->size) { + perrdev(heap_data->dev, "memory-region has no size"); + ret = -ENOMEM; + goto out; + } + + rbin_heap = kzalloc(sizeof(struct rbin_heap), GFP_KERNEL); + if (!rbin_heap) { + perrdev(heap_data->dev, "failed to alloc rbin_heap"); + ret = -ENOMEM; + goto out; + } + + rbin_kobject = kobject_create_and_add("rbin", kernel_kobj); + if (!rbin_kobject) { + perrdev(heap_data->dev, "failed to create rbin_kobject"); + ret = -ENOMEM; + goto free_rbin_heap; + } + + if (dmabuf_rbin_heap_create_pools(rbin_heap->pools)) { + perrdev(heap_data->dev, "failed to create dma-buf page pool"); + ret = -ENOMEM; + goto free_rbin_kobject; + } + + ret = init_rbinregion(heap_data->base, heap_data->size); + if (ret) { + perrdev(heap_data->dev, "failed to init rbinregion"); + goto destroy_pools; + } + + init_rbin_cpumask(); + init_waitqueue_head(&rbin_heap->waitqueue); + rbin_heap->count = heap_data->size >> PAGE_SHIFT; + rbin_heap->task = kthread_run(dmabuf_rbin_heap_prereclaim, rbin_heap, "rbin"); + rbin_heap->task_shrink = kthread_run(dmabuf_rbin_heap_shrink, rbin_heap, "rbin_shrink"); + g_rbin_heap = rbin_heap; + pr_info("%s created %s\n", __func__, heap_data->name); + + exp_info.name = "qcom,camera"; + exp_info.ops = &rbin_cached_heap_ops; + exp_info.priv = rbin_heap; + + rbin_cached_dma_heap = dma_heap_add(&exp_info); + if (IS_ERR(rbin_cached_dma_heap)) { + perrdev(heap_data->dev, "failed to dma_heap_add camera"); + ret = PTR_ERR(rbin_cached_dma_heap); + goto destroy_pools; + } + + exp_info.name = "qcom,camera-uncached"; + exp_info.ops = &rbin_uncached_heap_ops; + exp_info.priv = NULL; + + rbin_uncached_dma_heap = dma_heap_add(&exp_info); + if (IS_ERR(rbin_uncached_dma_heap)) { + perrdev(heap_data->dev, "failed to dma_heap_add camera-uncached"); + ret = PTR_ERR(rbin_uncached_dma_heap); + goto destroy_pools; + } + + dma_coerce_mask_and_coherent(dma_heap_get_dev(rbin_uncached_dma_heap), DMA_BIT_MASK(64)); + mb(); /* make sure we only set allocate after dma_mask is set */ + rbin_cached_heap_ops.allocate = rbin_cached_heap_allocate; + rbin_uncached_heap_ops.allocate = rbin_uncached_heap_allocate; + + register_trace_android_vh_show_mem(rbin_heap_show_mem, (void *)rbin_cached_dma_heap); + register_trace_android_vh_meminfo_proc_show(show_rbin_meminfo, (void *)rbin_cached_dma_heap); + + register_trace_android_vh_meminfo_cache_adjust(rbin_cache_adjust, NULL); + register_trace_android_vh_si_mem_available_adjust(rbin_available_adjust, NULL); + register_trace_android_vh_si_meminfo_adjust(rbin_meminfo_adjust, (void *)rbin_cached_dma_heap); + + ret = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN, + "ion/rbin:online", rbin_cpu_online, + NULL); + if (ret < 0) + pr_err("rbin: failed to register 'online' hotplug state\n"); + + pr_info("%s done\n", __func__); + + return 0; + +destroy_pools: + dmabuf_rbin_heap_destroy_pools(rbin_heap->pools); +free_rbin_kobject: + kobject_put(rbin_kobject); +free_rbin_heap: + kfree(rbin_heap); +out: + return ret; +} diff --git a/drivers/dma-buf/heaps/rbincache.c b/drivers/dma-buf/heaps/rbincache.c new file mode 100644 index 000000000000..e343443cce60 --- /dev/null +++ b/drivers/dma-buf/heaps/rbincache.c @@ -0,0 +1,863 @@ +/* + * linux/mm/rbincache.c + * + * A cleancache backend for fast ion allocation. + * Cache management methods based on rbincache. + * Copyright (C) 2019 Samsung Electronics + * + * With rbincache, active file pages can be backed up in memory during page + * reclaiming. When their data is needed again the I/O reading operation is + * avoided. Up to here it might seem as a waste of time and resource just to + * copy file pages into cleancache area. + * + * However, since the cleancache API ensures the cache pages to be clean, + * we can make use of clean file caches stored in one contiguous section. + * By having two modes(1.cache_mode, 2.alloc_mode), rbincache normally + * acts as a cleancache backbone, while providing a rapidly reclaimed + * contiguous space when needed. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "rbinregion.h" + +/* + * rbincache: a cleancache API implementation + * + * When a file page is passed from cleancache to rbincache, rbincache maintains + * a mapping of the to the + * rr_handle that represents the backed up file page. + * This mapping is achieved with a red-black tree per filesystem, + * plus a radix tree per a red-black node. + * + * A rbincache pool is assigned its pool_id when a filesystem mounted + * Each rbincache pool has a red-black tree, where the inode number(rb_index) + * is the search key. In other words, each rb_node represents an inode. + * Each rb_node has a radix tree which use page->index(ra_index) as the index. + * Each radix tree slot points to the rr_handle. + * + * The implementation borrows pretty much concepts from Zcache. + */ + +/* statistics */ +atomic_t rbin_allocated_pages = ATOMIC_INIT(0); +atomic_t rbin_cached_pages = ATOMIC_INIT(0); +atomic_t rbin_free_pages = ATOMIC_INIT(0); +atomic_t rbin_pool_pages = ATOMIC_INIT(0); +static atomic_t rbin_zero_pages = ATOMIC_INIT(0); + +static atomic_t rc_num_rbnode = ATOMIC_INIT(0); +static atomic_t rc_num_ra_entry = ATOMIC_INIT(0); +static atomic_t rc_num_dup_handle = ATOMIC_INIT(0); + +static atomic_t rc_num_init_fs = ATOMIC_INIT(0); +static atomic_t rc_num_init_shared_fs = ATOMIC_INIT(0); +static atomic_t rc_num_gets = ATOMIC_INIT(0); +static atomic_t rc_num_puts = ATOMIC_INIT(0); +static atomic_t rc_num_flush_page = ATOMIC_INIT(0); +static atomic_t rc_num_flush_inode = ATOMIC_INIT(0); +static atomic_t rc_num_flush_fs = ATOMIC_INIT(0); + +static atomic_t rc_num_succ_init_fs = ATOMIC_INIT(0); +static atomic_t rc_num_succ_gets = ATOMIC_INIT(0); +static atomic_t rc_num_succ_puts = ATOMIC_INIT(0); +static atomic_t rc_num_succ_flush_page = ATOMIC_INIT(0); +static atomic_t rc_num_succ_flush_inode = ATOMIC_INIT(0); +static atomic_t rc_num_succ_flush_fs = ATOMIC_INIT(0); +/* statistics end */ + +/* rbincache data structures */ +#define MAX_RC_POOLS 64 +#define ZERO_HANDLE ((void *)~(~0UL >> 1)) + +/* One rbincache pool per filesystem mount instance */ +struct rc_pool { + struct rb_root rbtree; + rwlock_t rb_lock; /* Protects rbtree */ +}; + +/* Manage all rbincache pools */ +struct rbincache { + struct rc_pool *pools[MAX_RC_POOLS]; + u32 num_pools; /* current number of rbincache pools */ + spinlock_t pool_lock; /* Protects pools[] and num_pools */ +}; +struct rbincache rbincache; + +/* + * Redblack tree node, each node has a page index radix-tree. + * Indexed by inode nubmer. + */ +struct rc_rbnode { + struct rb_node rb_node; + int rb_index; + struct radix_tree_root ratree; /* Page radix tree per inode rbtree */ + spinlock_t ra_lock; /* Protects radix tree */ + struct kref refcount; +}; +/* rbincache data structures end */ + +/* rbincache slab allocation */ +static struct kmem_cache *rc_rbnode_cache; +static int rc_rbnode_cache_create(void) +{ + rc_rbnode_cache = KMEM_CACHE(rc_rbnode, 0); + return rc_rbnode_cache == NULL; +} +static void rc_rbnode_cache_destroy(void) +{ + kmem_cache_destroy(rc_rbnode_cache); +} +/* rbincache slab allocation end */ + +/* rbincache rb_tree & ra_tree helper functions */ +/** + * radix_tree_gang_lookup_index - perform multiple lookup on a radix tree + * @root: radix tree root + * @results: where the results of the lookup are placed + * @indices: where their indices should be placed + * @first_index: start the lookup from this key + * @max_items: place up to this many items at *results + * + * Performs an index-ascending scan of the tree for present items. Places + * them at *@results and returns the number of items which were placed at + * *@results. The indices are placed in @indices. + * + * The implementation is naive. + * + * Just one difference from radix_tree_gang_lookup, the indices are also + * collected along with the results of lookup. + */ +static unsigned int +radix_tree_gang_lookup_index(const struct radix_tree_root *root, void **results, + unsigned long *indices, unsigned long first_index, + unsigned int max_items) +{ + struct radix_tree_iter iter; + void **slot; + unsigned int ret = 0; + + if (unlikely(!max_items)) + return 0; + + radix_tree_for_each_slot(slot, root, &iter, first_index) { + results[ret] = rcu_dereference_raw(*slot); + if (!results[ret]) + continue; + if (radix_tree_is_internal_node(results[ret])) { + slot = radix_tree_iter_retry(&iter); + continue; + } + if (indices) + indices[ret] = iter.index; + if (++ret == max_items) + break; + } + + return ret; +} + +/* + * The caller should hold rb_lock + */ +static struct rc_rbnode *rc_find_rbnode(struct rb_root *rbtree, int index, + struct rb_node **rb_parent, + struct rb_node ***rb_link) +{ + struct rc_rbnode *entry; + struct rb_node **__rb_link, *__rb_parent, *rb_prev; + + __rb_link = &rbtree->rb_node; + rb_prev = __rb_parent = NULL; + + while (*__rb_link) { + __rb_parent = *__rb_link; + entry = rb_entry(__rb_parent, struct rc_rbnode, rb_node); + if (entry->rb_index > index) + __rb_link = &__rb_parent->rb_left; + else if (entry->rb_index < index) { + rb_prev = __rb_parent; + __rb_link = &__rb_parent->rb_right; + } else + return entry; + } + + if (rb_parent) + *rb_parent = __rb_parent; + if (rb_link) + *rb_link = __rb_link; + return NULL; +} + +static struct rc_rbnode *rc_find_get_rbnode(struct rc_pool *rcpool, + int rb_index) +{ + unsigned long flags; + struct rc_rbnode *rbnode; + + read_lock_irqsave(&rcpool->rb_lock, flags); + rbnode = rc_find_rbnode(&rcpool->rbtree, rb_index, 0, 0); + if (rbnode) + kref_get(&rbnode->refcount); + read_unlock_irqrestore(&rcpool->rb_lock, flags); + return rbnode; +} + +/* + * kref_put callback for rc_rbnode. + * The rbnode must have been isolated from rbtree already. + * Called when rbnode has 0 references. + */ +static void rc_rbnode_release(struct kref *kref) +{ + struct rc_rbnode *rbnode; + + rbnode = container_of(kref, struct rc_rbnode, refcount); + BUG_ON(rbnode->ratree.xa_head); + kmem_cache_free(rc_rbnode_cache, rbnode); + atomic_dec(&rc_num_rbnode); +} + +/* + * Check whether the radix-tree of this rbnode is empty. + * If that's true, then we can delete this rc_rbnode from + * rc_pool->rbtree + * + * Caller must hold rc_rbnode->ra_lock + */ +static inline int rc_rbnode_empty(struct rc_rbnode *rbnode) +{ + return rbnode->ratree.xa_head == NULL; +} + +/* + * Remove rc_rbnode from rcpool->rbtree + */ +static void rc_rbnode_isolate(struct rc_pool *rcpool, struct rc_rbnode *rbnode) +{ + /* + * Someone can get reference on this rbnode before we could + * acquire write lock above. + * We want to remove it from rcpool->rbtree when only the caller and + * corresponding ratree holds a reference to this rbnode. + * Below check ensures that a racing rc put will not end up adding + * a page to an isolated node and thereby losing that memory. + */ + if (atomic_read(&rbnode->refcount.refcount.refs) == 2) { + rb_erase(&rbnode->rb_node, &rcpool->rbtree); + RB_CLEAR_NODE(&rbnode->rb_node); + kref_put(&rbnode->refcount, rc_rbnode_release); + } else { + pr_err("rbincache: unabled to erase rbnode : refcount=%d\n", + atomic_read(&rbnode->refcount.refcount.refs)); + } +} + +static int rc_store_handle(int pool_id, int rb_index, int ra_index, void *handle) +{ + unsigned long flags; + struct rc_pool *rcpool; + struct rc_rbnode *rbnode, *dup_rbnode; + struct rb_node **link = NULL, *parent = NULL; + int ret = 0; + void *dup_handle; + + rcpool = rbincache.pools[pool_id]; + rbnode = rc_find_get_rbnode(rcpool, rb_index); + if (!rbnode) { + /* create, init, and get(inc refcount) a new rbnode */ + rbnode = kmem_cache_alloc(rc_rbnode_cache, 0); + if (!rbnode) + return -ENOMEM; + atomic_inc(&rc_num_rbnode); + + INIT_RADIX_TREE(&rbnode->ratree, GFP_ATOMIC|__GFP_NOWARN); + spin_lock_init(&rbnode->ra_lock); + rbnode->rb_index = rb_index; + kref_init(&rbnode->refcount); + RB_CLEAR_NODE(&rbnode->rb_node); + + /* add that rbnode to rbtree */ + write_lock_irqsave(&rcpool->rb_lock, flags); + dup_rbnode = rc_find_rbnode(&rcpool->rbtree, rb_index, + &parent, &link); + if (dup_rbnode) { + /* somebody else allocated new rbnode */ + kmem_cache_free(rc_rbnode_cache, rbnode); + atomic_dec(&rc_num_rbnode); + rbnode = dup_rbnode; + } else { + rb_link_node(&rbnode->rb_node, parent, link); + rb_insert_color(&rbnode->rb_node, &rcpool->rbtree); + } + /* Inc the reference of this rc_rbnode */ + kref_get(&rbnode->refcount); + write_unlock_irqrestore(&rcpool->rb_lock, flags); + } + + /* Succfully got a rc_rbnode when arriving here */ + spin_lock_irqsave(&rbnode->ra_lock, flags); + dup_handle = radix_tree_delete(&rbnode->ratree, ra_index); + if (unlikely(dup_handle)) { + atomic_inc(&rc_num_dup_handle); + if (dup_handle == ZERO_HANDLE) + atomic_dec(&rbin_zero_pages); + else + region_flush_cache(dup_handle); + } + + ret = radix_tree_insert(&rbnode->ratree, ra_index, (void *)handle); + spin_unlock_irqrestore(&rbnode->ra_lock, flags); + + if (unlikely(ret)) { + // insert failed + write_lock_irqsave(&rcpool->rb_lock, flags); + spin_lock(&rbnode->ra_lock); + + if (rc_rbnode_empty(rbnode)) + rc_rbnode_isolate(rcpool, rbnode); + + spin_unlock(&rbnode->ra_lock); + write_unlock_irqrestore(&rcpool->rb_lock, flags); + } else { + atomic_inc(&rc_num_ra_entry); + } + + kref_put(&rbnode->refcount, rc_rbnode_release); + return ret; +} + +/* + * Load handle and delete it from radix tree. + * If the radix tree of the corresponding rbnode is empty, delete the rbnode + * from rcpool->rbtree also. + */ +static struct rr_handle *rc_load_del_handle(int pool_id, + int rb_index, int ra_index) +{ + struct rc_pool *rcpool; + struct rc_rbnode *rbnode; + struct rr_handle *handle = NULL; + unsigned long flags; + + rcpool = rbincache.pools[pool_id]; + rbnode = rc_find_get_rbnode(rcpool, rb_index); + if (!rbnode) + goto out; + + BUG_ON(rbnode->rb_index != rb_index); + + spin_lock_irqsave(&rbnode->ra_lock, flags); + handle = radix_tree_delete(&rbnode->ratree, ra_index); + spin_unlock_irqrestore(&rbnode->ra_lock, flags); + if (!handle) + goto no_handle; + else if (handle == ZERO_HANDLE) + atomic_dec(&rbin_zero_pages); + + atomic_dec(&rc_num_ra_entry); + /* rb_lock and ra_lock must be taken again in the given sequence */ + write_lock_irqsave(&rcpool->rb_lock, flags); + spin_lock(&rbnode->ra_lock); + if (rc_rbnode_empty(rbnode)) + rc_rbnode_isolate(rcpool, rbnode); + spin_unlock(&rbnode->ra_lock); + write_unlock_irqrestore(&rcpool->rb_lock, flags); + +no_handle: + kref_put(&rbnode->refcount, rc_rbnode_release); +out: + return handle; +} + +/* rbincache rb_tree & ra_tree helper functions */ + +/* Cleancache API implementation start */ +static bool is_zero_page(struct page *page) +{ + unsigned long *ptr = kmap_atomic(page); + int i; + bool ret = false; + + for (i = 0; i < PAGE_SIZE / sizeof(*ptr); i++) { + if (ptr[i]) + goto out; + } + ret = true; +out: + kunmap_atomic(ptr); + return ret; +} + +/* mem_boost throttles only kswapd's behavior */ +enum refill_mode_state { + ALLOW_REFILL, + BLOCK_REFILL = 1 +}; + +static int refill_mode = ALLOW_REFILL; +static unsigned long last_mode_change; + +#define BLOCK_REFILL_MAX_TIME (5 * HZ) /* 5 sec */ + +inline bool is_refill_blocked(void) +{ + if (time_after(jiffies, last_mode_change + BLOCK_REFILL_MAX_TIME)) + refill_mode = ALLOW_REFILL; + + if (refill_mode >= BLOCK_REFILL) + return true; + else + return false; +} + +static ssize_t refill_mode_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + if (time_after(jiffies, last_mode_change + BLOCK_REFILL_MAX_TIME)) + refill_mode = ALLOW_REFILL; + return sprintf(buf, "%d\n", refill_mode); +} + +static ssize_t refill_mode_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + int mode; + int err; + + err = kstrtoint(buf, 10, &mode); + + if (err || mode > BLOCK_REFILL || mode < ALLOW_REFILL) + return -EINVAL; + + refill_mode = mode; + last_mode_change = jiffies; + return count; +} + +/* + * function for cleancache_ops->put_page + * Though it might fail, it does not matter since Cleancache does not + * guarantee the stored pages to be preserved. + */ +static void rc_store_page(int pool_id, struct cleancache_filekey key, + pgoff_t index, struct page *src) +{ + struct rr_handle *handle; + int ret; + bool zero; + + if (!current_is_kswapd()) + return; + + if (is_refill_blocked()) + return; + + atomic_inc(&rc_num_puts); + zero = is_zero_page(src); + if (zero) { + handle = (struct rr_handle *)ZERO_HANDLE; + goto out_zero; + } + handle = region_store_cache(src, pool_id, key.u.ino, index); + if (!handle) + return; +out_zero: + ret = rc_store_handle(pool_id, key.u.ino, index, handle); + if (ret) { // failed + if (!zero) + region_flush_cache(handle); + return; + } + + /* update stats */ + atomic_inc(&rc_num_succ_puts); + if (zero) + atomic_inc(&rbin_zero_pages); +} + +/* + * function for cleancache_ops->get_page + */ +static int rc_load_page(int pool_id, struct cleancache_filekey key, + pgoff_t index, struct page *dst) +{ + struct rr_handle *handle; + int ret = -EINVAL; + void *addr; + + atomic_inc(&rc_num_gets); + handle = rc_load_del_handle(pool_id, key.u.ino, index); + if (!handle) + goto out; + + if (handle == ZERO_HANDLE) { + addr = kmap_atomic(dst); + memset(addr, 0, PAGE_SIZE); + kunmap_atomic(addr); + flush_dcache_page(dst); + ret = 0; + } else { + ret = region_load_cache(handle, dst, pool_id, key.u.ino, index); + } + if (ret) + goto out; + + /* update stats */ + atomic_inc(&rc_num_succ_gets); +out: + return ret; +} + +static void rc_flush_page(int pool_id, struct cleancache_filekey key, + pgoff_t index) +{ + struct rr_handle *handle; + + atomic_inc(&rc_num_flush_page); + handle = rc_load_del_handle(pool_id, key.u.ino, index); + if (!handle) + return; + + if (handle != ZERO_HANDLE) + region_flush_cache(handle); + + atomic_inc(&rc_num_succ_flush_page); +} + +#define FREE_BATCH 16 +/* + * Callers must hold the lock + */ +static int rc_flush_ratree(struct rc_pool *rcpool, struct rc_rbnode *rbnode) +{ + unsigned long index = 0; + int count, i, total_count = 0; + struct rr_handle *handle; + void *results[FREE_BATCH]; + unsigned long indices[FREE_BATCH]; + + do { + count = radix_tree_gang_lookup_index(&rbnode->ratree, + (void **)results, indices, index, FREE_BATCH); + + for (i = 0; i < count; i++) { + if (results[i] == ZERO_HANDLE) { + handle = radix_tree_delete(&rbnode->ratree, + indices[i]); + if (handle) + atomic_dec(&rbin_zero_pages); + continue; + } + handle = (struct rr_handle *)results[i]; + index = handle->ra_index; + handle = radix_tree_delete(&rbnode->ratree, index); + if (!handle) + continue; + atomic_dec(&rc_num_ra_entry); + total_count++; + region_flush_cache(handle); + } + index++; + } while (count == FREE_BATCH); + return total_count; +} + +static void rc_flush_inode(int pool_id, struct cleancache_filekey key) +{ + struct rc_rbnode *rbnode; + unsigned long flags1, flags2; + struct rc_pool *rcpool = rbincache.pools[pool_id]; + int pages_flushed; + + atomic_inc(&rc_num_flush_inode); + /* + * Refuse new pages added in to the same rbnode, so get rb_lock at + * first. + */ + write_lock_irqsave(&rcpool->rb_lock, flags1); + rbnode = rc_find_rbnode(&rcpool->rbtree, key.u.ino, 0, 0); + if (!rbnode) { + write_unlock_irqrestore(&rcpool->rb_lock, flags1); + return; + } + + kref_get(&rbnode->refcount); + spin_lock_irqsave(&rbnode->ra_lock, flags2); + + pages_flushed = rc_flush_ratree(rcpool, rbnode); + if (rc_rbnode_empty(rbnode)) + /* When arrvied here, we already hold rb_lock */ + rc_rbnode_isolate(rcpool, rbnode); + + spin_unlock_irqrestore(&rbnode->ra_lock, flags2); + kref_put(&rbnode->refcount, rc_rbnode_release); + write_unlock_irqrestore(&rcpool->rb_lock, flags1); + + atomic_inc(&rc_num_succ_flush_inode); +} + +static void rc_flush_fs(int pool_id) +{ + struct rc_rbnode *rbnode = NULL; + struct rb_node *node; + unsigned long flags1, flags2; + struct rc_pool *rcpool; + int pages_flushed = 0; + + atomic_inc(&rc_num_flush_fs); + + if (pool_id < 0) + return; + rcpool = rbincache.pools[pool_id]; + if (!rcpool) + return; + + /* + * Refuse new pages added in, so get rb_lock at first. + */ + write_lock_irqsave(&rcpool->rb_lock, flags1); + + node = rb_first(&rcpool->rbtree); + while (node) { + rbnode = rb_entry(node, struct rc_rbnode, rb_node); + node = rb_next(node); + if (rbnode) { + kref_get(&rbnode->refcount); + spin_lock_irqsave(&rbnode->ra_lock, flags2); + pages_flushed += rc_flush_ratree(rcpool, rbnode); + if (rc_rbnode_empty(rbnode)) + rc_rbnode_isolate(rcpool, rbnode); + spin_unlock_irqrestore(&rbnode->ra_lock, flags2); + kref_put(&rbnode->refcount, rc_rbnode_release); + } + } + write_unlock_irqrestore(&rcpool->rb_lock, flags1); + + atomic_inc(&rc_num_succ_flush_fs); +} + +static int rc_init_fs(size_t pagesize) +{ + struct rc_pool *rcpool; + int ret = -1; + + // init does not check rbincache_disabled. Even disabled, continue init. + + atomic_inc(&rc_num_init_fs); + + if (pagesize != PAGE_SIZE) { + pr_warn("Unsupported page size: %zu", pagesize); + ret = -EINVAL; + goto out; + } + + rcpool = kzalloc(sizeof(*rcpool), GFP_KERNEL); + if (!rcpool) { + ret = -ENOMEM; + goto out; + } + + spin_lock(&rbincache.pool_lock); + if (rbincache.num_pools == MAX_RC_POOLS) { + pr_err("Cannot create new pool (limit:%u)\n", MAX_RC_POOLS); + ret = -EPERM; + goto out_unlock; + } + + rwlock_init(&rcpool->rb_lock); + rcpool->rbtree = RB_ROOT; + + /* Add to pool list */ + for (ret = 0; ret < MAX_RC_POOLS; ret++) + if (!rbincache.pools[ret]) + break; + if (ret == MAX_RC_POOLS) { + ret = -ENOMEM; + goto out_unlock; + } + rbincache.pools[ret] = rcpool; + rbincache.num_pools++; + pr_info("New pool created id:%d\n", ret); + + atomic_inc(&rc_num_succ_init_fs); + +out_unlock: + spin_unlock(&rbincache.pool_lock); + if (ret < 0) + kfree(rcpool); +out: + return ret; +} + +static int rc_init_shared_fs(uuid_t *uuid, size_t pagesize) +{ + atomic_inc(&rc_num_init_shared_fs); + return rc_init_fs(pagesize); +} + +static const struct cleancache_ops rbincache_ops = { + .init_fs = rc_init_fs, + .init_shared_fs = rc_init_shared_fs, + .get_page = rc_load_page, + .put_page = rc_store_page, + .invalidate_page = rc_flush_page, + .invalidate_inode = rc_flush_inode, + .invalidate_fs = rc_flush_fs, +}; + +/* Cleancache API implementation end */ +static void rc_evict_cb(unsigned long raw_handle) +{ + struct rr_handle *h = (struct rr_handle *)raw_handle; + + /* try find and remove entry if exists */ + rc_load_del_handle(h->pool_id, h->rb_index, h->ra_index); +} + +static struct region_ops rc_region_ops = { + .evict = rc_evict_cb +}; + + /* Statistics */ +static ssize_t stats_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sprintf(buf, + "allocated_pages : %8d\n" + "cached_pages : %8d\n" + "pool_pages : %8d\n" + "zero_pages : %8d\n" + "num_dup_handle : %8d\n" + "init_fs : %8d\n" + "init_shared_fs : %8d\n" + "puts : %8d\n" + "gets : %8d\n" + "flush_page : %8d\n" + "flush_inode : %8d\n" + "flush_fs : %8d\n" + "succ_init_fs : %8d\n" + "succ_puts : %8d\n" + "succ_gets : %8d\n" + "succ_flush_page : %8d\n" + "succ_flush_inode : %8d\n" + "succ_flush_fs : %8d\n", + atomic_read(&rbin_allocated_pages), + atomic_read(&rbin_cached_pages), + atomic_read(&rbin_pool_pages), + atomic_read(&rbin_zero_pages), + atomic_read(&rc_num_dup_handle), + atomic_read(&rc_num_init_fs), + atomic_read(&rc_num_init_shared_fs), + atomic_read(&rc_num_puts), + atomic_read(&rc_num_gets), + atomic_read(&rc_num_flush_page), + atomic_read(&rc_num_flush_inode), + atomic_read(&rc_num_flush_fs), + atomic_read(&rc_num_succ_init_fs), + atomic_read(&rc_num_succ_puts), + atomic_read(&rc_num_succ_gets), + atomic_read(&rc_num_succ_flush_page), + atomic_read(&rc_num_succ_flush_inode), + atomic_read(&rc_num_succ_flush_fs)); +} + +/* mem_boost throttles only kswapd's behavior */ +enum mem_boost { + NO_BOOST, + BOOST_MID = 1, + BOOST_HIGH = 2, + BOOST_KILL = 3, +}; + +static int mem_boost_mode = NO_BOOST; +static unsigned long last_mode_change; + +#define MEM_BOOST_MAX_TIME (5 * HZ) /* 5 sec */ + +static ssize_t mem_boost_mode_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + if (time_after(jiffies, last_mode_change + MEM_BOOST_MAX_TIME)) + mem_boost_mode = NO_BOOST; + return sprintf(buf, "%d\n", mem_boost_mode); +} + +static ssize_t mem_boost_mode_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + int mode; + int err; + + err = kstrtoint(buf, 10, &mode); + if (err || mode > BOOST_KILL || mode < NO_BOOST) + return -EINVAL; + + mem_boost_mode = mode; + last_mode_change = jiffies; + if (mem_boost_mode >= BOOST_HIGH) + wake_dmabuf_rbin_heap_prereclaim(); + return count; +} + +static struct kobj_attribute stats_attr = __ATTR_RO(stats); +static struct kobj_attribute mem_boost_mode_attr = __ATTR_RW(mem_boost_mode); +static struct kobj_attribute refill_mode_attr = __ATTR_RW(refill_mode); + + +static struct attribute *rbincache_attrs[] = { + &stats_attr.attr, + &mem_boost_mode_attr.attr, + &refill_mode_attr.attr, + NULL, +}; + +ATTRIBUTE_GROUPS(rbincache); + +int init_rbincache(unsigned long pfn, unsigned long nr_pages) +{ + int err = 0; + + init_region(pfn, nr_pages, &rc_region_ops); + + err = rc_rbnode_cache_create(); + if (err) { + pr_err("entry cache creation failed\n"); + goto error; + } + + spin_lock_init(&rbincache.pool_lock); + + err = cleancache_register_ops(&rbincache_ops); + if (err) { + pr_err("failed to register cleancache_ops: %d\n", err); + goto register_fail; + } + + err = sysfs_create_groups(rbin_kobject, rbincache_groups); + if (err) { + kobject_put(rbin_kobject); + pr_warn("sysfs initialization failed\n"); + } + + pr_info("cleancache enabled for rbin cleancache\n"); + return 0; + +register_fail: + rc_rbnode_cache_destroy(); +error: + return err; +} + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("RBIN Cleancache"); diff --git a/drivers/dma-buf/heaps/rbinregion.c b/drivers/dma-buf/heaps/rbinregion.c new file mode 100644 index 000000000000..80393f88b0db --- /dev/null +++ b/drivers/dma-buf/heaps/rbinregion.c @@ -0,0 +1,353 @@ +/* + * linux/mm/rbinregion.c + * + * A physical memory allocator for rbincache. + * + * Manages physical pages based on lru + * Although struct page->lru is used for managing lists, + * struct page is not explicitly provided outside the region api. + * Instead, struct rr_handle is provided to the clients. + */ + +#include +#include "rbinregion.h" + +static struct rbin_region region; + +struct rr_handle *pfn_to_handle(unsigned long pfn) +{ + unsigned long idx = pfn - region.start_pfn; + + return ®ion.handles[idx]; +} + +struct page *handle_to_page(struct rr_handle *handle) +{ + unsigned long idx = (unsigned long)(handle - region.handles); + + return pfn_to_page(region.start_pfn + idx); +} + +bool handle_is_valid(struct rr_handle *handle) +{ + return (handle >= region.handles) + && (handle < region.handles + region.nr_pages); +} + +#define RC_AUTO_ENABLE_TIMEOUT (5*HZ) /* 5 sec */ +bool try_get_rbincache(void) +{ + bool ret = true; + unsigned long flags; + + spin_lock_irqsave(®ion.region_lock, flags); + if (time_before(region.timeout, jiffies)) { + if (region.rc_disabled == true) + wake_dmabuf_rbin_heap_shrink(); + region.rc_disabled = false; + } + + if (region.rc_disabled || region.dmabuf_inflight) + ret = false; + else + region.rc_inflight++; + spin_unlock_irqrestore(®ion.region_lock, flags); + + return ret; +} + +void put_rbincache(void) +{ + unsigned long flags; + + spin_lock_irqsave(®ion.region_lock, flags); + region.rc_inflight--; + spin_unlock_irqrestore(®ion.region_lock, flags); +} + +static bool try_get_dmabuf_rbin(void) +{ + bool ret = true; + unsigned long flags; + + spin_lock_irqsave(®ion.region_lock, flags); + /* disable rbincache ops for a while */ + region.rc_disabled = true; + region.timeout = jiffies + RC_AUTO_ENABLE_TIMEOUT; + if (region.rc_inflight) + ret = false; + else + region.dmabuf_inflight++; + spin_unlock_irqrestore(®ion.region_lock, flags); + + return ret; +} + +static void put_dmabuf_rbin(void) +{ + unsigned long flags; + + spin_lock_irqsave(®ion.region_lock, flags); + region.dmabuf_inflight--; + spin_unlock_irqrestore(®ion.region_lock, flags); +} + +#define RC_EVICT_BATCH 1 +static void region_mem_evict(void) +{ + struct rr_handle *handle, *next; + int count = 0; + unsigned long flags; + + if (!region.ops->evict) + return; + + spin_lock_irqsave(®ion.lru_lock, flags); + list_for_each_entry_safe(handle, next, ®ion.usedlist, lru) { + if (++count > RC_EVICT_BATCH) + break; + /* move to list tail and skip handle being used by ion. */ + if (handle->usage == DMABUF_INUSE) { + list_move_tail(&handle->lru, ®ion.usedlist); + continue; + } + if (handle->usage == RC_INUSE) { + atomic_inc(&rbin_free_pages); + atomic_dec(&rbin_cached_pages); + } + + /* mark freed, load/flush ops will be denied */ + handle->usage = RC_FREED; + list_del(&handle->lru); + spin_unlock_irqrestore(®ion.lru_lock, flags); + + region.ops->evict((unsigned long)handle); + spin_lock_irqsave(®ion.lru_lock, flags); + list_add(&handle->lru, ®ion.freelist); + } + spin_unlock_irqrestore(®ion.lru_lock, flags); +} + +/* Add handle to [free|used]list */ +static void add_to_list(struct rr_handle *handle, struct list_head *head) +{ + unsigned long flags; + + spin_lock_irqsave(®ion.lru_lock, flags); + list_add_tail(&handle->lru, head); + spin_unlock_irqrestore(®ion.lru_lock, flags); +} + +/* + * Find a free slot from region, detach the memory chunk from the list, + * then returns the corresponding handle. + * If there are no free slot, evict a slot from usedlist. + */ +static struct rr_handle *region_get_freemem(void) +{ + struct rr_handle *handle = NULL; + unsigned long flags; + + spin_lock_irqsave(®ion.lru_lock, flags); + if (list_empty(®ion.freelist)) { + spin_unlock_irqrestore(®ion.lru_lock, flags); + region_mem_evict(); + spin_lock_irqsave(®ion.lru_lock, flags); + } + handle = list_first_entry_or_null(®ion.freelist, struct rr_handle, lru); + if (!handle) { + spin_unlock_irqrestore(®ion.lru_lock, flags); + goto out; + } + list_del(&handle->lru); + spin_unlock_irqrestore(®ion.lru_lock, flags); + + /* Skip if handle is(was) used by dmabuf. Wait for eviction in usedlist */ + if (handle->usage == DMABUF_INUSE) { + add_to_list(handle, ®ion.usedlist); + return NULL; + } + + if (handle->usage == DMABUF_FREED) + region.ops->evict((unsigned long)handle); + memset(handle, 0, sizeof(struct rr_handle)); +out: + return handle; +} + +struct rr_handle *region_store_cache(struct page *src, int pool_id, + int rb_index, int ra_index) +{ + struct rr_handle *handle; + unsigned long flags; + + if (!try_get_rbincache()) + return NULL; + + handle = region_get_freemem(); + if (!handle) + goto out; + + BUG_ON(!handle_is_valid(handle)); + + copy_page(page_address(handle_to_page(handle)), page_address(src)); + + spin_lock_irqsave(®ion.lru_lock, flags); + handle->pool_id = pool_id; + handle->rb_index = rb_index; + handle->ra_index = ra_index; + handle->usage = RC_INUSE; + spin_unlock_irqrestore(®ion.lru_lock, flags); + add_to_list(handle, ®ion.usedlist); + atomic_dec(&rbin_free_pages); + atomic_inc(&rbin_cached_pages); +out: + put_rbincache(); + + return handle; +} + +int region_load_cache(struct rr_handle *handle, struct page *dst, + int pool_id, int rb_index, int ra_index) +{ + struct page *page; + unsigned long flags; + int ret = -EINVAL; + + if (!try_get_rbincache()) + return ret; + + BUG_ON(!handle_is_valid(handle)); + + if (handle->usage != RC_INUSE) + goto out; + + spin_lock_irqsave(®ion.lru_lock, flags); + /* skip if handle is invalid (freed or overwritten) */ + if ((handle->usage != RC_INUSE) || + (dst && (handle->pool_id != pool_id || + handle->rb_index != rb_index || + handle->ra_index != ra_index))) { + spin_unlock_irqrestore(®ion.lru_lock, flags); + goto out; + } + handle->usage = RC_FREED; + list_del(&handle->lru); + spin_unlock_irqrestore(®ion.lru_lock, flags); + + if (dst) { + page = handle_to_page(handle); + copy_page(page_address(dst), page_address(page)); + } + add_to_list(handle, ®ion.freelist); + atomic_inc(&rbin_free_pages); + atomic_dec(&rbin_cached_pages); + ret = 0; +out: + put_rbincache(); + + return ret; +} + +int region_flush_cache(struct rr_handle *handle) +{ + return region_load_cache(handle, NULL, 0, 0, 0); +} + +void init_region(unsigned long pfn, unsigned long nr_pages, + const struct region_ops *ops) +{ + struct rr_handle *handle; + unsigned long i; + + region.start_pfn = pfn; + region.nr_pages = nr_pages; + region.ops = ops; + spin_lock_init(®ion.lru_lock); + spin_lock_init(®ion.region_lock); + region.handles = vzalloc(nr_pages * sizeof(struct rr_handle)); + INIT_LIST_HEAD(®ion.freelist); + INIT_LIST_HEAD(®ion.usedlist); + for (i = 0; i < nr_pages; i++) { + handle = ®ion.handles[i]; + INIT_LIST_HEAD(&handle->lru); + list_add(&handle->lru, ®ion.freelist); + } + atomic_set(&rbin_free_pages, nr_pages); +} + +static void isolate_region(unsigned long start_pfn, unsigned long nr_pages) +{ + struct rr_handle *handle; + unsigned long pfn; + int nr_cached = 0; + + for (pfn = start_pfn; pfn < start_pfn + nr_pages; pfn++) { + /* + * Mark pages used by dmabuf. Rbincache ops are blocked for now, + * so it's okay to do this without any lock. Later, accessing + * these pages by rbincache will be denied. + */ + handle = pfn_to_handle(pfn); + if (handle->usage == RC_INUSE) + nr_cached++; + handle->usage = DMABUF_INUSE; + } + atomic_sub(nr_pages - nr_cached, &rbin_free_pages); + atomic_sub(nr_cached, &rbin_cached_pages); +} + +static void putback_region(unsigned long start_pfn, unsigned long nr_pages) +{ + struct rr_handle *handle; + unsigned long pfn; + + for (pfn = start_pfn; pfn < start_pfn + nr_pages; pfn++) { + handle = pfn_to_handle(pfn); + handle->usage = DMABUF_FREED; + } + atomic_add(nr_pages, &rbin_free_pages); +} + +phys_addr_t dmabuf_rbin_allocate(unsigned long size) +{ + unsigned long paddr; + + if (!try_get_dmabuf_rbin()) + return -EBUSY; + + paddr = gen_pool_alloc(region.pool, size); + if (!paddr) { + paddr = -ENOMEM; + goto out; + } + isolate_region(PFN_DOWN(paddr), size >> PAGE_SHIFT); +out: + put_dmabuf_rbin(); + + return paddr; +} + +void dmabuf_rbin_free(phys_addr_t addr, unsigned long size) +{ + if (IS_ERR_VALUE(addr)) + return; + + putback_region(PFN_DOWN(addr), size >> PAGE_SHIFT); + gen_pool_free(region.pool, addr, size); +} + +int init_rbinregion(unsigned long base, unsigned long size) +{ + region.pool = gen_pool_create(PAGE_SHIFT, -1); + if (!region.pool) { + pr_err("%s failed get_pool_create\n", __func__); + return -1; + } + gen_pool_add(region.pool, base, size, -1); + if (init_rbincache(PFN_DOWN(base), size >> PAGE_SHIFT)) { + gen_pool_destroy(region.pool); + return -1; + } + return 0; +} diff --git a/drivers/dma-buf/heaps/rbinregion.h b/drivers/dma-buf/heaps/rbinregion.h new file mode 100644 index 000000000000..375a67c0f5ae --- /dev/null +++ b/drivers/dma-buf/heaps/rbinregion.h @@ -0,0 +1,67 @@ +#ifndef _RBIN_REGION_H +#define _RBIN_REGION_H + +#include +#include +#include +#include +#include +#include +#include "page_pool.h" + +#define E_NOREGION 55 // returned when region is disabled + +struct rr_handle { + int pool_id; /* Pool index : corresponds to filesystem */ + int rb_index; /* Redblack tree index: value equals inode number */ + int ra_index; /* Radix tree index : value equals page->index */ + int usage; /* indicates current usage of corresponding page */ + struct list_head lru; /* use lru of struct page instead */ +}; + +enum usage { + RC_FREED, + RC_INUSE, + DMABUF_FREED, + DMABUF_INUSE, +}; + +struct region_ops { + void (*evict)(unsigned long); +}; + +struct rbin_region { + unsigned long start_pfn; + unsigned long nr_pages; + struct rr_handle *handles; + struct list_head freelist; /* protected by lru_lock. handle->lru */ + struct list_head usedlist; /* protected by lru_lock. handle->lru */ + const struct region_ops *ops; + + spinlock_t lru_lock; + spinlock_t region_lock; + struct gen_pool *pool; + int dmabuf_inflight; + int rc_inflight; + bool rc_disabled; + unsigned long timeout; /* timeout for rbincache enable */ +}; + +extern atomic_t rbin_allocated_pages; +extern atomic_t rbin_cached_pages; +extern atomic_t rbin_free_pages; +extern atomic_t rbin_pool_pages; +extern struct kobject *rbin_kobject; + +struct rr_handle *region_store_cache(struct page *, int, int, int); +int region_load_cache(struct rr_handle *, struct page *, int, int, int); +int region_flush_cache(struct rr_handle *); +phys_addr_t dmabuf_rbin_allocate(unsigned long); +void dmabuf_rbin_free(phys_addr_t, unsigned long); +void wake_dmabuf_rbin_heap_prereclaim(void); +void wake_dmabuf_rbin_heap_shrink(void); +void init_region(unsigned long, unsigned long, const struct region_ops *); +int init_rbincache(unsigned long, unsigned long); +int init_rbinregion(unsigned long, unsigned long); + +#endif /* _RBIN_REGION_H*/ diff --git a/drivers/fingerprint/Kconfig b/drivers/fingerprint/Kconfig new file mode 100644 index 000000000000..fa330977af15 --- /dev/null +++ b/drivers/fingerprint/Kconfig @@ -0,0 +1,121 @@ +# +# Sensor drivers configuration +# +menuconfig SENSORS_FINGERPRINT + tristate "Finger Print Sensor devices" + help + Say Y here, and a list of sensors drivers will be displayed. + Everything that didn't fit into the other categories is here. This option + doesn't affect the kernel. + If unsure, say Y. + +config FINGERPRINT_SECURE + tristate "fingerprint sensor support" + default n + help + If you say yes here you get support for Trustzone. + +config SENSORS_FPRINT_SECURE + tristate "fingerprint sensor support" + default n + help + If you say yes here you get support for Trustzone. + In case of user binary, fingerprint sensor operates within trustzone. + If this feature enabled, spi communication is controlled by Trusted Application. + Then it should be enabled in the user binary. + +config SENSORS_FINGERPRINT_MODULE + bool "module build" + default n + help + You choose yes if you are using module build on fingerprint. + +config SENSORS_FINGERPRINT_LSI + tristate "System LSI AP support" + default n + help + If you say yes here you get support for System LSI AP. + +config SENSORS_FINGERPRINT_QCOM + tristate "Qualcomm AP support" + default n + help + If you say yes here you get support for Qualcomm AP. + +config SENSORS_FINGERPRINT_MTK + tristate "Mediatek AP support" + default n + help + If you say yes here you get support for Mediatek AP. + +config SENSORS_FINGERPRINT_NORMALSPI + tristate "Goodix SPI normal mode" + default n + help + If you say yes here you get to use spi normal mode on goodix sensor. + +config SENSORS_FINGERPRINT_32BITS_PLATFORM_ONLY + tristate "32bits platform support" + default n + help + If you say yes here you get support for 32bits platform. + +config SENSORS_QBT2000 + tristate "QBT2000 fingerprint sensor support" + default n + help + If you say yes here you get support for Qualcomm's + fingerprint sensor QBT2000. + +config SENSORS_QFS4008 + tristate "QFS4008 fingerprint sensor support" + default n + help + If you say yes here you get support for Qualcomm's + fingerprint sensor QFS4008. + +config SENSORS_ET5XX + tristate "ET5XX fingerprint sensor support" + default n + help + Fingerprint sensor driver. + If you say yes here you get support for ET5XX sensor. + ET5XX is the touch type fingerprint sensor. + ET5XX module can support WOF mode. + +config SENSORS_EC6XX + tristate "EC6XX fingerprint sensor support" + default n + help + Fingerprint sensor driver. + If you say yes here you get support for EC6XX sensor. + EC6XX is the touch type fingerprint sensor. + EC6XX module can support WOF mode. + +config SENSORS_ET7XX + tristate "ET7XX fingerprint sensor support" + default n + help + If you say yes here you get support for Egistec's + fingerprint sensor ET7XX. + +config SENSORS_EL7XX + tristate "EL7XX fingerprint sensor support" + default n + help + If you say yes here you get support for Egistec's + fingerprint sensor EL7XX. + +config SENSORS_GW3X + tristate "generic goodix fingerprint driver" + default n + help + If you say yes here you get support for Goodix's + fingerprint sensor GW36X. + +config SENSORS_GW9558X + tristate "generic goodix fingerprint driver" + default n + help + If you say yes here you get support for Goodix's + fingerprint sensor GW9558X. diff --git a/drivers/fingerprint/Makefile b/drivers/fingerprint/Makefile new file mode 100644 index 000000000000..5d20157a115a --- /dev/null +++ b/drivers/fingerprint/Makefile @@ -0,0 +1,25 @@ +# +# Makefile for the Fingerprint sensor driver. +# + +# Each configuration option enables a list of files. + +ccflags-y := -Wformat + +obj-$(CONFIG_SENSORS_FINGERPRINT) += fingerprint.o fingerprint_sysfs.o + +fingerprint-$(CONFIG_SENSORS_FINGERPRINT) += fingerprint_common.o +fingerprint-$(CONFIG_SENSORS_FINGERPRINT_LSI) += fingerprint_common_lsi.o +fingerprint-$(CONFIG_SENSORS_FINGERPRINT_QCOM) += fingerprint_common_qcom.o +fingerprint-$(CONFIG_SENSORS_FINGERPRINT_MTK) += fingerprint_common_mtk.o + +fingerprint-$(CONFIG_SENSORS_ET5XX) += et5xx-spi.o et5xx-spi_data_transfer.o +fingerprint-$(CONFIG_SENSORS_EC6XX) += ec6xx-spi.o ec6xx-spi_data_transfer.o +fingerprint-$(CONFIG_SENSORS_ET7XX) += et7xx-spi.o et7xx-spi_data_transfer.o +fingerprint-$(CONFIG_SENSORS_EL7XX) += el7xx-spi.o el7xx-spi_data_transfer.o +fingerprint-$(CONFIG_SENSORS_GW3X) += gw3x_common.o gw3x_spidev.o +fingerprint-$(CONFIG_SENSORS_GW9558X) += gw9558x_common.o gw9558x_spidev.o +fingerprint-$(CONFIG_SENSORS_QBT2000) += qbt2000_common.o +obj-$(CONFIG_SENSORS_QBT2000) += qbt2000_spidev.o +fingerprint-$(CONFIG_SENSORS_QFS4008) += qfs4008_common.o +obj-$(CONFIG_SENSORS_QFS4008) += qfs4008_spidev.o \ No newline at end of file diff --git a/drivers/fingerprint/fingerprint.h b/drivers/fingerprint/fingerprint.h new file mode 100644 index 000000000000..117eb513c6b1 --- /dev/null +++ b/drivers/fingerprint/fingerprint.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef FINGERPRINT_H_ +#define FINGERPRINT_H_ + +#undef DEBUG /* If you need pr_debug logs, changes this difinition */ +#undef pr_fmt +#define pr_fmt(fmt) "fps_%s: " fmt, __func__ + +#include "fingerprint_sysfs.h" + +#if defined(CONFIG_FINGERPRINT_SECURE) && !defined(CONFIG_SEC_FACTORY) +#define ENABLE_SENSORS_FPRINT_SECURE +#endif + +/* For Sensor Type Check */ +enum { + SENSOR_OOO = -2, + SENSOR_UNKNOWN, + SENSOR_FAILED, + SENSOR_OK, +}; + +#define SENSOR_STATUS_SIZE 4 +static char sensor_status[SENSOR_STATUS_SIZE][10] = {"ooo", "unknown", "failed", "okay"}; + +/* For Finger Detect Mode */ +enum { + DETECT_NORMAL = 0, + DETECT_ADM, /* Always on Detect Mode */ +}; + +#endif diff --git a/drivers/fingerprint/fingerprint_common.c b/drivers/fingerprint/fingerprint_common.c new file mode 100644 index 000000000000..4c8ee3308d61 --- /dev/null +++ b/drivers/fingerprint/fingerprint_common.c @@ -0,0 +1,69 @@ +#include "fingerprint_common.h" + +void set_sensor_type(const int type_value, int *result) +{ + if (type_value >= SENSOR_OOO) { + if (type_value == SENSOR_OOO && *result == SENSOR_FAILED) { + pr_info("maintain type check from out of order :%s\n", + sensor_status[*result + 2]); + } else { + *result = type_value; + pr_info("FP_SET_SENSOR_TYPE :%s\n", *result > 0 ? + sensor_status[3] : sensor_status[*result + 2]); + } + } else { + pr_err("FP_SET_SENSOR_TYPE invalid value %d\n", type_value); + *result = SENSOR_UNKNOWN; + } +} + +void enable_fp_debug_timer(struct debug_logger *logger) +{ + mod_timer(&logger->dbg_timer, round_jiffies_up(jiffies + DEBUG_TIMER_SEC)); +} + +void disable_fp_debug_timer(struct debug_logger *logger) +{ + del_timer_sync(&logger->dbg_timer); + cancel_work_sync(&logger->work_debug); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) +void timer_func(unsigned long ptr) +#else +void timer_func(struct timer_list *t) +#endif +{ + queue_work(g_logger->wq_dbg, &g_logger->work_debug); + enable_fp_debug_timer(g_logger); +} + +int set_fp_debug_timer(struct debug_logger *logger, + void (*func)(struct work_struct *work)) +{ + int rc = 0; +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) + setup_timer(&logger->dbg_timer, timer_func, 0); +#else + timer_setup(&logger->dbg_timer, timer_func, 0); +#endif + logger->wq_dbg = create_singlethread_workqueue("fingerprint_debug_wq"); + if (!logger->wq_dbg) { + rc = -ENOMEM; + pr_err("could not create workqueue\n"); + return rc; + } + INIT_WORK(&logger->work_debug, func); + + return rc; +} + +void set_delay_in_spi_transfer(struct spi_transfer *xfer, unsigned int usec) +{ +#if LINUX_VERSION_CODE > KERNEL_VERSION(5, 10, 0) + xfer->delay.unit = SPI_DELAY_UNIT_USECS; + xfer->delay.value = usec; +#else + xfer->delay_usecs = usec; +#endif +} diff --git a/drivers/fingerprint/fingerprint_common.h b/drivers/fingerprint/fingerprint_common.h new file mode 100644 index 000000000000..74bd650513bb --- /dev/null +++ b/drivers/fingerprint/fingerprint_common.h @@ -0,0 +1,54 @@ +#ifndef _FINGERPRINT_COMMON_H_ +#define _FINGERPRINT_COMMON_H_ + +#include "fingerprint.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG_TIMER_SEC (10 * HZ) + +struct boosting_config { + unsigned int min_cpufreq_limit; + struct pm_qos_request pm_qos; +}; + +struct spi_clk_setting { + bool enabled_clk; + u32 spi_speed; + struct wakeup_source *spi_wake_lock; +#ifdef ENABLE_SENSORS_FPRINT_SECURE + struct clk *fp_spi_pclk; + struct clk *fp_spi_sclk; +#endif +}; + +struct debug_logger { + struct work_struct work_debug; + struct workqueue_struct *wq_dbg; + struct timer_list dbg_timer; + struct device *dev; +}; + +extern struct debug_logger *g_logger; + +void spi_get_ctrldata(struct spi_device *spi); +void set_sensor_type(const int type_value, int *sensortype); +int spi_clk_register(struct spi_clk_setting *clk_setting, struct device *dev); +int spi_clk_unregister(struct spi_clk_setting *clk_setting); +int spi_clk_enable(struct spi_clk_setting *clk_setting); +int spi_clk_disable(struct spi_clk_setting *clk_setting); +int cpu_speedup_enable(struct boosting_config *boosting); +int cpu_speedup_disable(struct boosting_config *boosting); +void enable_fp_debug_timer(struct debug_logger *logger); +void disable_fp_debug_timer(struct debug_logger *logger); +int set_fp_debug_timer(struct debug_logger *logger, + void (*func)(struct work_struct *work)); +void set_delay_in_spi_transfer(struct spi_transfer *xfer, unsigned int usec); + +#endif /* _FINGERPRINT_COMMON_H_ */ diff --git a/drivers/fingerprint/fingerprint_common_lsi.c b/drivers/fingerprint/fingerprint_common_lsi.c new file mode 100644 index 000000000000..d1ba72073cee --- /dev/null +++ b/drivers/fingerprint/fingerprint_common_lsi.c @@ -0,0 +1,150 @@ +#include "fingerprint_common.h" + +#if defined(CONFIG_SENSORS_FINGERPRINT_MODULE) +#if IS_ENABLED(CONFIG_EXYNOS_PM_QOS) || IS_ENABLED(CONFIG_EXYNOS_PM_QOS_MODULE) +#include +static struct exynos_pm_qos_request fingerprint_boost_qos; +#endif +#elif defined(CONFIG_SECURE_OS_BOOSTER_API) +#include +#elif defined(CONFIG_TZDEV_BOOST) +#if defined(CONFIG_TEEGRIS_VERSION) && (CONFIG_TEEGRIS_VERSION >= 4) +#include <../drivers/misc/tzdev/extensions/boost.h> +#else +#include <../drivers/misc/tzdev/tz_boost.h> +#endif +#endif + + +void spi_get_ctrldata(struct spi_device *spi) +{ + +} + +int spi_clk_register(struct spi_clk_setting *clk_setting, struct device *dev) +{ +#ifdef ENABLE_SENSORS_FPRINT_SECURE + clk_setting->fp_spi_pclk = devm_clk_get(dev, "gate_spi_clk"); + if (IS_ERR(clk_setting->fp_spi_pclk)) { + pr_err("Can't get gate_spi_clk\n"); + return PTR_ERR(clk_setting->fp_spi_pclk); + } + + clk_setting->fp_spi_sclk = devm_clk_get(dev, "ipclk_spi"); + if (IS_ERR(clk_setting->fp_spi_sclk)) { + pr_err("Can't get ipclk_spi\n"); + return PTR_ERR(clk_setting->fp_spi_sclk); + } +#endif + + return 0; +} + +int spi_clk_unregister(struct spi_clk_setting *clk_setting) +{ +#ifdef ENABLE_SENSORS_FPRINT_SECURE + clk_put(clk_setting->fp_spi_pclk); + clk_put(clk_setting->fp_spi_sclk); +#endif + + return 0; +} + +int spi_clk_enable(struct spi_clk_setting *clk_setting) +{ + int rc = 0; + +#ifdef ENABLE_SENSORS_FPRINT_SECURE + if (!clk_setting->enabled_clk) { + clk_prepare_enable(clk_setting->fp_spi_pclk); + clk_prepare_enable(clk_setting->fp_spi_sclk); + + if (clk_get_rate(clk_setting->fp_spi_sclk) != (clk_setting->spi_speed * 4)) { + rc = clk_set_rate(clk_setting->fp_spi_sclk, clk_setting->spi_speed * 4); + if (rc < 0) + pr_err("SPI clk set failed: %d\n", rc); + else + pr_debug("Set SPI clock rate: %u(%lu)\n", + clk_setting->spi_speed, clk_get_rate(clk_setting->fp_spi_sclk) / 4); + } else { + pr_debug("Set SPI clock rate: %u(%lu)\n", + clk_setting->spi_speed, clk_get_rate(clk_setting->fp_spi_sclk) / 4); + } + + pr_debug("ENABLE_SPI_CLOCK %d\n", clk_setting->spi_speed); + __pm_stay_awake(clk_setting->spi_wake_lock); + clk_setting->enabled_clk = true; + } +#endif + + return rc; +} + +int spi_clk_disable(struct spi_clk_setting *clk_setting) +{ +#ifdef ENABLE_SENSORS_FPRINT_SECURE + if (clk_setting->enabled_clk) { + clk_disable_unprepare(clk_setting->fp_spi_pclk); + clk_disable_unprepare(clk_setting->fp_spi_sclk); + + __pm_relax(clk_setting->spi_wake_lock); + clk_setting->enabled_clk = false; + pr_debug("DISABLE_SPI_CLOCK\n"); + } +#endif + + return 0; +} + +int cpu_speedup_enable(struct boosting_config *boosting) +{ + int retval = 0; + + pr_info("%s\n", __func__); +/* Module build & TEEGris */ +#if defined(CONFIG_SENSORS_FINGERPRINT_MODULE) +#if IS_ENABLED(CONFIG_EXYNOS_PM_QOS) || IS_ENABLED(CONFIG_EXYNOS_PM_QOS_MODULE) + exynos_pm_qos_add_request(&fingerprint_boost_qos, PM_QOS_CLUSTER1_FREQ_MIN, + PM_QOS_CLUSTER1_FREQ_MAX_DEFAULT_VALUE); +#endif +/* TEEGris */ +#elif defined(CONFIG_TZDEV_BOOST) + tz_boost_enable(); +/* Kinibi */ +#elif defined(CONFIG_SECURE_OS_BOOSTER_API) + retval = secos_booster_start(MAX_PERFORMANCE); + if (retval) + pr_err("booster start failed. (%d)\n", retval); + +#else + pr_info("FP_CPU_SPEEDUP does not supported\n"); +#endif + + return retval; +} + +int cpu_speedup_disable(struct boosting_config *boosting) +{ + int retval = 0; + + pr_info("%s\n", __func__); +/* Module build & TEEGris */ +#if defined(CONFIG_SENSORS_FINGERPRINT_MODULE) +#if IS_ENABLED(CONFIG_EXYNOS_PM_QOS) || IS_ENABLED(CONFIG_EXYNOS_PM_QOS_MODULE) + exynos_pm_qos_remove_request(&fingerprint_boost_qos); +#endif +/* TEEGris */ +#elif defined(CONFIG_TZDEV_BOOST) + tz_boost_disable(); +/* Kinibi */ +#elif defined(CONFIG_SECURE_OS_BOOSTER_API) + retval = secos_booster_stop(); + if (retval) + pr_err("booster stop failed. (%d)\n", retval); + +#else + pr_info("FP_CPU_SPEEDUP does not supported\n"); +#endif + + return retval; +} diff --git a/drivers/fingerprint/fingerprint_common_mtk.c b/drivers/fingerprint/fingerprint_common_mtk.c new file mode 100644 index 000000000000..fbac42044128 --- /dev/null +++ b/drivers/fingerprint/fingerprint_common_mtk.c @@ -0,0 +1,103 @@ +#include "fingerprint_common.h" +#include +#include + + +void spi_get_ctrldata(struct spi_device *spi) +{ +#ifndef ENABLE_SENSORS_FPRINT_SECURE + struct device_node *np, *data_np = NULL; + u32 tckdly = 0; + + np = spi->dev.of_node; + if (!np) { + pr_err("%s : device node not founded\n", __func__); + return; + } + + data_np = of_get_child_by_name(np, "controller-data"); + if (!data_np) { + pr_err("%s : controller-data not founded\n", __func__); + return; + } + + of_property_read_u32(data_np, "mediatek,tckdly", &tckdly); + ((struct mtk_chip_config *)spi->controller_data)->tick_delay = tckdly; + + of_node_put(data_np); + pr_info("%s done\n", __func__); +#endif +} + +int spi_clk_register(struct spi_clk_setting *clk_setting, struct device *dev) +{ +#ifdef ENABLE_SENSORS_FPRINT_SECURE + clk_setting->fp_spi_pclk = devm_clk_get(dev, "sel-clk"); + if (IS_ERR(clk_setting->fp_spi_pclk)) { + pr_err("Can't get sel-clk\n"); + return PTR_ERR(clk_setting->fp_spi_pclk); + } + + clk_setting->fp_spi_sclk = devm_clk_get(dev, "spi-clk"); + if (IS_ERR(clk_setting->fp_spi_sclk)) { + pr_err("Can't get spi-clk\n"); + return PTR_ERR(clk_setting->fp_spi_sclk); + } +#endif + + return 0; +} + +int spi_clk_unregister(struct spi_clk_setting *clk_setting) +{ +#ifdef ENABLE_SENSORS_FPRINT_SECURE + clk_put(clk_setting->fp_spi_pclk); + clk_put(clk_setting->fp_spi_sclk); +#endif + + return 0; +} + +int spi_clk_enable(struct spi_clk_setting *clk_setting) +{ + int retval = 0; + +#ifdef ENABLE_SENSORS_FPRINT_SECURE + if (!clk_setting->enabled_clk) { + retval = clk_prepare_enable(clk_setting->fp_spi_sclk); + if (retval < 0) { + pr_err("Unable to enable spi clk\n"); + return retval; + } + pr_debug("ENABLE_SPI_CLOCK %d\n", clk_setting->spi_speed); + __pm_stay_awake(clk_setting->spi_wake_lock); + clk_setting->enabled_clk = true; + } +#endif + + return retval; +} + +int spi_clk_disable(struct spi_clk_setting *clk_setting) +{ +#ifdef ENABLE_SENSORS_FPRINT_SECURE + if (clk_setting->enabled_clk) { + clk_disable_unprepare(clk_setting->fp_spi_sclk); + __pm_relax(clk_setting->spi_wake_lock); + clk_setting->enabled_clk = false; + pr_debug("DISABLE_SPI_CLOCK\n"); + } +#endif + + return 0; +} + +int cpu_speedup_enable(struct boosting_config *boosting) +{ + return 0; +} + +int cpu_speedup_disable(struct boosting_config *boosting) +{ + return 0; +} diff --git a/drivers/fingerprint/fingerprint_common_qcom.c b/drivers/fingerprint/fingerprint_common_qcom.c new file mode 100644 index 000000000000..9e29de4d0df5 --- /dev/null +++ b/drivers/fingerprint/fingerprint_common_qcom.c @@ -0,0 +1,89 @@ +#include "fingerprint_common.h" +#include +#if defined(CONFIG_CPU_FREQ_LIMIT) || defined(CONFIG_CPU_FREQ_LIMIT_USERSPACE) +#if (KERNEL_VERSION(5, 4, 0) > LINUX_VERSION_CODE) +#include +#else +#include +extern int set_freq_limit(unsigned int id, unsigned int freq); +#endif +#endif + +#define FINGER_ID 2 +#define LIMIT_RELEASE -1 + + +void spi_get_ctrldata(struct spi_device *spi) +{ + +} + +int spi_clk_register(struct spi_clk_setting *clk_setting, struct device *dev) +{ + return 0; +} + +int spi_clk_unregister(struct spi_clk_setting *clk_setting) +{ + return 0; +} + +int spi_clk_enable(struct spi_clk_setting *clk_setting) +{ +#ifdef ENABLE_SENSORS_FPRINT_SECURE + if (!clk_setting->enabled_clk) { + __pm_stay_awake(clk_setting->spi_wake_lock); + clk_setting->enabled_clk = true; + } +#endif + + return 0; +} + +int spi_clk_disable(struct spi_clk_setting *clk_setting) +{ +#ifdef ENABLE_SENSORS_FPRINT_SECURE + if (clk_setting->enabled_clk) { + __pm_relax(clk_setting->spi_wake_lock); + clk_setting->enabled_clk = false; + } +#endif + + return 0; +} + +int cpu_speedup_enable(struct boosting_config *boosting) +{ + int retval = 0; +#if defined(CONFIG_CPU_FREQ_LIMIT) || defined(CONFIG_CPU_FREQ_LIMIT_USERSPACE) + if (boosting->min_cpufreq_limit) { + pr_debug("%s\n", __func__); + pm_qos_add_request(&boosting->pm_qos, PM_QOS_CPU_DMA_LATENCY, 0); + retval = set_freq_limit(FINGER_ID, boosting->min_cpufreq_limit); + if (retval) + pr_err("booster start failed. (%d)\n", retval); + } +#else + pr_debug("FP_CPU_SPEEDUP does not supported\n"); +#endif + + return retval; +} + +int cpu_speedup_disable(struct boosting_config *boosting) +{ + int retval = 0; +#if defined(CONFIG_CPU_FREQ_LIMIT) || defined(CONFIG_CPU_FREQ_LIMIT_USERSPACE) + if (boosting->min_cpufreq_limit) { + pr_debug("%s\n", __func__); + retval = set_freq_limit(FINGER_ID, LIMIT_RELEASE); + if (retval) + pr_err("booster stop failed. (%d)\n", retval); + pm_qos_remove_request(&boosting->pm_qos); + } +#else + pr_debug("FP_CPU_SPEEDUP does not supported\n"); +#endif + + return retval; +} diff --git a/drivers/fingerprint/fingerprint_sysfs.c b/drivers/fingerprint/fingerprint_sysfs.c new file mode 100644 index 000000000000..eb310fae0cb9 --- /dev/null +++ b/drivers/fingerprint/fingerprint_sysfs.c @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2017, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "fingerprint_sysfs.h" + +struct class *fingerprint_class; +EXPORT_SYMBOL_GPL(fingerprint_class); + +/* + * Create sysfs interface + */ +void set_fingerprint_attr(struct device *dev, + struct device_attribute *attributes[]) +{ + int i; + + for (i = 0; attributes[i] != NULL; i++) + if ((device_create_file(dev, attributes[i])) < 0) + pr_err("%s: fail device_create_file! %d\n", __func__, i); +} + +int fingerprint_register(struct device *dev, void *drvdata, + struct device_attribute *attributes[], char *name) +{ + int ret = 0; + + if (!fingerprint_class) { +#if (KERNEL_VERSION(6, 3, 0) <= LINUX_VERSION_CODE) + fingerprint_class = class_create("fingerprint"); +#else + fingerprint_class = class_create(THIS_MODULE, "fingerprint"); +#endif + if (IS_ERR(fingerprint_class)) + return PTR_ERR(fingerprint_class); + } + + dev = device_create(fingerprint_class, NULL, 0, drvdata, "%s", name); + + if (IS_ERR(dev)) { + ret = PTR_ERR(dev); + pr_err("%s: device_create failed! %d\n", __func__, ret); + return ret; + } + + set_fingerprint_attr(dev, attributes); + + return 0; +} +EXPORT_SYMBOL_GPL(fingerprint_register); + +void fingerprint_unregister(struct device *dev, + struct device_attribute *attributes[]) +{ + int i; + + for (i = 0; attributes[i] != NULL; i++) + device_remove_file(dev, attributes[i]); +} +EXPORT_SYMBOL_GPL(fingerprint_unregister); + +void destroy_fingerprint_class(void) +{ + if (fingerprint_class) { + class_destroy(fingerprint_class); + fingerprint_class = NULL; + } +} +EXPORT_SYMBOL_GPL(destroy_fingerprint_class); + +static int __init fingerprint_class_init(void) +{ + pr_info("%s\n", __func__); +#if (KERNEL_VERSION(6, 3, 0) <= LINUX_VERSION_CODE) + fingerprint_class = class_create("fingerprint"); +#else + fingerprint_class = class_create(THIS_MODULE, "fingerprint"); +#endif + if (IS_ERR(fingerprint_class)) { + pr_err("%s, create fingerprint_class is failed.(err=%d)\n", + __func__, IS_ERR(fingerprint_class)); + return PTR_ERR(fingerprint_class); + } + + fingerprint_class->dev_uevent = NULL; + + return 0; +} + +static void __exit fingerprint_class_exit(void) +{ + if (fingerprint_class) { + class_destroy(fingerprint_class); + fingerprint_class = NULL; + } +} + +subsys_initcall(fingerprint_class_init); +module_exit(fingerprint_class_exit); + +MODULE_DESCRIPTION("fingerprint sysfs class"); +MODULE_LICENSE("GPL"); diff --git a/drivers/fingerprint/fingerprint_sysfs.h b/drivers/fingerprint/fingerprint_sysfs.h new file mode 100644 index 000000000000..094f6ac0f12f --- /dev/null +++ b/drivers/fingerprint/fingerprint_sysfs.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef FINGERPRINTSYSFS_H_ +#define FINGERPRINTSYSFS_H_ + +#include +#include +#include +#include +#include +#include +#include + +void set_fingerprint_attr(struct device *dev, + struct device_attribute *attributes[]); +int fingerprint_register(struct device *dev, void *drvdata, + struct device_attribute *attributes[], char *name); +void fingerprint_unregister(struct device *dev, + struct device_attribute *attributes[]); +void destroy_fingerprint_class(void); + +#endif diff --git a/drivers/fingerprint/qfs4008_common.c b/drivers/fingerprint/qfs4008_common.c new file mode 100644 index 000000000000..eab398b388aa --- /dev/null +++ b/drivers/fingerprint/qfs4008_common.c @@ -0,0 +1,1359 @@ +/* + * Copyright (C) 2018 Samsung Electronics. 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 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 "fingerprint.h" +#include "qfs4008_common.h" + +/* + * struct ipc_msg_type_to_fw_event - + * entry in mapping between an IPC message type to a firmware event + * @msg_type - IPC message type, as reported by firmware + * @fw_event - corresponding firmware event code to report to driver client + */ +struct ipc_msg_type_to_fw_event { + uint32_t msg_type; + enum qfs4008_fw_event fw_event; +}; + +/* mapping between firmware IPC message types to HLOS firmware events */ +struct ipc_msg_type_to_fw_event g_msg_to_event[] = { + {IPC_MSG_ID_CBGE_REQUIRED, FW_EVENT_CBGE_REQUIRED}, + {IPC_MSG_ID_FINGER_ON_SENSOR, FW_EVENT_FINGER_DOWN}, + {IPC_MSG_ID_FINGER_OFF_SENSOR, FW_EVENT_FINGER_UP}, +}; + +static ssize_t vendor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR); +} + +static ssize_t name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct qfs4008_drvdata *drvdata = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%s\n", drvdata->chipid); +} + +static ssize_t adm_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", DETECT_ADM); +} + +static ssize_t bfs_values_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct qfs4008_drvdata *drvdata = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "\"FP_SPICLK\":\"%d\"\n", + drvdata->clk_setting->spi_speed); +} + +static ssize_t type_check_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct qfs4008_drvdata *drvdata = dev_get_drvdata(dev); + + pr_info("%s\n", drvdata->sensortype > 0 ? drvdata->chipid : sensor_status[drvdata->sensortype + 2]); + return snprintf(buf, PAGE_SIZE, "%d\n", drvdata->sensortype); +} + +static ssize_t position_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct qfs4008_drvdata *drvdata = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%s\n", drvdata->sensor_position); +} + +static ssize_t cbgecnt_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct qfs4008_drvdata *drvdata = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", drvdata->cbge_count); +} + +static ssize_t cbgecnt_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t size) +{ + struct qfs4008_drvdata *drvdata = dev_get_drvdata(dev); + + if (sysfs_streq(buf, "c")) { + drvdata->cbge_count = 0; + pr_info("initialization is done\n"); + } + return size; +} + +static ssize_t intcnt_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct qfs4008_drvdata *drvdata = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", drvdata->wuhb_count); +} + +static ssize_t intcnt_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t size) +{ + struct qfs4008_drvdata *drvdata = dev_get_drvdata(dev); + + if (sysfs_streq(buf, "c")) { + drvdata->wuhb_count = 0; + pr_info("initialization is done\n"); + } + return size; +} + +static ssize_t resetcnt_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct qfs4008_drvdata *drvdata = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", drvdata->reset_count); +} + +static ssize_t resetcnt_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t size) +{ + struct qfs4008_drvdata *drvdata = dev_get_drvdata(dev); + + if (sysfs_streq(buf, "c")) { + drvdata->reset_count = 0; + pr_info("initialization is done\n"); + } + return size; +} + +static ssize_t wuhbtest_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct qfs4008_drvdata *drvdata = dev_get_drvdata(dev); + int rc = 0; + int gpio_value = 0; + + gpio_value = gpio_get_value(drvdata->fd_gpio.gpio); + if (gpio_value == 0) { /* Finger Leave */ + pr_info("wuhbtest Finger Leave Ok\n"); + rc = 1; + } else { /* Finger Down */ + pr_err("wuhbtest Finger Leave NG\n"); + rc = 0; + } + + return snprintf(buf, PAGE_SIZE, "%d\n", rc); +} + +static DEVICE_ATTR_RO(bfs_values); +static DEVICE_ATTR_RO(type_check); +static DEVICE_ATTR_RO(vendor); +static DEVICE_ATTR_RO(name); +static DEVICE_ATTR_RO(adm); +static DEVICE_ATTR_RO(position); +static DEVICE_ATTR_RW(cbgecnt); +static DEVICE_ATTR_RW(intcnt); +static DEVICE_ATTR_RW(resetcnt); +static DEVICE_ATTR_RO(wuhbtest); + +static struct device_attribute *fp_attrs[] = { + &dev_attr_bfs_values, + &dev_attr_type_check, + &dev_attr_vendor, + &dev_attr_name, + &dev_attr_adm, + &dev_attr_position, + &dev_attr_cbgecnt, + &dev_attr_intcnt, + &dev_attr_resetcnt, + &dev_attr_wuhbtest, + NULL, +}; + +int qfs4008_pinctrl_register(struct qfs4008_drvdata *drvdata) +{ + drvdata->p = devm_pinctrl_get(drvdata->dev); + if (IS_ERR(drvdata->p)) { + pr_err("failed pinctrl_get\n"); + goto pinctrl_get_exit; + } + +#if !defined(ENABLE_SENSORS_FPRINT_SECURE) || defined(DISABLED_GPIO_PROTECTION) + drvdata->pins_poweroff = pinctrl_lookup_state(drvdata->p, "pins_poweroff"); + if (IS_ERR(drvdata->pins_poweroff)) { + pr_err("could not get pins sleep_state (%li)\n", + PTR_ERR(drvdata->pins_poweroff)); + goto pinctrl_register_exit; + } + + drvdata->pins_poweron = pinctrl_lookup_state(drvdata->p, "pins_poweron"); + if (IS_ERR(drvdata->pins_poweron)) { + pr_err("could not get pins idle_state (%li)\n", + PTR_ERR(drvdata->pins_poweron)); + goto pinctrl_register_exit; + } +#endif + pr_info("finished\n"); + return 0; + +#if !defined(ENABLE_SENSORS_FPRINT_SECURE) || defined(DISABLED_GPIO_PROTECTION) +pinctrl_register_exit: + drvdata->pins_poweron = NULL; + drvdata->pins_poweroff = NULL; +#endif +pinctrl_get_exit: + pr_err("failed\n"); + return -ENODEV; +} + +static int qfs4008_power_control(struct qfs4008_drvdata *drvdata, int onoff) +{ + int rc = 0; + + if (regulator_is_enabled(drvdata->regulator_1p8) == onoff && + regulator_is_enabled(drvdata->regulator_3p0) == onoff) { + pr_err("regulator already turned %s\n", onoff ? "on" : "off"); + } else { + if (onoff) { + rc = regulator_enable(drvdata->regulator_3p0); + if (rc) + pr_err("regulator_3p0 enable failed, rc=%d\n", rc); + usleep_range(2000, 2050); + rc = regulator_enable(drvdata->regulator_1p8); + if (rc) + pr_err("regulator_1p8 enable failed, rc=%d\n", rc); + usleep_range(2000, 2050); + } else { + rc = regulator_disable(drvdata->regulator_1p8); + if (rc) + pr_err("regulator_1p8 disable failed, rc=%d\n", rc); + rc = regulator_disable(drvdata->regulator_3p0); + if (rc) + pr_err("regulator_3p0 disable failed, rc=%d\n", rc); + } + } + + if (!rc) { +#if !defined(ENABLE_SENSORS_FPRINT_SECURE) || defined(DISABLED_GPIO_PROTECTION) + if (onoff) { + if (drvdata->pins_poweron) { + rc = pinctrl_select_state(drvdata->p, drvdata->pins_poweron); + pr_debug("pinctrl for poweron. rc=%d\n", rc); + } + } else { + if (drvdata->pins_poweroff) { + rc = pinctrl_select_state(drvdata->p, drvdata->pins_poweroff); + pr_debug("pinctrl for poweroff. rc=%d\n", rc); + } + } +#endif + drvdata->enabled_ldo = onoff; + } + pr_info("%s, rc=%d\n", onoff ? "ON" : "OFF", rc); + + return rc; +} + +static int qfs4008_enable_spi_clock(struct qfs4008_drvdata *drvdata) +{ + int rc = 0; + +#if !defined(ENABLE_SENSORS_FPRINT_SECURE) || defined(DISABLED_GPIO_PROTECTION) + if (drvdata->pins_poweron) { + rc = pinctrl_select_state(drvdata->p, drvdata->pins_poweron); + pr_info("pinctrl for spi_active. rc=%d\n", rc); + } +#endif + rc = spi_clk_enable(drvdata->clk_setting); + return rc; +} + +static int qfs4008_disable_spi_clock(struct qfs4008_drvdata *drvdata) +{ + int rc = 0; + +#if !defined(ENABLE_SENSORS_FPRINT_SECURE) || defined(DISABLED_GPIO_PROTECTION) + if (drvdata->pins_poweroff) { + rc = pinctrl_select_state(drvdata->p, drvdata->pins_poweroff); + pr_info("pinctrl for spi_inactive. rc=%d\n", rc); + } +#endif + rc = spi_clk_disable(drvdata->clk_setting); + return rc; +} + +static int qfs4008_enable_ipc(struct qfs4008_drvdata *drvdata) +{ + int rc = 0; + + if (drvdata->fw_ipc.gpio) { + if (drvdata->enabled_ipc) { + rc = -EINVAL; + pr_err("already enabled ipc\n"); + } else { + enable_irq(drvdata->fw_ipc.irq); + enable_irq_wake(drvdata->fw_ipc.irq); + drvdata->enabled_ipc = true; + } + } + return rc; +} + +static int qfs4008_disable_ipc(struct qfs4008_drvdata *drvdata) +{ + int rc = 0; + + if (drvdata->fw_ipc.gpio) { + if (drvdata->enabled_ipc) { + disable_irq_wake(drvdata->fw_ipc.irq); + disable_irq(drvdata->fw_ipc.irq); + drvdata->enabled_ipc = false; + } else { + rc = -EINVAL; + pr_err("already disabled ipc\n"); + } + } + return rc; +} + +static int qfs4008_enable_wuhb(struct qfs4008_drvdata *drvdata) +{ + int rc = 0; + int gpio = 0; + + if (drvdata->fd_gpio.gpio) { + if (drvdata->enabled_wuhb) { + rc = -EINVAL; + pr_err("already enabled wuhb\n"); + } else { + enable_irq(drvdata->fd_gpio.irq); + enable_irq_wake(drvdata->fd_gpio.irq); + drvdata->enabled_wuhb = true; + /* To prevent FingerUp Missing issue. */ + gpio = gpio_get_value(drvdata->fd_gpio.gpio); + if (drvdata->fd_gpio.last_gpio_state == FINGER_DOWN_GPIO_STATE && + gpio == FINGER_LEAVE_GPIO_STATE) { + pr_info("Finger leave event already occurred. %d, %d\n", + drvdata->fd_gpio.last_gpio_state, gpio); + + __pm_wakeup_event(drvdata->fp_signal_lock, + msecs_to_jiffies(QFS4008_WAKELOCK_HOLD_TIME)); + schedule_work(&drvdata->fd_gpio.work); + } + } + } + return rc; +} + +static int qfs4008_disable_wuhb(struct qfs4008_drvdata *drvdata) +{ + int rc = 0; + + if (drvdata->fd_gpio.gpio) { + if (drvdata->enabled_wuhb) { + disable_irq(drvdata->fd_gpio.irq); + disable_irq_wake(drvdata->fd_gpio.irq); + drvdata->enabled_wuhb = false; + } else { + rc = -EINVAL; + pr_err("already disabled wuhb\n"); + } + } + return rc; +} + +/** + * qfs4008_open() - Function called when user space opens device. + * Successful if driver not currently open. + * @inode: ptr to inode object + * @file: ptr to file object + * + * Return: 0 on success. Error code on failure. + */ +static int qfs4008_open(struct inode *inode, struct file *file) +{ + struct qfs4008_drvdata *drvdata = NULL; + int rc = 0; + int minor_no = iminor(inode); + + if (minor_no == MINOR_NUM_FD) { + drvdata = container_of(inode->i_cdev, struct qfs4008_drvdata, + qfs4008_fd_cdev); + } else if (minor_no == MINOR_NUM_IPC) { + drvdata = container_of(inode->i_cdev, struct qfs4008_drvdata, + qfs4008_ipc_cdev); + } else { + pr_err("Invalid minor number\n"); + return -EINVAL; + } + file->private_data = drvdata; + + /* disallowing concurrent opens */ + if (minor_no == MINOR_NUM_FD && !atomic_dec_and_test(&drvdata->fd_available)) { + atomic_inc(&drvdata->fd_available); + pr_err("fd_unavailable\n"); + rc = -EBUSY; + } else if (minor_no == MINOR_NUM_IPC && !atomic_dec_and_test(&drvdata->ipc_available)) { + atomic_inc(&drvdata->ipc_available); + pr_err("ipc_unavailable\n"); + rc = -EBUSY; + } + + pr_debug("minor_no=%d, rc=%d,%d,%d\n", minor_no, rc, + atomic_read(&drvdata->fd_available), + atomic_read(&drvdata->ipc_available)); + return rc; +} + +/** + * qfs4008_release() - Function called when user space closes device. + + * @inode: ptr to inode object + * @file: ptr to file object + * + * Return: 0 on success. Error code on failure. + */ +static int qfs4008_release(struct inode *inode, struct file *file) +{ + struct qfs4008_drvdata *drvdata; + int minor_no; + int rc = 0; + + if (!file || !file->private_data) { + pr_err("%s: NULL pointer passed\n", __func__); + return -EINVAL; + } + drvdata = file->private_data; + minor_no = iminor(inode); + if (minor_no == MINOR_NUM_FD) { + atomic_inc(&drvdata->fd_available); + } else if (minor_no == MINOR_NUM_IPC) { + atomic_inc(&drvdata->ipc_available); + } else { + pr_err("Invalid minor number\n"); + rc = -EINVAL; + } + pr_debug("minor_no=%d, rc=%d,%d,%d\n", minor_no, rc, + atomic_read(&drvdata->fd_available), + atomic_read(&drvdata->ipc_available)); + return rc; +} + +/** + * qfs4008_ioctl() - Function called when user space calls ioctl. + * @file: struct file - not used + * @cmd: cmd identifier:QFS4008_LOAD_APP,QFS4008_UNLOAD_APP, + * QFS4008_SEND_TZCMD + * @arg: ptr to relevant structe: either qfs4008_app or + * qfs4008_send_tz_cmd depending on which cmd is passed + * + * Return: 0 on success. Error code on failure. + */ +static long qfs4008_ioctl( + struct file *file, unsigned int cmd, unsigned long arg) +{ + int rc = 0; + int data = 0; + void __user *priv_arg = (void __user *)arg; + struct qfs4008_drvdata *drvdata; + + if (!file || !file->private_data) { + pr_err("%s: NULL pointer passed\n", __func__); + return -EINVAL; + } + + drvdata = file->private_data; + + if (IS_ERR(priv_arg)) { + pr_err("invalid user space pointer %lu\n", arg); + return -EINVAL; + } + + mutex_lock(&drvdata->ioctl_mutex); + + switch (cmd) { + case QFS4008_IS_WHUB_CONNECTED: + break; + case QFS4008_POWER_CONTROL: + if (copy_from_user(&data, (void *)arg, sizeof(int)) != 0) { + pr_err("Failed copy from user.(POWER_CONTROL)\n"); + rc = -EFAULT; + goto ioctl_failed; + } + if (drvdata->enabled_ldo != data) { + pr_debug("POWER_CONTROL\n"); + qfs4008_power_control(drvdata, data); + } + break; + case QFS4008_ENABLE_SPI_CLOCK: + pr_info("ENABLE_SPI_CLOCK\n"); + rc = qfs4008_enable_spi_clock(drvdata); + break; + case QFS4008_DISABLE_SPI_CLOCK: + pr_info("DISABLE_SPI_CLOCK\n"); + rc = qfs4008_disable_spi_clock(drvdata); + break; + case QFS4008_ENABLE_IPC: + pr_info("ENABLE_IPC\n"); + rc = qfs4008_enable_ipc(drvdata); + break; + case QFS4008_DISABLE_IPC: + pr_info("DISABLE_IPC\n"); + rc = qfs4008_disable_ipc(drvdata); + break; + case QFS4008_ENABLE_WUHB: + pr_info("ENABLE_WUHB\n"); + rc = qfs4008_enable_wuhb(drvdata); + break; + case QFS4008_DISABLE_WUHB: + pr_info("DISABLE_WUHB\n"); + rc = qfs4008_disable_wuhb(drvdata); + break; + case QFS4008_CPU_SPEEDUP: + if (copy_from_user(&data, (void *)arg, sizeof(int)) != 0) { + pr_err("Failed copy from user.(SPEEDUP)\n"); + rc = -EFAULT; + goto ioctl_failed; + } + if (data) + rc = cpu_speedup_enable(drvdata->boosting); + else + rc = cpu_speedup_disable(drvdata->boosting); + break; + case QFS4008_SET_SENSOR_TYPE: + if (copy_from_user(&data, (void *)arg, sizeof(int)) != 0) { + pr_err("Failed to copy sensor type from user to kernel\n"); + rc = -EFAULT; + goto ioctl_failed; + } + set_sensor_type(data, &drvdata->sensortype); + break; + case QFS4008_SET_LOCKSCREEN: + break; + case QFS4008_SENSOR_RESET: + drvdata->reset_count++; + pr_err("SENSOR_RESET\n"); + break; + case QFS4008_SENSOR_TEST: + if (copy_from_user(&data, (void *)arg, sizeof(int)) != 0) { + pr_err("Failed to copy BGECAL from user to kernel\n"); + rc = -EFAULT; + goto ioctl_failed; + } +#ifndef ENABLE_SENSORS_FPRINT_SECURE //only for factory + if (data == QFS4008_SENSORTEST_DONE) + pr_info("SENSORTEST Finished\n"); + else + pr_info("SENSORTEST Start : 0x%x\n", data); +#endif + break; + case QFS4008_GET_MODELINFO: + pr_info("QFS4008_GET_MODELINFO : %s\n", drvdata->model_info); + if (copy_to_user((void __user *)priv_arg, drvdata->model_info, 10)) { + pr_err("Failed to copy GET_MODELINFO to user\n"); + rc = -EFAULT; + } + break; + case QFS4008_SET_TOUCH_IGNORE: + if (copy_from_user(&data, (void *)arg, sizeof(int)) != 0) { + pr_err("Failed copy from user.(TOUCH_IGNORE)\n"); + rc = -EFAULT; + goto ioctl_failed; + } + drvdata->touch_ignore = data; + pr_info("QFS4008_SET_TOUCH_IGNORE : %d, %d\n", drvdata->touch_ignore, data); + break; + default: + pr_err("invalid cmd %d\n", cmd); + rc = -ENOIOCTLCMD; + } + +ioctl_failed: + mutex_unlock(&drvdata->ioctl_mutex); + return rc; +} + + +static int get_events_fifo_len_locked(struct qfs4008_drvdata *drvdata, int minor_no) +{ + int len = 0; + + if (minor_no == MINOR_NUM_FD) { + mutex_lock(&drvdata->fd_events_mutex); + len = kfifo_len(&drvdata->fd_events); + mutex_unlock(&drvdata->fd_events_mutex); + } else if (minor_no == MINOR_NUM_IPC) { + mutex_lock(&drvdata->ipc_events_mutex); + len = kfifo_len(&drvdata->ipc_events); + mutex_unlock(&drvdata->ipc_events_mutex); + } + + return len; +} + +static ssize_t qfs4008_read(struct file *filp, char __user *ubuf, + size_t cnt, loff_t *ppos) +{ + struct fw_event_desc fw_event; + struct qfs4008_drvdata *drvdata = filp->private_data; + wait_queue_head_t *read_wait_queue = NULL; + int rc = 0; + int minor_no = iminor(filp->f_path.dentry->d_inode); + int fifo_len = get_events_fifo_len_locked(drvdata, minor_no); + + if (cnt < sizeof(fw_event.ev)) { + pr_err("Num bytes to read is too small, numBytes=%zd\n", cnt); + return -EINVAL; + } + + if (minor_no == MINOR_NUM_FD) { + read_wait_queue = &drvdata->read_wait_queue_fd; + } else if (minor_no == MINOR_NUM_IPC) { + read_wait_queue = &drvdata->read_wait_queue_ipc; + } else { + pr_err("Invalid minor number\n"); + return -EINVAL; + } + + while (fifo_len == 0) { + if (filp->f_flags & O_NONBLOCK) { + pr_debug("fw_events fifo: empty, returning\n"); + return -EAGAIN; + } + pr_debug("fw_events fifo: empty, waiting\n"); + if (wait_event_interruptible(*read_wait_queue, + (get_events_fifo_len_locked(drvdata, minor_no) > 0))) + return -ERESTARTSYS; + + fifo_len = get_events_fifo_len_locked(drvdata, minor_no); + } + + if (minor_no == MINOR_NUM_FD) { + mutex_lock(&drvdata->fd_events_mutex); + rc = kfifo_get(&drvdata->fd_events, &fw_event); + mutex_unlock(&drvdata->fd_events_mutex); + } else if (minor_no == MINOR_NUM_IPC) { + mutex_lock(&drvdata->ipc_events_mutex); + rc = kfifo_get(&drvdata->ipc_events, &fw_event); + mutex_unlock(&drvdata->ipc_events_mutex); + } else { + pr_err("Invalid minor number\n"); + } + + if (!rc) { + pr_err("fw_events fifo: unexpectedly empty\n"); + return -EINVAL; + } + + rc = copy_to_user(ubuf, &fw_event.ev, sizeof(fw_event.ev)); + if (rc != 0) { + pr_err("Failed to copy_to_user:%d - event:%d, minor:%d\n", + rc, (int)fw_event.ev, minor_no); + } else { + if (minor_no == MINOR_NUM_FD) { + pr_info("Firmware event %d at minor no %d read at time %lu uS, mutex_unlock\n", + (int)fw_event.ev, minor_no, + (unsigned long)ktime_to_us(ktime_get())); + } else { + pr_info("Firmware event %d at minor no %d read at time %lu uS\n", + (int)fw_event.ev, minor_no, + (unsigned long)ktime_to_us(ktime_get())); + } + } + return rc; +} + +static unsigned int qfs4008_poll(struct file *filp, + struct poll_table_struct *wait) +{ + struct qfs4008_drvdata *drvdata = filp->private_data; + unsigned int mask = 0; + int minor_no = iminor(filp->f_path.dentry->d_inode); + + if (minor_no == MINOR_NUM_FD) { + poll_wait(filp, &drvdata->read_wait_queue_fd, wait); + if (kfifo_len(&drvdata->fd_events) > 0) + mask |= (POLLIN | POLLRDNORM); + } else if (minor_no == MINOR_NUM_IPC) { + poll_wait(filp, &drvdata->read_wait_queue_ipc, wait); + if (kfifo_len(&drvdata->ipc_events) > 0) + mask |= (POLLIN | POLLRDNORM); + } else { + pr_err("Invalid minor number\n"); + return -EINVAL; + } + + return mask; +} + +static const struct file_operations qfs4008_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = qfs4008_ioctl, + .open = qfs4008_open, + .release = qfs4008_release, + .read = qfs4008_read, + .poll = qfs4008_poll +}; + +static int qfs4008_dev_register(struct qfs4008_drvdata *drvdata) +{ + dev_t dev_no, major_no; + int rc = 0; + struct device *dev = drvdata->dev; + + rc = alloc_chrdev_region(&dev_no, 0, 2, QFS4008_DEV); + if (rc) { + pr_err("alloc_chrdev_region failed %d\n", rc); + goto err_alloc; + } + major_no = MAJOR(dev_no); + + cdev_init(&drvdata->qfs4008_fd_cdev, &qfs4008_fops); + drvdata->qfs4008_fd_cdev.owner = THIS_MODULE; + rc = cdev_add(&drvdata->qfs4008_fd_cdev, + MKDEV(major_no, MINOR_NUM_FD), 1); + if (rc) { + pr_err("cdev_add failed for fd %d\n", rc); + goto err_cdev_add; + } + + cdev_init(&drvdata->qfs4008_ipc_cdev, &qfs4008_fops); + drvdata->qfs4008_ipc_cdev.owner = THIS_MODULE; + rc = cdev_add(&drvdata->qfs4008_ipc_cdev, + MKDEV(major_no, MINOR_NUM_IPC), 1); + if (rc) { + pr_err("cdev_add failed for ipc %d\n", rc); + goto err_cdev_add; + } + +#if (KERNEL_VERSION(6, 3, 0) <= LINUX_VERSION_CODE) + drvdata->qfs4008_class = class_create(QFS4008_DEV); +#else + drvdata->qfs4008_class = class_create(THIS_MODULE, QFS4008_DEV); +#endif + if (IS_ERR(drvdata->qfs4008_class)) { + rc = PTR_ERR(drvdata->qfs4008_class); + pr_err("class_create failed %d\n", rc); + goto err_class_create; + } + + dev = device_create(drvdata->qfs4008_class, NULL, + drvdata->qfs4008_fd_cdev.dev, + drvdata, "%s_fd", QFS4008_DEV); + if (IS_ERR(dev)) { + rc = PTR_ERR(dev); + pr_err("fd device_create failed %d\n", rc); + goto err_dev_create_fd; + } + + dev = device_create(drvdata->qfs4008_class, NULL, + drvdata->qfs4008_ipc_cdev.dev, + drvdata, "%s_ipc", QFS4008_DEV); + if (IS_ERR(dev)) { + rc = PTR_ERR(dev); + pr_err("ipc device_create failed %d\n", rc); + goto err_dev_create_ipc; + } + pr_info("finished\n"); + return 0; +err_dev_create_ipc: + device_destroy(drvdata->qfs4008_class, drvdata->qfs4008_fd_cdev.dev); +err_dev_create_fd: + class_destroy(drvdata->qfs4008_class); +err_class_create: + cdev_del(&drvdata->qfs4008_fd_cdev); + cdev_del(&drvdata->qfs4008_ipc_cdev); +err_cdev_add: + unregister_chrdev_region(drvdata->qfs4008_fd_cdev.dev, 1); + unregister_chrdev_region(drvdata->qfs4008_ipc_cdev.dev, 1); +err_alloc: + return rc; +} + + +static void qfs4008_gpio_report_event(struct qfs4008_drvdata *drvdata) +{ + int state; + struct fw_event_desc fw_event; + + state = (gpio_get_value(drvdata->fd_gpio.gpio) ? FINGER_DOWN_GPIO_STATE : FINGER_LEAVE_GPIO_STATE) + ^ drvdata->fd_gpio.active_low; + + if (state == drvdata->fd_gpio.last_gpio_state) { + pr_err("skip the report_event. this event already reported, last_gpio:%d\n", state); + return; + } + + if (drvdata->touch_ignore && state == FINGER_DOWN_GPIO_STATE) { + pr_info("touch ignored. %s state, %d \n", (state ? "Finger Down" : "Finger Leave"), drvdata->touch_ignore); + return; + } + + drvdata->fd_gpio.last_gpio_state = state; + + fw_event.ev = (state ? FW_EVENT_FINGER_DOWN : FW_EVENT_FINGER_UP); + + mutex_lock(&drvdata->fd_events_mutex); + + kfifo_reset(&drvdata->fd_events); + + if (!kfifo_put(&drvdata->fd_events, fw_event)) + pr_err("fw events fifo: error adding item\n"); + + mutex_unlock(&drvdata->fd_events_mutex); + wake_up_interruptible(&drvdata->read_wait_queue_fd); + pr_info("state: %s\n", state ? "Finger Down" : "Finger Leave"); +} + +static void qfs4008_wuhb_work_func(struct work_struct *work) +{ + struct qfs4008_drvdata *drvdata = container_of(work, + struct qfs4008_drvdata, fd_gpio.work); + qfs4008_gpio_report_event(drvdata); +} + +static irqreturn_t qfs4008_wuhb_irq_handler(int irq, void *dev_id) +{ + struct qfs4008_drvdata *drvdata = dev_id; + + if (irq != drvdata->fd_gpio.irq) { + pr_warn("invalid irq %d (expected %d)\n", irq, + drvdata->fd_gpio.irq); + return IRQ_HANDLED; + } + + drvdata->wuhb_count++; + __pm_wakeup_event(drvdata->fp_signal_lock, + msecs_to_jiffies(QFS4008_WAKELOCK_HOLD_TIME)); + + schedule_work(&drvdata->fd_gpio.work); + + return IRQ_HANDLED; +} + +/* + * qfs4008_ipc_irq_handler() - function processes IPC + * interrupts on its own thread + * @irq: the interrupt that occurred + * @dev_id: pointer to the qfs4008_drvdata + * + * Return: IRQ_HANDLED when complete + */ +static irqreturn_t qfs4008_ipc_irq_handler(int irq, void *dev_id) +{ + struct qfs4008_drvdata *drvdata = (struct qfs4008_drvdata *)dev_id; + enum qfs4008_fw_event ev = FW_EVENT_CBGE_REQUIRED; + struct fw_event_desc fw_ev_des; + + __pm_wakeup_event(drvdata->fp_signal_lock, + msecs_to_jiffies(QFS4008_WAKELOCK_HOLD_TIME)); + mutex_lock(&drvdata->mutex); + + if (irq != drvdata->fw_ipc.irq) { + pr_warn("invalid irq %d (expected %d)\n", irq, + drvdata->fw_ipc.irq); + goto ipc_irq_failed; + } + + mutex_lock(&drvdata->ipc_events_mutex); + fw_ev_des.ev = ev; + if (!kfifo_put(&drvdata->ipc_events, fw_ev_des)) + pr_err("fw events: fifo full, drop event %d\n", (int) ev); + + drvdata->cbge_count++; + mutex_unlock(&drvdata->ipc_events_mutex); + + wake_up_interruptible(&drvdata->read_wait_queue_ipc); + pr_debug("ipc interrupt received, irq=%d, event=%d\n", irq, (int)ev); +ipc_irq_failed: + mutex_unlock(&drvdata->mutex); + return IRQ_HANDLED; +} + +static int qfs4008_setup_fd_gpio_irq(struct platform_device *pdev, + struct qfs4008_drvdata *drvdata) +{ + int rc = 0; + int irq; + const char *desc = "qbt_finger_detect"; + + rc = devm_gpio_request_one(&pdev->dev, drvdata->fd_gpio.gpio, + GPIOF_IN, desc); + if (rc < 0) { + pr_err("failed to request gpio %d, error %d\n", + drvdata->fd_gpio.gpio, rc); + goto fd_gpio_failed; + } + + irq = gpio_to_irq(drvdata->fd_gpio.gpio); + if (irq < 0) { + rc = irq; + pr_err("unable to get irq number for gpio %d, error %d\n", + drvdata->fd_gpio.gpio, rc); + goto fd_gpio_failed_request; + } + + drvdata->fd_gpio.irq = irq; + INIT_WORK(&drvdata->fd_gpio.work, qfs4008_wuhb_work_func); + rc = devm_request_any_context_irq(&pdev->dev, drvdata->fd_gpio.irq, + qfs4008_wuhb_irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + desc, drvdata); + if (rc < 0) { + pr_err("unable to claim irq %d; error %d\n", + drvdata->fd_gpio.irq, rc); + goto fd_gpio_failed_request; + } + enable_irq_wake(drvdata->fd_gpio.irq); + drvdata->enabled_wuhb = true; + qfs4008_disable_wuhb(drvdata); + pr_debug("irq=%d,gpio=%d,rc=%d\n", drvdata->fd_gpio.irq, drvdata->fd_gpio.gpio, rc); +fd_gpio_failed_request: + gpio_free(drvdata->fd_gpio.gpio); +fd_gpio_failed: + return rc; +} + +static int qfs4008_setup_ipc_irq(struct platform_device *pdev, + struct qfs4008_drvdata *drvdata) +{ + int rc = 0; + const char *desc = "qbt_ipc"; + + drvdata->fw_ipc.irq = gpio_to_irq(drvdata->fw_ipc.gpio); + if (drvdata->fw_ipc.irq < 0) { + rc = drvdata->fw_ipc.irq; + pr_err("no irq for gpio %d, error=%d\n", + drvdata->fw_ipc.gpio, rc); + goto ipc_gpio_failed; + } + + rc = devm_gpio_request_one(&pdev->dev, drvdata->fw_ipc.gpio, + GPIOF_IN, desc); + + if (rc < 0) { + pr_err("failed to request gpio %d, error %d\n", + drvdata->fw_ipc.gpio, rc); + goto ipc_gpio_failed; + } + + rc = devm_request_threaded_irq(&pdev->dev, + drvdata->fw_ipc.irq, NULL, qfs4008_ipc_irq_handler, + IRQF_ONESHOT | IRQF_TRIGGER_FALLING, desc, drvdata); + + if (rc < 0) { + pr_err("failed to register for ipc irq %d, rc = %d\n", + drvdata->fw_ipc.irq, rc); + goto ipc_gpio_failed_request; + } + + enable_irq_wake(drvdata->fw_ipc.irq); + drvdata->enabled_ipc = true; + qfs4008_disable_ipc(drvdata); + pr_debug("irq=%d,gpio=%d,rc=%d\n", drvdata->fw_ipc.irq, + drvdata->fw_ipc.gpio, rc); +ipc_gpio_failed_request: + gpio_free(drvdata->fw_ipc.gpio); +ipc_gpio_failed: + return rc; +} + +/** + * qfs4008_read_device_tree() - Function reads device tree + * properties into driver data + * @pdev: ptr to platform device object + * @drvdata: ptr to driver data + * + * Return: 0 on success. Error code on failure. + */ +static int qfs4008_read_device_tree(struct platform_device *pdev, + struct qfs4008_drvdata *drvdata) +{ + int rc = 0; + + /* read IPC gpio */ + drvdata->fw_ipc.gpio = of_get_named_gpio(pdev->dev.of_node, + "qcom,ipc-gpio", 0); + if (drvdata->fw_ipc.gpio < 0) { + rc = drvdata->fw_ipc.gpio; + pr_err("ipc gpio not found, error=%d\n", rc); + goto dt_failed; + } + + /* read WUHB gpio */ + drvdata->fd_gpio.gpio = of_get_named_gpio(pdev->dev.of_node, + "qcom,wuhb-gpio", 0); + if (drvdata->fd_gpio.gpio < 0) { + rc = drvdata->fd_gpio.gpio; + pr_err("wuhb gpio not found, error=%d\n", rc); + goto dt_failed; + } else { + drvdata->fd_gpio.active_low = 0x0; + } + + rc = of_property_read_string(pdev->dev.of_node, "qcom,btp-regulator-1p8", &drvdata->btp_vdd_1p8); + if (rc < 0) { + pr_err("not set btp_regulator_1p8\n"); + drvdata->btp_vdd_1p8 = NULL; + goto dt_failed; + } else { + drvdata->regulator_1p8 = regulator_get(&pdev->dev, drvdata->btp_vdd_1p8); + if (IS_ERR(drvdata->regulator_1p8) || (drvdata->regulator_1p8) == NULL) { + pr_err("not set regulator_1p8\n"); + drvdata->regulator_1p8 = NULL; + goto dt_failed; + } else { + pr_info("btp_regulator_1p8 ok\n"); + drvdata->enabled_ldo = 0; + rc = regulator_set_load(drvdata->regulator_1p8, 500000); + if (rc) + pr_err("regulator_1p8 set_load failed, rc=%d\n", rc); + } + } + + rc = of_property_read_string(pdev->dev.of_node, "qcom,btp-regulator-3p0", &drvdata->btp_vdd_3p0); + if (rc < 0) { + pr_err("not set btp_regulator_3p0\n"); + drvdata->btp_vdd_3p0 = NULL; + goto dt_failed; + } else { + drvdata->regulator_3p0 = regulator_get(&pdev->dev, drvdata->btp_vdd_3p0); + if (IS_ERR(drvdata->regulator_3p0) || (drvdata->regulator_3p0) == NULL) { + pr_err("not set regulator_3p0\n"); + drvdata->regulator_3p0 = NULL; + goto dt_failed; + } else { + pr_info("btp_regulator_3p0 ok\n"); + drvdata->enabled_ldo = 0; + rc = regulator_set_load(drvdata->regulator_3p0, 500000); + if (rc) + pr_err("regulator_3p0 set_load failed, rc=%d\n", rc); + } + } + + if (of_property_read_u32(pdev->dev.of_node, "qcom,min_cpufreq_limit", + &drvdata->boosting->min_cpufreq_limit)) + drvdata->boosting->min_cpufreq_limit = 0; + + if (of_property_read_string_index(pdev->dev.of_node, "qcom,position", 0, + (const char **)&drvdata->sensor_position)) + drvdata->sensor_position = "32.48,0.00,7.50,8.25,14.80,14.80,13.00,13.00,5.00"; + pr_info("Sensor Position: %s\n", drvdata->sensor_position); + + if (of_property_read_string_index(pdev->dev.of_node, "qcom,modelinfo", 0, + (const char **)&drvdata->model_info)) { + drvdata->model_info = "S92X"; + } + pr_info("modelinfo: %s\n", drvdata->model_info); + + if (of_property_read_string_index(pdev->dev.of_node, "qcom,chipid", 0, + (const char **)&drvdata->chipid)) { + drvdata->chipid = "QFS4008"; + } + pr_info("chipid: %s\n", drvdata->chipid); + + pr_info("finished\n"); + return rc; +dt_failed: + pr_err("failed:%d\n", rc); + return rc; +} + +static void qfs4008_work_func_debug(struct work_struct *work) +{ + struct debug_logger *logger; + struct qfs4008_drvdata *drvdata; + + logger = container_of(work, struct debug_logger, work_debug); + drvdata = dev_get_drvdata(logger->dev); + pr_info("ldo:%d,ipc:%d,wuhb:%d,tz:%d,type:%s,int:%d,%d,rst:%d\n", + drvdata->enabled_ldo, drvdata->enabled_ipc, + drvdata->enabled_wuhb, drvdata->tz_mode, + drvdata->sensortype > 0 ? drvdata->chipid : sensor_status[drvdata->sensortype + 2], + drvdata->cbge_count, drvdata->wuhb_count, + drvdata->reset_count); +} + +/** + * qfs4008_probe() - Function loads hardware config from device tree + * @pdev: ptr to platform device object + * + * Return: 0 on success. Error code on failure. + */ +static int qfs4008_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct qfs4008_drvdata *drvdata; + int rc = 0; + + pr_info("Start\n"); +#ifdef CONFIG_BATTERY_SAMSUNG + if (lpcharge) { + pr_info("Do not load driver due to : lpm %d\n", lpcharge); + return rc; + } +#endif + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->clk_setting = devm_kzalloc(dev, sizeof(*drvdata->clk_setting), + GFP_KERNEL); + if (drvdata->clk_setting == NULL) + return -ENOMEM; + + drvdata->boosting = devm_kzalloc(dev, sizeof(*drvdata->boosting), + GFP_KERNEL); + if (drvdata->boosting == NULL) + return -ENOMEM; + + drvdata->logger = devm_kzalloc(dev, sizeof(*drvdata->logger), + GFP_KERNEL); + if (drvdata->logger == NULL) + return -ENOMEM; + + drvdata->dev = dev; + drvdata->logger->dev = dev; + platform_set_drvdata(pdev, drvdata); + + rc = qfs4008_read_device_tree(pdev, drvdata); + if (rc < 0) + goto probe_failed_dt; + + atomic_set(&drvdata->fd_available, 1); + atomic_set(&drvdata->ipc_available, 1); + + mutex_init(&drvdata->mutex); + mutex_init(&drvdata->ioctl_mutex); + mutex_init(&drvdata->fd_events_mutex); + mutex_init(&drvdata->ipc_events_mutex); + + rc = qfs4008_dev_register(drvdata); + if (rc < 0) + goto probe_failed_dev_register; + + INIT_KFIFO(drvdata->fd_events); + INIT_KFIFO(drvdata->ipc_events); + init_waitqueue_head(&drvdata->read_wait_queue_fd); + init_waitqueue_head(&drvdata->read_wait_queue_ipc); + + drvdata->clk_setting->spi_wake_lock = wakeup_source_register(dev, "qfs4008_spi_lock"); + drvdata->fp_signal_lock = wakeup_source_register(dev, "qfs4008_signal_lock"); + + rc = qfs4008_pinctrl_register(drvdata); + if (rc < 0) + pr_err("register pinctrl failed: %d\n", rc); + + rc = qfs4008_setup_fd_gpio_irq(pdev, drvdata); + if (rc < 0) + goto probe_failed_fd_gpio; + + rc = qfs4008_setup_ipc_irq(pdev, drvdata); + if (rc < 0) + goto probe_failed_ipc_gpio; + + rc = device_init_wakeup(dev, 1); + if (rc < 0) + goto probe_failed_device_init_wakeup; + + rc = spi_clk_register(drvdata->clk_setting, dev); + if (rc < 0) + goto probe_failed_spi_clk_register; + + rc = fingerprint_register(drvdata->fp_device, + drvdata, fp_attrs, "fingerprint"); + if (rc) { + pr_err("sysfs register failed\n"); + goto probe_failed_sysfs_register; + } + + drvdata->clk_setting->spi_speed = SPI_CLOCK_MAX; +#ifdef ENABLE_SENSORS_FPRINT_SECURE + drvdata->sensortype = SENSOR_UNKNOWN; +#else + drvdata->sensortype = SENSOR_OK; +#endif + drvdata->cbge_count = 0; + drvdata->wuhb_count = 0; + drvdata->reset_count = 0; + drvdata->touch_ignore = 0; +#ifdef ENABLE_SENSORS_FPRINT_SECURE + drvdata->clk_setting->enabled_clk = false; + drvdata->tz_mode = true; +#else + drvdata->clk_setting->enabled_clk = true; + drvdata->tz_mode = false; +#endif + + g_logger = drvdata->logger; + set_fp_debug_timer(drvdata->logger, qfs4008_work_func_debug); + enable_fp_debug_timer(drvdata->logger); + + + pr_info("finished\n"); + return 0; + +probe_failed_sysfs_register: + spi_clk_unregister(drvdata->clk_setting); +probe_failed_spi_clk_register: +probe_failed_device_init_wakeup: + gpio_free(drvdata->fw_ipc.gpio); +probe_failed_ipc_gpio: + gpio_free(drvdata->fd_gpio.gpio); +probe_failed_fd_gpio: + if (drvdata->p) { + devm_pinctrl_put(drvdata->p); + drvdata->p = NULL; + } + wakeup_source_unregister(drvdata->clk_setting->spi_wake_lock); + wakeup_source_unregister(drvdata->fp_signal_lock); + device_destroy(drvdata->qfs4008_class, drvdata->qfs4008_ipc_cdev.dev); + device_destroy(drvdata->qfs4008_class, drvdata->qfs4008_fd_cdev.dev); + class_destroy(drvdata->qfs4008_class); + cdev_del(&drvdata->qfs4008_fd_cdev); + cdev_del(&drvdata->qfs4008_ipc_cdev); + unregister_chrdev_region(drvdata->qfs4008_fd_cdev.dev, 1); + unregister_chrdev_region(drvdata->qfs4008_ipc_cdev.dev, 1); +probe_failed_dev_register: + if (drvdata->regulator_1p8) + regulator_put(drvdata->regulator_1p8); + if (drvdata->regulator_3p0) + regulator_put(drvdata->regulator_3p0); +probe_failed_dt: + drvdata = NULL; + pr_err("failed: %d\n", rc); + return rc; +} + +static int qfs4008_remove(struct platform_device *pdev) +{ + struct qfs4008_drvdata *drvdata = platform_get_drvdata(pdev); + + pr_info("called\n"); + + mutex_destroy(&drvdata->mutex); + mutex_destroy(&drvdata->ioctl_mutex); + mutex_destroy(&drvdata->fd_events_mutex); + mutex_destroy(&drvdata->ipc_events_mutex); + device_destroy(drvdata->qfs4008_class, drvdata->qfs4008_fd_cdev.dev); + device_destroy(drvdata->qfs4008_class, drvdata->qfs4008_ipc_cdev.dev); + + disable_fp_debug_timer(drvdata->logger); + if (drvdata->regulator_1p8) + regulator_put(drvdata->regulator_1p8); + if (drvdata->regulator_3p0) + regulator_put(drvdata->regulator_3p0); + wakeup_source_unregister(drvdata->clk_setting->spi_wake_lock); + wakeup_source_unregister(drvdata->fp_signal_lock); + class_destroy(drvdata->qfs4008_class); + cdev_del(&drvdata->qfs4008_fd_cdev); + cdev_del(&drvdata->qfs4008_ipc_cdev); + unregister_chrdev_region(drvdata->qfs4008_fd_cdev.dev, 1); + unregister_chrdev_region(drvdata->qfs4008_ipc_cdev.dev, 1); + fingerprint_unregister(drvdata->fp_device, fp_attrs); + device_init_wakeup(&pdev->dev, 0); + spi_clk_unregister(drvdata->clk_setting); + + if (drvdata->p) { + devm_pinctrl_put(drvdata->p); + drvdata->p = NULL; + } + drvdata = NULL; + + return 0; +} + +static int qfs4008_suspend(struct platform_device *pdev, pm_message_t state) +{ + int rc = 0; + struct qfs4008_drvdata *drvdata = platform_get_drvdata(pdev); + +#ifdef CONFIG_BATTERY_SAMSUNG + if (lpcharge) + return rc; +#endif + /* + * Returning an error code if driver currently making a TZ call. + * Note: The purpose of this driver is to ensure that the clocks are on + * while making a TZ call. Hence the clock check to determine if the + * driver will allow suspend to occur. + */ + if (!mutex_trylock(&drvdata->mutex)) + return -EBUSY; + + disable_fp_debug_timer(drvdata->logger); + pr_info("ret = %d\n", rc); + mutex_unlock(&drvdata->mutex); + + return 0; +} + +static int qfs4008_resume(struct platform_device *pdev) +{ + int rc = 0; + struct qfs4008_drvdata *drvdata = platform_get_drvdata(pdev); + +#ifdef CONFIG_BATTERY_SAMSUNG + if (lpcharge) + return rc; +#endif + + enable_fp_debug_timer(drvdata->logger); + pr_info("ret = %d\n", rc); + + return 0; +} + +static const struct of_device_id qfs4008_match[] = { + { .compatible = "qcom,qfs4008" }, + {} +}; + +static struct platform_driver qfs4008_plat_driver = { + .probe = qfs4008_probe, + .remove = qfs4008_remove, + .suspend = qfs4008_suspend, + .resume = qfs4008_resume, + .driver = { + .name = QFS4008_DEV, + .owner = THIS_MODULE, + .of_match_table = qfs4008_match, + }, +}; + +static int __init qfs4008_init(void) +{ + int rc = 0; + + rc = platform_driver_register(&qfs4008_plat_driver); + pr_info("ret : %d\n", rc); + + return rc; +} + +static void __exit qfs4008_exit(void) +{ + pr_info("entry\n"); + return platform_driver_unregister(&qfs4008_plat_driver); +} + +late_initcall(qfs4008_init); +module_exit(qfs4008_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("fp.sec@samsung.com"); +MODULE_DESCRIPTION("Samsung Electronics Inc. QFS4008 driver"); diff --git a/drivers/fingerprint/qfs4008_common.h b/drivers/fingerprint/qfs4008_common.h new file mode 100644 index 000000000000..ec9e23511059 --- /dev/null +++ b/drivers/fingerprint/qfs4008_common.h @@ -0,0 +1,151 @@ +#ifndef _UAPI_QFS4008_H_ +#define _UAPI_QFS4008_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fingerprint_common.h" + +#undef DISABLED_GPIO_PROTECTION + +#define VENDOR "QCOM" +#define CHIP_ID "QFS4008" +#define SPI_CLOCK_MAX 35000000 + +#define QFS4008_DEV "qbt2000" +#define MAX_FW_EVENTS 128 +#define IPC_MSG_ID_CBGE_REQUIRED 29 +#define IPC_MSG_ID_FINGER_ON_SENSOR 55 +#define IPC_MSG_ID_FINGER_OFF_SENSOR 56 +#define QFS4008_WAKELOCK_HOLD_TIME 500 + +#define MINOR_NUM_FD 0 +#define MINOR_NUM_IPC 1 + +#define FINGER_DOWN_GPIO_STATE 1 +#define FINGER_LEAVE_GPIO_STATE 0 + +enum qfs4008_commands { + QFS4008_POWER_CONTROL = 21, + QFS4008_ENABLE_SPI_CLOCK = 22, + QFS4008_DISABLE_SPI_CLOCK = 23, + QFS4008_ENABLE_IPC = 24, + QFS4008_DISABLE_IPC = 25, + QFS4008_ENABLE_WUHB = 26, + QFS4008_DISABLE_WUHB = 27, + QFS4008_CPU_SPEEDUP = 28, + QFS4008_SET_SENSOR_TYPE = 29, + QFS4008_SET_LOCKSCREEN = 30, + QFS4008_SENSOR_RESET = 31, + QFS4008_SENSOR_TEST = 32, + QFS4008_GET_MODELINFO = 38, + QFS4008_SET_TOUCH_IGNORE = 39, + QFS4008_IS_WHUB_CONNECTED = 105, +}; + +#define QFS4008_SENSORTEST_DONE 0x0000 // do nothing or test done +#define QFS4008_SENSORTEST_BGECAL 0x0001 // begin the BGECAL +#define QFS4008_SENSORTEST_NORMALSCAN 0x0002 // begin the normalscan +#define QFS4008_SENSORTEST_SNR 0x0004 // begin the snr +#define QFS4008_SENSORTEST_CAPTURE 0x0008 // begin the image capture. it also needs liveness capture. + +/* + * enum qfs4008_fw_event - + * enumeration of firmware events + * @FW_EVENT_FINGER_DOWN - finger down detected + * @FW_EVENT_FINGER_UP - finger up detected + * @FW_EVENT_INDICATION - an indication IPC from the firmware is pending + */ +enum qfs4008_fw_event { + FW_EVENT_FINGER_DOWN = 1, + FW_EVENT_FINGER_UP = 2, + FW_EVENT_CBGE_REQUIRED = 3, +}; + +struct finger_detect_gpio { + int gpio; + int active_low; + int irq; + struct work_struct work; + int last_gpio_state; +}; + +struct fw_event_desc { + enum qfs4008_fw_event ev; +}; + +struct fw_ipc_info { + int gpio; + int irq; +}; + +struct qfs4008_drvdata { + struct class *qfs4008_class; + struct cdev qfs4008_fd_cdev; + struct cdev qfs4008_ipc_cdev; + struct device *dev; + struct device *fp_device; + atomic_t fd_available; + atomic_t ipc_available; + struct mutex mutex; + struct mutex ioctl_mutex; + struct mutex fd_events_mutex; + struct mutex ipc_events_mutex; + struct fw_ipc_info fw_ipc; + struct finger_detect_gpio fd_gpio; + DECLARE_KFIFO(fd_events, struct fw_event_desc, MAX_FW_EVENTS); + DECLARE_KFIFO(ipc_events, struct fw_event_desc, MAX_FW_EVENTS); + wait_queue_head_t read_wait_queue_fd; + wait_queue_head_t read_wait_queue_ipc; + + int sensortype; + int cbge_count; + int wuhb_count; + int reset_count; + bool enabled_ipc; + bool enabled_wuhb; + bool enabled_ldo; + bool tz_mode; + bool touch_ignore; + const char *model_info; + const char *chipid; + struct pinctrl *p; + struct pinctrl_state *pins_poweron; + struct pinctrl_state *pins_poweroff; + const char *sensor_position; + const char *btp_vdd_1p8; + const char *btp_vdd_3p0; + struct regulator *regulator_1p8; + struct regulator *regulator_3p0; + struct wakeup_source *fp_signal_lock; + struct spi_clk_setting *clk_setting; + struct boosting_config *boosting; + struct debug_logger *logger; +}; + +#ifdef CONFIG_BATTERY_SAMSUNG +extern unsigned int lpcharge; +#endif + +struct debug_logger *g_logger; + +#endif /* _UAPI_QFS4008_H_ */ diff --git a/drivers/fingerprint/qfs4008_spidev.c b/drivers/fingerprint/qfs4008_spidev.c new file mode 100644 index 000000000000..f03053027bc4 --- /dev/null +++ b/drivers/fingerprint/qfs4008_spidev.c @@ -0,0 +1,760 @@ +/* + * Copyright (C) 2018 Samsung Electronics. 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 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 "fingerprint.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define QFSSPI_MAJOR 232 +#define N_SPI_MINORS 32 +static DECLARE_BITMAP(minors, N_SPI_MINORS); +#define QFSSPI_DEV "qbtspi" + +/* Bit masks for spi_device.mode management. Note that incorrect + * settings for some settings can cause *lots* of trouble for other + * devices on a shared bus: + * + * - CS_HIGH ... this device will be active when it shouldn't be + * - 3WIRE ... when active, it won't behave as it should + * - NO_CS ... there will be no explicit message boundaries; this + * is completely incompatible with the shared bus model + * - READY ... transfers may proceed when they shouldn't. + * + * REVISIT should changing those flags be privileged? + */ +#define SPI_MODE_MASK (SPI_CPHA | SPI_CPOL | SPI_CS_HIGH \ + | SPI_LSB_FIRST | SPI_3WIRE | SPI_LOOP \ + | SPI_NO_CS | SPI_READY | SPI_TX_DUAL \ + | SPI_TX_QUAD | SPI_RX_DUAL | SPI_RX_QUAD) + +struct qfsspi_data { + dev_t devt; + spinlock_t spi_lock; + struct spi_device *spi; + struct list_head device_entry; + + /* TX/RX buffers are NULL unless this device is open (users > 0) */ + struct mutex buf_lock; + unsigned int users; + u8 *tx_buffer; + u8 *rx_buffer; + u32 speed_hz; +}; + +static LIST_HEAD(device_list); +static DEFINE_MUTEX(device_list_lock); +static unsigned int bufsiz = 1024 * 256; + +module_param(bufsiz, uint, 0444); +MODULE_PARM_DESC(bufsiz, "data bytes in biggest supported SPI message"); + +/*-------------------------------------------------------------------------*/ + +static ssize_t +qfsspi_sync(struct qfsspi_data *spidev, struct spi_message *message) +{ + int status; + struct spi_device *spi; + + spin_lock_irq(&spidev->spi_lock); + spi = spidev->spi; + spin_unlock_irq(&spidev->spi_lock); + + if (spi == NULL) + status = -ESHUTDOWN; + else + status = spi_sync(spi, message); + + if (status == 0) + status = message->actual_length; + + return status; +} + +static inline ssize_t +qfsspi_sync_write(struct qfsspi_data *spidev, size_t len) +{ + struct spi_transfer t = { + .tx_buf = spidev->tx_buffer, + .len = len, + .speed_hz = spidev->speed_hz, + }; + struct spi_message m; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + return qfsspi_sync(spidev, &m); +} + +static inline ssize_t +qfsspi_sync_read(struct qfsspi_data *spidev, size_t len) +{ + struct spi_transfer t = { + .rx_buf = spidev->rx_buffer, + .len = len, + .speed_hz = spidev->speed_hz, + }; + struct spi_message m; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + return qfsspi_sync(spidev, &m); +} + +/*-------------------------------------------------------------------------*/ + +/* Read-only message with current device setup */ +static ssize_t +qfsspi_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) +{ + struct qfsspi_data *spidev; + ssize_t status = 0; + + /* chipselect only toggles at start or end of operation */ + if (count > bufsiz) + return -EMSGSIZE; + + spidev = filp->private_data; + + mutex_lock(&spidev->buf_lock); + status = qfsspi_sync_read(spidev, count); + if (status > 0) { + unsigned long missing; + + missing = copy_to_user(buf, spidev->rx_buffer, status); + if (missing == status) + status = -EFAULT; + else + status = status - missing; + } + mutex_unlock(&spidev->buf_lock); + + return status; +} + +/* Write-only message with current device setup */ +static ssize_t +qfsspi_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + struct qfsspi_data *spidev; + ssize_t status = 0; + unsigned long missing; + + /* chipselect only toggles at start or end of operation */ + if (count > bufsiz) + return -EMSGSIZE; + + spidev = filp->private_data; + + mutex_lock(&spidev->buf_lock); + missing = copy_from_user(spidev->tx_buffer, buf, count); + if (missing == 0) + status = qfsspi_sync_write(spidev, count); + else + status = -EFAULT; + mutex_unlock(&spidev->buf_lock); + + return status; +} + +static int qfsspi_message(struct qfsspi_data *spidev, + struct spi_ioc_transfer *u_xfers, unsigned int n_xfers) +{ + struct spi_message msg; + struct spi_transfer *k_xfers; + struct spi_transfer *k_tmp; + struct spi_ioc_transfer *u_tmp; + unsigned int n, total, tx_total, rx_total; + u8 *tx_buf, *rx_buf; + int status = -EFAULT; + + spi_message_init(&msg); + k_xfers = kcalloc(n_xfers, sizeof(*k_tmp), GFP_KERNEL); + if (k_xfers == NULL) + return -ENOMEM; + + /* Construct spi_message, copying any tx data to bounce buffer. + * We walk the array of user-provided transfers, using each one + * to initialize a kernel version of the same transfer. + */ + tx_buf = spidev->tx_buffer; + rx_buf = spidev->rx_buffer; + total = 0; + tx_total = 0; + rx_total = 0; + for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers; + n; + n--, k_tmp++, u_tmp++) { + k_tmp->len = u_tmp->len; + + total += k_tmp->len; + /* Since the function returns the total length of transfers + * on success, restrict the total to positive int values to + * avoid the return value looking like an error. Also check + * each transfer length to avoid arithmetic overflow. + */ + if (total > INT_MAX || k_tmp->len > INT_MAX) { + status = -EMSGSIZE; + goto done; + } + + if (u_tmp->rx_buf) { + /* this transfer needs space in RX bounce buffer */ + rx_total += k_tmp->len; + if (rx_total > bufsiz) { + status = -EMSGSIZE; + goto done; + } + k_tmp->rx_buf = rx_buf; + rx_buf += k_tmp->len; + } + if (u_tmp->tx_buf) { + /* this transfer needs space in TX bounce buffer */ + tx_total += k_tmp->len; + if (tx_total > bufsiz) { + status = -EMSGSIZE; + goto done; + } + k_tmp->tx_buf = tx_buf; + if (copy_from_user(tx_buf, (const u8 __user *) + (uintptr_t) u_tmp->tx_buf, + u_tmp->len)) + goto done; + tx_buf += k_tmp->len; + } + + k_tmp->cs_change = !!u_tmp->cs_change; + k_tmp->tx_nbits = u_tmp->tx_nbits; + k_tmp->rx_nbits = u_tmp->rx_nbits; + k_tmp->bits_per_word = u_tmp->bits_per_word; + k_tmp->speed_hz = u_tmp->speed_hz; + if (!k_tmp->speed_hz) + k_tmp->speed_hz = spidev->speed_hz; + spi_message_add_tail(k_tmp, &msg); + } + + status = qfsspi_sync(spidev, &msg); + if (status < 0) + goto done; + + /* copy any rx data out of bounce buffer */ + rx_buf = spidev->rx_buffer; + for (n = n_xfers, u_tmp = u_xfers; n; n--, u_tmp++) { + if (u_tmp->rx_buf) { + if (copy_to_user((u8 __user *) + (uintptr_t) u_tmp->rx_buf, rx_buf, + u_tmp->len)) { + status = -EFAULT; + goto done; + } + rx_buf += u_tmp->len; + } + } + status = total; + +done: + kfree(k_xfers); + return status; +} + +static struct spi_ioc_transfer * +qfsspi_get_ioc_message(unsigned int cmd, struct spi_ioc_transfer __user *u_ioc, + unsigned int *n_ioc) +{ + u32 tmp; + + /* Check type, command number and direction */ + if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC + || _IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0)) + || _IOC_DIR(cmd) != _IOC_WRITE) + return ERR_PTR(-ENOTTY); + + tmp = _IOC_SIZE(cmd); + if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) + return ERR_PTR(-EINVAL); + *n_ioc = tmp / sizeof(struct spi_ioc_transfer); + if (*n_ioc == 0) + return NULL; + + /* copy into scratch area */ + return memdup_user(u_ioc, tmp); +} + +static long +qfsspi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + int retval = 0; + struct qfsspi_data *spidev; + struct spi_device *spi; + u32 tmp; + unsigned int n_ioc; + struct spi_ioc_transfer *ioc; + + /* Check type and command number */ + if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC) + return -ENOTTY; + + /* guard against device removal before, or while, + * we issue this ioctl. + */ + spidev = filp->private_data; + spin_lock_irq(&spidev->spi_lock); + spi = spi_dev_get(spidev->spi); + spin_unlock_irq(&spidev->spi_lock); + + if (spi == NULL) + return -ESHUTDOWN; + + /* use the buffer lock here for triple duty: + * - prevent I/O (from us) so calling spi_setup() is safe; + * - prevent concurrent SPI_IOC_WR_* from morphing + * data fields while SPI_IOC_RD_* reads them; + * - SPI_IOC_MESSAGE needs the buffer locked "normally". + */ + mutex_lock(&spidev->buf_lock); + + switch (cmd) { + /* read requests */ + case SPI_IOC_RD_MODE: + retval = put_user(spi->mode & SPI_MODE_MASK, + (__u8 __user *)arg); + break; + case SPI_IOC_RD_MODE32: + retval = put_user(spi->mode & SPI_MODE_MASK, + (__u32 __user *)arg); + break; + case SPI_IOC_RD_BITS_PER_WORD: + pr_info("%s: SPI_IOC_RD_BITS_PER_WORD\n", __func__); + retval = put_user(spi->bits_per_word, (__u8 __user *)arg); + break; + case SPI_IOC_RD_MAX_SPEED_HZ: + pr_info("%s: SPI_IOC_RD_MAX_SPEED_HZ\n", __func__); + retval = put_user(spidev->speed_hz, (__u32 __user *)arg); + break; + + /* write requests */ + case SPI_IOC_WR_MODE: + case SPI_IOC_WR_MODE32: + if (cmd == SPI_IOC_WR_MODE) + retval = get_user(tmp, (u8 __user *)arg); + else + retval = get_user(tmp, (u32 __user *)arg); + if (retval == 0) { + u32 save = spi->mode; + + if (tmp & ~SPI_MODE_MASK) { + retval = -EINVAL; + break; + } + + tmp |= spi->mode & ~SPI_MODE_MASK; + spi->mode = (u16)tmp; + retval = spi_setup(spi); + if (retval < 0) + spi->mode = save; + else + pr_debug("%s: spi mode %x\n", __func__, tmp); + } + break; + case SPI_IOC_WR_BITS_PER_WORD: + retval = get_user(tmp, (__u8 __user *)arg); + if (retval == 0) { + u8 save = spi->bits_per_word; + + spi->bits_per_word = tmp; + retval = spi_setup(spi); + if (retval < 0) + spi->bits_per_word = save; + else + pr_debug("%s: %d bits per word\n", __func__, tmp); + } + break; + case SPI_IOC_WR_MAX_SPEED_HZ: + retval = get_user(tmp, (__u32 __user *)arg); + break; + + default: + /* segmented and/or full-duplex I/O request */ + /* Check message and copy into scratch area */ + ioc = qfsspi_get_ioc_message(cmd, + (struct spi_ioc_transfer __user *)arg, &n_ioc); + if (IS_ERR(ioc)) { + retval = PTR_ERR(ioc); + break; + } + if (!ioc) + break; /* n_ioc is also 0 */ + + /* translate to spi_message, execute */ + retval = qfsspi_message(spidev, ioc, n_ioc); + kfree(ioc); + break; + } + + mutex_unlock(&spidev->buf_lock); + spi_dev_put(spi); + return retval; +} + +#ifdef CONFIG_COMPAT +static long +qfsspi_compat_ioc_message(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct spi_ioc_transfer __user *u_ioc; + int retval = 0; + struct qfsspi_data *spidev; + struct spi_device *spi; + unsigned int n_ioc, n; + struct spi_ioc_transfer *ioc; + + u_ioc = (struct spi_ioc_transfer __user *) compat_ptr(arg); + + /* guard against device removal before, or while, + * we issue this ioctl. + */ + spidev = filp->private_data; + spin_lock_irq(&spidev->spi_lock); + spi = spi_dev_get(spidev->spi); + spin_unlock_irq(&spidev->spi_lock); + + if (spi == NULL) + return -ESHUTDOWN; + + /* SPI_IOC_MESSAGE needs the buffer locked "normally" */ + mutex_lock(&spidev->buf_lock); + + /* Check message and copy into scratch area */ + ioc = qfsspi_get_ioc_message(cmd, u_ioc, &n_ioc); + if (IS_ERR(ioc)) { + retval = PTR_ERR(ioc); + goto done; + } + if (!ioc) + goto done; /* n_ioc is also 0 */ + + /* Convert buffer pointers */ + for (n = 0; n < n_ioc; n++) { + ioc[n].rx_buf = (uintptr_t) compat_ptr(ioc[n].rx_buf); + ioc[n].tx_buf = (uintptr_t) compat_ptr(ioc[n].tx_buf); + } + + /* translate to spi_message, execute */ + retval = qfsspi_message(spidev, ioc, n_ioc); + kfree(ioc); + +done: + mutex_unlock(&spidev->buf_lock); + spi_dev_put(spi); + return retval; +} + +static long +qfsspi_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + if (_IOC_TYPE(cmd) == SPI_IOC_MAGIC + && _IOC_NR(cmd) == _IOC_NR(SPI_IOC_MESSAGE(0)) + && _IOC_DIR(cmd) == _IOC_WRITE) + return qfsspi_compat_ioc_message(filp, cmd, arg); + + return qfsspi_ioctl(filp, cmd, (unsigned long)compat_ptr(arg)); +} +#else +#define qfsspi_compat_ioctl NULL +#endif /* CONFIG_COMPAT */ + +static int qfsspi_open(struct inode *inode, struct file *filp) +{ + struct qfsspi_data *spidev; + int status = -ENXIO; + + mutex_lock(&device_list_lock); + + list_for_each_entry(spidev, &device_list, device_entry) { + if (spidev->devt == inode->i_rdev) { + status = 0; + break; + } + } + + if (status) { + pr_debug("%s: nothing for minor %d\n", __func__, iminor(inode)); + goto err_find_dev; + } + + if (!spidev->tx_buffer) { + spidev->tx_buffer = kmalloc(bufsiz, GFP_KERNEL); + if (!spidev->tx_buffer) { + pr_debug("%s: open/ENOMEM\n", __func__); + status = -ENOMEM; + goto err_find_dev; + } + } + + if (!spidev->rx_buffer) { + spidev->rx_buffer = kmalloc(bufsiz, GFP_KERNEL); + if (!spidev->rx_buffer) { + pr_debug("%s: open/ENOMEM\n", __func__); + status = -ENOMEM; + goto err_alloc_rx_buf; + } + } + + spidev->users++; + filp->private_data = spidev; + nonseekable_open(inode, filp); + + mutex_unlock(&device_list_lock); + return 0; + +err_alloc_rx_buf: + kfree(spidev->tx_buffer); + spidev->tx_buffer = NULL; +err_find_dev: + mutex_unlock(&device_list_lock); + return status; +} + +static int qfsspi_release(struct inode *inode, struct file *filp) +{ + struct qfsspi_data *spidev; + + mutex_lock(&device_list_lock); + spidev = filp->private_data; + filp->private_data = NULL; + + /* last close? */ + spidev->users--; + if (!spidev->users) { + int dofree; + + kfree(spidev->tx_buffer); + spidev->tx_buffer = NULL; + + kfree(spidev->rx_buffer); + spidev->rx_buffer = NULL; + + spin_lock_irq(&spidev->spi_lock); + if (spidev->spi) + spidev->speed_hz = spidev->spi->max_speed_hz; + + /* ... after we unbound from the underlying device? */ + dofree = (spidev->spi == NULL); + spin_unlock_irq(&spidev->spi_lock); + + if (dofree) + kfree(spidev); + } + mutex_unlock(&device_list_lock); + + return 0; +} + +static const struct file_operations qfsspi_fops = { + .owner = THIS_MODULE, + /* REVISIT switch to aio primitives, so that userspace + * gets more complete API coverage. It'll simplify things + * too, except for the locking. + */ + .write = qfsspi_write, + .read = qfsspi_read, + .unlocked_ioctl = qfsspi_ioctl, + .compat_ioctl = qfsspi_compat_ioctl, + .open = qfsspi_open, + .release = qfsspi_release, + .llseek = no_llseek, +}; + +/*-------------------------------------------------------------------------*/ + +/* The main reason to have this class is to make mdev/udev create the + * /dev/spidevB.C character device nodes exposing our userspace API. + * It also simplifies memory management. + */ + +static struct class *qfsspi_class; + +static const struct of_device_id qfsspi_dt_ids[] = { +#if !defined(ENABLE_SENSORS_FPRINT_SECURE) + { .compatible = "qcom,qfsspi" }, +#endif + {}, +}; + +/*-------------------------------------------------------------------------*/ + +static int qfsspi_probe(struct spi_device *spi) +{ + struct qfsspi_data *spidev; + int status; + unsigned long minor; + + pr_info("%s: start\n", __func__); + /* Allocate driver data */ + spidev = kzalloc(sizeof(*spidev), GFP_KERNEL); + if (!spidev) + return -ENOMEM; + + /* Initialize the driver data */ + spidev->spi = spi; + spin_lock_init(&spidev->spi_lock); + mutex_init(&spidev->buf_lock); + + INIT_LIST_HEAD(&spidev->device_entry); + + /* If we can allocate a minor number, hook up this device. + * Reusing minors is fine so long as udev or mdev is working. + */ + mutex_lock(&device_list_lock); + minor = find_first_zero_bit(minors, N_SPI_MINORS); + if (minor < N_SPI_MINORS) { + struct device *dev; + + spidev->devt = MKDEV(QFSSPI_MAJOR, minor); + dev = device_create(qfsspi_class, &spi->dev, spidev->devt, + spidev, QFSSPI_DEV); + status = PTR_ERR_OR_ZERO(dev); + } else { + pr_err("%s: no minor number available!\n", __func__); + status = -ENODEV; + } + if (status == 0) { + set_bit(minor, minors); + list_add(&spidev->device_entry, &device_list); + } + mutex_unlock(&device_list_lock); + + spidev->speed_hz = spi->max_speed_hz; + + if (status == 0) + spi_set_drvdata(spi, spidev); + else + kfree(spidev); + pr_info("%s: finish %d\n", __func__, status); + return status; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)) +static void qfsspi_remove(struct spi_device *spi) +#else +static int qfsspi_remove(struct spi_device *spi) +#endif +{ + struct qfsspi_data *spidev = spi_get_drvdata(spi); + + /* make sure ops on existing fds can abort cleanly */ + spin_lock_irq(&spidev->spi_lock); + spidev->spi = NULL; + spin_unlock_irq(&spidev->spi_lock); + + /* prevent new opens */ + mutex_lock(&device_list_lock); + list_del(&spidev->device_entry); + device_destroy(qfsspi_class, spidev->devt); + clear_bit(MINOR(spidev->devt), minors); + if (spidev->users == 0) + kfree(spidev); + mutex_unlock(&device_list_lock); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)) + return; +#else + return 0; +#endif +} + +static struct spi_driver qfsspi_spi_driver = { + .driver = { + .name = QFSSPI_DEV, + .owner = THIS_MODULE, + .of_match_table = qfsspi_dt_ids, + }, + .probe = qfsspi_probe, + .remove = qfsspi_remove, + + /* NOTE: suspend/resume methods are not necessary here. + * We don't do anything except pass the requests to/from + * the underlying controller. The refrigerator handles + * most issues; the controller driver handles the rest. + */ +}; + +/*-------------------------------------------------------------------------*/ + +static int __init qfsspi_init(void) +{ + int status = 0; +#if !defined(ENABLE_SENSORS_FPRINT_SECURE) + pr_info("%s\n", __func__); + /* Claim our 256 reserved device numbers. Then register a class + * that will key udev/mdev to add/remove /dev nodes. Last, register + * the driver which manages those device numbers. + */ + BUILD_BUG_ON(N_SPI_MINORS > 256); + status = register_chrdev(QFSSPI_MAJOR, QFSSPI_DEV, &qfsspi_fops); + if (status < 0) { + pr_err("%s: register_chrdev failed %d\n", __func__, status); + return status; + } + +#if (KERNEL_VERSION(6, 3, 0) <= LINUX_VERSION_CODE) + qfsspi_class = class_create(QFSSPI_DEV); +#else + qfsspi_class = class_create(THIS_MODULE, QFSSPI_DEV); +#endif + if (IS_ERR(qfsspi_class)) { + unregister_chrdev(QFSSPI_MAJOR, QFSSPI_DEV); + pr_err("%s: class_create failed\n", __func__); + return PTR_ERR(qfsspi_class); + } + + status = spi_register_driver(&qfsspi_spi_driver); + if (status < 0) { + class_destroy(qfsspi_class); + unregister_chrdev(QFSSPI_MAJOR, qfsspi_spi_driver.driver.name); + pr_err("%s: spi_register_driver failed\n", __func__); + } + pr_info("%s: finish %d\n", __func__, status); +#endif + return status; +} +module_init(qfsspi_init); + +static void __exit qfsspi_exit(void) +{ + spi_unregister_driver(&qfsspi_spi_driver); + class_destroy(qfsspi_class); + unregister_chrdev(QFSSPI_MAJOR, qfsspi_spi_driver.driver.name); +} +module_exit(qfsspi_exit); + +MODULE_AUTHOR("Kangwook.Her"); +MODULE_DESCRIPTION("Samsung Electronics Inc. QBT2000 spi driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/firmware/cirrus/Kconfig b/drivers/firmware/cirrus/Kconfig index f9503cb481d2..5f664bf38f67 100644 --- a/drivers/firmware/cirrus/Kconfig +++ b/drivers/firmware/cirrus/Kconfig @@ -3,3 +3,12 @@ config CS_DSP tristate default n + +# SPDX-License-Identifier: GPL-2.0-only +comment "Cirrus firmware configs" + +config CIRRUS_FIRMWARE_CL_DSP + tristate "Cirrus Logic Haptics DSP driver" + help + This driver is used to handle firmware loading + and configuration for Cirrus Logic Haptic devices. diff --git a/drivers/firmware/cirrus/Makefile b/drivers/firmware/cirrus/Makefile index f074e2638c9c..a1d63e376eee 100644 --- a/drivers/firmware/cirrus/Makefile +++ b/drivers/firmware/cirrus/Makefile @@ -1,3 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 # obj-$(CONFIG_CS_DSP) += cs_dsp.o + +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_CIRRUS_FIRMWARE_CL_DSP) += cl_dsp.o cl_dsp-debugfs.o diff --git a/drivers/firmware/cirrus/cl_dsp-debugfs.c b/drivers/firmware/cirrus/cl_dsp-debugfs.c new file mode 100644 index 000000000000..8a43a0bac12d --- /dev/null +++ b/drivers/firmware/cirrus/cl_dsp-debugfs.c @@ -0,0 +1,526 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// cl_dsp.c -- DSP Control for non-ALSA Cirrus Logic Devices +// +// Copyright 2021 Cirrus Logic, Inc. +// +// Author: Fred Treven + +#include + +#ifdef CONFIG_DEBUG_FS + +static inline u32 host_buffer_field_reg(struct cl_dsp_logger *dl, + unsigned long offset) +{ + return (u32)(CL_DSP_HALO_XMEM_UNPACKED24_BASE + + ((dl->host_buf_ptr + offset) * CL_DSP_BYTES_PER_WORD)); +} + +static inline u32 host_buffer_data_reg(struct cl_dsp_logger *dl, int offset) +{ + return (u32)(CL_DSP_HALO_XMEM_UNPACKED24_BASE + + ((dl->host_buf_base + offset) * CL_DSP_BYTES_PER_WORD)); +} + +static int cl_dsp_host_buffer_field_read(struct cl_dsp_debugfs *db, + unsigned long field_offset, u32 *data) +{ + struct regmap *regmap = db->core->regmap; + __be32 raw; + u32 reg; + int ret; + + reg = host_buffer_field_reg(&db->dl, field_offset); + + ret = regmap_raw_read(regmap, reg, &raw, sizeof(raw)); + if (ret) { + dev_err(db->core->dev, "Failed to get raw host buffer data\n"); + return ret; + } + + *data = CL_DSP_HOST_BUFFER_DATA_MASK & be32_to_cpu(raw); + return 0; +} + +static int cl_dsp_host_buffer_field_write(struct cl_dsp_debugfs *db, + unsigned long field_offset, u32 data) +{ + struct regmap *regmap = db->core->regmap; + struct device *dev = db->core->dev; + int ret; + u32 reg; + + reg = host_buffer_field_reg(&db->dl, field_offset); + + ret = regmap_write(regmap, reg, data); + if (ret) + dev_err(dev, "Failed to set host buffer data: %d\n", ret); + + return ret; +} + +static ssize_t cl_dsp_debugfs_logger_en_read(struct file *file, + char __user *user_buf, size_t count, loff_t *ppos) +{ + struct cl_dsp_debugfs *db = file->private_data; + struct regmap *regmap = db->core->regmap; + char str[CL_DSP_DEBUGFS_TRACE_LOG_STRING_SIZE]; + u32 reg, val; + ssize_t ret; + + ret = cl_dsp_get_reg(db->core, "ENABLED", CL_DSP_XM_UNPACKED_TYPE, + db->dl.algo_id, ®); + if (ret) + return ret; + + ret = pm_runtime_get_sync(db->core->dev); + if (ret < 0) { + dev_err(db->core->dev, "PM Runtime Resume Failed\n"); + return ret; + } + + ret = regmap_read(regmap, reg, &val); + if (ret) { + dev_err(db->core->dev, "Failed to get host buffer status\n"); + goto pm_exit; + } + + ret = snprintf(str, CL_DSP_DEBUGFS_TRACE_LOG_STRING_SIZE, "%d\n", val); + if (ret <= 0) { + dev_err(db->core->dev, "Failed to parse host buffer status\n"); + goto pm_exit; + } + + ret = simple_read_from_buffer(user_buf, count, ppos, str, strlen(str)); + +pm_exit: + pm_runtime_mark_last_busy(db->core->dev); + pm_runtime_put_autosuspend(db->core->dev); + + return ret; +} + +static ssize_t cl_dsp_debugfs_logger_en_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct cl_dsp_debugfs *db = file->private_data; + struct regmap *regmap = db->core->regmap; + struct device *dev = db->core->dev; + u32 reg, val; + ssize_t ret; + char *str; + + str = kzalloc(count, GFP_KERNEL); + if (!str) + return -ENOMEM; + + ret = simple_write_to_buffer(str, count, ppos, user_buf, count); + if (ret <= 0) { + dev_err(dev, "Failed to write debugfs data\n"); + goto exit_free; + } + + ret = kstrtou32(str, 10, &val); + if (ret) + goto exit_free; + + if (val != CL_DSP_DEBUGFS_TRACE_LOG_DISABLE && + val != CL_DSP_DEBUGFS_TRACE_LOG_ENABLE) { + dev_err(dev, "Invalid trace log write: %u\n", val); + ret = -EINVAL; + goto exit_free; + } + + ret = cl_dsp_get_reg(db->core, "ENABLED", CL_DSP_XM_UNPACKED_TYPE, + db->dl.algo_id, ®); + if (ret) + goto exit_free; + + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + dev_err(db->core->dev, "PM Runtime Resume Failed\n"); + goto exit_free; + } + + ret = regmap_write(regmap, reg, val); + if (ret) { + dev_err(dev, "Failed to set trace log status\n"); + goto exit_pm; + } + + if (val == CL_DSP_DEBUGFS_TRACE_LOG_DISABLE) { + /* Set next_read_index to -1 to reset logger */ + ret = cl_dsp_host_buffer_field_write(db, + HOST_BUFFER_FIELD(next_read_index), + CL_DSP_HOST_BUFFER_READ_INDEX_RESET); + if (ret) { + dev_err(dev, "Failed to reset event logger\n"); + goto exit_pm; + } + + db->dl.buf_data_size = 0; + kfree(db->dl.buf_data); + } + +exit_pm: + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + +exit_free: + kfree(str); + + return ret ? ret : count; +} + +static ssize_t cl_dsp_debugfs_timestamp_shift_read(struct file *file, + char __user *user_buf, size_t count, loff_t *ppos) +{ + struct cl_dsp_debugfs *db = file->private_data; + struct regmap *regmap = db->core->regmap; + char str[CL_DSP_DEBUGFS_TRACE_LOG_STRING_SIZE]; + u32 reg, val; + ssize_t ret; + + ret = cl_dsp_get_reg(db->core, "TIMESTAMP_SHIFT", + CL_DSP_XM_UNPACKED_TYPE, db->dl.algo_id, ®); + if (ret) + return ret; + + ret = pm_runtime_get_sync(db->core->dev); + if (ret < 0) { + dev_err(db->core->dev, "PM Runtime Resume Failed\n"); + return ret; + } + + ret = regmap_read(regmap, reg, &val); + if (ret) { + dev_err(db->core->dev, "Failed to get timestamp shift\n"); + goto pm_exit; + } + + ret = snprintf(str, CL_DSP_DEBUGFS_TRACE_LOG_STRING_SIZE, "%d\n", val); + if (ret <= 0) { + dev_err(db->core->dev, "Failed to parse host buffer status\n"); + goto pm_exit; + } + + ret = simple_read_from_buffer(user_buf, count, ppos, str, strlen(str)); + +pm_exit: + pm_runtime_mark_last_busy(db->core->dev); + pm_runtime_put_autosuspend(db->core->dev); + + return ret; +} + +static int cl_dsp_host_buffer_data_read(struct cl_dsp_debugfs *db, + u32 read_index, u32 num_words) +{ + u32 start_reg, offset = db->dl.buf_data_size; + struct regmap *regmap = db->core->regmap; + struct device *dev = db->core->dev; + int ret; + + start_reg = host_buffer_data_reg(&db->dl, (unsigned long)read_index); + + db->dl.buf_data_size += num_words; + db->dl.buf_data = krealloc(db->dl.buf_data, db->dl.buf_data_size * 4, + GFP_KERNEL); + if (!db->dl.buf_data || IS_ERR(db->dl.buf_data)) { + dev_err(dev, "Failed to allocate buffer data space\n"); + return -ENOMEM; + } + + ret = regmap_bulk_read(regmap, start_reg, db->dl.buf_data + offset, + num_words); + if (ret) + dev_err(dev, "Failed to get host buffer data\n"); + + return ret; +} + +int cl_dsp_logger_update(struct cl_dsp_debugfs *db) +{ + struct cl_dsp_logger *dl = &db->dl; + struct device *dev = db->core->dev; + u32 n_read_index, n_write_index, num_words; + u32 nirq, irq, error_code; + int ret; + + /* Check if interrupt was asserted due to an error */ + ret = cl_dsp_host_buffer_field_read(db, HOST_BUFFER_FIELD(error), + &error_code); + if (ret) + return ret; + + if (error_code) { + if (error_code != CL_DSP_HOST_BUFFER_ERROR_OVERFLOW) { + dev_err(dev, "Fatal Host Buffer Error with code 0x%X\n", + error_code); + return -ENOTRECOVERABLE; + } + dev_warn(dev, "Data lost from Host Buffer Overflow\n"); + } + + /* Check if next read index is != -1 in order to continue */ + ret = cl_dsp_host_buffer_field_read(db, + HOST_BUFFER_FIELD(next_read_index), &n_read_index); + if (ret) + return ret; + + if (n_read_index == CL_DSP_HOST_BUFFER_READ_INDEX_RESET) { + dev_err(dev, "Host Buffer Not Initialized\n"); + return -EPERM; + } + + ret = cl_dsp_host_buffer_field_read(db, HOST_BUFFER_FIELD(irq_count), + &nirq); + if (ret) + return ret; + + ret = cl_dsp_host_buffer_field_read(db, HOST_BUFFER_FIELD(irq_ack), + &irq); + if (ret) + return ret; + + ret = cl_dsp_host_buffer_field_read( + db, HOST_BUFFER_FIELD(next_write_index), &n_write_index); + if (ret) + return ret; + + if (n_write_index < n_read_index) + num_words = (n_write_index + dl->host_buf_size_words) - + n_read_index; + else + num_words = n_write_index - n_read_index; + + /* Get all messages in buffer */ + ret = cl_dsp_host_buffer_data_read(db, n_read_index, num_words); + if (ret) + return ret; + + /* Set next_read_index to next_write_index */ + ret = cl_dsp_host_buffer_field_write(db, + HOST_BUFFER_FIELD(next_read_index), n_write_index - 1); + if (ret) + return ret; + + /* Reset irq_ack by writing irq_count | 0x1 */ + ret = cl_dsp_host_buffer_field_write(db, HOST_BUFFER_FIELD(irq_ack), + nirq | CL_DSP_HOST_BUFFER_IRQ_MASK); + if (ret) + return ret; + + ret = cl_dsp_host_buffer_field_read(db, + HOST_BUFFER_FIELD(irq_ack), &irq); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL(cl_dsp_logger_update); + +static int cl_dsp_debugfs_logger_open(struct inode *inode, struct file *file) +{ + struct cl_dsp_debugfs *db; + int ret; + + ret = simple_open(inode, file); + if (ret) + return ret; + + db = file->private_data; + + return 0; +} + +static ssize_t cl_dsp_debugfs_logger_read(struct file *file, + char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct cl_dsp_debugfs *db = file->private_data; + struct cl_dsp_logger *dl = &db->dl; + struct device *dev = db->core->dev; + ssize_t ret, buf_str_size; + char *str, *buf_str; + int i; + + if (dl->buf_data_size == 0) + return -ENODATA; + + buf_str_size = + CL_DSP_HOST_BUFFER_DATA_SLOT_SIZE * dl->buf_data_size; + buf_str = kzalloc(buf_str_size, GFP_KERNEL); + if (!buf_str) + return -ENOMEM; + + str = kzalloc(CL_DSP_HOST_BUFFER_DATA_SLOT_SIZE, GFP_KERNEL); + if (!str) { + ret = -ENOMEM; + goto err_free2; + } + + for (i = 0; i < dl->buf_data_size; i++) { + ret = snprintf(str, CL_DSP_HOST_BUFFER_DATA_SLOT_SIZE, "%08X ", + dl->buf_data[i]); + if (ret <= 0) { + dev_err(dev, "Failed to get host buffer data string\n"); + goto err_free1; + } + + strncat(buf_str, str, CL_DSP_HOST_BUFFER_DATA_SLOT_SIZE); + } + + ret = simple_read_from_buffer(user_buf, count, ppos, buf_str, + strlen(buf_str)); + +err_free1: + kfree(str); +err_free2: + kfree(buf_str); + + return ret; +} + +static const struct { + const char *name; + const struct file_operations fops; +} cl_dsp_debugfs_fops[] = { + { + .name = "log_data", + .fops = { + .owner = THIS_MODULE, + .open = cl_dsp_debugfs_logger_open, + .read = cl_dsp_debugfs_logger_read, + }, + }, + { + .name = "logger_en", + .fops = { + .owner = THIS_MODULE, + .open = simple_open, + .read = cl_dsp_debugfs_logger_en_read, + .write = cl_dsp_debugfs_logger_en_write, + }, + }, + { + .name = "timestamp_shift", + .fops = { + .owner = THIS_MODULE, + .open = simple_open, + .read = cl_dsp_debugfs_timestamp_shift_read, + } + }, +}; + +static int cl_dsp_logger_init(struct cl_dsp_debugfs *db) +{ + struct regmap *regmap = db->core->regmap; + struct cl_dsp *dsp = db->core; + u32 reg; + int ret; + + ret = cl_dsp_get_reg(dsp, "EVENT_LOG_HEADER", CL_DSP_XM_UNPACKED_TYPE, + db->dl.algo_id, ®); + if (ret) + return ret; + + ret = regmap_read(regmap, reg, &db->dl.host_buf_ptr); + if (ret) { + dev_err(db->core->dev, "Failed to get host buffer address\n"); + return ret; + } + + ret = cl_dsp_host_buffer_field_read(db, HOST_BUFFER_FIELD(buf1_base), + &db->dl.host_buf_base); + if (ret) + return ret; + + ret = cl_dsp_host_buffer_field_read(db, + HOST_BUFFER_FIELD(buf_total_size), + &db->dl.host_buf_size_words); + if (ret) + return ret; + + ret = cl_dsp_host_buffer_field_read(db, + HOST_BUFFER_FIELD(high_water_mark), + &db->dl.high_watermark); + if (ret) + return ret; + + /* Set next_read_index to -1 to reset logger */ + ret = cl_dsp_host_buffer_field_write(db, + HOST_BUFFER_FIELD(next_read_index), + CL_DSP_HOST_BUFFER_READ_INDEX_RESET); + if (ret) + dev_err(db->core->dev, "Failed to reset event logger\n"); + + return ret; +} +struct cl_dsp_debugfs *cl_dsp_debugfs_create(struct cl_dsp *dsp, + struct dentry *parent_node, + u32 event_log_algo_id) +{ + struct cl_dsp_debugfs *db; + int ret, i; + + if (IS_ERR(dsp)) + return ERR_CAST(dsp); + + if (!dsp) + return NULL; + + if (IS_ERR(parent_node)) + return ERR_CAST(parent_node); + + if (!parent_node) + return NULL; + + db = kzalloc(sizeof(*db), GFP_KERNEL); + if (!db) + return ERR_PTR(-ENOMEM); + + db->core = dsp; + db->debugfs_root = parent_node ? parent_node : NULL; + + db->debugfs_node = debugfs_create_dir("cl_dsp", db->debugfs_root); + if (IS_ERR(db->debugfs_node)) { + ret = PTR_ERR(db->debugfs_node); + kfree(db); + return ERR_PTR(ret); + } + + for (i = 0; i < CL_DSP_DEBUGFS_NUM_CONTROLS; i++) + debugfs_create_file(cl_dsp_debugfs_fops[i].name, + CL_DSP_DEBUGFS_RW_FILE_MODE, db->debugfs_node, + db, &cl_dsp_debugfs_fops[i].fops); + + db->dl.algo_id = event_log_algo_id; + + ret = cl_dsp_logger_init(db); + if (ret) + return ERR_PTR(ret); + + debugfs_create_u32("high_watermark", CL_DSP_DEBUGFS_RO_FILE_MODE, + db->debugfs_node, &db->dl.high_watermark); + + return db; +} +EXPORT_SYMBOL(cl_dsp_debugfs_create); + +void cl_dsp_debugfs_destroy(struct cl_dsp_debugfs *db) +{ + if (IS_ERR_OR_NULL(db)) + return; + + debugfs_remove_recursive(db->debugfs_node); + kfree(db); +} +EXPORT_SYMBOL(cl_dsp_debugfs_destroy); + +#endif /* CONFIG_DEBUG_FS */ + +MODULE_DESCRIPTION("CL DSP Debugfs Driver"); +MODULE_AUTHOR("Fred Treven, Cirrus Logic Inc. "); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/cirrus/cl_dsp.c b/drivers/firmware/cirrus/cl_dsp.c new file mode 100644 index 000000000000..ba43bc8f193b --- /dev/null +++ b/drivers/firmware/cirrus/cl_dsp.c @@ -0,0 +1,1182 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// cl_dsp.c -- DSP Control for non-ALSA Cirrus Logic Devices +// +// Copyright 2022 Cirrus Logic, Inc. +// +// Author: Fred Treven + +#include + +struct cl_dsp_memchunk cl_dsp_memchunk_create(void *data, int size) +{ + struct cl_dsp_memchunk ch = { + .data = data, + .max = data + size, + }; + + return ch; +} +EXPORT_SYMBOL(cl_dsp_memchunk_create); + +static inline bool cl_dsp_memchunk_end(struct cl_dsp_memchunk *ch) +{ + return ch->data == ch->max; +} + +static inline bool cl_dsp_memchunk_valid_addr(struct cl_dsp_memchunk *ch, + void *addr) +{ + return (u8 *)addr <= ch->max; +} + +int cl_dsp_memchunk_read(struct cl_dsp *dsp, struct cl_dsp_memchunk *ch, + int nbits, void *val) +{ + int nbytes = nbits / 8, nread, i; + u32 result = 0; + + if (nbits > 32) { + dev_err(dsp->dev, "Exceeded maximum read length: %d > 32\n", nbits); + return -EINVAL; + } + + while (nbits) { + if (!ch->cachebits) { + if (cl_dsp_memchunk_end(ch)) { + dev_err(dsp->dev, "Read past end of memory chunk\n"); + return -ENOSPC; + } + + ch->cache = 0; + ch->cachebits = 24; + + for (i = 0; i < sizeof(ch->cache); i++, ch->cache <<= 8) + ch->cache |= *ch->data++; + + ch->bytes += sizeof(ch->cache); + } + + nread = min(ch->cachebits, nbits); + nbits -= nread; + + result |= ((ch->cache >> (32 - nread)) << nbits); + ch->cache <<= nread; + ch->cachebits -= nread; + } + + if (val) + memcpy(val, &result, nbytes); + + return 0; +} +EXPORT_SYMBOL(cl_dsp_memchunk_read); + +int cl_dsp_memchunk_write(struct cl_dsp_memchunk *ch, int nbits, u32 val) +{ + int nwrite, i; + + nwrite = min(24 - ch->cachebits, nbits); + + ch->cache <<= nwrite; + ch->cache |= val >> (nbits - nwrite); + ch->cachebits += nwrite; + nbits -= nwrite; + + if (ch->cachebits == 24) { + if (cl_dsp_memchunk_end(ch)) + return -ENOSPC; + + ch->cache &= 0xFFFFFF; + for (i = 0; i < sizeof(ch->cache); i++, ch->cache <<= 8) + *ch->data++ = (ch->cache & 0xFF000000) >> 24; + + ch->bytes += sizeof(ch->cache); + ch->cachebits = 0; + } + + if (nbits) + return cl_dsp_memchunk_write(ch, nbits, val); + + return 0; +} +EXPORT_SYMBOL(cl_dsp_memchunk_write); + +int cl_dsp_memchunk_flush(struct cl_dsp_memchunk *ch) +{ + if (!ch->cachebits) + return 0; + + return cl_dsp_memchunk_write(ch, 24 - ch->cachebits, 0); +} +EXPORT_SYMBOL(cl_dsp_memchunk_flush); + +int cl_dsp_raw_write(struct cl_dsp *dsp, unsigned int reg, + const void *val, size_t val_len, size_t limit) +{ + int i, ret = 0; + + if (!dsp) + return -EPERM; + + /* Restrict write length to limit value */ + for (i = 0; i < val_len; i += limit) { + ret = regmap_raw_write(dsp->regmap, (reg + i), (val + i), + (val_len - i) > limit ? limit : (val_len - i)); + if (ret) + break; + } + + return ret; +} +EXPORT_SYMBOL(cl_dsp_raw_write); + +int cl_dsp_get_reg(struct cl_dsp *dsp, const char *coeff_name, + const unsigned int block_type, + const unsigned int algo_id, unsigned int *reg) +{ + int ret = 0; + struct cl_dsp_coeff_desc *coeff_desc; + unsigned int mem_region_prefix; + + if (!dsp) + return -EPERM; + + if (list_empty(&dsp->coeff_desc_head)) { + dev_err(dsp->dev, "Coefficient list is empty\n"); + return -ENOENT; + } + + list_for_each_entry(coeff_desc, &dsp->coeff_desc_head, list) { + if (strncmp(coeff_desc->name, coeff_name, + CL_DSP_COEFF_NAME_LEN_MAX)) + continue; + if (coeff_desc->block_type != block_type) + continue; + if ((coeff_desc->parent_id & 0xFFFF) != (algo_id & 0xFFFF)) + continue; + + *reg = coeff_desc->reg; + if (*reg == 0) { + dev_err(dsp->dev, + "No DSP control called %s for block 0x%X\n", + coeff_name, block_type); + return -ENXIO; + } + + break; + } + + /* verify register found in expected region */ + switch (block_type) { + case CL_DSP_XM_PACKED_TYPE: + mem_region_prefix = (CL_DSP_HALO_XMEM_PACKED_BASE + & CL_DSP_MEM_REG_TYPE_MASK) + >> CL_DSP_MEM_REG_TYPE_SHIFT; + break; + case CL_DSP_XM_UNPACKED_TYPE: + mem_region_prefix = (CL_DSP_HALO_XMEM_UNPACKED24_BASE + & CL_DSP_MEM_REG_TYPE_MASK) + >> CL_DSP_MEM_REG_TYPE_SHIFT; + break; + case CL_DSP_YM_PACKED_TYPE: + mem_region_prefix = (CL_DSP_HALO_YMEM_PACKED_BASE + & CL_DSP_MEM_REG_TYPE_MASK) + >> CL_DSP_MEM_REG_TYPE_SHIFT; + break; + case CL_DSP_YM_UNPACKED_TYPE: + mem_region_prefix = (CL_DSP_HALO_YMEM_UNPACKED24_BASE + & CL_DSP_MEM_REG_TYPE_MASK) + >> CL_DSP_MEM_REG_TYPE_SHIFT; + break; + case CL_DSP_PM_PACKED_TYPE: + mem_region_prefix = (CL_DSP_HALO_PMEM_BASE + & CL_DSP_MEM_REG_TYPE_MASK) + >> CL_DSP_MEM_REG_TYPE_SHIFT; + break; + default: + dev_err(dsp->dev, "Unrecognized block type: 0x%X\n", + block_type); + return -EINVAL; + } + + if (((*reg & CL_DSP_MEM_REG_TYPE_MASK) >> CL_DSP_MEM_REG_TYPE_SHIFT) + != mem_region_prefix) { + dev_err(dsp->dev, + "DSP control %s at 0x%X found in unexpected region\n", + coeff_name, *reg); + + ret = -EFAULT; + } + + return ret; +} +EXPORT_SYMBOL(cl_dsp_get_reg); + +bool cl_dsp_algo_is_present(struct cl_dsp *dsp, const unsigned int algo_id) +{ + int i; + + if (!dsp) + return false; + + for (i = 0; i < dsp->num_algos; i++) { + if ((GENMASK(15, 0) & dsp->algo_info[i].id) == + (GENMASK(15, 0) & algo_id)) + return true; + } + + return false; +} +EXPORT_SYMBOL(cl_dsp_algo_is_present); + +static int cl_dsp_process_data_be(const u8 *data, + const unsigned int num_bytes, unsigned int *val) +{ + int i; + + if (num_bytes > sizeof(unsigned int)) + return -EINVAL; + + *val = 0; + for (i = 0; i < num_bytes; i++) + *val += *(data + i) << (i * CL_DSP_BITS_PER_BYTE); + + return 0; +} + +static int cl_dsp_owt_init(struct cl_dsp *dsp, const struct firmware *bin) +{ + if (!dsp->wt_desc) { + dev_err(dsp->dev, "Wavetable not supported by core driver\n"); + return -EPERM; + } + + dsp->wt_desc->owt.raw_data = devm_kzalloc(dsp->dev, bin->size, + GFP_KERNEL); + if (!dsp->wt_desc->owt.raw_data) + return -ENOMEM; + + memcpy(dsp->wt_desc->owt.raw_data, &bin->data[0], bin->size); + + return 0; +} + +static int cl_dsp_read_wt(struct cl_dsp *dsp, int pos, int size) +{ + struct cl_dsp_owt_header *entry = dsp->wt_desc->owt.waves; + void *buf = (void *)(dsp->wt_desc->owt.raw_data + pos); + struct cl_dsp_memchunk ch = cl_dsp_memchunk_create(buf, size); + u32 *wbuf = buf, *max = buf; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(dsp->wt_desc->owt.waves); i++, entry++) { + ret = cl_dsp_memchunk_read(dsp, &ch, 16, &entry->flags); + if (ret) + return ret; + + ret = cl_dsp_memchunk_read(dsp, &ch, 8, &entry->type); + if (ret) + return ret; + + if (entry->type == WT_TYPE_TERMINATOR) { + dsp->wt_desc->owt.nwaves = i; + dsp->wt_desc->owt.bytes = max(ch.bytes, + (int)((void *)max - buf)); + + return dsp->wt_desc->owt.bytes; + } + + ret = cl_dsp_memchunk_read(dsp, &ch, 24, &entry->offset); + if (ret) + return ret; + + ret = cl_dsp_memchunk_read(dsp, &ch, 24, &entry->size); + if (ret) + return ret; + + entry->data = wbuf + entry->offset; + + if (wbuf + entry->offset + entry->size > max) { + max = wbuf + entry->offset + entry->size; + + if (!cl_dsp_memchunk_valid_addr(&ch, max)) + return -EINVAL; + } + } + + dev_err(dsp->dev, "Maximum number of wavetable entries exceeded\n"); + return -E2BIG; +} + +static int cl_dsp_coeff_header_parse(struct cl_dsp *dsp, + union cl_dsp_wmdr_header header) +{ + struct device *dev = dsp->dev; + + if (memcmp(header.magic, CL_DSP_WMDR_MAGIC_ID, CL_DSP_MAGIC_ID_SIZE)) { + dev_err(dev, "Failed to recognize coefficient file\n"); + return -EINVAL; + } + + if (header.header_len != CL_DSP_COEFF_FILE_HEADER_SIZE) { + dev_err(dev, "Malformed coeff. header\n"); + return -EINVAL; + } + + if (header.fw_revision != dsp->algo_info[0].rev) { + dev_warn(dev, + "Coeff. rev. 0x%06X mismatches 0x%06X, continuing..\n", + header.fw_revision, dsp->algo_info[0].rev); + } + + if (header.file_format_version < CL_DSP_COEFF_MIN_FORMAT_VERSION) { + dev_err(dev, "File format version 0x%02X is outdated\n", + header.file_format_version); + return -EINVAL; + } + + if (header.api_revision != CL_DSP_COEFF_API_REV_HALO && + header.api_revision != CL_DSP_COEFF_API_REV_ADSP2) { + dev_err(dev, "API Revision 0x%06X is not compatible\n", + header.api_revision); + return -EINVAL; + } + + if (header.target_core != CL_DSP_TARGET_CORE_ADSP2 && + header.target_core != CL_DSP_TARGET_CORE_HALO) { + dev_err(dev, "Target core 0x%02X is not compatible\n", + header.target_core); + return -EINVAL; + } + + return 0; +} + +static void cl_dsp_coeff_handle_info_text(struct cl_dsp *dsp, const u8 *data, + u32 len) +{ + char *info_str; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + info_str = kzalloc(len+1, GFP_KERNEL); +#else + info_str = kzalloc(len, GFP_KERNEL); +#endif + if (!info_str) + return; + + memcpy(info_str, data, len); +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dsp->dev, "WMDR Info: %s\n", info_str); +#else + dev_dbg(dsp->dev, "WMDR Info: %s\n", info_str); +#endif + kfree(info_str); +} + +static int cl_dsp_wavetable_check(struct cl_dsp *dsp, const struct firmware *fw, + unsigned int reg, unsigned int pos, u32 data_len, u32 type) +{ + u32 data_len_bytes = data_len / 4 * 3; + unsigned int limit, wt_reg; + bool is_xm; + int ret; + + if (type == CL_DSP_XM_UNPACKED_TYPE) { + is_xm = true; + limit = dsp->wt_desc->wt_limit_xm; + ret = cl_dsp_get_reg(dsp, dsp->wt_desc->wt_name_xm, + type, dsp->wt_desc->id, &wt_reg); + } else if (type == CL_DSP_YM_UNPACKED_TYPE) { + is_xm = false; + limit = dsp->wt_desc->wt_limit_ym; + ret = cl_dsp_get_reg(dsp, dsp->wt_desc->wt_name_ym, + type, dsp->wt_desc->id, &wt_reg); + } else { + dev_err(dsp->dev, "Invalid wavetable memory type 0x%04X\n", + type); + ret = -EINVAL; + } + if (ret) + return ret; + + if (reg == wt_reg) { + if (data_len > limit) { + dev_err(dsp->dev, "%s too large: %d bytes\n", + is_xm ? "XM" : "YM", data_len_bytes); + return -EFBIG; + } + + ret = cl_dsp_owt_init(dsp, fw); + if (ret) + return ret; + + ret = cl_dsp_read_wt(dsp, pos, data_len); + if (ret < 0) + return ret; + + dsp->wt_desc->is_xm = is_xm; + + dev_info(dsp->dev, "Wavetable found: %d bytes (XM)\n", + data_len_bytes); + } + + return 0; +} + +int cl_dsp_coeff_file_parse(struct cl_dsp *dsp, const struct firmware *fw) +{ + unsigned int pos = CL_DSP_COEFF_FILE_HEADER_SIZE; + bool wt_found = false; + int ret = -EINVAL; + struct cl_dsp_coeff_data_block data_block; + union cl_dsp_wmdr_header wmdr_header; + char wt_date[CL_DSP_WMDR_DATE_LEN]; + unsigned int reg, algo_rev; + u16 algo_id, parent_id; + struct device *dev; + u32 data_len; + int i; + + if (!dsp) + return -EPERM; + + dev = dsp->dev; + + *wt_date = '\0'; + + memcpy(wmdr_header.data, fw->data, CL_DSP_COEFF_FILE_HEADER_SIZE); + + if (fw->size % CL_DSP_BYTES_PER_WORD) { + dev_err(dev, "Coefficient file is not word-aligned\n"); + return ret; + } + + ret = cl_dsp_coeff_header_parse(dsp, wmdr_header); + if (ret) + return ret; + + while (pos < fw->size) { + memcpy(data_block.header.data, &fw->data[pos], + CL_DSP_COEFF_DBLK_HEADER_SIZE); + pos += CL_DSP_COEFF_DBLK_HEADER_SIZE; + + data_len = data_block.header.data_len; + data_block.payload = kvmalloc(data_len, GFP_KERNEL); + if (!data_block.payload) + return -ENOMEM; + + memcpy(data_block.payload, &fw->data[pos], data_len); + + algo_id = data_block.header.algo_id & 0xFFFF; + + if (data_block.header.block_type != CL_DSP_WMDR_NAME_TYPE && + data_block.header.block_type != CL_DSP_WMDR_INFO_TYPE) { + for (i = 0; i < dsp->num_algos; i++) { + parent_id = dsp->algo_info[i].id & 0xFFFF; + if (algo_id == parent_id) + break; + } + + if (i == dsp->num_algos) { + dev_err(dev, "Invalid algo. ID: 0x%06X\n", + data_block.header.algo_id); + ret = -EINVAL; + goto err_free; + } + + algo_rev = data_block.header.algo_rev >> + CL_DSP_REV_OFFSET_SHIFT; + + if (CL_DSP_GET_MAJOR(algo_rev) != + CL_DSP_GET_MAJOR(dsp->algo_info[i].rev)) { + dev_err(dev, + "Invalid algo. rev.: %d.%d.%d (0x%06X)\n", + (int) CL_DSP_GET_MAJOR(algo_rev), + (int) CL_DSP_GET_MINOR(algo_rev), + (int) CL_DSP_GET_PATCH(algo_rev), + data_block.header.algo_id); + + ret = -EINVAL; + goto err_free; + } + + wt_found = (algo_id == (dsp->wt_desc->id & 0xFFFF)); + } + + switch (data_block.header.block_type) { + case CL_DSP_WMDR_NAME_TYPE: + case CL_DSP_WMDR_INFO_TYPE: + reg = 0; + + cl_dsp_coeff_handle_info_text(dsp, data_block.payload, + data_len); + + if (data_len < CL_DSP_WMDR_DATE_LEN) + break; + + if (memcmp(&fw->data[pos], CL_DSP_WMDR_DATE_PREFIX, + CL_DSP_WMDR_DATE_PREFIX_LEN)) + break; /* date already recorded */ + + memcpy(wt_date, + &fw->data[pos + CL_DSP_WMDR_DATE_PREFIX_LEN], + CL_DSP_WMDR_DATE_LEN - + CL_DSP_WMDR_DATE_PREFIX_LEN); + + wt_date[CL_DSP_WMDR_DATE_LEN - + CL_DSP_WMDR_DATE_PREFIX_LEN] = '\0'; + break; + case CL_DSP_XM_UNPACKED_TYPE: + reg = CL_DSP_HALO_XMEM_UNPACKED24_BASE + + data_block.header.start_offset + + dsp->algo_info[i].xm_base * + CL_DSP_BYTES_PER_WORD; + + if (wt_found) { + ret = cl_dsp_wavetable_check(dsp, + fw, reg, pos, data_len, + CL_DSP_XM_UNPACKED_TYPE); + if (ret) + goto err_free; + } + break; + case CL_DSP_XM_PACKED_TYPE: + reg = (CL_DSP_HALO_XMEM_PACKED_BASE + + data_block.header.start_offset + + dsp->algo_info[i].xm_base * + CL_DSP_PACKED_NUM_BYTES) & + ~CL_DSP_ALIGN; + break; + case CL_DSP_YM_UNPACKED_TYPE: + reg = CL_DSP_HALO_YMEM_UNPACKED24_BASE + + data_block.header.start_offset + + dsp->algo_info[i].ym_base * + CL_DSP_UNPACKED_NUM_BYTES; + if (wt_found) { + ret = cl_dsp_wavetable_check(dsp, + fw, reg, pos, data_len, + CL_DSP_YM_UNPACKED_TYPE); + if (ret) + goto err_free; + } + break; + case CL_DSP_YM_PACKED_TYPE: + reg = (CL_DSP_HALO_YMEM_PACKED_BASE + + data_block.header.start_offset + + dsp->algo_info[i].ym_base * + CL_DSP_PACKED_NUM_BYTES) & + ~CL_DSP_ALIGN; + break; + default: + dev_err(dev, "Unexpected block type: 0x%04X\n", + data_block.header.block_type); + ret = -EINVAL; + goto err_free; + } + if (reg) { + ret = cl_dsp_raw_write(dsp, reg, &fw->data[pos], + data_len, CL_DSP_MAX_WLEN); + if (ret) { + dev_err(dev, "Failed to write coefficients\n"); + goto err_free; + } + } + + /* Blocks are word-aligned */ + pos += (data_len + 3) & ~CL_DSP_ALIGN; + + kvfree(data_block.payload); + } + + if (wt_found) { + if (*wt_date != '\0') + strscpy(dsp->wt_desc->wt_date, wt_date, + CL_DSP_WMDR_DATE_LEN); + else + strscpy(dsp->wt_desc->wt_date, + CL_DSP_WMDR_FILE_DATE_MISSING, + CL_DSP_WMDR_DATE_LEN); + } + + return 0; + +err_free: + kvfree(data_block.payload); + + return ret; +} +EXPORT_SYMBOL(cl_dsp_coeff_file_parse); + +static int cl_dsp_algo_parse(struct cl_dsp *dsp, const unsigned char *data) +{ + struct cl_dsp_coeff_desc *coeff_desc; + u8 algo_name_len; + u16 algo_desc_len, block_offset, block_type; + u32 algo_id, block_len; + char *algo_name; + unsigned int coeff_count, val, pos = 0, header_pos = 0; + unsigned int coeff_name_len, coeff_fullname_len, coeff_desc_len; + unsigned int coeff_flags, coeff_len; + int i, ret; + + ret = cl_dsp_process_data_be(&data[pos], CL_DSP_ALGO_ID_SIZE, + &algo_id); + if (ret) { + dev_err(dsp->dev, "Failed to read data\n"); + return ret; + } + pos += CL_DSP_ALGO_ID_SIZE; + + ret = cl_dsp_process_data_be(&data[pos], CL_DSP_ALGO_NAME_LEN_SIZE, + &val); + if (ret) { + dev_err(dsp->dev, "Failed to read data\n"); + return ret; + } + algo_name_len = (u8) (val & CL_DSP_BYTE_MASK); + + algo_name = kzalloc(algo_name_len, GFP_KERNEL); + if (!algo_name) + return -ENOMEM; + + memcpy(algo_name, &data[pos + CL_DSP_ALGO_NAME_LEN_SIZE], + algo_name_len); + pos += CL_DSP_WORD_ALIGN(algo_name_len); + + ret = cl_dsp_process_data_be(&data[pos], CL_DSP_ALGO_DESC_LEN_SIZE, + &val); + if (ret) { + dev_err(dsp->dev, "Failed to read data\n"); + goto err_free; + } + + /* Nothing required for algo. description, so it is skipped */ + algo_desc_len = (u16) (val & CL_DSP_NIBBLE_MASK); + pos += CL_DSP_WORD_ALIGN(algo_desc_len); + + ret = cl_dsp_process_data_be(&data[pos], CL_DSP_COEFF_COUNT_SIZE, + &coeff_count); + if (ret) { + dev_err(dsp->dev, "Failed to read data\n"); + goto err_free; + } + pos += CL_DSP_COEFF_COUNT_SIZE; + + for (i = 0; i < coeff_count; i++) { + ret = cl_dsp_process_data_be(&data[pos], + CL_DSP_COEFF_OFFSET_SIZE, &val); + if (ret) { + dev_err(dsp->dev, "Failed to read data\n"); + goto err_free; + } + + block_offset = (u16) (val & CL_DSP_NIBBLE_MASK); + pos += CL_DSP_COEFF_OFFSET_SIZE; + + ret = cl_dsp_process_data_be(&data[pos], + CL_DSP_COEFF_TYPE_SIZE, &val); + if (ret) { + dev_err(dsp->dev, "Failed to read data\n"); + goto err_free; + } + + block_type = (u16) (val & CL_DSP_NIBBLE_MASK); + pos += CL_DSP_COEFF_TYPE_SIZE; + + ret = cl_dsp_process_data_be(&data[pos], CL_DSP_COEFF_LEN_SIZE, + &block_len); + if (ret) { + dev_err(dsp->dev, "Failed to read data\n"); + goto err_free; + } + pos += CL_DSP_COEFF_LEN_SIZE; + header_pos = pos; + + coeff_desc = devm_kzalloc(dsp->dev, sizeof(*coeff_desc), + GFP_KERNEL); + if (!coeff_desc) { + ret = -ENOMEM; + goto err_free; + } + + coeff_desc->parent_id = algo_id; + coeff_desc->parent_name = algo_name; + coeff_desc->block_offset = block_offset; + coeff_desc->block_type = block_type; + + memcpy(coeff_desc->name, data + pos + 1, *(data + pos)); + coeff_desc->name[*(data + pos)] = '\0'; + + ret = cl_dsp_process_data_be(&data[pos], + CL_DSP_COEFF_NAME_LEN_SIZE, &coeff_name_len); + if (ret) { + dev_err(dsp->dev, "Failed to read data\n"); + return ret; + } + pos += CL_DSP_WORD_ALIGN(coeff_name_len); + + ret = cl_dsp_process_data_be(&data[pos], + CL_DSP_COEFF_FULLNAME_LEN_SIZE, + &coeff_fullname_len); + if (ret) { + dev_err(dsp->dev, "Failed to read data\n"); + return ret; + } + pos += CL_DSP_WORD_ALIGN(coeff_fullname_len); + + ret = cl_dsp_process_data_be(&data[pos], + CL_DSP_COEFF_DESC_LEN_SIZE, &coeff_desc_len); + + if (ret) { + dev_err(dsp->dev, "Failed to read data\n"); + return ret; + } + pos += CL_DSP_WORD_ALIGN(coeff_desc_len); + + ret = cl_dsp_process_data_be(&data[pos], + CL_DSP_COEFF_FLAGS_SIZE, &coeff_flags); + if (ret) { + dev_err(dsp->dev, "Failed to read data\n"); + return ret; + } + pos += CL_DSP_COEFF_FLAGS_SIZE; + + ret = cl_dsp_process_data_be(&data[pos], CL_DSP_COEFF_LEN_SIZE, + &coeff_len); + if (ret) { + dev_err(dsp->dev, "Failed to read data\n"); + return ret; + } + pos += CL_DSP_COEFF_LEN_SIZE; + + coeff_desc->flags = coeff_flags >> CL_DSP_COEFF_FLAGS_SHIFT; + coeff_desc->length = coeff_len; + + list_add(&coeff_desc->list, &dsp->coeff_desc_head); + + pos = header_pos + block_len; + } + +err_free: + kfree(algo_name); + + return ret; +} + +int cl_dsp_fw_id_get(struct cl_dsp *dsp, unsigned int *id) +{ + int ret = 0; + + ret = regmap_read(dsp->regmap, CL_DSP_HALO_XM_FW_ID_REG, id); + if (ret) + dev_err(dsp->dev, "Failed to read firmware ID\n"); + + return ret; +} +EXPORT_SYMBOL(cl_dsp_fw_id_get); + +int cl_dsp_fw_rev_get(struct cl_dsp *dsp, unsigned int *rev) +{ + int ret = 0; + + ret = regmap_read(dsp->regmap, CL_DSP_HALO_XM_FW_ID_REG + + CL_DSP_HALO_ALGO_REV_OFFSET, rev); + if (ret) + dev_err(dsp->dev, "Failed to read firmware revision\n"); + + return ret; +} +EXPORT_SYMBOL(cl_dsp_fw_rev_get); + +static int cl_dsp_coeff_init(struct cl_dsp *dsp) +{ + unsigned int reg = CL_DSP_HALO_XM_FW_ID_REG; + struct cl_dsp_coeff_desc *coeff_desc; + struct regmap *regmap; + struct device *dev; + unsigned int val; + int ret, i; + + if (!dsp) + return -EPERM; + + dev = dsp->dev; + regmap = dsp->regmap; + + ret = regmap_read(regmap, CL_DSP_HALO_NUM_ALGOS_REG, &val); + if (ret) { + dev_err(dev, "Failed to read number of algorithms\n"); + return ret; + } + + if (val > CL_DSP_NUM_ALGOS_MAX) { + dev_err(dev, "Invalid number of algorithms: %d\n", val); + return -EINVAL; + } + dsp->num_algos = val + 1; + + for (i = 0; i < dsp->num_algos; i++) { + ret = regmap_read(regmap, reg, &dsp->algo_info[i].id); + if (ret) { + dev_err(dev, "Failed to read algo. %d ID\n", i); + return ret; + } + + ret = regmap_read(regmap, reg + CL_DSP_HALO_ALGO_REV_OFFSET, + &dsp->algo_info[i].rev); + if (ret) { + dev_err(dev, "Failed to read algo. %d revision\n", i); + return ret; + } + + ret = regmap_read(regmap, reg + + CL_DSP_HALO_ALGO_XM_BASE_OFFSET, + &dsp->algo_info[i].xm_base); + if (ret) { + dev_err(dev, "Failed to read algo. %d XM_BASE\n", i); + return ret; + } + + ret = regmap_read(regmap, reg + + CL_DSP_HALO_ALGO_XM_SIZE_OFFSET, + &dsp->algo_info[i].xm_size); + if (ret) { + dev_err(dev, "Failed to read algo. %d XM_SIZE\n", i); + return ret; + } + + ret = regmap_read(regmap, reg + + CL_DSP_HALO_ALGO_YM_BASE_OFFSET, + &dsp->algo_info[i].ym_base); + if (ret) { + dev_err(dev, "Failed to read algo. %d YM_BASE\n", i); + return ret; + } + + ret = regmap_read(regmap, reg + + CL_DSP_HALO_ALGO_YM_SIZE_OFFSET, + &dsp->algo_info[i].ym_size); + if (ret) { + dev_err(dev, "Failed to read algo. %d YM_SIZE\n", i); + return ret; + } + + list_for_each_entry(coeff_desc, &dsp->coeff_desc_head, list) { + if (coeff_desc->parent_id != dsp->algo_info[i].id) + continue; + + switch (coeff_desc->block_type) { + case CL_DSP_XM_UNPACKED_TYPE: + coeff_desc->reg = + CL_DSP_HALO_XMEM_UNPACKED24_BASE + + dsp->algo_info[i].xm_base + * CL_DSP_UNPACKED_NUM_BYTES + + coeff_desc->block_offset + * CL_DSP_UNPACKED_NUM_BYTES; + + if (dsp->wt_desc && !strncmp(coeff_desc->name, + dsp->wt_desc->wt_name_xm, + CL_DSP_COEFF_NAME_LEN_MAX)) + dsp->wt_desc->wt_limit_xm = + (dsp->algo_info[i].xm_size - + coeff_desc->block_offset) * + CL_DSP_UNPACKED_NUM_BYTES; + break; + case CL_DSP_YM_UNPACKED_TYPE: + coeff_desc->reg = + CL_DSP_HALO_YMEM_UNPACKED24_BASE + + dsp->algo_info[i].ym_base * + CL_DSP_BYTES_PER_WORD + + coeff_desc->block_offset * + CL_DSP_BYTES_PER_WORD; + + if (dsp->wt_desc && !strncmp(coeff_desc->name, + dsp->wt_desc->wt_name_ym, + CL_DSP_COEFF_NAME_LEN_MAX)) + dsp->wt_desc->wt_limit_ym = + (dsp->algo_info[i].ym_size - + coeff_desc->block_offset) * + CL_DSP_UNPACKED_NUM_BYTES; + break; + } + + dev_dbg(dev, + "Control %s at 0x%08X with parent ID = 0x%X\n", + coeff_desc->name, coeff_desc->reg, + coeff_desc->parent_id); + } + + /* System algo. contains one extra register (num. algos.) */ + if (i) + reg += CL_DSP_ALGO_ENTRY_SIZE; + else + reg += (CL_DSP_ALGO_ENTRY_SIZE + + CL_DSP_BYTES_PER_WORD); + } + + ret = regmap_read(regmap, reg, &val); + if (ret) { + dev_err(dev, "Failed to read list terminator\n"); + return ret; + } + + if (val != CL_DSP_ALGO_LIST_TERM) { + dev_err(dev, "Invalid list terminator: 0x%X\n", val); + return -EINVAL; + } + + if (dsp->wt_desc) + dev_info(dev, + "Max. wavetable size: %d bytes (XM), %d bytes (YM)\n", + dsp->wt_desc->wt_limit_xm / 4 * 3, + dsp->wt_desc->wt_limit_ym / 4 * 3); + + return 0; +} + +static void cl_dsp_coeff_free(struct cl_dsp *dsp) +{ + struct cl_dsp_coeff_desc *coeff_desc; + + if (!dsp) + return; + + while (!list_empty(&dsp->coeff_desc_head)) { + coeff_desc = list_first_entry(&dsp->coeff_desc_head, + struct cl_dsp_coeff_desc, list); + list_del(&coeff_desc->list); + devm_kfree(dsp->dev, coeff_desc); + } +} + +static int cl_dsp_header_parse(struct cl_dsp *dsp, + union cl_dsp_wmfw_header header) +{ + struct device *dev = dsp->dev; + + if (memcmp(header.magic, CL_DSP_WMFW_MAGIC_ID, CL_DSP_MAGIC_ID_SIZE)) { + dev_err(dev, "Failed to recognize firmware file\n"); + return -EINVAL; + } + + if (header.header_len != CL_DSP_FW_FILE_HEADER_SIZE) { + dev_err(dev, "Malformed fimrware header\n"); + return -EINVAL; + } + + if (header.api_revision != CL_DSP_API_REVISION) { + dev_err(dev, "Firmware API Revision Incompatible with Core\n"); + return -EINVAL; + } + + if (header.target_core != CL_DSP_TARGET_CORE_HALO) { + dev_err(dev, "Unexpected target core type: 0x%02X\n", + header.target_core); + return -EINVAL; + } + + if (header.file_format_version < CL_DSP_MIN_FORMAT_VERSION) { + dev_err(dev, "File Format Version 0x%02X is outdated", + header.file_format_version); + return -EINVAL; + } + + dev_info(dev, + "Loading memory (bytes): XM: %u, YM: %u, PM: %u, ZM: %u\n", + header.xm_size, header.ym_size, header.pm_size, header.zm_size); + + return 0; +} + +static void cl_dsp_handle_info_text(struct cl_dsp *dsp, + const u8 *data, u32 len) +{ + char *info_str; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + info_str = kzalloc(len+1, GFP_KERNEL); +#else + info_str = kzalloc(len, GFP_KERNEL); +#endif + if (!info_str) + /* info block is empty or memory not allocated */ + return; + + memcpy(info_str, data, len); + + dev_info(dsp->dev, "WMFW Info: %s\n", info_str); + + kfree(info_str); +} + +int cl_dsp_firmware_parse(struct cl_dsp *dsp, const struct firmware *fw, + bool write_fw) +{ + unsigned int pos = CL_DSP_FW_FILE_HEADER_SIZE, reg = 0; + struct cl_dsp_data_block data_block; + union cl_dsp_wmfw_header wmfw_header; + struct device *dev; + int ret; + + if (!dsp) + return -EPERM; + + dev = dsp->dev; + + memcpy(wmfw_header.data, fw->data, CL_DSP_FW_FILE_HEADER_SIZE); + + ret = cl_dsp_header_parse(dsp, wmfw_header); + if (ret) + return ret; + + if (fw->size % CL_DSP_BYTES_PER_WORD) { + dev_err(dev, "Firmware file is not word-aligned\n"); + return -EINVAL; + } + + while (pos < fw->size) { + memcpy(data_block.header.data, &fw->data[pos], + CL_DSP_DBLK_HEADER_SIZE); + + pos += CL_DSP_DBLK_HEADER_SIZE; + + data_block.payload = + kvmalloc(data_block.header.data_len, GFP_KERNEL); + memcpy(data_block.payload, &fw->data[pos], + data_block.header.data_len); + + switch (data_block.header.block_type) { + case CL_DSP_WMFW_INFO_TYPE: + reg = 0; + cl_dsp_handle_info_text(dsp, data_block.payload, + data_block.header.data_len); + break; + case CL_DSP_PM_PACKED_TYPE: + reg = CL_DSP_HALO_PMEM_BASE + + data_block.header.start_offset * + CL_DSP_PM_NUM_BYTES; + break; + case CL_DSP_XM_PACKED_TYPE: + reg = CL_DSP_HALO_XMEM_PACKED_BASE + + data_block.header.start_offset * + CL_DSP_PACKED_NUM_BYTES; + break; + case CL_DSP_XM_UNPACKED_TYPE: + reg = CL_DSP_HALO_XMEM_UNPACKED24_BASE + + data_block.header.start_offset * + CL_DSP_UNPACKED_NUM_BYTES; + break; + case CL_DSP_YM_PACKED_TYPE: + reg = CL_DSP_HALO_YMEM_PACKED_BASE + + data_block.header.start_offset * + CL_DSP_PACKED_NUM_BYTES; + break; + case CL_DSP_YM_UNPACKED_TYPE: + reg = CL_DSP_HALO_YMEM_UNPACKED24_BASE + + data_block.header.start_offset * + CL_DSP_UNPACKED_NUM_BYTES; + break; + case CL_DSP_ALGO_INFO_TYPE: + reg = 0; + + ret = cl_dsp_algo_parse(dsp, data_block.payload); + if (ret) + goto err_free; + break; + default: + dev_err(dev, "Unexpected block type : 0x%02X\n", + data_block.header.block_type); + ret = -EINVAL; + goto err_free; + } + + if (write_fw && reg) { + ret = cl_dsp_raw_write(dsp, reg, data_block.payload, + data_block.header.data_len, + CL_DSP_MAX_WLEN); + if (ret) { + dev_err(dev, + "Failed to write to base 0x%X\n", reg); + goto err_free; + } + } + + /* Blocks are word-aligned */ + pos += (data_block.header.data_len + 3) & ~CL_DSP_ALIGN; + + kvfree(data_block.payload); + } + + return cl_dsp_coeff_init(dsp); + +err_free: + kvfree(data_block.payload); + + return ret; +} +EXPORT_SYMBOL(cl_dsp_firmware_parse); + +int cl_dsp_wavetable_create(struct cl_dsp *dsp, unsigned int id, + const char *wt_name_xm, const char *wt_name_ym, + const char *wt_file) +{ + struct cl_dsp_wt_desc *wt_desc; + + if (!dsp) + return -EPERM; + + wt_desc = devm_kzalloc(dsp->dev, sizeof(struct cl_dsp_wt_desc), + GFP_KERNEL); + if (!wt_desc) + return -ENOMEM; + + wt_desc->id = id; + strscpy(wt_desc->wt_name_xm, wt_name_xm, strlen(wt_name_xm) + 1); + strscpy(wt_desc->wt_name_ym, wt_name_ym, strlen(wt_name_ym) + 1); + strscpy(wt_desc->wt_file, wt_file, strlen(wt_file) + 1); + + dsp->wt_desc = wt_desc; + + return 0; +} +EXPORT_SYMBOL(cl_dsp_wavetable_create); + +struct cl_dsp *cl_dsp_create(struct device *dev, struct regmap *regmap) +{ + struct cl_dsp *dsp; + + dsp = devm_kzalloc(dev, sizeof(struct cl_dsp), GFP_KERNEL); + if (!dsp) + return ERR_PTR(-ENOMEM); + + dsp->dev = dev; + dsp->regmap = regmap; + + INIT_LIST_HEAD(&dsp->coeff_desc_head); + + return dsp; +} +EXPORT_SYMBOL(cl_dsp_create); + +int cl_dsp_destroy(struct cl_dsp *dsp) +{ + if (!dsp) + return -EPERM; + + if (!list_empty(&dsp->coeff_desc_head)) + cl_dsp_coeff_free(dsp); + + if (dsp->wt_desc) + devm_kfree(dsp->dev, dsp->wt_desc); + + devm_kfree(dsp->dev, dsp); + + return 0; +} +EXPORT_SYMBOL(cl_dsp_destroy); + +MODULE_DESCRIPTION("Cirrus Logic DSP Firmware Driver"); +MODULE_AUTHOR("Fred Treven, Cirrus Logic Inc, "); +MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/cirrus/cs_dsp.c b/drivers/firmware/cirrus/cs_dsp.c index 64ed9d3f5d5d..e7c077fc6341 100644 --- a/drivers/firmware/cirrus/cs_dsp.c +++ b/drivers/firmware/cirrus/cs_dsp.c @@ -1976,7 +1976,7 @@ static int cs_dsp_halo_setup_algs(struct cs_dsp *dsp) return ret; } -static int cs_dsp_load_coeff(struct cs_dsp *dsp, const struct firmware *firmware, +int cs_dsp_load_coeff(struct cs_dsp *dsp, const struct firmware *firmware, const char *file) { LIST_HEAD(buf_list); @@ -2177,6 +2177,7 @@ static int cs_dsp_load_coeff(struct cs_dsp *dsp, const struct firmware *firmware kfree(text); return ret; } +EXPORT_SYMBOL_GPL(cs_dsp_load_coeff); static int cs_dsp_create_name(struct cs_dsp *dsp) { diff --git a/drivers/input/input_boost/Kconfig b/drivers/input/input_boost/Kconfig new file mode 100644 index 000000000000..3ffffc9b24ad --- /dev/null +++ b/drivers/input/input_boost/Kconfig @@ -0,0 +1,71 @@ +config SEC_INPUT_BOOSTER + tristate "SEC INPUT BOOSTER" + depends on INPUT_EVDEV + default n + help + Say Y here if you enable input boosting for various input device + such as touch. + + Say N here if you disable input boosting. + + If unsure, say Y. + +config SEC_INPUT_BOOSTER_MODE + bool "SEC INPUT BOOSTER MODE" + depends on SEC_INPUT_BOOSTER + default n + help + Say Y here if you enable input boosting mode for various scenarios + where needed to disable or change input booster. + + Say N here if you disable input booster mode + which means DVFS file will be used in this model for handling scenarios. + + If unsure, say N. + +config SEC_INPUT_BOOSTER_QC + bool "SEC INPUT BOOSTER QC" + depends on SEC_INPUT_BOOSTER + default n + help + Say Y here if you enable input boosting of QC under QC chipset + + Say N here if you disable input boosting of QC + + If unsure, say N. + +config SEC_INPUT_BOOSTER_SLSI + bool "SEC INPUT BOOSTER SLSI" + depends on SEC_INPUT_BOOSTER + default n + help + Say Y here if you enable input boosting of SLSI under SLSI chipset + such as touch. + + Say N here if you disable input boosting of SLSI + + If unsure, say N. + +config SEC_INPUT_BOOSTER_MTK + bool "SEC INPUT BOOSTER MTK" + depends on SEC_INPUT_BOOSTER + default n + help + Say Y here if you enable input boosting of MTK under MTK chipset + such as touch. + + Say N here if you disable input boosting of MTK + + If unsure, say N. + + +config SEC_INPUT_BOOSTER_HANDLER + bool "SEC INPUT BOOSTER HANDLER" + depends on SEC_INPUT_BOOSTER + default n + help + Say Y here if you enable input_handler + + Say N here if you use legacy evdev.c event + + If unsure, say N. \ No newline at end of file diff --git a/drivers/input/input_boost/Makefile b/drivers/input/input_boost/Makefile new file mode 100644 index 000000000000..57d5eb7b3d15 --- /dev/null +++ b/drivers/input/input_boost/Makefile @@ -0,0 +1,27 @@ +obj-$(CONFIG_SEC_INPUT_BOOSTER) += input_booster_lkm.o +ifdef CONFIG_SEC_INPUT_BOOSTER_MODE + input_booster_lkm-$(CONFIG_SEC_INPUT_BOOSTER) += evdev_booster.o input_booster_mode.o +else + input_booster_lkm-$(CONFIG_SEC_INPUT_BOOSTER) += evdev_booster.o input_booster.o +endif + +input_booster_lkm-$(CONFIG_SEC_INPUT_BOOSTER_QC) += input_booster_qc.o +input_booster_lkm-$(CONFIG_SEC_INPUT_BOOSTER_SLSI) += input_booster_lsi.o +input_booster_lkm-$(CONFIG_SEC_INPUT_BOOSTER_MTK) += input_booster_mtk.o +# DDRfreq limit API +ifdef CONFIG_MACH_MT6739 +ccflags-y += -I$(srctree)/drivers/misc/mediatek/base/power/include/ +endif +ifdef CONFIG_MACH_MT6853 +ccflags-y += -I$(srctree)/drivers/misc/mediatek/sched/ +endif +ifdef CONFIG_MACH_MT6768 +ccflags-y += -I$(srctree)/drivers/misc/mediatek/sched/ +endif +ifdef CONFIG_MACH_MT6877 +ccflags-y += -I$(srctree)/drivers/misc/mediatek/sched/ +endif +ifdef CONFIG_MTK_DVFSRC +ccflags-y += -I$(srctree)/drivers/misc/mediatek/include/mt-plat/ +ccflags-y += -I$(srctree)/../kernel_device_modules-6.1/drivers/misc/mediatek/include/mt-plat/ +endif diff --git a/drivers/input/input_boost/evdev_booster.c b/drivers/input/input_boost/evdev_booster.c new file mode 100644 index 000000000000..1640f79716ee --- /dev/null +++ b/drivers/input/input_boost/evdev_booster.c @@ -0,0 +1,491 @@ +#define ITAG " [Evdev Booster] " +#include + +#include +#include +#include +#include +#include + +#if IS_ENABLED(CONFIG_SEC_INPUT_BOOSTER) +spinlock_t ib_ev_lock; +struct workqueue_struct *ev_unbound_wq; +static struct device *evbst_dev; + +void input_booster(struct ib_event_data *ib_ev_data); + +int evdev_ib_notify_callback(struct notifier_block *nb, + unsigned long event_type, void *data) +{ + if (ib_init_succeed) { + spin_lock(&ib_ev_lock); + struct ib_event_data *ib_ev_data = data; + + switch (event_type) { + case IB_EVENT_TOUCH_BOOSTER: + input_booster(ib_ev_data); + break; + default: + break; + } + spin_unlock(&ib_ev_lock); + } + + return 0; +} + +static struct notifier_block ib_event_notifier = { + .notifier_call = evdev_ib_notify_callback, + .priority = 1, +}; + +#if defined(CONFIG_SEC_INPUT_BOOSTER_HANDLER) +struct workqueue_struct *ib_unbound_highwq; +spinlock_t ib_idx_lock; +struct ib_event_work *ib_evt_work; +int ib_work_cnt; + +int ib_notifier_register(struct notifier_block *nb) { + /* nothing to do here */ + return 0; +} + +int ib_notifier_unregister(struct notifier_block *nb) { + /* nothing to do here */ + return 0; +} + +static void evdev_ib_trigger(struct work_struct* work) +{ + struct ib_event_work *ib_work = container_of(work, struct ib_event_work, evdev_work); + struct ib_event_data ib_data = {0, }; + + spin_lock(&ib_ev_lock); + ib_data.evt_cnt = ib_work->evt_cnt; + ib_data.vals = ib_work->vals; + input_booster(&ib_data); + spin_unlock(&ib_ev_lock); +} + +static void sec_input_boost_events(struct input_handle *handle, + const struct input_value *vals, unsigned int count) { + int cur_ib_idx; + + spin_lock(&ib_idx_lock); + cur_ib_idx = ib_work_cnt++; + if (ib_work_cnt >= MAX_IB_COUNT) { + pr_info("[Input Booster] Ib_Work_Cnt(%d), Event_Cnt(%d)\n", ib_work_cnt, count); + ib_work_cnt = 0; + } + + if (ib_evt_work != NULL) { + ib_evt_work[cur_ib_idx].evt_cnt = count; + memcpy(ib_evt_work[cur_ib_idx].vals, vals, sizeof(struct input_value) * count); + queue_work(ib_unbound_highwq, &(ib_evt_work[cur_ib_idx].evdev_work)); + } + spin_unlock(&ib_idx_lock); +} + +static void sec_input_boost_event(struct input_handle *handle, + unsigned int type, unsigned int code, int value) +{ + struct input_value vals[] = { { type, code, value } }; + + sec_input_boost_events(handle, vals, 1); +} + +static int sec_input_boost_connect(struct input_handler *handler, + struct input_dev *dev, const struct input_device_id *id) +{ + struct input_handle *handle; + int error; + + handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + /* + dev_name(&dev->dev) : eventX + dev->name : input driver name (like touchsreen) + */ + handle->dev = input_get_device(dev); + handle->name = "sec_input_booster"; + handle->handler = handler; + + error = input_register_handle(handle); + if (error) + goto err2; + + error = input_open_device(handle); + if (error) + goto err1; + + return 0; +err1: + input_unregister_handle(handle); +err2: + kfree(handle); + return error; +} + +static void sec_input_boost_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +static const struct input_device_id sec_input_boost_ids[] = { + /* multi-touch touchscreen */ + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | + INPUT_DEVICE_ID_MATCH_ABSBIT, + .evbit = { BIT_MASK(EV_ABS) }, + .absbit = { [BIT_WORD(ABS_MT_POSITION_X)] = + BIT_MASK(ABS_MT_POSITION_X) | + BIT_MASK(ABS_MT_POSITION_Y) + }, + }, + /* touchpad */ + { + .flags = INPUT_DEVICE_ID_MATCH_KEYBIT | + INPUT_DEVICE_ID_MATCH_ABSBIT, + .keybit = { [BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH) }, + .absbit = { [BIT_WORD(ABS_X)] = + BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) + }, + }, + /* Keypad */ + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT, + .evbit = { BIT_MASK(EV_KEY) }, + }, + { }, +}; + +static struct input_handler ib_input_handler = { + .event = sec_input_boost_event, + .events = sec_input_boost_events, + .connect = sec_input_boost_connect, + .disconnect = sec_input_boost_disconnect, + .name = "sec_input_booster", + .id_table = sec_input_boost_ids, +}; + +static int init_input_handler(void) +{ + int i; + + ib_work_cnt = 0; + spin_lock_init(&ib_idx_lock); + + ib_evt_work = kmalloc(sizeof(struct ib_event_work) * MAX_IB_COUNT, GFP_KERNEL); + if (ib_evt_work != NULL) + for (i = 0; i < MAX_IB_COUNT; i++) + INIT_WORK(&(ib_evt_work[i].evdev_work), evdev_ib_trigger); + + ib_unbound_highwq + = alloc_ordered_workqueue("ib_unbound_highwq", WQ_HIGHPRI); + if (!ib_unbound_highwq) + return -EPERM; + + return input_register_handler(&ib_input_handler); +} +#endif + +int chk_next_data(const struct input_value *vals, int next_idx, int input_type) +{ + int ret_val = 0; + int next_type = -1; + int next_code = -1; + + next_type = vals[next_idx].type; + next_code = vals[next_idx].code; + + switch (input_type) { + case BTN_TOUCH: + if ((next_type == EV_ABS) && (next_code == ABS_PRESSURE)) + ret_val = 1; + break; + case EV_KEY: + ret_val = 1; + break; + default: + break; + } + + return ret_val; +} + +int chk_boost_on_off(const struct input_value *vals, int idx, int dev_type) +{ + int ret_val = -1; + + if (dev_type < 0) + return ret_val; + + /* In case of SPEN or HOVER, it must be empty multi event + * Before starting input booster. + */ + if (dev_type == SPEN || dev_type == HOVER) { + if (!evdev_mt_event[dev_type] && vals[idx].value) + ret_val = 1; + else if (evdev_mt_event[dev_type] && !vals[idx].value) + ret_val = 0; + } else if (dev_type == TOUCH || dev_type == MULTI_TOUCH) { + if (vals[idx].value >= 0) + ret_val = 1; + else + ret_val = 0; + } else if (vals[idx].value > 0) + ret_val = 1; + else if (vals[idx].value <= 0) + ret_val = 0; + + return ret_val; +} + +/* + * get_device_type : Define type of device for input_booster. + * device_type : Current device that in which input events triggered. + * keyId : Each device get given unique keyid using Type, Code, Slot values + * to identify which booster will be triggered. + * cur_idx : Pointing current handling index from input booster. + * cur_idx will be updated when cur_idx is same as head. + * vals : Event set to determine what booster would be triggered. + * evt_cnt : Total count of event in this time. + */ +int get_device_type(int *device_type, unsigned int *keyId, + int *cur_idx, const struct input_value *vals, int evt_cnt) +{ + int i; + int ret_val = -1; + int dev_type = NONE_TYPE_DEVICE; + int uniq_slot = 0; + int next_idx = 0 ; + int target_idx = 0; + + if (evt_cnt > MAX_EVENTS) { + dev_warn_ratelimited(evbst_dev, "Exceed max event number\n"); + return ret_val; + } + + /* Initializing device type before finding the proper device type. */ + *device_type = dev_type; + + for (i = *cur_idx; i < evt_cnt; i++) { + pr_booster(" %s Type : %d, Code : %d, Value : %d, idx : %d\n", + "Input Data || ", vals[i].type, + vals[i].code, vals[i].value, i); + + if (vals[i].type == EV_SYN || vals[i].code == SYN_REPORT) { + break; + } + + if (vals[i].type == EV_KEY) { + target_idx = i; + switch (vals[i].code) { + case BTN_TOUCH: + next_idx = i+1; + if (!chk_next_data(vals, next_idx, BTN_TOUCH)) + break; + dev_type = SPEN; + break; + case BTN_TOOL_PEN: + dev_type = HOVER; + break; + case KEY_BACK: + case KEY_HOMEPAGE: + dev_type = TOUCH_KEY; + break; + case KEY_VOLUMEUP: + case KEY_VOLUMEDOWN: + case KEY_POWER: +#if IS_ENABLED(CONFIG_SOC_S5E5515) // watch - lower key(back key) Code : 0x244(580) + case KEY_APPSELECT: +#endif + dev_type = KEY; + break; + default: + break; + } + + } else if (vals[i].type == EV_ABS) { + target_idx = i; + switch (vals[i].code) { + case ABS_MT_TRACKING_ID: + if (vals[i].value >= 0) { + evdev_mt_slot++; + } else { + evdev_mt_slot--; + if (evdev_mt_slot < 0) { + evdev_mt_slot = 0; + break; + } + } + + if (vals[i].value >= 0) { + if (evdev_mt_slot == 1) { + dev_type = TOUCH; + uniq_slot = 1; + } else if (evdev_mt_slot == 2) { + dev_type = MULTI_TOUCH; + uniq_slot = 2; + } + } else if (vals[i].value < 0) { + if (evdev_mt_slot == 0) { + dev_type = TOUCH; + uniq_slot = 1; + } else if (evdev_mt_slot == 1) { + dev_type = MULTI_TOUCH; + uniq_slot = 2; + } + } + + pr_booster("Touch Booster Trigger(%d), T(%d) C(%d) V(%d), Idx(%d), Cnt(%d)", + evdev_mt_slot, + vals[i].type, vals[i].code, vals[i].value, + i, evt_cnt); + + break; + } + } else if (vals[i].type == EV_MSC && + vals[i].code == MSC_SCAN) { + next_idx = i++; + if (!chk_next_data(vals, next_idx, EV_KEY)) + break; + + next_idx = i++; + target_idx = next_idx; + switch (vals[next_idx].code) { + case BTN_LEFT: /* Checking Touch Button Event */ + case BTN_RIGHT: + case BTN_MIDDLE: + dev_type = MOUSE; + //Remain the last of CODE value as a uniq_slot to recognize BTN Type (LEFT, RIGHT, MIDDLE) + uniq_slot = vals[next_idx].code; + break; + default: /* Checking Keyboard Event */ + dev_type = KEYBOARD; + uniq_slot = vals[next_idx].code; + + pr_booster("KBD Booster Trigger(%d), Type(%d), Code(%d), Val(%d), Idx(%d), Cnt(%d)\n", + vals[next_idx].code, + vals[i].type, vals[i].code, vals[i].value, + i, evt_cnt); + break; + } + } + + if (dev_type != NONE_TYPE_DEVICE ) { + *keyId = create_uniq_id(vals[i].type, vals[i].code, uniq_slot); + ret_val = chk_boost_on_off(vals, target_idx, dev_type); + pr_booster("Dev type Find(%d), KeyID(%d), enable(%d), Target(%d)\n", + dev_type, *keyId, ret_val, target_idx); + break; + } + + } + + *cur_idx = i+1; + + *device_type = dev_type; + return ret_val; +} + +// ********** Detect Events ********** // +//void input_booster(int head, int bufsize) +void input_booster(struct ib_event_data *ib_ev_data) +{ +#if IS_ENABLED(CONFIG_SEC_INPUT_BOOSTER_QC) || \ + IS_ENABLED(CONFIG_SEC_INPUT_BOOSTER_SLSI) || \ + IS_ENABLED(CONFIG_SEC_INPUT_BOOSTER_MTK) + int dev_type = 0; + int keyId = 0; + int cur_idx = -1; + + pr_booster("%s Triggered :: evt_cnt(%d), ib_init_succeed(%d)", + __func__, ib_ev_data->evt_cnt, ib_init_succeed); + + if (!ib_init_succeed || ib_ev_data->evt_cnt == 0) { + pr_err(ITAG"ev_cnt(%d) dev is Null OR dt_infor hasn't mem alloc\n", ib_ev_data->evt_cnt); + return; + } + + cur_idx = 0; + + while (cur_idx < ib_ev_data->evt_cnt) { + + keyId = 0; + + int enable = get_device_type(&dev_type, &keyId, &cur_idx, ib_ev_data->vals, ib_ev_data->evt_cnt); + if (enable < 0 || keyId == 0) { + continue; + } + + if (dev_type <= NONE_TYPE_DEVICE || dev_type >= MAX_DEVICE_TYPE_NUM) { + continue; + } + + if (enable == BOOSTER_ON) { + evdev_mt_event[dev_type]++; + } else { + evdev_mt_event[dev_type]--; + } + + pr_booster("Device_Type(%d), KeyId(%d), IB_Cnt(%d), enable(%d)", + dev_type, keyId, trigger_cnt, enable); + + ib_trigger[trigger_cnt].key_id = keyId; + ib_trigger[trigger_cnt].event_type = enable; + ib_trigger[trigger_cnt].dev_type = dev_type; + + queue_work(ev_unbound_wq, &(ib_trigger[trigger_cnt++].ib_trigger_work)); + trigger_cnt = (trigger_cnt == MAX_IB_COUNT) ? 0 : trigger_cnt; + } +#endif +} + +static int __init ev_boost_init(void) +{ + int err; + + pr_info(ITAG" Input Booster Module Init\n"); + input_booster_init(); + pr_info(ITAG" Input Booster Module Init End\n"); + spin_lock_init(&ib_ev_lock); + ib_notifier_register(&ib_event_notifier); + ev_unbound_wq = + alloc_ordered_workqueue("ev_unbound_wq", WQ_HIGHPRI); + + evbst_dev = kzalloc(sizeof(struct device), GFP_KERNEL); + dev_set_name(evbst_dev, "evdev_booster_dev"); + evbst_dev->release = NULL; + err = device_register(evbst_dev); + if (err) + pr_err(ITAG" evdev device register failed\n"); + +#if defined(CONFIG_SEC_INPUT_BOOSTER_HANDLER) + err = init_input_handler(); + if (err) + pr_err(ITAG" input handler fail\n"); +#endif + + return 0; +} + +static void __exit ev_boost_exit(void) +{ + ib_notifier_unregister(&ib_event_notifier); + device_unregister(evbst_dev); + kfree(evbst_dev); + input_booster_exit(); +} + +late_initcall(ev_boost_init); +module_exit(ev_boost_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("hongc.shim"); +MODULE_DESCRIPTION("EVDEV Booster in kernel"); +#endif //--CONFIG_SEC_INPUT_BOOSTER diff --git a/drivers/input/input_boost/input_booster.c b/drivers/input/input_boost/input_booster.c new file mode 100644 index 000000000000..0197d71a0a04 --- /dev/null +++ b/drivers/input/input_boost/input_booster.c @@ -0,0 +1,933 @@ +#define ITAG " [Input Booster] " +#include + +#include +#include +#include +#include +#include + +#if IS_ENABLED(CONFIG_SEC_INPUT_BOOSTER_QC) || \ + IS_ENABLED(CONFIG_SEC_INPUT_BOOSTER_SLSI) || \ + IS_ENABLED(CONFIG_SEC_INPUT_BOOSTER_MTK) +spinlock_t write_ib_lock; +spinlock_t write_qos_lock; +struct mutex trigger_ib_lock; +struct mutex mem_lock; +struct mutex rel_ib_lock; +struct mutex sip_rel_lock; +struct workqueue_struct *ib_handle_highwq; + +int total_ib_cnt; +int ib_init_succeed; +int u_ib_mode; +static int num_of_mode; + +int level_value = IB_MAX; + +unsigned int debug_flag; +unsigned int enable_event_booster = INIT_ZERO; + +// Input Booster Init Variables +int *release_val; +int *cpu_cluster_policy; +int *allowed_resources; + +int max_resource_count; +int device_count; +int max_cluster_count; +int allowed_res_count; +struct t_ib_device_tree* ib_device_trees; +struct t_ib_trigger* ib_trigger; + +struct list_head* ib_list; +struct list_head* qos_list; + +// @evdev_mt_slot : save the number of inputed touch slot. +int evdev_mt_slot; +// @evdev_mt_event[] : save count of each boooter's events. +int evdev_mt_event[MAX_DEVICE_TYPE_NUM]; +int trigger_cnt; +int send_ev_enable; + +struct t_ib_info* find_release_ib(int dev_type, int key_id); +struct t_ib_info* create_ib_instance(struct t_ib_trigger* p_IbTrigger, int uniqId); +bool is_validate_uniqid(unsigned int uniq_id); +struct t_ib_target* find_update_target(int uniq_id, int res_id); +long get_qos_value(int res_id); +void remove_ib_instance(struct t_ib_info* ib); + +void trigger_input_booster(struct work_struct *work) +{ + unsigned int uniq_id = 0; + int res_type = -1; + + struct t_ib_info* ib; + struct t_ib_trigger *p_IbTrigger = container_of(work, struct t_ib_trigger, ib_trigger_work); + + if (p_IbTrigger == NULL) { + return; + } + + mutex_lock(&trigger_ib_lock); + + // Input booster On/Off handling + if (p_IbTrigger->event_type == BOOSTER_ON) { + + if (find_release_ib(p_IbTrigger->dev_type, p_IbTrigger->key_id) != NULL) { + pr_err(ITAG" IB Trigger :: ib already exist. Key(%d)", p_IbTrigger->key_id); + mutex_unlock(&trigger_ib_lock); + return; + } + + // Check if uniqId exits. + do { + uniq_id = total_ib_cnt++; + + if (total_ib_cnt == MAX_IB_COUNT) + total_ib_cnt = 0; + + } while (!is_validate_uniqid(uniq_id)); + + // Make ib instance with all needed factor. + ib = create_ib_instance(p_IbTrigger, uniq_id); + + pr_info(ITAG" IB Trigger Press :: IB Uniq Id(%d)", uniq_id); + + if (ib == NULL) { + mutex_unlock(&trigger_ib_lock); + pr_err(ITAG" Creating ib object fail"); + return; + } + + ib->press_flag = FLAG_ON; + + // When create ib instance, insert resource info in qos list with value 0. + for (res_type = 0; res_type < allowed_res_count; res_type++) { + if (allowed_resources[res_type] > max_resource_count) { + pr_err(ITAG" allow res num(%d) exceeds over max res count", + allowed_resources[res_type]); + continue; + } + if (ib != NULL && + ib->ib_dt->res[allowed_resources[res_type]].head_value != 0) { + struct t_ib_target* tv; + tv = kmalloc(sizeof(struct t_ib_target), GFP_KERNEL); + + if (tv == NULL) + continue; + + tv->uniq_id = ib->uniq_id; + tv->value = 0; + + spin_lock(&write_qos_lock); + list_add_tail_rcu(&(tv->list), &qos_list[allowed_resources[res_type]]); + spin_unlock(&write_qos_lock); + } + } + queue_work(ib_handle_highwq, &(ib->ib_state_work[IB_HEAD])); + + } else { + /* Find ib instance in the list. if not, ignore this event. + * if exists, Release flag on. Call ib's Release func. + */ + + mutex_lock(&sip_rel_lock); + ib = find_release_ib(p_IbTrigger->dev_type, p_IbTrigger->key_id); + + mutex_lock(&mem_lock); + if (ib == NULL) { + pr_err(ITAG" IB is null on release"); + mutex_unlock(&mem_lock); + mutex_unlock(&sip_rel_lock); + mutex_unlock(&trigger_ib_lock); + return; + } + mutex_unlock(&mem_lock); + + mutex_lock(&ib->lock); + pr_info(ITAG" IB Trigger Release :: Uniq ID(%d)", ib->uniq_id); + ib->rel_flag = FLAG_ON; + if(ib->ib_dt->tail_time == 0) { + pr_booster(" IB tail time is 0"); + mutex_unlock(&ib->lock); + mutex_unlock(&sip_rel_lock); + mutex_unlock(&trigger_ib_lock); + return; + } + + // If head operation is already finished, tail timeout work will be triggered. + if (ib->isHeadFinished) { + if (!delayed_work_pending(&(ib->ib_timeout_work[IB_TAIL]))) { + queue_delayed_work(ib_handle_highwq, + &(ib->ib_timeout_work[IB_TAIL]), + msecs_to_jiffies(ib->ib_dt->tail_time)); + } else { + cancel_delayed_work(&(ib->ib_timeout_work[IB_TAIL])); + queue_delayed_work(ib_handle_highwq, + &(ib->ib_timeout_work[IB_TAIL]), + msecs_to_jiffies(ib->ib_dt->tail_time)); + } + } + mutex_unlock(&ib->lock); + mutex_unlock(&sip_rel_lock); + } + mutex_unlock(&trigger_ib_lock); + +} + +struct t_ib_info* create_ib_instance(struct t_ib_trigger* p_IbTrigger, int uniqId) +{ + struct t_ib_info* ib = kmalloc(sizeof(struct t_ib_info), GFP_KERNEL); + int dev_type = p_IbTrigger->dev_type; + + if (ib == NULL) + return NULL; + + ib->key_id = p_IbTrigger->key_id; + ib->uniq_id = uniqId; + ib->press_flag = FLAG_OFF; + ib->rel_flag = FLAG_OFF; + ib->isHeadFinished = 0; + + ib->ib_dt = &ib_device_trees[dev_type]; + + INIT_WORK(&ib->ib_state_work[IB_HEAD], press_state_func); + INIT_DELAYED_WORK(&ib->ib_timeout_work[IB_HEAD], press_timeout_func); + INIT_WORK(&ib->ib_state_work[IB_TAIL], release_state_func); + INIT_DELAYED_WORK(&ib->ib_timeout_work[IB_TAIL], release_timeout_func); + mutex_init(&ib->lock); + + spin_lock(&write_ib_lock); + list_add_tail_rcu(&(ib->list), &ib_list[dev_type]); + spin_unlock(&write_ib_lock); + + return ib; +} + +bool is_validate_uniqid(unsigned int uniq_id) +{ + int dev_type; + int cnt = 0; + struct t_ib_info* ib = NULL; + rcu_read_lock(); + + for (dev_type = 0; dev_type < device_count; dev_type++) { + if (list_empty(&ib_list[dev_type])) { + pr_booster("IB List(%d) Empty", dev_type); + continue; + } + list_for_each_entry_rcu(ib, &ib_list[dev_type], list) { + cnt++; + if (ib != NULL && ib->uniq_id == uniq_id) { + rcu_read_unlock(); + pr_booster("uniq id find :: IB Idx(%d) old(%d) new(%d)", cnt, ib->uniq_id, uniq_id); + return false; + } + } + cnt = 0; + } + + rcu_read_unlock(); + return true; +} + +struct t_ib_info* find_release_ib(int dev_type, int key_id) +{ + struct t_ib_info* ib = NULL; + rcu_read_lock(); + if (list_empty(&ib_list[dev_type])) { + rcu_read_unlock(); + pr_booster("Release IB(%d) Not Exist & List Empty", key_id); + return NULL; + } + + list_for_each_entry_rcu(ib, &ib_list[dev_type], list) { + if (ib != NULL && ib->key_id == key_id && ib->rel_flag == FLAG_OFF) { + rcu_read_unlock(); + pr_booster("Release IB(%d) Found", key_id); + return ib; + } + } + + rcu_read_unlock(); + pr_booster("Release IB(%d) Not Exist", key_id); + return NULL; + +} + +void press_state_func(struct work_struct* work) +{ + + struct t_ib_res_info res; + struct t_ib_target* tv; + long qos_values[MAX_RES_COUNT] = {0, }; + int res_type = 0; + + struct t_ib_info* target_ib = container_of(work, struct t_ib_info, ib_state_work[IB_HEAD]); + + pr_info(ITAG" Press State Func :::: Unique_Id(%d)", target_ib->uniq_id); + + // Get_Res_List(head) and update head value. + for (res_type = 0; res_type < allowed_res_count; res_type++) { + res = target_ib->ib_dt->res[allowed_resources[res_type]]; + if (res.head_value == 0) + continue; + + // Find already added target value instance and update value as a head. + tv = find_update_target(target_ib->uniq_id, res.res_id); + + if (tv == NULL) { + pr_err(ITAG"Press State Func :::: %d's tv(%d) is null T.T", + target_ib->uniq_id, res.res_id); + continue; + } + + tv->value = res.head_value; + pr_booster("Press State Func :::: Uniq(%d)'s Update Res(%d) Head Val(%d)", + tv->uniq_id, res.res_id, res.head_value); + + qos_values[res.res_id] = get_qos_value(res.res_id); + + } + + ib_set_booster(qos_values); + pr_booster("Press State Func :::: Press Delay Time(%lu)", + msecs_to_jiffies(target_ib->ib_dt->head_time)); + + queue_delayed_work(ib_handle_highwq, &(target_ib->ib_timeout_work[IB_HEAD]), + msecs_to_jiffies(target_ib->ib_dt->head_time)); +} + +void press_timeout_func(struct work_struct* work) +{ + + struct t_ib_info* target_ib = container_of(work, struct t_ib_info, ib_timeout_work[IB_HEAD].work); + + if (!target_ib) + return; + + pr_info(ITAG" Press Timeout Func :::: Unique_Id(%d) Tail_Time(%d)", + target_ib->uniq_id, target_ib->ib_dt->tail_time); + + int res_type; + struct t_ib_res_info res; + struct t_ib_target* tv; + long qos_values[MAX_RES_COUNT] = {0, }; + long rel_flags[MAX_RES_COUNT] = {0, }; + + mutex_lock(&sip_rel_lock); + if (target_ib->ib_dt->tail_time != 0) { + mutex_lock(&target_ib->lock); + queue_work(ib_handle_highwq, &(target_ib->ib_state_work[IB_TAIL])); + mutex_unlock(&target_ib->lock); + } else { + //NO TAIL Scenario : Delete Ib instance and free all memory space. + for (res_type = 0; res_type < allowed_res_count; res_type++) { + res = target_ib->ib_dt->res[allowed_resources[res_type]]; + + tv = find_update_target(target_ib->uniq_id, res.res_id); + if (tv == NULL) { + pr_err(ITAG" Press Timeout Func :::: %d's TV No Exist(%d)", + target_ib->uniq_id, res.res_id); + continue; + } + + spin_lock(&write_qos_lock); + list_del_rcu(&(tv->list)); + spin_unlock(&write_qos_lock); + synchronize_rcu(); + kfree(tv); + tv = NULL; + + rcu_read_lock(); + if (!list_empty(&qos_list[res.res_id])) { + rcu_read_unlock(); + qos_values[res.res_id] = get_qos_value(res.res_id); + pr_booster("Press Timeout ::: Remove Val Cuz No Tail ::: Uniq(%d) Res(%d) Qos Val(%ld)", + target_ib->uniq_id, res.res_id, qos_values[res.res_id]); + } + else { + rcu_read_unlock(); + rel_flags[res.res_id] = 1; + pr_booster("Press Timeout ::: Uniq(%d) Release Booster(%d) ::: No Tail and List Empty", + target_ib->uniq_id, res.res_id); + + } + } + + mutex_lock(&rel_ib_lock); + ib_release_booster(rel_flags); + ib_set_booster(qos_values); + mutex_unlock(&rel_ib_lock); + + remove_ib_instance(target_ib); + } + mutex_unlock(&sip_rel_lock); + +} + +void release_state_func(struct work_struct* work) +{ + + long qos_values[MAX_RES_COUNT] = {0, }; + int res_type = 0; + struct t_ib_target* tv; + struct t_ib_res_info res; + + struct t_ib_info* target_ib = container_of(work, struct t_ib_info, ib_state_work[IB_TAIL]); + + if (target_ib == NULL) + return; + + mutex_lock(&target_ib->lock); + + target_ib->isHeadFinished = 1; + + pr_info(ITAG" Release State Func :::: Unique_Id(%d) Rel_Flag(%d)", + target_ib->uniq_id, target_ib->rel_flag); + + for (res_type = 0; res_type < allowed_res_count; res_type++) { + res = target_ib->ib_dt->res[allowed_resources[res_type]]; + if (res.tail_value == 0) + continue; + + tv = find_update_target(target_ib->uniq_id, res.res_id); + if (tv == NULL) + continue; + + spin_lock(&write_qos_lock); + tv->value = res.tail_value; + spin_unlock(&write_qos_lock); + + qos_values[res.res_id] = get_qos_value(res.res_id); + pr_booster("Release State Func :::: Uniq(%d)'s Update Tail Val (%ld), Qos_Val(%ld)", + tv->uniq_id, tv->value, qos_values[res.res_id]); + } + + ib_set_booster(qos_values); + + // If release event already triggered, tail delay work will be triggered after relese state func. + if (target_ib->rel_flag == FLAG_ON) { + if (!delayed_work_pending(&(target_ib->ib_timeout_work[IB_TAIL]))) { + queue_delayed_work(ib_handle_highwq, + &(target_ib->ib_timeout_work[IB_TAIL]), + msecs_to_jiffies(target_ib->ib_dt->tail_time)); + } else { + pr_err(ITAG" Release State Func :: tail timeout start"); + } + } else { + queue_delayed_work(ib_handle_highwq, + &(target_ib->ib_timeout_work[IB_TAIL]), + msecs_to_jiffies(60000)); + } + + mutex_unlock(&target_ib->lock); +} + +void release_timeout_func(struct work_struct* work) +{ + long qos_values[MAX_RES_COUNT] = {0, }; + long rel_flags[MAX_RES_COUNT] = {0, }; + struct t_ib_target* tv; + struct t_ib_res_info res; + int res_type; + + struct t_ib_info* target_ib = container_of(work, struct t_ib_info, ib_timeout_work[IB_TAIL].work); + if(!target_ib) + return; + + pr_info(ITAG" Release Timeout Func :::: Unique_Id(%d)", target_ib->uniq_id); + mutex_lock(&sip_rel_lock); + for (res_type = 0; res_type < allowed_res_count; res_type++) { + res = target_ib->ib_dt->res[allowed_resources[res_type]]; + + tv = find_update_target(target_ib->uniq_id, res.res_id); + if (tv == NULL) { + pr_err(ITAG" Release Timeout Func :::: %d's TV No Exist(%d)", + target_ib->uniq_id, res.res_id); + continue; + } + + pr_booster("Release Timeout Func :::: Delete Uniq(%d)'s TV Val (%ld)", + tv->uniq_id, tv->value); + + spin_lock(&write_qos_lock); + list_del_rcu(&(tv->list)); + spin_unlock(&write_qos_lock); + synchronize_rcu(); + kfree(tv); + tv = NULL; + + rcu_read_lock(); + if (!list_empty(&qos_list[res.res_id])) { + rcu_read_unlock(); + qos_values[res.res_id] = get_qos_value(res.res_id); + pr_booster("Release Timeout Func ::: Uniq(%d) Res(%d) Qos Val(%ld)", + target_ib->uniq_id, res.res_id, qos_values[res.res_id]); + } + else { + rcu_read_unlock(); + rel_flags[res.res_id] = 1; + pr_booster("Release Timeout ::: Release Booster(%d's %d) ::: List Empty", + target_ib->uniq_id, res.res_id); + + } + } + mutex_lock(&rel_ib_lock); + ib_release_booster(rel_flags); + ib_set_booster(qos_values); + mutex_unlock(&rel_ib_lock); + + remove_ib_instance(target_ib); + mutex_unlock(&sip_rel_lock); + +} + +struct t_ib_target* find_update_target(int uniq_id, int res_id) +{ + struct t_ib_target* tv; + + rcu_read_lock(); + list_for_each_entry_rcu(tv, &qos_list[res_id], list) { + if (tv->uniq_id == uniq_id) { + rcu_read_unlock(); + return tv; + } + } + rcu_read_unlock(); + + return NULL; +} + +long get_qos_value(int res_id) +{ + //Find tv instance that has max value in the qos_list that has the passed res_id. + struct t_ib_target* tv; + long ret_val = 0; + + rcu_read_lock(); + + if (list_empty(&qos_list[res_id])) { + rcu_read_unlock(); + return 0; + } + + list_for_each_entry_rcu(tv, &qos_list[res_id], list) { + if (tv->value > ret_val) + ret_val = tv->value; + } + rcu_read_unlock(); + + return ret_val; +} + +void remove_ib_instance(struct t_ib_info* target_ib) +{ + struct t_ib_info* ib = NULL; + int ib_exist = 0; + + //Check if target instance exists in the list or not. + spin_lock(&write_ib_lock); + list_for_each_entry_rcu(ib, &ib_list[target_ib->ib_dt->type], list) { + if (ib != NULL && ib == target_ib) { + ib_exist = 1; + break; + } + } + + if (!ib_exist) { + spin_unlock(&write_ib_lock); + pr_err(ITAG" Del Ib Fail Id : %d", target_ib->uniq_id); + } else { + list_del_rcu(&(target_ib->list)); + spin_unlock(&write_ib_lock); + synchronize_rcu(); + pr_info(ITAG" Del Ib Instance's Id : %d", target_ib->uniq_id); + mutex_lock(&mem_lock); + if (target_ib != NULL) { + kfree(target_ib); + target_ib = NULL; + } + mutex_unlock(&mem_lock); + } +} + +unsigned int create_uniq_id(int type, int code, int slot) +{ + //id1 | (id2 << num_bits_id1) | (id3 << (num_bits_id2 + num_bits_id1)) + pr_booster("Create Key Id -> type(%d), code(%d), slot(%d)", type, code, slot); + return (type << (TYPE_BITS + CODE_BITS)) | (code << CODE_BITS) | slot; +} + +void ib_auto_test(int type, int code, int val) +{ + send_ev_enable = 1; +} + +//+++++++++++++++++++++++++++++++++++++++++++++++ STRUCT & VARIABLE FOR SYSFS +++++++++++++++++++++++++++++++++++++++++++++++// +SYSFS_CLASS(enable_event, (buf, "%u\n", enable_event), 1) +SYSFS_CLASS(debug_level, (buf, "%u\n", debug_level), 1) +SYSFS_CLASS(sendevent, (buf, "%d\n", sendevent), 3) +HEAD_TAIL_SYSFS_DEVICE(head) +HEAD_TAIL_SYSFS_DEVICE(tail) +LEVEL_SYSFS_DEVICE(level) + +struct attribute* dvfs_attributes[] = { + &dev_attr_head.attr, + &dev_attr_tail.attr, + &dev_attr_level.attr, + NULL, +}; + +struct attribute_group dvfs_attr_group = { + .attrs = dvfs_attributes, +}; + +void init_sysfs_device(struct class* sysfs_class, struct t_ib_device_tree* ib_dt) { + struct device* sysfs_dev; + int ret = 0; + int bus_ret = 0; + sysfs_dev = device_create(sysfs_class, NULL, 0, ib_dt, "%s", ib_dt->label); + if (IS_ERR(sysfs_dev)) { + ret = IS_ERR(sysfs_dev); + pr_err(ITAG" Failed to create %s sysfs device[%d]n", ib_dt->label, ret); + return; + } + ret = sysfs_create_group(&sysfs_dev->kobj, &dvfs_attr_group); + if (ret) { + pr_err(ITAG" Failed to create %s sysfs groupn", ib_dt->label); + return; + } +} + +int parse_dtsi_str(struct device_node *np, const char *target_node, void *target_arr, int isIntType) +{ + char prop_str[100]; + size_t prop_size = 0; + char *prop_pointer = NULL; + const char *token = NULL; + int iter = 0; + int *int_target_arr_ptr; + char *str_target_arr_ptr; + int copy_result; + const char *full_str = of_get_property(np, target_node, NULL); + + if (full_str == NULL) + return -1; + + if (isIntType) + int_target_arr_ptr = (int *)target_arr; + else + str_target_arr_ptr = (char *)target_arr; + + prop_size = strlcpy(prop_str, full_str, sizeof(char)*100); + prop_pointer = prop_str; + token = strsep(&prop_pointer, ","); + + while (token != NULL) { + pr_booster("%s %d's Type Value(%s)", target_node, iter, token); + + //Release Values inserted inside array + if (isIntType) { + copy_result = sscanf(token, "%d", &int_target_arr_ptr[iter]); + if (!copy_result) { + pr_err(ITAG"DTSI string value parsing fail"); + return -1; + } + pr_booster("Target_arr[%d] : %d", iter, int_target_arr_ptr[iter]); + } else { + copy_result = sscanf(token, "%s", &str_target_arr_ptr[iter]); + if (!copy_result) { + pr_err(ITAG"DTSI string value parsing fail"); + return -1; + } + } + + token = strsep(&prop_pointer, ","); + iter++; + } + + return iter; +} + +int is_ib_init_succeed(void) +{ + return (ib_trigger != NULL && ib_device_trees != NULL && + ib_list != NULL && qos_list != NULL) ? 1 : 0; +} + +void input_booster_exit(void) +{ + + kfree(ib_trigger); + kfree(ib_device_trees); + kfree(ib_list); + kfree(qos_list); + kfree(cpu_cluster_policy); + kfree(allowed_resources); + kfree(release_val); + + input_booster_exit_vendor(); + +} + +// ********** Init Booster ********** // + +void input_booster_init(void) +{ + // ********** Load Frequency data from DTSI ********** + struct device_node* np; + int i; + + int ib_dt_size = sizeof(struct t_ib_device_tree); + int ib_res_size = sizeof(struct t_ib_res_info); + int list_head_size = sizeof(struct list_head); + int ddr_info_size = sizeof(struct t_ddr_info); + + int ndevice_in_dt = 0; + int res_cnt; + int result; + + total_ib_cnt = 0; + ib_init_succeed = 0; + debug_flag = 0; + enable_event_booster = INIT_ZERO; + max_resource_count = 0; + allowed_res_count = 0; + device_count = 0; + evdev_mt_slot = 0; + trigger_cnt = 0; + send_ev_enable = 0; + + spin_lock_init(&write_ib_lock); + spin_lock_init(&write_qos_lock); + mutex_init(&trigger_ib_lock); + mutex_init(&sip_rel_lock); + mutex_init(&rel_ib_lock); + mutex_init(&mem_lock); + +//Input Booster Trigger Strcut Init + ib_trigger = kzalloc(sizeof(struct t_ib_trigger) * MAX_IB_COUNT, GFP_KERNEL); + if (ib_trigger == NULL) { + pr_err(ITAG" ib_trigger mem alloc fail"); + goto out; + } + + for (i = 0; i < MAX_IB_COUNT; i++) + INIT_WORK(&(ib_trigger[i].ib_trigger_work), trigger_input_booster); + + np = of_find_compatible_node(NULL, NULL, "input_booster"); + if (np == NULL) { + goto out; + } + +// Geting the count of devices. + ndevice_in_dt = of_get_child_count(np); + pr_info(ITAG" %s ndevice_in_dt : %d\n", __func__, ndevice_in_dt); + + ib_device_trees = kzalloc(ib_dt_size * ndevice_in_dt, GFP_KERNEL); + if (ib_device_trees == NULL) { + pr_err(ITAG" dt_infor mem alloc fail"); + goto out; + } + +// ib list mem alloc + ib_list = kzalloc(list_head_size * ndevice_in_dt, GFP_KERNEL); + if (ib_list == NULL) { + pr_err(ITAG" ib list mem alloc fail"); + goto out; + } + +// Get Needed Information from dtsi + result = sscanf((of_get_property(np, "max_resource_count", + NULL)), "%d", &max_resource_count); + if (!result) { + pr_err(ITAG"max_resource_count value parsing fail"); + goto out; + } + + result = sscanf((of_get_property(np, "max_cluster_count", + NULL)), "%d", &max_cluster_count); + if (!result) { + pr_err(ITAG"max_cluster_count value parsing fail"); + goto out; + } + + pr_info(ITAG" resource size : %d, cluster count : %d", + max_resource_count, max_cluster_count); + +//qos list mem alloc + qos_list = kzalloc(list_head_size * max_resource_count, GFP_KERNEL); + if (qos_list == NULL) { + pr_err(ITAG" ib list mem alloc fail"); + goto out; + } + for (res_cnt = 0; res_cnt < max_resource_count; res_cnt++) + INIT_LIST_HEAD(&qos_list[res_cnt]); + +//Init Cpu Cluster Value + cpu_cluster_policy = kzalloc(sizeof(int) * max_cluster_count, GFP_KERNEL); + if (cpu_cluster_policy == NULL) { + pr_err(ITAG" cpu_cluster_policy mem alloc fail"); + goto out; + } + result = parse_dtsi_str(np, "cpu_cluster_policy", cpu_cluster_policy, 1); + pr_info(ITAG" Init:: Total Cpu Cluster Count : %d", result); + if (result < 0) + goto out; + + if (result < max_cluster_count) { + for (i = result; i < max_cluster_count; i++) + cpu_cluster_policy[i] = -1; + } + +//Allow Resource + allowed_resources = kzalloc(sizeof(int) * max_resource_count, GFP_KERNEL); + if (allowed_resources == NULL) { + pr_err(ITAG" allowed_resources mem alloc fail"); + goto out; + } + result = parse_dtsi_str(np, "allowed_resources", allowed_resources, 1); + pr_info(ITAG" Init:: Total Allow Resource Count: %d", result); + allowed_res_count = result; + if (result < 0) + goto out; + + for (i = 0; i < result; i++) { + if (allowed_resources[i] >= max_resource_count) { + pr_err(ITAG" allow res index(%d) exceeds over max res count", + allowed_resources[i]); + goto out; + } + } + + if (result > max_resource_count) { + pr_err(ITAG" allow resources exceed over max resource count"); + goto out; + } + +//Init Resource Release Values + release_val = kzalloc(sizeof(int) * max_resource_count, GFP_KERNEL); + if (release_val == NULL) { + pr_err(ITAG" release_val mem alloc fail"); + goto out; + } + result = parse_dtsi_str(np, "ib_release_values", release_val, 1); + pr_info(ITAG" Init:: Total Release Value Count: %d", result); + if (result < 0) + goto out; + + if (result > max_resource_count) { + pr_err(ITAG" release value parse fail :: overceed max value"); + goto out; + } + + struct device_node* cnp; + + for_each_child_of_node(np, cnp) { + /************************************************/ + // fill all needed data into res_info instance that is in dt instance. + struct t_ib_device_tree* ib_dt = (ib_device_trees + device_count); + struct device_node* child_resource_node; + struct device_node* resource_node = of_find_compatible_node(cnp, NULL, "resource"); + + ib_dt->res = kzalloc(ib_res_size * max_resource_count, GFP_KERNEL); + for (i = 0; i < max_resource_count; ++i){ + ib_dt->res[i].res_id = -1; + ib_dt->res[i].label = 0; + ib_dt->res[i].head_value = 0; + ib_dt->res[i].tail_value = 0; + } + + int resource_node_index = 0; + int res_type = 0; + for_each_child_of_node(resource_node, child_resource_node) { + // allowed_resources[resource_node_index] is same as Resource's ID. + ib_dt->res[allowed_resources[resource_node_index]].res_id = allowed_resources[resource_node_index]; + ib_dt->res[allowed_resources[resource_node_index]].label = of_get_property(child_resource_node, "resource,label", NULL); + + int inputbooster_size = 0; + + const u32* is_exist_inputbooster_size = of_get_property(child_resource_node, "resource,value", &inputbooster_size); + + if (is_exist_inputbooster_size && inputbooster_size) { + inputbooster_size = inputbooster_size / sizeof(u32); + } + + if (inputbooster_size != 2) { + pr_err(ITAG" inputbooster size must be 2!"); + return; // error + } + + for (res_type = 0; res_type < inputbooster_size; ++res_type) { + if (res_type == IB_HEAD) { + of_property_read_u32_index(child_resource_node, "resource,value", + res_type, &ib_dt->res[allowed_resources[resource_node_index]].head_value); + } + else if (res_type == IB_TAIL) { + of_property_read_u32_index(child_resource_node, "resource,value", + res_type, &ib_dt->res[allowed_resources[resource_node_index]].tail_value); + } + } + + resource_node_index++; + } + + ib_dt->label = of_get_property(cnp, "input_booster,label", NULL); + pr_info(ITAG" %s ib_dt->label : %s\n", __func__, ib_dt->label); + + if (of_property_read_u32(cnp, "input_booster,type", &ib_dt->type)) { + pr_err(ITAG" Failed to get type property\n"); + break; + } + if (of_property_read_u32(cnp, "input_booster,head_time", &ib_dt->head_time)) { + pr_err(ITAG" Fail Get Head Time\n"); + break; + } + if (of_property_read_u32(cnp, "input_booster,tail_time", &ib_dt->tail_time)) { + pr_err(ITAG" Fail Get Tail Time\n"); + break; + } + + //Init all type of ib list. + INIT_LIST_HEAD(&ib_list[device_count]); + + device_count++; + } + + ib_init_succeed = is_ib_init_succeed(); + pr_info(ITAG" Total Input Device Count(%d), IsSuccess(%d)", device_count, ib_init_succeed); + ib_init_succeed = input_booster_init_vendor(); + + if (ib_init_succeed) + ib_handle_highwq = alloc_workqueue("ib_unbound_high_wq", WQ_UNBOUND | WQ_HIGHPRI, + MAX_IB_COUNT); + +out: + // ********** Initialize Sysfs ********** + { + struct class* sysfs_class; + int ret; + int ib_type; + sysfs_class = class_create(THIS_MODULE, "input_booster"); + + if (IS_ERR(sysfs_class)) { + pr_err(ITAG" Failed to create class\n"); + return; + } + if (ib_init_succeed) { + INIT_SYSFS_CLASS(enable_event) + INIT_SYSFS_CLASS(debug_level) + INIT_SYSFS_CLASS(sendevent) + + for (ib_type = 0; ib_type < ndevice_in_dt; ib_type++) { + init_sysfs_device(sysfs_class, &ib_device_trees[ib_type]); + } + } + } +} + +#endif //CONFIG_SEC_INPUT_BOOSTER_QC || CONFIG_SEC_INPUT_BOOSTER_SLSI || CONFIG_SEC_INPUT_BOOSTER_MTK + +MODULE_LICENSE("GPL"); diff --git a/drivers/input/input_boost/input_booster_lsi.c b/drivers/input/input_boost/input_booster_lsi.c new file mode 100644 index 000000000000..54f6455c680d --- /dev/null +++ b/drivers/input/input_boost/input_booster_lsi.c @@ -0,0 +1,251 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +static struct emstune_mode_request emstune_req_input; +struct freq_qos_request *cpu_cluster_qos; +struct exynos_pm_qos_request mif_qos; +struct exynos_pm_qos_request int_qos; +static bool cluster_qos_init_success; + +static struct ucc_req ucc_req = +{ + .name = "input_booster", +}; + +static DEFINE_MUTEX(input_lock); +bool current_hmp_boost = INIT_ZERO; +bool current_ucc_boost = INIT_ZERO; + +int freq_qos_init(void); + +void set_ib_ucc(int ucc_value) +{ + mutex_lock(&input_lock); + + if (ucc_value != current_ucc_boost) { + pr_booster("[Input Booster2] ****** set_ib_ucc : %d ( %s )\n", ucc_value, __FUNCTION__); + if (ucc_value) { + ucc_add_request(&ucc_req, ucc_value); + } else { + ucc_remove_request(&ucc_req); + } + current_ucc_boost = ucc_value; + } + + mutex_unlock(&input_lock); +} + +void set_ib_hmp(int hmp_value) +{ + mutex_lock(&input_lock); + + if (hmp_value != current_hmp_boost) { + pr_booster("[Input Booster2] ****** set_ib_hmp : %d ( %s )\n", hmp_value, __FUNCTION__); + emstune_update_request(&emstune_req_input, hmp_value); + current_hmp_boost = hmp_value; + } + + mutex_unlock(&input_lock); +} + +void ib_set_booster(long* qos_values) +{ + int res_type = 0; + int cur_res_idx; + long value = -1; + + for (res_type = 0; res_type < allowed_res_count; res_type++) { + + cur_res_idx = allowed_resources[res_type]; + value = qos_values[cur_res_idx]; + + if (value <= 0) + continue; + + switch (cur_res_idx) { + case CLUSTER2: + case CLUSTER1: + case CLUSTER0: + mutex_lock(&input_lock); + + if (cluster_qos_init_success) { + freq_qos_update_request(&cpu_cluster_qos[cur_res_idx], + value); + } else { + freq_qos_init(); + } + + mutex_unlock(&input_lock); + break; + case MIF: + exynos_pm_qos_update_request(&mif_qos, value); + break; + case INT: + exynos_pm_qos_update_request(&int_qos, value); + break; + case HMPBOOST: + set_ib_hmp(value); + break; + case UCC: + set_ib_ucc(value); + break; + default: + break; + } + } +} + +void ib_release_booster(long *rel_flags) +{ + int res_type = 0; + int cur_res_idx; + long flag = -1; + + for (res_type = 0; res_type < allowed_res_count; res_type++) { + + cur_res_idx = allowed_resources[res_type]; + flag = rel_flags[cur_res_idx]; + + if (flag <= 0) + continue; + + switch (cur_res_idx) { + case CLUSTER2: + case CLUSTER1: + case CLUSTER0: + if (cpu_cluster_policy[cur_res_idx] != -1) + freq_qos_update_request(&cpu_cluster_qos[cur_res_idx], release_val[cur_res_idx]); + break; + case MIF: + exynos_pm_qos_update_request(&mif_qos, release_val[MIF]); + break; + case INT: + exynos_pm_qos_update_request(&int_qos, release_val[INT]); + break; + case HMPBOOST: + set_ib_hmp(release_val[HMPBOOST]); + break; + case UCC: + set_ib_ucc(release_val[UCC]); + break; + default: + break; + } + } +} + +int freq_qos_init(void) +{ + int cpu_cluster; + int ret; + int fail_cluster; + + struct cpufreq_policy *policy; + struct freq_qos_request *req; + + for (cpu_cluster = 0; cpu_cluster < max_cluster_count; cpu_cluster++) { + if (cpu_cluster_policy[cpu_cluster] == -1) + continue; + + policy = cpufreq_cpu_get(cpu_cluster_policy[cpu_cluster]); + if (!policy) { + pr_err("%s: Failed to get cpufreq policy for cluster(%d)\n", + __func__, cpu_cluster_policy[cpu_cluster]); + fail_cluster = cpu_cluster; + ret = -EAGAIN; + goto reset_qos; + } + ret = freq_qos_add_request(&policy->constraints, + &cpu_cluster_qos[cpu_cluster], FREQ_QOS_MIN, policy->min); + + if (ret < 0) { + pr_err("%s: Failed to add qos constraint (%d)\n", + __func__, ret); + fail_cluster = cpu_cluster; + goto reset_qos; + } + } + cluster_qos_init_success = true; + return 0; + +reset_qos: + for (cpu_cluster = fail_cluster - 1; cpu_cluster >= 0; cpu_cluster--) { + if (cpu_cluster_policy[cpu_cluster] == -1) + freq_qos_remove_request(&cpu_cluster_qos[cpu_cluster]); + } + return ret; +} + +int input_booster_init_vendor(void) +{ + + int res_type = 0; + + cpu_cluster_qos = kcalloc(ABS_CNT, sizeof(struct freq_qos_request) * max_cluster_count, GFP_KERNEL); + if (cpu_cluster_qos == NULL) + return 0; + + for (res_type = 0; res_type < allowed_res_count; res_type++) { + switch (allowed_resources[res_type]) { + case MIF: + exynos_pm_qos_add_request(&mif_qos, + PM_QOS_BUS_THROUGHPUT, PM_QOS_BUS_THROUGHPUT_DEFAULT_VALUE); + break; + case INT: + exynos_pm_qos_add_request(&int_qos, + PM_QOS_DEVICE_THROUGHPUT, PM_QOS_DEVICE_THROUGHPUT_DEFAULT_VALUE); + break; + case HMPBOOST: + emstune_add_request(&emstune_req_input); + break; + case UCC: + ucc_add_request(&ucc_req, DEFAULT_LEVEL); + break; + default: + break; + } + } + + return 1; +} + +void input_booster_exit_vendor() +{ + int res_type = 0; + + for (res_type = 0; res_type < allowed_res_count; res_type++) { + switch (allowed_resources[res_type]) { + case CLUSTER2: + if (cpu_cluster_policy[CLUSTER2] != -1) + freq_qos_remove_request(&cpu_cluster_qos[CLUSTER2]); + break; + case CLUSTER1: + if (cpu_cluster_policy[CLUSTER1] != -1) + freq_qos_remove_request(&cpu_cluster_qos[CLUSTER1]); + break; + case CLUSTER0: + if (cpu_cluster_policy[CLUSTER0] != -1) + freq_qos_remove_request(&cpu_cluster_qos[CLUSTER0]); + break; + case MIF: + exynos_pm_qos_remove_request(&mif_qos); + break; + case INT: + exynos_pm_qos_remove_request(&int_qos); + break; + case HMPBOOST: + break; + case UCC: + break; + default: + break; + } + } + kfree(cpu_cluster_qos); +} diff --git a/drivers/input/input_boost/input_booster_mode.c b/drivers/input/input_boost/input_booster_mode.c new file mode 100644 index 000000000000..24099e99d392 --- /dev/null +++ b/drivers/input/input_boost/input_booster_mode.c @@ -0,0 +1,1023 @@ +#define ITAG " [Input Booster] " +#include + +#include +#include +#include +#include +#include + +#if IS_ENABLED(CONFIG_SEC_INPUT_BOOSTER_QC) || \ + IS_ENABLED(CONFIG_SEC_INPUT_BOOSTER_SLSI) || \ + IS_ENABLED(CONFIG_SEC_INPUT_BOOSTER_MTK) +spinlock_t write_ib_lock; +spinlock_t write_qos_lock; +struct mutex trigger_ib_lock; +struct mutex mem_lock; +struct mutex rel_ib_lock; +struct mutex sip_rel_lock; +struct workqueue_struct *ib_handle_highwq; + +int total_ib_cnt; +int ib_init_succeed; +int level_value = IB_MAX; + +unsigned int debug_flag; +unsigned int enable_event_booster = INIT_ZERO; +unsigned int u_ib_mode; + +// Input Booster Init Variables +int *release_val; +int *cpu_cluster_policy; +int *allowed_resources; + +int max_resource_count; +int max_cluster_count; +int allowed_res_count; + +static int allowed_mask; +static struct t_ib_boost_mode* ib_boost_modes; +static unsigned int ib_mode_mask; +static int num_of_mode; + +struct t_ib_device_tree* ib_device_trees; +struct t_ib_trigger* ib_trigger; + +struct list_head* ib_list; +struct list_head* qos_list; + +// @evdev_mt_slot : save the number of inputed touch slot. +int evdev_mt_slot; +// @evdev_mt_event[] : save count of each boooter's events. +int evdev_mt_event[MAX_DEVICE_TYPE_NUM]; +int trigger_cnt; +int send_ev_enable; + +struct t_ib_info* find_release_ib(int dev_type, int key_id); +struct t_ib_info* create_ib_instance(struct t_ib_trigger* p_IbTrigger, int uniqId); +bool is_validate_uniqid(unsigned int uniq_id); +struct t_ib_target* find_update_target(int uniq_id, int res_id); +long get_qos_value(int res_id); +void remove_ib_instance(struct t_ib_info* ib); + +void trigger_input_booster(struct work_struct *work) +{ + unsigned int uniq_id = 0; + int res_type = -1; + + struct t_ib_info* ib; + struct t_ib_trigger *p_IbTrigger = container_of(work, struct t_ib_trigger, ib_trigger_work); + + if (p_IbTrigger == NULL) { + pr_err(ITAG" IB Trigger instance is null\n"); + return; + } + + mutex_lock(&trigger_ib_lock); + + // Input booster On/Off handling + if (p_IbTrigger->event_type == BOOSTER_ON) { + if (find_release_ib(p_IbTrigger->dev_type, p_IbTrigger->key_id) != NULL) { + pr_err(ITAG" IB Trigger :: ib already exist. Key(%d)\n", p_IbTrigger->key_id); + mutex_unlock(&trigger_ib_lock); + return; + } + // Check if uniqId exits. + do { + uniq_id = total_ib_cnt++; + + if (total_ib_cnt == MAX_IB_COUNT) + total_ib_cnt = 0; + + } while (!is_validate_uniqid(uniq_id)); + + // Make ib instance with all needed factor. + ib = create_ib_instance(p_IbTrigger, uniq_id); + + pr_info(ITAG" IB Trigger Press :: IB Uniq Id(%d)\n", uniq_id); + + if (ib == NULL || ib->ib_dt == NULL || ib->ib_dt->res == NULL) { + mutex_unlock(&trigger_ib_lock); + pr_err(ITAG" Creating ib object fail\n"); + return; + } + + // Head time must be existed + if (ib->ib_dt->head_time == 0) { + mutex_unlock(&trigger_ib_lock); + remove_ib_instance(ib); + return; + } + + ib->press_flag = FLAG_ON; + + // When create ib instance, insert resource info in qos list with value 0. + for (res_type = 0; res_type < allowed_res_count; res_type++) { + if (allowed_resources[res_type] > max_resource_count) { + pr_err(ITAG" allow res num(%d) exceeds over max res count\n", + allowed_resources[res_type]); + continue; + } + if (ib != NULL && + ib->ib_dt->res[allowed_resources[res_type]].head_value != 0) { + struct t_ib_target* tv; + tv = kmalloc(sizeof(struct t_ib_target), GFP_KERNEL); + + if (tv == NULL) + continue; + + tv->uniq_id = ib->uniq_id; + tv->value = 0; + + spin_lock(&write_qos_lock); + list_add_tail_rcu(&(tv->list), &qos_list[allowed_resources[res_type]]); + spin_unlock(&write_qos_lock); + } + } + queue_work(ib_handle_highwq, &(ib->ib_state_work[IB_HEAD])); + + } else { + /* Find ib instance in the list. if not, ignore this event. + * if exists, Release flag on. Call ib's Release func. + */ + + mutex_lock(&sip_rel_lock); + ib = find_release_ib(p_IbTrigger->dev_type, p_IbTrigger->key_id); + + mutex_lock(&mem_lock); + if (ib == NULL) { + pr_err(ITAG" IB is null on release\n"); + mutex_unlock(&mem_lock); + mutex_unlock(&sip_rel_lock); + mutex_unlock(&trigger_ib_lock); + return; + } + mutex_unlock(&mem_lock); + + mutex_lock(&ib->lock); + pr_info(ITAG" IB Trigger Release :: Uniq ID(%d)\n", ib->uniq_id); + ib->rel_flag = FLAG_ON; + if(ib->ib_dt->tail_time == 0) { + pr_booster(" IB tail time is 0"); + mutex_unlock(&ib->lock); + mutex_unlock(&sip_rel_lock); + mutex_unlock(&trigger_ib_lock); + return; + } + + // If head operation is already finished, tail timeout work will be triggered. + if (ib->isHeadFinished) { + if (!delayed_work_pending(&(ib->ib_timeout_work[IB_TAIL]))) { + queue_delayed_work(ib_handle_highwq, + &(ib->ib_timeout_work[IB_TAIL]), + msecs_to_jiffies(ib->ib_dt->tail_time)); + } else { + cancel_delayed_work(&(ib->ib_timeout_work[IB_TAIL])); + queue_delayed_work(ib_handle_highwq, + &(ib->ib_timeout_work[IB_TAIL]), + msecs_to_jiffies(ib->ib_dt->tail_time)); + } + } + mutex_unlock(&ib->lock); + mutex_unlock(&sip_rel_lock); + } + mutex_unlock(&trigger_ib_lock); + +} + +struct t_ib_info* create_ib_instance(struct t_ib_trigger* p_IbTrigger, int uniqId) +{ + struct t_ib_info* ib = kmalloc(sizeof(struct t_ib_info), GFP_KERNEL); + int dev_type = p_IbTrigger->dev_type; + int idx = 0, conv_idx = 0; + + if (ib == NULL) + return NULL; + + ib->key_id = p_IbTrigger->key_id; + ib->uniq_id = uniqId; + ib->press_flag = FLAG_OFF; + ib->rel_flag = FLAG_OFF; + ib->isHeadFinished = 0; + + if (!(ib_mode_mask & (1 << u_ib_mode)) || !((1 < ib_boost_modes[0].dt_count) { + pr_err(ITAG" dev_type(%d) is over dt count(%d)\n", dev_type, ib_boost_modes[0].dt_count); + kfree(ib); + ib = NULL; + return NULL; + } + conv_idx = ib_boost_modes[0].type_to_idx_table[dev_type]; + ib->ib_dt = &ib_boost_modes[0].dt[dev_type]; + } else { + conv_idx = ib_boost_modes[u_ib_mode].type_to_idx_table[dev_type]; + ib->ib_dt = &ib_boost_modes[u_ib_mode].dt[conv_idx]; + } + + INIT_WORK(&ib->ib_state_work[IB_HEAD], press_state_func); + INIT_DELAYED_WORK(&ib->ib_timeout_work[IB_HEAD], press_timeout_func); + INIT_WORK(&ib->ib_state_work[IB_TAIL], release_state_func); + INIT_DELAYED_WORK(&ib->ib_timeout_work[IB_TAIL], release_timeout_func); + mutex_init(&ib->lock); + + spin_lock(&write_ib_lock); + list_add_tail_rcu(&(ib->list), &ib_list[dev_type]); + spin_unlock(&write_ib_lock); + + return ib; +} + +bool is_validate_uniqid(unsigned int uniq_id) +{ + int dev_type; + int cnt = 0; + struct t_ib_info* ib = NULL; + rcu_read_lock(); + + for (dev_type = 0; dev_type < MAX_DEVICE_TYPE_NUM; dev_type++) { + if (list_empty(&ib_list[dev_type])) { + pr_booster("IB List(%d) Empty", dev_type); + continue; + } + list_for_each_entry_rcu(ib, &ib_list[dev_type], list) { + cnt++; + if (ib != NULL && ib->uniq_id == uniq_id) { + rcu_read_unlock(); + pr_booster("uniq id find :: IB Idx(%d) old(%d) new(%d)", cnt, ib->uniq_id, uniq_id); + return false; + } + } + cnt = 0; + } + + rcu_read_unlock(); + return true; +} + +struct t_ib_info* find_release_ib(int dev_type, int key_id) +{ + struct t_ib_info* ib = NULL; + rcu_read_lock(); + if (list_empty(&ib_list[dev_type])) { + rcu_read_unlock(); + pr_booster("Release IB(%d) Not Exist & List Empty", key_id); + return NULL; + } + + list_for_each_entry_rcu(ib, &ib_list[dev_type], list) { + if (ib != NULL && ib->key_id == key_id && ib->rel_flag == FLAG_OFF) { + rcu_read_unlock(); + pr_booster("Release IB(%d) Found", key_id); + return ib; + } + } + + rcu_read_unlock(); + pr_booster("Release IB(%d) Not Exist", key_id); + return NULL; + +} + +void press_state_func(struct work_struct* work) +{ + + struct t_ib_res_info res; + struct t_ib_target* tv; + long qos_values[MAX_RES_COUNT] = {0, }; + int res_type = 0; + + struct t_ib_info* target_ib = container_of(work, struct t_ib_info, ib_state_work[IB_HEAD]); + + pr_info(ITAG" Press State Func :::: Unique_Id(%d) Head_Time(%d)\n", + target_ib->uniq_id, target_ib->ib_dt->head_time); + + // Get_Res_List(head) and update head value. + for (res_type = 0; res_type < allowed_res_count; res_type++) { + res = target_ib->ib_dt->res[allowed_resources[res_type]]; + if (res.head_value == 0) + continue; + + // Find already added target value instance and update value as a head. + tv = find_update_target(target_ib->uniq_id, res.res_id); + + if (tv == NULL) { + pr_err(ITAG"Press State Func :::: %d's tv(%d) is null T.T\n", + target_ib->uniq_id, res.res_id); + continue; + } + + tv->value = res.head_value; + pr_booster("Press State Func :::: Uniq(%d)'s Update Res(%d) Head Val(%d)", + tv->uniq_id, res.res_id, res.head_value); + + qos_values[res.res_id] = get_qos_value(res.res_id); + + } + + ib_set_booster(qos_values); + + queue_delayed_work(ib_handle_highwq, &(target_ib->ib_timeout_work[IB_HEAD]), + msecs_to_jiffies(target_ib->ib_dt->head_time)); +} + +void press_timeout_func(struct work_struct* work) +{ + + struct t_ib_info* target_ib = container_of(work, struct t_ib_info, ib_timeout_work[IB_HEAD].work); + + if (!target_ib) + return; + + pr_info(ITAG" Press Timeout Func :::: Unique_Id(%d) Tail_Time(%d)\n", + target_ib->uniq_id, target_ib->ib_dt->tail_time); + + int res_type; + struct t_ib_res_info res; + struct t_ib_target* tv; + long qos_values[MAX_RES_COUNT] = {0, }; + long rel_flags[MAX_RES_COUNT] = {0, }; + + mutex_lock(&sip_rel_lock); + if (target_ib->ib_dt->tail_time != 0) { + mutex_lock(&target_ib->lock); + queue_work(ib_handle_highwq, &(target_ib->ib_state_work[IB_TAIL])); + mutex_unlock(&target_ib->lock); + } else { + //NO TAIL Scenario : Delete Ib instance and free all memory space. + for (res_type = 0; res_type < allowed_res_count; res_type++) { + res = target_ib->ib_dt->res[allowed_resources[res_type]]; + + tv = find_update_target(target_ib->uniq_id, res.res_id); + if (tv == NULL) { + pr_err(ITAG" Press Timeout Func :::: %d's TV No Exist(%d)\n", + target_ib->uniq_id, res.res_id); + continue; + } + + spin_lock(&write_qos_lock); + list_del_rcu(&(tv->list)); + spin_unlock(&write_qos_lock); + synchronize_rcu(); + kfree(tv); + tv = NULL; + + rcu_read_lock(); + if (!list_empty(&qos_list[res.res_id])) { + rcu_read_unlock(); + qos_values[res.res_id] = get_qos_value(res.res_id); + pr_booster("Press Timeout ::: Remove Val Cuz No Tail ::: Uniq(%d) Res(%d) Qos Val(%ld)", + target_ib->uniq_id, res.res_id, qos_values[res.res_id]); + } + else { + rcu_read_unlock(); + rel_flags[res.res_id] = 1; + pr_booster("Press Timeout ::: Uniq(%d) Release Booster(%d) ::: No Tail and List Empty", + target_ib->uniq_id, res.res_id); + + } + } + + mutex_lock(&rel_ib_lock); + ib_release_booster(rel_flags); + ib_set_booster(qos_values); + mutex_unlock(&rel_ib_lock); + + remove_ib_instance(target_ib); + } + mutex_unlock(&sip_rel_lock); + +} + +void release_state_func(struct work_struct* work) +{ + + long qos_values[MAX_RES_COUNT] = {0, }; + int res_type = 0; + struct t_ib_target* tv; + struct t_ib_res_info res; + + struct t_ib_info* target_ib = container_of(work, struct t_ib_info, ib_state_work[IB_TAIL]); + + if (target_ib == NULL) + return; + + mutex_lock(&target_ib->lock); + + target_ib->isHeadFinished = 1; + + pr_info(ITAG" Release State Func :::: Unique_Id(%d) Rel_Flag(%d)\n", + target_ib->uniq_id, target_ib->rel_flag); + + for (res_type = 0; res_type < allowed_res_count; res_type++) { + res = target_ib->ib_dt->res[allowed_resources[res_type]]; + if (res.tail_value == 0) + continue; + + tv = find_update_target(target_ib->uniq_id, res.res_id); + if (tv == NULL) + continue; + + spin_lock(&write_qos_lock); + tv->value = res.tail_value; + spin_unlock(&write_qos_lock); + + qos_values[res.res_id] = get_qos_value(res.res_id); + pr_booster("Release State Func :::: Uniq(%d)'s Update Tail Val (%ld), Qos_Val(%ld)", + tv->uniq_id, tv->value, qos_values[res.res_id]); + } + + ib_set_booster(qos_values); + + // If release event already triggered, tail delay work will be triggered after relese state func. + if (target_ib->rel_flag == FLAG_ON) { + if (!delayed_work_pending(&(target_ib->ib_timeout_work[IB_TAIL]))) { + queue_delayed_work(ib_handle_highwq, + &(target_ib->ib_timeout_work[IB_TAIL]), + msecs_to_jiffies(target_ib->ib_dt->tail_time)); + } else { + pr_err(ITAG" Release State Func :: tail timeout start\n"); + } + } else { + queue_delayed_work(ib_handle_highwq, + &(target_ib->ib_timeout_work[IB_TAIL]), + msecs_to_jiffies(60000)); + } + + mutex_unlock(&target_ib->lock); +} + +void release_timeout_func(struct work_struct* work) +{ + long qos_values[MAX_RES_COUNT] = {0, }; + long rel_flags[MAX_RES_COUNT] = {0, }; + struct t_ib_target* tv; + struct t_ib_res_info res; + int res_type; + + struct t_ib_info* target_ib = container_of(work, struct t_ib_info, ib_timeout_work[IB_TAIL].work); + if(!target_ib) + return; + + pr_info(ITAG" Release Timeout Func :::: Unique_Id(%d)\n", target_ib->uniq_id); + mutex_lock(&sip_rel_lock); + for (res_type = 0; res_type < allowed_res_count; res_type++) { + res = target_ib->ib_dt->res[allowed_resources[res_type]]; + + tv = find_update_target(target_ib->uniq_id, res.res_id); + if (tv == NULL) { + pr_err(ITAG" Release Timeout Func :::: %d's TV No Exist(%d)\n", + target_ib->uniq_id, res.res_id); + continue; + } + + pr_booster("Release Timeout Func :::: Delete Uniq(%d)'s TV Val (%ld)", + tv->uniq_id, tv->value); + + spin_lock(&write_qos_lock); + list_del_rcu(&(tv->list)); + spin_unlock(&write_qos_lock); + synchronize_rcu(); + kfree(tv); + tv = NULL; + + rcu_read_lock(); + if (!list_empty(&qos_list[res.res_id])) { + rcu_read_unlock(); + qos_values[res.res_id] = get_qos_value(res.res_id); + pr_booster("Release Timeout Func ::: Uniq(%d) Res(%d) Qos Val(%ld)", + target_ib->uniq_id, res.res_id, qos_values[res.res_id]); + } + else { + rcu_read_unlock(); + rel_flags[res.res_id] = 1; + pr_booster("Release Timeout ::: Release Booster(%d's %d) ::: List Empty", + target_ib->uniq_id, res.res_id); + + } + } + mutex_lock(&rel_ib_lock); + ib_release_booster(rel_flags); + ib_set_booster(qos_values); + mutex_unlock(&rel_ib_lock); + + remove_ib_instance(target_ib); + mutex_unlock(&sip_rel_lock); + +} + +struct t_ib_target* find_update_target(int uniq_id, int res_id) +{ + struct t_ib_target* tv; + + if (res_id < 0 || res_id >= MAX_RES_COUNT) + return NULL; + + rcu_read_lock(); + list_for_each_entry_rcu(tv, &qos_list[res_id], list) { + if (tv != NULL && tv->uniq_id == uniq_id) { + rcu_read_unlock(); + return tv; + } + } + rcu_read_unlock(); + + return NULL; +} + +long get_qos_value(int res_id) +{ + //Find tv instance that has max value in the qos_list that has the passed res_id. + struct t_ib_target* tv; + long ret_val = 0; + + rcu_read_lock(); + + if (list_empty(&qos_list[res_id])) { + rcu_read_unlock(); + return 0; + } + + list_for_each_entry_rcu(tv, &qos_list[res_id], list) { + if (tv->value > ret_val) + ret_val = tv->value; + } + rcu_read_unlock(); + + return ret_val; +} + +void remove_ib_instance(struct t_ib_info* target_ib) +{ + struct t_ib_info* ib = NULL; + int ib_exist = 0; + + //Check if target instance exists in the list or not. + spin_lock(&write_ib_lock); + list_for_each_entry_rcu(ib, &ib_list[target_ib->ib_dt->type], list) { + if (ib != NULL && ib == target_ib) { + ib_exist = 1; + break; + } + } + + if (!ib_exist) { + spin_unlock(&write_ib_lock); + pr_err(ITAG" Del Ib Fail Id : %d\n", target_ib->uniq_id); + } else { + list_del_rcu(&(target_ib->list)); + spin_unlock(&write_ib_lock); + synchronize_rcu(); + pr_info(ITAG" Del Ib Instance's Id : %d\n", target_ib->uniq_id); + mutex_lock(&mem_lock); + if (target_ib != NULL) { + kfree(target_ib); + target_ib = NULL; + } + mutex_unlock(&mem_lock); + } +} + +unsigned int create_uniq_id(int type, int code, int slot) +{ + pr_booster("Create Key Id -> type(%d), code(%d), slot(%d)", type, code, slot); + return (type << (TYPE_BITS + CODE_BITS)) | (code << CODE_BITS) | slot; +} + +void ib_auto_test(int type, int code, int val) +{ + send_ev_enable = 1; +} + +//+++++++++++++++++++++++++++++++++++++++++++++++ STRUCT & VARIABLE FOR SYSFS +++++++++++++++++++++++++++++++++++++++++++++++// +SYSFS_CLASS(enable_event, (buf, "%u\n", enable_event), 1) +SYSFS_CLASS(debug_level, (buf, "%u\n", debug_level), 1) +SYSFS_CLASS(ib_mode_state, (buf, "%u\n", ib_mode_state), 1) +SYSFS_CLASS(sendevent, (buf, "%d\n", sendevent), 3) +HEAD_TAIL_SYSFS_DEVICE(head) +HEAD_TAIL_SYSFS_DEVICE(tail) +LEVEL_SYSFS_DEVICE(level) + +struct attribute* dvfs_attributes[] = { + &dev_attr_head.attr, + &dev_attr_tail.attr, + &dev_attr_level.attr, + NULL, +}; + +struct attribute_group dvfs_attr_group = { + .attrs = dvfs_attributes, +}; + +void init_sysfs_device(struct class* sysfs_class, struct device* pdev, struct t_ib_device_tree* ib_dt) { + struct device* sysfs_dev; + int ret = 0; + int bus_ret = 0; + + sysfs_dev = device_create(sysfs_class, NULL, 0, ib_dt, "%s", ib_dt->label); + if (IS_ERR(sysfs_dev)) { + ret = IS_ERR(sysfs_dev); + pr_err(ITAG" Failed to create %s sysfs device[%d]\n", ib_dt->label, ret); + return; + } + ret = sysfs_create_group(&sysfs_dev->kobj, &dvfs_attr_group); + if (ret) { + pr_err(ITAG" Failed to create %s sysfs group\n", ib_dt->label); + return; + } +} + +int parse_dtsi_str(struct device_node *np, const char *target_node, void *target_arr, int isIntType) +{ + char prop_str[100]; + size_t prop_size = 0; + char *prop_pointer = NULL; + const char *token = NULL; + int iter = 0; + int *int_target_arr_ptr; + char *str_target_arr_ptr; + int copy_result; + const char *full_str = of_get_property(np, target_node, NULL); + + if (full_str == NULL) { + pr_err(ITAG" Target Node(%s) is null\n", target_node); + return -1; + } + + if (isIntType) + int_target_arr_ptr = (int *)target_arr; + else + str_target_arr_ptr = (char *)target_arr; + + prop_size = strlcpy(prop_str, full_str, sizeof(char)*100); + prop_pointer = prop_str; + token = strsep(&prop_pointer, ","); + + while (token != NULL) { + pr_info("%s %d's Type Value(%s)\n", target_node, iter, token); + + //Release Values inserted inside array + if (isIntType) { + copy_result = sscanf(token, "%d", &int_target_arr_ptr[iter]); + if (!copy_result) { + pr_err(ITAG" DTSI string value parsing fail\n"); + return -1; + } + pr_info("Target_arr[%d] : %d\n", iter, int_target_arr_ptr[iter]); + } else { + copy_result = sscanf(token, "%s", &str_target_arr_ptr[iter]); + if (!copy_result) { + pr_err(ITAG" DTSI string value parsing fail\n"); + return -1; + } + } + + token = strsep(&prop_pointer, ","); + iter++; + } + + return iter; +} + +int is_ib_init_succeed(void) +{ + return (ib_trigger != NULL && ib_boost_modes != NULL && + ib_list != NULL && qos_list != NULL) ? 1 : 0; +} + +void input_booster_exit(void) +{ + + kfree(ib_trigger); + kfree(ib_boost_modes); + kfree(ib_device_trees); + kfree(ib_list); + kfree(qos_list); + kfree(cpu_cluster_policy); + kfree(allowed_resources); + kfree(release_val); + + input_booster_exit_vendor(); + +} + +// ********** Init Booster ********** // +int parse_device_info(struct device_node* np, struct t_ib_device_tree* ib_device_trees) { + int ib_res_size = sizeof(struct t_ib_res_info); + struct device_node* cnp; + int device_count = 0, i; + + for_each_child_of_node(np, cnp) { + /************************************************/ + // fill all needed data into res_info instance in dt instance. + struct t_ib_device_tree* ib_dt = (ib_device_trees + device_count); + struct device_node* child_resource_node; + struct device_node* resource_node = of_find_compatible_node(cnp, NULL, "resource"); + + ib_dt->res = kzalloc(ib_res_size * max_resource_count, GFP_KERNEL); + for (i = 0; i < max_resource_count; ++i){ + ib_dt->res[i].res_id = -1; + } + + int res_type = 0, res_id = 0; + + for_each_child_of_node(resource_node, child_resource_node) { + int result = parse_dtsi_str(child_resource_node, "resource,id", &res_id, 1); + pr_info(ITAG" res_id(%d) result(%d)\n", res_id, result); + if (result == 0 || result == -1) continue; + + if (!(allowed_mask & (1<res[res_id].res_id = res_id; + ib_dt->res[res_id].label = of_get_property(child_resource_node, "resource,label", NULL); + + int inputbooster_size = 0; + const u32* is_exist_inputbooster_size = of_get_property(child_resource_node, "resource,value", &inputbooster_size); + + if (is_exist_inputbooster_size && inputbooster_size) { + inputbooster_size = inputbooster_size / sizeof(u32); + } + + if (inputbooster_size != 2) { + pr_err(ITAG" inputbooster size must be 2!\n"); + return -1; // error + } + + for (res_type = 0; res_type < inputbooster_size; ++res_type) { + if (res_type == IB_HEAD) { + of_property_read_u32_index(child_resource_node, "resource,value", + res_type, &ib_dt->res[res_id].head_value); + } + else if (res_type == IB_TAIL) { + of_property_read_u32_index(child_resource_node, "resource,value", + res_type, &ib_dt->res[res_id].tail_value); + } + } + } + + ib_dt->label = of_get_property(cnp, "input_booster,label", NULL); + pr_info(ITAG"ib_dt->label : %s\n", ib_dt->label); + + if (of_property_read_u32(cnp, "input_booster,type", &ib_dt->type)) { + pr_err(ITAG" Failed to get type property\n"); + break; + } + if (of_property_read_u32(cnp, "input_booster,head_time", &ib_dt->head_time)) { + pr_err(ITAG" Fail Get Head Time\n"); + break; + } + if (of_property_read_u32(cnp, "input_booster,tail_time", &ib_dt->tail_time)) { + pr_err(ITAG" Fail Get Tail Time\n"); + break; + } + //Init all type of ib list. + INIT_LIST_HEAD(&ib_list[device_count]); + device_count++; + } + + return device_count; +} + +void input_booster_init(void) +{ + // ********** Load Frequency data from DTSI ********** + struct device_node* np; + int i; + + int ib_dt_size = sizeof(struct t_ib_device_tree); + int ib_res_size = sizeof(struct t_ib_res_info); + int list_head_size = sizeof(struct list_head); + int ddr_info_size = sizeof(struct t_ddr_info); + + int ndevice_in_dt = 0; + int res_cnt; + int result; + + total_ib_cnt = 0; + ib_init_succeed = 0; + debug_flag = 0; + enable_event_booster = INIT_ZERO; + max_resource_count = 0; + allowed_res_count = 0; + evdev_mt_slot = 0; + trigger_cnt = 0; + send_ev_enable = 0; + + spin_lock_init(&write_ib_lock); + spin_lock_init(&write_qos_lock); + mutex_init(&trigger_ib_lock); + mutex_init(&sip_rel_lock); + mutex_init(&rel_ib_lock); + mutex_init(&mem_lock); + +//Input Booster Trigger Strcut Init + ib_trigger = kzalloc(sizeof(struct t_ib_trigger) * MAX_IB_COUNT, GFP_KERNEL); + + if (ib_trigger == NULL) { + pr_err(ITAG" ib_trigger mem alloc fail\n"); + goto out; + } + + for (i = 0; i < MAX_IB_COUNT; i++) + INIT_WORK(&(ib_trigger[i].ib_trigger_work), trigger_input_booster); + + np = of_find_compatible_node(NULL, NULL, "input_booster"); + if (np == NULL) { + pr_err(ITAG" Input Booster Compatible wasn't found in dtsi\n"); + goto out; + } + +// Geting the count of devices. + ndevice_in_dt = of_get_child_count(np); + + ib_device_trees = kzalloc(ib_dt_size * ndevice_in_dt, GFP_KERNEL); + if (ib_device_trees == NULL) { + pr_err(ITAG" dt_infor mem alloc fail\n"); + goto out; + } + +// ib list mem alloc + ib_list = kzalloc(list_head_size * MAX_DEVICE_TYPE_NUM, GFP_KERNEL); + if (ib_list == NULL) { + pr_err(ITAG" ib list mem alloc fail\n"); + goto out; + } + +// Get Needed Information from dtsi + result = sscanf((of_get_property(np, "max_resource_count", + NULL)), "%d", &max_resource_count); + if (!result) { + pr_err(ITAG"max_resource_count value parsing fail\n"); + goto out; + } + + result = sscanf((of_get_property(np, "max_cluster_count", + NULL)), "%d", &max_cluster_count); + if (!result) { + pr_err(ITAG"max_cluster_count value parsing fail\n"); + goto out; + } + + pr_info(ITAG" resource size : %d, cluster count : %d\n", + max_resource_count, max_cluster_count); + +//qos list mem alloc + qos_list = kzalloc(list_head_size * max_resource_count, GFP_KERNEL); + if (qos_list == NULL) { + pr_err(ITAG" ib list mem alloc fail\n"); + goto out; + } + for (res_cnt = 0; res_cnt < max_resource_count; res_cnt++) + INIT_LIST_HEAD(&qos_list[res_cnt]); + +//Init Cpu Cluster Value + cpu_cluster_policy = kzalloc(sizeof(int) * max_cluster_count, GFP_KERNEL); + if (cpu_cluster_policy == NULL) { + pr_err(ITAG" cpu_cluster_policy mem alloc fail\n"); + goto out; + } + + result = parse_dtsi_str(np, "cpu_cluster_policy", cpu_cluster_policy, 1); + pr_info(ITAG" Init:: Total Cpu Cluster Count : %d\n", result); + if (result < 0) + goto out; + + if (result < max_cluster_count) { + for (i = result; i < max_cluster_count; i++) + cpu_cluster_policy[i] = -1; + } + +//Allow Resource + allowed_resources = kzalloc(sizeof(int) * max_resource_count, GFP_KERNEL); + if (allowed_resources == NULL) { + pr_err(ITAG" allowed_resources mem alloc fail\n"); + goto out; + } + result = parse_dtsi_str(np, "allowed_resources", allowed_resources, 1); + pr_info(ITAG" Init:: Total Allow Resource Count: %d\n", result); + allowed_res_count = result; + if (result < 0) + goto out; + + for (i = 0; i < result; i++) { + allowed_mask |= (1<= max_resource_count) { + pr_err(ITAG" allow res index exceeds over max res count %d\n", + allowed_resources[i]); + goto out; + } + } + + if (result > max_resource_count) { + pr_err(ITAG" allow resources exceed over max resource count\n"); + goto out; + } + +//Init Resource Release Values + release_val = kzalloc(sizeof(int) * max_resource_count, GFP_KERNEL); + if (release_val == NULL) { + pr_err(ITAG" release_val mem alloc fail\n"); + goto out; + } + result = parse_dtsi_str(np, "ib_release_values", release_val, 1); + pr_info(ITAG" Init:: Total Release Value Count: %d\n", result); + if (result == 0) + goto out; + + if (result > max_resource_count) { + pr_err(ITAG" release value parse fail :: overceed max value\n"); + goto out; + } + +// Geting the count of sub boosters. + num_of_mode = of_get_child_count(np); + //ib_dt_grp = kzalloc(sizeof(t_ib_device_tree*) * num_of_mode, GFP_KERNEL); + pr_info(ITAG" %s : Total IB Mode Cnt : %d\n", __func__, num_of_mode); + ib_boost_modes = kzalloc(sizeof(struct t_ib_boost_mode) * num_of_mode, GFP_KERNEL); + +// ib_boost_modes Init + struct device_node* cnp; + for_each_child_of_node(np, cnp) { + struct t_ib_device_tree* ib_dt; + int boost_mode = 0; + + // Getting Mode Type + result = parse_dtsi_str(cnp, "booster,mode", &boost_mode, 1); + if (result == 0 || boost_mode < 0 || boost_mode >= num_of_mode) { + pr_err(ITAG "Booster Mode(%d) exceeds max number of mode(%d)\n", boost_mode, num_of_mode); + continue; + } + + ib_boost_modes[boost_mode].type = boost_mode; + ib_mode_mask |= (1 << boost_mode); + + //Getting Sub Booster Label + ib_boost_modes[boost_mode].label = of_get_property(cnp, "booster,label", NULL); + pr_info(ITAG" %s : Mode_label : %s\n", __func__, ib_boost_modes[boost_mode].label); + + ib_boost_modes[boost_mode].dt_count = of_get_child_count(cnp); + ib_dt = ib_boost_modes[boost_mode].dt = + kzalloc(ib_dt_size * ib_boost_modes[boost_mode].dt_count, GFP_KERNEL); + if (ib_dt == NULL) { + pr_err(ITAG" Mode[%d] ib_dt mem alloc fail\n", boost_mode); + goto out; + } + + result = parse_device_info(cnp, ib_dt); + if (result < 0) + goto out; + + for (i=0; itype); + //Make table to convert type to index; + ib_boost_modes[boost_mode].type_to_idx_table[(ib_dt + i)->type] = i; + } + + pr_info(ITAG"%s Total Input Device Count(%d)\n", ib_boost_modes[boost_mode].label, result); + } + + ib_init_succeed = is_ib_init_succeed(); + ib_init_succeed = input_booster_init_vendor(); + + if (ib_init_succeed) + ib_handle_highwq = alloc_workqueue("ib_unbound_high_wq", WQ_UNBOUND | WQ_HIGHPRI, + MAX_IB_COUNT); + +out: + // ********** Initialize Sysfs ********** + { + struct class* sysfs_class; + struct device* sysfs_dev; + int ret; + int dev_type, mode_type; + struct t_ib_boost_mode* ib_mode; + + if (ib_init_succeed) { + for (mode_type = 0; mode_type < num_of_mode; mode_type++) { + ib_mode = &ib_boost_modes[mode_type]; + sysfs_class = class_create(THIS_MODULE, ib_mode->label); + if (IS_ERR(sysfs_class)) { + pr_err(ITAG" Failed to create class\n"); + continue; + } + if (!mode_type) { + INIT_SYSFS_CLASS(enable_event) + INIT_SYSFS_CLASS(debug_level) + INIT_SYSFS_CLASS(ib_mode_state) + INIT_SYSFS_CLASS(sendevent) + } + + for (dev_type = 0; dev_type < ib_mode->dt_count; dev_type++) { + init_sysfs_device(sysfs_class, NULL, &ib_mode->dt[dev_type]); + } + } + } + } +} +#endif //CONFIG_SEC_INPUT_BOOSTER_QC || CONFIG_SEC_INPUT_BOOSTER_SLSI || CONFIG_SEC_INPUT_BOOSTER_MTK +MODULE_LICENSE("GPL"); \ No newline at end of file diff --git a/drivers/input/input_boost/input_booster_qc.c b/drivers/input/input_boost/input_booster_qc.c new file mode 100644 index 000000000000..3985d83caaba --- /dev/null +++ b/drivers/input/input_boost/input_booster_qc.c @@ -0,0 +1,257 @@ +#include +#include +#include +#include "../../../kernel/sched/walt/hyst_qos.h" +#include + +int current_hmp_boost; +struct user_req lpm_bias_pm_qos_request; + +static struct device *dev; +struct icc_path *path_touch_bw; +int register_ddr; + +#define MHZ_TO_BPS(mhz, w) ((uint64_t)mhz * 1000 * 1000 * w) +#define MHZ_TO_KBPS(mhz, w) ((uint64_t)mhz * 1000 * w) + +//Refer to "include/dt-bindings/interconnect/qcom,lahaina.h" +#define MASTER_APPSS_PROC 2 +#define SLAVE_EBI1 512 + +void set_hmp(int level); + +#if IS_ENABLED(CONFIG_CPU_FREQ_LIMIT) + /* TEMP: for KPI */ +#define DVFS_TOUCH_ID 1 +#else +#define DVFS_TOUCH_ID 0 +int set_freq_limit(unsigned long id, unsigned int freq) +{ + pr_err("%s is not yet implemented\n", __func__); + return 0; +} +#endif + +#if IS_ENABLED(CONFIG_ARCH_KALAMA) +#define NUM_BUS_TABLE 14 +#define BUS_W 4 /* SM8350 DDR Voting('w' for DDR is 4) */ + +int ab_ib_bus_vectors[NUM_BUS_TABLE][2] = { + //{ab, ib} + {0, 0}, /* 0 */ + {0, 200}, /* 1 */ + {0, 451}, /* 2 */ + {0, 547}, /* 3 */ + {0, 681}, /* 4 */ + {0, 768}, /* 5 */ + + {0, 1017}, /* 6 */ + {0, 1353}, /* 7 */ + {0, 1555}, /* 8 */ + {0, 1708}, /* 9 */ + {0, 2092}, /* 10 */ + {0, 2133}, /* 11 */ + {0, 2736}, /* 12 */ + {0, 3196} /* 13 */ +}; +#else +#define NUM_BUS_TABLE 1 +#define BUS_W 0 + +int ab_ib_bus_vectors[NUM_BUS_TABLE][2] = { + {0, 0}, /* 0 */ +}; +#endif //set null for other chipset + + +struct msm_touch_bus_vector_bps { + uint32_t ab; + uint32_t ib; +}; +struct msm_touch_bus_vector_bps touch_bus_vectors_bps[NUM_BUS_TABLE]; +void fill_bus_vector(void) +{ + int i = 0; + + for (i = 0; i < NUM_BUS_TABLE; i++) { + touch_bus_vectors_bps[i].ab = ab_ib_bus_vectors[i][0]; + touch_bus_vectors_bps[i].ib = MHZ_TO_KBPS(ab_ib_bus_vectors[i][1], BUS_W); + } +} + +int trans_freq_to_idx(long request_ddr_freq) +{ + int i = 0; + + if (request_ddr_freq <= 0) { + return 0; + } + + // in case of null table, return 0 + if (NUM_BUS_TABLE == 1) { + return 0; + } + + for (i = 0; i < NUM_BUS_TABLE-1; i++) { + if (request_ddr_freq > ab_ib_bus_vectors[i][1] && + request_ddr_freq <= ab_ib_bus_vectors[i+1][1]) { + return (i+1); + } + } + + return i+1; +} + +void ib_set_booster(long *qos_values) +{ + long value = -1; + int ddr_idx = 0; + int res_type = 0; + int cur_res_idx; + int rc = 0; + + for (res_type = 0; res_type < allowed_res_count; res_type++) { + + cur_res_idx = allowed_resources[res_type]; + value = qos_values[cur_res_idx]; + + if (value <= 0) + continue; + + switch (cur_res_idx) { + case CPUFREQ: + set_freq_limit(DVFS_TOUCH_ID, value); + pr_booster("%s :: cpufreq value : %ld", __func__, value); + break; + case DDRFREQ: + ddr_idx = trans_freq_to_idx(value); + pr_booster("%s :: bus value : %ld, %u", __func__, value, touch_bus_vectors_bps[ddr_idx].ib); + if (register_ddr) + rc = icc_set_bw(path_touch_bw, + touch_bus_vectors_bps[ddr_idx].ab, touch_bus_vectors_bps[ddr_idx].ib); + break; + case HMPBOOST: + set_hmp(value); + pr_booster("%s :: hmpboost value : %ld", __func__, value); + break; + case LPMBIAS: + hyst_update_request(&lpm_bias_pm_qos_request, PM_QOS_MIN_LIMIT, value*NSEC_PER_MSEC); + pr_booster("%s :: LPM Bias value : %ld", __func__, value*NSEC_PER_MSEC); + break; + default: + break; + } + } +} + +void ib_release_booster(long *rel_flags) +{ + //cpufreq : -1, ddrfreq : 0, HMP : 0, lpm_bias = 0 + int ddr_idx; + int flag; + int rc = 0; + int value; + + int res_type = 0; + int cur_res_idx; + + for (res_type = 0; res_type < allowed_res_count; res_type++) { + + cur_res_idx = allowed_resources[res_type]; + flag = rel_flags[cur_res_idx]; + if (flag <= 0) + continue; + + value = release_val[cur_res_idx]; + + switch (cur_res_idx) { + case CPUFREQ: + set_freq_limit(DVFS_TOUCH_ID, value); + break; + case DDRFREQ: + ddr_idx = trans_freq_to_idx(value); + if (register_ddr) + rc = icc_set_bw(path_touch_bw, + touch_bus_vectors_bps[ddr_idx].ab, touch_bus_vectors_bps[ddr_idx].ib); + break; + case HMPBOOST: + set_hmp(value); + break; + case LPMBIAS: + hyst_update_request(&lpm_bias_pm_qos_request, PM_QOS_MIN_LIMIT, value*NSEC_PER_MSEC); + pr_booster("%s :: LPM Bias value : %ld", __func__, value*NSEC_PER_MSEC); + break; + default: + break; + } + } +} + +#ifdef USE_HMP_BOOST +void set_hmp(int level) +{ + if (level != current_hmp_boost) { + if (level == 0) { + level = -current_hmp_boost; + current_hmp_boost = 0; + } else { + current_hmp_boost = level; + } + pr_booster("[Input Booster2] ****** hmp_boost : %d ( %s )\n", level, __func__); + if (sched_set_boost(level) < 0) + pr_err("[Input Booster2] ****** !!! fail to HMP !!!\n"); + } +} +#else +void set_hmp(int level) +{ + pr_booster("It does not use hmp\n", level, __func__); +} +#endif + +static void icc_release(struct device *dev) +{ + pr_booster("Release icc path of device\n"); +} + +int input_booster_init_vendor(void) +{ + + int bus_ret = 0, reg_ret = 0; + current_hmp_boost = 0; + +//Bus boost factors initailized + dev = kzalloc(sizeof(struct device), GFP_KERNEL); + dev_set_name(dev, "input_booster"); + dev->release = icc_release; + reg_ret = device_register(dev); + pr_info(ITAG "icc register result(%d)\n", reg_ret); + + if (!register_ddr) { + path_touch_bw = icc_get(dev, MASTER_APPSS_PROC, SLAVE_EBI1); + if (IS_ERR(path_touch_bw)) { + bus_ret = PTR_ERR(path_touch_bw); + if (bus_ret != -EPROBE_DEFER) + pr_err("Failed to get path from input bus booster. ret=%d\n", bus_ret); + } else { + pr_info(ITAG" Making icc path for bus success\n"); + register_ddr = 1; + } + } + + fill_bus_vector(); + hyst_add_request(&lpm_bias_pm_qos_request, 0, "Input Booster"); + return 1; +} + +void input_booster_exit_vendor(void) +{ + if (register_ddr) + icc_put(path_touch_bw); + + device_unregister(dev); + kfree(dev); + hyst_remove_request(&lpm_bias_pm_qos_request); +} + +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/hall/Kconfig b/drivers/input/misc/hall/Kconfig new file mode 100644 index 000000000000..7356bd256cc6 --- /dev/null +++ b/drivers/input/misc/hall/Kconfig @@ -0,0 +1,27 @@ +config INPUT_HALL_IC + tristate "Enable HALL IC" + default n + help + Enable Hall IC Feature + +config HALL_NOTIFIER + tristate "use hall ic notifier" + depends on INPUT_HALL_IC + default n + help + Enable notifier functions to notify hall ic status + +config HALL_LOGICAL + tristate "use logical hall ic" + depends on INPUT_HALL_IC + default n + help + Enable logical hall ic feature + It is used for pogo keyboard usually + +config HALL_DUMP_KEY_MODE + tristate "use hall dump key" + default n + help + Enable hall dump key + diff --git a/drivers/input/misc/hall/Makefile b/drivers/input/misc/hall/Makefile new file mode 100644 index 000000000000..ac70290bee5a --- /dev/null +++ b/drivers/input/misc/hall/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 +# +obj-$(CONFIG_INPUT_HALL_IC) += hall_ic.o +obj-$(CONFIG_HALL_NOTIFIER) += hall_ic_notifier.o +obj-$(CONFIG_HALL_DUMP_KEY_MODE) += sec_hall_dumpkey.o \ No newline at end of file diff --git a/drivers/input/misc/hall/hall_ic.c b/drivers/input/misc/hall/hall_ic.c new file mode 100644 index 000000000000..3ba62616f2f4 --- /dev/null +++ b/drivers/input/misc/hall/hall_ic.c @@ -0,0 +1,806 @@ +/* + * + * Copyright 2017 SEC + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) +#include +#endif +#if IS_ENABLED(CONFIG_SEC_FACTORY) +#include +#endif +#if IS_ENABLED(CONFIG_HALL_NOTIFIER) +#include +#endif +#if IS_ENABLED(CONFIG_TOUCHSCREEN_DUAL_FOLDABLE) || IS_ENABLED(CONFIG_SEC_INPUT_MULTI_DEVICE) +#if IS_ENABLED(CONFIG_USB_HW_PARAM) +#include +#endif +#endif +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) +#include +#endif +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) +#include +#endif + +/* + * Switch events + */ +#define SW_FOLDER 0x00 /* set = folder open, close*/ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)) +#define SW_FLIP 0x10 /* set = flip cover open, close*/ +#define SW_CERTIFYHALL 0x0b /* set = certify_hall attach/detach */ +#define SW_WACOM_HALL 0x0c /* set = tablet wacom hall attach/detach(set wacom cover mode) */ +#else +#define SW_FLIP 0x15 /* set = flip cover open, close*/ +#define SW_CERTIFYHALL 0x1b /* set = certify_hall attach/detach */ +#define SW_WACOM_HALL 0x1e /* set = tablet wacom hall attach/detach(set wacom cover mode) */ +#endif + +#define DEFAULT_DEBOUNCE_INTERVAL 50 +#define HALL_IC_WAKEUP_TIMEOUT 500 + +struct device *sec_hall_ic; +EXPORT_SYMBOL(sec_hall_ic); + +struct class *hall_sec_class; + +struct hall_ic_data { + struct delayed_work dwork; + struct wakeup_source *ws; + struct input_dev *input; + struct list_head list; + int gpio; + int irq; + int state; + int active_low; + unsigned int event; + const char *name; + int hall_count; +}; + +struct hall_ic_pdata { + struct hall_ic_data *hall; + unsigned int nhalls; + unsigned int debounce_interval; +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) + struct notifier_block vbus_nb; + bool charger_mode; +#endif +}; + +struct hall_ic_drvdata { + struct hall_ic_pdata *pdata; + struct work_struct work; + struct device *dev; +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + struct device *sec_dev; +#endif + struct mutex lock; + bool probe_done; +}; + +static LIST_HEAD(hall_ic_list); + +#if IS_ENABLED(CONFIG_HALL_DUMP_KEY_MODE) +#include +extern struct hall_dump_callbacks hall_dump_callbacks; +extern struct device *phall; +#endif + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) +struct hall_ic_drvdata *gddata; +/* + * the sysfs just returns the level of the gpio + */ +static ssize_t hall_detect_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hall_ic_data *hall; + int state; + + list_for_each_entry(hall, &hall_ic_list, list) { + if (hall->event != SW_FLIP) + continue; + hall->state = !!gpio_get_value_cansleep(hall->gpio); + state = hall->state ^ hall->active_low; + if (state) + sprintf(buf, "CLOSE\n"); + else + sprintf(buf, "OPEN\n"); + } + return strlen(buf); +} + +static ssize_t certify_hall_detect_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hall_ic_data *hall; + int state; + + list_for_each_entry(hall, &hall_ic_list, list) { + if (hall->event != SW_CERTIFYHALL) + continue; + hall->state = !!gpio_get_value_cansleep(hall->gpio); + state = hall->state ^ hall->active_low; + if (state) + sprintf(buf, "CLOSE\n"); + else + sprintf(buf, "OPEN\n"); + } + + return strlen(buf); +} + +static ssize_t hall_wacom_detect_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hall_ic_data *hall; + int state; + + list_for_each_entry(hall, &hall_ic_list, list) { + if (hall->event != SW_WACOM_HALL) + continue; + hall->state = !!gpio_get_value_cansleep(hall->gpio); + state = hall->state ^ hall->active_low; + if (state) + sprintf(buf, "CLOSE\n"); + else + sprintf(buf, "OPEN\n"); + } + + return strlen(buf); +} + +static ssize_t flip_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hall_ic_data *hall; + int state; + + list_for_each_entry(hall, &hall_ic_list, list) { + if (hall->event != SW_FOLDER) + continue; + hall->state = !!gpio_get_value_cansleep(hall->gpio); + state = hall->state ^ hall->active_low; + if (state) + snprintf(buf, 2, "0"); /* close */ + else + snprintf(buf, 2, "1"); /* open */ + } + + return strlen(buf); +} + +#if !defined(CONFIG_SAMSUNG_PRODUCT_SHIP) +static ssize_t flip_status_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct hall_ic_data *hall; + int state; + int ret; + + ret = kstrtoint(buf, 10, &state); + if (ret < 0) + return ret; + + list_for_each_entry(hall, &hall_ic_list, list) { + if (hall->event != SW_FOLDER) + continue; + + if (hall->input) { + input_report_switch(hall->input, hall->event, state); + input_sync(hall->input); + pr_info("[sec_input] %s: %s %d: %d\n", __func__, hall->name, hall->event, state); + } + } + return count; +} +#endif + +static ssize_t hall_number_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hall_ic_drvdata *ddata = dev_get_drvdata(dev); + + sprintf(buf, "%u\n", ddata->pdata->nhalls); + + return strlen(buf); +} + +static ssize_t debounce_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hall_ic_drvdata *ddata = dev_get_drvdata(dev); + + sprintf(buf, "%d\n", ddata->pdata->debounce_interval); + + return strlen(buf); +} + +static ssize_t hall_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hall_ic_data *hall; + + list_for_each_entry(hall, &hall_ic_list, list) { + if (hall->event == SW_FOLDER) { + snprintf(buf, PAGE_SIZE, "\"FCNT\":\"%d\"", hall->hall_count); + hall->hall_count = 0; + break; + } + } + + return strlen(buf); +} + +static DEVICE_ATTR_RO(hall_detect); +static DEVICE_ATTR_RO(certify_hall_detect); +static DEVICE_ATTR_RO(hall_wacom_detect); +#if defined(CONFIG_SAMSUNG_PRODUCT_SHIP) +static DEVICE_ATTR_RO(flip_status); +#else +static DEVICE_ATTR_RW(flip_status); +#endif +static DEVICE_ATTR_RO(hall_number); +static DEVICE_ATTR_RO(debounce); +static DEVICE_ATTR_RO(hall_count); + +static struct device_attribute *hall_ic_attrs[] = { + &dev_attr_hall_detect, + &dev_attr_certify_hall_detect, + &dev_attr_hall_wacom_detect, + &dev_attr_flip_status, + NULL, +}; +#endif + +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) && IS_ENABLED(CONFIG_HALL_DUMP_KEY_MODE) +int sec_hall_vbus_notification(struct notifier_block *nb, unsigned long cmd, void *data) +{ + struct hall_ic_pdata *pdata = container_of(nb, struct hall_ic_pdata, vbus_nb); + vbus_status_t vbus_type = *(vbus_status_t *)data; + + switch (vbus_type) { + case STATUS_VBUS_HIGH: + pdata->charger_mode = true; + break; + case STATUS_VBUS_LOW: + pdata->charger_mode = false; + break; + default: + break; + } + + pr_info("%s %d\n", __func__, pdata->charger_mode); + + return 0; +} + +static void dump_hall_event(struct device *dev) +{ + struct hall_ic_pdata *pdata = gddata->pdata; + struct hall_ic_data *hall; + + if (pdata->charger_mode) { + list_for_each_entry(hall, &hall_ic_list, list) { + if (hall->event != SW_FOLDER) + continue; + if (hall->input) { + input_report_switch(hall->input, hall->event, 0); + input_sync(hall->input); + pr_info("[sec_input] %s: %s %d\n", __func__, hall->name, hall->event); + } + } + } +} +#endif + +#if IS_ENABLED(CONFIG_SEC_FACTORY) +static void hall_ic_work(struct work_struct *work) +{ + struct hall_ic_data *hall = container_of(work, + struct hall_ic_data, dwork.work); + struct hall_ic_drvdata *ddata = gddata; + int first, second, state; + char hall_uevent[20] = {0,}; + char *hall_status[2] = {hall_uevent, NULL}; + + mutex_lock(&gddata->lock); + first = gpio_get_value_cansleep(hall->gpio); + msleep(50); + second = gpio_get_value_cansleep(hall->gpio); + if (first == second) { + hall->state = first; + state = first ^ hall->active_low; + pr_info("%s %s %s\n", __func__, hall->name, + state ? "close" : "open"); + + if (hall->input) { + input_report_switch(hall->input, hall->event, state); + input_sync(hall->input); + } + + /* send uevent for hall ic */ + snprintf(hall_uevent, sizeof(hall_uevent), "%s=%s", + hall->name, state ? "close" : "open"); + kobject_uevent_env(&ddata->sec_dev->kobj, KOBJ_CHANGE, hall_status); + +#if IS_ENABLED(CONFIG_HALL_NOTIFIER) + hall_notifier_notify(hall->name, state); +#endif + hall->hall_count++; + + } else { + pr_info("%s %d,%d\n", hall->name, + first, second); + } + mutex_unlock(&gddata->lock); +} +#else +static void hall_ic_work(struct work_struct *work) +{ + struct hall_ic_data *hall = container_of(work, + struct hall_ic_data, dwork.work); + struct hall_ic_drvdata *ddata = gddata; + int state; + char hall_uevent[20] = {0,}; + char *hall_status[2] = {hall_uevent, NULL}; + + mutex_lock(&gddata->lock); + hall->state = !!gpio_get_value_cansleep(hall->gpio); + state = hall->state ^ hall->active_low; + pr_info("%s %s %s(%d)\n", __func__, hall->name, + state ? "close" : "open", hall->state); + + if (hall->input) { + input_report_switch(hall->input, hall->event, state); + input_sync(hall->input); + } + + /* send uevent for hall ic */ + snprintf(hall_uevent, sizeof(hall_uevent), "%s=%s", + hall->name, state ? "close" : "open"); + kobject_uevent_env(&ddata->sec_dev->kobj, KOBJ_CHANGE, hall_status); + +#if IS_ENABLED(CONFIG_HALL_NOTIFIER) + hall_notifier_notify(hall->name, state); +#endif + mutex_unlock(&gddata->lock); + + hall->hall_count++; + +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) + if (STUI_MODE_TOUCH_SEC & stui_get_mode()) + stui_cancel_session(); +#endif +#if IS_ENABLED(CONFIG_TOUCHSCREEN_DUAL_FOLDABLE) || IS_ENABLED(CONFIG_SEC_INPUT_MULTI_DEVICE) +#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0)) +#if IS_ENABLED(CONFIG_USB_HW_PARAM) + if (strncmp(hall->name, "flip", 4) == 0) { + struct otg_notify *o_notify = get_otg_notify(); + + if (state && o_notify) + inc_hw_param(o_notify, USB_HALL_FOLDING_COUNT); + + } +#endif +#endif +#endif +} +#endif + +#if IS_ENABLED(CONFIG_HALL_NOTIFIER) +void hall_ic_request_notitfy(void) +{ + struct hall_ic_data *hall; + int state; + + list_for_each_entry(hall, &hall_ic_list, list) { + mutex_lock(&gddata->lock); + hall->state = !!gpio_get_value_cansleep(hall->gpio); + state = hall->state ^ hall->active_low; + pr_info("%s %s %s(%d)\n", __func__, + hall->name, state ? "close" : "open", hall->state); + + hall_notifier_notify(hall->name, state); + mutex_unlock(&gddata->lock); + } +} +EXPORT_SYMBOL(hall_ic_request_notitfy); +#endif + +static irqreturn_t hall_ic_detect(int irq, void *dev_id) +{ + struct hall_ic_data *hall = dev_id; + struct hall_ic_pdata *pdata = gddata->pdata; + int state = !!gpio_get_value_cansleep(hall->gpio); + + pr_info("%s %s(%d)\n", __func__, + hall->name, state); + cancel_delayed_work_sync(&hall->dwork); +#if IS_ENABLED(CONFIG_SEC_FACTORY) + schedule_delayed_work(&hall->dwork, msecs_to_jiffies(pdata->debounce_interval)); +#else + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_DUAL_FOLDABLE) || IS_ENABLED(CONFIG_SEC_INPUT_MULTI_DEVICE) + __pm_wakeup_event(hall->ws, HALL_IC_WAKEUP_TIMEOUT); + schedule_delayed_work(&hall->dwork, msecs_to_jiffies(pdata->debounce_interval)); +#else + if (state) { + __pm_wakeup_event(hall->ws, pdata->debounce_interval + 5); + schedule_delayed_work(&hall->dwork, msecs_to_jiffies(pdata->debounce_interval)); + } else { + __pm_relax(hall->ws); + schedule_delayed_work(&hall->dwork, msecs_to_jiffies(pdata->debounce_interval)); + } +#endif +#endif + return IRQ_HANDLED; +} + +static int hall_ic_open(struct input_dev *input) +{ + struct hall_ic_data *hall = input_get_drvdata(input); + + if (gddata->probe_done) + pr_info("%s: %s\n", __func__, hall->name); + else { + pr_info("%s: %s (skip not finished probe_done)\n", __func__, hall->name); + return 0; + } + + schedule_delayed_work(&hall->dwork, HZ / 2); + input_sync(input); + + return 0; +} + +static void hall_ic_close(struct input_dev *input) +{ +} + +static int hall_ic_input_dev_register(struct hall_ic_data *hall) +{ + struct input_dev *input; + int ret = 0; + + input = input_allocate_device(); + if (!input) { + pr_err("failed to allocate state\n"); + return -ENOMEM; + } + + hall->input = input; + input_set_capability(input, EV_SW, hall->event); + input->name = hall->name; + input->phys = hall->name; + input->open = hall_ic_open; + input->close = hall_ic_close; + + input_set_drvdata(input, hall); + + ret = input_register_device(input); + if (ret) { + pr_err("failed to register input device\n"); + return ret; + } + + return 0; +} + +static int hall_ic_setup_halls(struct hall_ic_drvdata *ddata) +{ + struct hall_ic_data *hall; + int ret = 0; + int i = 0; + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + ddata->sec_dev = sec_device_create(ddata, "hall_ic"); + if (IS_ERR(ddata->sec_dev)) + pr_err("%s failed to create hall_ic\n", __func__); +#else + hall_sec_class = class_create(THIS_MODULE, "hall_ic"); + if (unlikely(IS_ERR(hall_sec_class))) { + pr_err("%s %s: Failed to create class(sec) %ld\n", SECLOG, __func__, PTR_ERR(tsp_sec_class)); + return PTR_ERR(tsp_sec_class); + } + ddata->sec_dev = device_create(hall_sec_class, NULL, 17, ddata, "%s", "hall_ic"); + +#endif + sec_hall_ic = ddata->sec_dev; + + ret = sysfs_create_file(&ddata->sec_dev->kobj, &dev_attr_hall_number.attr); + if (ret < 0) + pr_err("failed to create sysfs number ret(%d)\n", ret); + ret = sysfs_create_file(&ddata->sec_dev->kobj, &dev_attr_debounce.attr); + if (ret < 0) + pr_err("failed to create sysfs debounce ret(%d)\n", ret); + ret = sysfs_create_file(&ddata->sec_dev->kobj, &dev_attr_hall_count.attr); + if (ret < 0) + pr_err("failed to create sysfs hall_count ret(%d)\n", ret); + + list_for_each_entry(hall, &hall_ic_list, list) { + hall->state = gpio_get_value_cansleep(hall->gpio); +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 19, 190) //mt6877 : 4, 19, 191 + // 4.19 R + wakeup_source_init(hall->ws, "hall_ic_wlock"); + // 4.19 Q + if (!(hall->ws)) { + hall->ws = wakeup_source_create("hall_ic_wlock"); + if (hall->ws) + wakeup_source_add(hall->ws); + } +#else + hall->ws = wakeup_source_register(NULL, "hall_ic_wlock"); +#endif + INIT_DELAYED_WORK(&hall->dwork, hall_ic_work); + ret = request_threaded_irq(hall->irq, NULL, hall_ic_detect, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + hall->name, hall); + if (ret < 0) + pr_err("failed to request irq %d(%d)\n", + hall->irq, ret); + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + if (!ddata->sec_dev) + continue; + + for (i = 0; i < ARRAY_SIZE(hall_ic_attrs); i++) { + if (!strncmp(hall->name, hall_ic_attrs[i]->attr.name, + strlen(hall->name))) { + ret = sysfs_create_file(&ddata->sec_dev->kobj, + &hall_ic_attrs[i]->attr); + if (ret < 0) + pr_err("failed to create sysfr %d(%d)\n", + hall->irq, ret); + break; + } + } +#endif + } + return ret; +} + +static struct hall_ic_pdata *hall_ic_parsing_dt(struct device *dev) +{ + struct device_node *node = dev->of_node, *pp; + struct hall_ic_pdata *pdata; + struct hall_ic_data *hall; + int nhalls = 0, ret = 0, i = 0; + + if (!node) + return ERR_PTR(-ENODEV); + + nhalls = of_get_child_count(node); + if (nhalls == 0) + return ERR_PTR(-ENODEV); + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + pdata->hall = devm_kzalloc(dev, nhalls * sizeof(*hall), GFP_KERNEL); + if (!pdata->hall) + return ERR_PTR(-ENOMEM); + + if (of_property_read_u32(node, "hall_ic,debounce-interval", &pdata->debounce_interval)) { + pr_info("%s failed to get debounce value, set to default\n", __func__); + pdata->debounce_interval = DEFAULT_DEBOUNCE_INTERVAL; + } + pr_info("%s debounce interval: %d\n", __func__, pdata->debounce_interval); + + pdata->nhalls = nhalls; + for_each_child_of_node(node, pp) { + struct hall_ic_data *hall = &pdata->hall[i++]; + enum of_gpio_flags flags; + + hall->gpio = of_get_gpio_flags(pp, 0, &flags); + if (hall->gpio < 0) { + ret = hall->gpio; + if (ret) { + pr_err("Failed to get gpio flags %d\n", ret); + return ERR_PTR(ret); + } + } + + hall->active_low = flags & OF_GPIO_ACTIVE_LOW; + gpio_direction_input(hall->gpio); + hall->irq = gpio_to_irq(hall->gpio); + hall->name = of_get_property(pp, "name", NULL); + + pr_info("%s flags: %d\n", __func__, flags); + pr_info("%s %s\n", __func__, hall->name); + + if (of_property_read_u32(pp, "event", &hall->event)) { + pr_err("failed to get event: 0x%x\n", hall->event); + return ERR_PTR(-EINVAL); + } +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)) + if (hall->event == 0x15) { /* SW_FLIP */ + hall->event = 0x10; + } else if (hall->event == 0x1b) { /* SW_CERTIFYHALL */ + hall->event = 0x0b; + } else if (hall->event == 0x1e) { /* SW_WACOM_HALL */ + hall->event = 0x0c; + } else if (hall->event == 0x00) { /* SW_FOLDER */ +// continue; + } else { + pr_err("failed to get name, not match event\n"); + return ERR_PTR(-EINVAL); + } +#endif + list_add(&hall->list, &hall_ic_list); + } + return pdata; +} + +static int hall_ic_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct hall_ic_pdata *pdata = dev_get_platdata(dev); + struct hall_ic_drvdata *ddata; + struct hall_ic_data *hall; + int ret = 0; + + if (!pdata) { + pdata = hall_ic_parsing_dt(dev); + if (IS_ERR(pdata)) { + pr_err("%s : fail to get the DT\n", __func__); + goto fail1; + } + } + + ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) { + pr_err("failed to allocate drvdata\n"); + ret = -ENOMEM; + goto fail1; + } + + ddata->pdata = pdata; + device_init_wakeup(&pdev->dev, true); + platform_set_drvdata(pdev, ddata); + mutex_init(&ddata->lock); + gddata = ddata; + + list_for_each_entry(hall, &hall_ic_list, list) { + ret = hall_ic_input_dev_register(hall); + if (ret) { + pr_err("hall_ic_input_dev_register failed %d\n", ret); + goto fail2; + } + + hall->input->dev.parent = &pdev->dev; + } + + ret = hall_ic_setup_halls(ddata); + if (ret) { + pr_err("failed to set up hall : %d\n", ret); + goto fail2; + } + +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) && IS_ENABLED(CONFIG_HALL_DUMP_KEY_MODE) + vbus_notifier_register(&pdata->vbus_nb, sec_hall_vbus_notification, + VBUS_NOTIFY_DEV_CHARGER); + phall = dev; + hall_dump_callbacks.inform_dump = dump_hall_event; +#endif + + ddata->probe_done = true; + pr_info("%s done\n", __func__); + + return 0; + +fail2: + platform_set_drvdata(pdev, NULL); +fail1: + return ret; +} + +static int hall_ic_remove(struct platform_device *pdev) +{ +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) && IS_ENABLED(CONFIG_HALL_DUMP_KEY_MODE) + struct device *dev = &pdev->dev; + struct hall_ic_pdata *pdata = dev_get_platdata(dev); +#endif + struct hall_ic_data *hall; + +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) && IS_ENABLED(CONFIG_HALL_DUMP_KEY_MODE) + vbus_notifier_unregister(&pdata->vbus_nb); +#endif + list_for_each_entry(hall, &hall_ic_list, list) { + input_unregister_device(hall->input); + wakeup_source_unregister(hall->ws); + } + device_init_wakeup(&pdev->dev, 0); + platform_set_drvdata(pdev, NULL); + return 0; +} + +static const struct of_device_id hall_ic_dt_ids[] = { + { .compatible = "hall_ic", }, + { }, +}; +MODULE_DEVICE_TABLE(of, hall_ic_dt_ids); + +static int hall_ic_suspend(struct device *dev) +{ + struct hall_ic_data *hall; + + list_for_each_entry(hall, &hall_ic_list, list) + enable_irq_wake(hall->irq); + + return 0; +} + +static int hall_ic_resume(struct device *dev) +{ + struct hall_ic_data *hall; + + list_for_each_entry(hall, &hall_ic_list, list) { + int state = !!gpio_get_value_cansleep(hall->gpio); + + state ^= hall->active_low; + pr_info("%s %s %s(%d)\n", __func__, hall->name, + state ? "close" : "open", hall->state); + disable_irq_wake(hall->irq); + input_report_switch(hall->input, hall->event, state); + input_sync(hall->input); + } + return 0; +} + +static SIMPLE_DEV_PM_OPS(hall_ic_pm_ops, + hall_ic_suspend, hall_ic_resume); + +static struct platform_driver hall_ic_device_driver = { + .probe = hall_ic_probe, + .remove = hall_ic_remove, + .driver = { + .name = "hall_ic", + .owner = THIS_MODULE, + .pm = &hall_ic_pm_ops, + .of_match_table = hall_ic_dt_ids, + } +}; + +static int __init hall_ic_init(void) +{ + return platform_driver_register(&hall_ic_device_driver); +} + +static void __exit hall_ic_exit(void) +{ + platform_driver_unregister(&hall_ic_device_driver); +} + +late_initcall(hall_ic_init); +module_exit(hall_ic_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Hall IC driver for SEC covers"); diff --git a/drivers/input/misc/hall/hall_ic_logical.c b/drivers/input/misc/hall/hall_ic_logical.c new file mode 100644 index 000000000000..3f321c93834d --- /dev/null +++ b/drivers/input/misc/hall/hall_ic_logical.c @@ -0,0 +1,353 @@ +/* + * + * Copyright 2021 SEC + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if IS_ENABLED(CONFIG_KEYBOARD_STM32_POGO) || IS_ENABLED(CONFIG_KEYBOARD_STM32_POGO_V2) || IS_ENABLED(CONFIG_KEYBOARD_STM32_POGO_V3) +#define POGO_NOTIFIER_ENABLED +#endif + +#ifdef POGO_NOTIFIER_ENABLED +#if IS_ENABLED(CONFIG_KEYBOARD_STM32_POGO_V3) +#include "../../sec_input/stm32/pogo_notifier_v3.h" +#include "../../sec_input/stm32/stm32_pogo_v3.h" +#else +#include +#include "../../sec_input/stm32/stm32_pogo_i2c.h" +#endif +#endif + +/* + * Switch events + */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)) +#define SW_FLIP 0x10 /* set = flip cover open, close*/ +#define SW_HALL_LOGICAL 0x0a /* set = logical hall ic attach/detach */ +#else +#define SW_FLIP 0x15 /* set = flip cover open, close*/ +#define SW_HALL_LOGICAL 0x1f /* set = logical hall ic attach/detach */ +#endif +#define SW_POGO_HALL 0x0d /* set = pogo cover attach/detach */ + +enum LID_POSITION { + E_LID_0 = 1, + E_LID_NORMAL = 2, + E_LID_360 = 3, +}; + +enum LOGICAL_HALL_STATUS { + LOGICAL_HALL_OPEN = 0, + LOGICAL_HALL_CLOSE = 1, + LOGICAL_HALL_BACK = 2, +}; + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) +struct device *hall_logical; +#endif + +struct hall_drvdata { + struct input_dev *input; +#ifdef POGO_NOTIFIER_ENABLED + struct notifier_block pogo_nb; +#endif +}; + +static int hall_logical_status; +static int hall_backflip_status; +static int pogo_cover_status; + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) +static ssize_t hall_logical_detect_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (hall_logical_status == LOGICAL_HALL_OPEN) + sprintf(buf, "OPEN\n"); + else if (hall_logical_status == LOGICAL_HALL_CLOSE) + sprintf(buf, "CLOSE\n"); + else + sprintf(buf, "BACK\n"); + + return strlen(buf); +} +static DEVICE_ATTR_RO(hall_logical_detect); +#endif + +static int hall_logical_open(struct input_dev *input) +{ + /* Report current state of buttons that are connected to GPIOs */ + input_report_switch(input, SW_FLIP, hall_logical_status); + input_sync(input); + + return 0; +} + +static void hall_logical_close(struct input_dev *input) +{ +} + +static int of_hall_data_parsing_dt(struct device *dev, struct hall_drvdata *ddata) +{ + return 0; +} + +#ifdef POGO_NOTIFIER_ENABLED +static int logical_hallic_notifier_handler(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct hall_drvdata *logical_hall_dev = container_of(nb, struct hall_drvdata, pogo_nb); + struct pogo_data_struct pogo_data = *(struct pogo_data_struct *)data; + int hall_status; + + switch (action) { + case POGO_NOTIFIER_ID_DETACHED: + hall_logical_status = LOGICAL_HALL_OPEN; + input_report_switch(logical_hall_dev->input, SW_FLIP, hall_logical_status); + input_sync(logical_hall_dev->input); + break; + case POGO_NOTIFIER_EVENTID_HALL: + if (pogo_data.size != 1) { + pr_info("%s size is wrong. size=%d!\n", __func__, pogo_data.size); + break; + } + + hall_status = *pogo_data.data; + + if (hall_status == E_LID_0) { + hall_logical_status = LOGICAL_HALL_CLOSE; + input_info(true, &logical_hall_dev->input->dev, "%s hall_status = %d (CLOSE)\n", __func__, hall_status); + input_report_switch(logical_hall_dev->input, SW_FLIP, hall_logical_status); + input_sync(logical_hall_dev->input); + } else if (hall_status == E_LID_NORMAL) { + hall_logical_status = LOGICAL_HALL_OPEN; + hall_backflip_status = 0; + input_info(true, &logical_hall_dev->input->dev, "%s hall_status = %d (NORMAL)\n", __func__, hall_status); + input_report_switch(logical_hall_dev->input, SW_FLIP, hall_logical_status); + input_report_switch(logical_hall_dev->input, SW_HALL_LOGICAL, hall_backflip_status); + input_sync(logical_hall_dev->input); + } else if (hall_status == E_LID_360) { + hall_backflip_status = 1; + input_info(true, &logical_hall_dev->input->dev, "%s hall_status = %d (BACK)\n", __func__, hall_status); + input_report_switch(logical_hall_dev->input, SW_HALL_LOGICAL, hall_backflip_status); + input_sync(logical_hall_dev->input); + } + break; +#if IS_ENABLED(CONFIG_KEYBOARD_STM32_POGO_V3) + case POGO_NOTIFIER_EVENTID_ACESSORY: + if (pogo_data.size != 2) { + pr_info("%s size is wrong. size=%d!\n", __func__, pogo_data.size); + break; + } + pogo_cover_status = pogo_data.data[1]; + input_info(true, &logical_hall_dev->input->dev, "%s pogo_cover_status = %d\n", + __func__, pogo_cover_status); + input_report_switch(logical_hall_dev->input, SW_POGO_HALL, pogo_cover_status); + input_sync(logical_hall_dev->input); + break; +#endif + default: + break; + }; + + return NOTIFY_DONE; +} +#endif + +static int hall_logical_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct hall_drvdata *ddata; + struct input_dev *input; + int error; + int wakeup = 0; + + pr_info("%s called", __func__); + ddata = kzalloc(sizeof(struct hall_drvdata), GFP_KERNEL); + if (!ddata) { + dev_err(dev, "failed to allocate state\n"); + return -ENOMEM; + } + + if (dev->of_node) { + error = of_hall_data_parsing_dt(dev, ddata); + if (error < 0) { + pr_info("%s : fail to get the dt (HALL)\n", __func__); + goto fail1; + } + } + + input = input_allocate_device(); + if (!input) { + dev_err(dev, "failed to allocate state\n"); + error = -ENOMEM; + goto fail1; + } + + ddata->input = input; + + platform_set_drvdata(pdev, ddata); + input_set_drvdata(input, ddata); + + input->name = "hall_logical"; + input->phys = "hall_logical"; + input->dev.parent = &pdev->dev; + + input->evbit[0] |= BIT_MASK(EV_SW); + input_set_capability(input, EV_SW, SW_FLIP); + input_set_capability(input, EV_SW, SW_HALL_LOGICAL); + input_set_capability(input, EV_SW, SW_POGO_HALL); + + input->open = hall_logical_open; + input->close = hall_logical_close; + + /* Enable auto repeat feature of Linux input subsystem */ + __set_bit(EV_REP, input->evbit); + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + hall_logical = sec_device_create(ddata, "hall_logical"); + if (IS_ERR(hall_logical)) { + dev_err(dev, "%s: failed to create device for the sysfs\n",__func__); + } + + error = device_create_file(hall_logical, &dev_attr_hall_logical_detect); + if (error < 0) { + pr_err("Failed to create device file(%s)!, error: %d\n", + dev_attr_hall_logical_detect.attr.name, error); + } +#endif + error = input_register_device(input); + if (error) { + dev_err(dev, "Unable to register input device, error: %d\n", + error); + goto fail1; + } + + device_init_wakeup(&pdev->dev, wakeup); + +#ifdef POGO_NOTIFIER_ENABLED + pogo_notifier_register(&ddata->pogo_nb, + logical_hallic_notifier_handler, POGO_NOTIFY_DEV_HALLIC); +#endif + + input_report_switch(input, SW_FLIP, hall_logical_status); + input_report_switch(input, SW_HALL_LOGICAL, hall_backflip_status); + input_report_switch(input, SW_POGO_HALL, pogo_cover_status); + input_info(true, dev, "%s hall_status = %d backflip_status = %d pogo_cover_status = %d\n", + __func__, hall_logical_status, hall_backflip_status, pogo_cover_status); + + pr_info("%s end", __func__); + return 0; + + fail1: + kfree(ddata); + + return error; +} + +static int hall_logical_remove(struct platform_device *pdev) +{ + struct hall_drvdata *ddata = platform_get_drvdata(pdev); + struct input_dev *input = ddata->input; + + pr_info("%s start\n", __func__); + + device_init_wakeup(&pdev->dev, 0); + + input_unregister_device(input); +#ifdef POGO_NOTIFIER_ENABLED + pogo_notifier_unregister(&ddata->pogo_nb); +#endif + + kfree(ddata); + + return 0; +} + +static const struct of_device_id hall_logical_dt_ids[] = { + { .compatible = "hall_logical" }, + { }, +}; +MODULE_DEVICE_TABLE(of, hall_logical_dt_ids); + +static int hall_logical_suspend(struct device *dev) +{ + struct hall_drvdata *ddata = dev_get_drvdata(dev); + struct input_dev *input = ddata->input; + + pr_info("%s start\n", __func__); + + if (device_may_wakeup(dev)) { + } else { + mutex_lock(&input->mutex); + if (input->users) + hall_logical_close(input); + mutex_unlock(&input->mutex); + } + + return 0; +} + +static int hall_logical_resume(struct device *dev) +{ + struct hall_drvdata *ddata = dev_get_drvdata(dev); + struct input_dev *input = ddata->input; + + pr_info("%s start\n", __func__); + input_sync(input); + return 0; +} + +static SIMPLE_DEV_PM_OPS(hall_logical_pm_ops, hall_logical_suspend, hall_logical_resume); + +static struct platform_driver hall_device_driver = { + .probe = hall_logical_probe, + .remove = hall_logical_remove, + .driver = { + .name = "hall_logical", + .owner = THIS_MODULE, + .pm = &hall_logical_pm_ops, + .of_match_table = hall_logical_dt_ids, + } +}; + +int hall_logical_init(void) +{ + pr_info("%s start\n", __func__); + + return platform_driver_probe(&hall_device_driver, hall_logical_probe); +} +EXPORT_SYMBOL(hall_logical_init); + + +void hall_logical_exit(void) +{ + pr_info("%s start\n", __func__); + platform_driver_unregister(&hall_device_driver); +} +EXPORT_SYMBOL(hall_logical_exit); + +MODULE_AUTHOR("Samsung"); +MODULE_DESCRIPTION("Hall IC logical driver for GPIOs"); diff --git a/drivers/input/misc/hall/hall_ic_notifier.c b/drivers/input/misc/hall/hall_ic_notifier.c new file mode 100644 index 000000000000..d0921a4df6bf --- /dev/null +++ b/drivers/input/misc/hall/hall_ic_notifier.c @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2019 Samsung Electronics + */ + +#if IS_ENABLED(CONFIG_HALL_NOTIFIER) +#include +#include + +static struct hall_notifier_context hall_notifier; +static struct blocking_notifier_head hall_nb_head = BLOCKING_NOTIFIER_INIT(hall_nb_head); + +int hall_notifier_register(struct notifier_block *n) +{ + int ret = 0; + + pr_info("%s\n", __func__); + + ret = blocking_notifier_chain_register(&hall_nb_head, n); + if (ret < 0) + pr_err("%s: failed(%d)\n", __func__, ret); + + return ret; +} +EXPORT_SYMBOL(hall_notifier_register); + +int hall_notifier_unregister(struct notifier_block *nb) +{ + int ret = 0; + + pr_info("%s\n", __func__); + + ret = blocking_notifier_chain_unregister(&hall_nb_head, nb); + if (ret < 0) + pr_err("%s: failed(%d)\n", __func__, ret); + + return ret; +} +EXPORT_SYMBOL(hall_notifier_unregister); + +int hall_notifier_notify(const char *hall_name, int hall_value) +{ + int ret = 0; + + pr_info("%s: name: %s value: %d\n", __func__, hall_name, hall_value); + + hall_notifier.name = hall_name; + hall_notifier.value = hall_value; + + ret = blocking_notifier_call_chain(&hall_nb_head, hall_value, &hall_notifier); + + switch (ret) { + case NOTIFY_DONE: + case NOTIFY_OK: + pr_info("%s done(0x%x)\n", __func__, ret); + break; + default: + pr_info("%s failed(0x%x)\n", __func__, ret); + break; + } + + return ret; +} +EXPORT_SYMBOL(hall_notifier_notify); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Hall IC notifier"); + +#endif /* if IS_ENABLED(CONFIG_HALL_NOTIFIER) */ diff --git a/drivers/input/misc/hall/sec_hall_dumpkey.c b/drivers/input/misc/hall/sec_hall_dumpkey.c new file mode 100644 index 000000000000..19a9c173a509 --- /dev/null +++ b/drivers/input/misc/hall/sec_hall_dumpkey.c @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Samsung TN debugging code + * + * 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. + */ + +#include +#include +#include +#include +#include + +#include <../../sec_input/sec_input.h> +#include + +static DEFINE_SPINLOCK(sec_hall_dumpkey_event_lock); +static atomic_t sec_hall_dumpkey_acceptable_event[KEY_MAX] __read_mostly; + +#ifndef ARRAYSIZE +#define ARRAYSIZE(a) (sizeof(a) / sizeof(a[0])) +#endif + +#define DUMP_COUNT_FIRST 1 +#define DUMP_COUNT_SECOND 2 + +#define KEY_STATE_DOWN 1 +#define KEY_STATE_UP 0 + +struct hall_dump_callbacks hall_dump_callbacks; +EXPORT_SYMBOL(hall_dump_callbacks); + +struct device *phall; +EXPORT_SYMBOL(phall); + +static unsigned int __hall_dump_keys[] = { + KEY_POWER, + KEY_VOLUMEUP, + KEY_VOLUMEDOWN, + KEY_HOMEPAGE, + KEY_HOT, + KEY_EMERGENCY, + KEY_BACK, + KEY_RECENT +}; + +struct dump_key { + unsigned int key_code; + unsigned int dump_count; +}; + +struct dump_key hall_dump_key_combination[] = { + {KEY_VOLUMEUP, DUMP_COUNT_FIRST}, + {KEY_VOLUMEDOWN, DUMP_COUNT_SECOND}, +}; + +struct hall_dump_key_state { + unsigned int key_code; + unsigned int state; +}; + +struct hall_dump_key_state hall_dump_key_states[] = { + {KEY_VOLUMEDOWN, KEY_STATE_UP}, + {KEY_VOLUMEUP, KEY_STATE_UP}, + {KEY_POWER, KEY_STATE_UP}, + {KEY_HOMEPAGE, KEY_STATE_UP}, + {KEY_HOT, KEY_STATE_UP}, + {KEY_EMERGENCY, KEY_STATE_UP}, + {KEY_BACK, KEY_STATE_UP}, + {KEY_RECENT, KEY_STATE_UP}, +}; + +static unsigned int hold_key = KEY_POWER; +static unsigned int hold_key_hold = KEY_STATE_UP; +static unsigned int check_count; +static unsigned int check_step; + +static int is_hold_key(unsigned int code) +{ + return (code == hold_key); +} + +static void set_hold_key_hold(int state) +{ + hold_key_hold = state; +} + +static unsigned int is_hold_key_hold(void) +{ + return hold_key_hold; +} + +static unsigned int get_current_step_key_code(void) +{ + return hall_dump_key_combination[check_step].key_code; +} + +static int is_key_matched_for_current_step(unsigned int code) +{ + return (code == get_current_step_key_code()); +} + +static int is_dump_keys(unsigned int code) +{ + unsigned long i; + + for (i = 0; i < ARRAY_SIZE(hall_dump_key_states); i++) + if (hall_dump_key_states[i].key_code == code) + return 1; + return 0; +} + +static int get_count_for_next_step(void) +{ + int i; + int count = 0; + + for (i = 0; i < check_step + 1; i++) + count += hall_dump_key_combination[i].dump_count; + return count; +} + +static int is_reaching_count_for_next_step(void) +{ + return (check_count == get_count_for_next_step()); +} + +static int get_count_for_dump(void) +{ + unsigned long i; + int count = 0; + + for (i = 0; i < ARRAY_SIZE(hall_dump_key_combination); i++) + count += hall_dump_key_combination[i].dump_count; + return count - 1; +} + +static unsigned int is_key_state_down(unsigned int code) +{ + unsigned long i; + + if (is_hold_key(code)) + return is_hold_key_hold(); + + for (i = 0; i < ARRAY_SIZE(hall_dump_key_states); i++) + if (hall_dump_key_states[i].key_code == code) + return hall_dump_key_states[i].state == KEY_STATE_DOWN; + return 0; +} + +static void set_key_state_down(unsigned int code) +{ + unsigned long i; + + if (is_hold_key(code)) + set_hold_key_hold(KEY_STATE_DOWN); + + for (i = 0; i < ARRAY_SIZE(hall_dump_key_states); i++) + if (hall_dump_key_states[i].key_code == code) + hall_dump_key_states[i].state = KEY_STATE_DOWN; +} + +static void set_key_state_up(unsigned int code) +{ + unsigned long i; + + if (is_hold_key(code)) + set_hold_key_hold(KEY_STATE_UP); + + for (i = 0; i < ARRAY_SIZE(hall_dump_key_states); i++) + if (hall_dump_key_states[i].key_code == code) + hall_dump_key_states[i].state = KEY_STATE_UP; +} + +static void increase_step(void) +{ + if (check_step < ARRAY_SIZE(hall_dump_key_combination)) + check_step++; + else if (hall_dump_callbacks.inform_dump) + hall_dump_callbacks.inform_dump(phall); +} + +static void reset_step(void) +{ + check_step = 0; +} + +static void increase_count(void) +{ + if (check_count < get_count_for_dump()) + check_count++; + else if (hall_dump_callbacks.inform_dump) + hall_dump_callbacks.inform_dump(phall); +} + +static void reset_count(void) +{ + check_count = 0; +} + +static void check_hall_dump_keys(struct sec_hall_dumpkey_param *param) +{ + unsigned int code = param->keycode; + int state = param->down; + + if (!is_dump_keys(code)) + return; + + if (state == KEY_STATE_DOWN) { + /* Duplicated input */ + if (is_key_state_down(code)) + return; + set_key_state_down(code); + + if (is_hold_key(code)) { + set_hold_key_hold(KEY_STATE_DOWN); + return; + } + if (is_hold_key_hold()) { + if (is_key_matched_for_current_step(code)) { + increase_count(); + } else { + reset_count(); + reset_step(); + } + if (is_reaching_count_for_next_step()) + increase_step(); + } + + } else { + set_key_state_up(code); + if (is_hold_key(code)) { + set_hold_key_hold(KEY_STATE_UP); + reset_step(); + reset_count(); + } + } + +} + +static void inline update_acceptable_event(unsigned int event_code, bool is_add) +{ + if (is_add) + atomic_inc(&(sec_hall_dumpkey_acceptable_event[event_code])); + else + atomic_dec(&(sec_hall_dumpkey_acceptable_event[event_code])); +} + +static inline bool is_event_supported(unsigned int event_type, + unsigned int event_code) +{ + bool ret; + + if (event_type != EV_KEY || event_code >= KEY_MAX) + return false; + + ret = !!atomic_read(&(sec_hall_dumpkey_acceptable_event[event_code])); + + return ret; +} + +static void sec_hall_dumpkey_event(struct input_handle *handle, unsigned int event_type, + unsigned int event_code, int value) +{ + struct sec_hall_dumpkey_param param = { + .keycode = event_code, + .down = value, + }; + + if (!is_event_supported(event_type, event_code)) + return; + + //pr_info("%s key_event: %u, %d\n", SECLOG, event_code, value); + + spin_lock(&sec_hall_dumpkey_event_lock); + + check_hall_dump_keys(¶m); + + spin_unlock(&sec_hall_dumpkey_event_lock); +} + +static int sec_hall_dumpkey_connect(struct input_handler *handler, struct input_dev *dev, + const struct input_device_id *id) +{ + struct input_handle *handle; + int error; + + handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = "sec_hall_dumpkey"; + + error = input_register_handle(handle); + if (error) + goto err_free_handle; + + error = input_open_device(handle); + if (error) + goto err_unregister_handle; + + return 0; + +err_unregister_handle: + input_unregister_handle(handle); +err_free_handle: + kfree(handle); + return error; +} + +static void sec_hall_dumpkey_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +static const struct input_device_id sec_hall_dumpkey_ids[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT, + .evbit = { BIT_MASK(EV_KEY) }, + }, + {}, +}; + +static struct input_handler sec_hall_dumpkey_handler = { + .event = sec_hall_dumpkey_event, + .connect = sec_hall_dumpkey_connect, + .disconnect = sec_hall_dumpkey_disconnect, + .name = "sec_hall_dumpkey", + .id_table = sec_hall_dumpkey_ids, +}; + +static int __init sec_hall_dumpkey_init(void) +{ + int err; + size_t i; + + pr_info("%s %s\n", SECLOG, __func__); + + for (i = 0; i < KEY_MAX; i++) + atomic_set(&(sec_hall_dumpkey_acceptable_event[i]), 0); + + for (i = 0; i < ARRAY_SIZE(__hall_dump_keys); i++) + update_acceptable_event(__hall_dump_keys[i], true); + + spin_lock_init(&sec_hall_dumpkey_event_lock); + + err = input_register_handler(&sec_hall_dumpkey_handler); + + return err; + +} + +static void __exit sec_hall_dumpkey_exit(void) +{ + input_unregister_handler(&sec_hall_dumpkey_handler); +} + +module_init(sec_hall_dumpkey_init); +module_exit(sec_hall_dumpkey_exit); + +MODULE_DESCRIPTION("Samsung hall dumpkey driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/pm8941-pwrkey.c b/drivers/input/misc/pm8941-pwrkey.c index ad3e360e23db..5f4e194639a4 100644 --- a/drivers/input/misc/pm8941-pwrkey.c +++ b/drivers/input/misc/pm8941-pwrkey.c @@ -20,6 +20,13 @@ #include #include #include +#if IS_ENABLED(CONFIG_SEC_PM) +#include + +#define PON_PBS_KPDPWR_S2_CNTL2 0x43 +#define PON_PBS_KPDPWR_RESIN_S2_CNTL2 0x4B +#define PON_PBS_S2_CNTL_EN BIT(7) +#endif #define PON_REV2 0x01 @@ -87,8 +94,77 @@ struct pm8941_pwrkey { bool pull_up; bool log_kpd_event; const struct pm8941_data *data; + +#if IS_ENABLED(CONFIG_SEC_PM) + int wake_enabled; +#endif }; +#if IS_ENABLED(CONFIG_SEC_PM) +static struct _sec_pon { + struct pm8941_pwrkey *key_dev[SEC_PON_KEY_CNT]; + struct pm8941_pwrkey *reset_dev; +} sec_pon; + +int sec_get_s2_reset(enum sec_pon_type type) +{ + int rc; + unsigned int en; + struct pm8941_pwrkey *pwrkey = sec_pon.reset_dev; + unsigned int offset; + + if (type != SEC_PON_KPDPWR && type != SEC_PON_KPDPWR_RESIN) + return -EINVAL; + if (!pwrkey) + return -ENXIO; + + offset = (type == SEC_PON_KPDPWR) ? + PON_PBS_KPDPWR_S2_CNTL2 : PON_PBS_KPDPWR_RESIN_S2_CNTL2; + + rc = regmap_read(pwrkey->regmap, + pwrkey->pon_pbs_baseaddr + offset, &en); + if (rc) { + pr_err("Unable to get S2 reset on/off for type %d\n", type); + return rc; + } + + return (en & PON_PBS_S2_CNTL_EN) ? true : false; +} +EXPORT_SYMBOL_GPL(sec_get_s2_reset); + +// Key wakeup enable for (SEC_PON_KPDPWR, SEC_PON_RESIN) +int sec_set_pm_key_wk_init(enum sec_pon_type type, int en) +{ + struct pm8941_pwrkey *pwrkey; + + if (type != SEC_PON_KPDPWR && type != SEC_PON_RESIN) + return -EINVAL; + + pwrkey = sec_pon.key_dev[type]; + if (!pwrkey) + return -ENXIO; + + if (pwrkey->wake_enabled != en) { + pwrkey->wake_enabled = en; + pr_info("%s: wk_int (type:%d, en:%d)\n", __func__, type, en); + } + else { + pr_info("%s: already wk_int (type:%d, en:%d)\n", __func__, type, en); + } + return 0; +} +EXPORT_SYMBOL_GPL(sec_set_pm_key_wk_init); + +int sec_get_pm_key_wk_init(enum sec_pon_type type) +{ + if (type != SEC_PON_KPDPWR && type != SEC_PON_RESIN) + return -EINVAL; + + return sec_pon.key_dev[type]->wake_enabled; +} +EXPORT_SYMBOL_GPL(sec_get_pm_key_wk_init); +#endif + static int pm8941_reboot_notify(struct notifier_block *nb, unsigned long code, void *unused) { @@ -318,8 +394,15 @@ static int pm8941_pwrkey_suspend(struct device *dev) return pm8941_pwrkey_freeze(dev); if (device_may_wakeup(dev)) + { +#if IS_ENABLED(CONFIG_SEC_PM) + if (!pwrkey->wake_enabled) { + pr_info("%s: enable_irq_wake skip by sec_ap_pmic\n", pwrkey->input->name); + return 0; + } +#endif enable_irq_wake(pwrkey->irq); - + } return 0; } @@ -331,7 +414,15 @@ static int pm8941_pwrkey_resume(struct device *dev) return pm8941_pwrkey_restore(dev); if (device_may_wakeup(dev)) + { +#if IS_ENABLED(CONFIG_SEC_PM) + if (!pwrkey->wake_enabled) { + pr_info("%s: disable_irq_wake skip by sec_ap_pmic\n", pwrkey->input->name); + return 0; + } +#endif disable_irq_wake(pwrkey->irq); + } return 0; } @@ -497,6 +588,22 @@ static int pm8941_pwrkey_probe(struct platform_device *pdev) platform_set_drvdata(pdev, pwrkey); device_init_wakeup(&pdev->dev, 1); +#if IS_ENABLED(CONFIG_SEC_PM) + if (pwrkey->data->has_pon_pbs) { + if (!strncmp(pwrkey->data->name, "pmic_pwrkey", 11)) { + pr_info("%s: pon_kpdpwr", __func__); + sec_pon.key_dev[SEC_PON_KPDPWR] = pwrkey; + sec_pon.reset_dev = pwrkey; + pm8941_pwrkey_irq(pwrkey->irq, pwrkey); + } else if (!strncmp(pwrkey->data->name, "pmic_resin", 10)) { + pr_info("%s: pon_resin", __func__); + sec_pon.key_dev[SEC_PON_RESIN] = pwrkey; + pm8941_pwrkey_irq(pwrkey->irq, pwrkey); + } + } + pwrkey->wake_enabled = true; +#endif + return 0; } diff --git a/drivers/input/sec_input/Kconfig b/drivers/input/sec_input/Kconfig new file mode 100644 index 000000000000..656e6b86c543 --- /dev/null +++ b/drivers/input/sec_input/Kconfig @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# SEC Input device configuration +# + +config INPUT_SEC_INPUT + tristate "Support for SEC input layer" + help + Say Y here if you want to use sec cmd. + +config SEC_DEBUG_TSP_LOG + tristate "Support for tsp_msg" + help + Say Y here if you want to use tsp_msg node. + +config INPUT_TOUCHSCREEN_TCLMV2 + tristate "touchscreen tclmv2" + help + Say Y here if you want to use tclm. + +config INPUT_SEC_SECURE_TOUCH + tristate "input sec secure touch" + depends on INPUT + help + Say Y here if you need to use sec secure touch. + +config INPUT_SEC_TRUSTED_TOUCH + tristate "input sec secure touch" + depends on INPUT + help + Say Y here if you need to use sec secure touch. + +config TOUCHSCREEN_DUMP_MODE + tristate "touchscreen dump mode" + help + Say Y here if you need to get raw data. + +config INPUT_SEC_NOTIFIER + tristate "input sec notifier" + help + Say Y here if you need to use input notifier. + +config SEC_INPUT_MULTI_DEVICE + tristate "Multi device for Foldable" + help + Say Y here if you need to use Multi device. + +config SEC_INPUT_CMD_TEST + tristate "KUnit test for tsp_common_test" + depends on SEC_KUNIT + help + TODO: Describe config fully. + +config SEC_INPUT_RAWDATA + tristate "input rawdata" + help + Say Y hear if you need to use rawdata. + diff --git a/drivers/input/sec_input/Makefile b/drivers/input/sec_input/Makefile new file mode 100644 index 000000000000..3480ff5094e4 --- /dev/null +++ b/drivers/input/sec_input/Makefile @@ -0,0 +1,44 @@ +TARGET = sec_common_fn + +$(TARGET)-objs := sec_input.o sec_cmd.o + +ifneq ($(filter y m, $(CONFIG_SEC_DEBUG_TSP_LOG)),) + $(TARGET)-objs += sec_tsp_log.o +endif +ifneq ($(filter y m, $(CONFIG_INPUT_TOUCHSCREEN_TCLMV2)),) + $(TARGET)-objs += sec_tclm_v2.o +endif +ifneq ($(filter y m, $(CONFIG_INPUT_SEC_SECURE_TOUCH)),) + $(TARGET)-objs += sec_secure_touch.o +endif +ifneq ($(filter y m, $(CONFIG_INPUT_SEC_TRUSTED_TOUCH)),) + $(TARGET)-objs += sec_trusted_touch.o +endif +ifneq ($(filter y m, $(CONFIG_TOUCHSCREEN_DUMP_MODE)),) + $(TARGET)-objs += sec_tsp_dumpkey.o +endif +ifneq ($(filter y m, $(CONFIG_SEC_INPUT_RAWDATA)),) + $(TARGET)-objs += sec_input_rawdata.o +endif +ifneq ($(filter y m, $(CONFIG_SEC_INPUT_MULTI_DEVICE)),) + $(TARGET)-objs += sec_input_multi_dev.o +endif + +obj-$(CONFIG_INPUT_SEC_INPUT) += $(TARGET).o + +# sec_input_notifier need to be separated to avoid dependency cycle error by other modules +obj-$(CONFIG_INPUT_SEC_NOTIFIER) += sec_input_notifier.o + +ifeq ($(CONFIG_SEC_KUNIT), y) +endif + +ccflags-y += -Wformat + +GCOV_PROFILE_sec_input.o := $(CONFIG_SEC_KUNIT) +GCOV_PROFILE_sec_cmd.o := $(CONFIG_SEC_KUNIT) +GCOV_PROFILE_sec_tsp_log.o := $(CONFIG_SEC_KUNIT) +GCOV_PROFILE_sec_tclm_v2.o := $(CONFIG_SEC_KUNIT) +GCOV_PROFILE_sec_tsp_dumpkey.o := $(CONFIG_SEC_KUNIT) + +subdir-ccflags-$(CONFIG_SEC_KUNIT) += \ + -Wno-format-zero-length diff --git a/drivers/input/sec_input/sec_cmd.c b/drivers/input/sec_input/sec_cmd.c new file mode 100644 index 000000000000..468223f20ea8 --- /dev/null +++ b/drivers/input/sec_input/sec_cmd.c @@ -0,0 +1,1295 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2014 Samsung Electronics Co., Ltd. + * + * 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. + */ + +#include "sec_cmd.h" +#include "sec_input.h" +#include "sec_tsp_log.h" + +struct class *tsp_sec_class; + +#if IS_ENABLED(CONFIG_SEC_KUNIT) +__visible_for_testing struct sec_cmd_data *kunit_sec; +EXPORT_SYMBOL(kunit_sec); +#else +#define __visible_for_testing static +#endif + +const char *str_power_state[3] = { "OFF", "LP", "ON" }; +const char *str_use_case[CHECK_ALL + 1] = { + "NONE", // 0 + "OFF", // 1 CHECK_POWEROFF + "LP", // 2 CHECK_LPMODE + "LP/OFF", // 3 CHECK_POWEROFF | CHECK_LPMODE + "ON", // 4 CHECK_POWERON + "ON/OFF", // 5 CHECK_POWEROFF | CHECK_POWERON + "ON/LP", // 6 CHECK_ON_LP + "ON/LP/OFF", // 7 CHECK_ALL +}; + +#ifdef USE_SEC_CMD_QUEUE +static void cmd_store_function(struct sec_cmd_data *data); +void sec_cmd_execution(struct sec_cmd_data *data, bool lock) +{ + if (lock) + mutex_lock(&data->fs_lock); + + /* check lock */ + mutex_lock(&data->cmd_lock); + atomic_set(&data->cmd_is_running, 1); + mutex_unlock(&data->cmd_lock); + + data->cmd_state = SEC_CMD_STATUS_RUNNING; + cmd_store_function(data); + + if (lock) + mutex_unlock(&data->fs_lock); +} +#endif + +void sec_cmd_set_cmd_exit(struct sec_cmd_data *data) +{ +#ifdef USE_SEC_CMD_QUEUE + mutex_lock(&data->fifo_lock); + if (kfifo_len(&data->cmd_queue)) { + pr_info("%s: %s %s: do next cmd, left cmd[%d]\n", dev_name(data->fac_dev), SECLOG, __func__, + (int)(kfifo_len(&data->cmd_queue) / sizeof(struct command))); + mutex_unlock(&data->fifo_lock); + + sec_cmd_execution(data, false); + } else { + mutex_unlock(&data->fifo_lock); + + mutex_lock(&data->cmd_lock); + atomic_set(&data->cmd_is_running, 0); + mutex_unlock(&data->cmd_lock); + } + if (data->wait_cmd_result_done) + complete_all(&data->cmd_result_done); +#else + mutex_lock(&data->cmd_lock); + atomic_set(&data->cmd_is_running, 0); + mutex_unlock(&data->cmd_lock); +#endif +} +EXPORT_SYMBOL(sec_cmd_set_cmd_exit); + +#ifdef USE_SEC_CMD_QUEUE +static void cmd_exit_work(struct work_struct *work) +{ + struct sec_cmd_data *data = container_of(work, struct sec_cmd_data, cmd_work.work); + + sec_cmd_execution(data, true); +} +#endif + +void sec_cmd_set_default_result(struct sec_cmd_data *data) +{ + char *delim = ":"; + + memset(data->cmd_result, 0x00, SEC_CMD_RESULT_STR_LEN_EXPAND); + memcpy(data->cmd_result, data->cmd, SEC_CMD_STR_LEN); + strlcat(data->cmd_result, delim, SEC_CMD_RESULT_STR_LEN_EXPAND); +} +EXPORT_SYMBOL(sec_cmd_set_default_result); + +void sec_cmd_set_cmd_result_all(struct sec_cmd_data *data, char *buff, int len, char *item) +{ + char *delim1 = " "; + char *delim2 = ":"; + int cmd_result_len; + + cmd_result_len = (int)strlen(data->cmd_result_all) + len + 2 + (int)strlen(item); + + if (cmd_result_len >= SEC_CMD_RESULT_STR_LEN) { + pr_err("%s: %s %s: cmd length is over (%d)!!", dev_name(data->fac_dev), SECLOG, __func__, cmd_result_len); + return; + } + + data->item_count++; + strlcat(data->cmd_result_all, delim1, sizeof(data->cmd_result_all)); + strlcat(data->cmd_result_all, item, sizeof(data->cmd_result_all)); + strlcat(data->cmd_result_all, delim2, sizeof(data->cmd_result_all)); + strlcat(data->cmd_result_all, buff, sizeof(data->cmd_result_all)); +} +EXPORT_SYMBOL(sec_cmd_set_cmd_result_all); + +void sec_cmd_set_cmd_result(struct sec_cmd_data *data, char *buff, int len) +{ + if (strlen(buff) >= (unsigned int)SEC_CMD_RESULT_STR_LEN_EXPAND) { + pr_err("%s %s: cmd length is over (%d)!!", SECLOG, __func__, (int)strlen(buff)); + strlcat(data->cmd_result, "NG", SEC_CMD_RESULT_STR_LEN_EXPAND); + return; + } + + data->cmd_result_expand = (int)strlen(buff) / SEC_CMD_RESULT_STR_LEN; + data->cmd_result_expand_count = 0; + + strlcat(data->cmd_result, buff, SEC_CMD_RESULT_STR_LEN_EXPAND); +} +EXPORT_SYMBOL(sec_cmd_set_cmd_result); + +void sec_cmd_check_store_condition(struct sec_cmd_data *data, struct sec_cmd *sec_cmd_ptr) +{ + struct sec_ts_plat_data *plat_data = data->dev->platform_data; + int prev_result_len = 0; + + pr_info("%s %s power_state:%s, check_power:%s%s, wait_result:%d\n", + dev_name(data->fac_dev), SECLOG, str_power_state[atomic_read(&plat_data->power_state)], + str_use_case[sec_cmd_ptr->cmd_use_cases], + sec_cmd_ptr->cmd_func_forced ? "(force func)" : "", + sec_cmd_ptr->wait_read_result); + + sec_cmd_set_default_result(data); + prev_result_len = (int)strlen(data->cmd_result); + + if (sec_input_cmp_ic_status(data->dev, sec_cmd_ptr->cmd_use_cases)) { + sec_cmd_ptr->cmd_func(data); + } else { + if (sec_cmd_ptr->cmd_func_forced) + sec_cmd_ptr->cmd_func_forced(data); + else + goto CMD_NG; + } + + if (prev_result_len == (int)strlen(data->cmd_result)) { + if ((data->cmd_state == SEC_CMD_STATUS_WAITING) || (data->cmd_state == SEC_CMD_STATUS_OK)) + strlcat(data->cmd_result, "OK", SEC_CMD_RESULT_STR_LEN_EXPAND); + else if (data->cmd_state == SEC_CMD_STATUS_NOT_APPLICABLE) + strlcat(data->cmd_result, "NA", SEC_CMD_RESULT_STR_LEN_EXPAND); + else + strlcat(data->cmd_result, "NG", SEC_CMD_RESULT_STR_LEN_EXPAND); + } + + if (sec_cmd_ptr->wait_read_result == EXIT_RESULT) { + data->cmd_state = SEC_CMD_STATUS_WAITING; + sec_cmd_set_cmd_exit(data); + } + + return; +CMD_NG: + data->cmd_state = SEC_CMD_STATUS_FAIL; + strlcat(data->cmd_result, "NG", SEC_CMD_RESULT_STR_LEN_EXPAND); + + if (sec_cmd_ptr->wait_read_result == EXIT_RESULT) + sec_cmd_set_cmd_exit(data); +} + +#ifndef USE_SEC_CMD_QUEUE +__visible_for_testing ssize_t cmd_store(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count) +{ + struct sec_cmd_data *data = dev_get_drvdata(dev); + char *cur, *start, *end; + char buff[SEC_CMD_STR_LEN] = { 0 }; + int len, i; + struct sec_cmd *sec_cmd_ptr = NULL; + char delim = ','; + bool cmd_found = false; + int param_cnt = 0; + + if (!data) { + pr_err("%s %s: No platform data found\n", SECLOG, __func__); + return -EINVAL; + } + + if (strnlen(buf, SEC_CMD_STR_LEN) >= SEC_CMD_STR_LEN) { + pr_err("%s: %s %s: cmd length(strlen(buf)) is over (%d,%s)!!\n", + dev_name(data->fac_dev), SECLOG, __func__, (int)strlen(buf), buf); + return -EINVAL; + } + + if (count >= (unsigned int)SEC_CMD_STR_LEN) { + pr_err("%s: %s %s: cmd length(count) is over (%d,%s)!!\n", + dev_name(data->fac_dev), SECLOG, __func__, (unsigned int)count, buf); + return -EINVAL; + } + + if (atomic_read(&data->cmd_is_running)) { + pr_err("%s: %s %s: other cmd is running.\n", dev_name(data->fac_dev), SECLOG, __func__); + return -EBUSY; + } + + /* check lock */ + mutex_lock(&data->cmd_lock); + atomic_set(&data->cmd_is_running, 1); + mutex_unlock(&data->cmd_lock); + + data->cmd_state = SEC_CMD_STATUS_RUNNING; + for (i = 0; i < ARRAY_SIZE(data->cmd_param); i++) + data->cmd_param[i] = 0; + + len = (int)count; + if (*(buf + len - 1) == '\n') + len--; + + memset(data->cmd, 0x00, ARRAY_SIZE(data->cmd)); + memcpy(data->cmd, buf, len); + + cur = strchr(buf, (int)delim); + if (cur) + memcpy(buff, buf, cur - buf); + else + memcpy(buff, buf, len); + + pr_debug("%s: %s %s: COMMAND = %s\n", dev_name(data->fac_dev), SECLOG, __func__, buff); + + /* find command */ + list_for_each_entry(sec_cmd_ptr, &data->cmd_list_head, list) { + if (!strncmp(buff, sec_cmd_ptr->cmd_name, SEC_CMD_STR_LEN)) { + if (!sec_cmd_ptr->not_support_cmds) { + cmd_found = true; + break; + } + pr_err("%s: %s %s: [%s] is in not_support_cmds list\n", dev_name(data->fac_dev), SECLOG, __func__, buff); + } + } + +check_not_support_cmd: + /* set not_support_cmd */ + if (!cmd_found) { + list_for_each_entry(sec_cmd_ptr, &data->cmd_list_head, list) { + if (!strncmp("not_support_cmd", sec_cmd_ptr->cmd_name, + SEC_CMD_STR_LEN)) + break; + } + } + + /* parsing parameters */ + if (cur && cmd_found) { + cur++; + start = cur; + memset(buff, 0x00, ARRAY_SIZE(buff)); + + do { + if (*cur == delim || cur - buf == len) { + end = cur; + memcpy(buff, start, end - start); + *(buff + strnlen(buff, ARRAY_SIZE(buff))) = '\0'; + if (kstrtoint(buff, 10, data->cmd_param + param_cnt) < 0) { + pr_err("%s: %s %s: error to parse parameter\n", + dev_name(data->fac_dev), SECLOG, __func__); + cmd_found = false; + goto check_not_support_cmd; + } + start = cur + 1; + memset(buff, 0x00, ARRAY_SIZE(buff)); + param_cnt++; + } + cur++; + } while ((cur - buf <= len) && (param_cnt < SEC_CMD_PARAM_NUM)); + } + + if (cmd_found) { + char dbuff[13]; + char tdbuff[13 * SEC_CMD_PARAM_NUM] = { 0 }; + + for (i = 0; i < param_cnt; i++) { + snprintf(dbuff, sizeof(dbuff), "%d ", data->cmd_param[i]); + strlcat(tdbuff, dbuff, sizeof(tdbuff)); + } + + if (param_cnt == 0) + snprintf(tdbuff, sizeof(tdbuff), "none"); + + pr_info("%s: %s %s: cmd = %s param = %s\n", dev_name(data->fac_dev), SECLOG, __func__, sec_cmd_ptr->cmd_name, tdbuff); + } else { + pr_info("%s: %s %s: cmd = %s(%s)\n", dev_name(data->fac_dev), SECLOG, __func__, buff, sec_cmd_ptr->cmd_name); + } + + if (sec_cmd_ptr->cmd_use_cases) + sec_cmd_check_store_condition(data, sec_cmd_ptr); + else + sec_cmd_ptr->cmd_func(data); + + return count; +} +#if IS_ENABLED(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_KUNIT(cmd_store); +#endif + +#else /* defined USE_SEC_CMD_QUEUE */ +static void cmd_store_function(struct sec_cmd_data *data) +{ + char *cur, *start, *end; + char buff[SEC_CMD_STR_LEN] = { 0 }; + int len, i; + struct sec_cmd *sec_cmd_ptr = NULL; + char delim = ','; + bool cmd_found = false; + int param_cnt = 0; + int ret; + const char *buf; + size_t count; + struct command cmd = {{0}}; + + if (!data) { + pr_err("%s %s: No platform data found\n", SECLOG, __func__); + return; + } + + mutex_lock(&data->fifo_lock); + if (kfifo_len(&data->cmd_queue)) { + ret = kfifo_out(&data->cmd_queue, &cmd, sizeof(struct command)); + if (!ret) { + pr_err("%s: %s %s: kfifo_out failed, it seems empty, ret=%d\n", dev_name(data->fac_dev), SECLOG, __func__, ret); + mutex_unlock(&data->fifo_lock); + return; + } + } else { + pr_err("%s: %s %s: left cmd is nothing\n", dev_name(data->fac_dev), SECLOG, __func__); + mutex_unlock(&data->fifo_lock); + mutex_lock(&data->cmd_lock); + atomic_set(&data->cmd_is_running, 0); + mutex_unlock(&data->cmd_lock); + return; + } + mutex_unlock(&data->fifo_lock); + + buf = cmd.cmd; + count = strlen(buf); + + for (i = 0; i < (int)ARRAY_SIZE(data->cmd_param); i++) + data->cmd_param[i] = 0; + + len = (int)count; + if (*(buf + len - 1) == '\n') + len--; + + memset(data->cmd, 0x00, ARRAY_SIZE(data->cmd)); + memcpy(data->cmd, buf, len); + + cur = strchr(buf, (int)delim); + if (cur) + memcpy(buff, buf, cur - buf); + else + memcpy(buff, buf, len); + + pr_debug("%s: %s %s: COMMAND : %s\n", dev_name(data->fac_dev), SECLOG, __func__, buff); + + /* find command */ + list_for_each_entry(sec_cmd_ptr, &data->cmd_list_head, list) { + if (!strncmp(buff, sec_cmd_ptr->cmd_name, SEC_CMD_STR_LEN)) { + if (!sec_cmd_ptr->not_support_cmds) { + cmd_found = true; + break; + } + pr_err("%s: %s %s: [%s] is in not_support_cmds list\n", dev_name(data->fac_dev), SECLOG, __func__, buff); + } + } + +check_not_support_cmd: + /* set not_support_cmd */ + if (!cmd_found) { + list_for_each_entry(sec_cmd_ptr, &data->cmd_list_head, list) { + if (!strncmp("not_support_cmd", sec_cmd_ptr->cmd_name, + SEC_CMD_STR_LEN)) + break; + } + } + + /* parsing parameters */ + if (cur && cmd_found) { + cur++; + start = cur; + memset(buff, 0x00, ARRAY_SIZE(buff)); + + do { + if (*cur == delim || cur - buf == len) { + end = cur; + memcpy(buff, start, end - start); + *(buff + strnlen(buff, ARRAY_SIZE(buff))) = '\0'; + if (kstrtoint(buff, 10, data->cmd_param + param_cnt) < 0) { + pr_err("%s: %s %s: error to parse parameter\n", + dev_name(data->fac_dev), SECLOG, __func__); + cmd_found = false; + goto check_not_support_cmd; + } + start = cur + 1; + memset(buff, 0x00, ARRAY_SIZE(buff)); + param_cnt++; + } + cur++; + } while ((cur - buf <= len) && (param_cnt < SEC_CMD_PARAM_NUM)); + } + + if (cmd_found) { + char dbuff[13]; + char tdbuff[13 * SEC_CMD_PARAM_NUM] = { 0 }; + + for (i = 0; i < param_cnt; i++) { + snprintf(dbuff, sizeof(dbuff), "%d ", data->cmd_param[i]); + strlcat(tdbuff, dbuff, sizeof(tdbuff)); + } + + if (param_cnt == 0) + snprintf(tdbuff, sizeof(tdbuff), "none"); + + pr_info("%s: %s %s: cmd = %s param = %s\n", dev_name(data->fac_dev), SECLOG, __func__, sec_cmd_ptr->cmd_name, tdbuff); + } else { + pr_info("%s: %s %s: cmd = %s(%s)\n", dev_name(data->fac_dev), SECLOG, __func__, buff, sec_cmd_ptr->cmd_name); + } + + if (sec_cmd_ptr->cmd_use_cases) + sec_cmd_check_store_condition(data, sec_cmd_ptr); + else + sec_cmd_ptr->cmd_func(data); + + if (cmd_found && sec_cmd_ptr->cmd_log) { + char tbuf[32]; + unsigned long long t; + unsigned long nanosec_rem; + + memset(tbuf, 0x00, sizeof(tbuf)); + t = local_clock(); + nanosec_rem = do_div(t, 1000000000); + snprintf(tbuf, sizeof(tbuf), "[r:%lu.%06lu]", + (unsigned long)t, + nanosec_rem / 1000); +#if IS_ENABLED(CONFIG_SEC_DEBUG_TSP_LOG) + sec_debug_tsp_command_history(tbuf); +#endif + } +} + +__visible_for_testing ssize_t cmd_store(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sec_cmd_data *data = dev_get_drvdata(dev); + struct command cmd = {{0}}; + struct sec_cmd *sec_cmd_ptr = NULL; + int queue_size; + + if (!data) { + pr_err("%s %s: No platform data found\n", SECLOG, __func__); + return -EINVAL; + } + + if (strnlen(buf, SEC_CMD_STR_LEN) >= SEC_CMD_STR_LEN) { + pr_err("%s: %s %s: cmd length(strlen(buf)) is over (%d,%s)!!\n", + dev_name(data->fac_dev), SECLOG, __func__, (int)strlen(buf), buf); + return -EINVAL; + } + + if (count >= (unsigned int)SEC_CMD_STR_LEN) { + pr_err("%s: %s %s: cmd length(count) is over (%d,%s)!!\n", + dev_name(data->fac_dev), SECLOG, __func__, (unsigned int)count, buf); + return -EINVAL; + } + + if (strnlen(buf, SEC_CMD_STR_LEN) == 0) { + pr_err("%s: %s %s: cmd length is zero (%d,%s) count(%ld)!!\n", + dev_name(data->fac_dev), SECLOG, __func__, (int)strlen(buf), buf, count); + return -EINVAL; + } + + strncpy(cmd.cmd, buf, count); + + if (data->wait_cmd_result_done) { + int ret; + + mutex_lock(&data->wait_lock); + if (!data->cmd_result_done.done) + pr_info("%s: %s %s: %s - waiting prev cmd...\n", dev_name(data->fac_dev), SECLOG, __func__, cmd.cmd); + ret = wait_for_completion_interruptible_timeout(&data->cmd_result_done, msecs_to_jiffies(2000)); + if (ret <= 0) + pr_err("%s: %s %s: completion %d\n", dev_name(data->fac_dev), SECLOG, __func__, ret); + + reinit_completion(&data->cmd_result_done); + mutex_unlock(&data->wait_lock); + } + + list_for_each_entry(sec_cmd_ptr, &data->cmd_list_head, list) { + if (!strncmp(cmd.cmd, sec_cmd_ptr->cmd_name, strlen(sec_cmd_ptr->cmd_name))) { + if (sec_cmd_ptr->cmd_log) { + char task_info[40]; + char tbuf[32]; + unsigned long long t; + unsigned long nanosec_rem; + + memset(tbuf, 0x00, sizeof(tbuf)); + t = local_clock(); + nanosec_rem = do_div(t, 1000000000); + snprintf(tbuf, sizeof(tbuf), "[q:%lu.%06lu]", + (unsigned long)t, + nanosec_rem / 1000); + + snprintf(task_info, 40, "\n[%d:%s:%s]", current->pid, current->comm, dev_name(data->fac_dev)); +#if IS_ENABLED(CONFIG_SEC_DEBUG_TSP_LOG) + sec_debug_tsp_command_history(task_info); + sec_debug_tsp_command_history(cmd.cmd); + sec_debug_tsp_command_history(tbuf); +#endif + } + break; + } + } + + mutex_lock(&data->fifo_lock); + queue_size = (kfifo_len(&data->cmd_queue) / sizeof(struct command)); + + if (kfifo_avail(&data->cmd_queue) && (queue_size < SEC_CMD_MAX_QUEUE)) { + kfifo_in(&data->cmd_queue, &cmd, sizeof(struct command)); + pr_info("%s: %s %s: push cmd: %s\n", dev_name(data->fac_dev), SECLOG, __func__, cmd.cmd); + } else { + pr_err("%s: %s %s: cmd_queue is full!!\n", dev_name(data->fac_dev), SECLOG, __func__); + + kfifo_reset(&data->cmd_queue); + pr_err("%s: %s %s: cmd_queue is reset!!\n", dev_name(data->fac_dev), SECLOG, __func__); + mutex_unlock(&data->fifo_lock); + + mutex_lock(&data->cmd_lock); + atomic_set(&data->cmd_is_running, 0); + mutex_unlock(&data->cmd_lock); + + if (data->wait_cmd_result_done) + complete_all(&data->cmd_result_done); + + return -ENOSPC; + } + + if (atomic_read(&data->cmd_is_running)) { + pr_err("%s: %s %s: other cmd is running. Wait until previous cmd is done[%d]\n", + dev_name(data->fac_dev), SECLOG, __func__, (int)(kfifo_len(&data->cmd_queue) / sizeof(struct command))); + mutex_unlock(&data->fifo_lock); + return count; + } + mutex_unlock(&data->fifo_lock); + + sec_cmd_execution(data, true); + return count; +} +#if IS_ENABLED(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_KUNIT(cmd_store); +#endif +#endif + +__visible_for_testing ssize_t cmd_status_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sec_cmd_data *data = dev_get_drvdata(dev); + char buff[16] = { 0 }; + + if (!data) { + pr_err("%s %s: No platform data found\n", SECLOG, __func__); + return -EINVAL; + } + + if (data->cmd_state == SEC_CMD_STATUS_WAITING) + snprintf(buff, sizeof(buff), "WAITING"); + + else if (data->cmd_state == SEC_CMD_STATUS_RUNNING) + snprintf(buff, sizeof(buff), "RUNNING"); + + else if (data->cmd_state == SEC_CMD_STATUS_OK) + snprintf(buff, sizeof(buff), "OK"); + + else if (data->cmd_state == SEC_CMD_STATUS_FAIL) + snprintf(buff, sizeof(buff), "FAIL"); + + else if (data->cmd_state == SEC_CMD_STATUS_EXPAND) + snprintf(buff, sizeof(buff), "EXPAND"); + + else if (data->cmd_state == SEC_CMD_STATUS_NOT_APPLICABLE) + snprintf(buff, sizeof(buff), "NOT_APPLICABLE"); + + pr_debug("%s: %s %s: %d, %s\n", dev_name(data->fac_dev), SECLOG, __func__, data->cmd_state, buff); + + return snprintf(buf, sizeof(buff), "%s\n", buff); +} +#if IS_ENABLED(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_KUNIT(cmd_status_show); +#endif + +__visible_for_testing ssize_t cmd_status_all_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sec_cmd_data *data = dev_get_drvdata(dev); + char buff[16] = { 0 }; + + if (!data) { + pr_err("%s %s: No platform data found\n", SECLOG, __func__); + return -EINVAL; + } + + if (data->cmd_all_factory_state == SEC_CMD_STATUS_WAITING) + snprintf(buff, sizeof(buff), "WAITING"); + + else if (data->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + snprintf(buff, sizeof(buff), "RUNNING"); + + else if (data->cmd_all_factory_state == SEC_CMD_STATUS_OK) + snprintf(buff, sizeof(buff), "OK"); + + else if (data->cmd_all_factory_state == SEC_CMD_STATUS_FAIL) + snprintf(buff, sizeof(buff), "FAIL"); + + else if (data->cmd_state == SEC_CMD_STATUS_EXPAND) + snprintf(buff, sizeof(buff), "EXPAND"); + + else if (data->cmd_all_factory_state == SEC_CMD_STATUS_NOT_APPLICABLE) + snprintf(buff, sizeof(buff), "NOT_APPLICABLE"); + + pr_debug("%s: %s %s: %d, %s\n", dev_name(data->fac_dev), SECLOG, __func__, data->cmd_all_factory_state, buff); + + return snprintf(buf, sizeof(buff), "%s\n", buff); +} +#if IS_ENABLED(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_KUNIT(cmd_status_all_show); +#endif + +__visible_for_testing ssize_t cmd_result_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sec_cmd_data *data = dev_get_drvdata(dev); + int size; + + if (!data) { + pr_err("%s %s: No platform data found\n", SECLOG, __func__); + return -EINVAL; + } + + size = snprintf(buf, SEC_CMD_RESULT_STR_LEN, "%s\n", + data->cmd_result + (SEC_CMD_RESULT_STR_LEN - 1) * data->cmd_result_expand_count); + + if (data->cmd_result_expand_count != data->cmd_result_expand) { + data->cmd_state = SEC_CMD_STATUS_EXPAND; + data->cmd_result_expand_count++; + } else { + data->cmd_state = SEC_CMD_STATUS_WAITING; + } + + pr_info("%s: %s %s: %s\n", dev_name(data->fac_dev), SECLOG, __func__, buf); + + sec_cmd_set_cmd_exit(data); + + return size; +} +#if IS_ENABLED(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_KUNIT(cmd_result_show); +#endif + +__visible_for_testing ssize_t cmd_result_all_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sec_cmd_data *data = dev_get_drvdata(dev); + int size; + + if (!data) { + pr_err("%s %s: No platform data found\n", SECLOG, __func__); + return -EINVAL; + } + + data->cmd_state = SEC_CMD_STATUS_WAITING; + pr_info("%s: %s %s: %d, %s\n", dev_name(data->fac_dev), SECLOG, __func__, data->item_count, data->cmd_result_all); + size = snprintf(buf, SEC_CMD_RESULT_STR_LEN, "%d%s\n", data->item_count, data->cmd_result_all); + + sec_cmd_set_cmd_exit(data); + + data->item_count = 0; + memset(data->cmd_result_all, 0x00, SEC_CMD_RESULT_STR_LEN); + + return size; +} +#if IS_ENABLED(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_KUNIT(cmd_result_all_show); +#endif + +__visible_for_testing ssize_t cmd_list_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *data = dev_get_drvdata(dev); + struct sec_cmd *sec_cmd_ptr = NULL; + char *buffer; + char buffer_name[SEC_CMD_STR_LEN]; + int ret = 0; + + buffer = kzalloc(data->cmd_buffer_size + 30, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + snprintf(buffer, 30, "++factory command list++\n"); + + list_for_each_entry(sec_cmd_ptr, &data->cmd_list_head, list) { + if (sec_cmd_ptr->not_support_cmds) + continue; + if (strncmp(sec_cmd_ptr->cmd_name, "not_support_cmd", 15)) { + snprintf(buffer_name, SEC_CMD_STR_LEN, "%s\n", sec_cmd_ptr->cmd_name); + strlcat(buffer, buffer_name, data->cmd_buffer_size + 30); + } + } + + ret = snprintf(buf, SEC_CMD_BUF_SIZE, "%s\n", buffer); + kfree(buffer); + + return ret; +} +#if IS_ENABLED(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_KUNIT(cmd_list_show); +#endif + +static DEVICE_ATTR(cmd, 0220, NULL, cmd_store); +static DEVICE_ATTR_RO(cmd_status); +static DEVICE_ATTR_RO(cmd_status_all); +static DEVICE_ATTR_RO(cmd_result); +static DEVICE_ATTR_RO(cmd_result_all); +static DEVICE_ATTR_RO(cmd_list); + +static struct attribute *sec_fac_attrs[] = { + &dev_attr_cmd.attr, + &dev_attr_cmd_status.attr, + &dev_attr_cmd_status_all.attr, + &dev_attr_cmd_result.attr, + &dev_attr_cmd_result_all.attr, + &dev_attr_cmd_list.attr, + NULL, +}; + +static struct attribute_group sec_fac_attr_group = { + .attrs = sec_fac_attrs, +}; + +static ssize_t prox_power_off_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct sec_ts_plat_data *plat_data = sec->dev->platform_data; + + input_info(true, sec->dev, "%s: %d\n", __func__, plat_data->prox_power_off); + + return snprintf(buf, SEC_CMD_BUF_SIZE, "%d", plat_data->prox_power_off); +} + +static ssize_t prox_power_off_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct sec_ts_plat_data *plat_data = sec->dev->platform_data; + long data; + int ret; + + ret = kstrtol(buf, 10, &data); + if (ret < 0) + return ret; + + input_info(true, sec->dev, "%s: %ld\n", __func__, data); + + plat_data->prox_power_off = data; + + return count; +} + +static ssize_t support_feature_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct sec_ts_plat_data *plat_data = sec->dev->platform_data; + u32 feature = 0; + + if (plat_data->enable_settings_aot) + feature |= INPUT_FEATURE_ENABLE_SETTINGS_AOT; + + if (plat_data->support_vrr) + feature |= INPUT_FEATURE_ENABLE_VRR; + + if (plat_data->support_open_short_test) + feature |= INPUT_FEATURE_SUPPORT_OPEN_SHORT_TEST; + + if (plat_data->support_mis_calibration_test) + feature |= INPUT_FEATURE_SUPPORT_MIS_CALIBRATION_TEST; + + if (plat_data->support_wireless_tx) + feature |= INPUT_FEATURE_SUPPORT_WIRELESS_TX; + + if (plat_data->enable_sysinput_enabled) + feature |= INPUT_FEATURE_ENABLE_SYSINPUT_ENABLED; + + if (plat_data->prox_lp_scan_enabled) + feature |= INPUT_FEATURE_ENABLE_PROX_LP_SCAN_ENABLED; + + if (plat_data->support_input_monitor) + feature |= INPUT_FEATURE_SUPPORT_INPUT_MONITOR; + + if (plat_data->support_rawdata_motion_aivf) + feature |= INPUT_FEATURE_SUPPORT_MOTION_AIVF; + + if (plat_data->support_rawdata_motion_palm) + feature |= INPUT_FEATURE_SUPPORT_MOTION_PALM; + + input_info(true, sec->dev, "%s: %d%s%s%s%s%s%s%s%s%s%s%s\n", + __func__, feature, + feature & INPUT_FEATURE_ENABLE_SETTINGS_AOT ? " aot" : "", + feature & INPUT_FEATURE_ENABLE_PRESSURE ? " pressure" : "", + feature & INPUT_FEATURE_ENABLE_VRR ? " vrr" : "", + feature & INPUT_FEATURE_SUPPORT_OPEN_SHORT_TEST ? " openshort" : "", + feature & INPUT_FEATURE_SUPPORT_MIS_CALIBRATION_TEST ? " miscal" : "", + feature & INPUT_FEATURE_SUPPORT_WIRELESS_TX ? " wirelesstx" : "", + feature & INPUT_FEATURE_SUPPORT_INPUT_MONITOR ? " inputmonitor" : "", + feature & INPUT_FEATURE_ENABLE_SYSINPUT_ENABLED ? " SE" : "", + feature & INPUT_FEATURE_ENABLE_PROX_LP_SCAN_ENABLED ? " LPSCAN" : "", + feature & INPUT_FEATURE_SUPPORT_MOTION_AIVF ? " AIVF" : "", + feature & INPUT_FEATURE_SUPPORT_MOTION_PALM ? " PALM" : ""); + + return snprintf(buf, SEC_CMD_BUF_SIZE, "%d", feature); +} + +static ssize_t enabled_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct sec_ts_plat_data *plat_data = sec->dev->platform_data; + + if (!plat_data->enable_sysinput_enabled) + return -EINVAL; + + input_info(true, sec->dev, "%s: enabled %d\n", __func__, atomic_read(&plat_data->enabled)); + + return snprintf(buf, SEC_CMD_BUF_SIZE, "%d", atomic_read(&plat_data->enabled)); +} + +ssize_t sec_cmd_enabled_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return enabled_show(dev, attr, buf); +} +#if IS_ENABLED(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_KUNIT(sec_cmd_enabled_show); +#endif + +static ssize_t enabled_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct sec_ts_plat_data *plat_data = sec->dev->platform_data; + int buff[2]; + int ret; + + if (!plat_data->enable_sysinput_enabled) + return -EINVAL; + + ret = sscanf(buf, "%d,%d", &buff[0], &buff[1]); + if (ret != 2) { + input_err(true, sec->dev, + "%s: failed read params [%d]\n", __func__, ret); + return -EINVAL; + } + + if (buff[0] == DISPLAY_STATE_ON && buff[1] == DISPLAY_EVENT_LATE) { + input_info(true, sec->dev, "%s: DISPLAY_STATE_ON\n", __func__); + if (atomic_read(&plat_data->enabled)) { + input_err(true, sec->dev, "%s: device already enabled\n", __func__); + goto out; + } + ret = sec_input_enable_device(plat_data->dev); + } else if (buff[0] == DISPLAY_STATE_OFF && buff[1] == DISPLAY_EVENT_EARLY) { + input_info(true, sec->dev, "%s: DISPLAY_STATE_OFF\n", __func__); + if (!atomic_read(&plat_data->enabled)) { + input_err(true, sec->dev, "%s: device already disabled\n", __func__); + goto out; + } + ret = sec_input_disable_device(plat_data->dev); + } else if (buff[0] == DISPLAY_STATE_FORCE_ON) { + input_info(true, sec->dev, "%s: DISPLAY_STATE_FORCE_ON\n", __func__); + if (atomic_read(&plat_data->enabled)) { + input_err(true, sec->dev, "%s: device already enabled\n", __func__); + goto out; + } + ret = sec_input_enable_device(plat_data->dev); + } else if (buff[0] == DISPLAY_STATE_FORCE_OFF || buff[0] == DISPLAY_STATE_LPM_OFF) { + input_info(true, sec->dev, "%s: %s\n", __func__, buff[0] == DISPLAY_STATE_FORCE_OFF ? + "DISPLAY_STATE_FORCE_OFF" : "DISPLAY_STATE_LPM_OFF"); + if (!atomic_read(&plat_data->enabled)) { + input_err(true, sec->dev, "%s: device already disabled\n", __func__); + goto out; + } + ret = sec_input_disable_device(plat_data->dev); + } + + if (ret) + return ret; + +out: + return count; +} + +ssize_t sec_cmd_enabled_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return enabled_store(dev, attr, buf, count); +} +#if IS_ENABLED(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_KUNIT(sec_cmd_enabled_store); +#endif + +static DEVICE_ATTR_RW(prox_power_off); +static DEVICE_ATTR_RO(support_feature); +static DEVICE_ATTR_RW(enabled); + +static struct attribute *sec_fac_common_attrs[] = { + &dev_attr_prox_power_off.attr, + &dev_attr_support_feature.attr, + &dev_attr_enabled.attr, + NULL, +}; + +static struct attribute_group sec_fac_common_attr_group = { + .attrs = sec_fac_common_attrs, +}; + +static void sec_cmd_parse_dt_not_support_cmds(struct sec_cmd_data *data) +{ + struct device *dev; + struct device_node *np; + struct sec_cmd *sec_cmd_ptr = NULL; + int count = 0; + int ii = 0; + int ret = 0; + + if (data->dev) + dev = data->dev; + else + return; + + if (dev->of_node) + np = dev->of_node; + else + return; + + count = of_property_count_strings(np, "sec_cmd,not_support_cmds"); + for (ii = 0; ii < count; ii++) { + const char *buff; + + ret = of_property_read_string_index(np, "sec_cmd,not_support_cmds", ii, &buff); + if (ret < 0 || strlen(buff) <= 0) { + input_err(true, dev, "%s: failed get sec_cmd,not_support_cmds: %d, %lu\n", __func__, ret, strlen(buff)); + return; + } + list_for_each_entry(sec_cmd_ptr, &data->cmd_list_head, list) { + if (!strncmp(buff, sec_cmd_ptr->cmd_name, SEC_CMD_STR_LEN)) { + sec_cmd_ptr->not_support_cmds = true; + } + } + + input_info(true, dev, "%s: sec_cmd,not_support_cmds(%d): %s\n", __func__, ii, buff); + } +} + +int sec_cmd_init(struct sec_cmd_data *data, struct device *dev, struct sec_cmd *cmds, + int len, int devt, struct attribute_group *vendor_attr_group) +{ + const char *dev_name; + int ret, i; + + INIT_LIST_HEAD(&data->cmd_list_head); + + data->cmd_buffer_size = 0; + for (i = 0; i < len; i++) { + list_add_tail(&cmds[i].list, &data->cmd_list_head); + if (cmds[i].cmd_name) + data->cmd_buffer_size += strlen(cmds[i].cmd_name) + 1; + } + + mutex_init(&data->cmd_lock); + mutex_init(&data->fs_lock); + + mutex_lock(&data->cmd_lock); + atomic_set(&data->cmd_is_running, 0); + mutex_unlock(&data->cmd_lock); + + data->cmd_result = kzalloc(SEC_CMD_RESULT_STR_LEN_EXPAND, GFP_KERNEL); + if (!data->cmd_result) + goto err_alloc_cmd_result; + + if (!IS_ERR_OR_NULL(dev)) + data->dev = dev; + +#ifdef USE_SEC_CMD_QUEUE + if (kfifo_alloc(&data->cmd_queue, + SEC_CMD_MAX_QUEUE * sizeof(struct command), GFP_KERNEL)) { + pr_err("%s %s: failed to alloc queue for cmd\n", SECLOG, __func__); + goto err_alloc_queue; + } + mutex_init(&data->fifo_lock); + mutex_init(&data->wait_lock); + init_completion(&data->cmd_result_done); + complete_all(&data->cmd_result_done); + + INIT_DELAYED_WORK(&data->cmd_work, cmd_exit_work); +#endif + + switch (devt) { + case SEC_CLASS_DEVT_TSP: + dev_name = SEC_CLASS_DEV_NAME_TSP; + break; + case SEC_CLASS_DEVT_TSP1: + dev_name = SEC_CLASS_DEV_NAME_TSP1; + break; + case SEC_CLASS_DEVT_TSP2: + dev_name = SEC_CLASS_DEV_NAME_TSP2; + break; + case SEC_CLASS_DEVT_TKEY: + dev_name = SEC_CLASS_DEV_NAME_TKEY; + break; + case SEC_CLASS_DEVT_WACOM: + dev_name = SEC_CLASS_DEV_NAME_WACOM; + break; + case SEC_CLASS_DEVT_SIDEKEY: + dev_name = SEC_CLASS_DEV_NAME_SIDEKEY; + break; + default: + pr_err("%s %s: not defined devt=%d\n", SECLOG, __func__, devt); + goto err_get_dev_name; + } + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + data->fac_dev = sec_device_create(data, dev_name); +#else + tsp_sec_class = class_create(THIS_MODULE, dev_name); + if (IS_ERR(tsp_sec_class)) { + pr_err("%s %s: Failed to create class(sec) %ld\n", SECLOG, __func__, PTR_ERR(tsp_sec_class)); + return PTR_ERR(tsp_sec_class); + } + data->fac_dev = device_create(tsp_sec_class, NULL, devt, data, "%s", dev_name); +#endif + + if (IS_ERR(data->fac_dev)) { + pr_err("%s %s: failed to create device for the sysfs\n", SECLOG, __func__); + goto err_sysfs_device; + } + + dev_set_drvdata(data->fac_dev, data); + + sec_cmd_parse_dt_not_support_cmds(data); + + ret = sysfs_create_group(&data->fac_dev->kobj, &sec_fac_attr_group); + if (ret < 0) { + pr_err("%s %s: failed to create sysfs group\n", SECLOG, __func__); + goto err_sysfs_group; + } + pr_info("%s: %s create sec_fac_attr_group: done\n", SECLOG, __func__); + + if (!IS_ERR_OR_NULL(vendor_attr_group)) { + ret = sysfs_create_group(&data->fac_dev->kobj, vendor_attr_group); + if (ret < 0) { + pr_err("%s %s: failed to create sysfs group\n", SECLOG, __func__); + goto err_vendor_sysfs_group; + } + data->vendor_attr_group = vendor_attr_group; + pr_info("%s: %s create vendor_attr_group: done\n", SECLOG, __func__); + } + + if (!IS_ERR_OR_NULL(dev)) { + /* if you do not use sec_ts_plat_data, should invoke sec_cmd_init_without_platdata */ + struct sec_ts_plat_data *plat_data = dev->platform_data; + + plat_data->sec = data; + + ret = sysfs_create_group(&data->fac_dev->kobj, &sec_fac_common_attr_group); + if (ret < 0) { + pr_err("%s %s: failed to create sec_fac_common_attr_group\n", SECLOG, __func__); + goto err_common_sysfs_group; + } + pr_info("%s: %s create sec_fac_common_attr_group: done\n", SECLOG, __func__); + } + + pr_info("%s: %s %s: done\n", dev_name, SECLOG, __func__); + + sec_cmd_send_event_to_user(data, NULL, "RESULT=PROBE_DONE"); + + return 0; + +err_common_sysfs_group: + if (!IS_ERR_OR_NULL(data->vendor_attr_group)) + sysfs_remove_group(&data->fac_dev->kobj, data->vendor_attr_group); +err_vendor_sysfs_group: + sysfs_remove_group(&data->fac_dev->kobj, &sec_fac_attr_group); +err_sysfs_group: +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + sec_device_destroy(data->fac_dev->devt); +#else + device_destroy(tsp_sec_class, devt); +#endif +err_sysfs_device: +err_get_dev_name: +#ifdef USE_SEC_CMD_QUEUE + mutex_destroy(&data->fifo_lock); + kfifo_free(&data->cmd_queue); + mutex_destroy(&data->wait_lock); +err_alloc_queue: +#endif + kfree(data->cmd_result); +err_alloc_cmd_result: + mutex_destroy(&data->cmd_lock); + list_del(&data->cmd_list_head); + return -ENODEV; +} +EXPORT_SYMBOL(sec_cmd_init); + +/* + * sec_cmd_init_without_platdata + * + * If device driver doesn't use sec_ts_plat_data as platform_data, you should init cmds with this function. + * and should make sysfs like sec_fac_common_attrs on device driver side + */ +int sec_cmd_init_without_platdata(struct sec_cmd_data *data, struct sec_cmd *cmds, + int len, int devt, struct attribute_group *vendor_attr_group) +{ + return sec_cmd_init(data, NULL, cmds, len, devt, vendor_attr_group); +} +EXPORT_SYMBOL(sec_cmd_init_without_platdata); + +void sec_cmd_exit(struct sec_cmd_data *data, int devt) +{ +#ifdef USE_SEC_CMD_QUEUE + struct command cmd = {{0}}; + int ret; +#endif + + pr_info("%s: %s %s\n", dev_name(data->fac_dev), SECLOG, __func__); + + if (!IS_ERR_OR_NULL(data->fac_dev)) + sysfs_remove_group(&data->fac_dev->kobj, &sec_fac_common_attr_group); + if (!IS_ERR_OR_NULL(data->vendor_attr_group)) + sysfs_remove_group(&data->fac_dev->kobj, data->vendor_attr_group); + sysfs_remove_group(&data->fac_dev->kobj, &sec_fac_attr_group); + dev_set_drvdata(data->fac_dev, NULL); +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + sec_device_destroy(data->fac_dev->devt); +#else + device_destroy(tsp_sec_class, devt); +#endif +#ifdef USE_SEC_CMD_QUEUE + mutex_lock(&data->fifo_lock); + while (kfifo_len(&data->cmd_queue)) { + ret = kfifo_out(&data->cmd_queue, &cmd, sizeof(struct command)); + if (!ret) + pr_err("%s %s: kfifo_out failed, it seems empty, ret=%d\n", SECLOG, __func__, ret); + pr_info("%s %s: remove pending commands: %s", SECLOG, __func__, cmd.cmd); + } + mutex_unlock(&data->fifo_lock); + mutex_destroy(&data->fifo_lock); + kfifo_free(&data->cmd_queue); + mutex_destroy(&data->wait_lock); + + cancel_delayed_work_sync(&data->cmd_work); + flush_delayed_work(&data->cmd_work); +#endif + if (!IS_ERR_OR_NULL(data->dev)) { + struct sec_ts_plat_data *plat_data = data->dev->platform_data; + + plat_data->sec = NULL; + } + data->fac_dev = NULL; + kfree(data->cmd_result); + mutex_destroy(&data->cmd_lock); + list_del(&data->cmd_list_head); + +} +EXPORT_SYMBOL(sec_cmd_exit); + +void sec_cmd_send_event_to_user(struct sec_cmd_data *data, char *test, char *result) +{ + char *event[5]; + char timestamp[32]; + char feature[32]; + char stest[32]; + char sresult[64]; + ktime_t calltime; + u64 realtime; + int curr_time; + char *eol = "\0"; + + if (!data || !data->fac_dev) + return; + + calltime = ktime_get(); + realtime = ktime_to_ns(calltime); + do_div(realtime, NSEC_PER_USEC); + curr_time = realtime / USEC_PER_MSEC; + + snprintf(timestamp, 32, "TIMESTAMP=%d", curr_time); + strncat(timestamp, eol, 1); + snprintf(feature, 32, "FEATURE=TSP"); + strncat(feature, eol, 1); + if (!test) + snprintf(stest, 32, "TEST=NULL"); + else + snprintf(stest, 32, "%s", test); + + strncat(stest, eol, 1); + + if (!result) + snprintf(sresult, 64, "RESULT=NULL"); + else + snprintf(sresult, 64, "%s", result); + + strncat(sresult, eol, 1); + + pr_info("%s: %s %s: time:%s, feature:%s, test:%s, result:%s\n", + dev_name(data->fac_dev), SECLOG, __func__, timestamp, feature, stest, sresult); + + event[0] = timestamp; + event[1] = feature; + event[2] = stest; + event[3] = sresult; + event[4] = NULL; + + kobject_uevent_env(&data->fac_dev->kobj, KOBJ_CHANGE, event); +} +EXPORT_SYMBOL(sec_cmd_send_event_to_user); + +void sec_cmd_send_status_uevent(struct sec_cmd_data *data, enum sec_cmd_status_uevent_type type, int value) +{ + char test[32] = { 0 }; + char result[32] = { 0 }; + + switch (type) { + case STATUS_TYPE_WET: + snprintf(test, sizeof(test), "STATUS=WET"); + break; + case STATUS_TYPE_NOISE: + snprintf(test, sizeof(test), "STATUS=NOISE"); + break; + case STATUS_TYPE_FREQ: + snprintf(test, sizeof(test), "STATUS=FREQ"); + break; + default: + pr_info("%s: %s %s: undefined type %d\n", + dev_name(data->fac_dev), SECLOG, __func__, type); + return; + } + + snprintf(result, sizeof(result), "VALUE=%d", value); + sec_cmd_send_event_to_user(data, test, result); +} +EXPORT_SYMBOL(sec_cmd_send_status_uevent); + +void sec_cmd_send_gesture_uevent(struct sec_cmd_data *data, int type, int x, int y) +{ + struct sec_ts_plat_data *plat_data; + char test[32] = { 0 }; + char result[32] = { 0 }; + + if (!data->dev) + return; + + plat_data = data->dev->platform_data; + if (IS_ERR_OR_NULL(plat_data)) + return; + + snprintf(test, sizeof(test), "GESTURE=%d", type); + snprintf(result, sizeof(result), "POS=%d,%d", x, y); + + sec_cmd_send_event_to_user(data, test, result); +} +EXPORT_SYMBOL(sec_cmd_send_gesture_uevent); + +MODULE_DESCRIPTION("Samsung input command"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/input/sec_input/sec_cmd.h b/drivers/input/sec_input/sec_cmd.h new file mode 100644 index 000000000000..ee16dfd22083 --- /dev/null +++ b/drivers/input/sec_input/sec_cmd.h @@ -0,0 +1,164 @@ +/* SPDX-License-Identifier: GPL-2.0 + * Copyright (C) 2022 Samsung Electronics Co., Ltd. + * + * 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. + */ + +#ifndef _SEC_CMD_H_ +#define _SEC_CMD_H_ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) +#include +#endif + +#if !IS_ENABLED(CONFIG_SEC_FACTORY) +#define USE_SEC_CMD_QUEUE +#include +#endif + +#define SEC_CLASS_DEVT_TSP 10 +#define SEC_CLASS_DEVT_TKEY 11 +#define SEC_CLASS_DEVT_WACOM 12 +#define SEC_CLASS_DEVT_SIDEKEY 13 +#define SEC_CLASS_DEV_NAME_TSP "tsp" +#define SEC_CLASS_DEV_NAME_TKEY "sec_touchkey" +#define SEC_CLASS_DEV_NAME_WACOM "sec_epen" +#define SEC_CLASS_DEV_NAME_SIDEKEY "sec_sidekey" + +#define SEC_CLASS_DEVT_TSP1 15 +#define SEC_CLASS_DEVT_TSP2 16 +#define SEC_CLASS_DEV_NAME_TSP1 "tsp1" +#define SEC_CLASS_DEV_NAME_TSP2 "tsp2" + +#define SEC_CMD(name, func) .cmd_name = name, .cmd_func = func +#define SEC_CMD_H(name, func) .cmd_name = name, .cmd_func = func, .cmd_log = 1 +#define SEC_CMD_V2(name, func, force_func, use_cases, wait_result) \ + .cmd_name = name, .cmd_func = func, .cmd_func_forced = force_func, \ + .cmd_use_cases = use_cases, .wait_read_result = wait_result +#define SEC_CMD_V2_H(name, func, force_func, use_cases, wait_result) \ + .cmd_name = name, .cmd_func = func, .cmd_func_forced = force_func, \ + .cmd_use_cases = use_cases, .wait_read_result = wait_result, .cmd_log = 1 + +#define SEC_CMD_BUF_SIZE (4096 - 1) +#define SEC_CMD_STR_LEN 256 +#define SEC_CMD_RESULT_STR_LEN (4096 - 1) +#define SEC_CMD_RESULT_STR_LEN_EXPAND (SEC_CMD_RESULT_STR_LEN * 6) +#define SEC_CMD_PARAM_NUM 8 + +#define WAIT_RESULT true +#define EXIT_RESULT false + +struct sec_cmd { + struct list_head list; + const char *cmd_name; + void (*cmd_func)(void *device_data); + int (*cmd_func_forced)(void *device_data); +/* + * cmd_use_cases using as below + * 1 : call function, 0 : no call + * first bit(1<<0) CHECK_POWEROFF + * second bit(1<<1) CHECK_LPMODE + * third bit(1<<2) CHECK_POWERON + */ + int cmd_use_cases; + bool wait_read_result; + int cmd_log; + bool not_support_cmds; +}; + +enum sec_cmd_status_uevent_type { + STATUS_TYPE_WET = 0, + STATUS_TYPE_NOISE, + STATUS_TYPE_FREQ, +}; + +enum SEC_CMD_STATUS { + SEC_CMD_STATUS_WAITING = 0, + SEC_CMD_STATUS_RUNNING, // = 1 + SEC_CMD_STATUS_OK, // = 2 + SEC_CMD_STATUS_FAIL, // = 3 + SEC_CMD_STATUS_EXPAND, // = 4 + SEC_CMD_STATUS_NOT_APPLICABLE, // = 5 +}; + +#define INPUT_CMD_RESULT_NOT_EXIT 0 +#define INPUT_CMD_RESULT_NEED_EXIT 1 + +#define input_cmd_result(cmd_state_parm, need_exit) \ +({ \ + if (need_exit == INPUT_CMD_RESULT_NEED_EXIT) { \ + if (cmd_state_parm == SEC_CMD_STATUS_OK) \ + snprintf(buff, sizeof(buff), "OK"); \ + else if (cmd_state_parm == SEC_CMD_STATUS_FAIL) \ + snprintf(buff, sizeof(buff), "NG"); \ + else if (cmd_state_parm == SEC_CMD_STATUS_NOT_APPLICABLE) \ + snprintf(buff, sizeof(buff), "NA"); \ + } \ + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); \ + sec->cmd_state = cmd_state_parm; \ + if (need_exit == INPUT_CMD_RESULT_NEED_EXIT) \ + sec_cmd_set_cmd_exit(sec); \ + input_info(true, ptsp, "%s: %s\n", __func__, buff); \ +}) + +#ifdef USE_SEC_CMD_QUEUE +#define SEC_CMD_MAX_QUEUE 10 + +struct command { + char cmd[SEC_CMD_STR_LEN]; +}; +#endif + +struct sec_cmd_data { + struct device *fac_dev; + struct device *dev; + struct list_head cmd_list_head; + u8 cmd_state; + char cmd[SEC_CMD_STR_LEN]; + int cmd_param[SEC_CMD_PARAM_NUM]; + char *cmd_result; + int cmd_result_expand; + int cmd_result_expand_count; + int cmd_buffer_size; + atomic_t cmd_is_running; + struct mutex cmd_lock; + struct mutex fs_lock; +#ifdef USE_SEC_CMD_QUEUE + struct kfifo cmd_queue; + struct mutex fifo_lock; + struct delayed_work cmd_work; + struct mutex wait_lock; + struct completion cmd_result_done; +#endif + bool wait_cmd_result_done; + int item_count; + char cmd_result_all[SEC_CMD_RESULT_STR_LEN]; + u8 cmd_all_factory_state; + struct attribute_group *vendor_attr_group; +}; + +void sec_cmd_set_cmd_exit(struct sec_cmd_data *data); +void sec_cmd_set_default_result(struct sec_cmd_data *data); +void sec_cmd_set_cmd_result(struct sec_cmd_data *data, char *buff, int len); +void sec_cmd_set_cmd_result_all(struct sec_cmd_data *data, char *buff, int len, char *item); +int sec_cmd_init(struct sec_cmd_data *data, struct device *dev, struct sec_cmd *cmds, + int len, int devt, struct attribute_group *vendor_attr_group); +int sec_cmd_init_without_platdata(struct sec_cmd_data *data, struct sec_cmd *cmds, + int len, int devt, struct attribute_group *vendor_attr_group); +void sec_cmd_exit(struct sec_cmd_data *data, int devt); +void sec_cmd_send_event_to_user(struct sec_cmd_data *data, char *test, char *result); +void sec_cmd_send_status_uevent(struct sec_cmd_data *data, enum sec_cmd_status_uevent_type type, int value); +void sec_cmd_send_gesture_uevent(struct sec_cmd_data *data, int type, int x, int y); + +#endif /* _SEC_CMD_H_ */ diff --git a/drivers/input/sec_input/sec_input.c b/drivers/input/sec_input/sec_input.c new file mode 100644 index 000000000000..07c11d00b4d4 --- /dev/null +++ b/drivers/input/sec_input/sec_input.c @@ -0,0 +1,2108 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Samsung Electronics Co., Ltd. + * + * 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. + */ + +#include "sec_input.h" + +static char *lcd_id; +module_param(lcd_id, charp, 0444); + +static char *lcd_id1; +module_param(lcd_id1, charp, 0444); + +struct device *ptsp; +EXPORT_SYMBOL(ptsp); + +struct sec_ts_secure_data *psecuretsp; +EXPORT_SYMBOL(psecuretsp); + +void sec_input_utc_marker(struct device *dev, const char *annotation) +{ + struct timespec64 ts; + struct rtc_time tm; + + ktime_get_real_ts64(&ts); + rtc_time64_to_tm(ts.tv_sec, &tm); + input_info(true, dev, "%s %d-%02d-%02d %02d:%02d:%02d.%09lu UTC\n", + annotation, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec); +} + +bool sec_input_cmp_ic_status(struct device *dev, int check_bit) +{ + struct sec_ts_plat_data *plat_data = dev->platform_data; + + if (MODE_TO_CHECK_BIT(atomic_read(&plat_data->power_state)) & check_bit) + return true; + + return false; +} +EXPORT_SYMBOL(sec_input_cmp_ic_status); + +bool sec_input_need_ic_off(struct sec_ts_plat_data *pdata) +{ + bool lpm = pdata->lowpower_mode || pdata->ed_enable || pdata->pocket_mode || pdata->fod_lp_mode || pdata->support_always_on; + + return (sec_input_need_fold_off(pdata->multi_dev) || !lpm); +} +EXPORT_SYMBOL(sec_input_need_ic_off); + +bool sec_check_secure_trusted_mode_status(struct sec_ts_plat_data *pdata) +{ +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) + if (atomic_read(&pdata->secure_enabled) == SECURE_TOUCH_ENABLE) { + input_err(true, pdata->dev, "%s: secure touch enabled\n", __func__); + return true; + } +#if IS_ENABLED(CONFIG_INPUT_SEC_TRUSTED_TOUCH) + if (!IS_ERR_OR_NULL(pdata->pvm)) { + if (atomic_read(&pdata->pvm->trusted_touch_enabled) != 0) { + input_err(true, pdata->dev, "%s: TVM is enabled\n", __func__); + return true; + } + } +#endif +#endif + return false; +} +EXPORT_SYMBOL(sec_check_secure_trusted_mode_status); + +static int sec_input_lcd_parse_panel_id(char *panel_id) +{ + char *pt; + int lcd_id_p = 0; + + if (IS_ERR_OR_NULL(panel_id)) + return lcd_id_p; + + for (pt = panel_id; *pt != 0; pt++) { + lcd_id_p <<= 4; + switch (*pt) { + case '0' ... '9': + lcd_id_p += *pt - '0'; + break; + case 'a' ... 'f': + lcd_id_p += 10 + *pt - 'a'; + break; + case 'A' ... 'F': + lcd_id_p += 10 + *pt - 'A'; + break; + } + } + return lcd_id_p; +} + +int sec_input_get_lcd_id(struct device *dev) +{ +#if !IS_ENABLED(CONFIG_SMCDSD_PANEL) + int lcdtype = 0; +#endif +#if IS_ENABLED(CONFIG_EXYNOS_DPU30) || IS_ENABLED(CONFIG_MCD_PANEL) || IS_ENABLED(CONFIG_USDM_PANEL) + int connected; +#endif + int lcd_id_param = 0; + int dev_id; + +#if IS_ENABLED(CONFIG_DISPLAY_SAMSUNG) + lcdtype = get_lcd_attached("GET"); + if (lcdtype == 0xFFFFFF) { + input_err(true, dev, "%s: lcd is not attached(GET)\n", __func__); + return -ENODEV; + } +#endif + +#if IS_ENABLED(CONFIG_EXYNOS_DPU30) || IS_ENABLED(CONFIG_MCD_PANEL) || IS_ENABLED(CONFIG_USDM_PANEL) + connected = get_lcd_info("connected"); + if (connected < 0) { + input_err(true, dev, "%s: Failed to get lcd info(connected)\n", __func__); + return -EINVAL; + } + + if (!connected) { + input_err(true, dev, "%s: lcd is disconnected(connected)\n", __func__); + return -ENODEV; + } + + input_info(true, dev, "%s: lcd is connected\n", __func__); + + lcdtype = get_lcd_info("id"); + if (lcdtype < 0) { + input_err(true, dev, "%s: Failed to get lcd info(id)\n", __func__); + return -EINVAL; + } +#endif +#if IS_ENABLED(CONFIG_SMCDSD_PANEL) + if (!lcdtype) { + input_err(true, dev, "%s: lcd is disconnected(lcdtype)\n", __func__); + return -ENODEV; + } +#endif + + dev_id = sec_input_multi_device_parse_dt(dev); + input_info(true, dev, "%s: foldable %s\n", __func__, GET_FOLD_STR(dev_id)); + if (dev_id == MULTI_DEV_SUB) + lcd_id_param = sec_input_lcd_parse_panel_id(lcd_id1); + else + lcd_id_param = sec_input_lcd_parse_panel_id(lcd_id); + + if (lcdtype <= 0 && lcd_id_param != 0) { + lcdtype = lcd_id_param; + if (lcdtype == 0xFFFFFF) { + input_err(true, dev, "%s: lcd is not attached(PARAM)\n", __func__); + return -ENODEV; + } + } + + input_info(true, dev, "%s: lcdtype 0x%08X\n", __func__, lcdtype); + return lcdtype; +} +EXPORT_SYMBOL(sec_input_get_lcd_id); + +void sec_input_probe_work_remove(struct sec_ts_plat_data *pdata) +{ + if (pdata == NULL) + return; + + if (!pdata->work_queue_probe_enabled) { + input_err(true, pdata->dev, "%s: work_queue_probe_enabled is false\n", __func__); + return; + } + + cancel_work_sync(&pdata->probe_work); + flush_workqueue(pdata->probe_workqueue); + destroy_workqueue(pdata->probe_workqueue); +} +EXPORT_SYMBOL(sec_input_probe_work_remove); + +static void sec_input_probe_work(struct work_struct *work) +{ + struct sec_ts_plat_data *pdata = container_of(work, struct sec_ts_plat_data, probe_work); + int ret = 0; + + if (pdata->probe == NULL) { + input_err(true, pdata->dev, "%s: probe function is null\n", __func__); + return; + } + + sec_delay(pdata->work_queue_probe_delay); + + ret = pdata->probe(pdata->dev); + if (pdata->first_booting_disabled && ret == 0) { + input_info(true, pdata->dev, "%s: first_booting_disabled.\n", __func__); + pdata->disable(pdata->dev); + } +} + +static void sec_input_handler_wait_resume_work(struct work_struct *work) +{ + struct sec_ts_plat_data *pdata = container_of(work, struct sec_ts_plat_data, irq_work); + unsigned int irq = gpio_to_irq(pdata->irq_gpio); + struct irq_desc *desc = irq_to_desc(irq); + int ret; + + ret = wait_for_completion_interruptible_timeout(&pdata->resume_done, + msecs_to_jiffies(SEC_TS_WAKE_LOCK_TIME)); + if (ret == 0) { + input_err(true, pdata->dev, "%s: LPM: pm resume is not handled\n", __func__); + goto out; + } + if (ret < 0) { + input_err(true, pdata->dev, "%s: LPM: -ERESTARTSYS if interrupted, %d\n", __func__, ret); + goto out; + } + + if (desc && desc->action && desc->action->thread_fn) { + input_info(true, pdata->dev, "%s: run irq thread\n", __func__); + desc->action->thread_fn(irq, desc->action->dev_id); + } +out: + sec_input_forced_enable_irq(irq); +} + +int sec_input_handler_start(struct device *dev) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + unsigned int irq = gpio_to_irq(pdata->irq_gpio); + struct irq_desc *desc = irq_to_desc(irq); + + if (desc && desc->action) { + if ((desc->action->flags & IRQF_TRIGGER_LOW) && (gpio_get_value(pdata->irq_gpio) == 1)) + return SEC_ERROR; + } + + if (sec_input_cmp_ic_status(dev, CHECK_LPMODE)) { + __pm_wakeup_event(pdata->sec_ws, SEC_TS_WAKE_LOCK_TIME); + + if (!pdata->resume_done.done) { + if (!IS_ERR_OR_NULL(pdata->irq_workqueue)) { + input_info(true, dev, "%s: disable irq and queue waiting work\n", __func__); + disable_irq_nosync(gpio_to_irq(pdata->irq_gpio)); + queue_work(pdata->irq_workqueue, &pdata->irq_work); + } else { + input_err(true, dev, "%s: irq_workqueue not exist\n", __func__); + } + return SEC_ERROR; + } + } + + return SEC_SUCCESS; +} +EXPORT_SYMBOL(sec_input_handler_start); + +/************************************************************ + * 720 * 1480 : <48 96 60> + * indicator: 24dp navigator:48dp edge:60px dpi=320 + * 1080 * 2220 : 4096 * 4096 : <133 266 341> (approximately value) + ************************************************************/ +static void location_detect(struct sec_ts_plat_data *pdata, int t_id) +{ + int x = pdata->coord[t_id].x, y = pdata->coord[t_id].y; + + memset(pdata->location, 0x00, SEC_TS_LOCATION_DETECT_SIZE); + + if (x < pdata->area_edge) + strlcat(pdata->location, "E.", SEC_TS_LOCATION_DETECT_SIZE); + else if (x < (pdata->max_x - pdata->area_edge)) + strlcat(pdata->location, "C.", SEC_TS_LOCATION_DETECT_SIZE); + else + strlcat(pdata->location, "e.", SEC_TS_LOCATION_DETECT_SIZE); + + if (y < pdata->area_indicator) + strlcat(pdata->location, "S", SEC_TS_LOCATION_DETECT_SIZE); + else if (y < (pdata->max_y - pdata->area_navigation)) + strlcat(pdata->location, "C", SEC_TS_LOCATION_DETECT_SIZE); + else + strlcat(pdata->location, "N", SEC_TS_LOCATION_DETECT_SIZE); +} + +void sec_delay(unsigned int ms) +{ + if (!ms) + return; + if (ms < 20) + usleep_range(ms * 1000, ms * 1000); + else + msleep(ms); +} +EXPORT_SYMBOL(sec_delay); + +int sec_input_set_temperature(struct device *dev, int state) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + int ret = 0; + u8 temperature_data = 0; + bool bforced = false; + + if (pdata->set_temperature == NULL) { + input_dbg(false, dev, "%s: vendor function is not allocated\n", __func__); + return SEC_ERROR; + } + + if (state == SEC_INPUT_SET_TEMPERATURE_NORMAL) { + if (pdata->touch_count) { + pdata->tsp_temperature_data_skip = true; + input_err(true, dev, "%s: skip, t_cnt(%d)\n", + __func__, pdata->touch_count); + return SEC_SUCCESS; + } + } else if (state == SEC_INPUT_SET_TEMPERATURE_IN_IRQ) { + if (pdata->touch_count != 0 || pdata->tsp_temperature_data_skip == false) + return SEC_SUCCESS; + } else if (state == SEC_INPUT_SET_TEMPERATURE_FORCE) { + bforced = true; + } else { + input_err(true, dev, "%s: invalid param %d\n", __func__, state); + return SEC_ERROR; + } + + pdata->tsp_temperature_data_skip = false; + + if (!pdata->psy) + pdata->psy = power_supply_get_by_name("battery"); + + if (!pdata->psy) { + input_err(true, dev, "%s: cannot find power supply\n", __func__); + return SEC_ERROR; + } + + ret = power_supply_get_property(pdata->psy, POWER_SUPPLY_PROP_TEMP, &pdata->psy_value); + if (ret < 0) { + input_err(true, dev, "%s: couldn't get temperature value, ret:%d\n", __func__, ret); + return ret; + } + + temperature_data = (u8)(pdata->psy_value.intval / 10); + + if (bforced || pdata->tsp_temperature_data != temperature_data) { + ret = pdata->set_temperature(dev, temperature_data); + if (ret < 0) { + input_err(true, dev, "%s: failed to write temperature %u, ret=%d\n", + __func__, temperature_data, ret); + return ret; + } + + pdata->tsp_temperature_data = temperature_data; + input_info(true, dev, "%s set temperature:%u\n", __func__, temperature_data); + } else { + input_dbg(true, dev, "%s skip temperature:%u\n", __func__, temperature_data); + } + + return SEC_SUCCESS; +} +EXPORT_SYMBOL(sec_input_set_temperature); + +void sec_input_set_grip_type(struct device *dev, u8 set_type) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + u8 mode = G_NONE; + + if (pdata->set_grip_data == NULL) { + input_dbg(true, dev, "%s: vendor function is not allocated\n", __func__); + return; + } + + input_info(true, dev, "%s: re-init grip(%d), edh:%d, edg:%d, lan:%d\n", __func__, + set_type, pdata->grip_data.edgehandler_direction, + pdata->grip_data.edge_range, pdata->grip_data.landscape_mode); + + if (pdata->grip_data.edgehandler_direction != 0) + mode |= G_SET_EDGE_HANDLER; + + if (set_type == GRIP_ALL_DATA) { + /* edge */ + if (pdata->grip_data.edge_range != 60) + mode |= G_SET_EDGE_ZONE; + + /* dead zone default 0 mode, 32 */ + if (pdata->grip_data.landscape_mode == 1) + mode |= G_SET_LANDSCAPE_MODE; + else + mode |= G_SET_NORMAL_MODE; + } + + if (mode) + pdata->set_grip_data(dev, mode); +} +EXPORT_SYMBOL(sec_input_set_grip_type); + +int sec_input_store_grip_data(struct device *dev, int *cmd_param) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + int mode = G_NONE; + + if (!pdata) + return -ENODEV; + + if (cmd_param[0] == 0) { + mode |= G_SET_EDGE_HANDLER; + if (cmd_param[1] == 0) { + pdata->grip_data.edgehandler_direction = 0; + input_info(true, dev, "%s: [edge handler] clear\n", __func__); + } else if (cmd_param[1] < 5) { + pdata->grip_data.edgehandler_direction = cmd_param[1]; + pdata->grip_data.edgehandler_start_y = cmd_param[2]; + pdata->grip_data.edgehandler_end_y = cmd_param[3]; + input_info(true, dev, "%s: [edge handler] dir:%d, range:%d,%d\n", __func__, + pdata->grip_data.edgehandler_direction, + pdata->grip_data.edgehandler_start_y, + pdata->grip_data.edgehandler_end_y); + } else { + input_err(true, dev, "%s: [edge handler] cmd1 is abnormal, %d\n", __func__, cmd_param[1]); + return -EINVAL; + } + } else if (cmd_param[0] == 1) { + if (pdata->grip_data.edge_range != cmd_param[1]) + mode = mode | G_SET_EDGE_ZONE; + pdata->grip_data.edge_range = cmd_param[1]; + pdata->grip_data.deadzone_up_x = cmd_param[2]; + pdata->grip_data.deadzone_dn_x = cmd_param[3]; + pdata->grip_data.deadzone_y = cmd_param[4]; + /* 3rd reject zone */ + pdata->grip_data.deadzone_dn2_x = cmd_param[5]; + pdata->grip_data.deadzone_dn_y = cmd_param[6]; + mode |= G_SET_NORMAL_MODE; + + if (pdata->grip_data.landscape_mode == 1) { + pdata->grip_data.landscape_mode = 0; + mode |= G_CLR_LANDSCAPE_MODE; + } + + /* + * w means width of divided by 3 zone (X1, X2, X3) + * h means height of divided by 3 zone (Y1, Y2) - y coordinate which is the point of divide line + */ + input_info(true, dev, "%s: [%sportrait] grip:%d | reject w:%d/%d/%d, h:%d/%d\n", + __func__, (mode & G_CLR_LANDSCAPE_MODE) ? "landscape->" : "", + pdata->grip_data.edge_range, pdata->grip_data.deadzone_up_x, + pdata->grip_data.deadzone_dn_x, pdata->grip_data.deadzone_dn2_x, + pdata->grip_data.deadzone_y, pdata->grip_data.deadzone_dn_y); + } else if (cmd_param[0] == 2) { + if (cmd_param[1] == 0) { + pdata->grip_data.landscape_mode = 0; + mode |= G_CLR_LANDSCAPE_MODE; + input_info(true, dev, "%s: [landscape] clear\n", __func__); + } else if (cmd_param[1] == 1) { + pdata->grip_data.landscape_mode = 1; + pdata->grip_data.landscape_edge = cmd_param[2]; + pdata->grip_data.landscape_deadzone = cmd_param[3]; + pdata->grip_data.landscape_top_deadzone = cmd_param[4]; + pdata->grip_data.landscape_bottom_deadzone = cmd_param[5]; + pdata->grip_data.landscape_top_gripzone = cmd_param[6]; + pdata->grip_data.landscape_bottom_gripzone = cmd_param[7]; + mode |= G_SET_LANDSCAPE_MODE; + + /* + * v means width of grip/reject zone of vertical both edge side + * h means height of grip/reject zone of horizontal top/bottom side (top/bottom) + */ + input_info(true, dev, "%s: [landscape] grip v:%d, h:%d/%d | reject v:%d, h:%d/%d\n", + __func__, pdata->grip_data.landscape_edge, + pdata->grip_data.landscape_top_gripzone, pdata->grip_data.landscape_bottom_gripzone, + pdata->grip_data.landscape_deadzone, pdata->grip_data.landscape_top_deadzone, + pdata->grip_data.landscape_bottom_deadzone); + } else { + input_err(true, dev, "%s: [landscape] cmd1 is abnormal, %d\n", __func__, cmd_param[1]); + return -EINVAL; + } + } else { + input_err(true, dev, "%s: cmd0 is abnormal, %d", __func__, cmd_param[0]); + return -EINVAL; + } + + return mode; +} +EXPORT_SYMBOL(sec_input_store_grip_data); + +int sec_input_check_cover_type(struct device *dev) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + int cover_cmd = 0; + + switch (pdata->cover_type) { + case SEC_TS_FLIP_COVER: + case SEC_TS_SVIEW_COVER: + case SEC_TS_SVIEW_CHARGER_COVER: + case SEC_TS_S_VIEW_WALLET_COVER: + case SEC_TS_LED_COVER: + case SEC_TS_CLEAR_COVER: + case SEC_TS_KEYBOARD_KOR_COVER: + case SEC_TS_KEYBOARD_US_COVER: + case SEC_TS_CLEAR_SIDE_VIEW_COVER: + case SEC_TS_MINI_SVIEW_WALLET_COVER: + case SEC_TS_MONTBLANC_COVER: + case SEC_TS_CLEAR_CAMERA_VIEW_COVER: + cover_cmd = pdata->cover_type; + break; + default: + input_err(true, dev, "%s: not change touch state, cover_type=%d\n", + __func__, pdata->cover_type); + break; + } + + return cover_cmd; +} +EXPORT_SYMBOL(sec_input_check_cover_type); + +void sec_input_set_fod_info(struct device *dev, int vi_x, int vi_y, int vi_size, int vi_event) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + int byte_size = vi_x * vi_y / 8; + + if (vi_x * vi_y % 8) + byte_size++; + + pdata->fod_data.vi_x = vi_x; + pdata->fod_data.vi_y = vi_y; + pdata->fod_data.vi_size = vi_size; + pdata->fod_data.vi_event = vi_event; + + if (byte_size != vi_size) + input_err(true, dev, "%s: NEED TO CHECK! vi size %d maybe wrong (byte size should be %d)\n", + __func__, vi_size, byte_size); + + input_info(true, dev, "%s: vi_event:%d, x:%d, y:%d, size:%d\n", + __func__, pdata->fod_data.vi_event, pdata->fod_data.vi_x, pdata->fod_data.vi_y, + pdata->fod_data.vi_size); +} +EXPORT_SYMBOL(sec_input_set_fod_info); + +ssize_t sec_input_get_fod_info(struct device *dev, char *buf) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + + if (!pdata->support_fod) { + input_err(true, dev, "%s: fod is not supported\n", __func__); + return snprintf(buf, SEC_CMD_BUF_SIZE, "NA"); + } + + if (pdata->x_node_num <= 0 || pdata->y_node_num <= 0) { + input_err(true, dev, "%s: x/y node num value is wrong\n", __func__); + return snprintf(buf, SEC_CMD_BUF_SIZE, "NG"); + } + + input_info(true, dev, "%s: x:%d/%d, y:%d/%d, size:%d\n", + __func__, pdata->fod_data.vi_x, pdata->x_node_num, + pdata->fod_data.vi_y, pdata->y_node_num, pdata->fod_data.vi_size); + + return snprintf(buf, SEC_CMD_BUF_SIZE, "%d,%d,%d,%d,%d", + pdata->fod_data.vi_x, pdata->fod_data.vi_y, + pdata->fod_data.vi_size, pdata->x_node_num, pdata->y_node_num); +} +EXPORT_SYMBOL(sec_input_get_fod_info); + +bool sec_input_set_fod_rect(struct device *dev, int *rect_data) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + int i; + + pdata->fod_data.set_val = 1; + + if (rect_data[0] <= 0 || rect_data[1] <= 0 || rect_data[2] <= 0 || rect_data[3] <= 0) + pdata->fod_data.set_val = 0; + + if (pdata->fod_data.set_val) + for (i = 0; i < 4; i++) + pdata->fod_data.rect_data[i] = rect_data[i]; + + return pdata->fod_data.set_val; +} +EXPORT_SYMBOL(sec_input_set_fod_rect); + +int sec_input_check_wirelesscharger_mode(struct device *dev, int mode, int force) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + + if (mode != TYPE_WIRELESS_CHARGER_NONE + && mode != TYPE_WIRELESS_CHARGER + && mode != TYPE_WIRELESS_BATTERY_PACK) { + input_err(true, dev, + "%s: invalid param %d\n", __func__, mode); + return SEC_ERROR; + } + + if (pdata->force_wirelesscharger_mode == true && force == 0) { + input_err(true, dev, + "%s: [force enable] skip %d\n", __func__, mode); + return SEC_SKIP; + } + + if (force == 1) { + if (mode == TYPE_WIRELESS_CHARGER_NONE) { + pdata->force_wirelesscharger_mode = false; + input_err(true, dev, + "%s: force enable off\n", __func__); + return SEC_SKIP; + } + + pdata->force_wirelesscharger_mode = true; + } + + pdata->wirelesscharger_mode = mode & 0xFF; + + return SEC_SUCCESS; +} +EXPORT_SYMBOL(sec_input_check_wirelesscharger_mode); + +ssize_t sec_input_get_common_hw_param(struct sec_ts_plat_data *pdata, char *buf) +{ + char buff[SEC_INPUT_HW_PARAM_SIZE]; + char tbuff[SEC_CMD_STR_LEN]; + char mdev[SEC_CMD_STR_LEN]; + + memset(mdev, 0x00, sizeof(mdev)); + if (GET_DEV_COUNT(pdata->multi_dev) == MULTI_DEV_SUB) + snprintf(mdev, sizeof(mdev), "%s", "2"); + else + snprintf(mdev, sizeof(mdev), "%s", ""); + + memset(buff, 0x00, sizeof(buff)); + + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "\"TITO%s\":\"%02X%02X%02X%02X\",", + mdev, pdata->hw_param.ito_test[0], pdata->hw_param.ito_test[1], + pdata->hw_param.ito_test[2], pdata->hw_param.ito_test[3]); + strlcat(buff, tbuff, sizeof(buff)); + + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "\"TWET%s\":\"%d\",", mdev, pdata->hw_param.wet_count); + strlcat(buff, tbuff, sizeof(buff)); + + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "\"TNOI%s\":\"%d\",", mdev, pdata->hw_param.noise_count); + strlcat(buff, tbuff, sizeof(buff)); + + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "\"TCOM%s\":\"%d\",", mdev, pdata->hw_param.comm_err_count); + strlcat(buff, tbuff, sizeof(buff)); + + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "\"TCHK%s\":\"%d\",", mdev, pdata->hw_param.checksum_result); + strlcat(buff, tbuff, sizeof(buff)); + + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "\"TRIC%s\":\"%d\"", mdev, pdata->hw_param.ic_reset_count); + strlcat(buff, tbuff, sizeof(buff)); + + if (GET_DEV_COUNT(pdata->multi_dev) != MULTI_DEV_SUB) { + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), ",\"TMUL\":\"%d\",", pdata->hw_param.multi_count); + strlcat(buff, tbuff, sizeof(buff)); + + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "\"TTCN\":\"%d\",\"TACN\":\"%d\",\"TSCN\":\"%d\",", + pdata->hw_param.all_finger_count, pdata->hw_param.all_aod_tap_count, + pdata->hw_param.all_spay_count); + strlcat(buff, tbuff, sizeof(buff)); + + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "\"TMCF\":\"%d\"", pdata->hw_param.mode_change_failed_count); + strlcat(buff, tbuff, sizeof(buff)); + } + + return snprintf(buf, SEC_INPUT_HW_PARAM_SIZE, "%s", buff); +} +EXPORT_SYMBOL(sec_input_get_common_hw_param); + +void sec_input_clear_common_hw_param(struct sec_ts_plat_data *pdata) +{ + pdata->hw_param.multi_count = 0; + pdata->hw_param.wet_count = 0; + pdata->hw_param.noise_count = 0; + pdata->hw_param.comm_err_count = 0; + pdata->hw_param.checksum_result = 0; + pdata->hw_param.all_finger_count = 0; + pdata->hw_param.all_aod_tap_count = 0; + pdata->hw_param.all_spay_count = 0; + pdata->hw_param.mode_change_failed_count = 0; + pdata->hw_param.ic_reset_count = 0; +} +EXPORT_SYMBOL(sec_input_clear_common_hw_param); + +void sec_input_print_info(struct device *dev, struct sec_tclm_data *tdata) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + unsigned int irq = gpio_to_irq(pdata->irq_gpio); + struct irq_desc *desc = irq_to_desc(irq); + char tclm_buff[INPUT_TCLM_LOG_BUF_SIZE] = { 0 }; + char fw_ver_prefix[7] = { 0 }; + + pdata->print_info_cnt_open++; + + if (pdata->print_info_cnt_open > 0xfff0) + pdata->print_info_cnt_open = 0; + + if (pdata->touch_count == 0) + pdata->print_info_cnt_release++; + +#if IS_ENABLED(CONFIG_INPUT_TOUCHSCREEN_TCLMV2) + if (tdata && (tdata->tclm_level == TCLM_LEVEL_NOT_SUPPORT)) + snprintf(tclm_buff, sizeof(tclm_buff), ""); + else if (tdata && tdata->tclm_string) + snprintf(tclm_buff, sizeof(tclm_buff), "C%02XT%04X.%4s%s Cal_flag:%d fail_cnt:%d", + tdata->nvdata.cal_count, tdata->nvdata.tune_fix_ver, + tdata->tclm_string[tdata->nvdata.cal_position].f_name, + (tdata->tclm_level == TCLM_LEVEL_LOCKDOWN) ? ".L" : " ", + tdata->nvdata.cal_fail_falg, tdata->nvdata.cal_fail_cnt); + else + snprintf(tclm_buff, sizeof(tclm_buff), "TCLM data is empty"); +#else + snprintf(tclm_buff, sizeof(tclm_buff), ""); +#endif + + if (pdata->ic_vendor_name[0] != 0) + snprintf(fw_ver_prefix, sizeof(fw_ver_prefix), "%c%c%02X%02X", + pdata->ic_vendor_name[0], pdata->ic_vendor_name[1], pdata->img_version_of_ic[0], pdata->img_version_of_ic[1]); + + input_info(true, dev, + "tc:%d noise:%d/%d ext_n:%d wet:%d wc:%d(f:%d) lp:%x fn:%04X/%04X irqd:%d ED:%d PK:%d LS:%d// v:%s%02X%02X %s chk:%d // tmp:%d // #%d %d\n", + pdata->touch_count, + atomic_read(&pdata->touch_noise_status), atomic_read(&pdata->touch_pre_noise_status), + pdata->external_noise_mode, pdata->wet_mode, + pdata->wirelesscharger_mode, pdata->force_wirelesscharger_mode, + pdata->lowpower_mode, pdata->touch_functions, pdata->ic_status, desc->depth, + pdata->ed_enable, pdata->pocket_mode, pdata->low_sensitivity_mode, + fw_ver_prefix, pdata->img_version_of_ic[2], pdata->img_version_of_ic[3], + tclm_buff, pdata->hw_param.checksum_result, + pdata->tsp_temperature_data, + pdata->print_info_cnt_open, pdata->print_info_cnt_release); +} +EXPORT_SYMBOL(sec_input_print_info); + +void sec_input_proximity_report(struct device *dev, int data) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + + if (!pdata->input_dev_proximity) + return; + + if (pdata->support_lightsensor_detect) { + if (data == PROX_EVENT_TYPE_LIGHTSENSOR_PRESS || data == PROX_EVENT_TYPE_LIGHTSENSOR_RELEASE) { + input_report_abs(pdata->input_dev_proximity, ABS_MT_CUSTOM, data); + input_sync(pdata->input_dev_proximity); + input_info(true, dev, "%s: LIGHTSENSOR(%d)\n", __func__, data & 1); + return; + } + } + + if (pdata->support_ear_detect) { + if (!(pdata->ed_enable || pdata->pocket_mode)) + return; + + input_report_abs(pdata->input_dev_proximity, ABS_MT_CUSTOM, data); + input_sync(pdata->input_dev_proximity); + input_info(true, dev, "%s: PROX(%d)\n", __func__, data); + } +} +EXPORT_SYMBOL(sec_input_proximity_report); + +void sec_input_gesture_report(struct device *dev, int id, int x, int y) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + char buff[SEC_TS_GESTURE_REPORT_BUFF_SIZE] = { 0 }; + + if (pdata->support_gesture_uevent) { + if (!IS_ERR_OR_NULL(pdata->sec)) + sec_cmd_send_gesture_uevent(pdata->sec, id, x, y); + return; + } + + pdata->gesture_id = id; + pdata->gesture_x = x; + pdata->gesture_y = y; + + input_report_key(pdata->input_dev, KEY_BLACK_UI_GESTURE, 1); + input_sync(pdata->input_dev); + input_report_key(pdata->input_dev, KEY_BLACK_UI_GESTURE, 0); + input_sync(pdata->input_dev); + + if (id == SPONGE_EVENT_TYPE_SPAY) { + snprintf(buff, sizeof(buff), "SPAY"); + pdata->hw_param.all_spay_count++; + } else if (id == SPONGE_EVENT_TYPE_SINGLE_TAP) { + snprintf(buff, sizeof(buff), "SINGLE TAP"); + } else if (id == SPONGE_EVENT_TYPE_AOD_DOUBLETAB) { + snprintf(buff, sizeof(buff), "AOD"); + pdata->hw_param.all_aod_tap_count++; + } else if (id == SPONGE_EVENT_TYPE_FOD_PRESS) { + snprintf(buff, sizeof(buff), "FOD PRESS"); + } else if (id == SPONGE_EVENT_TYPE_FOD_RELEASE) { + snprintf(buff, sizeof(buff), "FOD RELEASE"); + } else if (id == SPONGE_EVENT_TYPE_FOD_OUT) { + snprintf(buff, sizeof(buff), "FOD OUT"); + } else if (id == SPONGE_EVENT_TYPE_TSP_SCAN_UNBLOCK) { + snprintf(buff, sizeof(buff), "SCAN UNBLOCK"); + } else if (id == SPONGE_EVENT_TYPE_TSP_SCAN_BLOCK) { + snprintf(buff, sizeof(buff), "SCAN BLOCK"); + } else if (id == SPONGE_EVENT_TYPE_LONG_PRESS) { + snprintf(buff, sizeof(buff), "LONG PRESS"); + } else { + snprintf(buff, sizeof(buff), ""); + } + +#if IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP) + input_info(true, dev, "%s: %s: %d\n", __func__, buff, pdata->gesture_id); +#else + input_info(true, dev, "%s: %s: %d, %d, %d\n", + __func__, buff, pdata->gesture_id, pdata->gesture_x, pdata->gesture_y); +#endif +} +EXPORT_SYMBOL(sec_input_gesture_report); + +static void sec_input_coord_report(struct device *dev, u8 t_id) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + int action = pdata->coord[t_id].action; + + if (action == SEC_TS_COORDINATE_ACTION_RELEASE) { + input_mt_slot(pdata->input_dev, t_id); + input_mt_report_slot_state(pdata->input_dev, MT_TOOL_FINGER, 0); + + pdata->palm_flag &= ~(1 << t_id); + + if (pdata->touch_count > 0) + pdata->touch_count--; + if (pdata->touch_count == 0) { + input_report_key(pdata->input_dev, BTN_TOUCH, 0); + input_report_key(pdata->input_dev, BTN_TOOL_FINGER, 0); + pdata->hw_param.check_multi = 0; + pdata->print_info_cnt_release = 0; + pdata->palm_flag = 0; + } + if (pdata->blocking_palm) + input_report_key(pdata->input_dev, BTN_PALM, 0); + else + input_report_key(pdata->input_dev, BTN_PALM, pdata->palm_flag); + } else if (action == SEC_TS_COORDINATE_ACTION_PRESS || action == SEC_TS_COORDINATE_ACTION_MOVE) { + if (action == SEC_TS_COORDINATE_ACTION_PRESS) { + pdata->touch_count++; + pdata->coord[t_id].p_x = pdata->coord[t_id].x; + pdata->coord[t_id].p_y = pdata->coord[t_id].y; + + pdata->hw_param.all_finger_count++; + if ((pdata->touch_count > 4) && (pdata->hw_param.check_multi == 0)) { + pdata->hw_param.check_multi = 1; + pdata->hw_param.multi_count++; + } + } else { + /* action == SEC_TS_COORDINATE_ACTION_MOVE */ + pdata->coord[t_id].mcount++; + } + + input_mt_slot(pdata->input_dev, t_id); + input_mt_report_slot_state(pdata->input_dev, MT_TOOL_FINGER, 1); + input_report_key(pdata->input_dev, BTN_TOUCH, 1); + input_report_key(pdata->input_dev, BTN_TOOL_FINGER, 1); + if (pdata->blocking_palm) + input_report_key(pdata->input_dev, BTN_PALM, 0); + else + input_report_key(pdata->input_dev, BTN_PALM, pdata->palm_flag); + + input_report_abs(pdata->input_dev, ABS_MT_POSITION_X, pdata->coord[t_id].x); + input_report_abs(pdata->input_dev, ABS_MT_POSITION_Y, pdata->coord[t_id].y); + input_report_abs(pdata->input_dev, ABS_MT_TOUCH_MAJOR, pdata->coord[t_id].major); + input_report_abs(pdata->input_dev, ABS_MT_TOUCH_MINOR, pdata->coord[t_id].minor); + } +} + +static void sec_input_coord_log(struct device *dev, u8 t_id, int action) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + + location_detect(pdata, t_id); + + if (action == SEC_TS_COORDINATE_ACTION_PRESS) { +#if !IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP) + input_info(true, dev, + "[P] tID:%d.%d x:%d y:%d z:%d major:%d minor:%d loc:%s tc:%d type:%X noise:(%x,%d%d), nlvl:%d, maxS:%d, hid:%d, fid:%d fod:%x\n", + t_id, input_mt_get_value(&pdata->input_dev->mt->slots[t_id], ABS_MT_TRACKING_ID), + pdata->coord[t_id].x, pdata->coord[t_id].y, pdata->coord[t_id].z, + pdata->coord[t_id].major, pdata->coord[t_id].minor, + pdata->location, pdata->touch_count, + pdata->coord[t_id].ttype, + pdata->coord[t_id].noise_status, atomic_read(&pdata->touch_noise_status), + atomic_read(&pdata->touch_pre_noise_status), pdata->coord[t_id].noise_level, + pdata->coord[t_id].max_strength, pdata->coord[t_id].hover_id_num, + pdata->coord[t_id].freq_id, pdata->coord[t_id].fod_debug); +#else + input_info(true, dev, + "[P] tID:%d.%d z:%d major:%d minor:%d loc:%s tc:%d type:%X noise:(%x,%d%d), nlvl:%d, maxS:%d, hid:%d, fid:%d fod:%x\n", + t_id, input_mt_get_value(&pdata->input_dev->mt->slots[t_id], ABS_MT_TRACKING_ID), + pdata->coord[t_id].z, pdata->coord[t_id].major, + pdata->coord[t_id].minor, pdata->location, pdata->touch_count, + pdata->coord[t_id].ttype, + pdata->coord[t_id].noise_status, atomic_read(&pdata->touch_noise_status), + atomic_read(&pdata->touch_pre_noise_status), pdata->coord[t_id].noise_level, + pdata->coord[t_id].max_strength, pdata->coord[t_id].hover_id_num, + pdata->coord[t_id].freq_id, pdata->coord[t_id].fod_debug); +#endif + + } else if (action == SEC_TS_COORDINATE_ACTION_MOVE) { +#if !IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP) + input_info(true, dev, + "[M] tID:%d.%d x:%d y:%d z:%d major:%d minor:%d loc:%s tc:%d type:%X noise:(%x,%d%d), nlvl:%d, maxS:%d, hid:%d, fid:%d fod:%x\n", + t_id, input_mt_get_value(&pdata->input_dev->mt->slots[t_id], ABS_MT_TRACKING_ID), + pdata->coord[t_id].x, pdata->coord[t_id].y, pdata->coord[t_id].z, + pdata->coord[t_id].major, pdata->coord[t_id].minor, + pdata->location, pdata->touch_count, + pdata->coord[t_id].ttype, pdata->coord[t_id].noise_status, + atomic_read(&pdata->touch_noise_status), atomic_read(&pdata->touch_pre_noise_status), + pdata->coord[t_id].noise_level, pdata->coord[t_id].max_strength, + pdata->coord[t_id].hover_id_num, pdata->coord[t_id].freq_id, pdata->coord[t_id].fod_debug); +#else + input_info(true, dev, + "[M] tID:%d.%d z:%d major:%d minor:%d loc:%s tc:%d type:%X noise:(%x,%d%d), nlvl:%d, maxS:%d, hid:%d, fid:%d fod:%x\n", + t_id, input_mt_get_value(&pdata->input_dev->mt->slots[t_id], ABS_MT_TRACKING_ID), + pdata->coord[t_id].z, + pdata->coord[t_id].major, pdata->coord[t_id].minor, + pdata->location, pdata->touch_count, + pdata->coord[t_id].ttype, pdata->coord[t_id].noise_status, + atomic_read(&pdata->touch_noise_status), atomic_read(&pdata->touch_pre_noise_status), + pdata->coord[t_id].noise_level, pdata->coord[t_id].max_strength, + pdata->coord[t_id].hover_id_num, pdata->coord[t_id].freq_id, pdata->coord[t_id].fod_debug); +#endif + } else if (action == SEC_TS_COORDINATE_ACTION_RELEASE || action == SEC_TS_COORDINATE_ACTION_FORCE_RELEASE) { +#if !IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP) + input_info(true, dev, + "[R%s] tID:%d loc:%s dd:%d,%d mc:%d tc:%d lx:%d ly:%d p:%d noise:(%x,%d%d) nlvl:%d, maxS:%d, hid:%d, fid:%d fod:%x\n", + action == SEC_TS_COORDINATE_ACTION_FORCE_RELEASE ? "A" : "", + t_id, pdata->location, + pdata->coord[t_id].x - pdata->coord[t_id].p_x, + pdata->coord[t_id].y - pdata->coord[t_id].p_y, + pdata->coord[t_id].mcount, pdata->touch_count, + pdata->coord[t_id].x, pdata->coord[t_id].y, + pdata->coord[t_id].palm_count, + pdata->coord[t_id].noise_status, atomic_read(&pdata->touch_noise_status), + atomic_read(&pdata->touch_pre_noise_status), pdata->coord[t_id].noise_level, + pdata->coord[t_id].max_strength, pdata->coord[t_id].hover_id_num, + pdata->coord[t_id].freq_id, pdata->coord[t_id].fod_debug); +#else + input_info(true, dev, + "[R%s] tID:%d loc:%s dd:%d,%d mc:%d tc:%d p:%d noise:(%x,%d%d) nlvl:%d, maxS:%d, hid:%d, fid:%d fod:%x\n", + action == SEC_TS_COORDINATE_ACTION_FORCE_RELEASE ? "A" : "", + t_id, pdata->location, + pdata->coord[t_id].x - pdata->coord[t_id].p_x, + pdata->coord[t_id].y - pdata->coord[t_id].p_y, + pdata->coord[t_id].mcount, pdata->touch_count, + pdata->coord[t_id].palm_count, + pdata->coord[t_id].noise_status, atomic_read(&pdata->touch_noise_status), + atomic_read(&pdata->touch_pre_noise_status), pdata->coord[t_id].noise_level, + pdata->coord[t_id].max_strength, pdata->coord[t_id].hover_id_num, + pdata->coord[t_id].freq_id, pdata->coord[t_id].fod_debug); +#endif + } +} + +void sec_input_coord_event_fill_slot(struct device *dev, int t_id) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + + if (pdata->coord[t_id].action == SEC_TS_COORDINATE_ACTION_RELEASE) { + if (pdata->prev_coord[t_id].action == SEC_TS_COORDINATE_ACTION_NONE + || pdata->prev_coord[t_id].action == SEC_TS_COORDINATE_ACTION_RELEASE) { + input_err(true, dev, + "%s: tID %d released without press\n", __func__, t_id); + return; + } + + sec_input_coord_report(dev, t_id); + if ((pdata->touch_count == 0) && !IS_ERR_OR_NULL(&pdata->interrupt_notify_work.work)) { + if (pdata->interrupt_notify_work.work.func && list_empty(&pdata->interrupt_notify_work.work.entry)) + schedule_work(&pdata->interrupt_notify_work.work); + } + sec_input_coord_log(dev, t_id, SEC_TS_COORDINATE_ACTION_RELEASE); + + pdata->coord[t_id].action = SEC_TS_COORDINATE_ACTION_NONE; + pdata->coord[t_id].mcount = 0; + pdata->coord[t_id].palm_count = 0; + pdata->coord[t_id].noise_level = 0; + pdata->coord[t_id].max_strength = 0; + pdata->coord[t_id].hover_id_num = 0; + } else if (pdata->coord[t_id].action == SEC_TS_COORDINATE_ACTION_PRESS) { + sec_input_coord_report(dev, t_id); + if ((pdata->touch_count == 1) && !IS_ERR_OR_NULL(&pdata->interrupt_notify_work.work)) { + if (pdata->interrupt_notify_work.work.func && list_empty(&pdata->interrupt_notify_work.work.entry)) + schedule_work(&pdata->interrupt_notify_work.work); + } + sec_input_coord_log(dev, t_id, SEC_TS_COORDINATE_ACTION_PRESS); + } else if (pdata->coord[t_id].action == SEC_TS_COORDINATE_ACTION_MOVE) { + if (pdata->prev_coord[t_id].action == SEC_TS_COORDINATE_ACTION_NONE + || pdata->prev_coord[t_id].action == SEC_TS_COORDINATE_ACTION_RELEASE) { + pdata->coord[t_id].action = SEC_TS_COORDINATE_ACTION_PRESS; + sec_input_coord_report(dev, t_id); + sec_input_coord_log(dev, t_id, SEC_TS_COORDINATE_ACTION_MOVE); + } else { + sec_input_coord_report(dev, t_id); + } + } else { + input_dbg(true, dev, + "%s: do not support coordinate action(%d)\n", + __func__, pdata->coord[t_id].action); + } + + if ((pdata->coord[t_id].action == SEC_TS_COORDINATE_ACTION_PRESS) + || (pdata->coord[t_id].action == SEC_TS_COORDINATE_ACTION_MOVE)) { + if (pdata->coord[t_id].ttype != pdata->prev_coord[t_id].ttype) { + input_info(true, dev, "%s : tID:%d ttype(%x->%x)\n", + __func__, pdata->coord[t_id].id, + pdata->prev_coord[t_id].ttype, pdata->coord[t_id].ttype); + } + } + + pdata->fill_slot = true; +} +EXPORT_SYMBOL(sec_input_coord_event_fill_slot); + +void sec_input_coord_event_sync_slot(struct device *dev) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + + if (pdata->fill_slot) + input_sync(pdata->input_dev); + pdata->fill_slot = false; +} +EXPORT_SYMBOL(sec_input_coord_event_sync_slot); + +void sec_input_release_all_finger(struct device *dev) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + int i; + + if (!pdata->input_dev) + return; + + if (pdata->touch_count > 0) { + input_report_key(pdata->input_dev, KEY_INT_CANCEL, 1); + input_sync(pdata->input_dev); + input_report_key(pdata->input_dev, KEY_INT_CANCEL, 0); + input_sync(pdata->input_dev); + } + + if (pdata->input_dev_proximity) { + input_report_abs(pdata->input_dev_proximity, ABS_MT_CUSTOM, 0xff); + input_sync(pdata->input_dev_proximity); + } + + for (i = 0; i < SEC_TS_SUPPORT_TOUCH_COUNT; i++) { + input_mt_slot(pdata->input_dev, i); + input_mt_report_slot_state(pdata->input_dev, MT_TOOL_FINGER, false); + + if (pdata->coord[i].action == SEC_TS_COORDINATE_ACTION_PRESS + || pdata->coord[i].action == SEC_TS_COORDINATE_ACTION_MOVE) { + sec_input_coord_log(dev, i, SEC_TS_COORDINATE_ACTION_FORCE_RELEASE); + pdata->coord[i].action = SEC_TS_COORDINATE_ACTION_RELEASE; + } + + pdata->coord[i].mcount = 0; + pdata->coord[i].palm_count = 0; + pdata->coord[i].noise_level = 0; + pdata->coord[i].max_strength = 0; + pdata->coord[i].hover_id_num = 0; + } + + input_mt_slot(pdata->input_dev, 0); + + input_report_key(pdata->input_dev, BTN_PALM, false); + input_report_key(pdata->input_dev, BTN_LARGE_PALM, false); + input_report_key(pdata->input_dev, BTN_TOUCH, false); + input_report_key(pdata->input_dev, BTN_TOOL_FINGER, false); + pdata->palm_flag = 0; + pdata->touch_count = 0; + pdata->hw_param.check_multi = 0; + + input_sync(pdata->input_dev); +} +EXPORT_SYMBOL(sec_input_release_all_finger); + +static void sec_input_set_prop(struct device *dev, struct input_dev *input_dev, u8 propbit, void *data) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + + input_dev->phys = input_dev->name; + input_dev->id.bustype = BUS_I2C; + input_dev->dev.parent = dev; + + set_bit(EV_SYN, input_dev->evbit); + set_bit(EV_KEY, input_dev->evbit); + set_bit(EV_ABS, input_dev->evbit); + set_bit(EV_SW, input_dev->evbit); + set_bit(BTN_TOUCH, input_dev->keybit); + set_bit(BTN_TOOL_FINGER, input_dev->keybit); + set_bit(BTN_PALM, input_dev->keybit); + set_bit(BTN_LARGE_PALM, input_dev->keybit); + if (!pdata->support_gesture_uevent) + set_bit(KEY_BLACK_UI_GESTURE, input_dev->keybit); + set_bit(KEY_INT_CANCEL, input_dev->keybit); + + set_bit(propbit, input_dev->propbit); + set_bit(KEY_WAKEUP, input_dev->keybit); + set_bit(KEY_WATCH, input_dev->keybit); + + input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, pdata->max_x, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, pdata->max_y, 0, 0); + input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); + input_set_abs_params(input_dev, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0); + + if (propbit == INPUT_PROP_POINTER) + input_mt_init_slots(input_dev, SEC_TS_SUPPORT_TOUCH_COUNT, INPUT_MT_POINTER); + else + input_mt_init_slots(input_dev, SEC_TS_SUPPORT_TOUCH_COUNT, INPUT_MT_DIRECT); + + input_set_drvdata(input_dev, data); +} + +static void sec_input_set_prop_proximity(struct device *dev, struct input_dev *input_dev, void *data) +{ + input_dev->phys = input_dev->name; + input_dev->id.bustype = BUS_I2C; + input_dev->dev.parent = dev; + + set_bit(EV_SYN, input_dev->evbit); + set_bit(EV_SW, input_dev->evbit); + + set_bit(INPUT_PROP_DIRECT, input_dev->propbit); + + input_set_abs_params(input_dev, ABS_MT_CUSTOM, 0, 0xFFFFFFFF, 0, 0); + input_set_drvdata(input_dev, data); +} + +int sec_input_device_register(struct device *dev, void *data) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + int ret = 0; + + /* register input_dev */ + pdata->input_dev = devm_input_allocate_device(dev); + if (!pdata->input_dev) { + input_err(true, dev, "%s: allocate input_dev err!\n", __func__); + return -ENOMEM; + } + + if (GET_DEV_COUNT(pdata->multi_dev) == MULTI_DEV_SUB) + pdata->input_dev->name = "sec_touchscreen2"; + else + pdata->input_dev->name = "sec_touchscreen"; + sec_input_set_prop(dev, pdata->input_dev, INPUT_PROP_DIRECT, data); + + ret = input_register_device(pdata->input_dev); + if (ret) { + input_err(true, dev, "%s: Unable to register %s input device\n", + __func__, pdata->input_dev->name); + return ret; + } + + if (pdata->support_dex) { + /* register input_dev_pad */ + pdata->input_dev_pad = devm_input_allocate_device(dev); + if (!pdata->input_dev_pad) { + input_err(true, dev, "%s: allocate input_dev_pad err!\n", __func__); + return -ENOMEM; + } + + pdata->input_dev_pad->name = "sec_touchpad"; + sec_input_set_prop(dev, pdata->input_dev_pad, INPUT_PROP_POINTER, data); + ret = input_register_device(pdata->input_dev_pad); + if (ret) { + input_err(true, dev, "%s: Unable to register %s input device\n", + __func__, pdata->input_dev_pad->name); + return ret; + } + } + + if (pdata->support_ear_detect || pdata->support_lightsensor_detect) { + /* register input_dev_proximity */ + pdata->input_dev_proximity = devm_input_allocate_device(dev); + if (!pdata->input_dev_proximity) { + input_err(true, dev, "%s: allocate input_dev_proximity err!\n", __func__); + return -ENOMEM; + } + + if (GET_DEV_COUNT(pdata->multi_dev) == MULTI_DEV_SUB) + pdata->input_dev_proximity->name = "sec_touchproximity2"; + else + pdata->input_dev_proximity->name = "sec_touchproximity"; + sec_input_set_prop_proximity(dev, pdata->input_dev_proximity, data); + ret = input_register_device(pdata->input_dev_proximity); + if (ret) { + input_err(true, dev, "%s: Unable to register %s input device\n", + __func__, pdata->input_dev_proximity->name); + return ret; + } + } + + return 0; +} +EXPORT_SYMBOL(sec_input_device_register); + +int sec_input_pinctrl_configure(struct device *dev, bool on) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + struct pinctrl_state *state; + + input_info(true, dev, "%s: %s\n", __func__, on ? "ACTIVE" : "SUSPEND"); + + if (sec_check_secure_trusted_mode_status(pdata)) + return 0; + + if (on) { + state = pinctrl_lookup_state(pdata->pinctrl, "on_state"); + if (IS_ERR(pdata->pinctrl)) + input_err(true, dev, "%s: could not get active pinstate\n", __func__); + } else { + state = pinctrl_lookup_state(pdata->pinctrl, "off_state"); + if (IS_ERR(pdata->pinctrl)) + input_err(true, dev, "%s: could not get suspend pinstate\n", __func__); + } + + if (!IS_ERR_OR_NULL(state)) + return pinctrl_select_state(pdata->pinctrl, state); + + return 0; +} +EXPORT_SYMBOL(sec_input_pinctrl_configure); + +int sec_input_power(struct device *dev, bool on) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + int ret = 0; + + if (pdata->power_enabled == on) { + input_info(true, dev, "%s: power_enabled %d\n", __func__, pdata->power_enabled); + return ret; + } + + if (on) { + if (!pdata->not_support_io_ldo) { + ret = regulator_enable(pdata->dvdd); + if (ret) { + input_err(true, dev, "%s: Failed to enable dvdd: %d\n", __func__, ret); + goto out; + } + + sec_delay(1); + } + ret = regulator_enable(pdata->avdd); + if (ret) { + input_err(true, dev, "%s: Failed to enable avdd: %d\n", __func__, ret); + goto out; + } + } else { + regulator_disable(pdata->avdd); + if (!pdata->not_support_io_ldo) { + sec_delay(4); + regulator_disable(pdata->dvdd); + } + } + + pdata->power_enabled = on; + +out: + if (!pdata->not_support_io_ldo) { + input_info(true, dev, "%s: %s: avdd:%s, dvdd:%s\n", __func__, on ? "on" : "off", + regulator_is_enabled(pdata->avdd) ? "on" : "off", + regulator_is_enabled(pdata->dvdd) ? "on" : "off"); + } else { + input_info(true, dev, "%s: %s: avdd:%s\n", __func__, on ? "on" : "off", + regulator_is_enabled(pdata->avdd) ? "on" : "off"); + } + + return ret; +} +EXPORT_SYMBOL(sec_input_power); + +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) +static int sec_input_ccic_notification(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct sec_ts_plat_data *pdata = container_of(nb, struct sec_ts_plat_data, ccic_nb); + PD_NOTI_USB_STATUS_TYPEDEF usb_status = *(PD_NOTI_USB_STATUS_TYPEDEF *)data; + + if (pdata->dev == NULL) { + pr_err("%s %s: dev is null\n", SECLOG, __func__); + return 0; + } + + if (usb_status.dest != PDIC_NOTIFY_DEV_USB) + return 0; + + switch (usb_status.drp) { + case USB_STATUS_NOTIFY_ATTACH_DFP: + pdata->otg_flag = true; + input_info(true, pdata->dev, "%s: otg_flag %d\n", __func__, pdata->otg_flag); + break; + case USB_STATUS_NOTIFY_DETACH: + pdata->otg_flag = false; + input_info(true, pdata->dev, "%s: otg_flag %d\n", __func__, pdata->otg_flag); + break; + default: + break; + } + return 0; +} +#endif +static int sec_input_vbus_notification(struct notifier_block *nb, + unsigned long cmd, void *data) +{ + struct sec_ts_plat_data *pdata = container_of(nb, struct sec_ts_plat_data, vbus_nb); + vbus_status_t vbus_type = *(vbus_status_t *) data; + + if (pdata->dev == NULL) { + pr_err("%s %s: dev is null\n", SECLOG, __func__); + return 0; + } + + input_info(true, pdata->dev, "%s: cmd=%lu, vbus_type=%d, otg_flag:%d\n", + __func__, cmd, vbus_type, pdata->otg_flag); + + if (atomic_read(&pdata->shutdown_called)) + return 0; + + switch (vbus_type) { + case STATUS_VBUS_HIGH: + if (!pdata->otg_flag) + pdata->charger_flag = true; + else + return 0; + break; + case STATUS_VBUS_LOW: + pdata->charger_flag = false; + break; + default: + return 0; + } + + queue_work(pdata->vbus_notifier_workqueue, &pdata->vbus_notifier_work); + + return 0; +} + +static void sec_input_vbus_notification_work(struct work_struct *work) +{ + struct sec_ts_plat_data *pdata = container_of(work, struct sec_ts_plat_data, vbus_notifier_work); + int ret = 0; + + if (pdata->dev == NULL) { + pr_err("%s %s: dev is null\n", SECLOG, __func__); + return; + } + + if (pdata->set_charger_mode == NULL) { + input_err(true, pdata->dev, "%s: set_charger_mode function is not allocated\n", __func__); + return; + } + + if (atomic_read(&pdata->shutdown_called)) + return; + + input_info(true, pdata->dev, "%s: charger_flag:%d\n", __func__, pdata->charger_flag); + + ret = pdata->set_charger_mode(pdata->dev, pdata->charger_flag); + if (ret < 0) { + input_info(true, pdata->dev, "%s: failed to set charger_flag\n", __func__); + return; + } +} +#endif + +void sec_input_register_vbus_notifier(struct device *dev) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + + if (!pdata->support_vbus_notifier) + return; + + input_info(true, dev, "%s\n", __func__); + + pdata->otg_flag = false; + pdata->charger_flag = false; + +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + manager_notifier_register(&pdata->ccic_nb, sec_input_ccic_notification, + MANAGER_NOTIFY_PDIC_INITIAL); + input_info(true, dev, "%s: register ccic notification\n", __func__); +#endif + pdata->vbus_notifier_workqueue = create_singlethread_workqueue("sec_input_vbus_noti"); + INIT_WORK(&pdata->vbus_notifier_work, sec_input_vbus_notification_work); + vbus_notifier_register(&pdata->vbus_nb, sec_input_vbus_notification, VBUS_NOTIFY_DEV_CHARGER); + input_info(true, dev, "%s: register vbus notification\n", __func__); +#endif + +} +EXPORT_SYMBOL(sec_input_register_vbus_notifier); + +void sec_input_unregister_vbus_notifier(struct device *dev) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + + if (!pdata->support_vbus_notifier) + return; + + input_info(true, dev, "%s\n", __func__); + +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + manager_notifier_unregister(&pdata->ccic_nb); +#endif + vbus_notifier_unregister(&pdata->vbus_nb); + cancel_work_sync(&pdata->vbus_notifier_work); + flush_workqueue(pdata->vbus_notifier_workqueue); + destroy_workqueue(pdata->vbus_notifier_workqueue); +#endif +} +EXPORT_SYMBOL(sec_input_unregister_vbus_notifier); + +void sec_input_support_feature_parse_dt(struct device *dev) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + struct device_node *np = dev->of_node; + + pdata->dev = dev; + pdata->regulator_boot_on = of_property_read_bool(np, "sec,regulator_boot_on"); + pdata->support_dex = of_property_read_bool(np, "support_dex_mode"); + pdata->support_fod = of_property_read_bool(np, "support_fod"); + pdata->support_fod_lp_mode = of_property_read_bool(np, "support_fod_lp_mode"); + pdata->enable_settings_aot = of_property_read_bool(np, "enable_settings_aot"); + pdata->support_refresh_rate_mode = of_property_read_bool(np, "support_refresh_rate_mode"); + pdata->support_vrr = of_property_read_bool(np, "support_vrr"); + pdata->support_ear_detect = of_property_read_bool(np, "support_ear_detect_mode"); + pdata->support_open_short_test = of_property_read_bool(np, "support_open_short_test"); + pdata->support_mis_calibration_test = of_property_read_bool(np, "support_mis_calibration_test"); + pdata->support_wireless_tx = of_property_read_bool(np, "support_wireless_tx"); + pdata->support_input_monitor = of_property_read_bool(np, "support_input_monitor"); + pdata->disable_vsync_scan = of_property_read_bool(np, "disable_vsync_scan"); + pdata->sense_off_when_cover_closed = of_property_read_bool(np, "sense_off_when_cover_closed"); + pdata->not_support_temp_noti = of_property_read_bool(np, "not_support_temp_noti"); + pdata->support_vbus_notifier = of_property_read_bool(np, "support_vbus_notifier"); + pdata->support_gesture_uevent = of_property_read_bool(np, "support_gesture_uevent"); + pdata->support_always_on = of_property_read_bool(np, "support_always_on"); + + if (of_property_read_u32(np, "support_rawdata_map_num", &pdata->support_rawdata_map_num) < 0) + pdata->support_rawdata_map_num = 0; + + if (of_property_read_u32(np, "sec,support_sensor_hall", &pdata->support_sensor_hall) < 0) + pdata->support_sensor_hall = 0; + pdata->support_lightsensor_detect = of_property_read_bool(np, "support_lightsensor_detect"); + + pdata->prox_lp_scan_enabled = of_property_read_bool(np, "sec,prox_lp_scan_enabled"); + input_info(true, dev, "%s: Prox LP Scan enabled %s\n", + __func__, pdata->prox_lp_scan_enabled ? "ON" : "OFF"); + + pdata->enable_sysinput_enabled = of_property_read_bool(np, "sec,enable_sysinput_enabled"); + input_info(true, dev, "%s: Sysinput enabled %s\n", + __func__, pdata->enable_sysinput_enabled ? "ON" : "OFF"); + + pdata->support_rawdata = of_property_read_bool(np, "sec,support_rawdata"); + input_info(true, dev, "%s: report rawdata %s\n", + __func__, pdata->support_rawdata ? "ON" : "OFF"); + pdata->support_rawdata_motion_aivf = of_property_read_bool(np, "sec,support_rawdata_motion_aivf"); + input_info(true, dev, "%s: motion aivf %s\n", + __func__, pdata->support_rawdata_motion_aivf ? "ON" : "OFF"); + pdata->support_rawdata_motion_palm = of_property_read_bool(np, "sec,support_rawdata_motion_palm"); + input_info(true, dev, "%s: motion palm %s\n", + __func__, pdata->support_rawdata_motion_palm ? "ON" : "OFF"); + + input_info(true, dev, "dex:%d, max(%d/%d), FOD:%d, AOT:%d, ED:%d, FLM:%d,\n", + pdata->support_dex, pdata->max_x, pdata->max_y, + pdata->support_fod, pdata->enable_settings_aot, + pdata->support_ear_detect, pdata->support_fod_lp_mode); + input_info(true, dev, "COB:%d, disable_vsync_scan:%d,\n", + pdata->chip_on_board, pdata->disable_vsync_scan); + input_info(true, dev, "not_support_temp_noti:%d, support_vbus_notifier:%d support_always_on:%d\n", + pdata->not_support_temp_noti, pdata->support_vbus_notifier, pdata->support_always_on); +} +EXPORT_SYMBOL(sec_input_support_feature_parse_dt); + +int sec_input_parse_dt(struct device *dev) +{ + struct sec_ts_plat_data *pdata; + struct device_node *np; + u32 coords[2]; + int ret = 0; + int count = 0, i; + u32 px_zone[3] = { 0 }; + int lcd_type = 0; + u32 lcd_bitmask[10] = { 0 }; + + if (IS_ERR_OR_NULL(dev)) { + input_err(true, dev, "%s: dev is null\n", __func__); + return -ENODEV; + } + pdata = dev->platform_data; + np = dev->of_node; + + pdata->dev = dev; + pdata->chip_on_board = of_property_read_bool(np, "chip_on_board"); + + lcd_type = sec_input_get_lcd_id(dev); + if (lcd_type < 0) { + input_err(true, dev, "%s: lcd is not attached\n", __func__); + if (!pdata->chip_on_board) + return -ENODEV; + } + + input_info(true, dev, "%s: lcdtype 0x%06X\n", __func__, lcd_type); + + /* + * usage of sec,bitmask_unload + * bitmask[0] : masking bit + * bitmask[1],[2]... : target bit (max 9) + * ie) sec,bitmask_unload = <0x00FF00 0x008100 0x008200>; + * -> unload lcd id XX81XX, XX82XX + */ + count = of_property_count_u32_elems(np, "sec,bitmask_unload"); + if (lcd_type != 0 && count > 1 && count < 10 && + (!of_property_read_u32_array(np, "sec,bitmask_unload", lcd_bitmask, count))) { + input_info(true, dev, "%s: bitmask_unload: 0x%06X, check %d type\n", + __func__, lcd_bitmask[0], count - 1); + for (i = 1; i < count; i++) { + if ((lcd_type & lcd_bitmask[0]) == lcd_bitmask[i]) { + input_err(true, dev, + "%s: do not load lcdtype:0x%06X masked:0x%06X\n", + __func__, lcd_type, lcd_bitmask[i]); + return -ENODEV; + } + } + } + + mutex_init(&pdata->irq_lock); + + pdata->irq_gpio = of_get_named_gpio_flags(np, "sec,irq_gpio", 0, &pdata->irq_flag); + if (gpio_is_valid(pdata->irq_gpio)) { + char label[15] = { 0 }; + + snprintf(label, sizeof(label), "sec,%s", dev_driver_string(dev)); + ret = devm_gpio_request_one(dev, pdata->irq_gpio, GPIOF_DIR_IN, label); + if (ret) { + input_err(true, dev, "%s: Unable to request %s [%d]\n", __func__, label, pdata->irq_gpio); + return -EINVAL; + } + input_info(true, dev, "%s: irq gpio requested. %s [%d]\n", __func__, label, pdata->irq_gpio); + if (pdata->irq_flag) + input_info(true, dev, "%s: irq flag 0x%02X\n", __func__, pdata->irq_flag); + pdata->irq = gpio_to_irq(pdata->irq_gpio); + } else { + input_err(true, dev, "%s: Failed to get irq gpio\n", __func__); + return -EINVAL; + } + + pdata->gpio_spi_cs = of_get_named_gpio(np, "sec,gpio_spi_cs", 0); + if (gpio_is_valid(pdata->gpio_spi_cs)) { + ret = gpio_request(pdata->gpio_spi_cs, "tsp,gpio_spi_cs"); + input_info(true, dev, "%s: gpio_spi_cs: %d, ret: %d\n", __func__, pdata->gpio_spi_cs, ret); + } + + if (of_property_read_u32(np, "sec,i2c-burstmax", &pdata->i2c_burstmax)) { + input_dbg(false, dev, "%s: Failed to get i2c_burstmax property\n", __func__); + pdata->i2c_burstmax = 0xffff; + } + + if (of_property_read_u32_array(np, "sec,max_coords", coords, 2)) { + input_err(true, dev, "%s: Failed to get max_coords property\n", __func__); + return -EINVAL; + } + pdata->max_x = coords[0] - 1; + pdata->max_y = coords[1] - 1; + + if (of_property_read_u32(np, "sec,bringup", &pdata->bringup) < 0) + pdata->bringup = 0; + + count = of_property_count_strings(np, "sec,firmware_name"); + if (count <= 0) { + pdata->firmware_name = NULL; + } else { + u8 lcd_id_num = of_property_count_u32_elems(np, "sec,select_lcdid"); + + if ((lcd_id_num != count) || (lcd_id_num <= 0)) { + of_property_read_string_index(np, "sec,firmware_name", 0, &pdata->firmware_name); + } else { + u32 *lcd_id_t; + u32 lcd_id_mask; + + lcd_id_t = kcalloc(lcd_id_num, sizeof(u32), GFP_KERNEL); + if (!lcd_id_t) + return -ENOMEM; + + of_property_read_u32_array(np, "sec,select_lcdid", lcd_id_t, lcd_id_num); + if (of_property_read_u32(np, "sec,lcdid_mask", &lcd_id_mask) < 0) + lcd_id_mask = 0xFFFFFF; + else + input_info(true, dev, "%s: lcd_id_mask: 0x%06X\n", __func__, lcd_id_mask); + + for (i = 0; i < lcd_id_num; i++) { + if (((lcd_id_t[i] & lcd_id_mask) == (lcd_type & lcd_id_mask)) || (i == (lcd_id_num - 1))) { + of_property_read_string_index(np, "sec,firmware_name", i, &pdata->firmware_name); + break; + } + } + if (!pdata->firmware_name) + pdata->bringup = 1; + else if (strlen(pdata->firmware_name) == 0) + pdata->bringup = 1; + + input_info(true, dev, "%s: count: %d, index:%d, lcd_id: 0x%X, firmware: %s\n", + __func__, count, i, lcd_id_t[i], pdata->firmware_name); + kfree(lcd_id_t); + } + + if (pdata->bringup == 4) + pdata->bringup = 3; + } + + pdata->not_support_vdd = of_property_read_bool(np, "not_support_vdd"); + if (!pdata->not_support_vdd) { + const char *avdd_name, *dvdd_name; + + pdata->not_support_io_ldo = of_property_read_bool(np, "not_support_io_ldo"); + if (!pdata->not_support_io_ldo) { + if (of_property_read_string(np, "sec,dvdd_name", &dvdd_name) < 0) + dvdd_name = SEC_INPUT_DEFAULT_DVDD_NAME; + input_info(true, dev, "%s: get dvdd: %s\n", __func__, dvdd_name); + + pdata->dvdd = devm_regulator_get(dev, dvdd_name); + if (IS_ERR_OR_NULL(pdata->dvdd)) { + input_err(true, dev, "%s: Failed to get %s regulator.\n", + __func__, dvdd_name); +#if !IS_ENABLED(CONFIG_QGKI) + if (gpio_is_valid(pdata->gpio_spi_cs)) + gpio_free(pdata->gpio_spi_cs); +#endif + return -EINVAL; + } + } + + if (of_property_read_string(np, "sec,avdd_name", &avdd_name) < 0) + avdd_name = SEC_INPUT_DEFAULT_AVDD_NAME; + input_info(true, dev, "%s: get avdd: %s\n", __func__, avdd_name); + + pdata->avdd = devm_regulator_get(dev, avdd_name); + if (IS_ERR_OR_NULL(pdata->avdd)) { + input_err(true, dev, "%s: Failed to get %s regulator.\n", + __func__, avdd_name); +#if !IS_ENABLED(CONFIG_QGKI) + if (gpio_is_valid(pdata->gpio_spi_cs)) + gpio_free(pdata->gpio_spi_cs); +#endif + return -EINVAL; + } + } + if (of_property_read_u32(np, "sec,dump_ic_ver", &pdata->dump_ic_ver) < 0) + pdata->dump_ic_ver = 0; + if (of_property_read_u32_array(np, "sec,area-size", px_zone, 3)) { + input_info(true, dev, "Failed to get zone's size\n"); + pdata->area_indicator = 48; + pdata->area_navigation = 96; + pdata->area_edge = 60; + } else { + pdata->area_indicator = px_zone[0]; + pdata->area_navigation = px_zone[1]; + pdata->area_edge = px_zone[2]; + } + input_info(true, dev, "%s : zone's size - indicator:%d, navigation:%d, edge:%d\n", + __func__, pdata->area_indicator, pdata->area_navigation, pdata->area_edge); + +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) + of_property_read_u32(np, "sec,ss_touch_num", &pdata->ss_touch_num); + input_err(true, dev, "%s: ss_touch_num:%d\n", __func__, pdata->ss_touch_num); +#endif + input_info(true, dev, "%s: i2c buffer limit: %d, lcd_id:%06X, bringup:%d,\n", + __func__, pdata->i2c_burstmax, lcd_type, pdata->bringup); + + pdata->irq_workqueue = create_singlethread_workqueue("sec_input_irq_wq"); + if (!IS_ERR_OR_NULL(pdata->irq_workqueue)) { + SEC_INPUT_INIT_WORK(dev, &pdata->irq_work, sec_input_handler_wait_resume_work); + input_info(true, dev, "%s: set sec_input_handler_wait_resume_work\n", __func__); + } else { + input_err(true, dev, "%s: failed to create irq_workqueue, err: %ld\n", + __func__, PTR_ERR(pdata->irq_workqueue)); + } + + pdata->work_queue_probe_enabled = of_property_read_bool(np, "work_queue_probe_enabled"); + if (pdata->work_queue_probe_enabled) { + pdata->probe_workqueue = create_singlethread_workqueue("sec_tsp_probe_wq"); + if (!IS_ERR_OR_NULL(pdata->probe_workqueue)) { + INIT_WORK(&pdata->probe_work, sec_input_probe_work); + input_info(true, dev, "%s: set sec_input_probe_work\n", __func__); + } else { + pdata->work_queue_probe_enabled = false; + input_err(true, dev, "%s: failed to create probe_work, err: %ld enabled:%s\n", + __func__, PTR_ERR(pdata->probe_workqueue), + pdata->work_queue_probe_enabled ? "ON" : "OFF"); + } + } + + if (of_property_read_u32(np, "sec,probe_queue_delay", &pdata->work_queue_probe_delay)) { + input_dbg(false, dev, "%s: Failed to get work_queue_probe_delay property\n", __func__); + pdata->work_queue_probe_delay = 0; + } + + + sec_input_support_feature_parse_dt(dev); + + return 0; +} +EXPORT_SYMBOL(sec_input_parse_dt); + +int sec_input_multi_device_parse_dt(struct device *dev) +{ + struct device_node *np = dev->of_node; + int device_count; + + if (!np) + return 0; + + if (of_property_read_u32(np, "sec,multi_device_count", &device_count) < 0) + device_count = MULTI_DEV_NONE; + + input_info(true, dev, "%s: device_count:%d\n", __func__, device_count); + + return device_count; +} +EXPORT_SYMBOL(sec_input_multi_device_parse_dt); + +void sec_tclm_parse_dt(struct device *dev, struct sec_tclm_data *tdata) +{ + struct device_node *np = dev->of_node; + + if (of_property_read_u32(np, "sec,tclm_level", &tdata->tclm_level) < 0) { + tdata->tclm_level = 0; + input_err(true, dev, "%s: Failed to get tclm_level property\n", __func__); + } + + if (of_property_read_u32(np, "sec,afe_base", &tdata->afe_base) < 0) { + tdata->afe_base = 0; + input_err(true, dev, "%s: Failed to get afe_base property\n", __func__); + } + + tdata->support_tclm_test = of_property_read_bool(np, "support_tclm_test"); + + input_err(true, dev, "%s: tclm_level %d, sec_afe_base %04X\n", __func__, tdata->tclm_level, tdata->afe_base); +} +EXPORT_SYMBOL(sec_tclm_parse_dt); + +int sec_input_check_fw_name_incell(struct device *dev, const char **firmware_name, const char **firmware_name_2nd) +{ + struct device_node *np = dev->of_node; + int lcd_id1_gpio = 0, lcd_id2_gpio = 0, lcd_id3_gpio = 0, dt_lcdtype = 0; + int fw_name_cnt = 0; + int lcdtype_cnt = 0; + int fw_sel_idx = 0; + int lcdtype = 0; + int vendor_chk_gpio = 0; + int ret = 0; + + if (!np) + return -ENODEV; + + vendor_chk_gpio = of_get_named_gpio(np, "sec,vendor_check-gpio", 0); + if (gpio_is_valid(vendor_chk_gpio)) { + int vendor_chk_en_val = 0; + int vendor_chk_gpio_val = gpio_get_value(vendor_chk_gpio); + + ret = of_property_read_u32(np, "sec,vendor_check_enable_value", &vendor_chk_en_val); + if (ret < 0) { + input_err(true, dev, "%s: Unable to get vendor_check_enable_value, %d\n", __func__, ret); + return -ENODEV; + } + + if (vendor_chk_gpio_val != vendor_chk_en_val) { + input_err(true, dev, "%s: tsp ic mismated (%d) != (%d)\n", + __func__, vendor_chk_gpio_val, vendor_chk_en_val); + return -ENODEV; + } + + input_info(true, dev, "%s: lcd vendor_check_gpio %d (%d) == vendor_check_enable_value(%d)\n", + __func__, vendor_chk_gpio, vendor_chk_gpio_val, vendor_chk_en_val); + } else + input_info(true, dev, "%s: Not support vendor_check-gpio\n", __func__); + + lcdtype = sec_input_get_lcd_id(dev); + if (lcdtype < 0) { + input_err(true, dev, "lcd is not attached\n"); + return -ENODEV; + } + + fw_name_cnt = of_property_count_strings(np, "sec,fw_name"); + + if (fw_name_cnt == 0) { + input_err(true, dev, "%s: no fw_name in DT\n", __func__); + return -EINVAL; + + } else if (fw_name_cnt == 1) { + ret = of_property_read_u32(np, "sec,lcdtype", &dt_lcdtype); + if (ret < 0) + input_err(true, dev, "%s: failed to read lcdtype in DT\n", __func__); + else { + input_info(true, dev, "%s: fw_name_cnt(1), ap lcdtype=0x%06X & dt lcdtype=0x%06X\n", + __func__, lcdtype, dt_lcdtype); + if (lcdtype != dt_lcdtype) { + input_err(true, dev, "%s: panel mismatched, unload driver\n", __func__); + return -EINVAL; + } + } + fw_sel_idx = 0; + + } else { + + lcd_id1_gpio = of_get_named_gpio(np, "sec,lcdid1-gpio", 0); + if (gpio_is_valid(lcd_id1_gpio)) + input_info(true, dev, "%s: lcd sec,id1_gpio %d(%d)\n", __func__, lcd_id1_gpio, gpio_get_value(lcd_id1_gpio)); + else + input_err(true, dev, "%s: Failed to get sec,lcdid1-gpio\n", __func__); + + lcd_id2_gpio = of_get_named_gpio(np, "sec,lcdid2-gpio", 0); + if (gpio_is_valid(lcd_id2_gpio)) + input_info(true, dev, "%s: lcd sec,id2_gpio %d(%d)\n", __func__, lcd_id2_gpio, gpio_get_value(lcd_id2_gpio)); + else + input_err(true, dev, "%s: Failed to get sec,lcdid2-gpio\n", __func__); + + lcd_id3_gpio = of_get_named_gpio(np, "sec,lcdid3-gpio", 0); + if (gpio_is_valid(lcd_id3_gpio)) + input_info(true, dev, "%s: lcd sec,id3_gpio %d(%d)\n", __func__, lcd_id3_gpio, gpio_get_value(lcd_id3_gpio)); + else + input_err(true, dev, "%s: Failed to get sec,lcdid3-gpio\n", __func__); + + fw_sel_idx = (gpio_get_value(lcd_id3_gpio) << 2) | (gpio_get_value(lcd_id2_gpio) << 1) | gpio_get_value(lcd_id1_gpio); + + lcdtype_cnt = of_property_count_u32_elems(np, "sec,lcdtype"); + input_info(true, dev, "%s: fw_name_cnt(%d) & lcdtype_cnt(%d) & fw_sel_idx(%d)\n", + __func__, fw_name_cnt, lcdtype_cnt, fw_sel_idx); + + if (lcdtype_cnt <= 0 || fw_name_cnt <= 0 || lcdtype_cnt <= fw_sel_idx || fw_name_cnt <= fw_sel_idx) { + input_err(true, dev, "%s: abnormal lcdtype & fw name count, fw_sel_idx(%d)\n", __func__, fw_sel_idx); + return -EINVAL; + } + of_property_read_u32_index(np, "sec,lcdtype", fw_sel_idx, &dt_lcdtype); + input_info(true, dev, "%s: lcd id(%d), ap lcdtype=0x%06X & dt lcdtype=0x%06X\n", + __func__, fw_sel_idx, lcdtype, dt_lcdtype); + } + + ret = of_property_read_string_index(np, "sec,fw_name", fw_sel_idx, firmware_name); + if (ret < 0 || *firmware_name == NULL || strlen(*firmware_name) == 0) { + input_err(true, dev, "%s: Failed to get fw name\n", __func__); + return -EINVAL; + } else + input_info(true, dev, "%s: fw name(%s)\n", __func__, *firmware_name); + + /* only for novatek */ + if (of_property_count_strings(np, "sec,fw_name_2nd") > 0) { + ret = of_property_read_string_index(np, "sec,fw_name_2nd", fw_sel_idx, firmware_name_2nd); + if (ret < 0 || *firmware_name_2nd == NULL || strlen(*firmware_name_2nd) == 0) { + input_err(true, dev, "%s: Failed to get fw name 2nd\n", __func__); + return -EINVAL; + } else + input_info(true, dev, "%s: firmware name 2nd(%s)\n", __func__, *firmware_name_2nd); + } + + return ret; +} +EXPORT_SYMBOL(sec_input_check_fw_name_incell); + +void stui_tsp_init(int (*stui_tsp_enter)(void), int (*stui_tsp_exit)(void), int (*stui_tsp_type)(void)) +{ + pr_info("%s %s: called\n", SECLOG, __func__); + + psecuretsp = kzalloc(sizeof(struct sec_ts_secure_data), GFP_KERNEL); + + psecuretsp->stui_tsp_enter = stui_tsp_enter; + psecuretsp->stui_tsp_exit = stui_tsp_exit; + psecuretsp->stui_tsp_type = stui_tsp_type; +} +EXPORT_SYMBOL(stui_tsp_init); + + +int stui_tsp_enter(void) +{ + struct sec_ts_plat_data *pdata = NULL; + + if (psecuretsp != NULL) { + pr_info("%s %s: psecuretsp->stui_tsp_enter called!\n", SECLOG, __func__); + return psecuretsp->stui_tsp_enter(); + } + + if (ptsp == NULL) { + pr_info("%s: ptsp is null\n", __func__); + return -EINVAL; + } + + pdata = ptsp->platform_data; + if (pdata == NULL) { + pr_info("%s: pdata is null\n", __func__); + return -EINVAL; + } + + pr_info("%s %s: pdata->stui_tsp_enter called!\n", SECLOG, __func__); + return pdata->stui_tsp_enter(); +} +EXPORT_SYMBOL(stui_tsp_enter); + +int stui_tsp_exit(void) +{ + struct sec_ts_plat_data *pdata = NULL; + + if (psecuretsp != NULL) { + pr_info("%s %s: psecuretsp->stui_tsp_exit called!\n", SECLOG, __func__); + return psecuretsp->stui_tsp_exit(); + } + + if (ptsp == NULL) + return -EINVAL; + + pdata = ptsp->platform_data; + if (pdata == NULL) + return -EINVAL; + + pr_info("%s %s: pdata->stui_tsp_exit called!\n", SECLOG, __func__); + return pdata->stui_tsp_exit(); +} +EXPORT_SYMBOL(stui_tsp_exit); + +int stui_tsp_type(void) +{ + struct sec_ts_plat_data *pdata = NULL; + + if (psecuretsp != NULL) { + pr_info("%s %s: psecuretsp->stui_tsp_type called!\n", SECLOG, __func__); + return psecuretsp->stui_tsp_type(); + } + + if (ptsp == NULL) + return -EINVAL; + + pdata = ptsp->platform_data; + if (pdata == NULL) + return -EINVAL; + + pr_info("%s %s: pdata->stui_tsp_type called!\n", SECLOG, __func__); + return pdata->stui_tsp_type(); +} +EXPORT_SYMBOL(stui_tsp_type); + +void sec_input_irq_enable(struct sec_ts_plat_data *pdata) +{ + struct irq_desc *desc; + + if (!pdata->irq) + return; + + desc = irq_to_desc(pdata->irq); + if (!desc) { + pr_info("%s: invalid irq number: %d\n", __func__, pdata->irq); + return; + } + + mutex_lock(&pdata->irq_lock); + + while (desc->depth > 0) { + enable_irq(pdata->irq); + pr_info("%s: depth: %d\n", __func__, desc->depth); + } + atomic_set(&pdata->irq_enabled, SEC_INPUT_IRQ_ENABLE); + mutex_unlock(&pdata->irq_lock); +} +EXPORT_SYMBOL(sec_input_irq_enable); + +void sec_input_irq_disable(struct sec_ts_plat_data *pdata) +{ + struct irq_desc *desc; + + if (!pdata->irq) + return; + + desc = irq_to_desc(pdata->irq); + if (!desc) { + pr_info("%s: invalid irq number: %d\n", __func__, pdata->irq); + return; + } + + mutex_lock(&pdata->irq_lock); + if (atomic_read(&pdata->irq_enabled) == SEC_INPUT_IRQ_ENABLE) + disable_irq(pdata->irq); + pr_info("%s: depth: %d\n", __func__, desc->depth); + + atomic_set(&pdata->irq_enabled, SEC_INPUT_IRQ_DISABLE); + mutex_unlock(&pdata->irq_lock); +} +EXPORT_SYMBOL(sec_input_irq_disable); + +void sec_input_irq_disable_nosync(struct sec_ts_plat_data *pdata) +{ + struct irq_desc *desc; + + if (!pdata->irq) + return; + + desc = irq_to_desc(pdata->irq); + if (!desc) { + pr_info("%s: invalid irq number: %d\n", __func__, pdata->irq); + return; + } + + mutex_lock(&pdata->irq_lock); + + if (atomic_read(&pdata->irq_enabled) == SEC_INPUT_IRQ_ENABLE) + disable_irq_nosync(pdata->irq); + pr_info("%s: depth: %d\n", __func__, desc->depth); + + atomic_set(&pdata->irq_enabled, SEC_INPUT_IRQ_DISABLE_NOSYNC); + mutex_unlock(&pdata->irq_lock); +} +EXPORT_SYMBOL(sec_input_irq_disable_nosync); + +void sec_input_forced_enable_irq(int irq) +{ + struct irq_desc *desc = irq_to_desc(irq); + + if (!desc) + return; + + while (desc->depth > 0) { + enable_irq(irq); + pr_info("%s: depth: %d\n", __func__, desc->depth); + } +} +EXPORT_SYMBOL(sec_input_forced_enable_irq); + +int sec_input_enable_device(struct device *dev) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + int retval; + + sec_input_utc_marker(dev, __func__); + + retval = mutex_lock_interruptible(&pdata->enable_mutex); + if (retval) + return retval; + + if (pdata->enable) + retval = pdata->enable(dev); + + mutex_unlock(&pdata->enable_mutex); + return retval; +} +EXPORT_SYMBOL(sec_input_enable_device); + +int sec_input_disable_device(struct device *dev) +{ + struct sec_ts_plat_data *pdata = dev->platform_data; + int retval; + + sec_input_utc_marker(dev, __func__); + + retval = mutex_lock_interruptible(&pdata->enable_mutex); + if (retval) + return retval; + + if (pdata->disable) + retval = pdata->disable(dev); + + mutex_unlock(&pdata->enable_mutex); + return 0; +} +EXPORT_SYMBOL(sec_input_disable_device); + +static int __init sec_input_init(void) +{ + int ret = 0; + + pr_info("%s %s ++\n", SECLOG, __func__); + +#if IS_ENABLED(CONFIG_SEC_DEBUG_TSP_LOG) + ret = sec_tsp_log_init(); + pr_info("%s %s: sec_tsp_log_init %d\n", SECLOG, __func__, ret); +#endif +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) + ret = sec_secure_touch_init(); + pr_info("%s %s: sec_secure_touch_init %d\n", SECLOG, __func__, ret); +#endif +#if IS_ENABLED(CONFIG_TOUCHSCREEN_DUMP_MODE) + ret = sec_tsp_dumpkey_init(); + pr_info("%s %s: sec_tsp_dumpkey_init %d\n", SECLOG, __func__, ret); +#endif + + pr_info("%s %s --\n", SECLOG, __func__); + return ret; +} + +static void __exit sec_input_exit(void) +{ + pr_info("%s %s ++\n", SECLOG, __func__); +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) + sec_secure_touch_exit(); +#endif +#if IS_ENABLED(CONFIG_TOUCHSCREEN_DUMP_MODE) + sec_tsp_dumpkey_exit(); +#endif + pr_info("%s %s --\n", SECLOG, __func__); +} + +module_init(sec_input_init); +module_exit(sec_input_exit); + +MODULE_DESCRIPTION("Samsung input common functions"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/sec_input/sec_input.h b/drivers/input/sec_input/sec_input.h new file mode 100644 index 000000000000..6f10bac79468 --- /dev/null +++ b/drivers/input/sec_input/sec_input.h @@ -0,0 +1,953 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* drivers/input/sec_input/sec_input.h + * + * Core file for Samsung input device driver + * + * 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. + */ +#ifndef _SEC_INPUT_H_ +#define _SEC_INPUT_H_ + +#if IS_ENABLED(CONFIG_SEC_KUNIT) +#include +#include +#endif +#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 + +#if IS_ENABLED(CONFIG_INPUT_SEC_TRUSTED_TOUCH) +#include "sec_trusted_touch.h" +#endif +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) +#include "sec_secure_touch.h" +#endif + +#define SECURE_TOUCH_ENABLE 1 +#define SECURE_TOUCH_DISABLE 0 + +#if IS_ENABLED(CONFIG_SEC_ABC) +#include +#endif +#include +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) +#include +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) +#include +#endif +#endif +#if (KERNEL_VERSION(5, 14, 0) <= LINUX_VERSION_CODE) +#include +#define SEC_INPUT_INIT_WORK(dev, work, func) devm_work_autocancel(dev, work, func) +#else +#define SEC_INPUT_INIT_WORK(dev, work, func) INIT_WORK(work, func) +#endif + +#include "sec_cmd.h" +#include "sec_tclm_v2.h" +#include "sec_input_multi_dev.h" +#if IS_ENABLED(CONFIG_TOUCHSCREEN_DUMP_MODE) +#include "sec_tsp_dumpkey.h" +#endif + +#if (KERNEL_VERSION(5, 6, 0) <= LINUX_VERSION_CODE) +#define sec_input_proc_ops(ops_owner, ops_name, read_fn, write_fn) \ +const struct proc_ops ops_name = { \ + .proc_read = read_fn, \ + .proc_write = write_fn, \ + .proc_lseek = generic_file_llseek, \ +} +#else +#define sec_input_proc_ops(ops_owner, ops_name, read_fn, write_fn) \ +const struct file_operations ops_name = { \ + .owner = ops_owner, \ + .read = read_fn, \ + .write = write_fn, \ + .llseek = generic_file_llseek, \ +} +#endif + +/* + * sys/class/sec/tsp/support_feature + * bit value should be made a promise with InputFramework. + */ +#define INPUT_FEATURE_ENABLE_SETTINGS_AOT (1 << 0) /* Double tap wakeup settings */ +#define INPUT_FEATURE_ENABLE_PRESSURE (1 << 1) /* homekey pressure */ +//#define INPUT_FEATURE_ENABLE_SYNC_RR120 (1 << 2) /* UNUSED: sync reportrate 120hz */ +#define INPUT_FEATURE_ENABLE_VRR (1 << 3) /* variable refresh rate (support 240hz) */ +#define INPUT_FEATURE_SUPPORT_WIRELESS_TX (1 << 4) /* enable call wireless cmd during wireless power sharing */ +#define INPUT_FEATURE_ENABLE_SYSINPUT_ENABLED (1 << 5) /* resume/suspend called by system input service */ +#define INPUT_FEATURE_ENABLE_PROX_LP_SCAN_ENABLED (1 << 6) /* prox_lp_scan_mode called by system input service */ + +#define INPUT_FEATURE_SUPPORT_OPEN_SHORT_TEST (1 << 8) /* open/short test support */ +#define INPUT_FEATURE_SUPPORT_MIS_CALIBRATION_TEST (1 << 9) /* mis-calibration test support */ +#define INPUT_FEATURE_ENABLE_MULTI_CALIBRATION (1 << 10) /* multi calibration support */ + +#define INPUT_FEATURE_SUPPORT_INPUT_MONITOR (1 << 16) /* input monitor support */ + +#define INPUT_FEATURE_SUPPORT_MOTION_PALM (1 << 20) /* rawdata motion control: palm */ +#define INPUT_FEATURE_SUPPORT_MOTION_AIVF (1 << 21) /* rawdata motion control: aivf */ + +/* + * sec Log + */ +#define SECLOG "[sec_input]" +#define INPUT_LOG_BUF_SIZE 512 +#define INPUT_TCLM_LOG_BUF_SIZE 64 +#define INPUT_DEBUG_INFO_SIZE 1024 + +#if IS_ENABLED(CONFIG_SEC_DEBUG_TSP_LOG) +#include "sec_tsp_log.h" + +#define input_dbg(mode, dev, fmt, ...) \ +({ \ + static char input_log_buf[INPUT_LOG_BUF_SIZE]; \ + dev_dbg(dev, SECLOG " " fmt, ## __VA_ARGS__); \ + if (mode) { \ + if (dev) \ + snprintf(input_log_buf, sizeof(input_log_buf), "%s %s %d:%s", \ + dev_driver_string(dev), dev_name(dev), \ + current->pid, current->comm); \ + else \ + snprintf(input_log_buf, sizeof(input_log_buf), "NULL %d:%s", \ + current->pid, current->comm); \ + sec_debug_tsp_log_msg(input_log_buf, fmt, ## __VA_ARGS__); \ + } \ +}) +#define input_info(mode, dev, fmt, ...) \ +({ \ + static char input_log_buf[INPUT_LOG_BUF_SIZE]; \ + dev_info(dev, SECLOG " " fmt, ## __VA_ARGS__); \ + if (mode) { \ + if (dev) \ + snprintf(input_log_buf, sizeof(input_log_buf), "%s %s %d:%s", \ + dev_driver_string(dev), dev_name(dev), \ + current->pid, current->comm); \ + else \ + snprintf(input_log_buf, sizeof(input_log_buf), "NULL %d:%s", \ + current->pid, current->comm); \ + sec_debug_tsp_log_msg(input_log_buf, fmt, ## __VA_ARGS__); \ + } \ +}) +#define input_err(mode, dev, fmt, ...) \ +({ \ + static char input_log_buf[INPUT_LOG_BUF_SIZE]; \ + dev_err(dev, SECLOG " " fmt, ## __VA_ARGS__); \ + if (mode) { \ + if (dev) \ + snprintf(input_log_buf, sizeof(input_log_buf), "%s %s %d:%s", \ + dev_driver_string(dev), dev_name(dev), \ + current->pid, current->comm); \ + else \ + snprintf(input_log_buf, sizeof(input_log_buf), "NULL %d:%s", \ + current->pid, current->comm); \ + sec_debug_tsp_log_msg(input_log_buf, fmt, ## __VA_ARGS__); \ + } \ +}) + +#define input_fail_hist(mode, dev, fmt, ...) \ +({ \ + static char input_log_buf[INPUT_LOG_BUF_SIZE]; \ + dev_info(dev, SECLOG " " fmt, ## __VA_ARGS__); \ + if (mode) { \ + if (dev) \ + snprintf(input_log_buf, sizeof(input_log_buf), "%s %s %d:%s", \ + dev_driver_string(dev), dev_name(dev), \ + current->pid, current->comm); \ + else \ + snprintf(input_log_buf, sizeof(input_log_buf), "NULL %d:%s", \ + current->pid, current->comm); \ + sec_debug_tsp_log_msg(input_log_buf, fmt, ## __VA_ARGS__); \ + sec_debug_tsp_fail_hist(input_log_buf, fmt, ## __VA_ARGS__); \ + } \ +}) + +#define input_raw_info_d(dev_count, dev, fmt, ...) \ +({ \ + static char input_log_buf[INPUT_LOG_BUF_SIZE]; \ + dev_info(dev, SECLOG " " fmt, ## __VA_ARGS__); \ + if (dev) \ + snprintf(input_log_buf, sizeof(input_log_buf), "%s %s", \ + dev_driver_string(dev), dev_name(dev)); \ + else \ + snprintf(input_log_buf, sizeof(input_log_buf), "NULL"); \ + sec_debug_tsp_log_msg(input_log_buf, fmt, ## __VA_ARGS__); \ + sec_debug_tsp_raw_data_msg(dev_count, input_log_buf, fmt, ## __VA_ARGS__); \ +}) + +#define input_raw_info(mode, dev, fmt, ...) \ +({ \ + if (mode) { \ + input_raw_info_d(0, dev, fmt, ## __VA_ARGS__); \ + } else { \ + dev_info(dev, SECLOG " " fmt, ## __VA_ARGS__); \ + } \ +}) + +#define input_raw_data_clear_by_device(mode) sec_tsp_raw_data_clear(mode) +#define input_raw_data_clear() sec_tsp_raw_data_clear(0) + +#define input_log_fix() sec_tsp_log_fix() +#else +#define input_dbg(mode, dev, fmt, ...) \ +({ \ + dev_dbg(dev, SECLOG " " fmt, ## __VA_ARGS__); \ +}) +#define input_info(mode, dev, fmt, ...) \ +({ \ + dev_info(dev, SECLOG " " fmt, ## __VA_ARGS__); \ +}) +#define input_err(mode, dev, fmt, ...) \ +({ \ + dev_err(dev, SECLOG " " fmt, ## __VA_ARGS__); \ +}) +#define input_fail_hist(mode, dev, fmt, ...) input_err(mode, dev, fmt, ## __VA_ARGS__) +#define input_raw_info_d(dev_count, dev, fmt, ...) input_info(true, dev, fmt, ## __VA_ARGS__) +#define input_raw_info(mode, dev, fmt, ...) input_info(mode, dev, fmt, ## __VA_ARGS__) +#define input_log_fix() {} +#define input_raw_data_clear_by_device(mode) {} +#define input_raw_data_clear() {} +#endif + +/* + * for input_event_codes.h + */ +#define KEY_HOT 252 +#define KEY_WAKEUP_UNLOCK 253 /* Wake-up to recent view, ex: AOP */ +#define KEY_RECENT 254 + +#define KEY_WATCH 550 /* Premium watch: 2finger double tap */ + +#define BTN_PALM 0x118 /* palm flag */ +#define BTN_LARGE_PALM 0x119 /* large palm flag */ + +#define KEY_BLACK_UI_GESTURE 0x1c7 +#define KEY_APPSELECT 0x244 /* AL Select Task/Application */ +#define KEY_EMERGENCY 0x2a0 +#define KEY_INT_CANCEL 0x2be /* for touch event skip */ +#define KEY_DEX_ON 0x2bd +#define KEY_WINK 0x2bf /* Intelligence Key */ +#define KEY_APPS 0x2c1 /* APPS key */ +#define KEY_SIP 0x2c2 /* Sip key */ +#define KEY_SETTING 0x2c5 /* Setting */ +#define KEY_SFINDER 0x2c6 /* Sfinder key */ +#define KEY_SHARE 0x2c9 /* keyboard share */ +#define KEY_FN_LOCK 0x2ca /* fn_lock key */ +#define KEY_FN_UNLOCK 0x2cb /* fn_unlock key */ + +#define ABS_MT_CUSTOM 0x3e /* custom event */ + +#if (KERNEL_VERSION(5, 10, 0) <= LINUX_VERSION_CODE) +#define SW_PEN_INSERT 0x0f /* set = pen insert, remove */ +#else +#define SW_PEN_INSERT 0x13 /* set = pen insert, remove */ +#endif + +#define EXYNOS_DISPLAY_INPUT_NOTIFIER ((IS_ENABLED(CONFIG_EXYNOS_DPU30) || IS_ENABLED(CONFIG_DRM_SAMSUNG_DPU)) && IS_ENABLED(CONFIG_PANEL_NOTIFY)) + +enum grip_write_mode { + G_NONE = 0, + G_SET_EDGE_HANDLER = 1, + G_SET_EDGE_ZONE = 2, + G_SET_NORMAL_MODE = 4, + G_SET_LANDSCAPE_MODE = 8, + G_CLR_LANDSCAPE_MODE = 16, +}; +enum grip_set_data { + ONLY_EDGE_HANDLER = 0, + GRIP_ALL_DATA = 1, +}; + +enum external_noise_mode { + EXT_NOISE_MODE_NONE = 0, + EXT_NOISE_MODE_MONITOR = 1, /* for dex mode */ + EXT_NOISE_MODE_MAX, /* add new mode above this line */ +}; + +enum wireless_charger_param { + TYPE_WIRELESS_CHARGER_NONE = 0, + TYPE_WIRELESS_CHARGER = 1, + TYPE_WIRELESS_BATTERY_PACK = 3, +}; + +enum charger_param { + TYPE_WIRE_CHARGER_NONE = 0, + TYPE_WIRE_CHARGER = 1, +}; + +enum set_temperature_state { + SEC_INPUT_SET_TEMPERATURE_NORMAL = 0, + SEC_INPUT_SET_TEMPERATURE_IN_IRQ, + SEC_INPUT_SET_TEMPERATURE_FORCE, +}; + +/* FACTORY TEST RESULT SAVING FUNCTION + * bit 3 ~ 0 : OCTA Assy + * bit 7 ~ 4 : OCTA module + * param[0] : OCTA modue(1) / OCTA Assy(2) + * param[1] : TEST NONE(0) / TEST FAIL(1) / TEST PASS(2) : 2 bit + */ +#define TEST_OCTA_MODULE 1 +#define TEST_OCTA_ASSAY 2 +#define TEST_OCTA_NONE 0 +#define TEST_OCTA_FAIL 1 +#define TEST_OCTA_PASS 2 + +/* + * write 0xE4 [ 11 | 10 | 01 | 00 ] + * MSB <-------------------> LSB + * read 0xE4 + * mapping sequnce : LSB -> MSB + * struct sec_ts_test_result { + * * assy : front + OCTA assay + * * module : only OCTA + * union { + * struct { + * u8 assy_count:2; -> 00 + * u8 assy_result:2; -> 01 + * u8 module_count:2; -> 10 + * u8 module_result:2; -> 11 + * } __attribute__ ((packed)); + * unsigned char data[1]; + * }; + *}; + */ + +struct sec_ts_test_result { + union { + struct { + u8 assy_count:2; + u8 assy_result:2; + u8 module_count:2; + u8 module_result:2; + } __attribute__ ((packed)); + unsigned char data[1]; + }; +}; + +enum sponge_event_type { + SPONGE_EVENT_TYPE_SPAY = 0x04, + SPONGE_EVENT_TYPE_SINGLE_TAP = 0x08, + SPONGE_EVENT_TYPE_AOD_PRESS = 0x09, + SPONGE_EVENT_TYPE_AOD_LONGPRESS = 0x0A, + SPONGE_EVENT_TYPE_AOD_DOUBLETAB = 0x0B, + SPONGE_EVENT_TYPE_AOD_HOMEKEY_PRESS = 0x0C, + SPONGE_EVENT_TYPE_AOD_HOMEKEY_RELEASE = 0x0D, + SPONGE_EVENT_TYPE_AOD_HOMEKEY_RELEASE_NO_HAPTIC = 0x0E, + SPONGE_EVENT_TYPE_FOD_PRESS = 0x0F, + SPONGE_EVENT_TYPE_FOD_RELEASE = 0x10, + SPONGE_EVENT_TYPE_FOD_OUT = 0x11, + SPONGE_EVENT_TYPE_LONG_PRESS = 0x12, + SPONGE_EVENT_TYPE_TSP_SCAN_UNBLOCK = 0xE1, + SPONGE_EVENT_TYPE_TSP_SCAN_BLOCK = 0xE2, +}; + +enum proximity_event_type { + PROX_EVENT_TYPE_NP_FAR = 0, + PROX_EVENT_TYPE_NP_SENSITIVE_CLOSE = 1, + PROX_EVENT_TYPE_NP_ENOUGH_CLOSE = 2, + PROX_EVENT_TYPE_NP_STRONG_CLOSE = 3, + PROX_EVENT_TYPE_LP_CLOSE = 4, + PROX_EVENT_TYPE_LP_FAR = 5, + + PROX_EVENT_TYPE_POCKET_RELEASE = 10, + PROX_EVENT_TYPE_POCKET_PRESS = 11, + + PROX_EVENT_TYPE_LIGHTSENSOR_RELEASE = 20, + PROX_EVENT_TYPE_LIGHTSENSOR_PRESS = 21, +}; + +/* SEC_TS_DEBUG : Print event contents */ +#define SEC_TS_DEBUG_PRINT_ALLEVENT 0x01 +#define SEC_TS_DEBUG_PRINT_ONEEVENT 0x02 +#define SEC_TS_DEBUG_PRINT_READ_CMD 0x04 +#define SEC_TS_DEBUG_PRINT_WRITE_CMD 0x08 + +#define CMD_RESULT_WORD_LEN 10 + +#define SEC_TS_I2C_RETRY_CNT 3 +#define SEC_TS_WAIT_RETRY_CNT 100 + +#define SEC_TS_LOCATION_DETECT_SIZE 6 +#define SEC_TS_SUPPORT_TOUCH_COUNT 10 +#define SEC_TS_GESTURE_REPORT_BUFF_SIZE 20 + +/* SPONGE MODE 0x00 */ +#define SEC_TS_MODE_SPONGE_SWIPE (1 << 1) +#define SEC_TS_MODE_SPONGE_AOD (1 << 2) +#define SEC_TS_MODE_SPONGE_SINGLE_TAP (1 << 3) +#define SEC_TS_MODE_SPONGE_PRESS (1 << 4) +#define SEC_TS_MODE_SPONGE_DOUBLETAP_TO_WAKEUP (1 << 5) +#define SEC_TS_MODE_SPONGE_TWO_FINGER_DOUBLETAP (1 << 7) +#define SEC_TS_MODE_SPONGE_VVC (1 << 8) + +/* SPONGE MODE 0x01 */ +#define SEC_TS_MODE_SPONGE_INF_DUMP_CLEAR (1 << 0) +#define SEC_TS_MODE_SPONGE_INF_DUMP (1 << 1) + +/*SPONGE library parameters*/ +#define SEC_TS_MAX_SPONGE_DUMP_BUFFER 512 +#define SEC_TS_SPONGE_DUMP_EVENT_MASK 0x7F +#define SEC_TS_SPONGE_DUMP_INF_MASK 0x80 +#define SEC_TS_SPONGE_DUMP_INF_SHIFT 7 + +/* SEC_TS SPONGE OPCODE COMMAND */ +#define SEC_TS_CMD_SPONGE_DUMP_FLUSH 0x01 +#define SEC_TS_CMD_SPONGE_AOD_ACTIVE_INFO 0x0A +#define SEC_TS_CMD_SPONGE_OFFSET_UTC 0x10 +#define SEC_TS_CMD_SPONGE_PRESS_PROPERTY 0x14 +#define SEC_TS_CMD_SPONGE_FOD_INFO 0x15 +#define SEC_TS_CMD_SPONGE_FOD_POSITION 0x19 +#define SEC_TS_CMD_SPONGE_GET_INFO 0x90 +#define SEC_TS_CMD_SPONGE_WRITE_PARAM 0x91 +#define SEC_TS_CMD_SPONGE_READ_PARAM 0x92 +#define SEC_TS_CMD_SPONGE_NOTIFY_PACKET 0x93 +#define SEC_TS_CMD_SPONGE_LP_DUMP 0xF0 +#define SEC_TS_CMD_SPONGE_LP_DUMP_CUR_IDX 0xF2 +#define SEC_TS_CMD_SPONGE_LP_DUMP_EVENT 0xF4 + +#define SEC_INPUT_DEFAULT_AVDD_NAME "tsp_avdd_ldo" +#define SEC_INPUT_DEFAULT_DVDD_NAME "tsp_io_ldo" + +#define SEC_TS_MAX_FW_PATH 64 +#define TSP_EXTERNAL_FW "tsp.bin" +#define TSP_EXTERNAL_FW_SIGNED "tsp_signed.bin" +#define TSP_SPU_FW_SIGNED "/TSP/ffu_tsp.bin" + +enum fw_version_index { + SEC_INPUT_FW_IC_VER = 0, + SEC_INPUT_FW_VER_PROJECT_ID, + SEC_INPUT_FW_MODULE_VER, + SEC_INPUT_FW_VER, + SEC_INPUT_FW_INDEX_MAX, +}; + +#if IS_ENABLED(CONFIG_SEC_ABC) +#define SEC_ABC_SEND_EVENT_TYPE "MODULE=tsp@WARN=tsp_int_fault" +#define SEC_ABC_SEND_EVENT_TYPE_SUB "MODULE=tsp_sub@WARN=tsp_int_fault" +#define SEC_ABC_SEND_EVENT_TYPE_WACOM_DIGITIZER_NOT_CONNECTED "MODULE=wacom@WARN=digitizer_not_connected" +#endif + +enum display_state { + DISPLAY_STATE_SERVICE_SHUTDOWN = -1, + DISPLAY_STATE_NONE = 0, + DISPLAY_STATE_OFF, + DISPLAY_STATE_ON, + DISPLAY_STATE_DOZE, + DISPLAY_STATE_DOZE_SUSPEND, + DISPLAY_STATE_LPM_OFF = 20, + DISPLAY_STATE_FORCE_OFF, + DISPLAY_STATE_FORCE_ON, +}; + +enum display_event { + DISPLAY_EVENT_EARLY = 0, + DISPLAY_EVENT_LATE, +}; +/*If you want to add mode, please check "MODE_TO_CHECK_BIT" as well.*/ +enum power_mode { + SEC_INPUT_STATE_POWER_OFF = 0, + SEC_INPUT_STATE_LPM, + SEC_INPUT_STATE_POWER_ON +}; + +#define CHECK_POWEROFF (1 << SEC_INPUT_STATE_POWER_OFF) +#define CHECK_LPMODE (1 << SEC_INPUT_STATE_LPM) +#define CHECK_POWERON (1 << SEC_INPUT_STATE_POWER_ON) +#define CHECK_ON_LP (CHECK_POWERON | CHECK_LPMODE) +#define CHECK_ALL (CHECK_POWERON | CHECK_LPMODE | CHECK_POWEROFF) +#define MODE_TO_CHECK_BIT(x) (1 << x) + +enum switch_system_mode { + TO_TOUCH_MODE = 0, + TO_LOWPOWER_MODE = 1, +}; + +enum sec_ts_cover_id { + SEC_TS_FLIP_COVER = 0, + SEC_TS_SVIEW_COVER, + SEC_TS_NONE, + SEC_TS_SVIEW_CHARGER_COVER, + SEC_TS_HEALTH_COVER, + SEC_TS_S_CHARGER_COVER, + SEC_TS_S_VIEW_WALLET_COVER, + SEC_TS_LED_COVER, + SEC_TS_CLEAR_COVER, + SEC_TS_KEYBOARD_KOR_COVER, + + SEC_TS_KEYBOARD_US_COVER = 10, + SEC_TS_NEON_COVER, + SEC_TS_ALCANTARA_COVER, + SEC_TS_GAMEPACK_COVER, + SEC_TS_LED_BACK_COVER, + SEC_TS_CLEAR_SIDE_VIEW_COVER, + SEC_TS_MINI_SVIEW_WALLET_COVER, + SEC_TS_CLEAR_CAMERA_VIEW_COVER, + + SEC_TS_MONTBLANC_COVER = 100, + SEC_TS_NFC_SMART_COVER = 255, +}; + +#define TEST_MODE_MIN_MAX false +#define TEST_MODE_ALL_NODE true +#define TEST_MODE_READ_FRAME false +#define TEST_MODE_READ_CHANNEL true + +enum offset_fac_position { + OFFSET_FAC_NOSAVE = 0, // FW index 0 + OFFSET_FAC_SUB = 1, // FW Index 2 + OFFSET_FAC_MAIN = 2, // FW Index 3 + OFFSET_FAC_SVC = 3, // FW Index 4 +}; + +enum offset_fw_position { + OFFSET_FW_NOSAVE = 0, + OFFSET_FW_SDC = 1, + OFFSET_FW_SUB = 2, + OFFSET_FW_MAIN = 3, + OFFSET_FW_SVC = 4, +}; + +enum offset_fac_data_type { + OFFSET_FAC_DATA_NO = 0, + OFFSET_FAC_DATA_CM = 1, + OFFSET_FAC_DATA_CM2 = 2, + OFFSET_FAC_DATA_CM3 = 3, + OFFSET_FAC_DATA_MISCAL = 5, + OFFSET_FAC_DATA_SELF_FAIL = 7, +}; + +enum sec_input_notify_t { + NOTIFIER_NOTHING = 0, + NOTIFIER_MAIN_TOUCH_ON, + NOTIFIER_MAIN_TOUCH_OFF, + NOTIFIER_SUB_TOUCH_ON, + NOTIFIER_SUB_TOUCH_OFF, + NOTIFIER_SECURE_TOUCH_ENABLE, /* secure touch is enabled */ + NOTIFIER_SECURE_TOUCH_DISABLE, /* secure touch is disabled */ + NOTIFIER_TSP_BLOCKING_REQUEST, /* wacom called tsp block enable */ + NOTIFIER_TSP_BLOCKING_RELEASE, /* wacom called tsp block disable */ + NOTIFIER_WACOM_PEN_CHARGING_FINISHED, /* to tsp: pen charging finished */ + NOTIFIER_WACOM_PEN_CHARGING_STARTED, /* to tsp: pen charging started */ + NOTIFIER_WACOM_PEN_INSERT, /* to tsp: pen is inserted */ + NOTIFIER_WACOM_PEN_REMOVE, /* to tsp: pen is removed */ + NOTIFIER_WACOM_PEN_HOVER_IN, /* to tsp: pen hover is in */ + NOTIFIER_WACOM_PEN_HOVER_OUT, /* to tsp: pen hover is out */ + NOTIFIER_LCD_VRR_LFD_LOCK_REQUEST, /* to LCD: set LFD min lock */ + NOTIFIER_LCD_VRR_LFD_LOCK_RELEASE, /* to LCD: unset LFD min lock */ + NOTIFIER_LCD_VRR_LFD_OFF_REQUEST, /* to LCD: set LFD OFF */ + NOTIFIER_LCD_VRR_LFD_OFF_RELEASE, /* to LCD: unset LFD OFF */ + NOTIFIER_TSP_ESD_INTERRUPT, + NOTIFIER_WACOM_SAVING_MODE_ON, /* to tsp: multi spen enable cmd on */ + NOTIFIER_WACOM_SAVING_MODE_OFF, /* to tsp: multi spen enable cmd off */ + NOTIFIER_WACOM_KEYBOARDCOVER_FLIP_OPEN, + NOTIFIER_WACOM_KEYBOARDCOVER_FLIP_CLOSE, + NOTIFIER_VALUE_MAX, +}; + +enum notify_set_scan_mode { + DISABLE_TSP_SCAN_BLOCK = 0, + ENABLE_TSP_SCAN_BLOCK = 1, + ENABLE_SPEN_CHARGING_MODE = 2, + DISABLE_SPEN_CHARGING_MODE = 3, + ENABLE_SPEN_IN = 4, + ENABLE_SPEN_OUT = 5, +}; + +enum coord_action { + SEC_TS_COORDINATE_ACTION_NONE = 0, + SEC_TS_COORDINATE_ACTION_PRESS = 1, + SEC_TS_COORDINATE_ACTION_MOVE = 2, + SEC_TS_COORDINATE_ACTION_RELEASE = 3, + SEC_TS_COORDINATE_ACTION_FORCE_RELEASE = 4, +}; + +#define SEC_TS_LFD_CTRL_LOCK_TIME 500 /* msec */ +#define SEC_TS_WAKE_LOCK_TIME 500 /* msec */ + +enum lfd_lock_ctrl { + SEC_TS_LFD_CTRL_LOCK = 0, + SEC_TS_LFD_CTRL_UNLOCK, +}; + +enum notify_tsp_type { + MAIN_TOUCHSCREEN = 0, + SUB_TOUCHSCREEN, +}; + +enum sec_ts_error { + SEC_SKIP = 1, + SEC_SUCCESS = 0, + SEC_ERROR = -1, +}; + +struct sec_input_grip_data { + u8 edgehandler_direction; + int edgehandler_start_y; + int edgehandler_end_y; + u16 edge_range; + u8 deadzone_up_x; + u8 deadzone_dn_x; + int deadzone_y; + u8 deadzone_dn2_x; + int deadzone_dn_y; + u8 landscape_mode; + int landscape_edge; + u16 landscape_deadzone; + u16 landscape_top_deadzone; + u16 landscape_bottom_deadzone; + u16 landscape_top_gripzone; + u16 landscape_bottom_gripzone; +}; + +struct sec_input_notify_data { + u8 dual_policy; +}; + +struct sec_ts_coordinate { + u8 id; + u8 ttype; + u8 action; + u16 x; + u16 y; + u16 p_x; + u16 p_y; + u8 z; + u8 hover_flag; + u8 glove_flag; + u8 touch_height; + u16 mcount; + u8 major; + u8 minor; + bool palm; + int palm_count; + u8 left_event; + u8 noise_level; + u8 max_strength; + u8 hover_id_num; + u8 noise_status; + u8 freq_id; + u8 fod_debug; +}; + +struct sec_ts_aod_data { + u16 rect_data[4]; + u16 active_area[3]; +}; + +struct sec_ts_fod_data { + bool set_val; + u16 rect_data[4]; + + u8 vi_x; + u8 vi_y; + u8 vi_size; + u8 vi_data[SEC_CMD_STR_LEN]; + u8 vi_event; + u8 press_prop; +}; + +enum sec_ts_fod_vi_method { + CMD_SYSFS = 0, + CMD_IRQ = 1, +}; + +#define SEC_INPUT_HW_PARAM_SIZE 512 + +struct sec_ts_hw_param_data { + unsigned char ito_test[4]; + bool check_multi; + unsigned int multi_count; + unsigned int wet_count; + unsigned int noise_count; + unsigned int comm_err_count; + unsigned int checksum_result; + unsigned int all_finger_count; + unsigned int all_aod_tap_count; + unsigned int all_spay_count; + int mode_change_failed_count; + int ic_reset_count; +}; + +struct sec_ts_plat_data { + struct device *dev; + + struct input_dev *input_dev; + struct input_dev *input_dev_pad; + struct input_dev *input_dev_proximity; + + struct sec_input_multi_device *multi_dev; + struct sec_cmd_data *sec; + + int max_x; + int max_y; + int x_node_num; + int y_node_num; + + unsigned int irq_gpio; + u32 irq_flag; + int gpio_spi_cs; + int i2c_burstmax; + int bringup; + u32 area_indicator; + u32 area_navigation; + u32 area_edge; + char location[SEC_TS_LOCATION_DETECT_SIZE]; + + u8 prox_power_off; + bool power_enabled; + + struct sec_ts_coordinate coord[SEC_TS_SUPPORT_TOUCH_COUNT]; + struct sec_ts_coordinate prev_coord[SEC_TS_SUPPORT_TOUCH_COUNT]; + bool fill_slot; + + int touch_count; + unsigned int palm_flag; + bool blocking_palm; + atomic_t touch_noise_status; + atomic_t touch_pre_noise_status; + int gesture_id; + int gesture_x; + int gesture_y; + + struct sec_ts_fod_data fod_data; + struct sec_ts_aod_data aod_data; + + const char *firmware_name; + struct regulator *dvdd; + struct regulator *avdd; + + char ic_vendor_name[3]; + u8 img_version_of_ic[SEC_INPUT_FW_INDEX_MAX]; + u8 img_version_of_bin[SEC_INPUT_FW_INDEX_MAX]; + + struct pinctrl *pinctrl; + + int (*pinctrl_configure)(struct device *dev, bool on); + int (*power)(struct device *dev, bool on); + int (*start_device)(void *data); + int (*stop_device)(void *data); + int (*lpmode)(void *data, u8 mode); + void (*init)(void *data); + int (*stui_tsp_enter)(void); + int (*stui_tsp_exit)(void); + int (*stui_tsp_type)(void); + int (*probe)(struct device *dev); + + int (*enable)(struct device *dev); + int (*disable)(struct device *dev); + struct mutex enable_mutex; + + union power_supply_propval psy_value; + struct power_supply *psy; + u8 tsp_temperature_data; + bool tsp_temperature_data_skip; + int (*set_temperature)(struct device *dev, u8 temperature_data); + + struct sec_input_grip_data grip_data; + void (*set_grip_data)(struct device *dev, u8 flag); + + atomic_t enabled; + atomic_t power_state; + atomic_t shutdown_called; + + u16 touch_functions; + u16 ic_status; + u8 lowpower_mode; + u8 sponge_mode; + u8 external_noise_mode; + u8 touchable_area; + u8 ed_enable; + u8 pocket_mode; + u8 lightsensor_detect; + u8 fod_lp_mode; + int cover_type; + u8 wirelesscharger_mode; + bool force_wirelesscharger_mode; + int wet_mode; + int noise_mode; /* for debug app */ + int freq_id; /* for debug app */ + int low_sensitivity_mode; + + bool regulator_boot_on; + bool support_dex; + bool support_ear_detect; + + atomic_t secure_enabled; + atomic_t secure_pending_irqs; + struct completion secure_powerdown; + struct completion secure_interrupt; + int ss_touch_num; +#if IS_ENABLED(CONFIG_INPUT_SEC_TRUSTED_TOUCH) + struct sec_trusted_touch *pvm; +#endif +#define SEC_INPUT_IRQ_ENABLE 0 +#define SEC_INPUT_IRQ_DISABLE 1 +#define SEC_INPUT_IRQ_DISABLE_NOSYNC 2 + int irq; + atomic_t irq_enabled; + struct mutex irq_lock; + + struct device *bus_master; + + bool support_fod; + bool support_fod_lp_mode; + bool enable_settings_aot; + bool support_refresh_rate_mode; + bool support_vrr; + bool support_open_short_test; + bool support_mis_calibration_test; + bool support_wireless_tx; + bool support_lightsensor_detect; + bool support_input_monitor; + int support_sensor_hall; + int support_rawdata_map_num; + int dump_ic_ver; + bool disable_vsync_scan; + bool chip_on_board; + bool enable_sysinput_enabled; + bool support_rawdata; + bool support_rawdata_motion_aivf; + bool support_rawdata_motion_palm; + bool not_support_io_ldo; + bool not_support_vdd; + bool sense_off_when_cover_closed; + bool not_support_temp_noti; + bool support_vbus_notifier; + bool support_gesture_uevent; + bool support_always_on; + bool prox_lp_scan_enabled; + + int always_lpm; + + bool work_queue_probe_enabled; + bool first_booting_disabled; + int work_queue_probe_delay; + struct work_struct probe_work; + struct workqueue_struct *probe_workqueue; + + struct work_struct irq_work; + struct workqueue_struct *irq_workqueue; + struct completion resume_done; + struct wakeup_source *sec_ws; + + struct sec_ts_hw_param_data hw_param; + + struct delayed_work interrupt_notify_work; + + int (*set_charger_mode)(struct device *dev, bool on); + bool charger_flag; + struct work_struct vbus_notifier_work; + struct workqueue_struct *vbus_notifier_workqueue; + struct notifier_block vbus_nb; + struct notifier_block ccic_nb; + bool otg_flag; + + u32 print_info_cnt_release; + u32 print_info_cnt_open; +}; + +struct sec_ts_secure_data { + int (*stui_tsp_enter)(void); + int (*stui_tsp_exit)(void); + int (*stui_tsp_type)(void); +}; + +#ifdef TCLM_CONCEPT +int sec_tclm_data_read(struct i2c_client *client, int address); +int sec_tclm_data_write(struct i2c_client *client, int address); +int sec_tclm_execute_force_calibration(struct i2c_client *client, int cal_mode); +#endif + +#if IS_ENABLED(CONFIG_DISPLAY_SAMSUNG) +extern int get_lcd_attached(char *mode); +#endif + +#if IS_ENABLED(CONFIG_EXYNOS_DPU30) || IS_ENABLED(CONFIG_MCD_PANEL) || IS_ENABLED(CONFIG_USDM_PANEL) +extern int get_lcd_info(char *arg); +#endif + +#if IS_ENABLED(CONFIG_SMCDSD_PANEL) +extern unsigned int lcdtype; +#endif + +void sec_input_utc_marker(struct device *dev, const char *annotation); +bool sec_input_cmp_ic_status(struct device *dev, int check_bit); +bool sec_input_need_ic_off(struct sec_ts_plat_data *pdata); +bool sec_check_secure_trusted_mode_status(struct sec_ts_plat_data *pdata); +int sec_input_get_lcd_id(struct device *dev); +void sec_input_probe_work_remove(struct sec_ts_plat_data *pdata); +int sec_input_handler_start(struct device *dev); +void sec_delay(unsigned int ms); +int sec_input_set_temperature(struct device *dev, int state); +void sec_input_set_grip_type(struct device *dev, u8 set_type); +int sec_input_store_grip_data(struct device *dev, int *cmd_param); +int sec_input_check_cover_type(struct device *dev); +void sec_input_set_fod_info(struct device *dev, int vi_x, int vi_y, int vi_size, int vi_event); +ssize_t sec_input_get_fod_info(struct device *dev, char *buf); +bool sec_input_set_fod_rect(struct device *dev, int *rect_data); +int sec_input_check_wirelesscharger_mode(struct device *dev, int mode, int force); + +ssize_t sec_input_get_common_hw_param(struct sec_ts_plat_data *pdata, char *buf); +void sec_input_clear_common_hw_param(struct sec_ts_plat_data *pdata); + +void sec_input_print_info(struct device *dev, struct sec_tclm_data *tdata); + +void sec_input_proximity_report(struct device *dev, int data); +void sec_input_gesture_report(struct device *dev, int id, int x, int y); +void sec_input_coord_event_fill_slot(struct device *dev, int t_id); +void sec_input_coord_event_sync_slot(struct device *dev); +void sec_input_release_all_finger(struct device *dev); +int sec_input_device_register(struct device *dev, void *data); +void sec_tclm_parse_dt(struct device *dev, struct sec_tclm_data *tdata); +int sec_input_parse_dt(struct device *dev); +int sec_input_multi_device_parse_dt(struct device *dev); +void sec_input_support_feature_parse_dt(struct device *dev); +int sec_input_check_fw_name_incell(struct device *dev, const char **firmware_name, const char **firmware_name_mp); +int sec_input_pinctrl_configure(struct device *dev, bool on); +int sec_input_power(struct device *dev, bool on); +void sec_input_register_vbus_notifier(struct device *dev); +void sec_input_unregister_vbus_notifier(struct device *dev); + +void sec_input_register_notify(struct notifier_block *nb, notifier_fn_t notifier_call, int priority); +void sec_input_unregister_notify(struct notifier_block *nb); +int sec_input_notify(struct notifier_block *nb, unsigned long noti, void *v); +int sec_input_self_request_notify(struct notifier_block *nb); +int sec_input_enable_device(struct device *dev); +int sec_input_disable_device(struct device *dev); +void stui_tsp_init(int (*stui_tsp_enter)(void), int (*stui_tsp_exit)(void), int (*stui_tsp_type)(void)); +int stui_tsp_enter(void); +int stui_tsp_exit(void); +int stui_tsp_type(void); +void sec_input_forced_enable_irq(int irq); +#endif diff --git a/drivers/input/sec_input/sec_input_multi_dev.c b/drivers/input/sec_input/sec_input_multi_dev.c new file mode 100644 index 000000000000..237dc6fde9ac --- /dev/null +++ b/drivers/input/sec_input/sec_input_multi_dev.c @@ -0,0 +1,325 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* drivers/input/sec_input/sec_input_multi_dev.c + * + * Core file for Samsung input device driver for multi device + * + * 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. + */ + +#include "sec_input_multi_dev.h" + +bool sec_input_need_fold_off(struct sec_input_multi_device *mdev) +{ + struct sec_ts_plat_data *plat_data; + + plat_data = mdev->dev->platform_data; + if (!plat_data) { + input_err(true, mdev->dev, "%s: no platform data\n", __func__); + return false; + } + + if (IS_NOT_FOLD_DEV(mdev)) + return false; + + if ((mdev->device_count == MULTI_DEV_MAIN) && (mdev->flip_status_current == FOLD_STATUS_FOLDING)) + return true; + + if ((mdev->device_count == MULTI_DEV_SUB) && + (mdev->flip_status_current == FOLD_STATUS_UNFOLDING) && !plat_data->always_lpm) + return true; + + return false; +} +EXPORT_SYMBOL(sec_input_need_fold_off); + +void sec_input_set_fold_state(struct sec_input_multi_device *mdev, int state) +{ + if (IS_NOT_FOLD_DEV(mdev)) + return; + + cancel_delayed_work(&mdev->switching_work); + mdev->flip_status_current = state; + schedule_work(&mdev->switching_work.work); + input_info(true, mdev->dev, "%s: [%s] state:%d %sfolding\n", + __func__, mdev->name, + state, mdev->flip_status_current ? "" : "un"); +} +EXPORT_SYMBOL(sec_input_set_fold_state); + +void sec_input_multi_device_ready(struct sec_input_multi_device *mdev) +{ + if (IS_NOT_FOLD_DEV(mdev)) + return; + + mdev->device_ready = true; + + if (mdev->change_flip_status) { + input_info(true, mdev->dev, "%s: re-try switching\n", __func__); + schedule_work(&mdev->switching_work.work); + } +} +EXPORT_SYMBOL(sec_input_multi_device_ready); + +void sec_input_get_multi_device_debug_info(struct sec_input_multi_device *mdev, char *buf, ssize_t size) +{ + char mbuff[MULTI_DEV_DEBUG_INFO_SIZE] = { 0 }; + + if (IS_NOT_FOLD_DEV(mdev)) + return; + + snprintf(mbuff, sizeof(mbuff), "Multi Device Info(%s)\n", mdev->name); + strlcat(buf, mbuff, size); + snprintf(mbuff, sizeof(mbuff), "- status:%d / current:%d\n", mdev->flip_status, mdev->flip_status_current); + strlcat(buf, mbuff, size); + snprintf(mbuff, sizeof(mbuff), "- flip_mismatch_count:%d\n", mdev->flip_mismatch_count); + strlcat(buf, mbuff, size); +} +EXPORT_SYMBOL(sec_input_get_multi_device_debug_info); + +void sec_input_check_fold_status(struct sec_input_multi_device *mdev, bool flip_changed) +{ + struct sec_ts_plat_data *plat_data; + void *data; + + if (IS_NOT_FOLD_DEV(mdev) || !mdev->dev) + return; + + data = dev_get_drvdata(mdev->dev); + if (!data) { + input_err(true, mdev->dev, "%s: no drvdata\n", __func__); + return; + } + + plat_data = mdev->dev->platform_data; + if (!plat_data) { + input_err(true, mdev->dev, "%s: no platform data\n", __func__); + return; + } + + if (!plat_data->stop_device || !plat_data->start_device || !plat_data->lpmode) { + input_err(true, mdev->dev, "%s: func pointer is not connected for platform data\n", __func__); + return; + } + + input_info(true, mdev->dev, "%s: [%s] %s%sfolding\n", __func__, mdev->name, + flip_changed ? "changed to " : "", + (mdev->flip_status_current == FOLD_STATUS_FOLDING) ? "" : "un"); + + switch (mdev->device_count) { + case MULTI_DEV_MAIN: + if (flip_changed) { + if (mdev->flip_status_current == FOLD_STATUS_FOLDING) { + if (sec_input_cmp_ic_status(mdev->dev, CHECK_LPMODE)) { + input_info(true, mdev->dev, "%s: [%s] TSP IC LP => IC OFF\n", + __func__, mdev->name); + plat_data->stop_device(data); + } + } + } else { + mdev->flip_mismatch_count++; + if (mdev->flip_status_current == FOLD_STATUS_UNFOLDING) { + if (sec_input_cmp_ic_status(mdev->dev, CHECK_POWEROFF) && plat_data->lowpower_mode) { + input_info(true, mdev->dev, "%s: [%s] TSP IC OFF => LP mode[0x%X]\n", + __func__, mdev->name, plat_data->lowpower_mode); + plat_data->start_device(data); + plat_data->lpmode(data, TO_LOWPOWER_MODE); + } else if (sec_input_cmp_ic_status(mdev->dev, CHECK_LPMODE) + && (plat_data->lowpower_mode == 0)) { + input_info(true, mdev->dev, "%s: [%s] LP mode [0x0] => TSP IC OFF\n", + __func__, mdev->name); + plat_data->stop_device(data); + } else { + input_info(true, mdev->dev, "%s: [%s] reinit, LP[0x%X]\n", + __func__, mdev->name, plat_data->lowpower_mode); + if (plat_data->init) + plat_data->init(data); + else + input_err(true, mdev->dev, "%s: [%s] no init function. do nothing\n", + __func__, mdev->name); + } + } + } + break; + case MULTI_DEV_SUB: + if (flip_changed) { + if (mdev->flip_status_current == FOLD_STATUS_FOLDING) { + if (sec_input_cmp_ic_status(mdev->dev, CHECK_POWEROFF) && plat_data->lowpower_mode) { + input_info(true, mdev->dev, "%s: [%s] TSP IC OFF => LP[0x%X]\n", + __func__, mdev->name, plat_data->lowpower_mode); + plat_data->start_device(data); + plat_data->lpmode(data, TO_LOWPOWER_MODE); + } + } else { + if (sec_input_cmp_ic_status(mdev->dev, CHECK_LPMODE)) { + if (sec_input_need_fold_off(mdev)) { + input_info(true, mdev->dev, "%s: [%s] TSP IC LP => IC OFF\n", + __func__, mdev->name); + plat_data->stop_device(data); + } else { + input_info(true, mdev->dev, "%s: [%s] TSP IC LP => on going\n", + __func__, mdev->name); + } + } else if (sec_input_cmp_ic_status(mdev->dev, CHECK_POWEROFF)) { + if (!sec_input_need_fold_off(mdev)) { + input_info(true, mdev->dev, "%s: [%s] TSP IC OFF => LP[0x%X]\n", + __func__, mdev->name, plat_data->lowpower_mode); + plat_data->start_device(data); + plat_data->lpmode(data, TO_LOWPOWER_MODE); + } + } + } + } else { + mdev->flip_mismatch_count++; + if (mdev->flip_status_current == FOLD_STATUS_FOLDING) { + if (sec_input_cmp_ic_status(mdev->dev, CHECK_POWEROFF) && plat_data->lowpower_mode) { + input_info(true, mdev->dev, "%s: [%s] TSP IC OFF => LP[0x%X]\n", + __func__, mdev->name, plat_data->lowpower_mode); + plat_data->start_device(data); + plat_data->lpmode(data, TO_LOWPOWER_MODE); + } else if (sec_input_cmp_ic_status(mdev->dev, CHECK_LPMODE) + && plat_data->lowpower_mode == 0) { + input_info(true, mdev->dev, "%s: [%s] LP mode [0x0] => IC OFF\n", + __func__, mdev->name); + plat_data->stop_device(data); + } else if (sec_input_cmp_ic_status(mdev->dev, CHECK_LPMODE) + && plat_data->lowpower_mode) { + /* + * CAUTION! + * need to check there is no problem with duplicated call of lpmode function + */ + input_info(true, mdev->dev, "%s: [%s] call LP mode again\n", + __func__, mdev->name); + plat_data->lpmode(data, TO_LOWPOWER_MODE); + } + } else { + if (sec_input_cmp_ic_status(mdev->dev, CHECK_LPMODE)) { + if (sec_input_need_fold_off(mdev)) { + input_info(true, mdev->dev, "%s: [%s] TSP IC LP => IC OFF\n", + __func__, mdev->name); + plat_data->stop_device(data); + } else { + input_info(true, mdev->dev, "%s: [%s] TSP IC LP => on going\n", + __func__, mdev->name); + } + } else if (sec_input_cmp_ic_status(mdev->dev, CHECK_POWEROFF)) { + if (!sec_input_need_fold_off(mdev)) { + input_info(true, mdev->dev, "%s: [%s] TSP IC OFF => LP[0x%X]\n", + __func__, mdev->name, plat_data->lowpower_mode); + plat_data->start_device(data); + plat_data->lpmode(data, TO_LOWPOWER_MODE); + } + } + } + + } + break; + default: + break; + } +} + +void sec_input_switching_work(struct work_struct *work) +{ + struct sec_input_multi_device *mdev = container_of(work, struct sec_input_multi_device, + switching_work.work); + struct sec_ts_plat_data *plat_data; + bool flip_changed = false; + + if (mdev == NULL) { + input_err(true, NULL, "%s: multi device is null\n", __func__); + return; + } + + if (IS_NOT_FOLD_DEV(mdev) || !mdev->dev) + return; + + plat_data = mdev->dev->platform_data; + if (!plat_data || !plat_data->input_dev) { + input_err(true, mdev->dev, "%s: no platform data\n", __func__); + return; + } + + if (mutex_lock_interruptible(&plat_data->enable_mutex)) { + input_err(true, mdev->dev, "%s: failed to lock enable_mutex\n", __func__); + return; + } + + if (mdev->flip_status != mdev->flip_status_current) + flip_changed = true; + + if (flip_changed && !mdev->device_ready) { + input_err(true, mdev->dev, "%s: device is not ready yet\n", __func__); + mdev->change_flip_status = 1; + goto out; + } + + mdev->change_flip_status = 0; + if (plat_data->sec_ws) + __pm_stay_awake(plat_data->sec_ws); + sec_input_check_fold_status(mdev, flip_changed); + if (plat_data->sec_ws) + __pm_relax(plat_data->sec_ws); + mdev->flip_status = mdev->flip_status_current; + input_info(true, mdev->dev, "%s: [%s] done\n", __func__, mdev->name); +out: + mutex_unlock(&plat_data->enable_mutex); +} + +/* + * sec_input_multi_device_create + * struct device *dev : + * it should be a (i2c/spi)client->dev. + * driver data should be set as drvdata. + * sec_ts_plat_data should be connected as dev->platform_data. + * start_device, stop_device, lpmode function pointer should be set in platform_data. + */ +void sec_input_multi_device_create(struct device *dev) +{ + struct sec_ts_plat_data *pdata; + struct sec_input_multi_device *multi_dev; + int device_count; + + device_count = sec_input_multi_device_parse_dt(dev); + if (IS_NOT_FOLD(device_count)) + return; + + pdata = dev->platform_data; + if (!pdata) { + input_err(true, dev, "%s: no platform data\n", __func__); + return; + } + + input_info(true, dev, "%s for %s\n", __func__, GET_FOLD_STR(device_count)); + multi_dev = devm_kzalloc(dev, sizeof(struct sec_input_multi_device), GFP_KERNEL); + if (!multi_dev) + return; + + multi_dev->dev = dev; + multi_dev->device_count = device_count; + multi_dev->name = GET_FOLD_STR(device_count); + multi_dev->flip_status = -1; + if (device_count == MULTI_DEV_SUB) + multi_dev->flip_status_current = FOLD_STATUS_FOLDING; + else + multi_dev->flip_status_current = FOLD_STATUS_UNFOLDING; + + INIT_DELAYED_WORK(&multi_dev->switching_work, sec_input_switching_work); + + pdata->multi_dev = multi_dev; +} +EXPORT_SYMBOL(sec_input_multi_device_create); + +void sec_input_multi_device_remove(struct sec_input_multi_device *mdev) +{ + if (!mdev || !mdev->dev) + return; + + input_info(true, mdev->dev, "%s\n", __func__); + cancel_delayed_work_sync(&mdev->switching_work); +} +EXPORT_SYMBOL(sec_input_multi_device_remove); + +MODULE_DESCRIPTION("Samsung input multi device"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/sec_input/sec_input_multi_dev.h b/drivers/input/sec_input/sec_input_multi_dev.h new file mode 100644 index 000000000000..c8cbc1c6e511 --- /dev/null +++ b/drivers/input/sec_input/sec_input_multi_dev.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* drivers/input/sec_input/sec_input_multi_dev.h + * + * Core file for Samsung input device driver for multi device + * + * 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. + */ + +#ifndef _SEC_INPUT_MULTI_DEV_H_ +#define _SEC_INPUT_MULTI_DEV_H_ + +#include "sec_input.h" + +#define MULTI_DEV_DEBUG_INFO_SIZE 256 + +#define MULTI_DEV_NONE 0 +#define MULTI_DEV_MAIN 1 +#define MULTI_DEV_SUB 2 +#define FOLD_STATUS_FOLDING 1 +#define FOLD_STATUS_UNFOLDING 0 +#define GET_DEV_COUNT(mdev) ((mdev == NULL) ? MULTI_DEV_NONE : mdev->device_count) +#define IS_FOLD(x) (x == MULTI_DEV_MAIN || x == MULTI_DEV_SUB) +#define IS_FOLD_DEV(mdev) IS_FOLD(GET_DEV_COUNT(mdev)) +#define IS_NOT_FOLD(x) !IS_FOLD(x) +#define IS_NOT_FOLD_DEV(mdev) IS_NOT_FOLD(GET_DEV_COUNT(mdev)) +#define GET_FOLD_STR(x) (x == MULTI_DEV_MAIN ? "MAIN" : x == MULTI_DEV_SUB ? "SUB" : "NONE") +#define GET_SEC_CLASS_DEVT_TSP(mdev) (GET_DEV_COUNT(mdev) == MULTI_DEV_MAIN ? SEC_CLASS_DEVT_TSP1 \ + : GET_DEV_COUNT(mdev) == MULTI_DEV_SUB ? SEC_CLASS_DEVT_TSP2 \ + : SEC_CLASS_DEVT_TSP) +#if IS_ENABLED(CONFIG_SEC_ABC) +#define GET_INT_ABC_TYPE(mdev) (GET_DEV_COUNT(mdev) == MULTI_DEV_SUB ? SEC_ABC_SEND_EVENT_TYPE_SUB \ + : SEC_ABC_SEND_EVENT_TYPE) +#endif + +struct sec_input_multi_device { + struct device *dev; + + int device_count; + const char *name; + + bool device_ready; + + int flip_status; + int flip_status_current; + int change_flip_status; + + unsigned int flip_mismatch_count; + + struct delayed_work switching_work; +}; + +#if IS_ENABLED(CONFIG_SEC_INPUT_MULTI_DEVICE) +bool sec_input_need_fold_off(struct sec_input_multi_device *mdev); +void sec_input_set_fold_state(struct sec_input_multi_device *mdev, int state); +void sec_input_multi_device_ready(struct sec_input_multi_device *mdev); + +void sec_input_multi_device_create(struct device *dev); +void sec_input_multi_device_remove(struct sec_input_multi_device *mdev); +void sec_input_get_multi_device_debug_info(struct sec_input_multi_device *mdev, + char *buf, ssize_t size); +#else +static inline bool sec_input_need_fold_off(struct sec_input_multi_device *mdev) { return false; } +static inline void sec_input_set_fold_state(struct sec_input_multi_device *mdev, int state) {} +static inline void sec_input_multi_device_ready(struct sec_input_multi_device *mdev) {} + +static inline void sec_input_multi_device_create(struct device *dev) {} +static inline void sec_input_multi_device_remove(struct sec_input_multi_device *mdev) {} +static inline void sec_input_get_multi_device_debug_info(struct sec_input_multi_device *mdev, + char *buf, ssize_t size) { return; } +#endif +#endif diff --git a/drivers/input/sec_input/sec_input_notifier.c b/drivers/input/sec_input/sec_input_notifier.c new file mode 100644 index 000000000000..20c0f600ac6a --- /dev/null +++ b/drivers/input/sec_input/sec_input_notifier.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2014 Samsung Electronics Co., Ltd. + * + * 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. + */ + +//#include +#include +#include + +static BLOCKING_NOTIFIER_HEAD(sec_input_notifier_list); + +/* + * sec_input_register_notify + * @nb: pointer of blocking notifier chain structure + * @notifier_fn_t: register notifier callback function + * + * register universal notifier for any development issue. + * ex) folder open/close, seucre touch enable/disable ... + */ +void sec_input_register_notify(struct notifier_block *nb, notifier_fn_t notifier_call, int priority) +{ + nb->notifier_call = notifier_call; + nb->priority = priority; + blocking_notifier_chain_register(&sec_input_notifier_list, nb); +} +EXPORT_SYMBOL(sec_input_register_notify); + +/* + * sec_input_unregister_notify + * @nb: pointer of blocking notifier chain structure + * + * unregister notifier + */ +void sec_input_unregister_notify(struct notifier_block *nb) +{ + blocking_notifier_chain_unregister(&sec_input_notifier_list, nb); +} +EXPORT_SYMBOL(sec_input_unregister_notify); + +/* + * sec_input_notify + * @nb: pointer of blocking notifier chain structure + * data: notifier type is defined in sec_input.h(enum sec_input_notify_t) + * v: structure data + * + * notifier call function + */ +int sec_input_notify(struct notifier_block *nb, unsigned long noti, void *v) +{ + return blocking_notifier_call_chain(&sec_input_notifier_list, noti, v); +} +EXPORT_SYMBOL(sec_input_notify); + +/* + * sec_input_self_request_notify + * @nb: pointer of blocking notifier chain structure + * + * only test + */ +int sec_input_self_request_notify(struct notifier_block *nb) +{ + return nb->notifier_call(nb, 0, NULL); +} +EXPORT_SYMBOL(sec_input_self_request_notify); + +MODULE_DESCRIPTION("Samsung input notifier"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/input/sec_input/sec_input_rawdata.c b/drivers/input/sec_input/sec_input_rawdata.c new file mode 100755 index 000000000000..b31ffe0c1af0 --- /dev/null +++ b/drivers/input/sec_input/sec_input_rawdata.c @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Samsung Electronics Co., Ltd. + * + * 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. + */ + +#include "sec_input.h" +#include "sec_input_rawdata.h" + +static struct sec_input_rawdata_device *raw_dev; + +static long sec_input_rawdata_ioctl(struct file *file, unsigned int cmd, void __user *p, int compat_mode) +{ + static struct raw_data t; + u8 *copier; + int total; + + if (!raw_dev->rawdata_pool[0] || !raw_dev->rawdata_pool[1] || !raw_dev->rawdata_pool[2]) { + input_err(true, raw_dev->fac_dev, "%s: is not allocated\n", __func__); + return -ENOMEM; + } + + mutex_lock(&raw_dev->lock); + + if (cmd == IOCTL_TSP_MAP_READ) { + t.num = raw_dev->raw_write_index - raw_dev->raw_read_index; + if (t.num == 0) { + if (copy_to_user(p, (void *)&t, sizeof(struct raw_data))) { + input_err(true, raw_dev->fac_dev, "%s: failed to 0 copy_to_user\n", + __func__); + mutex_unlock(&raw_dev->lock); + return -EFAULT; + } + mutex_unlock(&raw_dev->lock); + return 0; + } else if (t.num < 0) { + t.num = RAW_VEC_NUM - raw_dev->raw_read_index + raw_dev->raw_write_index; + } + + /* rawdata_len is 4K size, and if num is over 1, u8 data[IOCTL_SIZE] is overflow + * will be fixed to support numbers buffer. + */ + if (t.num > 1) { + t.num = 1; + } + + for (total = 0; total < t.num; total++) { + copier = raw_dev->rawdata_pool[raw_dev->raw_read_index]; + memcpy(&t.data[raw_dev->rawdata_len * total], copier, raw_dev->rawdata_len); + raw_dev->raw_read_index++; + if (raw_dev->raw_read_index >= 3) + raw_dev->raw_read_index = 0; + } + + if (copy_to_user(p, (void *)&t, sizeof(struct raw_data))) { + input_err(true, raw_dev->fac_dev, "%s: failed to copyt_to_user\n", + __func__); + mutex_unlock(&raw_dev->lock); + return -EFAULT; + } + } else if (cmd == IOCTL_TSP_MAP_WRITE_TEST_1) { + if (copy_from_user((void *)&t, p, sizeof(struct raw_data))) { + input_err(true, raw_dev->fac_dev, "%s: failed to copyt_from_user\n", __func__); + mutex_unlock(&raw_dev->lock); + return -EFAULT; + } + input_info(true, raw_dev->fac_dev, "%s: TEST_1, %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n", __func__, + t.data[0], t.data[1], t.data[2], t.data[3], t.data[4], t.data[5], + t.data[6], t.data[7], t.data[8], t.data[9], t.data[10], t.data[11]); + } + + mutex_unlock(&raw_dev->lock); + return 0; +} + +static long sec_input_rawdata_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + return sec_input_rawdata_ioctl(file, cmd, (void __user *)arg, 0); +} + +static long sec_input_rawdata_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + return sec_input_rawdata_ioctl(file, cmd, (void __user *)arg, 1); +} + +static int sec_input_rawdata_ioctl_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static int sec_input_rawdata_ioctl_close(struct inode *inode, struct file *file) +{ + raw_dev->raw_write_index++; + if (raw_dev->raw_write_index >= RAW_VEC_NUM) + raw_dev->raw_write_index = 0; + sysfs_notify(&raw_dev->fac_dev->kobj, NULL, "raw_irq"); + + return 0; +} + +static const struct file_operations misc_rawdata_fos = { + .owner = THIS_MODULE, + .unlocked_ioctl = sec_input_rawdata_unlocked_ioctl, + .compat_ioctl = sec_input_rawdata_compat_ioctl, + .open = sec_input_rawdata_ioctl_open, + .release = sec_input_rawdata_ioctl_close, +}; + +static struct miscdevice misc_rawdata = { + .fops = &misc_rawdata_fos, + .minor = MISC_DYNAMIC_MINOR, + .name = "tspio", +}; + +MODULE_ALIAS_MISCDEV(MISC_DYNAMIC_MINOR); + +static ssize_t raw_irq_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return snprintf(buf, IOCTL_SIZE, "%d,%d", raw_dev->raw_write_index, raw_dev->raw_read_index); +} + +static DEVICE_ATTR_RO(raw_irq); + +static struct attribute *rawdata_attrs[] = { + &dev_attr_raw_irq.attr, + NULL, +}; + +static struct attribute_group rawdata_attr_group = { + .attrs = rawdata_attrs, +}; + +void sec_input_rawdata_copy_to_user(s16 *data, int len, int press) +{ + u8 *target_mem; + s16 *buff; + int tlength = len; + + target_mem = raw_dev->rawdata_pool[raw_dev->raw_write_index++]; + if (raw_dev->raw_write_index >= RAW_VEC_NUM) + raw_dev->raw_write_index = 0; + + memcpy(target_mem, data, tlength); + if (raw_dev->raw_write_index >= 3) + raw_dev->raw_write_index = 0; + + buff = (s16 *)target_mem; + buff[3] = press; + + sysfs_notify(&raw_dev->fac_dev->kobj, NULL, "raw_irq"); +} +EXPORT_SYMBOL(sec_input_rawdata_copy_to_user); + +int sec_input_rawdata_buffer_alloc(void) +{ + input_info(true, raw_dev->fac_dev, "%s\n", __func__); + + if (!raw_dev->rawdata_pool[0]) { + raw_dev->rawdata_pool[0] = vmalloc(IOCTL_SIZE/*raw_dev->rawdata_len*/); + if (!raw_dev->rawdata_pool[0]) + goto alloc_out; + } + + if (!raw_dev->rawdata_pool[1]) { + raw_dev->rawdata_pool[1] = vmalloc(IOCTL_SIZE/*raw_dev->rawdata_len*/); + if (!raw_dev->rawdata_pool[1]) + goto alloc_out; + } + + if (!raw_dev->rawdata_pool[2]) { + raw_dev->rawdata_pool[2] = vmalloc(IOCTL_SIZE/*raw_dev->rawdata_len*/); + if (!raw_dev->rawdata_pool[2]) + goto alloc_out; + } + + return 0; + +alloc_out: + if (raw_dev->rawdata_pool[0]) + vfree(raw_dev->rawdata_pool[0]); + if (raw_dev->rawdata_pool[1]) + vfree(raw_dev->rawdata_pool[1]); + if (raw_dev->rawdata_pool[2]) + vfree(raw_dev->rawdata_pool[2]); + + raw_dev->rawdata_pool[0] = raw_dev->rawdata_pool[1] = raw_dev->rawdata_pool[2] = NULL; + + return 0; +} +EXPORT_SYMBOL(sec_input_rawdata_buffer_alloc); + +int sec_input_rawdata_init(struct device *dev, struct device *fac_dev) +{ + int ret; + + input_info(true, fac_dev, "%s\n", __func__); + + raw_dev = devm_kzalloc(dev, sizeof(struct sec_input_rawdata_device), GFP_KERNEL); + raw_dev->dev = dev; + raw_dev->rawdata_len = IOCTL_SIZE; + raw_dev->raw_u8 = devm_kzalloc(dev, IOCTL_SIZE, GFP_KERNEL); + raw_dev->fac_dev = fac_dev; + + mutex_init(&raw_dev->lock); + + ret = sysfs_create_group(&fac_dev->kobj, &rawdata_attr_group); + input_info(true, raw_dev->fac_dev, "%s: sysfs_create_group: ret: %d\n", __func__, ret); + + ret = misc_register(&misc_rawdata); + input_info(true, raw_dev->fac_dev, "%s: misc_register: ret: %d\n", __func__, ret); + return 0; +} +EXPORT_SYMBOL(sec_input_rawdata_init); + +MODULE_DESCRIPTION("Samsung input rawdata"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/sec_input/sec_input_rawdata.h b/drivers/input/sec_input/sec_input_rawdata.h new file mode 100755 index 000000000000..d4d3aca63e7a --- /dev/null +++ b/drivers/input/sec_input/sec_input_rawdata.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0 + * Copyright (C) 2022 Samsung Electronics Co., Ltd. + * + * 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. + */ + +//#ifndef _SEC_INPUT_RAWDATA_H_ +//#define _SEC_INPUT_RAWDATA_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define IOCTL_SIZE 4096 +#define POSTFIX_BYTE_LENGTH (1 * 2) +#define PREFIX_BYTE_LENGTH (4 * 2) +#define RAW_VEC_NUM 3 + +#define IOCTL_TSP_MAP_READ _IOR(0, 0, struct raw_data) +#define IOCTL_TSP_MAP_WRITE _IOW(0, 0, struct raw_data) +#define IOCTL_TSP_MAP_WRITE_TEST_1 _IOW('T', 1, struct raw_data) + +struct raw_data { + int num; + u8 data[IOCTL_SIZE]; +}; + +struct sec_input_rawdata_device { + struct device *dev; + struct device *fac_dev; + int num; + int rawdata_len; + u8 *raw_u8; + u8 *rawdata_pool[3]; + struct mutex lock; + int raw_write_index; + int raw_read_index; +}; + +#if IS_ENABLED(CONFIG_SEC_INPUT_RAWDATA) +void sec_input_rawdata_copy_to_user(s16 *data, int len, int press); +int sec_input_rawdata_buffer_alloc(void); +int sec_input_rawdata_init(struct device *dev, struct device *fac_dev); +#else +static inline void sec_input_rawdata_copy_to_user(s16 *data, int len, int press) +{ + +} +static inline int sec_input_rawdata_buffer_alloc(void) +{ + return 0; +} +static inline int sec_input_rawdata_init(struct device *dev, struct device *fac_dev) +{ + return 0; +} +#endif +//#endif diff --git a/drivers/input/sec_input/sec_secure_touch.c b/drivers/input/sec_input/sec_secure_touch.c new file mode 100644 index 000000000000..db04ac64b0df --- /dev/null +++ b/drivers/input/sec_input/sec_secure_touch.c @@ -0,0 +1,443 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Samsung Electronics Co., Ltd. + * + * 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. + */ + +struct sec_secure_touch *g_ss_touch; + +#include "sec_secure_touch.h" +#include +#include "sec_input.h" + +int sec_secure_touch_set_device(struct sec_secure_touch *data, int dev_num) +{ + int number = dev_num - 1; + int ret; + + if (data->touch_driver[number].registered == 0) + return -ENODEV; + + if (data->device_number > 1) + return -EBUSY; + + mutex_lock(&data->lock); + ret = sysfs_create_link(&data->device->kobj, data->touch_driver[number].kobj, "secure"); + if (ret < 0) { + mutex_unlock(&data->lock); + return -EINVAL; + } + + pr_info("%s: %s: create link ret:%d, %s\n", SECLOG, __func__, ret, data->touch_driver[number].kobj->name); + + data->touch_driver[number].enabled = 1; + + mutex_unlock(&data->lock); + + return ret; +} + +struct sec_touch_driver *sec_secure_touch_register(void *drv_data, struct device *dev, int dev_num, struct kobject *kobj) +{ + struct sec_secure_touch *data = g_ss_touch; + int number = dev_num - 1; + + if (!data) { + pr_info("%s %s: null\n", SECLOG, __func__); + return NULL; + } + + pr_info("%s %s\n", SECLOG, __func__); + + if (dev_num < 1) { + dev_err(&data->pdev->dev, "%s: invalid parameter:%d\n", __func__, dev_num); + return NULL; + } + + if (data->touch_driver[number].registered) { + dev_info(&data->pdev->dev, "%s: already registered device number\n", __func__); + return NULL; + } + + pr_info("%s %s: name is %s\n", SECLOG, __func__, kobj->name); + data->touch_driver[number].drv_number = dev_num; + data->touch_driver[number].drv_data = drv_data; + data->touch_driver[number].kobj = kobj; + data->touch_driver[number].dev = dev; + data->touch_driver[number].registered = 1; + + data->device_number++; + + sec_secure_touch_set_device(data, dev_num); + + return &data->touch_driver[number]; +} +EXPORT_SYMBOL(sec_secure_touch_register); + + +void sec_secure_touch_unregister(int dev_num) +{ + struct sec_secure_touch *data = g_ss_touch; + int number = dev_num - 1; + + pr_info("%s: %s\n", SECLOG, __func__); + + data->touch_driver[number].drv_number = 0; + data->touch_driver[number].drv_data = NULL; + data->touch_driver[number].kobj = NULL; + data->touch_driver[number].registered = 0; + + data->device_number--; + +} +EXPORT_SYMBOL(sec_secure_touch_unregister); + +void sec_secure_touch_sysfs_notify(struct sec_secure_touch *data) +{ + if (!data) + sysfs_notify(&g_ss_touch->device->kobj, NULL, "secure_touch"); + else + sysfs_notify(&data->device->kobj, NULL, "secure_touch"); + + dev_info(&g_ss_touch->pdev->dev, "%s\n", __func__); +} + +static ssize_t dev_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_secure_touch *data = dev_get_drvdata(dev); + + if (!data) + return -ENOMEM; + + return snprintf(buf, PAGE_SIZE, "%d", data->device_number); +} + +static DEVICE_ATTR_RO(dev_count); + +static struct attribute *sec_secure_touch_attrs[] = { + &dev_attr_dev_count.attr, + NULL, +}; + +static struct attribute_group sec_secure_touch_attr_group = { + .attrs = sec_secure_touch_attrs, +}; + +#if IS_ENABLED(CONFIG_SEC_INPUT_MULTI_DEVICE) +static void sec_secure_touch_hall_ic_work(struct work_struct *work) +{ + struct sec_secure_touch *data = container_of(work, struct sec_secure_touch, folder_work.work); + int ret; + + mutex_lock(&data->lock); + + if (data->hall_ic == SECURE_TOUCH_FOLDER_OPEN) { + if (data->touch_driver[SECURE_TOUCH_SUB_DEV].enabled) { + if (data->touch_driver[SECURE_TOUCH_SUB_DEV].is_running) { + schedule_delayed_work(&data->folder_work, msecs_to_jiffies(10)); + mutex_unlock(&data->lock); + return; + } + + if (!IS_ERR_OR_NULL(sysfs_get_dirent(data->device->kobj.sd, "secure"))) { + sysfs_remove_link(&data->device->kobj, "secure"); + data->touch_driver[SECURE_TOUCH_SUB_DEV].enabled = 0; + } + } else { + pr_info("%s: %s: sub is not enabled: %d\n", SECLOG, __func__, __LINE__); + } + + if (data->touch_driver[SECURE_TOUCH_MAIN_DEV].registered) { + if (data->touch_driver[SECURE_TOUCH_MAIN_DEV].enabled == 1) { + mutex_unlock(&data->lock); + return; + } + + if (!IS_ERR_OR_NULL(data->touch_driver[SECURE_TOUCH_MAIN_DEV].kobj)) { + ret = sysfs_create_link(&data->device->kobj, data->touch_driver[SECURE_TOUCH_MAIN_DEV].kobj, "secure"); + if (ret < 0) { + pr_info("%s: %s: ret:%d, line:%d\n", SECLOG, __func__, ret, __LINE__); + mutex_unlock(&data->lock); + return; + } + + data->touch_driver[SECURE_TOUCH_MAIN_DEV].enabled = 1; + } + } else { + pr_info("%s: %s: main is not enabled: %d\n", SECLOG, __func__, __LINE__); + } + } else if (data->hall_ic == SECURE_TOUCH_FOLDER_CLOSE) { + if (data->touch_driver[SECURE_TOUCH_MAIN_DEV].enabled) { + if (data->touch_driver[SECURE_TOUCH_MAIN_DEV].is_running) { + schedule_delayed_work(&data->folder_work, msecs_to_jiffies(10)); + mutex_unlock(&data->lock); + return; + } + + if (!IS_ERR_OR_NULL(sysfs_get_dirent(data->device->kobj.sd, "secure"))) { + sysfs_remove_link(&data->device->kobj, "secure"); + data->touch_driver[SECURE_TOUCH_MAIN_DEV].enabled = 0; + } + } else { + pr_info("%s: %s: main is not enabled: %d\n", SECLOG, __func__, __LINE__); + } + + if (data->touch_driver[SECURE_TOUCH_SUB_DEV].registered) { + if (data->touch_driver[SECURE_TOUCH_SUB_DEV].enabled == 1) { + mutex_unlock(&data->lock); + return; + } + + if (!IS_ERR_OR_NULL(data->touch_driver[SECURE_TOUCH_SUB_DEV].kobj)) { + ret = sysfs_create_link(&data->device->kobj, data->touch_driver[SECURE_TOUCH_SUB_DEV].kobj, "secure"); + if (ret < 0) { + pr_info("%s: %s: ret:%d, line:%d\n", SECLOG, __func__, ret, __LINE__); + mutex_unlock(&data->lock); + return; + } + + data->touch_driver[SECURE_TOUCH_SUB_DEV].enabled = 1; + } + } else { + pr_info("%s: %s: sub is not enabled: %d\n", SECLOG, __func__, __LINE__); + } + } else { + mutex_unlock(&data->lock); + return; + } + + mutex_unlock(&data->lock); +} + +#if IS_ENABLED(CONFIG_HALL_NOTIFIER) +static int sec_secure_touch_hall_ic_notifier(struct notifier_block *nb, unsigned long hall_ic, void *ptr) +{ + struct sec_secure_touch *data = container_of(nb, struct sec_secure_touch, nb); + struct hall_notifier_context *hall_notifier; + + if (!data) + return -ENOMEM; + + if (data->device_number < 1) + return -ENODEV; + + hall_notifier = ptr; + + if (strncmp(hall_notifier->name, "flip", 4) != 0) { + pr_info("%s: %s\n", __func__, hall_notifier->name); + return 0; + } + + data->hall_ic = hall_ic; + + pr_info("%s %s: device number:%d,%s %s%s\n", SECLOG, __func__, data->device_number, + data->hall_ic ? "CLOSE" : "OPEN", + data->touch_driver[SECURE_TOUCH_MAIN_DEV].is_running ? "tsp1" : "", + data->touch_driver[SECURE_TOUCH_SUB_DEV].is_running ? "tsp2" : ""); + + if (data->device_number < 2) + return 0; + + if (data->hall_ic == SECURE_TOUCH_FOLDER_OPEN) { + if (data->touch_driver[SECURE_TOUCH_SUB_DEV].registered) { + if (data->touch_driver[SECURE_TOUCH_SUB_DEV].enabled) { + struct sec_ts_plat_data *pdata; + struct sec_trusted_touch *pvm; + + if (!data->touch_driver[SECURE_TOUCH_SUB_DEV].dev) + goto out; + pdata = data->touch_driver[SECURE_TOUCH_SUB_DEV].dev->platform_data; + pvm = pdata->pvm; + + if (atomic_read(&pvm->trusted_touch_enabled) == 1) { + pr_info("[sec_input] %s wait for disabling trusted touch(sub)\n", __func__); + wait_for_completion_interruptible(&pvm->trusted_touch_powerdown); + pr_info("[sec_input] %s complete disabling trusted touch(sub)\n", __func__); + } + } + } + } else if (data->hall_ic == SECURE_TOUCH_FOLDER_CLOSE) { + if (data->touch_driver[SECURE_TOUCH_MAIN_DEV].registered) { + if (data->touch_driver[SECURE_TOUCH_MAIN_DEV].enabled) { + struct sec_ts_plat_data *pdata; + struct sec_trusted_touch *pvm; + + if (!data->touch_driver[SECURE_TOUCH_MAIN_DEV].dev) + goto out; + pdata = data->touch_driver[SECURE_TOUCH_MAIN_DEV].dev->platform_data; + pvm = pdata->pvm; + + if (atomic_read(&pvm->trusted_touch_enabled) == 1) { + pr_info("[sec_input] %s wait for disabling trusted touch(main)\n", __func__); + wait_for_completion_interruptible(&pvm->trusted_touch_powerdown); + pr_info("[sec_input] %s complete disabling trusted touch(main)\n", __func__); + } + } + } + } +out: + schedule_work(&data->folder_work.work); + + return 0; +} +#endif + +#if IS_ENABLED(CONFIG_SUPPORT_SENSOR_FOLD) +static int sec_secure_touch_hall_ic_ssh_notifier(struct notifier_block *nb, unsigned long hall_ic, void *ptr) +{ + struct sec_secure_touch *data = container_of(nb, struct sec_secure_touch, nb_ssh); + + if (!data) + return -ENOMEM; + + if (data->device_number < 1) + return -ENODEV; + + data->hall_ic = hall_ic; + + pr_info("%s %s: device number:%d,%s %s%s\n", SECLOG, __func__, data->device_number, + data->hall_ic ? "CLOSE" : "OPEN", + data->touch_driver[SECURE_TOUCH_MAIN_DEV].is_running ? "tsp1" : "", + data->touch_driver[SECURE_TOUCH_SUB_DEV].is_running ? "tsp2" : ""); + + schedule_work(&data->folder_work.work); + + return 0; +} +#endif +#endif + +static int sec_secure_touch_probe(struct platform_device *pdev) +{ + struct sec_secure_touch *data; + int ret; + +#if !IS_ENABLED(CONFIG_DRV_SAMSUNG) + pr_info("%s %s: sec_class is not support\n", SECLOG, __func__); + return -ENOENT; +#endif + data = kzalloc(sizeof(struct sec_secure_touch), GFP_KERNEL); + if (!data) { + pr_info("%s %s: failed probe: mem\n", SECLOG, __func__); + return -ENOMEM; + } + data->pdev = pdev; + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + data->device = sec_device_create(data, SECURE_TOUCH_DEV_NAME); + if (IS_ERR(data->device)) { + pr_info("%s %s: failed probe: create\n", SECLOG, __func__); + kfree(data); + return -ENODEV; + } +#endif + g_ss_touch = data; + + dev_set_drvdata(data->device, data); + + platform_set_drvdata(pdev, data); + + mutex_init(&data->lock); + + ret = sysfs_create_group(&data->device->kobj, &sec_secure_touch_attr_group); + if (ret < 0) { + pr_info("%s %s: failed probe: create sysfs\n", SECLOG, __func__); +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + sec_device_destroy(data->device->devt); +#endif + g_ss_touch = NULL; + kfree(data); + return -ENODEV; + } + +#if IS_ENABLED(CONFIG_SEC_INPUT_MULTI_DEVICE) +#if IS_ENABLED(CONFIG_HALL_NOTIFIER) + data->nb.notifier_call = sec_secure_touch_hall_ic_notifier; + data->nb.priority = 1; + hall_notifier_register(&data->nb); +#endif +#if IS_ENABLED(CONFIG_SUPPORT_SENSOR_FOLD) + data->nb_ssh.notifier_call = sec_secure_touch_hall_ic_ssh_notifier; + data->nb_ssh.priority = 1; + sensorfold_notifier_register(&data->nb_ssh); +#endif + INIT_DELAYED_WORK(&data->folder_work, sec_secure_touch_hall_ic_work); +#else + sec_secure_touch_set_device(data, 1); +#endif + pr_info("%s: %s\n", SECLOG, __func__); + + return 0; +} + +static int sec_secure_touch_remove(struct platform_device *pdev) +{ + struct sec_secure_touch *data = platform_get_drvdata(pdev); + int ii; + + pr_info("%s: %s\n", SECLOG, __func__); +#if IS_ENABLED(CONFIG_SEC_INPUT_MULTI_DEVICE) + mutex_lock(&data->lock); +#if IS_ENABLED(CONFIG_HALL_NOTIFIER) + hall_notifier_unregister(&data->nb); +#endif + mutex_unlock(&data->lock); +#endif + for (ii = 0; ii < data->device_number; ii++) { + if (data->touch_driver[ii].enabled) + sysfs_remove_link(&data->device->kobj, "secure"); + + sysfs_remove_group(&data->device->kobj, &sec_secure_touch_attr_group); + } + + mutex_destroy(&data->lock); +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + sec_device_destroy(data->device->devt); +#endif + g_ss_touch = NULL; + kfree(data); + return 0; +} + +#if CONFIG_OF +static const struct of_device_id sec_secure_touch_dt_match[] = { + { .compatible = "samsung,ss_touch" }, + {} +}; +#endif + +struct platform_driver sec_secure_touch_driver = { + .probe = sec_secure_touch_probe, + .remove = sec_secure_touch_remove, + .driver = { + .name = "sec_secure_touch", + .owner = THIS_MODULE, +#if CONFIG_OF + .of_match_table = of_match_ptr(sec_secure_touch_dt_match), +#endif + }, +}; + +int sec_secure_touch_init(void) +{ + pr_info("%s: %s\n", SECLOG, __func__); + + platform_driver_register(&sec_secure_touch_driver); + return 0; +} +EXPORT_SYMBOL(sec_secure_touch_init); + +void sec_secure_touch_exit(void) +{ + pr_info("%s; %s\n", SECLOG, __func__); + +}; +EXPORT_SYMBOL(sec_secure_touch_exit); + +MODULE_DESCRIPTION("Samsung Secure Touch Driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/input/sec_input/sec_secure_touch.h b/drivers/input/sec_input/sec_secure_touch.h new file mode 100644 index 000000000000..0cc236c9540c --- /dev/null +++ b/drivers/input/sec_input/sec_secure_touch.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: GPL-2.0 + * Copyright (C) 2022 Samsung Electronics Co., Ltd. + * + * 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. + */ + +#ifndef _SEC_SECURE_TOUCH_H_ +#define _SEC_SECURE_TOUCH_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#if defined(CONFIG_SEC_SYSFS) +//#include +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) +#include +#endif +//#else +//extern struct class *sec_class; +//#endif + +#if IS_ENABLED(CONFIG_HALL_NOTIFIER) +#include +#endif +#if IS_ENABLED(CONFIG_SUPPORT_SENSOR_FOLD) +#include +#endif + +#define SECURE_TOUCH_DEVICE_FIRST 1 +#define SECURE_TOUCH_DEVICE_SECOND 2 + +/* + * folder open = main screen = 0 + * folder close = sub screen = 1 + */ +#define SECURE_TOUCH_MAIN_DEV 0 +#define SECURE_TOUCH_SUB_DEV 1 + +#define SECURE_TOUCH_FOLDER_OPEN 0 +#define SECURE_TOUCH_FOLDER_CLOSE 1 + +#define SECURE_TOUCH_DEV_NAME "ss_touch" + +struct sec_touch_driver { + struct list_head list; + struct device *dev; + int drv_number; + int (*enable)(void *drv_data); + int (*disable)(void *drv_data); + int (*status)(void *drv_data); + int (*irq_handle)(void *drv_data); + void *drv_data; + struct kobject *kobj; + int enabled; + int registered; + int is_running; +}; + +struct sec_secure_touch { + struct list_head list_head; + int secure_enabled; + void *data; + struct platform_device *pdev; + struct device *device; +#if IS_ENABLED(CONFIG_SEC_INPUT_MULTI_DEVICE) + struct delayed_work folder_work; +#endif + int hall_ic; + struct mutex lock; + struct notifier_block nb; + struct notifier_block nb_ssh; + struct sec_touch_driver touch_driver[2]; + int device_number; + int current_device; +}; + +int sec_secure_touch_set_device(struct sec_secure_touch *data, int dev_num); +void sec_secure_touch_sysfs_notify(struct sec_secure_touch *data); +struct sec_touch_driver *sec_secure_touch_register(void *drv_data, struct device *dev, int dev_num, struct kobject *kobj); +void sec_secure_touch_unregister(int dev_num); + +int sec_secure_touch_init(void); +void sec_secure_touch_exit(void); + +extern void hall_ic_register_notify(struct notifier_block *nb); +extern void hall_ic_unregister_notify(struct notifier_block *nb); +#endif diff --git a/drivers/input/sec_input/sec_tclm_v2.c b/drivers/input/sec_input/sec_tclm_v2.c new file mode 100644 index 000000000000..bcb3553834a3 --- /dev/null +++ b/drivers/input/sec_input/sec_tclm_v2.c @@ -0,0 +1,564 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Samsung Electronics Co., Ltd. + * + * 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. + */ + +#include +#include "sec_input.h" +#include "sec_tclm_v2.h" +#include "sec_tsp_log.h" + +struct sec_cal_position sec_cal_positions[CALPOSITION_MAX] = { + {CAL_POS_CMD("NONE", 'N'),}, /* 0, NONe */ + {CAL_POS_CMD("INIT", 'I'),}, /* 1, INIT case, calcount is 00 or FF */ + {CAL_POS_CMD("FACT", 'F'),}, /* 2, FACTory line, run_force_calibration without value */ + {CAL_POS_CMD("OUTS", 'O'),}, /* 3, OUTSide of factory */ + {CAL_POS_CMD("LCIA", 'L'),}, /* 4, LCIA in factory line */ + {CAL_POS_CMD("CENT", 'C'),}, /* 5, svc CENTer, cal from service center */ + {CAL_POS_CMD("ABNO", 'A'),}, /* 6, ABNOrmal case */ + {CAL_POS_CMD("BOOT", 'B'),}, /* 7, BOOT firmup, when firmup in booting time */ + {CAL_POS_CMD("SPEC", 'S'),}, /* 8, SPECout case */ + {CAL_POS_CMD("TUNE", 'V'),}, /* 9, TUNE Version up, when afe version is lage than tune version */ + {CAL_POS_CMD("EVER", 'E'),}, /* 10, EVERytime, always cal in Booting */ + {CAL_POS_CMD("TEST", 'T'),}, /* 11, TESTmode, firmup case in *#2663# */ + {CAL_POS_CMD("UNDE", 'U'),}, /* 12, UNDEfine, undefined value */ + {CAL_POS_CMD("UNDE", 'U'),}, /* 13 */ + {CAL_POS_CMD("UNDE", 'U'),}, /* 14 */ + {CAL_POS_CMD("UNDE", 'U'),} /* 15 */ +}; + +void sec_tclm_case(struct sec_tclm_data *data, int tclm_case) +{ + switch (tclm_case) { + case 0: + case 'F': + case 'f': + if (data->external_factory == true) + sec_tclm_root_of_cal(data, CALPOSITION_OUTSIDE); + else + sec_tclm_root_of_cal(data, CALPOSITION_FACTORY); + break; + + case 'L': + case 'l': + sec_tclm_root_of_cal(data, CALPOSITION_LCIA); + break; + + case 'C': + case 'c': + sec_tclm_root_of_cal(data, CALPOSITION_SVCCENTER); + break; + + case 'O': + case 'o': + sec_tclm_root_of_cal(data, CALPOSITION_OUTSIDE); + break; + + default: + sec_tclm_root_of_cal(data, CALPOSITION_ABNORMAL); + } +} +EXPORT_SYMBOL(sec_tclm_case); + + +int sec_tclm_read(struct sec_tclm_data *data, int address) +{ + if (data->dev && data->tclm_read) + return data->tclm_read(data->dev, address); + + return -ENODEV; +} +int sec_tclm_write(struct sec_tclm_data *data, int address) +{ + if (data->dev && data->tclm_write) + return data->tclm_write(data->dev, address); + + return -ENODEV; +} +int sec_tclm_execute_force_calibration(struct sec_tclm_data *data, int cal_mode) +{ + if (data->dev && data->tclm_execute_force_calibration) + return data->tclm_execute_force_calibration(data->dev, cal_mode); + + return -ENODEV; +} + +int tclm_test_command(struct sec_tclm_data *data, int test_case, int cmd_param1, int cmd_param2, char *buff) +{ + int ret = 1; + const int buff_size = 256; + struct device *dev = data->dev; + + if (!dev) + return -ENODEV; + + switch (test_case) { + case 0: // get tclm_level,afe_base + snprintf(buff, buff_size, "%d,%04X", data->tclm_level, data->afe_base); + break; + case 1: + /* change cal_position & history + * cmd_param[1]: cal_position enum + * cmd_param[2]: tune_fix_ver + */ + if (cmd_param1 == 0xff) + sec_tclm_root_of_cal(data, 0); + else + sec_tclm_root_of_cal(data, cmd_param1); + + if (data->root_of_calibration != data->nvdata.cal_position) { + sec_tclm_reposition_history(data); + data->nvdata.cal_count = 0; + } + data->nvdata.cal_count++; + if (cmd_param1 == 0) { + data->nvdata.cal_count = 0; + + data->nvdata.cal_pos_hist_cnt = 0; + data->nvdata.cal_pos_hist_lastp = 0; + + cmd_param2 = 0; + } else if (cmd_param1 == 0xff) { + data->nvdata.cal_count = 0xff; + + data->nvdata.cal_pos_hist_cnt = 0; + data->nvdata.cal_pos_hist_lastp = 0; + + cmd_param2 = 0xffff; + } + data->nvdata.cal_position = data->root_of_calibration; + data->nvdata.tune_fix_ver = cmd_param2; + ret = sec_tclm_write(data, SEC_TCLM_NVM_ALL_DATA); + if (ret < 0) { + input_info(true, dev, "%s failed\n", __func__); + snprintf(buff, buff_size, "%s", "FAIL"); + return ret; + } + sec_tclm_root_of_cal(data, CALPOSITION_NONE); + + input_info(true, dev, "%s,1: cal_pos: %d, tune_fix_ver:0x%04X\n", + __func__, cmd_param1, cmd_param2); + + sec_tclm_position_history(data); + + snprintf(buff, buff_size, "%s", "OK"); + break; + case 2: + /* change tclm_level, afe_base + * cmd_param[1]: tclm_level + * cmd_param[2]: afe_base + */ + { + data->tclm[0] = (cmd_param1 & 0xFF); + data->tclm[1] = ((cmd_param2 >> 8) & 0xFF); + data->tclm[2] = (cmd_param2 & 0xFF); + + ret = sec_tclm_write(data, SEC_TCLM_NVM_TEST); + if (ret < 0) { + input_info(true, dev, "%s failed\n", __func__); + snprintf(buff, buff_size, "%s", "FAIL"); + return ret; + } + + memset(data->tclm, 0x00, SEC_TCLM_NVM_OFFSET_LENGTH); + ret = sec_tclm_read(data, SEC_TCLM_NVM_TEST); + if (ret < 0) { + input_info(true, dev, "%s failed\n", __func__); + snprintf(buff, buff_size, "%s", "FAIL"); + return ret; + } + data->tclm_level = data->tclm[0]; + data->afe_base = (data->tclm[1] << 8) | data->tclm[2]; + + input_err(true, dev, "%s,2: tclm_level %d, sec_afe_base %04X\n", __func__, data->tclm_level, data->afe_base); + snprintf(buff, buff_size, "%s", "OK"); + } + break; + case 3: /* clear tclm_level, afe_base nv & set to dt_data */ + { + data->tclm[0] = 0xff; + data->tclm[1] = 0xff; + data->tclm[2] = 0xff; + + /* clear tclm_level, afe_base nvm to 0xff */ + ret = sec_tclm_write(data, SEC_TCLM_NVM_TEST); + if (ret < 0) { + input_info(true, dev, "%s failed\n", __func__); + snprintf(buff, buff_size, "%s", "FAIL"); + return ret; + } + + /* get dt_data again */ + data->tclm_parse_dt(dev, data); + + input_err(true, dev, "%s,3: tclm_level %d, sec_afe_base %04X\n", __func__, data->tclm_level, data->afe_base); + snprintf(buff, buff_size, "%s", "OK"); + } + break; + } + + return ret; +} +EXPORT_SYMBOL(tclm_test_command); + +int sec_tclm_test_on_probe(struct sec_tclm_data *data) +{ + int retry = 3; + int ret = 0; + struct device *dev = data->dev; + + if (!dev) + return -ENODEV; + + while (retry--) { + ret = sec_tclm_read(data, SEC_TCLM_NVM_TEST); + if (ret >= 0) + break; + } + + if (ret < 0) + input_err(true, dev, "%s: failed ret:%d\n", __func__, ret); + + return ret; +} +EXPORT_SYMBOL(sec_tclm_test_on_probe); + +int sec_tclm_get_nvm_all(struct sec_tclm_data *data) +{ + int ret = -1; + int retry = 3; + struct device *dev = data->dev; + + if (!dev) + return -ENODEV; + + /* just don't read tune_fix_version, because this is write_only_value. */ + while (retry--) { + ret = sec_tclm_read(data, SEC_TCLM_NVM_ALL_DATA); + if (ret >= 0) + break; + } + + if (ret < 0) { + input_err(true, dev, "%s: failed ret:%d\n", __func__, ret); + return ret; + } + + if (data->nvdata.cal_count == 0xFF || data->nvdata.cal_position >= CALPOSITION_MAX) { + data->nvdata.cal_count = 0; + data->nvdata.cal_position = 0; + data->nvdata.tune_fix_ver = 0; + data->nvdata.cal_pos_hist_cnt = 0; + data->nvdata.cal_pos_hist_lastp = 0; + input_info(true, dev, "%s: cal data is abnormal\n", __func__); + return TCLM_RESULT_ABNORMAL; + } + + if (data->nvdata.cal_pos_hist_cnt > CAL_HISTORY_QUEUE_MAX) + data->nvdata.cal_pos_hist_cnt = 0; /* error case */ + + input_info(true, dev, "%s: cal_count:%d, pos:%d(%4s), hist_count:%d, lastp:%d\n", + __func__, data->nvdata.cal_count, data->nvdata.cal_position, + data->tclm_string[data->nvdata.cal_position].f_name, + data->nvdata.cal_pos_hist_cnt, data->nvdata.cal_pos_hist_lastp); + + sec_tclm_position_history(data); + + return TCLM_RESULT_DONE; +} + +void sec_tclm_position_history(struct sec_tclm_data *data) +{ + int i; + int now_lastp = data->nvdata.cal_pos_hist_lastp; + unsigned char *pStr = NULL; + unsigned char pTmp[5] = { 0 }; + struct device *dev = data->dev; + + if (!dev) + return; + + if (data->nvdata.cal_pos_hist_cnt > CAL_HISTORY_QUEUE_MAX + || data->nvdata.cal_pos_hist_lastp >= CAL_HISTORY_QUEUE_MAX) { + input_info(true, dev, "%s: not initial case, count:%X, p:%X\n", __func__, + data->nvdata.cal_pos_hist_cnt, data->nvdata.cal_pos_hist_lastp); + return; + } + + input_info(true, dev, "%s: [Now] %4s%d\n", __func__, + data->tclm_string[data->nvdata.cal_position].f_name, data->nvdata.cal_count); + + pStr = kzalloc(CAL_HISTORY_QUEUE_MAX * 5, GFP_KERNEL); + if (pStr == NULL) + return; + + for (i = 0; i < data->nvdata.cal_pos_hist_cnt; i++) { + snprintf(pTmp, sizeof(pTmp), "%c%d", data->tclm_string[data->nvdata.cal_pos_hist_queue[2 * now_lastp]].s_name, data->nvdata.cal_pos_hist_queue[2 * now_lastp + 1]); + strlcat(pStr, pTmp, CAL_HISTORY_QUEUE_MAX * 5); + if (i < CAL_HISTORY_QUEUE_SHORT_DISPLAY) { + data->cal_pos_hist_last3[2 * i] = data->tclm_string[data->nvdata.cal_pos_hist_queue[2 * now_lastp]].s_name; + data->cal_pos_hist_last3[2 * i + 1] = data->nvdata.cal_pos_hist_queue[2 * now_lastp + 1]; + } + + if (now_lastp <= 0) + now_lastp = CAL_HISTORY_QUEUE_MAX - 1; + else + now_lastp--; + } + + input_info(true, dev, "%s: [Old] %s\n", __func__, pStr); + + if (i < CAL_HISTORY_QUEUE_SHORT_DISPLAY) + data->cal_pos_hist_last3[2 * i] = 0; + else + data->cal_pos_hist_last3[6] = 0; + + kfree(pStr); +} + +void sec_tclm_debug_info(struct sec_tclm_data *data) +{ + sec_tclm_position_history(data); +} +EXPORT_SYMBOL(sec_tclm_debug_info); + +void sec_tclm_root_of_cal(struct sec_tclm_data *data, int pos) +{ + struct device *dev = data->dev; + + if (!dev) + return; + + data->root_of_calibration = pos; + input_info(true, dev, "%s: root - %d(%4s)\n", __func__, + pos, data->tclm_string[pos].f_name); +} +EXPORT_SYMBOL(sec_tclm_root_of_cal); + +static bool sec_tclm_check_condition_valid(struct sec_tclm_data *data) +{ + struct device *dev = data->dev; + + if (!dev) + return false; + + input_err(true, dev, "%s tclm_level:%02X, last pos:%d(%4s), now pos:%d(%4s)\n", + __func__, data->tclm_level, data->nvdata.cal_position, data->tclm_string[data->nvdata.cal_position].f_name, + data->root_of_calibration, data->tclm_string[data->root_of_calibration].f_name); + + /* enter case */ + switch (data->tclm_level) { + case TCLM_LEVEL_LOCKDOWN: + if ((data->root_of_calibration == CALPOSITION_TUNEUP) + || (data->root_of_calibration == CALPOSITION_INITIAL)) { + return true; + } else if ((data->root_of_calibration == CALPOSITION_TESTMODE) + && ((data->nvdata.cal_position == CALPOSITION_TESTMODE) + || (data->nvdata.cal_position == CALPOSITION_TUNEUP) + || (data->nvdata.cal_position == CALPOSITION_FIRMUP))) { + return true; + } + break; + + case TCLM_LEVEL_CLEAR_NV: + return true; + + case TCLM_LEVEL_EVERYTIME: + return true; + + case TCLM_LEVEL_NONE: + if ((data->root_of_calibration == CALPOSITION_TESTMODE) + || (data->root_of_calibration == CALPOSITION_INITIAL)) { + return true; + } else { + return false; + } + case TCLM_LEVEL_NOT_SUPPORT: + return false; + } + + return false; +} + +void sec_tclm_reposition_history(struct sec_tclm_data *data) +{ + /* current data of cal count,position save cal history queue */ + + if (data->nvdata.cal_pos_hist_cnt > CAL_HISTORY_QUEUE_MAX || data->nvdata.cal_pos_hist_lastp >= CAL_HISTORY_QUEUE_MAX) { + /* queue nvm clear case */ + data->nvdata.cal_pos_hist_cnt = 0; + data->nvdata.cal_pos_hist_lastp = 0; + } + + /*calculate queue lastpointer */ + if (data->nvdata.cal_pos_hist_cnt == 0) + data->nvdata.cal_pos_hist_lastp = 0; + else if (data->nvdata.cal_pos_hist_lastp >= (CAL_HISTORY_QUEUE_MAX - 1)) + data->nvdata.cal_pos_hist_lastp = 0; + else + data->nvdata.cal_pos_hist_lastp++; + + /*calculate queue count */ + if (data->nvdata.cal_pos_hist_cnt >= CAL_HISTORY_QUEUE_MAX) + data->nvdata.cal_pos_hist_cnt = CAL_HISTORY_QUEUE_MAX; + else + data->nvdata.cal_pos_hist_cnt++; + + data->nvdata.cal_pos_hist_queue[data->nvdata.cal_pos_hist_lastp * 2] = data->nvdata.cal_position; + data->nvdata.cal_pos_hist_queue[data->nvdata.cal_pos_hist_lastp * 2 + 1] = data->nvdata.cal_count; +} + +int sec_execute_tclm_package(struct sec_tclm_data *data, int factory_mode) +{ + int ret; + int retry = 3; + struct device *dev = data->dev; + + if (!dev) + return -ENODEV; + + /* first read cal data for compare */ + + ret = sec_tclm_get_nvm_all(data); + if (ret < 0) { + input_err(true, dev, "%s: sec_tclm_nvm_all_data i2c read fail", __func__); + goto out; + } + + + input_err(true, dev, "%s: tclm_level:%02X, last pos:%d(%4s), now pos:%d(%4s), factory:%d\n", + __func__, data->tclm_level, data->nvdata.cal_position, data->tclm_string[data->nvdata.cal_position].f_name, + data->root_of_calibration, data->tclm_string[data->root_of_calibration].f_name, + factory_mode); + + /* if is run_for_calibration, don't check cal condition */ + if (!factory_mode) { + + /*check cal condition */ + ret = sec_tclm_check_condition_valid(data); + if (!ret) { + input_err(true, dev, "%s: fail tclm condition,%d, root:%d\n", + __func__, ret, data->root_of_calibration); + return TCLM_RESULT_DONE; /* do not need calibration */ + } + + input_err(true, dev, "%s: RUN OFFSET CALIBRATION,%d\n", __func__, ret); + + /* execute force cal */ + ret = sec_tclm_execute_force_calibration(data, TCLM_OFFSET_CAL_SEC); + if (ret < 0) { + input_err(true, dev, "%s: fail to write OFFSET CAL SEC!\n", __func__); + return ret; + } + } + + if ((data->nvdata.cal_count < 1) || (data->nvdata.cal_count >= 0xFF)) { + /* all nvm clear */ + data->nvdata.cal_count = 0; + data->nvdata.cal_pos_hist_cnt = 0; + data->nvdata.cal_pos_hist_lastp = 0; + } else if (data->root_of_calibration != data->nvdata.cal_position) { + sec_tclm_reposition_history(data); + data->nvdata.cal_count = 0; + } + + if (data->nvdata.cal_count == 0) { + /* saving cal position */ + data->nvdata.cal_position = data->root_of_calibration; + } + + data->nvdata.cal_count++; + + /* saving tune_version */ + ret = sec_tclm_read(data, SEC_TCLM_NVM_OFFSET_IC_FIRMWARE_VER); + if (ret < 0) { + input_err(true, dev, "%s: SEC_TCLM_NVM_OFFSET_IC_FIRMWARE_VER i2c read fail", __func__); + goto out; + } + data->nvdata.tune_fix_ver = ret; + + while (retry--) { + ret = sec_tclm_write(data, SEC_TCLM_NVM_ALL_DATA); + if (ret >= 0) + break; + } + + if (ret < 0) { + input_err(true, dev, "%s: failed ret:%d\n", __func__, ret); + goto out; + } + + sec_tclm_position_history(data); + + return TCLM_RESULT_CAL_DONE; + +out: + return ret; +} +EXPORT_SYMBOL(sec_execute_tclm_package); + +int sec_tclm_check_cal_case(struct sec_tclm_data *data) +{ + int restore_cal = 0; + int ret = 0; + struct device *dev = data->dev; + + if (!dev) + return -ENODEV; + + if (data->nvdata.cal_count == 0xFF) { + ret = sec_tclm_read(data, SEC_TCLM_NVM_ALL_DATA); + if (ret < 0) { + input_err(true, dev, "%s: fail to read SEC_TCLM_NVM_ALL_DATA !\n", __func__); + return ret; + } + + input_info(true, dev, "%s: cal_count value [%d]\n", __func__, data->nvdata.cal_count); + } + + if ((data->nvdata.cal_count == 0) || (data->nvdata.cal_count == 0xFF)) { + input_err(true, dev, "%s: Calcount is abnormal,%02X\n", __func__, data->nvdata.cal_count); + /* nvm uninitialed case */ + sec_tclm_root_of_cal(data, CALPOSITION_INITIAL); + restore_cal = 1; + } else if (data->tclm_level == TCLM_LEVEL_EVERYTIME) { + /* everytime case */ + sec_tclm_root_of_cal(data, CALPOSITION_EVERYTIME); + restore_cal = 1; + } + + if (restore_cal) { + ret = sec_execute_tclm_package(data, 0); + if (ret < 0) { + input_err(true, dev, "%s: fail sec_execute_tclm_package!\n", __func__); + return ret; + } + sec_tclm_root_of_cal(data, CALPOSITION_NONE); + return ret; + } + + ret = sec_tclm_get_nvm_all(data); + if (ret < 0) + input_info(true, dev, "%s: sec_tclm_get_nvm_all error\n", __func__); + + return ret; +} +EXPORT_SYMBOL(sec_tclm_check_cal_case); + +void sec_tclm_initialize(struct sec_tclm_data *data) +{ + data->root_of_calibration = CALPOSITION_NONE; + data->nvdata.cal_position = 0; + data->nvdata.cal_pos_hist_cnt = 0; + memset(data->cal_pos_hist_last3, 0x00, sizeof(data->cal_pos_hist_last3)); + data->tclm_string = sec_cal_positions; + data->nvdata.cal_count = 0xFF; +} +EXPORT_SYMBOL(sec_tclm_initialize); + +MODULE_DESCRIPTION("Samsung tclm command"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/sec_input/sec_tclm_v2.h b/drivers/input/sec_input/sec_tclm_v2.h new file mode 100644 index 000000000000..88fbe6ac7f33 --- /dev/null +++ b/drivers/input/sec_input/sec_tclm_v2.h @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: GPL-2.0 + * Copyright (C) 2022 Samsung Electronics Co., Ltd. + * + * 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. + */ + +#ifndef _SEC_TCLM_H_ +#define _SEC_TCLM_H_ + +#include +#include +#include +#include +#include +#include + +/* TCLM_CONCEPT - start */ +#define TCLM_LEVEL_NONE 0x00 +#define TCLM_LEVEL_CLEAR_NV 0x01 +#define TCLM_LEVEL_LOCKDOWN 0x02 +#define TCLM_LEVEL_EVERYTIME 0x05 +#define TCLM_LEVEL_NOT_SUPPORT 0xFF + +#define CAL_HISTORY_QUEUE_MAX 10 +#define CAL_HISTORY_QUEUE_SHORT_DISPLAY 3 + +#define TCLM_AMBIENT_CAL 0 +#define TCLM_OFFSET_CAL_SDC 1 +#define TCLM_OFFSET_CAL_SEC 2 + +#define SEC_TCLM_NVM_OFFSET 1 +#define SEC_TCLM_NVM_OFFSET_LENGTH 4 +/* [0]: tclm_level, [1] afe_base_high, [2] afe_base_low = 3byte */ + +#define SEC_CAL_PASS 1 + +enum tclm_offset { + SEC_TCLM_NVM_OFFSET_IC_FIRMWARE_VER = 1, + SEC_TCLM_NVM_ALL_DATA = 2, + SEC_TCLM_NVM_TEST = 3, +}; + +#define CAL_POS_CMD(full_name, short_name) .f_name = full_name, .s_name = short_name + +enum tclm_root { + CALPOSITION_NONE = 0, + CALPOSITION_INITIAL = 1, + CALPOSITION_FACTORY = 2, + CALPOSITION_OUTSIDE = 3, + CALPOSITION_LCIA = 4, + CALPOSITION_SVCCENTER = 5, + CALPOSITION_ABNORMAL = 6, + CALPOSITION_FIRMUP = 7, + CALPOSITION_SPECOUT = 8, + CALPOSITION_TUNEUP = 9, + CALPOSITION_EVERYTIME = 10, + CALPOSITION_TESTMODE = 11, + CALPOSITION_UNDEFINE = 12, + CALPOSITION_MAX = 16, +}; + +enum tclm_result { + TCLM_RESULT_DONE = 0, + TCLM_RESULT_ABNORMAL = 1, + TCLM_RESULT_CAL_DONE = 2, +}; + +struct sec_cal_position { + const char *f_name; + const char s_name; +}; + +struct sec_tclm_nvdata { + u8 cal_count; + u16 tune_fix_ver; + u8 cal_position; + u8 cal_pos_hist_cnt; + u8 cal_pos_hist_lastp; + u8 cal_pos_hist_queue[2 * CAL_HISTORY_QUEUE_MAX]; + u8 cal_fail_falg; /* pass : 1 fail : etc */ + u8 cal_fail_cnt; /* history cnt */ +} __attribute__ ((packed)); + +/* TCLM_CONCEPT - end */ +struct sec_tclm_data { + int tclm_level; + int afe_base; + bool external_factory; + u8 root_of_calibration; + struct sec_cal_position *tclm_string; + u8 cal_pos_hist_last3[2 * CAL_HISTORY_QUEUE_SHORT_DISPLAY + 1]; /* 7 */ + + struct device *dev; + int (*tclm_read)(struct device *dev, int address); + int (*tclm_write)(struct device *dev, int address); + int (*tclm_execute_force_calibration)(struct device *dev, int cal_mode); + void (*tclm_parse_dt)(struct device *dev, struct sec_tclm_data *tdata); + + struct sec_tclm_nvdata nvdata; + u8 tclm[SEC_TCLM_NVM_OFFSET_LENGTH]; + bool support_tclm_test; +}; + +void sec_tclm_case(struct sec_tclm_data *data, int tclm_case); +int sec_tclm_get_nvm_all(struct sec_tclm_data *data); +void sec_tclm_position_history(struct sec_tclm_data *data); +void sec_tclm_root_of_cal(struct sec_tclm_data *data, int pos); +void sec_tclm_debug_info(struct sec_tclm_data *data); +void sec_tclm_reposition_history(struct sec_tclm_data *data); +int sec_execute_tclm_package(struct sec_tclm_data *data, int factory_mode); +int sec_tclm_check_cal_case(struct sec_tclm_data *data); +void sec_tclm_initialize(struct sec_tclm_data *data); +int tclm_test_command(struct sec_tclm_data *data, int test_case, int cmd_param1, int cmd_param2, char *buff); +int sec_tclm_test_on_probe(struct sec_tclm_data *data); + +#endif diff --git a/drivers/input/sec_input/sec_trusted_touch.c b/drivers/input/sec_input/sec_trusted_touch.c new file mode 100644 index 000000000000..00cccc36dc73 --- /dev/null +++ b/drivers/input/sec_input/sec_trusted_touch.c @@ -0,0 +1,903 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* drivers/input/sec_input/sec_trusted_touch.c + * + * Core file for Samsung input device driver + * + * 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. + */ + +#include "sec_trusted_touch.h" + +static int sec_trusted_touch_get_pvm_driver_state(struct trusted_touch_vm_info *vm_info) +{ + return atomic_read(&vm_info->vm_state); +} + +static void sec_trusted_touch_set_pvm_driver_state(struct trusted_touch_vm_info *vm_info, + int state) +{ + atomic_set(&vm_info->vm_state, state); +} + +static void sec_trusted_touch_event_notify(struct sec_ts_plat_data *pdata, int event) +{ + atomic_set(&pdata->pvm->trusted_touch_event, event); + sysfs_notify(&pdata->input_dev->dev.kobj, NULL, "trusted_touch_event"); +} + +static void sec_trusted_clk_disable_unprepare(struct sec_trusted_touch *pvm) +{ + clk_disable_unprepare(pvm->core_clk); + clk_disable_unprepare(pvm->iface_clk); +} + +static void sec_trusted_bus_put(struct sec_ts_plat_data *pdata) +{ + struct sec_trusted_touch *pvm = pdata->pvm; + struct device *dev = NULL; + + dev = pdata->bus_master; + + mutex_lock(&pvm->clk_io_ctrl_mutex); + if (pvm->core_clk != NULL && pvm->iface_clk != NULL) + sec_trusted_clk_disable_unprepare(pvm); + pm_runtime_put_sync(dev); + mutex_unlock(&pvm->clk_io_ctrl_mutex); +} + +static void sec_trusted_touch_abort_handler(struct sec_ts_plat_data *pdata, int error) +{ + atomic_set(&pdata->pvm->trusted_touch_abort_status, error); + input_info(true, pdata->dev, "TUI session aborted with failure:%d\n", error); + sec_trusted_touch_event_notify(pdata, error); +} + + +static struct gh_acl_desc *sec_trusted_vm_get_acl(enum gh_vm_names vm_name) +{ + struct gh_acl_desc *acl_desc; + gh_vmid_t vmid; + +#if (KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE) + ghd_rm_get_vmid(vm_name, &vmid); +#else + gh_rm_get_vmid(vm_name, &vmid); +#endif + + acl_desc = kzalloc(offsetof(struct gh_acl_desc, acl_entries[1]), + GFP_KERNEL); + if (!acl_desc) + return ERR_PTR(ENOMEM); + + acl_desc->n_acl_entries = 1; + acl_desc->acl_entries[0].vmid = vmid; + acl_desc->acl_entries[0].perms = GH_RM_ACL_R | GH_RM_ACL_W; + + return acl_desc; +} + +static struct gh_sgl_desc *sec_trusted_vm_get_sgl( + struct trusted_touch_vm_info *vm_info) +{ + struct gh_sgl_desc *sgl_desc; + int i; + + sgl_desc = kzalloc(offsetof(struct gh_sgl_desc, + sgl_entries[vm_info->iomem_list_size]), GFP_KERNEL); + if (!sgl_desc) + return ERR_PTR(ENOMEM); + + sgl_desc->n_sgl_entries = vm_info->iomem_list_size; + + for (i = 0; i < vm_info->iomem_list_size; i++) { + sgl_desc->sgl_entries[i].ipa_base = vm_info->iomem_bases[i]; + sgl_desc->sgl_entries[i].size = vm_info->iomem_sizes[i]; + } + + return sgl_desc; +} + +static int sec_trusted_populate_vm_info(struct device *dev) +{ + int i, gpio, rc = 0; + struct trusted_touch_vm_info *vm_info; + struct device_node *np = dev->of_node; + int num_regs, num_sizes, num_gpios, list_size; + struct resource res; + struct sec_ts_plat_data *pdata; + + pdata = (struct sec_ts_plat_data *)dev_get_platdata(dev); + + vm_info = kzalloc(sizeof(struct trusted_touch_vm_info), GFP_KERNEL); + if (!vm_info) { + rc = -ENOMEM; + goto error; + } + + pdata->pvm->vm_info = vm_info; + vm_info->vm_name = GH_TRUSTED_VM; + + rc = of_property_read_u32(np, "trusted-touch-spi-irq", &vm_info->hw_irq); + if (rc) { + input_err(true, pdata->dev, "Failed to read trusted touch SPI irq:%d\n", rc); + goto vm_error; + } + + num_regs = of_property_count_u32_elems(np, "trusted-touch-io-bases"); + if (num_regs < 0) { + input_err(true, pdata->dev, "Invalid number of IO regions specified\n"); + rc = -EINVAL; + goto vm_error; + } + + num_sizes = of_property_count_u32_elems(np, "trusted-touch-io-sizes"); + if (num_sizes < 0) { + input_err(true, pdata->dev, "Invalid number of IO regions specified\n"); + rc = -EINVAL; + goto vm_error; + } + + if (num_regs != num_sizes) { + input_err(true, pdata->dev, "IO bases and sizes doe not match\n"); + rc = -EINVAL; + goto vm_error; + } + + num_gpios = of_gpio_named_count(np, "trusted-touch-vm-gpio-list"); + if (num_gpios < 0) { + input_err(true, pdata->dev, "Ignoring invalid trusted gpio list: %d\n", num_gpios); + num_gpios = 0; + } + + list_size = num_regs + num_gpios; + vm_info->iomem_list_size = list_size; + + vm_info->iomem_bases = devm_kcalloc(pdata->dev, list_size, sizeof(*vm_info->iomem_bases), GFP_KERNEL); + if (!vm_info->iomem_bases) { + rc = -ENOMEM; + goto vm_error; + } + + vm_info->iomem_sizes = devm_kcalloc(pdata->dev, list_size, sizeof(*vm_info->iomem_sizes), GFP_KERNEL); + if (!vm_info->iomem_sizes) { + rc = -ENOMEM; + goto io_bases_error; + } + + for (i = 0; i < num_gpios; ++i) { + gpio = of_get_named_gpio(np, "trusted-touch-vm-gpio-list", i); + if (gpio < 0 || !gpio_is_valid(gpio)) { + input_err(true, pdata->dev, "Invalid gpio %d at position %d\n", gpio, i); + return gpio; + } + + if (!msm_gpio_get_pin_address(gpio, &res)) { + input_err(true, pdata->dev, "Failed to retrieve gpio-%d resource\n", gpio); + return -ENODATA; + } + + vm_info->iomem_bases[i] = res.start; + vm_info->iomem_sizes[i] = resource_size(&res); + } + + rc = of_property_read_u32_array(np, "trusted-touch-io-bases", + &vm_info->iomem_bases[i], list_size - i); + if (rc) { + input_err(true, pdata->dev, "Failed to read trusted touch io bases:%d\n", rc); + goto io_bases_error; + } + + rc = of_property_read_u32_array(np, "trusted-touch-io-sizes", + &vm_info->iomem_sizes[i], list_size - i); + if (rc) { + input_err(true, pdata->dev, "Failed to read trusted touch io sizes:%d\n", rc); + goto io_sizes_error; + } + + rc = of_property_read_string(np, "trusted-touch-type", + &vm_info->trusted_touch_type); + if (rc) { + input_err(true, pdata->dev, "%s: No trusted touch type selection made\n", __func__); + vm_info->mem_tag = GH_MEM_NOTIFIER_TAG_TOUCH_PRIMARY; + vm_info->irq_label = GH_IRQ_LABEL_TRUSTED_TOUCH_PRIMARY; + rc = 0; + } else if (!strcmp(vm_info->trusted_touch_type, "primary")) { + vm_info->mem_tag = GH_MEM_NOTIFIER_TAG_TOUCH_PRIMARY; + vm_info->irq_label = GH_IRQ_LABEL_TRUSTED_TOUCH_PRIMARY; + } else if (!strcmp(vm_info->trusted_touch_type, "secondary")) { + vm_info->mem_tag = GH_MEM_NOTIFIER_TAG_TOUCH_SECONDARY; + vm_info->irq_label = GH_IRQ_LABEL_TRUSTED_TOUCH_SECONDARY; + } + + return rc; + +io_sizes_error: + kfree(vm_info->iomem_sizes); +io_bases_error: + kfree(vm_info->iomem_bases); +vm_error: + kfree(vm_info); +error: + return rc; +} + +static void sec_trusted_destroy_vm_info(struct trusted_touch_vm_info *vm_info) +{ + kfree(vm_info->iomem_sizes); + kfree(vm_info->iomem_bases); + kfree(vm_info); +} + +static void sec_trusted_vm_deinit(struct trusted_touch_vm_info *vm_info) +{ + if (vm_info->mem_cookie) + gh_mem_notifier_unregister(vm_info->mem_cookie); + sec_trusted_destroy_vm_info(vm_info); +} + +static void sec_trusted_touch_abort_pvm(struct sec_ts_plat_data *pdata) +{ + struct sec_trusted_touch *pvm = pdata->pvm; + struct trusted_touch_vm_info *vm_info = pvm->vm_info; + int rc = 0; + int vm_state = sec_trusted_touch_get_pvm_driver_state(vm_info); + + if (vm_state >= TRUSTED_TOUCH_PVM_STATE_MAX) { + input_err(true, pdata->dev, "Invalid driver state: %d\n", vm_state); + return; + } + + switch (vm_state) { + case PVM_IRQ_RELEASE_NOTIFIED: + case PVM_ALL_RESOURCES_RELEASE_NOTIFIED: + case PVM_IRQ_LENT: + case PVM_IRQ_LENT_NOTIFIED: + rc = gh_irq_reclaim(vm_info->irq_label); + if (rc) + input_err(true, pdata->dev, "failed to reclaim irq on pvm rc:%d\n", rc); + fallthrough; + case PVM_IRQ_RECLAIMED: + case PVM_IOMEM_LENT: + case PVM_IOMEM_LENT_NOTIFIED: + case PVM_IOMEM_RELEASE_NOTIFIED: +#if (KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE) + rc = ghd_rm_mem_reclaim(vm_info->vm_mem_handle, 0); +#else + rc = gh_rm_mem_reclaim(vm_info->vm_mem_handle, 0); +#endif + if (rc) + input_err(true, pdata->dev, "failed to reclaim iomem on pvm rc:%d\n", rc); + vm_info->vm_mem_handle = 0; + fallthrough; + case PVM_IOMEM_RECLAIMED: + case PVM_INTERRUPT_DISABLED: + enable_irq(pdata->irq); + fallthrough; + case PVM_I2C_RESOURCE_ACQUIRED: + case PVM_INTERRUPT_ENABLED: + sec_trusted_bus_put(pdata); + fallthrough; + case TRUSTED_TOUCH_PVM_INIT: + case PVM_I2C_RESOURCE_RELEASED: + atomic_set(&pvm->trusted_touch_enabled, 0); + atomic_set(&pvm->trusted_touch_transition, 0); + complete_all(&pvm->trusted_touch_powerdown); + break; + } + + atomic_set(&pvm->trusted_touch_abort_status, 0); + + sec_trusted_touch_set_pvm_driver_state(vm_info, TRUSTED_TOUCH_PVM_INIT); +} + +static int sec_trusted_clk_prepare_enable(struct sec_ts_plat_data *pdata) +{ + struct sec_trusted_touch *pvm = pdata->pvm; + int ret; + + ret = clk_prepare_enable(pvm->iface_clk); + if (ret) { + input_err(true, pdata->dev, "%s: error on clk_prepare_enable(iface_clk): %d\n", __func__, ret); + return ret; + } + + ret = clk_prepare_enable(pvm->core_clk); + if (ret) { + clk_disable_unprepare(pvm->iface_clk); + input_err(true, pdata->dev, "%s: error clk_prepare_enable(core_clk): %d\n", __func__, ret); + } + return ret; +} + +static int sec_trusted_bus_get(struct sec_ts_plat_data *pdata) +{ + struct sec_trusted_touch *pvm = pdata->pvm; + + int rc = 0; + struct device *dev = NULL; + + dev = pdata->bus_master->parent; + + mutex_lock(&pvm->clk_io_ctrl_mutex); + rc = pm_runtime_get_sync(dev); + if (rc >= 0 && pvm->core_clk != NULL && + pvm->iface_clk != NULL) { + rc = sec_trusted_clk_prepare_enable(pdata); + if (rc) + pm_runtime_put_sync(dev); + } + + mutex_unlock(&pvm->clk_io_ctrl_mutex); + return rc; +} + +static struct gh_notify_vmid_desc *sec_trusted_vm_get_vmid(gh_vmid_t vmid) +{ + struct gh_notify_vmid_desc *vmid_desc; + + vmid_desc = kzalloc(offsetof(struct gh_notify_vmid_desc, + vmid_entries[1]), GFP_KERNEL); + if (!vmid_desc) + return ERR_PTR(ENOMEM); + + vmid_desc->n_vmid_entries = 1; + vmid_desc->vmid_entries[0].vmid = vmid; + return vmid_desc; +} + +static void stm_trusted_touch_pvm_vm_mode_disable(struct sec_ts_plat_data *pdata) +{ + struct sec_trusted_touch *pvm = pdata->pvm; + struct trusted_touch_vm_info *vm_info = pvm->vm_info; + int rc = 0; + + atomic_set(&pvm->trusted_touch_transition, 1); + + if (atomic_read(&pvm->trusted_touch_abort_status)) { + sec_trusted_touch_abort_pvm(pdata); + return; + } + + if (sec_trusted_touch_get_pvm_driver_state(vm_info) != + PVM_ALL_RESOURCES_RELEASE_NOTIFIED) + input_err(true, pdata->dev, "all release notifications are not received yet\n"); + +#if (KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE) + rc = ghd_rm_mem_reclaim(vm_info->vm_mem_handle, 0); +#else + rc = gh_rm_mem_reclaim(vm_info->vm_mem_handle, 0); +#endif + if (rc) { + input_err(true, pdata->dev, "Trusted touch VM mem reclaim failed rc:%d\n", rc); + goto error; + } + sec_trusted_touch_set_pvm_driver_state(vm_info, PVM_IOMEM_RECLAIMED); + vm_info->vm_mem_handle = 0; + input_info(true, pdata->dev, "vm mem reclaim succeded!\n"); + + rc = gh_irq_reclaim(vm_info->irq_label); + if (rc) { + input_err(true, pdata->dev, "failed to reclaim irq on pvm rc:%d\n", rc); + goto error; + } + sec_trusted_touch_set_pvm_driver_state(vm_info, PVM_IRQ_RECLAIMED); + input_info(true, pdata->dev, "vm irq reclaim succeded!\n"); + + sec_trusted_touch_set_pvm_driver_state(vm_info, PVM_INTERRUPT_ENABLED); + sec_trusted_bus_put(pdata); + atomic_set(&pvm->trusted_touch_transition, 0); + sec_trusted_touch_set_pvm_driver_state(vm_info, + PVM_I2C_RESOURCE_RELEASED); + sec_trusted_touch_set_pvm_driver_state(vm_info, + TRUSTED_TOUCH_PVM_INIT); + + sec_delay(250); + + atomic_set(&pvm->trusted_touch_enabled, 0); + complete_all(&pvm->trusted_touch_powerdown); + input_info(true, pdata->dev, "trusted touch disabled\n"); + + enable_irq(pdata->irq); + return; +error: + sec_trusted_touch_abort_handler(pdata, + TRUSTED_TOUCH_EVENT_RECLAIM_FAILURE); +} + +static void sec_trusted_vm_irq_on_release_callback(void *data, + unsigned long notif_type, + enum gh_irq_label label) +{ + struct sec_ts_plat_data *pdata = (struct sec_ts_plat_data *)data; + struct trusted_touch_vm_info *vm_info = pdata->pvm->vm_info; + if (notif_type != GH_RM_NOTIF_VM_IRQ_RELEASED) { + input_err(true, pdata->dev, "invalid notification type\n"); + return; + } + if (sec_trusted_touch_get_pvm_driver_state(vm_info) == PVM_IOMEM_RELEASE_NOTIFIED) + sec_trusted_touch_set_pvm_driver_state(vm_info, PVM_ALL_RESOURCES_RELEASE_NOTIFIED); + else + sec_trusted_touch_set_pvm_driver_state(vm_info, PVM_IRQ_RELEASE_NOTIFIED); +} + +static void sec_trusted_vm_mem_on_release_handler(enum gh_mem_notifier_tag tag, + unsigned long notif_type, void *entry_data, void *notif_msg) +{ + struct sec_ts_plat_data *pdata; + struct trusted_touch_vm_info *vm_info; + struct gh_rm_notif_mem_released_payload *release_payload; + + if (!entry_data) { + pr_err("[sec_input] %s: Invalid entry_data\n", __func__); + return; + } + + pdata = (struct sec_ts_plat_data *)entry_data; + + vm_info = pdata->pvm->vm_info; + if (!vm_info) { + input_err(true, pdata->dev, "Invalid vm_info\n"); + return; + } + + if (notif_type != GH_RM_NOTIF_MEM_RELEASED) { + input_err(true, pdata->dev, "Invalid notification type\n"); + return; + } + + if (tag != vm_info->mem_tag) { + input_err(true, pdata->dev, "Invalid tag\n"); + return; + } + + if (!notif_msg) { + input_err(true, pdata->dev, "Invalid data or notification message\n"); + return; + } + + release_payload = (struct gh_rm_notif_mem_released_payload *)notif_msg; + if (release_payload->mem_handle != vm_info->vm_mem_handle) { + input_err(true, pdata->dev, "Invalid mem handle detected\n"); + return; + } + + input_info(true, pdata->dev, "received mem lend request with handle:\n"); + if (sec_trusted_touch_get_pvm_driver_state(vm_info) == + PVM_IRQ_RELEASE_NOTIFIED) { + sec_trusted_touch_set_pvm_driver_state(vm_info, + PVM_ALL_RESOURCES_RELEASE_NOTIFIED); + } else { + sec_trusted_touch_set_pvm_driver_state(vm_info, + PVM_IOMEM_RELEASE_NOTIFIED); + } +} + +static int sec_trusted_vm_mem_lend(struct sec_ts_plat_data *pdata) +{ + struct gh_acl_desc *acl_desc; + struct gh_sgl_desc *sgl_desc; + struct gh_notify_vmid_desc *vmid_desc; + gh_memparcel_handle_t mem_handle; + gh_vmid_t trusted_vmid; + struct trusted_touch_vm_info *vm_info = pdata->pvm->vm_info; + int rc = 0; + + acl_desc = sec_trusted_vm_get_acl(GH_TRUSTED_VM); + if (IS_ERR(acl_desc)) { + input_err(true, pdata->dev, "Failed to get acl of IO memories for Trusted touch(%ld)\n", + PTR_ERR(acl_desc)); + return -EINVAL; + } + + sgl_desc = sec_trusted_vm_get_sgl(vm_info); + if (IS_ERR(sgl_desc)) { + input_err(true, pdata->dev, "Failed to get sgl of IO memories for Trusted touch(%ld)\n", + PTR_ERR(sgl_desc)); + rc = -EINVAL; + goto sgl_error; + } + +#if (KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE) + rc = ghd_rm_mem_lend(GH_RM_MEM_TYPE_IO, 0, TRUSTED_TOUCH_MEM_LABEL, + acl_desc, sgl_desc, NULL, &mem_handle); +#else + rc = gh_rm_mem_lend(GH_RM_MEM_TYPE_IO, 0, TRUSTED_TOUCH_MEM_LABEL, + acl_desc, sgl_desc, NULL, &mem_handle); +#endif + if (rc) { + input_err(true, pdata->dev, "Failed to lend IO memories for Trusted touch rc:%d\n", + rc); + goto error; + } + + input_info(true, pdata->dev, "vm mem lend succeded\n"); + + sec_trusted_touch_set_pvm_driver_state(vm_info, PVM_IOMEM_LENT); + +#if (KERNEL_VERSION(6, 1, 0) <= LINUX_VERSION_CODE) + ghd_rm_get_vmid(GH_TRUSTED_VM, &trusted_vmid); +#else + gh_rm_get_vmid(GH_TRUSTED_VM, &trusted_vmid); +#endif + + vmid_desc = sec_trusted_vm_get_vmid(trusted_vmid); + + rc = gh_rm_mem_notify(mem_handle, GH_RM_MEM_NOTIFY_RECIPIENT_SHARED, + vm_info->mem_tag, vmid_desc); + if (rc) { + input_err(true, pdata->dev, "Failed to notify mem lend to hypervisor rc:%d\n", rc); + goto vmid_error; + } + + sec_trusted_touch_set_pvm_driver_state(vm_info, PVM_IOMEM_LENT_NOTIFIED); + + vm_info->vm_mem_handle = mem_handle; +vmid_error: + kfree(vmid_desc); +error: + kfree(sgl_desc); +sgl_error: + kfree(acl_desc); + + return rc; +} + +static int sec_trusted_touch_pvm_vm_mode_enable(struct sec_ts_plat_data *pdata) +{ + struct sec_trusted_touch *pvm = pdata->pvm; + int rc = 0; + struct trusted_touch_vm_info *vm_info = pvm->vm_info; + + atomic_set(&pvm->trusted_touch_transition, 1); + mutex_lock(&pvm->transition_lock); + + if (atomic_read(&pdata->enabled) == false) { + input_err(true, pdata->dev, "Invalid power state for operation\n"); + atomic_set(&pvm->trusted_touch_transition, 0); + rc = -EPERM; + goto error; + } + + /* i2c session start and resource acquire */ + if (sec_trusted_bus_get(pdata) < 0) { + input_err(true, pdata->dev, "sec_trusted_bus_get failed\n"); + rc = -EIO; + goto error; + } + + sec_trusted_touch_set_pvm_driver_state(vm_info, PVM_I2C_RESOURCE_ACQUIRED); + /* flush pending interurpts from FIFO */ + disable_irq(pdata->irq); + reinit_completion(&pvm->trusted_touch_powerdown); + atomic_set(&pvm->trusted_touch_enabled, 1); + sec_trusted_touch_set_pvm_driver_state(vm_info, PVM_INTERRUPT_DISABLED); + sec_input_release_all_finger(pdata->dev); + + rc = sec_trusted_vm_mem_lend(pdata); + if (rc) { + input_err(true, pdata->dev, "Failed to lend memory\n"); + goto abort_handler; + } + input_info(true, pdata->dev, "[sec_input] vm mem lend succeded\n"); + rc = gh_irq_lend_v2(vm_info->irq_label, vm_info->vm_name, + pdata->irq, &sec_trusted_vm_irq_on_release_callback, pdata); + if (rc) { + input_err(true, pdata->dev, "Failed to lend irq\n"); + goto abort_handler; + } + + input_info(true, pdata->dev, "vm irq lend succeded for irq:%d\n", pdata->irq); + sec_trusted_touch_set_pvm_driver_state(vm_info, PVM_IRQ_LENT); + + rc = gh_irq_lend_notify(vm_info->irq_label); + if (rc) { + input_info(true, pdata->dev, "Failed to notify irq\n"); + goto abort_handler; + } + sec_trusted_touch_set_pvm_driver_state(vm_info, PVM_IRQ_LENT_NOTIFIED); + + mutex_unlock(&pvm->transition_lock); + atomic_set(&pvm->trusted_touch_transition, 0); + input_info(true, pdata->dev, "trusted touch enabled\n"); + return rc; + +abort_handler: + atomic_set(&pvm->trusted_touch_enabled, 0); + complete_all(&pvm->trusted_touch_powerdown); + sec_trusted_touch_abort_handler(pdata, TRUSTED_TOUCH_EVENT_LEND_FAILURE); + +error: + mutex_unlock(&pvm->transition_lock); + return rc; +} + +int sec_trusted_handle_trusted_touch_pvm(struct sec_ts_plat_data *pdata, int value) +{ + struct sec_trusted_touch *pvm = pdata->pvm; + int err = 0; + + switch (value) { + case 0: + if (atomic_read(&pvm->trusted_touch_enabled) == 0 && + (atomic_read(&pvm->trusted_touch_abort_status) == 0)) { + input_err(true, pdata->dev, "Trusted touch is already disabled\n"); + break; + } + if (atomic_read(&pvm->trusted_touch_mode) == + TRUSTED_TOUCH_VM_MODE) { + stm_trusted_touch_pvm_vm_mode_disable(pdata); + } else { + input_err(true, pdata->dev, "Unsupported trusted touch mode\n"); + } + break; + + case 1: + if (atomic_read(&pvm->trusted_touch_enabled)) { + input_err(true, pdata->dev, "Trusted touch usecase underway\n"); + err = -EBUSY; + break; + } + if (atomic_read(&pvm->trusted_touch_mode) == + TRUSTED_TOUCH_VM_MODE) { + err = sec_trusted_touch_pvm_vm_mode_enable(pdata); + } else { + input_err(true, pdata->dev, "Unsupported trusted touch mode\n"); + } + break; + + default: + input_err(true, pdata->dev, "unsupported value: %d\n", value); + err = -EINVAL; + break; + } + return err; +} + +static int sec_trusted_vm_init(struct device *dev) +{ + int rc = 0; + struct trusted_touch_vm_info *vm_info; + struct sec_ts_plat_data *pdata = (struct sec_ts_plat_data *)dev_get_platdata(dev); + struct sec_trusted_touch *pvm; + void *mem_cookie; + + pvm = pdata->pvm; + + rc = sec_trusted_populate_vm_info(dev); + if (rc) { + input_err(true, pdata->dev, "Cannot setup vm pipeline\n"); + rc = -EINVAL; + goto fail; + } + + vm_info = pvm->vm_info; + mem_cookie = gh_mem_notifier_register(vm_info->mem_tag, + sec_trusted_vm_mem_on_release_handler, pdata); + if (!mem_cookie) { + input_err(true, pdata->dev, "Failed to register on release mem notifier\n"); + rc = -EINVAL; + goto init_fail; + } + + vm_info->mem_cookie = mem_cookie; + sec_trusted_touch_set_pvm_driver_state(vm_info, TRUSTED_TOUCH_PVM_INIT); + return rc; +init_fail: + sec_trusted_vm_deinit(vm_info); +fail: + return rc; +} + +static void sec_trusted_dt_parse_touch_info(struct device *dev, struct sec_trusted_touch *pvm) +{ + struct device_node *np = dev->of_node; + struct sec_ts_plat_data *pdata = dev->platform_data; + + int rc = 0; + const char *selection; + const char *environment; + + rc = of_property_read_string(np, "trusted-touch-mode", + &selection); + if (rc) { + input_err(true, pdata->dev, "%s: No trusted touch mode selection made\n", __func__); + atomic_set(&pvm->trusted_touch_mode, + TRUSTED_TOUCH_MODE_NONE); + return; + } + + if (!strcmp(selection, "vm_mode")) { + atomic_set(&pvm->trusted_touch_mode, + TRUSTED_TOUCH_VM_MODE); + input_err(true, pdata->dev, "Selected trusted touch mode to VM mode\n"); + } else { + atomic_set(&pvm->trusted_touch_mode, + TRUSTED_TOUCH_MODE_NONE); + input_err(true, pdata->dev, "Invalid trusted_touch mode\n"); + } + + rc = of_property_read_string(np, "touch-environment", + &environment); + if (rc) + input_err(true, pdata->dev, "%s: No trusted touch mode environment\n", __func__); + + pvm->touch_environment = environment; + input_info(true, pdata->dev, "Trusted touch environment:%s\n", + pvm->touch_environment); +} + + +static ssize_t trusted_touch_enable_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct input_dev *input_dev = to_input_dev(dev); + struct sec_ts_plat_data *pdata = input_dev->dev.parent->platform_data; + struct sec_trusted_touch *pvm = pdata->pvm; + + input_info(true, pdata->dev, "%s\n", __func__); + return scnprintf(buf, PAGE_SIZE, "%d", atomic_read(&pvm->trusted_touch_enabled)); +} + +static ssize_t trusted_touch_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct input_dev *input_dev = to_input_dev(dev); + struct sec_ts_plat_data *pdata = input_dev->dev.parent->platform_data; + struct sec_trusted_touch *pvm = pdata->pvm; + unsigned long value; + int ret; + + if (count > 2) + return -EINVAL; + + ret = kstrtoul(buf, 10, &value); + if (ret < 0) + return ret; + + if (!atomic_read(&pvm->trusted_touch_initialized)) + return -EIO; + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER)//TODO: check for call back from core driver. + if (value == 1) + sec_input_notify(NULL, NOTIFIER_SECURE_TOUCH_ENABLE, NULL); +#endif + input_info(true, pdata->dev, "%s: value: %ld\n", __func__, value); + + ret = sec_trusted_handle_trusted_touch_pvm(pdata, value); + + if (ret) { + input_err(true, pdata->dev, "Failed to handle touch vm\n"); +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER)//TODO: check for call back from core driver. + sec_input_notify(NULL, NOTIFIER_SECURE_TOUCH_DISABLE, NULL); +#endif + return -EINVAL; + } + + if (value == 1) + reinit_completion(&pdata->secure_powerdown); + else + complete_all(&pdata->secure_powerdown); + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER)//TODO: check for call back from core driver. + if (value == 0) + sec_input_notify(NULL, NOTIFIER_SECURE_TOUCH_DISABLE, NULL); +#endif + + return count; +} + +static ssize_t trusted_touch_event_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct input_dev *input_dev = to_input_dev(dev); + struct sec_ts_plat_data *pdata = input_dev->dev.parent->platform_data; + + input_err(true, pdata->dev, "%s\n", __func__); + return scnprintf(buf, PAGE_SIZE, "%d", atomic_read(&pdata->pvm->trusted_touch_event)); +} + +static ssize_t trusted_touch_event_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct input_dev *input_dev = to_input_dev(dev); + struct sec_ts_plat_data *pdata = input_dev->dev.parent->platform_data; + unsigned long value; + int ret; + + if (count > 2) + return -EINVAL; + + ret = kstrtoul(buf, 10, &value); + if (ret < 0) + return ret; + + if (!atomic_read(&pdata->pvm->trusted_touch_initialized)) + return -EIO; + + input_info(true, pdata->dev, "%s: value: %ld\n", __func__, value); + + atomic_set(&pdata->pvm->trusted_touch_event, value); + + return count; +} + +static ssize_t trusted_touch_type_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct input_dev *input_dev = to_input_dev(dev); + struct sec_ts_plat_data *pdata = input_dev->dev.parent->platform_data; + input_info(true, pdata->dev, "%s\n", __func__); + return scnprintf(buf, PAGE_SIZE, "%s", pdata->pvm->vm_info->trusted_touch_type); +} + +static DEVICE_ATTR_RW(trusted_touch_enable); +static DEVICE_ATTR_RW(trusted_touch_event); +static DEVICE_ATTR_RO(trusted_touch_type); + +static struct attribute *trusted_attr[] = { + &dev_attr_trusted_touch_enable.attr, + &dev_attr_trusted_touch_event.attr, + &dev_attr_trusted_touch_type.attr, + NULL, +}; + +static struct attribute_group trusted_attr_group = {//TODO: Move it back to core driver + .attrs = trusted_attr, +}; + +int sec_trusted_touch_init(struct device *dev) +{ + + int rc = 0; + int ret = 0; + struct sec_trusted_touch *pvm; + struct sec_ts_plat_data *pdata = dev->platform_data; + + pvm = devm_kzalloc(pdata->dev, sizeof(struct sec_trusted_touch), GFP_KERNEL); + if (!pvm) { + ret = -ENOMEM; + input_err(true, pdata->dev, "%s: fail to allocate memory for sec_trusted_touch\n", __func__); + goto error_allocate_pvm; + } + pdata->pvm = pvm; + + atomic_set(&pvm->trusted_touch_initialized, 0); + sec_trusted_dt_parse_touch_info(pdata->dev, pvm); + + if (atomic_read(&pvm->trusted_touch_mode) == TRUSTED_TOUCH_MODE_NONE) + return -1; + + init_completion(&pvm->trusted_touch_powerdown); + + /* Get clocks */ + + pvm->core_clk = devm_clk_get(pdata->bus_master->parent, "m-ahb"); + if (IS_ERR(pvm->core_clk)) { + pvm->core_clk = NULL; + input_err(true, pdata->dev, "%s: core_clk is not defined\n", __func__); + } + + pvm->iface_clk = devm_clk_get(pdata->bus_master->parent, "se-clk"); + if (IS_ERR(pvm->iface_clk)) { + pvm->iface_clk = NULL; + input_err(true, pdata->dev, "%s: iface_clk is not defined\n", __func__); + } + + if (atomic_read(&pvm->trusted_touch_mode) == + TRUSTED_TOUCH_VM_MODE) { + rc = sec_trusted_vm_init(pdata->dev); + if (rc) + input_err(true, pdata->dev, "Failed to init VM\n"); + } + atomic_set(&pvm->trusted_touch_initialized, 1); + + if (sysfs_create_group(&pdata->input_dev->dev.kobj, &trusted_attr_group) < 0) + input_err(true, pdata->dev, "%s: do not make secure group\n", __func__); + + mutex_init(&pvm->clk_io_ctrl_mutex); + mutex_init(&pvm->transition_lock); + +error_allocate_pvm: + return ret; +} +EXPORT_SYMBOL(sec_trusted_touch_init); + +MODULE_DESCRIPTION("Samsung input trusted PVM touch"); +MODULE_LICENSE("GPL"); \ No newline at end of file diff --git a/drivers/input/sec_input/sec_trusted_touch.h b/drivers/input/sec_input/sec_trusted_touch.h new file mode 100644 index 000000000000..9cb59aadfc51 --- /dev/null +++ b/drivers/input/sec_input/sec_trusted_touch.h @@ -0,0 +1,124 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* drivers/input/sec_input/sec_trusted_touch.h + * + * Core file for Samsung input device driver for multi device + * + * 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. + */ + +#ifndef _SEC_TRUSTED_TOUCH_H_ +#define _SEC_TRUSTED_TOUCH_H_ + +#include "sec_input.h" +#if IS_ENABLED(CONFIG_INPUT_SEC_TRUSTED_TOUCH) +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#define TRUSTED_TOUCH_MEM_LABEL 0x7 +enum trusted_touch_mode_config { + TRUSTED_TOUCH_VM_MODE, + TRUSTED_TOUCH_MODE_NONE +}; + +enum trusted_touch_pvm_states { + TRUSTED_TOUCH_PVM_INIT, + PVM_I2C_RESOURCE_ACQUIRED, + PVM_INTERRUPT_DISABLED, + PVM_IOMEM_LENT, + PVM_IOMEM_LENT_NOTIFIED, + PVM_IRQ_LENT, + PVM_IRQ_LENT_NOTIFIED, + PVM_IOMEM_RELEASE_NOTIFIED, + PVM_IRQ_RELEASE_NOTIFIED, + PVM_ALL_RESOURCES_RELEASE_NOTIFIED, + PVM_IRQ_RECLAIMED, + PVM_IOMEM_RECLAIMED, + PVM_INTERRUPT_ENABLED, + PVM_I2C_RESOURCE_RELEASED, + TRUSTED_TOUCH_PVM_STATE_MAX +}; + +enum trusted_touch_tvm_states { + TRUSTED_TOUCH_TVM_INIT, + TVM_IOMEM_LENT_NOTIFIED, + TVM_IRQ_LENT_NOTIFIED, + TVM_ALL_RESOURCES_LENT_NOTIFIED, + TVM_IOMEM_ACCEPTED, + TVM_I2C_SESSION_ACQUIRED, + TVM_IRQ_ACCEPTED, + TVM_INTERRUPT_ENABLED, + TVM_INTERRUPT_DISABLED, + TVM_IRQ_RELEASED, + TVM_I2C_SESSION_RELEASED, + TVM_IOMEM_RELEASED, + TRUSTED_TOUCH_TVM_STATE_MAX +}; + +#define TOUCH_INTR_GPIO_BASE 0xF12E000 +#define TOUCH_INTR_GPIO_SIZE 0x1000 +#define TOUCH_INTR_GPIO_OFFSET 0x8 + +#define TRUSTED_TOUCH_EVENT_LEND_FAILURE -1 +#define TRUSTED_TOUCH_EVENT_LEND_NOTIFICATION_FAILURE -2 +#define TRUSTED_TOUCH_EVENT_ACCEPT_FAILURE -3 +#define TRUSTED_TOUCH_EVENT_FUNCTIONAL_FAILURE -4 +#define TRUSTED_TOUCH_EVENT_RELEASE_FAILURE -5 +#define TRUSTED_TOUCH_EVENT_RECLAIM_FAILURE -6 +#define TRUSTED_TOUCH_EVENT_I2C_FAILURE -7 +#define TRUSTED_TOUCH_EVENT_NOTIFICATIONS_PENDING 5 + +struct trusted_touch_vm_info { + enum gh_irq_label irq_label; + enum gh_mem_notifier_tag mem_tag; + enum gh_vm_names vm_name; + const char *trusted_touch_type; + u32 hw_irq; + gh_memparcel_handle_t vm_mem_handle; + u32 *iomem_bases; + u32 *iomem_sizes; + u32 iomem_list_size; + void *mem_cookie; + atomic_t vm_state; +}; + +struct sec_trusted_touch { + struct device *dev; + struct trusted_touch_vm_info *vm_info; + struct mutex clk_io_ctrl_mutex; + struct mutex transition_lock; + const char *touch_environment; + struct completion trusted_touch_powerdown; + struct clk *core_clk; + struct clk *iface_clk; + atomic_t trusted_touch_initialized; + atomic_t trusted_touch_enabled; + atomic_t trusted_touch_transition; + atomic_t trusted_touch_event; + atomic_t trusted_touch_abort_status; + atomic_t delayed_vm_probe_pending; + atomic_t trusted_touch_mode; +}; +int sec_trusted_touch_init(struct device *dev); +#else +static inline int sec_trusted_touch_init(struct device *dev) +{ + return 0; +} +#endif + +#endif \ No newline at end of file diff --git a/drivers/input/sec_input/sec_tsp_dumpkey.c b/drivers/input/sec_input/sec_tsp_dumpkey.c new file mode 100644 index 000000000000..08bb2baa9091 --- /dev/null +++ b/drivers/input/sec_input/sec_tsp_dumpkey.c @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2016 Samsung Electronics Co., Ltd. + * + * 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. + */ + +#include +#include +#include +#include +#include + +#include "sec_input.h" +#include "sec_tsp_dumpkey.h" + +static DEFINE_SPINLOCK(sec_tsp_dumpkey_event_lock); +static atomic_t sec_tsp_dumpkey_acceptable_event[KEY_MAX] __read_mostly; + +/* Input sequence 9530 */ +#define DUMP_COUNT_FIRST 3 +#define DUMP_COUNT_SECOND 3 +#define DUMP_COUNT_THIRD 3 + +#define KEY_STATE_DOWN 1 +#define KEY_STATE_UP 0 + +struct tsp_dump_callbacks *dump_callbacks; + +static unsigned int __tsp_dump_keys[] = { + KEY_POWER, + KEY_VOLUMEUP, + KEY_VOLUMEDOWN, + KEY_HOMEPAGE, + KEY_HOT, + KEY_EMERGENCY, + KEY_BACK, + KEY_RECENT, + KEY_APPSELECT +}; + +struct dump_key { + unsigned int key_code; + unsigned int dump_count; +}; + +struct dump_key tsp_dump_key_combination[] = { + {KEY_VOLUMEDOWN, DUMP_COUNT_FIRST}, + {KEY_POWER, DUMP_COUNT_SECOND}, + {KEY_VOLUMEDOWN, DUMP_COUNT_THIRD}, +}; + +struct tsp_dump_key_state { + unsigned int key_code; + unsigned int state; +}; + +struct tsp_dump_key_state tsp_dump_key_states[] = { + {KEY_VOLUMEDOWN, KEY_STATE_UP}, + {KEY_VOLUMEUP, KEY_STATE_UP}, + {KEY_POWER, KEY_STATE_UP}, + {KEY_HOMEPAGE, KEY_STATE_UP}, + {KEY_HOT, KEY_STATE_UP}, + {KEY_EMERGENCY, KEY_STATE_UP}, + {KEY_BACK, KEY_STATE_UP}, + {KEY_RECENT, KEY_STATE_UP}, + {KEY_APPSELECT, KEY_STATE_UP}, +}; + +static unsigned int hold_key = KEY_VOLUMEUP; +static unsigned int hold_key_hold = KEY_STATE_UP; +static unsigned int check_count; +static unsigned int check_step; + +static int is_hold_key(unsigned int code) +{ + return (code == hold_key); +} + +static void set_hold_key_hold(int state) +{ + hold_key_hold = state; +} + +static unsigned int is_hold_key_hold(void) +{ + return hold_key_hold; +} + +static unsigned int get_current_step_key_code(void) +{ + return tsp_dump_key_combination[check_step].key_code; +} + +static int is_key_matched_for_current_step(unsigned int code) +{ + return (code == get_current_step_key_code()); +} + +static int is_dump_keys(unsigned int code) +{ + unsigned long i; + + for (i = 0; i < ARRAY_SIZE(tsp_dump_key_states); i++) + if (tsp_dump_key_states[i].key_code == code) + return 1; + return 0; +} + +static int get_count_for_next_step(void) +{ + int i; + int count = 0; + + for (i = 0; i < check_step + 1; i++) + count += tsp_dump_key_combination[i].dump_count; + return count; +} + +static int is_reaching_count_for_next_step(void) +{ + return (check_count == get_count_for_next_step()); +} + +static int get_count_for_dump(void) +{ + unsigned long i; + int count = 0; + + for (i = 0; i < ARRAY_SIZE(tsp_dump_key_combination); i++) + count += tsp_dump_key_combination[i].dump_count; + return count - 1; +} + +static unsigned int is_key_state_down(unsigned int code) +{ + unsigned long i; + + if (is_hold_key(code)) + return is_hold_key_hold(); + + for (i = 0; i < ARRAY_SIZE(tsp_dump_key_states); i++) + if (tsp_dump_key_states[i].key_code == code) + return tsp_dump_key_states[i].state == KEY_STATE_DOWN; + return 0; +} + +static void set_key_state_down(unsigned int code) +{ + unsigned long i; + + if (is_hold_key(code)) + set_hold_key_hold(KEY_STATE_DOWN); + + for (i = 0; i < ARRAY_SIZE(tsp_dump_key_states); i++) + if (tsp_dump_key_states[i].key_code == code) + tsp_dump_key_states[i].state = KEY_STATE_DOWN; +} + +static void set_key_state_up(unsigned int code) +{ + unsigned long i; + + if (is_hold_key(code)) + set_hold_key_hold(KEY_STATE_UP); + + for (i = 0; i < ARRAY_SIZE(tsp_dump_key_states); i++) + if (tsp_dump_key_states[i].key_code == code) + tsp_dump_key_states[i].state = KEY_STATE_UP; +} + +static void increase_step(void) +{ + int i; + if (check_step < ARRAY_SIZE(tsp_dump_key_combination)) + check_step++; + else { + for (i = 0; i < NUM_DEVICES; i++) { + if (dump_callbacks[i].inform_dump) + dump_callbacks[i].inform_dump(dump_callbacks[i].ptsp); + } + } +} + +static void reset_step(void) +{ + check_step = 0; +} + +static void increase_count(void) +{ + int i; + if (check_count < get_count_for_dump()) + check_count++; + else { + for (i = 0; i < NUM_DEVICES; i++) { + if (dump_callbacks[i].inform_dump) + dump_callbacks[i].inform_dump(dump_callbacks[i].ptsp); + } + } +} + +static void reset_count(void) +{ + check_count = 0; +} + +static void check_tsp_dump_keys(struct sec_tsp_dumpkey_param *param) +{ + unsigned int code = param->keycode; + int state = param->down; + + if (!is_dump_keys(code)) + return; + + if (state == KEY_STATE_DOWN) { + /* Duplicated input */ + if (is_key_state_down(code)) + return; + set_key_state_down(code); + + if (is_hold_key(code)) { + set_hold_key_hold(KEY_STATE_DOWN); + return; + } + if (is_hold_key_hold()) { + if (is_key_matched_for_current_step(code)) { + increase_count(); + } else { + reset_count(); + reset_step(); + } + if (is_reaching_count_for_next_step()) + increase_step(); + } + + } else { + set_key_state_up(code); + if (is_hold_key(code)) { + set_hold_key_hold(KEY_STATE_UP); + reset_step(); + reset_count(); + } + } + +} + +static inline void update_acceptable_event(unsigned int event_code, bool is_add) +{ + if (is_add) + atomic_inc(&(sec_tsp_dumpkey_acceptable_event[event_code])); + else + atomic_dec(&(sec_tsp_dumpkey_acceptable_event[event_code])); +} + +static inline bool is_event_supported(unsigned int event_type, + unsigned int event_code) +{ + bool ret; + + if (event_type != EV_KEY || event_code >= KEY_MAX) + return false; + + ret = !!atomic_read(&(sec_tsp_dumpkey_acceptable_event[event_code])); + + return ret; +} + +static void sec_tsp_dumpkey_event(struct input_handle *handle, unsigned int event_type, + unsigned int event_code, int value) +{ + struct sec_tsp_dumpkey_param param = { + .keycode = event_code, + .down = value, + }; + + if (!is_event_supported(event_type, event_code)) + return; + + pr_info("%s key_event: %u, %d\n", SECLOG, event_code, value); + + spin_lock(&sec_tsp_dumpkey_event_lock); + + check_tsp_dump_keys(¶m); + + spin_unlock(&sec_tsp_dumpkey_event_lock); +} + +static int sec_tsp_dumpkey_connect(struct input_handler *handler, struct input_dev *dev, + const struct input_device_id *id) +{ + struct input_handle *handle; + int error; + + handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = "sec_tsp_dumpkey"; + + error = input_register_handle(handle); + if (error) + goto err_free_handle; + + error = input_open_device(handle); + if (error) + goto err_unregister_handle; + + return 0; + +err_unregister_handle: + input_unregister_handle(handle); +err_free_handle: + kfree(handle); + return error; +} + +static void sec_tsp_dumpkey_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +void sec_input_dumpkey_register(int dev_id, void (*callback)(struct device *dev), struct device *dev) +{ + if (dev_id < NUM_DEVICES && dev_id >= MULTI_DEV_NONE) { + dump_callbacks[dev_id].inform_dump = callback; + dump_callbacks[dev_id].ptsp = dev; + } +} +EXPORT_SYMBOL(sec_input_dumpkey_register); + +void sec_input_dumpkey_unregister(int dev_id) +{ + if (dev_id < NUM_DEVICES && dev_id >= MULTI_DEV_NONE) { + dump_callbacks[dev_id].inform_dump = NULL; + dump_callbacks[dev_id].ptsp = NULL; + } +} +EXPORT_SYMBOL(sec_input_dumpkey_unregister); + +static const struct input_device_id sec_tsp_dumpkey_ids[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT, + .evbit = { BIT_MASK(EV_KEY) }, + }, + {}, +}; + +static struct input_handler sec_tsp_dumpkey_handler = { + .event = sec_tsp_dumpkey_event, + .connect = sec_tsp_dumpkey_connect, + .disconnect = sec_tsp_dumpkey_disconnect, + .name = "sec_tsp_dumpkey", + .id_table = sec_tsp_dumpkey_ids, +}; + +int sec_tsp_dumpkey_init(void) +{ + int err; + size_t i; + + pr_info("%s %s\n", SECLOG, __func__); + + for (i = 0; i < KEY_MAX; i++) + atomic_set(&(sec_tsp_dumpkey_acceptable_event[i]), 0); + + for (i = 0; i < ARRAY_SIZE(__tsp_dump_keys); i++) + update_acceptable_event(__tsp_dump_keys[i], true); + + spin_lock_init(&sec_tsp_dumpkey_event_lock); + + dump_callbacks = kzalloc(sizeof(struct tsp_dump_callbacks) * NUM_DEVICES, GFP_KERNEL); + if (!dump_callbacks) + return -ENOMEM; + + err = input_register_handler(&sec_tsp_dumpkey_handler); + + return err; + +} +EXPORT_SYMBOL(sec_tsp_dumpkey_init); + +void sec_tsp_dumpkey_exit(void) +{ + input_unregister_handler(&sec_tsp_dumpkey_handler); + kfree(dump_callbacks); +} +EXPORT_SYMBOL(sec_tsp_dumpkey_exit); + +MODULE_DESCRIPTION("Samsung tsp dumpkey driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/sec_input/sec_tsp_dumpkey.h b/drivers/input/sec_input/sec_tsp_dumpkey.h new file mode 100644 index 000000000000..1d2dca9e6f24 --- /dev/null +++ b/drivers/input/sec_input/sec_tsp_dumpkey.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0 + * Copyright (C) 2022 Samsung Electronics Co., Ltd. + * + * 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. + */ + +#ifndef __SEC_TSP_DUMPKEY_H__ +#define __SEC_TSP_DUMPKEY_H__ + +#define NUM_DEVICES 3 + +struct sec_tsp_dumpkey_param { + unsigned int keycode; + int down; +}; + +struct tsp_dump_callbacks { + void (*inform_dump)(struct device *dev); + struct device *ptsp; +}; + +void sec_input_dumpkey_register(int dev_id, void (*callback)(struct device *dev), struct device *dev); +void sec_input_dumpkey_unregister(int dev_id); + +int sec_tsp_dumpkey_init(void); +void sec_tsp_dumpkey_exit(void); +#endif /* __SEC_TSP_DUMPKEY_H__ */ diff --git a/drivers/input/sec_input/sec_tsp_log.c b/drivers/input/sec_input/sec_tsp_log.c new file mode 100644 index 000000000000..e6b5b97ce6bd --- /dev/null +++ b/drivers/input/sec_input/sec_tsp_log.c @@ -0,0 +1,572 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * sec_tsp_log.c + * + * driver supporting debug functions for Samsung touch device + * + * COPYRIGHT(C) Samsung Electronics Co., Ltd. 2006-2011 All Right Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "sec_input.h" +#include "sec_tsp_log.h" + +static int sec_tsp_log_index; +static int sec_tsp_log_index_fix; +static int sec_tsp_log_index_full; +static char *sec_tsp_log_buf; +static unsigned int sec_tsp_log_size; + +static int sec_tsp_fail_hist_index; +static int sec_tsp_fail_hist_index_fix; +static int sec_tsp_fail_hist_index_full; +static char *sec_tsp_fail_hist_buf; +static unsigned int sec_tsp_fail_hist_size; + +static int sec_tsp_raw_data_index_main; +static int sec_tsp_raw_data_index_sub; +static int sec_tsp_raw_data_index; +static int sec_tsp_raw_data_index_full; +static char *sec_tsp_raw_data_buf; +static unsigned int sec_tsp_raw_data_size; + +static int sec_tsp_command_history_index; +static int sec_tsp_command_history_index_full; +static char *sec_tsp_command_history_buf; +static unsigned int sec_tsp_command_history_size; + +/* Sponge Infinite dump */ +static int sec_tsp_sponge_log_index; +static int sec_tsp_sponge_log_index_full; +static char *sec_tsp_sponge_log_buf; +static unsigned int sec_tsp_sponge_log_size; + +static struct mutex tsp_log_mutex; + +static size_t sec_tsp_log_timestamp(unsigned long idx, char *tbuf) +{ + /* Add the current time stamp */ + unsigned long long t; + unsigned long nanosec_rem; + + t = local_clock(); + nanosec_rem = do_div(t, 1000000000); + + snprintf(tbuf, SEC_TSP_LOG_TIMESTAMP_SIZE, "[%5lu.%06lu] ", (unsigned long)t, nanosec_rem / 1000); + + return strlen(tbuf); +} + +void sec_tsp_sponge_log(char *buf) +{ + int len = 0; + char tbuf[SEC_TSP_LOG_TIMESTAMP_SIZE]; + unsigned int idx; + size_t size, total_size, time_size; + + /* In case of sec_tsp_log_setup is failed */ + if (!sec_tsp_sponge_log_size || !sec_tsp_sponge_log_buf) + return; + + mutex_lock(&tsp_log_mutex); + idx = sec_tsp_sponge_log_index; + size = strlen(buf); + time_size = sec_tsp_log_timestamp(idx, tbuf); + total_size = size + time_size + SEC_TSP_LOG_EXTRA_SIZE; + + /* Overflow buffer size */ + if (idx + total_size >= sec_tsp_sponge_log_size) { + len = scnprintf(&sec_tsp_sponge_log_buf[0], total_size, "%s%s", tbuf, buf); + sec_tsp_sponge_log_index_full = sec_tsp_sponge_log_index; + sec_tsp_sponge_log_index = len; + } else { + len = scnprintf(&sec_tsp_sponge_log_buf[idx], total_size, "%s%s", tbuf, buf); + sec_tsp_sponge_log_index += len; + sec_tsp_sponge_log_index_full = max(sec_tsp_sponge_log_index, sec_tsp_sponge_log_index_full); + } + mutex_unlock(&tsp_log_mutex); +} +EXPORT_SYMBOL(sec_tsp_sponge_log); + +static ssize_t sec_tsp_sponge_log_read(struct file *file, char __user *buf, + size_t len, loff_t *offset) +{ + loff_t pos = *offset; + ssize_t count; + + if (!sec_tsp_sponge_log_buf) + return 0; + + if (pos >= sec_tsp_sponge_log_index_full) + return 0; + + count = min_t(size_t, len, sec_tsp_sponge_log_index_full - pos); + if (copy_to_user(buf, sec_tsp_sponge_log_buf + pos, count)) + return -EFAULT; + + *offset += count; + return count; +} + +#define TSP_BUF_SIZE 512 + +void sec_debug_tsp_log_msg(char *msg, char *fmt, ...) +{ + va_list args; + char buf[TSP_BUF_SIZE]; + char tbuf[SEC_TSP_LOG_TIMESTAMP_SIZE]; + int len = 0; + unsigned int idx; + size_t size, size_dev_name, time_size, total_size; + + /* In case of sec_tsp_log_setup is failed */ + if (!sec_tsp_log_size) + return; + + mutex_lock(&tsp_log_mutex); + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + + idx = sec_tsp_log_index; + size = strlen(buf); + size_dev_name = strlen(msg); + time_size = sec_tsp_log_timestamp(idx, tbuf); + total_size = size + size_dev_name + time_size + SEC_TSP_LOG_EXTRA_SIZE; + + /* Overflow buffer size */ + if (idx + total_size >= sec_tsp_log_size) { + if (sec_tsp_log_index_fix + total_size >= sec_tsp_log_size) { + mutex_unlock(&tsp_log_mutex); + return; + } + len = scnprintf(&sec_tsp_log_buf[sec_tsp_log_index_fix], total_size, "%s%s : %s", tbuf, msg, buf); + sec_tsp_log_index_full = sec_tsp_log_index; + sec_tsp_log_index = sec_tsp_log_index_fix + len; + } else { + len = scnprintf(&sec_tsp_log_buf[idx], total_size, "%s%s : %s", tbuf, msg, buf); + sec_tsp_log_index += len; + sec_tsp_log_index_full = max(sec_tsp_log_index_full, sec_tsp_log_index); + } + mutex_unlock(&tsp_log_mutex); +} +EXPORT_SYMBOL(sec_debug_tsp_log_msg); + +void sec_debug_tsp_fail_hist(char *msg, char *fmt, ...) +{ + va_list args; + char buf[TSP_BUF_SIZE]; + char tbuf[SEC_TSP_LOG_TIMESTAMP_SIZE]; + int len = 0; + unsigned int idx; + size_t size, size_dev_name, time_size, total_size; + + /* In case of sec_tsp_log_setup is failed */ + if (!sec_tsp_fail_hist_size) + return; + + mutex_lock(&tsp_log_mutex); + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + + idx = sec_tsp_fail_hist_index; + size = strlen(buf); + size_dev_name = strlen(msg); + time_size = sec_tsp_log_timestamp(idx, tbuf); + total_size = size + size_dev_name + time_size + SEC_TSP_LOG_EXTRA_SIZE; + + /* Overflow buffer size */ + if (idx + total_size >= sec_tsp_fail_hist_size) { + if (sec_tsp_fail_hist_index_fix + total_size >= sec_tsp_fail_hist_size) { + mutex_unlock(&tsp_log_mutex); + return; + } + len = scnprintf(&sec_tsp_fail_hist_buf[sec_tsp_fail_hist_index_fix], total_size, "%s%s : %s", tbuf, msg, buf); + sec_tsp_fail_hist_index_full = sec_tsp_fail_hist_index; + sec_tsp_fail_hist_index = sec_tsp_fail_hist_index_fix + len; + } else { + len = scnprintf(&sec_tsp_fail_hist_buf[idx], total_size, "%s%s : %s", tbuf, msg, buf); + sec_tsp_fail_hist_index += len; + sec_tsp_fail_hist_index_full = max(sec_tsp_fail_hist_index_full, sec_tsp_fail_hist_index); + } + mutex_unlock(&tsp_log_mutex); +} +EXPORT_SYMBOL(sec_debug_tsp_fail_hist); + +void sec_debug_tsp_raw_data_msg(char mode, char *msg, char *fmt, ...) +{ + va_list args; + char buf[TSP_BUF_SIZE]; + char tbuf[SEC_TSP_LOG_TIMESTAMP_SIZE]; + int len = 0; + unsigned int idx; + size_t size, size_dev_name, time_size, total_size; + + /* In case of sec_tsp_log_setup is failed */ + if (!sec_tsp_raw_data_size || !sec_tsp_raw_data_buf) + return; + + mutex_lock(&tsp_log_mutex); + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + + if (mode == MULTI_DEV_MAIN) { + idx = sec_tsp_raw_data_index_main; + } else if (mode == MULTI_DEV_SUB) { + idx = sec_tsp_raw_data_index_sub; + } else { + idx = sec_tsp_raw_data_index; + } + + size = strlen(buf); + size_dev_name = strlen(msg); + time_size = sec_tsp_log_timestamp(idx, tbuf); + total_size = size + size_dev_name + time_size + SEC_TSP_LOG_EXTRA_SIZE; + + if (mode == MULTI_DEV_MAIN) { + /* Overflow buffer size */ + if (idx + total_size >= (sec_tsp_raw_data_size / 2)) { + len = scnprintf(&sec_tsp_raw_data_buf[0], total_size, "%s%s : %s", tbuf, msg, buf); + sec_tsp_raw_data_index_main = len; + } else { + len = scnprintf(&sec_tsp_raw_data_buf[idx], total_size, "%s%s : %s", tbuf, msg, buf); + sec_tsp_raw_data_index_main += len; + } + } else if (mode == MULTI_DEV_SUB) { + /* Overflow buffer size */ + if (idx + total_size >= sec_tsp_raw_data_size) { + len = scnprintf(&sec_tsp_raw_data_buf[sec_tsp_raw_data_size / 2], + total_size, "%s%s : %s", tbuf, msg, buf); + sec_tsp_raw_data_index_sub = sec_tsp_raw_data_size / 2 + len; + } else { + len = scnprintf(&sec_tsp_raw_data_buf[idx], total_size, "%s%s : %s", tbuf, msg, buf); + sec_tsp_raw_data_index_sub += len; + } + } else { + /* Overflow buffer size */ + if (idx + total_size >= sec_tsp_raw_data_size) { + len = scnprintf(&sec_tsp_raw_data_buf[0], total_size, "%s%s : %s", tbuf, msg, buf); + sec_tsp_raw_data_index_full = sec_tsp_raw_data_index; + sec_tsp_raw_data_index = len; + } else { + len = scnprintf(&sec_tsp_raw_data_buf[idx], total_size, "%s%s : %s", tbuf, msg, buf); + sec_tsp_raw_data_index += len; + sec_tsp_raw_data_index_full = max(sec_tsp_raw_data_index, sec_tsp_raw_data_index_full); + } + } + mutex_unlock(&tsp_log_mutex); +} +EXPORT_SYMBOL(sec_debug_tsp_raw_data_msg); + +void sec_debug_tsp_command_history(char *buf) +{ + int len = 0; + unsigned int idx; + size_t size, total_size; + + /* In case of sec_tsp_log_setup is failed */ + if (!sec_tsp_command_history_size || !sec_tsp_command_history_buf) + return; + + mutex_lock(&tsp_log_mutex); + idx = sec_tsp_command_history_index; + size = strlen(buf); + total_size = size + SEC_TSP_LOG_EXTRA_SIZE; + + /* Overflow buffer size */ + if (idx + total_size >= sec_tsp_command_history_size) { + len = scnprintf(&sec_tsp_command_history_buf[0], total_size, "%s", buf); + sec_tsp_command_history_index_full = sec_tsp_command_history_index; + sec_tsp_command_history_index = len; + } else { + len = scnprintf(&sec_tsp_command_history_buf[idx], total_size, "%s", buf); + sec_tsp_command_history_index += len; + sec_tsp_command_history_index_full = max(sec_tsp_command_history_index, + sec_tsp_command_history_index_full); + } + mutex_unlock(&tsp_log_mutex); +} +EXPORT_SYMBOL(sec_debug_tsp_command_history); + +void sec_tsp_raw_data_clear(char mode) +{ + if (!sec_tsp_raw_data_size || !sec_tsp_raw_data_buf) + return; + + mutex_lock(&tsp_log_mutex); + if (mode == MULTI_DEV_MAIN) { + sec_tsp_raw_data_index_main = 0; + memset(sec_tsp_raw_data_buf, 0x00, sec_tsp_raw_data_size / 2); + } else if (mode == MULTI_DEV_SUB) { + sec_tsp_raw_data_index_sub = sec_tsp_raw_data_size / 2; + memset(sec_tsp_raw_data_buf + sec_tsp_raw_data_index_sub, 0x00, sec_tsp_raw_data_size / 2); + } else { + sec_tsp_raw_data_index = 0; + sec_tsp_raw_data_index_full = 0; + memset(sec_tsp_raw_data_buf, 0x00, sec_tsp_raw_data_size); + } + mutex_unlock(&tsp_log_mutex); +} +EXPORT_SYMBOL(sec_tsp_raw_data_clear); + +void sec_tsp_log_fix(void) +{ + char *buf = "FIX LOG!\n"; + char tbuf[SEC_TSP_LOG_TIMESTAMP_SIZE]; + int len = 0; + unsigned int idx; + size_t size, time_size, total_size; + + /* In case of sec_tsp_log_setup is failed */ + if (!sec_tsp_log_size) + return; + + mutex_lock(&tsp_log_mutex); + idx = sec_tsp_log_index; + size = strlen(buf); + time_size = sec_tsp_log_timestamp(idx, tbuf); + total_size = size + time_size + SEC_TSP_LOG_EXTRA_SIZE; + + /* Overflow buffer size */ + if (idx + total_size >= sec_tsp_log_size) { + if (sec_tsp_log_index_fix + total_size >= sec_tsp_log_size) { + mutex_unlock(&tsp_log_mutex); + return; + } + len = scnprintf(&sec_tsp_log_buf[sec_tsp_log_index_fix], total_size, "%s%s", tbuf, buf); + sec_tsp_log_index = sec_tsp_log_index_fix + len; + } else { + len = scnprintf(&sec_tsp_log_buf[idx], total_size, "%s%s", tbuf, buf); + sec_tsp_log_index += len; + } + sec_tsp_log_index_fix = sec_tsp_log_index; + sec_tsp_log_index_full = max(sec_tsp_log_index_full, sec_tsp_log_index); + mutex_unlock(&tsp_log_mutex); +} +EXPORT_SYMBOL(sec_tsp_log_fix); + +static ssize_t sec_tsp_log_read(struct file *file, char __user *buf, + size_t len, loff_t *offset) +{ + loff_t pos = *offset; + ssize_t count; + + if (!sec_tsp_log_buf) + return 0; + + if (pos >= sec_tsp_log_index_full) + return 0; + + count = min_t(size_t, len, sec_tsp_log_index_full - pos); + if (copy_to_user(buf, sec_tsp_log_buf + pos, count)) + return -EFAULT; + + *offset += count; + return count; +} + +static ssize_t sec_tsp_fail_hist_read(struct file *file, char __user *buf, + size_t len, loff_t *offset) +{ + loff_t pos = *offset; + ssize_t count; + + if (!sec_tsp_fail_hist_buf) + return 0; + + if (pos >= sec_tsp_fail_hist_index_full) + return 0; + + count = min_t(size_t, len, sec_tsp_fail_hist_index_full - pos); + if (copy_to_user(buf, sec_tsp_fail_hist_buf + pos, count)) + return -EFAULT; + + *offset += count; + return count; +} + +#if IS_ENABLED(CONFIG_SEC_INPUT_MULTI_DEVICE) +static ssize_t sec_tsp_raw_data_read(struct file *file, char __user *buf, + size_t len, loff_t *offset) +{ + loff_t pos = *offset; + ssize_t count; + int pos_range; + + if (!sec_tsp_raw_data_buf) + return 0; + + pos_range = sec_tsp_raw_data_index_main + (sec_tsp_raw_data_index_sub - (sec_tsp_raw_data_size / 2)); + if (pos_range < 0) + return 0; + if (pos >= pos_range) + return 0; + + if (pos < sec_tsp_raw_data_index_main) { + count = min_t(size_t, len, sec_tsp_raw_data_index_main - pos); + if (copy_to_user(buf, sec_tsp_raw_data_buf + pos, count)) + return -EFAULT; + } else { + count = min_t(size_t, len, sec_tsp_raw_data_index_sub - (sec_tsp_raw_data_size / 2) + - (pos - sec_tsp_raw_data_index_main)); + if (copy_to_user(buf, &sec_tsp_raw_data_buf[sec_tsp_raw_data_size / 2] + (pos - sec_tsp_raw_data_index_main), count)) + return -EFAULT; + } + + *offset += count; + return count; +} +#else +static ssize_t sec_tsp_raw_data_read(struct file *file, char __user *buf, + size_t len, loff_t *offset) +{ + loff_t pos = *offset; + ssize_t count; + + if (!sec_tsp_raw_data_buf) + return 0; + + if (pos >= sec_tsp_raw_data_index_full) + return 0; + + count = min_t(size_t, len, sec_tsp_raw_data_index_full - pos); + if (copy_to_user(buf, sec_tsp_raw_data_buf + pos, count)) + return -EFAULT; + + *offset += count; + return count; +} +#endif + +static ssize_t sec_tsp_command_history_read(struct file *file, char __user *buf, + size_t len, loff_t *offset) +{ + loff_t pos = *offset; + ssize_t count; + + if (!sec_tsp_command_history_buf) + return 0; + + if (pos >= sec_tsp_command_history_index_full) + return 0; + + count = min_t(size_t, len, sec_tsp_command_history_index_full - pos); + if (copy_to_user(buf, sec_tsp_command_history_buf + pos, count)) + return -EFAULT; + + *offset += count; + return count; +} + +static sec_input_proc_ops(THIS_MODULE, tsp_msg_file_ops, sec_tsp_log_read, NULL); +static sec_input_proc_ops(THIS_MODULE, tsp_fail_hist_file_ops, sec_tsp_fail_hist_read, NULL); +static sec_input_proc_ops(THIS_MODULE, tsp_raw_data_file_ops, sec_tsp_raw_data_read, NULL); +static sec_input_proc_ops(THIS_MODULE, tsp_command_history_file_ops, sec_tsp_command_history_read, NULL); +static sec_input_proc_ops(THIS_MODULE, tsp_sponge_log_file_ops, sec_tsp_sponge_log_read, NULL); + +int sec_tsp_log_init(void) +{ + struct proc_dir_entry *entry_tsp_msg, *entry_tsp_fail_hist, + *entry_tsp_raw_data, *entry_tsp_cmd_hist, *entry_tsp_sponge_log; + char *vaddr_tsp_log, *vaddr_tsp_fail_hist, *vaddr_tsp_raw_data, + *vaddr_tsp_command_history, *vaddr_tsp_sponge_log; + + pr_info("%s %s: init start\n", SECLOG, __func__); + + mutex_init(&tsp_log_mutex); + + sec_tsp_log_size = SEC_TSP_LOG_BUF_SIZE; + vaddr_tsp_log = kmalloc(sec_tsp_log_size, GFP_KERNEL | __GFP_COMP); + if (!vaddr_tsp_log) + pr_info("%s %s: ERROR! vaddr_tsp_log alloc failed!\n", SECLOG, __func__); + + sec_tsp_log_buf = vaddr_tsp_log; + + sec_tsp_fail_hist_size = SEC_TSP_FAIL_HIST_BUF_SIZE; + vaddr_tsp_fail_hist = kmalloc(sec_tsp_fail_hist_size, GFP_KERNEL | __GFP_COMP); + if (!vaddr_tsp_fail_hist) + pr_info("%s %s: ERROR! vaddr_tsp_fail_hist alloc failed!\n", SECLOG, __func__); + + sec_tsp_fail_hist_buf = vaddr_tsp_fail_hist; + + sec_tsp_raw_data_size = SEC_TSP_RAW_DATA_BUF_SIZE; + vaddr_tsp_raw_data = kmalloc(sec_tsp_raw_data_size, GFP_KERNEL | __GFP_COMP); + if (!vaddr_tsp_raw_data) + pr_info("%s %s: ERROR! vaddr_tsp_raw_data alloc failed!\n", SECLOG, __func__); + + sec_tsp_raw_data_buf = vaddr_tsp_raw_data; + + sec_tsp_raw_data_index_sub = (sec_tsp_raw_data_size / 2); + + sec_tsp_command_history_size = SEC_TSP_COMMAND_HISTORY_BUF_SIZE; + vaddr_tsp_command_history = kmalloc(sec_tsp_command_history_size, GFP_KERNEL | __GFP_COMP); + if (!vaddr_tsp_command_history) + pr_info("%s %s: ERROR! vaddr_tsp_command_history alloc failed!\n", SECLOG, __func__); + + sec_tsp_command_history_buf = vaddr_tsp_command_history; + + sec_tsp_sponge_log_size = SEC_TSP_SPONGE_LOG_BUF_SIZE; + vaddr_tsp_sponge_log = kmalloc(sec_tsp_sponge_log_size, GFP_KERNEL | __GFP_COMP); + if (!vaddr_tsp_sponge_log) + pr_info("%s %s: ERROR! vaddr_tsp_sponge_log alloc failed!\n", SECLOG, __func__); + + sec_tsp_sponge_log_buf = vaddr_tsp_sponge_log; + + if (sec_tsp_log_buf) { + entry_tsp_msg = proc_create("tsp_msg", S_IFREG | 0440, + NULL, &tsp_msg_file_ops); + if (!entry_tsp_msg) + pr_err("%s %s: failed to create proc entry of tsp_msg\n", SECLOG, __func__); + } + + if (sec_tsp_fail_hist_buf) { + entry_tsp_fail_hist = proc_create("tsp_fail_hist_all", S_IFREG | 0444, + NULL, &tsp_fail_hist_file_ops); + if (!entry_tsp_fail_hist) + pr_err("%s %s: failed to create proc entry of tsp_fail_hist_all\n", SECLOG, __func__); + } + + if (sec_tsp_raw_data_buf) { + entry_tsp_raw_data = proc_create("tsp_raw_data", S_IFREG | 0444, + NULL, &tsp_raw_data_file_ops); + if (!entry_tsp_raw_data) + pr_err("%s %s: failed to create proc entry of tsp_raw_data\n", SECLOG, __func__); + } + + if (sec_tsp_command_history_buf) { + entry_tsp_cmd_hist = proc_create("tsp_cmd_hist", S_IFREG | 0444, + NULL, &tsp_command_history_file_ops); + if (!entry_tsp_cmd_hist) + pr_err("%s %s: failed to create proc entry of tsp_command_history\n", SECLOG, __func__); + } + + if (sec_tsp_sponge_log_buf) { + entry_tsp_sponge_log = proc_create("tsp_sponge_log", S_IFREG | 0444, + NULL, &tsp_sponge_log_file_ops); + if (!entry_tsp_sponge_log) + pr_err("%s %s: failed to create proc entry of tsp_sponge_log\n", SECLOG, __func__); + } + + pr_info("%s %s: init done\n", SECLOG, __func__); + + return 0; +} +EXPORT_SYMBOL(sec_tsp_log_init); + +MODULE_DESCRIPTION("Samsung debug sysfs for Input module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/sec_input/sec_tsp_log.h b/drivers/input/sec_input/sec_tsp_log.h new file mode 100644 index 000000000000..c4f09cbd93ca --- /dev/null +++ b/drivers/input/sec_input/sec_tsp_log.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0 + * Copyright (C) 2022 Samsung Electronics Co., Ltd. + * + * 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. + */ +#ifndef _SEC_TSP_LOG_H_ +#define _SEC_TSP_LOG_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SEC_TSP_LOG_BUF_SIZE (256 * 1024) /* 256 KB */ +#define SEC_TSP_FAIL_HIST_BUF_SIZE (50 * 1024) /* 50 KB */ +#if IS_ENABLED(CONFIG_SEC_INPUT_MULTI_DEVICE) +#define SEC_TSP_RAW_DATA_BUF_SIZE (2 * 50 * 1024) /* 100 KB */ +#else +#define SEC_TSP_RAW_DATA_BUF_SIZE (50 * 1024) /* 50 KB */ +#endif +#define SEC_TSP_COMMAND_HISTORY_BUF_SIZE (10 * 1024) /* 10 KB */ +#define SEC_TSP_SPONGE_LOG_BUF_SIZE (128 * 1024) /* 128 KB */ + +#define SEC_TSP_LOG_TIMESTAMP_SIZE 50 +#define SEC_TSP_LOG_EXTRA_SIZE 10 + +/** + * sec_debug_tsp_log : Leave tsp log in tsp_msg file. + * ( Timestamp + Tsp logs ) + * sec_debug_tsp_log_msg : Leave tsp log in tsp_msg file and + * add additional message between timestamp and tsp log. + * ( Timestamp + additional Message + Tsp logs ) + */ +void sec_tsp_sponge_log(char *buf); +void sec_debug_tsp_log_msg(char *msg, char *fmt, ...); +void sec_debug_tsp_fail_hist(char *msg, char *fmt, ...); +void sec_tsp_log_fix(void); +void sec_tsp_raw_data_clear(char mode); +void sec_debug_tsp_raw_data_msg(char mode, char *msg, char *fmt, ...); +void sec_debug_tsp_command_history(char *buf); + +int sec_tsp_log_init(void); +#endif /* _SEC_TSP_LOG_H_ */ diff --git a/drivers/input/sec_input/stm_spi/Kconfig b/drivers/input/sec_input/stm_spi/Kconfig new file mode 100644 index 000000000000..cded6a35dbe2 --- /dev/null +++ b/drivers/input/sec_input/stm_spi/Kconfig @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# SLSI TOUCH driver configuration +# + +config TOUCHSCREEN_STM + tristate "STM Touchscreen" + depends on I2C + help + Say Y here if you want support for SEC touchscreen controllers. + If unsure, say N. + +config TOUCHSCREEN_STM_SPI + tristate "STM SPI Touchscreen" + depends on SPI + help + Say Y here if you want support for STM SPI touchscreen controllers. + If unsure, say N. \ No newline at end of file diff --git a/drivers/input/sec_input/stm_spi/Makefile b/drivers/input/sec_input/stm_spi/Makefile new file mode 100644 index 000000000000..278ff5106f57 --- /dev/null +++ b/drivers/input/sec_input/stm_spi/Makefile @@ -0,0 +1,15 @@ +TARGET = stm_ts +TARGET_SPI = stm_ts_spi + +$(TARGET)-objs := stm_i2c.o stm_core.o stm_fn.o stm_fw.o stm_dump.o stm_cmd.o +ifeq ($(CONFIG_SAMSUNG_PRODUCT_SHIP), y) + $(TARGET_SPI)-objs := stm_spi.o stm_core.o stm_fn.o stm_fw.o stm_dump.o stm_cmd.o stm_ioctl.o +else + $(TARGET_SPI)-objs := stm_spi.o stm_core.o stm_fn.o stm_fw.o stm_dump.o stm_cmd.o stm_ioctl.o stm_vendor.o +endif + +obj-$(CONFIG_TOUCHSCREEN_STM) += $(TARGET).o +obj-$(CONFIG_TOUCHSCREEN_STM_SPI) += $(TARGET_SPI).o + +ccflags-y += -Wformat + diff --git a/drivers/input/sec_input/stm_spi/stm_cmd.c b/drivers/input/sec_input/stm_spi/stm_cmd.c new file mode 100644 index 000000000000..c4d4c3544b38 --- /dev/null +++ b/drivers/input/sec_input/stm_spi/stm_cmd.c @@ -0,0 +1,5861 @@ +#include "stm_dev.h" +#include "stm_reg.h" + +enum { + TYPE_RAW_DATA = 0x30, // only for winner, etc 0x31 + TYPE_FILTERED_RAW_DATA = 0x31, // only for winner + TYPE_BASELINE_DATA = 0x32, + TYPE_STRENGTH_DATA = 0x33, +}; + +enum ito_error_type { + ITO_FORCE_SHRT_GND = 0x60, + ITO_SENSE_SHRT_GND = 0x61, + ITO_FORCE_SHRT_VDD = 0x62, + ITO_SENSE_SHRT_VDD = 0x63, + ITO_FORCE_SHRT_FORCE = 0x64, + ITO_SENSE_SHRT_SENSE = 0x65, + ITO_FORCE_OPEN = 0x66, + ITO_SENSE_OPEN = 0x67, + ITO_KEY_OPEN = 0x68 +}; + +static ssize_t scrub_pos_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[256] = { 0 }; + +#if IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP) + input_info(true, ts->dev, + "%s: id: %d\n", __func__, ts->plat_data->gesture_id); +#else + input_info(true, ts->dev, + "%s: id: %d, X:%d, Y:%d\n", __func__, + ts->plat_data->gesture_id, ts->plat_data->gesture_x, ts->plat_data->gesture_y); +#endif + snprintf(buff, sizeof(buff), "%d %d %d", ts->plat_data->gesture_id, + ts->plat_data->gesture_x, ts->plat_data->gesture_y); + + ts->plat_data->gesture_x = 0; + ts->plat_data->gesture_y = 0; + + return snprintf(buf, PAGE_SIZE, "%s", buff); +} + +/* read param */ +static ssize_t hw_param_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[516]; + char tbuff[128]; + char temp[128]; + + memset(buff, 0x00, sizeof(buff)); + + sec_input_get_common_hw_param(ts->plat_data, buff); + + /* module_id */ + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), ",\"TMOD\":\"ST%02X%04X%02X%c%01X\",", + ts->panel_revision, ts->fw_main_version_of_ic, + ts->test_result.data[0], +#ifdef TCLM_CONCEPT + ts->tdata->tclm_string[ts->tdata->nvdata.cal_position].s_name, + ts->tdata->nvdata.cal_count & 0xF); +#else + '0', 0); +#endif + strlcat(buff, tbuff, sizeof(buff)); + + /* vendor_id */ + memset(tbuff, 0x00, sizeof(tbuff)); + if (ts->plat_data->firmware_name) { + memset(temp, 0x00, sizeof(temp)); + snprintf(temp, 9, "%s", ts->plat_data->firmware_name + 8); + + snprintf(tbuff, sizeof(tbuff), "\"TVEN\":\"STM_%s\"", temp); + } else { + snprintf(tbuff, sizeof(tbuff), "\"TVEN\":\"STM\""); + } + strlcat(buff, tbuff, sizeof(buff)); + + input_info(true, ts->dev, "%s: %s\n", __func__, buff); + + return snprintf(buf, SEC_CMD_BUF_SIZE, "%s", buff); +} + +/* clear param */ +static ssize_t hw_param_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + sec_input_clear_common_hw_param(ts->plat_data); + + return count; +} + +static ssize_t read_ambient_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + input_info(true, ts->dev, + "\"TAMB_MAX\":\"%d\",\"TAMB_MAX_TX\":\"%d\",\"TAMB_MAX_RX\":\"%d\"" + "\"TAMB_MIN\":\"%d\",\"TAMB_MIN_TX\":\"%d\",\"TAMB_MIN_RX\":\"%d\"", + ts->rawcap_max, ts->rawcap_max_tx, ts->rawcap_max_rx, + ts->rawcap_min, ts->rawcap_min_tx, ts->rawcap_min_rx); + + return snprintf(buf, SEC_CMD_BUF_SIZE, + "\"TAMB_MAX\":\"%d\",\"TAMB_MAX_TX\":\"%d\",\"TAMB_MAX_RX\":\"%d\"," + "\"TAMB_MIN\":\"%d\",\"TAMB_MIN_TX\":\"%d\",\"TAMB_MIN_RX\":\"%d\"", + ts->rawcap_max, ts->rawcap_max_tx, ts->rawcap_max_rx, + ts->rawcap_min, ts->rawcap_min_tx, ts->rawcap_min_rx); +} + +static ssize_t sensitivity_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + unsigned char wbuf[3] = { 0 }; + unsigned long value = 0; + int ret = 0; + + if (count > 2) + return -EINVAL; + + ret = kstrtoul(buf, 10, &value); + if (ret != 0) + return ret; + + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: [ERROR] Touch is stopped\n", __func__); + return -EPERM; + } + + cancel_delayed_work(&ts->work_print_info); + + wbuf[0] = STM_TS_CMD_SENSITIVITY_MODE; + + switch (value) { + case 0: + wbuf[1] = 0xFF; /* disable */ + break; + case 1: + wbuf[1] = 0x24; /* enable */ + wbuf[2] = 0x01; + ts->sensitivity_mode = 1; + break; + case 2: + wbuf[1] = 0x24; /* flex mode enable */ + wbuf[2] = 0x02; + ts->sensitivity_mode = 2; + break; + default: + break; + } + + ret = ts->stm_ts_write(ts, &wbuf[0], 1, &wbuf[1], 2); + if (ret < 0) { + input_err(true, ts->dev, + "%s: write failed. ret: %d\n", __func__, ret); + schedule_delayed_work(&ts->work_print_info, msecs_to_jiffies(TOUCH_PRINT_INFO_DWORK_TIME)); + return ret; + } + + sec_delay(30); + + if (value == 0) + schedule_delayed_work(&ts->work_print_info, msecs_to_jiffies(TOUCH_PRINT_INFO_DWORK_TIME)); + + input_info(true, ts->dev, "%s: %ld\n", __func__, value); + return count; +} + +static ssize_t sensitivity_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + u8 *rbuf; + u8 reg_read = STM_TS_READ_SENSITIVITY_VALUE; + int ret, i; + s16 value[12]; + u8 count = 9; + char *buffer; + ssize_t len; + + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: [ERROR] Touch is stopped\n", __func__); + return -EPERM; + } + + if (ts->sensitivity_mode == 2) /* flex mode */ + count = 12; + + rbuf = kzalloc(count * 2, GFP_KERNEL); + if (!rbuf) + return -ENOMEM; + + buffer = kzalloc(SEC_CMD_BUF_SIZE, GFP_KERNEL); + if (!buffer) { + kfree(rbuf); + return -ENOMEM; + } + + ret = ts->stm_ts_read(ts, ®_read, 1, rbuf, count * 2); + if (ret < 0) { + kfree(rbuf); + kfree(buffer); + input_err(true, ts->dev, "%s: read failed ret = %d\n", __func__, ret); + return ret; + } + + for (i = 0; i < count; i++) { + char temp[16]; + + memset(temp, 0x00, 16); + value[i] = rbuf[i * 2] + (rbuf[i * 2 + 1] << 8); + snprintf(temp, 16, "%d,", value[i]); + strlcat(buffer, temp, SEC_CMD_BUF_SIZE); + } + + input_info(true, ts->dev, "%s: %s\n", __func__, buffer); + len = snprintf(buf, SEC_CMD_BUF_SIZE, "%s", buffer); + + kfree(rbuf); + kfree(buffer); + + return len; +} + +ssize_t get_lp_dump_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + u8 string_data[10] = {0, }; + u16 current_index; + u16 dump_start, dump_end, dump_cnt; + int i, ret, dump_area, dump_gain; + unsigned char *sec_spg_dat; + u8 data[3] = {0}; + + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: Touch is stopped!\n", __func__); + if (buf) + return snprintf(buf, SEC_CMD_BUF_SIZE, "TSP turned off"); + else + return 0; + } + + if (atomic_read(&ts->reset_is_on_going)) { + input_err(true, ts->dev, "%s: Reset is ongoing!\n", __func__); + if (buf) + return snprintf(buf, SEC_CMD_BUF_SIZE, "Reset is ongoing"); + else + return 0; + } + + /* preparing dump buffer */ + sec_spg_dat = vmalloc(SEC_TS_MAX_SPONGE_DUMP_BUFFER); + if (!sec_spg_dat) { + input_err(true, ts->dev, "%s : Failed!!\n", __func__); + if (buf) + return snprintf(buf, SEC_CMD_BUF_SIZE, "vmalloc failed"); + else + return 0; + } + memset(sec_spg_dat, 0, SEC_TS_MAX_SPONGE_DUMP_BUFFER); + + disable_irq(ts->irq); + + string_data[0] = SEC_TS_CMD_SPONGE_LP_DUMP_CUR_IDX; + + ret = ts->stm_ts_read_sponge(ts, string_data, 2); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to read rect\n", __func__); + if (buf) + snprintf(buf, SEC_CMD_BUF_SIZE, "NG, Failed to read rect"); + goto out; + } + + if (ts->sponge_inf_dump) + dump_gain = 2; + else + dump_gain = 1; + + current_index = (string_data[1] & 0xFF) << 8 | (string_data[0] & 0xFF); + dump_start = SEC_TS_CMD_SPONGE_LP_DUMP_EVENT; + dump_end = dump_start + (ts->sponge_dump_format * ((ts->sponge_dump_event * dump_gain) - 1)); + + if (current_index == 0) { + input_info(true, ts->dev, "%s: length 0\n", __func__); + if (buf) + snprintf(buf, SEC_CMD_BUF_SIZE, "length 0"); + goto out; + } + + if (current_index > dump_end || current_index < dump_start) { + input_err(true, ts->dev, + "Failed to Sponge LP log %d\n", current_index); + if (buf) + snprintf(buf, SEC_CMD_BUF_SIZE, + "NG, Failed to Sponge LP log, current_index=%d", + current_index); + goto out; + } + + /* legacy get_lp_dump */ + input_info(true, ts->dev, "%s: DEBUG format=%d, num=%d, start=%d, end=%d, current_index=%d\n", + __func__, ts->sponge_dump_format, ts->sponge_dump_event, dump_start, dump_end, current_index); + + for (i = (ts->sponge_dump_event * dump_gain) - 1 ; i >= 0 ; i--) { + u16 data0, data1, data2, data3, data4; + char buff[30] = {0, }; + u16 string_addr; + + if (current_index < (ts->sponge_dump_format * i)) + string_addr = (ts->sponge_dump_format * ts->sponge_dump_event * dump_gain) + current_index - (ts->sponge_dump_format * i); + else + string_addr = current_index - (ts->sponge_dump_format * i); + + if (string_addr < dump_start) + string_addr += (ts->sponge_dump_format * ts->sponge_dump_event * dump_gain); + + string_data[0] = string_addr & 0xFF; + string_data[1] = (string_addr & 0xFF00) >> 8; + + if (ts->sponge_dump_format > 10) { + input_err(true, ts->dev, "%s: wrong sponge sponge_dump_format size:%d\n", __func__, ts->sponge_dump_format); + if (buf) + snprintf(buf, SEC_CMD_BUF_SIZE, "NG,wrong sponge_dump_format"); + goto out; + } + + ret = ts->stm_ts_read_sponge(ts, string_data, ts->sponge_dump_format); + if (ret < 0) { + input_err(true, ts->dev, + "%s: Failed to read sponge\n", __func__); + if (buf) + snprintf(buf, SEC_CMD_BUF_SIZE, + "NG, Failed to read sponge, addr=%d", + string_addr); + goto out; + } + + data0 = (string_data[1] & 0xFF) << 8 | (string_data[0] & 0xFF); + data1 = (string_data[3] & 0xFF) << 8 | (string_data[2] & 0xFF); + data2 = (string_data[5] & 0xFF) << 8 | (string_data[4] & 0xFF); + data3 = (string_data[7] & 0xFF) << 8 | (string_data[6] & 0xFF); + data4 = (string_data[9] & 0xFF) << 8 | (string_data[8] & 0xFF); + + if (data0 || data1 || data2 || data3 || data4) { + if (ts->sponge_dump_format == 10) { + snprintf(buff, sizeof(buff), + "%d: %04x%04x%04x%04x%04x\n", + string_addr, data0, data1, data2, data3, data4); + } else { + snprintf(buff, sizeof(buff), + "%d: %04x%04x%04x%04x\n", + string_addr, data0, data1, data2, data3); + } + if (buf) + strlcat(buf, buff, PAGE_SIZE); + } + } + + if (ts->sponge_inf_dump) { + if (current_index >= ts->sponge_dump_border) { + dump_cnt = ((current_index - (ts->sponge_dump_border)) / ts->sponge_dump_format) + 1; + dump_area = 1; + sec_spg_dat[0] = (u8)ts->sponge_dump_border & 0xff; + sec_spg_dat[1] = (u8)(ts->sponge_dump_border >> 8); + } else { + dump_cnt = ((current_index - SEC_TS_CMD_SPONGE_LP_DUMP_EVENT) / ts->sponge_dump_format) + 1; + dump_area = 0; + sec_spg_dat[0] = SEC_TS_CMD_SPONGE_LP_DUMP_EVENT; + sec_spg_dat[1] = 0; + } + + if (dump_cnt * ts->sponge_dump_format > SEC_TS_MAX_SPONGE_DUMP_BUFFER) { + input_err(true, ts->dev, "%s: wrong sponge sponge_dump_format size:%d dump_cnt:%d\n", + __func__, ts->sponge_dump_format, dump_cnt); + if (buf) + snprintf(buf, SEC_CMD_BUF_SIZE, "NG,wrong sponge_dump_format"); + goto out; + } + + ret = ts->stm_ts_read_sponge(ts, sec_spg_dat, dump_cnt * ts->sponge_dump_format); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to read sponge\n", __func__); + goto out; + } + + for (i = 0 ; i <= dump_cnt ; i++) { + int e_offset = i * ts->sponge_dump_format; + char ibuff[30] = {0, }; + u16 edata[5]; + + edata[0] = (sec_spg_dat[1 + e_offset] & 0xFF) << 8 | (sec_spg_dat[0 + e_offset] & 0xFF); + edata[1] = (sec_spg_dat[3 + e_offset] & 0xFF) << 8 | (sec_spg_dat[2 + e_offset] & 0xFF); + edata[2] = (sec_spg_dat[5 + e_offset] & 0xFF) << 8 | (sec_spg_dat[4 + e_offset] & 0xFF); + edata[3] = (sec_spg_dat[7 + e_offset] & 0xFF) << 8 | (sec_spg_dat[6 + e_offset] & 0xFF); + edata[4] = (sec_spg_dat[9 + e_offset] & 0xFF) << 8 | (sec_spg_dat[8 + e_offset] & 0xFF); + + if (edata[0] || edata[1] || edata[2] || edata[3] || edata[4]) { + snprintf(ibuff, sizeof(ibuff), "%03d: %04x%04x%04x%04x%04x\n", + i + (ts->sponge_dump_event * dump_area), + edata[0], edata[1], edata[2], edata[3], edata[4]); +#if IS_ENABLED(CONFIG_SEC_DEBUG_TSP_LOG) + sec_tsp_sponge_log(ibuff); +#endif + } + } + + ts->sponge_dump_delayed_flag = false; + data[0] = STM_TS_CMD_SPONGE_OFFSET_MODE_01; + data[2] = ts->plat_data->sponge_mode |= SEC_TS_MODE_SPONGE_INF_DUMP_CLEAR; + + ret = ts->stm_ts_write_sponge(ts, data, 3); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to clear sponge dump\n", __func__); + } + ts->plat_data->sponge_mode &= ~SEC_TS_MODE_SPONGE_INF_DUMP_CLEAR; + } +out: + vfree(sec_spg_dat); + enable_irq(ts->irq); + + if (buf) + return strlen(buf); + else + return 0; +} + +static ssize_t ear_detect_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + input_info(true, ts->dev, "%s: %d\n", __func__, + ts->plat_data->ed_enable); + + return snprintf(buf, SEC_CMD_BUF_SIZE, "%d", ts->plat_data->ed_enable); +} + +static ssize_t ear_detect_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret, value; + u8 address = STM_TS_CMD_SET_EAR_DETECT; + + ret = kstrtoint(buf, 10, &value); + if (ret < 0) + return ret; + + if (ts->plat_data->support_ear_detect) { + ts->plat_data->ed_enable = value; + ret = ts->stm_ts_write(ts, &address, 1, &ts->plat_data->ed_enable, 1); + input_info(true, ts->dev, "%s: set ear detect %s, ret = %d\n", + __func__, ts->plat_data->ed_enable ? "enable" : "disable", ret); + } + + return count; +} + +static ssize_t virtual_prox_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + input_info(true, ts->dev, "%s: %d\n", __func__, ts->hover_event); + + return snprintf(buf, SEC_CMD_BUF_SIZE, "%d", ts->hover_event != 3 ? 0 : 3); +} + +static ssize_t virtual_prox_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret; + u8 address; + u8 data; + + if (!ts->plat_data->support_ear_detect) { + input_err(true, ts->dev, "%s: ear detection is not supported\n", __func__); + return -ENODEV; + } + + ret = kstrtou8(buf, 8, &data); + if (ret < 0) + return ret; + + address = STM_TS_CMD_SET_EAR_DETECT; + + ret = ts->stm_ts_write(ts, &address, 1, &data, 1); + input_info(true, ts->dev, "%s: set %d: ret:%d\n", __func__, data, ret); + + return count; +} + +static ssize_t fod_pos_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[3] = { 0 }; + int i, ret; + + if (!ts->plat_data->support_fod) { + input_err(true, ts->dev, "%s: fod is not supported\n", __func__); + return snprintf(buf, SEC_CMD_BUF_SIZE, "NA"); + } + + if (!ts->plat_data->fod_data.vi_size) { + input_err(true, ts->dev, "%s: not read fod_info yet\n", __func__); + return snprintf(buf, SEC_CMD_BUF_SIZE, "NG"); + } + + if (!ts->plat_data->support_fod_lp_mode) { + ret = stm_ts_fod_vi_event(ts); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to read\n", __func__); + return snprintf(buf, SEC_CMD_BUF_SIZE, "NG"); + } + } + + for (i = 0; i < ts->plat_data->fod_data.vi_size; i++) { + snprintf(buff, 3, "%02X", ts->plat_data->fod_data.vi_data[i]); + strlcat(buf, buff, SEC_CMD_BUF_SIZE); + } + + return strlen(buf); +} + +static ssize_t fod_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + return sec_input_get_fod_info(ts->dev, buf); +} + +static ssize_t aod_active_area_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + input_info(true, ts->dev, "%s: top:%d, edge:%d, bottom:%d\n", + __func__, ts->plat_data->aod_data.active_area[0], + ts->plat_data->aod_data.active_area[1], ts->plat_data->aod_data.active_area[2]); + + return snprintf(buf, SEC_CMD_BUF_SIZE, "%d,%d,%d", + ts->plat_data->aod_data.active_area[0], ts->plat_data->aod_data.active_area[1], + ts->plat_data->aod_data.active_area[2]); +} + +static DEVICE_ATTR_RO(scrub_pos); +static DEVICE_ATTR_RW(hw_param); +static DEVICE_ATTR_RO(read_ambient_info); +static DEVICE_ATTR_RW(sensitivity_mode); +static DEVICE_ATTR_RO(get_lp_dump); +static DEVICE_ATTR_RW(ear_detect_enable); +static DEVICE_ATTR_RW(virtual_prox); +static DEVICE_ATTR_RO(fod_pos); +static DEVICE_ATTR_RO(fod_info); +static DEVICE_ATTR_RO(aod_active_area); + +static struct attribute *cmd_attributes[] = { + &dev_attr_scrub_pos.attr, + &dev_attr_hw_param.attr, + &dev_attr_read_ambient_info.attr, + &dev_attr_sensitivity_mode.attr, + &dev_attr_get_lp_dump.attr, + &dev_attr_ear_detect_enable.attr, + &dev_attr_virtual_prox.attr, + &dev_attr_fod_pos.attr, + &dev_attr_fod_info.attr, + &dev_attr_aod_active_area.attr, + NULL, +}; + +static struct attribute_group cmd_attr_group = { + .attrs = cmd_attributes, +}; + +static void enter_factory_mode(struct stm_ts_data *ts, bool fac_mode) +{ + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) + return; + + ts->stm_ts_systemreset(ts, 50); + + stm_ts_release_all_finger(ts); + + if (fac_mode) { + stm_ts_execute_autotune(ts, false); + sec_delay(50); + } + + stm_ts_set_scanmode(ts, ts->scan_mode); + + sec_delay(50); +} + +static int stm_ts_check_index(struct stm_ts_data *ts) +{ + struct sec_cmd_data *sec = &ts->sec; + char buff[SEC_CMD_STR_LEN] = { 0 }; + int node; + + if (sec->cmd_param[0] < 0 + || sec->cmd_param[0] >= ts->rx_count + || sec->cmd_param[1] < 0 + || sec->cmd_param[1] >= ts->tx_count) { + + snprintf(buff, sizeof(buff), "%s", "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + input_err(true, ts->dev, "%s: parameter error: %u,%u\n", + __func__, sec->cmd_param[0], sec->cmd_param[1]); + node = -1; + return node; + } + node = sec->cmd_param[1] * ts->rx_count + sec->cmd_param[0]; + input_info(true, ts->dev, "%s: node = %d\n", __func__, node); + return node; +} + +static void stm_ts_print_channel(struct stm_ts_data *ts, s16 *tx_data, s16 *rx_data) +{ + unsigned char *pStr = NULL; + unsigned char pTmp[16] = { 0 }; + int len, max_num = 0; + int i = 0, k = 0; + int tx_count = ts->tx_count; + int rx_count = ts->rx_count; + + if (tx_count > 20) + max_num = 10; + else + max_num = tx_count; + + if (!max_num) + return; + + len = 7 * (max_num + 1); + pStr = vzalloc(len); + if (!pStr) + return; + + /* Print TX channel */ + memset(pStr, 0x0, len); + snprintf(pTmp, sizeof(pTmp), " TX"); + strlcat(pStr, pTmp, len); + + for (k = 0; k < max_num; k++) { + snprintf(pTmp, sizeof(pTmp), " %02d", k); + strlcat(pStr, pTmp, len); + } + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", pStr); + + memset(pStr, 0x0, len); + snprintf(pTmp, sizeof(pTmp), " +"); + strlcat(pStr, pTmp, len); + + for (k = 0; k < max_num; k++) { + snprintf(pTmp, sizeof(pTmp), "------"); + strlcat(pStr, pTmp, len); + } + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", pStr); + + memset(pStr, 0x0, len); + snprintf(pTmp, sizeof(pTmp), " | "); + strlcat(pStr, pTmp, len); + + for (i = 0; i < tx_count; i++) { + if (i && i % max_num == 0) { + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", pStr); + memset(pStr, 0x0, len); + snprintf(pTmp, sizeof(pTmp), " | "); + strlcat(pStr, pTmp, len); + } + + snprintf(pTmp, sizeof(pTmp), " %5d", tx_data[i]); + strlcat(pStr, pTmp, len); + } + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", pStr); + + /* Print RX channel */ + memset(pStr, 0x0, len); + snprintf(pTmp, sizeof(pTmp), " RX"); + strlcat(pStr, pTmp, len); + + for (k = 0; k < max_num; k++) { + snprintf(pTmp, sizeof(pTmp), " %02d", k); + strlcat(pStr, pTmp, len); + } + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", pStr); + + memset(pStr, 0x0, len); + snprintf(pTmp, sizeof(pTmp), " +"); + strlcat(pStr, pTmp, len); + + for (k = 0; k < max_num; k++) { + snprintf(pTmp, sizeof(pTmp), "------"); + strlcat(pStr, pTmp, len); + } + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", pStr); + + memset(pStr, 0x0, len); + snprintf(pTmp, sizeof(pTmp), " | "); + strlcat(pStr, pTmp, len); + + for (i = 0; i < rx_count; i++) { + if (i && i % max_num == 0) { + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", pStr); + memset(pStr, 0x0, len); + snprintf(pTmp, sizeof(pTmp), " | "); + strlcat(pStr, pTmp, len); + } + + snprintf(pTmp, sizeof(pTmp), " %5d", rx_data[i]); + strlcat(pStr, pTmp, len); + } + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", pStr); + + vfree(pStr); +} + +void stm_ts_print_frame(struct stm_ts_data *ts, short *min, short *max) +{ + int i = 0; + int j = 0; + u8 *pStr = NULL; + u8 pTmp[16] = { 0 }; + int lsize = 6 * (ts->rx_count + 1); + + pStr = kzalloc(lsize, GFP_KERNEL); + if (pStr == NULL) + return; + + snprintf(pTmp, 4, " "); + strlcat(pStr, pTmp, lsize); + + for (i = 0; i < ts->rx_count; i++) { + snprintf(pTmp, 6, "Rx%02d ", i); + strlcat(pStr, pTmp, lsize); + } + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", pStr); + + memset(pStr, 0x0, 6 * (ts->rx_count + 1)); + snprintf(pTmp, 2, " +"); + strlcat(pStr, pTmp, lsize); + + for (i = 0; i < ts->rx_count; i++) { + snprintf(pTmp, 6, "------"); + strlcat(pStr, pTmp, lsize); + } + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", pStr); + + for (i = 0; i < ts->tx_count; i++) { + memset(pStr, 0x0, 6 * (ts->rx_count + 1)); + snprintf(pTmp, 7, "Tx%02d | ", i); + strlcat(pStr, pTmp, lsize); + + for (j = 0; j < ts->rx_count; j++) { + snprintf(pTmp, 6, "%5d ", ts->pFrame[(i * ts->rx_count) + j]); + strlcat(pStr, pTmp, lsize); + + if (ts->pFrame[(i * ts->rx_count) + j] < *min) + *min = ts->pFrame[(i * ts->rx_count) + j]; + + if (ts->pFrame[(i * ts->rx_count) + j] > *max) + *max = ts->pFrame[(i * ts->rx_count) + j]; + } + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", pStr); + } + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s, min:%d, max:%d\n", __func__, *min, *max); + + kfree(pStr); +} + +int stm_ts_read_frame(struct stm_ts_data *ts, u8 type, short *min, short *max) +{ + struct stm_ts_syncframeheader *psyncframeheader; + u8 reg[8] = { 0 }; + unsigned int totalbytes = 0; + u8 *pRead; + int ret = 0; + int i = 0, j = 0; + int retry = 10; + + pRead = kzalloc(ts->tx_count * ts->rx_count * 3 + 1, GFP_KERNEL); + if (!pRead) + return -ENOMEM; + + /* Request Data Type */ + reg[0] = 0xA4; + reg[1] = 0x06; + reg[2] = (u8)type; + ts->stm_ts_write(ts, ®[0], 3, NULL, 0); + sec_delay(50); + + do { + reg[0] = STM_TS_CMD_FRM_BUFF_R; + reg[1] = 0x00; + reg[2] = 0x00; + ret = ts->stm_ts_read(ts, ®[0], 3, &pRead[0], STM_TS_COMP_DATA_HEADER_SIZE); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to read stm_ts_comp_data_header_size\n", __func__); + goto ERROREXIT; + } + + psyncframeheader = (struct stm_ts_syncframeheader *) &pRead[0]; + + if ((psyncframeheader->header == 0xA5) && (psyncframeheader->host_data_mem_id == type)) + break; + + sec_delay(100); + } while (retry--); + + if (retry == 0) { + input_err(true, ts->dev, + "%s: didn't match header or id. header = %02X, id = %02X\n", + __func__, psyncframeheader->header, psyncframeheader->host_data_mem_id); + ret = -STM_TS_ERROR_INVALID_CHIP_VERSION_ID; + goto ERROREXIT; + } + + totalbytes = (ts->tx_count * ts->rx_count * 2); + + reg[0] = STM_TS_CMD_FRM_BUFF_R; + reg[1] = 0x00; + reg[2] = STM_TS_COMP_DATA_HEADER_SIZE + psyncframeheader->dbg_frm_len; + ret = ts->stm_ts_read(ts, ®[0], 3, &pRead[0], totalbytes); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to read totalbytes\n", __func__); + goto ERROREXIT; + } + + for (i = 0; i < totalbytes / 2; i++) + ts->pFrame[i] = (short)(pRead[i * 2] + (pRead[i * 2 + 1] << 8)); + + switch (type) { + case TYPE_RAW_DATA: + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s", "[Raw Data]\n"); + break; + case TYPE_STRENGTH_DATA: + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s: [Strength Data]\n", __func__); + break; + case TYPE_BASELINE_DATA: + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s: [Baseline Data]\n", __func__); + break; + } + + stm_ts_print_frame(ts, min, max); + + if (!ts->info_work_done) { + if (type == TYPE_RAW_DATA) { + for (j = 0; j < ts->tx_count; j++) { + for (i = 0; i < ts->rx_count; i++) { + if (ts->rawcap_max < ts->pFrame[j * ts->rx_count + i]) { + ts->rawcap_max = ts->pFrame[j * ts->rx_count + i]; + ts->rawcap_max_tx = j; + ts->rawcap_max_rx = i; + } + if (ts->rawcap_min > ts->pFrame[j * ts->rx_count + i]) { + ts->rawcap_min = ts->pFrame[j * ts->rx_count + i]; + ts->rawcap_min_tx = j; + ts->rawcap_min_rx = i; + } + } + } + } + } +ERROREXIT: + kfree(pRead); + return ret; +} + +int stm_ts_read_nonsync_frame(struct stm_ts_data *ts, short *min, short *max) +{ + struct stm_ts_syncframeheader *psyncframeheader; + u8 reg[8] = { 0 }; + unsigned int totalbytes = 0; + u8 *pRead; + int ret = 0; + int i = 0; + int retry = 10; + + input_info(true, ts->dev, "%s\n", __func__); + + pRead = kzalloc(ts->tx_count * ts->rx_count * 3 + 1, GFP_KERNEL); + if (!pRead) + return -ENOMEM; + + /* Request System Information */ + reg[0] = 0xA4; + reg[1] = 0x06; + reg[2] = 0x01; + ts->stm_ts_write(ts, ®[0], 3, NULL, 0); + sec_delay(50); + + do { + reg[0] = STM_TS_CMD_FRM_BUFF_R; + reg[1] = 0x00; + reg[2] = 0x00; + ret = ts->stm_ts_read(ts, ®[0], 3, &pRead[0], STM_TS_COMP_DATA_HEADER_SIZE); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to read stm_ts_comp_data_header_size\n", __func__); + goto ERROREXIT; + } + + psyncframeheader = (struct stm_ts_syncframeheader *) &pRead[0]; + + if ((psyncframeheader->header == 0xA5) && (psyncframeheader->host_data_mem_id == 0x01)) + break; + + sec_delay(100); + } while (retry--); + + if (retry == 0) { + input_err(true, ts->dev, + "%s: didn't match header or id. header = %02X, id = %02X\n", + __func__, psyncframeheader->header, psyncframeheader->host_data_mem_id); + ret = -STM_TS_ERROR_INVALID_CHIP_VERSION_ID; + goto ERROREXIT; + } + + reg[0] = STM_TS_CMD_FRM_BUFF_R; + reg[1] = 0x00; + reg[2] = 0x88; // ms screen rawdata + ret = ts->stm_ts_read(ts, ®[0], 3, &pRead[0], 2); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to read ms screen rawdata(0x88)\n", __func__); + goto ERROREXIT; + } + + totalbytes = (ts->tx_count * ts->rx_count * 2); + + reg[0] = STM_TS_CMD_FRM_BUFF_R; + reg[1] = (u8)pRead[1]; + reg[2] = (u8)pRead[0]; + ret = ts->stm_ts_read(ts, ®[0], 3, &pRead[0], totalbytes); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to read totalbytes\n", __func__); + goto ERROREXIT; + } + + for (i = 0; i < totalbytes / 2; i++) + ts->pFrame[i] = (short)(pRead[i * 2] + (pRead[i * 2 + 1] << 8)); + + stm_ts_print_frame(ts, min, max); + +ERROREXIT: + kfree(pRead); + return ret; +} + +#define JITTER_TEST_RETRY 10 +int stm_ts_get_jitter_result(struct stm_ts_data *ts, u8 *reg, u8 count, s16 *result, u8 type) +{ + int ret = 0; + u8 address; + u8 data[STM_TS_EVENT_BUFF_SIZE]; + int retry = 0; + int retry_time = JITTER_TEST_RETRY; + + if (type == STM_TS_EVENT_JITTER_DELTA_TEST) + retry_time = JITTER_TEST_RETRY * 5; + + stm_ts_release_all_finger(ts); + + mutex_lock(&ts->fn_mutex); + disable_irq(ts->irq); + + ret = stm_ts_fix_active_mode(ts, STM_TS_ACTIVE_TRUE); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to set active mode\n", __func__); + goto ERROR; + } + + ret = ts->stm_ts_write(ts, reg, count, NULL, 0); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to write command\n", __func__); + goto ERROR; + } + + sec_delay(5); + + memset(data, 0x0, STM_TS_EVENT_BUFF_SIZE); + + address = STM_TS_READ_ONE_EVENT; + ret = -1; + while (ts->stm_ts_read(ts, &address, 1, data, STM_TS_EVENT_BUFF_SIZE) >= 0) { + if (data[0] != 0x00) + input_info(true, ts->dev, + "%s: event %02X, %02X, %02X, %02X, %02X, %02X, %02X, %02X\n", + __func__, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]); + + if ((data[0] == STM_TS_EVENT_JITTER_RESULT) && (data[1] == 0x03)) { + if ((type == STM_TS_EVENT_JITTER_MUTUAL_TEST) || (type == STM_TS_EVENT_JITTER_SELF_TEST)) { + if (data[2] == STM_TS_EVENT_JITTER_MUTUAL_MAX) { + result[1] = (s16)((data[8] << 8) + data[9]); + input_info(true, ts->dev, "%s: Mutual max jitter Strength : %d, RX:%d, TX:%d\n", + __func__, result[1], data[5], data[6]); + } else if (data[2] == STM_TS_EVENT_JITTER_MUTUAL_MIN) { + result[0] = (s16)((data[3] << 8) + data[4]); + input_info(true, ts->dev, "%s: Mutual min jitter Strength : %d, RX:%d, TX:%d\n", + __func__, result[0], data[5], data[6]); + ret = 0; + break; + } else if (data[2] == STM_TS_EVENT_JITTER_SELF_TX_P2P) { + result[0] = (s16)((data[3] << 8) + data[4]); + input_info(true, ts->dev, "%s: Self TX P2P jitter Strength : %d, TX:%d\n", + __func__, result[0], data[6]); + } else if (data[2] == STM_TS_EVENT_JITTER_SELF_RX_P2P) { + result[1] = (s16)((data[3] << 8) + data[4]); + input_info(true, ts->dev, "%s: Self RX P2P jitter Strength : %d, RX:%d\n", + __func__, result[1], data[5]); + ret = 0; + break; + } + } else if (type == STM_TS_EVENT_JITTER_DELTA_TEST) { + if (data[2] == STM_TS_EVENT_JITTER_MUTUAL_MIN) { + result[0] = data[3] << 8 | data[4]; + result[1] = data[8] << 8 | data[9]; + input_info(true, ts->dev, "%s: MIN: min:%d, max:%d\n", __func__, result[0], result[1]); + } else if (data[2] == STM_TS_EVENT_JITTER_MUTUAL_MAX) { + result[2] = data[3] << 8 | data[4]; + result[3] = data[8] << 8 | data[9]; + input_info(true, ts->dev, "%s: MAX: min:%d, max:%d\n", __func__, result[2], result[3]); + } else if (data[2] == STM_TS_EVENT_JITTER_MUTUAL_AVG) { + result[4] = data[3] << 8 | data[4]; + result[5] = data[8] << 8 | data[9]; + input_info(true, ts->dev, "%s: AVG: min:%d, max:%d\n", __func__, result[4], result[5]); + ret = 0; + break; + } + } else { + ret = -1; + break; + } + } else if (data[0] == STM_TS_EVENT_ERROR_REPORT) { + input_info(true, ts->dev, "%s: Error detected %02X,%02X,%02X,%02X,%02X,%02X\n", + __func__, data[0], data[1], data[2], data[3], data[4], data[5]); + break; + } + + if (retry++ > STM_TS_RETRY_COUNT * retry_time) { + ret = -1; + input_err(true, ts->dev, "%s: Time Over (%02X,%02X,%02X,%02X,%02X,%02X)\n", + __func__, data[0], data[1], data[2], data[3], data[4], data[5]); + break; + } + sec_delay(20); + } + +ERROR: + enable_irq(ts->irq); + mutex_unlock(&ts->fn_mutex); + + ts->stm_ts_systemreset(ts, 0); + stm_ts_set_scanmode(ts, ts->scan_mode); + + return ret; +} + +void stm_ts_checking_miscal(struct stm_ts_data *ts) +{ + u8 reg[3] = { 0 }; + short min = 0x7FFF; + short max = 0x8000; + + ts->stm_ts_systemreset(ts, 0); + stm_ts_command(ts, STM_TS_CMD_CLEAR_ALL_EVENT, true); + + stm_ts_release_all_finger(ts); + + /* get the raw data after C7 02 : mis cal test */ + reg[0] = 0xC7; + reg[1] = 0x02; + + ts->stm_ts_write(ts, ®[0], 2, NULL, 0); + sec_delay(300); + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s: [miscal diff data]\n", __func__); + stm_ts_read_nonsync_frame(ts, &min, &max); + + stm_ts_set_scanmode(ts, ts->scan_mode); + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s: mis cal min/max :%d/%d\n", __func__, min, max); +} + +int stm_ts_panel_ito_test(struct stm_ts_data *ts, int testmode) +{ + u8 cmd = STM_TS_READ_ONE_EVENT; + u8 reg[4] = { 0 }; + u8 data[STM_TS_EVENT_BUFF_SIZE]; + int i; + bool matched = false; + int retry = 0; + int result = 0; + + result = ts->stm_ts_systemreset(ts, 0); + if (result < 0) { + input_info(true, ts->dev, "%s: stm_ts_systemreset fail (%d)\n", __func__, result); + return result; + } + + disable_irq(ts->irq); + stm_ts_command(ts, STM_TS_CMD_CLEAR_ALL_EVENT, true); + + stm_ts_release_all_finger(ts); + + reg[0] = 0xA4; + reg[1] = 0x04; + switch (testmode) { + case OPEN_TEST: + reg[2] = 0x00; + reg[3] = 0x30; + break; + + case OPEN_SHORT_CRACK_TEST: + case SAVE_MISCAL_REF_RAW: + default: + reg[2] = 0xFF; + reg[3] = 0x01; + break; + } + + ts->stm_ts_write(ts, ®[0], 4, NULL, 0); // ITO test command + sec_delay(100); + + memset(ts->plat_data->hw_param.ito_test, 0x0, 4); + memset(data, 0x0, STM_TS_EVENT_BUFF_SIZE); + + while (ts->stm_ts_read(ts, &cmd, 1, (u8 *)data, STM_TS_EVENT_BUFF_SIZE) >= 0) { + if ((data[0] == STM_TS_EVENT_STATUS_REPORT) && (data[1] == 0x01)) { // Check command ECHO - finished + for (i = 0; i < 4; i++) { + if (data[i + 2] != reg[i]) { + matched = false; + break; + } + matched = true; + } + + if (matched == true) + break; + } else if (data[0] == STM_TS_EVENT_ERROR_REPORT) { + ts->plat_data->hw_param.ito_test[0] = data[0]; + ts->plat_data->hw_param.ito_test[1] = data[1]; + ts->plat_data->hw_param.ito_test[2] = data[2]; + ts->plat_data->hw_param.ito_test[3] = 0x00; + result = -ITO_FAIL; + + switch (data[1]) { + case ITO_FORCE_SHRT_GND: + input_info(true, ts->dev, "%s: Force channel [%d] short to GND\n", + __func__, data[2]); + result = -ITO_FAIL_SHORT; + break; + + case ITO_SENSE_SHRT_GND: + input_info(true, ts->dev, "%s: Sense channel [%d] short to GND\n", + __func__, data[2]); + result = ITO_FAIL_SHORT; + break; + + case ITO_FORCE_SHRT_VDD: + input_info(true, ts->dev, "%s: Force channel [%d] short to VDD\n", + __func__, data[2]); + result = -ITO_FAIL_SHORT; + break; + + case ITO_SENSE_SHRT_VDD: + input_info(true, ts->dev, "%s: Sense channel [%d] short to VDD\n", + __func__, data[2]); + result = -ITO_FAIL_SHORT; + break; + + case ITO_FORCE_SHRT_FORCE: + input_info(true, ts->dev, "%s: Force channel [%d] short to force\n", + __func__, data[2]); + result = -ITO_FAIL_SHORT; + break; + + case ITO_SENSE_SHRT_SENSE: + input_info(true, ts->dev, "%s: Sennse channel [%d] short to sense\n", + __func__, data[2]); + result = -ITO_FAIL_SHORT; + break; + + case ITO_FORCE_OPEN: + input_info(true, ts->dev, "%s: Force channel [%d] open\n", + __func__, data[2]); + result = -ITO_FAIL_OPEN; + break; + + case ITO_SENSE_OPEN: + input_info(true, ts->dev, "%s: Sense channel [%d] open\n", + __func__, data[2]); + result = -ITO_FAIL_OPEN; + break; + + case ITO_KEY_OPEN: + input_info(true, ts->dev, "%s: Key channel [%d] open\n", + __func__, data[2]); + result = -ITO_FAIL_OPEN; + break; + + default: + input_info(true, ts->dev, "%s: unknown event %02x %02x %02x %02x %02x %02x %02x %02x\n", + __func__, data[0], data[1], data[2], data[3], + data[4], data[5], data[6], data[7]); + break; + } + } + + if (retry++ > 50) { + result = -ITO_FAIL; + input_err(true, ts->dev, "%s: Time over - wait for result of ITO test\n", __func__); + break; + } + sec_delay(20); + } + + ts->stm_ts_systemreset(ts, 0); + + ts->plat_data->touch_functions = (ts->plat_data->touch_functions & (~STM_TS_TOUCHTYPE_BIT_COVER)); + if (ts->glove_enabled) + ts->plat_data->touch_functions = ts->plat_data->touch_functions | STM_TS_TOUCHTYPE_BIT_GLOVE; + else + ts->plat_data->touch_functions = ts->plat_data->touch_functions & (~STM_TS_TOUCHTYPE_BIT_GLOVE); + + stm_ts_set_touch_function(ts); + sec_delay(10); + + ts->plat_data->touch_count = 0; + + stm_ts_set_scanmode(ts, ts->scan_mode); + + enable_irq(ts->irq); + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s: mode:%d [%s] %02X %02X %02X %02X\n", + __func__, testmode, result < 0 ? "FAIL" : "PASS", + ts->plat_data->hw_param.ito_test[0], ts->plat_data->hw_param.ito_test[1], + ts->plat_data->hw_param.ito_test[2], ts->plat_data->hw_param.ito_test[3]); + + return result; +} + +int stm_ts_panel_test_result(struct stm_ts_data *ts, int type) +{ + u8 data[STM_TS_EVENT_BUFF_SIZE]; + u8 cmd = STM_TS_READ_ONE_EVENT; + uint8_t reg[4] = { 0 }; + bool matched = false; + int retry = 0; + int i = 0; + int result = 0; + short temp_min = 32767, temp_max = -32768; + + bool cheked_short_to_gnd = false; + bool cheked_short_to_vdd = false; + bool cheked_short = false; + bool cheked_open = false; + char tempv[25] = {0}; + char temph[30] = {0}; + struct sec_cmd_data *sec = &ts->sec; + + char buff[SEC_CMD_STR_LEN] = {0}; + u8 *result_buff = NULL; + int size = 4095; + + result_buff = kzalloc(size, GFP_KERNEL); + if (!result_buff) { + input_err(true, ts->dev, "%s: result_buff kzalloc fail!\n", __func__); + goto alloc_fail; + } + + + disable_irq(ts->irq); + + reg[0] = 0xA4; + reg[1] = 0x04; + reg[2] = 0xFC; + reg[3] = 0x09; + ts->stm_ts_write(ts, ®[0], 4, NULL, 0); + sec_delay(100); + + memset(ts->plat_data->hw_param.ito_test, 0x0, 4); + + memset(data, 0x0, STM_TS_EVENT_BUFF_SIZE); + while (ts->stm_ts_read(ts, &cmd, 1, (u8 *)data, STM_TS_EVENT_BUFF_SIZE) >= 0) { + memset(tempv, 0x00, 25); + memset(temph, 0x00, 30); + if ((data[0] == STM_TS_EVENT_STATUS_REPORT) && (data[1] == 0x01)) { + for (i = 0; i < 4; i++) { + if (data[i + 2] != reg[i]) { + matched = false; + break; + } + matched = true; + } + if (matched == true) + break; + + } else if (data[0] == STM_TS_EVENT_ERROR_REPORT) { + ts->plat_data->hw_param.ito_test[0] = data[0]; + ts->plat_data->hw_param.ito_test[1] = data[1]; + ts->plat_data->hw_param.ito_test[2] = data[2]; + ts->plat_data->hw_param.ito_test[3] = 0x00; + + switch (data[1]) { + case ITO_FORCE_SHRT_GND: + case ITO_SENSE_SHRT_GND: + input_info(true, ts->dev, "%s: TX/RX_SHORT_TO_GND:%cX[%d]\n", + __func__, data[1] == ITO_FORCE_SHRT_VDD ? 'T' : 'R', data[2]); + if (type != SHORT_TEST) + break; + if (!cheked_short_to_gnd) { + snprintf(temph, 30, "TX/RX_SHORT_TO_GND:"); + strlcat(result_buff, temph, size); + cheked_short_to_gnd = true; + } + snprintf(tempv, sizeof(tempv), "%c%d,", data[1] == ITO_FORCE_SHRT_GND ? 'T' : 'R', data[2]); + strlcat(result_buff, tempv, size); + result = -ITO_FAIL_SHORT; + break; + case ITO_FORCE_SHRT_VDD: + case ITO_SENSE_SHRT_VDD: + input_info(true, ts->dev, "%s: TX/RX_SHORT_TO_VDD:%cX[%d]\n", + __func__, data[1] == ITO_FORCE_SHRT_VDD ? 'T' : 'R', data[2]); + if (type != SHORT_TEST) + break; + if (!cheked_short_to_vdd) { + snprintf(temph, 30, "TX/RX_SHORT_TO_VDD:"); + strlcat(result_buff, temph, size); + cheked_short_to_vdd = true; + } + snprintf(tempv, sizeof(tempv), "%c%d,", data[1] == ITO_FORCE_SHRT_VDD ? 'T' : 'R', data[2]); + strlcat(result_buff, tempv, size); + result = -ITO_FAIL_SHORT; + break; + case ITO_FORCE_SHRT_FORCE: + case ITO_SENSE_SHRT_SENSE: + input_info(true, ts->dev, "%s: TX/RX_SHORT:%cX[%d]\n", + __func__, data[1] == ITO_FORCE_SHRT_FORCE ? 'T' : 'R', data[2]); + if (type != SHORT_TEST) + break; + if (!cheked_short) { + snprintf(temph, 30, "TX/RX_SHORT:"); + strlcat(result_buff, temph, size); + cheked_short = true; + } + snprintf(tempv, sizeof(tempv), "%c%d,", data[1] == ITO_FORCE_SHRT_FORCE ? 'T' : 'R', data[2]); + strlcat(result_buff, tempv, size); + result = -ITO_FAIL_SHORT; + break; + case ITO_FORCE_OPEN: + case ITO_SENSE_OPEN: + input_info(true, ts->dev, "%s: TX/RX_OPEN:%cX[%d]\n", + __func__, data[1] == ITO_FORCE_OPEN ? 'T' : 'R', data[2]); + if (type != OPEN_TEST) + break; + if (!cheked_open) { + snprintf(temph, 30, "TX/RX_OPEN:"); + strlcat(result_buff, temph, size); + cheked_open = true; + } + snprintf(tempv, sizeof(tempv), "%c%d,", data[1] == ITO_FORCE_OPEN ? 'T' : 'R', data[2]); + strlcat(result_buff, tempv, size); + result = -ITO_FAIL_OPEN; + break; + default: + input_info(true, ts->dev, "%s: unknown event %02x %02x %02x %02x %02x %02x %02x %02x\n", + __func__, data[0], data[1], data[2], data[3], + data[4], data[5], data[6], data[7]); + break; + } + } + + if (retry++ > 50) { + input_err(true, ts->dev, "%s: Time over - wait for result of ITO test\n", __func__); + goto test_fail; + } + sec_delay(20); + } + + /* read rawdata */ + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", __func__); + stm_ts_read_nonsync_frame(ts, &temp_min, &temp_max); + + if (type == OPEN_TEST && result == -ITO_FAIL_OPEN) { + snprintf(result_buff, sizeof(result_buff), "NG"); + sec_cmd_set_cmd_result(sec, result_buff, strnlen(result_buff, sizeof(result_buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + + } else if (type == SHORT_TEST && result == -ITO_FAIL_SHORT) { + snprintf(result_buff, sizeof(result_buff), "NG"); + sec_cmd_set_cmd_result(sec, result_buff, strnlen(result_buff, sizeof(result_buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + + } else { + snprintf(result_buff, sizeof(result_buff), "OK"); + sec_cmd_set_cmd_result(sec, result_buff, strnlen(result_buff, sizeof(result_buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + } + + input_info(true, ts->dev, "%s: %s\n", __func__, result_buff); + enable_irq(ts->irq); + kfree(result_buff); + + return result; + +test_fail: + enable_irq(ts->irq); + kfree(result_buff); +alloc_fail: + snprintf(buff, 30, "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + + return -ITO_FAIL; +} + +static void fw_update(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int retval = 0; + +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) +#if IS_ENABLED(CONFIG_INPUT_SEC_TRUSTED_TOUCH) + if (atomic_read(&ts->plat_data->pvm->trusted_touch_enabled)) { + input_info(true, ts->dev, "%s trusted touch is enabled. skip\n", __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } +#endif +#endif + + mutex_lock(&ts->plat_data->enable_mutex); + retval = stm_ts_fw_update_on_hidden_menu(ts, sec->cmd_param[0]); + if (retval < 0) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + input_err(true, ts->dev, "%s: failed [%d]\n", __func__, retval); + } else { + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: success [%d]\n", __func__, retval); + } + mutex_unlock(&ts->plat_data->enable_mutex); +} + +static void get_fw_ver_bin(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[16] = { 0 }; + + snprintf(buff, sizeof(buff), "ST%02X%02X%02X%02X", + ts->ic_name_of_bin, + ts->project_id_of_bin, + ts->module_version_of_bin, + ts->fw_main_version_of_bin & 0xFF); + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "FW_VER_BIN"); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void get_fw_ver_ic(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[16] = { 0 }; + char model_ver[7] = { 0 }; + + stm_ts_get_version_info(ts); + + snprintf(buff, sizeof(buff), "ST%02X%02X%02X%02X", + ts->plat_data->img_version_of_ic[0], ts->plat_data->img_version_of_ic[1], + ts->plat_data->img_version_of_ic[2], ts->plat_data->img_version_of_ic[3]); + snprintf(model_ver, sizeof(model_ver), "ST%02X%02X", + ts->plat_data->img_version_of_ic[0], ts->plat_data->img_version_of_ic[1]); + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) { + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "FW_VER_IC"); + sec_cmd_set_cmd_result_all(sec, model_ver, strnlen(model_ver, sizeof(model_ver)), "FW_MODEL"); + } + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void get_config_ver(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[20] = { 0 }; + + snprintf(buff, sizeof(buff), "ST_%04X", ts->config_version_of_ic); + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void module_off_master(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret = 0; + + ret = stm_ts_stop_device(ts); + if (ret == 0) + sec->cmd_state = SEC_CMD_STATUS_OK; + else + sec->cmd_state = SEC_CMD_STATUS_FAIL; + + input_info(true, ts->dev, "%s: %s\n", __func__, sec->cmd_state == SEC_CMD_STATUS_OK ? "OK" : "NG"); +} + +static void module_on_master(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret = 0; + + ret = stm_ts_start_device(ts); + if (ret == 0) + sec->cmd_state = SEC_CMD_STATUS_OK; + else + sec->cmd_state = SEC_CMD_STATUS_FAIL; + + input_info(true, ts->dev, "%s: %s\n", __func__, sec->cmd_state == SEC_CMD_STATUS_OK ? "OK" : "NG"); +} + +static void get_chip_vendor(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[16] = { 0 }; + + snprintf(buff, sizeof(buff), "STM"); + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "IC_VENDOR"); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void get_chip_name(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[16] = { 0 }; + + if (ts->plat_data->firmware_name) + memcpy(buff, ts->plat_data->firmware_name + 8, 9); + else + snprintf(buff, 10, "FTS"); + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "IC_NAME"); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void run_jitter_test(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + u8 reg[4] = { 0 }; + int ret; + s16 result[2] = { 0 }; + + ts->stm_ts_systemreset(ts, 0); + + // Mutual jitter. + reg[0] = 0xC7; + reg[1] = 0x08; + reg[2] = 0x64; //100 frame + reg[3] = 0x00; + + ret = stm_ts_get_jitter_result(ts, reg, 4, result, STM_TS_EVENT_JITTER_MUTUAL_TEST); + if (ret < 0) { + input_info(true, ts->dev, "%s: failed to read Mutual jitter\n", __func__); + snprintf(buff, sizeof(buff), "NG"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + input_info(true, ts->dev, "%s: Fail %s\n", __func__, buff); + } else { + snprintf(buff, sizeof(buff), "%d", result[1]); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: Mutual max jitter %s\n", __func__, buff); + } + + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "MUTUAL_JITTER"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + + return; +} + +static void run_mutual_jitter(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + u8 reg[4] = { 0 }; + int ret; + s16 result[2] = { 0 }; + + ts->stm_ts_systemreset(ts, 0); + + // Mutual jitter. + reg[0] = 0xC7; + reg[1] = 0x08; + reg[2] = 0x64; //100 frame + reg[3] = 0x00; + + ret = stm_ts_get_jitter_result(ts, reg, 4, result, STM_TS_EVENT_JITTER_MUTUAL_TEST); + if (ret < 0) { + input_info(true, ts->dev, "%s: failed to read Mutual jitter\n", __func__); + snprintf(buff, sizeof(buff), "NG"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s: Fail %s\n", __func__, buff); + } else { + snprintf(buff, sizeof(buff), "%d,%d", result[0], result[1]); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s: %s\n", __func__, buff); + } + + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "MUTUAL_JITTER"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + + return; +} + +static void run_self_jitter(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + u8 reg[4] = { 0 }; + int ret; + s16 result[2] = { 0 }; + + ts->stm_ts_systemreset(ts, 300); + + /* Self jitter */ + reg[0] = 0xC7; + reg[1] = 0x0A; + reg[2] = 0x64; /* 100 frame */ + reg[3] = 0x00; + + ret = stm_ts_get_jitter_result(ts, reg, 4, result, STM_TS_EVENT_JITTER_SELF_TEST); + if (ret < 0) { + input_info(true, ts->dev, "%s: failed to read Self jitter\n", __func__); + snprintf(buff, sizeof(buff), "NG"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s: Fail %s\n", __func__, buff); + } else { + snprintf(buff, sizeof(buff), "%d,%d", result[0], result[1]); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s: %s\n", __func__, buff); + } + + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "SELF_JITTER"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + + return; +} + +static void run_jitter_delta_test(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + u8 reg[4] = { 0 }; + int ret = -1; + s16 result[6] = { 0 }; + + ts->stm_ts_systemreset(ts, 0); + + /* jitter delta + 1000 frame*/ + reg[0] = 0xC7; + reg[1] = 0x08; + reg[2] = 0xE8; + reg[3] = 0x03; + + ret = stm_ts_get_jitter_result(ts, reg, 4, result, STM_TS_EVENT_JITTER_DELTA_TEST); + if (ret < 0) { + input_info(true, ts->dev, "%s: failed to read jitter delta\n", __func__); + snprintf(buff, sizeof(buff), "NG"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + input_info(true, ts->dev, "%s: Fail %s\n", __func__, buff); + } else { + snprintf(buff, sizeof(buff), "%d,%d,%d,%d,%d,%d", + result[0], result[1], result[2], result[3], result[4], result[5]); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); + } + + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) { + char buffer[SEC_CMD_STR_LEN] = { 0 }; + + snprintf(buffer, sizeof(buffer), "%d,%d", result[0], result[1]); + sec_cmd_set_cmd_result_all(sec, buffer, strnlen(buffer, sizeof(buffer)), "JITTER_DELTA_MIN"); + + memset(buffer, 0x00, sizeof(buffer)); + snprintf(buffer, sizeof(buffer), "%d,%d", result[2], result[3]); + sec_cmd_set_cmd_result_all(sec, buffer, strnlen(buffer, sizeof(buffer)), "JITTER_DELTA_MAX"); + + memset(buffer, 0x00, sizeof(buffer)); + snprintf(buffer, sizeof(buffer), "%d,%d", result[4], result[5]); + sec_cmd_set_cmd_result_all(sec, buffer, strnlen(buffer, sizeof(buffer)), "JITTER_DELTA_AVG"); + } + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); +} + +static void run_lcdoff_mutual_jitter(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + u8 reg[4] = { 0 }; + int ret; + s16 mutual_min = 0, mutual_max = 0; + u8 data[STM_TS_EVENT_BUFF_SIZE] = { 0 }; + u8 result = 0; + int retry = 0; + + ts->stm_ts_systemreset(ts, 0); + stm_ts_release_all_finger(ts); + + ret = stm_ts_fix_active_mode(ts, STM_TS_ACTIVE_TRUE); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to set active mode\n", __func__); + goto ERROR; + } + + disable_irq(ts->irq); + mutex_lock(&ts->fn_mutex); + + // lcd off Mutual jitter. + reg[0] = 0xC7; + reg[1] = 0x0D; + reg[2] = 0x64; //100 frame + reg[3] = 0x00; + + ret = ts->stm_ts_write(ts, ®[0], 4, NULL, 0); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to write command\n", __func__); + mutex_unlock(&ts->fn_mutex); + enable_irq(ts->irq); + goto ERROR; + } + + sec_delay(5); + + memset(data, 0x0, STM_TS_EVENT_BUFF_SIZE); + + reg[0] = STM_TS_READ_ONE_EVENT; + while (ts->stm_ts_read(ts, ®[0], 1, data, STM_TS_EVENT_BUFF_SIZE) >= 0) { + if ((data[0] == STM_TS_EVENT_ERROR_REPORT) || (data[0] == STM_TS_EVENT_PASS_REPORT)) { + mutual_max = data[3] << 8 | data[2]; + mutual_min = data[5] << 8 | data[4]; + + if (data[0] == STM_TS_EVENT_PASS_REPORT) { + sec->cmd_state = SEC_CMD_STATUS_OK; + result = 0; + } else { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + result = 1; + } + break; + } + + /* waiting 10 seconds */ + if (retry++ > STM_TS_RETRY_COUNT * 50) { + input_err(true, ts->dev, "%s: Time Over (%02X,%02X,%02X,%02X,%02X,%02X)\n", + __func__, data[0], data[1], data[2], data[3], data[4], data[5]); + break; + } + sec_delay(20); + } + + mutex_unlock(&ts->fn_mutex); + enable_irq(ts->irq); + + ts->stm_ts_systemreset(ts, 0); + stm_ts_set_scanmode(ts, ts->scan_mode); + + snprintf(buff, sizeof(buff), "%d,%d,%d", result, mutual_min, mutual_max); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "LCDOFF_MUTUAL_JITTER"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + + return; + +ERROR: + ts->stm_ts_systemreset(ts, 0); + stm_ts_set_scanmode(ts, ts->scan_mode); + + snprintf(buff, sizeof(buff), "NG"); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "LCDOFF_MUTUAL_JITTER"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; +} + +static void get_wet_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + u8 reg[3] = { 0 }; + u8 data[2] = { 0 }; + int ret; + + reg[0] = 0xC7; + reg[1] = 0x03; + ret = ts->stm_ts_read(ts, ®[0], 2, &data[0], 1); + if (ret < 0) { + input_err(true, ts->dev, "%s: [ERROR] failed to read\n", __func__); + snprintf(buff, sizeof(buff), "%s", "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "WET_MODE"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + snprintf(buff, sizeof(buff), "%d", data[0]); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "WET_MODE"); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void get_x_num(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[16] = { 0 }; + + snprintf(buff, sizeof(buff), "%d", ts->tx_count); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void get_y_num(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[16] = { 0 }; + + snprintf(buff, sizeof(buff), "%d", ts->rx_count); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void get_checksum_data(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[16] = { 0 }; + int ret; + u32 checksum_data; + + ts->stm_ts_systemreset(ts, 0); + + ret = stm_ts_get_sysinfo_data(ts, STM_TS_SI_CONFIG_CHECKSUM, 4, (u8 *)&checksum_data); + if (ret < 0) { + input_err(true, ts->dev, "%s: Get checksum data Read Fail!! [Data : %08X]\n", + __func__, checksum_data); + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + stm_ts_reinit(ts); + + snprintf(buff, sizeof(buff), "%08X", checksum_data); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void check_fw_corruption(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret; + + if (ts->fw_corruption) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + } else { + ret = stm_ts_fw_corruption_check(ts); + if (ret == -STM_TS_ERROR_FW_CORRUPTION || ret == -STM_TS_ERROR_BROKEN_FW) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + } else { + sec->cmd_state = SEC_CMD_STATUS_OK; + stm_ts_reinit(ts); + } + } + ts->fw_corruption = false; + + input_info(true, ts->dev, "%s: %s\n", __func__, sec->cmd_state == SEC_CMD_STATUS_OK ? "OK" : "NG"); +} + +static void run_reference_read(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + short min = 0x7FFF; + short max = 0x8000; + + stm_ts_read_frame(ts, TYPE_BASELINE_DATA, &min, &max); + snprintf(buff, sizeof(buff), "%d,%d", min, max); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void get_reference(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + short val = 0; + int node = 0; + + node = stm_ts_check_index(ts); + if (node < 0) + return; + + val = ts->pFrame[node]; + snprintf(buff, sizeof(buff), "%d", val); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void run_rawcap_read(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + short min = 0x7FFF; + short max = 0x8000; + + stm_ts_read_frame(ts, TYPE_RAW_DATA, &min, &max); + snprintf(buff, sizeof(buff), "%d,%d", min, max); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "RAW_DATA"); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void run_rawcap_read_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + short min = 0x7FFF; + short max = 0x8000; + char *all_strbuff; + int i, j; + + all_strbuff = kzalloc(ts->tx_count * ts->rx_count * 7 + 1, GFP_KERNEL); + if (!all_strbuff) { + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", __func__); + + enter_factory_mode(ts, true); + stm_ts_read_frame(ts, TYPE_RAW_DATA, &min, &max); + + for (j = 0; j < ts->tx_count; j++) { + for (i = 0; i < ts->rx_count; i++) { + snprintf(buff, sizeof(buff), "%d,", ts->pFrame[j * ts->rx_count + i]); + strlcat(all_strbuff, buff, ts->tx_count * ts->rx_count * 7); + } + } + enter_factory_mode(ts, false); + + sec->cmd_state = SEC_CMD_STATUS_OK; + sec_cmd_set_cmd_result(sec, all_strbuff, strlen(all_strbuff)); + input_info(true, ts->dev, "%s: %ld\n", __func__, strlen(all_strbuff)); + kfree(all_strbuff); +} + +static void run_nonsync_rawcap_read_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + short min = 0x7FFF; + short max = 0x8000; + char *all_strbuff; + int i, j; + + all_strbuff = kzalloc(ts->tx_count * ts->rx_count * 7 + 1, GFP_KERNEL); + if (!all_strbuff) { + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", __func__); + stm_ts_read_nonsync_frame(ts, &min, &max); + + for (j = 0; j < ts->tx_count; j++) { + for (i = 0; i < ts->rx_count; i++) { + snprintf(buff, sizeof(buff), "%d,", ts->pFrame[j * ts->rx_count + i]); + strlcat(all_strbuff, buff, ts->tx_count * ts->rx_count * 7); + } + } + enter_factory_mode(ts, false); + + sec->cmd_state = SEC_CMD_STATUS_OK; + sec_cmd_set_cmd_result(sec, all_strbuff, strlen(all_strbuff)); + input_info(true, ts->dev, "%s: %ld\n", __func__, strlen(all_strbuff)); + kfree(all_strbuff); +} + +static void get_rawcap(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + short val = 0; + int node = 0; + + node = stm_ts_check_index(ts); + if (node < 0) + return; + + val = ts->pFrame[node]; + snprintf(buff, sizeof(buff), "%d", val); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void run_lp_single_ended_rawcap_read(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + u8 regr[4] = { 0xA4, 0x0A, 0x01, 0x00 }; + int ret; + short min = 0x7FFF; + short max = 0x8000; + + ts->stm_ts_systemreset(ts, 0); + + /* Request to Prepare single ended raw data from flash */ + + ret = stm_ts_wait_for_echo_event(ts, regr, 4, 0); + if (ret < 0) { + input_err(true, ts->dev, + "%s: timeout, ret: %d\n", __func__, ret); + goto out; + } + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", __func__); + stm_ts_read_nonsync_frame(ts, &min, &max); + + stm_ts_set_scanmode(ts, ts->scan_mode); + + snprintf(buff, sizeof(buff), "%d,%d", min, max); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "MUTUAL_RAW"); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); + + return; + +out: + stm_ts_set_scanmode(ts, ts->scan_mode); + + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "MUTUAL_RAW"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + input_info(true, ts->dev, "%s: fail : %s\n", __func__, buff); + + return; +} + +static void run_lp_single_ended_rawcap_read_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + u8 regr[4] = { 0xA4, 0x0A, 0x01, 0x00 }; + int ret; + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", __func__); + + enter_factory_mode(ts, true); + + stm_ts_set_scanmode(ts, STM_TS_SCAN_MODE_SCAN_OFF); + sec_delay(30); + + /* Request to Prepare single ended raw data from flash */ + + ret = stm_ts_wait_for_echo_event(ts, regr, 4, 0); + if (ret < 0) { + input_err(true, ts->dev, + "%s: timeout, ret: %d\n", __func__, ret); + goto out; + } + + run_nonsync_rawcap_read_all(sec); + + stm_ts_set_scanmode(ts, ts->scan_mode); + + return; + +out: + stm_ts_set_scanmode(ts, ts->scan_mode); + + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; +} + +static void run_low_frequency_rawcap_read(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + u8 reg[4] = { 0xA4, 0x04, 0x00, 0xC0 }; + int ret; + short min = 0x7FFF; + short max = 0x8000; + + stm_ts_set_scanmode(ts, STM_TS_SCAN_MODE_SCAN_OFF); + sec_delay(30); + + /* Request to Prepare Hight Frequency(ITO) raw data from flash */ + + ret = stm_ts_wait_for_echo_event(ts, reg, 4, 30); + if (ret < 0) { + input_err(true, ts->dev, + "%s: timeout, ret: %d\n", __func__, ret); + goto out; + } + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", __func__); + + stm_ts_read_nonsync_frame(ts, &min, &max); + + snprintf(buff, sizeof(buff), "%d,%d", min, max); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) { + char rawcap_buff[SEC_CMD_STR_LEN]; + + snprintf(rawcap_buff, SEC_CMD_STR_LEN, "%d,%d", min, max); + sec_cmd_set_cmd_result_all(sec, rawcap_buff, SEC_CMD_STR_LEN, "LF_RAW_DATA"); + } + + stm_ts_set_scanmode(ts, ts->scan_mode); + + return; + +out: + stm_ts_set_scanmode(ts, ts->scan_mode); + + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; +} + +static void run_low_frequency_rawcap_read_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + u8 reg[4] = { 0xA4, 0x04, 0x00, 0xC0 }; + int ret; + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", __func__); + + enter_factory_mode(ts, true); + + stm_ts_set_scanmode(ts, STM_TS_SCAN_MODE_SCAN_OFF); + sec_delay(30); + + /* Request to Prepare Hight Frequency(ITO) raw data from flash */ + + ret = stm_ts_wait_for_echo_event(ts, reg, 4, 30); + if (ret < 0) { + input_err(true, ts->dev, + "%s: timeout, ret: %d\n", __func__, ret); + goto out; + } + + run_nonsync_rawcap_read_all(sec); + + return; + +out: + stm_ts_set_scanmode(ts, ts->scan_mode); + + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; +} + +static void run_high_frequency_rawcap_read(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + short rawcap[2] = {SHRT_MAX, SHRT_MIN}; /* min, max */ + short rawcap_edge[2] = {SHRT_MAX, SHRT_MIN}; /* min, max */ + u8 reg[4] = { 0xA4, 0x04, 0xFF, 0x01 }; + int ret; + int i, j; + short min = 0x7FFF; + short max = 0x8000; + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", __func__); + + stm_ts_set_scanmode(ts, STM_TS_SCAN_MODE_SCAN_OFF); + sec_delay(30); + + /* Request to Prepare Hight Frequency(ITO) raw data from flash */ + + ret = stm_ts_wait_for_echo_event(ts, reg, 4, 100); + if (ret < 0) { + input_err(true, ts->dev, + "%s: timeout, ret: %d\n", __func__, ret); + goto out; + } + + stm_ts_read_nonsync_frame(ts, &min, &max); + + snprintf(buff, sizeof(buff), "%d,%d", min, max); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + + stm_ts_set_scanmode(ts, ts->scan_mode); + + for (i = 0; i < ts->tx_count; i++) { + for (j = 0; j < ts->rx_count; j++) { + short *rawcap_ptr; + + if (i == 0 || i == ts->tx_count - 1 || j == 0 || j == ts->rx_count - 1) + rawcap_ptr = rawcap_edge; + else + rawcap_ptr = rawcap; + + if (ts->pFrame[(i * ts->rx_count) + j] < rawcap_ptr[0]) + rawcap_ptr[0] = ts->pFrame[(i * ts->rx_count) + j]; + if (ts->pFrame[(i * ts->rx_count) + j] > rawcap_ptr[1]) + rawcap_ptr[1] = ts->pFrame[(i * ts->rx_count) + j]; + } + } + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, + "%s: rawcap:%d,%d rawcap_edge:%d,%d\n", + __func__, rawcap[0], rawcap[1], rawcap_edge[0], rawcap_edge[1]); + + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) { + char rawcap_buff[SEC_CMD_STR_LEN]; + + snprintf(rawcap_buff, SEC_CMD_STR_LEN, "%d,%d", min, max); + sec_cmd_set_cmd_result_all(sec, rawcap_buff, SEC_CMD_STR_LEN, "HF_RAW_DATA"); + snprintf(rawcap_buff, SEC_CMD_STR_LEN, "%d,%d", rawcap[0], rawcap[1]); + sec_cmd_set_cmd_result_all(sec, rawcap_buff, SEC_CMD_STR_LEN, "TSP_RAWCAP"); + snprintf(rawcap_buff, SEC_CMD_STR_LEN, "%d,%d", rawcap_edge[0], rawcap_edge[1]); + sec_cmd_set_cmd_result_all(sec, rawcap_buff, SEC_CMD_STR_LEN, "TSP_RAWCAP_EDGE"); + } + + return; + +out: + stm_ts_set_scanmode(ts, ts->scan_mode); + + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; +} + +static void run_high_frequency_rawcap_read_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + u8 reg[4] = { 0xA4, 0x04, 0xFF, 0x01 }; + int ret; + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", __func__); + + enter_factory_mode(ts, true); + + stm_ts_set_scanmode(ts, STM_TS_SCAN_MODE_SCAN_OFF); + sec_delay(30); + + /* Request to Prepare Hight Frequency(ITO) raw data from flash */ + + ret = stm_ts_wait_for_echo_event(ts, reg, 4, 100); + if (ret < 0) { + input_err(true, ts->dev, + "%s: timeout, ret: %d\n", __func__, ret); + goto out; + } + + run_nonsync_rawcap_read_all(sec); + stm_ts_set_scanmode(ts, ts->scan_mode); + + return; + +out: + stm_ts_set_scanmode(ts, ts->scan_mode); + + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; +} + +static void run_delta_read(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + short min = 0x7FFF; + short max = 0x8000; + + stm_ts_read_frame(ts, TYPE_STRENGTH_DATA, &min, &max); + snprintf(buff, sizeof(buff), "%d,%d", min, max); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void run_prox_intensity_read_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + int ret; + u8 reg[2]; + u8 thd_data[4]; + u8 sum_data[4]; + + memset(thd_data, 0x00, 4); + memset(sum_data, 0x00, 4); + + /* Threshold */ + reg[0] = 0xC7; + reg[1] = 0x0C; + ret = ts->stm_ts_read(ts, ®[0], 2, &thd_data[0], 4); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to read thd_data\n", __func__); + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + /* Sum */ + reg[0] = 0xC7; + reg[1] = 0x0D; + ret = ts->stm_ts_read(ts, ®[0], 2, &sum_data[0], 4); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to read sum_data\n", __func__); + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + snprintf(buff, sizeof(buff), "SUM_X:%d SUM_Y:%d THD_X:%d THD_Y:%d", + (sum_data[0] << 8) + sum_data[1], (sum_data[2] << 8) + sum_data[3], + (thd_data[0] << 8) + thd_data[1], (thd_data[2] << 8) + thd_data[3]); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); + +} + +static void run_cs_raw_read_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + short min = 0x7FFF; + short max = 0x8000; + char *all_strbuff; + short *rdata; + int i, j, k; + + all_strbuff = kzalloc(ts->tx_count * ts->rx_count * 7 + 1, GFP_KERNEL); + if (!all_strbuff) { + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + rdata = (short *)kzalloc(ts->tx_count * ts->rx_count * 2, GFP_KERNEL); + if (!rdata) { + input_err(true, ts->dev, "%s: rdata alloc failed\n", __func__); + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + kfree(all_strbuff); + return; + } + + enter_factory_mode(ts, true); + stm_ts_read_frame(ts, TYPE_RAW_DATA, &min, &max); + + for (i = 0; i < ts->tx_count; i++) { + for (j = 0; j < ts->rx_count; j++) { + rdata[(ts->rx_count - j - 1) * ts->tx_count + i] = ts->pFrame[i * ts->rx_count + j]; + } + } + + k = 0; + for (j = 0; j < ts->rx_count; j++) { + for (i = 0; i < ts->tx_count; i++) { + snprintf(buff, sizeof(buff), "%d,", rdata[k++]); + strlcat(all_strbuff, buff, ts->tx_count * ts->rx_count * 7); + } + } + + k = 0; + for (j = 0; j < ts->rx_count; j++) { + pr_cont("[sec_input]: "); + for (i = 0; i < ts->tx_count; i++) { + pr_cont(" %d", rdata[k++]); + } + pr_cont("\n"); + } + + enter_factory_mode(ts, false); + + stm_ts_reinit(ts); + + sec->cmd_state = SEC_CMD_STATUS_OK; + sec_cmd_set_cmd_result(sec, all_strbuff, strlen(all_strbuff)); + input_info(true, ts->dev, "%s: %ld\n", __func__, strlen(all_strbuff)); + kfree(all_strbuff); + kfree(rdata); +} + +static void run_cs_delta_read_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + short min = 0x7FFF; + short max = 0x8000; + char *all_strbuff; + short *rdata; + int i, j, k; + + all_strbuff = kzalloc(ts->tx_count * ts->rx_count * 7 + 1, GFP_KERNEL); + if (!all_strbuff) { + input_err(true, ts->dev, "%s: alloc failed\n", __func__); + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + rdata = (short *)kzalloc(ts->tx_count * ts->rx_count * 2, GFP_KERNEL); + if (!rdata) { + input_err(true, ts->dev, "%s: rdata alloc failed\n", __func__); + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + kfree(all_strbuff); + return; + } + + stm_ts_read_frame(ts, TYPE_STRENGTH_DATA, &min, &max); + + for (i = 0; i < ts->tx_count; i++) { + for (j = 0; j < ts->rx_count; j++) { + rdata[(ts->rx_count - j - 1) * ts->tx_count + i] = ts->pFrame[i * ts->rx_count + j]; + } + } + + k = 0; + for (j = 0; j < ts->rx_count; j++) { + for (i = 0; i < ts->tx_count; i++) { + snprintf(buff, sizeof(buff), "%d,", rdata[k++]); + strlcat(all_strbuff, buff, ts->tx_count * ts->rx_count * 7); + } + } + + k = 0; + for (j = 0; j < ts->rx_count; j++) { + pr_cont("[sec_input]: "); + for (i = 0; i < ts->tx_count; i++) { + pr_cont(" %d", rdata[k++]); + } + pr_cont("\n"); + } + + sec->cmd_state = SEC_CMD_STATUS_OK; + + sec_cmd_set_cmd_result(sec, all_strbuff, strlen(all_strbuff)); + input_info(true, ts->dev, "%s: %ld\n", __func__, strlen(all_strbuff)); + kfree(all_strbuff); + kfree(rdata); +} + +static void get_strength_all_data(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + short min = 0x7FFF; + short max = 0x8000; + char *all_strbuff; + int i, j; + + all_strbuff = kzalloc(ts->tx_count * ts->rx_count * 7 + 1, GFP_KERNEL); + if (!all_strbuff) { + input_err(true, ts->dev, "%s: alloc failed\n", __func__); + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + stm_ts_read_frame(ts, TYPE_STRENGTH_DATA, &min, &max); + + for (i = 0; i < ts->tx_count; i++) { + for (j = 0; j < ts->rx_count; j++) { + snprintf(buff, sizeof(buff), "%d,", ts->pFrame[(i * ts->rx_count) + j]); + strlcat(all_strbuff, buff, ts->tx_count * ts->rx_count * 7); + } + } + + sec->cmd_state = SEC_CMD_STATUS_OK; + + sec_cmd_set_cmd_result(sec, all_strbuff, strlen(all_strbuff)); + input_info(true, ts->dev, "%s: %ld\n", __func__, strlen(all_strbuff)); + kfree(all_strbuff); +} + +static void get_delta(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + short val = 0; + int node = 0; + + node = stm_ts_check_index(ts); + if (node < 0) + return; + + val = ts->pFrame[node]; + snprintf(buff, sizeof(buff), "%d", val); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void stm_ts_read_self_raw_frame(struct stm_ts_data *ts, bool allnode) +{ + struct sec_cmd_data *sec = &ts->sec; + char buff[SEC_CMD_STR_LEN] = { 0 }; + struct stm_ts_syncframeheader *psyncframeheader; + u8 reg[STM_TS_EVENT_BUFF_SIZE] = {0}; + u8 *data; + s16 self_force_raw_data[100]; + s16 self_sense_raw_data[100]; + int Offset = 0; + u8 count = 0; + int i; + int ret; + int totalbytes; + int retry = 10; + s16 min_tx_self_raw_data = S16_MAX; + s16 max_tx_self_raw_data = S16_MIN; + s16 min_rx_self_raw_data = S16_MAX; + s16 max_rx_self_raw_data = S16_MIN; + + data = kzalloc((ts->tx_count + ts->rx_count) * 2 + 1, GFP_KERNEL); + if (!data) + return; + + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: [ERROR] Touch is stopped\n", + __func__); + snprintf(buff, sizeof(buff), "NG"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto out; + } + + // Request Data Type + reg[0] = 0xA4; + reg[1] = 0x06; + reg[2] = TYPE_RAW_DATA; + ts->stm_ts_write(ts, ®[0], 3, NULL, 0); + + do { + reg[0] = STM_TS_CMD_FRM_BUFF_R; + reg[1] = 0x00; + reg[2] = 0x00; + ret = ts->stm_ts_read(ts, ®[0], 3, &data[0], STM_TS_COMP_DATA_HEADER_SIZE); + if (ret < 0) { + input_err(true, ts->dev, "%s: read failed ret = %d\n", __func__, ret); + snprintf(buff, sizeof(buff), "NG"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto out; + } + + psyncframeheader = (struct stm_ts_syncframeheader *) &data[0]; + + if ((psyncframeheader->header == 0xA5) && (psyncframeheader->host_data_mem_id == TYPE_RAW_DATA)) + break; + + sec_delay(100); + } while (retry--); + + if (retry == 0) { + input_err(true, ts->dev, "%s: didn't match header or id. header = %02X, id = %02X\n", + __func__, psyncframeheader->header, psyncframeheader->host_data_mem_id); + snprintf(buff, sizeof(buff), "NG"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto out; + } + + Offset = STM_TS_COMP_DATA_HEADER_SIZE + psyncframeheader->dbg_frm_len + + (psyncframeheader->ms_force_len * psyncframeheader->ms_sense_len * 2); + + totalbytes = (psyncframeheader->ss_force_len + psyncframeheader->ss_sense_len) * 2; + + reg[0] = STM_TS_CMD_FRM_BUFF_R; + reg[1] = (u8)(Offset >> 8); + reg[2] = (u8)(Offset & 0xFF); + ret = ts->stm_ts_read(ts, ®[0], 3, &data[0], totalbytes); + if (ret < 0) { + input_err(true, ts->dev, "%s: read failed ret = %d\n", __func__, ret); + snprintf(buff, sizeof(buff), "NG"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto out; + } + + Offset = 0; + for (count = 0; count < (ts->tx_count); count++) { + self_force_raw_data[count] = (s16)(data[count * 2 + Offset] + (data[count * 2 + 1 + Offset] << 8)); + + /* Only for YOCTA */ + if ((count == 0) && STM_TS_IS_EXCEPTIONAL_CHIP(ts->chip_id)) + continue; + + if (max_tx_self_raw_data < self_force_raw_data[count]) + max_tx_self_raw_data = self_force_raw_data[count]; + if (min_tx_self_raw_data > self_force_raw_data[count]) + min_tx_self_raw_data = self_force_raw_data[count]; + } + + Offset = (ts->tx_count * 2); + for (count = 0; count < ts->rx_count; count++) { + self_sense_raw_data[count] = (s16)(data[count * 2 + Offset] + (data[count * 2 + 1 + Offset] << 8)); + + /* Only for YOCTA */ + if ((count == 0) && STM_TS_IS_EXCEPTIONAL_CHIP(ts->chip_id)) + continue; + + if (max_rx_self_raw_data < self_sense_raw_data[count]) + max_rx_self_raw_data = self_sense_raw_data[count]; + if (min_rx_self_raw_data > self_sense_raw_data[count]) + min_rx_self_raw_data = self_sense_raw_data[count]; + } + + stm_ts_print_channel(ts, self_force_raw_data, self_sense_raw_data); + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s: MIN_TX_SELF_RAW: %d MAX_TX_SELF_RAW : %d\n", + __func__, (s16)min_tx_self_raw_data, (s16)max_tx_self_raw_data); + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s: MIN_RX_SELF_RAW : %d MIN_RX_SELF_RAW : %d\n", + __func__, (s16)min_rx_self_raw_data, (s16)max_rx_self_raw_data); + + if (allnode == true) { + char *mbuff; + char temp[10] = { 0 }; + + mbuff = kzalloc((ts->tx_count + ts->rx_count + 2) * 10, GFP_KERNEL); + if (!mbuff) + goto out; + + for (i = 0; i < (ts->tx_count); i++) { + snprintf(temp, sizeof(temp), "%d,", (s16)self_force_raw_data[i]); + strlcat(mbuff, temp, sizeof(mbuff)); + } + for (i = 0; i < (ts->rx_count); i++) { + snprintf(temp, sizeof(temp), "%d,", (s16)self_sense_raw_data[i]); + strlcat(mbuff, temp, sizeof(mbuff)); + } + + sec_cmd_set_cmd_result(sec, mbuff, sizeof(mbuff)); + sec->cmd_state = SEC_CMD_STATUS_OK; + kfree(mbuff); + return; + } + + snprintf(buff, sizeof(buff), "%d,%d,%d,%d", + (s16)min_tx_self_raw_data, (s16)max_tx_self_raw_data, + (s16)min_rx_self_raw_data, (s16)max_rx_self_raw_data); + sec->cmd_state = SEC_CMD_STATUS_OK; +out: + kfree(data); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) { + char ret_buff[SEC_CMD_STR_LEN] = { 0 }; + + snprintf(ret_buff, sizeof(ret_buff), "%d,%d", (s16)min_rx_self_raw_data, (s16)max_rx_self_raw_data); + sec_cmd_set_cmd_result_all(sec, ret_buff, strnlen(ret_buff, sizeof(ret_buff)), "SELF_RAW_RX"); + snprintf(ret_buff, sizeof(ret_buff), "%d,%d", (s16)min_tx_self_raw_data, (s16)max_tx_self_raw_data); + sec_cmd_set_cmd_result_all(sec, ret_buff, strnlen(ret_buff, sizeof(ret_buff)), "SELF_RAW_TX"); + } + sec_cmd_set_cmd_result(sec, &buff[0], strnlen(buff, sizeof(buff))); + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void run_self_raw_read(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", __func__); + stm_ts_read_self_raw_frame(ts, false); +} + +static void get_cx_data(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + short val = 0; + int node = 0; + + node = stm_ts_check_index(ts); + if (node < 0) + return; + + if (ts->cx_data) + val = ts->cx_data[node]; + snprintf(buff, sizeof(buff), "%d", val); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); + +} + +static int read_ms_cx_data(struct stm_ts_data *ts, u8 active, s8 *cx_min, s8 *cx_max) +{ + s8 *rdata = NULL; + u8 cdata[STM_TS_COMP_DATA_HEADER_SIZE]; + u8 reg[STM_TS_EVENT_BUFF_SIZE] = { 0 }; + u8 dataID = 0; + u16 comp_start_addr; + int i, j, ret = 0; + u8 *pStr = NULL; + u8 pTmp[16] = { 0 }; + + ts->stm_ts_command(ts, STM_TS_CMD_CLEAR_ALL_EVENT, true); // Clear FIFO + stm_ts_release_all_finger(ts); + + sec_delay(20); + + // Request compensation data type + if (active == NORMAL_CX2) + dataID = 0x11; // MS - LP + else if (active == ACTIVE_CX2) + dataID = 0x10; // MS - ACTIVE + + reg[0] = 0xA4; + reg[1] = 0x06; + reg[2] = dataID; + + ret = stm_ts_wait_for_echo_event(ts, ®[0], 3, 0); + if (ret < 0) + return ret; + + // Read Header + reg[0] = STM_TS_CMD_FRM_BUFF_R; + reg[1] = 0x00; + reg[2] = 0x00; + ret = ts->stm_ts_read(ts, ®[0], 3, &cdata[0], STM_TS_COMP_DATA_HEADER_SIZE); + if (ret < 0) + return ret; + + if ((cdata[0] != 0xA5) && (cdata[1] != dataID)) { + input_info(true, ts->dev, "%s: failed to read signature data of header.\n", __func__); + ret = -EIO; + return ret; + } + + rdata = kzalloc(ts->rx_count * ts->tx_count + 1, GFP_KERNEL); + if (!rdata) + return -ENOMEM; + + comp_start_addr = (u16)STM_TS_COMP_DATA_HEADER_SIZE; + reg[0] = STM_TS_CMD_FRM_BUFF_R; + reg[1] = (u8)(comp_start_addr >> 8); + reg[2] = (u8)(comp_start_addr & 0xFF); + ret = ts->stm_ts_read(ts, ®[0], 3, &rdata[0], ts->tx_count * ts->rx_count); + if (ret < 0) + goto out; + + pStr = kzalloc(7 * (ts->rx_count + 1), GFP_KERNEL); + if (!pStr) { + ret = -ENOMEM; + goto out; + } + + *cx_min = *cx_max = rdata[0]; + for (i = 0; i < ts->tx_count; i++) { + memset(pStr, 0x0, 7 * (ts->rx_count + 1)); + snprintf(pTmp, sizeof(pTmp), "Tx%02d | ", i); + strlcat(pStr, pTmp, 7 * (ts->rx_count + 1)); + + for (j = 0; j < ts->rx_count; j++) { + snprintf(pTmp, sizeof(pTmp), "%4d", rdata[i * ts->rx_count + j]); + strlcat(pStr, pTmp, 7 * (ts->rx_count + 1)); + + *cx_min = min(*cx_min, rdata[i * ts->rx_count + j]); + *cx_max = max(*cx_max, rdata[i * ts->rx_count + j]); + } + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", pStr); + } + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "cx min:%d, cx max:%d\n", *cx_min, *cx_max); + + /* ts->cx_data length: Force * Sense */ + if (ts->cx_data && active == NORMAL_CX2) + memcpy(&ts->cx_data[0], &rdata[0], ts->tx_count * ts->rx_count); + + /* for get_gap_data_all */ + for (i = 0; i < ts->tx_count; i++) { + for (j = 0; j < ts->rx_count; j++) { + ts->pFrame[i * ts->rx_count + j] = rdata[i * ts->rx_count + j]; + } + } + + kfree(pStr); +out: + kfree(rdata); + return ret; +} + +static void run_active_cx_data_read(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + char buff_minmax[SEC_CMD_STR_LEN] = { 0 }; + int ret; + s8 cx_min, cx_max; + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s: start\n", __func__); + + ret = read_ms_cx_data(ts, ACTIVE_CX2, &cx_min, &cx_max); + if (ret < 0) { + snprintf(buff, sizeof(buff), "NG"); + snprintf(buff_minmax, sizeof(buff_minmax), "NG"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + } else { + snprintf(buff_minmax, sizeof(buff_minmax), "%d,%d", cx_min, cx_max); + snprintf(buff, sizeof(buff), "OK"); + sec->cmd_state = SEC_CMD_STATUS_OK; + } + + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff_minmax, strnlen(buff_minmax, sizeof(buff_minmax)), "ACTIVE_CX2_DATA"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void get_cx_gap_data(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + int rx_max = 0, tx_max = 0, ii; + + for (ii = 0; ii < (ts->rx_count * ts->tx_count); ii++) { + /* rx(x) gap max */ + if ((ii + 1) % (ts->rx_count) != 0) + rx_max = max(rx_max, (int)abs(ts->cx_data[ii + 1] - ts->cx_data[ii])); + + /* tx(y) gap max */ + if (ii < (ts->tx_count - 1) * ts->rx_count) + tx_max = max(tx_max, (int)abs(ts->cx_data[ii + ts->rx_count] - ts->cx_data[ii])); + } + + input_raw_info(true, ts->dev, "%s: rx max:%d, tx max:%d\n", __func__, rx_max, tx_max); + + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) { + snprintf(buff, sizeof(buff), "%d,%d", 0, rx_max); + sec_cmd_set_cmd_result_all(sec, buff, SEC_CMD_STR_LEN, "CX2_GAP_RX"); + snprintf(buff, sizeof(buff), "%d,%d", 0, tx_max); + sec_cmd_set_cmd_result_all(sec, buff, SEC_CMD_STR_LEN, "CX2_GAP_TX"); + } +} + +static void run_cx_gap_data_x_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char *buff = NULL; + int ii; + char temp[5] = { 0 }; + + buff = kzalloc(ts->tx_count * ts->rx_count * 5, GFP_KERNEL); + if (!buff) { + snprintf(temp, sizeof(temp), "NG"); + sec_cmd_set_cmd_result(sec, temp, strnlen(temp, sizeof(temp))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + for (ii = 0; ii < (ts->rx_count * ts->tx_count); ii++) { + if ((ii + 1) % (ts->rx_count) != 0) { + snprintf(temp, sizeof(temp), "%d,", (int)abs(ts->cx_data[ii + 1] - ts->cx_data[ii])); + strlcat(buff, temp, ts->tx_count * ts->rx_count * 5); + memset(temp, 0x00, 5); + } + } + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, ts->tx_count * ts->rx_count * 5)); + sec->cmd_state = SEC_CMD_STATUS_OK; + kfree(buff); +} + +static void run_cx_gap_data_y_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char *buff = NULL; + int ii; + char temp[5] = { 0 }; + + buff = kzalloc(ts->tx_count * ts->rx_count * 5, GFP_KERNEL); + if (!buff) { + snprintf(temp, sizeof(temp), "NG"); + sec_cmd_set_cmd_result(sec, temp, strnlen(temp, sizeof(temp))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + for (ii = 0; ii < (ts->rx_count * ts->tx_count); ii++) { + if (ii < (ts->tx_count - 1) * ts->rx_count) { + snprintf(temp, sizeof(temp), "%d,", + (int)abs(ts->cx_data[ii + ts->rx_count] - ts->cx_data[ii])); + strlcat(buff, temp, ts->tx_count * ts->rx_count * 5); + memset(temp, 0x00, 5); + } + } + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, ts->tx_count * ts->rx_count * 5)); + sec->cmd_state = SEC_CMD_STATUS_OK; + kfree(buff); +} + +#define GAP_MULTIPLE_VAL 100 +static void get_gap_data_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char *buff = NULL; + int ii; + char temp[6] = { 0 }; + int node_gap_tx = 0; + int node_gap_rx = 0; + int node_gap_max = 0; + + buff = kzalloc(ts->tx_count * ts->rx_count * 6, GFP_KERNEL); + if (!buff) { + snprintf(temp, sizeof(temp), "NG"); + sec_cmd_set_cmd_result(sec, temp, strnlen(temp, sizeof(temp))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + for (ii = 0; ii < (ts->rx_count * ts->tx_count); ii++) { + node_gap_tx = node_gap_rx = node_gap_max = 0; + + /* rx(x) gap max */ + if ((ii + 1) % (ts->rx_count) != 0) + node_gap_tx = 1 * GAP_MULTIPLE_VAL - (min(ts->pFrame[ii], ts->pFrame[ii + 1]) * GAP_MULTIPLE_VAL) / (max(ts->pFrame[ii], ts->pFrame[ii + 1])); + + /* tx(y) gap max */ + if (ii < (ts->tx_count - 1) * ts->rx_count) + node_gap_rx = 1 * GAP_MULTIPLE_VAL - (min(ts->pFrame[ii], ts->pFrame[ii + ts->rx_count]) * GAP_MULTIPLE_VAL) / (max(ts->pFrame[ii], ts->pFrame[ii + ts->rx_count])); + + node_gap_max = max(node_gap_tx, node_gap_rx); + + snprintf(temp, sizeof(temp), "%d,", node_gap_max); + strlcat(buff, temp, ts->tx_count * ts->rx_count * 6); + memset(temp, 0x00, 6); + } + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, ts->tx_count * ts->rx_count * 5)); + sec->cmd_state = SEC_CMD_STATUS_OK; + kfree(buff); +} + +static void run_cx_data_read(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + char buff_minmax[SEC_CMD_STR_LEN] = { 0 }; + int ret; + s8 cx_min, cx_max; + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s: start\n", __func__); + + ret = read_ms_cx_data(ts, NORMAL_CX2, &cx_min, &cx_max); + if (ret < 0) { + snprintf(buff, sizeof(buff), "NG"); + snprintf(buff_minmax, sizeof(buff_minmax), "NG"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + } else { + snprintf(buff_minmax, sizeof(buff_minmax), "%d,%d", cx_min, cx_max); + snprintf(buff, sizeof(buff), "OK"); + sec->cmd_state = SEC_CMD_STATUS_OK; + } + + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff_minmax, strnlen(buff_minmax, sizeof(buff_minmax)), "CX2_DATA"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void run_cx_data_read_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + int ret, i, j; + s8 cx_min, cx_max; + char *all_strbuff; + + input_info(true, ts->dev, "%s: start\n", __func__); + + ret = read_ms_cx_data(ts, NORMAL_CX2, &cx_min, &cx_max); + + enter_factory_mode(ts, false); + + if (ret < 0) { + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + all_strbuff = kzalloc(ts->tx_count * ts->rx_count * 4 + 1, GFP_KERNEL); + if (!all_strbuff) { + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + /* Read compensation data */ + if (ts->cx_data) { + for (j = 0; j < ts->tx_count; j++) { + for (i = 0; i < ts->rx_count; i++) { + snprintf(buff, sizeof(buff), "%d,", ts->cx_data[j * ts->rx_count + i]); + strlcat(all_strbuff, buff, ts->tx_count * ts->rx_count * 4 + 1); + } + } + } + + sec->cmd_state = SEC_CMD_STATUS_OK; + sec_cmd_set_cmd_result(sec, all_strbuff, strlen(all_strbuff)); + input_info(true, ts->dev, "%s: %ld\n", __func__, strlen(all_strbuff)); + kfree(all_strbuff); +} + +static void stm_ts_read_ix_data(struct stm_ts_data *ts, bool allnode) +{ + struct sec_cmd_data *sec = &ts->sec; + char buff[SEC_CMD_STR_LEN] = { 0 }; + int ret; + u16 max_tx_ix_sum = 0; + u16 min_tx_ix_sum = 0xFFFF; + u16 max_rx_ix_sum = 0; + u16 min_rx_ix_sum = 0xFFFF; + u8 *data; + u8 reg[STM_TS_EVENT_BUFF_SIZE]; + u8 dataID; + u16 force_ix_data[100]; + u16 sense_ix_data[100]; + int buff_size, j; + char *mbuff = NULL; + int num, n, a, fzero; + char cnum; + int i = 0; + u16 comp_start_tx_addr, comp_start_rx_addr; + unsigned int rx_num, tx_num; + + data = kzalloc((ts->tx_count + ts->rx_count) * 2 + 1, GFP_KERNEL); + if (!data) + return; + + ts->stm_ts_command(ts, STM_TS_CMD_CLEAR_ALL_EVENT, true); // Clear FIFO + + stm_ts_release_all_finger(ts); + + // Request compensation data type + dataID = 0x52; + reg[0] = 0xA4; + reg[1] = 0x06; + reg[2] = dataID; // SS - CX total + + ret = stm_ts_wait_for_echo_event(ts, ®[0], 3, 0); + if (ret < 0) { + snprintf(buff, sizeof(buff), "NG"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto out; + } + + // Read Header + reg[0] = STM_TS_CMD_FRM_BUFF_R; + reg[1] = 0x00; + reg[2] = 0x00; + ts->stm_ts_read(ts, ®[0], 3, &data[0], STM_TS_COMP_DATA_HEADER_SIZE); + + if ((data[0] != 0xA5) && (data[1] != dataID)) { + snprintf(buff, sizeof(buff), "NG"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto out; + } + + tx_num = data[4]; + rx_num = data[5]; + + /* Read TX IX data */ + comp_start_tx_addr = (u16)STM_TS_COMP_DATA_HEADER_SIZE; + reg[0] = STM_TS_CMD_FRM_BUFF_R; + reg[1] = (u8)(comp_start_tx_addr >> 8); + reg[2] = (u8)(comp_start_tx_addr & 0xFF); + ts->stm_ts_read(ts, ®[0], 3, &data[0], tx_num * 2); + + for (i = 0; i < tx_num; i++) { + force_ix_data[i] = data[2 * i] | data[2 * i + 1] << 8; + + if (max_tx_ix_sum < force_ix_data[i]) + max_tx_ix_sum = force_ix_data[i]; + if (min_tx_ix_sum > force_ix_data[i]) + min_tx_ix_sum = force_ix_data[i]; + + } + + /* Read RX IX data */ + comp_start_rx_addr = (u16)(STM_TS_COMP_DATA_HEADER_SIZE + (tx_num * 2)); + reg[0] = STM_TS_CMD_FRM_BUFF_R; + reg[1] = (u8)(comp_start_rx_addr >> 8); + reg[2] = (u8)(comp_start_rx_addr & 0xFF); + ts->stm_ts_read(ts, ®[0], 3, &data[0], rx_num * 2); + + for (i = 0; i < rx_num; i++) { + sense_ix_data[i] = data[2 * i] | data[2 * i + 1] << 8; + + if (max_rx_ix_sum < sense_ix_data[i]) + max_rx_ix_sum = sense_ix_data[i]; + if (min_rx_ix_sum > sense_ix_data[i]) + min_rx_ix_sum = sense_ix_data[i]; + } + + stm_ts_print_channel(ts, force_ix_data, sense_ix_data); + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s: MIN_TX_IX_SUM : %d MAX_TX_IX_SUM : %d\n", + __func__, min_tx_ix_sum, max_tx_ix_sum); + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s: MIN_RX_IX_SUM : %d MAX_RX_IX_SUM : %d\n", + __func__, min_rx_ix_sum, max_rx_ix_sum); + + if (allnode == true) { + buff_size = (ts->tx_count + ts->rx_count + 2) * 5; + mbuff = kzalloc(buff_size, GFP_KERNEL); + } + if (mbuff != NULL) { + char *pBuf = mbuff; + + for (i = 0; i < ts->tx_count; i++) { + num = force_ix_data[i]; + n = 100000; + fzero = 0; + for (j = 5; j > 0; j--) { + n = n / 10; + a = num / n; + if (a) + fzero = 1; + cnum = a + '0'; + num = num - a*n; + if (fzero) + *pBuf++ = cnum; + } + if (!fzero) + *pBuf++ = '0'; + *pBuf++ = ','; + } + for (i = 0; i < ts->rx_count; i++) { + num = sense_ix_data[i]; + n = 100000; + fzero = 0; + for (j = 5; j > 0; j--) { + n = n / 10; + a = num / n; + if (a) + fzero = 1; + cnum = a + '0'; + num = num - a * n; + if (fzero) + *pBuf++ = cnum; + } + if (!fzero) + *pBuf++ = '0'; + if (i < (ts->rx_count - 1)) + *pBuf++ = ','; + } + + sec_cmd_set_cmd_result(sec, mbuff, buff_size); + sec->cmd_state = SEC_CMD_STATUS_OK; + kfree(mbuff); + return; + } + + if (allnode == true) { + snprintf(buff, sizeof(buff), "NG"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + } else { + snprintf(buff, sizeof(buff), "%d,%d,%d,%d", + min_tx_ix_sum, max_tx_ix_sum, min_rx_ix_sum, max_rx_ix_sum); + sec->cmd_state = SEC_CMD_STATUS_OK; + } + +out: + kfree(data); + + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) { + char ret_buff[SEC_CMD_STR_LEN] = { 0 }; + + snprintf(ret_buff, sizeof(ret_buff), "%d,%d", min_rx_ix_sum, max_rx_ix_sum); + sec_cmd_set_cmd_result_all(sec, ret_buff, strnlen(ret_buff, sizeof(ret_buff)), "IX_DATA_RX"); + snprintf(ret_buff, sizeof(ret_buff), "%d,%d", min_tx_ix_sum, max_tx_ix_sum); + sec_cmd_set_cmd_result_all(sec, ret_buff, strnlen(ret_buff, sizeof(ret_buff)), "IX_DATA_TX"); + } + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void run_ix_data_read(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", __func__); + stm_ts_read_ix_data(ts, false); +} + +static void run_ix_data_read_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + enter_factory_mode(ts, true); + stm_ts_read_ix_data(ts, true); + enter_factory_mode(ts, false); +} + +static void run_self_raw_read_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + enter_factory_mode(ts, true); + stm_ts_read_self_raw_frame(ts, true); + enter_factory_mode(ts, false); +} + +static void run_rawdata_read_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + + input_raw_data_clear(); + + ts->tsp_dump_lock = true; + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, + "%s: start (noise:%d, wet:%d, tc:%d)##\n", + __func__, atomic_read(&ts->plat_data->touch_noise_status), ts->plat_data->wet_mode, + ts->plat_data->touch_count); + + run_high_frequency_rawcap_read(sec); +#if 0 // this function is no longer support in Samsung + run_low_frequency_rawcap_read(sec); +#endif + run_rawcap_read(sec); + run_self_raw_read(sec); + stm_ts_checking_miscal(ts); + run_cx_data_read(sec); + run_ix_data_read(sec); + stm_ts_panel_ito_test(ts, OPEN_SHORT_CRACK_TEST); + run_mutual_jitter(sec); + + ts->tsp_dump_lock = false; + + sec->cmd_state = SEC_CMD_STATUS_OK; + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s: %s\n", __func__, buff); +} + +void stm_ts_run_rawdata_all(struct stm_ts_data *ts) +{ + struct sec_cmd_data *sec = &ts->sec; + + run_rawdata_read_all(sec); +} + +static void run_trx_short_test(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + int result = 0; + int type = 0; + char test[32]; + + if (sec->cmd_param[0] == 1 && sec->cmd_param[1] == 0) { + input_err(true, ts->dev, + "%s: %s: seperate cm1 test open / short test result\n", __func__, buff); + + snprintf(buff, sizeof(buff), "%s", "CONT"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + return; + } + + if (sec->cmd_param[0] == 1 && sec->cmd_param[1] == 1) { + type = OPEN_TEST; + } else if (sec->cmd_param[0] == 1 && sec->cmd_param[1] == 2) { + type = SHORT_TEST; + } else { + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + input_info(true, ts->dev, "%s : not supported test case\n", __func__); + return; + } + + input_info(true, ts->dev, "%s : CM%d factory_position[%d]\n", + __func__, sec->cmd_param[0], ts->factory_position); + + /* Prevent F3 12 Error */ + stm_ts_systemreset(ts, 0); + stm_ts_set_hsync_scanmode(ts, STM_TS_CMD_LPM_ASYNC_SCAN); + + ts->stm_ts_command(ts, STM_TS_CMD_CLEAR_ALL_EVENT, true); // Clear FIFO + stm_ts_release_all_finger(ts); + + result = stm_ts_panel_test_result(ts, type); + + /* reinit */ + stm_ts_reinit(ts); + + ts->plat_data->touch_count = 0; + + if (sec->cmd_param[1]) + snprintf(test, sizeof(test), "TEST=%d,%d", sec->cmd_param[0], sec->cmd_param[1]); + else + snprintf(test, sizeof(test), "TEST=%d", sec->cmd_param[0]); + + if (result == 0) + sec_cmd_send_event_to_user(sec, test, "RESULT=PASS"); + else + sec_cmd_send_event_to_user(sec, test, "RESULT=FAIL"); + + input_info(true, ts->dev, "%s : test done\n", __func__); +} + +#ifdef TCLM_CONCEPT +static void get_pat_information(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[50] = { 0 }; + +#ifdef CONFIG_SEC_FACTORY + if (ts->factory_position == 1) { + sec_tclm_initialize(ts->tdata); + stm_tclm_data_read(stm_ts_get_client_dev(ts), SEC_TCLM_NVM_ALL_DATA); + } +#endif + /* fixed tune version will be saved at excute autotune */ + snprintf(buff, sizeof(buff), "C%02XT%04X.%4s%s%c%d%c%d%c%d", + ts->tdata->nvdata.cal_count, ts->tdata->nvdata.tune_fix_ver, + ts->tdata->tclm_string[ts->tdata->nvdata.cal_position].f_name, + (ts->tdata->tclm_level == TCLM_LEVEL_LOCKDOWN) ? ".L " : " ", + ts->tdata->cal_pos_hist_last3[0], ts->tdata->cal_pos_hist_last3[1], + ts->tdata->cal_pos_hist_last3[2], ts->tdata->cal_pos_hist_last3[3], + ts->tdata->cal_pos_hist_last3[4], ts->tdata->cal_pos_hist_last3[5]); + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void set_external_factory(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + ts->tdata->external_factory = true; + sec->cmd_state = SEC_CMD_STATUS_OK; +} + +static void tclm_test_cmd(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + struct sec_tclm_data *data = ts->tdata; + char buff[SEC_CMD_STR_LEN] = { 0 }; + int ret = 0; + + if (!ts->tdata->support_tclm_test) + goto not_support; + + ret = tclm_test_command(data, sec->cmd_param[0], sec->cmd_param[1], sec->cmd_param[2], buff); + if (ret < 0) + sec->cmd_state = SEC_CMD_STATUS_FAIL; + else + sec->cmd_state = SEC_CMD_STATUS_OK; + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + return; + +not_support: + snprintf(buff, sizeof(buff), "%s", "NA"); + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); +} + +static void get_calibration(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + + if (!ts->tdata->support_tclm_test) + goto not_support; + + snprintf(buff, sizeof(buff), "%d", ts->is_cal_done); + + ts->is_cal_done = false; + sec->cmd_state = SEC_CMD_STATUS_OK; + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + return; + +not_support: + snprintf(buff, sizeof(buff), "%s", "NA"); + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); +} +#endif + +static void run_factory_miscalibration(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN]; + char data[STM_TS_EVENT_BUFF_SIZE]; + char echo; + int ret; + int retry = 200; + short min = SHRT_MIN, max = SHRT_MAX; + + disable_irq(ts->irq); + + memset(data, 0x00, sizeof(data)); + memset(buff, 0x00, sizeof(buff)); + + data[0] = 0xC7; + data[1] = 0x02; + ret = ts->stm_ts_write(ts, data, 2, NULL, 0); + if (ret < 0) { + input_err(true, ts->dev, "%s: write failed: %d\n", __func__, ret); + goto error; + } + + sec_delay(200); + + /* maximum timeout 2sec ? */ + while (retry-- >= 0) { + memset(data, 0x00, sizeof(data)); + echo = STM_TS_READ_ONE_EVENT; + ret = ts->stm_ts_read(ts, &echo, 1, data, STM_TS_EVENT_BUFF_SIZE); + if (ret < 0) { + input_err(true, ts->dev, "%s: read failed: %d\n", __func__, ret); + goto error; + } + + input_info(true, ts->dev, "%s: %02X %02X %02X %02X %02X %02X %02X %02X\n", + __func__, data[0], data[1], data[2], data[3], + data[4], data[5], data[6], data[7]); + sec_delay(10); + + if (data[0] == 0x03 || data[0] == 0xF3) { + max = data[3] << 8 | data[2]; + min = data[5] << 8 | data[4]; + + break; + } + if (retry == 0) + goto error; + } + + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) { + snprintf(buff, sizeof(buff), "%d,%d", min, max); + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "MIS_CAL"); + } else { + if (data[0] == 0x03) { + snprintf(buff, sizeof(buff), "OK,%d,%d", min, max); + } else { + snprintf(buff, sizeof(buff), "NG,%d,%d", min, max); + } + } + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + stm_ts_reinit(ts); + enable_irq(ts->irq); + return; + +error: + snprintf(buff, sizeof(buff), "NG"); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "MIS_CAL"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + stm_ts_reinit(ts); + enable_irq(ts->irq); + +} + +static void run_factory_miscalibration_read_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + u8 reg[3] = { 0 }; + short min = 0x7FFF; + short max = 0x8000; + char *all_strbuff; + int i, j; + + all_strbuff = kzalloc(ts->tx_count * ts->rx_count * 7 + 1, GFP_KERNEL); + if (!all_strbuff) { + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + ts->stm_ts_systemreset(ts, 0); + stm_ts_command(ts, STM_TS_CMD_CLEAR_ALL_EVENT, true); + + stm_ts_release_all_finger(ts); + + /* get the raw data after C7 02 : mis cal test */ + reg[0] = 0xC7; + reg[1] = 0x02; + + ts->stm_ts_write(ts, ®[0], 2, NULL, 0); + sec_delay(300); + stm_ts_read_nonsync_frame(ts, &min, &max); + + stm_ts_set_scanmode(ts, ts->scan_mode); + + for (j = 0; j < ts->tx_count; j++) { + for (i = 0; i < ts->rx_count; i++) { + snprintf(buff, sizeof(buff), "%d,", ts->pFrame[j * ts->rx_count + i]); + strlcat(all_strbuff, buff, ts->tx_count * ts->rx_count * 7); + } + } + + sec->cmd_state = SEC_CMD_STATUS_OK; + sec_cmd_set_cmd_result(sec, all_strbuff, strlen(all_strbuff)); + input_info(true, ts->dev, "%s: %ld\n", __func__, strlen(all_strbuff)); + kfree(all_strbuff); +} + +static void run_miscalibration(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN]; + char data[STM_TS_EVENT_BUFF_SIZE]; + char echo; + int ret; + int retry = 200; + short min = SHRT_MIN, max = SHRT_MAX; + + disable_irq(ts->irq); + memset(data, 0x00, sizeof(data)); + memset(buff, 0x00, sizeof(buff)); + + data[0] = 0xA4; + data[1] = 0x0B; + data[2] = 0x00; + data[3] = 0xC0; + + ret = ts->stm_ts_write(ts, data, 4, NULL, 0); + if (ret < 0) { + input_err(true, ts->dev, "%s: write failed: %d\n", __func__, ret); + goto error; + } + + sec_delay(5); + + /* maximum timeout 2sec ? */ + while (retry-- >= 0) { + memset(data, 0x00, sizeof(data)); + echo = STM_TS_READ_ONE_EVENT; + ret = ts->stm_ts_read(ts, &echo, 1, data, STM_TS_EVENT_BUFF_SIZE); + if (ret < 0) { + input_err(true, ts->dev, "%s: read failed: %d\n", __func__, ret); + goto error; + } + + input_info(true, ts->dev, "%s: %02X %02X %02X %02X %02X %02X %02X %02X\n", + __func__, data[0], data[1], data[2], data[3], + data[4], data[5], data[6], data[7]); + sec_delay(10); + + if (data[0] == 0x03 || data[0] == 0xF3) { + max = data[3] << 8 | data[2]; + min = data[5] << 8 | data[4]; + + break; + } + if (retry == 0) + goto error; + } + + if (data[0] == 0x03) { + snprintf(buff, sizeof(buff), "OK,%d,%d", min, max); + } else { + snprintf(buff, sizeof(buff), "NG,%d,%d", min, max); + } + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + + stm_ts_reinit(ts); + enable_irq(ts->irq); + return; +error: + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + stm_ts_reinit(ts); + enable_irq(ts->irq); +} + +static void check_connection(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret = 0; + + ret = stm_ts_panel_ito_test(ts, OPEN_TEST); + if (ret == 0) + sec->cmd_state = SEC_CMD_STATUS_OK; + else + sec->cmd_state = SEC_CMD_STATUS_FAIL; + + input_info(true, ts->dev, "%s: %s\n", __func__, sec->cmd_state == SEC_CMD_STATUS_OK ? "OK" : "NG"); +} + +int stm_ts_get_tsp_test_result(struct stm_ts_data *ts) +{ + u8 data; + int ret; + + ret = get_nvm_data(ts, STM_TS_NVM_OFFSET_FAC_RESULT, &data); + if (ret < 0) + goto err_read; + + if (data == 0xFF) + data = 0; + + ts->test_result.data[0] = data; + +err_read: + return ret; +} +EXPORT_SYMBOL(stm_ts_get_tsp_test_result); + +static void get_tsp_test_result(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + int ret; + + ret = stm_ts_get_tsp_test_result(ts); + if (ret < 0) { + input_err(true, ts->dev, + "%s: get failed. ret: %d\n", __func__, ret); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + } else { + snprintf(buff, sizeof(buff), "M:%s, M:%d, A:%s, A:%d", + ts->test_result.module_result == 0 ? "NONE" : + ts->test_result.module_result == 1 ? "FAIL" : + ts->test_result.module_result == 2 ? "PASS" : "A", + ts->test_result.module_count, + ts->test_result.assy_result == 0 ? "NONE" : + ts->test_result.assy_result == 1 ? "FAIL" : + ts->test_result.assy_result == 2 ? "PASS" : "A", + ts->test_result.assy_count); + + sec_cmd_set_cmd_result(sec, buff, strlen(buff)); + sec->cmd_state = SEC_CMD_STATUS_OK; + } +} + +/* FACTORY TEST RESULT SAVING FUNCTION + * bit 3 ~ 0 : OCTA Assy + * bit 7 ~ 4 : OCTA module + * param[0] : OCTA module(1) / OCTA Assy(2) + * param[1] : TEST NONE(0) / TEST FAIL(1) / TEST PASS(2) : 2 bit + */ +static void set_tsp_test_result(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret; + + ret = stm_ts_get_tsp_test_result(ts); + if (ret < 0) { + input_err(true, ts->dev, + "%s: get failed. ret: %d\n", __func__, ret); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + sec->cmd_state = SEC_CMD_STATUS_RUNNING; + + if (sec->cmd_param[0] == TEST_OCTA_ASSAY) { + ts->test_result.assy_result = sec->cmd_param[1]; + if (ts->test_result.assy_count < 3) + ts->test_result.assy_count++; + + } else if (sec->cmd_param[0] == TEST_OCTA_MODULE) { + ts->test_result.module_result = sec->cmd_param[1]; + if (ts->test_result.module_count < 3) + ts->test_result.module_count++; + } + + input_info(true, ts->dev, "%s: [0x%X] M:%s, M:%d, A:%s, A:%d\n", + __func__, ts->test_result.data[0], + ts->test_result.module_result == 0 ? "NONE" : + ts->test_result.module_result == 1 ? "FAIL" : + ts->test_result.module_result == 2 ? "PASS" : "A", + ts->test_result.module_count, + ts->test_result.assy_result == 0 ? "NONE" : + ts->test_result.assy_result == 1 ? "FAIL" : + ts->test_result.assy_result == 2 ? "PASS" : "A", + ts->test_result.assy_count); + + ret = set_nvm_data(ts, STM_TS_NVM_OFFSET_FAC_RESULT, ts->test_result.data); + if (ret < 0) { + input_err(true, ts->dev, + "%s: set failed. ret: %d\n", __func__, ret); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + } else { + sec->cmd_state = SEC_CMD_STATUS_OK; + } + + input_info(true, ts->dev, "%s: %s\n", __func__, sec->cmd_state == SEC_CMD_STATUS_OK ? "OK" : "NG"); +} + +int stm_ts_get_disassemble_count(struct stm_ts_data *ts) +{ + u8 data; + int ret; + + ret = get_nvm_data(ts, STM_TS_NVM_OFFSET_DISASSEMBLE_COUNT, &data); + if (ret < 0) + goto err_read; + + if (data == 0xFF) + data = 0; + + ts->disassemble_count = data; + +err_read: + return ret; +} + +static void increase_disassemble_count(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret; + + ret = stm_ts_get_disassemble_count(ts); + if (ret < 0) { + input_err(true, ts->dev, + "%s: get failed. ret: %d\n", __func__, ret); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + sec->cmd_state = SEC_CMD_STATUS_RUNNING; + + if (ts->disassemble_count < 0xFE) + ts->disassemble_count++; + + ret = set_nvm_data(ts, STM_TS_NVM_OFFSET_DISASSEMBLE_COUNT, &ts->disassemble_count); + if (ret < 0) { + input_err(true, ts->dev, + "%s: set failed. ret: %d\n", __func__, ret); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + } else { + sec->cmd_state = SEC_CMD_STATUS_OK; + } + + input_info(true, ts->dev, "%s: %s\n", __func__, sec->cmd_state == SEC_CMD_STATUS_OK ? "OK" : "NG"); +} + +static void get_disassemble_count(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + int ret; + + ret = stm_ts_get_disassemble_count(ts); + if (ret < 0) { + input_err(true, ts->dev, + "%s: get failed. ret: %d\n", __func__, ret); + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + } else { + snprintf(buff, sizeof(buff), "%d", ts->disassemble_count); + + sec_cmd_set_cmd_result(sec, buff, strlen(buff)); + sec->cmd_state = SEC_CMD_STATUS_OK; + } +} + +static void get_osc_trim_error(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + int ret; + + ret = ts->stm_ts_systemreset(ts, 0); + if (ret == -STM_TS_ERROR_BROKEN_OSC_TRIM) { + snprintf(buff, sizeof(buff), "1"); + } else { + snprintf(buff, sizeof(buff), "0"); + } + stm_ts_set_scanmode(ts, ts->scan_mode); + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "OSC_TRIM_ERR"); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void get_osc_trim_info(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + int ret; + unsigned char data[4]; + unsigned char reg[3]; + + ret = stm_ts_get_sysinfo_data(ts, STM_TS_SI_OSC_TRIM_INFO, 2, data); + if (ret < 0) { + input_err(true, ts->dev, + "%s: system info read failed. ret: %d\n", __func__, ret); + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + reg[0] = STM_TS_CMD_FRM_BUFF_R; + reg[1] = data[1]; + reg[2] = data[0]; + + memset(data, 0x00, 4); + ret = ts->stm_ts_read(ts, reg, 3, data, 4); + if (ret < 0) { + input_err(true, ts->dev, + "%s: osc trim info read failed. ret: %d\n", __func__, ret); + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + snprintf(buff, sizeof(buff), "%02X%02X%02X%02X", data[0], data[1], data[2], data[3]); + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "OSC_TRIM_INFO"); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void run_elvss_test(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret; + u8 data[STM_TS_EVENT_BUFF_SIZE + 1]; + int retry = 10; + + stm_ts_systemreset(ts, 0); + disable_irq(ts->irq); + + memset(data, 0x00, 8); + + data[0] = 0xA4; + data[1] = 0x04; + data[2] = 0x00; + data[3] = 0x04; + + ret = ts->stm_ts_write(ts, &data[0], 4, NULL, 0); + if (ret < 0) { + input_err(true, ts->dev, + "%s: write failed. ret: %d\n", __func__, ret); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + stm_ts_reinit(ts); + enable_irq(ts->irq); + return; + } + + sec_delay(5); + + memset(data, 0x00, STM_TS_EVENT_BUFF_SIZE); + data[0] = STM_TS_READ_ONE_EVENT; + + while (ts->stm_ts_read(ts, &data[0], 1, &data[1], STM_TS_EVENT_BUFF_SIZE) >= 0) { + input_info(true, ts->dev, + "%s: %02X: %02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X\n", + __func__, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], + data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15]); + + if (data[1] == STM_TS_EVENT_ERROR_REPORT) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + break; + } else if (data[1] == 0x03) { + sec->cmd_state = SEC_CMD_STATUS_OK; + break; + } else if (retry < 0) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + break; + } + retry--; + sec_delay(50); + } + + stm_ts_reinit(ts); + enable_irq(ts->irq); + + input_info(true, ts->dev, "%s: %s\n", __func__, sec->cmd_state == SEC_CMD_STATUS_OK ? "OK" : "NG"); +} + +static void run_snr_non_touched(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + u8 address[5] = { 0 }; + u16 status; + int ret = 0; + int wait_time = 0; + int retry_cnt = 0; + + if (sec->cmd_param[0] < 1 || sec->cmd_param[0] > 1000) { + input_err(true, ts->dev, "%s: strange value frame:%d\n", + __func__, sec->cmd_param[0]); + goto ERROR_INIT; + } + + ret = stm_ts_fix_active_mode(ts, STM_TS_ACTIVE_TRUE); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to set active mode\n", __func__); + goto ERROR_INIT; + } + + /* start Non-touched Peak Noise */ + address[0] = 0xC7; + address[1] = 0x09; + address[2] = 0x01; + address[3] = (u8)(sec->cmd_param[0] & 0xff); + address[4] = (u8)(sec->cmd_param[0] >> 8); + ret = ts->stm_ts_write(ts, &address[0], 5, NULL, 0); + if (ret < 0) { + input_err(true, ts->dev, "%s: i2c_write failed\n", __func__); + goto ERROR; + } + + /* enter SNR mode */ + address[0] = 0x70; + address[1] = 0x25; + ret = ts->stm_ts_write(ts, &address[0], 2, NULL, 0); + if (ret < 0) { + input_err(true, ts->dev, "%s: i2c_write failed\n", __func__); + goto ERROR; + } + + wait_time = (sec->cmd_param[0] * 1000) / 120 + 200; /* frame count * 1000 / frame rate + 200 msec margin*/ + + sec_delay(wait_time); + + retry_cnt = 50; + address[0] = STM_TS_CMD_SNR_R; + while ((ts->stm_ts_read(ts, &address[0], 1, (u8 *)&status, STM_TS_CMD_SNR_R_SIZE) >= 0) && (retry_cnt-- > 0)) { + if (status == 1) + break; + sec_delay(20); + } + + if (status == 0) { + input_err(true, ts->dev, "%s: failed non-touched status:%d\n", __func__, status); + goto ERROR; + } + + sec->cmd_state = SEC_CMD_STATUS_OK; + + /* EXIT SNR mode */ + address[0] = 0x70; + address[1] = 0x00; + ts->stm_ts_write(ts, &address[0], 2, NULL, 0); + stm_ts_fix_active_mode(ts, STM_TS_ACTIVE_FALSE_SNR); + input_info(true, ts->dev, "%s: %s\n", __func__, "OK"); + + return; + +ERROR: + address[0] = 0x70; + address[1] = 0x00; + ts->stm_ts_write(ts, &address[0], 2, NULL, 0); + stm_ts_fix_active_mode(ts, STM_TS_ACTIVE_FALSE_SNR); + +ERROR_INIT: + sec->cmd_state = SEC_CMD_STATUS_FAIL; + + input_info(true, ts->dev, "%s: %s\n", __func__, "NG"); +} + +static void run_snr_touched(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + struct stm_ts_snr_result_cmd snr_cmd_result; + struct stm_ts_snr_result snr_result; + char buff[SEC_CMD_STR_LEN] = { 0 }; + char tbuff[SEC_CMD_STR_LEN] = { 0 }; + u8 address[5] = { 0 }; + int ret = 0; + int wait_time = 0; + int retry_cnt = 0; + int i = 0; + + memset(&snr_result, 0, sizeof(struct stm_ts_snr_result)); + memset(&snr_cmd_result, 0, sizeof(struct stm_ts_snr_result_cmd)); + + if (sec->cmd_param[0] < 1 || sec->cmd_param[0] > 1000) { + input_err(true, ts->dev, "%s: strange value frame:%d\n", + __func__, sec->cmd_param[0]); + goto ERROR_INIT; + } + + ret = stm_ts_fix_active_mode(ts, STM_TS_ACTIVE_TRUE); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to set active mode\n", __func__); + goto ERROR_INIT; + } + + /* start touched Peak Noise */ + address[0] = 0xC7; + address[1] = 0x09; + address[2] = 0x02; + address[3] = (u8)(sec->cmd_param[0] & 0xff); + address[4] = (u8)(sec->cmd_param[0] >> 8); + ret = ts->stm_ts_write(ts, &address[0], 5, NULL, 0); + if (ret < 0) { + input_err(true, ts->dev, "%s: i2c_write failed\n", __func__); + goto ERROR; + } + + /* enter SNR mode */ + address[0] = 0x70; + address[1] = 0x25; + ret = ts->stm_ts_write(ts, &address[0], 2, NULL, 0); + if (ret < 0) { + input_err(true, ts->dev, "%s: i2c_write failed\n", __func__); + goto ERROR; + } + + wait_time = (sec->cmd_param[0] * 1000) / 120 + 200; /* frame count * 1000 / frame rate + 200 msec margin*/ + sec_delay(wait_time); + + retry_cnt = 50; + memset(address, 0x00, 5); + address[0] = STM_TS_CMD_SNR_R; + while ((ts->stm_ts_read(ts, &address[0], 1, (u8 *)&snr_cmd_result, 6) >= 0) && (retry_cnt-- > 0)) { + if (snr_cmd_result.status == 1) + break; + sec_delay(20); + } + + if (snr_cmd_result.status == 0) { + input_err(true, ts->dev, "%s: failed non-touched status:%d\n", __func__, snr_cmd_result.status); + goto ERROR; + } else { + input_info(true, ts->dev, "%s: status:%d, point:%d, average:%d\n", __func__, + snr_cmd_result.status, snr_cmd_result.point, snr_cmd_result.average); + } + + memset(address, 0x00, 5); + address[0] = STM_TS_CMD_SNR_R; + ret = ts->stm_ts_read(ts, &address[0], 1, (u8 *)&snr_result, sizeof(struct stm_ts_snr_result)); + if (ret < 0) { + input_err(true, ts->dev, "%s: i2c_write failed size:%ld\n", __func__, sizeof(struct stm_ts_snr_result)); + goto ERROR; + } + + for (i = 0; i < 9; i++) { + input_info(true, ts->dev, "%s: average:%d, snr1:%d, snr2:%d\n", __func__, + snr_result.result[i].average, snr_result.result[i].snr1, snr_result.result[i].snr2); + snprintf(tbuff, sizeof(tbuff), "%d,%d,%d,", + snr_result.result[i].average, + snr_result.result[i].snr1, + snr_result.result[i].snr2); + strlcat(buff, tbuff, sizeof(buff)); + } + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + + /* EXIT SNR mode */ + address[0] = 0x70; + address[1] = 0x00; + ts->stm_ts_write(ts, &address[0], 2, NULL, 0); + stm_ts_fix_active_mode(ts, STM_TS_ACTIVE_FALSE_SNR); + input_info(true, ts->dev, "%s: %s\n", __func__, buff); + + return; + +ERROR: + address[0] = 0x70; + address[1] = 0x00; + ts->stm_ts_write(ts, &address[0], 2, NULL, 0); + stm_ts_fix_active_mode(ts, STM_TS_ACTIVE_FALSE_SNR); +ERROR_INIT: + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void run_sram_test(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN]; + u8 reg; + u8 data[STM_TS_EVENT_BUFF_SIZE]; + int ret, retry = 0; + + ts->stm_ts_systemreset(ts, 0); + + mutex_lock(&ts->fn_mutex); + disable_irq(ts->irq); + + reg = STM_TS_CMD_RUN_SRAM_TEST; + ret = ts->stm_ts_write(ts, ®, 1, NULL, 0); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to write cmd\n", __func__); + goto error; + } + + sec_delay(300); + + memset(data, 0x0, STM_TS_EVENT_BUFF_SIZE); + ret = -EIO; + reg = STM_TS_READ_ONE_EVENT; + while (ts->stm_ts_read(ts, ®, 1, data, STM_TS_EVENT_BUFF_SIZE) >= 0) { + if (data[0] != 0x00) + input_info(true, ts->dev, + "%s: event %02X, %02X, %02X, %02X, %02X, %02X, %02X, %02X\n", + __func__, data[0], data[1], data[2], data[3], + data[4], data[5], data[6], data[7]); + + if (data[0] == STM_TS_EVENT_PASS_REPORT && data[1] == STM_TS_EVENT_SRAM_TEST_RESULT) { + ret = 0; /* PASS */ + break; + } else if (data[0] == STM_TS_EVENT_ERROR_REPORT && data[1] == STM_TS_EVENT_SRAM_TEST_RESULT) { + ret = 1; /* FAIL */ + break; + } + + if (retry++ > STM_TS_RETRY_COUNT * 25) { + input_err(true, ts->dev, + "%s: Time Over (%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X)\n", + __func__, data[0], data[1], data[2], data[3], + data[4], data[5], data[6], data[7]); + break; + } + sec_delay(20); + } + +error: + mutex_unlock(&ts->fn_mutex); + + if (ret < 0) { + snprintf(buff, sizeof(buff), "NG"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + } else { + snprintf(buff, sizeof(buff), "%d", ret); + sec->cmd_state = SEC_CMD_STATUS_OK; + } + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "SRAM"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + + stm_ts_reinit(ts); + enable_irq(ts->irq); +} + +static void run_polarity_test(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN]; + u8 reg; + u8 data[STM_TS_EVENT_BUFF_SIZE]; + int ret, retry = 0; + + ts->stm_ts_systemreset(ts, 0); + + mutex_lock(&ts->fn_mutex); + disable_irq(ts->irq); + + reg = STM_TS_CMD_RUN_POLARITY_TEST; + ret = ts->stm_ts_write(ts, ®, 1, NULL, 0); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to write cmd\n", __func__); + goto error; + } + + sec_delay(300); + + memset(data, 0x0, STM_TS_EVENT_BUFF_SIZE); + ret = -EIO; + reg = STM_TS_READ_ONE_EVENT; + while (ts->stm_ts_read(ts, ®, 1, data, STM_TS_EVENT_BUFF_SIZE) >= 0) { + input_info(true, ts->dev, + "%s: event %02X, %02X, %02X, %02X, %02X, %02X, %02X, %02X\n", + __func__, data[0], data[1], data[2], data[3], + data[4], data[5], data[6], data[7]); + + if (data[0] == STM_TS_EVENT_PASS_REPORT) { + ret = 0; /* PASS */ + break; + } else if (data[0] == STM_TS_EVENT_ERROR_REPORT) { + ret = 1; /* FAIL */ + break; + } + + if (retry++ > STM_TS_RETRY_COUNT * 25) { + input_err(true, ts->dev, + "%s: Time Over (%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X)\n", + __func__, data[0], data[1], data[2], data[3], + data[4], data[5], data[6], data[7]); + break; + } + sec_delay(20); + } + +error: + mutex_unlock(&ts->fn_mutex); + + if (ret == 0) { + snprintf(buff, sizeof(buff), "0"); + sec->cmd_state = SEC_CMD_STATUS_OK; + } else { + snprintf(buff, sizeof(buff), "1"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + } + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "POLARITY"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + + stm_ts_reinit(ts); + enable_irq(ts->irq); +} + +static int run_force_calibration_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; +#ifdef TCLM_CONCEPT + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + ts->tdata->external_factory = false; +#endif + sec->cmd_state = SEC_CMD_STATUS_FAIL; + + return SEC_SUCCESS; +} + +static void run_force_calibration(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + bool touch_on = false; + + if (ts->tsp_dump_lock == 1) { + input_err(true, ts->dev, "%s: ramdump mode is running, %d\n", + __func__, ts->tsp_dump_lock); + goto autotune_fail; + } + + if (ts->plat_data->touch_count > 0) { + touch_on = true; + input_err(true, ts->dev, "%s: finger on touch(%d)\n", __func__, ts->plat_data->touch_count); + } + + ts->stm_ts_systemreset(ts, 0); + + stm_ts_release_all_finger(ts); + + if (touch_on) { + input_err(true, ts->dev, "%s: finger! do not run autotune\n", __func__); + } else { + input_info(true, ts->dev, "%s: run autotune\n", __func__); + + input_err(true, ts->dev, "%s: RUN OFFSET CALIBRATION\n", __func__); + if (stm_ts_execute_autotune(ts, true) < 0) { + stm_ts_set_scanmode(ts, ts->scan_mode); + goto autotune_fail; + } +#ifdef TCLM_CONCEPT + /* devide tclm case */ + sec_tclm_case(ts->tdata, sec->cmd_param[0]); + + input_info(true, ts->dev, "%s: param, %d, %c, %d\n", __func__, + sec->cmd_param[0], sec->cmd_param[0], ts->tdata->root_of_calibration); + + if (sec_execute_tclm_package(ts->tdata, 1) < 0) + input_err(true, ts->dev, + "%s: sec_execute_tclm_package\n", __func__); + + sec_tclm_root_of_cal(ts->tdata, CALPOSITION_NONE); +#endif + } + + stm_ts_set_scanmode(ts, ts->scan_mode); + + if (touch_on) + sec->cmd_state = SEC_CMD_STATUS_FAIL; + else + sec->cmd_state = SEC_CMD_STATUS_OK; + +#ifdef TCLM_CONCEPT + ts->tdata->external_factory = false; +#endif + input_info(true, ts->dev, "%s: %s\n", __func__, sec->cmd_state == SEC_CMD_STATUS_OK ? "OK" : "NG"); + return; + +autotune_fail: +#ifdef TCLM_CONCEPT + ts->tdata->external_factory = false; +#endif + sec->cmd_state = SEC_CMD_STATUS_FAIL; + + input_info(true, ts->dev, "%s: %s\n", __func__, sec->cmd_state == SEC_CMD_STATUS_OK ? "OK" : "NG"); +} + +static void run_interrupt_gpio_test(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN]; + u8 drv_data[3] = { 0xA4, 0x01, 0x01 }; + u8 irq_data[3] = { 0xA4, 0x01, 0x00 }; + int drv_value = -1; + int irq_value = -1; + + disable_irq(ts->irq); + + ts->stm_ts_write(ts, drv_data, 3, NULL, 0); + + sec_delay(50); + + drv_value = gpio_get_value(ts->plat_data->irq_gpio); + ts->stm_ts_write(ts, irq_data, 3, NULL, 0); + + sec_delay(50); + + irq_value = gpio_get_value(ts->plat_data->irq_gpio); + input_info(true, ts->dev, "%s: drv_value:%d, irq_value:%d\n", __func__, drv_value, irq_value); + + if (drv_value == 0 && irq_value == 1) { + snprintf(buff, sizeof(buff), "0"); + sec->cmd_state = SEC_CMD_STATUS_OK; + } else { + if (drv_value != 0) + snprintf(buff, sizeof(buff), "1:HIGH"); + else if (irq_value != 1) + snprintf(buff, sizeof(buff), "1:LOW"); + else + snprintf(buff, sizeof(buff), "1:FAIL"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + } + + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "INT_GPIO"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + + stm_ts_reinit(ts); + enable_irq(ts->irq); +} + +static void set_factory_level(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + if (sec->cmd_param[0] < OFFSET_FAC_SUB || sec->cmd_param[0] > OFFSET_FAC_MAIN) { + input_err(true, ts->dev, + "%s: cmd data is abnormal, %d\n", __func__, sec->cmd_param[0]); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + ts->factory_position = sec->cmd_param[0]; + input_info(true, ts->dev, "%s: %d\n", __func__, ts->factory_position); + + sec->cmd_state = SEC_CMD_STATUS_OK; +} + +static void factory_cmd_result_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + sec->item_count = 0; + memset(sec->cmd_result_all, 0x00, SEC_CMD_RESULT_STR_LEN); + + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: [ERROR] Touch is stopped\n", + __func__); + sec->cmd_all_factory_state = SEC_CMD_STATUS_FAIL; + goto out; + } + + sec->cmd_all_factory_state = SEC_CMD_STATUS_RUNNING; + + get_chip_vendor(sec); + get_chip_name(sec); + get_fw_ver_bin(sec); + get_fw_ver_ic(sec); + + enter_factory_mode(ts, true); + if (ts->support_mutual_raw) + run_lp_single_ended_rawcap_read(sec); /* Mutual raw */ + else + run_rawcap_read(sec); + run_self_raw_read(sec); + + stm_ts_set_scanmode(ts, STM_TS_SCAN_MODE_SCAN_OFF); + stm_ts_release_all_finger(ts); + + run_active_cx_data_read(sec); + run_cx_data_read(sec); + get_cx_gap_data(sec); + run_ix_data_read(sec); + + enter_factory_mode(ts, false); + + get_wet_mode(sec); + + run_mutual_jitter(sec); + run_self_jitter(sec); + run_factory_miscalibration(sec); + run_sram_test(sec); + run_polarity_test(sec); + + run_high_frequency_rawcap_read(sec); + run_interrupt_gpio_test(sec); + + sec->cmd_all_factory_state = SEC_CMD_STATUS_OK; + +out: + input_info(true, ts->dev, "%s: %d%s\n", __func__, sec->item_count, sec->cmd_result_all); +} + +static void factory_cmd_result_all_imagetest(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + sec->item_count = 0; + memset(sec->cmd_result_all, 0x00, SEC_CMD_RESULT_STR_LEN); + + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: [ERROR] Touch is stopped\n", + __func__); + sec->cmd_all_factory_state = SEC_CMD_STATUS_FAIL; + goto out; + } + + sec->cmd_all_factory_state = SEC_CMD_STATUS_RUNNING; + + run_jitter_delta_test(sec); + + sec->cmd_all_factory_state = SEC_CMD_STATUS_OK; + +out: + input_info(true, ts->dev, "%s: %d%s\n", __func__, sec->item_count, sec->cmd_result_all); +} + +static int glove_mode_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + ts->glove_enabled = sec->cmd_param[0]; + if (ts->glove_enabled) + ts->plat_data->touch_functions = ts->plat_data->touch_functions | STM_TS_TOUCHTYPE_BIT_GLOVE; + else + ts->plat_data->touch_functions = (ts->plat_data->touch_functions & (~STM_TS_TOUCHTYPE_BIT_GLOVE)); + + sec->cmd_state = SEC_CMD_STATUS_OK; + return SEC_SUCCESS; +} + +static void glove_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret = 0; + + ret = glove_mode_save(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: %s\n", __func__, "NG"); + return; + } + + if (ts->probe_done) + stm_ts_set_touch_function(ts); + else + input_info(true, ts->dev, "%s: probe is not done\n", __func__); + + input_info(true, ts->dev, "%s: %s\n", __func__, "OK"); +} + +static int clear_cover_mode_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 3) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + if (sec->cmd_param[0] > 1) { + ts->plat_data->cover_type = sec->cmd_param[1]; + ts->plat_data->touch_functions |= STM_TS_TOUCHTYPE_BIT_COVER; + } else { + ts->plat_data->touch_functions &= ~STM_TS_TOUCHTYPE_BIT_COVER; + } + + sec->cmd_state = SEC_CMD_STATUS_OK; + return SEC_SUCCESS; +}; + +static void clear_cover_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret = 0; + + ret = clear_cover_mode_save(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: %s\n", __func__, "NG"); + return; + } + + if (ts->probe_done) + stm_ts_set_cover_type(ts, ts->plat_data->touch_functions & STM_TS_TOUCHTYPE_BIT_COVER); + else + input_info(true, ts->dev, "%s: probe is not done\n", __func__); + + input_info(true, ts->dev, "%s: %s\n", __func__, "OK"); +}; + +static int set_wirelesscharger_mode_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret = 0; + + ret = sec_input_check_wirelesscharger_mode(ts->dev, sec->cmd_param[0], sec->cmd_param[1]); + if (ret == SEC_ERROR) + sec->cmd_state = SEC_CMD_STATUS_FAIL; + else + sec->cmd_state = SEC_CMD_STATUS_OK; + + return ret; +}; + +static void set_wirelesscharger_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + u8 reg[3]; +#endif + int ret = 0; + + ret = set_wirelesscharger_mode_save(device_data); + if (ret != SEC_SUCCESS) { + input_info(true, ts->dev, "%s: %s\n", __func__, "NG"); + return; + } + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + reg[0] = STM_TS_CMD_SET_FUNCTION_ONOFF; + reg[1] = STM_TS_CMD_FUNCTION_SET_TSP_BLOCK; + reg[2] = 0; + ret = ts->stm_ts_write(ts, reg, 3, NULL, 0); + input_info(true, ts->dev, "%s: force tsp unblock, ret=%d\n", __func__, ret); + sec_input_gesture_report(ts->dev, SPONGE_EVENT_TYPE_TSP_SCAN_UNBLOCK, 0, 0); +#endif + + ret = stm_ts_set_wirelesscharger_mode(ts); + if (ret < 0) + sec->cmd_state = SEC_CMD_STATUS_FAIL; +}; + +/* + * index + * 0 : set edge handler + * 1 : portrait (normal) mode + * 2 : landscape mode + * data + * 0, X (direction), X (y start), X (y end) + * direction : 0 (off), 1 (left), 2 (right) + * ex) echo set_grip_data,0,2,600,900 > cmd + * + * + * 1, X (edge zone), X (dead zone up x), X (dead zone down x), X (dead zone y) + * ex) echo set_grip_data,1,200,10,50,1500 > cmd + * + * 2, 1 (landscape mode), X (edge zone), X (dead zone), X (dead zone top y), X (dead zone bottom y) + * ex) echo set_grip_data,2,1,200,100,120,0 > cmd + * + * 2, 0 (portrait mode) + * ex) echo set_grip_data,2,0 > cmd + */ +static int set_grip_data_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + int mode = G_NONE; + + mode = sec_input_store_grip_data(sec->dev, sec->cmd_param); + if (mode < 0) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + sec->cmd_state = SEC_CMD_STATUS_OK; + + return mode; +} + +static void set_grip_data(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int mode = G_NONE; + + mode = set_grip_data_save(device_data); + if (mode < 0) { + input_info(true, ts->dev, "%s: %s\n", __func__, "NG"); + return; + } + + mutex_lock(&ts->plat_data->enable_mutex); + stm_set_grip_data_to_ic(ts->dev, mode); + mutex_unlock(&ts->plat_data->enable_mutex); +} + +static int dead_zone_enable_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + ts->dead_zone = sec->cmd_param[0]; + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void dead_zone_enable(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret = 0; + + ret = dead_zone_enable_save(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: %s\n", __func__, "NG"); + return; + } + + ret = stm_ts_dead_zone_enable(ts); + if (ret < 0) + sec->cmd_state = SEC_CMD_STATUS_FAIL; + + input_info(true, ts->dev, "%s: %s\n", __func__, sec->cmd_state == SEC_CMD_STATUS_OK ? "OK" : "NG"); +} + +static int spay_enable_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + if (sec->cmd_param[0]) + ts->plat_data->lowpower_mode |= SEC_TS_MODE_SPONGE_SWIPE; + else + ts->plat_data->lowpower_mode &= ~SEC_TS_MODE_SPONGE_SWIPE; + + input_info(true, ts->dev, "%s: %s, %02X\n", + __func__, sec->cmd_param[0] ? "on" : "off", ts->plat_data->lowpower_mode); + + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void spay_enable(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret = 0; + + ret = spay_enable_save(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: %s\n", __func__, "NG"); + return; + } + + stm_ts_set_custom_library(ts); + + input_info(true, ts->dev, "%s: %s\n", __func__, sec->cmd_state == SEC_CMD_STATUS_OK ? "OK" : "NG"); +} + +static int aot_enable_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + if (sec->cmd_param[0]) + ts->plat_data->lowpower_mode |= SEC_TS_MODE_SPONGE_DOUBLETAP_TO_WAKEUP; + else + ts->plat_data->lowpower_mode &= ~SEC_TS_MODE_SPONGE_DOUBLETAP_TO_WAKEUP; + + input_info(true, ts->dev, "%s: %s, %02X\n", + __func__, sec->cmd_param[0] ? "on" : "off", ts->plat_data->lowpower_mode); + + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void aot_enable(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret = 0; + + ret = aot_enable_save(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: %s\n", __func__, "NG"); + return; + } + + if (ts->probe_done) + stm_ts_set_custom_library(ts); + else + input_info(true, ts->dev, "%s: probe is not done\n", __func__); + + input_info(true, ts->dev, "%s: %d\n", __func__, sec->cmd_param[0]); +} + +static int singletap_enable_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + if (sec->cmd_param[0]) + ts->plat_data->lowpower_mode |= SEC_TS_MODE_SPONGE_SINGLE_TAP; + else + ts->plat_data->lowpower_mode &= ~SEC_TS_MODE_SPONGE_SINGLE_TAP; + + input_info(true, ts->dev, "%s: %s, %02X\n", + __func__, sec->cmd_param[0] ? "on" : "off", ts->plat_data->lowpower_mode); + + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void singletap_enable(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret = 0; + + ret = singletap_enable_save(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: %s\n", __func__, "NG"); + return; + } + + if (ts->probe_done) + stm_ts_set_custom_library(ts); + else + input_info(true, ts->dev, "%s: probe is not done\n", __func__); + + input_info(true, ts->dev, "%s: %d\n", __func__, sec->cmd_param[0]); +} + +static int aod_enable_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + if (sec->cmd_param[0]) + ts->plat_data->lowpower_mode |= SEC_TS_MODE_SPONGE_AOD; + else + ts->plat_data->lowpower_mode &= ~SEC_TS_MODE_SPONGE_AOD; + + input_info(true, ts->dev, "%s: %s, %02X\n", + __func__, sec->cmd_param[0] ? "on" : "off", ts->plat_data->lowpower_mode); + + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void aod_enable(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret = 0; + + ret = aod_enable_save(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: %s\n", __func__, "NG"); + return; + } + + if (ts->probe_done) + stm_ts_set_custom_library(ts); + else + input_info(true, ts->dev, "%s: probe is not done\n", __func__); + + input_info(true, ts->dev, "%s: %d\n", __func__, sec->cmd_param[0]); +} + +static int set_aod_rect_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int i; + +#if !defined(CONFIG_SAMSUNG_PRODUCT_SHIP) + input_info(true, ts->dev, "%s: w:%d, h:%d, x:%d, y:%d\n", + __func__, sec->cmd_param[0], sec->cmd_param[1], + sec->cmd_param[2], sec->cmd_param[3]); +#endif + + for (i = 0; i < 4; i++) + ts->plat_data->aod_data.rect_data[i] = sec->cmd_param[i]; + + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void set_aod_rect(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret = 0; + + set_aod_rect_save(device_data); + + ret = stm_ts_set_aod_rect(ts); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed. ret: %d\n", __func__, ret); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + } +} + +static void get_aod_rect(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + u8 data[8] = {0, }; + u16 rect_data[4] = {0, }; + int i, ret = -1; + + data[0] = STM_TS_CMD_SPONGE_OFFSET_AOD_RECT; + + ret = ts->stm_ts_read_sponge(ts, data, sizeof(data)); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed. ret: %d\n", __func__, ret); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + for (i = 0; i < 4; i++) + rect_data[i] = (data[i * 2 + 1] & 0xFF) << 8 | (data[i * 2] & 0xFF); + +#if !defined(CONFIG_SAMSUNG_PRODUCT_SHIP) + input_info(true, ts->dev, "%s: w:%d, h:%d, x:%d, y:%d\n", + __func__, rect_data[0], rect_data[1], rect_data[2], rect_data[3]); +#endif + + sec->cmd_state = SEC_CMD_STATUS_OK; + return; +} + +static int fod_enable_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + if (!ts->plat_data->support_fod) { + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + return SEC_ERROR; + } + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + if (sec->cmd_param[0]) + ts->plat_data->lowpower_mode |= SEC_TS_MODE_SPONGE_PRESS; + else + ts->plat_data->lowpower_mode &= ~SEC_TS_MODE_SPONGE_PRESS; + + ts->plat_data->fod_data.press_prop = (sec->cmd_param[1] & 0x01) | ((sec->cmd_param[2] & 0x01) << 1); + + input_info(true, ts->dev, "%s: %s, fast:%s, strict:%s, %02X\n", + __func__, sec->cmd_param[0] ? "on" : "off", + ts->plat_data->fod_data.press_prop & 1 ? "on" : "off", + ts->plat_data->fod_data.press_prop & 2 ? "on" : "off", + ts->plat_data->lowpower_mode); + sec->cmd_state = SEC_CMD_STATUS_OK; + +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) +#if IS_ENABLED(CONFIG_INPUT_SEC_TRUSTED_TOUCH) + if (atomic_read(&ts->plat_data->pvm->trusted_touch_enabled)) { + input_info(true, ts->dev, "%s trusted touch is enabled. skip\n", __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } +#endif +#endif + + return SEC_SUCCESS; +} + +static void fod_enable(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret = 0; + + ret = fod_enable_save(sec); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + mutex_lock(&ts->plat_data->enable_mutex); + + if (!atomic_read(&ts->plat_data->enabled) && sec_input_need_ic_off(ts->plat_data)) { + if (atomic_read(&ts->plat_data->power_state) == SEC_INPUT_STATE_LPM) + disable_irq_wake(ts->irq); + ts->plat_data->stop_device(ts); + } else { + stm_ts_set_custom_library(ts); + stm_ts_set_press_property(ts); + stm_ts_set_fod_finger_merge(ts); + } + + mutex_unlock(&ts->plat_data->enable_mutex); + + sec->cmd_state = SEC_CMD_STATUS_OK; +} + +static void fod_lp_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } else if (!ts->plat_data->support_fod_lp_mode) { + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + return; + } + + ts->plat_data->fod_lp_mode = sec->cmd_param[0]; + input_info(true, ts->dev, "%s: %d\n", __func__, ts->plat_data->fod_lp_mode); + + sec->cmd_state = SEC_CMD_STATUS_OK; +} + +static int set_fod_rect_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + input_info(true, ts->dev, "%s: l:%d, t:%d, r:%d, b:%d\n", + __func__, sec->cmd_param[0], sec->cmd_param[1], + sec->cmd_param[2], sec->cmd_param[3]); + + if (!ts->plat_data->support_fod) { + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + return SEC_ERROR; + } + + if (!sec_input_set_fod_rect(ts->dev, sec->cmd_param)) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void set_fod_rect(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret = 0; + + ret = set_fod_rect_save(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + ret = stm_ts_set_fod_rect(ts); + if (ret < 0) + sec->cmd_state = SEC_CMD_STATUS_FAIL; +} + +static void fp_int_control(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret; + u8 reg[2] = {STM_TS_CMD_SET_FOD_INT_CONTROL, 0x00}; + u8 enable; + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + input_err(true, ts->dev, "%s: invalid param %d\n", __func__, sec->cmd_param[0]); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } else if (!ts->plat_data->support_fod) { + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + return; + } + + enable = sec->cmd_param[0]; + if (enable) + reg[1] = 0x01; + else + reg[1] = 0x00; + + ret = ts->stm_ts_write(ts, reg, 2, NULL, 0); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed. ret: %d\n", __func__, ret); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + input_info(true, ts->dev, "%s: fod int %d\n", __func__, enable); + + sec->cmd_state = SEC_CMD_STATUS_OK; +} + +/* + * Enable or disable specific external_noise_mode (sec_cmd) + * + * This cmd has 2 params. + * param 0 : the mode that you want to change. + * param 1 : enable or disable the mode. + * + * For example, + * enable EXT_NOISE_MODE_MONITOR mode, + * write external_noise_mode,1,1 + * disable EXT_NOISE_MODE_MONITOR mode, + * write external_noise_mode,1,0 + */ +static int external_noise_mode_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + if (sec->cmd_param[0] <= EXT_NOISE_MODE_NONE || sec->cmd_param[0] >= EXT_NOISE_MODE_MAX || + sec->cmd_param[1] < 0 || sec->cmd_param[1] > 1) { + input_err(true, ts->dev, "%s: not support param\n", __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + if (sec->cmd_param[1] == 1) + ts->plat_data->external_noise_mode |= 1 << sec->cmd_param[0]; + else + ts->plat_data->external_noise_mode &= ~(1 << sec->cmd_param[0]); + + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void external_noise_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret; + + ret = external_noise_mode_save(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + ret = stm_ts_set_external_noise_mode(ts, ts->plat_data->external_noise_mode); + if (ret < 0) + sec->cmd_state = SEC_CMD_STATUS_FAIL; +} + +static int brush_enable_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + ts->brush_mode = sec->cmd_param[0]; + + input_info(true, ts->dev, + "%s: set brush mode %s\n", __func__, ts->brush_mode ? "enable" : "disable"); + + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void brush_enable(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + u8 reg[3] = {STM_TS_CMD_SET_FUNCTION_ONOFF, STM_TS_FUNCTION_ENABLE_BRUSH_MODE, 0x00}; + int ret = 0; + + ret = brush_enable_save(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + if (ts->brush_mode == 0) + reg[2] = 0x00; /* 0: Disable Artcanvas min phi mode */ + else + reg[2] = 0x01; /* 1: Enable Artcanvas min phi mode */ + + ret = ts->stm_ts_write(ts, ®[0], 3, NULL, 0); + if (ret < 0) { + input_err(true, ts->dev, + "%s: failed to set brush mode\n", __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + } + + input_info(true, ts->dev, "%s: %s\n", __func__, sec->cmd_state == SEC_CMD_STATUS_OK ? "OK" : "NG"); +} + +static int set_touchable_area_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + ts->plat_data->touchable_area = sec->cmd_param[0]; + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void set_touchable_area(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret = 0; + + ret = set_touchable_area_save(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + ret = stm_ts_set_touchable_area(ts); + if (ret < 0) + sec->cmd_state = SEC_CMD_STATUS_FAIL; + +} + +static void debug(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + ts->debug_flag = sec->cmd_param[0]; + + input_info(true, ts->dev, "%s: command is %d\n", __func__, ts->debug_flag); + sec->cmd_state = SEC_CMD_STATUS_WAITING; +} + +#ifdef ENABLE_RAWDATA_SERVICE +static int rawdata_init_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + if (!ts->plat_data->support_rawdata) { + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + return SEC_ERROR; + } + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + ts->raw_mode = sec->cmd_param[0]; //param : 1 strength , 2 raw + input_info(true, ts->dev, "%s: %d\n", __func__, ts->raw_mode); + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void rawdata_init(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + int ret = 0; + + ret = rawdata_init_save(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + snprintf(buff, sizeof(buff), "OK:%d", ts->plat_data->support_rawdata_map_num); + sec->cmd_state = SEC_CMD_STATUS_OK; + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void blocking_palm(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + ts->plat_data->blocking_palm = sec->cmd_param[0]; + + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, "OK"); +} +#endif + +static int fix_active_mode_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + ts->fix_active_mode = !!sec->cmd_param[0]; + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void fix_active_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret = 0; + + ret = fix_active_mode_save(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + stm_ts_fix_active_mode(ts, ts->fix_active_mode); + + input_info(true, ts->dev, "%s: %s\n", __func__, "OK"); +} + +static int touch_aging_mode_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + ts->touch_aging_mode = sec->cmd_param[0]; + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void touch_aging_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + char reg[3]; + int ret = 0; + + ret = touch_aging_mode_save(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: %s\n", __func__, "NG"); + return; + } + + if (ts->touch_aging_mode) { + reg[0] = 0xA0; + reg[1] = 0x03; + reg[2] = 0x20; + + ts->stm_ts_write(ts, ®[0], 3, NULL, 0); + } else { + stm_ts_reinit(ts); + } + + input_info(true, ts->dev, "%s: %s\n", __func__, "OK"); +} + +static int ear_detect_enable_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + if (!ts->plat_data->support_ear_detect) { + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + return SEC_ERROR; + } else if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 3) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + ts->plat_data->ed_enable = sec->cmd_param[0]; + input_info(true, ts->dev, "%s: %s\n", + __func__, ts->plat_data->ed_enable ? "on" : "off"); + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void ear_detect_enable(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret = 0; + + ret = ear_detect_enable_save(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: %s\n", __func__, "NG"); + return; + } + + if (ts->probe_done) + ret = stm_ts_ear_detect_enable(ts, ts->plat_data->ed_enable); + else + input_info(true, ts->dev, "%s: probe is not done\n", __func__); + + if (ret < 0) + sec->cmd_state = SEC_CMD_STATUS_FAIL; + + input_info(true, ts->dev, "%s: %s\n", __func__, sec->cmd_state == SEC_CMD_STATUS_OK ? "OK" : "NG"); +} + +static int pocket_mode_enable_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + if (!ts->plat_data->support_ear_detect) { + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + return SEC_ERROR; + } else if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + ts->plat_data->pocket_mode = sec->cmd_param[0]; + input_info(true, ts->dev, "%s: %s\n", + __func__, ts->plat_data->pocket_mode ? "enable" : "disable"); + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void pocket_mode_enable(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret = 0; + + ret = pocket_mode_enable_save(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: %s\n", __func__, "NG"); + return; + } + + ret = stm_ts_pocket_mode_enable(ts, ts->plat_data->pocket_mode); + if (ret < 0) + sec->cmd_state = SEC_CMD_STATUS_FAIL; + + input_info(true, ts->dev, "%s: %s\n", __func__, sec->cmd_state == SEC_CMD_STATUS_OK ? "OK" : "NG"); +} + +static int low_sensitivity_mode_enable_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 3) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + ts->plat_data->low_sensitivity_mode = sec->cmd_param[0]; + input_info(true, ts->dev, "%s: %s\n", __func__, + ts->plat_data->low_sensitivity_mode ? "enable" : "disable"); + sec->cmd_state = SEC_CMD_STATUS_OK; + +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) +#if IS_ENABLED(CONFIG_INPUT_SEC_TRUSTED_TOUCH) + if (atomic_read(&ts->plat_data->pvm->trusted_touch_enabled)) { + input_info(true, ts->dev, "%s trusted touch is enabled. skip\n", __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } +#endif +#endif + + return SEC_SUCCESS; +} + +static void low_sensitivity_mode_enable(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret; + u8 reg[3] = { 0 }; + + ret = low_sensitivity_mode_enable_save(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + mutex_lock(&ts->plat_data->enable_mutex); + reg[0] = STM_TS_CMD_FUNCTION_SET_LOW_SENSITIVITY_MODE; + reg[1] = sec->cmd_param[0]; + ret = ts->stm_ts_write(ts, ®[0], 1, ®[1], 1); + if (ret < 0) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + } else { + sec->cmd_state = SEC_CMD_STATUS_OK; + } + mutex_unlock(&ts->plat_data->enable_mutex); + + input_info(true, ts->dev, "%s: %s\n", __func__, sec->cmd_state == SEC_CMD_STATUS_OK ? "OK" : "NG"); +} + +static int set_sip_mode_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + ts->sip_mode = sec->cmd_param[0]; + input_info(true, ts->dev, "%s: %s\n", __func__, ts->sip_mode ? "enable" : "disable"); + + sec->cmd_state = SEC_CMD_STATUS_OK; + return SEC_SUCCESS; +} + +static void set_sip_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret; + + ret = set_sip_mode_save(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: %s\n", __func__, "NG"); + return; + } + + ret = stm_ts_sip_mode_enable(ts); + if (ret < 0) + sec->cmd_state = SEC_CMD_STATUS_FAIL; + + input_info(true, ts->dev, "%s: %s\n", __func__, sec->cmd_state == SEC_CMD_STATUS_OK ? "OK" : "NG"); +} + +static int set_game_mode_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + ts->game_mode = sec->cmd_param[0]; + input_info(true, ts->dev, "%s: %s\n", __func__, ts->game_mode ? "enable" : "disable"); + + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void set_game_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret; + + ret = set_game_mode_save(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: %s\n", __func__, "NG"); + return; + } + + ret = stm_ts_game_mode_enable(ts); + if (ret < 0) + sec->cmd_state = SEC_CMD_STATUS_FAIL; + + input_info(true, ts->dev, "%s: %s\n", __func__, sec->cmd_state == SEC_CMD_STATUS_OK ? "OK" : "NG"); +} + +static int set_note_mode_save(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + ts->note_mode = sec->cmd_param[0]; + input_info(true, ts->dev, "%s: %s\n", __func__, ts->note_mode ? "enable" : "disable"); + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void set_note_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + int ret; + + ret = set_note_mode_save(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: %s\n", __func__, "NG"); + return; + } + + ret = stm_ts_note_mode_enable(ts); + if (ret < 0) + sec->cmd_state = SEC_CMD_STATUS_FAIL; + + input_info(true, ts->dev, "%s: %s\n", __func__, sec->cmd_state == SEC_CMD_STATUS_OK ? "OK" : "NG"); +} + +static void not_support_cmd(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + + input_info(true, ts->dev, "%s: %s\n", __func__, "NA"); +} + +struct sec_cmd sec_cmds[] = { + {SEC_CMD_V2("fw_update", fw_update, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("get_fw_ver_bin", get_fw_ver_bin, NULL, CHECK_ALL, WAIT_RESULT),}, + {SEC_CMD_V2("get_fw_ver_ic", get_fw_ver_ic, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("get_config_ver", get_config_ver, NULL, CHECK_ALL, WAIT_RESULT),}, + {SEC_CMD_V2("module_off_master", module_off_master, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("module_on_master", module_on_master, NULL, CHECK_POWEROFF, WAIT_RESULT),}, + {SEC_CMD_V2("get_chip_vendor", get_chip_vendor, NULL, CHECK_ALL, WAIT_RESULT),}, + {SEC_CMD_V2("get_chip_name", get_chip_name, NULL, CHECK_ALL, WAIT_RESULT),}, + {SEC_CMD_V2("run_jitter_test", run_jitter_test, NULL, CHECK_POWERON, WAIT_RESULT),}, + {SEC_CMD_V2("run_mutual_jitter", run_mutual_jitter, NULL, CHECK_POWERON, WAIT_RESULT),}, + {SEC_CMD_V2("run_self_jitter", run_self_jitter, NULL, CHECK_POWERON, WAIT_RESULT),}, + {SEC_CMD_V2("run_jitter_delta_test", run_jitter_delta_test, NULL, CHECK_POWERON, WAIT_RESULT),}, + {SEC_CMD_V2("run_lcdoff_mutual_jitter", run_lcdoff_mutual_jitter, NULL, CHECK_POWERON, WAIT_RESULT),}, + {SEC_CMD_V2("get_wet_mode", get_wet_mode, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("get_x_num", get_x_num, NULL, CHECK_ALL, WAIT_RESULT),}, + {SEC_CMD_V2("get_y_num", get_y_num, NULL, CHECK_ALL, WAIT_RESULT),}, + {SEC_CMD_V2("get_checksum_data", get_checksum_data, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("get_crc_check", check_fw_corruption, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_reference_read", run_reference_read, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("get_reference", get_reference, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_rawcap_read", run_rawcap_read, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_rawcap_read_all", run_rawcap_read_all, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("get_rawcap", get_rawcap, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_lp_single_ended_rawcap_read", run_lp_single_ended_rawcap_read, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_lp_single_ended_rawcap_read_all", run_lp_single_ended_rawcap_read_all, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_low_frequency_rawcap_read", run_low_frequency_rawcap_read, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_low_frequency_rawcap_read_all", run_low_frequency_rawcap_read_all, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_high_frequency_rawcap_read", run_high_frequency_rawcap_read, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_high_frequency_rawcap_read_all", run_high_frequency_rawcap_read_all, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_delta_read", run_delta_read, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("get_delta", get_delta, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_prox_intensity_read_all", run_prox_intensity_read_all, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_cs_raw_read_all", run_cs_raw_read_all, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_cs_delta_read_all", run_cs_delta_read_all, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_rawdata_read_all_for_ghost", run_rawdata_read_all, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_ix_data_read", run_ix_data_read, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_ix_data_read_all", run_ix_data_read_all, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_self_raw_read", run_self_raw_read, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_self_raw_read_all", run_self_raw_read_all, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_trx_short_test", run_trx_short_test, NULL, CHECK_ON_LP, WAIT_RESULT),}, +#ifdef TCLM_CONCEPT + {SEC_CMD_V2("get_pat_information", get_pat_information, NULL, CHECK_ALL, WAIT_RESULT),}, + {SEC_CMD_V2("set_external_factory", set_external_factory, NULL, CHECK_ALL, WAIT_RESULT),}, + {SEC_CMD_V2("tclm_test_cmd", tclm_test_cmd, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("get_calibration", get_calibration, NULL, CHECK_ALL, WAIT_RESULT),}, +#endif + {SEC_CMD_V2("run_factory_miscalibration", run_factory_miscalibration, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_factory_miscalibration_read_all", run_factory_miscalibration_read_all, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_miscalibration", run_miscalibration, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("check_connection", check_connection, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("get_cx_data", get_cx_data, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_active_cx_data_read", run_active_cx_data_read, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_cx_data_read", run_cx_data_read, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_cx_data_read_all", run_cx_data_read_all, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_cx_gap_data_rx_all", run_cx_gap_data_x_all, NULL, CHECK_ALL, WAIT_RESULT),}, + {SEC_CMD_V2("run_cx_gap_data_tx_all", run_cx_gap_data_y_all, NULL, CHECK_ALL, WAIT_RESULT),}, + {SEC_CMD_V2("get_gap_data_all", get_gap_data_all, NULL, CHECK_ALL, WAIT_RESULT),}, + {SEC_CMD_V2("get_strength_all_data", get_strength_all_data, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("set_tsp_test_result", set_tsp_test_result, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("get_tsp_test_result", get_tsp_test_result, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("increase_disassemble_count", increase_disassemble_count, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("get_disassemble_count", get_disassemble_count, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("get_osc_trim_error", get_osc_trim_error, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("get_osc_trim_info", get_osc_trim_info, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_elvss_test", run_elvss_test, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_snr_non_touched", run_snr_non_touched, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_snr_touched", run_snr_touched, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_sram_test", run_sram_test, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_polarity_test", run_polarity_test, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_force_calibration", run_force_calibration, run_force_calibration_save, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("set_factory_level", set_factory_level, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("factory_cmd_result_all", factory_cmd_result_all, NULL, CHECK_ALL, WAIT_RESULT),}, + {SEC_CMD_V2("factory_cmd_result_all_imagetest", factory_cmd_result_all_imagetest, NULL, CHECK_ALL, WAIT_RESULT),}, + {SEC_CMD_V2_H("glove_mode", glove_mode, glove_mode_save, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("clear_cover_mode", clear_cover_mode, clear_cover_mode_save, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2("set_wirelesscharger_mode", set_wirelesscharger_mode, set_wirelesscharger_mode_save, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2("set_grip_data", set_grip_data, set_grip_data_save, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2("dead_zone_enable", dead_zone_enable, dead_zone_enable_save, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("spay_enable", spay_enable, spay_enable_save, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("singletap_enable", singletap_enable, singletap_enable_save, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("aot_enable", aot_enable, aot_enable_save, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("aod_enable", aod_enable, aod_enable_save, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2("set_aod_rect", set_aod_rect, set_aod_rect_save, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2("get_aod_rect", get_aod_rect, NULL, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2("fod_enable", fod_enable, fod_enable_save, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("fod_lp_mode", fod_lp_mode, NULL, CHECK_ALL, EXIT_RESULT),}, + {SEC_CMD_V2("set_fod_rect", set_fod_rect, set_fod_rect_save, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("fp_int_control", fp_int_control, NULL, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("external_noise_mode", external_noise_mode, external_noise_mode_save, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("brush_enable", brush_enable, brush_enable_save, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("set_touchable_area", set_touchable_area, set_touchable_area_save, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2("debug", debug, NULL, CHECK_ALL, EXIT_RESULT),}, +#ifdef ENABLE_RAWDATA_SERVICE + {SEC_CMD_V2("rawdata_init", rawdata_init, rawdata_init_save, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2("blocking_palm", blocking_palm, NULL, CHECK_ALL, EXIT_RESULT),}, +#endif + {SEC_CMD_V2_H("fix_active_mode", fix_active_mode, fix_active_mode_save, CHECK_POWERON, EXIT_RESULT),}, + {SEC_CMD_V2("touch_aging_mode", touch_aging_mode, touch_aging_mode_save, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("ear_detect_enable", ear_detect_enable, ear_detect_enable_save, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("pocket_mode_enable", pocket_mode_enable, pocket_mode_enable_save, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2("low_sensitivity_mode_enable", low_sensitivity_mode_enable, low_sensitivity_mode_enable_save, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2("set_sip_mode", set_sip_mode, set_sip_mode_save, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2("set_game_mode", set_game_mode, set_game_mode_save, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2("set_note_mode", set_note_mode, set_note_mode_save, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2("run_interrupt_gpio_test", run_interrupt_gpio_test, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("not_support_cmd", not_support_cmd, NULL, CHECK_ALL, EXIT_RESULT),}, +}; + +int stm_ts_fn_init(struct stm_ts_data *ts) +{ + int retval = 0; + + retval = sec_cmd_init(&ts->sec, ts->dev, sec_cmds, + ARRAY_SIZE(sec_cmds), SEC_CLASS_DEVT_TSP, &cmd_attr_group); + if (retval < 0) { + input_err(true, ts->dev, + "%s: Failed to sec_cmd_init\n", __func__); + goto exit; + } + + retval = sysfs_create_link(&ts->sec.fac_dev->kobj, + &ts->plat_data->input_dev->dev.kobj, "input"); + if (retval < 0) { + input_err(true, ts->dev, + "%s: Failed to create input symbolic link\n", + __func__); + sec_cmd_exit(&ts->sec, SEC_CLASS_DEVT_TSP); + goto exit; + } + + return 0; +exit: + return retval; +} + +void stm_ts_fn_remove(struct stm_ts_data *ts) +{ + input_err(true, ts->dev, "%s\n", __func__); + + sysfs_remove_link(&ts->sec.fac_dev->kobj, "input"); + + sec_cmd_exit(&ts->sec, SEC_CLASS_DEVT_TSP); +} + +MODULE_LICENSE("GPL"); diff --git a/drivers/input/sec_input/stm_spi/stm_core.c b/drivers/input/sec_input/stm_spi/stm_core.c new file mode 100644 index 000000000000..91288687260f --- /dev/null +++ b/drivers/input/sec_input/stm_spi/stm_core.c @@ -0,0 +1,1536 @@ +/* drivers/input/sec_input/stm/stm_core.c + * + * Copyright (C) 2020 Samsung Electronics Co., Ltd. + * + * Core file for Samsung TSC driver + * + * 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. + */ + +#include "stm_dev.h" +#include "stm_reg.h" + +struct stm_ts_data *g_ts; + +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) +static irqreturn_t secure_filter_interrupt(struct stm_ts_data *ts) +{ + if (atomic_read(&ts->plat_data->secure_enabled) == SECURE_TOUCH_ENABLE) { + if (atomic_cmpxchg(&ts->plat_data->secure_pending_irqs, 0, 1) == 0) { + sysfs_notify(&ts->plat_data->input_dev->dev.kobj, NULL, "secure_touch"); + + } else { + input_info(true, ts->dev, "%s: pending irq:%d\n", + __func__, (int)atomic_read(&ts->plat_data->secure_pending_irqs)); + } + + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +/** + * Sysfs attr group for secure touch & interrupt handler for Secure world. + * @atomic : syncronization for secure_enabled + * @pm_runtime : set rpm_resume or rpm_ilde + */ +static ssize_t secure_touch_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct stm_ts_data *ts = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d", atomic_read(&ts->plat_data->secure_enabled)); +} + +static ssize_t secure_touch_enable_store(struct device *dev, + struct device_attribute *addr, const char *buf, size_t count) +{ + struct stm_ts_data *ts = dev_get_drvdata(dev); + int ret; + unsigned long data; + + if (count > 2) { + input_err(true, ts->dev, + "%s: cmd length is over (%s,%d)!!\n", + __func__, buf, (int)strlen(buf)); + return -EINVAL; + } + + ret = kstrtoul(buf, 10, &data); + if (ret != 0) { + input_err(true, ts->dev, "%s: failed to read:%d\n", + __func__, ret); + return -EINVAL; + } + + if (data == 1) { + if (atomic_read(&ts->reset_is_on_going)) { + input_err(true, ts->dev, "%s: reset is on going because i2c fail\n", __func__); + return -EBUSY; + } + + /* Enable Secure World */ + if (atomic_read(&ts->plat_data->secure_enabled) == SECURE_TOUCH_ENABLE) { + input_err(true, ts->dev, "%s: already enabled\n", __func__); + return -EBUSY; + } + + sec_delay(200); + + /* syncronize_irq -> disable_irq + enable_irq + * concern about timing issue. + */ + disable_irq(ts->irq); + + /* Release All Finger */ + stm_ts_release_all_finger(ts); + + if (pm_runtime_get_sync(ts->plat_data->bus_master->parent) < 0) { + enable_irq(ts->irq); + input_err(true, ts->dev, "%s: failed to get pm_runtime\n", __func__); + return -EIO; + } + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&ts->stm_input_nb, NOTIFIER_SECURE_TOUCH_ENABLE, NULL); +#endif + reinit_completion(&ts->plat_data->secure_powerdown); + reinit_completion(&ts->plat_data->secure_interrupt); + + atomic_set(&ts->plat_data->secure_enabled, 1); + atomic_set(&ts->plat_data->secure_pending_irqs, 0); + + enable_irq(ts->irq); + + input_info(true, ts->dev, "%s: secure touch enable\n", __func__); + } else if (data == 0) { + /* Disable Secure World */ + if (atomic_read(&ts->plat_data->secure_enabled) == SECURE_TOUCH_DISABLE) { + input_err(true, ts->dev, "%s: already disabled\n", __func__); + return count; + } + + sec_delay(200); + + pm_runtime_put_sync(ts->plat_data->bus_master->parent); + atomic_set(&ts->plat_data->secure_enabled, 0); + + sysfs_notify(&ts->plat_data->input_dev->dev.kobj, NULL, "secure_touch"); + + sec_delay(10); + + stm_ts_irq_thread(ts->irq, ts); + complete(&ts->plat_data->secure_interrupt); + complete(&ts->plat_data->secure_powerdown); + + input_info(true, ts->dev, "%s: secure touch disable\n", __func__); + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&ts->stm_input_nb, NOTIFIER_SECURE_TOUCH_DISABLE, NULL); +#endif + } else { + input_err(true, ts->dev, "%s: unsupport value:%ld\n", __func__, data); + return -EINVAL; + } + + return count; +} + +static ssize_t secure_touch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct stm_ts_data *ts = dev_get_drvdata(dev); + int val = 0; + + if (atomic_read(&ts->plat_data->secure_enabled) == SECURE_TOUCH_DISABLE) { + input_err(true, ts->dev, "%s: disabled\n", __func__); + return -EBADF; + } + + if (atomic_cmpxchg(&ts->plat_data->secure_pending_irqs, -1, 0) == -1) { + input_err(true, ts->dev, "%s: pending irq -1\n", __func__); + return -EINVAL; + } + + if (atomic_cmpxchg(&ts->plat_data->secure_pending_irqs, 1, 0) == 1) { + val = 1; + input_err(true, ts->dev, "%s: pending irq is %d\n", + __func__, atomic_read(&ts->plat_data->secure_pending_irqs)); + } + + complete(&ts->plat_data->secure_interrupt); + + return snprintf(buf, PAGE_SIZE, "%u", val); +} + +static ssize_t secure_ownership_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "1"); +} + +static int secure_touch_init(struct stm_ts_data *ts) +{ + input_info(true, ts->dev, "%s\n", __func__); + + init_completion(&ts->plat_data->secure_interrupt); + init_completion(&ts->plat_data->secure_powerdown); + + return 0; +} + +static void secure_touch_stop(struct stm_ts_data *ts, bool stop) +{ + if (atomic_read(&ts->plat_data->secure_enabled)) { + atomic_set(&ts->plat_data->secure_pending_irqs, -1); + + sysfs_notify(&ts->plat_data->input_dev->dev.kobj, NULL, "secure_touch"); + + if (stop) + wait_for_completion_interruptible(&ts->plat_data->secure_powerdown); + + input_info(true, ts->dev, "%s: %d\n", __func__, stop); + } +} + +static DEVICE_ATTR_RW(secure_touch_enable); +static DEVICE_ATTR_RO(secure_touch); +static DEVICE_ATTR_RO(secure_ownership); +static struct attribute *secure_attr[] = { + &dev_attr_secure_touch_enable.attr, + &dev_attr_secure_touch.attr, + &dev_attr_secure_ownership.attr, + NULL, +}; + +static struct attribute_group secure_attr_group = { + .attrs = secure_attr, +}; +#endif + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) +static int stm_touch_notify_call(struct notifier_block *n, unsigned long data, void *v) +{ + struct stm_ts_data *ts = container_of(n, struct stm_ts_data, stm_input_nb); + u8 reg[3]; + int ret = 0; + + if (!ts->info_work_done) { + input_info(true, ts->dev, "%s: info work is not done. skip\n", __func__); + return ret; + } + + if (atomic_read(&ts->plat_data->shutdown_called)) + return -ENODEV; + + reg[0] = STM_TS_CMD_SET_FUNCTION_ONOFF; + + switch (data) { + case NOTIFIER_TSP_BLOCKING_REQUEST: + if (ts->plat_data->wirelesscharger_mode != TYPE_WIRELESS_CHARGER_NONE) { + input_info(true, ts->dev, "%s: Skip tsp block on wireless charger\n", __func__); + } else { + reg[1] = STM_TS_CMD_FUNCTION_SET_TSP_BLOCK; + reg[2] = 1; + ret = ts->stm_ts_write(ts, reg, 3, NULL, 0); + input_info(true, ts->dev, "%s: tsp block, ret=%d\n", __func__, ret); + sec_input_gesture_report(ts->dev, SPONGE_EVENT_TYPE_TSP_SCAN_BLOCK, 0, 0); + } + break; + case NOTIFIER_TSP_BLOCKING_RELEASE: + if (ts->plat_data->wirelesscharger_mode != TYPE_WIRELESS_CHARGER_NONE) { + input_info(true, ts->dev, "%s: Skip tsp unblock on wireless charger\n", __func__); + } else { + reg[1] = STM_TS_CMD_FUNCTION_SET_TSP_BLOCK; + reg[2] = 0; + ret = ts->stm_ts_write(ts, reg, 3, NULL, 0); + input_info(true, ts->dev, "%s: tsp unblock, ret=%d\n", __func__, ret); + sec_input_gesture_report(ts->dev, SPONGE_EVENT_TYPE_TSP_SCAN_UNBLOCK, 0, 0); + } + break; + case NOTIFIER_WACOM_PEN_INSERT: + reg[1] = STM_TS_CMD_FUNCTION_SET_PEN_DETECTION; + reg[2] = 0; + ret = ts->stm_ts_write(ts, reg, 3, NULL, 0); + input_info(true, ts->dev, "%s: pen in detect, ret=%d\n", __func__, ret); + break; + case NOTIFIER_WACOM_PEN_REMOVE: + reg[1] = STM_TS_CMD_FUNCTION_SET_PEN_DETECTION; + reg[2] = 1; + ret = ts->stm_ts_write(ts, reg, 3, NULL, 0); + input_info(true, ts->dev, "%s: pen out detect, ret=%d\n", __func__, ret); + break; + case NOTIFIER_WACOM_SAVING_MODE_ON: + reg[1] = STM_TS_CMD_FUNCTION_SET_PEN_SAVING_MODE; + reg[2] = 1; + ret = ts->stm_ts_write(ts, reg, 3, NULL, 0); + input_info(true, ts->dev, "%s: pen saving mode on, ret=%d\n", __func__, ret); + break; + case NOTIFIER_WACOM_SAVING_MODE_OFF: + reg[1] = STM_TS_CMD_FUNCTION_SET_PEN_SAVING_MODE; + reg[2] = 0; + ret = ts->stm_ts_write(ts, reg, 3, NULL, 0); + input_info(true, ts->dev, "%s: pen saving mode off, ret=%d\n", __func__, ret); + break; + case NOTIFIER_WACOM_PEN_HOVER_IN: + reg[1] = STM_TS_CMD_FUNCTION_SET_HOVER_DETECTION; + reg[2] = 1; + ret = ts->stm_ts_write(ts, reg, 3, NULL, 0); + input_info(true, ts->dev, "%s: pen hover in detect, ret=%d\n", __func__, ret); + break; + case NOTIFIER_WACOM_PEN_HOVER_OUT: + reg[1] = STM_TS_CMD_FUNCTION_SET_HOVER_DETECTION; + reg[2] = 0; + ret = ts->stm_ts_write(ts, reg, 3, NULL, 0); + input_info(true, ts->dev, "%s: pen hover out detect, ret=%d\n", __func__, ret); + break; + default: + break; + } + return ret; +} +#endif + +void stm_ts_reinit(void *data) +{ + struct stm_ts_data *ts = (struct stm_ts_data *)data; + int ret = 0; + int retry = 3; + + do { + ret = stm_ts_systemreset(ts, 0); + if (ret < 0) + stm_ts_reset(ts, 20); + else + break; + } while (--retry); + + if (retry == 0) { + input_err(true, ts->dev, "%s: Failed to system reset\n", __func__); + goto out; + } + + input_info(true, ts->dev, + "%s: charger=0x%x, wc=0x%x, touch_functions=0x%x, Power mode=%d\n", + __func__, ts->charger_mode, ts->plat_data->wirelesscharger_mode, + ts->plat_data->touch_functions, atomic_read(&ts->plat_data->power_state)); + + atomic_set(&ts->plat_data->touch_noise_status, 0); + atomic_set(&ts->plat_data->touch_pre_noise_status, 0); + ts->plat_data->wet_mode = 0; + + ts->stm_ts_command(ts, STM_TS_CMD_CLEAR_ALL_EVENT, false); + stm_ts_release_all_finger(ts); + + if (ts->plat_data->wirelesscharger_mode != TYPE_WIRELESS_CHARGER_NONE) + stm_ts_set_wirelesscharger_mode(ts); + + if (ts->charger_mode != TYPE_WIRE_CHARGER_NONE) + stm_ts_set_wirecharger_mode(ts); + + stm_ts_set_cover_type(ts, ts->plat_data->touch_functions & STM_TS_TOUCHTYPE_BIT_COVER); + + stm_ts_set_custom_library(ts); + stm_ts_set_press_property(ts); + stm_ts_set_fod_finger_merge(ts); + + if (ts->plat_data->support_fod && ts->plat_data->fod_data.set_val) + stm_ts_set_fod_rect(ts); + + /* Power mode */ + if (atomic_read(&ts->plat_data->power_state) == SEC_INPUT_STATE_LPM) { + stm_ts_set_opmode(ts, STM_TS_OPMODE_LOWPOWER); + sec_delay(50); + if (ts->plat_data->lowpower_mode & SEC_TS_MODE_SPONGE_AOD) + stm_ts_set_aod_rect(ts); + } else { + sec_input_set_grip_type(ts->dev, ONLY_EDGE_HANDLER); + + stm_ts_set_external_noise_mode(ts, EXT_NOISE_MODE_MAX); + + if (ts->plat_data->touchable_area) { + ret = stm_ts_set_touchable_area(ts); + if (ret < 0) + goto out; + } + } + + if (ts->plat_data->ed_enable) + stm_ts_ear_detect_enable(ts, ts->plat_data->ed_enable); + if (ts->plat_data->pocket_mode) + stm_ts_pocket_mode_enable(ts, ts->plat_data->pocket_mode); + if (ts->sip_mode) + stm_ts_sip_mode_enable(ts); + if (ts->note_mode) + stm_ts_note_mode_enable(ts); + if (ts->game_mode) + stm_ts_game_mode_enable(ts); + if (ts->dead_zone) + stm_ts_dead_zone_enable(ts); + if (ts->fix_active_mode) + stm_ts_fix_active_mode(ts, true); +out: + stm_ts_set_scanmode(ts, ts->scan_mode); +} +/* + * don't need it in interrupt handler in reality, but, need it in vendor IC for requesting vendor IC. + * If you are requested additional i2c protocol in interrupt handler by vendor. + * please add it in stm_ts_external_func. + */ +static void stm_ts_external_func(struct stm_ts_data *ts) +{ + sec_input_set_temperature(ts->dev, SEC_INPUT_SET_TEMPERATURE_IN_IRQ); + +} + +static void stm_ts_coord_parsing(struct stm_ts_data *ts, struct stm_ts_event_coordinate *p_event_coord, u8 t_id) +{ + ts->plat_data->coord[t_id].id = t_id; + ts->plat_data->coord[t_id].action = p_event_coord->tchsta; + ts->plat_data->coord[t_id].x = (p_event_coord->x_11_4 << 4) | (p_event_coord->x_3_0); + ts->plat_data->coord[t_id].y = (p_event_coord->y_11_4 << 4) | (p_event_coord->y_3_0); + ts->plat_data->coord[t_id].z = p_event_coord->z & 0x3F; + ts->plat_data->coord[t_id].ttype = p_event_coord->ttype_3_2 << 2 | p_event_coord->ttype_1_0 << 0; + ts->plat_data->coord[t_id].major = p_event_coord->major; + ts->plat_data->coord[t_id].minor = p_event_coord->minor; + + if (!ts->plat_data->coord[t_id].palm && (ts->plat_data->coord[t_id].ttype == STM_TS_TOUCHTYPE_PALM)) + ts->plat_data->coord[t_id].palm_count++; + + ts->plat_data->coord[t_id].palm = (ts->plat_data->coord[t_id].ttype == STM_TS_TOUCHTYPE_PALM); + if (ts->plat_data->coord[t_id].palm) + ts->plat_data->palm_flag |= (1 << t_id); + else + ts->plat_data->palm_flag &= ~(1 << t_id); + + ts->plat_data->coord[t_id].left_event = p_event_coord->left_event; + + ts->plat_data->coord[t_id].noise_level = max(ts->plat_data->coord[t_id].noise_level, + p_event_coord->noise_level); + ts->plat_data->coord[t_id].max_strength = max(ts->plat_data->coord[t_id].max_strength, + p_event_coord->max_strength); + ts->plat_data->coord[t_id].hover_id_num = max_t(u8, ts->plat_data->coord[t_id].hover_id_num, + p_event_coord->hover_id_num); + ts->plat_data->coord[t_id].noise_status = p_event_coord->noise_status; + ts->plat_data->coord[t_id].freq_id = p_event_coord->freq_id; + ts->plat_data->coord[t_id].fod_debug = p_event_coord->fod_debug; + + if (ts->plat_data->coord[t_id].z <= 0) + ts->plat_data->coord[t_id].z = 1; +} + +int stm_ts_fod_vi_event(struct stm_ts_data *ts) +{ + int ret = 0; + + ts->plat_data->fod_data.vi_data[0] = SEC_TS_CMD_SPONGE_FOD_POSITION; + ts->plat_data->fod_data.vi_data[1] = 0x00; + ret = ts->stm_ts_read_sponge(ts, ts->plat_data->fod_data.vi_data, ts->plat_data->fod_data.vi_size); + if (ret < 0) + input_info(true, ts->dev, "%s: failed read fod vi\n", __func__); + + return ret; +} + +static void stm_ts_gesture_event(struct stm_ts_data *ts, u8 *event_buff) +{ + struct stm_ts_gesture_status *p_gesture_status; + int x, y; + + p_gesture_status = (struct stm_ts_gesture_status *)event_buff; + + x = (p_gesture_status->gesture_data_1 << 4) | (p_gesture_status->gesture_data_3 >> 4); + y = (p_gesture_status->gesture_data_2 << 4) | (p_gesture_status->gesture_data_3 & 0x0F); + + if (p_gesture_status->stype == STM_TS_SPONGE_EVENT_SWIPE_UP) { + sec_input_gesture_report(ts->dev, SPONGE_EVENT_TYPE_SPAY, 0, 0); + } else if (p_gesture_status->stype == STM_TS_GESTURE_CODE_DOUBLE_TAP) { + if (p_gesture_status->gesture_id == STM_TS_SPONGE_EVENT_GESTURE_ID_AOD) { + sec_input_gesture_report(ts->dev, SPONGE_EVENT_TYPE_AOD_DOUBLETAB, x, y); + } else if (p_gesture_status->gesture_id == STM_TS_SPONGE_EVENT_GESTURE_ID_DOUBLETAP_TO_WAKEUP) { + input_info(true, ts->dev, "%s: AOT\n", __func__); + input_report_key(ts->plat_data->input_dev, KEY_WAKEUP, 1); + input_sync(ts->plat_data->input_dev); + input_report_key(ts->plat_data->input_dev, KEY_WAKEUP, 0); + input_sync(ts->plat_data->input_dev); + } + } else if (p_gesture_status->stype == STM_TS_SPONGE_EVENT_SINGLETAP) { + sec_input_gesture_report(ts->dev, SPONGE_EVENT_TYPE_SINGLE_TAP, x, y); + } else if (p_gesture_status->stype == STM_TS_SPONGE_EVENT_PRESS) { + if (p_gesture_status->gesture_id == STM_TS_SPONGE_EVENT_GESTURE_ID_FOD_LONG || + p_gesture_status->gesture_id == STM_TS_SPONGE_EVENT_GESTURE_ID_FOD_NORMAL) { + sec_input_gesture_report(ts->dev, SPONGE_EVENT_TYPE_FOD_PRESS, x, y); + input_info(true, ts->dev, "%s: FOD %sPRESS\n", + __func__, p_gesture_status->gesture_id ? "" : "LONG"); + } else if (p_gesture_status->gesture_id == STM_TS_SPONGE_EVENT_GESTURE_ID_FOD_RELEASE) { + sec_input_gesture_report(ts->dev, SPONGE_EVENT_TYPE_FOD_RELEASE, x, y); + input_info(true, ts->dev, "%s: FOD RELEASE\n", __func__); + memset(ts->plat_data->fod_data.vi_data, 0x0, ts->plat_data->fod_data.vi_size); + } else if (p_gesture_status->gesture_id == STM_TS_SPONGE_EVENT_GESTURE_ID_FOD_OUT) { + sec_input_gesture_report(ts->dev, SPONGE_EVENT_TYPE_FOD_OUT, x, y); + input_info(true, ts->dev, "%s: FOD OUT\n", __func__); + } else if (p_gesture_status->gesture_id == STM_TS_SPONGE_EVENT_GESTURE_ID_FOD_VI) { + if ((ts->plat_data->lowpower_mode & SEC_TS_MODE_SPONGE_PRESS) && ts->plat_data->support_fod_lp_mode) + stm_ts_fod_vi_event(ts); + } else { + input_info(true, ts->dev, "%s: invalid id %d\n", + __func__, p_gesture_status->gesture_id); + } + } else if (p_gesture_status->stype == STM_TS_GESTURE_CODE_DUMPFLUSH) { +#if IS_ENABLED(CONFIG_TOUCHSCREEN_DUMP_MODE) + if (ts->sponge_inf_dump) { + if (atomic_read(&ts->plat_data->power_state) == SEC_INPUT_STATE_LPM) { + if (p_gesture_status->gesture_id == STM_TS_SPONGE_DUMP_0) + stm_ts_sponge_dump_flush(ts, STM_TS_SPONGE_DUMP_0); + if (p_gesture_status->gesture_id == STM_TS_SPONGE_DUMP_1) + stm_ts_sponge_dump_flush(ts, STM_TS_SPONGE_DUMP_1); + } else { + ts->sponge_dump_delayed_flag = true; + ts->sponge_dump_delayed_area = p_gesture_status->gesture_id; + } + } +#endif + } else if (p_gesture_status->stype == STM_TS_VENDOR_EVENT_LARGEPALM) { //fold + input_info(true, ts->dev, "%s: LARGE PALM %s\n", __func__, + p_gesture_status->gesture_id == 1 ? "RELEASED" : "PRESSED"); + if (p_gesture_status->gesture_id == 0x01) + input_report_key(ts->plat_data->input_dev, BTN_LARGE_PALM, 0); + else + input_report_key(ts->plat_data->input_dev, BTN_LARGE_PALM, 1); + input_sync(ts->plat_data->input_dev); + } else if (p_gesture_status->stype == STM_TS_SPONGE_EVENT_LONGPRESS) { + sec_input_gesture_report(ts->dev, SPONGE_EVENT_TYPE_LONG_PRESS, x, y); + input_info(true, ts->dev, "%s: LONG PRESS\n", __func__); + } +} + +static void stm_ts_coordinate_event(struct stm_ts_data *ts, u8 *event_buff) +{ + struct stm_ts_event_coordinate *p_event_coord; + u8 t_id = 0; + + if (atomic_read(&ts->plat_data->power_state) != SEC_INPUT_STATE_POWER_ON) { + input_err(true, ts->dev, + "%s: device is closed %x %x %x %x %x %x %x %x\n", __func__, + event_buff[0], event_buff[1], event_buff[2], + event_buff[3], event_buff[4], event_buff[5], + event_buff[6], event_buff[7]); + return; + } + + p_event_coord = (struct stm_ts_event_coordinate *)event_buff; + ts->block_rawdata = p_event_coord->game_mode; + + t_id = p_event_coord->tid; + + if (t_id < SEC_TS_SUPPORT_TOUCH_COUNT) { + ts->plat_data->prev_coord[t_id] = ts->plat_data->coord[t_id]; + stm_ts_coord_parsing(ts, p_event_coord, t_id); + + if ((ts->plat_data->coord[t_id].ttype == STM_TS_TOUCHTYPE_NORMAL) + || (ts->plat_data->coord[t_id].ttype == STM_TS_TOUCHTYPE_PALM) + || (ts->plat_data->coord[t_id].ttype == STM_TS_TOUCHTYPE_WET) + || (ts->plat_data->coord[t_id].ttype == STM_TS_TOUCHTYPE_GLOVE)) { + sec_input_coord_event_fill_slot(ts->dev, t_id); + } else { + input_err(true, ts->dev, + "%s: do not support coordinate type(%d)\n", + __func__, ts->plat_data->coord[t_id].ttype); + } + } else { + input_err(true, ts->dev, "%s: tid(%d) is out of range\n", __func__, t_id); + } +} + +static void stm_ts_status_event(struct stm_ts_data *ts, u8 *event_buff) +{ + struct stm_ts_event_status *p_event_status; + + p_event_status = (struct stm_ts_event_status *)event_buff; + + if (p_event_status->stype > 0) + input_info(true, ts->dev, "%s: STATUS %x %x %x %x %x %x %x %x\n", __func__, + event_buff[0], event_buff[1], event_buff[2], + event_buff[3], event_buff[4], event_buff[5], + event_buff[6], event_buff[7]); + + if (p_event_status->stype == STM_TS_EVENT_STATUSTYPE_ERROR) { + if (p_event_status->status_id == STM_TS_ERR_EVENT_QUEUE_FULL) { + input_err(true, ts->dev, "%s: IC Event Queue is full\n", __func__); + stm_ts_release_all_finger(ts); + } else if (p_event_status->status_id == STM_TS_ERR_EVENT_ESD) { + input_err(true, ts->dev, "%s: ESD detected\n", __func__); + if (!atomic_read(&ts->reset_is_on_going)) + schedule_delayed_work(&ts->reset_work, msecs_to_jiffies(10)); + } + } else if (p_event_status->stype == STM_TS_EVENT_STATUSTYPE_INFO) { + if (p_event_status->status_id == STM_TS_INFO_READY_STATUS) { + if (p_event_status->status_data_1 == 0x10) { + input_err(true, ts->dev, "%s: IC Reset\n", __func__); + if (!atomic_read(&ts->reset_is_on_going)) + schedule_delayed_work(&ts->reset_work, msecs_to_jiffies(10)); + } + } else if (p_event_status->status_id == STM_TS_INFO_WET_MODE) { + ts->plat_data->wet_mode = p_event_status->status_data_1; + input_info(true, ts->dev, "%s: water wet mode %d\n", + __func__, ts->plat_data->wet_mode); + if (ts->plat_data->wet_mode) + ts->plat_data->hw_param.wet_count++; + sec_cmd_send_status_uevent(&ts->sec, STATUS_TYPE_WET, p_event_status->status_data_1); + } else if (p_event_status->status_id == STM_TS_INFO_NOISE_MODE) { + atomic_set(&ts->plat_data->touch_noise_status, (p_event_status->status_data_1 >> 4)); + + input_info(true, ts->dev, "%s: NOISE MODE %s[%02X]\n", + __func__, atomic_read(&ts->plat_data->touch_noise_status) == 0 ? "OFF" : "ON", + p_event_status->status_data_1); + + if (atomic_read(&ts->plat_data->touch_noise_status)) + ts->plat_data->hw_param.noise_count++; + ts->plat_data->noise_mode = p_event_status->status_data_1; + sec_cmd_send_status_uevent(&ts->sec, STATUS_TYPE_NOISE, p_event_status->status_data_1); + } + } else if (p_event_status->stype == STM_TS_EVENT_STATUSTYPE_VENDORINFO) { + if (ts->plat_data->support_ear_detect) { + if (p_event_status->status_id == 0x6A) { + ts->hover_event = p_event_status->status_data_1; + sec_input_proximity_report(ts->dev, p_event_status->status_data_1); + } + } + } +} + +static int stm_ts_get_event(struct stm_ts_data *ts, u8 *data, int *remain_event_count) +{ + int ret = 0; + u8 address = 0; + + address = STM_TS_READ_ONE_EVENT; + ret = ts->stm_ts_read(ts, &address, 1, (u8 *)data, STM_TS_EVENT_BUFF_SIZE); + if (ret < 0) { + input_err(true, ts->dev, "%s: i2c read one event failed\n", __func__); + return ret; + } + + if (ts->debug_flag & SEC_TS_DEBUG_PRINT_ONEEVENT) + input_info(true, ts->dev, "ONE: %02X %02X %02X %02X %02X %02X %02X %02X\n", + data[0], data[1], + data[2], data[3], + data[4], data[5], + data[6], data[7]); + + if (data[0] == 0) { + input_info(true, ts->dev, "%s: event buffer is empty\n", __func__); +#if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_SEC_FACTORY) + ts->irq_empty_count++; + if (ts->irq_empty_count >= 100) { + ts->irq_empty_count = 0; + sec_abc_send_event(SEC_ABC_SEND_EVENT_TYPE); + } +#endif + + return SEC_ERROR; + } + + *remain_event_count = data[7] & 0x1F; + + if (*remain_event_count > MAX_EVENT_COUNT - 1) { + input_err(true, ts->dev, "%s: event buffer overflow\n", __func__); + address = STM_TS_CMD_CLEAR_ALL_EVENT; + ret = ts->stm_ts_write(ts, &address, 1, NULL, 0); //guide + if (ret < 0) + input_err(true, ts->dev, "%s: i2c write clear event failed\n", __func__); + + stm_ts_release_all_finger(ts); + + return SEC_ERROR; + } + + if (*remain_event_count > 0) { + address = STM_TS_READ_ALL_EVENT; + ret = ts->stm_ts_read(ts, &address, 1, &data[1 * STM_TS_EVENT_BUFF_SIZE], + (STM_TS_EVENT_BUFF_SIZE) * (*remain_event_count)); + if (ret < 0) { + input_err(true, ts->dev, "%s: i2c read one event failed\n", __func__); + return ret; + } + } + + return SEC_SUCCESS; +} + +#ifdef ENABLE_RAWDATA_SERVICE +#ifdef RAWDATA_MMAP +static int stm_ts_get_rawdata(struct stm_ts_data *ts) +{ + u8 reg[3]; + int ret; + s16 *val; + int ii; + int jj; + int kk = 0; + int num; + static int prev_touch_count; + + if (ts->raw_mode == 0) + return 0; + + if (!ts->raw_addr_h && !ts->raw_addr_l) + return 0; + + if (ts->raw_len == 0) + return 0; + + if (!ts->raw_u8) + return 0; + + if (!ts->raw) + return 0; + + reg[0] = 0xA7; + reg[1] = ts->raw_addr_h; + reg[2] = ts->raw_addr_l; + + mutex_lock(&ts->raw_lock); + if (prev_touch_count == 0 && ts->plat_data->touch_count != 0) { + /* first press */ + ts->raw_irq_count = 1; + ts->before_irq_count = 1; + } else if (prev_touch_count != 0 && ts->plat_data->touch_count == 0) { + /* all finger released */ + ts->raw_irq_count++; + ts->before_irq_count = 0; + } else if (prev_touch_count != 0 && ts->plat_data->touch_count != 0) { + /* move */ + ts->raw_irq_count++; + ts->before_irq_count++; + } + prev_touch_count = ts->plat_data->touch_count; + + ret = ts->stm_ts_read(ts, reg, 3, ts->raw_u8, ts->tx_count * ts->rx_count * 2); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to get rawdata: %d\n", __func__, ret); + mutex_unlock(&ts->raw_lock); + return ret; + } + + val = (s16 *)ts->raw_u8; + + for (ii = 0; ii < ts->tx_count; ii++) { + for (jj = (ts->rx_count - 1); jj >= 0; jj--) + ts->raw[4 + jj * ts->tx_count + ii] = val[kk++]; + } + + ts->raw[3] = ts->plat_data->coord[0].action == 0 ? 3 : ts->plat_data->coord[0].action; + ts->raw[1] = ts->raw_irq_count; + + num = ts->raw_irq_count % 5; + if (num == 0) + memcpy(ts->raw_v0, ts->raw, ts->raw_len); + else if (num == 1) + memcpy(ts->raw_v1, ts->raw, ts->raw_len); + else if (num == 2) + memcpy(ts->raw_v2, ts->raw, ts->raw_len); + else if (num == 3) + memcpy(ts->raw_v3, ts->raw, ts->raw_len); + else if (num == 4) + memcpy(ts->raw_v4, ts->raw, ts->raw_len); + + input_info(true, ts->dev, "%s: num: %d | %d | %d | %d | %d\n", __func__, num, ts->raw[0], ts->raw[1], ts->raw[2], ts->raw[3]); + mutex_unlock(&ts->raw_lock); + + sysfs_notify(&ts->sec.fac_dev->kobj, NULL, "raw_irq"); + + return ret; +} +#endif //#ifdef RAWDATA_MMAP +#ifdef RAWDATA_IOCTL +static int stm_ts_get_rawdata(struct stm_ts_data *ts) +{ + u8 reg[5]; + int ret; + s16 *val; + int ii; + int jj; + int kk = 0; + static int prev_touch_count; + u8 *target_mem; + + if (ts->raw_mode == 0) + return 0; + + if (!ts->raw_addr_h && !ts->raw_addr_l) + return 0; + + if (ts->raw_len == 0) + return 0; + + if (!ts->raw_u8) + return 0; + + if (!ts->raw) + return 0; + + reg[0] = STM_TS_CMD_REG_R; + reg[1] = 0x20; + reg[2] = 0x01; + reg[3] = ts->raw_addr_h; + reg[4] = ts->raw_addr_l; + + mutex_lock(&ts->raw_lock); + prev_touch_count = ts->plat_data->touch_count; + ret = ts->stm_ts_read(ts, ®[0], 5, ts->raw_u8, ts->tx_count * ts->rx_count * 2); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to get rawdata: %d\n", __func__, ret); + mutex_unlock(&ts->raw_lock); + return ret; + } + + val = (s16 *)ts->raw_u8; + + for (ii = 0; ii < ts->tx_count; ii++) { + for (jj = (ts->rx_count - 1); jj >= 0; jj--) + ts->raw[4 + jj * ts->tx_count + ii] = val[kk++]; + } + + ts->raw[3] = ts->plat_data->coord[0].action == 0 ? 3 : ts->plat_data->coord[0].action; + + target_mem = ts->raw_pool[ts->raw_write_index++]; + memcpy(target_mem, ts->raw, ts->raw_len); + + if (ts->raw_write_index >= RAW_VEC_NUM) + ts->raw_write_index = 0; + +/* input_info(true, ts->dev, "%s: | %d | %d | %d | %d\n", __func__, ts->raw[0], ts->raw[1], ts->raw[2], ts->raw[3]);*/ + + mutex_unlock(&ts->raw_lock); + + sysfs_notify(&ts->sec.fac_dev->kobj, NULL, "raw_irq"); + + return ret; +} + + +#endif //RAWDATA_IOCTL +#endif + +irqreturn_t stm_ts_irq_thread(int irq, void *ptr) +{ + struct stm_ts_data *ts = (struct stm_ts_data *)ptr; + int ret; + u8 event_id; + u8 read_event_buff[MAX_EVENT_COUNT * STM_TS_EVENT_BUFF_SIZE] = {0}; + u8 *event_buff; + int curr_pos; + int remain_event_count; + char taas[32]; + char tresult[64]; + int raw_irq_flag = 0; + + ret = event_id = curr_pos = remain_event_count = 0; + +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) + if (secure_filter_interrupt(ts) == IRQ_HANDLED) { + wait_for_completion_interruptible_timeout(&ts->plat_data->secure_interrupt, + msecs_to_jiffies(5 * MSEC_PER_SEC)); + + input_info(true, ts->dev, + "%s: secure interrupt handled\n", __func__); + + return IRQ_HANDLED; + } +#endif +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) + if (STUI_MODE_TOUCH_SEC & stui_get_mode()) + return IRQ_HANDLED; +#endif + + ret = sec_input_handler_start(ts->dev); + if (ret < 0) + return IRQ_HANDLED; + + ret = stm_ts_get_event(ts, read_event_buff, &remain_event_count); + if (ret < 0) + return IRQ_HANDLED; + + mutex_lock(&ts->eventlock); + + do { + event_buff = &read_event_buff[curr_pos * STM_TS_EVENT_BUFF_SIZE]; + event_id = event_buff[0] & 0x3; + if (ts->debug_flag & SEC_TS_DEBUG_PRINT_ALLEVENT) + input_info(true, ts->dev, "ALL: %02X %02X %02X %02X %02X %02X %02X %02X\n", + event_buff[0], event_buff[1], event_buff[2], event_buff[3], + event_buff[4], event_buff[5], event_buff[6], event_buff[7]); + + if (event_id == STM_TS_STATUS_EVENT) { + stm_ts_status_event(ts, event_buff); + } else if (event_id == STM_TS_COORDINATE_EVENT) { + stm_ts_coordinate_event(ts, event_buff); + raw_irq_flag = 1; + } else if (event_id == STM_TS_GESTURE_EVENT) { + stm_ts_gesture_event(ts, event_buff); + } else if (event_id == STM_TS_VENDOR_EVENT) { + input_info(true, ts->dev, + "%s: %s event %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n", + __func__, (event_buff[1] == 0x01 ? "echo": ""), event_buff[0], event_buff[1], event_buff[2], event_buff[3], event_buff[4], event_buff[5], + event_buff[6], event_buff[7], event_buff[8], event_buff[9], event_buff[10], event_buff[11], + event_buff[12], event_buff[13], event_buff[14], event_buff[15]); + + if (event_buff[2] == STM_TS_CMD_SET_GET_OPMODE && event_buff[3] == STM_TS_OPMODE_NORMAL) { + sec_input_gesture_report(ts->dev, SPONGE_EVENT_TYPE_TSP_SCAN_UNBLOCK, 0, 0); + input_info(true, ts->dev, "%s: Normal changed\n", __func__); + } else if (event_buff[2] == STM_TS_CMD_SET_GET_OPMODE && event_buff[3] == STM_TS_OPMODE_LOWPOWER) { + sec_input_gesture_report(ts->dev, SPONGE_EVENT_TYPE_TSP_SCAN_UNBLOCK, 0, 0); + input_info(true, ts->dev, "%s: lp changed\n", __func__); + } + + if ((event_buff[0] == 0xF3) && ((event_buff[1] == 0x02) || (event_buff[1] == 0x46))) { + if (!atomic_read(&ts->reset_is_on_going)) + schedule_delayed_work(&ts->reset_work, msecs_to_jiffies(10)); + break; + } + + if ((event_buff[0] == 0x43) && (event_buff[1] == 0x05) && (event_buff[1] == 0x11)) { + snprintf(taas, sizeof(taas), "TAAS=CASB"); + snprintf(tresult, sizeof(tresult), "RESULT=STM TSP Force Calibration Event"); + sec_cmd_send_event_to_user(&ts->sec, taas, tresult); + } + } else { + input_info(true, ts->dev, + "%s: unknown event %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n", + __func__, event_buff[0], event_buff[1], event_buff[2], event_buff[3], event_buff[4], event_buff[5], + event_buff[6], event_buff[7], event_buff[8], event_buff[9], event_buff[10], event_buff[11], + event_buff[12], event_buff[13], event_buff[14], event_buff[15]); + } + curr_pos++; + remain_event_count--; + } while (remain_event_count >= 0); + + sec_input_coord_event_sync_slot(ts->dev); + + stm_ts_external_func(ts); + + mutex_unlock(&ts->eventlock); + +#ifdef ENABLE_RAWDATA_SERVICE + if (ts->plat_data->support_rawdata && raw_irq_flag && !ts->block_rawdata) + stm_ts_get_rawdata(ts); +#endif + return IRQ_HANDLED; +} + +int stm_ts_enable(struct device *dev) +{ + struct stm_ts_data *ts = dev_get_drvdata(dev); + int ret; + u8 data[2] = { 0 }; + int retrycnt = 0; + + if (!ts->probe_done) { + input_err(true, ts->dev, "%s: device probe is not done\n", __func__); + ts->plat_data->first_booting_disabled = false; + return 0; + } + cancel_delayed_work_sync(&ts->work_read_info); + + atomic_set(&ts->plat_data->enabled, true); + ts->plat_data->prox_power_off = 0; + +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) + secure_touch_stop(ts, 0); +#endif + if (atomic_read(&ts->plat_data->power_state) == SEC_INPUT_STATE_LPM) { + ts->plat_data->lpmode(ts, TO_TOUCH_MODE); + sec_input_set_grip_type(ts->dev, ONLY_EDGE_HANDLER); + } else { + ret = ts->plat_data->start_device(ts); + if (ret < 0) + input_err(true, ts->dev, "%s: Failed to start device\n", __func__); + } + + if (ts->fix_active_mode) + stm_ts_fix_active_mode(ts, STM_TS_ACTIVE_TRUE); + + if (ts->plat_data->low_sensitivity_mode) { + u8 reg[2]; + + reg[0] = STM_TS_CMD_FUNCTION_SET_LOW_SENSITIVITY_MODE; + reg[1] = ts->plat_data->low_sensitivity_mode; + ret = ts->stm_ts_write(ts, reg, 2, NULL, 0); + if (ret < 0) + input_err(true, ts->dev, "%s: Failed to set low sensitivity mode\n", __func__); + } + + sec_input_set_temperature(ts->dev, SEC_INPUT_SET_TEMPERATURE_FORCE); + +retry_fodmode: + if (ts->plat_data->support_fod && (ts->plat_data->lowpower_mode & SEC_TS_MODE_SPONGE_PRESS)) { + data[0] = STM_TS_CMD_SPONGE_OFFSET_MODE; + ret = ts->stm_ts_read_sponge(ts, data, 2); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to read sponge offset data\n", __func__); + retrycnt++; + if (retrycnt < 5) + goto retry_fodmode; + } + input_info(true, ts->dev, "%s: read offset data(0x%x)\n", __func__, data[0]); + + if((data[0] & SEC_TS_MODE_SPONGE_PRESS) != (ts->plat_data->lowpower_mode & SEC_TS_MODE_SPONGE_PRESS)) { + input_err(true, ts->dev, "%s: fod data not matched(%d,%d) retry %d\n", + __func__, (data[0] & SEC_TS_MODE_SPONGE_PRESS), + (ts->plat_data->lowpower_mode & SEC_TS_MODE_SPONGE_PRESS), retrycnt); + retrycnt++; + /*rewrite*/ + stm_ts_set_custom_library(ts); + stm_ts_set_press_property(ts); + stm_ts_set_fod_finger_merge(ts); + if (retrycnt < 5) + goto retry_fodmode; + } + } + + cancel_delayed_work(&ts->work_print_info); + ts->plat_data->print_info_cnt_open = 0; + ts->plat_data->print_info_cnt_release = 0; + if (!atomic_read(&ts->plat_data->shutdown_called)) + schedule_work(&ts->work_print_info.work); + return 0; +} + +int stm_ts_disable(struct device *dev) +{ + struct stm_ts_data *ts = dev_get_drvdata(dev); + + if (!ts->probe_done) { + ts->plat_data->first_booting_disabled = true; + input_err(true, ts->dev, "%s: device probe is not done\n", __func__); + return 0; + } + cancel_delayed_work_sync(&ts->work_read_info); + + if (atomic_read(&ts->plat_data->shutdown_called)) { + input_err(true, ts->dev, "%s shutdown was called\n", __func__); + return 0; + } + + atomic_set(&ts->plat_data->enabled, false); + +#ifdef TCLM_CONCEPT + sec_tclm_debug_info(ts->tdata); +#endif + cancel_delayed_work(&ts->work_print_info); + sec_input_print_info(ts->dev, ts->tdata); +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) + secure_touch_stop(ts, 1); +#if IS_ENABLED(CONFIG_INPUT_SEC_TRUSTED_TOUCH) + if (atomic_read(&ts->plat_data->pvm->trusted_touch_enabled)) { + input_info(true, ts->dev, "%s wait for disabling trusted touch\n", __func__); + wait_for_completion_interruptible(&ts->plat_data->pvm->trusted_touch_powerdown); + } +#endif +#endif +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) + stui_cancel_session(); +#endif + cancel_delayed_work(&ts->reset_work); + + if (sec_input_need_ic_off(ts->plat_data)) { + ts->plat_data->stop_device(ts); + } else { + if (ts->fix_active_mode) + stm_ts_fix_active_mode(ts, STM_TS_ACTIVE_FALSE); + ts->plat_data->lpmode(ts, TO_LOWPOWER_MODE); + } + + return 0; +} + +int stm_ts_stop_device(void *data) +{ + struct stm_ts_data *ts = (struct stm_ts_data *)data; + + input_info(true, ts->dev, "%s\n", __func__); + +#if IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) && IS_ENABLED(CONFIG_SEC_FACTORY) + if (ts->panel_attached == STM_PANEL_DETACHED) { + input_err(true, ts->dev, "%s: panel detached(%d) skip!\n", __func__, ts->panel_attached); + return 0; + } +#endif + + mutex_lock(&ts->device_mutex); + + if (atomic_read(&ts->plat_data->power_state) == SEC_INPUT_STATE_POWER_OFF) { + input_err(true, ts->dev, "%s: already power off\n", __func__); + goto out; + } + + disable_irq(ts->irq); + + atomic_set(&ts->plat_data->power_state, SEC_INPUT_STATE_POWER_OFF); + + stm_ts_locked_release_all_finger(ts); + + ts->plat_data->power(ts->dev, false); + ts->plat_data->pinctrl_configure(ts->dev, false); + +out: + mutex_unlock(&ts->device_mutex); + return 0; +} + +int stm_ts_start_device(void *data) +{ + struct stm_ts_data *ts = (struct stm_ts_data *)data; + int ret = -1; + u8 address = 0; + + input_info(true, ts->dev, "%s\n", __func__); + +#if IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) && IS_ENABLED(CONFIG_SEC_FACTORY) + if (ts->panel_attached == STM_PANEL_DETACHED) { + input_err(true, ts->dev, "%s: panel detached(%d) skip!\n", __func__, ts->panel_attached); + return ret; + } +#endif + + ts->plat_data->pinctrl_configure(ts->dev, true); + + mutex_lock(&ts->device_mutex); + + if (atomic_read(&ts->plat_data->power_state) == SEC_INPUT_STATE_POWER_ON) { + input_err(true, ts->dev, "%s: already power on\n", __func__); + goto out; + } + + stm_ts_locked_release_all_finger(ts); + + ts->plat_data->power(ts->dev, true); + + sec_delay(TOUCH_POWER_ON_DWORK_TIME); + + atomic_set(&ts->plat_data->power_state, SEC_INPUT_STATE_POWER_ON); + atomic_set(&ts->plat_data->touch_noise_status, 0); + + ts->plat_data->init(ts); + + stm_ts_read_chip_id(ts); + + /* Sense_on */ + address = STM_TS_CMD_SENSE_ON; + ret = ts->stm_ts_write(ts, &address, 1, NULL, 0); + if (ret < 0) + input_err(true, ts->dev, "%s: fail to write Sense_on\n", __func__); + + enable_irq(ts->irq); +out: + mutex_unlock(&ts->device_mutex); + return ret; +} + +static int stm_ts_hw_check(struct stm_ts_data *ts) +{ + int ret = 0; + int retry = 3; + + do { + ret = stm_ts_fw_corruption_check(ts); + if (ret == -STM_TS_ERROR_FW_CORRUPTION) { + ts->plat_data->hw_param.checksum_result = 1; + break; + } else if (ret < 0) { + if (ret == -STM_TS_ERROR_BROKEN_OSC_TRIM) { + break; + } else if (ts->plat_data->hw_param.checksum_result) { + break; + } else if (ret == -STM_TS_ERROR_TIMEOUT_ZERO) { + stm_ts_read_chip_id_hw(ts); + } + stm_ts_systemreset(ts, 20); + } else { + break; + } + } while (--retry); + + return ret; +} + +static int stm_ts_hw_init(struct stm_ts_data *ts) +{ + int ret = 0; + + ts->plat_data->pinctrl_configure(ts->dev, true); + + ts->plat_data->power(ts->dev, true); + if (!ts->plat_data->regulator_boot_on) + sec_delay(TOUCH_POWER_ON_DWORK_TIME); + + atomic_set(&ts->plat_data->power_state, SEC_INPUT_STATE_POWER_ON); +#if IS_ENABLED(CONFIG_TOUCHSCREEN_STM_SPI) + stm_ts_set_spi_mode(ts); +#endif + ret = stm_ts_hw_check(ts); + stm_ts_get_version_info(ts); + + if (ret == -STM_TS_ERROR_BROKEN_OSC_TRIM) { + ret = stm_ts_osc_trim_recovery(ts); + if (ret < 0) + input_err(true, ts->dev, "%s: Failed to recover osc trim\n", __func__); + } + + if (ts->plat_data->hw_param.checksum_result) { + ts->fw_version_of_ic = 0; + ts->config_version_of_ic = 0; + ts->fw_main_version_of_ic = 0; + } + + stm_ts_read_chip_id(ts); + + ret = stm_ts_fw_update_on_probe(ts); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to firmware update\n", + __func__); + return -STM_TS_ERROR_FW_UPDATE_FAIL; + } + + ret = stm_ts_get_channel_info(ts); + if (ret < 0) { + input_err(true, ts->dev, "%s: read failed ret = %d\n", __func__, ret); + return ret; + } + + ts->pFrame = devm_kzalloc(ts->dev, ts->rx_count * ts->tx_count * 2 + 1, GFP_KERNEL); + if (unlikely(ts->pFrame == NULL)) + return -ENOMEM; + + ts->cx_data = devm_kzalloc(ts->dev, ts->rx_count * ts->tx_count + 1, GFP_KERNEL); + if (unlikely(ts->cx_data == NULL)) + return -ENOMEM; + + ts->ito_result = devm_kzalloc(ts->dev, STM_TS_ITO_RESULT_PRINT_SIZE, GFP_KERNEL); + if (unlikely(ts->ito_result == NULL)) + return -ENOMEM; + + /* fts driver set functional feature */ + ts->plat_data->touch_count = 0; + ts->touch_opmode = STM_TS_OPMODE_NORMAL; + ts->charger_mode = STM_TS_BIT_CHARGER_MODE_NORMAL; + ts->irq_empty_count = 0; +#ifdef TCLM_CONCEPT + ts->tdata->external_factory = false; +#endif + ts->plat_data->touch_functions = STM_TS_TOUCHTYPE_DEFAULT_ENABLE; + stm_ts_set_touch_function(ts); + sec_delay(10); + + stm_ts_command(ts, STM_TS_CMD_FORCE_CALIBRATION, true); + stm_ts_command(ts, STM_TS_CMD_CLEAR_ALL_EVENT, true); + ts->scan_mode = STM_TS_SCAN_MODE_DEFAULT; + stm_ts_set_scanmode(ts, ts->scan_mode); + + input_info(true, ts->dev, "%s: Initialized\n", __func__); + + return ret; +} + +static void stm_ts_parse_dt(struct device *dev, struct stm_ts_data *ts) +{ + struct device_node *np = dev->of_node; + + if (!np) { + input_err(true, dev, "%s: of_node is not exist\n", __func__); + return; + } + + if (of_property_read_u32(np, "stm,lpmode_change_delay", &ts->lpmode_change_delay)) + ts->lpmode_change_delay = 5; + + ts->support_mutual_raw = of_property_read_bool(np, "stm,support_mutual_raw"); + ts->support_grip_cmd_v2 = of_property_read_bool(np, "stm,support_grip_cmd_v2"); + + input_info(true, dev, "%s: lpmode_change_delay:%d, support_mutual_raw:%d, support_grip_cmd_v2:%d\n", + __func__, ts->lpmode_change_delay, ts->support_mutual_raw, ts->support_grip_cmd_v2); +} + +int stm_ts_init(struct stm_ts_data *ts) +{ + int ret = 0; + + ret = sec_input_parse_dt(ts->dev); + if (ret) { + input_err(true, ts->dev, "%s: Failed to parse dt\n", __func__); + goto error_allocate_mem; + } + + stm_ts_parse_dt(ts->dev, ts); +#ifdef TCLM_CONCEPT + sec_tclm_parse_dt(ts->dev, ts->tdata); +#endif + ts->plat_data->pinctrl = devm_pinctrl_get(ts->dev); + if (IS_ERR(ts->plat_data->pinctrl)) + input_err(true, ts->dev, "%s: could not get pinctrl\n", __func__); + + ts->irq = gpio_to_irq(ts->plat_data->irq_gpio); + ts->plat_data->irq = ts->irq; + ts->stm_ts_systemreset = stm_ts_systemreset; + ts->stm_ts_command = stm_ts_command; + ts->plat_data->pinctrl_configure = sec_input_pinctrl_configure; + ts->plat_data->probe = stm_ts_probe; + ts->plat_data->power = sec_input_power; + ts->plat_data->start_device = stm_ts_start_device; + ts->plat_data->stop_device = stm_ts_stop_device; + ts->plat_data->init = stm_ts_reinit; + ts->plat_data->lpmode = stm_ts_set_lowpowermode; + ts->plat_data->set_grip_data = stm_set_grip_data_to_ic; + ts->plat_data->set_temperature = stm_ts_set_temperature; + + ptsp = ts->dev; + g_ts = ts; +#ifdef TCLM_CONCEPT + sec_tclm_initialize(ts->tdata); + ts->tdata->dev = ts->dev; + ts->tdata->tclm_read = stm_tclm_data_read; + ts->tdata->tclm_write = stm_tclm_data_write; + ts->tdata->tclm_execute_force_calibration = stm_ts_tclm_execute_force_calibration; + ts->tdata->tclm_parse_dt = sec_tclm_parse_dt; +#endif + INIT_DELAYED_WORK(&ts->reset_work, stm_ts_reset_work); + INIT_DELAYED_WORK(&ts->work_read_info, stm_ts_read_info_work); + INIT_DELAYED_WORK(&ts->work_print_info, stm_ts_print_info_work); + INIT_DELAYED_WORK(&ts->work_read_functions, stm_ts_get_touch_function); +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + INIT_DELAYED_WORK(&ts->plat_data->interrupt_notify_work, stm_ts_interrupt_notify); +#endif + mutex_init(&ts->device_mutex); + mutex_init(&ts->read_write_mutex); + mutex_init(&ts->eventlock); + mutex_init(&ts->sponge_mutex); + mutex_init(&ts->fn_mutex); +#ifdef ENABLE_RAWDATA_SERVICE + mutex_init(&ts->raw_lock); +#endif + init_completion(&ts->plat_data->resume_done); + complete_all(&ts->plat_data->resume_done); + + ret = sec_input_device_register(ts->dev, ts); + if (ret) { + input_err(true, ts->dev, "failed to register input device, %d\n", ret); + goto err_register_input_device; + } + + mutex_init(&ts->plat_data->enable_mutex); + ts->plat_data->enable = stm_ts_enable; + ts->plat_data->disable = stm_ts_disable; + + ts->plat_data->sec_ws = wakeup_source_register(NULL, "TSP"); + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_DUMP_MODE) + sec_input_dumpkey_register(MULTI_DEV_NONE, stm_ts_dump_tsp_log, ts->dev); + INIT_DELAYED_WORK(&ts->check_rawdata, stm_ts_check_rawdata); +#endif + input_info(true, ts->dev, "%s: init resource\n", __func__); + + return 0; + +err_register_input_device: +error_allocate_mem: + input_err(true, ts->dev, "%s: failed(%d)\n", __func__, ret); + input_log_fix(); + return ret; +} + +void stm_ts_release(struct stm_ts_data *ts) +{ + input_info(true, ts->dev, "%s\n", __func__); + + ts->plat_data->enable = NULL; + ts->plat_data->disable = NULL; + +#if IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) && IS_ENABLED(CONFIG_SEC_FACTORY) + panel_notifier_unregister(&ts->lcd_nb); +#endif + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_unregister_notify(&ts->stm_input_nb); +#endif +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) + vbus_notifier_unregister(&ts->vbus_nb); +#endif + cancel_delayed_work_sync(&ts->work_read_info); + cancel_delayed_work_sync(&ts->work_print_info); + cancel_delayed_work_sync(&ts->work_read_functions); + cancel_delayed_work_sync(&ts->reset_work); +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + cancel_delayed_work_sync(&ts->plat_data->interrupt_notify_work); +#endif + flush_delayed_work(&ts->reset_work); +#if IS_ENABLED(CONFIG_TOUCHSCREEN_DUMP_MODE) + cancel_delayed_work_sync(&ts->check_rawdata); + sec_input_dumpkey_unregister(MULTI_DEV_NONE); +#endif + wakeup_source_unregister(ts->plat_data->sec_ws); + + ts->plat_data->lowpower_mode = false; + ts->probe_done = false; + + ts->plat_data->power(ts->dev, false); +} + +#if IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) && IS_ENABLED(CONFIG_SEC_FACTORY) +static int stm_notifier_call(struct notifier_block *n, unsigned long event, void *data) +{ + struct stm_ts_data *ts = container_of(n, struct stm_ts_data, lcd_nb); + struct panel_notifier_event_data *evtdata = data; + + input_dbg(false, ts->dev, "%s: called! event = %ld, ev_data->state = %d\n", __func__, event, evtdata->state); + + if (event == PANEL_EVENT_UB_CON_STATE_CHANGED) { + input_info(false, ts->dev, "%s: event = %ld, ev_data->state = %d\n", __func__, event, evtdata->state); + + if (evtdata->state == PANEL_EVENT_UB_CON_STATE_DISCONNECTED) { + input_info(true, ts->dev, "%s: UB_CON_DISCONNECTED : stm_ts_stop_device\n", __func__); + stm_ts_stop_device(ts); + ts->panel_attached = STM_PANEL_DETACHED; + } else if(evtdata->state == PANEL_EVENT_UB_CON_STATE_CONNECTED && ts->panel_attached != STM_PANEL_ATTACHED) { + input_info(true, ts->dev, "%s: UB_CON_CONNECTED : panel attached!\n", __func__); + ts->panel_attached = STM_PANEL_ATTACHED; + } + } + + return 0; +} +#endif + +int stm_ts_probe(struct device *dev) +{ + struct stm_ts_data *ts = dev_get_drvdata(dev); + int ret = 0; + + input_info(true, ts->dev, "%s\n", __func__); + + ret = stm_ts_hw_init(ts); + if (ret < 0) { + input_err(true, ts->dev, "%s: fail to init hw\n", __func__); + stm_ts_release(ts); + return ret; + } + + stm_ts_get_custom_library(ts); + stm_ts_set_custom_library(ts); + + input_info(true, ts->dev, "%s: request_irq = %d\n", __func__, ts->irq); + ret = devm_request_threaded_irq(ts->dev, ts->irq, NULL, stm_ts_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, STM_TS_I2C_NAME, ts); + if (ret < 0) { + input_err(true, ts->dev, "%s: Unable to request threaded irq\n", __func__); + stm_ts_release(ts); + return ret; + } + + ts->probe_done = true; + atomic_set(&ts->plat_data->enabled, true); + ret = stm_ts_fn_init(ts); + if (ret < 0) { + input_err(true, ts->dev, "%s: fail to init fn\n", __func__); + stm_ts_release(ts); + return ret; + } + +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) + if (sysfs_create_group(&ts->plat_data->input_dev->dev.kobj, &secure_attr_group) < 0) + input_err(true, ts->dev, "%s: do not make secure group\n", __func__); + else + secure_touch_init(ts); + +#if IS_ENABLED(CONFIG_INPUT_SEC_TRUSTED_TOUCH) + ret = sec_trusted_touch_init(ts->dev); + if (ret < 0) + input_err(true, ts->dev, "%s: Failed to init trusted touch\n", __func__); +#endif + + sec_secure_touch_register(ts, ts->dev, ts->plat_data->ss_touch_num, &ts->plat_data->input_dev->dev.kobj); +#endif +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_register_notify(&ts->stm_input_nb, stm_touch_notify_call, 1); +#endif +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) + vbus_notifier_register(&ts->vbus_nb, stm_ts_vbus_notification, + VBUS_NOTIFY_DEV_CHARGER); +#endif + +#if IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) && IS_ENABLED(CONFIG_SEC_FACTORY) + ts->lcd_nb.priority = 1; + ts->lcd_nb.notifier_call = stm_notifier_call; + ts->panel_attached = STM_PANEL_ATTACHED; + panel_notifier_register(&ts->lcd_nb); +#endif + + input_err(true, ts->dev, "%s: done\n", __func__); +#ifdef ENABLE_RAWDATA_SERVICE + if (ts->plat_data->support_rawdata) { + stm_ts_rawdata_init(ts); + stm_ts_read_rawdata_address(ts); + } +#endif + input_log_fix(); + + if (!atomic_read(&ts->plat_data->shutdown_called)) + schedule_delayed_work(&ts->work_read_info, msecs_to_jiffies(50)); + +#if !IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP) && IS_ENABLED(CONFIG_TOUCHSCREEN_STM_SPI) + stm_ts_tool_proc_init(ts); +#endif + return 0; +} + +int stm_ts_remove(struct stm_ts_data *ts) +{ + if (!ts->probe_done) { + input_info(true, ts->dev, "%s don't success probe yet\n", __func__); + return 0; + } + input_info(true, ts->dev, "%s\n", __func__); + +#if !IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP) && IS_ENABLED(CONFIG_TOUCHSCREEN_STM_SPI) + stm_ts_tool_proc_remove(); +#endif + mutex_lock(&ts->plat_data->enable_mutex); + atomic_set(&ts->plat_data->shutdown_called, true); + mutex_unlock(&ts->plat_data->enable_mutex); + + sec_input_probe_work_remove(ts->plat_data); + disable_irq_nosync(ts->irq); + stm_ts_release(ts); + stm_ts_fn_remove(ts); + + return 0; +} + +void stm_ts_shutdown(struct stm_ts_data *ts) +{ + input_info(true, ts->dev, "%s\n", __func__); + + stm_ts_remove(ts); +} + + +#if IS_ENABLED(CONFIG_PM) +int stm_ts_pm_suspend(struct stm_ts_data *ts) +{ + reinit_completion(&ts->plat_data->resume_done); + + return 0; +} + +int stm_ts_pm_resume(struct stm_ts_data *ts) +{ + complete_all(&ts->plat_data->resume_done); + + return 0; +} +#endif + +MODULE_LICENSE("GPL"); diff --git a/drivers/input/sec_input/stm_spi/stm_dev.h b/drivers/input/sec_input/stm_spi/stm_dev.h new file mode 100644 index 000000000000..4b8928ab379f --- /dev/null +++ b/drivers/input/sec_input/stm_spi/stm_dev.h @@ -0,0 +1,704 @@ +#ifndef _LINUX_STM_TS_H_ +#define _LINUX_STM_TS_H_ + +#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 + +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) +#include +#endif + +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) +#include "../sec_secure_touch.h" +#include +#include +#include + +#define SECURE_TOUCH_ENABLE 1 +#define SECURE_TOUCH_DISABLE 0 + +#include +#include +#include +#include + +#if IS_ENABLED(CONFIG_INPUT_SEC_TRUSTED_TOUCH) +#include "../sec_trusted_touch.h" +#endif +#endif + +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) +#include +#endif + +#if IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) && IS_ENABLED(CONFIG_SEC_FACTORY) +#include +#define STM_PANEL_DETACHED 0 +#define STM_PANEL_ATTACHED 1 +#endif + +#include "../sec_tclm_v2.h" +#if IS_ENABLED(CONFIG_INPUT_TOUCHSCREEN_TCLMV2) +#define TCLM_CONCEPT +#endif + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_DUMP_MODE) +#include "../sec_tsp_dumpkey.h" +extern struct tsp_dump_callbacks dump_callbacks; +#endif + +#include "../sec_input.h" +#include "../sec_tsp_log.h" + +#ifndef I2C_M_DMA_SAFE +#define I2C_M_DMA_SAFE 0 +#endif + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_STM_SPI) +#define ENABLE_RAWDATA_SERVICE +#undef RAWDATA_MMAP +#define RAWDATA_IOCTL +#define RAW_VEC_NUM 3 +#endif + +#define USE_OPEN_CLOSE + +#define STM_TS_I2C_NAME "stm_ts" +#define STM_TS_SPI_NAME "stm_ts_spi" +#define STM_TS_DEVICE_NAME "STM_TS" + +enum stm_ts_fw_update_status { + STM_TS_NOT_UPDATE = 10, + STM_TS_NEED_FW_UPDATE, + STM_TS_NEED_CALIBRATION_ONLY, + STM_TS_NEED_FW_UPDATE_N_CALIBRATION, +}; + +enum stm_ts_active_mode_status { + STM_TS_ACTIVE_FALSE = 0, + STM_TS_ACTIVE_TRUE, + STM_TS_ACTIVE_FALSE_SNR, +}; + +extern struct device *ptsp; +extern struct stm_ts_data *g_ts; + +/** + * struct stm_ts_finger - Represents fingers. + * @ state: finger status (Event ID). + * @ mcount: moving counter for debug. + */ +struct stm_ts_finger { + u8 id; + u8 prev_ttype; + u8 ttype; + u8 action; + u16 x; + u16 y; + u16 p_x; + u16 p_y; + u8 z; + u8 hover_flag; + u8 glove_flag; + u8 touch_height; + u16 mcount; + u8 major; + u8 minor; + bool palm; + int palm_count; + u8 left_event; + u8 max_energy; + u16 max_energy_x; + u16 max_energy_y; + u8 noise_level; + u8 max_strength; + u8 hover_id_num; +}; + +enum stm_ts_cover_id { + STM_TS_FLIP_WALLET = 0, + STM_TS_VIEW_COVER, + STM_TS_COVER_NOTHING1, + STM_TS_VIEW_WIRELESS, + STM_TS_COVER_NOTHING2, + STM_TS_CHARGER_COVER, + STM_TS_VIEW_WALLET, + STM_TS_LED_COVER, + STM_TS_CLEAR_FLIP_COVER, + STM_TS_QWERTY_KEYBOARD_EUR, + STM_TS_QWERTY_KEYBOARD_KOR, + STM_TS_MONTBLANC_COVER = 100, +}; + +enum { + SPECIAL_EVENT_TYPE_SPAY = 0x04, + SPECIAL_EVENT_TYPE_AOD = 0x08, + SPECIAL_EVENT_TYPE_AOD_PRESS = 0x09, + SPECIAL_EVENT_TYPE_AOD_LONGPRESS = 0x0A, + SPECIAL_EVENT_TYPE_AOD_DOUBLETAB = 0x0B, +}; + +enum stm_ts_system_information_address { + STM_TS_SI_CONFIG_CHECKSUM = 0x58, /* 4 bytes */ + STM_TS_SI_OSC_TRIM_INFO = 0x60, /* 4 bytes */ +}; + +enum stm_ts_ito_test_mode { + OPEN_TEST = 0, // trx_open_test 1,1 + SHORT_TEST, // trx_open_test 1,2 + MICRO_OPEN_TEST, // trx_open_test 2 + MICRO_SHORT_TEST, // trx_open_test 3 + OPEN_SHORT_CRACK_TEST, + SAVE_MISCAL_REF_RAW, +}; + +enum stm_ts_ito_test_result { + ITO_PASS = 0, + ITO_FAIL, + ITO_FAIL_OPEN, + ITO_FAIL_SHORT, + ITO_FAIL_MICRO_OPEN, + ITO_FAIL_MICRO_SHORT, +}; + +/* STM_TS_OFFSET_SIGNUTRE */ +#define STM_TS_OFFSET_SIGNATURE 0x59525446 +#define STM_TS_CM2_SIGNATURE 0x324D5446 +#define STM_TS_CM3_SIGNATURE 0x334D5446 +#define STM_TS_FAIL_HIST_SIGNATURE 0x53484646 + +enum stm_ts_miscal_test_result { + MISCAL_PASS = 0, + MISCAL_FAIL, +}; + +#define UEVENT_OPEN_SHORT_PASS 1 +#define UEVENT_OPEN_SHORT_FAIL 2 + +/* ---------------------------------------- + * write 0xE4 [ 11 | 10 | 01 | 00 ] + * MSB <-------------------> LSB + * read 0xE4 + * mapping sequnce : LSB -> MSB + * struct sec_ts_test_result { + * * assy : front + OCTA assay + * * module : only OCTA + * union { + * struct { + * u8 assy_count:2; -> 00 + * u8 assy_result:2; -> 01 + * u8 module_count:2; -> 10 + * u8 module_result:2; -> 11 + * } __attribute__ ((packed)); + * u8 data[1]; + * }; + *}; + * ---------------------------------------- + */ +struct stm_ts_test_result { + union { + struct { + u8 assy_count:2; + u8 assy_result:2; + u8 module_count:2; + u8 module_result:2; + } __packed; + u8 data[1]; + }; +}; + +#define TEST_OCTA_MODULE 1 +#define TEST_OCTA_ASSAY 2 + +#define TEST_OCTA_NONE 0 +#define TEST_OCTA_FAIL 1 +#define TEST_OCTA_PASS 2 + +#define SEC_OFFSET_SIGNATURE 0x59525446 + +#define STM_TS_ITO_RESULT_PRINT_SIZE 1024 + +struct stm_ts_sec_panel_test_result { + u8 flag; + u8 num_of_test; + u16 max_of_tx_gap; + u16 max_of_rx_gap; + u8 tx_of_txmax_gap; + u8 rx_of_txmax_gap; + u8 tx_of_rxmax_gap; + u8 rx_of_rxmax_gap; +} __packed; + +/* 16 byte */ +struct stm_ts_event_coordinate { + u8 eid:2; + u8 tid:4; + u8 tchsta:2; + u8 x_11_4; + u8 y_11_4; + u8 y_3_0:4; + u8 x_3_0:4; + u8 major; + u8 minor; + u8 z:6; + u8 ttype_3_2:2; + u8 left_event:5; + u8 max_energy:1; + u8 ttype_1_0:2; + u8 noise_level; + u8 max_strength; + u8 hover_id_num:4; + u8 noise_status:2; + u8 eom:1; + u8 game_mode:1; + u8 freq_id:4; + u8 fod_debug:4; + u8 reserved_12; + u8 reserved_13; + u8 reserved_14; + u8 reserved_15; +} __packed; + + +/* 16 byte */ +struct stm_ts_event_status { + u8 eid:2; + u8 stype:4; + u8 sf:2; + u8 status_id; + u8 status_data_1; + u8 status_data_2; + u8 status_data_3; + u8 status_data_4; + u8 status_data_5; + u8 left_event_4_0:5; + u8 max_energy:1; + u8 reserved:2; + u8 reserved_8; + u8 reserved_9; + u8 reserved_10; + u8 reserved_11; + u8 reserved_12; + u8 reserved_13; + u8 reserved_14; + u8 reserved_15; +} __packed; + +/* 16 byte */ +struct stm_ts_gesture_status { + u8 eid:2; + u8 stype:4; + u8 sf:2; + u8 gesture_id; + u8 gesture_data_1; + u8 gesture_data_2; + u8 gesture_data_3; + u8 gesture_data_4; + u8 reserved_6; + u8 left_event_4_0:5; + u8 max_energy:1; + u8 reserved_7:2; + u8 reserved_8; + u8 reserved_9; + u8 reserved_10; + u8 reserved_11; + u8 reserved_12; + u8 reserved_13; + u8 reserved_14; + u8 reserved_15; +} __packed; + +struct stm_ts_syncframeheader { + u8 header; // 0 + u8 host_data_mem_id; // 1 + u16 cnt;// 2~3 + u8 dbg_frm_len; // 4 + u8 ms_force_len; // 5 + u8 ms_sense_len; // 6 + u8 ss_force_len; // 7 + u8 ss_sense_len; // 8 + u8 key_len; // 9 + u16 reserved1; // 10~11 + u32 reserved2; // 12~15 +} __packed; + +enum stm_ts_nvm_data_type { /* Write Command */ + STM_TS_NVM_OFFSET_FAC_RESULT = 1, + STM_TS_NVM_OFFSET_CAL_COUNT, + STM_TS_NVM_OFFSET_DISASSEMBLE_COUNT, + STM_TS_NVM_OFFSET_TUNE_VERSION, + STM_TS_NVM_OFFSET_CAL_POSITION, + STM_TS_NVM_OFFSET_HISTORY_QUEUE_COUNT, + STM_TS_NVM_OFFSET_HISTORY_QUEUE_LASTP, + STM_TS_NVM_OFFSET_HISTORY_QUEUE_ZERO, + STM_TS_NVM_OFFSET_CAL_FAIL_FLAG, + STM_TS_NVM_OFFSET_CAL_FAIL_COUNT, +}; + +struct stm_ts_nvm_data_map { + int type; + int offset; + int length; +}; + +#define STM_TS_COMP_DATA_HEADER_SIZE 16 + +struct stm_ts_snr_result_cmd { + s16 status; + s16 point; + s16 average; +} __packed; + +struct tsp_snr_result_of_point { + s16 max; + s16 min; + s16 average; + s16 nontouch_peak_noise; + s16 touch_peak_noise; + s16 snr1; + s16 snr2; +} __packed; + +struct stm_ts_snr_result { + s16 status; + s16 reserved[6]; + struct tsp_snr_result_of_point result[9]; +} __packed; + +/* This Flash Meory Map is FIXED by STM firmware + * Do not change MAP. + */ +#define STM_TS_NVM_OFFSET_ALL 31 + +struct stm_ts_data { + void *client; + struct device *dev; + + bool support_mutual_raw; + bool support_grip_cmd_v2; + int irq; + int irq_empty_count; + struct sec_ts_plat_data *plat_data; + struct sec_input_multi_device *multi_dev; + struct mutex lock; + bool probe_done; + struct sec_cmd_data sec; + int tx_count; + int rx_count; + u8 *read_buf; + u8 *write_buf; + struct spi_message *message; + struct spi_transfer *transfer; + + short *pFrame; + u8 *cx_data; + u8 *ito_result; + struct stm_ts_test_result test_result; + u8 disassemble_count; + u8 fac_nv; + + struct sec_tclm_data *tdata; + bool is_cal_done; + + bool fw_corruption; + bool glove_enabled; + u8 brush_mode; + + int resolution_x; + int resolution_y; + + u8 touch_opmode; + u8 charger_mode; + u8 scan_mode; + u8 game_mode; + u8 sip_mode; + u8 note_mode; + u8 dead_zone; + u8 block_rawdata; + + int fw_version_of_ic; /* firmware version of IC */ + int fw_version_of_bin; /* firmware version of binary */ + int config_version_of_ic; /* Config release data from IC */ + int config_version_of_bin; /* Config release data from IC */ + u16 fw_main_version_of_ic; /* firmware main version of IC */ + u16 fw_main_version_of_bin; /* firmware main version of binary */ + u8 project_id_of_ic; + u8 project_id_of_bin; + u8 ic_name_of_ic; + u8 ic_name_of_bin; + u8 module_version_of_ic; + u8 module_version_of_bin; + int panel_revision; /* Octa panel revision */ + u32 chip_id; +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) + struct notifier_block vbus_nb; +#endif +#if IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) && IS_ENABLED(CONFIG_SEC_FACTORY) + u8 panel_attached; + struct notifier_block lcd_nb; +#endif + struct notifier_block stm_input_nb; + struct delayed_work work_print_info; + struct delayed_work work_read_functions; + struct delayed_work reset_work; + struct delayed_work work_read_info; + struct delayed_work debug_work; + struct delayed_work check_rawdata; +#if IS_ENABLED(CONFIG_INPUT_SEC_TRUSTED_TOUCH) + struct delayed_work close_work; +#endif + + atomic_t reset_is_on_going; + + int debug_flag; + struct mutex read_write_mutex; + struct mutex device_mutex; + struct mutex eventlock; + struct mutex sponge_mutex; + struct mutex fn_mutex; + bool info_work_done; + + int lpmode_change_delay; + + u8 factory_position; + char *miscal_proc; + + int proc_fail_hist_size; + int proc_fail_hist_all_size; + char *fail_hist_sdc_proc; + char *fail_hist_sub_proc; + char *fail_hist_main_proc; + char *fail_hist_all_proc; + + bool sponge_inf_dump; + u8 sponge_dump_format; + u8 sponge_dump_event; + u8 sponge_dump_border_msb; + u8 sponge_dump_border_lsb; + bool sponge_dump_delayed_flag; + u8 sponge_dump_delayed_area; + u16 sponge_dump_border; + + bool rear_selfie_mode; + + u8 hover_event; + + bool tsp_dump_lock; + + bool fix_active_mode; + bool touch_aging_mode; + int sensitivity_mode; +#ifdef ENABLE_RAWDATA_SERVICE + u8 raw_addr_h; + u8 raw_addr_l; + u8 raw_mode; + int raw_len; + u8 *raw_u8;//read from IC + s16 *raw;//convert x/y + struct mutex raw_lock; + +#ifdef RAWDATA_MMAP + int raw_irq_count; + int before_irq_count; + u8 *raw_v0;//mmap0 ... + u8 *raw_v1; + u8 *raw_v2; + u8 *raw_v3; + u8 *raw_v4; + short *mmapdata; +#endif +#ifdef RAWDATA_IOCTL + u8 *raw_pool[RAW_VEC_NUM]; + u8 raw_read_index; + u8 raw_write_index; +#endif +#endif + bool rawcap_lock; + int rawcap_max; + int rawcap_max_tx; + int rawcap_max_rx; + int rawcap_min; + int rawcap_min_tx; + int rawcap_min_rx; + + u8 vvc_mode; + + int (*stop_device)(struct stm_ts_data *ts); + int (*start_device)(struct stm_ts_data *ts); + + int (*stm_ts_write)(struct stm_ts_data *ts, u8 *reg, int cunum, u8 *data, int len); + int (*stm_ts_read)(struct stm_ts_data *ts, u8 *reg, int cnum, u8 *data, int len); + int (*stm_ts_read_sponge)(struct stm_ts_data *ts, u8 *data, int length); + int (*stm_ts_write_sponge)(struct stm_ts_data *ts, u8 *data, int length); + int (*stm_ts_systemreset)(struct stm_ts_data *ts, unsigned int msec); + int (*stm_ts_wait_for_ready)(struct stm_ts_data *ts); + void (*stm_ts_command)(struct stm_ts_data *ts, u8 cmd, bool checkecho); +}; + +//core +int stm_ts_stop_device(void *data); +int stm_ts_start_device(void *data); +irqreturn_t stm_ts_irq_thread(int irq, void *ptr); +int stm_ts_probe(struct device *dev); +int stm_ts_remove(struct stm_ts_data *ts); +void stm_ts_shutdown(struct stm_ts_data *ts); +int stm_ts_pm_suspend(struct stm_ts_data *ts); +int stm_ts_pm_resume(struct stm_ts_data *ts); +#if IS_ENABLED(CONFIG_TOUCHSCREEN_STM_SPI) +void stm_ts_set_spi_mode(struct stm_ts_data *ts); +#endif +int stm_ts_init(struct stm_ts_data *ts); + +//i2c or spi +int stm_ts_wire_mode_change(struct stm_ts_data *ts, u8 *reg); +int stm_tclm_data_read(struct device *dev, int address); +int stm_tclm_data_write(struct device *dev, int address); +int stm_ts_tool_proc_init(struct stm_ts_data *ts); +int stm_ts_tool_proc_remove(void); +int stm_pm_runtime_get_sync(struct stm_ts_data *ts); +void stm_pm_runtime_put_sync(struct stm_ts_data *ts); +struct device *stm_ts_get_client_dev(struct stm_ts_data *ts); + + +void stm_ts_reinit(void *data); +int stm_ts_execute_autotune(struct stm_ts_data *ts, bool IsSaving); +int stm_ts_get_tsp_test_result(struct stm_ts_data *ts); +void stm_ts_release_all_finger(struct stm_ts_data *ts); +void stm_ts_locked_release_all_finger(struct stm_ts_data *ts); + +int stm_ts_set_external_noise_mode(struct stm_ts_data *ts, u8 mode); +int stm_ts_fix_active_mode(struct stm_ts_data *ts, int mode); +int stm_ts_get_version_info(struct stm_ts_data *ts); +int stm_ts_wait_for_ready(struct stm_ts_data *ts); + +//fn +int stm_ts_read_from_sponge(struct stm_ts_data *ts, u8 *data, int length); +int stm_ts_write_to_sponge(struct stm_ts_data *ts, u8 *data, int length); +void stm_ts_command(struct stm_ts_data *ts, u8 cmd, bool checkecho); +void stm_set_grip_data_to_ic(struct device *dev, u8 flag); +int stm_ts_set_temperature(struct device *dev, u8 temperature_data); +int stm_ts_fw_corruption_check(struct stm_ts_data *ts); +void stm_ts_read_chip_id_hw(struct stm_ts_data *ts); +void stm_ts_read_chip_id(struct stm_ts_data *ts); +int stm_ts_get_version_info(struct stm_ts_data *ts); +int stm_ts_systemreset(struct stm_ts_data *ts, unsigned int msec); +int stm_ts_set_scanmode(struct stm_ts_data *ts, u8 scan_mode); +int stm_ts_set_lowpowermode(void *data, u8 mode); +int stm_ts_set_aod_rect(struct stm_ts_data *ts); +void stm_ts_reset(struct stm_ts_data *ts, unsigned int ms); +void stm_ts_reset_work(struct work_struct *work); +void stm_ts_read_info_work(struct work_struct *work); +void stm_ts_print_info_work(struct work_struct *work); +int get_nvm_data_by_size(struct stm_ts_data *ts, u8 offset, int length, u8 *nvdata); +int get_nvm_data(struct stm_ts_data *ts, int type, u8 *nvdata); +int stm_ts_set_custom_library(struct stm_ts_data *ts); +void stm_ts_get_custom_library(struct stm_ts_data *ts); +void stm_ts_set_fod_finger_merge(struct stm_ts_data *ts); +int stm_ts_set_fod_rect(struct stm_ts_data *ts); +int stm_ts_set_touchable_area(struct stm_ts_data *ts); +int stm_ts_ear_detect_enable(struct stm_ts_data *ts, u8 enable); +int stm_ts_pocket_mode_enable(struct stm_ts_data *ts, u8 enable); +int stm_ts_set_wirelesscharger_mode(struct stm_ts_data *ts); +int stm_ts_set_wirecharger_mode(struct stm_ts_data *ts); +void stm_ts_set_cover_type(struct stm_ts_data *ts, bool enable); +int stm_ts_set_press_property(struct stm_ts_data *ts); +int stm_ts_get_sysinfo_data(struct stm_ts_data *ts, u8 sysinfo_addr, u8 read_cnt, u8 *data); +void stm_ts_change_scan_rate(struct stm_ts_data *ts, u8 rate); +int stm_ts_osc_trim_recovery(struct stm_ts_data *ts); +int get_nvm_data(struct stm_ts_data *ts, int type, u8 *nvdata); +int set_nvm_data(struct stm_ts_data *ts, u8 type, u8 *buf); +int get_nvm_data_by_size(struct stm_ts_data *ts, u8 offset, int length, u8 *nvdata); +int set_nvm_data_by_size(struct stm_ts_data *ts, u8 offset, int length, u8 *buf); +int stm_ts_get_channel_info(struct stm_ts_data *ts); +int stm_ts_set_opmode(struct stm_ts_data *ts, u8 mode); +int stm_ts_set_touch_function(struct stm_ts_data *ts); +void stm_ts_get_touch_function(struct work_struct *work); +int _stm_tclm_data_read(struct stm_ts_data *ts, int address); +int _stm_tclm_data_write(struct stm_ts_data *ts, int address); +int stm_ts_set_hsync_scanmode(struct stm_ts_data *ts, u8 scan_mode); +int stm_ts_fod_vi_event(struct stm_ts_data *ts); +int stm_ts_set_vvc_mode(struct stm_ts_data *ts, bool enable); +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) +void stm_ts_interrupt_notify(struct work_struct *work); +#endif +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) +int stm_ts_vbus_notification(struct notifier_block *nb, unsigned long cmd, void *data); +#endif +int stm_ts_tclm_execute_force_calibration(struct device *dev, int cal_mode); + +int stm_ts_sip_mode_enable(struct stm_ts_data *ts); +int stm_ts_game_mode_enable(struct stm_ts_data *ts); +int stm_ts_note_mode_enable(struct stm_ts_data *ts); +int stm_ts_dead_zone_enable(struct stm_ts_data *ts); + +//cmd +void stm_ts_fn_remove(struct stm_ts_data *ts); +int stm_ts_fn_init(struct stm_ts_data *ts); +int stm_ts_panel_ito_test(struct stm_ts_data *ts, int testmode); +void stm_ts_run_rawdata_all(struct stm_ts_data *ts); + +//dump +#if IS_ENABLED(CONFIG_TOUCHSCREEN_DUMP_MODE) +void stm_ts_check_rawdata(struct work_struct *work); +void stm_ts_dump_tsp_log(struct device *dev); +void stm_ts_sponge_dump_flush(struct stm_ts_data *ts, int dump_area); +#endif + +//fw +int stm_ts_fw_update_on_probe(struct stm_ts_data *ts); +int stm_ts_fw_update_on_hidden_menu(struct stm_ts_data *ts, int update_type); +int stm_ts_wait_for_echo_event(struct stm_ts_data *ts, u8 *cmd, u8 cmd_cnt, int delay); +int stm_ts_fw_wait_for_event(struct stm_ts_data *ts, u8 *result, u8 result_cnt); +void stm_ts_checking_miscal(struct stm_ts_data *ts); + +#ifdef CONFIG_TOUCHSCREEN_DUMP_MODE +extern struct tsp_dump_callbacks dump_callbacks; +#endif +#ifdef ENABLE_RAWDATA_SERVICE +void stm_ts_read_rawdata_address(struct stm_ts_data *ts); +int stm_ts_rawdata_buffer_alloc(struct stm_ts_data *ts); +int stm_ts_rawdata_init(struct stm_ts_data *ts); +void stm_ts_rawdata_buffer_remove(struct stm_ts_data *ts); +#endif + +#if IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) && IS_ENABLED(CONFIG_SEC_FACTORY) +extern int panel_notifier_register(struct notifier_block *nb); +extern int panel_notifier_unregister(struct notifier_block *nb); +#endif + +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) +#if IS_ENABLED(CONFIG_INPUT_SEC_TRUSTED_TOUCH) +#if !IS_ENABLED(CONFIG_ARCH_QTI_VM) +void stm_ts_trusted_touch_tvm_i2c_failure_report(struct stm_ts_data *ts); +#endif +#endif +#endif + +#endif /* _LINUX_STM_TS_H_ */ + diff --git a/drivers/input/sec_input/stm_spi/stm_dump.c b/drivers/input/sec_input/stm_spi/stm_dump.c new file mode 100644 index 000000000000..a76364b2ff07 --- /dev/null +++ b/drivers/input/sec_input/stm_spi/stm_dump.c @@ -0,0 +1,114 @@ +/* drivers/input/sec_input/stm/stm_dump.c + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * + * Core file for Samsung TSC driver + * + * 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. + */ + +#include "stm_dev.h" +#include "stm_reg.h" + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_DUMP_MODE) +void stm_ts_check_rawdata(struct work_struct *work) +{ + struct stm_ts_data *ts = container_of(work, struct stm_ts_data, check_rawdata.work); + + if (ts->tsp_dump_lock == 1) { + input_err(true, ts->dev, "%s: ignored ## already checking..\n", __func__); + return; + } + if (atomic_read(&ts->plat_data->power_state) == SEC_INPUT_STATE_POWER_OFF) { + input_err(true, ts->dev, "%s: ignored ## IC is power off\n", __func__); + return; + } + + stm_ts_run_rawdata_all(ts); +} + +void stm_ts_dump_tsp_log(struct device *dev) +{ + struct stm_ts_data *ts = dev_get_drvdata(dev); + + pr_info("%s: %s %s: start\n", STM_TS_I2C_NAME, SECLOG, __func__); + + if (!ts) { + pr_err("%s: %s %s: ignored ## tsp probe fail!!\n", STM_TS_I2C_NAME, SECLOG, __func__); + return; + } + + schedule_delayed_work(&ts->check_rawdata, msecs_to_jiffies(100)); +} + +void stm_ts_sponge_dump_flush(struct stm_ts_data *ts, int dump_area) +{ + int i, ret; + unsigned char *sec_spg_dat; + input_info(true, ts->dev, "%s: ++\n", __func__); + + sec_spg_dat = vmalloc(SEC_TS_MAX_SPONGE_DUMP_BUFFER); + if (!sec_spg_dat) { + input_err(true, ts->dev, "%s : Failed!!\n", __func__); + return; + } + memset(sec_spg_dat, 0, SEC_TS_MAX_SPONGE_DUMP_BUFFER); + + input_info(true, ts->dev, "%s: dump area=%d\n", __func__, dump_area); + + /* check dump area */ + if (dump_area == 0) { + sec_spg_dat[0] = SEC_TS_CMD_SPONGE_LP_DUMP_EVENT; + sec_spg_dat[1] = 0; + } else { + sec_spg_dat[0] = (u8)ts->sponge_dump_border & 0xff; + sec_spg_dat[1] = (u8)(ts->sponge_dump_border >> 8); + } + + /* dump all events at once */ + + if (ts->sponge_dump_event * ts->sponge_dump_format > SEC_TS_MAX_SPONGE_DUMP_BUFFER) { + input_err(true, ts->dev, "%s: wrong sponge dump read size (%d)\n", + __func__, ts->sponge_dump_event * ts->sponge_dump_format); + vfree(sec_spg_dat); + return; + } + + ret = ts->stm_ts_read_sponge(ts, sec_spg_dat, ts->sponge_dump_event * ts->sponge_dump_format); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to read sponge\n", __func__); + vfree(sec_spg_dat); + return; + } + + for (i = 0 ; i < ts->sponge_dump_event ; i++) { + int e_offset = i * ts->sponge_dump_format; + char buff[30] = {0, }; + u16 edata[5]; + edata[0] = (sec_spg_dat[1 + e_offset] & 0xFF) << 8 | (sec_spg_dat[0 + e_offset] & 0xFF); + edata[1] = (sec_spg_dat[3 + e_offset] & 0xFF) << 8 | (sec_spg_dat[2 + e_offset] & 0xFF); + edata[2] = (sec_spg_dat[5 + e_offset] & 0xFF) << 8 | (sec_spg_dat[4 + e_offset] & 0xFF); + edata[3] = (sec_spg_dat[7 + e_offset] & 0xFF) << 8 | (sec_spg_dat[6 + e_offset] & 0xFF); + edata[4] = (sec_spg_dat[9 + e_offset] & 0xFF) << 8 | (sec_spg_dat[8 + e_offset] & 0xFF); + + if (edata[0] || edata[1] || edata[2] || edata[3] || edata[4]) { + snprintf(buff, sizeof(buff), "%03d: %04x%04x%04x%04x%04x\n", + i + (ts->sponge_dump_event * dump_area), + edata[0], edata[1], edata[2], edata[3], edata[4]); +#if IS_ENABLED(CONFIG_SEC_DEBUG_TSP_LOG) + sec_tsp_sponge_log(buff); +#endif + } + } + + vfree(sec_spg_dat); + input_info(true, ts->dev, "%s: --\n", __func__); + return; +} +EXPORT_SYMBOL(stm_ts_sponge_dump_flush); +#endif + +MODULE_LICENSE("GPL"); + diff --git a/drivers/input/sec_input/stm_spi/stm_fn.c b/drivers/input/sec_input/stm_spi/stm_fn.c new file mode 100644 index 000000000000..f36ac095dac5 --- /dev/null +++ b/drivers/input/sec_input/stm_spi/stm_fn.c @@ -0,0 +1,1821 @@ +/* drivers/input/sec_input/stm/stm_fn.c + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * + * Core file for Samsung TSC driver + * + * 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. + */ + +#include "stm_dev.h" +#include "stm_reg.h" + +int stm_ts_set_custom_library(struct stm_ts_data *ts) +{ + u8 data[3] = { 0 }; + int ret; + u8 force_fod_enable = 0; + +#if IS_ENABLED(CONFIG_SEC_FACTORY) + /* enable FOD when LCD on state */ + if (ts->plat_data->support_fod && atomic_read(&ts->plat_data->enabled)) + force_fod_enable = SEC_TS_MODE_SPONGE_PRESS; +#endif + + input_err(true, ts->dev, "%s: Sponge (0x%02x)%s\n", + __func__, ts->plat_data->lowpower_mode, + force_fod_enable ? ", force fod enable" : ""); + + if (ts->plat_data->prox_power_off) { + data[2] = (ts->plat_data->lowpower_mode | force_fod_enable) & + ~SEC_TS_MODE_SPONGE_DOUBLETAP_TO_WAKEUP; + input_info(true, ts->dev, "%s: prox off. disable AOT\n", __func__); + } else { + data[2] = ts->plat_data->lowpower_mode | force_fod_enable; + } + + ret = ts->stm_ts_write_sponge(ts, data, 3); + if (ret < 0) + input_err(true, ts->dev, "%s: Failed to write sponge\n", __func__); + +#if !IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP) + /* inf dump enable */ + data[0] = STM_TS_CMD_SPONGE_OFFSET_MODE_01; + data[2] = ts->plat_data->sponge_mode |= SEC_TS_MODE_SPONGE_INF_DUMP; + + ret = ts->stm_ts_write_sponge(ts, data, 3); + if (ret < 0) + input_err(true, ts->dev, "%s: Failed to write sponge\n", __func__); +#endif + /* read dump info */ + data[0] = STM_TS_CMD_SPONGE_LP_DUMP; + + ret = ts->stm_ts_read_sponge(ts, data, 2); + if (ret < 0) + input_err(true, ts->dev, "%s: Failed to read dump_data\n", __func__); + + ts->sponge_inf_dump = (data[0] & SEC_TS_SPONGE_DUMP_INF_MASK) >> SEC_TS_SPONGE_DUMP_INF_SHIFT; + ts->sponge_dump_format = data[0] & SEC_TS_SPONGE_DUMP_EVENT_MASK; + ts->sponge_dump_event = data[1]; + ts->sponge_dump_border = SEC_TS_CMD_SPONGE_LP_DUMP_EVENT + + (ts->sponge_dump_format * ts->sponge_dump_event); + ts->sponge_dump_border_lsb = ts->sponge_dump_border & 0xFF; + ts->sponge_dump_border_msb = (ts->sponge_dump_border & 0xFF00) >> 8; + + input_info(true, ts->dev, "%s: sponge_inf_dump:%d, sponge_dump_format:%d, sponge_dump_event:%d\n", __func__, + ts->sponge_inf_dump, ts->sponge_dump_format, ts->sponge_dump_event); + + return ret; +} + +void stm_ts_get_custom_library(struct stm_ts_data *ts) +{ + u8 data[6] = { 0 }; + int ret, i; + + data[0] = SEC_TS_CMD_SPONGE_AOD_ACTIVE_INFO; + ret = ts->stm_ts_read_sponge(ts, data, 6); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to read aod active area\n", __func__); + return; + } + + for (i = 0; i < 3; i++) + ts->plat_data->aod_data.active_area[i] = (data[i * 2 + 1] & 0xFF) << 8 | (data[i * 2] & 0xFF); + + input_info(true, ts->dev, "%s: aod_active_area - top:%d, edge:%d, bottom:%d\n", + __func__, ts->plat_data->aod_data.active_area[0], + ts->plat_data->aod_data.active_area[1], ts->plat_data->aod_data.active_area[2]); + + memset(data, 0x00, 6); + + data[0] = SEC_TS_CMD_SPONGE_FOD_INFO; + ret = ts->stm_ts_read_sponge(ts, data, 4); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to read fod info\n", __func__); + return; + } + + sec_input_set_fod_info(ts->dev, data[0], data[1], data[2], data[3]); + + data[0] = STM_TS_CMD_SPONGE_OFFSET_MODE_01; + ret = ts->stm_ts_read_sponge(ts, data, 2); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to read mode 01 info\n", __func__); + return; + } + ts->plat_data->sponge_mode = data[0]; + input_info(true, ts->dev, "%s: sponge_mode %x\n", __func__, ts->plat_data->sponge_mode); +} + +void stm_ts_set_fod_finger_merge(struct stm_ts_data *ts) +{ + int ret; + u8 address[2] = {STM_TS_CMD_SET_FOD_FINGER_MERGE, 0}; + + if (!ts->plat_data->support_fod) + return; + + if (ts->plat_data->lowpower_mode & SEC_TS_MODE_SPONGE_PRESS) + address[1] = 1; + else + address[1] = 0; + + mutex_lock(&ts->sponge_mutex); + input_info(true, ts->dev, "%s: %d\n", __func__, address[1]); + + ret = ts->stm_ts_write(ts, address, 2, NULL, 0); + if (ret < 0) + input_err(true, ts->dev, "%s: failed\n", __func__); + mutex_unlock(&ts->sponge_mutex); +} + +void stm_ts_read_chip_id(struct stm_ts_data *ts) +{ + u8 address = STM_TS_READ_DEVICE_ID; + u8 data[5] = {0}; + int ret; + + ret = ts->stm_ts_read(ts, &address, 1, &data[0], 5); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed. ret: %d\n", __func__, ret); + return; + } + + ts->chip_id = (data[2] << 16) + (data[3] << 8) + data[4]; + + input_info(true, ts->dev, "%s: %c %c %02X %02X %02X (%x)\n", + __func__, data[0], data[1], data[2], data[3], data[4], ts->chip_id); +} + +void stm_ts_read_chip_id_hw(struct stm_ts_data *ts) +{ + u8 address[5] = {STM_TS_CMD_REG_R, 0x20, 0x00, 0x00, 0x00 }; + u8 data[8] = {0}; + int ret; + + ret = ts->stm_ts_read(ts, address, 5, &data[0], 8); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed. ret: %d\n", __func__, ret); + } + + input_info(true, ts->dev, "%s: %02X %02X %02X %02X %02X %02X %02X %02X\n", + __func__, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]); +} + +int stm_ts_get_channel_info(struct stm_ts_data *ts) +{ + int ret = -1; + u8 address = 0; + u8 data[STM_TS_EVENT_BUFF_SIZE] = { 0 }; + + memset(data, 0x0, STM_TS_EVENT_BUFF_SIZE); + + address = STM_TS_READ_PANEL_INFO; + ret = ts->stm_ts_read(ts, &address, 1, data, 11); + if (ret < 0) { + ts->tx_count = 0; + ts->rx_count = 0; + input_err(true, ts->dev, "%s: Get channel info Read Fail!!\n", __func__); + return ret; + } + + ts->tx_count = data[8]; // Number of TX CH + ts->rx_count = data[9]; // Number of RX CH + + if (!(ts->tx_count > 0 && ts->tx_count < STM_TS_MAX_NUM_FORCE && + ts->rx_count > 0 && ts->rx_count < STM_TS_MAX_NUM_SENSE)) { + ts->tx_count = STM_TS_MAX_NUM_FORCE; + ts->rx_count = STM_TS_MAX_NUM_SENSE; + input_err(true, ts->dev, "%s: set channel num based on max value, check it!\n", __func__); + } + + ts->plat_data->x_node_num = ts->tx_count; + ts->plat_data->y_node_num = ts->rx_count; + + ts->resolution_x = (data[0] << 8) | data[1]; // X resolution of IC + ts->resolution_y = (data[2] << 8) | data[3]; // Y resolution of IC + + input_info(true, ts->dev, "%s: RX:Sense(%02d) TX:Force(%02d) resolution:(IC)x:%d y:%d, (DT)x:%d,y:%d\n", + __func__, ts->rx_count, ts->tx_count, + ts->resolution_x, ts->resolution_y, ts->plat_data->max_x, ts->plat_data->max_y); + + if (ts->resolution_x > 0 && ts->resolution_x < STM_TS_MAX_X_RESOLUTION && + ts->resolution_y > 0 && ts->resolution_y < STM_TS_MAX_Y_RESOLUTION && + ts->plat_data->max_x != ts->resolution_x && ts->plat_data->max_y != ts->resolution_y) { + ts->plat_data->max_x = ts->resolution_x; + ts->plat_data->max_y = ts->resolution_y; + input_err(true, ts->dev, "%s: set resolution based on ic value, check it!\n", __func__); + } + + if (data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x00 && data[3] == 0x00 && data[8] == 0x00 && data[9] == 0x00) { + input_err(true, ts->dev, "%s: channel number and resoltion value is zero. return ENODEV\n", __func__); + return -ENODEV; + } + + if (data[0] == 0xFF && data[1] == 0xFF && data[2] == 0xFF && data[3] == 0xFF && data[8] == 0xFF && data[9] == 0xFF) { + input_err(true, ts->dev, "%s: channel number and resoltion value is FF. return ENODEV\n", __func__); + return -ENODEV; + } + + return ret; +} + +int stm_ts_get_sysinfo_data(struct stm_ts_data *ts, u8 sysinfo_addr, u8 read_cnt, u8 *data) +{ + int ret; + u8 *buff = NULL; + + u8 address[3] = { 0xA4, 0x06, 0x01 }; // request system information + + ret = stm_ts_wait_for_echo_event(ts, &address[0], 3, 0); + if (ret < 0) { + input_err(true, ts->dev, "%s: timeout wait for event\n", __func__); + goto ERROR; + } + + address[0] = 0xA6; + address[1] = 0x00; + address[2] = sysinfo_addr; + + buff = kzalloc(read_cnt, GFP_KERNEL); + if (unlikely(buff == NULL)) { + ret = -STM_TS_ERROR; + goto ERROR; + } + + ret = ts->stm_ts_read(ts, &address[0], 3, &buff[0], read_cnt); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed. ret: %d\n", + __func__, ret); + kfree(buff); + goto ERROR; + } + + memcpy(data, &buff[0], read_cnt); + kfree(buff); + +ERROR: + return ret; +} + +int stm_ts_get_version_info(struct stm_ts_data *ts) +{ + int ret; + u8 address = STM_TS_READ_FW_VERSION; + u8 data[STM_TS_VERSION_SIZE] = { 0 }; + + memset(data, 0x0, STM_TS_VERSION_SIZE); + + ret = ts->stm_ts_read(ts, &address, 1, (u8 *)data, STM_TS_VERSION_SIZE); + + ts->fw_version_of_ic = (data[0] << 8) + data[1]; + ts->config_version_of_ic = (data[2] << 8) + data[3]; + ts->fw_main_version_of_ic = data[4] + (data[5] << 8); + ts->project_id_of_ic = data[6]; + ts->ic_name_of_ic = data[7]; + ts->module_version_of_ic = data[8]; + + ts->plat_data->ic_vendor_name[0] = 'S'; + ts->plat_data->ic_vendor_name[1] = 'T'; + + ts->plat_data->img_version_of_ic[0] = ts->ic_name_of_ic; + ts->plat_data->img_version_of_ic[1] = ts->project_id_of_ic; + ts->plat_data->img_version_of_ic[2] = ts->module_version_of_ic; + ts->plat_data->img_version_of_ic[3] = ts->fw_main_version_of_ic & 0xFF; + + input_info(true, ts->dev, + "%s: [IC] Firmware Ver: 0x%04X, Config Ver: 0x%04X, Main Ver: 0x%04X\n", + __func__, ts->fw_version_of_ic, + ts->config_version_of_ic, ts->fw_main_version_of_ic); + input_info(true, ts->dev, + "%s: [IC] Project ID: 0x%02X, IC Name: 0x%02X, Module Ver: 0x%02X\n", + __func__, ts->project_id_of_ic, + ts->ic_name_of_ic, ts->module_version_of_ic); + + return ret; +} + +void stm_ts_command(struct stm_ts_data *ts, u8 cmd, bool checkecho) +{ + int ret = 0; + + + if (checkecho) + ret = stm_ts_wait_for_echo_event(ts, &cmd, 1, 100); + else + ret = ts->stm_ts_write(ts, &cmd, 1, 0, 0); + if (ret < 0) + input_err(true, ts->dev, + "%s: failed to write command(0x%02X), ret = %d\n", __func__, cmd, ret); + +} + +int stm_ts_systemreset(struct stm_ts_data *ts, unsigned int msec) +{ + u8 address = STM_TS_CMD_REG_W; + u8 data[5] = { 0x20, 0x00, 0x00, 0x24, 0x81 }; + int ret; + + input_info(true, ts->dev, "%s\n", __func__); + + disable_irq(ts->irq); + + ts->stm_ts_write(ts, &address, 1, &data[0], 5); + + sec_delay(msec + 10); + + ret = stm_ts_wait_for_ready(ts); + + stm_ts_release_all_finger(ts); + + enable_irq(ts->irq); + + return ret; +} + +int stm_ts_fix_active_mode(struct stm_ts_data *ts, int mode) +{ + u8 address[3] = {0xA0, 0x00, 0x00}; + int ret = -EINVAL; + + switch (mode) { + case STM_TS_ACTIVE_FALSE: + address[1] = 0x00; + address[2] = 0x01; + ts->stm_ts_command(ts, STM_TS_CMD_SENSE_OFF, false); + sec_delay(10); + break; + case STM_TS_ACTIVE_TRUE: + address[1] = 0x03; + address[2] = 0x00; + break; + case STM_TS_ACTIVE_FALSE_SNR: + address[1] = 0x00; + address[2] = 0x01; + break; + default: + input_err(true, ts->dev, "%s: err data mode: %d\n", __func__, mode); + return ret; + } + + ret = ts->stm_ts_write(ts, &address[0], 3, NULL, 0); + if (ret < 0) + input_err(true, ts->dev, "%s: err: %d, mode: %d\n", __func__, ret, mode); + else + input_info(true, ts->dev, "%s: %d STM_TS_ACTIVE_%s\n", __func__, mode, + (mode == STM_TS_ACTIVE_TRUE) ? "TRUE" : + (mode == STM_TS_ACTIVE_FALSE) ? "FALSE" : "FALSE_SNR"); + + sec_delay(10); + return ret; +} + +void stm_ts_change_scan_rate(struct stm_ts_data *ts, u8 rate) +{ + u8 address = STM_TS_CMD_SET_GET_REPORT_RATE; + u8 data = rate; + int ret = 0; + + ret = ts->stm_ts_write(ts, &address, 1, &data, 1); + + input_dbg(true, ts->dev, "%s: scan rate (%d Hz), ret = %d\n", __func__, address, ret); +} + +int stm_ts_fw_corruption_check(struct stm_ts_data *ts) +{ + u8 address[6] = { 0, }; + u8 val = 0; + int ret; + + ret = ts->stm_ts_systemreset(ts, 0); + if (ret < 0) { + input_info(true, ts->dev, "%s: stm_ts_systemreset fail (%d)\n", __func__, ret); + goto out; + } + + /* Firmware Corruption Check */ + address[0] = STM_TS_CMD_REG_R; + address[1] = 0x20; + address[2] = 0x00; + address[3] = 0x00; + address[4] = 0x78; + ret = ts->stm_ts_read(ts, address, 5, &val, 1); + if (ret < 0) { + ret = -STM_TS_I2C_ERROR; + goto out; + } + if (val & 0x03) { // Check if crc error + input_err(true, ts->dev, "%s: firmware corruption. CRC status:%02X\n", + __func__, val & 0x03); + ret = -STM_TS_ERROR_FW_CORRUPTION; + } else { + ret = 0; + } + +out: + return ret; +} + +int stm_ts_wait_for_ready(struct stm_ts_data *ts) +{ + struct stm_ts_event_status *p_event_status; + int ret = -STM_TS_ERROR; + u8 address = STM_TS_READ_ONE_EVENT; + u8 data[STM_TS_EVENT_BUFF_SIZE]; + int retry = 0; + int err_cnt = 0; + + mutex_lock(&ts->fn_mutex); + memset(data, 0x0, STM_TS_EVENT_BUFF_SIZE); + + while (ts->stm_ts_read(ts, &address, 1, (u8 *)data, STM_TS_EVENT_BUFF_SIZE) >= 0) { + p_event_status = (struct stm_ts_event_status *)data; + + if ((p_event_status->stype == STM_TS_EVENT_STATUSTYPE_INFO) && + (p_event_status->status_id == STM_TS_INFO_READY_STATUS)) { + ret = 0; + break; + } + + if (data[0] == 0xff && data[1] == 0xff && data[2] == 0xff) { + ret = -STM_TS_ERROR_BROKEN_FW; + break; + } + + if (data[0] == STM_TS_EVENT_ERROR_REPORT) { + input_err(true, ts->dev, + "%s: Err detected %02X, %02X, %02X, %02X, %02X, %02X, %02X, %02X\n", + __func__, data[0], data[1], data[2], data[3], + data[4], data[5], data[6], data[7]); + + // check if config / cx / panel configuration area is corrupted + if (((data[1] >= 0x20) && (data[1] <= 0x23)) || ((data[1] >= 0xA0) && (data[1] <= 0xA8))) { + ret = -STM_TS_ERROR_FW_CORRUPTION; + ts->plat_data->hw_param.checksum_result = 1; + ts->fw_corruption = true; + input_err(true, ts->dev, "%s: flash corruption\n", __func__); + break; + } + if (data[1] == 0x24 || data[1] == 0x25 || data[1] == 0x29 || data[1] == 0x2A || data[1] == 0x2D || data[1] == 0x34) { + input_err(true, ts->dev, "%s: osc trim is broken\n", __func__); + ret = -STM_TS_ERROR_BROKEN_OSC_TRIM; + ts->fw_corruption = true; + break; + } + + if (err_cnt++ > 32) { + ret = -STM_TS_ERROR_EVENT_ID; + break; + } + continue; + } + + if (retry++ > STM_TS_RETRY_COUNT * 15) { + ret = -STM_TS_ERROR_TIMEOUT; + if (data[0] == 0 && data[1] == 0 && data[2] == 0) + ret = -STM_TS_ERROR_TIMEOUT_ZERO; + + input_err(true, ts->dev, "%s: Time Over\n", __func__); + + if (atomic_read(&ts->plat_data->power_state) == SEC_INPUT_STATE_LPM) + schedule_delayed_work(&ts->reset_work, msecs_to_jiffies(10)); + break; + } + sec_delay(20); + } + + input_info(true, ts->dev, + "%s: %02X, %02X, %02X, %02X, %02X, %02X, %02X, %02X\n", + __func__, data[0], data[1], data[2], data[3], + data[4], data[5], data[6], data[7]); + + mutex_unlock(&ts->fn_mutex); + + return ret; + +} + +int stm_ts_wait_for_echo_event(struct stm_ts_data *ts, u8 *cmd, u8 cmd_cnt, int delay) +{ + int ret; + int i; + bool matched = false; + u8 reg = STM_TS_READ_ONE_EVENT; + u8 data[STM_TS_EVENT_BUFF_SIZE]; + int retry = 0; + + mutex_lock(&ts->fn_mutex); + disable_irq(ts->irq); + + ret = ts->stm_ts_write(ts, cmd, cmd_cnt, NULL, 0); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to write command\n", __func__); + enable_irq(ts->irq); + mutex_unlock(&ts->fn_mutex); + return ret; + } + + if (delay) + sec_delay(delay); + else + sec_delay(5); + + memset(data, 0x0, STM_TS_EVENT_BUFF_SIZE); + + ret = -EIO; + + while (ts->stm_ts_read(ts, ®, 1, (u8 *)data, STM_TS_EVENT_BUFF_SIZE) >= 0) { + if (data[0] != 0x00) + input_info(true, ts->dev, + "%s: event %02X, %02X, %02X, %02X, %02X, %02X, %02X, %02X," + " %02X, %02X, %02X, %02X, %02X, %02X, %02X, %02X\n", + __func__, data[0], data[1], data[2], data[3], + data[4], data[5], data[6], data[7], + data[8], data[9], data[10], data[11], + data[12], data[13], data[14], data[15]); + + if ((data[0] == STM_TS_EVENT_STATUS_REPORT) && (data[1] == 0x01)) { // Check command ECHO + int loop_cnt; + + if (cmd_cnt > 4) + loop_cnt = 4; + else + loop_cnt = cmd_cnt; + + for (i = 0; i < loop_cnt; i++) { + if (data[i + 2] != cmd[i]) { + matched = false; + break; + } + matched = true; + } + + if (matched == true) { + ret = 0; + break; + } + } else if (data[0] == STM_TS_EVENT_ERROR_REPORT) { + input_info(true, ts->dev, "%s: Error detected %02X,%02X,%02X,%02X,%02X,%02X\n", + __func__, data[0], data[1], data[2], data[3], data[4], data[5]); + + if (retry >= STM_TS_RETRY_COUNT) + break; + } + + if (retry++ > STM_TS_RETRY_COUNT * 50) { + input_err(true, ts->dev, "%s: Time Over (%02X,%02X,%02X,%02X,%02X,%02X)\n", + __func__, data[0], data[1], data[2], data[3], data[4], data[5]); + break; + } + sec_delay(20); + } + + enable_irq(ts->irq); + mutex_unlock(&ts->fn_mutex); + + return ret; +} + +int stm_ts_set_scanmode(struct stm_ts_data *ts, u8 scan_mode) +{ + u8 address[3] = { 0xA0, 0x00, scan_mode }; + int ret; + + ret = stm_ts_wait_for_echo_event(ts, &address[0], 3, 20); + if (ret < 0) { + input_info(true, ts->dev, "%s: timeout, ret = %d\n", __func__, ret); + return ret; + } + + input_info(true, ts->dev, "%s: 0x%02X\n", __func__, scan_mode); + + return 0; + +} + +/* optional reg : SEC_TS_CMD_LPM_AOD_OFF_ON(0x9B) */ +/* 0 : Async base scan (default on lp mode) */ +/* 1 : sync base scan */ +int stm_ts_set_hsync_scanmode(struct stm_ts_data *ts, u8 scan_mode) +{ + u8 address[2] = { STM_TS_CMD_SET_LPM_AOD_OFF_ON, 0}; + int ret; + + input_info(true, ts->dev, "%s: mode:%x\n", __func__, scan_mode); + address[1] = scan_mode; + + ret = ts->stm_ts_write(ts, &address[0], 2, NULL, 0); + if (ret < 0) + input_err(true, ts->dev, "%s: Failed to send command: %x/%x", + __func__, address[0], address[1]); + return ret; +} + +int stm_ts_set_touch_function(struct stm_ts_data *ts) +{ + int ret = 0; + u8 address = 0; + u8 data[2] = { 0 }; + + address = STM_TS_CMD_SET_GET_TOUCHTYPE; + data[0] = (u8)(ts->plat_data->touch_functions & 0xFF); + data[1] = (u8)(ts->plat_data->touch_functions >> 8); + ret = ts->stm_ts_write(ts, &address, 1, data, 2); + if (ret < 0) + input_err(true, ts->dev, "%s: Failed to send command(0x%x)", + __func__, STM_TS_CMD_SET_GET_TOUCHTYPE); + + if (!atomic_read(&ts->plat_data->shutdown_called)) + schedule_delayed_work(&ts->work_read_functions, msecs_to_jiffies(30)); + + return ret; +} + +void stm_ts_get_touch_function(struct work_struct *work) +{ + struct stm_ts_data *ts = container_of(work, struct stm_ts_data, + work_read_functions.work); + int ret = 0; + u8 address = 0; + u8 data[2] = { 0 }; + + address = STM_TS_CMD_SET_GET_TOUCHTYPE; + data[0] = (u8)(ts->plat_data->touch_functions & 0xFF); + data[1] = (u8)(ts->plat_data->touch_functions >> 8); + ret = ts->stm_ts_read(ts, &address, 1, (u8 *)&(ts->plat_data->ic_status), 2); + if (ret < 0) { + input_err(true, ts->dev, + "%s: failed to read touch functions(%d)\n", + __func__, ret); + return; + } + + input_info(true, ts->dev, + "%s: touch_functions:%x ic_status:%x\n", __func__, + ts->plat_data->touch_functions, ts->plat_data->ic_status); +} + +int stm_ts_osc_trim_recovery(struct stm_ts_data *ts) +{ + u8 address[3]; + int ret; + + input_info(true, ts->dev, "%s\n", __func__); + + /* OSC trim error recovery command. */ + address[0] = 0xA4; + address[1] = 0x00; + address[2] = 0x05; + + ret = stm_ts_wait_for_echo_event(ts, &address[0], 3, 800); + if (ret < 0) { + ret = -STM_TS_ERROR_BROKEN_OSC_TRIM; + goto out; + } + + /* save panel configuration area */ + address[0] = 0xA4; + address[1] = 0x05; + address[2] = 0x04; + + ret = stm_ts_wait_for_echo_event(ts, &address[0], 3, 100); + if (ret < 0) { + ret = -STM_TS_ERROR_BROKEN_OSC_TRIM; + goto out; + } + + sec_delay(500); + ret = ts->stm_ts_systemreset(ts, 0); + sec_delay(50); +out: + return ret; +} + +int stm_ts_set_vvc_mode(struct stm_ts_data *ts, bool enable) +{ + int ret = 0; + u8 data[3]; + + /* register */ + data[0] = 0xC1; + data[1] = 0x17; + if (enable) + data[2] = ts->vvc_mode; + else + data[2] = 0x00; + + ret = ts->stm_ts_write(ts, data, 3, NULL, 0); + input_info(true, ts->dev, "%s: vvc mode write: %02X, ret: %d\n", __func__, data[2], ret); + return ret; +} + +int stm_ts_set_opmode(struct stm_ts_data *ts, u8 mode) +{ + int ret; + u8 address[2] = {STM_TS_CMD_SET_GET_OPMODE, mode}; + + ret = ts->stm_ts_write(ts, &address[0], 2, NULL, 0); + if (ret < 0) + input_err(true, ts->dev, "%s: Failed to write opmode", __func__); + + sec_delay(5); + + if (ts->plat_data->lowpower_mode) { + address[0] = STM_TS_CMD_WRITE_WAKEUP_GESTURE; + address[1] = 0x02; + ret = ts->stm_ts_write(ts, &address[0], 1, &address[1], 1); + if (ret < 0) + input_err(true, ts->dev, "%s: Failed to send lowpower flag command", __func__); + } + + input_info(true, ts->dev, "%s: opmode %d", __func__, mode); + + return ret; +} + + +void stm_ts_set_utc_sponge(struct stm_ts_data *ts) +{ + struct timespec64 current_time; + u8 data[6] = {STM_TS_CMD_SPONGE_OFFSET_UTC, 0}; + int ret = 0; + + ktime_get_real_ts64(¤t_time); + data[2] = (0xFF & (u8)((current_time.tv_sec) >> 0)); + data[3] = (0xFF & (u8)((current_time.tv_sec) >> 8)); + data[4] = (0xFF & (u8)((current_time.tv_sec) >> 16)); + data[5] = (0xFF & (u8)((current_time.tv_sec) >> 24)); + input_info(true, ts->dev, "Write UTC to Sponge = %X\n", (int)(current_time.tv_sec)); + + + ret = ts->stm_ts_write_sponge(ts, data, 6); + if (ret < 0) + input_err(true, ts->dev, "%s: Failed to write sponge\n", __func__); +} + + +int stm_ts_set_lowpowermode(void *data, u8 mode) +{ + struct stm_ts_data *ts = (struct stm_ts_data *)data; + int ret; + int retrycnt = 0; + char para = 0; + u8 address = 0; + + input_err(true, ts->dev, "%s: %s(%X)\n", __func__, + mode == TO_LOWPOWER_MODE ? "ENTER" : "EXIT", ts->plat_data->lowpower_mode); + + if (mode) { + stm_ts_set_utc_sponge(ts); + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_DUMP_MODE) + if (ts->sponge_inf_dump) { + if (ts->sponge_dump_delayed_flag) { + stm_ts_sponge_dump_flush(ts, ts->sponge_dump_delayed_area); + ts->sponge_dump_delayed_flag = false; + input_info(true, ts->dev, "%s : Sponge dump flush delayed work have procceed\n", __func__); + } + } +#endif + ret = stm_ts_set_custom_library(ts); + if (ret < 0) + goto error; + } else { + if (!atomic_read(&ts->plat_data->shutdown_called)) + stm_ts_get_touch_function(&ts->work_read_functions.work); + } + +retry_pmode: + if (mode) { + stm_ts_command(ts, STM_TS_CMD_CLEAR_ALL_EVENT, false); + ret = stm_ts_set_opmode(ts, STM_TS_OPMODE_LOWPOWER); + } else { + ret = stm_ts_set_opmode(ts, STM_TS_OPMODE_NORMAL); + } + if (ret < 0) { + input_err(true, ts->dev, "%s: stm_ts_set_opmode failed!\n", __func__); + goto error; + } + + sec_delay(ts->lpmode_change_delay); + + address = STM_TS_CMD_SET_GET_OPMODE; + ret = ts->stm_ts_read(ts, &address, 1, ¶, 1); + if (ret < 0) { + input_err(true, ts->dev, "%s: read power mode failed!\n", __func__); + retrycnt++; + if (retrycnt < 10) + goto retry_pmode; + } + + input_info(true, ts->dev, "%s: write(%d) read(%d) retry %d\n", __func__, mode, para, retrycnt); + + if (mode != para) { + retrycnt++; + ts->plat_data->hw_param.mode_change_failed_count++; + if (retrycnt < 10) + goto retry_pmode; + } + + stm_ts_locked_release_all_finger(ts); + + if (mode) + enable_irq_wake(ts->irq); + else + disable_irq_wake(ts->irq); + + if (mode == TO_LOWPOWER_MODE) + atomic_set(&ts->plat_data->power_state, SEC_INPUT_STATE_LPM); + else + atomic_set(&ts->plat_data->power_state, SEC_INPUT_STATE_POWER_ON); + +error: + input_info(true, ts->dev, "%s: end %d\n", __func__, ret); + + return ret; +} + +void stm_ts_release_all_finger(struct stm_ts_data *ts) +{ + sec_input_release_all_finger(ts->dev); +} + +void stm_ts_locked_release_all_finger(struct stm_ts_data *ts) +{ + mutex_lock(&ts->eventlock); + + stm_ts_release_all_finger(ts); + + mutex_unlock(&ts->eventlock); +} + +void stm_ts_reset(struct stm_ts_data *ts, unsigned int ms) +{ + input_info(true, ts->dev, "%s: Recover IC, discharge time:%d\n", __func__, ms); + + if (ts->plat_data->power) + ts->plat_data->power(ts->dev, false); + + sec_delay(ms); + + if (ts->plat_data->power) + ts->plat_data->power(ts->dev, true); + + sec_delay(TOUCH_POWER_ON_DWORK_TIME); +} + +int stm_ts_reset_work_from_preparation_to_completion(struct stm_ts_data *ts, bool prepare, bool need_to_reset) +{ + if (prepare) { +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) + if (atomic_read(&ts->plat_data->secure_enabled) == SECURE_TOUCH_ENABLE) { + input_err(true, ts->dev, "%s: secure touch enabled\n", __func__); + return -1; + } +#endif + if (atomic_read(&ts->reset_is_on_going)) { + input_err(true, ts->dev, "%s: reset is ongoing\n", __func__); + return -1; + } + + mutex_lock(&ts->plat_data->enable_mutex); + __pm_stay_awake(ts->plat_data->sec_ws); + + atomic_set(&ts->reset_is_on_going, 1); + } else { + char result[32]; + + atomic_set(&ts->reset_is_on_going, 0); + + if (need_to_reset) { + cancel_delayed_work(&ts->reset_work); + if (!atomic_read(&ts->plat_data->shutdown_called)) + schedule_delayed_work(&ts->reset_work, msecs_to_jiffies(TOUCH_RESET_DWORK_TIME)); + } + + snprintf(result, sizeof(result), "RESULT=RESET"); + if (ts->probe_done) + sec_cmd_send_event_to_user(&ts->sec, NULL, result); + + mutex_unlock(&ts->plat_data->enable_mutex); + __pm_relax(ts->plat_data->sec_ws); + } + + return 0; +} + +void stm_ts_reset_work(struct work_struct *work) +{ + struct stm_ts_data *ts = container_of(work, struct stm_ts_data, reset_work.work); + int ret; + bool prepare = true; + bool need_to_reset = false; + +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) + if (atomic_read(&ts->plat_data->secure_enabled) == SECURE_TOUCH_ENABLE) { + input_err(true, ts->dev, "%s: secure touch enabled\n", __func__); + return; + } +#endif + + ret = stm_ts_reset_work_from_preparation_to_completion(ts, prepare, need_to_reset); + if (ret < 0) + return; + + prepare = false; + input_info(true, ts->dev, "%s\n", __func__); + + ts->plat_data->stop_device(ts); + sec_delay(TOUCH_POWER_ON_DWORK_TIME); + + ret = ts->plat_data->start_device(ts); + if (ret < 0) { + /* for ACT recovery fail test */ + char result[32]; + char test[32]; + snprintf(test, sizeof(test), "TEST=RECOVERY"); + snprintf(result, sizeof(result), "RESULT=FAIL"); + if (ts->probe_done) + sec_cmd_send_event_to_user(&ts->sec, test, result); + + input_err(true, ts->dev, "%s: Reset failure, ret:%d\n", __func__, ret); + need_to_reset = true; + goto reset_work_completion; + } + + if (!atomic_read(&ts->plat_data->enabled)) { + input_err(true, ts->dev, "%s: call input_close\n", __func__); + + if (sec_input_need_ic_off(ts->plat_data)) { + ts->plat_data->stop_device(ts); + } else { + ret = ts->plat_data->lpmode(ts, TO_LOWPOWER_MODE); + if (ret < 0) { + input_err(true, ts->dev, "%s: Reset failure, ret:%d\n", __func__, ret); + need_to_reset = true; + goto reset_work_completion; + } + + if (ts->plat_data->lowpower_mode & SEC_TS_MODE_SPONGE_AOD) + stm_ts_set_aod_rect(ts); + } + } + + if ((atomic_read(&ts->plat_data->power_state) == SEC_INPUT_STATE_POWER_ON) && (ts->fix_active_mode)) + stm_ts_fix_active_mode(ts, STM_TS_ACTIVE_TRUE); + +reset_work_completion: + stm_ts_reset_work_from_preparation_to_completion(ts, prepare, need_to_reset); +} + +void stm_ts_print_info_work(struct work_struct *work) +{ + struct stm_ts_data *ts = container_of(work, struct stm_ts_data, + work_print_info.work); + + sec_input_print_info(ts->dev, ts->tdata); + + if (atomic_read(&ts->sec.cmd_is_running)) + input_err(true, ts->dev, "%s: skip set temperature, cmd running\n", __func__); + else + sec_input_set_temperature(ts->dev, SEC_INPUT_SET_TEMPERATURE_NORMAL); + + if (!atomic_read(&ts->plat_data->shutdown_called)) + schedule_delayed_work(&ts->work_print_info, msecs_to_jiffies(TOUCH_PRINT_INFO_DWORK_TIME)); +} + +#ifdef ENABLE_RAWDATA_SERVICE +void stm_ts_read_rawdata_address(struct stm_ts_data *ts) +{ + u8 reg[8]; + u8 header[16]; + int ret; + int retry = 0; + + if (!ts->raw) { + input_info(true, ts->dev, "%s: alloc rawdata buffer\n", __func__); + stm_ts_rawdata_buffer_alloc(ts); + } + + input_info(true, ts->dev, "%s\n", __func__); + + disable_irq(ts->irq); + + reg[0] = 0xA4; + reg[1] = 0x06; + reg[2] = 0x01; + + ret = ts->stm_ts_write(ts, reg, 3, NULL, 0); + sec_delay(50); + do { + memset(header, 0x00, 16); + reg[0] = 0xA7; + reg[1] = 0x00; + reg[2] = 0x00; + ret = ts->stm_ts_read(ts, reg, 3, header, 16); + if (header[0] == 0xA5 && header[1] == 0x1) + break; + sec_delay(30); + } while (retry--); + + reg[0] = 0xA7; + reg[1] = 0x00; + reg[2] = 0x8C; // strength + memset(header, 0x00, 16); + ret = ts->stm_ts_read(ts, reg, 3, header, 2); + + ts->raw_addr_h = header[1]; + ts->raw_addr_l = header[0]; + + if (ts->raw_addr_h == 0 && ts->raw_addr_l == 0) + input_fail_hist(true, ts->dev, "%s: rawdata address is 0\n", __func__); + else + input_info(true, ts->dev, "%s: rawdata address 0x%02X 0x%02X\n", + __func__, ts->raw_addr_h, ts->raw_addr_l); + + reg[0] = 0xA7; + reg[1] = ts->raw_addr_h; + reg[2] = ts->raw_addr_l; + + ret = ts->stm_ts_read(ts, reg, 3, ts->raw_u8, ts->tx_count * ts->rx_count * 2); + enable_irq(ts->irq); +} +#endif + +void stm_ts_read_info_work(struct work_struct *work) +{ + struct stm_ts_data *ts = container_of(work, struct stm_ts_data, + work_read_info.work); + int ret; + + stm_ts_set_scanmode(ts, STM_TS_SCAN_MODE_SCAN_OFF); + sec_delay(30); + +#ifdef TCLM_CONCEPT + ret = sec_tclm_check_cal_case(ts->tdata); + input_info(true, ts->dev, "%s: sec_tclm_check_cal_case ret: %d \n", __func__, ret); +#endif + ret = stm_ts_get_tsp_test_result(ts); + if (ret < 0) + input_err(true, ts->dev, "%s: failed to get result\n", + __func__); + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s: fac test result %02X\n", + __func__, ts->test_result.data[0]); + stm_ts_set_scanmode(ts, ts->scan_mode); + + stm_ts_run_rawdata_all(ts); + + ts->info_work_done = true; + + /* reinit */ + if (!atomic_read(&ts->reset_is_on_going)) + ts->plat_data->init(ts); + + if (atomic_read(&ts->plat_data->shutdown_called)) { + input_err(true, ts->dev, "%s done, do not run work\n", __func__); + return; + } + + schedule_work(&ts->work_print_info.work); + +} + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) +void stm_ts_interrupt_notify(struct work_struct *work) +{ + struct sec_ts_plat_data *pdata = container_of(work, struct sec_ts_plat_data, + interrupt_notify_work.work); + struct sec_input_notify_data data; + data.dual_policy = MAIN_TOUCHSCREEN; + if (pdata->touch_count > 0) + sec_input_notify(NULL, NOTIFIER_LCD_VRR_LFD_LOCK_REQUEST, &data); + else + sec_input_notify(NULL, NOTIFIER_LCD_VRR_LFD_LOCK_RELEASE, &data); +} +#endif + +void stm_ts_set_cover_type(struct stm_ts_data *ts, bool enable) +{ + int ret; + u8 address; + u8 cover_cmd; + u8 data[2] = { 0 }; + + input_info(true, ts->dev, "%s: %d\n", __func__, ts->plat_data->cover_type); + + cover_cmd = sec_input_check_cover_type(ts->dev) & 0xFF; + + if (enable) { + address = STM_TS_CMD_SET_GET_COVERTYPE; + data[0] = cover_cmd; + ret = ts->stm_ts_write(ts, &address, 1, data, 1); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to send covertype command: %d", + __func__, cover_cmd); + } + + ts->plat_data->touch_functions = ts->plat_data->touch_functions | STM_TS_TOUCHTYPE_BIT_COVER; + + } else { + ts->plat_data->touch_functions = (ts->plat_data->touch_functions & (~STM_TS_TOUCHTYPE_BIT_COVER)); + } + + ret = stm_ts_set_touch_function(ts); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to send touch type command: 0x%02X%02X", + __func__, data[0], data[1]); + } + +} +EXPORT_SYMBOL(stm_ts_set_cover_type); + +int stm_ts_set_temperature(struct device *dev, u8 temperature_data) +{ + struct stm_ts_data *ts = dev_get_drvdata(dev); + u8 address; + + address = STM_TS_CMD_SET_LOWTEMPERATURE_MODE; + + return ts->stm_ts_write(ts, &address, 1, &temperature_data, 1); +} +int stm_ts_set_aod_rect(struct stm_ts_data *ts) +{ + u8 data[10] = {0x02, 0}; + int ret, i; + + for (i = 0; i < 4; i++) { + data[i * 2 + 2] = ts->plat_data->aod_data.rect_data[i] & 0xFF; + data[i * 2 + 3] = (ts->plat_data->aod_data.rect_data[i] >> 8) & 0xFF; + } + + ret = ts->stm_ts_write_sponge(ts, data, 10); + if (ret < 0) + input_err(true, ts->dev, "%s: Failed to write sponge\n", __func__); + + return ret; +} + +int stm_ts_set_press_property(struct stm_ts_data *ts) +{ + u8 data[3] = { SEC_TS_CMD_SPONGE_PRESS_PROPERTY, 0 }; + int ret; + + if (!ts->plat_data->support_fod) + return 0; + + data[2] = ts->plat_data->fod_data.press_prop; + + ret = ts->stm_ts_write_sponge(ts, data, 3); + if (ret < 0) + input_err(true, ts->dev, "%s: Failed to write sponge\n", __func__); + + input_info(true, ts->dev, "%s: %d\n", __func__, ts->plat_data->fod_data.press_prop); + + return ret; +} + +int stm_ts_set_fod_rect(struct stm_ts_data *ts) +{ + u8 data[10] = {0x4b, 0}; + int ret, i; + + input_info(true, ts->dev, "%s: l:%d, t:%d, r:%d, b:%d\n", + __func__, ts->plat_data->fod_data.rect_data[0], ts->plat_data->fod_data.rect_data[1], + ts->plat_data->fod_data.rect_data[2], ts->plat_data->fod_data.rect_data[3]); + + for (i = 0; i < 4; i++) { + data[i * 2 + 2] = ts->plat_data->fod_data.rect_data[i] & 0xFF; + data[i * 2 + 3] = (ts->plat_data->fod_data.rect_data[i] >> 8) & 0xFF; + } + + ret = ts->stm_ts_write_sponge(ts, data, 10); + if (ret < 0) + input_err(true, ts->dev, "%s: Failed to write sponge\n", __func__); + + return ret; +} + +int stm_ts_set_wirelesscharger_mode(struct stm_ts_data *ts) +{ + int ret; + u8 address; + u8 data; + + if (ts->plat_data->wirelesscharger_mode == TYPE_WIRELESS_CHARGER_NONE) { + data = STM_TS_BIT_CHARGER_MODE_NORMAL; + } else if (ts->plat_data->wirelesscharger_mode == TYPE_WIRELESS_CHARGER) { + data = STM_TS_BIT_CHARGER_MODE_WIRELESS_CHARGER; + } else if (ts->plat_data->wirelesscharger_mode == TYPE_WIRELESS_BATTERY_PACK) { + data = STM_TS_BIT_CHARGER_MODE_WIRELESS_BATTERY_PACK; + } else { + input_err(true, ts->dev, "%s: not supported mode %d\n", + __func__, ts->plat_data->wirelesscharger_mode); + return SEC_ERROR; + } + + address = STM_TS_CMD_SET_GET_WIRELESSCHARGER_MODE; + ret = ts->stm_ts_write(ts, &address, 1, &data, 1); + if (ret < 0) + input_err(true, ts->dev, + "%s: Failed to write mode 0x%02X (cmd:%d), ret=%d\n", + __func__, address, ts->plat_data->wirelesscharger_mode, ret); + else + input_info(true, ts->dev, "%s: %sabled, mode=%d\n", __func__, + ts->plat_data->wirelesscharger_mode == TYPE_WIRELESS_CHARGER_NONE ? "dis" : "en", + ts->plat_data->wirelesscharger_mode); + + return ret; +} + +int stm_ts_set_wirecharger_mode(struct stm_ts_data *ts) +{ + int ret; + u8 address; + u8 data; + + if (ts->charger_mode == TYPE_WIRE_CHARGER_NONE) { + data = STM_TS_BIT_CHARGER_MODE_NORMAL; + } else if (ts->charger_mode == TYPE_WIRE_CHARGER) { + data = STM_TS_BIT_CHARGER_MODE_WIRE_CHARGER; + } else { + input_err(true, ts->dev, "%s: not supported mode %d\n", + __func__, ts->charger_mode); + return SEC_ERROR; + } + + address = STM_TS_CMD_SET_GET_WIRECHARGER_MODE; + ret = ts->stm_ts_write(ts, &address, 1, &data, 1); + if (ret < 0) + input_err(true, ts->dev, + "%s: Failed to write mode 0x%02X (cmd:%d), ret=%d\n", + __func__, address, ts->charger_mode, ret); + + return ret; +} + +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) +int stm_ts_vbus_notification(struct notifier_block *nb, unsigned long cmd, void *data) +{ + struct stm_ts_data *ts = container_of(nb, struct stm_ts_data, vbus_nb); + vbus_status_t vbus_type = *(vbus_status_t *)data; + + if (atomic_read(&ts->plat_data->shutdown_called)) + return 0; + + switch (vbus_type) { + case STATUS_VBUS_HIGH: + ts->charger_mode = TYPE_WIRE_CHARGER; + break; + case STATUS_VBUS_LOW: + ts->charger_mode = TYPE_WIRE_CHARGER_NONE; + break; + default: + goto out; + } + + input_info(true, ts->dev, "%s: %sabled\n", __func__, + ts->charger_mode == TYPE_WIRE_CHARGER_NONE ? "dis" : "en"); + + stm_ts_set_wirecharger_mode(ts); + +out: + return 0; +} +#endif + +/* + * flag 1 : set edge handler + * 2 : set (portrait, normal) edge zone data + * 4 : set (portrait, normal) dead zone data + * 8 : set landscape mode data + * 16 : mode clear + * data + * 0xAA, FFF (y start), FFF (y end), FF(direction) + * 0xAB, FFFF (edge zone) + * 0xAC, FF (up x), FF (down x), FFFF (up y), FF (bottom x), FFFF (down y) + * 0xAD, FF (mode), FFF (edge), FFF (dead zone x), FF (dead zone top y), FF (dead zone bottom y) + * case + * edge handler set : 0xAA.... + * booting time : 0xAA... + 0xAB... + * normal mode : 0xAC... (+0xAB...) + * landscape mode : 0xAD... + * landscape -> normal (if same with old data) : 0xAD, 0 + * landscape -> normal (etc) : 0xAC.... + 0xAD, 0 + */ + +void stm_set_grip_data_to_ic(struct device *dev, u8 flag) +{ + struct stm_ts_data *ts = dev_get_drvdata(dev); + + u8 data[9] = { 0 }; + u8 address[2] = {STM_TS_CMD_SET_FUNCTION_ONOFF,}; + + input_info(true, ts->dev, "%s: flag: %02X (clr,lan,nor,edg,han)\n", __func__, flag); + + if (flag & G_SET_EDGE_HANDLER) { + if (ts->plat_data->grip_data.edgehandler_direction == 0) { + data[0] = 0x0; + data[1] = 0x0; + data[2] = 0x0; + data[3] = 0x0; + } else { + data[0] = (ts->plat_data->grip_data.edgehandler_start_y >> 4) & 0xFF; + data[1] = (ts->plat_data->grip_data.edgehandler_start_y << 4 & 0xF0) + | ((ts->plat_data->grip_data.edgehandler_end_y >> 8) & 0xF); + data[2] = ts->plat_data->grip_data.edgehandler_end_y & 0xFF; + data[3] = ts->plat_data->grip_data.edgehandler_direction & 0xF; + } + address[1] = STM_TS_FUNCTION_EDGE_HANDLER; + ts->stm_ts_write(ts, address, 2, data, 4); + input_info(true, ts->dev, "%s: 0x%02X %02X,%02X,%02X,%02X\n", + __func__, STM_TS_FUNCTION_EDGE_HANDLER, data[0], data[1], data[2], data[3]); + } + + if (flag & G_SET_EDGE_ZONE) { + data[0] = (ts->plat_data->grip_data.edge_range >> 8) & 0xFF; + data[1] = ts->plat_data->grip_data.edge_range & 0xFF; + data[2] = (ts->plat_data->grip_data.edge_range >> 8) & 0xFF; + data[3] = ts->plat_data->grip_data.edge_range & 0xFF; + address[1] = STM_TS_FUNCTION_EDGE_AREA; + ts->stm_ts_write(ts, address, 2, data, 4); + input_info(true, ts->dev, "%s: 0x%02X %02X,%02X,%02X,%02X\n", + __func__, STM_TS_FUNCTION_EDGE_AREA, data[0], data[1], data[2], data[3]); + } + + if (flag & G_SET_NORMAL_MODE) { + data[0] = ts->plat_data->grip_data.deadzone_up_x & 0xFF; + data[1] = ts->plat_data->grip_data.deadzone_dn_x & 0xFF; + data[2] = (ts->plat_data->grip_data.deadzone_y >> 8) & 0xFF; + data[3] = ts->plat_data->grip_data.deadzone_y & 0xFF; + data[4] = ts->plat_data->grip_data.deadzone_dn2_x & 0xFF; + data[5] = (ts->plat_data->grip_data.deadzone_dn_y >> 8) & 0xFF; + data[6] = ts->plat_data->grip_data.deadzone_dn_y & 0xFF; + + address[1] = STM_TS_FUNCTION_DEAD_ZONE; + if (ts->support_grip_cmd_v2) { + ts->stm_ts_write(ts, address, 2, data, 4); + input_info(true, ts->dev, "%s: 0x%02X %02X,%02X,%02X,%02X\n", + __func__, STM_TS_FUNCTION_DEAD_ZONE, data[0], data[1], data[2], data[3]); + } else { + ts->stm_ts_write(ts, address, 2, data, 7); + input_info(true, ts->dev, "%s: 0x%02X %02X,%02X,%02X,%02X,%02X,%02X,%02X\n", + __func__, STM_TS_FUNCTION_DEAD_ZONE, data[0], data[1], data[2], data[3], data[4], data[5], data[6]); + } + } + + if (flag & G_SET_LANDSCAPE_MODE) { + data[0] = ts->plat_data->grip_data.landscape_mode; + data[1] = (ts->plat_data->grip_data.landscape_edge >> 8) & 0xFF; + data[2] = ts->plat_data->grip_data.landscape_edge & 0xFF; + data[3] = (ts->plat_data->grip_data.landscape_edge >> 8) & 0xFF; + data[4] = ts->plat_data->grip_data.landscape_edge & 0xFF; + data[5] = (ts->plat_data->grip_data.landscape_deadzone >> 8) & 0xFF; + data[6] = ts->plat_data->grip_data.landscape_deadzone & 0xFF; + + address[1] = STM_TS_FUNCTION_LANDSCAPE_MODE; + ts->stm_ts_write(ts, address, 2, data, 7); + input_info(true, ts->dev, "%s: 0x%02X %02X,%02X,%02X,%02X, %02X,%02X,%02X\n", + __func__, STM_TS_FUNCTION_LANDSCAPE_MODE, data[0], data[1], data[2], data[3], data[4], data[5], data[6]); + + + + data[0] = ts->plat_data->grip_data.landscape_mode; + data[1] = (ts->plat_data->grip_data.landscape_top_gripzone >> 8)& 0xFF; + data[2] = ts->plat_data->grip_data.landscape_top_gripzone & 0xFF; + data[3] = (ts->plat_data->grip_data.landscape_bottom_gripzone >> 8)& 0xFF; + data[4] = ts->plat_data->grip_data.landscape_bottom_gripzone & 0xFF; + data[5] = (ts->plat_data->grip_data.landscape_top_deadzone >> 8)& 0xFF; + data[6] = ts->plat_data->grip_data.landscape_top_deadzone & 0xFF; + data[7] = (ts->plat_data->grip_data.landscape_bottom_deadzone >> 8)& 0xFF; + data[8] = ts->plat_data->grip_data.landscape_bottom_deadzone & 0xFF; + + address[1] = STM_TS_FUNCTION_LANDSCAPE_TOP_BOTTOM; + ts->stm_ts_write(ts, address, 2, data, 9); + input_info(true, ts->dev, "%s: 0x%02X %02X,%02X,%02X,%02X, %02X,%02X,%02X,%02X,%02X\n", + __func__, STM_TS_FUNCTION_LANDSCAPE_TOP_BOTTOM, data[0], data[1], data[2], data[3], + data[4], data[5], data[6], data[7], data[8]); + } + + if (flag & G_CLR_LANDSCAPE_MODE) { + memset(data, 0x00, 9); + data[0] = ts->plat_data->grip_data.landscape_mode; + address[1] = STM_TS_FUNCTION_LANDSCAPE_MODE; + ts->stm_ts_write(ts, address, 2, data, 7); + input_info(true, ts->dev, "%s: 0x%02X %02X\n", + __func__, STM_TS_FUNCTION_LANDSCAPE_MODE, data[0]); + + address[1] = STM_TS_FUNCTION_LANDSCAPE_TOP_BOTTOM; + ts->stm_ts_write(ts, address, 2, data, 9); + input_info(true, ts->dev, "%s: 0x%02X %02X\n", + __func__, STM_TS_FUNCTION_LANDSCAPE_TOP_BOTTOM, data[0]); + } +} + +/* + * Enable or disable external_noise_mode + * + * If mode has EXT_NOISE_MODE_MAX, + * then write enable cmd for all enabled mode. (set as ts->plat_data->external_noise_mode bit value) + * This routine need after IC power reset. TSP IC need to be re-wrote all enabled modes. + * + * Else if mode has specific value like EXT_NOISE_MODE_MONITOR, + * then write enable/disable cmd about for that mode's latest setting value. + * + * If you want to add new mode, + * please define new enum value like EXT_NOISE_MODE_MONITOR, + * then set cmd for that mode like below. (it is in this function) + * noise_mode_cmd[EXT_NOISE_MODE_MONITOR] = stm_TS_CMD_SET_MONITOR_NOISE_MODE; + */ +int stm_ts_set_external_noise_mode(struct stm_ts_data *ts, u8 mode) +{ + int i, ret, fail_count = 0; + u8 mode_bit_to_set, check_bit, mode_enable; + u8 noise_mode_cmd[EXT_NOISE_MODE_MAX] = { 0 }; + u8 address = STM_TS_CMD_SET_FUNCTION_ONOFF; + u8 data[2] = { 0 }; + + if (atomic_read(&ts->plat_data->power_state) == SEC_INPUT_STATE_POWER_OFF) { + input_err(true, ts->dev, "%s: Touch is stopped!\n", __func__); + return -ENODEV; + } + + if (mode == EXT_NOISE_MODE_MAX) { + /* write all enabled mode */ + mode_bit_to_set = ts->plat_data->external_noise_mode; + } else { + /* make enable or disable the specific mode */ + mode_bit_to_set = 1 << mode; + } + + input_info(true, ts->dev, "%s: %sable %d\n", __func__, + ts->plat_data->external_noise_mode & mode_bit_to_set ? "en" : "dis", mode_bit_to_set); + + /* set cmd for each mode */ + noise_mode_cmd[EXT_NOISE_MODE_MONITOR] = STM_TS_FUNCTION_SET_MONITOR_NOISE_MODE; + + /* write mode */ + for (i = EXT_NOISE_MODE_NONE + 1; i < EXT_NOISE_MODE_MAX; i++) { + check_bit = 1 << i; + if (mode_bit_to_set & check_bit) { + mode_enable = !!(ts->plat_data->external_noise_mode & check_bit); + data[0] = noise_mode_cmd[i]; + data[1] = mode_enable; + ret = ts->stm_ts_write(ts, &address, 1, data, 2); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to set 0x%02X %d\n", + __func__, noise_mode_cmd[i], mode_enable); + fail_count++; + } + } + } + + if (fail_count != 0) + return -EIO; + else + return 0; +} + +int stm_ts_set_touchable_area(struct stm_ts_data *ts) +{ + int ret; + u8 address[2] = {STM_TS_CMD_SET_FUNCTION_ONOFF, STM_TS_FUNCTION_SET_TOUCHABLE_AREA}; + input_info(true, ts->dev, + "%s: set 16:9 mode %s\n", __func__, + ts->plat_data->touchable_area ? "enable" : "disable"); + + ret = ts->stm_ts_write(ts, address, 2, &ts->plat_data->touchable_area, 1); + if (ret < 0) + input_err(true, ts->dev, + "%s: failed to set 16:9 mode, ret=%d\n", __func__, ret); + return ret; +} + +int stm_ts_ear_detect_enable(struct stm_ts_data *ts, u8 enable) +{ + int ret; + u8 address = STM_TS_CMD_SET_EAR_DETECT; + u8 data = enable; + + input_info(true, ts->dev, "%s: enable:%d\n", __func__, enable); + + /* 00: off, 01:Mutual, 10:Self, 11: Mutual+Self */ + ret = ts->stm_ts_write(ts, &address, 1, &data, 1); + if (ret < 0) + input_err(true, ts->dev, + "%s: failed to set ed_enable %d, ret=%d\n", __func__, data, ret); + return ret; +} + +int stm_ts_pocket_mode_enable(struct stm_ts_data *ts, u8 enable) +{ + int ret; + u8 address = STM_TS_CMD_SET_POCKET_MODE; + u8 data = enable; + + input_info(true, ts->dev, "%s: %s\n", + __func__, ts->plat_data->pocket_mode ? "enable" : "disable"); + + ret = ts->stm_ts_write(ts, &address, 1, &data, 1); + if (ret < 0) + input_err(true, ts->dev, + "%s: failed to pocket mode%d, ret=%d\n", __func__, data, ret); + return ret; +} + +int stm_ts_sip_mode_enable(struct stm_ts_data *ts) +{ + int ret; + u8 reg[3] = { 0 }; + + input_info(true, ts->dev, "%s: %s\n", + __func__, ts->sip_mode ? "enable" : "disable"); + + reg[0] = STM_TS_CMD_SET_FUNCTION_ONOFF; + reg[1] = STM_TS_FUNCTION_ENABLE_SIP_MODE; + reg[2] = ts->sip_mode; + ret = ts->stm_ts_write(ts, reg, 3, NULL, 0); + if (ret < 0) + input_err(true, ts->dev, + "%s: failed to sip mode%d, ret=%d\n", __func__, ts->sip_mode, ret); + return ret; +} + +int stm_ts_game_mode_enable(struct stm_ts_data *ts) +{ + int ret; + u8 reg[3] = { 0 }; + + input_info(true, ts->dev, "%s: %s\n", + __func__, ts->game_mode ? "enable" : "disable"); + + reg[0] = STM_TS_CMD_SET_FUNCTION_ONOFF; + reg[1] = STM_TS_CMD_FUNCTION_SET_GAME_MODE; + reg[2] = ts->game_mode; + ret = ts->stm_ts_write(ts, reg, 3, NULL, 0); + if (ret < 0) + input_err(true, ts->dev, + "%s: failed to game mode%d, ret=%d\n", __func__, ts->game_mode, ret); + return ret; +} + +int stm_ts_note_mode_enable(struct stm_ts_data *ts) +{ + int ret; + u8 reg[3] = { 0 }; + + input_info(true, ts->dev, "%s: %s\n", + __func__, ts->note_mode ? "enable" : "disable"); + + reg[0] = STM_TS_CMD_SET_FUNCTION_ONOFF; + reg[1] = STM_TS_CMD_FUNCTION_SET_NOTE_MODE; + reg[2] = ts->note_mode; + ret = ts->stm_ts_write(ts, reg, 3, NULL, 0); + if (ret < 0) + input_err(true, ts->dev, + "%s: failed to note mode%d, ret=%d\n", __func__, ts->note_mode, ret); + return ret; +} + +int stm_ts_dead_zone_enable(struct stm_ts_data *ts) +{ + u8 reg[3] = {STM_TS_CMD_SET_FUNCTION_ONOFF, STM_TS_FUNCTION_ENABLE_DEAD_ZONE, 0x00}; + int ret; + + if (ts->dead_zone == 0) + reg[2] = 0x01; /* dead zone disable */ + else + reg[2] = 0x00; /* dead zone enable */ + + ret = ts->stm_ts_write(ts, reg, 3, NULL, 0); + if (ret < 0) + input_err(true, ts->dev, "%s: failed. ret: %d\n", __func__, ret); + else + input_info(true, ts->dev, "%s: %d, ret: %d\n", __func__, ts->dead_zone, ret); + + return ret; +} + +#define NVM_CMD(mtype, moffset, mlength) .type = mtype, .offset = moffset, .length = mlength +struct stm_ts_nvm_data_map nvm_data[] = { + {NVM_CMD(0, 0x00, 0),}, + {NVM_CMD(STM_TS_NVM_OFFSET_FAC_RESULT, 0x00, 1),}, /* SEC */ + {NVM_CMD(STM_TS_NVM_OFFSET_CAL_COUNT, 0x01, 1),}, /* SEC */ + {NVM_CMD(STM_TS_NVM_OFFSET_DISASSEMBLE_COUNT, 0x02, 1),}, /* SEC */ + {NVM_CMD(STM_TS_NVM_OFFSET_TUNE_VERSION, 0x03, 2),}, /* SEC */ + {NVM_CMD(STM_TS_NVM_OFFSET_CAL_POSITION, 0x05, 1),}, /* SEC */ + {NVM_CMD(STM_TS_NVM_OFFSET_HISTORY_QUEUE_COUNT, 0x06, 1),}, /* SEC */ + {NVM_CMD(STM_TS_NVM_OFFSET_HISTORY_QUEUE_LASTP, 0x07, 1),}, /* SEC */ + {NVM_CMD(STM_TS_NVM_OFFSET_HISTORY_QUEUE_ZERO, 0x08, 20),}, /* SEC */ + {NVM_CMD(STM_TS_NVM_OFFSET_CAL_FAIL_FLAG, 0x1C, 1),}, /* SEC */ + {NVM_CMD(STM_TS_NVM_OFFSET_CAL_FAIL_COUNT, 0x1D, 1),}, /* SEC */ +}; + +int get_nvm_data(struct stm_ts_data *ts, int type, u8 *nvdata) +{ + int size = sizeof(nvm_data) / sizeof(struct stm_ts_nvm_data_map); + + if (type >= size) + return -EINVAL; + + return get_nvm_data_by_size(ts, nvm_data[type].offset, nvm_data[type].length, nvdata); +} + +int set_nvm_data(struct stm_ts_data *ts, u8 type, u8 *buf) +{ + return set_nvm_data_by_size(ts, nvm_data[type].offset, nvm_data[type].length, buf); +} + +int get_nvm_data_by_size(struct stm_ts_data *ts, u8 offset, int length, u8 *nvdata) +{ + u8 address[3] = {0}; + u8 data[128] = { 0 }; + int ret; + + sec_delay(200); + ts->stm_ts_command(ts, STM_TS_CMD_CLEAR_ALL_EVENT, true); // Clear FIFO + + stm_ts_release_all_finger(ts); + + // Request SEC factory debug data from flash + address[0] = 0xA4; + address[1] = 0x06; + address[2] = 0x90; + + ret = stm_ts_wait_for_echo_event(ts, &address[0], 3, 50); + if (ret < 0) { + input_err(true, ts->dev, + "%s: timeout. ret: %d\n", __func__, ret); + return ret; + } + + + address[0] = STM_TS_CMD_FRM_BUFF_R; + address[1] = 0x00; + address[2] = offset; + + ret = ts->stm_ts_read(ts, &address[0], 3, data, length + 1); + if (ret < 0) { + input_err(true, ts->dev, + "%s: read failed. ret: %d\n", __func__, ret); + return ret; + } + + memcpy(nvdata, &data[0], length); + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s: offset [%d], length [%d]\n", + __func__, offset, length); + + return ret; +} + +int set_nvm_data_by_size(struct stm_ts_data *ts, u8 offset, int length, u8 *buf) +{ + u8 buff[256] = { 0 }; + u8 remaining, index, sendinglength; + int ret; + + sec_delay(200); + ts->stm_ts_command(ts, STM_TS_CMD_CLEAR_ALL_EVENT, true); // Clear FIFO + + stm_ts_release_all_finger(ts); + + remaining = length; + index = 0; + sendinglength = 0; + + while (remaining) { + buff[0] = 0xC7; + buff[1] = 0x01; + buff[2] = offset + index; + + // write data up to 12 bytes available + if (remaining < 13) { + memcpy(&buff[3], &buf[index], remaining); + sendinglength = remaining; + } else { + memcpy(&buff[3], &buf[index], 12); + index += 12; + sendinglength = 12; + } + + ret = ts->stm_ts_write(ts, &buff[0], sendinglength + 3, NULL, 0); + if (ret < 0) { + input_err(true, ts->dev, + "%s: write failed. ret: %d\n", __func__, ret); + return ret; + } + remaining -= sendinglength; + } + + // Save to flash + buff[0] = 0xA4; + buff[1] = 0x05; + buff[2] = 0x04; // panel configuration area + + ret = stm_ts_wait_for_echo_event(ts, &buff[0], 3, 200); + if (ret < 0) + input_err(true, ts->dev, + "%s: failed to get echo. ret: %d\n", __func__, ret); + return ret; + +} + +int stm_ts_tclm_execute_force_calibration(struct device *dev, int cal_mode) +{ + struct stm_ts_data *ts = dev_get_drvdata(dev); + + return stm_ts_execute_autotune(ts, true); +} + +int stm_tclm_data_read(struct device *dev, int address) +{ + struct stm_ts_data *ts = dev_get_drvdata(dev); + int ret = 0; + int i = 0; + u8 nbuff[STM_TS_NVM_OFFSET_ALL]; + u16 ic_version; + + switch (address) { + case SEC_TCLM_NVM_OFFSET_IC_FIRMWARE_VER: + ret = stm_ts_get_version_info(ts); + ic_version = (ts->module_version_of_ic << 8) | (ts->fw_main_version_of_ic & 0xFF); + return ic_version; + case SEC_TCLM_NVM_ALL_DATA: + ret = get_nvm_data_by_size(ts, nvm_data[STM_TS_NVM_OFFSET_FAC_RESULT].offset, + STM_TS_NVM_OFFSET_ALL, nbuff); + if (ret < 0) + return ret; + ts->tdata->nvdata.cal_count = nbuff[nvm_data[STM_TS_NVM_OFFSET_CAL_COUNT].offset]; + ts->tdata->nvdata.tune_fix_ver = (nbuff[nvm_data[STM_TS_NVM_OFFSET_TUNE_VERSION].offset] << 8) | + nbuff[nvm_data[STM_TS_NVM_OFFSET_TUNE_VERSION].offset + 1]; + ts->tdata->nvdata.cal_position = nbuff[nvm_data[STM_TS_NVM_OFFSET_CAL_POSITION].offset]; + ts->tdata->nvdata.cal_pos_hist_cnt = nbuff[nvm_data[STM_TS_NVM_OFFSET_HISTORY_QUEUE_COUNT].offset]; + ts->tdata->nvdata.cal_pos_hist_lastp = nbuff[nvm_data[STM_TS_NVM_OFFSET_HISTORY_QUEUE_LASTP].offset]; + for (i = nvm_data[STM_TS_NVM_OFFSET_HISTORY_QUEUE_ZERO].offset; + i < nvm_data[STM_TS_NVM_OFFSET_HISTORY_QUEUE_ZERO].offset + + nvm_data[STM_TS_NVM_OFFSET_HISTORY_QUEUE_ZERO].length; i++) + ts->tdata->nvdata.cal_pos_hist_queue[i - nvm_data[STM_TS_NVM_OFFSET_HISTORY_QUEUE_ZERO].offset] = nbuff[i]; + + ts->tdata->nvdata.cal_fail_falg = nbuff[nvm_data[STM_TS_NVM_OFFSET_CAL_FAIL_FLAG].offset]; + ts->tdata->nvdata.cal_fail_cnt= nbuff[nvm_data[STM_TS_NVM_OFFSET_CAL_FAIL_COUNT].offset]; + ts->fac_nv = nbuff[nvm_data[STM_TS_NVM_OFFSET_FAC_RESULT].offset]; + ts->disassemble_count = nbuff[nvm_data[STM_TS_NVM_OFFSET_DISASSEMBLE_COUNT].offset]; + return ret; + case SEC_TCLM_NVM_TEST: + input_info(true, ts->dev, "%s: dt: tclm_level [%d] afe_base [%04X]\n", + __func__, ts->tdata->tclm_level, ts->tdata->afe_base); + ret = get_nvm_data_by_size(ts, STM_TS_NVM_OFFSET_ALL + SEC_TCLM_NVM_OFFSET, + SEC_TCLM_NVM_OFFSET_LENGTH, ts->tdata->tclm); + if (ts->tdata->tclm[0] != 0xFF) { + ts->tdata->tclm_level = ts->tdata->tclm[0]; + ts->tdata->afe_base = (ts->tdata->tclm[1] << 8) | ts->tdata->tclm[2]; + input_info(true, ts->dev, "%s: nv: tclm_level [%d] afe_base [%04X]\n", + __func__, ts->tdata->tclm_level, ts->tdata->afe_base); + } + return ret; + default: + return ret; + } + +} + +int stm_tclm_data_write(struct device *dev, int address) +{ + struct stm_ts_data *ts = dev_get_drvdata(dev); + int ret = 1; + int i = 0; + u8 nbuff[STM_TS_NVM_OFFSET_ALL]; + + switch (address) { + case SEC_TCLM_NVM_ALL_DATA: + memset(nbuff, 0x00, STM_TS_NVM_OFFSET_ALL); + nbuff[nvm_data[STM_TS_NVM_OFFSET_FAC_RESULT].offset] = ts->fac_nv; + nbuff[nvm_data[STM_TS_NVM_OFFSET_DISASSEMBLE_COUNT].offset] = ts->disassemble_count; + nbuff[nvm_data[STM_TS_NVM_OFFSET_CAL_COUNT].offset] = ts->tdata->nvdata.cal_count; + nbuff[nvm_data[STM_TS_NVM_OFFSET_TUNE_VERSION].offset] = (u8)(ts->tdata->nvdata.tune_fix_ver >> 8); + nbuff[nvm_data[STM_TS_NVM_OFFSET_TUNE_VERSION].offset + 1] = (u8)(0xff & ts->tdata->nvdata.tune_fix_ver); + nbuff[nvm_data[STM_TS_NVM_OFFSET_CAL_POSITION].offset] = ts->tdata->nvdata.cal_position; + nbuff[nvm_data[STM_TS_NVM_OFFSET_HISTORY_QUEUE_COUNT].offset] = ts->tdata->nvdata.cal_pos_hist_cnt; + nbuff[nvm_data[STM_TS_NVM_OFFSET_HISTORY_QUEUE_LASTP].offset] = ts->tdata->nvdata.cal_pos_hist_lastp; + for (i = nvm_data[STM_TS_NVM_OFFSET_HISTORY_QUEUE_ZERO].offset; + i < nvm_data[STM_TS_NVM_OFFSET_HISTORY_QUEUE_ZERO].offset + + nvm_data[STM_TS_NVM_OFFSET_HISTORY_QUEUE_ZERO].length; i++) + nbuff[i] = ts->tdata->nvdata.cal_pos_hist_queue[i - nvm_data[STM_TS_NVM_OFFSET_HISTORY_QUEUE_ZERO].offset]; + nbuff[nvm_data[STM_TS_NVM_OFFSET_CAL_FAIL_FLAG].offset] = ts->tdata->nvdata.cal_fail_falg; + nbuff[nvm_data[STM_TS_NVM_OFFSET_CAL_FAIL_COUNT].offset] = ts->tdata->nvdata.cal_fail_cnt; + ret = set_nvm_data_by_size(ts, nvm_data[STM_TS_NVM_OFFSET_FAC_RESULT].offset, STM_TS_NVM_OFFSET_ALL, nbuff); + return ret; + case SEC_TCLM_NVM_TEST: + ret = set_nvm_data_by_size(ts, STM_TS_NVM_OFFSET_ALL + SEC_TCLM_NVM_OFFSET, + SEC_TCLM_NVM_OFFSET_LENGTH, ts->tdata->tclm); + return ret; + default: + return ret; + + } +} + +MODULE_LICENSE("GPL"); diff --git a/drivers/input/sec_input/stm_spi/stm_fw.c b/drivers/input/sec_input/stm_spi/stm_fw.c new file mode 100644 index 000000000000..cc1ec13d3f80 --- /dev/null +++ b/drivers/input/sec_input/stm_spi/stm_fw.c @@ -0,0 +1,956 @@ +#include "stm_dev.h" +#include "stm_reg.h" + +#if IS_ENABLED(CONFIG_SPU_VERIFY) +#define SUPPORT_FW_SIGNED +#endif + +#ifdef SUPPORT_FW_SIGNED +#include +#endif + +#define STM_TS_FILE_SIGNATURE 0xAA55AA55 + +enum { + TSP_BUILT_IN = 0, + TSP_SDCARD, + TSP_SIGNED_SDCARD, + TSP_SPU, + TSP_VERIFICATION, +}; + +/** + * @brief Type definitions - header structure of firmware file (ftb) + */ + +struct stm_ts_header { + u32 signature; + u32 ftb_ver; + u32 target; + u32 fw_id; + u32 fw_ver; + u32 cfg_id; + u32 cfg_ver; + u8 fw_area_bs; // bs : block size + u8 panel_area_bs; + u8 cx_area_bs; + u8 cfg_area_bs; + u32 reserved1; + u32 ext_release_ver; + u8 project_id; + u8 ic_name; + u8 module_ver; + u8 reserved2; + u32 sec0_size; + u32 sec1_size; + u32 sec2_size; + u32 sec3_size; + u32 hdr_crc; +} __packed; + +#define FW_HEADER_SIZE 64 + +#define WRITE_CHUNK_SIZE 1024 +#define DRAM_SIZE (32 * 1024) // 64kB +#define SIGNEDKEY_SIZE (256) + +int stm_ts_check_dma_startanddone(struct stm_ts_data *ts) +{ + int timeout = 60; + u8 reg[6] = { STM_TS_CMD_REG_W, 0x20, 0x00, 0x00, 0x71, 0xC0 }; + u8 val[1]; + int ret; + + ret = ts->stm_ts_write(ts, ®[0], 6, NULL, 0); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to write\n", __func__); + return ret; + } + + sec_delay(10); + + do { + reg[0] = STM_TS_CMD_REG_R; + ret = ts->stm_ts_read(ts, ®[0], 5, (u8 *)val, 1); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to read\n", __func__); + return ret; + } + + if ((val[0] & 0x80) != 0x80) + break; + + sec_delay(50); + timeout--; + } while (timeout != 0); + + if (timeout == 0) { + input_err(true, ts->dev, "%s: Time Over\n", __func__); + return -EIO; + } + + return 0; +} + +static int stm_ts_check_erase_done(struct stm_ts_data *ts) +{ + int timeout = 60; // 3 sec timeout + u8 reg[5] = { STM_TS_CMD_REG_R, 0x20, 0x00, 0x00, 0x6A }; + u8 val[1]; + int ret; + + do { + ret = ts->stm_ts_read(ts, ®[0], 5, (u8 *)val, 1); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to read\n", __func__); + return ret; + } + + if ((val[0] & 0x80) != 0x80) + break; + + sec_delay(50); + timeout--; + } while (timeout != 0); + + if (timeout == 0) { + input_err(true, ts->dev, "%s: Time Over\n", __func__); + return -EIO; + } + + return 0; +} + +int stm_ts_fw_fillflash(struct stm_ts_data *ts, u32 address, u8 *data, int size) +{ + int remaining, index = 0; + int towrite = 0; + int byteblock = 0; + int wheel = 0; + u32 addr = 0; + int ret; + int delta; + + u8 buff[WRITE_CHUNK_SIZE + 5] = {0}; + u8 buff2[12] = {0}; + + remaining = size; + while (remaining > 0) { + byteblock = 0; + addr = 0x00100000; + + while ((byteblock < DRAM_SIZE) && remaining > 0) { + if (remaining >= WRITE_CHUNK_SIZE) { + if ((byteblock + WRITE_CHUNK_SIZE) <= DRAM_SIZE) { + towrite = WRITE_CHUNK_SIZE; + remaining -= WRITE_CHUNK_SIZE; + byteblock += WRITE_CHUNK_SIZE; + } else { + delta = DRAM_SIZE - byteblock; + towrite = delta; + remaining -= delta; + byteblock += delta; + } + } else { + if ((byteblock + remaining) <= DRAM_SIZE) { + towrite = remaining; + byteblock += remaining; + remaining = 0; + } else { + delta = DRAM_SIZE - byteblock; + towrite = delta; + remaining -= delta; + byteblock += delta; + } + } + + index = 0; + buff[index++] = 0xFA; + buff[index++] = (u8) ((addr & 0xFF000000) >> 24); + buff[index++] = (u8) ((addr & 0x00FF0000) >> 16); + buff[index++] = (u8) ((addr & 0x0000FF00) >> 8); + buff[index++] = (u8) ((addr & 0x000000FF)); + + memcpy(&buff[index], data, towrite); + + ret = ts->stm_ts_write(ts, &buff[0], index + towrite, NULL, 0); + if (ret < 0) { + input_err(true, ts->dev, + "%s failed to write i2c register. ret:%d\n", + __func__, ret); + return ret; + } + sec_delay(5); + + addr += towrite; + data += towrite; + } + + input_info(true, ts->dev, "%s: Write %d Bytes\n", __func__, byteblock); + + //configuring the DMA + byteblock = byteblock / 4 - 1; + + index = 0; + buff2[index++] = 0xFA; + buff2[index++] = 0x20; + buff2[index++] = 0x00; + buff2[index++] = 0x00; + buff2[index++] = 0x72; + buff2[index++] = 0x00; + buff2[index++] = 0x00; + + addr = address + ((wheel * DRAM_SIZE) / 4); + buff2[index++] = (u8) ((addr & 0x000000FF)); + buff2[index++] = (u8) ((addr & 0x0000FF00) >> 8); + buff2[index++] = (u8) (byteblock & 0x000000FF); + buff2[index++] = (u8) ((byteblock & 0x0000FF00) >> 8); + buff2[index++] = 0x00; + + ret = ts->stm_ts_write(ts, &buff2[0], index, NULL, 0); + if (ret < 0) { + input_err(true, ts->dev, + "%s failed to write i2c register. ret:%d\n", + __func__, ret); + return ret; + } + sec_delay(10); + + ret = stm_ts_check_dma_startanddone(ts); + if (ret < 0) + return ret; + + wheel++; + } + + return 0; +} + +static int stm_ts_fw_burn(struct stm_ts_data *ts, const u8 *fw_data) +{ + const struct stm_ts_header *fw_header; + u8 *pfwdata; + int ret; + int i; + u8 reg[STM_TS_EVENT_BUFF_SIZE] = {0}; + int numberofmainblock = 0; + int indexofconfigblock = 0; + + fw_header = (struct stm_ts_header *) &fw_data[0]; + + if ((fw_header->fw_area_bs) > 0) + numberofmainblock = fw_header->fw_area_bs; + else + numberofmainblock = 26; // original value + + input_info(true, ts->dev, "%s: Number Of MainBlock: %d\n", + __func__, numberofmainblock); + + indexofconfigblock = fw_header->fw_area_bs + fw_header->panel_area_bs + fw_header->cx_area_bs; + input_info(true, ts->dev, "%s: Index of Config Block: %d\n", + __func__, indexofconfigblock); + + // System Reset and Hold + reg[0] = STM_TS_CMD_REG_W; + reg[1] = 0x20; + reg[2] = 0x00; + reg[3] = 0x00; + reg[4] = 0x24; + reg[5] = 0x01; + ret = ts->stm_ts_write(ts, ®[0], 6, NULL, 0); + if (ret < 0) + return ret; + sec_delay(200); + + ret = stm_ts_wire_mode_change(ts, reg); + if (ret < 0) + return ret; + + // Change application mode + reg[0] = STM_TS_CMD_REG_W; + reg[1] = 0x20; + reg[2] = 0x00; + reg[3] = 0x00; + reg[4] = 0x25; + reg[5] = 0x20; + ret = ts->stm_ts_write(ts, ®[0], 6, NULL, 0); + if (ret < 0) + return ret; + sec_delay(200); + + // Unlock Flash + reg[0] = STM_TS_CMD_REG_W; + reg[1] = 0x20; + reg[2] = 0x00; + reg[3] = 0x00; + reg[4] = 0xDE; + reg[5] = 0x03; + ret = ts->stm_ts_write(ts, ®[0], 6, NULL, 0); + if (ret < 0) + return ret; + sec_delay(200); + + //==================== Erase Partial Flash ==================== + input_info(true, ts->dev, "%s: Start Flash(Main Block) Erasing\n", __func__); + for (i = 0; i < numberofmainblock; i++) { + reg[0] = STM_TS_CMD_REG_W; + reg[1] = 0x20; + reg[2] = 0x00; + reg[3] = 0x00; + reg[4] = 0x6B; + reg[5] = 0x00; + ret = ts->stm_ts_write(ts, ®[0], 6, NULL, 0); + if (ret < 0) + return ret; + sec_delay(50); + + reg[0] = STM_TS_CMD_REG_W; + reg[1] = 0x20; + reg[2] = 0x00; + reg[3] = 0x00; + reg[4] = 0x6A; + reg[5] = (0x80 + i) & 0xFF; + ret = ts->stm_ts_write(ts, ®[0], 6, NULL, 0); + if (ret < 0) + return ret; + ret = stm_ts_check_erase_done(ts); + if (ret < 0) + return ret; + } + + input_info(true, ts->dev, "%s: Start Flash(Config Block) Erasing\n", __func__); + reg[0] = STM_TS_CMD_REG_W; + reg[1] = 0x20; + reg[2] = 0x00; + reg[3] = 0x00; + reg[4] = 0x6B; + reg[5] = 0x00; + ret = ts->stm_ts_write(ts, ®[0], 6, NULL, 0); + if (ret < 0) + return ret; + sec_delay(50); + + reg[0] = STM_TS_CMD_REG_W; + reg[1] = 0x20; + reg[2] = 0x00; + reg[3] = 0x00; + reg[4] = 0x6A; +#if IS_ENABLED(CONFIG_TOUCHSCREEN_STM_SPI) + reg[5] = (0x80 + indexofconfigblock) & 0xFF; +#else + reg[5] = (0x80 + 31) & 0xFF; +#endif + ret = ts->stm_ts_write(ts, ®[0], 6, NULL, 0); + if (ret < 0) + return ret; + + ret = stm_ts_check_erase_done(ts); + if (ret < 0) + return ret; + + // Code Area + if (fw_header->sec0_size > 0) { + pfwdata = (u8 *) &fw_data[FW_HEADER_SIZE]; + + input_info(true, ts->dev, "%s: Start Flashing for Code\n", __func__); + ret = stm_ts_fw_fillflash(ts, CODE_ADDR_START, &pfwdata[0], fw_header->sec0_size); + if (ret < 0) + return ret; + + input_info(true, ts->dev, "%s: Finished total flashing %u Bytes for Code\n", + __func__, fw_header->sec0_size); + } + + // Config Area + if (fw_header->sec1_size > 0) { + input_info(true, ts->dev, "%s: Start Flashing for Config\n", __func__); + pfwdata = (u8 *) &fw_data[FW_HEADER_SIZE + fw_header->sec0_size]; + ret = stm_ts_fw_fillflash(ts, CONFIG_ADDR_START, &pfwdata[0], fw_header->sec1_size); + if (ret < 0) + return ret; + input_info(true, ts->dev, "%s: Finished total flashing %u Bytes for Config\n", + __func__, fw_header->sec1_size); + } + + // CX Area + if (fw_header->sec2_size > 0) { + input_info(true, ts->dev, "%s: Start Flashing for CX\n", __func__); + pfwdata = (u8 *) &fw_data[FW_HEADER_SIZE + fw_header->sec0_size + fw_header->sec1_size]; + ret = stm_ts_fw_fillflash(ts, CX_ADDR_START, &pfwdata[0], fw_header->sec2_size); + if (ret < 0) + return ret; + input_info(true, ts->dev, "%s: Finished total flashing %u Bytes for CX\n", + __func__, fw_header->sec2_size); + } + + reg[0] = STM_TS_CMD_REG_W; + reg[1] = 0x20; + reg[2] = 0x00; + reg[3] = 0x00; + reg[4] = 0x24; + reg[5] = 0x80; + + ret = ts->stm_ts_write(ts, ®[0], 6, NULL, 0); + if (ret < 0) + return ret; + sec_delay(200); + + // System Reset + ts->stm_ts_systemreset(ts, 0); + + return 0; +} + +static void stm_ts_set_factory_history_data(struct stm_ts_data *ts, u8 level) +{ + int ret; + u8 regaddr[3] = { 0 }; + u8 wlevel; + + switch (level) { + case OFFSET_FAC_NOSAVE: + input_info(true, ts->dev, "%s: not save to flash area\n", __func__); + return; + case OFFSET_FAC_SUB: + wlevel = OFFSET_FW_SUB; + break; + case OFFSET_FAC_MAIN: + wlevel = OFFSET_FW_MAIN; + break; + default: + input_info(true, ts->dev, "%s: wrong level %d\n", __func__, level); + return; + } + + regaddr[0] = 0xC7; + regaddr[1] = 0x04; + regaddr[2] = wlevel; + + ret = ts->stm_ts_write(ts, regaddr, 3, NULL, 0); + if (ret < 0) { + input_err(true, ts->dev, + "%s: failed to write factory level %d\n", __func__, wlevel); + return; + } + + regaddr[0] = 0xA4; + regaddr[1] = 0x05; + regaddr[2] = 0x04; /* panel configuration area */ + + ret = stm_ts_wait_for_echo_event(ts, regaddr, 3, 200); + if (ret < 0) + return; + + input_info(true, ts->dev, "%s: save to flash area, level=%d\n", __func__, wlevel); + return; +} + +int stm_ts_execute_autotune(struct stm_ts_data *ts, bool issaving) +{ + u8 reg[STM_TS_EVENT_BUFF_SIZE] = {0,}; + u8 datatype = 0x00; + int ret; + + input_info(true, ts->dev, "%s: start\n", __func__); + + stm_ts_set_scanmode(ts, STM_TS_SCAN_MODE_SCAN_OFF); + ts->stm_ts_command(ts, STM_TS_CMD_CLEAR_ALL_EVENT, true); + + // w A4 00 03 + if (issaving == true) { + // full panel init + reg[0] = 0xA4; + reg[1] = 0x00; + reg[2] = 0x03; + + ret = stm_ts_wait_for_echo_event(ts, ®[0], 3, 500); +#ifdef TCLM_CONCEPT + if (ts->tdata->nvdata.cal_fail_cnt == 0xFF) + ts->tdata->nvdata.cal_fail_cnt = 0; + if (ret < 0) { + ts->tdata->nvdata.cal_fail_cnt++; + ts->tdata->nvdata.cal_fail_falg = 0; + } else { + ts->tdata->nvdata.cal_fail_falg = SEC_CAL_PASS; + ts->is_cal_done = true; + } + stm_tclm_data_write(ts->dev, SEC_TCLM_NVM_ALL_DATA); +#endif + if (ret < 0) { + input_err(true, ts->dev, "%s: timeout\n", __func__); + goto ERROR; + } + } else { + // SS ATune + //DataType = 0x0C; + datatype = 0x3F; + + reg[0] = 0xA4; + reg[1] = 0x03; + reg[2] = (u8)datatype; + reg[3] = 0x00; + + ret = stm_ts_wait_for_echo_event(ts, ®[0], 4, 500); + if (ret < 0) { + input_err(true, ts->dev, "%s: timeout\n", __func__); + goto ERROR; + } + } + + if (issaving == true) { + stm_ts_set_factory_history_data(ts, ts->factory_position); + stm_ts_panel_ito_test(ts, SAVE_MISCAL_REF_RAW); + } + +ERROR: + stm_ts_set_scanmode(ts, ts->scan_mode); + ts->factory_position = OFFSET_FAC_NOSAVE; + + return ret; +} + +static const int stm_ts_fw_updater(struct stm_ts_data *ts, const u8 *fw_data) +{ + const struct stm_ts_header *header; + int retval; + int retry; + u16 fw_main_version; + + if (!fw_data) { + input_err(true, ts->dev, "%s: Firmware data is NULL\n", + __func__); + return -ENODEV; + } + +#if IS_ENABLED(CONFIG_SEC_FACTORY) + if (ts->plat_data->firmware_name) { + if (strnlen(ts->plat_data->firmware_name, SEC_CMD_STR_LEN) >= 21) { + if (!strncmp(ts->plat_data->firmware_name, "tsp_stm/fts5cu56a_r11", 21)) { + if ((ts->fw_main_version_of_ic & 0xFF) == 0x04) { + u8 disassemble_count = 0x00; + + set_nvm_data(ts, STM_TS_NVM_OFFSET_DISASSEMBLE_COUNT, &disassemble_count); + + retval = get_nvm_data(ts, STM_TS_NVM_OFFSET_DISASSEMBLE_COUNT, &disassemble_count); + input_info(true, ts->dev, "%s: retval: %d, disassemble_count: %d\n", __func__, retval, disassemble_count); + } + } + } + } +#endif + + header = (struct stm_ts_header *)fw_data; + fw_main_version = (u16)header->ext_release_ver; + + input_info(true, ts->dev, + "%s: Starting firmware update : 0x%04X\n", __func__, + fw_main_version); + + retry = 0; + + disable_irq(ts->irq); + + while (1) { + retval = stm_ts_fw_burn(ts, fw_data); + if (retval >= 0) { + stm_ts_get_version_info(ts); + + if (fw_main_version == ts->fw_main_version_of_ic) { + input_info(true, ts->dev, + "%s: Success Firmware update\n", + __func__); + ts->fw_corruption = false; + + retval = ts->stm_ts_systemreset(ts, 0); + + if (retval == -STM_TS_ERROR_BROKEN_OSC_TRIM) { + retval = stm_ts_osc_trim_recovery(ts); + if (retval < 0) + input_err(true, ts->dev, "%s: Failed to recover osc trim\n", __func__); + else + ts->fw_corruption = false; + } + + stm_ts_set_scanmode(ts, ts->scan_mode); + retval = 0; + break; + } + } + + if (retry++ > 3) { + input_err(true, ts->dev, "%s: Fail Firmware update\n", + __func__); + retval = -EIO; + break; + } + } + + enable_irq(ts->irq); + + return retval; +} + +int stm_ts_fw_update_on_probe(struct stm_ts_data *ts) +{ + int retval = 0; + const struct firmware *fw_entry = NULL; + char fw_path[STM_TS_MAX_FW_PATH]; + const struct stm_ts_header *header; +#ifdef TCLM_CONCEPT + int ret = 0; + bool restore_cal = false; + int retry = 3; + + if (ts->tdata->support_tclm_test) { + ret = sec_tclm_test_on_probe(ts->tdata); + if (ret < 0) + input_info(true, ts->dev, "%s: SEC_TCLM_NVM_ALL_DATA read fail", __func__); + } +#endif + + if (ts->plat_data->bringup == 1) + return STM_TS_NOT_ERROR; + + if (!ts->plat_data->firmware_name) { + input_err(true, ts->dev, "%s: firmware name does not declair in dts\n", __func__); + retval = -ENOENT; + goto exit_fwload; + } + + snprintf(fw_path, STM_TS_MAX_FW_PATH, "%s", ts->plat_data->firmware_name); + input_info(true, ts->dev, "%s: Load firmware : %s\n", __func__, fw_path); + + while (retry--) { + retval = request_firmware(&fw_entry, fw_path, ts->dev); + if (retval) + input_err(true, ts->dev, + "%s: Firmware image %s not available retry:%d\n", __func__, + fw_path, retry); + else + break; + sec_delay(1000); + } + + if (retval) { + retval = STM_TS_NOT_ERROR; + goto exit_fwload; + } + + header = (struct stm_ts_header *)fw_entry->data; + + ts->fw_version_of_bin = (u16)header->fw_ver; + ts->fw_main_version_of_bin = (u16)header->ext_release_ver; + ts->config_version_of_bin = (u16)header->cfg_ver; + ts->project_id_of_bin = header->project_id; + ts->ic_name_of_bin = header->ic_name; + ts->module_version_of_bin = header->module_ver; + ts->plat_data->img_version_of_bin[2] = ts->module_version_of_bin; + ts->plat_data->img_version_of_bin[3] = ts->fw_main_version_of_bin & 0xFF; + + input_info(true, ts->dev, + "%s: [BIN] Firmware Ver: 0x%04X, Config Ver: 0x%04X, Main Ver: 0x%04X\n", __func__, + ts->fw_version_of_bin, + ts->config_version_of_bin, + ts->fw_main_version_of_bin); + input_info(true, ts->dev, + "%s: [BIN] Project ID: 0x%02X, IC Name: 0x%02X, Module Ver: 0x%02X\n", + __func__, ts->project_id_of_bin, + ts->ic_name_of_bin, ts->module_version_of_bin); + + if (ts->plat_data->bringup == 2) { + input_err(true, ts->dev, "%s: skip fw_update for bringup\n", __func__); + retval = STM_TS_NOT_ERROR; + goto done; + } + + if (ts->plat_data->hw_param.checksum_result) + retval = STM_TS_NEED_FW_UPDATE; + else if ((ts->ic_name_of_ic == 0xFF) && (ts->project_id_of_ic == 0xFF) && (ts->module_version_of_ic == 0xFF)) + retval = STM_TS_NEED_FW_UPDATE; + else if (ts->ic_name_of_ic != ts->ic_name_of_bin) + retval = STM_TS_NOT_UPDATE; + else if (ts->project_id_of_ic != ts->project_id_of_bin) + retval = STM_TS_NEED_FW_UPDATE; + else if (ts->module_version_of_ic != ts->module_version_of_bin) + retval = STM_TS_NOT_UPDATE; + else if ((ts->fw_main_version_of_ic < ts->fw_main_version_of_bin) + || ((ts->config_version_of_ic < ts->config_version_of_bin)) + || ((ts->fw_version_of_ic < ts->fw_version_of_bin))) + retval = STM_TS_NEED_FW_UPDATE; + else + retval = STM_TS_NOT_ERROR; + + if ((ts->plat_data->bringup == 3) && + ((ts->fw_main_version_of_ic != ts->fw_main_version_of_bin) + || (ts->config_version_of_ic != ts->config_version_of_bin) + || (ts->fw_version_of_ic != ts->fw_version_of_bin))) { + input_info(true, ts->dev, + "%s: bringup 3, force update because version is different\n", __func__); + retval = STM_TS_NEED_FW_UPDATE; + } + + /* ic fw ver > bin fw ver && force is false */ + if (retval != STM_TS_NEED_FW_UPDATE) { + input_err(true, ts->dev, "%s: skip fw update\n", __func__); + + goto done; + } + + retval = stm_ts_fw_updater(ts, fw_entry->data); + if (retval < 0) + goto done; + +#ifdef TCLM_CONCEPT + ret = stm_tclm_data_read(ts->dev, SEC_TCLM_NVM_ALL_DATA); + if (ret < 0) { + input_info(true, ts->dev, "%s: SEC_TCLM_NVM_ALL_DATA i2c read fail", __func__); + goto done; + } + + input_info(true, ts->dev, "%s: tune_fix_ver [%04X] afe_base [%04X]\n", + __func__, ts->tdata->nvdata.tune_fix_ver, ts->tdata->afe_base); + + if ((ts->tdata->tclm_level > TCLM_LEVEL_CLEAR_NV) && + ((ts->tdata->nvdata.tune_fix_ver == 0xffff) + || (ts->tdata->afe_base > ts->tdata->nvdata.tune_fix_ver))) { + /* tune version up case */ + sec_tclm_root_of_cal(ts->tdata, CALPOSITION_TUNEUP); + restore_cal = true; + } else if (ts->tdata->tclm_level == TCLM_LEVEL_CLEAR_NV) { + /* firmup case */ + sec_tclm_root_of_cal(ts->tdata, CALPOSITION_FIRMUP); + restore_cal = true; + } + + if (restore_cal) { + input_info(true, ts->dev, "%s: RUN OFFSET CALIBRATION\n", __func__); + if (sec_execute_tclm_package(ts->tdata, 0) < 0) + input_err(true, ts->dev, "%s: sec_execute_tclm_package fail\n", __func__); + } +#endif + +done: +#ifdef TCLM_CONCEPT + sec_tclm_root_of_cal(ts->tdata, CALPOSITION_NONE); +#endif + release_firmware(fw_entry); +exit_fwload: + return retval; +} +EXPORT_SYMBOL(stm_ts_fw_update_on_probe); + +static int stm_ts_load_fw_from_kernel(struct stm_ts_data *ts) +{ + int retval = 0; + const struct firmware *fw_entry = NULL; + char fw_path[STM_TS_MAX_FW_PATH]; + + if (ts->plat_data->bringup == 1) { + retval = -1; + input_info(true, ts->dev, "%s: can't update for bringup:%d\n", + __func__, ts->plat_data->bringup); + return retval; + } + + if (ts->plat_data->firmware_name) + snprintf(fw_path, STM_TS_MAX_FW_PATH, "%s", ts->plat_data->firmware_name); + else + return 0; + + input_info(true, ts->dev, "%s: Load firmware : %s\n", __func__, + fw_path); + + retval = request_firmware(&fw_entry, fw_path, ts->dev); + if (retval) { + input_err(true, ts->dev, + "%s: Firmware image %s not available\n", __func__, + fw_path); + retval = -ENOENT; + goto done; + } + + + ts->stm_ts_systemreset(ts, 20); + +#ifdef TCLM_CONCEPT + sec_tclm_root_of_cal(ts->tdata, CALPOSITION_TESTMODE); +#endif + + retval = stm_ts_fw_updater(ts, fw_entry->data); + if (retval < 0) + input_err(true, ts->dev, "%s: failed update firmware\n", + __func__); + +#ifdef TCLM_CONCEPT + input_info(true, ts->dev, "%s: RUN OFFSET CALIBRATION\n", __func__); + if (sec_execute_tclm_package(ts->tdata, 0) < 0) + input_err(true, ts->dev, "%s: sec_execute_tclm_package fail\n", __func__); + + sec_tclm_root_of_cal(ts->tdata, CALPOSITION_NONE); +#endif + +done: + if (fw_entry) + release_firmware(fw_entry); + return retval; +} + +static int stm_ts_load_fw(struct stm_ts_data *ts, int update_type) +{ + int error = 0; + const struct stm_ts_header *header; + const struct firmware *fw_entry; + char fw_path[SEC_TS_MAX_FW_PATH]; + bool is_fw_signed = false; + + switch (update_type) { + case TSP_SDCARD: +#if !defined(CONFIG_SAMSUNG_PRODUCT_SHIP) + snprintf(fw_path, SEC_TS_MAX_FW_PATH, "%s", TSP_EXTERNAL_FW); +#else + snprintf(fw_path, SEC_TS_MAX_FW_PATH, "%s", TSP_EXTERNAL_FW_SIGNED); + is_fw_signed = true; +#endif + + break; + case TSP_SPU: + case TSP_VERIFICATION: + snprintf(fw_path, SEC_TS_MAX_FW_PATH, "%s", TSP_SPU_FW_SIGNED); + is_fw_signed = true; + break; + default: + return -EINVAL; + } + + error = request_firmware(&fw_entry, fw_path, ts->dev); + if (error) { + input_err(true, ts->dev, "%s: firmware is not available %d\n", __func__, error); + goto err_request_fw; + } + + + + header = (struct stm_ts_header *)fw_entry->data; + if (header->signature != STM_TS_FILE_SIGNATURE) { + error = -EIO; + input_err(true, ts->dev, + "%s: File type is not match with stm_ts64 file. [%8x]\n", + __func__, header->signature); + goto done; + } + + input_info(true, ts->dev, + "%s:%s FirmVer:0x%04X, MainVer:0x%04X," + " IC:0x%02X, Proj:0x%02X, Module:0x%02X\n", + __func__, is_fw_signed ? " [SIGNED]":"", + (u16)header->fw_ver, (u16)header->ext_release_ver, + header->ic_name, header->project_id, header->module_ver); + +#ifdef SUPPORT_FW_SIGNED + if (is_fw_signed) { + long spu_ret = 0, org_size = 0; + if (update_type == TSP_VERIFICATION) { + org_size = fw_entry->size - SPU_METADATA_SIZE(TSP); + spu_ret = spu_firmware_signature_verify("TSP", fw_entry->data, fw_entry->size); + if (spu_ret != org_size) { + input_err(true, ts->dev, "%s: signature verify failed, spu_ret:%ld, org_size:%ld\n", + __func__, spu_ret, org_size); + error = -EPERM; + } + goto done; + } else if (update_type == TSP_SPU && ts->ic_name_of_ic == header->ic_name + && ts->project_id_of_ic == header->project_id + && ts->module_version_of_ic == header->module_ver) { + if ((ts->fw_main_version_of_ic >= header->ext_release_ver) + && (ts->config_version_of_ic >= header->cfg_ver) + && (ts->fw_version_of_ic >= header->fw_ver)) { + input_info(true, ts->dev, "%s: skip spu update\n", __func__); + error = 0; + goto done; + } else { + input_info(true, ts->dev, "%s: run spu update\n", __func__); + } + } else if (update_type == TSP_SDCARD && ts->ic_name_of_ic == header->ic_name) { + input_info(true, ts->dev, "%s: run signed fw update\n", __func__); + } else { + input_err(true, ts->dev, + "%s: not matched product version\n", __func__); + error = -ENOENT; + goto done; + } + + /* digest 32, signature 512, TSP tag 3 */ + org_size = fw_entry->size - SPU_METADATA_SIZE(TSP); + spu_ret = spu_firmware_signature_verify("TSP", fw_entry->data, fw_entry->size); + if (spu_ret != org_size) { + input_err(true, ts->dev, + "%s: signature verify failed, %ld/%ld\n", + __func__, spu_ret, org_size); + error = -EIO; + goto done; + } + } +#endif + ts->stm_ts_systemreset(ts, 0); + +#ifdef TCLM_CONCEPT + sec_tclm_root_of_cal(ts->tdata, CALPOSITION_TESTMODE); +#endif + + error = stm_ts_fw_updater(ts, fw_entry->data); + +#ifdef TCLM_CONCEPT + input_info(true, ts->dev, "%s: RUN OFFSET CALIBRATION\n", __func__); + if (sec_execute_tclm_package(ts->tdata, 0) < 0) + input_err(true, ts->dev, "%s: sec_execute_tclm_package fail\n", __func__); + + sec_tclm_root_of_cal(ts->tdata, CALPOSITION_NONE); +#endif +done: + if (error < 0) + input_err(true, ts->dev, "%s: failed update firmware\n", + __func__); + release_firmware(fw_entry); +err_request_fw: + return error; +} + +int stm_ts_fw_update_on_hidden_menu(struct stm_ts_data *ts, int update_type) +{ + int retval = SEC_ERROR; + + /* Factory cmd for firmware update + * argument represent what is source of firmware like below. + * + * 0 : [BUILT_IN] Getting firmware which is for user. + * 1 : [UMS] Getting firmware from sd card. + * 2 : none + * 3 : [FFU] Getting firmware from apk. + */ + switch (update_type) { + case TSP_BUILT_IN: + retval = stm_ts_load_fw_from_kernel(ts); + break; + + case TSP_SDCARD: + case TSP_SPU: + case TSP_VERIFICATION: + retval = stm_ts_load_fw(ts, update_type); + break; + + default: + input_err(true, ts->dev, "%s: Not support command[%d]\n", + __func__, update_type); + break; + } + + stm_ts_get_custom_library(ts); + stm_ts_set_custom_library(ts); +#ifdef ENABLE_RAWDATA_SERVICE + if (ts->plat_data->support_rawdata) + stm_ts_read_rawdata_address(ts); +#endif + return retval; +} +EXPORT_SYMBOL(stm_ts_fw_update_on_hidden_menu); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/sec_input/stm_spi/stm_i2c.c b/drivers/input/sec_input/stm_spi/stm_i2c.c new file mode 100644 index 000000000000..85d9e788b436 --- /dev/null +++ b/drivers/input/sec_input/stm_spi/stm_i2c.c @@ -0,0 +1,635 @@ +/* drivers/input/sec_input/stm/stm_core.c + * + * Copyright (C) 2020 Samsung Electronics Co., Ltd. + * + * Core file for Samsung TSC driver + * + * 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. + */ + +#include "stm_dev.h" +#include "stm_reg.h" + +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) +int stm_pm_runtime_get_sync(struct stm_ts_data *ts) +{ + struct i2c_client *client; + + client = (struct i2c_client *)ts->client; + + return pm_runtime_get_sync(client->adapter->dev.parent); +} + +void stm_pm_runtime_put_sync(struct stm_ts_data *ts) +{ + struct i2c_client *client; + + client = (struct i2c_client *)ts->client; + pm_runtime_put_sync(client->adapter->dev.parent); +} +#endif + +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) +extern int stui_i2c_lock(struct i2c_adapter *adap); +extern int stui_i2c_unlock(struct i2c_adapter *adap); + +int stm_stui_tsp_enter(void) +{ + struct stm_ts_data *ts = dev_get_drvdata(ptsp); + int ret = 0; + struct i2c_client *client; + + if (!ts) + return -EINVAL; + + client = (struct i2c_client *) ts->client; + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&ts->stm_input_nb, NOTIFIER_SECURE_TOUCH_ENABLE, NULL); +#endif + + disable_irq(ts->irq); + stm_ts_release_all_finger(ts); + + ret = stui_i2c_lock(client->adapter); + if (ret) { + pr_err("[STUI] stui_i2c_lock failed : %d\n", ret); +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&ts->stm_input_nb, NOTIFIER_SECURE_TOUCH_DISABLE, NULL); +#endif + enable_irq(ts->irq); + return -1; + } + + return 0; +} + +int stm_stui_tsp_exit(void) +{ + struct stm_ts_data *ts = dev_get_drvdata(ptsp); + int ret = 0; + struct i2c_client *client; + + if (!ts) + return -EINVAL; + + client = (struct i2c_client *) ts->client; + + ret = stui_i2c_unlock(client->adapter); + if (ret) + pr_err("[STUI] stui_i2c_unlock failed : %d\n", ret); + + enable_irq(ts->irq); + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&ts->stm_input_nb, NOTIFIER_SECURE_TOUCH_DISABLE, NULL); +#endif + + return ret; +} + +int stm_stui_tsp_type(void) +{ + return STUI_TSP_TYPE_STM; +} +#endif + +#if 0 //def TCLM_CONCEPT +int stm_ts_tclm_execute_force_calibration(struct i2c_client *client, int cal_mode) +{ + struct stm_ts_data *ts = (struct stm_ts_data *)i2c_get_clientdata(client); + + return stm_ts_execute_autotune(ts, true); +} + +int stm_tclm_data_read(struct stm_ts_data *ts, int address) +{ + return ts->tdata->tclm_read(ts->tdata->client, address); +} + +int stm_tclm_data_write(struct stm_ts_data *ts, int address) +{ + return ts->tdata->tclm_write(ts->tdata->client, address); +} + +int stm_tclm_i2c_data_read(struct i2c_client *client, int address) +{ + struct stm_ts_data *ts = i2c_get_clientdata(client); + + return _stm_tclm_data_read(ts, address); +} +int stm_tclm_i2c_data_write(struct i2c_client *client, int address) +{ + struct stm_ts_data *ts = i2c_get_clientdata(client); + + return _stm_tclm_data_write(ts, address); +} +#endif + +int stm_ts_wire_mode_change(struct stm_ts_data *ts, u8 *reg) +{ + return 0; +} + +int stm_ts_tool_proc_init(struct stm_ts_data *ts) +{ + return 0; +} + +int stm_ts_tool_proc_remove(void) +{ + return 0; +} + +struct device *stm_ts_get_client_dev(struct stm_ts_data *ts) +{ + struct i2c_client *client = (struct i2c_client *)ts->client; + + return &client->dev; +} + +int stm_ts_read_from_sponge(struct stm_ts_data *ts, u8 *data, int length) +{ + int ret; + u8 address[3]; + + mutex_lock(&ts->sponge_mutex); + address[0] = STM_TS_CMD_SPONGE_READ_WRITE_CMD; + address[1] = data[1]; + address[2] = data[0]; + ret = ts->stm_ts_read(ts, address, 3, data, length); + if (ret < 0) + input_err(true, ts->dev, "%s: fail to read sponge command\n", __func__); + mutex_unlock(&ts->sponge_mutex); + + return ret; +} + +int stm_ts_write_to_sponge(struct stm_ts_data *ts, u8 *data, int length) +{ + int ret; + u8 address[3]; + + mutex_lock(&ts->sponge_mutex); + address[0] = STM_TS_CMD_SPONGE_READ_WRITE_CMD; + address[1] = data[1]; + address[2] = data[0]; + ret = ts->stm_ts_write(ts, address, 3, &data[2], length - 2); + if (ret < 0) + input_err(true, ts->dev, "%s: Failed to write offset\n", __func__); + + address[0] = STM_TS_CMD_SPONGE_NOTIFY_CMD; + ret = ts->stm_ts_write(ts, address, 3, NULL, 0); + if (ret < 0) + input_err(true, ts->dev, "%s: Failed to send notify\n", __func__); + mutex_unlock(&ts->sponge_mutex); + + return ret; +} + +int stm_ts_i2c_write(struct stm_ts_data *ts, u8 *reg, int cnum, u8 *data, int len) +{ + struct i2c_client *client; + int ret; + unsigned char retry; + struct i2c_msg msg; + int i; + const int len_max = 0xffff; + u8 *buf; + u8 *msg_buff; + + client = (struct i2c_client *)ts->client; + + if (len + 1 > len_max) { + input_err(true, ts->dev, + "%s: The i2c buffer size is exceeded.\n", __func__); + return -ENOMEM; + } + + if (!ts->plat_data->resume_done.done) { + ret = wait_for_completion_interruptible_timeout(&ts->plat_data->resume_done, msecs_to_jiffies(500)); + if (ret <= 0) { + input_err(true, ts->dev, "%s: LPM: pm resume is not handled:%d\n", __func__, ret); + return -EIO; + } + } + +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) + if (STUI_MODE_TOUCH_SEC & stui_get_mode()) + return -EBUSY; +#endif + if (sec_check_secure_trusted_mode_status(ts->plat_data)) + return -EBUSY; + + buf = kzalloc(cnum, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + memcpy(buf, reg, cnum); + + msg_buff = kzalloc(len + cnum, GFP_KERNEL); + if (!msg_buff) { + kfree(buf); + return -ENOMEM; + } + + if (atomic_read(&ts->plat_data->power_state) == SEC_INPUT_STATE_POWER_OFF) { + input_err(true, ts->dev, "%s: POWER_STATUS : OFF\n", __func__); + goto err; + } + + memcpy(msg_buff, buf, cnum); + memcpy(msg_buff + cnum, data, len); + + msg.addr = client->addr; + msg.flags = 0 | I2C_M_DMA_SAFE; + msg.len = len + cnum; + msg.buf = msg_buff; + + mutex_lock(&ts->read_write_mutex); + for (retry = 0; retry < SEC_TS_I2C_RETRY_CNT; retry++) { + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret == 1) + break; + + if (atomic_read(&ts->plat_data->power_state) == SEC_INPUT_STATE_POWER_OFF) { + input_err(true, ts->dev, "%s: POWER_STATUS : OFF, retry:%d\n", __func__, retry); + mutex_unlock(&ts->read_write_mutex); + goto err; + } + + usleep_range(1 * 1000, 1 * 1000); + + if (retry > 1) { + char result[32]; + input_err(true, ts->dev, "%s: I2C retry %d, ret:%d\n", __func__, retry + 1, ret); + ts->plat_data->hw_param.comm_err_count++; + + snprintf(result, sizeof(result), "RESULT=I2C"); + if (ts->probe_done) + sec_cmd_send_event_to_user(&ts->sec, NULL, result); + } + } + + mutex_unlock(&ts->read_write_mutex); + + if (retry == SEC_TS_I2C_RETRY_CNT) { + input_err(true, ts->dev, "%s: I2C write over retry limit\n", __func__); + ret = -EIO; + if (ts->probe_done && !atomic_read(&ts->reset_is_on_going) && !atomic_read(&ts->plat_data->shutdown_called)) + schedule_delayed_work(&ts->reset_work, msecs_to_jiffies(TOUCH_RESET_DWORK_TIME)); + } + + if (ts->debug_flag & SEC_TS_DEBUG_PRINT_WRITE_CMD) { + pr_info("sec_input:i2c_cmd: W: %02X | ", *reg); + for (i = 0; i < len; i++) + pr_cont("%02X ", data[i]); + pr_cont("\n"); + } + + if (ret == 1) { + kfree(msg_buff); + kfree(buf); + return 0; + } +err: + kfree(msg_buff); + kfree(buf); + return -EIO; +} + +int stm_ts_i2c_read(struct stm_ts_data *ts, u8 *reg, int cnum, u8 *data, int len) +{ + int ret; + unsigned char retry; + struct i2c_msg msg[2]; + int remain = len; + int i; + u8 *msg_buff; + u8 *buf; + struct i2c_client *client; + + client = (struct i2c_client *)ts->client; + + if (!ts->plat_data->resume_done.done) { + ret = wait_for_completion_interruptible_timeout(&ts->plat_data->resume_done, msecs_to_jiffies(500)); + if (ret <= 0) { + input_err(true, ts->dev, "%s: LPM: pm resume is not handled:%d\n", __func__, ret); + return -EIO; + } + } + +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) + if (STUI_MODE_TOUCH_SEC & stui_get_mode()) + return -EBUSY; +#endif + if (sec_check_secure_trusted_mode_status(ts->plat_data)) + return -EBUSY; + + buf = kzalloc(cnum, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + memcpy(buf, reg, cnum); + + msg_buff = kzalloc(len, GFP_KERNEL); + if (!msg_buff) { + kfree(buf); + return -ENOMEM; + } + + if (atomic_read(&ts->plat_data->power_state) == SEC_INPUT_STATE_POWER_OFF) { + input_err(true, ts->dev, "%s: POWER_STATUS : OFF\n", __func__); + goto err; + } + + msg[0].addr = client->addr; + msg[0].flags = 0 | I2C_M_DMA_SAFE; + msg[0].len = cnum; + msg[0].buf = buf; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD | I2C_M_DMA_SAFE; + msg[1].buf = msg_buff; + + mutex_lock(&ts->read_write_mutex); + if (len <= ts->plat_data->i2c_burstmax) { + msg[1].len = len; + for (retry = 0; retry < SEC_TS_I2C_RETRY_CNT; retry++) { + ret = i2c_transfer(client->adapter, msg, 2); + if (ret == 2) + break; + usleep_range(1 * 1000, 1 * 1000); + if (atomic_read(&ts->plat_data->power_state) == SEC_INPUT_STATE_POWER_OFF) { + input_err(true, ts->dev, "%s: POWER_STATUS : OFF, retry:%d\n", __func__, retry); + mutex_unlock(&ts->read_write_mutex); + goto err; + } + + if (retry > 1) { + char result[32]; + input_err(true, ts->dev, "%s: I2C retry %d, ret:%d\n", + __func__, retry + 1, ret); + ts->plat_data->hw_param.comm_err_count++; + + snprintf(result, sizeof(result), "RESULT=I2C"); + if (ts->probe_done) + sec_cmd_send_event_to_user(&ts->sec, NULL, result); + } + } + } else { + /* + * I2C read buffer is 256 byte. do not support long buffer over than 256. + * So, try to seperate reading data about 256 bytes. + */ + for (retry = 0; retry < SEC_TS_I2C_RETRY_CNT; retry++) { + ret = i2c_transfer(client->adapter, msg, 1); + if (ret == 1) + break; + usleep_range(1 * 1000, 1 * 1000); + if (atomic_read(&ts->plat_data->power_state) == SEC_INPUT_STATE_POWER_OFF) { + input_err(true, ts->dev, "%s: POWER_STATUS : OFF, retry:%d\n", __func__, retry); + mutex_unlock(&ts->read_write_mutex); + goto err; + } + + if (retry > 1) { + input_err(true, ts->dev, "%s: I2C retry %d, ret:%d\n", + __func__, retry + 1, ret); + ts->plat_data->hw_param.comm_err_count++; + } + } + + do { + if (remain > ts->plat_data->i2c_burstmax) + msg[1].len = ts->plat_data->i2c_burstmax; + else + msg[1].len = remain; + + remain -= ts->plat_data->i2c_burstmax; + + for (retry = 0; retry < SEC_TS_I2C_RETRY_CNT; retry++) { + ret = i2c_transfer(client->adapter, &msg[1], 1); + if (ret == 1) + break; + usleep_range(1 * 1000, 1 * 1000); + if (atomic_read(&ts->plat_data->power_state) == SEC_INPUT_STATE_POWER_OFF) { + input_err(true, ts->dev, "%s: POWER_STATUS : OFF, retry:%d\n", __func__, retry); + mutex_unlock(&ts->read_write_mutex); + goto err; + } + + if (retry > 1) { + input_err(true, ts->dev, "%s: I2C retry %d, ret:%d\n", + __func__, retry + 1, ret); + ts->plat_data->hw_param.comm_err_count++; + } + } + msg[1].buf += msg[1].len; + } while (remain > 0); + } + + mutex_unlock(&ts->read_write_mutex); + + if (retry == SEC_TS_I2C_RETRY_CNT) { + input_err(true, ts->dev, "%s: I2C read over retry limit\n", __func__); + ret = -EIO; + if (ts->probe_done && !atomic_read(&ts->reset_is_on_going) && !atomic_read(&ts->plat_data->shutdown_called)) + schedule_delayed_work(&ts->reset_work, msecs_to_jiffies(TOUCH_RESET_DWORK_TIME)); + } + + memcpy(data, msg_buff, len); + if (ts->debug_flag & SEC_TS_DEBUG_PRINT_READ_CMD) { + pr_info("sec_input:i2c_cmd: R: %02X | ", *reg); + for (i = 0; i < len; i++) + pr_cont("%02X ", data[i]); + pr_cont("\n"); + } + + kfree(buf); + kfree(msg_buff); + return ret; +err: + kfree(buf); + kfree(msg_buff); + return -EIO; +} + +int stm_ts_i2c_init(struct i2c_client *client) +{ + struct stm_ts_data *ts; + struct sec_ts_plat_data *pdata; + struct sec_tclm_data *tdata; + int ret = 0; + + input_info(true, &client->dev, "%s\n", __func__); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + input_err(true, &client->dev, "%s: EIO err!\n", __func__); + return -EIO; + } + + ts = devm_kzalloc(&client->dev, sizeof(struct stm_ts_data), GFP_KERNEL); + if (!ts) { + ret = -ENOMEM; + goto error_allocate_mem; + } + + pdata = devm_kzalloc(&client->dev, + sizeof(struct sec_ts_plat_data), GFP_KERNEL); + if (!pdata) { + ret = -ENOMEM; + goto error_allocate_pdata; + } + + tdata = devm_kzalloc(&client->dev, + sizeof(struct sec_tclm_data), GFP_KERNEL); + if (!tdata) { + ret = -ENOMEM; + goto error_allocate_tdata; + } + client->dev.platform_data = pdata; + + ts->client = client; + ts->plat_data = pdata; + ts->dev = &client->dev; +#if IS_ENABLED(CONFIG_INPUT_SEC_TRUSTED_TOUCH) + ts->plat_data->dev = &client->dev; + ts->plat_data->bus_master = &client->adapter->dev; +#endif + ts->stm_ts_read = stm_ts_i2c_read; + ts->stm_ts_write = stm_ts_i2c_write; + ts->stm_ts_read_sponge = stm_ts_read_from_sponge; + ts->stm_ts_write_sponge = stm_ts_write_to_sponge; + ts->tdata = tdata; +#if 0 //def TCLM_CONCEPT + ts->tdata->client = ts->client; + ts->tdata->tclm_read = stm_tclm_i2c_data_read; + ts->tdata->tclm_write = stm_tclm_i2c_data_write; + ts->tdata->tclm_execute_force_calibration = stm_ts_tclm_execute_force_calibration; +#endif + i2c_set_clientdata(client, ts); + +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) + ts->plat_data->stui_tsp_enter = stm_stui_tsp_enter; + ts->plat_data->stui_tsp_exit = stm_stui_tsp_exit; + ts->plat_data->stui_tsp_type = stm_stui_tsp_type; +#endif + ret = stm_ts_init(ts); + if (ret < 0) { + input_err(true, ts->dev, "%s: fail to init resource\n", __func__); + return ret; + } + ret = 0; + return ret; + +error_allocate_tdata: +error_allocate_pdata: +error_allocate_mem: + return ret; +} + +int stm_ts_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct stm_ts_data *ts; + int ret = 0; + + input_info(true, &client->dev, "%s\n", __func__); + + ret = stm_ts_i2c_init(client); + if (ret < 0) { + input_err(true, &client->dev, "%s: fail to init resource\n", __func__); + return ret; + } + + ts = i2c_get_clientdata(client); + if (!ts->plat_data->work_queue_probe_enabled) + return stm_ts_probe(ts->dev); + + queue_work(ts->plat_data->probe_workqueue, &ts->plat_data->probe_work); + return 0; +} + +int stm_ts_i2c_remove(struct i2c_client *client) +{ + struct stm_ts_data *ts = i2c_get_clientdata(client); + int ret = 0; + + ret = stm_ts_remove(ts); + + return 0; +} + +void stm_ts_i2c_shutdown(struct i2c_client *client) +{ + struct stm_ts_data *ts = i2c_get_clientdata(client); + + stm_ts_shutdown(ts); +} + +#if IS_ENABLED(CONFIG_PM) +static int stm_ts_i2c_pm_suspend(struct device *dev) +{ + struct stm_ts_data *ts = dev_get_drvdata(dev); + + stm_ts_pm_suspend(ts); + + return 0; +} + +static int stm_ts_i2c_pm_resume(struct device *dev) +{ + struct stm_ts_data *ts = dev_get_drvdata(dev); + + stm_ts_pm_resume(ts); + + return 0; +} +#endif + + +static const struct i2c_device_id stm_ts_id[] = { + { STM_TS_I2C_NAME, 0 }, + { }, +}; + +#if IS_ENABLED(CONFIG_PM) +static const struct dev_pm_ops stm_ts_dev_pm_ops = { + .suspend = stm_ts_i2c_pm_suspend, + .resume = stm_ts_i2c_pm_resume, +}; +#endif + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id stm_ts_match_table[] = { + { .compatible = "stm,stm_ts",}, + { }, +}; +#else +#define stm_ts_match_table NULL +#endif + +static struct i2c_driver stm_ts_driver = { + .probe = stm_ts_i2c_probe, + .remove = stm_ts_i2c_remove, + .shutdown = stm_ts_i2c_shutdown, + .id_table = stm_ts_id, + .driver = { + .owner = THIS_MODULE, + .name = STM_TS_I2C_NAME, +#if IS_ENABLED(CONFIG_OF) + .of_match_table = stm_ts_match_table, +#endif +#if IS_ENABLED(CONFIG_PM) + .pm = &stm_ts_dev_pm_ops, +#endif + }, +}; + +module_i2c_driver(stm_ts_driver); + +MODULE_SOFTDEP("pre: acpm-mfd-bus"); +MODULE_DESCRIPTION("stm TouchScreen driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/sec_input/stm_spi/stm_ioctl.c b/drivers/input/sec_input/stm_spi/stm_ioctl.c new file mode 100644 index 000000000000..5547fed5206f --- /dev/null +++ b/drivers/input/sec_input/stm_spi/stm_ioctl.c @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Samsung Electronics Co., Ltd. + * + * 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. + */ + +#include "stm_dev.h" + +#ifdef RAWDATA_IOCTL +#include "stm_reg.h" +#include +#include + +struct tsp_ioctl { + int num; + u8 data[4096]; +}; + +static struct mutex lock; + +#define IOCTL_TSP_MAP_READ _IOR(0, 0, struct tsp_ioctl) +#define IOCTL_TSP_MAP_WRITE _IOW(0, 0, struct tsp_ioctl) +#define IOCTL_TSP_MAP_WRITE_TEST_1 _IOW('T', 1, struct tsp_ioctl) + +static long tsp_ioctl_handler(struct file *file, unsigned int cmd, void __user *p, int compat_mode) +{ + static struct tsp_ioctl t; + u8 *copier; + int total; + + if (!g_ts->raw_pool[0] || !g_ts->raw_pool[1] || !g_ts->raw_pool[2]) { + input_info(true, g_ts->dev, "%s: is not allocated\n", __func__); + return -ENOMEM; + } + + mutex_lock(&lock); + + if (cmd == IOCTL_TSP_MAP_READ) { +#if 0 + input_info(true, g_ts->dev, "%s: [w] %d, [r] %d\n", __func__, + g_ts->raw_write_index, g_ts->raw_read_index); +#endif + mutex_lock(&g_ts->raw_lock); + t.num = g_ts->raw_write_index - g_ts->raw_read_index; + mutex_unlock(&g_ts->raw_lock); + if (t.num == 0) { + if (copy_to_user(p, (void *)&t, sizeof(struct tsp_ioctl))) { + input_err(true, g_ts->dev, "%s: failed to 0 copy_to_user\n", + __func__); + mutex_unlock(&lock); + return -EFAULT; + } else { + mutex_unlock(&lock); + return 0; + } + } else if (t.num < 0) { + mutex_lock(&g_ts->raw_lock); + t.num = RAW_VEC_NUM - g_ts->raw_read_index + g_ts->raw_write_index; + mutex_unlock(&g_ts->raw_lock); + } + + if (t.num > 1) { + mutex_lock(&g_ts->raw_lock); + t.num = 1; + mutex_unlock(&g_ts->raw_lock); + } + + for (total = 0; total < t.num; total++) { + mutex_lock(&g_ts->raw_lock); + copier = g_ts->raw_pool[g_ts->raw_read_index]; + memcpy(&t.data[g_ts->raw_len * total], copier, g_ts->raw_len); + g_ts->raw_read_index++; + if (g_ts->raw_read_index >= 3) + g_ts->raw_read_index = 0; + mutex_unlock(&g_ts->raw_lock); + } + + if (copy_to_user(p, (void *)&t, sizeof(struct tsp_ioctl))) { + input_err(true, g_ts->dev, "%s: failed to copyt_to_user\n", + __func__); + mutex_unlock(&lock); + return -EFAULT; + } + } else if (cmd == IOCTL_TSP_MAP_WRITE_TEST_1) { + if (copy_from_user((void *)&t, p, sizeof(struct tsp_ioctl))) { + input_err(true, g_ts->dev, "%s: failed to copyt_from_user\n", __func__); + mutex_unlock(&lock); + return -EFAULT; + } + input_info(true, g_ts->dev, "%s: TEST_1, %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n", __func__, + t.data[0], t.data[1], t.data[2], t.data[3], t.data[4], t.data[5], + t.data[6], t.data[7], t.data[8], t.data[9], t.data[10], t.data[11]); + } + + mutex_unlock(&lock); + return 0; +} + +static long tsp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + return tsp_ioctl_handler(file, cmd, (void __user *)arg, 0); +} + +static int tsp_open(struct inode *inode, struct file *file) +{ + input_info(true, g_ts->dev, "%s\n", __func__); + return 0; +} + +static int tsp_close(struct inode *inode, struct file *file) +{ + input_info(true, g_ts->dev, "%s\n", __func__); + + g_ts->raw_write_index++; + if (g_ts->raw_write_index >= RAW_VEC_NUM) + g_ts->raw_write_index = 0; + sysfs_notify(&g_ts->sec.fac_dev->kobj, NULL, "raw_irq"); + + return 0; +} + +static const struct file_operations tsp_io_fos = { + .owner = THIS_MODULE, + .unlocked_ioctl = tsp_ioctl, + .open = tsp_open, + .release = tsp_close, +}; + +static struct miscdevice tsp_misc = { + .fops = &tsp_io_fos, + .minor = MISC_DYNAMIC_MINOR, + .name = "tspio", +}; + +MODULE_ALIAS_MISCDEV(MISC_DYNAMIC_MINOR); + +static ssize_t raw_irq_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d,%d", g_ts->raw_write_index, g_ts->raw_read_index); +} + +static DEVICE_ATTR_RO(raw_irq); + +static struct attribute *rawdata_attrs[] = { + &dev_attr_raw_irq.attr, + NULL, +}; + +static struct attribute_group rawdata_attr_group = { + .attrs = rawdata_attrs, +}; + +int stm_ts_rawdata_buffer_alloc(struct stm_ts_data *ts) +{ + ts->raw_pool[0] = vmalloc(ts->raw_len); + if (!ts->raw_pool[0]) + goto alloc_out; + ts->raw_pool[1] = vmalloc(ts->raw_len); + if (!ts->raw_pool[1]) + goto alloc_out; + ts->raw_pool[2] = vmalloc(ts->raw_len); + if (!ts->raw_pool[2]) + goto alloc_out; + + ts->raw_u8 = kzalloc(ts->raw_len, GFP_KERNEL); + if (!ts->raw_u8) + goto alloc_out; + ts->raw = kzalloc(ts->raw_len, GFP_KERNEL); + if (!ts->raw) + goto alloc_out; + + return 0; + +alloc_out: + if (ts->raw_pool[0]) + vfree(ts->raw_pool[0]); + if (ts->raw_pool[1]) + vfree(ts->raw_pool[1]); + if (ts->raw_pool[2]) + vfree(ts->raw_pool[2]); + if (ts->raw_u8) + kfree(ts->raw_u8); + if (ts->raw) + kfree(ts->raw); + ts->raw_pool[0] = ts->raw_pool[1] = ts->raw_pool[2] = NULL; + ts->raw_u8 = NULL; + ts->raw = NULL; + return 0; +} + + +int stm_ts_rawdata_init(struct stm_ts_data *ts) +{ + int ret; + + ts->raw_len = PAGE_SIZE; + ret = sysfs_create_group(&ts->sec.fac_dev->kobj, &rawdata_attr_group); + input_info(true, ts->dev, "%s: sysfs_create_group: ret: %d\n", __func__, ret); + + mutex_init(&lock); + + ret = misc_register(&tsp_misc); + input_info(true, ts->dev, "%s: misc_register: ret: %d\n", __func__, ret); + return 0; +} + +void stm_ts_rawdata_buffer_remove(struct stm_ts_data *ts) +{ + input_info(true, ts->dev, "%s\n", __func__); + if (ts->raw_pool[0]) + vfree(ts->raw_pool[0]); + if (ts->raw_pool[1]) + vfree(ts->raw_pool[1]); + if (ts->raw_pool[2]) + vfree(ts->raw_pool[2]); + if (ts->raw_u8) + kfree(ts->raw_u8); + if (ts->raw) + kfree(ts->raw); + + ts->raw_pool[0] = ts->raw_pool[1] = ts->raw_pool[2] = NULL; + ts->raw_u8 = NULL; + ts->raw = NULL; +} +#endif //#ifdef RAWDATA_MMAP + diff --git a/drivers/input/sec_input/stm_spi/stm_mmap.c b/drivers/input/sec_input/stm_spi/stm_mmap.c new file mode 100644 index 000000000000..215056147f6f --- /dev/null +++ b/drivers/input/sec_input/stm_spi/stm_mmap.c @@ -0,0 +1,660 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Samsung Electronics Co., Ltd. + * + * 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. + */ + +#include "stm_dev.h" + +#ifdef RAWDATA_MMAP +#include "stm_reg.h" +#include + +struct tsp_ioctl { + int num; + u8 data[4096]; +}; + +#define IOCTL_TSP_MAP_READ _IOR(0, 0, struct tsp_ioctl) + +static long tsp_ioctl_handler(struct file *file, unsigned int cmd, void __user *p, int compat_mode) +{ + static struct tsp_ioctl t; + int num; + + if (copy_from_user(&t, p, sizeof(struct tsp_ioctl))) { + input_err(true, &g_ts->client->dev, "%s: failed to copyt_from_user\n", __func__); + return -EFAULT; + } + + if (cmd == IOCTL_TSP_MAP_READ) { +#if 0 + input_info(true, &g_ts->client->dev, "%s: num: %d\n", __func__, t.num); +#endif + num = t.num % 5; + if (num == 0) { + memcpy(&t.data, g_ts->raw_v0, g_ts->raw_len); + if (copy_to_user(p, (void *)&t, sizeof(struct tsp_ioctl))) { + input_err(true, &g_ts->client->dev, "%s: failed to copyt_to_user\n", __func__); + return -EFAULT; + } + } else if (num == 1) { + memcpy(&t.data, g_ts->raw_v1, g_ts->raw_len); + if (copy_to_user(p, (void *)&t, sizeof(struct tsp_ioctl))) { + input_err(true, &g_ts->client->dev, "%s: failed to copyt_to_user\n", __func__); + return -EFAULT; + } + } else if (num == 2) { + memcpy(&t.data, g_ts->raw_v2, g_ts->raw_len); + if (copy_to_user(p, (void *)&t, sizeof(struct tsp_ioctl))) { + input_err(true, &g_ts->client->dev, "%s: failed to copyt_to_user\n", __func__); + return -EFAULT; + } + } else if (num == 3) { + memcpy(&t.data, g_ts->raw_v3, g_ts->raw_len); + if (copy_to_user(p, (void *)&t, sizeof(struct tsp_ioctl))) { + input_err(true, &g_ts->client->dev, "%s: failed to copyt_to_user\n", __func__); + return -EFAULT; + } + } else if (num == 4) { + memcpy(&t.data, g_ts->raw_v4, g_ts->raw_len); + if (copy_to_user(p, (void *)&t, sizeof(struct tsp_ioctl))) { + input_err(true, &g_ts->client->dev, "%s: failed to copyt_to_user\n", __func__); + return -EFAULT; + } + } + } + + return 0; +} + +static long tsp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + return tsp_ioctl_handler(file, cmd, (void __user *)arg, 0); +} + +static int tsp_open(struct inode *inode, struct file *file) +{ + input_info(true, &g_ts->client->dev, "%s\n", __func__); + return 0; +} + +static int tsp_close(struct inode *inode, struct file *file) +{ + input_info(true, &g_ts->client->dev, "%s\n", __func__); + return 0; +} + +static const struct file_operations tsp_io_fos = { + .owner = THIS_MODULE, + .unlocked_ioctl = tsp_ioctl, + .open = tsp_open, + .release = tsp_close, +}; + +static const struct miscdevice tsp_misc = { + .fops = &tsp_io_fos, + .minor = MISC_DYNAMIC_MINOR, + .name = "tspio", +}; + +MODULE_ALIAS_MISCDEV(MISC_DYNAMIC_MINOR); + +void tsp_ioctl_init(void) +{ + int ret; + + ret = misc_register(&tsp_misc); + input_info(true, &g_ts->client->dev, "%s: ret: %d\n", __func__, ret); +} + +/* tsp rawdata : using mmap */ +#define DEV_NAME "tsp_data" +#define DEV_NAME0 "tsp_data0" +#define DEV_NAME1 "tsp_data1" +#define DEV_NAME2 "tsp_data2" +#define DEV_NAME3 "tsp_data3" +#define DEV_NAME4 "tsp_data4" +#define DATA_SIZE (2 * PAGE_SIZE) + +static const unsigned int MINOR_BASE; +static const unsigned int MINOR_NUM0 = 1; +static const unsigned int MINOR_NUM1 = 2; +static const unsigned int MINOR_NUM2 = 3; +static const unsigned int MINOR_NUM3 = 4; +static const unsigned int MINOR_NUM4 = 5; + +static struct cdev *mmapdev_cdev0; +static struct cdev *mmapdev_cdev1; +static struct cdev *mmapdev_cdev2; +static struct cdev *mmapdev_cdev3; +static struct cdev *mmapdev_cdev4; + +static struct class *mmapdev_class; + +static dev_t dev0; +static dev_t dev1; +static dev_t dev2; +static dev_t dev3; +static dev_t dev4; + +/* mmap0 */ +static vm_fault_t mmap0_vm_fault(struct vm_fault *vmf) +{ + struct page *page = NULL; + unsigned long offset = 0; + void *page_ptr = NULL; + + input_info(true, &g_ts->client->dev, "%s\n", __func__); + if (vmf == NULL) + return VM_FAULT_SIGBUS; + + offset = vmf->address - vmf->vma->vm_start; + if (offset >= DATA_SIZE) + return VM_FAULT_SIGBUS; + + page_ptr = g_ts->raw_v0 + offset; + page = vmalloc_to_page(page_ptr); + get_page(page); + vmf->page = page; + return 0; +} + +static const struct vm_operations_struct vma0_ops = { + .fault = mmap0_vm_fault +}; + +static int mmap0_open(struct inode *inode, struct file *filp) +{ + input_info(true, &g_ts->client->dev, "%s\n", __func__); + return 0; +} + +static int mmap0_release(struct inode *inode, struct file *filp) +{ + input_info(true, &g_ts->client->dev, "%s\n", __func__); + return 0; +} + +static int mmap0_remap(struct file *filp, struct vm_area_struct *vma) +{ + input_info(true, &g_ts->client->dev, "%s\n", __func__); + + vma->vm_flags |= VM_IO; + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + vma->vm_ops = &vma0_ops; + return 0; +} + +static const struct file_operations mmap0_fops = { + .open = mmap0_open, + .release = mmap0_release, + .mmap = mmap0_remap, +}; + +/* map1 */ +static vm_fault_t mmap1_vm_fault(struct vm_fault *vmf) +{ + struct page *page = NULL; + unsigned long offset = 0; + void *page_ptr = NULL; + + input_info(true, &g_ts->client->dev, "%s\n", __func__); + if (vmf == NULL) + return VM_FAULT_SIGBUS; + + offset = vmf->address - vmf->vma->vm_start; + if (offset >= DATA_SIZE) + return VM_FAULT_SIGBUS; + + page_ptr = g_ts->raw_v1 + offset; + page = vmalloc_to_page(page_ptr); + get_page(page); + vmf->page = page; + return 0; +} + +static const struct vm_operations_struct vma1_ops = { + .fault = mmap1_vm_fault +}; + +static int mmap1_open(struct inode *inode, struct file *filp) +{ + input_info(true, &g_ts->client->dev, "%s\n", __func__); + return 0; +} + +static int mmap1_release(struct inode *inode, struct file *filp) +{ + input_info(true, &g_ts->client->dev, "%s\n", __func__); + return 0; +} + +static int mmap1_remap(struct file *filp, struct vm_area_struct *vma) +{ + input_info(true, &g_ts->client->dev, "%s\n", __func__); + + vma->vm_flags |= VM_IO; + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + vma->vm_ops = &vma1_ops; + return 0; +} + +static const struct file_operations mmap1_fops = { + .open = mmap1_open, + .release = mmap1_release, + .mmap = mmap1_remap, +}; + +/* map2 */ +static vm_fault_t mmap2_vm_fault(struct vm_fault *vmf) +{ + struct page *page = NULL; + unsigned long offset = 0; + void *page_ptr = NULL; + + input_info(true, &g_ts->client->dev, "%s\n", __func__); + if (vmf == NULL) + return VM_FAULT_SIGBUS; + + offset = vmf->address - vmf->vma->vm_start; + if (offset >= DATA_SIZE) + return VM_FAULT_SIGBUS; + + page_ptr = g_ts->raw_v2 + offset; + page = vmalloc_to_page(page_ptr); + get_page(page); + vmf->page = page; + return 0; +} + +static const struct vm_operations_struct vma2_ops = { + .fault = mmap2_vm_fault +}; + +static int mmap2_open(struct inode *inode, struct file *filp) +{ + input_info(true, &g_ts->client->dev, "%s\n", __func__); + return 0; +} + +static int mmap2_release(struct inode *inode, struct file *filp) +{ + input_info(true, &g_ts->client->dev, "%s\n", __func__); + return 0; +} + +static int mmap2_remap(struct file *filp, struct vm_area_struct *vma) +{ + input_info(true, &g_ts->client->dev, "%s\n", __func__); + + vma->vm_flags |= VM_IO; + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + vma->vm_ops = &vma2_ops; + return 0; +} + +static const struct file_operations mmap2_fops = { + .open = mmap2_open, + .release = mmap2_release, + .mmap = mmap2_remap, +}; + +/* map3 */ +static vm_fault_t mmap3_vm_fault(struct vm_fault *vmf) +{ + struct page *page = NULL; + unsigned long offset = 0; + void *page_ptr = NULL; + + input_info(true, &g_ts->client->dev, "%s\n", __func__); + if (vmf == NULL) + return VM_FAULT_SIGBUS; + + offset = vmf->address - vmf->vma->vm_start; + if (offset >= DATA_SIZE) + return VM_FAULT_SIGBUS; + + page_ptr = g_ts->raw_v3 + offset; + page = vmalloc_to_page(page_ptr); + get_page(page); + vmf->page = page; + return 0; +} + +static const struct vm_operations_struct vma3_ops = { + .fault = mmap3_vm_fault +}; + +static int mmap3_open(struct inode *inode, struct file *filp) +{ + input_info(true, &g_ts->client->dev, "%s\n", __func__); + return 0; +} + +static int mmap3_release(struct inode *inode, struct file *filp) +{ + input_info(true, &g_ts->client->dev, "%s\n", __func__); + return 0; +} + +static int mmap3_remap(struct file *filp, struct vm_area_struct *vma) +{ + input_info(true, &g_ts->client->dev, "%s\n", __func__); + + vma->vm_flags |= VM_IO; + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + vma->vm_ops = &vma3_ops; + return 0; +} + +static const struct file_operations mmap3_fops = { + .open = mmap3_open, + .release = mmap3_release, + .mmap = mmap3_remap, +}; + +/* map4 */ +static vm_fault_t mmap4_vm_fault(struct vm_fault *vmf) +{ + struct page *page = NULL; + unsigned long offset = 0; + void *page_ptr = NULL; + + input_info(true, &g_ts->client->dev, "%s\n", __func__); + if (vmf == NULL) + return VM_FAULT_SIGBUS; + + offset = vmf->address - vmf->vma->vm_start; + if (offset >= DATA_SIZE) + return VM_FAULT_SIGBUS; + + page_ptr = g_ts->raw_v4 + offset; + page = vmalloc_to_page(page_ptr); + get_page(page); + vmf->page = page; + return 0; +} + +static const struct vm_operations_struct vma4_ops = { + .fault = mmap4_vm_fault +}; + +static int mmap4_open(struct inode *inode, struct file *filp) +{ + input_info(true, &g_ts->client->dev, "%s\n", __func__); + return 0; +} + +static int mmap4_release(struct inode *inode, struct file *filp) +{ + input_info(true, &g_ts->client->dev, "%s\n", __func__); + return 0; +} + +static int mmap4_remap(struct file *filp, struct vm_area_struct *vma) +{ + input_info(true, &g_ts->client->dev, "%s\n", __func__); + + vma->vm_flags |= VM_IO; + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + vma->vm_ops = &vma4_ops; + return 0; +} + +static const struct file_operations mmap4_fops = { + .open = mmap4_open, + .release = mmap4_release, + .mmap = mmap4_remap, +}; + +static ssize_t raw_irq_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct stm_ts_data *ts = container_of(sec, struct stm_ts_data, sec); + ssize_t ret; + + mutex_lock(&ts->raw_lock); + ret = snprintf(buf, PAGE_SIZE, "%d,%d", ts->before_irq_count, ts->raw_irq_count); + mutex_unlock(&ts->raw_lock); + + return ret; +} + +static DEVICE_ATTR_RO(raw_irq); + +static struct attribute *rawdata_attrs[] = { + &dev_attr_raw_irq.attr, + NULL, +}; + +static struct attribute_group rawdata_attr_group = { + .attrs = rawdata_attrs, +}; + +int stm_ts_rawdata_buffer_alloc(struct stm_ts_data *ts) +{ + ts->raw_v0 = vmalloc(ts->raw_len); + if (!ts->raw_v0) + goto alloc_out; + ts->raw_v1 = vmalloc(ts->raw_len); + if (!ts->raw_v1) + goto alloc_out; + ts->raw_v2 = vmalloc(ts->raw_len); + if (!ts->raw_v2) + goto alloc_out; + ts->raw_v3 = vmalloc(ts->raw_len); + if (!ts->raw_v3) + goto alloc_out; + ts->raw_v4 = vmalloc(ts->raw_len); + if (!ts->raw_v4) + goto alloc_out; + + memset(ts->raw_v0, 0, ts->raw_len); + memset(ts->raw_v1, 0, ts->raw_len); + memset(ts->raw_v2, 0, ts->raw_len); + memset(ts->raw_v3, 0, ts->raw_len); + memset(ts->raw_v4, 0, ts->raw_len); + + ts->raw_u8 = kzalloc(ts->raw_len, GFP_KERNEL); + if (!ts->raw_u8) + goto alloc_out; + ts->raw = kzalloc(ts->raw_len, GFP_KERNEL); + if (!ts->raw) + goto alloc_out; + + return 0; + +alloc_out: + if (!ts->raw_v0) + vfree(ts->raw_v0); + if (!ts->raw_v1) + vfree(ts->raw_v1); + if (!ts->raw_v2) + vfree(ts->raw_v2); + if (!ts->raw_v3) + vfree(ts->raw_v3); + if (!ts->raw_v4) + vfree(ts->raw_v4); + if (!ts->raw_u8) + kfree(ts->raw_u8); + if (!ts->raw) + kfree(ts->raw); + return 0; +} + +int stm_ts_rawdata_init(struct stm_ts_data *ts) +{ + int alloc_ret = 0, cdev_err = 0; + unsigned int mmapdev_major0; + unsigned int mmapdev_major1; + unsigned int mmapdev_major2; + unsigned int mmapdev_major3; + unsigned int mmapdev_major4; + + input_info(true, ts->dev, "%s: num: %d\n", __func__, ts->plat_data->support_rawdata_map_num); + + ts->raw_len = PAGE_SIZE; + + /* map0 */ + mmapdev_cdev0 = cdev_alloc(); + alloc_ret = alloc_chrdev_region(&dev0, MINOR_BASE, MINOR_NUM0, DEV_NAME0); + if (alloc_ret != 0) { + input_info(true, ts->dev, "%s: alloc_chrdev_region = %d\n", __func__, alloc_ret); + return -1; + } + + mmapdev_major0 = MAJOR(dev0); + dev0 = MKDEV(mmapdev_major0, MINOR_BASE); + + cdev_init(mmapdev_cdev0, &mmap0_fops); + mmapdev_cdev0->owner = THIS_MODULE; + + cdev_err = cdev_add(mmapdev_cdev0, dev0, MINOR_NUM0); + if (cdev_err != 0) { + input_info(true, ts->dev, "%s: cdev_add = %d\n", __func__, cdev_err); + goto OUT2; + } + + /* map1 */ + mmapdev_cdev1 = cdev_alloc(); + alloc_ret = alloc_chrdev_region(&dev1, MINOR_BASE, MINOR_NUM1, DEV_NAME1); + if (alloc_ret != 0) { + input_info(true, ts->dev, "%s: alloc_chrdev_region = %d\n", __func__, alloc_ret); + return -1; + } + + mmapdev_major1 = MAJOR(dev1); + dev1 = MKDEV(mmapdev_major1, MINOR_BASE); + + cdev_init(mmapdev_cdev1, &mmap1_fops); + mmapdev_cdev1->owner = THIS_MODULE; + + cdev_err = cdev_add(mmapdev_cdev1, dev1, MINOR_NUM1); + if (cdev_err != 0) { + input_info(true, ts->dev, "%s: cdev_add = %d\n", __func__, cdev_err); + goto OUT2; + } + + /* map2 */ + mmapdev_cdev2 = cdev_alloc(); + alloc_ret = alloc_chrdev_region(&dev2, MINOR_BASE, MINOR_NUM2, DEV_NAME2); + if (alloc_ret != 0) { + input_info(true, ts->dev, "%s: alloc_chrdev_region = %d\n", __func__, alloc_ret); + return -1; + } + + mmapdev_major2 = MAJOR(dev2); + dev2 = MKDEV(mmapdev_major2, MINOR_BASE); + + cdev_init(mmapdev_cdev2, &mmap2_fops); + mmapdev_cdev2->owner = THIS_MODULE; + + cdev_err = cdev_add(mmapdev_cdev2, dev2, MINOR_NUM2); + if (cdev_err != 0) { + input_info(true, ts->dev, "%s: cdev_add = %d\n", __func__, cdev_err); + goto OUT2; + } + + /* map3 */ + mmapdev_cdev3 = cdev_alloc(); + alloc_ret = alloc_chrdev_region(&dev3, MINOR_BASE, MINOR_NUM3, DEV_NAME3); + if (alloc_ret != 0) { + input_info(true, ts->dev, "%s: alloc_chrdev_region = %d\n", __func__, alloc_ret); + return -1; + } + + mmapdev_major3 = MAJOR(dev3); + dev3 = MKDEV(mmapdev_major3, MINOR_BASE); + + cdev_init(mmapdev_cdev3, &mmap3_fops); + mmapdev_cdev3->owner = THIS_MODULE; + + cdev_err = cdev_add(mmapdev_cdev3, dev3, MINOR_NUM3); + if (cdev_err != 0) { + input_info(true, ts->dev, "%s: cdev_add = %d\n", __func__, cdev_err); + goto OUT2; + } + + /* map4 */ + mmapdev_cdev4 = cdev_alloc(); + alloc_ret = alloc_chrdev_region(&dev4, MINOR_BASE, MINOR_NUM4, DEV_NAME4); + if (alloc_ret != 0) { + input_info(true, ts->dev, "%s: alloc_chrdev_region = %d\n", __func__, alloc_ret); + return -1; + } + + mmapdev_major4 = MAJOR(dev4); + dev4 = MKDEV(mmapdev_major4, MINOR_BASE); + + cdev_init(mmapdev_cdev4, &mmap4_fops); + mmapdev_cdev4->owner = THIS_MODULE; + + cdev_err = cdev_add(mmapdev_cdev4, dev4, MINOR_NUM4); + if (cdev_err != 0) { + input_info(true, ts->dev, "%s: cdev_add = %d\n", __func__, cdev_err); + goto OUT2; + } + + /* class create */ + mmapdev_class = class_create(THIS_MODULE, "mmap_device"); + if (IS_ERR(mmapdev_class)) { + input_info(true, ts->dev, "%s: class_create\n", __func__); + goto OUT; + } + + sysfs_create_group(&ts->sec.fac_dev->kobj, &rawdata_attr_group); + + device_create(mmapdev_class, NULL, MKDEV(mmapdev_major0, MINOR_BASE), NULL, DEV_NAME0); + device_create(mmapdev_class, NULL, MKDEV(mmapdev_major1, MINOR_BASE), NULL, DEV_NAME1); + device_create(mmapdev_class, NULL, MKDEV(mmapdev_major2, MINOR_BASE), NULL, DEV_NAME2); + device_create(mmapdev_class, NULL, MKDEV(mmapdev_major3, MINOR_BASE), NULL, DEV_NAME3); + device_create(mmapdev_class, NULL, MKDEV(mmapdev_major4, MINOR_BASE), NULL, DEV_NAME4); + + + input_info(true, ts->dev, "%s: done", __func__); + + tsp_ioctl_init(); + return 0; + +OUT: + cdev_del(mmapdev_cdev4); + cdev_del(mmapdev_cdev3); + cdev_del(mmapdev_cdev2); + cdev_del(mmapdev_cdev1); + cdev_del(mmapdev_cdev0); +OUT2: + unregister_chrdev_region(dev3, MINOR_NUM4); + unregister_chrdev_region(dev2, MINOR_NUM3); + unregister_chrdev_region(dev2, MINOR_NUM2); + unregister_chrdev_region(dev1, MINOR_NUM1); + unregister_chrdev_region(dev0, MINOR_NUM0); + return -1; +} + +void stm_ts_rawdata_buffer_remove(struct stm_ts_data *ts) +{ + input_info(true, ts->dev, "%s\n", __func__); + kfree(ts->raw); + kfree(ts->raw_u8); + + ts->raw = NULL; + ts->raw_u8 = NULL; + + cdev_del(mmapdev_cdev4); + cdev_del(mmapdev_cdev3); + cdev_del(mmapdev_cdev2); + cdev_del(mmapdev_cdev1); + cdev_del(mmapdev_cdev0); + + unregister_chrdev_region(dev3, MINOR_NUM4); + unregister_chrdev_region(dev2, MINOR_NUM3); + unregister_chrdev_region(dev2, MINOR_NUM2); + unregister_chrdev_region(dev1, MINOR_NUM1); + unregister_chrdev_region(dev0, MINOR_NUM0); +} +#endif //#ifdef RAWDATA_MMAP diff --git a/drivers/input/sec_input/stm_spi/stm_reg.h b/drivers/input/sec_input/stm_spi/stm_reg.h new file mode 100644 index 000000000000..74fbb1a8e8d9 --- /dev/null +++ b/drivers/input/sec_input/stm_spi/stm_reg.h @@ -0,0 +1,359 @@ +/* drivers/input/sec_input/stm/stm_reg.h + * + * Copyright (C) 2020 Samsung Electronics Co., Ltd. + * + * Core file for Samsung TSC driver + * + * 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. + */ + +#define USE_POR_AFTER_I2C_RETRY + +#define BRUSH_Z_DATA 63 /* for ArtCanvas */ + +#undef USE_OPEN_DWORK + +#ifdef USE_OPEN_DWORK +#define TOUCH_OPEN_DWORK_TIME 10 +#endif +#define TOUCH_PRINT_INFO_DWORK_TIME 30000 /* 30s */ + +#define TOUCH_PRINT_INFO_DWORK_TIME 30000 /* 30s */ +#define TOUCH_RESET_DWORK_TIME 10 +#define TOUCH_POWER_ON_DWORK_TIME 35 + +#define STM_TS_MAX_FW_PATH 64 +/* + * STM_TS1b: ID0: 0x39 + * STM_TS9c: ID0: 0x50 + * STM_TS5c: ID0: 0x48 + */ +#define STM_TS_ID0 0x48 +#define STM_TS_ID1 0x36 + +#define MAX_EVENT_COUNT 31 +#define STM_TS_EVENT_BUFF_SIZE 16 +#define STM_TS_VERSION_SIZE 9 +#define STM_TS_EVENT_BUFF_MAX_SIZE (MAX_EVENT_COUNT * STM_TS_EVENT_BUFF_SIZE) + +#define PRESSURE_MIN 0 +#define PRESSURE_MAX 127 +#define FINGER_MAX 10 +#define AREA_MIN PRESSURE_MIN +#define AREA_MAX PRESSURE_MAX + +#define INT_ENABLE 1 +#define INT_DISABLE 0 + +#define STM_TS_CMD_SPONGE_ACCESS 0x0000 + +/* COMMANDS */ +#define STM_TS_CMD_SENSE_ON 0x10 +#define STM_TS_CMD_SENSE_OFF 0x11 +#define STM_TS_CMD_SW_RESET 0x12 +#define STM_TS_CMD_FORCE_CALIBRATION 0x13 +#define STM_TS_CMD_FACTORY_PANELCALIBRATION 0x14 + +#define STM_TS_READ_GPIO_STATUS 0x20 +#define STM_TS_READ_FIRMWARE_INTEGRITY 0x21 +#define STM_TS_READ_DEVICE_ID 0x22 +#define STM_TS_READ_PANEL_INFO 0x23 +#define STM_TS_READ_FW_VERSION 0x24 + +#define STM_TS_CMD_SET_GET_TOUCHTYPE 0x30 +#define STM_TS_CMD_SET_GET_OPMODE 0x31 +#define STM_TS_CMD_SET_GET_WIRELESSCHARGER_MODE 0x32 +#define STM_TS_CMD_SET_GET_NOISE_MODE 0x33 +#define STM_TS_CMD_SET_GET_REPORT_RATE 0x34 +#define STM_TS_CMD_SET_GET_TOUCH_MODE_FOR_THRESHOLD 0x35 +#define STM_TS_CMD_SET_GET_TOUCH_THRESHOLD 0x36 +#define STM_TS_CMD_SET_GET_KEY_THRESHOLD 0x37 +#define STM_TS_CMD_SET_GET_COVERTYPE 0x38 +#define STM_TS_CMD_WRITE_WAKEUP_GESTURE 0x39 +#define STM_TS_CMD_WRITE_COORDINATE_FILTER 0x3A +#define STM_TS_CMD_SET_FOD_FINGER_MERGE 0x3B +#define STM_TS_CMD_SET_LOWTEMPERATURE_MODE 0x3C +#define STM_TS_CMD_SET_FOD_INT_CONTROL 0x3D +#define STM_TS_CMD_SET_GET_WIRECHARGER_MODE 0x3F + +#define STM_TS_CMD_FUNCTION_SET_LOW_SENSITIVITY_MODE 0x40 +#define STM_TS_CMD_SET_EAR_DETECT 0x41 +#define STM_TS_CMD_SET_POCKET_MODE 0x42 + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_STM_SPI) +#define STM_TS_READ_ONE_EVENT 0x87 +#define STM_TS_READ_ALL_EVENT 0x87 +#else +#define STM_TS_READ_ONE_EVENT 0x60 +#define STM_TS_READ_ALL_EVENT 0x61 +#endif + +#define STM_TS_CMD_CLEAR_ALL_EVENT 0x62 + +#define STM_TS_CMD_SENSITIVITY_MODE 0x70 +#define STM_TS_READ_SENSITIVITY_VALUE 0x72 +#define STM_TS_CMD_RUN_POLARITY_TEST 0x77 +#define STM_TS_CMD_RUN_HF_SENSOR_DIFF_TEST 0x79 +#define STM_TS_CMD_RUN_SRAM_TEST 0x78 + +#define STM_TS_CMD_SET_LPM_AOD_OFF_ON 0x9B + +#define STM_TS_CMD_LPM_ASYNC_SCAN 0x00 +#define STM_TS_CMD_LPM_SYNC_SCAN 0x01 +#define STM_TS_CMD_NPM_SYNC_SCAN 0x01 + +#define STM_TS_CMD_SET_FUNCTION_ONOFF 0xC1 +#define STM_TS_CMD_FUNCTION_SET_NOTE_MODE 0x10 +#define STM_TS_CMD_FUNCTION_SET_GAME_MODE 0x11 +#define STM_TS_CMD_FUNCTION_SET_HOVER_DETECTION 0x12 +#define STM_TS_CMD_FUNCTION_SET_PEN_SAVING_MODE 0x13 +#define STM_TS_CMD_FUNCTION_SET_PEN_DETECTION 0x14 +#define STM_TS_CMD_FUNCTION_SET_TSP_BLOCK 0x15 + +/* STM_TS SPONGE COMMAND */ +#define STM_TS_CMD_SPONGE_READ_WRITE_CMD 0xAA +#define STM_TS_CMD_SPONGE_NOTIFY_CMD 0xC0 + +#define STM_TS_CMD_SPONGE_OFFSET_MODE 0x00 +#define STM_TS_CMD_SPONGE_OFFSET_MODE_01 0x01 +#define STM_TS_CMD_SPONGE_OFFSET_AOD_RECT 0x02 +#define STM_TS_CMD_SPONGE_OFFSET_UTC 0x10 +#define STM_TS_CMD_SPONGE_LP_DUMP 0xF0 + +/* First byte of ONE EVENT */ +#define STM_TS_EVENT_PASS_REPORT 0x03 +#define STM_TS_EVENT_STATUS_REPORT 0x43 +#define STM_TS_EVENT_JITTER_RESULT 0x49 +#define STM_TS_EVENT_ERROR_REPORT 0xF3 + +/* Test Event */ +#define STM_TS_EVENT_JITTER_MUTUAL_TEST 0x01 +#define STM_TS_EVENT_JITTER_SELF_TEST 0x02 +#define STM_TS_EVENT_JITTER_DELTA_TEST 0x03 + +#define STM_TS_EVENT_JITTER_MUTUAL_MAX 0x01 +#define STM_TS_EVENT_JITTER_MUTUAL_MIN 0x02 +#define STM_TS_EVENT_JITTER_MUTUAL_AVG 0x03 +#define STM_TS_EVENT_JITTER_SELF_TX_P2P 0x05 +#define STM_TS_EVENT_JITTER_SELF_RX_P2P 0x06 + +#define STM_TS_EVENT_SRAM_TEST_RESULT 0xD0 + +/* Status Event */ +#define STM_TS_COORDINATE_EVENT 0 +#define STM_TS_STATUS_EVENT 1 +#define STM_TS_GESTURE_EVENT 2 +#define STM_TS_VENDOR_EVENT 3 + +#define STM_TS_GESTURE_CODE_SPAY 0x00 +#define STM_TS_GESTURE_CODE_DOUBLE_TAP 0x01 +#define STM_TS_GESTURE_CODE_DUMPFLUSH 0x05 + +#define STM_TS_COORDINATE_ACTION_NONE 0 +#define STM_TS_COORDINATE_ACTION_PRESS 1 +#define STM_TS_COORDINATE_ACTION_MOVE 2 +#define STM_TS_COORDINATE_ACTION_RELEASE 3 + +#define STM_TS_TOUCHTYPE_NORMAL 0 +#define STM_TS_TOUCHTYPE_HOVER 1 +#define STM_TS_TOUCHTYPE_FLIPCOVER 2 +#define STM_TS_TOUCHTYPE_GLOVE 3 +#define STM_TS_TOUCHTYPE_STYLUS 4 +#define STM_TS_TOUCHTYPE_PALM 5 +#define STM_TS_TOUCHTYPE_WET 6 +#define STM_TS_TOUCHTYPE_PROXIMITY 7 +#define STM_TS_TOUCHTYPE_JIG 8 + +/* SEC_TS_DUMP_ID */ +#define STM_TS_SPONGE_DUMP_0 0x00 +#define STM_TS_SPONGE_DUMP_1 0x01 + +/* Status - ERROR event */ +#define STM_TS_EVENT_STATUSTYPE_CMDDRIVEN 0 +#define STM_TS_EVENT_STATUSTYPE_ERROR 1 +#define STM_TS_EVENT_STATUSTYPE_INFO 2 +#define STM_TS_EVENT_STATUSTYPE_USERINPUT 3 +#define STM_TS_EVENT_STATUSTYPE_VENDORINFO 7 + +#define STM_TS_ERR_EVNET_CORE_ERR 0x00 +#define STM_TS_ERR_EVENT_QUEUE_FULL 0x01 +#define STM_TS_ERR_EVENT_ESD 0x02 + +/* Status - Information report */ +#define STM_TS_INFO_READY_STATUS 0x00 +#define STM_TS_INFO_WET_MODE 0x01 +#define STM_TS_INFO_NOISE_MODE 0x02 +#define STM_TS_INFO_XENOSENSOR_DETECT 0x04 + +// Scan mode for A0 command +#define STM_TS_SCAN_MODE_SCAN_OFF 0 +#define STM_TS_SCAN_MODE_MS_SS_SCAN (1 << 0) +#define STM_TS_SCAN_MODE_KEY_SCAN (1 << 1) +#define STM_TS_SCAN_MODE_HOVER_SCAN (1 << 2) +#define STM_TS_SCAN_MODE_FORCE_TOUCH_SCAN (1 << 4) +#define STM_TS_SCAN_MODE_DEFAULT STM_TS_SCAN_MODE_MS_SS_SCAN + + +/* Control Command */ + +// For 0x30 command - touch type +#define STM_TS_TOUCHTYPE_BIT_TOUCH (1 << 0) +#define STM_TS_TOUCHTYPE_BIT_HOVER (1 << 1) +#define STM_TS_TOUCHTYPE_BIT_COVER (1 << 2) +#define STM_TS_TOUCHTYPE_BIT_GLOVE (1 << 3) +#define STM_TS_TOUCHTYPE_BIT_STYLUS (1 << 4) +#define STM_TS_TOUCHTYPE_BIT_PALM (1 << 5) +#define STM_TS_TOUCHTYPE_BIT_WET (1 << 6) +#define STM_TS_TOUCHTYPE_BIT_PROXIMITY (1 << 7) +#define STM_TS_TOUCHTYPE_DEFAULT_ENABLE (STM_TS_TOUCHTYPE_BIT_TOUCH | STM_TS_TOUCHTYPE_BIT_PALM | STM_TS_TOUCHTYPE_BIT_WET) + +// For 0x31 command - touch operation mode +#define STM_TS_OPMODE_NORMAL 0 +#define STM_TS_OPMODE_LOWPOWER 1 + +// For 0x32 command - charger mode +#define STM_TS_BIT_CHARGER_MODE_NORMAL 0 +#define STM_TS_BIT_CHARGER_MODE_WIRE_CHARGER 1 +#define STM_TS_BIT_CHARGER_MODE_WIRELESS_CHARGER 2 +#define STM_TS_BIT_CHARGER_MODE_WIRELESS_BATTERY_PACK 3 + +// For 0xC1 command - on/off function +#define STM_TS_FUNCTION_ENABLE_SIP_MODE 0x00 +#define STM_TS_FUNCTION_SET_MONITOR_NOISE_MODE 0x01 +#define STM_TS_FUNCTION_ENABLE_BRUSH_MODE 0x02 +#define STM_TS_FUNCTION_ENABLE_DEAD_ZONE 0x04 /* *#0*# */ +#define STM_TS_FUNCTION_ENABLE_SPONGE_LIB 0x05 +#define STM_TS_FUNCTION_EDGE_AREA 0x07 /* used for grip cmd */ +#define STM_TS_FUNCTION_DEAD_ZONE 0x08 /* used for grip cmd */ +#define STM_TS_FUNCTION_LANDSCAPE_MODE 0x09 /* used for grip cmd */ +#define STM_TS_FUNCTION_LANDSCAPE_TOP_BOTTOM 0x0A /* used for grip cmd */ +#define STM_TS_FUNCTION_EDGE_HANDLER 0x0C /* used for grip cmd */ +#define STM_TS_FUNCTION_ENABLE_VSYNC 0x0D +#define STM_TS_FUNCTION_SET_TOUCHABLE_AREA 0x0F + +/* STM_TS DEBUG FLAG */ +#define STM_TS_DEBUG_PRINT_I2C_READ_CMD 0x04 +#define STM_TS_DEBUG_PRINT_I2C_WRITE_CMD 0x08 +#define STM_TS_DEBUG_SEND_UEVENT 0x80 + +#define STM_TS_RETRY_COUNT 10 +#define STM_TS_DELAY_NVWRITE 50 + +/* gesture SF */ +#define STM_TS_GESTURE_SAMSUNG_FEATURE 1 + +/* gesture type */ +#define STM_TS_SPONGE_EVENT_SWIPE_UP 0 +#define STM_TS_SPONGE_EVENT_DOUBLETAP 1 +#define STM_TS_SPONGE_EVENT_PRESS 3 +#define STM_TS_SPONGE_EVENT_SINGLETAP 4 + +#define STM_TS_VENDOR_EVENT_LARGEPALM 6 +#define STM_TS_SPONGE_EVENT_LONGPRESS 8 + +/* gesture ID */ +#define STM_TS_SPONGE_EVENT_GESTURE_ID_AOD 0 +#define STM_TS_SPONGE_EVENT_GESTURE_ID_DOUBLETAP_TO_WAKEUP 1 +#define STM_TS_SPONGE_EVENT_GESTURE_ID_FOD_LONG 0 +#define STM_TS_SPONGE_EVENT_GESTURE_ID_FOD_NORMAL 1 +#define STM_TS_SPONGE_EVENT_GESTURE_ID_FOD_RELEASE 2 +#define STM_TS_SPONGE_EVENT_GESTURE_ID_FOD_OUT 3 +#define STM_TS_SPONGE_EVENT_GESTURE_ID_FOD_VI 4 + +#define STM_TS_ENABLE 1 +#define STM_TS_DISABLE 0 + +#define STM_TS_SPONGE_LP_DUMP_LENGTH 70 + +#define STM_TS_MAX_X_RESOLUTION 4096 +#define STM_TS_MAX_Y_RESOLUTION 4096 +#define STM_TS_MAX_NUM_FORCE 50 /* Number of TX CH */ +#define STM_TS_MAX_NUM_SENSE 50 /* Number of RX CH */ + +#define STM_TS_LFD_CTRL_LOCK 1 +#define STM_TS_LFD_CTRL_UNLOCK 2 + +#define STM_TS_TS_LOCATION_DETECT_SIZE 6 + +#define STM_TS_STATUS_UNFOLDING 0x00 +#define STM_TS_STATUS_FOLDING 0x01 + +#define NORMAL_CX2 0 /* single driving */ +#define ACTIVE_CX2 1 /* multi driving */ + +#define STM_TS_GET_CMOFFSET_DUMP_V1 0 +#define STM_TS_GET_CMOFFSET_DUMP_V2 1 + +/* fw update */ +#define CODE_ADDR_START 0x00000000 +#if IS_ENABLED(CONFIG_TOUCHSCREEN_STM_SPI) +#define CX_ADDR_START 0x0000B400 +#define CONFIG_ADDR_START 0x0000BC00 +#define STM_REG_MISO_PORT_SETTING 0x3E +#else +#define CX_ADDR_START 0x00007000 +#define CONFIG_ADDR_START 0x00007C00 +#define STM_REG_MISO_PORT_SETTING 0x3F +#endif + +/* fw update */ +#define CODE_ADDR_START 0x00000000 +#if IS_ENABLED(CONFIG_TOUCHSCREEN_STM_SPI) +#define CX_ADDR_START 0x0000B400 +#define CONFIG_ADDR_START 0x0000BC00 +#define STM_REG_MISO_PORT_SETTING 0x3E +#else +#define CX_ADDR_START 0x00007000 +#define CONFIG_ADDR_START 0x00007C00 +#define STM_REG_MISO_PORT_SETTING 0x3F +#endif + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_STM_SPI) +#define STM_TS_CMD_REG_W 0xFA +#define STM_TS_CMD_REG_R 0xFB +#define STM_TS_CMD_SCAN_MODE 0xA0 +#define STM_TS_CMD_FEATURE 0xA2 +#define STM_TS_CMD_SYSTEM 0xA4 +#define STM_TS_CMD_FRM_BUFF_W 0xA6 +#define STM_TS_CMD_FRM_BUFF_R 0xA7 +#define STM_TS_CMD_CUSTOM_W 0xC0 +#define STM_TS_CMD_SPONGE_W 0xD0 +#define STM_TS_CMD_CUSTOM_R 0xD1 +#define STM_TS_CMD_SNR_R 0x75 +#define STM_TS_CMD_SNR_R_SIZE 3 +#else +#define STM_TS_CMD_REG_W 0xFA +#define STM_TS_CMD_REG_R 0xFA +#define STM_TS_CMD_SCAN_MODE 0xA0 +#define STM_TS_CMD_FEATURE 0xA2 +#define STM_TS_CMD_SYSTEM 0xA4 +#define STM_TS_CMD_FRM_BUFF_W 0xA6 +#define STM_TS_CMD_FRM_BUFF_R 0xA6 +#define STM_TS_CMD_CUSTOM_W 0xC0 +#define STM_TS_CMD_SPONGE_W 0xD0 +#define STM_TS_CMD_CUSTOM_R 0xD1 +#define STM_TS_CMD_SNR_R 0x72 +#define STM_TS_CMD_SNR_R_SIZE 2 +#endif + +#define STM_TS_NOT_ERROR 0 +#define STM_TS_ERROR 1 +#define STM_TS_I2C_ERROR 2 +#define STM_TS_ERROR_INVALID_CHIP_ID 3 +#define STM_TS_ERROR_INVALID_CHIP_VERSION_ID 4 +#define STM_TS_ERROR_INVALID_SW_VERSION 5 +#define STM_TS_ERROR_EVENT_ID 6 +#define STM_TS_ERROR_FW_CORRUPTION 7 +#define STM_TS_ERROR_TIMEOUT 8 +#define STM_TS_ERROR_TIMEOUT_ZERO 9 +#define STM_TS_ERROR_FW_UPDATE_FAIL 10 +#define STM_TS_ERROR_BROKEN_OSC_TRIM 11 +#define STM_TS_ERROR_BROKEN_FW 12 + +/* 403601 is rgb */ +#define STM_TS_IS_EXCEPTIONAL_CHIP(id) ((id == 0x503601) || (id == 0x393603) || (id == 0x403601) || (id == 0x403603)) + + + diff --git a/drivers/input/sec_input/stm_spi/stm_spi.c b/drivers/input/sec_input/stm_spi/stm_spi.c new file mode 100644 index 000000000000..c74cf06f9e55 --- /dev/null +++ b/drivers/input/sec_input/stm_spi/stm_spi.c @@ -0,0 +1,873 @@ +/* drivers/input/sec_input/stm/stm_core.c + * + * Copyright (C) 2020 Samsung Electronics Co., Ltd. + * + * Core file for Samsung TSC driver + * + * 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. + */ + +#include "stm_dev.h" +#include "stm_reg.h" + +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) +extern int stui_spi_lock(struct spi_master *spi); +extern int stui_spi_unlock(struct spi_master *spi); + +int stm_stui_tsp_enter(void) +{ + struct stm_ts_data *ts = dev_get_drvdata(ptsp); + struct spi_device *spi; + int ret = 0; + + if (!ts) + return -EINVAL; + + spi = (struct spi_device *)ts->client; + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&ts->stm_input_nb, NOTIFIER_SECURE_TOUCH_ENABLE, NULL); +#endif + + disable_irq(ts->irq); + stm_ts_release_all_finger(ts); + + ret = stui_spi_lock(spi->controller); + if (ret < 0) { + pr_err("[STUI] stui_spi_lock failed : %d\n", ret); +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&ts->stm_input_nb, NOTIFIER_SECURE_TOUCH_DISABLE, NULL); +#endif + enable_irq(ts->irq); + return -1; + } + return 0; +} + +int stm_stui_tsp_exit(void) +{ + struct stm_ts_data *ts = dev_get_drvdata(ptsp); + struct spi_device *spi; + int ret = 0; + + if (!ts) + return -EINVAL; + + spi = (struct spi_device *)ts->client; + + ret = stui_spi_unlock(spi->controller); + if (ret < 0) + pr_err("[STUI] stui_spi_unlock failed : %d\n", ret); + + enable_irq(ts->irq); + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&ts->stm_input_nb, NOTIFIER_SECURE_TOUCH_DISABLE, NULL); +#endif + + return ret; +} + +int stm_stui_tsp_type(void) +{ + return STUI_TSP_TYPE_STM; +} +#endif + +int stm_ts_wire_mode_change(struct stm_ts_data *ts, u8 *reg) +{ + int ret = 0; + + reg[0] = STM_TS_CMD_REG_W; + reg[1] = 0x20; + reg[2] = 0x00; + reg[3] = 0x00; + reg[4] = 0x32; + reg[5] = 0x10; + ret = ts->stm_ts_write(ts, ®[0], 6, NULL, 0); + if (ret < 0) + return ret; + sec_delay(2); + + reg[0] = STM_TS_CMD_REG_W; + reg[1] = 0x20; + reg[2] = 0x00; + reg[3] = 0x00; + reg[4] = 0x34; + reg[5] = 0x02; + ret = ts->stm_ts_write(ts, ®[0], 6, NULL, 0); + if (ret < 0) + return ret; + sec_delay(2); + + reg[0] = STM_TS_CMD_REG_W; + reg[1] = 0x20; + reg[2] = 0x00; + reg[3] = 0x00; + reg[4] = STM_REG_MISO_PORT_SETTING; + reg[5] = 0x07; + ret = ts->stm_ts_write(ts, ®[0], 6, NULL, 0); + if (ret < 0) + return ret; + sec_delay(2); + + reg[0] = STM_TS_CMD_REG_W; + reg[1] = 0x20; + reg[2] = 0x00; + reg[3] = 0x00; + reg[4] = 0x3D; + reg[5] = 0x30; + ret = ts->stm_ts_write(ts, ®[0], 6, NULL, 0); + if (ret < 0) + return ret; + sec_delay(2); + + reg[0] = STM_TS_CMD_REG_W; + reg[1] = 0x20; + reg[2] = 0x00; + reg[3] = 0x00; + reg[4] = 0x2D; + reg[5] = 0x02; + ret = ts->stm_ts_write(ts, ®[0], 6, NULL, 0); + if (ret < 0) + return ret; + sec_delay(15); + + reg[0] = STM_TS_CMD_REG_W; + reg[1] = 0x20; + reg[2] = 0x00; + reg[3] = 0x00; + reg[4] = 0x1B; + reg[5] = 0x66; + ret = ts->stm_ts_write(ts, ®[0], 6, NULL, 0); + if (ret < 0) + return ret; + sec_delay(30); + + reg[0] = STM_TS_CMD_REG_W; + reg[1] = 0x20; + reg[2] = 0x00; + reg[3] = 0x00; + reg[4] = 0x68; + reg[5] = 0x13; + ret = ts->stm_ts_write(ts, ®[0], 6, NULL, 0); + if (ret < 0) + return ret; + sec_delay(30); + return 0; +} + +struct device *stm_ts_get_client_dev(struct stm_ts_data *ts) +{ + struct spi_device *spi = (struct spi_device *)ts->client; + + return &spi->dev; +} + +void stm_ts_set_spi_mode(struct stm_ts_data *ts) +{ + struct spi_device *spi = (struct spi_device *)ts->client; + if (ts->plat_data->gpio_spi_cs > 0) { + gpio_direction_output(ts->plat_data->gpio_spi_cs, 1); + input_err(true, ts->dev, "%s: cs gpio: %d\n", + __func__, gpio_get_value(ts->plat_data->gpio_spi_cs)); + } + + spi->mode = SPI_MODE_0; + input_err(true, ts->dev, "%s: mode: %d, max_speed_hz: %d: cs: %d\n", __func__, spi->mode, spi->max_speed_hz, ts->plat_data->gpio_spi_cs); + input_err(true, ts->dev, "%s: chip_select: %d, bits_per_word: %d\n", __func__, spi->chip_select, spi->bits_per_word); +} + +static int stm_ts_read_buf_malloc(struct stm_ts_data *ts, int length) +{ + static int define_length = STM_TS_EVENT_BUFF_MAX_SIZE; + + if (define_length == STM_TS_EVENT_BUFF_MAX_SIZE && length <= STM_TS_EVENT_BUFF_MAX_SIZE) + return SEC_SUCCESS; + + if (length <= STM_TS_EVENT_BUFF_MAX_SIZE) { + if (ts->read_buf) + devm_kfree(ts->dev, ts->read_buf); + ts->read_buf = devm_kzalloc(ts->dev, STM_TS_EVENT_BUFF_MAX_SIZE, GFP_KERNEL); + if (unlikely(ts->read_buf == NULL)) + return -ENOMEM; + define_length = STM_TS_EVENT_BUFF_MAX_SIZE; + } else { + if (ts->read_buf) + devm_kfree(ts->dev, ts->read_buf); + ts->read_buf = devm_kzalloc(ts->dev, length, GFP_KERNEL); + if (unlikely(ts->read_buf == NULL)) + return -ENOMEM; + define_length = length; + } + + return SEC_SUCCESS; +} + +static int stm_ts_write_buf_malloc(struct stm_ts_data *ts, int length) +{ + static int define_length = STM_TS_EVENT_BUFF_MAX_SIZE; + + if (define_length == STM_TS_EVENT_BUFF_MAX_SIZE && length <= STM_TS_EVENT_BUFF_MAX_SIZE) + return SEC_SUCCESS; + + if (length <= STM_TS_EVENT_BUFF_MAX_SIZE) { + if (ts->write_buf) + devm_kfree(ts->dev, ts->write_buf); + ts->write_buf = devm_kzalloc(ts->dev, STM_TS_EVENT_BUFF_MAX_SIZE, GFP_KERNEL); + if (unlikely(ts->write_buf == NULL)) + return -ENOMEM; + define_length = STM_TS_EVENT_BUFF_MAX_SIZE; + } else { + if (ts->write_buf) + devm_kfree(ts->dev, ts->write_buf); + ts->write_buf = devm_kzalloc(ts->dev, length, GFP_KERNEL); + if (unlikely(ts->write_buf == NULL)) + return -ENOMEM; + define_length = length; + } + + return SEC_SUCCESS; +} + +int stm_ts_read_from_sponge(struct stm_ts_data *ts, u8 *data, int length) +{ + int ret = -ENOMEM; + int total_length = 3 + 1 + length; /* (dummy:D1(1) + offset(2) + length*/ + + if (atomic_read(&ts->plat_data->power_state) == SEC_INPUT_STATE_POWER_OFF) { + input_err(true, ts->dev, "%s: Touch is stopped!\n", __func__); + return -ENODEV; + } + + if (!ts->plat_data->resume_done.done) { + ret = wait_for_completion_interruptible_timeout(&ts->plat_data->resume_done, msecs_to_jiffies(500)); + if (ret <= 0) { + input_err(true, ts->dev, "%s: LPM: pm resume is not handled:%d\n", __func__, ret); + return -EIO; + } + } + if (sec_check_secure_trusted_mode_status(ts->plat_data)) + return -EBUSY; + + mutex_lock(&ts->read_write_mutex); + ret = stm_ts_write_buf_malloc(ts, total_length); + if (ret < 0) { + mutex_unlock(&ts->read_write_mutex); + return -ENODEV; + } + + ret = stm_ts_read_buf_malloc(ts, total_length); + if (ret < 0) { + mutex_unlock(&ts->read_write_mutex); + return -ENODEV; + } + + ts->write_buf[0] = 0xC0; + ts->write_buf[1] = STM_TS_CMD_SPONGE_READ_WRITE_CMD; + + ts->transfer->len = 2; + ts->transfer->tx_buf = ts->write_buf; + + spi_message_init(ts->message); + spi_message_add_tail(ts->transfer, ts->message); + + if (ts->plat_data->gpio_spi_cs > 0) + gpio_direction_output(ts->plat_data->gpio_spi_cs, 0); + + ret = spi_sync(ts->client, ts->message); + if (ret < 0) + input_err(true, ts->dev, "%s: write failed: %d\n", __func__, ret); + + if (ts->plat_data->gpio_spi_cs > 0) + gpio_direction_output(ts->plat_data->gpio_spi_cs, 1); + + if (ts->debug_flag & SEC_TS_DEBUG_PRINT_READ_CMD) + pr_info("sec_input: spi rsp: W: %02X %02X\n", ts->write_buf[0], ts->write_buf[1]); + + memset(ts->write_buf, 0x0, total_length); + + ts->write_buf[0] = 0xD1; + ts->write_buf[1] = data[1]; //offset + ts->write_buf[2] = data[0]; + + spi_message_init(ts->message); + ts->transfer->len = total_length; + ts->transfer->tx_buf = ts->write_buf; + ts->transfer->rx_buf = ts->read_buf; +#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0)) + ts->transfer->delay_usecs = 10; +#endif + spi_message_add_tail(ts->transfer, ts->message); + + if (ts->plat_data->gpio_spi_cs > 0) + gpio_direction_output(ts->plat_data->gpio_spi_cs, 0); + + ret = spi_sync(ts->client, ts->message); + if (ret < 0) + input_err(true, ts->dev, "%s: read failed%d\n", __func__, ret); + + if (ts->plat_data->gpio_spi_cs > 0) + gpio_direction_output(ts->plat_data->gpio_spi_cs, 1); + + if (ts->debug_flag & SEC_TS_DEBUG_PRINT_READ_CMD) { + int i; + + pr_info("sec_input: spi rsp: R: "); + for (i = 0; i < 3; i++) + pr_cont("%02X ", ts->write_buf[i]); + pr_cont("| "); + for (i = 0; i < (total_length); i++) { + if (i == (total_length - length)) + pr_cont("|| "); + pr_cont("%02X ", ts->read_buf[i]); + } + pr_cont("\n"); + + } + memcpy(data, &ts->read_buf[4], length); + memset(ts->write_buf, 0x0, 3 + 1 + length); + memset(ts->read_buf, 0x0, 3 + 1 + length); + ts->transfer->tx_buf = NULL; + ts->transfer->rx_buf = NULL; + mutex_unlock(&ts->read_write_mutex); + + return ret; +} + +int stm_ts_write_to_sponge(struct stm_ts_data *ts, u8 *data, int length) +{ + int ret = -ENOMEM; + + if (atomic_read(&ts->plat_data->power_state) == SEC_INPUT_STATE_POWER_OFF) { + input_err(true, ts->dev, "%s: Touch is stopped!\n", __func__); + return -ENODEV; + } + + if (!ts->plat_data->resume_done.done) { + ret = wait_for_completion_interruptible_timeout(&ts->plat_data->resume_done, msecs_to_jiffies(500)); + if (ret <= 0) { + input_err(true, ts->dev, "%s: LPM: pm resume is not handled:%d\n", __func__, ret); + return -EIO; + } + } + if (sec_check_secure_trusted_mode_status(ts->plat_data)) + return -EBUSY; + + mutex_lock(&ts->read_write_mutex); + ret = stm_ts_write_buf_malloc(ts, 3 + length); + if (ret < 0) { + mutex_unlock(&ts->read_write_mutex); + return -ENODEV; + } + + ts->write_buf[0] = 0xC0; + ts->write_buf[1] = STM_TS_CMD_SPONGE_READ_WRITE_CMD; + + ts->transfer->len = 2; + ts->transfer->tx_buf = ts->write_buf; + + spi_message_init(ts->message); + spi_message_add_tail(ts->transfer, ts->message); + + if (ts->plat_data->gpio_spi_cs > 0) + gpio_direction_output(ts->plat_data->gpio_spi_cs, 0); + + ret = spi_sync(ts->client, ts->message); + if (ret < 0) + input_err(true, ts->dev, "%s: failed: %d\n", __func__, ret); + + if (ts->plat_data->gpio_spi_cs > 0) + gpio_direction_output(ts->plat_data->gpio_spi_cs, 1); + if (ts->debug_flag & SEC_TS_DEBUG_PRINT_WRITE_CMD) + pr_info("sec_input: spi wsp: W: %02X %02X\n", ts->write_buf[0], ts->write_buf[1]); + memset(ts->write_buf, 0x0, 3 + length); + + ts->write_buf[0] = 0xD0; + ts->write_buf[1] = data[1]; + ts->write_buf[2] = data[0]; + + memcpy(&ts->write_buf[3], &data[2], length - 2); + + ts->transfer->len = 1 + length; /* (tbuf2(3) - offset(2) + length*/ + ts->transfer->tx_buf = ts->write_buf; + + spi_message_init(ts->message); + spi_message_add_tail(ts->transfer, ts->message); + + if (ts->plat_data->gpio_spi_cs > 0) + gpio_direction_output(ts->plat_data->gpio_spi_cs, 0); + + ret = spi_sync(ts->client, ts->message); + if (ret < 0) + input_err(true, ts->dev, "%s: failed: %d\n", __func__, ret); + + if (ts->plat_data->gpio_spi_cs > 0) + gpio_direction_output(ts->plat_data->gpio_spi_cs, 1); + + if (ts->debug_flag & SEC_TS_DEBUG_PRINT_WRITE_CMD) { + int i; + + pr_info("sec_input: spi wsp: W: "); + for (i = 0; i < ts->transfer->len; i++) + pr_cont("%02X ", ts->write_buf[i]); + pr_cont("\n"); + + } + memset(ts->write_buf, 0x0, 3 + length); + + ts->write_buf[0] = 0xC0; + ts->write_buf[1] = STM_TS_CMD_SPONGE_NOTIFY_CMD; + + ts->transfer->len = 2; + ts->transfer->tx_buf = ts->write_buf; + + spi_message_init(ts->message); + spi_message_add_tail(ts->transfer, ts->message); + + if (ts->plat_data->gpio_spi_cs > 0) + gpio_direction_output(ts->plat_data->gpio_spi_cs, 0); + + ret = spi_sync(ts->client, ts->message); + if (ret < 0) + input_err(true, ts->dev, "%s: failed: %d\n", __func__, ret); + + if (ts->plat_data->gpio_spi_cs > 0) + gpio_direction_output(ts->plat_data->gpio_spi_cs, 1); + + if (ts->debug_flag & SEC_TS_DEBUG_PRINT_WRITE_CMD) + pr_info("sec_input: spi wsp: W: %02X %02X\n", ts->write_buf[0], ts->write_buf[1]); + + memset(ts->write_buf, 0x0, 3 + length); + ts->transfer->tx_buf = NULL; + ts->transfer->rx_buf = NULL; + mutex_unlock(&ts->read_write_mutex); + + return ret; +} + +int stm_ts_no_lock_spi_write(struct stm_ts_data *ts, u8 *reg, int tlen, u8 *data, int len) +{ + int ret = -ENOMEM; + int wlen; + + ret = stm_ts_write_buf_malloc(ts, tlen + len + 1); + if (ret < 0) { + return -ENODEV; + } + + switch (reg[0]) { + case STM_TS_CMD_SCAN_MODE: + case STM_TS_CMD_FEATURE: + case STM_TS_CMD_SYSTEM: + case STM_TS_CMD_FRM_BUFF_R: + case STM_TS_CMD_REG_W: + case STM_TS_CMD_REG_R: + case STM_TS_CMD_SPONGE_W: + memcpy(ts->write_buf, reg, tlen); + memcpy(&ts->write_buf[tlen], data, len); + wlen = tlen + len; + break; + default: + ts->write_buf[0] = 0xC0; + memcpy(&ts->write_buf[1], reg, tlen); + memcpy(&ts->write_buf[1 + tlen], data, len); + wlen = tlen + len + 1; + break; + } + + ts->transfer->len = wlen; + ts->transfer->tx_buf = ts->write_buf; + + spi_message_init(ts->message); + spi_message_add_tail(ts->transfer, ts->message); + + if (ts->plat_data->gpio_spi_cs > 0) + gpio_direction_output(ts->plat_data->gpio_spi_cs, 0); + + ret = spi_sync(ts->client, ts->message); + if (ret < 0) + input_err(true, ts->dev, "%s: failed: %d\n", __func__, ret); + + if (ts->plat_data->gpio_spi_cs > 0) + gpio_direction_output(ts->plat_data->gpio_spi_cs, 1); + + if (ts->debug_flag & SEC_TS_DEBUG_PRINT_WRITE_CMD) { + int i; + + pr_info("sec_input: spi: W: "); + for (i = 0; i < wlen; i++) + pr_cont("%02X ", ts->write_buf[i]); + pr_cont("\n"); + } + memset(ts->write_buf, 0x0, tlen + len + 1); + ts->transfer->tx_buf = NULL; + ts->transfer->rx_buf = NULL; + + return ret; +} + +int stm_ts_spi_write(struct stm_ts_data *ts, u8 *reg, int tlen, u8 *data, int len) +{ + int ret = -ENOMEM; + + if (atomic_read(&ts->plat_data->power_state) == SEC_INPUT_STATE_POWER_OFF) { + input_err(true, ts->dev, "%s: Sensor stopped\n", __func__); + return -EIO; + } + + if (!ts->plat_data->resume_done.done) { + ret = wait_for_completion_interruptible_timeout(&ts->plat_data->resume_done, msecs_to_jiffies(500)); + if (ret <= 0) { + input_err(true, ts->dev, "%s: LPM: pm resume is not handled:%d\n", __func__, ret); + return -EIO; + } + } + +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) + if (STUI_MODE_TOUCH_SEC & stui_get_mode()) + return -EBUSY; +#endif + if (sec_check_secure_trusted_mode_status(ts->plat_data)) + return -EBUSY; + + mutex_lock(&ts->read_write_mutex); + ret = stm_ts_no_lock_spi_write(ts, reg, tlen, data, len); + mutex_unlock(&ts->read_write_mutex); + + return ret; +} + +int stm_ts_spi_read(struct stm_ts_data *ts, u8 *reg, int tlen, u8 *buf, int rlen) +{ + int ret = -ENOMEM; + int wmsg = 0; + int buf_len = tlen; + int mem_len; + u8 temp_write_buf[5] = { 0 }; + + if (atomic_read(&ts->plat_data->power_state) == SEC_INPUT_STATE_POWER_OFF) { + input_err(true, ts->dev, "%s: Sensor stopped\n", __func__); + return -EIO; + } + + if (!ts->plat_data->resume_done.done) { + ret = wait_for_completion_interruptible_timeout(&ts->plat_data->resume_done, msecs_to_jiffies(500)); + if (ret <= 0) { + input_err(true, ts->dev, "%s: LPM: pm resume is not handled:%d\n", __func__, ret); + return -EIO; + } + } + +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) + if (STUI_MODE_TOUCH_SEC & stui_get_mode()) + return -EBUSY; +#endif + if (sec_check_secure_trusted_mode_status(ts->plat_data)) + return -EBUSY; + + mutex_lock(&ts->read_write_mutex); + if (tlen > 3) + mem_len = rlen + 1 + tlen; + else + mem_len = rlen + 1 + 3; + + ret = stm_ts_write_buf_malloc(ts, mem_len); + if (ret < 0) { + mutex_unlock(&ts->read_write_mutex); + return -ENODEV; + } + + ret = stm_ts_read_buf_malloc(ts, mem_len); + if (ret < 0) { + mutex_unlock(&ts->read_write_mutex); + return -ENODEV; + } + + switch (reg[0]) { + case 0x87: + case STM_TS_CMD_FRM_BUFF_R: + case STM_TS_CMD_SYSTEM: + case STM_TS_CMD_REG_W: + case STM_TS_CMD_REG_R: + memcpy(temp_write_buf, reg, tlen); + wmsg = 0; + break; + case 0x75: + buf_len = 3; + temp_write_buf[0] = 0xD1; + if (tlen == 1) { + temp_write_buf[1] = 0x00; + temp_write_buf[2] = 0x00; + } else if (tlen == 2) { + temp_write_buf[1] = 0x00; + temp_write_buf[2] = reg[0]; + } else if (tlen == 3) { + temp_write_buf[1] = reg[1]; + temp_write_buf[2] = reg[0]; + } else { + input_err(true, ts->dev, "%s: tlen is mismatched: %d\n", __func__, tlen); + mutex_unlock(&ts->read_write_mutex); + return -EINVAL; + } + wmsg = 0; + break; + default: + buf_len = 3; + temp_write_buf[0] = 0xD1; + + if (tlen <= 3) { + temp_write_buf[1] = 0x00; + temp_write_buf[2] = 0x00; + } else { + input_err(true, ts->dev, "%s: tlen is mismatched: %d\n", __func__, tlen); + mutex_unlock(&ts->read_write_mutex); + return -EINVAL; + } + wmsg = 1; + break; + } + + if (wmsg) + stm_ts_no_lock_spi_write(ts, reg, tlen, NULL, 0); + + spi_message_init(ts->message); + memcpy(ts->write_buf, temp_write_buf, buf_len); + ts->transfer->len = rlen + 1 + buf_len; + ts->transfer->tx_buf = ts->write_buf; + ts->transfer->rx_buf = ts->read_buf; +#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0)) + ts->transfer->delay_usecs = 10; +#endif + spi_message_add_tail(ts->transfer, ts->message); + + if (ts->plat_data->gpio_spi_cs > 0) + gpio_direction_output(ts->plat_data->gpio_spi_cs, 0); + + ret = spi_sync(ts->client, ts->message); + if (ret < 0) + input_err(true, ts->dev, "%s: %d\n", __func__, ret); + + if (ts->plat_data->gpio_spi_cs > 0) + gpio_direction_output(ts->plat_data->gpio_spi_cs, 1); + + if (ts->debug_flag & SEC_TS_DEBUG_PRINT_READ_CMD) { + int i; + + pr_info("sec_input: spi: R: "); + for (i = 0; i < buf_len; i++) + pr_cont("%02X ", ts->write_buf[i]); + pr_cont("| "); + for (i = 0; i < rlen + 1 + buf_len; i++) { + if (i == buf_len + 1) + pr_cont("|| "); + pr_cont("%02X ", ts->read_buf[i]); + } + pr_cont("\n"); + + } + memcpy(buf, &ts->read_buf[1 + buf_len], rlen); + memset(ts->write_buf, 0x0, mem_len); + memset(ts->read_buf, 0x0, mem_len); + ts->transfer->tx_buf = NULL; + ts->transfer->rx_buf = NULL; + mutex_unlock(&ts->read_write_mutex); + + return ret; +} + +static int stm_ts_spi_init(struct spi_device *client) +{ + struct stm_ts_data *ts; + int ret = 0; + + ts = devm_kzalloc(&client->dev, sizeof(struct stm_ts_data), GFP_KERNEL); + if (unlikely(ts == NULL)) { + ret = -ENOMEM; + goto error_allocate; + } + + ts->plat_data = devm_kzalloc(&client->dev, sizeof(struct sec_ts_plat_data), GFP_KERNEL); + if (unlikely(ts->plat_data == NULL)) { + ret = -ENOMEM; + goto error_allocate; + } + + ts->tdata = devm_kzalloc(&client->dev, sizeof(struct sec_tclm_data), GFP_KERNEL); + if (unlikely(ts->tdata == NULL)) { + ret = -ENOMEM; + goto error_allocate; + } + client->dev.platform_data = ts->plat_data; + + ts->read_buf = devm_kzalloc(&client->dev, STM_TS_EVENT_BUFF_MAX_SIZE, GFP_KERNEL); + if (unlikely(ts->read_buf == NULL)) { + ret = -ENOMEM; + goto error_allocate; + } + + ts->write_buf = devm_kzalloc(&client->dev, STM_TS_EVENT_BUFF_MAX_SIZE, GFP_KERNEL); + if (unlikely(ts->write_buf == NULL)) { + ret = -ENOMEM; + goto error_allocate; + } + + ts->message = devm_kzalloc(&client->dev, sizeof(struct spi_message), GFP_KERNEL); + if (unlikely(ts->message == NULL)) { + ret = -ENOMEM; + goto error_allocate; + } + + ts->transfer = devm_kzalloc(&client->dev, sizeof(struct spi_transfer), GFP_KERNEL); + if (unlikely(ts->transfer == NULL)) { + ret = -ENOMEM; + goto error_allocate; + } + + ts->client = client; + ts->dev = &client->dev; +#if IS_ENABLED(CONFIG_INPUT_SEC_TRUSTED_TOUCH) + ts->plat_data->dev = &client->dev; + ts->plat_data->bus_master = &client->controller->dev; +#endif + ts->stm_ts_read = stm_ts_spi_read; + ts->stm_ts_write = stm_ts_spi_write; + ts->stm_ts_read_sponge = stm_ts_read_from_sponge; + ts->stm_ts_write_sponge = stm_ts_write_to_sponge; + + spi_set_drvdata(client, ts); + ret = spi_setup(client); + input_info(true, &client->dev, "%s: spi setup: %d\n", __func__, ret); + + +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) + ts->plat_data->stui_tsp_enter = stm_stui_tsp_enter; + ts->plat_data->stui_tsp_exit = stm_stui_tsp_exit; + ts->plat_data->stui_tsp_type = stm_stui_tsp_type; +#endif + + ret = stm_ts_init(ts); + if (ret < 0) { + input_err(true, ts->dev, "%s: fail to init resource\n", __func__); + return ret; + } + ret = 0; +error_allocate: + return ret; +} + +int stm_ts_spi_probe(struct spi_device *client) +{ + struct stm_ts_data *ts; + int ret = 0; + + input_info(true, &client->dev, "%s\n", __func__); + + ret = stm_ts_spi_init(client); + if (ret < 0) { + input_err(true, &client->dev, "%s: fail to init resource\n", __func__); + return ret; + } + + ts = spi_get_drvdata(client); + if (!ts->plat_data->work_queue_probe_enabled) + return stm_ts_probe(ts->dev); + + queue_work(ts->plat_data->probe_workqueue, &ts->plat_data->probe_work); + return 0; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)) +void stm_ts_spi_remove(struct spi_device *client) +#else +int stm_ts_spi_remove(struct spi_device *client) +#endif + +{ + struct stm_ts_data *ts = spi_get_drvdata(client); + int ret = 0; + + ret = stm_ts_remove(ts); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)) + return; +#else + return ret; +#endif + +} + +void stm_ts_spi_shutdown(struct spi_device *client) +{ + struct stm_ts_data *ts = spi_get_drvdata(client); + + stm_ts_shutdown(ts); +} + + +#if IS_ENABLED(CONFIG_PM) +static int stm_ts_spi_pm_suspend(struct device *dev) +{ + struct stm_ts_data *ts = dev_get_drvdata(dev); + + stm_ts_pm_suspend(ts); + + return 0; +} + +static int stm_ts_spi_pm_resume(struct device *dev) +{ + struct stm_ts_data *ts = dev_get_drvdata(dev); + + stm_ts_pm_resume(ts); + + return 0; +} +#endif + + +static const struct spi_device_id stm_ts_spi_id[] = { + { STM_TS_SPI_NAME, 0 }, + { }, +}; + +#if IS_ENABLED(CONFIG_PM) +static const struct dev_pm_ops stm_ts_dev_pm_ops = { + .suspend = stm_ts_spi_pm_suspend, + .resume = stm_ts_spi_pm_resume, +}; +#endif + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id stm_ts_match_table[] = { + { .compatible = "stm,stm_ts_spi",}, + { }, +}; +#else +#define stm_ts_match_table NULL +#endif + +static struct spi_driver stm_ts_driver = { + .probe = stm_ts_spi_probe, + .remove = stm_ts_spi_remove, + .shutdown = stm_ts_spi_shutdown, + .id_table = stm_ts_spi_id, + .driver = { + .owner = THIS_MODULE, + .name = STM_TS_SPI_NAME, +#if IS_ENABLED(CONFIG_OF) + .of_match_table = stm_ts_match_table, +#endif +#if IS_ENABLED(CONFIG_PM) + .pm = &stm_ts_dev_pm_ops, +#endif + }, +}; + +module_spi_driver(stm_ts_driver); + + +MODULE_SOFTDEP("pre: acpm-mfd-bus"); +MODULE_DESCRIPTION("stm TouchScreen driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/sec_input/stm_spi/stm_vendor.c b/drivers/input/sec_input/stm_spi/stm_vendor.c new file mode 100644 index 000000000000..31df438105af --- /dev/null +++ b/drivers/input/sec_input/stm_spi/stm_vendor.c @@ -0,0 +1,555 @@ +/* + * + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com * + ************************************************************************** + * * + * Utilities published in /proc/fts * + * * + ************************************************************************** + ************************************************************************** + * + */ + +/*! + * \file fts_proc.c + * \brief contains the function and variables needed to publish a file node in + * the file system which allow to communicate with the IC from userspace +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "stm_dev.h" + +static struct stm_ts_data *proc_ts; + +char tag[12] = "[fts_touch]\0"; + +#define FTS_TS_DRV_VER 0x05021300 /* driver version u32 format */ + +/* FIRST LEVEL ERROR CODE */ +/** @defgroup first_level First Level Error Code + * @ingroup error_codes + * Errors related to low level operation which are not under control of driver, + * such as: communication protocol (I2C/SPI), timeout, file operations ... + * @{ + */ +#define OK ((int)0x00000000) /* /< No ERROR */ +#define ERROR_ALLOC ((int)0x80000001) /* /< allocation of memory failed */ +#define ERROR_BUS_R ((int)0x80000002) /* /< i2c/spi read failed */ +#define ERROR_BUS_W ((int)0x80000003) /* /< i2c/spi write failed */ +#define ERROR_BUS_WR ((int)0x80000004) /* /< i2c/spi write/read failed */ +#define ERROR_BUS_O ((int)0x80000005) /* /< error during opening an i2c device */ +#define ERROR_OP_NOT_ALLOW ((int)0x80000006) /* /< operation not allowed */ +#define ERROR_TIMEOUT ((int)0x80000007) /* /< timeout expired! exceed the max number of retries or the max waiting time */ + +#define DRIVER_TEST_FILE_NODE "driver_test" /* /< name of file node published */ +#define CHUNK_PROC 1024 /* /< Max chunk of data printed on the sequential file in each iteration */ +#define DIAGNOSTIC_NUM_FRAME 10 /* /< number of frames reading iterations during the diagnostic test */ + +/** @}*/ + +/** @defgroup scriptless Scriptless Protocol + * @ingroup proc_file_code + * Scriptless Protocol allows ST Software (such as FingerTip Studio etc) to + * communicate with the IC from an user space. + * This mode gives access to common bus operations (write, read etc) and + * support additional functionalities. \n + * The protocol is based on exchange of binary messages included between a + * start and an end byte + * @{ + */ + +#define MESSAGE_START_BYTE 0x7B /* /< start byte of each message transferred in Scriptless Mode */ +#define MESSAGE_END_BYTE 0x7D /* /< end byte of each message transferred in Scriptless Mode */ +#define MESSAGE_MIN_HEADER_SIZE 8 /* /< minimun number of bytes of the structure of a messages exchanged with host (include start/end byte, counter, actions, msg_size) */ + +/* GUI utils byte ver */ +#define CMD_WRITE_BYTE 0xF1 /* /< Byte output version of I2C/SPI write @see CMD_WRITE */ +#define CMD_WRITEREAD_BYTE 0xF2 /* /< Byte output version of I2C/SPI writeRead @see CMD_WRITEREAD */ +#define CMD_VERSION_BYTE 0xFA /* /< Byte output version of driver version and setting @see CMD_VERSION */ + +/** + * Possible actions that can be requested by an host + */ +typedef enum { + ACTION_WRITE = (u16) 0x0001, /* /< Bus Write */ + ACTION_READ = (u16) 0x0002, /* /< Bus Read */ + ACTION_WRITE_READ = (u16) 0x0003, /* /< Bus Write followed by a Read */ + ACTION_GET_VERSION = (u16) 0x0004, /* /< Get Version of the protocol (equal to the first 2 bye of driver version) */ +} Actions; + +/** + * Struct used to contain info of the message received by the host in + * Scriptless mode +*/ + +typedef struct { + u16 msg_size; /* /< total size of the message in bytes */ + u16 counter; /* /< counter ID to identify a message */ + Actions action; /* /< type of operation requested by the host see Actions */ + u8 dummy; /* /< (optional)in case of any kind of read operations, specify if the first byte is dummy */ +} Message; + +/** @}*/ + +static int limit; /* /< store the amount of data to print into the shell */ +static int chunk; /* /< store the chuk of data that should be printed in this iteration */ +static int printed; /* /< store the amount of data already printed in the shell */ +static struct proc_dir_entry *fts_dir; /* /< reference to the directory fts under /proc */ +static u8 *driver_test_buff; /* /< pointer to an array of bytes used to store the result of the function executed */ +char buf_chunk[CHUNK_PROC] = { 0 }; /* /< buffer used to store the message info received */ +static Message mess = { 0 }; /* /< store the information of the Scriptless message received */ + +/************************ SEQUENTIAL FILE UTILITIES **************************/ +/** + * This function is called at the beginning of the stream to a sequential file + * or every time into the sequential were already written PAGE_SIZE bytes and + * the stream need to restart + * @param s pointer to the sequential file on which print the data + * @param pos pointer to the offset where write the data + * @return NULL if there is no data to print or the pointer to the beginning of + * the data that need to be printed + */ +static void *fts_seq_start(struct seq_file *s, loff_t *pos) +{ + // pr_info("%s %s: Entering start(), pos = %ld limit = %d printed = %d\n", tag, __func__, *pos, limit, printed); + + if (driver_test_buff == NULL && *pos == 0) { + pr_info("%s %s: No data to print!\n", tag, __func__); + + driver_test_buff = (u8 *)kzalloc(13 * sizeof(u8), GFP_KERNEL); + + snprintf(driver_test_buff, 14, "{ %08X }\n", ERROR_OP_NOT_ALLOW); + + + limit = strlen(driver_test_buff); + /* pr_info("%s %s: len = %d driver_test_buff = %s\n", tag, __func__, limit, driver_test_buff); */ + } else { + if (*pos != 0) + *pos += chunk - 1; + + if (*pos >= limit) + /* pr_info("%s %s: Apparently, we're done.\n", tag, __func__); */ + return NULL; + } + + chunk = CHUNK_PROC; + if (limit - *pos < CHUNK_PROC) + chunk = limit - *pos; + + /* pr_info("%s %s: In start(), updated pos = %ld limit = %d printed = %d chunk = %d\n", tag, __func__, *pos, limit, printed, chunk); */ + memset(buf_chunk, 0, CHUNK_PROC); + memcpy(buf_chunk, &driver_test_buff[(int)*pos], chunk); + + return buf_chunk; +} + +/** + * This function actually print a chunk amount of data in the sequential file + * @param s pointer to the sequential file where to print the data + * @param v pointer to the data to print + * @return 0 + */ +static int fts_seq_show(struct seq_file *s, void *v) +{ + /* pr_info("%s %s: In show()\n", tag, __func__); */ + seq_write(s, (u8 *)v, chunk); + printed += chunk; + return 0; +} + +/** + * This function update the pointer and the counters to the next data to be + * printed + * @param s pointer to the sequential file where to print the data + * @param v pointer to the data to print + * @param pos pointer to the offset where write the next data + * @return NULL if there is no data to print or the pointer to the beginning of + * the next data that need to be printed + */ +static void *fts_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + /* int* val_ptr; */ + /* pr_info("%s %s: In next(), v = %X, pos = %ld.\n", tag, __func__, v, *pos); */ + (*pos) += chunk; /* increase my position counter */ + chunk = CHUNK_PROC; + + /* pr_info("%s %s: In next(), updated pos = %ld limit = %d printed = %d\n", tag, __func__, *pos, limit,printed); */ + if (*pos >= limit) /* are we done? */ + return NULL; + else if (limit - *pos < CHUNK_PROC) + chunk = limit - *pos; + + + memset(buf_chunk, 0, CHUNK_PROC); + memcpy(buf_chunk, &driver_test_buff[(int)*pos], chunk); + return buf_chunk; +} + + +/** + * This function is called when there are no more data to print the stream + *need to be terminated or when PAGE_SIZE data were already written into the + *sequential file + * @param s pointer to the sequential file where to print the data + * @param v pointer returned by fts_seq_next + */ +static void fts_seq_stop(struct seq_file *s, void *v) +{ + /* pr_info("%s %s: Entering stop().\n", tag, __func__); */ + + if (v) { + /* pr_info("%s %s: v is %X.\n", tag, __func__, v); */ + } else { + /* pr_info("%s %s: v is null.\n", tag, __func__); */ + limit = 0; + chunk = 0; + printed = 0; + if (driver_test_buff != NULL) { + /* pr_info("%s %s: Freeing and clearing driver_test_buff.\n", tag, __func__); */ + kfree(driver_test_buff); + driver_test_buff = NULL; + } else { + /* pr_info("%s %s: driver_test_buff is already null.\n", tag, __func__); */ + } + } +} + +/** + * Struct where define and specify the functions which implements the flow for + *writing on a sequential file + */ +static const struct seq_operations fts_seq_ops = { + .start = fts_seq_start, + .next = fts_seq_next, + .stop = fts_seq_stop, + .show = fts_seq_show +}; + +/** + * This function open a sequential file + * @param inode Inode in the file system that was called and triggered this + * function + * @param file file associated to the file node + * @return error code, 0 if success + */ +static int fts_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &fts_seq_ops); +}; + + +/*****************************************************************************/ + +/**************************** DRIVER TEST ************************************/ + +/** @addtogroup proc_file_code + * @{ + */ + +/** + * Receive the OP code and the inputs from shell when the file node is called, + * parse it and then execute the corresponding function + * echo cmd+parameters > /proc/fts/driver_test to execute the select command + * cat /proc/fts/driver_test to obtain the result into the + * shell \n + * the string returned in the shell is made up as follow: \n + * { = start byte \n + * the answer content and format strictly depend on the cmd executed. In + * general can be: an HEX string or a byte array (e.g in case of 0xF- commands) + * \n + * } = end byte \n + */ +static ssize_t fts_driver_test_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + int numberParam = 0; + char *p = NULL; + int res = -1, j, index = 0; + int size = 6; + int temp, byte_call = 0; + u16 byteToRead = 0; + u8 *readData = NULL; + u8 *pbuf = NULL; + u8 *cmd; + u32 *funcToTest; + + pbuf = kzalloc(count, GFP_KERNEL); + cmd = kzalloc(count, GFP_KERNEL); + funcToTest = kzalloc((((count + 1) / 3) * 4), GFP_KERNEL);; + + mess.dummy = 0; + mess.action = 0; + mess.msg_size = 0; + + if (access_ok(buf, count) < OK || copy_from_user(pbuf, buf, count) != 0) { + res = ERROR_ALLOC; + goto END; + } + + p = pbuf; + if (count > MESSAGE_MIN_HEADER_SIZE - 1 && p[0] == MESSAGE_START_BYTE) { + byte_call = 1; + mess.msg_size = (p[1] << 8) | p[2]; + mess.counter = (p[3] << 8) | p[4]; + mess.action = (p[5] << 8) | p[6]; + + size = MESSAGE_MIN_HEADER_SIZE + 2; /* +2 error code */ + if (count < mess.msg_size || p[count - 2] != MESSAGE_END_BYTE) { + pr_info("%s number of byte received or end byte wrong! msg_size = %d != %d, last_byte = %02X != %02X ... ERROR %08X\n", + tag, mess.msg_size, (int)count, p[count - 1], MESSAGE_END_BYTE, ERROR_OP_NOT_ALLOW); + res = ERROR_OP_NOT_ALLOW; + goto END; + } else { + numberParam = mess.msg_size - MESSAGE_MIN_HEADER_SIZE + 1; /* +1 because put the internal op code */ + size = MESSAGE_MIN_HEADER_SIZE + 2; /* +2 send also the first 2 lsb of the error code */ + switch (mess.action) { + case ACTION_WRITE: + cmd[0] = funcToTest[0] = CMD_WRITE_BYTE; + break; + case ACTION_WRITE_READ: + cmd[0] = funcToTest[0] = CMD_WRITEREAD_BYTE; + break; + case ACTION_GET_VERSION: + cmd[0] = funcToTest[0] = CMD_VERSION_BYTE; + break; + default: + pr_info("%s Invalid Action = %d ... ERROR %08X\n", tag, mess.action, ERROR_OP_NOT_ALLOW); + res = ERROR_OP_NOT_ALLOW; + goto END; + } + + if (numberParam - 1 != 0) + memcpy(&cmd[1], &p[7], numberParam - 1); + /* -1 because i need to exclude the cmd[0] */ + } + } else { + if (((count + 1) / 3) >= 1) { + if (sscanf(p, "%02X ", &funcToTest[0]) == 1) { + p += 3; + cmd[0] = (u8)funcToTest[0]; + numberParam = 1; + } + } else { + res = ERROR_OP_NOT_ALLOW; + goto END; + } + + for (; numberParam < (count + 1) / 3; numberParam++) { + if (sscanf(p, "%02X ", &funcToTest[numberParam]) == 1) { + p += 3; + cmd[numberParam] = (u8)funcToTest[numberParam]; + } + } + } + + /* elaborate input */ + if (numberParam >= 1) { + switch (funcToTest[0]) { + case CMD_VERSION_BYTE: + byteToRead = 2; + mess.dummy = 0; + readData = (u8 *)kzalloc(byteToRead * sizeof(u8), GFP_KERNEL); + if (!readData) { + pr_info("%s %s: error to alloc readData\n", tag, __func__); + return -ENOMEM; + } + size += byteToRead; + if (readData != NULL) { + readData[0] = (u8)(FTS_TS_DRV_VER >> 24); + readData[1] = (u8)(FTS_TS_DRV_VER >> 16); + res = OK; + pr_info("%s %s: Version = %02X%02X\n", tag, __func__, readData[0], readData[1]); + } else { + res = ERROR_ALLOC; + pr_info("%s %s: Impossible allocate memory... ERROR %08X\n", tag, __func__, res); + } + break; + case CMD_WRITEREAD_BYTE: + if (numberParam >= 5) { /* need to pass: cmd[0] cmd[1] … cmd[cmdLength-1] byteToRead1 byteToRead0 dummyByte */ + temp = numberParam - 4; + if (cmd[numberParam - 1] == 0) + mess.dummy = 0; + else + mess.dummy = 1; + + byteToRead = (u16)(((cmd[numberParam - 3 + 0] & 0x00FF) << 8) + (cmd[numberParam - 3 + 1] & 0x00FF)); + + readData = (u8 *)kzalloc((byteToRead + mess.dummy + 1) * sizeof(u8), GFP_KERNEL); + if (!readData) { + pr_info("%s %s: error to alloc readData\n", tag, __func__); + return -ENOMEM; + } + res = proc_ts->stm_ts_read(proc_ts, &cmd[1], temp, &readData[1], byteToRead + mess.dummy); + size += (byteToRead * sizeof(u8)); + } else { + pr_info("%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + case CMD_WRITE_BYTE: + if (numberParam >= 2) { /* need to pass: cmd[0] cmd[1] … cmd[cmdLength-1] */ + temp = numberParam - 1; + res = proc_ts->stm_ts_write(proc_ts, &cmd[1], temp, NULL, 0); + } else { + pr_info("%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + default: + pr_info("%s COMMAND ID NOT VALID!!!\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + } else { + pr_info("%s NO COMMAND SPECIFIED!!! do: 'echo [cmd_code] [args] > stm_fts_cmd' before looking for result!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + +END: /* here start the reporting phase, assembling the data to send in the + * file node */ + if (driver_test_buff != NULL) { + pr_info("%s Consecutive echo on the file node, free the buffer with the previous result\n", tag); + kfree(driver_test_buff); + } + + if (byte_call == 0) { + size *= 2; + size += 2; /* add \n and \0 (terminator char) */ + } else { + size *= 2; /* need to code each byte as HEX string */ + size -= 1; /* start byte is just one, the extra + * byte of end byte taken by \n */ + } + driver_test_buff = (u8 *)kzalloc(size, GFP_KERNEL); + + if (driver_test_buff == NULL) { + pr_info("%s Unable to allocate driver_test_buff! ERROR %08X\n", tag, ERROR_ALLOC); + goto ERROR; + } + /* start byte */ + driver_test_buff[index++] = MESSAGE_START_BYTE; + + snprintf(&driver_test_buff[index], 5, "%02X%02X", (size & 0xFF00) >> 8, size & 0xFF); + index += 4; + index += snprintf(&driver_test_buff[index], 5, "%04X", (u16)mess.counter); + index += snprintf(&driver_test_buff[index], 5, "%04X", (u16)mess.action); + index += snprintf(&driver_test_buff[index], 5, "%02X%02X", (res & 0xFF00) >> 8, res & 0xFF); + + switch (funcToTest[0]) { + case CMD_VERSION_BYTE: + case CMD_WRITEREAD_BYTE: + j = mess.dummy; + for (; j < byteToRead + mess.dummy; j++) + index += snprintf(&driver_test_buff[index], 3, "%02X", (u8)readData[j]); + break; + default: + break; + } + + driver_test_buff[index++] = MESSAGE_END_BYTE; + driver_test_buff[index] = '\n'; + + limit = size; + printed = 0; +ERROR: + numberParam = 0;/* need to reset the number of parameters in order to + * wait the next command, comment if you want to repeat + * the last comand sent just doing a cat */ + + /* pr_info("%s numberParameters = %d\n",tag, numberParam); */ + if (readData != NULL) + kfree(readData); + + kfree(pbuf); + kfree(cmd); + kfree(funcToTest); + + return count; +} + +/** @}*/ + +/** + * file_operations struct which define the functions for the canonical + *operation on a device file node (open. read, write etc.) + */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)) +static const struct proc_ops fts_driver_test_ops = { + .proc_open = fts_open, + .proc_read = seq_read, + .proc_write = fts_driver_test_write, + //.unlocked_ioctl = driver_ioctl, + .proc_lseek = seq_lseek, + .proc_release = seq_release +}; +#else +static const struct file_operations fts_driver_test_ops = { + .open = fts_open, + .read = seq_read, + .write = fts_driver_test_write, + //.unlocked_ioctl = driver_ioctl, + .llseek = seq_lseek, + .release = seq_release +}; +#endif + +/*****************************************************************************/ + +/** + * This function is called in the probe to initialize and create the directory + * proc/fts and the driver test file node DRIVER_TEST_FILE_NODE into the /proc + * file system + * @return OK if success or an error code which specify the type of error + */ +int stm_ts_tool_proc_init(struct stm_ts_data *ts) +{ + struct proc_dir_entry *entry; + + int retval = 0; + + proc_ts = (struct stm_ts_data *)ts; + + fts_dir = proc_mkdir_data("fts", 0777, NULL, NULL); + if (fts_dir == NULL) { /* directory creation failed */ + retval = -ENOMEM; + goto out; + } + + entry = proc_create(DRIVER_TEST_FILE_NODE, 0777, fts_dir, &fts_driver_test_ops); + + if (entry) + pr_info("%s %s: proc entry CREATED!\n", tag, __func__); + else { + pr_info("%s %s: error creating proc entry!\n", tag, __func__); + retval = -ENOMEM; + goto badfile; + } + return OK; +badfile: + remove_proc_entry("fts", NULL); +out: + return retval; +} + +/** + * Delete and Clean from the file system, all the references to the driver test + * file node + * @return OK + */ +int stm_ts_tool_proc_remove(void) +{ + remove_proc_entry(DRIVER_TEST_FILE_NODE, fts_dir); + remove_proc_entry("fts", NULL); + return OK; +} diff --git a/drivers/input/sec_input/synaptics/Kconfig b/drivers/input/sec_input/synaptics/Kconfig new file mode 100644 index 000000000000..295883826ed8 --- /dev/null +++ b/drivers/input/sec_input/synaptics/Kconfig @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# SYNAPTICS TOUCH driver configuration +# + +config TOUCHSCREEN_SYNAPTICS_SPI + tristate "SYNAPTICS SPI Touchscreen" + depends on SPI + help + Say Y here if you want support for SYNAPTICS SPI touchscreen controllers. + If unsure, say N. + +config TOUCHSCREEN_SYNAPTICS_I2C + tristate "SYNAPTICS I2C Touchscreen" + depends on I2C + help + Say Y here if you want support for SYNAPTICS I2C touchscreen controllers. + If unsure, say N. + diff --git a/drivers/input/sec_input/synaptics/Makefile b/drivers/input/sec_input/synaptics/Makefile new file mode 100644 index 000000000000..32a68e271c02 --- /dev/null +++ b/drivers/input/sec_input/synaptics/Makefile @@ -0,0 +1,17 @@ +TARGET_I2C = synaptics_ts_i2c +TARGET_SPI = synaptics_ts_spi + +$(TARGET_SPI)-objs := synaptics_spi.o synaptics_core.o synaptics_interrupt.o synaptics_fn.o synaptics_fw.o \ + synaptics_dump.o synaptics_cmd.o synaptics_ic_setting.o +$(TARGET_I2C)-objs := synaptics_i2c.o synaptics_core.o synaptics_interrupt.o synaptics_fn.o synaptics_fw.o \ + synaptics_dump.o synaptics_cmd.o synaptics_ic_setting.o + +ifneq ($(CONFIG_SAMSUNG_PRODUCT_SHIP), y) + $(TARGET_SPI)-objs += synaptics_vendor.o + $(TARGET_I2C)-objs += synaptics_vendor.o +endif + +obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_SPI) += $(TARGET_SPI).o +obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_I2C) += $(TARGET_I2C).o + +ccflags-y += -Wformat diff --git a/drivers/input/sec_input/synaptics/synaptics_base.h b/drivers/input/sec_input/synaptics/synaptics_base.h new file mode 100644 index 000000000000..61206059605d --- /dev/null +++ b/drivers/input/sec_input/synaptics/synaptics_base.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 + * Copyright (C) 2022 Samsung Electronics Co., Ltd. + * + * 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. + */ + +#ifndef _SYNAPTICS_TS_C_BASE_H_ +#define _SYNAPTICS_TS_C_BASE_H_ + +#endif diff --git a/drivers/input/sec_input/synaptics/synaptics_cmd.c b/drivers/input/sec_input/synaptics/synaptics_cmd.c new file mode 100644 index 000000000000..09e984a5f1f2 --- /dev/null +++ b/drivers/input/sec_input/synaptics/synaptics_cmd.c @@ -0,0 +1,3898 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Samsung Electronics Co., Ltd. + * + * 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. + */ + +#include "synaptics_dev.h" +#include "synaptics_reg.h" + +static void synaptics_ts_print_channel(struct synaptics_ts_data *ts, + struct synaptics_ts_buffer *tdata, int *min, int *max, u8 type) +{ + unsigned char *pStr = NULL; + unsigned char pTmp[16] = { 0 }; + int i = 0, j = 0, k = 0; + short *data_ptr = NULL; + int *data_ptr_int = NULL; + int lsize = PRINT_SIZE * (ts->cols + 1); + + if (!ts->cols) + return; + + switch (type) { + case TEST_PID18_HYBRID_ABS_RAW: /* int */ + data_ptr_int = (int *)&tdata->buf[0]; + for (i = 0; i < ts->rows + ts->cols; i++) { + ts->pFrame[i] = *data_ptr_int; + data_ptr_int++; + } + break; + default: /* short */ + data_ptr = (short *)&tdata->buf[0]; + for (i = 0; i < ts->rows + ts->cols; i++) { + ts->pFrame[i] = *data_ptr; + data_ptr++; + } + break; + } + + *min = *max = ts->pFrame[0]; + + pStr = vzalloc(lsize); + if (!pStr) + return; + + memset(pStr, 0x0, lsize); + snprintf(pTmp, sizeof(pTmp), " RX"); + strlcat(pStr, pTmp, lsize); + + for (k = 0; k < ts->cols; k++) { + snprintf(pTmp, sizeof(pTmp), " %02d", k); + strlcat(pStr, pTmp, lsize); + } + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", pStr); + + memset(pStr, 0x0, lsize); + snprintf(pTmp, sizeof(pTmp), " +"); + strlcat(pStr, pTmp, lsize); + + for (k = 0; k < ts->cols; k++) { + snprintf(pTmp, sizeof(pTmp), "------"); + strlcat(pStr, pTmp, lsize); + } + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", pStr); + + memset(pStr, 0x0, lsize); + snprintf(pTmp, sizeof(pTmp), " | "); + strlcat(pStr, pTmp, lsize); + + for (i = 0; i < ts->cols + ts->rows; i++) { + if (i == ts->cols) { + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", pStr); + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "\n"); + memset(pStr, 0x0, lsize); + snprintf(pTmp, sizeof(pTmp), " TX"); + strlcat(pStr, pTmp, lsize); + + for (k = 0; k < ts->rows; k++) { + snprintf(pTmp, sizeof(pTmp), " %02d", k); + strlcat(pStr, pTmp, lsize); + } + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", pStr); + + memset(pStr, 0x0, lsize); + snprintf(pTmp, sizeof(pTmp), " +"); + strlcat(pStr, pTmp, lsize); + + for (k = 0; k < ts->cols; k++) { + snprintf(pTmp, sizeof(pTmp), "------"); + strlcat(pStr, pTmp, lsize); + } + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", pStr); + + memset(pStr, 0x0, lsize); + snprintf(pTmp, sizeof(pTmp), " | "); + strlcat(pStr, pTmp, lsize); + } else if (i && !(i % ts->cols)) { + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", pStr); + memset(pStr, 0x0, lsize); + snprintf(pTmp, sizeof(pTmp), " | "); + strlcat(pStr, pTmp, lsize); + } + + snprintf(pTmp, sizeof(pTmp), " %5d", ts->pFrame[i]); + strlcat(pStr, pTmp, lsize); + + *min = min(*min, ts->pFrame[i]); + *max = max(*max, ts->pFrame[i]); + + j++; + } + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", pStr); + vfree(pStr); +} + +static void synaptics_ts_print_frame(struct synaptics_ts_data *ts, + struct synaptics_ts_buffer *tdata, int *min, int *max, u8 type) +{ + int i = 0; + int j = 0; + unsigned char *pStr = NULL; + unsigned char pTmp[16] = { 0 }; + int lsize = PRINT_SIZE * (ts->rows + 1); + short *data_ptr = NULL; + int *data_ptr_int = NULL; + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", __func__); + + switch (type) { + case TEST_PID18_HYBRID_ABS_RAW: /* int */ + data_ptr_int = (int *)&tdata->buf[0]; + for (i = 0; i < ts->rows; i++) { + for (j = 0; j < ts->cols; j++) { + ts->pFrame[i + (ts->rows * j)] = *data_ptr_int; + data_ptr_int++; + } + } + break; + default: /* short */ + data_ptr = (short *)&tdata->buf[0]; + for (i = 0; i < ts->rows; i++) { + for (j = 0; j < ts->cols; j++) { + ts->pFrame[i + (ts->rows * j)] = *data_ptr; + data_ptr++; + } + } + break; + } + + *min = *max = ts->pFrame[0]; + + pStr = kzalloc(lsize, GFP_KERNEL); + if (pStr == NULL) + return; + + memset(pStr, 0x0, lsize); + snprintf(pTmp, sizeof(pTmp), " TX"); + strlcat(pStr, pTmp, lsize); + + for (i = 0; i < ts->rows; i++) { + snprintf(pTmp, sizeof(pTmp), " %02d ", i); + strlcat(pStr, pTmp, lsize); + } + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", pStr); + memset(pStr, 0x0, lsize); + snprintf(pTmp, sizeof(pTmp), " +"); + strlcat(pStr, pTmp, lsize); + + for (i = 0; i < ts->rows; i++) { + snprintf(pTmp, sizeof(pTmp), "----"); + strlcat(pStr, pTmp, lsize); + } + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", pStr); + + for (i = 0; i < ts->cols; i++) { + memset(pStr, 0x0, lsize); + snprintf(pTmp, sizeof(pTmp), "RX%02d | ", i); + strlcat(pStr, pTmp, lsize); + + for (j = 0; j < ts->rows; j++) { + snprintf(pTmp, sizeof(pTmp), " %5d", ts->pFrame[j + (i * ts->rows)]); + + if (ts->pFrame[j + (i * ts->rows)] < *min) + *min = ts->pFrame[j + (i * ts->rows)]; + + if (ts->pFrame[j + (i * ts->rows)] > *max) + *max = ts->pFrame[j + (i * ts->rows)]; + + strlcat(pStr, pTmp, lsize); + } + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s\n", pStr); + } + kfree(pStr); +} + +static int get_self_channel_data(void *device_data, u8 type) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char buff[16] = { 0 }; + int ii; + int tx_min = 0; + int rx_min = 0; + int tx_max = 0; + int rx_max = 0; + char *item_name = "NULL"; + char temp[SEC_CMD_STR_LEN] = { 0 }; + + switch (type) { + case TEST_PID05_FULL_RAW_CAP: + item_name = "FULL_RAWCAP"; /* short * */ + break; + case TEST_PID10_DELTA_NOISE: + item_name = "DELTA_NOISE"; /* short * */ + break; + case TEST_PID18_HYBRID_ABS_RAW: /* int */ + item_name = "ABS_RAW"; + break; + case TEST_PID22_TRANS_RAW_CAP: /* short */ + item_name = "TRANS_RAW"; + break; + case TEST_PID29_HYBRID_ABS_NOISE: /* short */ + item_name = "ABS_NOISE"; + break; + case TEST_PID65_MISCALDATA_NORMAL: /* short */ + item_name = "MISCALDATA_NORMAL"; + break; + } + + for (ii = 0; ii < ts->cols; ii++) { + if (ii == 0) + tx_min = tx_max = ts->pFrame[ii]; + + tx_min = min(tx_min, ts->pFrame[ii]); + tx_max = max(tx_max, ts->pFrame[ii]); + } + snprintf(buff, sizeof(buff), "%d,%d", tx_min, tx_max); + snprintf(temp, sizeof(temp), "%s%s", item_name, "_RX"); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, sizeof(buff), temp); + + memset(temp, 0x00, SEC_CMD_STR_LEN); + + for (ii = ts->cols; ii < ts->cols + ts->rows; ii++) { + if (ii == ts->cols) + rx_min = rx_max = ts->pFrame[ii]; + + rx_min = min(rx_min, ts->pFrame[ii]); + rx_max = max(rx_max, ts->pFrame[ii]); + } + snprintf(buff, sizeof(buff), "%d,%d", rx_min, rx_max); + snprintf(temp, sizeof(temp), "%s%s", item_name, "_TX"); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, sizeof(buff), temp); + + return 0; +} + +/** + * syna_tcm_run_production_test() + * + * Implement the appplication fw command code to request the device to run + * the production test. + * + * Production tests are listed at enum test_code (PID$). + * + * @param + * [ in] tcm_dev: the device handle + * [ in] test_item: the requested testing item + * [out] tdata: testing data returned + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int synaptics_ts_run_production_test(struct synaptics_ts_data *ts, + unsigned char test_item, struct synaptics_ts_buffer *tdata) +{ + int retval = 0; + unsigned char resp_code; + unsigned char test_code; + + if (IS_NOT_APP_FW_MODE(ts->dev_mode)) { + input_err(true, ts->dev, "%s: Device is not in application fw mode, mode: %x\n", __func__, + ts->dev_mode); + return -EINVAL; + } + + test_code = (unsigned char)test_item; + + retval = ts->write_message(ts, + SYNAPTICS_TS_CMD_PRODUCTION_TEST, + &test_code, + 1, + &resp_code, + FORCE_ATTN_DRIVEN); + if (retval < 0) { + input_err(true, ts->dev, "Fail to send command 0x%02x\n", + SYNAPTICS_TS_CMD_PRODUCTION_TEST); + goto exit; + } + + if (tdata == NULL) + goto exit; + + /* copy testing data to caller */ + retval = synaptics_ts_buf_copy(tdata, &ts->resp_buf); + if (retval < 0) { + input_err(true, ts->dev, "Fail to copy testing data\n"); + goto exit; + } + +exit: + return retval; +} + +static void synaptics_ts_test_rawdata_read(struct synaptics_ts_data *ts, + struct sec_cmd_data *sec, struct synaptics_ts_test_mode *mode) +{ + struct synaptics_ts_buffer test_data; + char temp[SEC_CMD_STR_LEN] = { 0 }; + char *buff; + char *item_name = "NULL"; + short rawcap[2] = {SHRT_MAX, SHRT_MIN}; /* min, max */ + short rawcap_edge[2] = {SHRT_MAX, SHRT_MIN}; /* min, max */ + int ret; + int ii, jj; + + switch (mode->type) { + case TEST_PID05_FULL_RAW_CAP: + item_name = "FULL_RAWCAP"; /* short * */ + break; + case TEST_PID10_DELTA_NOISE: + item_name = "FULL_NOISE"; /* short * */ + break; + case TEST_PID18_HYBRID_ABS_RAW: /* int */ + item_name = "ABS_RAW"; + break; + case TEST_PID22_TRANS_RAW_CAP: /* short */ + item_name = "TRANS_RAW"; + break; + case TEST_PID29_HYBRID_ABS_NOISE: /* short */ + item_name = "ABS_NOISE"; + break; + case TEST_PID65_MISCALDATA_NORMAL: /* short */ + item_name = "MISCALDATA_NORMAL"; + break; + default: + break; + } + + input_err(true, ts->dev, "%s: [%s] test\n", __func__, item_name); + + buff = kzalloc(ts->cols * ts->rows * CMD_RESULT_WORD_LEN, GFP_KERNEL); + if (!buff) + goto error_alloc_mem; + + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: [ERROR] Touch is stopped\n", + __func__); + goto error_power_state; + } + + synaptics_ts_buf_init(&test_data); + memset(ts->pFrame, 0x00, ts->cols * ts->rows * sizeof(int)); + + ret = synaptics_ts_run_production_test(ts, mode->type, &test_data); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to test\n", + __func__); + goto error_test_fail; + } + + if (mode->frame_channel) + synaptics_ts_print_channel(ts, &test_data, &mode->min, &mode->max, mode->type); + else + synaptics_ts_print_frame(ts, &test_data, &mode->min, &mode->max, mode->type); + + if (mode->allnode) { + if (mode->frame_channel) { + for (ii = 0; ii < (ts->cols + ts->rows); ii++) { + snprintf(temp, CMD_RESULT_WORD_LEN, "%d,", ts->pFrame[ii]); + strlcat(buff, temp, ts->cols * ts->rows * CMD_RESULT_WORD_LEN); + + memset(temp, 0x00, SEC_CMD_STR_LEN); + } + } else { + for (ii = 0; ii < (ts->cols * ts->rows); ii++) { + snprintf(temp, CMD_RESULT_WORD_LEN, "%d,", ts->pFrame[ii]); + strlcat(buff, temp, ts->cols * ts->rows * CMD_RESULT_WORD_LEN); + + memset(temp, 0x00, SEC_CMD_STR_LEN); + } + } + } else { + snprintf(buff, SEC_CMD_STR_LEN, "%d,%d", mode->min, mode->max); + } + + if (!sec) + goto out_rawdata; + + if (mode->type == TEST_PID22_TRANS_RAW_CAP && !mode->frame_channel) { + char rawcap_buff[SEC_CMD_STR_LEN]; + + for (ii = 0; ii < ts->cols; ii++) { + for (jj = 0; jj < ts->rows; jj++) { + short *rawcap_ptr; + + if (ii == 0 || ii == ts->cols - 1 || jj == 0 || jj == ts->rows - 1) + rawcap_ptr = rawcap_edge; + else + rawcap_ptr = rawcap; + + if (ts->pFrame[jj + (ii * ts->rows)] < rawcap_ptr[0]) + rawcap_ptr[0] = ts->pFrame[jj + (ii * ts->rows)]; + if (ts->pFrame[jj + (ii * ts->rows)] > rawcap_ptr[1]) + rawcap_ptr[1] = ts->pFrame[jj + (ii * ts->rows)]; + } + } + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, + "%s: rawcap:%d,%d rawcap_edge:%d,%d\n", + __func__, rawcap[0], rawcap[1], rawcap_edge[0], rawcap_edge[1]); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) { + snprintf(rawcap_buff, SEC_CMD_STR_LEN, "%d,%d", rawcap[0], rawcap[1]); + sec_cmd_set_cmd_result_all(sec, rawcap_buff, SEC_CMD_STR_LEN, "TSP_RAWCAP"); + snprintf(rawcap_buff, SEC_CMD_STR_LEN, "%d,%d", rawcap_edge[0], rawcap_edge[1]); + sec_cmd_set_cmd_result_all(sec, rawcap_buff, SEC_CMD_STR_LEN, "TSP_RAWCAP_EDGE"); + } + } + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, ts->cols * ts->rows * CMD_RESULT_WORD_LEN)); + + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, + strnlen(buff, ts->cols * ts->rows * CMD_RESULT_WORD_LEN), item_name); + + sec->cmd_state = SEC_CMD_STATUS_OK; + + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s: %d, %s\n", + __func__, mode->type, mode->allnode ? "ALL" : ""); + +out_rawdata: + synaptics_ts_buf_release(&test_data); + kfree(buff); + + synaptics_ts_locked_release_all_finger(ts); + + return; +error_test_fail: + synaptics_ts_buf_release(&test_data); +error_power_state: + kfree(buff); +error_alloc_mem: + if (!sec) + return; + + snprintf(temp, SEC_CMD_STR_LEN, "NG"); + sec_cmd_set_cmd_result(sec, temp, SEC_CMD_STR_LEN); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, temp, SEC_CMD_STR_LEN, item_name); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + + synaptics_ts_locked_release_all_finger(ts); + +} + +/**************************************************************************************************/ +static ssize_t scrub_pos_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char buff[256] = { 0 }; + +#if IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP) + input_info(true, ts->dev, + "%s: id: %d\n", __func__, ts->plat_data->gesture_id); +#else + input_info(true, ts->dev, + "%s: id: %d, X:%d, Y:%d\n", __func__, + ts->plat_data->gesture_id, ts->plat_data->gesture_x, ts->plat_data->gesture_y); +#endif + snprintf(buff, sizeof(buff), "%d %d %d", ts->plat_data->gesture_id, + ts->plat_data->gesture_x, ts->plat_data->gesture_y); + + ts->plat_data->gesture_x = 0; + ts->plat_data->gesture_y = 0; + + return snprintf(buf, PAGE_SIZE, "%s", buff); +} + +/* for bigdata */ +/* read param */ +static ssize_t hw_param_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char buff[SEC_INPUT_HW_PARAM_SIZE]; + char tbuff[SEC_CMD_STR_LEN]; + char mdev[SEC_CMD_STR_LEN]; + + memset(mdev, 0x00, sizeof(mdev)); + if (GET_DEV_COUNT(ts->multi_dev) == MULTI_DEV_SUB) + snprintf(mdev, sizeof(mdev), "%s", "2"); + else + snprintf(mdev, sizeof(mdev), "%s", ""); + + memset(buff, 0x00, sizeof(buff)); + + sec_input_get_common_hw_param(ts->plat_data, buff); + + /* module_id */ + memset(tbuff, 0x00, sizeof(tbuff)); + + snprintf(tbuff, sizeof(tbuff), ",\"TMOD%s\":\"SY%02X%02X%02X%c%01X\"", + mdev, ts->plat_data->img_version_of_bin[1], ts->plat_data->img_version_of_bin[2], + ts->plat_data->img_version_of_bin[3], +#ifdef TCLM_CONCEPT + ts->tdata->tclm_string[ts->tdata->nvdata.cal_position].s_name, + ts->tdata->nvdata.cal_count & 0xF); +#else + '0', 0); +#endif + strlcat(buff, tbuff, sizeof(buff)); + + /* vendor_id */ + memset(tbuff, 0x00, sizeof(tbuff)); + if (ts->plat_data->img_version_of_ic[0] == 0x31) + snprintf(tbuff, sizeof(tbuff), ",\"TVEN%s\":\"SYNA_S3908\"", mdev); + else if (ts->plat_data->img_version_of_ic[0] == 0x32) + snprintf(tbuff, sizeof(tbuff), ",\"TVEN%s\":\"SYNA_S3907\"", mdev); + else if (ts->plat_data->img_version_of_ic[0] == 0x33) + snprintf(tbuff, sizeof(tbuff), ",\"TVEN%s\":\"SYNA_S3916\"", mdev); + else if (ts->plat_data->img_version_of_ic[0] == 0x34) + snprintf(tbuff, sizeof(tbuff), ",\"TVEN%s\":\"SYNA_S3908S\"", mdev); + else + snprintf(tbuff, sizeof(tbuff), ",\"TVEN%s\":\"SYNA\"", mdev); + + strlcat(buff, tbuff, sizeof(buff)); + + input_info(true, ts->dev, "%s: %s\n", __func__, buff); + + return snprintf(buf, SEC_CMD_BUF_SIZE, "%s", buff); +} + +/* clear param */ +static ssize_t hw_param_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + sec_input_clear_common_hw_param(ts->plat_data); + + return count; +} + +#define SENSITIVITY_POINT_CNT 9 /* ~ davinci : 5 ea => 9 ea */ +static ssize_t sensitivity_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int value[SENSITIVITY_POINT_CNT] = { 0 }; + int ret, i; + char tempv[10] = { 0 }; + char buff[SENSITIVITY_POINT_CNT * 10] = { 0 }; + struct synaptics_ts_buffer test_data; + unsigned short *data_ptr = NULL; + + synaptics_ts_buf_init(&test_data); + + ret = synaptics_ts_run_production_test(ts, + TEST_PID60_SENSITIVITY, + &test_data); + if (ret < 0) { + input_err(true, ts->dev, "Fail to run test %d\n", + TEST_PID54_BSC_DIFF_TEST); + synaptics_ts_buf_release(&test_data); + return ret; + } + + data_ptr = (short *)&test_data.buf[0]; + for (i = 0; i < SENSITIVITY_POINT_CNT; i++) { + value[i] = *data_ptr; + if (i != 0) + strlcat(buff, ",", sizeof(buff)); + snprintf(tempv, 10, "%d", value[i]); + strlcat(buff, tempv, sizeof(buff)); + data_ptr++; + } + + input_info(true, ts->dev, "%s: sensitivity mode : %s\n", __func__, buff); + synaptics_ts_buf_release(&test_data); + + return snprintf(buf, SEC_CMD_BUF_SIZE, "%s", buff); +} + +static ssize_t sensitivity_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret; + unsigned long value = 0; + + if (count > 2) + return -EINVAL; + + ret = kstrtoul(buf, 10, &value); + if (ret != 0) + return ret; + + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: power off in IC\n", __func__); + return 0; + } + + input_err(true, ts->dev, "%s: enable:%ld\n", __func__, value); + + input_info(true, ts->dev, "%s: done\n", __func__); + + return count; +} + +ssize_t get_lp_dump_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + u8 string_data[10] = {0, }; + u16 current_index; + u16 dump_start, dump_end, dump_cnt; + int i, ret, dump_area, dump_gain, block_cnt, block_size; + unsigned char *sec_spg_dat; + unsigned char *r_buf = NULL; + int size[2] = { 0 }; + int read_offset[2] = { 0 }; + u8 data = 0; + u16 string_addr; + char dev_buff[10] = {0, }; + + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: Touch is stopped!\n", __func__); + if (buf) + return snprintf(buf, SEC_CMD_BUF_SIZE, "TSP turned off"); + else + return 0; + } + + if (atomic_read(&ts->reset_is_on_going)) { + input_err(true, ts->dev, "%s: Reset is ongoing!\n", __func__); + if (buf) + return snprintf(buf, SEC_CMD_BUF_SIZE, "Reset is ongoing"); + else + return 0; + } + + if (IS_FOLD_DEV(ts->multi_dev)) + snprintf(dev_buff, sizeof(dev_buff), "%4s: ", ts->multi_dev->name); + else + snprintf(dev_buff, sizeof(dev_buff), ""); + + /* preparing dump buffer */ + sec_spg_dat = vmalloc(SEC_TS_MAX_SPONGE_DUMP_BUFFER); + if (!sec_spg_dat) { + if (buf) + return snprintf(buf, SEC_CMD_BUF_SIZE, "vmalloc failed"); + else + return 0; + } + memset(sec_spg_dat, 0, SEC_TS_MAX_SPONGE_DUMP_BUFFER); + + ret = ts->synaptics_ts_read_sponge(ts, string_data, 2, SEC_TS_CMD_SPONGE_LP_DUMP_CUR_IDX, 2); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to read rect\n", __func__); + if (buf) + snprintf(buf, SEC_CMD_BUF_SIZE, "NG, Failed to read rect"); + goto out; + } + + if (ts->sponge_inf_dump) + dump_gain = 2; + else + dump_gain = 1; + + current_index = (string_data[1] & 0xFF) << 8 | (string_data[0] & 0xFF); + dump_start = SEC_TS_CMD_SPONGE_LP_DUMP_EVENT; + dump_end = dump_start + (ts->sponge_dump_format * ((ts->sponge_dump_event * dump_gain) - 1)); + + if (current_index == 0) { + input_info(true, ts->dev, "%s: length 0\n", __func__); + if (buf) + snprintf(buf, SEC_CMD_BUF_SIZE, "length 0"); + goto out; + } + + if (current_index > dump_end || current_index < dump_start) { + input_err(true, ts->dev, + "Failed to Sponge LP log %d\n", current_index); + if (buf) + snprintf(buf, SEC_CMD_BUF_SIZE, + "NG, Failed to Sponge LP log, current_index=%d", + current_index); + goto out; + } + + /* legacy get_lp_dump */ + input_info(true, ts->dev, "%s: DEBUG format=%d, num=%d, start=%d, end=%d, current_index=%d\n", + __func__, ts->sponge_dump_format, ts->sponge_dump_event, dump_start, dump_end, current_index); + + size[0] = dump_end - current_index; + size[1] = (current_index + ts->sponge_dump_format) - dump_start; + read_offset[0] = current_index + ts->sponge_dump_format; + read_offset[1] = dump_start; + block_size = max(size[0], size[1]) + 1; + + r_buf = kzalloc(block_size, GFP_KERNEL); + if (!r_buf) { + if (buf) + snprintf(buf, SEC_CMD_BUF_SIZE, "alloc r_buf failed, block_size:%d", block_size); + goto out; + } + + for (block_cnt = 0; block_cnt < 2; block_cnt++) { + input_info(true, ts->dev, "%s: block%d - size:%d, read_offset:%d\n", + __func__, block_cnt, size[block_cnt], read_offset[block_cnt]); + + if (size[block_cnt] == 0) + continue; + + memset(r_buf, 0x0, block_size); + + ret = ts->synaptics_ts_read_sponge(ts, r_buf, block_size, read_offset[block_cnt], size[block_cnt]); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to read data for block%d, size:%d, from %d\n", + __func__, block_cnt, size[block_cnt], read_offset[block_cnt]); + if (buf) + snprintf(buf, SEC_CMD_BUF_SIZE, "NG, Failed to read data for block%d, size:%d, from %d", + block_cnt, size[block_cnt], read_offset[block_cnt]); + goto out; + } + + string_addr = read_offset[block_cnt]; + for (i = string_addr; i < size[block_cnt] + read_offset[block_cnt]; i += ts->sponge_dump_format) { + char buff[40] = {0, }; + u16 data0, data1, data2, data3, data4; + + data0 = (r_buf[i + 1 - string_addr] & 0xFF) << 8 | (r_buf[i + 0 - string_addr] & 0xFF); + data1 = (r_buf[i + 3 - string_addr] & 0xFF) << 8 | (r_buf[i + 2 - string_addr] & 0xFF); + data2 = (r_buf[i + 5 - string_addr] & 0xFF) << 8 | (r_buf[i + 4 - string_addr] & 0xFF); + data3 = (r_buf[i + 7 - string_addr] & 0xFF) << 8 | (r_buf[i + 6 - string_addr] & 0xFF); + data4 = (r_buf[i + 9 - string_addr] & 0xFF) << 8 | (r_buf[i + 8 - string_addr] & 0xFF); + + if (data0 || data1 || data2 || data3 || data4) { + if (ts->sponge_dump_format == 10) { + snprintf(buff, sizeof(buff), + "%s%03d: %04x%04x%04x%04x%04x\n", + dev_buff, i, data0, data1, data2, data3, data4); + } else { + snprintf(buff, sizeof(buff), + "%s%03d: %04x%04x%04x%04x\n", + dev_buff, i, data0, data1, data2, data3); + } + if (buf) + strlcat(buf, buff, PAGE_SIZE); +#if IS_ENABLED(CONFIG_SEC_DEBUG_TSP_LOG) + if (!ts->sponge_inf_dump) + sec_tsp_sponge_log(buff); +#endif + } + } + } + + if (ts->sponge_inf_dump) { + if (current_index >= ts->sponge_dump_border) { + dump_cnt = ((current_index - (ts->sponge_dump_border)) / ts->sponge_dump_format) + 1; + dump_area = 1; + sec_spg_dat[0] = ts->sponge_dump_border_lsb; + sec_spg_dat[1] = ts->sponge_dump_border_msb; + } else { + dump_cnt = ((current_index - SEC_TS_CMD_SPONGE_LP_DUMP_EVENT) / ts->sponge_dump_format) + 1; + dump_area = 0; + sec_spg_dat[0] = SEC_TS_CMD_SPONGE_LP_DUMP_EVENT; + sec_spg_dat[1] = 0; + } + + ret = ts->synaptics_ts_read_sponge(ts, sec_spg_dat, dump_cnt * ts->sponge_dump_format, + (sec_spg_dat[1] & 0xFF) << 8 | (sec_spg_dat[0] & 0xFF), dump_cnt * ts->sponge_dump_format); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to read sponge\n", __func__); + goto out; + } + + for (i = 0 ; i <= dump_cnt ; i++) { + int e_offset = i * ts->sponge_dump_format; + char ibuff[40] = {0, }; + u16 edata[5]; + + edata[0] = (sec_spg_dat[1 + e_offset] & 0xFF) << 8 | (sec_spg_dat[0 + e_offset] & 0xFF); + edata[1] = (sec_spg_dat[3 + e_offset] & 0xFF) << 8 | (sec_spg_dat[2 + e_offset] & 0xFF); + edata[2] = (sec_spg_dat[5 + e_offset] & 0xFF) << 8 | (sec_spg_dat[4 + e_offset] & 0xFF); + edata[3] = (sec_spg_dat[7 + e_offset] & 0xFF) << 8 | (sec_spg_dat[6 + e_offset] & 0xFF); + edata[4] = (sec_spg_dat[9 + e_offset] & 0xFF) << 8 | (sec_spg_dat[8 + e_offset] & 0xFF); + + if (edata[0] || edata[1] || edata[2] || edata[3] || edata[4]) { + snprintf(ibuff, sizeof(ibuff), "%s%03d: %04x%04x%04x%04x%04x\n", + dev_buff, i + (ts->sponge_dump_event * dump_area), + edata[0], edata[1], edata[2], edata[3], edata[4]); +#if IS_ENABLED(CONFIG_SEC_DEBUG_TSP_LOG) + sec_tsp_sponge_log(ibuff); +#endif + } + } + + ts->sponge_dump_delayed_flag = false; + data = ts->plat_data->sponge_mode |= SEC_TS_MODE_SPONGE_INF_DUMP_CLEAR; + ret = ts->synaptics_ts_write_sponge(ts, &data, 1, 0x01, 1); + if (ret < 0) + input_err(true, ts->dev, "%s: Failed to clear sponge dump\n", __func__); + ts->plat_data->sponge_mode &= ~SEC_TS_MODE_SPONGE_INF_DUMP_CLEAR; + } +out: + vfree(sec_spg_dat); + if (r_buf) + kfree(r_buf); + if (buf) + return strlen(buf); + else + return 0; +} + +static ssize_t fod_pos_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char buff[3] = { 0 }; + int i, ret; + + if (!ts->plat_data->support_fod) { + input_err(true, ts->dev, "%s: fod is not supported\n", __func__); + return snprintf(buf, SEC_CMD_BUF_SIZE, "NA"); + } + + if (!ts->plat_data->fod_data.vi_size) { + input_err(true, ts->dev, "%s: not read fod_info yet\n", __func__); + return snprintf(buf, SEC_CMD_BUF_SIZE, "NG"); + } + + if (ts->plat_data->fod_data.vi_event == CMD_SYSFS) { + ret = ts->synaptics_ts_read_sponge(ts, ts->plat_data->fod_data.vi_data, ts->plat_data->fod_data.vi_size, + SEC_TS_CMD_SPONGE_FOD_POSITION, ts->plat_data->fod_data.vi_size); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to read\n", __func__); + return snprintf(buf, SEC_CMD_BUF_SIZE, "NG"); + } + } + + for (i = 0; i < ts->plat_data->fod_data.vi_size; i++) { + snprintf(buff, 3, "%02X", ts->plat_data->fod_data.vi_data[i]); + strlcat(buf, buff, SEC_CMD_BUF_SIZE); + } + + return strlen(buf); +} + +static ssize_t fod_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + return sec_input_get_fod_info(ts->dev, buf); +} + +static ssize_t aod_active_area_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + input_info(true, ts->dev, "%s: top:%d, edge:%d, bottom:%d\n", + __func__, ts->plat_data->aod_data.active_area[0], + ts->plat_data->aod_data.active_area[1], ts->plat_data->aod_data.active_area[2]); + + return snprintf(buf, SEC_CMD_BUF_SIZE, "%d,%d,%d", + ts->plat_data->aod_data.active_area[0], ts->plat_data->aod_data.active_area[1], + ts->plat_data->aod_data.active_area[2]); +} + +static ssize_t virtual_prox_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int retval; + + retval = (ts->hover_event != 3) ? 0 : 3; + + input_info(true, ts->dev, "%s: hover_event: %d, retval: %d\n", __func__, ts->hover_event, retval); + + return snprintf(buf, SEC_CMD_BUF_SIZE, "%d", retval); +} + +static ssize_t virtual_prox_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret; + u8 data; + + if (!ts->plat_data->support_ear_detect) { + input_err(true, ts->dev, "%s: ear detection is not supported\n", __func__); + return -ENODEV; + } + + ret = kstrtou8(buf, 8, &data); + if (ret < 0) + return ret; + + if (data != 0 && data != 1 && data != 3) { + input_err(true, ts->dev, "%s: %d is wrong param\n", __func__, data); + return -EINVAL; + } + + ts->plat_data->ed_enable = data; + synaptics_ts_ear_detect_enable(ts, ts->plat_data->ed_enable); + input_info(true, ts->dev, "%s: %d\n", __func__, data); + + return count; +} + +static ssize_t debug_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char buff[INPUT_DEBUG_INFO_SIZE]; + char tbuff[128]; + + if (sizeof(tbuff) > INPUT_DEBUG_INFO_SIZE) + input_info(true, ts->dev, "%s: buff_size[%d], tbuff_size[%ld]\n", + __func__, INPUT_DEBUG_INFO_SIZE, sizeof(tbuff)); + + memset(buff, 0x00, sizeof(buff)); + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "FW: (I)%02X%02X%02X%02X / (B)%02X%02X%02X%02X\n", + ts->plat_data->img_version_of_ic[0], ts->plat_data->img_version_of_ic[1], + ts->plat_data->img_version_of_ic[2], ts->plat_data->img_version_of_ic[3], + ts->plat_data->img_version_of_bin[0], ts->plat_data->img_version_of_bin[1], + ts->plat_data->img_version_of_bin[2], ts->plat_data->img_version_of_bin[3]); + strlcat(buff, tbuff, sizeof(buff)); + +#ifdef TCLM_CONCEPT + if (ts->tdata->tclm_level != TCLM_LEVEL_NOT_SUPPORT) { + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "C%02XT%04X.%4s%s Cal_flag:%d fail_cnt:%d\n", + ts->tdata->nvdata.cal_count, ts->tdata->nvdata.tune_fix_ver, + ts->tdata->tclm_string[ts->tdata->nvdata.cal_position].f_name, + (ts->tdata->tclm_level == TCLM_LEVEL_LOCKDOWN) ? ".L" : " ", + ts->tdata->nvdata.cal_fail_falg, ts->tdata->nvdata.cal_fail_cnt); + strlcat(buff, tbuff, sizeof(buff)); + } +#endif + + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "ic_reset_count %d\nchecksum bit %d\n", + ts->plat_data->hw_param.ic_reset_count, + ts->plat_data->hw_param.checksum_result); + strlcat(buff, tbuff, sizeof(buff)); + + sec_input_get_multi_device_debug_info(ts->multi_dev, buff, sizeof(buff)); + + input_info(true, ts->dev, "%s: size: %ld\n", __func__, strlen(buff)); + + return snprintf(buf, SEC_CMD_BUF_SIZE, "%s", buff); +} + +static DEVICE_ATTR_RO(scrub_pos); +static DEVICE_ATTR_RW(hw_param); +static DEVICE_ATTR_RO(get_lp_dump); +static DEVICE_ATTR_RW(sensitivity_mode); +static DEVICE_ATTR_RO(fod_pos); +static DEVICE_ATTR_RO(fod_info); +static DEVICE_ATTR_RO(aod_active_area); +static DEVICE_ATTR_RW(virtual_prox); +static DEVICE_ATTR_RO(debug_info); + +static struct attribute *cmd_attributes[] = { + &dev_attr_scrub_pos.attr, + &dev_attr_hw_param.attr, + &dev_attr_get_lp_dump.attr, + &dev_attr_sensitivity_mode.attr, + &dev_attr_fod_pos.attr, + &dev_attr_fod_info.attr, + &dev_attr_aod_active_area.attr, + &dev_attr_virtual_prox.attr, + &dev_attr_debug_info.attr, + NULL, +}; + +static struct attribute_group cmd_attr_group = { + .attrs = cmd_attributes, +}; + +static void fw_update(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int retval = 0; + + mutex_lock(&ts->plat_data->enable_mutex); + retval = synaptics_ts_fw_update_on_hidden_menu(ts, sec->cmd_param[0]); + if (retval < 0) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + input_err(true, ts->dev, "%s: failed [%d]\n", __func__, retval); + } else { + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: success [%d]\n", __func__, retval); + } + mutex_unlock(&ts->plat_data->enable_mutex); +} + +static void get_fw_ver_bin(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char buff[16] = { 0 }; + + snprintf(buff, sizeof(buff), "SY%02X%02X%02X%02X", + ts->plat_data->img_version_of_bin[0], ts->plat_data->img_version_of_bin[1], + ts->plat_data->img_version_of_bin[2], ts->plat_data->img_version_of_bin[3]); + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "FW_VER_BIN"); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void get_fw_ver_ic(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char buff[16] = { 0 }; + char model[16] = { 0 }; + int ret; + int idx; + + ret = synaptics_ts_get_app_info(ts, &ts->app_info); + if (ret < 0) { + input_err(true, ts->dev, "%s: Fail to get application info\n", __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + for (idx = 0; idx < 4; idx++) + ts->plat_data->img_version_of_ic[idx] = ts->config_id[idx]; + + snprintf(buff, sizeof(buff), "SY%02X%02X%02X%02X", + ts->plat_data->img_version_of_ic[0], ts->plat_data->img_version_of_ic[1], + ts->plat_data->img_version_of_ic[2], ts->plat_data->img_version_of_ic[3]); + snprintf(model, sizeof(model), "SY%02X%02X", + ts->plat_data->img_version_of_ic[0], ts->plat_data->img_version_of_ic[1]); + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) { + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "FW_VER_IC"); + sec_cmd_set_cmd_result_all(sec, model, strnlen(model, sizeof(model)), "FW_MODEL"); + } + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void get_chip_vendor(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char buff[16] = { 0 }; + + strncpy(buff, "SYNAPTICS", sizeof(buff)); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "IC_VENDOR"); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void get_chip_name(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char buff[16] = { 0 }; + + if (ts->plat_data->img_version_of_ic[0] == 0x31) + strncpy(buff, "S3908", sizeof(buff)); + else if (ts->plat_data->img_version_of_ic[0] == 0x32) + strncpy(buff, "S3907", sizeof(buff)); + else if (ts->plat_data->img_version_of_ic[0] == 0x33) + strncpy(buff, "S3916A", sizeof(buff)); + else if (ts->plat_data->img_version_of_ic[0] == 0x34) + strncpy(buff, "S3908S", sizeof(buff)); + else if (ts->plat_data->img_version_of_ic[0] == 0x35) + strncpy(buff, "S3916T", sizeof(buff)); + else + strncpy(buff, "N/A", sizeof(buff)); + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "IC_NAME"); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void get_x_num(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char buff[16] = { 0 }; + + snprintf(buff, sizeof(buff), "%d", ts->rows); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void get_y_num(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char buff[16] = { 0 }; + + snprintf(buff, sizeof(buff), "%d", ts->cols); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void module_off_master(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret = 0; + + ret = ts->plat_data->stop_device(ts); + if (ret == 0) + sec->cmd_state = SEC_CMD_STATUS_OK; + else + sec->cmd_state = SEC_CMD_STATUS_FAIL; + + input_info(true, ts->dev, "%s: %d\n", __func__, ret); +} + +static void module_on_master(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret = 0; + + ret = ts->plat_data->start_device(ts); + if (ret == 0) + sec->cmd_state = SEC_CMD_STATUS_OK; + else + sec->cmd_state = SEC_CMD_STATUS_FAIL; + + ts->plat_data->init(ts); + + input_info(true, ts->dev, "%s: %d\n", __func__, ret); +} + +static void get_crc_check(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + struct synaptics_ts_message_data_blob *tcm_msg = NULL; + unsigned char resp_code; + int ret; + + ret = ts->write_message(ts, + SYNAPTICS_TS_CMD_IDENTIFY, + NULL, + 0, + &resp_code, + 0); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to write identify cmd\n", __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + tcm_msg = &ts->msg_data; + + synaptics_ts_buf_lock(&tcm_msg->in); + + ret = synaptics_ts_v1_parse_idinfo(ts, + &tcm_msg->in.buf[SYNAPTICS_TS_MESSAGE_HEADER_SIZE], + tcm_msg->in.buf_size - SYNAPTICS_TS_MESSAGE_HEADER_SIZE, + tcm_msg->payload_length); + if (ret < 0) { + input_info(true, ts->dev, "%s:Fail to identify device\n", __func__); + synaptics_ts_buf_unlock(&tcm_msg->in); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + synaptics_ts_buf_unlock(&tcm_msg->in); + + if (IS_NOT_APP_FW_MODE(ts->dev_mode)) { + input_info(true, ts->dev, "%s: in 0x%02X mode\n", __func__, ts->dev_mode); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + } else { + input_info(true, ts->dev, "%s: in application mode\n", __func__); + sec->cmd_state = SEC_CMD_STATUS_OK; + } +} + +static void run_full_rawcap_read(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + struct synaptics_ts_test_mode mode; + + memset(&mode, 0x00, sizeof(struct synaptics_ts_test_mode)); + mode.type = TEST_PID05_FULL_RAW_CAP; + + synaptics_ts_test_rawdata_read(ts, sec, &mode); +} + +static void run_full_rawcap_read_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + struct synaptics_ts_test_mode mode; + + memset(&mode, 0x00, sizeof(struct synaptics_ts_test_mode)); + mode.type = TEST_PID05_FULL_RAW_CAP; + mode.allnode = TEST_MODE_ALL_NODE; + + synaptics_ts_test_rawdata_read(ts, sec, &mode); +} + +static void run_trans_rawcap_read(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + struct synaptics_ts_test_mode mode; + + memset(&mode, 0x00, sizeof(struct synaptics_ts_test_mode)); + mode.type = TEST_PID22_TRANS_RAW_CAP; + + synaptics_ts_test_rawdata_read(ts, sec, &mode); +} + +static void run_trans_rawcap_read_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + struct synaptics_ts_test_mode mode; + + memset(&mode, 0x00, sizeof(struct synaptics_ts_test_mode)); + mode.type = TEST_PID22_TRANS_RAW_CAP; + mode.allnode = TEST_MODE_ALL_NODE; + + synaptics_ts_test_rawdata_read(ts, sec, &mode); +} + +static void run_delta_noise_read(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + struct synaptics_ts_test_mode mode; + + memset(&mode, 0x00, sizeof(struct synaptics_ts_test_mode)); + mode.type = TEST_PID10_DELTA_NOISE; + + synaptics_ts_test_rawdata_read(ts, sec, &mode); +} + +static void run_delta_noise_read_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + struct synaptics_ts_test_mode mode; + + memset(&mode, 0x00, sizeof(struct synaptics_ts_test_mode)); + mode.type = TEST_PID10_DELTA_NOISE; + mode.allnode = TEST_MODE_ALL_NODE; + + synaptics_ts_test_rawdata_read(ts, sec, &mode); +} + +static void run_abs_rawcap_read(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + struct synaptics_ts_test_mode mode; + + memset(&mode, 0x00, sizeof(struct synaptics_ts_test_mode)); + mode.type = TEST_PID18_HYBRID_ABS_RAW; + mode.frame_channel = TEST_MODE_READ_CHANNEL; + + synaptics_ts_test_rawdata_read(ts, sec, &mode); +} + +static void run_abs_rawcap_read_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + struct synaptics_ts_test_mode mode; + + memset(&mode, 0x00, sizeof(struct synaptics_ts_test_mode)); + mode.type = TEST_PID18_HYBRID_ABS_RAW; + mode.allnode = TEST_MODE_ALL_NODE; + mode.frame_channel = TEST_MODE_READ_CHANNEL; + + synaptics_ts_test_rawdata_read(ts, sec, &mode); +} + +static void run_abs_noise_read(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + struct synaptics_ts_test_mode mode; + + memset(&mode, 0x00, sizeof(struct synaptics_ts_test_mode)); + mode.type = TEST_PID29_HYBRID_ABS_NOISE; + mode.frame_channel = TEST_MODE_READ_CHANNEL; + + synaptics_ts_test_rawdata_read(ts, sec, &mode); +} + +static void run_abs_noise_read_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + struct synaptics_ts_test_mode mode; + + memset(&mode, 0x00, sizeof(struct synaptics_ts_test_mode)); + mode.type = TEST_PID29_HYBRID_ABS_NOISE; + mode.frame_channel = TEST_MODE_READ_CHANNEL; + mode.allnode = TEST_MODE_ALL_NODE; + + synaptics_ts_test_rawdata_read(ts, sec, &mode); +} + +static int get_gap_data(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char buff[16] = { 0 }; + int ii; + int node_gap_tx = 0; + int node_gap_rx = 0; + int tx_max = 0; + int rx_max = 0; + + for (ii = 0; ii < (ts->rows * ts->cols); ii++) { + if ((ii + 1) % (ts->rows) != 0) { + node_gap_tx = (abs(ts->pFrame[ii + 1] - ts->pFrame[ii])) * 100 / max(ts->pFrame[ii], ts->pFrame[ii + 1]); + tx_max = max(tx_max, node_gap_tx); + } + + if (ii < (ts->cols - 1) * ts->rows) { + node_gap_rx = (abs(ts->pFrame[ii + ts->rows] - ts->pFrame[ii])) * 100 / max(ts->pFrame[ii], ts->pFrame[ii + ts->rows]); + rx_max = max(rx_max, node_gap_rx); + } + } + + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) { + snprintf(buff, sizeof(buff), "%d,%d", 0, tx_max); + sec_cmd_set_cmd_result_all(sec, buff, SEC_CMD_STR_LEN, "GAP_DATA_TX"); + snprintf(buff, sizeof(buff), "%d,%d", 0, rx_max); + sec_cmd_set_cmd_result_all(sec, buff, SEC_CMD_STR_LEN, "GAP_DATA_RX"); + snprintf(buff, sizeof(buff), "%d,%d", 0, max(tx_max, rx_max)); + sec_cmd_set_cmd_result_all(sec, buff, SEC_CMD_STR_LEN, "GAP_DATA"); + } + + sec->cmd_state = SEC_CMD_STATUS_OK; + return 0; +} + +static void get_gap_data_x_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char *buff = NULL; + int ii; + int node_gap = 0; + char temp[SEC_CMD_STR_LEN] = { 0 }; + + buff = kzalloc(ts->cols * ts->rows * CMD_RESULT_WORD_LEN, GFP_KERNEL); + if (!buff) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + for (ii = 0; ii < (ts->rows * ts->cols); ii++) { + if ((ii + 1) % (ts->rows) != 0) { + node_gap = (abs(ts->pFrame[ii + 1] - ts->pFrame[ii])) * 100 / max(ts->pFrame[ii], ts->pFrame[ii + 1]); + snprintf(temp, CMD_RESULT_WORD_LEN, "%d,", node_gap); + strlcat(buff, temp, ts->cols * ts->rows * CMD_RESULT_WORD_LEN); + memset(temp, 0x00, SEC_CMD_STR_LEN); + } + } + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, ts->cols * ts->rows * CMD_RESULT_WORD_LEN)); + sec->cmd_state = SEC_CMD_STATUS_OK; + kfree(buff); +} + +static void get_gap_data_y_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char *buff = NULL; + int ii; + int node_gap = 0; + char temp[SEC_CMD_STR_LEN] = { 0 }; + + buff = kzalloc(ts->cols * ts->rows * CMD_RESULT_WORD_LEN, GFP_KERNEL); + if (!buff) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + for (ii = 0; ii < (ts->rows * ts->cols); ii++) { + if (ii < (ts->cols - 1) * ts->rows) { + node_gap = (abs(ts->pFrame[ii + ts->rows] - ts->pFrame[ii])) * 100 / max(ts->pFrame[ii], ts->pFrame[ii + ts->rows]); + + snprintf(temp, CMD_RESULT_WORD_LEN, "%d,", node_gap); + strlcat(buff, temp, ts->cols * ts->rows * CMD_RESULT_WORD_LEN); + memset(temp, 0x00, SEC_CMD_STR_LEN); + } + } + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, ts->cols * ts->rows * CMD_RESULT_WORD_LEN)); + sec->cmd_state = SEC_CMD_STATUS_OK; + kfree(buff); +} + +#define GAP_MULTIPLE_VAL 100 +static void get_gap_data_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char *buff = NULL; + int ii; + char temp[SEC_CMD_STR_LEN] = { 0 }; + int node_gap_tx = 0; + int node_gap_rx = 0; + int node_gap_max = 0; + + buff = kzalloc(ts->cols * ts->rows * CMD_RESULT_WORD_LEN, GFP_KERNEL); + if (!buff) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + for (ii = 0; ii < (ts->rows * ts->cols); ii++) { + node_gap_tx = node_gap_rx = node_gap_max = 0; + + /* rx(x) gap max */ + if ((ii + 1) % (ts->rows) != 0) + node_gap_tx = 1 * GAP_MULTIPLE_VAL - (min(ts->pFrame[ii], ts->pFrame[ii + 1]) * GAP_MULTIPLE_VAL) / (max(ts->pFrame[ii], ts->pFrame[ii + 1])); + + /* tx(y) gap max */ + if (ii < (ts->cols - 1) * ts->rows) + node_gap_rx = 1 * GAP_MULTIPLE_VAL - (min(ts->pFrame[ii], ts->pFrame[ii + ts->rows]) * GAP_MULTIPLE_VAL) / (max(ts->pFrame[ii], ts->pFrame[ii + ts->rows])); + + node_gap_max = max(node_gap_tx, node_gap_rx); + + snprintf(temp, sizeof(temp), "%d,", node_gap_max); + strlcat(buff, temp, ts->cols * ts->rows * 6); + memset(temp, 0x00, CMD_RESULT_WORD_LEN); + } + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, ts->cols * ts->rows * CMD_RESULT_WORD_LEN)); + sec->cmd_state = SEC_CMD_STATUS_OK; + kfree(buff); +} + +static void run_interrupt_gpio_test(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = {0}; + unsigned char *buf = NULL; + int ret, status1, status2; + unsigned char command[3] = {0x00, 0x00, 0x00}; + + synaptics_ts_enable_report(ts, REPORT_SEC_COORDINATE_EVENT, false); + synaptics_ts_enable_report(ts, REPORT_SEC_STATUS_EVENT, false); + + buf = synaptics_ts_pal_mem_alloc(2, sizeof(struct synaptics_ts_identification_info)); + if (unlikely(buf == NULL)) { + input_err(true, ts->dev, "%s: Fail to create a buffer for test\n", __func__); + goto err_power_state; + } + + disable_irq(ts->irq); + + command[0] = CMD_DBG_ATTN; + ret = ts->synaptics_ts_write_data(ts, command, sizeof(command), NULL, 0); + if (ret < 0) { + input_err(true, ts->dev, "%s: Fail to write cmd\n", __func__); + goto err; + } + + msleep(20); + status1 = gpio_get_value(ts->plat_data->irq_gpio); + input_info(true, ts->dev, "%s: gpio value %d (should be 0)\n", __func__, status1); + + msleep(20); + + ret = ts->synaptics_ts_read_data_only(ts, buf, SYNAPTICS_TS_MESSAGE_HEADER_SIZE); + if (ret < 0) { + input_err(true, ts->dev, "%s: Fail to read cmd\n", __func__); + goto err; + } + + msleep(10); + + status2 = gpio_get_value(ts->plat_data->irq_gpio); + input_info(true, ts->dev, "%s: gpio value %d (should be 1)\n", __func__, status2); + + if ((status1 == 0) && (status2 == 1)) { + snprintf(buff, sizeof(buff), "0"); + sec->cmd_state = SEC_CMD_STATUS_OK; + } else { + if (status1 != 0) + snprintf(buff, sizeof(buff), "1:HIGH"); + else if (status2 != 1) + snprintf(buff, sizeof(buff), "1:LOW"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + } + + enable_irq(ts->irq); + synaptics_ts_pal_mem_free(buf); + + synaptics_ts_enable_report(ts, REPORT_SEC_COORDINATE_EVENT, true); + synaptics_ts_enable_report(ts, REPORT_SEC_STATUS_EVENT, true); + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "INT_GPIO"); + return; + +err: + enable_irq(ts->irq); + synaptics_ts_pal_mem_free(buf); +err_power_state: + + synaptics_ts_enable_report(ts, REPORT_SEC_COORDINATE_EVENT, true); + synaptics_ts_enable_report(ts, REPORT_SEC_STATUS_EVENT, true); + + snprintf(buff, sizeof(buff), "NG"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "INT_GPIO"); +} + +static void run_sram_test(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = {0}; + int ret; + u8 result = 0; + unsigned char command[4] = {SYNAPTICS_TS_CMD_PRODUCTION_TEST, + 0x01, 0x00, TEST_PID53_SRAM_TEST}; + unsigned char id; + + synaptics_ts_enable_report(ts, REPORT_SEC_COORDINATE_EVENT, false); + synaptics_ts_enable_report(ts, REPORT_SEC_STATUS_EVENT, false); + + disable_irq(ts->irq); + + ret = ts->synaptics_ts_write_data(ts, command, sizeof(command), NULL, 0); + if (ret < 0) { + input_err(true, ts->dev, "%s: Fail to write cmd\n", __func__); + enable_irq(ts->irq); + synaptics_ts_enable_report(ts, REPORT_SEC_COORDINATE_EVENT, true); + synaptics_ts_enable_report(ts, REPORT_SEC_STATUS_EVENT, true); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + msleep(500); + + ret = ts->read_message(ts, &id); + if (ret < 0) { + input_err(true, ts->dev, "%s: Fail to read cmd\n", __func__); + enable_irq(ts->irq); + synaptics_ts_enable_report(ts, REPORT_SEC_COORDINATE_EVENT, true); + synaptics_ts_enable_report(ts, REPORT_SEC_STATUS_EVENT, true); + + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + if (id == REPORT_IDENTIFY) + result = 0; /* PASS */ + else + result = 1; /* FAIL */ + + enable_irq(ts->irq); + msleep(200); + + synaptics_ts_enable_report(ts, REPORT_SEC_COORDINATE_EVENT, true); + synaptics_ts_enable_report(ts, REPORT_SEC_STATUS_EVENT, true); + + snprintf(buff, sizeof(buff), "%d", result); + sec->cmd_state = SEC_CMD_STATUS_OK; + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "SRAM"); + +} + +/* Automatic Test Equipment for the checking of VDDHSO */ +/* Return - 1 : ATE screened, 0 : NOT screen */ +static void run_ate_test(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = {0}; + int ret = 0; + u8 result = 0; + unsigned char resp_code; + unsigned char test_code = TEST_PID82_CUSTOMER_DATA_CHECK; + + if (IS_NOT_APP_FW_MODE(ts->dev_mode)) { + input_err(true, ts->dev, "%s: Device is not in application fw mode, mode: %x\n", __func__, + ts->dev_mode); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + ret = ts->write_message(ts, + SYNAPTICS_TS_CMD_PRODUCTION_TEST, + &test_code, + 1, + &resp_code, + CMD_RESPONSE_POLLING_DELAY_MS); + if (ret < 0) { + input_err(true, ts->dev, "Fail to send command 0x%02x\n", + SYNAPTICS_TS_CMD_PRODUCTION_TEST); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + if (resp_code != STATUS_OK) { + input_err(true, ts->dev, "Invalid status of PID82\n"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + synaptics_ts_buf_lock(&ts->resp_buf); + + if (ts->resp_buf.data_length > 0) { + input_info(true, ts->dev, "%s: resp_buf.buf[0] = %x\n", __func__, ts->resp_buf.buf[0]); + if ((ts->resp_buf.buf[0] & 0x01) == 0x01) + result = true; /* Pass */ + } + + synaptics_ts_buf_unlock(&ts->resp_buf); + + snprintf(buff, sizeof(buff), "%d", result); + sec->cmd_state = SEC_CMD_STATUS_OK; + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "ATE"); +} + +static int check_support_calibration(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (ts->tdata->tclm_level == TCLM_LEVEL_NOT_SUPPORT) { + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + input_info(true, ts->dev, "%s: tclm not supported\n", __func__); + return SEC_ERROR; + } + + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_SUCCESS; +} + +static void run_factory_miscalibration(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = {0}; + struct synaptics_ts_buffer test_data; + unsigned short *data_ptr = NULL; + int ret, i; + short result[3] = {0}; + + if (check_support_calibration(device_data) < 0) + return; + + synaptics_ts_buf_init(&test_data); + + ret = synaptics_ts_run_production_test(ts, + TEST_PID54_BSC_DIFF_TEST, + &test_data); + if (ret < 0) { + input_err(true, ts->dev, "Fail to run test %d\n", + TEST_PID54_BSC_DIFF_TEST); + synaptics_ts_buf_release(&test_data); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + data_ptr = (unsigned short *)&test_data.buf[0]; + for (i = 0; i < test_data.data_length / 2; i++) { + result[i] = *data_ptr; + data_ptr++; + } + + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) { + snprintf(buff, sizeof(buff), "%d,%d", result[1], result[2]); + sec_cmd_set_cmd_result_all(sec, buff, strnlen(buff, sizeof(buff)), "MIS_CAL"); + } else { + if (result[0] == 1) + snprintf(buff, sizeof(buff), "OK,%d,%d", result[1], result[2]); + else + snprintf(buff, sizeof(buff), "NG,%d,%d", result[1], result[2]); + } + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + + synaptics_ts_buf_release(&test_data); +} + +static void run_miscalibration(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = {0}; + struct synaptics_ts_buffer test_data; + unsigned short *data_ptr = NULL; + int ret, i; + short result[3] = {0}; + + if (check_support_calibration(device_data) < 0) + return; + + synaptics_ts_buf_init(&test_data); + + ret = synaptics_ts_run_production_test(ts, + TEST_PID61_MISCAL, + &test_data); + if (ret < 0) { + input_err(true, ts->dev, "Fail to run test %d\n", + TEST_PID61_MISCAL); + synaptics_ts_buf_release(&test_data); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + data_ptr = (unsigned short *)&test_data.buf[0]; + for (i = 0; i < test_data.data_length / 2; i++) { + result[i] = *data_ptr; + data_ptr++; + } + + if (result[0] == 1) + snprintf(buff, sizeof(buff), "OK,%d,%d", result[1], result[2]); + else + snprintf(buff, sizeof(buff), "NG,%d,%d", result[1], result[2]); + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + + synaptics_ts_buf_release(&test_data); +} + +static void run_miscal_data_read_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + struct synaptics_ts_test_mode mode; + + if (check_support_calibration(device_data) < 0) + return; + + memset(&mode, 0x00, sizeof(struct synaptics_ts_test_mode)); + mode.type = TEST_PID65_MISCALDATA_NORMAL; + mode.allnode = TEST_MODE_ALL_NODE; + + synaptics_ts_test_rawdata_read(ts, sec, &mode); +} + +void synaptics_ts_rawdata_read_all(struct synaptics_ts_data *ts) +{ + struct synaptics_ts_buffer test_data; + int min, max; + int i, test_num; + int ret; + u8 test_type[7] = {TEST_PID10_DELTA_NOISE, + TEST_PID18_HYBRID_ABS_RAW, TEST_PID22_TRANS_RAW_CAP, TEST_PID29_HYBRID_ABS_NOISE, + TEST_PID65_MISCALDATA_NORMAL, TEST_PID66_MISCALDATA_NOISE, TEST_PID67_MISCALDATA_WET}; + + if (ts->tdata->tclm_level == TCLM_LEVEL_NOT_SUPPORT) + test_num = 4; + else if (ts->support_miscal_wet) + test_num = 7; + else + test_num = 6; + + ts->tsp_dump_lock = 1; + input_raw_data_clear_by_device(GET_DEV_COUNT(ts->multi_dev)); + + for (i = 0; i < test_num; i++) { + input_raw_info_d(GET_DEV_COUNT(ts->multi_dev), ts->dev, "%s: %d\n", __func__, test_type[i]); + synaptics_ts_buf_init(&test_data); + ret = synaptics_ts_run_production_test(ts, test_type[i], &test_data); + if (ret < 0) { + input_err(true, ts->dev, "Fail to run test %d\n", test_type[i]); + synaptics_ts_buf_release(&test_data); + goto out; + } + if (test_type[i] == TEST_PID18_HYBRID_ABS_RAW || test_type[i] == TEST_PID29_HYBRID_ABS_NOISE) + synaptics_ts_print_channel(ts, &test_data, &min, &max, test_type[i]); + else + synaptics_ts_print_frame(ts, &test_data, &min, &max, test_type[i]); + synaptics_ts_buf_release(&test_data); + } + + synaptics_ts_clear_buffer(ts); + synaptics_ts_locked_release_all_finger(ts); + +out: + ts->tsp_dump_lock = 0; +} + +static void factory_cmd_result_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + sec->item_count = 0; + memset(sec->cmd_result_all, 0x00, SEC_CMD_RESULT_STR_LEN); + + if (ts->tsp_dump_lock == 1) { + input_err(true, ts->dev, "%s: already checking now\n", __func__); + sec->cmd_all_factory_state = SEC_CMD_STATUS_FAIL; + goto out; + } + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: IC is power off\n", __func__); + sec->cmd_all_factory_state = SEC_CMD_STATUS_FAIL; + goto out; + } + + sec->cmd_all_factory_state = SEC_CMD_STATUS_RUNNING; + + get_chip_vendor(sec); + get_chip_name(sec); + get_fw_ver_bin(sec); + get_fw_ver_ic(sec); + + run_trans_rawcap_read(sec); + get_gap_data(sec); + + run_full_rawcap_read(sec); + run_delta_noise_read(sec); + run_abs_rawcap_read(sec); + get_self_channel_data(sec, TEST_PID18_HYBRID_ABS_RAW); + run_abs_noise_read(sec); + + if (ts->tdata->tclm_level != TCLM_LEVEL_NOT_SUPPORT) + run_factory_miscalibration(sec); + + run_sram_test(sec); + run_interrupt_gpio_test(sec); + run_ate_test(sec); + + sec->cmd_all_factory_state = SEC_CMD_STATUS_OK; + +out: + input_info(true, ts->dev, "%s: %d%s\n", __func__, sec->item_count, sec->cmd_result_all); +} + +static void run_force_calibration(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int rc; + + if (check_support_calibration(device_data) < 0) + goto out_force_cal; + + if (ts->plat_data->touch_count > 0) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto out_force_cal; + } + + rc = synaptics_ts_calibration(ts); + if (rc < 0) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto out_force_cal; + } + +#ifdef TCLM_CONCEPT + /* devide tclm case */ + sec_tclm_case(ts->tdata, sec->cmd_param[0]); + + input_info(true, ts->dev, "%s: param, %d, %c, %d\n", __func__, + sec->cmd_param[0], sec->cmd_param[0], ts->tdata->root_of_calibration); + + rc = sec_execute_tclm_package(ts->tdata, 1); + if (rc < 0) { + input_err(true, ts->dev, + "%s: sec_execute_tclm_package\n", __func__); + } + sec_tclm_root_of_cal(ts->tdata, CALPOSITION_NONE); +#endif + + sec->cmd_state = SEC_CMD_STATUS_OK; + +out_force_cal: +#ifdef TCLM_CONCEPT + /* not to enter external factory mode without setting everytime */ + ts->tdata->external_factory = false; +#endif + input_info(true, ts->dev, "%s: %s\n", __func__, sec->cmd_state == SEC_CMD_STATUS_OK ? "OK" : "NG"); +} + +static int run_force_calibration_store(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + +#ifdef TCLM_CONCEPT + ts->tdata->external_factory = false; +#endif + + return check_support_calibration(device_data); +} + +#ifdef TCLM_CONCEPT +static void set_tsp_test_result(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + struct sec_ts_test_result *result; + + synaptics_ts_tclm_read(ts->dev, SEC_TCLM_NVM_ALL_DATA); + if (ts->fac_nv == 0xFF) + ts->fac_nv = 0; + + result = (struct sec_ts_test_result *)&ts->fac_nv; + + if (sec->cmd_param[0] == TEST_OCTA_ASSAY) { + result->assy_result = sec->cmd_param[1]; + if (result->assy_count < 3) + result->assy_count++; + } + + if (sec->cmd_param[0] == TEST_OCTA_MODULE) { + result->module_result = sec->cmd_param[1]; + if (result->module_count < 3) + result->module_count++; + } + + input_info(true, ts->dev, "%s: %d, %d, %d, %d, 0x%X\n", __func__, + result->module_result, result->module_count, + result->assy_result, result->assy_count, result->data[0]); + + ts->fac_nv = *result->data; + + synaptics_ts_tclm_write(ts->dev, SEC_TCLM_NVM_ALL_DATA); + synaptics_ts_tclm_read(ts->dev, SEC_TCLM_NVM_ALL_DATA); + + input_info(true, ts->dev, "%s: command (1)%X, (2)%X: %X\n", + __func__, sec->cmd_param[0], sec->cmd_param[1], ts->fac_nv); + + sec->cmd_state = SEC_CMD_STATUS_OK; +} + +static void get_tsp_test_result(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + struct sec_ts_test_result *result; + + synaptics_ts_tclm_read(ts->dev, SEC_TCLM_NVM_ALL_DATA); + if (ts->fac_nv == 0xFF) { + ts->fac_nv = 0; + synaptics_ts_tclm_write(ts->dev, SEC_TCLM_NVM_ALL_DATA); + } + + result = (struct sec_ts_test_result *)&ts->fac_nv; + + input_info(true, ts->dev, "%s: [0x%X][0x%X] M:%d, M:%d, A:%d, A:%d\n", + __func__, *result->data, ts->fac_nv, + result->module_result, result->module_count, + result->assy_result, result->assy_count); + + snprintf(buff, sizeof(buff), "M:%s, M:%d, A:%s, A:%d", + result->module_result == 0 ? "NONE" : + result->module_result == 1 ? "FAIL" : + result->module_result == 2 ? "PASS" : "A", + result->module_count, + result->assy_result == 0 ? "NONE" : + result->assy_result == 1 ? "FAIL" : + result->assy_result == 2 ? "PASS" : "A", + result->assy_count); + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; +} + +static void clear_tsp_test_result(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + ts->fac_nv = 0; + synaptics_ts_tclm_write(ts->dev, SEC_TCLM_NVM_ALL_DATA); + synaptics_ts_tclm_read(ts->dev, SEC_TCLM_NVM_ALL_DATA); + + input_info(true, ts->dev, "%s: fac_nv:0x%02X\n", __func__, ts->fac_nv); + + sec->cmd_state = SEC_CMD_STATUS_OK; +} + +static void increase_disassemble_count(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + synaptics_ts_tclm_read(ts->dev, SEC_TCLM_NVM_ALL_DATA); + + input_info(true, ts->dev, "%s: disassemble count is #1 %d\n", __func__, ts->disassemble_count); + + if (ts->disassemble_count == 0xFF) + ts->disassemble_count = 0; + + if (ts->disassemble_count < 0xFE) + ts->disassemble_count++; + + /* Use TSP NV area : in this model, use only one byte + * buff[0] : offset from user NVM storage + * buff[1] : length of stroed data - 1 (ex. using 1byte, value is 1 - 1 = 0) + * buff[2] : write data + */ + synaptics_ts_tclm_write(ts->dev, SEC_TCLM_NVM_ALL_DATA); + synaptics_ts_tclm_read(ts->dev, SEC_TCLM_NVM_ALL_DATA); + + input_info(true, ts->dev, "%s: check disassemble count: %d\n", __func__, ts->disassemble_count); + + sec->cmd_state = SEC_CMD_STATUS_OK; +} + +static void get_disassemble_count(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + + synaptics_ts_tclm_read(ts->dev, SEC_TCLM_NVM_ALL_DATA); + + input_info(true, ts->dev, "%s: read disassemble count: %d\n", __func__, ts->disassemble_count); + + snprintf(buff, sizeof(buff), "%d", ts->disassemble_count); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; +} + +static void get_pat_information(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char buff[50] = { 0 }; + + if (check_support_calibration(device_data) < 0) + return; + +#ifdef CONFIG_SEC_FACTORY + if (ts->factory_position == 1) { + sec_tclm_initialize(ts->tdata); + if (sec_input_cmp_ic_status(ts->dev, CHECK_ON_LP)) + ts->tdata->tclm_read(ts->dev, SEC_TCLM_NVM_ALL_DATA); + } +#endif + /* fixed tune version will be saved at excute autotune */ + snprintf(buff, sizeof(buff), "C%02XT%04X.%4s%s%c%d%c%d%c%d", + ts->tdata->nvdata.cal_count, ts->tdata->nvdata.tune_fix_ver, + ts->tdata->tclm_string[ts->tdata->nvdata.cal_position].f_name, + (ts->tdata->tclm_level == TCLM_LEVEL_LOCKDOWN) ? ".L " : " ", + ts->tdata->cal_pos_hist_last3[0], ts->tdata->cal_pos_hist_last3[1], + ts->tdata->cal_pos_hist_last3[2], ts->tdata->cal_pos_hist_last3[3], + ts->tdata->cal_pos_hist_last3[4], ts->tdata->cal_pos_hist_last3[5]); + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void set_external_factory(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + ts->tdata->external_factory = true; + + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s\n", __func__); +} +#endif + +static void set_factory_level(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (sec->cmd_param[0] < OFFSET_FAC_SUB || sec->cmd_param[0] > OFFSET_FAC_MAIN) { + input_err(true, ts->dev, + "%s: cmd data is abnormal, %d\n", __func__, sec->cmd_param[0]); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + ts->factory_position = sec->cmd_param[0]; + + input_info(true, ts->dev, "%s: %d\n", __func__, ts->factory_position); + sec->cmd_state = SEC_CMD_STATUS_OK; +} + +static void run_trx_short_test(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + struct synaptics_ts_buffer test_data; + char buff[SEC_CMD_STR_LEN]; + u8 type = 0; + int ret; + char result_uevent[32]; + char test[32]; + char tempn[40] = {0}; + int i, j; + unsigned char result_data = 0; + u8 *test_result_buff; + unsigned char tmp = 0; + unsigned char p = 0; + + if (sec->cmd_param[0] == 1 && sec->cmd_param[1] == 0) { + input_err(true, ts->dev, + "%s: %s: seperate cm1 test open / short test result\n", __func__, buff); + + snprintf(buff, sizeof(buff), "%s", "CONT"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + return; + } + + if (sec->cmd_param[0] == 1 && sec->cmd_param[1] == 1) { + type = TEST_PID58_TRX_OPEN; + } else if (sec->cmd_param[0] == 1 && sec->cmd_param[1] == 2) { + type = TEST_PID01_TRX_SHORTS; + } else { + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + input_info(true, ts->dev, "%s : not supported test case\n", __func__); + return; + } + + test_result_buff = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!test_result_buff) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + if (sec->cmd_param[1]) + snprintf(test, sizeof(test), "TEST=%d,%d", sec->cmd_param[0], sec->cmd_param[1]); + else + snprintf(test, sizeof(test), "TEST=%d", sec->cmd_param[0]); + + synaptics_ts_buf_init(&test_data); + + ret = synaptics_ts_run_production_test(ts, type, &test_data); + if (ret < 0) { + input_err(true, ts->dev, "Fail to run test %d\n", type); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + + snprintf(result_uevent, sizeof(result_uevent), "RESULT=FAIL"); + sec_cmd_send_event_to_user(&ts->sec, test, result_uevent); + + synaptics_ts_buf_release(&test_data); + kfree(test_result_buff); + return; + } + + memset(tempn, 0x00, 40); + /* print log */ + if (type == TEST_PID58_TRX_OPEN) + snprintf(tempn, 40, " TX/RX_OPEN:"); + else if (type == TEST_PID01_TRX_SHORTS) + snprintf(tempn, 40, " TX/RX_SHORT:"); + + strlcat(test_result_buff, tempn, PAGE_SIZE); + + if (type == TEST_PID01_TRX_SHORTS) { + for (i = 0; i < test_data.data_length; i++) { + memset(tempn, 0x00, 40); + tmp = test_data.buf[i]; + result_data += tmp; + for (j = 0; j < 8; j++) { + p = GET_BIT_LSB(tmp, j); + if (p == 0x1) { + snprintf(tempn, 20, "TRX%d,", i * 8 + j); + strlcat(test_result_buff, tempn, PAGE_SIZE); + } + } + } + } else { + for (i = 0; i < test_data.data_length; i++) { + memset(tempn, 0x00, 40); + tmp = test_data.buf[i]; + result_data += tmp; + for (j = 0; j < 8; j++) { + p = GET_BIT_MSB(tmp, j); + if (p == 0x1) { + if (i * 8 + j < ts->cols) + snprintf(tempn, 20, "RX%d,", i * 8 + j); + else + snprintf(tempn, 20, "TX%d,", (i * 8 + j) - ts->cols); + strlcat(test_result_buff, tempn, PAGE_SIZE); + } + } + } + } + synaptics_ts_buf_release(&test_data); + + if (result_data == 0) + ret = SEC_SUCCESS; + else + ret = SEC_ERROR; + + if (ret < 0) { + sec_cmd_set_cmd_result(sec, test_result_buff, strnlen(buff, PAGE_SIZE)); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + input_info(true, ts->dev, "%s: %s\n", __func__, test_result_buff); + snprintf(result_uevent, sizeof(result_uevent), "RESULT=FAIL"); + } else { + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: OK\n", __func__); + snprintf(result_uevent, sizeof(result_uevent), "RESULT=PASS"); + } + sec_cmd_send_event_to_user(&ts->sec, test, result_uevent); + + kfree(test_result_buff); +} + +static void run_jitter_delta_test(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + int ret; + int result = -1; + const int IDX_MIN = 1; + const int IDX_MAX = 0; + short min[2] = { 0 }; + short max[2] = { 0 }; + short avg[2] = { 0 }; + unsigned char buf[32] = { 0 }; + unsigned int time = 0; + unsigned int timeout = 30; + unsigned char command[4] = {SYNAPTICS_TS_CMD_PRODUCTION_TEST, + 0x01, 0x00, TEST_PID52_JITTER_DELTA_TEST}; + + synaptics_ts_release_all_finger(ts); + + disable_irq(ts->irq); + + ret = ts->synaptics_ts_write_data(ts, command, sizeof(command), NULL, 0); + if (ret < 0) { + input_info(true, ts->dev, "%s: failed to set active mode\n", __func__); + goto OUT_JITTER_DELTA; + } + + /* jitter delta + 1000 frame*/ + for (time = 0; time < timeout; time++) { + sec_delay(500); + + ret = ts->synaptics_ts_read_data_only(ts, + buf, + sizeof(buf)); + if (ret < 0) { + input_info(true, ts->dev, "Fail to read the test result\n"); + goto OUT_JITTER_DELTA; + } + + if (buf[1] == STATUS_OK) { + result = 0; + break; + } + } + + if (time >= timeout) { + input_info(true, ts->dev, "Timed out waiting for result of PT34\n"); + goto OUT_JITTER_DELTA; + } + + min[IDX_MIN] = (short)synaptics_ts_pal_le2_to_uint(&buf[SYNAPTICS_TS_MESSAGE_HEADER_SIZE + 0]); + min[IDX_MAX] = (short)synaptics_ts_pal_le2_to_uint(&buf[SYNAPTICS_TS_MESSAGE_HEADER_SIZE + 2]); + + max[IDX_MIN] = (short)synaptics_ts_pal_le2_to_uint(&buf[SYNAPTICS_TS_MESSAGE_HEADER_SIZE + 4]); + max[IDX_MAX] = (short)synaptics_ts_pal_le2_to_uint(&buf[SYNAPTICS_TS_MESSAGE_HEADER_SIZE + 6]); + + avg[IDX_MIN] = (short)synaptics_ts_pal_le2_to_uint(&buf[SYNAPTICS_TS_MESSAGE_HEADER_SIZE + 8]); + avg[IDX_MAX] = (short)synaptics_ts_pal_le2_to_uint(&buf[SYNAPTICS_TS_MESSAGE_HEADER_SIZE + 10]); + +OUT_JITTER_DELTA: + + enable_irq(ts->irq); + + if (result < 0) + snprintf(buff, sizeof(buff), "NG"); + else + snprintf(buff, sizeof(buff), "%d,%d,%d,%d,%d,%d", min[IDX_MIN], min[IDX_MAX], + max[IDX_MIN], max[IDX_MAX], avg[IDX_MIN], avg[IDX_MAX]); + + if (sec->cmd_all_factory_state == SEC_CMD_STATUS_RUNNING) { + char buffer[SEC_CMD_STR_LEN] = { 0 }; + + snprintf(buffer, sizeof(buffer), "%d,%d", min[IDX_MIN], min[IDX_MAX]); + sec_cmd_set_cmd_result_all(sec, buffer, strnlen(buffer, sizeof(buffer)), "JITTER_DELTA_MIN"); + + memset(buffer, 0x00, sizeof(buffer)); + snprintf(buffer, sizeof(buffer), "%d,%d", max[IDX_MIN], max[IDX_MAX]); + sec_cmd_set_cmd_result_all(sec, buffer, strnlen(buffer, sizeof(buffer)), "JITTER_DELTA_MAX"); + + memset(buffer, 0x00, sizeof(buffer)); + snprintf(buffer, sizeof(buffer), "%d,%d", avg[IDX_MIN], avg[IDX_MAX]); + sec_cmd_set_cmd_result_all(sec, buffer, strnlen(buffer, sizeof(buffer)), "JITTER_DELTA_AVG"); + } + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void run_snr_non_touched(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + unsigned char buf[2 * 7 * (3 * 3) + 4] = { 0 }; + int ret = 0; + int wait_time = 0; + unsigned int time = 0; + unsigned int timeout = 30; + unsigned int input = 0; + unsigned char command[4] = {SYNAPTICS_TS_CMD_PRODUCTION_TEST, + 0x01, 0x00, TEST_PID56_TSP_SNR_NONTOUCH}; + + input_info(true, ts->dev, "%s\n", __func__); + + if (sec->cmd_param[0] < 1 || sec->cmd_param[0] > 1000) { + input_err(true, ts->dev, "%s: strange value frame:%d\n", + __func__, sec->cmd_param[0]); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + input = sec->cmd_param[0]; + + /* set up snr frame */ + ret = synaptics_ts_set_dynamic_config(ts, + DC_TSP_SNR_TEST_FRAMES, + (unsigned short)input); + if (ret < 0) { + input_err(true, ts->dev, "Fail to set %d with dynamic command 0x%x\n", + input, DC_TSP_SNR_TEST_FRAMES); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + disable_irq(ts->irq); + /* enter SNR mode */ + ret = ts->synaptics_ts_write_data(ts, command, sizeof(command), NULL, 0); + if (ret < 0) { + input_info(true, ts->dev, "%s: failed to set active mode\n", __func__); + goto out; + } + + wait_time = (sec->cmd_param[0] * 1000) / 120 + (sec->cmd_param[0] * 1000) % 120; + + sec_delay(wait_time); + + for (time = 0; time < timeout; time++) { + sec_delay(20); + + ret = ts->synaptics_ts_read_data_only(ts, + buf, + sizeof(buf)); + if (ret < 0) { + input_info(true, ts->dev, "Fail to read the test result\n"); + goto out; + } + + if (buf[1] == STATUS_OK) + break; + } + + if (buf[1] == 0) { + input_err(true, ts->dev, "%s: failed non-touched status:%d\n", __func__, buf[1]); + goto out; + } + + enable_irq(ts->irq); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: OK\n", __func__); + return; +out: + enable_irq(ts->irq); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + input_info(true, ts->dev, "%s: NG\n", __func__); +} + +static void run_snr_touched(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + char tbuff[SEC_CMD_STR_LEN] = { 0 }; + unsigned char buf[2 * 7 * (3 * 3) + 4] = { 0 }; + int ret = 0; + int wait_time = 0; + int i = 0; + unsigned int time = 0; + unsigned int timeout = 30; + unsigned int input = 0; + unsigned char command[4] = {SYNAPTICS_TS_CMD_PRODUCTION_TEST, + 0x01, 0x00, TEST_PID57_TSP_SNR_TOUCH}; + struct tsp_snr_result_of_point result[9]; + + memset(result, 0x00, sizeof(struct tsp_snr_result_of_point) * 9); + + input_info(true, ts->dev, "%s\n", __func__); + + if (sec->cmd_param[0] < 1 || sec->cmd_param[0] > 1000) { + input_err(true, ts->dev, "%s: strange value frame:%d\n", + __func__, sec->cmd_param[0]); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + input = sec->cmd_param[0]; + + /* set up snr frame */ + ret = synaptics_ts_set_dynamic_config(ts, + DC_TSP_SNR_TEST_FRAMES, + (unsigned short)input); + if (ret < 0) { + input_err(true, ts->dev, "Fail to set %d with dynamic command 0x%x\n", + input, DC_TSP_SNR_TEST_FRAMES); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + disable_irq(ts->irq); + /* enter SNR mode */ + ret = ts->synaptics_ts_write_data(ts, command, sizeof(command), NULL, 0); + if (ret < 0) { + input_info(true, ts->dev, "%s: failed to set active mode\n", __func__); + goto out; + } + + wait_time = (sec->cmd_param[0] * 1000) / 120 + (sec->cmd_param[0] * 1000) % 120; + + sec_delay(wait_time); + + for (time = 0; time < timeout; time++) { + sec_delay(20); + + ret = ts->synaptics_ts_read_data_only(ts, + buf, + sizeof(buf)); + if (ret < 0) { + input_info(true, ts->dev, "Fail to read the test result\n"); + goto out; + } + + if (buf[1] == STATUS_OK) + break; + } + + if (buf[1] == 0) { + input_err(true, ts->dev, "%s: failed non-touched status:%d\n", __func__, buf[1]); + goto out; + } + + enable_irq(ts->irq); + + //memcpy(result, &buf[SYNAPTICS_TS_MESSAGE_HEADER_SIZE], sizeof(struct tsp_snr_result_of_point) * 9); + + for (i = 0; i < 9; i++) { + result[i].max = (short)synaptics_ts_pal_le2_to_uint(&buf[i * 14 + SYNAPTICS_TS_MESSAGE_HEADER_SIZE]); + result[i].min = (short)synaptics_ts_pal_le2_to_uint(&buf[i * 14 + SYNAPTICS_TS_MESSAGE_HEADER_SIZE + 2]); + result[i].average = (short)synaptics_ts_pal_le2_to_uint(&buf[i * 14 + SYNAPTICS_TS_MESSAGE_HEADER_SIZE + 4]); + result[i].nontouch_peak_noise = (short)synaptics_ts_pal_le2_to_uint(&buf[i * 14 + SYNAPTICS_TS_MESSAGE_HEADER_SIZE + 6]); + result[i].touch_peak_noise = (short)synaptics_ts_pal_le2_to_uint(&buf[i * 14 + SYNAPTICS_TS_MESSAGE_HEADER_SIZE + 8]); + result[i].snr1 = (short)synaptics_ts_pal_le2_to_uint(&buf[i * 14 + SYNAPTICS_TS_MESSAGE_HEADER_SIZE + 10]); + result[i].snr2 = (short)synaptics_ts_pal_le2_to_uint(&buf[i * 14 + SYNAPTICS_TS_MESSAGE_HEADER_SIZE + 12]); + + input_info(true, ts->dev, "%s: average:%d, snr1:%d, snr2:%d\n", __func__, + result[i].average, result[i].snr1, result[i].snr2); + snprintf(tbuff, sizeof(tbuff), "%d,%d,%d,", result[i].average, result[i].snr1, result[i].snr2); + strlcat(buff, tbuff, sizeof(buff)); + } + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + + input_info(true, ts->dev, "%s: %s\n", __func__, buff); + + return; + +out: + enable_irq(ts->irq); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + input_info(true, ts->dev, "%s: NG\n", __func__); +} + +static void factory_cmd_result_all_imagetest(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + sec->item_count = 0; + memset(sec->cmd_result_all, 0x00, SEC_CMD_RESULT_STR_LEN); + + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: [ERROR] Touch is stopped\n", + __func__); + sec->cmd_all_factory_state = SEC_CMD_STATUS_FAIL; + goto out; + } + + sec->cmd_all_factory_state = SEC_CMD_STATUS_RUNNING; + + run_jitter_delta_test(sec); + + sec->cmd_all_factory_state = SEC_CMD_STATUS_OK; + +out: + input_info(true, ts->dev, "%s: %d%s\n", __func__, sec->item_count, sec->cmd_result_all); +} + +static int glove_mode_store(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + ts->glove_mode = sec->cmd_param[0]; + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %d\n", __func__, sec->cmd_param[0]); + + return SEC_SUCCESS; +} + +static void glove_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret; + + ret = glove_mode_store(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + ret = synaptics_ts_set_dynamic_config(ts, DC_ENABLE_HIGHSENSMODE, ts->glove_mode); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed, retval = %d\n", __func__, ret); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + } + + input_info(true, ts->dev, "%s: %s\n", __func__, sec->cmd_state == SEC_CMD_STATUS_OK ? "OK" : "NG"); +} + +static int dead_zone_store(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + ts->dead_zone = sec->cmd_param[0]; + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %d\n", __func__, sec->cmd_param[0]); + + return SEC_SUCCESS; +} + +static void dead_zone_enable(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret; + + ret = dead_zone_store(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + ret = synaptics_ts_set_dynamic_config(ts, DC_ENABLE_DEADZONE, ts->dead_zone); + if (ret < 0) { + input_err(true, ts->dev, + "%s: failed to set deadzone\n", __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + } + + input_info(true, ts->dev, "%s: %s\n", __func__, sec->cmd_state == SEC_CMD_STATUS_OK ? "OK" : "NG"); +}; + +static int clear_cover_mode_store(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 3) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + if (sec->cmd_param[0] > 1) { + ts->plat_data->cover_type = sec->cmd_param[1]; + ts->cover_closed = true; + } else { + ts->cover_closed = false; + } + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: OK %d,%d\n", __func__, sec->cmd_param[0], sec->cmd_param[1]); + + return SEC_SUCCESS; +} + +static void clear_cover_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret; + + ret = clear_cover_mode_store(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + synaptics_ts_set_cover_type(ts, ts->cover_closed); + input_info(true, ts->dev, "%s: OK\n", __func__); +} + +static void run_rawdata_read_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (ts->tsp_dump_lock == 1) { + input_err(true, ts->dev, "%s: already checking now\n", __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: IC is power off\n", __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + synaptics_ts_rawdata_read_all(ts); + + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: OK\n", __func__); +} + +static int spay_store(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + if (sec->cmd_param[0]) + ts->plat_data->lowpower_mode |= SEC_TS_MODE_SPONGE_SWIPE; + else + ts->plat_data->lowpower_mode &= ~SEC_TS_MODE_SPONGE_SWIPE; + sec->cmd_state = SEC_CMD_STATUS_OK; + + input_info(true, ts->dev, "%s: %s, %02X\n", + __func__, sec->cmd_param[0] ? "on" : "off", ts->plat_data->lowpower_mode); + return SEC_SUCCESS; +} + +static void spay_enable(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret; + + ret = spay_store(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + synaptics_ts_set_custom_library(ts); + input_info(true, ts->dev, "%s: OK\n", __func__); +} + +static int aod_rect_store(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int i; + + input_info(true, ts->dev, "%s: w:%d, h:%d, x:%d, y:%d, lowpower_mode:0x%02X\n", + __func__, sec->cmd_param[0], sec->cmd_param[1], + sec->cmd_param[2], sec->cmd_param[3], ts->plat_data->lowpower_mode); + + for (i = 0; i < 4; i++) + ts->plat_data->aod_data.rect_data[i] = sec->cmd_param[i]; + + sec->cmd_state = SEC_CMD_STATUS_OK; + return SEC_SUCCESS; +} + +static void set_aod_rect(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret; + + ret = aod_rect_store(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + ret = synaptics_ts_set_aod_rect(ts); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + input_info(true, ts->dev, "%s: OK\n", __func__); +} + +static int aod_store(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + if (sec->cmd_param[0]) + ts->plat_data->lowpower_mode |= SEC_TS_MODE_SPONGE_AOD; + else + ts->plat_data->lowpower_mode &= ~SEC_TS_MODE_SPONGE_AOD; + + input_info(true, ts->dev, "%s: %s, %02X\n", + __func__, sec->cmd_param[0] ? "on" : "off", ts->plat_data->lowpower_mode); + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void aod_enable(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret; + + ret = aod_store(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + synaptics_ts_set_custom_library(ts); + input_info(true, ts->dev, "%s: OK\n", __func__); +} + +static int aot_store(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + if (sec->cmd_param[0]) + ts->plat_data->lowpower_mode |= SEC_TS_MODE_SPONGE_DOUBLETAP_TO_WAKEUP; + else + ts->plat_data->lowpower_mode &= ~SEC_TS_MODE_SPONGE_DOUBLETAP_TO_WAKEUP; + + input_info(true, ts->dev, "%s: %s, %02X\n", + __func__, sec->cmd_param[0] ? "on" : "off", ts->plat_data->lowpower_mode); + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void aot_enable(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret; + + ret = aot_store(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + synaptics_ts_set_custom_library(ts); + input_info(true, ts->dev, "%s: OK\n", __func__); +} + +static int singletap_store(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + if (sec->cmd_param[0]) + ts->plat_data->lowpower_mode |= SEC_TS_MODE_SPONGE_SINGLE_TAP; + else + ts->plat_data->lowpower_mode &= ~SEC_TS_MODE_SPONGE_SINGLE_TAP; + + input_info(true, ts->dev, "%s: %s, %02X\n", + __func__, sec->cmd_param[0] ? "on" : "off", ts->plat_data->lowpower_mode); + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void singletap_enable(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret; + + ret = singletap_store(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + synaptics_ts_set_custom_library(ts); + input_info(true, ts->dev, "%s: OK\n", __func__); +} + +static int ear_detect_store(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (!ts->plat_data->support_ear_detect) { + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + return SEC_ERROR; + } + + if (sec->cmd_param[0] != 0 && sec->cmd_param[0] != 1 && sec->cmd_param[0] != 3) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + ts->plat_data->ed_enable = sec->cmd_param[0]; + input_info(true, ts->dev, "%s: %s\n", + __func__, sec->cmd_param[0] ? "on" : "off"); + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void ear_detect_enable(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret; + + ret = ear_detect_store(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + synaptics_ts_ear_detect_enable(ts, ts->plat_data->ed_enable); + input_info(true, ts->dev, "%s: OK\n", __func__); +} + +static int pocket_mode_store(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (!ts->plat_data->support_ear_detect) { + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + return SEC_ERROR; + } + + if (sec->cmd_param[0] != 0 && sec->cmd_param[0] != 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + ts->plat_data->pocket_mode = sec->cmd_param[0]; + input_info(true, ts->dev, "%s: %s\n", + __func__, ts->plat_data->pocket_mode ? "on" : "off"); + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void pocket_mode_enable(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret; + + ret = pocket_mode_store(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + synaptics_ts_pocket_mode_enable(ts, ts->plat_data->pocket_mode); + input_info(true, ts->dev, "%s: OK\n", __func__); +} + +/* + * low_sensitivity_mode_store + * - input param can be 3 + * param 0 : enable / disable + * 0 - disable + * 1 - enable + * param 1 : sensitivity level + * 0 - case 1 + * 1 - case 2 + * param 2 : debug + * 0 - don't care + * 1 - start + * 2 - end + */ +static int low_sensitivity_mode_store(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + if (sec->cmd_param[1] < 0 || sec->cmd_param[1] > 7) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + if (sec->cmd_param[2] < 0 || sec->cmd_param[2] > 2) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + ts->plat_data->low_sensitivity_mode = sec->cmd_param[0] & 0x01; + ts->plat_data->low_sensitivity_mode |= ((sec->cmd_param[1] & 0x07) << 1); + ts->plat_data->low_sensitivity_mode |= ((sec->cmd_param[2] & 0x03) << 6); + input_info(true, ts->dev, "%s: 0x%02X, enable:%d, level:%d, debug:%d\n", + __func__, ts->plat_data->low_sensitivity_mode, + sec->cmd_param[0], sec->cmd_param[1], + sec->cmd_param[2]); + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void low_sensitivity_mode_enable(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret; + + ret = low_sensitivity_mode_store(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + mutex_lock(&ts->plat_data->enable_mutex); + synaptics_ts_low_sensitivity_mode_enable(ts, ts->plat_data->low_sensitivity_mode); + mutex_unlock(&ts->plat_data->enable_mutex); + + input_info(true, ts->dev, "%s: OK\n", __func__); +} + +static int fod_store(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (!ts->plat_data->support_fod) { + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + return SEC_ERROR; + } + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + if (sec->cmd_param[0]) + ts->plat_data->lowpower_mode |= SEC_TS_MODE_SPONGE_PRESS; + else + ts->plat_data->lowpower_mode &= ~SEC_TS_MODE_SPONGE_PRESS; + + ts->plat_data->fod_data.press_prop = (sec->cmd_param[1] & 0x01) | ((sec->cmd_param[2] & 0x01) << 1); + + input_info(true, ts->dev, "%s: %s, fast:%s, strict:%s, %02X\n", + __func__, sec->cmd_param[0] ? "on" : "off", + ts->plat_data->fod_data.press_prop & 1 ? "on" : "off", + ts->plat_data->fod_data.press_prop & 2 ? "on" : "off", + ts->plat_data->lowpower_mode); + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void fod_enable(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret; + + ret = fod_store(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + mutex_lock(&ts->plat_data->enable_mutex); + if (!atomic_read(&ts->plat_data->enabled) && sec_input_need_ic_off(ts->plat_data)) { + if (sec_input_cmp_ic_status(ts->dev, CHECK_LPMODE)) + disable_irq_wake(ts->irq); + ts->plat_data->stop_device(ts); + } else { + synaptics_ts_set_custom_library(ts); + synaptics_ts_set_press_property(ts); + } + mutex_unlock(&ts->plat_data->enable_mutex); + + input_info(true, ts->dev, "%s: OK\n", __func__); +} + +static void fod_lp_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (!!sec->cmd_param[0]) + ts->plat_data->fod_lp_mode = 1; + else + ts->plat_data->fod_lp_mode = 0; + + input_info(true, ts->dev, "%s: %s\n", + __func__, sec->cmd_param[0] ? "on" : "off"); + + sec->cmd_state = SEC_CMD_STATUS_OK; +} + +static int fod_rect_store(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (!ts->plat_data->support_fod) { + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + return SEC_ERROR; + } + + input_info(true, ts->dev, "%s: l:%d, t:%d, r:%d, b:%d\n", + __func__, sec->cmd_param[0], sec->cmd_param[1], + sec->cmd_param[2], sec->cmd_param[3]); + + if (!sec_input_set_fod_rect(ts->dev, sec->cmd_param)) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + sec->cmd_state = SEC_CMD_STATUS_OK; + return SEC_SUCCESS; +} + +static void set_fod_rect(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret = 0; + + ret = fod_rect_store(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + ret = synaptics_ts_set_fod_rect(ts); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + input_info(true, ts->dev, "%s: OK\n", __func__); +} + +static void fp_int_control(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret = 0; + unsigned int input; + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } else if (!ts->plat_data->support_fod) { + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + return; + } + + if (sec->cmd_param[0]) + input = 0x01; + else + input = 0x03; + + ret = synaptics_ts_set_dynamic_config(ts, DC_ENABLE_FOD_INT, (unsigned short)input); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to set (%d)\n", __func__, sec->cmd_param[0]); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + input_info(true, ts->dev, "%s: fod int %d\n", __func__, sec->cmd_param[0]); + + sec->cmd_state = SEC_CMD_STATUS_OK; +} + +static int game_mode_store(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + ts->game_mode = sec->cmd_param[0]; + input_info(true, ts->dev, "%s: %d\n", __func__, sec->cmd_param[0]); + sec->cmd_state = SEC_CMD_STATUS_OK; + return SEC_SUCCESS; +} + +static void set_game_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret; + + ret = game_mode_store(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + ret = synaptics_ts_set_dynamic_config(ts, DC_ENABLE_GAMEMODE, ts->game_mode); + if (ret < 0) { + input_err(true, ts->dev, + "%s: failed to set deadzone\n", __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + if (ts->game_mode == 0) { + if (ts->raw_mode && ts->plat_data->support_rawdata) + synaptics_ts_set_immediate_dynamic_config(ts, DC_ENABLE_EXTENDED_TOUCH_REPORT_CONTROL, 0x01); + } + + input_info(true, ts->dev, "%s: OK\n", __func__); +} + +/* + * index 0 : set edge handler + * 1 : portrait (normal) mode + * 2 : landscape mode + * + * data + * 0, X (direction), X (y start), X (y end) + * direction : 0 (off), 1 (left), 2 (right) + * ex) echo set_grip_data,0,2,600,900 > cmd + * + * 1, X (edge zone), X (dead zone up x), X (dead zone down x), X (dead zone up y), X (dead zone bottom x), X (dead zone down y) + * ex) echo set_grip_data,1,60,10,32,926,32,3088 > cmd + * + * 2, 1 (landscape mode), X (edge zone), X (dead zone x), X (dead zone top y), X (dead zone bottom y), X (edge zone top y), X (edge zone bottom y) + * ex) echo set_grip_data,2,1,200,100,120,0 > cmd + * + * 2, 0 (portrait mode) + * ex) echo set_grip_data,2,0 > cmd + */ + +static int grip_data_store(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int mode; + + mode = sec_input_store_grip_data(ts->dev, sec->cmd_param); + if (mode < 0) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + sec->cmd_state = SEC_CMD_STATUS_OK; + return mode; +} + +static void set_grip_data(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int mode; + + mode = grip_data_store(device_data); + if (mode < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + mutex_lock(&ts->plat_data->enable_mutex); + synaptics_set_grip_data_to_ic(ts->dev, mode); + mutex_unlock(&ts->plat_data->enable_mutex); +} + +static int fix_active_mode_store(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + ts->fix_active_mode = sec->cmd_param[0]; + input_info(true, ts->dev, "%s: %d\n", __func__, sec->cmd_param[0]); + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void fix_active_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret; + + ret = fix_active_mode_store(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + ret = synaptics_ts_set_dynamic_config(ts, DC_DISABLE_DOZE, ts->fix_active_mode); + if (ret < 0) { + input_err(true, ts->dev, + "%s: failed to set fix active mode\n", __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + input_info(true, ts->dev, "%s: OK\n", __func__); +}; + +enum prox_intensity_test_item { + PROX_INTENSITY_TEST_THD_X = 0, + PROX_INTENSITY_TEST_THD_Y, + PROX_INTENSITY_TEST_SUM_X, + PROX_INTENSITY_TEST_SUM_Y, + PROX_INTENSITY_TEST_MAX, +}; +static void run_prox_intensity_read_all(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + struct synaptics_ts_buffer test_data; + short *data_ptr = NULL; + short value[PROX_INTENSITY_TEST_MAX] = { 0 }; + int ret, i; + + sec_cmd_set_default_result(sec); + + if (!ts->plat_data->support_ear_detect) { + input_info(true, ts->dev, "%s: ear detection is not supported\n", __func__); + snprintf(buff, sizeof(buff), "NA"); + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + return; + } + + synaptics_ts_buf_init(&test_data); + + ret = synaptics_ts_run_production_test(ts, + TEST_PID198_PROX_INTENSITY, + &test_data); + if (ret < 0) { + input_err(true, ts->dev, "%s: Fail to run test %d\n", + __func__, TEST_PID198_PROX_INTENSITY); + synaptics_ts_buf_release(&test_data); + snprintf(buff, sizeof(buff), "NG"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + return; + } + + data_ptr = (short *)&test_data.buf[0]; + for (i = 0; i < PROX_INTENSITY_TEST_MAX; i++) { + value[i] = *data_ptr; + data_ptr++; + } + + synaptics_ts_buf_release(&test_data); + + snprintf(buff, sizeof(buff), "SUM_X:%d SUM_Y:%d THD_X:%d THD_Y:%d", + value[PROX_INTENSITY_TEST_SUM_X], value[PROX_INTENSITY_TEST_SUM_Y], + value[PROX_INTENSITY_TEST_THD_X], value[PROX_INTENSITY_TEST_THD_Y]); + sec->cmd_state = SEC_CMD_STATUS_OK; + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void touch_aging_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + unsigned char resp_code; + unsigned char command; + struct synaptics_ts_buffer resp; + int ret = 0; + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + if (IS_NOT_APP_FW_MODE(ts->dev_mode)) { + input_err(true, ts->dev, "%s: Device is not in application fw mode, mode: %x\n", __func__, + ts->dev_mode); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + if (sec->cmd_param[0]) + command = CMD_SET_AGING_MODE_ON; + else + command = CMD_SET_AGING_MODE_OFF; + + synaptics_ts_buf_init(&resp); + + ret = synaptics_ts_send_command(ts, + command, + NULL, + 0, + &resp_code, + &resp, + FORCE_ATTN_DRIVEN); + if (ret < 0) + sec->cmd_state = SEC_CMD_STATUS_FAIL; + else + sec->cmd_state = SEC_CMD_STATUS_OK; + + synaptics_ts_buf_release(&resp); + + input_info(true, ts->dev, "%s: %d %s\n", __func__, + sec->cmd_param[0], sec->cmd_state == SEC_CMD_STATUS_OK ? "OK" : "NG"); +} + +/* for game mode + * byte[0]: Setting for the Game Mode with 240Hz scan rate + * - 0: Disable + * - 1: Enable + * + * byte[1]: Vsycn mode + * - 0: Normal 60 + * - 1: HS60 + * - 2: HS120 + * - 3: VSYNC 48 + * - 4: VSYNC 96 + */ + +static int scan_rate_store(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1 || sec->cmd_param[1] < 0 || sec->cmd_param[1] > 4) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + ts->scan_rate = sec->cmd_param[0]; + input_info(true, ts->dev, "%s: %d\n", __func__, sec->cmd_param[0]); + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void set_scan_rate(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret; + + ret = scan_rate_store(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + ret = synaptics_ts_set_dynamic_config(ts, DC_SET_SCANRATE, ts->scan_rate); + if (ret < 0) { + input_err(true, ts->dev, + "%s: failed to set fix_active_mode\n", __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + input_info(true, ts->dev, "%s: OK\n", __func__); +} + +/* + * cmd_param[0] means, + * 0 : normal (60hz) + * 1 : adaptive + * 2 : always (120hz) + */ + +static int refresh_rate_mode_store(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (!ts->plat_data->support_refresh_rate_mode) { + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + return SEC_ERROR; + } + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 2) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + if (sec->cmd_param[0] > 0) { + /* 120hz */ + ts->refresh_rate = 1 | (2 << 8); + } else { + /* 60hz */ + ts->refresh_rate = 0 | (1 << 8); + } + + input_info(true, ts->dev, "%s: %d, refresh_rate:0x%02X\n", + __func__, sec->cmd_param[0], ts->refresh_rate); + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} +static void refresh_rate_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret; + + ret = refresh_rate_mode_store(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + ret = synaptics_ts_set_dynamic_config(ts, DC_SET_SCANRATE, ts->refresh_rate); + if (ret < 0) { + input_err(true, ts->dev, + "%s: failed to set refresh rate mode\n", __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + input_info(true, ts->dev, "%s: OK\n", __func__); +} + +static int sip_mode_store(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + ts->sip_mode = sec->cmd_param[0]; + input_info(true, ts->dev, "%s: %d\n", __func__, sec->cmd_param[0]); + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void set_sip_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret; + + ret = sip_mode_store(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + ret = synaptics_ts_set_dynamic_config(ts, DC_ENABLE_SIPMODE, ts->sip_mode); + if (ret < 0) { + input_err(true, ts->dev, + "%s: failed to set sip mode\n", __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + input_info(true, ts->dev, "%s: OK\n", __func__); +} + +static int wirelesscharger_mode_store(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret; + + input_info(true, ts->dev, "%s: %d,%d\n", __func__, sec->cmd_param[0], sec->cmd_param[1]); + + ret = sec_input_check_wirelesscharger_mode(ts->dev, sec->cmd_param[0], sec->cmd_param[1]); + if (ret == SEC_ERROR) + sec->cmd_state = SEC_CMD_STATUS_FAIL; + else + sec->cmd_state = SEC_CMD_STATUS_OK; + + return ret; +} + +static void set_wirelesscharger_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret; + + ret = wirelesscharger_mode_store(device_data); + if (ret != SEC_SUCCESS) + return; + + ret = synaptics_ts_set_dynamic_config(ts, DC_ENABLE_WIRELESS_CHARGER, + ts->plat_data->wirelesscharger_mode); + if (ret < 0) { + input_err(true, ts->dev, + "%s: failed to set wireless charge mode\n", __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + input_info(true, ts->dev, "%s: OK\n", __func__); +} + +static int note_mode_store(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return SEC_ERROR; + } + + ts->note_mode = sec->cmd_param[0]; + input_info(true, ts->dev, "%s: %d\n", __func__, sec->cmd_param[0]); + sec->cmd_state = SEC_CMD_STATUS_OK; + + return SEC_SUCCESS; +} + +static void set_note_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + int ret; + + ret = note_mode_store(device_data); + if (ret < 0) { + input_info(true, ts->dev, "%s: NG\n", __func__); + return; + } + + ret = synaptics_ts_set_dynamic_config(ts, DC_ENABLE_NOTEMODE, ts->note_mode); + if (ret < 0) { + input_err(true, ts->dev, + "%s: failed to set note mode\n", __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + input_info(true, ts->dev, "%s: OK\n", __func__); +} + +static void debug(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + ts->debug_flag = sec->cmd_param[0]; + + input_info(true, ts->dev, "%s: %d\n", __func__, ts->debug_flag); + sec->cmd_state = SEC_CMD_STATUS_OK; +} + +static void set_fold_state(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (IS_NOT_FOLD_DEV(ts->multi_dev)) { + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + return; + } + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + } + + sec_input_set_fold_state(ts->multi_dev, sec->cmd_param[0]); + sec->cmd_state = SEC_CMD_STATUS_OK; +} + +static int rawdata_store(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (!ts->plat_data->support_rawdata) { + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + return SEC_ERROR; + } + + ts->raw_mode = sec->cmd_param[0]; + input_info(true, ts->dev, "%s: %d\n", __func__, sec->cmd_param[0]); + sec->cmd_state = SEC_CMD_STATUS_OK; +#if IS_ENABLED(CONFIG_SEC_INPUT_RAWDATA) + sec_input_rawdata_buffer_alloc(); +#endif + return SEC_SUCCESS; +} + +static void rawdata_init(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (rawdata_store(device_data) < 0) + return; + + if (ts->raw_mode) { + if (ts->game_mode) + input_info(true, ts->dev, "%s: game mode is ongoing\n", __func__); + else + synaptics_ts_set_immediate_dynamic_config(ts, DC_ENABLE_EXTENDED_TOUCH_REPORT_CONTROL, 0x01); + } else { + synaptics_ts_set_immediate_dynamic_config(ts, DC_ENABLE_EXTENDED_TOUCH_REPORT_CONTROL, 0x00); + } + + input_info(true, ts->dev, "%s: OK\n", __func__); +} + +static void blocking_palm(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + if (!ts->plat_data->support_rawdata) { + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + return; + } + + ts->plat_data->blocking_palm = sec->cmd_param[0]; + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: OK\n", __func__); +} + +static void get_status(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + + snprintf(buff, sizeof(buff), "WET:%d,NOISE:%d,FREQ:%d", + ts->plat_data->wet_mode, ts->plat_data->noise_mode, ts->plat_data->freq_id); + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, ts->dev, "%s: %s\n", __func__, buff); +} + +static void not_support_cmd(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct synaptics_ts_data *ts = container_of(sec, struct synaptics_ts_data, sec); + + input_info(true, ts->dev, "%s\n", __func__); + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; +} + +static struct sec_cmd sec_cmds[] = { + {SEC_CMD_V2("fw_update", fw_update, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("get_fw_ver_bin", get_fw_ver_bin, NULL, CHECK_ALL, WAIT_RESULT),}, + {SEC_CMD_V2("get_fw_ver_ic", get_fw_ver_ic, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("module_off_master", module_off_master, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("module_on_master", module_on_master, NULL, CHECK_POWEROFF, WAIT_RESULT),}, + {SEC_CMD_V2("get_chip_vendor", get_chip_vendor, NULL, CHECK_ALL, WAIT_RESULT),}, + {SEC_CMD_V2("get_chip_name", get_chip_name, NULL, CHECK_ALL, WAIT_RESULT),}, + {SEC_CMD_V2("get_x_num", get_x_num, NULL, CHECK_ALL, WAIT_RESULT),}, + {SEC_CMD_V2("get_y_num", get_y_num, NULL, CHECK_ALL, WAIT_RESULT),}, + {SEC_CMD_V2("run_full_rawcap_read", run_full_rawcap_read, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_full_rawcap_read_all", run_full_rawcap_read_all, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_trans_rawcap_read", run_trans_rawcap_read, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_trans_rawcap_read_all", run_trans_rawcap_read_all, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_delta_noise_read", run_delta_noise_read, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_delta_noise_read_all", run_delta_noise_read_all, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_abs_rawcap_read", run_abs_rawcap_read, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_abs_rawcap_read_all", run_abs_rawcap_read_all, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_abs_noise_read", run_abs_noise_read, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_abs_noise_read_all", run_abs_noise_read_all, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("get_gap_data_x_all", get_gap_data_x_all, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("get_gap_data_y_all", get_gap_data_y_all, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("get_gap_data_all", get_gap_data_all, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_sram_test", run_sram_test, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_ate_test", run_ate_test, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_interrupt_gpio_test", run_interrupt_gpio_test, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_factory_miscalibration", run_factory_miscalibration, check_support_calibration, + CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_miscalibration", run_miscalibration, check_support_calibration, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_force_calibration", run_force_calibration, run_force_calibration_store, + CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_miscal_data_read_all", run_miscal_data_read_all, check_support_calibration, + CHECK_ON_LP, WAIT_RESULT),}, + // {SEC_CMD_V2("get_wet_mode", get_wet_mode, NULL, CHECK_ALL, WAIT_RESULT),}, + {SEC_CMD_V2("run_cs_raw_read_all", run_trans_rawcap_read_all, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_cs_delta_read_all", run_delta_noise_read_all, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_rawdata_read_all_for_ghost", run_rawdata_read_all, NULL, CHECK_ALL, WAIT_RESULT),}, + {SEC_CMD_V2("run_trx_short_test", run_trx_short_test, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_jitter_delta_test", run_jitter_delta_test, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("get_crc_check", get_crc_check, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("factory_cmd_result_all", factory_cmd_result_all, NULL, CHECK_ALL, WAIT_RESULT),}, + {SEC_CMD_V2("factory_cmd_result_all_imagetest", factory_cmd_result_all_imagetest, NULL, + CHECK_ALL, WAIT_RESULT),}, + {SEC_CMD_V2("run_snr_non_touched", run_snr_non_touched, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("run_snr_touched", run_snr_touched, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("set_factory_level", set_factory_level, NULL, CHECK_ALL, WAIT_RESULT),}, + {SEC_CMD_V2_H("fix_active_mode", fix_active_mode, fix_active_mode_store, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("touch_aging_mode", touch_aging_mode, NULL, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2("run_prox_intensity_read_all", run_prox_intensity_read_all, NULL, CHECK_ON_LP, WAIT_RESULT),}, +#ifdef TCLM_CONCEPT + {SEC_CMD_V2("set_tsp_test_result", set_tsp_test_result, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("get_tsp_test_result", get_tsp_test_result, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("clear_tsp_test_result", clear_tsp_test_result, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("increase_disassemble_count", increase_disassemble_count, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("get_disassemble_count", get_disassemble_count, NULL, CHECK_ON_LP, WAIT_RESULT),}, + {SEC_CMD_V2("get_pat_information", get_pat_information, NULL, CHECK_ALL, WAIT_RESULT),}, + {SEC_CMD_V2("set_external_factory", set_external_factory, NULL, CHECK_ALL, WAIT_RESULT),}, +#endif + {SEC_CMD_V2_H("glove_mode", glove_mode, glove_mode_store, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2("dead_zone_enable", dead_zone_enable, dead_zone_store, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("clear_cover_mode", clear_cover_mode, clear_cover_mode_store, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("set_wirelesscharger_mode", set_wirelesscharger_mode, wirelesscharger_mode_store, + CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("spay_enable", spay_enable, spay_store, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2("set_aod_rect", set_aod_rect, aod_rect_store, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("aod_enable", aod_enable, aod_store, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("aot_enable", aot_enable, aot_store, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2("fod_enable", fod_enable, fod_store, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("fod_lp_mode", fod_lp_mode, NULL, CHECK_ALL, EXIT_RESULT),}, + {SEC_CMD_V2("set_fod_rect", set_fod_rect, fod_rect_store, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2("fp_int_control", fp_int_control, NULL, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("singletap_enable", singletap_enable, singletap_store, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("ear_detect_enable", ear_detect_enable, ear_detect_store, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("pocket_mode_enable", pocket_mode_enable, pocket_mode_store, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2("low_sensitivity_mode_enable", low_sensitivity_mode_enable, low_sensitivity_mode_store, + CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2("set_grip_data", set_grip_data, grip_data_store, CHECK_ON_LP, EXIT_RESULT),}, + // {SEC_CMD_V2_H("external_noise_mode", external_noise_mode, external_noise_mode_store, + // CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("set_scan_rate", set_scan_rate, scan_rate_store, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("refresh_rate_mode", refresh_rate_mode, refresh_rate_mode_store, CHECK_ON_LP, EXIT_RESULT),}, + // {SEC_CMD_V2_H("set_touchable_area", set_touchable_area, touchable_area_store, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2("set_sip_mode", set_sip_mode, sip_mode_store, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("set_note_mode", set_note_mode, note_mode_store, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2_H("set_game_mode", set_game_mode, game_mode_store, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2("debug", debug, NULL, CHECK_ALL, EXIT_RESULT),}, + {SEC_CMD_V2("rawdata_init", rawdata_init, rawdata_store, CHECK_ON_LP, EXIT_RESULT),}, + {SEC_CMD_V2("blocking_palm", blocking_palm, NULL, CHECK_ALL, EXIT_RESULT),}, + {SEC_CMD_V2("set_fold_state", set_fold_state, NULL, CHECK_ALL, EXIT_RESULT),}, + {SEC_CMD_V2("get_status", get_status, NULL, CHECK_ALL, EXIT_RESULT),}, + {SEC_CMD_V2("not_support_cmd", not_support_cmd, NULL, CHECK_ALL, EXIT_RESULT),}, +}; + +int synaptics_ts_fn_init(struct synaptics_ts_data *ts) +{ + int retval = 0; + + retval = sec_cmd_init(&ts->sec, ts->dev, sec_cmds, ARRAY_SIZE(sec_cmds), + GET_SEC_CLASS_DEVT_TSP(ts->multi_dev), &cmd_attr_group); + if (retval < 0) { + input_err(true, ts->dev, + "%s: Failed to sec_cmd_init\n", __func__); + return retval; + } + + retval = sysfs_create_link(&ts->sec.fac_dev->kobj, + &ts->plat_data->input_dev->dev.kobj, "input"); + if (retval < 0) { + input_err(true, ts->dev, + "%s: Failed to create input symbolic link\n", + __func__); + goto err_create_link; + } + + return 0; + +err_create_link: + sec_cmd_exit(&ts->sec, GET_SEC_CLASS_DEVT_TSP(ts->multi_dev)); + return retval; + +} + +void synaptics_ts_fn_remove(struct synaptics_ts_data *ts) +{ + input_err(true, ts->dev, "%s\n", __func__); + + sysfs_remove_link(&ts->sec.fac_dev->kobj, "input"); + + sec_cmd_exit(&ts->sec, GET_SEC_CLASS_DEVT_TSP(ts->multi_dev)); +} + +MODULE_LICENSE("GPL"); diff --git a/drivers/input/sec_input/synaptics/synaptics_core.c b/drivers/input/sec_input/synaptics/synaptics_core.c new file mode 100644 index 000000000000..55a73488ef6f --- /dev/null +++ b/drivers/input/sec_input/synaptics/synaptics_core.c @@ -0,0 +1,1139 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Samsung Electronics Co., Ltd. + * + * 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. + */ + +#include "synaptics_dev.h" +#include "synaptics_reg.h" + +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) +irqreturn_t synaptics_secure_filter_interrupt(struct synaptics_ts_data *ts) +{ + if (atomic_read(&ts->plat_data->secure_enabled) == SECURE_TOUCH_ENABLE) { + if (atomic_cmpxchg(&ts->plat_data->secure_pending_irqs, 0, 1) == 0) { + sysfs_notify(&ts->plat_data->input_dev->dev.kobj, NULL, "secure_touch"); + + } else { + input_info(true, ts->dev, "%s: pending irq:%d\n", + __func__, (int)atomic_read(&ts->plat_data->secure_pending_irqs)); + } + + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +/** + * Sysfs attr group for secure touch & interrupt handler for Secure world. + * @atomic : syncronization for secure_enabled + * @pm_runtime : set rpm_resume or rpm_ilde + */ +static ssize_t secure_touch_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct synaptics_ts_data *ts = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d", atomic_read(&ts->plat_data->secure_enabled)); +} + +static ssize_t secure_touch_enable_store(struct device *dev, + struct device_attribute *addr, const char *buf, size_t count) +{ + struct synaptics_ts_data *ts = dev_get_drvdata(dev); + int ret; + unsigned long data; + + if (count > 2) { + input_err(true, ts->dev, + "%s: cmd length is over (%s,%d)!!\n", + __func__, buf, (int)strlen(buf)); + return -EINVAL; + } + + ret = kstrtoul(buf, 10, &data); + if (ret != 0) { + input_err(true, ts->dev, "%s: failed to read:%d\n", + __func__, ret); + return -EINVAL; + } + + if (data == 1) { + if (atomic_read(&ts->reset_is_on_going)) { + input_err(true, ts->dev, "%s: reset is on going because i2c fail\n", __func__); + return -EBUSY; + } + + /* Enable Secure World */ + if (atomic_read(&ts->plat_data->secure_enabled) == SECURE_TOUCH_ENABLE) { + input_err(true, ts->dev, "%s: already enabled\n", __func__); + return -EBUSY; + } + + sec_delay(200); + + /* synchronize_irq -> disable_irq + enable_irq + * concern about timing issue. + */ + disable_irq(ts->irq); + + /* Release All Finger */ + synaptics_ts_release_all_finger(ts); + + if (pm_runtime_get_sync(ts->plat_data->bus_master->parent) < 0) { + enable_irq(ts->irq); + input_err(true, ts->dev, "%s: failed to get pm_runtime\n", __func__); + return -EIO; + } + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&ts->synaptics_input_nb, NOTIFIER_SECURE_TOUCH_ENABLE, NULL); +#endif + reinit_completion(&ts->plat_data->secure_powerdown); + reinit_completion(&ts->plat_data->secure_interrupt); + + atomic_set(&ts->plat_data->secure_enabled, 1); + atomic_set(&ts->plat_data->secure_pending_irqs, 0); + + enable_irq(ts->irq); + + input_info(true, ts->dev, "%s: secure touch enable\n", __func__); + } else if (data == 0) { + /* Disable Secure World */ + if (atomic_read(&ts->plat_data->secure_enabled) == SECURE_TOUCH_DISABLE) { + input_err(true, ts->dev, "%s: already disabled\n", __func__); + return count; + } + + sec_delay(200); + + pm_runtime_put_sync(ts->plat_data->bus_master->parent); + atomic_set(&ts->plat_data->secure_enabled, 0); + + sysfs_notify(&ts->plat_data->input_dev->dev.kobj, NULL, "secure_touch"); + + sec_delay(10); + + synaptics_ts_irq_thread(ts->irq, ts); + complete(&ts->plat_data->secure_interrupt); + complete(&ts->plat_data->secure_powerdown); + + input_info(true, ts->dev, "%s: secure touch disable\n", __func__); + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&ts->synaptics_input_nb, NOTIFIER_SECURE_TOUCH_DISABLE, NULL); +#endif + } else { + input_err(true, ts->dev, "%s: unsupport value:%ld\n", __func__, data); + return -EINVAL; + } + + return count; +} + +static ssize_t secure_touch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct synaptics_ts_data *ts = dev_get_drvdata(dev); + int val = 0; + + if (atomic_read(&ts->plat_data->secure_enabled) == SECURE_TOUCH_DISABLE) { + input_err(true, ts->dev, "%s: disabled\n", __func__); + return -EBADF; + } + + if (atomic_cmpxchg(&ts->plat_data->secure_pending_irqs, -1, 0) == -1) { + input_err(true, ts->dev, "%s: pending irq -1\n", __func__); + return -EINVAL; + } + + if (atomic_cmpxchg(&ts->plat_data->secure_pending_irqs, 1, 0) == 1) { + val = 1; + input_err(true, ts->dev, "%s: pending irq is %d\n", + __func__, atomic_read(&ts->plat_data->secure_pending_irqs)); + } + + complete(&ts->plat_data->secure_interrupt); + + return snprintf(buf, PAGE_SIZE, "%u", val); +} + +static ssize_t secure_ownership_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "1"); +} + +static int secure_touch_init(struct synaptics_ts_data *ts) +{ + input_info(true, ts->dev, "%s\n", __func__); + + init_completion(&ts->plat_data->secure_interrupt); + init_completion(&ts->plat_data->secure_powerdown); + + return 0; +} + +static void secure_touch_stop(struct synaptics_ts_data *ts, bool stop) +{ + if (atomic_read(&ts->plat_data->secure_enabled)) { + atomic_set(&ts->plat_data->secure_pending_irqs, -1); + + sysfs_notify(&ts->plat_data->input_dev->dev.kobj, NULL, "secure_touch"); + + if (stop) + wait_for_completion_interruptible(&ts->plat_data->secure_powerdown); + + input_info(true, ts->dev, "%s: %d\n", __func__, stop); + } +} + +static DEVICE_ATTR_RW(secure_touch_enable); +static DEVICE_ATTR_RO(secure_touch); +static DEVICE_ATTR_RO(secure_ownership); +static struct attribute *secure_attr[] = { + &dev_attr_secure_touch_enable.attr, + &dev_attr_secure_touch.attr, + &dev_attr_secure_ownership.attr, + NULL, +}; + +static struct attribute_group secure_attr_group = { + .attrs = secure_attr, +}; +#endif + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) +static int synaptics_touch_notify_call(struct notifier_block *n, unsigned long data, void *v) +{ + struct synaptics_ts_data *ts = container_of(n, struct synaptics_ts_data, synaptics_input_nb); + int ret = 0; + + if (!ts->info_work_done) { + input_info(true, ts->dev, "%s: info work is not done. skip\n", __func__); + return ret; + } + + if (atomic_read(&ts->plat_data->shutdown_called)) + return -ENODEV; + + switch (data) { + case NOTIFIER_TSP_BLOCKING_REQUEST: + input_info(false, ts->dev, "%s: tsp block, ret=%d\n", __func__, ret); + sec_input_gesture_report(ts->dev, SPONGE_EVENT_TYPE_TSP_SCAN_BLOCK, 0, 0); + break; + case NOTIFIER_TSP_BLOCKING_RELEASE: + input_info(false, ts->dev, "%s: tsp unblock, ret=%d\n", __func__, ret); + sec_input_gesture_report(ts->dev, SPONGE_EVENT_TYPE_TSP_SCAN_UNBLOCK, 0, 0); + break; + default: + break; + } + return ret; +} +#endif + +void synaptics_ts_reinit(void *data) +{ + struct synaptics_ts_data *ts = (struct synaptics_ts_data *)data; + int ret = 0, retry = 0; + + if (ts->raw_mode) + synaptics_ts_set_up_rawdata_report_type(ts); + + disable_irq(ts->irq); + synaptics_ts_rezero(ts); + enable_irq(ts->irq); + + input_info(true, ts->dev, + "%s: charger=0x%x, touch_functions=0x%x, Power mode=0x%x ic mode = %d\n", + __func__, ts->plat_data->charger_flag, ts->plat_data->touch_functions, atomic_read(&ts->plat_data->power_state), ts->dev_mode); + + atomic_set(&ts->plat_data->touch_noise_status, 0); + atomic_set(&ts->plat_data->touch_pre_noise_status, 0); + ts->plat_data->wet_mode = 0; + /* buffer clear asking */ + synaptics_ts_release_all_finger(ts); + + for (retry = 0; retry < SYNAPTICS_TS_TIMEOUT_RETRY_CNT; retry++) { + ret = synaptics_ts_set_custom_library(ts); + if (ret == -ETIMEDOUT) { +#ifdef SYNAPTICS_TS_RESET_ON_REINIT_TIMEOUT + ts->plat_data->hw_param.ic_reset_count++; + input_fail_hist(true, ts->dev, "%s: timeout error. do hw reset\n", __func__); + disable_irq(ts->irq); + synaptics_ts_hw_reset(ts); + synaptics_ts_setup(ts); + enable_irq(ts->irq); + + /* reinit again*/ + if (ts->raw_mode) + synaptics_ts_set_up_rawdata_report_type(ts); + disable_irq(ts->irq); + synaptics_ts_rezero(ts); + enable_irq(ts->irq); +#else + input_fail_hist(true, ts->dev, "%s: timeout error.\n", __func__); + break; +#endif + } else { + break; + } + } + + if (retry >= SYNAPTICS_TS_TIMEOUT_RETRY_CNT) { + input_fail_hist(true, ts->dev, "%s: failed to recover ic from timeout error\n", __func__); + return; + } + + synaptics_ts_set_press_property(ts); + + if (ts->cover_closed) + synaptics_ts_set_cover_type(ts, ts->cover_closed); + + if (ts->plat_data->support_fod && ts->plat_data->fod_data.set_val) + synaptics_ts_set_fod_rect(ts); + + /* Power mode */ + if (sec_input_cmp_ic_status(ts->dev, CHECK_LPMODE)) { + ts->plat_data->lpmode(ts, TO_LOWPOWER_MODE); + sec_delay(50); + if (ts->plat_data->lowpower_mode & SEC_TS_MODE_SPONGE_AOD) + synaptics_ts_set_aod_rect(ts); + } else { + sec_input_set_grip_type(ts->dev, ONLY_EDGE_HANDLER); + } + + input_info(true, ts->dev, "%s: [mode recovery] ed:%d, pocket:%d, low_sen:%d, charger:%d, glove:%d, " + "dead_zone:%d, game:%d, fix_active:%d, scan_rate:%d, refresh_rate:%d, sip:%d, " + "wireless_charger:%d, note:%d\n", + __func__, ts->plat_data->ed_enable, ts->plat_data->pocket_mode, + ts->plat_data->low_sensitivity_mode, ts->plat_data->charger_flag, ts->glove_mode, + ts->dead_zone, ts->game_mode, ts->fix_active_mode, ts->scan_rate, ts->refresh_rate, + ts->sip_mode, ts->plat_data->wirelesscharger_mode, ts->note_mode); + + if (ts->plat_data->ed_enable) + synaptics_ts_ear_detect_enable(ts, ts->plat_data->ed_enable); + if (ts->plat_data->pocket_mode) + synaptics_ts_pocket_mode_enable(ts, ts->plat_data->pocket_mode); + if (ts->plat_data->low_sensitivity_mode) + synaptics_ts_low_sensitivity_mode_enable(ts, ts->plat_data->low_sensitivity_mode); + if (ts->plat_data->charger_flag) + synaptics_ts_set_charger_mode(ts->dev, ts->plat_data->charger_flag); + if (ts->glove_mode) + synaptics_ts_set_dynamic_config(ts, DC_ENABLE_HIGHSENSMODE, ts->glove_mode); + if (ts->dead_zone) + synaptics_ts_set_dynamic_config(ts, DC_ENABLE_DEADZONE, ts->dead_zone); + if (ts->game_mode) + synaptics_ts_set_dynamic_config(ts, DC_ENABLE_GAMEMODE, ts->game_mode); + if (ts->fix_active_mode) + synaptics_ts_set_dynamic_config(ts, DC_DISABLE_DOZE, ts->fix_active_mode); + if (ts->scan_rate) + synaptics_ts_set_dynamic_config(ts, DC_SET_SCANRATE, ts->scan_rate); + if (ts->refresh_rate) + synaptics_ts_set_dynamic_config(ts, DC_SET_SCANRATE, ts->refresh_rate); + if (ts->sip_mode) + synaptics_ts_set_dynamic_config(ts, DC_ENABLE_SIPMODE, ts->sip_mode); + if (ts->plat_data->wirelesscharger_mode != TYPE_WIRELESS_CHARGER_NONE) + synaptics_ts_set_dynamic_config(ts, DC_ENABLE_WIRELESS_CHARGER, + ts->plat_data->wirelesscharger_mode); + if (ts->note_mode) + synaptics_ts_set_dynamic_config(ts, DC_ENABLE_NOTEMODE, ts->note_mode); +} + +/** + * synaptics_ts_init_message_wrap() + * + * Initialize internal buffers and related structures for command processing. + * The function must be called to prepare all essential structures for + * command wrapper. + * + * @param + * [in] tcm_msg: message wrapper structure + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int synaptics_ts_init_message_wrap(struct synaptics_ts_data *ts, struct synaptics_ts_message_data_blob *tcm_msg) +{ + /* initialize internal buffers */ + synaptics_ts_buf_init(&tcm_msg->in); + synaptics_ts_buf_init(&tcm_msg->out); + synaptics_ts_buf_init(&tcm_msg->temp); + + /* allocate the completion event for command processing */ + if (synaptics_ts_pal_completion_alloc(&tcm_msg->cmd_completion) < 0) { + input_err(true, ts->dev, "%s:Fail to allocate cmd completion event\n", __func__); + return -EINVAL; + } + + /* allocate the cmd_mutex for command protection */ + if (synaptics_ts_pal_mutex_alloc(&tcm_msg->cmd_mutex) < 0) { + input_err(true, ts->dev, "%s:Fail to allocate cmd_mutex\n", __func__); + return -EINVAL; + } + + /* allocate the rw_mutex for rw protection */ + if (synaptics_ts_pal_mutex_alloc(&tcm_msg->rw_mutex) < 0) { + input_err(true, ts->dev, "%s:Fail to allocate rw_mutex\n", __func__); + return -EINVAL; + } + + /* set default state of command_status */ + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_IDLE); + + /* allocate the internal buffer.in at first */ + synaptics_ts_buf_lock(&tcm_msg->in); + + if (synaptics_ts_buf_alloc(&tcm_msg->in, SYNAPTICS_TS_MESSAGE_HEADER_SIZE) < 0) { + input_err(true, ts->dev, "%s:Fail to allocate memory for buf.in (size = %d)\n", __func__, + SYNAPTICS_TS_MESSAGE_HEADER_SIZE); + tcm_msg->in.buf_size = 0; + tcm_msg->in.data_length = 0; + synaptics_ts_buf_unlock(&tcm_msg->in); + return -EINVAL; + } + tcm_msg->in.buf_size = SYNAPTICS_TS_MESSAGE_HEADER_SIZE; + + synaptics_ts_buf_unlock(&tcm_msg->in); + + return 0; +} + +/** + * synaptics_ts_del_message_wrap() + * + * Remove message wrapper interface and internal buffers. + * Call the function once the message wrapper is no longer needed. + * + * @param + * [in] tcm_msg: message wrapper structure + * + * @return + * none. + */ +static void synaptics_ts_del_message_wrap(struct synaptics_ts_message_data_blob *tcm_msg) +{ + /* release the mutex */ + synaptics_ts_pal_mutex_free(&tcm_msg->rw_mutex); + synaptics_ts_pal_mutex_free(&tcm_msg->cmd_mutex); + + /* release the completion event */ + synaptics_ts_pal_completion_free(&tcm_msg->cmd_completion); + + /* release internal buffers */ + synaptics_ts_buf_release(&tcm_msg->temp); + synaptics_ts_buf_release(&tcm_msg->out); + synaptics_ts_buf_release(&tcm_msg->in); +} + +/** + * synaptics_ts_allocate_device() + * + * Create the TouchCom core device handle. + * This function must be called in order to allocate the main device handle, + * structure synaptics_ts_dev, which will be passed to all other operations and + * functions within the entire source code. + * + * Meanwhile, caller has to prepare specific synaptics_ts_hw_interface structure, + * so that all the implemented functions can access hardware components + * through synaptics_ts_hw_interface. + * + * @param + * [out] ptcm_dev_ptr: a pointer to the device handle returned + * [ in] hw_if: hardware-specific data on target platform + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int synaptics_ts_allocate_device(struct synaptics_ts_data *ts) +{ + int retval = 0; + + ts->write_message = NULL; + ts->read_message = NULL; + ts->write_immediate_message = NULL; + + /* allocate internal buffers */ + synaptics_ts_buf_init(&ts->report_buf); + synaptics_ts_buf_init(&ts->resp_buf); + synaptics_ts_buf_init(&ts->external_buf); + synaptics_ts_buf_init(&ts->touch_config); + synaptics_ts_buf_init(&ts->event_data); + + /* initialize the command wrapper interface */ + retval = synaptics_ts_init_message_wrap(ts, &ts->msg_data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to initialize command interface\n", __func__); + goto err_init_message_wrap; + } + + input_err(true, ts->dev, "%s:TouchCom core module created, ver.: %d.%02d\n", __func__, + (unsigned char)(SYNA_TCM_CORE_LIB_VERSION >> 8), + (unsigned char)SYNA_TCM_CORE_LIB_VERSION & 0xff); + + input_err(true, ts->dev, "%s:Capability: wr_chunk(%d), rd_chunk(%d)\n", __func__, + ts->max_wr_size, ts->max_rd_size); + + return 0; + +err_init_message_wrap: + synaptics_ts_buf_release(&ts->touch_config); + synaptics_ts_buf_release(&ts->external_buf); + synaptics_ts_buf_release(&ts->report_buf); + synaptics_ts_buf_release(&ts->resp_buf); + synaptics_ts_buf_release(&ts->event_data); + + return retval; +} + +/** + * synaptics_ts_remove_device() + * + * Remove the TouchCom core device handler. + * This function must be invoked when the device is no longer needed. + * + * @param + * [ in] tcm_dev: the device handle + * + * @return + * none. + */ +void synaptics_ts_remove_device(struct synaptics_ts_data *ts) +{ + if (!ts) { + pr_err("%s%s: Invalid tcm device handle\n", SECLOG, __func__); + return; + } + + /* release the command interface */ + synaptics_ts_del_message_wrap(&ts->msg_data); + + /* release buffers */ + synaptics_ts_buf_release(&ts->touch_config); + synaptics_ts_buf_release(&ts->external_buf); + synaptics_ts_buf_release(&ts->report_buf); + synaptics_ts_buf_release(&ts->resp_buf); + synaptics_ts_buf_release(&ts->event_data); + + input_err(true, ts->dev, "%s: Failtcm device handle removed\n", __func__); +} + + +/* + * don't need it in interrupt handler in reality, but, need it in vendor IC for requesting vendor IC. + * If you are requested additional i2c protocol in interrupt handler by vendor. + * please add it in synaptics_ts_external_func. + */ +void synaptics_ts_external_func(struct synaptics_ts_data *ts) +{ + if (ts->support_immediate_cmd) + sec_input_set_temperature(ts->dev, SEC_INPUT_SET_TEMPERATURE_IN_IRQ); +} + +int synaptics_ts_enable(struct device *dev) +{ + struct synaptics_ts_data *ts = dev_get_drvdata(dev); + int ret; + + if (!ts->probe_done) { + input_err(true, ts->dev, "%s: probe is not done yet\n", __func__); + ts->plat_data->first_booting_disabled = false; + return 0; + } + + cancel_delayed_work_sync(&ts->work_read_info); + + atomic_set(&ts->plat_data->enabled, 1); + ts->plat_data->prox_power_off = 0; + +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) + secure_touch_stop(ts, 0); +#endif + + if (sec_input_cmp_ic_status(ts->dev, CHECK_LPMODE)) { + ts->plat_data->lpmode(ts, TO_TOUCH_MODE); + sec_input_set_grip_type(ts->dev, ONLY_EDGE_HANDLER); + if (ts->plat_data->low_sensitivity_mode) + synaptics_ts_low_sensitivity_mode_enable(ts, ts->plat_data->low_sensitivity_mode); + } else { + ret = ts->plat_data->start_device(ts); + if (ret < 0) + input_err(true, ts->dev, "%s: Failed to start device\n", __func__); + } + + sec_input_set_temperature(ts->dev, SEC_INPUT_SET_TEMPERATURE_FORCE); + + cancel_delayed_work(&ts->work_print_info); + ts->plat_data->print_info_cnt_open = 0; + ts->plat_data->print_info_cnt_release = 0; + if (!atomic_read(&ts->plat_data->shutdown_called)) + schedule_work(&ts->work_print_info.work); + + if (atomic_read(&ts->plat_data->power_state) != SEC_INPUT_STATE_POWER_OFF) + sec_input_forced_enable_irq(ts->irq); + return 0; +} + +int synaptics_ts_disable(struct device *dev) +{ + struct synaptics_ts_data *ts = dev_get_drvdata(dev); + + if (!ts->probe_done) { + ts->plat_data->first_booting_disabled = true; + input_err(true, ts->dev, "%s: probe is not done yet\n", __func__); + return 0; + } + + cancel_delayed_work_sync(&ts->work_read_info); + cancel_delayed_work_sync(&ts->work_print_info); + cancel_delayed_work_sync(&ts->set_temperature_work); + + if (atomic_read(&ts->plat_data->shutdown_called)) { + input_err(true, ts->dev, "%s shutdown was called\n", __func__); + return 0; + } + + atomic_set(&ts->plat_data->enabled, 0); + +#ifdef TCLM_CONCEPT + sec_tclm_debug_info(ts->tdata); +#endif + sec_input_print_info(ts->dev, ts->tdata); +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) + secure_touch_stop(ts, 1); +#if IS_ENABLED(CONFIG_INPUT_SEC_TRUSTED_TOUCH) + if (atomic_read(&ts->plat_data->pvm->trusted_touch_enabled)) { + input_err(true, ts->dev, "%s wait for disabling trusted touch\n", __func__); + wait_for_completion_interruptible(&ts->plat_data->pvm->trusted_touch_powerdown); + } +#endif +#endif +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) + stui_cancel_session(); +#endif + cancel_delayed_work(&ts->reset_work); + + if (sec_input_need_ic_off(ts->plat_data)) + ts->plat_data->stop_device(ts); + else + ts->plat_data->lpmode(ts, TO_LOWPOWER_MODE); + + return 0; +} + +int synaptics_ts_stop_device(void *data) +{ + struct synaptics_ts_data *ts = (struct synaptics_ts_data *)data; + struct irq_desc *desc = irq_to_desc(ts->irq); + + input_info(true, ts->dev, "%s\n", __func__); + +#if IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) && IS_ENABLED(CONFIG_SEC_FACTORY) + if (ts->panel_attached == SYNAPTICS_PANEL_DETACHED) { + input_err(true, ts->dev, "%s: panel detached(%d) skip!\n", __func__, ts->panel_attached); + return 0; + } +#endif + + if (ts->sec.fac_dev) + get_lp_dump_show(ts->sec.fac_dev, NULL, NULL); + + mutex_lock(&ts->device_mutex); + + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: already power off\n", __func__); + goto out; + } + + disable_irq(ts->irq); + + while (desc->wake_depth > 0) + disable_irq_wake(ts->irq); + + atomic_set(&ts->plat_data->power_state, SEC_INPUT_STATE_POWER_OFF); + + synaptics_ts_locked_release_all_finger(ts); + + ts->plat_data->power(ts->dev, false); + ts->plat_data->pinctrl_configure(ts->dev, false); + +out: + mutex_unlock(&ts->device_mutex); + return 0; +} + +int synaptics_ts_start_device(void *data) +{ + struct synaptics_ts_data *ts = (struct synaptics_ts_data *)data; + int ret = 0; + + input_info(true, ts->dev, "%s\n", __func__); + +#if IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) && IS_ENABLED(CONFIG_SEC_FACTORY) + if (ts->panel_attached == SYNAPTICS_PANEL_DETACHED) { + input_err(true, ts->dev, "%s: panel detached(%d) skip!\n", __func__, ts->panel_attached); + return 0; + } +#endif + + ts->plat_data->pinctrl_configure(ts->dev, true); + + mutex_lock(&ts->device_mutex); + + if (atomic_read(&ts->plat_data->power_state) == SEC_INPUT_STATE_POWER_ON) { + input_err(true, ts->dev, "%s: already power on\n", __func__); + goto out; + } + + synaptics_ts_locked_release_all_finger(ts); + + ts->plat_data->power(ts->dev, true); + + sec_delay(ts->power_on_delay); + + atomic_set(&ts->plat_data->power_state, SEC_INPUT_STATE_POWER_ON); + atomic_set(&ts->plat_data->touch_noise_status, 0); + + ret = synaptics_ts_detect_protocol(ts); + if (ret < 0) { + input_fail_hist(true, ts->dev, "%s: fail to detect protocol, ret=%d\n", __func__, ret); + goto out; + } + + if (atomic_read(&ts->plat_data->power_state) != SEC_INPUT_STATE_POWER_OFF) + sec_input_forced_enable_irq(ts->irq); + + ts->plat_data->init(ts); +out: + if (IS_BOOTLOADER_MODE(ts->dev_mode)) { + ts->plat_data->hw_param.checksum_result |= SYNAPTICS_TS_CHK_BLMODE_ON_START; + input_err(true, ts->dev, "%s: device is in bootloader mode. count:%d\n", + __func__, ts->plat_data->hw_param.checksum_result); + } + mutex_unlock(&ts->device_mutex); + return ret; +} + +static int synaptics_ts_hw_init(struct synaptics_ts_data *ts) +{ + int ret = 0; + + ret = synaptics_ts_allocate_device(ts); + if (ret < 0) { + input_err(true, ts->dev, "%s: Fail to allocate TouchCom device handle\n", __func__); + goto err_allocate_cdev; + } + + ts->plat_data->pinctrl_configure(ts->dev, true); + + ts->plat_data->power(ts->dev, true); + if (gpio_is_valid(ts->plat_data->gpio_spi_cs)) + gpio_direction_output(ts->plat_data->gpio_spi_cs, 1); + + if (!ts->plat_data->regulator_boot_on) + sec_delay(ts->power_on_delay); + + atomic_set(&ts->plat_data->power_state, SEC_INPUT_STATE_POWER_ON); + + ret = synaptics_ts_setup(ts); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to synaptics_ts_setup\n", __func__); + goto err_hw_init; + } + + if (IS_BOOTLOADER_MODE(ts->dev_mode)) { + ts->plat_data->hw_param.checksum_result |= SYNAPTICS_TS_CHK_BLMODE_ON_PROBE; + input_fail_hist(true, ts->dev, "%s: device is in bootloader mode\n", __func__); + } + + input_info(true, ts->dev, "%s: request_irq = %d (gpio:%d)\n", + __func__, ts->irq, ts->plat_data->irq_gpio); + ret = devm_request_threaded_irq(ts->dev, ts->irq, NULL, synaptics_ts_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, dev_driver_string(ts->dev), ts); + if (ret < 0) { + input_err(true, ts->dev, "%s: Unable to request threaded irq\n", __func__); + return ret; + } + + __pm_stay_awake(ts->plat_data->sec_ws); + ret = synaptics_ts_fw_update_on_probe(ts); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to synaptics_ts_fw_update_on_probe\n", __func__); + __pm_relax(ts->plat_data->sec_ws); + goto err_hw_init; + } + __pm_relax(ts->plat_data->sec_ws); + + if (ts->dev_mode != SYNAPTICS_TS_MODE_APPLICATION_FIRMWARE) { + input_err(true, ts->dev, "%s: Not app mode mode: 0x%02x\n", __func__, + ts->dev_mode); + goto err_hw_init; + } + + if (ts->rows && ts->cols) { + ts->pFrame = devm_kzalloc(ts->dev, ts->rows * ts->cols * sizeof(int), GFP_KERNEL); + if (!ts->pFrame) + return -ENOMEM; + +// slsi_ts_init_proc(ts); + } else { + input_err(true, ts->dev, "%s: fail to alloc pFrame nTX:%d, nRX:%d\n", + __func__, ts->rows, ts->cols); + return -ENOMEM; + } + +err_hw_init: +err_allocate_cdev: + return ret; + +} + +static void synaptics_ts_parse_dt(struct device *dev, struct synaptics_ts_data *ts) +{ + struct device_node *np = dev->of_node; + int temp[2]; + + if (!np) { + input_err(true, dev, "%s: of_node is not exist\n", __func__); + return; + } + + ts->support_immediate_cmd = of_property_read_bool(np, "synaptics,support_immediate_cmd"); + ts->support_miscal_wet = of_property_read_bool(np, "synaptics,support_miscal_wet"); + + /* specific tcm drv/dev name is only need when device has more than 2 synaptics device */ + if (of_property_read_string(np, "synaptics,tcm_drv_name", &ts->tcm_drv_name)) + ts->tcm_drv_name = SYNAPTICS_TCM_DRV_DEFAULT; + if (of_property_read_string(np, "synaptics,tcm_dev_name", &ts->tcm_dev_name)) + ts->tcm_dev_name = SYNAPTICS_TCM_DEV_DEFAULT; + + if (of_property_read_u32_array(np, "synaptics,fw_delay", temp, 2)) { + /* + * fw erase delay need to be + * 800ms : when it use S3908 & S3907 IC + * 2000ms : when it use S3916A IC + * fw write block delay need to be + * 50ms : when it use I2C transfer + * 20ms : when it use SPI transfer + * + * default value(800ms/50ms) meets for S3908 & S3907 I2C driver + */ + ts->fw_erase_delay = ERASE_DELAY; + ts->fw_write_block_delay = WRITE_FLASH_DELAY; + } else { + ts->fw_erase_delay = temp[0]; + ts->fw_write_block_delay = temp[1]; + } + + if (of_property_read_u32(np, "synaptics,power_on_delay", &ts->power_on_delay)) + ts->power_on_delay = TOUCH_POWER_ON_DWORK_TIME; + + input_info(true, dev, "%s: support_immediate_cmd:%d, support_miscal_wet:%d, tcm_name:%s/%s," + " fw_erase_delay:%d, fw_write_block_delay:%d, power_on_delay:%d\n", + __func__, ts->support_immediate_cmd, ts->support_miscal_wet, + ts->tcm_drv_name, ts->tcm_dev_name, + ts->fw_erase_delay, ts->fw_write_block_delay, ts->power_on_delay); +} + +int synaptics_ts_init(struct synaptics_ts_data *ts) +{ + int ret = 0; + + if (!ts) + return -EINVAL; + + input_info(true, ts->dev, "%s\n", __func__); + + synaptics_ts_parse_dt(ts->dev, ts); + + sec_input_multi_device_create(ts->dev); + ts->multi_dev = ts->plat_data->multi_dev; + + if (GET_DEV_COUNT(ts->multi_dev) != MULTI_DEV_SUB) + ptsp = ts->dev; + + ts->synaptics_ts_read_sponge = synaptics_ts_read_from_sponge; + ts->synaptics_ts_write_sponge = synaptics_ts_write_to_sponge; + + ts->plat_data->dev = ts->dev; + ts->plat_data->irq = ts->irq; + ts->plat_data->probe = synaptics_ts_probe; + ts->plat_data->pinctrl_configure = sec_input_pinctrl_configure; + ts->plat_data->power = sec_input_power; + ts->plat_data->start_device = synaptics_ts_start_device; + ts->plat_data->stop_device = synaptics_ts_stop_device; + ts->plat_data->init = synaptics_ts_reinit; + ts->plat_data->lpmode = synaptics_ts_set_lowpowermode; + ts->plat_data->set_grip_data = synaptics_set_grip_data_to_ic; + if (!ts->plat_data->not_support_temp_noti) + ts->plat_data->set_temperature = synaptics_ts_set_temperature; + if (ts->plat_data->support_vbus_notifier) + ts->plat_data->set_charger_mode = synaptics_ts_set_charger_mode; + +#ifdef TCLM_CONCEPT + sec_tclm_initialize(ts->tdata); + ts->tdata->dev = ts->dev; + ts->tdata->tclm_read = synaptics_ts_tclm_read; + ts->tdata->tclm_write = synaptics_ts_tclm_write; + ts->tdata->tclm_execute_force_calibration = synaptics_ts_tclm_execute_force_calibration; + ts->tdata->tclm_parse_dt = sec_tclm_parse_dt; +#endif + + INIT_DELAYED_WORK(&ts->reset_work, synaptics_ts_reset_work); + INIT_DELAYED_WORK(&ts->work_read_info, synaptics_ts_read_info_work); + INIT_DELAYED_WORK(&ts->work_print_info, synaptics_ts_print_info_work); + INIT_DELAYED_WORK(&ts->work_read_functions, synaptics_ts_get_touch_function); + INIT_DELAYED_WORK(&ts->set_temperature_work, synaptics_ts_external_func_work); + mutex_init(&ts->device_mutex); + mutex_init(&ts->eventlock); + mutex_init(&ts->sponge_mutex); + mutex_init(&ts->fn_mutex); + + init_completion(&ts->plat_data->resume_done); + complete_all(&ts->plat_data->resume_done); + + ret = sec_input_device_register(ts->dev, ts); + if (ret) { + input_err(true, ts->dev, "failed to register input device, %d\n", ret); + goto err_register_input_device; + } + + mutex_init(&ts->plat_data->enable_mutex); + ts->plat_data->enable = synaptics_ts_enable; + ts->plat_data->disable = synaptics_ts_disable; + + if (IS_FOLD_DEV(ts->multi_dev)) { + char dev_name[16]; + + snprintf(dev_name, sizeof(dev_name), "TSP-%s: ", ts->multi_dev->name); + ts->plat_data->sec_ws = wakeup_source_register(NULL, dev_name); + } else { + ts->plat_data->sec_ws = wakeup_source_register(NULL, "TSP"); + } + +#if !IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP) + /* for vendor */ + /* create the device file and register to char device classes */ + ret = syna_cdev_create_sysfs(ts); + if (ret < 0) { + input_err(true, ts->dev, "Fail to create the device sysfs\n"); + goto error_vendor_data; + } +#endif + +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) + if (sysfs_create_group(&ts->plat_data->input_dev->dev.kobj, &secure_attr_group) < 0) + input_err(true, ts->dev, "%s: do not make secure group\n", __func__); + else + secure_touch_init(ts); + +#if IS_ENABLED(CONFIG_INPUT_SEC_TRUSTED_TOUCH) + ret = sec_trusted_touch_init(ts->dev); + if (ret < 0) + input_err(true, ts->dev, "%s: Failed to init trusted touch\n", __func__); +#endif + + sec_secure_touch_register(ts, ts->dev, ts->plat_data->ss_touch_num, &ts->plat_data->input_dev->dev.kobj); +#endif +#if IS_ENABLED(CONFIG_TOUCHSCREEN_DUMP_MODE) + sec_input_dumpkey_register(GET_DEV_COUNT(ts->multi_dev), synaptics_ts_dump_tsp_log, ts->dev); + INIT_DELAYED_WORK(&ts->check_rawdata, synaptics_ts_check_rawdata); +#endif + + input_info(true, ts->dev, "%s: init resource\n", __func__); + + return 0; + +#if !IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP) +error_vendor_data: + wakeup_source_unregister(ts->plat_data->sec_ws); +#endif + ts->plat_data->enable = NULL; + ts->plat_data->disable = NULL; +err_register_input_device: + return ret; +} + +static void synaptics_ts_release(struct synaptics_ts_data *ts) +{ + input_info(true, ts->dev, "%s\n", __func__); + + ts->plat_data->enable = NULL; + ts->plat_data->disable = NULL; + +#if IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) && IS_ENABLED(CONFIG_SEC_FACTORY) + panel_notifier_unregister(&ts->lcd_nb); +#endif + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_unregister_notify(&ts->synaptics_input_nb); +#endif + +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) + sec_secure_touch_unregister(ts->plat_data->ss_touch_num); +#endif + sec_input_multi_device_remove(ts->multi_dev); + + cancel_delayed_work_sync(&ts->work_read_info); + cancel_delayed_work_sync(&ts->work_print_info); + cancel_delayed_work_sync(&ts->work_read_functions); + cancel_delayed_work_sync(&ts->reset_work); + cancel_delayed_work_sync(&ts->set_temperature_work); + flush_delayed_work(&ts->reset_work); +#if IS_ENABLED(CONFIG_TOUCHSCREEN_DUMP_MODE) + cancel_delayed_work_sync(&ts->check_rawdata); + sec_input_dumpkey_unregister(GET_DEV_COUNT(ts->multi_dev)); +#endif + wakeup_source_unregister(ts->plat_data->sec_ws); +#if !IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP) + syna_cdev_remove_sysfs(ts); +#endif + + ts->plat_data->lowpower_mode = false; + ts->probe_done = false; + + ts->plat_data->power(ts->dev, false); +} + +#if IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) && IS_ENABLED(CONFIG_SEC_FACTORY) +static int synaptics_notifier_call(struct notifier_block *n, unsigned long event, void *data) +{ + struct synaptics_ts_data *ts = container_of(n, struct synaptics_ts_data, lcd_nb); + struct panel_notifier_event_data *evtdata = data; + + input_dbg(false, ts->dev, "%s: called! event = %ld, ev_data->state = %d\n", __func__, event, evtdata->state); + + if (event == PANEL_EVENT_UB_CON_STATE_CHANGED) { + input_info(false, ts->dev, "%s: event = %ld, ev_data->state = %d\n", __func__, event, evtdata->state); + + if (evtdata->state == PANEL_EVENT_UB_CON_STATE_DISCONNECTED) { + input_info(true, ts->dev, "%s: UB_CON_DISCONNECTED : synaptics_ts_stop_device\n", __func__); + synaptics_ts_stop_device(ts); + ts->panel_attached = SYNAPTICS_PANEL_DETACHED; + } else if(evtdata->state == PANEL_EVENT_UB_CON_STATE_CONNECTED && ts->panel_attached != SYNAPTICS_PANEL_ATTACHED) { + input_info(true, ts->dev, "%s: UB_CON_CONNECTED : panel attached!\n", __func__); + ts->panel_attached = SYNAPTICS_PANEL_ATTACHED; + } + } + + return 0; +} +#endif + +int synaptics_ts_probe(struct device *dev) +{ + struct synaptics_ts_data *ts = dev_get_drvdata(dev); + int ret = 0; + + input_info(true, ts->dev, "%s\n", __func__); + + ret = synaptics_ts_hw_init(ts); + if (ret < 0) { + input_err(true, ts->dev, "%s: fail to init hw\n", __func__); + disable_irq(ts->irq); + synaptics_ts_release(ts); + return ret; + } + + synaptics_ts_get_custom_library(ts); + synaptics_ts_set_custom_library(ts); + + sec_input_register_vbus_notifier(ts->dev); + + atomic_set(&ts->plat_data->enabled, 1); + ret = synaptics_ts_fn_init(ts); + if (ret) { + input_err(true, ts->dev, "%s: fail to init fn\n", __func__); + atomic_set(&ts->plat_data->enabled, 0); + disable_irq(ts->irq); + synaptics_ts_release(ts); + return ret; + } + + ts->probe_done = true; + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_register_notify(&ts->synaptics_input_nb, synaptics_touch_notify_call, 1); +#endif + +#if IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) && IS_ENABLED(CONFIG_SEC_FACTORY) + ts->lcd_nb.priority = 1; + ts->lcd_nb.notifier_call = synaptics_notifier_call; + ts->panel_attached = SYNAPTICS_PANEL_ATTACHED; + panel_notifier_register(&ts->lcd_nb); +#endif + + input_err(true, ts->dev, "%s: done\n", __func__); + + input_log_fix(); +#if IS_ENABLED(CONFIG_SEC_INPUT_RAWDATA) + sec_input_rawdata_init(ts->plat_data->dev, ts->sec.fac_dev); +#endif + if (!atomic_read(&ts->plat_data->shutdown_called)) + schedule_delayed_work(&ts->work_read_info, msecs_to_jiffies(50)); + + return 0; +} + +int synaptics_ts_remove(struct synaptics_ts_data *ts) +{ + if (!ts->probe_done) { + input_info(true, ts->dev, "%s don't success probe yet\n", __func__); + return 0; + } + + input_info(true, ts->dev, "%s\n", __func__); + + mutex_lock(&ts->plat_data->enable_mutex); + atomic_set(&ts->plat_data->shutdown_called, 1); + mutex_unlock(&ts->plat_data->enable_mutex); + disable_irq(ts->irq); + + sec_input_probe_work_remove(ts->plat_data); + + if (!ts->probe_done) { + input_err(true, ts->dev, "%s: probe is not done yet\n", __func__); + return 0; + } + + sec_input_unregister_vbus_notifier(ts->dev); + + synaptics_ts_release(ts); + synaptics_ts_fn_remove(ts); + synaptics_ts_remove_device(ts); + + return 0; +} + +void synaptics_ts_shutdown(struct synaptics_ts_data *ts) +{ + input_info(true, ts->dev, "%s\n", __func__); + + synaptics_ts_remove(ts); +} + + +#if IS_ENABLED(CONFIG_PM) +int synaptics_ts_pm_suspend(struct device *dev) +{ + struct synaptics_ts_data *ts = dev_get_drvdata(dev); + + reinit_completion(&ts->plat_data->resume_done); + + return 0; +} + +int synaptics_ts_pm_resume(struct device *dev) +{ + struct synaptics_ts_data *ts = dev_get_drvdata(dev); + + complete_all(&ts->plat_data->resume_done); + + return 0; +} +#endif diff --git a/drivers/input/sec_input/synaptics/synaptics_dev.h b/drivers/input/sec_input/synaptics/synaptics_dev.h new file mode 100644 index 000000000000..67f67876e085 --- /dev/null +++ b/drivers/input/sec_input/synaptics/synaptics_dev.h @@ -0,0 +1,1749 @@ +/* SPDX-License-Identifier: GPL-2.0 + * Copyright (C) 2022 Samsung Electronics Co., Ltd. + * + * 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. + */ + +#ifndef _LINUX_SYNAPTICS_TS_H_ +#define _LINUX_SYNAPTICS_TS_H_ + +#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 + +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) +#include +#endif + +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) +#include "../sec_secure_touch.h" +#include +#include +#include + +#define SECURE_TOUCH_ENABLE 1 +#define SECURE_TOUCH_DISABLE 0 + +#include +#include +#include +#include +#endif + +#if IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) && IS_ENABLED(CONFIG_SEC_FACTORY) +#include +#define SYNAPTICS_PANEL_DETACHED 0 +#define SYNAPTICS_PANEL_ATTACHED 1 +#endif + +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) +#include +#endif + +#include "../sec_tclm_v2.h" +#if IS_ENABLED(CONFIG_INPUT_TOUCHSCREEN_TCLMV2) +#define TCLM_CONCEPT +#endif + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_DUMP_MODE) +#include "../sec_tsp_dumpkey.h" +#endif + +#if IS_ENABLED(CONFIG_SEC_INPUT_RAWDATA) +#include "../sec_input_rawdata.h" +#endif + +#include "../sec_input.h" +#include "../sec_tsp_log.h" + +#ifndef I2C_M_DMA_SAFE +#define I2C_M_DMA_SAFE 0 +#endif + +#include "synaptics_reg.h" + +#define SYNAPTICS_TS_NAME "synaptics_ts" +#define SYNAPTICS_TS_I2C_NAME "synaptics_ts_i2c" +#define SYNAPTICS_TCM_DRV_DEFAULT "synaptics_tcm" +#define SYNAPTICS_TCM_DEV_DEFAULT "tcm" + +#define PRINT_SIZE 10 + +extern struct device *ptsp; + +#define SYNAPTICS_TS_I2C_RETRY_CNT 5 +#define SYNAPTICS_TS_TIMEOUT_RETRY_CNT 3 + +#define SYNAPTICS_TS_CHK_UPDATE_FAIL 1 << 0 +#define SYNAPTICS_TS_CHK_BLMODE_ON_PROBE 1 << 1 +#define SYNAPTICS_TS_CHK_BLMODE_ON_START 1 << 2 + +/* if it is not ship build, disable reset on reinit for debugging */ +#if IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP) +#define SYNAPTICS_TS_RESET_ON_REINIT_TIMEOUT +#endif + +#define ERASE_DELAY (800) +#define ERASE_DELAY_EX (15000) +#define WRITE_FLASH_DELAY (50) + +/** + * @section: Blocks to be updated + */ +enum update_area { + UPDATE_NONE = 0, + UPDATE_FIRMWARE_AND_CONFIG, + UPDATE_CONFIG, + UPDATE_ALL_BLOCKS, +}; + +/** + * @section: Data Type in flash memory + */ +enum flash_data { + FLASH_LCM_DATA = 1, + FLASH_OEM_DATA, + FLASH_PPDT_DATA, + FLASH_FORCE_CALIB_DATA, + FLASH_OPEN_SHORT_TUNING_DATA, +}; + +/** + * @section: Some specific definition in the firmware file + */ +#define ID_STRING_SIZE (32) + +#define SIZE_WORDS (8) + +#define IHEX_RECORD_SIZE (14) + +#define IHEX_MAX_BLOCKS (64) + +#define IMAGE_FILE_MAGIC_VALUE (0x4818472b) + +#define FLASH_AREA_MAGIC_VALUE (0x7c05e516) + +#define SYNAPTICS_TS_NVM_CALIB_DATA_SIZE (30) + +#define SEC_NVM_CALIB_DATA_SIZE (28) + +/** + * @section: Helper macros for firmware file parsing + */ +#define CRC32(data, length) \ + (crc32(~0, data, length) ^ ~0) + +#define VALUE(value) \ + (synaptics_ts_pal_le2_to_uint(value)) + +#define AREA_ID_STR(area) \ + (synaptics_ts_get_flash_area_string(area)) + +/** + * @section: Area Partitions in firmware + */ +enum flash_area { + AREA_NONE = 0, + /* please add the declarations below */ + + AREA_BOOT_CODE, + AREA_BOOT_CONFIG, + AREA_APP_CODE, + AREA_APP_CODE_COPRO, + AREA_APP_CONFIG, + AREA_PROD_TEST, + AREA_DISP_CONFIG, + AREA_F35_APP_CODE, + AREA_FORCE_TUNING, + AREA_GAMMA_TUNING, + AREA_TEMPERATURE_GAMM_TUNING, + AREA_CUSTOM_LCM, + AREA_LOOKUP, + AREA_CUSTOM_OEM, + AREA_OPEN_SHORT_TUNING, + AREA_CUSTOM_OTP, + AREA_PPDT, + AREA_ROMBOOT_APP_CODE, + AREA_TOOL_BOOT_CONFIG, + + /* please add the declarations above */ + AREA_MAX, +}; +/** + * @section: String of Area Partitions in firmware + */ +static char *flash_area_str[] = { + NULL, + /* please add the declarations below */ + + "BOOT_CODE", /* AREA_BOOT_CODE */ + "BOOT_CONFIG", /* AREA_BOOT_CONFIG */ + "APP_CODE", /* AREA_APP_CODE */ + "APP_CODE_COPRO", /* AREA_APP_CODE_COPRO */ + "APP_CONFIG", /* AREA_APP_CONFIG */ + "APP_PROD_TEST", /* AREA_PROD_TEST */ + "DISPLAY", /* AREA_DISP_CONFIG */ + "F35_APP_CODE", /* AREA_F35_APP_CODE */ + "FORCE", /* AREA_FORCE_TUNING */ + "GAMMA", /* AREA_GAMMA_TUNING */ + "TEMPERATURE_GAMM",/* AREA_TEMPERATURE_GAMM_TUNING */ + "LCM", /* AREA_CUSTOM_LCM */ + "LOOKUP", /* AREA_LOOKUP */ + "OEM", /* AREA_CUSTOM_OEM */ + "OPEN_SHORT", /* AREA_OPEN_SHORT_TUNING */ + "OTP", /* AREA_CUSTOM_OTP */ + "PPDT", /* AREA_PPDT */ + "ROMBOOT_APP_CODE",/* AREA_ROMBOOT_APP_CODE */ + "TOOL_BOOT_CONFIG",/* AREA_TOOL_BOOT_CONFIG */ + + /* please add the declarations above */ + NULL +}; + +typedef struct completion synaptics_ts_pal_completion_t; +typedef struct mutex synaptics_ts_pal_mutex_t; + +/** + * @section: Internal Buffer Structure + * + * This structure is taken as the internal common buffer. + */ +struct synaptics_ts_buffer { + unsigned char *buf; + unsigned int buf_size; + unsigned int data_length; + synaptics_ts_pal_mutex_t buf_mutex; + unsigned char ref_cnt; +}; + +/** + * @section: Header Content of app config defined + * in firmware file + */ +struct app_config_header { + unsigned short magic_value[4]; + unsigned char checksum[4]; + unsigned char length[2]; + unsigned char build_id[4]; + unsigned char customer_config_id[16]; +}; +/** + * @section: The Partition Descriptor defined + * in firmware file + */ +struct area_descriptor { + unsigned char magic_value[4]; + unsigned char id_string[16]; + unsigned char flags[4]; + unsigned char flash_addr_words[4]; + unsigned char length[4]; + unsigned char checksum[4]; +}; +/** + * @section: Structure for the Data Block defined + * in firmware file + */ +struct block_data { + bool available; + const unsigned char *data; + unsigned int size; + unsigned int flash_addr; + unsigned char id; +}; +/** + * @section: Structure for the Parsed Image File + */ +struct image_info { + struct block_data data[AREA_MAX]; +}; +/** + * @section: Header of Image File + * + * Define the header of firmware image file + */ +struct image_header { + unsigned char magic_value[4]; + unsigned char num_of_areas[4]; +}; +/** + * @section: Structure for the Parsed iHex File + */ +struct ihex_info { + unsigned int records; + unsigned char *bin; + unsigned int bin_size; + struct block_data block[IHEX_MAX_BLOCKS]; +}; + +/* factory test mode */ +struct synaptics_ts_test_mode { + u8 type; + int min; + int max; + bool allnode; + bool frame_channel; +}; + +/** + * @section: Specific data blob for reflash + * + * The structure contains various parameters being used in reflash + */ +struct synaptics_ts_reflash_data_blob { + /* binary data of an image file */ + const unsigned char *image; + unsigned int image_size; + /* parsed data based on given image file */ + struct image_info image_info; + /* standard information for flash access */ + unsigned int page_size; + unsigned int write_block_size; + unsigned int max_write_payload_size; + struct synaptics_ts_buffer out; +}; + +/** + * @section: Header of TouchComm v1 Message Packet + * + * The 4-byte header in the TouchComm v1 packet + */ +struct synaptics_ts_v1_message_header { + union { + struct { + unsigned char marker; + unsigned char code; + unsigned char length[2]; + }; + unsigned char data[SYNAPTICS_TS_MESSAGE_HEADER_SIZE]; + }; +}; + + +/** + * @section: TouchComm Identify Info Packet + * Ver.1: size is 24 (0x18) bytes + * Ver.2: size is extended to 32 (0x20) bytes + * + * The identify packet provides the basic TouchComm information and indicate + * that the device is ready to receive commands. + * + * The report is received whenever the device initially powers up, resets, + * or switches fw between bootloader and application modes. + */ +struct synaptics_ts_identification_info { + unsigned char version; + unsigned char mode; + unsigned char part_number[16]; + unsigned char build_id[4]; + unsigned char max_write_size[2]; + /* extension in ver.2 */ + unsigned char max_read_size[2]; + unsigned char reserved[6]; +}; + +/** + * @section: TouchComm Application Information Packet + * + * The application info packet provides the information about the application + * firmware as well as the touch controller. + */ +struct synaptics_ts_application_info { + unsigned char version[2]; + unsigned char status[2]; + unsigned char static_config_size[2]; + unsigned char dynamic_config_size[2]; + unsigned char app_config_start_write_block[2]; + unsigned char app_config_size[2]; + unsigned char max_touch_report_config_size[2]; + unsigned char max_touch_report_payload_size[2]; + unsigned char customer_config_id[MAX_SIZE_CONFIG_ID]; + unsigned char max_x[2]; + unsigned char max_y[2]; + unsigned char max_objects[2]; + unsigned char num_of_buttons[2]; + unsigned char num_of_image_rows[2]; + unsigned char num_of_image_cols[2]; + unsigned char has_hybrid_data[2]; + unsigned char num_of_force_elecs[2]; +}; + +/** + * @section: TouchComm boot information packet + * + * The boot info packet provides the information of TouchBoot. + */ +struct synaptics_ts_boot_info { + unsigned char version; + unsigned char status; + unsigned char asic_id[2]; + unsigned char write_block_size_words; + unsigned char erase_page_size_words[2]; + unsigned char max_write_payload_size[2]; + unsigned char last_reset_reason; + unsigned char pc_at_time_of_last_reset[2]; + unsigned char boot_config_start_block[2]; + unsigned char boot_config_size_blocks[2]; + /* extension in ver.2 */ + unsigned char display_config_start_block[4]; + unsigned char display_config_length_blocks[2]; + unsigned char backup_display_config_start_block[4]; + unsigned char backup_display_config_length_blocks[2]; + unsigned char custom_otp_start_block[2]; + unsigned char custom_otp_length_blocks[2]; +}; + +/* @section: Data blob for touch data reported + * + * Once receiving a touch report generated by firmware, the touched data + * will be parsed and converted to touch_data_blob structure as a data blob. + * + * @subsection: tcm_touch_data_blob + * The touch_data_blob contains all sorts of touched data entities. + * + * @subsection tcm_objects_data_blob + * The objects_data_blob includes the data for each active objects. + * + * @subsection tcm_gesture_data_blob + * The gesture_data_blob contains the gesture data if detected. + */ +struct synaptics_ts_objects_data_blob { + unsigned char status; + unsigned int x_pos; + unsigned int y_pos; + unsigned int x_width; + unsigned int y_width; + unsigned int z; + unsigned int tx_pos; + unsigned int rx_pos; +}; + +struct synaptics_ts_gesture_data_blob { + union { + struct { + unsigned char tap_x[2]; + unsigned char tap_y[2]; + }; + struct { + unsigned char swipe_x[2]; + unsigned char swipe_y[2]; + unsigned char swipe_direction[2]; + }; + unsigned char data[MAX_SIZE_GESTURE_DATA]; + }; +}; + +struct synaptics_ts_touch_data_blob { + + /* for each active objects */ + unsigned int num_of_active_objects; + struct synaptics_ts_objects_data_blob object_data[MAX_NUM_OBJECTS]; + + /* for gesture */ + unsigned int gesture_id; + struct synaptics_ts_gesture_data_blob gesture_data; + + /* various data */ + unsigned int timestamp; + unsigned int buttons_state; + unsigned int frame_rate; + unsigned int power_im; + unsigned int cid_im; + unsigned int rail_im; + unsigned int cid_variance_im; + unsigned int nsm_frequency; + unsigned int nsm_state; + unsigned int num_of_cpu_cycles; + unsigned int fd_data; + unsigned int force_data; + unsigned int fingerprint_area_meet; + unsigned int sensing_mode; +}; + +/** + * @section: TouchComm Message Handling Wrapper + * + * The structure contains the essential buffers and parameters to implement + * the command-response protocol for both TouchCom ver.1 and TouchCom ver.2. + */ +struct synaptics_ts_message_data_blob { + /* parameters for command processing */ + syna_pal_atomic_t command_status; + unsigned char command; + unsigned char status_report_code; + unsigned int payload_length; + unsigned char response_code; + unsigned char report_code; + unsigned char seq_toggle; + + /* completion event command processing */ + synaptics_ts_pal_completion_t cmd_completion; + + /* internal buffers + * in : buffer storing the data being read 'in' + * out : buffer storing the data being sent 'out' + * temp: 'temp' buffer used for continued read operation + */ + struct synaptics_ts_buffer in; + struct synaptics_ts_buffer out; + struct synaptics_ts_buffer temp; + + /* mutex for the protection of command processing */ + synaptics_ts_pal_mutex_t cmd_mutex; + + /* mutex for the read/write protection */ + synaptics_ts_pal_mutex_t rw_mutex; + +}; + +#define UEVENT_OPEN_SHORT_PASS 1 +#define UEVENT_OPEN_SHORT_FAIL 2 + +/** + * @brief: context of SEC touch/coordinate event data + */ +struct sec_touch_event_data { + union { + struct { + unsigned char eid:2; + unsigned char tid:4; + unsigned char tchsta:2; + unsigned char x_11_4; + unsigned char y_11_4; + unsigned char y_3_0:4; + unsigned char x_3_0:4; + unsigned char major; + unsigned char minor; + unsigned char z:6; + unsigned char ttype_3_2:2; + unsigned char left_event:5; + unsigned char max_energy_flag:1; + unsigned char ttype_1_0:2; + unsigned char noise_level; + unsigned char max_strength; + unsigned char hover_id_num:4; + unsigned char noise_status:2; + unsigned char reserved10:2; + unsigned char freq_id:4; + unsigned char fod_debug:4; + unsigned char reserved_byte12; + unsigned char reserved_byte13; + unsigned char reserved_byte14; + unsigned char reserved_byte15; + } __packed; + unsigned char data[16]; + }; +}; + +/** + * @brief: context of SEC status event data + */ +struct sec_status_event_data { + union { + struct { + unsigned char eid:2; + unsigned char stype:4; + unsigned char sf:2; + unsigned char status_id; + unsigned char status_data_1; + unsigned char status_data_2; + unsigned char status_data_3; + unsigned char status_data_4; + unsigned char status_data_5; + unsigned char left_event_5_0:6; + unsigned char reserved_byte07_7_6:2; + unsigned char reserved_byte08; + unsigned char reserved_byte09; + unsigned char reserved_byte10; + unsigned char reserved_byte11; + unsigned char reserved_byte12; + unsigned char reserved_byte13; + unsigned char reserved_byte14; + unsigned char reserved_byte15; + } __packed; + unsigned char data[16]; + }; +}; + +/** + * @brief: context of SEC gesture event data + */ +struct sec_gesture_event_data { + union { + struct { + unsigned char eid:2; + unsigned char stype:4; + unsigned char sf:2; + unsigned char gesture_id; + unsigned char gesture_data_1; + unsigned char gesture_data_2; + unsigned char gesture_data_3; + unsigned char gesture_data_4; + unsigned char gesture_data_5; + unsigned char left_event_5_0:6; + unsigned char reserved_byte07_7_6:2; + unsigned char reserved_byte08; + unsigned char reserved_byte09; + unsigned char reserved_byte10; + unsigned char reserved_byte11; + unsigned char reserved_byte12; + unsigned char reserved_byte13; + unsigned char reserved_byte14; + unsigned char reserved_byte15; + } __packed; + unsigned char data[16]; + }; +}; + +struct tsp_snr_result_of_point { + s16 max; + s16 min; + s16 average; + s16 nontouch_peak_noise; + s16 touch_peak_noise; + s16 snr1; + s16 snr2; +} __packed; + +/* This Flash Meory Map is FIXED by SYNAPTICS firmware + * Do not change MAP. + */ +#define SYNAPTICS_TS_NVM_OFFSET_ALL 31 + +#define ENABLE_EXTERNAL_FRAME_PROCESS +#define REPORT_TYPES (256) +#define EFP_ENABLE (1) +#define EFP_DISABLE (0) + +struct synaptics_ts_sysfs { + /* cdev and sysfs nodes creation */ + struct cdev char_dev; + dev_t char_dev_num; + int char_dev_ref_count; + + struct class *device_class; + struct device *device; + + struct kobject *sysfs_dir; + + /* IOCTL-related variables */ + pid_t proc_pid; + struct task_struct *proc_task; + + /* fifo to pass the report to userspace */ + unsigned int fifo_remaining_frame; + struct list_head frame_fifo_queue; + wait_queue_head_t wait_frame; + unsigned char report_to_queue[REPORT_TYPES]; + + bool is_attn_redirecting; +}; + +struct synaptics_ts_data { + void *client; + struct device *dev; + struct mutex transfer_mutex; + unsigned int spi_mode; + int irq; + int irq_empty_count; + struct sec_ts_plat_data *plat_data; + struct sec_input_multi_device *multi_dev; + struct synaptics_ts_identification_info id_info; + struct synaptics_ts_application_info app_info; + struct synaptics_ts_boot_info boot_info; + + struct synaptics_ts_touch_data_blob tp_data; + /* internal buffers + * resp : record the command response to caller + */ + struct synaptics_ts_buffer resp_buf; + struct synaptics_ts_buffer external_buf; + + /* touch report configuration */ + struct synaptics_ts_buffer touch_config; + struct synaptics_ts_buffer report_buf; + + /* Buffer stored the irq event data */ + struct synaptics_ts_buffer event_data; + struct synaptics_ts_v1_message_header *header; + + /* TouchComm message handling wrapper */ + struct synaptics_ts_message_data_blob msg_data; + + /* for vendor sysfs */ + struct synaptics_ts_sysfs *vendor_data; + + /* indicate that fw update is on-going */ + syna_pal_atomic_t firmware_flashing; + + /* basic device information */ + unsigned char dev_mode; + unsigned int packrat_number; + unsigned int max_x; + unsigned int max_y; + unsigned int max_objects; + unsigned int rows; + unsigned int cols; + unsigned char config_id[MAX_SIZE_CONFIG_ID]; + + struct mutex lock; + bool probe_done; + struct sec_cmd_data sec; + + /* capability of read/write transferred + * being assigned through syna_hw_interface + */ + unsigned int max_wr_size; + unsigned int max_rd_size; + + int *pFrame; + u8 miscal_result; + u8 *cx_data; + u8 *ito_result; + u8 disassemble_count; + u8 fac_nv; + + struct sec_tclm_data *tdata; + bool is_cal_done; + int firmware_update_done; + + bool fw_corruption; + + int glove_mode; + int game_mode; + int sip_mode; + int note_mode; + int scan_rate; + int refresh_rate; + int dead_zone; + bool cover_closed; + int fix_active_mode; + + int resolution_x; + int resolution_y; + + bool support_immediate_cmd; + bool support_miscal_wet; + const char *tcm_drv_name; + const char *tcm_dev_name; + int fw_erase_delay; + int fw_write_block_delay; + int power_on_delay; + +#if IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) && IS_ENABLED(CONFIG_SEC_FACTORY) + u8 panel_attached; + struct notifier_block lcd_nb; +#endif + struct notifier_block synaptics_input_nb; + struct delayed_work work_print_info; + struct delayed_work work_read_functions; + struct delayed_work reset_work; + struct delayed_work work_read_info; + struct delayed_work debug_work; + struct delayed_work check_rawdata; + struct delayed_work set_temperature_work; + + atomic_t reset_is_on_going; + + u8 prev_event_id; + + int debug_flag; + struct mutex device_mutex; + struct mutex eventlock; + struct mutex sponge_mutex; + struct mutex fn_mutex; + bool info_work_done; + + bool sponge_inf_dump; + u8 sponge_dump_format; + u8 sponge_dump_event; + u8 sponge_dump_border_msb; + u8 sponge_dump_border_lsb; + bool sponge_dump_delayed_flag; + u8 sponge_dump_delayed_area; + u16 sponge_dump_border; + + bool rear_selfie_mode; + + u8 hover_event; + + bool tsp_dump_lock; + + bool touch_aging_mode; + int sensitivity_mode; + + s16 *raw; + int raw_mode; + bool rawcap_lock; + int rawcap_max; + int rawcap_max_tx; + int rawcap_max_rx; + int rawcap_min; + int rawcap_min_tx; + int rawcap_min_rx; + u8 factory_position; + + int (*stop_device)(struct synaptics_ts_data *ts); + int (*start_device)(struct synaptics_ts_data *ts); + + int (*synaptics_ts_write_data)(struct synaptics_ts_data *ts, u8 *reg, int cunum, u8 *data, int len); + int (*synaptics_ts_read_data)(struct synaptics_ts_data *ts, u8 *reg, int cnum, u8 *data, int len); + int (*synaptics_ts_read_data_only)(struct synaptics_ts_data *ts, u8 *data, int len); + int (*synaptics_ts_read_sponge)(struct synaptics_ts_data *ts, unsigned char *rd_buf, int size_buf, + unsigned short offset, unsigned int rd_len); + int (*synaptics_ts_write_sponge)(struct synaptics_ts_data *ts, unsigned char *wr_buf, int size_buf, + unsigned short offset, unsigned int wr_len); + int (*synaptics_ts_systemreset)(struct synaptics_ts_data *ts, unsigned int msec); + int (*synaptics_ts_get_baredata)(struct synaptics_ts_data *ts, u8 *data, int len); + int (*write_message)(struct synaptics_ts_data *ts, + unsigned char command, unsigned char *payload, + unsigned int payload_len, unsigned char *resp_code, + unsigned int delay_ms_resp); + int (*read_message)(struct synaptics_ts_data *ts, + unsigned char *status_report_code); + int (*write_immediate_message)(struct synaptics_ts_data *ts, + unsigned char command, unsigned char *payload, + unsigned int payload_len); +}; + +//temporily + +/** + * @section: C Integer Calculation helpers + * + * @brief: synaptics_ts_pal_le2_to_uint + * Convert 2-byte data to an unsigned integer + * + * @brief: synaptics_ts_pal_le4_to_uint + * Convert 4-byte data to an unsigned integer + * + * @brief: synaptics_ts_pal_ceil_div + * Calculate the ceiling of the integer division + */ + +/** + * synaptics_ts_pal_le2_to_uint() + * + * Convert 2-byte data in little-endianness to an unsigned integer + * + * @param + * [ in] src: 2-byte data in little-endianness + * + * @return + * an unsigned integer converted + */ +static inline unsigned int synaptics_ts_pal_le2_to_uint(const unsigned char *src) +{ + return (unsigned int)src[0] + + (unsigned int)src[1] * 0x100; +} +/** + * synaptics_ts_pal_le4_to_uint() + * + * Convert 4-byte data in little-endianness to an unsigned integer + * + * @param + * [ in] src: 4-byte data in little-endianness + * + * @return + * an unsigned integer converted + */ +static inline unsigned int synaptics_ts_pal_le4_to_uint(const unsigned char *src) +{ + return (unsigned int)src[0] + + (unsigned int)src[1] * 0x100 + + (unsigned int)src[2] * 0x10000 + + (unsigned int)src[3] * 0x1000000; +} +/** + * synaptics_ts_pal_ceil_div() + * + * Calculate the ceiling of the integer division + * + * @param + * [ in] dividend: the dividend value + * [ in] divisor: the divisor value + * + * @return + * the ceiling of the integer division + */ +static inline unsigned int synaptics_ts_pal_ceil_div(unsigned int dividend, + unsigned int divisor) +{ + return (dividend + divisor - 1) / divisor; +} + + +/** + * @section: C Runtime for Memory Management helpers + * + * @brief: synaptics_ts_pal_mem_calloc + * Allocate a block of memory space + * + * @brief: synaptics_ts_pal_mem_free + * Deallocate a block of memory previously allocated + * + * @brief: synaptics_ts_pal_mem_set + * Fill memory with a constant byte + * + * @brief: synaptics_ts_pal_mem_cpy + * Ensure the safe size before doing memory copy + */ + +/** + * synaptics_ts_pal_mem_calloc() + * + * Allocates a block of memory for an array of 'num' elements, + * each of them has 'size' bytes long, and initializes all its bits to zero. + * + * @param + * [ in] num: number of elements for an array + * [ in] size: number of bytes for each elements + * + * @return + * On success, a pointer to the memory block allocated by the function. + */ +static inline void *synaptics_ts_pal_mem_alloc(unsigned int num, unsigned int size) +{ +#ifdef DEV_MANAGED_API + struct device *dev = synaptics_ts_request_managed_device(); + + if (!dev) { + pr_err("[sec_input]Invalid managed device\n"); + return NULL; + } +#endif + + if ((int)(num * size) <= 0) { + pr_err("[sec_input]Invalid parameter\n"); + return NULL; + } + +#ifdef DEV_MANAGED_API + return devm_kcalloc(dev, num, size, GFP_KERNEL); +#else /* Legacy API */ + return kcalloc(num, size, GFP_KERNEL); +#endif +} +/** + * synaptics_ts_pal_mem_free() + * + * Deallocate a block of memory previously allocated. + * + * @param + * [ in] ptr: a memory block previously allocated + * + * @return + * none. + */ +static inline void synaptics_ts_pal_mem_free(void *ptr) +{ +#ifdef DEV_MANAGED_API + struct device *dev = synaptics_ts_request_managed_device(); + + if (!dev) { + pr_err("[sec_input]Invalid managed device\n"); + return; + } + + if (ptr) + devm_kfree(dev, ptr); +#else /* Legacy API */ + kfree(ptr); +#endif +} +/** + * synaptics_ts_pal_mem_set() + * + * Fill memory with a constant byte + * + * @param + * [ in] ptr: pointer to a memory block + * [ in] c: the constant value + * [ in] n: number of byte being set + * + * @return + * none. + */ +static inline void synaptics_ts_pal_mem_set(void *ptr, int c, unsigned int n) +{ + memset(ptr, c, n); +} +/** + * synaptics_ts_pal_mem_cpy() + * + * Ensure the safe size before copying the values of num bytes from the + * location to the memory block pointed to by destination. + * + * @param + * [out] dest: pointer to the destination space + * [ in] dest_size: size of destination array + * [ in] src: pointer to the source of data to be copied + * [ in] src_size: size of source array + * [ in] num: number of bytes to copy + * + * @return + * 0 on success; otherwise, on error. + */ +static inline int synaptics_ts_pal_mem_cpy(void *dest, unsigned int dest_size, + const void *src, unsigned int src_size, unsigned int num) +{ + if (dest == NULL || src == NULL) + return -1; + + if (num > dest_size || num > src_size) { + pr_err("[sec_input]Invalid size. src:%d, dest:%d, num:%d\n", + src_size, dest_size, num); + return -1; + } + + memcpy((void *)dest, (const void *)src, num); + + return 0; +} + + +/** + * synaptics_ts_pal_mutex_alloc() + * + * Create a mutex object. + * + * @param + * [out] ptr: pointer to the mutex handle being allocated + * + * @return + * 0 on success; otherwise, on error. + */ +static inline int synaptics_ts_pal_mutex_alloc(synaptics_ts_pal_mutex_t *ptr) +{ + mutex_init((struct mutex *)ptr); + return 0; +} +/** + * synaptics_ts_pal_mutex_free() + * + * Release the mutex object previously allocated. + * + * @param + * [ in] ptr: mutex handle previously allocated + * + * @return + * none. + */ +static inline void synaptics_ts_pal_mutex_free(synaptics_ts_pal_mutex_t *ptr) +{ + /* do nothing */ +} +/** + * synaptics_ts_pal_mutex_lock() + * + * Acquire/lock the mutex. + * + * @param + * [ in] ptr: a mutex handle + * + * @return + * none. + */ +static inline void synaptics_ts_pal_mutex_lock(synaptics_ts_pal_mutex_t *ptr) +{ + mutex_lock((struct mutex *)ptr); +} +/** + * synaptics_ts_pal_mutex_unlock() + * + * Unlock the locked mutex. + * + * @param + * [ in] ptr: a mutex handle + * + * @return + * none. + */ +static inline void synaptics_ts_pal_mutex_unlock(synaptics_ts_pal_mutex_t *ptr) +{ + mutex_unlock((struct mutex *)ptr); +} + + +/** + * synaptics_ts_pal_completion_alloc() + * + * Allocate a completion event, and the default state is not set. + * Caller must reset the event before each use. + * + * @param + * [out] ptr: pointer to the completion handle being allocated + * + * @return + * 0 on success; otherwise, on error. + */ +static inline int synaptics_ts_pal_completion_alloc(synaptics_ts_pal_completion_t *ptr) +{ + init_completion((struct completion *)ptr); + return 0; +} +/** + * synaptics_ts_pal_completion_free() + * + * Release the completion event previously allocated + * + * @param + * [ in] ptr: the completion event previously allocated + event + * @return + * none. + */ +static inline void synaptics_ts_pal_completion_free(synaptics_ts_pal_completion_t *ptr) +{ + /* do nothing */ +} +/** + * synaptics_ts_pal_completion_complete() + * + * Complete the completion event being waiting for + * + * @param + * [ in] ptr: the completion event + * + * @return + * none. + */ +static inline void synaptics_ts_pal_completion_complete(synaptics_ts_pal_completion_t *ptr) +{ + complete((struct completion *)ptr); +} +/** + * synaptics_ts_pal_completion_reset() + * + * Reset or reinitialize the completion event + * + * @param + * [ in] ptr: the completion event + * + * @return + * none. + */ +static inline void synaptics_ts_pal_completion_reset(synaptics_ts_pal_completion_t *ptr) +{ +#if (KERNEL_VERSION(3, 13, 0) > LINUX_VERSION_CODE) + init_completion((struct completion *)ptr); +#else + reinit_completion((struct completion *)ptr); +#endif +} +/** + * synaptics_ts_pal_completion_wait_for() + * + * Wait for the completion event during the given time slot + * + * @param + * [ in] ptr: the completion event + * [ in] timeout_ms: time frame in milliseconds + * + * @return + * 0 if a signal is received; otherwise, on timeout or error occurs. + */ +static inline int synaptics_ts_pal_completion_wait_for(synaptics_ts_pal_completion_t *ptr, + unsigned int timeout_ms) +{ + int retval; + + retval = wait_for_completion_timeout((struct completion *)ptr, + msecs_to_jiffies(timeout_ms)); + if (retval == 0) /* timeout occurs */ + return -1; + + return 0; +} + + +/** + * @section: C Runtime to Pause the Execution + * + * @brief: synaptics_ts_pal_sleep_ms + * Sleep for a fixed amount of time in milliseconds + * + * @brief: synaptics_ts_pal_sleep_us + * Sleep for a range of time in microseconds + * + * @brief: synaptics_ts_pal_busy_delay_ms + * Busy wait for a fixed amount of time in milliseconds + */ + +/** + * synaptics_ts_pal_sleep_ms() + * + * Sleep for a fixed amount of time in milliseconds + * + * @param + * [ in] time_ms: time frame in milliseconds + * + * @return + * none. + */ +static inline void synaptics_ts_pal_sleep_ms(int time_ms) +{ + msleep(time_ms); +} +/** + * synaptics_ts_pal_sleep_us() + * + * Sleep for a range of time in microseconds + * + * @param + * [ in] time_us_min: the min. time frame in microseconds + * [ in] time_us_max: the max. time frame in microseconds + * + * @return + * none. + */ +static inline void synaptics_ts_pal_sleep_us(int time_us_min, int time_us_max) +{ + usleep_range(time_us_min, time_us_max); +} +/** + * synaptics_ts_pal_busy_delay_ms() + * + * Busy wait for a fixed amount of time in milliseconds + * + * @param + * [ in] time_ms: time frame in milliseconds + * + * @return + * none. + */ +static inline void synaptics_ts_pal_busy_delay_ms(int time_ms) +{ + mdelay(time_ms); +} + + +/** + * @section: C Runtime for String operations + * + * @brief: synaptics_ts_pal_str_len + * Return the length of C string + * + * @brief: synaptics_ts_pal_str_cpy: + * Ensure the safe size before doing C string copy + * + * @brief: synaptics_ts_pal_str_cmp: + * Compare whether the given C strings are equal or not + */ + +/** + * synaptics_ts_pal_str_len() + * + * Return the length of C string + * + * @param + * [ in] str: an array of characters + * + * @return + * the length of given string + */ +static inline unsigned int synaptics_ts_pal_str_len(const char *str) +{ + return (unsigned int)strlen(str); +} +/** + * synaptics_ts_pal_str_cpy() + * + * Copy the C string pointed by source into the array pointed by destination. + * + * @param + * [ in] dest: pointer to the destination C string + * [ in] dest_size: size of destination C string + * [out] src: pointer to the source of C string to be copied + * [ in] src_size: size of source C string + * [ in] num: number of bytes to copy + * + * @return + * 0 on success; otherwise, on error. + */ +static inline int synaptics_ts_pal_str_cpy(char *dest, unsigned int dest_size, + const char *src, unsigned int src_size, unsigned int num) +{ + if (dest == NULL || src == NULL) + return -1; + + if (num > dest_size || num > src_size) { + pr_err("[sec_input]Invalid size. src:%d, dest:%d, num:%d\n", + src_size, dest_size, num); + return -1; + } + + strncpy(dest, src, num); + + return 0; +} +/** + * synaptics_ts_pal_str_cmp() + * + * Compares up to num characters of the C string str1 to those of the + * C string str2. + * + * @param + * [ in] str1: C string to be compared + * [ in] str2: C string to be compared + * [ in] num: number of characters to compare + * + * @return + * 0 if both strings are equal; otherwise, not equal. + */ +static inline int synaptics_ts_pal_str_cmp(const char *str1, const char *str2, + unsigned int num) +{ + return strncmp(str1, str2, num); +} +/** + * synaptics_ts_pal_hex_to_uint() + * + * Convert the given string in hex to an integer returned + * + * @param + * [ in] str: C string to be converted + * [ in] length: target length + * + * @return + * An integer converted + */ +static inline unsigned int synaptics_ts_pal_hex_to_uint(char *str, int length) +{ + unsigned int result = 0; + char *ptr = NULL; + + for (ptr = str; ptr != str + length; ++ptr) { + result <<= 4; + if (*ptr >= 'A') + result += *ptr - 'A' + 10; + else + result += *ptr - '0'; + } + + return result; +} + +/** + * @section: C Runtime for Checksum Calculation + * + * @brief: synaptics_ts_pal_crc32 + * Calculates the CRC32 value + */ + +/** + * synaptics_ts_pal_crc32() + * + * Calculates the CRC32 value of the data + * + * @param + * [ in] seed: the previous crc32 value + * [ in] data: byte data for the calculation + * [ in] len: the byte length of the data. + * + * @return + * 0 if both strings are equal; otherwise, not equal. + */ +static inline unsigned int synaptics_ts_pal_crc32(unsigned int seed, + const char *data, unsigned int len) +{ + return crc32(seed, data, len); +} + +static inline int synaptics_ts_buf_alloc(struct synaptics_ts_buffer *pbuf, + unsigned int size) +{ + if (!pbuf) { + pr_err("[sec_input]Invalid buffer structure\n"); + return -1; + } + + if (size > pbuf->buf_size) { + if (pbuf->buf) + synaptics_ts_pal_mem_free((void *)pbuf->buf); + + pbuf->buf = synaptics_ts_pal_mem_alloc(size, sizeof(unsigned char)); + if (!(pbuf->buf)) { + pr_err("[sec_input]Fail to allocate memory (size = %d)\n", + (int)(size*sizeof(unsigned char))); + pbuf->buf_size = 0; + pbuf->data_length = 0; + return -1; + } + pbuf->buf_size = size; + } + + synaptics_ts_pal_mem_set(pbuf->buf, 0x00, pbuf->buf_size); + pbuf->data_length = 0; + + return 0; +} + +/** + * synaptics_ts_buf_realloc() + * + * Extend the requested memory space for the given buffer only if + * the existed buffer is not enough for the requirement. + * Then, move the content to the new memory space. + * + * @param + * [ in] pbuf: pointer to an internal buffer + * [ in] size: required size to be extended + * + * @return + * 0 or positive value on success; otherwise, on error. + */ +static inline int synaptics_ts_buf_realloc(struct synaptics_ts_buffer *pbuf, + unsigned int size) +{ + int retval; + unsigned char *temp_src; + unsigned int temp_size = 0; + + if (!pbuf) { + pr_err("[sec_input]Invalid buffer structure\n"); + return -1; + } + + if (size > pbuf->buf_size) { + temp_src = pbuf->buf; + temp_size = pbuf->buf_size; + + pbuf->buf = synaptics_ts_pal_mem_alloc(size, sizeof(unsigned char)); + if (!(pbuf->buf)) { + pr_err("[sec_input]Fail to allocate memory (size = %d)\n", + (int)(size * sizeof(unsigned char))); + synaptics_ts_pal_mem_free((void *)temp_src); + pbuf->buf_size = 0; + return -1; + } + + retval = synaptics_ts_pal_mem_cpy(pbuf->buf, + size, + temp_src, + temp_size, + temp_size); + if (retval < 0) { + pr_err("[sec_input]Fail to copy data\n"); + synaptics_ts_pal_mem_free((void *)temp_src); + synaptics_ts_pal_mem_free((void *)pbuf->buf); + pbuf->buf_size = 0; + return retval; + } + + synaptics_ts_pal_mem_free((void *)temp_src); + pbuf->buf_size = size; + } + + return 0; +} +/** + * synaptics_ts_buf_init() + * + * Initialize the buffer structure. + * + * @param + * [ in] pbuf: pointer to an internal buffer + * + * @return + * none + */ +static inline void synaptics_ts_buf_init(struct synaptics_ts_buffer *pbuf) +{ + pbuf->buf_size = 0; + pbuf->data_length = 0; + pbuf->ref_cnt = 0; + pbuf->buf = NULL; + synaptics_ts_pal_mutex_alloc(&pbuf->buf_mutex); +} +/** + * synaptics_ts_buf_lock() + * + * Protect the access of current buffer structure. + * + * @param + * [ in] pbuf: pointer to an internal buffer + * + * @return + * none + */ +static inline void synaptics_ts_buf_lock(struct synaptics_ts_buffer *pbuf) +{ + if (pbuf->ref_cnt != 0) { + pr_err("[sec_input]Buffer access out-of balance, %d\n", pbuf->ref_cnt); + return; + } + + synaptics_ts_pal_mutex_lock(&pbuf->buf_mutex); + pbuf->ref_cnt++; +} +/** + * synaptics_ts_buf_unlock() + * + * Open the access of current buffer structure. + * + * @param + * [ in] pbuf: pointer to an internal buffer + * + * @return + * none + */ +static inline void synaptics_ts_buf_unlock(struct synaptics_ts_buffer *pbuf) +{ + if (pbuf->ref_cnt != 1) { + pr_err("[sec_input]Buffer access out-of balance, %d\n", pbuf->ref_cnt); + return; + } + + pbuf->ref_cnt--; + synaptics_ts_pal_mutex_unlock(&pbuf->buf_mutex); +} +/** + * synaptics_ts_buf_release() + * + * Release the buffer structure. + * + * @param + * [ in] pbuf: pointer to an internal buffer + * + * @return + * none + */ +static inline void synaptics_ts_buf_release(struct synaptics_ts_buffer *pbuf) +{ + if (pbuf->ref_cnt != 0) + pr_err("[sec_input]Buffer access hold, %d\n", pbuf->ref_cnt); + + synaptics_ts_pal_mutex_free(&pbuf->buf_mutex); + synaptics_ts_pal_mem_free((void *)pbuf->buf); + pbuf->buf_size = 0; + pbuf->data_length = 0; + pbuf->ref_cnt = 0; +} +/** + * synaptics_ts_buf_copy() + * + * Helper to copy data from the source buffer to the destination buffer. + * The size of destination buffer may be reallocated, if the size is + * smaller than the actual data size to be copied. + * + * @param + * [out] dest: pointer to an internal buffer + * [ in] src: required size to be extended + * + * @return + * 0 or positive value on success; otherwise, on error. + */ +static inline int synaptics_ts_buf_copy(struct synaptics_ts_buffer *dest, + struct synaptics_ts_buffer *src) +{ + int retval = 0; + + if (dest->buf_size < src->data_length) { + retval = synaptics_ts_buf_alloc(dest, src->data_length + 1); + if (retval < 0) { + pr_err("[sec_input]Fail to reallocate the given buffer, size: %d\n", + src->data_length + 1); + return retval; + } + } + + /* copy data content to the destination */ + retval = synaptics_ts_pal_mem_cpy(dest->buf, + dest->buf_size, + src->buf, + src->buf_size, + src->data_length); + if (retval < 0) { + pr_err("[sec_input]Fail to copy data to caller, size: %d\n", + src->data_length); + return retval; + } + + dest->data_length = src->data_length; + + return retval; +} + + +//core +int synaptics_ts_init(struct synaptics_ts_data *ts); +int synaptics_ts_probe(struct device *dev); +int synaptics_ts_remove(struct synaptics_ts_data *ts); +void synaptics_ts_shutdown(struct synaptics_ts_data *ts); +#if IS_ENABLED(CONFIG_PM) +int synaptics_ts_pm_suspend(struct device *dev); +int synaptics_ts_pm_resume(struct device *dev); +#endif +int synaptics_ts_stop_device(void *data); +int synaptics_ts_start_device(void *data); +irqreturn_t synaptics_ts_irq_thread(int irq, void *ptr); + + +void synaptics_ts_reinit(void *data); +int synaptics_ts_execute_autotune(struct synaptics_ts_data *ts, bool IsSaving); +int synaptics_ts_get_tsp_test_result(struct synaptics_ts_data *ts); +void synaptics_ts_release_all_finger(struct synaptics_ts_data *ts); +void synaptics_ts_locked_release_all_finger(struct synaptics_ts_data *ts); + +int synaptics_ts_set_external_noise_mode(struct synaptics_ts_data *ts, u8 mode); +int synaptics_ts_get_version_info(struct synaptics_ts_data *ts); + +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) +irqreturn_t synaptics_secure_filter_interrupt(struct synaptics_ts_data *ts); +#endif + +//fn +int synaptics_ts_read_from_sponge(struct synaptics_ts_data *ts, unsigned char *rd_buf, int size_buf, + unsigned short offset, unsigned int rd_len); +int synaptics_ts_write_to_sponge(struct synaptics_ts_data *ts, unsigned char *wr_buf, int size_buf, + unsigned short offset, unsigned int wr_len); +void synaptics_set_grip_data_to_ic(struct device *dev, u8 flag); +int synaptics_ts_set_temperature(struct device *dev, u8 temperature_data); +int synaptics_ts_set_lowpowermode(void *data, u8 mode); +int synaptics_ts_set_aod_rect(struct synaptics_ts_data *ts); +int synaptics_ts_setup(struct synaptics_ts_data *ts); +void synaptics_ts_hw_reset(struct synaptics_ts_data *ts); +void synaptics_ts_reset_work(struct work_struct *work); +void synaptics_ts_read_info_work(struct work_struct *work); +void synaptics_ts_print_info_work(struct work_struct *work); +int get_nvm_data_by_size(struct synaptics_ts_data *ts, u8 offset, int length, u8 *nvdata); +int get_nvm_data(struct synaptics_ts_data *ts, int type, u8 *nvdata); +int synaptics_ts_set_custom_library(struct synaptics_ts_data *ts); +void synaptics_ts_get_custom_library(struct synaptics_ts_data *ts); +void synaptics_ts_set_fod_finger_merge(struct synaptics_ts_data *ts); +int synaptics_ts_set_fod_rect(struct synaptics_ts_data *ts); +int synaptics_ts_set_touchable_area(struct synaptics_ts_data *ts); +int synaptics_ts_ear_detect_enable(struct synaptics_ts_data *ts, u8 enable); +int synaptics_ts_pocket_mode_enable(struct synaptics_ts_data *ts, u8 enable); +int synaptics_ts_low_sensitivity_mode_enable(struct synaptics_ts_data *ts, u16 enable); +int synaptics_ts_set_charger_mode(struct device *dev, bool on); +void synaptics_ts_set_cover_type(struct synaptics_ts_data *ts, bool enable); +int synaptics_ts_set_press_property(struct synaptics_ts_data *ts); +int get_nvm_data(struct synaptics_ts_data *ts, int type, u8 *nvdata); +int set_nvm_data(struct synaptics_ts_data *ts, u8 type, u8 *buf); +int get_nvm_data_by_size(struct synaptics_ts_data *ts, u8 offset, int length, u8 *nvdata); +int set_nvm_data_by_size(struct synaptics_ts_data *ts, u8 offset, int length, u8 *buf); +int synaptics_ts_set_touch_function(struct synaptics_ts_data *ts); +void synaptics_ts_get_touch_function(struct work_struct *work); +int synaptics_ts_detect_protocol(struct synaptics_ts_data *ts); +int synaptics_ts_set_up_app_fw(struct synaptics_ts_data *ts); +inline unsigned int synaptics_ts_pal_le2_to_uint(const unsigned char *src); +inline unsigned int synaptics_ts_pal_le4_to_uint(const unsigned char *src); +int synaptics_ts_switch_fw_mode(struct synaptics_ts_data *ts, unsigned char mode); +int synaptics_ts_get_boot_info(struct synaptics_ts_data *ts, struct synaptics_ts_boot_info *boot_info); +inline int synaptics_ts_memcpy(void *dest, unsigned int dest_size, + const void *src, unsigned int src_size, unsigned int num); +int synaptics_ts_v1_parse_idinfo(struct synaptics_ts_data *ts, + unsigned char *data, unsigned int size, unsigned int data_len); +inline void synaptics_ts_buffer_size_set(struct synaptics_ts_buffer *pbuf, unsigned int size); +int synaptics_ts_soft_reset(struct synaptics_ts_data *ts); +int synaptics_ts_rezero(struct synaptics_ts_data *ts); +int synaptics_ts_get_app_info(struct synaptics_ts_data *ts, struct synaptics_ts_application_info *app_info); +int synaptics_ts_send_command(struct synaptics_ts_data *ts, + unsigned char command, unsigned char *payload, + unsigned int payload_length, unsigned char *resp_code, + struct synaptics_ts_buffer *resp, unsigned int delay_ms_resp); +int synaptics_ts_send_immediate_command(struct synaptics_ts_data *ts, + unsigned char command, unsigned char *payload, + unsigned int payload_length); +int synaptics_ts_calibration(struct synaptics_ts_data *ts); +int synaptics_ts_set_dynamic_config(struct synaptics_ts_data *ts, + unsigned char id, unsigned short value); +int synaptics_ts_set_immediate_dynamic_config(struct synaptics_ts_data *ts, + unsigned char id, unsigned short value); +int synaptics_ts_clear_buffer(struct synaptics_ts_data *ts); +int synaptics_ts_tclm_read(struct device *dev, int address); +int synaptics_ts_tclm_write(struct device *dev, int address); +int synaptics_ts_tclm_execute_force_calibration(struct device *dev, int cal_mode); +int synaptics_ts_set_up_rawdata_report_type(struct synaptics_ts_data *ts); +int synaptics_ts_enable_report(struct synaptics_ts_data *ts, unsigned char report_code, bool en); +void synaptics_ts_external_func_work(struct work_struct *work); + +//cmd +void synaptics_ts_fn_remove(struct synaptics_ts_data *ts); +int synaptics_ts_fn_init(struct synaptics_ts_data *ts); +int synaptics_ts_panel_ito_test(struct synaptics_ts_data *ts, int tesynapticsode); +void synaptics_ts_run_rawdata_all(struct synaptics_ts_data *ts); +int synaptics_ts_run_production_test(struct synaptics_ts_data *ts, + unsigned char test_item, struct synaptics_ts_buffer *tdata); +void synaptics_ts_rawdata_read_all(struct synaptics_ts_data *ts); + + +//dump +#if IS_ENABLED(CONFIG_TOUCHSCREEN_DUMP_MODE) +void synaptics_ts_check_rawdata(struct work_struct *work); +void synaptics_ts_dump_tsp_log(struct device *dev); +void synaptics_ts_sponge_dump_flush(struct synaptics_ts_data *ts, int dump_area); +#endif +ssize_t get_lp_dump_show(struct device *dev, struct device_attribute *attr, char *buf); + +//fw +int synaptics_ts_fw_update_on_probe(struct synaptics_ts_data *ts); +int synaptics_ts_fw_update_on_hidden_menu(struct synaptics_ts_data *ts, int update_type); +int synaptics_ts_wait_for_echo_event(struct synaptics_ts_data *ts, u8 *cmd, u8 cmd_cnt, int delay); +int synaptics_ts_fw_wait_for_event(struct synaptics_ts_data *ts, u8 *result, u8 result_cnt); +void synaptics_ts_checking_miscal(struct synaptics_ts_data *ts); +void synaptics_ts_external_func(struct synaptics_ts_data *ts); + +/* for vendor */ +#if !IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP) +int syna_cdev_create_sysfs(struct synaptics_ts_data *ts); +void syna_cdev_remove_sysfs(struct synaptics_ts_data *ts); +void syna_cdev_redirect_attn(struct synaptics_ts_data *ts); +#ifdef ENABLE_EXTERNAL_FRAME_PROCESS +void syna_cdev_update_report_queue(struct synaptics_ts_data *ts, + unsigned char code, struct synaptics_ts_buffer *pevent_data); +#endif +#endif + +//ic_setting +void synaptics_ts_fw_delay_setting(struct synaptics_ts_data *ts); +int synaptics_ts_fw_resp_delay_ms(struct synaptics_ts_data *ts, + unsigned int xfer_length, unsigned int wr_delay_ms, unsigned int num_blocks); +int synaptics_ts_fw_set_page_count(struct synaptics_ts_data *ts, + struct synaptics_ts_reflash_data_blob *reflash_data, unsigned int size); +int synaptics_ts_fw_set_erase_delay(struct synaptics_ts_data *ts, + unsigned int erase_delay_ms, unsigned int page_count); + +#if IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) && IS_ENABLED(CONFIG_SEC_FACTORY) +extern int panel_notifier_register(struct notifier_block *nb); +extern int panel_notifier_unregister(struct notifier_block *nb); +#endif + +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) +extern int stui_spi_lock(struct spi_master *spi); +extern int stui_spi_unlock(struct spi_master *spi); +#endif + +#endif /* _LINUX_synaptics_ts_H_ */ + diff --git a/drivers/input/sec_input/synaptics/synaptics_dump.c b/drivers/input/sec_input/synaptics/synaptics_dump.c new file mode 100644 index 000000000000..54b4ded1bbe1 --- /dev/null +++ b/drivers/input/sec_input/synaptics/synaptics_dump.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Samsung Electronics Co., Ltd. + * + * 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. + */ + +#include "synaptics_dev.h" +#include "synaptics_reg.h" + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_DUMP_MODE) +void synaptics_ts_check_rawdata(struct work_struct *work) +{ + struct synaptics_ts_data *ts = container_of(work, struct synaptics_ts_data, check_rawdata.work); + + if (ts->tsp_dump_lock == 1) { + input_err(true, ts->dev, "%s: ignored ## already checking..\n", __func__); + return; + } + + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: ignored ## IC is power off\n", __func__); + return; + } + + synaptics_ts_rawdata_read_all(ts); +} + +void synaptics_ts_dump_tsp_log(struct device *dev) +{ + struct synaptics_ts_data *ts = dev_get_drvdata(dev); + + pr_info("%s: %s %s: start\n", dev_driver_string(dev), SECLOG, __func__); + + if (!ts) { + pr_err("%s: %s %s: ignored ## tsp probe fail!!\n", dev_driver_string(dev), SECLOG, __func__); + return; + } + + schedule_delayed_work(&ts->check_rawdata, msecs_to_jiffies(100)); +} + +void synaptics_ts_sponge_dump_flush(struct synaptics_ts_data *ts, int dump_area) +{ + int i, ret = 0; + unsigned char *sec_spg_dat; + + sec_spg_dat = vmalloc(SEC_TS_MAX_SPONGE_DUMP_BUFFER); + if (!sec_spg_dat) { + input_err(true, ts->dev, "%s : Failed!!\n", __func__); + return; + } + + memset(sec_spg_dat, 0, SEC_TS_MAX_SPONGE_DUMP_BUFFER); + + input_info(true, ts->dev, "%s: dump area=%d\n", __func__, dump_area); + + /* check dump area */ + // if (dump_area == 0) { + // sec_spg_dat[0] = SEC_TS_CMD_SPONGE_LP_DUMP_EVENT; + // sec_spg_dat[1] = 0; + // } else { + // sec_spg_dat[0] = (u8)ts->sponge_dump_border & 0xff; + // sec_spg_dat[1] = (u8)(ts->sponge_dump_border >> 8); + // } + + /* dump all events at once */ + + if (ts->sponge_dump_event * ts->sponge_dump_format > SEC_TS_MAX_SPONGE_DUMP_BUFFER) { + input_err(true, ts->dev, "%s: wrong sponge dump read size (%d)\n", + __func__, ts->sponge_dump_event * ts->sponge_dump_format); + vfree(sec_spg_dat); + return; + } + + /* check dump area */ + if (dump_area == 0) + ret = ts->synaptics_ts_read_sponge(ts, sec_spg_dat, ts->sponge_dump_event * ts->sponge_dump_format, + SEC_TS_CMD_SPONGE_LP_DUMP_EVENT, ts->sponge_dump_event * ts->sponge_dump_format); + else + ret = ts->synaptics_ts_read_sponge(ts, sec_spg_dat, ts->sponge_dump_event * ts->sponge_dump_format, + ts->sponge_dump_border, ts->sponge_dump_event * ts->sponge_dump_format); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to read sponge\n", __func__); + vfree(sec_spg_dat); + return; + } + + for (i = 0 ; i < ts->sponge_dump_event ; i++) { + int e_offset = i * ts->sponge_dump_format; + char buff[40] = {0, }; + u16 edata[5]; + + edata[0] = (sec_spg_dat[1 + e_offset] & 0xFF) << 8 | (sec_spg_dat[0 + e_offset] & 0xFF); + edata[1] = (sec_spg_dat[3 + e_offset] & 0xFF) << 8 | (sec_spg_dat[2 + e_offset] & 0xFF); + edata[2] = (sec_spg_dat[5 + e_offset] & 0xFF) << 8 | (sec_spg_dat[4 + e_offset] & 0xFF); + edata[3] = (sec_spg_dat[7 + e_offset] & 0xFF) << 8 | (sec_spg_dat[6 + e_offset] & 0xFF); + edata[4] = (sec_spg_dat[9 + e_offset] & 0xFF) << 8 | (sec_spg_dat[8 + e_offset] & 0xFF); + + if (edata[0] || edata[1] || edata[2] || edata[3] || edata[4]) { + char dev_buff[10] = {0, }; + + if (IS_FOLD_DEV(ts->multi_dev)) + snprintf(dev_buff, sizeof(dev_buff), "%4s: ", ts->multi_dev->name); + else + snprintf(dev_buff, sizeof(dev_buff), ""); + snprintf(buff, sizeof(buff), "%s%03d: %04x%04x%04x%04x%04x\n", + dev_buff, i + (ts->sponge_dump_event * dump_area), + edata[0], edata[1], edata[2], edata[3], edata[4]); +#if IS_ENABLED(CONFIG_SEC_DEBUG_TSP_LOG) + sec_tsp_sponge_log(buff); +#endif + } + } + + vfree(sec_spg_dat); +} +#endif + +MODULE_LICENSE("GPL"); + diff --git a/drivers/input/sec_input/synaptics/synaptics_fn.c b/drivers/input/sec_input/synaptics/synaptics_fn.c new file mode 100644 index 000000000000..0be1f09fb863 --- /dev/null +++ b/drivers/input/sec_input/synaptics/synaptics_fn.c @@ -0,0 +1,3017 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Samsung Electronics Co., Ltd. + * + * 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. + */ + +#include "synaptics_dev.h" +#include "synaptics_reg.h" + +/** + * syna_tcm_send_command() + * + * Helper to forward the custom commnd to the device + * + * @param + * [ in] tcm_dev: the device handle + * [ in] command: TouchComm command + * [ in] payload: data payload, if any + * [ in] payload_length: length of data payload, if any + * [out] resp_code: response code returned + * [out] resp: buffer to store the response data + * [ in] delay_ms_resp: delay time for response reading. + * '0' is in default; and, + * 'FORCE_ATTN_DRIVEN' is to read resp in ISR + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int synaptics_ts_send_command(struct synaptics_ts_data *ts, + unsigned char command, unsigned char *payload, + unsigned int payload_length, unsigned char *resp_code, + struct synaptics_ts_buffer *resp, unsigned int delay_ms_resp) +{ + int retval = 0; + + if (!ts) { + pr_err("%s%s: Invalid tcm device handle\n", SECLOG, __func__); + return SEC_ERROR; + } + + if (!resp_code) { + input_err(true, ts->dev, "Invalid parameter\n"); + return SEC_ERROR; + } + + retval = ts->write_message(ts, + command, + payload, + payload_length, + resp_code, + delay_ms_resp); + if (retval < 0) { + input_err(true, ts->dev, "Fail to send command 0x%02x\n", + command); + goto exit; + } + + /* response data returned */ + if ((resp != NULL) && (ts->resp_buf.data_length > 0)) { + retval = synaptics_ts_buf_copy(resp, &ts->resp_buf); + if (retval < 0) { + input_err(true, ts->dev, "Fail to copy resp data\n"); + goto exit; + } + } + +exit: + return retval; +} + +/** + * syna_tcm_send_immediate_command() + * + * Helper to forward the immediate custom commnd to the device. This + * function only support the immediate command. For having the response + * please use the generic commands through the synaptics_ts_send_command + * function. The firmware will not send the response for the immediate + * commands including the error code. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] command: TouchComm immediate command + * [ in] payload: data payload, if any + * [ in] payload_length: length of data payload, if any + * + * @return + * on success for sending the command, 0 or positive value; + * otherwise, negative value on error. + */ +int synaptics_ts_send_immediate_command(struct synaptics_ts_data *ts, + unsigned char command, unsigned char *payload, + unsigned int payload_length) +{ + int retval = 0; + + if (!ts) { + pr_err("%s%s: Invalid tcm device handle\n", SECLOG, __func__); + return SEC_ERROR; + } + + retval = ts->write_immediate_message(ts, + command, + payload, + payload_length); + if (retval < 0) { + input_err(true, ts->dev, "Fail to send immediate command 0x%02x\n", + command); + } + + return retval; +} + + +/** + * syna_tcm_rezero() + * + * Implement the application fw command code to force the device to rezero its + * baseline estimate. + * + * @param + * [ in] tcm_dev: the device handle + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int synaptics_ts_rezero(struct synaptics_ts_data *ts) +{ + int retval = 0; + unsigned char resp_code; + + input_err(true, ts->dev, "%s:\n", __func__); + + if (IS_NOT_APP_FW_MODE(ts->dev_mode)) { + input_err(true, ts->dev, "%s:Device is not in application fw mode, mode: %x\n", __func__, + ts->dev_mode); + return -EINVAL; + } + + retval = ts->write_message(ts, + SYNAPTICS_TS_CMD_REZERO, + NULL, + 0, + &resp_code, + 0); + if (retval < 0) { + input_err(true, ts->dev, "Fail to send command 0x%02x\n", + SYNAPTICS_TS_CMD_REZERO); + goto exit; + } + + + retval = 0; +exit: + return retval; +} + +/** + * synaptics_ts_pal_mem_cpy() + * + * Ensure the safe size before copying the values of num bytes from the + * location to the memory block pointed to by destination. + * + * @param + * [out] dest: pointer to the destination space + * [ in] dest_size: size of destination array + * [ in] src: pointer to the source of data to be copied + * [ in] src_size: size of source array + * [ in] num: number of bytes to copy + * + * @return + * 0 on success; otherwise, on error. + */ +inline int synaptics_ts_memcpy(void *dest, unsigned int dest_size, + const void *src, unsigned int src_size, unsigned int num) +{ + if (dest == NULL || src == NULL) + return -1; + + if (num > dest_size || num > src_size) + return -1; + + memcpy((void *)dest, (const void *)src, num); + + return 0; +} + +/** + * syna_tcm_get_boot_info() + * + * Implement the bootloader command code, which is used to request a + * boot information packet. + * + * @param + * [ in] tcm_dev: the device handle + * [out] boot_info: the boot info packet returned + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int synaptics_ts_get_boot_info(struct synaptics_ts_data *ts, + struct synaptics_ts_boot_info *boot_info) +{ + int retval = 0; + unsigned char resp_code; + unsigned int resp_data_len = 0; + unsigned int copy_size; + + retval = ts->write_message(ts, + SYNAPTICS_TS_CMD_GET_BOOT_INFO, + NULL, + 0, + &resp_code, + 0); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to send command 0x%02x\n", __func__, + SYNAPTICS_TS_CMD_GET_BOOT_INFO); + goto exit; + } + + resp_data_len = ts->resp_buf.data_length; + copy_size = MIN(sizeof(struct synaptics_ts_boot_info), resp_data_len); + + /* save the boot_info */ + retval = synaptics_ts_memcpy((unsigned char *)&ts->boot_info, + sizeof(struct synaptics_ts_boot_info), + ts->resp_buf.buf, + ts->resp_buf.buf_size, + copy_size); + if (retval < 0) { + input_err(true, ts->dev, "%s:Fail to copy boot info\n", __func__); + goto exit; + } + + if (boot_info == NULL) + goto exit; + + /* copy boot_info to caller */ + retval = synaptics_ts_memcpy((unsigned char *)boot_info, + sizeof(struct synaptics_ts_boot_info), + ts->resp_buf.buf, + ts->resp_buf.buf_size, + copy_size); + if (retval < 0) + input_err(true, ts->dev, "%s:Fail to copy boot info to caller\n", __func__); +exit: + return retval; +} + + +/** + * syna_tcm_get_app_info() + * + * Implement the application fw command code to request an application + * info packet from device. + * + * @param + * [ in] tcm_dev: the device handle + * [out] app_info: the application info packet returned + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int synaptics_ts_get_app_info(struct synaptics_ts_data *ts, + struct synaptics_ts_application_info *app_info) +{ + int retval = 0; + unsigned char resp_code; + unsigned int app_status; + unsigned int resp_data_len = 0; + unsigned int copy_size; + struct synaptics_ts_application_info *info; + int i; + + if (IS_NOT_APP_FW_MODE(ts->dev_mode)) { + input_err(true, ts->dev, "%s:Device is not in application fw mode, mode: %x\n", __func__, + ts->dev_mode); + return -EINVAL; + } + + retval = ts->write_message(ts, + SYNAPTICS_TS_CMD_GET_APPLICATION_INFO, + NULL, + 0, + &resp_code, + 0); + if (retval < 0) { + input_err(true, ts->dev, "%s:Fail to send command 0x%02x\n", __func__, + SYNAPTICS_TS_CMD_GET_APPLICATION_INFO); + goto exit; + } + + resp_data_len = ts->resp_buf.data_length; + copy_size = MIN(sizeof(ts->app_info), resp_data_len); + + info = &ts->app_info; + + /* save the app_info */ + retval = synaptics_ts_memcpy((unsigned char *)info, + sizeof(struct synaptics_ts_application_info), + ts->resp_buf.buf, + ts->resp_buf.buf_size, + copy_size); + if (retval < 0) { + input_err(true, ts->dev, "%s:Fail to copy application info\n", __func__); + goto exit; + } + + if (app_info == NULL) + goto show_info; + + /* copy app_info to caller */ + retval = synaptics_ts_memcpy((unsigned char *)app_info, + sizeof(struct synaptics_ts_application_info), + ts->resp_buf.buf, + ts->resp_buf.buf_size, + copy_size); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to copy application info to caller\n", __func__); + goto exit; + } + +show_info: + app_status = synaptics_ts_pal_le2_to_uint(ts->app_info.status); + + if (app_status == APP_STATUS_BAD_APP_CONFIG) { + input_err(true, ts->dev, "%s: Bad application firmware, status: 0x%x\n", __func__, app_status); + retval = -ENODEV; + goto exit; + } else if (app_status != APP_STATUS_OK) { + input_err(true, ts->dev, "%s:Incorrect application status, 0x%x\n", __func__, app_status); + retval = -ENODEV; + goto exit; + } + + ts->max_objects = synaptics_ts_pal_le2_to_uint(info->max_objects); + ts->max_x = synaptics_ts_pal_le2_to_uint(info->max_x); + ts->max_y = synaptics_ts_pal_le2_to_uint(info->max_y); + ts->plat_data->max_x = ts->max_x; + ts->plat_data->max_y = ts->max_y; + + ts->cols = synaptics_ts_pal_le2_to_uint(info->num_of_image_cols); + ts->rows = synaptics_ts_pal_le2_to_uint(info->num_of_image_rows); + + ts->plat_data->x_node_num = ts->rows; + ts->plat_data->y_node_num = ts->cols; + + synaptics_ts_pal_mem_cpy((unsigned char *)ts->config_id, + MAX_SIZE_CONFIG_ID, + info->customer_config_id, + MAX_SIZE_CONFIG_ID, + MAX_SIZE_CONFIG_ID); + + for (i = 0; i < 4; i++) + ts->plat_data->img_version_of_ic[i] = ts->config_id[i]; + + input_info(true, ts->dev, "%s: ic:%02x%02x%02x%02x\n", __func__, + ts->plat_data->img_version_of_ic[0], ts->plat_data->img_version_of_ic[1], + ts->plat_data->img_version_of_ic[2], ts->plat_data->img_version_of_ic[3]); + + input_info(true, ts->dev, "%s: App info version: %d, status: %d\n", __func__, + synaptics_ts_pal_le2_to_uint(info->version), app_status); + input_info(true, ts->dev, "%s: App info: max_objs: %d, max_x:%d, max_y: %d, img: %dx%d\n", __func__, + ts->max_objects, ts->max_x, ts->max_y, + ts->rows, ts->cols); + +exit: + return retval; +} + +/** + * syna_tcm_v1_dispatch_report() + * + * Handle the TouchCom report packet being received. + * + * If it's an identify report, parse the identification packet and signal + * the command completion just in case. + * Otherwise, copy the data from internal buffer.in to internal buffer.report + * + * @param + * [ in] tcm_dev: the device handle + * + * @return + * none. + */ +static void synaptics_ts_v1_dispatch_report(struct synaptics_ts_data *ts) +{ + int retval; + struct synaptics_ts_message_data_blob *tcm_msg = NULL; + synaptics_ts_pal_completion_t *cmd_completion = NULL; + + if (!ts) { + pr_err("%s%s: Invalid tcm device handle\n", SECLOG, __func__); + return; + } + + tcm_msg = &ts->msg_data; + cmd_completion = &tcm_msg->cmd_completion; + + tcm_msg->report_code = tcm_msg->status_report_code; + + if (tcm_msg->payload_length == 0) { + ts->report_buf.data_length = 0; + goto exit; + } + + /* The identify report may be resulted from reset or fw mode switching + */ + if (tcm_msg->report_code == REPORT_IDENTIFY) { + + synaptics_ts_buf_lock(&tcm_msg->in); + + retval = synaptics_ts_v1_parse_idinfo(ts, + &tcm_msg->in.buf[SYNAPTICS_TS_MESSAGE_HEADER_SIZE], + tcm_msg->in.buf_size - SYNAPTICS_TS_MESSAGE_HEADER_SIZE, + tcm_msg->payload_length); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to identify device\n", __func__); + synaptics_ts_buf_unlock(&tcm_msg->in); + return; + } + + synaptics_ts_buf_unlock(&tcm_msg->in); + + /* in case, the identify info packet is caused by the command */ + if (ATOMIC_GET(tcm_msg->command_status) == CMD_STATE_BUSY) { + switch (tcm_msg->command) { + case SYNAPTICS_TS_CMD_RESET: + input_err(true, ts->dev, "%s: Reset by CMD_RESET\n", __func__); + fallthrough; + case SYNAPTICS_TS_CMD_REBOOT_TO_ROM_BOOTLOADER: + case SYNAPTICS_TS_CMD_RUN_BOOTLOADER_FIRMWARE: + case SYNAPTICS_TS_CMD_RUN_APPLICATION_FIRMWARE: + case SYNAPTICS_TS_CMD_ENTER_PRODUCTION_TEST_MODE: + case SYNAPTICS_TS_CMD_ROMBOOT_RUN_BOOTLOADER_FIRMWARE: + tcm_msg->status_report_code = STATUS_OK; + tcm_msg->response_code = STATUS_OK; + ATOMIC_SET(tcm_msg->command_status, + CMD_STATE_IDLE); + synaptics_ts_pal_completion_complete(cmd_completion); + goto exit; + default: + input_err(true, ts->dev, "%s: Device has been reset\n", __func__); + ATOMIC_SET(tcm_msg->command_status, + CMD_STATE_ERROR); + synaptics_ts_pal_completion_complete(cmd_completion); + goto exit; + } + } + } + + /* store the received report into the internal buffer.report */ + synaptics_ts_buf_lock(&ts->report_buf); + + retval = synaptics_ts_buf_alloc(&ts->report_buf, + tcm_msg->payload_length); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to allocate memory for internal buf.report\n", __func__); + synaptics_ts_buf_unlock(&ts->report_buf); + goto exit; + } + + synaptics_ts_buf_lock(&tcm_msg->in); + + retval = synaptics_ts_pal_mem_cpy(ts->report_buf.buf, + ts->report_buf.buf_size, + &tcm_msg->in.buf[SYNAPTICS_TS_MESSAGE_HEADER_SIZE], + tcm_msg->in.buf_size - SYNAPTICS_TS_MESSAGE_HEADER_SIZE, + tcm_msg->payload_length); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to copy payload to buf_report\n", __func__); + synaptics_ts_buf_unlock(&tcm_msg->in); + synaptics_ts_buf_unlock(&ts->report_buf); + goto exit; + } + + ts->report_buf.data_length = tcm_msg->payload_length; + + synaptics_ts_buf_unlock(&tcm_msg->in); + synaptics_ts_buf_unlock(&ts->report_buf); + +exit: + return; +} + +/** + * syna_tcm_v1_dispatch_response() + * + * Handle the response packet. + * + * Copy the data from internal buffer.in to internal buffer.resp, + * and then signal the command completion. + * + * @param + * [ in] tcm_dev: the device handle + * + * @return + * none. + */ +static void synaptics_ts_v1_dispatch_response(struct synaptics_ts_data *ts) +{ + int retval; + struct synaptics_ts_message_data_blob *tcm_msg = NULL; + synaptics_ts_pal_completion_t *cmd_completion = NULL; + + if (!ts) { + pr_err("%s%s: Invalid tcm device handle\n", SECLOG, __func__); + return; + } + + tcm_msg = &ts->msg_data; + cmd_completion = &tcm_msg->cmd_completion; + + tcm_msg->response_code = tcm_msg->status_report_code; + + if (ATOMIC_GET(tcm_msg->command_status) != CMD_STATE_BUSY) + return; + + if (tcm_msg->payload_length == 0) { + ts->resp_buf.data_length = tcm_msg->payload_length; + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_IDLE); + goto exit; + } + + /* copy the received resp data into the internal buffer.resp */ + synaptics_ts_buf_lock(&ts->resp_buf); + + retval = synaptics_ts_buf_alloc(&ts->resp_buf, + tcm_msg->payload_length); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to allocate memory for internal buf.resp\n", __func__); + synaptics_ts_buf_unlock(&ts->resp_buf); + goto exit; + } + + synaptics_ts_buf_lock(&tcm_msg->in); + + retval = synaptics_ts_pal_mem_cpy(ts->resp_buf.buf, + ts->resp_buf.buf_size, + &tcm_msg->in.buf[SYNAPTICS_TS_MESSAGE_HEADER_SIZE], + tcm_msg->in.buf_size - SYNAPTICS_TS_MESSAGE_HEADER_SIZE, + tcm_msg->payload_length); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to copy payload to internal resp_buf\n", __func__); + synaptics_ts_buf_unlock(&tcm_msg->in); + synaptics_ts_buf_unlock(&ts->resp_buf); + goto exit; + } + + ts->resp_buf.data_length = tcm_msg->payload_length; + + synaptics_ts_buf_unlock(&tcm_msg->in); + synaptics_ts_buf_unlock(&ts->resp_buf); + + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_IDLE); + +exit: + synaptics_ts_pal_completion_complete(cmd_completion); +} + +static int synaptics_ts_v1_continued_read(struct synaptics_ts_data *ts, + unsigned int length) +{ + int retval = 0; + unsigned char marker; + unsigned char code; + unsigned int idx; + unsigned int offset; + unsigned int chunks; + unsigned int chunk_space; + unsigned int xfer_length; + unsigned int total_length; + unsigned int remaining_length; + struct synaptics_ts_message_data_blob *tcm_msg = NULL; + + if (!ts) { + pr_err("%s%s: Invalid tcm device handle\n", SECLOG, __func__); + return -EINVAL; + } + + tcm_msg = &ts->msg_data; + + /* continued read packet contains the header, payload, and a padding */ + total_length = SYNAPTICS_TS_MESSAGE_HEADER_SIZE + length + 1; + remaining_length = total_length - SYNAPTICS_TS_MESSAGE_HEADER_SIZE; + + if (total_length > PAGE_SIZE) { + input_fail_hist(true, ts->dev, "%s: Invalid length %d to read\n", __func__, total_length); + return -EINVAL; + } + + synaptics_ts_buf_lock(&tcm_msg->in); + + /* in case the current buf.in is smaller than requested size */ + retval = synaptics_ts_buf_realloc(&tcm_msg->in, + total_length + 1); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to allocate memory for internal buf_in\n", __func__); + synaptics_ts_buf_unlock(&tcm_msg->in); + return retval; + } + + /* delay for the bus turnaround time */ + synaptics_ts_pal_sleep_us(MSG_DELAY_US_MIN, MSG_DELAY_US_MAX); + + /* available chunk space for payload = + * total chunk size - (header marker byte + header status byte) + */ + if (ts->max_rd_size == 0) + chunk_space = remaining_length; + else + chunk_space = ts->max_rd_size - 2; + + chunks = synaptics_ts_pal_ceil_div(remaining_length, chunk_space); + chunks = chunks == 0 ? 1 : chunks; + + offset = SYNAPTICS_TS_MESSAGE_HEADER_SIZE; + + synaptics_ts_buf_lock(&tcm_msg->temp); + + for (idx = 0; idx < chunks; idx++) { + if (remaining_length > chunk_space) + xfer_length = chunk_space; + else + xfer_length = remaining_length; + + if (xfer_length == 1) { + tcm_msg->in.buf[offset] = SYNAPTICS_TS_V1_MESSAGE_PADDING; + offset += xfer_length; + remaining_length -= xfer_length; + continue; + } + + /* allocate the internal temp buffer */ + retval = synaptics_ts_buf_alloc(&tcm_msg->temp, + xfer_length + 2); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to allocate memory for internal buf.temp\n", __func__); + synaptics_ts_buf_unlock(&tcm_msg->temp); + synaptics_ts_buf_unlock(&tcm_msg->in); + return retval; + } + /* retrieve data from the bus + * data should include header marker and status code + */ + retval = ts->synaptics_ts_read_data_only(ts, + tcm_msg->temp.buf, + xfer_length + 2); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to read %d bytes from device\n", __func__, + xfer_length + 2); + synaptics_ts_buf_unlock(&tcm_msg->temp); + synaptics_ts_buf_unlock(&tcm_msg->in); + return retval; + } + + /* check the data content */ + marker = tcm_msg->temp.buf[0]; + code = tcm_msg->temp.buf[1]; + + if (marker != SYNAPTICS_TS_V1_MESSAGE_MARKER) { + input_err(true, ts->dev, "%s: Incorrect header marker, 0x%02x\n", __func__, + marker); + synaptics_ts_buf_unlock(&tcm_msg->temp); + synaptics_ts_buf_unlock(&tcm_msg->in); + return -EIO; + } + + if (code != STATUS_CONTINUED_READ) { + input_err(true, ts->dev, "%s: Incorrect header status code, 0x%02x\n", __func__, + code); + synaptics_ts_buf_unlock(&tcm_msg->temp); + synaptics_ts_buf_unlock(&tcm_msg->in); + return -EIO; + } + + /* copy data from internal buffer.temp to buffer.in */ + retval = synaptics_ts_pal_mem_cpy(&tcm_msg->in.buf[offset], + tcm_msg->in.buf_size - offset, + &tcm_msg->temp.buf[2], + tcm_msg->temp.buf_size - 2, + xfer_length); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to copy payload\n", __func__); + synaptics_ts_buf_unlock(&tcm_msg->temp); + synaptics_ts_buf_unlock(&tcm_msg->in); + return retval; + } + + offset += xfer_length; + remaining_length -= xfer_length; + } + + synaptics_ts_buf_unlock(&tcm_msg->temp); + synaptics_ts_buf_unlock(&tcm_msg->in); + + return 0; +} + + +/** + * syna_tcm_v1_read_message() + * + * Read in a TouchCom packet from device. + * The packet including its payload is read in from device and stored in + * the internal buffer.resp or buffer.report based on the code received. + * + * @param + * [ in] tcm_dev: the device handle + * [out] status_report_code: status code or report code received + * + * @return + * 0 or positive value on success; otherwise, on error. + */ +static int synaptics_ts_v1_read_message(struct synaptics_ts_data *ts, unsigned char *status_report_code) +{ + int retval = 0; + bool retry; + struct synaptics_ts_message_data_blob *tcm_msg = NULL; + synaptics_ts_pal_mutex_t *rw_mutex = NULL; + synaptics_ts_pal_completion_t *cmd_completion = NULL; + + if (!ts) { + pr_err("%s%s:Invalid tcm device handle\n", SECLOG, __func__); + return -EINVAL; + } + + if (sec_check_secure_trusted_mode_status(ts->plat_data)) + return -EBUSY; + + if (!ts->header) { + ts->header = devm_kzalloc(ts->dev, sizeof(struct synaptics_ts_v1_message_header), GFP_KERNEL); + if (!ts->header) + return -ENODEV; + } + +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) + if (STUI_MODE_TOUCH_SEC & stui_get_mode()) + return -EBUSY; +#endif + tcm_msg = &ts->msg_data; + rw_mutex = &tcm_msg->rw_mutex; + cmd_completion = &tcm_msg->cmd_completion; + + if (status_report_code) + *status_report_code = STATUS_INVALID; + + synaptics_ts_pal_mutex_lock(rw_mutex); + + retry = true; + +retry: + synaptics_ts_buf_lock(&tcm_msg->in); + + /* in case the current in.buf is smaller than header size */ + retval = synaptics_ts_buf_realloc(&tcm_msg->in, SYNAPTICS_TS_MESSAGE_HEADER_SIZE); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to allocate memory for internal buf_in\n", __func__); + synaptics_ts_buf_unlock(&tcm_msg->in); + tcm_msg->status_report_code = STATUS_INVALID; + tcm_msg->payload_length = 0; + goto exit; + } + + /* read in the message header from device */ + retval = ts->synaptics_ts_read_data_only(ts, + tcm_msg->in.buf, + SYNAPTICS_TS_MESSAGE_HEADER_SIZE); + if (retval < 0) { + input_err(true, ts->dev, "%s:Fail to read message header from device\n", __func__); + synaptics_ts_buf_unlock(&tcm_msg->in); + + tcm_msg->status_report_code = STATUS_INVALID; + tcm_msg->payload_length = 0; + + if (retry) { + synaptics_ts_pal_sleep_us(RD_RETRY_US_MIN, RD_RETRY_US_MAX); + retry = false; + goto retry; + } + goto exit; + } + + /* check the message header */ + memcpy(ts->header, tcm_msg->in.buf, sizeof(struct synaptics_ts_v1_message_header)); + + if (ts->header->marker != SYNAPTICS_TS_V1_MESSAGE_MARKER) { + input_err(true, ts->dev, "%s:Incorrect header marker, 0x%02x\n", __func__, ts->header->marker); + synaptics_ts_buf_unlock(&tcm_msg->in); + + tcm_msg->status_report_code = STATUS_INVALID; + tcm_msg->payload_length = 0; + + retval = -1; + if (retry) { + synaptics_ts_pal_sleep_us(RD_RETRY_US_MIN, RD_RETRY_US_MAX); + retry = false; + goto retry; + } + goto exit; + } + + tcm_msg->status_report_code = ts->header->code; + + tcm_msg->payload_length = synaptics_ts_pal_le2_to_uint(ts->header->length); + + if (tcm_msg->status_report_code != STATUS_IDLE) + input_dbg(false, ts->dev, "%s:Status code: 0x%02x, length: %d (%02x %02x %02x %02x)\n", __func__, + tcm_msg->status_report_code, tcm_msg->payload_length, + ts->header->marker, ts->header->code, ts->header->length[0], + ts->header->length[1]); + + synaptics_ts_buf_unlock(&tcm_msg->in); + + /* retrieve the remaining data, if any */ + if (tcm_msg->payload_length > 0) { + retval = synaptics_ts_v1_continued_read(ts, + tcm_msg->payload_length); + if (retval < 0) { + input_err(true, ts->dev, "Fail to do continued read\n"); + goto exit; + } + } + + if ((tcm_msg->status_report_code <= STATUS_ERROR) || + (tcm_msg->status_report_code == STATUS_INVALID)) { + switch (tcm_msg->status_report_code) { + case STATUS_OK: + break; + case STATUS_CONTINUED_READ: + input_err(true, ts->dev, "%s:Out-of-sync continued read\n", __func__); + fallthrough; + case STATUS_IDLE: + tcm_msg->payload_length = 0; + retval = 0; + goto exit; + case STATUS_BAD_COMMAND: + input_fail_hist(true, ts->dev, "%s:Status Bad\n", __func__); + tcm_msg->payload_length = 0; + goto do_dispatch; + default: + input_fail_hist(true, ts->dev, "%s:Incorrect Status code, 0x%02x\n", __func__, + tcm_msg->status_report_code); + tcm_msg->payload_length = 0; + goto do_dispatch; + } + } + + /* refill the header for dispatching */ + synaptics_ts_buf_lock(&tcm_msg->in); + + tcm_msg->in.buf[0] = SYNAPTICS_TS_V1_MESSAGE_MARKER; + tcm_msg->in.buf[1] = tcm_msg->status_report_code; + tcm_msg->in.buf[2] = (unsigned char)tcm_msg->payload_length; + tcm_msg->in.buf[3] = (unsigned char)(tcm_msg->payload_length >> 8); + + synaptics_ts_buf_unlock(&tcm_msg->in); + +do_dispatch: + /* duplicate the data to external buffer */ + if (tcm_msg->payload_length > 0) { + retval = synaptics_ts_buf_alloc(&ts->external_buf, + tcm_msg->payload_length); + if (retval < 0) { + input_err(true, ts->dev, "%s:Fail to allocate memory, external_buf invalid\n", __func__); + } else { + retval = synaptics_ts_pal_mem_cpy(&ts->external_buf.buf[0], + tcm_msg->payload_length, + &tcm_msg->in.buf[SYNAPTICS_TS_MESSAGE_HEADER_SIZE], + tcm_msg->in.buf_size - SYNAPTICS_TS_MESSAGE_HEADER_SIZE, + tcm_msg->payload_length); + if (retval < 0) + input_err(true, ts->dev, "%s: Fail to copy data to external buffer\n", __func__); + } + } + ts->external_buf.data_length = tcm_msg->payload_length; + + /* process the retrieved packet */ + if (tcm_msg->status_report_code >= REPORT_IDENTIFY) + synaptics_ts_v1_dispatch_report(ts); + else + synaptics_ts_v1_dispatch_response(ts); + + /* copy the status report code to caller */ + if (status_report_code) + *status_report_code = tcm_msg->status_report_code; + + retval = 0; + +exit: + if (retval < 0) { + if (ATOMIC_GET(tcm_msg->command_status) == CMD_STATE_BUSY) { + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_ERROR); + synaptics_ts_pal_completion_complete(cmd_completion); + } + } + + /* have a delay in case the next message comes in a very short period */ + synaptics_ts_pal_sleep_us(MSG_DELAY_US_MIN, MSG_DELAY_US_MAX); + + synaptics_ts_pal_mutex_unlock(rw_mutex); + + return retval; + +} + +static int synaptics_ts_v1_write_message(struct synaptics_ts_data *ts, + unsigned char command, unsigned char *payload, + unsigned int payload_len, unsigned char *resp_code, + unsigned int delay_ms_resp) +{ + int retval = 0; + unsigned int idx; + unsigned int chunks; + unsigned int chunk_space; + unsigned int xfer_length; + unsigned int remaining_length; + int timeout = 0; + int cmd_response_timeout = CMD_RESPONSE_TIMEOUT_MS; + int polling_ms = 0; + struct synaptics_ts_message_data_blob *tcm_msg = NULL; + synaptics_ts_pal_mutex_t *cmd_mutex = NULL; + synaptics_ts_pal_mutex_t *rw_mutex = NULL; + synaptics_ts_pal_completion_t *cmd_completion = NULL; + + if (!ts) { + pr_err("%s%s: Invalid tcm device handle\n", SECLOG, __func__); + return -EINVAL; + } + + if (sec_check_secure_trusted_mode_status(ts->plat_data)) + return -EBUSY; + +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) + if (STUI_MODE_TOUCH_SEC & stui_get_mode()) + return -EBUSY; +#endif + + tcm_msg = &ts->msg_data; + cmd_mutex = &tcm_msg->cmd_mutex; + rw_mutex = &tcm_msg->rw_mutex; + cmd_completion = &tcm_msg->cmd_completion; + + if (resp_code) + *resp_code = STATUS_INVALID; + + synaptics_ts_pal_mutex_lock(cmd_mutex); + + if (delay_ms_resp != FORCE_ATTN_DRIVEN) + disable_irq_nosync(ts->irq); + + synaptics_ts_pal_mutex_lock(rw_mutex); + + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_BUSY); + + /* reset the command completion */ + synaptics_ts_pal_completion_reset(cmd_completion); + + tcm_msg->command = command; + + /* adding two length bytes as part of payload */ + remaining_length = payload_len + 2; + + /* available space for payload = total size - command byte */ + if (ts->max_wr_size == 0) + chunk_space = remaining_length; + else + chunk_space = ts->max_wr_size - 1; + + chunks = synaptics_ts_pal_ceil_div(remaining_length, chunk_space); + chunks = chunks == 0 ? 1 : chunks; + + input_dbg(false, ts->dev, "%s: Command: 0x%02x, payload len: %d\n", __func__, command, payload_len); + + synaptics_ts_buf_lock(&tcm_msg->out); + + for (idx = 0; idx < chunks; idx++) { + if (remaining_length > chunk_space) + xfer_length = chunk_space; + else + xfer_length = remaining_length; + + /* allocate the space storing the written data */ + retval = synaptics_ts_buf_alloc(&tcm_msg->out, + xfer_length + 1); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to allocate memory for internal buf.out\n", __func__); + synaptics_ts_buf_unlock(&tcm_msg->out); + synaptics_ts_pal_mutex_unlock(rw_mutex); + goto exit; + } + + /* construct the command packet */ + if (idx == 0) { + tcm_msg->out.buf[0] = tcm_msg->command; + tcm_msg->out.buf[1] = (unsigned char)payload_len; + tcm_msg->out.buf[2] = (unsigned char)(payload_len >> 8); + + if (xfer_length > 2) { + retval = synaptics_ts_pal_mem_cpy(&tcm_msg->out.buf[3], + tcm_msg->out.buf_size - 3, + payload, + remaining_length - 2, + xfer_length - 2); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to copy payload\n", __func__); + synaptics_ts_buf_unlock(&tcm_msg->out); + synaptics_ts_pal_mutex_unlock(rw_mutex); + goto exit; + } + } + } + /* construct the continued writes packet */ + else { + tcm_msg->out.buf[0] = SYNAPTICS_TS_CMD_CONTINUE_WRITE; + + retval = synaptics_ts_pal_mem_cpy(&tcm_msg->out.buf[1], + tcm_msg->out.buf_size - 1, + &payload[idx * chunk_space - 2], + remaining_length, + xfer_length); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to copy continued write\n", __func__); + synaptics_ts_buf_unlock(&tcm_msg->out); + synaptics_ts_pal_mutex_unlock(rw_mutex); + goto exit; + } + } + + /* write command packet to the device */ + retval = ts->synaptics_ts_write_data(ts, + tcm_msg->out.buf, + xfer_length + 1, NULL, 0); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to write %d bytes to device\n", __func__, + xfer_length + 1); + synaptics_ts_buf_unlock(&tcm_msg->out); + synaptics_ts_pal_mutex_unlock(rw_mutex); + goto exit; + } + + remaining_length -= xfer_length; + + if (chunks > 1) + synaptics_ts_pal_sleep_us(WR_DELAY_US_MIN, WR_DELAY_US_MAX); + } + + synaptics_ts_buf_unlock(&tcm_msg->out); + + synaptics_ts_pal_mutex_unlock(rw_mutex); + + /* polling the command response */ + timeout = 0; + + if ((command == SYNAPTICS_TS_CMD_REZERO) || (command == CMD_READ_SEC_SPONGE_REG) || + (command == CMD_WRITE_SEC_SPONGE_REG) || (command == CMD_SET_GRIP)) + cmd_response_timeout = CMD_RESPONSE_SHORT_TIMEOUT_MS; + + if (delay_ms_resp == FORCE_ATTN_DRIVEN) + polling_ms = cmd_response_timeout; + else + polling_ms = (delay_ms_resp > 0) ? delay_ms_resp : + CMD_RESPONSE_POLLING_DELAY_MS; + + do { + if (atomic_read(&ts->plat_data->power_state) == SEC_INPUT_STATE_POWER_OFF) + goto check_response; + + /* wait for the command completion triggered by read_message */ + retval = synaptics_ts_pal_completion_wait_for(cmd_completion, + polling_ms); + if (retval < 0) + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_BUSY); + + if (ATOMIC_GET(tcm_msg->command_status) != CMD_STATE_IDLE) + timeout += polling_ms + 10; + else + goto check_response; + + /* retrieve the message packet back */ + if (delay_ms_resp != FORCE_ATTN_DRIVEN) { + retval = synaptics_ts_v1_read_message(ts, NULL); + if (retval < 0) + synaptics_ts_pal_completion_reset(cmd_completion); + } + + } while (timeout < cmd_response_timeout); + + if ((tcm_msg->response_code != STATUS_OK) && (timeout >= cmd_response_timeout)) { + input_fail_hist(true, ts->dev, "Timed out waiting for response of command 0x%02x (%dms)\n", + command, timeout); + retval = -ETIMEDOUT; + goto exit; + } + +check_response: + if (ATOMIC_GET(tcm_msg->command_status) != CMD_STATE_IDLE) { + input_err(true, ts->dev, "Fail to get valid response of command 0x%02x\n", + command); + retval = -EIO; + goto exit; + } + + /* copy response code to the caller */ + if (resp_code) + *resp_code = tcm_msg->response_code; + + if (tcm_msg->response_code != STATUS_OK) { + input_err(true, ts->dev, "Error code 0x%02x of command 0x%02x\n", + tcm_msg->response_code, tcm_msg->command); + retval = -EIO; + } else { + retval = 0; + } + +exit: + tcm_msg->command = SYNAPTICS_TS_CMD_NONE; + + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_IDLE); + + if (delay_ms_resp != FORCE_ATTN_DRIVEN) + enable_irq(ts->irq); + + synaptics_ts_pal_mutex_unlock(cmd_mutex); + + return retval; + +} + +static int synaptics_ts_v1_write_immediate_message(struct synaptics_ts_data *ts, + unsigned char command, unsigned char *payload, unsigned int payload_len) +{ + int retval = 0; + unsigned int idx; + unsigned int chunks; + unsigned int chunk_space; + unsigned int xfer_length; + unsigned int remaining_length; + struct synaptics_ts_message_data_blob *tcm_msg = NULL; + synaptics_ts_pal_mutex_t *cmd_mutex = NULL; + synaptics_ts_pal_mutex_t *rw_mutex = NULL; + + if (!ts) { + pr_err("%s%s: Invalid tcm device handle\n", SECLOG, __func__); + return -EINVAL; + } + + if (!ts->support_immediate_cmd) { + input_err(true, ts->dev, "%s: not support immediate cmd\n", __func__); + return -EINVAL; + } + + if (sec_check_secure_trusted_mode_status(ts->plat_data)) + return -EBUSY; + +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) + if (STUI_MODE_TOUCH_SEC & stui_get_mode()) + return -EBUSY; +#endif + + /* add the extra condition if any other immediate command is supported. */ + if (command != CMD_SET_IMMEDIATE_DYNAMIC_CONFIG && command != SYNAPTICS_TS_CMD_ERASE_FLASH) { + pr_err("Invalid command, 0x%02x is not a immediate command\n", command); + return -EINVAL; + } + + tcm_msg = &ts->msg_data; + cmd_mutex = &tcm_msg->cmd_mutex; + rw_mutex = &tcm_msg->rw_mutex; + + synaptics_ts_pal_mutex_lock(cmd_mutex); + + synaptics_ts_pal_mutex_lock(rw_mutex); + + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_BUSY); + + tcm_msg->command = command; + + /* adding two length bytes as part of payload */ + remaining_length = payload_len + 2; + + /* available space for payload = total size - command byte */ + if (ts->max_wr_size == 0) + chunk_space = remaining_length; + else + chunk_space = ts->max_wr_size - 1; + + chunks = synaptics_ts_pal_ceil_div(remaining_length, chunk_space); + chunks = chunks == 0 ? 1 : chunks; + + input_dbg(false, ts->dev, "%s: Command: 0x%02x, payload len: %d\n", __func__, command, payload_len); + + synaptics_ts_buf_lock(&tcm_msg->out); + + for (idx = 0; idx < chunks; idx++) { + if (remaining_length > chunk_space) + xfer_length = chunk_space; + else + xfer_length = remaining_length; + + /* allocate the space storing the written data */ + retval = synaptics_ts_buf_alloc(&tcm_msg->out, + xfer_length + 1); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to allocate memory for internal buf.out\n", __func__); + synaptics_ts_buf_unlock(&tcm_msg->out); + synaptics_ts_pal_mutex_unlock(rw_mutex); + goto exit; + } + + /* construct the command packet */ + if (idx == 0) { + tcm_msg->out.buf[0] = tcm_msg->command; + tcm_msg->out.buf[1] = (unsigned char)payload_len; + tcm_msg->out.buf[2] = (unsigned char)(payload_len >> 8); + + if (xfer_length > 2) { + retval = synaptics_ts_pal_mem_cpy(&tcm_msg->out.buf[3], + tcm_msg->out.buf_size - 3, + payload, + remaining_length - 2, + xfer_length - 2); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to copy payload\n", __func__); + synaptics_ts_buf_unlock(&tcm_msg->out); + synaptics_ts_pal_mutex_unlock(rw_mutex); + goto exit; + } + } + } + /* construct the continued writes packet */ + else { + tcm_msg->out.buf[0] = SYNAPTICS_TS_CMD_CONTINUE_WRITE; + + retval = synaptics_ts_pal_mem_cpy(&tcm_msg->out.buf[1], + tcm_msg->out.buf_size - 1, + &payload[idx * chunk_space - 2], + remaining_length, + xfer_length); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to copy continued write\n", __func__); + synaptics_ts_buf_unlock(&tcm_msg->out); + synaptics_ts_pal_mutex_unlock(rw_mutex); + goto exit; + } + } + + /* write command packet to the device */ + retval = ts->synaptics_ts_write_data(ts, + tcm_msg->out.buf, + xfer_length + 1, NULL, 0); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to write %d bytes to device\n", __func__, + xfer_length + 1); + synaptics_ts_buf_unlock(&tcm_msg->out); + synaptics_ts_pal_mutex_unlock(rw_mutex); + goto exit; + } + + remaining_length -= xfer_length; + + if (chunks > 1) + synaptics_ts_pal_sleep_us(WR_DELAY_US_MIN, WR_DELAY_US_MAX); + } + + synaptics_ts_buf_unlock(&tcm_msg->out); + + synaptics_ts_pal_mutex_unlock(rw_mutex); + + retval = 0; + +exit: + tcm_msg->command = SYNAPTICS_TS_CMD_NONE; + + ATOMIC_SET(tcm_msg->command_status, CMD_STATE_IDLE); + + synaptics_ts_pal_mutex_unlock(cmd_mutex); + + return retval; + +} + +/** + * syna_tcm_enable_report() + * + * Implement the application fw command code to enable or disable a report. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] report_code: the requested report code being generated + * [ in] en: '1' for enabling; '0' for disabling + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int synaptics_ts_enable_report(struct synaptics_ts_data *ts, + unsigned char report_code, bool en) +{ + int retval = 0; + unsigned char resp_code; + unsigned char command; + + if (IS_NOT_APP_FW_MODE(ts->dev_mode)) { + input_err(true, ts->dev, "%s: Device is not in application fw mode, mode: %x\n", __func__, + ts->dev_mode); + return -EINVAL; + } + + command = (en) ? SYNAPTICS_TS_CMD_ENABLE_REPORT : SYNAPTICS_TS_CMD_DISABLE_REPORT; + + retval = ts->write_message(ts, + command, + &report_code, + 1, + &resp_code, + 0); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to send command 0x%02x to %s 0x%02x report\n", __func__, + command, (en)?"enable":"disable", report_code); + goto exit; + } + + if (resp_code != STATUS_OK) { + input_err(true, ts->dev, "%s: Fail to %s 0x%02x report, resp_code:%x\n", __func__, + (en) ? "enable" : "disable", report_code, resp_code); + } else { + input_err(true, ts->dev, "%s: Report 0x%x %s\n", __func__, report_code, + (en) ? "enabled" : "disabled"); + } + +exit: + return retval; +} + +/** + * syna_tcm_preserve_touch_report_config() + * + * Retrieve and preserve the current touch report configuration. + * + * The retrieved configuration is stored in touch_config buffer defined + * in structure syna_tcm_dev for later using of touch position parsing. + * + * The touch_config buffer will be allocated internally and its size will + * be updated accordingly. + * + * @param + * [ in] tcm_dev: the device handle + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int synaptics_ts_preserve_touch_report_config(struct synaptics_ts_data *ts) +{ + int retval = 0; + unsigned char resp_code; + unsigned int size = 0; + + if (IS_NOT_APP_FW_MODE(ts->dev_mode)) { + input_err(true, ts->dev, "%s: Device is not in application fw mode, mode: %x\n", __func__, + ts->dev_mode); + return -EINVAL; + } + + retval = ts->write_message(ts, + SYNAPTICS_TS_CMD_GET_TOUCH_REPORT_CONFIG, + NULL, + 0, + &resp_code, + 0); + if (retval < 0) { + input_err(true, ts->dev, "Fail to write command CMD_GET_TOUCH_REPORT_CONFIG\n"); + goto exit; + } + + synaptics_ts_buf_lock(&ts->resp_buf); + + size = ts->resp_buf.data_length; + retval = synaptics_ts_buf_alloc(&ts->touch_config, + size); + if (retval < 0) { + input_err(true, ts->dev, "Fail to allocate memory for internal touch_config\n"); + synaptics_ts_buf_unlock(&ts->resp_buf); + goto exit; + } + + synaptics_ts_buf_lock(&ts->touch_config); + + retval = synaptics_ts_pal_mem_cpy(ts->touch_config.buf, + ts->touch_config.buf_size, + ts->resp_buf.buf, + ts->resp_buf.buf_size, + size); + if (retval < 0) { + input_err(true, ts->dev, "Fail to clone touch config\n"); + synaptics_ts_buf_unlock(&ts->touch_config); + synaptics_ts_buf_unlock(&ts->resp_buf); + goto exit; + } + + ts->touch_config.data_length = size; + + synaptics_ts_buf_unlock(&ts->touch_config); + synaptics_ts_buf_unlock(&ts->resp_buf); + +exit: + return retval; +} + +int synaptics_ts_set_up_rawdata_report_type(struct synaptics_ts_data *ts) +{ + int retval = 0; + + if (!ts->plat_data->support_rawdata) + return 0; + + /* VERIFY_SEC_REPORTS code segmant is used to verify the + * SEC reports in the prior stage, it should be disabled + * at the final release and configured in default fw. + */ + /* preserve the format of touch report */ + retval = synaptics_ts_preserve_touch_report_config(ts); + if (retval < 0) { + input_err(true, ts->dev, "Fail to preserve touch report config\n"); + return -EIO; + } + + if (ts->raw_mode) + retval = synaptics_ts_set_immediate_dynamic_config(ts, DC_ENABLE_EXTENDED_TOUCH_REPORT_CONTROL, 0x01); + else + retval = synaptics_ts_set_immediate_dynamic_config(ts, DC_ENABLE_EXTENDED_TOUCH_REPORT_CONTROL, 0x00); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to set immediate dynamic config\n", __func__); + return -EIO; + } + + return retval; +} +/** + * syna_dev_set_up_app_fw() + * + * Implement the essential steps for the initialization including the + * preparation of app info and the configuration of touch report. + * + * This function should be called whenever the device initially powers + * up, resets, or firmware update. + * + * @param + * [ in] tcm: tcm driver handle + * + * @return + * on success, 0; otherwise, negative value on error. + */ +int synaptics_ts_set_up_app_fw(struct synaptics_ts_data *ts) +{ + int retval = 0; + + if (IS_NOT_APP_FW_MODE(ts->dev_mode)) { + input_err(true, ts->dev, "%s: Application firmware not running, current mode: %02x\n", __func__, + ts->dev_mode); + return -EINVAL; + } + + /* collect app info containing most of sensor information */ + retval = synaptics_ts_get_app_info(ts, &ts->app_info); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get application info\n", __func__); + return -EIO; + } + + /* VERIFY_SEC_REPORTS code segmant is used to verify the + * SEC reports in the prior stage, it should be disabled + * at the final release and configured in default fw. + */ + /* preserve the format of touch report */ + retval = synaptics_ts_preserve_touch_report_config(ts); + if (retval < 0) { + input_err(true, ts->dev, "Fail to preserve touch report config\n"); + return -EIO; + } + + /* disable the generic touchcomm touch report */ + retval = synaptics_ts_enable_report(ts, + REPORT_TOUCH, false); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to disable touch report\n", __func__); + return -EIO; + } + /* enable the sec reports */ + retval = synaptics_ts_enable_report(ts, + REPORT_SEC_SPONGE_GESTURE, true); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to enable sec gesture report\n", __func__); + return -EIO; + } + retval = synaptics_ts_enable_report(ts, + REPORT_SEC_COORDINATE_EVENT, true); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to enable sec coordinate report\n", __func__); + return -EIO; + } + retval = synaptics_ts_enable_report(ts, + REPORT_SEC_STATUS_EVENT, true); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to enable sec status report\n", __func__); + return -EIO; + } + + return retval; +} + + +/** + * syna_tcm_v1_parse_idinfo() + * + * Copy the given data to the identification info structure + * and parse the basic information, e.g. fw build id. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] data: data buffer + * [ in] size: size of given data buffer + * [ in] data_len: length of actual data + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int synaptics_ts_v1_parse_idinfo(struct synaptics_ts_data *ts, + unsigned char *data, unsigned int size, unsigned int data_len) +{ + int retval; + unsigned int wr_size = 0; + unsigned int build_id = 0; + struct synaptics_ts_identification_info *id_info; + + if ((!data) || (data_len == 0)) { + input_err(true, ts->dev, "%s:Invalid given data buffer\n", __func__); + return -EINVAL; + } + + id_info = &ts->id_info; + retval = synaptics_ts_memcpy((unsigned char *)id_info, + sizeof(struct synaptics_ts_identification_info), + data, + size, + MIN(sizeof(*id_info), data_len)); + if (retval < 0) { + input_info(true, ts->dev, "%s: Fail to copy identification info\n", __func__); + return retval; + } + + build_id = synaptics_ts_pal_le4_to_uint(id_info->build_id); + + wr_size = synaptics_ts_pal_le2_to_uint(id_info->max_write_size); + ts->max_wr_size = MIN(wr_size, ts->max_wr_size); + if (ts->max_wr_size == 0) + ts->max_wr_size = wr_size; + + input_info(true, ts->dev, "%s: max_wr_size = %d\n", __func__, ts->max_wr_size); + + input_info(true, ts->dev, "%s:TCM Fw mode: 0x%02x\n", __func__, id_info->mode); + + ts->packrat_number = build_id; + + ts->dev_mode = id_info->mode; + + return 0; +} + +int synaptics_ts_v1_detect(struct synaptics_ts_data *ts) +{ + int retval; + struct synaptics_ts_v1_message_header *header; + struct synaptics_ts_message_data_blob *tcm_msg = NULL; + unsigned int payload_length = 0; + unsigned char data[4] = { 0 }; + + if (!ts) { + pr_err("%s%s:Invalid tcm device handle\n", SECLOG, __func__); + return -EINVAL; + } + + tcm_msg = &ts->msg_data; + + synaptics_ts_pal_mutex_lock(&tcm_msg->rw_mutex); + + retval = ts->synaptics_ts_get_baredata(ts, data, SYNAPTICS_TS_MESSAGE_HEADER_SIZE); + if (retval < 0) { + input_fail_hist(true, ts->dev, "%s: failed to get bare data\n", __func__); + synaptics_ts_pal_mutex_unlock(&tcm_msg->rw_mutex); + return -EINVAL; + } + + if (data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x00 && data[3] == 0x00) { + input_fail_hist(true, ts->dev, "%s: baredata, %02x %02x %02x %02x\n", __func__, + data[0], data[1], data[2], data[3]); +#if IS_ENABLED(CONFIG_SEC_FACTORY) + /* return when empty baredata while start_device for pretest without panel */ + if (mutex_is_locked(&ts->device_mutex)) { + synaptics_ts_pal_mutex_unlock(&tcm_msg->rw_mutex); + return -ENODEV; + } +#endif + } else { + input_info(true, ts->dev, "%s: baredata, %02x %02x %02x %02x\n", __func__, + data[0], data[1], data[2], data[3]); + } + + header = (struct synaptics_ts_v1_message_header *)data; + + if ((header->marker == SYNAPTICS_TS_V1_MESSAGE_MARKER) && (header->length > 0)) { + /* retrieve the identify info packet */ + payload_length = synaptics_ts_pal_le2_to_uint(header->length); + retval = synaptics_ts_v1_continued_read(ts, + payload_length); + if (retval < 0) { + input_info(true, ts->dev, "%s:Fail to read in identify info packet\n", __func__); + synaptics_ts_pal_mutex_unlock(&tcm_msg->rw_mutex); + return retval; + } + } + + synaptics_ts_pal_mutex_unlock(&tcm_msg->rw_mutex); + + if (header->code == REPORT_IDENTIFY) { + synaptics_ts_buf_lock(&tcm_msg->in); + retval = synaptics_ts_v1_parse_idinfo(ts, + &tcm_msg->in.buf[SYNAPTICS_TS_MESSAGE_HEADER_SIZE], + tcm_msg->in.buf_size - SYNAPTICS_TS_MESSAGE_HEADER_SIZE, + payload_length); + if (retval < 0) { + input_info(true, ts->dev, "%s:Fail to identify device\n", __func__); + synaptics_ts_buf_unlock(&tcm_msg->in); + return retval; + } + synaptics_ts_buf_unlock(&tcm_msg->in); + } + + /* expose the read / write operations */ + ts->read_message = synaptics_ts_v1_read_message; + ts->write_message = synaptics_ts_v1_write_message; + ts->write_immediate_message = synaptics_ts_v1_write_immediate_message; + + return retval; +} + +int synaptics_ts_detect_protocol(struct synaptics_ts_data *ts) +{ + return synaptics_ts_v1_detect(ts); +} + +int synaptics_ts_set_custom_library(struct synaptics_ts_data *ts) +{ + int retval; + unsigned short offset; + unsigned char data; + unsigned char data_orig; + unsigned char data_dump[2]; + unsigned char force_fod_enable = 0; + + offset = 0; + + retval = ts->synaptics_ts_read_sponge(ts, + &data, sizeof(data), offset, 1); + if (retval < 0) { + input_err(true, ts->dev, "Fail to read sponge reg, offset:0x%x len:%d\n", + offset, 1); + return retval; + } + + data_orig = data; + +#if IS_ENABLED(CONFIG_SEC_FACTORY) + /* enable FOD when LCD on state */ + if (ts->plat_data->support_fod && atomic_read(&ts->plat_data->enabled)) + force_fod_enable = SEC_TS_MODE_SPONGE_PRESS; +#endif + + if (ts->plat_data->prox_power_off) { + data = ts->plat_data->lowpower_mode & ~SEC_TS_MODE_SPONGE_DOUBLETAP_TO_WAKEUP; + input_info(true, ts->dev, "%s: prox off. disable AOT\n", __func__); + } else { + data = ts->plat_data->lowpower_mode | force_fod_enable; + } + + input_info(true, ts->dev, "sponge data:0x%x (original:0x%x)%s\n", + data, data_orig, + force_fod_enable ? ", force fod enable" : ""); + + retval = ts->synaptics_ts_write_sponge(ts, + &data, sizeof(data), offset, 1); + if (retval < 0) { + input_err(true, ts->dev, "Fail to write sponge reg, data:0x%x offset:0x%x\n", + data, offset); + return retval; + } + +#if !IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP) + /* inf dump enable */ + offset = SEC_TS_CMD_SPONGE_DUMP_FLUSH; + data = ts->plat_data->sponge_mode |= SEC_TS_MODE_SPONGE_INF_DUMP; + retval = ts->synaptics_ts_write_sponge(ts, &data, 1, offset, 1); + if (retval < 0) { + input_err(true, ts->dev, "Fail to write sponge reg, data:0x%x, offset:0x%x\n", + data, offset); + return retval; + } +#endif + + /* read dump info */ + retval = ts->synaptics_ts_read_sponge(ts, + data_dump, sizeof(data_dump), SEC_TS_CMD_SPONGE_LP_DUMP, sizeof(data_dump)); + if (retval < 0) { + input_err(true, ts->dev, "%s: Failed to read dump_data\n", __func__); + return retval; + } + + /* synaptics ic have problem with sponge infinite dump. it takes too long time while isr. + * need to be enable after fix this issue + * ts->sponge_inf_dump = (data_dump[0] & SEC_TS_SPONGE_DUMP_INF_MASK) >> SEC_TS_SPONGE_DUMP_INF_SHIFT; + */ + ts->sponge_dump_format = data_dump[0] & SEC_TS_SPONGE_DUMP_EVENT_MASK; + ts->sponge_dump_event = data_dump[1]; + ts->sponge_dump_border = SEC_TS_CMD_SPONGE_LP_DUMP_EVENT + + (ts->sponge_dump_format * ts->sponge_dump_event); + ts->sponge_dump_border_lsb = ts->sponge_dump_border & 0xFF; + ts->sponge_dump_border_msb = (ts->sponge_dump_border & 0xFF00) >> 8; + + return 0; +} + +void synaptics_ts_get_custom_library(struct synaptics_ts_data *ts) +{ + u8 data[6] = { 0 }; + int ret, i; + unsigned short offset; + + offset = SEC_TS_CMD_SPONGE_AOD_ACTIVE_INFO; + ret = ts->synaptics_ts_read_sponge(ts, + data, sizeof(data), offset, sizeof(data)); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to read aod active area\n", __func__); + return; + } + + for (i = 0; i < 3; i++) + ts->plat_data->aod_data.active_area[i] = (data[i * 2 + 1] & 0xFF) << 8 | (data[i * 2] & 0xFF); + + input_info(true, ts->dev, "%s: aod_active_area - top:%d, edge:%d, bottom:%d\n", + __func__, ts->plat_data->aod_data.active_area[0], + ts->plat_data->aod_data.active_area[1], ts->plat_data->aod_data.active_area[2]); + + memset(data, 0x00, 6); + + offset = SEC_TS_CMD_SPONGE_FOD_INFO; + ret = ts->synaptics_ts_read_sponge(ts, + data, sizeof(data), offset, sizeof(data)); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to read fod info\n", __func__); + return; + } + + sec_input_set_fod_info(ts->dev, data[0], data[1], data[2], data[3]); + + offset = SEC_TS_CMD_SPONGE_DUMP_FLUSH; + ret = ts->synaptics_ts_read_sponge(ts, data, 1, offset, 1); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to sponge_mode\n", __func__); + return; + } + ts->plat_data->sponge_mode = data[0]; + + input_info(true, ts->dev, "%s: sponge_mode %x\n", __func__, ts->plat_data->sponge_mode); +} + +void synaptics_ts_set_fod_finger_merge(struct synaptics_ts_data *ts) +{ + +} + +int synaptics_ts_read_from_sponge(struct synaptics_ts_data *ts, unsigned char *rd_buf, int size_buf, + unsigned short offset, unsigned int rd_len) +{ + int retval = 0; + unsigned char payload[4] = {0}; + unsigned char resp_code; + struct synaptics_ts_buffer resp; + + if (ts->dev_mode != SYNAPTICS_TS_MODE_APPLICATION_FIRMWARE) { + input_err(true, ts->dev, "Application firmware not running, current mode: %02x\n", + ts->dev_mode); + return SEC_ERROR; + } + + if ((!rd_buf) || (size_buf == 0) || (size_buf < rd_len) || (rd_len == 0)) { + input_err(true, ts->dev, "Invalid buffer size\n"); + return SEC_ERROR; + } + + mutex_lock(&ts->sponge_mutex); + + synaptics_ts_buf_init(&resp); + + payload[0] = (unsigned char)(offset & 0xFF); + payload[1] = (unsigned char)((offset >> 8) & 0xFF); + payload[2] = (unsigned char)(rd_len & 0xFF); + payload[3] = (unsigned char)((rd_len >> 8) & 0xFF); + + retval = synaptics_ts_send_command(ts, + CMD_READ_SEC_SPONGE_REG, + payload, + sizeof(payload), + &resp_code, + &resp, + FORCE_ATTN_DRIVEN); + if (retval < 0) { + input_err(true, ts->dev, "Fail to read from sponge, status:%x (offset:%x len:%d)\n", + resp_code, offset, rd_len); + goto exit; + } + + if (resp.data_length != rd_len) { + input_err(true, ts->dev, "Invalid data length\n"); + retval = SEC_ERROR; + goto exit; + } + + retval = synaptics_ts_pal_mem_cpy(rd_buf, + size_buf, + resp.buf, + resp.buf_size, + rd_len); + if (retval < 0) { + input_err(true, ts->dev, "Fail to copy sponge data to caller\n"); + goto exit; + } + +exit: + synaptics_ts_buf_release(&resp); + mutex_unlock(&ts->sponge_mutex); + + return retval; + +} + +int synaptics_ts_write_to_sponge(struct synaptics_ts_data *ts, unsigned char *wr_buf, int size_buf, + unsigned short offset, unsigned int wr_len) +{ + int retval = 0; + unsigned char *payload = NULL; + unsigned int payload_size; + unsigned char resp_code; + struct synaptics_ts_buffer resp; + + if (ts->dev_mode != SYNAPTICS_TS_MODE_APPLICATION_FIRMWARE) { + input_err(true, ts->dev, "Application firmware not running, current mode: %02x\n", + ts->dev_mode); + return SEC_ERROR; + } + + if ((!wr_buf) || (size_buf == 0) || (size_buf < wr_len)) { + input_err(true, ts->dev, "Invalid buffer size\n"); + return SEC_ERROR; + } + + if (wr_len == 0) + return 0; + + mutex_lock(&ts->sponge_mutex); + + synaptics_ts_buf_init(&resp); + + payload_size = wr_len + 2; + payload = synaptics_ts_pal_mem_alloc(payload_size, sizeof(unsigned char)); + if (!payload) { + input_err(true, ts->dev, "Fail to allocate payload buffer\n"); + goto exit; + } + + payload[0] = (unsigned char)(offset & 0xFF); + payload[1] = (unsigned char)((offset >> 8) & 0xFF); + + retval = synaptics_ts_pal_mem_cpy(&payload[2], + payload_size - 2, + wr_buf, + size_buf, + wr_len); + if (retval < 0) { + input_err(true, ts->dev, "Fail to copy sponge data to wr buffer\n"); + goto exit; + } + + retval = synaptics_ts_send_command(ts, + CMD_WRITE_SEC_SPONGE_REG, + payload, + payload_size, + &resp_code, + &resp, + FORCE_ATTN_DRIVEN); + if (retval < 0) { + input_err(true, ts->dev, "Fail to write to sponge, status:%x (offset:%x len:%d)\n", + resp_code, offset, wr_len); + goto exit; + } + +exit: + if (payload) + synaptics_ts_pal_mem_free(payload); + + synaptics_ts_buf_release(&resp); + mutex_unlock(&ts->sponge_mutex); + return retval; +} + +int synaptics_ts_set_touch_function(struct synaptics_ts_data *ts) +{ + return 0; +} + +void synaptics_ts_get_touch_function(struct work_struct *work) +{ + +} + +void synaptics_ts_external_func_work(struct work_struct *work) +{ + struct synaptics_ts_data *ts = container_of(work, struct synaptics_ts_data, + set_temperature_work.work); + + cancel_delayed_work(&ts->set_temperature_work); + synaptics_ts_external_func(ts); +} + +void synaptics_ts_set_utc_sponge(struct synaptics_ts_data *ts) +{ + int ret; + u8 data[4] = {0}; + struct timespec64 current_time; + + ktime_get_real_ts64(¤t_time); + data[0] = (0xFF & (u8)((current_time.tv_sec) >> 0)); + data[1] = (0xFF & (u8)((current_time.tv_sec) >> 8)); + data[2] = (0xFF & (u8)((current_time.tv_sec) >> 16)); + data[3] = (0xFF & (u8)((current_time.tv_sec) >> 24)); + input_info(true, ts->dev, "Write UTC to Sponge = %X\n", (int)(current_time.tv_sec)); + + ret = ts->synaptics_ts_write_sponge(ts, data, sizeof(data), SEC_TS_CMD_SPONGE_OFFSET_UTC, sizeof(data)); + if (ret < 0) + input_err(true, ts->dev, "%s: Failed to write sponge\n", __func__); + +} + +/** + * syna_tcm_get_dynamic_config() + * + * Implement the application fw command code to get the value from the a single + * field of the dynamic configuration. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] id: target field id + * [out] value: the value returned + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int synaptics_ts_get_dynamic_config(struct synaptics_ts_data *ts, + unsigned char id, unsigned short *value) +{ + int retval = 0; + unsigned char out; + unsigned char resp_code; + + if (IS_NOT_APP_FW_MODE(ts->dev_mode)) { + input_err(true, ts->dev, "%s: Device is not in application fw mode, mode: %x\n", __func__, + ts->dev_mode); + return -EINVAL; + } + + out = (unsigned char)id; + + retval = ts->write_message(ts, + SYNAPTICS_TS_CMD_GET_DYNAMIC_CONFIG, + &out, + sizeof(out), + &resp_code, + FORCE_ATTN_DRIVEN); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to send command 0x%02x to get dynamic field 0x%x\n", __func__, + SYNAPTICS_TS_CMD_GET_DYNAMIC_CONFIG, (unsigned char)id); + goto exit; + } + + /* return dynamic config data */ + if (ts->resp_buf.data_length < 2) { + input_err(true, ts->dev, "%s: Invalid resp data size, %d\n", __func__, + ts->resp_buf.data_length); + goto exit; + } + + *value = (unsigned short)synaptics_ts_pal_le2_to_uint(ts->resp_buf.buf); + + input_err(true, ts->dev, "%s: Get %d from dynamic field 0x%x\n", __func__, *value, id); + + retval = 0; + +exit: + return retval; +} + +/** + * syna_tcm_set_dynamic_config() + * + * Implement the application fw command code to set the specified value to + * the selected field of the dynamic configuration. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] id: target field id + * [ in] value: the value to the selected field + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int synaptics_ts_set_dynamic_config(struct synaptics_ts_data *ts, + unsigned char id, unsigned short value) +{ + int retval = 0; + unsigned char out[3]; + unsigned char resp_code; + + if (IS_NOT_APP_FW_MODE(ts->dev_mode)) { + input_err(true, ts->dev, "%s: Device is not in application fw mode, mode: %x\n", __func__, + ts->dev_mode); + return -EINVAL; + } + + input_err(true, ts->dev, "%s: Set %d to dynamic field 0x%x\n", __func__, value, id); + + out[0] = (unsigned char)id; + out[1] = (unsigned char)value; + out[2] = (unsigned char)(value >> 8); + + retval = ts->write_message(ts, + SYNAPTICS_TS_CMD_SET_DYNAMIC_CONFIG, + out, + sizeof(out), + &resp_code, + FORCE_ATTN_DRIVEN); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to send command 0x%02x to set %d to field 0x%x\n", __func__, + SYNAPTICS_TS_CMD_SET_DYNAMIC_CONFIG, value, (unsigned char)id); + goto exit; + } + + retval = 0; + +exit: + return retval; +} + +/** + * syna_tcm_set_immediate_dynamic_config() + * + * Implement the application firmware command code to set the specified + * value to the selected field of the immediate dynamic configuration. + * The firmware will not send the response for immediate commands + * including error response for setting the config. The firmware will + * ignore the operation of dynamic config setting when failed to set the + * config. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] id: target field id + * [ in] value: the value to the selected field + * + * @return + * on success for sending the config, 0 or positive value; otherwise, + * negative value on error. + */ +int synaptics_ts_set_immediate_dynamic_config(struct synaptics_ts_data *ts, + unsigned char id, unsigned short value) +{ + int retval = 0; + unsigned char out[3]; + + if (IS_NOT_APP_FW_MODE(ts->dev_mode)) { + input_err(true, ts->dev, "%s: Device is not in application fw mode, mode: %x\n", __func__, + ts->dev_mode); + return -EINVAL; + } + + input_err(true, ts->dev, "%s: Set %d to dynamic field 0x%x\n", __func__, value, id); + + out[0] = (unsigned char)id; + out[1] = (unsigned char)value; + out[2] = (unsigned char)(value >> 8); + + retval = ts->write_immediate_message(ts, + CMD_SET_IMMEDIATE_DYNAMIC_CONFIG, + out, + sizeof(out)); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to send immediate command 0x%02x to set %d to field 0x%x\n", __func__, + CMD_SET_IMMEDIATE_DYNAMIC_CONFIG, value, (unsigned char)id); + goto exit; + } + +exit: + return retval; +} + +int synaptics_ts_clear_buffer(struct synaptics_ts_data *ts) +{ + int retval = 0; + unsigned char resp_code; + struct synaptics_ts_buffer resp; + + input_err(true, ts->dev, "%s\n", __func__); + + if (IS_NOT_APP_FW_MODE(ts->dev_mode)) { + input_err(true, ts->dev, "%s: Device is not in application fw mode, mode: %x\n", __func__, + ts->dev_mode); + return -EINVAL; + } + + synaptics_ts_buf_init(&resp); + + retval = synaptics_ts_send_command(ts, + CMD_EVENT_BUFFER_CLEAR, + NULL, + 0, + &resp_code, + &resp, + FORCE_ATTN_DRIVEN); + if (retval < 0) + input_err(true, ts->dev, "%s: write clear event failed\n", __func__); + + synaptics_ts_buf_release(&resp); + + return retval; +} + +int synaptics_ts_set_lowpowermode(void *data, u8 mode) +{ + struct synaptics_ts_data *ts = (struct synaptics_ts_data *)data; + struct irq_desc *desc = irq_to_desc(ts->irq); + int ret = 0; + int retrycnt = 0; + unsigned short para; + + input_err(true, ts->dev, "%s: %s(%X)\n", __func__, + mode == TO_LOWPOWER_MODE ? "ENTER" : "EXIT", ts->plat_data->lowpower_mode); + + if (mode) { + synaptics_ts_set_utc_sponge(ts); + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_DUMP_MODE) + if (ts->sponge_inf_dump) { + if (ts->sponge_dump_delayed_flag) { + synaptics_ts_sponge_dump_flush(ts, ts->sponge_dump_delayed_area); + ts->sponge_dump_delayed_flag = false; + input_info(true, ts->dev, "%s : Sponge dump flush delayed work have proceed\n", __func__); + } + } +#endif + ret = synaptics_ts_set_custom_library(ts); + if (ret < 0) + goto error; + } else { + if (!atomic_read(&ts->plat_data->shutdown_called)) + schedule_work(&ts->work_read_functions.work); + } + +retry_pmode: + if (mode) + synaptics_ts_clear_buffer(ts); + + synaptics_ts_locked_release_all_finger(ts); + + synaptics_ts_set_dynamic_config(ts, DC_ENABLE_WAKEUP_GESTURE_MODE, mode); + synaptics_ts_get_dynamic_config(ts, DC_ENABLE_WAKEUP_GESTURE_MODE, ¶); + + if (mode != (u8)para) { + retrycnt++; + ts->plat_data->hw_param.mode_change_failed_count++; + if (retrycnt < 5) + goto retry_pmode; + } + + if (mode) { + while (desc->wake_depth < 1) + enable_irq_wake(ts->irq); + } else { + while (desc->wake_depth > 0) + disable_irq_wake(ts->irq); + } + + if (mode == TO_LOWPOWER_MODE) + atomic_set(&ts->plat_data->power_state, SEC_INPUT_STATE_LPM); + else + atomic_set(&ts->plat_data->power_state, SEC_INPUT_STATE_POWER_ON); + +error: + input_info(true, ts->dev, "%s: end %d\n", __func__, ret); + + return ret; +} + +void synaptics_ts_release_all_finger(struct synaptics_ts_data *ts) +{ + sec_input_release_all_finger(ts->dev); + +} + +void synaptics_ts_locked_release_all_finger(struct synaptics_ts_data *ts) +{ + mutex_lock(&ts->eventlock); + + synaptics_ts_release_all_finger(ts); + + mutex_unlock(&ts->eventlock); +} + +int synaptics_ts_setup(struct synaptics_ts_data *ts) +{ + int ret = 0; + + input_info(true, ts->dev, "%s\n", __func__); + + ret = synaptics_ts_detect_protocol(ts); + if (ret < 0) { + input_err(true, ts->dev, "%s: fail to detect protocol\n", __func__); + goto out; + } + + /* check the running mode */ + switch (ts->dev_mode) { + case SYNAPTICS_TS_MODE_APPLICATION_FIRMWARE: + input_info(true, ts->dev, "%s: Device in Application FW, build id: %d, %s\n", __func__, + ts->packrat_number, + ts->id_info.part_number); + break; + case SYNAPTICS_TS_MODE_BOOTLOADER: + case SYNAPTICS_TS_MODE_TDDI_BOOTLOADER: + input_info(true, ts->dev, "%s: Device in Bootloader\n", __func__); + goto out; + case SYNAPTICS_TS_MODE_ROMBOOTLOADER: + input_info(true, ts->dev, "%s: Device in ROMBoot uBL\n", __func__); + goto out; + case SYNAPTICS_TS_MODE_MULTICHIP_TDDI_BOOTLOADER: + input_info(true, ts->dev, "%s: Device in multi-chip TDDI Bootloader\n", __func__); + goto out; + default: + input_err(true, ts->dev, "%s: Found TouchCom device, but unsupported mode: 0x%02x\n", __func__, + ts->dev_mode); + goto out; + } + + ret = synaptics_ts_set_up_app_fw(ts); + if (ret < 0) + input_err(true, ts->dev, "%s: Fail to set up application firmware\n", __func__); +out: + return ret; +} + +void synaptics_ts_hw_reset(struct synaptics_ts_data *ts) +{ + input_info(true, ts->dev, "%s\n", __func__); + + atomic_set(&ts->plat_data->power_state, SEC_INPUT_STATE_POWER_OFF); + + if (ts->plat_data->power) + ts->plat_data->power(ts->dev, false); + + ts->plat_data->pinctrl_configure(ts->dev, false); + + if (gpio_is_valid(ts->plat_data->gpio_spi_cs)) + gpio_direction_output(ts->plat_data->gpio_spi_cs, 0); + + sec_delay(10); + + ts->plat_data->pinctrl_configure(ts->dev, true); + + if (ts->plat_data->power) + ts->plat_data->power(ts->dev, true); + + if (gpio_is_valid(ts->plat_data->gpio_spi_cs)) + gpio_direction_output(ts->plat_data->gpio_spi_cs, 1); + + sec_delay(ts->power_on_delay); + + atomic_set(&ts->plat_data->power_state, SEC_INPUT_STATE_POWER_ON); +} + + +int synaptics_ts_soft_reset(struct synaptics_ts_data *ts) +{ + int retval = 0; + unsigned char resp_code; + unsigned int delay; + + delay = RESET_DELAY_MS; + + input_info(true, ts->dev, "%s: Recover IC, discharge time:%d\n", __func__, delay); + + retval = ts->write_message(ts, + SYNAPTICS_TS_CMD_RESET, + NULL, + 0, + &resp_code, + delay); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to send command 0x%02x\n", __func__, SYNAPTICS_TS_CMD_RESET); + goto exit; + } + + /* current device mode is expected to be updated + * because identification report will be received after reset + */ + ts->dev_mode = ts->id_info.mode; + if (IS_NOT_APP_FW_MODE(ts->dev_mode)) { + input_err(true, ts->dev, "%s: Device mode 0x%02X running after reset\n", __func__, + ts->dev_mode); + } + + retval = 0; +exit: + return retval; + +} + +void synaptics_ts_reset_work(struct work_struct *work) +{ + struct synaptics_ts_data *ts = container_of(work, struct synaptics_ts_data, + reset_work.work); + int ret; + char result[32]; + char test[32]; + + if (sec_check_secure_trusted_mode_status(ts->plat_data)) + return; + + if (atomic_read(&ts->reset_is_on_going)) { + input_err(true, ts->dev, "%s: reset is ongoing\n", __func__); + return; + } + + mutex_lock(&ts->plat_data->enable_mutex); + __pm_stay_awake(ts->plat_data->sec_ws); + + atomic_set(&ts->reset_is_on_going, 1); + ts->plat_data->hw_param.ic_reset_count++; + input_info(true, ts->dev, "%s\n", __func__); + + ts->plat_data->stop_device(ts); + + sec_delay(30); + + ret = ts->plat_data->start_device(ts); + if (ret < 0) { + /* for ACT i2c recovery fail test */ + snprintf(test, sizeof(test), "TEST=RECOVERY"); + snprintf(result, sizeof(result), "RESULT=FAIL"); + sec_cmd_send_event_to_user(&ts->sec, test, result); + + input_err(true, ts->dev, "%s: failed to reset, ret:%d\n", __func__, ret); + atomic_set(&ts->reset_is_on_going, 0); + cancel_delayed_work(&ts->reset_work); + if (!atomic_read(&ts->plat_data->shutdown_called)) + schedule_delayed_work(&ts->reset_work, msecs_to_jiffies(TOUCH_RESET_DWORK_TIME)); + mutex_unlock(&ts->plat_data->enable_mutex); + + snprintf(result, sizeof(result), "RESULT=RESET"); + sec_cmd_send_event_to_user(&ts->sec, NULL, result); + + __pm_relax(ts->plat_data->sec_ws); + + return; + } + + if (!atomic_read(&ts->plat_data->enabled)) { + input_err(true, ts->dev, "%s: call input_close\n", __func__); + + if (sec_input_need_ic_off(ts->plat_data)) { + ts->plat_data->stop_device(ts); + } else { + ret = ts->plat_data->lpmode(ts, TO_LOWPOWER_MODE); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to reset, ret:%d\n", __func__, ret); + atomic_set(&ts->reset_is_on_going, 0); + cancel_delayed_work(&ts->reset_work); + if (!atomic_read(&ts->plat_data->shutdown_called)) + schedule_delayed_work(&ts->reset_work, msecs_to_jiffies(TOUCH_RESET_DWORK_TIME)); + mutex_unlock(&ts->plat_data->enable_mutex); + __pm_relax(ts->plat_data->sec_ws); + return; + } + if (ts->plat_data->lowpower_mode & SEC_TS_MODE_SPONGE_AOD) + synaptics_ts_set_aod_rect(ts); + } + } + + atomic_set(&ts->reset_is_on_going, 0); + mutex_unlock(&ts->plat_data->enable_mutex); + + snprintf(result, sizeof(result), "RESULT=RESET"); + sec_cmd_send_event_to_user(&ts->sec, NULL, result); + + __pm_relax(ts->plat_data->sec_ws); +} + +void synaptics_ts_print_info_work(struct work_struct *work) +{ + struct synaptics_ts_data *ts = container_of(work, struct synaptics_ts_data, + work_print_info.work); + + sec_input_print_info(ts->dev, ts->tdata); + + if (atomic_read(&ts->sec.cmd_is_running)) + input_err(true, ts->dev, "%s: skip set temperature, cmd running\n", __func__); + else + sec_input_set_temperature(ts->dev, SEC_INPUT_SET_TEMPERATURE_NORMAL); + + if (!atomic_read(&ts->plat_data->shutdown_called)) + schedule_delayed_work(&ts->work_print_info, msecs_to_jiffies(TOUCH_PRINT_INFO_DWORK_TIME)); +} + +void synaptics_ts_read_info_work(struct work_struct *work) +{ + struct synaptics_ts_data *ts = container_of(work, struct synaptics_ts_data, + work_read_info.work); +#ifdef TCLM_CONCEPT + int ret; + + ret = sec_tclm_check_cal_case(ts->tdata); + input_info(true, ts->dev, "%s: sec_tclm_check_cal_case ret: %d\n", __func__, ret); +#endif + + synaptics_ts_rawdata_read_all(ts); + + if (atomic_read(&ts->plat_data->shutdown_called)) { + input_err(true, ts->dev, "%s done, do not run work\n", __func__); + return; + } + ts->info_work_done = true; + schedule_work(&ts->work_print_info.work); + + sec_input_multi_device_ready(ts->multi_dev); +} + +void synaptics_ts_set_cover_type(struct synaptics_ts_data *ts, bool enable) +{ + int ret; + u8 cover_cmd; + + input_info(true, ts->dev, "%s: %s, type:%d\n", + __func__, enable ? "close" : "open", ts->plat_data->cover_type); + + cover_cmd = sec_input_check_cover_type(ts->dev) & 0xFF; + ts->cover_closed = enable; + + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: pwr off, close:%d, touch_fn:%x\n", __func__, + enable, ts->plat_data->touch_functions); + return; + } + + ret = synaptics_ts_set_dynamic_config(ts, DC_ENABLE_COVER, enable); + if (ret < 0) { + input_err(true, ts->dev, + "%s: Failed to send cover enable command: %d, ret:%d\n", + __func__, enable, ret); + return; + } + + if (enable) { + ret = synaptics_ts_set_dynamic_config(ts, DC_ENABLE_COVERTYPE, cover_cmd); + if (ret < 0) { + input_err(true, ts->dev, + "%s: Failed to send covertype command: %d, ret:%d\n", + __func__, cover_cmd, ret); + return; + } + } + + return; + +} + +int synaptics_ts_set_temperature(struct device *dev, u8 temperature_data) +{ + struct synaptics_ts_data *ts = dev_get_drvdata(dev); + int ret; + + if (ts->plat_data->not_support_temp_noti) { + input_info(true, ts->dev, "%s: SKIP! temp:%d\n", + __func__, temperature_data); + return SEC_ERROR; + } + + if (ts->support_immediate_cmd) + ret = synaptics_ts_set_immediate_dynamic_config(ts, DC_TSP_SET_TEMP, temperature_data); + else + ret = synaptics_ts_set_dynamic_config(ts, DC_TSP_SET_TEMP, temperature_data); + if (ret < 0) { + input_err(true, ts->dev, "%s: failed to set temperature_data(%d)\n", + __func__, temperature_data); + } + + return SEC_SUCCESS; +} + +int synaptics_ts_set_aod_rect(struct synaptics_ts_data *ts) +{ + unsigned char data[8] = {0}; + int ret, i; + unsigned short offset; + + for (i = 0; i < 4; i++) { + data[i * 2] = ts->plat_data->aod_data.rect_data[i] & 0xFF; + data[i * 2 + 1] = (ts->plat_data->aod_data.rect_data[i] >> 8) & 0xFF; + } + + offset = 2; + + ret = ts->synaptics_ts_write_sponge(ts, + data, sizeof(data), offset, sizeof(data)); + if (ret < 0) + input_err(true, ts->dev, "%s: Failed to write sponge\n", __func__); + + return ret; +} + +int synaptics_ts_set_press_property(struct synaptics_ts_data *ts) +{ + u8 data[3] = {0}; + unsigned short offset; + int ret; + + if (!ts->plat_data->support_fod) + return 0; + + offset = SEC_TS_CMD_SPONGE_PRESS_PROPERTY; + data[0] = ts->plat_data->fod_data.press_prop; + + ret = ts->synaptics_ts_write_sponge(ts, data, sizeof(data), offset, sizeof(data)); + ret = 0; + if (ret < 0) + input_err(true, ts->dev, "%s: Failed to write sponge\n", __func__); + + input_info(true, ts->dev, "%s: %d\n", __func__, ts->plat_data->fod_data.press_prop); + + return ret; +} + +int synaptics_ts_set_fod_rect(struct synaptics_ts_data *ts) +{ + u8 data[8] = {0}; + int ret, i; + unsigned short offset; + + input_info(true, ts->dev, "%s: l:%d, t:%d, r:%d, b:%d\n", + __func__, ts->plat_data->fod_data.rect_data[0], ts->plat_data->fod_data.rect_data[1], + ts->plat_data->fod_data.rect_data[2], ts->plat_data->fod_data.rect_data[3]); + + offset = 0x4b; + + for (i = 0; i < 4; i++) { + data[i * 2] = ts->plat_data->fod_data.rect_data[i] & 0xFF; + data[i * 2 + 1] = (ts->plat_data->fod_data.rect_data[i] >> 8) & 0xFF; + } + + ret = ts->synaptics_ts_write_sponge(ts, data, sizeof(data), offset, sizeof(data)); + if (ret < 0) + input_err(true, ts->dev, "%s: Failed to write sponge\n", __func__); + + return ret; + +} + +int synaptics_ts_write_grip_data(struct synaptics_ts_data *ts, unsigned char *payload, int payload_size) +{ + int ret; + unsigned char resp_code; + struct synaptics_ts_buffer resp; + + input_dbg(true, ts->dev, "%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X %02X,%02X,%02X,%02X,%02X,%02X,%02X\n", + payload[0], payload[1], payload[2], payload[3], + payload[4], payload[5], payload[6], payload[7], + payload[8], payload[9], payload[10], payload[11], + payload[12], payload[13], payload[14], payload[15]); + + if (IS_NOT_APP_FW_MODE(ts->dev_mode)) { + input_err(true, ts->dev, "%s: Device is not in application fw mode, mode: %x\n", __func__, + ts->dev_mode); + return -EINVAL; + } + + synaptics_ts_buf_init(&resp); + + ret = synaptics_ts_send_command(ts, + CMD_SET_GRIP, + payload, + payload_size, + &resp_code, + &resp, + FORCE_ATTN_DRIVEN); + if (ret < 0) + input_err(true, ts->dev, "%s: Fail to write grip data, %d\n", __func__, ret); + + synaptics_ts_buf_release(&resp); + + return ret; +} + +/* + * flag 1 : set edge handler + * 2 : set (portrait, normal) edge zone data + * 4 : set (portrait, normal) dead zone data + * 8 : set landscape mode data + * 16 : mode clear + * data + * 0xAA, FFF (y start), FFF (y end), FF(direction) + * 0xAB, FFFF (edge zone) + * 0xAC, FF (up x), FF (down x), FFFF (up y), FF (bottom x), FFFF (down y) + * 0xAD, FF (mode), FFF (edge), FFF (dead zone x), FF (dead zone top y), FF (dead zone bottom y) + * case + * edge handler set : 0xAA.... + * booting time : 0xAA... + 0xAB... + * normal mode : 0xAC... (+0xAB...) + * landscape mode : 0xAD... + * landscape -> normal (if same with old data) : 0xAD, 0 + * landscape -> normal (etc) : 0xAC.... + 0xAD, 0 + */ + +void synaptics_set_grip_data_to_ic(struct device *dev, u8 flag) +{ + struct synaptics_ts_data *ts = dev_get_drvdata(dev); + unsigned char payload[16] = {0}; + int payload_size = 0; + + input_info(true, ts->dev, "%s: flag: 0x%02X (clr,lan,nor,edg,han)\n", __func__, flag); + + if (flag & G_SET_EDGE_HANDLER) { + memset(payload, 0x00, 16); + payload[0] = 0x0; + payload[1] = 0x0; + payload[2] = ts->plat_data->grip_data.edgehandler_direction & 0xFF; + payload[3] = (ts->plat_data->grip_data.edgehandler_direction >> 8) & 0xFF; + payload[4] = ts->plat_data->grip_data.edgehandler_start_y & 0xFF; + payload[5] = (ts->plat_data->grip_data.edgehandler_start_y >> 8) & 0xFF; + payload[6] = ts->plat_data->grip_data.edgehandler_end_y & 0xFF; + payload[7] = (ts->plat_data->grip_data.edgehandler_end_y >> 8) & 0xFF; + payload_size = 8; + if (synaptics_ts_write_grip_data(ts, payload, payload_size) < 0) + return; + } + + if (flag & G_SET_NORMAL_MODE) { + memset(payload, 0x00, 16); + payload[0] = 0x01; + payload[1] = 0x0; + payload[2] = ts->plat_data->grip_data.edge_range & 0xFF; + payload[3] = (ts->plat_data->grip_data.edge_range >> 8) & 0xFF; + payload[4] = ts->plat_data->grip_data.deadzone_up_x & 0xFF; + payload[5] = (ts->plat_data->grip_data.deadzone_up_x >> 8) & 0xFF; + payload[6] = ts->plat_data->grip_data.deadzone_dn_x & 0xFF; + payload[7] = (ts->plat_data->grip_data.deadzone_dn_x >> 8) & 0xFF; + payload[8] = ts->plat_data->grip_data.deadzone_y & 0xFF; + payload[9] = (ts->plat_data->grip_data.deadzone_y >> 8) & 0xFF; + if (GET_DEV_COUNT(ts->multi_dev) == MULTI_DEV_SUB) { + /* temporary */ + payload_size = 10; + } else { + payload[10] = ts->plat_data->grip_data.deadzone_dn2_x & 0xFF; + payload[11] = (ts->plat_data->grip_data.deadzone_dn2_x >> 8) & 0xFF; + payload[12] = ts->plat_data->grip_data.deadzone_dn_y & 0xFF; + payload[13] = (ts->plat_data->grip_data.deadzone_dn_y >> 8) & 0xFF; + payload_size = 14; + } + if (synaptics_ts_write_grip_data(ts, payload, payload_size) < 0) + return; + } + + if (flag & G_SET_LANDSCAPE_MODE) { + memset(payload, 0x00, 16); + payload[0] = 0x02; + payload[1] = 0x00; + payload[2] = ts->plat_data->grip_data.landscape_mode & 0xFF; + payload[3] = (ts->plat_data->grip_data.landscape_mode >> 8) & 0xFF; + payload[4] = ts->plat_data->grip_data.landscape_edge & 0xFF; + payload[5] = (ts->plat_data->grip_data.landscape_edge >> 8) & 0xFF; + payload[6] = ts->plat_data->grip_data.landscape_deadzone & 0xFF; + payload[7] = (ts->plat_data->grip_data.landscape_deadzone >> 8) & 0xFF; + payload[8] = ts->plat_data->grip_data.landscape_top_deadzone & 0xFF; + payload[9] = (ts->plat_data->grip_data.landscape_top_deadzone >> 8) & 0xFF; + payload[10] = ts->plat_data->grip_data.landscape_bottom_deadzone & 0xFF; + payload[11] = (ts->plat_data->grip_data.landscape_bottom_deadzone >> 8) & 0xFF; + payload[12] = ts->plat_data->grip_data.landscape_top_gripzone & 0xFF; + payload[13] = (ts->plat_data->grip_data.landscape_top_gripzone >> 8) & 0xFF; + payload[14] = ts->plat_data->grip_data.landscape_bottom_gripzone & 0xFF; + payload[15] = (ts->plat_data->grip_data.landscape_bottom_gripzone >> 8) & 0xFF; + payload_size = 16; + if (synaptics_ts_write_grip_data(ts, payload, payload_size) < 0) + return; + } + + if (flag & G_CLR_LANDSCAPE_MODE) { + memset(payload, 0x00, 16); + payload[0] = 0x02; + payload[1] = 0x00; + payload[2] = ts->plat_data->grip_data.landscape_mode & 0xFF; + payload[3] = (ts->plat_data->grip_data.landscape_mode >> 8) & 0xFF; + payload_size = 4; + if (synaptics_ts_write_grip_data(ts, payload, payload_size) < 0) + return; + } +} + +int synaptics_ts_ear_detect_enable(struct synaptics_ts_data *ts, u8 enable) +{ + int retval = 0; + + if (ts->dev_mode != SYNAPTICS_TS_MODE_APPLICATION_FIRMWARE) { + input_err(true, ts->dev, "Application firmware not running, current mode: %02x\n", + ts->dev_mode); + return SEC_ERROR; + } + + /* set up ear detection mode */ + retval = synaptics_ts_set_dynamic_config(ts, + DC_DISABLE_PROXIMITY, + enable); + if (retval < 0) { + input_err(true, ts->dev, "Fail to set %d with dynamic command 0x%x\n", + DC_DISABLE_PROXIMITY, enable); + return retval; + } + + input_info(true, ts->dev, "%s: %s\n", + __func__, ts->plat_data->ed_enable ? "on" : "off"); + + return 0; +} + +int synaptics_ts_pocket_mode_enable(struct synaptics_ts_data *ts, u8 enable) +{ + int ret; + + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: ic power is off\n", __func__); + return 0; + } + + ret = synaptics_ts_set_dynamic_config(ts, DC_TSP_ENABLE_POCKET_MODE, enable); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to send %d, ret:%d\n", + __func__, enable, ret); + return ret; + } + + input_info(true, ts->dev, "%s: %s\n", __func__, enable ? "on" : "off"); + + return 0; +} + +int synaptics_ts_low_sensitivity_mode_enable(struct synaptics_ts_data *ts, u16 enable) +{ + int ret; + + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: ic power is off\n", __func__); + return 0; + } + + ret = synaptics_ts_set_dynamic_config(ts, DC_TSP_ENABLE_LOW_SENSITIVITY_MODE, enable); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to send %d, ret:%d\n", + __func__, enable, ret); + return ret; + } + + input_info(true, ts->dev, "%s: 0x%02X\n", __func__, enable); + + return 0; +} + +int synaptics_ts_set_charger_mode(struct device *dev, bool on) +{ + struct synaptics_ts_data *ts = dev_get_drvdata(dev); + int ret; + + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, dev, "%s: ic power is off\n", __func__); + return 0; + } + + ret = synaptics_ts_set_dynamic_config(ts, DC_ENABLE_CHARGER_CONNECTED, on); + if (ret < 0) { + input_err(true, dev, "%s: Failed to send charger cmd: %d, ret:%d\n", + __func__, on, ret); + return ret; + } + + input_info(true, dev, "%s: %s\n", __func__, on ? "on" : "off"); + + return 0; +} + +/** + * syna_testing_pt1e() + * + * Sample code to perform BSC Calib (PT1E) testing + * + * @param + * [ in] tcm: the driver handle + * + * @return + * on success, 0; otherwise, negative value on error. + */ +int synaptics_ts_calibration(struct synaptics_ts_data *ts) +{ + int retval; + bool result = false; + struct synaptics_ts_buffer test_data; + unsigned short *data_ptr = NULL; + unsigned char parameter; + unsigned char resp_code; + unsigned char test_code; + int debug_flag; + + synaptics_ts_buf_init(&test_data); + + input_info(true, ts->dev, "%s: Start testing\n", __func__); + + debug_flag = ts->debug_flag; + ts->debug_flag |= (SEC_TS_DEBUG_PRINT_WRITE_CMD | SEC_TS_DEBUG_PRINT_READ_CMD); + + parameter = 3; + retval = synaptics_ts_send_command(ts, + CMD_BSC_DO_CALIBRATION, + ¶meter, + 1, + &resp_code, + NULL, + FORCE_ATTN_DRIVEN); + if ((retval < 0) || resp_code != STATUS_OK) { + input_err(true, ts->dev, "%s: Fail to erase bsc data\n", __func__); + result = false; + goto exit; + } + + parameter = 2; + retval = synaptics_ts_send_command(ts, + CMD_BSC_DO_CALIBRATION, + ¶meter, + 1, + &resp_code, + NULL, + FORCE_ATTN_DRIVEN); + if ((retval < 0) || resp_code != STATUS_OK) { + input_err(true, ts->dev, "%s: Fail to calibrate bsc\n", __func__); + result = false; + goto exit; + } + + test_code = (unsigned char)TEST_PID30_BSC_CALIB_TEST; + + retval = ts->write_message(ts, + SYNAPTICS_TS_CMD_PRODUCTION_TEST, + &test_code, + 1, + &resp_code, + FORCE_ATTN_DRIVEN); + if (retval < 0) { + input_err(true, ts->dev, "Fail to send command 0x%02x\n", + SYNAPTICS_TS_CMD_PRODUCTION_TEST); + result = false; + goto exit; + } + + retval = synaptics_ts_buf_copy(&test_data, &ts->resp_buf); + if (retval < 0) { + input_err(true, ts->dev, "Fail to copy testing data\n"); + result = false; + goto exit; + } + + data_ptr = (unsigned short *)&test_data.buf[0]; + if (*data_ptr == 1) + result = true; + + ts->debug_flag = debug_flag; +exit: + synaptics_ts_soft_reset(ts); + + input_err(true, ts->dev, "%s: Result = %s\n", __func__, (result) ? "pass" : "fail"); + + synaptics_ts_buf_release(&test_data); + + return ((result) ? 0 : -1); +} + +#ifdef TCLM_CONCEPT +int synaptics_ts_tclm_execute_force_calibration(struct device *dev, int cal_mode) +{ + struct synaptics_ts_data *ts = dev_get_drvdata(dev); + + return synaptics_ts_calibration(ts); +} +#endif + +int synaptics_ts_tclm_read(struct device *dev, int address) +{ + struct synaptics_ts_data *ts = dev_get_drvdata(dev); + int ret = 0; + u16 ic_version; + unsigned char payload[2]; + unsigned char resp_code; + struct synaptics_ts_buffer resp; + + synaptics_ts_buf_init(&resp); + + payload[0] = 0x00; + payload[1] = SYNAPTICS_TS_NVM_CALIB_DATA_SIZE; + ret = synaptics_ts_send_command(ts, + CMD_ACCESS_CALIB_DATA_FROM_NVM, + payload, + sizeof(payload), + &resp_code, + &resp, + 0); + if (ret < 0) { + input_err(true, ts->dev, "%s: Fail to read calib data, status:%x\n", __func__, + resp_code); + return ret; + } + + if (address == SEC_TCLM_NVM_OFFSET_IC_FIRMWARE_VER) { + ret = synaptics_ts_get_app_info(ts, &ts->app_info); + if (ret < 0) + input_err(true, ts->dev, "%s: Fail to get application info\n", __func__); + + ic_version = (ts->plat_data->img_version_of_ic[2] << 8) | (ts->plat_data->img_version_of_ic[3] & 0xFF); + input_info(true, ts->dev, "%s: version %04X\n", __func__, ic_version); + synaptics_ts_buf_release(&resp); + return ic_version; + } + memset(&ts->tdata->nvdata, 0x00, sizeof(struct sec_tclm_nvdata)); + if (resp.buf != NULL) { + ts->fac_nv = resp.buf[0]; + ts->disassemble_count = resp.buf[1]; + memcpy(&ts->tdata->nvdata, &resp.buf[2], sizeof(struct sec_tclm_nvdata)); + } else { + input_fail_hist(true, ts->dev, "%s: resp.buf is NULL\n", __func__); + } + + synaptics_ts_buf_release(&resp); + return ret; +} + +int synaptics_ts_tclm_write(struct device *dev, int address) +{ + struct synaptics_ts_data *ts = dev_get_drvdata(dev); + int ret = 1; + unsigned char payload_erase; + unsigned char resp_code; + unsigned char payload_write[SYNAPTICS_TS_NVM_CALIB_DATA_SIZE + 2]; + struct synaptics_ts_buffer resp; + + input_err(true, ts->dev, "%s: start\n", __func__); + + synaptics_ts_buf_init(&resp); + payload_erase = 0x02; + + ret = synaptics_ts_send_command(ts, + CMD_ACCESS_CALIB_DATA_FROM_NVM, + &payload_erase, + sizeof(payload_erase), + &resp_code, + &resp, + 100); + if (ret < 0) { + input_err(true, ts->dev, "%s: Fail to erase calib data, status:%x\n", __func__, + resp_code); + } + + memset(payload_write, 0x00, SYNAPTICS_TS_NVM_CALIB_DATA_SIZE + 2); + + payload_write[0] = 0x01; + payload_write[1] = SYNAPTICS_TS_NVM_CALIB_DATA_SIZE; + payload_write[2] = ts->fac_nv; + payload_write[3] = ts->disassemble_count; + + memcpy(&payload_write[4], &ts->tdata->nvdata, sizeof(struct sec_tclm_nvdata)); + + ret = synaptics_ts_send_command(ts, + CMD_ACCESS_CALIB_DATA_FROM_NVM, + payload_write, + SEC_NVM_CALIB_DATA_SIZE + 2, + &resp_code, + &resp, + 0); + if (ret < 0) { + input_err(true, ts->dev, "%s: Fail to write calib data, status:%x\n", __func__, + resp_code); + synaptics_ts_buf_release(&resp); + return ret; + } + + synaptics_ts_buf_release(&resp); + return ret; +} + +MODULE_LICENSE("GPL"); diff --git a/drivers/input/sec_input/synaptics/synaptics_fw.c b/drivers/input/sec_input/synaptics/synaptics_fw.c new file mode 100644 index 000000000000..5557c8312ce6 --- /dev/null +++ b/drivers/input/sec_input/synaptics/synaptics_fw.c @@ -0,0 +1,1954 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Samsung Electronics Co., Ltd. + * + * 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. + */ + +#include "synaptics_dev.h" +#include "synaptics_reg.h" + +#if IS_ENABLED(CONFIG_SPU_VERIFY) +#define SUPPORT_FW_SIGNED +#endif + +#ifdef SUPPORT_FW_SIGNED +#include +#endif + +enum { + TSP_BUILT_IN = 0, + TSP_SDCARD, + TSP_SIGNED_SDCARD, + TSP_SPU, + TSP_VERIFICATION, +}; + +#define FW_UPDATE_RETRY_COUNT 3 + +#define FLASH_READ_DELAY_MS (10) +#define FLASH_WRITE_DELAY_MS (20) +#define FLASH_ERASE_DELAY_MS (500) + +#define BOOT_CONFIG_SIZE 8 +#define BOOT_CONFIG_SLOTS 16 + +#define DO_NONE (0) +#define DO_UPDATE (1) + +/** + * syna_tcm_identify() + * + * Implement the standard command code, which is used to request + * an IDENTIFY report packet. + * + * @param + * [ in] tcm_dev: the device handle + * [out] id_info: the identification info packet returned + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int synaptics_ts_identify(struct synaptics_ts_data *ts, + struct synaptics_ts_identification_info *id_info) +{ + int retval = 0; + unsigned char resp_code; + + if (!ts) { + pr_err("%s%s: Invalid ts device handle\n", SECLOG, __func__); + return -EINVAL; + } + + retval = ts->write_message(ts, + SYNAPTICS_TS_CMD_IDENTIFY, + NULL, + 0, + &resp_code, + 0); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to send command 0x%02x\n", __func__, SYNAPTICS_TS_CMD_IDENTIFY); + goto exit; + } + + ts->dev_mode = ts->id_info.mode; + + if (id_info == NULL) + goto show_info; + + /* copy identify info to caller */ + retval = synaptics_ts_pal_mem_cpy((unsigned char *)id_info, + sizeof(struct synaptics_ts_identification_info), + ts->resp_buf.buf, + ts->resp_buf.buf_size, + MIN(sizeof(*id_info), ts->resp_buf.data_length)); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to copy identify info to caller\n", __func__); + goto exit; + } + +show_info: + input_err(true, ts->dev, "%s: TCM Fw mode: 0x%02x, TCM ver.: %d\n", __func__, + ts->id_info.mode, ts->id_info.version); + +exit: + return retval; +} + + +/** + * syna_tcm_set_up_flash_access() + * + * Enter the bootloader fw if not in the mode. + * Besides, get the necessary parameters in boot info. + * + * @param + * [ in] tcm_dev: the device handle + * [out] reflash_data: data blob for reflash + * + * @return + * Result of image file comparison + */ +static int synaptics_ts_set_up_flash_access(struct synaptics_ts_data *ts, + struct synaptics_ts_reflash_data_blob *reflash_data) +{ + int retval; + unsigned int temp; + struct synaptics_ts_identification_info id_info; + struct synaptics_ts_boot_info *boot_info; + unsigned int wr_chunk; + + if (!reflash_data) { + input_err(true, ts->dev, "%s: Invalid reflash data blob\n", __func__); + return -EINVAL; + } + + input_info(true, ts->dev, "%s:Set up flash access\n", __func__); + + retval = synaptics_ts_identify(ts, &id_info); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to do identification\n", __func__); + return retval; + } + + /* switch to bootloader mode */ + if (IS_APP_FW_MODE(id_info.mode)) { + input_err(true, ts->dev, "%s: Prepare to enter bootloader mode\n", __func__); + + retval = synaptics_ts_switch_fw_mode(ts, SYNAPTICS_TS_MODE_BOOTLOADER); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to enter bootloader mode\n", __func__); + return retval; + } + } + + if (!IS_BOOTLOADER_MODE(ts->dev_mode)) { + input_err(true, ts->dev, "%s: Fail to enter bootloader mode (current: 0x%x)\n", __func__, + ts->dev_mode); + return retval; + } + + boot_info = &ts->boot_info; + + /* get boot info to set up the flash access */ + retval = synaptics_ts_get_boot_info(ts, boot_info); + if (retval < 0) { + input_err(true, ts->dev, "%s:Fail to get boot info at mode 0x%x\n", __func__, + id_info.mode); + return retval; + } + + wr_chunk = ts->max_wr_size; + + temp = boot_info->write_block_size_words; + reflash_data->write_block_size = temp * 2; + + input_err(true, ts->dev, "%s:Write block size: %d (words size: %d)\n", __func__, + reflash_data->write_block_size, temp); + + temp = synaptics_ts_pal_le2_to_uint(boot_info->erase_page_size_words); + reflash_data->page_size = temp * 2; + + input_err(true, ts->dev, "%s:Erase page size: %d (words size: %d)\n", __func__, + reflash_data->page_size, temp); + + temp = synaptics_ts_pal_le2_to_uint(boot_info->max_write_payload_size); + reflash_data->max_write_payload_size = temp; + + input_err(true, ts->dev, "%s:Max write flash data size: %d\n", __func__, + reflash_data->max_write_payload_size); + + if (reflash_data->write_block_size > (wr_chunk - 9)) { + input_err(true, ts->dev, "%s:Write block size, %d, greater than chunk space, %d\n", __func__, + reflash_data->write_block_size, (wr_chunk - 9)); + return -EINVAL; + } + + if (reflash_data->write_block_size == 0) { + input_err(true, ts->dev, "%s:Invalid write block size %d\n", __func__, + reflash_data->write_block_size); + return -EINVAL; + } + + if (reflash_data->page_size == 0) { + input_err(true, ts->dev, "%s:Invalid erase page size %d\n", __func__, + reflash_data->page_size); + return -EINVAL; + } + + return 0; +} + +/** + * syna_ts_run_bootloader_fw() + * + * Requests that the bootloader firmware be run. + * Once the bootloader firmware has finished starting, an IDENTIFY report + * to indicate that it is in the new mode. + * + * @param + * [ in] ts_dev: the device handle + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int synaptics_ts_run_bootloader_fw(struct synaptics_ts_data *ts) +{ + int retval = 0; + unsigned char resp_code; + + retval = ts->write_message(ts, + SYNAPTICS_TS_CMD_RUN_BOOTLOADER_FIRMWARE, + NULL, + 0, + &resp_code, + FW_MODE_SWITCH_DELAY_MS); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to send command 0x%02x\n", __func__, + SYNAPTICS_TS_CMD_RUN_BOOTLOADER_FIRMWARE); + goto exit; + } + + if (!IS_BOOTLOADER_MODE(ts->dev_mode)) { + input_err(true, ts->dev, "%s: Fail to enter bootloader, mode: %x\n", __func__, + ts->dev_mode); + retval = -ENODEV; + goto exit; + } + + input_err(true, ts->dev, "%s: Bootloader Firmware (mode 0x%x) activated\n", __func__, + ts->dev_mode); + + retval = 0; +exit: + return retval; +} + +/** + * syna_ts_run_application_fw() + * + * Requests that the application firmware be run. + * Once the application firmware has finished starting, an IDENTIFY report + * to indicate that it is in the new mode. + * + * @param + * [ in] ts_dev: the device handle + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int synaptics_ts_run_application_fw(struct synaptics_ts_data *ts) +{ + int retval = 0; + unsigned char resp_code; + + retval = ts->write_message(ts, + SYNAPTICS_TS_CMD_RUN_APPLICATION_FIRMWARE, + NULL, + 0, + &resp_code, + FW_MODE_SWITCH_DELAY_MS); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to send command 0x%02x\n", __func__, + SYNAPTICS_TS_CMD_RUN_APPLICATION_FIRMWARE); + goto exit; + } + + if (IS_NOT_APP_FW_MODE(ts->dev_mode)) { + input_err(true, ts->dev, "%s: Fail to enter application fw, mode: %x\n", __func__, + ts->dev_mode); + retval = -ENODEV; + goto exit; + } + + input_err(true, ts->dev, "%s: Application Firmware (mode 0x%x) activated\n", __func__, + ts->dev_mode); + + retval = 0; + +exit: + return retval; +} + +/** + * syna_ts_switch_fw_mode() + * + * Implement the command code to switch the firmware mode. + * + * @param + * [ in] ts_dev: the device handle + * [ in] mode: target firmware mode + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int synaptics_ts_switch_fw_mode(struct synaptics_ts_data *ts, + unsigned char mode) +{ + int retval = 0; + + switch (mode) { + case SYNAPTICS_TS_MODE_APPLICATION_FIRMWARE: + retval = synaptics_ts_run_application_fw(ts); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to switch to application mode\n", __func__); + goto exit; + } + break; + case SYNAPTICS_TS_MODE_BOOTLOADER: + case SYNAPTICS_TS_MODE_TDDI_BOOTLOADER: + case SYNAPTICS_TS_MODE_TDDI_HDL_BOOTLOADER: + case SYNAPTICS_TS_MODE_MULTICHIP_TDDI_BOOTLOADER: + retval = synaptics_ts_run_bootloader_fw(ts); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to switch to bootloader mode\n", __func__); + goto exit; + } + break; + case SYNAPTICS_TS_MODE_ROMBOOTLOADER: + /*need to make nextversion */ + input_err(true, ts->dev, "%s:ROM_BOOTLOADER\n", __func__); + break; + default: + input_err(true, ts->dev, "%s: Invalid firmware mode requested\n", __func__); + retval = -EINVAL; + goto exit; + } + + retval = 0; + +exit: + return retval; +} + + +/** + * syna_ts_compare_image_id_info() + * + * Compare the ID information between device and the image file, + * and then determine the area to be updated. + * The function should be called after parsing the image file. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] reflash_data: data blob for reflash + * + * @return + * Blocks to be updated + */ +int synaptics_ts_compare_image_id_info(struct synaptics_ts_data *ts, + struct synaptics_ts_reflash_data_blob *reflash_data) +{ + enum update_area result; + unsigned int idx; + unsigned int image_fw_id; + unsigned int device_fw_id; + unsigned char *image_config_id; + unsigned char *device_config_id; + struct app_config_header *header; + const unsigned char *app_config_data; + struct block_data *app_config; + + result = UPDATE_NONE; + + if (!ts) { + pr_err("%s%s:Invalid ts device handle\n", SECLOG, __func__); + return -EINVAL; + } + + if (!reflash_data) { + input_err(true, ts->dev, "Invalid reflash_data\n"); + return -EINVAL; + } + + app_config = &reflash_data->image_info.data[AREA_APP_CONFIG]; + + if (app_config->size < sizeof(struct app_config_header)) { + input_err(true, ts->dev, "%s:Invalid application config in image file\n", __func__); + return -EINVAL; + } + + app_config_data = app_config->data; + header = (struct app_config_header *)app_config_data; + + image_fw_id = synaptics_ts_pal_le4_to_uint(header->build_id); + device_fw_id = ts->packrat_number; + + input_info(true, ts->dev, "%s:Device firmware ID: %d, image build id: %d\n", __func__, + device_fw_id, image_fw_id); + + image_config_id = header->customer_config_id; + device_config_id = ts->app_info.customer_config_id; + + for (idx = 0; idx < 4; idx++) { + ts->plat_data->img_version_of_bin[idx] = image_config_id[idx]; + ts->plat_data->img_version_of_ic[idx] = device_config_id[idx]; + } + + ts->plat_data->ic_vendor_name[0] = 'S'; + ts->plat_data->ic_vendor_name[1] = 'Y'; + + input_info(true, ts->dev, "%s: bin: %02x%02x%02x%02x ic:%02x%02x%02x%02x\n", __func__, + image_config_id[0], image_config_id[1], image_config_id[2], image_config_id[3], + device_config_id[0], device_config_id[1], device_config_id[2], device_config_id[3]); + + if (ts->plat_data->bringup == 2) { + input_info(true, ts->dev, "%s: skip fw_update for bringup\n", __func__); + result = UPDATE_NONE; + goto exit; + } + + /* check f/w version + * ver[0] : IC version -> skip update + * ver[1] : Project version -> need update + * ver[2] : Panel infomation -> skip update + * ver[3] : Firmware version -> version compare + */ + for (idx = 0; idx < 4; idx++) { + if (ts->plat_data->img_version_of_ic[idx] != ts->plat_data->img_version_of_bin[idx]) { + if (ts->plat_data->bringup == 3) { + input_err(true, ts->dev, "%s: bringup. force update\n", __func__); + result = UPDATE_FIRMWARE_AND_CONFIG; + goto exit; + } else if (idx == 0 || idx == 2) { + result = UPDATE_NONE; + goto exit; + } else if (idx == 1) { + input_err(true, ts->dev, "%s: not matched version info\n", __func__); + result = UPDATE_FIRMWARE_AND_CONFIG; + goto exit; + } else if (ts->plat_data->img_version_of_ic[3] < ts->plat_data->img_version_of_bin[3]) { + result = UPDATE_FIRMWARE_AND_CONFIG; + goto exit; + } + } + } + + result = UPDATE_NONE; + +exit: + switch (result) { + case UPDATE_FIRMWARE_AND_CONFIG: + input_err(true, ts->dev, "%s:Update firmware and config\n", __func__); + break; + case UPDATE_CONFIG: + input_err(true, ts->dev, "%s:Update config only\n", __func__); + break; + case UPDATE_NONE: + default: + input_err(true, ts->dev, "%s:No need to do reflash\n", __func__); + break; + } + + return (int)result; +} + + +/** + * syna_tcm_get_flash_area_string() + * + * Return the string ID of target area in the flash memory + * + * @param + * [ in] area: target flash area + * + * @return + * the string ID + */ +static inline char *synaptics_ts_get_flash_area_string(enum flash_area area) +{ + if (area < AREA_MAX) + return (char *)flash_area_str[area]; + else + return ""; +} + +/** + * syna_tcm_save_flash_block_data() + * + * Save the block data of flash memory to the corresponding structure. + * + * @param + * [out] image_info: image info used for storing the block data + * [ in] area: target area + * [ in] content: content of data + * [ in] flash_addr: offset of block data + * [ in] size: size of block data + * [ in] checksum: checksum of block data + * + * @return + * on success, return 0; otherwise, negative value on error. + */ +static int synaptics_ts_save_flash_block_data(struct synaptics_ts_data *ts, struct image_info *image_info, + enum flash_area area, const unsigned char *content, + unsigned int offset, unsigned int size, unsigned int checksum) +{ + if (area >= AREA_MAX) { + input_err(true, ts->dev, "%s: Invalid flash area\n", __func__); + return -EINVAL; + } + + if (checksum != CRC32((const char *)content, size)) { + input_err(true, ts->dev, "%s:%s checksum error, in image: 0x%x (0x%x)\n", __func__, + AREA_ID_STR(area), checksum, + CRC32((const char *)content, size)); + return -EINVAL; + } + image_info->data[area].size = size; + image_info->data[area].data = content; + image_info->data[area].flash_addr = offset; + image_info->data[area].id = (unsigned char)area; + image_info->data[area].available = true; + + input_err(true, ts->dev, "%s:%s area - address:0x%08x (%d), size:%d\n", __func__, + AREA_ID_STR(area), offset, offset, size); + + return 0; +} + +/** + * syna_tcm_get_flash_area_id() + * + * Return the corresponding ID of flash area based on the given string + * + * @param + * [ in] str: string to look for + * + * + * @return + * if matching, return the corresponding ID; otherwise, return AREA_MAX. + */ +static enum flash_area synaptics_ts_get_flash_area_id(char *str) +{ + int area; + char *target; + unsigned int len; + + for (area = AREA_MAX - 1; area >= 0; area--) { + target = AREA_ID_STR(area); + len = strlen(target); + + if (strncmp(str, target, len) == 0) + return area; + } + return AREA_MAX; +} + + +/** + * syna_tcm_parse_fw_image() + * + * Parse and analyze the information of each areas from the given + * firmware image. + * + * @param + * [ in] image: image file given + * [ in] image_info: data blob stored the parsed data from an image file + * + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static inline int synaptics_ts_parse_fw_image(struct synaptics_ts_data *ts, const unsigned char *image, + struct image_info *image_info) +{ + int retval = 0; + unsigned int idx; + unsigned int addr; + unsigned int offset; + unsigned int length; + unsigned int checksum; + unsigned int flash_addr; + unsigned int magic_value; + unsigned int num_of_areas; + struct image_header *header; + struct area_descriptor *descriptor; + const unsigned char *content; + enum flash_area target_area; + + if (!image) { + input_err(true, ts->dev, + "%s: No image data\n", __func__); + return -EINVAL; + } + + if (!image_info) { + input_err(true, ts->dev, "%s: Invalid image_info blob\n", __func__); + return -EINVAL; + } + + synaptics_ts_pal_mem_set(image_info, 0x00, sizeof(struct image_info)); + + header = (struct image_header *)image; + + magic_value = synaptics_ts_pal_le4_to_uint(header->magic_value); + if (magic_value != IMAGE_FILE_MAGIC_VALUE) { + input_err(true, ts->dev, "%s: Invalid image file magic value\n", __func__); + return -EINVAL; + } + + offset = sizeof(struct image_header); + num_of_areas = synaptics_ts_pal_le4_to_uint(header->num_of_areas); + + for (idx = 0; idx < num_of_areas; idx++) { + addr = synaptics_ts_pal_le4_to_uint(image + offset); + descriptor = (struct area_descriptor *)(image + addr); + offset += 4; + + magic_value = synaptics_ts_pal_le4_to_uint(descriptor->magic_value); + if (magic_value != FLASH_AREA_MAGIC_VALUE) + continue; + + length = synaptics_ts_pal_le4_to_uint(descriptor->length); + content = (unsigned char *)descriptor + sizeof(*descriptor); + flash_addr = synaptics_ts_pal_le4_to_uint(descriptor->flash_addr_words); + flash_addr = flash_addr * 2; + checksum = synaptics_ts_pal_le4_to_uint(descriptor->checksum); + + target_area = synaptics_ts_get_flash_area_id( + (char *)descriptor->id_string); + + retval = synaptics_ts_save_flash_block_data(ts, image_info, + target_area, + content, + flash_addr, + length, + checksum); + if (retval < 0) + return -EINVAL; + } + + return 0; +} + +/** + * syna_tcm_check_flash_boot_config() + * + * Check whether the same flash address of boot config in between the device + * and the image file. + * + * @param + * [ in] boot_config: block data of boot_config from image file + * [ in] boot_info: data of boot info + * [ in] block_size: max size of write block + * + * @return + * 0, no need to do reflash; 1, able to do reflash; error otherwise. + */ + +static int synaptics_ts_check_flash_boot_config(struct synaptics_ts_data *ts, struct block_data *boot_config, + struct synaptics_ts_boot_info *boot_info, unsigned int block_size) +{ + unsigned int start_block; + unsigned int image_addr; + unsigned int device_addr; + + if (boot_config->size < BOOT_CONFIG_SIZE) { + input_err(true, ts->dev, + "%s:No valid BOOT_CONFIG size, %d, in image file\n", __func__, boot_config->size); + return -EINVAL; + } + + image_addr = boot_config->flash_addr; + + input_err(true, ts->dev, "%s:Boot Config address in image file: 0x%x\n", __func__, image_addr); + + start_block = VALUE(boot_info->boot_config_start_block); + device_addr = start_block * block_size; + + input_err(true, ts->dev, "%s:Boot Config address in device: 0x%x\n", __func__, device_addr); + + return DO_NONE; +} + +/** + * syna_tcm_check_flash_app_config() + * + * Check whether the same flash address of app config in between the + * device and the image file. + * + * @param + * [ in] app_config: block data of app_config from image file + * [ in] app_info: data of application info + * [ in] block_size: max size of write block + * + * @return + * 0, no need to do reflash; 1, able to do reflash; error otherwise. + */ +static int synaptics_ts_check_flash_app_config(struct synaptics_ts_data *ts, struct block_data *app_config, + struct synaptics_ts_application_info *app_info, unsigned int block_size) +{ + unsigned int temp; + unsigned int image_addr; + unsigned int image_size; + unsigned int device_addr; + unsigned int device_size; + + if (app_config->size == 0) { + input_err(true, ts->dev, "%s: No APP_CONFIG in image file\n", __func__); + return DO_NONE; + } + + image_addr = app_config->flash_addr; + image_size = app_config->size; + + input_err(true, ts->dev, "%s: App Config address in image file: 0x%x, size: %d\n", __func__, image_addr, image_size); + + temp = VALUE(app_info->app_config_start_write_block); + device_addr = temp * block_size; + device_size = VALUE(app_info->app_config_size); + + input_err(true, ts->dev, "%s: App Config address in device: 0x%x, size: %d\n", __func__, + device_addr, device_size); + + if (device_addr == 0 && device_size == 0) + return DO_UPDATE; + + if (image_addr != device_addr) + input_err(true, ts->dev, "%s: App Config address mismatch, image:0x%x, dev:0x%x\n", __func__, + image_addr, device_addr); + + if (image_size != device_size) + input_err(true, ts->dev, "%s: App Config address size mismatch, image:%d, dev:%d\n", __func__, + image_size, device_size); + + return DO_UPDATE; +} + +/** + * syna_tcm_check_flash_disp_config() + * + * Check whether the same flash address of display config in between the + * device and the image file. + * + * @param + * [ in] disp_config: block data of disp_config from image file + * [ in] boot_info: data of boot info + * [ in] block_size: max size of write block + * + * @return + * 0, no need to do reflash; 1, able to do reflash; error otherwise. + */ +static int synaptics_ts_check_flash_disp_config(struct synaptics_ts_data *ts, struct block_data *disp_config, + struct synaptics_ts_boot_info *boot_info, unsigned int block_size) +{ + unsigned int temp; + unsigned int image_addr; + unsigned int image_size; + unsigned int device_addr; + unsigned int device_size; + + /* disp_config area may not be included in all product */ + if (disp_config->size == 0) { + input_err(true, ts->dev, "%s: No DISP_CONFIG in image file\n", __func__); + return DO_NONE; + } + + image_addr = disp_config->flash_addr; + image_size = disp_config->size; + + input_err(true, ts->dev, "%s: Disp Config address in image file: 0x%x, size: %d\n", __func__, + image_addr, image_size); + + temp = VALUE(boot_info->display_config_start_block); + device_addr = temp * block_size; + + temp = VALUE(boot_info->display_config_length_blocks); + device_size = temp * block_size; + + input_err(true, ts->dev, "%s: Disp Config address in device: 0x%x, size: %d\n", __func__, + device_addr, device_size); + + if (image_addr != device_addr) + input_err(true, ts->dev, "%s: Disp Config address mismatch, image:0x%x, dev:0x%x\n", __func__, + image_addr, device_addr); + + if (image_size != device_size) + input_err(true, ts->dev, "%s: Disp Config address size mismatch, image:%d, dev:%d\n", __func__, + image_size, device_size); + + return DO_UPDATE; +} + +/** + * syna_tcm_check_flash_app_code() + * + * Check whether the valid size of app firmware in the image file + * + * @param + * [ in] app_code: block data of app_code from image file + * + * @return + * 0, no need to do reflash; 1, able to do reflash; error otherwise. + */ +static int synaptics_ts_check_flash_app_code(struct synaptics_ts_data *ts, struct block_data *app_code) +{ + + if (app_code->size == 0) { + input_err(true, ts->dev, "%s: No %s in image file\n", __func__, AREA_ID_STR(app_code->id)); + return -EINVAL; + } + + return DO_UPDATE; +} + +/** + * syna_tcm_check_flash_openshort() + * + * Check whether the valid size of openshort area in the image file + * + * @param + * [ in] open_short: block data of open_short from image file + * + * @return + * 0, no need to do reflash; 1, able to do reflash; error otherwise. + */ +static int synaptics_ts_check_flash_openshort(struct synaptics_ts_data *ts, struct block_data *open_short) +{ + /* open_short area may not be included in all product */ + if (open_short->size == 0) { + input_err(true, ts->dev, "%s: No %s in image file\n", __func__, AREA_ID_STR(open_short->id)); + return DO_NONE; + } + + return DO_UPDATE; +} + +/** + * syna_tcm_check_flash_app_prod_test() + * + * Check whether the valid size of app prod_test area in the image file + * + * @param + * [ in] prod_test: block data of app_prod_test from image file + * + * @return + * 0, no need to do reflash; 1, able to do reflash; error otherwise. + */ +static int synaptics_ts_check_flash_app_prod_test(struct synaptics_ts_data *ts, struct block_data *prod_test) +{ + /* app_prod_test area may not be included in all product */ + if (prod_test->size == 0) { + input_err(true, ts->dev, "%s: No %s in image file\n", __func__, AREA_ID_STR(prod_test->id)); + return DO_NONE; + } + + return DO_UPDATE; +} + +/** + * syna_tcm_check_flash_ppdt() + * + * Check whether the valid size of ppdt area in the image file + * + * @param + * [ in] ppdt: block data of PPDT from image file + * + * @return + * 0, no need to do reflash; 1, able to do reflash; error otherwise. + */ +static int synaptics_ts_check_flash_ppdt(struct synaptics_ts_data *ts, struct block_data *ppdt) +{ + /* open_short area may not be included in all product */ + if (ppdt->size == 0) { + input_err(true, ts->dev, "%s: No %s in image file\n", __func__, AREA_ID_STR(ppdt->id)); + return DO_NONE; + } + + return DO_UPDATE; +} + +/** + * syna_tcm_reflash_send_command() + * + * Helper to wrap up the write_message() function. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] command: given command code + * [ in] payload: payload data, if any + * [ in] payload_len: length of payload data + * [ in] delay_ms_resp: delay time to get the response of command + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int synaptics_ts_reflash_send_command(struct synaptics_ts_data *ts, + unsigned char command, unsigned char *payload, + unsigned int payload_len, unsigned int delay_ms_resp) +{ + int retval = 0; + unsigned char resp_code; + + if (!ts) { + pr_err("%s%s: Invalid ts device handle\n", SECLOG, __func__); + return -EINVAL; + } + + if (!IS_BOOTLOADER_MODE(ts->dev_mode)) { + input_err(true, ts->dev, "%s: Device is not in BL mode, 0x%x\n", __func__, ts->dev_mode); + retval = -EINVAL; + } + + retval = ts->write_message(ts, + command, + payload, + payload_len, + &resp_code, + delay_ms_resp); + if (retval < 0) + input_err(true, ts->dev, "%s: Fail to send command 0x%02x\n", __func__, command); + + return retval; +} + +/** + * syna_tcm_write_flash() + * + * Implement the bootloader command to write specified data to flash memory. + * + * If the length of the data to write is not an integer multiple of words, + * the trailing byte will be discarded. If the length of the data to write + * is not an integer number of write blocks, it will be zero-padded to the + * next write block. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] reflash_data: data blob for reflash + * [ in] address: the address in flash memory to write + * [ in] wr_data: data to write + * [ in] wr_len: length of data to write + * [ in] wr_delay_ms: a short delay after the command executed + * set '0' to use default time, which is 20 ms; + * set 'FORCE_ATTN_DRIVEN' to adopt ATTN-driven. + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int synaptics_ts_write_flash(struct synaptics_ts_data *ts, + struct synaptics_ts_reflash_data_blob *reflash_data, + unsigned int address, const unsigned char *wr_data, + unsigned int wr_len, unsigned int wr_delay_ms) +{ + int retval; + unsigned int offset; + unsigned int w_length; + unsigned int xfer_length; + unsigned int remaining_length; + unsigned int flash_address; + unsigned int block_address; + unsigned int num_blocks; + unsigned int resp_delay; + + if (!ts) { + pr_err("%s%s: Invalid ts device handle\n", SECLOG, __func__); + return -EINVAL; + } + + /* ensure that the length to write is the multiple of max_write_payload_size */ + w_length = reflash_data->max_write_payload_size; + + w_length = w_length - (w_length % reflash_data->write_block_size); + + offset = 0; + + remaining_length = wr_len; + + synaptics_ts_buf_lock(&reflash_data->out); + + while (remaining_length) { + if (remaining_length > w_length) + xfer_length = w_length; + else + xfer_length = remaining_length; + + retval = synaptics_ts_buf_alloc(&reflash_data->out, + xfer_length + 2); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to allocate memory for buf.out\n", __func__); + synaptics_ts_buf_unlock(&reflash_data->out); + return retval; + } + + flash_address = address + offset; + block_address = flash_address / reflash_data->write_block_size; + reflash_data->out.buf[0] = (unsigned char)block_address; + reflash_data->out.buf[1] = (unsigned char)(block_address >> 8); + + num_blocks = synaptics_ts_pal_ceil_div(xfer_length, reflash_data->write_block_size); + + resp_delay = synaptics_ts_fw_resp_delay_ms(ts, xfer_length, wr_delay_ms, num_blocks); + + retval = synaptics_ts_memcpy(&reflash_data->out.buf[2], + reflash_data->out.buf_size - 2, + &wr_data[offset], + wr_len - offset, + xfer_length); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to copy write data ,size: %d\n", __func__, + xfer_length); + synaptics_ts_buf_unlock(&reflash_data->out); + return retval; + } + + retval = synaptics_ts_reflash_send_command(ts, + SYNAPTICS_TS_CMD_WRITE_FLASH, + reflash_data->out.buf, + xfer_length + 2, + resp_delay); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to write data to flash addr 0x%x, size %d\n", __func__, + flash_address, xfer_length + 2); + synaptics_ts_buf_unlock(&reflash_data->out); + return retval; + } + + offset += xfer_length; + remaining_length -= xfer_length; + } + + synaptics_ts_buf_unlock(&reflash_data->out); + return 0; +} + +/** + * syna_tcm_write_flash_block() + * + * Write data to the target block data area in the flash memory. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] reflash_data: data blob for reflash + * [ in] area: target block area to write + * [ in] resp_reading: method to read in the response + * a positive value presents the us time delay for the processing + * of each BLOCKs in the flash to write; + * or, set '0' or 'RESP_IN_ATTN' for ATTN driven + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int synaptics_ts_write_flash_block(struct synaptics_ts_data *ts, + struct synaptics_ts_reflash_data_blob *reflash_data, + struct block_data *block, unsigned int resp_reading) +{ + int retval; + unsigned int size; + unsigned int flash_addr; + const unsigned char *data; + + if (!ts) { + pr_err("%s%s: Invalid ts device handle\n", SECLOG, __func__); + return -EINVAL; + } + + if (!reflash_data) { + input_err(true, ts->dev, "%s: Invalid reflash data blob\n", __func__); + return -EINVAL; + } + + if (!block) { + input_err(true, ts->dev, "%s: Invalid block data\n", __func__); + return -EINVAL; + } + + data = block->data; + size = block->size; + flash_addr = block->flash_addr; + + input_err(true, ts->dev, "%s: Write data to %s - address: 0x%x, size: %d\n", __func__, + AREA_ID_STR(block->id), flash_addr, size); + + if (size == 0) { + input_err(true, ts->dev, "%s: No need to update, size = %d\n", __func__, size); + goto exit; + } + + retval = synaptics_ts_write_flash(ts, reflash_data, + flash_addr, data, size, resp_reading); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to write %s to flash (addr: 0x%x, size: %d)\n", __func__, + AREA_ID_STR(block->id), flash_addr, size); + return retval; + } + +exit: + input_err(true, ts->dev, "%s: %s area written\n", __func__, AREA_ID_STR(block->id)); + + return 0; +} + +/** + * syna_tcm_erase_flash() + * + * Implement the bootloader command, which is used to erase the specified + * blocks of flash memory. + * + * Until this command completes, the device may be unresponsive. + * Therefore, this helper is implemented as a blocked function, and the delay + * time is set to DEFAULT_FLASH_ERASE_DELAY_MS in default. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] reflash_data: data blob for reflash + * [ in] address: the address in flash memory to read + * [ in] size: size of data to write + * [ in] erase_delay_ms: a short delay after the command executed + * set a positive value or 'DEFAULT_FLASH_ERASE_DELAY' to use default; + * set '0' or 'RESP_IN_ATTN' to select ATTN-driven. + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int synaptics_ts_erase_flash(struct synaptics_ts_data *ts, + struct synaptics_ts_reflash_data_blob *reflash_data, + unsigned int address, unsigned int size, + unsigned int erase_delay_ms) +{ + int retval; + unsigned int page_start = 0; + unsigned int page_count = 0; + unsigned char out_buf[4] = {0}; + int size_erase_cmd; + unsigned int resp_delay; + + page_start = address / reflash_data->page_size; + + page_count = synaptics_ts_fw_set_page_count(ts, reflash_data, size); + + resp_delay = synaptics_ts_fw_set_erase_delay(ts, erase_delay_ms, page_count); + + if ((page_start > 0xff) || (page_count > 0xff)) { + size_erase_cmd = 4; + + out_buf[0] = (unsigned char)(page_start & 0xff); + out_buf[1] = (unsigned char)((page_start >> 8) & 0xff); + out_buf[2] = (unsigned char)(page_count & 0xff); + out_buf[3] = (unsigned char)((page_count >> 8) & 0xff); + } else { + size_erase_cmd = 2; + + out_buf[0] = (unsigned char)(page_start & 0xff); + out_buf[1] = (unsigned char)(page_count & 0xff); + } + + retval = synaptics_ts_reflash_send_command(ts, + SYNAPTICS_TS_CMD_ERASE_FLASH, + out_buf, + size_erase_cmd, + resp_delay); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to erase data at flash page 0x%x, count %d\n", __func__, + page_start, page_count); + return retval; + } + + return 0; +} + +/** + * syna_tcm_erase_flash_block() + * + * Mass erase the target block data area in the flash memory. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] reflash_data: data blob for reflash + * [ in] block: target block area to erase + * [ in] resp_reading: method to read in the response + * a positive value presents the us time delay for the processing + * of each PAGEs in the flash to erase; + * or, set '0' or 'RESP_IN_ATTN' for ATTN driven + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int synaptics_ts_erase_flash_block(struct synaptics_ts_data *ts, + struct synaptics_ts_reflash_data_blob *reflash_data, + struct block_data *block, unsigned int resp_reading) +{ + int retval; + unsigned int size; + unsigned int flash_addr; + + if (!ts) { + pr_err("%s%s: Invalid ts device handle\n", SECLOG, __func__); + return -EINVAL; + } + + if (!reflash_data) { + input_err(true, ts->dev, "%s: Invalid reflash data blob\n", __func__); + return -EINVAL; + } + + if (!block) { + input_err(true, ts->dev, "%s: Invalid block data\n", __func__); + return -EINVAL; + } + + flash_addr = block->flash_addr; + + size = block->size; + + input_info(true, ts->dev, "%s: Erase %s block - address: 0x%x, size: %d\n", __func__, + AREA_ID_STR(block->id), flash_addr, size); + + if (size == 0) { + input_info(true, ts->dev, "%s: No need to erase, size = %d\n", __func__, size); + goto exit; + } + + retval = synaptics_ts_erase_flash(ts, reflash_data, + flash_addr, size, resp_reading); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to erase %s data (addr: 0x%x, size: %d)\n", __func__, + AREA_ID_STR(block->id), flash_addr, size); + return retval; + } + +exit: + input_info(true, ts->dev, "%s: %s area erased\n", __func__, AREA_ID_STR(block->id)); + + return 0; +} + +/** + * syna_tcm_check_flash_block() + * + * Dispatch to the proper helper to ensure the data of associated block area + * is correct in between the device and the image file. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] reflash_data: data blob for reflash + * [ in] block: target block area to check + * + * @return + * 0, no need to do reflash; 1, able to do reflash; error otherwise. + */ +static int synaptics_ts_check_flash_block(struct synaptics_ts_data *ts, + struct synaptics_ts_reflash_data_blob *reflash_data, + struct block_data *block) +{ + int retval = 0; + struct synaptics_ts_application_info *app_info; + struct synaptics_ts_boot_info *boot_info; + + if (!ts) { + pr_err("%s%s: Invalid ts device handle\n", SECLOG, __func__); + return DO_NONE; + } + + if (!reflash_data) { + input_err(true, ts->dev, "%s: Invalid reflash data blob\n", __func__); + return DO_NONE; + } + + if (!block) { + input_err(true, ts->dev, "%s: Invalid block data\n", __func__); + return DO_NONE; + } + + app_info = &ts->app_info; + boot_info = &ts->boot_info; + + switch (block->id) { + case AREA_APP_CODE: + retval = synaptics_ts_check_flash_app_code(ts, block); + break; + case AREA_APP_CONFIG: + retval = synaptics_ts_check_flash_app_config(ts, block, app_info, + reflash_data->write_block_size); + break; + case AREA_BOOT_CONFIG: + retval = synaptics_ts_check_flash_boot_config(ts, block, boot_info, + reflash_data->write_block_size); + break; + case AREA_DISP_CONFIG: + retval = synaptics_ts_check_flash_disp_config(ts, block, boot_info, + reflash_data->write_block_size); + break; + case AREA_OPEN_SHORT_TUNING: + retval = synaptics_ts_check_flash_openshort(ts, block); + break; + case AREA_PROD_TEST: + retval = synaptics_ts_check_flash_app_prod_test(ts, block); + break; + case AREA_PPDT: + retval = synaptics_ts_check_flash_ppdt(ts, block); + break; + default: + retval = DO_NONE; + break; + } + + return retval; +} + +/** + * syna_tcm_update_flash_block() + * + * Perform the reflash sequence to the target area + * + * @param + * [ in] tcm_dev: the device handle + * [ in] reflash_data: data blob for reflash + * [ in] block: target block area to update + * [ in] delay_setting: set up the us delay time to wait for the completion of flash access + * for polling, set a value formatted with [erase us | write us]; + * for ATTN-driven, use '0' or 'RESP_IN_ATTN' + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int synaptics_ts_update_flash_block(struct synaptics_ts_data *ts, + struct synaptics_ts_reflash_data_blob *reflash_data, + struct block_data *block, unsigned int delay_setting) +{ + int retval; + unsigned int erase_delay_ms = (delay_setting >> 16) & 0xFFFF; + unsigned int wr_blk_delay_ms = delay_setting & 0xFFFF; + + if (!ts) { + pr_err("%s%s: Invalid ts device handle\n", SECLOG, __func__); + return -EINVAL; + } + + if (!reflash_data) { + input_err(true, ts->dev, "%s: Invalid reflash data blob\n", __func__); + return -EINVAL; + } + + if (!block) { + input_err(true, ts->dev, "%s: Invalid block data\n", __func__); + return -EINVAL; + } + + /* reflash is not needed for the partition */ + retval = synaptics_ts_check_flash_block(ts, + reflash_data, + block); + if (retval < 0) { + input_err(true, ts->dev, "%s: Invalid %s area\n", __func__, AREA_ID_STR(block->id)); + return retval; + } + + if (retval == DO_NONE) + return 0; + + input_err(true, ts->dev, "%s: Prepare to erase %s area\n", __func__, AREA_ID_STR(block->id)); + + retval = synaptics_ts_erase_flash_block(ts, + reflash_data, + block, + erase_delay_ms); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to erase %s area\n", __func__, AREA_ID_STR(block->id)); + return retval; + } + + input_err(true, ts->dev, "%s: Prepare to update %s area\n", __func__, AREA_ID_STR(block->id)); + + retval = synaptics_ts_write_flash_block(ts, + reflash_data, + block, + wr_blk_delay_ms); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to write %s area\n", __func__, AREA_ID_STR(block->id)); + return retval; + } + + return 0; +} + +/** + * syna_tcm_do_reflash_generic() + * + * Implement the generic sequence of fw update in MODE_BOOTLOADER. + * + * Typically, it is applied on most of discrete touch controllers + * + * @param + * [ in] tcm_dev: the device handle + * [ in] reflash_data: misc. data used for fw update + * [ in] type: the area to update + * [ in] delay_setting: set up the us delay time to wait for the completion of flash access + * for polling, set a value formatted with [erase ms | write us]; + * for ATTN-driven, use '0' or 'RESP_IN_ATTN' + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int synaptics_ts_do_reflash_generic(struct synaptics_ts_data *ts, + struct synaptics_ts_reflash_data_blob *reflash_data, + enum update_area type, unsigned int delay_setting) +{ + int retval = 0; + struct block_data *block; + + if (!ts) { + pr_err("%s%s: Invalid ts device handle\n", SECLOG, __func__); + return -EINVAL; + } + + if (!reflash_data) { + input_err(true, ts->dev, "%s: Invalid reflash_data blob\n", __func__); + return -EINVAL; + } + + if (ts->dev_mode != SYNAPTICS_TS_MODE_BOOTLOADER) { + input_err(true, ts->dev, "%s: Incorrect bootloader mode, 0x%02x, expected: 0x%02x\n", __func__, + ts->dev_mode, SYNAPTICS_TS_MODE_BOOTLOADER); + return -EINVAL; + } + + if (type != UPDATE_FIRMWARE_AND_CONFIG) { + input_err(true, ts->dev, "%s: type is not UPDATE_FIRMWARE_AND_CONFIG\n", __func__); + return -EINVAL; + } + + block = &reflash_data->image_info.data[AREA_APP_CODE]; + + retval = synaptics_ts_update_flash_block(ts, + reflash_data, + block, + delay_setting); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to update application firmware\n", __func__); + goto exit; + } + block = &reflash_data->image_info.data[AREA_APP_CONFIG]; + retval = synaptics_ts_update_flash_block(ts, + reflash_data, + block, + delay_setting); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to update application config\n", __func__); + goto exit; + } + +exit: + return retval; +} + +/** + * syna_tcm_do_fw_update() + * + * The entry function to perform fw update upon TouchBoot. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] image: binary data to write + * [ in] image_size: size of data array + * [ in] delay_setting: set up the us delay time to wait for the completion of flash access + * for polling, set a value formatted with [erase ms | write us]; + * for ATTN-driven, use '0' or 'RESP_IN_ATTN' + * [ in] force_reflash: '1' to do reflash anyway + * '0' to compare ID info before doing reflash. + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int synaptics_ts_do_fw_update(struct synaptics_ts_data *ts, + const unsigned char *image, unsigned int image_size, + unsigned int delay_setting, bool force_reflash) +{ + int retval = 0, retry = FW_UPDATE_RETRY_COUNT; + unsigned int app_status; + enum update_area type = UPDATE_NONE; + struct synaptics_ts_reflash_data_blob reflash_data; + + if (!ts) { + pr_err("%s%s: Invalid ts device handle\n", SECLOG, __func__); + return -EINVAL; + } + + if ((!image) || (image_size == 0)) { + input_err(true, ts->dev, "%s: Invalid image data\n", __func__); + return -EINVAL; + } + + input_err(true, ts->dev, "%s: Prepare to do reflash\n", __func__); + + synaptics_ts_buf_init(&reflash_data.out); + + reflash_data.image = image; + reflash_data.image_size = image_size; + synaptics_ts_pal_mem_set(&reflash_data.image_info, 0x00, + sizeof(struct image_info)); + + retval = synaptics_ts_parse_fw_image(ts, image, &reflash_data.image_info); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to parse firmware image\n", __func__); + return retval; + } + + + input_err(true, ts->dev, "%s: Start of reflash\n", __func__); + + ATOMIC_SET(ts->firmware_flashing, 1); + + app_status = synaptics_ts_pal_le2_to_uint(ts->app_info.status); + + type = (enum update_area)synaptics_ts_compare_image_id_info(ts, &reflash_data); + + /* to forcedly update the firmware and config + * - flag of 'force_reflash' has been set + * - device stays in bootloader + * - app firmware doesn't run properly + */ + if (IS_BOOTLOADER_MODE(ts->dev_mode)) + force_reflash = true; + if (IS_APP_FW_MODE(ts->dev_mode) && (app_status != APP_STATUS_OK)) + force_reflash = true; + + if (force_reflash) { + input_err(true, ts->dev, "%s: Bad application firmware, dev_mode: 0x%x, status: 0x%x\n", + __func__, ts->dev_mode, app_status); + type = UPDATE_FIRMWARE_AND_CONFIG; + goto reflash; + } + + ts->firmware_update_done = type; + + if (type == UPDATE_NONE) + goto exit; + +reflash: + do { + if (retval < 0) { + ts->plat_data->hw_param.checksum_result |= SYNAPTICS_TS_CHK_UPDATE_FAIL; + input_fail_hist(true, ts->dev, "%s: fw update is failed. retry:%d\n", + __func__, FW_UPDATE_RETRY_COUNT - retry); + synaptics_ts_hw_reset(ts); + retval = synaptics_ts_setup(ts); + if (retval < 0) { + input_err(true, ts->dev, "%s: fail to do setup, retry=%d\n", + __func__, FW_UPDATE_RETRY_COUNT - retry); + continue; + } + } + synaptics_ts_buf_init(&reflash_data.out); + + /* set up flash access, and enter the bootloader mode */ + retval = synaptics_ts_set_up_flash_access(ts, &reflash_data); + if (retval < 0) { + input_err(true, ts->dev, + "%s: fail to set up flash access, %d\n", __func__, retval); + } else { + /* perform the fw update */ + if (ts->dev_mode == SYNAPTICS_TS_MODE_BOOTLOADER) { + retval = synaptics_ts_do_reflash_generic(ts, + &reflash_data, + type, + delay_setting); + if (retval < 0) { + input_err(true, ts->dev, "%s: fail to do firmware update, retry=%d\n", + __func__, FW_UPDATE_RETRY_COUNT - retry); + } else { + input_info(true, ts->dev, "%s: succeed to do firmware update\n", __func__); + if (synaptics_ts_soft_reset(ts) < 0) + input_err(true, ts->dev, "Fail to do reset\n"); + } + } else { + retval = -EINVAL; + input_err(true, ts->dev, "%s: Incorrect bootloader mode, 0x%02x\n", __func__, + ts->dev_mode); + } + } + } while ((retval < 0) && (--retry > 0)); +exit: + ATOMIC_SET(ts->firmware_flashing, 0); + + synaptics_ts_buf_release(&reflash_data.out); + + return retval; +} + +int synaptics_ts_fw_update_on_probe(struct synaptics_ts_data *ts) +{ + int retval = 0; + const struct firmware *fw_entry = NULL; + char fw_path[SYNAPTICS_TS_MAX_FW_PATH]; +#ifdef TCLM_CONCEPT + int restore_cal = 0; +#endif + int retry = 3; + + input_info(true, ts->dev, "%s:\n", __func__); + + synaptics_ts_fw_delay_setting(ts); + + if (ts->plat_data->bringup == 1) { + input_info(true, ts->dev, "%s: bringup 1\n", __func__); + goto exit_fwload; + } + + if (!ts->plat_data->firmware_name) { + input_err(true, ts->dev, "%s: firmware name does not declair in dts\n", __func__); + retval = -ENOENT; + goto exit_fwload; + } + + snprintf(fw_path, SYNAPTICS_TS_MAX_FW_PATH, "%s", ts->plat_data->firmware_name); + input_info(true, ts->dev, "%s: Load firmware : %s\n", __func__, fw_path); + + disable_irq(ts->irq); + + while (retry--) { + retval = request_firmware(&fw_entry, fw_path, ts->dev); + if (retval) + input_err(true, ts->dev, + "%s: Firmware image %s not available retry %d\n", __func__, + fw_path, retry); + else + break; + sec_delay(1000); + } + + if (retval) { + retval = 0; + enable_irq(ts->irq); + goto exit_fwload; + } + + retval = synaptics_ts_do_fw_update(ts, fw_entry->data, fw_entry->size, + (ts->fw_erase_delay << 16) | ts->fw_write_block_delay, false); + if (retval < 0) { + input_err(true, ts->dev, "%s: failed fw update, ret=%d\n", __func__, retval); + enable_irq(ts->irq); + goto done; + } + + retval = synaptics_ts_set_up_app_fw(ts); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to set up application firmware\n", __func__); + enable_irq(ts->irq); + goto done; + } + + enable_irq(ts->irq); + +#ifdef TCLM_CONCEPT + if (ts->firmware_update_done) { + retval = synaptics_ts_tclm_read(ts->dev, SEC_TCLM_NVM_ALL_DATA); + if (retval < 0) { + input_info(true, ts->dev, "%s: SEC_TCLM_NVM_ALL_DATA read fail", __func__); + goto done; + } + + input_info(true, ts->dev, "%s: tune_fix_ver [%04X] afe_base [%04X]\n", + __func__, ts->tdata->nvdata.tune_fix_ver, ts->tdata->afe_base); + + if ((ts->tdata->tclm_level > TCLM_LEVEL_CLEAR_NV) && + ((ts->tdata->nvdata.tune_fix_ver == 0xffff) + || (ts->tdata->afe_base > ts->tdata->nvdata.tune_fix_ver))) { + /* tune version up case */ + sec_tclm_root_of_cal(ts->tdata, CALPOSITION_TUNEUP); + restore_cal = true; + } else if (ts->tdata->tclm_level == TCLM_LEVEL_CLEAR_NV) { + /* firmup case */ + sec_tclm_root_of_cal(ts->tdata, CALPOSITION_FIRMUP); + restore_cal = true; + } else if ((ts->tdata->nvdata.tune_fix_ver >> 8) == 0x31) { + sec_tclm_root_of_cal(ts->tdata, CALPOSITION_TUNEUP); + restore_cal = true; /* temp */ + } + + if (restore_cal) { + input_info(true, ts->dev, "%s: RUN OFFSET CALIBRATION\n", __func__); + if (sec_execute_tclm_package(ts->tdata, 0) < 0) + input_err(true, ts->dev, "%s: sec_execute_tclm_package fail\n", __func__); + } + } +#endif + +done: +#ifdef TCLM_CONCEPT + sec_tclm_root_of_cal(ts->tdata, CALPOSITION_NONE); +#endif + release_firmware(fw_entry); +exit_fwload: + return retval; +} + +static int synaptics_ts_load_fw_from_bin(struct synaptics_ts_data *ts) +{ + int error = 0; + int restore_cal = 0; + const struct firmware *fw_entry; + char fw_path[SEC_TS_MAX_FW_PATH]; + + if (ts->plat_data->bringup == 1) { + error = -1; + input_err(true, ts->dev, "%s: can't update for bringup:%d\n", + __func__, ts->plat_data->bringup); + return error; + } + + if (ts->plat_data->firmware_name) + snprintf(fw_path, SEC_TS_MAX_FW_PATH, "%s", ts->plat_data->firmware_name); + else + return 0; + + disable_irq(ts->irq); + + /* Loading Firmware */ + error = request_firmware(&fw_entry, fw_path, ts->dev); + if (error) { + input_err(true, ts->dev, "%s: not exist firmware\n", __func__); + error = -1; + enable_irq(ts->irq); + goto err_request_fw; + } + + +#ifdef TCLM_CONCEPT + sec_tclm_root_of_cal(ts->tdata, CALPOSITION_TESTMODE); + restore_cal = 1; +#endif + /* use virtual tclm_control - magic cal 1 */ + error = synaptics_ts_do_fw_update(ts, + fw_entry->data, + fw_entry->size, + (ts->fw_erase_delay << 16) | ts->fw_write_block_delay, + true); + if (error < 0) { + input_err(true, ts->dev, "Fail to do reflash\n"); + restore_cal = 0; + } + + /* re-initialize the app fw */ + error = synaptics_ts_set_up_app_fw(ts); + if (error < 0) { + input_err(true, ts->dev, "Fail to set up app fw after fw update\n"); + release_firmware(fw_entry); + enable_irq(ts->irq); + goto err_request_fw; + } + + enable_irq(ts->irq); + +#ifdef TCLM_CONCEPT + if (restore_cal == 1) + sec_execute_tclm_package(ts->tdata, 0); + sec_tclm_root_of_cal(ts->tdata, CALPOSITION_NONE); +#endif + + release_firmware(fw_entry); +err_request_fw: + + return error; +} + +static int synaptics_ts_load_fw(struct synaptics_ts_data *ts, int update_type) +{ + int error = 0; + struct app_config_header *header; + struct synaptics_ts_reflash_data_blob reflash_data; + const struct firmware *fw_entry; + char fw_path[SEC_TS_MAX_FW_PATH]; + bool is_fw_signed = false; +#ifdef SUPPORT_FW_SIGNED + long spu_ret = 0; + long ori_size = 0; +#endif +#ifdef TCLM_CONCEPT + int restore_cal = 0; +#endif + + disable_irq(ts->irq); + + switch (update_type) { + case TSP_SDCARD: +#if !defined(CONFIG_SAMSUNG_PRODUCT_SHIP) + snprintf(fw_path, SEC_TS_MAX_FW_PATH, "%s", TSP_EXTERNAL_FW); +#else + snprintf(fw_path, SEC_TS_MAX_FW_PATH, "%s", TSP_EXTERNAL_FW_SIGNED); + is_fw_signed = true; +#endif + break; + case TSP_SPU: + case TSP_VERIFICATION: + snprintf(fw_path, SEC_TS_MAX_FW_PATH, "%s", TSP_SPU_FW_SIGNED); + is_fw_signed = true; + break; + default: + goto err_firmware_path; + } + + error = request_firmware(&fw_entry, fw_path, ts->dev); + if (error) { + input_err(true, ts->dev, "%s: firmware is not available %d\n", __func__, error); + goto err_request_fw; + } + + synaptics_ts_buf_init(&reflash_data.out); + + reflash_data.image = fw_entry->data; + reflash_data.image_size = fw_entry->size; + synaptics_ts_pal_mem_set(&reflash_data.image_info, 0x00, + sizeof(struct image_info)); + + error = synaptics_ts_parse_fw_image(ts, fw_entry->data, &reflash_data.image_info); + if (error < 0) { + input_err(true, ts->dev, "%s: Fail to parse firmware image\n", __func__); + synaptics_ts_buf_release(&reflash_data.out); + release_firmware(fw_entry); + goto err_request_fw; + } + + header = (struct app_config_header *)reflash_data.image_info.data[AREA_APP_CONFIG].data; + +#ifdef SUPPORT_FW_SIGNED + /* If SPU firmware version is lower than IC's version, do not run update routine */ + if (update_type == TSP_VERIFICATION) { + ori_size = fw_entry->size - SPU_METADATA_SIZE(TSP); + spu_ret = spu_firmware_signature_verify("TSP", fw_entry->data, fw_entry->size); + if (spu_ret != ori_size) { + input_err(true, ts->dev, "%s: signature verify failed, spu_ret:%ld, ori_size:%ld\n", + __func__, spu_ret, ori_size); + error = -EPERM; + } + synaptics_ts_buf_release(&reflash_data.out); + release_firmware(fw_entry); + goto err_request_fw; + + } else if (is_fw_signed) { + /* digest 32, signature 512 TSP 3 */ + ori_size = fw_entry->size - SPU_METADATA_SIZE(TSP); + if ((update_type == TSP_SPU) && (ts->plat_data->img_version_of_ic[0] == header->customer_config_id[0] && + ts->plat_data->img_version_of_ic[1] == header->customer_config_id[1] && + ts->plat_data->img_version_of_ic[2] == header->customer_config_id[2])) { + if (ts->plat_data->img_version_of_ic[3] >= header->customer_config_id[3]) { + input_err(true, ts->dev, "%s: img version: %02X%02X%02X%02Xexit\n", + __func__, ts->plat_data->img_version_of_ic[0], ts->plat_data->img_version_of_ic[1], + ts->plat_data->img_version_of_ic[2], ts->plat_data->img_version_of_ic[3]); + error = 0; + input_info(true, ts->dev, "%s: skip spu\n", __func__); + goto done; + } else { + input_info(true, ts->dev, "%s: run spu\n", __func__); + } + } else if ((update_type == TSP_SDCARD) && (ts->plat_data->img_version_of_ic[0] == header->customer_config_id[0] && + ts->plat_data->img_version_of_ic[1] == header->customer_config_id[1])) { + input_info(true, ts->dev, "%s: run sfu\n", __func__); + } else { + input_info(true, ts->dev, "%s: not matched product version\n", __func__); + error = -ENOENT; + goto done; + } + + spu_ret = spu_firmware_signature_verify("TSP", fw_entry->data, fw_entry->size); + if (spu_ret != ori_size) { + input_err(true, ts->dev, "%s: signature verify failed, spu_ret:%ld, ori_size:%ld\n", + __func__, spu_ret, ori_size); + error = -EPERM; + goto done; + } + } +#endif + +#ifdef TCLM_CONCEPT + sec_tclm_root_of_cal(ts->tdata, CALPOSITION_TESTMODE); + restore_cal = 1; +#endif + error = synaptics_ts_do_fw_update(ts, + fw_entry->data, + fw_entry->size, + (ts->fw_erase_delay << 16) | ts->fw_write_block_delay, + true); + if (error < 0) { + input_err(true, ts->dev, "Fail to do reflash\n"); + goto done; + } + + /* re-initialize the app fw */ + error = synaptics_ts_set_up_app_fw(ts); + if (error < 0) { + input_err(true, ts->dev, "Fail to set up app fw after fw update\n"); + goto done; + } + + enable_irq(ts->irq); + +#ifdef TCLM_CONCEPT + sec_execute_tclm_package(ts->tdata, 0); +#endif + + synaptics_ts_buf_release(&reflash_data.out); +#ifdef TCLM_CONCEPT + sec_tclm_root_of_cal(ts->tdata, CALPOSITION_NONE); +#endif + release_firmware(fw_entry);; + return error; +done: + synaptics_ts_buf_release(&reflash_data.out); +#ifdef TCLM_CONCEPT + sec_tclm_root_of_cal(ts->tdata, CALPOSITION_NONE); +#endif + release_firmware(fw_entry); + +err_request_fw: +err_firmware_path: + enable_irq(ts->irq); + return error; + +} + + +int synaptics_ts_fw_update_on_hidden_menu(struct synaptics_ts_data *ts, int update_type) +{ + int retval = SEC_ERROR; + + /* Factory cmd for firmware update + * argument represent what is source of firmware like below. + * + * 0 : [BUILT_IN] Getting firmware which is for user. + * 1 : [UMS] Getting firmware from sd card. + * 2 : none + * 3 : [FFU] Getting firmware from apk. + */ + switch (update_type) { + case TSP_BUILT_IN: + retval = synaptics_ts_load_fw_from_bin(ts); + break; + + case TSP_SDCARD: + case TSP_SPU: + case TSP_VERIFICATION: + retval = synaptics_ts_load_fw(ts, update_type); + break; + + default: + input_err(true, ts->dev, "%s: Not support command[%d]\n", + __func__, update_type); + break; + } + + synaptics_ts_get_custom_library(ts); + ts->plat_data->init(ts); + + return retval; +} + +MODULE_LICENSE("GPL"); diff --git a/drivers/input/sec_input/synaptics/synaptics_i2c.c b/drivers/input/sec_input/synaptics/synaptics_i2c.c new file mode 100644 index 000000000000..0f290796165a --- /dev/null +++ b/drivers/input/sec_input/synaptics/synaptics_i2c.c @@ -0,0 +1,756 @@ +/* drivers/input/sec_input/synaptics/synaptics_core.c + * + * Copyright (C) 2020 Samsung Electronics Co., Ltd. + * + * Core file for Samsung TSC driver + * + * 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. + */ + +#include "synaptics_dev.h" +#include "synaptics_reg.h" + +#define RD_CHUNK_SIZE_I2C (1024) +#define WR_CHUNK_SIZE_I2C (1024) + + +static int synaptics_ts_i2c_write(struct synaptics_ts_data *ts, u8 *reg, int cnum, u8 *data, int len) +{ + struct i2c_client *client = ts->client; + int ret; + unsigned char retry; + struct i2c_msg msg; + int i; + const int len_max = 0xffff; + u8 *buf; + u8 *buff; + + if (len + 1 > len_max) { + input_err(true, ts->dev, + "%s: The i2c buffer size is exceeded.\n", __func__); + return -ENOMEM; + } + + if (!ts->plat_data->resume_done.done) { + ret = wait_for_completion_interruptible_timeout(&ts->plat_data->resume_done, msecs_to_jiffies(500)); + if (ret <= 0) { + input_err(true, ts->dev, "%s: LPM: pm resume is not handled:%d\n", __func__, ret); + return -EIO; + } + } + + if (sec_check_secure_trusted_mode_status(ts->plat_data)) + return -EBUSY; + +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) + if (STUI_MODE_TOUCH_SEC & stui_get_mode()) + return -EBUSY; +#endif + buf = kzalloc(cnum, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + memcpy(buf, reg, cnum); + + buff = kzalloc(len + cnum, GFP_KERNEL); + if (!buff) { + kfree(buf); + return -ENOMEM; + } + + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: POWER_STATUS : OFF\n", __func__); + goto err; + } + + memcpy(buff, buf, cnum); + memcpy(buff + cnum, data, len); + + msg.addr = client->addr; + msg.flags = 0 | I2C_M_DMA_SAFE; + msg.len = len + cnum; + msg.buf = buff; + + mutex_lock(&ts->transfer_mutex); + for (retry = 0; retry < SYNAPTICS_TS_I2C_RETRY_CNT; retry++) { + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret == 1) + break; + + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: POWER_STATUS : OFF, retry:%d\n", __func__, retry); + mutex_unlock(&ts->transfer_mutex); + goto err; + } + + sec_delay(20); + } + + mutex_unlock(&ts->transfer_mutex); + + if (retry == SYNAPTICS_TS_I2C_RETRY_CNT) { + char result[32]; + input_err(true, ts->dev, "%s: I2C write over retry limit retry:%d\n", __func__, retry); + ts->plat_data->hw_param.comm_err_count++; + + snprintf(result, sizeof(result), "RESULT=I2C"); + sec_cmd_send_event_to_user(&ts->sec, NULL, result); + + ret = -EIO; + if (ts->probe_done && !atomic_read(&ts->reset_is_on_going) && !atomic_read(&ts->plat_data->shutdown_called)) + schedule_delayed_work(&ts->reset_work, msecs_to_jiffies(TOUCH_RESET_DWORK_TIME)); + } + + if (ts->debug_flag & SEC_TS_DEBUG_PRINT_WRITE_CMD) { + char *dbuff; + char *dtbuff; + int dbuff_len = ((cnum + len) * 3) + 6; + int dtbuff_len = 3 * 3 + 3; + + dbuff = kzalloc(dbuff_len, GFP_KERNEL); + if (!dbuff) + goto out_write; + + dtbuff = kzalloc(dtbuff_len, GFP_KERNEL); + if (!dtbuff) { + kfree(dbuff); + goto out_write; + } + + for (i = 0; i < cnum; i++) { + memset(dtbuff, 0x00, dtbuff_len); + snprintf(dtbuff, dtbuff_len, "%02X ", reg[i]); + strlcat(dbuff, dtbuff, dbuff_len); + } + strlcat(dbuff, " | ", dbuff_len); + for (i = 0; i < len; i++) { + memset(dtbuff, 0x00, dtbuff_len); + snprintf(dtbuff, dtbuff_len, "%02X ", data[i]); + strlcat(dbuff, dtbuff, dbuff_len); + } + input_info(true, ts->dev, "%s: debug: %s\n", __func__, dbuff); + kfree(dbuff); + kfree(dtbuff); + } + +out_write: + if (ret == 1) { + kfree(buf); + kfree(buff); + return 0; + } +err: + kfree(buf); + kfree(buff); + return -EIO; +} + + +/** + * syna_i2c_read() + * + * TouchCom over I2C uses the normal I2C addressing and transaction direction + * mechanisms to select the device and retrieve the data. + * + * @param + * [ in] hw_if: the handle of hw interface + * [out] rd_data: buffer for storing data retrieved from device + * [ in] rd_len: number of bytes retrieved from device + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int synaptics_ts_i2c_only_read(struct synaptics_ts_data *ts, u8 *data, int len) +{ + struct i2c_client *client = ts->client; + int ret; + unsigned char retry; + struct i2c_msg msg; + int remain = len; + int i; + u8 *buff; + + if (!ts->plat_data->resume_done.done) { + ret = wait_for_completion_interruptible_timeout(&ts->plat_data->resume_done, msecs_to_jiffies(500)); + if (ret <= 0) { + input_err(true, ts->dev, "%s: LPM: pm resume is not handled:%d\n", __func__, ret); + return -EIO; + } + } + if (sec_check_secure_trusted_mode_status(ts->plat_data)) + return -EBUSY; + +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) + if (STUI_MODE_TOUCH_SEC & stui_get_mode()) + return -EBUSY; +#endif + + buff = kzalloc(len, GFP_KERNEL); + if (!buff) + return -ENOMEM; + + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: POWER_STATUS : OFF\n", __func__); + goto err; + } + + msg.addr = client->addr; + msg.flags = I2C_M_RD | I2C_M_DMA_SAFE; + msg.buf = buff; + + mutex_lock(&ts->transfer_mutex); + if (len <= ts->plat_data->i2c_burstmax) { + msg.len = len; + for (retry = 0; retry < SYNAPTICS_TS_I2C_RETRY_CNT; retry++) { + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret == 1) + break; + sec_delay(20); + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: POWER_STATUS : OFF, retry:%d\n", __func__, retry); + mutex_unlock(&ts->transfer_mutex); + goto err; + } + } + } else { + /* + * I2C read buffer is 256 byte. do not support long buffer over than 256. + * So, try to seperate reading data about 256 bytes. + */ + for (retry = 0; retry < SYNAPTICS_TS_I2C_RETRY_CNT; retry++) { + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret == 1) + break; + sec_delay(20); + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: POWER_STATUS : OFF, retry:%d\n", __func__, retry); + mutex_unlock(&ts->transfer_mutex); + goto err; + } + } + + do { + if (remain > ts->plat_data->i2c_burstmax) + msg.len = ts->plat_data->i2c_burstmax; + else + msg.len = remain; + + remain -= ts->plat_data->i2c_burstmax; + + for (retry = 0; retry < SYNAPTICS_TS_I2C_RETRY_CNT; retry++) { + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret == 1) + break; + sec_delay(20); + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: POWER_STATUS : OFF, retry:%d\n", __func__, retry); + mutex_unlock(&ts->transfer_mutex); + goto err; + } + } + msg.buf += msg.len; + } while (remain > 0); + } + + mutex_unlock(&ts->transfer_mutex); + + if (retry == SYNAPTICS_TS_I2C_RETRY_CNT) { + char result[32]; + input_err(true, ts->dev, "%s: I2C read over retry limit retry:%d\n", __func__, retry); + ts->plat_data->hw_param.comm_err_count++; + + snprintf(result, sizeof(result), "RESULT=I2C"); + sec_cmd_send_event_to_user(&ts->sec, NULL, result); + + ret = -EIO; + if (ts->probe_done && !atomic_read(&ts->reset_is_on_going) && !atomic_read(&ts->plat_data->shutdown_called)) + schedule_delayed_work(&ts->reset_work, msecs_to_jiffies(TOUCH_RESET_DWORK_TIME)); + } + + memcpy(data, buff, len); + if (ts->debug_flag & SEC_TS_DEBUG_PRINT_READ_CMD) { + char *dbuff; + char *dtbuff; + int dbuff_len = (len * 3) + 6; + int dtbuff_len = 3 * 3 + 3; + + dbuff = kzalloc(dbuff_len, GFP_KERNEL); + if (!dbuff) + goto out_read; + + dtbuff = kzalloc(dtbuff_len, GFP_KERNEL); + if (!dtbuff) { + kfree(dbuff); + goto out_read; + } + + for (i = 0; i < len; i++) { + memset(dtbuff, 0x00, dtbuff_len); + snprintf(dtbuff, dtbuff_len, "%02X ", data[i]); + strlcat(dbuff, dtbuff, dbuff_len); + } + input_info(true, ts->dev, "%s: debug: %s\n", __func__, dbuff); + kfree(dbuff); + kfree(dtbuff); + } + +out_read: + kfree(buff); + return ret; +err: + kfree(buff); + return -EIO; +} + +static int synaptics_ts_i2c_read(struct synaptics_ts_data *ts, u8 *reg, int cnum, u8 *data, int len) +{ + int ret; + unsigned char retry; + struct i2c_client *client = ts->client; + struct i2c_msg msg[2]; + int remain = len; + int i; + u8 *buff; + u8 *buf; + + if (!ts->plat_data->resume_done.done) { + ret = wait_for_completion_interruptible_timeout(&ts->plat_data->resume_done, msecs_to_jiffies(500)); + if (ret <= 0) { + input_err(true, ts->dev, "%s: LPM: pm resume is not handled:%d\n", __func__, ret); + return -EIO; + } + } + + if (sec_check_secure_trusted_mode_status(ts->plat_data)) + return -EBUSY; +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) + if (STUI_MODE_TOUCH_SEC & stui_get_mode()) + return -EBUSY; +#endif + buf = kzalloc(cnum, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + memcpy(buf, reg, cnum); + + buff = kzalloc(len, GFP_KERNEL); + if (!buff) { + kfree(buf); + return -ENOMEM; + } + + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: POWER_STATUS : OFF\n", __func__); + goto err; + } + + msg[0].addr = client->addr; + msg[0].flags = 0 | I2C_M_DMA_SAFE; + msg[0].len = cnum; + msg[0].buf = buf; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD | I2C_M_DMA_SAFE; + msg[1].buf = buff; + + mutex_lock(&ts->transfer_mutex); + if (len <= ts->plat_data->i2c_burstmax) { + msg[1].len = len; + for (retry = 0; retry < SYNAPTICS_TS_I2C_RETRY_CNT; retry++) { + ret = i2c_transfer(client->adapter, msg, 2); + if (ret == 2) + break; + sec_delay(20); + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: POWER_STATUS : OFF, retry:%d\n", __func__, retry); + mutex_unlock(&ts->transfer_mutex); + goto err; + } + } + } else { + /* + * I2C read buffer is 256 byte. do not support long buffer over than 256. + * So, try to seperate reading data about 256 bytes. + */ + for (retry = 0; retry < SYNAPTICS_TS_I2C_RETRY_CNT; retry++) { + ret = i2c_transfer(client->adapter, msg, 1); + if (ret == 1) + break; + sec_delay(20); + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: POWER_STATUS : OFF, retry:%d\n", __func__, retry); + mutex_unlock(&ts->transfer_mutex); + goto err; + } + } + + do { + if (remain > ts->plat_data->i2c_burstmax) + msg[1].len = ts->plat_data->i2c_burstmax; + else + msg[1].len = remain; + + remain -= ts->plat_data->i2c_burstmax; + + for (retry = 0; retry < SYNAPTICS_TS_I2C_RETRY_CNT; retry++) { + ret = i2c_transfer(client->adapter, &msg[1], 1); + if (ret == 1) + break; + sec_delay(20); + if (sec_input_cmp_ic_status(ts->dev, CHECK_POWEROFF)) { + input_err(true, ts->dev, "%s: POWER_STATUS : OFF, retry:%d\n", __func__, retry); + mutex_unlock(&ts->transfer_mutex); + goto err; + } + } + msg[1].buf += msg[1].len; + } while (remain > 0); + } + + mutex_unlock(&ts->transfer_mutex); + + if (retry == SYNAPTICS_TS_I2C_RETRY_CNT) { + char result[32]; + input_err(true, ts->dev, "%s: I2C read over retry limit retry:%d\n", __func__, retry); + ts->plat_data->hw_param.comm_err_count++; + + snprintf(result, sizeof(result), "RESULT=I2C"); + sec_cmd_send_event_to_user(&ts->sec, NULL, result); + + ret = -EIO; + if (ts->probe_done && !atomic_read(&ts->reset_is_on_going) && !atomic_read(&ts->plat_data->shutdown_called)) + schedule_delayed_work(&ts->reset_work, msecs_to_jiffies(TOUCH_RESET_DWORK_TIME)); + } + + memcpy(data, buff, len); + if (ts->debug_flag & SEC_TS_DEBUG_PRINT_READ_CMD) { + char *dbuff; + char *dtbuff; + int dbuff_len = ((cnum + len) * 3) + 6; + int dtbuff_len = 3 * 3 + 3; + + dbuff = kzalloc(dbuff_len, GFP_KERNEL); + if (!dbuff) + goto out_read; + + dtbuff = kzalloc(dtbuff_len, GFP_KERNEL); + if (!dtbuff) { + kfree(dbuff); + goto out_read; + } + + for (i = 0; i < cnum; i++) { + memset(dtbuff, 0x00, dtbuff_len); + snprintf(dtbuff, dtbuff_len, "%02X ", reg[i]); + strlcat(dbuff, dtbuff, dbuff_len); + } + strlcat(dbuff, " | ", dbuff_len); + for (i = 0; i < len; i++) { + memset(dtbuff, 0x00, dtbuff_len); + snprintf(dtbuff, dtbuff_len, "%02X ", data[i]); + strlcat(dbuff, dtbuff, dbuff_len); + } + input_info(true, ts->dev, "%s: debug: %s\n", __func__, dbuff); + kfree(dbuff); + kfree(dtbuff); + } + +out_read: + kfree(buf); + kfree(buff); + return ret; +err: + kfree(buf); + kfree(buff); + return -EIO; +} + +static int synaptics_ts_i2c_get_baredata(struct synaptics_ts_data *ts, u8 *data, int len) +{ + unsigned char address = 0x07; + + return ts->synaptics_ts_read_data(ts, &address, 1, data, len); +} + +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) +extern int stui_i2c_lock(struct i2c_adapter *adap); +extern int stui_i2c_unlock(struct i2c_adapter *adap); + +int synaptics_stui_tsp_enter(void) +{ + struct synaptics_ts_data *ts = dev_get_drvdata(ptsp); + int ret = 0; + struct i2c_client *client; + + if (!ts) + return -EINVAL; + + client = (struct i2c_client *)ts->client; + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&ts->synaptics_input_nb, NOTIFIER_SECURE_TOUCH_ENABLE, NULL); +#endif + + disable_irq(ts->irq); + synaptics_ts_release_all_finger(ts); + + ret = stui_i2c_lock(client->adapter); + if (ret) { + pr_err("[STUI] stui_i2c_lock failed : %d\n", ret); +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&ts->synaptics_input_nb, NOTIFIER_SECURE_TOUCH_DISABLE, NULL); +#endif + enable_irq(ts->irq); + return -1; + } + + return 0; +} + +int synaptics_stui_tsp_exit(void) +{ + struct synaptics_ts_data *ts = dev_get_drvdata(ptsp); + int ret = 0; + struct i2c_client *client; + + if (!ts) + return -EINVAL; + + client = (struct i2c_client *)ts->client; + + ret = stui_i2c_unlock(client->adapter); + if (ret) + pr_err("[STUI] stui_i2c_unlock failed : %d\n", ret); + + enable_irq(ts->irq); + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&ts->synaptics_input_nb, NOTIFIER_SECURE_TOUCH_DISABLE, NULL); +#endif + + return ret; +} + +int synaptics_stui_tsp_type(void) +{ + return STUI_TSP_TYPE_SNAPTICS; +} +#endif + +static int synaptics_ts_i2c_init(struct i2c_client *client) +{ + struct synaptics_ts_data *ts; + struct sec_ts_plat_data *pdata; + struct sec_tclm_data *tdata = NULL; + int ret = 0; + + ts = devm_kzalloc(&client->dev, sizeof(struct synaptics_ts_data), GFP_KERNEL); + if (!ts) { + ret = -ENOMEM; + goto error_allocate_pdata; + } + + if (client->dev.of_node) { + pdata = devm_kzalloc(&client->dev, + sizeof(struct sec_ts_plat_data), GFP_KERNEL); + + if (!pdata) { + ret = -ENOMEM; + goto error_allocate_pdata; + } + + client->dev.platform_data = pdata; + + ret = sec_input_parse_dt(&client->dev); + if (ret) { + input_err(true, &client->dev, "%s: Failed to parse dt\n", __func__); + goto error_allocate_pdata; + } + tdata = devm_kzalloc(&client->dev, + sizeof(struct sec_tclm_data), GFP_KERNEL); + if (!tdata) { + ret = -ENOMEM; + goto error_allocate_mem; + } + +#ifdef TCLM_CONCEPT + sec_tclm_parse_dt(&client->dev, tdata); +#endif + } else { + pdata = client->dev.platform_data; + if (!pdata) { + ret = -ENOMEM; + input_err(true, &client->dev, "%s: No platform data found\n", __func__); + goto error_allocate_pdata; + } + } + + pdata->pinctrl = devm_pinctrl_get(&client->dev); + if (IS_ERR(pdata->pinctrl)) + input_err(true, &client->dev, "%s: could not get pinctrl\n", __func__); + +#if !IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP) + ts->vendor_data = devm_kzalloc(&client->dev, sizeof(struct synaptics_ts_sysfs), GFP_KERNEL); + if (!ts->vendor_data) { + ret = -ENOMEM; + goto error_allocate_mem; + } +#endif + + client->irq = gpio_to_irq(pdata->irq_gpio); + + ts->client = client; + ts->dev = &client->dev; + ts->plat_data = pdata; + ts->plat_data->bus_master = &client->adapter->dev; + ts->irq = client->irq; + +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) + ts->plat_data->stui_tsp_enter = synaptics_stui_tsp_enter; + ts->plat_data->stui_tsp_exit = synaptics_stui_tsp_exit; + ts->plat_data->stui_tsp_type = synaptics_stui_tsp_type; +#endif + + mutex_init(&ts->transfer_mutex); + + ts->synaptics_ts_write_data = synaptics_ts_i2c_write; + ts->synaptics_ts_read_data = synaptics_ts_i2c_read; + ts->synaptics_ts_read_data_only = synaptics_ts_i2c_only_read; + ts->synaptics_ts_get_baredata = synaptics_ts_i2c_get_baredata; + + if (!ts->synaptics_ts_write_data || !ts->synaptics_ts_read_data) { + input_err(true, &client->dev, "%s: not valid r/w operations\n", __func__); + ret = -ENOMEM; + goto error_allocate_mem; + } + + ts->tdata = tdata; + if (!ts->tdata) { + ret = -ENOMEM; + goto error_allocate_mem; + } + + ts->max_rd_size = RD_CHUNK_SIZE_I2C; + ts->max_wr_size = WR_CHUNK_SIZE_I2C; + + ret = synaptics_ts_init(ts); + if (ret < 0) { + input_err(true, &client->dev, "%s: fail to do ts init\n", __func__); + goto error_init; + } + + i2c_set_clientdata(client, ts); + + return 0; + +error_init: +error_allocate_mem: +error_allocate_pdata: + input_err(true, &client->dev, "%s: failed(%d)\n", __func__, ret); + input_log_fix(); + return ret; +} + +static int synaptics_ts_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct synaptics_ts_data *ts; + int ret = 0; + static int deferred_flag; + + if (!deferred_flag) { + deferred_flag = 1; + input_info(true, &client->dev, "deferred_flag boot %s\n", __func__); + return -EPROBE_DEFER; + } + + input_info(true, &client->dev, "%s\n", __func__); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + input_err(true, &client->dev, "%s: EIO err!\n", __func__); + return -EIO; + } + + ret = synaptics_ts_i2c_init(client); + if (ret < 0) { + input_err(true, &client->dev, "%s: fail to init resource\n", __func__); + return ret; + } + + ts = i2c_get_clientdata(client); + if (!ts->plat_data->work_queue_probe_enabled) + return synaptics_ts_probe(ts->dev); + + queue_work(ts->plat_data->probe_workqueue, &ts->plat_data->probe_work); + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +static void synaptics_ts_i2c_remove(struct i2c_client *client) +{ + struct synaptics_ts_data *ts = i2c_get_clientdata(client); + + synaptics_ts_remove(ts); +} +#else +static int synaptics_ts_i2c_remove(struct i2c_client *client) +{ + struct synaptics_ts_data *ts = i2c_get_clientdata(client); + + synaptics_ts_remove(ts); + + return 0; +} +#endif + +static void synaptics_ts_i2c_shutdown(struct i2c_client *client) +{ + struct synaptics_ts_data *ts = i2c_get_clientdata(client); + + synaptics_ts_shutdown(ts); +} + +static const struct i2c_device_id synaptics_ts_id[] = { + { SYNAPTICS_TS_I2C_NAME, 0 }, + { }, +}; + +#if IS_ENABLED(CONFIG_PM) +static const struct dev_pm_ops synaptics_ts_dev_pm_ops = { + .suspend = synaptics_ts_pm_suspend, + .resume = synaptics_ts_pm_resume, +}; +#endif + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id synaptics_ts_match_table[] = { + { .compatible = "synaptics,synaptics_ts",}, + { }, +}; +#else +#define synaptics_ts_match_table NULL +#endif + +static struct i2c_driver synaptics_ts_driver = { + .probe = synaptics_ts_i2c_probe, + .remove = synaptics_ts_i2c_remove, + .shutdown = synaptics_ts_i2c_shutdown, + .id_table = synaptics_ts_id, + .driver = { + .owner = THIS_MODULE, + .name = SYNAPTICS_TS_I2C_NAME, +#if IS_ENABLED(CONFIG_OF) + .of_match_table = synaptics_ts_match_table, +#endif +#if IS_ENABLED(CONFIG_PM) + .pm = &synaptics_ts_dev_pm_ops, +#endif + }, +}; + +module_i2c_driver(synaptics_ts_driver); + +MODULE_SOFTDEP("pre: acpm-mfd-bus"); +MODULE_DESCRIPTION("synaptics TouchScreen driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/sec_input/synaptics/synaptics_ic_setting.c b/drivers/input/sec_input/synaptics/synaptics_ic_setting.c new file mode 100644 index 000000000000..8f7d98e4ea3c --- /dev/null +++ b/drivers/input/sec_input/synaptics/synaptics_ic_setting.c @@ -0,0 +1,55 @@ +#include "synaptics_dev.h" +#include "synaptics_reg.h" + +#define PER_PAGE_ERASE_DELAY_MS_3916T (88) +#define PER_BLOCK_WRITE_DELAY_MS_3916T (90) +#define FLASH_WRITE_DELAY_MS_DEFAULT (20) +#define SYNAPTICS_IC_S3916T "S3916T" + +void synaptics_ts_fw_delay_setting(struct synaptics_ts_data *ts) +{ + if (strncmp(ts->id_info.part_number, SYNAPTICS_IC_S3916T, 6) == 0) { + input_info(true, ts->dev, "%s: %s require product-specified settings (S3916T)\n", + __func__, ts->id_info.part_number); + ts->fw_erase_delay = PER_PAGE_ERASE_DELAY_MS_3916T; + ts->fw_write_block_delay = PER_BLOCK_WRITE_DELAY_MS_3916T; + } else { + input_info(true, ts->dev, "%s: %s Other IC\n", + __func__, ts->id_info.part_number); + } +} + +int synaptics_ts_fw_resp_delay_ms(struct synaptics_ts_data *ts, + unsigned int xfer_length, unsigned int wr_delay_ms, unsigned int num_blocks) +{ + unsigned int resp_delay; + + if (wr_delay_ms == FORCE_ATTN_DRIVEN) { + input_dbg(true, ts->dev, "%s: xfer: %d (blocks: %d), delay: ATTN-driven\n", __func__, xfer_length, num_blocks); + resp_delay = FORCE_ATTN_DRIVEN; + } else { + resp_delay = (wr_delay_ms * num_blocks) / 1000; + //input_dbg(true, ts->dev, "%s: xfer: %d (blocks: %d), delay: %d ms\n", __func__, xfer_length, num_blocks, resp_delay); + } + + return resp_delay; +} + +int synaptics_ts_fw_set_page_count(struct synaptics_ts_data *ts, + struct synaptics_ts_reflash_data_blob *reflash_data, unsigned int size) +{ + return synaptics_ts_pal_ceil_div(size, reflash_data->page_size); +} + +int synaptics_ts_fw_set_erase_delay(struct synaptics_ts_data *ts, + unsigned int erase_delay_ms, unsigned int page_count) +{ + int resp_delay = erase_delay_ms; + + if (erase_delay_ms == FORCE_ATTN_DRIVEN) + resp_delay = FORCE_ATTN_DRIVEN; + else + resp_delay = erase_delay_ms * page_count; + + return resp_delay; +} diff --git a/drivers/input/sec_input/synaptics/synaptics_interrupt.c b/drivers/input/sec_input/synaptics/synaptics_interrupt.c new file mode 100644 index 000000000000..ab929d9f5809 --- /dev/null +++ b/drivers/input/sec_input/synaptics/synaptics_interrupt.c @@ -0,0 +1,1048 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Samsung Electronics Co., Ltd. + * + * 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. + */ + +#include "synaptics_dev.h" +#include "synaptics_reg.h" + +static void synaptics_ts_coord_parsing(struct synaptics_ts_data *ts, struct sec_touch_event_data *p_event_coord, u8 t_id) +{ + ts->plat_data->coord[t_id].id = t_id; + ts->plat_data->coord[t_id].action = p_event_coord->tchsta; + ts->plat_data->coord[t_id].x = (p_event_coord->x_11_4 << 4) | (p_event_coord->x_3_0); + ts->plat_data->coord[t_id].y = (p_event_coord->y_11_4 << 4) | (p_event_coord->y_3_0); + ts->plat_data->coord[t_id].z = p_event_coord->z & 0x3F; + ts->plat_data->coord[t_id].ttype = p_event_coord->ttype_3_2 << 2 | p_event_coord->ttype_1_0 << 0; + ts->plat_data->coord[t_id].major = p_event_coord->major; + ts->plat_data->coord[t_id].minor = p_event_coord->minor; + + if (!ts->plat_data->coord[t_id].palm && (ts->plat_data->coord[t_id].ttype == SYNAPTICS_TS_TOUCHTYPE_PALM)) + ts->plat_data->coord[t_id].palm_count++; + + ts->plat_data->coord[t_id].palm = (ts->plat_data->coord[t_id].ttype == SYNAPTICS_TS_TOUCHTYPE_PALM); + if (ts->plat_data->coord[t_id].palm) + ts->plat_data->palm_flag |= (1 << t_id); + else + ts->plat_data->palm_flag &= ~(1 << t_id); + + ts->plat_data->coord[t_id].left_event = p_event_coord->left_event; + + ts->plat_data->coord[t_id].noise_level = max(ts->plat_data->coord[t_id].noise_level, + p_event_coord->noise_level); + ts->plat_data->coord[t_id].max_strength = max(ts->plat_data->coord[t_id].max_strength, + p_event_coord->max_strength); + ts->plat_data->coord[t_id].hover_id_num = max_t(u8, ts->plat_data->coord[t_id].hover_id_num, + p_event_coord->hover_id_num); + ts->plat_data->coord[t_id].noise_status = p_event_coord->noise_status; + ts->plat_data->coord[t_id].freq_id = p_event_coord->freq_id; + ts->plat_data->coord[t_id].fod_debug = p_event_coord->fod_debug; + + if (ts->plat_data->freq_id != p_event_coord->freq_id) + sec_cmd_send_status_uevent(&ts->sec, STATUS_TYPE_FREQ, p_event_coord->freq_id); + ts->plat_data->freq_id = p_event_coord->freq_id; + + if (ts->plat_data->coord[t_id].z <= 0) + ts->plat_data->coord[t_id].z = 1; +} + +static void synaptics_ts_fod_vi_event(struct synaptics_ts_data *ts) +{ + int ret = 0; + unsigned short offset; + + offset = SEC_TS_CMD_SPONGE_FOD_POSITION; + + ret = ts->synaptics_ts_read_sponge(ts, ts->plat_data->fod_data.vi_data, ts->plat_data->fod_data.vi_size, + offset, ts->plat_data->fod_data.vi_size); + if (ret < 0) + input_info(true, ts->dev, "%s: failed read fod vi\n", __func__); +} + +static void synaptics_ts_gesture_event(struct synaptics_ts_data *ts, u8 *event_buff) +{ + struct sec_gesture_event_data *p_gesture_status; + int x, y; + + p_gesture_status = (struct sec_gesture_event_data *)event_buff; + + x = (p_gesture_status->gesture_data_1 << 4) | (p_gesture_status->gesture_data_3 >> 4); + y = (p_gesture_status->gesture_data_2 << 4) | (p_gesture_status->gesture_data_3 & 0x0F); + + if (p_gesture_status->stype == SYNAPTICS_TS_GESTURE_CODE_SWIPE) { + sec_input_gesture_report(ts->dev, SPONGE_EVENT_TYPE_SPAY, 0, 0); + } else if (p_gesture_status->stype == SYNAPTICS_TS_GESTURE_CODE_DOUBLE_TAP) { + if (p_gesture_status->gesture_id == SYNAPTICS_TS_GESTURE_ID_AOD) { + sec_input_gesture_report(ts->dev, SPONGE_EVENT_TYPE_AOD_DOUBLETAB, x, y); + } else if (p_gesture_status->gesture_id == SYNAPTICS_TS_GESTURE_ID_DOUBLETAP_TO_WAKEUP) { + input_info(true, ts->dev, "%s: AOT\n", __func__); + input_report_key(ts->plat_data->input_dev, KEY_WAKEUP, 1); + input_sync(ts->plat_data->input_dev); + input_report_key(ts->plat_data->input_dev, KEY_WAKEUP, 0); + input_sync(ts->plat_data->input_dev); + } + } else if (p_gesture_status->stype == SYNAPTICS_TS_GESTURE_CODE_SINGLE_TAP) { + sec_input_gesture_report(ts->dev, SPONGE_EVENT_TYPE_SINGLE_TAP, x, y); + } else if (p_gesture_status->stype == SYNAPTICS_TS_GESTURE_CODE_PRESS) { + if (p_gesture_status->gesture_id == SYNAPTICS_TS_GESTURE_ID_FOD_LONG || + p_gesture_status->gesture_id == SYNAPTICS_TS_GESTURE_ID_FOD_NORMAL) { + sec_input_gesture_report(ts->dev, SPONGE_EVENT_TYPE_FOD_PRESS, x, y); + input_info(true, ts->dev, "%s: FOD %sPRESS\n", + __func__, p_gesture_status->gesture_id ? "" : "LONG"); + } else if (p_gesture_status->gesture_id == SYNAPTICS_TS_GESTURE_ID_FOD_RELEASE) { + sec_input_gesture_report(ts->dev, SPONGE_EVENT_TYPE_FOD_RELEASE, x, y); + input_info(true, ts->dev, "%s: FOD RELEASE\n", __func__); + memset(ts->plat_data->fod_data.vi_data, 0x0, ts->plat_data->fod_data.vi_size); + } else if (p_gesture_status->gesture_id == SYNAPTICS_TS_GESTURE_ID_FOD_OUT) { + sec_input_gesture_report(ts->dev, SPONGE_EVENT_TYPE_FOD_OUT, x, y); + input_info(true, ts->dev, "%s: FOD OUT\n", __func__); + } else if (p_gesture_status->gesture_id == SYNAPTICS_TS_GESTURE_ID_FOD_VI) { + if ((ts->plat_data->lowpower_mode & SEC_TS_MODE_SPONGE_PRESS) && ts->plat_data->support_fod_lp_mode) + synaptics_ts_fod_vi_event(ts); + } else { + input_info(true, ts->dev, "%s: invalid id %d\n", + __func__, p_gesture_status->gesture_id); + } + } else if (p_gesture_status->stype == SYNAPTICS_TS_GESTURE_CODE_DUMPFLUSH) { +#if IS_ENABLED(CONFIG_TOUCHSCREEN_DUMP_MODE) + if (ts->sponge_inf_dump) { + if (sec_input_cmp_ic_status(ts->dev, CHECK_LPMODE)) { + if (p_gesture_status->gesture_id == SYNAPTICS_TS_SPONGE_DUMP_0) + synaptics_ts_sponge_dump_flush(ts, SYNAPTICS_TS_SPONGE_DUMP_0); + if (p_gesture_status->gesture_id == SYNAPTICS_TS_SPONGE_DUMP_1) + synaptics_ts_sponge_dump_flush(ts, SYNAPTICS_TS_SPONGE_DUMP_1); + } else { + ts->sponge_dump_delayed_flag = true; + ts->sponge_dump_delayed_area = p_gesture_status->gesture_id; + } + } +#endif + } +} + +static void synaptics_ts_coordinate_event(struct synaptics_ts_data *ts, u8 *event_buff) +{ + struct sec_touch_event_data *p_event_coord; + u8 t_id = 0; + + if (atomic_read(&ts->plat_data->power_state) != SEC_INPUT_STATE_POWER_ON) { + input_err(true, ts->dev, + "%s: device is closed %x %x %x %x %x %x %x %x\n", __func__, + event_buff[0], event_buff[1], event_buff[2], + event_buff[3], event_buff[4], event_buff[5], + event_buff[6], event_buff[7]); + return; + } + + p_event_coord = (struct sec_touch_event_data *)event_buff; + + t_id = p_event_coord->tid; + + if (t_id < SEC_TS_SUPPORT_TOUCH_COUNT) { + ts->plat_data->prev_coord[t_id] = ts->plat_data->coord[t_id]; + synaptics_ts_coord_parsing(ts, p_event_coord, t_id); + + if ((ts->plat_data->coord[t_id].ttype == SYNAPTICS_TS_TOUCHTYPE_NORMAL) + || (ts->plat_data->coord[t_id].ttype == SYNAPTICS_TS_TOUCHTYPE_PALM) + || (ts->plat_data->coord[t_id].ttype == SYNAPTICS_TS_TOUCHTYPE_WET) + || (ts->plat_data->coord[t_id].ttype == SYNAPTICS_TS_TOUCHTYPE_GLOVE)) { + sec_input_coord_event_fill_slot(ts->dev, t_id); + } else { + input_err(true, ts->dev, + "%s: do not support coordinate type(%d)\n", + __func__, ts->plat_data->coord[t_id].ttype); + } + } else { + input_err(true, ts->dev, "%s: tid(%d) is out of range\n", __func__, t_id); + } +} + +static void synaptics_ts_status_event(struct synaptics_ts_data *ts, u8 *event_buff) +{ + struct sec_status_event_data *p_event_status; + int event; + + p_event_status = (struct sec_status_event_data *)event_buff; + + if (p_event_status->data[0] > 0) + input_info(true, ts->dev, "%s: STATUS %x %x %x %x %x %x %x %x\n", __func__, + event_buff[0], event_buff[1], event_buff[2], + event_buff[3], event_buff[4], event_buff[5], + event_buff[6], event_buff[7]); + + if (p_event_status->stype == SYNAPTICS_TS_STATUS_TYPE_ERROR) { + if (p_event_status->status_id == SYNAPTICS_TS_STATUS_EVENT_ERROR_CORE) { + input_err(true, ts->dev, "%s: Core Error\n", __func__); + } else if (p_event_status->status_id == SYNAPTICS_TS_STATUS_EVENT_ERROR_OVERFLOW) { + input_err(true, ts->dev, "%s: Queue Overflow Error\n", __func__); + } else if (p_event_status->status_id == SYNAPTICS_TS_STATUS_EVENT_ERROR_ESD) { + input_err(true, ts->dev, "%s: ESD Error\n", __func__); + } + } else if (p_event_status->stype == SYNAPTICS_TS_STATUS_TYPE_INFO) { + if (p_event_status->status_id == SYNAPTICS_TS_STATUS_EVENT_INFO_READY_STATUS) { + input_info(true, ts->dev, "%s: TSC Ready Status\n", __func__); + } else if (p_event_status->status_id == SYNAPTICS_TS_STATUS_EVENT_INFO_WET) { + ts->plat_data->wet_mode = p_event_status->status_data_1; + input_info(true, ts->dev, "%s: water wet mode %d\n", + __func__, ts->plat_data->wet_mode); + if (ts->plat_data->wet_mode) + ts->plat_data->hw_param.wet_count++; + sec_cmd_send_status_uevent(&ts->sec, STATUS_TYPE_WET, p_event_status->status_data_1); + } else if (p_event_status->status_id == SYNAPTICS_TS_STATUS_EVENT_INFO_NOISE) { + atomic_set(&ts->plat_data->touch_noise_status, p_event_status->status_data_1 & 0x0f); + input_info(true, ts->dev, "%s: NOISE MODE %s[%02X]\n", + __func__, atomic_read(&ts->plat_data->touch_noise_status) == 0 ? "OFF" : "ON", + p_event_status->status_data_1); + if (atomic_read(&ts->plat_data->touch_noise_status)) + ts->plat_data->hw_param.noise_count++; + ts->plat_data->noise_mode = p_event_status->status_data_1; + sec_cmd_send_status_uevent(&ts->sec, STATUS_TYPE_NOISE, p_event_status->status_data_1); + } else if (p_event_status->status_id == SYNAPTICS_TS_STATUS_EVENT_INFO_JITTER) { + input_info(true, ts->dev, "%s: jitter %d\n", + __func__, p_event_status->status_data_1); + } else if (p_event_status->status_id == SYNAPTICS_TS_STATUS_EVENT_INFO_XENOSENSOR) { + input_info(true, ts->dev, "%s: xenosensor %d\n", + __func__, (p_event_status->status_data_5 >> 7) & 0x1); + } else if (p_event_status->status_id == SYNAPTICS_TS_STATUS_EVENT_INFO_LIGHTSENSOR) { + if ((p_event_status->status_data_5 >> 7) & 0x1) + event = PROX_EVENT_TYPE_LIGHTSENSOR_PRESS; + else + event = PROX_EVENT_TYPE_LIGHTSENSOR_RELEASE; + sec_input_proximity_report(ts->dev, event); + } + } else if (p_event_status->stype == SYNAPTICS_TS_STATUS_TYPE_VENDOR) { + if (p_event_status->status_id == SYNAPTICS_TS_STATUS_EVENT_VENDOR_PROXIMITY) { + ts->hover_event = p_event_status->status_data_1; + sec_input_proximity_report(ts->dev, p_event_status->status_data_1); + } else if (p_event_status->status_id == SYNAPTICS_TS_STATUS_EVENT_VENDOR_SYNC_STATUS) { + input_err(true, ts->dev, + "%s: sync status 0x%02x\n", __func__, p_event_status->status_data_1); + } else if (p_event_status->status_id == SYNAPTICS_TS_STATUS_EVENT_VENDOR_REZERO_REASON) { + input_err(true, ts->dev, + "%s: baseline reason 0x%02x\n", __func__, p_event_status->status_data_1); + } else if (p_event_status->status_id == SYNAPTICS_TS_STATUS_EVENT_VENDOR_NSM_STATUS) { + input_err(true, ts->dev, + "%s: NSM state 0x%02x\n", __func__, p_event_status->status_data_1); + } else if (p_event_status->status_id == SYNAPTICS_TS_STATUS_EVENT_VENDOR_BASELINE_ERROR) { + input_err(true, ts->dev, + "%s: baseline 0x%02x, relax:0x%02x\n", + __func__, p_event_status->status_data_1, p_event_status->status_data_2); + } else if (p_event_status->status_id == SYNAPTICS_TS_STATUS_EVENT_VENDOR_NSM_FREQ) { + input_err(true, ts->dev, + "%s: NSM freq 0x%02x\n", __func__, p_event_status->status_data_1); + } else if (p_event_status->status_id == SYNAPTICS_TS_STATUS_EVENT_VENDOR_CHARGER_CON) { + input_err(true, ts->dev, + "%s: charger %d, %d\n", + __func__, p_event_status->status_data_1, p_event_status->status_data_2); + } + } +} + +/** + * syna_tcm_get_touch_data() + * + * Get data entity from the received report according to bit offset and bit + * length defined in the touch report configuration. + * + * @param + * [ in] report: touch report generated by TouchComm device + * [ in] report_size: size of given report + * [ in] offset: bit offset in the report + * [ in] bits: number of bits representing the data + * [out] data: data parsed + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int synaptics_ts_get_touch_data(struct synaptics_ts_data *ts, const unsigned char *report, + unsigned int report_size, unsigned int offset, + unsigned int bits, unsigned int *data) +{ + unsigned char mask; + unsigned char byte_data; + unsigned int output_data; + unsigned int bit_offset; + unsigned int byte_offset; + unsigned int data_bits; + unsigned int available_bits; + unsigned int remaining_bits; + + if (bits == 0 || bits > 32) { + input_err(true, ts->dev, "%s: Invalid number of bits %d\n", __func__, bits); + return -EINVAL; + } + + if (!report) { + input_err(true, ts->dev, "Invalid report data\n"); + return -EINVAL; + } + + if (offset + bits > report_size * 8) { + *data = 0; + return 0; + } + + output_data = 0; + remaining_bits = bits; + + bit_offset = offset % 8; + byte_offset = offset / 8; + + while (remaining_bits) { + byte_data = report[byte_offset]; + byte_data >>= bit_offset; + + available_bits = 8 - bit_offset; + data_bits = MIN(available_bits, remaining_bits); + mask = 0xff >> (8 - data_bits); + + byte_data &= mask; + + output_data |= byte_data << (bits - remaining_bits); + + bit_offset = 0; + byte_offset += 1; + remaining_bits -= data_bits; + } + + *data = output_data; + + return 0; +} + +/** + * syna_tcm_get_gesture_data() + * + * The contents of the gesture data entity depend on which gesture + * is detected. The default size of data is defined in 16-64 bits natively. + * + * @param + * [ in] report: touch report generated by TouchComm device + * [ in] report_size: size of given report + * [ in] offset: bit offset in the report + * [ in] bits: total bits representing the gesture data + * [out] gesture_data: gesture data parsed + * [ in] gesture_id: gesture id retrieved + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +static int synaptics_ts_get_gesture_data(struct synaptics_ts_data *ts, const unsigned char *report, + unsigned int report_size, unsigned int offset, + unsigned int bits, struct synaptics_ts_gesture_data_blob *gesture_data, + unsigned int gesture_id) +{ + int retval; + unsigned int idx; + unsigned int data; + unsigned int size; + unsigned int data_end; + + if (offset + bits > report_size * 8) + return 0; + + data_end = offset + bits; + + size = (sizeof(gesture_data->data) / sizeof(unsigned char)); + + idx = 0; + while ((offset < data_end) && (idx < size)) { + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, 16, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get object index\n", __func__); + return retval; + } + gesture_data->data[idx++] = (unsigned char)(data & 0xff); + gesture_data->data[idx++] = (unsigned char)((data >> 8) & 0xff); + offset += 16; + } + + switch (gesture_id) { + case GESTURE_ID_DOUBLE_TAP: + case GESTURE_ID_ACTIVE_TAP_AND_HOLD: + input_info(true, ts->dev, "%s: Tap info: (%d, %d)\n", __func__, + synaptics_ts_pal_le2_to_uint(gesture_data->tap_x), + synaptics_ts_pal_le2_to_uint(gesture_data->tap_y)); + break; + case GESTURE_ID_SWIPE: + input_info(true, ts->dev, "%s: Swipe info: direction:%x (%d, %d)\n", __func__, + synaptics_ts_pal_le2_to_uint(gesture_data->swipe_direction), + synaptics_ts_pal_le2_to_uint(gesture_data->tap_x), + synaptics_ts_pal_le2_to_uint(gesture_data->tap_y)); + break; + default: + input_err(true, ts->dev, "%s: Unknown gesture_id:%d\n", __func__, gesture_id); + break; + } + + return 0; +} + +/** + * syna_tcm_parse_touch_report() + * + * Traverse through touch report configuration and parse the contents of + * report packet to get the exactly touched data entity from touch reports. + * + * At the end of function, the touched data will be parsed and stored at the + * associated position in structure touch_data_blob. + * + * @param + * [ in] tcm_dev: the device handle + * [ in] report: touch report generated by TouchComm device + * [ in] report_size: size of given report + * [out] touch_data: touch data generated + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int synaptics_ts_parse_touch_report(struct synaptics_ts_data *ts, + unsigned char *report, unsigned int report_size, + struct synaptics_ts_touch_data_blob *touch_data) +{ + int retval; + bool active_only; + bool num_of_active_objects; + unsigned char code; + unsigned int size; + unsigned int idx; + unsigned int obj; + unsigned int next; + unsigned int data; + unsigned int bits; + unsigned int offset; + unsigned int objects; + unsigned int active_objects; + unsigned int config_size; + unsigned char *config_data; + struct synaptics_ts_objects_data_blob *object_data; + static unsigned int end_of_foreach; + + if (!ts) { + pr_err("%s%s:Invalid tcm device handle\n", SECLOG, __func__); + return -EINVAL; + } + + if (!report) { + input_err(true, ts->dev, "Invalid report data\n"); + return -EINVAL; + } + + if (!touch_data) { + input_err(true, ts->dev, "Invalid touch data structure\n"); + return -EINVAL; + } + if (ts->max_objects == 0) { + input_err(true, ts->dev, "%s: Invalid max_objects supported\n", __func__); + return -EINVAL; + } + + object_data = touch_data->object_data; + + if (!object_data) { + input_err(true, ts->dev, "%s: Invalid object_data\n", __func__); + return -EINVAL; + } + + config_data = ts->touch_config.buf; + config_size = ts->touch_config.data_length; + + if ((!config_data) || (config_size == 0)) { + input_err(true, ts->dev, "%s: Invalid config_data\n", __func__); + return -EINVAL; + } + + size = sizeof(touch_data->object_data); + synaptics_ts_pal_mem_set(touch_data->object_data, 0x00, size); + + num_of_active_objects = false; + + idx = 0; + offset = 0; + objects = 0; + active_objects = 0; + active_only = false; + obj = 0; + next = 0; + + while (idx < config_size) { + code = config_data[idx++]; + switch (code) { + case TOUCH_REPORT_END: + goto exit; + case TOUCH_REPORT_FOREACH_ACTIVE_OBJECT: + obj = 0; + next = idx; + active_only = true; + break; + case TOUCH_REPORT_FOREACH_OBJECT: + obj = 0; + next = idx; + active_only = false; + break; + case TOUCH_REPORT_FOREACH_END: + end_of_foreach = idx; + if (active_only) { + if (num_of_active_objects) { + objects++; + obj++; + if (objects < active_objects) + idx = next; + } else if (offset < report_size * 8) { + idx = next; + } + } else { + obj++; + if (obj < ts->max_objects) + idx = next; + } + break; + case TOUCH_REPORT_PAD_TO_NEXT_BYTE: + offset = (((offset + 8) - 1) / 8) * 8; + break; + case TOUCH_REPORT_TIMESTAMP: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, bits, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get time-stamp\n", __func__); + return retval; + } + touch_data->timestamp = data; + offset += bits; + break; + case TOUCH_REPORT_OBJECT_N_INDEX: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, bits, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get object index\n", __func__); + return retval; + } + obj = data; + offset += bits; + break; + case TOUCH_REPORT_OBJECT_N_CLASSIFICATION: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, bits, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get object classification\n", __func__); + return retval; + } + object_data[obj].status = (unsigned char)data; + offset += bits; + break; + case TOUCH_REPORT_OBJECT_N_X_POSITION: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, bits, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get object x position\n", __func__); + return retval; + } + object_data[obj].x_pos = data; + offset += bits; + break; + case TOUCH_REPORT_OBJECT_N_Y_POSITION: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, bits, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get object y position\n", __func__); + return retval; + } + object_data[obj].y_pos = data; + offset += bits; + break; + case TOUCH_REPORT_OBJECT_N_Z: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, bits, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get object z\n", __func__); + return retval; + } + object_data[obj].z = data; + offset += bits; + break; + case TOUCH_REPORT_OBJECT_N_X_WIDTH: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, bits, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get object x width\n", __func__); + return retval; + } + object_data[obj].x_width = data; + offset += bits; + break; + case TOUCH_REPORT_OBJECT_N_Y_WIDTH: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, bits, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get object y width\n", __func__); + return retval; + } + object_data[obj].y_width = data; + offset += bits; + break; + case TOUCH_REPORT_OBJECT_N_TX_POSITION_TIXELS: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, bits, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get object tx position\n", __func__); + return retval; + } + object_data[obj].tx_pos = data; + offset += bits; + break; + case TOUCH_REPORT_OBJECT_N_RX_POSITION_TIXELS: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, bits, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get object rx position\n", __func__); + return retval; + } + object_data[obj].rx_pos = data; + offset += bits; + break; + case TOUCH_REPORT_NUM_OF_ACTIVE_OBJECTS: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, bits, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get number of active objects\n", __func__); + return retval; + } + active_objects = data; + num_of_active_objects = true; + touch_data->num_of_active_objects = data; + offset += bits; + if (touch_data->num_of_active_objects == 0) { + if (end_of_foreach == 0) { + input_err(true, ts->dev, "%s: Invalid end_foreach\n", __func__); + return 0; + } + idx = end_of_foreach; + } + break; + case TOUCH_REPORT_0D_BUTTONS_STATE: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, bits, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get 0D buttons state\n", __func__); + return retval; + } + touch_data->buttons_state = data; + offset += bits; + break; + case TOUCH_REPORT_GESTURE_ID: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, + report_size, offset, bits, &data); + + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get gesture id\n", __func__); + return retval; + } + + touch_data->gesture_id = data; + offset += bits; + break; + case TOUCH_REPORT_GESTURE_DATA: + bits = config_data[idx++]; + retval = synaptics_ts_get_gesture_data(ts, report, + report_size, + offset, bits, + &touch_data->gesture_data, + touch_data->gesture_id); + offset += bits; + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get gesture data\n", __func__); + return retval; + } + break; + case TOUCH_REPORT_FRAME_RATE: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, bits, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get frame rate\n", __func__); + return retval; + } + touch_data->frame_rate = data; + offset += bits; + break; + case TOUCH_REPORT_FORCE_MEASUREMENT: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, bits, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get force measurement data\n", __func__); + return retval; + } + touch_data->force_data = data; + offset += bits; + break; + case TOUCH_REPORT_FINGERPRINT_AREA_MEET: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, bits, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get data for fingerprint area\n", __func__); + return retval; + } + touch_data->fingerprint_area_meet = data; + offset += bits; + break; + case TOUCH_REPORT_POWER_IM: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, bits, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get power IM\n", __func__); + return retval; + } + touch_data->power_im = data; + offset += bits; + break; + case TOUCH_REPORT_CID_IM: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, bits, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get CID IM\n", __func__); + return retval; + } + touch_data->cid_im = data; + offset += bits; + break; + case TOUCH_REPORT_RAIL_IM: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, bits, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get rail IM\n", __func__); + return retval; + } + touch_data->rail_im = data; + offset += bits; + break; + case TOUCH_REPORT_CID_VARIANCE_IM: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, bits, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get CID variance IM\n", __func__); + return retval; + } + touch_data->cid_variance_im = data; + offset += bits; + break; + case TOUCH_REPORT_NSM_FREQUENCY_INDEX: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, bits, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get NSM frequency\n", __func__); + return retval; + } + touch_data->nsm_frequency = data; + offset += bits; + break; + case TOUCH_REPORT_NSM_STATE: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, bits, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get NSM state\n", __func__); + return retval; + } + touch_data->nsm_state = data; + offset += bits; + break; + case TOUCH_REPORT_CPU_CYCLES_USED_SINCE_LAST_FRAME: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, bits, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get cpu cycles info\n", __func__); + return retval; + } + touch_data->num_of_cpu_cycles = data; + offset += bits; + break; + case TOUCH_REPORT_FACE_DETECT: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, bits, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to detect face\n", __func__); + return retval; + } + touch_data->fd_data = data; + offset += bits; + break; + case TOUCH_REPORT_SENSING_MODE: + bits = config_data[idx++]; + retval = synaptics_ts_get_touch_data(ts, report, report_size, + offset, bits, &data); + if (retval < 0) { + input_err(true, ts->dev, "%s: Fail to get sensing mode\n", __func__); + return retval; + } + touch_data->sensing_mode = data; + offset += bits; + break; + default: + + input_err(true, ts->dev, "%s: Unknown touch config code:0x%02x (length:%d)\n", __func__, + code, config_data[idx]); + bits = config_data[idx++]; + offset += bits; + break; + } + } + +exit: + return 0; +} + +/** + * syna_tcm_get_event_data() + * + * Helper to read TouchComm messages when ATTN signal is asserted. + * After returning, the ATTN signal should be no longer asserted. + * + * The 'code' returned will guide the caller on the next action. + * For example, do touch reporting once returned code is equal to REPORT_TOUCH. + * + * @param + * [ in] tcm_dev: the device handle + * [out] code: received report code + * [out] report: report data returned + * [out] tp_data: touched data returned, if 'code' is REPORT_TOUCH + * + * @return + * on success, 0 or positive value; otherwise, negative value on error. + */ +int synaptics_ts_get_event(struct synaptics_ts_data *ts, + unsigned char *code, struct synaptics_ts_buffer *report, + struct synaptics_ts_touch_data_blob *tp_data) +{ + int retval = 0; + + if (!ts) { + pr_err("%s%s:Invalid tcm device handle\n", SECLOG, __func__); + return -EINVAL; + } + + if (!code) { + input_err(true, ts->dev, "Invalid parameter\n"); + return -EINVAL; + } + + /* retrieve the event data */ + retval = ts->read_message(ts, + code); + if (retval < 0) { + input_err(true, ts->dev, "Fail to read messages\n"); + return retval; + } + + if (ts->header) { + if (ts->header->marker == SYNAPTICS_TS_V1_MESSAGE_MARKER && !ts->header->code && !ts->header->length[0] && !ts->header->length[1]) { + ts->irq_empty_count++; + if (ts->irq_empty_count >= 100) { + ts->irq_empty_count = 0; + input_info(true, ts->dev, "empty message\n"); +#if IS_ENABLED(CONFIG_SEC_ABC) + sec_abc_send_event(GET_INT_ABC_TYPE(ts->multi_dev)); +#endif + } + } + } + + synaptics_ts_buf_lock(&ts->report_buf); + + /* no perform data parsing if no tp_data provided */ + if (!tp_data) + goto report_cpy; + + /* parse touch report once received */ + if (*code == REPORT_TOUCH) { + retval = synaptics_ts_parse_touch_report(ts, + ts->report_buf.buf, + ts->report_buf.data_length, + tp_data); + if (retval < 0) + input_err(true, ts->dev, "%s: Fail to parse touch report\n", __func__); + } + +report_cpy: + /* exit if no buffer provided or no action needed */ + if ((!report) || (ts->report_buf.data_length == 0)) + goto exit; + + /* return the received report */ + if ((*code >= REPORT_IDENTIFY) && (*code != STATUS_INVALID)) { + retval = synaptics_ts_buf_copy(report, &ts->report_buf); + if (retval < 0) { + input_err(true, ts->dev, "Fail to copy report data\n"); + goto exit; + } + } + +exit: + synaptics_ts_buf_unlock(&ts->report_buf); + + return retval; +} + +void synaptics_ts_send_rawdata(struct synaptics_ts_data *ts, u8 *data) { + s16 *val; + int ii, jj, kk = 0; + int length = ts->cols * ts->rows * 2 + (POSTFIX_BYTE_LENGTH + PREFIX_BYTE_LENGTH); + int press = ts->plat_data->coord[0].action == 0 ? 3 : ts->plat_data->coord[0].action; + + if (!ts->raw) + ts->raw = kzalloc(IOCTL_SIZE, GFP_KERNEL); + + val = (s16 *)data; + for (ii = 0; ii < ts->rows; ii++) { + for (jj = 0; jj < ts->cols; jj++) + ts->raw[4 + jj * ts->rows + ii] = val[kk++]; + } + sec_input_rawdata_copy_to_user(ts->raw, length, press); +} + +irqreturn_t synaptics_ts_irq_thread(int irq, void *ptr) +{ + struct synaptics_ts_data *ts = (struct synaptics_ts_data *)ptr; + unsigned char event_id; + int ret; + int curr_pos; + u8 *event_buff; + int remain_event_count; + u8 *raw; + bool is_raw_event = false; + + ret = event_id = curr_pos = remain_event_count = 0; + +#if IS_ENABLED(CONFIG_INPUT_SEC_SECURE_TOUCH) + if (synaptics_secure_filter_interrupt(ts) == IRQ_HANDLED) { + wait_for_completion_interruptible_timeout(&ts->plat_data->secure_interrupt, + msecs_to_jiffies(5 * MSEC_PER_SEC)); + + input_info(true, ts->dev, + "%s: secure interrupt handled\n", __func__); + + return IRQ_HANDLED; + } +#endif +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) + if (STUI_MODE_TOUCH_SEC & stui_get_mode()) + return IRQ_HANDLED; +#endif + +#if !IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP) + /* for vendor */ + if (ts->vendor_data->is_attn_redirecting) + syna_cdev_redirect_attn(ts); +#endif + + ret = sec_input_handler_start(ts->dev); + if (ret < 0) + return IRQ_HANDLED; + + ret = synaptics_ts_get_event(ts, &event_id, &ts->event_data, &ts->tp_data); + if (ret < 0) + return IRQ_HANDLED; + + if (ts->event_data.data_length == 0) { + input_err(true, ts->dev, "%s:data_length %d\n", __func__, ts->event_data.data_length); + return IRQ_HANDLED; + } + + mutex_lock(&ts->eventlock); + + // don't need to dispatch the response of command + if (event_id < 0x10) + goto exit; + + if (ts->plat_data->support_rawdata && event_id == REPORT_SEC_COORDINATE_RAWDATA_EVENT) + is_raw_event = true; + + if (event_id == REPORT_SEC_COORDINATE_EVENT || event_id == REPORT_SEC_COORDINATE_RAWDATA_EVENT) { + if (ts->prev_event_id != event_id) + input_info(true, ts->dev, "%s: event id is changed(%02X -> %02X)\n", __func__, ts->prev_event_id, event_id); + ts->prev_event_id = event_id; + } + + if (is_raw_event) { + remain_event_count = (ts->event_data.buf[3] << 8 | ts->event_data.buf[2]) / 8; + raw = &ts->event_data.buf[4 + remain_event_count * sizeof(struct sec_touch_event_data) + 2]; + } else { + remain_event_count = ts->event_data.data_length / sizeof(struct sec_touch_event_data); + } + + do { + if (is_raw_event) { + event_buff = &ts->event_data.buf[4 + curr_pos * sizeof(struct sec_touch_event_data)]; + } else { + event_buff = &ts->event_data.buf[curr_pos * sizeof(struct sec_touch_event_data)]; + } +// event_id = event_buff[0] & 0x3; + if (ts->debug_flag & SEC_TS_DEBUG_PRINT_ALLEVENT) + input_info(true, ts->dev, "%s: ALL event_id(%02X) %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n", + __func__, event_id, event_buff[0], event_buff[1], event_buff[2], event_buff[3], event_buff[4], event_buff[5], + event_buff[6], event_buff[7], event_buff[8], event_buff[9], event_buff[10], event_buff[11], + event_buff[12], event_buff[13], event_buff[14], event_buff[15]); + + if (event_id == REPORT_SEC_STATUS_EVENT) + synaptics_ts_status_event(ts, event_buff); + else if (event_id == REPORT_SEC_COORDINATE_EVENT || is_raw_event) + synaptics_ts_coordinate_event(ts, event_buff); + else if (event_id == REPORT_SEC_SPONGE_GESTURE) + synaptics_ts_gesture_event(ts, event_buff); + else if (event_id == STATUS_OK) + input_dbg(false, ts->dev, "%s: response event event_id(%02X)\n", __func__, event_id); + else + input_info(true, ts->dev, + "%s: unknown event event_id(%02X) %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n", + __func__, event_id, event_buff[0], event_buff[1], event_buff[2], event_buff[3], event_buff[4], event_buff[5], + event_buff[6], event_buff[7], event_buff[8], event_buff[9], event_buff[10], event_buff[11], + event_buff[12], event_buff[13], event_buff[14], event_buff[15]); + curr_pos++; + remain_event_count--; + } while (remain_event_count > 0); + + sec_input_coord_event_sync_slot(ts->dev); + +exit: + if (!mutex_is_locked((struct mutex *)&ts->msg_data.cmd_mutex) && + !mutex_is_locked(&ts->plat_data->enable_mutex)) + schedule_work(&ts->set_temperature_work.work); + + mutex_unlock(&ts->eventlock); + +#if IS_ENABLED(CONFIG_SEC_INPUT_RAWDATA) + if (is_raw_event) { + synaptics_ts_send_rawdata(ts, raw); + } +#endif + return IRQ_HANDLED; +} + + +MODULE_LICENSE("GPL"); diff --git a/drivers/input/sec_input/synaptics/synaptics_reg.h b/drivers/input/sec_input/synaptics/synaptics_reg.h new file mode 100644 index 000000000000..08bc19bc541f --- /dev/null +++ b/drivers/input/sec_input/synaptics/synaptics_reg.h @@ -0,0 +1,586 @@ +/* SPDX-License-Identifier: GPL-2.0 + * Copyright (C) 2022 Samsung Electronics Co., Ltd. + * + * 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. + */ + +#ifndef _LINUX_SYNAPTICS_TS_REG_H_ +#define _LINUX_SYNAPTICS_TS_REG_H_ + +/* drivers/input/sec_input/stm/stm_reg.h + * + * Copyright (C) 2020 Samsung Electronics Co., Ltd. + * + * Core file for Samsung TSC driver + * + * 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. + */ + +#define USE_POR_AFTER_I2C_RETRY + +#define BRUSH_Z_DATA 63 /* for ArtCanvas */ + +#define TOUCH_PRINT_INFO_DWORK_TIME 30000 +#define TOUCH_RESET_DWORK_TIME 10 +#define TOUCH_POWER_ON_DWORK_TIME 70 + +#define SYNA_TCM_CORE_LIB_VERSION 0x010B + +#define FIRMWARE_IC "SYNAPTICS" +#define SYNAPTICS_TS_MAX_FW_PATH 64 +#define SYNAPTICS_TS_TS_DRV_NAME "SYNAPTICS_TS_TOUCH" +#define SYNAPTICS_TS_TS_DRV_VERSION "0100" + + + /** + * @section: Data Comparison helpers + * + * @brief: MAX + * Find the maximum value between + * + * @brief: MIN: + * Find the minimum value between + * + * @brief: GET_BIT + * Return the value of target bit + */ +#define MAX(a, b) \ + ({__typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a > _b ? _a : _b; }) + +#define MIN(a, b) \ + ({__typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a < _b ? _a : _b; }) + +#define GET_BIT(var, pos) \ + (((var) & (1 << (pos))) >> (pos)) +#define GET_BIT_LSB(var, pos) \ + (((var) & (1 << (pos))) >> (pos)) +#define GET_BIT_MSB(var, pos) \ + (((var) & (1 << (7 - pos))) >> (7 - pos)) + + /** + * @section: C Atomic operations + * + * @brief: ATOMIC_SET + * Set an atomic data + * + * @brief: ATOMIC_GET: + * Get an atomic data + */ + typedef atomic_t syna_pal_atomic_t; + +#define ATOMIC_SET(atomic, value) \ + atomic_set(&atomic, value) + +#define ATOMIC_GET(atomic) \ + atomic_read(&atomic) + + +/** + * @section: Parameters pre-defined + * + * @brief: MAX_NUM_OBJECTS + * Maximum number of objects being detected + * + * @brief: MAX_SIZE_GESTURE_DATA + * Maximum size of gesture data + * + * @brief: MAX_SIZE_CONFIG_ID + * Maximum size of customer configuration ID + */ +#define MAX_NUM_OBJECTS (10) + +#define MAX_SIZE_GESTURE_DATA (8) + +#define MAX_SIZE_CONFIG_ID (16) + +/** + * @section: Command-handling relevant definitions + * + * @brief: MESSAGE_HEADER_SIZE + * The size of message header + * + * @brief: CMD_RESPONSE_TIMEOUT_MS + * Time frame for a command execution + * + * @brief: CMD_RESPONSE_POLLING_DELAY_MS + * Generic time frame to check the response in polling + * + * @brief: RD_RETRY_US + * For retry reading, delay time range in microsecond + * + * @brief: WR_DELAY_US + * For continued writes, delay time range in microsecond + * + * @brief: TAT_DELAY_US + * For bus turn-around, delay time range in microsecond + * + * @brief: FW_MODE_SWITCH_DELAY_MS + * The default time for fw mode switching + * + * @brief: RESET_DELAY_MS + * The default time after reset in case it's not set properly + * + * @brief: FORCE_ATTN_DRIVEN + * Special flag to read in resp packet in ISR function + */ +#define SYNAPTICS_TS_MESSAGE_HEADER_SIZE (4) + +#define CMD_RESPONSE_TIMEOUT_MS (5000) +#define CMD_RESPONSE_SHORT_TIMEOUT_MS (1000) + +#define CMD_RESPONSE_POLLING_DELAY_MS (10) + +#define RD_RETRY_US_MIN (5000) +#define RD_RETRY_US_MAX (10000) + +#define WR_DELAY_US_MIN (1000)//(500) +#define WR_DELAY_US_MAX (1000) + +#define MSG_DELAY_US_MIN (100)//(50) +#define MSG_DELAY_US_MAX (100) + +#define FW_MODE_SWITCH_DELAY_MS (200) + +#define RESET_DELAY_MS (300) + +#define FORCE_ATTN_DRIVEN (~0) + + +/** + * @section: Macro to show string in log + */ +#define STR(x) #x + +/** + * @section: Helpers to check the device mode + */ +#define IS_APP_FW_MODE(mode) \ + (mode == SYNAPTICS_TS_MODE_APPLICATION_FIRMWARE) + +#define IS_NOT_APP_FW_MODE(mode) \ + (!IS_APP_FW_MODE(mode)) + +#define IS_BOOTLOADER_MODE(mode) \ + ((mode == SYNAPTICS_TS_MODE_BOOTLOADER) || \ + (mode == SYNAPTICS_TS_MODE_TDDI_BOOTLOADER) || \ + (mode == SYNAPTICS_TS_MODE_TDDI_HDL_BOOTLOADER) || \ + (mode == SYNAPTICS_TS_MODE_MULTICHIP_TDDI_BOOTLOADER)) + +#define IS_ROM_BOOTLOADER_MODE(mode) \ + (mode == SYNAPTICS_TS_MODE_ROMBOOTLOADER) + +#define SYNAPTICS_TS_BITS_IN_MESSAGE_HEADER (SYNAPTICS_TS_MESSAGE_HEADER_SIZE * 8) + +#define HOST_PRIMARY (0) + +#define COMMAND_RETRY_TIMES (5) + + +/** + * @section: Types for lower-level bus being used + */ +enum bus_connection { + BUS_TYPE_NONE, + BUS_TYPE_I2C, + BUS_TYPE_SPI, + BUS_TYPE_I3C, +}; + +#define SYNAPTICS_TS_V1_MESSAGE_MARKER 0xa5 +#define SYNAPTICS_TS_V1_MESSAGE_PADDING 0x5a + + +/** + * @section: TouchComm Firmware Modes + * + * The current mode running is defined in Identify Info Packet. + */ +enum synaptics_ts_firmware_mode { + SYNAPTICS_TS_MODE_UNKNOWN = 0x00, + SYNAPTICS_TS_MODE_APPLICATION_FIRMWARE = 0x01, + SYNAPTICS_TS_MODE_HOSTDOWNLOAD_FIRMWARE = 0x02, + SYNAPTICS_TS_MODE_ROMBOOTLOADER = 0x04, + SYNAPTICS_TS_MODE_BOOTLOADER = 0x0b, + SYNAPTICS_TS_MODE_TDDI_BOOTLOADER = 0x0c, + SYNAPTICS_TS_MODE_TDDI_HDL_BOOTLOADER = 0x0d, + SYNAPTICS_TS_MODE_PRODUCTIONTEST_FIRMWARE = 0x0e, + SYNAPTICS_TS_MODE_MULTICHIP_TDDI_BOOTLOADER = 0xab, +}; + +/** + * @section: Status of Application Firmware + * + * The current status is defined in Application Info Packet. + */ +enum tcm_app_status { + APP_STATUS_OK = 0x00, + APP_STATUS_BOOTING = 0x01, + APP_STATUS_UPDATING = 0x02, + APP_STATUS_BAD_APP_CONFIG = 0xff, +}; + +/** + * @section: Field IDs in Dynamic Configuration + * + * The codes specify the generic dynamic configuration options. + */ +enum dynamic_tcm_config_id { + DC_UNKNOWN = 0x00, + DC_DISABLE_NOISE_MITIGATION = 0x02, + DC_DISABLE_FREQUENCY_SHIFT = 0x03, + DC_REQUEST_FREQUENCY_INDEX = 0x04, + DC_DISABLE_HSYNC = 0x05, + DC_REZERO_ON_EXIT_DEEP_SLEEP = 0x06, + DC_ENABLE_CHARGER_CONNECTED = 0x07, + DC_DISABLE_BASELINE_RELAXATION = 0x08, + DC_ENABLE_WAKEUP_GESTURE_MODE = 0x09, + DC_REQUEST_TESTING_FINGERS = 0x0a, + DC_ENABLE_GRIP_SUPPRESSION = 0x0b, + DC_ENABLE_THICK_GLOVE = 0x0c, + DC_ENABLE_GLOVE = 0x0d, + DC_ENABLE_FACE_DETECTION = 0x0e, + DC_INHIBIT_ACTIVE_GESTURE = 0x0f, + DC_DISABLE_PROXIMITY = 0x11, + DC_DISABLE_DOZE = 0xc4, + DC_ENABLE_FOD_INT = 0xd2, + DC_ENABLE_DEADZONE = 0xda, + DC_ENABLE_COVER = 0xd7, + DC_ENABLE_GAMEMODE = 0xd8, + DC_ENABLE_SIPMODE = 0xd9, + DC_ENABLE_HIGHSENSMODE = 0xdb, + DC_ENABLE_NOTEMODE = 0xdc, + DC_SET_SCANRATE = 0xdf, + DC_ENABLE_COVERTYPE = 0xe0, + DC_ENABLE_WIRELESS_CHARGER = 0xe2, + DC_ENABLE_EXTENDED_TOUCH_REPORT_CONTROL = 0xf9, +}; + +enum custom_dynamic_config_id { + DC_TSP_SNR_TEST_FRAMES = 0xe1, + DC_TSP_SET_TEMP = 0xe4, + DC_TSP_ENABLE_POCKET_MODE = 0xe5, + DC_TSP_ENABLE_LOW_SENSITIVITY_MODE = 0xe6, +}; + + +/** + * @section: TouchComm Commands + * + * List the generic commands supported in TouchComm command-response protocol. + */ +enum tcm_command { + SYNAPTICS_TS_CMD_NONE = 0x00, + SYNAPTICS_TS_CMD_CONTINUE_WRITE = 0x01, + SYNAPTICS_TS_CMD_IDENTIFY = 0x02, + SYNAPTICS_TS_CMD_RESET = 0x04, + SYNAPTICS_TS_CMD_ENABLE_REPORT = 0x05, + SYNAPTICS_TS_CMD_DISABLE_REPORT = 0x06, + SYNAPTICS_TS_CMD_TCM2_ACK = 0x07, + SYNAPTICS_TS_CMD_TCM2_RETRY = 0x08, + SYNAPTICS_TS_CMD_TCM2_SET_MAX_READ_LENGTH = 0x09, + SYNAPTICS_TS_CMD_TCM2_GET_REPORT = 0x0a, + SYNAPTICS_TS_CMD_GET_BOOT_INFO = 0x10, + SYNAPTICS_TS_CMD_ERASE_FLASH = 0x11, + SYNAPTICS_TS_CMD_WRITE_FLASH = 0x12, + SYNAPTICS_TS_CMD_READ_FLASH = 0x13, + SYNAPTICS_TS_CMD_RUN_APPLICATION_FIRMWARE = 0x14, + SYNAPTICS_TS_CMD_SPI_MASTER_WRITE_THEN_READ = 0x15, + SYNAPTICS_TS_CMD_REBOOT_TO_ROM_BOOTLOADER = 0x16, + SYNAPTICS_TS_CMD_RUN_BOOTLOADER_FIRMWARE = 0x1f, + SYNAPTICS_TS_CMD_GET_APPLICATION_INFO = 0x20, + SYNAPTICS_TS_CMD_GET_STATIC_CONFIG = 0x21, + SYNAPTICS_TS_CMD_SET_STATIC_CONFIG = 0x22, + SYNAPTICS_TS_CMD_GET_DYNAMIC_CONFIG = 0x23, + SYNAPTICS_TS_CMD_SET_DYNAMIC_CONFIG = 0x24, + SYNAPTICS_TS_CMD_GET_TOUCH_REPORT_CONFIG = 0x25, + SYNAPTICS_TS_CMD_SET_TOUCH_REPORT_CONFIG = 0x26, + SYNAPTICS_TS_CMD_REZERO = 0x27, + SYNAPTICS_TS_CMD_COMMIT_CONFIG = 0x28, + SYNAPTICS_TS_CMD_DESCRIBE_DYNAMIC_CONFIG = 0x29, + SYNAPTICS_TS_CMD_PRODUCTION_TEST = 0x2a, + SYNAPTICS_TS_CMD_SET_CONFIG_ID = 0x2b, + SYNAPTICS_TS_CMD_ENTER_DEEP_SLEEP = 0x2c, + SYNAPTICS_TS_CMD_EXIT_DEEP_SLEEP = 0x2d, + SYNAPTICS_TS_CMD_GET_TOUCH_INFO = 0x2e, + SYNAPTICS_TS_CMD_GET_DATA_LOCATION = 0x2f, + SYNAPTICS_TS_CMD_DOWNLOAD_CONFIG = 0x30, + SYNAPTICS_TS_CMD_ENTER_PRODUCTION_TEST_MODE = 0x31, + SYNAPTICS_TS_CMD_GET_FEATURES = 0x32, + SYNAPTICS_TS_CMD_GET_ROMBOOT_INFO = 0x40, + SYNAPTICS_TS_CMD_WRITE_PROGRAM_RAM = 0x41, + SYNAPTICS_TS_CMD_ROMBOOT_RUN_BOOTLOADER_FIRMWARE = 0x42, + SYNAPTICS_TS_CMD_SPI_MASTER_WRITE_THEN_READ_EXTENDED = 0x43, + SYNAPTICS_TS_CMD_ENTER_IO_BRIDGE_MODE = 0x44, + SYNAPTICS_TS_CMD_ROMBOOT_DOWNLOAD = 0x45, +}; + +/** + * @section: TouchComm Status Codes + * + * Define the following status codes for all command responses. + * 0x00: (v1) no commands are pending and no reports are available. + * 0x01: (v1 & v2) the previous command succeeded. + * 0x03: (v1 & v2) the payload continues a previous response. + * 0x04: (v2) command was written, but no reports were available. + * 0x07: (v2) the previous write was successfully received. + * 0x08: (v2) the previous write was corrupt. The host should resend. + * 0x09: (v2) the previous command failed. + * 0x0c: (v1 & v2) write was larger than the device's receive buffer. + * 0x0d: (v1 & v2) a command was sent before the previous command completed. + * 0x0e: (v1 & v2) the requested command is not implemented. + * 0x0f: (v1 & v2) generic communication error, probably incorrect payload. + * + * 0xfe: self-defined status for a corrupted packet. + * 0xff: self-defined status for an invalid data. + */ +enum tcm_status_code { + STATUS_IDLE = 0x00, + STATUS_OK = 0x01, + STATUS_CONTINUED_READ = 0x03, + STATUS_NO_REPORT_AVAILABLE = 0x04, + STATUS_BAD_COMMAND = 0x06, + STATUS_ACK = 0x07, + STATUS_RETRY_REQUESTED = 0x08, + STATUS_CMD_FAILED = 0x09, + STATUS_RECEIVE_BUFFER_OVERFLOW = 0x0c, + STATUS_PREVIOUS_COMMAND_PENDING = 0x0d, + STATUS_NOT_IMPLEMENTED = 0x0e, + STATUS_ERROR = 0x0f, + STATUS_PACKET_CORRUPTED = 0xfe, + STATUS_INVALID = 0xff, +}; + +/** + * @section: Types of Object Reported + * + * List the object classifications + */ +enum object_classification { + LIFT = 0, + FINGER = 1, + GLOVED_OBJECT = 2, + STYLUS = 3, + ERASER = 4, + SMALL_OBJECT = 5, + PALM = 6, + EDGE_TOUCHED = 8, + HOVER_OBJECT = 9, + NOP = -1, +}; + +/** + * @section: Types of Gesture ID + * + * List the gesture ID assigned + */ +enum gesture_classification { + GESTURE_ID_NONE = 0, + GESTURE_ID_DOUBLE_TAP = 1, + GESTURE_ID_SWIPE = 2, + GESTURE_ID_ACTIVE_TAP_AND_HOLD = 7, +}; + + +/** + * @section: Codes for Touch Report Configuration + * + * Define the 8-bit codes for the touch report configuration + */ +enum touch_report_code { + /* control flow codes */ + TOUCH_REPORT_END = 0x00, + TOUCH_REPORT_FOREACH_ACTIVE_OBJECT = 0x01, + TOUCH_REPORT_FOREACH_OBJECT = 0x02, + TOUCH_REPORT_FOREACH_END = 0x03, + TOUCH_REPORT_PAD_TO_NEXT_BYTE = 0x04, + /* entity codes */ + TOUCH_REPORT_TIMESTAMP = 0x05, + TOUCH_REPORT_OBJECT_N_INDEX = 0x06, + TOUCH_REPORT_OBJECT_N_CLASSIFICATION = 0x07, + TOUCH_REPORT_OBJECT_N_X_POSITION = 0x08, + TOUCH_REPORT_OBJECT_N_Y_POSITION = 0x09, + TOUCH_REPORT_OBJECT_N_Z = 0x0a, + TOUCH_REPORT_OBJECT_N_X_WIDTH = 0x0b, + TOUCH_REPORT_OBJECT_N_Y_WIDTH = 0x0c, + TOUCH_REPORT_OBJECT_N_TX_POSITION_TIXELS = 0x0d, + TOUCH_REPORT_OBJECT_N_RX_POSITION_TIXELS = 0x0e, + TOUCH_REPORT_0D_BUTTONS_STATE = 0x0f, + TOUCH_REPORT_GESTURE_ID = 0x10, + TOUCH_REPORT_FRAME_RATE = 0x11, + TOUCH_REPORT_POWER_IM = 0x12, + TOUCH_REPORT_CID_IM = 0x13, + TOUCH_REPORT_RAIL_IM = 0x14, + TOUCH_REPORT_CID_VARIANCE_IM = 0x15, + TOUCH_REPORT_NSM_FREQUENCY_INDEX = 0x16, + TOUCH_REPORT_NSM_STATE = 0x17, + TOUCH_REPORT_NUM_OF_ACTIVE_OBJECTS = 0x18, + TOUCH_REPORT_CPU_CYCLES_USED_SINCE_LAST_FRAME = 0x19, + TOUCH_REPORT_FACE_DETECT = 0x1a, + TOUCH_REPORT_GESTURE_DATA = 0x1b, + TOUCH_REPORT_FORCE_MEASUREMENT = 0x1c, + TOUCH_REPORT_FINGERPRINT_AREA_MEET = 0x1d, + TOUCH_REPORT_SENSING_MODE = 0x1e, + TOUCH_REPORT_KNOB_DATA = 0x24, +}; + + +/** + * @section: TouchComm Report Codes + * + * Define the following report codes generated by TouchComm firmware. + * 0x10: Identify Info Packet + * 0x11: Touch Report + * 0x12: Delta Cap. Image + * 0x13: Raw Cap. Image + */ +enum tcm_report_type { + REPORT_IDENTIFY = 0x10, + REPORT_TOUCH = 0x11, + REPORT_DELTA = 0x12, + REPORT_RAW = 0x13, +}; +/** + * @brief: SEC Custom Commands, Reports, or Events + */ +enum custom_command { + CMD_BSC_DO_CALIBRATION = 0x33, + CMD_READ_SEC_SPONGE_REG = 0xc0, + CMD_WRITE_SEC_SPONGE_REG = 0xc1, + CMD_EVENT_BUFFER_CLEAR = 0xc2, + CMD_GET_FACE_AREA = 0xc3, + CMD_SET_AGING_MODE_ON = 0xc4, + CMD_SET_AGING_MODE_OFF = 0xc5, + CMD_DBG_ATTN = 0xc7, + CMD_SET_GRIP = 0xe6, + CMD_ACCESS_CALIB_DATA_FROM_NVM = 0xe7, + CMD_SET_IMMEDIATE_DYNAMIC_CONFIG = 0xe8, +}; + +enum custom_report_type { + REPORT_SEC_SPONGE_GESTURE = 0xc0, + REPORT_SEC_COORDINATE_EVENT = 0xc1, + REPORT_SEC_STATUS_EVENT = 0xc2, + REPORT_SEC_COORDINATE_RAWDATA_EVENT = 0xc3, +}; + +/** + * @section: States in Command Processing + * + * List the states in command processing. + */ +enum tcm_command_status { + CMD_STATE_IDLE = 0, + CMD_STATE_BUSY = 1, + CMD_STATE_ERROR = -1, +}; + +/** + * @section: Production Test Items + * + * List the generic production test items + */ +enum tcm_test_code { + TEST_NOT_IMPLEMENTED = 0x00, + TEST_PID01_TRX_SHORTS = 0x01, + TEST_PID02_TRX_SENSOR_OPENS = 0x02, + TEST_PID03_TRX_GROUND_SHORTS = 0x03, + TEST_PID05_FULL_RAW_CAP = 0x05, + TEST_PID06_EE_SHORT = 0x06, + TEST_PID07_DYNAMIC_RANGE = 0x07, + TEST_PID08_HIGH_RESISTANCE = 0x08, + TEST_PID10_DELTA_NOISE = 0x0a, + TEST_PID11_OPEN_DETECTION = 0x0b, + TEST_PID12 = 0x0c, + TEST_PID13 = 0x0d, + TEST_PID14_DOZE_DYNAMIC_RANGE = 0x0e, + TEST_PID15_DOZE_NOISE = 0x0f, + TEST_PID16_SENSOR_SPEED = 0x10, + TEST_PID17_ADC_RANGE = 0x11, + TEST_PID18_HYBRID_ABS_RAW = 0x12, + TEST_PID22_TRANS_RAW_CAP = 0x16, + TEST_PID25_TRANS_TRX_SHORT = 0x19, + TEST_PID29_HYBRID_ABS_NOISE = 0x1D, + TEST_PID30_BSC_CALIB_TEST = 0x1E, + TEST_PID52_JITTER_DELTA_TEST = 0x34, + TEST_PID53_SRAM_TEST = 0x35, + TEST_PID54_BSC_DIFF_TEST = 0x36, + TEST_PID56_TSP_SNR_NONTOUCH = 0x38, + TEST_PID57_TSP_SNR_TOUCH = 0x39, + TEST_PID58_TRX_OPEN = 0x3A, + TEST_PID59_PATTERN_SHORT = 0x3B, + TEST_PID60_SENSITIVITY = 0x3C, + TEST_PID61_MISCAL = 0x3D, + TEST_PID65_MISCALDATA_NORMAL = 0x41, + TEST_PID66_MISCALDATA_NOISE = 0x42, + TEST_PID67_MISCALDATA_WET = 0x43, + TEST_PID82_CUSTOMER_DATA_CHECK = 0x52, + TEST_PID198_PROX_INTENSITY = 0xC6, + TEST_PID_MAX, +}; + +/* SYNAPTICS_TS_GESTURE_TYPE */ +#define SYNAPTICS_TS_GESTURE_CODE_SWIPE 0x00 +#define SYNAPTICS_TS_GESTURE_CODE_DOUBLE_TAP 0x01 +#define SYNAPTICS_TS_GESTURE_CODE_PRESS 0x03 +#define SYNAPTICS_TS_GESTURE_CODE_SINGLE_TAP 0x04 +#define SYNAPTICS_TS_GESTURE_CODE_DUMPFLUSH 0x05 + +/* SYNAPTICS_TS_GESTURE_ID */ +#define SYNAPTICS_TS_GESTURE_ID_AOD 0x00 +#define SYNAPTICS_TS_GESTURE_ID_DOUBLETAP_TO_WAKEUP 0x01 +#define SYNAPTICS_TS_GESTURE_ID_FOD_LONG 0 +#define SYNAPTICS_TS_GESTURE_ID_FOD_NORMAL 1 +#define SYNAPTICS_TS_GESTURE_ID_FOD_RELEASE 2 +#define SYNAPTICS_TS_GESTURE_ID_FOD_OUT 3 +#define SYNAPTICS_TS_GESTURE_ID_FOD_VI 4 + +#define SYNAPTICS_TS_TOUCHTYPE_NORMAL 0 +#define SYNAPTICS_TS_TOUCHTYPE_HOVER 1 +#define SYNAPTICS_TS_TOUCHTYPE_FLIPCOVER 2 +#define SYNAPTICS_TS_TOUCHTYPE_GLOVE 3 +#define SYNAPTICS_TS_TOUCHTYPE_STYLUS 4 +#define SYNAPTICS_TS_TOUCHTYPE_PALM 5 +#define SYNAPTICS_TS_TOUCHTYPE_WET 6 +#define SYNAPTICS_TS_TOUCHTYPE_PROXIMITY 7 +#define SYNAPTICS_TS_TOUCHTYPE_JIG 8 + +/* status event type */ +#define SYNAPTICS_TS_STATUS_TYPE_ERROR 1 +#define SYNAPTICS_TS_STATUS_TYPE_INFO 2 +#define SYNAPTICS_TS_STATUS_TYPE_VENDOR 7 + +/* status event id: error */ +#define SYNAPTICS_TS_STATUS_EVENT_ERROR_CORE 0 +#define SYNAPTICS_TS_STATUS_EVENT_ERROR_OVERFLOW 1 +#define SYNAPTICS_TS_STATUS_EVENT_ERROR_ESD 2 + +/* status event id: info */ +#define SYNAPTICS_TS_STATUS_EVENT_INFO_READY_STATUS 0 +#define SYNAPTICS_TS_STATUS_EVENT_INFO_WET 1 +#define SYNAPTICS_TS_STATUS_EVENT_INFO_NOISE 2 +#define SYNAPTICS_TS_STATUS_EVENT_INFO_JITTER 3 +#define SYNAPTICS_TS_STATUS_EVENT_INFO_XENOSENSOR 4 +#define SYNAPTICS_TS_STATUS_EVENT_INFO_LIGHTSENSOR 5 + +/* status event id: vendor */ +#define SYNAPTICS_TS_STATUS_EVENT_VENDOR_PROXIMITY 0x6A +#define SYNAPTICS_TS_STATUS_EVENT_VENDOR_SYNC_STATUS 0x70 +#define SYNAPTICS_TS_STATUS_EVENT_VENDOR_REZERO_REASON 0x71 +#define SYNAPTICS_TS_STATUS_EVENT_VENDOR_NSM_STATUS 0x72 +#define SYNAPTICS_TS_STATUS_EVENT_VENDOR_BASELINE_ERROR 0x73 +#define SYNAPTICS_TS_STATUS_EVENT_VENDOR_NSM_FREQ 0x74 +#define SYNAPTICS_TS_STATUS_EVENT_VENDOR_CHARGER_CON 0x75 + +/* SEC_TS_DUMP_ID */ +#define SYNAPTICS_TS_SPONGE_DUMP_0 0x00 +#define SYNAPTICS_TS_SPONGE_DUMP_1 0x01 + +#endif diff --git a/drivers/input/sec_input/synaptics/synaptics_spi.c b/drivers/input/sec_input/synaptics/synaptics_spi.c new file mode 100644 index 000000000000..d70f9d1b5e2a --- /dev/null +++ b/drivers/input/sec_input/synaptics/synaptics_spi.c @@ -0,0 +1,688 @@ +/* drivers/input/sec_input/synaptics/synaptics_core.c + * + * Copyright (C) 2020 Samsung Electronics Co., Ltd. + * + * Core file for Samsung TSC driver + * + * 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. + */ + +#include "synaptics_dev.h" +#include "synaptics_reg.h" + +#define RD_CHUNK_SIZE_SPI (1024) +#define WR_CHUNK_SIZE_SPI (1024) + +static unsigned char *rx_buf; +static unsigned char *tx_buf; + +static unsigned int buf_size; + +static struct spi_transfer *xfer; + +static int synaptics_ts_spi_alloc_mem(struct spi_device *client, unsigned int size) +{ + if (!xfer) { + xfer = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); + if (!xfer) + return -ENOMEM; + } else { + memset(xfer, 0, sizeof(struct spi_transfer)); + } + + if (size > buf_size) { + if (rx_buf) { + kfree((void *)rx_buf); + rx_buf = NULL; + } + if (tx_buf) { + kfree((void *)tx_buf); + tx_buf = NULL; + } + + rx_buf = kzalloc(size, GFP_KERNEL); + if (!rx_buf) { + buf_size = 0; + return -ENOMEM; + } + tx_buf = kzalloc(size, GFP_KERNEL); + if (!tx_buf) { + buf_size = 0; + return -ENOMEM; + } + + buf_size = size; + } else { + memset(rx_buf, 0, size); + memset(tx_buf, 0, size); + } + + return 0; +} + +static int synaptics_ts_spi_write(struct synaptics_ts_data *ts, u8 *reg, int cnum, u8 *data, int len) +{ + int ret; + struct spi_message msg; + struct spi_device *spi = ts->client; + int i; + const int len_max = 0xffff; + + if (!spi) { + input_err(true, ts->dev, + "%s: Invalid bus io device.\n", __func__); + return -ENXIO; + } + + if (cnum + len >= len_max) { + input_err(true, ts->dev, + "%s: The spi buffer size is exceeded.\n", __func__); + return -ENOMEM; + } + + if (!ts->plat_data->resume_done.done) { + ret = wait_for_completion_interruptible_timeout(&ts->plat_data->resume_done, msecs_to_jiffies(500)); + if (ret <= 0) { + input_err(true, ts->dev, "%s: LPM: pm resume is not handled:%d\n", __func__, ret); + return -EIO; + } + } + + if (sec_check_secure_trusted_mode_status(ts->plat_data)) + return -EBUSY; + +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) + if (STUI_MODE_TOUCH_SEC & stui_get_mode()) + return -EBUSY; +#endif + + if (atomic_read(&ts->plat_data->power_state) == SEC_INPUT_STATE_POWER_OFF) { + input_err(true, ts->dev, "%s: POWER_STATUS : OFF\n", __func__); + return -EIO; + } + + if (atomic_read(&ts->plat_data->shutdown_called)) { + input_err(true, ts->dev, "%s shutdown was called\n", __func__); + return -EIO; + } + + mutex_lock(&ts->transfer_mutex); + + spi_message_init(&msg); + + ret = synaptics_ts_spi_alloc_mem(spi, len + cnum); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to allocate memory\n", __func__); + goto exit; + } + + memcpy(tx_buf, reg, cnum); + if (len > 0) { + if (data) { + memcpy(tx_buf + cnum, data, len); + } else { + ret = -ENOMEM; + goto exit; + } + } + + spi_message_init(&msg); + xfer[0].len = len + cnum; + xfer[0].tx_buf = tx_buf; + //xfer[0].rx_buf = rx_buf; + spi_message_add_tail(&xfer[0], &msg); + + + if (gpio_is_valid(ts->plat_data->gpio_spi_cs)) { + gpio_direction_output(ts->plat_data->gpio_spi_cs, 0); + if (gpio_get_value(ts->plat_data->gpio_spi_cs) != 0) + input_err(true, ts->dev, "%s: gpio value invalid:%d\n", + __func__, gpio_get_value(ts->plat_data->gpio_spi_cs)); + } + + if (spi->mode != SPI_MODE_0) { + input_err(true, ts->dev, "%s: spi mode is not 0, %d\n", + __func__, spi->mode); + spi->mode = SPI_MODE_0; + } + + ret = spi_sync(spi, &msg); + if (ret != 0) { + input_err(true, ts->dev, "%s: Failed to complete SPI transfer, error = %d\n", + __func__, ret); + ret = -EIO; + goto exit; + } + + if (gpio_is_valid(ts->plat_data->gpio_spi_cs)) { + gpio_direction_output(ts->plat_data->gpio_spi_cs, 1); + if (gpio_get_value(ts->plat_data->gpio_spi_cs) != 1) + input_err(true, ts->dev, "%s: gpio value invalid:%d\n", + __func__, gpio_get_value(ts->plat_data->gpio_spi_cs)); + } + + ret = len + cnum; + + if (ts->debug_flag & SEC_TS_DEBUG_PRINT_WRITE_CMD) { + unsigned char *wdata; + + wdata = kzalloc((cnum + len) * 3 + 6, GFP_KERNEL); + + for (i = 0; i < cnum; i++) { + unsigned char strbuff[6]; + + memset(strbuff, 0x00, 6); + snprintf(strbuff, 6, "%02X ", reg[i]); + strlcat(wdata, strbuff, (cnum + len) * 3 + 6); + } + + for (i = 0; i < len; i++) { + unsigned char strbuff[6]; + + memset(strbuff, 0x00, 6); + snprintf(strbuff, 6, "%02X ", data[i]); + strlcat(wdata, strbuff, (cnum + len) * 3 + 6); + } + + input_info(true, ts->dev, "%s: sec_input:spi_cmd: write(%d): %s\n", __func__, len, wdata); + kfree(wdata); + } +exit: + mutex_unlock(&ts->transfer_mutex); + + return ret; +} + +/** + * syna_spi_read() + * + * TouchCom over SPI requires the host to assert the SSB signal to address + * the device and retrieve the data. + * + * @param + * [ in] hw_if: the handle of hw interface + * [out] rd_data: buffer for storing data retrieved from device + * [ in] rd_len: number of bytes retrieved from device + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int synaptics_ts_spi_only_read(struct synaptics_ts_data *ts, u8 *data, int len) +{ + int ret; + struct spi_message msg; + struct spi_device *spi = ts->client; + int i; + const int len_max = 0xffff; + + if (!spi) { + input_err(true, ts->dev, + "%s: Invalid bus io device.\n", __func__); + return -ENXIO; + } + + if (len >= len_max) { + input_err(true, ts->dev, + "%s: The spi buffer size is exceeded.\n", __func__); + return -ENOMEM; + } + + if (!ts->plat_data->resume_done.done) { + ret = wait_for_completion_interruptible_timeout(&ts->plat_data->resume_done, msecs_to_jiffies(500)); + if (ret <= 0) { + input_err(true, ts->dev, "%s: LPM: pm resume is not handled:%d\n", __func__, ret); + return -EIO; + } + } + + if (sec_check_secure_trusted_mode_status(ts->plat_data)) + return -EBUSY; + +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) + if (STUI_MODE_TOUCH_SEC & stui_get_mode()) + return -EBUSY; +#endif + + if (atomic_read(&ts->plat_data->power_state) == SEC_INPUT_STATE_POWER_OFF) { + input_err(true, ts->dev, "%s: POWER_STATUS : OFF\n", __func__); + return -EIO; + } + + if (atomic_read(&ts->plat_data->shutdown_called)) { + input_err(true, ts->dev, "%s shutdown was called\n", __func__); + return -EIO; + } + + mutex_lock(&ts->transfer_mutex); + + spi_message_init(&msg); + + ret = synaptics_ts_spi_alloc_mem(spi, len); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to allocate memory\n", __func__); + goto exit; + } + + memset(tx_buf, 0xff, len); + xfer[0].len = len; + xfer[0].tx_buf = tx_buf; + xfer[0].rx_buf = rx_buf; + spi_message_add_tail(&xfer[0], &msg); + + if (gpio_is_valid(ts->plat_data->gpio_spi_cs)) { + gpio_direction_output(ts->plat_data->gpio_spi_cs, 0); + if (gpio_get_value(ts->plat_data->gpio_spi_cs) != 0) + input_err(true, ts->dev, "%s: gpio value invalid:%d\n", + __func__, gpio_get_value(ts->plat_data->gpio_spi_cs)); + } + + if (spi->mode != SPI_MODE_0) { + input_err(true, ts->dev, "%s: spi mode is not 0, %d\n", + __func__, spi->mode); + spi->mode = SPI_MODE_0; + } + + ret = spi_sync(spi, &msg); + if (ret != 0) { + input_err(true, ts->dev, "%s: Failed to complete SPI transfer, error = %d\n", + __func__, ret); + ret = -EIO; + goto exit; + } + + if (gpio_is_valid(ts->plat_data->gpio_spi_cs)) { + gpio_direction_output(ts->plat_data->gpio_spi_cs, 1); + if (gpio_get_value(ts->plat_data->gpio_spi_cs) != 1) + input_err(true, ts->dev, "%s: gpio value invalid:%d\n", + __func__, gpio_get_value(ts->plat_data->gpio_spi_cs)); + } + if (data) { + memcpy(data, rx_buf, len); + } else { + ret = -ENOMEM; + goto exit; + } + + if (ts->debug_flag & SEC_TS_DEBUG_PRINT_READ_CMD) { + unsigned char *rdata; + + rdata = kzalloc(len * 3 + 3, GFP_KERNEL); + for (i = 0; i < len; i++) { + unsigned char strbuff[6]; + + memset(strbuff, 0x00, 6); + snprintf(strbuff, 6, "%02X ", rx_buf[i]); + strlcat(rdata, strbuff, len * 3 + 3); + } + + input_info(true, ts->dev, "%s: sec_input:spi_cmd: only read(%d): %s\n", __func__, len, rdata); + kfree(rdata); + } + + ret = len; + +exit: + mutex_unlock(&ts->transfer_mutex); + + return ret; +} + +static int synaptics_ts_spi_read(struct synaptics_ts_data *ts, u8 *reg, int cnum, u8 *data, int len) +{ + int ret; + + ret = synaptics_ts_spi_write(ts, reg, cnum, NULL, 0); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to complete SPI transfer, error = %d\n", + __func__, ret); + return ret; + } + + ret = synaptics_ts_spi_only_read(ts, data, len); + if (ret < 0) { + input_err(true, ts->dev, "%s: Failed to complete SPI transfer, error = %d\n", + __func__, ret); + return ret; + } + + return len; +} + +static int synaptics_ts_spi_get_baredata(struct synaptics_ts_data *ts, u8 *data, int len) +{ + return ts->synaptics_ts_read_data_only(ts, data, len); +} + + +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) +extern int stui_spi_lock(struct spi_master *spi); +extern int stui_spi_unlock(struct spi_master *spi); + +int synaptics_stui_tsp_enter(void) +{ + struct synaptics_ts_data *ts = dev_get_drvdata(ptsp); + struct spi_device *client; + int ret = 0; + + if (!ts) + return -EINVAL; + + client = (struct spi_device *)ts->client; + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&ts->synaptics_input_nb, NOTIFIER_SECURE_TOUCH_ENABLE, NULL); +#endif + + disable_irq(ts->irq); + synaptics_ts_release_all_finger(ts); + + ret = stui_spi_lock(client->controller); + if (ret < 0) { + pr_err("[STUI] stui_spi_lock failed : %d\n", ret); +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&ts->synaptics_input_nb, NOTIFIER_SECURE_TOUCH_DISABLE, NULL); +#endif + enable_irq(ts->irq); + return -1; + } + + return 0; +} + +int synaptics_stui_tsp_exit(void) +{ + struct synaptics_ts_data *ts = dev_get_drvdata(ptsp); + struct spi_device *client; + int ret = 0; + + if (!ts) + return -EINVAL; + + client = (struct spi_device *)ts->client; + + ret = stui_spi_unlock(client->controller); + if (ret < 0) + pr_err("[STUI] stui_spi_unlock failed : %d\n", ret); + + enable_irq(ts->irq); + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&ts->synaptics_input_nb, NOTIFIER_SECURE_TOUCH_DISABLE, NULL); +#endif + + return ret; +} + +int synaptics_stui_tsp_type(void) +{ + return STUI_TSP_TYPE_SNAPTICS; +} +#endif + + +static int synaptics_ts_spi_init(struct spi_device *client) +{ + struct synaptics_ts_data *ts; + struct sec_ts_plat_data *pdata; + struct sec_tclm_data *tdata = NULL; + int ret = 0; + + input_info(true, &client->dev, "%s\n", __func__); + + rx_buf = NULL; + tx_buf = NULL; + xfer = NULL; + buf_size = 0;; + + ts = devm_kzalloc(&client->dev, sizeof(struct synaptics_ts_data), GFP_KERNEL); + if (!ts) { + ret = -ENOMEM; + goto error_allocate_pdata; + } + + if (client->dev.of_node) { + pdata = devm_kzalloc(&client->dev, + sizeof(struct sec_ts_plat_data), GFP_KERNEL); + + if (!pdata) { + ret = -ENOMEM; + goto error_allocate_pdata; + } + + client->dev.platform_data = pdata; + + ret = sec_input_parse_dt(&client->dev); + if (ret) { + input_err(true, &client->dev, "%s: Failed to parse dt\n", __func__); + goto error_allocate_pdata; + } + tdata = devm_kzalloc(&client->dev, + sizeof(struct sec_tclm_data), GFP_KERNEL); + if (!tdata) { + ret = -ENOMEM; + goto error_allocate_mem; + } + +#ifdef TCLM_CONCEPT + sec_tclm_parse_dt(&client->dev, tdata); +#endif + } else { + pdata = client->dev.platform_data; + if (!pdata) { + ret = -ENOMEM; + input_err(true, &client->dev, "%s: No platform data found\n", __func__); + goto error_allocate_pdata; + } + } + + pdata->pinctrl = devm_pinctrl_get(&client->dev); + if (IS_ERR(pdata->pinctrl)) + input_err(true, &client->dev, "%s: could not get pinctrl\n", __func__); + +#if !IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP) + ts->vendor_data = devm_kzalloc(&client->dev, sizeof(struct synaptics_ts_sysfs), GFP_KERNEL); + if (!ts->vendor_data) { + ret = -ENOMEM; + goto error_allocate_mem; + } +#endif + + client->irq = gpio_to_irq(pdata->irq_gpio); + input_err(true, &client->dev, "%s: pdata->irq_gpio: %d, client->irq: %d\n", __func__, pdata->irq_gpio, client->irq); + + ts->client = client; + ts->dev = &client->dev; + ts->plat_data = pdata; + ts->plat_data->bus_master = &client->controller->dev; + ts->irq = client->irq; + +#if IS_ENABLED(CONFIG_SAMSUNG_TUI) + ts->plat_data->stui_tsp_enter = synaptics_stui_tsp_enter; + ts->plat_data->stui_tsp_exit = synaptics_stui_tsp_exit; + ts->plat_data->stui_tsp_type = synaptics_stui_tsp_type; +#endif + + mutex_init(&ts->transfer_mutex); + + ts->synaptics_ts_write_data = synaptics_ts_spi_write; + ts->synaptics_ts_read_data = synaptics_ts_spi_read; + ts->synaptics_ts_read_data_only = synaptics_ts_spi_only_read; + ts->synaptics_ts_get_baredata = synaptics_ts_spi_get_baredata; + + if (!ts->synaptics_ts_write_data || !ts->synaptics_ts_read_data) { + input_err(true, &client->dev, "%s: not valid r/w operations\n", __func__); + ret = -ENOMEM; + goto error_allocate_mem; + } + + input_err(true, &client->dev, "%s: cs gpio: %d, value: %d\n", __func__, ts->plat_data->gpio_spi_cs, gpio_get_value(ts->plat_data->gpio_spi_cs)); + + ts->tdata = tdata; + if (!ts->tdata) { + ret = -ENOMEM; + goto error_allocate_mem; + } + + ts->max_rd_size = RD_CHUNK_SIZE_SPI; + ts->max_wr_size = WR_CHUNK_SIZE_SPI; + + ret = synaptics_ts_init(ts); + if (ret < 0) { + input_err(true, &client->dev, "%s: fail to do ts init\n", __func__); + goto err_init; + } + + spi_set_drvdata(client, ts); + + ts->spi_mode = 0; + + /* set up spi driver */ + switch (ts->spi_mode) { + case 0: + client->mode = SPI_MODE_0; + break; + case 1: + client->mode = SPI_MODE_1; + break; + case 2: + client->mode = SPI_MODE_2; + break; + case 3: + client->mode = SPI_MODE_3; + break; + } + + client->bits_per_word = 8; + ret = spi_setup(client); + if (ret < 0) { + input_err(true, &client->dev, "%s: fail to set up SPI protocol driver\n", __func__); + goto err_init; + } + + return 0; + +err_init: +error_allocate_mem: +error_allocate_pdata: + input_err(true, &client->dev, "%s: failed(%d)\n", __func__, ret); + input_log_fix(); + return ret; +} + +static int synaptics_ts_spi_probe(struct spi_device *client) +{ + struct synaptics_ts_data *ts; + int ret = 0; + + input_info(true, &client->dev, "%s\n", __func__); + + if (client->master->flags & SPI_MASTER_HALF_DUPLEX) { + input_err(true, &client->dev, "%s: Full duplex not supported by host\n", __func__); + return -EIO; + } + + ret = synaptics_ts_spi_init(client); + if (ret < 0) { + input_err(true, &client->dev, "%s: fail to init resource\n", __func__); + return ret; + } + + ts = spi_get_drvdata(client); + if (!ts->plat_data->work_queue_probe_enabled) + return synaptics_ts_probe(ts->dev); + + queue_work(ts->plat_data->probe_workqueue, &ts->plat_data->probe_work); + + return 0; +} + +static int synaptics_ts_dev_remove(struct spi_device *client) +{ + struct synaptics_ts_data *ts = spi_get_drvdata(client); + + synaptics_ts_remove(ts); + + if (rx_buf) { + kfree((void *)rx_buf); + rx_buf = NULL; + } + + if (tx_buf) { + kfree((void *)tx_buf); + tx_buf = NULL; + } + + if (xfer) { + kfree((void *)xfer); + xfer = NULL; + } + + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) +static void synaptics_ts_spi_remove(struct spi_device *client) +{ + synaptics_ts_dev_remove(client); +} +#else +static int synaptics_ts_spi_remove(struct spi_device *client) +{ + synaptics_ts_dev_remove(client); + + return 0; +} +#endif + +static void synaptics_ts_spi_shutdown(struct spi_device *client) +{ + struct synaptics_ts_data *ts = spi_get_drvdata(client); + + synaptics_ts_shutdown(ts); +} + +static const struct spi_device_id synaptics_ts_id[] = { + { SYNAPTICS_TS_NAME, 0 }, + { }, +}; + +#if IS_ENABLED(CONFIG_PM) +static const struct dev_pm_ops synaptics_ts_dev_pm_ops = { + .suspend = synaptics_ts_pm_suspend, + .resume = synaptics_ts_pm_resume, +}; +#endif + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id synaptics_ts_match_table[] = { + { .compatible = "synaptics,synaptics_ts",}, + { }, +}; +#else +#define synaptics_ts_match_table NULL +#endif + +static struct spi_driver synaptics_ts_driver = { + .probe = synaptics_ts_spi_probe, + .remove = synaptics_ts_spi_remove, + .shutdown = synaptics_ts_spi_shutdown, + .id_table = synaptics_ts_id, + .driver = { + .owner = THIS_MODULE, + .name = SYNAPTICS_TS_NAME, +#if IS_ENABLED(CONFIG_OF) + .of_match_table = synaptics_ts_match_table, +#endif +#if IS_ENABLED(CONFIG_PM) + .pm = &synaptics_ts_dev_pm_ops, +#endif + }, +}; + +module_spi_driver(synaptics_ts_driver); + +MODULE_SOFTDEP("pre: acpm-mfd-bus"); +MODULE_DESCRIPTION("synaptics TouchScreen driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/sec_input/synaptics/synaptics_vendor.c b/drivers/input/sec_input/synaptics/synaptics_vendor.c new file mode 100644 index 000000000000..79df4a73a6b6 --- /dev/null +++ b/drivers/input/sec_input/synaptics/synaptics_vendor.c @@ -0,0 +1,2323 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Synaptics TouchCom touchscreen driver + * + * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +/** + * @file syna_tcm2_sysfs.c + * + * This file implements cdev and ioctl interface in the reference driver. + */ + +#include + +#include "synaptics_dev.h" +#include "synaptics_reg.h" + +#define CHAR_DEVICE_MODE (0x0600) + +#define SYNAPTICS_TCM_DRIVER_ID (1 << 0) +#define SYNAPTICS_TCM_DRIVER_VERSION 0x0100 +#define SYNAPTICS_TCM_DRIVER_SUBVER 3 + +/* #define ENABLE_PID_TASK */ + +#define SIG_ATTN (46) + +/* structure for IOCTLs + */ +struct syna_ioctl_data { + unsigned int data_length; + unsigned int buf_size; + unsigned char __user *buf; +}; + +#if defined(CONFIG_COMPAT) +struct syna_tcm_ioctl_data_compat { + unsigned int data_length; + unsigned int buf_size; + compat_uptr_t __user *buf; +}; +#endif + +/* defines the IOCTLs supported + */ +#define IOCTL_MAGIC 's' + +/* Previous IOCTLs in early driver */ +#define OLD_RESET_ID (0x00) +#define OLD_SET_IRQ_MODE_ID (0x01) +#define OLD_SET_RAW_MODE_ID (0x02) +#define OLD_CONCURRENT_ID (0x03) + +#define IOCTL_OLD_RESET \ + _IO(IOCTL_MAGIC, OLD_RESET_ID) +#define IOCTL_OLD_SET_IRQ_MODE \ + _IOW(IOCTL_MAGIC, OLD_SET_IRQ_MODE_ID, int) +#define IOCTL_OLD_SET_RAW_MODE \ + _IOW(IOCTL_MAGIC, OLD_SET_RAW_MODE_ID, int) +#define IOCTL_OLD_CONCURRENT \ + _IOW(IOCTL_MAGIC, OLD_CONCURRENT_ID, int) + +/* Standard IOCTLs in TCM2 driver */ +#define STD_IOCTL_BEGIN (0x10) +#define STD_SET_PID_ID (0x11) +#define STD_ENABLE_IRQ_ID (0x12) +#define STD_RAW_READ_ID (0x13) +#define STD_RAW_WRITE_ID (0x14) +#define STD_GET_FRAME_ID (0x15) +#define STD_SEND_MESSAGE_ID (0x16) +#define STD_SET_REPORTS_ID (0x17) +#define STD_CHECK_FRAMES_ID (0x18) +#define STD_CLEAN_OUT_FRAMES_ID (0x19) + +#define IOCTL_STD_IOCTL_BEGIN \ + _IOR(IOCTL_MAGIC, STD_IOCTL_BEGIN) +#define IOCTL_STD_SET_PID \ + _IOW(IOCTL_MAGIC, STD_SET_PID_ID, struct syna_ioctl_data *) +#define IOCTL_STD_ENABLE_IRQ \ + _IOW(IOCTL_MAGIC, STD_ENABLE_IRQ_ID, struct syna_ioctl_data *) +#define IOCTL_STD_RAW_READ \ + _IOR(IOCTL_MAGIC, STD_RAW_READ_ID, struct syna_ioctl_data *) +#define IOCTL_STD_RAW_WRITE \ + _IOW(IOCTL_MAGIC, STD_RAW_WRITE_ID, struct syna_ioctl_data *) +#define IOCTL_STD_GET_FRAME \ + _IOWR(IOCTL_MAGIC, STD_GET_FRAME_ID, struct syna_ioctl_data *) +#define IOCTL_STD_SEND_MESSAGE \ + _IOWR(IOCTL_MAGIC, STD_SEND_MESSAGE_ID, struct syna_ioctl_data *) +#define IOCTL_STD_SET_REPORT_TYPES \ + _IOW(IOCTL_MAGIC, STD_SET_REPORTS_ID, struct syna_ioctl_data *) +#define IOCTL_STD_CHECK_FRAMES \ + _IOWR(IOCTL_MAGIC, STD_CHECK_FRAMES_ID, struct syna_ioctl_data *) +#define IOCTL_STD_CLEAN_OUT_FRAMES \ + _IOWR(IOCTL_MAGIC, STD_CLEAN_OUT_FRAMES_ID, struct syna_ioctl_data *) + +/* g_sysfs_dir represents the root directory of sysfs nodes being created + */ +static struct kobject *g_sysfs_dir; + +/* g_extif_mutex is used to protect the access from the userspace application + */ +static synaptics_ts_pal_mutex_t g_extif_mutex; + +/* g_cdev_buf is a temporary buffer storing the data from userspace + */ +static struct synaptics_ts_buffer g_cdev_cbuf; + +/* g_fifo_queue_mutex is used to protect the access from + * the userspace application + */ +static synaptics_ts_pal_mutex_t g_fifo_queue_mutex; + +/* The g_sysfs_io_polling_interval is used to set the polling interval + * for syna_tcm_send_command from syna_cdev_ioctl_send_message. + * It will set to the mode SYSFS_FULL_INTERRUPT for using the full + * interrupt mode. The way to update this variable is through the + * syna_cdev_ioctl_enable_irq. + */ +unsigned int g_sysfs_io_polling_interval; + +/* 3 sec buffer for 300 FPS + * consider touch report and one the other reports may be co-enabled + * at the same time, give a little buffer here + */ +#define FIFO_QUEUE_MAX_FRAMES (1200) +#define SEND_MESSAGE_HEADER_LENGTH (3) + +/* I/O mode */ +#define SYSFS_DISABLED_INTERRUPT (0) +#define SYSFS_GENERIC_INTERRUPT (1) +#define SYSFS_FULL_INTERRUPT (FORCE_ATTN_DRIVEN) + +/*Define a data structure that contains a list_head*/ +struct fifo_queue { + struct list_head next; + unsigned char *fifo_data; + unsigned int data_length; + struct timespec64 timestamp; +}; + +/** + * syna_sysfs_info_show() + * + * Attribute to show the device and driver information to the console. + * + * @param + * [ in] kobj: an instance of kobj + * [ in] attr: an instance of kobj attribute structure + * [out] buf: string buffer shown on console + * + * @return + * on success, number of characters being output; + * otherwise, negative value on error. + */ +static ssize_t syna_sysfs_info_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int retval; + unsigned int count; + struct device *p_dev; + struct kobject *p_kobj; + struct synaptics_ts_data *ts; + int i; + + p_kobj = g_sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + ts = dev_get_drvdata(p_dev); + + synaptics_ts_pal_mutex_lock(&g_extif_mutex); + + count = 0; + + if (!ts->probe_done) { + retval = snprintf(buf, PAGE_SIZE - count, + "Device is NOT connected\n"); + goto exit; + } + + retval = snprintf(buf, PAGE_SIZE - count, + "TouchComm version: %d\n", ts->id_info.version); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "Driver version: %d.%d.%d\n", + (unsigned char)(SYNAPTICS_TCM_DRIVER_VERSION >> 8), + (unsigned char)SYNAPTICS_TCM_DRIVER_VERSION, + (unsigned char)SYNAPTICS_TCM_DRIVER_SUBVER); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "Core lib version: %d.%02d\n", + (unsigned char)(SYNA_TCM_CORE_LIB_VERSION >> 8), + (unsigned char)SYNA_TCM_CORE_LIB_VERSION); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + switch (ts->id_info.mode) { + case SYNAPTICS_TS_MODE_APPLICATION_FIRMWARE: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: Application Firmware, 0x%02x\n", + ts->id_info.mode); + if (retval < 0) + goto exit; + break; + case SYNAPTICS_TS_MODE_BOOTLOADER: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: Bootloader, 0x%02x\n", + ts->id_info.mode); + if (retval < 0) + goto exit; + break; + case SYNAPTICS_TS_MODE_ROMBOOTLOADER: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: Rom Bootloader, 0x%02x\n", + ts->id_info.mode); + if (retval < 0) + goto exit; + break; + default: + retval = snprintf(buf, PAGE_SIZE - count, + "Firmware mode: Mode 0x%02x\n", + ts->id_info.mode); + if (retval < 0) + goto exit; + break; + } + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "Part number: "); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = synaptics_ts_pal_mem_cpy(buf, + PAGE_SIZE - count, + ts->id_info.part_number, + sizeof(ts->id_info.part_number), + sizeof(ts->id_info.part_number)); + if (retval < 0) { + input_err(true, ts->dev, "Fail to copy part number string\n"); + goto exit; + } + buf += sizeof(ts->id_info.part_number); + count += sizeof(ts->id_info.part_number); + + retval = snprintf(buf, PAGE_SIZE - count, "\n"); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "Packrat number: %d\n\n", ts->packrat_number); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + if (ts->id_info.mode != SYNAPTICS_TS_MODE_APPLICATION_FIRMWARE) { + retval = count; + goto exit; + } + + retval = snprintf(buf, PAGE_SIZE - count, "Config ID: "); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + for (i = 0; i < MAX_SIZE_CONFIG_ID; i++) { + retval = snprintf(buf, PAGE_SIZE - count, + "0x%2x ", ts->config_id[i]); + if (retval < 0) + goto exit; + buf += retval; + count += retval; + } + + retval = snprintf(buf, PAGE_SIZE - count, "\n"); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "Max X & Y: %d, %d\n", ts->max_x, ts->max_y); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "Num of objects: %d\n", ts->max_objects); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = snprintf(buf, PAGE_SIZE - count, + "Num of cols & rows: %d, %d\n", ts->cols, ts->rows); + if (retval < 0) + goto exit; + + buf += retval; + count += retval; + + retval = count; + +exit: + synaptics_ts_pal_mutex_unlock(&g_extif_mutex); + return retval; +} + +static struct kobj_attribute kobj_attr_info = + __ATTR(info, 0444, syna_sysfs_info_show, NULL); + +/** + * syna_sysfs_irq_en_store() + * + * Attribute to disable/enable the irq + * + * @param + * [ in] kobj: an instance of kobj + * [ in] attr: an instance of kobj attribute structure + * [ in] buf: string buffer input + * [ in] count: size of buffer input + * + * @return + * on success, return count; otherwise, return error code + */ +static ssize_t syna_sysfs_irq_en_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int retval = 0; + unsigned int input; + struct device *p_dev; + struct kobject *p_kobj; + struct synaptics_ts_data *ts; + + p_kobj = g_sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + ts = dev_get_drvdata(p_dev); + + if (kstrtouint(buf, 10, &input)) + return -EINVAL; + + //if (!ts->vendor_data->hw_if->ops_enable_irq) + // return 0; + + if (!ts->probe_done) { + input_err(true, ts->dev, "Device is NOT connected\n"); + retval = count; + goto exit; + } + + synaptics_ts_pal_mutex_lock(&g_extif_mutex); + + /* disable the interrupt line */ + if (input == 0) { + //retval = ts->vendor_data->hw_if->ops_enable_irq(ts->vendor_data->hw_if, false); + //if (retval < 0) { + // input_err(true, ts->dev, "Fail to disable interrupt\n"); + // goto exit; + //} + disable_irq(ts->irq); + } else if (input == 1) { + /* enable the interrupt line */ + //retval = ts->vendor_data->hw_if->ops_enable_irq(ts->vendor_data->hw_if, true); + //if (retval < 0) { + // input_err(true, ts->dev, "Fail to enable interrupt\n"); + // goto exit; + //} + enable_irq(ts->irq); + } else { + retval = -EINVAL; + goto exit; + } + + retval = count; + +exit: + synaptics_ts_pal_mutex_unlock(&g_extif_mutex); + return retval; +} + +static struct kobj_attribute kobj_attr_irq_en = + __ATTR(irq_en, 0220, NULL, syna_sysfs_irq_en_store); + +/** + * syna_sysfs_reset_store() + * + * Attribute to issue a reset. + * "1" for a sw reset; "2" for a hardware reset + * + * @param + * [ in] kobj: an instance of kobj + * [ in] attr: an instance of kobj attribute structure + * [ in] buf: string buffer input + * [ in] count: size of buffer input + * + * @return + * on success, return count; otherwise, return error code + */ +static ssize_t syna_sysfs_reset_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int retval = 0; + unsigned int input; + struct device *p_dev; + struct kobject *p_kobj; + struct synaptics_ts_data *ts; + + p_kobj = g_sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + ts = dev_get_drvdata(p_dev); + + if (kstrtouint(buf, 10, &input)) + return -EINVAL; + + if (!ts->probe_done) { + input_err(true, ts->dev, "Device is NOT connected\n"); + retval = count; + goto exit; + } + + synaptics_ts_pal_mutex_lock(&g_extif_mutex); + + if (input == 1) { + retval = synaptics_ts_soft_reset(ts); + if (retval < 0) { + input_err(true, ts->dev, "Fail to do reset\n"); + goto exit; + } + } else { + goto exit; + } + + /* check the fw setup in case the settings is changed */ + if (IS_APP_FW_MODE(ts->dev_mode)) { + retval = synaptics_ts_set_up_app_fw(ts); + if (retval < 0) { + input_err(true, ts->dev, "Fail to set up app fw\n"); + goto exit; + } + } + + retval = count; + +exit: + synaptics_ts_pal_mutex_unlock(&g_extif_mutex); + return retval; +} + +static struct kobj_attribute kobj_attr_reset = + __ATTR(reset, 0220, NULL, syna_sysfs_reset_store); + +/** + * syna_sysfs_enable_store() + * + * Attribute to enable/disable the input device + * "1"to enable input device; "0" to disable + * + * @param + * [ in] kobj: an instance of kobj + * [ in] attr: an instance of kobj attribute structure + * [ in] buf: string buffer input + * [ in] count: size of buffer input + * + * @return + * on success, return count; otherwise, return error code + */ +static ssize_t syna_sysfs_enable_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int retval = 0; + unsigned int input; + struct device *p_dev; + struct kobject *p_kobj; + struct synaptics_ts_data *ts; + bool support_ear_detect; + + p_kobj = g_sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + ts = dev_get_drvdata(p_dev); + support_ear_detect = ts->plat_data->support_ear_detect; + + if (kstrtouint(buf, 10, &input)) + return -EINVAL; + + if (input == 1) { + if (ts->plat_data->lowpower_mode || support_ear_detect) { + retval = ts->plat_data->lpmode(ts, TO_TOUCH_MODE); + if (retval < 0) + return retval; + } + } else if (input == 0) { + if (ts->plat_data->lowpower_mode || support_ear_detect) { + retval = ts->plat_data->lpmode(ts, TO_LOWPOWER_MODE); + if (retval < 0) + return retval; + } + } else { + input_err(true, ts->dev, "Un-Supported value: %d\n", input); + return -EINVAL; + } + + return count; +} + +static struct kobj_attribute kobj_attr_enable = + __ATTR(enable, 0220, NULL, syna_sysfs_enable_store); + +/** + * syna_sysfs_ownership_show() + * + * Attribute to show whether the device is connected + * + * @param + * [ in] kobj: an instance of kobj + * [ in] attr: an instance of kobj attribute structure + * [out] buf: string buffer shown on console + * + * @return + * on success, number of characters being output; + * otherwise, negative value on error. + */ +static ssize_t syna_sysfs_ownership_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct device *p_dev; + struct kobject *p_kobj; + struct synaptics_ts_data *ts; + + p_kobj = g_sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + ts = dev_get_drvdata(p_dev); + + return snprintf(buf, PAGE_SIZE, "%s\n", (ts->probe_done)?"1":"0"); +} + +static struct kobj_attribute kobj_attr_ownership = + __ATTR(ownership, 0444, syna_sysfs_ownership_show, NULL); + +/** + * syna_sysfs_check_touch_count_show() + * + * Attribute to show the current touch_count for debugging + * + * @param + * [ in] kobj: an instance of kobj + * [ in] attr: an instance of kobj attribute structure + * [out] buf: string buffer shown on console + * + * @return + * on success, number of characters being output; + * otherwise, negative value on error. + */ +static ssize_t syna_sysfs_check_touch_count_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct device *p_dev; + struct kobject *p_kobj; + struct synaptics_ts_data *ts; + + p_kobj = g_sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + ts = dev_get_drvdata(p_dev); + + return snprintf(buf, PAGE_SIZE, "touch count: %d\n", ts->plat_data->touch_count); +} + +static struct kobj_attribute kobj_attr_check_touch_count = + __ATTR(check_touch_count, 0444, syna_sysfs_check_touch_count_show, NULL); + + +/** + * declaration of sysfs attributes + */ +static struct attribute *attrs[] = { + &kobj_attr_info.attr, + &kobj_attr_irq_en.attr, + &kobj_attr_reset.attr, + &kobj_attr_enable.attr, + &kobj_attr_ownership.attr, + &kobj_attr_check_touch_count.attr, + NULL, +}; + +static struct attribute_group attr_group = { + .attrs = attrs, +}; + +/** + * syna_sysfs_create_dir() + * + * Create a directory and register it with sysfs. + * Then, create all defined sysfs files. + * + * @param + * [ in] tcm: the driver handle + * [ in] pdev: an instance of platform device + * + * @return + * on success, 0; otherwise, negative value on error. + */ +int syna_sysfs_create_dir(struct synaptics_ts_data *ts) +{ + int retval = 0; + + g_sysfs_dir = kobject_create_and_add("sysfs", + &ts->dev->kobj); + if (!g_sysfs_dir) { + input_err(true, ts->dev, "Fail to create sysfs directory\n"); + return -EINVAL; + } + + ts->vendor_data->sysfs_dir = g_sysfs_dir; + + retval = sysfs_create_group(g_sysfs_dir, &attr_group); + if (retval < 0) { + input_err(true, ts->dev, "Fail to create sysfs group\n"); + + kobject_put(ts->vendor_data->sysfs_dir); + return retval; + } + +#ifdef CONFIG_TOUCHSCREEN_SYNA_TCM2_TESTING + retval = syna_testing_create_dir(ts, g_sysfs_dir); + if (retval < 0) { + input_err(true, ts->dev, "Fail to create testing sysfs\n"); + + sysfs_remove_group(ts->vendor_data->sysfs_dir, &attr_group); + kobject_put(ts->vendor_data->sysfs_dir); + return retval; + } +#endif + return 0; +} +/** + * syna_sysfs_remove_dir() + * + * Remove the allocate sysfs directory + * + * @param + * [ in] tcm: the driver handle + * + * @return + * on success, 0; otherwise, negative value on error. + */ + +void syna_sysfs_remove_dir(struct synaptics_ts_data *ts) +{ + if (!ts) { + pr_err("%s%s:Invalid tcm device handle\n", SECLOG, __func__); + return; + } + + if (ts->vendor_data->sysfs_dir) { +#ifdef CONFIG_TOUCHSCREEN_SYNA_TCM2_TESTING + syna_testing_remove_dir(); +#endif + + sysfs_remove_group(ts->vendor_data->sysfs_dir, &attr_group); + + kobject_put(ts->vendor_data->sysfs_dir); + } + +} +#ifdef ENABLE_EXTERNAL_FRAME_PROCESS +/** + * syna_cdev_insert_fifo() + * + * Insert/Push the data to the queue. + * + * This function is called by syna_cdev_update_report_queue(), + * where the event data will be placed as the below format in byte + * and use this function to store the data in queue. + * [0 ] : status / report code + * [1 : 2 ] : length of data frame + * [3 : N + 3] : N bytes of data payload + * + * @param + * [ in] tcm: the driver handle + * [ in] buf_ptr: points to a data going to push + * [ in] length: data length + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_insert_fifo(struct synaptics_ts_data *ts, + unsigned char *buf_ptr, unsigned int length) +{ + int retval = 0; + struct fifo_queue *pfifo_data; + struct fifo_queue *pfifo_data_temp; + static int pre_remaining_frames = -1; + + synaptics_ts_pal_mutex_lock(&g_fifo_queue_mutex); + + /* check queue buffer limit */ + if (ts->vendor_data->fifo_remaining_frame >= FIFO_QUEUE_MAX_FRAMES) { + if (ts->vendor_data->fifo_remaining_frame != pre_remaining_frames) + input_info(true, ts->dev, "Reached %d and drop FIFO first frame\n", + ts->vendor_data->fifo_remaining_frame); + + pfifo_data_temp = list_first_entry(&ts->vendor_data->frame_fifo_queue, + struct fifo_queue, next); + + list_del(&pfifo_data_temp->next); + kfree(pfifo_data_temp->fifo_data); + kfree(pfifo_data_temp); + pre_remaining_frames = ts->vendor_data->fifo_remaining_frame; + ts->vendor_data->fifo_remaining_frame--; + } else if (pre_remaining_frames >= FIFO_QUEUE_MAX_FRAMES) { + input_info(true, ts->dev, "Reached limit, dropped oldest frame, remaining:%d\n", + ts->vendor_data->fifo_remaining_frame); + pre_remaining_frames = ts->vendor_data->fifo_remaining_frame; + } + + pfifo_data = kmalloc(sizeof(*pfifo_data), GFP_KERNEL); + if (!(pfifo_data)) { + input_err(true, ts->dev, "Failed to allocate memory\n"); + input_err(true, ts->dev, "Allocation size = %zu\n", (sizeof(*pfifo_data))); + retval = -ENOMEM; + goto exit; + } + + pfifo_data->fifo_data = kmalloc(length, GFP_KERNEL); + if (!(pfifo_data->fifo_data)) { + input_err(true, ts->dev, "Failed to allocate memory, size = %d\n", length); + kfree(pfifo_data); + retval = -ENOMEM; + goto exit; + } + + pfifo_data->data_length = length; + + memcpy((void *)pfifo_data->fifo_data, (void *)buf_ptr, length); +// do_gettimeofday(&(pfifo_data->timestamp)); + ktime_get_real_ts64(&(pfifo_data->timestamp)); + + /* append the data to the tail for FIFO queueing */ + list_add_tail(&pfifo_data->next, &ts->vendor_data->frame_fifo_queue); + ts->vendor_data->fifo_remaining_frame++; + retval = 0; + +exit: + synaptics_ts_pal_mutex_unlock(&g_fifo_queue_mutex); + return retval; +} +#endif +/** + * syna_cdev_ioctl_check_frame() + * + * Check the queuing status and wait for the data if it's empty. + * + * @param + * [ in] tcm: the driver handle + * [ in/out] ubuf_ptr: points to a memory space from userspace + * [ in] buf_size: size of given space + * [ in] data_size: timeout value for queue waiting + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_ioctl_check_frame(struct synaptics_ts_data *ts, + const unsigned char *ubuf_ptr, unsigned int buf_size, + unsigned int data_size) +{ + int retval = 0; + int result = 0; + unsigned int timeout = 0; + unsigned int frames = 0; + unsigned char data[4] = {0}; + + if (!ts->probe_done) { + input_err(true, ts->dev, "Not connected\n"); + return -EINVAL; + } + + if (buf_size < 4 || data_size < 4) { + input_err(true, ts->dev, "Invalid sync data size, buf_size: %u\n", buf_size); + retval = -EINVAL; + goto exit; + } + + result = copy_from_user(data, ubuf_ptr, + sizeof(data)); + if (result) { + input_err(true, ts->dev, "Fail to copy data from user space\n"); + retval = -EIO; + goto exit; + } + + /* Parese the waiting duration length */ + timeout = synaptics_ts_pal_le4_to_uint(&data[0]); + input_dbg(true, ts->dev, "Time out: %d\n", timeout); + + if (list_empty(&ts->vendor_data->frame_fifo_queue)) { + input_dbg(true, ts->dev, "The queue is empty, wait for the frames\n"); + result = wait_event_interruptible_timeout(ts->vendor_data->wait_frame, + (ts->vendor_data->fifo_remaining_frame > 0), + msecs_to_jiffies(timeout)); + if (result == 0) { + input_dbg(true, ts->dev, "Queue waiting timed out after %dms\n", timeout); + retval = -ETIMEDOUT; + goto exit; + } + input_dbg(true, ts->dev, "Data queued\n"); + retval = data_size; + } else { + input_dbg(true, ts->dev, "Queue is not empty\n"); + retval = data_size; + } + +exit: + if (retval != -EIO) { + frames = ts->vendor_data->fifo_remaining_frame; + data[0] = (unsigned char)(frames & 0xff); + data[1] = (unsigned char)((frames >> 8) & 0xff); + data[2] = (unsigned char)((frames >> 16) & 0xff); + data[3] = (unsigned char)((frames >> 24) & 0xff); + result = copy_to_user((void *)ubuf_ptr, + data, sizeof(data)); + if (result) { + input_err(true, ts->dev, "Fail to copy data to user space\n"); + retval = -EIO; + } + } + + return retval; +} + +/** + * syna_cdev_clean_queue() + * + * Clean the data queue. + * All data in the queue will be cleaned up in every time of device + * open and close. + * + * @param + * [ in] tcm: the driver handle + * + * @return + * void. + */ +static void syna_cdev_clean_queue(struct synaptics_ts_data *ts) +{ + struct fifo_queue *pfifo_data; + + synaptics_ts_pal_mutex_lock(&g_fifo_queue_mutex); + + while (!list_empty(&ts->vendor_data->frame_fifo_queue)) { + pfifo_data = list_first_entry(&ts->vendor_data->frame_fifo_queue, + struct fifo_queue, next); + list_del(&pfifo_data->next); + kfree(pfifo_data->fifo_data); + kfree(pfifo_data); + if (ts->vendor_data->fifo_remaining_frame != 0) + ts->vendor_data->fifo_remaining_frame--; + } + + input_dbg(true, ts->dev, "Queue cleaned, frame: %d\n", ts->vendor_data->fifo_remaining_frame); + + synaptics_ts_pal_mutex_unlock(&g_fifo_queue_mutex); +} +/** + * syna_cdev_ioctl_get_frame() + * + * Read the data from the queue and return to userspace if data is + * copied or the specified timeout is expired. + * + * Please be noted that the retried data is formatted as follows. + * [0 ] : status / report code + * [1 : 2 ] : length of data frame + * [3 : N + 3] : N bytes of data payload + * + * @param + * [ in] tcm: the driver handle + * [in/out] ubuf_ptr: points to a memory space from userspace + * [ in] buf_size: size of given space + * [out] frame_size: frame size returned + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_ioctl_get_frame(struct synaptics_ts_data *ts, + const unsigned char *ubuf_ptr, unsigned int buf_size, + unsigned int *frame_size) +{ + int retval = 0; + int timeout = 0; + unsigned char timeout_data[4] = {0}; + struct fifo_queue *pfifo_data; + + if (!ts->probe_done) { + input_err(true, ts->dev, "Not connected\n"); + return -EINVAL; + } + + if (buf_size < 4) { + input_err(true, ts->dev, "Invalid sync data size, buf_size:%d\n", buf_size); + retval = -EINVAL; + goto exit; + } + +#if !defined(ENABLE_EXTERNAL_FRAME_PROCESS) + input_err(true, ts->dev, "ENABLE_EXTERNAL_FRAME_PROCESS is not enabled\n"); + return -EINVAL; +#endif + + retval = copy_from_user(timeout_data, ubuf_ptr, sizeof(timeout_data)); + if (retval) { + input_err(true, ts->dev, "Fail to copy data from user space, size:%d\n", retval); + retval = -EIO; + goto exit; + } + + /* get the waiting duration */ + timeout = synaptics_ts_pal_le4_to_uint(&timeout_data[0]); + input_dbg(true, ts->dev, "Wait time: %dms\n", timeout); + + if (list_empty(&ts->vendor_data->frame_fifo_queue)) { + input_dbg(true, ts->dev, "The queue is empty, wait for the frame\n"); + retval = wait_event_interruptible_timeout(ts->vendor_data->wait_frame, + (ts->vendor_data->fifo_remaining_frame > 0), + msecs_to_jiffies(timeout)); + if (retval == 0) { + input_dbg(true, ts->dev, "Queue waiting timed out after %dms\n", timeout); + retval = -ETIMEDOUT; + *frame_size = 0; + goto exit; + } + input_dbg(true, ts->dev, "Data queued\n"); + } + + /* confirm the queue status */ + if (list_empty(&ts->vendor_data->frame_fifo_queue)) { + input_dbg(true, ts->dev, "Is queue empty? The remaining frame = %d\n", + ts->vendor_data->fifo_remaining_frame); + retval = -ENODATA; + goto exit; + } + + synaptics_ts_pal_mutex_lock(&g_fifo_queue_mutex); + + pfifo_data = list_first_entry(&ts->vendor_data->frame_fifo_queue, + struct fifo_queue, next); + + input_dbg(true, ts->dev, "Pop data from the queue, data length = %d\n", + pfifo_data->data_length); + + if (buf_size >= pfifo_data->data_length) { + retval = copy_to_user((void *)ubuf_ptr, + pfifo_data->fifo_data, + pfifo_data->data_length); + if (retval) { + input_err(true, ts->dev, "Fail to copy data to user space, size:%d\n", + retval); + retval = -EIO; + } + + *frame_size = pfifo_data->data_length; + + } else { + input_err(true, ts->dev, "No enough space for data copy, buf_size:%d data:%d\n", + buf_size, pfifo_data->data_length); + retval = copy_to_user((void *)ubuf_ptr, + pfifo_data->fifo_data, + buf_size); + retval = -EOVERFLOW; + } + + input_dbg(true, ts->dev, "From FIFO: (0x%02x, 0x%02x, 0x%02x, 0x%02x)\n", + pfifo_data->fifo_data[0], pfifo_data->fifo_data[1], + pfifo_data->fifo_data[2], pfifo_data->fifo_data[3]); + + if (retval >= 0) + retval = pfifo_data->data_length; + + list_del(&pfifo_data->next); + kfree(pfifo_data->fifo_data); + kfree(pfifo_data); + if (ts->vendor_data->fifo_remaining_frame != 0) + ts->vendor_data->fifo_remaining_frame--; + + synaptics_ts_pal_mutex_unlock(&g_fifo_queue_mutex); + +exit: + return retval; +} + +/** + * syna_cdev_ioctl_set_reports() + * + * Assign the report types for queuing. The enabled reports will be queued + * into the FIFO queue. + * + * @param + * [ in] tcm: the driver handle + * [ in] ubuf_ptr: points to a memory space from userspace + * [ in] buf_size: size of given space + * [ in] report_size: report types data size + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_ioctl_set_reports(struct synaptics_ts_data *ts, + const unsigned char *ubuf_ptr, unsigned int buf_size, + unsigned int report_size) +{ + int retval = 0; + unsigned char data[REPORT_TYPES] = {0}; + unsigned int reports = 0; + unsigned int report_set = 0; + + if (buf_size < sizeof(data)) { + input_err(true, ts->dev, "Invalid sync data size, buf_size:%d, expected:%d\n", + buf_size, (unsigned int)sizeof(data)); + return -EINVAL; + } + +#if !defined(ENABLE_EXTERNAL_FRAME_PROCESS) + input_err(true, ts->dev, "ENABLE_EXTERNAL_FRAME_PROCESS is not enabled\n"); + return -EINVAL; +#endif + + if (report_size == 0 || report_size > buf_size) { + input_err(true, ts->dev, "Invalid written size: %u\n", buf_size); + return -EINVAL; + } + + retval = copy_from_user(data, ubuf_ptr, report_size); + if (retval) { + input_err(true, ts->dev, "Fail to copy data from user space, size:%d\n", retval); + retval = -EIO; + goto exit; + } + + retval = synaptics_ts_pal_mem_cpy(ts->vendor_data->report_to_queue, REPORT_TYPES, + data, sizeof(data), REPORT_TYPES); + for (reports = 0 ; reports < REPORT_TYPES ; reports++) { + if (ts->vendor_data->report_to_queue[reports] == EFP_ENABLE) { + report_set++; + input_dbg(true, ts->dev, "Set report 0x%02x for queue\n", reports); + } + } + + input_info(true, ts->dev, "Forward %d types of reports to the Queue.\n", report_set); + + retval = report_set; + +exit: + return retval; +} +/** + * syna_cdev_ioctl_send_message() + * + * Send the command/message from userspace. + * + * For updating the g_sysfs_io_polling_interval, it need to be configured + * by syna_cdev_ioctl_enable_irq from userspace. + * + * @param + * [ in] tcm: the driver handle + * [ in/out] ubuf_ptr: points to a memory space from userspace + * [ in] buf_size: size of given space + * [ in/out] msg_size: size of message + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_ioctl_send_message(struct synaptics_ts_data *ts, + const unsigned char *ubuf_ptr, unsigned int buf_size, + unsigned int *msg_size) +{ + int retval = 0; + unsigned char *data = NULL; + unsigned char resp_code = 0; + unsigned int payload_length = 0; + unsigned int delay_ms_resp = 0; + struct synaptics_ts_buffer resp_data_buf; + + if (!ts->probe_done) { + input_err(true, ts->dev, "Not connected\n"); + return -EINVAL; + } + + if (buf_size < SEND_MESSAGE_HEADER_LENGTH) { + input_err(true, ts->dev, "Invalid sync data size, buf_size:%d\n", buf_size); + return -EINVAL; + } + + if (*msg_size == 0 || *msg_size > buf_size) { + input_err(true, ts->dev, "Invalid message length, msg size: %u\n", *msg_size); + return -EINVAL; + } + + synaptics_ts_buf_lock(&g_cdev_cbuf); + synaptics_ts_buf_init(&resp_data_buf); + + retval = synaptics_ts_buf_alloc(&g_cdev_cbuf, buf_size); + if (retval < 0) { + input_err(true, ts->dev, "Fail to allocate memory for g_cdev_cbuf, size: %d\n", + buf_size); + goto exit; + } + + data = g_cdev_cbuf.buf; + + retval = copy_from_user(data, ubuf_ptr, *msg_size); + if (retval) { + input_err(true, ts->dev, "Fail to copy data from user space, size:%d\n", *msg_size); + retval = -EIO; + goto exit; + } + + payload_length = synaptics_ts_pal_le2_to_uint(&data[1]); + input_dbg(true, ts->dev, "Command = 0x%02x, payload length = %d\n", + data[0], payload_length); + + if (g_sysfs_io_polling_interval > 0) + delay_ms_resp = g_sysfs_io_polling_interval; + + retval = synaptics_ts_send_command(ts, + data[0], + &data[3], + payload_length, + &resp_code, + &resp_data_buf, + delay_ms_resp); + if (retval) { + input_err(true, ts->dev, "Fail to send command:%d\n", retval); + retval = -EIO; + goto exit; + } + + synaptics_ts_pal_mem_set(data, 0, buf_size); + /* status code */ + data[0] = resp_code; + /* the length for response data */ + data[1] = (unsigned char)(resp_data_buf.data_length & 0xff); + data[2] = (unsigned char)((resp_data_buf.data_length >> 8) & 0xff); + /* response data */ + if (resp_data_buf.data_length > 0) { + retval = synaptics_ts_pal_mem_cpy(&g_cdev_cbuf.buf[3], + (g_cdev_cbuf.buf_size - SEND_MESSAGE_HEADER_LENGTH), + resp_data_buf.buf, + resp_data_buf.buf_size, + resp_data_buf.data_length); + if (retval < 0) { + input_err(true, ts->dev, "Fail to copy resp data\n"); + retval = -EIO; + goto exit; + } + } + +#ifdef ENABLE_EXTERNAL_FRAME_PROCESS + /* It's for queuing the data when user is polling the command + * response for the selected responses. The response will not be + * queued if the user doesn't set the report/response types through + * syna_cdev_ioctl_set_reports. + */ + if (delay_ms_resp != FORCE_ATTN_DRIVEN) { + if (ts->vendor_data->report_to_queue[resp_code] == EFP_ENABLE) { + syna_cdev_update_report_queue(ts, resp_code, + &resp_data_buf); + } + } +#endif + + if (buf_size < resp_data_buf.data_length) { + input_err(true, ts->dev, "No enough space for data copy, buf_size:%d data:%d\n", + buf_size, resp_data_buf.data_length); + retval = -EOVERFLOW; + goto exit; + } + + retval = copy_to_user((void *)ubuf_ptr, + data, resp_data_buf.data_length); + if (retval) { + input_err(true, ts->dev, "Fail to copy data to user space\n"); + retval = -EIO; + goto exit; + } + + *msg_size = resp_data_buf.data_length + 3; + retval = *msg_size; + +exit: + synaptics_ts_buf_unlock(&g_cdev_cbuf); + + synaptics_ts_buf_release(&resp_data_buf); + + return retval; +} + +/** + * syna_cdev_ioctl_enable_irq() + * + * Enable or disable the irq via IOCTL. + * + * The 4 bytes unsigned int parameter from the buffer: + * 0: disable the irq. + * 1: enable the irq and set the g_sysfs_io_polling_interval + * to default. + * 2~(~0)-1: the polling interval for response reading from + * syna_cdev_ioctl_send_message. + * (~0): To use the fill isr for reading the response from + * syna_cdev_ioctl_send_message. + * + * @param + * [ in] tcm: the driver handle + * [ in] ubuf_ptr: points to a memory space from userspace + * [ in] buf_size: size of given space + * [ in] data_size: size of actual data + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_ioctl_enable_irq(struct synaptics_ts_data *ts, + const unsigned char *ubuf_ptr, unsigned int buf_size, + unsigned int data_size) +{ + int retval = 0; + unsigned int en = 0; + unsigned char *data = NULL; + + if (!ts->probe_done) { + input_err(true, ts->dev, "Not connected\n"); + return -EINVAL; + } + + if ((buf_size < 4) || (data_size < 4)) { + input_err(true, ts->dev, "Invalid sync data size, buf_size:%d, data_size:%d\n", + buf_size, data_size); + return -EINVAL; + } + + //if (!ts->vendor_data->hw_if->ops_enable_irq) { + // input_err(true, ts->dev, "Not support to enable irq\n"); + // return 0; + //} + + synaptics_ts_buf_lock(&g_cdev_cbuf); + + retval = synaptics_ts_buf_alloc(&g_cdev_cbuf, buf_size); + if (retval < 0) { + input_err(true, ts->dev, "Fail to allocate memory for g_cdev_buf, size: %d\n", + buf_size); + goto exit; + } + + data = g_cdev_cbuf.buf; + + retval = copy_from_user(data, ubuf_ptr, buf_size); + if (retval) { + input_err(true, ts->dev, "Fail to copy data from user space, size:%d\n", retval); + retval = -EIO; + goto exit; + } + + en = synaptics_ts_pal_le4_to_uint(&data[0]); + + input_dbg(true, ts->dev, "Prepare to %s irq\n", (en)?"enable":"disable"); + + switch (en) { + case SYSFS_DISABLED_INTERRUPT: + //retval = ts->vendor_data->hw_if->ops_enable_irq(ts->vendor_data->hw_if, false); + //if (retval < 0) { + // input_err(true, ts->dev, "Fail to disable interrupt\n"); + // goto exit; + //} + disable_irq(ts->irq); + g_sysfs_io_polling_interval = 0; + input_info(true, ts->dev, "IRQ is disabled\n"); + break; + default: + if (en == SYSFS_FULL_INTERRUPT) + g_sysfs_io_polling_interval = SYSFS_FULL_INTERRUPT; + else if (en == SYSFS_GENERIC_INTERRUPT) + g_sysfs_io_polling_interval = 0; + else + g_sysfs_io_polling_interval = en; + enable_irq(ts->irq); + //retval = ts->vendor_data->hw_if->ops_enable_irq(ts->vendor_data->hw_if, true); + //if (retval < 0) { + // input_err(true, ts->dev, "Fail to enable interrupt\n"); + // goto exit; + //} + input_info(true, ts->dev, "IRQ is enabled\n"); + break; + } + +exit: + synaptics_ts_buf_unlock(&g_cdev_cbuf); + + return retval; +} +/** + * syna_cdev_ioctl_store_pid() + * + * Save PID through IOCTL interface + * + * @param + * [ in] tcm: the driver handle + * [ in] ubuf_ptr: points to a memory space from userspace + * [ in] buf_size: size of given space + * [ in] data_size: size of actual data + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_ioctl_store_pid(struct synaptics_ts_data *ts, + const unsigned char *ubuf_ptr, unsigned int buf_size, + unsigned int data_size) +{ + int retval = 0; + unsigned char *data = NULL; + + if (!ts->probe_done) { + input_err(true, ts->dev, "Not connected\n"); + return -EINVAL; + } + + if (buf_size < 4) { + input_err(true, ts->dev, "Invalid sync data size, buf_size:%d\n", buf_size); + return -EINVAL; + } + + if (data_size < 4 || data_size > buf_size) { + input_err(true, ts->dev, "Invalid data_size\n"); + return -EINVAL; + } + + synaptics_ts_buf_lock(&g_cdev_cbuf); + + retval = synaptics_ts_buf_alloc(&g_cdev_cbuf, buf_size); + if (retval < 0) { + input_err(true, ts->dev, "Fail to allocate memory for g_cdev_buf, size: %d\n", + buf_size); + goto exit; + } + + data = g_cdev_cbuf.buf; + + retval = copy_from_user(data, ubuf_ptr, data_size); + if (retval) { + input_err(true, ts->dev, "Fail to copy data from user space, size:%d\n", retval); + retval = -EIO; + goto exit; + } + + ts->vendor_data->proc_pid = synaptics_ts_pal_le4_to_uint(&data[0]); + + input_dbg(true, ts->dev, "PID: %d\n", (unsigned int)ts->vendor_data->proc_pid); +#ifdef ENABLE_PID_TASK + if (ts->vendor_data->proc_pid) { + ts->vendor_data->proc_task = pid_task( + find_vpid(ts->vendor_data->proc_pid), + PIDTYPE_PID); + if (!ts->vendor_data->proc_task) { + input_err(true, ts->dev, "Fail to locate task, pid: %d\n", + (unsigned int)ts->vendor_data->proc_pid); + retval = -EINVAL; + goto exit; + } + } +#endif +exit: + synaptics_ts_buf_unlock(&g_cdev_cbuf); + + return retval; +} +/** + * syna_cdev_ioctl_raw_read() + * + * Read the data from device directly without routing to command wrapper + * interface. + * + * @param + * [ in] tcm: the driver handle + * [in/out] ubuf_ptr: ubuf_ptr: points to a memory space from userspace + * [ in] buf_size: size of given space + * [ in] rd_size: reading size + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_ioctl_raw_read(struct synaptics_ts_data *ts, + const unsigned char *ubuf_ptr, unsigned int buf_size, + unsigned int rd_size) +{ + int retval = 0; + unsigned char *data = NULL; + + if (!ts->probe_done) { + input_err(true, ts->dev, "Not connected\n"); + return -EINVAL; + } + + if ((buf_size < 0) || (rd_size > buf_size)) { + input_err(true, ts->dev, "Invalid sync data size, buf_size:%d, rd_size:%d\n", + buf_size, rd_size); + return -EINVAL; + } + + if (rd_size == 0) { + input_err(true, ts->dev, "The read length is 0\n"); + return 0; + } + + synaptics_ts_buf_lock(&g_cdev_cbuf); + + retval = synaptics_ts_buf_alloc(&g_cdev_cbuf, rd_size); + if (retval < 0) { + input_err(true, ts->dev, "Fail to allocate memory for g_cdev_cbuf, size: %d\n", + rd_size); + goto exit; + } + + data = g_cdev_cbuf.buf; + + retval = ts->synaptics_ts_read_data_only(ts, + data, + rd_size); + if (retval < 0) { + input_err(true, ts->dev, "Fail to read raw data, size: %d\n", rd_size); + goto exit; + } + + if (copy_to_user((void *)ubuf_ptr, data, rd_size)) { + input_err(true, ts->dev, "Fail to copy data to user space\n"); + retval = -EINVAL; + goto exit; + } + + retval = rd_size; + +exit: + synaptics_ts_buf_unlock(&g_cdev_cbuf); + + return retval; +} +/** + * syna_cdev_ioctl_raw_write() + * + * Write the given data to device directly without routing to command wrapper + * interface. + * + * @param + * [ in] tcm: the driver handle + * [ in] ubuf_ptr: points to a memory space from userspace + * [ in] buf_size: size of given space + * [ in] wr_size: size to write + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_ioctl_raw_write(struct synaptics_ts_data *ts, + const unsigned char *ubuf_ptr, unsigned int buf_size, + unsigned int wr_size) +{ + int retval = 0; + unsigned char *data = NULL; + + if (!ts->probe_done) { + input_err(true, ts->dev, "Not connected\n"); + return -EINVAL; + } + + if ((buf_size < 0) || (wr_size > buf_size)) { + input_err(true, ts->dev, "Invalid sync data size, buf_size:%d, wr_size:%d\n", + buf_size, wr_size); + return -EINVAL; + } + + if (wr_size == 0) { + input_err(true, ts->dev, "Invalid written size\n"); + return -EINVAL; + } + + synaptics_ts_buf_lock(&g_cdev_cbuf); + + retval = synaptics_ts_buf_alloc(&g_cdev_cbuf, wr_size); + if (retval < 0) { + input_err(true, ts->dev, "Fail to allocate memory for g_cdev_cbuf, size: %d\n", + wr_size); + goto exit; + } + + data = g_cdev_cbuf.buf; + + retval = copy_from_user(data, ubuf_ptr, wr_size); + if (retval) { + input_err(true, ts->dev, "Fail to copy data from user space, size:%d\n", retval); + retval = -EIO; + goto exit; + } + + input_dbg(true, ts->dev, "Header: (0x%02x, 0x%02x, 0x%02x)\n", + data[0], data[1], data[2]); + + retval = ts->synaptics_ts_write_data(ts, + data, + wr_size, NULL, 0); + if (retval < 0) { + input_err(true, ts->dev, "Fail to write raw data, size: %d\n", wr_size); + goto exit; + } + + retval = wr_size; + +exit: + synaptics_ts_buf_unlock(&g_cdev_cbuf); + + return retval; +} + +/** + * syna_cdev_ioctl_dispatch() + * + * Dispatch the IOCTLs operation based on the given code + * + * @param + * [ in] tcm: the driver handle + * [ in] code: code for the target operation + * [ in] ubuf_ptr: points to a memory space from userspace + * [ in] ubuf_size: size of given space + * [ in] wr_size: written data size + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_ioctl_dispatch(struct synaptics_ts_data *ts, + unsigned int code, const unsigned char *ubuf_ptr, + unsigned int ubuf_size, unsigned int *data_size) +{ + int retval = 0; + + switch (code) { + case STD_SET_PID_ID: + retval = syna_cdev_ioctl_store_pid(ts, + ubuf_ptr, ubuf_size, *data_size); + break; + case STD_ENABLE_IRQ_ID: + retval = syna_cdev_ioctl_enable_irq(ts, + ubuf_ptr, ubuf_size, *data_size); + break; + case STD_RAW_WRITE_ID: + retval = syna_cdev_ioctl_raw_write(ts, + ubuf_ptr, ubuf_size, *data_size); + break; + case STD_RAW_READ_ID: + retval = syna_cdev_ioctl_raw_read(ts, + ubuf_ptr, ubuf_size, *data_size); + break; + case STD_GET_FRAME_ID: + retval = syna_cdev_ioctl_get_frame(ts, + ubuf_ptr, ubuf_size, data_size); + break; + case STD_SEND_MESSAGE_ID: + retval = syna_cdev_ioctl_send_message(ts, + ubuf_ptr, ubuf_size, data_size); + break; + case STD_SET_REPORTS_ID: + retval = syna_cdev_ioctl_set_reports(ts, + ubuf_ptr, ubuf_size, *data_size); + break; + case STD_CHECK_FRAMES_ID: + retval = syna_cdev_ioctl_check_frame(ts, + ubuf_ptr, ubuf_size, *data_size); + break; + case STD_CLEAN_OUT_FRAMES_ID: + input_dbg(true, ts->dev, "STD_CLEAN_OUT_FRAMES_ID called\n"); + syna_cdev_clean_queue(ts); + retval = 0; + break; + default: + input_err(true, ts->dev, "Unknown ioctl code: 0x%x\n", code); + return -ENOTTY; + } + + return retval; +} +/** + * syna_cdev_ioctl_old_dispatch() + * + * Dispatch the old IOCTLs operation based on the given code + * + * @param + * [ in] tcm: the driver handle + * [ in] code: code for the target operation + * [ in] arg: argument passed from user-space + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_ioctl_old_dispatch(struct synaptics_ts_data *ts, + unsigned int code, unsigned long arg) +{ + int retval = 0; + + switch (code) { + case OLD_RESET_ID: + retval = synaptics_ts_soft_reset(ts); + if (retval < 0) { + input_err(true, ts->dev, "Fail to do reset\n"); + break; + } + + retval = synaptics_ts_set_up_app_fw(ts); + if (retval < 0) { + input_err(true, ts->dev, "Fail to set up app fw\n"); + break; + } + + break; + case OLD_SET_IRQ_MODE_ID: + //if (!ts->vendor_data->hw_if->ops_enable_irq) { + // retval = -EINVAL; + // break; + //} + + if (arg == 0) + //retval = ts->vendor_data->hw_if->ops_enable_irq(ts->vendor_data->hw_if, + // false); + disable_irq(ts->irq); + else if (arg == 1) + //retval = ts->vendor_data->hw_if->ops_enable_irq(ts->vendor_data->hw_if, + // true); + enable_irq(ts->irq); + break; + case OLD_SET_RAW_MODE_ID: + if (arg == 0) + ts->vendor_data->is_attn_redirecting = false; + else if (arg == 1) + ts->vendor_data->is_attn_redirecting = true; + + break; + case OLD_CONCURRENT_ID: + retval = 0; + break; + + default: + input_err(true, ts->dev, "Unknown ioctl code: 0x%x\n", code); + retval = -ENOTTY; + break; + } + + return retval; +} + +/** + * syna_cdev_ioctls() + * + * Used to implements the IOCTL operations + * + * @param + * [ in] filp: represents the file descriptor + * [ in] cmd: command code sent from userspace + * [ in] arg: arguments sent from userspace + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static long syna_cdev_ioctls(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + int retval = 0; + struct device *p_dev; + struct kobject *p_kobj; + struct synaptics_ts_data *ts; + struct syna_ioctl_data ioc_data; + unsigned char *ptr = NULL; + + p_kobj = g_sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + ts = dev_get_drvdata(p_dev); + + synaptics_ts_pal_mutex_lock(&g_extif_mutex); + + retval = 0; + + /* handle the old IOCTLs */ + if ((_IOC_NR(cmd)) < STD_IOCTL_BEGIN) { + retval = syna_cdev_ioctl_old_dispatch(ts, + (unsigned int)_IOC_NR(cmd), arg); + + goto exit; + } else if ((_IOC_NR(cmd)) == STD_IOCTL_BEGIN) { + retval = 1; + goto exit; + } + + retval = copy_from_user(&ioc_data, + (void __user *) arg, + sizeof(struct syna_ioctl_data)); + if (retval) { + input_err(true, ts->dev, "Fail to copy ioctl_data from user space, size:%d\n", + retval); + retval = -EINVAL; + goto exit; + } + + ptr = ioc_data.buf; + + retval = syna_cdev_ioctl_dispatch(ts, + (unsigned int)_IOC_NR(cmd), + (const unsigned char *)ptr, + ioc_data.buf_size, + &ioc_data.data_length); + if (retval < 0) + goto exit; + + retval = copy_to_user((void __user *) arg, + &ioc_data, + sizeof(struct syna_ioctl_data)); + if (retval) { + input_err(true, ts->dev, "Fail to update ioctl_data to user space, size:%d\n", + retval); + retval = -EIO; + goto exit; + } + +exit: + synaptics_ts_pal_mutex_unlock(&g_extif_mutex); + + return retval; +} + +#if defined(CONFIG_COMPAT) +/** + * syna_cdev_compat_ioctls() + * + * Used to implements the IOCTL compatible operations + * + * @param + * [ in] filp: represents the file descriptor + * [ in] cmd: command code sent from userspace + * [ in] arg: arguments sent from userspace + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static long syna_cdev_compat_ioctls(struct file *filp, + unsigned int cmd, unsigned long arg) +{ + int retval = 0; + struct device *p_dev; + struct kobject *p_kobj; + struct synaptics_ts_data *ts; + struct syna_tcm_ioctl_data_compat ioc_data; + unsigned char *ptr = NULL; + + p_kobj = g_sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + ts = dev_get_drvdata(p_dev); + + synaptics_ts_pal_mutex_lock(&g_extif_mutex); + + retval = 0; + + /* handle the old IOCTLs */ + if ((_IOC_NR(cmd)) < STD_IOCTL_BEGIN) { + retval = syna_cdev_ioctl_old_dispatch(ts, + (unsigned int)_IOC_NR(cmd), arg); + + goto exit; + } else if ((_IOC_NR(cmd)) == STD_IOCTL_BEGIN) { + retval = 1; + goto exit; + } + + retval = copy_from_user(&ioc_data, + (struct syna_tcm_ioctl_data_compat __user *) compat_ptr(arg), + sizeof(struct syna_tcm_ioctl_data_compat)); + if (retval) { + input_err(true, ts->dev, "Fail to copy ioctl_data from user space, size:%d\n", + retval); + retval = -EINVAL; + goto exit; + } + + ptr = compat_ptr((unsigned long)ioc_data.buf); + + retval = syna_cdev_ioctl_dispatch(ts, + (unsigned int)_IOC_NR(cmd), + (const unsigned char *)ptr, + ioc_data.buf_size, + &ioc_data.data_length); + if (retval < 0) + goto exit; + + retval = copy_to_user(compat_ptr(arg), + &ioc_data, + sizeof(struct syna_tcm_ioctl_data_compat)); + if (retval) { + input_err(true, ts->dev, "Fail to update ioctl_data to user space, size:%d\n", + retval); + retval = -EIO; + goto exit; + } + +exit: + synaptics_ts_pal_mutex_unlock(&g_extif_mutex); + + return retval; +} +#endif + +/** + * syna_cdev_llseek() + * + * Used to change the current position in a file. + * + * @param + * [ in] filp: represents the file descriptor + * [ in] off: the file position + * [ in] whence: flag for seeking + * + * @return + * negative value always because TouchComm doesn't need. + */ +static loff_t syna_cdev_llseek(struct file *filp, + loff_t off, int whence) +{ + return -EINVAL; +} +/** + * syna_cdev_read() + * + * Used to read data through the device file. + * Function will use raw write approach. + * + * @param + * [ in] filp: represents the file descriptor + * [out] buf: given buffer from userspace + * [ in] count: size of buffer + * [ in] f_pos: the file position + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static ssize_t syna_cdev_read(struct file *filp, + char __user *buf, size_t count, loff_t *f_pos) +{ + int retval = 0; + struct device *p_dev; + struct kobject *p_kobj; + struct synaptics_ts_data *ts; + + p_kobj = g_sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + ts = dev_get_drvdata(p_dev); + + if (count == 0) + return 0; + + synaptics_ts_pal_mutex_lock(&g_extif_mutex); + + retval = syna_cdev_ioctl_raw_read(ts, + (const unsigned char *)buf, count, count); + if (retval != count) { + input_err(true, ts->dev, "Invalid read operation, request:%d, return:%d\n", + (unsigned int)count, retval); + } + + synaptics_ts_pal_mutex_unlock(&g_extif_mutex); + + return retval; +} +/** + * syna_cdev_write() + * + * Used to send data to device through the device file. + * Function will use raw write approach. + * + * @param + * [ in] filp: represents the file descriptor + * [ in] buf: given buffer from userspace + * [ in] count: size of buffer + * [ in] f_pos: the file position + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static ssize_t syna_cdev_write(struct file *filp, + const char __user *buf, size_t count, loff_t *f_pos) +{ + int retval = 0; + struct device *p_dev; + struct kobject *p_kobj; + struct synaptics_ts_data *ts; + + p_kobj = g_sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + ts = dev_get_drvdata(p_dev); + + if (count == 0) + return 0; + + synaptics_ts_pal_mutex_lock(&g_extif_mutex); + + retval = syna_cdev_ioctl_raw_write(ts, + (const unsigned char *)buf, count, count); + if (retval != count) { + input_err(true, ts->dev, "Invalid write operation, request:%d, return:%d\n", + (unsigned int)count, retval); + } + + synaptics_ts_pal_mutex_unlock(&g_extif_mutex); + + return retval; +} +/** + * syna_cdev_open() + * + * Invoked when the device file is being open, which should be + * always the first operation performed on the device file + * + * @param + * [ in] inp: represents a file in rootfs + * [ in] filp: represents the file descriptor + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_open(struct inode *inp, struct file *filp) +{ + struct device *p_dev; + struct kobject *p_kobj; + struct synaptics_ts_data *ts; + + p_kobj = g_sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + ts = dev_get_drvdata(p_dev); + + synaptics_ts_pal_mutex_lock(&g_extif_mutex); + + if (ts->vendor_data->char_dev_ref_count != 0) { + input_info(true, ts->dev, "cdev already open, %d\n", + ts->vendor_data->char_dev_ref_count); + synaptics_ts_pal_mutex_unlock(&g_extif_mutex); + return -EBUSY; + } + + ts->vendor_data->char_dev_ref_count++; + + g_sysfs_io_polling_interval = 0; + +#ifdef ENABLE_EXTERNAL_FRAME_PROCESS + syna_cdev_clean_queue(ts); +#endif + synaptics_ts_pal_mutex_unlock(&g_extif_mutex); + + input_info(true, ts->dev, "cdev open\n"); + + return 0; +} +/** + * syna_cdev_release() + * + * Invoked when the device file is being released + * + * @param + * [ in] inp: represents a file in rootfs + * [ in] filp: represents the file descriptor + * + * @return + * on success, 0; otherwise, negative value on error. + */ +static int syna_cdev_release(struct inode *inp, struct file *filp) +{ + struct device *p_dev; + struct kobject *p_kobj; + struct synaptics_ts_data *ts; + + p_kobj = g_sysfs_dir->parent; + p_dev = container_of(p_kobj, struct device, kobj); + ts = dev_get_drvdata(p_dev); + + synaptics_ts_pal_mutex_lock(&g_extif_mutex); + + if (ts->vendor_data->char_dev_ref_count <= 0) { + input_info(true, ts->dev, "cdev already closed, %d\n", + ts->vendor_data->char_dev_ref_count); + synaptics_ts_pal_mutex_unlock(&g_extif_mutex); + return -EBUSY; + } + + ts->vendor_data->char_dev_ref_count--; + + ts->vendor_data->is_attn_redirecting = false; + synaptics_ts_pal_mem_set(ts->vendor_data->report_to_queue, 0, REPORT_TYPES); +#ifdef ENABLE_EXTERNAL_FRAME_PROCESS + syna_cdev_clean_queue(ts); +#endif + synaptics_ts_pal_mutex_unlock(&g_extif_mutex); + + g_sysfs_io_polling_interval = 0; + + input_info(true, ts->dev, "cdev close\n"); + + return 0; +} + +/** + * Declare the operations of TouchCom device file + */ +static const struct file_operations device_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = syna_cdev_ioctls, +#if defined(CONFIG_COMPAT) + .compat_ioctl = syna_cdev_compat_ioctls, +#endif + .llseek = syna_cdev_llseek, + .read = syna_cdev_read, + .write = syna_cdev_write, + .open = syna_cdev_open, + .release = syna_cdev_release, +}; +/** + * syna_cdev_redirect_attn() + * + * Expose the status of ATTN signal to userspace + * + * @param + * [ in] tcm: the driver handle + * + * @return + * none. + */ +void syna_cdev_redirect_attn(struct synaptics_ts_data *ts) +{ + if (ts->vendor_data->proc_pid) + return; +} +#ifdef ENABLE_EXTERNAL_FRAME_PROCESS +/** + * syna_cdev_update_report_queue() + * + * Push the selected data to the queue. + * + * @param + * [ in] tcm: the driver handle + * [ in] code: report type + * [ in] pevent_data: report payload + * + * @return + * none. + */ +void syna_cdev_update_report_queue(struct synaptics_ts_data *ts, + unsigned char code, struct synaptics_ts_buffer *pevent_data) +{ + int retval; + unsigned char *frame_buffer = NULL; + unsigned int frame_length = 0; + + if (pevent_data == NULL) { + input_err(true, ts->dev, "Returned, invalid event data pointer\n"); + return; + } + frame_length = pevent_data->data_length + 3; + input_dbg(true, ts->dev, "The overall queuing data length = %d\n", frame_length); + frame_buffer = (unsigned char *)synaptics_ts_pal_mem_alloc(frame_length, + sizeof(unsigned char)); + if (!frame_buffer) { + input_err(true, ts->dev, "Fail to allocate buffer, size: %d, data_length: %d\n", + pevent_data->data_length + 3, pevent_data->data_length); + return; + } + + frame_buffer[0] = code; + frame_buffer[1] = (unsigned char)pevent_data->data_length; + frame_buffer[2] = (unsigned char)(pevent_data->data_length >> 8); + + if (pevent_data->data_length > 0) { + retval = synaptics_ts_pal_mem_cpy(&frame_buffer[3], + (frame_length - 3), + pevent_data->buf, + pevent_data->data_length, + pevent_data->data_length); + if (retval < 0) { + input_err(true, ts->dev, "Fail to copy data to buffer, size: %d\n", + pevent_data->data_length); + goto exit; + } + } + retval = syna_cdev_insert_fifo(ts, frame_buffer, frame_length); + if (retval < 0) { + input_err(true, ts->dev, "Fail to insert data to fifo\n"); + goto exit; + } + + wake_up_interruptible(&(ts->vendor_data->wait_frame)); + +exit: + synaptics_ts_pal_mem_free((void *)frame_buffer); +} +#endif +/** + * syna_cdev_devnode() + * + * Provide the declaration of devtmpfs + * + * @param + * [ in] dev: an instance of device + * [ in] mode: mode of created node + * + * @return + * the string of devtmpfs + */ +static char *syna_cdev_devnode(struct device *dev, umode_t *mode) +{ + if (!mode) + return NULL; + + /* S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH */ + *mode = CHAR_DEVICE_MODE; + + return kasprintf(GFP_KERNEL, "%s", dev_name(dev)); +} +/** + * syna_cdev_create_sysfs() + * + * Create a device node and register it with sysfs. + * + * @param + * [ in] tcm: the driver handle + * [ in] pdev: an instance of platform device + * + * @return + * on success, 0; otherwise, negative value on error. + */ +int syna_cdev_create_sysfs(struct synaptics_ts_data *ts) +{ + int retval = 0; + struct class *device_class = NULL; + struct device *device = NULL; + static int cdev_major_num; + + ts->vendor_data->device_class = NULL; + ts->vendor_data->device = NULL; + + ts->vendor_data->is_attn_redirecting = false; + + synaptics_ts_pal_mutex_alloc(&g_extif_mutex); +#ifdef ENABLE_EXTERNAL_FRAME_PROCESS + synaptics_ts_pal_mutex_alloc(&g_fifo_queue_mutex); +#endif + synaptics_ts_buf_init(&g_cdev_cbuf); + + if (cdev_major_num) { + ts->vendor_data->char_dev_num = MKDEV(cdev_major_num, 0); + retval = register_chrdev_region(ts->vendor_data->char_dev_num, 1, + ts->tcm_drv_name); + if (retval < 0) { + input_err(true, ts->dev, "Fail to register char device\n"); + goto err_register_chrdev_region; + } + } else { + retval = alloc_chrdev_region(&ts->vendor_data->char_dev_num, 0, 1, + ts->tcm_drv_name); + if (retval < 0) { + input_err(true, ts->dev, "Fail to allocate char device\n"); + goto err_alloc_chrdev_region; + } + + cdev_major_num = MAJOR(ts->vendor_data->char_dev_num); + } + + cdev_init(&ts->vendor_data->char_dev, &device_fops); + ts->vendor_data->char_dev.owner = THIS_MODULE; + + retval = cdev_add(&ts->vendor_data->char_dev, ts->vendor_data->char_dev_num, 1); + if (retval < 0) { + input_err(true, ts->dev, "Fail to add cdev_add\n"); + goto err_add_chardev; + } + + device_class = class_create(THIS_MODULE, ts->tcm_drv_name); + if (IS_ERR(device_class)) { + input_err(true, ts->dev, "Fail to create device class\n"); + retval = PTR_ERR(device_class); + goto err_create_class; + } + + device_class->devnode = syna_cdev_devnode; + + device = device_create(device_class, NULL, + ts->vendor_data->char_dev_num, NULL, + "%s%d", ts->tcm_dev_name, MINOR(ts->vendor_data->char_dev_num)); + if (IS_ERR(ts->vendor_data->device)) { + input_err(true, ts->dev, "Fail to create character device\n"); + retval = -ENODEV; + goto err_create_device; + } + + ts->vendor_data->device_class = device_class; + + ts->vendor_data->device = device; + + ts->vendor_data->char_dev_ref_count = 0; + ts->vendor_data->proc_pid = 0; + +#ifdef ENABLE_EXTERNAL_FRAME_PROCESS + INIT_LIST_HEAD(&ts->vendor_data->frame_fifo_queue); + init_waitqueue_head(&ts->vendor_data->wait_frame); +#endif + synaptics_ts_pal_mem_set(ts->vendor_data->report_to_queue, 0, REPORT_TYPES); + + retval = syna_sysfs_create_dir(ts); + if (retval < 0) { + input_err(true, ts->dev, "Fail to create sysfs dir\n"); + retval = -ENODEV; + goto err_create_dir; + } + + return 0; + +err_create_dir: + device_destroy(ts->vendor_data->device_class, ts->vendor_data->char_dev_num); +err_create_device: + class_destroy(ts->vendor_data->device_class); +err_create_class: + cdev_del(&ts->vendor_data->char_dev); +err_add_chardev: + unregister_chrdev_region(ts->vendor_data->char_dev_num, 1); +err_alloc_chrdev_region: +err_register_chrdev_region: + return retval; +} +/** + * syna_cdev_remove_sysfs() + * + * Remove the allocate cdev device node and release the resource + * + * @param + * [ in] tcm: the driver handle + * + * @return + * none. + */ +void syna_cdev_remove_sysfs(struct synaptics_ts_data *ts) +{ + if (!ts) { + pr_err("%s%s:Invalid tcm driver handle\n", SECLOG, __func__); + return; + } + + syna_sysfs_remove_dir(ts); + + synaptics_ts_pal_mem_set(ts->vendor_data->report_to_queue, 0, REPORT_TYPES); + syna_cdev_clean_queue(ts); + synaptics_ts_pal_mutex_free(&g_fifo_queue_mutex); + + ts->vendor_data->char_dev_ref_count = 0; + ts->vendor_data->proc_pid = 0; + + if (ts->vendor_data->device) { + device_destroy(ts->vendor_data->device_class, ts->vendor_data->char_dev_num); + class_destroy(ts->vendor_data->device_class); + cdev_del(&ts->vendor_data->char_dev); + unregister_chrdev_region(ts->vendor_data->char_dev_num, 1); + } + + synaptics_ts_buf_release(&g_cdev_cbuf); + + synaptics_ts_pal_mutex_free(&g_extif_mutex); + + ts->vendor_data->device_class = NULL; + + ts->vendor_data->device = NULL; +} + + diff --git a/drivers/input/sec_input/wacom/Kconfig b/drivers/input/sec_input/wacom/Kconfig new file mode 100755 index 000000000000..e9beb954815d --- /dev/null +++ b/drivers/input/sec_input/wacom/Kconfig @@ -0,0 +1,11 @@ +# +# Wacom configuration +# + +config INPUT_WACOM_WEZ02 + tristate "Wacom penabled i2c touchscreen" + depends on I2C + help + Say Y here if you have an Wacom penabled i2c touchscreen + connected to your system. + If unsure, say N. diff --git a/drivers/input/sec_input/wacom/Makefile b/drivers/input/sec_input/wacom/Makefile new file mode 100755 index 000000000000..0ada29d44071 --- /dev/null +++ b/drivers/input/sec_input/wacom/Makefile @@ -0,0 +1,6 @@ +TARGET = wez02 + +$(TARGET)-objs := wacom_i2c.o wacom_core.o wacom_sec.o wacom_elec.o wez02_flash.o +obj-$(CONFIG_INPUT_WACOM_WEZ02) += $(TARGET).o + +ccflags-y += -Wformat diff --git a/drivers/input/sec_input/wacom/wacom_core.c b/drivers/input/sec_input/wacom/wacom_core.c new file mode 100644 index 000000000000..b80ff6e2f50e --- /dev/null +++ b/drivers/input/sec_input/wacom/wacom_core.c @@ -0,0 +1,3368 @@ +#include "wacom_dev.h" + +// need for ble charge +struct wacom_data *g_wacom; + +struct notifier_block *g_nb_wac_camera; +EXPORT_SYMBOL(g_nb_wac_camera); + +void wacom_release(struct input_dev *input_dev) +{ + input_report_abs(input_dev, ABS_X, 0); + input_report_abs(input_dev, ABS_Y, 0); + input_report_abs(input_dev, ABS_PRESSURE, 0); + input_report_abs(input_dev, ABS_DISTANCE, 0); + + input_report_key(input_dev, BTN_STYLUS, 0); + input_report_key(input_dev, BTN_TOUCH, 0); + input_report_key(input_dev, BTN_TOOL_RUBBER, 0); + input_report_key(input_dev, BTN_TOOL_PEN, 0); + + input_sync(input_dev); +} + +void wacom_forced_release(struct wacom_data *wacom) +{ +#ifdef SUPPORT_STANDBY_MODE + struct timespec64 current_time; +#endif + +#if WACOM_PRODUCT_SHIP + if (wacom->pen_pressed) { + input_info(true, wacom->dev, "%s : [R] dd:%d,%d mc:%d & [HO] dd:%d,%d\n", + __func__, wacom->x - wacom->pressed_x, wacom->y - wacom->pressed_y, + wacom->mcount, wacom->x - wacom->hi_x, wacom->y - wacom->hi_y); +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&wacom->nb, NOTIFIER_WACOM_PEN_HOVER_OUT, NULL); +#endif + } else if (wacom->pen_prox) { + input_info(true, wacom->dev, "%s : [HO] dd:%d,%d mc:%d\n", + __func__, wacom->x - wacom->hi_x, wacom->y - wacom->hi_y, wacom->mcount); +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&wacom->nb, NOTIFIER_WACOM_PEN_HOVER_OUT, NULL); +#endif + } else { + input_info(true, wacom->dev, "%s : pen_prox(%d), pen_pressed(%d)\n", + __func__, wacom->pen_prox, wacom->pen_pressed); + } +#else + if (wacom->pen_pressed) { + input_info(true, wacom->dev, "%s : [R] lx:%d ly:%d dd:%d,%d mc:%d & [HO] dd:%d,%d\n", + __func__, wacom->x, wacom->y, + wacom->x - wacom->pressed_x, wacom->y - wacom->pressed_y, + wacom->mcount, wacom->x - wacom->hi_x, wacom->y - wacom->hi_y); +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&wacom->nb, NOTIFIER_WACOM_PEN_HOVER_OUT, NULL); +#endif + } else if (wacom->pen_prox) { + input_info(true, wacom->dev, "%s : [HO] lx:%d ly:%d dd:%d,%d mc:%d\n", + __func__, wacom->x, wacom->y, + wacom->x - wacom->hi_x, wacom->y - wacom->hi_y, wacom->mcount); +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&wacom->nb, NOTIFIER_WACOM_PEN_HOVER_OUT, NULL); +#endif + } else { + input_info(true, wacom->dev, "%s : pen_prox(%d), pen_pressed(%d)\n", + __func__, wacom->pen_prox, wacom->pen_pressed); + } +#endif + + wacom_release(wacom->input_dev); + +#ifdef SUPPORT_STANDBY_MODE + if (wacom->pen_pressed || wacom->pen_prox) { + ktime_get_real_ts64(¤t_time); + wacom->activate_time = current_time.tv_sec; + wacom->standby_state = WACOM_ORIGINAL_SCAN_RATE; + + input_info(true, wacom->dev, "%s: set activate time(%lld)\n", + __func__, wacom->activate_time); + } +#endif + + wacom->hi_x = 0; + wacom->hi_y = 0; + wacom->pressed_x = 0; + wacom->pressed_y = 0; + + wacom->pen_prox = 0; + wacom->pen_pressed = 0; + wacom->side_pressed = 0; + wacom->mcount = 0; +} + +int wacom_start_stop_cmd(struct wacom_data *wacom, int mode) +{ + int retry; + int ret = 0; + char buff; + + input_info(true, wacom->dev, "%s: mode (%d)\n", __func__, mode); + +reset_start: + if (mode == WACOM_STOP_CMD) { + buff = COM_SAMPLERATE_STOP; + } else if (mode == WACOM_START_CMD) { + buff = COM_SAMPLERATE_START; + } else if (mode == WACOM_STOP_AND_START_CMD) { + buff = COM_SAMPLERATE_STOP; + wacom->samplerate_state = WACOM_STOP_CMD; + } else { + input_info(true, wacom->dev, "%s: abnormal mode (%d)\n", __func__, mode); + return ret; + } + + retry = WACOM_CMD_RETRY; + do { + ret = wacom_send(wacom, &buff, 1); + sec_delay(50); + if (ret < 0) + input_err(true, wacom->dev, + "%s: failed to send 0x%02X(%d)\n", __func__, buff, retry); + else + break; + + } while (--retry); + + if (mode == WACOM_STOP_AND_START_CMD) { + mode = WACOM_START_CMD; + goto reset_start; + } + + if (ret == 1) + wacom->samplerate_state = mode; + + return ret; +} + +int wacom_checksum(struct wacom_data *wacom) +{ + int ret = 0, retry = 10; + int i = 0; + u8 buf[5] = { 0, }; + + while (retry--) { + buf[0] = COM_CHECKSUM; + ret = wacom_send(wacom, &buf[0], 1); + if (ret < 0) { + input_err(true, wacom->dev, "i2c fail, retry, %d\n", __LINE__); + continue; + } + + sec_delay(200); + + ret = wacom_recv(wacom, buf, 5); + if (ret < 0) { + input_err(true, wacom->dev, "i2c fail, retry, %d\n", __LINE__); + continue; + } + + if (buf[0] == 0x1F) + break; + + input_info(true, wacom->dev, "buf[0]: 0x%x, checksum retry\n", buf[0]); + } + + if (ret >= 0) { + input_info(true, wacom->dev, "received checksum %x, %x, %x, %x, %x\n", + buf[0], buf[1], buf[2], buf[3], buf[4]); + } + + for (i = 0; i < 5; ++i) { + if (buf[i] != wacom->fw_chksum[i]) { + input_info(true, wacom->dev, "checksum fail %dth %x %x\n", + i, buf[i], wacom->fw_chksum[i]); + break; + } + } + + wacom->checksum_result = (i == 5); + + return wacom->checksum_result; +} + +int wacom_query(struct wacom_data *wacom) +{ + u8 data[COM_QUERY_BUFFER] = { 0, }; + u8 *query = data + COM_QUERY_POS; + int read_size = COM_QUERY_BUFFER; + int ret; + int i; + + for (i = 0; i < RETRY_COUNT; i++) { + ret = wacom_recv(wacom, data, read_size); + if (ret < 0) { + input_err(true, wacom->dev, "%s: failed to recv\n", __func__); + continue; + } + + input_info(true, wacom->dev, "%s: %dth ret of wacom query=%d\n", __func__, i, ret); + + if (read_size != ret) { + input_err(true, wacom->dev, "%s: read size error %d of %d\n", __func__, ret, read_size); + continue; + } + + input_info(true, wacom->dev, + "(retry:%d) %X, %X, %X, %X, %X, %X, %X, %X, %X, %X, %X, %X, %X, %X, %X, %X\n", + i, query[0], query[1], query[2], query[3], query[4], query[5], + query[6], query[7], query[8], query[9], query[10], query[11], + query[12], query[13], query[14], query[15]); + + if (query[EPEN_REG_HEADER] == 0x0F) { + wacom->plat_data->img_version_of_ic[0] = query[EPEN_REG_MPUVER]; + wacom->plat_data->img_version_of_ic[1] = query[EPEN_REG_PROJ_ID]; + wacom->plat_data->img_version_of_ic[2] = query[EPEN_REG_FWVER1]; + wacom->plat_data->img_version_of_ic[3] = query[EPEN_REG_FWVER2]; + break; + } + } + + input_info(true, wacom->dev, + "%X, %X, %X, %X, %X, %X, %X, %X, %X, %X, %X, %X, %X, %X, %X, %X\n", + query[0], query[1], query[2], query[3], query[4], query[5], + query[6], query[7], query[8], query[9], query[10], query[11], + query[12], query[13], query[14], query[15]); + + if (ret < 0) { + input_err(true, wacom->dev, "%s: failed to read query\n", __func__); + for (i = 0; i < 4; i++) + wacom->plat_data->img_version_of_ic[i] = 0; + + wacom->query_status = false; + return ret; + } + + wacom->query_status = true; + + if (wacom->use_dt_coord == false) { + wacom->plat_data->max_x = ((u16)query[EPEN_REG_X1] << 8) + query[EPEN_REG_X2]; + wacom->plat_data->max_y = ((u16)query[EPEN_REG_Y1] << 8) + query[EPEN_REG_Y2]; + } + + wacom->max_pressure = ((u16)query[EPEN_REG_PRESSURE1] << 8) + query[EPEN_REG_PRESSURE2]; + wacom->max_x_tilt = query[EPEN_REG_TILT_X]; + wacom->max_y_tilt = query[EPEN_REG_TILT_Y]; + wacom->max_height = query[EPEN_REG_HEIGHT]; + wacom->bl_ver = query[EPEN_REG_BLVER]; + + input_info(true, wacom->dev, "use_dt_coord: %d, max_x: %d max_y: %d, max_pressure: %d\n", + wacom->use_dt_coord, wacom->plat_data->max_x, wacom->plat_data->max_y, + wacom->max_pressure); + input_info(true, wacom->dev, "fw_ver_ic=%02X%02X%02X%02X\n", + wacom->plat_data->img_version_of_ic[0], wacom->plat_data->img_version_of_ic[1], + wacom->plat_data->img_version_of_ic[2], wacom->plat_data->img_version_of_ic[3]); + input_info(true, wacom->dev, "bl: 0x%X, tiltX: %d, tiltY: %d, maxH: %d\n", + wacom->bl_ver, wacom->max_x_tilt, wacom->max_y_tilt, wacom->max_height); + + return 0; +} + +int wacom_set_sense_mode(struct wacom_data *wacom) +{ + int retval; + char data[4] = { 0, 0, 0, 0 }; + + input_info(true, wacom->dev, "%s cmd mod(%d)\n", __func__, wacom->wcharging_mode); + + if (wacom->wcharging_mode == 1) + data[0] = COM_LOW_SENSE_MODE; + else if (wacom->wcharging_mode == 3) + data[0] = COM_LOW_SENSE_MODE2; + else { + /* it must be 0 */ + data[0] = COM_NORMAL_SENSE_MODE; + } + + retval = wacom_send(wacom, &data[0], 1); + if (retval < 0) { + input_err(true, wacom->dev, "%s: failed to send wacom i2c mode, %d\n", __func__, retval); + return retval; + } + + sec_delay(60); + + retval = wacom_start_stop_cmd(wacom, WACOM_STOP_CMD); + if (retval < 0) { + input_err(true, wacom->dev, "%s: failed to set stop cmd, %d\n", + __func__, retval); + return retval; + } + + retval = wacom_start_stop_cmd(wacom, WACOM_START_CMD); + if (retval < 0) { + input_err(true, wacom->dev, "%s: failed to set start cmd, %d\n", + __func__, retval); + return retval; + } + + return retval; +} + +#ifdef SUPPORT_STANDBY_MODE +static void wacom_standby_mode_handler(struct wacom_data *wacom) +{ + struct timespec64 current_time; + long long diff_time; + char cmd; + int ret = 0; + + ktime_get_real_ts64(¤t_time); + diff_time = current_time.tv_sec - wacom->activate_time; + + input_info(true, wacom->dev, + "%s: standby_state(%d) activate_time(%lld), diff time(%lld)(%lldh %lldm %llds)\n", + __func__, wacom->standby_state, wacom->activate_time, diff_time, + (diff_time / 3600), ((diff_time % 3600) / 60), ((diff_time % 3600) % 60)); + + if ((diff_time >= SET_STANDBY_TIME) && (wacom->standby_state == WACOM_ORIGINAL_SCAN_RATE)) { + /*set standby mode */ + input_info(true, wacom->dev, "%s : set standby mode\n", __func__); + wacom->activate_time = 0; + + cmd = COM_SET_STANDBY_MODE; + ret = wacom_send(wacom, &cmd, 1); + if (ret < 0) + input_err(true, wacom->dev, "%s: failed to send standby mode %d\n", + __func__, ret); + + } else if ((diff_time < SET_STANDBY_TIME) && (wacom->standby_state == WACOM_LOW_SCAN_RATE)) { + /*set activated mode */ + input_info(true, wacom->dev, "%s : set active mode\n", __func__); + cmd = COM_SET_ACTIVATED_MODE; + ret = wacom_send(wacom, &cmd, 1); + if (ret < 0) + input_err(true, wacom->dev, "%s: failed to send activate mode %d\n", __func__, ret); + + } else { + input_info(true, wacom->dev, "%s: skip %s mode setting\n", __func__, + wacom->standby_state == WACOM_LOW_SCAN_RATE ? "stand by" : "normal"); + } +} +#endif + +static void wacom_handler_wait_resume_pdct_work(struct work_struct *work) +{ + struct wacom_data *wacom = container_of(work, struct wacom_data, irq_work_pdct); + struct irq_desc *desc = irq_to_desc(wacom->irq_pdct); + int ret; + + ret = wait_for_completion_interruptible_timeout(&wacom->plat_data->resume_done, + msecs_to_jiffies(SEC_TS_WAKE_LOCK_TIME)); + if (ret == 0) { + input_err(true, wacom->dev, "%s: LPM: pm resume is not handled(pdct)\n", __func__); + goto out; + } + if (ret < 0) { + input_err(true, wacom->dev, "%s: LPM: -ERESTARTSYS if interrupted(pdct), %d\n", __func__, ret); + goto out; + } + + if (desc && desc->action && desc->action->thread_fn) { + input_info(true, wacom->dev, "%s: run irq_pdct thread\n", __func__); + desc->action->thread_fn(wacom->irq_pdct, desc->action->dev_id); + } +out: + wacom_enable_irq(wacom, true); +} + +void wacom_select_survey_mode_without_pdct(struct wacom_data *wacom, bool enable) +{ + if (enable) { + if (wacom->epen_blocked || + (wacom->battery_saving_mode && !(wacom->function_result & EPEN_EVENT_PEN_OUT))) { + input_info(true, wacom->dev, "%s: %s power off\n", __func__, + wacom->epen_blocked ? "epen blocked" : "ps on & pen in"); + + if (!wacom->reset_is_on_going) { + wacom_enable_irq(wacom, false); + atomic_set(&wacom->plat_data->power_state, SEC_INPUT_STATE_POWER_OFF); + wacom_power(wacom, false); + } + + wacom->survey_mode = EPEN_SURVEY_MODE_NONE; + wacom->function_result &= ~EPEN_EVENT_SURVEY; + } else if (wacom->survey_mode) { + input_info(true, wacom->dev, "%s: exit aop mode\n", __func__); + wacom_set_survey_mode(wacom, EPEN_SURVEY_MODE_NONE); +#if defined(CONFIG_EPEN_WACOM_W9020) + wacom->reset_flag = true; + sec_delay(200); + input_info(true, wacom->dev, "%s: reset_flag %d\n", __func__, wacom->reset_flag); +#endif + } else { + input_info(true, wacom->dev, "%s: power on\n", __func__); + if (!wacom->reset_is_on_going) { + wacom_power(wacom, true); + atomic_set(&wacom->plat_data->power_state, SEC_INPUT_STATE_POWER_ON); + sec_delay(100); + } +#ifdef SUPPORT_STANDBY_MODE + wacom->standby_state = WACOM_LOW_SCAN_RATE; + wacom_standby_mode_handler(wacom); +#endif + if (!wacom->reset_is_on_going) + wacom_enable_irq(wacom, true); + } + } else { + if (wacom->epen_blocked || (wacom->battery_saving_mode && + !(wacom->function_result & EPEN_EVENT_PEN_OUT))) { + input_info(true, wacom->dev, "%s: %s power off\n", + __func__, wacom->epen_blocked ? "epen blocked" : "ps on & pen in"); + if (!wacom->reset_is_on_going) { + wacom_enable_irq(wacom, false); + atomic_set(&wacom->plat_data->power_state, SEC_INPUT_STATE_POWER_OFF); + wacom_power(wacom, false); + } + + wacom->survey_mode = EPEN_SURVEY_MODE_NONE; + wacom->function_result &= ~EPEN_EVENT_SURVEY; + } else if (!(wacom->function_set & EPEN_SETMODE_AOP)) { + input_info(true, wacom->dev, "%s: aop off & garage off. power off\n", __func__); + if (!wacom->reset_is_on_going) { + wacom_enable_irq(wacom, false); + atomic_set(&wacom->plat_data->power_state, SEC_INPUT_STATE_POWER_OFF); + wacom_power(wacom, false); + } + + wacom->survey_mode = EPEN_SURVEY_MODE_NONE; + wacom->function_result &= ~EPEN_EVENT_SURVEY; + } else { + /* aop on => (aod : screen off memo = 1:1 or 1:0 or 0:1) + * double tab & hover + button event will be occurred, + * but some of them will be skipped at reporting by mode + */ + input_info(true, wacom->dev, "%s: aop on. aop mode\n", __func__); + if (sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF) && !wacom->reset_is_on_going) { + input_info(true, wacom->dev, "%s: power on\n", __func__); + + wacom_power(wacom, true); + atomic_set(&wacom->plat_data->power_state, SEC_INPUT_STATE_POWER_ON); + sec_delay(100); + + wacom_enable_irq(wacom, true); + } + + wacom_set_survey_mode(wacom, EPEN_SURVEY_MODE_GARAGE_AOP); + } + } + + if (sec_input_cmp_ic_status(wacom->dev, CHECK_ON_LP)) { + input_info(true, wacom->dev, "%s: screen %s, survey mode:%d, result:%d\n", + __func__, enable ? "on" : "off", wacom->survey_mode, + wacom->function_result & EPEN_EVENT_SURVEY); + + if ((wacom->function_result & EPEN_EVENT_SURVEY) != wacom->survey_mode) { + input_err(true, wacom->dev, "%s: survey mode cmd failed\n", __func__); + + wacom_set_survey_mode(wacom, wacom->survey_mode); + } + } +} + +void wacom_select_survey_mode_with_pdct(struct wacom_data *wacom, bool enable) +{ + if (enable) { + if (wacom->epen_blocked || + (wacom->battery_saving_mode && !(wacom->function_result & EPEN_EVENT_PEN_OUT))) { + input_info(true, wacom->dev, "%s: %s & garage on. garage only mode\n", + __func__, wacom->epen_blocked ? "epen blocked" : "ps on & pen in"); + wacom_set_survey_mode(wacom, EPEN_SURVEY_MODE_GARAGE_ONLY); + } else if (wacom->survey_mode) { + input_info(true, wacom->dev, "%s: exit aop mode\n", __func__); + wacom_set_survey_mode(wacom, EPEN_SURVEY_MODE_NONE); + } else { + input_info(true, wacom->dev, "%s: power on\n", __func__); + wacom_power(wacom, true); + msleep(100); +#ifdef SUPPORT_STANDBY_MODE + wacom->standby_state = WACOM_LOW_SCAN_RATE; + wacom_standby_mode_handler(wacom); +#endif + wacom_enable_irq(wacom, true); + } + } else { + if (wacom->epen_blocked || (wacom->battery_saving_mode && + !(wacom->function_result & EPEN_EVENT_PEN_OUT))) { + input_info(true, wacom->dev, "%s: %s & garage on. garage only mode\n", + __func__, wacom->epen_blocked ? "epen blocked" : "ps on & pen in"); + wacom_set_survey_mode(wacom, EPEN_SURVEY_MODE_GARAGE_ONLY); + } else if (!(wacom->function_set & EPEN_SETMODE_AOP)) { + input_info(true, wacom->dev, "%s: aop off & garage on. garage only mode\n", __func__); + wacom_set_survey_mode(wacom, EPEN_SURVEY_MODE_GARAGE_ONLY); + } else { + /* aop on => (aod : screen off memo = 1:1 or 1:0 or 0:1) + * double tab & hover + button event will be occurred, + * but some of them will be skipped at reporting by mode + */ + input_info(true, wacom->dev, "%s: aop on. aop mode\n", __func__); + + if (sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF)) { + input_info(true, wacom->dev, "%s: power on\n", __func__); + + wacom_power(wacom, true); + msleep(100); + atomic_set(&wacom->plat_data->power_state, SEC_INPUT_STATE_POWER_ON); + wacom_enable_irq(wacom, true); + } + + wacom_set_survey_mode(wacom, EPEN_SURVEY_MODE_GARAGE_AOP); + } + } + + if (sec_input_cmp_ic_status(wacom->dev, CHECK_ON_LP)) { + input_info(true, wacom->dev, "%s: screen %s, survey mode:%d, result:%d\n", + __func__, enable ? "on" : "off", wacom->survey_mode, + wacom->function_result & EPEN_EVENT_SURVEY); + + if ((wacom->function_result & EPEN_EVENT_SURVEY) != wacom->survey_mode) { + input_err(true, wacom->dev, "%s: survey mode cmd failed\n", __func__); + + wacom_set_survey_mode(wacom, wacom->survey_mode); + } + } + +} + +void wacom_select_survey_mode(struct wacom_data *wacom, bool enable) +{ + mutex_lock(&wacom->mode_lock); + + if (wacom->use_garage) + wacom_select_survey_mode_with_pdct(wacom, enable); + else + wacom_select_survey_mode_without_pdct(wacom, enable); + + mutex_unlock(&wacom->mode_lock); +} + +int wacom_set_survey_mode(struct wacom_data *wacom, int mode) +{ + int retval; + char data[4] = { 0, 0, 0, 0 }; + + switch (mode) { + case EPEN_SURVEY_MODE_NONE: + data[0] = COM_SURVEY_EXIT; + break; + case EPEN_SURVEY_MODE_GARAGE_ONLY: + if (wacom->use_garage) { + data[0] = COM_SURVEY_GARAGE_ONLY; + } else { + input_err(true, wacom->dev, "%s: garage mode is not supported\n", __func__); + return -EPERM; + } + break; + case EPEN_SURVEY_MODE_GARAGE_AOP: + if ((wacom->function_set & EPEN_SETMODE_AOP_OPTION_AOD_LCD_ON) == EPEN_SETMODE_AOP_OPTION_AOD_LCD_ON) + data[0] = COM_SURVEY_SYNC_SCAN; + else + data[0] = COM_SURVEY_ASYNC_SCAN; + break; + default: + input_err(true, wacom->dev, "%s: wrong param %d\n", __func__, mode); + return -EINVAL; + } + + wacom->survey_mode = mode; + input_info(true, wacom->dev, "%s: ps %s & mode : %d cmd(0x%2X)\n", __func__, + wacom->battery_saving_mode ? "on" : "off", mode, data[0]); + + retval = wacom_send(wacom, &data[0], 1); + if (retval < 0) { + input_err(true, wacom->dev, "%s: failed to send data(%02x %d)\n", __func__, data[0], retval); + wacom->reset_flag = true; + + return retval; + } + + wacom->check_survey_mode = mode; + + if (mode) { + atomic_set(&wacom->plat_data->power_state, SEC_INPUT_STATE_LPM); + sec_delay(300); + } else { + atomic_set(&wacom->plat_data->power_state, SEC_INPUT_STATE_POWER_ON); + } + + wacom->reset_flag = false; + wacom->function_result &= ~EPEN_EVENT_SURVEY; + wacom->function_result |= mode; + + return 0; +} + +void wacom_pdct_survey_mode(struct wacom_data *wacom) +{ + if (wacom->epen_blocked || + (wacom->battery_saving_mode && !(wacom->function_result & EPEN_EVENT_PEN_OUT))) { + input_info(true, wacom->dev, + "%s: %s & garage on. garage only mode\n", __func__, + wacom->epen_blocked ? "epen blocked" : "ps on & pen in"); + + mutex_lock(&wacom->mode_lock); + wacom_set_survey_mode(wacom, + EPEN_SURVEY_MODE_GARAGE_ONLY); + mutex_unlock(&wacom->mode_lock); + } else if (atomic_read(&wacom->screen_on) && wacom->survey_mode) { + input_info(true, wacom->dev, + "%s: ps %s & pen %s & lcd on. normal mode\n", + __func__, wacom->battery_saving_mode ? "on" : "off", + (wacom->function_result & EPEN_EVENT_PEN_OUT) ? "out" : "in"); + + mutex_lock(&wacom->mode_lock); + wacom_set_survey_mode(wacom, EPEN_SURVEY_MODE_NONE); + mutex_unlock(&wacom->mode_lock); + } else { + input_info(true, wacom->dev, + "%s: ps %s & pen %s & lcd %s. keep current mode(%s)\n", + __func__, wacom->battery_saving_mode ? "on" : "off", + (wacom->function_result & EPEN_EVENT_PEN_OUT) ? "out" : "in", + atomic_read(&wacom->screen_on) ? "on" : "off", + wacom->function_result & EPEN_EVENT_SURVEY ? "survey" : "normal"); + } +} + +void forced_release_fullscan(struct wacom_data *wacom) +{ + input_info(true, wacom->dev, "%s: full scan OUT\n", __func__); + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + wacom->tsp_scan_mode = sec_input_notify(&wacom->nb, NOTIFIER_TSP_BLOCKING_RELEASE, NULL); + wacom->is_tsp_block = false; +#endif +} + +int wacom_power(struct wacom_data *wacom, bool on) +{ + return sec_input_power(wacom->dev, on); +} + +void wacom_reset_hw(struct wacom_data *wacom) +{ + atomic_set(&wacom->plat_data->power_state, SEC_INPUT_STATE_POWER_OFF); + wacom_power(wacom, false); + /* recommended delay in spec */ + sec_delay(100); + wacom_power(wacom, true); + atomic_set(&wacom->plat_data->power_state, SEC_INPUT_STATE_POWER_ON); + + sec_delay(100); +} + +void wacom_compulsory_flash_mode(struct wacom_data *wacom, bool enable) +{ + gpio_direction_output(wacom->fwe_gpio, enable); + input_info(true, wacom->dev, "%s : enable(%d) fwe(%d)\n", + __func__, enable, gpio_get_value(wacom->fwe_gpio)); +} + +void wacom_enable_irq(struct wacom_data *wacom, bool enable) +{ + struct irq_desc *desc = irq_to_desc(wacom->irq); + + if (desc == NULL) { + input_err(true, wacom->dev, "%s : irq desc is NULL\n", __func__); + return; + } + + mutex_lock(&wacom->irq_lock); + if (enable) { + while (desc->depth > 0) + enable_irq(wacom->irq); + } else { + disable_irq(wacom->irq); + } + + if (gpio_is_valid(wacom->pdct_gpio)) { + struct irq_desc *desc_pdct = irq_to_desc(wacom->irq_pdct); + + if (enable) { + while (desc_pdct->depth > 0) + enable_irq(wacom->irq_pdct); + } else { + disable_irq(wacom->irq_pdct); + } + } + + if (gpio_is_valid(wacom->esd_detect_gpio)) { + struct irq_desc *desc_esd = irq_to_desc(wacom->irq_esd_detect); + + if (enable) { + while (desc_esd->depth > 0) + enable_irq(wacom->irq_esd_detect); + } else { + disable_irq(wacom->irq_esd_detect); + } + } + mutex_unlock(&wacom->irq_lock); +} + +static void wacom_enable_irq_wake(struct wacom_data *wacom, bool enable) +{ + struct irq_desc *desc = irq_to_desc(wacom->irq); + + if (enable) { + while (desc->wake_depth < 1) + enable_irq_wake(wacom->irq); + } else { + while (desc->wake_depth > 0) + disable_irq_wake(wacom->irq); + } + + if (gpio_is_valid(wacom->pdct_gpio)) { + struct irq_desc *desc_pdct = irq_to_desc(wacom->irq_pdct); + + if (enable) { + while (desc_pdct->wake_depth < 1) + enable_irq_wake(wacom->irq_pdct); + } else { + while (desc_pdct->wake_depth > 0) + disable_irq_wake(wacom->irq_pdct); + } + } + + if (gpio_is_valid(wacom->esd_detect_gpio)) { + struct irq_desc *desc_esd = irq_to_desc(wacom->irq_esd_detect); + + if (enable) { + while (desc_esd->wake_depth < 1) + enable_irq_wake(wacom->irq_esd_detect); + } else { + while (desc_esd->wake_depth > 0) + disable_irq_wake(wacom->irq_esd_detect); + } + } +} + +void wacom_wakeup_sequence(struct wacom_data *wacom) +{ + int retry = 1; + int ret; + + mutex_lock(&wacom->lock); +#if WACOM_SEC_FACTORY + if (wacom->fac_garage_mode) + input_info(true, wacom->dev, "%s: garage mode\n", __func__); +#endif + if (!wacom->reset_flag && wacom->ble_disable_flag) { + input_info(true, wacom->dev, + "%s: ble is diabled & send enable cmd!\n", __func__); + + mutex_lock(&wacom->ble_charge_mode_lock); + wacom_ble_charge_mode(wacom, EPEN_BLE_C_ENABLE); + wacom->ble_disable_flag = false; + mutex_unlock(&wacom->ble_charge_mode_lock); + } + +reset: + if (wacom->reset_flag) { + input_info(true, wacom->dev, "%s: IC reset start\n", __func__); + + wacom->abnormal_reset_count++; + wacom->reset_flag = false; + wacom->survey_mode = EPEN_SURVEY_MODE_NONE; + wacom->function_result &= ~EPEN_EVENT_SURVEY; + + wacom_enable_irq(wacom, false); + + wacom_reset_hw(wacom); + + if (gpio_is_valid(wacom->pdct_gpio)) { + wacom->pen_pdct = gpio_get_value(wacom->pdct_gpio); + + input_info(true, wacom->dev, "%s: IC reset end, pdct(%d)\n", __func__, wacom->pen_pdct); + + if (wacom->use_garage) { + if (wacom->pen_pdct) + wacom->function_result &= ~EPEN_EVENT_PEN_OUT; + else + wacom->function_result |= EPEN_EVENT_PEN_OUT; + } + } + + input_info(true, wacom->dev, "%s: IC reset end\n", __func__); + + if (wacom->support_cover_noti || + wacom->support_set_display_mode) { + wacom_swap_compensation(wacom, wacom->compensation_type); + } + + wacom_enable_irq(wacom, true); + } + + wacom_select_survey_mode(wacom, true); + + if (wacom->reset_flag && retry--) + goto reset; + + if (wacom->wcharging_mode) + wacom_set_sense_mode(wacom); + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + if (wacom->tsp_scan_mode < 0) { + wacom->tsp_scan_mode = sec_input_notify(&wacom->nb, NOTIFIER_TSP_BLOCKING_RELEASE, NULL); + wacom->is_tsp_block = false; + } +#endif + + if (!gpio_get_value(wacom->plat_data->irq_gpio)) { + u8 data[COM_COORD_NUM + 1] = { 0, }; + + input_info(true, wacom->dev, "%s: irq was enabled\n", __func__); + + ret = wacom_recv(wacom, data, COM_COORD_NUM + 1); + if (ret < 0) + input_err(true, wacom->dev, "%s: failed to receive\n", __func__); + + input_info(true, wacom->dev, + "%x %x %x %x %x, %x %x %x %x %x, %x %x %x\n", + data[0], data[1], data[2], data[3], data[4], data[5], + data[6], data[7], data[8], data[9], data[10], + data[11], data[12]); + } + + if (!wacom->samplerate_state) { + char cmd = COM_SAMPLERATE_START; + + input_info(true, wacom->dev, "%s: samplerate state is %d, need to recovery\n", + __func__, wacom->samplerate_state); + + ret = wacom_send(wacom, &cmd, 1); + if (ret < 0) + input_err(true, wacom->dev, "%s: failed to sned start cmd %d\n", __func__, ret); + else + wacom->samplerate_state = true; + } + + if (gpio_is_valid(wacom->pdct_gpio)) { + input_info(true, wacom->dev, + "%s: i(%d) p(%d) f_set(0x%x) f_ret(0x%x) samplerate(%d)\n", + __func__, gpio_get_value(wacom->plat_data->irq_gpio), + gpio_get_value(wacom->pdct_gpio), wacom->function_set, + wacom->function_result, wacom->samplerate_state); + } else { + input_info(true, wacom->dev, + "%s: i(%d) f_set(0x%x) f_ret(0x%x) samplerate(%d)\n", + __func__, gpio_get_value(wacom->plat_data->irq_gpio), wacom->function_set, + wacom->function_result, wacom->samplerate_state); + } + + wacom_enable_irq_wake(wacom, false); + + if (wacom->pdct_lock_fail) { + wacom->pdct_lock_fail = false; + wacom_pdct_survey_mode(wacom); + } + + mutex_unlock(&wacom->lock); + + input_info(true, wacom->dev, "%s: end\n", __func__); +} + +void wacom_sleep_sequence(struct wacom_data *wacom) +{ + int retry = 1; + + mutex_lock(&wacom->lock); +#if WACOM_SEC_FACTORY + if (wacom->fac_garage_mode) + input_info(true, wacom->dev, "%s: garage mode\n", __func__); + +#endif + +reset: + if (wacom->reset_flag) { + input_info(true, wacom->dev, "%s: IC reset start\n", __func__); + + wacom->abnormal_reset_count++; + wacom->reset_flag = false; + wacom->survey_mode = EPEN_SURVEY_MODE_NONE; + wacom->function_result &= ~EPEN_EVENT_SURVEY; + + wacom_enable_irq(wacom, false); + + wacom_reset_hw(wacom); + + input_info(true, wacom->dev, "%s : IC reset end\n", __func__); + + wacom_enable_irq(wacom, true); + } + + wacom_select_survey_mode(wacom, false); + + /* release pen, if it is pressed */ + if (wacom->pen_pressed || wacom->side_pressed || wacom->pen_prox) + wacom_forced_release(wacom); + + forced_release_fullscan(wacom); + + if (wacom->reset_flag && retry--) + goto reset; + + if (sec_input_cmp_ic_status(wacom->dev, CHECK_ON_LP)) + wacom_enable_irq_wake(wacom, true); + + if (wacom->pdct_lock_fail) { + wacom_pdct_survey_mode(wacom); + wacom->pdct_lock_fail = false; + } + + mutex_unlock(&wacom->lock); + + input_info(true, wacom->dev, "%s end\n", __func__); +} + +static void wacom_reply_handler(struct wacom_data *wacom, char *data) +{ + char pack_sub_id; +#if !WACOM_SEC_FACTORY && WACOM_PRODUCT_SHIP + int ret; +#endif + + pack_sub_id = data[1]; + + switch (pack_sub_id) { + case SWAP_PACKET: + input_info(true, wacom->dev, "%s: SWAP_PACKET received()\n", __func__); + break; + case ELEC_TEST_PACKET: + wacom->check_elec++; + + input_info(true, wacom->dev, "%s: ELEC TEST PACKET received(%d)\n", __func__, wacom->check_elec); + +#if !WACOM_SEC_FACTORY && WACOM_PRODUCT_SHIP + ret = wacom_start_stop_cmd(wacom, WACOM_STOP_AND_START_CMD); + if (ret < 0) { + input_err(true, wacom->dev, "%s: failed to set stop-start cmd, %d\n", + __func__, ret); + return; + } +#endif + break; + default: + input_info(true, wacom->dev, "%s: unexpected packet %d\n", __func__, pack_sub_id); + break; + }; +} + +static void wacom_cmd_noti_handler(struct wacom_data *wacom, char *data) +{ +#ifdef SUPPORT_STANDBY_MODE + if (data[3] == COM_SURVEY_EXIT || data[3] == COM_SURVEY_SYNC_SCAN) { + wacom->standby_enable = (data[4] & 0x10) >> 4; + wacom->standby_state = data[4] & 0x0F; + input_info(true, wacom->dev, "%s: noti ack packet(0x%X) %s state:%d(en:%d)\n", + __func__, data[3], + wacom->standby_state == WACOM_LOW_SCAN_RATE ? "standby" : "normal", + wacom->standby_state, wacom->standby_enable); + } + /* only control standby mode in COM_SURVEY_EXIT */ + if (data[3] == COM_SURVEY_EXIT) + wacom_standby_mode_handler(wacom); +#endif + +#if defined(CONFIG_EPEN_WACOM_W9020) + wacom->reset_flag = false; + input_info(true, wacom->dev, "%s: reset_flag %d\n", __func__, wacom->reset_flag); +#endif +} + +static void wacom_block_tsp_scan(struct wacom_data *wacom, char *data) +{ + bool tsp; + u8 wacom_mode, scan_mode; + + wacom_mode = (data[2] & 0xF0) >> 4; + scan_mode = data[2] & 0x0F; + tsp = !!(data[5] & 0x80); + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + if (tsp && !wacom->is_tsp_block) { + input_info(true, wacom->dev, "%s: tsp:%d wacom mode:%d scan mode:%d\n", + __func__, tsp, wacom_mode, scan_mode); + wacom->is_tsp_block = true; + wacom->tsp_scan_mode = sec_input_notify(&wacom->nb, NOTIFIER_TSP_BLOCKING_REQUEST, NULL); + wacom->tsp_block_cnt++; + } else if (!tsp && wacom->is_tsp_block) { + input_info(true, wacom->dev, "%s: tsp:%d wacom mode:%d scan mode:%d\n", + __func__, tsp, wacom_mode, scan_mode); + wacom->tsp_scan_mode = sec_input_notify(&wacom->nb, NOTIFIER_TSP_BLOCKING_RELEASE, NULL); + wacom->is_tsp_block = false; + } +#endif +} + +static void wacom_noti_handler(struct wacom_data *wacom, char *data) +{ + char pack_sub_id; + + pack_sub_id = data[1]; + + switch (pack_sub_id) { + case ERROR_PACKET: + // have to check it later! + input_err(true, wacom->dev, "%s: ERROR_PACKET\n", __func__); + break; + case TABLE_SWAP_PACKET: + case EM_NOISE_PACKET: + break; + case TSP_STOP_PACKET: + wacom_block_tsp_scan(wacom, data); + break; + case OOK_PACKET: + if (data[4] & 0x80) + input_err(true, wacom->dev, "%s: OOK Fail!\n", __func__); + break; + case CMD_PACKET: + wacom_cmd_noti_handler(wacom, data); + break; + case GCF_PACKET: + wacom->ble_charging_state = false; +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&wacom->nb, NOTIFIER_WACOM_PEN_CHARGING_FINISHED, NULL); +#endif + break; +#ifdef SUPPORT_STANDBY_MODE + case STANDBY_PACKET: + //print for debugging + /* standby_enable : support standby_enable or not + * standby state - 0 : original scan rate + * 1 : set standby mode + */ + wacom->standby_enable = (data[3] & 0x10) >> 4; + wacom->standby_state = data[3] & 0x0F; + input_info(true, wacom->dev, "%s: stand-by noti packet - state:%d(en:%d)\n", + __func__, wacom->standby_state, wacom->standby_enable); + break; +#endif + case ESD_DETECT_PACKET: + wacom->esd_packet_count++; + input_err(true, wacom->dev, "%s: DETECT ESD(%d)\n", __func__, wacom->esd_packet_count); + if (wacom->reset_is_on_going) { + input_info(true, wacom->dev, "%s: already reset on going:%d\n", + __func__, wacom->reset_is_on_going); + } else { + disable_irq_nosync(wacom->irq); + disable_irq_nosync(wacom->irq_esd_detect); + wacom->reset_is_on_going = true; + cancel_delayed_work_sync(&wacom->esd_reset_work); + schedule_delayed_work(&wacom->esd_reset_work, msecs_to_jiffies(1)); + } + break; + default: + input_err(true, wacom->dev, "%s: unexpected packet %d\n", __func__, pack_sub_id); + break; + }; +} + +static void wacom_aop_handler(struct wacom_data *wacom, char *data) +{ + bool stylus, prox = false; + s16 x, y; + u8 wacom_mode, wakeup_id; + + if (atomic_read(&wacom->screen_on) || !wacom->survey_mode || + !(wacom->function_set & EPEN_SETMODE_AOP)) { + input_info(true, wacom->dev, "%s: unexpected status(%d %d %d)\n", + __func__, atomic_read(&wacom->screen_on), wacom->survey_mode, + wacom->function_set & EPEN_SETMODE_AOP); + return; + } + + prox = !!(data[0] & 0x10); + x = ((u16)data[1] << 8) + (u16)data[2]; + y = ((u16)data[3] << 8) + (u16)data[4]; + wacom_mode = (data[6] & 0xF0) >> 4; + wakeup_id = data[6] & 0x0F; + + stylus = !!(data[0] & 0x20); + + if (data[0] & 0x40) + wacom->tool = BTN_TOOL_RUBBER; + else + wacom->tool = BTN_TOOL_PEN; + + /* [for checking */ + input_info(true, wacom->dev, "wacom mode %d, wakeup id %d\n", wacom_mode, wakeup_id); + /* for checking] */ + + if (stylus && (wakeup_id == HOVER_WAKEUP) && + (wacom->function_set & EPEN_SETMODE_AOP_OPTION_SCREENOFFMEMO)) { + input_info(true, wacom->dev, "Hover & Side Button detected\n"); + + input_report_key(wacom->input_dev, KEY_WAKEUP_UNLOCK, 1); + input_sync(wacom->input_dev); + + input_report_key(wacom->input_dev, KEY_WAKEUP_UNLOCK, 0); + input_sync(wacom->input_dev); + + wacom->survey_pos.id = EPEN_POS_ID_SCREEN_OF_MEMO; + wacom->survey_pos.x = x; + wacom->survey_pos.y = y; + } else if (wakeup_id == DOUBLETAP_WAKEUP) { + if ((wacom->function_set & EPEN_SETMODE_AOP_OPTION_AOD_LCD_ON) == EPEN_SETMODE_AOP_OPTION_AOD_LCD_ON) { + input_info(true, wacom->dev, "Double Tab detected in AOD\n"); + + /* make press / release event for AOP double tab gesture */ + input_report_abs(wacom->input_dev, ABS_X, x); + input_report_abs(wacom->input_dev, ABS_Y, y); + input_report_abs(wacom->input_dev, ABS_PRESSURE, 1); + input_report_key(wacom->input_dev, BTN_TOUCH, 1); + input_report_key(wacom->input_dev, wacom->tool, 1); + input_sync(wacom->input_dev); + + input_report_abs(wacom->input_dev, ABS_PRESSURE, 0); + input_report_key(wacom->input_dev, BTN_TOUCH, 0); + input_report_key(wacom->input_dev, wacom->tool, 0); + input_sync(wacom->input_dev); +#if WACOM_PRODUCT_SHIP + input_info(true, wacom->dev, "[P/R] tool:%x\n", + wacom->tool); +#else + input_info(true, wacom->dev, + "[P/R] x:%d y:%d tool:%x\n", x, y, wacom->tool); +#endif + } else if (wacom->function_set & EPEN_SETMODE_AOP_OPTION_AOT) { + input_info(true, wacom->dev, "Double Tab detected\n"); + + input_report_key(wacom->input_dev, KEY_HOMEPAGE, 1); + input_sync(wacom->input_dev); + input_report_key(wacom->input_dev, KEY_HOMEPAGE, 0); + input_sync(wacom->input_dev); + } + } else { + input_info(true, wacom->dev, + "unexpected AOP data : %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x\n", + data[0], data[1], data[2], data[3], data[4], data[5], + data[6], data[7], data[8], data[9], data[10], data[11], + data[12], data[13], data[14], data[15]); + } +} + +#define EPEN_LOCATION_DETECT_SIZE 6 +void epen_location_detect(struct wacom_data *wacom, char *loc, int x, int y) +{ + struct sec_ts_plat_data *plat_data = wacom->plat_data; + int i; + int max_x = plat_data->max_x; + int max_y = plat_data->max_y; + + if (plat_data->area_indicator == 0) { + /* approximately value */ + plat_data->area_indicator = max_y * 4 / 100; + plat_data->area_navigation = max_y * 6 / 100; + plat_data->area_edge = max_y * 3 / 100; + + input_raw_info(true, wacom->dev, + "MAX XY(%d/%d), area_edge %d, area_indicator %d, area_navigation %d\n", + max_x, max_y, plat_data->area_edge, + plat_data->area_indicator, plat_data->area_navigation); + } + + for (i = 0; i < EPEN_LOCATION_DETECT_SIZE; ++i) + loc[i] = 0; + + if (x < plat_data->area_edge) + strcat(loc, "E."); + else if (x < (max_x - plat_data->area_edge)) + strcat(loc, "C."); + else + strcat(loc, "e."); + + if (y < plat_data->area_indicator) + strcat(loc, "S"); + else if (y < (max_y - plat_data->area_navigation)) + strcat(loc, "C"); + else + strcat(loc, "N"); +} + +static void wacom_coord_handler(struct wacom_data *wacom, char *data) +{ + bool prox = false; + bool rdy = false; + bool stylus; + char location[EPEN_LOCATION_DETECT_SIZE] = { 0, }; + struct timespec64 current_time; + struct input_dev *input = wacom->input_dev; + + ktime_get_real_ts64(¤t_time); + + if (wacom->debug_flag & WACOM_DEBUG_PRINT_COORDEVENT) { + input_info(true, wacom->dev, + "%s : %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x\n", + __func__, data[0], data[1], data[2], data[3], data[4], data[5], + data[6], data[7], data[8], data[9], data[10], data[11], + data[12], data[13], data[14], data[15]); + } + + rdy = !!(data[0] & 0x80); + prox = !!(data[0] & 0x10); + + wacom->x = ((u16)data[1] << 8) + (u16)data[2]; + wacom->y = ((u16)data[3] << 8) + (u16)data[4]; + wacom->pressure = ((u16)(data[5] & 0x0F) << 8) + (u16)data[6]; + wacom->height = (u8)data[7]; + wacom->tilt_x = (s8)data[8]; + wacom->tilt_y = (s8)data[9]; + + if (rdy) { + int max_x = wacom->plat_data->max_x; + int max_y = wacom->plat_data->max_y; + + /* validation check */ + if (unlikely(wacom->x > max_x || wacom->y > max_y)) { +#if WACOM_PRODUCT_SHIP + input_info(true, wacom->dev, "%s : Abnormal raw data x & y\n", __func__); +#else + input_info(true, wacom->dev, "%s : Abnormal raw data x=%d, y=%d\n", + __func__, wacom->x, wacom->y); +#endif + return; + } + + stylus = !!(data[0] & 0x20); + if (data[0] & 0x40) + wacom->tool = BTN_TOOL_RUBBER; + else + wacom->tool = BTN_TOOL_PEN; + + if (!wacom->pen_prox) { + wacom->pen_prox = true; + + epen_location_detect(wacom, location, wacom->x, wacom->y); + wacom->hi_x = wacom->x; + wacom->hi_y = wacom->y; +#if WACOM_PRODUCT_SHIP + input_info(true, wacom->dev, "[HI] loc:%s (%s%s)\n", location, + input == wacom->input_dev_unused ? "un_" : "", + wacom->tool == BTN_TOOL_PEN ? "p" : "r"); +#else + input_info(true, wacom->dev, "[HI] x:%d y:%d loc:%s (%s%s) 0x%02x\n", + wacom->x, wacom->y, location, + input == wacom->input_dev_unused ? "unused_" : "", + wacom->tool == BTN_TOOL_PEN ? "pen" : "rubber", data[0]); +#endif +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&wacom->nb, NOTIFIER_WACOM_PEN_HOVER_IN, NULL); +#endif + return; + } + + /* report info */ + input_report_abs(input, ABS_X, wacom->x); + input_report_abs(input, ABS_Y, wacom->y); + input_report_key(input, BTN_STYLUS, stylus); + input_report_key(input, BTN_TOUCH, prox); + + input_report_abs(input, ABS_PRESSURE, wacom->pressure); + input_report_abs(input, ABS_DISTANCE, wacom->height); + input_report_abs(input, ABS_TILT_X, wacom->tilt_x); + input_report_abs(input, ABS_TILT_Y, wacom->tilt_y); + input_report_key(input, wacom->tool, 1); + input_sync(input); + + if (wacom->debug_flag & WACOM_DEBUG_PRINT_ALLEVENT) + input_info(true, wacom->dev, "[A] x:%d y:%d, p:%d, h:%d, tx:%d, ty:%d\n", + wacom->x, wacom->y, wacom->pressure, wacom->height, + wacom->tilt_x, wacom->tilt_y); + + wacom->mcount++; + + /* log */ + if (prox && !wacom->pen_pressed) { + epen_location_detect(wacom, location, wacom->x, wacom->y); + wacom->pressed_x = wacom->x; + wacom->pressed_y = wacom->y; + +#if WACOM_PRODUCT_SHIP + input_info(true, wacom->dev, "[P] p:%d loc:%s tool:%x mc:%d\n", + wacom->pressure, location, wacom->tool, wacom->mcount); +#else + input_info(true, wacom->dev, + "[P] x:%d y:%d p:%d loc:%s tool:%x mc:%d\n", + wacom->x, wacom->y, wacom->pressure, location, wacom->tool, wacom->mcount); +#endif + } else if (!prox && wacom->pen_pressed) { + epen_location_detect(wacom, location, wacom->x, wacom->y); +#if WACOM_PRODUCT_SHIP + input_info(true, wacom->dev, "[R] dd:%d,%d loc:%s tool:%x mc:%d\n", + wacom->x - wacom->pressed_x, wacom->y - wacom->pressed_y, + location, wacom->tool, wacom->mcount); +#else + input_info(true, wacom->dev, + "[R] lx:%d ly:%d dd:%d,%d loc:%s tool:%x mc:%d\n", + wacom->x, wacom->y, + wacom->x - wacom->pressed_x, wacom->y - wacom->pressed_y, + location, wacom->tool, wacom->mcount); +#endif + wacom->pressed_x = wacom->pressed_y = 0; + } + + if (wacom->pen_pressed) { + long msec_diff; + int sec_diff; + int time_diff; + int x_diff; + int y_diff; + + msec_diff = (current_time.tv_nsec - wacom->tv_nsec) / 1000000; + sec_diff = current_time.tv_sec - wacom->tv_sec; + time_diff = sec_diff * 1000 + (int)msec_diff; + if (time_diff > TIME_MILLS_MAX) + input_info(true, wacom->dev, "time_diff: %d\n", time_diff); + + x_diff = wacom->previous_x - wacom->x; + y_diff = wacom->previous_y - wacom->y; + if (x_diff < DIFF_MIN || x_diff > DIFF_MAX || y_diff < DIFF_MIN || y_diff > DIFF_MAX) + input_info(true, wacom->dev, "x_diff: %d, y_diff: %d\n", x_diff, y_diff); + } + wacom->pen_pressed = prox; + + wacom->previous_x = wacom->x; + wacom->previous_y = wacom->y; + + /* check side */ + if (stylus && !wacom->side_pressed) + input_info(true, wacom->dev, "side on\n"); + else if (!stylus && wacom->side_pressed) + input_info(true, wacom->dev, "side off\n"); + + wacom->side_pressed = stylus; + } else { + if (wacom->pen_prox) { + input_report_abs(input, ABS_PRESSURE, 0); + input_report_key(input, BTN_STYLUS, 0); + input_report_key(input, BTN_TOUCH, 0); + /* prevent invalid operation of input booster */ + input_sync(input); + + input_report_abs(input, ABS_DISTANCE, 0); + input_report_key(input, BTN_TOOL_RUBBER, 0); + input_report_key(input, BTN_TOOL_PEN, 0); + input_sync(input); + + epen_location_detect(wacom, location, wacom->x, wacom->y); + + if (wacom->pen_pressed) { +#if WACOM_PRODUCT_SHIP + input_info(true, wacom->dev, + "[R] dd:%d,%d loc:%s tool:%x mc:%d & [HO] dd:%d,%d\n", + wacom->x - wacom->pressed_x, wacom->y - wacom->pressed_y, + location, wacom->tool, wacom->mcount, + wacom->x - wacom->hi_x, wacom->y - wacom->hi_y); +#else + input_info(true, wacom->dev, + "[R] lx:%d ly:%d dd:%d,%d loc:%s tool:%x mc:%d & [HO] dd:%d,%d\n", + wacom->x, wacom->y, + wacom->x - wacom->pressed_x, wacom->y - wacom->pressed_y, + location, wacom->tool, wacom->mcount, + wacom->x - wacom->hi_x, wacom->y - wacom->hi_y); +#endif + } else { +#if WACOM_PRODUCT_SHIP + input_info(true, wacom->dev, "[HO] dd:%d,%d loc:%s mc:%d\n", + wacom->x - wacom->hi_x, wacom->y - wacom->hi_y, + location, wacom->mcount); + +#else + input_info(true, wacom->dev, "[HO] lx:%d ly:%d dd:%d,%d loc:%s mc:%d\n", + wacom->x, wacom->y, + wacom->x - wacom->hi_x, wacom->y - wacom->hi_y, + location, wacom->mcount); +#endif + wacom_release(wacom->input_dev); + } +#ifdef SUPPORT_STANDBY_MODE +// ktime_get_real_ts64(¤t_time); + wacom->activate_time = current_time.tv_sec; + wacom->standby_state = WACOM_ORIGINAL_SCAN_RATE; + + input_info(true, wacom->dev, "%s: set activate time(%lld)\n", + __func__, wacom->activate_time); +#endif +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&wacom->nb, NOTIFIER_WACOM_PEN_HOVER_OUT, NULL); +#endif + wacom->pressed_x = wacom->pressed_y = wacom->hi_x = wacom->hi_y = 0; + + } + + wacom->pen_prox = 0; + wacom->pen_pressed = 0; + wacom->side_pressed = 0; + wacom->mcount = 0; + } + + wacom->tv_sec = current_time.tv_sec; + wacom->tv_nsec = current_time.tv_nsec; +} + +static int wacom_event_handler(struct wacom_data *wacom) +{ + int ret; + char pack_id; + bool debug = true; + char buff[COM_COORD_NUM + 1] = { 0, }; + + ret = wacom_recv(wacom, buff, COM_COORD_NUM + 1); + if (ret < 0) { + input_err(true, wacom->dev, "%s: read failed\n", __func__); + return ret; + } + + pack_id = buff[0] & 0x0F; + + switch (pack_id) { + case COORD_PACKET: + wacom_coord_handler(wacom, buff); + debug = false; + break; + case AOP_PACKET: + wacom_aop_handler(wacom, buff); + break; + case NOTI_PACKET: + wacom->report_scan_seq = (buff[2] & 0xF0) >> 4; + wacom_noti_handler(wacom, buff); + break; + case REPLY_PACKET: + wacom_reply_handler(wacom, buff); + break; + case SEPC_PACKET: + break; + default: + input_info(true, wacom->dev, "%s: unexpected packet %d\n", + __func__, pack_id); + break; + }; + + if (debug) { + input_info(true, wacom->dev, + "%x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x / s:%d\n", + buff[0], buff[1], buff[2], buff[3], buff[4], buff[5], + buff[6], buff[7], buff[8], buff[9], buff[10], buff[11], + buff[12], buff[13], buff[14], buff[15], + pack_id == NOTI_PACKET ? wacom->report_scan_seq : 0); + } + + return ret; +} + +static irqreturn_t wacom_interrupt(int irq, void *dev_id) +{ + struct wacom_data *wacom = dev_id; + int ret = 0; + + ret = sec_input_handler_start(wacom->dev); + if (ret < 0) + return IRQ_HANDLED; + + ret = wacom_event_handler(wacom); + if (ret < 0) { + wacom_forced_release(wacom); + forced_release_fullscan(wacom); + wacom->reset_flag = true; + } + + return IRQ_HANDLED; +} + +static irqreturn_t wacom_interrupt_pdct(int irq, void *dev_id) +{ + struct wacom_data *wacom = dev_id; + + if (wacom->use_garage == false) { + input_err(true, wacom->dev, "%s: not support pdct skip!\n", __func__); + return IRQ_HANDLED; + } + + if (wacom->query_status == false) + return IRQ_HANDLED; + +#if !WACOM_SEC_FACTORY + if (wacom->support_garage_open_test && + wacom->garage_connection_check == false) { + input_err(true, wacom->dev, "%s: garage_connection_check fail skip(%d)!!\n", + __func__, gpio_get_value(wacom->pdct_gpio)); + return IRQ_HANDLED; + } +#endif + + if (wacom->pm_suspend) { + __pm_wakeup_event(wacom->wacom_ws, SEC_TS_WAKE_LOCK_TIME); + + if (!wacom->plat_data->resume_done.done) { + if (!IS_ERR_OR_NULL(wacom->irq_workqueue_pdct)) { + input_info(true, wacom->dev, "%s: disable pdct irq and queue waiting work\n", __func__); + disable_irq_nosync(wacom->irq_pdct); + queue_work(wacom->irq_workqueue_pdct, &wacom->irq_work_pdct); + } else { + input_err(true, wacom->dev, "%s: irq_pdct_workqueue not exist\n", __func__); + } + return IRQ_HANDLED; + } + } + + wacom->pen_pdct = gpio_get_value(wacom->pdct_gpio); + if (wacom->pen_pdct) + wacom->function_result &= ~EPEN_EVENT_PEN_OUT; + else + wacom->function_result |= EPEN_EVENT_PEN_OUT; + + input_info(true, wacom->dev, "%s: pen is %s (%d)\n", + __func__, wacom->pen_pdct ? "IN" : "OUT", + gpio_get_value(wacom->plat_data->irq_gpio)); + + input_report_switch(wacom->input_dev, SW_PEN_INSERT, (wacom->function_result & EPEN_EVENT_PEN_OUT)); + input_sync(wacom->input_dev); + + if (wacom->function_result & EPEN_EVENT_PEN_OUT) + wacom->pen_out_count++; + + if (!mutex_trylock(&wacom->lock)) { + input_err(true, wacom->dev, "%s: mutex lock fail!\n", __func__); + wacom->pdct_lock_fail = true; + goto irq_ret; + } + + wacom_pdct_survey_mode(wacom); + mutex_unlock(&wacom->lock); + +irq_ret: +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + if (wacom->pen_pdct) + sec_input_notify(&wacom->nb, NOTIFIER_WACOM_PEN_INSERT, NULL); + else + sec_input_notify(&wacom->nb, NOTIFIER_WACOM_PEN_REMOVE, NULL); +#endif + + return IRQ_HANDLED; +} + + +int wacom_reset_wakeup_sequence(struct wacom_data *wacom) +{ + int ret = 0; + + input_info(true, wacom->dev, "%s start\n", __func__); +#if WACOM_SEC_FACTORY + if (wacom->fac_garage_mode) + input_info(true, wacom->dev, "%s: garage mode\n", __func__); +#endif + + wacom_select_survey_mode(wacom, true); + if (sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF)) + goto wakeup_sequence_out; + + if (wacom->wcharging_mode) + wacom_set_sense_mode(wacom); + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + if (wacom->tsp_scan_mode < 0) { + wacom->tsp_scan_mode = sec_input_notify(&wacom->nb, NOTIFIER_TSP_BLOCKING_RELEASE, NULL); + wacom->is_tsp_block = false; + } +#endif + + if (!gpio_get_value(wacom->plat_data->irq_gpio)) { + u8 data[COM_COORD_NUM + 1] = { 0, }; + char pack_id; + + input_info(true, wacom->dev, "%s: irq was enabled\n", __func__); + + ret = wacom_recv(wacom, data, COM_COORD_NUM + 1); + if (ret < 0) + input_err(true, wacom->dev, "%s: failed to receive\n", __func__); + + input_info(true, wacom->dev, + "%x %x %x %x %x, %x %x %x %x %x, %x %x %x\n", + data[0], data[1], data[2], data[3], data[4], data[5], + data[6], data[7], data[8], data[9], data[10], + data[11], data[12]); + pack_id = data[0] & 0x0F; + if ((pack_id == NOTI_PACKET) && (data[1] == ESD_DETECT_PACKET)) { + input_err(true, wacom->dev, "%s: ESD_DETECT_PACKET occurred during reset(%d).\n", + __func__, wacom->esd_packet_count); + wacom->esd_packet_count++; + ret = -EAGAIN; + return ret; + } + } + + if (!wacom->samplerate_state) { + char cmd = COM_SAMPLERATE_START; + + input_info(true, wacom->dev, "%s: samplerate state is %d, need to recovery\n", + __func__, wacom->samplerate_state); + + ret = wacom_send(wacom, &cmd, 1); + if (ret < 0) + input_err(true, wacom->dev, "%s: failed to sned start cmd %d\n", __func__, ret); + else + wacom->samplerate_state = true; + } + + input_info(true, wacom->dev, + "%s: i(%d) f_set(0x%x) f_ret(0x%x) samplerate(%d)\n", + __func__, gpio_get_value(wacom->plat_data->irq_gpio), wacom->function_set, + wacom->function_result, wacom->samplerate_state); + + wacom->print_info_cnt_open = 0; + wacom->tsp_block_cnt = 0; + schedule_work(&wacom->work_print_info.work); + + wacom_enable_irq_wake(wacom, false); + +wakeup_sequence_out: +// !!! +// atomic_set(&wacom->screen_on, 1); + + input_info(true, wacom->dev, "%s: end\n", __func__); + return ret; +} + +void wacom_reset_sleep_sequence(struct wacom_data *wacom) +{ + input_info(true, wacom->dev, "%s start\n", __func__); +#if WACOM_SEC_FACTORY + if (wacom->fac_garage_mode) + input_info(true, wacom->dev, "%s: garage mode\n", __func__); + +#endif + wacom_select_survey_mode(wacom, false); + + if (sec_input_cmp_ic_status(wacom->dev, CHECK_ON_LP)) + wacom_enable_irq_wake(wacom, true); + + input_info(true, wacom->dev, "%s end\n", __func__); +} + +void wacom_reset_block_sequence(struct wacom_data *wacom) +{ + input_info(true, wacom->dev, "%s start\n", __func__); + wacom->esd_reset_blocked_enable = true; + wacom_enable_irq_wake(wacom, false); + cancel_delayed_work_sync(&wacom->work_print_info); + atomic_set(&wacom->plat_data->power_state, SEC_INPUT_STATE_POWER_OFF); + wacom_power(wacom, false); + + input_info(true, wacom->dev, "%s end\n", __func__); +} + +static void wacom_esd_reset_work(struct work_struct *work) +{ + struct wacom_data *wacom = container_of(work, struct wacom_data, esd_reset_work.work); + int ret = 0; + + input_info(true, wacom->dev, "%s : start\n", __func__); + mutex_lock(&wacom->lock); + cancel_delayed_work_sync(&wacom->work_print_info); + + if (wacom->pen_pressed || wacom->side_pressed || wacom->pen_prox) + wacom_forced_release(wacom); + + forced_release_fullscan(wacom); + /* Reset IC */ + wacom_reset_hw(wacom); + /* I2C Test */ + ret = wacom_query(wacom); + if (ret < 0) { + input_info(true, wacom->dev, "%s : failed to query to IC(%d)\n", __func__, ret); + goto out; + } + + input_info(true, wacom->dev, "%s: result %d\n", __func__, wacom->query_status); + + ret = wacom_reset_wakeup_sequence(wacom); + if (ret == -EAGAIN) { + wacom->esd_continuous_reset_count++; + goto retry_out; + } + + if (wacom->epen_blocked || (wacom->fold_state == FOLD_STATUS_FOLDING) || + sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF)) { + input_err(true, wacom->dev, "%s : power off, epen_blockecd(%d), flip(%d)\n", + __func__, wacom->epen_blocked, wacom->fold_state); + wacom_enable_irq_wake(wacom, false); + atomic_set(&wacom->plat_data->power_state, SEC_INPUT_STATE_POWER_OFF); + wacom_power(wacom, false); + wacom->function_result &= ~EPEN_EVENT_SURVEY; + wacom->survey_mode = EPEN_SURVEY_MODE_NONE; + wacom->reset_flag = false; + + goto out; + } + + if (!atomic_read(&wacom->plat_data->enabled)) + wacom_reset_sleep_sequence(wacom); + +out: + if (sec_input_cmp_ic_status(wacom->dev, CHECK_ON_LP)) { + if (!gpio_get_value(wacom->esd_detect_gpio)) { + wacom->esd_continuous_reset_count++; + if (wacom->esd_max_continuous_reset_count < wacom->esd_continuous_reset_count) + wacom->esd_max_continuous_reset_count = wacom->esd_continuous_reset_count; + + input_err(true, wacom->dev, + "%s : Esd irq gpio went down to low during the reset. Reset gain(%d)\n", + __func__, wacom->esd_continuous_reset_count); + if (wacom->esd_continuous_reset_count >= MAXIMUM_CONTINUOUS_ESD_RESET_COUNT) { + input_err(true, wacom->dev, + "%s : The esd reset is continuously operated, so the wacom is stopped until device reboot.(%d)\n", + __func__, wacom->esd_continuous_reset_count); + wacom_reset_block_sequence(wacom); + goto retry_out; + } + } + } + wacom->esd_continuous_reset_count = 0; + +retry_out: + wacom->abnormal_reset_count++; + wacom->reset_flag = false; + wacom->reset_is_on_going = false; + if (sec_input_cmp_ic_status(wacom->dev, CHECK_ON_LP)) + wacom_enable_irq(wacom, true); + + mutex_unlock(&wacom->lock); + + if (ret == -EAGAIN) + schedule_delayed_work(&wacom->esd_reset_work, msecs_to_jiffies(10)); + + input_info(true, wacom->dev, "%s : end(%d)\n", __func__, wacom->esd_continuous_reset_count); +} + + +static irqreturn_t wacom_esd_detect_interrupt(int irq, void *dev_id) +{ + struct wacom_data *wacom = dev_id; + + input_info(true, wacom->dev, "%s: normal state: gpio:%d, reset on going:%d\n", + __func__, gpio_get_value(wacom->esd_detect_gpio), wacom->reset_is_on_going); + + if (gpio_get_value(wacom->esd_detect_gpio)) + return IRQ_HANDLED; + + wacom->esd_irq_count++; + + if (!wacom->probe_done) { + input_err(true, wacom->dev, "%s : not probe done & skip!\n", __func__); + wacom->reset_flag = true; + wacom->esd_continuous_reset_count = 0; + return IRQ_HANDLED; + } + + if (wacom->fold_state == FOLD_STATUS_FOLDING) { + input_info(true, wacom->dev, "%s: folded, skip!\n", __func__); + wacom->reset_flag = true; + wacom->esd_continuous_reset_count = 0; + disable_irq_nosync(wacom->irq); + disable_irq_nosync(wacom->irq_esd_detect); + return IRQ_HANDLED; + } + + __pm_wakeup_event(wacom->wacom_esd_ws, WACOM_ESD_WAKE_LOCK_TIME); + input_info(true, wacom->dev, "%s: esd reset pm wake lock(%dms)\n", + __func__, WACOM_ESD_WAKE_LOCK_TIME); + + if (wacom->reset_is_on_going) { + input_info(true, wacom->dev, + "%s: already reset on going:%d\n", __func__, wacom->reset_is_on_going); + wacom->reset_flag = true; + } + + disable_irq_nosync(wacom->irq); + disable_irq_nosync(wacom->irq_esd_detect); + wacom->reset_is_on_going = true; + cancel_delayed_work_sync(&wacom->esd_reset_work); + schedule_delayed_work(&wacom->esd_reset_work, msecs_to_jiffies(1)); + + return IRQ_HANDLED; +} + +static void open_test_work(struct work_struct *work) +{ + struct wacom_data *wacom = + container_of(work, struct wacom_data, open_test_dwork.work); + int ret = 0; + + input_info(true, wacom->dev, "%s : start!\n", __func__); + + wacom->probe_done = false; + + if (wacom->support_garage_open_test) { + ret = wacom_open_test(wacom, WACOM_GARAGE_TEST); + if (ret) { +#if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event(SEC_ABC_SEND_EVENT_TYPE_WACOM_DIGITIZER_NOT_CONNECTED); +#endif + input_err(true, wacom->dev, "grage test check failed %d\n", ret); + wacom_reset_hw(wacom); + } + } + + ret = wacom_open_test(wacom, WACOM_DIGITIZER_TEST); + if (ret) { +#if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event(SEC_ABC_SEND_EVENT_TYPE_WACOM_DIGITIZER_NOT_CONNECTED); +#endif + input_err(true, wacom->dev, "open test check failed %d\n", ret); + wacom_reset_hw(wacom); + } + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + if (wacom->is_tsp_block) { + wacom->tsp_scan_mode = sec_input_notify(&wacom->nb, NOTIFIER_TSP_BLOCKING_RELEASE, NULL); + wacom->is_tsp_block = false; + input_err(true, wacom->dev, "%s : release tsp scan block\n", __func__); + } +#endif + + if (wacom->use_garage) { + wacom->pen_pdct = gpio_get_value(wacom->pdct_gpio); + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + if (wacom->pen_pdct) + sec_input_notify(&wacom->nb, NOTIFIER_WACOM_PEN_INSERT, NULL); + else + sec_input_notify(&wacom->nb, NOTIFIER_WACOM_PEN_REMOVE, NULL); +#endif + input_info(true, wacom->dev, "%s: pdct(%d)\n", __func__, wacom->pen_pdct); + + if (wacom->pen_pdct) + wacom->function_result &= ~EPEN_EVENT_PEN_OUT; + else + wacom->function_result |= EPEN_EVENT_PEN_OUT; + + input_report_switch(wacom->input_dev, SW_PEN_INSERT, + (wacom->function_result & EPEN_EVENT_PEN_OUT)); + input_sync(wacom->input_dev); + + input_info(true, wacom->dev, "%s : pen is %s\n", __func__, + (wacom->function_result & EPEN_EVENT_PEN_OUT) ? "OUT" : "IN"); + } + + wacom->probe_done = true; + + input_info(true, wacom->dev, "%s : end!\n", __func__); +} + +static void probe_open_test(struct wacom_data *wacom) +{ + INIT_DELAYED_WORK(&wacom->open_test_dwork, open_test_work); + + /* update the current status */ + schedule_delayed_work(&wacom->open_test_dwork, msecs_to_jiffies(1)); +} + +int wacom_enable(struct device *dev) +{ + struct wacom_data *wacom = dev_get_drvdata(dev); + int ret = 0; + + input_info(true, wacom->dev, "%s\n", __func__); + + if (!wacom->probe_done) { + input_err(true, wacom->dev, "%s : not probe done & skip!\n", __func__); + return 0; + } + + atomic_set(&wacom->screen_on, 1); + +#if WACOM_SEC_FACTORY + if (wacom->epen_blocked) { + input_err(true, wacom->dev, "%s : FAC epen_blocked SKIP!!\n", __func__); + return ret; + } +#else + if (wacom->epen_blocked) { + input_err(true, wacom->dev, "%s : epen_blocked\n", __func__); + if (sec_input_cmp_ic_status(wacom->dev, CHECK_ON_LP)) + wacom_disable_mode(wacom, WACOM_DISABLE); + return ret; + } +#endif + + if (sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF)) { + mutex_lock(&wacom->lock); + wacom_power(wacom, true); + atomic_set(&wacom->plat_data->power_state, SEC_INPUT_STATE_POWER_ON); + sec_delay(100); + wacom->reset_flag = false; + mutex_unlock(&wacom->lock); + } + + atomic_set(&wacom->plat_data->enabled, 1); + + wacom_wakeup_sequence(wacom); + + cancel_delayed_work(&wacom->work_print_info); + wacom->print_info_cnt_open = 0; + wacom->tsp_block_cnt = 0; + schedule_work(&wacom->work_print_info.work); + + return ret; +} + +static int wacom_disable(struct device *dev) +{ + struct wacom_data *wacom = dev_get_drvdata(dev); + + input_info(true, wacom->dev, "%s\n", __func__); + + if (!wacom->probe_done) { + input_err(true, wacom->dev, "%s : not probe done & skip!\n", __func__); + return 0; + } + + atomic_set(&wacom->screen_on, 0); + + atomic_set(&wacom->plat_data->enabled, 0); + + cancel_delayed_work(&wacom->work_print_info); + wacom_print_info(wacom); + +#if WACOM_SEC_FACTORY + if (wacom->epen_blocked) { + input_err(true, wacom->dev, "%s : FAC epen_blocked SKIP!!\n", __func__); + return 0; + } +#else + if (wacom->epen_blocked) { + input_err(true, wacom->dev, "%s : epen_blocked\n", __func__); + if (sec_input_cmp_ic_status(wacom->dev, CHECK_ON_LP)) + wacom_disable_mode(wacom, WACOM_DISABLE); + return 0; + } +#endif + + if (wacom->fold_state == FOLD_STATUS_FOLDING) { + input_info(true, wacom->dev, "%s: folder closed, turn off\n", __func__); + mutex_lock(&wacom->lock); + wacom_enable_irq(wacom, false); + atomic_set(&wacom->plat_data->power_state, SEC_INPUT_STATE_POWER_OFF); + wacom_power(wacom, false); + wacom->reset_flag = false; + wacom->survey_mode = EPEN_SURVEY_MODE_NONE; + wacom->function_result &= ~EPEN_EVENT_SURVEY; + mutex_unlock(&wacom->lock); + } else { + wacom_sleep_sequence(wacom); + } + + return 0; +} + +static void wacom_set_input_values(struct wacom_data *wacom, + struct input_dev *input_dev) +{ + /* Set input values before registering input device */ + + input_dev->phys = input_dev->name; + input_dev->id.bustype = BUS_I2C; + input_dev->dev.parent = wacom->dev; + + input_set_abs_params(input_dev, ABS_PRESSURE, 0, wacom->max_pressure, 0, 0); + input_set_abs_params(input_dev, ABS_DISTANCE, 0, wacom->max_height, 0, 0); + input_set_abs_params(input_dev, ABS_TILT_X, -wacom->max_x_tilt, wacom->max_x_tilt, 0, 0); + input_set_abs_params(input_dev, ABS_TILT_Y, -wacom->max_y_tilt, wacom->max_y_tilt, 0, 0); + + input_set_abs_params(input_dev, ABS_X, 0, wacom->plat_data->max_x, 4, 0); + input_set_abs_params(input_dev, ABS_Y, 0, wacom->plat_data->max_y, 4, 0); + + input_set_capability(input_dev, EV_KEY, BTN_TOOL_PEN); + input_set_capability(input_dev, EV_KEY, BTN_TOOL_RUBBER); + input_set_capability(input_dev, EV_KEY, BTN_TOUCH); + input_set_capability(input_dev, EV_KEY, BTN_STYLUS); + + input_set_capability(input_dev, EV_SW, SW_PEN_INSERT); + + /* AOP */ + input_set_capability(input_dev, EV_KEY, KEY_WAKEUP_UNLOCK); + input_set_capability(input_dev, EV_KEY, KEY_HOMEPAGE); + + input_set_drvdata(input_dev, wacom); +} + +void wacom_print_info(struct wacom_data *wacom) +{ + if (!wacom) + return; + + if (!wacom->dev) + return; + + wacom->print_info_cnt_open++; + + if (wacom->print_info_cnt_open > 0xfff0) + wacom->print_info_cnt_open = 0; + if (wacom->scan_info_fail_cnt > 1000) + wacom->scan_info_fail_cnt = 1000; + + input_info(true, wacom->dev, + "%s: ps %s, pen %s, screen_on %d, report_scan_seq %d, epen %s, stdby:%d count(%u,%u,%u), " + "mode(%d), block_cnt(%d), check(%d), test(%d,%d), ver[%02X%02X%02X%02X] #%d\n", + __func__, wacom->battery_saving_mode ? "on" : "off", + (wacom->function_result & EPEN_EVENT_PEN_OUT) ? "out" : "in", + atomic_read(&wacom->screen_on), + wacom->report_scan_seq, wacom->epen_blocked ? "blocked" : "unblocked", +#ifdef SUPPORT_STANDBY_MODE + wacom->standby_state, +#else + 0, +#endif + wacom->i2c_fail_count, wacom->abnormal_reset_count, wacom->scan_info_fail_cnt, + wacom->check_survey_mode, wacom->tsp_block_cnt, wacom->check_elec, + wacom->connection_check, wacom->garage_connection_check, + wacom->plat_data->img_version_of_ic[0], wacom->plat_data->img_version_of_ic[1], + wacom->plat_data->img_version_of_ic[2], wacom->plat_data->img_version_of_ic[3], + wacom->print_info_cnt_open); +} + +static void wacom_print_info_work(struct work_struct *work) +{ + struct wacom_data *wacom = container_of(work, struct wacom_data, work_print_info.work); + + wacom_print_info(wacom); + schedule_delayed_work(&wacom->work_print_info, msecs_to_jiffies(30000)); // 30s +} + +int load_fw_built_in(struct wacom_data *wacom, int fw_index) +{ + int ret = 0; + const char *fw_load_path = NULL; +#if WACOM_SEC_FACTORY + int index = 0; +#endif + + input_info(true, wacom->dev, "%s: fw_index (%d)\n", __func__, fw_index); + + if (wacom->fw_separate_by_camera) { + ret = of_property_read_string_index(wacom->dev->of_node, + "sec,firmware_name", wacom->fw_bin_idx, &wacom->plat_data->firmware_name); + if (ret) { + input_err(true, wacom->dev, "%s: failed to read firmware_name(%d) idx(%d)\n", + __func__, ret, wacom->fw_bin_idx); + wacom->plat_data->firmware_name = NULL; + } + + fw_load_path = wacom->plat_data->firmware_name; + + input_info(true, wacom->dev, "%s: load %s firmware(%d)\n", + __func__, fw_load_path, wacom->fw_bin_idx); + } else { + fw_load_path = wacom->plat_data->firmware_name; + } + +#if WACOM_SEC_FACTORY + if (fw_index != FW_BUILT_IN) { + if (fw_index == FW_FACTORY_GARAGE) + index = 0; + else if (fw_index == FW_FACTORY_UNIT) + index = 1; + + ret = of_property_read_string_index(wacom->dev->of_node, + "wacom,fw_fac_path", index, &wacom->fw_fac_path); + if (ret) { + input_err(true, wacom->dev, "%s: failed to read fw_fac_path %d\n", __func__, ret); + wacom->fw_fac_path = NULL; + } + + fw_load_path = wacom->fw_fac_path; + + input_info(true, wacom->dev, "%s: load %s firmware\n", + __func__, fw_load_path); + } +#endif + + if (fw_load_path == NULL) { + input_err(true, wacom->dev, + "Unable to open firmware. firmware_name is NULL\n"); + return -EINVAL; + } + + ret = request_firmware(&wacom->firm_data, fw_load_path, wacom->dev); + if (ret < 0) { + input_err(true, wacom->dev, "Unable to open firmware(%s) ret %d\n", fw_load_path, ret); + return ret; + } + + wacom->fw_img = (struct fw_image *)wacom->firm_data->data; + + input_info(true, wacom->dev, "%s : fw load done(%s)\n", __func__, fw_load_path); + + return ret; +} + +static int wacom_get_fw_size(struct wacom_data *wacom) +{ + u32 fw_size = 0; + + if (wacom->plat_data->img_version_of_ic[0] == MPU_W9020) + fw_size = 144 * 1024; + else if (wacom->plat_data->img_version_of_ic[0] == MPU_W9021) + fw_size = 256 * 1024; + else if (wacom->plat_data->img_version_of_ic[0] == MPU_WEZ01) + fw_size = 256 * 1024; + else if (wacom->plat_data->img_version_of_ic[0] == MPU_WEZ02) + fw_size = 192 * 1024; + else + fw_size = 128 * 1024; + + input_info(true, wacom->dev, "%s: type[%d] size[0x%X]\n", + __func__, wacom->plat_data->img_version_of_ic[0], fw_size); + + return fw_size; +} + +static int load_fw_sdcard(struct wacom_data *wacom, u8 fw_index, const char *file_path) +{ + long fsize; + int ret = 0; + unsigned int nSize; + unsigned long nSize2; +#ifdef SUPPORT_FW_SIGNED + long spu_ret; +#endif + + nSize = wacom_get_fw_size(wacom); + nSize2 = nSize + sizeof(struct fw_image); + + ret = request_firmware(&wacom->firm_data, file_path, wacom->dev); + if (ret) { + input_err(true, wacom->dev, "firmware is not available %d.\n", ret); + ret = -ENOENT; + return ret; + } + + fsize = wacom->firm_data->size; + input_info(true, wacom->dev, "start, file path %s, size %ld Bytes\n", file_path, fsize); + +#ifdef SUPPORT_FW_SIGNED + if (fw_index == FW_IN_SDCARD_SIGNED || fw_index == FW_SPU || fw_index == FW_VERIFICATION) { + /* name 5, digest 32, signature 512 */ + fsize -= SPU_METADATA_SIZE(WACOM); + } +#endif + + if ((fsize != nSize) && (fsize != nSize2)) { + input_err(true, wacom->dev, "UMS firmware size is different\n"); + ret = -EFBIG; + goto out; + } + +#ifdef SUPPORT_FW_SIGNED + if (fw_index == FW_IN_SDCARD_SIGNED || fw_index == FW_SPU || fw_index == FW_VERIFICATION) { + /* name 5, digest 32, signature 512 */ + + spu_ret = spu_firmware_signature_verify("WACOM", wacom->firm_data->data, wacom->firm_data->size); + + input_info(true, wacom->dev, "%s: spu_ret : %ld, spu_fsize : %ld // fsize:%ld\n", + __func__, spu_ret, wacom->firm_data->size, fsize); + + if (spu_ret != fsize) { + input_err(true, wacom->dev, "%s: signature verify failed, %ld\n", __func__, spu_ret); + ret = -ENOENT; + goto out; + } + } +#endif + + wacom->fw_img = (struct fw_image *)wacom->firm_data->data; + return 0; + +out: + return ret; +} + +int wacom_load_fw(struct wacom_data *wacom, u8 fw_update_way) +{ + int ret = 0; + struct fw_image *fw_img; + + switch (fw_update_way) { + case FW_BUILT_IN: +#if WACOM_SEC_FACTORY + case FW_FACTORY_GARAGE: + case FW_FACTORY_UNIT: +#endif + ret = load_fw_built_in(wacom, fw_update_way); + break; + case FW_IN_SDCARD: + ret = load_fw_sdcard(wacom, fw_update_way, WACOM_PATH_EXTERNAL_FW); + break; + case FW_IN_SDCARD_SIGNED: + ret = load_fw_sdcard(wacom, fw_update_way, WACOM_PATH_EXTERNAL_FW_SIGNED); + break; + case FW_SPU: + case FW_VERIFICATION: + if (!wacom->fw_separate_by_camera) + ret = load_fw_sdcard(wacom, fw_update_way, WACOM_PATH_SPU_FW_SIGNED); + else { + if (wacom->fw_bin_idx == FW_INDEX_0) + ret = load_fw_sdcard(wacom, fw_update_way, WACOM_PATH_SPU_FW0_SIGNED); + else if (wacom->fw_bin_idx == FW_INDEX_1) + ret = load_fw_sdcard(wacom, fw_update_way, WACOM_PATH_SPU_FW1_SIGNED); + else if (wacom->fw_bin_idx == FW_INDEX_2) + ret = load_fw_sdcard(wacom, fw_update_way, WACOM_PATH_SPU_FW2_SIGNED); + else if (wacom->fw_bin_idx == FW_INDEX_3) + ret = load_fw_sdcard(wacom, fw_update_way, WACOM_PATH_SPU_FW3_SIGNED); + } + break; + default: + input_info(true, wacom->dev, "unknown path(%d)\n", fw_update_way); + goto err_load_fw; + } + + if (ret < 0) + goto err_load_fw; + + fw_img = wacom->fw_img; + + /* header check */ + if (fw_img->hdr_ver == 1 && fw_img->hdr_len == sizeof(struct fw_image)) { + wacom->fw_data = (u8 *) fw_img->data; +#if WACOM_SEC_FACTORY + if ((fw_update_way == FW_BUILT_IN) || + (fw_update_way == FW_FACTORY_UNIT) || (fw_update_way == FW_FACTORY_GARAGE)) { +#else + if (fw_update_way == FW_BUILT_IN) { +#endif + //need to check data + wacom->plat_data->img_version_of_bin[0] = (fw_img->fw_ver1 >> 8) & 0xFF; + wacom->plat_data->img_version_of_bin[1] = fw_img->fw_ver1 & 0xFF; + wacom->plat_data->img_version_of_bin[2] = (fw_img->fw_ver2 >> 8) & 0xFF; + wacom->plat_data->img_version_of_bin[3] = fw_img->fw_ver2 & 0xFF; + memcpy(wacom->fw_chksum, fw_img->checksum, 5); + } else if (fw_update_way == FW_SPU || fw_update_way == FW_VERIFICATION) { + wacom->img_version_of_spu[0] = (fw_img->fw_ver1 >> 8) & 0xFF; + wacom->img_version_of_spu[1] = fw_img->fw_ver1 & 0xFF; + wacom->img_version_of_spu[2] = (fw_img->fw_ver2 >> 8) & 0xFF; + wacom->img_version_of_spu[3] = fw_img->fw_ver2 & 0xFF; + memcpy(wacom->fw_chksum, fw_img->checksum, 5); + } + } else { + input_err(true, wacom->dev, "no hdr\n"); + wacom->fw_data = (u8 *) fw_img; + } + + return ret; + +err_load_fw: + wacom->fw_data = NULL; + return ret; +} + +void wacom_unload_fw(struct wacom_data *wacom) +{ + switch (wacom->fw_update_way) { + case FW_BUILT_IN: +#if WACOM_SEC_FACTORY + case FW_FACTORY_GARAGE: + case FW_FACTORY_UNIT: +#endif + release_firmware(wacom->firm_data); + break; + case FW_IN_SDCARD: + case FW_IN_SDCARD_SIGNED: + case FW_SPU: + case FW_VERIFICATION: + release_firmware(wacom->firm_data); + break; + default: + break; + } + + wacom->fw_img = NULL; + wacom->fw_update_way = FW_NONE; + wacom->firm_data = NULL; + wacom->fw_data = NULL; +} + +int wacom_fw_update_on_hidden_menu(struct wacom_data *wacom, u8 fw_update_way) +{ + int ret; + int retry = 3; + int i; + + input_info(true, wacom->dev, "%s: update:%d\n", __func__, fw_update_way); + + mutex_lock(&wacom->update_lock); + wacom_enable_irq(wacom, false); + + /* release pen, if it is pressed */ + if (wacom->pen_pressed || wacom->side_pressed || wacom->pen_prox) + wacom_forced_release(wacom); + + if (wacom->is_tsp_block) + forced_release_fullscan(wacom); + + ret = wacom_load_fw(wacom, fw_update_way); + if (ret < 0) { + input_info(true, wacom->dev, "failed to load fw data\n"); + wacom->update_status = FW_UPDATE_FAIL; + goto err_update_load_fw; + } + wacom->fw_update_way = fw_update_way; + + /* firmware info */ + if (fw_update_way == FW_SPU || fw_update_way == FW_VERIFICATION) + input_info(true, wacom->dev, "ic fw ver : %02X%02X%02X%02X new fw ver : %02X%02X%02X%02X\n", + wacom->plat_data->img_version_of_ic[0], wacom->plat_data->img_version_of_ic[1], + wacom->plat_data->img_version_of_ic[2], wacom->plat_data->img_version_of_ic[3], + wacom->img_version_of_spu[0], wacom->img_version_of_spu[1], + wacom->img_version_of_spu[2], wacom->img_version_of_spu[3]); + else + input_info(true, wacom->dev, "ic fw ver : %02X%02X%02X%02X new fw ver : %02X%02X%02X%02X\n", + wacom->plat_data->img_version_of_ic[0], wacom->plat_data->img_version_of_ic[1], + wacom->plat_data->img_version_of_ic[2], wacom->plat_data->img_version_of_ic[3], + wacom->plat_data->img_version_of_bin[0], wacom->plat_data->img_version_of_bin[1], + wacom->plat_data->img_version_of_bin[2], wacom->plat_data->img_version_of_bin[3]); + + // have to check it later + if (wacom->fw_update_way == FW_BUILT_IN && wacom->plat_data->bringup == 1) { + input_info(true, wacom->dev, "bringup #1. do not update\n"); + wacom->update_status = FW_UPDATE_FAIL; + goto out_update_fw; + } + + /* If FFU firmware version is lower than IC's version, do not run update routine */ + if (fw_update_way == FW_SPU && + (wacom->plat_data->img_version_of_ic[0] == wacom->img_version_of_spu[0] && + wacom->plat_data->img_version_of_ic[1] == wacom->img_version_of_spu[1] && + wacom->plat_data->img_version_of_ic[2] == wacom->img_version_of_spu[2] && + wacom->plat_data->img_version_of_ic[3] >= wacom->img_version_of_spu[3])) { + + input_info(true, wacom->dev, "FFU. update is skipped\n"); + wacom->update_status = FW_UPDATE_PASS; + + wacom_unload_fw(wacom); + wacom_enable_irq(wacom, true); + mutex_unlock(&wacom->update_lock); + return 0; + } else if (fw_update_way == FW_VERIFICATION) { + input_info(true, wacom->dev, "SPU verified. update is skipped\n"); + wacom->update_status = FW_UPDATE_PASS; + + wacom_unload_fw(wacom); + wacom_enable_irq(wacom, true); + mutex_unlock(&wacom->update_lock); + return 0; + } + + wacom->update_status = FW_UPDATE_RUNNING; + + while (retry--) { + ret = wacom_flash(wacom); + if (ret) { + input_info(true, wacom->dev, "failed to flash fw(%d) %d\n", ret, retry); + continue; + } + break; + } + + retry = 3; + while (retry--) { + ret = wacom_query(wacom); + if (ret < 0) { + input_info(true, wacom->dev, "%s : failed to query to IC(%d) & reset, %d\n", + __func__, ret, retry); + wacom_compulsory_flash_mode(wacom, false); + wacom_reset_hw(wacom); + continue; + } + break; + } + + if (ret < 0) { + wacom->update_status = FW_UPDATE_FAIL; + for (i = 0; i < 4; i++) + wacom->plat_data->img_version_of_ic[i] = 0; + goto out_update_fw; + } + + wacom->update_status = FW_UPDATE_PASS; + + ret = wacom_check_ub(wacom); + if (ret < 0) { + wacom_set_survey_mode(wacom, EPEN_SURVEY_MODE_GARAGE_AOP); + input_info(true, wacom->dev, "Change mode for garage scan\n"); + } + + wacom_unload_fw(wacom); + wacom_enable_irq(wacom, true); + mutex_unlock(&wacom->update_lock); + + return 0; + +out_update_fw: + wacom_unload_fw(wacom); +err_update_load_fw: + wacom_enable_irq(wacom, true); + mutex_unlock(&wacom->update_lock); + + ret = wacom_check_ub(wacom); + if (ret < 0) { + wacom_set_survey_mode(wacom, EPEN_SURVEY_MODE_GARAGE_AOP); + input_info(true, wacom->dev, "Change mode for garage scan\n"); + } + + return -1; +} + +static int wacom_input_init(struct wacom_data *wacom) +{ + int ret = 0; + /* using managed input device */ + wacom->input_dev = devm_input_allocate_device(wacom->dev); + if (!wacom->input_dev) { + input_err(true, wacom->dev, "failed to allocate input device\n"); + return -ENOMEM; + } + + wacom->input_dev->name = "sec_e-pen"; + + /* expand evdev buffer size */ + wacom->input_dev->hint_events_per_packet = 256;//hint 256 -> 2048 buffer size; + + wacom_set_input_values(wacom, wacom->input_dev); + ret = input_register_device(wacom->input_dev); + if (ret) { + input_err(true, wacom->dev, "failed to register input_dev device\n"); + /* managed input devices need not be explicitly unregistred or freed. */ + return ret; + } + + return ret; +} + +int wacom_fw_update_on_probe(struct wacom_data *wacom) +{ + int ret; + int retry = 3; + bool bforced = false; + int i; + + input_info(true, wacom->dev, "%s\n", __func__); + + if (wacom->plat_data->bringup == 1) { + input_info(true, wacom->dev, "bringup #1. do not update\n"); + for (i = 0; i < 4; i++) + wacom->plat_data->img_version_of_bin[i] = 0; + goto skip_update_fw; + } + + ret = wacom_load_fw(wacom, FW_BUILT_IN); + if (ret < 0) { + input_info(true, wacom->dev, "failed to load fw data (set bringup 1)\n"); + wacom->plat_data->bringup = 1; + goto skip_update_fw; + } + + if (wacom->plat_data->bringup == 2) { + input_info(true, wacom->dev, "bringup #2. do not update\n"); + goto out_update_fw; + } + + wacom->fw_update_way = FW_BUILT_IN; + + /* firmware info */ + input_info(true, wacom->dev, "ic fw ver : %02X%02X%02X%02X new fw ver : %02X%02X%02X%02X\n", + wacom->plat_data->img_version_of_ic[0], wacom->plat_data->img_version_of_ic[1], + wacom->plat_data->img_version_of_ic[2], wacom->plat_data->img_version_of_ic[3], + wacom->plat_data->img_version_of_bin[0], wacom->plat_data->img_version_of_bin[1], + wacom->plat_data->img_version_of_bin[2], wacom->plat_data->img_version_of_bin[3]); + + if (wacom->plat_data->bringup == 3) { + input_info(true, wacom->dev, "bringup #3. force update\n"); + bforced = true; + } + + if (bforced) { + for (i = 0; i < 4; i++) { + if (wacom->plat_data->img_version_of_ic[i] != wacom->plat_data->img_version_of_bin[i]) { + input_info(true, wacom->dev, "force update : not equal, update fw\n"); + goto fw_update; + } + } + input_info(true, wacom->dev, "force update : equal, skip update fw\n"); + goto out_update_fw; + } + + if (wacom->plat_data->img_version_of_ic[2] == 0 && wacom->plat_data->img_version_of_ic[3] == 0) { + input_info(true, wacom->dev, "fw is not written in ic. force update\n"); + goto fw_update; + } + + for (i = 0; i < 2; i++) { + if (wacom->plat_data->img_version_of_ic[i] != wacom->plat_data->img_version_of_bin[i]) { + input_err(true, wacom->dev, "%s: do not matched version info\n", __func__); + goto out_update_fw; + } + } + + if ((wacom->plat_data->img_version_of_ic[2] >> 4) == WACOM_FIRMWARE_VERSION_UNIT) { + input_err(true, wacom->dev, "%s: assy fw. force update\n", __func__); + goto fw_update; + } else if ((wacom->plat_data->img_version_of_ic[2] >> 4) == WACOM_FIRMWARE_VERSION_SET) { + if ((wacom->plat_data->img_version_of_ic[2] & 0x0F) != + (wacom->plat_data->img_version_of_bin[2] & 0x0F)) { + input_err(true, wacom->dev, "%s: HW ID is different. force update\n", __func__); + goto fw_update; + } + + if (wacom->plat_data->img_version_of_ic[3] == wacom->plat_data->img_version_of_bin[3]) { + ret = wacom_checksum(wacom); + if (ret) { + input_info(true, wacom->dev, "crc ok, do not update fw\n"); + goto out_update_fw; + } + input_info(true, wacom->dev, "crc err, do update\n"); + } else if (wacom->plat_data->img_version_of_ic[3] >= wacom->plat_data->img_version_of_bin[3]) { + input_info(true, wacom->dev, "ic version is high, do not update fw\n"); + goto out_update_fw; + } + } else if ((wacom->plat_data->img_version_of_ic[2] >> 4) == WACOM_FIRMWARE_VERSION_TEST) { + input_info(true, wacom->dev, "test fw\n"); + if (wacom->plat_data->img_version_of_ic[3] == wacom->plat_data->img_version_of_bin[3]) { + input_info(true, wacom->dev, "fw ver is same, do not update fw\n"); + goto out_update_fw; + } + } + +fw_update: + while (retry--) { + ret = wacom_flash(wacom); + if (ret) { + input_info(true, wacom->dev, "failed to flash fw(%d) %d\n", ret, retry); + continue; + } + break; + } + + retry = 3; + while (retry--) { + ret = wacom_query(wacom); + if (ret < 0) { + input_info(true, wacom->dev, "%s : failed to query to IC(%d) & reset, %d\n", + __func__, ret, retry); + wacom_compulsory_flash_mode(wacom, false); + wacom_reset_hw(wacom); + continue; + } + break; + } + if (ret) { + for (i = 0; i < 4; i++) + wacom->plat_data->img_version_of_ic[i] = 0; + goto err_update_fw; + } + +out_update_fw: + wacom_unload_fw(wacom); +skip_update_fw: + ret = wacom_check_ub(wacom); + if (ret < 0) { + wacom_set_survey_mode(wacom, EPEN_SURVEY_MODE_GARAGE_AOP); + input_info(true, wacom->dev, "Change mode for garage scan\n"); + } + + return 0; + +err_update_fw: + wacom_unload_fw(wacom); + ret = wacom_check_ub(wacom); + if (ret < 0) { + wacom_set_survey_mode(wacom, EPEN_SURVEY_MODE_GARAGE_AOP); + input_info(true, wacom->dev, "Change mode for garage scan\n"); + } + + return -1; +} + +/* epen_disable_mode + * 0 : wacom ic on + * 1 : wacom ic off + */ +void wacom_disable_mode(struct wacom_data *wacom, wacom_disable_mode_t mode) +{ + if (mode == WACOM_DISABLE) { + input_info(true, wacom->dev, "%s: power off\n", __func__); + wacom->epen_blocked = mode; + + wacom_enable_irq(wacom, false); + atomic_set(&wacom->plat_data->power_state, SEC_INPUT_STATE_POWER_OFF); + wacom_power(wacom, false); + + wacom->survey_mode = EPEN_SURVEY_MODE_NONE; + wacom->function_result &= ~EPEN_EVENT_SURVEY; + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + wacom->tsp_scan_mode = sec_input_notify(&wacom->nb, NOTIFIER_TSP_BLOCKING_RELEASE, NULL); + wacom->is_tsp_block = false; +#endif + wacom_forced_release(wacom); + } else if (mode == WACOM_ENABLE) { + input_info(true, wacom->dev, "%s: power on\n", __func__); + + wacom_power(wacom, true); + atomic_set(&wacom->plat_data->power_state, SEC_INPUT_STATE_POWER_ON); + sec_delay(100); + + wacom_enable_irq(wacom, true); + wacom->epen_blocked = mode; + + if (atomic_read(&wacom->screen_on)) + wacom_wakeup_sequence(wacom); + else + wacom_sleep_sequence(wacom); + } + input_info(true, wacom->dev, "%s: done %d!\n", __func__, mode); +} + +void wacom_swap_compensation(struct wacom_data *wacom, char cmd) +{ + char data; + int ret; + + input_info(true, wacom->dev, "%s: %d\n", __func__, cmd); + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s : reset is on going, return\n", __func__); + return; + } + + if (sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF)) { + input_err(true, wacom->dev, + "%s: powered off\n", __func__); + return; + } + + data = COM_SAMPLERATE_STOP; + ret = wacom_send_sel(wacom, &data, 1, WACOM_I2C_MODE_NORMAL); + if (ret != 1) { + input_err(true, wacom->dev, "%s: failed to send stop cmd %d\n", + __func__, ret); + wacom->reset_flag = true; + return; + } + wacom->samplerate_state = false; + sec_delay(50); + + switch (cmd) { + case NOMAL_MODE: + data = COM_NORMAL_COMPENSATION; + break; + case BOOKCOVER_MODE: + data = COM_BOOKCOVER_COMPENSATION; + break; + case KBDCOVER_MODE: + data = COM_KBDCOVER_COMPENSATION; + break; + case DISPLAY_NS_MODE: + data = COM_DISPLAY_NS_MODE_COMPENSATION; + break; + case DISPLAY_HS_MODE: + data = COM_DISPLAY_HS_MODE_COMPENSATION; + break; + case REARCOVER_TYPE1: + data = COM_REARCOVER_TYPE1; + break; + case REARCOVER_TYPE2: + data = COM_REARCOVER_TYPE2; + break; + default: + input_err(true, wacom->dev, + "%s: get wrong cmd %d\n", + __func__, cmd); + } + + ret = wacom_send_sel(wacom, &data, 1, WACOM_I2C_MODE_NORMAL); + if (ret != 1) { + input_err(true, wacom->dev, + "%s: failed to send table swap cmd %d\n", + __func__, ret); + wacom->reset_flag = true; + return; + } + + data = COM_SAMPLERATE_STOP; + ret = wacom_send_sel(wacom, &data, 1, WACOM_I2C_MODE_NORMAL); + if (ret != 1) { + input_err(true, wacom->dev, + "%s: failed to send stop cmd %d\n", + __func__, ret); + wacom->reset_flag = true; + return; + } + + sec_delay(50); + + data = COM_SAMPLERATE_START; + ret = wacom_send_sel(wacom, &data, 1, WACOM_I2C_MODE_NORMAL); + if (ret != 1) { + input_err(true, wacom->dev, + "%s: failed to send start cmd, %d\n", + __func__, ret); + wacom->reset_flag = true; + return; + } + wacom->samplerate_state = true; + + input_info(true, wacom->dev, "%s:send cover cmd %x\n", + __func__, cmd); +} + +#ifdef CONFIG_USB_TYPEC_MANAGER_NOTIFIER +static void wacom_usb_typec_work(struct work_struct *work) +{ + struct wacom_data *wacom = container_of(work, struct wacom_data, typec_work); + char data[5] = { 0 }; + int ret; + + if (wacom->dp_connect_state == wacom->dp_connect_cmd) + return; + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s : reset is on going, return\n", __func__); + return; + } + + if (sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF)) { + input_err(true, wacom->dev, "%s: powered off now\n", __func__); + return; + } + + ret = wacom_start_stop_cmd(wacom, WACOM_STOP_CMD); + if (ret < 0) { + input_err(true, wacom->dev, "%s: failed to set stop cmd, %d\n", __func__, ret); + return; + } + + sec_delay(50); + + if (wacom->dp_connect_cmd) + data[0] = COM_SPECIAL_COMPENSATION; + else + data[0] = COM_NORMAL_COMPENSATION; + + ret = wacom_send(wacom, &data[0], 1); + if (ret < 0) { + input_err(true, wacom->dev, "%s: failed to send table swap cmd %d\n", __func__, ret); + return; + } + + ret = wacom_start_stop_cmd(wacom, WACOM_STOP_AND_START_CMD); + if (ret < 0) { + input_err(true, wacom->dev, "%s: failed to set stop-start cmd, %d\n", __func__, ret); + return; + } + + input_info(true, wacom->dev, "%s: %s\n", __func__, wacom->dp_connect_cmd ? "on" : "off"); +} + +static int wacom_usb_typec_notification_cb(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct wacom_data *wacom = container_of(nb, struct wacom_data, typec_nb); + CC_NOTI_TYPEDEF usb_typec_info = *(CC_NOTI_TYPEDEF *)data; + + if (usb_typec_info.src != CCIC_NOTIFY_DEV_CCIC || + usb_typec_info.dest != CCIC_NOTIFY_DEV_DP || + usb_typec_info.id != CCIC_NOTIFY_ID_DP_CONNECT) + goto out; + + input_info(true, wacom->dev, "%s: %s (vid:0x%04X pid:0x%04X)\n", + __func__, usb_typec_info.sub1 ? "attached" : "detached", + usb_typec_info.sub2, usb_typec_info.sub3); + + switch (usb_typec_info.sub1) { + case CCIC_NOTIFY_ATTACH: + if (usb_typec_info.sub2 != 0x04E8 || usb_typec_info.sub3 != 0xA020) + goto out; + break; + case CCIC_NOTIFY_DETACH: + break; + default: + input_err(true, wacom->dev, "%s: invalid value %d\n", __func__, usb_typec_info.sub1); + goto out; + } + + //need to fix it (build error) + cancel_work(&wacom->typec_work); + wacom->dp_connect_cmd = usb_typec_info.sub1; + schedule_work(&wacom->typec_work); +out: + return 0; +} +#endif + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) +static int wacom_notifier_call(struct notifier_block *n, unsigned long data, void *v) +{ + struct wacom_data *wacom = container_of(n, struct wacom_data, nb); + + switch (data) { + case NOTIFIER_SECURE_TOUCH_ENABLE: + wacom_disable_mode(wacom, WACOM_DISABLE); + break; + case NOTIFIER_SECURE_TOUCH_DISABLE: + wacom_disable_mode(wacom, WACOM_ENABLE); + if (wacom->pdct_lock_fail) { + wacom_pdct_survey_mode(wacom); + wacom->pdct_lock_fail = false; + } + break; + default: + break; + } + + return 0; +} +#endif + +const char *wacom_fwe_str = "wacom_fwe"; +const char *wacom_pdct_str = "wacom_pdct"; +const char *wacom_esd_detect_str = "wacom_esd_detect"; +static int wacom_request_gpio(struct wacom_data *wacom) +{ + int ret; + + ret = devm_gpio_request(wacom->dev, wacom->fwe_gpio, wacom_fwe_str); + if (ret) { + input_err(true, wacom->dev, "unable to request gpio for fwe\n"); + return ret; + } + + if (wacom->use_garage) { + ret = devm_gpio_request(wacom->dev, wacom->pdct_gpio, wacom_pdct_str); + if (ret) { + input_err(true, wacom->dev, "unable to request gpio for pdct\n"); + return ret; + } + } + + if (gpio_is_valid(wacom->esd_detect_gpio)) { + ret = devm_gpio_request(wacom->dev, wacom->esd_detect_gpio, wacom_esd_detect_str); + if (ret) { + input_err(true, wacom->dev, "unable to request gpio for esd_detect\n"); + return ret; + } + } + return 0; +} + +int wacom_check_ub(struct wacom_data *wacom) +{ + int lcdtype = 0; + + lcdtype = sec_input_get_lcd_id(wacom->dev); + if (lcdtype == 0xFFFFFF) { + input_info(true, wacom->dev, "lcd is not attached\n"); + return -ENODEV; + } + + input_info(true, wacom->dev, "%s: lcdtype 0x%08x\n", __func__, lcdtype); + + return lcdtype; +} + +void wacom_dev_remove(struct wacom_data *wacom) +{ + g_wacom = NULL; + wacom->probe_done = false; + wacom->plat_data->enable = NULL; + wacom->plat_data->disable = NULL; + + input_info(true, wacom->dev, "%s called!\n", __func__); + + if (gpio_is_valid(wacom->esd_detect_gpio)) + cancel_delayed_work_sync(&wacom->esd_reset_work); + cancel_delayed_work_sync(&wacom->open_test_dwork); + cancel_delayed_work_sync(&wacom->work_print_info); + + wacom_enable_irq(wacom, false); + + atomic_set(&wacom->plat_data->power_state, SEC_INPUT_STATE_POWER_OFF); + wacom_power(wacom, false); + wakeup_source_unregister(wacom->wacom_esd_ws); + wakeup_source_unregister(wacom->wacom_ws); + wakeup_source_unregister(wacom->plat_data->sec_ws); + + mutex_destroy(&wacom->i2c_mutex); + mutex_destroy(&wacom->irq_lock); + mutex_destroy(&wacom->update_lock); + mutex_destroy(&wacom->lock); + mutex_destroy(&wacom->mode_lock); + mutex_destroy(&wacom->ble_lock); + mutex_destroy(&wacom->ble_charge_mode_lock); + + wacom_sec_remove(wacom); + + i2c_unregister_device(wacom->client_boot); + + input_info(true, wacom->dev, "%s Done\n", __func__); +} + +static void wacom_camera_check_work(struct work_struct *work) +{ + struct wacom_data *wacom = container_of(work, struct wacom_data, work_camera_check.work); + int tmp = 0, ret = 0; + static int retry; + + input_info(true, wacom->dev, "%s : start!(%d/%d)\n", __func__, wacom->probe_done, retry); + + if (wacom->probe_done == false) { + if (retry++ < 20) + schedule_delayed_work(&wacom->work_camera_check, msecs_to_jiffies(1000)); // 1 sec + else + input_err(true, wacom->dev, "%s : retry(%d) over\n", __func__, retry); + return; + } + + wacom->probe_done = false; + cancel_delayed_work_sync(&wacom->open_test_dwork); + + tmp = wacom->plat_data->bringup; + wacom->plat_data->bringup = 3; + + ret = wacom_fw_update_on_probe(wacom); + if (ret < 0) + input_err(true, wacom->dev, "%s : update fail!\n", __func__); + + wacom->plat_data->bringup = tmp; + wacom->probe_done = true; + + // set default setting + wacom_enable(wacom->plat_data->dev); + + input_info(true, wacom->dev, "%s : end!\n", __func__); +} + +static int wacom_get_camera_type_notify(struct notifier_block *nb, unsigned long action, void *data) +{ + struct wacom_data *wacom = container_of(nb, struct wacom_data, nb_camera); + u8 tele_cam, wide_cam; + u16 result = 0; + + if (!wacom->fw_separate_by_camera) { + input_info(true, wacom->dev, "%s : not support notify val(%ld)\n", __func__, action); + return 0; + } + + input_info(true, wacom->dev, "%s: idx(#%d) fw ver(%02X%02X%02X%02X) // notify val(0x%08lx)\n", + __func__, wacom->fw_bin_idx, + wacom->plat_data->img_version_of_ic[0], wacom->plat_data->img_version_of_ic[1], + wacom->plat_data->img_version_of_ic[2], wacom->plat_data->img_version_of_ic[3], action); + + if (wacom->nb_camera_call_cnt++ > 0) { + input_info(true, wacom->dev, "%s : duplicate call & skip(%d)!\n", + __func__, wacom->nb_camera_call_cnt); + return 0; + } + + tele_cam = (action >> 24) & 0xFF; + wide_cam = action & 0xFF; + + if (wacom->plat_data->img_version_of_ic[1] == WEZ02_E3) { + if (tele_cam == RT_SUNNY && wide_cam == RW_SUNNY) + result = FW_INDEX_0; + else if (tele_cam == RT_SEC && wide_cam == RW_SUNNY) + result = FW_INDEX_1; + else if (tele_cam == RT_SUNNY && wide_cam == RW_SVCC) + result = FW_INDEX_2; + else if (tele_cam == RT_SEC && wide_cam == RW_SVCC) + result = FW_INDEX_3; + else { + input_info(true, wacom->dev, "%s : not matched(tele:%d,wide:%d) skip!\n", + __func__, tele_cam, wide_cam); + return 0; + } + } else { + input_err(true, wacom->dev, "%s : not matched model\n", __func__); + return 0; + } + + if (wacom->fw_bin_idx == result) { + input_info(true, wacom->dev, "%s : type matched & skip!\n", __func__); + return 0; + } + + input_info(true, wacom->dev, "%s : mismatched & call workqueue!\n", __func__); + wacom->fw_bin_idx = result; + schedule_delayed_work(&wacom->work_camera_check, msecs_to_jiffies(10)); + + return 0; +} + +static int wacom_hw_init(struct wacom_data *wacom) +{ + u16 firmware_index = 0; + + /* Power on */ + if (gpio_get_value(wacom->fwe_gpio) == 1) { + input_info(true, wacom->dev, "%s: fwe gpio is high, change low and reset device\n", __func__); + wacom_compulsory_flash_mode(wacom, false); + wacom_reset_hw(wacom); + sec_delay(200); + } else { + wacom_compulsory_flash_mode(wacom, false); + wacom_power(wacom, true); + atomic_set(&wacom->plat_data->power_state, SEC_INPUT_STATE_POWER_ON); + if (!wacom->plat_data->regulator_boot_on) + sec_delay(100); + } + + atomic_set(&wacom->screen_on, 1); + + wacom_query(wacom); + + if (wacom->fw_separate_by_camera) { + if (wacom->plat_data->img_version_of_ic[2] == 0 && wacom->plat_data->img_version_of_ic[3] == 0) { + wacom->fw_bin_idx = wacom->fw_bin_default_idx; + input_err(true, wacom->dev, "%s: ic ver(%02X%02X%02X%02X) default idx(%d)\n", + __func__, wacom->plat_data->img_version_of_ic[0], + wacom->plat_data->img_version_of_ic[1], + wacom->plat_data->img_version_of_ic[2], + wacom->plat_data->img_version_of_ic[3], wacom->fw_bin_idx); + } else { + /* E3 */ + firmware_index = (wacom->plat_data->img_version_of_ic[2] & 0x0F); + switch (firmware_index) { + case E3_RT2_08_RW1_01: + wacom->fw_bin_idx = FW_INDEX_0; + break; + case E3_RT2_01_RW1_01: + wacom->fw_bin_idx = FW_INDEX_1; + break; + case E3_RT2_08_RW1_05: + wacom->fw_bin_idx = FW_INDEX_2; + break; + case E3_RT2_01_RW1_05: + wacom->fw_bin_idx = FW_INDEX_3; + break; + + default: + wacom->fw_bin_idx = wacom->fw_bin_default_idx; + input_err(true, wacom->dev, "%s: fw idx is not matched(0x%x) & set default\n", + __func__, firmware_index); + } + + input_info(true, wacom->dev, "%s: idx(#%d/0x%X) : ic ver(%02X%02X%02X%02X)\n", + __func__, wacom->fw_bin_idx, firmware_index, + wacom->plat_data->img_version_of_ic[0], wacom->plat_data->img_version_of_ic[1], + wacom->plat_data->img_version_of_ic[2], wacom->plat_data->img_version_of_ic[3]); + } + } + + INIT_DELAYED_WORK(&wacom->work_camera_check, wacom_camera_check_work); + wacom->nb_camera.notifier_call = wacom_get_camera_type_notify; + g_nb_wac_camera = &wacom->nb_camera; + + return wacom_fw_update_on_probe(wacom); +} + +int wacom_probe(struct wacom_data *wacom) +{ + int ret = 0; + + input_info(true, wacom->dev, "%s\n", __func__); + + ret = wacom_hw_init(wacom); + if (ret < 0) { + input_err(true, wacom->dev, "%s: fail to init hw\n", __func__); + g_nb_wac_camera = NULL; + wacom_dev_remove(wacom); + return ret; + } + + wacom_input_init(wacom); + /*Request IRQ */ + ret = devm_request_threaded_irq(wacom->dev, wacom->irq, NULL, wacom_interrupt, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, "sec_epen_irq", wacom); + if (ret < 0) { + input_err(true, wacom->dev, "failed to request irq(%d) - %d\n", wacom->irq, ret); + wacom_dev_remove(wacom); + return ret; + } + + if (wacom->use_garage) { + ret = devm_request_threaded_irq(wacom->dev, wacom->irq_pdct, NULL, wacom_interrupt_pdct, + IRQF_ONESHOT | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "sec_epen_pdct", wacom); + if (ret < 0) { + input_err(true, wacom->dev, "failed to request pdct irq(%d) - %d\n", wacom->irq_pdct, ret); + wacom_dev_remove(wacom); + return ret; + } + input_info(true, wacom->dev, "init pdct %d\n", wacom->irq_pdct); + } + + if (wacom->use_garage) { + wacom->irq_workqueue_pdct = create_singlethread_workqueue("irq_wq_pdct"); + if (!IS_ERR_OR_NULL(wacom->irq_workqueue_pdct)) { + INIT_WORK(&wacom->irq_work_pdct, wacom_handler_wait_resume_pdct_work); + input_info(true, wacom->dev, "%s: set wacom_handler_wait_resume_pdct_work\n", __func__); + } else { + input_err(true, wacom->dev, "%s: failed to create irq_workqueue_pdct, err: %ld\n", + __func__, PTR_ERR(wacom->irq_workqueue_pdct)); + } + } + + if (gpio_is_valid(wacom->esd_detect_gpio)) { + INIT_DELAYED_WORK(&wacom->esd_reset_work, wacom_esd_reset_work); + ret = devm_request_threaded_irq(wacom->dev, wacom->irq_esd_detect, NULL, + wacom_esd_detect_interrupt, IRQF_ONESHOT | IRQF_TRIGGER_FALLING, + "sec_epen_esd_detectirq", wacom); + if (ret < 0) + input_err(true, wacom->dev, "failed to request esd_detect_irq(%d) - %d\n", + wacom->irq_esd_detect, ret); + } + + input_info(true, wacom->dev, "init irq %d\n", wacom->irq); + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_register_notify(&wacom->nb, wacom_notifier_call, 2); +#endif + + wacom->ble_hist = devm_kzalloc(wacom->dev, WACOM_BLE_HISTORY_SIZE, GFP_KERNEL); + if (!wacom->ble_hist) + input_err(true, wacom->dev, "failed to history mem\n"); + + wacom->ble_hist1 = devm_kzalloc(wacom->dev, WACOM_BLE_HISTORY1_SIZE, GFP_KERNEL); + if (!wacom->ble_hist1) + input_err(true, wacom->dev, "failed to history1 mem\n"); + + probe_open_test(wacom); + g_wacom = wacom; + wacom->probe_done = true; + +#ifdef SUPPORT_STANDBY_MODE + // wacom ic default mode : WACOM_LOW_SCAN_RATE + wacom->standby_state = WACOM_LOW_SCAN_RATE; +#endif + + input_info(true, wacom->dev, "probe done\n"); + input_log_fix(); + + return 0; +} + +static int wacom_parse_dt(struct device *dev, struct wacom_data *wacom) +{ + struct device_node *np = dev->of_node; + u32 tmp[5] = { 0, }; + int ret = 0; + + if (!np) { + input_err(true, dev, "%s: of_node is not exist\n", __func__); + return -ENODEV; + } + + wacom->fwe_gpio = of_get_named_gpio(np, "wacom,fwe-gpio", 0); + if (!gpio_is_valid(wacom->fwe_gpio)) { + input_err(true, dev, "failed to get fwe-gpio\n"); + return -EINVAL; + } + + wacom->pdct_gpio = of_get_named_gpio(np, "wacom,pdct-gpio", 0); + if (!gpio_is_valid(wacom->pdct_gpio)) { + input_err(true, dev, "failed to get pdct-gpio, not support garage\n"); + } else { + wacom->use_garage = true; + ret = of_property_read_u32(np, "wacom,support_garage_open_test", &wacom->support_garage_open_test); + if (ret) { + input_err(true, dev, "failed to read support_garage_open_test %d\n", ret); + wacom->support_garage_open_test = 0; + } + } + + wacom->esd_detect_gpio = of_get_named_gpio(np, "wacom,esd_detect_gpio", 0); + if (!gpio_is_valid(wacom->esd_detect_gpio)) + input_err(true, dev, "failed to get esd_detect_gpio\n"); + else + input_info(true, dev, "using esd_detect_gpio\n"); + + /* get features */ + ret = of_property_read_u32(np, "wacom,boot_addr", tmp); + if (ret < 0) { + input_err(true, dev, "failed to read boot address %d\n", ret); + return -EINVAL; + } + wacom->boot_addr = tmp[0]; + + wacom->use_dt_coord = of_property_read_bool(np, "wacom,use_dt_coord"); + + ret = of_property_read_u32(np, "wacom,max_pressure", tmp); + if (ret != -EINVAL) { + if (ret) + input_err(true, dev, "failed to read max pressure %d\n", ret); + else + wacom->max_pressure = tmp[0]; + } + + ret = of_property_read_u32_array(np, "wacom,max_tilt", tmp, 2); + if (ret != -EINVAL) { + if (ret) { + input_err(true, dev, "failed to read max x tilt %d\n", ret); + } else { + wacom->max_x_tilt = tmp[0]; + wacom->max_y_tilt = tmp[1]; + } + } + + ret = of_property_read_u32(np, "wacom,max_height", tmp); + if (ret != -EINVAL) { + if (ret) + input_err(true, dev, "failed to read max height %d\n", ret); + else + wacom->max_height = tmp[0]; + } + + ret = of_property_read_u32(np, "wacom,fw_separate_by_camera", &wacom->fw_separate_by_camera); + if (ret != -EINVAL) { + if (ret) { + wacom->fw_separate_by_camera = 0; + input_err(true, dev, "failed to read fw_separate_by_camera %d\n", ret); + } + } + input_info(true, dev, "%s : wacom,fw_separate_by_camera(%d)\n", + __func__, wacom->fw_separate_by_camera); + + if (wacom->fw_separate_by_camera) { + ret = of_property_read_u32(np, "wacom,fw_default_idx", &wacom->fw_bin_default_idx); + if (ret != -EINVAL) { + if (ret) { + wacom->fw_bin_default_idx = 0; + input_err(true, dev, "failed to read fw_default_idx %d\n", ret); + } + } + input_info(true, dev, "%s : wacom,fw_default_idx(%d) & read fw name later\n", + __func__, wacom->fw_bin_default_idx); + } + + ret = of_property_read_u32(np, "wacom,module_ver", &wacom->module_ver); + if (ret) { + input_err(true, dev, "failed to read module_ver %d\n", ret); + /* default setting to open test */ + wacom->module_ver = 1; + } + + wacom->support_cover_noti = of_property_read_bool(np, "wacom,support_cover_noti"); + wacom->support_set_display_mode = of_property_read_bool(np, "wacom,support_set_display_mode"); + of_property_read_u32(np, "wacom,support_sensor_hall", &wacom->support_sensor_hall); + + input_info(true, dev, + "boot_addr: 0x%X, max_pressure: %d, max_height: %d, max_tilt: (%d,%d), " + "module_ver:%d %s%s%s\n", + wacom->boot_addr, wacom->max_pressure, wacom->max_height, + wacom->max_x_tilt, wacom->max_y_tilt, + wacom->module_ver, wacom->use_garage ? ", use garage" : "", + wacom->support_garage_open_test ? ", support garage open test" : "", + wacom->support_cover_noti ? ", support cover noti" : ""); + + return 0; +} + +int wacom_init(struct wacom_data *wacom) +{ + int ret = 0; + + if (!wacom) + return -EINVAL; + + input_info(true, wacom->dev, "%s\n", __func__); + + ret = wacom_parse_dt(wacom->dev, wacom); + if (ret < 0) { + input_err(true, wacom->dev, "failed to parse dt\n"); + return ret; + } + + wacom->plat_data->dev = wacom->dev; + wacom->plat_data->irq = wacom->irq; + //wacom->plat_data->pinctrl_configure = sec_input_pinctrl_configure; + //wacom->plat_data->power = sec_input_power; + //wacom->plat_data->start_device; + //wacom->plat_data->stop_device; + //wacom->plat_data->init; + //wacom->plat_data->lpmode; + + if (wacom->use_garage) { + wacom->irq_pdct = gpio_to_irq(wacom->pdct_gpio); + wacom->pen_pdct = PDCT_NOSIGNAL; + } + + ret = wacom_request_gpio(wacom); + if (ret) { + input_err(true, wacom->dev, "failed to request gpio\n"); + return ret; + } + + if (gpio_is_valid(wacom->esd_detect_gpio)) + wacom->irq_esd_detect = gpio_to_irq(wacom->esd_detect_gpio); + + wacom->fw_img = NULL; + wacom->fw_update_way = FW_NONE; +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + wacom->tsp_scan_mode = DISABLE_TSP_SCAN_BLCOK; +#endif + wacom->survey_mode = EPEN_SURVEY_MODE_NONE; + wacom->function_result = EPEN_EVENT_PEN_OUT; + /* Consider about factory, it may be need to as default 1 */ +#if WACOM_SEC_FACTORY + wacom->battery_saving_mode = true; +#else + wacom->battery_saving_mode = false; +#endif + wacom->reset_flag = false; + wacom->pm_suspend = false; + wacom->samplerate_state = true; + wacom->update_status = FW_UPDATE_PASS; + wacom->reset_is_on_going = false; + wacom->esd_continuous_reset_count = 0; + wacom->esd_reset_blocked_enable = false; + wacom->esd_packet_count = 0; + wacom->esd_irq_count = 0; + wacom->esd_max_continuous_reset_count = 0; + + init_completion(&wacom->i2c_done); + complete_all(&wacom->i2c_done); + // for irq + init_completion(&wacom->plat_data->resume_done); + complete_all(&wacom->plat_data->resume_done); + + /*Initializing for semaphor */ + mutex_init(&wacom->i2c_mutex); + mutex_init(&wacom->lock); + mutex_init(&wacom->update_lock); + mutex_init(&wacom->irq_lock); + mutex_init(&wacom->mode_lock); + mutex_init(&wacom->ble_lock); + mutex_init(&wacom->ble_charge_mode_lock); + + INIT_DELAYED_WORK(&wacom->work_print_info, wacom_print_info_work); + + mutex_init(&wacom->plat_data->enable_mutex); + wacom->plat_data->enable = wacom_enable; + wacom->plat_data->disable = wacom_disable; + + ret = wacom_sec_fn_init(wacom); + if (ret) { + wacom->plat_data->enable = NULL; + wacom->plat_data->disable = NULL; + return ret; + } + + input_info(true, wacom->dev, "%s: fn_init\n", __func__); + + wacom->plat_data->sec_ws = wakeup_source_register(NULL, "wacom_wakelock"); + // for pdct + wacom->wacom_ws = wakeup_source_register(NULL, "wacom_wakelock_pdct"); + wacom->wacom_esd_ws = wakeup_source_register(NULL, "wacom_esd_wakelock"); + + input_info(true, wacom->dev, "%s: init resource\n", __func__); + + return 0; +} diff --git a/drivers/input/sec_input/wacom/wacom_dev.h b/drivers/input/sec_input/wacom/wacom_dev.h new file mode 100755 index 000000000000..6b71850ea1d0 --- /dev/null +++ b/drivers/input/sec_input/wacom/wacom_dev.h @@ -0,0 +1,740 @@ +#ifndef _LINUX_WACOM_H_ +#define _LINUX_WACOM_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if IS_ENABLED(CONFIG_SPU_VERIFY) +#include +#define SUPPORT_FW_SIGNED +#endif +#include + + +#include "wacom_reg.h" +#include "../sec_input.h" +#include "../sec_tsp_log.h" + +#undef CONFIG_USB_TYPEC_MANAGER_NOTIFIER +#if defined(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) +#include +#endif + +#ifdef CONFIG_BATTERY_SAMSUNG +extern unsigned int lpcharge; +#endif + +#define RETRY_COUNT 3 + +#if defined(CONFIG_SEC_FACTORY) +#define WACOM_SEC_FACTORY 1 +#else +#define WACOM_SEC_FACTORY 0 +#endif + +#if defined(CONFIG_SAMSUNG_PRODUCT_SHIP) +#define WACOM_PRODUCT_SHIP 1 +#else +#define WACOM_PRODUCT_SHIP 0 +#endif + +#define WACOM_I2C_RETRY 3 +#define WACOM_CMD_RETRY 2 +#define WACOM_INVALID_IRQ_COUNT 2 + +#define WACOM_CMD_RESULT_WORD_LEN 20 + +#define WACOM_I2C_MODE_NORMAL false +#define WACOM_I2C_MODE_BOOT true + +#define WACOM_BLE_HISTORY_SIZE (22 * 150) +#define WACOM_BLE_HISTORY1_SIZE (22) + +/* wacom features */ +#define USE_OPEN_CLOSE +#define WACOM_USE_SURVEY_MODE /* SURVEY MODE is LPM mode : Only detect garage(pdct) & aop */ + +#ifdef WACOM_USE_SURVEY_MODE +#define EPEN_RESUME_DELAY 0 +#else +#define EPEN_RESUME_DELAY 180 +#endif +#define EPEN_OFF_TIME_LIMIT 10000 // usec + +/* query data */ +#define COM_COORD_NUM 16 +#define COM_RESERVED_NUM 0 +#define COM_QUERY_NUM 16 +#define COM_QUERY_POS (COM_COORD_NUM + COM_RESERVED_NUM) +#define COM_QUERY_BUFFER (COM_QUERY_POS + COM_QUERY_NUM) + +#define MAXIMUM_CONTINUOUS_ESD_RESET_COUNT 100 +#define WACOM_ESD_WAKE_LOCK_TIME 1000 + +/* standby mode */ +#define SUPPORT_STANDBY_MODE +#ifdef SUPPORT_STANDBY_MODE +#define SET_STANDBY_TIME (60 * 60 * 72) // 72hours (60s * 60m * 72h) + +enum VSYNC_SKIP_ID { + WACOM_ORIGINAL_SCAN_RATE = 0, + WACOM_LOW_SCAN_RATE, +}; +#endif + +/* packet ID for wacom data */ +enum PACKET_ID { + COORD_PACKET = 1, + AOP_PACKET = 3, + NOTI_PACKET = 13, + REPLY_PACKET, + SEPC_PACKET, +}; + +enum NOTI_SUB_ID { + ERROR_PACKET = 0, + TABLE_SWAP_PACKET, + EM_NOISE_PACKET, + TSP_STOP_PACKET, + OOK_PACKET, + CMD_PACKET, + GCF_PACKET = 7, /* Garage Charging Finished notification data*/ + STANDBY_PACKET = 9, + ESD_DETECT_PACKET = 126, +}; + +enum REPLY_SUB_ID { + OPEN_CHECK_PACKET = 1, + SWAP_PACKET, + SENSITIVITY_PACKET, + TSP_STOP_COND_PACKET, + GARAGE_CHARGE_PACKET = 6, + ELEC_TEST_PACKET = 101, +}; + +enum SEPC_SUB_ID { + QUERY_PACKET = 0, + CHECKSUM_PACKET, +}; + +enum TABLE_SWAP { + TABLE_SWAP_DEX_STATION = 1, + TABLE_SWAP_KBD_COVER = 2, +}; + +enum COMPENSATION_STATE { + NOMAL_MODE = 0, + BOOKCOVER_MODE, + KBDCOVER_MODE, + DISPLAY_NS_MODE, + DISPLAY_HS_MODE, + REARCOVER_TYPE1, + REARCOVER_TYPE2, +}; + +enum display_mode_state { + NOMAL_SPEED_MODE = 0, + ADAPTIVE_MODE, + HIGH_SPEED_MODE, +}; + +#define WACOM_PATH_EXTERNAL_FW "wacom.bin" +#define WACOM_PATH_EXTERNAL_FW_SIGNED "wacom_signed.bin" +#define WACOM_PATH_SPU_FW_SIGNED "/WACOM/ffu_wacom.bin" +#define WACOM_PATH_SPU_FW0_SIGNED "/WACOM/ffu_wacom0.bin" +#define WACOM_PATH_SPU_FW1_SIGNED "/WACOM/ffu_wacom1.bin" +#define WACOM_PATH_SPU_FW2_SIGNED "/WACOM/ffu_wacom2.bin" +#define WACOM_PATH_SPU_FW3_SIGNED "/WACOM/ffu_wacom3.bin" + +#define FW_UPDATE_RUNNING 1 +#define FW_UPDATE_PASS 2 +#define FW_UPDATE_FAIL -1 + +enum { + WACOM_BUILT_IN = 0, + WACOM_SDCARD, + WACOM_NONE, + WACOM_SPU, + WACOM_VERIFICATION, +}; + +enum { + FW_NONE = 0, + FW_BUILT_IN, + FW_SPU, + FW_HEADER, + FW_IN_SDCARD, + FW_IN_SDCARD_SIGNED, +#if WACOM_SEC_FACTORY + FW_FACTORY_GARAGE, + FW_FACTORY_UNIT, +#endif + FW_VERIFICATION, +}; + +/* header ver 1 */ +struct fw_image { + u8 hdr_ver; + u8 hdr_len; + u16 fw_ver1; + u16 fw_ver2; + u16 fw_ver3; + u32 fw_len; + u8 checksum[5]; + u8 alignment_dummy[3]; + u8 data[0]; +} __attribute__ ((packed)); + + +#define PDCT_NOSIGNAL true +#define PDCT_DETECT_PEN false + +/*-------------------------------------------------- + * event + * wacom->function_result + * 7. ~ 4. reserved || 3. Garage | 2. Power Save | 1. AOP | 0. Pen In/Out | + * + * 0. Pen In/Out ( pen_insert ) + * ( 0: IN / 1: OUT ) + *-------------------------------------------------- + */ +#define EPEN_EVENT_PEN_OUT (1 << 0) +#define EPEN_EVENT_GARAGE (1 << 1) +#define EPEN_EVENT_AOP (1 << 2) +#define EPEN_EVENT_SURVEY (EPEN_EVENT_GARAGE | EPEN_EVENT_AOP) + +#define EPEN_SURVEY_MODE_NONE 0x0 +#define EPEN_SURVEY_MODE_GARAGE_ONLY EPEN_EVENT_GARAGE +#define EPEN_SURVEY_MODE_GARAGE_AOP EPEN_EVENT_AOP + + +/*-------------------------------------------------- + * function setting by user or default + * wacom->function_set + * 7~4. reserved | 3. AOT | 2. ScreenOffMemo | 1. AOD | 0. Depend on AOD + * + * 3. AOT - aot_enable sysfs + * 2. ScreenOffMemo - screen_off_memo_enable sysfs + * 1. AOD - aod_enable sysfs + * 0. Depend on AOD - 0 : lcd off status, 1 : lcd on status + *-------------------------------------------------- + */ +#define EPEN_SETMODE_AOP_OPTION_AOD_LCD_STATUS (0x1<<0) +#define EPEN_SETMODE_AOP_OPTION_AOD (0x1<<1) +#define EPEN_SETMODE_AOP_OPTION_SCREENOFFMEMO (0x1<<2) +#define EPEN_SETMODE_AOP_OPTION_AOT (0x1<<3) +#define EPEN_SETMODE_AOP_OPTION_AOD_LCD_ON (EPEN_SETMODE_AOP_OPTION_AOD | EPEN_SETMODE_AOP_OPTION_AOD_LCD_STATUS) +#define EPEN_SETMODE_AOP_OPTION_AOD_LCD_OFF EPEN_SETMODE_AOP_OPTION_AOD +#define EPEN_SETMODE_AOP (EPEN_SETMODE_AOP_OPTION_AOD | EPEN_SETMODE_AOP_OPTION_SCREENOFFMEMO | \ + EPEN_SETMODE_AOP_OPTION_AOT) + +enum { + EPEN_POS_ID_SCREEN_OF_MEMO = 1, +}; +/* + * struct epen_pos - for using to send coordinate in survey mode + * @id: for extension of function + * 0 -> not use + * 1 -> for Screen off Memo + * 2 -> for oter app or function + * @x: x coordinate + * @y: y coordinate + */ +struct epen_pos { + u8 id; + int x; + int y; +}; + +/*-------------------------------------------------- + * Set S-Pen mode for TSP + * 1 byte input/output parameter + * byte[0]: S-pen mode + * - 0: non block tsp scan + * - 1: block tsp scan + * - others: Reserved for future use + *-------------------------------------------------- + */ +#define DISABLE_TSP_SCAN_BLCOK 0x00 +#define ENABLE_TSP_SCAN_BLCOK 0x01 + +enum WAKEUP_ID { + HOVER_WAKEUP = 2, + SINGLETAP_WAKEUP, + DOUBLETAP_WAKEUP, +}; + +enum SCAN_MODE { + GLOBAL_Y_MODE = 1, + GLOBAL_X_MODE, + FULL_MODE, + LOCAL_MODE, +}; + +#define WACOM_STOP_CMD 0x00 +#define WACOM_START_CMD 0x01 +#define WACOM_STOP_AND_START_CMD 0x02 + +#define EPEN_CHARGER_OFF_TIME 90 /* default 60s + buffer time */ +enum epen_scan_info{ + EPEN_CHARGE_OFF = 0, + EPEN_CHARGE_ON = 1, + EPEN_CHARGE_HAPPENED = 2, + EPEN_NONE, +}; + +typedef enum { + WACOM_ENABLE = 0, + WACOM_DISABLE = 1, +} wacom_disable_mode_t; + +enum epen_fw_ver_info{ + WACOM_FIRMWARE_VERSION_UNIT = 3, + WACOM_FIRMWARE_VERSION_SET = 4, + WACOM_FIRMWARE_VERSION_TEST = 6, +} ; + +/* WACOM_DEBUG : Print event contents */ +#define WACOM_DEBUG_PRINT_I2C_READ_CMD 0x01 +#define WACOM_DEBUG_PRINT_I2C_WRITE_CMD 0x02 +#define WACOM_DEBUG_PRINT_COORDEVENT 0x04 +#define WACOM_DEBUG_PRINT_ALLEVENT 0x08 + +/* digitizer & garage open test */ +#define EPEN_OPEN_TEST_PASS 0 +#define EPEN_OPEN_TEST_NOTSUPPORT -1 +#define EPEN_OPEN_TEST_EIO -2 +#define EPEN_OPEN_TEST_FAIL -3 +#define EPEN_OPEN_TEST_EMODECHG -4 +enum { + WACOM_GARAGE_TEST = 0, + WACOM_DIGITIZER_TEST, +}; + +#define RT_SUNNY 8 +#define RT_SEC 1 +#define RW_SUNNY 1 +#define RW_SVCC 5 + +enum camera_dualized_system { + E3_RT2_08_RW1_01 = 0, // SUNNY_AND_SEC + E3_RT2_01_RW1_01 = 1, // SUNNY_AND_SVCC + E3_RT2_08_RW1_05 = 2, // SEC_AND_SEC + E3_RT2_01_RW1_05 = 3, // SEC_AND_SVCC +}; + +enum firmware_index { + FW_INDEX_0 = 0, + FW_INDEX_1, + FW_INDEX_2, + FW_INDEX_3, +}; + +/* elec data */ +#define COM_ELEC_NUM 38 +#define COM_ELEC_DATA_NUM 12 +#define COM_ELEC_DATA_POS 14 + +#define POWER_OFFSET 1000000000000 + +#define TIME_MILLS_MAX 5 +#if WACOM_SEC_FACTORY +#define DIFF_MAX 250 +#define DIFF_MIN -250 +#else +#define DIFF_MAX 650 +#define DIFF_MIN -650 +#endif + +enum { + SEC_NORMAL = 0, + SEC_SHORT, + SEC_OPEN, +}; + +enum epen_elec_spec_mode { + EPEN_ELEC_DATA_MAIN = 0, // main + EPEN_ELEC_DATA_ASSY = 1, // sub - assy + EPEN_ELEC_DATA_UNIT = 2, // sub - unit + EPEN_ELEC_DATA_NEW_ASSY = 3, // sub - new assy + EPEN_ELEC_DATA_DGT_ASSY = 4, // sub - dgt assy + EPEN_ELEC_DATA_MAX, +}; + +struct wacom_elec_data { + u32 spec_ver[2]; + u8 max_x_ch; + u8 max_y_ch; + u8 shift_value; + + u16 *elec_data; + + u16 *xx; + u16 *xy; + u16 *yx; + u16 *yy; + + u16 *xx_self; + u16 *yy_self; + u16 *xy_edg; + u16 *yx_edg; + + long long cal_xx; + long long cal_xy; + long long cal_yx; + long long cal_yy; + long long cal_xy_edg; + long long cal_yx_edg; + + long long *xx_xx; + long long *xy_xy; + long long *yx_yx; + long long *yy_yy; + long long *xy_xy_edg; + long long *yx_yx_edg; + + long long *rxx; + long long *rxy; + long long *ryx; + long long *ryy; + long long *rxy_edg; + long long *ryx_edg; + + long long *drxx; + long long *drxy; + long long *dryx; + long long *dryy; + long long *drxy_edg; + long long *dryx_edg; + + long long *xx_ref; + long long *xy_ref; + long long *yx_ref; + long long *yy_ref; + long long *xy_edg_ref; + long long *yx_edg_ref; + + long long *xx_spec; + long long *xy_spec; + long long *yx_spec; + long long *yy_spec; + long long *xx_self_spec; + long long *yy_self_spec; + + long long *rxx_ref; + long long *rxy_ref; + long long *ryx_ref; + long long *ryy_ref; + + long long *drxx_spec; + long long *drxy_spec; + long long *dryx_spec; + long long *dryy_spec; + long long *drxy_edg_spec; + long long *dryx_edg_spec; + +}; + +struct wacom_data { + struct i2c_client *client; + struct i2c_client *client_boot; + struct device *dev; + struct input_dev *input_dev; + struct input_dev *input_dev_unused; + struct sec_cmd_data sec; + struct sec_ts_plat_data *plat_data; + struct mutex i2c_mutex; + struct mutex lock; + struct mutex update_lock; + struct mutex irq_lock; + struct mutex ble_lock; + struct mutex ble_charge_mode_lock; + + bool probe_done; + bool query_status; + struct completion i2c_done; + int tv_sec; + long tv_nsec; + + int irq; + int irq_pdct; + int pen_pdct; + bool pdct_lock_fail; + struct delayed_work open_test_dwork; + + struct wacom_elec_data *edata; /* currnet test spec */ + struct wacom_elec_data *edatas[EPEN_ELEC_DATA_MAX]; + +// int irq_gpio; + int pdct_gpio; + int fwe_gpio; + int esd_detect_gpio; + + int boot_addr; +// struct regulator *avdd; + + bool use_dt_coord; + int max_x; + int max_y; + int max_pressure; + int max_x_tilt; + int max_y_tilt; + int max_height; + u32 fw_separate_by_camera; + u32 fw_bin_default_idx; +// const char *fw_path; +#if WACOM_SEC_FACTORY + const char *fw_fac_path; +#endif + u8 bl_ver; + u32 module_ver; + u32 support_garage_open_test; + bool use_garage; +// bool regulator_boot_on; +// u32 bringup; + bool support_cover_noti; + bool support_set_display_mode; + int support_sensor_hall; + +// bool enable_sysinput_enabled; + +// u32 area_indicator; +// u32 area_navigation; +// u32 area_edge; + +// u8 img_version_of_ic[4]; +// u8 img_version_of_bin[4]; + u8 img_version_of_spu[4]; + +// !!!! +// bool power_enable; + struct wakeup_source *wacom_ws; + bool pm_suspend; +// volatile bool probe_done; +// bool query_status; + +// struct completion i2c_done; + +// struct work_struct irq_work_spen; +// struct workqueue_struct *irq_workqueue_spen; + struct work_struct irq_work_pdct; + struct workqueue_struct *irq_workqueue_pdct; + + /* survey & garage */ + struct mutex mode_lock; + u8 function_set; + u8 function_result; + atomic_t screen_on; + u8 survey_mode; + u8 check_survey_mode; + int samplerate_state; + volatile u8 report_scan_seq; /* 1:normal, 2:garage+aod, 3:garage only, 4:sync aop*/ + + /* ble(ook) */ + volatile u8 ble_mode; + long chg_time_stamp; + volatile bool ble_mode_change; + volatile bool ble_block_flag; + bool ble_charging_state; + bool ble_disable_flag; + + /* for tui or factory test */ + bool epen_blocked; + + /* coord */ + u16 hi_x; + u16 hi_y; + u16 pressed_x; + u16 pressed_y; + u16 x; + u16 y; + u16 previous_x; + u16 previous_y; + s8 tilt_x; + s8 tilt_y; + u16 pressure; + u16 height; + int tool; + u32 mcount; + int pen_prox; + int pen_pressed; + int side_pressed; + + struct epen_pos survey_pos; + /* esd reset */ + int esd_packet_count; + int esd_irq_count; + int esd_max_continuous_reset_count; + int esd_continuous_reset_count; + int irq_esd_detect; + struct delayed_work esd_reset_work; + bool reset_is_on_going; + struct mutex esd_reset_mutex; + bool esd_reset_blocked_enable; + struct wakeup_source *wacom_esd_ws; + + /* fw update */ + struct fw_image *fw_img; + const struct firmware *firm_data; + + int update_status; + u8 *fw_data; + u8 fw_update_way; + u16 fw_bin_idx; + + bool checksum_result; + char fw_chksum[5]; + bool do_crc_check; + + /* tsp block */ + bool is_tsp_block; + int tsp_scan_mode; + int tsp_block_cnt; + + /* support additional mode */ + bool battery_saving_mode; + int wcharging_mode; + + struct delayed_work nb_reg_work; + + struct notifier_block nb; + + /* open test*/ + volatile bool is_open_test; + // digitizer + bool connection_check; + int fail_channel; + int min_adc_val; + int error_cal; + int min_cal_val; + // garage + bool garage_connection_check; + int garage_fail_channel; + int garage_min_adc_val; + int garage_error_cal; + int garage_min_cal_val; + + /* check abnormal case*/ + struct delayed_work work_print_info; + u32 print_info_cnt_open; + u32 scan_info_fail_cnt; + u32 check_elec; + u32 i2c_fail_count; + u32 abnormal_reset_count; + u32 pen_out_count; + volatile bool reset_flag; + u8 debug_flag; + u8 fold_state; + +#if WACOM_SEC_FACTORY + volatile bool fac_garage_mode; + u32 garage_gain0; + u32 garage_gain1; + u32 garage_freq0; + u32 garage_freq1; +#endif + char *ble_hist; + char *ble_hist1; + int ble_hist_index; + char compensation_type; + + /* camera module nb */ + struct notifier_block nb_camera; + struct delayed_work work_camera_check; + u32 nb_camera_call_cnt; + + /* standby mode */ +#ifdef SUPPORT_STANDBY_MODE + bool standby_enable; + char standby_state; + long long activate_time; +#endif +}; + +extern struct wacom_data *g_wacom; +#ifdef CONFIG_DISPLAY_SAMSUNG +extern int get_lcd_attached(char *mode); +#endif +#ifdef CONFIG_EXYNOS_DPU30 +extern int get_lcd_info(char *arg); +#endif + +extern int is_register_eeprom_notifier(struct notifier_block *nb); + +int wacom_enable(struct device *dev); + +int wacom_power(struct wacom_data *wacom, bool on); +void wacom_reset_hw(struct wacom_data *wacom); + +void wacom_compulsory_flash_mode(struct wacom_data *wacom, bool enable); +int wacom_get_irq_state(struct wacom_data *wacom); + +void wacom_wakeup_sequence(struct wacom_data *wacom); +void wacom_sleep_sequence(struct wacom_data *wacom); + +int wacom_load_fw(struct wacom_data *wacom, u8 fw_path); +void wacom_unload_fw(struct wacom_data *wacom); +int wacom_fw_update_on_hidden_menu(struct wacom_data *wacom, u8 fw_update_way); +int wacom_flash(struct wacom_data *wacom); + +void wacom_enable_irq(struct wacom_data *wacom, bool enable); + +int wacom_send(struct wacom_data *wacom, const char *buf, int count); +int wacom_send_boot(struct wacom_data *wacom, const char *buf, int count); +int wacom_recv(struct wacom_data *wacom, char *buf, int count); +int wacom_recv_boot(struct wacom_data *wacom, char *buf, int count); + +int wacom_query(struct wacom_data *wacom); +int wacom_checksum(struct wacom_data *wacom); +int wacom_set_sense_mode(struct wacom_data *wacom); + +void wacom_forced_release(struct wacom_data *wacom); +void forced_release_fullscan(struct wacom_data *wacom); + +void wacom_select_survey_mode(struct wacom_data *wacom, bool enable); +int wacom_set_survey_mode(struct wacom_data *wacom, int mode); +int wacom_start_stop_cmd(struct wacom_data *wacom, int mode); + +int wacom_open_test(struct wacom_data *wacom, int test_mode); + +int wacom_sec_fn_init(struct wacom_data *wacom); +void wacom_sec_remove(struct wacom_data *wacom); + +void wacom_print_info(struct wacom_data *wacom); +void wacom_coord_modify(struct wacom_data *wacom); +void wacom_disable_mode(struct wacom_data *wacom, wacom_disable_mode_t mode); + +int wacom_check_ub(struct wacom_data *wacom); + +void wacom_swap_compensation(struct wacom_data *wacom, char cmd); + +int wacom_ble_charge_mode(struct wacom_data *wacom, int mode); + +//core +int wacom_init(struct wacom_data *wacom); +int wacom_probe(struct wacom_data *wacom); +void wacom_dev_remove(struct wacom_data *wacom); +int wacom_send_sel(struct wacom_data *wacom, const char *buf, int count, bool mode); + +//elec +int calibration_trx_data(struct wacom_data *wacom); +void calculate_ratio(struct wacom_data *wacom); +void make_decision(struct wacom_data *wacom, u16 *arrResult); +void print_elec_data(struct wacom_data *wacom); +void print_trx_data(struct wacom_data *wacom); +void print_cal_trx_data(struct wacom_data *wacom); +void print_ratio_trx_data(struct wacom_data *wacom); +void print_difference_ratio_trx_data(struct wacom_data *wacom); + +#endif diff --git a/drivers/input/sec_input/wacom/wacom_elec.c b/drivers/input/sec_input/wacom/wacom_elec.c new file mode 100755 index 000000000000..208702828030 --- /dev/null +++ b/drivers/input/sec_input/wacom/wacom_elec.c @@ -0,0 +1,800 @@ +/* + * wacom_elec.c - Wacom G5 Digitizer Controller (I2C bus) + * + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "wacom_dev.h" + +long long median(long long *arr, int length) +{ + long long ret = -1; + int i, j; + long long temp; + + for (i = 0; i < length; i++) { + for (j = 0; j < length - (i + 1); j++) { + if (arr[j] > arr[j + 1]) { + temp = arr[j + 1]; + arr[j + 1] = arr[j]; + arr[j] = temp; + } + } + } + + if (length % 2 == 0) + ret = (arr[length / 2 - 1] + arr[length / 2]) / 2; + else + ret = arr[length / 2]; + + return ret; +} + +int builds(long long *x, long long *m, int start, int end, int ind, long long *data) +{ + int i; + long long *temp; + + temp = kmalloc_array((end - start + 1), sizeof(long long), GFP_KERNEL); + if (!temp) + return -ENOMEM; + + memcpy(temp, &x[start], sizeof(long long)*(end - start + 1)); + for (i = 0; i < end - start + 1; i++) { + temp[i] -= m[ind]; + temp[i] = (temp[i] > 0) ? temp[i] : -temp[i]; + } + + *data = median(temp, end - start + 1); + + kfree(temp); + + return 0; +} + +int hampel(long long *x, int length, int k, int nsig) +{ + const long long kappa = 14826; + int i; + long long *m; + long long *s; + long long *temp; + long long data; + + m = kmalloc_array(length, sizeof(long long), GFP_KERNEL); + if (!m) + return -ENOMEM; + s = kmalloc_array(length, sizeof(long long), GFP_KERNEL); + if (!s) { + kfree(m); + return -ENOMEM; + } + temp = kmalloc_array(length, sizeof(long long), GFP_KERNEL); + if (!temp) { + kfree(m); + kfree(s); + return -ENOMEM; + } + + memset(m, 0x0, length * sizeof(long long)); + memset(s, 0x0, length * sizeof(long long)); + memset(temp, 0x0, length * sizeof(long long)); + + for (i = 0; i < length; i++) { + if (i < k) { + if (i + k + 1 > length) + continue; + + memcpy(temp, x, sizeof(long long) * (i + k + 1)); + m[i] = median(temp, i + k + 1); + } else if (i >= length - k) { + memcpy(temp, &x[i - k], sizeof(long long) * (length - i + k)); + m[i] = median(temp, length - i + k); + } else { + memcpy(temp, &x[i - k], sizeof(long long) * (2 * k + 1)); + m[i] = median(temp, 2 * k + 1); + } + } + + for (i = 0; i < length; i++) { + if (i < k) { + if (builds(x, m, 0, i + k, i, &data) < 0) + goto error; + s[i] = data; + } else if (i >= length - k) { + if (builds(x, m, i - k, length - 1, i, &data) < 0) + goto error; + s[i] = data; + } else { + if (builds(x, m, i - k, i + k, i, &data) < 0) + goto error; + s[i] = data; + } + + s[i] = (s[i] * kappa) / 10000; + } + + for (i = 0; i < length; i++) { + if (x[i] - m[i] > nsig * s[i] || x[i] - m[i] < -(nsig * s[i])) + x[i] = m[i]; + } + + kfree(m); + kfree(s); + kfree(temp); + + return 0; +error: + kfree(m); + kfree(s); + kfree(temp); + + pr_err("%s: %s failed", SECLOG, __func__); + + return -ENOMEM; +} + +long long mean(long long *arr, int length) +{ + long long ret = arr[0]; + int n; + + for (n = 1; n < length; n++) + ret = (n * ret + arr[n]) / (n + 1); + + return ret; +} + +int calibration_trx_data(struct wacom_data *wacom) +{ + struct wacom_elec_data *edata = wacom->edata; + long long *cal_xx_raw, *cal_xy_raw, *cal_yx_raw, *cal_yy_raw, *cal_xy_edg_raw, *cal_yx_edg_raw; + int i; + + cal_xx_raw = cal_xy_raw = cal_yx_raw = cal_yy_raw = cal_xy_edg_raw = cal_yx_edg_raw = NULL; + edata->cal_xx = edata->cal_xy = edata->cal_yx = edata->cal_yy = edata->cal_xy_edg = edata->cal_yx_edg = 0; + + cal_xx_raw = kzalloc(edata->max_x_ch * sizeof(long long), GFP_KERNEL); + cal_xy_raw = kzalloc(edata->max_x_ch * sizeof(long long), GFP_KERNEL); + cal_yx_raw = kzalloc(edata->max_y_ch * sizeof(long long), GFP_KERNEL); + cal_yy_raw = kzalloc(edata->max_y_ch * sizeof(long long), GFP_KERNEL); + cal_xy_edg_raw = kzalloc(edata->max_x_ch * sizeof(long long), GFP_KERNEL); + cal_yx_edg_raw = kzalloc(edata->max_y_ch * sizeof(long long), GFP_KERNEL); + + if (!cal_xx_raw || !cal_xy_raw || !cal_yx_raw || !cal_yy_raw || !cal_xy_edg_raw || !cal_yx_edg_raw) { + if (cal_xx_raw) + kfree(cal_xx_raw); + if (cal_xy_raw) + kfree(cal_xy_raw); + if (cal_yx_raw) + kfree(cal_yx_raw); + if (cal_yy_raw) + kfree(cal_yy_raw); + if (cal_xy_edg_raw) + kfree(cal_xy_edg_raw); + if (cal_yx_edg_raw) + kfree(cal_yx_edg_raw); + + return -ENOMEM; + } + + for (i = 0; i < edata->max_x_ch; i++) { + cal_xx_raw[i] = edata->xx_ref[i] * POWER_OFFSET / edata->xx[i]; + cal_xy_raw[i] = edata->xy_ref[i] * POWER_OFFSET / edata->xy[i]; + cal_xy_edg_raw[i] = edata->xy_edg_ref[i] * POWER_OFFSET / edata->xy_edg[i]; + } + + for (i = 0; i < edata->max_y_ch; i++) { + cal_yx_raw[i] = edata->yx_ref[i] * POWER_OFFSET / edata->yx[i]; + cal_yy_raw[i] = edata->yy_ref[i] * POWER_OFFSET / edata->yy[i]; + cal_yx_edg_raw[i] = edata->yx_edg_ref[i] * POWER_OFFSET / edata->yx_edg[i]; + } + + hampel(cal_xx_raw, edata->max_x_ch, 3, 3); + hampel(cal_xy_raw, edata->max_x_ch, 3, 3); + hampel(cal_yx_raw, edata->max_y_ch, 3, 3); + hampel(cal_yy_raw, edata->max_y_ch, 3, 3); + hampel(cal_xy_edg_raw, edata->max_x_ch, 3, 3); + hampel(cal_yx_edg_raw, edata->max_y_ch, 3, 3); + + edata->cal_xx = mean(cal_xx_raw, edata->max_x_ch); + edata->cal_xy = mean(cal_xy_raw, edata->max_x_ch); + edata->cal_yx = mean(cal_yx_raw, edata->max_y_ch); + edata->cal_yy = mean(cal_yy_raw, edata->max_y_ch); + edata->cal_xy_edg = mean(cal_xy_edg_raw, edata->max_x_ch); + edata->cal_yx_edg = mean(cal_yx_edg_raw, edata->max_y_ch); + + for (i = 0; i < edata->max_x_ch; i++) { + edata->xx_xx[i] = edata->cal_xx * edata->xx[i]; + edata->xy_xy[i] = edata->cal_xy * edata->xy[i]; + edata->xy_xy_edg[i] = edata->cal_xy_edg * edata->xy_edg[i]; + } + + for (i = 0; i < edata->max_y_ch; i++) { + edata->yx_yx[i] = edata->cal_yx * edata->yx[i]; + edata->yy_yy[i] = edata->cal_yy * edata->yy[i]; + edata->yx_yx_edg[i] = edata->cal_yx_edg * edata->yx_edg[i]; + } + + input_info(true, wacom->dev, "%s: cal_xx(%lld), cal_xy(%lld), cal_yx(%lld), cal_yy(%lld) , cal_xy_edg(%lld), cal_yx_edg(%lld)\n", + __func__, edata->cal_xx, edata->cal_xy, edata->cal_yx, edata->cal_yy, edata->cal_xy_edg, edata->cal_yx_edg); + + kfree(cal_xx_raw); + kfree(cal_xy_raw); + kfree(cal_yx_raw); + kfree(cal_yy_raw); + kfree(cal_xy_edg_raw); + kfree(cal_yx_edg_raw); + + return 0; +} + +void calculate_ratio(struct wacom_data *wacom) +{ + struct wacom_elec_data *edata = wacom->edata; + int i; + + for (i = 0; i < edata->max_x_ch; i++) + edata->rxx[i] = edata->xx_ref[i] * POWER_OFFSET / edata->xx[i]; + + for (i = 0; i < edata->max_x_ch; i++) { + edata->rxy[i] = edata->xy_ref[i] * POWER_OFFSET / edata->xy[i]; + edata->rxy_edg[i] = edata->xy_edg_ref[i] * POWER_OFFSET / edata->xy_edg[i]; + } + + for (i = 0; i < edata->max_y_ch; i++) { + edata->ryx[i] = edata->yx_ref[i] * POWER_OFFSET / edata->yx[i]; + edata->ryx_edg[i] = edata->yx_edg_ref[i] * POWER_OFFSET / edata->yx_edg[i]; + } + + for (i = 0; i < edata->max_y_ch; i++) + edata->ryy[i] = edata->yy_ref[i] * POWER_OFFSET / edata->yy[i]; + +} + +void make_decision(struct wacom_data *wacom, u16 *arrResult) +{ + struct wacom_elec_data *edata = wacom->edata; + u32 open_count, short_count; + int i; + + open_count = short_count = 0; + for (i = 0; i < edata->max_x_ch; i++) { + edata->drxx[i] = edata->rxx[i] - edata->cal_xx; + edata->drxy[i] = edata->rxy[i] - edata->cal_xy; + edata->drxy_edg[i] = edata->rxy_edg[i] - edata->cal_xy_edg; + + if (edata->xy[i] < edata->xy_ref[i] / 2 || edata->xx[i] < edata->xx_ref[i] / 2) + arrResult[i + 1] |= SEC_OPEN; + + if (edata->xy_xy[i] > edata->xy_spec[i] || edata->xx_xx[i] > edata->xx_spec[i]) + arrResult[i + 1] |= SEC_SHORT; + + if (edata->xx_self[i] > edata->xx_self_spec[i]) + arrResult[i + 1] |= SEC_OPEN; + + if (edata->drxy[i] > edata->drxy_spec[i] || edata->drxy[i] < -edata->drxy_spec[i]) + arrResult[i + 1] |= SEC_SHORT; + + if (edata->drxx[i] > edata->drxx_spec[i] || edata->drxx[i] < -edata->drxx_spec[i]) + arrResult[i + 1] |= SEC_SHORT; + + if (edata->drxy_edg[i] > edata->drxy_edg_spec[i] || edata->drxy_edg[i] < -edata->drxy_edg_spec[i]) + arrResult[i + 1] |= SEC_SHORT; + + if (arrResult[i + 1] & SEC_OPEN) + open_count++; + + if (arrResult[i + 1] & SEC_SHORT) + short_count++; + } + + for (i = 0; i < edata->max_y_ch; i++) { + edata->dryy[i] = edata->ryy[i] - edata->cal_yy; + edata->dryx[i] = edata->ryx[i] - edata->cal_yx; + edata->dryx_edg[i] = edata->ryx_edg[i] - edata->cal_yx_edg; + + if (edata->yx[i] < edata->yx_ref[i] / 2 || edata->yy[i] < edata->yy_ref[i] / 2) + arrResult[i + 1 + edata->max_x_ch] |= SEC_OPEN; + + if (edata->yx_yx[i] > edata->yx_spec[i] || edata->yy_yy[i] > edata->yy_spec[i]) + arrResult[i + 1 + edata->max_x_ch] |= SEC_SHORT; + + if (edata->yy_self[i] > edata->yy_self_spec[i]) + arrResult[i + 1 + edata->max_x_ch] |= SEC_OPEN; + + if (edata->dryx[i] > edata->dryx_spec[i] || edata->dryx[i] < -edata->dryx_spec[i]) + arrResult[i + 1 + edata->max_x_ch] |= SEC_SHORT; + + if (edata->dryy[i] > edata->dryy_spec[i] || edata->dryy[i] < -edata->dryy_spec[i]) + arrResult[i + 1 + edata->max_x_ch] |= SEC_SHORT; + + if (edata->dryx_edg[i] > edata->dryx_edg_spec[i] || edata->dryx_edg[i] < -edata->dryx_edg_spec[i]) + arrResult[i + 1 + edata->max_x_ch] |= SEC_SHORT; + + if (arrResult[i + 1 + edata->max_x_ch] & SEC_OPEN) + open_count++; + + if (arrResult[i + 1 + edata->max_x_ch] & SEC_SHORT) + short_count++; + } + + arrResult[0] = (short_count << 8) + open_count; +} + +void print_elec_data(struct wacom_data *wacom) +{ + struct wacom_elec_data *edata = wacom->edata; + u8 *pstr = NULL; + u8 ptmp[WACOM_CMD_RESULT_WORD_LEN] = { 0 }; + int chsize, lsize; + int i, j; + + input_info(true, wacom->dev, "%s\n", __func__); + + chsize = edata->max_x_ch + edata->max_y_ch; + lsize = WACOM_CMD_RESULT_WORD_LEN * (chsize + 1); + + pstr = kzalloc(lsize, GFP_KERNEL); + if (pstr == NULL) + return; + + memset(pstr, 0x0, lsize); + snprintf(ptmp, sizeof(ptmp), " TX"); + strlcat(pstr, ptmp, lsize); + + for (i = 0; i < chsize; i++) { + snprintf(ptmp, sizeof(ptmp), " %02d ", i); + strlcat(pstr, ptmp, lsize); + } + + input_info(true, wacom->dev, "%s\n", pstr); + memset(pstr, 0x0, lsize); + snprintf(ptmp, sizeof(ptmp), " +"); + strlcat(pstr, ptmp, lsize); + + for (i = 0; i < chsize; i++) { + snprintf(ptmp, sizeof(ptmp), "----"); + strlcat(pstr, ptmp, lsize); + } + + input_info(true, wacom->dev, "%s\n", pstr); + + for (i = 0; i < chsize; i++) { + memset(pstr, 0x0, lsize); + snprintf(ptmp, sizeof(ptmp), "Rx%02d | ", i); + strlcat(pstr, ptmp, lsize); + + for (j = 0; j < chsize; j++) { + snprintf(ptmp, sizeof(ptmp), " %4d", + edata->elec_data[(i * chsize) + j]); + + strlcat(pstr, ptmp, lsize); + } + input_info(true, wacom->dev, "%s\n", pstr); + } + kfree(pstr); +} + +void print_trx_data(struct wacom_data *wacom) +{ + struct wacom_elec_data *edata = wacom->edata; + u8 tmp_buf[WACOM_CMD_RESULT_WORD_LEN] = { 0 }; + u8 *buff; + int buff_size; + int i; + + buff_size = edata->max_x_ch > edata->max_y_ch ? edata->max_x_ch : edata->max_y_ch; + buff_size = WACOM_CMD_RESULT_WORD_LEN * (buff_size + 1); + + buff = kzalloc(buff_size, GFP_KERNEL); + if (buff == NULL) + return; + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "xx: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%d ", edata->xx[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "xy: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%d ", edata->xy[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "yx: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%d ", edata->yx[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "yy: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%d ", edata->yy[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "xx_self: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%d ", edata->xx_self[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "yy_self: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%d ", edata->yy_self[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "xy_edg: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%d ", edata->xy_edg[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "yx_edg: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%d ", edata->yx_edg[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + kfree(buff); +} + +void print_cal_trx_data(struct wacom_data *wacom) +{ + struct wacom_elec_data *edata = wacom->edata; + u8 tmp_buf[WACOM_CMD_RESULT_WORD_LEN] = { 0 }; + u8 *buff; + int buff_size; + int i; + + buff_size = edata->max_x_ch > edata->max_y_ch ? edata->max_x_ch : edata->max_y_ch; + buff_size = WACOM_CMD_RESULT_WORD_LEN * (buff_size + 1); + + buff = kzalloc(buff_size, GFP_KERNEL); + if (buff == NULL) + return; + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "xx_xx: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", edata->xx_xx[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "xy_xy: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", edata->xy_xy[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "yx_yx: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", edata->yx_yx[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "yy_yy: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", edata->yy_yy[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "xy_xy_edg: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", edata->xy_xy_edg[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "yx_yx_edg: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", edata->yx_yx_edg[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + kfree(buff); +} + +void print_ratio_trx_data(struct wacom_data *wacom) +{ + struct wacom_elec_data *edata = wacom->edata; + u8 tmp_buf[WACOM_CMD_RESULT_WORD_LEN] = { 0 }; + u8 *buff; + int buff_size; + int i; + + buff_size = edata->max_x_ch > edata->max_y_ch ? edata->max_x_ch : edata->max_y_ch; + buff_size = WACOM_CMD_RESULT_WORD_LEN * (buff_size + 1); + + buff = kzalloc(buff_size, GFP_KERNEL); + if (buff == NULL) + return; + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "rxx: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", edata->rxx[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "rxy: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", edata->rxy[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "ryx: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", edata->ryx[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "ryy: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", edata->ryy[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "rxy_edg: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", edata->rxy_edg[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "ryx_edg: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", edata->ryx_edg[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + kfree(buff); +} + +void print_difference_ratio_trx_data(struct wacom_data *wacom) +{ + struct wacom_elec_data *edata = wacom->edata; + u8 tmp_buf[WACOM_CMD_RESULT_WORD_LEN] = { 0 }; + u8 *buff; + int buff_size; + int i; + + buff_size = edata->max_x_ch > edata->max_y_ch ? edata->max_x_ch : edata->max_y_ch; + buff_size = WACOM_CMD_RESULT_WORD_LEN * (buff_size + 1); + + buff = kzalloc(buff_size, GFP_KERNEL); + if (buff == NULL) + return; + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "drxx: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", edata->drxx[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "drxy: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", edata->drxy[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "dryx: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", edata->dryx[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "dryy: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", edata->dryy[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "drxy_edg: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", edata->drxy_edg[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "dryx_edg: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", edata->dryx_edg[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + kfree(buff); +} diff --git a/drivers/input/sec_input/wacom/wacom_i2c.c b/drivers/input/sec_input/wacom/wacom_i2c.c new file mode 100755 index 000000000000..adde194412a6 --- /dev/null +++ b/drivers/input/sec_input/wacom/wacom_i2c.c @@ -0,0 +1,399 @@ +/* + * wacom.c - Wacom Digitizer Controller (I2C bus) + * + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "wacom_dev.h" + +int wacom_send_sel(struct wacom_data *wacom, const char *buf, int count, bool mode) +{ + struct i2c_client *client = mode ? wacom->client_boot : wacom->client; + int retry = WACOM_I2C_RETRY; + int ret; + u8 *buff; + int i; + + /* in LPM, waiting blsp block resume */ + if (!wacom->plat_data->resume_done.done) { + __pm_wakeup_event(wacom->plat_data->sec_ws, SEC_TS_WAKE_LOCK_TIME); + ret = wait_for_completion_interruptible_timeout(&wacom->plat_data->resume_done, + msecs_to_jiffies(SEC_TS_WAKE_LOCK_TIME)); + if (ret <= 0) { + input_err(true, wacom->dev, + "%s: LPM: pm resume is not handled [timeout]\n", __func__); + return -ENOMEM; + } else { + input_info(true, wacom->dev, + "%s: run LPM interrupt handler, %d\n", __func__, jiffies_to_msecs(ret)); + } + } + + buff = kzalloc(count, GFP_KERNEL); + if (!buff) + return -ENOMEM; + + mutex_lock(&wacom->i2c_mutex); + + memcpy(buff, buf, count); + + reinit_completion(&wacom->i2c_done); + do { + if (sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF)) { + input_err(true, &client->dev, "%s: Power status off\n", __func__); + ret = -EIO; + + goto out; + } + + ret = i2c_master_send(client, buff, count); + if (ret == count) + break; + + if (retry < WACOM_I2C_RETRY) { + input_err(true, &client->dev, "%s: I2C retry(%d) mode(%d)\n", + __func__, WACOM_I2C_RETRY - retry, mode); + wacom->i2c_fail_count++; + } + } while (--retry); + +out: + complete_all(&wacom->i2c_done); + mutex_unlock(&wacom->i2c_mutex); + + if (wacom->debug_flag & WACOM_DEBUG_PRINT_I2C_WRITE_CMD) { + pr_info("sec_input : i2c_cmd: W: "); + for (i = 0; i < count; i++) + pr_cont("%02X ", buf[i]); + pr_cont("\n"); + } + + kfree(buff); + + return ret; +} + +int wacom_recv_sel(struct wacom_data *wacom, char *buf, int count, bool mode) +{ + struct i2c_client *client = mode ? wacom->client_boot : wacom->client; + int retry = WACOM_I2C_RETRY; + int ret; + u8 *buff; + int i; + + /* in LPM, waiting blsp block resume */ + if (!wacom->plat_data->resume_done.done) { + __pm_wakeup_event(wacom->plat_data->sec_ws, SEC_TS_WAKE_LOCK_TIME); + ret = wait_for_completion_interruptible_timeout(&wacom->plat_data->resume_done, + msecs_to_jiffies(SEC_TS_WAKE_LOCK_TIME)); + if (ret <= 0) { + input_err(true, wacom->dev, + "%s: LPM: pm resume is not handled [timeout]\n", __func__); + return -ENOMEM; + } else { + input_info(true, wacom->dev, + "%s: run LPM interrupt handler, %d\n", __func__, jiffies_to_msecs(ret)); + } + } + + buff = kzalloc(count, GFP_KERNEL); + if (!buff) + return -ENOMEM; + + mutex_lock(&wacom->i2c_mutex); + + reinit_completion(&wacom->i2c_done); + do { + if (sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF)) { + input_err(true, &client->dev, "%s: Power status off\n", __func__); + ret = -EIO; + + goto out; + } + + ret = i2c_master_recv(client, buff, count); + if (ret == count) + break; + + if (retry < WACOM_I2C_RETRY) { + input_err(true, &client->dev, "%s: I2C retry(%d) mode(%d)\n", + __func__, WACOM_I2C_RETRY - retry, mode); + wacom->i2c_fail_count++; + } + } while (--retry); + + memcpy(buf, buff, count); + + if (wacom->debug_flag & WACOM_DEBUG_PRINT_I2C_READ_CMD) { + pr_info("sec_input : i2c_cmd: R: "); + for (i = 0; i < count; i++) + pr_cont("%02X ", buf[i]); + pr_cont("\n"); + } + +out: + complete_all(&wacom->i2c_done); + mutex_unlock(&wacom->i2c_mutex); + + kfree(buff); + + return ret; +} + +int wacom_send_boot(struct wacom_data *wacom, const char *buf, int count) +{ + return wacom_send_sel(wacom, buf, count, WACOM_I2C_MODE_BOOT); +} + +int wacom_send(struct wacom_data *wacom, const char *buf, int count) +{ + return wacom_send_sel(wacom, buf, count, WACOM_I2C_MODE_NORMAL); +} + +int wacom_recv_boot(struct wacom_data *wacom, char *buf, int count) +{ + return wacom_recv_sel(wacom, buf, count, WACOM_I2C_MODE_BOOT); +} + +int wacom_recv(struct wacom_data *wacom, char *buf, int count) +{ + return wacom_recv_sel(wacom, buf, count, WACOM_I2C_MODE_NORMAL); +} + +static int wacom_i2c_init(struct i2c_client *client) +{ + struct wacom_data *wacom; + struct sec_ts_plat_data *pdata; + int ret = 0; + + wacom = devm_kzalloc(&client->dev, sizeof(struct wacom_data), GFP_KERNEL); + if (!wacom) { + ret = -ENOMEM; + goto error_allocate_pdata; + } + + if (client->dev.of_node) { + pdata = devm_kzalloc(&client->dev, sizeof(struct sec_ts_plat_data), GFP_KERNEL); + if (!pdata) { + ret = -ENOMEM; + goto error_allocate_pdata; + } + client->dev.platform_data = pdata; + ret = sec_input_parse_dt(&client->dev); + if (ret) { + input_err(true, &client->dev, "failed to sec_input_parse_dt\n"); + goto error_allocate_pdata; + } + } else { + pdata = client->dev.platform_data; + if (!pdata) { + ret = -ENOMEM; + input_err(true, &client->dev, "%s: No platform data found\n", __func__); + goto error_allocate_pdata; + } + } + + client->irq = gpio_to_irq(pdata->irq_gpio); + wacom->client = client; + wacom->irq = client->irq; + wacom->plat_data = pdata; + wacom->dev = &client->dev; + + ret = wacom_init(wacom); + if (ret < 0) { + input_err(true, &client->dev, "%s: fail to do ts init\n", __func__); + goto error_init; + } + + /* using 2 slave address. one is normal mode, another is boot mode for + * fw update. + */ +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 10, 0)) + wacom->client_boot = i2c_new_dummy_device(client->adapter, wacom->boot_addr); +#else + wacom->client_boot = i2c_new_dummy(client->adapter, wacom->boot_addr); +#endif + if (!wacom->client_boot) { + ret = -ENOMEM; + input_err(true, &client->dev, "failed to register sub client[0x%x]\n", wacom->boot_addr); + goto error_allocate_pdata; + } + /*Set client data */ + i2c_set_clientdata(client, wacom); + i2c_set_clientdata(wacom->client_boot, wacom); + + return 0; + +error_init: +error_allocate_pdata: + input_err(true, &client->dev, "%s: failed(%d)\n", __func__, ret); + input_log_fix(); + return ret; +} + +static int wacom_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct wacom_data *wacom; + int ret = 0; + + input_info(true, &client->dev, "%s\n", __func__); + + ret = i2c_check_functionality(client->adapter, I2C_FUNC_I2C); + if (!ret) { + input_err(true, &client->dev, "I2C functionality not supported\n"); + return -EIO; + } + + ret = wacom_i2c_init(client); + if (ret < 0) { + input_err(true, &client->dev, "%s: fail to init resource\n", __func__); + return ret; + } + + wacom = i2c_get_clientdata(client); + + return wacom_probe(wacom); +} + +#ifdef CONFIG_PM +static int wacom_i2c_suspend(struct device *dev) +{ + struct wacom_data *wacom = dev_get_drvdata(dev); + int ret; + + if (wacom->i2c_done.done == 0) { + /* completion.done == 0 :: initialized + * completion.done > 0 :: completeted + */ + ret = wait_for_completion_interruptible_timeout(&wacom->i2c_done, + msecs_to_jiffies(SEC_TS_WAKE_LOCK_TIME)); + if (ret <= 0) + input_err(true, wacom->dev, "%s: completion expired, %d\n", __func__, ret); + } + + wacom->pm_suspend = true; + reinit_completion(&wacom->plat_data->resume_done); + +#ifndef USE_OPEN_CLOSE + if (wacom->input_dev->users) + wacom_sleep_sequence(wacom); +#endif + + return 0; +} + +static int wacom_i2c_resume(struct device *dev) +{ + struct wacom_data *wacom = dev_get_drvdata(dev); + + wacom->pm_suspend = false; + complete_all(&wacom->plat_data->resume_done); +#ifndef USE_OPEN_CLOSE + if (wacom->input_dev->users) + wacom_wakeup_sequence(wacom); +#endif + + return 0; +} + +static SIMPLE_DEV_PM_OPS(wacom_pm_ops, wacom_i2c_suspend, wacom_i2c_resume); +#endif + +static void wacom_i2c_shutdown(struct i2c_client *client) +{ + struct wacom_data *wacom = i2c_get_clientdata(client); + + if (!wacom) + return; + + g_wacom = NULL; + wacom->probe_done = false; + wacom->plat_data->enable = NULL; + wacom->plat_data->disable = NULL; + + input_info(true, wacom->dev, "%s called!\n", __func__); + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_unregister_notify(&wacom->nb); +#endif + + if (gpio_is_valid(wacom->esd_detect_gpio)) + cancel_delayed_work_sync(&wacom->esd_reset_work); + cancel_delayed_work_sync(&wacom->open_test_dwork); + cancel_delayed_work_sync(&wacom->work_print_info); + + wacom_enable_irq(wacom, false); + + atomic_set(&wacom->plat_data->power_state, SEC_INPUT_STATE_POWER_OFF); + wacom_power(wacom, false); + + input_info(true, wacom->dev, "%s Done\n", __func__); +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)) +static void wacom_i2c_remove(struct i2c_client *client) +#else +static int wacom_i2c_remove(struct i2c_client *client) +#endif +{ + struct wacom_data *wacom = i2c_get_clientdata(client); + + wacom_dev_remove(wacom); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)) + return; +#else + return 0; +#endif +} + +static const struct i2c_device_id wacom_id[] = { + {"wacom_wez02", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, wacom_id); + +#ifdef CONFIG_OF +static const struct of_device_id wacom_dt_ids[] = { + {.compatible = "wacom,wez02"}, + {} +}; +#endif + +static struct i2c_driver wacom_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "wacom_wez02", +#ifdef CONFIG_PM + .pm = &wacom_pm_ops, +#endif +#ifdef CONFIG_OF + .of_match_table = of_match_ptr(wacom_dt_ids), +#endif + }, + .probe = wacom_i2c_probe, + .remove = wacom_i2c_remove, + .shutdown = wacom_i2c_shutdown, + .id_table = wacom_id, +}; + +module_i2c_driver(wacom_driver); + +MODULE_AUTHOR("Samsung"); +MODULE_DESCRIPTION("Driver for Wacom Digitizer Controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/sec_input/wacom/wacom_reg.h b/drivers/input/sec_input/wacom/wacom_reg.h new file mode 100755 index 000000000000..e2f08f2c49e1 --- /dev/null +++ b/drivers/input/sec_input/wacom/wacom_reg.h @@ -0,0 +1,122 @@ +/* for query request (not use) */ +#define COM_QUERY 0x2A + +/* scan mode*/ +#define COM_SURVEY_ASYNC_SCAN 0x2B +#define COM_SURVEY_EXIT 0x2D +#define COM_SURVEY_SYNC_SCAN 0x2E +#define COM_SURVEY_GARAGE_ONLY 0x3B + +#define COM_SAMPLERATE_STOP 0x30 +#define COM_SAMPLERATE_START 0x31 + +#define COM_CHECKSUM 0x63 + +#define COM_DISPLAY_NS_MODE_COMPENSATION 0x78 +#define COM_DISPLAY_HS_MODE_COMPENSATION 0x79 +#define COM_NORMAL_COMPENSATION 0x80 +#define COM_SPECIAL_COMPENSATION 0x81 +#define COM_BOOKCOVER_COMPENSATION 0x81 +#define COM_KBDCOVER_COMPENSATION 0x82 +#define COM_REARCOVER_TYPE1 0x81 +#define COM_REARCOVER_TYPE2 0x82 + +/* standby mode */ +#define COM_SET_ACTIVATED_MODE 0x85 +#define COM_SET_STANDBY_MODE 0x86 + +/* elec test*/ +#define COM_ASYNC_VSYNC 0X28 +#define COM_SYNC_VSYNC 0X29 +#define COM_ELEC_SCAN_START 0xC8 +#define COM_ELEC_XSCAN 0xCA +#define COM_ELEC_YSCAN 0xCB +#define COM_ELEC_TRSCON 0xCE +#define COM_ELEC_TRSX0 0xCF +#define COM_ELEC_REQUESTDATA 0xD6 + +/* digitizer & garage open test */ +#define COM_GARAGE_TEST_MODE 0xC4 +#define COM_DIGITIZER_TEST_MODE 0xC5 +#define COM_OPEN_CHECK_START 0xC9 +#define COM_OPEN_CHECK_STATUS 0xD8 + +/* sensitivity set cmd */ +#define COM_NORMAL_SENSE_MODE 0xDB +#define COM_LOW_SENSE_MODE 0xDC +#define COM_LOW_SENSE_MODE2 0xE7 +#define COM_REQUEST_SENSE_MODE 0xDD + +/* have to check below register*/ +#define COM_REQUEST_GARAGEDATA 0XE5 +#define COM_REQUEST_SCANMODE 0xE6 + +#define COM_BLE_C_DISABLE 0XE8 +#define COM_BLE_C_ENABLE 0XE9 +#define COM_BLE_C_RESET 0XEA +#define COM_BLE_C_START 0XEB +#define COM_BLE_C_KEEP_ON 0XEC +#define COM_BLE_C_KEEP_OFF 0XED +#define COM_BLE_C_MODE_RETURN 0XEE +#define COM_BLE_C_FORCE_RESET 0xEF +#define COM_BLE_C_FULL 0xF3 +#define COM_FLASH 0xFF + +/* pen ble charging */ +enum epen_ble_charge_mode { + EPEN_BLE_C_DISABLE = 0, + EPEN_BLE_C_ENABLE, + EPEN_BLE_C_RESET, + EPEN_BLE_C_START, + EPEN_BLE_C_KEEP_ON, + EPEN_BLE_C_KEEP_OFF, + EPEN_BLE_C_M_RETURN, + EPEN_BLE_C_DSPX, + EPEN_BLE_C_FULL, + EPEN_BLE_C_MAX, +}; + +enum epen_ble_charge_state { + BLE_C_OFF = 0, + BLE_C_START, + BLE_C_TRANSIT, + BLE_C_RESET, + BLE_C_AFTER_START, + BLE_C_AFTER_RESET, + BLE_C_ON_KEEP_1, + BLE_C_OFF_KEEP_1, + BLE_C_ON_KEEP_2, + BLE_C_OFF_KEEP_2, + BLE_C_FULL, +}; + +/* wacom query data format */ +#define EPEN_REG_HEADER 0x00 +#define EPEN_REG_X1 0x01 +#define EPEN_REG_X2 0x02 +#define EPEN_REG_Y1 0x03 +#define EPEN_REG_Y2 0x04 +#define EPEN_REG_PRESSURE1 0x05 +#define EPEN_REG_PRESSURE2 0x06 +#define EPEN_REG_FWVER1 0x07 +#define EPEN_REG_FWVER2 0x08 +#define EPEN_REG_MPUVER 0x09 +#define EPEN_REG_BLVER 0x0A +#define EPEN_REG_TILT_X 0x0B +#define EPEN_REG_TILT_Y 0x0C +#define EPEN_REG_HEIGHT 0x0D +#define EPEN_REG_FMTREV 0x0E +#define EPEN_REG_PROJ_ID 0x0F + +/* wacom ic values */ +#define MPU_W9018 0x42 +#define MPU_W9019 0x43 +#define MPU_W9020 0x44 +#define MPU_W9021 0x45 +#define MPU_WEZ01 0x46 +#define MPU_WEZ02 0x47 + +/* wacom ic model */ +#define WEZ02_B0 0x01 +#define WEZ02_DM3 0x02 +#define WEZ02_E3 0x03 diff --git a/drivers/input/sec_input/wacom/wacom_sec.c b/drivers/input/sec_input/wacom/wacom_sec.c new file mode 100755 index 000000000000..b5367bae7bfd --- /dev/null +++ b/drivers/input/sec_input/wacom/wacom_sec.c @@ -0,0 +1,4148 @@ +/* + * wacom_func.c - Wacom G5 Digitizer Controller (I2C bus) + * + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "wacom_dev.h" + +static inline int power(int num) +{ + int i, ret; + + for (i = 0, ret = 1; i < num; i++) + ret = ret * 10; + + return ret; +} + +#if WACOM_SEC_FACTORY +static ssize_t epen_fac_select_firmware_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + u8 fw_update_way = FW_NONE; + int ret = 0; + + input_info(true, wacom->dev, "%s\n", __func__); + mutex_lock(&wacom->lock); + switch (*buf) { + case 'k': + case 'K': + fw_update_way = FW_BUILT_IN; + break; + case 't': + case 'T': + fw_update_way = FW_FACTORY_GARAGE; + break; + case 'u': + case 'U': + fw_update_way = FW_FACTORY_UNIT; + break; + default: + input_err(true, wacom->dev, "wrong parameter\n"); + goto out; + } + ret = wacom_load_fw(wacom, fw_update_way); + if (ret < 0) { + input_info(true, wacom->dev, "failed to load fw data\n"); + goto out; + } + + wacom_unload_fw(wacom); +out: + mutex_unlock(&wacom->lock); + return count; +} +#endif + +static ssize_t epen_firm_update_status_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + int status = wacom->update_status; + int ret = 0; + + input_info(true, wacom->dev, "%s:(%d)\n", __func__, status); + + if (status == FW_UPDATE_PASS) + ret = snprintf(buf, PAGE_SIZE, "PASS\n"); + else if (status == FW_UPDATE_RUNNING) + ret = snprintf(buf, PAGE_SIZE, "DOWNLOADING\n"); + else if (status == FW_UPDATE_FAIL) + ret = snprintf(buf, PAGE_SIZE, "FAIL\n"); + else + ret = 0; + + return ret; +} + +static ssize_t epen_firm_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + + input_info(true, wacom->dev, "%s: %02X%02X%02X%02X|%02X%02X%02X%02X\n", __func__, + wacom->plat_data->img_version_of_ic[0], wacom->plat_data->img_version_of_ic[1], + wacom->plat_data->img_version_of_ic[2], wacom->plat_data->img_version_of_ic[3], + wacom->plat_data->img_version_of_bin[0], wacom->plat_data->img_version_of_bin[1], + wacom->plat_data->img_version_of_bin[2], wacom->plat_data->img_version_of_bin[3]); + + return snprintf(buf, PAGE_SIZE, "%02X%02X%02X%02X\t%02X%02X%02X%02X\n", + wacom->plat_data->img_version_of_ic[0], wacom->plat_data->img_version_of_ic[1], + wacom->plat_data->img_version_of_ic[2], wacom->plat_data->img_version_of_ic[3], + wacom->plat_data->img_version_of_bin[0], wacom->plat_data->img_version_of_bin[1], + wacom->plat_data->img_version_of_bin[2], wacom->plat_data->img_version_of_bin[3]); +} + +static ssize_t epen_firm_update_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + u8 fw_update_way = FW_NONE; + + input_info(true, wacom->dev, "%s\n", __func__); + + mutex_lock(&wacom->lock); + + switch (*buf) { + case 'i': + case 'I': +#if WACOM_PRODUCT_SHIP + fw_update_way = FW_IN_SDCARD_SIGNED; +#else + fw_update_way = FW_IN_SDCARD; +#endif + break; + case 'k': + case 'K': + fw_update_way = FW_BUILT_IN; + break; +#if WACOM_SEC_FACTORY + case 't': + case 'T': + fw_update_way = FW_FACTORY_GARAGE; + break; + case 'u': + case 'U': + fw_update_way = FW_FACTORY_UNIT; + break; +#endif + default: + input_err(true, wacom->dev, "wrong parameter\n"); + mutex_unlock(&wacom->lock); + return count; + } + + wacom_fw_update_on_hidden_menu(wacom, fw_update_way); + mutex_unlock(&wacom->lock); + + return count; +} + +static ssize_t ver_of_ic_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + + input_info(true, wacom->dev, "%s: %02X%02X%02X%02X\n", __func__, + wacom->plat_data->img_version_of_ic[0], wacom->plat_data->img_version_of_ic[1], + wacom->plat_data->img_version_of_ic[2], wacom->plat_data->img_version_of_ic[3]); + + return snprintf(buf, PAGE_SIZE, "%02X%02X%02X%02X", + wacom->plat_data->img_version_of_ic[0], wacom->plat_data->img_version_of_ic[1], + wacom->plat_data->img_version_of_ic[2], wacom->plat_data->img_version_of_ic[3]); +} + +static ssize_t ver_of_bin_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + + input_info(true, wacom->dev, "%s: %02X%02X%02X%02X\n", __func__, + wacom->plat_data->img_version_of_bin[0], wacom->plat_data->img_version_of_bin[1], + wacom->plat_data->img_version_of_bin[2], wacom->plat_data->img_version_of_bin[3]); + + return snprintf(buf, PAGE_SIZE, "%02X%02X%02X%02X", + wacom->plat_data->img_version_of_bin[0], wacom->plat_data->img_version_of_bin[1], + wacom->plat_data->img_version_of_bin[2], wacom->plat_data->img_version_of_bin[3]); +} + + +static ssize_t epen_reset_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + int val; + + if (kstrtoint(buf, 0, &val)) { + input_err(true, wacom->dev, "%s: failed to get param\n", + __func__); + return count; + } + + val = !(!val); + if (!val) + goto out; + + wacom_enable_irq(wacom, false); + + wacom->function_result &= ~EPEN_EVENT_SURVEY; + wacom->survey_mode = EPEN_SURVEY_MODE_NONE; + + /* Reset IC */ + wacom_reset_hw(wacom); + /* I2C Test */ + wacom_query(wacom); + + wacom_enable_irq(wacom, true); + + input_info(true, wacom->dev, + "%s: result %d\n", __func__, wacom->query_status); + return count; + +out: + input_err(true, wacom->dev, "%s: invalid value %d\n", __func__, val); + + return count; +} + +static ssize_t epen_reset_result_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + int ret = 0; + + if (wacom->query_status) { + input_info(true, wacom->dev, "%s: PASS\n", __func__); + ret = snprintf(buf, PAGE_SIZE, "PASS\n"); + } else { + input_err(true, wacom->dev, "%s: FAIL\n", __func__); + ret = snprintf(buf, PAGE_SIZE, "FAIL\n"); + } + + return ret; +} + +static ssize_t epen_checksum_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + int val; + + if (sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF)) { + input_err(true, wacom->dev, "%s: power off state, skip!\n", + __func__); + return count; + } + + if (!wacom->probe_done) { + input_err(true, wacom->dev, "%s: probe_done yet, skip!\n", __func__); + return count; + } + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s: reset is on going, skip!\n", + __func__); + return count; + } + + if (kstrtoint(buf, 0, &val)) { + input_err(true, wacom->dev, "%s: failed to get param\n", + __func__); + return count; + } + + val = !(!val); + if (!val) + goto out; + + wacom_enable_irq(wacom, false); + + wacom_checksum(wacom); + + wacom_enable_irq(wacom, true); + + input_info(true, wacom->dev, + "%s: result %d\n", __func__, wacom->checksum_result); + + return count; + +out: + input_err(true, wacom->dev, "%s: invalid value %d\n", __func__, val); + + return count; +} + +static ssize_t epen_checksum_result_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + int ret; + + if (wacom->checksum_result) { + input_info(true, wacom->dev, "checksum, PASS\n"); + ret = snprintf(buf, PAGE_SIZE, "PASS\n"); + } else { + input_err(true, wacom->dev, "checksum, FAIL\n"); + ret = snprintf(buf, PAGE_SIZE, "FAIL\n"); + } + + return ret; +} + +int wacom_open_test(struct wacom_data *wacom, int test_mode) +{ + u8 cmd = 0; + u8 buf[COM_COORD_NUM + 1] = { 0, }; + int ret = 0, retry = 0, retval = 0; + int retry_int = 30; + + input_info(true, wacom->dev, "%s : start (%d)\n", __func__, test_mode); + + if (test_mode == WACOM_GARAGE_TEST) { + wacom->garage_connection_check = false; + wacom->garage_fail_channel = 0; + wacom->garage_min_adc_val = 0; + wacom->garage_error_cal = 0; + wacom->garage_min_cal_val = 0; + } else if (test_mode == WACOM_DIGITIZER_TEST) { + wacom->connection_check = false; + wacom->fail_channel = 0; + wacom->min_adc_val = 0; + wacom->error_cal = 0; + wacom->min_cal_val = 0; + } + + if (!wacom->support_garage_open_test && test_mode == WACOM_GARAGE_TEST) { + input_err(true, wacom->dev, "%s: not support garage open test", __func__); + retval = EPEN_OPEN_TEST_NOTSUPPORT; + goto err1; + } + + /* change normal mode for open test */ + mutex_lock(&wacom->mode_lock); + wacom_set_survey_mode(wacom, EPEN_SURVEY_MODE_NONE); + // prevent I2C fail + sec_delay(300); + mutex_unlock(&wacom->mode_lock); + + ret = wacom_start_stop_cmd(wacom, WACOM_STOP_CMD); + if (ret != 1) { + input_err(true, wacom->dev, "%s: failed to set stop cmd, %d\n", + __func__, ret); + retval = EPEN_OPEN_TEST_EIO; + goto err1; + } + + sec_delay(50); + + wacom_enable_irq(wacom, false); + + retry = 3; + do { + if (gpio_get_value(wacom->plat_data->irq_gpio)) + break; + + input_info(true, wacom->dev, "%s : irq check, retry %d\n", __func__, retry); + ret = wacom_recv(wacom, buf, COM_COORD_NUM); + if (ret != COM_COORD_NUM) + input_err(true, wacom->dev, "%s: failed to recv status data\n", __func__); + sec_delay(50); + } while (retry--); + + if (test_mode == WACOM_GARAGE_TEST) + cmd = COM_GARAGE_TEST_MODE; + else if (test_mode == WACOM_DIGITIZER_TEST) + cmd = COM_DIGITIZER_TEST_MODE; + + if (wacom->support_garage_open_test) { + input_info(true, wacom->dev, "%s : send data(%02x)\n", __func__, cmd); + + ret = wacom_send(wacom, &cmd, 1); + if (ret != 1) { + input_err(true, wacom->dev, "%s : failed to send data(%02x)\n", __func__, cmd); + retval = EPEN_OPEN_TEST_EIO; + goto err2; + } + sec_delay(50); + } + + cmd = COM_OPEN_CHECK_START; + ret = wacom_send(wacom, &cmd, 1); + if (ret != 1) { + input_err(true, wacom->dev, "%s : failed to send data(%02x)\n", __func__, cmd); + retval = EPEN_OPEN_TEST_EIO; + goto err2; + } + + sec_delay(100); + + retry = 5; + cmd = COM_OPEN_CHECK_STATUS; + do { + input_info(true, wacom->dev, "%s : read status, retry %d\n", __func__, retry); + ret = wacom_send(wacom, &cmd, 1); + if (ret != 1) { + input_err(true, wacom->dev, + "%s : failed to send data(%02x)\n", __func__, cmd); + sec_delay(6); + continue; + } + + retry_int = 30; + while (retry_int--) { + if (!gpio_get_value(wacom->plat_data->irq_gpio)) + break; + sec_delay(20); + } + input_info(true, wacom->dev, "%s : retry_int[%d]\n", __func__, retry_int); + + ret = wacom_recv(wacom, buf, COM_COORD_NUM); + if (ret != COM_COORD_NUM) { + input_err(true, wacom->dev, "%s : failed to recv\n", __func__); + sec_delay(6); + continue; + } + + if (buf[0] != 0x0E && buf[1] != 0x01) { + input_err(true, wacom->dev, + "%s : invalid packet(%02x %02x %02x)\n", + __func__, buf[0], buf[1], buf[3]); + continue; + } + /* + * status value + * 0 : data is not ready + * 1 : PASS + * 2 : Fail (coil function error) + * 3 : Fail (All coil function error) + */ + if (buf[2] == 1) { + input_info(true, wacom->dev, "%s : Open check Pass\n", __func__); + break; + } + } while (retry--); + + if (test_mode == WACOM_GARAGE_TEST) { + if (ret == COM_COORD_NUM) { + if (wacom->module_ver == 0x2) + wacom->garage_connection_check = ((buf[2] == 1) && !buf[6]); + else + wacom->garage_connection_check = (buf[2] == 1); + } else { + wacom->garage_connection_check = false; + retval = EPEN_OPEN_TEST_EIO; + goto err2; + } + + wacom->garage_fail_channel = buf[3]; + wacom->garage_min_adc_val = buf[4] << 8 | buf[5]; + wacom->garage_error_cal = buf[6]; + wacom->garage_min_cal_val = buf[7] << 8 | buf[8]; + + input_info(true, wacom->dev, + "%s: garage %s buf[3]:%d, buf[4]:%d, buf[5]:%d, buf[6]:%d, buf[7]:%d, buf[8]:%d\n", + __func__, wacom->garage_connection_check ? "Pass" : "Fail", + buf[3], buf[4], buf[5], buf[6], buf[7], buf[8]); + + if (!wacom->garage_connection_check) + retval = EPEN_OPEN_TEST_FAIL; + + } else if (test_mode == WACOM_DIGITIZER_TEST) { + if (ret == COM_COORD_NUM) { + if (wacom->module_ver == 0x2) + wacom->connection_check = ((buf[2] == 1) && !buf[6]); + else + wacom->connection_check = (buf[2] == 1); + } else { + wacom->connection_check = false; + retval = EPEN_OPEN_TEST_EIO; + goto err2; + } + + wacom->fail_channel = buf[3]; + wacom->min_adc_val = buf[4] << 8 | buf[5]; + wacom->error_cal = buf[6]; + wacom->min_cal_val = buf[7] << 8 | buf[8]; + + input_info(true, wacom->dev, + "%s: digitizer %s buf[3]:%d, buf[4]:%d, buf[5]:%d, buf[6]:%d, buf[7]:%d, buf[8]:%d\n", + __func__, wacom->connection_check ? "Pass" : "Fail", + buf[3], buf[4], buf[5], buf[6], buf[7], buf[8]); + + if (!wacom->connection_check) + retval = EPEN_OPEN_TEST_FAIL; + } + +err2: + wacom_enable_irq(wacom, true); + +err1: + ret = wacom_start_stop_cmd(wacom, WACOM_STOP_AND_START_CMD); + if (ret != 1) { + input_err(true, wacom->dev, "%s: failed to set stop-start cmd, %d\n", + __func__, ret); + retval = EPEN_OPEN_TEST_EMODECHG; + goto out; + } + +out: + return retval; +} + +static int get_connection_test(struct wacom_data *wacom, int test_mode) +{ + int retry = 2; + int ret; + + if (wacom->reset_is_on_going) { + input_info(true, wacom->dev, "%s : reset is on going!\n", __func__); + return -EIO; + } + + mutex_lock(&wacom->lock); + + input_info(true, wacom->dev, "%s : start(%d)\n", __func__, test_mode); + + wacom->is_open_test = true; + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + if (wacom->is_tsp_block) { + input_info(true, wacom->dev, "%s: full scan OUT\n", __func__); + wacom->tsp_scan_mode = sec_input_notify(&wacom->nb, NOTIFIER_TSP_BLOCKING_RELEASE, NULL); + wacom->is_tsp_block = false; + } +#endif + + if (sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF)) { + input_info(true, wacom->dev, "%s : force power on\n", __func__); + wacom_power(wacom, true); + atomic_set(&wacom->plat_data->power_state, SEC_INPUT_STATE_POWER_ON); + sec_delay(100); + wacom_enable_irq(wacom, true); + } + + while (retry--) { + ret = wacom_open_test(wacom, test_mode); + if (ret == EPEN_OPEN_TEST_PASS || ret == EPEN_OPEN_TEST_NOTSUPPORT) + break; + + input_err(true, wacom->dev, "failed(%d). retry %d\n", ret, retry); + + wacom_enable_irq(wacom, false); + atomic_set(&wacom->plat_data->power_state, SEC_INPUT_STATE_POWER_OFF); + wacom_power(wacom, false); + wacom->function_result &= ~EPEN_EVENT_SURVEY; + wacom->survey_mode = EPEN_SURVEY_MODE_NONE; + /* recommended delay in spec */ + sec_delay(100); + wacom_power(wacom, true); + atomic_set(&wacom->plat_data->power_state, SEC_INPUT_STATE_POWER_ON); + sec_delay(100); + wacom_enable_irq(wacom, true); + if (ret == EPEN_OPEN_TEST_EIO || ret == EPEN_OPEN_TEST_FAIL) + continue; + else if (ret == EPEN_OPEN_TEST_EMODECHG) + break; + } + + if (!wacom->samplerate_state) { + input_info(true, wacom->dev, "%s: samplerate state is %d, need to recovery\n", + __func__, wacom->samplerate_state); + + ret = wacom_start_stop_cmd(wacom, WACOM_START_CMD); + if (ret != 1) { + input_err(true, wacom->dev, "%s: failed to set start cmd, %d\n", + __func__, ret); + } + } + + wacom->is_open_test = false; + + mutex_unlock(&wacom->lock); + + input_info(true, wacom->dev, "connection_check : %s\n", + wacom->connection_check ? "OK" : "NG"); + +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + if (wacom->is_tsp_block) { + wacom->tsp_scan_mode = sec_input_notify(&wacom->nb, NOTIFIER_TSP_BLOCKING_RELEASE, NULL); + wacom->is_tsp_block = false; + input_err(true, wacom->dev, "%s : release tsp scan block\n", __func__); + } +#endif + return ret; +} + +static ssize_t epen_connection_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + int ret; + + ret = wacom_check_ub(wacom); + if (ret < 0) { + input_info(true, wacom->dev, "%s: digitizer is not attached\n", __func__); + wacom->connection_check = false; + goto out; + } + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s: reset is on going, skip!\n", + __func__); + wacom->connection_check = false; + goto out; + } + + get_connection_test(wacom, WACOM_DIGITIZER_TEST); + +out: + if (wacom->module_ver == 0x2) { + return snprintf(buf, PAGE_SIZE, "%s %d %d %d %s %d\n", + wacom->connection_check ? "OK" : "NG", + wacom->module_ver, wacom->fail_channel, + wacom->min_adc_val, wacom->error_cal ? "NG" : "OK", + wacom->min_cal_val); + } else { + return snprintf(buf, PAGE_SIZE, "%s %d %d %d\n", + wacom->connection_check ? "OK" : "NG", + wacom->module_ver, wacom->fail_channel, + wacom->min_adc_val); + } +} + +static ssize_t epen_saving_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + static u64 call_count; + int val; + + call_count++; + + if (kstrtoint(buf, 0, &val)) { + input_err(true, wacom->dev, "%s: failed to get param\n", + __func__); + return count; + } + + wacom->battery_saving_mode = !(!val); + + input_info(true, wacom->dev, "%s: ps %s & pen %s [%llu]\n", + __func__, wacom->battery_saving_mode ? "on" : "off", + (wacom->function_result & EPEN_EVENT_PEN_OUT) ? "out" : "in", + call_count); + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s : reset is on going, data saved and skip!\n", __func__); + return count; + } + + if (sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF) && wacom->battery_saving_mode) { + input_err(true, wacom->dev, + "%s: already power off, save & return\n", __func__); + return count; + } + + if (!(wacom->function_result & EPEN_EVENT_PEN_OUT)) { + wacom_select_survey_mode(wacom, atomic_read(&wacom->screen_on)); + + if (wacom->battery_saving_mode) + forced_release_fullscan(wacom); + } + + return count; +} + +static ssize_t epen_insert_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + int pen_state = (wacom->function_result & EPEN_EVENT_PEN_OUT); + + input_info(true, wacom->dev, "%s : pen is %s\n", __func__, + pen_state ? "OUT" : "IN"); + + return snprintf(buf, PAGE_SIZE, "%d\n", pen_state ? 0 : 1); +} + +static ssize_t screen_off_memo_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + int val = 0; + + if (kstrtoint(buf, 0, &val)) { + input_err(true, wacom->dev, "%s: failed to get param\n", + __func__); + return count; + } + +#if WACOM_SEC_FACTORY + input_info(true, wacom->dev, + "%s : Not support screen off memo mode(%d) in Factory Bin\n", + __func__, val); + + return count; +#endif + + val = !(!val); + + if (val) + wacom->function_set |= EPEN_SETMODE_AOP_OPTION_SCREENOFFMEMO; + else + wacom->function_set &= (~EPEN_SETMODE_AOP_OPTION_SCREENOFFMEMO); + + input_info(true, wacom->dev, + "%s: ps %s aop(%d) set(0x%x) ret(0x%x)\n", __func__, + wacom->battery_saving_mode ? "on" : "off", + (wacom->function_set & EPEN_SETMODE_AOP) ? 1 : 0, + wacom->function_set, wacom->function_result); + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s: reset is on going, data saved and skip!\n", + __func__); + return count; + } + + if (!wacom->probe_done) { + input_err(true, wacom->dev, "%s: probe is not done\n", __func__); + return count; + } + + if (!atomic_read(&wacom->screen_on)) + wacom_select_survey_mode(wacom, false); + + return count; +} + +static ssize_t aod_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + int val = 0; + + if (kstrtoint(buf, 0, &val)) { + input_err(true, wacom->dev, "%s: failed to get param\n", + __func__); + return count; + } + +#if WACOM_SEC_FACTORY + input_info(true, wacom->dev, + "%s : Not support aod mode(%d) in Factory Bin\n", + __func__, val); + + return count; +#endif + + val = !(!val); + + if (val) + wacom->function_set |= EPEN_SETMODE_AOP_OPTION_AOD_LCD_OFF; + else + wacom->function_set &= ~EPEN_SETMODE_AOP_OPTION_AOD_LCD_ON; + + input_info(true, wacom->dev, + "%s: ps %s aop(%d) set(0x%x) ret(0x%x)\n", __func__, + wacom->battery_saving_mode ? "on" : "off", + (wacom->function_set & EPEN_SETMODE_AOP) ? 1 : 0, + wacom->function_set, wacom->function_result); + + if (!atomic_read(&wacom->screen_on)) + wacom_select_survey_mode(wacom, false); + + return count; +} + +static ssize_t aod_lcd_onoff_status_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + int val = 0; + + if (kstrtoint(buf, 0, &val)) { + input_err(true, wacom->dev, "%s: failed to get param\n", + __func__); + return count; + } + +#if WACOM_SEC_FACTORY + input_info(true, wacom->dev, + "%s : Not support aod mode(%d) in Factory Bin\n", + __func__, val); + + return count; +#endif + + val = !(!val); + + if (val) + wacom->function_set |= EPEN_SETMODE_AOP_OPTION_AOD_LCD_STATUS; + else + wacom->function_set &= ~EPEN_SETMODE_AOP_OPTION_AOD_LCD_STATUS; + + if (!(wacom->function_set & EPEN_SETMODE_AOP_OPTION_AOD) || atomic_read(&wacom->screen_on)) + goto out; + + if (sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF) || (wacom->reset_is_on_going == true)) + goto out; + + if (wacom->survey_mode == EPEN_SURVEY_MODE_GARAGE_AOP) { + if ((wacom->function_set & EPEN_SETMODE_AOP_OPTION_AOD_LCD_ON) + == EPEN_SETMODE_AOP_OPTION_AOD_LCD_OFF) { + forced_release_fullscan(wacom); + } + + mutex_lock(&wacom->mode_lock); + wacom_set_survey_mode(wacom, wacom->survey_mode); + mutex_unlock(&wacom->mode_lock); + } + +out: + input_info(true, wacom->dev, "%s: screen %s, survey mode:0x%x, set:0x%x\n", + __func__, atomic_read(&wacom->screen_on) ? "on" : "off", + wacom->survey_mode, wacom->function_set); + + return count; +} + +static ssize_t aot_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + int val = 0; + + if (kstrtoint(buf, 0, &val)) { + input_err(true, wacom->dev, "%s: failed to get param\n", + __func__); + return count; + } + +#if WACOM_SEC_FACTORY + input_info(true, wacom->dev, + "%s : Not support aot mode(%d) in Factory Bin\n", + __func__, val); + + return count; +#endif + + val = !(!val); + + if (val) + wacom->function_set |= EPEN_SETMODE_AOP_OPTION_AOT; + else + wacom->function_set &= (~EPEN_SETMODE_AOP_OPTION_AOT); + + input_info(true, wacom->dev, + "%s: ps %s aop(%d) set(0x%x) ret(0x%x)\n", __func__, + wacom->battery_saving_mode ? "on" : "off", + (wacom->function_set & EPEN_SETMODE_AOP) ? 1 : 0, + wacom->function_set, wacom->function_result); + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s: reset is on going, data saved and skip!\n", + __func__); + return count; + } + + if (!atomic_read(&wacom->screen_on)) + wacom_select_survey_mode(wacom, false); + + return count; +} + +static ssize_t epen_wcharging_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + + input_info(true, wacom->dev, "%s: %s\n", __func__, + !wacom->wcharging_mode ? "NORMAL" : "LOWSENSE"); + + return snprintf(buf, PAGE_SIZE, "%s\n", + !wacom->wcharging_mode ? "NORMAL" : "LOWSENSE"); +} + +static ssize_t epen_wcharging_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + int retval = 0; + + if (kstrtoint(buf, 0, &retval)) { + input_err(true, wacom->dev, "%s: failed to get param\n", + __func__); + return count; + } + + wacom->wcharging_mode = retval; + + input_info(true, wacom->dev, "%s: %d\n", __func__, + wacom->wcharging_mode); + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s : reset is on going, data saved and skip!\n", __func__); + return count; + } + + if (sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF)) { + input_err(true, wacom->dev, + "%s: power off, save & return\n", __func__); + return count; + } + + if (!wacom->probe_done) { + input_err(true, wacom->dev, "%s: probe is not done\n", __func__); + return count; + } + + retval = wacom_set_sense_mode(wacom); + if (retval < 0) { + input_err(true, wacom->dev, + "%s: do not set sensitivity mode, %d\n", __func__, + retval); + } + + return count; + +} + +char ble_charge_command[] = { + COM_BLE_C_DISABLE, /* Disable charge (charging mode enable) */ + COM_BLE_C_ENABLE, /* Enable charge (charging mode disable) */ + COM_BLE_C_RESET, /* Reset (make reset pattern + 1m charge) */ + COM_BLE_C_START, /* Start (make start patter + 1m charge) */ + COM_BLE_C_KEEP_ON, /* Keep on charge (you need send this cmd within a minute after Start cmd) */ + COM_BLE_C_KEEP_OFF, /* Keep off charge */ + COM_BLE_C_MODE_RETURN, /* Request charging mode */ + COM_BLE_C_FORCE_RESET, /* DSP force reset */ + COM_BLE_C_FULL, /* Full charge (depend on fw) */ +}; + +int wacom_ble_charge_mode(struct wacom_data *wacom, int mode) +{ + int ret = 0; + char data = -1; + ktime_t current_time; + + if (wacom->is_open_test) { + input_err(true, wacom->dev, "%s: other cmd is working\n", + __func__); + return -EPERM; + } + + if (wacom->update_status == FW_UPDATE_RUNNING) { + input_err(true, wacom->dev, "%s: fw update running\n", + __func__); + return -EPERM; + } + + if (wacom->ble_block_flag && (mode != EPEN_BLE_C_DISABLE)) { + input_err(true, wacom->dev, "%s: operation not permitted\n", + __func__); + return -EPERM; + } + + if (mode >= EPEN_BLE_C_MAX || mode < EPEN_BLE_C_DISABLE) { + input_err(true, wacom->dev, "%s: wrong mode, %d\n", __func__, mode); + return -EINVAL; + } + + mutex_lock(&wacom->ble_lock); + + /* need to check mode */ + if (ble_charge_command[mode] == COM_BLE_C_RESET + || ble_charge_command[mode] == COM_BLE_C_START) { + current_time = ktime_get(); + wacom->chg_time_stamp = ktime_to_ms(current_time); + input_err(true, wacom->dev, "%s: chg_time_stamp(%ld)\n", + __func__, wacom->chg_time_stamp); + } else { + wacom->chg_time_stamp = 0; + } + + if (mode == EPEN_BLE_C_DSPX) { + /* add abnormal ble reset case (while pen in status, do not ble charge) */ + mutex_lock(&wacom->mode_lock); + ret = wacom_set_survey_mode(wacom, EPEN_SURVEY_MODE_NONE); + mutex_unlock(&wacom->mode_lock); + if (ret < 0) + goto out_save_result; + + sec_delay(250); + + data = ble_charge_command[mode]; + ret = wacom_send(wacom, &data, 1); + if (ret < 0) { + input_err(true, wacom->dev, "%s: failed to send data(%02x %d)\n", + __func__, data, ret); + wacom_select_survey_mode(wacom, atomic_read(&wacom->screen_on)); + goto out_save_result; + } + + sec_delay(130); + + wacom_select_survey_mode(wacom, atomic_read(&wacom->screen_on)); + } else { + data = ble_charge_command[mode]; + ret = wacom_send(wacom, &data, 1); + if (ret < 0) { + input_err(true, wacom->dev, "%s: failed to send data(%02x %d)\n", + __func__, data, ret); + goto out_save_result; + } + + switch (data) { + case COM_BLE_C_RESET: + case COM_BLE_C_START: + case COM_BLE_C_KEEP_ON: + case COM_BLE_C_FULL: + wacom->ble_mode_change = true; + wacom->ble_charging_state = true; +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&wacom->nb, NOTIFIER_WACOM_PEN_CHARGING_STARTED, NULL); +#endif + break; + case COM_BLE_C_DISABLE: + case COM_BLE_C_KEEP_OFF: + wacom->ble_mode_change = false; + wacom->ble_charging_state = false; +#if IS_ENABLED(CONFIG_INPUT_SEC_NOTIFIER) + sec_input_notify(&wacom->nb, NOTIFIER_WACOM_PEN_CHARGING_FINISHED, NULL); +#endif + break; + } + } + + input_info(true, wacom->dev, "%s: now %02X, prev %02X\n", __func__, data, + wacom->ble_mode ? wacom->ble_mode : 0); + + wacom->ble_mode = ble_charge_command[mode]; + +out_save_result: + if (wacom->ble_hist) { + unsigned long long ksec; + unsigned long nanosec; + char buffer[40]; + int len; + + ksec = local_clock(); + nanosec = do_div(ksec, 1000000000); + + memset(buffer, 0x00, sizeof(buffer)); + len = snprintf(buffer, sizeof(buffer), "%5lu.%06lu:%02X,%02X,%02d\n", + (unsigned long)ksec, nanosec / 1000, mode, data, ret); + if (len < 0) + goto out_ble_charging; + + if (wacom->ble_hist_index + len >= WACOM_BLE_HISTORY_SIZE) { + memcpy(&wacom->ble_hist[0], buffer, len); + wacom->ble_hist_index = 0; + } else { + memcpy(&wacom->ble_hist[wacom->ble_hist_index], buffer, len); + wacom->ble_hist_index += len; + } + + if (mode == EPEN_BLE_C_DISABLE || mode == EPEN_BLE_C_ENABLE) { + if (!wacom->ble_hist1) + goto out_ble_charging; + + memset(wacom->ble_hist1, 0x00, WACOM_BLE_HISTORY1_SIZE); + memcpy(wacom->ble_hist1, buffer, WACOM_BLE_HISTORY1_SIZE); + } + } + +out_ble_charging: + mutex_unlock(&wacom->ble_lock); + return ret; +} + +int get_wacom_scan_info(bool mode) +{ + struct wacom_data *wacom = g_wacom; + ktime_t current_time; + long diff_time; + + if (!wacom) + return -ENODEV; + + current_time = ktime_get(); + diff_time = ktime_to_ms(current_time) - wacom->chg_time_stamp; + input_info(true, wacom->dev, "%s: START mode[%d] & ble[%x] & diff time[%ld]\n", + __func__, mode, wacom->ble_mode, diff_time); + + if (!wacom->probe_done || sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF) || wacom->is_open_test) { + input_err(true, wacom->dev, "%s: wacom is not ready\n", __func__); + return EPEN_CHARGE_ON; + } + + /* clear ble_mode_change or check ble_mode_change value */ + if (mode) { + wacom->ble_mode_change = false; + return EPEN_NONE; + } else if (!mode && wacom->ble_mode_change) { + input_info(true, wacom->dev, "%s : charge happened\n", __func__); + return EPEN_CHARGE_HAPPENED; + } + + if (!wacom->pen_pdct) { + input_err(true, wacom->dev, "%s: pen out & charge off\n", __func__); + return EPEN_CHARGE_OFF; + } + + if (wacom->ble_mode == COM_BLE_C_KEEP_ON || wacom->ble_mode == COM_BLE_C_FULL) { + input_info(true, wacom->dev, "%s: charging [%x]\n", __func__, wacom->ble_mode); + return EPEN_CHARGE_ON; + /* check charge time */ + } else if (wacom->ble_mode == COM_BLE_C_RESET || wacom->ble_mode == COM_BLE_C_START) { + + if (diff_time > EPEN_CHARGER_OFF_TIME) { + input_info(true, wacom->dev, "%s: charge off time BLE chg[%x], diff[%ld]\n", + __func__, wacom->ble_mode, diff_time); + return EPEN_CHARGE_OFF; + } else { + input_info(true, wacom->dev, "%s: charge on time BLE chg[%x], diff[%ld]\n", + __func__, wacom->ble_mode, diff_time); + return EPEN_CHARGE_ON; + } + return EPEN_NONE; + + } + + input_err(true, wacom->dev, "%s: charge off mode[%d] & ble[%x]\n", + __func__, mode, wacom->ble_mode); + return EPEN_CHARGE_OFF; +} +EXPORT_SYMBOL(get_wacom_scan_info); + +int set_wacom_ble_charge_mode(bool mode) +{ + struct wacom_data *wacom = g_wacom; + int ret = 0; + int ret_val = 0; /* 0:pass, etc:fail */ + + if (g_wacom == NULL) { + pr_info("%s: %s: g_wacom is NULL(%d)\n", SECLOG, __func__, mode); + return 0; + } + + if (!wacom->probe_done) { + input_err(true, wacom->dev, "%s: probe_done yet, skip!\n", __func__); + return 0; + } + + mutex_lock(&wacom->ble_charge_mode_lock); + input_info(true, wacom->dev, "%s start(%d)\n", __func__, mode); + + if (!mode) { + ret = wacom_ble_charge_mode(wacom, EPEN_BLE_C_KEEP_OFF); + if (ret > 0) { + wacom->ble_block_flag = true; + } else { + input_err(true, wacom->dev, "%s Fail to keep off\n", __func__); + ret_val = 1; + } + } else { +#ifdef CONFIG_SEC_FACTORY + ret = wacom_ble_charge_mode(wacom, EPEN_BLE_C_ENABLE); + if (ret > 0) { + wacom->ble_block_flag = false; + } else { + input_err(true, wacom->dev, "%s Fail to enable in fac\n", __func__); + ret_val = 1; + } +#else + wacom->ble_block_flag = false; +#endif + } + + input_info(true, wacom->dev, "%s done(%d)\n", __func__, mode); + mutex_unlock(&wacom->ble_charge_mode_lock); + + return ret_val; +} +EXPORT_SYMBOL(set_wacom_ble_charge_mode); + +static ssize_t epen_ble_charging_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + + if (wacom->ble_charging_state == false) + return snprintf(buf, PAGE_SIZE, "DISCHARGE\n"); + + return snprintf(buf, PAGE_SIZE, "CHARGE\n"); +} + +static ssize_t epen_ble_charging_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + int retval = 0; + + if (!wacom->probe_done) { + input_err(true, wacom->dev, "%s: probe_done yet, skip!\n", __func__); + return count; + } + + if (kstrtoint(buf, 0, &retval)) { + input_err(true, wacom->dev, "%s: failed to get param\n", + __func__); + return count; + } + + if (sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF)) { + input_err(true, wacom->dev, + "%s: power off return\n", __func__); + return count; + } + +#if !WACOM_SEC_FACTORY + if (retval == EPEN_BLE_C_DISABLE) { + input_info(true, wacom->dev, "%s: use keep off insted of disable\n", __func__); + retval = EPEN_BLE_C_KEEP_OFF; + } +#endif + + mutex_lock(&wacom->ble_charge_mode_lock); + wacom_ble_charge_mode(wacom, retval); + mutex_unlock(&wacom->ble_charge_mode_lock); + + return count; +} + +static ssize_t epen_ble_hist_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + int size, size1; + + if (!wacom->ble_hist) + return -ENODEV; + + if (!wacom->ble_hist1) + return -ENODEV; + + size1 = strnlen(wacom->ble_hist1, WACOM_BLE_HISTORY1_SIZE); + memcpy(buf, wacom->ble_hist1, size1); + + size = strnlen(wacom->ble_hist, WACOM_BLE_HISTORY_SIZE); + memcpy(buf + size1, wacom->ble_hist, size); + + return size + size1; +} + +static ssize_t epen_disable_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + int val = 0; + + if (kstrtoint(buf, 0, &val)) { + input_err(true, wacom->dev, "%s: failed to get param\n", + __func__); + return count; + } + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s: reset is on going, skip!\n", + __func__); + return count; + } + + if (val) + wacom_disable_mode(wacom, WACOM_DISABLE); + else + wacom_disable_mode(wacom, WACOM_ENABLE); + + input_info(true, wacom->dev, "%s: %d\n", __func__, val); + + return count; +} + +static ssize_t hw_param_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[516]; + char tbuff[128]; + + memset(buff, 0x00, sizeof(buff)); + + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "\"RESET\":\"%d\",", wacom->abnormal_reset_count); + strlcat(buff, tbuff, sizeof(buff)); + + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "\"I2C_FAIL\":\"%d\",", wacom->i2c_fail_count); + strlcat(buff, tbuff, sizeof(buff)); + + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "\"PEN_OUT\":\"%d\",", wacom->pen_out_count); + strlcat(buff, tbuff, sizeof(buff)); + + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "\"SDCONN\":\"%d\",", wacom->connection_check); + strlcat(buff, tbuff, sizeof(buff)); + + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "\"SECCNT\":\"%d\",", wacom->fail_channel); + strlcat(buff, tbuff, sizeof(buff)); + + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "\"SADCVAL\":\"%d\",", wacom->min_adc_val); + strlcat(buff, tbuff, sizeof(buff)); + + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "\"SECLVL\":\"%d\",", wacom->error_cal); + strlcat(buff, tbuff, sizeof(buff)); + + memset(tbuff, 0x00, sizeof(tbuff)); + /* remove last comma (csv) */ + snprintf(tbuff, sizeof(tbuff), "\"SCALLVL\":\"%d\",", wacom->min_cal_val); + strlcat(buff, tbuff, sizeof(buff)); + /*ESD PACKET COUNT*/ + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "\"ESDPCNT\":\"%d\",", wacom->esd_packet_count); + strlcat(buff, tbuff, sizeof(buff)); + /*ESD INTERRUPT COUNT*/ + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "\"ESDICNT\":\"%d\",", wacom->esd_irq_count); + strlcat(buff, tbuff, sizeof(buff)); + /*ESD MAX CONTINUOUS COUNT*/ + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "\"ESDMCCNT\":\"%d\"", wacom->esd_max_continuous_reset_count); + strlcat(buff, tbuff, sizeof(buff)); + + input_info(true, wacom->dev, "%s: %s\n", __func__, buff); + + return snprintf(buf, PAGE_SIZE, "%s", buff); +} + +static ssize_t hw_param_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + + wacom->abnormal_reset_count = 0; + wacom->i2c_fail_count = 0; + wacom->pen_out_count = 0; + wacom->esd_packet_count = 0; + wacom->esd_irq_count = 0; + + input_info(true, wacom->dev, "%s: clear\n", __func__); + + return count; +} + +static ssize_t epen_connection_check_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + + input_info(true, wacom->dev, "%s: SDCONN:%d,SECCNT:%d,SADCVAL:%d,SECLVL:%d,SCALLVL:%d\n", + __func__, wacom->connection_check, + wacom->fail_channel, wacom->min_adc_val, + wacom->error_cal, wacom->min_cal_val); + + return snprintf(buf, PAGE_SIZE, + "\"SDCONN\":\"%d\",\"SECCNT\":\"%d\",\"SADCVAL\":\"%d\",\"SECLVL\":\"%d\",\"SCALLVL\":\"%d\"", + wacom->connection_check, wacom->fail_channel, + wacom->min_adc_val, wacom->error_cal, + wacom->min_cal_val); +} + +static ssize_t get_epen_pos_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + int max_x, max_y; + + max_x = wacom->plat_data->max_x; + max_y = wacom->plat_data->max_y; + + input_info(true, wacom->dev, + "%s: id(%d), x(%d), y(%d), max_x(%d), max_y(%d)\n", __func__, + wacom->survey_pos.id, wacom->survey_pos.x, wacom->survey_pos.y, + max_x, max_y); + + return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d,%d\n", + wacom->survey_pos.id, wacom->survey_pos.x, wacom->survey_pos.y, + max_x, max_y); +} + +static ssize_t debug_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_cmd_data *sec = dev_get_drvdata(dev); + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[INPUT_DEBUG_INFO_SIZE]; + char tbuff[256]; + + if (sizeof(tbuff) > INPUT_DEBUG_INFO_SIZE) + input_info(true, wacom->dev, "%s: buff_size[%d], tbuff_size[%ld]\n", + __func__, INPUT_DEBUG_INFO_SIZE, sizeof(tbuff)); + + memset(buff, 0x00, sizeof(buff)); + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "FW: (I)%02X%02X%02X%02X / (B)%02X%02X%02X%02X\n", + wacom->plat_data->img_version_of_ic[0], wacom->plat_data->img_version_of_ic[1], + wacom->plat_data->img_version_of_ic[2], wacom->plat_data->img_version_of_ic[3], + wacom->plat_data->img_version_of_bin[0], wacom->plat_data->img_version_of_bin[1], + wacom->plat_data->img_version_of_bin[2], wacom->plat_data->img_version_of_bin[3]); + strlcat(buff, tbuff, sizeof(buff)); + + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "ps:%s pen:%s %s aop: lcd(%s), aod(%s), screenmemo(%s), aot(%s)\n", + wacom->battery_saving_mode ? "on" : "off", + (wacom->function_result & EPEN_EVENT_PEN_OUT) ? "out" : "in", + (wacom->function_result & EPEN_EVENT_SURVEY) ? "survey" : "normal", + (wacom->function_set & EPEN_SETMODE_AOP_OPTION_AOD_LCD_STATUS) ? "true" : "false", + (wacom->function_set & EPEN_SETMODE_AOP_OPTION_AOD) ? "true" : "false", + (wacom->function_set & EPEN_SETMODE_AOP_OPTION_SCREENOFFMEMO) ? "true" : "false", + (wacom->function_set & EPEN_SETMODE_AOP_OPTION_AOT) ? "true" : "false"); + strlcat(buff, tbuff, sizeof(buff)); + + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "screen_on %d, report_scan_seq %d, epen %s, stdby:%d\n", + atomic_read(&wacom->screen_on), + wacom->report_scan_seq, wacom->epen_blocked ? "blocked" : "unblocked", +#ifdef SUPPORT_STANDBY_MODE + wacom->standby_state +#else + 0 +#endif + ); + strlcat(buff, tbuff, sizeof(buff)); + + memset(tbuff, 0x00, sizeof(tbuff)); + snprintf(tbuff, sizeof(tbuff), "count(%u,%u,%u), mode(%d), block_cnt(%d), check(%d), test(%d,%d)\n", + wacom->i2c_fail_count, wacom->abnormal_reset_count, wacom->scan_info_fail_cnt, + wacom->check_survey_mode, wacom->tsp_block_cnt, wacom->check_elec, + wacom->connection_check, wacom->garage_connection_check); + strlcat(buff, tbuff, sizeof(buff)); + + input_info(true, wacom->dev, "%s: size: %ld\n", __func__, strlen(buff)); + + return snprintf(buf, SEC_CMD_BUF_SIZE, "%s", buff); +} + +/* firmware update */ +static DEVICE_ATTR_WO(epen_firm_update); +/* return firmware update status */ +static DEVICE_ATTR_RO(epen_firm_update_status); +/* return firmware version */ +static DEVICE_ATTR_RO(epen_firm_version); +static DEVICE_ATTR_RO(ver_of_ic); +static DEVICE_ATTR_RO(ver_of_bin); + +/* For SMD Test */ +static DEVICE_ATTR_WO(epen_reset); +static DEVICE_ATTR_RO(epen_reset_result); +/* For SMD Test. Check checksum */ +static DEVICE_ATTR_WO(epen_checksum); +static DEVICE_ATTR_RO(epen_checksum_result); +static DEVICE_ATTR_RO(epen_connection); +static DEVICE_ATTR_WO(epen_saving_mode); +static DEVICE_ATTR_RW(epen_wcharging_mode); +static DEVICE_ATTR_WO(epen_disable_mode); +static DEVICE_ATTR_WO(screen_off_memo_enable); +static DEVICE_ATTR_RO(get_epen_pos); +static DEVICE_ATTR_WO(aod_enable); +static DEVICE_ATTR_WO(aod_lcd_onoff_status); +static DEVICE_ATTR_WO(aot_enable); +static DEVICE_ATTR_RW(hw_param); +static DEVICE_ATTR_RO(epen_connection_check); + +static DEVICE_ATTR_RO(epen_insert); +static DEVICE_ATTR_RW(epen_ble_charging_mode); +static DEVICE_ATTR_RO(epen_ble_hist); + +#if WACOM_SEC_FACTORY +static DEVICE_ATTR_WO(epen_fac_select_firmware); +#endif + +static DEVICE_ATTR_RO(debug_info); + +static struct attribute *epen_attributes[] = { + &dev_attr_epen_firm_update.attr, + &dev_attr_epen_firm_update_status.attr, + &dev_attr_epen_firm_version.attr, + &dev_attr_ver_of_ic.attr, + &dev_attr_ver_of_bin.attr, + &dev_attr_epen_reset.attr, + &dev_attr_epen_reset_result.attr, + &dev_attr_epen_checksum.attr, + &dev_attr_epen_checksum_result.attr, + &dev_attr_epen_connection.attr, + &dev_attr_epen_saving_mode.attr, + &dev_attr_epen_wcharging_mode.attr, + &dev_attr_epen_disable_mode.attr, + &dev_attr_screen_off_memo_enable.attr, + &dev_attr_get_epen_pos.attr, + &dev_attr_aod_enable.attr, + &dev_attr_aod_lcd_onoff_status.attr, + &dev_attr_aot_enable.attr, + &dev_attr_hw_param.attr, + &dev_attr_epen_connection_check.attr, + + &dev_attr_epen_insert.attr, + &dev_attr_epen_ble_charging_mode.attr, + &dev_attr_epen_ble_hist.attr, +#if WACOM_SEC_FACTORY + &dev_attr_epen_fac_select_firmware.attr, +#endif + &dev_attr_debug_info.attr, + NULL, +}; + +static struct attribute_group epen_attr_group = { + .attrs = epen_attributes, +}; + +static void print_spec_data(struct wacom_data *wacom) +{ + struct wacom_elec_data *edata = wacom->edata; + u8 tmp_buf[WACOM_CMD_RESULT_WORD_LEN] = { 0 }; + u8 *buff; + int buff_size; + int i; + + buff_size = edata->max_x_ch > edata->max_y_ch ? edata->max_x_ch : edata->max_y_ch; + buff_size = WACOM_CMD_RESULT_WORD_LEN * (buff_size + 1); + + buff = kzalloc(buff_size, GFP_KERNEL); + if (!buff) + return; + + input_info(true, wacom->dev, "%s: shift_value %d, spec ver %d.%d", __func__, + edata->shift_value, edata->spec_ver[0], edata->spec_ver[1]); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "xx_ref: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", + edata->xx_ref[i] * power(edata->shift_value)); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "xy_ref: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", + edata->xy_ref[i] * power(edata->shift_value)); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "yx_ref: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", + edata->yx_ref[i] * power(edata->shift_value)); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "yy_ref: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", + edata->yy_ref[i] * power(edata->shift_value)); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "xy_edg_ref: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", + edata->xy_edg_ref[i] * power(edata->shift_value)); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "yx_edg_ref: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", + edata->yx_edg_ref[i] * power(edata->shift_value)); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "xx_spec: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", + edata->xx_spec[i] / POWER_OFFSET * power(edata->shift_value)); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "xy_spec: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", + edata->xy_spec[i] / POWER_OFFSET * power(edata->shift_value)); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "yx_spec: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", + edata->yx_spec[i] / POWER_OFFSET * power(edata->shift_value)); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "yy_spec: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", + edata->yy_spec[i] / POWER_OFFSET * power(edata->shift_value)); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "rxx_ref: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", + edata->rxx_ref[i] * power(edata->shift_value) / POWER_OFFSET); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "rxy_ref: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", + edata->rxy_ref[i] * power(edata->shift_value) / POWER_OFFSET); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "ryx_ref: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", + edata->ryx_ref[i] * power(edata->shift_value) / POWER_OFFSET); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "ryy_ref: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", + edata->ryy_ref[i] * power(edata->shift_value) / POWER_OFFSET); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "drxx_spec: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", + edata->drxx_spec[i] * power(edata->shift_value) / POWER_OFFSET); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "drxy_spec: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", + edata->drxy_spec[i] * power(edata->shift_value) / POWER_OFFSET); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "dryx_spec: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", + edata->dryx_spec[i] * power(edata->shift_value) / POWER_OFFSET); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "dryy_spec: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", + edata->dryy_spec[i] * power(edata->shift_value) / POWER_OFFSET); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "drxy_edg_spec: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", + edata->drxy_edg_spec[i] * power(edata->shift_value) / POWER_OFFSET); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "dryx_edg_spec: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", + edata->dryx_edg_spec[i] * power(edata->shift_value) / POWER_OFFSET); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "xx_self_spec: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", edata->xx_self_spec[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "yy_self_spec: "); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, WACOM_CMD_RESULT_WORD_LEN, "%lld ", edata->yy_self_spec[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, WACOM_CMD_RESULT_WORD_LEN); + } + + input_info(true, wacom->dev, "%s\n", buff); + memset(buff, 0x00, buff_size); + + kfree(buff); +} + +static void run_elec_test(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + struct wacom_elec_data *edata = NULL; + u8 *buff = NULL; + u8 tmp_buf[WACOM_CMD_RESULT_WORD_LEN] = { 0 }; + u8 data[COM_ELEC_NUM] = { 0, }; + u16 tmp, offset; + u8 cmd; + int i, tx, rx; + int retry = RETRY_COUNT; + int ret; + int max_ch; + int buff_size; + u32 count; + int remain_data_count; + u16 *elec_result = NULL; + + sec_cmd_set_default_result(sec); + + input_info(true, wacom->dev, "%s: parm[%d]\n", __func__, sec->cmd_param[0]); + + if (sec->cmd_param[0] < EPEN_ELEC_DATA_MAIN || sec->cmd_param[0] >= EPEN_ELEC_DATA_MAX) { + input_err(true, wacom->dev, + "%s: Abnormal parm[%d]\n", __func__, sec->cmd_param[0]); + goto error; + } + + if (sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF)) { + input_err(true, wacom->dev, "%s: power off state, skip!\n", + __func__); + goto error; + } + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s: reset is on going, skip!\n", + __func__); + goto error; + } + + edata = wacom->edata = wacom->edatas[sec->cmd_param[0]]; + + if (!edata) { + input_err(true, wacom->dev, "%s: test spec is NULL\n", __func__); + goto error; + } + + max_ch = edata->max_x_ch + edata->max_y_ch; + buff_size = max_ch * 2 * WACOM_CMD_RESULT_WORD_LEN + SEC_CMD_STR_LEN; + + buff = kzalloc(buff_size, GFP_KERNEL); + if (!buff) + goto error; + + elec_result = kzalloc((max_ch + 1) * sizeof(u16), GFP_KERNEL); + if (!elec_result) + goto error; + + wacom_enable_irq(wacom, false); + + /* change wacom mode to 0x2D */ + mutex_lock(&wacom->mode_lock); + wacom_set_survey_mode(wacom, EPEN_SURVEY_MODE_NONE); + mutex_unlock(&wacom->mode_lock); + + /* wait for status event for normal mode of wacom */ + sec_delay(250); + do { + ret = wacom_recv(wacom, data, COM_COORD_NUM); + if (ret != COM_COORD_NUM) { + input_err(true, wacom->dev, "%s: failed to recv status data\n", + __func__); + sec_delay(50); + continue; + } + + /* check packet ID */ + if ((data[0] & 0x0F) != NOTI_PACKET && (data[1] != CMD_PACKET)) { + input_info(true, wacom->dev, + "%s: invalid status data %d\n", + __func__, (RETRY_COUNT - retry + 1)); + } else { + break; + } + + sec_delay(50); + } while (retry--); + + retry = RETRY_COUNT; + cmd = COM_ELEC_XSCAN; + ret = wacom_send(wacom, &cmd, 1); + if (ret != 1) { + input_err(true, wacom->dev, + "%s: failed to send 0x%02X\n", __func__, cmd); + goto error_test; + } + sec_delay(30); + + cmd = COM_ELEC_TRSX0; + ret = wacom_send(wacom, &cmd, 1); + if (ret != 1) { + input_err(true, wacom->dev, + "%s: failed to send 0x%02X\n", __func__, cmd); + goto error_test; + } + sec_delay(30); + + cmd = COM_ELEC_SCAN_START; + ret = wacom_send(wacom, &cmd, 1); + if (ret != 1) { + input_err(true, wacom->dev, + "%s: failed to send 0x%02X\n", __func__, cmd); + goto error_test; + } + sec_delay(30); + + for (tx = 0; tx < max_ch; tx++) { + /* x scan mode receive data through x coils */ + remain_data_count = edata->max_x_ch; + + do { + cmd = COM_ELEC_REQUESTDATA; + ret = wacom_send(wacom, &cmd, 1); + if (ret != 1) { + input_err(true, wacom->dev, + "%s: failed to send 0x%02X\n", + __func__, cmd); + goto error_test; + } + sec_delay(30); + + ret = wacom_recv(wacom, data, COM_ELEC_NUM); + if (ret != COM_ELEC_NUM) { + input_err(true, wacom->dev, + "%s: failed to receive elec data\n", + __func__); + goto error_test; + } +#if 0 + input_info(true, wacom->dev, "%X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X\n", + data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], + data[10], data[11], data[12], data[13], data[14], data[15], data[16], data[17], data[18], data[19], + data[20], data[21], data[22], data[23], data[24], data[25], data[26], data[27], data[28], data[29], + data[30], data[31], data[32], data[33], data[34], data[35], data[36], data[37]); +#endif + /* check packet ID & sub packet ID */ + if (((data[0] & 0x0F) != 0x0E) && (data[1] != 0x65)) { + input_info(true, wacom->dev, + "%s: %d data of wacom elec\n", + __func__, (RETRY_COUNT - retry + 1)); + if (--retry) { + continue; + } else { + input_err(true, wacom->dev, + "%s: invalid elec data %d %d\n", + __func__, (data[0] & 0x0F), data[1]); + goto error_test; + } + } + + if (data[11] != 3) { + input_info(true, wacom->dev, + "%s: %d data of wacom elec\n", + __func__, (RETRY_COUNT - retry + 1)); + if (--retry) { + continue; + } else { + input_info(true, wacom->dev, + "%s: invalid elec status %d\n", + __func__, data[11]); + goto error_test; + } + } + + for (i = 0; i < COM_ELEC_DATA_NUM; i++) { + if (!remain_data_count) + break; + + offset = COM_ELEC_DATA_POS + (i * 2); + tmp = ((u16)(data[offset]) << 8) + data[offset + 1]; + rx = data[13] + i; + + edata->elec_data[rx * max_ch + tx] = tmp; + + remain_data_count--; + } + } while (remain_data_count); + + cmd = COM_ELEC_TRSCON; + ret = wacom_send(wacom, &cmd, 1); + if (ret != 1) { + input_err(true, wacom->dev, "%s: failed to send 0x%02X\n", + __func__, cmd); + goto error_test; + } + sec_delay(30); + } + + cmd = COM_ELEC_YSCAN; + ret = wacom_send(wacom, &cmd, 1); + if (ret != 1) { + input_err(true, wacom->dev, + "%s: failed to send 0x%02X\n", __func__, cmd); + goto error_test; + } + sec_delay(30); + + cmd = COM_ELEC_TRSX0; + ret = wacom_send(wacom, &cmd, 1); + if (ret != 1) { + input_err(true, wacom->dev, + "%s: failed to send 0x%02X\n", __func__, cmd); + goto error_test; + } + sec_delay(30); + + cmd = COM_ELEC_SCAN_START; + ret = wacom_send(wacom, &cmd, 1); + if (ret != 1) { + input_err(true, wacom->dev, + "%s: failed to send 0x%02X\n", __func__, cmd); + goto error_test; + } + sec_delay(30); + + for (tx = 0; tx < max_ch; tx++) { + /* y scan mode receive data through y coils */ + remain_data_count = edata->max_y_ch; + + do { + cmd = COM_ELEC_REQUESTDATA; + ret = wacom_send(wacom, &cmd, 1); + if (ret != 1) { + input_err(true, wacom->dev, + "%s: failed to send 0x%02X\n", + __func__, cmd); + goto error_test; + } + sec_delay(30); + + ret = wacom_recv(wacom, data, COM_ELEC_NUM); + if (ret != COM_ELEC_NUM) { + input_err(true, wacom->dev, + "%s: failed to receive elec data\n", + __func__); + goto error_test; + } +#if 0 + input_info(true, wacom->dev, "%X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X %X\n", + data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], + data[10], data[11], data[12], data[13], data[14], data[15], data[16], data[17], data[18], data[19], + data[20], data[21], data[22], data[23], data[24], data[25], data[26], data[27], data[28], data[29], + data[30], data[31], data[32], data[33], data[34], data[35], data[36], data[37]); +#endif + /* check packet ID & sub packet ID */ + if (((data[0] & 0x0F) != 0x0E) && (data[1] != 0x65)) { + input_info(true, wacom->dev, + "%s: %d data of wacom elec\n", + __func__, (RETRY_COUNT - retry + 1)); + if (--retry) { + continue; + } else { + input_err(true, wacom->dev, + "%s: invalid elec data %d %d\n", + __func__, (data[0] & 0x0F), data[1]); + goto error_test; + } + } + + if (data[11] != 3) { + input_info(true, wacom->dev, + "%s: %d data of wacom elec\n", + __func__, (RETRY_COUNT - retry + 1)); + if (--retry) { + continue; + } else { + input_info(true, wacom->dev, + "%s: invalid elec status %d\n", + __func__, data[11]); + goto error_test; + } + } + + for (i = 0; i < COM_ELEC_DATA_NUM; i++) { + if (!remain_data_count) + break; + + offset = COM_ELEC_DATA_POS + (i * 2); + tmp = ((u16)(data[offset]) << 8) + data[offset + 1]; + rx = edata->max_x_ch + data[13] + i; + + edata->elec_data[rx * max_ch + tx] = tmp; + + remain_data_count--; + } + } while (remain_data_count); + + cmd = COM_ELEC_TRSCON; + ret = wacom_send(wacom, &cmd, 1); + if (ret != 1) { + input_err(true, wacom->dev, "%s: failed to send 0x%02X\n", + __func__, cmd); + goto error_test; + } + sec_delay(30); + } + + print_spec_data(wacom); + + print_elec_data(wacom); + + /* tx about x coil */ + for (tx = 0, i = 0; tx < edata->max_x_ch; tx++, i++) { + edata->xx[i] = 0; + edata->xy[i] = 0; + /* rx about x coil */ + for (rx = 0; rx < edata->max_x_ch; rx++) { + offset = rx * max_ch + tx; + if ((edata->elec_data[offset] > edata->xx[i]) && (tx != rx)) + edata->xx[i] = edata->elec_data[offset]; + if (tx == rx) + edata->xx_self[i] = edata->elec_data[offset]; + } + /* rx about y coil */ + for (rx = edata->max_x_ch; rx < max_ch; rx++) { + offset = rx * max_ch + tx; + if (edata->elec_data[offset] > edata->xy[i]) + edata->xy[i] = edata->elec_data[offset]; + } + + edata->xy_edg[i] = min(edata->elec_data[(edata->max_x_ch + 1) * max_ch + tx], + edata->elec_data[(max_ch - 2) * max_ch + tx]); + } + + /* tx about y coil */ + for (tx = edata->max_x_ch, i = 0; tx < max_ch; tx++, i++) { + edata->yx[i] = 0; + edata->yy[i] = 0; + /* rx about x coil */ + for (rx = 0; rx < edata->max_x_ch; rx++) { + offset = rx * max_ch + tx; + if (edata->elec_data[offset] > edata->yx[i]) + edata->yx[i] = edata->elec_data[offset]; + } + /* rx about y coil */ + for (rx = edata->max_x_ch; rx < max_ch; rx++) { + offset = rx * max_ch + tx; + if ((edata->elec_data[offset] > edata->yy[i]) && (rx != tx)) + edata->yy[i] = edata->elec_data[offset]; + if (rx == tx) + edata->yy_self[i] = edata->elec_data[offset]; + } + + edata->yx_edg[i] = min(edata->elec_data[max_ch + tx], + edata->elec_data[(edata->max_x_ch - 2) * max_ch + tx]); + } + + print_trx_data(wacom); + + calibration_trx_data(wacom); + + print_cal_trx_data(wacom); + + calculate_ratio(wacom); + + print_ratio_trx_data(wacom); + + make_decision(wacom, elec_result); + + print_difference_ratio_trx_data(wacom); + + count = elec_result[0] >> 8; + if (!count) { + snprintf(tmp_buf, sizeof(tmp_buf), "0_"); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, sizeof(tmp_buf)); + } else { + if (count > 3) + snprintf(tmp_buf, sizeof(tmp_buf), "3,"); + else + snprintf(tmp_buf, sizeof(tmp_buf), "%d,", count); + + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, sizeof(tmp_buf)); + + for (i = 0, count = 0; i < max_ch; i++) { + if ((elec_result[i + 1] & SEC_SHORT) && count < 3) { + if (i < edata->max_x_ch) + snprintf(tmp_buf, sizeof(tmp_buf), "X%d,", i); + else + snprintf(tmp_buf, sizeof(tmp_buf), "Y%d,", i - edata->max_x_ch); + + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, sizeof(tmp_buf)); + count++; + } + } + buff[strlen(buff) - 1] = '_'; + } + + count = elec_result[0] & 0x00FF; + if (!count) { + snprintf(tmp_buf, sizeof(tmp_buf), "0_"); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, sizeof(tmp_buf)); + } else { + if (count > 3) + snprintf(tmp_buf, sizeof(tmp_buf), "3,"); + else + snprintf(tmp_buf, sizeof(tmp_buf), "%d,", count); + + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, sizeof(tmp_buf)); + + for (i = 0, count = 0; i < max_ch; i++) { + if ((elec_result[i + 1] & SEC_OPEN) && count < 3) { + if (i < edata->max_x_ch) + snprintf(tmp_buf, sizeof(tmp_buf), "X%d,", i); + else + snprintf(tmp_buf, sizeof(tmp_buf), "Y%d,", i - edata->max_x_ch); + + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, sizeof(tmp_buf)); + count++; + } + } + buff[strlen(buff) - 1] = '_'; + } + + snprintf(tmp_buf, sizeof(tmp_buf), "%d,%d_", edata->max_x_ch, edata->max_y_ch); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, sizeof(tmp_buf)); + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, sizeof(tmp_buf), "%d,", edata->xx[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, sizeof(tmp_buf)); + } + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, sizeof(tmp_buf), "%d,", edata->xy[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, sizeof(tmp_buf)); + } + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, sizeof(tmp_buf), "%d,", edata->yx[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, sizeof(tmp_buf)); + } + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, sizeof(tmp_buf), "%d,", edata->yy[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, sizeof(tmp_buf)); + } + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, sizeof(tmp_buf), "%d,", edata->xx_self[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, sizeof(tmp_buf)); + } + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, sizeof(tmp_buf), "%d,", edata->yy_self[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, sizeof(tmp_buf)); + } + + for (i = 0; i < edata->max_x_ch; i++) { + snprintf(tmp_buf, sizeof(tmp_buf), "%d,", edata->xy_edg[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, sizeof(tmp_buf)); + } + + for (i = 0; i < edata->max_y_ch; i++) { + snprintf(tmp_buf, sizeof(tmp_buf), "%d,", edata->yx_edg[i]); + strlcat(buff, tmp_buf, buff_size); + memset(tmp_buf, 0x00, sizeof(tmp_buf)); + } + + sec_cmd_set_cmd_result(sec, buff, buff_size); + sec->cmd_state = SEC_CMD_STATUS_OK; + + kfree(elec_result); + kfree(buff); + + ret = wacom_start_stop_cmd(wacom, WACOM_STOP_AND_START_CMD); + if (ret != 1) { + input_err(true, wacom->dev, "%s: failed to set stop-start cmd, %d\n", + __func__, ret); + } + + wacom_enable_irq(wacom, true); + + return; + +error_test: + ret = wacom_start_stop_cmd(wacom, WACOM_STOP_AND_START_CMD); + if (ret != 1) { + input_err(true, wacom->dev, "%s: failed to set stop-start cmd, %d\n", + __func__, ret); + } + + wacom_enable_irq(wacom, true); + +error: + if (elec_result) + kfree(elec_result); + + if (buff) + kfree(buff); + + snprintf(tmp_buf, sizeof(tmp_buf), "NG"); + sec_cmd_set_cmd_result(sec, tmp_buf, sizeof(tmp_buf)); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + + return; +} + +static void ready_elec_test(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + char data; + int ret; + + sec_cmd_set_default_result(sec); + + if (sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF)) { + input_err(true, wacom->dev, "%s: power off state, skip!\n", + __func__); + goto err_out; + } + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s: reset is on going, skip!\n", + __func__); + goto err_out; + } + + if (sec->cmd_param[0]) + data = COM_ASYNC_VSYNC; + else + data = COM_SYNC_VSYNC; + + ret = wacom_send(wacom, &data, 1); + if (ret != 1) { + input_err(true, wacom->dev, + "%s: failed to send data(%02x, %d)\n", + __func__, data, ret); + goto err_out; + } + + sec_delay(30); + + snprintf(buff, sizeof(buff), "OK\n"); + sec->cmd_state = SEC_CMD_STATUS_OK; + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + + input_info(true, wacom->dev, "%s: %02x %s\n", + __func__, data, buff); + return; +err_out: + snprintf(buff, sizeof(buff), "NG\n"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); +} + +static void get_elec_test_ver(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + + sec_cmd_set_default_result(sec); + + if (sec->cmd_param[0] < EPEN_ELEC_DATA_MAIN || sec->cmd_param[0] >= EPEN_ELEC_DATA_MAX) { + input_err(true, wacom->dev, + "%s: Abnormal parm[%d]\n", __func__, sec->cmd_param[0]); + goto err_out; + } + + if (wacom->edatas[sec->cmd_param[0]] == NULL) { + input_err(true, wacom->dev, + "%s: Elec test spec is null[%d]\n", __func__, sec->cmd_param[0]); + goto err_out; + } + + snprintf(buff, sizeof(buff), "%d.%d", + wacom->edatas[sec->cmd_param[0]]->spec_ver[0], + wacom->edatas[sec->cmd_param[0]]->spec_ver[1]); + + input_info(true, wacom->dev, "%s: pos[%d] ver(%s)\n", + __func__, sec->cmd_param[0], buff); + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; + return; + +err_out: + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; +} + +/* have to add cmd from Q os for garage test */ +static void get_chip_name(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + + sec_cmd_set_default_result(sec); + + if (wacom->plat_data->img_version_of_ic[0] == MPU_W9018) + snprintf(buff, sizeof(buff), "W9018"); + else if (wacom->plat_data->img_version_of_ic[0] == MPU_W9019) + snprintf(buff, sizeof(buff), "W9019"); + else if (wacom->plat_data->img_version_of_ic[0] == MPU_W9020) + snprintf(buff, sizeof(buff), "W9020"); + else if (wacom->plat_data->img_version_of_ic[0] == MPU_W9021) + snprintf(buff, sizeof(buff), "W9021"); + else if (wacom->plat_data->img_version_of_ic[0] == MPU_WEZ01) + snprintf(buff, sizeof(buff), "WEZ01"); + else if (wacom->plat_data->img_version_of_ic[0] == MPU_WEZ02) + snprintf(buff, sizeof(buff), "WEZ02"); + else + snprintf(buff, sizeof(buff), "N/A"); + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; +} + +/* Factory cmd for firmware update + * argument represent what is source of firmware like below. + * + * 0 : [BUILT_IN] Getting firmware which is for user. + * 1 : [UMS] Getting firmware from sd card. + * 2 : none + * 3 : [FFU] Getting firmware from apk. + */ + +static void fw_update(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + int ret = 0; + + sec_cmd_set_default_result(sec); + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s : reset is on going, skip!\n", __func__); + goto err_out; + } + + if (sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF)) { + input_err(true, wacom->dev, "%s: [ERROR] Wacom is stopped\n", __func__); + goto err_out; + } + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > WACOM_VERIFICATION) { + input_err(true, wacom->dev, "%s: [ERROR] parm error![%d]\n", + __func__, sec->cmd_param[0]); + goto err_out; + } + + mutex_lock(&wacom->lock); + + switch (sec->cmd_param[0]) { + case WACOM_BUILT_IN: + ret = wacom_fw_update_on_hidden_menu(wacom, FW_BUILT_IN); + break; + case WACOM_SDCARD: +#if WACOM_PRODUCT_SHIP + ret = wacom_fw_update_on_hidden_menu(wacom, FW_IN_SDCARD_SIGNED); +#else + ret = wacom_fw_update_on_hidden_menu(wacom, FW_IN_SDCARD); +#endif + break; + case WACOM_SPU: + ret = wacom_fw_update_on_hidden_menu(wacom, FW_SPU); + break; + case WACOM_VERIFICATION: + ret = wacom_fw_update_on_hidden_menu(wacom, FW_VERIFICATION); + break; + default: + input_err(true, wacom->dev, "%s: Not support command[%d]\n", + __func__, sec->cmd_param[0]); + mutex_unlock(&wacom->lock); + goto err_out; + } + + mutex_unlock(&wacom->lock); + + if (ret) { + input_err(true, wacom->dev, "%s: Wacom fw update(%d) fail ret [%d]\n", + __func__, sec->cmd_param[0], ret); + goto err_out; + } + + input_info(true, wacom->dev, "%s: Wacom fw update[%d]\n", __func__, sec->cmd_param[0]); + + snprintf(buff, sizeof(buff), "OK\n"); + sec->cmd_state = SEC_CMD_STATUS_OK; + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + + input_info(true, wacom->dev, "%s: Done\n", __func__); + + return; + +err_out: + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + return; + +} + +static void get_fw_ver_bin(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + + sec_cmd_set_default_result(sec); + + snprintf(buff, sizeof(buff), "%02X%02X%02X%02X", + wacom->plat_data->img_version_of_bin[0], wacom->plat_data->img_version_of_bin[1], + wacom->plat_data->img_version_of_bin[2], wacom->plat_data->img_version_of_bin[3]); + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; +} + +static void get_fw_ver_ic(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + + sec_cmd_set_default_result(sec); + + snprintf(buff, sizeof(buff), "%02X%02X%02X%02X", + wacom->plat_data->img_version_of_ic[0], wacom->plat_data->img_version_of_ic[1], + wacom->plat_data->img_version_of_ic[2], wacom->plat_data->img_version_of_ic[3]); + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_OK; +} + +static void set_ic_reset(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + + sec_cmd_set_default_result(sec); + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s: reset is on going, skip!\n", + __func__); + wacom->query_status = false; + goto out; + } + + wacom_enable_irq(wacom, false); + + wacom->function_result &= ~EPEN_EVENT_SURVEY; + wacom->survey_mode = EPEN_SURVEY_MODE_NONE; + + /* Reset IC */ + wacom_reset_hw(wacom); + /* I2C Test */ + wacom_query(wacom); + + wacom_enable_irq(wacom, true); + + input_info(true, wacom->dev, "%s: result %d\n", __func__, wacom->query_status); +out: + if (wacom->query_status) { + snprintf(buff, sizeof(buff), "OK"); + sec->cmd_state = SEC_CMD_STATUS_OK; + } else { + snprintf(buff, sizeof(buff), "NG"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + } + + input_info(true, wacom->dev, "%s: %s\n", __func__, buff); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); +} + +static void get_checksum(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + + wacom->checksum_result = false; + + if (sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF)) { + input_err(true, wacom->dev, "%s: power off state, skip!\n", __func__); + goto out; + } + + if (!wacom->probe_done) { + input_err(true, wacom->dev, "%s: probe_done yet, skip!\n", __func__); + goto out; + } + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s: reset is on going, skip!\n", __func__); + goto out; + } + + sec_cmd_set_default_result(sec); + + wacom_enable_irq(wacom, false); + + wacom_checksum(wacom); + + wacom_enable_irq(wacom, true); + + input_info(true, wacom->dev, + "%s: result %d\n", __func__, wacom->checksum_result); + +out: + if (wacom->checksum_result) { + snprintf(buff, sizeof(buff), "OK"); + sec->cmd_state = SEC_CMD_STATUS_OK; + } else { + snprintf(buff, sizeof(buff), "NG"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + } + + input_info(true, wacom->dev, "%s: %s\n", __func__, buff); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); +} + +static void get_digitizer_connection(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + int ret; + + input_info(true, wacom->dev, "%s\n", __func__); + + sec_cmd_set_default_result(sec); + + ret = wacom_check_ub(wacom); + if (ret < 0) { + input_info(true, wacom->dev, "%s: digitizer is not attached\n", __func__); + goto out; + } + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s: reset is on going, skip!\n", + __func__); + wacom->connection_check = false; + goto out; + } + + if (!wacom->probe_done) { + input_err(true, wacom->dev, "%s: probe_done yet, OK & skip!\n", __func__); + wacom->connection_check = false; + goto out; + } + + get_connection_test(wacom, WACOM_DIGITIZER_TEST); + +out: + if (wacom->module_ver == 0x2) { + snprintf(buff, sizeof(buff), "%s %d %d %d %s %d\n", + wacom->connection_check ? "OK" : "NG", + wacom->module_ver, wacom->fail_channel, + wacom->min_adc_val, wacom->error_cal ? "NG" : "OK", + wacom->min_cal_val); + } else { + snprintf(buff, sizeof(buff), "%s %d %d %d\n", + wacom->connection_check ? "OK" : "NG", + wacom->module_ver, wacom->fail_channel, + wacom->min_adc_val); + } + + if (wacom->connection_check) + sec->cmd_state = SEC_CMD_STATUS_OK; + else + sec->cmd_state = SEC_CMD_STATUS_FAIL; + + input_info(true, wacom->dev, "%s: %s\n", __func__, buff); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); +} + +static void get_garage_connection(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + + input_info(true, wacom->dev, "%s\n", __func__); + + sec_cmd_set_default_result(sec); + + if (!wacom->support_garage_open_test) { + input_err(true, wacom->dev, "%s: not support garage open test", __func__); + + snprintf(buff, sizeof(buff), "NA"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + sec_cmd_set_cmd_exit(sec); + return; + } + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s: reset is on going, skip!\n", + __func__); + wacom->garage_connection_check = false; + goto out; + } + + get_connection_test(wacom, WACOM_GARAGE_TEST); + +out: + if (wacom->module_ver == 0x2) { + snprintf(buff, sizeof(buff), "%s %d %d %d %s %d\n", + wacom->garage_connection_check ? "OK" : "NG", + wacom->module_ver, wacom->garage_fail_channel, + wacom->garage_min_adc_val, wacom->garage_error_cal ? "NG" : "OK", + wacom->garage_min_cal_val); + } else { + snprintf(buff, sizeof(buff), "%s %d %d %d\n", + wacom->garage_connection_check ? "OK" : "NG", + wacom->module_ver, wacom->garage_fail_channel, + wacom->garage_min_adc_val); + } + + if (wacom->garage_connection_check) + sec->cmd_state = SEC_CMD_STATUS_OK; + else + sec->cmd_state = SEC_CMD_STATUS_FAIL; + + input_info(true, wacom->dev, "%s: %s\n", __func__, buff); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); +} + +static void set_saving_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + static u32 call_count; + + sec_cmd_set_default_result(sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + input_err(true, wacom->dev, "%s: Abnormal parm[%d]\n", + __func__, sec->cmd_param[0]); + snprintf(buff, sizeof(buff), "NG"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto fail; + } + + if (call_count < 0xFFFFFFF0) + call_count++; + + wacom->battery_saving_mode = sec->cmd_param[0]; + + input_info(true, wacom->dev, "%s: ps %s & pen %s [%d]\n", + __func__, wacom->battery_saving_mode ? "on" : "off", + (wacom->function_result & EPEN_EVENT_PEN_OUT) ? "out" : "in", + call_count); + + if (!wacom->probe_done) { + input_err(true, wacom->dev, "%s : not probe done & skip!\n", __func__); + goto out; + } + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s : reset is on going, data saved and skip!\n", __func__); + goto out; + } + + if (sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF) && wacom->battery_saving_mode) { + input_err(true, wacom->dev, "%s: already power off, save & return\n", __func__); + goto out; + } + + if (!(wacom->function_result & EPEN_EVENT_PEN_OUT)) { + wacom_select_survey_mode(wacom, atomic_read(&wacom->screen_on)); + + if (wacom->battery_saving_mode) + forced_release_fullscan(wacom); + } + +out: + snprintf(buff, sizeof(buff), "OK"); + sec->cmd_state = SEC_CMD_STATUS_OK; + +fail: + input_info(true, wacom->dev, "%s: %s\n", __func__, buff); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec_cmd_set_cmd_exit(sec); +} + +static void get_insert_status(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + int pen_state = (wacom->function_result & EPEN_EVENT_PEN_OUT); + + sec_cmd_set_default_result(sec); + + input_info(true, wacom->dev, "%s : pen is %s\n", + __func__, pen_state ? "OUT" : "IN"); + + sec->cmd_state = SEC_CMD_STATUS_OK; + snprintf(buff, sizeof(buff), "%d\n", pen_state ? 0 : 1); + + input_info(true, wacom->dev, "%s: %s\n", __func__, buff); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); +} + +static void set_wcharging_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + int ret; + + sec_cmd_set_default_result(sec); + + wacom->wcharging_mode = sec->cmd_param[0]; + input_info(true, wacom->dev, "%s: %d\n", __func__, wacom->wcharging_mode); + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s : reset is on going, data saved and skip!\n", __func__); + goto out; + } + + if (sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF)) { + input_err(true, wacom->dev, + "%s: power off, save & return\n", __func__); + sec->cmd_state = SEC_CMD_STATUS_OK; + goto out; + } + + if (!wacom->probe_done) { + input_err(true, wacom->dev, "%s: probe is not done\n", __func__); + sec->cmd_state = SEC_CMD_STATUS_OK; + goto out; + } + + ret = wacom_set_sense_mode(wacom); + if (ret < 0) { + input_err(true, wacom->dev, "%s: do not set sensitivity mode, %d\n", + __func__, ret); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto out; + } + sec->cmd_state = SEC_CMD_STATUS_OK; + +out: + if (sec->cmd_state == SEC_CMD_STATUS_OK) + snprintf(buff, sizeof(buff), "OK"); + else + snprintf(buff, sizeof(buff), "NG"); + + input_info(true, wacom->dev, "%s: %s\n", __func__, buff); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec_cmd_set_cmd_exit(sec); +} + +static void get_wcharging_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + + sec_cmd_set_default_result(sec); + + input_info(true, wacom->dev, "%s: %s\n", __func__, + !wacom->wcharging_mode ? "NORMAL" : "LOWSENSE"); + + sec->cmd_state = SEC_CMD_STATUS_OK; + snprintf(buff, sizeof(buff), "%s\n", !wacom->wcharging_mode ? "NORMAL" : "LOWSENSE"); + + input_info(true, wacom->dev, "%s: %s\n", __func__, buff); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); +} + +static void set_disable_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + + sec_cmd_set_default_result(sec); + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s: reset is on going, skip!\n", + __func__); + goto err_out; + } + + if (sec->cmd_param[0]) + wacom_disable_mode(wacom, WACOM_DISABLE); + else + wacom_disable_mode(wacom, WACOM_ENABLE); + + snprintf(buff, sizeof(buff), "OK"); + + input_info(true, wacom->dev, "%s: %s\n", __func__, buff); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec_cmd_set_cmd_exit(sec); + + return; +err_out: + snprintf(buff, sizeof(buff), "NG"); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec_cmd_set_cmd_exit(sec); +} + +static void set_screen_off_memo(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + int val = 0; + + sec_cmd_set_default_result(sec); + +#if WACOM_SEC_FACTORY + input_info(true, wacom->dev, "%s : Not support screen off memo mode(%d) in Factory Bin\n", + __func__, sec->cmd_param[0]); + + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto out; +#endif + + val = !(!sec->cmd_param[0]); + + if (val) + wacom->function_set |= EPEN_SETMODE_AOP_OPTION_SCREENOFFMEMO; + else + wacom->function_set &= (~EPEN_SETMODE_AOP_OPTION_SCREENOFFMEMO); + + input_info(true, wacom->dev, + "%s: ps %s aop(%d) set(0x%x) ret(0x%x)\n", __func__, + wacom->battery_saving_mode ? "on" : "off", + (wacom->function_set & EPEN_SETMODE_AOP) ? 1 : 0, + wacom->function_set, wacom->function_result); + + if (!wacom->probe_done) { + input_err(true, wacom->dev, "%s: probe is not done\n", __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto out; + } + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s: reset is on going, data saved and skip!\n", + __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto out; + } + + if (!atomic_read(&wacom->screen_on)) + wacom_select_survey_mode(wacom, false); + + sec->cmd_state = SEC_CMD_STATUS_OK; + +out: + if (sec->cmd_state == SEC_CMD_STATUS_OK) + snprintf(buff, sizeof(buff), "OK"); + else + snprintf(buff, sizeof(buff), "NG"); + + input_info(true, wacom->dev, "%s: %s\n", __func__, buff); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec_cmd_set_cmd_exit(sec); +} + +static void set_epen_aod_enable(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + int val = 0; + + sec_cmd_set_default_result(sec); + sec->cmd_state = SEC_CMD_STATUS_OK; + +#if WACOM_SEC_FACTORY + input_info(true, wacom->dev, "%s : Not support aod mode(%d) in Factory Bin\n", + __func__, val); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto out; +#endif + + val = !(!sec->cmd_param[0]); + + if (val) + wacom->function_set |= EPEN_SETMODE_AOP_OPTION_AOD_LCD_OFF; + else + wacom->function_set &= ~EPEN_SETMODE_AOP_OPTION_AOD_LCD_ON; + + input_info(true, wacom->dev, + "%s: ps %s aop(%d) set(0x%x) ret(0x%x)\n", __func__, + wacom->battery_saving_mode ? "on" : "off", + (wacom->function_set & EPEN_SETMODE_AOP) ? 1 : 0, + wacom->function_set, wacom->function_result); + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s: reset is on going, data saved and skip!\n", + __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto out; + } + + if (!atomic_read(&wacom->screen_on)) + wacom_select_survey_mode(wacom, false); + + +out: + if (sec->cmd_state == SEC_CMD_STATUS_OK) + snprintf(buff, sizeof(buff), "OK"); + else + snprintf(buff, sizeof(buff), "NG"); + + input_info(true, wacom->dev, "%s: %s\n", __func__, buff); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec_cmd_set_cmd_exit(sec); +} + +static void set_aod_lcd_onoff_status(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + int val = 0; + int ret; + + sec_cmd_set_default_result(sec); + +#if WACOM_SEC_FACTORY + input_info(true, wacom->dev, "%s : Not support aod mode(%d) in Factory Bin\n", + __func__, val); + + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto out; +#endif + + val = !(!sec->cmd_param[0]); + + if (val) + wacom->function_set |= EPEN_SETMODE_AOP_OPTION_AOD_LCD_STATUS; + else + wacom->function_set &= ~EPEN_SETMODE_AOP_OPTION_AOD_LCD_STATUS; + + if (!(wacom->function_set & EPEN_SETMODE_AOP_OPTION_AOD) || atomic_read(&wacom->screen_on)) { + sec->cmd_state = SEC_CMD_STATUS_OK; + goto out; + } + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s: reset is on going, data saved and skip!\n", + __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto out; + } + + if (wacom->survey_mode == EPEN_SURVEY_MODE_GARAGE_AOP) { + if ((wacom->function_set & EPEN_SETMODE_AOP_OPTION_AOD_LCD_ON) + == EPEN_SETMODE_AOP_OPTION_AOD_LCD_OFF) { + forced_release_fullscan(wacom); + } + + mutex_lock(&wacom->mode_lock); + wacom_set_survey_mode(wacom, wacom->survey_mode); + mutex_unlock(&wacom->mode_lock); + } + + ret = wacom_set_sense_mode(wacom); + if (ret < 0) { + input_err(true, wacom->dev, "%s: do not set sensitivity mode, %d\n", + __func__, ret); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto out; + } + sec->cmd_state = SEC_CMD_STATUS_OK; + +out: + if (sec->cmd_state == SEC_CMD_STATUS_OK) + snprintf(buff, sizeof(buff), "OK"); + else + snprintf(buff, sizeof(buff), "NG"); + + input_info(true, wacom->dev, "%s: %s\n", __func__, buff); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec_cmd_set_cmd_exit(sec); +} + +static void set_epen_aot_enable(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + int val = 0; + + sec_cmd_set_default_result(sec); + sec->cmd_state = SEC_CMD_STATUS_OK; + +#if WACOM_SEC_FACTORY + input_info(true, wacom->dev, "%s : Not support aod mode(%d) in Factory Bin\n", + __func__, val); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto out; +#endif + + val = !(!sec->cmd_param[0]); + + if (val) + wacom->function_set |= EPEN_SETMODE_AOP_OPTION_AOT; + else + wacom->function_set &= (~EPEN_SETMODE_AOP_OPTION_AOT); + + input_info(true, wacom->dev, + "%s: ps %s aop(%d) set(0x%x) ret(0x%x)\n", __func__, + wacom->battery_saving_mode ? "on" : "off", + (wacom->function_set & EPEN_SETMODE_AOP) ? 1 : 0, + wacom->function_set, wacom->function_result); + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s: reset is on going, data saved and skip!\n", + __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto out; + } + + if (!atomic_read(&wacom->screen_on)) + wacom_select_survey_mode(wacom, false); + +out: + if (sec->cmd_state == SEC_CMD_STATUS_OK) + snprintf(buff, sizeof(buff), "OK"); + else + snprintf(buff, sizeof(buff), "NG"); + + input_info(true, wacom->dev, "%s: %s\n", __func__, buff); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec_cmd_set_cmd_exit(sec); +} + +#if WACOM_SEC_FACTORY +/* k|K|0 : FW_BUILT_IN, t|T|1 : FW_FACTORY_GARAGE u|U|2 : FW_FACTORY_UNIT */ +static void set_fac_select_firmware(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + u8 fw_update_way = FW_NONE; + int ret; + + sec_cmd_set_default_result(sec); + mutex_lock(&wacom->lock); + switch (sec->cmd_param[0]) { + case 0: + fw_update_way = FW_BUILT_IN; + break; + case 1: + fw_update_way = FW_FACTORY_GARAGE; + break; + case 2: + fw_update_way = FW_FACTORY_UNIT; + break; + default: + input_err(true, wacom->dev, "wrong parameter\n"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto out; + } + + ret = wacom_load_fw(wacom, fw_update_way); + if (ret < 0) { + input_info(true, wacom->dev, "failed to load fw data\n"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto out; + } + + wacom_unload_fw(wacom); + + sec->cmd_state = SEC_CMD_STATUS_OK; + +out: + if (sec->cmd_state == SEC_CMD_STATUS_OK) + snprintf(buff, sizeof(buff), "OK"); + else + snprintf(buff, sizeof(buff), "NG"); + + input_info(true, wacom->dev, "%s: %s\n", __func__, buff); + mutex_unlock(&wacom->lock); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec_cmd_set_cmd_exit(sec); +} + +static void set_fac_garage_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + int ret; + + sec_cmd_set_default_result(sec); + sec->cmd_state = SEC_CMD_STATUS_OK; + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + input_err(true, wacom->dev, "%s: abnormal param(%d)\n", + __func__, sec->cmd_param[0]); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto out; + } + + if (sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF)) { + input_err(true, wacom->dev, "%s: power off state, skip!\n", + __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto out; + } + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s: reset is on going, skip!\n", + __func__); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + goto out; + } + + if (sec->cmd_param[0]) { + /* change normal mode for garage monitoring */ + mutex_lock(&wacom->mode_lock); + wacom_set_survey_mode(wacom, EPEN_SURVEY_MODE_NONE); + mutex_unlock(&wacom->mode_lock); + + ret = wacom_start_stop_cmd(wacom, WACOM_STOP_CMD); + if (ret != 1) { + input_err(true, wacom->dev, "%s: failed to set stop cmd, %d\n", + __func__, ret); + wacom->fac_garage_mode = 0; + goto fail_out; + } + + sec_delay(50); + wacom->fac_garage_mode = 1; + + } else { + wacom->fac_garage_mode = 0; + + ret = wacom_start_stop_cmd(wacom, WACOM_STOP_AND_START_CMD); + if (ret != 1) { + input_err(true, wacom->dev, "%s: failed to set stop cmd, %d\n", + __func__, ret); + goto fail_out; + } + + /* recovery wacom mode */ + wacom_select_survey_mode(wacom, atomic_read(&wacom->screen_on)); + } + + input_info(true, wacom->dev, "%s: done(%d)\n", __func__, sec->cmd_param[0]); + +out: + if (sec->cmd_state == SEC_CMD_STATUS_OK) + snprintf(buff, sizeof(buff), "OK"); + else + snprintf(buff, sizeof(buff), "NG"); + + input_info(true, wacom->dev, "%s: %s\n", __func__, buff); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec_cmd_set_cmd_exit(sec); + + return; + +fail_out: + /* recovery wacom mode */ + wacom_select_survey_mode(wacom, atomic_read(&wacom->screen_on)); + + sec->cmd_state = SEC_CMD_STATUS_FAIL; + snprintf(buff, sizeof(buff), "NG"); + input_info(true, wacom->dev, "%s: %s\n", __func__, buff); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec_cmd_set_cmd_exit(sec); + +} + +static void get_fac_garage_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + + sec_cmd_set_default_result(sec); + sec->cmd_state = SEC_CMD_STATUS_OK; + + snprintf(buff, sizeof(buff), "garage mode %s", + wacom->fac_garage_mode ? "IN" : "OUT"); + + input_info(true, wacom->dev, "%s: %s\n", __func__, buff); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec_cmd_set_cmd_exit(sec); +} + +static void get_fac_garage_rawdata(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + char data[17] = { 0, }; + int ret, retry = 1; + u8 cmd; + + sec_cmd_set_default_result(sec); + sec->cmd_state = SEC_CMD_STATUS_OK; + + if (!wacom->fac_garage_mode) { + input_err(true, wacom->dev, "not in factory garage mode\n"); + goto out; + } + + if (sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF)) { + input_err(true, wacom->dev, "%s: power off state, skip!\n", + __func__); + goto out; + } + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s: reset is on going, skip!\n", + __func__); + goto out; + } + +get_garage_retry: + wacom_enable_irq(wacom, false); + + cmd = COM_REQUEST_GARAGEDATA; + ret = wacom_send(wacom, &cmd, 1); + if (ret < 0) { + input_err(true, wacom->dev, "failed to send request garage data command %d\n", ret); + sec_delay(30); + + goto fail_out; + } + + sec_delay(30); + + ret = wacom_recv(wacom, data, sizeof(data)); + if (ret < 0) { + input_err(true, wacom->dev, "failed to read garage raw data, %d\n", ret); + + wacom->garage_freq0 = wacom->garage_freq1 = 0; + wacom->garage_gain0 = wacom->garage_gain1 = 0; + + goto fail_out; + } + + wacom_enable_irq(wacom, true); + + input_info(true, wacom->dev, "%x %x %x %x %x %x %x %x %x %x\n", + data[2], data[3], data[4], data[5], data[6], data[7], + data[8], data[9], data[10], data[11]); + + wacom->garage_gain0 = data[6]; + wacom->garage_freq0 = ((u16)data[7] << 8) + data[8]; + + wacom->garage_gain1 = data[9]; + wacom->garage_freq1 = ((u16)data[10] << 8) + data[11]; + + if (wacom->garage_freq0 == 0 && retry > 0) { + retry--; + goto get_garage_retry; + } + + snprintf(buff, sizeof(buff), "%d,%d,%d,%d", + wacom->garage_gain0, wacom->garage_freq0, + wacom->garage_gain1, wacom->garage_freq1); + + input_info(true, wacom->dev, "%s: %s\n", __func__, buff); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + return; + +fail_out: + wacom_enable_irq(wacom, true); +out: + sec->cmd_state = SEC_CMD_STATUS_FAIL; + snprintf(buff, sizeof(buff), "NG"); + + input_info(true, wacom->dev, "%s: %s\n", __func__, buff); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); +} +#endif + +static void debug(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + + sec_cmd_set_default_result(sec); + + wacom->debug_flag = sec->cmd_param[0]; + + snprintf(buff, sizeof(buff), "OK"); + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + sec_cmd_set_cmd_exit(sec); +} + +static void set_cover_type(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + + sec_cmd_set_default_result(sec); + + if (wacom->support_cover_noti) { + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 6) { + input_err(true, wacom->dev, "%s: Not support command[%d]\n", + __func__, sec->cmd_param[0]); + goto err_out; + } else { + wacom->compensation_type = sec->cmd_param[0]; + } + } else { + input_err(true, wacom->dev, "%s: Not support notice cover command\n", + __func__); + goto err_out; + } + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s : reset is on going, skip!\n", __func__); + goto err_out; + } + + if (sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF)) { + input_err(true, wacom->dev, "%s: [ERROR] Wacom is stopped\n", __func__); + goto err_out; + } + + input_info(true, wacom->dev, "%s: %d\n", __func__, sec->cmd_param[0]); + + wacom_swap_compensation(wacom, wacom->compensation_type); + + snprintf(buff, sizeof(buff), "OK\n"); + sec->cmd_state = SEC_CMD_STATUS_OK; + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec_cmd_set_cmd_exit(sec); + + input_info(true, wacom->dev, "%s: Done\n", __func__); + + return; + +err_out: + snprintf(buff, sizeof(buff), "NG"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec_cmd_set_cmd_exit(sec); + + input_info(true, wacom->dev, "%s: Fail\n", __func__); +} + +static void set_display_mode(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + + sec_cmd_set_default_result(sec); + + if (wacom->support_set_display_mode) { + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 3) { + input_err(true, wacom->dev, "%s: Not support command[%d]\n", + __func__, sec->cmd_param[0]); + goto err_out; + } else { + if (sec->cmd_param[0] == NOMAL_SPEED_MODE) + wacom->compensation_type = DISPLAY_NS_MODE; + else if (sec->cmd_param[0] == ADAPTIVE_MODE || sec->cmd_param[0] == HIGH_SPEED_MODE) + wacom->compensation_type = DISPLAY_HS_MODE; + } + } else { + input_err(true, wacom->dev, "%s: Not support set display mode command\n", + __func__); + goto err_out; + } + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s : reset is on going, skip!\n", __func__); + goto err_out; + } + + if (sec_input_cmp_ic_status(wacom->dev, CHECK_POWEROFF)) { + input_err(true, wacom->dev, "%s: [ERROR] Wacom is stopped\n", __func__); + goto err_out; + } + + input_info(true, wacom->dev, "%s: %d\n", __func__, sec->cmd_param[0]); + + if (!wacom->probe_done) { + input_err(true, wacom->dev, "%s: probe is not done\n", __func__); + goto err_out; + } + + wacom_swap_compensation(wacom, wacom->compensation_type); + + snprintf(buff, sizeof(buff), "OK\n"); + sec->cmd_state = SEC_CMD_STATUS_OK; + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec_cmd_set_cmd_exit(sec); + + input_info(true, wacom->dev, "%s: Done\n", __func__); + + return; + +err_out: + snprintf(buff, sizeof(buff), "NG"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec_cmd_set_cmd_exit(sec); + + input_info(true, wacom->dev, "%s: Fail\n", __func__); +} + +static void set_fold_state(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + + sec_cmd_set_default_result(sec); + + input_info(true, wacom->dev, "%s: %d\n", __func__, sec->cmd_param[0]); + + /* 1: folding, 0: unfolding */ + wacom->fold_state = sec->cmd_param[0]; + + if (wacom->fold_state == FOLD_STATUS_FOLDING) { + input_info(true, wacom->dev, "%s: folding device, turn off\n", __func__); + mutex_lock(&wacom->lock); + wacom_enable_irq(wacom, false); + atomic_set(&wacom->plat_data->power_state, SEC_INPUT_STATE_POWER_OFF); + wacom_power(wacom, false); + atomic_set(&wacom->screen_on, 0); + wacom->reset_flag = false; + wacom->survey_mode = EPEN_SURVEY_MODE_NONE; + wacom->function_result &= ~EPEN_EVENT_SURVEY; + mutex_unlock(&wacom->lock); + } + + snprintf(buff, sizeof(buff), "OK\n"); + sec->cmd_state = SEC_CMD_STATUS_OK; + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec_cmd_set_cmd_exit(sec); + + input_info(true, wacom->dev, "%s: Done\n", __func__); +} + +#if !WACOM_SEC_FACTORY +static void sec_wacom_device_enable(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + struct wacom_data *wacom = container_of(sec, struct wacom_data, sec); + char buff[SEC_CMD_STR_LEN] = { 0 }; + + sec_cmd_set_default_result(sec); + + if (sec->cmd_param[0] < 0 || sec->cmd_param[0] > 1) { + input_err(true, wacom->dev, "%s: Not support command[%d]\n", + __func__, sec->cmd_param[0]); + goto err_out; + } + + //check probe done & save data + if (!wacom->probe_done) { + input_err(true, wacom->dev, "%s : not probe done. skip and data saved\n", __func__); + goto err_out; + } + + if (wacom->reset_is_on_going) { + input_err(true, wacom->dev, "%s: reset is on going, data saved and skip!\n", + __func__); + goto err_out; + } + + if (sec->cmd_param[0]) { + wacom->epen_blocked = WACOM_ENABLE; + if (wacom->fold_state == FOLD_STATUS_UNFOLDING) + sec_input_enable_device(wacom->plat_data->dev); + } else { + wacom->epen_blocked = WACOM_DISABLE; + if (wacom->fold_state == FOLD_STATUS_UNFOLDING) + sec_input_disable_device(wacom->plat_data->dev); + wacom->epen_blocked = WACOM_DISABLE; + } + + snprintf(buff, sizeof(buff), "OK"); + sec->cmd_state = SEC_CMD_STATUS_OK; + input_info(true, wacom->dev, "%s: %s\n", __func__, buff); + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec_cmd_set_cmd_exit(sec); + + return; + +err_out: + if (sec->cmd_param[0]) + wacom->epen_blocked = WACOM_ENABLE; + else + wacom->epen_blocked = WACOM_DISABLE; + snprintf(buff, sizeof(buff), "NG"); + sec->cmd_state = SEC_CMD_STATUS_FAIL; + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec_cmd_set_cmd_exit(sec); + + input_info(true, wacom->dev, "%s: Fail\n", __func__); + +} +#endif + +static void not_support_cmd(void *device_data) +{ + struct sec_cmd_data *sec = (struct sec_cmd_data *)device_data; + char buff[SEC_CMD_STR_LEN] = { 0 }; + + sec_cmd_set_default_result(sec); + snprintf(buff, sizeof(buff), "NA"); + + sec_cmd_set_cmd_result(sec, buff, strnlen(buff, sizeof(buff))); + sec->cmd_state = SEC_CMD_STATUS_NOT_APPLICABLE; + sec_cmd_set_cmd_exit(sec); +} + +static struct sec_cmd sec_cmds[] = { + {SEC_CMD_H("run_elec_test", run_elec_test),}, + {SEC_CMD_H("ready_elec_test", ready_elec_test),}, + {SEC_CMD_H("get_elec_test_ver", get_elec_test_ver),}, + {SEC_CMD("fw_update", fw_update),}, + {SEC_CMD("get_fw_ver_bin", get_fw_ver_bin),}, + {SEC_CMD("get_fw_ver_ic", get_fw_ver_ic),}, + {SEC_CMD("set_ic_reset", set_ic_reset),}, + {SEC_CMD("get_chip_name", get_chip_name),}, + {SEC_CMD("get_checksum", get_checksum),}, + {SEC_CMD("get_digitizer_connection", get_digitizer_connection),}, + {SEC_CMD("get_garage_connection", get_garage_connection),}, + {SEC_CMD_H("set_saving_mode", set_saving_mode),}, + {SEC_CMD("get_insert_status", get_insert_status),}, + {SEC_CMD_H("set_wcharging_mode", set_wcharging_mode),}, + {SEC_CMD("get_wcharging_mode", get_wcharging_mode),}, + {SEC_CMD_H("set_disable_mode", set_disable_mode),}, + {SEC_CMD("set_screen_off_memo", set_screen_off_memo),}, + {SEC_CMD("set_epen_aod_enable", set_epen_aod_enable),}, + {SEC_CMD("set_aod_lcd_onoff_status", set_aod_lcd_onoff_status),}, + {SEC_CMD("set_epen_aot_enable", set_epen_aot_enable),}, +#if WACOM_SEC_FACTORY + {SEC_CMD_H("set_fac_select_firmware", set_fac_select_firmware),}, + {SEC_CMD("set_fac_garage_mode", set_fac_garage_mode),}, + {SEC_CMD("get_fac_garage_mode", get_fac_garage_mode),}, + {SEC_CMD("get_fac_garage_rawdata", get_fac_garage_rawdata),}, +#endif + {SEC_CMD("debug", debug),}, + {SEC_CMD("set_cover_type", set_cover_type),}, + {SEC_CMD("refresh_rate_mode", set_display_mode),}, + {SEC_CMD("set_fold_state", set_fold_state),}, +#if !WACOM_SEC_FACTORY + {SEC_CMD_H("sec_wacom_device_enable", sec_wacom_device_enable),}, +#endif + {SEC_CMD("not_support_cmd", not_support_cmd),}, +}; + +static int wacom_sec_elec_init(struct wacom_data *wacom, int pos) +{ + struct wacom_elec_data *edata = NULL; + struct device_node *np = wacom->client->dev.of_node; + u32 tmp[2]; + int max_len; + int ret; + int i; + u8 tmp_name[30] = { 0 }; + + snprintf(tmp_name, sizeof(tmp_name), "wacom_elec%d", pos); + input_info(true, wacom->dev, "%s: init : %s\n", __func__, tmp_name); + + np = of_get_child_by_name(np, tmp_name); + if (!np) { + input_err(true, wacom->dev, "%s: node is not exist for elec\n", __func__); + return -EINVAL; + } + + edata = devm_kzalloc(wacom->dev, sizeof(*edata), GFP_KERNEL); + if (!edata) + return -ENOMEM; + + wacom->edatas[pos] = edata; + + ret = of_property_read_u32_array(np, "spec_ver", tmp, 2); + if (ret) { + input_err(true, wacom->dev, + "failed to read sepc ver %d\n", ret); + return -EINVAL; + } + edata->spec_ver[0] = tmp[0]; + edata->spec_ver[1] = tmp[1]; + + ret = of_property_read_u32_array(np, "max_channel", tmp, 2); + if (ret) { + input_err(true, wacom->dev, + "failed to read max channel %d\n", ret); + return -EINVAL; + } + edata->max_x_ch = tmp[0]; + edata->max_y_ch = tmp[1]; + + ret = of_property_read_u32(np, "shift_value", tmp); + if (ret) { + input_err(true, wacom->dev, + "failed to read max channel %d\n", ret); + return -EINVAL; + } + edata->shift_value = tmp[0]; + + input_info(true, wacom->dev, "channel(%d %d), spec_ver %d.%d shift_value %d\n", + edata->max_x_ch, edata->max_y_ch, edata->spec_ver[0], edata->spec_ver[1], edata->shift_value); + + max_len = edata->max_x_ch + edata->max_y_ch; + + edata->elec_data = devm_kzalloc(wacom->dev, + max_len * max_len * sizeof(u16), + GFP_KERNEL); + if (!edata->elec_data) + return -ENOMEM; + + edata->xx = devm_kzalloc(wacom->dev, edata->max_x_ch * sizeof(u16), GFP_KERNEL); + edata->xy = devm_kzalloc(wacom->dev, edata->max_x_ch * sizeof(u16), GFP_KERNEL); + edata->yx = devm_kzalloc(wacom->dev, edata->max_y_ch * sizeof(u16), GFP_KERNEL); + edata->yy = devm_kzalloc(wacom->dev, edata->max_y_ch * sizeof(u16), GFP_KERNEL); + if (!edata->xx || !edata->xy || !edata->yx || !edata->yy) + return -ENOMEM; + + edata->xx_self = devm_kzalloc(wacom->dev, edata->max_x_ch * sizeof(u16), GFP_KERNEL); + edata->yy_self = devm_kzalloc(wacom->dev, edata->max_y_ch * sizeof(u16), GFP_KERNEL); + edata->xy_edg = devm_kzalloc(wacom->dev, edata->max_x_ch * sizeof(u16), GFP_KERNEL); + edata->yx_edg = devm_kzalloc(wacom->dev, edata->max_y_ch * sizeof(u16), GFP_KERNEL); + if (!edata->xx_self || !edata->yy_self || !edata->xy_edg || !edata->yx_edg) + return -ENOMEM; + + edata->xx_xx = devm_kzalloc(wacom->dev, edata->max_x_ch * sizeof(long long), GFP_KERNEL); + edata->xy_xy = devm_kzalloc(wacom->dev, edata->max_x_ch * sizeof(long long), GFP_KERNEL); + edata->yx_yx = devm_kzalloc(wacom->dev, edata->max_y_ch * sizeof(long long), GFP_KERNEL); + edata->yy_yy = devm_kzalloc(wacom->dev, edata->max_y_ch * sizeof(long long), GFP_KERNEL); + if (!edata->xx_xx || !edata->xy_xy || !edata->yx_yx || !edata->yy_yy) + return -ENOMEM; + + edata->xy_xy_edg = devm_kzalloc(wacom->dev, edata->max_x_ch * sizeof(long long), GFP_KERNEL); + edata->yx_yx_edg = devm_kzalloc(wacom->dev, edata->max_y_ch * sizeof(long long), GFP_KERNEL); + if (!edata->xy_xy_edg || !edata->yx_yx_edg) + return -ENOMEM; + + edata->rxx = devm_kzalloc(wacom->dev, edata->max_x_ch * sizeof(long long), GFP_KERNEL); + edata->rxy = devm_kzalloc(wacom->dev, edata->max_x_ch * sizeof(long long), GFP_KERNEL); + edata->ryx = devm_kzalloc(wacom->dev, edata->max_y_ch * sizeof(long long), GFP_KERNEL); + edata->ryy = devm_kzalloc(wacom->dev, edata->max_y_ch * sizeof(long long), GFP_KERNEL); + if (!edata->rxx || !edata->rxy || !edata->ryx || !edata->ryy) + return -ENOMEM; + + edata->rxy_edg = devm_kzalloc(wacom->dev, edata->max_x_ch * sizeof(long long), GFP_KERNEL); + edata->ryx_edg = devm_kzalloc(wacom->dev, edata->max_y_ch * sizeof(long long), GFP_KERNEL); + if (!edata->rxy_edg || !edata->ryx_edg) + return -ENOMEM; + + edata->drxx = devm_kzalloc(wacom->dev, edata->max_x_ch * sizeof(long long), GFP_KERNEL); + edata->drxy = devm_kzalloc(wacom->dev, edata->max_x_ch * sizeof(long long), GFP_KERNEL); + edata->dryx = devm_kzalloc(wacom->dev, edata->max_y_ch * sizeof(long long), GFP_KERNEL); + edata->dryy = devm_kzalloc(wacom->dev, edata->max_y_ch * sizeof(long long), GFP_KERNEL); + if (!edata->drxx || !edata->drxy || !edata->dryx || !edata->dryy) + return -ENOMEM; + + edata->drxy_edg = devm_kzalloc(wacom->dev, edata->max_x_ch * sizeof(long long), GFP_KERNEL); + edata->dryx_edg = devm_kzalloc(wacom->dev, edata->max_y_ch * sizeof(long long), GFP_KERNEL); + if (!edata->drxy_edg || !edata->dryx_edg) + return -ENOMEM; + + edata->xx_ref = devm_kzalloc(wacom->dev, edata->max_x_ch * sizeof(long long), GFP_KERNEL); + edata->xy_ref = devm_kzalloc(wacom->dev, edata->max_x_ch * sizeof(long long), GFP_KERNEL); + edata->yx_ref = devm_kzalloc(wacom->dev, edata->max_y_ch * sizeof(long long), GFP_KERNEL); + edata->yy_ref = devm_kzalloc(wacom->dev, edata->max_y_ch * sizeof(long long), GFP_KERNEL); + edata->xy_edg_ref = devm_kzalloc(wacom->dev, edata->max_x_ch * sizeof(long long), GFP_KERNEL); + edata->yx_edg_ref = devm_kzalloc(wacom->dev, edata->max_y_ch * sizeof(long long), GFP_KERNEL); + if (!edata->xx_ref || !edata->xy_ref || !edata->yx_ref || !edata->yy_ref || !edata->xy_edg_ref || !edata->yx_edg_ref) + return -ENOMEM; + + ret = of_property_read_u64_array(np, "xx_ref", edata->xx_ref, edata->max_x_ch); + if (ret) { + input_err(true, wacom->dev, + "failed to read xx reference data %d\n", ret); + return -EINVAL; + } + + ret = of_property_read_u64_array(np, "xy_ref", edata->xy_ref, edata->max_x_ch); + if (ret) { + input_err(true, wacom->dev, + "failed to read xy reference data %d\n", ret); + return -EINVAL; + } + + ret = of_property_read_u64_array(np, "yx_ref", edata->yx_ref, edata->max_y_ch); + if (ret) { + input_err(true, wacom->dev, + "failed to read yx reference data %d\n", ret); + return -EINVAL; + } + + ret = of_property_read_u64_array(np, "yy_ref", edata->yy_ref, edata->max_y_ch); + if (ret) { + input_err(true, wacom->dev, + "failed to read yy reference data %d\n", ret); + return -EINVAL; + } + + ret = of_property_read_u64_array(np, "xy_ref_edg", edata->xy_edg_ref, edata->max_x_ch); + if (ret) { + input_err(true, wacom->dev, + "failed to read xy reference edg data %d\n", ret); + return -EINVAL; + } + + ret = of_property_read_u64_array(np, "yx_ref_edg", edata->yx_edg_ref, edata->max_y_ch); + if (ret) { + input_err(true, wacom->dev, + "failed to read yx reference edg data %d\n", ret); + return -EINVAL; + } + + edata->xx_spec = devm_kzalloc(wacom->dev, edata->max_x_ch * sizeof(long long), GFP_KERNEL); + edata->xy_spec = devm_kzalloc(wacom->dev, edata->max_x_ch * sizeof(long long), GFP_KERNEL); + edata->yx_spec = devm_kzalloc(wacom->dev, edata->max_y_ch * sizeof(long long), GFP_KERNEL); + edata->yy_spec = devm_kzalloc(wacom->dev, edata->max_y_ch * sizeof(long long), GFP_KERNEL); + if (!edata->xx_spec || !edata->xy_spec || !edata->yx_spec || !edata->yy_spec) + return -ENOMEM; + + edata->xx_self_spec = devm_kzalloc(wacom->dev, edata->max_x_ch * sizeof(long long), GFP_KERNEL); + edata->yy_self_spec = devm_kzalloc(wacom->dev, edata->max_y_ch * sizeof(long long), GFP_KERNEL); + if (!edata->xx_self_spec || !edata->yy_self_spec) + return -ENOMEM; + + ret = of_property_read_u64_array(np, "xx_spec", edata->xx_spec, edata->max_x_ch); + if (ret) { + input_err(true, wacom->dev, + "failed to read xx spec data %d\n", ret); + return -EINVAL; + } + + for (i = 0; i < edata->max_x_ch; i++) + edata->xx_spec[i] = edata->xx_spec[i] * POWER_OFFSET; + + ret = of_property_read_u64_array(np, "xy_spec", edata->xy_spec, edata->max_x_ch); + if (ret) { + input_err(true, wacom->dev, + "failed to read xy spec data %d\n", ret); + return -EINVAL; + } + + for (i = 0; i < edata->max_x_ch; i++) + edata->xy_spec[i] = edata->xy_spec[i] * POWER_OFFSET; + + ret = of_property_read_u64_array(np, "yx_spec", edata->yx_spec, edata->max_y_ch); + if (ret) { + input_err(true, wacom->dev, + "failed to read yx spec data %d\n", ret); + return -EINVAL; + } + + for (i = 0; i < edata->max_y_ch; i++) + edata->yx_spec[i] = edata->yx_spec[i] * POWER_OFFSET; + + ret = of_property_read_u64_array(np, "yy_spec", edata->yy_spec, edata->max_y_ch); + if (ret) { + input_err(true, wacom->dev, + "failed to read yy spec data %d\n", ret); + return -EINVAL; + } + + ret = of_property_read_u64_array(np, "xx_spec_self", edata->xx_self_spec, edata->max_x_ch); + if (ret) { + input_err(true, wacom->dev, + "failed to read xx self spec data %d\n", ret); + return -EINVAL; + } + + ret = of_property_read_u64_array(np, "yy_spec_self", edata->yy_self_spec, edata->max_y_ch); + if (ret) { + input_err(true, wacom->dev, + "failed to read yy self spec data %d\n", ret); + return -EINVAL; + } + + for (i = 0; i < edata->max_y_ch; i++) + edata->yy_spec[i] = edata->yy_spec[i] * POWER_OFFSET; + + edata->rxx_ref = devm_kzalloc(wacom->dev, edata->max_x_ch * sizeof(long long), GFP_KERNEL); + edata->rxy_ref = devm_kzalloc(wacom->dev, edata->max_x_ch * sizeof(long long), GFP_KERNEL); + edata->ryx_ref = devm_kzalloc(wacom->dev, edata->max_y_ch * sizeof(long long), GFP_KERNEL); + edata->ryy_ref = devm_kzalloc(wacom->dev, edata->max_y_ch * sizeof(long long), GFP_KERNEL); + if (!edata->rxx_ref || !edata->rxy_ref || !edata->ryx_ref || !edata->ryy_ref) + return -ENOMEM; + + ret = of_property_read_u64_array(np, "rxx_ref", edata->rxx_ref, edata->max_x_ch); + if (ret) { + input_err(true, wacom->dev, + "failed to read xx ratio reference data %d\n", ret); + return -EINVAL; + } + + for (i = 0; i < edata->max_x_ch; i++) + edata->rxx_ref[i] = edata->rxx_ref[i] * POWER_OFFSET / power(edata->shift_value); + + ret = of_property_read_u64_array(np, "rxy_ref", edata->rxy_ref, edata->max_x_ch); + if (ret) { + input_err(true, wacom->dev, + "failed to read xy ratio reference data %d\n", ret); + return -EINVAL; + } + + for (i = 0; i < edata->max_x_ch; i++) + edata->rxy_ref[i] = edata->rxy_ref[i] * POWER_OFFSET / power(edata->shift_value); + + ret = of_property_read_u64_array(np, "ryx_ref", edata->ryx_ref, edata->max_y_ch); + if (ret) { + input_err(true, wacom->dev, + "failed to read yx ratio reference data %d\n", ret); + return -EINVAL; + } + + for (i = 0; i < edata->max_y_ch; i++) + edata->ryx_ref[i] = edata->ryx_ref[i] * POWER_OFFSET / power(edata->shift_value); + + ret = of_property_read_u64_array(np, "ryy_ref", edata->ryy_ref, edata->max_y_ch); + if (ret) { + input_err(true, wacom->dev, + "failed to read yy ratio reference data %d\n", ret); + return -EINVAL; + } + + for (i = 0; i < edata->max_y_ch; i++) + edata->ryy_ref[i] = edata->ryy_ref[i] * POWER_OFFSET / power(edata->shift_value); + + edata->drxx_spec = devm_kzalloc(wacom->dev, edata->max_x_ch * sizeof(long long), GFP_KERNEL); + edata->drxy_spec = devm_kzalloc(wacom->dev, edata->max_x_ch * sizeof(long long), GFP_KERNEL); + edata->dryx_spec = devm_kzalloc(wacom->dev, edata->max_y_ch * sizeof(long long), GFP_KERNEL); + edata->dryy_spec = devm_kzalloc(wacom->dev, edata->max_y_ch * sizeof(long long), GFP_KERNEL); + if (!edata->drxx_spec || !edata->drxy_spec || !edata->dryx_spec || !edata->dryy_spec) + return -ENOMEM; + + edata->drxy_edg_spec = devm_kzalloc(wacom->dev, edata->max_x_ch * sizeof(long long), GFP_KERNEL); + edata->dryx_edg_spec = devm_kzalloc(wacom->dev, edata->max_y_ch * sizeof(long long), GFP_KERNEL); + if (!edata->drxy_edg_spec || !edata->dryx_edg_spec) + return -ENOMEM; + + + ret = of_property_read_u64_array(np, "drxx_spec", edata->drxx_spec, edata->max_x_ch); + if (ret) { + input_err(true, wacom->dev, + "failed to read xx difference ratio spec data %d\n", ret); + return -EINVAL; + } + + for (i = 0; i < edata->max_x_ch; i++) + edata->drxx_spec[i] = edata->drxx_spec[i] * POWER_OFFSET / power(edata->shift_value); + + ret = of_property_read_u64_array(np, "drxy_spec", edata->drxy_spec, edata->max_x_ch); + if (ret) { + input_err(true, wacom->dev, + "failed to read xy difference ratio spec data %d\n", ret); + return -EINVAL; + } + + for (i = 0; i < edata->max_x_ch; i++) + edata->drxy_spec[i] = edata->drxy_spec[i] * POWER_OFFSET / power(edata->shift_value); + + ret = of_property_read_u64_array(np, "dryx_spec", edata->dryx_spec, edata->max_y_ch); + if (ret) { + input_err(true, wacom->dev, + "failed to read yx difference ratio spec data %d\n", ret); + return -EINVAL; + } + + for (i = 0; i < edata->max_y_ch; i++) + edata->dryx_spec[i] = edata->dryx_spec[i] * POWER_OFFSET / power(edata->shift_value); + + ret = of_property_read_u64_array(np, "dryy_spec", edata->dryy_spec, edata->max_y_ch); + if (ret) { + input_err(true, wacom->dev, + "failed to read yy difference ratio spec data %d\n", ret); + return -EINVAL; + } + + for (i = 0; i < edata->max_y_ch; i++) + edata->dryy_spec[i] = edata->dryy_spec[i] * POWER_OFFSET / power(edata->shift_value); + + ret = of_property_read_u64_array(np, "drxy_spec_edg", edata->drxy_edg_spec, edata->max_x_ch); + if (ret) { + input_err(true, wacom->dev, + "failed to read xy difference ratio edg spec data %d\n", ret); + return -EINVAL; + } + + for (i = 0; i < edata->max_x_ch; i++) + edata->drxy_edg_spec[i] = edata->drxy_edg_spec[i] * POWER_OFFSET / power(edata->shift_value); + + ret = of_property_read_u64_array(np, "dryx_spec_edg", edata->dryx_edg_spec, edata->max_y_ch); + if (ret) { + input_err(true, wacom->dev, + "failed to read yx difference ratio edg spec data %d\n", ret); + return -EINVAL; + } + + for (i = 0; i < edata->max_y_ch; i++) + edata->dryx_edg_spec[i] = edata->dryx_edg_spec[i] * POWER_OFFSET / power(edata->shift_value); + + return 1; +} + +int wacom_sec_fn_init(struct wacom_data *wacom) +{ + int retval = 0; + int i = 0; + + retval = sec_cmd_init(&wacom->sec, wacom->dev, sec_cmds, ARRAY_SIZE(sec_cmds), + SEC_CLASS_DEVT_WACOM, &epen_attr_group); + if (retval < 0) { + input_err(true, wacom->dev, "failed to sec_cmd_init\n"); + return retval; + } + + for (i = EPEN_ELEC_DATA_MAIN ; i < EPEN_ELEC_DATA_MAX; i++) + wacom_sec_elec_init(wacom, i); + + return 0; + +} + +void wacom_sec_remove(struct wacom_data *wacom) +{ + sysfs_remove_link(&wacom->sec.fac_dev->kobj, "input"); + sec_cmd_exit(&wacom->sec, SEC_CLASS_DEVT_WACOM); +} diff --git a/drivers/input/sec_input/wacom/wez02_flash.c b/drivers/input/sec_input/wacom/wez02_flash.c new file mode 100755 index 000000000000..3dffc7895bab --- /dev/null +++ b/drivers/input/sec_input/wacom/wez02_flash.c @@ -0,0 +1,625 @@ +/* + * Wacom Penabled Driver for I2C + * + * Copyright (c) 2011-2014 Tatsunosuke Tobita, Wacom. + * + * + * 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 of 2 of the License, + * or (at your option) any later version. + */ + +#include "wez02_flash.h" +#include "wacom_dev.h" + +static bool wacom_set_feature(struct wacom_data *wacom, u8 report_id, + unsigned int buf_size, u8 *data, u16 cmdreg, + u16 datareg) +{ + int i, ret = -1; + int total = SFEATURE_SIZE + buf_size; + u8 *sFeature = NULL; + bool bRet = false; + + sFeature = kzalloc(sizeof(u8) * total, GFP_KERNEL); + if (!sFeature) { + input_err(true, wacom->dev, + "%s cannot preserve memory\n", __func__); + goto out; + } + + memset(sFeature, 0, sizeof(u8) * total); + + sFeature[0] = (u8)(cmdreg & 0x00ff); + sFeature[1] = (u8)((cmdreg & 0xff00) >> 8); + sFeature[2] = (RTYPE_FEATURE << 4) | report_id; + sFeature[3] = CMD_SET_FEATURE; + sFeature[4] = (u8)(datareg & 0x00ff); + sFeature[5] = (u8)((datareg & 0xff00) >> 8); + + if ((buf_size + 2) > 255) { + sFeature[6] = (u8)((buf_size + 2) & 0x00ff); + sFeature[7] = (u8)(((buf_size + 2) & 0xff00) >> 8); + } else { + sFeature[6] = (u8)(buf_size + 2); + sFeature[7] = (u8)(0x00); + } + + for (i = 0; i < buf_size; i++) + sFeature[i + SFEATURE_SIZE] = *(data + i); + + ret = wacom_send_boot(wacom, sFeature, total); + if (ret != total) { + input_err(true, wacom->dev, + "Sending Set_Feature failed sent bytes: %d\n", ret); + goto err; + } + + usleep_range(60, 61); + bRet = true; +err: + kfree(sFeature); + sFeature = NULL; + +out: + return bRet; +} + +static bool wacom_get_feature(struct wacom_data *wacom, u8 report_id, + unsigned int buf_size, u8 *data, u16 cmdreg, + u16 datareg, int delay) +{ + /*"+ 2", adding 2 more spaces for organizeing again later in the passed data, "data" */ + unsigned int total = buf_size + 2; + int ret = -1; + u8 *recv = NULL; + bool bRet = false; + u8 gFeature[] = { + (u8)(cmdreg & 0x00ff), + (u8)((cmdreg & 0xff00) >> 8), + (RTYPE_FEATURE << 4) | report_id, + CMD_GET_FEATURE, + (u8)(datareg & 0x00ff), + (u8)((datareg & 0xff00) >> 8) + }; + + recv = kzalloc(sizeof(u8) * total, GFP_KERNEL); + if (!recv) { + input_err(true, wacom->dev, + "%s cannot preserve memory\n", __func__); + goto err_alloc_memory; + } + + /*Append 2 bytes for length low and high of the byte */ + memset(recv, 0, sizeof(u8) * total); + + ret = wacom_send_boot(wacom, gFeature, GFEATURE_SIZE); + if (ret != GFEATURE_SIZE) { + input_info(true, wacom->dev, + "%s Sending Get_Feature failed; sent bytes: %d\n", + __func__, ret); + + udelay(delay); + + goto err_fail_i2c; + } + + udelay(delay); + + ret = wacom_recv_boot(wacom, recv, total); + if (ret != total) { + input_err(true, wacom->dev, + "%s Receiving data failed; recieved bytes: %d\n", + __func__, ret); + goto err_fail_i2c; + } + + /*Coppy data pointer, subtracting the first two bytes of the length */ + memcpy(data, (recv + 2), buf_size); + + bRet = true; +err_fail_i2c: + kfree(recv); + recv = NULL; + +err_alloc_memory: + return bRet; +} + +static int wacom_flash_cmd(struct wacom_data *wacom) +{ + u8 command[10] = { 0 }; + int len = 0; + int ret = -1; + + command[len++] = 0x0d; + command[len++] = FLASH_START0; + command[len++] = FLASH_START1; + command[len++] = FLASH_START2; + command[len++] = FLASH_START3; + command[len++] = FLASH_START4; + command[len++] = FLASH_START5; + command[len++] = 0x0d; + + ret = wacom_send_boot(wacom, command, len); + if (ret < 0) { + input_err(true, wacom->dev, + "Sending flash command failed\n"); + return -EXIT_FAIL; + } + + sec_delay(300); + + return 0; +} + +static int flash_query_w9020(struct wacom_data *wacom) +{ + bool bRet = false; + u8 command[BOOT_CMD_SIZE] = { 0 }; + u8 response[BOOT_RSP_SIZE] = { 0 }; + int ECH = 0, len = 0; + + command[len++] = BOOT_CMD_REPORT_ID; /* Report:ReportID */ + command[len++] = BOOT_QUERY; /* Report:Boot Query command */ + command[len++] = ECH = 7; /* Report:echo */ + + bRet = wacom_set_feature(wacom, REPORT_ID_1, len, command, + COMM_REG, DATA_REG); + if (!bRet) { + input_err(true, wacom->dev, + "%s failed to set feature\n", __func__); + return -EXIT_FAIL_SEND_QUERY_COMMAND; + } + + bRet = wacom_get_feature(wacom, REPORT_ID_2, BOOT_RSP_SIZE, response, + COMM_REG, DATA_REG, (10 * 1000)); + if (!bRet) { + input_err(true, wacom->dev, + "%s failed to get feature\n", __func__); + return -EXIT_FAIL_SEND_QUERY_COMMAND; + } + + if ((response[1] != BOOT_CMD_REPORT_ID) || (response[2] != ECH)) { + input_err(true, wacom->dev, "%s res1:%x res2:%x\n", + __func__, response[1], response[2]); + return -EXIT_FAIL_SEND_QUERY_COMMAND; + } + + if (response[3] != QUERY_RSP) { + input_err(true, wacom->dev, "%s res3:%x \n", + __func__, response[3]); + return -EXIT_FAIL_SEND_QUERY_COMMAND; + } + +/* + if ((response[3] != QUERY_CMD) || (response[4] != ECH)) { + input_err(true, wacom->dev, "%s res3:%x res4:%x\n", + __func__, response[3], response[4]); + return -EXIT_FAIL_SEND_QUERY_COMMAND; + } + + if (response[5] != QUERY_RSP) { + input_err(true, wacom->dev, "%s res5:%x\n", + __func__, response[5]); + return -EXIT_FAIL_SEND_QUERY_COMMAND; + } +*/ + input_info(true, wacom->dev, "QUERY SUCCEEDED\n"); + + return 0; +} + +static bool flash_blver_w9020(struct wacom_data *wacom, int *blver) +{ + bool bRet = false; + u8 command[BOOT_CMD_SIZE] = { 0 }; + u8 response[BOOT_RSP_SIZE] = { 0 }; + int ECH = 0, len = 0; + + command[len++] = BOOT_CMD_REPORT_ID; /* Report:ReportID */ + command[len++] = BOOT_BLVER; /* Report:Boot Version command */ + command[len++] = ECH = 7; /* Report:echo */ + + bRet = wacom_set_feature(wacom, REPORT_ID_1, len, command, COMM_REG, + DATA_REG); + if (!bRet) { + input_err(true, wacom->dev, + "%s failed to set feature1\n", __func__); + return false; + } + + bRet = wacom_get_feature(wacom, REPORT_ID_2, BOOT_RSP_SIZE, response, + COMM_REG, DATA_REG, (10 * 1000)); + if (!bRet) { + input_err(true, wacom->dev, + "%s 2 failed to set feature\n", __func__); + return false; + } + + if ((response[1] != BOOT_BLVER) || (response[2] != ECH)) { + input_err(true, wacom->dev, "%s res1:%x res2:%x\n", + __func__, response[1], response[2]); + return false; + } +/* + if ((response[3] != BOOT_CMD) || (response[4] != ECH)) { + input_err(true, wacom->dev, "%s res3:%x res4:%x\n", + __func__, response[3], response[4]); + return false; + } +*/ + *blver = (int)response[3]; + + return true; +} + +static bool flash_mputype_w9020(struct wacom_data *wacom, int *pMpuType) +{ + bool bRet = false; + u8 command[BOOT_CMD_SIZE] = { 0 }; + u8 response[BOOT_RSP_SIZE] = { 0 }; + int ECH = 0, len = 0; + + command[len++] = BOOT_CMD_REPORT_ID; /* Report:ReportID */ + command[len++] = BOOT_MPU; /* Report:Boot Query command */ + command[len++] = ECH = 7; /* Report:echo */ + + bRet = wacom_set_feature(wacom, REPORT_ID_1, len, command, COMM_REG, + DATA_REG); + if (!bRet) { + input_err(true, wacom->dev, + "%s failed to set feature\n", __func__); + return false; + } + + bRet = wacom_get_feature(wacom, REPORT_ID_2, BOOT_RSP_SIZE, response, + COMM_REG, DATA_REG, (10 * 1000)); + if (!bRet) { + input_err(true, wacom->dev, + "%s failed to get feature\n", __func__); + return false; + } + + if ((response[1] != BOOT_MPU) || (response[2] != ECH)) { + input_err(true, wacom->dev, "%s res1:%x res2:%x\n", + __func__, response[1], response[2]); + return false; + } +/* + if ((response[3] != MPU_CMD) || (response[4] != ECH)) { + input_err(true, wacom->dev, "%s res3:%x res4:%x\n", + __func__, response[3], response[4]); + return false; + } +*/ + *pMpuType = (int)response[3]; + return true; +} + +static bool flash_end_w9020(struct wacom_data *wacom) +{ + bool bRet = false; + u8 command[BOOT_CMD_SIZE] = { 0 }; + int len = 0; + + command[len++] = BOOT_CMD_REPORT_ID; + command[len++] = BOOT_EXIT; + command[len++] = 0; + + bRet = wacom_set_feature(wacom, REPORT_ID_1, len, command, COMM_REG, + DATA_REG); + if (!bRet) { + input_err(true, wacom->dev, + "%s failed to set feature 1\n", __func__); + return false; + } + + return true; +} + +static int check_progress(struct wacom_data *wacom, u8 *data, size_t size, u8 cmd, u8 ech) +{ + if (data[0] != cmd || data[1] != ech) { + input_err(true, wacom->dev, "%s failed to erase\n", + __func__); + return -EXIT_FAIL; + } + + switch (data[2]) { + case PROCESS_CHKSUM1_ERR: + case PROCESS_CHKSUM2_ERR: + case PROCESS_TIMEOUT_ERR: + input_err(true, wacom->dev, "%s error: %x\n", + __func__, data[2]); + return -EXIT_FAIL; + } + + return data[2]; +} + +static bool flash_erase_all(struct wacom_data *wacom) +{ + bool bRet = false; + u8 command[BOOT_CMD_SIZE] = { 0 }; + u8 response[BOOT_RSP_SIZE] = { 0 }; + int i = 0, len = 0; + int ECH = 0, sum = 0; + int ret = -1; + + command[len++] = 7; + command[len++] = ERS_ALL_CMD; + command[len++] = ECH = 2; + command[len++] = ERS_ECH2; + + /* Preliminarily stored data that cannnot appear here, but in wacom_set_feature() */ + sum += 0x05; + sum += 0x07; + for (i = 0; i < len; i++) + sum += command[i]; + + command[len++] = ~sum + 1; + + bRet = wacom_set_feature(wacom, REPORT_ID_1, len, command, COMM_REG, + DATA_REG); + if (!bRet) { + input_err(true, wacom->dev, + "%s failed to set feature\n", __func__); + return false; + } + + do { + bRet = wacom_get_feature(wacom, REPORT_ID_2, BOOT_RSP_SIZE, + response, COMM_REG, DATA_REG, 0); + if (!bRet) { + input_err(true, wacom->dev, + "%s failed to set feature\n", __func__); + return false; + } + + ret = check_progress(wacom, &response[1], (BOOT_RSP_SIZE - 3), ERS_ALL_CMD, ECH); + if (ret < 0) + return false; + + } while (ret == PROCESS_INPROGRESS); + + return true; +} + +static bool flash_write_block_w9020(struct wacom_data *wacom, char *flash_data, + unsigned long ulAddress, u8 *pcommand_id, + int *ECH) +{ + const int MAX_COM_SIZE = (8 + FLASH_BLOCK_SIZE + 2); /* 8: num of command[0] to command[7] */ + /* FLASH_BLOCK_SIZE: unit to erase the block */ + /* Num of Last 2 checksums */ + bool bRet = false; + u8 command[300] = { 0 }; + unsigned char sum = 0; + int i = 0; + + command[0] = BOOT_CMD_REPORT_ID; /* Report:ReportID */ + command[1] = BOOT_WRITE_FLASH; /* Report:program command */ + command[2] = *ECH = ++(*pcommand_id); /* Report:echo */ + command[3] = ulAddress & 0x000000ff; + command[4] = (ulAddress & 0x0000ff00) >> 8; + command[5] = (ulAddress & 0x00ff0000) >> 16; + command[6] = (ulAddress & 0xff000000) >> 24; /* Report:address(4bytes) */ + command[7] = 0x20; + + /*Preliminarily stored data that cannnot appear here, but in wacom_set_feature() */ + sum = (0x05 + 0x0c + 0x01); + + for (i = 0; i < 8; i++) + sum += command[i]; + + command[MAX_COM_SIZE - 2] = ~sum + 1; /* Report:command checksum */ + + sum = 0; + + for (i = 8; i < (FLASH_BLOCK_SIZE + 8); i++) { + command[i] = flash_data[ulAddress + (i - 8)]; + sum += flash_data[ulAddress + (i - 8)]; + } + + command[MAX_COM_SIZE - 1] = ~sum + 1; /* Report:data checksum */ + + /*Subtract 8 for the first 8 bytes */ + bRet = wacom_set_feature(wacom, REPORT_ID_1, (BOOT_CMD_SIZE + 4 - 8), + command, COMM_REG, DATA_REG); + if (!bRet) { + input_err(true, wacom->dev, + "%s failed to set feature\n", __func__); + return false; + } + + udelay(50); + + return true; +} + +static bool flash_write_w9020(struct wacom_data *wacom, + unsigned char *flash_data, + unsigned long start_address, + unsigned long *max_address) +{ + bool bRet = false; + u8 command_id = 0; + u8 response[BOOT_RSP_SIZE] = { 0 }; + int i = 0, j = 0, ECH = 0, ECH_len = 0; + int ECH_ARRAY[3] = { 0 }; + int ret = -1; + unsigned long ulAddress = 0; + + j = 0; + for (ulAddress = start_address; ulAddress < *max_address; ulAddress += FLASH_BLOCK_SIZE) { + for (i = 0; i < FLASH_BLOCK_SIZE; i++) { + if (flash_data[ulAddress + i] != 0xFF) + break; + } + if (i == (FLASH_BLOCK_SIZE)) + continue; + + bRet = flash_write_block_w9020(wacom, flash_data, ulAddress, + &command_id, &ECH); + if (!bRet) + return false; + + if (ECH_len == 3) + ECH_len = 0; + + ECH_ARRAY[ECH_len++] = ECH; + if (ECH_len == 3) { + for (j = 0; j < 3; j++) { + do { + bRet = wacom_get_feature(wacom, + REPORT_ID_2, + BOOT_RSP_SIZE, + response, + COMM_REG, + DATA_REG, 50); + if (!bRet) { + input_err(true, + wacom->dev, + "%s failed to set feature\n", + __func__); + return false; + } + + ret = check_progress(wacom, &response[1], (BOOT_RSP_SIZE - 3), 0x01, ECH_ARRAY[j]); + if (ret < 0) { + input_err(true, wacom->dev, + "%s mismatched echo array\n", + __func__); + return false; + } + } while (ret == PROCESS_INPROGRESS); + } + } + } + + return true; +} + +static int wacom_flash_w9020(struct wacom_data *wacom, unsigned char *fw_data) +{ + bool bRet = false; + int iBLVer = 0, iMpuType = 0; + unsigned long max_address; /* Max.address of Load data */ + unsigned long start_address; /* Start.address of Load data */ + + /*Obtain boot loader version */ + if (!flash_blver_w9020(wacom, &iBLVer)) { + input_err(true, wacom->dev, + "%s failed to get Boot Loader version\n", __func__); + return -EXIT_FAIL_GET_BOOT_LOADER_VERSION; + } + input_info(true, wacom->dev, "BL version: %x\n", iBLVer); + + /*Obtain MPUtype: this can be manually done in user space */ + if (!flash_mputype_w9020(wacom, &iMpuType)) { + input_err(true, wacom->dev, + "%s failed to get MPU type\n", __func__); + return -EXIT_FAIL_GET_MPU_TYPE; + } + + if (iMpuType == MPU_WEZ02) { + max_address = WEZ02_END_ADDR; + start_address = WEZ02_START_ADDR; + } else { + input_err(true, wacom->dev, "MPU is not matched : %x\n", iMpuType); + return -EXIT_FAIL_GET_MPU_TYPE; + } + + input_info(true, wacom->dev, "MPU type: %x\n", iMpuType); + + /*-----------------------------------*/ + /*Flashing operation starts from here */ + + /*Erase the current loaded program */ + input_info(true, wacom->dev, + "%s erasing the current firmware\n", __func__); + bRet = flash_erase_all(wacom); + if (!bRet) { + input_err(true, wacom->dev, + "%s failed to erase the user program\n", __func__); + return -EXIT_FAIL_ERASE; + } + + /*Write the new program */ + input_info(true, wacom->dev, "%s writing new firmware\n", + __func__); + bRet = flash_write_w9020(wacom, wacom->fw_data, start_address, + &max_address); + if (!bRet) { + input_err(true, wacom->dev, + "%s failed to write firmware\n", __func__); + return -EXIT_FAIL_WRITE_FIRMWARE; + } + + /*Return to the user mode */ + input_info(true, wacom->dev, "%s closing the boot mode\n", + __func__); + bRet = flash_end_w9020(wacom); + if (!bRet) { + input_err(true, wacom->dev, + "%s closing boot mode failed\n", __func__); + return -EXIT_FAIL_WRITING_MARK_NOT_SET; + } + + input_info(true, wacom->dev, + "%s write and verify completed\n", __func__); + + return EXIT_OK; +} + +int wacom_flash(struct wacom_data *wacom) +{ + int ret; + + if (wacom->fw_data == NULL) { + input_err(true, wacom->dev, + "epen:Data is NULL. Exit.\n"); + return -EINVAL; + } + + wacom_compulsory_flash_mode(wacom, true); + wacom_reset_hw(wacom); + + ret = wacom_flash_cmd(wacom); + if (ret < 0) { + input_err(true, wacom->dev, + "epen:%s cannot send flash command\n", __func__); + ret = -EXIT_FAIL; + goto out; + } + + ret = flash_query_w9020(wacom); + if (ret < 0) { + input_err(true, wacom->dev, + "epen:%s Error: cannot send query\n", __func__); + ret = -EXIT_FAIL; + goto out; + } + + ret = wacom_flash_w9020(wacom, wacom->fw_data); + if (ret < 0) { + input_err(true, wacom->dev, + "epen:%s Error: flash failed\n", __func__); + ret = -EXIT_FAIL; + goto out; + } + + sec_delay(200); + +out: + wacom->function_result &= ~EPEN_EVENT_SURVEY; + wacom->survey_mode = EPEN_SURVEY_MODE_NONE; + + wacom_compulsory_flash_mode(wacom, false); + wacom_reset_hw(wacom); + + return ret; +} diff --git a/drivers/input/sec_input/wacom/wez02_flash.h b/drivers/input/sec_input/wacom/wez02_flash.h new file mode 100755 index 000000000000..d083ebeb3868 --- /dev/null +++ b/drivers/input/sec_input/wacom/wez02_flash.h @@ -0,0 +1,124 @@ +#ifndef _WACOM_I2C_FLASH_H_ +#define _WACOM_I2C_FLASH_H_ + + +#define FLASH_START0 'f' +#define FLASH_START1 'l' +#define FLASH_START2 'a' +#define FLASH_START3 's' +#define FLASH_START4 'h' +#define FLASH_START5 '\r' +#define FLASH_ACK 0x06 + +//#define MPU_W9020 0x44 +//#define MPU_W9021 0x45 +//#define MPU_WEZ01 0x46 + +#define FLASH_BLOCK_SIZE 256 +#define DATA_SIZE (65536 * 5) +#define BLOCK_NUM 143 +#define W9020_START_ADDR 0x2000 +#define W9020_END_ADDR 0x23fff +#define W9021_START_ADDR 0x3000 +#define W9021_END_ADDR 0x3efff +#define WEZ02_START_ADDR 0x3000 +#define WEZ02_END_ADDR 0x2efff + +#define BOOT_CMD_SIZE (0x010c + 0x02) /* 78 */ +#define BOOT_RSP_SIZE 6 + +#define BOOT_WRITE_FLASH 1 +#define BOOT_EXIT 3 +#define BOOT_BLVER 4 +#define BOOT_MPU 5 +#define BOOT_QUERY 7 + +#define ERS_ALL_CMD 0x10 + +#define ERS_ECH2 0x03 +#define QUERY_RSP 0x06 + +#define PROCESS_INPROGRESS 0xff +#define PROCESS_COMPLETED 0x00 +#define PROCESS_CHKSUM1_ERR 0x81 +#define PROCESS_CHKSUM2_ERR 0x82 +#define PROCESS_TIMEOUT_ERR 0x87 + + + +/* + * exit codes + */ +#define EXIT_OK (0) +#define EXIT_REBOOT (1) +#define EXIT_FAIL (2) +#define EXIT_USAGE (3) +#define EXIT_NO_SUCH_FILE (4) +#define EXIT_NO_INTEL_HEX (5) +#define EXIT_FAIL_OPEN_COM_PORT (6) +#define EXIT_FAIL_ENTER_FLASH_MODE (7) +#define EXIT_FAIL_FLASH_QUERY (8) +#define EXIT_FAIL_BAUDRATE_CHANGE (9) +#define EXIT_FAIL_WRITE_FIRMWARE (10) +#define EXIT_FAIL_EXIT_FLASH_MODE (11) +#define EXIT_CANCEL_UPDATE (12) +#define EXIT_SUCCESS_UPDATE (13) +#define EXIT_FAIL_HID2SERIAL (14) +#define EXIT_FAIL_VERIFY_FIRMWARE (15) +#define EXIT_FAIL_MAKE_WRITING_MARK (16) +#define EXIT_FAIL_ERASE_WRITING_MARK (17) +#define EXIT_FAIL_READ_WRITING_MARK (18) +#define EXIT_EXIST_MARKING (19) +#define EXIT_FAIL_MISMATCHING (20) +#define EXIT_FAIL_ERASE (21) +#define EXIT_FAIL_GET_BOOT_LOADER_VERSION (22) +#define EXIT_FAIL_GET_MPU_TYPE (23) +#define EXIT_MISMATCH_BOOTLOADER (24) +#define EXIT_MISMATCH_MPUTYPE (25) +#define EXIT_FAIL_ERASE_BOOT (26) +#define EXIT_FAIL_WRITE_BOOTLOADER (27) +#define EXIT_FAIL_SWAP_BOOT (28) +#define EXIT_FAIL_WRITE_DATA (29) +#define EXIT_FAIL_GET_FIRMWARE_VERSION (30) +#define EXIT_FAIL_GET_UNIT_ID (31) +#define EXIT_FAIL_SEND_STOP_COMMAND (32) +#define EXIT_FAIL_SEND_QUERY_COMMAND (33) +#define EXIT_NOT_FILE_FOR_535 (34) +#define EXIT_NOT_FILE_FOR_514 (35) +#define EXIT_NOT_FILE_FOR_503 (36) +#define EXIT_MISMATCH_MPU_TYPE (37) +#define EXIT_NOT_FILE_FOR_515 (38) +#define EXIT_NOT_FILE_FOR_1024 (39) +#define EXIT_FAIL_VERIFY_WRITING_MARK (40) +#define EXIT_DEVICE_NOT_FOUND (41) +#define EXIT_FAIL_WRITING_MARK_NOT_SET (42) +#define EXIT_FAIL_SET_PDCT (43) +#define ERR_SET_PDCT (44) +#define ERR_GET_PDCT (45) + +#define HEX_READ_ERR (-1) + + +/*-----------------------------------*/ +/*-----------------------------------*/ +/*------ HID requiring items---------*/ +/*-----------------------------------*/ +/*-----------------------------------*/ +#define RTYPE_FEATURE 0x03 /* : Report type -> feature(11b) */ +#define CMD_GET_FEATURE 2 +#define CMD_SET_FEATURE 3 + +#define GFEATURE_SIZE 6 +#define SFEATURE_SIZE 8 + +/*HID specific register*/ +#define HID_DESC_REGISTER 1 +#define COMM_REG 0x04 +#define DATA_REG 0x05 + +#define REPORT_ID_1 0x07 +#define REPORT_ID_2 0x08 +#define FLASH_CMD_REPORT_ID 2 +#define BOOT_CMD_REPORT_ID 7 + +#endif /* _WACOM_I2C_FLASH_H_ */ diff --git a/drivers/kperfmon/Kconfig b/drivers/kperfmon/Kconfig new file mode 100644 index 000000000000..db55a37ee8c8 --- /dev/null +++ b/drivers/kperfmon/Kconfig @@ -0,0 +1,26 @@ +# +# Samsung Performance Logging system +# + +menu "samsung Performace manager" + +config KPERFMON + bool "Enable performance log" + default y + help + Samsung performance log(OLOG). + Say Y here if enable performance olog driver to do logging system resources. + When some delay occurs in the kernel, native or user user space, + the logging information should be restored into the system. + +config KPERFMON_BUILD + tristate "Building tyoe of performance log" + default y + help + Samsung performance log(OLOG). + This is to set a build type for module or build-in. + Say m here if you want a module of performance olog driver. + Say y here if you want build-in object of the performance olog driver. + +endmenu + diff --git a/drivers/kperfmon/Makefile b/drivers/kperfmon/Makefile new file mode 100644 index 000000000000..6d21e4def798 --- /dev/null +++ b/drivers/kperfmon/Makefile @@ -0,0 +1,51 @@ +# +# Makefile for the Linux kernel device drivers. +# +# Sep 2018, Binse Park +# Rewritten to use lists instead of if-statements. +# + +FLAG=1 + +ifneq ($(CONFIG_KPERFMON), y) +FLAG=0 +$(info kperfmon_DUMMY="CONFIG_KPERFMON is off.") +endif + +ifneq ($(shell [ -e $(srctree)/include/linux/olog.pb.h ] && echo exist), exist) +$(info kperfmon_DUMMY="olog.pb.h file is missing... retrying") + +$(info kperfmon_DUMMY="$(shell cp -f $(srctree)/../../frameworks/base/proto/src/olog.proto $(srctree)/drivers/kperfmon/)") +$(info kperfmon_DUMMY="$(shell cp -f $(srctree)/../../vendor/samsung/system/libperflog/aprotoc $(srctree)/drivers/kperfmon/)") +$(info kperfmon_DUMMY="$(shell cp -f $(srctree)/../../system/logging/libperflog/aprotoc $(srctree)/drivers/kperfmon/)") +$(info kperfmon_DUMMY="$(shell cp -f $(srctree)/../frameworks/base/proto/src/olog.proto $(srctree)/drivers/kperfmon/)") +$(info kperfmon_DUMMY="$(shell cp -f $(srctree)/../vendor/samsung/system/libperflog/aprotoc $(srctree)/drivers/kperfmon/)") +$(info kperfmon_DUMMY="$(shell chmod 777 $(srctree)/drivers/kperfmon/aprotoc)") +$(info kperfmon_DUMMY="$(shell $(srctree)/drivers/kperfmon/aprotoc --perflog_out=$(srctree)/drivers/kperfmon/ --proto_path=$(srctree)/drivers/kperfmon/ $(srctree)/drivers/kperfmon/olog.proto)") +$(info kperfmon_DUMMY="$(shell cp -f $(srctree)/drivers/kperfmon/olog.pb.h $(srctree)/include/linux/)") +#$(info kperfmon_DUMMY="$(shell ls $(srctree)/drivers/kperfmon/*)") +#$(info kperfmon_DUMMY="$(shell ls $(srctree)/include/linux/olog*)") + +ifneq ($(shell [ -e $(srctree)/include/linux/olog.pb.h ] && echo exist), exist) +$(info kperfmon_DUMMY="olog.pb.h file is missing... again") +FLAG=0 +endif +endif + +$(info kperfmon_DUMMY="$(shell cp -f $(srctree)/../../system/core/liblog/include/log/perflog.h $(srctree)/include/linux/)") +$(info kperfmon_DUMMY="$(shell cp -f $(srctree)/../../system/logging/liblog/include/log/perflog.h $(srctree)/include/linux/)") +$(info kperfmon_DUMMY="$(shell cp -f $(srctree)/../system/core/liblog/include/log/perflog.h $(srctree)/include/linux/)") + +ifneq ($(shell [ -e $(srctree)/drivers/kperfmon/perflog.h ] && echo exist), exist) +FLAG=0 +$(info kperfmon_DUMMY="perflog.h file is missing.") +endif + +ifeq ($(FLAG), 1) +$(info kperfmon_DUMMY="$(shell cp -f $(srctree)/drivers/kperfmon/ologk.h $(srctree)/include/linux/)") +ifeq ($(CONFIG_KPERFMON_BUILD), y) + obj-y += kperfmon.o +else + obj-m += kperfmon.o +endif +endif diff --git a/drivers/kperfmon/kperfmon.c b/drivers/kperfmon/kperfmon.c new file mode 100644 index 000000000000..33fc92a3f204 --- /dev/null +++ b/drivers/kperfmon/kperfmon.c @@ -0,0 +1,915 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Samsung's performance logging +// +// Copyright (c) 2017 Samsung Electronics Co., Ltd +// http://www.samsung.com +// +// Binse Park + +#define KPERFMON_KERNEL +#include +#undef KPERFMON_KERNEL + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) +#define KPERFMON_KERNEL +#include "perflog.h" +#undef KPERFMON_KERNEL +#else +#include "perflog.h" +#endif +#if !defined(KPERFMON_KMALLOC) +#include +#endif +#include +#include +#include +#include +#include + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) +#define rtc_time_to_tm(a, b) rtc_time64_to_tm(a, b) + +struct timeval { + time64_t tv_sec; + long tv_usec; +}; +#endif + + +#include "kperfmon.h" + +#define PROC_NAME "kperfmon" +#if defined(KPERFMON_KMALLOC) +#define BUFFER_SIZE (5 * 1024) +#else +#define MEM_SZ_4G 0x100000000 +#define BUFFER_SIZE_2M (2 * 1024 * 1024) +#define BUFFER_SIZE_5M (5 * 1024 * 1024) +#define IS_MEMORY_UNDER_4GB(x) (x <= (MEM_SZ_4G >> PAGE_SHIFT)) +#endif + +#define HEADER_SIZE PERFLOG_HEADER_SIZE +#define DEBUGGER_SIZE 32 +#define STREAM_SIZE (PERFLOG_BUFF_STR_MAX_SIZE + PERFLOG_HEADER_SIZE) +#define READ_BUFFER_SIZE (STREAM_SIZE + 100) + +#define MAX_DEPTH_OF_CALLSTACK 20 +#define MAX_MUTEX_RAWDATA 20 + +#define SIGNAL_35 35 +#define SIGNAL_OLOG 5209 + +#if defined(USE_MONITOR) +#define MAX_MUTEX_RAWDATA_DIGIT 2 +#define DIGIT_UNIT 100000000 +#endif + +#define KPERFMON_VERSION_LENGTH 100 + +struct tRingBuffer buffer = {0, }; +//const struct file_operations; + +struct t_before_print *before_list_cur_pos; +static LIST_HEAD(before_print_list); + +void CreateBuffer(struct tRingBuffer *buffer, + unsigned long length) +{ + if (buffer->data != 0) + return; + +#if defined(KPERFMON_KMALLOC) + buffer->data = kmalloc(length + 1, GFP_KERNEL); +#else + buffer->data = vmalloc(length + 1); +#endif + if (buffer->data == 0) { + pr_info("kperfmon error [%s] buffer->data is null!!!\n", + __func__); + return; + } + + buffer->length = length; + buffer->start = -1; + buffer->end = 0; + buffer->status = FLAG_NOTHING; + buffer->debugger = 0; + + memset(buffer->data, 0, length + 1); + + mutex_init(&buffer->mutex); +} + +void DestroyBuffer(struct tRingBuffer *buffer) +{ + if (buffer->data != 0) { +#if defined(KPERFMON_KMALLOC) + kfree(buffer->data); +#else + vfree(buffer->data); +#endif + buffer->data = 0; + } +} + +void WriteBuffer(struct tRingBuffer *buffer, + byte *data, + unsigned long length) +{ + long RemainSize = 0; + + if (length < 0) + return; + + if (buffer->length < buffer->end + length) { + long FirstSize = buffer->length - buffer->end; + + WriteBuffer(buffer, data, FirstSize); + WriteBuffer(buffer, data + FirstSize, length - FirstSize); + return; + } + + RemainSize = (buffer->start < buffer->end) ? + (buffer->length - buffer->end) : + (buffer->start - buffer->end); + + while (RemainSize < length) { + int bstart = (buffer->start + HEADER_SIZE - 1); + int cur_length = *(buffer->data + bstart % buffer->length); + + buffer->start += HEADER_SIZE + cur_length; + buffer->start %= buffer->length; + + RemainSize = (buffer->start < buffer->end) ? + (buffer->length - buffer->end) : + (buffer->start - buffer->end); + } + + memcpy(buffer->data + buffer->end, data, length); + //copy_from_user(buffer->data + buffer->end, data, length); + + buffer->end += length; + + if (buffer->start < 0) + buffer->start = 0; + + if (buffer->end >= buffer->length) + buffer->end = 0; + + if (buffer->status != FLAG_READING) + buffer->position = buffer->start; +} + +void ReadBuffer(struct tRingBuffer *buffer, + byte *data, + unsigned long *length) +{ + if (buffer->start < buffer->end) { + *length = buffer->end - buffer->start; + memcpy(data, buffer->data + buffer->start, *length); + //copy_to_user(data, (buffer->data + buffer->start), *length); + } else { + *length = buffer->length - buffer->start; + memcpy(data, buffer->data + buffer->start, *length); + memcpy(data + *length, buffer->data, buffer->end); + //copy_to_user(data, (buffer->data + buffer->start), *length); + //copy_to_user(data + *length, (buffer->data), buffer->end); + } +} + +void ReadBufferByPosition(struct tRingBuffer *buffer, + byte *data, + unsigned long *length, + unsigned long start, + unsigned long end) +{ + if (start < end) { + *length = end - start; + + if (*length >= PERFLOG_PACKET_SIZE) { + *length = 0; + return; + } + + memcpy(data, buffer->data + start, *length); + } else if (buffer->length > start) { + *length = buffer->length - start; + + if ((*length + end) >= PERFLOG_PACKET_SIZE) { + *length = 0; + return; + } + + memcpy(data, buffer->data + start, *length); + memcpy(data + *length, buffer->data, end); + } else { + *length = 0; + } +} + +void GetNext(struct tRingBuffer *buffer) +{ + int bstart = (buffer->position + HEADER_SIZE - 1); + int cur_length = *(buffer->data + bstart % buffer->length); + + buffer->position += HEADER_SIZE + cur_length; + buffer->position %= buffer->length; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) +static const struct proc_ops kperfmon_fops = { + .proc_open = kperfmon_open, + .proc_read = kperfmon_read, + .proc_write = kperfmon_write, +}; +#else +static const struct file_operations kperfmon_fops = { + .read = kperfmon_read, + .write = kperfmon_write, +}; +#endif + +void set_kperfmon_debugger_function(char *writebuffer) +{ + buffer.debugger = !buffer.debugger; + + pr_info("%s() - buffer.debugger : %d\n", + __func__, + (int)buffer.debugger); +} + +void process_version_function(char *writebuffer) +{ + struct t_before_print *pprinter = NULL; + int length = 0; + + if (writebuffer == NULL) + return; + + pprinter = kmalloc(sizeof(struct t_before_print), GFP_ATOMIC); + + if (pprinter == NULL) + return; + + length = strlen(writebuffer) + 1; + pprinter->pdata = kmalloc(length, GFP_ATOMIC); + + if (pprinter->pdata == NULL) { + kfree(pprinter); + return; + } + + strlcpy(pprinter->pdata, writebuffer, length); + + list_add_tail(&pprinter->list, &before_print_list); +} + +int ops_write_buffer(struct tRingBuffer *buffer, + byte *writebuffer, unsigned long length) +{ + unsigned long DataLength; + + if (buffer == NULL) + return length; + + if (writebuffer[HEADER_SIZE - 1] > PERFLOG_BUFF_STR_MAX_SIZE) + writebuffer[HEADER_SIZE - 1] = PERFLOG_BUFF_STR_MAX_SIZE; + + DataLength = writebuffer[HEADER_SIZE - 1] + HEADER_SIZE; + + mutex_lock(&buffer->mutex); + WriteBuffer(buffer, writebuffer, DataLength); + mutex_unlock(&buffer->mutex); +#if defined(KPERFMON_DEBUG) + { + int i; + + for (i = 0 ; i < 110 ; i++) { + pr_info("%s(buffer.data[%d] : %c\n", + __func__, + i, + buffer.data[i]); + } + } +#endif + return length; +} + +int ops_process_command(struct tRingBuffer *buffer, + byte *writebuffer, unsigned long length) +{ + int idx; + int max_commands = (int)(sizeof(commands) / sizeof(struct t_command)); + + for (idx = 0 ; idx < max_commands ; idx++) { + int cmd_length = strlen(commands[idx].command); + + if (cmd_length >= HEADER_SIZE + PERFLOG_BUFF_STR_MAX_SIZE) + continue; + + if (!strncmp(writebuffer, commands[idx].command, cmd_length) + && commands[idx].func != NULL + && strlen(writebuffer) > cmd_length) { + commands[idx].func(writebuffer); + return length; + } + } + + return length; +} + +int kperfmon_open(struct inode *finode, struct file *filp) +{ + return 0; +} + +ssize_t kperfmon_write(struct file *filp, + const char __user *data, + size_t length, + loff_t *loff_data) +{ + byte writebuffer[HEADER_SIZE + PERFLOG_BUFF_STR_MAX_SIZE + SH_IDX_PACKET + 1] = {0, }; + unsigned long DataLength = length; + int max_write_ops = (int)(sizeof(write_opts) / sizeof(void *)); + int type_of_data; + + if (!buffer.data) { + pr_info("%s() - Error buffer allocation is failed!!!\n", + __func__); + return length; + } + + if (length <= 0) { + pr_info("%s() - Error length : %d", __func__, (int) length); + return length; + } + + if (DataLength > (HEADER_SIZE + PERFLOG_BUFF_STR_MAX_SIZE + SH_IDX_PACKET)) + DataLength = HEADER_SIZE + PERFLOG_BUFF_STR_MAX_SIZE + SH_IDX_PACKET; + + if (copy_from_user(writebuffer, data, DataLength)) + return length; + + // [[[ This will be replaced with below code + type_of_data = writebuffer[SH_TYPE]; + + if (type_of_data < max_write_ops && type_of_data >= 0) + return write_opts[type_of_data](&buffer, + writebuffer + SH_IDX_PACKET, + DataLength - SH_IDX_PACKET); + // This will be replaced with below code ]]] + + type_of_data -= (int)'0'; + + if (type_of_data < max_write_ops && type_of_data >= 0) + return write_opts[type_of_data](&buffer, + writebuffer + SH_IDX_PACKET, + DataLength - SH_IDX_PACKET); + + return length; +} + +ssize_t kperfmon_read(struct file *filp, + char __user *data, + size_t count, + loff_t *loff_data) +{ + unsigned long length; + byte readbuffer[READ_BUFFER_SIZE] = {0, }; + union _uPLogPacket readlogpacket; + char timestamp[32] = {0, }; + + unsigned long start = 0; + unsigned long end = 0; + + if (!buffer.data) { + pr_info("%s() Error buffer allocation is failed!\n", __func__); + return 0; + } +#if defined(USE_MONITOR) + if (buffer.position == buffer.start) { + char mutex_log[PERFLOG_BUFF_STR_MAX_SIZE + 1] = {0, }; + int i, idx_mutex_log = 0; + + idx_mutex_log += snprintf((mutex_log + idx_mutex_log), + PERFLOG_BUFF_STR_MAX_SIZE - idx_mutex_log, + "mutex test "); + + for (i = 0; + i <= MAX_MUTEX_RAWDATA && + idx_mutex_log < (PERFLOG_BUFF_STR_MAX_SIZE - 20); + i++) { + + int digit, flag = 0; + + mutex_log[idx_mutex_log++] = '['; + idx_mutex_log += snprintf((mutex_log + idx_mutex_log), + PERFLOG_BUFF_STR_MAX_SIZE - idx_mutex_log, + "%d", + i); + mutex_log[idx_mutex_log++] = ']'; + mutex_log[idx_mutex_log++] = ':'; + //idx_mutex_log += snprintf((mutex_log + idx_mutex_log), + // PERFLOG_BUFF_STR_MAX_SIZE - idx_mutex_log, + // "%d", + // mutex_rawdata[i]); + //mutex_rawdata[i][1] = 99999999; + for (digit = (MAX_MUTEX_RAWDATA_DIGIT-1) ; digit >= 0 ; digit--) { + if (flag) { + idx_mutex_log += snprintf((mutex_log + idx_mutex_log), + PERFLOG_BUFF_STR_MAX_SIZE - idx_mutex_log, + "%08u", + mutex_rawdata[i][digit]); + } else { + if (mutex_rawdata[i][digit] > 0) { + idx_mutex_log += snprintf((mutex_log + idx_mutex_log), + PERFLOG_BUFF_STR_MAX_SIZE - idx_mutex_log, + "%u", + mutex_rawdata[i][digit]); + flag = 1; + } + } + } + + if (!flag) + mutex_log[idx_mutex_log++] = '0'; + + mutex_log[idx_mutex_log++] = ' '; + } + + _perflog(PERFLOG_EVT, PERFLOG_MUTEX, mutex_log); + } +#endif + buffer.status = FLAG_READING; + + mutex_lock(&buffer.mutex); + + if (buffer.position == buffer.start) { + + if (before_list_cur_pos != + list_last_entry(&before_print_list, typeof(*before_list_cur_pos), list)) { + before_list_cur_pos = list_next_entry(before_list_cur_pos, list); + + if (before_list_cur_pos != 0 && before_list_cur_pos->pdata != 0) { + int length = snprintf(readbuffer, + READ_BUFFER_SIZE, + "%s\n", + (char *) before_list_cur_pos->pdata); + + if (length <= 0 || copy_to_user(data, readbuffer, length)) { + pr_info("%s(copy_to_user(4) returned > 0)\n", __func__); + mutex_unlock(&buffer.mutex); + buffer.status = FLAG_NOTHING; + return 0; + } + + mutex_unlock(&buffer.mutex); + return length; + } + } + } + + if (buffer.position == buffer.end || buffer.start < 0) { + buffer.position = buffer.start; + mutex_unlock(&buffer.mutex); + buffer.status = FLAG_NOTHING; + before_list_cur_pos + = list_first_entry(&before_print_list, typeof(*before_list_cur_pos), list); + return 0; + } + + start = buffer.position; + GetNext(&buffer); + end = buffer.position; + + //printk("kperfmon_read(start : %d, end : %d)\n", (int)start, (int)end); + + if (start == end) { + buffer.position = buffer.start; + mutex_unlock(&buffer.mutex); + buffer.status = FLAG_NOTHING; + return 0; + } + + //ReadPacket.raw = &rawpacket; + ReadBufferByPosition(&buffer, readlogpacket.stream, &length, start, end); + mutex_unlock(&buffer.mutex); + //printk(KERN_INFO "kperfmon_read(length : %d)\n", (int)length); + //readlogpacket.stream[length++] = '\n'; + + if (length >= PERFLOG_PACKET_SIZE) { + length = PERFLOG_PACKET_SIZE - 1; + } else if (length == 0) { + return 0; + } + + readlogpacket.stream[length] = 0; + +#if NOT_USED + change2localtime(timestamp, readlogpacket.itemes.timestemp_sec); +#else + snprintf(timestamp, 32, "%02d-%02d %02d:%02d:%02d.%03d", + readlogpacket.itemes.timestamp.month, + readlogpacket.itemes.timestamp.day, + readlogpacket.itemes.timestamp.hour, + readlogpacket.itemes.timestamp.minute, + readlogpacket.itemes.timestamp.second, + readlogpacket.itemes.timestamp.msecond); + + if (readlogpacket.itemes.type >= OlogTestEnum_Type_maxnum + || readlogpacket.itemes.type < 0) { + readlogpacket.itemes.type = PERFLOG_LOG; + } + + if (readlogpacket.itemes.id >= OlogTestEnum_ID_maxnum + || readlogpacket.itemes.id < 0) { + readlogpacket.itemes.id = PERFLOG_UNKNOWN; + } + + length = snprintf(readbuffer, READ_BUFFER_SIZE, + "[%s %d %5d %5d (%3d)][%s][%s] %s\n", + timestamp, + readlogpacket.itemes.type, + readlogpacket.itemes.pid, + readlogpacket.itemes.tid, + readlogpacket.itemes.context_length, + OlogTestEnum_Type_strings[readlogpacket.itemes.type], + OlogTestEnum_ID_strings[readlogpacket.itemes.id], + readlogpacket.itemes.context_buffer); + + + if (length > count) + length = count; + + if (buffer.debugger && count > DEBUGGER_SIZE) { + char debugger[DEBUGGER_SIZE] = "______________________________"; + + snprintf(debugger, DEBUGGER_SIZE, "S:%010lu_E:%010lu_____", start, end); + + if (length + DEBUGGER_SIZE > count) + length = count - DEBUGGER_SIZE; + + if (copy_to_user(data, debugger, strnlen(debugger, DEBUGGER_SIZE))) { + pr_info("%s(copy_to_user(1) returned > 0)\n", __func__); + return 0; + } + + if (copy_to_user(data + DEBUGGER_SIZE, readbuffer, length)) { + pr_info("%s(copy_to_user(2) returned > 0)\n", __func__); + return 0; + } + + length += DEBUGGER_SIZE; + } else { + if (length <= 0 || copy_to_user(data, readbuffer, length)) { + pr_info("%s(copy_to_user(3) returned > 0)\n", __func__); + return 0; + } + } + + //printk(KERN_INFO "kperfmon_read(count : %d)\n", count); + + + return length; +#endif +} + +static int __init kperfmon_init(void) +{ + struct proc_dir_entry *entry; + // char kperfmon_version[KPERFMON_VERSION_LENGTH] = {0, }; + +#if defined(KPERFMON_KMALLOC) + CreateBuffer(&buffer, BUFFER_SIZE); +#else + char *context_buffer_size; + struct sysinfo si; + + /* getting the usable main memory size from sysinfo */ + si_meminfo(&si); + + if (IS_MEMORY_UNDER_4GB(si.totalram)) { + CreateBuffer(&buffer, BUFFER_SIZE_2M); + context_buffer_size = "kperfmon buffer size [2M]"; + } else { + CreateBuffer(&buffer, BUFFER_SIZE_5M); + context_buffer_size = "kperfmon buffer size [5M]"; + } +#endif + + if (!buffer.data) { + pr_info("%s() - Error buffer allocation is failed!!!\n", __func__); + return -ENOMEM; + } + + entry = proc_create(PROC_NAME, 0664, NULL, &kperfmon_fops); + + if (!entry) { + pr_info("%s() - Error creating entry in proc failed!!!\n", __func__); + DestroyBuffer(&buffer); + return -EBUSY; + } + + /*dbg_level_is_low = (sec_debug_level() == ANDROID_DEBUG_LEVEL_LOW);*/ + + INIT_LIST_HEAD(&before_print_list); + before_list_cur_pos = + list_first_entry(&before_print_list, typeof(*before_list_cur_pos), list); + process_version_function(" "); + // snprintf(kperfmon_version, KPERFMON_VERSION_LENGTH, "kperfmon_version [1.0.1] kperfmon_read : 0x%x, kperfmon_write : 0x%x", kperfmon_read, kperfmon_write); + // process_version_function(kperfmon_version); +#if !defined(KPERFMON_KMALLOC) + process_version_function(context_buffer_size); +#endif + + pr_info("%s()\n", __func__); + + return 0; +} + +static void __exit kperfmon_exit(void) +{ + DestroyBuffer(&buffer); + pr_info("%s()\n", __func__); +} + +#if defined(USE_WORKQUEUE) +static void ologk_workqueue_func(struct work_struct *work) +{ + struct t_ologk_work *workqueue = (struct t_ologk_work *)work; + + if (work) { + mutex_lock(&buffer.mutex); + WriteBuffer(&buffer, + workqueue->writelogpacket.stream, + PERFLOG_HEADER_SIZE + workqueue->writelogpacket.itemes.context_length); + mutex_unlock(&buffer.mutex); + + kfree((void *)work); + } +} +#endif + +//#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) +static inline void do_gettimeofday2(struct timeval *tv) +{ + struct timespec64 now; + + ktime_get_real_ts64(&now); + tv->tv_sec = now.tv_sec; + tv->tv_usec = now.tv_nsec/1000; +} +//#endif /* LINUX_VER >= 5.0 */ + +void _perflog(int type, int logid, const char *fmt, ...) +{ +#if !defined(USE_WORKQUEUE) + union _uPLogPacket writelogpacket; +#endif + struct rtc_time tm; + struct timeval time; + unsigned long local_time; +#if defined(USE_WORKQUEUE) + struct t_ologk_work *workqueue = 0; +#endif + va_list args; + + va_start(args, fmt); + + if (buffer.data == 0) { + va_end(args); + return; + } + +#if defined(USE_WORKQUEUE) + workqueue = kmalloc(sizeof(struct t_ologk_work), GFP_ATOMIC); + + if (workqueue) { + struct _PLogPacket *pitemes = &workqueue->writelogpacket.itemes; + + INIT_WORK((struct work_struct *)workqueue, ologk_workqueue_func); + + do_gettimeofday2(&time); + local_time = (u32)(time.tv_sec - (sys_tz.tz_minuteswest * 60)); + rtc_time_to_tm(local_time, &tm); + + //printk(" @ (%04d-%02d-%02d %02d:%02d:%02d)\n", + // tm.tm_year + 1900, + // tm.tm_mon + 1, + // tm.tm_mday, + // tm.tm_hour, + // tm.tm_min, + // tm.tm_sec); + + pitemes->timestamp.month = tm.tm_mon + 1; + pitemes->timestamp.day = tm.tm_mday; + pitemes->timestamp.hour = tm.tm_hour; + pitemes->timestamp.minute = tm.tm_min; + pitemes->timestamp.second = tm.tm_sec; + pitemes->timestamp.msecond = time.tv_usec / 1000; + pitemes->type = PERFLOG_LOG; + pitemes->id = logid; + pitemes->pid = current->pid;//getpid(); + pitemes->tid = 0;//gettid(); + pitemes->context_length = vscnprintf( + pitemes->context_buffer, + PERFLOG_BUFF_STR_MAX_SIZE, + fmt, + args); + + if (pitemes->context_length > PERFLOG_BUFF_STR_MAX_SIZE) + pitemes->context_length = PERFLOG_BUFF_STR_MAX_SIZE; + + schedule_work((struct work_struct *)workqueue); + + //{ + // struct timeval end_time; + // do_gettimeofday2(&end_time); + // printk("ologk() execution time with workqueue : %ld us ( %ld - %ld )\n", + // end_time.tv_usec - time.tv_usec, + // end_time.tv_usec, + // time.tv_usec); + //} + } else { + pr_info("%s : workqueue is not working\n", __func__); + } + +#else + do_gettimeofday2(&time); + local_time = (u32)(time.tv_sec - (sys_tz.tz_minuteswest * 60)); + rtc_time_to_tm(local_time, &tm); + + //printk(" @ (%04d-%02d-%02d %02d:%02d:%02d)\n", + // tm.tm_year + 1900, + // tm.tm_mon + 1, + // tm.tm_mday, + // tm.tm_hour, + // tm.tm_min, + // tm.tm_sec); + + writelogpacket.itemes.timestamp.month = tm.tm_mon + 1; + writelogpacket.itemes.timestamp.day = tm.tm_mday; + writelogpacket.itemes.timestamp.hour = tm.tm_hour; + writelogpacket.itemes.timestamp.minute = tm.tm_min; + writelogpacket.itemes.timestamp.second = tm.tm_sec; + writelogpacket.itemes.timestamp.msecond = time.tv_usec / 1000; + writelogpacket.itemes.type = type; + writelogpacket.itemes.pid = current->pid;//getpid(); + writelogpacket.itemes.tid = 0;//gettid(); + writelogpacket.itemes.context_length + = vscnprintf(writelogpacket.itemes.context_buffer, + PERFLOG_BUFF_STR_MAX_SIZE, + fmt, + args); + + if (writelogpacket.itemes.context_length > PERFLOG_BUFF_STR_MAX_SIZE) + writelogpacket.itemes.context_length = PERFLOG_BUFF_STR_MAX_SIZE; + + mutex_lock(&buffer.mutex); + WriteBuffer(&buffer, + writelogpacket.stream, + PERFLOG_HEADER_SIZE + writelogpacket.itemes.context_length); + mutex_unlock(&buffer.mutex); + + //{ + // struct timeval end_time; + // do_gettimeofday2(&end_time); + // printk(KERN_INFO "ologk() execution time : %ld us ( %ld - %ld )\n", + // end_time.tv_usec - time.tv_usec, + // end_time.tv_usec, time.tv_usec); + //} +#endif + + va_end(args); +} + +// void get_callstack(char *buffer, int max_size, int max_count) +// { +// struct stackframe frame; +// struct task_struct *tsk = current; +// //int len; + +// if (!try_get_task_stack(tsk)) +// return; + +// frame.fp = (unsigned long)__builtin_frame_address(0); +// frame.pc = (unsigned long)get_callstack; + +// #if defined(CONFIG_FUNCTION_GRAPH_TRACER) +// frame.graph = tsk->curr_ret_stack; +// #endif +// #if NOT_USED // temporary for GKI +// if (max_size > 0) { +// int count = 0; + +// max_count += 3; + +// do { +// if (count > 2) { +// int len = snprintf(buffer, max_size, " %pS", (void *)frame.pc); + +// max_size -= len; +// buffer += len; +// } +// count++; +// } while (!unwind_frame(tsk, &frame) && +// max_size > 0 && +// max_count > count); + +// put_task_stack(tsk); +// } +// #endif +// } + +void send_signal(void) +{ +#if NOT_USED // temporary for GKI + siginfo_t info; + + info.si_signo = SIGNAL_35; + info.si_errno = SIGNAL_OLOG; + info.si_code = SIGNAL_OLOG; + send_sig_info(SIGNAL_35, &info, current); +#endif +} + +void perflog_evt(int logid, int arg1) +{ +#if defined(USE_MONITOR) + struct timeval start_time; + struct timeval end_time; + + int digit = 0; + + do_gettimeofday2(&start_time); +#endif + if (arg1 < 0 || buffer.status != FLAG_NOTHING) + return; + + if (arg1 > MAX_MUTEX_RAWDATA) { + char log_buffer[PERFLOG_BUFF_STR_MAX_SIZE]; + int len; + u64 utime, stime; + + task_cputime(current, &utime, &stime); + + if (utime > 0) { + len = snprintf(log_buffer, + PERFLOG_BUFF_STR_MAX_SIZE, + "%d jiffies", + arg1); + // Make some stuck problems to be needed to check + // how many the mutex logging are occurred. + // Refer to P200523-00343, P200523-01815. + /*send_signal();*/ + + // get_callstack(log_buffer + len, + // PERFLOG_BUFF_STR_MAX_SIZE - len, + // /*(dbg_level_is_low ? 1 : 3)*/MAX_DEPTH_OF_CALLSTACK); + _perflog(PERFLOG_EVT, PERFLOG_MUTEX, log_buffer); + arg1 = MAX_MUTEX_RAWDATA; + + //do_gettimeofday2(&end_time); + //_perflog(PERFLOG_EVT, + // PERFLOG_MUTEX, + // "[MUTEX] processing time : %d", + // end_time.tv_usec - start_time.tv_usec); + } + } +#if defined(USE_MONITOR) + for (digit = 0 ; digit < MAX_MUTEX_RAWDATA_DIGIT ; digit++) { + mutex_rawdata[arg1][digit]++; + if (mutex_rawdata[arg1][digit] >= DIGIT_UNIT) + mutex_rawdata[arg1][digit] = 0; + else + break; + } +#endif +} + +//EXPORT_SYMBOL(ologk); +//EXPORT_SYMBOL(_perflog); +//EXPORT_SYMBOL(perflog_evt); + +module_init(kperfmon_init); +module_exit(kperfmon_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Binse Park "); +MODULE_DESCRIPTION("Performance Log(OLOG)"); + diff --git a/drivers/kperfmon/kperfmon.h b/drivers/kperfmon/kperfmon.h new file mode 100644 index 000000000000..8337c0fa0bce --- /dev/null +++ b/drivers/kperfmon/kperfmon.h @@ -0,0 +1,92 @@ +#define FLAG_NOTHING 0 +#define FLAG_READING 1 +#define USE_WORKQUEUE 1 +#define NOT_USED 0 + +#define byte unsigned char + +struct tRingBuffer { + byte *data; + long length; + long start; + long end; + long position; + + struct mutex mutex; + long debugger; + bool status; +}; + +#if defined(USE_WORKQUEUE) +struct t_ologk_work { + struct work_struct ologk_work; + union _uPLogPacket writelogpacket; +}; +#endif + +struct t_command { + char *command; + void (*func)(char *writebuffer); +}; + +#if defined(USE_MONITOR) +unsigned long mutex_rawdata[MAX_MUTEX_RAWDATA + 1][MAX_MUTEX_RAWDATA_DIGIT] = {{0, },}; +#endif + +int ops_write_buffer(struct tRingBuffer *buffer, + byte *data, unsigned long length); +int ops_process_command(struct tRingBuffer *buffer, + byte *data, unsigned long length); + +enum { + SH_TYPE_PACKET, + SH_TYPE_COMMAND, +}; + +enum { + SH_TYPE, + SH_IDX_PACKET +}; + +int (*write_opts[])(struct tRingBuffer *buffer, + byte *data, unsigned long length) + = { + ops_write_buffer, + ops_process_command, + }; + +void set_kperfmon_debugger_function(char *writebuffer); +void process_version_function(char *writebuffer); + +struct t_command commands[] = { + {"kperfmon_debugger", set_kperfmon_debugger_function}, + {"java_version", process_version_function}, + {"nativelib_version", process_version_function}, + {"perfmond_version", process_version_function}, +}; + +struct t_before_print { + void *pdata; + int (*func)(char *read_buffer); + struct list_head list; +}; + +void CreateBuffer(struct tRingBuffer *buffer, + unsigned long length); +void DestroyBuffer(struct tRingBuffer *buffer); +void WriteBuffer(struct tRingBuffer *buffer, + byte *data, unsigned long length); +void GetNext(struct tRingBuffer *buffer); +void ReadBuffer(struct tRingBuffer *buffer, + byte *data, + unsigned long *length); +int kperfmon_open(struct inode *, struct file *); +ssize_t kperfmon_write(struct file *filp, + const char __user *data, + size_t length, + loff_t *loff_data); +ssize_t kperfmon_read(struct file *filp, + char __user *data, + size_t count, + loff_t *loff_data); + diff --git a/drivers/kperfmon/olog.pb.h b/drivers/kperfmon/olog.pb.h new file mode 100644 index 000000000000..53ddbaca357d --- /dev/null +++ b/drivers/kperfmon/olog.pb.h @@ -0,0 +1,88 @@ +// Generated by the protocol buffer compiler for perflog!! DO NOT EDIT! +#ifndef _OLOG_PROTOCOL_BUFFER_H_ +#define _OLOG_PROTOCOL_BUFFER_H_ + +//EnumGenerator::GenerateDefinition in perflog_enum.cc +enum OlogTestEnum_Type { + PERFLOG_DEF = 0, + PERFLOG_LOG = 1, + PERFLOG_EVT = 2, + PERFLOG_WRN = 3, + PERFLOG_CRI = 4 +}; +#if defined(KPERFMON_KERNEL) +int OlogTestEnum_Type_maxnum = 5; +char * OlogTestEnum_Type_strings[5] = { + "DEF", + "LOG", + "EVT", + "WRN", + "CRI" +}; +#endif //KPERFMON_KERNEL +//EnumGenerator::GenerateDefinition in perflog_enum.cc +enum OlogTestEnum_ID { + PERFLOG_UNKNOWN = 0, + PERFLOG_LCDV = 2, + PERFLOG_ARGOS = 3, + PERFLOG_APPLAUNCH = 4, + PERFLOG_LOADAPK = 5, + PERFLOG_MAINLOOPER = 6, + PERFLOG_EXCESSIVECPUUSAGE = 7, + PERFLOG_ACTIVITYSLOW = 8, + PERFLOG_BROADCAST = 9, + PERFLOG_STORE = 10, + PERFLOG_CPUTOP = 11, + PERFLOG_LCD = 12, + PERFLOG_CPU = 13, + PERFLOG_LOCKCONTENTION = 14, + PERFLOG_CPUFREQ = 15, + PERFLOG_MEMPRESSURE = 16, + PERFLOG_INPUTD = 17, + PERFLOG_AMPSS = 18, + PERFLOG_SERVICEMANAGERSLOW = 19, + PERFLOG_IPCSTARVE = 20, + PERFLOG_SCREENSHOT = 21, + PERFLOG_MUTEX = 22, + PERFLOG_SYSTEMSERVER = 23, + PERFLOG_PERFETTOLOGGINGENABLED = 24, + PERFLOG_BIGDATA = 25, + PERFLOG_PSI = 26, + PERFLOG_JANK = 27 +}; +#if defined(KPERFMON_KERNEL) +int OlogTestEnum_ID_maxnum = 28; +char * OlogTestEnum_ID_strings[28] = { + "UNKNOWN", + " ", + "LCDV", + "ARGOS", + "APPLAUNCH", + "LOADAPK", + "MAINLOOPER", + "EXCESSIVECPUUSAGE", + "ACTIVITYSLOW", + "BROADCAST", + "STORE", + "CPUTOP", + "LCD", + "CPU", + "LOCKCONTENTION", + "CPUFREQ", + "MEMPRESSURE", + "INPUTD", + "AMPSS", + "SERVICEMANAGERSLOW", + "IPCSTARVE", + "SCREENSHOT", + "MUTEX", + "SYSTEMSERVER", + "PERFETTOLOGGINGENABLED", + "BIGDATA", + "PSI", + "JANK" +}; +#endif //KPERFMON_KERNEL + +#endif //_OLOG_PROTOCOL_BUFFER_H_ + diff --git a/drivers/kperfmon/ologk.c b/drivers/kperfmon/ologk.c new file mode 100644 index 000000000000..56de9dd8041f --- /dev/null +++ b/drivers/kperfmon/ologk.c @@ -0,0 +1,10 @@ +#include +#include +#include + +//void _perflog(int type, int logid, const char *fmt, ...) { +//} + +//void perflog_evt(int logid, int arg1) { +//} + diff --git a/drivers/kperfmon/ologk.h b/drivers/kperfmon/ologk.h new file mode 100644 index 000000000000..0af51d89eb10 --- /dev/null +++ b/drivers/kperfmon/ologk.h @@ -0,0 +1,15 @@ +#ifndef _OLOG_KERNEL_H_ +#define _OLOG_KERNEL_H_ + +#include +#include "olog.pb.h" + +#define OLOG_CPU_FREQ_FILTER 1500000 +#define PERFLOG_MUTEX_THRESHOLD 20 + +#define ologk(...) _perflog(PERFLOG_LOG, PERFLOG_UNKNOWN, __VA_ARGS__) +#define perflog(...) _perflog(PERFLOG_LOG, __VA_ARGS__) +extern void _perflog(int type, int logid, const char *fmt, ...); +extern void perflog_evt(int logid, int arg1); + +#endif diff --git a/drivers/kperfmon/perflog.h b/drivers/kperfmon/perflog.h new file mode 100644 index 000000000000..3f2425952e4c --- /dev/null +++ b/drivers/kperfmon/perflog.h @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Samsung's performance logging +// +// Copyright (c) 2014 Samsung Electronics Co., Ltd +// http://www.samsung.com + +#ifndef PERFLOG_H_ +#define PERFLOG_H_ + +#define PERFLOG_LOC __FILE__, __LINE__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "olog.pb.h" + +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint32; + +// extern FILE* perflog_fout; +#define PERFLOG_PACKET_SIZE 256 +#define PERFLOG_HEADER_SIZE 24 +#define PERFLOG_BUFF_STR_MAX_SIZE (PERFLOG_PACKET_SIZE - PERFLOG_HEADER_SIZE) +#define PERFLOG_BUFF_STR_MAX_SIZE_FOR_MULTILINE 4096 +#define PERFLOG_BUFF_STR_MAX_SIZE_FOR_EVTI PERFLOG_BUFF_STR_MAX_SIZE - PERFLOG_UINT16_SIZE +#define PERFLOG_UINT16_SIZE 2 +#define PERFLOG_INT_SIZE 4 + +/* PerfLog Phase 2 :: header format modification + should be changed to protobuff type +*/ +typedef enum PerfLogAffectTag { + AFFECT_K, + AFFECT_F, + AFFECT_A +}PerfLogAffect; + +typedef enum EvtNamingTag { + NAMING_LockC, + NAMING_AppLaunch, +}EvtNamingTag; + +typedef enum PerfLevelTag{ + LOW, + MID, + HIGH, + CRITICAL +}PerfLevelTag; + +// ############################################################################### +#pragma pack(push, 1) + +struct Payload { + int param1; + int param2; + char logbuffer[PERFLOG_BUFF_STR_MAX_SIZE + 1]; +}; + +struct LogPacket { +#if defined(KPERFMON_KERNEL) + struct timespec64 logtime; +#else + struct timespec logtime; +#endif + uint16 logtype; + uint16 logid; + uint16 pid; + uint16 tid; + struct Payload payload; +}; + +struct _Timestamp { + uint8 month; + uint8 day; + uint8 hour; + uint8 minute; + uint8 second; + uint16 msecond; +}; + +struct _PLogPacket { + struct _Timestamp timestamp; + uint16 pid; + uint16 tid; + + uint8 type; + uint8 id; + + char pname[10]; + uint8 context_length; + char context_buffer[PERFLOG_BUFF_STR_MAX_SIZE + 1]; +}; + +union _uPLogPacket { + struct _PLogPacket itemes; + char stream[PERFLOG_HEADER_SIZE + PERFLOG_BUFF_STR_MAX_SIZE]; +}; + +#pragma pack(pop) + +// Start API +int perflog_sending_log_via_socket(uint16 type, uint16 logid, int param1, int param2, char const *str); + +int perflog_write(char const * fmt, ...); + +int perflog_write_log(uint16 type, uint16 logid, char const * fmt, ...); + +int perflog_write_evt(uint16 maintype, uint16 logid, uint16 param1, char const * fmt, ...); + +// int perflog_getlog(char **buff); +// End API + +#ifdef __cplusplus +} +#endif /* #ifdef __cplusplus */ + +#endif diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index c4cff264331f..c5c72ea30602 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -939,3 +939,10 @@ comment "Simple LED drivers" source "drivers/leds/simple/Kconfig" endif # NEW_LEDS + +config LEDS_S2MPB02 + tristate "LED support for the S2MPB02" + def_tristate m + depends on LEDS_CLASS && MFD_S2MPB02 && REGULATOR_S2MPB02 + help + This option enables support for the LEDs on the S2MPB02. diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 6e05472cdcf5..59f37400d72a 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -115,3 +115,5 @@ obj-y += blink/ # Simple LED drivers obj-y += simple/ + +obj-$(CONFIG_LEDS_S2MPB02) += leds-s2mpb02.o diff --git a/drivers/leds/leds-qti-flash.c b/drivers/leds/leds-qti-flash.c index 9a979010f9b1..09a45a3e4e46 100644 --- a/drivers/leds/leds-qti-flash.c +++ b/drivers/leds/leds-qti-flash.c @@ -929,6 +929,41 @@ int qti_flash_led_set_param(struct led_trigger *trig, } EXPORT_SYMBOL(qti_flash_led_set_param); +#if IS_ENABLED(CONFIG_LEDS_QTI_FLASH) && (IS_ENABLED(CONFIG_SENSORS_STK6D2X) || IS_ENABLED(CONFIG_SENSORS_TSL2511)) +#define FLASH_LED_MULTI_STROBE_CTRL 0x67 +#define FLASH_LED_MULTI_STROBE_SEL BIT(0) + +int qti_flash_led_set_strobe_sel(struct led_trigger *trig, + int strobe_sel) +{ + struct led_classdev *led_cdev = trigger_to_lcdev(trig); + struct flash_switch_data *snode; + struct qti_flash_led *led; + int rc = 0, i; + + if (!led_cdev) { + pr_err("Invalid led_cdev in trigger %s\n", trig->name); + return -EINVAL; + } + + snode = container_of(led_cdev, struct flash_switch_data, cdev); + led = snode->led; + + for (i = 0; i < led->num_fnodes; i++) { + led->fnode[i].strobe_sel = strobe_sel; //0:SW_STROBE, 1:HW_STROBE + rc = qti_flash_led_masked_write(led, + FLASH_LED_STROBE_CTRL(led->fnode[i].id), FLASH_LED_HW_SW_STROBE_SEL, led->fnode[i].strobe_sel << FLASH_LED_STROBE_SEL_SHIFT); + if (rc < 0) + return rc; + } + qti_flash_led_masked_write(led, + FLASH_LED_MULTI_STROBE_CTRL, FLASH_LED_MULTI_STROBE_SEL, strobe_sel? 0 : 1); + + return 0; +} +EXPORT_SYMBOL(qti_flash_led_set_strobe_sel); +#endif + #define UCONV 1000000LL #define MCONV 1000LL #define VIN_FLASH_MIN_UV 3300000LL diff --git a/drivers/leds/leds-s2mpb02.c b/drivers/leds/leds-s2mpb02.c new file mode 100644 index 000000000000..89729896ba66 --- /dev/null +++ b/drivers/leds/leds-s2mpb02.c @@ -0,0 +1,858 @@ +/* + * LED driver for Samsung S2MPB02 + * + * Copyright (C) 2014 Samsung Electronics + * + * 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 driver is based on leds-max77804.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct device *s2mpb02_led_dev; +struct s2mpb02_led_data **global_led_datas; +bool sysfs_flash_op; +bool flash_config_factory; + +#ifdef S2MPB02_FLED_CHANNEL_1 +#define S2MPB02_REG_FLED_CTRL S2MPB02_REG_FLED_CTRL1 +#define S2MPB02_REG_FLED_CUR S2MPB02_REG_FLED_CUR1 +#define S2MPB02_REG_FLED_TIME S2MPB02_REG_FLED_TIME1 +#else +#define S2MPB02_REG_FLED_CTRL S2MPB02_REG_FLED_CTRL2 +#define S2MPB02_REG_FLED_CUR S2MPB02_REG_FLED_CUR2 +#define S2MPB02_REG_FLED_TIME S2MPB02_REG_FLED_TIME2 +#endif + +//#define DEBUG_READ_REGISTER //To dump register values with /sys/kernel/debug/s2mpb02-led-regs +//#define DEBUG_WRITE_REGISTER //To write register with sysfs + +struct s2mpb02_led_data { + struct led_classdev led; + struct s2mpb02_dev *s2mpb02; + struct s2mpb02_led *data; + struct i2c_client *i2c; + struct work_struct work; + struct mutex lock; + spinlock_t value_lock; + int brightness; +}; + +static u8 leds_mask[S2MPB02_LED_MAX] = { + S2MPB02_FLASH_MASK, + S2MPB02_TORCH_MASK, +}; + +static u8 leds_shift[S2MPB02_LED_MAX] = { + 4, + 0, +}; + + +#if defined(DEBUG_READ_REGISTER) +#if 0 +static void print_all_reg_value(struct i2c_client *client) +{ + int ret; + u8 value; + u8 i; + + for (i = 0; i <= S2MPB02_REG_LDO_DSCH3; i++) { + ret = s2mpb02_read_reg(client, i, &value); + if (unlikely(ret < 0)) + pr_err("[s2mpb02-LED][%s] read failed", __func__); + pr_err("[s2mpb02-LED] register(%x) = %x\n", i, value); + value = 0; + } +} +#endif +static int s2mpb02_debugfs_show(struct seq_file *s, void *data) +{ + struct s2mpb02_dev *ldata = s->private; + u8 reg; + u8 reg_data; + int ret; + + seq_printf(s, "s2mpb02 IF PMIC :\n"); + seq_printf(s, "=============\n"); + for (reg = 0; reg <= S2MPB02_REG_LDO_DSCH3; reg++) { + ret = s2mpb02_read_reg(ldata->i2c, reg, ®_data); + if (unlikely(ret < 0)) { + pr_err("[s2mpb02-LED][%s] read failed", __func__); + } + seq_printf(s, "0x%02x:\t0x%02x\n", reg, reg_data); + } + //print_all_reg_value(ldata->i2c); + seq_printf(s, "\n"); + + return 0; +} + +static int s2mpb02_debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, s2mpb02_debugfs_show, inode->i_private); +} + +static const struct file_operations s2mpb02_debugfs_fops = { + .open = s2mpb02_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; +#endif + +static int s2mpb02_set_bits(struct i2c_client *client, const u8 reg, + const u8 mask, const u8 inval) +{ + int ret; + u8 value; + + ret = s2mpb02_read_reg(client, reg, &value); + if (unlikely(ret < 0)) + return ret; + + value = (value & ~mask) | (inval & mask); + + ret = s2mpb02_write_reg(client, reg, value); + + return ret; +} + +static int s2mpb02_led_get_en_value(struct s2mpb02_led_data *led_data, int on) +{ + if (on) { + if (led_data->data->id == S2MPB02_FLASH_LED_1) + return ((S2MPB02_FLED_ENABLE << S2MPB02_FLED_ENABLE_SHIFT) | + (S2MPB02_FLED_FLASH_MODE << S2MPB02_FLED_MODE_SHIFT)); + /* Turn on FLASH by I2C */ + else + return ((S2MPB02_FLED_ENABLE << S2MPB02_FLED_ENABLE_SHIFT) | + (S2MPB02_FLED_TORCH_MODE << S2MPB02_FLED_MODE_SHIFT)); + /* Turn on TORCH by I2C */ + } else + return (S2MPB02_FLED_DISABLE << S2MPB02_FLED_ENABLE_SHIFT); + /* controlled by GPIO */ +} + +static void s2mpb02_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + unsigned long flags; + struct s2mpb02_led_data *led_data + = container_of(led_cdev, struct s2mpb02_led_data, led); + + pr_debug("[LED] %s\n", __func__); + + spin_lock_irqsave(&led_data->value_lock, flags); + led_data->data->brightness = min_t(int, (int)value, (int)led_cdev->max_brightness); + spin_unlock_irqrestore(&led_data->value_lock, flags); + + schedule_work(&led_data->work); +} + +static void led_set(struct s2mpb02_led_data *led_data, enum s2mpb02_led_turn_way turn_way) +{ + int ret; + struct s2mpb02_led *data = led_data->data; + int id = data->id; + int value = 0; + + if (turn_way == S2MPB02_LED_TURN_WAY_GPIO) { + /* First turn off LED turned on by I2C */ + value = s2mpb02_led_get_en_value(led_data, 0); + ret = s2mpb02_set_bits(led_data->i2c, + S2MPB02_REG_FLED_CTRL1, S2MPB02_FLED_ENABLE_MODE_MASK, value); + if (unlikely(ret)) + goto error_set_bits; + + /* Turn way LED by GPIO */ + /* set current */ + ret = s2mpb02_set_bits(led_data->i2c, S2MPB02_REG_FLED_CUR1, + leds_mask[id], data->brightness << leds_shift[id]); + if (unlikely(ret)) + goto error_set_bits; + + value = led_data->data->brightness ? 1 : 0; + gpio_request(led_data->data->gpio, NULL); + gpio_direction_output(led_data->data->gpio, value); + gpio_free(led_data->data->gpio); + } else { + if (led_data->data->brightness == LED_OFF) { + value = s2mpb02_led_get_en_value(led_data, 0); + ret = s2mpb02_set_bits(led_data->i2c, + S2MPB02_REG_FLED_CTRL1, S2MPB02_FLED_ENABLE_MODE_MASK, value); + if (unlikely(ret)) + goto error_set_bits; + } + /* set current */ + ret = s2mpb02_set_bits(led_data->i2c, S2MPB02_REG_FLED_CUR1, + leds_mask[id], data->brightness << leds_shift[id]); + if (unlikely(ret)) + goto error_set_bits; + + if (led_data->data->brightness != LED_OFF) { + /* Turn on LED by I2C */ + value = s2mpb02_led_get_en_value(led_data, 1); + ret = s2mpb02_set_bits(led_data->i2c, + S2MPB02_REG_FLED_CTRL1, S2MPB02_FLED_ENABLE_MODE_MASK, value); + if (unlikely(ret)) + goto error_set_bits; + } + } + + return; + +error_set_bits: + pr_err("%s: can't set led level %d\n", __func__, ret); + + return; +} + +static void s2mpb02_led_work(struct work_struct *work) +{ + struct s2mpb02_led_data *led_data + = container_of(work, struct s2mpb02_led_data, work); + + pr_debug("[LED] %s\n", __func__); + + if (sysfs_flash_op) { + pr_warn("%s : The camera led control is not allowed" + "because sysfs led control already used it\n", __FUNCTION__); + return; + } + + mutex_lock(&led_data->lock); + led_set(led_data, S2MPB02_LED_TURN_WAY_I2C); + mutex_unlock(&led_data->lock); +} + +static int s2mpb02_led_setup(struct s2mpb02_led_data *led_data) +{ + int ret = 0; + struct s2mpb02_led *data = led_data->data; + int id = data->id; + int value; + + /* Disable Low Voltage operating mode control */ + ret |= s2mpb02_update_reg(led_data->i2c, S2MPB02_REG_FLED_CTRL1, + S2MPB02_FLED_CTRL1_LV_DISABLE, S2MPB02_FLED_CTRL1_LV_EN_MASK); + + /* set operating minimum voltage */ + ret |= s2mpb02_update_reg(led_data->i2c, S2MPB02_REG_FLED_CTRL1, + S2MPB02_LV_SEL_VOLT(3000), S2MPB02_LV_SEL_VOUT_MASK); + + /* set current & timeout */ + ret |= s2mpb02_update_reg(led_data->i2c, S2MPB02_REG_FLED_CUR1, + data->brightness << leds_shift[id], leds_mask[id]); + ret |= s2mpb02_update_reg(led_data->i2c, S2MPB02_REG_FLED_TIME1, + data->timeout << leds_shift[id], leds_mask[id]); + + value = s2mpb02_led_get_en_value(led_data, 0); + ret |= s2mpb02_update_reg(led_data->i2c, + S2MPB02_REG_FLED_CTRL1, value, S2MPB02_FLED_ENABLE_MODE_MASK); + +#if defined(CONFIG_SAMSUNG_SECURE_CAMERA) + ret |= s2mpb02_ir_led_init(); +#endif + + return ret; +} + +void s2mpb02_led_get_status(struct led_classdev *led_cdev, bool status, bool onoff) +{ + int ret = 0; + u8 value[6] = {0, }; + struct s2mpb02_led_data *led_data + = container_of(led_cdev, struct s2mpb02_led_data, led); + + ret = s2mpb02_read_reg(led_data->i2c, 0x12, &value[0]); //Fled_ctrl1 + ret |= s2mpb02_read_reg(led_data->i2c, 0x13, &value[1]); //Fled_ctrl2 + ret |= s2mpb02_read_reg(led_data->i2c, 0x14, &value[2]); //Fled_cur1 + ret |= s2mpb02_read_reg(led_data->i2c, 0x15, &value[3]); //Fled_time1 + ret |= s2mpb02_read_reg(led_data->i2c, 0x16, &value[4]); //Fled_cur2 + ret |= s2mpb02_read_reg(led_data->i2c, 0x17, &value[5]); //Fled_time2 + if (unlikely(ret < 0)) { + printk("%s : error to get dt node\n", __func__); + } + + printk("%s[%d, %d] : Fled_ctrl1 = 0x%12x, Fled_ctrl2 = 0x%13x, Fled_cur1 = 0x%14x, " + "Fled_time1 = 0x%15x, Fled_cur2 = 0x%16x, Fled_time2 = 0x%17x\n", + __func__, status, onoff, value[0], value[1], value[2], value[3], value[4], value[5]); +} + +int s2mpb02_led_en(int mode, int onoff, enum s2mpb02_led_turn_way turn_way) +{ + int ret = 0; + int i = 0; + + if (global_led_datas == NULL) { + pr_err("<%s> global_led_datas is NULL\n", __func__); + return -1; + } + + for (i = 0; i < S2MPB02_LED_MAX; i++) { + if (global_led_datas[i] == NULL) { + pr_err("<%s> global_led_datas[%d] is NULL\n", __func__, i); + return -1; + } + } + + if (onoff > 0) {/* enable */ + pr_info("<%s> enable %d, %d\n", __func__, onoff, mode); + if (mode == S2MPB02_TORCH_LED_1) { + if (onoff >= S2MPB02_TORCH_OUT_I_MAX) + onoff = S2MPB02_TORCH_OUT_I_MAX-1; + } else if (mode == S2MPB02_FLASH_LED_1) { + if (onoff >= S2MPB02_FLASH_OUT_I_MAX) + onoff = S2MPB02_FLASH_OUT_I_MAX-1; + } else { + pr_err("<%s> mode %d is invalid\n", __func__, mode); + return -1; + } + global_led_datas[mode]->data->brightness = onoff; + } else {/* disable */ + pr_info("<%s> disable %d, %d\n", __func__, onoff, mode); + global_led_datas[mode]->data->brightness = 0; + } + + led_set(global_led_datas[mode], turn_way); + + return ret; +} +EXPORT_SYMBOL(s2mpb02_led_en); + +#ifdef DEBUG_WRITE_REGISTER +ssize_t s2mpb02_write(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + unsigned int value = 0; + unsigned char reg = 0; + unsigned char data = 0; + + if (buf == NULL || kstrtouint(buf, 16, &value)) { + pr_err("[%s] error buf is NULL\n", __func__); + return -1; + } + + reg = value >> 8; + data = value & 0xFF; + pr_info("[%s] reg: %x, data: %x\n", __func__, reg, data); + + s2mpb02_write_reg(global_led_datas[0]->i2c, reg, data); + + return count; +} +static DEVICE_ATTR(s2mpb02_cam_reg, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH, + NULL, s2mpb02_write); +#endif + +#if defined(CONFIG_SAMSUNG_SECURE_CAMERA) +int s2mpb02_ir_led_init(void) +{ + int ret = 0; + + ret |= s2mpb02_write_reg(global_led_datas[0]->i2c, S2MPB02_REG_FLED_CTRL2, 0x38); + ret |= s2mpb02_write_reg(global_led_datas[0]->i2c, S2MPB02_REG_FLED_CUR2, 0xAF); + + ret |= s2mpb02_write_reg(global_led_datas[0]->i2c, S2MPB02_REG_FLED_TIME2, 0x34); + ret |= s2mpb02_write_reg(global_led_datas[0]->i2c, S2MPB02_REG_FLED_TIME2, 0x35); + + ret |= s2mpb02_write_reg(global_led_datas[0]->i2c, S2MPB02_REG_FLED_IRON1, 0x19); + ret |= s2mpb02_write_reg(global_led_datas[0]->i2c, S2MPB02_REG_FLED_IRON2, 0x0B); + ret |= s2mpb02_write_reg(global_led_datas[0]->i2c, S2MPB02_REG_FLED_IRD1, 0x00); + ret |= s2mpb02_write_reg(global_led_datas[0]->i2c, S2MPB02_REG_FLED_IRD2, 0X2C); + +#if 1 //TEMP_845 + s2mpb02_ir_led_current(5); + s2mpb02_ir_led_pulse_width(240); + s2mpb02_ir_led_pulse_delay(0); + s2mpb02_ir_led_max_time(0); +#endif + + return ret; +} +EXPORT_SYMBOL(s2mpb02_ir_led_init); + +int s2mpb02_ir_led_current(int32_t current_value) +{ + int ret = 0; + unsigned int value = 0; + unsigned char data = 0; + + if (current_value > 0) + value = current_value - 1; + + pr_info("[%s] led current value : %u [current_value::%d]\n", __func__, value, current_value); + + data = ((value & 0x0F) << 4) | 0x0F; + + ret = s2mpb02_write_reg(global_led_datas[0]->i2c, S2MPB02_REG_FLED_CUR2, data); + if (ret < 0) + pr_err("[%s] i2c write error", __func__); + + return ret; +} +EXPORT_SYMBOL(s2mpb02_ir_led_current); + +int s2mpb02_ir_led_pulse_width(int32_t width) +{ + unsigned int value = width; + unsigned char iron1 = 0; + unsigned char iron2 = 0; + int ret = 0; + + pr_info("[%s] led pulse_width value : %u\n", __func__, value); + + iron1 = (value >> 2) & 0xFF; + iron2 = ((value & 0x03) << 6) | 0x0B; + + pr_info("[%s] IRON1(0x%02x), IRON2(0x%02x)\n", __func__, iron1, iron2); + + /* set 0x18, 0x19 */ + ret |= s2mpb02_write_reg(global_led_datas[0]->i2c, S2MPB02_REG_FLED_IRON1, iron1); + ret |= s2mpb02_write_reg(global_led_datas[0]->i2c, S2MPB02_REG_FLED_IRON2, iron2); + if (ret < 0) + pr_err("[%s] i2c write error", __func__); + + return ret; + +} +EXPORT_SYMBOL(s2mpb02_ir_led_pulse_width); + +int s2mpb02_ir_led_pulse_delay(int32_t delay) +{ + unsigned int value = delay; + unsigned char ird1 = 0; + unsigned char ird2 = 0; + int ret = 0; + + pr_info("[%s] led pulse_delay value : %u\n", __func__, value); + + ird1 = (value >> 2) & 0xFF; + ird2 = ((value & 0x03) << 6) | 0x2C; /* value 0x2C means RSVD[5:0] Reserved */ + + pr_info("[%s] IRD1(0x%02x), IRD2(0x%02x)\n", __func__, ird1, ird2); + + /* set 0x18, 0x19 */ + ret |= s2mpb02_write_reg(global_led_datas[0]->i2c, S2MPB02_REG_FLED_IRD1, ird1); + ret |= s2mpb02_write_reg(global_led_datas[0]->i2c, S2MPB02_REG_FLED_IRD2, ird2); + if (ret < 0) + pr_err("[%s] i2c write error", __func__); + + return ret; +} +EXPORT_SYMBOL(s2mpb02_ir_led_pulse_delay); + +int s2mpb02_ir_led_max_time(int32_t max_time) +{ + int ret = 0; + + pr_info("[%s] led max_time value : %u\n", __func__, max_time); + + ret |= s2mpb02_set_bits(global_led_datas[0]->i2c, S2MPB02_REG_FLED_CTRL2, + S2MPB02_FLED2_MAX_TIME_CLEAR_MASK, 0x00); + ret |= s2mpb02_set_bits(global_led_datas[0]->i2c, S2MPB02_REG_FLED_TIME2, + S2MPB02_FLED2_MAX_TIME_EN_MASK, 0x00); + if (max_time > 0) { + ret |= s2mpb02_set_bits(global_led_datas[0]->i2c, S2MPB02_REG_FLED_TIME2, + S2MPB02_FLED2_MAX_TIME_EN_MASK, 0x01); + + ret |= s2mpb02_set_bits(global_led_datas[0]->i2c, S2MPB02_REG_FLED_IRON2, + S2MPB02_FLED2_MAX_TIME_MASK, (u8) max_time - 1); + } + + return ret; +} +EXPORT_SYMBOL(s2mpb02_ir_led_max_time); +#endif + +ssize_t s2mpb02_store(const char *buf) +{ + int i = 0, ret = 0; + int onoff = -1; + sysfs_flash_op = 0; + + if (buf == NULL || kstrtouint(buf, 10, &onoff)) + return -1; + + if (global_led_datas == NULL) { + pr_err("<%s> global_led_datas is NULL\n", __func__); + return -1; + } + + for (i = 0; i < S2MPB02_LED_MAX; i++) { + if (global_led_datas[i] == NULL) { + pr_err("<%s> global_led_datas[%d] is NULL\n", __func__, i); + return -1; + } + } + + pr_info("<%s> sysfs torch/flash value %d\n", __func__, onoff); + if (onoff == 0) { + // Torch OFF + onoff = 0; + } else if (onoff == 1) { + // Torch ON + onoff = S2MPB02_TORCH_OUT_I_60MA; + } else if (onoff == 100) { + // Factory Torch ON + onoff = S2MPB02_TORCH_OUT_I_240MA; + } else if (onoff == 200) { + pr_info("<%s> sysfs flash value %d\n", __func__, onoff); + + /* Factory mode Turn on Flash */ + /* set reserved reg. 0x63 for continuous flash on */ + flash_config_factory = true; + ret = s2mpb02_write_reg(global_led_datas[S2MPB02_FLASH_LED_1]->i2c, 0x63, 0x5F); + if (ret < 0) + pr_info("[LED]%s , failed set flash register setting\n", __func__); + onoff = S2MPB02_FLASH_OUT_I_300MA; + } else if (onoff == 1001) { + // level 1 (Flashlight level 1) + onoff = S2MPB02_TORCH_OUT_I_40MA; + } else if (onoff == 1002) { + // level 2 (Flashlight level 2) + onoff = S2MPB02_TORCH_OUT_I_60MA; + } else if (onoff == 1003) { + // level 3 + onoff = S2MPB02_TORCH_OUT_I_80MA; + } else if (onoff == 1004) { + // level 4 (Flashlight level 3) + onoff = S2MPB02_TORCH_OUT_I_100MA; + } else if (onoff == 1005) { + // level 5 + onoff = S2MPB02_TORCH_OUT_I_120MA; + } else if (onoff == 1006) { + // level 6 (Flashlight level 4) + onoff = S2MPB02_TORCH_OUT_I_160MA; + } else if (onoff == 1007) { + // level 7 + onoff = S2MPB02_TORCH_OUT_I_180MA; + } else if (onoff == 1008) { + // level 8 + onoff = S2MPB02_TORCH_OUT_I_180MA; + } else if (onoff == 1009) { + // level 9 (Flashlight level 5) + onoff = S2MPB02_TORCH_OUT_I_200MA; + } else if (onoff == 1010) { + // level 10 + onoff = S2MPB02_TORCH_OUT_I_200MA; + } else if ((2001 <= onoff) && (onoff <= 2015)) { + // Torch ON for tunning : Step 20mA ~ 300mA + onoff = onoff - 2000; + pr_info("<%s> torch level %d\n", __func__, onoff); + } else { + pr_err("<%s> value %d is invalid\n", __func__, onoff); + onoff = 0; + } + + if (flash_config_factory) { + if (onoff == 0) { + s2mpb02_write_reg(global_led_datas[S2MPB02_FLASH_LED_1]->i2c, 0x63, 0x7F); + flash_config_factory = false; + } +#if defined(S2MPB02_FLED_CONTROLED_BY_GPIO) + s2mpb02_led_en(S2MPB02_FLASH_LED_1, onoff, S2MPB02_LED_TURN_WAY_GPIO); +#else + s2mpb02_led_en(S2MPB02_FLASH_LED_1, onoff, S2MPB02_LED_TURN_WAY_I2C); +#endif + } else +#if defined(S2MPB02_FLED_CONTROLED_BY_GPIO) + s2mpb02_led_en(S2MPB02_TORCH_LED_1, onoff, S2MPB02_LED_TURN_WAY_GPIO); +#else + s2mpb02_led_en(S2MPB02_TORCH_LED_1, onoff, S2MPB02_LED_TURN_WAY_I2C); +#endif + if (onoff) + sysfs_flash_op = 1; + + return 0; +} +EXPORT_SYMBOL(s2mpb02_store); + +ssize_t s2mpb02_show(char *buf) +{ + int i = 0; + + if (global_led_datas == NULL) { + pr_err("<%s> global_led_datas is NULL\n", __func__); + return sprintf(buf, "%d\n", -1); + } + + for (i = 0; i < S2MPB02_LED_MAX; i++) { + if (global_led_datas[i] == NULL) { + pr_err("<%s> global_led_datas[%d] is NULL\n", __func__, i); + return sprintf(buf, "%d\n", -1); + } + } + + if (global_led_datas[S2MPB02_TORCH_LED_1]->data->brightness == LED_OFF) { + return sprintf(buf, "%d\n", 0); + } else { + return sprintf(buf, "%d\n", global_led_datas[S2MPB02_TORCH_LED_1]->data->brightness); + } +} +EXPORT_SYMBOL(s2mpb02_show); + +#if defined(CONFIG_OF) +static int of_s2mpb02_led_dt(struct s2mpb02_dev *iodev, + struct s2mpb02_led_platform_data *pdata) +{ + struct device_node *led_np, *np, *c_np; + int ret; + u32 temp; + const char *temp_str; + int index; + + led_np = iodev->dev->of_node; + if (!led_np) { + pr_err("<%s> could not find led sub-node\n", __func__); + return -ENODEV; + } + + np = of_find_node_by_name(led_np, "torch"); + if (!np) { + pr_err("<%s> could not find led sub-node\n", + __func__); + return -EINVAL; + } + + pdata->num_leds = of_get_child_count(np); + + for_each_child_of_node(np, c_np) { + ret = of_property_read_u32(c_np, "id", &temp); + if (ret < 0) + goto dt_err; + index = temp; + pdata->leds[index].id = temp; + + ret = of_property_read_string(c_np, "ledname", &temp_str); + if (ret < 0) + goto dt_err; + pdata->leds[index].name = temp_str; + + ret = of_property_read_u32(c_np, "brightness", &temp); + if (ret < 0) + goto dt_err; + if (temp > S2MPB02_FLASH_TORCH_CURRENT_MAX) + temp = S2MPB02_FLASH_TORCH_CURRENT_MAX; + pdata->leds[index].brightness = temp; + + ret = of_property_read_u32(c_np, "timeout", &temp); + if (ret < 0) + goto dt_err; + if (temp > S2MPB02_TIMEOUT_MAX) + temp = S2MPB02_TIMEOUT_MAX; + pdata->leds[index].timeout = temp; + + ret = of_property_read_string(c_np, "default-trigger", &temp_str); + if (ret < 0) + goto dt_err; + pdata->leds[index].default_trigger = temp_str; +#if defined(S2MPB02_FLED_CONTROLED_BY_GPIO) + temp = of_gpio_count(c_np); + if (temp > 0) { + pdata->leds[index].gpio = of_get_gpio(c_np, 0); + } +#endif + } + of_node_put(led_np); + + return 0; +dt_err: + pr_err("%s failed to get a dt info\n", __func__); + return ret; +} +#endif /* CONFIG_OF */ + +static int s2mpb02_led_probe(struct platform_device *pdev) +{ + int ret = 0; + int i; + struct s2mpb02_dev *s2mpb02 = dev_get_drvdata(pdev->dev.parent); +#ifndef CONFIG_OF + struct s2mpb02_platform_data *s2mpb02_pdata + = dev_get_platdata(s2mpb02->dev); +#endif + struct s2mpb02_led_platform_data *pdata; + struct s2mpb02_led_data *led_data; + struct s2mpb02_led *data; + struct s2mpb02_led_data **led_datas; + +#ifdef CONFIG_OF + pdata = kzalloc(sizeof(struct s2mpb02_led_platform_data), GFP_KERNEL); + if (!pdata) { + pr_err("%s: failed to allocate driver data\n", __func__); + return -ENOMEM; + } + ret = of_s2mpb02_led_dt(s2mpb02, pdata); + if (ret < 0) { + pr_err("s2mpb02-torch : %s not found torch dt! ret[%d]\n", + __func__, ret); + kfree(pdata); + return ret; + } +#else + pdata = s2mpb02_pdata->led_data; + if (pdata == NULL) { + pr_err("[LED] no platform data for this led is found\n"); + return -EFAULT; + } +#endif + + sysfs_flash_op = 0; //default off + global_led_datas = kzalloc(sizeof(struct s2mpb02_led_data *)*S2MPB02_LED_MAX, GFP_KERNEL); + + led_datas = kzalloc(sizeof(struct s2mpb02_led_data *) + * S2MPB02_LED_MAX, GFP_KERNEL); + if (unlikely(!led_datas)) { + pr_err("[LED] memory allocation error %s", __func__); + kfree(pdata); + return -ENOMEM; + } + platform_set_drvdata(pdev, led_datas); + + pr_info("[LED] %s %d leds\n", __func__, pdata->num_leds); + + for (i = 0; i != pdata->num_leds; ++i) { + pr_info("%s led%d setup ...\n", __func__, i); + + data = kzalloc(sizeof(struct s2mpb02_led), GFP_KERNEL); + if (unlikely(!data)) { + pr_err("[LED] memory allocation error %s\n", __func__); + ret = -ENOMEM; + continue; + } + + memcpy(data, &(pdata->leds[i]), sizeof(struct s2mpb02_led)); + + led_data = kzalloc(sizeof(struct s2mpb02_led_data), + GFP_KERNEL); + + global_led_datas[i] = led_data; + led_datas[i] = led_data; + if (unlikely(!led_data)) { + pr_err("[LED] memory allocation error %s\n", __func__); + ret = -ENOMEM; + kfree(data); + continue; + } + + led_data->s2mpb02 = s2mpb02; + led_data->i2c = s2mpb02->i2c; + led_data->data = data; + led_data->led.name = data->name; + led_data->led.brightness_set = s2mpb02_led_set; + led_data->led.brightness = LED_OFF; + led_data->brightness = data->brightness; + led_data->led.flags = 0; + led_data->led.max_brightness = LED_FULL; + led_data->led.default_trigger = data->default_trigger; + + mutex_init(&led_data->lock); + spin_lock_init(&led_data->value_lock); + INIT_WORK(&led_data->work, s2mpb02_led_work); + + ret = led_classdev_register(&pdev->dev, &led_data->led); + if (unlikely(ret)) { + pr_err("unable to register LED\n"); + cancel_work_sync(&led_data->work); + mutex_destroy(&led_data->lock); + kfree(data); + kfree(led_data); + led_datas[i] = NULL; + ret = -EFAULT; + continue; + } + + ret = s2mpb02_led_setup(led_data); + if (unlikely(ret)) { + pr_err("unable to register LED\n"); + cancel_work_sync(&led_data->work); + mutex_destroy(&led_data->lock); + led_classdev_unregister(&led_data->led); + kfree(data); + kfree(led_data); + led_datas[i] = NULL; + ret = -EFAULT; + } + } + +#if defined(DEBUG_READ_REGISTER) + (void)debugfs_create_file("s2mpb02-led-regs", S_IRUGO, NULL, + (void *)s2mpb02, &s2mpb02_debugfs_fops); +#endif + +#ifdef CONFIG_OF + kfree(pdata); +#endif + + pr_info("<%s> end\n", __func__); + return ret; +} + +static int s2mpb02_led_remove(struct platform_device *pdev) +{ + struct s2mpb02_led_data **led_datas = platform_get_drvdata(pdev); + int i; + + for (i = 0; i != S2MPB02_LED_MAX; ++i) { + if (led_datas[i] == NULL) + continue; + + cancel_work_sync(&led_datas[i]->work); + mutex_destroy(&led_datas[i]->lock); + led_classdev_unregister(&led_datas[i]->led); + kfree(led_datas[i]->data); + kfree(led_datas[i]); + } + kfree(led_datas); + + if (global_led_datas != NULL) + kfree(global_led_datas); + + return 0; +} + +static struct platform_driver s2mpb02_led_driver = { + .probe = s2mpb02_led_probe, + .remove = s2mpb02_led_remove, + .driver = { + .name = "s2mpb02-led", + .owner = THIS_MODULE, + .suppress_bind_attrs = true, + }, +}; + +static int __init s2mpb02_led_init(void) +{ + return platform_driver_register(&s2mpb02_led_driver); +} +module_init(s2mpb02_led_init); + +static void __exit s2mpb02_led_exit(void) +{ + platform_driver_unregister(&s2mpb02_led_driver); +} +module_exit(s2mpb02_led_exit); + +MODULE_DESCRIPTION("S2MPB02 LED driver"); +MODULE_LICENSE("GPL"); +MODULE_SOFTDEP("pre: s2mpb02-regulator"); diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 18557b690d1e..7122c00c8cee 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -2274,5 +2274,8 @@ config MFD_RSMU_SPI Additional drivers must be enabled in order to use the functionality of the device. +config SEC_AP_PMIC + tristate "SEC AP PMIC Driver" + endmenu endif diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index d328fb1014e2..394c061b79a8 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -281,3 +281,5 @@ rsmu-i2c-objs := rsmu_core.o rsmu_i2c.o rsmu-spi-objs := rsmu_core.o rsmu_spi.o obj-$(CONFIG_MFD_RSMU_I2C) += rsmu-i2c.o obj-$(CONFIG_MFD_RSMU_SPI) += rsmu-spi.o + +obj-$(CONFIG_SEC_AP_PMIC) += sec_ap_pmic.o diff --git a/drivers/mfd/maxim/Kconfig b/drivers/mfd/maxim/Kconfig new file mode 100644 index 000000000000..a3f2f920b0a1 --- /dev/null +++ b/drivers/mfd/maxim/Kconfig @@ -0,0 +1,71 @@ +config MFD_MAX77705 + tristate "Maxim Semiconductor MAX77705 PMIC Support" + depends on I2C=y + select MFD_CORE + help + Say yes here to support for Maxim Semiconductor MAX77705. + This is a Power Management IC with Charger, safe LDOs, Flash, Haptic + and MUIC controls on chip. + This driver provides common support for accessing the device; + additional drivers + +config QCOM_IFPMIC_SUSPEND + tristate "QCOM_IFPMIC_SUSPEND Function Support" + default n + help + If you say yes here you will get support for + QCOM_IFPMIC_SUSPEND function + +config ABC_IFPMIC_EVENT + bool "Using EVENT SENDING TO ABC MODULE" + depends on I2C=y + default n + help + If you say yes here you will not get support for the sending event to abc module. + +config IFPMIC_CRC_CHECK + bool "Using IFPMIC_CRC_CHECK Function Support" + default n + help + If you say yes here you will get support for + IFPMIC_CRC_CHECK function + +config MAX77705_FW_SEPARATION_PID_BY_MODEL + bool "Separate of pid by model" + depends on CCIC_MAX77705 + default n + help + If you want to use specific firmware by model, + this feature must be enabled. + Each models will have own firmware pid. + MAX77705_FW_PID03_SUPPORT feature in usbc is ignored. + +config MAX77705_USE_EXTRA_FW + bool "Use extra firmware" + depends on CCIC_MAX77705 + default n + help + If you want to use extra firmware for specific hw id, + this feature must be enabled. + The specific hw board only use extra firmware. + That board cannot update normal version firmware. + + +config MFD_MAX77775 + tristate "Maxim Semiconductor MAX77775 PMIC Support" + depends on I2C=y + select MFD_CORE + help + Say yes here to support for Maxim Semiconductor MAX77775. + This is a Power Management IC with Charger, safe LDOs, + and Haptic controls on chip. + This driver provides common support for accessing the device; + additional drivers + +config MAX77775_ABC_IFPMIC_EVENT + bool "Using EVENT SENDING TO ABC MODULE" + depends on I2C=y + default n + help + If you say yes here you will not get support for the sending event to abc module. + diff --git a/drivers/mfd/maxim/Makefile b/drivers/mfd/maxim/Makefile new file mode 100644 index 000000000000..9d830e47013b --- /dev/null +++ b/drivers/mfd/maxim/Makefile @@ -0,0 +1,7 @@ +subdir-ccflags-y := -Wformat +obj-$(CONFIG_MFD_MAX77705) += mfd_max77705.o +mfd_max77705-y := max77705.o max77705-irq.o + +subdir-ccflags-y := -Wformat +obj-$(CONFIG_MFD_MAX77775) += mfd_max77775.o +mfd_max77775-y := max77775.o max77775-irq.o diff --git a/drivers/mfd/maxim/max77705-irq.c b/drivers/mfd/maxim/max77705-irq.c new file mode 100644 index 000000000000..cab7cb1d8180 --- /dev/null +++ b/drivers/mfd/maxim/max77705-irq.c @@ -0,0 +1,522 @@ +/* + * max77705-irq.c - Interrupt controller support for MAX77705 + * + * Copyright (C) 2016 Samsung Electronics Co.Ltd + * Insun Choi + * + * 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, see . + * + * This driver is based on max77705-irq.c + */ + +#include +#include +#include +#include +#include +#include + +static const u8 max77705_mask_reg[] = { + /* TODO: Need to check other INTMASK */ + [SYS_INT] = MAX77705_PMIC_REG_SYSTEM_INT_MASK, + [CHG_INT] = MAX77705_CHG_REG_INT_MASK, + [FUEL_INT] = MAX77705_REG_INVALID, + [USBC_INT] = MAX77705_USBC_REG_UIC_INT_M, + [CC_INT] = MAX77705_USBC_REG_CC_INT_M, + [PD_INT] = MAX77705_USBC_REG_PD_INT_M, + [VDM_INT] = MAX77705_USBC_REG_VDM_INT_M, + [VIR_INT] = MAX77705_REG_INVALID, +}; + +static struct i2c_client *get_i2c(struct max77705_dev *max77705, + enum max77705_irq_source src) +{ + switch (src) { + case SYS_INT: + return max77705->i2c; + case FUEL_INT: + return max77705->fuelgauge; + case CHG_INT: + return max77705->charger; + case USBC_INT: + case CC_INT: + case PD_INT: + case VDM_INT: + case VIR_INT: + return max77705->muic; + default: + return ERR_PTR(-EINVAL); + } +} + +struct max77705_irq_data { + int mask; + enum max77705_irq_source group; +}; + +static const struct max77705_irq_data max77705_irqs[] = { + [MAX77705_SYSTEM_IRQ_BSTEN_INT] = { .group = SYS_INT, .mask = 1 << 3 }, + [MAX77705_SYSTEM_IRQ_SYSUVLO_INT] = { .group = SYS_INT, .mask = 1 << 4 }, + [MAX77705_SYSTEM_IRQ_SYSOVLO_INT] = { .group = SYS_INT, .mask = 1 << 5 }, + [MAX77705_SYSTEM_IRQ_TSHDN_INT] = { .group = SYS_INT, .mask = 1 << 6 }, + [MAX77705_SYSTEM_IRQ_TM_INT] = { .group = SYS_INT, .mask = 1 << 7 }, + + [MAX77705_CHG_IRQ_BYP_I] = { .group = CHG_INT, .mask = 1 << 0 }, + [MAX77705_CHG_IRQ_BATP_I] = { .group = CHG_INT, .mask = 1 << 2 }, + [MAX77705_CHG_IRQ_BAT_I] = { .group = CHG_INT, .mask = 1 << 3 }, + [MAX77705_CHG_IRQ_CHG_I] = { .group = CHG_INT, .mask = 1 << 4 }, + [MAX77705_CHG_IRQ_WCIN_I] = { .group = CHG_INT, .mask = 1 << 5 }, + [MAX77705_CHG_IRQ_CHGIN_I] = { .group = CHG_INT, .mask = 1 << 6 }, + [MAX77705_CHG_IRQ_AICL_I] = { .group = CHG_INT, .mask = 1 << 7 }, + + [MAX77705_FG_IRQ_ALERT] = { .group = FUEL_INT, .mask = 1 << 1 }, + + [MAX77705_USBC_IRQ_APC_INT] = { .group = USBC_INT, .mask = 1 << 7 }, + [MAX77705_USBC_IRQ_SYSM_INT] = { .group = USBC_INT, .mask = 1 << 6 }, + [MAX77705_USBC_IRQ_VBUS_INT] = { .group = USBC_INT, .mask = 1 << 5 }, + [MAX77705_USBC_IRQ_VBADC_INT] = { .group = USBC_INT, .mask = 1 << 4 }, + [MAX77705_USBC_IRQ_DCD_INT] = { .group = USBC_INT, .mask = 1 << 3 }, + [MAX77705_USBC_IRQ_FAKVB_INT] = { .group = USBC_INT, .mask = 1 << 2 }, + [MAX77705_USBC_IRQ_CHGT_INT] = { .group = USBC_INT, .mask = 1 << 1 }, + [MAX77705_USBC_IRQ_UIDADC_INT] = { .group = USBC_INT, .mask = 1 << 0 }, + + [MAX77705_CC_IRQ_VCONNCOP_INT] = { .group = CC_INT, .mask = 1 << 7 }, + [MAX77705_CC_IRQ_VSAFE0V_INT] = { .group = CC_INT, .mask = 1 << 6 }, + [MAX77705_CC_IRQ_DETABRT_INT] = { .group = CC_INT, .mask = 1 << 5 }, + [MAX77705_CC_IRQ_VCONNSC_INT] = { .group = CC_INT, .mask = 1 << 4 }, + [MAX77705_CC_IRQ_CCPINSTAT_INT] = { .group = CC_INT, .mask = 1 << 3 }, + [MAX77705_CC_IRQ_CCISTAT_INT] = { .group = CC_INT, .mask = 1 << 2 }, + [MAX77705_CC_IRQ_CCVCNSTAT_INT] = { .group = CC_INT, .mask = 1 << 1 }, + [MAX77705_CC_IRQ_CCSTAT_INT] = { .group = CC_INT, .mask = 1 << 0 }, + + [MAX77705_PD_IRQ_PDMSG_INT] = { .group = PD_INT, .mask = 1 << 7 }, + [MAX77705_PD_IRQ_PS_RDY_INT] = { .group = PD_INT, .mask = 1 << 6 }, + [MAX77705_PD_IRQ_DATAROLE_INT] = { .group = PD_INT, .mask = 1 << 5 }, + [MAX77705_IRQ_VDM_ATTENTION_INT] = { .group = PD_INT, .mask = 1 << 4 }, + [MAX77705_IRQ_VDM_DP_CONFIGURE_INT] = { .group = PD_INT, .mask = 1 << 3 }, + [MAX77705_IRQ_VDM_DP_STATUS_UPDATE_INT] = { .group = PD_INT, .mask = 1 << 2 }, + [MAX77705_PD_IRQ_SSACCI_INT] = { .group = PD_INT, .mask = 1 << 1 }, + [MAX77705_PD_IRQ_FCTIDI_INT] = { .group = PD_INT, .mask = 1 << 0 }, + + [MAX77705_IRQ_VDM_DISCOVER_ID_INT] = { .group = VDM_INT, .mask = 1 << 0 }, + [MAX77705_IRQ_VDM_DISCOVER_SVIDS_INT] = { .group = VDM_INT, .mask = 1 << 1 }, + [MAX77705_IRQ_VDM_DISCOVER_MODES_INT] = { .group = VDM_INT, .mask = 1 << 2 }, + [MAX77705_IRQ_VDM_ENTER_MODE_INT] = { .group = VDM_INT, .mask = 1 << 3 }, + + [MAX77705_VIR_IRQ_ALTERROR_INT] = { .group = VIR_INT, .mask = 1 << 0 }, +}; + +static void max77705_irq_lock(struct irq_data *data) +{ + struct max77705_dev *max77705 = irq_get_chip_data(data->irq); + + mutex_lock(&max77705->irqlock); +} + +static void max77705_irq_sync_unlock(struct irq_data *data) +{ + struct max77705_dev *max77705 = irq_get_chip_data(data->irq); + int i; + + for (i = 0; i < MAX77705_IRQ_GROUP_NR; i++) { + u8 mask_reg = max77705_mask_reg[i]; + struct i2c_client *i2c = get_i2c(max77705, i); + + if (mask_reg == MAX77705_REG_INVALID || + IS_ERR_OR_NULL(i2c)) + continue; + max77705->irq_masks_cache[i] = max77705->irq_masks_cur[i]; + + max77705_write_reg(i2c, max77705_mask_reg[i], + max77705->irq_masks_cur[i]); + } + + mutex_unlock(&max77705->irqlock); +} + +static const inline struct max77705_irq_data * +irq_to_max77705_irq(struct max77705_dev *max77705, int irq) +{ + return &max77705_irqs[irq - max77705->irq_base]; +} + +static void max77705_irq_mask(struct irq_data *data) +{ + struct max77705_dev *max77705 = irq_get_chip_data(data->irq); + const struct max77705_irq_data *irq_data = + irq_to_max77705_irq(max77705, data->irq); + + if (irq_data->group >= MAX77705_IRQ_GROUP_NR) + return; + + max77705->irq_masks_cur[irq_data->group] |= irq_data->mask; +} + +static void max77705_irq_unmask(struct irq_data *data) +{ + struct max77705_dev *max77705 = irq_get_chip_data(data->irq); + const struct max77705_irq_data *irq_data = + irq_to_max77705_irq(max77705, data->irq); + + if (irq_data->group >= MAX77705_IRQ_GROUP_NR) + return; + + max77705->irq_masks_cur[irq_data->group] &= ~irq_data->mask; +} + +static void max77705_irq_disable(struct irq_data *data) +{ + max77705_irq_mask(data); +} + +static struct irq_chip max77705_irq_chip = { + .name = MFD_DEV_NAME, + .irq_bus_lock = max77705_irq_lock, + .irq_bus_sync_unlock = max77705_irq_sync_unlock, + .irq_mask = max77705_irq_mask, + .irq_unmask = max77705_irq_unmask, + .irq_disable = max77705_irq_disable, +}; + +#define VB_LOW 0 + +static irqreturn_t max77705_irq_thread(int irq, void *data) +{ + struct max77705_dev *max77705 = data; + u8 irq_reg[MAX77705_IRQ_GROUP_NR] = {0}; + u8 irq_src; + int i, ret; + u8 irq_vdm_mask = 0x0; + u8 dummy[2] = {0, }; /* for pass1 intr reg clear issue */ + u8 dump_reg[10] = {0, }; + u8 pmic_rev = max77705->pmic_rev; + u8 reg_data; + u8 cc_status0 = 0; + u8 cc_status1 = 0; + u8 bc_status0 = 0; + u8 ccstat = 0; + u8 vbvolt = 0; + u8 pre_ccstati = 0; + u8 ic_alt_mode = 0; + + pr_debug("%s: irq gpio pre-state(0x%02x)\n", __func__, + gpio_get_value(max77705->irq_gpio)); + +#if defined(CONFIG_QCOM_IFPMIC_SUSPEND) + max77705->doing_irq = 1; + + ret = wait_event_timeout(max77705->suspend_wait, + !max77705->suspended, + msecs_to_jiffies(200)); + if (!ret) { + pr_info("%s suspend_wait timeout\n", __func__); + max77705->doing_irq = 0; + return IRQ_NONE; + } +#endif + + ret = max77705_read_reg(max77705->i2c, + MAX77705_PMIC_REG_INTSRC, &irq_src); + if (ret) { + pr_err("%s:%s Failed to read interrupt source: %d\n", + MFD_DEV_NAME, __func__, ret); + max77705->doing_irq = 0; + return IRQ_NONE; + } + + pr_info("%s:%s: irq[%d] %d/%d/%d irq_src=0x%02x pmic_rev=0x%02x\n", MFD_DEV_NAME, __func__, + irq, max77705->irq, max77705->irq_base, max77705->irq_gpio, irq_src, pmic_rev); + + if (irq_src & MAX77705_IRQSRC_CHG) { + /* CHG_INT */ + switch (pmic_rev) { + case MAX77705_PASS1: + ret = max77705_bulk_read(max77705->charger, MAX77705_CHG_REG_INT - 1, + 2, dummy); + irq_reg[CHG_INT] = dummy[1]; + break; + case MAX77705_PASS2: + case MAX77705_PASS3: + case MAX77705_PASS4: + case MAX77705_PASS5: + ret = max77705_read_reg(max77705->charger, MAX77705_CHG_REG_INT, + &irq_reg[CHG_INT]); + + if(max77705->enable_nested_irq){ + irq_reg[USBC_INT] |= max77705->usbc_irq; + max77705->enable_nested_irq = 0x0; + max77705->usbc_irq = 0x0; + } + + break; + default: + pr_err("%s: PMIC_REVISION(SRC_CHG) isn't valid\n", __func__); + break; + } + pr_debug("%s: charger interrupt(0x%02x)\n", + __func__, irq_reg[CHG_INT]); + /* mask chgin to prevent chgin infinite interrupt + * chgin is unmasked chgin isr + */ + if (irq_reg[CHG_INT] & + max77705_irqs[MAX77705_CHG_IRQ_CHGIN_I].mask) { + max77705_read_reg(max77705->charger, + MAX77705_CHG_REG_INT_MASK, ®_data); + reg_data |= (1 << 6); + max77705_write_reg(max77705->charger, + MAX77705_CHG_REG_INT_MASK, reg_data); + } + } + + + if (irq_src & MAX77705_IRQSRC_FG) { + pr_debug("[%s] fuelgauge interrupt\n", __func__); + pr_debug("[%s]IRQ_BASE(%d), NESTED_IRQ(%d)\n", + __func__, max77705->irq_base, max77705->irq_base + MAX77705_FG_IRQ_ALERT); + irq_reg[FUEL_INT] = 1 << 1; + } + + if (irq_src & MAX77705_IRQSRC_TOP) { + /* SYS_INT */ + switch (pmic_rev) { + case MAX77705_PASS1: + ret = max77705_bulk_read(max77705->i2c, MAX77705_PMIC_REG_SYSTEM_INT - 1, + 2, dummy); + irq_reg[SYS_INT] = dummy[1]; + break; + case MAX77705_PASS2: + case MAX77705_PASS3: + case MAX77705_PASS4: + case MAX77705_PASS5: + ret = max77705_read_reg(max77705->i2c, MAX77705_PMIC_REG_SYSTEM_INT, + &irq_reg[SYS_INT]); + break; + default: + pr_err("%s: PMIC_REVISION(SRC_TOP) isn't valid\n", __func__); + break; + } + pr_debug("%s: topsys interrupt(0x%02x)\n", __func__, irq_reg[SYS_INT]); + } + + if ((irq_src & MAX77705_IRQSRC_USBC) && max77705->cc_booting_complete) { + /* USBC INT */ + switch (pmic_rev) { + case MAX77705_PASS1: + ret = max77705_bulk_read(max77705->muic, MAX77705_USBC_REG_UIC_INT - 1, + 2, dummy); + irq_reg[USBC_INT] = dummy[1]; + ret = max77705_bulk_read(max77705->muic, MAX77705_USBC_REG_CC_INT - 1, + 2, dummy); + irq_reg[USBC_INT] |= dummy[0]; + irq_reg[CC_INT] = dummy[1]; + ret = max77705_bulk_read(max77705->muic, MAX77705_USBC_REG_PD_INT - 1, + 2, dummy); + irq_reg[CC_INT] |= dummy[0]; + irq_reg[PD_INT] = dummy[1]; + ret = max77705_bulk_read(max77705->muic, MAX77705_USBC_REG_VDM_INT_M, + 1, &irq_vdm_mask); + if (irq_vdm_mask == 0x0) { + ret = max77705_bulk_read(max77705->muic, + MAX77705_USBC_REG_VDM_INT - 1, 2, dummy); + irq_reg[PD_INT] |= dummy[0]; + irq_reg[VDM_INT] = dummy[1]; + } + break; + case MAX77705_PASS2: + case MAX77705_PASS3: + case MAX77705_PASS4: + case MAX77705_PASS5: + ret = max77705_bulk_read(max77705->muic, MAX77705_USBC_REG_UIC_INT, + 4, &irq_reg[USBC_INT]); + ret = max77705_read_reg(max77705->muic, MAX77705_USBC_REG_VDM_INT_M, + &irq_vdm_mask); + if (irq_reg[USBC_INT] & BIT_VBUSDetI) { + ret = max77705_read_reg(max77705->muic, REG_BC_STATUS, &bc_status0); + ret = max77705_read_reg(max77705->muic, REG_CC_STATUS0, &cc_status0); + vbvolt = (bc_status0 & BIT_VBUSDet) >> FFS(BIT_VBUSDet); + ccstat = (cc_status0 & BIT_CCStat) >> FFS(BIT_CCStat); + if (cc_No_Connection == ccstat && vbvolt == VB_LOW) { + pre_ccstati = irq_reg[CC_INT]; + irq_reg[CC_INT] |= 0x1; + pr_info("[MAX77705] set the cc_stat int [work-around] :%x, %x\n", + pre_ccstati,irq_reg[CC_INT]); + } + } + break; + default: + pr_err("%s: PMIC_REVISION(SRC_MUIC) isn't valid\n", __func__); + break; + } + ret = max77705_bulk_read(max77705->muic, MAX77705_USBC_REG_USBC_STATUS1, + 8, dump_reg); + pr_info("[MAX77705] irq_reg, complete [%x], %x, %x, %x, %x, %x\n", max77705->cc_booting_complete, + irq_reg[USBC_INT], irq_reg[CC_INT], irq_reg[PD_INT], irq_reg[VDM_INT], irq_vdm_mask); + pr_info("[MAX77705] dump_reg, %x, %x, %x, %x, %x, %x, %x, %x\n", dump_reg[0], dump_reg[1], + dump_reg[2], dump_reg[3], dump_reg[4], dump_reg[5], dump_reg[6], dump_reg[7]); + } + + if (max77705->cc_booting_complete) { + max77705_read_reg(max77705->muic, REG_CC_STATUS1, &cc_status1); + ic_alt_mode = (cc_status1 & BIT_Altmode) >> FFS(BIT_Altmode); + if (!ic_alt_mode && max77705->set_altmode_en) + irq_reg[VIR_INT] |= (1 << 0); + pr_info("%s ic_alt_mode=%d\n", __func__, ic_alt_mode); + + if (irq_reg[PD_INT] & BIT_PDMsg) { + if (dump_reg[6] == Sink_PD_PSRdy_received + || dump_reg[6] == SRC_CAP_RECEIVED) { + if (max77705->check_pdmsg) + max77705->check_pdmsg(max77705->usbc_data, dump_reg[6]); + } + } + } + + /* Apply masking */ + for (i = 0; i < MAX77705_IRQ_GROUP_NR; i++) + irq_reg[i] &= ~max77705->irq_masks_cur[i]; + + /* Report */ + for (i = 0; i < MAX77705_IRQ_NR; i++) { + if (irq_reg[max77705_irqs[i].group] & max77705_irqs[i].mask) + handle_nested_irq(max77705->irq_base + i); + } + +#if defined(CONFIG_QCOM_IFPMIC_SUSPEND) + max77705->doing_irq = 0; + if (max77705->check_usbc_opcode_queue) + max77705->is_usbc_queue = !(max77705->check_usbc_opcode_queue()); + + pr_info("%s doing_irq = %d, is_usbc_queue=%d\n", __func__, + max77705->doing_irq, max77705->is_usbc_queue); + + wake_up_interruptible(&max77705->queue_empty_wait_q); +#endif + return IRQ_HANDLED; +} + +int max77705_irq_init(struct max77705_dev *max77705) +{ + int i; + int ret; + u8 i2c_data; + int cur_irq; + + if (!gpio_is_valid(max77705->irq_gpio)) { + dev_warn(max77705->dev, "No interrupt specified.\n"); + max77705->irq_base = 0; + return 0; + } + + if (max77705->irq_base < 0) { + dev_err(max77705->dev, "No interrupt base specified.\n"); + return 0; + } + + mutex_init(&max77705->irqlock); + + max77705->irq = gpio_to_irq(max77705->irq_gpio); + pr_info("%s:%s irq=%d, irq->gpio=%d\n", MFD_DEV_NAME, __func__, + max77705->irq, max77705->irq_gpio); + + ret = gpio_request(max77705->irq_gpio, "if_pmic_irq"); + if (ret) { + dev_err(max77705->dev, "%s: failed requesting gpio %d\n", + __func__, max77705->irq_gpio); + return ret; + } + gpio_direction_input(max77705->irq_gpio); + gpio_free(max77705->irq_gpio); + + /* Mask individual interrupt sources */ + for (i = 0; i < MAX77705_IRQ_GROUP_NR; i++) { + struct i2c_client *i2c; + /* MUIC IRQ 0:MASK 1:NOT MASK => NOT USE */ + /* Other IRQ 1:MASK 0:NOT MASK */ + max77705->irq_masks_cur[i] = 0xff; + max77705->irq_masks_cache[i] = 0xff; + + i2c = get_i2c(max77705, i); + + if (IS_ERR_OR_NULL(i2c)) + continue; + if (max77705_mask_reg[i] == MAX77705_REG_INVALID) + continue; + max77705_write_reg(i2c, max77705_mask_reg[i], 0xff); + } + + /* Register with genirq */ + for (i = 0; i < MAX77705_IRQ_NR; i++) { + cur_irq = i + max77705->irq_base; + irq_set_chip_data(cur_irq, max77705); + irq_set_chip_and_handler(cur_irq, &max77705_irq_chip, + handle_level_irq); + irq_set_nested_thread(cur_irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(cur_irq, IRQF_VALID); +#else + irq_set_noprobe(cur_irq); +#endif + } + + + + /* Unmask max77705 interrupt */ + ret = max77705_read_reg(max77705->i2c, MAX77705_PMIC_REG_INTSRC_MASK, + &i2c_data); + if (ret) { + pr_err("%s:%s fail to read muic reg\n", MFD_DEV_NAME, __func__); + return ret; + } + i2c_data |= 0xF; /* mask muic interrupt */ + + max77705_write_reg(max77705->i2c, MAX77705_PMIC_REG_INTSRC_MASK, + i2c_data); + + + ret = request_threaded_irq(max77705->irq, NULL, max77705_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "max77705-irq", max77705); + if (ret) { + dev_err(max77705->dev, "Failed to request IRQ %d: %d\n", + max77705->irq, ret); + return ret; + } + + + /* Unmask max77705 interrupt */ + ret = max77705_read_reg(max77705->i2c, MAX77705_PMIC_REG_INTSRC_MASK, + &i2c_data); + if (ret) { + pr_err("%s:%s fail to read muic reg\n", MFD_DEV_NAME, __func__); + return ret; + } + + i2c_data &= ~(MAX77705_IRQSRC_CHG); /* Unmask charger interrupt */ +// i2c_data &= ~(MAX77705_IRQSRC_FG); /* Unmask fg interrupt */ + i2c_data |= MAX77705_IRQSRC_USBC; /* mask usbc interrupt */ + + max77705_write_reg(max77705->i2c, MAX77705_PMIC_REG_INTSRC_MASK, + i2c_data); + + pr_info("%s:%s max77705_PMIC_REG_INTSRC_MASK=0x%02x\n", + MFD_DEV_NAME, __func__, i2c_data); + + return 0; +} + +void max77705_irq_exit(struct max77705_dev *max77705) +{ + if (max77705->irq) + free_irq(max77705->irq, max77705); +} diff --git a/drivers/mfd/maxim/max77705.c b/drivers/mfd/maxim/max77705.c new file mode 100644 index 000000000000..ff3935180f12 --- /dev/null +++ b/drivers/mfd/maxim/max77705.c @@ -0,0 +1,1523 @@ +/* + * max77705.c - mfd core driver for the Maxim 77705 + * + * Copyright (C) 2016 Samsung Electronics + * Insun Choi + * + * 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, see . + * + * This driver is based on max8997.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#if defined(CONFIG_MAX77705_FW_SEPARATION_PID_BY_MODEL) +#include +#if defined(CONFIG_MAX77705_USE_EXTRA_FW) +#include +#endif +#else +#if defined(CONFIG_MAX77705_FW_PID03_SUPPORT) +#include +#if defined(CONFIG_MAX77705_USE_EXTRA_FW) +#include +#endif +#else +#include +#endif +#endif +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) +#include +#endif +#if defined(CONFIG_OF) +#include +#include +#endif /* CONFIG_OF */ +#include +#if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_ABC_IFPMIC_EVENT) +#include +#endif + +#if IS_ENABLED(CONFIG_SEC_MPARAM) || (IS_MODULE(CONFIG_SEC_PARAM) && defined(CONFIG_ARCH_EXYNOS)) +#if defined(CONFIG_SEC_FACTORY) +extern int factory_mode; +#endif +#else +#if defined(CONFIG_SEC_FACTORY) +static int __read_mostly factory_mode; +module_param(factory_mode, int, 0444); +#endif +#endif + +#define I2C_ADDR_PMIC (0xCC >> 1) /* Top sys, Haptic */ +#define I2C_ADDR_MUIC (0x4A >> 1) +#define I2C_ADDR_CHG (0xD2 >> 1) +#define I2C_ADDR_FG (0x6C >> 1) +#define I2C_ADDR_DEBUG (0xC4 >> 1) + +#define I2C_RETRY_CNT 3 + +/* + * pmic revision information + */ +struct max77705_revision_struct { + u8 id; + u8 rev; + u8 logical_id; +}; + +static struct max77705_revision_struct max77705_revision[3] = { + { 0x5, 0x3, 0x3}, // MD05 PASS3 + { 0x5, 0x4, 0x4}, // MD05 PASS4 + { 0x15,0x2, 0x5}, // MD15 PASS2 +}; + +static struct mfd_cell max77705_devs[] = { +#if IS_ENABLED(CONFIG_CCIC_MAX77705) + { .name = "max77705-usbc", }, +#endif +#if defined(CONFIG_REGULATOR_MAX77705) + { .name = "max77705-safeout", }, +#endif /* CONFIG_REGULATOR_MAX77705 */ +#if IS_ENABLED(CONFIG_FUELGAUGE_MAX77705) + { .name = "max77705-fuelgauge", }, +#endif +#if IS_ENABLED(CONFIG_CHARGER_MAX77705) + { .name = "max77705-charger", }, +#endif +#if IS_ENABLED(CONFIG_MAX77705_VIBRATOR) + { .name = "max77705_vibrator", + .of_compatible = "maxim,max77705_vibrator", }, +#endif /* CONFIG_MAX77705_VIBRATOR */ +#if defined(CONFIG_LEDS_MAX77705_RGB) + { .name = "leds-max77705-rgb", }, +#endif /* CONFIG_LEDS_MAX77705_RGB */ +#if defined(CONFIG_LEDS_MAX77705_FLASH) + { .name = "max77705-flash", }, +#endif +}; + +#if defined(CONFIG_SEC_FACTORY) +static int max77705_get_facmode(void) { return factory_mode; } +#endif + +int max77705_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest) +{ + struct max77705_dev *max77705 = i2c_get_clientdata(i2c); +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif + int ret, i; + + mutex_lock(&max77705->i2c_lock); + for (i = 0; i < I2C_RETRY_CNT; ++i) { + ret = i2c_smbus_read_byte_data(i2c, reg); + if (ret >= 0) + break; + pr_info("%s:%s reg(0x%x), ret(%d), i2c_retry_cnt(%d/%d)\n", + MFD_DEV_NAME, __func__, reg, ret, i + 1, I2C_RETRY_CNT); + } + mutex_unlock(&max77705->i2c_lock); + if (ret < 0) { + pr_info("%s:%s reg(0x%x), ret(%d) suspended(%d)\n", + MFD_DEV_NAME, __func__, reg, ret, max77705->suspended); + + if (max77705->suspended) + return ret; + +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); +#endif + +#if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_ABC_IFPMIC_EVENT) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=pdic@INFO=i2c_fail"); +#else + sec_abc_send_event("MODULE=pdic@WARN=i2c_fail"); +#endif +#endif + + return ret; + } + + ret &= 0xff; + *dest = ret; + return 0; +} +EXPORT_SYMBOL_GPL(max77705_read_reg); + +int max77705_bulk_read(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct max77705_dev *max77705 = i2c_get_clientdata(i2c); +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif + int ret, i; + + mutex_lock(&max77705->i2c_lock); + for (i = 0; i < I2C_RETRY_CNT; ++i) { + ret = i2c_smbus_read_i2c_block_data(i2c, reg, count, buf); + if (ret >= 0) + break; + pr_info("%s:%s reg(0x%x), ret(%d), i2c_retry_cnt(%d/%d)\n", + MFD_DEV_NAME, __func__, reg, ret, i + 1, I2C_RETRY_CNT); + } + mutex_unlock(&max77705->i2c_lock); + if (ret < 0) { + + if (max77705->suspended) + return ret; + +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); +#endif + +#if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_ABC_IFPMIC_EVENT) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=pdic@INFO=i2c_fail"); +#else + sec_abc_send_event("MODULE=pdic@WARN=i2c_fail"); +#endif +#endif + + return ret; + } + return 0; +} +EXPORT_SYMBOL_GPL(max77705_bulk_read); + +int max77705_read_word(struct i2c_client *i2c, u8 reg) +{ + struct max77705_dev *max77705 = i2c_get_clientdata(i2c); +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif + int ret, i; + + mutex_lock(&max77705->i2c_lock); + for (i = 0; i < I2C_RETRY_CNT; ++i) { + ret = i2c_smbus_read_word_data(i2c, reg); + if (ret >= 0) + break; + pr_info("%s:%s reg(0x%x), ret(%d), i2c_retry_cnt(%d/%d)\n", + MFD_DEV_NAME, __func__, reg, ret, i + 1, I2C_RETRY_CNT); + } + mutex_unlock(&max77705->i2c_lock); + if (ret < 0) { + + if (max77705->suspended) + return ret; + +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); +#endif + +#if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_ABC_IFPMIC_EVENT) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=pdic@INFO=i2c_fail"); +#else + sec_abc_send_event("MODULE=pdic@WARN=i2c_fail"); +#endif +#endif + } + return ret; +} +EXPORT_SYMBOL_GPL(max77705_read_word); + +int max77705_write_reg(struct i2c_client *i2c, u8 reg, u8 value) +{ + struct max77705_dev *max77705 = i2c_get_clientdata(i2c); +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif + int ret = -EIO, i; + int timeout = 2000; /* 2sec */ + int interval = 100; + + while (ret == -EIO) { + mutex_lock(&max77705->i2c_lock); + for (i = 0; i < I2C_RETRY_CNT; ++i) { + ret = i2c_smbus_write_byte_data(i2c, reg, value); + if ((ret >= 0) || (ret == -EIO)) + break; + pr_info("%s:%s reg(0x%02x), ret(%d), i2c_retry_cnt(%d/%d)\n", + MFD_DEV_NAME, __func__, reg, ret, i + 1, I2C_RETRY_CNT); + } + mutex_unlock(&max77705->i2c_lock); + + if (ret < 0) { + pr_info("%s:%s reg(0x%x), ret(%d), timeout %d\n", + MFD_DEV_NAME, __func__, reg, ret, timeout); + + if (timeout < 0) + break; + + msleep(interval); + timeout -= interval; + } + } + + if (ret < 0) { + + if (max77705->suspended) + return ret; + +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); +#endif + +#if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_ABC_IFPMIC_EVENT) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=pdic@INFO=i2c_fail"); +#else + sec_abc_send_event("MODULE=pdic@WARN=i2c_fail"); +#endif +#endif + } + return ret; +} +EXPORT_SYMBOL_GPL(max77705_write_reg); + +int max77705_write_reg_nolock(struct i2c_client *i2c, u8 reg, u8 value) +{ + struct max77705_dev *max77705 = i2c_get_clientdata(i2c); +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif + int ret = -EIO; + int timeout = 2000; /* 2sec */ + int interval = 100; + + while (ret == -EIO) { + ret = i2c_smbus_write_byte_data(i2c, reg, value); + + if (ret < 0) { + pr_info("%s:%s reg(0x%x), ret(%d), timeout %d\n", + MFD_DEV_NAME, __func__, reg, ret, timeout); + + if (timeout < 0) + break; + + msleep(interval); + timeout -= interval; + } + } + if (ret < 0) { + + if (max77705->suspended) + return ret; + +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); +#endif + +#if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_ABC_IFPMIC_EVENT) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=pdic@INFO=i2c_fail"); +#else + sec_abc_send_event("MODULE=pdic@WARN=i2c_fail"); +#endif +#endif + } + + return ret; +} +EXPORT_SYMBOL_GPL(max77705_write_reg_nolock); + +int max77705_bulk_write(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct max77705_dev *max77705 = i2c_get_clientdata(i2c); +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif + int ret = -EIO, i; + int timeout = 2000; /* 2sec */ + int interval = 100; + + while (ret == -EIO) { + mutex_lock(&max77705->i2c_lock); + for (i = 0; i < I2C_RETRY_CNT; ++i) { + ret = i2c_smbus_write_i2c_block_data(i2c, reg, count, buf); + if ((ret >= 0) || (ret == -EIO)) + break; + pr_info("%s:%s reg(0x%x), ret(%d), i2c_retry_cnt(%d/%d)\n", + MFD_DEV_NAME, __func__, reg, ret, i + 1, I2C_RETRY_CNT); + } + mutex_unlock(&max77705->i2c_lock); + + if (ret < 0) { + pr_info("%s:%s reg(0x%x), ret(%d), timeout %d\n", + MFD_DEV_NAME, __func__, reg, ret, timeout); + + if (timeout < 0) + break; + + msleep(interval); + timeout -= interval; + } + } + if (ret < 0) { + + if (max77705->suspended) + return ret; + +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); +#endif + +#if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_ABC_IFPMIC_EVENT) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=pdic@INFO=i2c_fail"); +#else + sec_abc_send_event("MODULE=pdic@WARN=i2c_fail"); +#endif +#endif + } + + return ret; +} +EXPORT_SYMBOL_GPL(max77705_bulk_write); + +int max77705_write_word(struct i2c_client *i2c, u8 reg, u16 value) +{ + struct max77705_dev *max77705 = i2c_get_clientdata(i2c); +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif + int ret, i; + + mutex_lock(&max77705->i2c_lock); + for (i = 0; i < I2C_RETRY_CNT; ++i) { + ret = i2c_smbus_write_word_data(i2c, reg, value); + if (ret >= 0) + break; + pr_info("%s:%s reg(0x%x), ret(%d), i2c_retry_cnt(%d/%d)\n", + MFD_DEV_NAME, __func__, reg, ret, i + 1, I2C_RETRY_CNT); + } + mutex_unlock(&max77705->i2c_lock); + + if (ret < 0) { + + if (max77705->suspended) + return ret; + +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); +#endif + +#if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_ABC_IFPMIC_EVENT) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=pdic@INFO=i2c_fail"); +#else + sec_abc_send_event("MODULE=pdic@WARN=i2c_fail"); +#endif +#endif + } + + return ret; +} +EXPORT_SYMBOL_GPL(max77705_write_word); + +int max77705_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask) +{ + struct max77705_dev *max77705 = i2c_get_clientdata(i2c); +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif + int ret, i; + u8 old_val, new_val; + + mutex_lock(&max77705->i2c_lock); + for (i = 0; i < I2C_RETRY_CNT; ++i) { + ret = i2c_smbus_read_byte_data(i2c, reg); + if (ret >= 0) + break; + pr_info("%s:%s read reg(0x%x), ret(%d), i2c_retry_cnt(%d/%d)\n", + MFD_DEV_NAME, __func__, reg, ret, i + 1, I2C_RETRY_CNT); + } + if (ret < 0) + goto err; + + if (ret >= 0) { + old_val = ret & 0xff; + new_val = (val & mask) | (old_val & (~mask)); + for (i = 0; i < I2C_RETRY_CNT; ++i) { + ret = i2c_smbus_write_byte_data(i2c, reg, new_val); + if (ret >= 0) + break; + pr_info("%s:%s write reg(0x%x), ret(%d), i2c_retry_cnt(%d/%d)\n", + MFD_DEV_NAME, __func__, reg, ret, i + 1, I2C_RETRY_CNT); + } + if (ret < 0) + goto err; + } +err: + mutex_unlock(&max77705->i2c_lock); + + if (ret < 0) { + + if (max77705->suspended) + return ret; + +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); +#endif + +#if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_ABC_IFPMIC_EVENT) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=pdic@INFO=i2c_fail"); +#else + sec_abc_send_event("MODULE=pdic@WARN=i2c_fail"); +#endif +#endif + } + + return ret; +} +EXPORT_SYMBOL_GPL(max77705_update_reg); + +#if defined(CONFIG_OF) +static int of_max77705_dt(struct device *dev, struct max77705_platform_data *pdata) +{ + struct device_node *np_max77705 = dev->of_node; + struct device_node *np_battery; + int ret, val; + + if (!np_max77705) + return -EINVAL; + + pdata->irq_gpio = of_get_named_gpio(np_max77705, "max77705,irq-gpio", 0); + pr_info("%s: irq-gpio: %u\n", __func__, pdata->irq_gpio); + + pdata->wakeup = of_property_read_bool(np_max77705, "max77705,wakeup"); + + if (of_property_read_u32(np_max77705, "max77705,fw_product_id", &pdata->fw_product_id)) + pdata->fw_product_id = 0; + +#if defined(CONFIG_SEC_FACTORY) + pdata->blocking_waterevent = 0; +#else + pdata->blocking_waterevent = of_property_read_bool(np_max77705, "max77705,blocking_waterevent"); +#endif + ret = of_property_read_u32(np_max77705, "max77705,extra_fw_enable", &val); + if (ret) { + pr_info("%s: extra_fw_enable value not specified\n", __func__); + pdata->extra_fw_enable = 0; + } else { + pr_info("%s: extra_fw_enable: %d\n", __func__, val); + pdata->extra_fw_enable = val; + } + + ret = of_property_read_u32(np_max77705, "max77705,siso_ovp", &val); + if (ret) { + pr_info("%s: siso_ovp value not specified\n", __func__); + pdata->siso_ovp = 0; + } else { + pr_info("%s: siso_ovp: %d\n", __func__, val); + pdata->siso_ovp = val; + } + + np_battery = of_find_node_by_name(NULL, "mfc-charger"); + if (!np_battery) { + pr_info("%s: np_battery NULL\n", __func__); + } else { + pdata->wpc_en = of_get_named_gpio(np_battery, "battery,wpc_en", 0); + if (pdata->wpc_en < 0) { + pr_info("%s: can't get wpc_en (%d)\n", __func__, pdata->wpc_en); + pdata->wpc_en = 0; + } + + ret = of_property_read_string(np_battery, + "battery,wireless_charger_name", (char const **)&pdata->wireless_charger_name); + if (ret) + pr_info("%s: Wireless charger name is Empty\n", __func__); + } + pdata->support_audio = of_property_read_bool(np_max77705, "max77705,support-audio"); + pr_info("%s: support_audio %d\n", __func__, pdata->support_audio); + + return 0; +} +#endif /* CONFIG_OF */ +/* samsung */ +#if IS_ENABLED(CONFIG_CCIC_MAX77705) +static void max77705_reset_ic(struct max77705_dev *max77705) +{ + pr_info("Reset!!"); + max77705_write_reg(max77705->muic, 0x80, 0x0F); +#if defined(CONFIG_IFPMIC_CRC_CHECK) + msleep(300); +#else + msleep(100); +#endif +} + +static void max77705_usbc_wait_response_q(struct work_struct *work) +{ + struct max77705_dev *max77705; + u8 read_value = 0x00; + u8 dummy[2] = { 0, }; + + max77705 = container_of(work, struct max77705_dev, fw_work); + + while (max77705->fw_update_state == FW_UPDATE_WAIT_RESP_START) { + switch (max77705->pmic_rev) { + case MAX77705_PASS1: + max77705_bulk_read(max77705->muic, REG_UIC_INT-1, 2, dummy); + read_value = dummy[1]; + break; + default: + max77705_bulk_read(max77705->muic, REG_UIC_INT, 1, dummy); + read_value = dummy[0]; + break; + } + if ((read_value & BIT_APCmdResI) == BIT_APCmdResI) + break; + } + + complete_all(&max77705->fw_completion); +} + +static int max77705_usbc_wait_response(struct max77705_dev *max77705) +{ + unsigned long time_remaining = 0; + + max77705->fw_update_state = FW_UPDATE_WAIT_RESP_START; + + init_completion(&max77705->fw_completion); + queue_work(max77705->fw_workqueue, &max77705->fw_work); + + time_remaining = wait_for_completion_timeout( + &max77705->fw_completion, + msecs_to_jiffies(FW_WAIT_TIMEOUT)); + + max77705->fw_update_state = FW_UPDATE_WAIT_RESP_STOP; + + if (!time_remaining) { + pr_info("Failed to update due to timeout"); + cancel_work_sync(&max77705->fw_work); + return FW_UPDATE_TIMEOUT_FAIL; + } + + return 0; +} + +static int __max77705_usbc_fw_update( + struct max77705_dev *max77705, const u8 *fw_bin) +{ + u8 fw_cmd = FW_CMD_END; + u8 fw_len = 0; + u8 fw_opcode = 0; + u8 fw_data_len = 0; + u8 fw_data[FW_CMD_WRITE_SIZE] = { 0, }; + u8 verify_data[FW_VERIFY_DATA_SIZE] = { 0, }; + int ret = -FW_UPDATE_CMD_FAIL; + + /* + * fw_bin[0] = Write Command (0x01) + * or + * fw_bin[0] = Read Command (0x03) + * or + * fw_bin[0] = End Command (0x00) + */ + fw_cmd = fw_bin[0]; + + /* + * Check FW Command + */ + if (fw_cmd == FW_CMD_END) { + max77705_reset_ic(max77705); + max77705->fw_update_state = FW_UPDATE_END; + return FW_UPDATE_END; + } + + /* + * fw_bin[1] = Length ( OPCode + Data ) + */ + fw_len = fw_bin[1]; + + /* + * Check fw data length + * We support 0x22 or 0x04 only + */ + if (fw_len != 0x22 && fw_len != 0x04) + return FW_UPDATE_MAX_LENGTH_FAIL; + + /* + * fw_bin[2] = OPCode + */ + fw_opcode = fw_bin[2]; + + /* + * In case write command, + * fw_bin[35:3] = Data + * + * In case read command, + * fw_bin[5:3] = Data + */ + fw_data_len = fw_len - 1; /* exclude opcode */ + memcpy(fw_data, &fw_bin[3], fw_data_len); + + switch (fw_cmd) { + case FW_CMD_WRITE: + if (fw_data_len > I2C_SMBUS_BLOCK_MAX) { + /* write the half data */ + max77705_bulk_write(max77705->muic, + fw_opcode, + I2C_SMBUS_BLOCK_HALF, + fw_data); + max77705_bulk_write(max77705->muic, + fw_opcode + I2C_SMBUS_BLOCK_HALF, + fw_data_len - I2C_SMBUS_BLOCK_HALF, + &fw_data[I2C_SMBUS_BLOCK_HALF]); + } else + max77705_bulk_write(max77705->muic, + fw_opcode, + fw_data_len, + fw_data); + + ret = max77705_usbc_wait_response(max77705); + if (ret) + return ret; + + /* + * Why do we need 1ms sleep in case MQ81? + */ + /* msleep(1); */ + + return FW_CMD_WRITE_SIZE; + + + case FW_CMD_READ: + max77705_bulk_read(max77705->muic, + fw_opcode, + fw_data_len, + verify_data); + /* + * Check fw data sequence number + * It should be increased from 1 step by step. + */ + if (memcmp(verify_data, &fw_data[1], 2)) { + pr_info("[0x%02x 0x%02x], [0x%02x, 0x%02x], [0x%02x, 0x%02x]", + verify_data[0], fw_data[0], + verify_data[1], fw_data[1], + verify_data[2], fw_data[2]); + return FW_UPDATE_VERIFY_FAIL; + } + + return FW_CMD_READ_SIZE; + } + + pr_info("Command error"); + + return ret; +} + +int max77705_write_jig_on(struct max77705_dev *max77705) +{ + u8 write_values[OPCODE_MAX_LENGTH] = { 0, }; + int ret = 0; + int length = 0x2; + int i = 0; + + write_values[0] = OPCODE_CTRL3_W; + write_values[1] = 0x1; + write_values[2] = 0x1; + + for (i = 0; i < length + OPCODE_SIZE; i++) + pr_info("[%d], 0x[%x]", i, write_values[i]); + + /* Write opcode and data */ + ret = max77705_bulk_write(max77705->muic, OPCODE_WRITE, + length + OPCODE_SIZE, write_values); + /* Write end of data by 0x00 */ + if (length < OPCODE_DATA_LENGTH) + max77705_write_reg(max77705->muic, OPCODE_WRITE_END, 0x00); + return 0; +} + +#if defined(CONFIG_SEC_FACTORY) +int max77705_write_fw_noautoibus(struct max77705_dev *max77705) +{ + u8 write_values[OPCODE_MAX_LENGTH] = { 0, }; + int ret = 0; + int length = 0x1; + int i = 0; + + write_values[0] = OPCODE_SAMSUNG_FW_AUTOIBUS; + write_values[1] = 0x3; /* usbc fw off & auto off(manual on) */ + + for (i = 0; i < length + OPCODE_SIZE; i++) + pr_info("[%d], 0x[%x]", i, write_values[i]); + + /* Write opcode and data */ + ret = max77705_bulk_write(max77705->muic, OPCODE_WRITE, + length + OPCODE_SIZE, write_values); + /* Write end of data by 0x00 */ + if (length < OPCODE_DATA_LENGTH) + max77705_write_reg(max77705->muic, OPCODE_WRITE_END, 0x00); + return 0; +} +#endif + +static int max77705_fuelgauge_read_vcell(struct max77705_dev *max77705) +{ + u8 data[2]; + u32 vcell; + u16 w_data; + u32 temp; + u32 temp2; + + if (max77705_bulk_read(max77705->fuelgauge, VCELL_REG, 2, data) < 0) { + pr_err("%s: Failed to read VCELL\n", __func__); + return -1; + } + + w_data = (data[1] << 8) | data[0]; + + temp = (w_data & 0xFFF) * 78125; + vcell = temp / 1000000; + + temp = ((w_data & 0xF000) >> 4) * 78125; + temp2 = temp / 1000000; + vcell += (temp2 << 4); + + return vcell; +} + +static void max77705_wc_control(struct max77705_dev *max77705, bool enable) +{ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + union power_supply_propval value = {0, }; + char wpc_en_status[2]; + int ret = 0; + + wpc_en_status[0] = WPC_EN_CCIC; + wpc_en_status[1] = enable ? true : false; + value.strval= wpc_en_status; + ret = psy_do_property(max77705->pdata->wireless_charger_name, set, POWER_SUPPLY_EXT_PROP_WPC_EN, value); + + if (ret < 0) { + if (max77705->pdata->wpc_en) { + if (enable) { + gpio_direction_output(max77705->pdata->wpc_en, 0); + pr_info("%s: WC CONTROL: ENABLE", __func__); + } else { + gpio_direction_output(max77705->pdata->wpc_en, 1); + pr_info("%s: WC CONTROL: DISABLE", __func__); + } + } else { + pr_info("%s : no wpc_en\n", __func__); + } + } else { + pr_info("%s: WC CONTROL: %s", __func__, wpc_en_status[1] ? "Enable" : "Disable"); + } + + pr_info("%s: wpc_en(%d)\n", __func__, gpio_get_value(max77705->pdata->wpc_en)); +#endif +} + +int max77705_usbc_fw_update(struct max77705_dev *max77705, + const u8 *fw_bin, int fw_bin_len, int enforce_do) +{ + max77705_fw_header *fw_header; +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif + int offset = 0; + unsigned long duration = 0; + int size = 0; + int try_count = 0; + int ret = 0; + u8 usbc_status1 = 0x0; + u8 pd_status1 = 0x0; + static u8 fct_id; /* FCT cable */ + u8 uidadc; /* FCT cable */ + u8 try_command = 0; +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + u8 sw_boot = 0; +#endif + u8 chg_cnfg_00 = 0; + bool chg_mode_changed = 0; + bool wpc_en_changed = 0; + int vcell = 0; + u8 chgin_dtls = 0; + u8 wcin_dtls = 0; + int error = 0; + + max77705->fw_size = fw_bin_len; + fw_header = (max77705_fw_header *)fw_bin; + pr_info("FW: magic/%x/ major/%x/ minor/%x/ product_id/%x/ rev/%x/ id/%x/", + fw_header->magic, fw_header->major, fw_header->minor, fw_header->product_id, fw_header->rev, fw_header->id); + + if(max77705->device_product_id != fw_header->product_id) { + pr_info("product indicator mismatch"); + return 0; + } + + if(fw_header->magic == MAX77705_SIGN) + pr_info("FW: matched"); + + max77705_read_reg(max77705->charger, MAX77705_CHG_REG_CNFG_00, &chg_cnfg_00); +retry: + disable_irq(max77705->irq); + max77705_write_reg(max77705->muic, REG_PD_INT_M, 0xFF); + max77705_write_reg(max77705->muic, REG_CC_INT_M, 0xFF); + max77705_write_reg(max77705->muic, REG_UIC_INT_M, 0xFF); + max77705_write_reg(max77705->muic, REG_VDM_INT_M, 0xFF); + + offset = 0; + duration = 0; + size = 0; + ret = 0; + + /* to do (unmask interrupt) */ + ret = max77705_read_reg(max77705->muic, REG_UIC_FW_REV, &max77705->FW_Revision); + ret = max77705_read_reg(max77705->muic, REG_UIC_FW_MINOR, &max77705->FW_Minor_Revision); + if (ret < 0 && (try_count == 0 && try_command == 0)) { + pr_info("Failed to read FW_REV"); + error = -EIO; + goto out; + } + + duration = jiffies; + + max77705->FW_Product_ID = max77705->FW_Minor_Revision >> FW_PRODUCT_ID_REG; + max77705->FW_Minor_Revision &= MINOR_VERSION_MASK; + pr_info("chip : %02X.%02X(PID 0x%x), FW : %02X.%02X(PID 0x%x)", + max77705->FW_Revision, max77705->FW_Minor_Revision, max77705->FW_Product_ID, + fw_header->major, fw_header->minor, fw_header->product_id); +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + store_ccic_bin_version(&fw_header->major, &sw_boot); +#endif + +#ifdef CONFIG_SEC_FACTORY + if ((max77705->FW_Revision != fw_header->major) || (max77705->FW_Minor_Revision != fw_header->minor)) { +#else + if ((max77705->FW_Revision == 0xff) || (max77705->FW_Revision < fw_header->major) || + ((max77705->FW_Revision == fw_header->major) && (max77705->FW_Minor_Revision < fw_header->minor)) || + (max77705->FW_Product_ID != fw_header->product_id) || + enforce_do) { +#endif + if (!enforce_do && max77705->pmic_rev > MAX77705_PASS1) { /* on Booting time */ + max77705_read_reg(max77705->muic, REG_PD_STATUS1, &pd_status1); + fct_id = (pd_status1 & BIT_FCT_ID) >> FFS(BIT_FCT_ID); + + max77705_read_reg(max77705->muic, REG_USBC_STATUS1, &usbc_status1); + uidadc = (usbc_status1 & BIT_UIDADC) >> FFS(BIT_UIDADC); +#ifdef DEBUG_MAX77705 + msg_maxim("FCT_ID : 0x%x UIDADC : 0x%x", fct_id, uidadc); +#endif + } + + if (try_count == 0 && try_command == 0) { + /* change chg_mode during FW update */ + vcell = max77705_fuelgauge_read_vcell(max77705); + + if (vcell < 3600) { + pr_info("%s: keep chg_mode(0x%x), vcell(%dmv)\n", + __func__, chg_cnfg_00 & 0x0F, vcell); + error = -EAGAIN; + goto out; + } + } + + max77705_read_reg(max77705->charger, MAX77705_CHG_REG_DETAILS_00, &wcin_dtls); + wcin_dtls = (wcin_dtls & 0x18) >> 3; + + wpc_en_changed = true; + max77705_wc_control(max77705, false); + + max77705_read_reg(max77705->charger, MAX77705_CHG_REG_DETAILS_00, &chgin_dtls); + + chgin_dtls = ((chgin_dtls & 0x60) >> 5); + + pr_info("%s: chgin_dtls:0x%x, wcin_dtls:0x%x\n", + __func__, chgin_dtls, wcin_dtls); + +#if !IS_ENABLED(CONFIG_SEC_FACTORY) + if (try_count == 0 && try_command == 0) { + if (chgin_dtls == 0x0 && wcin_dtls == 0x0) { + pr_info("%s: Battery only mode\n", __func__); + } else { + /* adb update case */ + if (enforce_do == 2) { + pr_info("%s: USB mode (ADB)\n", __func__); + } else { + error = -EAGAIN; + pr_info("%s: TA mode\n", __func__); + goto out; + } + } + } +#endif + if ((chgin_dtls != 0x3) && (wcin_dtls != 0x3)) { + chg_mode_changed = true; + /* Switching Frequency : 3MHz */ + max77705_update_reg(max77705->charger, + MAX77705_CHG_REG_CNFG_08, 0x00, 0x3); + pr_info("%s: +set Switching Frequency 3Mhz\n", __func__); + + /* Disable skip mode */ + max77705_update_reg(max77705->charger, + MAX77705_CHG_REG_CNFG_12, 0x1, 0x1); + pr_info("%s: +set Disable skip mode\n", __func__); + + max77705_update_reg(max77705->charger, + MAX77705_CHG_REG_CNFG_00, 0x09, 0x0F); + pr_info("%s: +change chg_mode(0x9), vcell(%dmv)\n", + __func__, vcell); + } else { + if (chg_mode_changed) { + chg_mode_changed = false; + /* Auto skip mode */ + max77705_update_reg(max77705->charger, + MAX77705_CHG_REG_CNFG_12, 0x0, 0x1); + pr_info("%s: -set Auto skip mode\n", __func__); + + max77705_update_reg(max77705->charger, + MAX77705_CHG_REG_CNFG_12, 0x0, 0x20); + pr_info("%s: -disable CHGINSEL\n", __func__); + + max77705_update_reg(max77705->charger, + MAX77705_CHG_REG_CNFG_00, 0x4, 0x0F); + pr_info("%s: -set chg_mode(0x4)\n", __func__); + + max77705_update_reg(max77705->charger, + MAX77705_CHG_REG_CNFG_12, 0x20, 0x20); + pr_info("%s: -enable CHGINSEL\n", __func__); + + max77705_update_reg(max77705->charger, + MAX77705_CHG_REG_CNFG_00, chg_cnfg_00, 0x0F); + pr_info("%s: -recover chg_mode(0x%x), vcell(%dmv)\n", + __func__, chg_cnfg_00 & 0x0F, vcell); + + /* Switching Frequency : 1.5MHz */ + max77705_update_reg(max77705->charger, + MAX77705_CHG_REG_CNFG_08, 0x02, 0x3); + + pr_info("%s: -set Switching Frequency 1.5MHz\n", __func__); + } + } + msleep(500); + + max77705_write_reg(max77705->muic, 0x21, 0xD0); + max77705_write_reg(max77705->muic, 0x41, 0x00); + msleep(500); + + max77705_read_reg(max77705->muic, REG_UIC_FW_REV, &max77705->FW_Revision); + max77705_read_reg(max77705->muic, REG_UIC_FW_MINOR, &max77705->FW_Minor_Revision); + max77705->FW_Minor_Revision &= MINOR_VERSION_MASK; + pr_info("Start FW updating (%02X.%02X)", max77705->FW_Revision, max77705->FW_Minor_Revision); + + if (max77705->FW_Revision != 0xFF) { + if (++try_command < FW_SECURE_MODE_TRY_COUNT) { + pr_info("the Fail to enter secure mode %d", + try_command); + max77705_reset_ic(max77705); + msleep(1000); + goto retry; + } else { + pr_info("the Secure Update Fail!!"); +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_FWUP_ERROR_COUNT); +#endif + error = -EIO; + goto out; + } + } + + try_command = 0; + + for (offset = FW_HEADER_SIZE; + offset < fw_bin_len && size != FW_UPDATE_END;) { + + size = __max77705_usbc_fw_update(max77705, &fw_bin[offset]); + + switch (size) { + case FW_UPDATE_VERIFY_FAIL: + offset -= FW_CMD_WRITE_SIZE; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) + fallthrough; +#endif + case FW_UPDATE_TIMEOUT_FAIL: + /* + * Retry FW updating + */ + if (++try_count < FW_VERIFY_TRY_COUNT) { + pr_info("Retry fw write. ret %d, count %d, offset %d", + size, try_count, offset); + max77705_reset_ic(max77705); + msleep(1000); + goto retry; + } else { + pr_info("Failed to update FW. ret %d, offset %d", + size, (offset + size)); +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_FWUP_ERROR_COUNT); +#endif + error = -EIO; + goto out; + } + break; + case FW_UPDATE_CMD_FAIL: + case FW_UPDATE_MAX_LENGTH_FAIL: + pr_info("Failed to update FW. ret %d, offset %d", + size, (offset + size)); +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_FWUP_ERROR_COUNT); +#endif + error = -EIO; + goto out; + case FW_UPDATE_END: /* 0x00 */ + /* JIG PIN for setting HIGH. */ + if (fct_id == FCT_523Kohm && !enforce_do) + max77705_write_jig_on(max77705); + max77705_read_reg(max77705->muic, + REG_UIC_FW_REV, &max77705->FW_Revision); + max77705_read_reg(max77705->muic, + REG_UIC_FW_MINOR, &max77705->FW_Minor_Revision); + max77705->FW_Minor_Revision &= MINOR_VERSION_MASK; + pr_info("chip : %02X.%02X, FW : %02X.%02X", + max77705->FW_Revision, max77705->FW_Minor_Revision, + fw_header->major, fw_header->minor); + pr_info("Completed"); +#if defined(CONFIG_SEC_FACTORY) + if (max77705_get_facmode()) + max77705_write_fw_noautoibus(max77705); +#endif + break; + default: + offset += size; + break; + } + if (offset == fw_bin_len) { + max77705_reset_ic(max77705); + max77705_read_reg(max77705->muic, + REG_UIC_FW_REV, &max77705->FW_Revision); + max77705_read_reg(max77705->muic, + REG_UIC_FW_MINOR, &max77705->FW_Minor_Revision); + max77705->FW_Minor_Revision &= MINOR_VERSION_MASK; + pr_info("chip : %02X.%02X, FW : %02X.%02X", + max77705->FW_Revision, max77705->FW_Minor_Revision, + fw_header->major, fw_header->minor); + pr_info("Completed via SYS path"); + } + } + } else { + pr_info("Don't need to update!"); + goto out; + } + + duration = jiffies - duration; + pr_info("Duration : %dms", jiffies_to_msecs(duration)); +out: + if (chg_mode_changed) { + vcell = max77705_fuelgauge_read_vcell(max77705); + /* Auto skip mode */ + max77705_update_reg(max77705->charger, + MAX77705_CHG_REG_CNFG_12, 0x0, 0x1); + pr_info("%s: -set Auto skip mode\n", __func__); + + max77705_update_reg(max77705->charger, + MAX77705_CHG_REG_CNFG_12, 0x0, 0x20); + pr_info("%s: -disable CHGINSEL\n", __func__); + + max77705_update_reg(max77705->charger, + MAX77705_CHG_REG_CNFG_00, 0x4, 0x0F); + pr_info("%s: -set chg_mode(0x4)\n", __func__); + + max77705_update_reg(max77705->charger, + MAX77705_CHG_REG_CNFG_12, 0x20, 0x20); + pr_info("%s: -enable CHGINSEL\n", __func__); + + max77705_update_reg(max77705->charger, + MAX77705_CHG_REG_CNFG_00, chg_cnfg_00, 0x0F); + pr_info("%s: -recover chg_mode(0x%x), vcell(%dmv)\n", + __func__, chg_cnfg_00 & 0x0F, vcell); + + /* Switching Frequency : 1.5MHz */ + max77705_update_reg(max77705->charger, + MAX77705_CHG_REG_CNFG_08, 0x02, 0x3); + pr_info("%s: -set Switching Frequency 1.5MHz\n", __func__); + } + + if (wpc_en_changed) { + max77705_wc_control(max77705, true); + } + enable_irq(max77705->irq); + return error; +} +EXPORT_SYMBOL_GPL(max77705_usbc_fw_update); + +void max77705_usbc_fw_setting(struct max77705_dev *max77705, int enforce_do) +{ +#if defined(CONFIG_MAX77705_USE_EXTRA_FW) + if (max77705->pdata->extra_fw_enable) { + max77705_usbc_fw_update(max77705, BOOT_FLASH_FW_EXTRA, ARRAY_SIZE(BOOT_FLASH_FW_EXTRA), enforce_do); + pr_info("%s: extra fw update\n", __func__); + return; + } +#endif + + switch (max77705->pmic_rev) { + case MAX77705_PASS1: + case MAX77705_PASS2: + case MAX77705_PASS3: + case MAX77705_PASS4: + pr_info("[MAX77705] Couldn't update the MAX77705 FirmWare\n"); + break; + case MAX77705_PASS5: + max77705_usbc_fw_update(max77705, BOOT_FLASH_FW_PASS2, ARRAY_SIZE(BOOT_FLASH_FW_PASS2), enforce_do); + break; + default: + pr_info("[MAX77705] FAIL F/W ON BOOTING TIME and PMIC_REVISION isn't valid\n"); + break; + }; +} +EXPORT_SYMBOL_GPL(max77705_usbc_fw_setting); +#endif + +static u8 max77705_revision_check(u8 pmic_id, u8 pmic_rev) { + int i, logical_id = 0; + int pmic_arrary = ARRAY_SIZE(max77705_revision); + for (i = 0; i < pmic_arrary; i++) { + if (max77705_revision[i].id == pmic_id && + max77705_revision[i].rev == pmic_rev) + logical_id = max77705_revision[i].logical_id; + } + return logical_id; +} + +void max77705_register_pdmsg_func(struct max77705_dev *max77705, + void (*check_pdmsg)(void *data, u8 pdmsg), void *data) +{ + if (!max77705) { + pr_err("%s max77705 is null\n", __func__); + return; + } + + max77705->check_pdmsg = check_pdmsg; + max77705->usbc_data = data; +} +EXPORT_SYMBOL_GPL(max77705_register_pdmsg_func); + +static int max77705_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *dev_id) +{ + struct max77705_dev *max77705; + struct max77705_platform_data *pdata = i2c->dev.platform_data; + int ret = 0; + u8 pmic_id, pmic_rev = 0; + + pr_info("%s:%s\n", MFD_DEV_NAME, __func__); + + max77705 = kzalloc(sizeof(struct max77705_dev), GFP_KERNEL); + if (!max77705) + return -ENOMEM; + + if (i2c->dev.of_node) { + pdata = devm_kzalloc(&i2c->dev, sizeof(struct max77705_platform_data), + GFP_KERNEL); + if (!pdata) { + ret = -ENOMEM; + goto err; + } + + ret = of_max77705_dt(&i2c->dev, pdata); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to get device of_node\n"); + goto err; + } + + i2c->dev.platform_data = pdata; + } else + pdata = i2c->dev.platform_data; + + max77705->dev = &i2c->dev; + max77705->i2c = i2c; + max77705->irq = i2c->irq; + if (pdata) { + max77705->pdata = pdata; + + pdata->irq_base = irq_alloc_descs(-1, 0, MAX77705_IRQ_NR, -1); + if (pdata->irq_base < 0) { + pr_err("%s:%s irq_alloc_descs Fail! ret(%d)\n", + MFD_DEV_NAME, __func__, pdata->irq_base); + ret = -EINVAL; + goto err; + } else + max77705->irq_base = pdata->irq_base; + + max77705->irq_gpio = pdata->irq_gpio; + max77705->wakeup = pdata->wakeup; + max77705->blocking_waterevent = pdata->blocking_waterevent; + max77705->device_product_id = pdata->fw_product_id; + } else { + ret = -EINVAL; + goto err; + } + mutex_init(&max77705->i2c_lock); + + max77705->suspended = false; +#if defined(CONFIG_QCOM_IFPMIC_SUSPEND) + init_waitqueue_head(&max77705->suspend_wait); +#endif + + i2c_set_clientdata(i2c, max77705); + + if (max77705_read_reg(i2c, MAX77705_PMIC_REG_PMICID1, &pmic_id) < 0) { + dev_err(max77705->dev, "device not found on this channel (this is not an error)\n"); + ret = -ENODEV; + goto err_w_lock; + } + if (max77705_read_reg(i2c, MAX77705_PMIC_REG_PMICREV, &pmic_rev) < 0) { + dev_err(max77705->dev, "device not found on this channel (this is not an error)\n"); + ret = -ENODEV; + goto err_w_lock; + } + + pr_info("%s:%s pmic_id:%x, pmic_rev:%x\n", + MFD_DEV_NAME, __func__, pmic_id, pmic_rev); + + max77705->pmic_rev = max77705_revision_check(pmic_id, pmic_rev & 0x7); + if (max77705->pmic_rev == 0) { + dev_err(max77705->dev, "Can not find matched revision\n"); + ret = -ENODEV; + goto err_w_lock; + } + max77705->pmic_ver = ((pmic_rev & 0xF8) >> 0x3); + + /* print rev */ + pr_info("%s:%s device found: rev:%x ver:%x\n", + MFD_DEV_NAME, __func__, max77705->pmic_rev, max77705->pmic_ver); + + /* No active discharge on safeout ldo 1,2 */ + /* max77705_update_reg(i2c, MAX77705_PMIC_REG_SAFEOUT_CTRL, 0x00, 0x30); */ +#if IS_ENABLED(CONFIG_CCIC_MAX77705) + init_completion(&max77705->fw_completion); + max77705->fw_workqueue = create_singlethread_workqueue("fw_update"); + if (max77705->fw_workqueue == NULL) + return -ENOMEM; + INIT_WORK(&max77705->fw_work, max77705_usbc_wait_response_q); +#endif +#if defined(CONFIG_QCOM_IFPMIC_SUSPEND) + init_waitqueue_head(&max77705->queue_empty_wait_q); +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0) + max77705->muic = i2c_new_dummy(i2c->adapter, I2C_ADDR_MUIC); +#else + max77705->muic = i2c_new_dummy_device(i2c->adapter, I2C_ADDR_MUIC); +#endif + i2c_set_clientdata(max77705->muic, max77705); +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0) + max77705->charger = i2c_new_dummy(i2c->adapter, I2C_ADDR_CHG); +#else + max77705->charger = i2c_new_dummy_device(i2c->adapter, I2C_ADDR_CHG); +#endif + i2c_set_clientdata(max77705->charger, max77705); +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0) + max77705->fuelgauge = i2c_new_dummy(i2c->adapter, I2C_ADDR_FG); +#else + max77705->fuelgauge = i2c_new_dummy_device(i2c->adapter, I2C_ADDR_FG); +#endif + i2c_set_clientdata(max77705->fuelgauge, max77705); + +#if IS_ENABLED(CONFIG_CCIC_MAX77705) + max77705_usbc_fw_setting(max77705, 0); +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0) + max77705->debug = i2c_new_dummy(i2c->adapter, I2C_ADDR_DEBUG); +#else + max77705->debug = i2c_new_dummy_device(i2c->adapter, I2C_ADDR_DEBUG); +#endif + i2c_set_clientdata(max77705->debug, max77705); + + disable_irq(max77705->irq); + ret = max77705_irq_init(max77705); + + if (ret < 0) + goto err_irq_init; + + ret = mfd_add_devices(max77705->dev, -1, max77705_devs, + ARRAY_SIZE(max77705_devs), NULL, 0, NULL); + if (ret < 0) + goto err_mfd; + + device_init_wakeup(max77705->dev, pdata->wakeup); + + return ret; + +err_mfd: + mfd_remove_devices(max77705->dev); +err_irq_init: + i2c_unregister_device(max77705->muic); + i2c_unregister_device(max77705->charger); + i2c_unregister_device(max77705->fuelgauge); + i2c_unregister_device(max77705->debug); +err_w_lock: + mutex_destroy(&max77705->i2c_lock); +err: + kfree(max77705); + return ret; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) +static int max77705_i2c_remove(struct i2c_client *i2c) +#else +static void max77705_i2c_remove(struct i2c_client *i2c) +#endif +{ + struct max77705_dev *max77705 = i2c_get_clientdata(i2c); + + device_init_wakeup(max77705->dev, 0); + max77705_irq_exit(max77705); + mfd_remove_devices(max77705->dev); + i2c_unregister_device(max77705->muic); + i2c_unregister_device(max77705->charger); + i2c_unregister_device(max77705->fuelgauge); + i2c_unregister_device(max77705->debug); + kfree(max77705); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) + return 0; +#else + return; +#endif +} + +static const struct i2c_device_id max77705_i2c_id[] = { + { MFD_DEV_NAME, TYPE_MAX77705 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max77705_i2c_id); + +#if defined(CONFIG_OF) +static const struct of_device_id max77705_i2c_dt_ids[] = { + { .compatible = "maxim,max77705" }, + { }, +}; +MODULE_DEVICE_TABLE(of, max77705_i2c_dt_ids); +#endif /* CONFIG_OF */ + +#if defined(CONFIG_PM) +static int max77705_suspend(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max77705_dev *max77705 = i2c_get_clientdata(i2c); + + pr_info("%s:%s\n", MFD_DEV_NAME, __func__); + + if (device_may_wakeup(dev)) + enable_irq_wake(max77705->irq); + +#if defined(CONFIG_QCOM_IFPMIC_SUSPEND) + wait_event_interruptible_timeout(max77705->queue_empty_wait_q, + (!max77705->doing_irq) && (!max77705->is_usbc_queue), 1*HZ); +#endif + +#if !defined(CONFIG_QCOM_IFPMIC_SUSPEND) + disable_irq(max77705->irq); +#endif + max77705->suspended = true; + + return 0; +} + +static int max77705_resume(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max77705_dev *max77705 = i2c_get_clientdata(i2c); + + max77705->suspended = false; +#if defined(CONFIG_QCOM_IFPMIC_SUSPEND) + wake_up(&max77705->suspend_wait); +#endif + + pr_info("%s:%s\n", MFD_DEV_NAME, __func__); + + if (device_may_wakeup(dev)) + disable_irq_wake(max77705->irq); + +#if !defined(CONFIG_QCOM_IFPMIC_SUSPEND) + enable_irq(max77705->irq); +#endif + + return 0; +} +#else +#define max77705_suspend NULL +#define max77705_resume NULL +#endif /* CONFIG_PM */ + +const struct dev_pm_ops max77705_pm = { + .suspend = max77705_suspend, + .resume = max77705_resume, +}; + +static struct i2c_driver max77705_i2c_driver = { + .driver = { + .name = MFD_DEV_NAME, + .owner = THIS_MODULE, +#if defined(CONFIG_PM) + .pm = &max77705_pm, +#endif /* CONFIG_PM */ +#if defined(CONFIG_OF) + .of_match_table = max77705_i2c_dt_ids, +#endif /* CONFIG_OF */ + }, + .probe = max77705_i2c_probe, + .remove = max77705_i2c_remove, + .id_table = max77705_i2c_id, +}; + +static int __init max77705_i2c_init(void) +{ + pr_info("%s:%s\n", MFD_DEV_NAME, __func__); + return i2c_add_driver(&max77705_i2c_driver); +} +/* init early so consumer devices can complete system boot */ +subsys_initcall(max77705_i2c_init); + +static void __exit max77705_i2c_exit(void) +{ + i2c_del_driver(&max77705_i2c_driver); +} +module_exit(max77705_i2c_exit); + +MODULE_AUTHOR("Samsung USB Team"); +MODULE_DESCRIPTION("Max77705 MFD driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/maxim/max77775-irq.c b/drivers/mfd/maxim/max77775-irq.c new file mode 100755 index 000000000000..f0553cc29241 --- /dev/null +++ b/drivers/mfd/maxim/max77775-irq.c @@ -0,0 +1,482 @@ +/* + * max77775-irq.c - Interrupt controller support for MAX77775 + * + * Copyright (C) 2016 Samsung Electronics Co.Ltd + * Insun Choi + * + * 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, see . + * + * This driver is based on max77775-irq.c + */ + +#include +#include +#include +#include +#include +#include +#include + +static const u8 max77775_mask_reg[] = { + /* TODO: Need to check other INTMASK */ + [SYS_INT] = MAX77775_PMIC_REG_SYSTEM_INT_MASK, + [CHG_INT] = MAX77775_CHG_REG_INT_MASK, + [FUEL_INT] = MAX77775_REG_INVALID, + [USBC_INT] = MAX77775_USBC_REG_UIC_INT_M, + [CC_INT] = MAX77775_USBC_REG_CC_INT_M, + [PD_INT] = MAX77775_USBC_REG_PD_INT_M, + [VDM_INT] = MAX77775_USBC_REG_VDM_INT_M, + [SPARE_INT] = MAX77775_USBC_REG_SPARE_INT_M, + [VIR_INT] = MAX77775_REG_INVALID, +}; + +static struct i2c_client *get_i2c(struct max77775_dev *max77775, + enum max77775_irq_source src) +{ + switch (src) { + case SYS_INT: + return max77775->i2c; + case FUEL_INT: + return max77775->fuelgauge; + case CHG_INT: + return max77775->charger; + case USBC_INT: + case CC_INT: + case PD_INT: + case VDM_INT: + case SPARE_INT: + case VIR_INT: + return max77775->muic; + default: + return ERR_PTR(-EINVAL); + } +} + +struct max77775_irq_data { + int mask; + enum max77775_irq_source group; +}; + +static const struct max77775_irq_data max77775_irqs[] = { + [MAX77775_SYSTEM_IRQ_SYSUVLO_INT] = { .group = SYS_INT, .mask = 1 << 4 }, + [MAX77775_SYSTEM_IRQ_SYSOVLO_INT] = { .group = SYS_INT, .mask = 1 << 5 }, + [MAX77775_SYSTEM_IRQ_TSHDN_INT] = { .group = SYS_INT, .mask = 1 << 6 }, + [MAX77775_SYSTEM_IRQ_SCP_INT] = { .group = SYS_INT, .mask = 1 << 7 }, + + [MAX77775_CHG_IRQ_BYP_I] = { .group = CHG_INT, .mask = 1 << 0 }, + [MAX77775_CHG_IRQ_BATP_I] = { .group = CHG_INT, .mask = 1 << 2 }, + [MAX77775_CHG_IRQ_BAT_I] = { .group = CHG_INT, .mask = 1 << 3 }, + [MAX77775_CHG_IRQ_CHG_I] = { .group = CHG_INT, .mask = 1 << 4 }, + [MAX77775_CHG_IRQ_WCIN_I] = { .group = CHG_INT, .mask = 1 << 5 }, + [MAX77775_CHG_IRQ_CHGIN_I] = { .group = CHG_INT, .mask = 1 << 6 }, + [MAX77775_CHG_IRQ_AICL_I] = { .group = CHG_INT, .mask = 1 << 7 }, + + [MAX77775_FG_IRQ_ALERT] = { .group = FUEL_INT, .mask = 1 << 1 }, + + [MAX77775_USBC_IRQ_APC_INT] = { .group = USBC_INT, .mask = 1 << 7 }, + [MAX77775_USBC_IRQ_SYSM_INT] = { .group = USBC_INT, .mask = 1 << 6 }, + [MAX77775_USBC_IRQ_VBUS_INT] = { .group = USBC_INT, .mask = 1 << 5 }, + [MAX77775_USBC_IRQ_VBADC_INT] = { .group = USBC_INT, .mask = 1 << 4 }, + [MAX77775_USBC_IRQ_DCD_INT] = { .group = USBC_INT, .mask = 1 << 3 }, + [MAX77775_USBC_IRQ_STOPMODE_INT] = { .group = USBC_INT, .mask = 1 << 2 }, + [MAX77775_USBC_IRQ_CHGT_INT] = { .group = USBC_INT, .mask = 1 << 1 }, + [MAX77775_USBC_IRQ_UIDADC_INT] = { .group = USBC_INT, .mask = 1 << 0 }, + + [MAX77775_CC_IRQ_VCONNCOP_INT] = { .group = CC_INT, .mask = 1 << 7 }, + [MAX77775_CC_IRQ_VSAFE0V_INT] = { .group = CC_INT, .mask = 1 << 6 }, + [MAX77775_CC_IRQ_DETABRT_INT] = { .group = CC_INT, .mask = 1 << 5 }, + [MAX77775_CC_IRQ_VCONNSC_INT] = { .group = CC_INT, .mask = 1 << 4 }, + [MAX77775_CC_IRQ_CCPINSTAT_INT] = { .group = CC_INT, .mask = 1 << 3 }, + [MAX77775_CC_IRQ_CCISTAT_INT] = { .group = CC_INT, .mask = 1 << 2 }, + [MAX77775_CC_IRQ_CCVCNSTAT_INT] = { .group = CC_INT, .mask = 1 << 1 }, + [MAX77775_CC_IRQ_CCSTAT_INT] = { .group = CC_INT, .mask = 1 << 0 }, + + [MAX77775_PD_IRQ_PDMSG_INT] = { .group = PD_INT, .mask = 1 << 7 }, + [MAX77775_PD_IRQ_PS_RDY_INT] = { .group = PD_INT, .mask = 1 << 6 }, + [MAX77775_PD_IRQ_DATAROLE_INT] = { .group = PD_INT, .mask = 1 << 5 }, + [MAX77775_IRQ_VDM_ATTENTION_INT] = { .group = PD_INT, .mask = 1 << 4 }, + [MAX77775_IRQ_VDM_DP_CONFIGURE_INT] = { .group = PD_INT, .mask = 1 << 3 }, + [MAX77775_IRQ_VDM_DP_STATUS_UPDATE_INT] = { .group = PD_INT, .mask = 1 << 2 }, + [MAX77775_PD_IRQ_SSACCI_INT] = { .group = PD_INT, .mask = 1 << 1 }, + [MAX77775_PD_IRQ_FCTIDI_INT] = { .group = PD_INT, .mask = 1 << 0 }, + + [MAX77775_IRQ_VDM_DISCOVER_ID_INT] = { .group = VDM_INT, .mask = 1 << 0 }, + [MAX77775_IRQ_VDM_DISCOVER_SVIDS_INT] = { .group = VDM_INT, .mask = 1 << 1 }, + [MAX77775_IRQ_VDM_DISCOVER_MODES_INT] = { .group = VDM_INT, .mask = 1 << 2 }, + [MAX77775_IRQ_VDM_ENTER_MODE_INT] = { .group = VDM_INT, .mask = 1 << 3 }, + + [MAX77775_IRQ_USBID_INT] = { .group = SPARE_INT, .mask = 1 << 7 }, + [MAX77775_IRQ_TACONN_INT] = { .group = SPARE_INT, .mask = 1 << 6 }, + + [MAX77775_VIR_IRQ_ALTERROR_INT] = { .group = VIR_INT, .mask = 1 << 0 }, +}; + +static void max77775_irq_lock(struct irq_data *data) +{ + struct max77775_dev *max77775 = irq_get_chip_data(data->irq); + + mutex_lock(&max77775->irqlock); +} + +static void max77775_irq_sync_unlock(struct irq_data *data) +{ + struct max77775_dev *max77775 = irq_get_chip_data(data->irq); + int i; + + for (i = 0; i < MAX77775_IRQ_GROUP_NR; i++) { + u8 mask_reg = max77775_mask_reg[i]; + struct i2c_client *i2c = get_i2c(max77775, i); + + if (mask_reg == MAX77775_REG_INVALID || + IS_ERR_OR_NULL(i2c)) + continue; + max77775->irq_masks_cache[i] = max77775->irq_masks_cur[i]; + + max77775_write_reg(i2c, max77775_mask_reg[i], + max77775->irq_masks_cur[i]); + } + + mutex_unlock(&max77775->irqlock); +} + +static const inline struct max77775_irq_data * +irq_to_max77775_irq(struct max77775_dev *max77775, int irq) +{ + return &max77775_irqs[irq - max77775->irq_base]; +} + +static void max77775_irq_mask(struct irq_data *data) +{ + struct max77775_dev *max77775 = irq_get_chip_data(data->irq); + const struct max77775_irq_data *irq_data = + irq_to_max77775_irq(max77775, data->irq); + + if (irq_data->group >= MAX77775_IRQ_GROUP_NR) + return; + + max77775->irq_masks_cur[irq_data->group] |= irq_data->mask; +} + +static void max77775_irq_unmask(struct irq_data *data) +{ + struct max77775_dev *max77775 = irq_get_chip_data(data->irq); + const struct max77775_irq_data *irq_data = + irq_to_max77775_irq(max77775, data->irq); + + if (irq_data->group >= MAX77775_IRQ_GROUP_NR) + return; + + max77775->irq_masks_cur[irq_data->group] &= ~irq_data->mask; +} + +static void max77775_irq_disable(struct irq_data *data) +{ + max77775_irq_mask(data); +} + +static struct irq_chip max77775_irq_chip = { + .name = MFD_DEV_NAME, + .irq_bus_lock = max77775_irq_lock, + .irq_bus_sync_unlock = max77775_irq_sync_unlock, + .irq_mask = max77775_irq_mask, + .irq_unmask = max77775_irq_unmask, + .irq_disable = max77775_irq_disable, +}; + +#define VB_LOW 0 + +static irqreturn_t max77775_irq_thread(int irq, void *data) +{ + struct max77775_dev *max77775 = data; + u8 irq_reg[MAX77775_IRQ_GROUP_NR] = {0}; + u8 irq_src; + int i, ret; + u8 irq_vdm_mask = 0x0; + u8 dump_reg[10] = {0, }; + u8 reg_data; + u8 cc_status1 = 0; + u8 cc_status2 = 0; + u8 bc_status = 0; + u8 ccstat = 0; + u8 vbvolt = 0; + u8 pre_ccstati = 0; + u8 ic_alt_mode = 0; + + md75_info_usb("%s:%s irq gpio pre-state(0x%02x)\n", + MFD_DEV_NAME, __func__, + gpio_get_value(max77775->irq_gpio)); + + __pm_stay_awake(&max77775->ws); + + if (max77775->suspended) { + md75_err_usb("%s:%s skip.max77775 suspended. irq gpio post-state(0x%02x)\n", + MFD_DEV_NAME, __func__, gpio_get_value(max77775->irq_gpio)); + /* Irq will occur again because of IRQF_TRIGGER_LOW */ + wait_event_interruptible_timeout(max77775->suspend_wait, + !max77775->suspended, + msecs_to_jiffies(50)); + __pm_relax(&max77775->ws); + return IRQ_NONE; + } + + ret = max77775_read_reg(max77775->i2c, + MAX77775_PMIC_REG_INTSRC, &irq_src); + if (ret) { + md75_err_usb("%s:%s Failed to read interrupt source: %d\n", + MFD_DEV_NAME, __func__, ret); + __pm_relax(&max77775->ws); + return IRQ_NONE; + } + + md75_info_usb("%s:%s: irq[%d] %d/%d/%d irq_src=0x%02x (FW%02x.%02x)\n", + MFD_DEV_NAME, __func__, irq, max77775->irq, + max77775->irq_base, max77775->irq_gpio, irq_src, + max77775->FW_Revision, max77775->FW_Minor_Revision); + + if (irq_src & MAX77775_IRQSRC_CHG) { + /* CHG_INT */ + ret = max77775_read_reg(max77775->charger, MAX77775_CHG_REG_INT, + &irq_reg[CHG_INT]); + + if (max77775->enable_nested_irq) { + irq_reg[USBC_INT] |= max77775->usbc_irq; + max77775->enable_nested_irq = 0x0; + max77775->usbc_irq = 0x0; + } + + pr_debug("%s:%s charger interrupt(0x%02x)\n", + MFD_DEV_NAME, __func__, irq_reg[CHG_INT]); + /* mask chgin to prevent chgin infinite interrupt + * chgin is unmasked chgin isr + */ + if (irq_reg[CHG_INT] & + max77775_irqs[MAX77775_CHG_IRQ_CHGIN_I].mask) { + max77775_read_reg(max77775->charger, + MAX77775_CHG_REG_INT_MASK, ®_data); + reg_data |= (1 << 6); + max77775_write_reg(max77775->charger, + MAX77775_CHG_REG_INT_MASK, reg_data); + } + } + + if (irq_src & MAX77775_IRQSRC_FG) { + pr_debug("%s:[%s] fuelgauge interrupt\n", MFD_DEV_NAME, __func__); + pr_debug("%s:[%s]IRQ_BASE(%d), NESTED_IRQ(%d)\n", + MFD_DEV_NAME, __func__, max77775->irq_base, + max77775->irq_base + MAX77775_FG_IRQ_ALERT); + irq_reg[FUEL_INT] = 1 << 1; + } + + if (irq_src & MAX77775_IRQSRC_TOP) { + /* SYS_INT */ + ret = max77775_read_reg(max77775->i2c, MAX77775_PMIC_REG_SYSTEM_INT, + &irq_reg[SYS_INT]); + pr_debug("%s:%s topsys interrupt(0x%02x)\n", + MFD_DEV_NAME, __func__, irq_reg[SYS_INT]); + } + + if ((irq_src & MAX77775_IRQSRC_USBC) && max77775->cc_booting_complete) { + /* USBC INT */ + ret = max77775_bulk_read(max77775->muic, MAX77775_USBC_REG_UIC_INT, + 5, &irq_reg[USBC_INT]); + ret = max77775_read_reg(max77775->muic, MAX77775_USBC_REG_VDM_INT_M, + &irq_vdm_mask); + if (irq_reg[USBC_INT] & BIT_VBUSDetI) { + ret = max77775_read_reg(max77775->muic, REG_BC_STATUS, &bc_status); + ret = max77775_read_reg(max77775->muic, REG_CC_STATUS1, &cc_status1); + vbvolt = (bc_status & BIT_VBUSDet) >> FFS(BIT_VBUSDet); + ccstat = (cc_status1 & BIT_CCStat) >> FFS(BIT_CCStat); + if (cc_No_Connection == ccstat && vbvolt == VB_LOW) { + pre_ccstati = irq_reg[CC_INT]; + irq_reg[CC_INT] |= 0x1; + md75_info_usb("%s:%s [max77775] set the cc_stat int [work-around] :%x, %x\n", + MFD_DEV_NAME, __func__, + pre_ccstati, irq_reg[CC_INT]); + } + } + if (irq_reg[SPARE_INT] & BIT_USBID) { + // To Do + } + + ret = max77775_bulk_read(max77775->muic, MAX77775_USBC_REG_USBC_STATUS1, + 10, dump_reg); + md75_info_usb("%s:%s irq_reg, complete [%x], uicI=%x, ccI=%x, pdI=%x, vdmI=%x, vdmM=%x, sprI=%x\n", + MFD_DEV_NAME, __func__, + max77775->cc_booting_complete, + irq_reg[USBC_INT], irq_reg[CC_INT], irq_reg[PD_INT], irq_reg[VDM_INT], irq_vdm_mask, irq_reg[SPARE_INT]); + md75_info_usb("%s:%s dump_reg, S1=%x, S2=%x, bcS=%x, fwrev2=%x, ccS1=%x, ccS2=%x, pdS1=%x, pdS2=%x, sp1S=%x, sp2S=%x\n", + MFD_DEV_NAME, __func__, + dump_reg[0], dump_reg[1], dump_reg[2], dump_reg[3], dump_reg[4], + dump_reg[5], dump_reg[6], dump_reg[7], dump_reg[8], dump_reg[9]); + } + + if (max77775->cc_booting_complete) { + max77775_read_reg(max77775->muic, REG_CC_STATUS2, &cc_status2); + ic_alt_mode = (cc_status2 & BIT_Altmode) >> FFS(BIT_Altmode); + if (!ic_alt_mode && max77775->set_altmode_en) + irq_reg[VIR_INT] |= (1 << 0); + md75_info_usb("%s:%s ic_alt_mode=%d\n", MFD_DEV_NAME, __func__, ic_alt_mode); + + if (irq_reg[PD_INT] & BIT_PDMsg) { + if (dump_reg[6] == Sink_PD_PSRdy_received + || dump_reg[6] == SRC_CAP_RECEIVED) { + if (max77775->check_pdmsg) + max77775->check_pdmsg(max77775->usbc_data, dump_reg[6]); + } + } + } + + /* Apply masking */ + for (i = 0; i < MAX77775_IRQ_GROUP_NR; i++) + irq_reg[i] &= ~max77775->irq_masks_cur[i]; + + /* Report */ + for (i = 0; i < MAX77775_IRQ_NR; i++) { + if (irq_reg[max77775_irqs[i].group] & max77775_irqs[i].mask) + handle_nested_irq(max77775->irq_base + i); + } + + __pm_relax(&max77775->ws); + + md75_info_usb("%s:%s irq gpio post-state(0x%02x)\n", + MFD_DEV_NAME, __func__, + gpio_get_value(max77775->irq_gpio)); + + return IRQ_HANDLED; +} + +int max77775_irq_init(struct max77775_dev *max77775) +{ + int i; + int ret = 0; + u8 i2c_data; + int cur_irq; + + if (!gpio_is_valid(max77775->irq_gpio)) { + dev_warn(max77775->dev, "No interrupt specified.\n"); + max77775->irq_base = 0; + goto err; + } + + if (max77775->irq_base < 0) { + dev_err(max77775->dev, "No interrupt base specified.\n"); + goto err; + } + + mutex_init(&max77775->irqlock); + + max77775->irq = gpio_to_irq(max77775->irq_gpio); + md75_info_usb("%s:%s irq=%d, irq->gpio=%d\n", MFD_DEV_NAME, __func__, + max77775->irq, max77775->irq_gpio); + + ret = gpio_request(max77775->irq_gpio, "if_pmic_irq"); + if (ret) { + dev_err(max77775->dev, "%s: failed requesting gpio %d\n", + __func__, max77775->irq_gpio); + goto err_gpio_request; + } + gpio_direction_input(max77775->irq_gpio); + + /* Mask individual interrupt sources */ + for (i = 0; i < MAX77775_IRQ_GROUP_NR; i++) { + struct i2c_client *i2c; + /* MUIC IRQ 0:MASK 1:NOT MASK => NOT USE */ + /* Other IRQ 1:MASK 0:NOT MASK */ + max77775->irq_masks_cur[i] = 0xff; + max77775->irq_masks_cache[i] = 0xff; + + i2c = get_i2c(max77775, i); + + if (IS_ERR_OR_NULL(i2c)) + continue; + if (max77775_mask_reg[i] == MAX77775_REG_INVALID) + continue; + max77775_write_reg(i2c, max77775_mask_reg[i], 0xff); + } + + /* Register with genirq */ + for (i = 0; i < MAX77775_IRQ_NR; i++) { + cur_irq = i + max77775->irq_base; + irq_set_chip_data(cur_irq, max77775); + irq_set_chip_and_handler(cur_irq, &max77775_irq_chip, + handle_level_irq); + irq_set_nested_thread(cur_irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(cur_irq, IRQF_VALID); +#else + irq_set_noprobe(cur_irq); +#endif + } + + /* Unmask max77775 interrupt */ + ret = max77775_read_reg(max77775->i2c, MAX77775_PMIC_REG_INTSRC_MASK, + &i2c_data); + if (ret) { + md75_err_usb("%s:%s fail to read muic reg\n", MFD_DEV_NAME, __func__); + goto err_read_intsrc_mask2; + } + i2c_data |= 0xF; /* mask intsrc interrupt */ + + max77775_write_reg(max77775->i2c, MAX77775_PMIC_REG_INTSRC_MASK, + i2c_data); + + + ret = request_threaded_irq(max77775->irq, NULL, max77775_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "max77775-irq", max77775); + if (ret) { + dev_err(max77775->dev, "Failed to request IRQ %d: %d\n", + max77775->irq, ret); + goto err_read_intsrc_mask2; + } + + enable_irq_wake(max77775->irq); + + /* Unmask max77775 interrupt */ + ret = max77775_read_reg(max77775->i2c, MAX77775_PMIC_REG_INTSRC_MASK, + &i2c_data); + if (ret) { + md75_err_usb("%s:%s fail to read muic reg\n", MFD_DEV_NAME, __func__); + goto err_read_intsrc_mask1; + } + + i2c_data &= ~(MAX77775_IRQSRC_CHG); /* Unmask charger interrupt */ + + max77775_write_reg(max77775->i2c, MAX77775_PMIC_REG_INTSRC_MASK, + i2c_data); + + md75_info_usb("%s:%s max77775_PMIC_REG_INTSRC_MASK=0x%02x\n", + MFD_DEV_NAME, __func__, i2c_data); + + return 0; +err_read_intsrc_mask1: + if (max77775->irq) + free_irq(max77775->irq, max77775); +err_read_intsrc_mask2: + if (gpio_is_valid(max77775->irq_gpio)) + gpio_free(max77775->irq_gpio); +err_gpio_request: + mutex_destroy(&max77775->irqlock); +err: + return ret; +} + +void max77775_irq_exit(struct max77775_dev *max77775) +{ + if (max77775->irq) + free_irq(max77775->irq, max77775); + if (gpio_is_valid(max77775->irq_gpio)) + gpio_free(max77775->irq_gpio); + mutex_destroy(&max77775->irqlock); +} diff --git a/drivers/mfd/maxim/max77775.c b/drivers/mfd/maxim/max77775.c new file mode 100755 index 000000000000..3a66f5a13313 --- /dev/null +++ b/drivers/mfd/maxim/max77775.c @@ -0,0 +1,1761 @@ +/* + * max77775.c - mfd core driver for the Maxim 77775 + * + * Copyright (C) 2016 Samsung Electronics + * Insun Choi + * + * 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, see . + * + * This driver is based on max8997.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#if defined(CONFIG_HV_MUIC_MAX77775_AFC) +#include +#endif + +#define FW_BIN_NAME "max77775-fw.bin" +#define EXTRA_FW_BIN_NAME "max77775-extra-fw.bin" +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) +#include +#endif +#if defined(CONFIG_OF) +#include +#include +#endif /* CONFIG_OF */ +#include +#if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_MAX77775_ABC_IFPMIC_EVENT) +#include +#endif + +#if IS_ENABLED(CONFIG_SEC_MPARAM) || (IS_MODULE(CONFIG_SEC_PARAM) && defined(CONFIG_ARCH_EXYNOS)) +extern int factory_mode; +#else +static int __read_mostly factory_mode; +module_param(factory_mode, int, 0444); +#endif + +#define I2C_ADDR_PMIC (0xCC >> 1) +#define I2C_ADDR_MUIC (0x4A >> 1) +#define I2C_ADDR_CHG (0xD2 >> 1) +#define I2C_ADDR_FG (0x6C >> 1) +#define I2C_ADDR_TESTSID (0xC4 >> 1) + +#define I2C_RETRY_CNT 3 + +#define MD75_FIRMWARE_TIMEOUT_SEC 5 +#define MD75_FIRMWARE_TIMEOUT_START 1 +#define MD75_FIRMWARE_TIMEOUT_PASS 2 +#define MD75_FIRMWARE_TIMEOUT_FAIL 3 +#define MD75_FIRMWARE_TIMEOUT_COMPLETE 4 + +/* + * pmic revision information + */ +struct max77775_revision_struct { + u8 id; + u8 rev; + u8 logical_id; +}; + +static struct max77775_revision_struct max77775_revision[] = { + { 0x75, 0x01, MAX77775_PASS1}, /* MD75 PASS1 */ + { 0x75, 0x02, MAX77775_PASS2}, /* MD75 PASS2 */ + { 0x75, 0x03, MAX77775_PASS3}, /* MD75 PASS3 */ + { 0x75, 0x04, MAX77775_PASS4}, /* MD75 PASS4 */ +}; + +static struct mfd_cell max77775_devs[] = { +#if IS_ENABLED(CONFIG_CCIC_MAX77775) + { .name = "max77775-usbc", }, +#endif +#if IS_ENABLED(CONFIG_FUELGAUGE_MAX77775) + { .name = "max77775-fuelgauge", }, +#endif +#if IS_ENABLED(CONFIG_CHARGER_MAX77775) + { .name = "max77775-charger", }, +#endif +}; + +static int max77775_firmware_timeout_state; +static int firmware_timeout_count; +static struct platform_device *pdev; + +static int max77775_get_facmode(void) { return factory_mode; } + +int max77775_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest) +{ + struct max77775_dev *max77775 = i2c_get_clientdata(i2c); +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif + int ret, i; + + if (max77775->shutdown) { + md75_err_usb("%s:%s shutdown. i2c command is skiped\n", + MFD_DEV_NAME, __func__); + return 0; + } + + mutex_lock(&max77775->i2c_lock); + for (i = 0; i < I2C_RETRY_CNT; ++i) { + ret = i2c_smbus_read_byte_data(i2c, reg); + if (ret >= 0) + break; + md75_info_usb("%s:%s reg(0x%x), ret(%d), i2c_retry_cnt(%d/%d)\n", + MFD_DEV_NAME, __func__, reg, ret, i + 1, I2C_RETRY_CNT); + } + mutex_unlock(&max77775->i2c_lock); + if (ret < 0) { + md75_info_usb("%s:%s reg(0x%x), ret(%d)\n", MFD_DEV_NAME, __func__, reg, ret); +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); +#endif + +#if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_MAX77775_ABC_IFPMIC_EVENT) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=pdic@INFO=i2c_fail"); +#else + sec_abc_send_event("MODULE=pdic@WARN=i2c_fail"); +#endif +#endif + + return ret; + } + + ret &= 0xff; + *dest = ret; + return 0; +} +EXPORT_SYMBOL_GPL(max77775_read_reg); + +int max77775_bulk_read(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct max77775_dev *max77775 = i2c_get_clientdata(i2c); +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif + int ret, i; + + if (max77775->shutdown) { + md75_err_usb("%s:%s shutdown. i2c command is skiped\n", + MFD_DEV_NAME, __func__); + return 0; + } + + mutex_lock(&max77775->i2c_lock); + for (i = 0; i < I2C_RETRY_CNT; ++i) { + ret = i2c_smbus_read_i2c_block_data(i2c, reg, count, buf); + if (ret >= 0) + break; + md75_info_usb("%s:%s reg(0x%x), ret(%d), i2c_retry_cnt(%d/%d)\n", + MFD_DEV_NAME, __func__, reg, ret, i + 1, I2C_RETRY_CNT); + } + mutex_unlock(&max77775->i2c_lock); + if (ret < 0) { +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); +#endif + +#if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_MAX77775_ABC_IFPMIC_EVENT) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=pdic@INFO=i2c_fail"); +#else + sec_abc_send_event("MODULE=pdic@WARN=i2c_fail"); +#endif +#endif + + return ret; + } + return 0; +} +EXPORT_SYMBOL_GPL(max77775_bulk_read); + +int max77775_read_word(struct i2c_client *i2c, u8 reg) +{ + struct max77775_dev *max77775 = i2c_get_clientdata(i2c); +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif + int ret, i; + + if (max77775->shutdown) { + md75_err_usb("%s:%s shutdown. i2c command is skiped\n", + MFD_DEV_NAME, __func__); + return 0; + } + + mutex_lock(&max77775->i2c_lock); + for (i = 0; i < I2C_RETRY_CNT; ++i) { + ret = i2c_smbus_read_word_data(i2c, reg); + if (ret >= 0) + break; + md75_info_usb("%s:%s reg(0x%x), ret(%d), i2c_retry_cnt(%d/%d)\n", + MFD_DEV_NAME, __func__, reg, ret, i + 1, I2C_RETRY_CNT); + } + mutex_unlock(&max77775->i2c_lock); +#if defined(CONFIG_USB_HW_PARAM) + if (ret < 0) { + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); + } +#endif + +#if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_MAX77775_ABC_IFPMIC_EVENT) + if (ret < 0) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=pdic@INFO=i2c_fail"); +#else + sec_abc_send_event("MODULE=pdic@WARN=i2c_fail"); +#endif +#endif + + return ret; +} +EXPORT_SYMBOL_GPL(max77775_read_word); + +int max77775_write_reg(struct i2c_client *i2c, u8 reg, u8 value) +{ + struct max77775_dev *max77775 = i2c_get_clientdata(i2c); +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif + int ret = -EIO, i; + int timeout = 2000; /* 2sec */ + int interval = 100; + + if (max77775->shutdown) { + md75_err_usb("%s:%s shutdown. i2c command is skiped\n", + MFD_DEV_NAME, __func__); + return 0; + } + + while (ret == -EIO) { + mutex_lock(&max77775->i2c_lock); + for (i = 0; i < I2C_RETRY_CNT; ++i) { + ret = i2c_smbus_write_byte_data(i2c, reg, value); + if ((ret >= 0) || (ret == -EIO)) + break; + md75_info_usb("%s:%s reg(0x%02x), ret(%d), i2c_retry_cnt(%d/%d)\n", + MFD_DEV_NAME, __func__, reg, ret, i + 1, I2C_RETRY_CNT); + } + mutex_unlock(&max77775->i2c_lock); + + if (ret < 0) { + md75_info_usb("%s:%s reg(0x%x), ret(%d), timeout %d\n", + MFD_DEV_NAME, __func__, reg, ret, timeout); + + if (timeout < 0) + break; + + msleep(interval); + timeout -= interval; + } + } +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify && ret < 0) + inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); +#endif + +#if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_MAX77775_ABC_IFPMIC_EVENT) + if (ret < 0) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=pdic@INFO=i2c_fail"); +#else + sec_abc_send_event("MODULE=pdic@WARN=i2c_fail"); +#endif +#endif + + return ret; +} +EXPORT_SYMBOL_GPL(max77775_write_reg); + +int max77775_write_reg_nolock(struct i2c_client *i2c, u8 reg, u8 value) +{ + struct max77775_dev *max77775 = i2c_get_clientdata(i2c); +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif + int ret = -EIO; + int timeout = 2000; /* 2sec */ + int interval = 100; + + if (max77775->shutdown) { + md75_err_usb("%s:%s shutdown. i2c command is skiped\n", + MFD_DEV_NAME, __func__); + return 0; + } + + while (ret == -EIO) { + ret = i2c_smbus_write_byte_data(i2c, reg, value); + + if (ret < 0) { + md75_info_usb("%s:%s reg(0x%x), ret(%d), timeout %d\n", + MFD_DEV_NAME, __func__, reg, ret, timeout); + + if (timeout < 0) + break; + + msleep(interval); + timeout -= interval; + } + } +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify && ret < 0) + inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); +#endif + +#if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_MAX77775_ABC_IFPMIC_EVENT) + if (ret < 0) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=pdic@INFO=i2c_fail"); +#else + sec_abc_send_event("MODULE=pdic@WARN=i2c_fail"); +#endif +#endif + + return ret; +} +EXPORT_SYMBOL_GPL(max77775_write_reg_nolock); + +int max77775_bulk_write(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct max77775_dev *max77775 = i2c_get_clientdata(i2c); +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif + int ret = -EIO, i; + int timeout = 2000; /* 2sec */ + int interval = 100; + + if (max77775->shutdown) { + md75_err_usb("%s:%s shutdown. i2c command is skiped\n", + MFD_DEV_NAME, __func__); + return 0; + } + + while (ret == -EIO) { + mutex_lock(&max77775->i2c_lock); + for (i = 0; i < I2C_RETRY_CNT; ++i) { + ret = i2c_smbus_write_i2c_block_data(i2c, reg, count, buf); + if ((ret >= 0) || (ret == -EIO)) + break; + md75_info_usb("%s:%s reg(0x%x), ret(%d), i2c_retry_cnt(%d/%d)\n", + MFD_DEV_NAME, __func__, reg, ret, i + 1, I2C_RETRY_CNT); + } + mutex_unlock(&max77775->i2c_lock); + + if (ret < 0) { + md75_info_usb("%s:%s reg(0x%x), ret(%d), timeout %d\n", + MFD_DEV_NAME, __func__, reg, ret, timeout); + + if (timeout < 0) + break; + + msleep(interval); + timeout -= interval; + } + } +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify && ret < 0) + inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); +#endif + +#if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_MAX77775_ABC_IFPMIC_EVENT) + if (ret < 0) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=pdic@INFO=i2c_fail"); +#else + sec_abc_send_event("MODULE=pdic@WARN=i2c_fail"); +#endif +#endif + + return ret; +} +EXPORT_SYMBOL_GPL(max77775_bulk_write); + +int max77775_write_word(struct i2c_client *i2c, u8 reg, u16 value) +{ + struct max77775_dev *max77775 = i2c_get_clientdata(i2c); +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif + int ret, i; + + if (max77775->shutdown) { + md75_err_usb("%s:%s shutdown. i2c command is skiped\n", + MFD_DEV_NAME, __func__); + return 0; + } + + mutex_lock(&max77775->i2c_lock); + for (i = 0; i < I2C_RETRY_CNT; ++i) { + ret = i2c_smbus_write_word_data(i2c, reg, value); + if (ret >= 0) + break; + md75_info_usb("%s:%s reg(0x%x), ret(%d), i2c_retry_cnt(%d/%d)\n", + MFD_DEV_NAME, __func__, reg, ret, i + 1, I2C_RETRY_CNT); + } + mutex_unlock(&max77775->i2c_lock); + if (ret < 0) { +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); +#endif + +#if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_MAX77775_ABC_IFPMIC_EVENT) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=pdic@INFO=i2c_fail"); +#else + sec_abc_send_event("MODULE=pdic@WARN=i2c_fail"); +#endif +#endif + + return ret; + } + return 0; +} +EXPORT_SYMBOL_GPL(max77775_write_word); + +int max77775_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask) +{ + struct max77775_dev *max77775 = i2c_get_clientdata(i2c); +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif + int ret, i; + u8 old_val, new_val; + + if (max77775->shutdown) { + md75_err_usb("%s:%s shutdown. i2c command is skiped\n", + MFD_DEV_NAME, __func__); + return 0; + } + + mutex_lock(&max77775->i2c_lock); + for (i = 0; i < I2C_RETRY_CNT; ++i) { + ret = i2c_smbus_read_byte_data(i2c, reg); + if (ret >= 0) + break; + md75_info_usb("%s:%s read reg(0x%x), ret(%d), i2c_retry_cnt(%d/%d)\n", + MFD_DEV_NAME, __func__, reg, ret, i + 1, I2C_RETRY_CNT); + } + if (ret < 0) { +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); +#endif + goto err; + } + if (ret >= 0) { + old_val = ret & 0xff; + new_val = (val & mask) | (old_val & (~mask)); + for (i = 0; i < I2C_RETRY_CNT; ++i) { + ret = i2c_smbus_write_byte_data(i2c, reg, new_val); + if (ret >= 0) + break; + md75_info_usb("%s:%s write reg(0x%x), ret(%d), i2c_retry_cnt(%d/%d)\n", + MFD_DEV_NAME, __func__, reg, ret, i + 1, I2C_RETRY_CNT); + } + if (ret < 0) { +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_I2C_ERROR_COUNT); +#endif + goto err; + } + } +err: + mutex_unlock(&max77775->i2c_lock); +#if IS_ENABLED(CONFIG_SEC_ABC) && IS_ENABLED(CONFIG_MAX77775_ABC_IFPMIC_EVENT) + if (ret < 0) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=pdic@INFO=i2c_fail"); +#else + sec_abc_send_event("MODULE=pdic@WARN=i2c_fail"); +#endif +#endif + return ret; +} +EXPORT_SYMBOL_GPL(max77775_update_reg); + +#if defined(CONFIG_OF) +static int of_max77775_dt(struct device *dev, struct max77775_platform_data *pdata) +{ + struct device_node *np_max77775 = dev->of_node; + struct device_node *np_battery; + int ret, val; + + if (!np_max77775) + return -EINVAL; + + pdata->irq_gpio = of_get_named_gpio(np_max77775, "max77775,irq-gpio", 0); + + if (of_property_read_u32(np_max77775, "max77775,rev", &pdata->rev)) + pdata->rev = 0; + + if (of_property_read_u32(np_max77775, "max77775,fw_product_id", &pdata->fw_product_id)) + pdata->fw_product_id = 0; + +#if defined(CONFIG_SEC_FACTORY) + pdata->blocking_waterevent = 0; +#else + pdata->blocking_waterevent = of_property_read_bool(np_max77775, "max77775,blocking_waterevent"); +#endif + ret = of_property_read_u32(np_max77775, "max77775,extra_fw_enable", &val); + if (ret) + pdata->extra_fw_enable = 0; + else + pdata->extra_fw_enable = val; + + np_battery = of_find_node_by_name(NULL, "mfc-charger"); + if (!np_battery) { + md75_info_usb("%s: np_battery NULL\n", __func__); + } else { + pdata->wpc_en = of_get_named_gpio(np_battery, "battery,wpc_en", 0); + if (pdata->wpc_en < 0) { + md75_info_usb("%s: can't get wpc_en (%d)\n", __func__, pdata->wpc_en); + pdata->wpc_en = 0; + } + + ret = of_property_read_string(np_battery, + "battery,wireless_charger_name", (char const **)&pdata->wireless_charger_name); + if (ret) + md75_info_usb("%s: Wireless charger name is Empty\n", __func__); + } + pdata->support_audio = of_property_read_bool(np_max77775, "max77775,support-audio"); + + return 0; +} +#endif /* CONFIG_OF */ + +static void max77775_print_pdata_property(struct max77775_platform_data *pdata) +{ + md75_info_usb("%s: irq-gpio: %u\n", __func__, pdata->irq_gpio); + md75_info_usb("%s: extra_fw_enable: %d\n", __func__, + pdata->extra_fw_enable); + md75_info_usb("%s: support_audio %d\n", __func__, pdata->support_audio); +} + +/* samsung */ +#if IS_ENABLED(CONFIG_CCIC_MAX77775) +static void max77775_reset_ic(struct max77775_dev *max77775) +{ + md75_info_usb("%s: Reset!!\n", __func__); + max77775_write_reg(max77775->muic, MAX77775_USBC_REG_UIC_SWRST, 0x0F); + msleep(150); +} + +static void max77775_usbc_wait_response_q(struct work_struct *work) +{ + struct max77775_dev *max77775; + u8 read_value = 0x00; + u8 dummy[2] = { 0, }; + + max77775 = container_of(work, struct max77775_dev, fw_work); + + while (max77775->fw_update_state == FW_UPDATE_WAIT_RESP_START) { + max77775_bulk_read(max77775->muic, REG_UIC_INT, 1, dummy); + read_value = dummy[0]; + if ((read_value & BIT_APCmdResI) == BIT_APCmdResI) + break; + } + + complete_all(&max77775->fw_completion); +} + +static int max77775_usbc_wait_response(struct max77775_dev *max77775) +{ + unsigned long time_remaining = 0; + + max77775->fw_update_state = FW_UPDATE_WAIT_RESP_START; + + init_completion(&max77775->fw_completion); + queue_work(max77775->fw_workqueue, &max77775->fw_work); + + time_remaining = wait_for_completion_timeout( + &max77775->fw_completion, + msecs_to_jiffies(FW_WAIT_TIMEOUT)); + + max77775->fw_update_state = FW_UPDATE_WAIT_RESP_STOP; + + if (!time_remaining) { + md75_info_usb("%s: Failed to update due to timeout\n", __func__); + cancel_work_sync(&max77775->fw_work); + return FW_UPDATE_TIMEOUT_FAIL; + } + + return 0; +} + +static int __max77775_usbc_fw_update( + struct max77775_dev *max77775, const u8 *fw_bin) +{ + u8 fw_cmd = FW_CMD_END; + u8 fw_len = 0; + u8 fw_opcode = 0; + u8 fw_data_len = 0; + u8 fw_data[FW_CMD_WRITE_SIZE] = { 0, }; + u8 verify_data[FW_VERIFY_DATA_SIZE] = { 0, }; + int ret = -FW_UPDATE_CMD_FAIL; + + /* + * fw_bin[0] = Write Command (0x01) + * or + * fw_bin[0] = Read Command (0x03) + * or + * fw_bin[0] = End Command (0x00) + */ + fw_cmd = fw_bin[0]; + + /* + * Check FW Command + */ + if (fw_cmd == FW_CMD_END) { + max77775_reset_ic(max77775); + max77775->fw_update_state = FW_UPDATE_END; + return FW_UPDATE_END; + } + + /* + * fw_bin[1] = Length ( OPCode + Data ) + */ + fw_len = fw_bin[1]; + + /* + * Check fw data length + * We support 0x22 or 0x04 only + */ + if (fw_len != 0x22 && fw_len != 0x04) + return FW_UPDATE_MAX_LENGTH_FAIL; + + /* + * fw_bin[2] = OPCode + */ + fw_opcode = fw_bin[2]; + + /* + * In case write command, + * fw_bin[35:3] = Data + * + * In case read command, + * fw_bin[5:3] = Data + */ + fw_data_len = fw_len - 1; /* exclude opcode */ + memcpy(fw_data, &fw_bin[3], fw_data_len); + + switch (fw_cmd) { + case FW_CMD_WRITE: + if (fw_data_len > I2C_SMBUS_BLOCK_MAX) { + /* write the half data */ + max77775_bulk_write(max77775->muic, + fw_opcode, + I2C_SMBUS_BLOCK_HALF, + fw_data); + max77775_bulk_write(max77775->muic, + fw_opcode + I2C_SMBUS_BLOCK_HALF, + fw_data_len - I2C_SMBUS_BLOCK_HALF, + &fw_data[I2C_SMBUS_BLOCK_HALF]); + } else + max77775_bulk_write(max77775->muic, + fw_opcode, + fw_data_len, + fw_data); + + ret = max77775_usbc_wait_response(max77775); + if (ret) + return ret; + + /* + * Why do we need 1ms sleep in case MQ81? + */ + /* msleep(1); */ + + return FW_CMD_WRITE_SIZE; + + + case FW_CMD_READ: + max77775_bulk_read(max77775->muic, + fw_opcode, + fw_data_len, + verify_data); + /* + * Check fw data sequence number + * It should be increased from 1 step by step. + */ + if (memcmp(verify_data, &fw_data[1], 2)) { + md75_info_usb("%s: [0x%02x 0x%02x], [0x%02x, 0x%02x], [0x%02x, 0x%02x]\n", + __func__, + verify_data[0], fw_data[0], + verify_data[1], fw_data[1], + verify_data[2], fw_data[2]); + return FW_UPDATE_VERIFY_FAIL; + } + + return FW_CMD_READ_SIZE; + } + + md75_info_usb("%s: Command error\n", __func__); + + return ret; +} + +int max77775_write_fw_noautoibus(struct max77775_dev *max77775) +{ + u8 write_values[OPCODE_MAX_LENGTH] = { 0, }; + int ret = 0; + int length = 0x1; + int i = 0; + + write_values[0] = OPCODE_SAMSUNG_FW_AUTOIBUS; + write_values[1] = 0x3; /* usbc fw off & auto off(manual on) */ + + for (i = 0; i < length + OPCODE_SIZE; i++) + md75_info_usb("%s: [%d], 0x[%x]\n", __func__, i, write_values[i]); + + /* Write opcode and data */ + ret = max77775_bulk_write(max77775->muic, OPCODE_WRITE, + length + OPCODE_SIZE, write_values); + /* Write end of data by 0x00 */ + if (length < OPCODE_DATA_LENGTH) + max77775_write_reg(max77775->muic, OPCODE_WRITE_END, 0x00); + return 0; +} + +static int max77775_fuelgauge_read_vcell(struct max77775_dev *max77775) +{ + u8 data[2]; + u32 vcell; + u16 w_data; + u32 temp; + u32 temp2; + + if (max77775_bulk_read(max77775->fuelgauge, MAX77775_FG_REG_VCELL, 2, data) < 0) { + md75_err_usb("%s: Failed to read VCELL\n", __func__); + return -1; + } + + w_data = (data[1] << 8) | data[0]; + + temp = (w_data & 0xFFF) * 78125; + vcell = temp / 1000000; + + temp = ((w_data & 0xF000) >> 4) * 78125; + temp2 = temp / 1000000; + vcell += (temp2 << 4); + + return vcell; +} + +static void max77775_wc_control(struct max77775_dev *max77775, bool enable) +{ +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + union power_supply_propval value = {0, }; + char wpc_en_status[2]; + int ret = 0; + + wpc_en_status[0] = WPC_EN_CCIC; + wpc_en_status[1] = enable ? true : false; + value.strval = wpc_en_status; + ret = psy_do_property(max77775->pdata->wireless_charger_name, set, POWER_SUPPLY_EXT_PROP_WPC_EN, value); + + if (ret < 0) { + if (max77775->pdata->wpc_en) { + if (enable) { + gpio_direction_output(max77775->pdata->wpc_en, 0); + md75_info_usb("%s: WC CONTROL: ENABLE\n", __func__); + } else { + gpio_direction_output(max77775->pdata->wpc_en, 1); + md75_info_usb("%s: WC CONTROL: DISABLE\n", __func__); + } + } else { + md75_info_usb("%s : no wpc_en\n", __func__); + } + } else { + md75_info_usb("%s: WC CONTROL: %s\n", __func__, wpc_en_status[1] ? "Enable" : "Disable"); + } + + md75_info_usb("%s: wpc_en(%d)\n", __func__, gpio_get_value(max77775->pdata->wpc_en)); +#endif +} + +#if defined(CONFIG_SEC_FACTORY) +bool max77775_is_factory(struct max77775_dev *max77775) +{ + bool is_factory = false; + u8 chgin_dtls; + u8 fctid = 7; + u8 uidadc = 7; + + max77775_read_reg(max77775->charger, MAX77775_CHG_REG_DETAILS_00, &chgin_dtls); + chgin_dtls = ((chgin_dtls & 0x60) >> 5); + + if (max77775->FW_Revision == 0xFF) { + is_factory = (chgin_dtls == 3) ? true : false; + md75_info_usb("%s: FW_Revision=0x%02X chgin_dtls=0x%02X is_factory=%d(forced)\n", __func__, max77775->FW_Revision, chgin_dtls, is_factory); + return is_factory; + } + + max77775_read_reg(max77775->muic, MAX77775_USBC_REG_USBC_STATUS1, &uidadc); + uidadc = uidadc & 0x07; + switch (uidadc) { + case 3: /* 255K */ + case 4: /* 301K */ + case 5: /* 523K */ + case 6: /* 619K */ + if (chgin_dtls == 3) + is_factory = true; + break; + default: + break; + } + + max77775_read_reg(max77775->muic, MAX77775_USBC_REG_PD_STATUS2, &fctid); + fctid = fctid & 0x0F; + switch (fctid) { + case 3: /* 255K */ + case 4: /* 301K */ + case 5: /* 523K */ + case 6: /* 619K */ + if (chgin_dtls == 3) + is_factory = true; + break; + default: + break; + } + + md75_info_usb("%s: FW_Revision=0x%02X, chgin_dtls=%x, uidadc=%x, fctid=%x, is_factory=%d\n", + __func__, max77775->FW_Revision, chgin_dtls, uidadc, fctid, (int)is_factory); + return is_factory; +} +#endif /* CONFIG_SEC_FACTORY */ + +void max77775_set_dpdm_gnd(struct max77775_dev *max77775) +{ + md75_info_usb("%s : Set DPDN GND\n", __func__); + max77775_write_reg(max77775->muic, OPCODE_WRITE, 0x04); + max77775_write_reg(max77775->muic, OPCODE_DATAOUT1, 0x10); + max77775_write_reg(max77775->muic, OPCODE_WRITE_END, 0x00); + msleep(150); +} + +int max77775_usbc_fw_update(struct max77775_dev *max77775, + const u8 *fw_bin, int fw_bin_len, int enforce_do) +{ + max77775_fw_header *fw_header; +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif + int offset = 0; + unsigned long duration = 0; + int size = 0; + int try_count = 0; + int ret = 0; + u8 pmicrev = 0x00; + u8 usbc_status1 = 0x0; + u8 pd_status2 = 0x0; + static u8 fct_id; /* FCT cable */ + u8 uidadc; /* FCT cable */ + u8 try_command = 0; +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + u8 sw_boot = 0; +#endif + u8 chg_cnfg_00 = 0; + u8 chg_cnfg_11 = 0; + u8 chg_cnfg_12 = 0; + bool recovery_needed = false; + bool wpc_en_changed = 0; + int vcell = 0; + u8 chgin_dtls = 0; + u8 wcin_dtls = 0; + u8 vbadc = 0; + bool is_factory = false; + int error = 0; +#if defined(CONFIG_SEC_FACTORY) + bool is_testsid = false; +#endif + + u8 bc_status = 0; + u8 chg_type = 0; + + max77775->fw_size = fw_bin_len; + fw_header = (max77775_fw_header *)fw_bin; + md75_info_usb("%s: FW_CHK: magic/%x/ major/%x/ minor/%x/ id/%x/ rev/%x/\n", + __func__, fw_header->magic, fw_header->major, + fw_header->minor, fw_header->id, fw_header->rev); + + max77775_read_reg(max77775->i2c, MAX77775_PMIC_REG_PMICREV, &pmicrev); + if (max77775->required_hw_rev != (pmicrev & 0x7)) { + md75_info_usb("%s: FW_SKIP: hw_rev mismatch. required_hw_rev=%x:pmicrev=%x\n", + __func__, max77775->required_hw_rev, pmicrev); + return 0; + } + + if (max77775->required_fw_pid != fw_header->id) { + md75_info_usb("%s: FW_SKIP: product id mismatch. required_fw_pid=%x:fw_header:%x\n", + __func__, max77775->required_fw_pid, fw_header->id); + return 0; + } + + if (fw_header->magic == MAX77775_SIGN) + md75_info_usb("%s: FW_MAGIC: matched\n", __func__); + + max77775_read_reg(max77775->charger, MAX77775_CHG_REG_CNFG_00, &chg_cnfg_00); + max77775_read_reg(max77775->charger, MAX77775_CHG_REG_CNFG_11, &chg_cnfg_11); + max77775_read_reg(max77775->charger, MAX77775_CHG_REG_CNFG_12, &chg_cnfg_12); + md75_info_usb("%s: FW_INFO: chg_cnfg_00=0x%02X | chg_cnfg_11=0x%02X | chg_cnfg_12=0x%02X\n", + __func__, chg_cnfg_00, chg_cnfg_11, chg_cnfg_12); + +#if defined(CONFIG_SEC_FACTORY) + max77775_read_reg(max77775->muic, REG_UIC_FW_REV, &max77775->FW_Revision); + is_factory = max77775_is_factory(max77775); + if (is_factory) { + if (max77775->FW_Revision != 0xFF) { + max77775_read_reg(max77775->muic, REG_USBC_STATUS1, &usbc_status1); + vbadc = (usbc_status1 & BIT_VBADC) >> FFS(BIT_VBADC); + } else + md75_info_usb("%s: vbadc check was skipped\n", __func__); + } +#endif /* CONFIG_SEC_FACTORY */ + + max77775_read_reg(max77775->muic, REG_BC_STATUS, &bc_status); + chg_type = (bc_status & BIT_ChgTyp) >> FFS(BIT_ChgTyp); + +retry: + md75_info_usb("%s: FW_TRY: try_count=%d, try_command=%d\n", + __func__, try_count, try_command); + disable_irq(max77775->irq); + max77775_write_reg(max77775->muic, REG_PD_INT_M, 0xFF); + max77775_write_reg(max77775->muic, REG_CC_INT_M, 0xFF); + max77775_write_reg(max77775->muic, REG_UIC_INT_M, 0xFF); + max77775_write_reg(max77775->muic, REG_VDM_INT_M, 0xFF); +#if defined(CONFIG_MAX77775_CCOPEN_AFTER_WATERCABLE) + max77775_write_reg(max77775->muic, REG_SPARE_INT_M, 0xFF); +#endif + + offset = 0; + duration = 0; + size = 0; + ret = 0; + + ret = max77775_read_reg(max77775->muic, REG_PRODUCT_ID, &max77775->FW_Product_ID); + ret = max77775_read_reg(max77775->muic, REG_UIC_FW_REV, &max77775->FW_Revision); + ret = max77775_read_reg(max77775->muic, REG_UIC_FW_REV2, &max77775->FW_Minor_Revision); + + max77775->FW_Product_ID_bin = fw_header->id; + max77775->FW_Revision_bin = fw_header->major; + max77775->FW_Minor_Revision_bin = fw_header->minor; + + if (ret < 0 && (try_count == 0 && try_command == 0)) { + md75_info_usb("%s: FW_READFAILED: Failed to read FW_REV\n", __func__); + error = -EIO; + goto out; + } + + duration = jiffies; + + md75_info_usb("%s: FW_INFO: chip : %02X.%02X(PID%02X), bin : %02X.%02X(PID%02X)\n", + __func__, max77775->FW_Revision, + max77775->FW_Minor_Revision, max77775->FW_Product_ID, + fw_header->major, fw_header->minor, fw_header->id); +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + store_ccic_bin_version(&fw_header->major, &sw_boot); +#endif + + if ((max77775->FW_Revision == 0xff) || + (max77775->FW_Revision != fw_header->major) || + (max77775->FW_Minor_Revision != fw_header->minor) || + (max77775->FW_Product_ID != fw_header->id) || + enforce_do) { + + if (IS_ENABLED(CONFIG_SEC_FACTORY_INTERPOSER) && !enforce_do) { + md75_err_usb("%s: Skip fw update on secondary factory binary\n", __func__); + error = -EINVAL; + goto out; + } + +#if defined(CONFIG_SEC_FACTORY) + max77775_read_reg(max77775->muic, MAX77775_USBC_REG_USBC_STATUS1, &usbc_status1); + uidadc = (usbc_status1 & BIT_UIDADC) >> FFS(BIT_UIDADC); + max77775_read_reg(max77775->muic, REG_PD_STATUS2, &pd_status2); + fct_id = (pd_status2 & BIT_FCT_ID) >> FFS(BIT_FCT_ID); + if ((is_testsid == false) && ((uidadc == 6) || (uidadc == 5) || (uidadc == 4) || (fct_id == 4))) { + ret = max77775_write_reg(max77775->i2c, 0xFE, 0xC5); + ret = max77775_write_reg(max77775->testsid, 0xB3, 0x0C); + ret = max77775_write_reg(max77775->testsid, 0x9B, 0x20); + ret = max77775_write_reg(max77775->testsid, 0xB3, 0x00); + ret = max77775_write_reg(max77775->i2c, 0xFE, 0x00); + is_testsid = true; + md75_info_usb("%s: testsid(%d)\n", __func__, (int)is_testsid); + } +#endif /* CONFIG_SEC_FACTORY */ + + if (!enforce_do) { /* on Booting time */ + max77775_read_reg(max77775->muic, REG_PD_STATUS2, &pd_status2); + fct_id = (pd_status2 & BIT_FCT_ID) >> FFS(BIT_FCT_ID); + + max77775_read_reg(max77775->muic, REG_USBC_STATUS1, &usbc_status1); + uidadc = (usbc_status1 & BIT_UIDADC) >> FFS(BIT_UIDADC); + md75_info_usb("%s: FCT_ID: 0x%x UIDADC: 0x%x\n", __func__, + fct_id, uidadc); + } + + max77775_read_reg(max77775->charger, MAX77775_CHG_REG_DETAILS_00, &wcin_dtls); + wcin_dtls = (wcin_dtls & 0x18) >> 3; + + wpc_en_changed = true; + max77775_wc_control(max77775, false); + + max77775_read_reg(max77775->charger, MAX77775_CHG_REG_DETAILS_00, &chgin_dtls); + chgin_dtls = ((chgin_dtls & 0x60) >> 5); + + md75_info_usb("%s: FW_INFO: chgin_dtls:0x%x, wcin_dtls:0x%x, vbadc=%x, is_factory=%d\n", + __func__, chgin_dtls, wcin_dtls, vbadc, (int)is_factory); + + if (try_count == 0 && try_command == 0) { + + if (is_factory) { + if ((chgin_dtls == 3) && (vbadc <= 3)) { + /* In case of : TA only connected, REV is OK or 0xFF + * the vbus would be 5V by ic-reset if REV was 0xFF in pre boot-up + * vbadc limit was changed to include 3 by SS request + */ + + /* Set ChgMode to 0x04 */ + max77775_update_reg(max77775->charger, + MAX77775_CHG_REG_CNFG_00, 0x04, 0x0F); + md75_info_usb("%s: FW_INFO: +change chg_mode(4), vbadc(%d)\n", + __func__, vbadc); + recovery_needed = true; + } else { + md75_info_usb("%s: chgin is not valid(%d) or out of vbadc(%d)\n", __func__, chgin_dtls, vbadc); + goto out; + } + } else { + /* In case of : battery connected (don't care TA), REV is OK or 0xFF */ + + /* Check VCell */ + vcell = max77775_fuelgauge_read_vcell(max77775); + if (vcell < 3600) { + md75_info_usb("%s: FW_SKIP: keep chg_mode(0x%x), vcell(%dmv)\n", + __func__, chg_cnfg_00 & 0x0F, vcell); + error = -EAGAIN; + goto out; + } + + /* Set Chginsel and Wcinsel to 0 */ + max77775_update_reg(max77775->charger, + MAX77775_CHG_REG_CNFG_12, 0x00, 0x60); + md75_info_usb("%s: FW_INFO: +disable CHGINSEL(0) / WCINSEL(0) -> chg_cnfg_12=0x%02X\n", + __func__, chg_cnfg_12 & 0x9F); + + /* Set VBypSet to 0x00 */ + max77775_write_reg(max77775->charger, + MAX77775_CHG_REG_CNFG_11, 0x00); + md75_info_usb("%s: FW_INFO: +clear VBYPSET(0x00)\n", __func__); + + /* Set ChgMode to 0x09 */ + max77775_update_reg(max77775->charger, + MAX77775_CHG_REG_CNFG_00, 0x09, 0x0F); + md75_info_usb("%s: FW_INFO: +change chg_mode(9), vcell(%dmv)\n", + __func__, vcell); + + recovery_needed = true; + + if (chg_type == 0x03 /*CHGTYP_DEDICATED_CHARGER*/ && max77775->FW_Revision != 0xFF) + max77775_set_dpdm_gnd(max77775); + } + } + + msleep(150); + + max77775_write_reg(max77775->muic, OPCODE_WRITE, 0xD0); + max77775_write_reg(max77775->muic, OPCODE_WRITE_END, 0x00); + msleep(300); + + max77775_read_reg(max77775->muic, REG_UIC_FW_REV, &max77775->FW_Revision); + max77775_read_reg(max77775->muic, REG_UIC_FW_REV2, &max77775->FW_Minor_Revision); + md75_info_usb("%s: FW_START: (%02X.%02X)\n", __func__, + max77775->FW_Revision, + max77775->FW_Minor_Revision); + + if (max77775->FW_Revision != 0xFF) { + if (++try_command < FW_SECURE_MODE_TRY_COUNT) { + md75_info_usb("%s: FW_FAILED: the Fail to enter secure mode %d\n", + __func__, try_command); + max77775_reset_ic(max77775); + goto retry; + } else { + md75_info_usb("%s: FW_FAILED: the Secure Update Fail!!\n", + __func__); +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_FWUP_ERROR_COUNT); +#endif + error = -EIO; + goto out; + } + } + + try_command = 0; + + for (offset = FW_HEADER_SIZE; + offset < fw_bin_len && size != FW_UPDATE_END;) { + + size = __max77775_usbc_fw_update(max77775, &fw_bin[offset]); + + switch (size) { + case FW_UPDATE_VERIFY_FAIL: + md75_err_usb("%s: FW_VERIFY_FAIL\n", __func__); + offset -= FW_CMD_WRITE_SIZE; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) + fallthrough; +#endif + case FW_UPDATE_TIMEOUT_FAIL: + /* + * Retry FW updating + */ + if (++try_count < FW_VERIFY_TRY_COUNT) { + md75_info_usb("%s: FW_TIMEOUT: Retry fw write. ret %d, count %d, offset %d\n", + __func__, size, + try_count, offset); + max77775_reset_ic(max77775); + goto retry; + } else { + md75_info_usb("%s: FW_TIMEOUT: Failed to update FW. ret %d, offset %d\n", + __func__, size, + (offset + size)); +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_FWUP_ERROR_COUNT); +#endif + error = -EIO; + goto out; + } + break; + case FW_UPDATE_CMD_FAIL: + case FW_UPDATE_MAX_LENGTH_FAIL: + md75_info_usb("%s: FW_LENGTH_FAIL: Failed to update FW. ret %d, offset %d\n", + __func__, size, (offset + size)); +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_FWUP_ERROR_COUNT); +#endif + error = -EIO; + goto out; + case FW_UPDATE_END: /* 0x00 */ + max77775_read_reg(max77775->muic, + REG_UIC_FW_REV, &max77775->FW_Revision); + max77775_read_reg(max77775->muic, + REG_UIC_FW_REV2, &max77775->FW_Minor_Revision); + max77775_read_reg(max77775->muic, + REG_PRODUCT_ID, &max77775->FW_Product_ID); + md75_info_usb("%s: chip : %02X.%02X(PID%02X), bin : %02X.%02X(PID%02X)\n", + __func__, max77775->FW_Revision, + max77775->FW_Minor_Revision, + max77775->FW_Product_ID, + fw_header->major, + fw_header->minor, + fw_header->id); + md75_info_usb("%s: FW_COMPLETED\n", __func__); + + if (max77775_get_facmode()) + max77775_write_fw_noautoibus(max77775); + + break; + default: + offset += size; + break; + } + if (offset == fw_bin_len) { + max77775_reset_ic(max77775); + max77775_read_reg(max77775->muic, + REG_UIC_FW_REV, &max77775->FW_Revision); + max77775_read_reg(max77775->muic, + REG_UIC_FW_REV2, &max77775->FW_Minor_Revision); + max77775_read_reg(max77775->muic, + REG_PRODUCT_ID, &max77775->FW_Product_ID); + md75_info_usb("%s: FW_INFO: chip : %02X.%02X(PID%02X), bin : %02X.%02X(PID%02X)\n", + __func__, max77775->FW_Revision, + max77775->FW_Minor_Revision, + max77775->FW_Product_ID, + fw_header->major, + fw_header->minor, + fw_header->id); + + md75_info_usb("%s: FW COMPLETED via SYS path\n", + __func__); + } + } + } else { + md75_info_usb("%s: FW_SKIP: Don't need to update!\n", __func__); + goto out; + } + + duration = jiffies - duration; + md75_info_usb("%s: FW_OK Duration : %dms\n", __func__, + jiffies_to_msecs(duration)); + +out: + if (recovery_needed) { + if (is_factory) { + max77775_update_reg(max77775->charger, + MAX77775_CHG_REG_CNFG_00, chg_cnfg_00, 0x0F); + md75_info_usb("%s: FW_INFO: -recover ChgMode(%d) -> chg_cnfg_12=0x%02X, vbadc(%d)\n", + __func__, chg_cnfg_00 & 0x0F, chg_cnfg_00, vbadc); + + } else { + /* Recover Chginsel and Wcinsel */ + max77775_update_reg(max77775->charger, MAX77775_CHG_REG_CNFG_12, + chg_cnfg_12, 0x60); + md75_info_usb("%s: FW_INFO: -recover CHGINSEL(%d) / WCINSEL(%d) -> chg_cnfg_12=0x%02X\n", + __func__, chg_cnfg_12 & 0x20 ? 1 : 0, + chg_cnfg_12 & 0x40 ? 1 : 0, chg_cnfg_12); + + /* Recover VBypSet */ + max77775_write_reg(max77775->charger, + MAX77775_CHG_REG_CNFG_11, chg_cnfg_11); + md75_info_usb("%s: FW_INFO: -recover VBYPSET(0x%02X)\n", + __func__, chg_cnfg_11); + + /* Recover ChgMode */ + max77775_update_reg(max77775->charger, + MAX77775_CHG_REG_CNFG_00, chg_cnfg_00, 0x0F); + md75_info_usb("%s: FW_INFO: -recover ChgMode(%d) -> chg_cnfg_12=0x%02X, vcell(%dmv)\n", + __func__, chg_cnfg_00 & 0x0F, + chg_cnfg_00, vcell); + } + } + + if (wpc_en_changed) { + max77775_wc_control(max77775, true); + } + enable_irq(max77775->irq); + +#if defined(CONFIG_SEC_FACTORY) + if (is_testsid == true) { + max77775_write_reg(max77775->i2c, 0xFE, 0xC5); + max77775_write_reg(max77775->testsid, 0xB3, 0x0C); + max77775_write_reg(max77775->testsid, 0x9B, 0x00); + max77775_write_reg(max77775->testsid, 0xB3, 0x00); + max77775_write_reg(max77775->i2c, 0xFE, 0x00); + is_testsid = false; + md75_info_usb("%s: testsid(%d)\n", __func__, (int)is_testsid); + } +#endif /* CONFIG_SEC_FATORY */ + + return error; +} +EXPORT_SYMBOL_GPL(max77775_usbc_fw_update); + +static void __max77775_usbc_fw_setting(struct max77775_dev *max77775, + const struct firmware *fw, char *fw_bin_name, int enforce_do) +{ + md75_info_usb("%s: fw update (name=%s size=%lu enforce_do=%d)\n", + __func__, fw_bin_name, fw->size, enforce_do); + + max77775_usbc_fw_update(max77775, fw->data, fw->size, enforce_do); +} + +int max77775_usbc_fw_setting(struct max77775_dev *max77775, int enforce_do) +{ + int err = 0; + const struct firmware *fw; + char *fw_bin_name = FW_BIN_NAME; + + if (max77775->pdata->extra_fw_enable) + fw_bin_name = EXTRA_FW_BIN_NAME; + + err = request_firmware(&fw, fw_bin_name, max77775->dev); + if (!err) { + __max77775_usbc_fw_setting(max77775, fw, fw_bin_name, enforce_do); + release_firmware(fw); + } + return err; +} +EXPORT_SYMBOL_GPL(max77775_usbc_fw_setting); +#endif /* CONFIG_CCIC_MAX77775 */ + +static u8 max77775_revision_check(u8 pmic_id, u8 pmic_rev) +{ + int i, logical_id = 0; + int pmic_arrary = ARRAY_SIZE(max77775_revision); + + md75_info_usb("%s: pmic_id(0x%02X) pmic_rev(0x%02X)\n", + __func__, pmic_id, pmic_rev); + for (i = 0; i < pmic_arrary; i++) { + md75_info_usb("%s: max77775_revision[%d].id(0x%02X) max77775_revision[%d].rev(0x%02X)\n", + __func__, + i, max77775_revision[i].id, + i, max77775_revision[i].rev); + if (max77775_revision[i].id == pmic_id && + max77775_revision[i].rev == pmic_rev) + logical_id = max77775_revision[i].logical_id; + } + md75_info_usb("%s: logical_id(0x%02X)\n", __func__, logical_id); + return logical_id; +} + +void max77775_register_pdmsg_func(struct max77775_dev *max77775, + void (*check_pdmsg)(void *data, u8 pdmsg), void *data) +{ + if (!max77775) { + md75_err_usb("%s max77775 is null\n", __func__); + return; + } + + max77775->check_pdmsg = check_pdmsg; + max77775->usbc_data = data; +} +EXPORT_SYMBOL_GPL(max77775_register_pdmsg_func); + +static int max77775_check_deferred_probe(struct max77775_dev *max77775, int ret) +{ + static int fw_setting_try_count; + + if (ret && max77775_firmware_timeout_state == 0 + && !is_recovery_mode_pdic_param()) { + fw_setting_try_count++; + md75_info_usb("%s: ret = (%d), return -EPROBE_DEFER(%d)\n", + __func__, ret, fw_setting_try_count); + return -EPROBE_DEFER; + } + + max77775_firmware_timeout_state = MD75_FIRMWARE_TIMEOUT_COMPLETE; + + return 0; +} + +static int max77775_firmware_timeout_probe(struct platform_device *pdev) +{ + md75_info_usb("%s firmware_timeout_count=%d\n", + __func__, firmware_timeout_count); + return 0; +} + +static int max77775_firmware_timeout_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver max77775_firmware_timeout_driver = { + .driver = { + .name = "max77775-firmware-timeout", + }, + .probe = max77775_firmware_timeout_probe, + .remove = max77775_firmware_timeout_remove, +}; + +static void firmware_load_timeout_del(void) +{ + platform_device_unregister(pdev); + platform_driver_unregister(&max77775_firmware_timeout_driver); +} + +static void max77775_firmware_load_timeout(struct work_struct *unused) +{ + int error = 0; + + md75_info_usb("%s enter\n", __func__); + + if (max77775_firmware_timeout_state == MD75_FIRMWARE_TIMEOUT_COMPLETE) { + md75_info_usb("%s already complete\n", __func__); + goto done; + } + + max77775_firmware_timeout_state = MD75_FIRMWARE_TIMEOUT_START; + + while (1) { + firmware_timeout_count++; + error = platform_driver_register(&max77775_firmware_timeout_driver); + if (error) { + md75_err_usb("%s platform_driver_register error %d\n", __func__, error); + goto err1; + } + + pdev = platform_device_alloc("max77775-firmware-timeout", PLATFORM_DEVID_AUTO); + if (!pdev) { + md75_err_usb("%s pdev error\n", __func__); + goto err2; + } + + error = platform_device_add(pdev); + if (error) { + md75_err_usb("%s platform_device_add error %d\n", __func__, error); + goto err3; + } + + max77775_firmware_timeout_state = MD75_FIRMWARE_TIMEOUT_PASS; + + msleep(1000); + + firmware_load_timeout_del(); + + /* 5 times retry. it will call deferred probe */ + if (firmware_timeout_count >= 5 || + max77775_firmware_timeout_state == MD75_FIRMWARE_TIMEOUT_COMPLETE) + break; + } +done: + return; +err3: + platform_device_put(pdev); +err2: + platform_driver_unregister(&max77775_firmware_timeout_driver); +err1: + max77775_firmware_timeout_state = MD75_FIRMWARE_TIMEOUT_FAIL; + md75_info_usb("%s fail\n", __func__); + return; +} + +static DECLARE_DELAYED_WORK(firmware_load_work, max77775_firmware_load_timeout); + +static int max77775_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *dev_id) +{ + struct max77775_dev *max77775; + struct max77775_platform_data *pdata = i2c->dev.platform_data; + int ret = 0; + u8 pmic_id, pmic_rev = 0; +#if IS_ENABLED(CONFIG_CCIC_MAX77775) + const struct firmware *fw = NULL; + char *fw_bin_name = FW_BIN_NAME; +#endif + + md75_info_usb("%s:%s\n", MFD_DEV_NAME, __func__); + +#ifdef CONFIG_USB_USING_ADVANCED_USBLOG + store_tcpc_name(MFD_DEV_NAME); +#endif + + max77775 = devm_kzalloc(&i2c->dev, sizeof(struct max77775_dev), GFP_KERNEL); + if (!max77775) { + ret = -ENOMEM; + goto err; + } + + if (i2c->dev.of_node) { + pdata = devm_kzalloc(&i2c->dev, sizeof(struct max77775_platform_data), + GFP_KERNEL); + if (!pdata) { + ret = -ENOMEM; + goto err; + } + +#if defined(CONFIG_OF) + ret = of_max77775_dt(&i2c->dev, pdata); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to get device of_node\n"); + goto err; + } +#endif + + i2c->dev.platform_data = pdata; + } else + pdata = i2c->dev.platform_data; + + max77775->dev = &i2c->dev; + max77775->i2c = i2c; + max77775->irq = i2c->irq; + if (pdata) { + max77775->pdata = pdata; + + pdata->irq_base = devm_irq_alloc_descs(&i2c->dev, -1, 0, MAX77775_IRQ_NR, -1); + if (pdata->irq_base < 0) { + md75_err_usb("%s:%s irq_alloc_descs Fail! ret(%d)\n", + MFD_DEV_NAME, __func__, pdata->irq_base); + ret = -EINVAL; + goto err; + } else + max77775->irq_base = pdata->irq_base; + + max77775->irq_gpio = pdata->irq_gpio; + max77775->blocking_waterevent = pdata->blocking_waterevent; + max77775->required_hw_rev = pdata->rev; + max77775->required_fw_pid = pdata->fw_product_id; + } else { + ret = -EINVAL; + goto err; + } + +#if IS_ENABLED(CONFIG_CCIC_MAX77775) + if (max77775->pdata->extra_fw_enable) + fw_bin_name = EXTRA_FW_BIN_NAME; + + ret = request_firmware(&fw, fw_bin_name, max77775->dev); + ret = max77775_check_deferred_probe(max77775, ret); + if (ret) + goto err; +#endif + + max77775_print_pdata_property(pdata); + + max77775->ws.name = MFD_DEV_NAME; + wakeup_source_add(&max77775->ws); + + mutex_init(&max77775->i2c_lock); + init_waitqueue_head(&max77775->suspend_wait); + + i2c_set_clientdata(i2c, max77775); + + if (max77775_read_reg(i2c, MAX77775_PMIC_REG_PMICID, &pmic_id) < 0) { + dev_err(max77775->dev, "device not found on this channel (this is not an error)\n"); + ret = -ENODEV; + goto err_w_lock; + } + if (max77775_read_reg(i2c, MAX77775_PMIC_REG_PMICREV, &pmic_rev) < 0) { + dev_err(max77775->dev, "device not found on this channel (this is not an error)\n"); + ret = -ENODEV; + goto err_w_lock; + } + + md75_info_usb("%s:%s pmic_id:%x, pmic_rev:%x\n", + MFD_DEV_NAME, __func__, pmic_id, pmic_rev); + + max77775->pmic_id = pmic_id; + max77775->pmic_rev = max77775_revision_check(pmic_id, pmic_rev & 0x7); + if (max77775->pmic_rev == 0) { + dev_err(max77775->dev, "Can not find matched revision\n"); + ret = -ENODEV; + goto err_w_lock; + } + + /* print rev */ + md75_info_usb("%s:%s device found: id:%x rev:%x\n", + MFD_DEV_NAME, __func__, max77775->pmic_id, max77775->pmic_rev); + + /* No active discharge on safeout ldo 1,2 */ + /* max77775_update_reg(i2c, MAX77775_PMIC_REG_SAFEOUT_CTRL, 0x00, 0x30); */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0) + max77775->muic = i2c_new_dummy(i2c->adapter, I2C_ADDR_MUIC); +#else + max77775->muic = i2c_new_dummy_device(i2c->adapter, I2C_ADDR_MUIC); +#endif + i2c_set_clientdata(max77775->muic, max77775); +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0) + max77775->charger = i2c_new_dummy(i2c->adapter, I2C_ADDR_CHG); +#else + max77775->charger = i2c_new_dummy_device(i2c->adapter, I2C_ADDR_CHG); +#endif + i2c_set_clientdata(max77775->charger, max77775); +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0) + max77775->fuelgauge = i2c_new_dummy(i2c->adapter, I2C_ADDR_FG); +#else + max77775->fuelgauge = i2c_new_dummy_device(i2c->adapter, I2C_ADDR_FG); +#endif + i2c_set_clientdata(max77775->fuelgauge, max77775); +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0) + max77775->testsid = i2c_new_dummy(i2c->adapter, I2C_ADDR_TESTSID); +#else + max77775->testsid = i2c_new_dummy_device(i2c->adapter, I2C_ADDR_TESTSID); +#endif + i2c_set_clientdata(max77775->testsid, max77775); + + /* read PRODUCT_ID, FW_REV, FW_REV2 */ + ret = max77775_read_reg(max77775->muic, REG_PRODUCT_ID, &max77775->FW_Product_ID); + ret += max77775_read_reg(max77775->muic, REG_UIC_FW_REV, &max77775->FW_Revision); + ret += max77775_read_reg(max77775->muic, REG_UIC_FW_REV2, &max77775->FW_Minor_Revision); + if (ret) { + md75_err_usb("%s: Failed to PRODUCT_ID, UIC_FW_REV and UIC_FW_REV2\n", __func__); + goto err_i2c; + } + +#if IS_ENABLED(CONFIG_CCIC_MAX77775) + init_completion(&max77775->fw_completion); + max77775->fw_workqueue = create_singlethread_workqueue("fw_update"); + if (max77775->fw_workqueue == NULL) { + ret = -ENOMEM; + goto err_i2c; + } + INIT_WORK(&max77775->fw_work, max77775_usbc_wait_response_q); + + if (fw) { + __max77775_usbc_fw_setting(max77775, fw, fw_bin_name, 0); + release_firmware(fw); + fw = NULL; + } +#endif + + disable_irq(max77775->irq); + ret = max77775_irq_init(max77775); + if (ret < 0) + goto err_i2c; + + ret = mfd_add_devices(max77775->dev, -1, max77775_devs, + ARRAY_SIZE(max77775_devs), NULL, 0, NULL); + if (ret < 0) + goto err_irq_init; + + ret = device_init_wakeup(max77775->dev, true); + if (ret < 0) + goto err_mfd; + + return ret; + +err_mfd: + mfd_remove_devices(max77775->dev); +err_irq_init: + max77775_irq_exit(max77775); +err_i2c: + i2c_unregister_device(max77775->muic); + i2c_unregister_device(max77775->charger); + i2c_unregister_device(max77775->fuelgauge); +err_w_lock: + mutex_destroy(&max77775->i2c_lock); + wakeup_source_remove(&max77775->ws); +#if IS_ENABLED(CONFIG_CCIC_MAX77775) + if (fw) + release_firmware(fw); +#endif +err: + return ret; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) +static int max77775_i2c_remove(struct i2c_client *i2c) +#else +static void max77775_i2c_remove(struct i2c_client *i2c) +#endif +{ + struct max77775_dev *max77775 = i2c_get_clientdata(i2c); + + device_init_wakeup(max77775->dev, false); + max77775_irq_exit(max77775); + mfd_remove_devices(max77775->dev); + i2c_unregister_device(max77775->muic); + i2c_unregister_device(max77775->charger); + i2c_unregister_device(max77775->fuelgauge); + mutex_destroy(&max77775->i2c_lock); + wakeup_source_remove(&max77775->ws); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) + return 0; +#else + return; +#endif +} + +static void max77775_i2c_shutdown(struct i2c_client *i2c) +{ + struct max77775_dev *max77775 = i2c_get_clientdata(i2c); + + max77775_irq_exit(max77775); + max77775->shutdown = 1; +} + +static const struct i2c_device_id max77775_i2c_id[] = { + { MFD_DEV_NAME, TYPE_MAX77775 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max77775_i2c_id); + +#if defined(CONFIG_OF) +static const struct of_device_id max77775_i2c_dt_ids[] = { + { .compatible = "maxim,max77775" }, + { }, +}; +MODULE_DEVICE_TABLE(of, max77775_i2c_dt_ids); +#endif /* CONFIG_OF */ + +#if defined(CONFIG_PM) +static int max77775_suspend(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max77775_dev *max77775 = i2c_get_clientdata(i2c); + + md75_info_usb("%s:%s\n", MFD_DEV_NAME, __func__); + + synchronize_irq(max77775->irq); + + max77775->suspended = true; + + return 0; +} + +static int max77775_resume(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max77775_dev *max77775 = i2c_get_clientdata(i2c); + + md75_info_usb("%s:%s\n", MFD_DEV_NAME, __func__); + + max77775->suspended = false; + wake_up_interruptible(&max77775->suspend_wait); + + return 0; +} +#else +#define max77775_suspend NULL +#define max77775_resume NULL +#endif /* CONFIG_PM */ + +const struct dev_pm_ops max77775_pm = { + .suspend = max77775_suspend, + .resume = max77775_resume, +}; + +static struct i2c_driver max77775_i2c_driver = { + .driver = { + .name = MFD_DEV_NAME, + .owner = THIS_MODULE, +#if defined(CONFIG_PM) + .pm = &max77775_pm, +#endif /* CONFIG_PM */ +#if defined(CONFIG_OF) + .of_match_table = max77775_i2c_dt_ids, +#endif /* CONFIG_OF */ + }, + .probe = max77775_i2c_probe, + .remove = max77775_i2c_remove, + .shutdown = max77775_i2c_shutdown, + .id_table = max77775_i2c_id, +}; + +static int __init max77775_i2c_init(void) +{ + md75_info_usb("%s:%s\n", MFD_DEV_NAME, __func__); + schedule_delayed_work(&firmware_load_work, MD75_FIRMWARE_TIMEOUT_SEC * HZ); + return i2c_add_driver(&max77775_i2c_driver); +} +/* init early so consumer devices can complete system boot */ +subsys_initcall(max77775_i2c_init); + +static void __exit max77775_i2c_exit(void) +{ + cancel_delayed_work_sync(&firmware_load_work); + i2c_del_driver(&max77775_i2c_driver); +} +module_exit(max77775_i2c_exit); + +MODULE_AUTHOR("Samsung USB Team"); +MODULE_DESCRIPTION("Max77775 MFD driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/sec_ap_pmic.c b/drivers/mfd/sec_ap_pmic.c new file mode 100644 index 000000000000..de956908374a --- /dev/null +++ b/drivers/mfd/sec_ap_pmic.c @@ -0,0 +1,322 @@ +/* + * Copyright (c) 2020, 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 +#if IS_ENABLED(CONFIG_SEC_CRASHKEY_LONG) +#include +#endif +#include +#include + +static struct device *sec_ap_pmic_dev; +static struct sec_ap_pmic_info *sec_ap_pmic_data; + +static ssize_t manual_reset_show(struct device *in_dev, + struct device_attribute *attr, char *buf) +{ + int ret = 0; + + ret = sec_get_s2_reset(SEC_PON_KPDPWR_RESIN); + + pr_info("%s: ret=%d\n", __func__, ret); + return sprintf(buf, "%d\n", !ret); +} + +static ssize_t manual_reset_store(struct device *in_dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + int onoff = 0; + + if (kstrtoint(buf, 10, &onoff)) + return -EINVAL; + + pr_info("%s: onoff=%d\n", __func__, onoff); +#if IS_ENABLED(CONFIG_SEC_CRASHKEY_LONG) + if (onoff) + sec_crashkey_long_connect_to_input_evnet(); + else + sec_crashkey_long_disconnect_from_input_event(); +#endif + + return len; +} +static DEVICE_ATTR_RW(manual_reset); + +static ssize_t wake_enabled_show(struct device *in_dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", (sec_get_pm_key_wk_init(SEC_PON_KPDPWR) && sec_get_pm_key_wk_init(SEC_PON_RESIN)) ? 1 : 0); +} + +static ssize_t wake_enabled_store(struct device *in_dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + int onoff; + int ret; + + if (kstrtoint(buf, 10, &onoff) < 0) + return -EINVAL; + + pr_info("%s: onoff=%d\n", __func__, onoff); + ret = sec_set_pm_key_wk_init(SEC_PON_KPDPWR, onoff); + pr_info("%s: PWR ret=%d\n", __func__, ret); + ret = sec_set_pm_key_wk_init(SEC_PON_RESIN, onoff); + pr_info("%s: RESIN ret=%d\n", __func__, ret); + + return len; +} +static DEVICE_ATTR_RW(wake_enabled); + +#if IS_ENABLED(CONFIG_SEC_GPIO_DUMP) +static ssize_t gpio_dump_show(struct device *in_dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", (gpio_dump_enabled) ? 1 : 0); +} + +static ssize_t gpio_dump_store(struct device *in_dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + int onoff; + + if (kstrtoint(buf, 10, &onoff) < 0) + return -EINVAL; + + pr_info("%s: onoff=%d\n", __func__, onoff); + gpio_dump_enabled = (onoff) ? true : false; + + return len; +} +static DEVICE_ATTR_RW(gpio_dump); +#endif + +/* VDD/IDDQ info */ +#define PARAM0_IVALID 1 +#define PARAM0_LESS_THAN_0 2 + +#define DEFAULT_LEN_STR 1023 + +#define default_scnprintf(buf, offset, fmt, ...) \ +do { \ + offset += scnprintf(&(buf)[offset], DEFAULT_LEN_STR - (size_t)offset, \ + fmt, ##__VA_ARGS__); \ +} while (0) + +static void check_format(char *buf, ssize_t *size, int max_len_str) +{ + int i = 0, cnt = 0, pos = 0; + + if (!buf || *size <= 0) + return; + + if (*size >= max_len_str) + *size = max_len_str - 1; + + while (i < *size && buf[i]) { + if (buf[i] == '"') { + cnt++; + pos = i; + } + + if ((buf[i] < 0x20) || (buf[i] == 0x5C) || (buf[i] > 0x7E)) + buf[i] = ' '; + i++; + } + + if (cnt % 2) { + if (pos == *size - 1) { + buf[*size - 1] = '\0'; + } else { + buf[*size - 1] = '"'; + buf[*size] = '\0'; + } + } +} + +static int get_param0(const char *name) +{ + struct device_node *np = of_find_node_by_path("/soc/sec_ap_param"); + u32 val; + int ret; + + if (!np) { + pr_err("No sec_avi_data found\n"); + return -PARAM0_IVALID; + } + + ret = of_property_read_u32(np, name, &val); + if (ret) { + pr_err("failed to get %s from node\n", name); + return -PARAM0_LESS_THAN_0; + } + + return val; +} + +#define GET_V(A) ((A) / 1000) +#define GET_MV(A) ((A) % 1000) +static ssize_t show_ap_info(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t info_size = 0; + + /* currently, support only for GC_OPV */ + default_scnprintf(buf, info_size, "\"GC_OPV_3\":\"%d.%03d\"", GET_V(get_param0("go")), GET_MV(get_param0("go"))); + default_scnprintf(buf, info_size, ",\"GC_PRM\":\"%d\"", get_param0("gi")); + default_scnprintf(buf, info_size, ",\"DOUR\":\"%d\"", get_param0("dour")); + default_scnprintf(buf, info_size, ",\"DOUB\":\"%d\"", get_param0("doub")); + + check_format(buf, &info_size, DEFAULT_LEN_STR); + + return info_size; +} +static DEVICE_ATTR(ap_info, 0440, show_ap_info, NULL); + +static struct attribute *sec_ap_pmic_attributes[] = { +#if IS_ENABLED(CONFIG_SEC_GPIO_DUMP) + &dev_attr_gpio_dump.attr, +#endif + &dev_attr_manual_reset.attr, + &dev_attr_wake_enabled.attr, + &dev_attr_ap_info.attr, + NULL, +}; + +static struct attribute_group sec_ap_pmic_attr_group = { + .attrs = sec_ap_pmic_attributes, +}; + +#if IS_ENABLED(CONFIG_SEC_GPIO_DUMP) +static void gpio_state_debug_suspend_trace_probe(void *unused, + const char *action, int val, bool start) +{ + /* SUSPEND: start(1), val(1), action(machine_suspend) */ + if (gpio_dump_enabled && start && val > 0 && !strcmp("machine_suspend", action)) { + sec_ap_gpio_debug_print(); + sec_pmic_gpio_debug_print(); + } +} +#endif + +static int sec_ap_pmic_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct sec_ap_pmic_info *info; + int err; + + if (!node) { + dev_err(&pdev->dev, "device-tree data is missing\n"); + return -ENXIO; + } + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(&pdev->dev, "%s: Fail to alloc info\n", __func__); + return -ENOMEM; + } + + platform_set_drvdata(pdev, info); + info->dev = &pdev->dev; + sec_ap_pmic_data = info; + +#if IS_ENABLED(CONFIG_SEC_CLASS) + sec_ap_pmic_dev = sec_device_create(NULL, "ap_pmic"); + + if (unlikely(IS_ERR(sec_ap_pmic_dev))) { + pr_err("%s: Failed to create ap_pmic device\n", __func__); + err = PTR_ERR(sec_ap_pmic_dev); + goto err_device_create; + } + + err = sysfs_create_group(&sec_ap_pmic_dev->kobj, + &sec_ap_pmic_attr_group); + if (err < 0) { + pr_err("%s: Failed to create sysfs group\n", __func__); + goto err_device_create; + } +#endif + +#if IS_ENABLED(CONFIG_SEC_GPIO_DUMP) + /* Register callback for cheking subsystem stats */ + err = register_trace_suspend_resume( + gpio_state_debug_suspend_trace_probe, NULL); + if (err) { + pr_err("%s: Failed to register suspend trace callback, ret=%d\n", + __func__, err); + } +#endif + + pr_info("%s: ap_pmic successfully inited.\n", __func__); + + return 0; + +#if IS_ENABLED(CONFIG_SEC_CLASS) +err_device_create: + sec_device_destroy(sec_ap_pmic_dev->devt); + return err; +#endif +} + +static int sec_ap_pmic_remove(struct platform_device *pdev) +{ +#if IS_ENABLED(CONFIG_SEC_GPIO_DUMP) + int ret; + + ret = unregister_trace_suspend_resume( + gpio_state_debug_suspend_trace_probe, NULL); +#endif + +#if IS_ENABLED(CONFIG_SEC_CLASS) + if (sec_ap_pmic_dev) { + sec_device_destroy(sec_ap_pmic_dev->devt); + } +#endif + + return 0; +} + +static const struct of_device_id sec_ap_pmic_match_table[] = { + { .compatible = "samsung,sec-ap-pmic" }, + {} +}; + +static struct platform_driver sec_ap_pmic_driver = { + .driver = { + .name = "samsung,sec-ap-pmic", + .of_match_table = sec_ap_pmic_match_table, + }, + .probe = sec_ap_pmic_probe, + .remove = sec_ap_pmic_remove, +}; + +module_platform_driver(sec_ap_pmic_driver); + +MODULE_DESCRIPTION("sec_ap_pmic driver"); +MODULE_SOFTDEP("pre: sec_class"); +MODULE_LICENSE("GPL"); +MODULE_SOFTDEP("pre: sec_crashkey_long"); +MODULE_SOFTDEP("pre: pm8941-pwrkey"); +MODULE_AUTHOR("Jiman Cho +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if IS_ENABLED(CONFIG_OF) +#include +#include +#endif /* CONFIG_OF */ + +static struct mfd_cell s2mpb02_devs[] = { +#if IS_ENABLED(CONFIG_LEDS_S2MPB02) + { .name = "s2mpb02-led", }, +#endif /* CONFIG_LEDS_S2MPB02 */ + { .name = "s2mpb02-regulator", }, +}; + +int s2mpb02_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest) +{ + struct s2mpb02_dev *s2mpb02 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&s2mpb02->i2c_lock); + ret = i2c_smbus_read_byte_data(i2c, reg); + mutex_unlock(&s2mpb02->i2c_lock); + if (ret < 0) { + pr_info("%s:%s reg(0x%02hhx), ret(%d)\n", + MFD_DEV_NAME, __func__, reg, ret); + return ret; + } + + ret &= 0xff; + *dest = ret; + return 0; +} +EXPORT_SYMBOL_GPL(s2mpb02_read_reg); + +int s2mpb02_bulk_read(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct s2mpb02_dev *s2mpb02 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&s2mpb02->i2c_lock); + ret = i2c_smbus_read_i2c_block_data(i2c, reg, count, buf); + mutex_unlock(&s2mpb02->i2c_lock); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(s2mpb02_bulk_read); + +int s2mpb02_write_reg(struct i2c_client *i2c, u8 reg, u8 value) +{ + struct s2mpb02_dev *s2mpb02 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&s2mpb02->i2c_lock); + ret = i2c_smbus_write_byte_data(i2c, reg, value); + mutex_unlock(&s2mpb02->i2c_lock); + if (ret < 0) + pr_info("%s:%s reg(0x%02hhx), ret(%d)\n", + MFD_DEV_NAME, __func__, reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(s2mpb02_write_reg); + +int s2mpb02_bulk_write(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct s2mpb02_dev *s2mpb02 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&s2mpb02->i2c_lock); + ret = i2c_smbus_write_i2c_block_data(i2c, reg, count, buf); + mutex_unlock(&s2mpb02->i2c_lock); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(s2mpb02_bulk_write); + +int s2mpb02_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask) +{ + struct s2mpb02_dev *s2mpb02 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&s2mpb02->i2c_lock); + ret = i2c_smbus_read_byte_data(i2c, reg); + if (ret >= 0) { + u8 old_val = ret & 0xff; + u8 new_val = (val & mask) | (old_val & (~mask)); + ret = i2c_smbus_write_byte_data(i2c, reg, new_val); + } + mutex_unlock(&s2mpb02->i2c_lock); + return ret; +} +EXPORT_SYMBOL_GPL(s2mpb02_update_reg); + +#if IS_ENABLED(CONFIG_OF) +static int of_s2mpb02_dt(struct device *dev, + struct s2mpb02_platform_data *pdata) +{ + struct device_node *np_s2mpb02 = dev->of_node; + int count = 0; + int i, ret; + + if (!np_s2mpb02) + return -EINVAL; + + pdata->irq_gpio = of_get_named_gpio(np_s2mpb02, "s2mpb02,irq-gpio", 0); + pdata->wakeup = of_property_read_bool(np_s2mpb02, "s2mpb02,wakeup"); + + count = of_property_count_strings(np_s2mpb02, "s2mpb02,mfd-cell"); + + if (!count || (count == -EINVAL)) { + pdata->devs_num = ARRAY_SIZE(s2mpb02_devs); + pdata->devs = s2mpb02_devs; + + for (i = 0; i < pdata->devs_num; i++) + pr_info("%s mfd-cell(%d) = %s\n", __func__, i, + pdata->devs[i].name); + + } else { + pdata->devs_num = count; + pdata->devs = kcalloc(pdata->devs_num, + sizeof(struct mfd_cell), GFP_KERNEL); + if (!pdata->devs) { + pr_err("%s failed %d\n", __func__, __LINE__); + return -ENOMEM; + } + + for (i = 0; i < pdata->devs_num; i++) { + ret = of_property_read_string_index(np_s2mpb02, + "s2mpb02,mfd-cell", i, &pdata->devs[i].name); + pr_info("%s mfd-cell(%d) = %s\n", __func__, i, + pdata->devs[i].name); + if (ret < 0) { + pr_err("%s failed %d\n", __func__, __LINE__); + goto err_mfd_cell; + } + } + } + + return 0; + +err_mfd_cell: + kfree(pdata->devs); + return ret; +} +#else +static int of_s2mpb02_dt(struct device *dev, + struct s2mpb02_platform_data *pdata) +{ + return 0; +} +#endif /* CONFIG_OF */ + +static int __s2mpb02_i2c_probe(struct i2c_client *i2c) +{ + struct s2mpb02_dev *s2mpb02; + struct s2mpb02_platform_data *pdata = i2c->dev.platform_data; + + int ret = 0; + u8 reg_data; + + pr_info("%s:%s\n", MFD_DEV_NAME, __func__); + + s2mpb02 = devm_kzalloc(&i2c->dev, sizeof(struct s2mpb02_dev), GFP_KERNEL); + if (!s2mpb02) { + dev_err(&i2c->dev, "%s: Failed to alloc mem for s2mpb02\n", + __func__); + return -ENOMEM; + } + + if (i2c->dev.of_node) { + pdata = devm_kzalloc(&i2c->dev, + sizeof(struct s2mpb02_platform_data), GFP_KERNEL); + if (!pdata) { + dev_err(&i2c->dev, "Failed to allocate memory\n"); + ret = -ENOMEM; + goto err_pdata; + } + + ret = of_s2mpb02_dt(&i2c->dev, pdata); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to get device of_node\n"); + goto err; + } + + i2c->dev.platform_data = pdata; + } else + pdata = i2c->dev.platform_data; + + s2mpb02->dev = &i2c->dev; + s2mpb02->i2c = i2c; + s2mpb02->irq = i2c->irq; + if (pdata) { + s2mpb02->pdata = pdata; + + pdata->irq_base = devm_irq_alloc_descs(s2mpb02->dev, -1, 600, S2MPB02_IRQ_NR, 0); + if (pdata->irq_base < 0) { + pr_err("%s:%s devm_irq_alloc_descs Fail! ret(%d)\n", + MFD_DEV_NAME, __func__, pdata->irq_base); + ret = -EINVAL; + goto err; + } else { + s2mpb02->irq_base = pdata->irq_base; + } + + s2mpb02->wakeup = pdata->wakeup; + } else { + ret = -EINVAL; + goto err; + } + mutex_init(&s2mpb02->i2c_lock); + + i2c_set_clientdata(i2c, s2mpb02); + + if (s2mpb02_read_reg(s2mpb02->i2c, + S2MPB02_REG_BST_CTRL2, ®_data) < 0) { + pr_err("device not found on this channel!!\n"); + ret = -ENODEV; + } else { + if (reg_data == DVS_DEFAULT_VALUE) { + S2MPB02_PMIC_REV(s2mpb02) = 1; + } else + S2MPB02_PMIC_REV(s2mpb02) = 0; + pr_info("%s: device id 0x%02hhx is found\n", + __func__, s2mpb02->rev_num); + } + + ret = s2mpb02_irq_init(s2mpb02); + if (ret < 0) + goto err_irq_init; + + ret = mfd_add_devices(s2mpb02->dev, -1, pdata->devs, + pdata->devs_num, NULL, 0, NULL); + + if (ret < 0) + goto err_irq_init; + + ret = device_init_wakeup(s2mpb02->dev, pdata->wakeup); + if (ret < 0) { + pr_err("%s: device_init_wakeup fail(%d)\n", __func__, ret); + goto err_irq_init; + } + + return ret; + +err_irq_init: + mutex_destroy(&s2mpb02->i2c_lock); +err: + kfree(pdata); + s2mpb02_irq_exit(s2mpb02); +err_pdata: + kfree(s2mpb02); + + return ret; +} + +static int __s2mpb02_i2c_remove(struct i2c_client *i2c) +{ + struct s2mpb02_dev *s2mpb02 = i2c_get_clientdata(i2c); + + if (s2mpb02->pdata->wakeup) + device_init_wakeup(s2mpb02->dev, false); + mfd_remove_devices(s2mpb02->dev); + s2mpb02_irq_exit(s2mpb02); + mutex_destroy(&s2mpb02->i2c_lock); + kfree(s2mpb02); + + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +static void s2mpb02_i2c_remove(struct i2c_client *i2c) +{ + __s2mpb02_i2c_remove(i2c); +} +#else +static int s2mpb02_i2c_remove(struct i2c_client *i2c) +{ + return __s2mpb02_i2c_remove(i2c); +} +#endif + +static const struct i2c_device_id s2mpb02_i2c_id[] = { + { MFD_DEV_NAME, TYPE_S2MPB02 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, s2mpb02_i2c_id); + +#if IS_ENABLED(CONFIG_OF) +static struct of_device_id s2mpb02_i2c_dt_ids[] = { + { .compatible = "s2mpb02,s2mpb02mfd" }, + {}, +}; +MODULE_DEVICE_TABLE(of, s2mpb02_i2c_dt_ids); +#endif /* CONFIG_OF */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) +static int s2mpb02_i2c_probe(struct i2c_client *i2c) +{ + return __s2mpb02_i2c_probe(i2c); +} +#else +static int s2mpb02_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *dev_id) +{ + return __s2mpb02_i2c_probe(i2c); +} +#endif + +#if IS_ENABLED(CONFIG_PM) +static int s2mpb02_suspend(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct s2mpb02_dev *s2mpb02 = i2c_get_clientdata(i2c); + + if (device_may_wakeup(dev)) + enable_irq_wake(s2mpb02->irq); + + disable_irq(s2mpb02->irq); + + return 0; +} + +static int s2mpb02_resume(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct s2mpb02_dev *s2mpb02 = i2c_get_clientdata(i2c); + + if (device_may_wakeup(dev)) + disable_irq_wake(s2mpb02->irq); + + enable_irq(s2mpb02->irq); + + return 0; +} +#else +#define s2mpb02_suspend NULL +#define s2mpb02_resume NULL +#endif /* CONFIG_PM */ + +static const struct dev_pm_ops s2mpb02_pm = { + .suspend = s2mpb02_suspend, + .resume = s2mpb02_resume, +}; + +static struct i2c_driver s2mpb02_i2c_driver = { + .driver = { + .name = MFD_DEV_NAME, + .owner = THIS_MODULE, +#if IS_ENABLED(CONFIG_PM) + .pm = &s2mpb02_pm, +#endif /* CONFIG_PM */ +#if IS_ENABLED(CONFIG_OF) + .of_match_table = s2mpb02_i2c_dt_ids, +#endif /* CONFIG_OF */ + .suppress_bind_attrs = true, + }, + .probe = s2mpb02_i2c_probe, + .remove = s2mpb02_i2c_remove, + .id_table = s2mpb02_i2c_id, +}; + +static int __init s2mpb02_i2c_init(void) +{ + pr_info("%s:%s\n", MFD_DEV_NAME, __func__); + return i2c_add_driver(&s2mpb02_i2c_driver); +} +/* init early so consumer devices can complete system boot */ +subsys_initcall(s2mpb02_i2c_init); + +static void __exit s2mpb02_i2c_exit(void) +{ + i2c_del_driver(&s2mpb02_i2c_driver); +} +module_exit(s2mpb02_i2c_exit); + +MODULE_DESCRIPTION("SAMSUNG s2mpb02 multi-function core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/slsi/s2mpb02/s2mpb02-irq.c b/drivers/mfd/slsi/s2mpb02/s2mpb02-irq.c new file mode 100755 index 000000000000..de3f6d32bd4c --- /dev/null +++ b/drivers/mfd/slsi/s2mpb02/s2mpb02-irq.c @@ -0,0 +1,230 @@ +/* + * s2mpb02-irq.c - Interrupt controller support for S2MPB02 + * + * Copyright (C) 2014 Samsung Electronics Co.Ltd + * + * 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 + * + * This driver is based on max77804-irq.c + */ + +#include +#include +#include +#include +#include +#include + +static const u8 s2mpb02_mask_reg[] = { + [LED_INT] = S2MPB02_REG_INT1M, +}; + +struct s2mpb02_irq_data { + int mask; + enum s2mpb02_irq_source group; +}; + +static const struct s2mpb02_irq_data s2mpb02_irqs[] = { + [S2MPB02_LED_IRQ_IRLED_END] = { + .group = LED_INT, + .mask = 1 << 5 + }, +}; + +static void s2mpb02_irq_lock(struct irq_data *data) +{ + struct s2mpb02_dev *s2mpb02 = irq_get_chip_data(data->irq); + + mutex_lock(&s2mpb02->irqlock); +} + +static void s2mpb02_irq_sync_unlock(struct irq_data *data) +{ + struct s2mpb02_dev *s2mpb02 = irq_get_chip_data(data->irq); + int i; + + for (i = 0; i < S2MPB02_IRQ_GROUP_NR; i++) { + u8 mask_reg = s2mpb02_mask_reg[i]; + struct i2c_client *i2c = s2mpb02->i2c; + + if (mask_reg == S2MPB02_REG_INVALID || IS_ERR_OR_NULL(i2c)) + continue; + s2mpb02->irq_masks_cache[i] = s2mpb02->irq_masks_cur[i]; + + s2mpb02_write_reg(i2c, s2mpb02_mask_reg[i], + s2mpb02->irq_masks_cur[i]); + } + + mutex_unlock(&s2mpb02->irqlock); +} + +static const inline struct s2mpb02_irq_data * +irq_to_s2mpb02_irq(struct s2mpb02_dev *s2mpb02, int irq) +{ + return &s2mpb02_irqs[irq - s2mpb02->irq_base]; +} + +static void s2mpb02_irq_mask(struct irq_data *data) +{ + struct s2mpb02_dev *s2mpb02 = irq_get_chip_data(data->irq); + const struct s2mpb02_irq_data *irq_data = + irq_to_s2mpb02_irq(s2mpb02, data->irq); + + if (irq_data->group >= S2MPB02_IRQ_GROUP_NR) + return; + + s2mpb02->irq_masks_cur[irq_data->group] |= irq_data->mask; +} + +static void s2mpb02_irq_unmask(struct irq_data *data) +{ + struct s2mpb02_dev *s2mpb02 = irq_get_chip_data(data->irq); + const struct s2mpb02_irq_data *irq_data = + irq_to_s2mpb02_irq(s2mpb02, data->irq); + + if (irq_data->group >= S2MPB02_IRQ_GROUP_NR) + return; + + s2mpb02->irq_masks_cur[irq_data->group] &= ~irq_data->mask; +} + +static struct irq_chip s2mpb02_irq_chip = { + .name = MFD_DEV_NAME, + .irq_bus_lock = s2mpb02_irq_lock, + .irq_bus_sync_unlock = s2mpb02_irq_sync_unlock, + .irq_mask = s2mpb02_irq_mask, + .irq_unmask = s2mpb02_irq_unmask, +}; + +static irqreturn_t s2mpb02_irq_thread(int irq, void *data) +{ + struct s2mpb02_dev *s2mpb02 = data; + u8 irq_reg[S2MPB02_IRQ_GROUP_NR] = {0}; + int i, ret; + + pr_debug("%s: irq gpio pre-state(0x%02x)\n", __func__, + gpio_get_value(s2mpb02->irq_gpio)); + + /* LED_INT */ + ret = s2mpb02_read_reg(s2mpb02->i2c, + S2MPB02_REG_INT1, &irq_reg[LED_INT]); + pr_info("%s: led interrupt(0x%02hhx)\n", + __func__, irq_reg[LED_INT]); + + pr_debug("%s: irq gpio post-state(0x%02x)\n", __func__, + gpio_get_value(s2mpb02->irq_gpio)); + + /* Apply masking */ + for (i = 0; i < S2MPB02_IRQ_GROUP_NR; i++) + irq_reg[i] &= ~s2mpb02->irq_masks_cur[i]; + + /* Report */ + for (i = 0; i < S2MPB02_IRQ_NR; i++) { + if (irq_reg[s2mpb02_irqs[i].group] & s2mpb02_irqs[i].mask) + handle_nested_irq(s2mpb02->irq_base + i); + } + + return IRQ_HANDLED; +} + +int s2mpb02_irq_init(struct s2mpb02_dev *s2mpb02) +{ + int i; + int ret; + + if (!s2mpb02->irq_gpio) { + pr_warn("%s:%s No interrupt specified.\n", + MFD_DEV_NAME, __func__); + s2mpb02->irq_base = 0; + return 0; + } + + if (s2mpb02->irq_base < 0) { + pr_err("%s:%s No interrupt base specified.\n", + MFD_DEV_NAME, __func__); + return 0; + } + + mutex_init(&s2mpb02->irqlock); + + s2mpb02->irq = gpio_to_irq(s2mpb02->irq_gpio); + pr_info("%s:%s irq=%d, irq->gpio=%d\n", MFD_DEV_NAME, __func__, + s2mpb02->irq, s2mpb02->irq_gpio); + + ret = gpio_request(s2mpb02->irq_gpio, "sub_pmic_irq"); + if (ret) { + pr_err("%s:%s failed requesting gpio %d\n", + MFD_DEV_NAME, __func__, s2mpb02->irq_gpio); + goto err; + } + gpio_direction_input(s2mpb02->irq_gpio); + gpio_free(s2mpb02->irq_gpio); + + /* Mask interrupt sources */ + for (i = 0; i < S2MPB02_IRQ_GROUP_NR; i++) { + struct i2c_client *i2c; + + s2mpb02->irq_masks_cur[i] = 0xff; + s2mpb02->irq_masks_cache[i] = 0xff; + + i2c = s2mpb02->i2c; + + if (IS_ERR_OR_NULL(i2c)) + continue; + if (s2mpb02_mask_reg[i] == S2MPB02_REG_INVALID) + continue; + s2mpb02_write_reg(i2c, s2mpb02_mask_reg[i], 0xff); + } + + /* Register with genirq */ + for (i = 0; i < S2MPB02_IRQ_NR; i++) { + int cur_irq = i + s2mpb02->irq_base; + ret = irq_set_chip_data(cur_irq, s2mpb02); + if (ret) { + dev_err(s2mpb02->dev, + "Failed to irq_set_chip_data %d: %d\n", + s2mpb02->irq, ret); + goto err; + } + irq_set_chip_and_handler(cur_irq, &s2mpb02_irq_chip, + handle_edge_irq); + irq_set_nested_thread(cur_irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(cur_irq, IRQF_VALID); +#else + irq_set_noprobe(cur_irq); +#endif + } + + ret = request_threaded_irq(s2mpb02->irq, NULL, s2mpb02_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, "s2mpb02-irq", s2mpb02); + if (ret) { + pr_err("%s:%s Failed to request IRQ %d: %d\n", MFD_DEV_NAME, + __func__, s2mpb02->irq, ret); + goto err; + } + + return 0; +err: + mutex_destroy(&s2mpb02->i2c_lock); + return ret; +} + +void s2mpb02_irq_exit(struct s2mpb02_dev *s2mpb02) +{ + if (s2mpb02->irq) + free_irq(s2mpb02->irq, s2mpb02); +} + diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 727c7046a6d1..5b0164bcce2d 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -532,3 +532,11 @@ source "drivers/misc/uacce/Kconfig" source "drivers/misc/pvpanic/Kconfig" source "drivers/misc/mchp_pci1xxxx/Kconfig" endmenu + +# +# Dev-Ril-Bridge driver +# + +config DEV_RIL_BRIDGE + tristate "Support a bridge between device and RIL" + default n diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index f5c57b1a1c1a..b79061a52f61 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -64,3 +64,9 @@ obj-$(CONFIG_GP_PCI1XXXX) += mchp_pci1xxxx/ obj-$(CONFIG_VCPU_STALL_DETECTOR) += vcpu_stall_detector.o obj-$(CONFIG_UID_SYS_STATS) += uid_sys_stats.o obj-$(CONFIG_QSEECOM_PROXY) += qseecom_proxy.o + +# +# Makefile for the Dev-Ril-Bridge driver +# + +obj-$(CONFIG_DEV_RIL_BRIDGE) += dev_ril_bridge.o diff --git a/drivers/misc/dev_ril_bridge.c b/drivers/misc/dev_ril_bridge.c new file mode 100644 index 000000000000..156aeefa384d --- /dev/null +++ b/drivers/misc/dev_ril_bridge.c @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2017 Samsung Electronics. + * + * 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 + +#include + +#define LOG_TAG "drb: " + +#define drb_err(fmt, ...) \ + pr_err(LOG_TAG "%s: " pr_fmt(fmt), __func__, ##__VA_ARGS__) + +#define drb_debug(fmt, ...) \ + pr_debug(LOG_TAG "%s: " pr_fmt(fmt), __func__, ##__VA_ARGS__) + +#define drb_info(fmt, ...) \ + pr_info(LOG_TAG "%s: " pr_fmt(fmt), __func__, ##__VA_ARGS__) + +struct drb_dev { + atomic_t opened; + wait_queue_head_t wq; + struct sk_buff_head sk_rx_q; + struct miscdevice miscdev; +}; + +static struct drb_dev *drb_dev; + +int dev_ril_bridge_send_msg(int id, int size, void *buf) +{ + struct sk_buff *skb; + struct sk_buff_head *rxq; + struct sipc_fmt_hdr *sipc_hdr; + unsigned int alloc_size; + unsigned int headroom; + + drb_info("id=%d size=%d\n", id, size); + if (!drb_dev) { + drb_err("ERR! dev_ril_bridge is not ready\n"); + return -ENODEV; + } + + rxq = &drb_dev->sk_rx_q; + headroom = sizeof(struct sipc_fmt_hdr); + alloc_size = size + headroom; + + skb = alloc_skb(alloc_size, GFP_ATOMIC); + if (!skb) { + drb_err("ERR! alloc_skb fail\n"); + return -ENOMEM; + } + + skb_reserve(skb, headroom); + memcpy(skb_put(skb, size), buf, size); + + sipc_hdr = (struct sipc_fmt_hdr *)skb_push(skb, headroom); + sipc_hdr->len = alloc_size; + sipc_hdr->main_cmd = 0x27; + sipc_hdr->sub_cmd = id; + sipc_hdr->cmd_type = 0x05; + + skb_queue_tail(rxq, skb); + + if (atomic_read(&drb_dev->opened) > 0) + wake_up(&drb_dev->wq); + else + return -EPIPE; + + return 0; +} +EXPORT_SYMBOL_GPL(dev_ril_bridge_send_msg); + +static RAW_NOTIFIER_HEAD(dev_ril_bridge_chain); + +int register_dev_ril_bridge_event_notifier(struct notifier_block *nb) +{ + if (!nb) + return -ENOENT; + + return raw_notifier_chain_register(&dev_ril_bridge_chain, nb); +} +EXPORT_SYMBOL_GPL(register_dev_ril_bridge_event_notifier); + +int unregister_dev_ril_bridge_event_notifier(struct notifier_block *nb) +{ + if (!nb) + return -ENOENT; + + return raw_notifier_chain_unregister(&dev_ril_bridge_chain, nb); +} +EXPORT_SYMBOL_GPL(unregister_dev_ril_bridge_event_notifier); + +static int misc_open(struct inode *inode, struct file *filp) +{ + filp->private_data = (void *)drb_dev; + atomic_inc(&drb_dev->opened); + + drb_info("drb (opened %d) by %s\n", + atomic_read(&drb_dev->opened), current->comm); + + return 0; +} + +static int misc_release(struct inode *inode, struct file *filp) +{ + struct drb_dev *drb_dev = (struct drb_dev *)filp->private_data; + + if (atomic_dec_and_test(&drb_dev->opened)) { + skb_queue_purge(&drb_dev->sk_rx_q); + } + + filp->private_data = NULL; + + drb_info("drb (opened %d) by %s\n", + atomic_read(&drb_dev->opened), current->comm); + + return 0; +} + +static unsigned int misc_poll(struct file *filp, struct poll_table_struct *wait) +{ + struct drb_dev *drb_dev = (struct drb_dev *)filp->private_data; + struct sk_buff_head *rxq; + int ret = 0; + + if (!drb_dev) + return POLLERR; + + rxq = &drb_dev->sk_rx_q; + + if (skb_queue_empty(rxq)) + poll_wait(filp, &drb_dev->wq, wait); + + /* drb_dev was already released by shutdown logic */ + if (!drb_dev) + return POLLERR; + + if (!skb_queue_empty(rxq)) + ret = POLLIN | POLLRDNORM; + + drb_info("poll done by %s (%d)\n", current->comm, ret); + + return ret; +} + +static ssize_t misc_write(struct file *filp, const char __user *data, + size_t count, loff_t *fops) +{ + struct dev_ril_bridge_msg msg; + struct sipc_fmt_hdr *sipc_hdr; + char *buf; + + if (count <= sizeof(struct sipc_fmt_hdr)) { + drb_err("ERR! too small size data(count %lu)\n", + (unsigned long)count); + return -EFAULT; + } + + buf = kmalloc(count, GFP_KERNEL); + if (!buf) { + drb_err("ERR! kmalloc failed\n"); + return -ENOMEM; + } + + if (copy_from_user(buf, data, count)) { + drb_err("ERR! copy_from_user fail(count %lu)\n", + (unsigned long)count); + kfree(buf); + return -EFAULT; + } + + sipc_hdr = (struct sipc_fmt_hdr *)buf; + if (sipc_hdr->main_cmd != 0x27 || sipc_hdr->cmd_type != 0x03) { + drb_err("ERR! wrong cmd(main_cmd=%02x, cmd_type=%02x)\n", + sipc_hdr->main_cmd, sipc_hdr->cmd_type); + kfree(buf); + return -EFAULT; + } + + msg.dev_id = sipc_hdr->sub_cmd; + msg.data_len = count - (unsigned int)sizeof(struct sipc_fmt_hdr); + msg.data = (void *)(buf + sizeof(struct sipc_fmt_hdr)); + + drb_info("notifier_call: dev_id=%u data_len=%u\n", msg.dev_id, msg.data_len); + + raw_notifier_call_chain(&dev_ril_bridge_chain, + sizeof(struct dev_ril_bridge_msg), (void *)&msg); + + kfree(buf); + return count; +} + +static ssize_t misc_read(struct file *filp, char *buf, size_t count, + loff_t *fops) +{ + struct drb_dev *drb_dev = (struct drb_dev *)filp->private_data; + struct sk_buff_head *rxq = &drb_dev->sk_rx_q; + unsigned int cnt = (unsigned int)count; + struct sk_buff *skb; + ssize_t copied; + + if (skb_queue_empty(rxq)) { + long tmo = msecs_to_jiffies(100); + wait_event_timeout(drb_dev->wq, !skb_queue_empty(rxq), tmo); + } + + skb = skb_dequeue(rxq); + if (unlikely(!skb)) { + drb_err("No data in RXQ\n"); + return 0; + } + + copied = skb->len > cnt ? cnt : skb->len; + + if (copy_to_user(buf, skb->data, copied)) { + drb_err("ERR! copy_to_user fail\n"); + dev_kfree_skb_any(skb); + return -EFAULT; + } + + drb_info("data:%d copied:%ld qlen:%d\n", skb->len, copied, rxq->qlen); + + if (skb->len > copied) { + skb_pull(skb, copied); + skb_queue_head(rxq, skb); + } else { + dev_kfree_skb_any(skb); + } + + return copied; +} + +static const struct file_operations misc_io_fops = { + .owner = THIS_MODULE, + .open = misc_open, + .release = misc_release, + .poll = misc_poll, + .write = misc_write, + .read = misc_read, +}; + +static int __init dev_ril_bridge_init(void) +{ + int err = 0; + + drb_info("+++\n"); + + if (drb_dev != NULL) { + drb_info("already probed\n"); + return 0; + } + + drb_dev = kzalloc(sizeof(struct drb_dev), GFP_KERNEL); + if (drb_dev == NULL) + return -ENOMEM; + + init_waitqueue_head(&drb_dev->wq); + skb_queue_head_init(&drb_dev->sk_rx_q); + + drb_dev->miscdev.minor = MISC_DYNAMIC_MINOR; + drb_dev->miscdev.name = "drb"; + drb_dev->miscdev.fops = &misc_io_fops; + + err = misc_register(&drb_dev->miscdev); + if (err) { + drb_err("misc_register fail\n"); + goto out; + } + + drb_info("---\n"); + + return 0; + +out: + drb_info("err = %d ---\n", err); + return err; +} + +static void __exit dev_ril_bridge_exit(void) +{ + drb_info("\n"); +} + +module_init(dev_ril_bridge_init); +module_exit(dev_ril_bridge_exit); + +MODULE_DESCRIPTION("dev_ril_bridge driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 97a4ef7eb8d4..d1fc95652a27 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -1152,3 +1152,12 @@ config SDHCI_MSM_DBG used for debugging purpose. If unsure, say N here. + +config SEC_MMC_FEATURE + tristate "SEC specific MMC/SD feature" + depends on MMC_SDHCI_MSM + help + Enable Samsung feature support + Enabling this allows kernel to use SEC specific feature + defined and implemented by SEC. + diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 207a7a86b562..a65a9c187ef9 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -92,7 +92,12 @@ obj-$(CONFIG_MMC_SDHCI_OF_SPARX5) += sdhci-of-sparx5.o obj-$(CONFIG_MMC_SDHCI_BCM_KONA) += sdhci-bcm-kona.o obj-$(CONFIG_MMC_SDHCI_IPROC) += sdhci-iproc.o obj-$(CONFIG_MMC_SDHCI_MSM_SCALING) += sdhci-msm-scaling.o +ifeq ($(CONFIG_SEC_MMC_FEATURE),m) +obj-$(CONFIG_MMC_SDHCI_MSM) += sdhci-msm-sec.o +sdhci-msm-sec-y += sdhci-msm.o mmc-sec-feature.o mmc-sec-sysfs.o +else obj-$(CONFIG_MMC_SDHCI_MSM) += sdhci-msm.o +endif obj-$(CONFIG_MMC_SDHCI_ST) += sdhci-st.o obj-$(CONFIG_MMC_SDHCI_MICROCHIP_PIC32) += sdhci-pic32.o obj-$(CONFIG_MMC_SDHCI_BRCMSTB) += sdhci-brcmstb.o diff --git a/drivers/mmc/host/mmc-sec-feature.c b/drivers/mmc/host/mmc-sec-feature.c new file mode 100644 index 000000000000..e1383ca52b59 --- /dev/null +++ b/drivers/mmc/host/mmc-sec-feature.c @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Samsung Specific feature + * + * Copyright (C) 2024 Samsung Electronics Co., Ltd. + * + * Authors: + * Storage Driver + */ + +#include +#include +#include +#include + +#include "mmc-sec-feature.h" +#include "mmc-sec-sysfs.h" + +struct mmc_sd_sec_device_info sdi; + +static int sd_sec_sdcard_uevent(struct device *dev, + struct kobj_uevent_env *env) +{ + struct mmc_host *host = dev_get_drvdata(dev); + int retval; + bool card_exist = false; + struct mmc_sd_sec_status_err_info *status_err = &sdi.status_err; + + add_uevent_var(env, "DEVNAME=%s", dev->kobj.name); + + if (host->card) + card_exist = true; + + retval = add_uevent_var(env, "IOERROR=%s", card_exist ? ( + ((status_err->ge_cnt && !(status_err->ge_cnt % 1000)) || + (status_err->ecc_cnt && !(status_err->ecc_cnt % 1000)) || + (status_err->wp_cnt && !(status_err->wp_cnt % 100)) || + (status_err->oor_cnt && !(status_err->oor_cnt % 100))) + ? "YES" : "NO") : "NoCard"); + + return retval; +} + +static void sd_sec_sdcard_noti_work(struct work_struct *work) +{ + struct mmc_sd_sec_device_info *cdi; + struct mmc_sd_sec_status_err_info *status_err = &sdi.status_err; + int ret; + + cdi = container_of(work, struct mmc_sd_sec_device_info, noti_work); + if (!cdi->mmc->card) + return; + + status_err->noti_cnt++; + pr_info("%s: Send notification for SD card IO error. cnt(%d)\n", + mmc_hostname(cdi->mmc), status_err->noti_cnt); + + ret = kobject_uevent(&sec_sdcard_cmd_dev->kobj, KOBJ_CHANGE); + if (ret) + pr_err("%s: Failed to send uevent with err %d\n", __func__, ret); +} + +static struct device_type sdcard_type = { + .uevent = sd_sec_sdcard_uevent, +}; + +static void mmc_sd_sec_inc_status_err(struct mmc_card *card, u32 status) +{ + struct mmc_sd_sec_status_err_info *status_err = &sdi.status_err; + bool noti = false; + + if (status & R1_ERROR) { + status_err->ge_cnt++; + if (!(status_err->ge_cnt % 1000)) + noti = true; + } + if (status & R1_CC_ERROR) + status_err->cc_cnt++; + if (status & R1_CARD_ECC_FAILED) { + status_err->ecc_cnt++; + if (!(status_err->ecc_cnt % 1000)) + noti = true; + } + if (status & R1_WP_VIOLATION) { + status_err->wp_cnt++; + if (!(status_err->wp_cnt % 100)) + noti = true; + } + if (status & R1_OUT_OF_RANGE) { + status_err->oor_cnt++; + if (!(status_err->oor_cnt % 100)) + noti = true; + } + /* + * Make notification for SD card errors + * + * Condition : + * GE, ECC : Every 1000 error + * WP, OOR : Every 100 error + */ + if (noti && mmc_card_sd(card) && sec_sdcard_cmd_dev) + schedule_work(&sdi.noti_work); +} + +static void mmc_sd_sec_inc_err_count(int index, int error, u32 status) +{ + int i = 0; + int cpu = raw_smp_processor_id(); + struct mmc_sd_sec_err_info *err_log = &sdi.err_info[0]; + + if (!error) + return; + + /* + * Storage error count's policy handles only both EILSEQ and ETIMEDOUT. + * There is possible to detect minor error cases(e.g. ENOMEIDUM, EIO) + * In this case, it should be handled as -ETIMEDOUT error. + */ + if (error != -EILSEQ) + error = -ETIMEDOUT; + + for (i = 0; i < MAX_ERR_TYPE_INDEX; i++) { + if (err_log[index + i].err_type == error) { + index += i; + break; + } + } + + if (i >= MAX_ERR_TYPE_INDEX) + return; + + /* log device status and time if this is the first error */ + if (!err_log[index].status || !(R1_CURRENT_STATE(status) & R1_STATE_TRAN)) + err_log[index].status = status; + if (!err_log[index].first_issue_time) + err_log[index].first_issue_time = cpu_clock(cpu); + + err_log[index].last_issue_time = cpu_clock(cpu); + err_log[index].count++; +} + +#define MMC_BLK_TIMEOUT_MS (9 * 1000) +static bool mmc_sd_sec_check_busy_stuck(u32 status) +{ + if (time_before(jiffies, + sdi.tstamp_last_cmd + msecs_to_jiffies(MMC_BLK_TIMEOUT_MS))) + return false; + + if (status && (!(status & R1_READY_FOR_DATA) || + (R1_CURRENT_STATE(status) == R1_STATE_PRG))) + return true; + + return false; +} + +static void mmc_sd_sec_log_err_count(struct mmc_card *card, + struct mmc_request *mrq) +{ + u32 status = (mrq->sbc ? mrq->sbc->resp[0] : 0) | + (mrq->stop ? mrq->stop->resp[0] : 0) | + (mrq->cmd ? mrq->cmd->resp[0] : 0); + + if (status & STATUS_MASK) + mmc_sd_sec_inc_status_err(card, status); + + if (mrq->cmd->error) + mmc_sd_sec_inc_err_count(SD_CMD_OFFSET, + mrq->cmd->error, status); + if (mrq->sbc && mrq->sbc->error) + mmc_sd_sec_inc_err_count(SD_SBC_OFFSET, + mrq->sbc->error, status); + if (mrq->data && mrq->data->error) + mmc_sd_sec_inc_err_count(SD_DATA_OFFSET, + mrq->data->error, status); + if (mrq->stop && mrq->stop->error) + mmc_sd_sec_inc_err_count(SD_STOP_OFFSET, + mrq->stop->error, status); + + /* + * in block.c + * #define MMC_BLK_TIMEOUT_MS (10 * 1000) + * refer to card_busy_detect() + * so, check CMD13's response(status) + * if there is no other CMD for 9 secs or more. + */ + if (mrq->cmd->opcode != MMC_SEND_STATUS) + return; + + if (mmc_sd_sec_check_busy_stuck(status)) { + /* card stuck in prg state */ + mmc_sd_sec_inc_err_count(SD_BUSY_OFFSET, -ETIMEDOUT, status); + /* not to check card busy again */ + sdi.tstamp_last_cmd = jiffies; + } +} + +static void mmc_sd_sec_clear_err_count(void) +{ + struct mmc_sd_sec_err_info *err_log = &sdi.err_info[0]; + struct mmc_sd_sec_status_err_info *status_err = &sdi.status_err; + int i = 0; + + for (i = 0; i < MAX_LOG_INDEX; i++) { + err_log[i].status = 0; + err_log[i].first_issue_time = 0; + err_log[i].last_issue_time = 0; + err_log[i].count = 0; + } + + memset(status_err, 0, sizeof(struct mmc_sd_sec_status_err_info)); +} + +static void mmc_sd_sec_init_err_count(void) +{ + static const char *const req_types[] = { + "sbc ", "cmd ", "data ", "stop ", "busy " + }; + struct mmc_sd_sec_err_info *err_log = &sdi.err_info[0]; + int i; + + /* + * err_log[0].type = "sbc " + * err_log[0].err_type = -EILSEQ; + * err_log[1].type = "sbc " + * err_log[1].err_type = -ETIMEDOUT; + * ... + */ + for (i = 0; i < MAX_LOG_INDEX; i++) { + snprintf(err_log[i].type, sizeof(char) * 5, "%s", + req_types[i / MAX_ERR_TYPE_INDEX]); + + err_log[i].err_type = + (i % MAX_ERR_TYPE_INDEX == 0) ? -EILSEQ : -ETIMEDOUT; + } +} + +void sd_sec_card_event(struct mmc_host *host) +{ + bool status; + + if (!host) + return; + + status = mmc_gpio_get_cd(host) ? true : false; + + if (status ^ sdi.tray_status) { + pr_info("%s: slot status change detected (%d -> %d), GPIO_ACTIVE_%s\n", + mmc_hostname(host), sdi.tray_status, status, + (host->caps2 & MMC_CAP2_CD_ACTIVE_HIGH) ? + "HIGH" : "LOW"); + sdi.tray_status = status; + if (sdi.card_detect_cnt < UINT_MAX) + sdi.card_detect_cnt++; + + host->unused = 0; + mmc_sd_sec_clear_err_count(); + } +} + +static bool mmc_sd_sec_check_cmd_type(struct mmc_request *mrq) +{ + /* + * cmd->flags info + * MMC_CMD_AC (0b00 << 5) : Addressed commands + * MMC_CMD_ADTC (0b01 << 5) : Addressed data transfer commands + * MMC_CMD_BC (0b10 << 5) : Broadcast commands + * MMC_CMD_BCR (0b11 << 5) : Broadcast commands with response + * + * Log the errors only for AC or ADTC type + */ + if (!(mrq->cmd->flags & MMC_RSP_PRESENT)) + return false; + + if (mrq->cmd->flags & MMC_CMD_BC) + return false; + /* + * No need to check if MMC_RSP_136 set or cmd MMC_APP_CMD. + * CMD55 is sent with MMC_CMD_AC flag but no need to log. + */ + if ((mrq->cmd->flags & MMC_RSP_136) || + (mrq->cmd->opcode == MMC_APP_CMD)) + return false; + + return true; +} + +void mmc_sd_sec_check_req_err(struct mmc_host *host, struct mmc_request *mrq) +{ + if (!host->card || !mrq || !mrq->cmd) + return; + + /* return if the cmd is tuning block */ + if ((mrq->cmd->opcode == MMC_SEND_TUNING_BLOCK) || + (mrq->cmd->opcode == MMC_SEND_TUNING_BLOCK_HS200)) + return; + + /* set CMD(except CMD13) timestamp to check card stuck */ + if (mrq->cmd->opcode != MMC_SEND_STATUS) + sdi.tstamp_last_cmd = jiffies; + + if (mmc_sd_sec_check_cmd_type(mrq)) + mmc_sd_sec_log_err_count(host->card, mrq); +} + +void sd_sec_set_features(struct mmc_host *host, struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + + host->caps &= ~MMC_CAP_AGGRESSIVE_PM; + host->caps2 |= MMC_CAP2_NO_PRESCAN_POWERUP; + + sdi.tray_status = mmc_gpio_get_cd(host) ? true : false; + /* skip init without SD card detect irq */ + host->trigger_card_event = true; + + if (of_property_read_u32(np, "sec-sd-slot-type", &sdi.sd_slot_type)) { + if (mmc_gpio_get_cd(host) < 0) + sdi.sd_slot_type = SEC_NO_DET_SD_SLOT; + else + sdi.sd_slot_type = SEC_INVALID_SD_SLOT; + } + + sdi.card_detect_cnt = 0; + + sd_sec_init_sysfs(host); + mmc_sd_sec_init_err_count(); + + /* Register sd uevent . */ + sdi.mmc = host; + sec_sdcard_cmd_dev->type = &sdcard_type; + INIT_WORK(&sdi.noti_work, sd_sec_sdcard_noti_work); +} + diff --git a/drivers/mmc/host/mmc-sec-feature.h b/drivers/mmc/host/mmc-sec-feature.h new file mode 100644 index 000000000000..9436597d7bf0 --- /dev/null +++ b/drivers/mmc/host/mmc-sec-feature.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Samsung Specific feature + * + * Copyright (C) 2024 Samsung Electronics Co., Ltd. + * + * Authors: + * Storage Driver + */ + +#ifndef __MMC_SEC_FEATURE_H__ +#define __MMC_SEC_FEATURE_H__ + +#include "sdhci-pltfm.h" + +void sd_sec_set_features(struct mmc_host *host, struct platform_device *pdev); +void sd_sec_card_event(struct mmc_host *host); +void mmc_sd_sec_check_req_err(struct mmc_host *host, struct mmc_request *mrq); + +extern struct device *sec_sdcard_cmd_dev; +#endif diff --git a/drivers/mmc/host/mmc-sec-sysfs.c b/drivers/mmc/host/mmc-sec-sysfs.c new file mode 100644 index 000000000000..628c631128fe --- /dev/null +++ b/drivers/mmc/host/mmc-sec-sysfs.c @@ -0,0 +1,472 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Samsung Specific feature + * + * Copyright (C) 2024 Samsung Electronics Co., Ltd. + * + * Authors: + * Storage Driver + */ + +#include +#include +#include + +#include "mmc-sec-sysfs.h" +#include "../core/card.h" + +struct device *sec_sdcard_cmd_dev; +struct device *sec_sdinfo_cmd_dev; +struct device *sec_sddata_cmd_dev; + +static const char *const uhs_speeds[] = { + [UHS_SDR12_BUS_SPEED] = "SDR12", + [UHS_SDR25_BUS_SPEED] = "SDR25", + [UHS_SDR50_BUS_SPEED] = "SDR50", + [UHS_SDR104_BUS_SPEED] = "SDR104", + [UHS_DDR50_BUS_SPEED] = "DDR50", +}; + +static ssize_t sd_sec_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmc_host *host = dev_get_drvdata(dev); + + if (!mmc_gpio_get_cd(host)) { + if (sdi.sd_slot_type == SEC_HYBRID_SD_SLOT) { + pr_info("SD slot tray Removed.\n"); + return sysfs_emit(buf, "Notray\n"); + } + } + + if (host->card) { + pr_info("SD card inserted.\n"); + return sysfs_emit(buf, "Insert\n"); + } + + pr_info("SD card removed.\n"); + return sysfs_emit(buf, "Remove\n"); +} + +static ssize_t sd_sec_maxmode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmc_host *host = dev_get_drvdata(dev); + const char *bus_speed_mode = ""; + + if (host->caps & MMC_CAP_UHS_SDR104) + bus_speed_mode = "SDR104"; + else if (host->caps & MMC_CAP_UHS_DDR50) + bus_speed_mode = "DDR50"; + else if (host->caps & MMC_CAP_UHS_SDR50) + bus_speed_mode = "SDR50"; + else if (host->caps & MMC_CAP_UHS_SDR25) + bus_speed_mode = "SDR25"; + else if (host->caps & MMC_CAP_UHS_SDR12) + bus_speed_mode = "SDR12"; + else + bus_speed_mode = "HS"; + + dev_info(dev, "%s: Max supported Host Speed Mode = %s\n", + __func__, bus_speed_mode); + return sysfs_emit(buf, "%s\n", bus_speed_mode); +} + +static inline void sd_sec_get_size(struct mmc_card *card, char *buf, int len) +{ + static const char *const unit[] = {"KB", "MB", "GB", "TB"}; + int capacity; + int digit = 1; + + if (card->csd.read_blkbits == 9) /* 1 Sector = 512 Bytes */ + capacity = (card->csd.capacity) >> 1; + else if (card->csd.read_blkbits == 11) /* 1 Sector = 2048 Bytes */ + capacity = (card->csd.capacity) << 1; + else /* 1 Sector = 1024 Bytes */ + capacity = card->csd.capacity; + + if (capacity >= 380000000 && capacity <= 410000000) + snprintf(buf, len, "400GB"); + else if (capacity >= 190000000 && capacity <= 210000000) + snprintf(buf, len, "200GB"); + else { + while ((capacity >> 1) > 0) { + capacity >>= 1; + digit++; + } + snprintf(buf, len, "%d%s", 1 << (digit % 10), unit[digit / 10]); + } +} + +static inline void sd_sec_get_speedmode(struct mmc_card *card, + const char **speedmode) +{ + if (mmc_card_uhs(card)) + *speedmode = uhs_speeds[card->sd_bus_speed]; + else if (mmc_card_hs(card)) + *speedmode = "HS"; + else + *speedmode = "DS"; +} + +static ssize_t sd_sec_curmode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmc_host *host = dev_get_drvdata(dev); + const char *bus_speed_mode = "No Card"; + + if (host && host->card) + sd_sec_get_speedmode(host->card, &bus_speed_mode); + + dev_info(dev, "%s: Current SD Card Speed = %s\n", + __func__, bus_speed_mode); + return sysfs_emit(buf, "%s\n", bus_speed_mode); +} + +static ssize_t sd_sec_detect_cnt_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + dev_info(dev, "%s : CD count is = %u\n", + __func__, sdi.card_detect_cnt); + return sysfs_emit(buf, "%u\n", sdi.card_detect_cnt); +} + +static ssize_t sd_sec_summary_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmc_host *host = dev_get_drvdata(dev); + struct mmc_card *card; + struct mmc_sd_sec_status_err_info *status_err = &sdi.status_err; + unsigned int serial; + char size[6]; + const char *bus_speed_mode = ""; + + if (host && host->card) { + card = host->card; + serial = card->cid.serial & (0x0000FFFF); + sd_sec_get_size(card, size, sizeof(size)); + sd_sec_get_speedmode(card, &bus_speed_mode); + dev_info(dev, "MANID: 0x%02X, SERIAL: %04X, SIZE: %s, SPEEDMODE: %s\n", + card->cid.manfid, serial, size, bus_speed_mode); + return sysfs_emit(buf, "\"MANID\":\"0x%02X\",\"SERIAL\":\"%04X\""\ + ",\"SIZE\":\"%s\",\"SPEEDMODE\":\"%s\",\"NOTI\":\"%d\"\n", + card->cid.manfid, serial, size, bus_speed_mode, + status_err->noti_cnt); + } else { + dev_info(dev, "%s : No SD Card\n", __func__); + return sysfs_emit(buf, "\"MANID\":\"NoCard\",\"SERIAL\":\"NoCard\""\ + ",\"SIZE\":\"NoCard\",\"SPEEDMODE\":\"NoCard\""\ + ",\"NOTI\":\"NoCard\"\n"); + } +} + +static inline void sd_sec_calc_error_count(struct mmc_sd_sec_err_info *err_log, + unsigned long long *crc_cnt, unsigned long long *tmo_cnt) +{ + int i = 0; + + /* Only sbc(0,1)/cmd(2,3)/data(4,5) is checked. */ + for (i = 0; i < 6; i++) { + if (err_log[i].err_type == -EILSEQ && *crc_cnt < U64_MAX) + *crc_cnt += err_log[i].count; + if (err_log[i].err_type == -ETIMEDOUT && *tmo_cnt < U64_MAX) + *tmo_cnt += err_log[i].count; + } +} + +static ssize_t mmc_sd_sec_error_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmc_host *host = dev_get_drvdata(dev); + struct mmc_card *card = host->card; + struct mmc_sd_sec_err_info *err_log; + struct mmc_sd_sec_status_err_info *status_err; + u64 crc_cnt = 0; + u64 tmo_cnt = 0; + int len = 0; + int i; + + if (!card) { + len = sysfs_emit(buf, "No card\n"); + goto out; + } + + err_log = &sdi.err_info[0]; + status_err = &sdi.status_err; + + len += snprintf(buf, PAGE_SIZE, + "type : err status: first_issue_time: last_issue_time: count\n"); + + for (i = 0; i < MAX_LOG_INDEX; i++) { + len += snprintf(buf + len, PAGE_SIZE - len, + "%5s:%4d 0x%08x %16llu, %16llu, %10d\n", + err_log[i].type, err_log[i].err_type, + err_log[i].status, + err_log[i].first_issue_time, + err_log[i].last_issue_time, + err_log[i].count); + } + + sd_sec_calc_error_count(err_log, &crc_cnt, &tmo_cnt); + + len += snprintf(buf + len, PAGE_SIZE - len, + "GE:%d,CC:%d,ECC:%d,WP:%d,OOR:%d,CRC:%lld,TMO:%lld\n", + status_err->ge_cnt, status_err->cc_cnt, + status_err->ecc_cnt, status_err->wp_cnt, + status_err->oor_cnt, crc_cnt, tmo_cnt); +out: + return len; +} + +static DEVICE_ATTR(status, 0444, sd_sec_status_show, NULL); +static DEVICE_ATTR(max_mode, 0444, sd_sec_maxmode_show, NULL); +static DEVICE_ATTR(current_mode, 0444, sd_sec_curmode_show, NULL); +static DEVICE_ATTR(cd_cnt, 0444, sd_sec_detect_cnt_show, NULL); +static DEVICE_ATTR(sdcard_summary, 0444, sd_sec_summary_show, NULL); +static DEVICE_ATTR(err_count, 0444, mmc_sd_sec_error_count_show, NULL); + +static ssize_t sd_sec_cid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmc_host *host = dev_get_drvdata(dev); + struct mmc_card *card = host->card; + + if (!card) + return sysfs_emit(buf, "no card\n"); + + return sysfs_emit(buf, "%08x%08x%08x%08x\n", + card->raw_cid[0], card->raw_cid[1], + card->raw_cid[2], card->raw_cid[3]); +} + +static ssize_t sd_sec_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmc_host *host = dev_get_drvdata(dev); + struct mmc_card *card = host->card; + struct mmc_sd_sec_err_info *err_log; + u64 total_cnt = 0; + int i; + + if (!card) + return sysfs_emit(buf, "no card\n"); + + err_log = &sdi.err_info[0]; + + for (i = 0; i < 6; i++) { + if (total_cnt < U64_MAX) + total_cnt += err_log[i].count; + } + return sysfs_emit(buf, "%lld\n", total_cnt); +} + +static inline bool is_bad_condition(struct mmc_sd_sec_status_err_info *status_err, + u64 crc_cnt, u64 tmo_cnt) +{ + return (status_err->ge_cnt > 100 || status_err->ecc_cnt > 0 || + status_err->wp_cnt > 0 || status_err->oor_cnt > 10 || + crc_cnt > 100 || tmo_cnt > 100); +} + +static ssize_t sd_sec_health_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmc_host *host = dev_get_drvdata(dev); + struct mmc_card *card = host->card; + struct mmc_sd_sec_err_info *err_log = &sdi.err_info[0]; + struct mmc_sd_sec_status_err_info *status_err = &sdi.status_err; + u64 crc_cnt = 0; + u64 tmo_cnt = 0; + + /* There should be no spaces in 'No Card'(Vold Team). */ + if (!card) + return sysfs_emit(buf, "NOCARD\n"); + + sd_sec_calc_error_count(err_log, &crc_cnt, &tmo_cnt); + + if (is_bad_condition(status_err, crc_cnt, tmo_cnt)) + return sysfs_emit(buf, "BAD\n"); + + return sysfs_emit(buf, "GOOD\n"); +} + +static ssize_t sd_sec_reason_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmc_host *host = dev_get_drvdata(dev); + struct mmc_card *card = host->card; + char *reason = NULL; + + if (card) + reason = mmc_card_readonly(card) ? "PERMWP" : "NORMAL"; + else + reason = host->unused ? "INITFAIL" : "NOCARD"; + + return sysfs_emit(buf, "%s\n", reason); +} + +static DEVICE_ATTR(data, 0444, sd_sec_cid_show, NULL); +static DEVICE_ATTR(sd_count, 0444, sd_sec_count_show, NULL); +static DEVICE_ATTR(fc, 0444, sd_sec_health_show, NULL); +static DEVICE_ATTR(reason, 0444, sd_sec_reason_show, NULL); + +#define MMC_SD_SEC_CALC_STATUS_ERR(member) ({ \ + cur_status_err->member = status_err->member - saved_status_err->member; }) + +static inline void mmc_sd_sec_get_curr_err_info(struct mmc_sd_sec_device_info *cdi, + struct mmc_card *card, unsigned long long *crc_cnt, + unsigned long long *tmo_cnt, struct mmc_sd_sec_status_err_info *cur_status_err) +{ + struct mmc_sd_sec_err_info *err_log = &cdi->err_info[0]; + struct mmc_sd_sec_err_info *saved_err_log = &cdi->saved_err_info[0]; + struct mmc_sd_sec_status_err_info *status_err = &cdi->status_err; + struct mmc_sd_sec_status_err_info *saved_status_err = &cdi->saved_status_err; + int i; + + /* Only sbc(0,1)/cmd(2,3)/data(4,5) is checked. */ + for (i = 0; i < 6; i++) { + if (err_log[i].err_type == -EILSEQ && *crc_cnt < U64_MAX) + *crc_cnt += (err_log[i].count - saved_err_log[i].count); + if (err_log[i].err_type == -ETIMEDOUT && *tmo_cnt < U64_MAX) + *tmo_cnt += (err_log[i].count - saved_err_log[i].count); + } + + MMC_SD_SEC_CALC_STATUS_ERR(ge_cnt); + MMC_SD_SEC_CALC_STATUS_ERR(cc_cnt); + MMC_SD_SEC_CALC_STATUS_ERR(ecc_cnt); + MMC_SD_SEC_CALC_STATUS_ERR(wp_cnt); + MMC_SD_SEC_CALC_STATUS_ERR(oor_cnt); + MMC_SD_SEC_CALC_STATUS_ERR(noti_cnt); +} + +#define MMC_SD_SEC_SAVE_STATUS_ERR(member) ({ \ + saved_status_err->member = status_err->member; }) + +static inline void mmc_sd_sec_save_err_info(struct mmc_sd_sec_device_info *cdi) +{ + struct mmc_sd_sec_err_info *err_log = &cdi->err_info[0]; + struct mmc_sd_sec_err_info *saved_err_log = &cdi->saved_err_info[0]; + struct mmc_sd_sec_status_err_info *status_err = &cdi->status_err; + struct mmc_sd_sec_status_err_info *saved_status_err = &cdi->saved_status_err; + int i; + + /* Save current error count */ + for (i = 0; i < MAX_LOG_INDEX; i++) + saved_err_log[i].count = err_log[i].count; + + MMC_SD_SEC_SAVE_STATUS_ERR(ge_cnt); + MMC_SD_SEC_SAVE_STATUS_ERR(cc_cnt); + MMC_SD_SEC_SAVE_STATUS_ERR(ecc_cnt); + MMC_SD_SEC_SAVE_STATUS_ERR(wp_cnt); + MMC_SD_SEC_SAVE_STATUS_ERR(oor_cnt); + MMC_SD_SEC_SAVE_STATUS_ERR(noti_cnt); +} + +static ssize_t sd_sec_data_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmc_host *host = dev_get_drvdata(dev); + struct mmc_card *card = host->card; + struct mmc_sd_sec_status_err_info status_err; + u64 crc_cnt = 0; + u64 tmo_cnt = 0; + + if (!card) + return sysfs_emit(buf, + "\"GE\":\"0\",\"CC\":\"0\",\"ECC\":\"0\",\"WP\":\"0\"," \ + "\"OOR\":\"0\",\"CRC\":\"0\",\"TMO\":\"0\"\n"); + + memset(&status_err, 0, sizeof(struct mmc_sd_sec_status_err_info)); + + mmc_sd_sec_get_curr_err_info(&sdi, card, &crc_cnt, &tmo_cnt, &status_err); + + return sysfs_emit(buf, + "\"GE\":\"%d\",\"CC\":\"%d\",\"ECC\":\"%d\",\"WP\":\"%d\"," \ + "\"OOR\":\"%d\",\"CRC\":\"%lld\",\"TMO\":\"%lld\"\n", + status_err.ge_cnt, + status_err.cc_cnt, + status_err.ecc_cnt, + status_err.wp_cnt, + status_err.oor_cnt, + crc_cnt, tmo_cnt); +} + +static ssize_t sd_sec_data_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct mmc_host *host = dev_get_drvdata(dev); + struct mmc_card *card = host->card; + + if (!card) + return -ENODEV; + + if ((buf[0] != 'C' && buf[0] != 'c') || (count != 1)) + return -EINVAL; + + mmc_sd_sec_save_err_info(&sdi); + + return count; +} + +static DEVICE_ATTR(sd_data, 0664, sd_sec_data_show, sd_sec_data_store); + +static struct attribute *sdcard_attributes[] = { + &dev_attr_status.attr, + &dev_attr_max_mode.attr, + &dev_attr_current_mode.attr, + &dev_attr_cd_cnt.attr, + &dev_attr_sdcard_summary.attr, + &dev_attr_err_count.attr, + NULL, +}; + +static struct attribute_group sdcard_attr_group = { + .attrs = sdcard_attributes, +}; + +static struct attribute *sdinfo_attributes[] = { + &dev_attr_data.attr, + &dev_attr_sd_count.attr, + &dev_attr_fc.attr, + &dev_attr_reason.attr, + NULL, +}; + +static struct attribute_group sdinfo_attr_group = { + .attrs = sdinfo_attributes, +}; + +static struct attribute *sddata_attributes[] = { + &dev_attr_sd_data.attr, + NULL, +}; + +static struct attribute_group sddata_attr_group = { + .attrs = sddata_attributes, +}; + +void sd_sec_create_sysfs_group(struct mmc_host *host, struct device **dev, + const struct attribute_group *dev_attr_group, + const char *group_name) +{ + *dev = sec_device_create(host, group_name); + if (IS_ERR(*dev)) { + pr_err("%s: Failed to create device for %s!\n", + __func__, group_name); + return; + } + if (sysfs_create_group(&(*dev)->kobj, dev_attr_group)) + pr_err("%s: Failed to create %s sysfs group\n", + __func__, group_name); +} + +void sd_sec_init_sysfs(struct mmc_host *host) +{ + sd_sec_create_sysfs_group(host, &sec_sdcard_cmd_dev, + &sdcard_attr_group, "sdcard"); + sd_sec_create_sysfs_group(host, &sec_sdinfo_cmd_dev, + &sdinfo_attr_group, "sdinfo"); + sd_sec_create_sysfs_group(host, &sec_sddata_cmd_dev, + &sddata_attr_group, "sddata"); +} + diff --git a/drivers/mmc/host/mmc-sec-sysfs.h b/drivers/mmc/host/mmc-sec-sysfs.h new file mode 100644 index 000000000000..81302c220a56 --- /dev/null +++ b/drivers/mmc/host/mmc-sec-sysfs.h @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Samsung Specific feature + * + * Copyright (C) 2024 Samsung Electronics Co., Ltd. + * + * Authors: + * Storage Driver + */ +#ifndef __MMC_SEC_SYSFS_H__ +#define __MMC_SEC_SYSFS_H__ + +#include + +#include "mmc-sec-feature.h" + +#define MAX_REQ_TYPE_INDEX 5 // sbc, cmd, data, stop, busy +#define MAX_ERR_TYPE_INDEX 2 // crc, timeout +#define MAX_LOG_INDEX (MAX_REQ_TYPE_INDEX * MAX_ERR_TYPE_INDEX) + +#define SD_SBC_OFFSET 0 +#define SD_CMD_OFFSET 2 +#define SD_DATA_OFFSET 4 +#define SD_STOP_OFFSET 6 +#define SD_BUSY_OFFSET 8 + +#define STATUS_MASK (R1_ERROR | R1_CC_ERROR | R1_CARD_ECC_FAILED | \ + R1_WP_VIOLATION | R1_OUT_OF_RANGE) + +enum { + SEC_INVALID_SD_SLOT = 0, + SEC_NO_DET_SD_SLOT, + SEC_HOTPLUG_SD_SLOT, + SEC_HYBRID_SD_SLOT, +}; + +struct mmc_sd_sec_err_info { + char type[MAX_REQ_TYPE_INDEX]; // sbc, cmd, data, stop, busy + int err_type; + u32 status; + u64 first_issue_time; + u64 last_issue_time; + u32 count; +}; + +struct mmc_sd_sec_status_err_info { + u32 ge_cnt; // status[19] : general error or unknown error + u32 cc_cnt; // status[20] : internal card controller error + u32 ecc_cnt; // status[21] : ecc error + u32 wp_cnt; // status[26] : write protection error + u32 oor_cnt; // status[31] : out of range error + u32 noti_cnt; // uevent notification count +}; + +struct mmc_sd_sec_device_info { + struct mmc_host *mmc; + unsigned int card_detect_cnt; + int sd_slot_type; + bool tray_status; + unsigned long tstamp_last_cmd; + struct work_struct noti_work; + struct mmc_sd_sec_err_info err_info[MAX_LOG_INDEX]; + struct mmc_sd_sec_status_err_info status_err; + struct mmc_sd_sec_err_info saved_err_info[MAX_LOG_INDEX]; + struct mmc_sd_sec_status_err_info saved_status_err; +}; + +void sd_sec_init_sysfs(struct mmc_host *host); + +extern struct mmc_sd_sec_device_info sdi; + +#endif diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 997878411b55..0c68ffa0dfbd 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -47,6 +47,10 @@ #endif #include "sdhci-msm.h" +#if IS_ENABLED(CONFIG_SEC_MMC_FEATURE) +#include "mmc-sec-feature.h" +#endif + #define CORE_MCI_VERSION 0x50 #define CORE_VERSION_MAJOR_SHIFT 28 #define CORE_VERSION_MAJOR_MASK (0xf << CORE_VERSION_MAJOR_SHIFT) @@ -4096,6 +4100,24 @@ static void sdhci_msm_hw_reset(struct sdhci_host *host) #endif } +#if IS_ENABLED(CONFIG_SEC_MMC_FEATURE) +static void sdhci_msm_sec_card_event(struct sdhci_host *host) +{ + sd_sec_card_event(host->mmc); +} + +void sdhci_msm_sec_request_done(struct sdhci_host *host, + struct mmc_request *mrq) +{ + struct mmc_host *mmc = host->mmc; + + mmc_sd_sec_check_req_err(mmc, mrq); + + /* call mmc_request_done() to finish processing an MMC request */ + mmc_request_done(mmc, mrq); +} +#endif + static const struct sdhci_ops sdhci_msm_ops = { .reset = sdhci_msm_reset, .set_clock = sdhci_msm_set_clock, @@ -4111,6 +4133,10 @@ static const struct sdhci_ops sdhci_msm_ops = { .set_power = sdhci_set_power_noreg, .hw_reset = sdhci_msm_hw_reset, .set_timeout = sdhci_msm_set_timeout, +#if IS_ENABLED(CONFIG_SEC_MMC_FEATURE) + .card_event = sdhci_msm_sec_card_event, + .request_done = sdhci_msm_sec_request_done, +#endif }; #if IS_ENABLED(CONFIG_MMC_SDHCI_MSM_SCALING) @@ -5579,6 +5605,10 @@ static int sdhci_msm_probe(struct platform_device *pdev) /* Initialize sysfs entries */ sdhci_msm_init_sysfs_gating_qos(dev); +#if IS_ENABLED(CONFIG_SEC_MMC_FEATURE) + sd_sec_set_features(host->mmc, pdev); +#endif + if (of_property_read_bool(node, "supports-cqe")) ret = sdhci_msm_cqe_add_host(host, pdev); else diff --git a/drivers/muic/common/Kconfig b/drivers/muic/common/Kconfig new file mode 100644 index 000000000000..9a251887a363 --- /dev/null +++ b/drivers/muic/common/Kconfig @@ -0,0 +1,148 @@ +# +# MUIC devices +# + +comment "MUIC configs" + +config USE_MUIC + bool "Using MUIC device driver" + depends on I2C + default n + help + If you say yes here you will get support for + the MUIC device driver. + +config MUIC_NOTIFIER + tristate "MUIC notifier support" + depends on USE_MUIC + default n + help + If you say yes here you will get support for + the MUIC attached device status change notification. + +config MUIC_HV + bool "MUIC_HV" + depends on USE_MUIC + default n + help + If you say yes here you will get support for the AFC. + +config MUIC_SUPPORT_PDIC + tristate "MUIC supports CCIC chip interface" + depends on USE_MUIC + default n + help + If you say yes here you will get support for the CCIC chip. + +config HICCUP_CHARGER + bool "Using HICCUP charger" + depends on USE_MUIC + default n + help + If you say yes here you will get support for + for the hiccup charger feature. + +config USE_SECOND_MUIC + bool "Using second MUIC" + depends on USE_MUIC + default n + help + If you say yes here you will get support for + for the second MUIC chip feature. + +config MUIC_UART_SWITCH + bool "UART SWITCH" + depends on USE_MUIC + default n + help + If you say yes here you will get support for the SEC UART SWITCH chip. + It needs additional uart select H/W and applied only to lsi. + +config MUIC_SUPPORT_UART_SEL + bool "MUIC supports uart selection" + depends on USE_MUIC + default n + help + If you say yes here you will get support for the uart selection. + It applied only to qc. + +config USE_DEDICATED_MUIC + bool "Using dedicated MUIC device driver" + depends on USE_MUIC + default n + help + If you say yes here you will get support for + Dedicated MUIC device driver. + This is only for charge + +config MUIC_AFC_RETRY + bool "Using MUIC AFC retry" + depends on USE_MUIC + default n + help + If you say yes here you will get support for MUIC AFC retry feature. + +config MUIC_COMMON_SYSFS + bool "Using muic sysfs Features" + depends on MUIC_NOTIFIER + default n + help + If you say yes here you will get support for the muic sysfs. + some ic drivers may have sysfs in own driver. + But we recommend driver to use this common sysfs. + new ic driver must use this feature. + +config MUIC_USE_MODULE_PARAM + bool "Using module param" + depends on MUIC_NOTIFIER + default n + help + If this feature is enabled, muic_core will use module_param directly. + Should not use extern variable and __setup for param. + need to add cmdline in bootloader. + (ex: common_muic.xxx) + +config HICCUP_CC_DISABLE + bool "Using HICCUP CC disable" + depends on USE_MUIC + default n + help + If you say yes here you will get support for + for the hiccup cc disable feature. + +config MUIC_POGO + bool "Using muic pogo" + depends on USE_MUIC + default n + help + If you say yes here you will get support for muic pogo + +config MUIC_LO_TA_LOW_CURRENT + bool "Using LO_TA_LOW_CURRENT" + depends on USE_MUIC + default n + help + If you say yes here you will get support for + for the hiccup cc disable feature. + +config MUIC_CORE_TEST_FOR_ON_DEVICE + tristate "KUnit test for muic_core_test" + depends on KUNIT + depends on MUIC_NOTIFIER + help + This is feature for muic-core.c + If you run this test driver on device, + SHOULD set this config as 'm' to build test driver modularly. + refer to muic_common.py file. + +config MUIC_CORE_TEST_FOR_ONLY_UML + tristate "KUnit test for muic_core_test" + depends on KUNIT + depends on UML + depends on MUIC_NOTIFIER + help + This is feature for muic-core.c + This CONFIG is recommended to set to y. + If you run UML test, this feature must be enabled. + refer to muic_common.py file. + diff --git a/drivers/muic/common/Makefile b/drivers/muic/common/Makefile new file mode 100644 index 000000000000..efb4588475d2 --- /dev/null +++ b/drivers/muic/common/Makefile @@ -0,0 +1,12 @@ +# +# Makefile for muic devices +# +subdir-ccflags-y := -Wformat + +obj-$(CONFIG_MUIC_NOTIFIER) += common_muic.o +common_muic-y := muic-core.o muic_notifier.o muic_param.o +common_muic-$(CONFIG_MUIC_COMMON_SYSFS) += muic_sysfs.o + +ifeq ($(CONFIG_SEC_KUNIT), y) +GCOV_PROFILE_muic-core.o := $(CONFIG_SEC_KUNIT) +endif diff --git a/drivers/muic/common/muic-core.c b/drivers/muic/common/muic-core.c new file mode 100644 index 000000000000..ddcdd602eaa6 --- /dev/null +++ b/drivers/muic/common/muic-core.c @@ -0,0 +1,696 @@ +/* drivers/muic/muic-core.c + * + * Copyright (c) 2011-2023 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * 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. + */ +#include +#include + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) +#include +#endif + +#if IS_ENABLED(CONFIG_ANDROID_SWITCH) || IS_ENABLED(CONFIG_SWITCH) +#include +#endif + +#if defined(CONFIG_USB_HW_PARAM) +#include +#endif + +#include +#include +#include + +static void muic_init_switch_dev_cb(void); +static void muic_cleanup_switch_dev_cb(void); +#if IS_MODULE(CONFIG_MUIC_NOTIFIER) +static int muic_init_gpio_cb(int switch_sel); +#else +static int muic_init_gpio_cb(void); +#endif + +struct muic_platform_data muic_pdata = { + .init_switch_dev_cb = muic_init_switch_dev_cb, + .cleanup_switch_dev_cb = muic_cleanup_switch_dev_cb, + .init_gpio_cb = muic_init_gpio_cb, +}; +EXPORT_SYMBOL(muic_pdata); + +#if IS_ENABLED(CONFIG_USE_SECOND_MUIC) +struct muic_platform_data muic_pdata2; +#endif + +#if IS_ENABLED(CONFIG_ANDROID_SWITCH) || IS_ENABLED(CONFIG_SWITCH) +static struct switch_dev switch_dock = { + .name = "dock", +}; + +struct switch_dev switch_uart3 = { + .name = "uart3", /* sys/class/switch/uart3/state */ +}; +#endif + +static struct notifier_block dock_notifier_block; +static struct notifier_block cable_data_notifier_block; + +static int muic_device_uevent(struct muic_platform_data *muic_pdata, + char *envp_ext[]) +{ + int ret = 0; + + if (muic_pdata && muic_pdata->muic_device) + kobject_uevent_env(&muic_pdata->muic_device->kobj, + KOBJ_CHANGE, envp_ext); + else { + ret = -ENOENT; + pr_err("%s: muic_pdata or muic_device is NULL\n", __func__); + goto err; + } + pr_info("%s\n", __func__); +err: + return ret; +} + +void muic_send_lcd_on_uevent(struct muic_platform_data *muic_pdata) +{ + char *envp[2]; + char *type = {"TYPE=LCD_ON"}; + int index = 0; + + envp[index++] = type; + envp[index++] = NULL; + + if (muic_device_uevent(muic_pdata, envp)) { + pr_err("%s: muic_device_uevent has error\n", __func__); + goto err; + } + pr_info("%s\n", __func__); +err: + return; +} +EXPORT_SYMBOL(muic_send_lcd_on_uevent); + +void muic_send_dock_intent(int type) +{ + pr_info("%s: MUIC dock type(%d)\n", __func__, type); +#if IS_ENABLED(CONFIG_ANDROID_SWITCH) || IS_ENABLED(CONFIG_SWITCH) + switch_set_state(&switch_dock, type); +#endif +} +EXPORT_SYMBOL(muic_send_dock_intent); + +static int muic_dock_attach_notify(int type, const char *name) +{ + pr_info("%s: %s\n", __func__, name); + muic_send_dock_intent(type); + + return NOTIFY_OK; +} + +static int muic_dock_detach_notify(void) +{ + pr_info("%s\n", __func__); + muic_send_dock_intent(MUIC_DOCK_DETACHED); + + return NOTIFY_OK; +} + +static muic_attached_dev_t muic_get_attached_dev(void *data) +{ +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) && IS_ENABLED(CONFIG_MUIC_SUPPORT_PDIC) + PD_NOTI_ATTACH_TYPEDEF *pnoti = (PD_NOTI_ATTACH_TYPEDEF *)data; + muic_attached_dev_t attached_dev = pnoti->cable_type; +#else + muic_attached_dev_t attached_dev = *(muic_attached_dev_t *)data; +#endif + + return attached_dev; +} + +static int muic_handle_dock_notification(struct notifier_block *nb, + unsigned long action, void *data) +{ + muic_attached_dev_t attached_dev = ATTACHED_DEV_NONE_MUIC; + int type = MUIC_DOCK_DETACHED; + const char *name; + + if (!data) { + pr_err("%s. data is null\n", __func__); + goto ignore; + } + + attached_dev = muic_get_attached_dev(data); + + switch (attached_dev) { + case ATTACHED_DEV_DESKDOCK_MUIC: + case ATTACHED_DEV_DESKDOCK_VB_MUIC: + if (action == MUIC_NOTIFY_CMD_ATTACH) { + type = MUIC_DOCK_DESKDOCK; + name = "Desk Dock Attach"; + return muic_dock_attach_notify(type, name); + } + else if (action == MUIC_NOTIFY_CMD_DETACH) + return muic_dock_detach_notify(); + break; + case ATTACHED_DEV_CARDOCK_MUIC: + if (action == MUIC_NOTIFY_CMD_ATTACH) { + type = MUIC_DOCK_CARDOCK; + name = "Car Dock Attach"; + return muic_dock_attach_notify(type, name); + } + else if (action == MUIC_NOTIFY_CMD_DETACH) + return muic_dock_detach_notify(); + break; + case ATTACHED_DEV_SMARTDOCK_MUIC: + case ATTACHED_DEV_SMARTDOCK_VB_MUIC: + case ATTACHED_DEV_SMARTDOCK_TA_MUIC: + case ATTACHED_DEV_SMARTDOCK_USB_MUIC: + if (action == MUIC_NOTIFY_CMD_LOGICALLY_ATTACH) { + type = MUIC_DOCK_SMARTDOCK; + name = "Smart Dock Attach"; + return muic_dock_attach_notify(type, name); + } + else if (action == MUIC_NOTIFY_CMD_LOGICALLY_DETACH) + return muic_dock_detach_notify(); + break; + case ATTACHED_DEV_UNIVERSAL_MMDOCK_MUIC: + if (action == MUIC_NOTIFY_CMD_ATTACH) { + type = MUIC_DOCK_SMARTDOCK; + name = "Universal MMDock Attach"; + return muic_dock_attach_notify(type, name); + } + else if (action == MUIC_NOTIFY_CMD_DETACH) + return muic_dock_detach_notify(); + break; + case ATTACHED_DEV_AUDIODOCK_MUIC: + if (action == MUIC_NOTIFY_CMD_ATTACH) { + type = MUIC_DOCK_AUDIODOCK; + name = "Audio Dock Attach"; + return muic_dock_attach_notify(type, name); + } + else if (action == MUIC_NOTIFY_CMD_DETACH) + return muic_dock_detach_notify(); + break; + case ATTACHED_DEV_HMT_MUIC: + if (action == MUIC_NOTIFY_CMD_ATTACH) { + type = MUIC_DOCK_HMT; + name = "HMT Attach"; + return muic_dock_attach_notify(type, name); + } + else if (action == MUIC_NOTIFY_CMD_DETACH) + return muic_dock_detach_notify(); + break; + case ATTACHED_DEV_GAMEPAD_MUIC: + if (action == MUIC_NOTIFY_CMD_ATTACH) { + type = MUIC_DOCK_GAMEPAD; + name = "Gamepad Attach"; + return muic_dock_attach_notify(type, name); + } else if (action == MUIC_NOTIFY_CMD_DETACH) + return muic_dock_detach_notify(); + break; + default: + break; + } +ignore: + pr_info("%s: ignore(%d)\n", __func__, attached_dev); + return NOTIFY_DONE; +} + +static int muic_handle_cable_data_notification(struct notifier_block *nb, + unsigned long action, void *data) +{ +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif + muic_attached_dev_t attached_dev; + int jig_state = 0; + + if (!data) { + pr_err("%s. data is null\n", __func__); + goto ignore; + } + + attached_dev = muic_get_attached_dev(data); + + switch (attached_dev) { + case ATTACHED_DEV_JIG_UART_OFF_MUIC: + case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC: /* VBUS enabled */ + case ATTACHED_DEV_JIG_UART_OFF_VB_OTG_MUIC: /* for otg test */ + case ATTACHED_DEV_JIG_UART_OFF_VB_FG_MUIC: /* for fg test */ + case ATTACHED_DEV_JIG_UART_ON_MUIC: + case ATTACHED_DEV_JIG_UART_ON_VB_MUIC: /* VBUS enabled */ + case ATTACHED_DEV_JIG_USB_OFF_MUIC: + case ATTACHED_DEV_JIG_USB_ON_MUIC: + if (action == MUIC_NOTIFY_CMD_ATTACH) + jig_state = 1; + break; + case ATTACHED_DEV_TIMEOUT_OPEN_MUIC: +#if defined(CONFIG_USB_HW_PARAM) + if (action == MUIC_NOTIFY_CMD_ATTACH && o_notify) + inc_hw_param(o_notify, USB_MUIC_DCD_TIMEOUT_COUNT); +#endif + break; + default: + jig_state = 0; + break; + } + + pr_info("%s: MUIC uart type(%d)\n", __func__, jig_state); +#if IS_ENABLED(CONFIG_ANDROID_SWITCH) || IS_ENABLED(CONFIG_SWITCH) + switch_set_state(&switch_uart3, jig_state); +#endif + return NOTIFY_OK; +ignore: + return NOTIFY_DONE; +} + +static void muic_init_switch_dev_cb(void) +{ +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + struct muic_platform_data *pdata = &muic_pdata; +#endif +#if IS_ENABLED(CONFIG_ANDROID_SWITCH) || IS_ENABLED(CONFIG_SWITCH) + int ret; + + /* for DockObserver */ + ret = switch_dev_register(&switch_dock); + if (ret < 0) { + pr_err("%s: Failed to register dock switch(%d)\n", + __func__, ret); + return; + } + + /* for UART event */ + ret = switch_dev_register(&switch_uart3); + if (ret < 0) { + pr_err("%s: Failed to register uart3 switch(%d)\n", + __func__, ret); + return; + } +#endif /* CONFIG_ANDROID_SWITCH || CONFIG_SWITCH */ + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + pdata->muic_device = sec_device_create(NULL, "muic"); + if (IS_ERR(pdata->muic_device)) + pr_err("%s: Failed to create device(muic)!\n", __func__); +#endif + + muic_notifier_register(&dock_notifier_block, + muic_handle_dock_notification, MUIC_NOTIFY_DEV_DOCK); + muic_notifier_register(&cable_data_notifier_block, + muic_handle_cable_data_notification, MUIC_NOTIFY_DEV_CABLE_DATA); + + pr_info("%s: done\n", __func__); +} + +static void muic_cleanup_switch_dev_cb(void) +{ +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + struct muic_platform_data *pdata = &muic_pdata; +#endif + + muic_notifier_unregister(&cable_data_notifier_block); + muic_notifier_unregister(&dock_notifier_block); + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + if (pdata->muic_device) + sec_device_destroy(pdata->muic_device->devt); +#endif + +#if IS_ENABLED(CONFIG_ANDROID_SWITCH) || IS_ENABLED(CONFIG_SWITCH) + /* for UART event */ + if (switch_uart3.dev) + switch_dev_unregister(&switch_uart3); + /* for DockObserver */ + if (switch_dock.dev) + switch_dev_unregister(&switch_dock); +#endif /* CONFIG_ANDROID_SWITCH || CONFIG_SWITCH */ + + pr_info("%s: done\n", __func__); +} + +bool is_muic_usb_path_ap_usb(void) +{ + if (MUIC_PATH_USB_AP == muic_pdata.usb_path) { + pr_info("%s: [%d]\n", __func__, muic_pdata.usb_path); + return true; + } + + return false; +} + +bool is_muic_usb_path_cp_usb(void) +{ + if (MUIC_PATH_USB_CP == muic_pdata.usb_path) { + pr_info("%s: [%d]\n", __func__, muic_pdata.usb_path); + return true; + } + + return false; +} + +__visible_for_testing void muic_set_afc_disable(struct muic_platform_data *pdata) +{ +#if !IS_ENABLED(CONFIG_HV_MUIC_AFC_DISABLE_ENFORCE) + if (get_afc_mode() == CH_MODE_AFC_DISABLE_VAL) { + pr_info("AFC mode disabled\n"); + pdata->afc_disable = true; + } else { + pr_info("AFC mode enabled\n"); + pdata->afc_disable = false; + } +#else + pr_info("AFC mode disable enforce\n"); + pdata->afc_disable = true; +#endif /* !CONFIG_HV_MUIC_AFC_DISABLE_ENFORCE */ +} +EXPORT_SYMBOL_KUNIT(muic_set_afc_disable); + +static const char *muic_path_name(int path) +{ + switch (path) { + case MUIC_PATH_USB_AP: + return "USB_AP"; + case MUIC_PATH_USB_CP: + return "USB_CP"; + case MUIC_PATH_UART_AP: + return "UART_AP"; + case MUIC_PATH_UART_CP: + return "UART_CP"; + case MUIC_PATH_OPEN: + return "OPEN"; + case MUIC_PATH_AUDIO: + return "AUDIO"; + default: + return "UNDEFINED"; + } +} + +__visible_for_testing int muic_set_path(struct muic_platform_data *pdata) +{ + int ret = 0; + + if (pdata->set_gpio_usb_sel) { + ret = pdata->set_gpio_usb_sel(pdata->drv_data, pdata->usb_path); + if (ret) { + pr_err("%s set_gpio_usb_sel error\n", __func__); + goto err; + } + } + + if (pdata->set_gpio_uart_sel) { + ret = pdata->set_gpio_uart_sel(pdata->drv_data, pdata->uart_path); + if (ret) { + pr_err("%s set_gpio_uart_sel error\n", __func__); + goto err; + } + } + + pr_info("%s: usb_path(%s), uart_path(%s)\n", __func__, + muic_path_name(pdata->usb_path), muic_path_name(pdata->uart_path)); + +err: + return ret; +} +EXPORT_SYMBOL_KUNIT(muic_set_path); + +#if IS_MODULE(CONFIG_MUIC_NOTIFIER) +static int muic_init_gpio_cb(int switch_sel) +{ + struct muic_platform_data *pdata = &muic_pdata; + int ret = 0; + + pr_info("%s (%d)\n", __func__, switch_sel); + + if (switch_sel & SWITCH_SEL_USB_MASK) + pdata->usb_path = MUIC_PATH_USB_AP; + else + pdata->usb_path = MUIC_PATH_USB_CP; + + if (switch_sel & SWITCH_SEL_UART_MASK) + pdata->uart_path = MUIC_PATH_UART_AP; + else + pdata->uart_path = MUIC_PATH_UART_CP; + + /* These flags MUST be updated again from probe function */ + pdata->rustproof_on = false; + +#if !defined(CONFIG_SEC_FACTORY) && defined(CONFIG_MUIC_SUPPORT_TYPEB) + if (!(switch_sel & SWITCH_SEL_RUSTPROOF_MASK)) + pdata->rustproof_on = true; +#endif + + muic_set_afc_disable(pdata); + + muic_set_path(pdata); + + return ret; +} +#else +static int muic_init_gpio_cb(void) +{ + struct muic_platform_data *pdata = &muic_pdata; + int switch_sel = get_switch_sel(); + int uart_sel = get_uart_sel(); + int ret = 0; + + pr_info("%s: switch_sel(%d) uart_sel(%d)\n", __func__, switch_sel, uart_sel); + + pdata->usb_path = MUIC_PATH_USB_AP; + + pdata->uart_path = MUIC_PATH_UART_AP; + + if (switch_sel != -1) { + if (switch_sel & SWITCH_SEL_USB_MASK) + pdata->usb_path = MUIC_PATH_USB_AP; + else + pdata->usb_path = MUIC_PATH_USB_CP; + + if (switch_sel & SWITCH_SEL_UART_MASK) + pdata->uart_path = MUIC_PATH_UART_AP; + else + pdata->uart_path = MUIC_PATH_UART_CP; + +#if IS_ENABLED(CONFIG_MUIC_UART_SWITCH) + if (switch_sel & SWITCH_SEL_UART_MASK2) + pdata->uart_path = MUIC_PATH_UART_CP2; +#endif + } + +#if defined(CONFIG_MUIC_SUPPORT_UART_SEL) + if (uart_sel == MUIC_PATH_UART_CP) + pdata->uart_path = MUIC_PATH_UART_CP; +#endif + + /* These flags MUST be updated again from probe function */ + pdata->rustproof_on = false; + + muic_set_afc_disable(pdata); + + muic_set_path(pdata); + + return ret; +} +#endif + +int muic_afc_get_voltage(void) +{ + struct muic_platform_data *pdata = &muic_pdata; + int vol = -ENODEV; + + pr_info("%s : %dV\n", __func__, vol); + + if (pdata && pdata->muic_afc_get_voltage_cb) + vol = pdata->muic_afc_get_voltage_cb(); + + return vol; +} +EXPORT_SYMBOL(muic_afc_get_voltage); + +int muic_afc_request_cause_clear(void) +{ + struct muic_platform_data *pdata = &muic_pdata; + + if (pdata == NULL) + return -ENOENT; + pdata->afc_request_cause &= ~(AFC_REQUEST_DETACH_CLEAR_BIT); + return 0; +} +EXPORT_SYMBOL_GPL(muic_afc_request_cause_clear); + +int muic_afc_get_request_cause(void) +{ + struct muic_platform_data *pdata = &muic_pdata; + int ret = 0; + + if (pdata == NULL) { + return ret; + } + + ret = pdata->afc_request_cause; + + return ret; +} +EXPORT_SYMBOL_GPL(muic_afc_get_request_cause); + +bool muic_is_enable_afc_request(void) +{ + struct muic_platform_data *pdata = &muic_pdata; + bool ret = false; + + if (pdata == NULL) { + return ret; + } + + ret = pdata->afc_request_cause == 0; + + return ret; +} +EXPORT_SYMBOL_GPL(muic_is_enable_afc_request); + +static int muic_afc_request_voltage_check(int cause, int vol) +{ + int ret = 0; + + if (vol == 9 && cause == 0) + ret = 9; + else + ret = 5; + pr_info("%s: cause=%x %dv->%dv\n", __func__, cause, vol, ret); + return ret; +} + +int muic_afc_request_voltage(int cause, int voltage) +{ + struct muic_platform_data *pdata = &muic_pdata; + int set_vol = 0, ret = 0; + + if (pdata == NULL) { + ret = -ENOENT; + goto out; + } + + if (voltage == 9) { + pr_info("%s: afc request clear, cause(%d), voltage(%d)\n", __func__, cause, voltage); + pdata->afc_request_cause &= ~(cause); + } else if (voltage == 5) { + pr_info("%s: afc request set, cause(%d), voltage(%d)\n", __func__, cause, voltage); + pdata->afc_request_cause |= (cause); + } else { + pr_err("%s: not support. cause(%d), voltage(%d)\n", __func__, cause, voltage); + ret = -EINVAL; + goto out; + } + + set_vol = muic_afc_request_voltage_check(pdata->afc_request_cause, voltage); + ret = muic_afc_set_voltage(set_vol); +out: + return ret; +} +EXPORT_SYMBOL_GPL(muic_afc_request_voltage); + +int muic_afc_set_voltage(int voltage) +{ + struct muic_platform_data *pdata = &muic_pdata; +#if defined(CONFIG_USE_SECOND_MUIC) + struct muic_platform_data *pdata2 = &muic_pdata2; +#endif + int ret = -ENODEV; + + pr_info("%s : %dV\n", __func__, voltage); + +#if defined(CONFIG_USE_SECOND_MUIC) + if (pdata2->muic_afc_set_voltage_cb) { + ret = pdata2->muic_afc_set_voltage_cb(voltage); + if (ret) + pr_err("%s second muic_afc_set_voltage_cb ret=%d\n", __func__, ret); + } +#endif + + if (pdata && pdata->muic_afc_set_voltage_cb) { + ret = pdata->muic_afc_set_voltage_cb(voltage); + if (ret) + pr_err("%s muic_afc_set_voltage_cb ret=%d\n", __func__, ret); + return ret; + } + + pr_err("%s: cannot supported\n", __func__); + return ret; +} +EXPORT_SYMBOL(muic_afc_set_voltage); + +int muic_hv_charger_disable(bool en) +{ + struct muic_platform_data *pdata = &muic_pdata; +#if defined(CONFIG_USE_SECOND_MUIC) + struct muic_platform_data *pdata2 = &muic_pdata2; +#endif + int ret = -ENODEV; + + pr_info("%s %sable\n", __func__, en ? "en" : "dis"); + +#if defined(CONFIG_USE_SECOND_MUIC) + if (pdata2->muic_hv_charger_disable_cb) { + ret = pdata2->muic_hv_charger_disable_cb(en); + if (ret) + pr_err("%s second muic_hv_charger_disable_cb ret=%d\n", __func__, ret); + } +#endif + + if (pdata && pdata->muic_hv_charger_disable_cb) { + ret = pdata->muic_hv_charger_disable_cb(en); + if (ret) + pr_err("%s muic_hv_charger_disable_cb ret=%d\n", __func__, ret); + } + + return ret; +} +EXPORT_SYMBOL(muic_hv_charger_disable); + +int muic_hv_charger_init(void) +{ + struct muic_platform_data *pdata = &muic_pdata; + + if (pdata) + set_bit(CHARGER_PROBE_DONE, &pdata->driver_probe_flag); + + if (pdata && pdata->muic_hv_charger_init_cb) + return pdata->muic_hv_charger_init_cb(); + + pr_err("%s: cannot supported\n", __func__); + return -ENODEV; +} +EXPORT_SYMBOL(muic_hv_charger_init); + +int muic_set_hiccup_mode(int on_off) +{ + struct muic_platform_data *pdata = &muic_pdata; + + pr_info("%s %d\n", __func__, on_off); + + if (pdata && pdata->muic_set_hiccup_mode_cb) + return pdata->muic_set_hiccup_mode_cb(on_off); + + pr_err("%s: cannot supported\n", __func__); + return -ENODEV; +} +EXPORT_SYMBOL_GPL(muic_set_hiccup_mode); + +#if IS_ENABLED(CONFIG_MUIC_POGO) +int muic_set_pogo_adc(int adc) +{ + struct muic_platform_data *pdata = &muic_pdata; + + if (pdata && pdata->muic_set_pogo_adc_cb) + return pdata->muic_set_pogo_adc_cb(adc); + + pr_err("%s: cannot supported\n", __func__); + return -ENODEV; +} +EXPORT_SYMBOL_GPL(muic_set_pogo_adc); +#endif diff --git a/drivers/muic/common/muic_notifier.c b/drivers/muic/common/muic_notifier.c new file mode 100644 index 000000000000..085280ab85e4 --- /dev/null +++ b/drivers/muic/common/muic_notifier.c @@ -0,0 +1,619 @@ + +#define pr_fmt(fmt) "[MUIC] " fmt +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_ANDROID_SWITCH) || IS_ENABLED(CONFIG_SWITCH) +#include +#endif /* CONFIG_ANDROID_SWITCH || CONFIG_SWITCH */ +#include +#include +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) +#include +#endif + +#define SET_MUIC_NOTIFIER_BLOCK(nb, fn, dev) do { \ + (nb)->notifier_call = (fn); \ + (nb)->priority = (dev); \ + } while (0) + +#define DESTROY_MUIC_NOTIFIER_BLOCK(nb) \ + SET_MUIC_NOTIFIER_BLOCK(nb, NULL, -1) + +struct device *switch_device; +EXPORT_SYMBOL(switch_device); + +static struct muic_notifier_struct muic_notifier; +#if IS_ENABLED(CONFIG_PDIC_SLSI_NON_MCU) +static struct muic_notifier_struct muic_pdic_notifier; +#endif + +static int muic_uses_new_noti; +#if IS_ENABLED(CONFIG_PDIC_SLSI_NON_MCU) +static int muic_pdic_uses_new_noti; +#endif + +static int muic_notifier_init_done; +static int muic_notifier_init(void); + +#if IS_ENABLED(CONFIG_SEC_FACTORY) +#if IS_ENABLED(CONFIG_ANDROID_SWITCH) || IS_ENABLED(CONFIG_SWITCH) +struct switch_dev switch_muic_dev = { + .name = "attached_muic_cable", +}; + +static void send_muic_cable_intent(int type) +{ + pr_info("%s: MUIC attached_muic_cable type(%d)\n", __func__, type); + switch_set_state(&switch_muic_dev, type); +} +#endif /* CONFIG_ANDROID_SWITCH || CONFIG_SWITCH */ +#endif + +void muic_notifier_set_new_noti(bool flag) +{ + muic_uses_new_noti = flag ? 1 : 0; +} +EXPORT_SYMBOL(muic_notifier_set_new_noti); + +static void __set_noti_cxt_dest(int attach, int type, int dest) +{ + if (type < 0) { + muic_notifier.cmd = attach; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + muic_notifier.cxt.attach = attach; + muic_notifier.cxt.dest = dest; +#endif + return; + } + + /* Old Interface */ + muic_notifier.cmd = attach; + muic_notifier.attached_dev = type; + +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + /* New Interface */ +#if defined(CONFIG_USE_DEDICATED_MUIC) + muic_notifier.cxt.src = PDIC_NOTIFY_DEV_DEDICATED_MUIC; +#else + muic_notifier.cxt.src = PDIC_NOTIFY_DEV_MUIC; +#endif + muic_notifier.cxt.dest = dest; + muic_notifier.cxt.id = PDIC_NOTIFY_ID_ATTACH; + muic_notifier.cxt.attach = attach; + muic_notifier.cxt.cable_type = type; + muic_notifier.cxt.rprd = 0; +#endif +} + +static void __set_noti_cxt(int attach, int type) +{ +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + __set_noti_cxt_dest(attach, type, PDIC_NOTIFY_DEV_ALL); +#else + __set_noti_cxt_dest(attach, type, 0); +#endif +} + +#if IS_ENABLED(CONFIG_PDIC_SLSI_NON_MCU) +static void __set_pdic_noti_cxt(int attach, int type) +{ + if (type < 0) { + muic_pdic_notifier.cmd = attach; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + muic_pdic_notifier.cxt.attach = attach; +#endif + return; + } + + /* Old Interface */ + muic_pdic_notifier.cmd = attach; + muic_pdic_notifier.attached_dev = type; + +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + /* New Interface */ +#if defined(CONFIG_USE_DEDICATED_MUIC) + muic_pdic_notifier.cxt.src = PDIC_NOTIFY_DEV_DEDICATED_MUIC; +#else + muic_pdic_notifier.cxt.src = PDIC_NOTIFY_DEV_MUIC; +#endif + muic_pdic_notifier.cxt.dest = PDIC_NOTIFY_DEV_ALL; + muic_pdic_notifier.cxt.id = PDIC_NOTIFY_ID_ATTACH; + muic_pdic_notifier.cxt.attach = attach; + muic_pdic_notifier.cxt.cable_type = type; + muic_pdic_notifier.cxt.rprd = 0; +#endif +} +#endif + +#if IS_ENABLED(CONFIG_MUIC_POGO) +static void __set_pogo_noti_cxt(int attach, int type) +{ +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + if (type < 0) { + muic_notifier.pogo_cxt.attach = attach; + return; + } + /* New Interface */ + muic_notifier.pogo_cxt.src = PDIC_NOTIFY_DEV_MUIC; + muic_notifier.pogo_cxt.dest = PDIC_NOTIFY_DEV_ALL; + muic_notifier.pogo_cxt.id = PDIC_NOTIFY_ID_POGO; + muic_notifier.pogo_cxt.attach = attach; + muic_notifier.pogo_cxt.cable_type = type; + muic_notifier.pogo_cxt.rprd = 0; +#endif +} +#endif /* CONFIG_MUIC_POGO */ + +int muic_notifier_register(struct notifier_block *nb, notifier_fn_t notifier, + muic_notifier_device_t listener) +{ + int ret = 0; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + void *pcxt; +#endif + + pr_info("%s: listener=%d register\n", __func__, listener); + if (!muic_notifier_init_done) + muic_notifier_init(); + + SET_MUIC_NOTIFIER_BLOCK(nb, notifier, listener); + ret = blocking_notifier_chain_register(&(muic_notifier.notifier_call_chain), nb); + if (ret < 0) + pr_err("%s: blocking_notifier_chain_register error(%d)\n", + __func__, ret); + +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + pcxt = muic_uses_new_noti ? &(muic_notifier.cxt) : + (void *)&(muic_notifier.attached_dev); + + /* current muic's attached_device status notify */ + nb->notifier_call(nb, muic_notifier.cxt.attach, pcxt); +#if IS_ENABLED(CONFIG_MUIC_POGO) + nb->notifier_call(nb, muic_notifier.pogo_cxt.attach, &(muic_notifier.pogo_cxt)); +#endif /* CONFIG_MUIC_POGO */ +#else + nb->notifier_call(nb, muic_notifier.cmd, + &(muic_notifier.attached_dev)); +#endif + + return ret; +} +EXPORT_SYMBOL(muic_notifier_register); + +int muic_notifier_unregister(struct notifier_block *nb) +{ + int ret = 0; + + pr_info("%s: listener=%d unregister\n", __func__, nb->priority); + + ret = blocking_notifier_chain_unregister(&(muic_notifier.notifier_call_chain), nb); + if (ret < 0) + pr_err("%s: blocking_notifier_chain_unregister error(%d)\n", + __func__, ret); + DESTROY_MUIC_NOTIFIER_BLOCK(nb); + + return ret; +} +EXPORT_SYMBOL(muic_notifier_unregister); + +static int muic_notifier_notify(void) +{ + int ret = 0; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + void *pcxt; + + pr_info("%s: CMD=%d, DATA=%d\n", __func__, muic_notifier.cxt.attach, + muic_notifier.cxt.cable_type); + + pcxt = muic_uses_new_noti ? &(muic_notifier.cxt) : + (void *)&(muic_notifier.attached_dev); + + ret = blocking_notifier_call_chain(&(muic_notifier.notifier_call_chain), + muic_notifier.cxt.attach, pcxt); +#else + pr_info("%s: CMD=%d, DATA=%d\n", __func__, muic_notifier.cmd, + muic_notifier.attached_dev); + ret = blocking_notifier_call_chain(&(muic_notifier.notifier_call_chain), + muic_notifier.cmd, &(muic_notifier.attached_dev)); +#endif + +#if IS_ENABLED(CONFIG_SEC_FACTORY) +#if IS_ENABLED(CONFIG_ANDROID_SWITCH) || IS_ENABLED(CONFIG_SWITCH) +#if defined(CONFIG_MUIC_SUPPORT_PDIC) && defined(CONFIG_PDIC_NOTIFIER) + if (muic_notifier.cxt.attach != 0) + send_muic_cable_intent(muic_notifier.cxt.cable_type); + else + send_muic_cable_intent(0); +#else + if (muic_notifier.cmd != 0) + send_muic_cable_intent(muic_notifier.attached_dev); + else + send_muic_cable_intent(0); +#if IS_ENABLED(CONFIG_MUIC_POGO) + if (muic_notifier.pogo_cxt.cable_type) + send_muic_cable_intent(muic_notifier.pogo_cxt.cable_type); +#endif /* CONFIG_MUIC_POGO */ +#endif /* CONFIG_MUIC_SUPPORT_PDIC */ +#endif /* CONFIG_ANDROID_SWITCH || CONFIG_SWITCH */ +#endif /* CONFIG_SEC_FACTORY */ + + switch (ret) { + case NOTIFY_STOP_MASK: + case NOTIFY_BAD: + pr_err("%s: notify error occur(0x%x)\n", __func__, ret); + break; + case NOTIFY_DONE: + case NOTIFY_OK: + pr_info("%s: notify done(0x%x)\n", __func__, ret); + break; + default: + pr_info("%s: notify status unknown(0x%x)\n", __func__, ret); + break; + } + + return ret; +} + +#if IS_ENABLED(CONFIG_PDIC_SLSI_NON_MCU) +int muic_pdic_notifier_register(struct notifier_block *nb, notifier_fn_t notifier, + muic_notifier_device_t listener) +{ + int ret = 0; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + void *pcxt; +#endif + + pr_info("%s: listener=%d register\n", __func__, listener); + + SET_MUIC_NOTIFIER_BLOCK(nb, notifier, listener); + ret = blocking_notifier_chain_register(&(muic_pdic_notifier.notifier_call_chain), nb); + if (ret < 0) + pr_err("%s: blocking_notifier_chain_register error(%d)\n", + __func__, ret); + +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + pcxt = muic_pdic_uses_new_noti ? &(muic_pdic_notifier.cxt) : \ + (void *)&(muic_pdic_notifier.attached_dev); + + /* current muic's attached_device status notify */ + nb->notifier_call(nb, muic_pdic_notifier.cxt.attach, pcxt); +#else + nb->notifier_call(nb, muic_pdic_notifier.cmd, + &(muic_pdic_notifier.attached_dev)); +#endif + + return ret; +} +EXPORT_SYMBOL(muic_pdic_notifier_register); + +int muic_pdic_notifier_unregister(struct notifier_block *nb) +{ + int ret = 0; + + pr_info("%s: listener=%d unregister\n", __func__, nb->priority); + + ret = blocking_notifier_chain_unregister(&(muic_pdic_notifier.notifier_call_chain), nb); + if (ret < 0) + pr_err("%s: blocking_notifier_chain_unregister error(%d)\n", + __func__, ret); + DESTROY_MUIC_NOTIFIER_BLOCK(nb); + + return ret; +} +EXPORT_SYMBOL(muic_pdic_notifier_unregister); + +static int muic_pdic_notifier_notify(void) +{ + int ret = 0; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + void *pcxt; + + pr_info("%s: CMD=%d, DATA=%d\n", __func__, muic_pdic_notifier.cxt.attach, + muic_pdic_notifier.cxt.cable_type); + + pcxt = muic_pdic_uses_new_noti ? &(muic_pdic_notifier.cxt) : \ + (void *)&(muic_pdic_notifier.attached_dev); + + ret = blocking_notifier_call_chain(&(muic_pdic_notifier.notifier_call_chain), + muic_pdic_notifier.cxt.attach, pcxt); +#else + pr_info("%s: CMD=%d, DATA=%d\n", __func__, muic_pdic_notifier.cmd, + muic_pdic_notifier.attached_dev); + ret = blocking_notifier_call_chain(&(muic_pdic_notifier.notifier_call_chain), + muic_pdic_notifier.cmd, &(muic_pdic_notifier.attached_dev)); +#endif + + switch (ret) { + case NOTIFY_STOP_MASK: + case NOTIFY_BAD: + pr_err("%s: notify error occur(0x%x)\n", __func__, ret); + break; + case NOTIFY_DONE: + case NOTIFY_OK: + pr_info("%s: notify done(0x%x)\n", __func__, ret); + break; + default: + pr_info("%s: notify status unknown(0x%x)\n", __func__, ret); + break; + } + + return ret; +} +#endif /* CONFIG_PDIC_SLSI_NON_MCU */ + +#if IS_ENABLED(CONFIG_MUIC_POGO) +static int muic_pogo_notifier_notify(void) +{ + int ret = 0; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + void *pcxt = &(muic_notifier.pogo_cxt); + + pr_info("%s: CMD=%d, DATA=%d\n", __func__, muic_notifier.pogo_cxt.attach, + muic_notifier.pogo_cxt.cable_type); + + ret = blocking_notifier_call_chain(&(muic_notifier.notifier_call_chain), + muic_notifier.pogo_cxt.attach, pcxt); +#endif + +#if IS_ENABLED(CONFIG_SEC_FACTORY) +#if defined(CONFIG_MUIC_SUPPORT_PDIC) && defined(CONFIG_PDIC_NOTIFIER) + if (muic_notifier.pogo_cxt.attach != 0) + send_muic_cable_intent(muic_notifier.pogo_cxt.cable_type); + else + send_muic_cable_intent(0); + if (muic_notifier.cxt.cable_type) + send_muic_cable_intent(muic_notifier.cxt.cable_type); +#endif /* CONFIG_MUIC_SUPPORT_CCIC */ +#endif /* CONFIG_SEC_FACTORY */ + + switch (ret) { + case NOTIFY_STOP_MASK: + case NOTIFY_BAD: + pr_err("%s: notify error occur(0x%x)\n", __func__, ret); + break; + case NOTIFY_DONE: + case NOTIFY_OK: + pr_info("%s: notify done(0x%x)\n", __func__, ret); + break; + default: + pr_info("%s: notify status unknown(0x%x)\n", __func__, ret); + break; + } + + return ret; +} + +void muic_pogo_notifier_attach_attached_dev(muic_attached_dev_t new_dev) +{ + pr_info("%s: (%d)\n", __func__, new_dev); + + __set_pogo_noti_cxt(MUIC_NOTIFY_CMD_ATTACH, new_dev); + + /* muic's attached_device attach broadcast */ + muic_pogo_notifier_notify(); +} +EXPORT_SYMBOL(muic_pogo_notifier_attach_attached_dev); + +void muic_pogo_notifier_detach_attached_dev(muic_attached_dev_t cur_dev) +{ + pr_info("%s: (%d)\n", __func__, cur_dev); + + __set_pogo_noti_cxt(MUIC_NOTIFY_CMD_DETACH, -1); + +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + if (muic_notifier.pogo_cxt.cable_type != cur_dev) + pr_warn("%s: attached_dev of muic_notifier(%d) != muic_data(%d)\n", + __func__, muic_notifier.pogo_cxt.cable_type, cur_dev); + + if (muic_notifier.pogo_cxt.cable_type != ATTACHED_DEV_NONE_MUIC) { + /* muic's attached_device detach broadcast */ + muic_pogo_notifier_notify(); + } +#endif + + __set_pogo_noti_cxt(0, ATTACHED_DEV_NONE_MUIC); +} +EXPORT_SYMBOL(muic_pogo_notifier_detach_attached_dev); +#endif /* CONFIG_MUIC_POGO */ + +void muic_notifier_attach_attached_dev(muic_attached_dev_t new_dev) +{ + pr_info("%s: (%d)\n", __func__, new_dev); + + __set_noti_cxt(MUIC_NOTIFY_CMD_ATTACH, new_dev); + + /* muic's attached_device attach broadcast */ + muic_notifier_notify(); +} +EXPORT_SYMBOL(muic_notifier_attach_attached_dev); + +void muic_pdic_notifier_attach_attached_dev(muic_attached_dev_t new_dev) +{ + pr_info("%s: (%d)\n", __func__, new_dev); + +#if IS_ENABLED(CONFIG_PDIC_SLSI_NON_MCU) + __set_pdic_noti_cxt(MUIC_PDIC_NOTIFY_CMD_ATTACH, new_dev); + + /* muic's attached_device attach broadcast */ + muic_pdic_notifier_notify(); +#else + __set_noti_cxt(MUIC_PDIC_NOTIFY_CMD_ATTACH, new_dev); + + /* muic's attached_device attach broadcast */ + muic_notifier_notify(); +#endif +} +EXPORT_SYMBOL(muic_pdic_notifier_attach_attached_dev); + +void muic_pdic_notifier_detach_attached_dev(muic_attached_dev_t new_dev) +{ + pr_info("%s: (%d)\n", __func__, new_dev); + +#if IS_ENABLED(CONFIG_PDIC_SLSI_NON_MCU) + __set_pdic_noti_cxt(MUIC_PDIC_NOTIFY_CMD_DETACH, new_dev); + + /* muic's attached_device attach broadcast */ + muic_pdic_notifier_notify(); +#else + __set_noti_cxt(MUIC_PDIC_NOTIFY_CMD_DETACH, muic_notifier.attached_dev); + /* muic's attached_device attach broadcast */ + muic_notifier_notify(); +#endif +} +EXPORT_SYMBOL(muic_pdic_notifier_detach_attached_dev); + +void muic_notifier_detach_attached_dev(muic_attached_dev_t cur_dev) +{ + pr_info("%s: (%d)\n", __func__, cur_dev); + + __set_noti_cxt(MUIC_NOTIFY_CMD_DETACH, -1); + +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + if (muic_notifier.cxt.cable_type != cur_dev) + pr_warn("%s: attached_dev of muic_notifier(%d) != muic_data(%d)\n", + __func__, muic_notifier.cxt.cable_type, cur_dev); + + if (muic_notifier.cxt.cable_type != ATTACHED_DEV_NONE_MUIC) { + /* muic's attached_device detach broadcast */ + muic_notifier_notify(); + } +#else + if (muic_notifier.attached_dev != cur_dev) + pr_warn("%s: attached_dev of muic_notifier(%d) != muic_data(%d)\n", + __func__, muic_notifier.attached_dev, cur_dev); + + if (muic_notifier.attached_dev != ATTACHED_DEV_NONE_MUIC) { + /* muic's attached_device detach broadcast */ + muic_notifier_notify(); + } +#endif + + __set_noti_cxt(0, ATTACHED_DEV_NONE_MUIC); +} +EXPORT_SYMBOL(muic_notifier_detach_attached_dev); + +void muic_notifier_logically_attach_attached_dev(muic_attached_dev_t new_dev) +{ + pr_info("%s: (%d)\n", __func__, new_dev); + + __set_noti_cxt(MUIC_NOTIFY_CMD_ATTACH, new_dev); + + /* muic's attached_device attach broadcast */ + muic_notifier_notify(); +} +EXPORT_SYMBOL(muic_notifier_logically_attach_attached_dev); + +void muic_notifier_logically_detach_attached_dev(muic_attached_dev_t cur_dev) +{ + pr_info("%s: (%d)\n", __func__, cur_dev); + + __set_noti_cxt(MUIC_NOTIFY_CMD_DETACH, cur_dev); + + /* muic's attached_device detach broadcast */ + muic_notifier_notify(); + + __set_noti_cxt(0, ATTACHED_DEV_NONE_MUIC); +} +EXPORT_SYMBOL(muic_notifier_logically_detach_attached_dev); + +#if IS_ENABLED(CONFIG_VIRTUAL_MUIC) +void vt_muic_notifier_attach_attached_dev(muic_attached_dev_t new_dev) +{ + pr_info("%s: (%d)\n", __func__, new_dev); + + __set_noti_cxt_dest(MUIC_NOTIFY_CMD_ATTACH, new_dev, PDIC_NOTIFY_DEV_PDIC); + + /* muic's attached_device attach broadcast */ + muic_notifier_notify(); +} +EXPORT_SYMBOL(vt_muic_notifier_attach_attached_dev); + +void vt_muic_notifier_detach_attached_dev(muic_attached_dev_t cur_dev) +{ + pr_info("%s: (%d)\n", __func__, cur_dev); + + __set_noti_cxt_dest(MUIC_NOTIFY_CMD_DETACH, -1, PDIC_NOTIFY_DEV_PDIC); + +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + if (muic_notifier.cxt.cable_type != cur_dev) + pr_warn("%s: attached_dev of muic_notifier(%d) != muic_data(%d)\n", + __func__, muic_notifier.cxt.cable_type, cur_dev); + + if (muic_notifier.cxt.cable_type != ATTACHED_DEV_NONE_MUIC) { + /* muic's attached_device detach broadcast */ + muic_notifier_notify(); + } +#else + if (muic_notifier.attached_dev != cur_dev) + pr_warn("%s: attached_dev of muic_notifier(%d) != muic_data(%d)\n", + __func__, muic_notifier.attached_dev, cur_dev); + + if (muic_notifier.attached_dev != ATTACHED_DEV_NONE_MUIC) { + /* muic's attached_device detach broadcast */ + muic_notifier_notify(); + } +#endif + + __set_noti_cxt(0, ATTACHED_DEV_NONE_MUIC); +} +EXPORT_SYMBOL(vt_muic_notifier_detach_attached_dev); +#endif + +static int muic_notifier_init(void) +{ + int ret = 0; + + pr_info("%s\n", __func__); + + if (muic_notifier_init_done) { + pr_info("%s already registered\n", __func__); + return ret; + } + muic_notifier_init_done = 1; + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + switch_device = sec_device_create(NULL, "switch"); + if (IS_ERR(switch_device)) { + pr_err("%s Failed to create device(switch)!\n", __func__); + ret = -ENODEV; + } +#endif +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + muic_uses_new_noti = 1; +#endif + BLOCKING_INIT_NOTIFIER_HEAD(&(muic_notifier.notifier_call_chain)); + __set_noti_cxt(0, ATTACHED_DEV_NONE_MUIC); +#if IS_ENABLED(CONFIG_PDIC_SLSI_NON_MCU) + BLOCKING_INIT_NOTIFIER_HEAD(&(muic_pdic_notifier.notifier_call_chain)); + __set_pdic_noti_cxt(0, ATTACHED_DEV_UNKNOWN_MUIC); + muic_pdic_uses_new_noti = 1; +#endif + +#if IS_ENABLED(CONFIG_MUIC_POGO) + __set_pogo_noti_cxt(0, ATTACHED_DEV_NONE_MUIC); +#endif /* CONFIG_MUIC_POGO */ + +#if IS_ENABLED(CONFIG_SEC_FACTORY) +#if IS_ENABLED(CONFIG_ANDROID_SWITCH) || IS_ENABLED(CONFIG_SWITCH) + ret = switch_dev_register(&switch_muic_dev); + if (ret < 0) + pr_err("%s: Failed to register attached_muic_cable switch(%d)\n", + __func__, ret); +#endif /* CONFIG_ANDROID_SWITCH || CONFIG_SWITCH */ +#endif + return ret; +} + +static void __exit muic_notifier_exit(void) +{ + pr_info("%s: exit\n", __func__); +} + +device_initcall(muic_notifier_init); +module_exit(muic_notifier_exit); + +MODULE_AUTHOR("Samsung USB Team"); +MODULE_DESCRIPTION("Muic Notifier"); +MODULE_LICENSE("GPL"); diff --git a/drivers/muic/common/muic_param.c b/drivers/muic/common/muic_param.c new file mode 100755 index 000000000000..4d3cd3577280 --- /dev/null +++ b/drivers/muic/common/muic_param.c @@ -0,0 +1,188 @@ +/* + * + * Copyright (C) 2021 Samsung Electronics + * + * 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, see . + * + */ +#define pr_fmt(fmt) "muic_param: " fmt + +#include +#include +#include +#include + +static int muic_param_pmic_info = -1; +module_param(muic_param_pmic_info, int, 0444); + +static int muic_param_uart_sel = -1; +module_param(muic_param_uart_sel, int, 0444); + +static int muic_param_afc_mode = -1; +module_param(muic_param_afc_mode, int, 0444); + +static int muic_param_pdic_info = -1; +module_param(muic_param_pdic_info, int, 0444); + +#if ((IS_MODULE(CONFIG_SEC_PARAM) || IS_ENABLED(CONFIG_SEC_MPARAM)) && !IS_ENABLED(CONFIG_MUIC_USE_MODULE_PARAM)) +extern int pmic_info; +extern unsigned int charging_mode; +extern int ccic_info; +#else +static int pmic_info = -1; +static unsigned int charging_mode; +static int ccic_info = 1; +#endif + +#if IS_BUILTIN(CONFIG_MUIC_NOTIFIER) +static int __init set_switch_sel(char *str) +{ + get_option(&str, &muic_param_pmic_info); + pr_info("%s: pmic_info 0x%x\n", __func__, muic_param_pmic_info); + + return 1; +} +__setup("pmic_info=", set_switch_sel); + +/* func : set_uart_sel for QC boot command + * uart_sel value get from bootloader command line + */ +static int __init set_uart_sel(char *str) +{ + get_option(&str, &muic_param_uart_sel); + pr_info("%s: uart_sel is 0x%02x\n", __func__, muic_param_uart_sel); + + return 0; +} +early_param("uart_sel", set_uart_sel); + +/* afc_mode: + * 0x31 : Disabled + * 0x30 : Enabled + */ +/* for LSI boot command */ +static int __init set_charging_mode(char *str) +{ + int mode; + get_option(&str, &mode); + muic_param_afc_mode = (mode & 0x0000FF00) >> 8; + pr_info("%s: afc_mode is 0x%02x\n", __func__, muic_param_afc_mode); + + return 0; +} +early_param("charging_mode", set_charging_mode); + +/* for QC boot command */ +static int __init set_afc_disable(char *str) +{ + get_option(&str, &muic_param_afc_mode); + pr_info("%s: afc_mode is 0x%02x\n", __func__, muic_param_afc_mode); + + return 0; +} +early_param("afc_disable", set_afc_disable); +/* + * __pdic_info : + * b'0: 1 if an active pdic is present, + * 0 when muic works without pdic chip or + * no pdic Noti. registration is needed + * even though a pdic chip is present. + */ +static int __init set_pdic_info(char *str) +{ + get_option(&str, &muic_param_pdic_info); + + pr_info("%s: pdic_info: 0x%04x\n", __func__, muic_param_pdic_info); + + return 1; +} +__setup("ccic_info=", set_pdic_info); + +#endif /* BUILTIN CONFIG_MUIC_NOTIFIER */ + +/* + * switch_sel value get from bootloader command line + * switch_sel data consist 8 bits (xxxxyyyyzzzz) + * first 4bits(zzzz) mean path information. + * next 4bits(yyyy) mean if pmic version info + * next 4bits(xxxx) mean afc disable info + */ +int get_switch_sel(void) +{ + int local_pmic_info = 0, local_switch_sel = 0; + + if (muic_param_pmic_info != -1) { + local_pmic_info = muic_param_pmic_info; + goto out; + } + + local_pmic_info = pmic_info; +out: + local_switch_sel = local_pmic_info & 0xfff; + pr_info("%s: switch_sel: 0x%03x\n", __func__, local_switch_sel); + + return local_switch_sel; +} +EXPORT_SYMBOL_GPL(get_switch_sel); + +int get_uart_sel(void) +{ + int local_uart_sel = -1; + + if (muic_param_uart_sel != -1) { + local_uart_sel = muic_param_uart_sel; + goto out; + } +out: + pr_info("%s: get_uart_sel 0x%x\n", __func__, local_uart_sel); + + return local_uart_sel; +} +EXPORT_SYMBOL_GPL(get_uart_sel); + +int get_afc_mode(void) +{ + int local_afc_mode = 0, local_charging_mode = 0; + + if (muic_param_afc_mode != -1) { + local_afc_mode = muic_param_afc_mode; + goto out; + } + + local_charging_mode = charging_mode; + local_afc_mode = (local_charging_mode & 0x0000FF00) >> 8; +out: + pr_info("%s: afc_mode is 0x%02x\n", __func__, local_afc_mode); + + return local_afc_mode; +} +EXPORT_SYMBOL_GPL(get_afc_mode); + +int get_pdic_info(void) +{ + int local_pdic_info = 1; + + if (muic_param_pdic_info != -1) { + local_pdic_info = muic_param_pdic_info; + goto out; + } + + local_pdic_info = ccic_info; +out: + pr_info("%s: ccic_info: 0x%04x\n", __func__, local_pdic_info); + + return local_pdic_info; +} +EXPORT_SYMBOL_GPL(get_pdic_info); + diff --git a/drivers/muic/common/muic_sysfs.c b/drivers/muic/common/muic_sysfs.c new file mode 100755 index 000000000000..fb2a6998228e --- /dev/null +++ b/drivers/muic/common/muic_sysfs.c @@ -0,0 +1,703 @@ +/* + * driver/muic/muic_sysfs.c - micro USB switch device driver + * + * Copyright (C) 2019 Samsung Electronics + * Sejong Park + * Taejung Kim + * + * 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 pr_fmt(fmt) "[MUIC] " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) +#include +#endif +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) +#include +#endif +#if IS_BUILTIN(CONFIG_MUIC_NOTIFIER) +#if defined(CONFIG_ARCH_QCOM) +#include +#endif +#endif +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) +#include +#endif + +static ssize_t muic_sysfs_uart_en_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + int ret = 0; + + if (!pdata->rustproof_on) { + pr_info("%s UART ENABLE\n", __func__); + ret = sprintf(buf, "1\n"); + } else { + pr_info("%s UART DISABLE\n", __func__); + ret = sprintf(buf, "0\n"); + } + + return ret; +} + +static ssize_t muic_sysfs_set_uart_en(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + int ret = 0; + + if (!strncmp(buf, "1", 1)) { + pdata->rustproof_on = false; + MUIC_PDATA_FUNC_MULTI_PARAM + (pdata->sysfs_cb.set_uart_en, + pdata->drv_data, MUIC_ENABLE, &ret); + } else if (!strncmp(buf, "0", 1)) { + pdata->rustproof_on = true; + MUIC_PDATA_FUNC_MULTI_PARAM + (pdata->sysfs_cb.set_uart_en, + pdata->drv_data, MUIC_DISABLE, &ret); + } else + pr_info("%s invalid value\n", __func__); + + pr_info("%s uart_en(%d)\n", + __func__, !pdata->rustproof_on); + + return count; +} + +static ssize_t muic_sysfs_uart_sel_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + const char *mode = "UNKNOWN\n"; + + switch (pdata->uart_path) { + case MUIC_PATH_UART_AP: + mode = "AP\n"; + break; + case MUIC_PATH_UART_CP: + mode = "CP\n"; + break; + default: + break; + } + + pr_info("%s %s", __func__, mode); + return snprintf(buf, strlen(mode) + 1, "%s", mode); +} + +static ssize_t muic_sysfs_set_uart_sel(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + + pr_info("%s %s\n", __func__, buf); + if (!strncasecmp(buf, "AP", 2)) + pdata->uart_path = MUIC_PATH_UART_AP; + else if (!strncasecmp(buf, "CP", 2)) + pdata->uart_path = MUIC_PATH_UART_CP; + else + pr_warn("%s invalid value\n", __func__); + + MUIC_PDATA_VOID_FUNC(pdata->sysfs_cb.set_uart_sel, pdata->drv_data); + + return count; +} + +static ssize_t muic_sysfs_usb_sel_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + const char *mode = "UNKNOWN\n"; + + switch (pdata->usb_path) { + case MUIC_PATH_USB_AP: + mode = "PDA\n"; + break; + case MUIC_PATH_USB_CP: + mode = "MODEM\n"; + break; + default: + break; + } + + pr_info("%s %s", __func__, mode); + return sprintf(buf, "%s", mode); +} + +static ssize_t muic_sysfs_set_usb_sel(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + + if (!strncasecmp(buf, "PDA", 3)) + pdata->usb_path = MUIC_PATH_USB_AP; + else if (!strncasecmp(buf, "MODEM", 5)) + pdata->usb_path = MUIC_PATH_USB_CP; + else + pr_warn("%s invalid value\n", __func__); + + pr_info("%s usb_path(%d)\n", __func__, + pdata->usb_path); + + return count; +} + +static ssize_t muic_sysfs_usb_en_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + int ret = 0; + + pr_info("%s+\n", __func__); + MUIC_PDATA_FUNC(pdata->sysfs_cb.get_usb_en, pdata->drv_data, &ret); + + pr_info("%s ret=%d-\n", __func__, ret); + + return sprintf(buf, "%s attached_dev = %d\n", + __func__, ret); +} + +static ssize_t muic_sysfs_set_usb_en(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + int ret = 0; + + pr_info("%s+\n", __func__); + if (!strncasecmp(buf, "1", 1)) { + MUIC_PDATA_FUNC_MULTI_PARAM(pdata->sysfs_cb.set_usb_en, pdata->drv_data, + MUIC_ENABLE, &ret); + } else if (!strncasecmp(buf, "0", 1)) { + MUIC_PDATA_FUNC_MULTI_PARAM(pdata->sysfs_cb.set_usb_en, pdata->drv_data, + MUIC_DISABLE, &ret); + } else { + pr_info("%s invalid value\n", __func__); + } + + pr_info("%s ret=%d-\n", __func__, ret); + return count; +} + +static ssize_t muic_sysfs_adc_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + int adc; + + pr_info("%s+\n", __func__); + MUIC_PDATA_FUNC(pdata->sysfs_cb.get_adc, pdata->drv_data, &adc); + + if (adc < 0) { + pr_err("%s err read adc reg(%d)\n", + __func__, adc); + return sprintf(buf, "UNKNOWN\n"); + } + + pr_info("%s adc=%x-\n", __func__, adc); + return sprintf(buf, "%x\n", adc); +} + +#if IS_ENABLED(CONFIG_MUIC_DEBUG) +static ssize_t muic_sysfs_mansw_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + char mesg[256] = ""; + int ret; + + MUIC_PDATA_FUNC_MULTI_PARAM(pdata->sysfs_cb.get_mansw, pdata->drv_data, mesg, &ret); + pr_info("%s:%s\n", __func__, mesg); + return sprintf(buf, "%s\n", mesg); +} + +static ssize_t muic_sysfs_interrupt_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + char mesg[256] = ""; + int ret; + + MUIC_PDATA_FUNC_MULTI_PARAM(pdata->sysfs_cb.get_interrupt_status, pdata->drv_data, mesg, &ret); + pr_info("%s:%s\n", __func__, mesg); + return sprintf(buf, "%s\n", mesg); + +} + +static ssize_t muic_sysfs_registers_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + char mesg[256] = ""; + int ret; + + MUIC_PDATA_FUNC_MULTI_PARAM(pdata->sysfs_cb.get_register, pdata->drv_data, mesg, &ret); + pr_info("%s:%s\n", __func__, mesg); + return sprintf(buf, "%s\n", mesg); +} +#endif + +static ssize_t muic_sysfs_usb_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + static unsigned long switch_slot_time; + int mdev = 0; + + MUIC_PDATA_FUNC(pdata->sysfs_cb.get_attached_dev, pdata->drv_data, &mdev); + + pr_info("%s attached_dev:%d\n", __func__, mdev); + + if (printk_timed_ratelimit(&switch_slot_time, 5000)) + pr_info("%s attached_dev(%d)\n", + __func__, mdev); + + switch (mdev) { + case ATTACHED_DEV_USB_MUIC: + case ATTACHED_DEV_CDP_MUIC: + case ATTACHED_DEV_JIG_USB_OFF_MUIC: + case ATTACHED_DEV_JIG_USB_ON_MUIC: + return sprintf(buf, "USB_STATE_CONFIGURED\n"); + default: + break; + } + + return sprintf(buf, "USB_STATE_NOTCONFIGURED\n"); +} + +#if IS_ENABLED(CONFIG_USB_HOST_NOTIFY) +static ssize_t muic_sysfs_otg_test_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + int ret; + + MUIC_PDATA_FUNC(pdata->sysfs_cb.get_otg_test, pdata->drv_data, &ret); + + pr_info("%s ret=%d\n", __func__, ret); + return sprintf(buf, "%d\n", ret); +} + +static ssize_t muic_sysfs_set_otg_test(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + int ret = 0; + + pr_info("%s buf:%s\n", __func__, buf); + + /* + * The otg_test is set 0 durring the otg test. Not 1 !!! + */ + + if (!strncmp(buf, "0", 1)) { + MUIC_PDATA_FUNC_MULTI_PARAM(pdata->sysfs_cb.set_otg_test, pdata->drv_data, 1, &ret); + } else if (!strncmp(buf, "1", 1)) { + MUIC_PDATA_FUNC_MULTI_PARAM(pdata->sysfs_cb.set_otg_test, pdata->drv_data, 0, &ret); + } else { + pr_info("%s Wrong command\n", __func__); + return count; + } + + return count; +} +#endif + +static ssize_t muic_sysfs_attached_dev_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + int mdev = 0; + + MUIC_PDATA_FUNC(pdata->sysfs_cb.get_attached_dev, pdata->drv_data, &mdev); + + pr_info("%s attached_dev:%d\n", __func__, mdev); + + switch (mdev) { + case ATTACHED_DEV_NONE_MUIC: + return sprintf(buf, "No VPS\n"); + case ATTACHED_DEV_USB_MUIC: + return sprintf(buf, "USB\n"); + case ATTACHED_DEV_CDP_MUIC: + return sprintf(buf, "CDP\n"); + case ATTACHED_DEV_OTG_MUIC: + return sprintf(buf, "OTG\n"); + case ATTACHED_DEV_TA_MUIC: + return sprintf(buf, "TA\n"); + case ATTACHED_DEV_JIG_UART_OFF_MUIC: + return sprintf(buf, "JIG UART OFF\n"); + case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC: + return sprintf(buf, "JIG UART OFF/VB\n"); + case ATTACHED_DEV_JIG_UART_ON_MUIC: + return sprintf(buf, "JIG UART ON\n"); + case ATTACHED_DEV_JIG_UART_ON_VB_MUIC: + return sprintf(buf, "JIG UART ON/VB\n"); + case ATTACHED_DEV_JIG_USB_OFF_MUIC: + return sprintf(buf, "JIG USB OFF\n"); + case ATTACHED_DEV_JIG_USB_ON_MUIC: + return sprintf(buf, "JIG USB ON\n"); + case ATTACHED_DEV_DESKDOCK_MUIC: + case ATTACHED_DEV_DESKDOCK_VB_MUIC: + return sprintf(buf, "DESKDOCK\n"); + case ATTACHED_DEV_AUDIODOCK_MUIC: + return sprintf(buf, "AUDIODOCK\n"); + case ATTACHED_DEV_CHARGING_CABLE_MUIC: + return sprintf(buf, "PS CABLE\n"); + case ATTACHED_DEV_AFC_CHARGER_5V_MUIC: + case ATTACHED_DEV_AFC_CHARGER_9V_MUIC: + case ATTACHED_DEV_AFC_CHARGER_DISABLED_MUIC: + case ATTACHED_DEV_QC_CHARGER_5V_MUIC: + case ATTACHED_DEV_QC_CHARGER_9V_MUIC: + return sprintf(buf, "AFC Charger\n"); + case ATTACHED_DEV_FACTORY_UART_MUIC: + return sprintf(buf, "FACTORY UART\n"); + case ATTACHED_DEV_POGO_DOCK_MUIC: + return sprintf(buf, "POGO Dock\n"); + case ATTACHED_DEV_POGO_DOCK_5V_MUIC: + return sprintf(buf, "POGO Dock/5V\n"); + case ATTACHED_DEV_POGO_DOCK_9V_MUIC: + return sprintf(buf, "POGO Dock/9V\n"); + default: + break; + } + + return sprintf(buf, "UNKNOWN\n"); +} + +static ssize_t muic_sysfs_audio_path_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("%s\n", __func__); + return 0; +} + +static ssize_t muic_sysfs_set_audio_path(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + + pr_info("%s\n", __func__); + MUIC_PDATA_VOID_FUNC(pdata->sysfs_cb.set_audio_path, pdata->drv_data); + + return count; +} + +static ssize_t muic_sysfs_apo_factory_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + const char *mode; + + /* true: Factory mode, false: not Factory mode */ + if (pdata->is_factory_start) + mode = "FACTORY_MODE"; + else + mode = "NOT_FACTORY_MODE"; + + pr_info("%s : %s\n", + __func__, mode); + + return sprintf(buf, "%s\n", mode); +} + +static ssize_t muic_sysfs_set_apo_factory(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + + pr_info("%s\n", __func__); + /* "FACTORY_START": factory mode */ + if (!strncmp(buf, "FACTORY_START", 13)) { + pdata->is_factory_start = true; + MUIC_PDATA_VOID_FUNC(pdata->sysfs_cb.set_apo_factory, + pdata->drv_data); + } else + pr_info("%s Wrong command\n", __func__); + + return count; +} + +static ssize_t muic_sysfs_vbus_value_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + int val = 0; + + pr_info("%s\n", __func__); + MUIC_PDATA_FUNC(pdata->sysfs_cb.get_vbus_value, pdata->drv_data, &val); + + switch (val) { + case 0 ... 3: + val = 0; + break; + case 4 ... 6: + val = 5; + break; + case 7 ... 9: + val = 9; + break; + } + pr_info("%s val=%d-\n", __func__, val); + return sprintf(buf, "%d\n", val); +} + +#if IS_ENABLED(CONFIG_MUIC_HV) +static ssize_t muic_sysfs_afc_disable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + + pr_info("%s\n", __func__); + if (pdata->afc_disable) { + pr_info("%s AFC DISABLE\n", __func__); + return sprintf(buf, "1\n"); + } + + pr_info("%s AFC ENABLE", __func__); + return sprintf(buf, "0\n"); +} + +static ssize_t muic_sysfs_set_afc_disable(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + bool curr_val = pdata->afc_disable; + int ret = 0; + int param_val = 0; +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + union power_supply_propval psy_val; +#endif + + pr_info("%s+\n", __func__); + if (!strncasecmp(buf, "1", 1)) { + pdata->afc_disable = true; + muic_afc_request_cause_clear(); + } else if (!strncasecmp(buf, "0", 1)) { + pdata->afc_disable = false; + } else { + pr_warn("%s invalid value\n", __func__); + ret = -EINVAL; + goto err; + } + + param_val = pdata->afc_disable ? '1' : '0'; + +#if IS_BUILTIN(CONFIG_MUIC_NOTIFIER) +#if defined(CONFIG_USB_DWC3_MSM) + ret = sec_set_param(param_index_afc_disable, ¶m_val); +#else + ret = sec_set_param(CM_OFFSET + 1, (char)param_val); +#endif + if ((!IS_ENABLED(CONFIG_USB_DWC3_MSM) && ret < 0) || + (IS_ENABLED(CONFIG_USB_DWC3_MSM) && ret == false)) { + pr_err("%s:set_param failed - %02x:%02x(%d)\n", __func__, + param_val, curr_val, ret); + + pdata->afc_disable = curr_val; + ret = -EIO; + goto err; + } +#endif + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + psy_val.intval = param_val; + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_HV_DISABLE, psy_val); +#endif + + pr_info("%s afc_disable(%d)\n", __func__, pdata->afc_disable); + + if (curr_val != pdata->afc_disable) + MUIC_PDATA_VOID_FUNC(pdata->sysfs_cb.set_afc_disable, pdata->drv_data); + + pr_info("%s ret=%d-\n", __func__, ret); + return count; +err: + pr_info("%s ret=%d-\n", __func__, ret); + return ret; +} + +static ssize_t muic_sysfs_set_afc_set_voltage(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + int ret = 0; + + pr_info("%s+\n", __func__); + if (!strncasecmp(buf, "5V", 2)) { + MUIC_PDATA_FUNC_MULTI_PARAM(pdata->sysfs_cb.afc_set_voltage, + pdata->drv_data, 5, &ret); + } else if (!strncasecmp(buf, "9V", 2)) { + MUIC_PDATA_FUNC_MULTI_PARAM(pdata->sysfs_cb.afc_set_voltage, + pdata->drv_data, 9, &ret); + } else { + pr_warn("%s invalid value : %s\n", __func__, buf); + } + + pr_info("%s ret=%d-\n", __func__, ret); + return count; +} +#endif /* CONFIG_MUIC_HV */ + +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) +static ssize_t hiccup_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + int ret; + + pr_info("%s+\n", __func__); + MUIC_PDATA_FUNC(pdata->sysfs_cb.get_hiccup, + pdata->drv_data, &ret); + + pr_info("%s ret=%d-\n", __func__, ret); + if (ret) + return sprintf(buf, "ENABLE\n"); + else + return sprintf(buf, "DISABLE\n"); +} + +static ssize_t hiccup_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct muic_platform_data *pdata = dev_get_drvdata(dev); + int ret = 0; + + pr_info("%s+\n", __func__); + if (!strncasecmp(buf, "DISABLE", 7)) { +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) && IS_ENABLED(CONFIG_HICCUP_CC_DISABLE) + usbpd_cc_control_command(pdata->man, 0); +#endif + MUIC_PDATA_FUNC_MULTI_PARAM(pdata->sysfs_cb.set_hiccup, + pdata->drv_data, MUIC_DISABLE, &ret); + } else { + pr_warn("%s invalid com : %s\n", __func__, buf); + } + + pr_info("%s ret=%d-\n", __func__, ret); + return count; +} +#endif /* CONFIG_HICCUP_CHARGER */ + +static DEVICE_ATTR(uart_en, 0664, muic_sysfs_uart_en_show, + muic_sysfs_set_uart_en); +static DEVICE_ATTR(uart_sel, 0664, muic_sysfs_uart_sel_show, + muic_sysfs_set_uart_sel); +static DEVICE_ATTR(usb_en, 0664, + muic_sysfs_usb_en_show, + muic_sysfs_set_usb_en); +static DEVICE_ATTR(usb_sel, 0664, muic_sysfs_usb_sel_show, + muic_sysfs_set_usb_sel); +static DEVICE_ATTR(adc, 0444, muic_sysfs_adc_show, NULL); + +#if IS_ENABLED(DEBUG_MUIC) +static DEVICE_ATTR(mansw, 0444, muic_sysfs_mansw_show, NULL); +static DEVICE_ATTR(dump_registers, 0444, muic_sysfs_registers_show, NULL); +static DEVICE_ATTR(int_status, 0444, muic_sysfs_interrupt_status_show, NULL); +#endif +static DEVICE_ATTR(usb_state, 0444, muic_sysfs_usb_state_show, NULL); +#if IS_ENABLED(CONFIG_USB_HOST_NOTIFY) +static DEVICE_ATTR(otg_test, 0664, + muic_sysfs_otg_test_show, muic_sysfs_set_otg_test); +#endif +static DEVICE_ATTR(attached_dev, 0444, muic_sysfs_attached_dev_show, NULL); +static DEVICE_ATTR(audio_path, 0664, + muic_sysfs_audio_path_show, muic_sysfs_set_audio_path); +static DEVICE_ATTR(apo_factory, 0664, + muic_sysfs_apo_factory_show, + muic_sysfs_set_apo_factory); +static DEVICE_ATTR(vbus_value, 0444, muic_sysfs_vbus_value_show, NULL); +#if IS_ENABLED(CONFIG_MUIC_HV) +static DEVICE_ATTR(afc_disable, 0664, + muic_sysfs_afc_disable_show, muic_sysfs_set_afc_disable); +static DEVICE_ATTR(afc_set_voltage, 0220, + NULL, muic_sysfs_set_afc_set_voltage); +#endif /* CONFIG_MUIC_HV */ +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) +static DEVICE_ATTR_RW(hiccup); +#endif /* CONFIG_HICCUP_CHARGER */ + +static struct attribute *muic_sysfs_attributes[] = { + &dev_attr_uart_en.attr, + &dev_attr_uart_sel.attr, + &dev_attr_usb_en.attr, + &dev_attr_usb_sel.attr, + &dev_attr_adc.attr, +#if IS_ENABLED(DEBUG_MUIC) + &dev_attr_mansw.attr, + &dev_attr_dump_registers.attr, + &dev_attr_int_status.attr, +#endif + &dev_attr_usb_state.attr, +#if IS_ENABLED(CONFIG_USB_HOST_NOTIFY) + &dev_attr_otg_test.attr, +#endif + &dev_attr_attached_dev.attr, + &dev_attr_audio_path.attr, + &dev_attr_apo_factory.attr, + &dev_attr_vbus_value.attr, +#if IS_ENABLED(CONFIG_MUIC_HV) + &dev_attr_afc_disable.attr, + &dev_attr_afc_set_voltage.attr, +#endif /* CONFIG_MUIC_HV */ +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) + &dev_attr_hiccup.attr, +#endif /* CONFIG_HICCUP_CHARGER */ + NULL +}; + +static const struct attribute_group muic_sysfs_group = { + .attrs = muic_sysfs_attributes, +}; + +int muic_sysfs_init(struct muic_platform_data *pdata) +{ + int ret; + + mutex_init(&pdata->sysfs_mutex); + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + if (pdata->switch_device == NULL) + pdata->switch_device = switch_device; + + ret = sysfs_create_group(&pdata->switch_device->kobj, &muic_sysfs_group); + if (ret) { + pr_err("failed to create sysfs\n"); + return ret; + } + dev_set_drvdata(pdata->switch_device, pdata); +#endif + + return ret; +} +EXPORT_SYMBOL_GPL(muic_sysfs_init); + +void muic_sysfs_deinit(struct muic_platform_data *pdata) +{ + if (pdata->switch_device) + sysfs_remove_group(&pdata->switch_device->kobj, &muic_sysfs_group); +} +EXPORT_SYMBOL_GPL(muic_sysfs_deinit); + diff --git a/drivers/net/dropdump/Kconfig b/drivers/net/dropdump/Kconfig new file mode 100755 index 000000000000..d0f8d002044b --- /dev/null +++ b/drivers/net/dropdump/Kconfig @@ -0,0 +1,8 @@ +# +# net: logging dropped skb +# + +comment "Enable Dropdump to logging dropped network packet" + +config SUPPORT_DROPDUMP + tristate "Network packet drop logging service for tcpdump" diff --git a/drivers/net/dropdump/Makefile b/drivers/net/dropdump/Makefile new file mode 100755 index 000000000000..a1e07ee00266 --- /dev/null +++ b/drivers/net/dropdump/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the dropdump driver +# + +obj-$(CONFIG_SUPPORT_DROPDUMP) += dropdump.o diff --git a/drivers/net/dropdump/dropdump.c b/drivers/net/dropdump/dropdump.c new file mode 100755 index 000000000000..0cb9d3e62de0 --- /dev/null +++ b/drivers/net/dropdump/dropdump.c @@ -0,0 +1,989 @@ +/* + * Monitoring code for network dropped packet alerts + * + * Copyright (C) 2018 SAMSUNG Electronics, Co,LTD + */ + +#include +#include +#if defined(CONFIG_ANDROID_VENDOR_HOOKS) +#include +#endif +#include +#include + +int debug_drd = 0; +#define drd_info(fmt, ...) pr_info("drd: %s: " pr_fmt(fmt), __func__, ##__VA_ARGS__) +#define drd_dbg(flag, ...) \ +do { \ + if (unlikely(debug_drd & flag)) { drd_info(__VA_ARGS__); } \ + else {} \ +} while (0) + +DEFINE_RATELIMIT_STATE(drd_ratelimit_print, 1 * HZ, 10); +#define drd_limit(...) \ +do { \ + if (__ratelimit(&drd_ratelimit_print)) \ + drd_info(__VA_ARGS__); \ +} while (0) + +DEFINE_RATELIMIT_STATE(drd_ratelimit_pkt, 1 * HZ, 32); + +struct list_head ptype_log __read_mostly; +EXPORT_SYMBOL_GPL(ptype_log); + +int support_dropdump; +EXPORT_SYMBOL_GPL(support_dropdump); + +extern struct list_head ptype_all; + +struct st_item hmap[DRD_HSIZE]; +spinlock_t hlock; +u64 hmap_count; +u64 hdup_count; +uint hmax_depth; +u16 skip_count; +u64 dropped_count; + +#ifdef DRD_WQ +struct _drd_worker drd_worker; + +unsigned int budget_default = BUDGET_DEFAULT; +unsigned int budget_limit; + +#define BUDGET_MAX (budget_default << 2) +#define LIST_MAX (BUDGET_MAX << 2) +#endif + +void init_st_item(struct st_item *item) +{ + INIT_LIST_HEAD(&item->list); + item->p = 0; + item->matched = 0; + item->st[0] = '\n'; +} + +int get_hkey(u64 *hvalue) +{ + u64 key = 0; + u64 src = *hvalue & 0x00000000ffffffff; + + while (src) { + key += src & 0x000000ff; + src >>= 8; + } + key %= DRD_HSIZE; + + return (int)key; +} + +char *get_hmap(u64 *hvalue) +{ + int hkey = get_hkey(hvalue); + struct st_item *lookup = &hmap[hkey]; + uint depth = 1; + + do { + drd_dbg(DEBUG_HASH, "hvalue search[%d]: <%pK|%pK|%pK> p:[%llx], hvalue:{%llx}\n", + hkey, lookup, lookup->list.next, &hmap[hkey], lookup->p, *hvalue); + if (lookup->p == *hvalue) { + drd_dbg(DEBUG_HASH, "hvalue found: '%s'\n", lookup->st); + if (lookup->matched < 0xffffffffffffffff) + lookup->matched++; + + if (depth >=3 && lookup->matched > ((struct st_item *)hmap[hkey].list.next)->matched) { + // simply reorder the list by matched count, except the hmap array head + list_del(&lookup->list); + __list_add(&lookup->list, &hmap[hkey].list, hmap[hkey].list.next); + } + + return lookup->st; + } + lookup = (struct st_item *)lookup->list.next; + if (hmax_depth < ++depth) + hmax_depth = depth; + } while (lookup != &hmap[hkey]); + + drd_dbg(DEBUG_HASH, "hvalue not found\n"); + return NULL; +} + +char *set_hmap(u64 *hvalue) +{ + int hkey = get_hkey(hvalue); + struct st_item *newItem = NULL; + bool first_hit = false; + + drd_dbg(DEBUG_HASH, "hvalue {%d}: <%llx> %llx\n", hkey, *hvalue, hmap[hkey].p); + if (hmap[hkey].p == 0) { + newItem = &hmap[hkey]; + first_hit = true; + } else { + newItem = kmalloc(sizeof(struct st_item), GFP_ATOMIC); + if (newItem == NULL) { + drd_dbg(DEBUG_HASH, "fail to alloc\n"); + spin_unlock_bh(&hlock); + return NULL; + } + + init_st_item(newItem); + list_add_tail(&newItem->list, &hmap[hkey].list); + hdup_count++; + } + + newItem->p = *hvalue; + hmap_count++; + spin_unlock_bh(&hlock); + + snprintf(newItem->st, ST_SIZE, "%pS", (void *)*hvalue); + drd_dbg(DEBUG_HASH, "{%d:%d} <%pK> '%s'\n", hkey, first_hit, hvalue, newItem->st); + + return newItem->st; +} + +/* use direct call instead of recursive stack trace */ +u64 __stack(int depth) +{ + u64 *func = NULL; + switch (depth + ST_START) { + case 3 : + func = __builtin_return_address(3); + break; + case 4 : + func = __builtin_return_address(4); + break; + case 5 : + func = __builtin_return_address(5); + break; + case 6 : + func = __builtin_return_address(6); + break; + case 7 : + func = __builtin_return_address(7); + break; + case 8 : + func = __builtin_return_address(8); + break; + case 9 : + func = __builtin_return_address(9); + break; + case 10 : + func = __builtin_return_address(10); + break; + case 11 : + func = __builtin_return_address(11); + break; + case 12 : + func = __builtin_return_address(12); + break; + case 13 : + func = __builtin_return_address(13); + break; + case 14 : + func = __builtin_return_address(14); + break; + case 15 : + func = __builtin_return_address(15); + break; + case 16 : + func = __builtin_return_address(16); + break; + case 17 : + func = __builtin_return_address(17); + break; + case 18 : + func = __builtin_return_address(18); + break; + case 19 : + func = __builtin_return_address(19); + break; + case 20 : + func = __builtin_return_address(20); + break; + case 21 : + func = __builtin_return_address(21); + break; + case 22 : + func = __builtin_return_address(22); + break; + case 23 : + func = __builtin_return_address(23); + break; + case 24 : + func = __builtin_return_address(24); + break; + case 25 : + func = __builtin_return_address(25); + break; + default : + return 0; + } + + return (u64)func; +} + + +#define NOT_TRACE (0xDD) +#define FIN_TRACE 1 +#define ACK_TRACE 2 +#define GET_TRACE 3 + +int chk_stack(char *pos, int net_pkt) +{ + /* stop tracing */ + if (!strncmp(pos + 4, "f_nbu", 5))// __qdf_nbuf_free + return NOT_TRACE; + if (!strncmp(pos, "unix", 4)) // unix_xxx + return NOT_TRACE; + if (!strncmp(pos + 2, "tlin", 4)) // netlink_xxx + return NOT_TRACE; + if (!strncmp(pos, "tpac", 4)) // tpacket_rcv + return NOT_TRACE; + if (!strncmp(pos, "drd", 3)) // drd_xxx + return NOT_TRACE; + if (!strncmp(pos + 1, "_sk_d", 5))// __sk_destruct + return NOT_TRACE; +#ifdef EXTENDED_DROPDUMP + /* ignore normally consumed packets on TX path */ + if (!strncmp(pos + 2, "it_on", 5))// xmit_one + return NOT_TRACE; + if (!strncmp(pos + 2, "t_tx_", 5))// net_tx_action + return NOT_TRACE; + if (!strncmp(pos, "dp_tx", 5)) //dp_tx_comp_xxx + return NOT_TRACE; + /* prevent recursive call by __kfree_skb() */ + if (!strncmp(pos + 4, "ree_s", 5))// __kfree_skb + return NOT_TRACE; +#endif + + /* end of callstack */ + if (!strncmp(pos, "loc", 3))// local_* + return FIN_TRACE; + if (!strncmp(pos + 7, "ftir", 4))// __do_softirq + return FIN_TRACE; + if (!strncmp(pos + 7, "rk_r", 4))// task_work_run + return FIN_TRACE; + if (!strncmp(pos, "SyS", 3)) // SyS_xxx + return FIN_TRACE; + if (!strncmp(pos, "ret_", 4)) // ret_from_xxx + return FIN_TRACE; + if (!strncmp(pos, "el", 2)) // el* + return FIN_TRACE; + if (!strncmp(pos, "gic", 3)) // gic_xxx + return FIN_TRACE; + if (!strncmp(pos + 3, "rt_ke", 5))// start_kernel + return FIN_TRACE; + if (!strncmp(pos + 13, "rt_ke", 5))// secondary_start_kernel + return FIN_TRACE; + + /* network pkt */ + if (!net_pkt) { + if (!strncmp(pos, "net", 3)) + return GET_TRACE; + if (!strncmp(pos, "tcp", 3)) { + // packet from tcp_drop() could be normal operation. + // don't logging pure ack. + if (!strncmp(pos, "tcp_drop", 8)) + return ACK_TRACE; + return GET_TRACE; + } + if (!strncmp(pos, "ip", 2)) + return GET_TRACE; + if (!strncmp(pos, "icmp", 4)) + return GET_TRACE; + if (!strncmp(pos, "xfr", 3)) + return GET_TRACE; + } + + return 0; +} + + +static bool _is_tcp_ack(struct sk_buff *skb) +{ + switch (skb->protocol) { + /* TCPv4 ACKs */ + case htons(ETH_P_IP): + if ((ip_hdr(skb)->protocol == IPPROTO_TCP) && + (ntohs(ip_hdr(skb)->tot_len) - (ip_hdr(skb)->ihl << 2) == + tcp_hdr(skb)->doff << 2) && + ((tcp_flag_word(tcp_hdr(skb)) & + cpu_to_be32(0x00FF0000)) == TCP_FLAG_ACK)) + return true; + break; + + /* TCPv6 ACKs */ + case htons(ETH_P_IPV6): + if ((ipv6_hdr(skb)->nexthdr == IPPROTO_TCP) && + (ntohs(ipv6_hdr(skb)->payload_len) == + (tcp_hdr(skb)->doff) << 2) && + ((tcp_flag_word(tcp_hdr(skb)) & + cpu_to_be32(0x00FF0000)) == TCP_FLAG_ACK)) + return true; + break; + } + + return false; +} + +static inline bool is_tcp_ack(struct sk_buff *skb) +{ + if (skb_is_tcp_pure_ack(skb)) + return true; + + if (unlikely(_is_tcp_ack(skb))) + return true; + + return false; +} + +int symbol_lookup(u64 *addr, int net_pkt) { + char *symbol = NULL; + + spin_lock_bh(&hlock); + symbol = get_hmap(addr); + + if (symbol != NULL) + spin_unlock_bh(&hlock); + else + symbol = set_hmap(addr); + + memcpy((char *)addr, symbol, strlen(symbol)); + return chk_stack(symbol, net_pkt); +} + +u8 get_stack(struct sk_buff *skb, struct sk_buff *dmy, unsigned int offset, unsigned int reason) +{ + u8 depth = 0, max_depth = ST_MAX; + struct _dmy_info *dmy_info = (struct _dmy_info *)(dmy->data + offset); + u64 *stack_base = &dmy_info->stack; + +#ifdef DRD_WQ + // sometimes __builtin_return_address() returns invalid address for deep stack of + // ksoftirq or kworker, and migration task. limit the maximun depth for them. + if ((current->comm[0] == 'k' && (current->comm[4] == 't' || current->comm[4] == 'k')) || + (current->comm[0] == 'm' && current->comm[4] == 'a')) { + dmy_info->flag |= LIMIT_DEPTH_BIT; + max_depth >>= 1; + } +#else + int chk = 0, net_pkt = 0; +#endif + + if (skb->tstamp >> 48 < 5000) { + // packet has kernel timestamp, not utc. + // using a zero-value for updating to utc at tpacket_rcv() + dmy_info->flag |= UPDATE_TIME_BIT; + dmy->tstamp = 0; + } else { + // using utc of original packet + dmy->tstamp = skb->tstamp; + } + + drd_dbg(DEBUG_SAVE, "trace <%pK>\n", skb); + for (depth = 0; depth < max_depth; depth++) { + *stack_base = __stack(depth); +#ifdef DRD_WQ + drd_dbg(DEBUG_SAVE, "%02d: <%pK>\n", depth, (u64 *)*stack_base); + if (*stack_base == 0) { + // __builtin_return_address() returned root stack + depth--; + break; + } +#else + /* functions that instead of when set_stack_work not used */ + chk = symbol_lookup(stack_base, net_pkt); + drd_dbg(DEBUG_SAVE, "[%2d:%d] <%s>\n", depth, chk, (char *)stack_base); + + if (chk == NOT_TRACE) { + drd_dbg(DEBUG_TRACE, "not target stack\n"); + return NOT_TRACE; + } + + if (chk == FIN_TRACE) + break; + + if (chk == ACK_TRACE) { + if (is_tcp_ack(skb)) { + drd_dbg(DEBUG_TRACE, "don't trace tcp ack\n"); + return NOT_TRACE; + } else { + net_pkt = 1; + } + } + + if (chk == GET_TRACE) + net_pkt = 1; +#endif + + stack_base += (ST_SIZE / sizeof(u64)); + } + + memcpy(dmy_info->magic, "DRD", 3); + dmy_info->depth = depth; + if (skip_count > 0) { + dmy_info->skip_count = skip_count; + skip_count = 0; + } + dmy_info->count = ++dropped_count; + dmy_info->reason_id = reason; + if (reason < DRD_REASON_MAX) { + memcpy(dmy_info->reason_str, drd_reasons[reason], min(16, (int)strlen(drd_reasons[reason]))); + } else { + memcpy(dmy_info->reason_str, "UNDEFINED_REASON", 16); + } + + drd_dbg(DEBUG_RAW, "<%pK:%pK> %*ph\n", dmy, dmy_info, 16, dmy_info); + + return depth; +} + +int set_stack_work(struct sk_buff *skb, struct _dmy_info *dmy_info) +{ + int chk = 0, net_pkt = 0; + u8 depth; + u64 *stack_base; + + drd_dbg(DEBUG_RAW, "<%pK:%pK> %*ph\n", skb, dmy_info, 16, dmy_info); + + if (strncmp(dmy_info->magic, "DRD", 3)) { + drd_dbg(DEBUG_TRACE, "invalid magic <%pK>\n", skb); + return -1; + } + + stack_base = &dmy_info->stack; + for (depth = 0; depth < dmy_info->depth; depth++) { + chk = symbol_lookup(stack_base, net_pkt); + drd_dbg(DEBUG_RESTORE, "[%2d:%d] <%s>\n", depth, chk, (char *)stack_base); + + if (chk == NOT_TRACE) { + drd_dbg(DEBUG_TRACE, "not target stack\n"); + return NOT_TRACE; + } + + if (chk == FIN_TRACE) + break; + + if (chk == ACK_TRACE) { + if (is_tcp_ack(skb)) { + drd_dbg(DEBUG_TRACE, "don't trace tcp ack\n"); + return NOT_TRACE; + } else { + net_pkt = 1; + } + } + + if (chk == GET_TRACE) + net_pkt = 1; + + stack_base += (ST_SIZE / sizeof(u64)); + } + + if (net_pkt == 0) { + drd_dbg(DEBUG_TRACE, "not defined packet\n"); + return -3; + } + + return depth; +} + +#ifdef DRD_WQ +static void save_pkt_work(struct work_struct *ws) +{ + struct sk_buff *skb, *next; + struct packet_type *ptype = NULL; + struct _dmy_info *dmy_info = NULL; + int st_depth = 0; + u16 budget = 0; + + list_for_each_entry_safe(skb, next, &drd_worker.list, list) { + spin_lock_bh(&drd_worker.lock); + if (support_dropdump) { + list_for_each_entry_rcu(ptype, &ptype_log, list) { + if (ptype != NULL) + break; + } + + drd_dbg(DEBUG_LOG, "del %u:%llu <%llx>\n", budget, drd_worker.num, (u64)(skb)); + skb_list_del_init(skb); + drd_worker.num--; + } else { + spin_unlock_bh(&drd_worker.lock); + return; + } + spin_unlock_bh(&drd_worker.lock); + + if (ptype == NULL || list_empty(&ptype->list)) { + drd_dbg(DEBUG_LOG,"pt list not ready\n"); + __kfree_skb(skb); + continue; + } + + dmy_info = (struct _dmy_info *)(skb->data + PKTINFO_OFFSET(skb)); + + st_depth = set_stack_work(skb, dmy_info); + if (st_depth != NOT_TRACE) { + ptype->func(skb, skb->dev, ptype, skb->dev); + } else { + __kfree_skb(skb); + } + + if (++budget >= budget_limit) + break; + } + + if (!list_empty(&drd_worker.list)) { + if (budget_limit < BUDGET_MAX) + budget_limit <<= 1; + queue_delayed_work(drd_worker.wq, &drd_worker.dwork, msecs_to_jiffies(1)); + drd_dbg(DEBUG_LOG, "pkt remained(%llu), trigger again. budget:%d\n", drd_worker.num, budget_limit); + } else { + drd_worker.num = 0; + } + + return; +} +#else +void save_pkt(struct sk_buff *skb) +{ + struct packet_type *ptype = NULL; + + rcu_read_lock(); + list_for_each_entry_rcu(ptype, &ptype_log, list) { + if (ptype != NULL) + break; + } + + if (ptype == NULL || list_empty(&ptype->list)) { + drd_dbg(DEBUG_LOG,"pt list not ready\n"); + __kfree_skb(skb); + goto out; + } + + drd_dbg(DEBUG_LOG, "%llu <%llx>\n", dropped_count, (u64)(skb)); + ptype->func(skb, skb->dev, ptype, skb->dev); +out: + rcu_read_unlock(); +} +#endif + +int skb_validate(struct sk_buff *skb) +{ + if (virt_addr_valid(skb) && virt_addr_valid(skb->dev)) { + struct iphdr *ip4hdr = (struct iphdr *)skb_network_header(skb); + + if (skb->protocol != htons(ETH_P_IPV6) + && skb->protocol != htons(ETH_P_IP)) + return -1; + + switch (skb->dev->name[0]) { + case 'r': // rmnet* + case 'v': // v4-rmnet* + case 't': // tun + case 'e': // epdg + break; + case 'l': // lo + case 'b': // bt* + case 'w': // wlan + case 's': // swlan + if (__ratelimit(&drd_ratelimit_pkt)) + break; + if (skip_count < 0xffff) + skip_count++; + dropped_count++; + return -9; + default: + drd_dbg(DEBUG_LOG, "invalid dev: %s\n", skb->dev->name); + return -2; + } + + if (unlikely((ip4hdr->version != 4 && ip4hdr->version != 6) + || ip4hdr->id == 0x6b6b)) + return -3; + + if (unlikely(!skb->len)) + return -4; + + if (unlikely(skb->len > skb->tail)) + return -5; + + if (unlikely(skb->data <= skb->head)) + return -6; + + if (unlikely(skb->tail > skb->end)) + return -7; + + if (unlikely(skb->pkt_type == PACKET_LOOPBACK)) + return -8; + + drd_dbg(DEBUG_RAW, "ndev: %s\n", skb->dev->name); + return 0; + } + + return -255; +} + + +struct sk_buff *get_dummy(struct sk_buff *skb, unsigned int reason)//, char *pos, int st_depth) +{ + struct sk_buff *dummy = NULL; + struct skb_shared_info *shinfo; + unsigned int copy_len = PKTINFO_COPYLEN_MAX; + unsigned int copy_buf_len = PKTINFO_COPYLEN_MAX; + unsigned int org_len, dummy_len; + u8 ret = 0; + + struct iphdr *ip4hdr = (struct iphdr *)(skb_network_header(skb)); + struct ipv6hdr *ip6hdr; + + if (ip4hdr->version == 4) { + org_len = ntohs(ip4hdr->tot_len); + } else { + ip6hdr = (struct ipv6hdr *)ip4hdr; + org_len = skb_network_header_len(skb) + ntohs(ip6hdr->payload_len); + } + + if (org_len < PKTINFO_COPYLEN_MAX) { + copy_len = org_len; + copy_buf_len = round_up(org_len, 0x10); + } + + dummy_len = copy_buf_len + sizeof(struct _dmy_info) + ST_BUF_SIZE; + + dummy = alloc_skb(dummy_len, GFP_ATOMIC); + if (unlikely(!dummy)) { + drd_dbg(DEBUG_LOG, "alloc fail, %u\n", dummy_len); + return NULL; + } + + drd_dbg(DEBUG_SAVE, "skb->len:%u org_len:%u copy_len:%u copy_buf_len:%u dummy_len:%u\n", + skb->len, org_len, copy_len, copy_buf_len, dummy_len); + + dummy->dev = skb->dev; + dummy->protocol = skb->protocol; + dummy->ip_summed = CHECKSUM_UNNECESSARY; + + refcount_set(&skb->users, 1); + + skb_put(dummy, dummy_len); + skb_reset_mac_header(dummy); + skb_reset_network_header(dummy); + skb_set_transport_header(dummy, skb_network_header_len(skb)); + + shinfo = skb_shinfo(dummy); + memset(shinfo, 0, sizeof(struct skb_shared_info)); + atomic_set(&shinfo->dataref, 1); + + INIT_LIST_HEAD(&dummy->list); + + memcpy(dummy->data, skb_network_header(skb), copy_len); + memset((void *)((u64)dummy->data + (u64)copy_len), 0, + 0x10 - (copy_len % 0x10) + sizeof(struct _dmy_info) + ST_BUF_SIZE); + + ret = get_stack(skb, dummy, copy_buf_len, reason); + if (ret != NOT_TRACE) { + PKTINFO_OFFSET(dummy) = copy_buf_len; + } else { + drd_dbg(DEBUG_SAVE, "not saving pkt\n"); + __kfree_skb(dummy); + return NULL; + } + + return dummy; +} + +void drd_kfree_skb(struct sk_buff *skb, unsigned int reason) +{ + struct sk_buff *dmy; +#ifdef DRD_WQ + struct sk_buff *next; +#endif + + if (support_dropdump < 2) { +#ifdef DRD_WQ + if (drd_worker.num) { + drd_dbg(DEBUG_LOG, "purge drd list\n"); + cancel_delayed_work(&drd_worker.dwork); + spin_lock_bh(&drd_worker.lock); + list_for_each_entry_safe(dmy, next, &drd_worker.list, list) { + skb_list_del_init(dmy); + __kfree_skb(dmy); + } + drd_worker.num = 0; + spin_unlock_bh(&drd_worker.lock); + } +#endif + return; + } + + if (skb_validate(skb)) + return; + +#ifdef DRD_WQ + if (unlikely(drd_worker.num >= LIST_MAX - 1)) { + drd_dbg(DEBUG_LOG, "drd list full\n"); + return; + } +#endif + + dmy = get_dummy(skb, reason); + if (dmy == NULL) + return; + +#ifdef DRD_WQ + spin_lock_bh(&drd_worker.lock); + + if (support_dropdump) { + list_add_tail(&dmy->list, &drd_worker.list); + drd_worker.num++; + drd_dbg(DEBUG_LOG, "add %llu <%pK>\n", drd_worker.num, dmy); + } + spin_unlock_bh(&drd_worker.lock); + + budget_limit = budget_default; + queue_delayed_work(drd_worker.wq, &drd_worker.dwork, 0); +#else + save_pkt(dmy); +#endif + +} +EXPORT_SYMBOL_GPL(drd_kfree_skb); + +void drd_ptype_head(const struct packet_type *pt, struct list_head *vendor_pt) +{ + if (pt->type == htons(ETH_P_LOG)) + vendor_pt->next = &ptype_log; +} +EXPORT_SYMBOL_GPL(drd_ptype_head); + +#if defined(CONFIG_ANDROID_VENDOR_HOOKS) +static void drd_ptype_head_handler(void *data, const struct packet_type *pt, struct list_head *vendor_pt) +{ + drd_ptype_head(pt, vendor_pt); +} +#else +/* can't use macro directing the drd_xxx functions instead of lapper. * + * because of have to use EXPORT_SYMBOL macro for module parts. * + * it should to be used at here with it's definition */ +void trace_android_vh_ptype_head(const struct packet_type *pt, struct list_head *vendor_pt) +{ + drd_ptype_head(pt, vendor_pt); +} +EXPORT_SYMBOL_GPL(trace_android_vh_ptype_head); +#endif + +#if defined(TRACE_SKB_DROP_REASON) || defined(DEFINE_DROP_REASON) +static void drd_kfree_skb_handler(void *data, struct sk_buff *skb, + void *location, enum skb_drop_reason reason) +{ +#else +static void drd_kfree_skb_handler(void *data, struct sk_buff *skb, void *location) +{ + unsigned int reason = 0; +#endif + drd_kfree_skb(skb, (unsigned int)reason); +} + +struct kobject *drd_kobj; +int get_attr_input(const char *buf, int *val) +{ + int ival; + int err = kstrtoint(buf, 0, &ival); + if (err >= 0) + *val = ival; + else + drd_info("invalid input: %s\n", buf); + return err; +} + +static ssize_t hstat_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sprintf(buf, + "stack : total %d, used %lld, dupplicated %llu, max_depth %u, dropped %llu\n", + DRD_HSIZE, hmap_count, hdup_count, hmax_depth, dropped_count); +} + + +static struct kobj_attribute hstat_attribute = { + .attr = {.name = "hstat", .mode = 0660}, + .show = hstat_show, +}; + +static ssize_t hmap_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int i; + struct st_item *lookup; + + for (i = 0; i < DRD_HSIZE; i++) { + lookup = &hmap[i]; + drd_info("---------------------------------------------------------------------\n"); + do { + drd_info("%03d <%llx:%llu> '%s'\n", i, lookup->p, lookup->matched, lookup->st); + lookup = (struct st_item *)lookup->list.next; + } while (lookup != &hmap[i]); + } + drd_info("---------------------------------------------------------------------\n"); + + return sprintf(buf, "hmap checked\n"); +} + +static struct kobj_attribute hmap_attribute = { + .attr = {.name = "hmap", .mode = 0660}, + .show = hmap_show, +}; + +static ssize_t debug_drd_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sprintf(buf, "current debug_drd: %d (0x%x)\n", debug_drd, debug_drd); +} + +ssize_t debug_drd_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + if (get_attr_input(buf, &debug_drd) >= 0) + drd_info("debug_drd = %d\n", debug_drd); + return count; +} + +static struct kobj_attribute debug_drd_attribute = { + .attr = {.name = "debug_drd", .mode = 0660}, + .show = debug_drd_show, + .store = debug_drd_store, +}; + +#ifdef DRD_WQ +static ssize_t budget_default_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sprintf(buf, "current budget_default: %u\n", budget_default); +} + +ssize_t budget_default_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + if (get_attr_input(buf, &budget_default) >= 0) + drd_info("budget_default = %u\n", budget_default); + return count; +} + +static struct kobj_attribute budget_default_attribute = { + .attr = {.name = "budget_default", .mode = 0660}, + .show = budget_default_show, + .store = budget_default_store, +}; +#endif + +static struct attribute *dropdump_attrs[] = { + &hstat_attribute.attr, + &hmap_attribute.attr, + &debug_drd_attribute.attr, +#ifdef DRD_WQ + &budget_default_attribute.attr, +#endif + NULL, +}; +ATTRIBUTE_GROUPS(dropdump); + +static struct ctl_table drd_proc_table[] = { + { + .procname = "support_dropdump", + .data = &support_dropdump, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec, + }, +#ifdef EXTENDED_DROPDUMP + { + .procname = "support_dropdump_ext", + .data = &support_dropdump, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec, + }, +#endif + { } +}; + +static int __init init_net_drop_dump(void) +{ + int rc = 0, i; + + drd_info("\n"); + + INIT_LIST_HEAD(&ptype_log); + + init_net.core.sysctl_hdr = register_net_sysctl(&init_net, "net/core", drd_proc_table); + if (init_net.core.sysctl_hdr == NULL) { + drd_info("init sysctrl failed\n"); + return -ENODEV; + } + +#if defined(CONFIG_ANDROID_VENDOR_HOOKS) + rc = register_trace_android_vh_ptype_head(drd_ptype_head_handler, NULL); +#endif + rc += register_trace_kfree_skb(drd_kfree_skb_handler, NULL); + if (rc) { + drd_info("fail to register android_trace\n"); + return -EIO; + } + +#ifdef DRD_WQ + drd_worker.wq = create_workqueue("drd_work"); + if (!drd_worker.wq) { + drd_info("fail to create wq\n"); + return -ENOMEM; + } + INIT_DELAYED_WORK(&drd_worker.dwork, save_pkt_work); + INIT_LIST_HEAD(&drd_worker.list); + spin_lock_init(&drd_worker.lock); + drd_worker.num = 0; +#endif + + drd_kobj = kobject_create_and_add("dropdump", kernel_kobj); + if (!drd_kobj) { + drd_info("fail to create kobj\n"); + rc = -ENOMEM; + goto kobj_error; + } + rc = sysfs_create_groups(drd_kobj, dropdump_groups); + if (rc) { + drd_info("fail to create attr\n"); + goto attr_error; + } + + for (i = 0; i < DRD_HSIZE; i++) { + init_st_item(&hmap[i]); + } + spin_lock_init(&hlock); + + support_dropdump = 0; + goto out; + +attr_error: + kobject_put(drd_kobj); + +kobj_error: +#ifdef DRD_WQ + destroy_workqueue(drd_worker.wq); +#endif + +out: + return rc; +} + +static void exit_net_drop_dump(void) +{ + drd_info("\n"); + + support_dropdump = 0; +} + +module_init(init_net_drop_dump); +module_exit(exit_net_drop_dump); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Samsung dropdump module"); + diff --git a/drivers/net/tun.c b/drivers/net/tun.c index 367255bb44cd..70fd37d224f3 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -81,6 +81,19 @@ #include #include +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN { +#ifdef CONFIG_KNOX_NCM +#include +#include +#include +#include +#include + +#define META_MARK_BASE_LOWER 100 +#define META_MARK_BASE_UPPER 500 +#endif +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN } + static void tun_default_link_ksettings(struct net_device *dev, struct ethtool_link_ksettings *cmd); @@ -101,6 +114,18 @@ static void tun_default_link_ksettings(struct net_device *dev, #define GOODCOPY_LEN 128 +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN { +#ifdef CONFIG_KNOX_NCM +struct knox_meta_param { + uid_t uid; + pid_t pid; +}; + +#define TUN_META_HDR_SZ sizeof(struct knox_meta_param) +#define TUN_META_MARK_OFFSET offsetof(struct knox_meta_param, uid) +#endif +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN } + #define FLT_EXACT_COUNT 8 struct tap_filter { unsigned int count; /* Number of addrs. Zero means disabled */ @@ -2046,6 +2071,44 @@ static ssize_t tun_chr_write_iter(struct kiocb *iocb, struct iov_iter *from) return result; } +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN { +#ifdef CONFIG_KNOX_NCM +static int get_meta_param_values(struct sk_buff *skb, + struct knox_meta_param *metalocal) { + + struct skb_shared_info *knox_shinfo = NULL; + + if (skb != NULL) + knox_shinfo = skb_shinfo(skb); + else { +#ifdef TUN_DEBUG + pr_err("KNOX: NULL SKB in knoxvpn_process_uidpid"); +#endif + return 1; + } + + if (knox_shinfo == NULL) { +#ifdef TUN_DEBUG + pr_err("KNOX: knox_shinfo value is null"); +#endif + return 1; + } + + if (knox_shinfo->android_oem_data1[2] >= META_MARK_BASE_LOWER && knox_shinfo->android_oem_data1[2] <= META_MARK_BASE_UPPER) { + metalocal->uid = (uid_t)knox_shinfo->android_oem_data1[0]; + metalocal->pid = (pid_t)knox_shinfo->android_oem_data1[1]; + } + + if (knox_shinfo != NULL) { + knox_shinfo->android_oem_data1[0] = knox_shinfo->android_oem_data1[1] = 0; + knox_shinfo->android_oem_data1[2] = 0; + } + + return 0; +} +#endif +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN } + static ssize_t tun_put_user_xdp(struct tun_struct *tun, struct tun_file *tfile, struct xdp_frame *xdp_frame, @@ -2083,6 +2146,12 @@ static ssize_t tun_put_user(struct tun_struct *tun, struct iov_iter *iter) { struct tun_pi pi = { 0, skb->protocol }; + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN { +#ifdef CONFIG_KNOX_NCM + struct knox_meta_param metalocal = { 0, 0 }; + int meta_param_get_status = 0; +#endif + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN } ssize_t total; int vlan_offset = 0; int vlan_hlen = 0; @@ -2110,6 +2179,33 @@ static ssize_t tun_put_user(struct tun_struct *tun, return -EFAULT; } + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN { +#ifdef CONFIG_KNOX_NCM + meta_param_get_status = get_meta_param_values(skb, &metalocal); + if(meta_param_get_status == 1) { +#ifdef TUN_DEBUG + pr_err("KNOX: Error obtaining meta param values"); +#endif + } else { + + if (tun->flags & TUN_META_HDR) { +#ifdef TUN_DEBUG + pr_err("KNOX: Appending uid: %d and pid: %d", metalocal.uid, + metalocal.pid); +#endif + if (iov_iter_count(iter) < sizeof(struct knox_meta_param)) { + return -EINVAL; + } + + total += sizeof(struct knox_meta_param); + if (copy_to_iter(&metalocal, sizeof(struct knox_meta_param), iter) != sizeof(struct knox_meta_param)) { + return -EFAULT; + } + } + } +#endif + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN } + if (vnet_hdr_sz) { struct virtio_net_hdr gso; @@ -2682,7 +2778,13 @@ static struct proto tun_proto = { static int tun_flags(struct tun_struct *tun) { + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN { +#ifdef CONFIG_KNOX_NCM + return tun->flags & (TUN_FEATURES | IFF_PERSIST | IFF_TUN | IFF_TAP | IFF_META_HDR); +#else return tun->flags & (TUN_FEATURES | IFF_PERSIST | IFF_TUN | IFF_TAP); +#endif + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN } } static ssize_t tun_flags_show(struct device *dev, struct device_attribute *attr, @@ -2858,6 +2960,16 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) else netif_carrier_on(tun->dev); + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN { +#ifdef CONFIG_KNOX_NCM + if (ifr->ifr_flags & IFF_META_HDR) { + tun->flags |= TUN_META_HDR; + } else { + tun->flags &= ~TUN_META_HDR; + } +#endif + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN { + /* Make sure persistent devices do not get stuck in * xoff state. */ @@ -3073,6 +3185,14 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd, int ret; bool do_notify = false; + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN { +#ifdef CONFIG_KNOX_NCM + int knox_flag = 0; + int tun_meta_param; + int tun_meta_value; +#endif + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN } + if (cmd == TUNSETIFF || cmd == TUNSETQUEUE || (_IOC_TYPE(cmd) == SOCK_IOC_TYPE && cmd != SIOCGSKNS)) { if (copy_from_user(&ifr, argp, ifreq_len)) @@ -3085,8 +3205,16 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd, * This is needed because we never checked for invalid flags on * TUNSETIFF. */ - return put_user(IFF_TUN | IFF_TAP | IFF_NO_CARRIER | - TUN_FEATURES, (unsigned int __user*)argp); + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN { +#ifdef CONFIG_KNOX_NCM + knox_flag |= IFF_META_HDR; + return put_user(IFF_TUN | IFF_TAP| IFF_NO_CARRIER | TUN_FEATURES | knox_flag, + (unsigned int __user*)argp); +#else + return put_user(IFF_TUN | IFF_TAP | IFF_NO_CARRIER | TUN_FEATURES, + (unsigned int __user*)argp); +#endif + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN } } else if (cmd == TUNSETQUEUE) { return tun_set_queue(file, &ifr); } else if (cmd == SIOCGSKNS) { @@ -3282,6 +3410,35 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd, ret = -EFAULT; break; + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN { +#ifdef CONFIG_KNOX_NCM + case TUNGETMETAPARAM: + if (copy_from_user(&tun_meta_param, argp, + sizeof(tun_meta_param))) { + ret = -EFAULT; + break; + } + ret = 0; + switch (tun_meta_param) { + case TUN_GET_META_HDR_SZ: + tun_meta_value = TUN_META_HDR_SZ; + break; + case TUN_GET_META_MARK_OFFSET: + tun_meta_value = TUN_META_MARK_OFFSET; + break; + default: + ret = -EINVAL; + break; + } + if (!ret) { + if (copy_to_user(argp, &tun_meta_value, + sizeof(tun_meta_value))) + ret = -EFAULT; + } + break; +#endif + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN } + case TUNSETVNETHDRSZ: if (copy_from_user(&vnet_hdr_sz, argp, sizeof(vnet_hdr_sz))) { ret = -EFAULT; diff --git a/drivers/nfc/nfc_logger/nfc_logger.c b/drivers/nfc/nfc_logger/nfc_logger.c new file mode 100644 index 000000000000..3d868e045c96 --- /dev/null +++ b/drivers/nfc/nfc_logger/nfc_logger.c @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * NFC logger + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 10, 0) +#include +#else +#include +#endif + +#ifdef CONFIG_SEC_NFC_LOGGER_ADD_ACPM_LOG +#include +#include +#include +#endif + +#include "nfc_logger.h" + +#define BUF_SIZE SZ_128K +#define BOOT_LOG_SIZE 2400 +#define MAX_STR_LEN 160 +#define PROC_FILE_NAME "nfclog" +#define LOG_PREFIX "sec-nfc" +#define PRINT_DATE_FREQ 20 + +static char nfc_log_buf[BUF_SIZE]; +static unsigned int g_curpos; +static int is_nfc_logger_init; +static int is_buf_full; +static int g_log_max_count = -1; +static void (*print_nfc_status)(void); +struct proc_dir_entry *g_entry; + +/* set max log count, if count is -1, no limit */ +void nfc_logger_set_max_count(int count) +{ + g_log_max_count = count; +} + +void nfc_logger_get_date_time(char *date_time, int size) +{ + struct timespec64 ts; + struct tm tm; + unsigned long sec; + int len = 0; + + ktime_get_real_ts64(&ts); + sec = ts.tv_sec - (sys_tz.tz_minuteswest * 60); + time64_to_tm(sec, 0, &tm); + len = snprintf(date_time, size, "%02d-%02d %02d:%02d:%02d.%03lu", tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec / 1000000); + +#ifdef CONFIG_SEC_NFC_LOGGER_ADD_ACPM_LOG + snprintf(date_time + len, size - len, ", rtc: %u", date_time, + nfc_logger_acpm_get_rtc_time()); +#endif +} + +void nfc_logger_print(char *fmt, ...) +{ + int len; + va_list args; + char buf[MAX_STR_LEN] = {0, }; + u64 time; + u32 nsec; + unsigned int curpos; + static unsigned int log_count = PRINT_DATE_FREQ; + char date_time[64] = {0, }; + bool date_time_print = false; + + if (!is_nfc_logger_init) + return; + + if (g_log_max_count == 0) + return; + else if (g_log_max_count > 0) + g_log_max_count--; + + if (--log_count == 0) { + nfc_logger_get_date_time(date_time, sizeof(date_time)); + log_count = PRINT_DATE_FREQ; + date_time_print = true; + } + + time = local_clock(); + nsec = do_div(time, 1000000000); + if (g_curpos < BOOT_LOG_SIZE) + len = snprintf(buf, sizeof(buf), "[B%4llu.%06u] ", time, nsec / 1000); + else + len = snprintf(buf, sizeof(buf), "[%5llu.%06u] ", time, nsec / 1000); + + if (date_time_print) + len += snprintf(buf + len, sizeof(buf) - len, "[%s] ", date_time); + + va_start(args, fmt); + len += vsnprintf(buf + len, MAX_STR_LEN - len, fmt, args); + va_end(args); + + if (len > MAX_STR_LEN) + len = MAX_STR_LEN; + + curpos = g_curpos; + if (curpos + len >= BUF_SIZE) { + g_curpos = curpos = BOOT_LOG_SIZE; + is_buf_full = 1; + } + memcpy(nfc_log_buf + curpos, buf, len); + g_curpos += len; +} + +void nfc_logger_register_nfc_stauts_func(void (*print_status_callback)(void)) +{ + print_nfc_status = print_status_callback; +} + +void nfc_print_hex_dump(void *buf, void *pref, size_t size) +{ + uint8_t *ptr = buf; + size_t i; + char tmp[128] = {0x0, }; + char *ptmp = tmp; + int len; + + if (!is_nfc_logger_init) + return; + + if (g_log_max_count == 0) + return; + else if (g_log_max_count > 0) + g_log_max_count--; + + for (i = 0; i < size; i++) { + len = snprintf(ptmp, 4, "%02x ", *ptr++); + ptmp = ptmp + len; + if (((i+1)%16) == 0) { + nfc_logger_print("%s%s\n", pref, tmp); + ptmp = tmp; + } + } + + if (i % 16) { + len = ptmp - tmp; + tmp[len] = 0x0; + nfc_logger_print("%s%s\n", pref, tmp); + } +} + +static ssize_t nfc_logger_read(struct file *file, char __user *buf, size_t len, loff_t *offset) +{ + loff_t pos = *offset; + ssize_t count; + size_t size; + unsigned int curpos = g_curpos; + + if (is_buf_full || BUF_SIZE <= curpos) + size = BUF_SIZE; + else + size = (size_t)curpos; + + if (pos >= size) + return 0; + + if (print_nfc_status && !pos) + print_nfc_status(); + + count = min(len, size); + + if ((pos + count) > size) + count = size - pos; + + if (copy_to_user(buf, nfc_log_buf + pos, count)) + return -EFAULT; + + *offset += count; + + return count; +} + +#if KERNEL_VERSION(5, 6, 0) <= LINUX_VERSION_CODE +static const struct proc_ops nfc_logger_ops = { + .proc_read = nfc_logger_read, + .proc_lseek = default_llseek, +}; +#else +static const struct file_operations nfc_logger_ops = { + .owner = THIS_MODULE, + .read = nfc_logger_read, + .llseek = default_llseek, +}; +#endif + +int nfc_logger_init(void) +{ + struct proc_dir_entry *entry; + + if (is_nfc_logger_init) + return 0; + + entry = proc_create(PROC_FILE_NAME, 0444, NULL, &nfc_logger_ops); + if (!entry) { + pr_err("%s: failed to create proc entry\n", __func__); + return 0; + } + + proc_set_size(entry, BUF_SIZE); + is_nfc_logger_init = 1; + nfc_logger_print("nfc logger init ok\n"); + + g_entry = entry; + + return 0; +} + +void nfc_logger_deinit(void) +{ + if (!g_entry) + return; + + proc_remove(g_entry); + g_entry = NULL; +} + +#ifdef CONFIG_SEC_NFC_LOGGER_ADD_ACPM_LOG +static __iomem *g_rtc_reg; + +u32 nfc_logger_acpm_get_rtc_time(void) +{ + u32 rtc = 0; + + if (g_rtc_reg) + rtc = readl(g_rtc_reg); + + return rtc; +} + +void nfc_logger_acpm_log_print(void) +{ + struct nfc_clk_req_log *acpm_log; + int last_ptr, len, i; + u32 rtc; + + if (!acpm_get_nfc_log_buf(&acpm_log, &last_ptr, &len)) { + for (i = 0; i < len; i++) { + rtc = nfc_logger_acpm_get_rtc_time(); + NFC_LOG_INFO("rtc[%u] - acpm[%2d][%d] %d\n", + rtc, i, acpm_log[i].timestamp, acpm_log[i].is_on); + } + } +} + +void nfc_logger_acpm_log_init(u32 rtc_addr) +{ + u32 rtc_reg_addr = CONFIG_SEC_NFC_LOGGER_RTC_REG_ADDR; + + if (rtc_addr) + rtc_reg_addr = rtc_addr; + + if (rtc_reg_addr) { + NFC_LOG_INFO("rtc: 0x%X\n", rtc_reg_addr); + g_rtc_reg = ioremap(rtc_reg_addr, 0x4); + } +} +#endif + diff --git a/drivers/nfc/nfc_logger/nfc_logger.h b/drivers/nfc/nfc_logger/nfc_logger.h new file mode 100644 index 000000000000..b1b7c5c11637 --- /dev/null +++ b/drivers/nfc/nfc_logger/nfc_logger.h @@ -0,0 +1,47 @@ +#ifndef _NFC_LOGGER_H_ +#define _NFC_LOGGER_H_ + +#ifdef CONFIG_SEC_NFC_LOGGER + +#define NFC_LOG_ERR(fmt, ...) \ + do { \ + pr_err("sec_nfc: "fmt, ##__VA_ARGS__); \ + nfc_logger_print(fmt, ##__VA_ARGS__); \ + } while (0) +#define NFC_LOG_INFO(fmt, ...) \ + do { \ + pr_info("sec_nfc: "fmt, ##__VA_ARGS__); \ + nfc_logger_print(fmt, ##__VA_ARGS__); \ + } while (0) +#define NFC_LOG_INFO_WITH_DATE(fmt, ...) \ + do { \ + char new_fmt[128] = {0, }; \ + char date_time[64] = {0, }; \ + pr_info("sec_nfc: "fmt, ##__VA_ARGS__); \ + nfc_logger_get_date_time(date_time, sizeof(date_time)); \ + snprintf(new_fmt, sizeof(new_fmt), "[%s] %s", date_time, fmt); \ + nfc_logger_print(new_fmt, ##__VA_ARGS__); \ + } while (0) +#define NFC_LOG_DBG(fmt, ...) \ + do { \ + pr_debug("sec_nfc: "fmt, ##__VA_ARGS__); \ + nfc_logger_print(fmt, ##__VA_ARGS__); \ + } while (0) +#define NFC_LOG_REC(fmt, ...) nfc_logger_print(fmt, ##__VA_ARGS__) + +void nfc_logger_set_max_count(int count); +void nfc_logger_get_date_time(char *date_time, int size); +void nfc_logger_print(char *fmt, ...); +void nfc_print_hex_dump(void *buf, void *pref, size_t len); +int nfc_logger_init(void); +void nfc_logger_deinit(void); +void nfc_logger_register_nfc_stauts_func(void (*nfc_status_func)(void)); +#endif + +#ifdef CONFIG_SEC_NFC_LOGGER_ADD_ACPM_LOG +u32 nfc_logger_acpm_get_rtc_time(void); +void nfc_logger_acpm_log_print(void); +void nfc_logger_acpm_log_init(u32 rtc_addr); +#endif + +#endif diff --git a/drivers/nfc/nxp_combined/Kconfig b/drivers/nfc/nxp_combined/Kconfig new file mode 100644 index 000000000000..ca9c3d02cffc --- /dev/null +++ b/drivers/nfc/nxp_combined/Kconfig @@ -0,0 +1,129 @@ +# +# near field communication configuration +# +config SAMSUNG_NFC + tristate "Samsung NFC driver" + default n + help + Say Y here if you want to build support for NFC (Near field + communication) devices. + To compile this support as a module, choose M here: the module will + be called nfc. + +config NFC_NXP_COMBINED + bool "NXP COMBINED driver Feature" + default n + help + NXP Near Field Communication controller support. + If this feature is enabled, sn2xx driver can support various ICs + such as PN557, sn1xx and sn2xx. + For one binary, this driver should be used. + +config NFC_SN2XX + bool "NXP SN2XX Feature" + default n + help + NXP SN2XX Near Field Communication controller support + This option enables device driver support for the NFC. + It is used by many services. NFC is fully controlled using I2C + to communicate the AP chip. + +config NFC_SN2XX_ESE_SUPPORT + tristate "Nxp secure element protocol driver (SPI) devices" + depends on SPI + help + This enables the Secure Element driver for SNxxx based devices. + If unsure, say N. + This selects Secure Element support. + If you want NFC support, you should say Y here and + also to your specific host controller driver. + +config ESE_USE_TZ_API + bool "use tz api" + depends on NFC_SN2XX_ESE_SUPPORT + default n + help + Enable when using TZ API. + You should select this feature if your NFC product + uses S.LSI AP and TZ API. + Say Y here to compile support for TZ API. + +config SEC_NFC_LOGGER + bool "NFC logger" + default n + help + Enable NFC log. + NFC log will be recorded at proc folder. + but will not included at dumpstate log. + so need to copy this file to log folder. + +config SEC_NFC_WAKELOCK_METHOD + int "nfc wakelock method" + default 0 + help + Different functions must be used depending on the kernel version + for wakelock initialization. + 0 - auto selection + 1 - wakeup_source_init + 2 - wakeup_source_register + +config SEC_NFC_LOGGER_ADD_ACPM_LOG + bool "NFC logger: add acpm log" + default n + depends on SEC_NFC_LOGGER + help + add acpm log. + this feature is for particualr AP. + rtc reg addr is needed to compare time + +config SEC_NFC_LOGGER_RTC_REG_ADDR + hex "NFC logger: add acpm log" + default 0x0 + depends on SEC_NFC_LOGGER_ADD_ACPM_LOG + help + RTC time is to compare between acpm and kernel. + address value is hex. + +config MAKE_NODE_USING_PLATFORM_DEVICE + bool "eSE platform driver" + default n + help + Using eSE platform driver. + Sometimes eSE node is created after permission setting + cause of SPI driver dependency. + So, use platform driver to make node first. + +config SEC_STAR + tristate "LSI star platform" + default n + help + LSI star platform + This driver provides support for LSI star platform. + +config STAR_MEMORY_LEAK + bool "memory leak test in sec-star" + help + memory leak test in sec-star. + All allocated memory are listed in array. + +config STAR_K250A + bool "S.LSI k250a driver" + help + S.LSI k250a driver except ISO7816 protocol layer + This driver provides support for S.LSI k250a product. + +config NFC_QTI_I2C + tristate "QTI NCI based NFC I2C Driver for SNxxx" + depends on I2C + help + This enables the NFC driver for SNxxx based devices. + This is for I2C connected version. NCI protocol logic + resides in the usermode and it has no other NFC dependencies. + + If unsure, say N. + +config CLK_ACPM_INIT + bool "ACPM INIT" + default n + help + ACPM INIT must be called to use CLK3 in S5910. diff --git a/drivers/nfc/nxp_combined/Makefile b/drivers/nfc/nxp_combined/Makefile new file mode 100644 index 000000000000..170312b6fbb0 --- /dev/null +++ b/drivers/nfc/nxp_combined/Makefile @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for nfc devices +# +ifneq ($(SEC_BUILD_CONF_USE_ESE_TZ), false) + ccflags-y += -DENABLE_ESE_SPI_SECURED +endif + +# for combined driver +ifeq ($(CONFIG_NFC_NXP_COMBINED),y) +ifeq ($(CONFIG_SAMSUNG_NFC),m) +obj-$(CONFIG_SAMSUNG_NFC) += nfc_nxp_sec.o +nfc_nxp_sec-$(CONFIG_NFC_SN2XX_ESE_SUPPORT) += p73.o ese_reset.o +nfc_nxp_sec-$(CONFIG_SEC_NFC_LOGGER) += ../nfc_logger/nfc_logger.o +nfc_nxp_sec-$(CONFIG_NFC_SN2XX) += common.o common_ese.o i2c_drv.o +else +obj-$(CONFIG_NFC_SN2XX) += nfc.o +nfc-objs += common.o common_ese.o i2c_drv.o +obj-$(CONFIG_NFC_SN2XX_ESE_SUPPORT) += p73.o ese_reset.o +obj-$(CONFIG_SEC_NFC_LOGGER) += ../nfc_logger/nfc_logger.o +endif # CONFIG_SAMSUNG_NFC + +else # !CONFIG_NFC_NXP_COMBINED +# for sn2xx only +ifeq ($(CONFIG_SAMSUNG_NFC),m) +obj-$(CONFIG_SAMSUNG_NFC) += nfc_sec.o +nfc_sec-$(CONFIG_NFC_SN2XX_ESE_SUPPORT) += p73.o ese_reset.o +nfc_sec-$(CONFIG_SEC_NFC_LOGGER) += nfc_logger/nfc_logger.o +nfc_sec-$(CONFIG_NFC_SN2XX) += common.o common_ese.o i2c_drv.o +else +obj-$(CONFIG_NFC_SN2XX) += nfc.o +nfc-objs += common.o common_ese.o i2c_drv.o +obj-$(CONFIG_NFC_SN2XX_ESE_SUPPORT) += p73.o ese_reset.o +obj-$(CONFIG_SEC_NFC_LOGGER) += nfc_logger/nfc_logger.o +endif # CONFIG_SAMSUNG_NFC + + +ifeq ($(CONFIG_SEC_STAR), m) +obj-$(CONFIG_SEC_STAR) = sec-star/sec_star.o + +sec_star-y += \ + sec-star/sec_star.o \ + sec-star/protocol/ese_data.o \ + sec-star/protocol/ese_iso7816_t1.o \ + sec-star/hal/ese_i2c.o \ + sec-star/hal/ese_spi.o \ + sec-star/hal/ese_hal.o + +sec_star-$(CONFIG_STAR_K250A) += sec-star/sec_k250a.o +else +obj-$(CONFIG_SEC_STAR) += sec-star/sec_star.o \ + sec-star/protocol/ese_data.o \ + sec-star/protocol/ese_memory.o \ + sec-star/protocol/ese_iso7816_t1.o \ + sec-star/hal/ese_i2c.o \ + sec-star/hal/ese_spi.o \ + sec-star/hal/ese_hal.o + +obj-$(CONFIG_STAR_K250A) += sec-star/sec_k250a.o +endif # CONFIG_SEC_STAR +endif # CONFIG_NFC_NXP_COMBINED + +ccflags-y += -DRECOVERY_ENABLE -UDEBUG diff --git a/drivers/nfc/nxp_combined/common.c b/drivers/nfc/nxp_combined/common.c new file mode 100644 index 000000000000..cdf003a75b51 --- /dev/null +++ b/drivers/nfc/nxp_combined/common.c @@ -0,0 +1,1004 @@ +/****************************************************************************** + * Copyright (C) 2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2019-2021 NXP + * + * 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 +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +#include +#include +#ifdef CONFIG_NFC_SN2XX_ESE_SUPPORT +#include "p73.h" +#endif +#endif +#include "common_ese.h" + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +static int nfc_param_lpcharge = LPM_NO_SUPPORT; +module_param(nfc_param_lpcharge, int, 0440); + +struct nfc_dev *g_nfc_dev; +static bool g_is_nfc_pvdd_enabled; +#endif + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +enum lpm_status nfc_get_lpcharge(void) +{ + return nfc_param_lpcharge; +} + +void nfc_set_i2c_pinctrl(struct device *dev, char *pinctrl_name) +{ + struct device_node *np = dev->of_node; + struct device_node *i2c_np = of_get_parent(np); + struct platform_device *i2c_pdev; + struct pinctrl *pinctrl_i2c; + + i2c_pdev = of_find_device_by_node(i2c_np); + if (!i2c_pdev) { + NFC_LOG_ERR("i2c pdev not found\n"); + return; + } + + pinctrl_i2c = devm_pinctrl_get_select(&i2c_pdev->dev, pinctrl_name); + + if (IS_ERR_OR_NULL(pinctrl_i2c)) { + NFC_LOG_ERR("No %s pinctrl %ld\n", pinctrl_name, PTR_ERR(pinctrl_i2c)); + } else { + devm_pinctrl_put(pinctrl_i2c); + NFC_LOG_INFO("%s pinctrl done\n", pinctrl_name); + } +} +#endif + +#ifdef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE +struct nfc_dev g_nfc_dev_for_chrdev; + +void nfc_parse_dt_for_platform_device(struct device *dev) +{ + struct device_node *np = dev->of_node; + struct platform_configs *nfc_configs = &g_nfc_dev_for_chrdev.configs; + struct platform_gpio *nfc_gpio = &nfc_configs->gpio; + + nfc_configs->late_pvdd_en = of_property_read_bool(np, "pn547,late_pvdd_en"); + NFC_LOG_INFO("late_pvdd_en :%d, lpcharge :%d\n", nfc_configs->late_pvdd_en, nfc_get_lpcharge()); + if (nfc_get_lpcharge() == LPM_FALSE) + nfc_configs->late_pvdd_en = false; + + if (nfc_configs->late_pvdd_en) { + nfc_gpio->ven = of_get_named_gpio(np, DTS_VEN_GPIO_STR, 0); + if (!gpio_is_valid(nfc_gpio->ven)) + NFC_LOG_ERR("%s: ven gpio invalid %d\n", __func__, nfc_gpio->ven); + else + configure_gpio(nfc_gpio->ven, GPIO_OUTPUT); + + nfc_configs->nfc_pvdd = regulator_get(dev, "nfc_pvdd"); + if (IS_ERR(nfc_configs->nfc_pvdd)) + NFC_LOG_ERR("get nfc_pvdd error\n"); + else + NFC_LOG_INFO("LDO nfc_pvdd: %pK, vol:%d\n", + nfc_configs->nfc_pvdd, regulator_get_voltage(nfc_configs->nfc_pvdd)); + } +} +#endif + +int nfc_parse_dt(struct device *dev, struct platform_configs *nfc_configs, + uint8_t interface) +{ + struct device_node *np = dev->of_node; + struct platform_gpio *nfc_gpio = &nfc_configs->gpio; +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + const char *ap_str; + static int retry_count = 3; +#endif + + if (!np) { + NFC_LOG_ERR("%s: nfc of_node NULL\n", __func__); + return -EINVAL; + } + + nfc_gpio->irq = -EINVAL; + nfc_gpio->dwl_req = -EINVAL; + nfc_gpio->ven = -EINVAL; + + /* irq required for i2c based chips only */ + if (interface == PLATFORM_IF_I2C) { + nfc_gpio->irq = of_get_named_gpio(np, DTS_IRQ_GPIO_STR, 0); + if ((!gpio_is_valid(nfc_gpio->irq))) { + NFC_LOG_ERR("%s: irq gpio invalid %d\n", __func__, + nfc_gpio->irq); + return -EINVAL; + } + NFC_LOG_INFO("%s: irq %d\n", __func__, nfc_gpio->irq); + } + nfc_gpio->ven = of_get_named_gpio(np, DTS_VEN_GPIO_STR, 0); + if ((!gpio_is_valid(nfc_gpio->ven))) { + NFC_LOG_ERR("%s: ven gpio invalid %d\n", __func__, nfc_gpio->ven); + return -EINVAL; + } + /* some products like sn220 does not required fw dwl pin */ + nfc_gpio->dwl_req = of_get_named_gpio(np, DTS_FWDN_GPIO_STR, 0); + if ((!gpio_is_valid(nfc_gpio->dwl_req))) + NFC_LOG_ERR("%s: dwl_req gpio is not supported(%d)\n", __func__, + nfc_gpio->dwl_req); + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + nfc_configs->late_pvdd_en = of_property_read_bool(np, "pn547,late_pvdd_en"); + + nfc_configs->disable_clk_irq_during_wakeup = + of_property_read_bool(np, "pn547,disable_clk_irq_during_wakeup"); + + if (nfc_get_lpcharge() == LPM_FALSE) + nfc_configs->late_pvdd_en = false; + + nfc_gpio->clk_req = of_get_named_gpio(np, "pn547,clk_req-gpio", 0); + if ((!gpio_is_valid(nfc_gpio->clk_req))) + NFC_LOG_ERR("nfc clk_req gpio invalid %d\n", nfc_gpio->clk_req); + + nfc_configs->clk_req_wake = of_property_read_bool(np, "pn547,clk_req_wake"); + + if (of_property_read_bool(np, "pn547,clk_req_all_trigger")) { + nfc_configs->clk_req_all_trigger = true; + NFC_LOG_INFO("irq_all_trigger\n"); + } + if (of_property_read_bool(np, "pn547,change_clkreq_for_acpm")) { + nfc_configs->change_clkreq_for_acpm = true; + NFC_LOG_INFO("change clkreq for acpm!!\n"); + } + + if (!of_property_read_string(np, "pn547,ap_vendor", &ap_str)) { + if (!strcmp(ap_str, "slsi")) + nfc_configs->ap_vendor = AP_VENDOR_SLSI; + else if (!strcmp(ap_str, "qct") || !strcmp(ap_str, "qualcomm")) + nfc_configs->ap_vendor = AP_VENDOR_QCT; + else if (!strcmp(ap_str, "mtk")) + nfc_configs->ap_vendor = AP_VENDOR_MTK; + } else { + NFC_LOG_INFO("AP vendor is not set\n"); + } +#ifdef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE + if (nfc_configs->late_pvdd_en) { + if (!IS_ERR_OR_NULL(g_nfc_dev_for_chrdev.configs.nfc_pvdd)) { + nfc_configs->nfc_pvdd = g_nfc_dev_for_chrdev.configs.nfc_pvdd; + } else { + nfc_configs->nfc_pvdd = regulator_get(dev, "nfc_pvdd"); + if (!IS_ERR(nfc_configs->nfc_pvdd)) + g_nfc_dev_for_chrdev.configs.nfc_pvdd = nfc_configs->nfc_pvdd; + NFC_LOG_INFO("retry to get platform pvdd\n"); + } + } else { + nfc_configs->nfc_pvdd = regulator_get(dev, "nfc_pvdd"); + } +#else + nfc_configs->nfc_pvdd = regulator_get(dev, "nfc_pvdd"); +#endif + if (IS_ERR(nfc_configs->nfc_pvdd)) { + NFC_LOG_ERR("get nfc_pvdd error\n"); + if (--retry_count > 0) + return -EPROBE_DEFER; + else + return -ENODEV; + } else { + NFC_LOG_INFO("LDO nfc_pvdd: %pK, vol:%d\n", + nfc_configs->nfc_pvdd, regulator_get_voltage(nfc_configs->nfc_pvdd)); + } + + if (of_property_read_bool(np, "pn547,always_on_pvdd")) { + /* set late_pvdd_en to true so that nfc_en reset can be called in nfc_power_control */ + nfc_configs->late_pvdd_en = true; + nfc_set_i2c_pinctrl(dev, "i2c_pull_up"); + } + + if (of_find_property(np, "clocks", NULL)) { + nfc_configs->nfc_clock = clk_get(dev, "oscclk_nfc"); + if (IS_ERR(nfc_configs->nfc_clock)) { + NFC_LOG_ERR("probe() clk not found\n"); + nfc_configs->nfc_clock = NULL; + } else { + NFC_LOG_INFO("found oscclk_nfc\n"); + } + } +#endif + + NFC_LOG_INFO("%s: irq %d, ven %d, fw %d\n", __func__, nfc_gpio->irq, nfc_gpio->ven, + nfc_gpio->dwl_req); + return 0; +} + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +static int nfc_ocp_notifier(struct notifier_block *nb, unsigned long event, void *data); + +int nfc_regulator_onoff(struct nfc_dev *nfc_dev, int onoff) +{ + int rc = 0; + struct platform_configs *nfc_configs = &nfc_dev->configs; + struct regulator *regulator_nfc_pvdd; + +#ifdef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE + /* if nfc probe is called before nfc platform device, nfc_configs->nfc_pvdd is NULL */ + if (!nfc_configs->nfc_pvdd) + nfc_configs->nfc_pvdd = g_nfc_dev_for_chrdev.configs.nfc_pvdd; +#endif + + /* if regulator is not ready, try to get that again */ + if (IS_ERR_OR_NULL(nfc_configs->nfc_pvdd) && nfc_dev->i2c_dev.client) { + NFC_LOG_INFO("%s retry to get regulator\n", __func__); + nfc_configs->nfc_pvdd = regulator_get(&nfc_dev->i2c_dev.client->dev, "nfc_pvdd"); + } + + regulator_nfc_pvdd = nfc_configs->nfc_pvdd; + + if (IS_ERR_OR_NULL(regulator_nfc_pvdd)) { + NFC_LOG_ERR("error at regulator!\n"); + rc = -ENODEV; + goto done; + } + + if (nfc_configs->ap_vendor == AP_VENDOR_QCT && !nfc_configs->ldo_ocp_nb.notifier_call) { + nfc_configs->ldo_ocp_nb.notifier_call = nfc_ocp_notifier; + devm_regulator_register_notifier(nfc_configs->nfc_pvdd, &nfc_configs->ldo_ocp_nb); + NFC_LOG_INFO("%s register nfc ocp notifier\n", __func__); + } + + NFC_LOG_INFO("onoff = %d, g_is_nfc_pvdd_enabled = %d\n", onoff, g_is_nfc_pvdd_enabled); + + if (g_is_nfc_pvdd_enabled == onoff) { + NFC_LOG_INFO("%s already pvdd %s\n", __func__, onoff ? "enabled" : "disabled"); + goto done; + } + + if (onoff) { + rc = regulator_set_load(regulator_nfc_pvdd, 300000); + if (rc) { + NFC_LOG_ERR("regulator_uwb_vdd set_load failed, rc=%d\n", rc); + goto done; + } + rc = regulator_enable(regulator_nfc_pvdd); + if (rc) { + NFC_LOG_ERR("enable failed, rc=%d\n", rc); + goto done; + } + } else { + rc = regulator_disable(regulator_nfc_pvdd); + if (rc) { + NFC_LOG_ERR("disable failed, rc=%d\n", rc); + goto done; + } + } + + g_is_nfc_pvdd_enabled = !!onoff; + + NFC_LOG_INFO("success\n"); +done: + return rc; +} + +bool nfc_check_pvdd_status(void) +{ + return g_is_nfc_pvdd_enabled; +} +#endif + +void set_valid_gpio(int gpio, int value) +{ + if (gpio_is_valid(gpio)) { + NFC_LOG_DBG("%s: gpio %d value %d\n", __func__, gpio, value); + gpio_set_value(gpio, value); + /* hardware dependent delay */ + usleep_range(NFC_GPIO_SET_WAIT_TIME_US, + NFC_GPIO_SET_WAIT_TIME_US + 100); + } +} + +int get_valid_gpio(int gpio) +{ + int value = -EINVAL; + + if (gpio_is_valid(gpio)) { + value = gpio_get_value(gpio); + NFC_LOG_DBG("%s: gpio %d value %d\n", __func__, gpio, value); + } + return value; +} + +void gpio_set_ven(struct nfc_dev *nfc_dev, int value) +{ + struct platform_gpio *nfc_gpio = &nfc_dev->configs.gpio; + + if (gpio_get_value(nfc_gpio->ven) != value) { + NFC_LOG_REC("%s: value %d\n", __func__, value); + /* reset on change in level from high to low */ + if (value) + ese_cold_reset_release(nfc_dev); + + gpio_set_value(nfc_gpio->ven, value); + /* hardware dependent delay */ + usleep_range(NFC_GPIO_SET_WAIT_TIME_US, + NFC_GPIO_SET_WAIT_TIME_US + 100); + } +} + +int configure_gpio(unsigned int gpio, int flag) +{ + int ret; + + NFC_LOG_DBG("%s: nfc gpio [%d] flag [%01x]\n", __func__, gpio, flag); + if (gpio_is_valid(gpio)) { + ret = gpio_request(gpio, "nfc_gpio"); + if (ret) { + NFC_LOG_ERR("%s: unable to request nfc gpio [%d]\n", + __func__, gpio); + return ret; + } + /* set direction and value for output pin */ + if (flag & GPIO_OUTPUT) { + ret = gpio_direction_output(gpio, (GPIO_HIGH & flag)); + NFC_LOG_DBG("%s: nfc o/p gpio %d level %d\n", __func__, + gpio, gpio_get_value(gpio)); + } else { + ret = gpio_direction_input(gpio); + NFC_LOG_DBG("%s: nfc i/p gpio %d\n", __func__, gpio); + } + + if (ret) { + NFC_LOG_ERR("%s: unable to set direction for nfc gpio [%d]\n", + __func__, gpio); + gpio_free(gpio); + return ret; + } + /* Consider value as control for input IRQ pin */ + if (flag & GPIO_IRQ) { + ret = gpio_to_irq(gpio); + if (ret < 0) { + NFC_LOG_ERR("%s: unable to set irq [%d]\n", __func__, + gpio); + gpio_free(gpio); + return ret; + } + NFC_LOG_DBG("%s: gpio_to_irq successful [%d]\n", __func__, + gpio); + return ret; + } + } else { + NFC_LOG_ERR("%s: invalid gpio\n", __func__); + ret = -EINVAL; + } + return ret; +} + +void gpio_free_all(struct nfc_dev *nfc_dev) +{ + struct platform_gpio *nfc_gpio = &nfc_dev->configs.gpio; + + if (gpio_is_valid(nfc_gpio->dwl_req)) + gpio_free(nfc_gpio->dwl_req); + + if (gpio_is_valid(nfc_gpio->irq)) + gpio_free(nfc_gpio->irq); + + if (gpio_is_valid(nfc_gpio->ven)) + gpio_free(nfc_gpio->ven); +} + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +void nfc_power_control(struct nfc_dev *nfc_dev) +{ + int ret; + + ret = nfc_regulator_onoff(nfc_dev, 1); + if (ret < 0) + NFC_LOG_ERR("%s pn547 regulator_on fail err = %d\n", __func__, ret); + + nfc_set_i2c_pinctrl(&nfc_dev->i2c_dev.client->dev, "i2c_pull_up"); +#ifdef CONFIG_NFC_SN2XX_ESE_SUPPORT + ese_set_spi_pinctrl_for_ese_off(NULL); +#endif + usleep_range(15000, 20000); /* spec : VDDIO high -> 15~20 ms -> VEN high*/ + + gpio_set_ven(nfc_dev, 1); + gpio_set_ven(nfc_dev, 0); + gpio_set_ven(nfc_dev, 1); +} + +static int nfc_ocp_notifier(struct notifier_block *nb, unsigned long event, void *data) +{ + if (event == REGULATOR_EVENT_OVER_CURRENT) + NFC_LOG_ERR("NFC power OCP\n"); + + return NOTIFY_OK; +} + +#if (KERNEL_VERSION(6, 3, 0) <= LINUX_VERSION_CODE) +static ssize_t nfc_support_show(const struct class *class, + const struct class_attribute *attr, char *buf) +#else +static ssize_t nfc_support_show(struct class *class, + struct class_attribute *attr, char *buf) +#endif +{ + NFC_LOG_INFO("\n"); + return 0; +} +static CLASS_ATTR_RO(nfc_support); + +#if (KERNEL_VERSION(6, 3, 0) <= LINUX_VERSION_CODE) +static ssize_t pvdd_store(const struct class *class, + const struct class_attribute *attr, const char *buf, size_t size) +#else +static ssize_t pvdd_store(struct class *class, + struct class_attribute *attr, const char *buf, size_t size) +#endif +{ + struct nfc_dev *nfc_dev = g_nfc_dev; + struct platform_configs *nfc_configs; + +#ifdef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE + if (!g_nfc_dev) { + nfc_dev = &g_nfc_dev_for_chrdev; + NFC_LOG_INFO("%s called before nfc probe\n", __func__); + } +#endif + if (!nfc_dev) { + NFC_LOG_ERR("%s nfc_dev is NULL!\n", __func__); + return size; + } + + nfc_configs = &nfc_dev->configs; + + NFC_LOG_INFO("%s val: %c, late_pvdd_en: %d\n", __func__, buf[0], nfc_configs->late_pvdd_en); + + if (buf[0] == '1' && nfc_configs->late_pvdd_en) + nfc_power_control(nfc_dev); + + return size; +} +static CLASS_ATTR_WO(pvdd); + +#ifdef CONFIG_SAMSUNG_NFC_DEBUG +#if (KERNEL_VERSION(6, 3, 0) <= LINUX_VERSION_CODE) +static ssize_t check_show(const struct class *class, + const struct class_attribute *attr, char *buf) +#else +static ssize_t check_show(struct class *class, + struct class_attribute *attr, char *buf) +#endif +{ + struct nfc_dev *nfc_dev = g_nfc_dev; + char *cmd = nfc_dev->write_kbuf; + char *rsp = nfc_dev->read_kbuf; + int timeout = NCI_CMD_RSP_TIMEOUT_MS; + int size = 0; + int ret; + int cmd_length = 4; + + if (!nfc_check_pvdd_status()) { + NFC_LOG_ERR("Turn on PVDD first\n"); + size = snprintf(buf, SZ_64, "Turn on PVDD first\n"); + goto end; + } + mutex_lock(&nfc_dev->write_mutex); + *cmd++ = 0x20; + *cmd++ = 0x00; + *cmd++ = 0x01; + *cmd++ = 0x00; + + ret = nfc_dev->nfc_write(nfc_dev, nfc_dev->write_kbuf, cmd_length, MAX_RETRY_COUNT); + if (ret != cmd_length) { + ret = -EIO; + NFC_LOG_ERR("%s: nfc_write returned %d\n", __func__, ret); + size = snprintf(buf, SZ_64, "nfc_write returned %d. count : %d\n", + ret, cmd_length); + mutex_unlock(&nfc_dev->write_mutex); + goto end; + } + mutex_unlock(&nfc_dev->write_mutex); + + /* Read data */ + mutex_lock(&nfc_dev->read_mutex); + cmd_length = 6; + ret = nfc_dev->nfc_read(nfc_dev, rsp, cmd_length, timeout); + + if (ret < 0 || ret > cmd_length) { + NFC_LOG_ERR("%s: nfc_read returned %d\n", __func__, ret); + size = snprintf(buf, SZ_64, "nfc_read returned %d. count : %d\n", + ret, cmd_length); + mutex_unlock(&nfc_dev->read_mutex); + goto end; + } + mutex_unlock(&nfc_dev->read_mutex); + + size = snprintf(buf, SZ_64, "test completed!! size: %d, data: %X %X %X %X %X %X\n", + ret, rsp[0], rsp[1], rsp[2], rsp[3], rsp[4], rsp[5]); +end: + return size; +} + +#ifdef FEATURE_SEC_NFC_TEST +extern void nfc_check_is_core_reset_ntf(u8 *data, int len); +#endif +#if (KERNEL_VERSION(6, 3, 0) <= LINUX_VERSION_CODE) +static ssize_t check_store(const struct class *class, + const struct class_attribute *attr, const char *buf, size_t size) +#else +static ssize_t check_store(struct class *class, + struct class_attribute *attr, const char *buf, size_t size) +#endif +{ + if (size > 0) { + if (buf[0] == '1') { + NFC_LOG_INFO("%s: test\n", __func__); + nfc_print_status(); + } +#ifdef FEATURE_SEC_NFC_TEST + else if (buf[0] == '2') { + u8 header[3] = {0x60, 0x00, 0x06}; + u8 data[10] = {0x0, }; + + nfc_check_is_core_reset_ntf(header, 3); + data[0] = 0xA0; + nfc_check_is_core_reset_ntf(data, 6); + } +#endif + } + + return size; +} +static CLASS_ATTR_RW(check); +#endif +#endif + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +void nfc_probe_done(struct nfc_dev *nfc_dev) +{ + g_nfc_dev = nfc_dev; +} +#endif + +void nfc_misc_unregister(struct nfc_dev *nfc_dev, int count) +{ + NFC_LOG_DBG("%s: entry\n", __func__); +#ifdef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE + if (nfc_dev == NULL) + nfc_dev = &g_nfc_dev_for_chrdev; +#endif +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + class_remove_file(nfc_dev->nfc_class, &class_attr_nfc_support); + class_remove_file(nfc_dev->nfc_class, &class_attr_pvdd); +#ifdef CONFIG_SAMSUNG_NFC_DEBUG + class_remove_file(nfc_dev->nfc_class, &class_attr_check); +#endif +#endif + device_destroy(nfc_dev->nfc_class, nfc_dev->devno); + cdev_del(&nfc_dev->c_dev); + class_destroy(nfc_dev->nfc_class); + unregister_chrdev_region(nfc_dev->devno, count); +} + +int nfc_misc_register(struct nfc_dev *nfc_dev, + const struct file_operations *nfc_fops, int count, + char *devname, char *classname) +{ + int ret = 0; + +#ifdef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE + if (nfc_dev == NULL) + nfc_dev = &g_nfc_dev_for_chrdev; +#endif + + ret = alloc_chrdev_region(&nfc_dev->devno, 0, count, devname); + if (ret < 0) { + NFC_LOG_ERR("%s: failed to alloc chrdev region ret %d\n", __func__, + ret); + return ret; + } +#if (KERNEL_VERSION(6, 3, 0) <= LINUX_VERSION_CODE) + nfc_dev->nfc_class = class_create(classname); +#else + nfc_dev->nfc_class = class_create(THIS_MODULE, classname); +#endif + if (IS_ERR(nfc_dev->nfc_class)) { + ret = PTR_ERR(nfc_dev->nfc_class); + NFC_LOG_ERR("%s: failed to register device class ret %d\n", __func__, + ret); + unregister_chrdev_region(nfc_dev->devno, count); + return ret; + } + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + ret = class_create_file(nfc_dev->nfc_class, &class_attr_nfc_support); + if (ret) + NFC_LOG_ERR("failed to create nfc_support file\n"); + + ret = class_create_file(nfc_dev->nfc_class, &class_attr_pvdd); + if (ret) + NFC_LOG_ERR("failed to create nfc_support file\n"); + +#ifdef CONFIG_SAMSUNG_NFC_DEBUG + ret = class_create_file(nfc_dev->nfc_class, &class_attr_check); + if (ret) + NFC_LOG_ERR("failed to create test file\n"); +#endif +#endif + + cdev_init(&nfc_dev->c_dev, nfc_fops); + ret = cdev_add(&nfc_dev->c_dev, nfc_dev->devno, count); + if (ret < 0) { + NFC_LOG_ERR("%s: failed to add cdev ret %d\n", __func__, ret); + class_destroy(nfc_dev->nfc_class); + unregister_chrdev_region(nfc_dev->devno, count); + return ret; + } + nfc_dev->nfc_device = device_create(nfc_dev->nfc_class, NULL, + nfc_dev->devno, nfc_dev, devname); + if (IS_ERR(nfc_dev->nfc_device)) { + ret = PTR_ERR(nfc_dev->nfc_device); + NFC_LOG_ERR("%s: failed to create the device ret %d\n", __func__, + ret); + cdev_del(&nfc_dev->c_dev); + class_destroy(nfc_dev->nfc_class); + unregister_chrdev_region(nfc_dev->devno, count); + return ret; + } + return 0; +} + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +void nfc_print_status(void) +{ + struct nfc_dev *nfc_dev = g_nfc_dev; + struct platform_configs *nfc_configs; + struct platform_gpio *nfc_gpio; + int en, firm, irq, pvdd = 0; + int clk_req_irq = -1; + + if (nfc_dev == NULL) + return; + + nfc_configs = &nfc_dev->configs; + nfc_gpio = &nfc_dev->configs.gpio; + + en = get_valid_gpio(nfc_gpio->ven); + firm = get_valid_gpio(nfc_gpio->dwl_req); + irq = get_valid_gpio(nfc_gpio->irq); + if (!IS_ERR_OR_NULL(nfc_configs->nfc_pvdd)) + pvdd = regulator_is_enabled(nfc_configs->nfc_pvdd); + else + NFC_LOG_ERR("nfc_pvdd is null\n"); + + clk_req_irq = get_valid_gpio(nfc_gpio->clk_req); + + NFC_LOG_INFO("en: %d, firm: %d, pvdd: %d, irq: %d, clk_req: %d\n", + en, firm, pvdd, irq, clk_req_irq); +#ifdef CONFIG_NFC_SN2XX_ESE_SUPPORT + p61_print_status(__func__); +#endif +#ifdef CONFIG_SEC_NFC_LOGGER_ADD_ACPM_LOG + nfc_logger_acpm_log_print(); +#endif +} +#endif +/** + ** nfc_gpio_info() - gets the status of nfc gpio pins and encodes into a byte. + ** @nfc_dev: nfc device data structure + ** @arg: userspace buffer + ** + ** Encoding can be done in following manner + ** 1) map the gpio value into INVALID(-2), SET(1), RESET(0). + ** 2) mask the first 2 bits of gpio. + ** 3) left shift the 2 bits as multiple of 2. + ** 4) multiply factor can be defined as position of gpio pin in struct platform_gpio + ** + ** Return: -EFAULT, if unable to copy the data from kernel space to userspace, 0 + ** if Success(or no issue) + **/ + +static int nfc_gpio_info(struct nfc_dev *nfc_dev, unsigned long arg) +{ + unsigned int gpios_status = 0; + int value = 0; + int gpio_no = 0; + int i; + struct platform_gpio *nfc_gpio = &nfc_dev->configs.gpio; + +#if !IS_ENABLED(CONFIG_SAMSUNG_NFC) + for (i = 0; i < sizeof(struct platform_gpio) / sizeof(unsigned int); i++) +#else + for (i = 0; i < PLATFORM_DEFAULT_GPIO_CNT; i++) +#endif + { + gpio_no = *((unsigned int *)nfc_gpio + i); + value = get_valid_gpio(gpio_no); + if (value < 0) + value = -2; + gpios_status |= (value & GPIO_STATUS_MASK_BITS)<<(GPIO_POS_SHIFT_VAL*i); + } +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + nfc_print_status(); +#endif + if (copy_to_user((uint32_t *) arg, &gpios_status, sizeof(value))) { + pr_err("%s : Unable to copy data from kernel space to user space\n", __func__); + return -EFAULT; + } + return 0; +} + +/** + * nfc_ioctl_power_states() - power control + * @nfc_dev: nfc device data structure + * @arg: mode that we want to move to + * + * Device power control. Depending on the arg value, device moves to + * different states, refer common.h for args + * + * Return: -ENOIOCTLCMD if arg is not supported, 0 if Success(or no issue) + * and error ret code otherwise + */ +static int nfc_ioctl_power_states(struct nfc_dev *nfc_dev, unsigned long arg) +{ + int ret = 0; + struct platform_gpio *nfc_gpio = &nfc_dev->configs.gpio; + + if (arg == NFC_POWER_OFF) { + /* + * We are attempting a hardware reset so let us disable + * interrupts to avoid spurious notifications to upper + * layers. + */ + nfc_dev->nfc_disable_intr(nfc_dev); + set_valid_gpio(nfc_gpio->dwl_req, 0); + gpio_set_ven(nfc_dev, 0); + nfc_dev->nfc_ven_enabled = false; + } else if (arg == NFC_POWER_ON) { + nfc_dev->nfc_enable_intr(nfc_dev); + set_valid_gpio(nfc_gpio->dwl_req, 0); + + gpio_set_ven(nfc_dev, 1); + nfc_dev->nfc_ven_enabled = true; + } else if (arg == NFC_FW_DWL_VEN_TOGGLE) { + /* + * We are switching to download Mode, toggle the enable pin + * in order to set the NFCC in the new mode + */ + nfc_dev->nfc_disable_intr(nfc_dev); + set_valid_gpio(nfc_gpio->dwl_req, 1); + nfc_dev->nfc_state = NFC_STATE_FW_DWL; + gpio_set_ven(nfc_dev, 0); + gpio_set_ven(nfc_dev, 1); + nfc_dev->nfc_enable_intr(nfc_dev); + } else if (arg == NFC_FW_DWL_HIGH) { + /* + * Setting firmware download gpio to HIGH + * before FW download start + */ + set_valid_gpio(nfc_gpio->dwl_req, 1); + nfc_dev->nfc_state = NFC_STATE_FW_DWL; + + } else if (arg == NFC_VEN_FORCED_HARD_RESET) { + nfc_dev->nfc_disable_intr(nfc_dev); + gpio_set_ven(nfc_dev, 0); + gpio_set_ven(nfc_dev, 1); + nfc_dev->nfc_enable_intr(nfc_dev); + } else if (arg == NFC_FW_DWL_LOW) { + /* + * Setting firmware download gpio to LOW + * FW download finished + */ + set_valid_gpio(nfc_gpio->dwl_req, 0); + nfc_dev->nfc_state = NFC_STATE_NCI; + } else { + NFC_LOG_ERR("%s: bad arg %lu\n", __func__, arg); + ret = -ENOIOCTLCMD; + } + return ret; +} + +#ifdef CONFIG_COMPAT +/** + * nfc_dev_compat_ioctl - used to set or get data from upper layer. + * @pfile file node for opened device. + * @cmd ioctl type from upper layer. + * @arg ioctl arg from upper layer. + * + * NFC and ESE Device power control, based on the argument value + * + * Return: -ENOIOCTLCMD if arg is not supported + * 0 if Success(or no issue) + * 0 or 1 in case of arg is ESE_GET_PWR/ESE_POWER_STATE + * and error ret code otherwise + */ +long nfc_dev_compat_ioctl(struct file *pfile, unsigned int cmd, + unsigned long arg) +{ + int ret = 0; + + arg = (compat_u64)arg; + NFC_LOG_DBG("%s: cmd = %x arg = %zx\n", __func__, cmd, arg); + ret = nfc_dev_ioctl(pfile, cmd, arg); + return ret; +} +#endif + +/** + * nfc_dev_ioctl - used to set or get data from upper layer. + * @pfile file node for opened device. + * @cmd ioctl type from upper layer. + * @arg ioctl arg from upper layer. + * + * NFC and ESE Device power control, based on the argument value + * + * Return: -ENOIOCTLCMD if arg is not supported + * 0 if Success(or no issue) + * 0 or 1 in case of arg is ESE_GET_PWR/ESE_POWER_STATE + * and error ret code otherwise + */ +long nfc_dev_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg) +{ + int ret = 0; + struct nfc_dev *nfc_dev = pfile->private_data; + + if (!nfc_dev) + return -ENODEV; + + NFC_LOG_INFO("%s: cmd = %x arg = %zx\n", __func__, cmd, arg); + switch (cmd) { + case NFC_SET_PWR: + NFC_LOG_INFO("%s: NFC_SET_PWR\n", __func__); + ret = nfc_ioctl_power_states(nfc_dev, arg); + break; + case ESE_SET_PWR: + NFC_LOG_INFO("%s: ESE_SET_PWR\n", __func__); + ret = nfc_ese_pwr(nfc_dev, arg); + break; + case ESE_GET_PWR: + NFC_LOG_INFO("%s: ESE_GET_PWR\n", __func__); + ret = nfc_ese_pwr(nfc_dev, ESE_POWER_STATE); + break; + case NFC_GET_GPIO_STATUS: + NFC_LOG_INFO("%s: NFC_GET_GPIO_STATUS\n", __func__); + ret = nfc_gpio_info(nfc_dev, arg); + break; + default: + NFC_LOG_ERR("%s: bad cmd %lu\n", __func__, arg); + ret = -ENOIOCTLCMD; + }; + return ret; +} + +int nfc_dev_open(struct inode *inode, struct file *filp) +{ + struct nfc_dev *nfc_dev = NULL; +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + struct platform_configs *nfc_configs; +#endif + +#ifdef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE + nfc_dev = g_nfc_dev; +#else + nfc_dev = container_of(inode->i_cdev, struct nfc_dev, c_dev); +#endif + if (!nfc_dev) + return -ENODEV; + + NFC_LOG_INFO("%s: %d, %d\n", __func__, imajor(inode), iminor(inode)); + + mutex_lock(&nfc_dev->dev_ref_mutex); + + filp->private_data = nfc_dev; + + if (nfc_dev->dev_ref_count == 0) { + set_valid_gpio(nfc_dev->configs.gpio.dwl_req, 0); + + nfc_dev->nfc_enable_intr(nfc_dev); +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + nfc_configs = &nfc_dev->configs; + if (!nfc_configs->disable_clk_irq_during_wakeup) + nfc_dev->nfc_enable_clk_intr(nfc_dev); +#endif + } + nfc_dev->dev_ref_count = nfc_dev->dev_ref_count + 1; + mutex_unlock(&nfc_dev->dev_ref_mutex); + return 0; +} + +int nfc_dev_flush(struct file *pfile, fl_owner_t id) +{ + struct nfc_dev *nfc_dev = pfile->private_data; + + if (!nfc_dev) + return -ENODEV; + /* + * release blocked user thread waiting for pending read during close + */ + if (!mutex_trylock(&nfc_dev->read_mutex)) { + nfc_dev->release_read = true; + nfc_dev->nfc_disable_intr(nfc_dev); + wake_up(&nfc_dev->read_wq); + NFC_LOG_DBG("%s: waiting for release of blocked read\n", __func__); + mutex_lock(&nfc_dev->read_mutex); + nfc_dev->release_read = false; + } else { + NFC_LOG_DBG("%s: read thread already released\n", __func__); + } + mutex_unlock(&nfc_dev->read_mutex); + return 0; +} + +int nfc_dev_close(struct inode *inode, struct file *filp) +{ + struct nfc_dev *nfc_dev = NULL; + +#ifdef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE + nfc_dev = g_nfc_dev; +#else + nfc_dev = container_of(inode->i_cdev, struct nfc_dev, c_dev); +#endif + if (!nfc_dev) + return -ENODEV; + + NFC_LOG_INFO("%s: %d, %d\n", __func__, imajor(inode), iminor(inode)); + mutex_lock(&nfc_dev->dev_ref_mutex); + if (nfc_dev->dev_ref_count == 1) { + nfc_dev->nfc_disable_intr(nfc_dev); + set_valid_gpio(nfc_dev->configs.gpio.dwl_req, 0); +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + nfc_dev->nfc_disable_clk_intr(nfc_dev); +#endif + } + if (nfc_dev->dev_ref_count > 0) + nfc_dev->dev_ref_count = nfc_dev->dev_ref_count - 1; + else { + /* + * Use "ESE_RST_PROT_DIS" as argument + * if eSE calls flow is via NFC driver + * i.e. direct calls from SPI HAL to NFC driver + */ + nfc_ese_pwr(nfc_dev, ESE_RST_PROT_DIS_NFC); + } + filp->private_data = NULL; + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + nfc_print_status(); +#endif + + mutex_unlock(&nfc_dev->dev_ref_mutex); + return 0; +} + +int validate_nfc_state_nci(struct nfc_dev *nfc_dev) +{ + struct platform_gpio *nfc_gpio = &nfc_dev->configs.gpio; + + if (!gpio_get_value(nfc_gpio->ven)) { + NFC_LOG_ERR("%s: ven low - nfcc powered off\n", __func__); + return -ENODEV; + } + if (get_valid_gpio(nfc_gpio->dwl_req) == 1) { + NFC_LOG_ERR("%s: fw download in-progress\n", __func__); + return -EBUSY; + } + if (nfc_dev->nfc_state != NFC_STATE_NCI) { + NFC_LOG_ERR("%s: fw download state\n", __func__); + return -EBUSY; + } + return 0; +} diff --git a/drivers/nfc/nxp_combined/common.h b/drivers/nfc/nxp_combined/common.h new file mode 100644 index 000000000000..9f8f0a998cf9 --- /dev/null +++ b/drivers/nfc/nxp_combined/common.h @@ -0,0 +1,326 @@ +/****************************************************************************** + * Copyright (C) 2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2019-2022 NXP + * + * 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 _COMMON_H_ +#define _COMMON_H_ + +#include +#include + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +#include +#include +#include "nfc_wakelock.h" +#ifdef CONFIG_SEC_NFC_LOGGER +#ifdef CONFIG_NFC_NXP_COMBINED +#include "../nfc_logger/nfc_logger.h" +#else +#include "nfc_logger/nfc_logger.h" +#endif +#endif +#endif + +#include "i2c_drv.h" + +/* Max device count for this driver */ +#define DEV_COUNT 1 +/* i2c device class */ +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +#define CLASS_NAME "nfc_sec" +#else +#define CLASS_NAME "nfc" +#endif + +/* NFC character device name, this will be in /dev/ */ +#define NFC_CHAR_DEV_NAME "pn547" + +/* NCI packet details */ +#define NCI_CMD (0x20) +#define NCI_RSP (0x40) +#define NCI_HDR_LEN (3) +#define NCI_HDR_IDX (0) +#define NCI_HDR_OID_IDX (1) +#define NCI_PAYLOAD_IDX (3) +#define NCI_PAYLOAD_LEN_IDX (2) + +/* FW DNLD packet details */ +#define DL_HDR_LEN (2) +#define DL_CRC_LEN (2) + +#define MAX_NCI_PAYLOAD_LEN (255) +#define MAX_NCI_BUFFER_SIZE (NCI_HDR_LEN + MAX_NCI_PAYLOAD_LEN) +/* + * Compile time option to select maximum writer buffer of either 4K or 550 bytes. + * Default value is set as 4K. This value shall be chosen based on Hal flag "HDLL_4K_WRITE_SUPPORTED". + * undef or comment HDLL_4K_WRITE_SUPPORTED to fallback to 550 bytes write frame buffer. + */ +#define HDLL_4K_WRITE_SUPPORTED +#ifdef HDLL_4K_WRITE_SUPPORTED +#define MAX_DL_PAYLOAD_LEN (4096) +#else +#define MAX_DL_PAYLOAD_LEN (550) +#endif +#define MAX_DL_BUFFER_SIZE (DL_HDR_LEN + DL_CRC_LEN + \ + MAX_DL_PAYLOAD_LEN) + + +/* Retry count for normal write */ +#define NO_RETRY (1) +/* Maximum retry count for standby writes */ +#define MAX_RETRY_COUNT (3) +#define MAX_WRITE_IRQ_COUNT (5) +#define MAX_IRQ_WAIT_TIME (90) +#define WAKEUP_SRC_TIMEOUT (2000) + +/* command response timeout */ +#define NCI_CMD_RSP_TIMEOUT_MS (2000) +/* Time to wait for NFCC to be ready again after any change in the GPIO */ +#define NFC_GPIO_SET_WAIT_TIME_US (15000) +/* Time to wait before retrying writes */ +#define WRITE_RETRY_WAIT_TIME_US (3000) +/* Time to wait before retrying read for some specific usecases */ +#define READ_RETRY_WAIT_TIME_US (3500) +#define NFC_MAGIC (0xE9) + +/* Ioctls */ +/* The type should be aligned with MW HAL definitions */ +#define NFC_SET_PWR _IOW(NFC_MAGIC, 0x01, uint64_t) +#define ESE_SET_PWR _IOW(NFC_MAGIC, 0x02, uint64_t) +#define ESE_GET_PWR _IOR(NFC_MAGIC, 0x03, uint64_t) +#define NFC_GET_GPIO_STATUS _IOR(NFC_MAGIC, 0x05, uint64_t) + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +#define CONFIG_SAMSUNG_NFC_DEBUG +#define FEATURE_CORE_RESET_NTF_CHECK + +#define DTS_IRQ_GPIO_STR "pn547,irq-gpio" +#define DTS_VEN_GPIO_STR "pn547,ven-gpio" +#define DTS_FWDN_GPIO_STR "pn547,firm-gpio" + +enum ap_vendors { + AP_VENDOR_NONE, + AP_VENDOR_SLSI, + AP_VENDOR_QCT, + AP_VENDOR_MTK, + AP_VENDOR_ERR +}; + +enum lpm_status { + LPM_NO_SUPPORT = -1, + LPM_FALSE, + LPM_TRUE +}; +#else +#define DTS_IRQ_GPIO_STR "nxp,sn-irq" +#define DTS_VEN_GPIO_STR "nxp,sn-ven-rstn" +#define DTS_FWDN_GPIO_STR "nxp,sn-dwl-req" +#endif + +/* Each GPIO occupies consecutive two bits */ +#define GPIO_POS_SHIFT_VAL 2 +/* Two bits to indicate GPIO status (Invalid(-2), Set(1) or Reset(0)) */ +#define GPIO_STATUS_MASK_BITS 3 + +#ifndef CONFIG_SEC_NFC_LOGGER +#define NFC_LOG_ERR(fmt, ...) pr_err("sec_nfc: "fmt, ##__VA_ARGS__) +#define NFC_LOG_INFO(fmt, ...) pr_info("sec_nfc: "fmt, ##__VA_ARGS__) +#define NFC_LOG_INFO_WITH_DATE(fmt, ...) pr_info("sec_nfc: "fmt, ##__VA_ARGS__) +#define NFC_LOG_DBG(fmt, ...) pr_debug("sec_nfc: "fmt, ##__VA_ARGS__) +#define NFC_LOG_REC(fmt, ...) do { } while (0) + +#define nfc_print_hex_dump(a, b, c) do { } while (0) +#define nfc_logger_init() do { } while (0) +#define nfc_logger_deinit() do { } while (0) +#define nfc_logger_set_max_count(a) do { } while (0) +#define nfc_logger_register_nfc_stauts_func(a) do { } while (0) +#endif /* CONFIG_SEC_NFC_LOGGER */ + +enum nfcc_ioctl_request { + /* NFC disable request with VEN LOW */ + NFC_POWER_OFF = 0, + /* NFC enable request with VEN Toggle */ + NFC_POWER_ON, + /* firmware download request with VEN Toggle */ + NFC_FW_DWL_VEN_TOGGLE, + /* ISO reset request */ + NFC_ISO_RESET, + /* request for firmware download gpio HIGH */ + NFC_FW_DWL_HIGH, + /* VEN hard reset request */ + NFC_VEN_FORCED_HARD_RESET, + /* request for firmware download gpio LOW */ + NFC_FW_DWL_LOW, +}; + +/* nfc platform interface type */ +enum interface_flags { + /* I2C physical IF for NFCC */ + PLATFORM_IF_I2C = 0, +}; + +/* nfc state flags */ +enum nfc_state_flags { + /* nfc in unknown state */ + NFC_STATE_UNKNOWN = 0, + /* nfc in download mode */ + NFC_STATE_FW_DWL = 0x1, + /* nfc booted in NCI mode */ + NFC_STATE_NCI = 0x2, + /* nfc booted in Fw teared mode */ + NFC_STATE_FW_TEARED = 0x4, +}; +/* + * Power state for IBI handing, mainly needed to defer the IBI handling + * for the IBI received in suspend state to do it later in resume call + */ +enum pm_state_flags { + PM_STATE_NORMAL = 0, + PM_STATE_SUSPEND, + PM_STATE_IBI_BEFORE_RESUME, +}; + +/* Enum for GPIO values */ +enum gpio_values { + GPIO_INPUT = 0x0, + GPIO_OUTPUT = 0x1, + GPIO_HIGH = 0x2, + GPIO_OUTPUT_HIGH = 0x3, + GPIO_IRQ = 0x4, +}; + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +#define PLATFORM_DEFAULT_GPIO_CNT 3 +#endif +/* NFC GPIO variables */ +struct platform_gpio { + int irq; + int ven; + int dwl_req; +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + int clk_req; + int clk_req_irq; + bool clk_req_irq_enabled; +#endif +}; + +/* NFC Struct to get all the required configs from DTS */ +struct platform_configs { + struct platform_gpio gpio; +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + bool clk_req_wake; + bool clk_req_all_trigger; + bool change_clkreq_for_acpm; + int ap_vendor; + struct regulator *nfc_pvdd; + struct clk *nfc_clock; + bool late_pvdd_en; + bool disable_clk_irq_during_wakeup; + struct notifier_block ldo_ocp_nb; +#endif +}; + +/* cold reset Features specific Parameters */ +struct cold_reset { + bool rsp_pending; /* cmd rsp pending status */ + bool in_progress; /* for cold reset when gurad timer in progress */ + bool reset_protection; /* reset protection enabled/disabled */ + uint8_t status; /* status from response buffer */ + uint8_t rst_prot_src; /* reset protection source (SPI, NFC) */ + struct timer_list timer; + wait_queue_head_t read_wq; +}; + +/* Device specific structure */ +struct nfc_dev { + wait_queue_head_t read_wq; + struct mutex read_mutex; + struct mutex write_mutex; + uint8_t *read_kbuf; + uint8_t *write_kbuf; + struct mutex dev_ref_mutex; + unsigned int dev_ref_count; + struct class *nfc_class; + struct device *nfc_device; + struct cdev c_dev; + dev_t devno; + /* Interface flag */ + uint8_t interface; + /* nfc state flags */ + uint8_t nfc_state; + /* NFC VEN pin state */ + bool nfc_ven_enabled; + bool release_read; + union { + struct i2c_dev i2c_dev; + }; + struct platform_configs configs; + struct cold_reset cold_reset; + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + struct nfc_wake_lock nfc_wake_lock; + struct nfc_wake_lock nfc_clk_wake_lock; + bool clk_req_wakelock; + bool screen_cfg; + bool screen_on_cmd; + bool screen_off_cmd; + int screen_off_rsp_count; + struct notifier_block reboot_nb; +#endif + + /* function pointers for the common i2c functionality */ + int (*nfc_read)(struct nfc_dev *dev, char *buf, size_t count, + int timeout); + int (*nfc_write)(struct nfc_dev *dev, const char *buf, + const size_t count, int max_retry_cnt); + int (*nfc_enable_intr)(struct nfc_dev *dev); + int (*nfc_disable_intr)(struct nfc_dev *dev); +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + void (*nfc_enable_clk_intr)(struct nfc_dev *dev); + void (*nfc_disable_clk_intr)(struct nfc_dev *dev); +#endif +}; + +int nfc_dev_open(struct inode *inode, struct file *filp); +int nfc_dev_flush(struct file *pfile, fl_owner_t id); +int nfc_dev_close(struct inode *inode, struct file *filp); +long nfc_dev_compat_ioctl(struct file *pfile, unsigned int cmd, + unsigned long arg); +long nfc_dev_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg); +int nfc_parse_dt(struct device *dev, struct platform_configs *nfc_configs, + uint8_t interface); +int nfc_misc_register(struct nfc_dev *nfc_dev, + const struct file_operations *nfc_fops, int count, + char *devname, char *classname); +void nfc_misc_unregister(struct nfc_dev *nfc_dev, int count); +int configure_gpio(unsigned int gpio, int flag); +void gpio_set_ven(struct nfc_dev *nfc_dev, int value); +void gpio_free_all(struct nfc_dev *nfc_dev); +int validate_nfc_state_nci(struct nfc_dev *nfc_dev); +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +int nfc_regulator_onoff(struct nfc_dev *nfc_dev, int onoff); +void nfc_power_control(struct nfc_dev *nfc_dev); +void nfc_print_status(void); +void nfc_probe_done(struct nfc_dev *nfc_dev); +bool nfc_check_pvdd_status(void); +enum lpm_status nfc_get_lpcharge(void); +#ifdef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE +void nfc_parse_dt_for_platform_device(struct device *dev); +#endif +#endif +#endif /* _COMMON_H_ */ diff --git a/drivers/nfc/nxp_combined/common_ese.c b/drivers/nfc/nxp_combined/common_ese.c new file mode 100644 index 000000000000..c114c6910829 --- /dev/null +++ b/drivers/nfc/nxp_combined/common_ese.c @@ -0,0 +1,360 @@ +/****************************************************************************** + * Copyright (C) 2020-2021 NXP + * + * 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 +#include +#include + +#include "common_ese.h" + +static void cold_reset_gaurd_timer_callback(struct timer_list *t) +{ + struct cold_reset *cold_reset = from_timer(cold_reset, t, timer); + + NFC_LOG_REC("%s: entry\n", __func__); + cold_reset->in_progress = false; +} + +static long start_cold_reset_guard_timer(struct cold_reset *cold_reset) +{ + long ret = -EINVAL; + + if (timer_pending(&cold_reset->timer) == 1) { + NFC_LOG_REC("%s: delete pending timer\n", __func__); + /* delete timer if already pending */ + del_timer(&cold_reset->timer); + } + cold_reset->in_progress = true; + timer_setup(&cold_reset->timer, cold_reset_gaurd_timer_callback, 0); + ret = mod_timer(&cold_reset->timer, + jiffies + msecs_to_jiffies(ESE_CLD_RST_GUARD_TIME_MS)); + return ret; +} + +static int send_cold_reset_protection_cmd(struct nfc_dev *nfc_dev, + bool requestType) +{ + int ret = 0; + int cmd_length = 0; + uint8_t *cmd = nfc_dev->write_kbuf; + struct cold_reset *cold_reset = &nfc_dev->cold_reset; + + *cmd++ = NCI_PROP_MSG_CMD; + + if (requestType) { /* reset protection */ + *cmd++ = RST_PROT_OID; + *cmd++ = RST_PROT_PAYLOAD_SIZE; + *cmd++ = (!cold_reset->reset_protection) ? 1 : 0; + } else { /* cold reset */ + *cmd++ = CLD_RST_OID; + *cmd++ = CLD_RST_PAYLOAD_SIZE; + } + cmd_length = cmd - nfc_dev->write_kbuf; + + ret = nfc_dev->nfc_write(nfc_dev, nfc_dev->write_kbuf, cmd_length, + MAX_RETRY_COUNT); + if (ret != cmd_length) { + ret = -EIO; + NFC_LOG_ERR("%s: nfc_write returned %d\n", __func__, ret); + goto exit; + } + cmd = nfc_dev->write_kbuf; + if (requestType) + NFC_LOG_DBG(" %s: NxpNciX: %d > 0x%02x%02x%02x%02x\n", __func__, + ret, cmd[NCI_HDR_IDX], cmd[NCI_HDR_OID_IDX], + cmd[NCI_PAYLOAD_LEN_IDX], cmd[NCI_PAYLOAD_IDX]); + else + NFC_LOG_DBG(" %s: NxpNciX: %d > 0x%02x%02x%02x\n", __func__, ret, + cmd[NCI_HDR_IDX], cmd[NCI_HDR_OID_IDX], + cmd[NCI_PAYLOAD_LEN_IDX]); +exit: + return ret; +} + +void wakeup_on_prop_rsp(struct nfc_dev *nfc_dev, uint8_t *buf) +{ + struct cold_reset *cold_reset = &nfc_dev->cold_reset; + + cold_reset->status = -EIO; + if ((NCI_HDR_LEN + buf[NCI_PAYLOAD_LEN_IDX]) != NCI_PROP_MSG_RSP_LEN) + NFC_LOG_ERR("%s: invalid response for cold_reset/protection\n", + __func__); + else + cold_reset->status = buf[NCI_PAYLOAD_IDX]; + + NFC_LOG_DBG(" %s: NxpNciR 0x%02x%02x%02x%02x\n", __func__, + buf[NCI_HDR_IDX], buf[NCI_HDR_OID_IDX], + buf[NCI_PAYLOAD_LEN_IDX], buf[NCI_PAYLOAD_IDX]); + + cold_reset->rsp_pending = false; + wake_up_interruptible(&cold_reset->read_wq); +} + +static int validate_cold_reset_protection_request(struct cold_reset *cold_reset, + unsigned long arg) +{ + int ret = 0; + + if (!cold_reset->reset_protection) { + if (IS_RST_PROT_EN_REQ(arg) && IS_SRC_VALID_PROT(arg)) { + NFC_LOG_REC("%s: reset protection enable\n", __func__); + } else if (IS_CLD_RST_REQ(arg) && IS_SRC_VALID(arg)) { + NFC_LOG_REC("%s: cold reset\n", __func__); + } else if (IS_RST_PROT_DIS_REQ(arg) && IS_SRC_VALID_PROT(arg)) { + NFC_LOG_REC("%s: reset protection already disable\n", + __func__); + ret = -EINVAL; + } else { + NFC_LOG_ERR("%s: operation not permitted\n", __func__); + ret = -EPERM; + } + } else { + if (IS_RST_PROT_DIS_REQ(arg) && + IS_SRC(arg, cold_reset->rst_prot_src)) { + NFC_LOG_REC("%s: disable reset protection from same src\n", + __func__); + } else if (IS_CLD_RST_REQ(arg) && + IS_SRC(arg, cold_reset->rst_prot_src)) { + NFC_LOG_REC("%s: cold reset from same source\n", __func__); + } else if (IS_RST_PROT_EN_REQ(arg) && + IS_SRC(arg, cold_reset->rst_prot_src)) { + NFC_LOG_REC("%s: enable reset protection from same src\n", + __func__); + } else { + NFC_LOG_ERR("%s: operation not permitted\n", __func__); + ret = -EPERM; + } + } + return ret; +} + +static int perform_cold_reset_protection(struct nfc_dev *nfc_dev, + unsigned long arg) +{ + int ret = 0; + int timeout = 0; + char *rsp = nfc_dev->read_kbuf; + struct cold_reset *cold_reset = &nfc_dev->cold_reset; + + /* check if NFCC not in the FW download or hard reset state */ + ret = validate_nfc_state_nci(nfc_dev); + if (ret < 0) { + NFC_LOG_ERR("%s: invalid state\n", __func__); + return ret; + } + + /* check if NFCC not in the FW download or hard reset state */ + ret = validate_cold_reset_protection_request(cold_reset, arg); + if (ret < 0) { + NFC_LOG_ERR("%s: invalid cmd\n", __func__); + goto err; + } + + /* check if cold reset already in progress */ + if (IS_CLD_RST_REQ(arg) && cold_reset->in_progress) { + NFC_LOG_ERR("%s: cold reset already in progress\n", __func__); + ret = -EBUSY; + goto err; + } + + /* enable interrupt if not enabled incase when devnode not opened by HAL */ + nfc_dev->nfc_enable_intr(nfc_dev); + + mutex_lock(&nfc_dev->write_mutex); + /* write api has 15ms maximum wait to clear any pending read before */ + cold_reset->status = -EIO; + cold_reset->rsp_pending = true; + + ret = send_cold_reset_protection_cmd(nfc_dev, IS_RST_PROT_REQ(arg)); + if (ret < 0) { + mutex_unlock(&nfc_dev->write_mutex); + cold_reset->rsp_pending = false; + NFC_LOG_ERR("%s: failed to send cold reset/protection cmd\n", + __func__); + goto err; + } + ret = 0; + /* start the cold reset guard timer */ + if (IS_CLD_RST_REQ(arg)) { + /* Guard timer not needed when OSU over NFC */ + if (!(cold_reset->reset_protection && IS_SRC_NFC(arg))) { + ret = start_cold_reset_guard_timer(cold_reset); + if (ret) { + mutex_unlock(&nfc_dev->write_mutex); + NFC_LOG_ERR("%s: error in mod_timer\n", __func__); + goto err; + } + } + } + + timeout = NCI_CMD_RSP_TIMEOUT_MS; + do { + /* call read api directly if reader thread is not blocked */ + if (mutex_trylock(&nfc_dev->read_mutex)) { + pr_debug("%s: reader thread not pending\n", __func__); + ret = nfc_dev->nfc_read(nfc_dev, rsp, 3, + timeout); + mutex_unlock(&nfc_dev->read_mutex); + if (!ret) + break; + usleep_range(READ_RETRY_WAIT_TIME_US, + READ_RETRY_WAIT_TIME_US + 500); + /* Read pending response form the HAL service */ + } else if (!wait_event_interruptible_timeout( + cold_reset->read_wq, + cold_reset->rsp_pending == false, + msecs_to_jiffies(timeout))) { + pr_err("%s: cold reset/prot response timeout\n", __func__); + ret = -EAGAIN; + } + } while (ret == -ERESTARTSYS || ret == -EFAULT); + mutex_unlock(&nfc_dev->write_mutex); + + timeout = ESE_CLD_RST_REBOOT_GUARD_TIME_MS; + if (ret == 0) { /* success case */ + ret = cold_reset->status; + if (IS_RST_PROT_REQ(arg)) { + cold_reset->reset_protection = IS_RST_PROT_EN_REQ(arg); + cold_reset->rst_prot_src = IS_RST_PROT_EN_REQ(arg) ? + GET_SRC(arg) : + SRC_NONE; + /* wait for reboot guard timer */ + } else if (wait_event_interruptible_timeout( + cold_reset->read_wq, true, + msecs_to_jiffies(timeout)) == 0) { + NFC_LOG_INFO("%s: reboot guard timer timeout\n", __func__); + } + } +err: + return ret; +} + +/** + * nfc_ese_pwr() - power control for ese + * @nfc_dev: nfc device data structure + * @arg: mode that we want to move to + * + * Device power control. Depending on the arg value, device moves to + * different states, refer common_ese.h for args + * + * Return: -ENOIOCTLCMD if arg is not supported + * 0 if Success(or no issue) + * 0 or 1 in case of arg is ESE_POWER_STATE + * and error ret code otherwise + */ +int nfc_ese_pwr(struct nfc_dev *nfc_dev, unsigned long arg) +{ + int ret = 0; + struct platform_gpio *nfc_gpio = &nfc_dev->configs.gpio; + + 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 + */ + nfc_dev->nfc_ven_enabled = gpio_get_value(nfc_gpio->ven); + if (!nfc_dev->nfc_ven_enabled) { + NFC_LOG_REC("%s: ese hal service setting ven high\n", + __func__); + gpio_set_ven(nfc_dev, 1); + } else { + NFC_LOG_REC("%s: ven already high\n", __func__); + } + } else if (arg == ESE_POWER_OFF) { + if (!nfc_dev->nfc_ven_enabled) { + NFC_LOG_REC("%s: nfc not enabled, disabling ven\n", + __func__); + gpio_set_ven(nfc_dev, 0); + } else { + NFC_LOG_REC("%s: keep ven high as nfc is enabled\n", + __func__); + } + } else if (arg == ESE_POWER_STATE) { + /* eSE get power state */ + ret = gpio_get_value(nfc_gpio->ven); + } else if (IS_CLD_RST_REQ(arg) || IS_RST_PROT_REQ(arg)) { + ret = perform_cold_reset_protection(nfc_dev, arg); + } else { + NFC_LOG_ERR("%s: bad arg %lu\n", __func__, arg); + ret = -ENOIOCTLCMD; + } + return ret; +} +EXPORT_SYMBOL(nfc_ese_pwr); + +#define ESE_LEGACY_INTERFACE +#ifdef ESE_LEGACY_INTERFACE +static struct nfc_dev *nfc_dev_legacy; + +/****************************************************************************** + * perform_ese_cold_reset() - It shall be called by others driver(not nfc/ese) + * to perform cold reset only + * @arg: request of cold reset from other drivers should be ESE_CLD_RST_OTHER + * + * Returns:- 0 in case of success and negative values in case of failure + *****************************************************************************/ +int perform_ese_cold_reset(unsigned long arg) +{ + int ret = 0; + + if (nfc_dev_legacy) { + if (IS_CLD_RST_REQ(arg) && IS_SRC_OTHER(arg)) { + ret = nfc_ese_pwr(nfc_dev_legacy, arg); + } else { + NFC_LOG_ERR("%s: operation not permitted\n", __func__); + return -EPERM; + } + } + NFC_LOG_DBG("%s: arg = %lu ret = %d\n", __func__, arg, ret); + return ret; +} +EXPORT_SYMBOL(perform_ese_cold_reset); +#endif /* ESE_LEGACY_INTERFACE */ + +void ese_cold_reset_release(struct nfc_dev *nfc_dev) +{ + struct cold_reset *cold_reset = &nfc_dev->cold_reset; + + cold_reset->rsp_pending = false; + cold_reset->in_progress = false; + if (timer_pending(&cold_reset->timer) == 1) + del_timer(&cold_reset->timer); +} + +void common_ese_init(struct nfc_dev *nfc_dev) +{ + struct cold_reset *cold_reset = &nfc_dev->cold_reset; + + cold_reset->reset_protection = false; + cold_reset->rst_prot_src = SRC_NONE; + init_waitqueue_head(&cold_reset->read_wq); + ese_cold_reset_release(nfc_dev); +#ifdef ESE_LEGACY_INTERFACE + nfc_dev_legacy = nfc_dev; +#endif /* ESE_LEGACY_INTERFACE */ +} + +void common_ese_exit(struct nfc_dev *nfc_dev) +{ +#ifdef ESE_LEGACY_INTERFACE + nfc_dev_legacy = NULL; +#endif /* ESE_LEGACY_INTERFACE */ +} diff --git a/drivers/nfc/nxp_combined/common_ese.h b/drivers/nfc/nxp_combined/common_ese.h new file mode 100644 index 000000000000..45863419ea95 --- /dev/null +++ b/drivers/nfc/nxp_combined/common_ese.h @@ -0,0 +1,101 @@ +/****************************************************************************** + * Copyright (C) 2020-2021 NXP + * + * 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 _COMMON_ESE_H_ +#define _COMMON_ESE_H_ + +#include "common.h" + +/* nci prop msg 1st byte */ +#define NCI_PROP_MSG_GID 0x0F +#define NCI_PROP_MSG_CMD (NCI_CMD | NCI_PROP_MSG_GID) +#define NCI_PROP_MSG_RSP (NCI_RSP | NCI_PROP_MSG_GID) + +/* nci prop msg 2nd byte */ +#define CLD_RST_OID 0x1E +#define RST_PROT_OID 0x1F + +/* nci prop msg 3rd byte */ +#define CLD_RST_PAYLOAD_SIZE 0x00 +#define RST_PROT_PAYLOAD_SIZE 0x01 + +/* nci prop msg response length */ +#define NCI_PROP_MSG_RSP_LEN 0x04 + +/* cold reset guard time to allow back to back cold reset after some time */ +#define ESE_CLD_RST_GUARD_TIME_MS (3000) +/* guard time to reboot after reset */ +#define ESE_CLD_RST_REBOOT_GUARD_TIME_MS (50) +/* sources of reset protection and cold reset */ +enum reset_source { + SRC_SPI = 0, + SRC_NFC = 0x10, + SRC_OTHER = 0x20, + SRC_NONE = 0x80, +}; + +enum ese_ioctl_request { + ESE_POWER_ON = 0, /* eSE POWER ON */ + ESE_POWER_OFF, /* eSE POWER OFF */ + ESE_POWER_STATE, /* eSE GET POWER STATE */ + + /* ese reset requests from eSE service/hal/driver */ + ESE_CLD_RST, /* eSE COLD RESET */ + ESE_RST_PROT_EN, /* eSE RESET PROTECTION ENABLE */ + ESE_RST_PROT_DIS, /* eSE RESET PROTECTION DISABLE */ + + /* similar ese reset requests from nfc service/hal/driver */ + ESE_CLD_RST_NFC = ESE_CLD_RST | SRC_NFC, + ESE_RST_PROT_EN_NFC = ESE_RST_PROT_EN | SRC_NFC, + ESE_RST_PROT_DIS_NFC = ESE_RST_PROT_DIS | SRC_NFC, + + /* similar ese reset requests from other service/hal/driver */ + ESE_CLD_RST_OTHER = ESE_CLD_RST | SRC_OTHER, +}; + +#define GET_SRC(arg) (arg & 0xF0) +#define IS_SRC(arg, src) (GET_SRC(arg) == src) +#define IS_SRC_SPI(arg) IS_SRC(arg, SRC_SPI) +#define IS_SRC_NFC(arg) IS_SRC(arg, SRC_NFC) +#define IS_SRC_OTHER(arg) IS_SRC(arg, SRC_OTHER) +#define IS_SRC_VALID(arg) (IS_SRC_SPI(arg) || \ + IS_SRC_NFC(arg) || \ + IS_SRC_OTHER(arg)) +#define IS_SRC_VALID_PROT(arg) (IS_SRC_SPI(arg) || \ + IS_SRC_NFC(arg)) + +#define IS_RST(arg, type) ((arg & 0xF) == type) +#define IS_CLD_RST_REQ(arg) IS_RST(arg, ESE_CLD_RST) +#define IS_RST_PROT_EN_REQ(arg) IS_RST(arg, ESE_RST_PROT_EN) +#define IS_RST_PROT_DIS_REQ(arg) IS_RST(arg, ESE_RST_PROT_DIS) +#define IS_RST_PROT_REQ(arg) (IS_RST_PROT_EN_REQ(arg) || \ + IS_RST_PROT_DIS_REQ(arg)) +/* This macro evaluates to 1 if prop cmd response is received */ +#define IS_PROP_CMD_RSP(buf) ((buf[0] == NCI_PROP_MSG_RSP) && \ + ((buf[1] == CLD_RST_OID) || \ + (buf[1] == RST_PROT_OID))) + +void wakeup_on_prop_rsp(struct nfc_dev *nfc_dev, uint8_t *buf); +int nfc_ese_pwr(struct nfc_dev *nfc_dev, unsigned long arg); +void ese_cold_reset_release(struct nfc_dev *nfc_dev); +void common_ese_init(struct nfc_dev *nfc_dev); +void common_ese_exit(struct nfc_dev *nfc_dev); +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +void ese_set_spi_pinctrl_for_ese_off(void *p61); +#endif +#endif /* _COMMON_ESE_H_ */ diff --git a/drivers/nfc/nxp_combined/ese_reset.c b/drivers/nfc/nxp_combined/ese_reset.c new file mode 100644 index 000000000000..8999eac0190c --- /dev/null +++ b/drivers/nfc/nxp_combined/ese_reset.c @@ -0,0 +1,160 @@ +/****************************************************************************** + * + * Copyright 2023 NXP + * + * 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 +#include +#include +#include "common_ese.h" +#include "ese_reset.h" + +static reset_timer_t sResetTimer; + +/*! + * \ingroup spi_driver + * \brief Function getting invoked after eSE reset guard time + * \n expiry + * \param[in] struct timer_list * + */ +static void gpio_reset_guard_timer_callback(struct timer_list *t) +{ + struct reset_timer *sResetTimer = from_timer(sResetTimer, t, timer); + + NFC_LOG_INFO("%s: entry\n", __func__); + sResetTimer->in_progress = false; + NFC_LOG_INFO("%s: exit with in_progress set to false\n", __func__); +} + +/*! + * \ingroup spi_driver + * \brief Initialising mutex + */ +void ese_reset_init(void) +{ + mutex_init(&sResetTimer.reset_mutex); + timer_setup(&sResetTimer.timer, gpio_reset_guard_timer_callback, 0); +} + +/*! + * \ingroup spi_driver + * \brief Deinitialising mutex + */ +void ese_reset_deinit(void) +{ + mutex_destroy(&sResetTimer.reset_mutex); + del_timer(&sResetTimer.timer); +} + +/*! + * \ingroup spi_driver + * \brief Setting the timer on + * \return long 0 for inactive timer 1 for active timer + */ +static long start_gpio_reset_guard_timer(void) +{ + long ret; + + NFC_LOG_INFO("%s: entry\n", __func__); + ret = mod_timer(&sResetTimer.timer, + jiffies + msecs_to_jiffies(ESE_GPIO_RST_GUARD_TIME_MS)); + if (!ret) + sResetTimer.in_progress = true; + else + NFC_LOG_ERR("%s: Error in mod_timer, returned:'%ld'\n", __func__, ret); + NFC_LOG_INFO("%s: exit\n", __func__); + return ret; +} + +/*! + * \ingroup spi_driver + * \brief Reset the gpio by toggling low and high state + * \param struct p61_dev + */ +int perform_ese_gpio_reset(int rst_gpio) +{ + int ret = 0; + + if (gpio_is_valid(rst_gpio)) { + NFC_LOG_INFO("%s: entry\n", __func__); + mutex_lock(&sResetTimer.reset_mutex); + if (sResetTimer.in_progress) { + NFC_LOG_ERR("%s: gpio reset already in progress\n", __func__); + ret = -EBUSY; + mutex_unlock(&sResetTimer.reset_mutex); + return ret; + } + NFC_LOG_INFO("%s: entering gpio ese reset case\n", __func__); + ret = start_gpio_reset_guard_timer(); + if (ret) { + mutex_unlock(&sResetTimer.reset_mutex); + NFC_LOG_ERR("%s: error in mod_timer\n", __func__); + ret = -EINVAL; + return ret; + } + NFC_LOG_INFO(" eSE Domain Reset"); + + gpio_set_value(rst_gpio, 0); + usleep_range(ESE_GPIO_RESET_WAIT_TIME_USEC, + ESE_GPIO_RESET_WAIT_TIME_USEC + 100); + gpio_set_value(rst_gpio, 1); + NFC_LOG_INFO("%s: exit\n", __func__); + mutex_unlock(&sResetTimer.reset_mutex); + } else { + NFC_LOG_ERR("%s not entering GPIO Reset, gpio value invalid : %x\n", + __func__, rst_gpio); + return -EPERM; + } + return ret; +} + +/*! + * \ingroup spi_driver + * \brief setup ese reset gpio pin as an output pin + * \param struct p61_spi_platform_data * + * \return Return 0 in case of success, else an error code + */ +int ese_reset_gpio_setup(struct p61_spi_platform_data *platform_data) +{ + int ret = -1; + + if (gpio_is_valid(platform_data->rst_gpio)) { + ret = gpio_request(platform_data->rst_gpio, "p61 reset"); + if (ret < 0) { + NFC_LOG_ERR("reset gpio 0x%x request failed\n", platform_data->rst_gpio); + return ret; + } + + /* reset gpio is set to default high */ + ret = gpio_direction_output(platform_data->rst_gpio, 1); + if (ret < 0) { + NFC_LOG_ERR("Failed to set the direction of reset gpio=0x%x\n", + platform_data->rst_gpio); + goto fail_gpio; + } + NFC_LOG_INFO("Exit : %s\n", __func__); + } else { + NFC_LOG_INFO("%s, gpio value invalid : %x\n", __func__, + platform_data->rst_gpio); + } + return ret; +fail_gpio: + if (gpio_is_valid(platform_data->rst_gpio)) + gpio_free(platform_data->rst_gpio); + + return ret; +} diff --git a/drivers/nfc/nxp_combined/ese_reset.h b/drivers/nfc/nxp_combined/ese_reset.h new file mode 100644 index 000000000000..9fc015181f61 --- /dev/null +++ b/drivers/nfc/nxp_combined/ese_reset.h @@ -0,0 +1,59 @@ +/****************************************************************************** + * + * Copyright 2023 NXP Semiconductors + * + * 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 _ESE_RESET_H_ +#define _ESE_RESET_H_ +#include +#include +#include +#include +#include +#include "p73.h" +/*! + * \brief Delay time required for ese reset by gpio + */ +#define ESE_GPIO_RESET_WAIT_TIME_USEC (12000) + +/*! + * \brief Guard time delay to prevent back to back gpio reset requests + */ +#define ESE_GPIO_RST_GUARD_TIME_MS (50) + +/*! + * \brief Argument values to be passed to ioctl call + */ +#define ESE_SOFT_RESET (1UL) +#define ESE_HARD_RESET (2UL) +#define ESE_DOMAIN_RESET (5UL) + +/*! + * \brief To denote gpio reset currently in progress + */ +typedef struct reset_timer { + int rst_gpio; + bool in_progress; + struct mutex reset_mutex; + struct timer_list timer; +} reset_timer_t; + +void ese_reset_init(void); +int perform_ese_gpio_reset(int rst_gpio); +void ese_reset_deinit(void); +int ese_reset_gpio_setup(struct p61_spi_platform_data *data); +#endif diff --git a/drivers/nfc/nxp_combined/i2c_drv.c b/drivers/nfc/nxp_combined/i2c_drv.c new file mode 100644 index 000000000000..70928c7bafcd --- /dev/null +++ b/drivers/nfc/nxp_combined/i2c_drv.c @@ -0,0 +1,1212 @@ +/****************************************************************************** + * Copyright (C) 2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2013-2022 NXP + * + * 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 + * + ******************************************************************************/ +/* + * Copyright (C) 2010 Trusted Logic S.A. + * + * 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 +#include +#include +#include +#include +#ifdef CONFIG_COMPAT +#include +#endif +#ifdef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE +#include +#endif + +#include "common_ese.h" +#include "p73.h" +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +#include +#include +#include "common.h" + +static int nfc_reboot_cb(struct notifier_block *nb, + unsigned long action, void *data); +#endif + +#ifdef FEATURE_CORE_RESET_NTF_CHECK +struct nfc_core_reset_type { + char name[16]; + int data_len; + u8 err_type; +}; + +/* if Power Reset comes after NFC on, it's normal operation */ +struct nfc_core_reset_type core_reset[] = { + {"Unrecover", 0xA, 0x00}, + {"Power Reset", 0x9, 0x01}, /* at new version */ + {"Power Reset", 0xA, 0x01}, /* at old version */ + {"Assert", 0x6, 0xA0}, + {"Clock lost", 0x6, 0xA4} +}; + +static bool is_core_reset; + +bool nfc_check_core_reset_type(u8 *data, int len) +{ + int arr_size = ARRAY_SIZE(core_reset); + int i; + bool found = false; + + for (i = 0; i < arr_size; i++) { + if (core_reset[i].data_len == len && + core_reset[i].err_type == data[0]) { + found = true; + break; + } + } + + if (found) { + char info[64] = {0x0, }; + int j, idx = 0; + + idx = snprintf(info, 64, "6000%02X", len); + for (j = 0; j < len; j++) + idx += snprintf(info + idx, 64 - idx, "%02X", data[j]); + NFC_LOG_INFO("CORE_RESET: %s (%s)\n", core_reset[i].name, info); + nfc_print_status(); + } + + return found; +} + +void nfc_check_is_core_reset_ntf(u8 *data, int len) +{ + if (is_core_reset) { + /* check payload */ + nfc_check_core_reset_type(data, len); + is_core_reset = false; + } else if (len == 3) { /* check header */ + /* check if it's core reset ntf */ + if (data[0] == 0x60 && data[1] == 0x00) { + switch (data[2]) { + case 0x06: + case 0x0A: + is_core_reset = true; + break; + default: + is_core_reset = false; + } + } else { + is_core_reset = false; + } + } +} +#endif + +/** + * i2c_disable_irq() + * + * Check if interrupt is disabled or not + * and disable interrupt + * + * Return: int + */ +int i2c_disable_irq(struct nfc_dev *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->i2c_dev.irq_enabled_lock, flags); + if (dev->i2c_dev.irq_enabled) { + disable_irq_nosync(dev->i2c_dev.client->irq); + dev->i2c_dev.irq_enabled = false; + } + spin_unlock_irqrestore(&dev->i2c_dev.irq_enabled_lock, flags); + + return 0; +} + +/** + * i2c_enable_irq() + * + * Check if interrupt is enabled or not + * and enable interrupt + * + * Return: int + */ +int i2c_enable_irq(struct nfc_dev *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->i2c_dev.irq_enabled_lock, flags); + if (!dev->i2c_dev.irq_enabled) { + dev->i2c_dev.irq_enabled = true; + enable_irq(dev->i2c_dev.client->irq); + } + spin_unlock_irqrestore(&dev->i2c_dev.irq_enabled_lock, flags); + + return 0; +} + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +void i2c_disable_clk_irq(struct nfc_dev *dev) +{ + struct platform_configs *nfc_configs = &dev->configs; + struct platform_gpio *nfc_gpio = &dev->configs.gpio; + + if (nfc_configs->clk_req_wake || nfc_configs->clk_req_all_trigger) { + if (nfc_gpio->clk_req_irq_enabled) { + disable_irq_nosync(nfc_gpio->clk_req_irq); + nfc_gpio->clk_req_irq_enabled = false; + } + dev->clk_req_wakelock = false; + } +} + +void i2c_enable_clk_irq(struct nfc_dev *dev) +{ + struct platform_configs *nfc_configs = &dev->configs; + struct platform_gpio *nfc_gpio = &dev->configs.gpio; + + if (nfc_configs->clk_req_wake || nfc_configs->clk_req_all_trigger) { + if (!nfc_gpio->clk_req_irq_enabled) { + enable_irq(nfc_gpio->clk_req_irq); + nfc_gpio->clk_req_irq_enabled = true; + } + } +} +#endif + +static irqreturn_t i2c_irq_handler(int irq, void *dev_id) +{ + struct nfc_dev *nfc_dev = dev_id; + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + wake_lock_timeout(&nfc_dev->nfc_wake_lock, 2*HZ); +#else + struct i2c_dev *i2c_dev = &nfc_dev->i2c_dev; + + if (device_may_wakeup(&i2c_dev->client->dev)) + pm_wakeup_event(&i2c_dev->client->dev, WAKEUP_SRC_TIMEOUT); +#endif + + i2c_disable_irq(nfc_dev); + wake_up(&nfc_dev->read_wq); + + NFC_LOG_REC("irq\n"); + + return IRQ_HANDLED; +} + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +static irqreturn_t nfc_clk_req_irq_handler(int irq, void *dev_id) +{ + struct nfc_dev *nfc_dev = dev_id; + struct platform_gpio *nfc_gpio = &nfc_dev->configs.gpio; + struct platform_configs *nfc_configs = &nfc_dev->configs; + static bool nfc_clk_status; + + if (nfc_configs->change_clkreq_for_acpm) + return IRQ_HANDLED; + + if (nfc_configs->clk_req_all_trigger) { + if (gpio_get_value(nfc_gpio->clk_req)) { + NFC_LOG_REC("clk_req 1\n"); + if (!wake_lock_active(&nfc_dev->nfc_clk_wake_lock)) + wake_lock(&nfc_dev->nfc_clk_wake_lock); + + if (nfc_configs->nfc_clock && !nfc_clk_status) { + if (clk_prepare_enable(nfc_configs->nfc_clock)) { + NFC_LOG_REC("clock enable failed\n"); + return IRQ_HANDLED; + } + nfc_clk_status = true; + } + } else { + NFC_LOG_REC("clk_req 0\n"); + if (wake_lock_active(&nfc_dev->nfc_clk_wake_lock)) + wake_unlock(&nfc_dev->nfc_clk_wake_lock); + + if (nfc_configs->nfc_clock && nfc_clk_status) { + clk_disable_unprepare(nfc_configs->nfc_clock); + nfc_clk_status = false; + } + } + } else { + NFC_LOG_REC("clk_req w:%d\n", nfc_dev->clk_req_wakelock); + if (nfc_dev->clk_req_wakelock) { + nfc_dev->clk_req_wakelock = false; + wake_lock_timeout(&nfc_dev->nfc_clk_wake_lock, 2*HZ); + } + } + return IRQ_HANDLED; +} + +#define PRINT_NFC_BUF 0 +#if PRINT_NFC_BUF +static void print_hex_buf(const char *tag, const char *data, int len) +{ + pr_info("%s len: %d\n", tag, len); + print_hex_dump(KERN_DEBUG, tag, DUMP_PREFIX_OFFSET, 16, 8, + data, len, false); +} +#else +static void print_hex_buf(const char *tag, const char *data, int len) +{ + do {} while (0); +} +#endif/*PRINT_NFC_BUF*/ + +static void secnfc_check_screen_on_rsp(struct nfc_dev *nfc_dev, + const char *buf, size_t count) +{ + if (nfc_dev->screen_on_cmd && nfc_dev->screen_cfg) { + nfc_dev->screen_on_cmd = false; + nfc_dev->screen_cfg = false; + NFC_LOG_INFO("scrn_on\n"); + } +} + +static void secnfc_check_screen_off_rsp(struct nfc_dev *nfc_dev, + const char *buf, size_t count) +{ + if (!nfc_dev->screen_off_cmd || !nfc_dev->screen_cfg) { + nfc_dev->screen_off_rsp_count = 0; + return; + } + + if (!nfc_dev->screen_off_rsp_count && count == 3/*header*/) { + nfc_dev->screen_off_rsp_count++; + return; + } + + if (nfc_dev->screen_off_rsp_count == 1/*payload*/) { + if (!buf[0]) {//00 or 0000 + if (wake_lock_active(&nfc_dev->nfc_wake_lock)) + wake_unlock(&nfc_dev->nfc_wake_lock); + NFC_LOG_INFO("scrn_off\n"); + + nfc_dev->screen_cfg = false; + nfc_dev->screen_off_cmd = false; + } + nfc_dev->screen_off_rsp_count = 0; + return; + } + + nfc_dev->screen_off_rsp_count = 0; +} +#endif + +int i2c_read(struct nfc_dev *nfc_dev, char *buf, size_t count, int timeout) +{ + int ret; + struct i2c_dev *i2c_dev = &nfc_dev->i2c_dev; + struct platform_gpio *nfc_gpio = &nfc_dev->configs.gpio; + + NFC_LOG_REC("rd: %zu\n", count); + + if (timeout > NCI_CMD_RSP_TIMEOUT_MS) + timeout = NCI_CMD_RSP_TIMEOUT_MS; + + if (count > MAX_NCI_BUFFER_SIZE) + count = MAX_NCI_BUFFER_SIZE; + + if (!gpio_get_value(nfc_gpio->irq)) { + while (1) { + ret = 0; + i2c_enable_irq(nfc_dev); + if (!gpio_get_value(nfc_gpio->irq)) { + if (timeout) { + ret = wait_event_interruptible_timeout( + nfc_dev->read_wq, + !i2c_dev->irq_enabled, + msecs_to_jiffies(timeout)); + if (ret <= 0) { + NFC_LOG_ERR("%s: timeout error\n", + __func__); + goto err; + } + } else { + ret = wait_event_interruptible( + nfc_dev->read_wq, + !i2c_dev->irq_enabled); + if (ret) { + NFC_LOG_ERR("%s: err wakeup of wq\n", + __func__); + goto err; + } + } + } + i2c_disable_irq(nfc_dev); + + if (gpio_get_value(nfc_gpio->irq)) + break; + if (!gpio_get_value(nfc_gpio->ven)) { + NFC_LOG_ERR("%s: releasing read\n", __func__); + ret = -EIO; + goto err; + } + /* + * NFC service wanted to close the driver so, + * release the calling reader thread asap. + * + * This can happen in case of nfc node close call from + * eSE HAL in that case the NFC HAL reader thread + * will again call read system call + */ + if (nfc_dev->release_read) { + NFC_LOG_REC("%s: releasing read\n", __func__); + return 0; + } + NFC_LOG_ERR("%s: spurious interrupt detected\n", __func__); + } + } + + memset(buf, 0x00, count); + /* Read data */ + ret = i2c_master_recv(nfc_dev->i2c_dev.client, buf, count); + if (ret <= 0) { + NFC_LOG_ERR("%s: returned %d\n", __func__, ret); + goto err; + } +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + print_hex_buf("nfc_rd: ", buf, count); + secnfc_check_screen_on_rsp(nfc_dev, buf, ret); + secnfc_check_screen_off_rsp(nfc_dev, buf, ret); +#ifdef FEATURE_CORE_RESET_NTF_CHECK + nfc_check_is_core_reset_ntf(buf, ret); +#endif +#endif + /* check if it's response of cold reset command + * NFC HAL process shouldn't receive this data as + * command was sent by driver + */ + if (nfc_dev->cold_reset.rsp_pending) { + if (IS_PROP_CMD_RSP(buf)) { + /* Read data */ + ret = i2c_master_recv(nfc_dev->i2c_dev.client, + &buf[NCI_PAYLOAD_IDX], + buf[NCI_PAYLOAD_LEN_IDX]); + if (ret <= 0) { + NFC_LOG_ERR("%s: error reading cold rst/prot rsp\n", + __func__); + goto err; + } + wakeup_on_prop_rsp(nfc_dev, buf); + /* + * 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; + } + } +err: + return ret; +} + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +#define SCREEN_ONOFF_CMD_SZ 4 +#define SCREEN_SET_CFG_SZ 7 + +static bool secnfc_check_screen_on_cmd(struct nfc_dev *nfc_dev, + const char *buf, size_t count) +{ + if (count != SCREEN_ONOFF_CMD_SZ) + return false; + + if (buf[0] == 0x20 && buf[1] == 0x09 && buf[2] == 0x01 && buf[3] == 0x2) + return true; + + return false; +} + +static bool secnfc_check_screen_off_cmd(struct nfc_dev *nfc_dev, + const char *buf, size_t count) +{ + if (count != SCREEN_ONOFF_CMD_SZ) + return false; + + if (buf[0] == 0x20 && buf[1] == 0x09 && buf[2] == 0x01 && + (buf[3] == 0x1 || buf[3] == 0x3)) + return true; + + return false; +} + +static bool secnfc_check_screen_cfg(struct nfc_dev *nfc_dev, + const char *buf, size_t count) +{ + if (count != SCREEN_SET_CFG_SZ) + return false; + + if (buf[0] == 0x20 && buf[1] == 0x02 && buf[2] == 0x04 && + buf[3] == 0x01 && buf[4] == 0x02 && + buf[5] == 0x01 && buf[6] == 0x00) + return true; + + return false; +} + +static void secnfc_configure_clk_irq_based_on_screen_onoff(struct nfc_dev *nfc_dev, bool is_screen_on) +{ + struct platform_configs *nfc_configs = &nfc_dev->configs; + + if (!nfc_configs->disable_clk_irq_during_wakeup) + return; + + if (is_screen_on) + i2c_disable_clk_irq(nfc_dev); + else + i2c_enable_clk_irq(nfc_dev); +} + +static void secnfc_check_screen_onoff(struct nfc_dev *nfc_dev, + const char *buf, size_t count) +{ + if (secnfc_check_screen_cfg(nfc_dev, buf, count)) { + nfc_dev->screen_cfg = true; + } else if (secnfc_check_screen_on_cmd(nfc_dev, buf, count)) { + nfc_dev->screen_on_cmd = true; + secnfc_configure_clk_irq_based_on_screen_onoff(nfc_dev, true); + } else if (secnfc_check_screen_off_cmd(nfc_dev, buf, count)) { + nfc_dev->screen_off_cmd = true; + secnfc_configure_clk_irq_based_on_screen_onoff(nfc_dev, false); + } else { + nfc_dev->screen_on_cmd = false; + nfc_dev->screen_off_cmd = false; + nfc_dev->screen_cfg = false; + } +} +#endif + +int i2c_write(struct nfc_dev *nfc_dev, const char *buf, size_t count, + int max_retry_cnt) +{ + int ret = -EINVAL; + int retry_cnt; + struct platform_gpio *nfc_gpio = &nfc_dev->configs.gpio; + + if (count > MAX_DL_BUFFER_SIZE) + count = MAX_DL_BUFFER_SIZE; + + NFC_LOG_REC("wr: %zu\n", count); +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + print_hex_buf("nfc_wr: ", buf, count); + secnfc_check_screen_onoff(nfc_dev, buf, count); +#endif + /* + * Wait for any pending read for max 15ms before write + * This is to avoid any packet corruption during read, when + * the host cmds resets NFCC during any parallel read operation + */ + for (retry_cnt = 1; retry_cnt <= MAX_WRITE_IRQ_COUNT; retry_cnt++) { + if (gpio_get_value(nfc_gpio->irq)) { + NFC_LOG_ERR("%s: irq high during write, wait\n", __func__); + usleep_range(WRITE_RETRY_WAIT_TIME_US, + WRITE_RETRY_WAIT_TIME_US + 100); + } else { + break; + } + if (retry_cnt == MAX_WRITE_IRQ_COUNT && + gpio_get_value(nfc_gpio->irq)) { + NFC_LOG_ERR("%s: allow after maximum wait\n", __func__); + } + } + + for (retry_cnt = 1; retry_cnt <= max_retry_cnt; retry_cnt++) { + ret = i2c_master_send(nfc_dev->i2c_dev.client, buf, count); + if (ret <= 0) { + NFC_LOG_ERR("%s: write failed ret(%d), maybe in standby\n", + __func__, ret); + usleep_range(WRITE_RETRY_WAIT_TIME_US, + WRITE_RETRY_WAIT_TIME_US + 100); + } else if (ret != count) { + NFC_LOG_ERR("%s: failed to write %d\n", __func__, ret); + ret = -EIO; + } else if (ret == count) + break; + } + return ret; +} + +ssize_t nfc_i2c_dev_read(struct file *filp, char __user *buf, size_t count, + loff_t *offset) +{ + int ret; + struct nfc_dev *nfc_dev = (struct nfc_dev *)filp->private_data; + + if (!nfc_dev) { + NFC_LOG_ERR("%s: device doesn't exist anymore\n", __func__); + return -ENODEV; + } +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + if (count > MAX_NCI_BUFFER_SIZE) { + //NFC_LOG_ERR("%s: too big count %u\n", __func__, count); + return -EINVAL; + } +#endif + mutex_lock(&nfc_dev->read_mutex); + if (filp->f_flags & O_NONBLOCK) { + ret = i2c_master_recv(nfc_dev->i2c_dev.client, nfc_dev->read_kbuf, count); + NFC_LOG_ERR("%s: NONBLOCK read ret = %d\n", __func__, ret); + } else { + ret = i2c_read(nfc_dev, nfc_dev->read_kbuf, count, 0); + } + if (ret > 0) { + if (copy_to_user(buf, nfc_dev->read_kbuf, ret)) { + NFC_LOG_ERR("%s: failed to copy to user space\n", __func__); + ret = -EFAULT; + } + } + mutex_unlock(&nfc_dev->read_mutex); + return ret; +} + +ssize_t nfc_i2c_dev_write(struct file *filp, const char __user *buf, + size_t count, loff_t *offset) +{ + int ret; + struct nfc_dev *nfc_dev = (struct nfc_dev *)filp->private_data; + + if (count > MAX_DL_BUFFER_SIZE) + count = MAX_DL_BUFFER_SIZE; + + if (!nfc_dev) { + NFC_LOG_ERR("%s: device doesn't exist anymore\n", __func__); + return -ENODEV; + } + + mutex_lock(&nfc_dev->write_mutex); + if (copy_from_user(nfc_dev->write_kbuf, buf, count)) { + NFC_LOG_ERR("%s: failed to copy from user space\n", __func__); + mutex_unlock(&nfc_dev->write_mutex); + return -EFAULT; + } + ret = i2c_write(nfc_dev, nfc_dev->write_kbuf, count, NO_RETRY); + mutex_unlock(&nfc_dev->write_mutex); + return ret; +} + +static const struct file_operations nfc_i2c_dev_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = nfc_i2c_dev_read, + .write = nfc_i2c_dev_write, + .open = nfc_dev_open, + .flush = nfc_dev_flush, + .release = nfc_dev_close, + .unlocked_ioctl = nfc_dev_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = nfc_dev_compat_ioctl, +#endif +}; + +#if (KERNEL_VERSION(6, 3, 0) <= LINUX_VERSION_CODE) +int nfc_i2c_dev_probe(struct i2c_client *client) +#else +int nfc_i2c_dev_probe(struct i2c_client *client,const struct i2c_device_id *id) +#endif +{ + int ret = 0; + struct nfc_dev *nfc_dev = NULL; + struct i2c_dev *i2c_dev = NULL; + struct platform_configs *nfc_configs = NULL; + struct platform_gpio *nfc_gpio = NULL; + +#ifdef CONFIG_SEC_NFC_LOGGER +#ifdef CONFIG_SEC_NFC_LOGGER_ADD_ACPM_LOG + nfc_logger_acpm_log_init(0x0); +#endif + nfc_logger_register_nfc_stauts_func(nfc_print_status); +#endif + + NFC_LOG_INFO("%s: enter\n", __func__); + nfc_dev = kzalloc(sizeof(struct nfc_dev), GFP_KERNEL); + if (nfc_dev == NULL) { + ret = -ENOMEM; + goto err; + } + nfc_configs = &nfc_dev->configs; + nfc_gpio = &nfc_configs->gpio; + /* retrieve details of gpios from dt */ + ret = nfc_parse_dt(&client->dev, nfc_configs, PLATFORM_IF_I2C); + if (ret) { + NFC_LOG_ERR("%s: failed to parse dt\n", __func__); + goto err_free_nfc_dev; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + NFC_LOG_ERR("%s: need I2C_FUNC_I2C\n", __func__); + ret = -ENODEV; + goto err_free_nfc_dev; + } + nfc_dev->read_kbuf = kzalloc(MAX_NCI_BUFFER_SIZE, GFP_DMA | GFP_KERNEL); + if (!nfc_dev->read_kbuf) { + ret = -ENOMEM; + goto err_free_nfc_dev; + } + nfc_dev->write_kbuf = kzalloc(MAX_DL_BUFFER_SIZE, GFP_DMA | GFP_KERNEL); + if (!nfc_dev->write_kbuf) { + ret = -ENOMEM; + goto err_free_read_kbuf; + } + nfc_dev->interface = PLATFORM_IF_I2C; + nfc_dev->nfc_state = NFC_STATE_NCI; + nfc_dev->i2c_dev.client = client; + i2c_dev = &nfc_dev->i2c_dev; + nfc_dev->nfc_read = i2c_read; + nfc_dev->nfc_write = i2c_write; + nfc_dev->nfc_enable_intr = i2c_enable_irq; + nfc_dev->nfc_disable_intr = i2c_disable_irq; +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + nfc_dev->nfc_enable_clk_intr = i2c_enable_clk_irq; + nfc_dev->nfc_disable_clk_intr = i2c_disable_clk_irq; + + /* reboot notifier callback */ + nfc_dev->reboot_nb.notifier_call = nfc_reboot_cb; + register_reboot_notifier(&nfc_dev->reboot_nb); +#endif +#ifdef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE + if (!nfc_configs->late_pvdd_en) { + ret = configure_gpio(nfc_gpio->ven, GPIO_OUTPUT); + if (ret) { + NFC_LOG_ERR("%s: unable to request nfc reset gpio [%d]\n", __func__, + nfc_gpio->ven); + goto err_free_write_kbuf; + } + } +#else + ret = configure_gpio(nfc_gpio->ven, GPIO_OUTPUT); + if (ret) { + NFC_LOG_ERR("%s: unable to request nfc reset gpio [%d]\n", __func__, + nfc_gpio->ven); + goto err_free_write_kbuf; + } +#endif + ret = configure_gpio(nfc_gpio->irq, GPIO_IRQ); + if (ret <= 0) { + NFC_LOG_ERR("%s: unable to request nfc irq gpio [%d]\n", __func__, + nfc_gpio->irq); + goto err_free_gpio; + } + client->irq = ret; + if (gpio_is_valid(nfc_gpio->dwl_req)) { + ret = configure_gpio(nfc_gpio->dwl_req, GPIO_OUTPUT); + if (ret) { + NFC_LOG_ERR("%s: unable to request nfc firm downl gpio [%d]\n", + __func__, nfc_gpio->dwl_req); + } + } + /* init mutex and queues */ + init_waitqueue_head(&nfc_dev->read_wq); + mutex_init(&nfc_dev->read_mutex); + mutex_init(&nfc_dev->write_mutex); + mutex_init(&nfc_dev->dev_ref_mutex); + spin_lock_init(&i2c_dev->irq_enabled_lock); + common_ese_init(nfc_dev); + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + wake_lock_init(&nfc_dev->nfc_wake_lock, WAKE_LOCK_SUSPEND, "nfc_wake_lock"); +#endif + +#ifndef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE + ret = nfc_misc_register(nfc_dev, &nfc_i2c_dev_fops, DEV_COUNT, + NFC_CHAR_DEV_NAME, CLASS_NAME); + if (ret) { + NFC_LOG_ERR("%s: nfc_misc_register failed\n", __func__); + goto err_mutex_destroy; + } +#endif + /* interrupt initializations */ + NFC_LOG_INFO("%s: requesting IRQ %d\n", __func__, client->irq); + i2c_dev->irq_enabled = true; + ret = request_irq(client->irq, i2c_irq_handler, IRQF_TRIGGER_HIGH, + client->name, nfc_dev); + if (ret) { + NFC_LOG_ERR("%s: request_irq failed\n", __func__); + goto err_nfc_misc_unregister; + } +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + if (nfc_configs->clk_req_wake || nfc_configs->clk_req_all_trigger) { + unsigned long irq_flag = IRQF_TRIGGER_RISING | IRQF_ONESHOT; + + wake_lock_init(&nfc_dev->nfc_clk_wake_lock, WAKE_LOCK_SUSPEND, "nfc_clk_wake_lock"); + + if (nfc_configs->clk_req_all_trigger) + irq_flag |= IRQF_TRIGGER_FALLING; + + ret = configure_gpio(nfc_gpio->clk_req, GPIO_IRQ); + if (ret <= 0) { + NFC_LOG_ERR("%s: unable to request nfc clk req irq gpio [%d]\n", __func__, nfc_gpio->irq); + } else { + nfc_gpio->clk_req_irq = ret; + + NFC_LOG_INFO("clk_req IRQ num %d\n", nfc_gpio->clk_req_irq); + + ret = request_threaded_irq(nfc_gpio->clk_req_irq, NULL, nfc_clk_req_irq_handler, + irq_flag, "pn547_clk_req", nfc_dev); + if (ret) + NFC_LOG_ERR("clk_req_irq failed\n"); + else { + nfc_gpio->clk_req_irq_enabled = true; + i2c_disable_clk_irq(nfc_dev); + } + } + } + + if (!nfc_configs->late_pvdd_en) + nfc_power_control(nfc_dev); +#else + gpio_set_ven(nfc_dev, 1); + gpio_set_ven(nfc_dev, 0); + gpio_set_ven(nfc_dev, 1); +#endif + i2c_disable_irq(nfc_dev); + + i2c_set_clientdata(client, nfc_dev); + i2c_dev->irq_wake_up = false; + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +#ifdef CONFIG_CLK_ACPM_INIT + if (nfc_configs->change_clkreq_for_acpm) { + NFC_LOG_INFO("acpm_init_nfc_clk_req clk_req : %d\n", nfc_gpio->clk_req); + acpm_init_eint_nfc_clk_req(nfc_gpio->clk_req); + } +#endif +#if IS_ENABLED(CONFIG_NFC_SN2XX_ESE_SUPPORT) + store_nfc_i2c_device(&client->dev); +#endif + nfc_probe_done(nfc_dev); +#endif + + NFC_LOG_INFO("%s: probing nfc i2c successfully\n", __func__); + return 0; +err_nfc_misc_unregister: +#ifndef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE + nfc_misc_unregister(nfc_dev, DEV_COUNT); +err_mutex_destroy: +#endif + mutex_destroy(&nfc_dev->dev_ref_mutex); + mutex_destroy(&nfc_dev->read_mutex); + mutex_destroy(&nfc_dev->write_mutex); +err_free_gpio: + gpio_free_all(nfc_dev); +err_free_write_kbuf: + kfree(nfc_dev->write_kbuf); +err_free_read_kbuf: + kfree(nfc_dev->read_kbuf); +err_free_nfc_dev: + kfree(nfc_dev); +err: + NFC_LOG_ERR("%s: probing not successful, check hardware\n", __func__); + return ret; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) +int nfc_i2c_dev_remove(struct i2c_client *client) +#else +void nfc_i2c_dev_remove(struct i2c_client *client) +#endif +{ + int ret = 0; + struct nfc_dev *nfc_dev = NULL; + + NFC_LOG_INFO("%s: remove device\n", __func__); + nfc_dev = i2c_get_clientdata(client); + if (!nfc_dev) { + NFC_LOG_ERR("%s: device doesn't exist anymore\n", __func__); + ret = -ENODEV; + goto end; + } + if (nfc_dev->dev_ref_count > 0) { + NFC_LOG_ERR("%s: device already in use\n", __func__); + ret = -EBUSY; + goto end; + } + + free_irq(client->irq, nfc_dev); +#ifdef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE + nfc_misc_unregister(NULL, DEV_COUNT); +#else + nfc_misc_unregister(nfc_dev, DEV_COUNT); +#endif +#ifdef CONFIG_SEC_NFC_LOGGER + nfc_logger_deinit(); +#endif + wake_lock_destroy(&nfc_dev->nfc_wake_lock); + + mutex_destroy(&nfc_dev->read_mutex); + mutex_destroy(&nfc_dev->write_mutex); + gpio_free_all(nfc_dev); + kfree(nfc_dev->read_kbuf); + kfree(nfc_dev->write_kbuf); + kfree(nfc_dev); +end: + NFC_LOG_INFO("%s: ret :%d\n", __func__, ret); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) + return ret; +#endif +} + +int nfc_i2c_dev_suspend(struct device *device) +{ + struct i2c_client *client = to_i2c_client(device); + struct nfc_dev *nfc_dev = i2c_get_clientdata(client); + struct i2c_dev *i2c_dev = NULL; +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + struct platform_configs *nfc_configs = &nfc_dev->configs; + struct platform_gpio *nfc_gpio = &nfc_dev->configs.gpio; +#endif + NFC_LOG_INFO_WITH_DATE("suspend\n"); + if (!nfc_dev) { + NFC_LOG_ERR("%s: device doesn't exist anymore\n", __func__); + return -ENODEV; + } + i2c_dev = &nfc_dev->i2c_dev; + + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + if (i2c_dev->irq_enabled && !i2c_dev->irq_wake_up) { + if (!enable_irq_wake(client->irq)) + i2c_dev->irq_wake_up = true; + } + if (nfc_configs->clk_req_wake || nfc_configs->clk_req_all_trigger) { + enable_irq_wake(nfc_gpio->clk_req_irq); + nfc_dev->clk_req_wakelock = true; + } +#else + if (device_may_wakeup(&client->dev) && i2c_dev->irq_enabled) { + if (!enable_irq_wake(client->irq)) + i2c_dev->irq_wake_up = true; + } +#endif + NFC_LOG_DBG("%s: irq_wake_up = %d\n", __func__, i2c_dev->irq_wake_up); + return 0; +} + +int nfc_i2c_dev_resume(struct device *device) +{ + struct i2c_client *client = to_i2c_client(device); + struct nfc_dev *nfc_dev = i2c_get_clientdata(client); + struct i2c_dev *i2c_dev = NULL; +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + struct platform_configs *nfc_configs = &nfc_dev->configs; + struct platform_gpio *nfc_gpio = &nfc_dev->configs.gpio; +#endif + NFC_LOG_INFO_WITH_DATE("resume\n"); + if (!nfc_dev) { + NFC_LOG_ERR("%s: device doesn't exist anymore\n", __func__); + return -ENODEV; + } + i2c_dev = &nfc_dev->i2c_dev; + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + if (i2c_dev->irq_wake_up) { + if (!disable_irq_wake(client->irq)) + i2c_dev->irq_wake_up = false; + } + if (nfc_configs->clk_req_wake || nfc_configs->clk_req_all_trigger) + disable_irq_wake(nfc_gpio->clk_req_irq); +#else + if (device_may_wakeup(&client->dev) && i2c_dev->irq_wake_up) { + if (!disable_irq_wake(client->irq)) + i2c_dev->irq_wake_up = false; + } +#endif + NFC_LOG_DBG("%s: irq_wake_up = %d\n", __func__, i2c_dev->irq_wake_up); + return 0; +} + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +#define SN300_PVDD_MIN 1100000 +#define SN300_PVDD_MAX 1300000 + +static int nfc_pvdd_off(struct nfc_dev *nfc_dev) +{ + struct platform_configs *nfc_configs; + int ret = 0, pvdd_val; + +// NFC_LOG_INFO("nfc_pvdd_off ++\n"); + + nfc_configs = &nfc_dev->configs; + + pvdd_val = regulator_get_voltage(nfc_configs->nfc_pvdd); + NFC_LOG_INFO("pvdd_val %d\n", pvdd_val); + + if (SN300_PVDD_MIN < pvdd_val && pvdd_val < SN300_PVDD_MAX) { +// NFC_LOG_INFO("nfc IO force off\n"); + ret = nfc_regulator_onoff(nfc_dev, 0); + if (ret < 0) + NFC_LOG_ERR("%s pn547 regulator_on FAIL %d\n", __func__, ret); + } + +// NFC_LOG_INFO("nfc_pvdd_off --\n"); +//exit: + return ret; +} + +static int nfc_reboot_cb(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct nfc_dev *nfc_dev = container_of(nb, struct nfc_dev, reboot_nb); + struct platform_gpio *nfc_gpio; + struct platform_configs *nfc_configs; + int ret; + + if (!nfc_dev) { + NFC_LOG_INFO("no nfc dev\n"); + goto exit; + } + + NFC_LOG_INFO("nfc_reboot_cb ++\n"); + + nfc_gpio = &nfc_dev->configs.gpio; + nfc_configs = &nfc_dev->configs; + + if (nfc_configs->ap_vendor == AP_VENDOR_QCT) { + ret = gpio_direction_output(nfc_gpio->ven, 0); + if (ret) + NFC_LOG_ERR("VEN control fail %d\n", ret); + + usleep_range(300, 301); + nfc_pvdd_off(nfc_dev); + } + + NFC_LOG_INFO("nfc_reboot_cb --\n"); +exit: + return NOTIFY_OK; +} + +static void nfc_shutdown(struct i2c_client *client) +{ + struct nfc_dev *nfc_dev = i2c_get_clientdata(client); + int ret; + + NFC_LOG_INFO("nfc_shutdown ++\n"); + + if (nfc_dev) { + struct platform_gpio *nfc_gpio = &nfc_dev->configs.gpio; + struct platform_configs *nfc_configs = &nfc_dev->configs; + + /* + * In case of S.LSI, NFC card mode doesn't work in LPM. + * so, it's skipped on S.LSI models using sn110. it need to be checked + */ + if (nfc_configs->ap_vendor != AP_VENDOR_SLSI) { + if (nfc_configs->ap_vendor == AP_VENDOR_MTK) { + /*use internal pull-up case*/ + struct pinctrl *pinctrl = NULL; + + pinctrl = devm_pinctrl_get_select(&client->dev, "i2c_off"); + if (IS_ERR_OR_NULL(pinctrl)) + NFC_LOG_ERR("Failed to pinctrl i2c_off\n"); + else + devm_pinctrl_put(pinctrl); + NFC_LOG_ERR("i2c off pinctrl called\n"); + }; + + ret = gpio_direction_output(nfc_gpio->ven, 0); + if (ret) + NFC_LOG_ERR("VEN control fail %d\n", ret); + + if (nfc_configs->ap_vendor == AP_VENDOR_QCT) { + usleep_range(300, 301); + nfc_pvdd_off(nfc_dev); + } + } + } + + NFC_LOG_INFO("nfc_shutdown --\n"); +} +#endif + +#ifdef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE +static int nfc_platform_probe(struct platform_device *pdev) +{ + int ret = -1; + + nfc_parse_dt_for_platform_device(&pdev->dev); + + ret = nfc_misc_register(NULL, &nfc_i2c_dev_fops, DEV_COUNT, + NFC_CHAR_DEV_NAME, CLASS_NAME); + if (ret) + NFC_LOG_ERR("%s: nfc_misc_register failed\n", __func__); + + NFC_LOG_INFO("%s: finished...\n", __func__); + return 0; +} + +static int nfc_platform_remove(struct platform_device *pdev) +{ + NFC_LOG_INFO("Entry : %s\n", __func__); + return 0; +} + +static const struct of_device_id nfc_platform_match_table[] = { + { .compatible = "nfc_platform",}, + {}, +}; + +static struct platform_driver nfc_platform_driver = { + .driver = { + .name = "nfc_platform", + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = nfc_platform_match_table, +#endif + }, + .probe = nfc_platform_probe, + .remove = nfc_platform_remove, +}; +#endif /* CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE */ + +static const struct i2c_device_id nfc_i2c_dev_id[] = {{NFC_I2C_DEV_ID, 0}, + {}}; + +static const struct of_device_id nfc_i2c_dev_match_table[] = { + { + .compatible = NFC_I2C_DRV_STR, + }, + {} +}; + +static const struct dev_pm_ops nfc_i2c_dev_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS( + nfc_i2c_dev_suspend, nfc_i2c_dev_resume) }; + +static struct i2c_driver nfc_i2c_dev_driver = { + .id_table = nfc_i2c_dev_id, + .probe = nfc_i2c_dev_probe, + .remove = nfc_i2c_dev_remove, +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + .shutdown = nfc_shutdown, +#endif + .driver = { + .name = NFC_I2C_DRV_STR, + .pm = &nfc_i2c_dev_pm_ops, + .of_match_table = nfc_i2c_dev_match_table, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; + +MODULE_DEVICE_TABLE(of, nfc_i2c_dev_match_table); + +#if IS_MODULE(CONFIG_SAMSUNG_NFC) +extern int p61_dev_init(void); +extern void p61_dev_exit(void); +static int __init nfc_i2c_dev_init(void) +{ + int ret = 0; + +#ifdef CONFIG_SEC_NFC_LOGGER + nfc_logger_init(); + nfc_logger_set_max_count(-1); +#endif + NFC_LOG_INFO("%s: Loading NXP NFC I2C driver\n", __func__); +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + if (nfc_get_lpcharge() == LPM_TRUE) + return ret; +#endif +#ifdef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE + ret = platform_driver_register(&nfc_platform_driver); + NFC_LOG_INFO("%s: platform_driver_register, ret %d\n", __func__, ret); +#endif + ret = i2c_add_driver(&nfc_i2c_dev_driver); + if (ret != 0) + NFC_LOG_ERR("%s: NFC I2C add driver error ret %d\n", __func__, ret); +#if IS_ENABLED(CONFIG_NFC_SN2XX_ESE_SUPPORT) + ret = p61_dev_init(); +#endif + return ret; +} + +module_init(nfc_i2c_dev_init); + +static void __exit nfc_i2c_dev_exit(void) +{ + NFC_LOG_INFO("%s: Unloading NXP NFC I2C driver\n", __func__); +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + if (nfc_get_lpcharge() == LPM_TRUE) + return; +#endif +#if IS_ENABLED(CONFIG_NFC_SN2XX_ESE_SUPPORT) + p61_dev_exit(); +#endif + i2c_del_driver(&nfc_i2c_dev_driver); +#ifdef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE + platform_driver_unregister(&nfc_platform_driver); +#endif +} + +module_exit(nfc_i2c_dev_exit); +#else /* not IS_MODULE(CONFIG_SAMSUNG_NFC) */ +static int __init nfc_i2c_dev_init(void) +{ + int ret = 0; + +#ifdef CONFIG_SEC_NFC_LOGGER + nfc_logger_init(); + nfc_logger_set_max_count(-1); +#endif + NFC_LOG_INFO("%s: Loading NXP NFC I2C driver\n", __func__); +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + if (nfc_get_lpcharge() == LPM_TRUE) + return ret; +#endif +#ifdef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE + ret = platform_driver_register(&nfc_platform_driver); + NFC_LOG_INFO("%s: platform_driver_register, ret %d\n", __func__, ret); +#endif + ret = i2c_add_driver(&nfc_i2c_dev_driver); + if (ret != 0) + NFC_LOG_ERR("%s: NFC I2C add driver error ret %d\n", __func__, ret); + + return ret; +} + +module_init(nfc_i2c_dev_init); + +static void __exit nfc_i2c_dev_exit(void) +{ + NFC_LOG_INFO("%s: Unloading NXP NFC I2C driver\n", __func__); + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + if (nfc_get_lpcharge() == LPM_TRUE) + return; +#endif + + i2c_del_driver(&nfc_i2c_dev_driver); +#ifdef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE + platform_driver_unregister(&nfc_platform_driver); +#endif +} + +module_exit(nfc_i2c_dev_exit); +#endif /* IS_MODULE(CONFIG_SAMSUNG_NFC) */ + +MODULE_DESCRIPTION("NXP NFC I2C driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nfc/nxp_combined/i2c_drv.h b/drivers/nfc/nxp_combined/i2c_drv.h new file mode 100644 index 000000000000..360908ea7aa3 --- /dev/null +++ b/drivers/nfc/nxp_combined/i2c_drv.h @@ -0,0 +1,57 @@ +/****************************************************************************** + * Copyright (C) 2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2019-2021 NXP + * + * 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 _I2C_DRV_H_ +#define _I2C_DRV_H_ + +#include + +/* kept same as dts */ +#define NFC_I2C_DRV_STR "pn547" +#define NFC_I2C_DEV_ID "pn547" + +/* Interface specific parameters */ +struct i2c_dev { + struct i2c_client *client; + /* IRQ parameters */ + bool irq_enabled; + spinlock_t irq_enabled_lock; + /* NFC_IRQ wake-up state */ + bool irq_wake_up; +}; + +long nfc_i2c_dev_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg); +#if (KERNEL_VERSION(6, 3, 0) <= LINUX_VERSION_CODE) +int nfc_i2c_dev_probe(struct i2c_client *client); +#else +int nfc_i2c_dev_probe(struct i2c_client *client, + const struct i2c_device_id *id); +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) +int nfc_i2c_dev_remove(struct i2c_client *client); +#else +void nfc_i2c_dev_remove(struct i2c_client *client); +#endif +int nfc_i2c_dev_suspend(struct device *device); +int nfc_i2c_dev_resume(struct device *device); +#ifdef CONFIG_CLK_ACPM_INIT +extern acpm_init_eint_nfc_clk_req(u32 eint_num); +#endif + +#endif /* _I2C_DRV_H_ */ diff --git a/drivers/nfc/nxp_combined/nfc_wakelock.h b/drivers/nfc/nxp_combined/nfc_wakelock.h new file mode 100644 index 000000000000..b386f525ca6d --- /dev/null +++ b/drivers/nfc/nxp_combined/nfc_wakelock.h @@ -0,0 +1,74 @@ +/* + * NFC Wakelock + * + * 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. + * + */ + +#ifndef _NFC_WAKELOCK_H +#define _NFC_WAKELOCK_H + +#include +#include +#include + +#define wake_lock_init(a, b, c) nfc_wake_lock_init(a, c) +#define wake_lock_destroy(a) nfc_wake_lock_destroy(a) +#define wake_lock_timeout(a, b) nfc_wake_lock_timeout(a, b) +#define wake_lock_active(a) nfc_wake_lock_active(a) +#define wake_lock(a) nfc_wake_lock(a) +#define wake_unlock(a) nfc_wake_unlock(a) + +struct nfc_wake_lock { + struct wakeup_source *ws; +}; + +static inline void nfc_wake_lock_init(struct nfc_wake_lock *lock, const char *name) +{ +#if CONFIG_SEC_NFC_WAKELOCK_METHOD == 2 + lock->ws = wakeup_source_register(NULL, name); +#elif (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0)) || CONFIG_SEC_NFC_WAKELOCK_METHOD == 1 + wakeup_source_init(lock->ws, name); /* 4.19 R */ + if (!(lock->ws)) { + lock->ws = wakeup_source_create(name); /* 4.19 Q */ + if (lock->ws) + wakeup_source_add(lock->ws); + } +#else + lock->ws = wakeup_source_register(NULL, name); /* 5.4 R */ +#endif +} + +static inline void nfc_wake_lock_destroy(struct nfc_wake_lock *lock) +{ + if (lock->ws) + wakeup_source_unregister(lock->ws); +} + +static inline void nfc_wake_lock(struct nfc_wake_lock *lock) +{ + if (lock->ws) + __pm_stay_awake(lock->ws); +} + +static inline void nfc_wake_lock_timeout(struct nfc_wake_lock *lock, long timeout) +{ + if (lock->ws) + __pm_wakeup_event(lock->ws, jiffies_to_msecs(timeout)); +} + +static inline void nfc_wake_unlock(struct nfc_wake_lock *lock) +{ + if (lock->ws) + __pm_relax(lock->ws); +} + +static inline int nfc_wake_lock_active(struct nfc_wake_lock *lock) +{ + return lock->ws->active; +} + +#endif diff --git a/drivers/nfc/nxp_combined/p73.c b/drivers/nfc/nxp_combined/p73.c new file mode 100644 index 000000000000..44717795bbed --- /dev/null +++ b/drivers/nfc/nxp_combined/p73.c @@ -0,0 +1,1881 @@ +/****************************************************************************** + * + * Copyright 2012-2023 NXP + * * + * 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 + * + ******************************************************************************/ + +/** +* \addtogroup spi_driver +* +* @{ */ + +#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 +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +#include +#if IS_ENABLED(CONFIG_SPI_MSM_GENI) +#include "nfc_wakelock.h" +#if IS_ENABLED(CONFIG_MSM_GENI_SE) +#include +#include +#endif +#endif +#endif + +#include "p73.h" +#include "common_ese.h" +#include "ese_reset.h" +#define DRAGON_P61 0 + +/* Device driver's configuration macro */ +/* Macro to configure poll/interrupt based req*/ +#undef P61_IRQ_ENABLE +//#define P61_IRQ_ENABLE + +/* Macro to configure Hard/Soft reset to P61 */ +//#define P61_HARD_RESET +#undef P61_HARD_RESET + +#ifdef P61_HARD_RESET +static struct regulator *p61_regulator = NULL; +#else +#endif + +#define P61_IRQ 33 /* this is the same used in omap3beagle.c */ +#define P61_RST 138 + +/* Macro to define SPI clock frequency */ + +//#define P61_SPI_CLOCK_7Mzh +#undef P61_SPI_CLOCK_7Mzh +#undef P61_SPI_CLOCK_13_3_Mzh +#undef P61_SPI_CLOCK_8Mzh +#undef P61_SPI_CLOCK_20Mzh +#define P61_SPI_CLOCK_25Mzh + +#ifdef P61_SPI_CLOCK_13_3_Mzh +//#define P61_SPI_CLOCK 13300000L;Further debug needed +#define P61_SPI_CLOCK 19000000L; +#else +#ifdef P61_SPI_CLOCK_7Mzh +#define P61_SPI_CLOCK 7000000L; +#else +#ifdef P61_SPI_CLOCK_8Mzh +#define P61_SPI_CLOCK 8000000L; +#else +#ifdef P61_SPI_CLOCK_20Mzh +#define P61_SPI_CLOCK 20000000L; +#else +#ifdef P61_SPI_CLOCK_25Mzh +#define P61_SPI_CLOCK 25000000L; +#else +#define P61_SPI_CLOCK 4000000L; +#endif +#endif +#endif +#endif +#endif + +/* size of maximum read/write buffer supported by driver */ +#ifdef MAX_BUFFER_SIZE +#undef MAX_BUFFER_SIZE +#endif //MAX_BUFFER_SIZE +#define MAX_BUFFER_SIZE 780U + +/* Different driver debug lever */ +enum P61_DEBUG_LEVEL { + P61_DEBUG_OFF, + P61_FULL_DEBUG +}; + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +static char *p61_pinctrl_name[] = {"ese_off", "ese_on", "lpm", "default", "active", "suspend"}; +enum p61_pin_ctrl { + P61_PIN_CTRL_ESE_OFF, + P61_PIN_CTRL_ESE_ON, + P61_PIN_CTRL_LPM, + P61_PIN_CTRL_DEFAULT, + P61_PIN_CTRL_ACTIVE, + P61_PIN_CTRL_SUSPEND, + P61_PIN_CTRL_MAX +}; +#endif + +/* Variable to store current debug level request by ioctl */ +static unsigned char debug_level; + +static DEFINE_MUTEX(open_close_mutex); + +#define P61_DBG_MSG(msg...) \ + switch(debug_level) \ + { \ + case P61_DEBUG_OFF: \ + break; \ + case P61_FULL_DEBUG: \ + printk(KERN_INFO "[NXP-P61] : " msg); \ + break; \ + default: \ + printk(KERN_ERR "[NXP-P61] : Wrong debug level %d", debug_level); \ + break; \ + } \ + +#define P61_ERR_MSG(msg...) printk(KERN_ERR "[NFC-P61] : " msg ); + +/* Device specific macro and structure */ +struct p61_dev { + wait_queue_head_t read_wq; /* wait queue for read interrupt */ + struct mutex read_mutex; /* read mutex */ + struct mutex write_mutex; /* write mutex */ + struct spi_device *spi; /* spi device structure */ + struct miscdevice p61_device; /* char device as misc driver */ + int rst_gpio; /* SW Reset gpio */ + int irq_gpio; /* P61 will interrupt DH for any ntf */ + bool irq_enabled; /* flag to indicate irq is used */ + unsigned char enable_poll_mode; /* enable the poll mode */ + spinlock_t irq_enabled_lock; /*spin lock for read irq */ + int trusted_ese_gpio; /* i/p to eSE to distunguish trusted Session */ + ese_spi_transition_state_t ese_spi_transition_state; /* State of the driver */ + struct device *nfcc_device; /*nfcc driver handle for driver to driver comm */ + struct nfc_dev *nfcc_data; + const char *nfcc_name; + bool gpio_coldreset; +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + int ap_vendor; + struct device_node *nfc_node; + struct pinctrl *pinctrl; + struct pinctrl_state *pinctrl_state[P61_PIN_CTRL_MAX]; + struct platform_device *spi_pdev; + struct nfc_wake_lock ese_lock; + int open_pid; + int release_pid; + char open_task_name[TASK_COMM_LEN]; + char release_task_name[TASK_COMM_LEN]; +#if IS_ENABLED(CONFIG_SPI_MSM_GENI) + struct delayed_work spi_release_work; + struct nfc_wake_lock spi_release_wakelock; +#endif +#endif + unsigned char *r_buf; + unsigned char *w_buf; +}; + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +struct device *g_nfc_device; +struct p61_dev *g_p61_dev; +#endif + +/* T==1 protocol specific global data */ +const unsigned char SOF = 0xA5u; + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +static void p61_get_task_info(struct p61_dev *p61_dev, char *task_name, int *pid) +{ + struct task_struct *task; + + if (!p61_dev) + return; + + rcu_read_lock(); + *pid = task_pid_nr(current); + task = pid_task(find_vpid(*pid), PIDTYPE_PID); + if (task) { + memcpy(task_name, task->comm, TASK_COMM_LEN); + task_name[TASK_COMM_LEN - 1] = '\0'; + } + rcu_read_unlock(); +} + +static void p61_init_task_info(struct p61_dev *p61_dev) +{ + p61_dev->open_pid = 0; + p61_dev->release_pid = 0; + memset(p61_dev->open_task_name, 0, TASK_COMM_LEN); + memset(p61_dev->release_task_name, 0, TASK_COMM_LEN); +} + +void p61_print_status(const char *func_name) +{ + struct p61_dev *p61_dev = g_p61_dev; + + if (!p61_dev) + return; + + NFC_LOG_INFO("%s: state=%d o_pid=%d rel_pid=%d o_task=%s rel_task=%s\n", + func_name, p61_dev->ese_spi_transition_state, + p61_dev->open_pid, + p61_dev->release_pid, + p61_dev->open_task_name, + p61_dev->release_task_name); +} + +static void p61_pinctrl_select(struct p61_dev *p61_dev, enum p61_pin_ctrl stat) +{ + int ret; + + NFC_LOG_INFO("pinctrl[%s]\n", p61_pinctrl_name[stat]); + + if (!p61_dev->pinctrl || !p61_dev->pinctrl_state[stat]) + return; + + ret = pinctrl_select_state(p61_dev->pinctrl, p61_dev->pinctrl_state[stat]); + if (ret < 0) + NFC_LOG_INFO("pinctrl[%d] failed\n", stat); +} + +#if IS_ENABLED(CONFIG_SPI_MSM_GENI) +static void p61_spi_release_work(struct work_struct *work) +{ + struct p61_dev *p61_dev = g_p61_dev; + + if (p61_dev == NULL) { + NFC_LOG_ERR("%s: spi probe is not called\n", __func__); + return; + } + + NFC_LOG_INFO("release ese spi\n"); + p61_pinctrl_select(p61_dev, P61_PIN_CTRL_SUSPEND); /* for QC AP */ +} +#endif +#endif + +/** + * \ingroup spi_driver + * \brief Will be called on device close to release resources + * + * \param[in] struct inode * + * \param[in] struct file * + * + * \retval 0 if ok. + * + */ +static int p61_dev_release(struct inode *inode, struct file *filp) +{ + struct p61_dev *p61_dev = NULL; + + NFC_LOG_INFO("Enter %s: ESE driver release\n", __func__); + + mutex_lock(&open_close_mutex); + p61_dev = filp->private_data; + p61_dev->ese_spi_transition_state = ESE_SPI_IDLE; +#if !IS_ENABLED(CONFIG_SAMSUNG_NFC) + gpio_set_value(p61_dev->trusted_ese_gpio, 0); +#endif + nfc_ese_pwr(p61_dev->nfcc_data, ESE_RST_PROT_DIS); + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + p61_pinctrl_select(p61_dev, P61_PIN_CTRL_ESE_OFF); /* for LSI AP */ +#if IS_ENABLED(CONFIG_SPI_MSM_GENI) + schedule_delayed_work(&p61_dev->spi_release_work, + msecs_to_jiffies(2000)); + wake_lock_timeout(&p61_dev->spi_release_wakelock, + msecs_to_jiffies(2100)); +#endif + if (wake_lock_active(&p61_dev->ese_lock)) + wake_unlock(&p61_dev->ese_lock); +#endif + mutex_unlock(&open_close_mutex); +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + p61_get_task_info(p61_dev, p61_dev->release_task_name, &p61_dev->release_pid); + p61_print_status(__func__); +#endif + NFC_LOG_INFO("Exit %s: ESE driver release\n", __func__); + return 0; +} + +static int p61_xfer(struct p61_dev *p61_dev, + struct p61_ioctl_transfer *tr) +{ + int status = 0; + struct spi_message m; + struct spi_transfer t; + static u32 read_try_cnt; + /*For SDM845 & linux4.9: need to change spi buffer + * from stack to dynamic memory + */ + + if (p61_dev == NULL || tr == NULL) + return -EFAULT; + + if (tr->len > MAX_BUFFER_SIZE || !tr->len) + return -EMSGSIZE; + + spi_message_init(&m); + memset(&t, 0, sizeof(t)); + + memset(p61_dev->w_buf, 0, tr->len); /*memset 0 for write */ + memset(p61_dev->r_buf, 0, tr->len); /*memset 0 for read */ + if (tr->tx_buffer != NULL) { /*write */ + if (copy_from_user(p61_dev->w_buf, tr->tx_buffer, tr->len) != 0) { + NFC_LOG_ERR("p61_wr: copy_from_user fail %d\n", tr->len); + status = -EFAULT; + goto xfer_exit; + } + } + + t.rx_buf = p61_dev->r_buf; + t.tx_buf = p61_dev->w_buf; + t.len = tr->len; + + spi_message_add_tail(&t, &m); + + status = spi_sync(p61_dev->spi, &m); + if (status == 0) { + if (tr->rx_buffer != NULL) { /*read */ + unsigned long missing = 0; + + missing = copy_to_user(tr->rx_buffer, p61_dev->r_buf, tr->len); + if (missing != 0) + tr->len = tr->len - (unsigned int)missing; + } + } + + if (tr->tx_buffer != NULL) { + if (read_try_cnt) + NFC_LOG_REC("p61w%d try%u\n", tr->len, read_try_cnt); + else + NFC_LOG_REC("p61w%d\n", tr->len); + } + if (tr->rx_buffer != NULL) { + if (tr->len == 2 && ((p61_dev->r_buf[0] == 0x0 && p61_dev->r_buf[2] == 0x0) || + (p61_dev->r_buf[0] == 0xff && p61_dev->r_buf[2] == 0xff))) { + read_try_cnt++; + } else { + if (read_try_cnt) + NFC_LOG_REC("p61r%d try%u\n", tr->len, read_try_cnt); + else + NFC_LOG_REC("p61r%d\n", tr->len); + read_try_cnt = 0; + } + } + +xfer_exit: + return status; +} /* vfsspi_xfer */ + +static int p61_rw_spi_message(struct p61_dev *p61_dev, + unsigned long arg) +{ + struct p61_ioctl_transfer *dup = NULL; + int err = 0; + + dup = kmalloc(sizeof(struct p61_ioctl_transfer), GFP_KERNEL); + if (dup == NULL) + return -ENOMEM; + + if (copy_from_user(dup, (void *)arg, + sizeof(struct p61_ioctl_transfer)) != 0) { + kfree(dup); + return -EFAULT; + } + + err = p61_xfer(p61_dev, dup); + if (err != 0) { + kfree(dup); + NFC_LOG_ERR("%s: p61_xfer failed, %d\n", __func__, err); + return err; + } + + if (copy_to_user((void *)arg, dup, + sizeof(struct p61_ioctl_transfer)) != 0) { + kfree(dup); + return -EFAULT; + } + kfree(dup); + return 0; +} + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +void store_nfc_i2c_device(struct device *nfc_i2c_dev) +{ + g_nfc_device = nfc_i2c_dev; +} +#endif + +#ifdef CONFIG_COMPAT +static int p61_rw_spi_message_compat(struct p61_dev *p61_dev, + unsigned long arg) +{ + struct p61_ioctl_transfer32 __user *argp = compat_ptr(arg); + struct p61_ioctl_transfer32 it32; + struct p61_ioctl_transfer *dup = NULL; + int err = 0; + + dup = kmalloc(sizeof(struct p61_ioctl_transfer), GFP_KERNEL); + if (dup == NULL) + return -ENOMEM; + + if (copy_from_user(&it32, argp, sizeof(it32))) { + kfree(dup); + return -EFAULT; + } + + dup->rx_buffer = (__u8 *)(uintptr_t)it32.rx_buffer; + dup->tx_buffer = (__u8 *)(uintptr_t)it32.tx_buffer; + dup->len = it32.len; + + err = p61_xfer(p61_dev, dup); + if (err != 0) { + kfree(dup); + NFC_LOG_ERR("%s: p61_xfer failed, %d\n", __func__, err); + return err; + } + + if (it32.rx_buffer) { + if (__put_user(dup->len, &argp->len)) { + kfree(dup); + return -EFAULT; + } + } + kfree(dup); + return 0; +} +#endif /*CONFIG_COMPAT */ + +/** + * \ingroup spi_driver + * \brief Called from SPI LibEse to initilaize the P61 device + * + * \param[in] struct inode * + * \param[in] struct file * + * + * \retval 0 if ok. + * + */ + +static int p61_dev_open(struct inode *inode, struct file *filp) +{ + +#ifdef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE + struct p61_dev *p61_dev = g_p61_dev; + + if (p61_dev == NULL) { + NFC_LOG_ERR("%s: spi probe is not called\n", __func__); + return -EAGAIN; + } +#else + struct p61_dev + *p61_dev = container_of(filp->private_data, + struct p61_dev, + p61_device); +#endif + + /* Find the NFC parent device if it exists. */ + if (p61_dev != NULL && p61_dev->nfcc_data == NULL) { +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + struct device *nfc_dev = g_nfc_device; + if (!nfc_dev) { + NFC_LOG_ERR("%s: cannot find NFC controller\n", __func__); + return -ENODEV; + } +#else + struct device *nfc_dev = bus_find_device_by_name(&i2c_bus_type, NULL, + p61_dev->nfcc_name); + if (!nfc_dev) { + NFC_LOG_ERR("%s: cannot find NFC controller '%s'\n", __func__, + p61_dev->nfcc_name); + return -ENODEV; + } +#endif + p61_dev->nfcc_data = dev_get_drvdata(nfc_dev); + if (!p61_dev->nfcc_data) { + NFC_LOG_ERR("%s: cannot find NFC controller device data\n", __func__); + put_device(nfc_dev); + return -ENODEV; + } + P61_DBG_MSG("%s: NFC controller found\n", __func__); + p61_dev->nfcc_device = nfc_dev; + } + filp->private_data = p61_dev; + if(p61_dev->ese_spi_transition_state == ESE_SPI_BUSY) { + NFC_LOG_ERR("%s : ESE is busy\n", __func__); + return -EBUSY; + } + + mutex_lock(&open_close_mutex); + p61_dev->ese_spi_transition_state = ESE_SPI_BUSY; + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + /* for checking previous open/close tasks */ + p61_print_status("p61_dev_open pre"); +#if IS_ENABLED(CONFIG_SPI_MSM_GENI) + cancel_delayed_work_sync(&p61_dev->spi_release_work); +#endif + if (!wake_lock_active(&p61_dev->ese_lock)) + wake_lock(&p61_dev->ese_lock); + + p61_pinctrl_select(p61_dev, P61_PIN_CTRL_ESE_ON); + + if (p61_dev->ap_vendor != AP_VENDOR_QCT) + msleep(60); + +#endif + mutex_unlock(&open_close_mutex); +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + p61_init_task_info(p61_dev); + p61_get_task_info(p61_dev, p61_dev->open_task_name, &p61_dev->open_pid); + p61_print_status(__func__); +#endif + return 0; +} + + + +/** + * \ingroup spi_driver + * \brief To configure the P61_SET_PWR/P61_SET_DBG/P61_SET_POLL + * \n P61_SET_PWR - hard reset (arg=2), soft reset (arg=1) + * \n P61_SET_DBG - Enable/Disable (based on arg value) the driver logs + * \n P61_SET_POLL - Configure the driver in poll (arg = 1), interrupt (arg = 0) based read operation + * \param[in] struct file * + * \param[in] unsigned int + * \param[in] unsigned long + * + * \retval 0 if ok. + * +*/ + +static long p61_dev_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + int ret = 0; + struct p61_dev *p61_dev = NULL; +#if !IS_ENABLED(CONFIG_SAMSUNG_NFC) + unsigned char buf[100]; +#endif + + P61_DBG_MSG(KERN_ALERT "p61_dev_ioctl-Enter %u arg = %ld\n", cmd, arg); + p61_dev = filp->private_data; + + switch (cmd) { + case P61_SET_PWR: +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + if (arg == 2) + NFC_LOG_INFO("[NXP-P61] - P61_SET_PWR. No Action.\n"); +#else + if (arg == 2) { +#ifdef P61_HARD_RESET + P61_DBG_MSG(KERN_ALERT " Disabling p61_regulator"); + if (p61_regulator != NULL) { + regulator_disable(p61_regulator); + msleep(50); + regulator_enable(p61_regulator); + P61_DBG_MSG(KERN_ALERT " Enabling p61_regulator"); + } else { + NFC_LOG_ERR(KERN_ALERT " ERROR : p61_regulator is not enabled\n"); + } +#endif + + } else if (arg == 1) { + P61_DBG_MSG(KERN_ALERT " Soft Reset"); + //gpio_set_value(p61_dev->rst_gpio, 1); + //msleep(20); + gpio_set_value(p61_dev->rst_gpio, 0); + msleep(50); + ret = spi_read(p61_dev->spi, (void *)buf, sizeof(buf)); + msleep(50); + gpio_set_value(p61_dev->rst_gpio, 1); + msleep(20); + + } +#endif + break; + + case P61_SET_DBG: + debug_level = (unsigned char)arg; + NFC_LOG_INFO("[NXP-P61] - Debug level %d\n", + debug_level); + break; + + case P61_SET_POLL: + + p61_dev->enable_poll_mode = (unsigned char)arg; + if (p61_dev->enable_poll_mode == 0) { + NFC_LOG_INFO("[NXP-P61] - IRQ Mode is set\n"); + } else { + NFC_LOG_INFO("[NXP-P61] - Poll Mode is set\n"); + p61_dev->enable_poll_mode = 1; + } + break; + case P61_RW_SPI_DATA: + ret = p61_rw_spi_message(p61_dev, arg); + break; + case P61_SET_SPM_PWR: + NFC_LOG_INFO("P61_SET_SPM_PWR: enter\n"); + ret = nfc_ese_pwr(p61_dev->nfcc_data, arg); + NFC_LOG_INFO("P61_SET_SPM_PWR: exit\n"); + break; + case P61_GET_SPM_STATUS: + NFC_LOG_INFO("P61_GET_SPM_STATUS: enter\n"); + ret = nfc_ese_pwr(p61_dev->nfcc_data, ESE_POWER_STATE); + NFC_LOG_INFO("P61_GET_SPM_STATUS: exiti\n"); + break; + case P61_SET_DWNLD_STATUS: + NFC_LOG_INFO("P61_SET_DWNLD_STATUS: enter\n"); + //ret = nfc_dev_ioctl(filp, PN544_SET_DWNLD_STATUS, arg); + NFC_LOG_INFO("P61_SET_DWNLD_STATUS: =%lu exit\n", arg); + break; + case P61_GET_ESE_ACCESS: + NFC_LOG_INFO("P61_GET_ESE_ACCESS: enter\n"); + //ret = nfc_dev_ioctl(filp, P544_GET_ESE_ACCESS, arg); + NFC_LOG_INFO("P61_GET_ESE_ACCESS ret: %d exit\n", ret); + break; + case P61_SET_POWER_SCHEME: + NFC_LOG_INFO("P61_SET_POWER_SCHEME: enter\n"); + //ret = nfc_dev_ioctl(filp, P544_SET_POWER_SCHEME, arg); + NFC_LOG_INFO("P61_SET_POWER_SCHEME ret: %d exit\n", + ret); + break; + case P61_INHIBIT_PWR_CNTRL: + NFC_LOG_INFO("P61_INHIBIT_PWR_CNTRL: enter\n"); + //ret = nfc_dev_ioctl(filp, P544_SECURE_TIMER_SESSION, arg); + NFC_LOG_INFO("P61_INHIBIT_PWR_CNTRL ret: %d exit\n", + ret); + break; + case ESE_PERFORM_COLD_RESET: + NFC_LOG_INFO("ESE_PERFORM_COLD_RESET: enter\n"); + if (p61_dev->gpio_coldreset) + ret = perform_ese_gpio_reset(p61_dev->rst_gpio); + else + ret = nfc_ese_pwr(p61_dev->nfcc_data, ESE_CLD_RST); + NFC_LOG_INFO("ESE_PERFORM_COLD_RESET ret: %d exit\n", ret); + break; + case PERFORM_RESET_PROTECTION: + if (p61_dev->gpio_coldreset) { + NFC_LOG_INFO("PERFORM_RESET_PROTECTION is not required and not supported\n"); + } else { + NFC_LOG_INFO("PERFORM_RESET_PROTECTION: enter\n"); + ret = nfc_ese_pwr(p61_dev->nfcc_data, + (arg == 1 ? ESE_RST_PROT_EN : ESE_RST_PROT_DIS)); + NFC_LOG_INFO("PERFORM_RESET_PROTECTION ret: %d exit\n", ret); + } + break; + case ESE_SET_TRUSTED_ACCESS: + NFC_LOG_INFO("Enter %s: TRUSTED access enabled=%lu\n", __func__, arg); +#if !IS_ENABLED(CONFIG_SAMSUNG_NFC) + if(arg == 1) { + NFC_LOG_INFO("ESE_SET_TRUSTED_ACCESS: enter Enabling\n"); + gpio_set_value(p61_dev->trusted_ese_gpio, 1); + NFC_LOG_INFO("ESE_SET_TRUSTED_ACCESS ret: exit\n"); + } else if (arg == 0) { + NFC_LOG_INFO("ESE_SET_TRUSTED_ACCESS: enter Disabling\n"); + gpio_set_value(p61_dev->trusted_ese_gpio, 0); + NFC_LOG_INFO("ESE_SET_TRUSTED_ACCESS ret: exit\n"); + } +#endif + break; + default: + NFC_LOG_ERR("Error case\n"); + ret = -EINVAL; + } + + P61_DBG_MSG(KERN_ALERT "p61_dev_ioctl-exit %u arg = %lu\n", cmd, arg); + return ret; +} + +#ifdef CONFIG_COMPAT +/** + * \ingroup spi_driver + * \brief To configure the P61_SET_PWR/P61_SET_DBG/P61_SET_POLL + * \n P61_SET_PWR - hard reset (arg=2), soft reset (arg=1) + * \n P61_SET_DBG - Enable/Disable (based on arg value) the driver logs + * \n P61_SET_POLL - Configure the driver in poll (arg = 1), interrupt (arg = 0) based read operation + * \param[in] struct file * + * \param[in] unsigned int + * \param[in] unsigned long + * + * \retval 0 if ok. + * + */ +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +static long p61_dev_compat_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + int ret = 0; + struct p61_dev *p61_dev = NULL; + + if (_IOC_TYPE(cmd) != P61_MAGIC) + return -ENOTTY; + + p61_dev = filp->private_data; + + switch (cmd) { + case P61_SET_PWR_COMPAT: + if (arg == 2) + NFC_LOG_INFO("%s: P61_SET_PWR. No Action.\n", __func__); + break; + + case P61_SET_DBG_COMPAT: + debug_level = (unsigned char)arg; + P61_DBG_MSG(KERN_INFO"[NXP-P61] - Debug level %d", + debug_level); + break; + case P61_SET_POLL_COMPAT: + p61_dev->enable_poll_mode = (unsigned char)arg; + if (p61_dev->enable_poll_mode == 0) { + P61_DBG_MSG(KERN_INFO"[NXP-P61] - IRQ Mode is set\n"); + } else { + P61_DBG_MSG(KERN_INFO"[NXP-P61] - Poll Mode is set\n"); + p61_dev->enable_poll_mode = 1; + } + break; + + case P61_RW_SPI_DATA_COMPAT: + ret = p61_rw_spi_message_compat(p61_dev, arg); + break; + + case P61_SET_SPM_PWR_COMPAT: + NFC_LOG_INFO("%s: P61_SET_SPM_PWR: enter\n", __func__); + ret = nfc_ese_pwr(p61_dev->nfcc_data, arg); + NFC_LOG_INFO("%s: P61_SET_SPM_PWR: exit\n", __func__); + break; + + case P61_GET_SPM_STATUS_COMPAT: + NFC_LOG_INFO("%s: P61_GET_SPM_STATUS: enter\n", __func__); + ret = nfc_ese_pwr(p61_dev->nfcc_data, ESE_POWER_STATE); + NFC_LOG_INFO("%s: P61_GET_SPM_STATUS: exit\n", __func__); + break; + + case P61_SET_DWNLD_STATUS_COMPAT: + NFC_LOG_INFO("P61_SET_DWNLD_STATUS: enter\n"); + //ret = pn547_dev_ioctl(filp, PN547_SET_DWNLD_STATUS, arg); + NFC_LOG_INFO("%s: P61_SET_DWNLD_STATUS: =%lu exit\n", __func__, arg); + break; + + case P61_GET_ESE_ACCESS_COMPAT: + NFC_LOG_INFO("P61_GET_ESE_ACCESS: enter\n"); + //ret = pn547_dev_ioctl(filp, P547_GET_ESE_ACCESS, arg); + NFC_LOG_INFO("P61_GET_ESE_ACCESS ret: %d exit\n", ret); + break; + + case P61_SET_POWER_SCHEME_COMPAT: + NFC_LOG_INFO("P61_SET_POWER_SCHEME: enter\n"); + //ret = pn547_dev_ioctl(filp, P544_SET_POWER_SCHEME, arg); + NFC_LOG_INFO("P61_SET_POWER_SCHEME ret: %d exit\n", + ret); + break; + + case P61_INHIBIT_PWR_CNTRL_COMPAT: + NFC_LOG_INFO("P61_INHIBIT_PWR_CNTRL: enter\n"); + //ret = pn547_dev_ioctl(filp, P544_SECURE_TIMER_SESSION, arg); + NFC_LOG_INFO("P61_INHIBIT_PWR_CNTRL ret: %d exit\n", + ret); + break; + + case ESE_PERFORM_COLD_RESET_COMPAT: + NFC_LOG_INFO("ESE_PERFORM_COLD_RESET: enter\n"); + if (p61_dev->gpio_coldreset) + ret = perform_ese_gpio_reset(p61_dev->rst_gpio); + else + ret = nfc_ese_pwr(p61_dev->nfcc_data, ESE_CLD_RST); + NFC_LOG_INFO("ESE_PERFORM_COLD_RESET ret: %d exit\n", ret); + break; + + case PERFORM_RESET_PROTECTION_COMPAT: + if (p61_dev->gpio_coldreset) { + NFC_LOG_INFO("PERFORM_RESET_PROTECTION is not required and not supported\n"); + } else { + NFC_LOG_INFO("PERFORM_RESET_PROTECTION: enter\n"); + ret = nfc_ese_pwr(p61_dev->nfcc_data, + (arg == 1 ? ESE_RST_PROT_EN : ESE_RST_PROT_DIS)); + NFC_LOG_INFO("PERFORM_RESET_PROTECTION ret: %d exit\n", ret); + } + break; + + case ESE_SET_TRUSTED_ACCESS: + NFC_LOG_INFO("Enter %s: TRUSTED access enabled=%lu\n", __func__, arg); +#if !IS_ENABLED(CONFIG_SAMSUNG_NFC) + if (arg == 1) { + NFC_LOG_INFO("ESE_SET_TRUSTED_ACCESS: enter Enabling\n"); + gpio_set_value(p61_dev->trusted_ese_gpio, 1); + NFC_LOG_INFO("ESE_SET_TRUSTED_ACCESS ret: exit\n"); + } else if (arg == 0) { + NFC_LOG_INFO("ESE_SET_TRUSTED_ACCESS: enter Disabling\n"); + gpio_set_value(p61_dev->trusted_ese_gpio, 0); + NFC_LOG_INFO("ESE_SET_TRUSTED_ACCESS ret: exit\n"); + } +#endif + break; + + default: + NFC_LOG_INFO("%s: no matching ioctl!\n", __func__); + ret = -EINVAL; + } + + P61_DBG_MSG(KERN_ALERT "%s %u arg = %lu\n", __func__, cmd, arg); + return ret; +} +#else +static long p61_dev_compat_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + int ret = 0; + + arg = (compat_u64)arg; + NFC_LOG_INFO(KERN_ALERT "%s-Enter %u arg = %ld\n", __func__, cmd, arg); + NFC_LOG_DBG("%s: cmd = %x arg = %zx\n", __func__, cmd, arg); + ret = p61_dev_ioctl(filp, cmd, arg); + return ret; +} +#endif +#endif + +/** + * \ingroup spi_driver + * \brief Write data to P61 on SPI + * + * \param[in] struct file * + * \param[in] const char * + * \param[in] size_t + * \param[in] loff_t * + * + * \retval data size + * +*/ + +static ssize_t p61_dev_write(struct file *filp, const char *buf, size_t count, + loff_t * offset) +{ + + int ret = -1; + struct p61_dev *p61_dev; +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + unsigned char *tx_buffer; +#else + unsigned char tx_buffer[MAX_BUFFER_SIZE]; +#endif + + P61_DBG_MSG(KERN_ALERT "p61_dev_write -Enter count %zu\n", count); + + p61_dev = filp->private_data; + + mutex_lock(&p61_dev->write_mutex); + if (count > MAX_BUFFER_SIZE) + count = MAX_BUFFER_SIZE; + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + tx_buffer = p61_dev->w_buf; + memset(&tx_buffer[0], 0, MAX_BUFFER_SIZE); +#else + memset(&tx_buffer[0], 0, sizeof(tx_buffer)); +#endif + if (copy_from_user(&tx_buffer[0], &buf[0], count)) { + NFC_LOG_ERR("%s: failed to copy from user space\n", __func__); + mutex_unlock(&p61_dev->write_mutex); + return -EFAULT; + } + /* Write data */ + ret = spi_write(p61_dev->spi, &tx_buffer[0], count); + if (ret < 0) { + NFC_LOG_ERR("%s: spi_write fail %d\n", __func__, ret); + ret = -EIO; + } else { + ret = count; + } + + mutex_unlock(&p61_dev->write_mutex); + NFC_LOG_REC("%s ret %d- Exit\n", __func__, ret); + return ret; +} + +#ifdef P61_IRQ_ENABLE + +/** + * \ingroup spi_driver + * \brief To disable IRQ + * + * \param[in] struct p61_dev * + * + * \retval void + * +*/ + +static void p61_disable_irq(struct p61_dev *p61_dev) +{ + unsigned long flags; + + P61_DBG_MSG("Entry : %s\n", __func__); + + spin_lock_irqsave(&p61_dev->irq_enabled_lock, flags); + if (p61_dev->irq_enabled) { + disable_irq_nosync(p61_dev->spi->irq); + p61_dev->irq_enabled = false; + } + spin_unlock_irqrestore(&p61_dev->irq_enabled_lock, flags); + + P61_DBG_MSG("Exit : %s\n", __func__); +} + +/** + * \ingroup spi_driver + * \brief Will get called when interrupt line asserted from P61 + * + * \param[in] int + * \param[in] void * + * + * \retval IRQ handle + * +*/ + +static irqreturn_t p61_dev_irq_handler(int irq, void *dev_id) +{ + struct p61_dev *p61_dev = dev_id; + + P61_DBG_MSG("Entry : %s\n", __func__); + p61_disable_irq(p61_dev); + + /* Wake up waiting readers */ + wake_up(&p61_dev->read_wq); + + P61_DBG_MSG("Exit : %s\n", __func__); + return IRQ_HANDLED; +} +#endif + +/** + * \ingroup spi_driver + * \brief Used to read data from P61 in Poll/interrupt mode configured using ioctl call + * + * \param[in] struct file * + * \param[in] char * + * \param[in] size_t + * \param[in] loff_t * + * + * \retval read size + * +*/ + +static ssize_t p61_dev_read(struct file *filp, char *buf, size_t count, + loff_t * offset) +{ + int ret = -EIO; + struct p61_dev *p61_dev = filp->private_data; +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + unsigned char *rx_buffer = p61_dev->r_buf; +#else + unsigned char rx_buffer[MAX_BUFFER_SIZE]; +#endif + + NFC_LOG_REC("%s count %zu - Enter\n", __func__, count); + + mutex_lock(&p61_dev->read_mutex); + if (count > MAX_BUFFER_SIZE) { + count = MAX_BUFFER_SIZE; + } + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + memset(&rx_buffer[0], 0x00, MAX_BUFFER_SIZE); +#else + memset(&rx_buffer[0], 0x00, sizeof(rx_buffer)); +#endif + + if (p61_dev->enable_poll_mode) { + NFC_LOG_REC("%s Poll Mode Enabled\n", __func__); + + NFC_LOG_REC("SPI_READ returned %zu\n", count); + ret = spi_read(p61_dev->spi, (void *)&rx_buffer[0], count); + if (0 > ret) { + NFC_LOG_ERR("spi_read failed [SOF]\n"); + goto fail; + } + } else { +#ifdef P61_IRQ_ENABLE + NFC_LOG_REC("%s Interrupt Mode Enabled\n", __func__); + if (!gpio_get_value(p61_dev->irq_gpio)) { + if (filp->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + goto fail; + } + while (1) { + NFC_LOG_REC("%s waiting for interrupt\n", __func__); + p61_dev->irq_enabled = true; + enable_irq(p61_dev->spi->irq); + ret = wait_event_interruptible(p61_dev->read_wq, !p61_dev->irq_enabled); + p61_disable_irq(p61_dev); + if (ret) { + NFC_LOG_ERR("wait_event_interruptible() : Failed\n"); + goto fail; + } + + if (gpio_get_value(p61_dev->irq_gpio)) + break; + + NFC_LOG_ERR("%s: spurious interrupt detected\n", __func__); + } + } +#else + NFC_LOG_REC(" %s P61_IRQ_ENABLE not Enabled\n", __func__); +#endif + ret = spi_read(p61_dev->spi, (void *)&rx_buffer[0], count); + if (0 > ret) { + NFC_LOG_ERR("SPI_READ returned 0x%x\n", ret); + ret = -EIO; + goto fail; + } + } + + NFC_LOG_REC("total_count = %zu\n", count); + + if (copy_to_user(buf, &rx_buffer[0], count)) { + NFC_LOG_ERR("%s : failed to copy to user space\n", __func__); + ret = -EFAULT; + goto fail; + } + NFC_LOG_REC("%s ret %d Exit\n", __func__, ret); + NFC_LOG_REC("%s ret %d Exit\n", __func__, rx_buffer[0]); + + mutex_unlock(&p61_dev->read_mutex); + + return ret; + +fail: + NFC_LOG_ERR("Error %s ret %d Exit\n", __func__, ret); + mutex_unlock(&p61_dev->read_mutex); + return ret; +} + +/** + * \ingroup spi_driver + * \brief It will configure the GPIOs required for soft reset, read interrupt & regulated power supply to P61. + * + * \param[in] struct p61_spi_platform_data * + * \param[in] struct p61_dev * + * \param[in] struct spi_device * + * + * \retval 0 if ok. + * +*/ + +#if !IS_ENABLED(CONFIG_SAMSUNG_NFC) +static int p61_hw_setup(struct p61_spi_platform_data *platform_data, + struct p61_dev *p61_dev, struct spi_device *spi) +{ + int ret = -1; + + NFC_LOG_INFO("Entry : %s\n", __func__); +#ifdef P61_IRQ_ENABLE + ret = gpio_request(platform_data->irq_gpio, "p61 irq"); + if (ret < 0) { + NFC_LOG_ERR("gpio request failed gpio = 0x%x\n", platform_data->irq_gpio); + goto fail; + } + + ret = gpio_direction_input(platform_data->irq_gpio); + if (ret < 0) { + NFC_LOG_ERR("gpio request failed gpio = 0x%x\n", platform_data->irq_gpio); + goto fail_irq; + } +#endif + +#ifdef P61_HARD_RESET + /* RC : platform specific settings need to be declare */ +#if !DRAGON_P61 + p61_regulator = regulator_get(&spi->dev, "vaux3"); +#else + p61_regulator = regulator_get(&spi->dev, "8941_l18"); +#endif + if (IS_ERR(p61_regulator)) { + ret = PTR_ERR(p61_regulator); +#if !DRAGON_P61 + NFC_LOG_ERR(" Error to get vaux3 (error code) = %d\n", ret); +#else + NFC_LOG_ERR(" Error to get 8941_l18 (error code) = %d\n", ret); +#endif + return -ENODEV; + } else { + NFC_LOG_INFO("successfully got regulator\n"); + } + + ret = regulator_set_voltage(p61_regulator, 1800000, 1800000); + if (ret != 0) { + NFC_LOG_ERR("Error setting the regulator voltage %d\n", ret); + regulator_put(p61_regulator); + return ret; + } else { + regulator_enable(p61_regulator); + NFC_LOG_INFO("successfully set regulator voltage\n"); + + } + ret = gpio_request(platform_data->rst_gpio, "p61 reset"); + if (ret < 0) { + NFC_LOG_ERR("gpio reset request failed = 0x%x\n", platform_data->rst_gpio); + goto fail_gpio; + } + + /*soft reset gpio is set to default high */ + ret = gpio_direction_output(platform_data->rst_gpio, 1); + if (ret < 0) { + NFC_LOG_ERR("gpio rst request failed gpio = 0x%x\n", platform_data->rst_gpio); + goto fail_gpio; + } +#endif + ret = gpio_request( platform_data->trusted_ese_gpio, "Trusted SE switch"); + if (ret < 0) { + NFC_LOG_ERR("gpio reset request failed = 0x%x\n", + platform_data->trusted_ese_gpio); + gpio_free(platform_data->trusted_ese_gpio); + NFC_LOG_ERR("%s failed\n", __func__); + return ret; + } + ret = gpio_direction_output(platform_data->trusted_ese_gpio,0); + if (ret < 0) { + NFC_LOG_ERR("gpio rst request failed gpio = 0x%x\n", + platform_data->trusted_ese_gpio); + gpio_free(platform_data->trusted_ese_gpio); + NFC_LOG_ERR("%s failed\n, __func__"); + return ret; + } + + ret = ese_reset_gpio_setup(platform_data); + if (ret < 0) { + P61_ERR_MSG("Failed to setup ese reset gpio"); + goto fail; + } + + ret = 0; + NFC_LOG_INFO("Exit : %s\n", __func__); + return ret; +#ifdef P61_IRQ_ENABLE +fail_irq: + gpio_free(platform_data->irq_gpio); +#endif +fail: + NFC_LOG_ERR("p61_hw_setup failed\n"); + return ret; +} +#endif + +/** + * \ingroup spi_driver + * \brief Set the P61 device specific context for future use. + * + * \param[in] struct spi_device * + * \param[in] void * + * + * \retval void + * +*/ + +static inline void p61_set_data(struct spi_device *spi, void *data) +{ + dev_set_drvdata(&spi->dev, data); +} + +/** + * \ingroup spi_driver + * \brief Get the P61 device specific context. + * + * \param[in] const struct spi_device * + * + * \retval Device Parameters + * +*/ + +static inline void *p61_get_data(const struct spi_device *spi) +{ + return dev_get_drvdata(&spi->dev); +} + +/* possible fops on the p61 device */ +static const struct file_operations p61_dev_fops = { + .owner = THIS_MODULE, + .read = p61_dev_read, + .write = p61_dev_write, + .open = p61_dev_open, + .release = p61_dev_release, + .unlocked_ioctl = p61_dev_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = p61_dev_compat_ioctl, +#endif +}; + +#if DRAGON_P61 || IS_ENABLED(CONFIG_SAMSUNG_NFC) +static int p61_parse_dt(struct device *dev, struct p61_spi_platform_data *data) +{ + struct device_node *np = dev->of_node; + int errorno = 0; +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + int ese_det_gpio; + const char *ap_str; + int ret; + + ese_det_gpio = of_get_named_gpio(np, "ese-det-gpio", 0); + if (!gpio_is_valid(ese_det_gpio)) { + NFC_LOG_INFO("%s: ese-det-gpio is not set\n", __func__); + } else { + ret = gpio_request(ese_det_gpio, "ese_det_gpio"); + if (ret < 0) + NFC_LOG_ERR("%s failed to get gpio ese_det_gpio\n", __func__); + + gpio_direction_input(ese_det_gpio); + if (!gpio_get_value(ese_det_gpio)) { + NFC_LOG_INFO("%s: ese is not supported\n", __func__); + return -ENODEV; + } + NFC_LOG_INFO("%s: ese is supported\n", __func__); + } + + if (!of_property_read_string(np, "p61,ap_vendor", &ap_str)) { + if (!strcmp(ap_str, "slsi")) + data->ap_vendor = AP_VENDOR_SLSI; + else if (!strcmp(ap_str, "qct") || !strcmp(ap_str, "qualcomm")) + data->ap_vendor = AP_VENDOR_QCT; + else if (!strcmp(ap_str, "mtk")) + data->ap_vendor = AP_VENDOR_MTK; + NFC_LOG_INFO("AP vendor is %d\n", data->ap_vendor); + } else { + NFC_LOG_INFO("AP vendor is not set\n"); + } + + data->gpio_coldreset = of_property_read_bool(np, "p61,gpio_coldreset_support"); + if (data->gpio_coldreset) { + NFC_LOG_INFO("gpio coldreset supports\n"); + data->rst_gpio = of_get_named_gpio(np, "p61,gpio-rst", 0); + if ((!gpio_is_valid(data->rst_gpio))) + return -EINVAL; + } +#else + data->irq_gpio = of_get_named_gpio(np, "nxp,p61-irq", 0); + if ((!gpio_is_valid(data->irq_gpio))) + return -EINVAL; + + data->rst_gpio = of_get_named_gpio(np, "nxp,p61-rst", 0); + if ((!gpio_is_valid(data->rst_gpio))) + return -EINVAL; + + data->trusted_ese_gpio = of_get_named_gpio(np, "nxp,trusted-se", 0); + if ((!gpio_is_valid(data->trusted_ese_gpio))) + return -EINVAL; + + NFC_LOG_INFO("%s: %d, %d, %d %d\n", __func__, data->irq_gpio, data->rst_gpio, + data->trusted_ese_gpio, errorno); +#endif + + return errorno; +} +#endif +#if IS_ENABLED(CONFIG_SPI_MSM_GENI) +#if IS_ENABLED(CONFIG_MSM_GENI_SE) +/* + * eSE driver can't access spi_geni_master structure because it's defined in drivers/spi/spi-msm-geni.c file. + * so, we need a logic to search se_geni_rsc in "void *spi_geni_master". + */ +struct se_geni_rsc *p61_find_spi_src(struct p61_dev *p61_dev, void *spi_geni_master) +{ + char *offset = spi_geni_master; + struct se_geni_rsc *rsc; + int i; + int max_addr_cnt = 250; + + for (i = 0; i < max_addr_cnt; i++) { + rsc = (struct se_geni_rsc *)offset; + + if (rsc->geni_gpio_active == p61_dev->pinctrl_state[P61_PIN_CTRL_DEFAULT]) { + NFC_LOG_INFO("%s, found se_geni_rsc!\n", __func__); + return rsc; + } + offset++; + } + + /* Check if spi_geni_master structure member which defined in spi-msm-geni.c file is changed or not when failed to find se_geni_src. */ + NFC_LOG_ERR("%s, failed to find se_geni_rsc!\n", __func__); + + return 0; +} + +#else +/* CONFIG_QCOM_GENI_SE */ +struct qc_spi_pinctrl { + struct pinctrl *geni_pinctrl; + struct pinctrl_state *geni_gpio_active; + struct pinctrl_state *geni_gpio_sleep; +}; + +struct qc_spi_pinctrl *p61_find_spi_src(struct p61_dev *p61_dev, void *spi_geni_master) +{ + char *offset = spi_geni_master; + struct qc_spi_pinctrl *spi_pinctrl; + int i; + int max_addr_cnt = 250; + + for (i = 0; i < max_addr_cnt; i++) { + spi_pinctrl = (struct qc_spi_pinctrl *)offset; + + if (spi_pinctrl->geni_pinctrl == p61_dev->pinctrl && + spi_pinctrl->geni_gpio_active == p61_dev->pinctrl_state[P61_PIN_CTRL_DEFAULT]) { + NFC_LOG_INFO("%s, found pinctrl in spi master!\n", __func__); + return spi_pinctrl; + } + + offset++; + } + + NFC_LOG_ERR("%s, failed to find spi pinctrl!\n", __func__); + return 0; +} +#endif +static void p61_set_spi_bus_pincontrol(struct p61_dev *p61_dev) +{ + struct spi_master *master; + void *geni_mas; +#if IS_ENABLED(CONFIG_MSM_GENI_SE) + struct se_geni_rsc *spi_pinctrl; +#else + struct qc_spi_pinctrl *spi_pinctrl; +#endif + static bool called; + + if (!p61_dev || called) + return; + + if (!p61_dev->pinctrl_state[P61_PIN_CTRL_ACTIVE] || !p61_dev->pinctrl_state[P61_PIN_CTRL_SUSPEND]) + return; + NFC_LOG_INFO("%s\n", __func__); + called = true; + master = platform_get_drvdata(p61_dev->spi_pdev); + geni_mas = spi_master_get_devdata(master); + spi_pinctrl = p61_find_spi_src(p61_dev, geni_mas); + if (spi_pinctrl) { + spi_pinctrl->geni_gpio_sleep = + pinctrl_lookup_state(spi_pinctrl->geni_pinctrl, + p61_pinctrl_name[P61_PIN_CTRL_SUSPEND]); + spi_pinctrl->geni_gpio_active = + pinctrl_lookup_state(spi_pinctrl->geni_pinctrl, + p61_pinctrl_name[P61_PIN_CTRL_ACTIVE]); + } +} +#endif + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +static void p61_parse_pinctrl_dt(struct device *dev, struct p61_dev *p61_dev) +{ + struct device_node *spi_dev_node; + struct device_node *np = dev->of_node; + struct platform_device *spi_pdev; + struct pinctrl_state *tmp_pinctrl; + int i; + + spi_dev_node = of_get_parent(np); + if (IS_ERR_OR_NULL(spi_dev_node)) { + NFC_LOG_INFO("no spi pinctrl\n"); + return; + } + + spi_pdev = of_find_device_by_node(spi_dev_node); + if (!spi_pdev) { + NFC_LOG_ERR("finding spi_dev failed\n"); + return; + } + + p61_dev->pinctrl = devm_pinctrl_get(&spi_pdev->dev); + if (IS_ERR(p61_dev->pinctrl)) { + NFC_LOG_ERR("pinctrl_get failed\n"); + return; + } + + for (i = 0; i < P61_PIN_CTRL_MAX; i++) { + tmp_pinctrl = pinctrl_lookup_state(p61_dev->pinctrl, p61_pinctrl_name[i]); + if (!IS_ERR(tmp_pinctrl)) { + NFC_LOG_INFO("pinctrl[%s] found\n", p61_pinctrl_name[i]); + p61_dev->pinctrl_state[i] = tmp_pinctrl; + } + } + + p61_dev->spi_pdev = spi_pdev; +#if IS_ENABLED(CONFIG_SPI_MSM_GENI) + p61_set_spi_bus_pincontrol(p61_dev); +#endif + if (nfc_check_pvdd_status()) + ese_set_spi_pinctrl_for_ese_off(p61_dev); +} + +void ese_set_spi_pinctrl_for_ese_off(void *p61) +{ + struct p61_dev *p61_dev = (struct p61_dev *)p61; + + if (!p61_dev) + p61_dev = g_p61_dev; + + if (!p61_dev) { + pr_err("%s: spi probe is not called\n", __func__); + return; + } + + p61_pinctrl_select(p61_dev, P61_PIN_CTRL_ESE_OFF); /* for LSI AP */ + p61_pinctrl_select(p61_dev, P61_PIN_CTRL_SUSPEND); /* for QC AP */ +} +#endif + +/** + * \ingroup spi_driver + * \brief To probe for P61 SPI interface. If found initialize the SPI clock, bit rate & SPI mode. + * It will create the dev entry (P61) for user space. + * + * \param[in] struct spi_device * + * + * \retval 0 if ok. + * +*/ + +static int p61_probe(struct spi_device *spi) +{ + int ret = -1; + struct p61_spi_platform_data *platform_data = NULL; + struct p61_spi_platform_data platform_data1; + struct p61_dev *p61_dev = NULL; + unsigned int max_speed_hz; + struct device_node *np = spi->dev.of_node; +#ifdef P61_IRQ_ENABLE + unsigned int irq_flags; +#endif + +#ifdef CONFIG_SEC_NFC_LOGGER + nfc_logger_init(); + nfc_logger_set_max_count(-1); +#endif + + NFC_LOG_INFO("%s chip select : %d , bus number = %d\n", __func__, + spi->chip_select, spi->master->bus_num); + + memset(&platform_data1, 0x00, sizeof(struct p61_spi_platform_data)); + +#if !DRAGON_P61 && !IS_ENABLED(CONFIG_SAMSUNG_NFC) + platform_data = spi->dev.platform_data; + if (platform_data == NULL) { + /* RC : rename the platformdata1 name */ + /* TBD: This is only for Panda as we are passing NULL for platform data */ + NFC_LOG_ERR("%s : p61 probe fail\n", __func__); + platform_data1.irq_gpio = P61_IRQ; + platform_data1.rst_gpio = P61_RST; + platform_data = &platform_data1; + NFC_LOG_ERR("%s : p61 probe fail1\n", __func__); + //return -ENODEV; + } +#else + ret = p61_parse_dt(&spi->dev, &platform_data1); + if (ret) { + NFC_LOG_ERR("%s - Failed to parse DT\n", __func__); + goto err_exit; + } + platform_data = &platform_data1; +#endif + p61_dev = kzalloc(sizeof(*p61_dev), GFP_KERNEL); + if (p61_dev == NULL) { + NFC_LOG_ERR("failed to allocate memory for module data\n"); + ret = -ENOMEM; + goto err_exit; + } + +#if !IS_ENABLED(CONFIG_SAMSUNG_NFC) + ret = p61_hw_setup(platform_data, p61_dev, spi); + if (ret < 0) { + NFC_LOG_ERR("Failed to p61_enable_P61_IRQ_ENABLE\n"); + goto err_exit0; + } +#endif + if (platform_data->gpio_coldreset) { + ret = ese_reset_gpio_setup(platform_data); + if (ret < 0) { + P61_ERR_MSG("Failed to setup ese reset gpio"); + goto err_exit0; + } + } + /* gpio cold reset doesn't work. so, set gpio coldreset to false to use i2c cold reset */ + platform_data->gpio_coldreset = false; + + spi->bits_per_word = 8; + spi->mode = SPI_MODE_0; + ret = of_property_read_u32(np, "spi-max-frequency", &max_speed_hz); + if (ret < 0) { + NFC_LOG_ERR("%s: There's no spi-max-frequency property\n", __func__); + goto err_exit0; + } + spi->max_speed_hz = max_speed_hz; + NFC_LOG_INFO("%s : spi max_speed_hz = %d\n", __func__, spi->max_speed_hz); + //spi->chip_select = SPI_NO_CS; + ret = spi_setup(spi); + if (ret < 0) { + NFC_LOG_ERR("failed to do spi_setup()\n"); + goto err_exit0; + } + + p61_dev->spi = spi; +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + p61_parse_pinctrl_dt(&spi->dev, p61_dev); +#endif +#ifndef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE + p61_dev->p61_device.minor = MISC_DYNAMIC_MINOR; + p61_dev->p61_device.name = "p61"; + p61_dev->p61_device.fops = &p61_dev_fops; + p61_dev->p61_device.parent = &spi->dev; +#endif + p61_dev->irq_gpio = platform_data->irq_gpio; + p61_dev->rst_gpio = platform_data->rst_gpio; + p61_dev->trusted_ese_gpio = platform_data->trusted_ese_gpio; + p61_dev->gpio_coldreset = platform_data->gpio_coldreset; +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + p61_dev->ap_vendor = platform_data->ap_vendor; +#else + p61_dev->trusted_ese_gpio = platform_data->trusted_ese_gpio; +#endif + p61_dev->ese_spi_transition_state = ESE_SPI_IDLE; + dev_set_drvdata(&spi->dev, p61_dev); + + /* init mutex and queues */ + init_waitqueue_head(&p61_dev->read_wq); + mutex_init(&p61_dev->read_mutex); + mutex_init(&p61_dev->write_mutex); + if (p61_dev->gpio_coldreset) + ese_reset_init(); + +#ifdef P61_IRQ_ENABLE + spin_lock_init(&p61_dev->irq_enabled_lock); +#endif +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + p61_dev->nfc_node = of_parse_phandle(np, "nxp,nfcc", 0); + if (!p61_dev->nfc_node) { + NFC_LOG_ERR("%s: nxp,nfcc invalid or missing in device tree\n", __func__); + goto err_exit0; + } +#else + ret = of_property_read_string(np, "nxp,nfcc", &p61_dev->nfcc_name); + if (ret < 0) { + NFC_LOG_ERR("%s: nxp,nfcc invalid or missing in device tree (%d)\n", + __func__, ret); + goto err_exit0; + } + NFC_LOG_INFO("%s: device tree set '%s' as eSE power controller\n", + __func__, p61_dev->nfcc_name); +#endif + +#ifndef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE + ret = misc_register(&p61_dev->p61_device); + if (ret < 0) { + NFC_LOG_ERR("misc_register failed! %d\n", ret); + goto err_exit0; + } +#endif +#ifdef P61_IRQ_ENABLE + p61_dev->spi->irq = gpio_to_irq(platform_data->irq_gpio); + + if (p61_dev->spi->irq < 0) { + NFC_LOG_ERR("gpio_to_irq request failed gpio = 0x%x\n", + platform_data->irq_gpio); + goto err_exit1; + } + /* request irq. the irq is set whenever the chip has data available + * for reading. it is cleared when all data has been read. + */ + p61_dev->irq_enabled = true; + irq_flags = IRQF_TRIGGER_RISING | IRQF_ONESHOT; + + ret = request_irq(p61_dev->spi->irq, p61_dev_irq_handler, irq_flags, + p61_dev->p61_device.name, p61_dev); + if (ret) { + NFC_LOG_ERR("request_irq failed\n"); + goto err_exit1; + } + p61_disable_irq(p61_dev); + +#endif +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + p61_dev->r_buf = kzalloc(sizeof(unsigned char) * MAX_BUFFER_SIZE, GFP_KERNEL); + if (p61_dev->r_buf == NULL) { + NFC_LOG_ERR("failed to allocate for spi read buffer\n"); + ret = -ENOMEM; + goto err_exit2; + } + + p61_dev->w_buf = kzalloc(sizeof(unsigned char) * MAX_BUFFER_SIZE, GFP_KERNEL); + if (p61_dev->w_buf == NULL) { + NFC_LOG_ERR("failed to allocate for spi write buffer\n"); + ret = -ENOMEM; + goto err_exit3; + } + wake_lock_init(&p61_dev->ese_lock, WAKE_LOCK_SUSPEND, "ese_lock"); +#if IS_ENABLED(CONFIG_SPI_MSM_GENI) + INIT_DELAYED_WORK(&p61_dev->spi_release_work, p61_spi_release_work); + wake_lock_init(&p61_dev->spi_release_wakelock, WAKE_LOCK_SUSPEND, "ese_spi_wake_lock"); +#endif +#endif + + p61_dev->enable_poll_mode = 0; /* Default IRQ read mode */ +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + g_p61_dev = p61_dev; +#endif + NFC_LOG_INFO("Exit : %s\n", __func__); + return ret; +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +err_exit3: + kfree(p61_dev->r_buf); +err_exit2: +#endif +#ifdef P61_IRQ_ENABLE +err_exit1: +#ifndef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE + misc_deregister(&p61_dev->p61_device); +#endif +#endif +err_exit0: + mutex_destroy(&p61_dev->read_mutex); + mutex_destroy(&p61_dev->write_mutex); + if (p61_dev->gpio_coldreset) + ese_reset_deinit(); + if (p61_dev != NULL) + kfree(p61_dev); +err_exit: + NFC_LOG_ERR("ERROR: Exit : %s ret %d\n", __func__, ret); + return ret; +} + +#ifdef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE +static struct miscdevice p61_misc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "p61", + .fops = &p61_dev_fops, +}; + +static int p61_platform_probe(struct platform_device *pdev) +{ + int ret = -1; + + ret = misc_register(&p61_misc_device); + if (ret < 0) + NFC_LOG_ERR("misc_register failed! %d\n", ret); + + NFC_LOG_INFO("%s: finished...\n", __func__); + return 0; +} + +static int p61_platform_remove(struct platform_device *pdev) +{ + NFC_LOG_INFO("Entry : %s\n", __func__); + return 0; +} + +static const struct of_device_id p61_secure_match_table[] = { + { .compatible = "p61_platform",}, + {}, +}; + +static struct platform_driver p61_platform_driver = { + .driver = { + .name = "p61_platform", + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = p61_secure_match_table, +#endif + }, + .probe = p61_platform_probe, + .remove = p61_platform_remove, +}; +#endif /* CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE */ + +/** + * \ingroup spi_driver + * \brief Will get called when the device is removed to release the resources. + * + * \param[in] struct spi_device + * + * \retval 0 if ok. + * +*/ +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) +static int p61_remove(struct spi_device *spi) +#else +static void p61_remove(struct spi_device *spi) +#endif +{ + struct p61_dev *p61_dev = p61_get_data(spi); + P61_DBG_MSG("Entry : %s\n", __func__); + +#ifdef P61_HARD_RESET + if (p61_regulator != NULL) { + regulator_disable(p61_regulator); + regulator_put(p61_regulator); + } else { + NFC_LOG_ERR("ERROR %s p61_regulator not enabled\n", __func__); + } +#endif + if (p61_dev != NULL) { + gpio_free(p61_dev->rst_gpio); + +#ifdef P61_IRQ_ENABLE + free_irq(p61_dev->spi->irq, p61_dev); + gpio_free(p61_dev->irq_gpio); +#endif +#if !IS_ENABLED(CONFIG_SAMSUNG_NFC) + if (gpio_is_valid(p61_dev->trusted_ese_gpio)) { + gpio_free(p61_dev->trusted_ese_gpio); + } +#endif + + mutex_destroy(&p61_dev->read_mutex); +#ifdef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE + misc_deregister(&p61_misc_device); +#else + misc_deregister(&p61_dev->p61_device); +#endif + if (p61_dev->gpio_coldreset) + ese_reset_deinit(); +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + wake_lock_destroy(&p61_dev->ese_lock); +#endif + kfree(p61_dev); + } + P61_DBG_MSG("Exit : %s\n", __func__); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) + return 0; +#endif +} + +#if DRAGON_P61 +static struct of_device_id p61_dt_match[] = { + { + .compatible = "p61", + }, + {} +}; +#endif +static struct spi_driver p61_driver = { + .driver = { + .name = "p61", + .bus = &spi_bus_type, + .owner = THIS_MODULE, +#if DRAGON_P61 + .of_match_table = p61_dt_match, +#endif + }, + .probe = p61_probe, + .remove = (p61_remove), +}; + +/** + * \ingroup spi_driver + * \brief Module init interface + * + * \param[in] void + * + * \retval handle + * +*/ + +#if IS_MODULE(CONFIG_SAMSUNG_NFC) +int p61_dev_init(void) +{ + int ret; + debug_level = P61_DEBUG_OFF; + + P61_DBG_MSG("Entry : %s\n", __func__); + +#ifdef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE + ret = platform_driver_register(&p61_platform_driver); + NFC_LOG_INFO("%s: platform_driver_register, ret %d\n", __func__, ret); +#endif + ret = spi_register_driver(&p61_driver); + return ret; +} +EXPORT_SYMBOL(p61_dev_init); + +void p61_dev_exit(void) +{ + P61_DBG_MSG("Entry : %s\n", __func__); + +#ifdef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE + platform_driver_unregister(&p61_platform_driver); +#endif + + spi_unregister_driver(&p61_driver); +} +EXPORT_SYMBOL(p61_dev_exit); +#else +static int __init p61_dev_init(void) +{ + int ret; + + debug_level = P61_DEBUG_OFF; + + P61_DBG_MSG("Entry : %s\n", __func__); +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + if (nfc_get_lpcharge() == LPM_TRUE) + return 0; +#endif + +#ifdef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE + ret = platform_driver_register(&p61_platform_driver); + NFC_LOG_INFO("%s: platform_driver_register, ret %d\n", __func__, ret); +#endif + ret = spi_register_driver(&p61_driver); + + return ret; +} + +module_init(p61_dev_init); + +/** + * \ingroup spi_driver + * \brief Module exit interface + * + * \param[in] void + * + * \retval void + * +*/ + +static void __exit p61_dev_exit(void) +{ + P61_DBG_MSG("Entry : %s\n", __func__); +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + if (nfc_get_lpcharge() == LPM_TRUE) + return; +#endif + +#ifdef CONFIG_MAKE_NODE_USING_PLATFORM_DEVICE + platform_driver_unregister(&p61_platform_driver); +#endif + spi_unregister_driver(&p61_driver); +} + +module_exit(p61_dev_exit); +MODULE_ALIAS("spi:p61"); +MODULE_AUTHOR("BHUPENDRA PAWAR"); +MODULE_DESCRIPTION("NXP P61 SPI driver"); +MODULE_LICENSE("GPL"); +#endif +/** @} */ diff --git a/drivers/nfc/nxp_combined/p73.h b/drivers/nfc/nxp_combined/p73.h new file mode 100644 index 000000000000..0c6696aa345a --- /dev/null +++ b/drivers/nfc/nxp_combined/p73.h @@ -0,0 +1,113 @@ +/****************************************************************************** + * + * Copyright (C) 2012-2020 NXP Semiconductors + * * + * 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 _P73_H_ +#define _P73_H_ +#define P61_MAGIC 0xEB +#define P61_SET_PWR _IOW(P61_MAGIC, 0x01, long) +#define P61_SET_DBG _IOW(P61_MAGIC, 0x02, long) +#define P61_SET_POLL _IOW(P61_MAGIC, 0x03, long) +/* + * SPI Request NFCC to enable p61 power, only in param + * Only for SPI + * level 1 = Enable power + * level 0 = Disable power + */ +#define P61_SET_SPM_PWR _IOW(P61_MAGIC, 0x04, long) + +/* SPI or DWP can call this ioctl to get the current + * power state of P61 + * +*/ +#define P61_GET_SPM_STATUS _IOR(P61_MAGIC, 0x05, long) + +/* throughput measurement is deprecated */ +/* #define P61_SET_THROUGHPUT _IOW(P61_MAGIC, 0x06, long) */ +#define P61_GET_ESE_ACCESS _IOW(P61_MAGIC, 0x07, long) + +#define P61_SET_POWER_SCHEME _IOW(P61_MAGIC, 0x08, long) + +#define P61_SET_DWNLD_STATUS _IOW(P61_MAGIC, 0x09, long) + +#define P61_INHIBIT_PWR_CNTRL _IOW(P61_MAGIC, 0x0A, long) + +/* SPI can call this IOCTL to perform the eSE COLD_RESET + * via NFC driver. + */ +#define ESE_PERFORM_COLD_RESET _IOW(P61_MAGIC, 0x0C, long) + +#define PERFORM_RESET_PROTECTION _IOW(P61_MAGIC, 0x0D, long) + +#define ESE_SET_TRUSTED_ACCESS _IOW(P61_MAGIC, 0x0B, long) + +#define P61_RW_SPI_DATA _IOWR(P61_MAGIC, 0x0F, unsigned long) + +#ifdef CONFIG_COMPAT +#define P61_SET_PWR_COMPAT _IOW(P61_MAGIC, 0x01, unsigned int) +#define P61_SET_DBG_COMPAT _IOW(P61_MAGIC, 0x02, unsigned int) +#define P61_SET_POLL_COMPAT _IOW(P61_MAGIC, 0x03, unsigned int) +#define P61_SET_SPM_PWR_COMPAT _IOW(P61_MAGIC, 0x04, unsigned int) +#define P61_GET_SPM_STATUS_COMPAT _IOR(P61_MAGIC, 0x05, unsigned int) +/* throughput measurement is deprecated */ +/* #define P61_SET_THROUGHPUT_COMPAT _IOW(P61_MAGIC, 0x06, unsigned int) */ +#define P61_GET_ESE_ACCESS_COMPAT _IOW(P61_MAGIC, 0x07, unsigned int) +#define P61_SET_POWER_SCHEME_COMPAT _IOW(P61_MAGIC, 0x08, unsigned int) +#define P61_SET_DWNLD_STATUS_COMPAT _IOW(P61_MAGIC, 0x09, unsigned int) +#define P61_INHIBIT_PWR_CNTRL_COMPAT _IOW(P61_MAGIC, 0x0A, unsigned int) +#define P61_RW_SPI_DATA_COMPAT _IOWR(P61_MAGIC, 0x0F, unsigned int) + +#define ESE_PERFORM_COLD_RESET_COMPAT _IOW(P61_MAGIC, 0x0C, unsigned int) +#define PERFORM_RESET_PROTECTION_COMPAT _IOW(P61_MAGIC, 0x0D, unsigned int) +#endif + +typedef enum ese_spi_transition_state { + ESE_SPI_IDLE = 0x00, + ESE_SPI_BUSY +} ese_spi_transition_state_t; + +struct p61_spi_platform_data { + int irq_gpio; + int rst_gpio; + int trusted_ese_gpio; + bool gpio_coldreset; +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) + int ap_vendor; +#endif +}; + +struct p61_ioctl_transfer { + unsigned char *rx_buffer; + unsigned char *tx_buffer; + unsigned int len; +}; + +#ifdef CONFIG_COMPAT +struct p61_ioctl_transfer32 { + u32 rx_buffer; + u32 tx_buffer; + u32 len; +}; +#endif + +#if IS_ENABLED(CONFIG_SAMSUNG_NFC) +void store_nfc_i2c_device(struct device *nfc_i2c_dev); +void p61_print_status(const char *func_name); +#endif +#endif diff --git a/drivers/nfc/nxp_combined/recovery_fw.c b/drivers/nfc/nxp_combined/recovery_fw.c new file mode 100644 index 000000000000..c210337bd464 --- /dev/null +++ b/drivers/nfc/nxp_combined/recovery_fw.c @@ -0,0 +1,1325 @@ +/******************************************************************************************** + * + * Copyright 2021 NXP + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"),to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + ********************************************************************************************/ +#include +const uint8_t gphDnldNfc_DlSequence[] = { + 0x01, 0x34, 0xC0, 0x00, 0xDE, 0x10, 0xAD, 0x69, 0xE0, 0x28, + 0xAC, 0xFA, 0xF2, 0x4A, 0x0F, 0x49, 0x7E, 0x6A, 0x61, 0xD1, + 0x00, 0xFD, 0x4A, 0x66, 0xDD, 0x42, 0x4D, 0xFF, 0x90, 0xA5, + 0x6C, 0x54, 0xF0, 0x53, 0x5E, 0x17, 0x1E, 0x28, 0x8B, 0xF2, + 0x56, 0x6D, 0x74, 0x7B, 0x4E, 0xBA, 0xEB, 0x8D, 0x22, 0x43, + 0x01, 0xE9, 0xC4, 0x85, 0x2B, 0xFB, 0xA7, 0xD9, 0x3C, 0x64, + 0x63, 0x2D, 0xBB, 0x63, 0x5C, 0xDB, 0x14, 0x4D, 0x86, 0xA0, + 0x73, 0xC1, 0x32, 0xA4, 0x12, 0xD0, 0xED, 0x12, 0x90, 0xCF, + 0xAF, 0x56, 0x35, 0x3D, 0x97, 0x6B, 0xAC, 0x81, 0x1B, 0xD6, + 0x77, 0x9A, 0x4F, 0x9C, 0x90, 0x7D, 0x16, 0x6E, 0xC4, 0xCD, + 0x2D, 0xD2, 0x57, 0x49, 0x99, 0x24, 0xBC, 0x06, 0x71, 0x04, + 0xA8, 0x14, 0xEF, 0x21, 0x55, 0x0D, 0xAD, 0x82, 0x88, 0x5C, + 0xEF, 0xDF, 0x03, 0x93, 0xB5, 0xE8, 0x21, 0x18, 0xE0, 0x54, + 0xB8, 0xE1, 0x7E, 0x88, 0xE9, 0xF7, 0xEE, 0x86, 0xD2, 0x36, + 0xD7, 0x4B, 0x99, 0x6C, 0x35, 0x8B, 0xE0, 0xA2, 0xA2, 0xFB, + 0xED, 0xB5, 0x9A, 0xA0, 0xEE, 0x19, 0x67, 0x66, 0x14, 0x14, + 0xEC, 0xEF, 0x3F, 0xC0, 0x25, 0x7C, 0xA8, 0x9F, 0x58, 0x11, + 0xCD, 0x99, 0x00, 0xBF, 0xB8, 0x6D, 0xD5, 0x06, 0x8A, 0x5D, + 0x40, 0xB0, 0x2C, 0x9A, 0x20, 0xA2, 0x30, 0xDA, 0xFD, 0xB0, + 0xE3, 0x05, 0x5F, 0xB7, 0x3B, 0x9C, 0xD6, 0xB8, 0x92, 0xAE, + 0x4B, 0xDC, 0xF2, 0x1D, 0xA4, 0x11, 0xCE, 0x0C, 0xFD, 0x37, + 0x6E, 0xBA, 0x8B, 0x6C, 0x84, 0x3E, 0x3A, 0x3D, 0x20, 0xBE, + 0x64, 0xA6, 0x56, 0x7F, 0x06, 0x66, 0x04, 0x38, 0x1D, 0x97, + 0xD9, 0x96, 0xE6, 0x07, 0x29, 0xE2, 0x1A, 0xC4, 0x5F, 0x0A, + 0xAC, 0x62, 0x50, 0xF8, 0xD2, 0x33, 0x3F, 0x7A, 0x50, 0x89, + 0x98, 0x44, 0xCB, 0xFD, 0x75, 0x79, 0x25, 0xB1, 0x3A, 0xDE, + 0x53, 0x86, 0x13, 0x54, 0x46, 0x0B, 0x5F, 0x94, 0xEC, 0x1B, + 0x72, 0x24, 0x35, 0x84, 0x67, 0xC8, 0xBE, 0xDB, 0xAC, 0xB2, + 0x0C, 0x94, 0x88, 0xE1, 0xC4, 0x36, 0xD1, 0x6B, 0xB9, 0x4C, + 0xAE, 0xB0, 0x55, 0xF8, 0xBE, 0xD4, 0x7D, 0x16, 0x76, 0x84, + 0x2E, 0x46, 0x0B, 0xF6, 0x0D, 0x43, 0xB6, 0xBB, 0x5F, 0x98, + 0x02, 0x26, 0xC0, 0x00, 0x02, 0x35, 0x79, 0x26, 0x91, 0x8C, + 0x34, 0x8D, 0x69, 0x6E, 0x7F, 0x6B, 0xBF, 0xBD, 0x81, 0x3F, + 0x9C, 0x94, 0x0C, 0x65, 0x47, 0xC5, 0x08, 0xA7, 0x14, 0x6F, + 0x5A, 0xAC, 0x7C, 0xAC, 0x0C, 0x15, 0xD5, 0x6F, 0xBF, 0xDC, + 0xCE, 0x97, 0xD0, 0xD5, 0xEB, 0x1D, 0x3C, 0x7A, 0xEB, 0x2A, + 0x4E, 0x76, 0x08, 0xF2, 0xEA, 0x3A, 0xA0, 0xB8, 0xFC, 0xA0, + 0x74, 0x92, 0x8F, 0x86, 0xB1, 0x15, 0x0D, 0x7D, 0x92, 0x0F, + 0x64, 0xCE, 0xCA, 0xC1, 0xC4, 0x20, 0xA2, 0x48, 0x2D, 0xB0, + 0x47, 0x24, 0xF1, 0x20, 0x77, 0xDF, 0x87, 0x0D, 0xFA, 0xC6, + 0x89, 0xEE, 0xBD, 0x5E, 0xD5, 0x85, 0x64, 0xBC, 0x40, 0x6E, + 0x86, 0x01, 0x3D, 0xB6, 0x83, 0x8C, 0xAA, 0xF3, 0x2D, 0x3A, + 0xBB, 0x0D, 0xE6, 0xA5, 0xC0, 0x64, 0x07, 0xCB, 0x57, 0x81, + 0x7E, 0xD8, 0x3C, 0x3D, 0x4A, 0x9F, 0x8E, 0xF0, 0x57, 0x2E, + 0x5E, 0x41, 0x42, 0x00, 0xB0, 0xC7, 0x4E, 0x17, 0xED, 0xC6, + 0xAB, 0x73, 0x71, 0x65, 0x1D, 0x60, 0x34, 0x2A, 0x2B, 0xAF, + 0x59, 0x82, 0x5E, 0x16, 0x48, 0x48, 0x0F, 0x86, 0x62, 0x0C, + 0x84, 0x56, 0x00, 0x03, 0x2A, 0xA6, 0x2C, 0x21, 0xD0, 0x70, + 0xF9, 0xE5, 0x37, 0x0E, 0x81, 0x20, 0x36, 0x4E, 0x86, 0x8D, + 0xCF, 0xED, 0xBD, 0x0D, 0x49, 0x75, 0x0E, 0x5B, 0x80, 0xF7, + 0xAF, 0x40, 0x56, 0x8B, 0xD8, 0xFC, 0xC6, 0xE4, 0x6D, 0xD6, + 0x2E, 0x0D, 0xD0, 0x76, 0x75, 0x39, 0x3E, 0xF0, 0xEA, 0xC5, + 0x23, 0x12, 0x06, 0x45, 0xEA, 0x04, 0x6D, 0xC1, 0xA2, 0x95, + 0x95, 0x40, 0xD6, 0x6B, 0x65, 0xD6, 0x7D, 0x62, 0xA5, 0xB4, + 0x6B, 0x6C, 0x24, 0x3E, 0xFB, 0xAB, 0x71, 0x4D, 0xFC, 0x24, + 0x9F, 0x71, 0x8C, 0x04, 0x9A, 0xEE, 0x6D, 0x72, 0x3A, 0x01, + 0x11, 0xC1, 0x01, 0xB2, 0xC2, 0xC8, 0xBA, 0x7D, 0x53, 0x56, + 0x0D, 0x3F, 0x35, 0xF6, 0x86, 0x46, 0x7C, 0x67, 0xBF, 0x83, + 0x04, 0x01, 0x98, 0xBC, 0x06, 0x08, 0xF3, 0x89, 0x88, 0x8E, + 0x93, 0xB3, 0xA9, 0x21, 0x18, 0x71, 0xFF, 0xFC, 0x4E, 0xF7, + 0xFE, 0x1A, 0x5D, 0xC9, 0x21, 0xF6, 0x3B, 0x27, 0x2C, 0x26, + 0x37, 0xE2, 0x4F, 0x8C, 0x94, 0x77, 0xC7, 0x0D, 0xB9, 0x74, + 0xCD, 0x9F, 0xE1, 0x70, 0xFD, 0x35, 0x11, 0xA2, 0xB6, 0xAC, + 0x39, 0x3D, 0xC9, 0x57, 0x94, 0x3F, 0x10, 0x89, 0x9F, 0x0F, + 0x7D, 0x49, 0x0E, 0xFE, 0x84, 0x34, 0x87, 0x5B, 0xA5, 0xA0, + 0x5E, 0x0D, 0xE4, 0x05, 0x5A, 0x45, 0x8B, 0x31, 0x28, 0xF0, + 0x80, 0x7A, 0xF9, 0x56, 0xE7, 0x60, 0xB0, 0x31, 0xBB, 0x75, + 0x7E, 0x30, 0x74, 0x53, 0x14, 0xF6, 0xDE, 0x24, 0x9E, 0xE0, + 0xB9, 0x9F, 0xE6, 0xB0, 0x5D, 0x5B, 0x7A, 0xF3, 0xFD, 0x0D, + 0x4C, 0xCA, 0xAD, 0x01, 0xE4, 0x3F, 0xFE, 0x1D, 0x03, 0xE7, + 0xD4, 0xC6, 0xC1, 0xE9, 0xF5, 0x9E, 0x72, 0x9C, 0x4A, 0x09, + 0x85, 0x2C, 0xBE, 0x46, 0x49, 0xE1, 0x0F, 0xBF, 0x27, 0xF2, + 0x81, 0xC3, 0x33, 0x95, 0xEC, 0xBE, 0x37, 0x2B, 0x65, 0xA5, + 0xEC, 0xF7, 0x69, 0x23, 0x3B, 0xA6, 0xA6, 0xC3, 0xC7, 0x9F, + 0x9B, 0x79, 0xE6, 0x37, 0xB4, 0xDB, 0x33, 0x3B, 0xC5, 0x19, + 0x50, 0x7B, 0xBB, 0x7B, 0x4B, 0xFF, 0x0A, 0x3A, 0x0F, 0xDE, + 0x9D, 0x1F, 0x70, 0xE0, 0x9C, 0x37, 0x9B, 0x61, 0x01, 0xD9, + 0xCB, 0x9F, 0xC7, 0x1F, 0x9B, 0x25, 0x7D, 0x18, 0xF0, 0x6F, + 0x12, 0x54, 0xC2, 0x17, 0x8F, 0xB2, 0x1F, 0x5D, 0x0F, 0x73, + 0xB2, 0xEE, 0xE9, 0x0E, 0xC4, 0xA1, 0x70, 0x48, 0xDF, 0x0B, + 0xB4, 0xDD, 0xFF, 0xC7, 0x01, 0xA6, 0xBC, 0x08, 0xCE, 0x0E, + 0xB4, 0x4A, 0xAE, 0xFF, 0x88, 0xA6, 0x4D, 0x60, 0xC7, 0x55, + 0x38, 0x74, 0x3C, 0x1D, 0x8F, 0x82, 0xD3, 0xD3, 0x31, 0x60, + 0xE4, 0xB2, 0x24, 0x76, 0xE7, 0xD8, 0x53, 0x05, 0x20, 0x3E, + 0x5C, 0xEF, 0x17, 0x4C, 0x20, 0x6F, 0x6A, 0x6D, 0x10, 0xA3, + 0xA4, 0xE9, 0xFB, 0x76, 0x66, 0x99, 0x5D, 0xD1, 0x34, 0x52, + 0x42, 0xBF, 0x02, 0x26, 0xC0, 0x00, 0x02, 0x9E, 0xED, 0xAE, + 0xE5, 0x23, 0x85, 0x5C, 0xFF, 0xAD, 0x6E, 0x89, 0x02, 0x33, + 0x97, 0x74, 0x05, 0x59, 0x1E, 0x73, 0x4E, 0xD3, 0x9A, 0x97, + 0xD3, 0x85, 0x2B, 0x12, 0xFB, 0xA2, 0xF0, 0xA4, 0x4C, 0x2A, + 0x58, 0xFB, 0x56, 0x6C, 0xE9, 0xA6, 0x04, 0x07, 0x9F, 0xBC, + 0x76, 0x66, 0x01, 0x80, 0xB8, 0x1E, 0x4A, 0x8A, 0x0C, 0x76, + 0x8A, 0x3C, 0x5F, 0x25, 0xAD, 0x83, 0xF3, 0x10, 0x02, 0xE1, + 0x38, 0x4B, 0xFA, 0x81, 0xD2, 0xC5, 0x6D, 0x18, 0xA7, 0xD8, + 0x0A, 0x4E, 0xC9, 0xC7, 0xA3, 0x19, 0x52, 0x0D, 0xBB, 0xA9, + 0xDE, 0x9C, 0x90, 0x57, 0x71, 0x1A, 0x37, 0xA8, 0x70, 0x4D, + 0x75, 0x7E, 0x34, 0x90, 0x74, 0x0E, 0x02, 0xC8, 0x8A, 0x80, + 0x3D, 0x06, 0x67, 0xF4, 0xF2, 0xD4, 0x15, 0x66, 0x0D, 0x23, + 0xAE, 0x46, 0x0A, 0x23, 0x7B, 0x36, 0x96, 0x48, 0x7D, 0x99, + 0xF4, 0x09, 0xE3, 0xA9, 0x53, 0xAC, 0x94, 0xB7, 0x23, 0x7E, + 0x57, 0xCF, 0x90, 0xCD, 0x13, 0x0D, 0x50, 0xBD, 0xC9, 0xE4, + 0xC2, 0x22, 0x5F, 0x28, 0x11, 0xF8, 0x1F, 0x42, 0x33, 0xEE, + 0xF3, 0xB4, 0xED, 0x8F, 0xF4, 0xA0, 0xAE, 0xF5, 0xAE, 0x56, + 0x59, 0xC3, 0x65, 0xDB, 0xF2, 0x51, 0x6D, 0x15, 0xA3, 0xAF, + 0xA5, 0xC7, 0x9F, 0x7A, 0xE8, 0xCC, 0xB4, 0xD8, 0xCA, 0x39, + 0x3F, 0x79, 0xB3, 0x86, 0xDB, 0x37, 0x52, 0xDA, 0x5E, 0xDB, + 0x7F, 0x53, 0x60, 0x43, 0x75, 0x53, 0x93, 0xD4, 0xA2, 0xE2, + 0xE7, 0xB7, 0x42, 0xF0, 0x97, 0xA5, 0xB5, 0x52, 0xD3, 0xCF, + 0xE7, 0x70, 0x6F, 0x10, 0xD4, 0x85, 0xC4, 0x4B, 0x3D, 0x09, + 0xE1, 0x02, 0xB8, 0xED, 0xA5, 0xCC, 0x7B, 0x2D, 0x68, 0xEF, + 0xEF, 0x9E, 0x87, 0x8C, 0xB7, 0xC9, 0x85, 0xA8, 0x01, 0xC2, + 0xCF, 0x43, 0xB5, 0x6D, 0x30, 0x2A, 0x9F, 0x06, 0x96, 0xE0, + 0x43, 0xEC, 0x3F, 0xC1, 0x2F, 0x7D, 0x4D, 0x85, 0x76, 0xD3, + 0xF7, 0xFA, 0xC8, 0x84, 0x20, 0xA8, 0x3C, 0xD9, 0x3E, 0x4A, + 0xB4, 0x87, 0x05, 0xCF, 0x9B, 0x51, 0x68, 0xF9, 0x49, 0xBA, + 0x4D, 0x68, 0x97, 0x6E, 0x79, 0xDB, 0x04, 0x51, 0x66, 0x6F, + 0xF9, 0x59, 0x2D, 0x55, 0x96, 0x13, 0x59, 0x52, 0x30, 0xB8, + 0x73, 0xD1, 0x12, 0x33, 0x31, 0xEC, 0x4C, 0x0C, 0x8E, 0xD4, + 0x47, 0xE7, 0x30, 0xC6, 0x98, 0xB5, 0x5B, 0x35, 0x1B, 0xAC, + 0x51, 0xBB, 0xFA, 0xC3, 0x8D, 0x3E, 0x89, 0x83, 0x1C, 0xED, + 0xB1, 0x03, 0x9C, 0xC7, 0x5C, 0x89, 0xF9, 0xC2, 0xE3, 0x45, + 0x91, 0xDB, 0x41, 0x0A, 0x22, 0xD1, 0x90, 0x39, 0xD6, 0x9D, + 0x0A, 0xD8, 0x36, 0xDC, 0xDB, 0xDD, 0x63, 0x22, 0xF8, 0x7B, + 0x4D, 0x90, 0x4C, 0x27, 0x0F, 0xCC, 0x16, 0x0E, 0x32, 0x46, + 0xD7, 0x20, 0x5A, 0x43, 0xC4, 0xC5, 0x37, 0x2E, 0xEB, 0x3F, + 0x42, 0x2C, 0xFA, 0x99, 0xE2, 0xF9, 0x70, 0xB3, 0xC3, 0xCF, + 0x4C, 0x67, 0xEB, 0x7C, 0x9D, 0xAF, 0x96, 0x15, 0x97, 0xD2, + 0x07, 0x3B, 0xF6, 0xEF, 0x2F, 0x98, 0xAA, 0x1D, 0x45, 0xDC, + 0x11, 0xBA, 0xF6, 0x0C, 0x18, 0x64, 0x80, 0xF2, 0x6B, 0xBD, + 0x3C, 0x85, 0xC1, 0xCD, 0x78, 0xD0, 0x62, 0x79, 0x0F, 0xCD, + 0xCA, 0x3D, 0x94, 0x0A, 0x11, 0xEF, 0x11, 0x86, 0xFA, 0x3F, + 0x31, 0xB2, 0xF1, 0x2C, 0x74, 0x1B, 0x57, 0x05, 0xD4, 0x4F, + 0xAF, 0xE7, 0xCB, 0x60, 0x9E, 0x78, 0x82, 0xAD, 0xF3, 0x34, + 0x6A, 0x2F, 0xDC, 0xA1, 0xC9, 0xEA, 0x3E, 0x6E, 0x01, 0x80, + 0x17, 0x5B, 0xCC, 0xBB, 0xED, 0xD0, 0x30, 0x11, 0xBE, 0xEE, + 0x2E, 0x9F, 0xCE, 0xE1, 0xFF, 0x32, 0xB8, 0x7D, 0x40, 0xC8, + 0x46, 0x0F, 0x41, 0x16, 0xE1, 0xB3, 0x98, 0x47, 0xCE, 0xE1, + 0x41, 0xDE, 0x80, 0xA7, 0x56, 0x83, 0xA8, 0xDA, 0xC3, 0x49, + 0x33, 0x6F, 0x93, 0x68, 0xA0, 0xC6, 0x1A, 0x0B, 0x82, 0x38, + 0x56, 0xEE, 0xBB, 0x97, 0x5D, 0xBD, 0x8A, 0x32, 0x2D, 0xFE, + 0x40, 0xC7, 0x0D, 0xCA, 0x32, 0x08, 0xCC, 0xE2, 0x18, 0x57, + 0xBB, 0xC1, 0x60, 0x43, 0x02, 0x26, 0xC0, 0x00, 0x02, 0xAD, + 0x9E, 0x61, 0x0D, 0x67, 0x25, 0xE4, 0x6E, 0x8F, 0x6D, 0xEA, + 0x16, 0x98, 0xFA, 0x76, 0x6D, 0x6D, 0x3C, 0xED, 0x4B, 0x25, + 0x60, 0xC6, 0xAB, 0x59, 0x91, 0x43, 0xAA, 0xAA, 0xEC, 0x8F, + 0xA1, 0x3E, 0xB1, 0x78, 0x72, 0xEE, 0xD1, 0x91, 0xBA, 0xD9, + 0x9B, 0xE3, 0x55, 0x61, 0x98, 0x46, 0x4B, 0xC5, 0x41, 0x3B, + 0x9C, 0x88, 0x1D, 0x82, 0xD5, 0xD5, 0x99, 0x13, 0x64, 0x44, + 0x00, 0xBD, 0xBE, 0xC4, 0xF6, 0x6A, 0x2D, 0xE3, 0xD0, 0xE7, + 0x88, 0xD3, 0xA1, 0x48, 0x88, 0x84, 0x8A, 0x25, 0xAB, 0x90, + 0x43, 0xFB, 0x3D, 0x1B, 0x06, 0xB2, 0xFD, 0x24, 0x58, 0x0C, + 0xFA, 0x0A, 0xD3, 0x23, 0x73, 0xA7, 0xF8, 0x50, 0xC5, 0x1B, + 0x3A, 0x75, 0xF4, 0x24, 0xA8, 0x52, 0xFB, 0x1B, 0x61, 0xAB, + 0x79, 0x06, 0x54, 0xD9, 0x1D, 0x3A, 0x72, 0x4F, 0x8F, 0xD3, + 0xC7, 0x71, 0xA8, 0x66, 0x80, 0x49, 0x8D, 0x50, 0xD1, 0xBF, + 0xC8, 0x70, 0xAE, 0x4F, 0x64, 0x53, 0x26, 0x37, 0xDA, 0x6B, + 0x62, 0xFE, 0xF9, 0x60, 0xAD, 0xAD, 0x49, 0x51, 0x37, 0x2E, + 0x61, 0xA9, 0xAA, 0x72, 0xFD, 0x02, 0x20, 0x0B, 0xB0, 0xB0, + 0x07, 0x95, 0x0C, 0x38, 0x13, 0xB6, 0xA4, 0x33, 0xFA, 0xFA, + 0x12, 0xE1, 0x62, 0x00, 0x92, 0x46, 0x23, 0xAA, 0xD7, 0x9D, + 0xBD, 0xAF, 0x15, 0xCA, 0x8C, 0x88, 0x2A, 0x4B, 0x1B, 0xA8, + 0x06, 0xA0, 0xC2, 0x80, 0x8E, 0x98, 0x46, 0xA1, 0x84, 0x2C, + 0x4A, 0x0F, 0xEA, 0x70, 0xE6, 0xDD, 0x17, 0x30, 0x8A, 0x41, + 0x69, 0x8A, 0x1B, 0xC2, 0xC7, 0x18, 0xD5, 0xB3, 0x94, 0x4F, + 0x51, 0x15, 0xCC, 0x37, 0x9A, 0x9E, 0xFD, 0x12, 0x40, 0xC1, + 0xA3, 0x06, 0x0F, 0x36, 0xFC, 0x04, 0x18, 0x35, 0x40, 0x9C, + 0x9E, 0x46, 0x5E, 0x14, 0xB6, 0xF7, 0xBF, 0xE5, 0x21, 0x12, + 0xF2, 0xE2, 0x17, 0xB3, 0x63, 0x8C, 0xD1, 0xE3, 0xA9, 0x0A, + 0x80, 0x1B, 0x74, 0x7A, 0x58, 0x91, 0x88, 0x9D, 0xAE, 0xE6, + 0x05, 0x1D, 0x3C, 0x1E, 0xE9, 0x95, 0x08, 0x75, 0xEE, 0x20, + 0xA8, 0x4B, 0x3D, 0x36, 0xAF, 0xB0, 0xFA, 0x94, 0x62, 0x5D, + 0x9F, 0xD5, 0x50, 0x2A, 0x24, 0xFC, 0x1D, 0xDC, 0x39, 0xCB, + 0x0D, 0x5A, 0xA1, 0xDA, 0xEF, 0xC9, 0xB3, 0x62, 0xF8, 0x7D, + 0x0C, 0xE4, 0x6B, 0xEC, 0xF0, 0xF7, 0x96, 0x63, 0x58, 0x80, + 0x55, 0x22, 0x6D, 0x42, 0x38, 0xE9, 0x1D, 0x69, 0xD5, 0x2F, + 0x03, 0x3C, 0xCD, 0x27, 0x34, 0x99, 0x39, 0xF0, 0x5C, 0xE0, + 0x23, 0x70, 0x85, 0x4B, 0x30, 0xBD, 0x21, 0x01, 0xF6, 0x06, + 0x0B, 0xED, 0xBA, 0xA1, 0x6F, 0xE0, 0x6E, 0xD3, 0x78, 0xA8, + 0x56, 0x94, 0x92, 0x84, 0xA8, 0x60, 0x35, 0xA8, 0x86, 0x56, + 0x41, 0xEA, 0x12, 0x34, 0x86, 0x52, 0x18, 0x75, 0x43, 0x01, + 0x0A, 0xCE, 0xBA, 0x04, 0xF7, 0x32, 0x09, 0x2D, 0xB1, 0xAC, + 0x04, 0xB0, 0x4E, 0xEA, 0xBE, 0xDE, 0xDE, 0x95, 0x37, 0xDA, + 0x86, 0x72, 0xFE, 0x10, 0x16, 0xBB, 0xA7, 0xE9, 0x67, 0xC9, + 0x3C, 0x85, 0x18, 0xFF, 0xD7, 0x74, 0x25, 0x3A, 0x95, 0x04, + 0xD5, 0x7F, 0x99, 0x41, 0x6A, 0x6A, 0x2C, 0xF6, 0x3E, 0x3C, + 0x4B, 0xA7, 0xB9, 0xDA, 0x13, 0xFC, 0xF6, 0x46, 0xEF, 0x7E, + 0xB4, 0xA6, 0x1B, 0x36, 0x93, 0x5B, 0xDA, 0xDB, 0x6A, 0xC7, + 0x37, 0x58, 0x3C, 0x4F, 0x52, 0x7E, 0x39, 0xD7, 0xE2, 0xBA, + 0x79, 0xA7, 0x9A, 0x05, 0x8A, 0xF5, 0x65, 0x86, 0xF4, 0x52, + 0xBC, 0x79, 0x6D, 0xA9, 0xFE, 0xEE, 0xE6, 0xC5, 0x2B, 0x28, + 0x62, 0x8E, 0xF6, 0x6E, 0xD5, 0x08, 0x90, 0x96, 0x72, 0x7A, + 0x6B, 0x61, 0x8B, 0x6A, 0xE6, 0xCD, 0x05, 0x63, 0x12, 0x9A, + 0xF7, 0x01, 0xAC, 0xF7, 0x8F, 0x1F, 0xE0, 0xCA, 0x1E, 0xF9, + 0x86, 0xC1, 0xF6, 0x0D, 0x2D, 0x9F, 0x8A, 0x2C, 0x8B, 0x3C, + 0xE4, 0x89, 0xDF, 0x72, 0x86, 0x17, 0xA8, 0x7F, 0x9D, 0x8B, + 0x0D, 0x87, 0xCB, 0xC5, 0xAE, 0xE3, 0x90, 0xB1, 0xD9, 0x8B, + 0x5E, 0x04, 0x97, 0xAA, 0x19, 0x85, 0x02, 0x26, 0xC0, 0x00, + 0x02, 0x26, 0xDA, 0x00, 0x7E, 0x08, 0xC0, 0x08, 0x12, 0x9A, + 0x78, 0x4E, 0x9D, 0xA7, 0xB0, 0x5E, 0x0D, 0x76, 0x6D, 0x4D, + 0xC7, 0x89, 0x35, 0xDD, 0xB5, 0x58, 0x9F, 0x20, 0x3C, 0x41, + 0x77, 0x9B, 0x85, 0x06, 0x21, 0xA4, 0x99, 0x56, 0xF9, 0x5B, + 0x07, 0x01, 0x06, 0x1A, 0xE1, 0x1B, 0xC7, 0x9F, 0x46, 0x5D, + 0x1F, 0xB8, 0xB2, 0x91, 0xA5, 0xC2, 0x01, 0x94, 0xFA, 0x53, + 0x74, 0xFA, 0x6A, 0x5B, 0x63, 0x08, 0x2E, 0x24, 0xB0, 0xCF, + 0x03, 0x40, 0x9C, 0xA1, 0xF2, 0xDB, 0xA2, 0x35, 0xD7, 0xCA, + 0x78, 0x4E, 0x9D, 0x8B, 0x92, 0x4A, 0xD1, 0xEA, 0xF8, 0x14, + 0xCA, 0xA0, 0x32, 0x55, 0x95, 0xA0, 0x1F, 0xB7, 0x1D, 0x8B, + 0x8E, 0x8E, 0xA6, 0xF0, 0x2A, 0x30, 0x76, 0xB4, 0x9B, 0x6F, + 0xAB, 0xBC, 0xC7, 0xA8, 0xF6, 0x2B, 0x9D, 0x1D, 0xC2, 0x24, + 0x06, 0x10, 0x06, 0xE7, 0x59, 0x34, 0xE9, 0x30, 0xA8, 0xF5, + 0x61, 0x55, 0xEC, 0xFB, 0xA1, 0x43, 0x17, 0x10, 0x08, 0xFF, + 0x3C, 0x93, 0x03, 0xFF, 0x83, 0x35, 0x81, 0x77, 0x7E, 0x97, + 0xC2, 0xA0, 0x53, 0x2A, 0x26, 0xD4, 0xDF, 0xC0, 0xA3, 0x21, + 0x07, 0x14, 0x41, 0xD3, 0x7C, 0xE5, 0x40, 0x00, 0xDE, 0x32, + 0xF5, 0xB9, 0xBB, 0x61, 0x44, 0xBC, 0xA4, 0xFD, 0x67, 0xAA, + 0x69, 0xF4, 0x96, 0x93, 0xD2, 0xAF, 0xEA, 0xC5, 0x72, 0xDB, + 0xEA, 0x88, 0x2A, 0x5F, 0xC5, 0xAB, 0x00, 0xFA, 0xA9, 0x32, + 0x61, 0x58, 0xBE, 0xC0, 0x42, 0xA9, 0xFA, 0xB6, 0x44, 0xD6, + 0x59, 0xF8, 0x26, 0x8B, 0xB9, 0x0A, 0xD9, 0xE2, 0x5B, 0x1B, + 0x20, 0xD1, 0x34, 0x91, 0x81, 0xF3, 0xFF, 0xED, 0x6D, 0x81, + 0xF9, 0x7D, 0xE3, 0x73, 0x37, 0xC1, 0x01, 0x12, 0x28, 0x9B, + 0xEA, 0xDA, 0x0E, 0x3D, 0x68, 0x16, 0x23, 0xE1, 0x68, 0xFF, + 0x1A, 0x59, 0xCC, 0x89, 0xF8, 0x44, 0x70, 0x13, 0xB2, 0x98, + 0xB1, 0x31, 0xEA, 0xEC, 0x65, 0x21, 0x36, 0xEF, 0xCC, 0x85, + 0x62, 0xC3, 0xEC, 0x62, 0xE9, 0x06, 0xEC, 0xB3, 0x47, 0x94, + 0xE5, 0xF2, 0x6F, 0xFD, 0x80, 0xEE, 0x70, 0xB0, 0x06, 0x56, + 0x8B, 0xF2, 0xDC, 0x7A, 0x52, 0x90, 0xC2, 0xE4, 0x77, 0xDD, + 0xF7, 0xC2, 0x0C, 0x9C, 0xBE, 0x5A, 0x0F, 0xC6, 0x45, 0xB1, + 0x3A, 0x63, 0x38, 0x2C, 0xD9, 0xC4, 0x45, 0x08, 0x44, 0x90, + 0xE2, 0xBC, 0xA2, 0x5A, 0xE6, 0x2E, 0xFD, 0xCB, 0x36, 0x7F, + 0xA9, 0xAC, 0x8C, 0x34, 0x1A, 0x3C, 0xE2, 0x9B, 0x24, 0x45, + 0xE3, 0x9C, 0xCF, 0xF9, 0x96, 0xFE, 0x58, 0xB5, 0x29, 0x20, + 0x0B, 0xC9, 0x5C, 0xAF, 0xCF, 0x7F, 0xCB, 0x8A, 0x14, 0xE1, + 0xCD, 0xF7, 0x5B, 0x93, 0xBC, 0x7D, 0x7A, 0x3B, 0xD2, 0xF2, + 0xFA, 0x2B, 0x57, 0x02, 0x9C, 0xAC, 0xA2, 0x16, 0x11, 0x2D, + 0xDB, 0x3D, 0x42, 0x26, 0x87, 0xBE, 0x9F, 0x8B, 0x7B, 0x00, + 0x20, 0x09, 0x41, 0x05, 0xB0, 0x42, 0x98, 0x44, 0xD6, 0xCC, + 0x08, 0xA2, 0x20, 0x1F, 0x2A, 0x59, 0xB3, 0x05, 0x4F, 0xB4, + 0xA6, 0xF8, 0xF8, 0xFD, 0x27, 0xBB, 0xC5, 0xC3, 0x52, 0x4D, + 0x63, 0x37, 0xCF, 0xAE, 0x4C, 0x60, 0x1E, 0x98, 0x26, 0x12, + 0x3D, 0xB9, 0xE6, 0x87, 0x09, 0x17, 0xB1, 0xE4, 0x81, 0x2C, + 0x8E, 0x73, 0xA1, 0x40, 0x53, 0x96, 0xD8, 0x17, 0x7F, 0x39, + 0xA8, 0x4F, 0xE9, 0xEF, 0x30, 0xE2, 0x5E, 0xEF, 0x9C, 0x13, + 0x70, 0x21, 0x14, 0xA3, 0x5D, 0xEA, 0x43, 0xFB, 0xA6, 0x80, + 0x70, 0xB9, 0x4B, 0x68, 0x9C, 0x5A, 0x82, 0x59, 0x00, 0xFA, + 0x5E, 0x4C, 0x4F, 0x76, 0xB1, 0xF4, 0x45, 0xCD, 0xE6, 0x18, + 0x09, 0xE2, 0x36, 0x8A, 0x60, 0x4B, 0xF4, 0x61, 0x55, 0xC4, + 0xE9, 0x69, 0xC1, 0x03, 0x9E, 0x9C, 0xAF, 0x1C, 0xE5, 0xD1, + 0xFF, 0x45, 0x16, 0x43, 0xD7, 0xE5, 0x4B, 0xCC, 0xEA, 0x24, + 0x2E, 0xCE, 0xE3, 0x90, 0x17, 0xDB, 0xC4, 0x57, 0x4D, 0xF9, + 0xCB, 0xEC, 0x09, 0x62, 0xBD, 0xD8, 0x7A, 0x89, 0x55, 0x92, + 0x90, 0x7B, 0x22, 0x20, 0xD9, 0x9A, 0xC9, 0x19, 0x02, 0x26, + 0xC0, 0x00, 0x02, 0x2E, 0xA2, 0xCF, 0xDE, 0xE3, 0xDE, 0xA9, + 0x10, 0x3D, 0xEB, 0xD9, 0x4B, 0x51, 0x68, 0x41, 0x57, 0xCD, + 0x7C, 0xF4, 0x28, 0x84, 0x22, 0x1C, 0xA7, 0xA6, 0x8A, 0xDD, + 0x97, 0x5B, 0x8A, 0x07, 0x01, 0x97, 0x13, 0x20, 0x06, 0x12, + 0xC9, 0xBB, 0x6D, 0x12, 0x25, 0x6D, 0x08, 0x13, 0x3C, 0x32, + 0x48, 0xFE, 0x49, 0xD9, 0xC1, 0x3E, 0x00, 0x1C, 0xD9, 0xF0, + 0xE2, 0xB3, 0xE0, 0x07, 0xE1, 0x8F, 0xDF, 0x75, 0xE0, 0xB9, + 0x61, 0x1A, 0xBF, 0xAF, 0x66, 0xE0, 0xAB, 0x8A, 0x4B, 0x71, + 0xCB, 0x4F, 0xB7, 0x4C, 0x30, 0xC8, 0xB0, 0xC4, 0xC0, 0x38, + 0x73, 0xDD, 0x3C, 0xDC, 0x5D, 0x81, 0x40, 0x14, 0x23, 0x9C, + 0xD2, 0x48, 0xF9, 0xCB, 0xC4, 0x1B, 0xD5, 0xC7, 0x85, 0xB0, + 0x57, 0xC1, 0xF5, 0x41, 0x7A, 0x2E, 0xC6, 0x03, 0x86, 0x8C, + 0x4E, 0xAF, 0x24, 0x21, 0xB9, 0x0B, 0xE9, 0x24, 0x12, 0x02, + 0x3E, 0x35, 0x7C, 0x1D, 0x9A, 0x2D, 0x5E, 0xB7, 0xEE, 0x08, + 0x34, 0x2C, 0x2B, 0xAD, 0x8D, 0x02, 0xF6, 0xFE, 0x06, 0x88, + 0xD0, 0xA5, 0xA2, 0x1E, 0x5B, 0xD2, 0x88, 0x09, 0x9F, 0x43, + 0xDF, 0xC4, 0x85, 0x59, 0x50, 0x7B, 0x94, 0xD9, 0xBB, 0xBE, + 0xF5, 0x1D, 0x60, 0xD0, 0x90, 0x6F, 0xD5, 0x1E, 0x49, 0xF1, + 0x76, 0x04, 0x96, 0xCA, 0x3F, 0x44, 0x64, 0xBE, 0x41, 0xE7, + 0x4A, 0x6E, 0xA8, 0x11, 0x14, 0x21, 0xEA, 0x9E, 0x43, 0x6D, + 0x2E, 0xF6, 0x49, 0x7A, 0x8C, 0xD6, 0xF1, 0x4A, 0xC3, 0x17, + 0x16, 0x23, 0x3B, 0x02, 0x6A, 0xA7, 0x90, 0x1D, 0x8C, 0xBF, + 0xB4, 0x25, 0xDA, 0xB6, 0x0F, 0x2A, 0x60, 0xF5, 0xC1, 0x7F, + 0xFA, 0x1D, 0x0B, 0xAF, 0x28, 0x8A, 0x00, 0x8A, 0xBE, 0xA8, + 0x72, 0x20, 0x5F, 0xEC, 0x3F, 0x16, 0x6E, 0xAB, 0xC4, 0xFC, + 0x6B, 0x75, 0xD7, 0xFB, 0x73, 0x60, 0x7E, 0x25, 0xE5, 0xA3, + 0x91, 0x6A, 0x33, 0xD3, 0x9E, 0xAC, 0xFB, 0x8C, 0xD1, 0xC6, + 0x63, 0xF4, 0x3F, 0xB7, 0xD1, 0xD3, 0x88, 0x90, 0x4C, 0xA2, + 0x7E, 0x6B, 0x19, 0x61, 0xCC, 0xAB, 0x48, 0xDD, 0x8D, 0xE2, + 0x1B, 0xB7, 0x9E, 0x2F, 0x58, 0x10, 0x78, 0xAC, 0x94, 0xF6, + 0xD6, 0x62, 0x4D, 0x66, 0x78, 0x67, 0x9F, 0x1B, 0x3A, 0x78, + 0x4E, 0xA0, 0xDB, 0x47, 0x92, 0xC4, 0x43, 0x1A, 0x22, 0xFC, + 0x26, 0x38, 0xA4, 0xF2, 0x7A, 0x52, 0x31, 0x71, 0x63, 0x16, + 0x58, 0xF5, 0xA4, 0x4A, 0xCA, 0x73, 0xD5, 0x90, 0x39, 0x55, + 0x8F, 0xB2, 0xC0, 0x3F, 0x3A, 0xEC, 0x69, 0xC4, 0x42, 0xCE, + 0xB9, 0x1B, 0xA4, 0x32, 0x52, 0x14, 0x7C, 0xBB, 0xF6, 0xB3, + 0x5A, 0x7C, 0xF1, 0x75, 0x1E, 0x4B, 0xB8, 0xB0, 0xB3, 0x8E, + 0x13, 0x63, 0x7B, 0xF5, 0xB9, 0x93, 0x22, 0x98, 0xDF, 0x6C, + 0xEC, 0x51, 0x4F, 0xC8, 0x0B, 0xA0, 0x14, 0x57, 0x75, 0x1B, + 0xF6, 0xE9, 0x5D, 0xC2, 0x47, 0x65, 0xDF, 0x79, 0x0D, 0x48, + 0xBE, 0x4F, 0x46, 0xF0, 0x37, 0xA5, 0x7C, 0xA3, 0x6B, 0x3E, + 0xE6, 0xA2, 0x0E, 0x69, 0xAF, 0x3C, 0x46, 0x8A, 0x77, 0xD2, + 0xBF, 0x2A, 0x16, 0x00, 0x17, 0x2F, 0x9C, 0x4E, 0xD8, 0xA8, + 0x48, 0xD4, 0xCC, 0x0C, 0x29, 0xD1, 0x7A, 0xAE, 0x81, 0x4F, + 0x04, 0x76, 0x53, 0xCF, 0x22, 0x76, 0x39, 0x61, 0xB6, 0x76, + 0x42, 0xE6, 0x4E, 0x71, 0xB3, 0x06, 0xB9, 0x31, 0x04, 0x60, + 0x88, 0x04, 0x02, 0x89, 0x72, 0x13, 0xD8, 0x8E, 0xD7, 0xE7, + 0x88, 0xCC, 0x3B, 0x88, 0x4A, 0xC7, 0x96, 0x58, 0x12, 0xDA, + 0x75, 0x15, 0xE2, 0x9A, 0xFA, 0x9E, 0x7C, 0x0E, 0xD6, 0x86, + 0x64, 0x7F, 0x31, 0xE3, 0x5C, 0xD8, 0xCF, 0xC0, 0x6D, 0x9B, + 0x1A, 0x1D, 0x8E, 0x90, 0xED, 0x8E, 0x7B, 0xFD, 0xAF, 0xE0, + 0x85, 0xE2, 0x51, 0x49, 0xAE, 0xE6, 0x03, 0x78, 0x41, 0xB9, + 0x05, 0xA0, 0xB8, 0x25, 0x03, 0x51, 0xD1, 0x93, 0x05, 0xE1, + 0xAA, 0x19, 0x0F, 0x1A, 0xF0, 0xD6, 0x18, 0xB6, 0x23, 0xFD, + 0xBC, 0x6E, 0x10, 0xA5, 0x18, 0xE9, 0x3B, 0xE5, 0xA4, 0x22, + 0x02, 0x26, 0xC0, 0x00, 0x02, 0x55, 0x43, 0x38, 0x66, 0x6B, + 0x96, 0x00, 0x6F, 0x33, 0xEE, 0x72, 0x7A, 0x9E, 0xD2, 0x5C, + 0x1F, 0x87, 0x4C, 0xFC, 0xE9, 0x3C, 0xAB, 0xC7, 0x5E, 0x55, + 0xD6, 0xEF, 0x78, 0xB1, 0x4D, 0xA7, 0x92, 0x3F, 0x57, 0x3B, + 0xF5, 0x7F, 0xE0, 0xD6, 0xE0, 0xD0, 0x8D, 0xE5, 0xE1, 0xAE, + 0x48, 0xC1, 0xF7, 0xF3, 0xC3, 0xA4, 0xF7, 0x8F, 0xE7, 0x6F, + 0xB2, 0xB1, 0xA5, 0x6F, 0x6A, 0xCF, 0x5F, 0x3C, 0xF2, 0x50, + 0x31, 0x19, 0x89, 0xF1, 0x74, 0x55, 0xA2, 0xFF, 0x7A, 0x5D, + 0xCB, 0x96, 0xED, 0xE4, 0xEA, 0x28, 0x4B, 0xF7, 0xE4, 0x45, + 0x7D, 0x99, 0xAF, 0xC5, 0xDB, 0xC1, 0x2D, 0xE8, 0xE9, 0xEA, + 0x2B, 0xAD, 0x7E, 0x8E, 0x96, 0x25, 0xA0, 0x9E, 0xDA, 0x31, + 0xE4, 0xCD, 0xC6, 0x8D, 0x69, 0x39, 0x04, 0x53, 0x1B, 0xCA, + 0xEF, 0x96, 0x8E, 0xEF, 0xFB, 0x4E, 0xC8, 0x11, 0x8E, 0x42, + 0x03, 0xC2, 0x96, 0x1A, 0x2C, 0xD8, 0xCF, 0x17, 0x05, 0x36, + 0xF7, 0x02, 0xAB, 0xBD, 0x1A, 0xF3, 0x51, 0xC0, 0xE2, 0x1E, + 0x0D, 0x8D, 0xE9, 0x81, 0x57, 0xD1, 0xA1, 0x9A, 0x2C, 0x7D, + 0x43, 0xBD, 0x23, 0x50, 0xA9, 0x3D, 0x77, 0xC1, 0x5F, 0x2D, + 0x3A, 0x61, 0x56, 0xE1, 0x47, 0xB4, 0x6C, 0xB1, 0xDF, 0xD7, + 0x1E, 0x95, 0x00, 0x02, 0x5B, 0xDA, 0xBD, 0x39, 0x2A, 0x06, + 0x98, 0x2B, 0x54, 0x63, 0xC9, 0xDB, 0x30, 0xF2, 0xB6, 0xB6, + 0xC8, 0x22, 0xAB, 0xB5, 0x68, 0xA3, 0xB7, 0x94, 0x6C, 0x97, + 0x5B, 0xC0, 0x6F, 0xA5, 0x11, 0xFD, 0x9A, 0x52, 0x5A, 0xB5, + 0x3D, 0xEA, 0xC3, 0x23, 0xD1, 0xA7, 0x31, 0xA1, 0xCE, 0xA8, + 0x4A, 0x7C, 0x5D, 0xF6, 0x21, 0xC1, 0x38, 0x73, 0xA3, 0x83, + 0x00, 0x28, 0x9D, 0x76, 0x5A, 0xC5, 0x63, 0xDB, 0x47, 0x25, + 0xAB, 0xD7, 0x25, 0xAE, 0x7D, 0x87, 0xF1, 0xA1, 0xE6, 0x9C, + 0x83, 0x33, 0x30, 0xE1, 0xAD, 0x65, 0xFD, 0x0F, 0xBB, 0xD0, + 0xEF, 0xF5, 0xC3, 0x19, 0x9B, 0x9D, 0x13, 0xBA, 0xEF, 0x9F, + 0x13, 0x2F, 0x76, 0x55, 0x2C, 0x2A, 0xC3, 0x8B, 0x40, 0x79, + 0x49, 0x02, 0x88, 0xE7, 0x4B, 0x57, 0xF5, 0xC5, 0x56, 0x30, + 0xF2, 0xB8, 0x2D, 0x83, 0x6F, 0xB6, 0xBF, 0xE2, 0x7E, 0xFF, + 0x18, 0x61, 0x11, 0x6B, 0x77, 0xCC, 0x04, 0x8B, 0xE4, 0x7C, + 0xF9, 0x48, 0x9C, 0xE0, 0x10, 0x7F, 0x68, 0xFE, 0x40, 0xF0, + 0x23, 0x5C, 0x13, 0x56, 0xD7, 0xCC, 0xDA, 0x83, 0xD6, 0x5C, + 0xB2, 0xED, 0x3F, 0x4C, 0x1E, 0x0A, 0x2F, 0x9D, 0xA2, 0x08, + 0x95, 0xB1, 0x80, 0xFB, 0x04, 0xAC, 0xED, 0x88, 0x01, 0xDC, + 0x69, 0xC7, 0x2D, 0xE8, 0xEB, 0xD1, 0xCE, 0x8E, 0x32, 0x04, + 0x0B, 0xA5, 0x1E, 0xFA, 0x47, 0xA2, 0x8F, 0xB6, 0xF4, 0xC8, + 0xFE, 0x81, 0xFC, 0x68, 0xFF, 0x82, 0x9A, 0x9A, 0xB6, 0x2C, + 0x58, 0x55, 0xEE, 0xFC, 0xBB, 0x23, 0x47, 0xA4, 0xEB, 0xF8, + 0x08, 0xB1, 0xF0, 0xAA, 0xE3, 0x7A, 0xDC, 0xEF, 0xD6, 0xB4, + 0x60, 0x39, 0x6F, 0x0F, 0x8A, 0x4C, 0x65, 0x48, 0x65, 0x78, + 0x6D, 0xF8, 0x44, 0x13, 0xA2, 0x9A, 0x76, 0xE8, 0xA7, 0x06, + 0x33, 0x54, 0x74, 0x73, 0xE4, 0x69, 0x66, 0x27, 0xA0, 0x66, + 0xA0, 0x5A, 0x9E, 0x26, 0xF2, 0xA4, 0x3C, 0xA6, 0xCF, 0x86, + 0x0F, 0x9D, 0x0A, 0x75, 0xC1, 0x9C, 0x34, 0xD1, 0x40, 0x21, + 0x64, 0x62, 0x7B, 0xBF, 0xAE, 0xCD, 0x44, 0x17, 0x6D, 0x5F, + 0x21, 0x02, 0x17, 0x1A, 0x2C, 0xA4, 0x18, 0x1D, 0x4F, 0x2E, + 0xC8, 0xE2, 0xCF, 0xAE, 0x25, 0x4A, 0xC4, 0xA2, 0x5A, 0x2E, + 0x10, 0x09, 0x69, 0xB2, 0xEA, 0xF9, 0xB9, 0x4C, 0x36, 0xA4, + 0xD6, 0xCB, 0x03, 0x6E, 0x7B, 0xC1, 0xA4, 0x28, 0x1A, 0x09, + 0xBF, 0xF1, 0xA1, 0x3F, 0x75, 0xB8, 0x5A, 0xF3, 0x9F, 0x12, + 0x35, 0x4B, 0x16, 0x21, 0x3B, 0x8D, 0xF9, 0x87, 0xCB, 0x8A, + 0xEC, 0x29, 0x1B, 0xF0, 0xB3, 0x96, 0x77, 0xB7, 0x7B, 0xA8, + 0x6A, 0x98, 0xE4, 0x8B, 0x61, 0xB6, 0x47, 0xD7, 0x92, 0xB4, + 0x0B, 0x47, 0x02, 0x26, 0xC0, 0x00, 0x02, 0x4B, 0x86, 0xD9, + 0x89, 0x5B, 0x11, 0x29, 0x1D, 0x8E, 0x56, 0xC4, 0x4A, 0x0D, + 0xD4, 0x9F, 0x43, 0x9F, 0xA2, 0x55, 0x58, 0xE1, 0xF6, 0xF6, + 0x58, 0xFB, 0xCD, 0x3F, 0x06, 0x4C, 0xC2, 0x60, 0x6D, 0xB7, + 0xB2, 0xFF, 0xFE, 0x89, 0x77, 0x7A, 0x1E, 0xA0, 0xC8, 0xDD, + 0x7F, 0x3E, 0x6B, 0x38, 0x2F, 0x41, 0x2B, 0xDF, 0x42, 0x14, + 0x50, 0x28, 0xEE, 0xDE, 0x3F, 0xD8, 0xFF, 0x09, 0x43, 0x7A, + 0x22, 0x4F, 0xF3, 0xFC, 0x60, 0x7E, 0x2C, 0x1A, 0xB6, 0x9E, + 0x21, 0x40, 0x76, 0x3B, 0x42, 0xFC, 0xBC, 0x1D, 0x00, 0xA4, + 0x55, 0x95, 0x47, 0xBD, 0x6E, 0x31, 0xAB, 0x05, 0xAD, 0x38, + 0x53, 0xB0, 0x24, 0x83, 0x7D, 0x0E, 0xA8, 0x40, 0xE6, 0x33, + 0x6D, 0xEF, 0x72, 0x35, 0xCF, 0x3C, 0x11, 0x67, 0x1D, 0x28, + 0xEF, 0x7B, 0x7A, 0x7A, 0x0E, 0xD2, 0x7F, 0xBA, 0xEB, 0xA0, + 0x49, 0x46, 0x29, 0x40, 0x5B, 0x62, 0xA9, 0xED, 0xC9, 0xAC, + 0xDD, 0xAF, 0xE9, 0xAF, 0xF9, 0x75, 0x70, 0xC7, 0xAD, 0xB2, + 0xB6, 0x51, 0x08, 0x23, 0x89, 0x1F, 0xCC, 0xDE, 0xB7, 0x6B, + 0x25, 0xC4, 0x77, 0x90, 0x01, 0xBA, 0x80, 0x62, 0x3E, 0xFF, + 0x93, 0x00, 0x15, 0x43, 0x7D, 0xBE, 0x80, 0x83, 0xBB, 0xB0, + 0xAA, 0x3B, 0xF2, 0x0A, 0x9D, 0x86, 0x23, 0xA4, 0xB7, 0xB7, + 0x44, 0x30, 0xA7, 0x17, 0x66, 0x70, 0xEE, 0x2D, 0x1F, 0xDF, + 0xBD, 0xD9, 0x14, 0x3E, 0x55, 0xC6, 0xC9, 0x5B, 0xC3, 0xF2, + 0xE5, 0x10, 0xD5, 0x1A, 0x93, 0xF6, 0x76, 0xAB, 0x71, 0x87, + 0xDE, 0xD5, 0x51, 0x99, 0x37, 0x6F, 0xEE, 0xB1, 0x63, 0xDA, + 0x7F, 0x3B, 0xAC, 0x77, 0x1B, 0x4B, 0xA3, 0xE3, 0x77, 0x41, + 0xEA, 0x23, 0xF3, 0xB9, 0x22, 0x75, 0x07, 0x8E, 0x64, 0x3E, + 0x32, 0xA9, 0x28, 0xAA, 0x93, 0x8F, 0xE8, 0xDA, 0x60, 0xE9, + 0xD7, 0x35, 0x7F, 0xD7, 0x8F, 0xC8, 0xE5, 0x5B, 0x61, 0x47, + 0xD6, 0xD4, 0xA7, 0xFF, 0x25, 0x8F, 0x03, 0xC3, 0x03, 0x07, + 0xED, 0x30, 0x45, 0x86, 0x73, 0x1E, 0x8F, 0x2F, 0x3D, 0xB4, + 0xC0, 0x37, 0x7A, 0xE7, 0x80, 0xEA, 0xA0, 0xDB, 0x98, 0xAE, + 0xF9, 0x24, 0x0C, 0x74, 0x36, 0x54, 0x13, 0x34, 0x30, 0xCF, + 0x0E, 0xC8, 0x9D, 0x94, 0xE7, 0xAF, 0x6A, 0xC0, 0x32, 0x32, + 0xA3, 0xDE, 0x3A, 0x5B, 0x72, 0x72, 0xE3, 0x8F, 0x91, 0xC5, + 0x44, 0xAB, 0x86, 0x7B, 0xE0, 0x6C, 0x80, 0x84, 0x64, 0x1D, + 0xC0, 0xF1, 0x28, 0x7C, 0x6A, 0x35, 0x76, 0xCF, 0x12, 0x56, + 0x84, 0x1C, 0x21, 0x51, 0x33, 0x00, 0xFF, 0xB2, 0x64, 0x30, + 0x44, 0x1C, 0xA5, 0x05, 0x09, 0xE2, 0x14, 0x8E, 0xF3, 0xD8, + 0x91, 0x21, 0x60, 0xC0, 0x51, 0x68, 0x62, 0xF5, 0x7E, 0x06, + 0xD5, 0xC0, 0xEE, 0xB8, 0x91, 0xF1, 0x52, 0x14, 0x6C, 0x27, + 0xB6, 0x6A, 0x1C, 0x64, 0xBF, 0x59, 0x47, 0x64, 0x03, 0x8D, + 0x4E, 0xEB, 0xA3, 0x73, 0x10, 0xE2, 0xBC, 0xA0, 0x30, 0x29, + 0xE6, 0xF5, 0xED, 0x04, 0xCA, 0xAA, 0xC2, 0xBA, 0xDB, 0x8E, + 0xBC, 0x00, 0x34, 0x3D, 0xB6, 0x12, 0xCB, 0xAF, 0xC0, 0x3D, + 0x97, 0xF7, 0x5A, 0x1B, 0x83, 0x90, 0x91, 0xD0, 0xE2, 0xCF, + 0xE6, 0x21, 0x07, 0xCF, 0x2E, 0xFD, 0x71, 0xA1, 0x10, 0x18, + 0x67, 0x3F, 0x8F, 0xE2, 0x86, 0xFA, 0xA6, 0xDF, 0x6D, 0xE4, + 0x31, 0x7D, 0x75, 0x12, 0x99, 0x23, 0xC7, 0xFB, 0xF2, 0x04, + 0x76, 0x4C, 0x93, 0x9B, 0xB9, 0x89, 0x1D, 0x88, 0x5A, 0x0E, + 0xDE, 0x5A, 0x27, 0x35, 0x88, 0xE7, 0x80, 0x14, 0x87, 0xCA, + 0x23, 0xE6, 0xEF, 0xA8, 0xEA, 0x73, 0x7D, 0x93, 0x0C, 0x61, + 0x81, 0x2E, 0x10, 0xDF, 0x13, 0x57, 0x96, 0xD9, 0x36, 0x68, + 0x93, 0x42, 0x7A, 0x67, 0x60, 0x44, 0x57, 0xD8, 0x6C, 0x4B, + 0xD4, 0xCC, 0x7E, 0x70, 0xE2, 0xCD, 0xC7, 0x14, 0x4A, 0xDE, + 0x32, 0x35, 0x8B, 0x4B, 0x14, 0xE0, 0x8D, 0x5C, 0x33, 0x72, + 0xC9, 0x5D, 0x1F, 0xF6, 0xD7, 0xC3, 0xCF, 0x72, 0xA1, 0x2C, + 0x98, 0x2C, 0x0E, 0xEA, 0x02, 0x26, 0xC0, 0x00, 0x02, 0xEC, + 0xFC, 0x5D, 0x99, 0x61, 0x74, 0x77, 0x64, 0xF2, 0x70, 0x18, + 0x1A, 0x0B, 0x9F, 0x5A, 0x64, 0xE4, 0xCE, 0xA9, 0xCE, 0xCF, + 0x15, 0x35, 0x0E, 0x90, 0x95, 0xD1, 0x78, 0xBC, 0x36, 0xDD, + 0x32, 0xB0, 0x72, 0xCF, 0xCE, 0xFE, 0x8D, 0xAA, 0xF4, 0x19, + 0x73, 0x80, 0x37, 0xB6, 0x7B, 0x88, 0xB5, 0x7F, 0x5B, 0x7E, + 0xC7, 0x1F, 0x40, 0x5E, 0xCF, 0xC5, 0xE3, 0x75, 0x43, 0xCA, + 0x9D, 0x7C, 0xA1, 0x5D, 0x12, 0xBE, 0x30, 0x0E, 0x2C, 0x6D, + 0xE1, 0xD4, 0xD9, 0xC0, 0x90, 0x9F, 0x13, 0x6C, 0x00, 0x75, + 0xC9, 0x98, 0xE8, 0x4A, 0x1E, 0x43, 0x27, 0xB2, 0x3D, 0x16, + 0x5D, 0xF7, 0x18, 0xF6, 0xF2, 0x57, 0xA8, 0x54, 0x57, 0x50, + 0xFD, 0x98, 0x0F, 0x99, 0x63, 0x9E, 0x94, 0x71, 0x8B, 0x6B, + 0x6A, 0xAC, 0x27, 0x7A, 0xE5, 0xFE, 0x49, 0x5F, 0xA9, 0x3F, + 0x72, 0x32, 0xE5, 0x67, 0x87, 0xE9, 0xCC, 0xBC, 0x64, 0xE9, + 0x6B, 0x15, 0x06, 0x60, 0x32, 0x43, 0x49, 0x53, 0x47, 0xB2, + 0x56, 0xCE, 0xBF, 0x5F, 0x9B, 0x16, 0x40, 0x7D, 0x90, 0x0E, + 0xFB, 0xFE, 0x66, 0x58, 0xB3, 0xFC, 0x42, 0xB5, 0x90, 0xE6, + 0xCA, 0x6C, 0xD0, 0x3C, 0xA0, 0x2D, 0x84, 0x5E, 0xA0, 0xE7, + 0xD7, 0x8B, 0xB0, 0x42, 0x56, 0x3D, 0x48, 0xAF, 0x18, 0xEF, + 0xAF, 0x27, 0x76, 0xE3, 0x26, 0x0B, 0xCA, 0xA0, 0x01, 0x4B, + 0x79, 0xD1, 0xAC, 0xD5, 0x8B, 0xCD, 0x70, 0x9F, 0x6E, 0xFB, + 0x72, 0xBB, 0x9B, 0xDA, 0x4A, 0xFA, 0x96, 0x45, 0x29, 0x59, + 0x16, 0x4B, 0x57, 0xC1, 0x7B, 0x5D, 0x94, 0x5E, 0xDA, 0xC1, + 0x2B, 0x3C, 0xD9, 0xD5, 0x6B, 0x23, 0xE7, 0x53, 0x9C, 0xAA, + 0x89, 0x07, 0xFC, 0x66, 0xBC, 0xBC, 0xDD, 0x5D, 0xC7, 0xC0, + 0x46, 0xAF, 0xF6, 0xCB, 0xBB, 0xD3, 0x37, 0xC1, 0x7F, 0xCD, + 0xAE, 0x69, 0x1F, 0xCA, 0x20, 0x07, 0x76, 0x3F, 0xD0, 0x71, + 0x4F, 0x19, 0x6A, 0x92, 0xA2, 0xD8, 0x81, 0xE7, 0x84, 0xC3, + 0x24, 0x55, 0xF3, 0x1F, 0xA6, 0x8B, 0xAF, 0x11, 0xA6, 0xAC, + 0x9B, 0x2D, 0x12, 0xD0, 0x0D, 0xBC, 0x20, 0x26, 0x01, 0x76, + 0x39, 0xBF, 0xAB, 0x52, 0xD6, 0x93, 0x76, 0x29, 0x4C, 0xD8, + 0x8F, 0xBA, 0xA9, 0xA1, 0xCE, 0x09, 0xCC, 0xB7, 0xC5, 0x34, + 0xCC, 0xC1, 0x27, 0xE9, 0x47, 0xFB, 0x02, 0xC0, 0x0B, 0xB7, + 0xE2, 0x1B, 0x9A, 0x05, 0x32, 0x99, 0x7E, 0xEF, 0xDA, 0xC3, + 0xE2, 0xB3, 0x0D, 0x2F, 0xD7, 0x32, 0x14, 0xAA, 0x90, 0x00, + 0x5B, 0x8F, 0xA9, 0x6D, 0xBD, 0x54, 0xAF, 0xE2, 0x47, 0x8C, + 0x20, 0xD4, 0x14, 0x34, 0x13, 0x08, 0x17, 0x1B, 0xA2, 0x3B, + 0xDC, 0x6D, 0x5A, 0x08, 0x04, 0x58, 0x38, 0xAC, 0x84, 0xBB, + 0x22, 0x64, 0x6B, 0xE6, 0xB9, 0x46, 0x4A, 0xB6, 0x39, 0xD2, + 0xF4, 0x6D, 0x13, 0x63, 0x7F, 0x79, 0x6F, 0x54, 0x38, 0x72, + 0x1A, 0x3D, 0x45, 0x28, 0x14, 0x8F, 0x9D, 0xFE, 0x51, 0x02, + 0x3F, 0x0C, 0xFD, 0xE4, 0x7A, 0xC4, 0xFC, 0x00, 0xF0, 0xF6, + 0x7E, 0x98, 0x36, 0x40, 0x35, 0x92, 0x3A, 0x42, 0xBE, 0xA6, + 0xAE, 0x3C, 0x55, 0xF5, 0x4E, 0x41, 0xF4, 0x22, 0x63, 0x80, + 0x2E, 0xC9, 0x65, 0x73, 0x43, 0x2F, 0xE7, 0xF9, 0xC5, 0x5C, + 0x21, 0xDD, 0xF5, 0x15, 0x38, 0xE4, 0xDD, 0x92, 0x08, 0x9D, + 0x75, 0x65, 0x13, 0x28, 0xA5, 0xF9, 0x45, 0x13, 0xE0, 0x8F, + 0xB2, 0x36, 0x91, 0xAA, 0xBE, 0x87, 0xD2, 0x78, 0x3F, 0xB0, + 0xEE, 0x0C, 0x4C, 0x8C, 0xD7, 0x56, 0x85, 0x21, 0x43, 0x40, + 0xC4, 0x26, 0x90, 0xC2, 0x63, 0xF0, 0xC3, 0x49, 0xA0, 0xF0, + 0x2A, 0xCA, 0xA3, 0x9A, 0x39, 0xA6, 0xAA, 0x98, 0x2B, 0x8E, + 0xBA, 0x0B, 0xD9, 0x21, 0xC4, 0xC9, 0x97, 0x1B, 0x57, 0x80, + 0x49, 0x3E, 0xC4, 0x4B, 0xD8, 0x5E, 0xE2, 0x43, 0xC2, 0x13, + 0x8D, 0x51, 0x28, 0x8B, 0x30, 0x7C, 0x17, 0xB0, 0x58, 0x15, + 0x5F, 0x56, 0xF8, 0xF3, 0x8F, 0x9A, 0xC0, 0x9B, 0x27, 0x90, + 0x23, 0x48, 0xED, 0x5E, 0x50, 0xE2, 0x02, 0x26, 0xC0, 0x00, + 0x02, 0x02, 0xCA, 0xFA, 0x0D, 0x3A, 0xC4, 0xBF, 0x20, 0x04, + 0xD3, 0xA1, 0x4F, 0xED, 0x80, 0x40, 0x81, 0x15, 0x67, 0x0D, + 0x65, 0x3E, 0x52, 0x9C, 0x30, 0x20, 0xD2, 0xD8, 0xAC, 0xAF, + 0x06, 0xE5, 0xDA, 0x8B, 0x8B, 0x3A, 0xCA, 0xDC, 0x59, 0x8F, + 0x2F, 0x93, 0x53, 0x5B, 0xAC, 0xCE, 0x1F, 0xDF, 0x3B, 0x50, + 0xBF, 0x70, 0xF1, 0x7A, 0xCC, 0xA2, 0x25, 0x88, 0xD1, 0xA0, + 0xBC, 0x9D, 0x41, 0x1F, 0x9C, 0x0A, 0x95, 0x27, 0xE2, 0x71, + 0x4C, 0x1C, 0x14, 0xDB, 0xEA, 0xCD, 0xA3, 0x7C, 0x39, 0x57, + 0x69, 0x95, 0xA5, 0x04, 0x4F, 0xA3, 0xC0, 0x24, 0xC1, 0x5B, + 0x39, 0xF4, 0x9F, 0xBC, 0xB2, 0xC3, 0x37, 0x28, 0x1F, 0xFA, + 0xB9, 0xD7, 0xFF, 0x7A, 0xA2, 0x52, 0x03, 0x1F, 0x96, 0x5B, + 0xC4, 0x83, 0x08, 0x75, 0xFF, 0xF6, 0xF2, 0xF9, 0x7C, 0x1D, + 0x09, 0xB4, 0x3B, 0xAE, 0xCD, 0x2E, 0xF2, 0x6F, 0x2F, 0x4F, + 0xBC, 0x2B, 0x5B, 0x6D, 0x9A, 0x1B, 0xE6, 0x5E, 0xF5, 0xA8, + 0x93, 0x98, 0xDC, 0x6E, 0x5E, 0x17, 0xCA, 0xAB, 0x90, 0xFD, + 0xFB, 0x68, 0x1F, 0x67, 0x88, 0x58, 0x85, 0x4F, 0xAA, 0xC3, + 0x68, 0x40, 0xD5, 0xBC, 0xAC, 0x1D, 0x94, 0xC5, 0xB6, 0x01, + 0x2A, 0xE8, 0xBD, 0xDD, 0xBB, 0x17, 0x9D, 0x0E, 0x55, 0x62, + 0x45, 0xFB, 0x21, 0xB6, 0x71, 0x76, 0xEB, 0xCC, 0xF5, 0xE7, + 0xF1, 0xCD, 0xB7, 0xCA, 0xBD, 0xFF, 0xA9, 0xF7, 0x1A, 0xAA, + 0x1A, 0xCD, 0xC1, 0x71, 0x62, 0xE3, 0xDD, 0x09, 0x06, 0xAA, + 0x23, 0xFD, 0xEF, 0x6B, 0xA7, 0x83, 0xD8, 0xE6, 0x70, 0x01, + 0xF0, 0xEF, 0x69, 0xA9, 0x4B, 0x9B, 0x83, 0x57, 0x77, 0xAD, + 0x51, 0xBE, 0xBA, 0xF6, 0x2A, 0x6F, 0x1F, 0x97, 0x9A, 0xFE, + 0xA8, 0xE2, 0xAA, 0x20, 0x51, 0x95, 0xBA, 0xAE, 0x66, 0xB3, + 0xA8, 0x61, 0xCF, 0x8B, 0xFA, 0x6B, 0x48, 0xC4, 0x22, 0xE1, + 0xDF, 0x3A, 0x0D, 0xA9, 0x09, 0xF1, 0x66, 0x0C, 0x46, 0x74, + 0x90, 0x8B, 0x68, 0x00, 0x4F, 0x4F, 0x3A, 0x90, 0x58, 0x56, + 0xF4, 0x8A, 0x71, 0xB6, 0x1A, 0x91, 0x59, 0x17, 0x5C, 0xC0, + 0x87, 0x82, 0x7E, 0xB0, 0x81, 0x90, 0xF1, 0x6A, 0x03, 0xB8, + 0xA0, 0xAB, 0xDD, 0x63, 0x96, 0x48, 0x19, 0x37, 0x1F, 0x0F, + 0x24, 0x7A, 0x70, 0x48, 0x7D, 0x28, 0x1F, 0xB3, 0xCD, 0x76, + 0x53, 0x83, 0xC5, 0x53, 0xE8, 0xAB, 0x3A, 0xFC, 0x5B, 0x8A, + 0xF0, 0x5A, 0x0F, 0xEF, 0xFF, 0xB0, 0xC1, 0x61, 0x61, 0x58, + 0x4A, 0x8C, 0x1C, 0x20, 0xFC, 0x46, 0x07, 0x58, 0xF0, 0x20, + 0x81, 0x66, 0x28, 0x0E, 0xC2, 0x16, 0xAB, 0x98, 0xFF, 0x6E, + 0x24, 0xAC, 0x78, 0x42, 0xAE, 0x7D, 0xAB, 0x6F, 0xB4, 0x11, + 0x1C, 0x0C, 0x40, 0xF8, 0xF4, 0x93, 0x63, 0xE4, 0x6A, 0xEB, + 0xC5, 0xD9, 0x6E, 0x35, 0xC9, 0xA7, 0x2D, 0x49, 0xEA, 0x5D, + 0x69, 0x73, 0x06, 0x1C, 0xC4, 0x7E, 0x46, 0xFD, 0x09, 0x88, + 0x77, 0x77, 0xE7, 0xEB, 0x31, 0x34, 0x16, 0x72, 0x76, 0x1B, + 0x4E, 0xF8, 0x67, 0x9F, 0xCA, 0x1C, 0x67, 0x4E, 0xD8, 0x88, + 0xAA, 0x01, 0x27, 0x8E, 0x08, 0x70, 0xF8, 0x0B, 0x23, 0xF6, + 0x84, 0x47, 0x2F, 0x4E, 0x5F, 0xB7, 0x2C, 0x39, 0xBF, 0x61, + 0xEC, 0x6D, 0x24, 0x5C, 0x57, 0xBE, 0xAE, 0x19, 0x20, 0xBE, + 0x55, 0x40, 0x1D, 0xB7, 0x5F, 0xC3, 0xF6, 0x5B, 0x19, 0xC6, + 0x8A, 0x94, 0x23, 0x5C, 0x95, 0x33, 0x4C, 0x90, 0xE0, 0x46, + 0xAC, 0x0A, 0x1D, 0x50, 0xFB, 0x0A, 0xAB, 0xA2, 0xEB, 0x2A, + 0x21, 0xBF, 0x15, 0xD5, 0x9E, 0x80, 0x3B, 0x16, 0xB0, 0x3F, + 0x6F, 0x6F, 0xF6, 0xBE, 0x92, 0xA1, 0x2F, 0x83, 0xA8, 0x3C, + 0xA6, 0xE7, 0x86, 0xCD, 0x3B, 0x96, 0xCE, 0xF1, 0x36, 0x7C, + 0x69, 0xD9, 0xD5, 0x0F, 0xC1, 0x4C, 0x7A, 0xE5, 0xAF, 0xAC, + 0x86, 0x90, 0x98, 0x3D, 0x2B, 0x94, 0xA2, 0x7C, 0x5B, 0xF7, + 0x27, 0xCD, 0xE5, 0x93, 0x60, 0x2C, 0x47, 0xE3, 0xFA, 0x18, + 0xCB, 0xEB, 0xDB, 0x0A, 0x49, 0x99, 0x0F, 0xAE, 0x02, 0x26, + 0xC0, 0x00, 0x02, 0xF3, 0x51, 0xA3, 0x96, 0x54, 0xDA, 0xE9, + 0x09, 0x47, 0x82, 0x50, 0xB8, 0x82, 0x39, 0x98, 0x54, 0x81, + 0x54, 0x91, 0x2C, 0xE6, 0xBC, 0x84, 0x03, 0x07, 0x26, 0x6D, + 0x0F, 0x5E, 0x2B, 0x45, 0xF5, 0x1D, 0x7B, 0xD6, 0x14, 0x03, + 0xD7, 0x09, 0x64, 0xEE, 0x05, 0xBA, 0xE0, 0x74, 0x67, 0x02, + 0xF5, 0x7E, 0x42, 0x42, 0xEC, 0x56, 0xE0, 0x9E, 0x82, 0x88, + 0x58, 0x3C, 0x96, 0xAF, 0x37, 0x95, 0x49, 0xC8, 0x87, 0xBD, + 0xFE, 0x7A, 0x6B, 0x4D, 0x37, 0xEE, 0x7C, 0xAA, 0x18, 0x5F, + 0x7E, 0x0B, 0x28, 0xA3, 0x95, 0x23, 0x42, 0xAB, 0xC1, 0xFA, + 0x41, 0xAE, 0xD5, 0xBD, 0x67, 0xA6, 0xC4, 0x7C, 0xAC, 0x2D, + 0xEF, 0x64, 0xC2, 0x5D, 0x30, 0x94, 0xF3, 0x97, 0x49, 0x00, + 0x39, 0x28, 0x57, 0x5D, 0x31, 0xBB, 0x1D, 0x10, 0x17, 0xE7, + 0x56, 0x55, 0xDF, 0x4C, 0xDD, 0xA6, 0x64, 0x02, 0xBC, 0x1C, + 0x2B, 0x4C, 0x30, 0xBF, 0x89, 0x7C, 0xFC, 0x7E, 0x84, 0xBC, + 0x51, 0x97, 0x6C, 0x74, 0x5B, 0x08, 0xE0, 0x96, 0x84, 0x81, + 0xF3, 0x17, 0x6B, 0xD6, 0xF6, 0x66, 0x06, 0x1B, 0x33, 0x4D, + 0xF8, 0xED, 0xC0, 0x53, 0x8B, 0x35, 0x6B, 0x85, 0x4F, 0x37, + 0x0F, 0x87, 0xF3, 0x85, 0x72, 0xAE, 0xCB, 0x3A, 0x23, 0x97, + 0xC0, 0xF6, 0xE7, 0x53, 0xDF, 0x57, 0x0E, 0x8E, 0x0B, 0x66, + 0x2A, 0xA2, 0x9D, 0xA8, 0xE2, 0x60, 0x57, 0xCA, 0x27, 0x7E, + 0xB1, 0xDB, 0x7B, 0x6A, 0xB0, 0xBE, 0xB5, 0x47, 0xEE, 0xE6, + 0xBA, 0x0E, 0xB2, 0x71, 0x0B, 0xF7, 0xE4, 0x27, 0x9D, 0x25, + 0xAA, 0x3F, 0xA9, 0x1A, 0x5B, 0xD9, 0x9D, 0xBB, 0x20, 0x32, + 0x37, 0xE1, 0xD3, 0x4A, 0xBA, 0x45, 0x9E, 0x00, 0x10, 0x3F, + 0x21, 0x6E, 0xA2, 0xCE, 0x30, 0xA0, 0x2A, 0x5B, 0x31, 0x51, + 0x05, 0xA1, 0x51, 0xED, 0x89, 0x5E, 0xB1, 0x09, 0xE6, 0x71, + 0xD8, 0x42, 0xFD, 0xA1, 0x04, 0x83, 0xFB, 0x09, 0x4F, 0x90, + 0x4D, 0x7E, 0x5A, 0xB9, 0x4C, 0xC9, 0xA3, 0x57, 0x4E, 0x54, + 0x98, 0xB3, 0xBD, 0xB7, 0x52, 0x9D, 0xDE, 0xF3, 0xD3, 0xE0, + 0x9C, 0x2D, 0x97, 0x46, 0x1B, 0xF9, 0x75, 0x37, 0x5D, 0x0A, + 0xA3, 0x4A, 0x18, 0x35, 0x80, 0xC5, 0x08, 0xA8, 0x37, 0x09, + 0x44, 0x92, 0xB1, 0x74, 0xFE, 0x28, 0x95, 0x55, 0xB6, 0x08, + 0xA0, 0x75, 0xE9, 0xA0, 0x4B, 0x8E, 0xE6, 0x61, 0x17, 0x2A, + 0xED, 0x15, 0x0B, 0x6C, 0x7C, 0xC3, 0x82, 0x57, 0x90, 0xC5, + 0xFF, 0xD8, 0xA5, 0xBF, 0xAA, 0xBE, 0xCF, 0x8E, 0x06, 0xFF, + 0x27, 0xDA, 0x40, 0x24, 0xDD, 0xC0, 0xBE, 0x4E, 0x19, 0x9D, + 0x23, 0xA2, 0x3A, 0x70, 0x64, 0xEB, 0xF6, 0xA7, 0xE9, 0x71, + 0x57, 0xE9, 0x63, 0x03, 0xAE, 0xEC, 0x73, 0x21, 0x23, 0x8D, + 0x61, 0x5A, 0x10, 0x54, 0xF9, 0x80, 0xE7, 0x47, 0x45, 0xD4, + 0x8B, 0x16, 0xEE, 0x2B, 0xD8, 0xC1, 0xEE, 0x0F, 0x4F, 0x78, + 0x40, 0x00, 0xB6, 0x25, 0x81, 0x2A, 0x37, 0x4D, 0x71, 0x92, + 0xFA, 0x56, 0x3F, 0xDC, 0x91, 0x01, 0x27, 0xC7, 0x17, 0xFD, + 0x27, 0x55, 0x6F, 0x32, 0x14, 0xE7, 0xEA, 0x18, 0xDA, 0x4A, + 0x70, 0x10, 0xF8, 0x72, 0x78, 0xA1, 0xC1, 0x13, 0x5F, 0x1B, + 0x98, 0x93, 0xC2, 0xBF, 0x29, 0xA3, 0x59, 0x79, 0x15, 0x57, + 0x17, 0xE5, 0x66, 0x7A, 0x3B, 0x8E, 0xB3, 0x3B, 0x9E, 0xC0, + 0x77, 0xBD, 0x2D, 0x95, 0x26, 0xF0, 0xD5, 0xB4, 0x30, 0xC8, + 0x0D, 0xCA, 0xB5, 0xDE, 0xB2, 0x21, 0x27, 0x9A, 0x27, 0xAF, + 0x89, 0xB4, 0x0D, 0x1B, 0x5A, 0x43, 0x3E, 0x69, 0x76, 0x25, + 0xAC, 0x42, 0x23, 0x5F, 0x5A, 0xA6, 0xDB, 0xE6, 0x77, 0x9D, + 0x2A, 0x99, 0x1B, 0xE7, 0x47, 0x05, 0x06, 0x47, 0x01, 0x14, + 0x0D, 0xEA, 0xF5, 0x28, 0x3A, 0x5B, 0x87, 0xF0, 0xFB, 0x7C, + 0x96, 0x39, 0x6C, 0x4A, 0x48, 0xA5, 0x7A, 0x94, 0xB0, 0xB8, + 0xBB, 0x03, 0xDA, 0xEA, 0x4F, 0xD1, 0x5B, 0x8B, 0x9D, 0x0A, + 0x5E, 0xAB, 0xD8, 0x89, 0x5C, 0x4D, 0xD1, 0xA7, 0xC4, 0x8A, + 0x02, 0x26, 0xC0, 0x00, 0x02, 0x14, 0x04, 0xA6, 0x12, 0xC1, + 0x4E, 0x67, 0x67, 0x6C, 0xEE, 0x9E, 0x7A, 0x55, 0x00, 0xCB, + 0x12, 0x7B, 0x69, 0x4C, 0x94, 0x57, 0x73, 0x71, 0xC8, 0x45, + 0xAA, 0x04, 0x75, 0x85, 0xED, 0x68, 0x7D, 0x09, 0xD5, 0x4A, + 0xD0, 0x86, 0xDB, 0x0B, 0xC3, 0x80, 0xD1, 0x11, 0xB3, 0x59, + 0xCF, 0xBD, 0x13, 0x7B, 0xD2, 0x30, 0xDF, 0xDD, 0x41, 0xD7, + 0xBC, 0x34, 0x11, 0x85, 0x58, 0x2A, 0x8E, 0x2B, 0xDC, 0x00, + 0x78, 0x94, 0x28, 0x52, 0xD9, 0x0C, 0x70, 0xCB, 0x7D, 0xE9, + 0xCF, 0x7C, 0x11, 0x91, 0x09, 0xA8, 0xD7, 0xBC, 0xC8, 0xA1, + 0xDF, 0xF3, 0xB4, 0x25, 0x3A, 0x88, 0x02, 0xF0, 0xBE, 0x8E, + 0x89, 0x2B, 0xA6, 0x43, 0x88, 0xD0, 0xCC, 0x27, 0x91, 0x77, + 0x8D, 0x01, 0x32, 0xA6, 0x0C, 0x3D, 0x86, 0x76, 0x03, 0x4F, + 0x01, 0xF6, 0x02, 0xB3, 0xD0, 0x7A, 0x39, 0x78, 0x1E, 0xF2, + 0x35, 0xB6, 0xB3, 0xC9, 0x50, 0xE7, 0x0A, 0xD1, 0x3C, 0xB4, + 0xE2, 0x02, 0x60, 0x13, 0x8A, 0x0F, 0x15, 0xEE, 0x48, 0x67, + 0x84, 0x9E, 0x8E, 0x56, 0x22, 0xB9, 0x0A, 0xE2, 0x36, 0xDE, + 0x97, 0x4E, 0x9F, 0x0A, 0xE0, 0xB8, 0x2A, 0x0D, 0x07, 0x7F, + 0xCE, 0x76, 0xEA, 0x58, 0x54, 0xBC, 0x7A, 0xA1, 0x87, 0x15, + 0x3F, 0xCE, 0xDA, 0x58, 0xA4, 0x51, 0x36, 0x49, 0xB0, 0x23, + 0xC0, 0x55, 0xFB, 0xE2, 0x47, 0xB6, 0xD6, 0x31, 0x90, 0xA9, + 0x99, 0x2A, 0xC3, 0x59, 0xAE, 0x60, 0x03, 0xC2, 0x56, 0xC0, + 0x03, 0xA9, 0x3E, 0xF0, 0xE9, 0x59, 0x30, 0xD5, 0x90, 0x15, + 0x72, 0x32, 0x30, 0xD1, 0xFA, 0xDD, 0xB3, 0x09, 0xF0, 0x1A, + 0xA3, 0x6E, 0x5E, 0xBB, 0xDE, 0x58, 0x08, 0x91, 0xFB, 0xA0, + 0xEC, 0x45, 0x7F, 0x05, 0x90, 0x78, 0xCF, 0x6F, 0x66, 0xF4, + 0x2A, 0x79, 0x93, 0x33, 0x17, 0x07, 0x86, 0xE6, 0xEC, 0xFD, + 0xEC, 0x07, 0x74, 0x17, 0x26, 0x84, 0x52, 0x54, 0x9A, 0xBC, + 0xDE, 0xEE, 0xD7, 0xE9, 0x2F, 0xA7, 0x60, 0xA6, 0x54, 0x74, + 0xB9, 0x83, 0x6D, 0xE6, 0xB7, 0x46, 0xDC, 0xC8, 0x09, 0x36, + 0x25, 0x1E, 0x8B, 0x61, 0x94, 0x2D, 0xBA, 0xE2, 0xAB, 0xE6, + 0xD0, 0xDB, 0x9C, 0x13, 0x74, 0x25, 0xDA, 0xD7, 0x69, 0x69, + 0xFA, 0x52, 0x3C, 0x15, 0xBB, 0xFC, 0x5D, 0xE2, 0x6E, 0xA5, + 0x26, 0x35, 0x65, 0xF4, 0xE0, 0x29, 0x77, 0xCF, 0xAB, 0xF9, + 0xAB, 0x8C, 0x9D, 0x7D, 0x60, 0xA4, 0xA0, 0x3D, 0x43, 0xB6, + 0x9D, 0xAE, 0x04, 0x46, 0x8A, 0xE2, 0x3B, 0x4C, 0xC8, 0xA4, + 0x5D, 0x61, 0xB4, 0x5B, 0x8B, 0x2E, 0xD5, 0x2A, 0x07, 0x27, + 0x6A, 0x34, 0x04, 0xBA, 0xF8, 0x72, 0x32, 0x99, 0x97, 0x8C, + 0xC0, 0xCD, 0x35, 0x68, 0xB8, 0xA9, 0x45, 0x76, 0xA5, 0xD4, + 0x17, 0x36, 0xB1, 0x25, 0xF7, 0x47, 0x69, 0x30, 0x68, 0x14, + 0x0B, 0x9C, 0x38, 0xB1, 0x29, 0x16, 0x88, 0xAD, 0xDF, 0x31, + 0x7E, 0x3C, 0x5F, 0xA4, 0x0D, 0x86, 0x5A, 0x29, 0x37, 0xA9, + 0x1B, 0xD5, 0x68, 0x9F, 0xE0, 0xE6, 0x32, 0x42, 0x12, 0x37, + 0x99, 0xC5, 0xB8, 0xA1, 0xD3, 0x5B, 0x90, 0x9C, 0xAD, 0x86, + 0x6B, 0x03, 0x82, 0x91, 0xD9, 0xF9, 0xDC, 0xD5, 0x41, 0x7B, + 0xF3, 0xE4, 0x08, 0x92, 0xCA, 0x6A, 0xAA, 0xBD, 0xFE, 0x25, + 0x50, 0xDC, 0x2C, 0x00, 0x65, 0x59, 0x9B, 0xD3, 0x30, 0xD2, + 0x39, 0xC0, 0x4D, 0xFD, 0x8C, 0x9D, 0x88, 0xD3, 0x52, 0xD6, + 0xC0, 0xA0, 0x1C, 0x08, 0x0B, 0x1F, 0x91, 0xAF, 0x60, 0x56, + 0xED, 0x8B, 0x37, 0xD3, 0x15, 0x08, 0x5C, 0xEA, 0xFA, 0x03, + 0x0A, 0x54, 0x92, 0x96, 0x34, 0x4F, 0x14, 0x0E, 0xD5, 0xB4, + 0xA0, 0x2E, 0xC0, 0xEC, 0x93, 0x8F, 0xA5, 0xF3, 0x82, 0x5F, + 0x0D, 0xA8, 0xBD, 0x28, 0x4B, 0x1C, 0x65, 0xC1, 0x97, 0x5B, + 0xEE, 0x99, 0xC5, 0xC2, 0xB9, 0x34, 0x4D, 0xDB, 0x6E, 0x42, + 0x26, 0x93, 0x49, 0x83, 0x36, 0x25, 0x72, 0x03, 0x08, 0xE2, + 0xE6, 0x67, 0x01, 0xBC, 0x7A, 0x10, 0x7A, 0xBC, 0x6B, 0x0B, + 0x4A, 0x94, 0x02, 0x26, 0xC0, 0x00, 0x02, 0x72, 0x08, 0xBA, + 0x77, 0xE2, 0x2F, 0x48, 0xD4, 0x9E, 0xC4, 0x23, 0x45, 0x55, + 0xF1, 0x0B, 0x1B, 0x50, 0x47, 0x8C, 0x40, 0x7C, 0xF7, 0xE4, + 0xCF, 0xD6, 0x0A, 0x0A, 0xD3, 0x7F, 0x35, 0x85, 0x84, 0x78, + 0xF2, 0x28, 0xEA, 0x14, 0x06, 0x2A, 0xC0, 0x53, 0x22, 0x45, + 0x9B, 0xDA, 0xE9, 0xEE, 0x98, 0x5D, 0xEC, 0xEC, 0xEF, 0xA1, + 0x1A, 0xDD, 0x7C, 0xE2, 0x29, 0xFF, 0x2B, 0x8A, 0x00, 0x49, + 0xD6, 0x2E, 0x7C, 0x50, 0x26, 0x29, 0x65, 0x97, 0xB4, 0xA8, + 0x6B, 0x38, 0x95, 0xC0, 0x7F, 0x41, 0xAD, 0xD5, 0xA2, 0x54, + 0x37, 0xE1, 0x72, 0x3A, 0x28, 0x58, 0xCA, 0x88, 0xB4, 0xF8, + 0x34, 0x43, 0xFF, 0xE4, 0xE4, 0xC2, 0x37, 0x0B, 0x72, 0x77, + 0x2B, 0x94, 0x8C, 0xB8, 0xCD, 0x72, 0x1E, 0xDB, 0x0A, 0xFA, + 0x86, 0x0E, 0xBC, 0x76, 0xDE, 0x89, 0x20, 0xBB, 0x1A, 0xC3, + 0x07, 0xAA, 0x0F, 0x0D, 0xED, 0x58, 0x42, 0x74, 0x44, 0xD0, + 0x89, 0x61, 0x25, 0xAF, 0xC0, 0x18, 0xFE, 0x16, 0xC9, 0x37, + 0x03, 0x11, 0x11, 0xEC, 0x9F, 0xFF, 0x2B, 0x00, 0x4F, 0x37, + 0xB6, 0xEC, 0x54, 0x0A, 0xA1, 0x68, 0xE5, 0x69, 0x38, 0xD5, + 0x55, 0x9E, 0x94, 0xAF, 0x3D, 0x67, 0xFF, 0x4D, 0x5D, 0x66, + 0x1D, 0xD0, 0x45, 0x1B, 0xF9, 0x23, 0x5F, 0xCF, 0x18, 0xFB, + 0x3F, 0x13, 0x0A, 0x2E, 0x86, 0xC4, 0x44, 0x28, 0xAB, 0x72, + 0x78, 0x77, 0x14, 0xCA, 0x70, 0xBF, 0x3E, 0x79, 0x47, 0xAB, + 0x3D, 0x22, 0xB9, 0x57, 0xB8, 0x04, 0x4B, 0x62, 0x2A, 0x26, + 0x4C, 0xEE, 0x80, 0xF4, 0x1C, 0x5C, 0xE3, 0xFF, 0x23, 0xC8, + 0x7C, 0x27, 0x90, 0xC8, 0x61, 0xC3, 0x7C, 0xC8, 0x5B, 0x46, + 0xB8, 0xCC, 0x8A, 0x67, 0xFC, 0xB9, 0xF1, 0xE7, 0x21, 0x68, + 0x47, 0x37, 0x9D, 0xEB, 0x14, 0xC4, 0x55, 0x02, 0x43, 0xA6, + 0xAA, 0x50, 0xE2, 0x78, 0x66, 0xE9, 0x55, 0x2D, 0x4C, 0x84, + 0xDF, 0x81, 0xCF, 0x0C, 0xD4, 0x36, 0xCA, 0x3D, 0xF7, 0xEE, + 0x2A, 0x5D, 0x10, 0xC9, 0xEA, 0x19, 0xF2, 0xF3, 0xBD, 0x42, + 0xA9, 0xE1, 0xA6, 0xD1, 0x84, 0xE9, 0x1A, 0x26, 0xDC, 0xBE, + 0x72, 0x43, 0xC7, 0x79, 0x92, 0xD9, 0x5F, 0x7C, 0x42, 0xCD, + 0xFF, 0x76, 0xBE, 0xB9, 0x99, 0x60, 0x6B, 0x5E, 0xAD, 0xAC, + 0x62, 0xAD, 0xFD, 0x58, 0x1C, 0x4E, 0xC6, 0x6D, 0xE7, 0xF9, + 0x2E, 0xD1, 0xEC, 0x9F, 0x98, 0xAE, 0x4F, 0xB6, 0xE1, 0xB3, + 0x77, 0xDD, 0xA4, 0x5D, 0x24, 0x76, 0xF0, 0xED, 0xBE, 0x19, + 0xB1, 0x98, 0x85, 0x08, 0xAB, 0xF0, 0x39, 0x94, 0x1D, 0x12, + 0xCA, 0x8B, 0xD2, 0xCC, 0x97, 0x44, 0xCB, 0x89, 0x9B, 0x66, + 0x50, 0x51, 0x64, 0x9E, 0xB1, 0x9E, 0x2C, 0xAA, 0xE5, 0x91, + 0x59, 0x53, 0xB1, 0x5E, 0xB3, 0xBB, 0x99, 0x00, 0x53, 0xA9, + 0xC1, 0x6C, 0x59, 0x46, 0xFD, 0xCB, 0x53, 0x83, 0xDD, 0x37, + 0xA1, 0xA3, 0x65, 0x26, 0xC6, 0x48, 0x6D, 0x15, 0xE8, 0xC1, + 0xE3, 0x45, 0x10, 0x6E, 0x8A, 0xE1, 0xDB, 0x10, 0xDB, 0x58, + 0x16, 0x5C, 0x31, 0x2E, 0x06, 0xA8, 0xAE, 0xD7, 0xB2, 0x11, + 0x55, 0x07, 0x2E, 0x57, 0x57, 0x02, 0x48, 0xC0, 0x8B, 0x59, + 0x8C, 0xBE, 0x1A, 0x47, 0x52, 0x3E, 0xD0, 0x05, 0xEA, 0xBC, + 0x86, 0xEA, 0x31, 0xD9, 0x58, 0x52, 0xCF, 0x7D, 0xD2, 0x30, + 0x4E, 0x0D, 0x24, 0x6F, 0x39, 0xF6, 0xEB, 0xC8, 0x5D, 0xE4, + 0xFF, 0x8D, 0xB9, 0x71, 0x02, 0x71, 0x67, 0x89, 0xFA, 0xB8, + 0x4F, 0xC9, 0x77, 0x78, 0x60, 0x36, 0xDF, 0x0E, 0x96, 0xAC, + 0x4F, 0x44, 0x8A, 0x2C, 0xE5, 0x8C, 0xC3, 0x96, 0x79, 0xE2, + 0x43, 0x75, 0xF5, 0x59, 0xC0, 0x81, 0x0A, 0x54, 0xC2, 0xA3, + 0x28, 0x8A, 0x32, 0xCE, 0x91, 0x5A, 0x91, 0x06, 0x45, 0xEB, + 0x34, 0x1A, 0x87, 0xF9, 0x57, 0x91, 0x0D, 0x34, 0x7F, 0x82, + 0x91, 0x82, 0xC5, 0x26, 0xCB, 0xB3, 0x4F, 0xF2, 0x20, 0x6A, + 0x02, 0x95, 0x73, 0x7B, 0x4F, 0x4E, 0xAF, 0x8A, 0x40, 0x83, + 0xFE, 0xF9, 0xD2, 0xFD, 0x02, 0x26, 0xC0, 0x00, 0x02, 0x0E, + 0xA2, 0xA8, 0x59, 0xF0, 0x82, 0x17, 0x4C, 0x0E, 0xF3, 0x83, + 0xA1, 0xB6, 0x36, 0x83, 0xCB, 0xFB, 0xA2, 0x66, 0x53, 0x65, + 0x40, 0x40, 0xAA, 0x3D, 0x82, 0x62, 0x54, 0x0A, 0x27, 0x6A, + 0xD6, 0x07, 0xAF, 0x91, 0xCF, 0x9B, 0x89, 0x58, 0xBC, 0xCE, + 0x74, 0x87, 0x9C, 0x7E, 0x30, 0xF3, 0x06, 0x2B, 0xF1, 0xB4, + 0x70, 0x9D, 0xAA, 0x41, 0xD7, 0x25, 0x75, 0xE5, 0x72, 0x25, + 0x00, 0xC7, 0x21, 0x9D, 0xEE, 0xD1, 0x24, 0x16, 0x0B, 0x12, + 0x22, 0x88, 0xB8, 0x9D, 0xA5, 0x38, 0xA0, 0x50, 0x39, 0x27, + 0xAE, 0x10, 0xF6, 0x9A, 0x0B, 0x77, 0x56, 0x3F, 0x96, 0xDF, + 0x22, 0xE5, 0xEA, 0xAC, 0x8F, 0xDF, 0xDD, 0x78, 0xEF, 0x5A, + 0x7B, 0x50, 0x72, 0x98, 0x92, 0xDD, 0x94, 0xB5, 0xDB, 0x99, + 0xCE, 0xF2, 0x7A, 0xF3, 0xFA, 0x80, 0xD3, 0xCD, 0x1F, 0xCA, + 0xA2, 0x2E, 0x21, 0x94, 0xF2, 0xB3, 0x71, 0x7C, 0x07, 0xE6, + 0xB9, 0x68, 0x1E, 0x13, 0x02, 0x15, 0xC8, 0x34, 0xA0, 0xB5, + 0xD9, 0x95, 0x1A, 0x9B, 0x57, 0xE7, 0xAF, 0xB0, 0xE1, 0xA7, + 0x95, 0xF2, 0x36, 0xA3, 0xF9, 0xC5, 0x2C, 0xF0, 0xC3, 0xE1, + 0xD1, 0x26, 0xA1, 0x8F, 0xE6, 0x07, 0xFB, 0x1F, 0x03, 0xFF, + 0xA5, 0x81, 0xF5, 0x5D, 0x21, 0x3B, 0x93, 0x66, 0x1A, 0x78, + 0x6F, 0xD4, 0x77, 0xB3, 0x91, 0x26, 0x6F, 0x1F, 0x5A, 0x8B, + 0x0F, 0x07, 0xEA, 0x24, 0x5F, 0x5E, 0x68, 0x9F, 0x89, 0x75, + 0x00, 0x23, 0x1C, 0x80, 0x55, 0x86, 0x93, 0x58, 0x94, 0x41, + 0x37, 0x0D, 0x23, 0xD3, 0x09, 0x72, 0x40, 0x79, 0xA2, 0x52, + 0x0D, 0xA3, 0x40, 0x3F, 0x54, 0x7E, 0x59, 0x55, 0x5B, 0x7D, + 0x0C, 0x52, 0xAA, 0x97, 0x0C, 0xC6, 0xCB, 0x0B, 0x6C, 0x83, + 0x45, 0x28, 0x62, 0x09, 0x0A, 0x74, 0x27, 0x1D, 0x4D, 0x02, + 0x07, 0xB3, 0xA5, 0x83, 0x9A, 0x43, 0xB3, 0x68, 0xF7, 0x97, + 0x3E, 0xD7, 0x34, 0x04, 0x83, 0xC7, 0x6F, 0x64, 0xB8, 0x0B, + 0xB9, 0xE9, 0x94, 0x46, 0xB9, 0x4D, 0xA7, 0xB4, 0x26, 0xF7, + 0x9C, 0x05, 0x94, 0x8B, 0xBE, 0x93, 0x03, 0xA7, 0x73, 0x65, + 0xAB, 0xC5, 0xE1, 0x2B, 0x3B, 0x4C, 0x56, 0x1F, 0x11, 0x7B, + 0xD3, 0xC0, 0x77, 0x48, 0x42, 0x27, 0x8D, 0x2A, 0xAA, 0x1E, + 0x18, 0x41, 0x76, 0xA1, 0x80, 0x99, 0x52, 0x50, 0x66, 0x7B, + 0x27, 0x4B, 0x49, 0xA1, 0xE8, 0xCA, 0xF1, 0x3E, 0x60, 0x2E, + 0x93, 0xEF, 0x4A, 0x20, 0x3C, 0xC4, 0xBB, 0x14, 0xD3, 0x8F, + 0x14, 0xAF, 0xC0, 0xF8, 0x4B, 0xC6, 0x49, 0x1A, 0x3B, 0x17, + 0x43, 0xC2, 0xAB, 0xF0, 0x01, 0xFC, 0x6E, 0x67, 0x86, 0xAC, + 0xE4, 0x6F, 0xF2, 0x26, 0xF2, 0xEC, 0x15, 0xA1, 0x96, 0x81, + 0xDD, 0xCF, 0xCA, 0xB4, 0xE1, 0x5A, 0xA0, 0xF5, 0xBB, 0x70, + 0xEB, 0xF9, 0xA6, 0x28, 0x66, 0xC3, 0xBE, 0xB9, 0xBD, 0xDD, + 0xEF, 0x8E, 0x9E, 0x3C, 0x91, 0x50, 0x38, 0x61, 0x40, 0x25, + 0x4D, 0xCD, 0xA0, 0xAF, 0xFE, 0x57, 0xB8, 0x96, 0xDD, 0x2B, + 0x52, 0x29, 0x91, 0x7A, 0x31, 0xE8, 0x9A, 0xBB, 0xC9, 0x70, + 0x2B, 0xE1, 0x40, 0x3A, 0x9F, 0xB7, 0x31, 0xBE, 0xE5, 0x46, + 0xDA, 0x8A, 0xDE, 0x13, 0x22, 0x87, 0x70, 0xFE, 0x96, 0x40, + 0x9F, 0xC1, 0x2B, 0xFE, 0x83, 0x31, 0xE9, 0xA5, 0x4C, 0x37, + 0xEB, 0xFD, 0xFB, 0x2C, 0xBD, 0x0C, 0x1F, 0xB2, 0x62, 0x32, + 0x0E, 0x76, 0x9A, 0x2C, 0xC2, 0x84, 0x34, 0xA0, 0x6D, 0x72, + 0x21, 0x47, 0xF9, 0x3E, 0xC6, 0x5C, 0xEE, 0xB6, 0xE1, 0xD9, + 0x2E, 0xDF, 0x50, 0xD6, 0x2C, 0x9B, 0x44, 0xBE, 0xF0, 0x01, + 0x8E, 0xB4, 0x34, 0x38, 0x9C, 0x6C, 0x3E, 0x26, 0xB2, 0x4C, + 0x8E, 0xF0, 0x41, 0x2F, 0x07, 0x0E, 0xE9, 0x69, 0x1C, 0x0A, + 0x00, 0x05, 0xB0, 0x09, 0xA7, 0x07, 0x18, 0x41, 0xD1, 0xF7, + 0x22, 0xE4, 0x85, 0x64, 0xB7, 0x83, 0xF7, 0xB1, 0xE2, 0xAC, + 0x2E, 0xD9, 0x83, 0x81, 0x18, 0xCB, 0xBD, 0xEE, 0xD9, 0x09, + 0x99, 0xBA, 0x95, 0x66, 0x1F, 0xBA, 0x02, 0x26, 0xC0, 0x00, + 0x02, 0x67, 0x72, 0x24, 0xB5, 0xFE, 0x8C, 0x50, 0x84, 0x47, + 0x41, 0x6A, 0x29, 0x1F, 0x17, 0xB2, 0x9E, 0x02, 0x67, 0x70, + 0x55, 0x03, 0x3F, 0x8F, 0x07, 0x20, 0x91, 0x6B, 0x22, 0x7F, + 0x06, 0xC3, 0xC9, 0x0A, 0xB5, 0x07, 0x0D, 0x91, 0x5F, 0x87, + 0x6D, 0xE2, 0xEE, 0x2B, 0x01, 0xE7, 0x45, 0x3F, 0x89, 0x1C, + 0x8B, 0x12, 0x33, 0x23, 0x61, 0x64, 0x3F, 0x4B, 0xF1, 0x27, + 0x0E, 0x8B, 0x7B, 0x7E, 0x64, 0x2E, 0xDE, 0xE0, 0x3F, 0x3E, + 0x5C, 0xAE, 0x8C, 0x73, 0xB2, 0x8B, 0x62, 0x0F, 0xF9, 0xEB, + 0xF0, 0x74, 0xBD, 0xB9, 0xCC, 0x43, 0x80, 0xC3, 0x05, 0x31, + 0x53, 0x6F, 0x72, 0x42, 0x8E, 0x14, 0xAD, 0xF0, 0x45, 0x36, + 0x88, 0xE4, 0xF9, 0x0F, 0xD9, 0xCA, 0x1E, 0x82, 0xC2, 0x89, + 0x44, 0xC3, 0x69, 0xC6, 0xDD, 0x27, 0xB5, 0xCA, 0xCF, 0x56, + 0x23, 0x5D, 0x50, 0xF9, 0x69, 0x87, 0xCF, 0x4D, 0x2F, 0xA8, + 0xEE, 0xD4, 0x55, 0x2A, 0xC7, 0x3A, 0x2D, 0x03, 0xCD, 0x38, + 0xDE, 0x4F, 0x29, 0x91, 0x14, 0xAD, 0x39, 0x05, 0x77, 0x5F, + 0x41, 0x48, 0x30, 0x63, 0xE6, 0x81, 0x40, 0x68, 0x75, 0x8D, + 0xC5, 0x70, 0x2D, 0xC9, 0xB9, 0x10, 0x75, 0xF3, 0x28, 0x91, + 0x99, 0x97, 0x16, 0x2E, 0x7C, 0xAD, 0x50, 0x80, 0x40, 0x00, + 0xEE, 0x8F, 0x59, 0xA1, 0x8F, 0x82, 0xDE, 0x1E, 0xDB, 0x56, + 0xA1, 0xD6, 0x2E, 0x06, 0x9D, 0x42, 0xCF, 0xA0, 0xB7, 0x1F, + 0x37, 0x22, 0x02, 0x2E, 0x33, 0xAE, 0xE4, 0x7B, 0x7C, 0xC6, + 0x11, 0x5E, 0xF6, 0xA5, 0x5B, 0x54, 0x8B, 0x28, 0x54, 0x01, + 0x4C, 0x41, 0x49, 0x8E, 0xAD, 0x22, 0xBB, 0x67, 0x7A, 0xF3, + 0xEC, 0xE3, 0x93, 0xE4, 0x3A, 0x71, 0xB2, 0x82, 0xC4, 0x8A, + 0x7B, 0x99, 0xA7, 0x6B, 0x69, 0x28, 0xE3, 0xC5, 0x89, 0xF7, + 0x3E, 0xF3, 0xC5, 0xB2, 0x40, 0x91, 0xF8, 0x6A, 0xE3, 0x63, + 0xB1, 0x5C, 0xE3, 0x79, 0xFF, 0x41, 0xE8, 0x2F, 0xCA, 0x11, + 0xA2, 0xED, 0x86, 0x59, 0x53, 0x9D, 0xF9, 0xE1, 0x15, 0x5D, + 0x38, 0x32, 0x69, 0x60, 0x45, 0x20, 0x74, 0xF5, 0xF6, 0x24, + 0xA2, 0x4E, 0xAE, 0xB5, 0x2F, 0xE2, 0xEE, 0xBD, 0xD3, 0x27, + 0xE1, 0x90, 0x89, 0x89, 0xA3, 0x7E, 0x2B, 0x73, 0x63, 0xB8, + 0x36, 0xFA, 0x17, 0x0C, 0x8F, 0x0F, 0xE8, 0xBE, 0xDC, 0x8D, + 0x52, 0x34, 0xB3, 0x52, 0x0E, 0xB1, 0x97, 0x8D, 0x69, 0xC8, + 0x27, 0x2E, 0xBC, 0xFC, 0xDB, 0x6C, 0x98, 0xE8, 0x50, 0xF1, + 0x7A, 0x14, 0xE7, 0xA4, 0x11, 0xD2, 0xDA, 0x71, 0xCB, 0x77, + 0x88, 0x7B, 0xEE, 0x24, 0x52, 0xF4, 0x83, 0x2F, 0x1B, 0x25, + 0x73, 0x7D, 0x57, 0xAE, 0x78, 0x42, 0xC7, 0x7F, 0x84, 0xDB, + 0x67, 0x7A, 0x81, 0xAB, 0xB5, 0xC4, 0x41, 0x38, 0xA0, 0x8D, + 0x8C, 0x50, 0x1F, 0x56, 0xF6, 0x9A, 0x2B, 0x57, 0x89, 0x20, + 0xA9, 0xE9, 0x1E, 0x55, 0x22, 0xD7, 0x88, 0xFA, 0x5D, 0x5F, + 0x51, 0x5C, 0x7C, 0x17, 0x6E, 0x1D, 0x1A, 0xE3, 0xE6, 0x24, + 0x67, 0xFE, 0xAA, 0xEF, 0x28, 0x25, 0xCA, 0xD7, 0x9B, 0x44, + 0x3A, 0x55, 0x6B, 0x2E, 0xDE, 0x7D, 0xD0, 0x1E, 0x16, 0xD6, + 0xA5, 0xA6, 0x97, 0xE0, 0x35, 0x90, 0x13, 0xD4, 0x16, 0x53, + 0xA2, 0x66, 0xCB, 0x12, 0xEE, 0xED, 0xA1, 0x42, 0x2C, 0x6A, + 0xF5, 0x13, 0x63, 0x5B, 0x2A, 0x0C, 0x8C, 0x3A, 0xC4, 0xAA, + 0x77, 0xFD, 0x03, 0x29, 0x47, 0x2E, 0x4E, 0xCE, 0x26, 0x2E, + 0xE2, 0x87, 0xD5, 0x49, 0xBE, 0xCE, 0x07, 0x71, 0x8F, 0xBB, + 0xDF, 0x5E, 0x73, 0x17, 0x28, 0x1B, 0x34, 0x55, 0xA2, 0xB1, + 0x99, 0x72, 0x90, 0xFD, 0xDF, 0xB3, 0xC7, 0x63, 0xFE, 0x1B, + 0x75, 0x6B, 0x6D, 0x73, 0xDE, 0x21, 0xD0, 0x18, 0xBE, 0x87, + 0xE8, 0xDA, 0x74, 0x1E, 0x78, 0x65, 0x6D, 0x30, 0x3B, 0xF1, + 0xEF, 0xE3, 0x32, 0xD6, 0xF8, 0x77, 0x31, 0xAB, 0x72, 0x72, + 0xF1, 0x43, 0x5C, 0x9F, 0xF1, 0x99, 0xA9, 0xB7, 0xD6, 0xEA, + 0x12, 0x4C, 0x96, 0x7E, 0x87, 0x76, 0xD2, 0xAB, 0x02, 0x26, + 0xC0, 0x00, 0x02, 0x91, 0x0F, 0xB9, 0x72, 0xF7, 0x06, 0x0A, + 0x2A, 0x92, 0x77, 0x84, 0x0C, 0xBE, 0x44, 0x6D, 0x58, 0x6C, + 0xBC, 0xE0, 0x19, 0x8A, 0xFC, 0x4C, 0x93, 0xDD, 0xB7, 0xC5, + 0x45, 0xBF, 0x0A, 0x66, 0x29, 0xEF, 0x95, 0x78, 0x07, 0x6D, + 0xD0, 0xAA, 0x6C, 0x32, 0xE9, 0xF8, 0xEA, 0x1A, 0x74, 0x79, + 0x51, 0x39, 0x25, 0xCB, 0x00, 0x7C, 0xD0, 0xE7, 0xBB, 0xC4, + 0x09, 0xC2, 0xFC, 0x0F, 0xA2, 0x29, 0x45, 0x30, 0x3F, 0x89, + 0x76, 0x36, 0xF2, 0x92, 0xAF, 0xAF, 0x60, 0xC3, 0x93, 0x3E, + 0x55, 0x6B, 0x87, 0xAE, 0xBF, 0x6E, 0x2C, 0x54, 0xFD, 0xAF, + 0x74, 0x4F, 0x0F, 0xAA, 0xC7, 0x4F, 0x4C, 0x6B, 0x18, 0xFE, + 0xC6, 0x84, 0x27, 0xC0, 0x78, 0xD2, 0xD9, 0xE3, 0x7B, 0x41, + 0x1C, 0x1C, 0xBF, 0xDA, 0xDF, 0xCC, 0x49, 0x41, 0xC0, 0xC0, + 0x65, 0xE0, 0x8F, 0x44, 0x19, 0xE4, 0x78, 0xAD, 0xCD, 0xC5, + 0x5A, 0x9D, 0x11, 0x22, 0x43, 0xD7, 0x6E, 0x39, 0xD5, 0x2B, + 0xA7, 0x73, 0x3F, 0x99, 0xA8, 0xA3, 0x02, 0x73, 0x99, 0xF0, + 0x62, 0x87, 0xF4, 0x8B, 0xD7, 0x4F, 0x47, 0x9C, 0x18, 0x72, + 0x98, 0xB0, 0x33, 0x1B, 0x5E, 0x29, 0x04, 0xEA, 0x71, 0x6B, + 0x45, 0xCB, 0xC0, 0x9E, 0x15, 0x6E, 0x92, 0xDA, 0x50, 0xC3, + 0x58, 0x07, 0x29, 0x5E, 0xC5, 0xBD, 0x3B, 0xCE, 0xAC, 0x11, + 0xE5, 0xBB, 0x3D, 0x7E, 0xB8, 0xC3, 0x1F, 0x94, 0x9F, 0xD9, + 0x63, 0x35, 0x56, 0x23, 0x45, 0xAF, 0x23, 0xF0, 0x0B, 0x7B, + 0xE3, 0xFB, 0x81, 0x2E, 0xD3, 0x20, 0xE3, 0x1D, 0x97, 0x84, + 0xC1, 0x88, 0x77, 0x87, 0x3C, 0xAB, 0xE9, 0x1E, 0x69, 0x04, + 0x7D, 0x7B, 0xE6, 0x6B, 0xDB, 0xE9, 0xF2, 0xB6, 0x66, 0x10, + 0x0F, 0x4D, 0x57, 0x3F, 0xF5, 0x3E, 0xDA, 0xEA, 0xBC, 0xD4, + 0x5B, 0xA1, 0x20, 0xB0, 0x69, 0xC0, 0x8A, 0xA1, 0x28, 0xC0, + 0xEF, 0xFE, 0x2D, 0x0B, 0xF8, 0x06, 0x23, 0x4B, 0x1C, 0x77, + 0x0A, 0x10, 0xBF, 0xA7, 0xD9, 0x24, 0x9E, 0xA2, 0x1B, 0x90, + 0x6C, 0xFD, 0x85, 0x3A, 0xD3, 0xC6, 0x39, 0xA7, 0xA2, 0x28, + 0xC5, 0xC1, 0x2F, 0xD2, 0x28, 0xDC, 0x4F, 0x63, 0x0D, 0x6B, + 0x54, 0xB4, 0x27, 0x95, 0xC3, 0xC1, 0xE0, 0x08, 0xB0, 0x9D, + 0x7F, 0x6C, 0xFE, 0xF5, 0x65, 0xCE, 0x0E, 0xCE, 0x24, 0xCD, + 0xAE, 0x4C, 0x0F, 0x82, 0x54, 0x48, 0xAF, 0x37, 0xAE, 0x64, + 0x31, 0x07, 0xEE, 0xE6, 0x6B, 0xEF, 0xAF, 0x7F, 0x2A, 0x07, + 0x2E, 0xB6, 0xD0, 0xAC, 0x70, 0x87, 0x19, 0x9E, 0xD3, 0xFB, + 0x89, 0xBD, 0xBA, 0xFF, 0xB9, 0x92, 0x4B, 0x25, 0x65, 0xA3, + 0x09, 0xCD, 0xFE, 0x32, 0x19, 0xF3, 0xF4, 0x05, 0xA4, 0x13, + 0xDF, 0x65, 0xA3, 0x4C, 0xE8, 0xF3, 0x3C, 0xA3, 0x11, 0x95, + 0xEA, 0xC3, 0x34, 0xCA, 0xC6, 0xB9, 0x34, 0x6B, 0x53, 0x18, + 0xCC, 0xF9, 0x44, 0x28, 0x1E, 0x90, 0x74, 0xA6, 0xB1, 0x59, + 0x4D, 0xAE, 0x18, 0x53, 0x1D, 0x60, 0xCF, 0xCC, 0x15, 0xDA, + 0x4E, 0x2B, 0x6F, 0x8F, 0x4E, 0x39, 0x89, 0xE3, 0x67, 0x05, + 0xB5, 0x9C, 0x36, 0x5B, 0x3C, 0xA0, 0x9F, 0x46, 0x24, 0xE7, + 0x61, 0x42, 0x1C, 0xA7, 0xD7, 0x27, 0x5B, 0x5C, 0xF5, 0xFD, + 0x5F, 0x03, 0x79, 0x5F, 0x36, 0xA0, 0x85, 0x13, 0x40, 0x48, + 0x8C, 0x44, 0xB4, 0x64, 0x54, 0x83, 0xD8, 0xB6, 0x83, 0xB8, + 0x4E, 0x46, 0xCF, 0x98, 0xA5, 0x1C, 0xAA, 0xF0, 0x03, 0xD6, + 0x26, 0x80, 0x0F, 0x2A, 0xC5, 0xF4, 0xD1, 0x28, 0x0C, 0x5F, + 0xF8, 0x63, 0x8D, 0x68, 0x26, 0x3A, 0xE5, 0x5D, 0xAE, 0x17, + 0x64, 0xF2, 0x60, 0x73, 0x6D, 0xBD, 0x75, 0x90, 0xD2, 0x50, + 0xC4, 0x9A, 0x4C, 0x3E, 0x4B, 0xAE, 0x57, 0xBE, 0xCD, 0x42, + 0xC1, 0xA7, 0xCB, 0xAD, 0x73, 0x15, 0xF6, 0x69, 0x36, 0xB5, + 0xC3, 0x15, 0x1D, 0xC0, 0x09, 0x4B, 0x6B, 0x4F, 0xA3, 0xD2, + 0xAF, 0x9E, 0xF4, 0x9A, 0xC1, 0x5F, 0xF5, 0x0A, 0x95, 0x93, + 0xA4, 0x57, 0xA9, 0x61, 0x8D, 0x98, 0xB1, 0x19, 0xCC, 0x95, + 0x02, 0x26, 0xC0, 0x00, 0x02, 0x6E, 0x4C, 0x0F, 0xB4, 0x2D, + 0xBC, 0x3A, 0x51, 0xAE, 0x16, 0xBA, 0xEC, 0x72, 0x04, 0x3E, + 0x8D, 0x3D, 0x4E, 0x0E, 0x29, 0x4B, 0x6F, 0x00, 0xF5, 0xE2, + 0x12, 0x0B, 0xAC, 0x73, 0x7E, 0xA5, 0x12, 0x8D, 0x59, 0x45, + 0x73, 0xC8, 0xB9, 0x57, 0xCB, 0x78, 0xAD, 0xA3, 0xBD, 0x95, + 0x18, 0x04, 0x86, 0xB9, 0xFE, 0x9F, 0xBF, 0x67, 0x03, 0x08, + 0xA9, 0x91, 0xF3, 0x71, 0x1A, 0x6C, 0xD2, 0x74, 0xAA, 0x40, + 0xCC, 0x30, 0x7D, 0x0B, 0x78, 0xEF, 0xC7, 0xDC, 0x95, 0xC3, + 0x29, 0x5D, 0x18, 0xBC, 0x38, 0x7C, 0xFE, 0x65, 0xEF, 0x44, + 0xF0, 0x61, 0x91, 0x3E, 0x59, 0x75, 0x96, 0x84, 0x75, 0xD9, + 0xA2, 0x99, 0xDB, 0xFA, 0x9B, 0x7D, 0x09, 0x06, 0x8D, 0xDA, + 0xCC, 0xA1, 0x6E, 0x2E, 0xDB, 0x12, 0x83, 0xA6, 0x2E, 0x9C, + 0xC8, 0x5A, 0xE9, 0xBB, 0xC3, 0x70, 0x16, 0x0C, 0x6E, 0x2A, + 0xDA, 0xD0, 0xDF, 0xF4, 0xAF, 0xAA, 0x42, 0x0A, 0x16, 0x8C, + 0x73, 0xAA, 0xAD, 0x75, 0x7B, 0xCC, 0x29, 0xB5, 0x18, 0xB9, + 0x58, 0xBF, 0xDB, 0x91, 0x44, 0x74, 0x25, 0x3E, 0x54, 0xC3, + 0x2D, 0x8A, 0x2E, 0x85, 0xD0, 0x92, 0x02, 0xAE, 0xA1, 0xF9, + 0x70, 0xC5, 0x33, 0x49, 0x57, 0xE1, 0x90, 0x48, 0x54, 0x73, + 0x04, 0xAD, 0x1E, 0x41, 0xE8, 0xD7, 0x24, 0xFE, 0x85, 0x83, + 0x47, 0x0F, 0x9C, 0x33, 0xF9, 0xF5, 0x10, 0xAD, 0x42, 0xFA, + 0x7B, 0xE7, 0x1C, 0x35, 0xB3, 0x8D, 0x0E, 0x2E, 0xD7, 0xCB, + 0x40, 0x32, 0x9C, 0x64, 0x3A, 0x68, 0x33, 0xF3, 0xBE, 0xD9, + 0xCD, 0xC9, 0x73, 0xEB, 0x67, 0x6E, 0x52, 0x25, 0x2F, 0xD9, + 0x7A, 0x93, 0x8B, 0x1D, 0x9D, 0xB2, 0xAF, 0xF3, 0xEE, 0x6F, + 0xB5, 0xC7, 0x8F, 0xD5, 0x4F, 0xC9, 0x7C, 0xAC, 0x05, 0x9A, + 0x16, 0x82, 0x04, 0xC4, 0xF5, 0x47, 0xD7, 0x61, 0xC7, 0xBC, + 0x8D, 0x76, 0xA9, 0xFA, 0xA6, 0x17, 0x32, 0x3A, 0x2C, 0x93, + 0x4D, 0x0B, 0x57, 0x50, 0xF5, 0xD8, 0xE6, 0xB2, 0x52, 0x4F, + 0xE5, 0x1F, 0xDB, 0xA0, 0x17, 0xEF, 0x44, 0x9A, 0x8D, 0xBF, + 0x8D, 0xB4, 0x66, 0x11, 0xF2, 0xB1, 0x51, 0x50, 0xB4, 0xDE, + 0xE0, 0x30, 0x5B, 0x1F, 0x92, 0xAC, 0x30, 0x43, 0xB3, 0x1F, + 0x32, 0xDC, 0x84, 0x2F, 0x29, 0xE9, 0x78, 0xBC, 0x5A, 0xA2, + 0x9A, 0xA8, 0xFC, 0xE2, 0xB4, 0xE7, 0x8C, 0xCC, 0x65, 0xA9, + 0x37, 0x85, 0xD8, 0x0B, 0xB3, 0xA3, 0x4F, 0x28, 0xBE, 0x4D, + 0x45, 0x97, 0xAA, 0x2E, 0xEC, 0x6D, 0x4C, 0x9E, 0x19, 0x5F, + 0xD1, 0x47, 0x87, 0x8A, 0x76, 0xCF, 0x20, 0xCC, 0xDC, 0xC5, + 0x37, 0xDD, 0x45, 0xE8, 0x13, 0xFD, 0x72, 0x92, 0x7E, 0x97, + 0xA4, 0xA7, 0x58, 0xC1, 0x0E, 0xB6, 0x9E, 0x20, 0xCC, 0xD3, + 0x5F, 0x94, 0x25, 0xD3, 0xB8, 0xB0, 0x51, 0x82, 0x45, 0x64, + 0x4B, 0x6D, 0xCB, 0x9F, 0xC6, 0x5F, 0xDD, 0x1D, 0x1B, 0xB0, + 0x7D, 0xEB, 0xEC, 0x85, 0xB0, 0x90, 0x36, 0xE0, 0xE5, 0x5F, + 0x66, 0xA3, 0x69, 0x72, 0x2C, 0x35, 0x52, 0x45, 0x8A, 0xD9, + 0x31, 0xCB, 0xD9, 0xDD, 0xF0, 0x62, 0x6A, 0x4B, 0x47, 0xEF, + 0x13, 0xD7, 0x65, 0x17, 0xC4, 0xE9, 0xD1, 0xA3, 0xF4, 0x5E, + 0x17, 0x73, 0x4C, 0xA5, 0x81, 0xD7, 0xBD, 0xB3, 0x34, 0x98, + 0x61, 0x01, 0x50, 0x1D, 0x7D, 0x7A, 0x82, 0x38, 0x93, 0x9F, + 0x0A, 0xAC, 0x42, 0x5B, 0x10, 0x4C, 0x57, 0x10, 0x4A, 0x97, + 0xD3, 0xCC, 0x8F, 0x23, 0xD1, 0xA6, 0xCB, 0xAD, 0x47, 0x8F, + 0x41, 0x24, 0x2B, 0xEB, 0xB9, 0x0B, 0x71, 0x1C, 0x27, 0x31, + 0x91, 0x21, 0x77, 0xA2, 0x01, 0x2D, 0x6F, 0x9B, 0x99, 0x5F, + 0xAC, 0xD4, 0x8E, 0x1D, 0xBB, 0x2C, 0xA3, 0xA1, 0xA2, 0xC3, + 0x2A, 0xAD, 0x6B, 0xA4, 0x62, 0xAA, 0xAE, 0xEA, 0xEE, 0x10, + 0x30, 0xB3, 0x9E, 0x2A, 0x13, 0x09, 0xF8, 0xC4, 0x2B, 0x8C, + 0xE1, 0x51, 0x62, 0xE7, 0x5D, 0xE8, 0x00, 0x50, 0x46, 0x7E, + 0xC7, 0x9A, 0x3F, 0x21, 0xD9, 0x0C, 0xB3, 0xA7, 0x2E, 0x5F, + 0x4B, 0x42, 0x02, 0x26, 0xC0, 0x00, 0x02, 0x12, 0xA7, 0x07, + 0x49, 0x33, 0x98, 0x05, 0x3D, 0xD0, 0x14, 0xD2, 0x03, 0xA9, + 0xA5, 0x66, 0x6B, 0x53, 0x43, 0x97, 0x83, 0x21, 0x8F, 0x46, + 0x67, 0x3C, 0x86, 0xDB, 0x7B, 0xBA, 0xD2, 0xC5, 0xB4, 0x09, + 0x0F, 0x2D, 0xD3, 0x4E, 0xD2, 0xB7, 0xFC, 0x75, 0x52, 0x16, + 0x8A, 0x56, 0x5A, 0xCC, 0xE6, 0xFE, 0x5C, 0xE8, 0xAE, 0x48, + 0x7F, 0x81, 0xF2, 0x46, 0x00, 0xE5, 0x53, 0xAF, 0xC7, 0x01, + 0x12, 0x9A, 0xF9, 0xDC, 0x96, 0x79, 0xC6, 0x61, 0xC6, 0xAC, + 0xCE, 0x0B, 0x6C, 0x9F, 0xFD, 0x16, 0xCA, 0xD1, 0xD6, 0xAB, + 0xE4, 0xCB, 0x64, 0x88, 0xF7, 0x17, 0x7F, 0xCD, 0xBB, 0x8F, + 0xF9, 0x5C, 0x75, 0x7C, 0x3D, 0xBE, 0x03, 0x68, 0x30, 0xA8, + 0xC7, 0x82, 0x62, 0xDD, 0x5A, 0x42, 0x3D, 0x8D, 0x02, 0xD1, + 0x68, 0xF2, 0x04, 0x0B, 0xFA, 0xC2, 0x23, 0xB9, 0x26, 0xAA, + 0x5F, 0x43, 0x7D, 0xE5, 0xD2, 0x97, 0xB8, 0xD5, 0x6F, 0xBE, + 0x56, 0xC6, 0xCA, 0xE7, 0x56, 0xDA, 0x14, 0x4A, 0xA6, 0x35, + 0x3F, 0xBF, 0xBC, 0x9D, 0xCD, 0x51, 0x55, 0x98, 0xAB, 0xC0, + 0x42, 0xC8, 0x7D, 0x67, 0xB3, 0xDD, 0x2B, 0x6B, 0xA0, 0x06, + 0x9F, 0x13, 0x79, 0xBC, 0xE2, 0xC8, 0x9E, 0xC3, 0xFF, 0x94, + 0x9E, 0x3D, 0x60, 0xAE, 0xB3, 0x28, 0x44, 0xC5, 0xE1, 0xD8, + 0x40, 0x7F, 0xC0, 0x8F, 0x93, 0xFC, 0xDA, 0xEC, 0x70, 0x14, + 0xB1, 0x07, 0xA1, 0x21, 0x6D, 0xA2, 0xA3, 0xEF, 0x80, 0xF1, + 0xC4, 0xCC, 0xA1, 0x77, 0xD9, 0xA3, 0xD9, 0x31, 0xFB, 0xC5, + 0xE5, 0xDE, 0xC1, 0xF2, 0x6F, 0xFC, 0x73, 0x35, 0xC4, 0x1D, + 0x2C, 0x15, 0x57, 0x64, 0x57, 0x4A, 0x0D, 0x4F, 0xCB, 0xD3, + 0x3E, 0x12, 0x82, 0x5C, 0xFC, 0xED, 0x7B, 0xA1, 0x3F, 0x72, + 0x1E, 0x6B, 0x12, 0x6A, 0xB9, 0x55, 0xD2, 0xC3, 0x04, 0x52, + 0x01, 0xBB, 0x54, 0x20, 0x0A, 0xA5, 0xDA, 0xDD, 0x2D, 0x56, + 0xF4, 0xD0, 0x08, 0x62, 0x1E, 0x68, 0x30, 0x40, 0x57, 0x8A, + 0xC2, 0xDD, 0xBF, 0x0D, 0x7B, 0x28, 0xE0, 0x82, 0x1A, 0x3F, + 0xDC, 0xEA, 0x07, 0xC4, 0x32, 0x74, 0x14, 0x18, 0x9C, 0x00, + 0x9C, 0x91, 0xC0, 0x7A, 0x0A, 0x18, 0x89, 0xAD, 0xA8, 0x3D, + 0x42, 0x21, 0x0F, 0x08, 0x8D, 0xA4, 0xD5, 0xA7, 0x08, 0x09, + 0x3C, 0x21, 0x7E, 0x4E, 0x17, 0xB9, 0x1A, 0x80, 0x22, 0x45, + 0x33, 0x43, 0x24, 0xDD, 0xC4, 0x66, 0xD2, 0xB4, 0x4E, 0x40, + 0xD5, 0x5F, 0xEA, 0x86, 0x9F, 0x8B, 0x41, 0x9D, 0xD0, 0x96, + 0xC9, 0x64, 0x32, 0x39, 0xA1, 0x89, 0x56, 0x7E, 0x1D, 0x22, + 0x08, 0x08, 0xB5, 0x60, 0x1F, 0x52, 0x41, 0xA4, 0x8F, 0xC0, + 0x52, 0x6B, 0xA7, 0xBB, 0x52, 0x0E, 0x96, 0xEE, 0x62, 0x62, + 0x1A, 0xCF, 0x4C, 0x65, 0x59, 0x32, 0x2D, 0x3C, 0x1D, 0x40, + 0x57, 0xB0, 0xE9, 0xF2, 0xBA, 0x04, 0xD2, 0xBE, 0x22, 0xA9, + 0x93, 0x8E, 0xC2, 0x32, 0x06, 0x20, 0xDC, 0x9F, 0xE5, 0xC2, + 0x57, 0xFC, 0xC5, 0xDE, 0xE7, 0xD3, 0xCA, 0xB6, 0xD8, 0x85, + 0xB2, 0xE1, 0xAD, 0x23, 0x47, 0x02, 0x3F, 0xD7, 0xBD, 0x51, + 0x2A, 0x6B, 0x33, 0x2D, 0x23, 0x6D, 0xD5, 0x72, 0x1F, 0xEC, + 0x8F, 0x7F, 0x1B, 0x22, 0x2D, 0x9E, 0x44, 0x58, 0xE0, 0xA3, + 0x50, 0x54, 0xCF, 0x6B, 0x5E, 0x59, 0x7A, 0xF0, 0xDA, 0x2E, + 0x9C, 0xC4, 0xD3, 0xD9, 0x83, 0x56, 0xBB, 0x09, 0x58, 0xFF, + 0x00, 0xDD, 0xDA, 0x57, 0x5C, 0xC3, 0xED, 0x10, 0x6E, 0xD6, + 0xD0, 0xF0, 0xB9, 0xD1, 0x2C, 0x1E, 0x4E, 0x33, 0xA7, 0x44, + 0x9C, 0x87, 0xB2, 0x91, 0x16, 0x2C, 0x44, 0x1E, 0x66, 0x79, + 0x06, 0x12, 0x26, 0xB6, 0x36, 0x1F, 0xBA, 0x56, 0x9F, 0xAB, + 0x64, 0x63, 0xE4, 0x90, 0x75, 0x72, 0xF9, 0x35, 0x77, 0x2B, + 0x38, 0xE5, 0x4F, 0x29, 0xF1, 0x4C, 0xD8, 0xBD, 0x20, 0x60, + 0x65, 0x26, 0x57, 0xCE, 0x52, 0x4B, 0x18, 0xD4, 0xDF, 0xCE, + 0xC0, 0x78, 0x60, 0x83, 0xD4, 0xDF, 0xE9, 0xA2, 0x61, 0x37, + 0x78, 0x7D, 0xEB, 0xE5, 0x02, 0x26, 0xC0, 0x00, 0x02, 0x47, + 0xA5, 0x8E, 0xA4, 0x38, 0x2C, 0xDC, 0x61, 0xFB, 0xD1, 0x29, + 0xFD, 0x30, 0xA5, 0xEE, 0xB0, 0x2F, 0xF6, 0x53, 0x4B, 0x4E, + 0xE9, 0x26, 0x67, 0x28, 0xC0, 0x06, 0x59, 0x13, 0x88, 0xF8, + 0x9B, 0xE8, 0x6B, 0x9E, 0x5A, 0xCF, 0x8D, 0x8D, 0xE1, 0x80, + 0x1A, 0xE1, 0x4A, 0x59, 0x92, 0x6A, 0x57, 0x38, 0x79, 0x1E, + 0x5D, 0xA4, 0x35, 0xA5, 0xD3, 0x80, 0x41, 0x97, 0x69, 0xB7, + 0xDF, 0xE8, 0x07, 0xFA, 0xA6, 0x64, 0xFD, 0xB2, 0xD0, 0x66, + 0xB4, 0x35, 0x52, 0xCC, 0xC9, 0x82, 0xCF, 0xFA, 0xB3, 0xE0, + 0xE6, 0x96, 0x35, 0xEF, 0xD5, 0xF6, 0xA1, 0x0B, 0xAD, 0xE0, + 0x50, 0x7D, 0x9B, 0x36, 0x8D, 0xD2, 0x6E, 0x27, 0x14, 0x2D, + 0x6D, 0xCE, 0xC2, 0xF8, 0xDA, 0x64, 0xDC, 0xFD, 0xC8, 0x79, + 0x72, 0x00, 0xE3, 0xF9, 0x3F, 0x40, 0x0D, 0x0F, 0x40, 0x3A, + 0xE3, 0xFE, 0xEA, 0x93, 0x18, 0x7F, 0x2D, 0xFA, 0x7C, 0x6B, + 0xCB, 0x45, 0x8C, 0xD5, 0x8E, 0xE3, 0x95, 0x4C, 0xF9, 0x60, + 0x83, 0x8E, 0x07, 0xC0, 0x17, 0xE9, 0x6C, 0xAF, 0x2C, 0x08, + 0x04, 0xF5, 0x8B, 0xB7, 0xB8, 0x09, 0xAD, 0x91, 0x02, 0x9A, + 0x28, 0x25, 0xE7, 0x80, 0x5C, 0xCB, 0x69, 0xA0, 0xDA, 0x43, + 0x4F, 0x4C, 0x4A, 0xA1, 0xCB, 0x45, 0x0E, 0x2F, 0x87, 0x77, + 0x43, 0x7E, 0x0E, 0x42, 0xE7, 0x21, 0x2B, 0x73, 0x90, 0x21, + 0x7B, 0x9B, 0x87, 0xEC, 0x27, 0x76, 0xFF, 0x9F, 0xEB, 0x79, + 0xF9, 0x70, 0xE6, 0x73, 0x51, 0x4B, 0x8D, 0xE4, 0xB6, 0x76, + 0xFF, 0x23, 0x71, 0xD9, 0x87, 0x43, 0x29, 0xE4, 0xA0, 0xDE, + 0xB7, 0xCF, 0x92, 0x03, 0x84, 0x07, 0x17, 0xA1, 0xE6, 0x7B, + 0x43, 0x14, 0xA3, 0x30, 0xC0, 0x1B, 0x44, 0x91, 0xCF, 0x80, + 0x48, 0x92, 0x2B, 0x6D, 0x59, 0x8E, 0x63, 0x8C, 0xE8, 0xC3, + 0xA9, 0x04, 0x33, 0x3F, 0x75, 0x6A, 0x2B, 0x7B, 0xEC, 0x74, + 0x26, 0xAD, 0x34, 0x68, 0xB9, 0x9F, 0x63, 0xEE, 0xEA, 0x42, + 0x0D, 0xC7, 0x24, 0x7B, 0xD4, 0x5E, 0x5A, 0x91, 0xF1, 0x41, + 0x2E, 0x6B, 0x0D, 0xB9, 0x75, 0xCE, 0x51, 0x1E, 0x6E, 0xF2, + 0x6B, 0x9C, 0xD4, 0xBD, 0xAF, 0x54, 0x63, 0x1A, 0xA0, 0x29, + 0x65, 0x22, 0x04, 0x9D, 0xE2, 0x6F, 0x37, 0x4B, 0xC2, 0xC2, + 0x62, 0x1B, 0x94, 0xE0, 0xF9, 0xC8, 0xB0, 0x94, 0x43, 0x29, + 0x0D, 0x4F, 0x23, 0x4A, 0xBA, 0x83, 0x15, 0x97, 0x96, 0xC8, + 0x0A, 0x85, 0xD2, 0x80, 0x8B, 0x0C, 0x16, 0x57, 0x32, 0xCA, + 0xA4, 0xF6, 0x20, 0xB5, 0x49, 0x30, 0xCC, 0xD3, 0x1C, 0xAB, + 0xE0, 0xAE, 0x52, 0x7B, 0x71, 0x40, 0x80, 0xAC, 0x30, 0x78, + 0xEE, 0x5A, 0xD3, 0x82, 0x3F, 0x51, 0x34, 0xE8, 0x38, 0xD4, + 0x8D, 0x09, 0xEB, 0xDC, 0x9E, 0x82, 0xF7, 0xE2, 0xAA, 0xCD, + 0x2D, 0x17, 0x0C, 0x08, 0x22, 0xAF, 0xED, 0xE4, 0xC0, 0xC1, + 0xBB, 0x9C, 0x47, 0xC1, 0x23, 0x81, 0x03, 0x85, 0xDD, 0x00, + 0x5C, 0x4D, 0x7F, 0xF1, 0x02, 0xF8, 0xA9, 0xA1, 0x8E, 0xB1, + 0xCD, 0xFC, 0x6F, 0xC5, 0x0D, 0x37, 0xD9, 0x83, 0x15, 0x23, + 0x14, 0x03, 0x54, 0x37, 0xFC, 0xAC, 0xB6, 0xC8, 0x0A, 0x47, + 0x3F, 0x22, 0x6F, 0xD7, 0xFA, 0xBB, 0x8D, 0x17, 0xB2, 0x7C, + 0x0F, 0x65, 0xB4, 0xA2, 0x3A, 0x5F, 0x55, 0x34, 0xEC, 0xCA, + 0x89, 0xDD, 0xD4, 0x44, 0x77, 0x30, 0x9A, 0x20, 0x6A, 0x1A, + 0x9D, 0xBE, 0x39, 0x25, 0x2E, 0xE2, 0x0C, 0xDD, 0xE0, 0x50, + 0x8F, 0xD7, 0x38, 0x01, 0xB1, 0x25, 0xC1, 0xFD, 0x06, 0xD0, + 0x60, 0xC9, 0xEB, 0x1D, 0x77, 0x6D, 0xA2, 0x18, 0xB2, 0x0B, + 0x03, 0xE6, 0xF7, 0x07, 0x51, 0xFC, 0xAF, 0xE9, 0xD4, 0xFC, + 0x80, 0x69, 0x1C, 0xE1, 0x82, 0x96, 0x39, 0x37, 0xC1, 0xD3, + 0xB1, 0x81, 0x62, 0xB7, 0x20, 0x7F, 0xDF, 0xB7, 0x84, 0xD2, + 0xCC, 0xFB, 0x7E, 0x90, 0xBB, 0x05, 0x1B, 0x81, 0xA2, 0xE0, + 0x66, 0x11, 0x8A, 0x37, 0x93, 0x0F, 0x67, 0x81, 0x77, 0x03, + 0xA6, 0xE2, 0x52, 0xB6, 0x0F, 0x9A, 0x02, 0x26, 0xC0, 0x00, + 0x02, 0x1D, 0x9D, 0x63, 0xD2, 0x5B, 0xB0, 0x79, 0x8A, 0xF4, + 0x7F, 0x08, 0x9B, 0x5E, 0xAB, 0x6E, 0xD1, 0xCC, 0x2B, 0x7C, + 0xFF, 0x0D, 0x0E, 0xDD, 0x5F, 0xAA, 0x4C, 0x53, 0x45, 0x53, + 0x48, 0xCD, 0xCD, 0xF7, 0xBA, 0x5B, 0xB9, 0x84, 0xBB, 0x00, + 0xC2, 0xF0, 0xFB, 0x30, 0x5F, 0x04, 0x4E, 0x25, 0x4C, 0x06, + 0x03, 0x8D, 0xC5, 0x37, 0xA6, 0x9D, 0x1C, 0xD8, 0x13, 0xA7, + 0x96, 0xBE, 0xED, 0x22, 0xD7, 0xBA, 0x9F, 0x4C, 0x2E, 0xF9, + 0xD0, 0x5B, 0xBB, 0xF8, 0xB8, 0x0A, 0xF3, 0xEC, 0x31, 0x3F, + 0x84, 0x6E, 0x3A, 0x12, 0x8B, 0x6A, 0x2E, 0x28, 0xCE, 0xB8, + 0x1A, 0xC6, 0xE6, 0x4A, 0xD9, 0x74, 0x04, 0x24, 0xD8, 0x79, + 0x8D, 0x62, 0x0C, 0xB0, 0xAE, 0xAF, 0x67, 0xB4, 0xA1, 0x3D, + 0x93, 0x1D, 0xA2, 0x52, 0x98, 0x3F, 0x57, 0x73, 0x94, 0xB6, + 0x94, 0xBD, 0x0F, 0x42, 0x6A, 0x64, 0x7B, 0x17, 0xAC, 0x8D, + 0x46, 0xD0, 0xE4, 0x1B, 0x8C, 0x56, 0xB6, 0x47, 0xCB, 0xFD, + 0x56, 0x61, 0x6E, 0xA0, 0xBF, 0x6B, 0x8E, 0x68, 0x05, 0x55, + 0xA4, 0xB3, 0x8C, 0x76, 0x48, 0x73, 0x4C, 0x8D, 0x9D, 0xA2, + 0xA0, 0xA1, 0xFB, 0xD0, 0x33, 0x32, 0x39, 0xD2, 0x10, 0x1C, + 0x3C, 0x93, 0xC9, 0xCA, 0x6A, 0x6E, 0x7C, 0xB6, 0xF1, 0x03, + 0xF3, 0x45, 0x51, 0x05, 0x48, 0x30, 0xF0, 0xC6, 0x84, 0xFD, + 0x4E, 0x3B, 0x03, 0xE0, 0x62, 0xB8, 0x53, 0x55, 0xB6, 0xB8, + 0x02, 0x7C, 0xB9, 0xD5, 0x5C, 0xA2, 0x9B, 0x97, 0x8A, 0xA4, + 0xDF, 0x42, 0xEB, 0x91, 0x2C, 0x98, 0x82, 0xA9, 0xAE, 0xB0, + 0x13, 0xF6, 0x6E, 0x90, 0x42, 0xFE, 0xD3, 0xAA, 0x1E, 0xBA, + 0x69, 0xFC, 0xF8, 0x20, 0xEA, 0x5D, 0xA8, 0xFE, 0x64, 0x56, + 0x26, 0x4C, 0x6C, 0x69, 0x8F, 0xAC, 0x30, 0xCF, 0xAE, 0x8B, + 0xD5, 0x63, 0x10, 0xDD, 0xCE, 0x6E, 0x0C, 0xAB, 0x31, 0x46, + 0xDF, 0x8A, 0x33, 0x28, 0x8A, 0x1C, 0xEB, 0xBE, 0xAA, 0x71, + 0x44, 0x5C, 0x89, 0xEA, 0x00, 0x34, 0x23, 0xEB, 0x18, 0x83, + 0x7F, 0xB1, 0x9B, 0x3A, 0xBF, 0x08, 0x68, 0xB6, 0xC8, 0xE2, + 0xD8, 0x2D, 0x5E, 0xA2, 0x99, 0xB9, 0xBC, 0xF8, 0x31, 0xD1, + 0xFD, 0x31, 0xD4, 0x32, 0x05, 0x58, 0x5C, 0xFB, 0xCD, 0x8C, + 0xFF, 0x75, 0x99, 0x93, 0xC8, 0x0C, 0xE8, 0xE8, 0x60, 0x11, + 0xB8, 0x5F, 0x15, 0xB5, 0x89, 0x47, 0xE1, 0x1C, 0x23, 0x1B, + 0x8E, 0x56, 0x02, 0xD3, 0x5F, 0xBD, 0xA9, 0x9F, 0x67, 0x06, + 0xCA, 0x0C, 0x6E, 0x9C, 0xFB, 0x2D, 0x3A, 0xE3, 0xF6, 0x2C, + 0x72, 0x86, 0xCD, 0x68, 0xAC, 0x7B, 0x22, 0xA3, 0x01, 0x15, + 0x50, 0xB7, 0xA0, 0x05, 0x88, 0xD0, 0xDF, 0xBD, 0xDA, 0x3B, + 0xC4, 0xFF, 0x40, 0xA1, 0x46, 0x55, 0x28, 0xB3, 0x0C, 0x1E, + 0xF8, 0xE9, 0x0A, 0x39, 0xD1, 0x66, 0xDF, 0x5B, 0x2B, 0x2C, + 0xCA, 0xA5, 0x36, 0x32, 0x0D, 0xAC, 0x8E, 0xDF, 0x75, 0x9A, + 0xE5, 0x8B, 0xE5, 0x99, 0x6B, 0xD6, 0xB7, 0x83, 0x6B, 0xFE, + 0xEA, 0xC9, 0x71, 0xB5, 0xB9, 0xAE, 0xBE, 0x74, 0xB6, 0x58, + 0x28, 0x06, 0x23, 0xCF, 0x8D, 0x09, 0xBC, 0xE3, 0x18, 0x6C, + 0xFA, 0x53, 0x02, 0xE8, 0x3D, 0x44, 0xB3, 0xFE, 0xB3, 0x11, + 0x70, 0x10, 0xDF, 0xC3, 0x93, 0x64, 0x0F, 0x19, 0x35, 0xF0, + 0x82, 0x3A, 0xE3, 0x04, 0xE3, 0x1D, 0xFE, 0x52, 0xE8, 0x52, + 0xA1, 0x7B, 0x19, 0x23, 0x8E, 0x3F, 0x5D, 0x16, 0x60, 0xCB, + 0x13, 0x1A, 0x26, 0x6D, 0xDB, 0x82, 0xEB, 0x72, 0xEE, 0x76, + 0x77, 0x36, 0xEC, 0xEE, 0x09, 0x90, 0xAF, 0x35, 0x25, 0x5A, + 0x29, 0x61, 0x7A, 0xE6, 0x8C, 0x6C, 0x2A, 0x3B, 0x3B, 0xC4, + 0xC5, 0xD4, 0xCF, 0x41, 0x40, 0x04, 0xF3, 0x0F, 0x4B, 0x1B, + 0x99, 0x7D, 0xCF, 0x1F, 0xC5, 0xEB, 0xC5, 0xF5, 0xA1, 0x16, + 0xA2, 0x2A, 0x2E, 0xD5, 0x78, 0xC5, 0xAD, 0x9C, 0x48, 0x76, + 0xF2, 0x58, 0xDE, 0x68, 0x19, 0x58, 0x8C, 0xEA, 0xF5, 0xD0, + 0x54, 0x4F, 0x73, 0x60, 0x92, 0x71, 0x27, 0xF7, 0x02, 0x26, + 0xC0, 0x00, 0x02, 0x24, 0xC2, 0xE1, 0xD7, 0x77, 0x1A, 0x4D, + 0x5F, 0x7B, 0xC6, 0xC7, 0x52, 0x42, 0x9C, 0x26, 0xBE, 0x75, + 0x2E, 0x37, 0x00, 0xD8, 0x2C, 0xEF, 0x37, 0xC2, 0xF8, 0x88, + 0x89, 0xBA, 0x60, 0x35, 0xCE, 0xD5, 0xC4, 0x3D, 0x00, 0x6A, + 0xF1, 0xE1, 0x66, 0x4C, 0xFF, 0x79, 0xC7, 0xDF, 0x04, 0x99, + 0x02, 0xEB, 0x0E, 0x9D, 0xBE, 0x2C, 0x06, 0xB8, 0xE1, 0xC8, + 0xAE, 0xC4, 0xE8, 0xB7, 0xE7, 0x66, 0xEF, 0x01, 0x72, 0x45, + 0xB2, 0xBF, 0x69, 0xF7, 0xA2, 0x72, 0xEC, 0x91, 0x82, 0x9D, + 0xE5, 0x91, 0x9A, 0x6D, 0x4D, 0x0D, 0x29, 0x58, 0xE3, 0xA2, + 0xBB, 0x4F, 0x7B, 0xED, 0xA6, 0xEC, 0x0D, 0x91, 0xF6, 0xE7, + 0x68, 0x61, 0xDF, 0x8A, 0x1A, 0x5A, 0x2A, 0x21, 0x51, 0x80, + 0x31, 0xD5, 0xD6, 0xFB, 0xE5, 0x0D, 0x01, 0x49, 0x22, 0x05, + 0xEE, 0xA2, 0x7A, 0xF6, 0x6C, 0xCD, 0x9D, 0x95, 0x79, 0x7E, + 0x9D, 0x22, 0x91, 0x59, 0x31, 0x80, 0x55, 0xC9, 0x66, 0xC8, + 0x33, 0x36, 0x56, 0x65, 0xCC, 0x26, 0x88, 0xE7, 0xC0, 0x01, + 0xF4, 0x22, 0xF9, 0xE6, 0xF1, 0x18, 0x69, 0xAB, 0x93, 0x80, + 0x95, 0xBF, 0xA4, 0xB7, 0x7F, 0x56, 0x7D, 0xC0, 0xEE, 0xA7, + 0x01, 0x5B, 0x9B, 0xA6, 0x80, 0x5A, 0x17, 0x31, 0xC8, 0x93, + 0xF6, 0x6F, 0xFD, 0x27, 0x9A, 0x09, 0xB8, 0x48, 0x04, 0xD5, + 0x1F, 0xBB, 0x8B, 0xED, 0xB7, 0x08, 0x43, 0xBF, 0x82, 0xB1, + 0xAF, 0xD9, 0x35, 0x03, 0xE8, 0x36, 0xB4, 0xCD, 0x74, 0x74, + 0x30, 0x85, 0x73, 0x1F, 0x51, 0xED, 0xA5, 0xFD, 0x64, 0xFC, + 0xF1, 0x22, 0x21, 0x36, 0x3C, 0x5E, 0x46, 0x74, 0x9D, 0x94, + 0x2C, 0x0F, 0xDF, 0x2B, 0x6E, 0x5E, 0xA8, 0x7C, 0x80, 0xA4, + 0x4C, 0xE4, 0x1A, 0xE2, 0x80, 0x29, 0x5F, 0x85, 0x76, 0x95, + 0xD0, 0x2D, 0x26, 0x50, 0xB2, 0x14, 0x37, 0xA3, 0x3A, 0x4E, + 0xB9, 0x68, 0xCB, 0x1D, 0x9A, 0x9B, 0x33, 0xD0, 0x3D, 0x1B, + 0x0F, 0x3F, 0xA2, 0x95, 0xE0, 0x80, 0xA1, 0x39, 0x03, 0x14, + 0x5F, 0x21, 0x27, 0x54, 0xB5, 0x4D, 0x1F, 0x7A, 0x9A, 0x1E, + 0xB1, 0xC9, 0x63, 0xBE, 0xEC, 0x0C, 0xC1, 0x16, 0x80, 0x0F, + 0xFD, 0x3B, 0x3F, 0xFD, 0x97, 0xF8, 0x4F, 0xD9, 0xFE, 0xC7, + 0x6A, 0xE5, 0x40, 0x5B, 0xB8, 0x50, 0xA8, 0x94, 0x6F, 0x9F, + 0xD8, 0x23, 0xE8, 0xBC, 0x16, 0x9B, 0xF8, 0xC5, 0x0C, 0x48, + 0x28, 0x1B, 0x51, 0x8B, 0x1C, 0x9F, 0x37, 0x97, 0x6B, 0x0B, + 0x1C, 0x2B, 0xCF, 0x7F, 0x9E, 0xC4, 0x54, 0x8F, 0x4D, 0xBF, + 0x43, 0xBB, 0x40, 0x20, 0x79, 0x1F, 0x29, 0xF2, 0x43, 0x65, + 0x0D, 0xC8, 0x16, 0xAC, 0xE4, 0xF3, 0x82, 0x67, 0x01, 0xD9, + 0x19, 0x80, 0xAD, 0x16, 0x1F, 0xF6, 0xFA, 0xD3, 0xD8, 0x74, + 0x6F, 0xE5, 0x00, 0x8E, 0x77, 0x95, 0x51, 0x83, 0xD4, 0xF2, + 0x8B, 0x79, 0x2D, 0xBA, 0xB1, 0x10, 0x0C, 0x0F, 0xCC, 0x7D, + 0x24, 0x7A, 0xF5, 0xDF, 0x54, 0x54, 0xC0, 0xAF, 0x80, 0x3D, + 0x7E, 0x9C, 0x8F, 0x27, 0xA7, 0x0A, 0xE4, 0x8C, 0xB7, 0x5C, + 0x61, 0xF4, 0xAD, 0xB3, 0xD8, 0xA9, 0x1B, 0xEC, 0x13, 0xCB, + 0xD8, 0xD3, 0x81, 0xBA, 0x9D, 0xCA, 0xD9, 0xAE, 0xC4, 0x43, + 0x69, 0xF4, 0xDE, 0xF9, 0xE0, 0xFE, 0x5D, 0x53, 0x6D, 0xDA, + 0x5F, 0xED, 0x5F, 0x30, 0x81, 0x2C, 0xDE, 0x92, 0x04, 0xDE, + 0x94, 0xBB, 0x45, 0xAF, 0x14, 0x84, 0xC9, 0xE3, 0xBD, 0xBD, + 0x7F, 0x52, 0xE9, 0x6B, 0x34, 0xCA, 0x06, 0x3A, 0xE1, 0x79, + 0x22, 0x3C, 0xA8, 0x34, 0xED, 0x7E, 0x26, 0x18, 0x84, 0xDB, + 0x92, 0x48, 0xFD, 0xD6, 0x82, 0x04, 0x1D, 0x82, 0x1A, 0xA6, + 0x28, 0x74, 0x31, 0xA5, 0x74, 0x1F, 0xD9, 0x0F, 0xF2, 0xC8, + 0x4C, 0x38, 0x8F, 0xAF, 0x2A, 0xE0, 0x5F, 0xD6, 0xA4, 0x1C, + 0xE3, 0xF8, 0x70, 0xD5, 0x4C, 0xF8, 0x3C, 0x92, 0x0A, 0x01, + 0xEF, 0xB7, 0xEA, 0x57, 0xA5, 0x88, 0x4C, 0x10, 0x68, 0x99, + 0xC8, 0x4F, 0x1D, 0x67, 0x8E, 0x43, 0x7D, 0x3D, 0x5E, 0x20, + 0x02, 0x26, 0xC0, 0x00, 0x02, 0x68, 0x4F, 0x5F, 0x8D, 0xC4, + 0x7F, 0x74, 0xCD, 0xFE, 0xFB, 0x21, 0x22, 0x03, 0x06, 0xFA, + 0x2E, 0x4C, 0xFF, 0x35, 0xBB, 0x9E, 0x93, 0x44, 0x35, 0x8A, + 0xFF, 0x97, 0x39, 0x36, 0x8D, 0x53, 0x4D, 0xFA, 0x04, 0xA8, + 0x98, 0x13, 0x9C, 0xAA, 0x87, 0x0D, 0x8B, 0x50, 0x0D, 0x9C, + 0xB1, 0xAC, 0x4C, 0x59, 0x9B, 0xA2, 0xE4, 0x4C, 0xFC, 0x46, + 0xC5, 0xA6, 0x00, 0x4A, 0xA8, 0xD3, 0x24, 0x40, 0x9E, 0x48, + 0x8F, 0xC0, 0x8C, 0x0E, 0x16, 0x02, 0x3C, 0xF5, 0x12, 0x3D, + 0x97, 0xCE, 0xDE, 0x11, 0xBE, 0x46, 0xE8, 0x8F, 0x15, 0x3B, + 0xB3, 0x1D, 0x74, 0xCB, 0xEE, 0x77, 0x2D, 0xEB, 0xAD, 0x87, + 0x39, 0x85, 0xD7, 0xBC, 0x38, 0x87, 0x2A, 0x0B, 0xB5, 0xB0, + 0xD1, 0x09, 0x7D, 0x12, 0xC6, 0x4D, 0x9A, 0xF8, 0x00, 0xC5, + 0xA2, 0xF9, 0x9A, 0xFF, 0x30, 0x5D, 0x6E, 0x79, 0xD7, 0xBA, + 0xB3, 0xCC, 0x2A, 0x43, 0x61, 0xAC, 0x38, 0x99, 0x10, 0xDF, + 0x60, 0x94, 0x7B, 0x49, 0xD9, 0xC2, 0x4C, 0x6D, 0x3F, 0xD7, + 0x38, 0x85, 0xE7, 0x91, 0x26, 0x9E, 0x7F, 0x7E, 0xA4, 0x91, + 0x9B, 0x92, 0x02, 0xC1, 0x1F, 0xAF, 0x5C, 0x6B, 0xBA, 0x27, + 0xAD, 0x33, 0xCC, 0xE6, 0x20, 0x23, 0x61, 0xBE, 0x4A, 0xDA, + 0x28, 0x63, 0xB2, 0xE2, 0xAC, 0xA9, 0x51, 0x50, 0x9A, 0xAB, + 0xCA, 0x8C, 0x5C, 0xF3, 0x0A, 0x44, 0x03, 0xA9, 0x94, 0x92, + 0x1D, 0xC5, 0xB2, 0x09, 0xF3, 0x0E, 0xB3, 0x6A, 0x1D, 0x72, + 0xDC, 0x71, 0x4A, 0xAE, 0x4E, 0x0B, 0x1B, 0xCF, 0xF3, 0xCF, + 0x4B, 0x38, 0xD1, 0xCA, 0x27, 0xC2, 0x6A, 0x41, 0x0F, 0x3A, + 0x1D, 0xCD, 0x3E, 0xB1, 0x1A, 0xF7, 0x16, 0x48, 0x4A, 0xAA, + 0xA7, 0x28, 0x16, 0x07, 0x9B, 0xE0, 0x41, 0x99, 0xF1, 0x47, + 0x8C, 0x48, 0x6C, 0x2D, 0x93, 0x9E, 0x47, 0x2B, 0x9A, 0x96, + 0x20, 0x44, 0xF4, 0x2A, 0xCB, 0xB0, 0xDA, 0xBD, 0x17, 0x38, + 0xCB, 0x45, 0x9F, 0x21, 0xA8, 0x93, 0x26, 0x67, 0x51, 0x36, + 0x6F, 0x05, 0x4A, 0x08, 0x72, 0xFB, 0xA3, 0x98, 0xBF, 0x9E, + 0x39, 0xEC, 0xAF, 0xDA, 0x8A, 0xFD, 0xC1, 0xB2, 0xC4, 0x4D, + 0xFE, 0xCD, 0xBD, 0x7F, 0xF8, 0xCE, 0x4F, 0x7E, 0x4E, 0xBA, + 0xD1, 0x87, 0x3E, 0x5F, 0x77, 0xA2, 0x96, 0x3E, 0x8B, 0x15, + 0xDE, 0xE7, 0xBF, 0x7F, 0x97, 0x0F, 0xE1, 0xE7, 0x2F, 0x58, + 0x5D, 0xEF, 0x56, 0x39, 0x66, 0x63, 0x61, 0x94, 0x3D, 0x31, + 0xC8, 0x8D, 0xA8, 0xAC, 0xB5, 0x4C, 0x20, 0x1A, 0xD4, 0x18, + 0x34, 0x61, 0xA2, 0x4F, 0x65, 0xB3, 0x12, 0xFE, 0xA9, 0x02, + 0xCC, 0x47, 0x8F, 0x1C, 0x39, 0xFD, 0xDB, 0x08, 0x62, 0xFD, + 0x74, 0x8D, 0xF9, 0xAA, 0x34, 0xCA, 0x20, 0x67, 0xE8, 0xBC, + 0xAB, 0xED, 0x8C, 0x09, 0xC1, 0xA1, 0xE2, 0x3A, 0x41, 0xB7, + 0x1D, 0x82, 0x99, 0xAA, 0x79, 0x93, 0xB5, 0x1D, 0x6A, 0xE8, + 0xFC, 0x31, 0xBF, 0x67, 0xD8, 0xE9, 0x36, 0xD4, 0xBF, 0x9E, + 0xBF, 0xAB, 0x13, 0x20, 0xE3, 0x01, 0x29, 0x60, 0x28, 0x19, + 0xD2, 0x0E, 0xBC, 0x21, 0x3E, 0xD6, 0xC0, 0xC3, 0xDD, 0x64, + 0xFC, 0x98, 0x45, 0x26, 0x9B, 0x5C, 0xCB, 0xE2, 0x76, 0x0A, + 0x0D, 0x13, 0x1A, 0x7F, 0xEF, 0xE2, 0x44, 0xA3, 0xD8, 0xCE, + 0xD6, 0x0E, 0x5A, 0xA8, 0x40, 0x7B, 0x9E, 0xA0, 0xBE, 0xD6, + 0x63, 0x56, 0x77, 0x57, 0x69, 0x2C, 0xF8, 0xB6, 0x8D, 0x08, + 0xF3, 0x80, 0xA1, 0x2F, 0x53, 0xE2, 0x2F, 0xFD, 0xE6, 0x86, + 0x91, 0x83, 0x12, 0x7E, 0x73, 0xCF, 0xC8, 0x12, 0x58, 0xCD, + 0x9B, 0x30, 0x2C, 0x1E, 0xB8, 0x1E, 0xA3, 0x66, 0x27, 0xE9, + 0x33, 0xB2, 0x57, 0x9B, 0xF7, 0xFC, 0x04, 0xE1, 0xDF, 0xA4, + 0xC5, 0x7A, 0x39, 0xCE, 0xDF, 0xEB, 0x03, 0x89, 0x37, 0xFB, + 0xB0, 0x17, 0x12, 0x55, 0x52, 0x60, 0x25, 0xD1, 0xA6, 0x54, + 0xED, 0xD2, 0xBA, 0xE9, 0x66, 0x5A, 0xCA, 0xE3, 0x71, 0xF8, + 0xE7, 0x35, 0x1C, 0xAC, 0x00, 0x2C, 0xBD, 0x52, 0x6F, 0xE0, + 0x96, 0x91, 0x02, 0x26, 0xC0, 0x00, 0x02, 0x02, 0xB1, 0x96, + 0x3E, 0x04, 0x9B, 0x7A, 0x13, 0x3F, 0xA3, 0x05, 0x16, 0xBF, + 0x79, 0xE5, 0x67, 0x77, 0x23, 0x1E, 0x4D, 0x4C, 0x26, 0x7C, + 0x9D, 0x3E, 0xF5, 0x37, 0x49, 0xAC, 0x5C, 0xA7, 0x9F, 0xD7, + 0x8B, 0x61, 0x4B, 0x21, 0xEC, 0x27, 0x9A, 0x62, 0x75, 0xD1, + 0xCB, 0xEB, 0x58, 0x98, 0x84, 0xD1, 0xD9, 0xD8, 0x00, 0x0E, + 0x38, 0xCB, 0x40, 0xE9, 0xC0, 0x87, 0x62, 0x6D, 0xD6, 0xBE, + 0x5E, 0x47, 0x40, 0xCE, 0x39, 0xD2, 0x28, 0xFF, 0x32, 0x08, + 0xFB, 0xF1, 0x86, 0xEF, 0xF1, 0x1F, 0xC7, 0x28, 0xE0, 0x2A, + 0x9B, 0xAA, 0xA7, 0xEC, 0xBA, 0x63, 0x27, 0x98, 0xCC, 0x4C, + 0x35, 0x48, 0xE2, 0x83, 0xE2, 0xE4, 0x7E, 0xCF, 0xF2, 0x38, + 0xED, 0x1C, 0xE7, 0x17, 0xCB, 0xAC, 0xCC, 0xDA, 0xF5, 0x3B, + 0xD3, 0xC5, 0xFE, 0xB5, 0x04, 0x3A, 0xFC, 0xF7, 0x66, 0x66, + 0x89, 0xB5, 0xAD, 0x1C, 0x18, 0xFE, 0x63, 0xF6, 0x9E, 0x8D, + 0x73, 0xDC, 0x9D, 0x54, 0xDD, 0x02, 0x3B, 0x1D, 0x3F, 0x3D, + 0xD1, 0x4C, 0xCB, 0x3C, 0x09, 0x8E, 0x5A, 0x06, 0x5C, 0x03, + 0x59, 0xF4, 0x7B, 0xF6, 0x39, 0x02, 0x2B, 0xDC, 0x76, 0x82, + 0x2A, 0x33, 0x34, 0x34, 0x57, 0x9F, 0xC8, 0xF0, 0xF0, 0x14, + 0x29, 0x0C, 0x6E, 0x92, 0x36, 0x56, 0x3B, 0xA6, 0x68, 0xF0, + 0x3B, 0x3C, 0xA7, 0x8F, 0x60, 0x85, 0x5D, 0xAD, 0x14, 0xBF, + 0xA7, 0x8B, 0x8E, 0xED, 0x20, 0x0A, 0x64, 0xB2, 0x05, 0xF9, + 0x59, 0x45, 0xC0, 0x97, 0x2D, 0x0D, 0x3A, 0x77, 0xC0, 0xBD, + 0xA2, 0x9F, 0xD4, 0x21, 0xA5, 0x23, 0x7D, 0x1A, 0xE2, 0x4D, + 0x72, 0x44, 0xF6, 0x3E, 0x2A, 0x12, 0xF6, 0xB3, 0xDD, 0x15, + 0x63, 0xCC, 0x7A, 0x5E, 0x60, 0xC2, 0xB0, 0xEE, 0x2C, 0x35, + 0x08, 0x35, 0xE8, 0xA8, 0x08, 0x86, 0xA6, 0xC9, 0xA2, 0x84, + 0xBF, 0x90, 0x14, 0x92, 0x95, 0x6A, 0x08, 0x39, 0x30, 0xB2, + 0x94, 0x3E, 0x0D, 0xE9, 0x3B, 0x28, 0xC7, 0x89, 0x6F, 0xFE, + 0xB9, 0x83, 0x00, 0xE9, 0x9A, 0xA5, 0x47, 0x82, 0xEB, 0xAE, + 0xA8, 0x2F, 0xD4, 0x89, 0xDB, 0x00, 0xC0, 0xA3, 0xE8, 0x79, + 0xC8, 0x5D, 0x90, 0xA5, 0x5F, 0xB6, 0x11, 0x42, 0x99, 0x1E, + 0x7C, 0x9C, 0xB5, 0xA6, 0x90, 0x85, 0xE8, 0xED, 0x14, 0x55, + 0x99, 0x13, 0x10, 0xFD, 0x94, 0xF6, 0x48, 0x86, 0x26, 0x5A, + 0x36, 0x15, 0x53, 0xE5, 0x02, 0xD4, 0x46, 0xA4, 0x3B, 0x97, + 0xB0, 0x65, 0x25, 0xD2, 0x52, 0x08, 0x7D, 0x12, 0x28, 0xC3, + 0x0A, 0x26, 0xF1, 0xCD, 0x10, 0x90, 0xB0, 0x61, 0x6C, 0x31, + 0xEE, 0xC4, 0x79, 0x86, 0x36, 0x94, 0x38, 0x26, 0xD8, 0xA0, + 0x44, 0x90, 0x38, 0x08, 0xCA, 0xCA, 0x8A, 0xEF, 0x6D, 0xFD, + 0xBF, 0xAC, 0xE2, 0x87, 0xC4, 0xD3, 0xB6, 0x68, 0xA5, 0x02, + 0x05, 0xB4, 0xC1, 0xA3, 0x38, 0x60, 0x1B, 0x7F, 0xA5, 0xF2, + 0xA8, 0x17, 0xB0, 0x17, 0x88, 0x0F, 0xE8, 0xE1, 0x25, 0x9C, + 0x4E, 0x82, 0x37, 0x0C, 0xC3, 0xFF, 0x6D, 0x99, 0x9F, 0xB3, + 0x8B, 0x92, 0x2B, 0x96, 0x6B, 0xD3, 0xDF, 0x3F, 0x45, 0xD8, + 0xA2, 0x46, 0xAA, 0x06, 0x7D, 0xA7, 0x57, 0xEC, 0x87, 0x99, + 0xFA, 0x2F, 0x93, 0x6D, 0x65, 0x77, 0xD3, 0x72, 0x2B, 0x3D, + 0xFC, 0x9D, 0x0D, 0x2C, 0x88, 0x75, 0x37, 0x4B, 0x18, 0xA9, + 0x2D, 0xA9, 0xD6, 0xE3, 0x75, 0xFA, 0x29, 0xCE, 0x91, 0x51, + 0x74, 0xF9, 0x71, 0xFB, 0x0B, 0x1F, 0x24, 0x3D, 0xA8, 0xF3, + 0x56, 0x67, 0x7A, 0x13, 0xAA, 0xFF, 0x1C, 0x6D, 0xDD, 0x0F, + 0x14, 0xBC, 0x34, 0x35, 0xE0, 0xAF, 0x7A, 0x55, 0x8C, 0x9A, + 0xE0, 0xA6, 0x35, 0x1F, 0xAB, 0xC3, 0xF7, 0x14, 0x7E, 0xFF, + 0x13, 0x64, 0xAF, 0x1C, 0x11, 0x68, 0x25, 0x4D, 0x07, 0x05, + 0x3F, 0x76, 0x61, 0x29, 0x32, 0xB5, 0xEF, 0xF7, 0x7E, 0xE8, + 0x5C, 0xE2, 0xAA, 0x17, 0x3F, 0x5C, 0x48, 0xFA, 0x05, 0xB0, + 0xDF, 0x1F, 0x2B, 0x9B, 0x25, 0xAA, 0xA8, 0x1F, 0x80, 0xF5, + 0xE5, 0x9C, 0xC2, 0xF5, 0x02, 0x06, 0xC0, 0x00, 0x02, 0x96, + 0xA3, 0xD9, 0x49, 0x25, 0x73, 0x8B, 0x32, 0xB3, 0xD6, 0x68, + 0x6E, 0x3F, 0xDE, 0x58, 0xCB, 0xA8, 0x00, 0x34, 0xB2, 0x7D, + 0x5A, 0x88, 0x7F, 0x1B, 0x3A, 0xA7, 0xD2, 0x56, 0x5B, 0x20, + 0x6F, 0x6B, 0x43, 0x58, 0xC9, 0x48, 0x1F, 0x76, 0xE3, 0xA5, + 0x79, 0x0D, 0xD3, 0xC7, 0xBB, 0x76, 0xE8, 0xC2, 0xD5, 0x77, + 0x2B, 0x39, 0x93, 0x7F, 0x9B, 0x33, 0xB3, 0x0C, 0x12, 0x50, + 0x8A, 0x95, 0xB3, 0x27, 0xCB, 0x49, 0xDB, 0x3C, 0x6A, 0xB9, + 0x06, 0xD8, 0x31, 0x8E, 0x14, 0x6A, 0x3F, 0x5B, 0x03, 0x98, + 0xD8, 0x5C, 0xCA, 0x46, 0x14, 0xDA, 0xAB, 0x51, 0xDF, 0x72, + 0x3A, 0xAD, 0x8E, 0xDA, 0x9A, 0x44, 0x0E, 0x4C, 0xB0, 0x92, + 0xF6, 0x6A, 0x0E, 0xC8, 0x37, 0x0F, 0x93, 0x73, 0xB0, 0xB5, + 0x11, 0x12, 0x3F, 0xCF, 0x29, 0x50, 0x6B, 0xD9, 0x22, 0x2C, + 0x76, 0x71, 0x07, 0xBC, 0x00, 0x10, 0xB8, 0x54, 0x6A, 0x9A, + 0x59, 0x82, 0x4E, 0xE9, 0x02, 0x8B, 0x9C, 0xC9, 0x4B, 0xD3, + 0x7F, 0xAD, 0xA5, 0xF9, 0x7C, 0xB4, 0x00, 0xB4, 0x5A, 0xAF, + 0x23, 0x12, 0xF2, 0x5B, 0xB9, 0x2C, 0xFD, 0x73, 0xB9, 0xFC, + 0x4B, 0x4F, 0xAF, 0xD2, 0x80, 0xC2, 0xF7, 0x15, 0xEC, 0xE1, + 0x4B, 0xEF, 0xA3, 0x99, 0x59, 0x0C, 0xD5, 0x2F, 0x98, 0xF5, + 0x38, 0xD2, 0xFC, 0xF3, 0x82, 0xA7, 0xE0, 0x15, 0x35, 0x27, + 0xCD, 0xCF, 0xCE, 0xA4, 0x65, 0x92, 0x1E, 0x6C, 0x25, 0x8F, + 0xB7, 0x78, 0x15, 0x5B, 0x7A, 0x9D, 0xB0, 0xEC, 0xDE, 0x0A, + 0xF5, 0x0B, 0xCD, 0xD4, 0xF6, 0xC4, 0xD5, 0xA9, 0xC1, 0x4E, + 0xE0, 0xA2, 0x8B, 0xC4, 0x43, 0x97, 0xAC, 0xC9, 0x41, 0xF2, + 0x5A, 0x36, 0xD1, 0xD6, 0x7E, 0xC2, 0xD3, 0x06, 0xCB, 0x78, + 0x47, 0x78, 0x05, 0x35, 0x61, 0xE6, 0xD4, 0xDC, 0x65, 0x75, + 0x63, 0x5B, 0x71, 0xD7, 0x3C, 0x27, 0x9B, 0x26, 0x70, 0xAB, + 0x4D, 0x97, 0xE5, 0x83, 0x7C, 0xEB, 0x5B, 0x8F, 0xD6, 0xBC, + 0x84, 0x9D, 0xF8, 0x08, 0x65, 0x10, 0x6E, 0x48, 0xBE, 0xD2, + 0x86, 0xC0, 0xEB, 0x08, 0x27, 0x38, 0xF8, 0x6E, 0x5D, 0xBA, + 0x5B, 0xC6, 0x78, 0xED, 0xB1, 0x8D, 0x57, 0xFC, 0xA4, 0xDD, + 0xF3, 0xDF, 0xB9, 0x36, 0x92, 0xB8, 0x00, 0x06, 0x5F, 0xAD, + 0x04, 0xE3, 0x8D, 0x64, 0x64, 0x7A, 0xE0, 0x57, 0x4C, 0x68, + 0x56, 0xDC, 0x1F, 0x93, 0xA5, 0xD7, 0xEE, 0xFC, 0x8A, 0xB9, + 0x26, 0x99, 0xC5, 0x1A, 0x63, 0xD6, 0x9B, 0xBC, 0xA2, 0xF3, + 0xE1, 0x84, 0xDC, 0xFB, 0x0C, 0x4F, 0xB4, 0x0D, 0x75, 0x8B, + 0xDD, 0xB6, 0x76, 0x2C, 0x33, 0x46, 0xDD, 0x94, 0xE0, 0x8C, + 0x37, 0x03, 0x14, 0x44, 0xC2, 0x0F, 0x6C, 0x03, 0xA6, 0x39, + 0x1E, 0xB5, 0x00, 0x83, 0xC6, 0x59, 0x81, 0xD6, 0x11, 0x3A, + 0x22, 0x6F, 0x74, 0x1C, 0x8B, 0x4C, 0xF3, 0x1C, 0x59, 0x2F, + 0x19, 0x18, 0xB5, 0x20, 0x24, 0x9C, 0x14, 0x45, 0x7E, 0x98, + 0xFB, 0x71, 0xC4, 0xCC, 0x19, 0x99, 0x28, 0x9E, 0x63, 0xC2, + 0xD6, 0x0D, 0x47, 0x2A, 0x3B, 0x36, 0xB7, 0x8C, 0xF1, 0x98, + 0x33, 0x96, 0x9D, 0x09, 0x99, 0x74, 0xE3, 0x41, 0xCD, 0x67, + 0xCE, 0x11, 0xBA, 0x35, 0xE3, 0x44, 0x3D, 0xE0, 0xB9, 0x53, + 0x79, 0x58, 0x0F, 0xC5, 0x98, 0xCC, 0xAB, 0xF8, 0x37, 0x5C, + 0x96, 0xCE, 0xB5, 0xDA, 0xB5, 0x05, 0x79, 0xD6, 0x96, 0x82, + 0xCA, 0x3F, 0x2C, 0x8A, 0x44, 0xCE, 0xCE, 0x99, 0xA2, 0x77, + 0x9E, 0xE0, 0xFE, 0x58, 0x97, 0x5A, 0xDA, 0x8B, 0xB8, 0x0A, + 0xA2, 0x3F, 0x26, 0x00, 0x5C, 0xAC, 0xD8, 0x33, 0x95, 0xBF, + 0x43, 0x0D, 0x09, 0xB5, 0x9A, 0x89, 0x4C, 0x46, 0x83, 0xAF, + 0xD1, 0xD3, 0x61, 0x33, 0xA1, 0x19, 0xD3, 0xD6, 0x81, 0xB0, + 0xDA, 0x55, 0x4B, 0xD3, +}; + +const uint32_t gphDnldNfc_DlSeqSz = sizeof(gphDnldNfc_DlSequence); diff --git a/drivers/nfc/snvm/Kconfig b/drivers/nfc/snvm/Kconfig new file mode 100644 index 000000000000..3678e4856fde --- /dev/null +++ b/drivers/nfc/snvm/Kconfig @@ -0,0 +1,41 @@ +# +# SEC STAR platform devices +# + +config STAR_MEMORY_LEAK + bool "memory leak test in sec-star" + help + memory leak test in sec-star. + All allocated memory are listed in array. + +config STAR_K250A_LEGO + tristate "S.LSI k250a driver" + default n + help + S.LSI k250a driver except ISO7816 protocol layer + This driver provides support for S.LSI k250a product. + +config SEC_SNVM_WAKELOCK_METHOD + int "snvm wakelock method" + default 0 + help + Different functions must be used depending on the kernel version + for wakelock initialization. + 0 - auto selection + 1 - wakeup_source_init + 2 - wakeup_source_register + +config SEC_SNVM_PLATFORM_DRV + bool "snvm platform driver" + default n + help + make snvm driver to platfrom driver + disable i2c node + +config SEC_SNVM_I2C_CLOCK_CONTROL + bool "snvm i2c clock control" + default n + help + control the I2C clocks + now only for MTK project + diff --git a/drivers/nfc/snvm/Makefile b/drivers/nfc/snvm/Makefile new file mode 100644 index 000000000000..6834a524eb36 --- /dev/null +++ b/drivers/nfc/snvm/Makefile @@ -0,0 +1,24 @@ +# +# Makefile for Samsung Secure Element +# +ifeq ($(CONFIG_STAR_K250A_LEGO), m) +obj-$(CONFIG_STAR_K250A_LEGO) = snvm.o + +snvm-y += sec_star.o \ + protocol/ese_data.o \ + protocol/ese_memory.o \ + protocol/ese_iso7816_t1.o \ + hal/ese_i2c.o \ + hal/ese_spi.o \ + hal/ese_hal.o \ + sec_k250a.o +else +obj-$(CONFIG_STAR_K250A_LEGO) += sec_star.o \ + protocol/ese_data.o \ + protocol/ese_memory.o \ + protocol/ese_iso7816_t1.o \ + hal/ese_i2c.o \ + hal/ese_spi.o \ + hal/ese_hal.o \ + sec_k250a.o +endif diff --git a/drivers/nfc/snvm/hal/ese_hal.c b/drivers/nfc/snvm/hal/ese_hal.c new file mode 100644 index 000000000000..d5ef8817b22e --- /dev/null +++ b/drivers/nfc/snvm/hal/ese_hal.c @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2020 Samsung Electronics. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; + * + */ +#include +#include +#include "ese_i2c.h" +#include "ese_spi.h" +#include "ese_hal.h" + +#undef ENABLE_HAL_LOG + +struct ese_hal_s { + void *client; + int (*send)(void *ctx, unsigned char *buf, unsigned int size); + int (*receive)(void *ctx, unsigned char *buf, unsigned int size); +}; + +int ese_hal_send(void *ctx, unsigned char *buf, unsigned int size) +{ + struct ese_hal_s *hal = (struct ese_hal_s *)ctx; + + if (hal == NULL || hal->client == NULL || hal->send == NULL + || buf == NULL || size == 0) { + return -1; + } +#ifdef ENABLE_HAL_LOG + print_hex_dump(KERN_DEBUG, "[star-hal] send : ", DUMP_PREFIX_NONE, 16, 1, buf, size, 0); +#endif + if (hal->send(hal->client, buf, size) < 0) { + return -1; + } + + return (int)size; +} + +int ese_hal_receive(void *ctx, unsigned char *buf, unsigned int size) +{ + struct ese_hal_s *hal = (struct ese_hal_s *)ctx; + + if (hal == NULL || hal->client == NULL || hal->receive == NULL + || buf == NULL || size == 0) { + return -1; + } + + if (hal->receive(hal->client, buf, size) < 0) { + return -1; + } +#ifdef ENABLE_HAL_LOG + print_hex_dump(KERN_DEBUG, "[star-hal] recv : ", DUMP_PREFIX_NONE, 16, 1, buf, size, 0); +#endif + return (int)size; +} + +void *ese_hal_init(enum hal_type_e type, void *client) +{ + struct ese_hal_s *hal = NULL; + + if (client == NULL) { + return NULL; + } + + hal = kzalloc(sizeof(struct ese_hal_s), GFP_KERNEL); + if (hal == NULL) { + return NULL; + } + + switch(type) { + case ESE_HAL_I2C: + hal->client = client; + hal->send = ese_i2c_send; + hal->receive = ese_i2c_receive; + break; + case ESE_HAL_SPI: + hal->client = client; + hal->send = ese_spi_send; + hal->receive = ese_spi_receive; + break; + default: + kfree(hal); + return NULL; + } + + return hal; +} + +void ese_hal_release(void *ctx) +{ + if (ctx != NULL) { + kfree(ctx); + } +} diff --git a/drivers/nfc/snvm/hal/ese_hal.h b/drivers/nfc/snvm/hal/ese_hal.h new file mode 100644 index 000000000000..1f25b971bfb1 --- /dev/null +++ b/drivers/nfc/snvm/hal/ese_hal.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 Samsung Electronics. 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 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __ESE_HAL__H +#define __ESE_HAL__H + +enum hal_type_e { + ESE_HAL_I2C = 0, + ESE_HAL_SPI, +}; + +void *ese_hal_init(enum hal_type_e type, void *client); +void ese_hal_release(void *ctx); +int ese_hal_send(void *ctx, unsigned char *buf, unsigned int size); +int ese_hal_receive(void *ctx, unsigned char *buf, unsigned int size); + +#endif diff --git a/drivers/nfc/snvm/hal/ese_i2c.c b/drivers/nfc/snvm/hal/ese_i2c.c new file mode 100644 index 000000000000..7df203f00377 --- /dev/null +++ b/drivers/nfc/snvm/hal/ese_i2c.c @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 Samsung Electronics. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; + * + */ + +#include +#include +#include + +#include "ese_i2c.h" + +#define ERR(msg...) pr_err("[star-i2c] : " msg) +#define INFO(msg...) pr_info("[star-i2c] : " msg) + +int ese_i2c_send(void *ctx, unsigned char *buf, unsigned int size) +{ + struct i2c_client *client = ctx; + int ret = 0; + + ret = i2c_master_send(client, buf, size); + if (ret < 0) { + ERR("failed to send data %d", ret); + return ret; + } + + return (int)size; +} + +int ese_i2c_receive(void *ctx, unsigned char *buf, unsigned int size) +{ + struct i2c_client *client = ctx; + int ret = 0; + + ret = i2c_master_recv(client, (void *)buf, size); + if (ret < 0) { + ERR("failed to recv data %d", ret); + return ret; + } + + return (int)size; +} diff --git a/drivers/nfc/snvm/hal/ese_i2c.h b/drivers/nfc/snvm/hal/ese_i2c.h new file mode 100644 index 000000000000..eb77f7dbf7fa --- /dev/null +++ b/drivers/nfc/snvm/hal/ese_i2c.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2020 Samsung Electronics. 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 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __ESE_I2C__H +#define __ESE_I2C__H + +int ese_i2c_send(void *ctx, unsigned char *buf, unsigned int size); +int ese_i2c_receive(void *ctx, unsigned char *buf, unsigned int size); + +#endif diff --git a/drivers/nfc/snvm/hal/ese_spi.c b/drivers/nfc/snvm/hal/ese_spi.c new file mode 100644 index 000000000000..a623ce156177 --- /dev/null +++ b/drivers/nfc/snvm/hal/ese_spi.c @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 Samsung Electronics. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; + * + */ + +#include +#include +#include + +#include "ese_spi.h" + +#define ERR(msg...) pr_err("[star-spi] : " msg) +#define INFO(msg...) pr_info("[star-spi] : " msg) + +int ese_spi_send(void *ctx, unsigned char *buf, unsigned int size) +{ + struct spi_device *spidev = ctx; + int ret = 0; + + ret = spi_write(spidev, buf, size); + if (ret < 0) { + ERR("failed to write data %d", ret); + return ret; + } + + return (int)size; +} + +int ese_spi_receive(void *ctx, unsigned char *buf, unsigned int size) +{ + struct spi_device *spidev = ctx; + int ret = 0; + + ret = spi_read(spidev, (void *)buf, size); + if (ret < 0) { + ERR("failed to read data %d", ret); + return ret; + } + + return (int)size; +} diff --git a/drivers/nfc/snvm/hal/ese_spi.h b/drivers/nfc/snvm/hal/ese_spi.h new file mode 100644 index 000000000000..12789cc74b7f --- /dev/null +++ b/drivers/nfc/snvm/hal/ese_spi.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2020 Samsung Electronics. 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 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __ESE_SPI__H +#define __ESE_SPI__H + +int ese_spi_send(void *ctx, unsigned char *buf, unsigned int size); +int ese_spi_receive(void *ctx, unsigned char *buf, unsigned int size); + +#endif diff --git a/drivers/nfc/snvm/protocol/ese_data.c b/drivers/nfc/snvm/protocol/ese_data.c new file mode 100644 index 000000000000..bb1498db412d --- /dev/null +++ b/drivers/nfc/snvm/protocol/ese_data.c @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2020 Samsung Electronics. 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 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include + +#include "ese_log.h" +#include "ese_memory.h" +#include "ese_data.h" + +static int32_t _ese_data_get_from_list(data_list_t *head, uint8_t *data, uint32_t *data_size) +{ + data_list_t *cur_node; + uint32_t offset = 0; + + if (head == NULL || data == NULL) { + LOG_E("invalid header and data"); + return -1; + } + + cur_node = head->next; + while (cur_node != NULL) { + memcpy((data + offset), cur_node->packet.data, cur_node->packet.size); + offset += cur_node->packet.size; + cur_node = cur_node->next; + } + + *data_size = offset; + return 0; +} + +static int32_t _ese_data_delete_list(data_list_t *head) +{ + data_list_t *cur, *next; + + if (head == NULL) { + return -1; + } + + cur = head->next; + while (cur != NULL) { + next = cur->next; + ESE_FREE(cur->packet.data); + ESE_FREE(cur); + cur = NULL; + cur = next; + } + + head->next = NULL; + head->packet.data = NULL; + head->packet.size = 0; + return 0; +} + +ESE_STATUS ese_data_init(data_list_t *head) +{ + head->next = NULL; + head->packet.data = NULL; + head->packet.size = 0; + return ESESTATUS_SUCCESS; +} + +ESE_STATUS ese_data_store(data_list_t *head, uint8_t *data, uint32_t data_size, uint8_t copy) +{ + data_list_t *new_node = NULL; + data_list_t *cur_node = NULL; + + new_node = ESE_MALLOC(sizeof(data_list_t)); + if (new_node == NULL) { + return ESESTATUS_MEMORY_ALLOCATION_FAIL; + } + + new_node->next = NULL; + new_node->packet.size = data_size; + if (copy) { + new_node->packet.data = ESE_MALLOC(data_size); + if (new_node->packet.data == NULL) { + ESE_FREE(new_node); + return ESESTATUS_MEMORY_ALLOCATION_FAIL; + } + memcpy(new_node->packet.data, data, data_size); + } else { + new_node->packet.data = data; + } + + cur_node = head; + while (cur_node->next != NULL) { + cur_node = cur_node->next; + } + + cur_node->next = new_node; + head->packet.size += data_size; + return ESESTATUS_SUCCESS; +} + +ESE_STATUS ese_data_get(data_list_t *head, uint8_t **data, uint32_t *data_size) +{ + uint32_t total_size = 0; + uint8_t* tmp_buf = NULL; + + if (data != NULL && data_size != NULL) { + if (head->packet.size == 0) { + *data = NULL; + *data_size = 0; + return ESESTATUS_INVALID_BUFFER; + } + + tmp_buf = ESE_MALLOC(head->packet.size); + if (tmp_buf == NULL) { + LOG_E("failed to allocate memory"); + _ese_data_delete_list(head); + return ESESTATUS_MEMORY_ALLOCATION_FAIL; + } + + if (_ese_data_get_from_list(head, tmp_buf, &total_size) != 0) { + LOG_E("failed to get data from list"); + ESE_FREE(tmp_buf); + _ese_data_delete_list(head); + return ESESTATUS_INVALID_BUFFER; + } + + if (total_size != head->packet.size) { + LOG_E("mismatch size [%d, %d]", total_size, head->packet.size); + ESE_FREE(tmp_buf); + _ese_data_delete_list(head); + return ESESTATUS_INVALID_BUFFER; + } + *data = tmp_buf; + *data_size = total_size; + } + + _ese_data_delete_list(head); + return ESESTATUS_SUCCESS; +} + +ESE_STATUS ese_data_delete(data_list_t *head) +{ + data_list_t *cur, *next; + + if (head == NULL) { + return -1; + } + + cur = head->next; + while (cur != NULL) { + next = cur->next; + ESE_FREE(cur->packet.data); + ESE_FREE(cur); + cur = NULL; + cur = next; + } + + head->next = NULL; + head->packet.data = NULL; + head->packet.size = 0; + return 0; +} diff --git a/drivers/nfc/snvm/protocol/ese_data.h b/drivers/nfc/snvm/protocol/ese_data.h new file mode 100644 index 000000000000..7602f3a4955d --- /dev/null +++ b/drivers/nfc/snvm/protocol/ese_data.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2020 Samsung Electronics. 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 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _ESE_DATA_H_ +#define _ESE_DATA_H_ + +#include "ese_error.h" + +typedef struct data_packet_s { + uint8_t *data; + uint32_t size; +} data_packet_t; + +typedef struct data_list_s { + data_packet_t packet; + struct data_list_s *next; +} data_list_t; + +ESE_STATUS ese_data_init(data_list_t *head); +ESE_STATUS ese_data_store(data_list_t *head, uint8_t *data, uint32_t data_size, uint8_t copy); +ESE_STATUS ese_data_get(data_list_t *head, uint8_t **data, uint32_t *data_size); +ESE_STATUS ese_data_delete(data_list_t *head); + +#endif diff --git a/drivers/nfc/snvm/protocol/ese_error.h b/drivers/nfc/snvm/protocol/ese_error.h new file mode 100644 index 000000000000..75de69be36f7 --- /dev/null +++ b/drivers/nfc/snvm/protocol/ese_error.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 Samsung Electronics. 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 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __ESE_ERROR__H +#define __ESE_ERROR__H + +enum ESESTATUS { + ESESTATUS_SUCCESS = (0x0000), + ESESTATUS_FAILED = (0x0001), + ESESTATUS_NOT_INITIALISED = (0x0002), + ESESTATUS_ALREADY_INITIALISED = (0x0003), + ESESTATUS_INVALID_PARAMETER = (0x0004), + ESESTATUS_INVALID_DEVICE = (0x0005), + ESESTATUS_INVALID_STATE = (0x0006), + ESESTATUS_FEATURE_NOT_SUPPORTED = (0x0007), + + ESESTATUS_INVALID_BUFFER = (0x0030), + ESESTATUS_NOT_ENOUGH_MEMORY = (0x0031), + ESESTATUS_MEMORY_ALLOCATION_FAIL = (0x0032), + + ESESTATUS_INVALID_CLA = (0x0050), + ESESTATUS_INVALID_CPDU_TYPE = (0x0051), + ESESTATUS_INVALID_LE_TYPE = (0x0052), + ESESTATUS_INVALID_FORMAT = (0x0053), + ESESTATUS_INVALID_FRAME = (0x0054), + + ESESTATUS_SEND_FAILED = (0x0060), + ESESTATUS_RECEIVE_FAILED = (0x0061), + ESESTATUS_RESPONSE_TIMEOUT = (0x0062), + ESESTATUS_INVALID_SEND_LENGTH = (0x0063), + ESESTATUS_INVALID_RECEIVE_LENGTH = (0x0064), +}; + +typedef uint32_t ESE_STATUS; +#endif diff --git a/drivers/nfc/snvm/protocol/ese_iso7816_t1.c b/drivers/nfc/snvm/protocol/ese_iso7816_t1.c new file mode 100644 index 000000000000..acfc3903113f --- /dev/null +++ b/drivers/nfc/snvm/protocol/ese_iso7816_t1.c @@ -0,0 +1,749 @@ +/* + * Copyright (C) 2020 Samsung Electronics. 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 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include + +#include "../hal/ese_hal.h" +#include "ese_log.h" +#include "ese_memory.h" +#include "ese_error.h" +#include "ese_protocol.h" +#include "ese_iso7816_t1.h" + +static void iso7816_t1_print_buffer(const char *buf_tag, uint8_t *buffer, uint32_t buffer_size) +{ + print_hex_dump(KERN_DEBUG, buf_tag, DUMP_PREFIX_NONE, 16, 1, buffer, buffer_size, 0); +} + +static uint8_t iso7816_t1_compute_lrc(uint8_t *data, uint32_t offset, uint32_t size) +{ + uint32_t lrc = 0, i = 0; + + for (i = offset; i < offset + size; i++) { + lrc = lrc ^ data[i]; + } + + return (uint8_t) lrc; +} + +static ESE_STATUS iso7816_t1_check_lrc(uint8_t* data, uint32_t data_size) +{ + uint8_t calc_crc = 0; + uint8_t recv_crc = 0; + + recv_crc = data[data_size - 1]; + calc_crc = iso7816_t1_compute_lrc(data, 0, (data_size - 1)); + if (recv_crc != calc_crc) { + return ESESTATUS_FAILED; + } + return ESESTATUS_SUCCESS; +} + +static void iso7816_t1_reset_params(iso7816_t1_t *protocol) +{ + protocol->state = STATE_IDLE; + protocol->last_rx.type = FRAME_INVALID; + protocol->next_tx.type = FRAME_INVALID; + protocol->next_tx.iframe.data = NULL; + protocol->last_tx.type = FRAME_INVALID; + protocol->last_tx.iframe.data = NULL; + protocol->next_tx.iframe.seq_no = 0x01; + protocol->last_rx.iframe.seq_no = 0x01; + protocol->last_tx.iframe.seq_no = 0x01; + protocol->next_tx.sframe.data = NULL; + protocol->next_tx.sframe.send_size = 0; + protocol->recovery_counter = 0; + protocol->timeout_counter = 0; + protocol->wtx_counter = 0; + protocol->last_frame = FRAME_UNKNOWN; + protocol->rnack_counter = 0; +} + +static void iso7816_t1_set_iframe(iso7816_t1_t *protocol) +{ + protocol->next_tx.iframe.offset = 0; + protocol->next_tx.type = FRAME_IFRAME; + protocol->next_tx.iframe.seq_no = protocol->last_tx.iframe.seq_no ^ 1; + protocol->state = STATE_SEND_IFRAME; + + if (protocol->next_tx.iframe.total_size > PROTOCOL_SEND_SIZE) { + protocol->next_tx.iframe.chain = 1; + protocol->next_tx.iframe.send_size = PROTOCOL_SEND_SIZE; + protocol->next_tx.iframe.total_size = + protocol->next_tx.iframe.total_size - PROTOCOL_SEND_SIZE; + } else { + protocol->next_tx.iframe.send_size = protocol->next_tx.iframe.total_size; + protocol->next_tx.iframe.chain = 0; + } +} + +static void iso7816_t1_next_iframe(iso7816_t1_t *protocol) +{ + protocol->next_tx.type = FRAME_IFRAME; + protocol->state = STATE_SEND_IFRAME; + + protocol->next_tx.iframe.seq_no = protocol->last_tx.iframe.seq_no ^ 1; + protocol->next_tx.iframe.offset = + protocol->last_tx.iframe.offset + PROTOCOL_SEND_SIZE; + protocol->next_tx.iframe.data = protocol->last_tx.iframe.data; + + if (protocol->last_tx.iframe.total_size > PROTOCOL_SEND_SIZE) { + protocol->next_tx.iframe.chain = 1; + protocol->next_tx.iframe.send_size = PROTOCOL_SEND_SIZE; + protocol->next_tx.iframe.total_size = + protocol->last_tx.iframe.total_size - PROTOCOL_SEND_SIZE; + } else { + protocol->next_tx.iframe.chain = 0; + protocol->next_tx.iframe.send_size = protocol->last_tx.iframe.total_size; + } +} + +static void iso7816_t1_finish_recovery(iso7816_t1_t *protocol) +{ + /* + * ToDo : need to reset interface + */ + LOG_E("finish recovery, set protocol state to IDLE"); + protocol->state = STATE_IDLE; + protocol->recovery_counter = PROTOCOL_ZERO; +} + +static ESE_STATUS iso7816_t1_send_sframe(iso7816_t1_t *protocol) +{ + uint32_t frame_size = (PROTOCOL_HEADER_SIZE + PROTOCOL_LRC_SIZE); + uint8_t buf[PROTOCOL_HEADER_SIZE + PROTOCOL_LRC_SIZE + SFRAME_MAX_INF_SIZE]; + uint8_t pcb_byte = 0; + + protocol->last_frame = FRAME_SFRAME; + if ((protocol->next_tx.sframe.type & 0x20 )== 0) { + buf[2] = 0x00; + + pcb_byte = PROTOCOL_S_BLOCK_REQ | protocol->next_tx.sframe.type; + + if (protocol->next_tx.sframe.data && protocol->next_tx.sframe.send_size > 0) { + if (protocol->next_tx.sframe.send_size > 4) + protocol->next_tx.sframe.send_size = 4; + buf[2] = protocol->next_tx.sframe.send_size; + memcpy(buf + PROTOCOL_HEADER_SIZE, + protocol->next_tx.sframe.data, protocol->next_tx.sframe.send_size); + frame_size += protocol->next_tx.sframe.send_size; + protocol->next_tx.sframe.send_size = 0; + } + } else if ((protocol->next_tx.sframe.type & 0x20) == 0x20) { + buf[2] = 0x00; + + pcb_byte = PROTOCOL_S_BLOCK_RSP | (protocol->next_tx.sframe.type & 0x0F); + + if (protocol->next_tx.sframe.data && protocol->next_tx.sframe.send_size > 0) { + if (protocol->next_tx.sframe.send_size > 4) + protocol->next_tx.sframe.send_size = 4; + buf[2] = protocol->next_tx.sframe.send_size; + memcpy(buf + PROTOCOL_HEADER_SIZE, + protocol->next_tx.sframe.data, protocol->next_tx.sframe.send_size); + frame_size += protocol->next_tx.sframe.send_size; + protocol->next_tx.sframe.send_size = 0; + } + } else { + LOG_E("Invalid SFrame"); + } + + buf[0] = protocol->send_address; + buf[1] = pcb_byte; + buf[frame_size - 1] = iso7816_t1_compute_lrc(buf, 0, (frame_size - 1)); + + iso7816_t1_print_buffer("[star-protocol] S_SFRAME: ", buf, frame_size); + if ((uint32_t)ese_hal_send(protocol->hal, buf, frame_size) != frame_size) { + return ESESTATUS_SEND_FAILED; + } + return ESESTATUS_SUCCESS; +} + +static ESE_STATUS iso7816_t1_send_rframe(iso7816_t1_t *protocol) +{ + uint8_t buf[RFRAME_PROTOCOL_SIZE] = {0x00, 0x80, 0x00, 0x00}; + + if (protocol->next_tx.rframe.error != RFRAME_ERROR_NO) { + buf[1] |= protocol->next_tx.rframe.error; + } else { + buf[1] = 0x80; + protocol->last_frame = FRAME_RFRAME; + } + + buf[0] = protocol->send_address; + buf[1] |= ((protocol->last_rx.iframe.seq_no ^ 1) << 4); + buf[2] = 0x00; + buf[3] = iso7816_t1_compute_lrc(buf, 0x00, RFRAME_PROTOCOL_SIZE - 1); + + iso7816_t1_print_buffer("[star-protocol] S_RFRAME: ", buf, RFRAME_PROTOCOL_SIZE); + if ((uint32_t)ese_hal_send(protocol->hal, buf, RFRAME_PROTOCOL_SIZE) != RFRAME_PROTOCOL_SIZE) { + return ESESTATUS_SEND_FAILED; + } + + return ESESTATUS_SUCCESS; +} + +static ESE_STATUS iso7816_t1_send_iframe(iso7816_t1_t *protocol) +{ + uint32_t frame_size = 0; + uint8_t pcb_byte = 0; + + if (protocol->proc_buf == NULL) { + LOG_E("Process Buffer is NULL, INVALID"); + return ESESTATUS_NOT_ENOUGH_MEMORY; + } + + if (protocol->next_tx.iframe.send_size == 0) { + LOG_E("Iframe Len is 0, INVALID"); + return ESESTATUS_INVALID_SEND_LENGTH; + } + + if (protocol->next_tx.iframe.data == NULL) { + LOG_E("Iframe Buffer is NULL, INVALID"); + return ESESTATUS_NOT_ENOUGH_MEMORY; + } + + protocol->last_frame = FRAME_IFRAME; + frame_size = (protocol->next_tx.iframe.send_size + PROTOCOL_HEADER_SIZE + PROTOCOL_LRC_SIZE); + + (protocol->proc_buf)[0] = protocol->send_address; + if (protocol->next_tx.iframe.chain) { + pcb_byte |= PROTOCOL_CHAINING; + } + + pcb_byte |= (protocol->next_tx.iframe.seq_no << 6); + (protocol->proc_buf)[1] = pcb_byte; + (protocol->proc_buf)[2] = protocol->next_tx.iframe.send_size; + memcpy(protocol->proc_buf + 3, + protocol->next_tx.iframe.data + protocol->next_tx.iframe.offset, + protocol->next_tx.iframe.send_size); + (protocol->proc_buf)[frame_size - 1] = iso7816_t1_compute_lrc(protocol->proc_buf, 0, (frame_size - 1)); + + iso7816_t1_print_buffer("[star-protocol] S_IFRAME: ", protocol->proc_buf, PROTOCOL_HEADER_SIZE); + if ((uint32_t)ese_hal_send(protocol->hal, protocol->proc_buf, frame_size) != frame_size) { + return ESESTATUS_SEND_FAILED; + } + + return ESESTATUS_SUCCESS; +} + +static ESE_STATUS iso7816_t1_decode_frame(iso7816_t1_t *protocol, uint8_t* data, uint32_t data_size) +{ + ESE_STATUS status = ESESTATUS_FAILED; + uint8_t pcb_byte; + int32_t frame_type; + uint32_t wait_time = 0; + + pcb_byte = data[PROTOCOL_PCB_OFFSET]; + + if (ESE_BIT(pcb_byte, 7) == 0x00) { + iso7816_t1_print_buffer("[star-protocol] R_IFRAME: ", data, PROTOCOL_HEADER_SIZE); + protocol->wtx_counter = 0; + protocol->last_rx.type = FRAME_IFRAME; + if (protocol->last_rx.iframe.seq_no != ESE_BIT(pcb_byte, 6)) { + protocol->recovery_counter = 0; + protocol->last_rx.iframe.seq_no = 0x00; + protocol->last_rx.iframe.seq_no |= ESE_BIT(pcb_byte, 6); + + if (ESE_BIT(pcb_byte, 5)) { + protocol->last_rx.iframe.chain = 1; + protocol->next_tx.type = FRAME_RFRAME; + protocol->next_tx.rframe.error = RFRAME_ERROR_NO; + protocol->state = STATE_SEND_RFRAME; + } else { + protocol->last_rx.iframe.chain = 0; + protocol->state = STATE_IDLE; + } + + status = ese_data_store(&(protocol->recv_data), &data[3], data_size - 4, 1); + if (status != ESESTATUS_SUCCESS) { + return status; + } + } else { + if (protocol->recovery_counter < PROTOCOL_FRAME_RETRY_COUNT) { + protocol->next_tx.type = FRAME_RFRAME; + protocol->next_tx.rframe.error = RFRAME_ERROR_OTHER; + protocol->state = STATE_SEND_RFRAME; + protocol->recovery_counter++; + } else { + iso7816_t1_finish_recovery(protocol); + } + } + } else if ((ESE_BIT(pcb_byte, 7) == 0x01) && (ESE_BIT(pcb_byte, 6) == 0x00)) { + iso7816_t1_print_buffer("[star-protocol] R_RFRAME: ", data, data_size); + protocol->wtx_counter = 0; + protocol->last_rx.type = FRAME_RFRAME; + protocol->last_rx.rframe.seq_no = ESE_BIT(pcb_byte, 4); + + if ((ESE_BIT(pcb_byte, 0) == 0x00) && (ESE_BIT(pcb_byte, 1) == 0x00)) { + protocol->last_rx.rframe.error = RFRAME_ERROR_NO; + protocol->recovery_counter = 0; + if (protocol->last_rx.rframe.seq_no != + protocol->last_tx.iframe.seq_no) { + protocol->state = STATE_SEND_IFRAME; + iso7816_t1_next_iframe(protocol); + } else { + /* + * ToDo : error handling. + */ + } + } else if (((ESE_BIT(pcb_byte, 0) == 0x01) && (ESE_BIT(pcb_byte, 1) == 0x00)) || + ((ESE_BIT(pcb_byte, 0) == 0x00) && (ESE_BIT(pcb_byte, 1) == 0x01))) { + if ((ESE_BIT(pcb_byte, 0) == 0x00) && (ESE_BIT(pcb_byte, 1) == 0x01)) + protocol->last_rx.rframe.error = RFRAME_ERROR_OTHER; + else + protocol->last_rx.rframe.error = RFRAME_ERROR_PARITY; + if (protocol->recovery_counter < PROTOCOL_FRAME_RETRY_COUNT) { + if (protocol->last_tx.type == FRAME_IFRAME) { + memcpy((uint8_t *)&protocol->next_tx, + (uint8_t *)&protocol->last_tx, sizeof(frame_info_t)); + protocol->state = STATE_SEND_IFRAME; + protocol->next_tx.type = FRAME_IFRAME; + } else if (protocol->last_tx.type == FRAME_RFRAME) { + if ((protocol->last_rx.rframe.seq_no == + protocol->last_tx.iframe.seq_no) && + (protocol->last_frame == FRAME_IFRAME)) { + /* + * Usecase to reach the below case: + * I-frame sent first, followed by R-NACK and we receive a R-NACK with + * last sent I-frame sequence number + */ + memcpy((uint8_t *)&protocol->next_tx, + (uint8_t *)&protocol->last_tx, sizeof(frame_info_t)); + protocol->state = STATE_SEND_IFRAME; + protocol->next_tx.type = FRAME_IFRAME; + } else if ((protocol->last_rx.rframe.seq_no != + protocol->last_tx.iframe.seq_no) && + (protocol->last_frame == FRAME_RFRAME)) { + /* + * Usecase to reach the below case: + * R-frame sent first, followed by R-NACK and we receive a R-NACK with + * next expected I-frame sequence number + */ + protocol->next_tx.type = FRAME_RFRAME; + protocol->next_tx.rframe.error = RFRAME_ERROR_NO; + protocol->state = STATE_SEND_RFRAME; + } else { + /* + * Usecase to reach the below case: + * I-frame sent first, followed by R-NACK and we receive a R-NACK with + * next expected I-frame sequence number + all the other unexpected + * scenarios + */ + protocol->next_tx.type = FRAME_RFRAME; + protocol->next_tx.rframe.error = RFRAME_ERROR_OTHER; + protocol->state = STATE_SEND_RFRAME; + } + } else if (protocol->last_tx.type == FRAME_SFRAME) { + memcpy((uint8_t *)&protocol->next_tx, + (uint8_t *)&protocol->last_tx, sizeof(frame_info_t)); + } + protocol->recovery_counter++; + } else { + iso7816_t1_finish_recovery(protocol); + } + } else if ((ESE_BIT(pcb_byte, 0) == 0x01) && (ESE_BIT(pcb_byte, 1) == 0x01)) { + if (protocol->recovery_counter < PROTOCOL_FRAME_RETRY_COUNT) { + protocol->last_rx.rframe.error = RFRAME_ERROR_SOF_MISSED; + memcpy((uint8_t *)&protocol->next_tx, + (uint8_t *)&protocol->last_tx, sizeof(frame_info_t)); + protocol->recovery_counter++; + } else { + iso7816_t1_finish_recovery(protocol); + } + } else { + if (protocol->recovery_counter < PROTOCOL_FRAME_RETRY_COUNT) { + protocol->last_rx.rframe.error = RFRAME_ERROR_UNDEFINED; + memcpy((uint8_t *)&protocol->next_tx, + (uint8_t *)&protocol->last_tx, sizeof(frame_info_t)); + protocol->recovery_counter++; + } else { + iso7816_t1_finish_recovery(protocol); + } + } + } else if ((ESE_BIT(pcb_byte, 7) == 0x01) && (ESE_BIT(pcb_byte, 6) == 0x01)) { + iso7816_t1_print_buffer("[star-protocol] R_SFRAME: ", data, data_size); + frame_type = (int32_t)(pcb_byte & 0x3F); + protocol->last_rx.type = FRAME_SFRAME; + if (frame_type != SFRAME_WTX_REQ) { + protocol->wtx_counter = 0; + } + switch (frame_type) { + case SFRAME_RESYNCH_REQ: + iso7816_t1_reset_params(protocol); + protocol->last_rx.sframe.type = SFRAME_RESYNCH_REQ; + protocol->next_tx.type = FRAME_SFRAME; + protocol->next_tx.sframe.type = SFRAME_RESYNCH_RSP; + protocol->state = STATE_SEND_SFRAME; + protocol->next_tx.sframe.data = NULL; + protocol->next_tx.sframe.send_size = 0; + break; + case SFRAME_RESYNCH_RSP: + protocol->last_rx.sframe.type = SFRAME_RESYNCH_RSP; + protocol->next_tx.type = FRAME_UNKNOWN; + protocol->state = STATE_IDLE; + break; + case SFRAME_ABORT_REQ: + protocol->last_rx.sframe.type = SFRAME_ABORT_REQ; + break; + case SFRAME_ABORT_RES: + protocol->last_rx.sframe.type = SFRAME_ABORT_RES; + protocol->next_tx.type = FRAME_UNKNOWN; + protocol->state = STATE_IDLE; + break; + case SFRAME_WTX_REQ: + if (protocol->last_tx.type == FRAME_SFRAME && + protocol->last_tx.sframe.type != SFRAME_WTX_RSP) { + if (protocol->recovery_counter < PROTOCOL_FRAME_RETRY_COUNT) { + memcpy((uint8_t *)&protocol->next_tx, + (uint8_t *)&protocol->last_tx, sizeof(frame_info_t)); + protocol->recovery_counter++; + } else { + iso7816_t1_finish_recovery(protocol); + } + } else { + protocol->wtx_counter++; + if (data_size == 8) { + wait_time = data[3] << 24 | data[4] << 16 | data[5] << 8 | data[6]; + wait_time = wait_time * 1000; + } + LOG_I("wtx_counter : %u, wait time : %u us", protocol->wtx_counter, wait_time); + PROTOCOL_UDELAY(wait_time); + protocol->last_rx.sframe.type = SFRAME_WTX_REQ; + protocol->next_tx.type = FRAME_SFRAME; + protocol->next_tx.sframe.type = SFRAME_WTX_RSP; + protocol->next_tx.sframe.data = NULL; + protocol->next_tx.sframe.send_size = 0; + protocol->state = STATE_SEND_SFRAME; + } + break; + case SFRAME_WTX_RSP: + protocol->last_rx.sframe.type = SFRAME_WTX_RSP; + break; + default: + break; + } + } else { + LOG_E("Wrong-Frame Received"); + return ESESTATUS_INVALID_FORMAT; + } + + return ESESTATUS_SUCCESS; +} + +static ESE_STATUS iso7816_t1_process(iso7816_t1_t *protocol) +{ + uint32_t data_size = 0, rec_size = 0, ret_size = 0; + ESE_STATUS status = ESESTATUS_FAILED; + int32_t i = 0; + + PROTOCOL_UDELAY(100); + for (i = 0; i < PROTOCOL_ADDRESS_POLLING_COUNT; i++) { + if (ese_hal_receive(protocol->hal, protocol->proc_buf, 1) != 1) { + status = ESESTATUS_INVALID_RECEIVE_LENGTH; + goto error; + } + + if ((protocol->proc_buf)[data_size] == protocol->receive_address) { + break; + } + + if ((protocol->proc_buf)[data_size] != 0x0) { + LOG_E("invalid address, expected : %d, received : %d", + protocol->receive_address, (protocol->proc_buf)[data_size]); + status = ESESTATUS_INVALID_FRAME; + goto error; + } + PROTOCOL_UDELAY(1500); + } + + if (i == PROTOCOL_ADDRESS_POLLING_COUNT) { + LOG_E("finish polling address, limit : %d", PROTOCOL_ADDRESS_POLLING_COUNT); + status = ESESTATUS_INVALID_FRAME; + goto error; + } + + data_size ++; + + ret_size = (uint32_t)ese_hal_receive(protocol->hal, protocol->proc_buf + data_size, 2); + if (ret_size != 2) { + LOG_E("mismatch receive size %u, %u", 2, ret_size); + status = ESESTATUS_INVALID_RECEIVE_LENGTH; + goto error; + } + data_size += ret_size; + + rec_size = (protocol->proc_buf)[2] + 1; + ret_size = (uint32_t)ese_hal_receive(protocol->hal, protocol->proc_buf + data_size, rec_size); + if (ret_size != rec_size) { + LOG_E("mismatch receive size %u, %u", rec_size, ret_size); + status = ESESTATUS_INVALID_RECEIVE_LENGTH; + goto error; + } + data_size += ret_size; + +error: + if (data_size > 0) { + protocol->timeout_counter = PROTOCOL_ZERO; + status = iso7816_t1_check_lrc(protocol->proc_buf, data_size); + if (status == ESESTATUS_SUCCESS) { + protocol->rnack_counter = PROTOCOL_ZERO; + iso7816_t1_decode_frame(protocol, protocol->proc_buf, data_size); + } else { + LOG_E("LRC Check failed"); + if (protocol->rnack_counter < PROTOCOL_MAX_RNACK_RETRY_LIMIT) { + protocol->last_rx.type = FRAME_INVALID; + protocol->next_tx.type = FRAME_RFRAME; + protocol->next_tx.rframe.error = RFRAME_ERROR_PARITY; + protocol->next_tx.rframe.seq_no = + (!protocol->last_rx.iframe.seq_no) << 4; + protocol->state = STATE_SEND_RFRAME; + protocol->rnack_counter++; + } else { + protocol->rnack_counter = PROTOCOL_ZERO; + protocol->state = STATE_IDLE; + } + } + } else { + LOG_E("ese_hal_receive failed"); + if ((protocol->last_tx.type == FRAME_SFRAME) + && ((protocol->last_tx.sframe.type == SFRAME_WTX_RSP) || + (protocol->last_tx.sframe.type == SFRAME_RESYNCH_RSP))) { + if (protocol->rnack_counter < PROTOCOL_MAX_RNACK_RETRY_LIMIT) { + protocol->last_rx.type = FRAME_INVALID; + protocol->next_tx.type = FRAME_RFRAME; + protocol->next_tx.rframe.error = RFRAME_ERROR_OTHER; + protocol->next_tx.rframe.seq_no = + (!protocol->last_rx.iframe.seq_no) << 4; + protocol->state = STATE_SEND_RFRAME; + protocol->rnack_counter++; + } else { + protocol->rnack_counter = PROTOCOL_ZERO; + protocol->state = STATE_IDLE; + protocol->timeout_counter = PROTOCOL_ZERO; + } + } else { + PROTOCOL_UDELAY(50 * 1000); + if (status == ESESTATUS_INVALID_FRAME) { + if (protocol->rnack_counter < PROTOCOL_MAX_RNACK_RETRY_LIMIT) { + protocol->last_rx.type = FRAME_INVALID; + protocol->next_tx.type = FRAME_RFRAME; + protocol->next_tx.rframe.error = RFRAME_ERROR_OTHER; + protocol->next_tx.rframe.seq_no = + (!protocol->last_rx.iframe.seq_no) << 4; + protocol->state = STATE_SEND_RFRAME; + protocol->rnack_counter++; + } else { + protocol->rnack_counter = PROTOCOL_ZERO; + protocol->state = STATE_IDLE; + protocol->timeout_counter = PROTOCOL_ZERO; + } + } else { + if (protocol->timeout_counter < PROTOCOL_TIMEOUT_RETRY_COUNT) { + LOG_E("re-transmitting the previous frame"); + protocol->timeout_counter++; + memcpy((uint8_t *)&protocol->next_tx, + (uint8_t *)&protocol->last_tx, sizeof(frame_info_t)); + } else { + LOG_E("finish timeout recovery, set protocol state to IDLE"); + protocol->state = STATE_IDLE; + protocol->timeout_counter = PROTOCOL_ZERO; + } + } + } + } + + return status; +} + +ESE_STATUS iso7816_t1_send(void *ctx, ese_data_t *data) +{ + iso7816_t1_t *protocol = (iso7816_t1_t *)ctx; + ESE_STATUS status = ESESTATUS_FAILED; + + if (protocol->state == STATE_IDLE && data != NULL) { + protocol->next_tx.iframe.data = data->data; + protocol->next_tx.iframe.total_size = data->size; + iso7816_t1_set_iframe(protocol); + } + + memcpy((uint8_t *)&protocol->last_tx, (uint8_t *)&protocol->next_tx, sizeof(frame_info_t)); + + switch (protocol->state) { + case STATE_SEND_IFRAME: + status = iso7816_t1_send_iframe(protocol); + break; + case STATE_SEND_RFRAME: + status = iso7816_t1_send_rframe(protocol); + break; + case STATE_SEND_SFRAME: + status = iso7816_t1_send_sframe(protocol); + break; + default: + LOG_E("invalid state"); + protocol->state = STATE_IDLE; + break; + } + + return status; +} + +ESE_STATUS iso7816_t1_receive(void *ctx, ese_data_t *data) +{ + iso7816_t1_t *protocol = (iso7816_t1_t *)ctx; + ESE_STATUS status = ESESTATUS_FAILED; + ESE_STATUS wstatus = ESESTATUS_FAILED; + + do { + status = iso7816_t1_process(protocol); + if (status == ESESTATUS_INVALID_DEVICE) { + return status; + } + + if (protocol->state != STATE_IDLE) { + status = iso7816_t1_send(protocol, NULL); + if (status != ESESTATUS_SUCCESS) { + LOG_E("send failed, going to recovery!"); + protocol->state = STATE_IDLE; + } + } + } while (protocol->state != STATE_IDLE); + wstatus = ese_data_get(&(protocol->recv_data), &data->data, &data->size); + if (wstatus != ESESTATUS_SUCCESS) { + status = wstatus; + } + + protocol->cur_buf = data->data; + protocol->cur_size = data->size; + return status; +} + +ESE_STATUS iso7816_t1_resync_request(void *ctx) +{ + iso7816_t1_t *protocol = (iso7816_t1_t *)ctx; + + protocol->state = STATE_SEND_SFRAME; + protocol->next_tx.type = FRAME_SFRAME; + protocol->next_tx.sframe.type = SFRAME_RESYNCH_REQ; + protocol->next_tx.sframe.data = NULL; + protocol->next_tx.sframe.send_size = 0; + return iso7816_t1_send(protocol, NULL); +} + +ESE_STATUS iso7816_t1_resync_response(void *ctx) +{ + iso7816_t1_t *protocol = (iso7816_t1_t *)ctx; + ESE_STATUS status = ESESTATUS_FAILED; + + status = iso7816_t1_process(protocol); + protocol->state = STATE_IDLE; + if (protocol->last_rx.type != FRAME_SFRAME && + protocol->last_rx.sframe.type != SFRAME_RESYNCH_RSP) { + return ESESTATUS_INVALID_FRAME; + } + return status; +} + +ESE_STATUS iso7816_t1_wtx_request(void *ctx, uint32_t time) +{ + iso7816_t1_t *protocol = (iso7816_t1_t *)ctx; + + protocol->state = STATE_SEND_SFRAME; + protocol->next_tx.type = FRAME_SFRAME; + protocol->next_tx.sframe.type = SFRAME_WTX_REQ; + protocol->next_tx.sframe.data = (uint8_t *)&time; + protocol->next_tx.sframe.send_size = 4; + return iso7816_t1_send(protocol, NULL); +} + +ESE_STATUS iso7816_t1_wtx_response(void *ctx) +{ + iso7816_t1_t *protocol = (iso7816_t1_t *)ctx; + ESE_STATUS status = ESESTATUS_FAILED; + + status = iso7816_t1_process(protocol); + protocol->state = STATE_IDLE; + if (protocol->last_rx.type != FRAME_SFRAME && + protocol->last_rx.sframe.type != SFRAME_WTX_RSP) { + return ESESTATUS_INVALID_FRAME; + } + return status; +} + +ESE_STATUS iso7816_t1_get_data(void *ctx, ese_data_t *data, uint8_t *le) +{ + iso7816_t1_t *protocol = (iso7816_t1_t *)ctx; + unsigned int data_size = 0; + + if (data != NULL) { + data_size = protocol->cur_buf[APDU_P3_OFFSET]; + data->data = protocol->cur_buf + APDU_HEADER_SIZE; + data->size = data_size; + } + + if (le != NULL) { + if (data_size == 0) { + *le = protocol->cur_buf[APDU_P3_OFFSET]; + } else { + *le = protocol->cur_buf[APDU_HEADER_SIZE + data_size]; + } + } + + return ESESTATUS_SUCCESS; +} + +void *iso7816_t1_init(uint8_t send_address, uint8_t receive_address, void *hal) +{ + iso7816_t1_t *protocol = NULL; + + protocol = ESE_MALLOC(sizeof(iso7816_t1_t)); + if (protocol == NULL) { + return NULL; + } + + iso7816_t1_reset_params(protocol); + ese_data_init(&(protocol->recv_data)); +/* + * T1 Header + Information Field + LRC (3 + 256 + 1) + */ + protocol->proc_buf = ESE_MALLOC(260); + if (protocol->proc_buf == NULL) { + ESE_FREE(protocol); + return NULL; + } + protocol->send_address = send_address; + protocol->receive_address = receive_address; + protocol->hal = hal; + + return (void *)protocol; +} + +void iso7816_t1_deinit(void *ctx) +{ + iso7816_t1_t *protocol = (iso7816_t1_t *)ctx; + + ESE_FREE(protocol->proc_buf); + ESE_FREE(protocol); +} + +void iso7816_t1_reset(void *ctx) +{ + iso7816_t1_t *protocol = (iso7816_t1_t *)ctx; + + iso7816_t1_reset_params(protocol); +} diff --git a/drivers/nfc/snvm/protocol/ese_iso7816_t1.h b/drivers/nfc/snvm/protocol/ese_iso7816_t1.h new file mode 100644 index 000000000000..a00a8ba3201c --- /dev/null +++ b/drivers/nfc/snvm/protocol/ese_iso7816_t1.h @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2020 Samsung Electronics. 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 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/********************* Definitions and structures *****************************/ + +#ifndef __ESE_PROTOCOL_T1__H +#define __ESE_PROTOCOL_T1__H + +#include +#include + +#include "ese_protocol.h" +#include "ese_data.h" + +typedef enum sframe_type_e { + SFRAME_RESYNCH_REQ = 0x00, + SFRAME_RESYNCH_RSP = 0x20, + SFRAME_IFSC_REQ = 0x01, + SFRAME_IFSC_RES = 0x21, + SFRAME_ABORT_REQ = 0x02, + SFRAME_ABORT_RES = 0x22, + SFRAME_WTX_REQ = 0x03, + SFRAME_WTX_RSP = 0x23, + SFRAME_INVALID_REQ_RES +} sframe_type_t; + +typedef enum rframe_type_e { + RFRAME_ACK = 0x01, + RFRAME_NACK = 0x02 +} rframe_type_t; + +typedef enum rframe_error_e { + RFRAME_ERROR_NO, + RFRAME_ERROR_PARITY, + RFRAME_ERROR_OTHER, + RFRAME_ERROR_SOF_MISSED, + RFRAME_ERROR_UNDEFINED +} rframe_error_t; + +typedef enum frame_type_e { + FRAME_IFRAME, + FRAME_SFRAME, + FRAME_RFRAME, + FRAME_INVALID, + FRAME_UNKNOWN +} frame_type_t; + +typedef struct iframe_info_s { + uint8_t *data; + uint32_t offset; + uint32_t total_size; + uint32_t send_size; + uint8_t chain; + uint8_t seq_no; +} iframe_info_t; + +typedef struct sframe_info_s { + uint8_t *data; + uint32_t send_size; + sframe_type_t type; +} sframe_info_t; + +typedef struct rframe_info_s { + uint8_t seq_no; + rframe_error_t error; +} rframe_info_t; + +typedef struct frame_info_s { + iframe_info_t iframe; + rframe_info_t rframe; + sframe_info_t sframe; + frame_type_t type; +} frame_info_t; + +typedef enum state_e { + STATE_IDLE, + STATE_SEND_IFRAME, + STATE_SEND_RFRAME, + STATE_SEND_SFRAME, +} state_t; + +typedef struct iso7816_t1_s { + frame_info_t last_tx; + frame_info_t next_tx; + frame_info_t last_rx; + data_list_t recv_data; + uint8_t *proc_buf; + uint8_t *cur_buf; + uint32_t cur_size; + uint32_t wtx_counter; + uint32_t timeout_counter; + uint32_t rnack_counter; + uint32_t recovery_counter; + frame_type_t last_frame; + state_t state; + uint8_t send_address; + uint8_t receive_address; + void *hal; +} iso7816_t1_t; + +#define PROTOCOL_HEADER_SIZE 0x03 +#define PROTOCOL_LRC_SIZE 0x01 +#define PROTOCOL_ZERO 0x00 +#define PROTOCOL_ADDRESS_POLLING_COUNT 100 +#define PROTOCOL_TIMEOUT_RETRY_COUNT 3 +#define PROTOCOL_PCB_OFFSET 1 +#define PROTOCOL_SEND_SIZE 254 +#define PROTOCOL_CHAINING 0x20 +#define PROTOCOL_S_BLOCK_REQ 0xC0 +#define PROTOCOL_S_BLOCK_RSP 0xE0 +#define PROTOCOL_FRAME_RETRY_COUNT 3 +#define PROTOCOL_MAX_RNACK_RETRY_LIMIT 2 +#define PROTOCOL_UDELAY(x) usleep_range(x, (x + 100)) + +#define SFRAME_MAX_INF_SIZE (4) +#define RFRAME_PROTOCOL_SIZE (PROTOCOL_HEADER_SIZE + PROTOCOL_LRC_SIZE) + +#define APDU_HEADER_SIZE (5) +#define APDU_P3_OFFSET (4) + +#define ESE_BIT(val, bit) ((val >> bit) & 0x1) + +ESE_STATUS iso7816_t1_send(void *ctx, ese_data_t *data); +ESE_STATUS iso7816_t1_receive(void *ctx, ese_data_t *data); +ESE_STATUS iso7816_t1_resync_request(void *ctx); +ESE_STATUS iso7816_t1_resync_response(void *ctx); +ESE_STATUS iso7816_t1_wtx_request(void *ctx, uint32_t time); +ESE_STATUS iso7816_t1_wtx_response(void *ctx); +ESE_STATUS iso7816_t1_get_data(void *ctx, ese_data_t *data, uint8_t *le); +void *iso7816_t1_init(uint8_t send_address, uint8_t receive_address, void *hal); +void iso7816_t1_deinit(void *ctx); +void iso7816_t1_reset(void *ctx); + +#endif diff --git a/drivers/nfc/snvm/protocol/ese_log.h b/drivers/nfc/snvm/protocol/ese_log.h new file mode 100644 index 000000000000..f7910bd33ed8 --- /dev/null +++ b/drivers/nfc/snvm/protocol/ese_log.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2020 Samsung Electronics. 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 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __ESE_LOG__H +#define __ESE_LOG__H + +#include + +#define LOG_D(msg...) pr_debug("[star-protocol] : " msg); +#define LOG_I(msg...) pr_info("[star-protocol] : " msg); +#define LOG_W(msg...) pr_warn("[star-protocol] : " msg); +#define LOG_E(msg...) pr_err("[star-protocol] : " msg); + +#endif diff --git a/drivers/nfc/snvm/protocol/ese_memory.c b/drivers/nfc/snvm/protocol/ese_memory.c new file mode 100644 index 000000000000..f254d615ed20 --- /dev/null +++ b/drivers/nfc/snvm/protocol/ese_memory.c @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2020 Samsung Electronics. 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 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +#include "ese_log.h" +#include "ese_memory.h" + +#ifdef CONFIG_STAR_MEMORY_LEAK +#define MAX_MEMORY_ALLOC_NUM 100 + +void *alloc_list[MAX_MEMORY_ALLOC_NUM]; + +void *ese_malloc(size_t size) +{ + int i = 0; + + for (i = 0; i < MAX_MEMORY_ALLOC_NUM; i ++) { + if (alloc_list[i] == NULL) { + break; + } + } + + if (i == MAX_MEMORY_ALLOC_NUM) { + LOG_E(" exceed alloc list size"); + return NULL; + } + + alloc_list[i] = kzalloc(size, GFP_KERNEL); + LOG_I(" kzalloc addr : %p, size : %u", alloc_list[i], (unsigned int)size); + return alloc_list[i]; +} + +void ese_free(void *ptr) +{ + int i = 0; + + for (i = 0; i < MAX_MEMORY_ALLOC_NUM; i ++) { + if (alloc_list[i] == ptr) { + break; + } + } + + if (i == MAX_MEMORY_ALLOC_NUM) { + LOG_E(" failed to find memory in alloc list : %p", ptr); + return; + } + + alloc_list[i] = NULL; + kfree((void *)ptr); + LOG_I(" free addr : %p", ptr); +} + +void ese_alloc_list(void) +{ + int i = 0; + + for (i = 0; i < MAX_MEMORY_ALLOC_NUM; i ++) { + if (alloc_list[i] != NULL) { + LOG_E(" non free memory : %p", alloc_list[i]); + } + } +} +#endif diff --git a/drivers/nfc/snvm/protocol/ese_memory.h b/drivers/nfc/snvm/protocol/ese_memory.h new file mode 100644 index 000000000000..96662ddf4d83 --- /dev/null +++ b/drivers/nfc/snvm/protocol/ese_memory.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 Samsung Electronics. 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 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __ESE_MEMORY__H +#define __ESE_MEMORY__H + +#ifdef CONFIG_STAR_MEMORY_LEAK +void *ese_malloc(size_t size); +void ese_free(void *ptr); +void ese_print_list(void); + +#define ESE_MALLOC ese_malloc +#define ESE_FREE ese_free +#define ESE_ALLOC_LIST() ese_alloc_list() +#else +#include + +#define ESE_MALLOC(x) kzalloc(x, GFP_KERNEL) +#define ESE_FREE(x) kfree(x) +#define ESE_ALLOC_LIST() +#endif + +#endif diff --git a/drivers/nfc/snvm/protocol/ese_protocol.h b/drivers/nfc/snvm/protocol/ese_protocol.h new file mode 100644 index 000000000000..fa3f66d2be24 --- /dev/null +++ b/drivers/nfc/snvm/protocol/ese_protocol.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2020 Samsung Electronics. 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 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __ESE_PROTOCOL__H +#define __ESE_PROTOCOL__H + +typedef struct ese_data_s { + uint32_t size; + uint8_t* data; +} ese_data_t; + +#endif diff --git a/drivers/nfc/snvm/sec_k250a.c b/drivers/nfc/snvm/sec_k250a.c new file mode 100644 index 000000000000..7169363998ad --- /dev/null +++ b/drivers/nfc/snvm/sec_k250a.c @@ -0,0 +1,517 @@ +/* + * Copyright (C) 2020 Samsung Electronics. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(CONFIG_SEC_SNVM_PLATFORM_DRV) +#include +#endif + +#include "sec_star.h" + +#undef USE_INTERNAL_PULLUP //if use external pull-up, not necessary + +#define ERR(msg...) pr_err("[star-k250a] : " msg) +#define INFO(msg...) pr_info("[star-k250a] : " msg) + +struct k250a_dev { + struct i2c_client *client; + struct regulator *vdd; + unsigned int reset_gpio; + struct pinctrl *pinctrl; + struct pinctrl_state *nvm_on_pin; + struct pinctrl_state *nvm_off_pin; +#if defined(USE_INTERNAL_PULLUP) +#define SCL_GPIO_NUM 335 +#define SDA_GPIO_NUM 320 + struct pinctrl *pinctrl; + struct pinctrl_state *pin_i2c; + struct pinctrl_state *pin_gpio; +#endif + sec_star_t *star; +#ifdef CONFIG_SEC_SNVM_I2C_CLOCK_CONTROL + struct clk *i2c_main_clk; + struct clk *i2c_dma_clk; + bool i2c_main_clk_enabled; + bool i2c_dma_clk_enabled; +#endif +}; + +static struct k250a_dev g_k250a; + +#ifdef CONFIG_SEC_SNVM_I2C_CLOCK_CONTROL +static void k250a_parse_i2c_clock(struct k250a_dev *k250a, struct device_node *np) +{ + struct device_node *i2c_np; + + i2c_np = of_parse_phandle(np, "i2c_node", 0); + if (!i2c_np) { + INFO("i2c_node not found\n"); + return; + } + + INFO("i2c_node found\n"); + k250a->i2c_main_clk = of_clk_get_by_name(i2c_np, "main"); + + if (IS_ERR(k250a->i2c_main_clk)) + INFO("failed to get i2c main clock\n"); + else + INFO("i2c main clock found"); + + k250a->i2c_dma_clk = of_clk_get_by_name(i2c_np, "dma"); + if (IS_ERR(k250a->i2c_dma_clk)) + INFO("failed to get i2c dma clock\n"); + else + INFO("i2c dma clock found"); +} + +static void k250a_i2c_clock_enable(void) +{ + int ret = 0; + + if (!g_k250a.i2c_main_clk_enabled && !IS_ERR_OR_NULL(g_k250a.i2c_main_clk)) { + ret = clk_prepare_enable(g_k250a.i2c_main_clk); + if (ret) + ERR("failed to enable i2c main clock\n"); + else + g_k250a.i2c_main_clk_enabled = true; + } + + if (!g_k250a.i2c_dma_clk_enabled && !IS_ERR_OR_NULL(g_k250a.i2c_dma_clk)) { + ret = clk_prepare_enable(g_k250a.i2c_dma_clk); + if (ret) + ERR("failed to enable i2c dma clock\n"); + else + g_k250a.i2c_dma_clk_enabled = true; + } +} + +static void k250a_i2c_clock_disable(void) +{ + if (g_k250a.i2c_main_clk_enabled && !IS_ERR_OR_NULL(g_k250a.i2c_main_clk)) { + clk_disable_unprepare(g_k250a.i2c_main_clk); + g_k250a.i2c_main_clk_enabled = false; + } + + if (g_k250a.i2c_dma_clk_enabled && !IS_ERR_OR_NULL(g_k250a.i2c_dma_clk)) { + clk_disable_unprepare(g_k250a.i2c_dma_clk); + g_k250a.i2c_dma_clk_enabled = false; + } +} +#endif + +static int k250a_poweron(void) +{ + int ret = 0; +#ifdef CONFIG_SEC_SNVM_I2C_CLOCK_CONTROL + bool i2c_main_clk = !IS_ERR_OR_NULL(g_k250a.i2c_main_clk); + bool i2c_dma_clk = !IS_ERR_OR_NULL(g_k250a.i2c_dma_clk); + + INFO("k250a_poweron (i2c clk:%s%s)\n", + i2c_main_clk ? " main" : "", i2c_dma_clk ? " dma" : ""); +#else + INFO("k250a_poweron\n"); +#endif + if (g_k250a.vdd == NULL) { + if (g_k250a.reset_gpio == 0) { + return 0; + } + + INFO("rest pin control instead of vdd\n"); + + gpio_set_value(g_k250a.reset_gpio, 0); + usleep_range(1000, 2000); + gpio_set_value(g_k250a.reset_gpio, 1); + + usleep_range(15000, 20000); + return 0; + } + + ret = regulator_enable(g_k250a.vdd); + if (ret) { + ERR("%s - enable vdd failed, ret=%d\n", __func__, ret); + return ret; + } + + usleep_range(1000, 2000); + if (g_k250a.nvm_on_pin) { + if (pinctrl_select_state(g_k250a.pinctrl, g_k250a.nvm_on_pin)) + ERR("nvm on pinctrl set error\n"); + else + INFO("nvm on pinctrl set\n"); + } + +#if defined(USE_INTERNAL_PULLUP) + if (pinctrl_select_state(g_k250a.pinctrl, g_k250a.pin_i2c) < 0) { + ERR("failed to pinctrl_select_state for gpio"); + } +#endif + + usleep_range(14000, 15000); + + return 0; +} + +static int k250a_poweroff(void) +{ + int ret = 0; + + INFO("k250a_poweroff\n"); + + if (g_k250a.vdd == NULL) { + return 0; + } + if (g_k250a.nvm_off_pin) { + if (pinctrl_select_state(g_k250a.pinctrl, g_k250a.nvm_off_pin)) + ERR("nvm off pinctrl set error\n"); + else + INFO("nvm off pinctrl set\n"); + } + +#if defined(USE_INTERNAL_PULLUP) + if (pinctrl_select_state(g_k250a.pinctrl, g_k250a.pin_gpio) < 0) { + ERR("failed to pinctrl_select_state for gpio"); + } +#endif + + ret = regulator_disable(g_k250a.vdd); + if (ret) { + ERR("%s - disable vdd failed, ret=%d\n", __func__, ret); + return ret; + } + + usleep_range(15000, 20000); + return 0; +} + +static int k250a_reset(void) +{ + if (g_k250a.reset_gpio == 0) { + return -ENODEV; + } + + gpio_set_value(g_k250a.reset_gpio, 0); + usleep_range(1000, 2000); + gpio_set_value(g_k250a.reset_gpio, 1); + usleep_range(15000, 20000); + return 0; +} + +static star_dev_t star_dev = { + .name = "k250a", + .hal_type = SEC_HAL_I2C, + .client = NULL, + .power_on = k250a_poweron, + .power_off = k250a_poweroff, + .reset = k250a_reset +}; + +static int k250a_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct device_node *np = client->dev.of_node; + + INFO("Entry : %s\n", __func__); + + if (np) { + g_k250a.vdd = devm_regulator_get_optional(&(client->dev), "1p8_pvdd"); + if (IS_ERR(g_k250a.vdd)) { + ERR("%s - 1p8_pvdd can not be used\n", __func__); + g_k250a.vdd = NULL; + } + + if (of_property_read_u32(np, "reset_gpio", &(g_k250a.reset_gpio)) < 0) { + ERR("%s - Reset Control can not be used\n", __func__); + g_k250a.reset_gpio = 0; + } else { + if (gpio_request(g_k250a.reset_gpio, "sec-reset") < 0) { + ERR("%s - Failed to request sec-reset gpio\n", __func__); + g_k250a.reset_gpio = 0; + } else { + INFO("%s - Reset GPIO Num : %u\n", __func__, g_k250a.reset_gpio); + if (gpio_direction_output(g_k250a.reset_gpio, 1) < 0) { + ERR("Failed to set reset gpio"); + } + } + } + } else { + return -ENODEV; + } + + g_k250a.pinctrl = devm_pinctrl_get(client->adapter->dev.parent); + if (IS_ERR(g_k250a.pinctrl)) { + ERR("devm_pinctrl_get failed\n"); + return -1; + } + g_k250a.nvm_on_pin = pinctrl_lookup_state(g_k250a.pinctrl, "nvm_on"); + if (IS_ERR(g_k250a.nvm_on_pin)) { + ERR("pinctrl_lookup_state failed-default\n"); + g_k250a.nvm_on_pin = NULL; + } + g_k250a.nvm_off_pin = pinctrl_lookup_state(g_k250a.pinctrl, "nvm_off"); + if (IS_ERR(g_k250a.nvm_off_pin)) { + ERR("pinctrl_lookup_state failed-nvm_off\n"); + g_k250a.nvm_off_pin = NULL; + } else if (pinctrl_select_state(g_k250a.pinctrl, g_k250a.nvm_off_pin)) { + ERR("nvm off pinctrl set error\n"); + } else { + INFO("nvm off pinctrl set\n"); + } + +#if defined(USE_INTERNAL_PULLUP) + g_k250a.pinctrl = devm_pinctrl_get(client->adapter->dev.parent); + if (IS_ERR(g_k250a.pinctrl)) { + ERR("failed to devm_pinctrl_get"); + return -1; + } + + g_k250a.pin_i2c = pinctrl_lookup_state(g_k250a.pinctrl, "default"); + if (IS_ERR(g_k250a.pin_i2c)) { + ERR("failed to pinctrl_lookup_state for i2c"); + devm_pinctrl_put(g_k250a.pinctrl); + return -1; + } + + g_k250a.pin_gpio = pinctrl_lookup_state(g_k250a.pinctrl, "gpio"); + if (IS_ERR(g_k250a.pin_gpio)) { + ERR("failed to pinctrl_lookup_state for gpio"); + devm_pinctrl_put(g_k250a.pinctrl); + return -1; + } + + if (pinctrl_select_state(g_k250a.pinctrl, g_k250a.pin_gpio) < 0) { + ERR("failed to pinctrl_select_state for gpio"); + devm_pinctrl_put(g_k250a.pinctrl); + return -1; + } + + if (gpio_request(SCL_GPIO_NUM, "sec-scl") < 0) { + ERR("failed to get scl gpio"); + devm_pinctrl_put(g_k250a.pinctrl); + return -1; + } + + if (gpio_request(SDA_GPIO_NUM, "sec-sda") < 0) { + ERR("failed to get sda gpio"); + devm_pinctrl_put(g_k250a.pinctrl); + return -1; + } + + if (gpio_direction_output(SCL_GPIO_NUM, 0) < 0) { + ERR("failed to set scl gpio"); + devm_pinctrl_put(g_k250a.pinctrl); + return -1; + } + + if (gpio_direction_output(SDA_GPIO_NUM, 0) < 0) { + ERR("failed to set sda gpio"); + devm_pinctrl_put(g_k250a.pinctrl); + return -1; + } +#endif + + g_k250a.client = client; + star_dev.client = client; + g_k250a.star = star_open(&star_dev); + if (g_k250a.star == NULL) { + return -ENODEV; + } + + INFO("Exit : %s\n", __func__); + return 0; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) +static int k250a_remove(struct i2c_client *client) +#else +static void k250a_remove(struct i2c_client *client) +#endif +{ + INFO("Entry : %s\n", __func__); +#if defined(USE_INTERNAL_PULLUP) + devm_pinctrl_put(g_k250a.pinctrl); + gpio_free(SCL_GPIO_NUM); + gpio_free(SDA_GPIO_NUM); +#endif + if (g_k250a.reset_gpio != 0) { + gpio_free(g_k250a.reset_gpio); + } + star_close(g_k250a.star); + INFO("Exit : %s\n", __func__); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) + return 0; +#endif +} + +#if defined(CONFIG_SEC_SNVM_PLATFORM_DRV) +static int k250a_dev_open(struct inode *inode, struct file *filp) +{ + k250a_poweron(); +#ifdef CONFIG_SEC_SNVM_I2C_CLOCK_CONTROL + k250a_i2c_clock_enable(); +#endif + + return 0; +} + +static int ese_dev_release(struct inode *inode, struct file *filp) +{ +#ifdef CONFIG_SEC_SNVM_I2C_CLOCK_CONTROL + k250a_i2c_clock_disable(); +#endif + k250a_poweroff(); + + return 0; +} + +static const struct file_operations k250a_dev_fops = { + .owner = THIS_MODULE, + .open = k250a_dev_open, + .release = ese_dev_release, +}; + +static struct miscdevice k250a_misc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "k250a", + .fops = &k250a_dev_fops, +}; + +void k250a_parse_dt_for_platform_device(struct device *dev) +{ + g_k250a.vdd = devm_regulator_get_optional(dev, "1p8_pvdd"); + if (IS_ERR(g_k250a.vdd)) { + ERR("%s - 1p8_pvdd can not be used\n", __func__); + g_k250a.vdd = NULL; + } else { + INFO("%s: regulator_get success\n", __func__); + } + +#ifdef CONFIG_SEC_SNVM_I2C_CLOCK_CONTROL + k250a_parse_i2c_clock(&g_k250a, dev->of_node); +#endif +} + +static int k250a_platform_probe(struct platform_device *pdev) +{ + int ret = -1; + + k250a_parse_dt_for_platform_device(&pdev->dev); + ret = misc_register(&k250a_misc_device); + if (ret < 0) + ERR("misc_register failed! %d\n", ret); + + INFO("%s: finished...\n", __func__); + return 0; +} + +static int k250a_platform_remove(struct platform_device *pdev) +{ + INFO("Entry : %s\n", __func__); + return 0; +} + +static const struct of_device_id k250a_secure_match_table[] = { + { .compatible = "sec_k250a_platform",}, + {}, +}; + +static struct platform_driver k250a_platform_driver = { + .driver = { + .name = "k250a_platform", + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = k250a_secure_match_table, +#endif + }, + .probe = k250a_platform_probe, + .remove = k250a_platform_remove, +}; +#endif + +static const struct i2c_device_id k250a_id[] = { + {"k250a", 0}, + {} +}; + +static const struct of_device_id k250a_match_table[] = { + { .compatible = "sec_k250a",}, + {}, +}; + +static struct i2c_driver k250a_driver = { + .id_table = k250a_id, + .probe = k250a_probe, + .remove = k250a_remove, + .driver = { + .name = "k250a", + .owner = THIS_MODULE, + .of_match_table = k250a_match_table, + }, +}; + +static int __init k250a_init(void) +{ +#if defined(CONFIG_SEC_SNVM_PLATFORM_DRV) + int ret; + + ret = platform_driver_register(&k250a_platform_driver); + if (!ret) { + INFO("platform_driver_register success : %s\n", __func__); + return ret; + } else { + ERR("platform_driver_register fail : %s\n", __func__); + return ret; + } +#endif + INFO("Entry : %s\n", __func__); + + return i2c_add_driver(&k250a_driver); +} +module_init(k250a_init); + +static void __exit k250a_exit(void) +{ + INFO("Entry : %s\n", __func__); +#if defined(CONFIG_SEC_SNVM_PLATFORM_DRV) + platform_driver_unregister(&k250a_platform_driver); + return; +#endif + i2c_del_driver(&k250a_driver); +} + +module_exit(k250a_exit); + +MODULE_AUTHOR("Sec"); +MODULE_DESCRIPTION("K250A driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nfc/snvm/sec_star.c b/drivers/nfc/snvm/sec_star.c new file mode 100644 index 000000000000..3762efa4502e --- /dev/null +++ b/drivers/nfc/snvm/sec_star.c @@ -0,0 +1,532 @@ +/* + * Copyright (C) 2020 Samsung Electronics. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hal/ese_hal.h" +#include "protocol/ese_memory.h" +#include "protocol/ese_iso7816_t1.h" +#include "sec_star.h" + +#define STAR_VERSION "STAR00010000" + +#define ERR(msg...) pr_err("[star] : " msg) +#define INFO(msg...) pr_info("[star] : " msg) + +#define BIGENDIAN_TO_UINT32(x, y) \ +{ \ + y = ((x)[0] << 24) | ((x)[1] << 16) | ((x)[2] << 8) | (x)[3]; \ +} + +#define SEND_ADDRESS 0x12 +#define RECEIVE_ADDRESS 0x21 + +#define APDU_CHAIN_NUM_SIZE 4 +#define APDU_CHAIN_SEQ_SIZE 4 +#define APDU_CHAIN_CMD_SIZE 4 +#define APDU_CHAIN_EXP_SIZE 4 +#define APDU_CHAIN_HEADER_SIZE (APDU_CHAIN_SEQ_SIZE + APDU_CHAIN_CMD_SIZE + APDU_CHAIN_EXP_SIZE) + +#define APDU_CHAIN_EXP_FLAG 4 + +#define EXPECTED_RESPONSE_NONE 0x0 +#define EXPECTED_RESPONSE_NEXT 0x1 +#define EXPECTED_RESPONSE_AGAIN 0x2 + +#define STAR_MAGIC_CODE 'S' +#define STAR_READ_SIZE _IOR(STAR_MAGIC_CODE, 0, unsigned int) +#define STAR_SET_DIRECT _IOW(STAR_MAGIC_CODE, 1, int) +#define STAR_RESET_PROTOCOL _IO(STAR_MAGIC_CODE, 2) +#define STAR_RESET_INTERFACE _IO(STAR_MAGIC_CODE, 3) + +#define APDU_CHAIN_MAX_SIZE ((65536/256) + 1) + +static int32_t star_transceive(void *ctx, uint8_t *cmd, uint32_t cmd_size, uint8_t **rsp, uint32_t *rsp_size) +{ + ese_data_t cmd_data = {0, NULL}; + ese_data_t rsp_data = {0, NULL}; + uint8_t *p_cmd = NULL; + uint32_t chain_num = 0; + uint32_t seq = 0; + uint32_t data_size = 0; + uint32_t expected_flag = 0; + uint32_t expected_size = 0; + uint32_t i = 0; + data_list_t total_rsp; + + if (ctx == NULL) { + return -1; + } + + if ((cmd == NULL) || (cmd_size == 0) || (rsp == NULL) || (rsp_size == NULL)) { + ERR("invalid parameter or no data"); + return -1; + } + + if (cmd_size < APDU_CHAIN_NUM_SIZE) { + return -1; + } + + p_cmd = cmd; + BIGENDIAN_TO_UINT32(p_cmd, chain_num); + p_cmd += APDU_CHAIN_NUM_SIZE; + cmd_size -= APDU_CHAIN_NUM_SIZE; + + if (chain_num == 0 || chain_num > APDU_CHAIN_MAX_SIZE) { + return -1; + } + + ese_data_init(&total_rsp); + for (i = 0; i < chain_num; i++) { + if (cmd_size <= APDU_CHAIN_HEADER_SIZE) { + ERR("invalid command chain header size"); + goto error; + } + + BIGENDIAN_TO_UINT32(p_cmd, seq); + p_cmd += APDU_CHAIN_SEQ_SIZE; + if (seq != i) { + ERR("invalid sequence number"); + goto error; + } + + BIGENDIAN_TO_UINT32(p_cmd, data_size); + p_cmd += APDU_CHAIN_CMD_SIZE; + BIGENDIAN_TO_UINT32(p_cmd, expected_size); + p_cmd += APDU_CHAIN_EXP_SIZE; + cmd_size -= APDU_CHAIN_HEADER_SIZE; + if (cmd_size < (data_size + expected_size) || data_size > (data_size + expected_size)) { + ERR("invalid send data or expected data size"); + goto error; + } + + cmd_data.data = p_cmd; + cmd_data.size = data_size; + rsp_data.data = NULL; + rsp_data.size = 0; + p_cmd += data_size; + cmd_size -= data_size; + +again: + if (iso7816_t1_send(ctx, &cmd_data) != ESESTATUS_SUCCESS) { + ERR("failed to send apdu"); + goto error; + } + + if (iso7816_t1_receive(ctx, &rsp_data) != ESESTATUS_SUCCESS) { + ERR("failed to receive response"); + if(rsp_data.data) { + ESE_FREE(rsp_data.data); + } + goto error; + } + + if (expected_size > 0) { + BIGENDIAN_TO_UINT32(p_cmd, expected_flag); + if (rsp_data.size < (expected_size - APDU_CHAIN_EXP_FLAG) || + memcmp(p_cmd + APDU_CHAIN_EXP_FLAG, + rsp_data.data + (rsp_data.size - (expected_size - APDU_CHAIN_EXP_FLAG)), + (expected_size - APDU_CHAIN_EXP_FLAG)) != 0) { + if (ese_data_store(&total_rsp, rsp_data.data, rsp_data.size, 0) != ESESTATUS_SUCCESS) { + goto error; + } + goto exit; + } + + if (expected_flag & EXPECTED_RESPONSE_AGAIN) { + if (rsp_data.size > 2) { + if (ese_data_store(&total_rsp, rsp_data.data, rsp_data.size - 2, 0) != ESESTATUS_SUCCESS) { + goto error; + } + } else { + ESE_FREE(rsp_data.data); + } + goto again; + } + + p_cmd += expected_size; + cmd_size -= expected_size; + } + + if (i < (chain_num - 1)) { + if (rsp_data.size > 2) { + if (ese_data_store(&total_rsp, rsp_data.data, rsp_data.size - 2, 0) != ESESTATUS_SUCCESS) { + goto error; + } + } else { + ESE_FREE(rsp_data.data); + } + } else { + if (ese_data_store(&total_rsp, rsp_data.data, rsp_data.size, 0) != ESESTATUS_SUCCESS) { + goto error; + } + } + } + +exit: + ese_data_get(&total_rsp, rsp, rsp_size); + return 0; +error: + ese_data_delete(&total_rsp); + return -1; +} + +static int star_dev_open(struct inode *inode, struct file *filp) +{ + sec_star_t *star = container_of(filp->private_data, + sec_star_t, misc); + int ret = 0; + + INFO("star_open\n"); + + mutex_lock(&(star->lock)); + + filp->private_data = star; + + if (star->access == 0) { +#ifdef FEATURE_STAR_WAKELOCK + if (!wake_lock_active(&star->snvm_wake_lock)) { + wake_lock(&star->snvm_wake_lock); + INFO("called to snvm_wake_lock\n"); + } +#endif + iso7816_t1_reset(star->protocol); + + ret = star->dev->power_on(); + if (ret < 0) { +#ifdef FEATURE_STAR_WAKELOCK + if (wake_lock_active(&star->snvm_wake_lock)) { + wake_unlock(&star->snvm_wake_lock); + INFO("called to snvm_wake_unlock\n"); + } +#endif + ERR("%s :failed to open star", __func__); + mutex_unlock(&(star->lock)); + return ret; + } + } + + star->access++; + + mutex_unlock(&(star->lock)); + return 0; +} + +static int star_dev_close(struct inode *inode, struct file *filp) +{ + sec_star_t *star = (sec_star_t *)filp->private_data; + int ret = 0; + + INFO("star_close\n"); + + if (star == NULL) { + return -EINVAL; + } + + mutex_lock(&(star->lock)); + + star->access--; + + if (star->access == 0) { + ret = star->dev->power_off(); + if (ret < 0) + ERR("%s :failed power_off", __func__); + +#ifdef FEATURE_STAR_WAKELOCK + if (wake_lock_active(&star->snvm_wake_lock)) { + wake_unlock(&star->snvm_wake_lock); + INFO("called to snvm_wake_unlock\n"); + } +#endif + } + + mutex_unlock(&(star->lock)); + return ret; +} + +static long star_dev_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + sec_star_t *star = (sec_star_t *)filp->private_data; + + if (star == NULL) { + return -EINVAL; + } + + if (_IOC_TYPE(cmd) != STAR_MAGIC_CODE) { + ERR("%s invalid magic. cmd=0x%X Received=0x%X Expected=0x%X\n", + __func__, cmd, _IOC_TYPE(cmd), STAR_MAGIC_CODE); + return -ENOTTY; + } + + mutex_lock(&(star->lock)); + + switch (cmd) { + case STAR_READ_SIZE: + INFO("%s read size : %u\n", __func__, star->rsp_size); + put_user(star->rsp_size, (unsigned int __user *)arg); + break; + case STAR_SET_DIRECT: + get_user(star->direct, (int __user *)arg); + INFO("%s set direct : %d\n", __func__, star->direct); + break; + case STAR_RESET_PROTOCOL: + INFO("%s reset protocol\n", __func__); + iso7816_t1_reset(star->protocol); + break; + case STAR_RESET_INTERFACE: + INFO("%s reset interface\n", __func__); + star->dev->reset(); + iso7816_t1_reset(star->protocol); + break; + default: + INFO("%s no matching ioctl! 0x%X\n", __func__, cmd); + mutex_unlock(&(star->lock)); + return -EINVAL; + } + + mutex_unlock(&(star->lock)); + return 0; +} + +static ssize_t star_dev_write(struct file *filp, const char __user *buf, size_t count, loff_t *offset) +{ + sec_star_t *star = (sec_star_t *)filp->private_data; + uint8_t *cmd = NULL; + uint32_t cmd_size = 0; + uint8_t *rsp = NULL; + uint32_t rsp_size = 0; + int ret = -EIO; + + if (star == NULL || count == 0) { + return -EINVAL; + } + + mutex_lock(&(star->lock)); + + if (star->rsp != NULL && star->rsp_size > 0) { + ESE_FREE(star->rsp); + star->rsp = NULL; + star->rsp_size = 0; + } + + cmd_size = (uint32_t)count; + cmd = ESE_MALLOC(cmd_size); + if (cmd == NULL) { + ERR("failed to allocate for i2c buf\n"); + mutex_unlock(&(star->lock)); + return -ENOMEM; + } + + if (copy_from_user(cmd, (void __user *)buf, cmd_size) > 0) { + ERR("%s: failed to copy from user space\n", __func__); + ret = -EFAULT; + goto error; + } + + if (star->direct > 0) { + if (ese_hal_send(star->hal, cmd, cmd_size) < 0) { + ERR("i2c_master_send failed\n"); + ret = -EIO; + goto error; + } + } else { + if (star_transceive(star->protocol, cmd, cmd_size, &rsp, &rsp_size) != ESESTATUS_SUCCESS) { + ERR("%s: failed to ese_transceive_chain\n", __func__); + ret = -EIO; + goto error; + } + } + + star->rsp = rsp; + star->rsp_size = rsp_size; + ret = (int)cmd_size; +error: + ESE_FREE(cmd); + mutex_unlock(&(star->lock)); + INFO("%s: count:%zu ret:%d\n", __func__, count, ret); + return ret; +} + +static ssize_t star_dev_read(struct file *filp, char __user *buf, size_t count, loff_t *offset) +{ + sec_star_t *star = (sec_star_t *)filp->private_data; + uint8_t *tmp = NULL; + + if (star == NULL || count == 0) { + return -EINVAL; + } + + mutex_lock(&(star->lock)); + if (star->direct > 0) { + tmp = ESE_MALLOC(count); + if (tmp == NULL) { + mutex_unlock(&(star->lock)); + return -ENOMEM; + } + + if (ese_hal_receive(star->hal, tmp, count) < 0) { + ERR("i2c_master_send failed\n"); + ESE_FREE(tmp); + mutex_unlock(&(star->lock)); + return -EIO; + } + + if (copy_to_user((void __user *)buf, tmp, count) > 0) { + ERR("copy_to_user failed\n"); + ESE_FREE(tmp); + mutex_unlock(&(star->lock)); + return -ENOMEM; + } + + ESE_FREE(tmp); + } else { + if (star->rsp == NULL || star->rsp_size == 0) { + mutex_unlock(&(star->lock)); + return -ENOSPC; + } + + if (star->rsp_size != count) { + ERR("mismatch response size\n"); + ESE_FREE(star->rsp); + star->rsp = NULL; + star->rsp_size = 0; + mutex_unlock(&(star->lock)); + return -E2BIG; + } + + if (copy_to_user((void __user *)buf, star->rsp, star->rsp_size) > 0) { + ERR("copy_to_user failed\n"); + ESE_FREE(star->rsp); + star->rsp = NULL; + star->rsp_size = 0; + mutex_unlock(&(star->lock)); + return -ENOMEM; + } + + ESE_FREE(star->rsp); + star->rsp = NULL; + star->rsp_size = 0; + } + INFO("%s: count:%zu\n", __func__, count); + mutex_unlock(&(star->lock)); + return (ssize_t)count; +} + +static const struct file_operations star_misc_fops = { + .owner = THIS_MODULE, + .read = star_dev_read, + .write = star_dev_write, + .open = star_dev_open, + .release = star_dev_close, + .unlocked_ioctl = star_dev_ioctl, +}; + +sec_star_t *star_open(star_dev_t *dev) +{ + sec_star_t *star = NULL; + int ret = -1; + + INFO("Version : %s\n", STAR_VERSION); + INFO("Entry : %s\n", __func__); + + star = ESE_MALLOC(sizeof(sec_star_t)); + if (star == NULL) { + return NULL; + } + + star->dev = dev; + star->protocol = NULL; + star->rsp = NULL; + star->rsp_size = 0; + star->access = 0; + star->direct = 0; + + star->hal = ese_hal_init(dev->hal_type, dev->client); + if (star->hal == NULL) { + ERR("%s :failed to init hal", __func__); + ESE_FREE(star); + return NULL; + } + + star->protocol = iso7816_t1_init(SEND_ADDRESS, RECEIVE_ADDRESS, star->hal); + if (star->protocol == NULL) { + ERR("%s :failed to open protocol", __func__); + ESE_FREE(star); + return NULL; + } + + mutex_init(&(star->lock)); +#ifdef FEATURE_STAR_WAKELOCK + wake_lock_init(&star->snvm_wake_lock, WAKE_LOCK_SUSPEND, "star_wake_lock"); +#endif + + star->misc.minor = MISC_DYNAMIC_MINOR; + star->misc.name = dev->name; + star->misc.fops = &star_misc_fops; + ret = misc_register(&(star->misc)); + if (ret < 0) { + ERR("misc_register failed! %d\n", ret); +#ifdef FEATURE_STAR_WAKELOCK + wake_lock_destroy(&star->snvm_wake_lock); +#endif + mutex_destroy(&(star->lock)); + ESE_FREE(star); + return NULL; + } + + INFO("Exit : %s\n", __func__); + return star; +} + +void star_close(sec_star_t *star) +{ + INFO("Entry : %s\n", __func__); + + if (star == NULL) { + return; + } + + misc_deregister(&(star->misc)); + + iso7816_t1_deinit(star->protocol); + ese_hal_release(star->hal); +#ifdef FEATURE_STAR_WAKELOCK + wake_lock_destroy(&star->snvm_wake_lock); +#endif + mutex_destroy(&(star->lock)); + ESE_FREE(star); + INFO("Exit : %s\n", __func__); +} + +MODULE_AUTHOR("sec"); +MODULE_DESCRIPTION("sec-star driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nfc/snvm/sec_star.h b/drivers/nfc/snvm/sec_star.h new file mode 100644 index 000000000000..9f1584096f16 --- /dev/null +++ b/drivers/nfc/snvm/sec_star.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2020 Samsung Electronics. 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 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __SEC_STAR__H +#define __SEC_STAR__H + +#include + +/* #define FEATURE_STAR_WAKELOCK */ + +#ifdef FEATURE_STAR_WAKELOCK +#include "snvm_wakelock.h" +#endif + +enum sec_hal_e { + SEC_HAL_I2C = 0, + SEC_HAL_SPI, +}; + +typedef struct star_dev_s { + const char *name; + int hal_type; + void *client; + int (*power_on)(void); + int (*power_off)(void); + int (*reset)(void); +} star_dev_t; + +typedef struct sec_star_s { + star_dev_t *dev; + struct mutex lock; +#ifdef FEATURE_STAR_WAKELOCK + struct snvm_wake_lock snvm_wake_lock; +#endif + struct miscdevice misc; + void *hal; + void *protocol; + unsigned char *rsp; + unsigned int rsp_size; + unsigned int access; + int direct; +} sec_star_t; + +sec_star_t *star_open(star_dev_t *dev); +void star_close(sec_star_t *star); + +#endif diff --git a/drivers/nfc/snvm/snvm_wakelock.h b/drivers/nfc/snvm/snvm_wakelock.h new file mode 100644 index 000000000000..3c8527272858 --- /dev/null +++ b/drivers/nfc/snvm/snvm_wakelock.h @@ -0,0 +1,74 @@ +/* + * SNVM Wakelock + * + * 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. + * + */ + +#ifndef _SNVM_WAKELOCK_H +#define _SNVM_WAKELOCK_H + +#include +#include +#include + +#define wake_lock_init(a, b, c) snvm_wake_lock_init(a, c) +#define wake_lock_destroy(a) snvm_wake_lock_destroy(a) +#define wake_lock_timeout(a, b) snvm_wake_lock_timeout(a, b) +#define wake_lock_active(a) snvm_wake_lock_active(a) +#define wake_lock(a) snvm_wake_lock(a) +#define wake_unlock(a) snvm_wake_unlock(a) + +struct snvm_wake_lock { + struct wakeup_source *ws; +}; + +static inline void snvm_wake_lock_init(struct snvm_wake_lock *lock, const char *name) +{ +#if CONFIG_SEC_SNVM_WAKELOCK_METHOD == 2 + lock->ws = wakeup_source_register(NULL, name); +#elif (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0)) || CONFIG_SEC_SNVM_WAKELOCK_METHOD == 1 + wakeup_source_init(lock->ws, name); /* 4.19 R */ + if (!(lock->ws)) { + lock->ws = wakeup_source_create(name); /* 4.19 Q */ + if (lock->ws) + wakeup_source_add(lock->ws); + } +#else + lock->ws = wakeup_source_register(NULL, name); /* 5.4 R */ +#endif +} + +static inline void snvm_wake_lock_destroy(struct snvm_wake_lock *lock) +{ + if (lock->ws) + wakeup_source_unregister(lock->ws); +} + +static inline void snvm_wake_lock(struct snvm_wake_lock *lock) +{ + if (lock->ws) + __pm_stay_awake(lock->ws); +} + +static inline void snvm_wake_lock_timeout(struct snvm_wake_lock *lock, long timeout) +{ + if (lock->ws) + __pm_wakeup_event(lock->ws, jiffies_to_msecs(timeout)); +} + +static inline void snvm_wake_unlock(struct snvm_wake_lock *lock) +{ + if (lock->ws) + __pm_relax(lock->ws); +} + +static inline int snvm_wake_lock_active(struct snvm_wake_lock *lock) +{ + return lock->ws->active; +} + +#endif diff --git a/drivers/optics/Kconfig b/drivers/optics/Kconfig new file mode 100755 index 000000000000..60b389123b12 --- /dev/null +++ b/drivers/optics/Kconfig @@ -0,0 +1,18 @@ +config SENSORS_FLICKER_SELF_TEST + tristate "Flicker EOL test use flash" + help + make Test environment using led flash. + +config SENSORS_STK6D2X + tristate "STK STK68210 ALS, Flicker sensor" + depends on I2C + help + If you say yes here, you get support for the STK STK68210. + This driver can also be built as a module. + If so, the module will be called flicker_sensor. + +config FLICKER_PWM_CALIBRATION + bool "Support Flicker OSC Calibration" + help + Using PWM pulse, Enhance resolution accuracy + This config just enable sensor side functions. diff --git a/drivers/optics/Makefile b/drivers/optics/Makefile new file mode 100755 index 000000000000..8fd3eec17d7d --- /dev/null +++ b/drivers/optics/Makefile @@ -0,0 +1,12 @@ +KBUILD_CFLAGS += -Wno-unused-variable -Wno-unused-function -Wno-unused-label -Wno-unused-parameter -Wno-frame-larger-than= -Wno-vla -Wno-uninitialized + +obj-$(CONFIG_SENSORS_FLICKER_SELF_TEST) += flicker_test.o + +KBUILD_CFLAGS += -Wno-unused-variable -Wno-unused-function -Wno-unused-label -Wno-unused-parameter -Wno-frame-larger-than= -Wno-vla -Wno-uninitialized -Wno-incompatible-function-pointer-types + +stk-src += stk6d2x.c stk6d2x_fifo.c stk6d2x_sec.c common_i2c.c common_timer.c common_gpio_sec.c + +flicker_sensor-y := $(stk-src:.c=.o) +flicker_sensor-$(CONFIG_FLICKER_PWM_CALIBRATION) += stk6d2x_cal.o + +obj-$(CONFIG_SENSORS_STK6D2X) += flicker_sensor.o \ No newline at end of file diff --git a/drivers/optics/common_define.h b/drivers/optics/common_define.h new file mode 100755 index 000000000000..e4f10ecb10fb --- /dev/null +++ b/drivers/optics/common_define.h @@ -0,0 +1,120 @@ +/* + * + * $Id: common_define.h + * + * Copyright (C) 2019 Bk, sensortek Inc. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + * + */ + +#ifndef __DEFINE_COMMON_H__ +#define __DEFINE_COMMON_H__ + +#include "platform_config.h" + +typedef struct stk_timer_info stk_timer_info; +typedef struct stk_gpio_info stk_gpio_info; + +struct stk_bus_ops +{ + int bustype; + int (*init)(void *); + int (*read)(int, unsigned int, unsigned char *); + int (*read_block)(int, unsigned int, int, void *); + int (*write)(int, unsigned int, unsigned char); + int (*write_block)(int, unsigned int, void *, int); + int (*read_modify_write)(int, unsigned int, unsigned char, unsigned char); + int (*remove)(void *); +}; + +typedef enum +{ + SECOND, + M_SECOND, + U_SECOND, + N_SECOND, +} TIMER_UNIT; + +typedef enum +{ + US_RANGE_DELAY, + MS_DELAY, +} BUSY_WAIT_TYPE; + +struct stk_timer_info +{ + char wq_name[4096]; + uint32_t interval_time; + TIMER_UNIT timer_unit; + void (*timer_cb)(stk_timer_info *t_info); + bool is_active; + bool is_exist; + bool is_periodic; + bool change_interval_time; + void *any; +} ; + +struct stk_timer_ops +{ + int (*register_timer)(stk_timer_info *); + int (*start_timer)(stk_timer_info *); + int (*stop_timer)(stk_timer_info *); + int (*remove)(stk_timer_info *); + void (*busy_wait)(unsigned long, unsigned long, BUSY_WAIT_TYPE); +}; + +typedef enum +{ + TRIGGER_RISING, + TRIGGER_FALLING, + TRIGGER_HIGH, + TRIGGER_LOW, +} GPIO_TRIGGER_TYPE; + +struct stk_gpio_info +{ + char wq_name[4096]; + char device_name[4096]; + void (*gpio_cb)(stk_gpio_info *gpio_info); + GPIO_TRIGGER_TYPE trig_type; + int int_pin; + int32_t irq; + bool is_active; + bool is_exist; + void *any; +} ; + +struct stk_gpio_ops +{ + int (*register_gpio_irq)(stk_gpio_info *); + int (*start_gpio_irq)(stk_gpio_info *); + int (*stop_gpio_irq)(stk_gpio_info *); + int (*remove)(stk_gpio_info *); +}; + +struct stk_storage_ops +{ + int (*init_storage)(void); + int (*write_to_storage)(char *, uint8_t *, int); + int (*read_from_storage)(char *, uint8_t *, int); + int (*remove)(void); +}; + +struct common_function +{ + const struct stk_bus_ops *bops; + const struct stk_timer_ops *tops; + const struct stk_gpio_ops *gops; +}; + +typedef struct stk_register_table +{ + uint8_t address; + uint8_t value; + uint8_t mask_bit; +} stk_register_table; + +#endif // __DEFINE_COMMON_H__ \ No newline at end of file diff --git a/drivers/optics/common_gpio_sec.c b/drivers/optics/common_gpio_sec.c new file mode 100755 index 000000000000..925ef1144dbe --- /dev/null +++ b/drivers/optics/common_gpio_sec.c @@ -0,0 +1,268 @@ +/* + * common_gpio_sec.c - Linux kernel modules for sensortek stk6d2x + * ambient light sensor (Common function) + * + * Copyright (C) 2019 Bk, sensortek 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct gpio_manager gpio_manager; + +struct gpio_manager +{ + struct work_struct stk_work; + struct workqueue_struct *stk_wq; + + stk_gpio_info *gpio_info; +} gpio_mgr_default = {.gpio_info = 0}; + +#define MAX_LINUX_GPIO_MANAGER_NUM 5 + +gpio_manager linux_gpio_mgr[MAX_LINUX_GPIO_MANAGER_NUM]; + +static gpio_manager* parser_work(struct work_struct *work) +{ + int gpio_idx = 0; + + if (!work) + { + return NULL; + } + + for (gpio_idx = 0; gpio_idx < MAX_LINUX_GPIO_MANAGER_NUM; gpio_idx ++) + { + if (&linux_gpio_mgr[gpio_idx].stk_work == work) + { + return &linux_gpio_mgr[gpio_idx]; + } + } + + return NULL; +} + +static void gpio_callback(struct work_struct *work) +{ + gpio_manager *gpio_mgr = parser_work(work); + + if (!gpio_mgr) + { + return; + } + + gpio_mgr->gpio_info->gpio_cb(gpio_mgr->gpio_info); + enable_irq(gpio_mgr->gpio_info->irq); +} + +static irqreturn_t stk_gpio_irq_handler(int irq, void *data) +{ + gpio_manager *pData = data; + disable_irq_nosync(irq); + queue_work(pData->stk_wq, &pData->stk_work); + return IRQ_HANDLED; +} + +int register_gpio_irq(stk_gpio_info *gpio_info) +{ + int gpio_idx = 0; + int irq; + int err = 0; + + if (!gpio_info) + { + return -1; + } + + for (gpio_idx = 0; gpio_idx < MAX_LINUX_GPIO_MANAGER_NUM; gpio_idx ++) + { + if (!linux_gpio_mgr[gpio_idx].gpio_info) + { + linux_gpio_mgr[gpio_idx].gpio_info = gpio_info; + break; + } + else + { + if (linux_gpio_mgr[gpio_idx].gpio_info == gpio_info) + { + //already register + return -1; + } + } + } + + if (gpio_idx >= MAX_LINUX_GPIO_MANAGER_NUM) + { + printk(KERN_ERR "%s: proper gpio not found", __func__); + return -1; + } + + printk(KERN_INFO "%s: irq num = %d \n", __func__, gpio_info->int_pin); + err = gpio_request(gpio_info->int_pin, "stk-int"); + + if (err < 0) + { + printk(KERN_ERR "%s: gpio_request, err=%d", __func__, err); + return err; + } + + linux_gpio_mgr[gpio_idx].stk_wq = create_singlethread_workqueue(linux_gpio_mgr[gpio_idx].gpio_info->wq_name); + INIT_WORK(&linux_gpio_mgr[gpio_idx].stk_work, gpio_callback); + err = gpio_direction_input(linux_gpio_mgr[gpio_idx].gpio_info->int_pin); + + if (err < 0) + { + printk(KERN_ERR "%s: gpio_direction_input, err=%d", __func__, err); + return err; + } + + irq = gpio_to_irq(linux_gpio_mgr[gpio_idx].gpio_info->int_pin); + printk(KERN_INFO "%s: int pin #=%d, irq=%d\n", __func__, linux_gpio_mgr[gpio_idx].gpio_info->int_pin, irq); + + if (irq < 0) + { + printk(KERN_ERR "irq number is not specified, irq # = %d, int pin=%d\n", irq, linux_gpio_mgr[gpio_idx].gpio_info->int_pin); + return irq; + } + + linux_gpio_mgr[gpio_idx].gpio_info->irq = irq; + + switch (linux_gpio_mgr[gpio_idx].gpio_info->trig_type) + { + case TRIGGER_RISING: + err = request_any_context_irq(irq, stk_gpio_irq_handler, IRQF_TRIGGER_RISING, \ + linux_gpio_mgr[gpio_idx].gpio_info->device_name, &linux_gpio_mgr[gpio_idx]); + break; + + case TRIGGER_FALLING: + err = request_any_context_irq(irq, stk_gpio_irq_handler, IRQF_TRIGGER_FALLING, \ + linux_gpio_mgr[gpio_idx].gpio_info->device_name, &linux_gpio_mgr[gpio_idx]); + break; + + case TRIGGER_HIGH: + case TRIGGER_LOW: + err = request_any_context_irq(irq, stk_gpio_irq_handler, IRQF_TRIGGER_LOW, \ + linux_gpio_mgr[gpio_idx].gpio_info->device_name, &linux_gpio_mgr[gpio_idx]); + break; + + default: + break; + } + + if (err < 0) + { + printk(KERN_WARNING "%s: request_any_context_irq(%d) failed for (%d)\n", __func__, irq, err); + goto err_request_any_context_irq; + } + + linux_gpio_mgr[gpio_idx].gpio_info->is_exist = true; + return 0; +err_request_any_context_irq: + gpio_free(linux_gpio_mgr[gpio_idx].gpio_info->int_pin); + return err; +} + +int start_gpio_irq(stk_gpio_info *gpio_info) +{ + int gpio_idx = 0; + + for (gpio_idx = 0; gpio_idx < MAX_LINUX_GPIO_MANAGER_NUM; gpio_idx ++) + { + if (linux_gpio_mgr[gpio_idx].gpio_info == gpio_info) + { + if (linux_gpio_mgr[gpio_idx].gpio_info->is_exist) + { + if (!linux_gpio_mgr[gpio_idx].gpio_info->is_active) + { + linux_gpio_mgr[gpio_idx].gpio_info->is_active = true; + } + } + + return 0; + } + } + + return -1; +} + +int stop_gpio_irq(stk_gpio_info *gpio_info) +{ + int gpio_idx = 0; + + for (gpio_idx = 0; gpio_idx < MAX_LINUX_GPIO_MANAGER_NUM; gpio_idx ++) + { + if (linux_gpio_mgr[gpio_idx].gpio_info == gpio_info) + { + if (linux_gpio_mgr[gpio_idx].gpio_info->is_exist) + { + if (linux_gpio_mgr[gpio_idx].gpio_info->is_active) + { + linux_gpio_mgr[gpio_idx].gpio_info->is_active = false; + } + } + + return 0; + } + } + + return -1; +} + +int remove_gpio_irq(stk_gpio_info *gpio_info) +{ + int gpio_idx = 0; + + for (gpio_idx = 0; gpio_idx < MAX_LINUX_GPIO_MANAGER_NUM; gpio_idx ++) + { + if (linux_gpio_mgr[gpio_idx].gpio_info == gpio_info) + { + if (linux_gpio_mgr[gpio_idx].gpio_info->is_exist) + { + if (linux_gpio_mgr[gpio_idx].gpio_info->is_active) + { + linux_gpio_mgr[gpio_idx].gpio_info->is_active = false; + free_irq(linux_gpio_mgr[gpio_idx].gpio_info->irq, &linux_gpio_mgr[gpio_idx]); + gpio_free(linux_gpio_mgr[gpio_idx].gpio_info->int_pin); + cancel_work_sync(&linux_gpio_mgr[gpio_idx].stk_work); + } + } + + return 0; + } + } + + return -1; +} + +const struct stk_gpio_ops stk_g_ops = +{ + .register_gpio_irq = register_gpio_irq, + .start_gpio_irq = start_gpio_irq, + .stop_gpio_irq = stop_gpio_irq, + .remove = remove_gpio_irq, + +}; diff --git a/drivers/optics/common_i2c.c b/drivers/optics/common_i2c.c new file mode 100755 index 000000000000..99f1d2186ace --- /dev/null +++ b/drivers/optics/common_i2c.c @@ -0,0 +1,347 @@ +/* + * common_i2c.c - Linux kernel modules for sensortek stk6d2x + * ambient light sensor (Common function) + * + * Copyright (C) 2019 Bk, sensortek 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define MAX_I2C_MANAGER_NUM 5 + +struct i2c_manager *pi2c_mgr[MAX_I2C_MANAGER_NUM] = {NULL}; + +int i2c_init(void* st) +{ + int i2c_idx = 0; + + if (!st) + { + return -1; + } + + for (i2c_idx = 0; i2c_idx < MAX_I2C_MANAGER_NUM; i2c_idx ++) + { + if (pi2c_mgr[i2c_idx] == (struct i2c_manager*)st) + { + printk(KERN_INFO "%s: i2c is exist\n", __func__); + break; + } + else if (pi2c_mgr[i2c_idx] == NULL) + { + pi2c_mgr[i2c_idx] = (struct i2c_manager*)st; + break; + } + } + + return i2c_idx; +} + +int i2c_reg_read(int i2c_idx, unsigned int reg, unsigned char *val) +{ + int error = 0; + struct i2c_manager *_pi2c = pi2c_mgr[i2c_idx]; + I2C_REG_ADDR_TYPE addr_type = _pi2c->addr_type; + mutex_lock(&_pi2c->lock); + + if (addr_type == ADDR_8BIT) + { + unsigned char reg_ = (unsigned char)(reg & 0xFF); + error = i2c_smbus_read_byte_data(_pi2c->client, reg_); + + if (error < 0) + { + dev_err(&_pi2c->client->dev, + "%s: failed to read reg:0x%x error:%d\n", + __func__, reg , error); + } + else + { + *(unsigned char *)val = error & 0xFF; + } + } + else if (addr_type == ADDR_16BIT) + { + } + + mutex_unlock(&_pi2c->lock); + return error; +} + +int i2c_reg_write(int i2c_idx, unsigned int reg, unsigned char val) +{ + int error = 0; + struct i2c_manager *_pi2c = pi2c_mgr[i2c_idx]; + I2C_REG_ADDR_TYPE addr_type = _pi2c->addr_type; + mutex_lock(&_pi2c->lock); + + if (addr_type == ADDR_8BIT) + { + unsigned char reg_ = (unsigned char)(reg & 0xFF); + error = i2c_smbus_write_byte_data(_pi2c->client, reg_, val); + } + else if (addr_type == ADDR_16BIT) + { + } + + mutex_unlock(&_pi2c->lock); + + if (error < 0) + { + dev_err(&_pi2c->client->dev, + "%s: failed to write reg:0x%x with val:0x%x error:%d\n", + __func__, reg, val, error); + } + + return error; +} + +int i2c_reg_write_block(int i2c_idx, unsigned int reg, void *val, int length) +{ + int error = 0; + struct i2c_manager *_pi2c = pi2c_mgr[i2c_idx]; + I2C_REG_ADDR_TYPE addr_type = _pi2c->addr_type; + mutex_lock(&_pi2c->lock); + + if (addr_type == ADDR_8BIT) + { + unsigned char reg_ = (unsigned char)(reg & 0xFF); + error = i2c_smbus_write_i2c_block_data(_pi2c->client, reg_, length, val); + } + else if (addr_type == ADDR_16BIT) + { + int i = 0; + unsigned char *buffer_inverse; + struct i2c_msg msgs; + buffer_inverse = kzalloc((sizeof(unsigned char) * (length + 2)), GFP_KERNEL); + buffer_inverse[0] = reg >> 8; + buffer_inverse[1] = reg & 0xff; + + for (i = 0; i < length; i ++) + { + buffer_inverse[2 + i] = *(u8*)((u8*)val + ((length - 1) - i)); + } + + msgs.addr = _pi2c->client->addr; + msgs.flags = _pi2c->client->flags & I2C_M_TEN; + msgs.len = length + 2; + msgs.buf = buffer_inverse; +#ifdef STK_RETRY_I2C + i = 0; + + do + { + error = i2c_transfer(_pi2c->client->adapter, &msgs, 1); + } + while (error != 1 && ++i < 3); + +#else + error = i2c_transfer(_pi2c->client->adapter, &msgs, 1); +#endif // STK_RETRY_I2C + kfree(buffer_inverse); + } + + mutex_unlock(&_pi2c->lock); + + if (error < 0) + { + dev_err(&_pi2c->client->dev, + "%s: failed to write reg:0x%x\n", + __func__, reg); + } + + return error; +} + +int i2c_reg_read_modify_write(int i2c_idx, unsigned int reg, unsigned char val, unsigned char mask) +{ + uint8_t rw_buffer = 0; + int error = 0; + struct i2c_manager *_pi2c = pi2c_mgr[i2c_idx]; + + if ((mask == 0xFF) || (mask == 0x0)) + { + error = i2c_reg_write(i2c_idx, reg, val); + + if (error < 0) + { + dev_err(&_pi2c->client->dev, + "%s: failed to write reg:0x%x with val:0x%x\n", + __func__, reg, val); + } + } + else + { + error = (uint8_t)i2c_reg_read(i2c_idx, reg, &rw_buffer); + + if (error < 0) + { + dev_err(&_pi2c->client->dev, + "%s: failed to read reg:0x%x\n", + __func__, reg); + return error; + } + else + { + rw_buffer = (rw_buffer & (~mask)) | (val & mask); + error = i2c_reg_write(i2c_idx, reg, rw_buffer); + + if (error < 0) + { + dev_err(&_pi2c->client->dev, + "%s: failed to write reg(mask):0x%x with val:0x%x\n", + __func__, reg, val); + } + } + } + + return error; +} + +int i2c_reg_read_block(int i2c_idx, unsigned int reg, int count, void *buf) +{ + int ret = 0; + // int loop_cnt = 0; + struct i2c_manager *_pi2c = pi2c_mgr[i2c_idx]; + I2C_REG_ADDR_TYPE addr_type = _pi2c->addr_type; + mutex_lock(&_pi2c->lock); + + if (addr_type == ADDR_8BIT) + { + struct i2c_msg msgs[2] = + { + { + .addr = _pi2c->client->addr, + .flags = 0, + .len = 1, + .buf = (u8*)® + }, + { + .addr = _pi2c->client->addr, + .flags = I2C_M_RD, + .len = count, + .buf = buf + } + }; + ret = i2c_transfer(_pi2c->client->adapter, msgs, 2); + + if (2 == ret) + { + ret = 0; + } + // unsigned char reg_ = (unsigned char)(reg & 0xFF); + + // while (count) + // { + // ret = i2c_smbus_read_i2c_block_data(_pi2c->client, reg_, + // (count > I2C_SMBUS_BLOCK_MAX) ? I2C_SMBUS_BLOCK_MAX : count, + // (buf + (loop_cnt * I2C_SMBUS_BLOCK_MAX)) + // ); + // (count > I2C_SMBUS_BLOCK_MAX) ? (count -= I2C_SMBUS_BLOCK_MAX) : (count -= count); + // loop_cnt ++; + // } + } + else if (addr_type == ADDR_16BIT) + { + int i = 0; + u16 reg_inverse = (reg & 0x00FF) << 8 | (reg & 0xFF00) >> 8; + int read_length = count; + u8 buffer_inverse[99] = { 0 }; + struct i2c_msg msgs[2] = + { + { + .addr = _pi2c->client->addr, + .flags = 0, + .len = 2, + .buf = (u8*)®_inverse + }, + { + .addr = _pi2c->client->addr, + .flags = I2C_M_RD, + .len = read_length, + .buf = buffer_inverse + } + }; +#ifdef STK_RETRY_I2C + i = 0; + + do + { + ret = i2c_transfer(_pi2c->client->adapter, msgs, 2); + } + while (ret != 2 && ++i < 3); + +#else + ret = i2c_transfer(_pi2c->client->adapter, msgs, 2); +#endif // STK_RETRY_I2C + + if (2 == ret) + { + ret = 0; + + for (i = 0; i < read_length; i ++) + { + *(u8*)((u8*)buf + i) = ((buffer_inverse[read_length - 1 - i])); + } + } + } + + mutex_unlock(&_pi2c->lock); + return ret; +} + +int i2c_remove(void* st) +{ + int i2c_idx = 0; + + if (!st) + { + return -1; + } + + for (i2c_idx = 0; i2c_idx < MAX_I2C_MANAGER_NUM; i2c_idx ++) + { + printk(KERN_INFO "%s: i2c_idx = %d\n", __func__, i2c_idx); + + if (pi2c_mgr[i2c_idx] == (struct i2c_manager*)st) + { + printk(KERN_INFO "%s: release i2c_idx = %d\n", __func__, i2c_idx); + pi2c_mgr[i2c_idx] = NULL; + break; + } + } + + return 0; +} + +const struct stk_bus_ops stk_i2c_bops = +{ + .bustype = BUS_I2C, + .init = i2c_init, + .write = i2c_reg_write, + .write_block = i2c_reg_write_block, + .read = i2c_reg_read, + .read_block = i2c_reg_read_block, + .read_modify_write = i2c_reg_read_modify_write, + .remove = i2c_remove, +}; diff --git a/drivers/optics/common_timer.c b/drivers/optics/common_timer.c new file mode 100755 index 000000000000..13d0f0bf1408 --- /dev/null +++ b/drivers/optics/common_timer.c @@ -0,0 +1,329 @@ +/* + * common_timer.c - Linux kernel modules for sensortek stk6d2x + * ambient light sensor (Common function) + * + * Copyright (C) 2019 Bk, sensortek 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct timer_manager timer_manager; + +struct timer_manager +{ + struct work_struct stk_work; + struct hrtimer stk_hrtimer; + struct workqueue_struct *stk_wq; + ktime_t timer_interval; + + stk_timer_info *timer_info; +} timer_mgr_default = {.timer_info = 0}; + +#define MAX_LINUX_TIMER_MANAGER_NUM 5 + +timer_manager linux_timer_mgr[MAX_LINUX_TIMER_MANAGER_NUM]; + +static timer_manager* parser_timer(struct hrtimer *timer) +{ + int timer_idx = 0; + + if (timer == NULL) + { + return NULL; + } + + for (timer_idx = 0; timer_idx < MAX_LINUX_TIMER_MANAGER_NUM; timer_idx ++) + { + if (&linux_timer_mgr[timer_idx].stk_hrtimer == timer) + { + return &linux_timer_mgr[timer_idx]; + } + } + + return NULL; +} + +static enum hrtimer_restart timer_func(struct hrtimer *timer) +{ + timer_manager *timer_mgr = parser_timer(timer); + + if (timer_mgr == NULL) + { + printk(KERN_ERR "%s: timer_mgr is NULL\n", __func__); + return HRTIMER_NORESTART; + } + + if (timer_mgr->stk_wq == NULL) + { + printk(KERN_ERR "%s: timer_mgr->stk_wq is NULL\n", __func__); + return HRTIMER_NORESTART; + } + + if (timer_mgr->timer_info == NULL) + { + printk(KERN_ERR "%s: timer_mgr->timer_info is NULL\n", __func__); + return HRTIMER_NORESTART; + } + + queue_work(timer_mgr->stk_wq, &timer_mgr->stk_work); + hrtimer_forward_now(&timer_mgr->stk_hrtimer, timer_mgr->timer_interval); + return HRTIMER_RESTART; +} + +static timer_manager* parser_work(struct work_struct *work) +{ + int timer_idx = 0; + + if (work == NULL) + { + return NULL; + } + + for (timer_idx = 0; timer_idx < MAX_LINUX_TIMER_MANAGER_NUM; timer_idx ++) + { + if (&linux_timer_mgr[timer_idx].stk_work == work) + { + return &linux_timer_mgr[timer_idx]; + } + } + + return NULL; +} + +static void timer_callback(struct work_struct *work) +{ + timer_manager *timer_mgr = parser_work(work); + + if (timer_mgr == NULL) + { + return; + } + + timer_mgr->timer_info->timer_cb(timer_mgr->timer_info); +} + +int register_timer(stk_timer_info *t_info) +{ + int timer_idx = 0; + + if (t_info == NULL) + { + return -1; + } + + for (timer_idx = 0; timer_idx < MAX_LINUX_TIMER_MANAGER_NUM; timer_idx ++) + { + if (!linux_timer_mgr[timer_idx].timer_info) + { + linux_timer_mgr[timer_idx].timer_info = t_info; + break; + } + else + { + if (linux_timer_mgr[timer_idx].timer_info == t_info) + { + //already register + if (linux_timer_mgr[timer_idx].timer_info->change_interval_time) + { + linux_timer_mgr[timer_idx].timer_info->change_interval_time = 0; + printk(KERN_ERR "%s: chang interval time\n", __func__); + switch (linux_timer_mgr[timer_idx].timer_info->timer_unit) + { + case N_SECOND: + linux_timer_mgr[timer_idx].timer_interval = ns_to_ktime(linux_timer_mgr[timer_idx].timer_info->interval_time); + break; + + case U_SECOND: + linux_timer_mgr[timer_idx].timer_interval = ns_to_ktime(linux_timer_mgr[timer_idx].timer_info->interval_time * NSEC_PER_USEC); + break; + + case M_SECOND: + linux_timer_mgr[timer_idx].timer_interval = ns_to_ktime(linux_timer_mgr[timer_idx].timer_info->interval_time * NSEC_PER_MSEC); + break; + + case SECOND: + break; + } + return 0; + } + printk(KERN_ERR "%s: this timer is registered\n", __func__); + return -1; + } + } + } + + // if search/register timer manager not successfully + if (timer_idx == MAX_LINUX_TIMER_MANAGER_NUM) + { + printk(KERN_ERR "%s: timer_idx out of range %d\n", __func__, timer_idx); + return -1; + } + + printk(KERN_ERR "%s: register timer name %s\n", __func__, linux_timer_mgr[timer_idx].timer_info->wq_name); + linux_timer_mgr[timer_idx].stk_wq = create_singlethread_workqueue(linux_timer_mgr[timer_idx].timer_info->wq_name); + if (linux_timer_mgr[timer_idx].stk_wq == NULL) + { + printk(KERN_ERR "%s: create single thread workqueue fail\n", __func__); + linux_timer_mgr[timer_idx].timer_info = 0; + return -1; + } + + INIT_WORK(&linux_timer_mgr[timer_idx].stk_work, timer_callback); + hrtimer_init(&linux_timer_mgr[timer_idx].stk_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + + switch (linux_timer_mgr[timer_idx].timer_info->timer_unit) + { + case N_SECOND: + linux_timer_mgr[timer_idx].timer_interval = ns_to_ktime(linux_timer_mgr[timer_idx].timer_info->interval_time); + break; + + case U_SECOND: + linux_timer_mgr[timer_idx].timer_interval = ns_to_ktime(linux_timer_mgr[timer_idx].timer_info->interval_time * NSEC_PER_USEC); + break; + + case M_SECOND: + linux_timer_mgr[timer_idx].timer_interval = ns_to_ktime(linux_timer_mgr[timer_idx].timer_info->interval_time * NSEC_PER_MSEC); + break; + + case SECOND: + break; + } + + linux_timer_mgr[timer_idx].stk_hrtimer.function = timer_func; + linux_timer_mgr[timer_idx].timer_info->is_exist = true; + return 0; +} + +int start_timer(stk_timer_info *t_info) +{ + int timer_idx = 0; + + for (timer_idx = 0; timer_idx < MAX_LINUX_TIMER_MANAGER_NUM; timer_idx ++) + { + if (linux_timer_mgr[timer_idx].timer_info == t_info) + { + if (linux_timer_mgr[timer_idx].timer_info->is_exist) + { + if (!linux_timer_mgr[timer_idx].timer_info->is_active) + { + hrtimer_start(&linux_timer_mgr[timer_idx].stk_hrtimer, linux_timer_mgr[timer_idx].timer_interval, HRTIMER_MODE_REL); + linux_timer_mgr[timer_idx].timer_info->is_active = true; + printk(KERN_ERR "%s: start timer name %s\n", __func__, linux_timer_mgr[timer_idx].timer_info->wq_name); + } + else + { + printk(KERN_INFO "%s: %s was already running\n", __func__, linux_timer_mgr[timer_idx].timer_info->wq_name); + } + } + + return 0; + } + } + + return -1; +} + +int stop_timer(stk_timer_info *t_info) +{ + int timer_idx = 0; + + for (timer_idx = 0; timer_idx < MAX_LINUX_TIMER_MANAGER_NUM; timer_idx ++) + { + if (linux_timer_mgr[timer_idx].timer_info == t_info) + { + if (linux_timer_mgr[timer_idx].timer_info->is_exist) + { + if (linux_timer_mgr[timer_idx].timer_info->is_active) + { + hrtimer_cancel(&linux_timer_mgr[timer_idx].stk_hrtimer); + drain_workqueue(linux_timer_mgr[timer_idx].stk_wq); + linux_timer_mgr[timer_idx].timer_info->is_active = false; + printk(KERN_ERR "%s: stop timer name %s\n", __func__, linux_timer_mgr[timer_idx].timer_info->wq_name); + } + else + { + printk(KERN_ERR "%s: %s stop already stop\n", __func__, linux_timer_mgr[timer_idx].timer_info->wq_name); + } + } + + return 0; + } + } + + return -1; +} + +int remove_timer(stk_timer_info *t_info) +{ + int timer_idx = 0; + + for (timer_idx = 0; timer_idx < MAX_LINUX_TIMER_MANAGER_NUM; timer_idx ++) + { + if (linux_timer_mgr[timer_idx].timer_info == t_info) + { + if (linux_timer_mgr[timer_idx].timer_info->is_exist) + { + if (linux_timer_mgr[timer_idx].timer_info->is_active) + { + hrtimer_try_to_cancel(&linux_timer_mgr[timer_idx].stk_hrtimer); + destroy_workqueue(linux_timer_mgr[timer_idx].stk_wq); + cancel_work_sync(&linux_timer_mgr[timer_idx].stk_work); + linux_timer_mgr[timer_idx].timer_info->is_active = false; + linux_timer_mgr[timer_idx].timer_info->is_exist = false; + linux_timer_mgr[timer_idx].timer_info = 0; + } + } + + return 0; + } + } + + return -1; +} + +void busy_wait(unsigned long min, unsigned long max, BUSY_WAIT_TYPE mode) +{ + if ((!min) || (!max) || (max < min)) + { + return; + } + + if (mode == US_RANGE_DELAY) + { + usleep_range(min, max); + } + + if (mode == MS_DELAY) + { + msleep(max); + } +} +const struct stk_timer_ops stk_t_ops = +{ + .register_timer = register_timer, + .start_timer = start_timer, + .stop_timer = stop_timer, + .remove = remove_timer, + .busy_wait = busy_wait, +}; diff --git a/drivers/optics/flicker_test.c b/drivers/optics/flicker_test.c new file mode 100755 index 000000000000..1ffc4b6d408f --- /dev/null +++ b/drivers/optics/flicker_test.c @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2021 Samsung Electronics Co., Ltd. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License, 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "flicker_test.h" + +static struct test_data *data = NULL; +static struct result_data *test_result = NULL; + +static void (*err_handler)(void); + + +void als_eol_set_err_handler(void (*handler)(void)){ + err_handler = handler; +} +EXPORT_SYMBOL_GPL(als_eol_set_err_handler); + +/** + * als_eol_update_als - Record every test value + * + * @awb : current 'awb' value + * (You can use any value as you need. Maybe Infrared value will be used in most cases) + * @clear : current clear value detected by sensor. It means visible light in light spectrum + * @wideband : current Wideband value detected by sensor. + * wideband light include visible light and infrared. + * + */ +void als_eol_update_als(int awb, int clear, int wideband, int uv) +{ + if (data->eol_enable && data->eol_count >= EOL_SKIP_COUNT) { + data->eol_awb += awb; + data->eol_clear += clear; + data->eol_wideband += wideband; + data->eol_uv += uv; + data->eol_sum_count++; + } + + if (data->eol_enable && data->eol_state < EOL_STATE_DONE) { + switch (data->eol_state) { + case EOL_STATE_INIT: + memset(test_result, 0, sizeof(struct result_data)); + + data->eol_count = 0; + data->eol_sum_count = 0; + data->eol_awb = 0; + data->eol_clear = 0; + data->eol_wideband = 0; + data->eol_flicker = 0; + data->eol_flicker_sum = 0; + data->eol_flicker_sum_count = 0; + data->eol_flicker_count = 0; + data->eol_flicker_skip_count = EOL_FLICKER_SKIP_COUNT; + data->eol_state = EOL_STATE_100; + data->eol_pulse_count = 0; + data->eol_uv = 0; + break; + default: + data->eol_count++; + printk(KERN_INFO"%s - eol_state:%d, eol_cnt:%d (sum_cnt:%d), flk:%d (flk_cnt:%d, sum_cnt:%d), ir:%d, clear:%d, wide:%d, uv:%d\n", __func__, + data->eol_state, data->eol_count, data->eol_sum_count, data->eol_flicker, data->eol_flicker_count, data->eol_flicker_sum_count, awb, clear, wideband, uv); + + if ((data->eol_count >= (EOL_COUNT + EOL_SKIP_COUNT)) && (data->eol_flicker_count >= (EOL_COUNT + data->eol_flicker_skip_count))) { + if (data->eol_flicker_sum_count) { + test_result->flicker[data->eol_state] = data->eol_flicker_sum / data->eol_flicker_sum_count; + } else { + test_result->flicker[data->eol_state] = data->eol_flicker; + } + + if (data->eol_sum_count) { + test_result->awb[data->eol_state] = data->eol_awb / data->eol_sum_count; + test_result->clear[data->eol_state] = data->eol_clear / data->eol_sum_count; + test_result->wideband[data->eol_state] = data->eol_wideband / data->eol_sum_count; + test_result->uv[data->eol_state] = data->eol_uv / data->eol_sum_count; + } else { + test_result->awb[data->eol_state] = awb; + test_result->clear[data->eol_state] = clear; + test_result->wideband[data->eol_state] = wideband; + test_result->uv[data->eol_state] = uv; + } + + printk(KERN_INFO"%s - eol_state = %d, pulse_count = %d, flicker_result = %d Hz (%d/%d)\n", + __func__, data->eol_state, data->eol_pulse_count, test_result->flicker[data->eol_state], data->eol_flicker_sum, data->eol_flicker_sum_count); + + data->eol_count = 0; + data->eol_sum_count = 0; + data->eol_awb = 0; + data->eol_clear = 0; + data->eol_wideband = 0; + data->eol_flicker = 0; + data->eol_flicker_sum = 0; + data->eol_flicker_sum_count = 0; + data->eol_flicker_count = 0; + data->eol_pulse_count = 0; + data->eol_uv = 0; + data->eol_state++; + } + break; + } + } +} +EXPORT_SYMBOL_GPL(als_eol_update_als); + +/** + * als_eol_update_flicker - Record every test value + * + * @flicker: current Flicker value detected by sensor. + */ +void als_eol_update_flicker(int Hz) +{ + data->eol_flicker_count++; + data->eol_flicker = Hz; + + if ((data->eol_flicker_skip_count < EOL_SKIP_COUNT) && (data->eol_flicker_count >= data->eol_count)) { + data->eol_flicker_skip_count = EOL_SKIP_COUNT; + } + + if ((data->eol_enable && Hz != 0) && (data->eol_flicker_count > data->eol_flicker_skip_count)) { + data->eol_flicker_sum += Hz; + data->eol_flicker_sum_count++; + } +} +EXPORT_SYMBOL_GPL(als_eol_update_flicker); + +void set_led_mode(int led_curr) +{ +#if IS_ENABLED (CONFIG_LEDS_S2MPB02) + s2mpb02_led_en(led_mode, led_curr, S2MPB02_LED_TURN_WAY_GPIO); +#elif IS_ENABLED(CONFIG_LEDS_KTD2692) + ktd2692_led_mode_ctrl(led_mode, led_curr); +#elif IS_ENABLED(CONFIG_LEDS_AW36518_FLASH) + if (led_curr) + aw36518_enable_flicker(led_curr, true); + else + aw36518_enable_flicker(0, false); +#elif IS_ENABLED(CONFIG_LEDS_QTI_FLASH) && (IS_ENABLED(CONFIG_SENSORS_STK6D2X) || IS_ENABLED(CONFIG_SENSORS_TSL2511)) + if(led_curr) { + qti_flash_led_set_strobe_sel(switch3_trigger, 1); + led_trigger_event(torch2_trigger, led_curr/2); + led_trigger_event(torch3_trigger, led_curr/2); + led_trigger_event(switch3_trigger, 1); + } else { + qti_flash_led_set_strobe_sel(switch3_trigger, 0); + led_trigger_event(switch3_trigger, 0); + } +#endif +} + +void als_eol_set_env(bool torch, int intensity) +{ +#if IS_ENABLED(CONFIG_LEDS_S2MPB02) + led_curr = (intensity/20); + led_mode = S2MPB02_TORCH_LED_1; +#elif IS_ENABLED(CONFIG_LEDS_KTD2692) + led_curr = 1400; + led_mode = KTD2692_FLICKER_FLASH_MODE; +#elif IS_ENABLED(CONFIG_LEDS_AW36518_FLASH) + led_curr = intensity; +#elif IS_ENABLED(CONFIG_LEDS_QTI_FLASH) + led_curr = intensity; +#endif + printk(KERN_INFO "%s - gpio:%d intensity:%d(%d) led_mode:%d", + __func__, gpio_torch, intensity, led_curr, led_mode); +} +EXPORT_SYMBOL_GPL(als_eol_set_env); + +/** + * als_eol_mode - start LED flicker loop + * + * Return result_data + * MUST call als_eol_update* functions to notify the sensor output!! + */ +struct result_data* als_eol_mode(void) +{ + int pulse_duty = 0; + int curr_state = EOL_STATE_INIT; + int ret = 0; + u32 prev_eol_count = 0, loop_count = 0; + + set_led_mode(0); + + ret = gpio_request(gpio_torch, NULL); + if (ret < 0) + return NULL; + + data->eol_state = EOL_STATE_INIT; + data->eol_enable = 1; + + printk(KERN_INFO"%s - eol_loop start", __func__); + while (data->eol_state < EOL_STATE_DONE) { + if (prev_eol_count == data->eol_count) + loop_count++; + else + loop_count = 0; + + prev_eol_count = data->eol_count; + + switch (data->eol_state) { + case EOL_STATE_INIT: + pulse_duty = 1000; + break; + case EOL_STATE_100: + pulse_duty = DEFAULT_DUTY_50HZ; + break; + case EOL_STATE_120: + pulse_duty = DEFAULT_DUTY_60HZ; + break; + default: + break; + } + + if (data->eol_state >= EOL_STATE_100) { + if (curr_state != data->eol_state) { +#if IS_ENABLED(CONFIG_LEDS_KTD2692) || IS_ENABLED(CONFIG_LEDS_AW36518_FLASH) + if(ret >= 0) { + gpio_free(gpio_torch); + } +#endif + set_led_mode(led_curr); + curr_state = data->eol_state; + +#if IS_ENABLED(CONFIG_LEDS_KTD2692) || IS_ENABLED(CONFIG_LEDS_AW36518_FLASH) + ret = gpio_request(gpio_torch, NULL); + if (ret < 0) + break; +#endif + } else { + gpio_direction_output(gpio_torch, 1); + udelay(pulse_duty); + gpio_direction_output(gpio_torch, 0); + data->eol_pulse_count++; + } + } + + if (loop_count > 1000) { + printk(KERN_ERR "%s - ERR NO Interrupt", __func__); + // Add Debug Code + if (err_handler) + err_handler(); + break; + } + + udelay(pulse_duty); + } + printk(KERN_INFO"%s - eol_loop end",__func__); + if(ret >= 0) { + gpio_free(gpio_torch); + } + set_led_mode(0); + + if (data->eol_state >= EOL_STATE_DONE) { + if(test_result->clear[EOL_STATE_100] != 0) { + test_result->ratio[EOL_STATE_100] = test_result->awb[EOL_STATE_100] * 100 / test_result->clear[EOL_STATE_100]; + } + if(test_result->clear[EOL_STATE_120] != 0) { + test_result->ratio[EOL_STATE_120] = test_result->awb[EOL_STATE_120] * 100 / test_result->clear[EOL_STATE_120]; + } + } else { + printk(KERN_ERR "%s - abnormal termination\n", __func__); + } + printk(KERN_INFO "%s - RESULT: flicker:%d|%d awb:%d|%d clear:%d|%d wide:%d|%d ratio:%d|%d uv:%d|%d", __func__, + test_result->flicker[EOL_STATE_100], test_result->flicker[EOL_STATE_120], + test_result->awb[EOL_STATE_100], test_result->awb[EOL_STATE_120], + test_result->clear[EOL_STATE_100], test_result->clear[EOL_STATE_120], + test_result->wideband[EOL_STATE_100], test_result->wideband[EOL_STATE_120], + test_result->ratio[EOL_STATE_100], test_result->ratio[EOL_STATE_120], + test_result->uv[EOL_STATE_100], test_result->uv[EOL_STATE_120]); + + data->eol_enable = 0; + + return test_result; +} +EXPORT_SYMBOL_GPL(als_eol_mode); + +int als_eol_parse_dt(void) +{ + struct device_node *np; +#if KERNEL_VERSION(6, 2, 0) > LINUX_VERSION_CODE + enum of_gpio_flags flags; +#endif + + np = of_find_node_by_name(NULL, LED_DT_NODE_NAME); + if (np == NULL) { + printk(KERN_ERR "%s - Can't find node", __func__); + return -1; + } + +#if KERNEL_VERSION(6, 2, 0) <= LINUX_VERSION_CODE + gpio_torch = of_get_named_gpio(np, "flicker_test,torch-gpio", 0); +#else + gpio_torch = of_get_named_gpio_flags(np, "flicker_test,torch-gpio", 0, &flags); +#endif + + printk(KERN_INFO "%s - torch : %d", __func__, gpio_torch); + + return 0; +} + +static int __init als_eol_init(void) +{ + int ret = 0; + printk(KERN_INFO "%s - EOL_TEST Module init", __func__); + + data = (struct test_data*)kzalloc(sizeof(struct test_data), GFP_KERNEL); + if (data == NULL) { + printk(KERN_INFO "%s - data alloc err", __func__); + return -1; + } + + test_result = (struct result_data*)kzalloc(sizeof(struct result_data), GFP_KERNEL); + if (test_result == NULL) { + printk(KERN_INFO "%s - test_result alloc err", __func__); + return -1; + } + + ret = als_eol_parse_dt(); + if (ret < 0) { + printk(KERN_ERR "%s - dt parse fail!", __func__); + return ret; + } + +#if !IS_ENABLED(CONFIG_LEDS_S2MPB02) && !IS_ENABLED(CONFIG_LEDS_KTD2692) && IS_ENABLED(CONFIG_LEDS_QTI_FLASH) \ + && (IS_ENABLED(CONFIG_SENSORS_STK6D2X) || IS_ENABLED(CONFIG_SENSORS_TSL2511)) && !IS_ENABLED(CONFIG_LEDS_AW36518_FLASH) + led_trigger_register_simple("torch2_trigger", &torch2_trigger); + led_trigger_register_simple("torch3_trigger", &torch3_trigger); + led_trigger_register_simple("switch3_trigger", &switch3_trigger); +#endif + + return ret; +} + +static void __exit als_eol_exit(void) +{ + printk(KERN_INFO "%s - EOL_TEST Module exit\n", __func__); + +#if !IS_ENABLED(CONFIG_LEDS_S2MPB02) && !IS_ENABLED(CONFIG_LEDS_KTD2692) && IS_ENABLED(CONFIG_LEDS_QTI_FLASH) \ + && (IS_ENABLED(CONFIG_SENSORS_STK6D2X) || IS_ENABLED(CONFIG_SENSORS_TSL2511)) && !IS_ENABLED(CONFIG_LEDS_AW36518_FLASH) + led_trigger_unregister_simple(torch2_trigger); + led_trigger_unregister_simple(torch3_trigger); + led_trigger_unregister_simple(switch3_trigger); +#endif + + if(data) { + kfree(data); + } + + if(test_result) { + kfree(test_result); + } +} + +module_init(als_eol_init); +module_exit(als_eol_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("Flicker Sensor Test Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/optics/flicker_test.h b/drivers/optics/flicker_test.h new file mode 100755 index 000000000000..6036fe4954bd --- /dev/null +++ b/drivers/optics/flicker_test.h @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2021 Samsung Electronics Co., Ltd. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License, 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef EOL_TEST_H +#define EOL_TEST_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Flicker Sensor Self test module + * + * Uses LEDs in the system. + * If you want to add a new LED, + * add a header below and define dt node name. + * + * You can build this with CONFIG_EOL_TEST + */ + +#if IS_ENABLED(CONFIG_LEDS_S2MPB02) +#include +#elif IS_ENABLED(CONFIG_LEDS_KTD2692) +#include +#elif IS_ENABLED(CONFIG_LEDS_AW36518_FLASH) +#include +#elif IS_ENABLED(CONFIG_LEDS_QTI_FLASH) && (IS_ENABLED(CONFIG_SENSORS_STK6D2X) || IS_ENABLED(CONFIG_SENSORS_TSL2511)) +#include +#include +DEFINE_LED_TRIGGER(torch2_trigger); +DEFINE_LED_TRIGGER(torch3_trigger); +DEFINE_LED_TRIGGER(switch3_trigger); +#endif + +#define LED_DT_NODE_NAME "flicker_test" + +#define DEFAULT_DUTY_50HZ 5000 +#define DEFAULT_DUTY_60HZ 4166 + +#define MAX_TEST_RESULT 256 +#if IS_ENABLED(CONFIG_SENSORS_STK6D2X) +#define EOL_COUNT 4 +#define EOL_SKIP_COUNT 4 +#else +#define EOL_COUNT 5 +#define EOL_SKIP_COUNT 5 +#endif +#define EOL_FLICKER_SKIP_COUNT 2 + +static int gpio_torch; +static int led_curr; +static int led_mode; + +static char result_str[MAX_TEST_RESULT]; + +enum TEST_STATE { + EOL_STATE_INIT = -1, + EOL_STATE_100, + EOL_STATE_120, + EOL_STATE_DONE, +}; + +struct test_data { + u8 eol_enable; + s16 eol_state; + u32 eol_count; + u32 eol_sum_count; + u32 eol_awb; + u32 eol_clear; + u32 eol_wideband; + u32 eol_flicker; + u32 eol_flicker_sum; + u32 eol_flicker_sum_count; + u32 eol_flicker_count; + u32 eol_flicker_skip_count; + u32 eol_pulse_count; + u32 eol_uv; +}; + +struct result_data { + int result; + u32 flicker[EOL_STATE_DONE]; + u32 awb[EOL_STATE_DONE]; + u32 clear[EOL_STATE_DONE]; + u32 wideband[EOL_STATE_DONE]; + u32 ratio[EOL_STATE_DONE]; + u32 uv[EOL_STATE_DONE]; +}; + +enum GPIO_TYPE { + EOL_FLASH, + EOL_TORCH, +}; + +void als_eol_set_env(bool torch, int intensity); +struct result_data* als_eol_mode(void); + +void als_eol_update_als(int awb, int clear, int wideband, int uv); +void als_eol_update_flicker(int Hz); +void als_eol_set_err_handler(void (*handler)(void)); +#endif /* EOL_TEST_H */ diff --git a/drivers/optics/platform_config.h b/drivers/optics/platform_config.h new file mode 100755 index 000000000000..d5b1cdcc8e9d --- /dev/null +++ b/drivers/optics/platform_config.h @@ -0,0 +1,74 @@ +/* + * + * $Id: platform_config.h + * + * Copyright (C) 2019 Bk, sensortek Inc. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + * + */ + +#ifndef __PLATFORM_CONFIG_H__ +#define __PLATFORM_CONFIG_H__ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_IIO + #include + #include + #include + #include + #include + #include + #include + +#endif + +typedef enum +{ + ADDR_8BIT, + ADDR_16BIT, +} I2C_REG_ADDR_TYPE; + +typedef enum +{ + SPI_MODE0, + SPI_MODE1, + SPI_MODE2, + SPI_MODE3, +} SPI_TRANSFER_MODE; + +struct spi_manager +{ + struct spi_device *spi; + struct mutex lock; + SPI_TRANSFER_MODE trans_mode; + void *any; + u8 *spi_buffer; /* SPI buffer, used for SPI transfer. */ +} ; + +struct i2c_manager +{ + struct i2c_client *client; + struct mutex lock; + I2C_REG_ADDR_TYPE addr_type; + void *any; +} ; + +#define kzalloc(size, mode) kzalloc(size, mode) +#define kfree(ptr) kfree(ptr) + +extern const struct stk_bus_ops stk_spi_bops; +extern const struct stk_bus_ops stk_i2c_bops; +extern const struct stk_timer_ops stk_t_ops; +extern const struct stk_gpio_ops stk_g_ops; + +#endif // __PLATFORM_CONFIG_H__ \ No newline at end of file diff --git a/drivers/optics/stk6d2x.c b/drivers/optics/stk6d2x.c new file mode 100755 index 000000000000..d1222498e017 --- /dev/null +++ b/drivers/optics/stk6d2x.c @@ -0,0 +1,1368 @@ +/* + * stk6d2x.c - Linux kernel modules for sensortek stk6d2x + * ambient light sensor (driver) + * + * Copyright (C) 2012~2018 Bk, sensortek 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include + +/**************************************************************************************************** +* Declaration function +****************************************************************************************************/ +static int32_t stk6d2x_set_als_thd(struct stk6d2x_data *alps_data, uint16_t thd_h, uint16_t thd_l); +void stk6d2x_get_data_polling(stk_timer_info *t_info); + +#ifdef STK_FIFO_ENABLE + void stk6d2x_fifo_switch_it(struct stk6d2x_data *alps_data, bool is_long_it); +#endif + +/**************************************************************************************************** +* Declaration Initiation Setting +****************************************************************************************************/ +stk6d2x_register_table stk6d2x_default_register_table[] = +{ + {STK6D2X_REG_ALS01_DGAIN, ((STK6D2X_ALS_DGAIN128 << STK6D2X_ALS0_DGAIN_SHIFT) | STK6D2X_ALS_DGAIN128), 0xFF}, + {STK6D2X_REG_ALS2_DGAIN, (STK6D2X_ALS_DGAIN128 << STK6D2X_ALS2_DGAIN_SHIFT), 0xFF}, +#ifndef STK_FIFO_ENABLE + {STK6D2X_REG_IT1, ((STK6D2X_ALS_IT50 >> 8) & STK6D2X_ALS_IT1_MASK), 0xFF}, + {STK6D2X_REG_IT2, (STK6D2X_ALS_IT50 & 0xFF), 0xFF}, +#endif + {STK6D2X_REG_WAIT1, 0x00, 0xFF}, + {STK6D2X_REG_WAIT2, 0x00, 0xFF}, +#ifdef STK_DATA_SUMMATION + {STK6D2X_REG_ALS_SUM_GAIN1, (STK6D2X_ALS_SUM_GAIN_DIV256 << 4) | STK6D2X_ALS_SUM_GAIN_DIV256, 0xFF}, + {STK6D2X_REG_ALS_SUM_GAIN2, (STK6D2X_ALS_SUM_GAIN_DIV256 << 4), 0xFF}, + {STK6D2X_REG_ALS_SUM, 0x32, 0xFF}, +#endif +#ifndef STK_FIFO_ENABLE + {STK6D2X_REG_ALS_IT_EXT, 0x00, 0xFF}, +#endif + {STK6D2X_REG_ALS_PRST, 0x00, 0xFF}, +#ifdef STK_ALS_AGC + {STK6D2X_REG_AGC1, 0x0F, 0xFF}, + {STK6D2X_REG_AGC2, 0x70, 0xFF}, +#endif +#ifdef STK_FIFO_ENABLE + {STK6D2X_REG_FIFO1, STK6D2X_FIFO_SEL_STA01_STA2_ALS0_ALS1_ALS2, 0xFF}, + {STK6D2X_REG_IT1, ((STK_FLICKER_IT >> 8) & STK6D2X_ALS_IT1_MASK), 0xFF}, + {STK6D2X_REG_IT2, (STK_FLICKER_IT & 0xFF), 0xFF}, + {STK6D2X_REG_ALS_IT_EXT, STK_FLICKER_EXT_IT, 0xFF}, +#endif + {STK6D2X_REG_ALS_AGAIN, 0x00, 0xFF}, + {0xF3, 0x03, 0xFF}, + {0x4F, 0x0F, 0xFF}, + {0x50, 0x04, 0xFF}, + {0xD1, 0x16, 0x1F}, //GC Setting + {0xE3, 0x80, 0xFF}, //WDT 50ms + {0xE5, 0x80, 0xFF}, //WDT 50ms + {0xEA, 0x80, 0xFF}, //WDT 50ms + {0xEC, 0x80, 0xFF}, //WDT 50ms +}; + +stk6d2x_register_table stk6d2x_default_otp_table[] = +{ + {0x93, 0x01, 0x01}, + {0x90, 0x11, 0x3f}, + {0x92, 0x01, 0x02}, + {0x93, 0x00, 0x01}, +}; + +uint8_t stk6d2x_pid_list[] = {0x30}; + +uint32_t stk6d2x_power(uint32_t base, uint32_t exp) +{ + uint32_t result = 1; + + while (exp) + { + if (exp & 1) + result *= base; + + exp >>= 1; + base *= base; + } + + return result; +} + +/**************************************************************************************************** +* stk6d2x.c API +****************************************************************************************************/ +int32_t stk6d2x_request_registry(struct stk6d2x_data *alps_data) +{ + uint32_t FILE_SIZE = sizeof(stk6d2x_cali_table); + uint8_t *file_out_buf; + int32_t err = 0; + ALS_info("start\n"); + file_out_buf = kzalloc(FILE_SIZE, GFP_KERNEL); + memset(file_out_buf, 0, FILE_SIZE); + return 0; +} + +int32_t stk6d2x_update_registry(struct stk6d2x_data *alps_data) +{ + uint32_t FILE_SIZE = sizeof(stk6d2x_cali_table); + uint8_t *file_buf; + int32_t err = 0; + ALS_info("start\n"); + file_buf = kzalloc(FILE_SIZE, GFP_KERNEL); + memset(file_buf, 0, FILE_SIZE); + memcpy(file_buf, &alps_data->cali_info.cali_para.als_version, FILE_SIZE); + //err = STK6D2X_W_TO_STORAGE(alps_data, STK6D2X_CALI_FILE, file_buf, FILE_SIZE); + + if (err < 0) + { + kfree(file_buf); + return -1; + } + + ALS_info("Done\n"); + kfree(file_buf); + return 0; +} + +void stk6d2x_get_reg_default_setting(uint8_t reg, uint16_t* value) +{ + uint16_t reg_count = 0, reg_num = sizeof(stk6d2x_default_register_table) / sizeof(stk6d2x_register_table); + + for (reg_count = 0; reg_count < reg_num; reg_count++) + { + if (stk6d2x_default_register_table[reg_count].address == reg) + { + *value = (uint16_t)stk6d2x_default_register_table[reg_count].value; + return; + } + } + + *value = 0x7FFF; +} + +static int32_t stk6d2x_register_timer(struct stk6d2x_data *alps_data, stk6d2x_timer_type timer_type) +{ + ALS_info("regist 0x%X timer\n", timer_type); + + switch (timer_type) + { + case STK6D2X_DATA_TIMER_ALPS: + strcpy(alps_data->alps_timer_info.wq_name, "stk_alps_wq"); + alps_data->alps_timer_info.timer_unit = M_SECOND; + alps_data->alps_timer_info.interval_time = alps_data->als_info.als_it; + alps_data->alps_timer_info.timer_cb = stk6d2x_get_data_polling; + alps_data->alps_timer_info.is_active = false; + alps_data->alps_timer_info.is_exist = false; + alps_data->alps_timer_info.any = alps_data; + STK6D2X_TIMER_REGISTER(alps_data, &alps_data->alps_timer_info); + break; + } + + return 0; +} + +void stk6d2x_als_set_new_thd(struct stk6d2x_data *alps_data, uint16_t alscode) +{ + uint16_t high_thd, low_thd; + high_thd = alscode + STK6D2X_ALS_THRESHOLD; + low_thd = (alscode > STK6D2X_ALS_THRESHOLD) ? (alscode - STK6D2X_ALS_THRESHOLD) : 0; + stk6d2x_set_als_thd(alps_data, (uint16_t)high_thd, (uint16_t)low_thd); +} + +void stk6d2x_dump_reg(struct stk6d2x_data *alps_data) +{ + uint8_t stk6d2x_reg_map[] = + { + STK6D2X_REG_STATE, + STK6D2X_REG_ALS01_DGAIN, + STK6D2X_REG_ALS2_DGAIN, + STK6D2X_REG_IT1, + STK6D2X_REG_IT2, + STK6D2X_REG_WAIT1, + STK6D2X_REG_WAIT2, + STK6D2X_REG_ALS_SUM_GAIN1, + STK6D2X_REG_ALS_SUM_GAIN2, + STK6D2X_REG_THDH1_ALS, + STK6D2X_REG_THDH2_ALS, + STK6D2X_REG_THDL1_ALS, + STK6D2X_REG_THDL2_ALS, + STK6D2X_REG_ALS_IT_EXT, + STK6D2X_REG_FLAG, + STK6D2X_REG_DATA1_ALS0, + STK6D2X_REG_DATA2_ALS0, + STK6D2X_REG_DATA1_ALS1, + STK6D2X_REG_DATA2_ALS1, + STK6D2X_REG_DATA1_ALS2, + STK6D2X_REG_DATA2_ALS2, + STK6D2X_REG_AGC1_DG, + STK6D2X_REG_AGC2_DG, + STK6D2X_REG_AGC_CROS_THD_FLAG, + STK6D2X_REG_AGC_AG, + STK6D2X_REG_AGC_PD, + STK6D2X_REG_DATA1_ALS0_SUM, + STK6D2X_REG_DATA2_ALS0_SUM, + STK6D2X_REG_DATA1_ALS1_SUM, + STK6D2X_REG_DATA2_ALS1_SUM, + STK6D2X_REG_DATA1_ALS2_SUM, + STK6D2X_REG_DATA2_ALS2_SUM, + STK6D2X_REG_DATA_AGC_SUM, + STK6D2X_REG_ALS_PRST, + STK6D2X_REG_FIFO1, + STK6D2X_REG_FIFO1_WM_LV, + STK6D2X_REG_FIFO2_WM_LV, + STK6D2X_REG_FIFO_FCNT1, + STK6D2X_REG_FIFO_FCNT2, + STK6D2X_REG_FIFO_OUT, + STK6D2X_REG_AGC1, + STK6D2X_REG_AGC2, + STK6D2X_REG_ALS_SUM, + STK6D2X_REG_SW_RESET, + STK6D2X_REG_PDT_ID, + STK6D2X_REG_INT2, + STK6D2X_REG_ALS_AGAIN, + STK6D2X_REG_ALS_PD_REDUCE, + }; + uint8_t i = 0; + uint16_t n = sizeof(stk6d2x_reg_map) / sizeof(stk6d2x_reg_map[0]); + int8_t reg_data; + int ret = 0; + + for (i = 0; i < n; i++) + { + ret = STK6D2X_REG_READ(alps_data, stk6d2x_reg_map[i], ®_data); + + if (ret < 0) + { + ALS_err("fail, ret=%d\n", ret); + return; + } + else + { + ALS_info("Reg[0x%2X] = 0x%2X\n", stk6d2x_reg_map[i], (uint8_t)reg_data); + } + } +} + +static int32_t stk6d2x_check_pid(struct stk6d2x_data *alps_data) +{ + uint8_t value; + uint16_t i = 0, pid_num = (sizeof(stk6d2x_pid_list) / sizeof(stk6d2x_pid_list[0])); + int err; + err = STK6D2X_REG_READ(alps_data, STK6D2X_REG_PDT_ID, &value); + + if (err < 0) + { + ALS_err("fail, ret=%d\n", err); + return err; + } + + ALS_err("PID = 0x%x\n", value); + + if ((value & 0xF0) == 0x30) + alps_data->isTrimmed = 1; + else + alps_data->isTrimmed = 0; + + for (i = 0; i < pid_num; i++) + { + if (value == stk6d2x_pid_list[i]) + { + return 0; + } + } + + return -1; +} + +static int32_t stk6d2x_check_rid(struct stk6d2x_data *alps_data) +{ + uint8_t value; + int err; + err = STK6D2X_REG_READ(alps_data, STK6D2X_REG_RID, &value); + + if (err < 0) + { + ALS_err("fail, ret=%d\n", err); + return err; + } + + ALS_err("RID = 0x%x\n", value); + alps_data->rid = value; + return 0; +} + +static int32_t stk6d2x_software_reset(struct stk6d2x_data *alps_data) +{ + int32_t r; + r = STK6D2X_REG_WRITE(alps_data, STK6D2X_REG_SW_RESET, 0x0); + + if (r < 0) + { + ALS_err("software reset: read error after reset\n"); + return r; + } + + STK6D2X_TIMER_BUSY_WAIT(alps_data, 13000, 15000, US_RANGE_DELAY); + return 0; +} + +int32_t stk6d2x_fsm_restart(struct stk6d2x_data *alps_data) +{ + int32_t r; + r = STK6D2X_REG_READ_MODIFY_WRITE(alps_data, + STK6D2X_REG_STATE, + STK6D2X_STATE_EN_FSM_RESTART_MASK, + STK6D2X_STATE_EN_FSM_RESTART_MASK); + if (r < 0) + { + ALS_err("fail\n"); + return r; + } + + return 0; +} + +/**************************************************************************************************** +* ALS control API +****************************************************************************************************/ +#ifdef STK_ALS_AGC +int32_t stk6d2x_cal_curDGain(uint8_t gain_val) +{ + uint32_t ret_gain = 0; + if (gain_val > STK6D2X_ALS_DGAIN64) + { + ret_gain = stk6d2x_power(2, gain_val + 3); + } + else if (gain_val <= STK6D2X_ALS_DGAIN64) + { + ret_gain = stk6d2x_power(4, gain_val); + } + return ret_gain; +} + +uint8_t stk6d2x_als_get_again_multiple(uint8_t gain) +{ + uint8_t value = 0; + + switch (gain) + { + case STK6D2X_ALS_CI_2_0: + value = STK6D2X_ALS_AGAIN_MULTI4; + break; + + case STK6D2X_ALS_CI_1_0: + value = STK6D2X_ALS_AGAIN_MULTI2; + break; + + case STK6D2X_ALS_CI_0_5: + value = STK6D2X_ALS_AGAIN_MULTI1; + break; + + default: + break; + } + + return value; +} + +uint8_t stk6d2x_als_get_pd_multiple(uint8_t gain, uint8_t pd_mode, uint8_t data_type) +{ + uint8_t value = 0; + if (data_type == STK6D2X_TYPE_ALS0 && (pd_mode == STK6D2X_ALS0_AGC_PDMODE0 || pd_mode == STK6D2X_ALS0_AGC_PDMODE1)) + { + switch (gain) + { + case STK6D2X_ALS_PD_REDUCE_DIS: + value = STK6D2X_ALS_PD_REDUCE_MULTI4; + break; + + case STK6D2X_ALS_PD_REDUCE_LV1: + value = STK6D2X_ALS_PD_REDUCE_MULTI2; + break; + + case STK6D2X_ALS_PD_REDUCE_LV2: + value = STK6D2X_ALS_PD_REDUCE_MULTI1; + break; + + default: + value = STK6D2X_ALS_PD_REDUCE_MULTI1; + break; + } + } + + if (data_type == STK6D2X_TYPE_ALS1 && pd_mode == STK6D2X_ALS0_AGC_PDMODE0) + { + switch (gain) + { + case STK6D2X_ALS_PD_REDUCE_DIS: + value = STK6D2X_ALS_PD_REDUCE_MULTI2; + break; + + case STK6D2X_ALS_PD_REDUCE_LV1: + value = STK6D2X_ALS_PD_REDUCE_MULTI1; + break; + + default: + value = STK6D2X_ALS_PD_REDUCE_MULTI1; + break; + } + } + + if (data_type == STK6D2X_TYPE_ALS1 && pd_mode == STK6D2X_ALS0_AGC_PDMODE1) + { + switch (gain) + { + case STK6D2X_ALS_PD_REDUCE_DIS: + value = STK6D2X_ALS_PD_REDUCE_MULTI3; + break; + + case STK6D2X_ALS_PD_REDUCE_LV1: + value = STK6D2X_ALS_PD_REDUCE_MULTI2; + break; + + case STK6D2X_ALS_PD_REDUCE_LV2: + value = STK6D2X_ALS_PD_REDUCE_MULTI1; + break; + + default: + value = STK6D2X_ALS_PD_REDUCE_MULTI1; + break; + } + } + + if (data_type == STK6D2X_TYPE_ALS2 && pd_mode == STK6D2X_ALS0_AGC_PDMODE0) + { + switch (gain) + { + case STK6D2X_ALS_PD_REDUCE_DIS: + value = STK6D2X_ALS_PD_REDUCE_MULTI2; + break; + + case STK6D2X_ALS_PD_REDUCE_LV1: + value = STK6D2X_ALS_PD_REDUCE_MULTI1; + break; + + default: + value = STK6D2X_ALS_PD_REDUCE_MULTI1; + break; + } + } + + if (data_type == STK6D2X_TYPE_ALS2 && pd_mode == STK6D2X_ALS0_AGC_PDMODE1) + { + switch (gain) + { + case STK6D2X_ALS_PD_REDUCE_DIS: + value = STK6D2X_ALS_PD_REDUCE_MULTI1; + break; + + default: + value = STK6D2X_ALS_PD_REDUCE_MULTI1; + break; + } + } + + return value; +} + +int32_t stk6d2x_get_curGain(struct stk6d2x_data *alps_data) +{ + int32_t ret = 0; + uint8_t flag_value = 0; + uint8_t reg_value = 0; + uint8_t pd_mode = 0; + + ret = STK6D2X_REG_READ(alps_data, STK6D2X_REG_ALS_PD_REDUCE, &pd_mode); + + if (ret < 0) + { + return ret; + } + alps_data->als_info.als_cur_pd_mode = (pd_mode & STK6D2X_ALS0_AGC_PDMODE_MASK) >> STK6D2X_ALS0_AGC_PDMODE_SHIFT; + + // dgain + ret = STK6D2X_REG_READ(alps_data, STK6D2X_REG_AGC1_DG, ®_value); + + if (ret < 0) + { + return ret; + } + + flag_value = reg_value & (STK6D2X_FLG_ALS2_DG_MASK | STK6D2X_FLG_ALS1_DG_MASK | STK6D2X_FLG_ALS0_DG_MASK); + + alps_data->als_info.als_cur_dgain[2] = stk6d2x_cal_curDGain(reg_value & STK6D2X_ALS2_AGC_DG_MASK); + + ret = STK6D2X_REG_READ(alps_data, STK6D2X_REG_AGC2_DG, ®_value); + + if (ret < 0) + { + return ret; + } + + alps_data->als_info.als_cur_dgain[0] = stk6d2x_cal_curDGain(reg_value & STK6D2X_ALS0_AGC_DG_MASK); + alps_data->als_info.als_cur_dgain[1] = stk6d2x_cal_curDGain((reg_value & STK6D2X_ALS1_AGC_DG_MASK) >> 4); + + // again + ret = STK6D2X_REG_READ(alps_data, STK6D2X_REG_AGC_AG, ®_value); + + if (ret < 0) + { + return ret; + } + + alps_data->als_info.als_cur_again[0] = stk6d2x_als_get_again_multiple(reg_value & STK6D2X_ALS0_AGC_AG_MASK); + alps_data->als_info.als_cur_again[1] = stk6d2x_als_get_again_multiple((reg_value & STK6D2X_ALS1_AGC_AG_MASK) >> 2); + alps_data->als_info.als_cur_again[2] = stk6d2x_als_get_again_multiple((reg_value & STK6D2X_ALS2_AGC_AG_MASK) >> 4); + + // PD + ret = STK6D2X_REG_READ(alps_data, STK6D2X_REG_AGC_PD, ®_value); + + if (ret < 0) + { + return ret; + } + alps_data->als_info.als_cur_pd_reduce[0] = stk6d2x_als_get_pd_multiple(reg_value & STK6D2X_ALS0_AGC_PD_MASK, \ + alps_data->als_info.als_cur_pd_mode, \ + STK6D2X_TYPE_ALS0); + alps_data->als_info.als_cur_pd_reduce[1] = stk6d2x_als_get_pd_multiple((reg_value & STK6D2X_ALS1_AGC_PD_MASK) >> 2, \ + alps_data->als_info.als_cur_pd_mode, \ + STK6D2X_TYPE_ALS1); + alps_data->als_info.als_cur_pd_reduce[2] = stk6d2x_als_get_pd_multiple((reg_value & STK6D2X_ALS2_AGC_PD_MASK) >> 4, \ + alps_data->als_info.als_cur_pd_mode, \ + STK6D2X_TYPE_ALS2); + + ALS_info("Current DG Gain = ALS0:%d, ALS1:%d, ALS2:%d, AG Gain multiple = ALS0:%d, ALS1:%d, ALS2:%d\n", + alps_data->als_info.als_cur_dgain[0], + alps_data->als_info.als_cur_dgain[1], + alps_data->als_info.als_cur_dgain[2], + alps_data->als_info.als_cur_again[0], + alps_data->als_info.als_cur_again[1], + alps_data->als_info.als_cur_again[2]); + ALS_info("Current PD Reduce multiple = ALS0:%d, ALS1:%d, ALS2:%d\n", + alps_data->als_info.als_cur_pd_reduce[0], + alps_data->als_info.als_cur_pd_reduce[1], + alps_data->als_info.als_cur_pd_reduce[2]); + return 0; +} + +void stk6d2x_get_als_ratio(struct stk6d2x_data *alps_data) +{ + alps_data->als_info.als_cur_ratio[0] = ((STK6D2X_ALS_DGAIN_MULTI1024 * STK6D2X_ALS_AGAIN_MULTI4 * STK6D2X_ALS_PD_REDUCE_MULTI4) / \ + ((alps_data->als_info.als_cur_dgain[0]) * \ + (alps_data->als_info.als_cur_again[0]) * \ + (alps_data->als_info.als_cur_pd_reduce[0]))); + + + //ALS1 + if (alps_data->als_info.als_cur_pd_mode == STK6D2X_ALS0_AGC_PDMODE0) + { + alps_data->als_info.als_cur_ratio[1] = ((STK6D2X_ALS_DGAIN_MULTI1024 * STK6D2X_ALS_AGAIN_MULTI4 * STK6D2X_ALS_PD_REDUCE_MULTI2) / \ + ((alps_data->als_info.als_cur_dgain[1]) * \ + (alps_data->als_info.als_cur_again[1]) * \ + (alps_data->als_info.als_cur_pd_reduce[1]))); + } + else if (alps_data->als_info.als_cur_pd_mode == STK6D2X_ALS0_AGC_PDMODE1) + { + alps_data->als_info.als_cur_ratio[1] = ((STK6D2X_ALS_DGAIN_MULTI1024 * STK6D2X_ALS_AGAIN_MULTI4 * STK6D2X_ALS_PD_REDUCE_MULTI3) / \ + ((alps_data->als_info.als_cur_dgain[1]) * \ + (alps_data->als_info.als_cur_again[1]) * \ + (alps_data->als_info.als_cur_pd_reduce[1]))); + } + + + //ALS2 + if (alps_data->als_info.als_cur_pd_mode == STK6D2X_ALS0_AGC_PDMODE0) + { + alps_data->als_info.als_cur_ratio[2] = ((STK6D2X_ALS_DGAIN_MULTI1024 * STK6D2X_ALS_AGAIN_MULTI4 * STK6D2X_ALS_PD_REDUCE_MULTI2) / \ + ((alps_data->als_info.als_cur_dgain[2]) * \ + (alps_data->als_info.als_cur_again[2]) * \ + (alps_data->als_info.als_cur_pd_reduce[2]))); + } + else if (alps_data->als_info.als_cur_pd_mode == STK6D2X_ALS0_AGC_PDMODE1) + { + alps_data->als_info.als_cur_ratio[2] = ((STK6D2X_ALS_DGAIN_MULTI1024 * STK6D2X_ALS_AGAIN_MULTI4 * STK6D2X_ALS_PD_REDUCE_MULTI1) / \ + ((alps_data->als_info.als_cur_dgain[2]) * \ + (alps_data->als_info.als_cur_again[2]) * \ + (alps_data->als_info.als_cur_pd_reduce[2]))); + } + /*ALS_info("Current AGC ratio = ALS0:%d, ALS1:%d, ALS2:%d\n", + alps_data->als_info.als_cur_ratio[0], + alps_data->als_info.als_cur_ratio[1], + alps_data->als_info.als_cur_ratio[2]);*/ +} + +#ifdef SEC_FFT_FLICKER_1024 +uint8_t stk6d2x_sec_dgain(uint8_t gain_val) +{ + uint8_t ret_gain = 0; + if (gain_val > STK6D2X_ALS_DGAIN64) + { + ret_gain = gain_val + 3; + } + else if (gain_val <= STK6D2X_ALS_DGAIN64) + { + ret_gain = gain_val * 2; + } + return ret_gain; +} + +uint8_t stk6d2x_sec_again(uint8_t gain) +{ + uint8_t value = 0; + + switch (gain) + { + case STK6D2X_ALS_CI_2_0: + value = 2; + break; + + case STK6D2X_ALS_CI_1_0: + value = 1; + break; + + case STK6D2X_ALS_CI_0_5: + value = 0; + break; + + default: + break; + } + + return value; +} + +uint8_t stk6d2x_sec_pd_multiple(uint8_t gain, uint8_t pd_mode, uint8_t data_type) +{ + uint8_t value = 0; + if (data_type == STK6D2X_TYPE_ALS0 && (pd_mode == STK6D2X_ALS0_AGC_PDMODE0 || pd_mode == STK6D2X_ALS0_AGC_PDMODE1)) + { + switch (gain) + { + case STK6D2X_ALS_PD_REDUCE_DIS: + value = 2; + break; + + case STK6D2X_ALS_PD_REDUCE_LV1: + value = 1; + break; + + case STK6D2X_ALS_PD_REDUCE_LV2: + value = 0; + break; + + default: + value = 0; + break; + } + } + + if (data_type == STK6D2X_TYPE_ALS1 && pd_mode == STK6D2X_ALS0_AGC_PDMODE0) + { + switch (gain) + { + case STK6D2X_ALS_PD_REDUCE_DIS: + value = 1; + break; + + case STK6D2X_ALS_PD_REDUCE_LV1: + value = 0; + break; + + default: + value = 0; + break; + } + } + + if (data_type == STK6D2X_TYPE_ALS1 && pd_mode == STK6D2X_ALS0_AGC_PDMODE1) + { + switch (gain) + { + case STK6D2X_ALS_PD_REDUCE_DIS: + value = 2; + break; + + case STK6D2X_ALS_PD_REDUCE_LV1: + value = 1; + break; + + case STK6D2X_ALS_PD_REDUCE_LV2: + value = 0; + break; + + default: + value = 0; + break; + } + } + + if (data_type == STK6D2X_TYPE_ALS2 && pd_mode == STK6D2X_ALS0_AGC_PDMODE0) + { + switch (gain) + { + case STK6D2X_ALS_PD_REDUCE_DIS: + value = 1; + break; + + case STK6D2X_ALS_PD_REDUCE_LV1: + value = 0; + break; + + default: + value = 0; + break; + } + } + + if (data_type == STK6D2X_TYPE_ALS2 && pd_mode == STK6D2X_ALS0_AGC_PDMODE1) + { + switch (gain) + { + case STK6D2X_ALS_PD_REDUCE_DIS: + value = 0; + break; + + default: + value = 0; + break; + } + } + + return value; +} +#endif +#endif + +static int32_t stk6d2x_als_latency(stk6d2x_data *alps_data) +{ +#ifdef STK_FIFO_ENABLE + int32_t ret; + uint8_t reg_value[2] = {0}; + uint8_t ext_it_reg = 0; + uint32_t als_it_time = 0; + if (!alps_data->is_long_it) + { + + ret = STK6D2X_REG_BLOCK_READ(alps_data, STK6D2X_REG_IT1, sizeof(reg_value), reg_value); + + if (ret < 0) + { + ALS_err("fail, ret=%d\n", ret); + } + + ret = STK6D2X_REG_READ(alps_data, STK6D2X_REG_ALS_IT_EXT, &ext_it_reg); + + if (ret < 0) + { + return ret; + } + + als_it_time = ((((reg_value[0] & 0x3f) | reg_value[1]) + 1) * 26660 + ext_it_reg * 833 + 130828); //convert time 130.8281 us + als_it_time *= STK_FIFO_I2C_READ_FRAME; + als_it_time /= 1000000; + alps_data->als_info.als_it = als_it_time * 12 / 10; + } + else +#endif + { + alps_data->als_info.als_it = 50 * 12 / 10; + } + + ALS_err("ALS IT = %d\n", alps_data->als_info.als_it); + return 0; +} + +static int32_t stk6d2x_set_als_thd(stk6d2x_data *alps_data, uint16_t thd_h, uint16_t thd_l) +{ + unsigned char val[4]; + int ret = 0; + val[0] = (thd_h & 0xFF00) >> 8; + val[1] = thd_h & 0x00FF; + val[2] = (thd_l & 0xFF00) >> 8; + val[3] = thd_l & 0x00FF; + ret = STK6D2X_REG_WRITE_BLOCK(alps_data, STK6D2X_REG_THDH1_ALS, val, sizeof(val)); + + if (ret < 0) + { + ALS_err("fail, ret=%d\n", ret); + } + + return ret; +} + +int32_t stk6d2x_enable_als(stk6d2x_data *alps_data, bool en) +{ + int32_t ret = 0; + uint8_t reg_value = 0; + + if (alps_data->als_info.enable == en) + { + ALS_err("ALS already set\n"); + return ret; + } + + ret = STK6D2X_REG_READ(alps_data, STK6D2X_REG_STATE, ®_value); + + if (ret < 0) + { + return ret; + } + + reg_value &= (~(STK6D2X_STATE_EN_WAIT_MASK | STK6D2X_STATE_EN_ALS_MASK)); + + if (en) + { + reg_value |= STK6D2X_STATE_EN_ALS_MASK; +#ifdef STK_DATA_SUMMATION + reg_value |= STK6D2X_STATE_EN_SUMMATION_MASK; +#endif + } + else + { +#ifdef STK_ALS_CALI + + if (alps_data->als_info.cali_enable) + { + reg_value |= STK6D2X_STATE_EN_ALS_MASK; + } + +#endif + } + + ret = STK6D2X_REG_READ_MODIFY_WRITE(alps_data, + STK6D2X_REG_STATE, + reg_value, + 0xFF); + + if (ret < 0) + { + return ret; + } + + alps_data->als_info.enable = en; + return ret; +} + +#ifdef STK_DATA_SUMMATION +static int32_t stk6d2x_als_get_data_summation(stk6d2x_data *alps_data, uint8_t agc_flag) +{ + uint8_t raw_data[6]; + int err = 0; + + err = STK6D2X_REG_BLOCK_READ(alps_data, STK6D2X_REG_DATA1_ALS0_SUM, 6, &raw_data[0]); + + if (err < 0) + { + ALS_err("return err\n"); + return err; + } + + if (!(agc_flag & 0x1)) + { + alps_data->als_info.last_raw_data[0] = ((*(raw_data) << 8) | *(raw_data + 1)); + } + if (!(agc_flag & 0x2)) + { + alps_data->als_info.last_raw_data[1] = ((*(raw_data + 2) << 8) | *(raw_data + 3)); + } + if (!(agc_flag & 0x4)) + { + alps_data->als_info.last_raw_data[2] = ((*(raw_data + 4) << 8) | *(raw_data + 5)); + } + return err; +} + +static int32_t stk6d2x_als_get_summation_gain(stk6d2x_data *alps_data) +{ + uint8_t raw_data[2]; + int err = 0, gain_div_value = 0; + uint8_t sum_num = 0; + + err = STK6D2X_REG_READ(alps_data, STK6D2X_REG_ALS_SUM, &sum_num); + + if (err < 0) + { + return err; + } + + while (gain_div_value < STK6D2X_ALS_SUM_GAIN_DIV256) { + if ((1 << gain_div_value) >= (sum_num + 1)) + { + ALS_err("set summation gain div = %d\n", (1 << gain_div_value)); + break; + } + gain_div_value ++; + } + + err = STK6D2X_REG_READ_MODIFY_WRITE(alps_data, + STK6D2X_REG_ALS_SUM_GAIN1, + (gain_div_value << 4) | gain_div_value, + 0xFF); + + if (err < 0) + { + return err; + } + + err = STK6D2X_REG_READ_MODIFY_WRITE(alps_data, + STK6D2X_REG_ALS_SUM_GAIN2, + (gain_div_value << 4), + 0xFF); + + if (err < 0) + { + return err; + } + + err = STK6D2X_REG_BLOCK_READ(alps_data, STK6D2X_REG_ALS_SUM_GAIN1, 2, &raw_data[0]); + if (err < 0) + { + ALS_err("return err\n"); + return err; + } + alps_data->als_info.als_sum_gain_div[0] = (raw_data[0] & 0xF0) >> 4; + alps_data->als_info.als_sum_gain_div[1] = (raw_data[0] & 0x0F); + alps_data->als_info.als_sum_gain_div[2] = (raw_data[0] & 0xF0) >> 4; + + return err; +} +#endif + +int32_t stk6d2x_als_get_data(stk6d2x_data *alps_data, bool is_skip) +{ + uint8_t raw_data[6]; + int loop_count; + int err = 0; + err = STK6D2X_REG_BLOCK_READ(alps_data, STK6D2X_REG_DATA1_ALS0, 6, &raw_data[0]); + + if (err < 0) + { + ALS_err("return err\n"); + return err; + } + + if (is_skip) + return err; + + for (loop_count = 0; loop_count < 3; loop_count++) + { + *(alps_data->als_info.last_raw_data + loop_count ) = (*(raw_data + (2 * loop_count)) << 8 | *(raw_data + (2 * loop_count + 1) )); + } + return err; +} + +void stk6d2x_get_data_polling(stk_timer_info *t_info) +{ + stk6d2x_data *alps_data = (stk6d2x_data*)t_info->any; + uint32_t als_data[3]; + int32_t ret = 0; + uint8_t flag_value; +#ifdef STK_FIFO_ENABLE + uint8_t clk_status; +#endif + + if (!alps_data->als_info.enable) + { + return; + } + + if (!alps_data->is_long_it) + { +#ifdef STK_FIFO_ENABLE + stk6d2x_get_fifo_data_polling(alps_data); + // Check using external clk or not + if (alps_data->is_long_it) { + stk6d2x_fifo_switch_it(alps_data, 1); + return; + } + + if (alps_data->is_local_avg_update && alps_data->fifo_info.fft_buf_idx != 0) + stk_sec_report(alps_data); //Stop reporting if it uses internal clk + +#ifdef STK_FIFO_DATA_SUMMATION + alps_data->als_info.last_raw_data[0] = alps_data->fifo_info.fifo_sum_als0; + alps_data->als_info.last_raw_data[1] = alps_data->fifo_info.fifo_sum_als1; + alps_data->als_info.last_raw_data[2] = alps_data->fifo_info.fifo_sum_als2; + alps_data->als_info.is_data_ready = true; +#endif +#ifdef STK_DATA_SUMMATION + ret = STK6D2X_REG_READ(alps_data, STK6D2X_REG_DATA_AGC_SUM, &alps_data->als_info.als_agc_sum_flag); + + if (ret < 0) + { + return; + } + stk6d2x_als_get_data_summation(alps_data, alps_data->als_info.als_agc_sum_flag); + stk6d2x_get_curGain(alps_data); + stk6d2x_get_als_ratio(alps_data); + if (!(alps_data->als_info.als_agc_sum_flag & 0x1)) + { + alps_data->als_info.last_raw_data[0] = alps_data->als_info.last_raw_data[0] * alps_data->als_info.als_cur_ratio[0] * (1 << alps_data->als_info.als_sum_gain_div[0]); + } + + if (!(alps_data->als_info.als_agc_sum_flag & 0x2)) + { + //ALS1 + alps_data->als_info.last_raw_data[1] = alps_data->als_info.last_raw_data[1] * alps_data->als_info.als_cur_ratio[1] * (1 << alps_data->als_info.als_sum_gain_div[1]); + } + + if (!(alps_data->als_info.als_agc_sum_flag & 0x4)) + { + //ALS2 + alps_data->als_info.last_raw_data[2] = alps_data->als_info.last_raw_data[2] * alps_data->als_info.als_cur_ratio[2] * (1 << alps_data->als_info.als_sum_gain_div[2]); + } +#endif +#endif + } + else + { +#ifdef STK_FIFO_ENABLE + ret = STK6D2X_REG_READ(alps_data, 0x50, &clk_status); + + if (ret < 0) + { + ALS_err("i2c failed\n"); + return; + } + + if ((clk_status >> 4) & 0x1) + { + stk6d2x_fifo_switch_it(alps_data, 0); + return; + } +#endif + // ALPS timer without FIFO control + if (alps_data->als_info.enable) + { + ret = STK6D2X_REG_READ(alps_data, STK6D2X_REG_FLAG, &flag_value); + + if (ret < 0) + { + ALS_err("i2c failed\n"); + return; + } + + if (flag_value & STK6D2X_FLG_ALSDR_MASK) + { + stk6d2x_als_get_data(alps_data, 0); + stk6d2x_get_curGain(alps_data); + stk6d2x_get_als_ratio(alps_data); + //ALS0 + alps_data->als_info.last_raw_data[0] = alps_data->als_info.last_raw_data[0] * alps_data->als_info.als_cur_ratio[0]; + //ALS1 + alps_data->als_info.last_raw_data[1] = alps_data->als_info.last_raw_data[1] * alps_data->als_info.als_cur_ratio[1]; + //ALS2 + alps_data->als_info.last_raw_data[2] = alps_data->als_info.last_raw_data[2] * alps_data->als_info.als_cur_ratio[2]; + alps_data->als_info.is_data_ready = true; + } + else + { + ALS_err("ALS data was not ready\n"); + } + } + } + + if (alps_data->als_info.is_data_ready) + { + alps_data->als_info.is_data_ready = false; + // Last information + als_data[0] = (alps_data->als_info.last_raw_data[0]) * alps_data->als_info.scale / 1000; + als_data[1] = (alps_data->als_info.last_raw_data[1]) * alps_data->als_info.scale / 1000; + als_data[2] = (alps_data->als_info.last_raw_data[2]) * alps_data->als_info.scale / 1000; + ALS_info("multiple ratio ALS0 Data = %llu, ALS1 data = %llu, ALS2 data = %llu\n", + alps_data->als_info.last_raw_data[0], + alps_data->als_info.last_raw_data[1], + alps_data->als_info.last_raw_data[2]); + STK6D2X_ALS_REPORT(alps_data, als_data, 3); + } +} + +#ifdef STK_FIFO_ENABLE +void stk6d2x_fifo_switch_it(struct stk6d2x_data *alps_data, bool is_long_it) +{ + if (!alps_data->als_info.enable) { + ALS_err("Sensor is not enable return\n"); + return; + } + + if (is_long_it) + { + alps_data->is_long_it = true; + STK6D2X_REG_WRITE(alps_data, STK6D2X_REG_IT1, (STK6D2X_ALS_IT50 >> 8) & STK6D2X_ALS_IT1_MASK); + STK6D2X_REG_WRITE(alps_data, STK6D2X_REG_IT2, STK6D2X_ALS_IT50 & 0xFF); + STK6D2X_REG_WRITE(alps_data, STK6D2X_REG_ALS_IT_EXT, 0x0); + stk6d2x_enable_fifo(alps_data, false); + //stk6d2x_fifo_stop_retry(alps_data); + ALS_err("Switch to long IT\n"); + } + else + { + alps_data->is_long_it = false; + STK6D2X_REG_WRITE(alps_data, STK6D2X_REG_IT1, (STK_FLICKER_IT >> 8) & STK6D2X_ALS_IT1_MASK); + STK6D2X_REG_WRITE(alps_data, STK6D2X_REG_IT2, STK_FLICKER_IT & 0xFF); + STK6D2X_REG_WRITE(alps_data, STK6D2X_REG_ALS_IT_EXT, STK_FLICKER_EXT_IT); + stk6d2x_enable_fifo(alps_data, true); + //stk6d2x_alloc_fifo_data(alps_data, STK_FIFO_I2C_READ_FRAME_TARGET); + ALS_err("Switch to short IT\n"); + } + + stk6d2x_fsm_restart(alps_data); + stk6d2x_als_get_data(alps_data, 1); + STK6D2X_TIMER_STOP(alps_data, &alps_data->alps_timer_info); + stk6d2x_als_latency(alps_data); + alps_data->alps_timer_info.interval_time = alps_data->als_info.als_it; + alps_data->alps_timer_info.change_interval_time = true; + STK6D2X_TIMER_REGISTER(alps_data, &alps_data->alps_timer_info); + STK6D2X_TIMER_START(alps_data, &alps_data->alps_timer_info); +} +#endif + +/**************************************************************************************************** +* System Init. API +****************************************************************************************************/ +static void stk6d2x_init_para(struct stk6d2x_data *alps_data, + struct stk6d2x_platform_data *plat_data) +{ + memset(&alps_data->als_info, 0, sizeof(alps_data->als_info)); + alps_data->als_info.first_init = true; +#ifdef STK_ALS_CALI + + if (alps_data->cali_info.cali_para.als_version != 0) + { + alps_data->als_info.scale = alps_data->cali_info.cali_para.als_scale; + } + else +#endif + { + alps_data->als_info.scale = plat_data->als_scale; + } + + alps_data->pdata = plat_data; + alps_data->als_info.is_data_ready = false; +#ifdef STK_FIFO_ENABLE + alps_data->is_long_it = false; + + alps_data->index_last = 0; + alps_data->is_first = true; + alps_data->is_local_avg_update = false; + alps_data->clear_local_average = 0; + alps_data->uv_local_average = 0; + alps_data->ir_local_average = 0; + alps_data->is_clear_local_sat = false; + alps_data->is_uv_local_sat = false; + alps_data->is_ir_local_sat = false; +#else + alps_data->is_long_it = true; +#endif +#if IS_ENABLED(CONFIG_SENSORS_FLICKER_SELF_TEST) + alps_data->eol_enabled = false; +#endif +} + +static int32_t stk6d2x_init_all_reg(struct stk6d2x_data *alps_data) +{ + int32_t ret = 0; + uint16_t reg_count = 0, reg_num = sizeof(stk6d2x_default_register_table) / sizeof(stk6d2x_register_table); + + for (reg_count = 0; reg_count < reg_num; reg_count++) + { + ret = STK6D2X_REG_READ_MODIFY_WRITE(alps_data, + stk6d2x_default_register_table[reg_count].address, + stk6d2x_default_register_table[reg_count].value, + stk6d2x_default_register_table[reg_count].mask_bit); + + if (ret < 0) + { + ALS_err("write i2c error\n"); + return ret; + } + } + + return ret; +} + +int32_t stk6d2x_init_all_setting(stk6d2x_data *alps_data) +{ + int32_t ret; + ret = stk6d2x_software_reset(alps_data); + + if (ret < 0) + return ret; + + ret = stk6d2x_check_pid(alps_data); + + if (ret < 0) + return ret; + + ret = stk6d2x_check_rid(alps_data); + + if (ret < 0) + return ret; + + ret = stk6d2x_init_all_reg(alps_data); + + if (ret < 0) + return ret; + +#ifdef STK_FIFO_ENABLE + stk6d2x_fifo_init(alps_data); +#endif + alps_data->first_init = true; +#if defined(CONFIG_AMS_ALS_COMPENSATION_FOR_AUTO_BRIGHTNESS) + alps_data->als_flag = false; + alps_data->flicker_flag = false; +#endif + stk6d2x_dump_reg(alps_data); + return 0; +} + +void stk6d2x_force_stop(stk6d2x_data *alps_data) +{ + ALS_err("Stop ALPS timer\n"); + STK6D2X_TIMER_STOP(alps_data, &alps_data->alps_timer_info); + + ALS_err("FIFO disable\n"); + stk6d2x_enable_fifo(alps_data, false); + stk_power_ctrl(alps_data, false); + if (alps_data->als_info.enable == true) + alps_data->als_info.enable = false; +} + +int32_t stk6d2x_alps_set_config(stk6d2x_data *alps_data, bool en) +{ + int32_t ret = 0; + + mutex_lock(&alps_data->config_lock); + if (alps_data->first_init) + { + ret = stk6d2x_request_registry(alps_data); + + if (ret < 0) + goto err; + + stk6d2x_init_para(alps_data, alps_data->pdata); + alps_data->first_init = false; + } + + if (alps_data->als_info.enable == en) + { + ALS_err("ALS status is same (%d)\n", en); + goto err; + } + + if (en == 1) + stk6d2x_init_all_reg(alps_data); + + stk6d2x_pin_control(alps_data, en); + + if (en) + { + ALS_err("ALPS Timer SETTING\n"); + + if (!alps_data->alps_timer_info.is_exist) + { + stk6d2x_als_latency(alps_data); + ret = stk6d2x_register_timer(alps_data, STK6D2X_DATA_TIMER_ALPS); + if (ret < 0) + { + ALS_err("register timer fail\n"); + mutex_unlock(&alps_data->config_lock); + return ret; + } + } +#if defined(CONFIG_AMS_ALS_COMPENSATION_FOR_AUTO_BRIGHTNESS) + if (alps_data->flicker_flag) +#endif + { + if (!alps_data->alps_timer_info.is_active) + { + ret = STK6D2X_TIMER_START(alps_data, &alps_data->alps_timer_info); + if (ret < 0) + { + ALS_err(" start timer fail\n"); + mutex_unlock(&alps_data->config_lock); + return ret; + } + } + } + } + +#ifdef STK_ALS_ENABLE + ret = stk6d2x_enable_als(alps_data, en); + if (ret < 0) { + stk6d2x_force_stop(alps_data); + goto err; + } +#endif + + if (!en && !alps_data->als_info.enable) + { + ALS_dbg("Stop ALPS timer\n"); + STK6D2X_TIMER_STOP(alps_data, &alps_data->alps_timer_info); + } + +#ifdef STK_FIFO_ENABLE + + if (alps_data->als_info.enable == true) + { + ALS_dbg("FIFO enable\n"); +#ifdef STK_DATA_SUMMATION + stk6d2x_als_get_summation_gain(alps_data); +#endif + stk6d2x_enable_fifo(alps_data, true); + } + else + { + ALS_dbg("FIFO disable\n"); + stk6d2x_enable_fifo(alps_data, false); + } +#endif + // stk6d2x_dump_reg(alps_data); + +err: + mutex_unlock(&alps_data->config_lock); + return 0; +} + +#if defined(CONFIG_AMS_ALS_COMPENSATION_FOR_AUTO_BRIGHTNESS) +void stk_als_init(struct stk6d2x_data *alps_data) +{ + STK6D2X_REG_WRITE(alps_data, STK6D2X_REG_IT1, + (STK6D2X_ALS_IT40 >> 8) & STK6D2X_ALS_IT1_MASK); + STK6D2X_REG_WRITE(alps_data, STK6D2X_REG_IT2, STK6D2X_ALS_IT40 & 0xFF); + STK6D2X_REG_WRITE(alps_data, STK6D2X_REG_ALS_IT_EXT, 0x0); + stk6d2x_enable_fifo(alps_data, false); + stk6d2x_fsm_restart(alps_data); +} + +void stk_als_start(struct stk6d2x_data *alps_data) +{ + ALS_dbg("stk_als_start\n"); + + if (!alps_data->flicker_flag) { + stk6d2x_alps_set_config(alps_data, true); + stk_als_init(alps_data); + } +} + +void stk_als_stop(struct stk6d2x_data *alps_data) +{ + ALS_dbg("%s\n",__func__); + + if (!alps_data->flicker_flag) + stk6d2x_alps_set_config(alps_data, false); +} +#endif /* CONFIG_AMS_ALS_COMPENSATION_FOR_AUTO_BRIGHTNESS */ diff --git a/drivers/optics/stk6d2x.h b/drivers/optics/stk6d2x.h new file mode 100755 index 000000000000..b9c79c01e8cc --- /dev/null +++ b/drivers/optics/stk6d2x.h @@ -0,0 +1,666 @@ +/* + * + * $Id: stk6d2x.h + * + * Copyright (C) 2012~2018 Bk, sensortek Inc. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + * + */ + +#ifndef __STK6D2X_H__ +#define __STK6D2X_H__ + +#if IS_ENABLED(CONFIG_SENSORS_FLICKER_SELF_TEST) +#include "flicker_test.h" +#endif + +#include "common_define.h" +#include "stk6d2x_ver.h" + +#define STK6D2X_DEV_NAME "STK6D2X" +//#define ALS_NAME "lightsensor-level" +#define MODULE_NAME_ALS "als_rear" +#define APS_TAG "[ALS/PS] " +#define APS_FUN(f) printk(KERN_INFO APS_TAG" %s\n", __FUNCTION__) +#define APS_ERR(fmt, args...) printk(KERN_ERR APS_TAG" %s %d: "fmt"\n", __FUNCTION__, __LINE__, ##args) +#define APS_LOG(fmt, args...) printk(KERN_INFO APS_TAG" %s %d: "fmt"\n", __FUNCTION__, __LINE__, ##args) +#define APS_DBG(fmt, args...) printk(KERN_INFO APS_TAG" %s %d: "fmt"\n", __FUNCTION__, __LINE__, ##args) + +/* Driver Settings */ +#define STK_ALS_ENABLE +// #define STK_ALS_CALI +#define STK_FIFO_ENABLE +#define STK_ALS_AGC +#define STK_CHK_XFLG +#define CONFIG_AMS_ALS_COMPENSATION_FOR_AUTO_BRIGHTNESS +#define CONFIG_AMS_ALWAYS_ON_MODE_FOR_AUTO_BRIGHTNESS + +#ifdef STK_FIFO_ENABLE + // #define STK_DATA_SUMMATION + // #define STK_FIFO_DATA_SUMMATION + #define STK_FFT_FLICKER + #ifdef STK_FFT_FLICKER + #define STK_CHK_CLK_SRC + #define SEC_FFT_FLICKER_1024 + #endif +#endif + +/* Define Register Map */ +#define STK6D2X_REG_STATE 0x00 +#define STK6D2X_REG_ALS01_DGAIN 0x01 +#define STK6D2X_REG_ALS2_DGAIN 0x02 +#define STK6D2X_REG_IT1 0x03 +#define STK6D2X_REG_IT2 0x04 +#define STK6D2X_REG_WAIT1 0x05 +#define STK6D2X_REG_WAIT2 0x06 +#define STK6D2X_REG_ALS_SUM_GAIN1 0x08 +#define STK6D2X_REG_ALS_SUM_GAIN2 0x09 +#define STK6D2X_REG_THDH1_ALS 0x0A +#define STK6D2X_REG_THDH2_ALS 0x0B +#define STK6D2X_REG_THDL1_ALS 0x0C +#define STK6D2X_REG_THDL2_ALS 0x0D +#define STK6D2X_REG_ALS_IT_EXT 0x0E +#define STK6D2X_REG_FLAG 0x10 +#define STK6D2X_REG_DATA1_ALS0 0x11 +#define STK6D2X_REG_DATA2_ALS0 0x12 +#define STK6D2X_REG_DATA1_ALS1 0x13 +#define STK6D2X_REG_DATA2_ALS1 0x14 +#define STK6D2X_REG_DATA1_ALS2 0x15 +#define STK6D2X_REG_DATA2_ALS2 0x16 +#define STK6D2X_REG_AGC1_DG 0x17 +#define STK6D2X_REG_AGC2_DG 0x18 +#define STK6D2X_REG_AGC_CROS_THD_FLAG 0x19 +#define STK6D2X_REG_AGC_AG 0x1A +#define STK6D2X_REG_AGC_PD 0x1B +#define STK6D2X_REG_DATA1_ALS0_SUM 0x1C +#define STK6D2X_REG_DATA2_ALS0_SUM 0x1D +#define STK6D2X_REG_DATA1_ALS1_SUM 0x1E +#define STK6D2X_REG_DATA2_ALS1_SUM 0x1F +#define STK6D2X_REG_DATA1_ALS2_SUM 0x20 +#define STK6D2X_REG_DATA2_ALS2_SUM 0x21 +#define STK6D2X_REG_DATA_AGC_SUM 0x22 +#define STK6D2X_REG_RID 0x3F +#define STK6D2X_REG_ALS_PRST 0x40 +#define STK6D2X_REG_FIFO1 0x60 +#define STK6D2X_REG_FIFO1_WM_LV 0x61 +#define STK6D2X_REG_FIFO2_WM_LV 0x62 +#define STK6D2X_REG_FIFO_FCNT1 0x64 +#define STK6D2X_REG_FIFO_FCNT2 0x65 +#define STK6D2X_REG_FIFO_OUT 0x66 +#define STK6D2X_REG_AGC1 0x6A +#define STK6D2X_REG_AGC2 0x6B +#define STK6D2X_REG_ALS_SUM 0x70 +#define STK6D2X_REG_SW_RESET 0x80 +#define STK6D2X_REG_PDT_ID 0x92 +#define STK6D2X_REG_INT2 0xA5 +#define STK6D2X_REG_XFLAG 0xA6 +#define STK6D2X_REG_ALS_AGAIN 0xDB +#define STK6D2X_REG_ALS_PD_REDUCE 0xF4 + +/* Define state reg */ +#define STK6D2X_STATE_EN_SUMMATION_SHIFT 3 +#define STK6D2X_STATE_EN_WAIT_SHIFT 2 +#define STK6D2X_STATE_EN_ALS_SHIFT 1 +#define STK6D2X_STATE_EN_FSM_RESTART_MASK 0x10 +#define STK6D2X_STATE_EN_SUMMATION_MASK 0x08 +#define STK6D2X_STATE_EN_WAIT_MASK 0x04 +#define STK6D2X_STATE_EN_ALS_MASK 0x02 + +/* Define ALS DGAIN reg */ +#define STK6D2X_ALS2_DGAIN_SHIFT 4 +#define STK6D2X_ALS1_DGAIN_SHIFT 0 +#define STK6D2X_ALS0_DGAIN_SHIFT 4 +#define STK6D2X_ALS2_DGAIN_MASK 0x70 +#define STK6D2X_ALS1_DGAIN_MASK 0x07 +#define STK6D2X_ALS0_DGAIN_MASK 0x70 + +/* Define interrupt reg */ +#define STK6D2X_INT_ALS_SHIFT 3 +#define STK6D2X_INT_ALS_MASK 0x08 +#define STK6D2X_INT_ALS 0x08 + +/* Define flag reg */ +#define STK6D2X_FLG_ALSDR_SHIFT 7 +#define STK6D2X_FLG_ALSINT_SHIFT 5 +#define STK6D2X_FLG_ALSSAT_SHIFT 2 + +#define STK6D2X_FLG_ALSDR_MASK 0x80 +#define STK6D2X_FLG_ALSINT_MASK 0x20 +#define STK6D2X_FLG_ALSSAT_MASK 0x04 + +/* Define ALS parameters */ +#define STK6D2X_ALS_PRS1 0x00 +#define STK6D2X_ALS_PRS2 0x40 +#define STK6D2X_ALS_PRS4 0x80 +#define STK6D2X_ALS_PRS8 0xC0 + +#define STK6D2X_ALS_DGAIN1 0x00 +#define STK6D2X_ALS_DGAIN4 0x01 +#define STK6D2X_ALS_DGAIN16 0x02 +#define STK6D2X_ALS_DGAIN64 0x03 +#define STK6D2X_ALS_DGAIN128 0x04 +#define STK6D2X_ALS_DGAIN256 0x05 +#define STK6D2X_ALS_DGAIN512 0x06 +#define STK6D2X_ALS_DGAIN1024 0x07 +#define STK6D2X_ALS_DGAIN_MASK 0x07 + +#define STK6D2X_ALS_SUM_GAIN_DIV1 0x00 +#define STK6D2X_ALS_SUM_GAIN_DIV2 0x01 +#define STK6D2X_ALS_SUM_GAIN_DIV4 0x02 +#define STK6D2X_ALS_SUM_GAIN_DIV8 0x03 +#define STK6D2X_ALS_SUM_GAIN_DIV6 0x04 +#define STK6D2X_ALS_SUM_GAIN_DIV32 0x05 +#define STK6D2X_ALS_SUM_GAIN_DIV64 0x06 +#define STK6D2X_ALS_SUM_GAIN_DIV128 0x07 +#define STK6D2X_ALS_SUM_GAIN_DIV256 0x08 +#define STK6D2X_ALS_SUM_GAIN_MASK 0x0F + +#define STK6D2X_ALS_CI_2_0 0x00 +#define STK6D2X_ALS_CI_1_0 0x01 +#define STK6D2X_ALS_CI_0_5 0x02 + +#define STK6D2X_ALS_PD_REDUCE_DIS 0x00 +#define STK6D2X_ALS_PD_REDUCE_LV1 0x01 +#define STK6D2X_ALS_PD_REDUCE_LV2 0x02 + +#define STK6D2X_ALS_IT25 0x3A9 +#define STK6D2X_ALS_IT40 0x5DB +#define STK6D2X_ALS_IT50 0x753 +#define STK6D2X_ALS_IT100 0xEA6 +#define STK6D2X_ALS_IT200 0x1D4D +#define STK6D2X_ALS_IT1_MASK 0x3F + +#define STK6D2X_ALS_IT_EXT_MASK 0x3F + +#define STK6D2X_WAIT20 0x359 +#define STK6D2X_WAIT50 0x85F +#define STK6D2X_WAIT100 0x10BE + +#define STK6D2X_PRST1 0x00 +#define STK6D2X_PRST2 0x01 +#define STK6D2X_PRST4 0x02 +#define STK6D2X_PRST8 0x03 + +#define STK6D2X_FLG_ALS2_DG_MASK 0x40 +#define STK6D2X_FLG_ALS1_DG_MASK 0x20 +#define STK6D2X_FLG_ALS0_DG_MASK 0x10 +#define STK6D2X_ALS2_AGC_DG_MASK 0x07 +#define STK6D2X_ALS1_AGC_DG_MASK 0x70 +#define STK6D2X_ALS0_AGC_DG_MASK 0x07 + +#define STK6D2X_ALS2_AGC_AG_MASK 0x30 +#define STK6D2X_ALS1_AGC_AG_MASK 0x0C +#define STK6D2X_ALS0_AGC_AG_MASK 0x03 +#define STK6D2X_ALS2_AGC_PD_MASK 0x30 +#define STK6D2X_ALS1_AGC_PD_MASK 0x0C +#define STK6D2X_ALS0_AGC_PD_MASK 0x01 +#define STK6D2X_ALS0_AGC_PDMODE_MASK 0xC0 +#define STK6D2X_ALS0_AGC_PDMODE0 0x00 +#define STK6D2X_ALS0_AGC_PDMODE1 0x01 +#define STK6D2X_ALS0_AGC_PDMODE_SHIFT 6 + +#define STK6D2X_FLG_ALS2_SUM_AGC_MASK 0x40 +#define STK6D2X_FLG_ALS1_SUM_AGC_MASK 0x20 +#define STK6D2X_FLG_ALS0_SUM_AGC_MASK 0x10 + +#define STK6D2X_TYPE_ALS0 0 +#define STK6D2X_TYPE_ALS1 1 +#define STK6D2X_TYPE_ALS2 2 + +/** sw reset value */ +#define STK_STK6D2X_SWRESET 0x00 + +/** Off to idle time */ +#define STK6D2X_OFF_TO_IDLE_MS 10 //ms + +/** ALS threshold */ +#define STK6D2X_ALS_THD_ADJ 0.05 +#define STK6D2X_NUM_AXES 3 +#define STK6D2X_MAX_MIN_DIFF 200 +#define STK6D2X_LT_N_CT 1700 +#define STK6D2X_HT_N_CT 2200 + +#define STK6D2X_ALS_DATA_READY_TIME 60 +#define STK6D2X_ALS_THRESHOLD 30 + +#define STK6D2X_ALS_SUMMATION_CNT 51 + +#ifdef STK_ALS_CALI + #define STK6D2X_ALS_CALI_DATA_READY_US 55000000 + #define STK6D2X_ALS_CALI_TARGET_LUX 500 +#endif + +#ifdef STK_UV_CALI + #define STK6D2X_UV_CALI_TARGET 10 +#endif + +#define STK_FLICKER_IT 0x0C +#define STK_FLICKER_EXT_IT 0x0D + +#ifdef STK_FIFO_ENABLE + #define STK_FIFO_DATA_BUFFER_LEN 1024 + #define STK_FIFO_I2C_READ_FRAME 30//get fifo data one time + #define STK_FIFO_I2C_READ_FRAME_BUF_SIZE STK_FIFO_I2C_READ_FRAME * 10 + #define STK_FIFO_I2C_READ_FRAME_TARGET 102 + +#ifdef STK_FFT_FLICKER + #define FFT_BLOCK_SIZE 128 + #define FFT_BUF_SIZE 1024 +#endif + + #define STK6D2X_FIFO_SEL_ALS0 0x00 + #define STK6D2X_FIFO_SEL_ALS1 0x01 + #define STK6D2X_FIFO_SEL_ALS2 0x02 + #define STK6D2X_FIFO_SEL_ALS0_ALS1 0x03 + #define STK6D2X_FIFO_SEL_ALS0_ALS2 0x04 + #define STK6D2X_FIFO_SEL_ALS1_ALS2 0x05 + #define STK6D2X_FIFO_SEL_ALS0_ALS1_ALS2 0x06 + #define STK6D2X_FIFO_SEL_STA0_ALS0 0x08 + #define STK6D2X_FIFO_SEL_STA1_ALS1 0x09 + #define STK6D2X_FIFO_SEL_STA2_ALS2 0x0A + #define STK6D2X_FIFO_SEL_STA01_ALS0_ALS1 0x0B + #define STK6D2X_FIFO_SEL_STA02_ALS0_ALS2 0x0C + #define STK6D2X_FIFO_SEL_STA12_ALS1_ALS2 0x0D + #define STK6D2X_FIFO_SEL_STA01_STA2_ALS0_ALS1_ALS2 0x0E + #define STK6D2X_FIFO_SEL_MASK 0x0F + + #define STK6D2X_FIFO_MODE_OFF 0x00 + #define STK6D2X_FIFO_MODE_BYPASS 0x10 + #define STK6D2X_FIFO_MODE_NORMAL 0x20 + #define STK6D2X_FIFO_MODE_STREAM 0x30 + + #define STK6D2X_FOVR_EN_MASK 0x04 + #define STK6D2X_FWM_EN_MASK 0x02 + #define STK6D2X_FFULL_EN_MASK 0x01 + + #define STK6D2X_FLG_FIFO_OVR_MASK 0x04 + #define STK6D2X_FLG_FIFO_WM_MASK 0x02 + #define STK6D2X_FLG_FIFO_FULL_MASK 0x01 +#endif + +#define STK_ALSPS_TIMER_MS 90 + +#define STK6D2X_CALI_FILE "/persist/sensors/stkalpscali.conf" +#define STK_CHANNEL_NUMBER 3 +#define STK_CHANNEL_OFFSET 3 +#define STK_CHANNEL_GOLDEN 10 + +#define STK6D2X_REG_READ(stk_data, reg, val) ((stk_data)->bops->read((stk_data)->bus_idx, reg, val)) +#define STK6D2X_REG_WRITE(stk_data, reg, val) ((stk_data)->bops->write((stk_data)->bus_idx, reg, val)) +#define STK6D2X_REG_WRITE_BLOCK(stk_data, reg, val, len) ((stk_data)->bops->write_block((stk_data)->bus_idx, reg, val, len)) +#define STK6D2X_REG_READ_MODIFY_WRITE(stk_data, reg, val, mask) ((stk_data)->bops->read_modify_write((stk_data)->bus_idx, reg, val, mask)) +#define STK6D2X_REG_BLOCK_READ(stk_data, reg, count, buf) ((stk_data)->bops->read_block((stk_data)->bus_idx, reg, count, buf)) + +#define STK6D2X_TIMER_REGISTER(stk_data, t_info) ((stk_data)->tops->register_timer(t_info)) +#define STK6D2X_TIMER_START(stk_data, t_info) ((stk_data)->tops->start_timer(t_info)) +#define STK6D2X_TIMER_STOP(stk_data, t_info) ((stk_data)->tops->stop_timer(t_info)) +#define STK6D2X_TIMER_REMOVE(stk_data, t_info) ((stk_data)->tops->remove(t_info)) +#define STK6D2X_TIMER_BUSY_WAIT(stk_data, min, max, mode) ((stk_data)->tops->busy_wait(min, max, mode)) + +#define STK6D2X_GPIO_IRQ_REGISTER(stk_data, g_info) ((stk_data)->gops->register_gpio_irq(g_info)) +#define STK6D2X_GPIO_IRQ_START(stk_data, g_info) ((stk_data)->gops->start_gpio_irq(g_info)) +#define STK6D2X_GPIO_IRQ_STOP(stk_data, g_info) ((stk_data)->gops->stop_gpio_irq(g_info)) +#define STK6D2X_GPIO_IRQ_REMOVE(stk_data, g_info) ((stk_data)->gops->remove(g_info)) + +#define STK6D2X_ALS_REPORT(stk_data, als_data, num) if ((stk_data)->als_report_cb) ((stk_data)->als_report_cb(stk_data, als_data, num)) + +#define STK_ABS(x) ((x < 0)? (-x):(x)) + +enum fft_size { + FFT_128 = 128, + FFT_256 = 256, + FFT_512 = 512, + FFT_1024 = 1024, + //FFT_2048 = 2048, // not ready +}; + +// sec_fft +#define SEC_FFT_SIZE FFT_1024 +#define SAMPLING_TIME (488319) /* nsec (2048Hz) */ +//#define FLICKER_AVG_SHIFT 7 +#define FLICKER_AVG_SHIFT 14 +#define SEC_LOCAL_AVG_SIZE 128 // 1024/n recommended, no 1024 + +// average/max thd +#define FLICKER_AVGMAX_THD 65LL +// sec_ calc_thd +#define FLICKER_THD_CLEAR 1800LL /* 1.8 * 1000 */ +#define FLICKER_THD_RATIO 3LL /* 0.003 * 1000 */ +#define FLICKER_GAIN_MAX 256LL /* 256 */ +#define FLICKER_THD_RATIO_AUTO 1000LL /* 1 * 1000 */ + +typedef struct stk6d2x_data stk6d2x_data; +typedef void (*STK_REPORT_CB)(stk6d2x_data *, uint32_t*, uint32_t); + +/* platform data */ +struct stk6d2x_platform_data +{ + uint32_t als_scale; + uint32_t int_flags; +}; +typedef enum +{ + STK6D2X_NONE = 0x0, + STK6D2X_ALS = 0x1, + STK6D2X_ALL = 0x2, +} stk6d2x_sensor_type; + +typedef enum +{ + STK6D2X_DATA_TIMER_ALPS, +#ifdef STK_FIFO_ENABLE + //STK6D2X_FIFO_RELEASE_TIMER, +#endif +} stk6d2x_timer_type; + +typedef struct stk6d2x_register_table +{ + uint8_t address; + uint8_t value; + uint8_t mask_bit; +} stk6d2x_register_table; + +#ifdef STK_FIFO_ENABLE +typedef enum +{ + STK6D2X_FIFO_ALS0 = 0x0, + STK6D2X_FIFO_ALS1 = 0x1, + STK6D2X_FIFO_ALS2 = 0x2, + STK6D2X_FIFO_ALS0_ALS1 = 0x3, + STK6D2X_FIFO_ALS0_ALS2 = 0x4, + STK6D2X_FIFO_ALS1_ALS2 = 0x5, + STK6D2X_FIFO_ALS0_ALS1_ALS2 = 0x6, + STK6D2X_FIFO_STA0_ALS0 = 0x8, + STK6D2X_FIFO_STA1_ALS1 = 0x9, + STK6D2X_FIFO_STA2_ALS2 = 0xA, + STK6D2X_FIFO_STA01_ALS0_ALS1 = 0xB, + STK6D2X_FIFO_STA02_ALS0_ALS2 = 0xC, + STK6D2X_FIFO_STA12_ALS1_ALS2 = 0xD, + STK6D2X_FIFO_STA01_STA2_ALS0_ALS1_ALS2 = 0xE, + STK6D2X_FIFO_UNAVAILABLE = 0xF, +} stk6d2x_frame_type; +#endif + +#ifdef STK_ALS_CALI +typedef enum +{ + STK6D2X_CALI_IDLE, + STK6D2X_CALI_RUNNING, + STK6D2X_CALI_FAILED, + STK6D2X_CALI_DONE +} stk6d2x_calibration_status; +#endif + +#ifdef STK_ALS_AGC +typedef enum +{ + STK6D2X_ALS_DGAIN_MULTI1 = 1, + STK6D2X_ALS_DGAIN_MULTI4 = 4, + STK6D2X_ALS_DGAIN_MULTI16 = 16, + STK6D2X_ALS_DGAIN_MULTI64 = 64, + STK6D2X_ALS_DGAIN_MULTI128 = 128, + STK6D2X_ALS_DGAIN_MULTI256 = 256, + STK6D2X_ALS_DGAIN_MULTI512 = 512, + STK6D2X_ALS_DGAIN_MULTI1024 = 1024, +} stk6d2x_als_dgain_multi; + +typedef enum +{ + STK6D2X_ALS_AGAIN_MULTI1 = 1, + STK6D2X_ALS_AGAIN_MULTI2 = 2, + STK6D2X_ALS_AGAIN_MULTI4 = 4, +} stk6d2x_als_again_multi; + +typedef enum +{ + STK6D2X_ALS_PD_REDUCE_MULTI1 = 1, + STK6D2X_ALS_PD_REDUCE_MULTI2 = 2, + STK6D2X_ALS_PD_REDUCE_MULTI3 = 3, + STK6D2X_ALS_PD_REDUCE_MULTI4 = 4, +} stk6d2x_als_pd_reduce_multi; +#endif + +typedef struct stk6d2x_cali_table +{ + uint32_t als_version; + uint32_t als_scale; + uint32_t als_bias; +} stk6d2x_cali_table; + +typedef struct stk6d2x_cali_info +{ +#ifdef STK_ALS_CALI + stk6d2x_calibration_status cali_status; +#endif + stk6d2x_cali_table cali_para; +} stk6d2x_cali_info; + +typedef struct stk6d2x_als_info +{ + bool first_init; + bool cali_enable; + bool enable; + uint32_t scale; + bool is_data_ready; + uint16_t als_it; + uint8_t cali_failed_count; + uint64_t last_raw_data[3]; + uint16_t als_cali_data; + uint16_t last_lux; +#ifdef STK_ALS_AGC + uint16_t als_agc_sum; + uint16_t als_cur_dgain[3]; + uint16_t als_cur_again[3]; + uint16_t als_cur_pd_reduce[3]; + uint8_t als_cur_pd_mode; + uint32_t als_cur_ratio[3]; + uint8_t als_agc_sum_flag; + uint32_t als_sum_gain_div[3]; +#endif +} stk6d2x_als_info; + +#ifdef STK_FIFO_ENABLE +typedef struct stk6d2x_fifo_info +{ + bool fifo_enable; + bool is_fifobuf_alloc; + uint32_t fifo_data0[FFT_BUF_SIZE]; + uint32_t fifo_data1[FFT_BUF_SIZE]; + uint32_t fifo_data2[FFT_BUF_SIZE]; +#ifdef SEC_FFT_FLICKER_1024 + uint32_t fifo_data_clear[FFT_BUF_SIZE]; + uint32_t fifo_data_uv[FFT_BUF_SIZE]; + uint32_t fifo_data_ir[FFT_BUF_SIZE]; + uint32_t fifo_gain_clear[FFT_BUF_SIZE]; + uint32_t fifo_gain_uv[FFT_BUF_SIZE]; + uint32_t fifo_gain_ir[FFT_BUF_SIZE]; + uint8_t fifo_xflag[FFT_BUF_SIZE]; +#endif + uint64_t fifo_sum_als0; + uint64_t fifo_sum_als1; + uint64_t fifo_sum_als2; + uint32_t fifo_sum_cnt; + uint16_t target_frame_count; + uint16_t read_frame; + uint16_t read_max_data_byte; + uint8_t latency_status; + stk6d2x_frame_type data_type; + bool sel_mode; + uint8_t frame_byte; + uint8_t frame_data; + uint16_t last_frame_count; + bool fifo_reading; + bool ext_clk_chk; + bool pre_ext_clk_chk; + uint16_t block_size; +#ifdef STK_FFT_FLICKER + uint32_t fft_buf[FFT_BUF_SIZE]; + uint32_t fft_uv_buf[FFT_BUF_SIZE]; + uint32_t fft_ir_buf[FFT_BUF_SIZE]; + uint32_t fft_gain_clear[FFT_BUF_SIZE]; + uint32_t fft_gain_uv[FFT_BUF_SIZE]; + uint32_t fft_gain_ir[FFT_BUF_SIZE]; + uint8_t fft_xflag[FFT_BUF_SIZE]; + uint32_t fft_buf_idx; +#endif +} stk6d2x_fifo_info; +#endif + +#define PWR_ON 1 +#define PWR_OFF 0 +#define PM_RESUME 1 +#define PM_SUSPEND 0 + +struct stk6d2x_data +{ + struct stk6d2x_platform_data *pdata; + const struct stk_bus_ops *bops; + struct mutex config_lock; +#if defined(CONFIG_AMS_ALS_COMPENSATION_FOR_AUTO_BRIGHTNESS) + struct mutex enable_lock; +#endif + const struct stk_timer_ops *tops; + const struct stk_gpio_ops *gops; + int bus_idx; + stk_gpio_info gpio_info; +#ifdef STK_ALS_CALI + stk_timer_info cali_timer_info; +#endif + stk6d2x_cali_info cali_info; + stk6d2x_als_info als_info; + stk_timer_info alps_timer_info; + bool saturation; +#ifdef STK_FIFO_ENABLE + stk6d2x_fifo_info fifo_info; + // fifo control timer + stk_timer_info fifo_release_timer_info; + uint64_t clear_local_average; + uint64_t uv_local_average; + uint64_t ir_local_average; + bool is_clear_local_sat; + bool is_uv_local_sat; + bool is_ir_local_sat; + int index_last; + bool is_first; + bool is_local_avg_update; + uint32_t flicker; + uint32_t uv_gain; + uint32_t clear_gain; + uint32_t ir_gain; +#endif + uint8_t rid; + uint8_t xflag; + STK_REPORT_CB als_report_cb; + bool first_init; + bool is_long_it; + bool pm_state; + u8 regulator_state; + struct regulator* regulator_vdd_1p8; + struct regulator* regulator_vbus_1p8; + bool vdd_1p8_enable; + bool vbus_1p8_enable; + struct pinctrl *als_pinctrl; + struct pinctrl_state *pins_sleep; + struct pinctrl_state *pins_active; +#if IS_ENABLED(CONFIG_SENSORS_FLICKER_SELF_TEST) + bool eol_enabled; + bool recover_state; +#endif + int isTrimmed; +#if defined(CONFIG_AMS_ALS_COMPENSATION_FOR_AUTO_BRIGHTNESS) + bool als_flag; + bool flicker_flag; +#endif + int ext_clk_gpio; + struct clk *pclk; + struct rcg_clk *camcc_mclk5_rcg_clk; + struct regulator *reg; + bool reg_enable; + bool use_ext_clk; +} ; + +extern int als_debug; +extern int als_info; + +#define ALS_DBG +//#define ALS_INFO + +#ifndef ALS_dbg +#if defined(ALS_DBG) +#define ALS_dbg(format, arg...) \ + printk(KERN_DEBUG "ALS_dbg : %s: %d " format, __func__, __LINE__, ##arg) +#define ALS_err(format, arg...) \ + printk(KERN_DEBUG "ALS_err : %s: %d " format, __func__, __LINE__, ##arg) +#else +#define ALS_dbg(format, arg...) {if (als_debug)\ + printk(KERN_DEBUG "ALS_dbg : %s: %d " format, __func__, __LINE__, ##arg);\ + } +#define ALS_err(format, arg...) {if (als_debug)\ + printk(KERN_DEBUG "ALS_err : %s: %d " format, __func__, __LINE__, ##arg);\ + } +#endif +#endif + +#ifndef ALS_info +#if defined(ALS_INFO) +#define ALS_info(format, arg...) \ + printk(KERN_INFO "ALS_info : %s: %d " format, __func__, __LINE__, ##arg); +#else +#define ALS_info(format, arg...) {if (als_info)\ + printk(KERN_INFO "ALS_info : %s: %d " format, __func__, __LINE__, ##arg);\ + } +#endif +#endif + +int32_t stk6d2x_cali_als(struct stk6d2x_data *alps_data); +void stk6d2x_get_reg_default_setting(uint8_t reg, uint16_t* value); +int32_t stk6d2x_request_registry(struct stk6d2x_data *alps_data); +int32_t stk6d2x_update_registry(struct stk6d2x_data *alps_data); +int32_t stk6d2x_alps_set_config(stk6d2x_data *alps_data, bool en); +int32_t stk6d2x_init_all_setting(stk6d2x_data *alps_data); +int32_t stk6d2x_als_get_data(stk6d2x_data *alps_data, bool is_skip); +void stk6d2x_dump_reg(struct stk6d2x_data *alps_data); +void stk6d2x_force_stop(stk6d2x_data *alps_data); + +extern int sensors_create_symlink(struct kobject *target, const char *name); +extern void sensors_remove_symlink(struct kobject *target, const char *name); +extern int sensors_register(struct device **dev, void *drvdata, + struct device_attribute *attributes[], char *name); +extern void sensors_unregister(struct device *dev, + struct device_attribute *attributes[]); + +int32_t stk6d2x_enable_als(stk6d2x_data *alps_data, bool en); + +#ifdef STK_ALS_AGC + int32_t stk6d2x_cal_curDGain(uint8_t gain_val); + uint8_t stk6d2x_als_get_again_multiple(uint8_t gain); + uint8_t stk6d2x_als_get_pd_multiple(uint8_t gain, uint8_t pd_mode, uint8_t data_type); + int32_t stk6d2x_get_curGain(struct stk6d2x_data *alps_data); + void stk6d2x_get_als_ratio(struct stk6d2x_data *alps_data); +#ifdef SEC_FFT_FLICKER_1024 + uint8_t stk6d2x_sec_dgain(uint8_t gain_val); + uint8_t stk6d2x_sec_again(uint8_t gain); + uint8_t stk6d2x_sec_pd_multiple(uint8_t gain, uint8_t pd_mode, uint8_t data_type); +#endif +#endif +#ifdef STK_FIFO_ENABLE + void stk6d2x_free_fifo_data(struct stk6d2x_data *alps_data); + void stk6d2x_alloc_fifo_data(struct stk6d2x_data *alps_data, uint32_t size); + void stk6d2x_fifo_init(struct stk6d2x_data *alps_data); + int32_t stk6d2x_enable_fifo(struct stk6d2x_data *alps_data, bool en); + void stk6d2x_fifo_get_data(struct stk6d2x_data *alps_data, uint16_t frame_num); + void stk6d2x_get_fifo_data_polling(struct stk6d2x_data *alps_data); + void stk6d2x_fifo_stop_control(stk_timer_info *t_info); +#endif +#if defined(CONFIG_AMS_ALS_COMPENSATION_FOR_AUTO_BRIGHTNESS) +void stk_als_init(struct stk6d2x_data *alps_data); +void stk_als_start(struct stk6d2x_data *alps_data); +void stk_als_stop(struct stk6d2x_data *alps_data); +#endif +int32_t stk_power_ctrl(struct stk6d2x_data *alps_data, bool en); +void stk6d2x_pin_control(struct stk6d2x_data *alps_data, bool pin_set); +void stk_sec_report(struct stk6d2x_data *alps_data); +#endif // __STK6D2X_H__ diff --git a/drivers/optics/stk6d2x_cal_gpio.c b/drivers/optics/stk6d2x_cal_gpio.c new file mode 100755 index 000000000000..c74c8df6c544 --- /dev/null +++ b/drivers/optics/stk6d2x_cal_gpio.c @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License, 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#define OSC_CAL_FREQ (40) + +static u16 cal_period = 0xffff; + +u16 ams_pwm_gpio_period(struct stk6d2x_data *alps_data); + +int stk6d2x_setup_pwm(struct stk6d2x_data *alps_data, long pwm_freq) +{ + int ret = 0; + stk6d2x_wrapper *stk_wrapper = container_of(alps_data, stk6d2x_wrapper, alps_data); + struct device *dev = stk_wrapper->dev; + struct clk *pclk = NULL; + + ALS_dbg("setup gpio pwm %d Hz\n", pwm_freq); + + pclk = devm_clk_get(dev, "gpio_pwm_default-clk"); + if (NULL == pclk) { + ALS_err("get pclk (gpio_pwm_default-clk) error. \n"); + return -ENODEV; + } + + if (pwm_freq > 0) { +#if 0 + if (__clk_is_enabled(pclk)) { + ALS_dbg("clk_disable_unprepare (pclk) start! (clk_is_enabled: %d, clk_get_rate: %lld)", __clk_is_enabled(pclk), clk_get_rate(pclk)); + clk_disable_unprepare(pclk); + } + + msleep_interruptible(10); +#endif + ret = clk_set_rate(pclk, pwm_freq); + if (ret) { + ALS_err("clk_set_rate (pclk) fail! (clk_is_enabled: %d, clk_get_rate: %lld)", __clk_is_enabled(pclk), clk_get_rate(pclk)); + return ret; + } else { + ALS_dbg("clk_set_rate (pclk) success! (clk_is_enabled: %d, clk_get_rate: %lld)", __clk_is_enabled(pclk), clk_get_rate(pclk)); + } + + if (!__clk_is_enabled(pclk)) { + ret = clk_prepare_enable(pclk); + if (ret) { + ALS_err("clk_prepare_enable (pclk) fail! (clk_is_enabled: %d, clk_get_rate: %lld)", __clk_is_enabled(pclk), clk_get_rate(pclk)); + return ret; + } else { + ALS_dbg("clk_prepare_enable (pclk) success! (clk_is_enabled: %d, clk_get_rate: %lld)", __clk_is_enabled(pclk), clk_get_rate(pclk)); + } + } + + } else { + if (__clk_is_enabled(pclk)) { + clk_disable_unprepare(pclk); + ALS_dbg("clk_disable_unprepare (pclk) start! (clk_is_enabled: %d, clk_get_rate: %lld)", __clk_is_enabled(pclk), clk_get_rate(pclk)); + } + } + + return ret; +} + +ssize_t stk6d2x_osc_cal_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *alps_data = &stk_wrapper->alps_data; + int pwm_Hz = 0; + int err = 0; + + if (!data->osc_cal) { + ALS_err("no osc calibration mode\n"); + return size; + } + + err = kstrtoint(buf, 10, &pwm_Hz); + + if (err < 0) { + ALS_err("kstrtoint failed.(%d)\n", err); + return size; + } + + if (pwm_Hz == 0) { + ALS_dbg("pwm output stop! (%d Hz)\n", pwm_Hz); + stk6d2x_setup_pwm(alps_data, 0); + } else if ((pwm_Hz > 0) && (pwm_Hz <= 120)) { + stk6d2x_pin_control(alps_data, true); + + if (pwm_Hz == 1) { + ALS_dbg("pwm output start! (%d Hz)\n", OSC_CAL_FREQ); + stk6d2x_osc_cal(alps_data); + } else { + ALS_dbg("pwm output start! (%d Hz)\n", pwm_Hz); + stk6d2x_setup_pwm(alps_data, pwm_Hz); + } + } else { + ALS_err("pwm out of range error! (%d Hz)\n", pwm_Hz); + } + + return size; +} + +void stk6d2x_osc_cal(struct stk6d2x_data *alps_data) +{ + u16 ret; + + stk6d2x_setup_pwm(alps_data, OSC_CAL_FREQ); + + ret = ams_pwm_gpio_period(alps_data); + if (ret != 0) { + cal_period = ret; + } + ALS_dbg("cal_period = %d", ret); + + stk6d2x_setup_pwm(alps_data, 0); +} + +u16 stk6d2x_get_pwm_calibration(void) +{ + if (cal_period == 0) + return 0xffff; + return cal_period; +} diff --git a/drivers/optics/stk6d2x_cal_pwm.c b/drivers/optics/stk6d2x_cal_pwm.c new file mode 100755 index 000000000000..0c85545f4aad --- /dev/null +++ b/drivers/optics/stk6d2x_cal_pwm.c @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2018 Samsung Electronics Co., Ltd. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License, 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#define OSC_CAL_FREQ 40 + +static u16 cal_period = -1; + +u16 ams_pwm_gpio_period(struct stk6d2x_data *alps_data); +static struct pwm_device *pwm = NULL; + +int stk6d2x_setup_pwm(struct stk6d2x_data *alps_data, bool onoff) +{ + stk6d2x_wrapper *stk_wrapper = container_of(alps_data, stk6d2x_wrapper, alps_data); + struct device_node *np = stk_wrapper->dev->of_node; + long period = 1000000000 / OSC_CAL_FREQ; + long duty = period >> 1; + int ret = 0; + + if (onoff) { + if (pwm == NULL) { + pwm = devm_of_pwm_get(dev, np, NULL); + if (IS_ERR(pwm)) { + ALS_dbg("pwm_get error %d", PTR_ERR(pwm)); + return PTR_ERR(pwm); + } + } + + ret = pwm_config(pwm, duty, period); + pwm_enable(pwm); + } else { + pwm_disable(pwm); + pwm_put(pwm); + pwm = NULL; + } + + return ret; +} + +ssize_t stk6d2x_osc_cal_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *alps_data = &stk_wrapper->alps_data; + u16 ret; + + stk6d2x_osc_cal(alps_data); + + return size; +} + +void stk6d2x_osc_cal(struct stk6d2x_data *alps_data) +{ + u16 ret; + + stk6d2x_setup_pwm(alps_data, true); + + ret = ams_pwm_gpio_period(alps_data); + if (ret != 0) + cal_period = ret; + ALS_dbg("period : %d", ret); + + stk6d2x_setup_pwm(alps_data, false); +} + +u16 tsl2585_get_pwm_calibration(void) +{ + if (cal_period == 0) + return 0xffff; + return cal_period; +} diff --git a/drivers/optics/stk6d2x_fifo.c b/drivers/optics/stk6d2x_fifo.c new file mode 100755 index 000000000000..ab42235acc1b --- /dev/null +++ b/drivers/optics/stk6d2x_fifo.c @@ -0,0 +1,1601 @@ +/* + * stk6d2x_fifo.c - Linux kernel modules for sensortek stk6d2x + * ambient light sensor (Algorithm) + * + * Copyright (C) 2012~2018 Bk, sensortek 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include "stk6d2x_sec.h" + +#ifdef STK_FIFO_ENABLE +#ifdef SEC_FFT_FLICKER_1024 + +// sec_hamming : 0~65535, Q16 +static const int32_t hamming[2048] = { + 5243, 5243, 5243, 5244, 5245, 5246, 5248, 5250, 5252, 5254, 5257, 5260, 5263, 5267, 5271, 5275, 5279, 5284, 5289, 5294, 5300, 5305, 5312, 5318, 5325, 5332, 5339, 5346, 5354, 5362, 5370, 5379, 5388, 5397, 5407, 5417, 5427, 5437, 5448, 5458, 5470, 5481, 5493, 5505, 5517, 5530, 5543, 5556, 5569, 5583, 5597, 5611, 5626, 5641, 5656, 5671, 5687, 5703, 5719, 5735, 5752, 5769, 5787, 5804, 5822, 5840, 5859, 5878, 5897, 5916, 5935, 5955, 5975, 5996, 6016, 6037, 6059, 6080, 6102, 6124, 6146, 6169, 6192, 6215, 6238, 6262, 6286, 6310, + 6335, 6360, 6385, 6410, 6436, 6462, 6488, 6514, 6541, 6568, 6595, 6623, 6651, 6679, 6707, 6736, 6764, 6794, 6823, 6853, 6883, 6913, 6943, 6974, 7005, 7036, 7068, 7100, 7132, 7164, 7197, 7230, 7263, 7296, 7330, 7364, 7398, 7433, 7467, 7502, 7538, 7573, 7609, 7645, 7681, 7718, 7755, 7792, 7829, 7867, 7905, 7943, 7981, 8020, 8059, 8098, 8137, 8177, 8217, 8257, 8297, 8338, 8379, 8420, 8462, 8504, 8545, 8588, 8630, 8673, 8716, 8759, 8803, 8846, 8890, 8934, 8979, 9024, 9069, 9114, 9159, 9205, 9251, 9297, 9344, 9390, 9437, 9484, + 9532, 9580, 9627, 9676, 9724, 9773, 9822, 9871, 9920, 9970, 10019, 10070, 10120, 10170, 10221, 10272, 10323, 10375, 10427, 10479, 10531, 10583, 10636, 10689, 10742, 10795, 10849, 10903, 10957, 11011, 11066, 11121, 11175, 11231, 11286, 11342, 11398, 11454, 11510, 11567, 11624, 11681, 11738, 11795, 11853, 11911, 11969, 12027, 12086, 12145, 12204, 12263, 12322, 12382, 12442, 12502, 12562, 12623, 12683, 12744, 12806, 12867, 12929, 12990, 13052, 13115, 13177, 13240, 13302, 13365, 13429, 13492, 13556, 13620, 13684, 13748, 13813, 13877, 13942, 14007, 14073, 14138, 14204, 14270, 14336, 14402, 14469, 14535, + 14602, 14669, 14737, 14804, 14872, 14940, 15008, 15076, 15144, 15213, 15282, 15351, 15420, 15489, 15559, 15629, 15699, 15769, 15839, 15909, 15980, 16051, 16122, 16193, 16265, 16336, 16408, 16480, 16552, 16624, 16697, 16769, 16842, 16915, 16988, 17062, 17135, 17209, 17283, 17357, 17431, 17506, 17580, 17655, 17730, 17805, 17880, 17955, 18031, 18107, 18182, 18258, 18335, 18411, 18487, 18564, 18641, 18718, 18795, 18872, 18950, 19027, 19105, 19183, 19261, 19339, 19418, 19496, 19575, 19654, 19733, 19812, 19891, 19970, 20050, 20130, 20209, 20289, 20370, 20450, 20530, 20611, 20691, 20772, 20853, 20934, 21016, 21097, + 21178, 21260, 21342, 21424, 21506, 21588, 21670, 21753, 21835, 21918, 22001, 22084, 22167, 22250, 22333, 22417, 22500, 22584, 22668, 22751, 22836, 22920, 23004, 23088, 23173, 23257, 23342, 23427, 23512, 23597, 23682, 23767, 23853, 23938, 24024, 24110, 24196, 24281, 24367, 24454, 24540, 24626, 24713, 24799, 24886, 24973, 25059, 25146, 25233, 25320, 25408, 25495, 25582, 25670, 25758, 25845, 25933, 26021, 26109, 26197, 26285, 26373, 26462, 26550, 26638, 26727, 26816, 26904, 26993, 27082, 27171, 27260, 27349, 27438, 27527, 27617, 27706, 27796, 27885, 27975, 28064, 28154, 28244, 28334, 28424, 28514, 28604, 28694, + 28784, 28875, 28965, 29055, 29146, 29236, 29327, 29417, 29508, 29599, 29690, 29781, 29871, 29962, 30053, 30144, 30236, 30327, 30418, 30509, 30600, 30692, 30783, 30875, 30966, 31058, 31149, 31241, 31332, 31424, 31516, 31607, 31699, 31791, 31883, 31975, 32067, 32159, 32251, 32343, 32435, 32527, 32619, 32711, 32803, 32895, 32987, 33079, 33172, 33264, 33356, 33449, 33541, 33633, 33725, 33818, 33910, 34003, 34095, 34187, 34280, 34372, 34465, 34557, 34650, 34742, 34835, 34927, 35019, 35112, 35204, 35297, 35389, 35482, 35574, 35667, 35759, 35852, 35944, 36037, 36129, 36222, 36314, 36407, 36499, 36591, 36684, 36776, + 36869, 36961, 37053, 37146, 37238, 37330, 37423, 37515, 37607, 37699, 37792, 37884, 37976, 38068, 38160, 38252, 38344, 38436, 38528, 38620, 38712, 38804, 38896, 38988, 39080, 39171, 39263, 39355, 39447, 39538, 39630, 39721, 39813, 39904, 39996, 40087, 40178, 40270, 40361, 40452, 40543, 40634, 40726, 40817, 40907, 40998, 41089, 41180, 41271, 41361, 41452, 41543, 41633, 41724, 41814, 41904, 41995, 42085, 42175, 42265, 42355, 42445, 42535, 42625, 42714, 42804, 42894, 42983, 43073, 43162, 43251, 43341, 43430, 43519, 43608, 43697, 43786, 43875, 43963, 44052, 44141, 44229, 44317, 44406, 44494, 44582, 44670, 44758, + 44846, 44934, 45021, 45109, 45196, 45284, 45371, 45458, 45546, 45633, 45719, 45806, 45893, 45980, 46066, 46153, 46239, 46325, 46411, 46497, 46583, 46669, 46755, 46841, 46926, 47011, 47097, 47182, 47267, 47352, 47437, 47521, 47606, 47691, 47775, 47859, 47943, 48027, 48111, 48195, 48279, 48362, 48446, 48529, 48612, 48695, 48778, 48861, 48944, 49026, 49109, 49191, 49273, 49355, 49437, 49519, 49600, 49682, 49763, 49845, 49926, 50007, 50087, 50168, 50249, 50329, 50409, 50489, 50569, 50649, 50729, 50808, 50888, 50967, 51046, 51125, 51204, 51283, 51361, 51440, 51518, 51596, 51674, 51752, 51829, 51907, 51984, 52061, + 52138, 52215, 52291, 52368, 52444, 52520, 52597, 52672, 52748, 52824, 52899, 52974, 53049, 53124, 53199, 53273, 53348, 53422, 53496, 53570, 53644, 53717, 53790, 53864, 53937, 54009, 54082, 54155, 54227, 54299, 54371, 54443, 54514, 54586, 54657, 54728, 54799, 54869, 54940, 55010, 55080, 55150, 55220, 55290, 55359, 55428, 55497, 55566, 55635, 55703, 55771, 55839, 55907, 55975, 56042, 56110, 56177, 56244, 56310, 56377, 56443, 56509, 56575, 56641, 56706, 56772, 56837, 56902, 56966, 57031, 57095, 57159, 57223, 57287, 57350, 57413, 57476, 57539, 57602, 57664, 57727, 57789, 57850, 57912, 57973, 58034, 58095, 58156, + 58217, 58277, 58337, 58397, 58457, 58516, 58575, 58634, 58693, 58752, 58810, 58868, 58926, 58984, 59041, 59098, 59155, 59212, 59269, 59325, 59381, 59437, 59493, 59548, 59603, 59658, 59713, 59768, 59822, 59876, 59930, 59983, 60037, 60090, 60143, 60196, 60248, 60300, 60352, 60404, 60455, 60507, 60558, 60608, 60659, 60709, 60759, 60809, 60859, 60908, 60957, 61006, 61055, 61103, 61151, 61199, 61247, 61294, 61342, 61389, 61435, 61482, 61528, 61574, 61620, 61665, 61710, 61755, 61800, 61844, 61889, 61933, 61976, 62020, 62063, 62106, 62149, 62191, 62233, 62275, 62317, 62359, 62400, 62441, 62481, 62522, 62562, 62602, + 62642, 62681, 62720, 62759, 62798, 62836, 62874, 62912, 62950, 62987, 63024, 63061, 63098, 63134, 63170, 63206, 63241, 63276, 63311, 63346, 63381, 63415, 63449, 63483, 63516, 63549, 63582, 63615, 63647, 63679, 63711, 63742, 63774, 63805, 63836, 63866, 63896, 63926, 63956, 63985, 64014, 64043, 64072, 64100, 64128, 64156, 64184, 64211, 64238, 64265, 64291, 64317, 64343, 64369, 64394, 64419, 64444, 64469, 64493, 64517, 64540, 64564, 64587, 64610, 64633, 64655, 64677, 64699, 64720, 64741, 64762, 64783, 64804, 64824, 64843, 64863, 64882, 64901, 64920, 64939, 64957, 64975, 64992, 65010, 65027, 65043, 65060, 65076, + 65092, 65108, 65123, 65138, 65153, 65168, 65182, 65196, 65210, 65223, 65236, 65249, 65262, 65274, 65286, 65298, 65309, 65320, 65331, 65342, 65352, 65362, 65372, 65382, 65391, 65400, 65408, 65417, 65425, 65433, 65440, 65447, 65454, 65461, 65467, 65473, 65479, 65485, 65490, 65495, 65500, 65504, 65508, 65512, 65516, 65519, 65522, 65525, 65527, 65529, 65531, 65532, 65534, 65535, 65535, 65536, 65536, 65536, 65535, 65535, 65534, 65532, 65531, 65529, 65527, 65525, 65522, 65519, 65516, 65512, 65508, 65504, 65500, 65495, 65490, 65485, 65479, 65473, 65467, 65461, 65454, 65447, 65440, 65433, 65425, 65417, 65408, 65400, + 65391, 65382, 65372, 65362, 65352, 65342, 65331, 65320, 65309, 65298, 65286, 65274, 65262, 65249, 65236, 65223, 65210, 65196, 65182, 65168, 65153, 65138, 65123, 65108, 65092, 65076, 65060, 65043, 65027, 65010, 64992, 64975, 64957, 64939, 64920, 64901, 64882, 64863, 64843, 64824, 64804, 64783, 64762, 64741, 64720, 64699, 64677, 64655, 64633, 64610, 64587, 64564, 64540, 64517, 64493, 64469, 64444, 64419, 64394, 64369, 64343, 64317, 64291, 64265, 64238, 64211, 64184, 64156, 64128, 64100, 64072, 64043, 64014, 63985, 63956, 63926, 63896, 63866, 63836, 63805, 63774, 63742, 63711, 63679, 63647, 63615, 63582, 63549, + 63516, 63483, 63449, 63415, 63381, 63346, 63311, 63276, 63241, 63206, 63170, 63134, 63098, 63061, 63024, 62987, 62950, 62912, 62874, 62836, 62798, 62759, 62720, 62681, 62642, 62602, 62562, 62522, 62481, 62441, 62400, 62359, 62317, 62275, 62233, 62191, 62149, 62106, 62063, 62020, 61976, 61933, 61889, 61844, 61800, 61755, 61710, 61665, 61620, 61574, 61528, 61482, 61435, 61389, 61342, 61294, 61247, 61199, 61151, 61103, 61055, 61006, 60957, 60908, 60859, 60809, 60759, 60709, 60659, 60608, 60558, 60507, 60455, 60404, 60352, 60300, 60248, 60196, 60143, 60090, 60037, 59983, 59930, 59876, 59822, 59768, 59713, 59658, + 59603, 59548, 59493, 59437, 59381, 59325, 59269, 59212, 59155, 59098, 59041, 58984, 58926, 58868, 58810, 58752, 58693, 58634, 58575, 58516, 58457, 58397, 58337, 58277, 58217, 58156, 58095, 58034, 57973, 57912, 57850, 57789, 57727, 57664, 57602, 57539, 57476, 57413, 57350, 57287, 57223, 57159, 57095, 57031, 56966, 56902, 56837, 56772, 56706, 56641, 56575, 56509, 56443, 56377, 56310, 56244, 56177, 56110, 56042, 55975, 55907, 55839, 55771, 55703, 55635, 55566, 55497, 55428, 55359, 55290, 55220, 55150, 55080, 55010, 54940, 54869, 54799, 54728, 54657, 54586, 54514, 54443, 54371, 54299, 54227, 54155, 54082, 54009, + 53937, 53864, 53790, 53717, 53644, 53570, 53496, 53422, 53348, 53273, 53199, 53124, 53049, 52974, 52899, 52824, 52748, 52672, 52597, 52520, 52444, 52368, 52291, 52215, 52138, 52061, 51984, 51907, 51829, 51752, 51674, 51596, 51518, 51440, 51361, 51283, 51204, 51125, 51046, 50967, 50888, 50808, 50729, 50649, 50569, 50489, 50409, 50329, 50249, 50168, 50087, 50007, 49926, 49845, 49763, 49682, 49600, 49519, 49437, 49355, 49273, 49191, 49109, 49026, 48944, 48861, 48778, 48695, 48612, 48529, 48446, 48362, 48279, 48195, 48111, 48027, 47943, 47859, 47775, 47691, 47606, 47521, 47437, 47352, 47267, 47182, 47097, 47011, + 46926, 46841, 46755, 46669, 46583, 46497, 46411, 46325, 46239, 46153, 46066, 45980, 45893, 45806, 45719, 45633, 45546, 45458, 45371, 45284, 45196, 45109, 45021, 44934, 44846, 44758, 44670, 44582, 44494, 44406, 44317, 44229, 44141, 44052, 43963, 43875, 43786, 43697, 43608, 43519, 43430, 43341, 43251, 43162, 43073, 42983, 42894, 42804, 42714, 42625, 42535, 42445, 42355, 42265, 42175, 42085, 41995, 41904, 41814, 41724, 41633, 41543, 41452, 41361, 41271, 41180, 41089, 40998, 40907, 40817, 40726, 40634, 40543, 40452, 40361, 40270, 40178, 40087, 39996, 39904, 39813, 39721, 39630, 39538, 39447, 39355, 39263, 39171, + 39080, 38988, 38896, 38804, 38712, 38620, 38528, 38436, 38344, 38252, 38160, 38068, 37976, 37884, 37792, 37699, 37607, 37515, 37423, 37330, 37238, 37146, 37053, 36961, 36869, 36776, 36684, 36591, 36499, 36407, 36314, 36222, 36129, 36037, 35944, 35852, 35759, 35667, 35574, 35482, 35389, 35297, 35204, 35112, 35019, 34927, 34835, 34742, 34650, 34557, 34465, 34372, 34280, 34187, 34095, 34003, 33910, 33818, 33725, 33633, 33541, 33449, 33356, 33264, 33172, 33079, 32987, 32895, 32803, 32711, 32619, 32527, 32435, 32343, 32251, 32159, 32067, 31975, 31883, 31791, 31699, 31607, 31516, 31424, 31332, 31241, 31149, 31058, + 30966, 30875, 30783, 30692, 30600, 30509, 30418, 30327, 30236, 30144, 30053, 29962, 29871, 29781, 29690, 29599, 29508, 29417, 29327, 29236, 29146, 29055, 28965, 28875, 28784, 28694, 28604, 28514, 28424, 28334, 28244, 28154, 28064, 27975, 27885, 27796, 27706, 27617, 27527, 27438, 27349, 27260, 27171, 27082, 26993, 26904, 26816, 26727, 26638, 26550, 26462, 26373, 26285, 26197, 26109, 26021, 25933, 25845, 25758, 25670, 25582, 25495, 25408, 25320, 25233, 25146, 25059, 24973, 24886, 24799, 24713, 24626, 24540, 24454, 24367, 24281, 24196, 24110, 24024, 23938, 23853, 23767, 23682, 23597, 23512, 23427, 23342, 23257, + 23173, 23088, 23004, 22920, 22836, 22751, 22668, 22584, 22500, 22417, 22333, 22250, 22167, 22084, 22001, 21918, 21835, 21753, 21670, 21588, 21506, 21424, 21342, 21260, 21178, 21097, 21016, 20934, 20853, 20772, 20691, 20611, 20530, 20450, 20370, 20289, 20209, 20130, 20050, 19970, 19891, 19812, 19733, 19654, 19575, 19496, 19418, 19339, 19261, 19183, 19105, 19027, 18950, 18872, 18795, 18718, 18641, 18564, 18487, 18411, 18335, 18258, 18182, 18107, 18031, 17955, 17880, 17805, 17730, 17655, 17580, 17506, 17431, 17357, 17283, 17209, 17135, 17062, 16988, 16915, 16842, 16769, 16697, 16624, 16552, 16480, 16408, 16336, + 16265, 16193, 16122, 16051, 15980, 15909, 15839, 15769, 15699, 15629, 15559, 15489, 15420, 15351, 15282, 15213, 15144, 15076, 15008, 14940, 14872, 14804, 14737, 14669, 14602, 14535, 14469, 14402, 14336, 14270, 14204, 14138, 14073, 14007, 13942, 13877, 13813, 13748, 13684, 13620, 13556, 13492, 13429, 13365, 13302, 13240, 13177, 13115, 13052, 12990, 12929, 12867, 12806, 12744, 12683, 12623, 12562, 12502, 12442, 12382, 12322, 12263, 12204, 12145, 12086, 12027, 11969, 11911, 11853, 11795, 11738, 11681, 11624, 11567, 11510, 11454, 11398, 11342, 11286, 11231, 11175, 11121, 11066, 11011, 10957, 10903, 10849, 10795, + 10742, 10689, 10636, 10583, 10531, 10479, 10427, 10375, 10323, 10272, 10221, 10170, 10120, 10070, 10019, 9970, 9920, 9871, 9822, 9773, 9724, 9676, 9627, 9580, 9532, 9484, 9437, 9390, 9344, 9297, 9251, 9205, 9159, 9114, 9069, 9024, 8979, 8934, 8890, 8846, 8803, 8759, 8716, 8673, 8630, 8588, 8545, 8504, 8462, 8420, 8379, 8338, 8297, 8257, 8217, 8177, 8137, 8098, 8059, 8020, 7981, 7943, 7905, 7867, 7829, 7792, 7755, 7718, 7681, 7645, 7609, 7573, 7538, 7502, 7467, 7433, 7398, 7364, 7330, 7296, 7263, 7230, 7197, 7164, 7132, 7100, 7068, 7036, + 7005, 6974, 6943, 6913, 6883, 6853, 6823, 6794, 6764, 6736, 6707, 6679, 6651, 6623, 6595, 6568, 6541, 6514, 6488, 6462, 6436, 6410, 6385, 6360, 6335, 6310, 6286, 6262, 6238, 6215, 6192, 6169, 6146, 6124, 6102, 6080, 6059, 6037, 6016, 5996, 5975, 5955, 5935, 5916, 5897, 5878, 5859, 5840, 5822, 5804, 5787, 5769, 5752, 5735, 5719, 5703, 5687, 5671, 5656, 5641, 5626, 5611, 5597, 5583, 5569, 5556, 5543, 5530, 5517, 5505, 5493, 5481, 5470, 5458, 5448, 5437, 5427, 5417, 5407, 5397, 5388, 5379, 5370, 5362, 5354, 5346, 5339, 5332, + 5325, 5318, 5312, 5305, 5300, 5294, 5289, 5284, 5279, 5275, 5271, 5267, 5263, 5260, 5257, 5254, 5252, 5250, 5248, 5246, 5245, 5244, 5243, 5243, +}; + +// sec_sin, sec_cos : -65536~65536 +static const int32_t sin[1024] = { + 0, 205886, 411764, 617627, 823467, 1029276, 1235045, + 1440769, 1646438, 1852045, 2057582, 2263042, 2468417, 2673699, + 2878880, 3083953, 3288909, 3493742, 3698444, 3903006, 4107421, + 4311681, 4515779, 4719707, 4923458, 5127023, 5330395, 5533566, + 5736529, 5939276, 6141799, 6344092, 6546145, 6747952, 6949505, + 7150796, 7351818, 7552563, 7753024, 7953192, 8153062, 8352624, + 8551872, 8750798, 8949395, 9147655, 9345570, 9543133, 9740337, + 9937175, 10133638, 10329720, 10525413, 10720709, 10915602, 11110084, + 11304148, 11497786, 11690991, 11883756, 12076074, 12267936, 12459338, + 12650270, 12840725, 13030697, 13220179, 13409163, 13597642, 13785609, + 13973057, 14159979, 14346368, 14532217, 14717519, 14902266, 15086453, + 15270071, 15453115, 15635577, 15817450, 15998727, 16179403, 16359469, + 16538919, 16717746, 16895944, 17073506, 17250426, 17426695, 17602309, + 17777260, 17951541, 18125147, 18298070, 18470305, 18641844, 18812681, + 18982810, 19152224, 19320917, 19488882, 19656114, 19822606, 19988352, + 20153345, 20317579, 20481048, 20643747, 20805668, 20966805, 21127153, + 21286706, 21445458, 21603402, 21760532, 21916844, 22072330, 22226985, + 22380804, 22533779, 22685907, 22837180, 22987593, 23137141, 23285818, + 23433618, 23580536, 23726566, 23871703, 24015941, 24159275, 24301699, + 24443209, 24583798, 24723461, 24862194, 24999991, 25136846, 25272755, + 25407713, 25541714, 25674753, 25806826, 25937927, 26068051, 26197194, + 26325351, 26452517, 26578686, 26703855, 26828019, 26951172, 27073311, + 27194431, 27314527, 27433594, 27551629, 27668626, 27784581, 27899491, + 28013350, 28126154, 28237899, 28348582, 28458196, 28566740, 28674208, + 28780596, 28885901, 28990118, 29093244, 29195275, 29296206, 29396034, + 29494756, 29592367, 29688864, 29784243, 29878501, 29971634, 30063639, + 30154511, 30244249, 30332847, 30420304, 30506615, 30591778, 30675789, + 30758645, 30840343, 30920880, 31000253, 31078459, 31155494, 31231357, + 31306043, 31379551, 31451878, 31523021, 31592976, 31661743, 31729317, + 31795696, 31860879, 31924862, 31987643, 32049219, 32109589, 32168751, + 32226701, 32283437, 32338958, 32393262, 32446346, 32498209, 32548848, + 32598261, 32646447, 32693405, 32739131, 32783624, 32826884, 32868907, + 32909693, 32949240, 32987546, 33024611, 33060432, 33095008, 33128338, + 33160421, 33191256, 33220841, 33249175, 33276257, 33302087, 33326663, + 33349984, 33372049, 33392858, 33412410, 33430704, 33447739, 33463515, + 33478031, 33491286, 33503281, 33514014, 33523486, 33531695, 33538642, + 33544326, 33548747, 33551905, 33553800, 33554432, 33553800, 33551905, + 33548747, 33544326, 33538642, 33531695, 33523486, 33514014, 33503281, + 33491286, 33478031, 33463515, 33447739, 33430704, 33412410, 33392858, + 33372049, 33349984, 33326663, 33302087, 33276257, 33249175, 33220841, + 33191256, 33160421, 33128338, 33095008, 33060432, 33024611, 32987546, + 32949240, 32909693, 32868907, 32826884, 32783624, 32739131, 32693405, + 32646447, 32598261, 32548848, 32498209, 32446346, 32393262, 32338958, + 32283437, 32226701, 32168751, 32109589, 32049219, 31987643, 31924862, + 31860879, 31795696, 31729317, 31661743, 31592976, 31523021, 31451878, + 31379551, 31306043, 31231357, 31155494, 31078459, 31000253, 30920880, + 30840343, 30758645, 30675789, 30591778, 30506615, 30420304, 30332847, + 30244249, 30154511, 30063639, 29971634, 29878501, 29784243, 29688864, + 29592367, 29494756, 29396034, 29296206, 29195275, 29093244, 28990118, + 28885901, 28780596, 28674208, 28566740, 28458196, 28348582, 28237899, + 28126154, 28013350, 27899491, 27784581, 27668626, 27551629, 27433594, + 27314527, 27194431, 27073311, 26951172, 26828019, 26703855, 26578686, + 26452517, 26325351, 26197194, 26068051, 25937927, 25806826, 25674753, + 25541714, 25407713, 25272755, 25136846, 24999991, 24862194, 24723461, + 24583798, 24443209, 24301699, 24159275, 24015941, 23871703, 23726566, + 23580536, 23433618, 23285818, 23137141, 22987593, 22837180, 22685907, + 22533779, 22380804, 22226985, 22072330, 21916844, 21760532, 21603402, + 21445458, 21286706, 21127153, 20966805, 20805668, 20643747, 20481048, + 20317579, 20153345, 19988352, 19822606, 19656114, 19488882, 19320917, + 19152224, 18982810, 18812681, 18641844, 18470305, 18298070, 18125147, + 17951541, 17777260, 17602309, 17426695, 17250426, 17073506, 16895944, + 16717746, 16538919, 16359469, 16179403, 15998727, 15817450, 15635577, + 15453115, 15270071, 15086453, 14902266, 14717519, 14532217, 14346368, + 14159979, 13973057, 13785609, 13597642, 13409163, 13220179, 13030697, + 12840725, 12650270, 12459338, 12267936, 12076074, 11883756, 11690991, + 11497786, 11304148, 11110084, 10915602, 10720709, 10525413, 10329720, + 10133638, 9937175, 9740337, 9543133, 9345570, 9147655, 8949395, + 8750798, 8551872, 8352624, 8153062, 7953192, 7753024, 7552563, + 7351818, 7150796, 6949505, 6747952, 6546145, 6344092, 6141799, + 5939276, 5736529, 5533566, 5330395, 5127023, 4923458, 4719707, + 4515779, 4311681, 4107421, 3903006, 3698444, 3493742, 3288909, + 3083953, 2878880, 2673699, 2468417, 2263042, 2057582, 1852045, + 1646438, 1440769, 1235045, 1029276, 823467, 617627, 411764, + 205886, 0, -205886, -411764, -617627, -823467, -1029276, + -1235045, -1440769, -1646438, -1852045, -2057582, -2263042, -2468417, + -2673699, -2878880, -3083953, -3288909, -3493742, -3698444, -3903006, + -4107421, -4311681, -4515779, -4719707, -4923458, -5127023, -5330395, + -5533566, -5736529, -5939276, -6141799, -6344092, -6546145, -6747952, + -6949505, -7150796, -7351818, -7552563, -7753024, -7953192, -8153062, + -8352624, -8551872, -8750798, -8949395, -9147655, -9345570, -9543133, + -9740337, -9937175, -10133638, -10329720, -10525413, -10720709, -10915602, + -11110084, -11304148, -11497786, -11690991, -11883756, -12076074, -12267936, + -12459338, -12650270, -12840725, -13030697, -13220179, -13409163, -13597642, + -13785609, -13973057, -14159979, -14346368, -14532217, -14717519, -14902266, + -15086453, -15270071, -15453115, -15635577, -15817450, -15998727, -16179403, + -16359469, -16538919, -16717746, -16895944, -17073506, -17250426, -17426695, + -17602309, -17777260, -17951541, -18125147, -18298070, -18470305, -18641844, + -18812681, -18982810, -19152224, -19320917, -19488882, -19656114, -19822606, + -19988352, -20153345, -20317579, -20481048, -20643747, -20805668, -20966805, + -21127153, -21286706, -21445458, -21603402, -21760532, -21916844, -22072330, + -22226985, -22380804, -22533779, -22685907, -22837180, -22987593, -23137141, + -23285818, -23433618, -23580536, -23726566, -23871703, -24015941, -24159275, + -24301699, -24443209, -24583798, -24723461, -24862194, -24999991, -25136846, + -25272755, -25407713, -25541714, -25674753, -25806826, -25937927, -26068051, + -26197194, -26325351, -26452517, -26578686, -26703855, -26828019, -26951172, + -27073311, -27194431, -27314527, -27433594, -27551629, -27668626, -27784581, + -27899491, -28013350, -28126154, -28237899, -28348582, -28458196, -28566740, + -28674208, -28780596, -28885901, -28990118, -29093244, -29195275, -29296206, + -29396034, -29494756, -29592367, -29688864, -29784243, -29878501, -29971634, + -30063639, -30154511, -30244249, -30332847, -30420304, -30506615, -30591778, + -30675789, -30758645, -30840343, -30920880, -31000253, -31078459, -31155494, + -31231357, -31306043, -31379551, -31451878, -31523021, -31592976, -31661743, + -31729317, -31795696, -31860879, -31924862, -31987643, -32049219, -32109589, + -32168751, -32226701, -32283437, -32338958, -32393262, -32446346, -32498209, + -32548848, -32598261, -32646447, -32693405, -32739131, -32783624, -32826884, + -32868907, -32909693, -32949240, -32987546, -33024611, -33060432, -33095008, + -33128338, -33160421, -33191256, -33220841, -33249175, -33276257, -33302087, + -33326663, -33349984, -33372049, -33392858, -33412410, -33430704, -33447739, + -33463515, -33478031, -33491286, -33503281, -33514014, -33523486, -33531695, + -33538642, -33544326, -33548747, -33551905, -33553800, -33554432, -33553800, + -33551905, -33548747, -33544326, -33538642, -33531695, -33523486, -33514014, + -33503281, -33491286, -33478031, -33463515, -33447739, -33430704, -33412410, + -33392858, -33372049, -33349984, -33326663, -33302087, -33276257, -33249175, + -33220841, -33191256, -33160421, -33128338, -33095008, -33060432, -33024611, + -32987546, -32949240, -32909693, -32868907, -32826884, -32783624, -32739131, + -32693405, -32646447, -32598261, -32548848, -32498209, -32446346, -32393262, + -32338958, -32283437, -32226701, -32168751, -32109589, -32049219, -31987643, + -31924862, -31860879, -31795696, -31729317, -31661743, -31592976, -31523021, + -31451878, -31379551, -31306043, -31231357, -31155494, -31078459, -31000253, + -30920880, -30840343, -30758645, -30675789, -30591778, -30506615, -30420304, + -30332847, -30244249, -30154511, -30063639, -29971634, -29878501, -29784243, + -29688864, -29592367, -29494756, -29396034, -29296206, -29195275, -29093244, + -28990118, -28885901, -28780596, -28674208, -28566740, -28458196, -28348582, + -28237899, -28126154, -28013350, -27899491, -27784581, -27668626, -27551629, + -27433594, -27314527, -27194431, -27073311, -26951172, -26828019, -26703855, + -26578686, -26452517, -26325351, -26197194, -26068051, -25937927, -25806826, + -25674753, -25541714, -25407713, -25272755, -25136846, -24999991, -24862194, + -24723461, -24583798, -24443209, -24301699, -24159275, -24015941, -23871703, + -23726566, -23580536, -23433618, -23285818, -23137141, -22987593, -22837180, + -22685907, -22533779, -22380804, -22226985, -22072330, -21916844, -21760532, + -21603402, -21445458, -21286706, -21127153, -20966805, -20805668, -20643747, + -20481048, -20317579, -20153345, -19988352, -19822606, -19656114, -19488882, + -19320917, -19152224, -18982810, -18812681, -18641844, -18470305, -18298070, + -18125147, -17951541, -17777260, -17602309, -17426695, -17250426, -17073506, + -16895944, -16717746, -16538919, -16359469, -16179403, -15998727, -15817450, + -15635577, -15453115, -15270071, -15086453, -14902266, -14717519, -14532217, + -14346368, -14159979, -13973057, -13785609, -13597642, -13409163, -13220179, + -13030697, -12840725, -12650270, -12459338, -12267936, -12076074, -11883756, + -11690991, -11497786, -11304148, -11110084, -10915602, -10720709, -10525413, + -10329720, -10133638, -9937175, -9740337, -9543133, -9345570, -9147655, + -8949395, -8750798, -8551872, -8352624, -8153062, -7953192, -7753024, + -7552563, -7351818, -7150796, -6949505, -6747952, -6546145, -6344092, + -6141799, -5939276, -5736529, -5533566, -5330395, -5127023, -4923458, + -4719707, -4515779, -4311681, -4107421, -3903006, -3698444, -3493742, + -3288909, -3083953, -2878880, -2673699, -2468417, -2263042, -2057582, + -1852045, -1646438, -1440769, -1235045, -1029276, -823467, -617627, + -411764, -205886 +}; + +static const int32_t cos[1024] = { + 33554432, 33553800, 33551905, 33548747, 33544326, 33538642, 33531695, + 33523486, 33514014, 33503281, 33491286, 33478031, 33463515, 33447739, + 33430704, 33412410, 33392858, 33372049, 33349984, 33326663, 33302087, + 33276257, 33249175, 33220841, 33191256, 33160421, 33128338, 33095008, + 33060432, 33024611, 32987546, 32949240, 32909693, 32868907, 32826884, + 32783624, 32739131, 32693405, 32646447, 32598261, 32548848, 32498209, + 32446346, 32393262, 32338958, 32283437, 32226701, 32168751, 32109589, + 32049219, 31987643, 31924862, 31860879, 31795696, 31729317, 31661743, + 31592976, 31523021, 31451878, 31379551, 31306043, 31231357, 31155494, + 31078459, 31000253, 30920880, 30840343, 30758645, 30675789, 30591778, + 30506615, 30420304, 30332847, 30244249, 30154511, 30063639, 29971634, + 29878501, 29784243, 29688864, 29592367, 29494756, 29396034, 29296206, + 29195275, 29093244, 28990118, 28885901, 28780596, 28674208, 28566740, + 28458196, 28348582, 28237899, 28126154, 28013350, 27899491, 27784581, + 27668626, 27551629, 27433594, 27314527, 27194431, 27073311, 26951172, + 26828019, 26703855, 26578686, 26452517, 26325351, 26197194, 26068051, + 25937927, 25806826, 25674753, 25541714, 25407713, 25272755, 25136846, + 24999991, 24862194, 24723461, 24583798, 24443209, 24301699, 24159275, + 24015941, 23871703, 23726566, 23580536, 23433618, 23285818, 23137141, + 22987593, 22837180, 22685907, 22533779, 22380804, 22226985, 22072330, + 21916844, 21760532, 21603402, 21445458, 21286706, 21127153, 20966805, + 20805668, 20643747, 20481048, 20317579, 20153345, 19988352, 19822606, + 19656114, 19488882, 19320917, 19152224, 18982810, 18812681, 18641844, + 18470305, 18298070, 18125147, 17951541, 17777260, 17602309, 17426695, + 17250426, 17073506, 16895944, 16717746, 16538919, 16359469, 16179403, + 15998727, 15817450, 15635577, 15453115, 15270071, 15086453, 14902266, + 14717519, 14532217, 14346368, 14159979, 13973057, 13785609, 13597642, + 13409163, 13220179, 13030697, 12840725, 12650270, 12459338, 12267936, + 12076074, 11883756, 11690991, 11497786, 11304148, 11110084, 10915602, + 10720709, 10525413, 10329720, 10133638, 9937175, 9740337, 9543133, + 9345570, 9147655, 8949395, 8750798, 8551872, 8352624, 8153062, + 7953192, 7753024, 7552563, 7351818, 7150796, 6949505, 6747952, + 6546145, 6344092, 6141799, 5939276, 5736529, 5533566, 5330395, + 5127023, 4923458, 4719707, 4515779, 4311681, 4107421, 3903006, + 3698444, 3493742, 3288909, 3083953, 2878880, 2673699, 2468417, + 2263042, 2057582, 1852045, 1646438, 1440769, 1235045, 1029276, + 823467, 617627, 411764, 205886, 0, -205886, -411764, + -617627, -823467, -1029276, -1235045, -1440769, -1646438, -1852045, + -2057582, -2263042, -2468417, -2673699, -2878880, -3083953, -3288909, + -3493742, -3698444, -3903006, -4107421, -4311681, -4515779, -4719707, + -4923458, -5127023, -5330395, -5533566, -5736529, -5939276, -6141799, + -6344092, -6546145, -6747952, -6949505, -7150796, -7351818, -7552563, + -7753024, -7953192, -8153062, -8352624, -8551872, -8750798, -8949395, + -9147655, -9345570, -9543133, -9740337, -9937175, -10133638, -10329720, + -10525413, -10720709, -10915602, -11110084, -11304148, -11497786, -11690991, + -11883756, -12076074, -12267936, -12459338, -12650270, -12840725, -13030697, + -13220179, -13409163, -13597642, -13785609, -13973057, -14159979, -14346368, + -14532217, -14717519, -14902266, -15086453, -15270071, -15453115, -15635577, + -15817450, -15998727, -16179403, -16359469, -16538919, -16717746, -16895944, + -17073506, -17250426, -17426695, -17602309, -17777260, -17951541, -18125147, + -18298070, -18470305, -18641844, -18812681, -18982810, -19152224, -19320917, + -19488882, -19656114, -19822606, -19988352, -20153345, -20317579, -20481048, + -20643747, -20805668, -20966805, -21127153, -21286706, -21445458, -21603402, + -21760532, -21916844, -22072330, -22226985, -22380804, -22533779, -22685907, + -22837180, -22987593, -23137141, -23285818, -23433618, -23580536, -23726566, + -23871703, -24015941, -24159275, -24301699, -24443209, -24583798, -24723461, + -24862194, -24999991, -25136846, -25272755, -25407713, -25541714, -25674753, + -25806826, -25937927, -26068051, -26197194, -26325351, -26452517, -26578686, + -26703855, -26828019, -26951172, -27073311, -27194431, -27314527, -27433594, + -27551629, -27668626, -27784581, -27899491, -28013350, -28126154, -28237899, + -28348582, -28458196, -28566740, -28674208, -28780596, -28885901, -28990118, + -29093244, -29195275, -29296206, -29396034, -29494756, -29592367, -29688864, + -29784243, -29878501, -29971634, -30063639, -30154511, -30244249, -30332847, + -30420304, -30506615, -30591778, -30675789, -30758645, -30840343, -30920880, + -31000253, -31078459, -31155494, -31231357, -31306043, -31379551, -31451878, + -31523021, -31592976, -31661743, -31729317, -31795696, -31860879, -31924862, + -31987643, -32049219, -32109589, -32168751, -32226701, -32283437, -32338958, + -32393262, -32446346, -32498209, -32548848, -32598261, -32646447, -32693405, + -32739131, -32783624, -32826884, -32868907, -32909693, -32949240, -32987546, + -33024611, -33060432, -33095008, -33128338, -33160421, -33191256, -33220841, + -33249175, -33276257, -33302087, -33326663, -33349984, -33372049, -33392858, + -33412410, -33430704, -33447739, -33463515, -33478031, -33491286, -33503281, + -33514014, -33523486, -33531695, -33538642, -33544326, -33548747, -33551905, + -33553800, -33554432, -33553800, -33551905, -33548747, -33544326, -33538642, + -33531695, -33523486, -33514014, -33503281, -33491286, -33478031, -33463515, + -33447739, -33430704, -33412410, -33392858, -33372049, -33349984, -33326663, + -33302087, -33276257, -33249175, -33220841, -33191256, -33160421, -33128338, + -33095008, -33060432, -33024611, -32987546, -32949240, -32909693, -32868907, + -32826884, -32783624, -32739131, -32693405, -32646447, -32598261, -32548848, + -32498209, -32446346, -32393262, -32338958, -32283437, -32226701, -32168751, + -32109589, -32049219, -31987643, -31924862, -31860879, -31795696, -31729317, + -31661743, -31592976, -31523021, -31451878, -31379551, -31306043, -31231357, + -31155494, -31078459, -31000253, -30920880, -30840343, -30758645, -30675789, + -30591778, -30506615, -30420304, -30332847, -30244249, -30154511, -30063639, + -29971634, -29878501, -29784243, -29688864, -29592367, -29494756, -29396034, + -29296206, -29195275, -29093244, -28990118, -28885901, -28780596, -28674208, + -28566740, -28458196, -28348582, -28237899, -28126154, -28013350, -27899491, + -27784581, -27668626, -27551629, -27433594, -27314527, -27194431, -27073311, + -26951172, -26828019, -26703855, -26578686, -26452517, -26325351, -26197194, + -26068051, -25937927, -25806826, -25674753, -25541714, -25407713, -25272755, + -25136846, -24999991, -24862194, -24723461, -24583798, -24443209, -24301699, + -24159275, -24015941, -23871703, -23726566, -23580536, -23433618, -23285818, + -23137141, -22987593, -22837180, -22685907, -22533779, -22380804, -22226985, + -22072330, -21916844, -21760532, -21603402, -21445458, -21286706, -21127153, + -20966805, -20805668, -20643747, -20481048, -20317579, -20153345, -19988352, + -19822606, -19656114, -19488882, -19320917, -19152224, -18982810, -18812681, + -18641844, -18470305, -18298070, -18125147, -17951541, -17777260, -17602309, + -17426695, -17250426, -17073506, -16895944, -16717746, -16538919, -16359469, + -16179403, -15998727, -15817450, -15635577, -15453115, -15270071, -15086453, + -14902266, -14717519, -14532217, -14346368, -14159979, -13973057, -13785609, + -13597642, -13409163, -13220179, -13030697, -12840725, -12650270, -12459338, + -12267936, -12076074, -11883756, -11690991, -11497786, -11304148, -11110084, + -10915602, -10720709, -10525413, -10329720, -10133638, -9937175, -9740337, + -9543133, -9345570, -9147655, -8949395, -8750798, -8551872, -8352624, + -8153062, -7953192, -7753024, -7552563, -7351818, -7150796, -6949505, + -6747952, -6546145, -6344092, -6141799, -5939276, -5736529, -5533566, + -5330395, -5127023, -4923458, -4719707, -4515779, -4311681, -4107421, + -3903006, -3698444, -3493742, -3288909, -3083953, -2878880, -2673699, + -2468417, -2263042, -2057582, -1852045, -1646438, -1440769, -1235045, + -1029276, -823467, -617627, -411764, -205886, 0, 205886, + 411764, 617627, 823467, 1029276, 1235045, 1440769, 1646438, + 1852045, 2057582, 2263042, 2468417, 2673699, 2878880, 3083953, + 3288909, 3493742, 3698444, 3903006, 4107421, 4311681, 4515779, + 4719707, 4923458, 5127023, 5330395, 5533566, 5736529, 5939276, + 6141799, 6344092, 6546145, 6747952, 6949505, 7150796, 7351818, + 7552563, 7753024, 7953192, 8153062, 8352624, 8551872, 8750798, + 8949395, 9147655, 9345570, 9543133, 9740337, 9937175, 10133638, + 10329720, 10525413, 10720709, 10915602, 11110084, 11304148, 11497786, + 11690991, 11883756, 12076074, 12267936, 12459338, 12650270, 12840725, + 13030697, 13220179, 13409163, 13597642, 13785609, 13973057, 14159979, + 14346368, 14532217, 14717519, 14902266, 15086453, 15270071, 15453115, + 15635577, 15817450, 15998727, 16179403, 16359469, 16538919, 16717746, + 16895944, 17073506, 17250426, 17426695, 17602309, 17777260, 17951541, + 18125147, 18298070, 18470305, 18641844, 18812681, 18982810, 19152224, + 19320917, 19488882, 19656114, 19822606, 19988352, 20153345, 20317579, + 20481048, 20643747, 20805668, 20966805, 21127153, 21286706, 21445458, + 21603402, 21760532, 21916844, 22072330, 22226985, 22380804, 22533779, + 22685907, 22837180, 22987593, 23137141, 23285818, 23433618, 23580536, + 23726566, 23871703, 24015941, 24159275, 24301699, 24443209, 24583798, + 24723461, 24862194, 24999991, 25136846, 25272755, 25407713, 25541714, + 25674753, 25806826, 25937927, 26068051, 26197194, 26325351, 26452517, + 26578686, 26703855, 26828019, 26951172, 27073311, 27194431, 27314527, + 27433594, 27551629, 27668626, 27784581, 27899491, 28013350, 28126154, + 28237899, 28348582, 28458196, 28566740, 28674208, 28780596, 28885901, + 28990118, 29093244, 29195275, 29296206, 29396034, 29494756, 29592367, + 29688864, 29784243, 29878501, 29971634, 30063639, 30154511, 30244249, + 30332847, 30420304, 30506615, 30591778, 30675789, 30758645, 30840343, + 30920880, 31000253, 31078459, 31155494, 31231357, 31306043, 31379551, + 31451878, 31523021, 31592976, 31661743, 31729317, 31795696, 31860879, + 31924862, 31987643, 32049219, 32109589, 32168751, 32226701, 32283437, + 32338958, 32393262, 32446346, 32498209, 32548848, 32598261, 32646447, + 32693405, 32739131, 32783624, 32826884, 32868907, 32909693, 32949240, + 32987546, 33024611, 33060432, 33095008, 33128338, 33160421, 33191256, + 33220841, 33249175, 33276257, 33302087, 33326663, 33349984, 33372049, + 33392858, 33412410, 33430704, 33447739, 33463515, 33478031, 33491286, + 33503281, 33514014, 33523486, 33531695, 33538642, 33544326, 33548747, + 33551905, 33553800 +}; + +uint32_t stk6d2x_als_get_max_gain_ratio(uint8_t pd_mode, uint8_t data_type) +{ + uint32_t value = STK6D2X_ALS_DGAIN_MULTI1024 * STK6D2X_ALS_AGAIN_MULTI4; + if (data_type == STK6D2X_TYPE_ALS0 && (pd_mode == STK6D2X_ALS0_AGC_PDMODE0 || pd_mode == STK6D2X_ALS0_AGC_PDMODE1)) + { + value *= STK6D2X_ALS_PD_REDUCE_MULTI4; + } + + if (data_type == STK6D2X_TYPE_ALS1 && pd_mode == STK6D2X_ALS0_AGC_PDMODE0) + { + value *= STK6D2X_ALS_PD_REDUCE_MULTI2; + } + + if (data_type == STK6D2X_TYPE_ALS1 && pd_mode == STK6D2X_ALS0_AGC_PDMODE1) + { + value *= STK6D2X_ALS_PD_REDUCE_MULTI3; + } + + if (data_type == STK6D2X_TYPE_ALS2 && pd_mode == STK6D2X_ALS0_AGC_PDMODE0) + { + value *= STK6D2X_ALS_PD_REDUCE_MULTI2; + } + + if (data_type == STK6D2X_TYPE_ALS2 && pd_mode == STK6D2X_ALS0_AGC_PDMODE1) + { + value *= STK6D2X_ALS_PD_REDUCE_MULTI1; + } + + return value; +} + +static uint64_t SEC_sqrt(uint64_t x) +{ + register uint64_t result, tmp; + + result = 0; + tmp = (1LL << 62); // second-to-top bit set + while (tmp > x) { + tmp >>= 2; + } + while (tmp != 0) { + if (x >= (result + tmp)) { + x -= result + tmp; + result += 2 * tmp; // <-- faster than 2 * one + } + result >>= 1; + tmp >>= 2; + } + return result; +} + +void get_magnitude(int64_t* data_r, int64_t* data_i, int32_t* buffer, int size) +// data : 30 bits +{ //data must be twice as long as size + int i; + + for (i = 0; i < size; ++i) { + //sqrt(real^2 + imaginary^2) + uint64_t square = 0; + int64_t t_r = data_r[i]; + int64_t t_i = data_i[i]; + + square = ((t_r * t_r) >> 1) + ((t_i * t_i) >> 1); // 60bit + buffer[i] = (int32_t)SEC_sqrt(square); // 30bit + } +} + +// n is a power of 2 +int _log2n(int n) +{ + //int len = sizeof(int) * 8; + int len = 32, i; + for (i = 0; i < len; i++) + { + if ((n & 1) == 1) + return i; + else + n >>= 1; + } + return -1; +} + +// Utility function for reversing the bits +// of given index x +unsigned int sec_bitReverse(unsigned int x, int log2n) +{ + int n = 0, i; + for (i = 0; i < log2n; i++) + { + n <<= 1; + n |= (x & 1); + x >>= 1; + } + return n; +} + +void _fft(int64_t* a_r, int64_t* a_i, int64_t* A_r, int64_t* A_i, int n) +{ + int log2n = _log2n(n), s, k, j; + unsigned int i; + // bit reversal of the given array + for (i = 0; i < n; ++i) { + int rev = sec_bitReverse(i, log2n); + A_r[i] = a_r[rev]; // 28 (29 with sign) + A_i[i] = a_i[rev]; // 28 (29 with sign) + } + for (s = 1; s <= log2n; s++) { + int m = 1 << s; + int m_2 = m >> 1; + int64_t wm_r = (int64_t)cos[1024 >> s]; // wm_r = cos(2 pi / m) Q25 + int64_t wm_i = -(int64_t)sin[1024 >> s]; // wm_i = -sin(2 pi / m) Q25 + + for (k = 0; k < n; k += m) + { + int64_t w_r = 1LL << 25; // Q25 (33554432) + int64_t w_i = 0; // Q25 + + for (j = 0; j < m_2; j++) { + int i1 = k + j; + int i2 = i1 + m_2; + int64_t t_r = w_r * A_r[i2] - + w_i * A_i[i2]; // 28+25+0~10=53~63 (64 with sign) + int64_t t_i = w_r * A_i[i2] + + w_i * A_r[i2]; // 28+25+0~10=53~63 (64 with sign) + int64_t u_r = A_r[i1]; // 37~47 + int64_t u_i = A_i[i1]; // 37~47 + int64_t w2_r = w_r * wm_r - w_i * wm_i; // Q25*Q25, 26+25=51 + int64_t w2_i = w_r * wm_i + w_i * wm_r; // Q25*Q25 + t_r >>= 25; // Q25 + t_i >>= 25; // Q25 + A_r[i1] = u_r + t_r; // 28+0~10 = 28~38 + A_i[i1] = u_i + t_i; + A_r[i2] = u_r - t_r; + A_i[i2] = u_i - t_i; + w_r = w2_r >> 25; // Q25 + w_i = w2_i >> 25; // Q25 + } + } + } +} + +int64_t buf_r[SEC_FFT_SIZE] = { 0 }; +int64_t buf_i[SEC_FFT_SIZE] = { 0 }; +int64_t out_r[SEC_FFT_SIZE] = { 0 }; +int64_t out_i[SEC_FFT_SIZE] = { 0 }; + +void FFT(int32_t* data, enum fft_size size) +// int32_t* data : 16bit fifo data +// enum fft_size : fft data size +{ + static int log_cnt = 0; + int i, hamming_step = 2048 / size; + + if (size > SEC_FFT_SIZE || (size & 0x7f) != 0) { + //FFT size : multiply of 128 + //@TODO add return codes so we know it failed + return; + } + + for (i = 0; i < size; i++) { + // if (!alps_data->saturation && data[i] >= '''saturation value(int)''') { + // ALS_err("DEBUG_FLICKER saturation\n"); + // alps_data->saturation = true; + // } + + buf_r[i] = ((int64_t)data[i] * (int64_t)hamming[i * hamming_step]) >> 4; // 16+16-4=28 + + if (log_cnt > 30) // print debug log + ALS_info("DEBUG_FLICKER data[%d] => %d buf[%d] => %lld\n", i, + data[i], i, buf_r[i]); + } + _fft(buf_r, buf_i, out_r, out_i, size); // output 38 (39 sign) + + for (i = 0; i < SEC_FFT_SIZE; i++) { + out_r[i] >>= 8; + out_i[i] >>= 8; + } + get_magnitude(out_r, out_i, data, size); + + if (log_cnt > 30) + log_cnt = 0; + else + log_cnt++; +} + +void SEC_fft_entry(struct stk6d2x_data *alps_data) +{ + stk6d2x_wrapper *stk_wrapper = container_of(alps_data, stk6d2x_wrapper, alps_data); + int i = 0; + static uint32_t clear_buffer[SEC_FFT_SIZE] = { 0 }; + static uint32_t uv_buffer[SEC_FFT_SIZE] = { 0 }; + static uint32_t ir_buffer[SEC_FFT_SIZE] = { 0 }; + static uint32_t clear_gain_log[SEC_FFT_SIZE] = { 0 }; + static uint32_t uv_gain_log[SEC_FFT_SIZE] = { 0 }; + static uint32_t ir_gain_log[SEC_FFT_SIZE] = { 0 }; + static bool is_clear_sat[SEC_FFT_SIZE] = { false }; + static bool is_uv_sat[SEC_FFT_SIZE] = { false }; + static bool is_ir_sat[SEC_FFT_SIZE] = { false }; + static bool is_ext_clk[SEC_FFT_SIZE] = { false }; + static uint32_t buf[SEC_FFT_SIZE] = { 0 }; + static int log_cnt = 0; + uint64_t average_thd = 0; + uint64_t max_thd = 0; + long long int ratio_thd = 0; + uint32_t extclkCnt = 0; + + uint32_t min_clear_gain_log = stk6d2x_als_get_max_gain_ratio(alps_data->als_info.als_cur_pd_mode, STK6D2X_TYPE_ALS0); + uint32_t min_uv_gain_log = stk6d2x_als_get_max_gain_ratio(alps_data->als_info.als_cur_pd_mode, STK6D2X_TYPE_ALS1); + uint32_t min_ir_gain_log = stk6d2x_als_get_max_gain_ratio(alps_data->als_info.als_cur_pd_mode, STK6D2X_TYPE_ALS2); + + uint32_t clear_average_fifo = 0; + + uint32_t clear_average = 0; + uint32_t ir_average = 0; + uint32_t uv_average = 0; + int16_t clear_gain = 0; + int16_t ir_gain = 0; + int16_t uv_gain = 0; + + uint resolution = DIV_ROUND_CLOSEST(1000000000, SAMPLING_TIME) / SEC_FFT_SIZE; + uint min_freq = 70 / resolution; + uint max_freq = 1000 / resolution; + int imax = min_freq; + + uint16_t freq = 9999; + + uint64_t ratio_DC_max = 0; + uint64_t ratio_max_avg = 0; + + /* + ***** read fifo ***** + * ~~_buffer : raw fifo data(max 16bit!!!) + * ~~_gain_log : gain(log value) + * ex) gain 1 : 0 + * ex) gain 2 : 1 + * ex) gain 4 : 2 + * ... + * ex) gain 256 : 8 + * This is an example, you can use it at an appropriate value according to STK68210 + * + */ + for (i = 0; i < SEC_FFT_SIZE; i++) + { + clear_buffer[i] = alps_data->fifo_info.fft_buf[i]; + uv_buffer[i] = alps_data->fifo_info.fft_uv_buf[i]; + ir_buffer[i] = alps_data->fifo_info.fft_ir_buf[i]; + clear_gain_log[i] = alps_data->fifo_info.fft_gain_clear[i]; + uv_gain_log[i] = alps_data->fifo_info.fft_gain_uv[i]; + ir_gain_log[i] = alps_data->fifo_info.fft_gain_ir[i]; + is_clear_sat[i] = (alps_data->fifo_info.fft_xflag[i] & 0x1)? true : false; //CL staturation flag + is_uv_sat[i] = (alps_data->fifo_info.fft_xflag[i] & 0x2)? true : false; //UV staturation flag + is_ir_sat[i] = (alps_data->fifo_info.fft_xflag[i] & 0x4)? true : false; //IR staturation flag + is_ext_clk[i] = (alps_data->fifo_info.fft_xflag[i] & 0x80)? true : false; //External clock flag + if (is_ext_clk[i]) + extclkCnt++; + } + + ALS_info("DEBUG_FLICKER extclk count:%d, intclk count:%d", extclkCnt, SEC_FFT_SIZE - extclkCnt); + + // find minimum gain ratio + for (i = 0; i < SEC_FFT_SIZE; i++) + { + if (clear_gain_log[i] < min_clear_gain_log) + min_clear_gain_log = clear_gain_log[i]; + + if (uv_gain_log[i] < min_uv_gain_log) + min_uv_gain_log = uv_gain_log[i]; + + if (ir_gain_log[i] < min_ir_gain_log) + min_ir_gain_log = ir_gain_log[i]; + } + + // gain unification + for (i = 0; i < SEC_FFT_SIZE; i++) + { + clear_average_fifo += clear_buffer[i]; + clear_buffer[i] = clear_buffer[i] >> (clear_gain_log[i] - min_clear_gain_log); + ir_buffer[i] = ir_buffer[i] >> (ir_gain_log[i] - min_ir_gain_log ); + uv_buffer[i] = uv_buffer[i] >> (uv_gain_log[i] - min_uv_gain_log ); + buf[i] = clear_buffer[i]; // for FFT, max 16bit + + clear_average += clear_buffer[i]; // 16+11=27 + ir_average += ir_buffer[i]; + uv_average += uv_buffer[i]; + } + + // cal average + clear_average_fifo = clear_average_fifo / SEC_FFT_SIZE; + clear_average = clear_average / SEC_FFT_SIZE; + ir_average = ir_average / SEC_FFT_SIZE; + uv_average = uv_average / SEC_FFT_SIZE; + + clear_average = (clear_average << FLICKER_AVG_SHIFT) >> min_clear_gain_log; // 16+14=30 -... + ir_average = (ir_average << FLICKER_AVG_SHIFT) >> min_ir_gain_log; // 16+14=30 -... + uv_average = (uv_average << FLICKER_AVG_SHIFT) >> min_uv_gain_log; // 16+14=30 -... + + clear_gain = 1<<(min_clear_gain_log); + ir_gain = 1<<(min_clear_gain_log); + uv_gain = 1<<(min_clear_gain_log); + + // do fft + FFT(buf, SEC_FFT_SIZE); + + // get peak index + for (i = min_freq; i <= max_freq; ++i) { // 70~1000 + // ALS_err("ccb_sw_bin4096_flicker_GetResult: buf[%d]=%10u\n", i, buf[i]); + if (buf[i] > buf[imax]) { + imax = i; + } + average_thd += buf[i]; // 29+10=39 + } + average_thd /= (max_freq - min_freq + 1); // 29 + max_thd = buf[imax]; // 29 + ratio_thd = (uint32_t)((average_thd << 10) / max_thd); // 0+10-0, 0+10-29, 29+10-29 + + ratio_DC_max = buf[0] / max_thd; + ratio_max_avg = max_thd / average_thd; + + ALS_info("DEBUG_FLICKER DECISION_THD buf[%d]=%u, avg=%llu, max=%llu, DC=%u, DC/max=%llu, max/avg=%llu", imax, buf[imax], average_thd, max_thd, buf[0], ratio_DC_max, ratio_max_avg); + + // get valid threshold + // thd = calc_thd(clear_average_fifo, clear_average, buf); + ALS_info("DEBUG_FLICKER DC:%u\n", buf[0]); + //ALS_info("DEBUG_FLICKER buf[%d]=%u, thd %lld", imax, buf[imax], thd); + ALS_info("DEBUG_FLICKER buf[%d]=%u, avg/maxthd %lld\n", imax, buf[imax], ratio_thd); + + // valid peak check + if (ratio_thd < FLICKER_AVGMAX_THD) + { + freq = imax * resolution; + ALS_info("flicker_freq %d\n", freq); + } + else { + freq = 0; + ALS_info("flicker_freq is zero\n"); + } + /* + if (buf[imax] > thd) { + freq = imax * resolution; + ALS_info("flicker_freq %d", freq); + } + else { + freq = 0; + ALS_info("flicker_freq is zero"); + } */ + + if (log_cnt++ > 10) { + ALS_dbg("flicker_freq : %d", freq); + log_cnt = 0; + } else { + ALS_info("flicker_freq : %d", freq); + } + + alps_data->flicker = freq; + + input_report_rel(stk_wrapper->als_input_dev, REL_RZ, alps_data->flicker + 1); + input_sync(stk_wrapper->als_input_dev); + +#if IS_ENABLED(CONFIG_SENSORS_FLICKER_SELF_TEST) + als_eol_update_flicker(alps_data->flicker); +#endif + /* + return values (average, flicker... ) + */ +} +#endif + +void stk6d2x_fifo_init(struct stk6d2x_data * alps_data) +{ + int32_t err; + uint8_t flag_value; + err = STK6D2X_REG_READ(alps_data, STK6D2X_REG_FIFO1, &flag_value); + + if (err < 0) + { + ALS_err("Read flag failed\n"); + return; + } + + alps_data->fifo_info.data_type = (flag_value & STK6D2X_FIFO_SEL_MASK); + ALS_info("data_type = %d\n", alps_data->fifo_info.data_type); + + switch (alps_data->fifo_info.data_type) + { + case STK6D2X_FIFO_ALS0: + case STK6D2X_FIFO_ALS1: + case STK6D2X_FIFO_ALS2: + alps_data->fifo_info.frame_byte = 2; + break; + + case STK6D2X_FIFO_ALS0_ALS1: + case STK6D2X_FIFO_ALS0_ALS2: + case STK6D2X_FIFO_ALS1_ALS2: + alps_data->fifo_info.frame_byte = 4; + break; + + case STK6D2X_FIFO_ALS0_ALS1_ALS2: + alps_data->fifo_info.frame_byte = 6; + break; + + case STK6D2X_FIFO_STA0_ALS0: + case STK6D2X_FIFO_STA1_ALS1: + case STK6D2X_FIFO_STA2_ALS2: + alps_data->fifo_info.frame_byte = 4; + break; + + case STK6D2X_FIFO_STA01_ALS0_ALS1: + case STK6D2X_FIFO_STA02_ALS0_ALS2: + case STK6D2X_FIFO_STA12_ALS1_ALS2: + alps_data->fifo_info.frame_byte = 6; + break; + + case STK6D2X_FIFO_STA01_STA2_ALS0_ALS1_ALS2: + alps_data->fifo_info.frame_byte = 10; + break; + + default: + alps_data->fifo_info.frame_byte = 0xFF; + ALS_err("Frame Type ERROR!\n"); + break; + } + + alps_data->fifo_info.read_frame = STK_FIFO_I2C_READ_FRAME; + alps_data->fifo_info.target_frame_count = STK_FIFO_I2C_READ_FRAME_TARGET; + alps_data->fifo_info.read_max_data_byte = alps_data->fifo_info.frame_byte * STK_FIFO_I2C_READ_FRAME; + alps_data->fifo_info.fifo_reading = false; + alps_data->fifo_info.block_size = STK_FIFO_I2C_READ_FRAME_TARGET; + alps_data->fifo_info.fft_buf_idx = 0; + alps_data->fifo_info.ext_clk_chk = false; + alps_data->fifo_info.pre_ext_clk_chk = false; +} + +int32_t stk6d2x_enable_fifo(struct stk6d2x_data * alps_data, bool en) +{ + int32_t ret = 0; + uint8_t fifo_status, i2c_data_reg[2] = {0}; + uint16_t max_frame_count = STK_FIFO_DATA_BUFFER_LEN / alps_data->fifo_info.frame_byte; + + if (alps_data->fifo_info.fifo_enable == en) + { + ALS_err("FIFO already set\n"); + return ret; + } + + ret = STK6D2X_REG_READ(alps_data, STK6D2X_REG_FIFO1, &fifo_status); + + if (ret < 0) + { + return ret; + } + + if (en) + { + fifo_status = (STK6D2X_FIFO_MODE_NORMAL | fifo_status); + i2c_data_reg[0] = (max_frame_count >> 8) & 0x03; + i2c_data_reg[1] = max_frame_count & 0xFF; + alps_data->fifo_info.fft_buf_idx = 0; + alps_data->index_last = 0; + } + else + { + fifo_status = (fifo_status & (~STK6D2X_FIFO_MODE_NORMAL)); + } + + ret = STK6D2X_REG_WRITE_BLOCK(alps_data, + STK6D2X_REG_FIFO1_WM_LV, + i2c_data_reg, + sizeof(i2c_data_reg) / sizeof(i2c_data_reg[0])); + + if (ret < 0) + { + ALS_err("fail, ret=%d\n", ret); + return ret; + } + + ret = STK6D2X_REG_READ_MODIFY_WRITE(alps_data, + STK6D2X_REG_FIFO1, + fifo_status, + 0xFF); + + if (ret < 0) + { + ALS_err("fail, ret=%d\n", ret); + return ret; + } + + alps_data->fifo_info.fifo_enable = en; + return ret; +} + +void SEC_local_average(struct stk6d2x_data* alps_data) +{ + uint64_t clear_local_average = 0; + uint64_t uv_local_average = 0; + uint64_t ir_local_average = 0; + + bool is_clear_local_sat = false; + bool is_uv_local_sat = false; + bool is_ir_local_sat = false; + int i = 0; + int n = 0; + + uint32_t min_clear_local_gain_log = alps_data->fifo_info.fft_gain_clear[alps_data->index_last]; + uint32_t min_uv_local_gain_log = alps_data->fifo_info.fft_gain_uv[alps_data->index_last]; + uint32_t min_ir_local_gain_log = alps_data->fifo_info.fft_gain_ir[alps_data->index_last]; + + int local_length = 0; + int index_last_temp = 0; + + if (alps_data->is_first) + { + n = (int)(alps_data->fifo_info.fft_buf_idx) - 1; + if (n > -1) + { + clear_local_average += (alps_data->fifo_info.fft_buf[n]) >> ((alps_data->fifo_info.fft_gain_clear[n]) - (alps_data->fifo_info.fft_gain_clear[n])); + uv_local_average += (alps_data->fifo_info.fft_uv_buf[n]) >> ((alps_data->fifo_info.fft_gain_uv[n]) - (alps_data->fifo_info.fft_gain_uv[n])); + ir_local_average += (alps_data->fifo_info.fft_ir_buf[n]) >> ((alps_data->fifo_info.fft_gain_ir[n]) - (alps_data->fifo_info.fft_gain_ir[n])); + if (alps_data->fifo_info.fft_xflag[n] & 0x1) + is_clear_local_sat = true; + if (alps_data->fifo_info.fft_xflag[n] & 0x2) + is_uv_local_sat = true; + if (alps_data->fifo_info.fft_xflag[n] & 0x4) + is_ir_local_sat = true; + alps_data->is_first = false; + } + else + { + return; // no data + } + } + + + if ((alps_data->fifo_info.fft_buf_idx < alps_data->index_last + SEC_LOCAL_AVG_SIZE)) + { + if (alps_data->fifo_info.fft_buf_idx == 0 && alps_data->index_last > 0) + { + + } + else + { + return; // not yet + } + } + + local_length = SEC_LOCAL_AVG_SIZE; + index_last_temp = alps_data->index_last + SEC_LOCAL_AVG_SIZE; + if (index_last_temp > SEC_FFT_SIZE) + { + index_last_temp = SEC_FFT_SIZE; + local_length = index_last_temp - alps_data->index_last; + } + + // find minimum gain ratio, check saturation + for (i = alps_data->index_last; i < index_last_temp; i++) + { + if (alps_data->fifo_info.fft_gain_clear[i] < min_clear_local_gain_log) + min_clear_local_gain_log = alps_data->fifo_info.fft_gain_clear[i]; + if (alps_data->fifo_info.fft_gain_uv[i] < min_uv_local_gain_log) + min_uv_local_gain_log = alps_data->fifo_info.fft_gain_uv[i]; + if (alps_data->fifo_info.fft_gain_ir[i] < min_ir_local_gain_log) + min_ir_local_gain_log = alps_data->fifo_info.fft_gain_ir[i]; + if (alps_data->fifo_info.fft_xflag[i] & 0x1) + is_clear_local_sat = true; + if (alps_data->fifo_info.fft_xflag[i] & 0x2) + is_uv_local_sat = true; + if (alps_data->fifo_info.fft_xflag[i] & 0x4) + is_ir_local_sat = true; + } + + // gain unification + for (i = alps_data->index_last; i < index_last_temp; i++) + { + clear_local_average += (alps_data->fifo_info.fft_buf[i]) >> ((alps_data->fifo_info.fft_gain_clear[i]) - min_clear_local_gain_log); + uv_local_average += (alps_data->fifo_info.fft_uv_buf[i]) >> ((alps_data->fifo_info.fft_gain_uv[i]) - min_uv_local_gain_log); + ir_local_average += (alps_data->fifo_info.fft_ir_buf[i]) >> ((alps_data->fifo_info.fft_gain_ir[i]) - min_ir_local_gain_log); + } + + // cal average + + clear_local_average /= local_length; // 16 + uv_local_average /= local_length; + ir_local_average /= local_length; + + + clear_local_average = (clear_local_average << FLICKER_AVG_SHIFT) >> min_clear_local_gain_log; // 16+14=30 -... + uv_local_average = (uv_local_average << FLICKER_AVG_SHIFT) >> min_uv_local_gain_log; // 16+14=30 -... + ir_local_average = (ir_local_average << FLICKER_AVG_SHIFT) >> min_ir_local_gain_log; // 16+14=30 -... + + alps_data->clear_gain = 1<<(min_clear_local_gain_log); + alps_data->ir_gain = 1<<(min_ir_local_gain_log); + alps_data->uv_gain = 1<<(min_uv_local_gain_log); + + alps_data->clear_local_average = clear_local_average; + alps_data->uv_local_average = uv_local_average; + alps_data->ir_local_average = ir_local_average; + + alps_data->is_clear_local_sat = is_clear_local_sat; + alps_data->is_uv_local_sat = is_uv_local_sat; + alps_data->is_ir_local_sat = is_ir_local_sat; + ALS_info("clear_local_average:%llu is_clear_local_sat:%d | uv_local_average:%llu is_uv_local_sat:%d | ir_local_average:%llu is_ir_local_sat:%d\n", + clear_local_average, is_clear_local_sat, uv_local_average, is_uv_local_sat, ir_local_average, is_ir_local_sat); + + alps_data->index_last = index_last_temp; + if (alps_data->index_last >= SEC_FFT_SIZE) + alps_data->index_last = 0; + + alps_data->is_local_avg_update = true; +} + +void stk6d2x_fifo_get_data(struct stk6d2x_data * alps_data, uint16_t frame_num) +{ + uint16_t offset; + uint8_t raw_data[STK_FIFO_I2C_READ_FRAME_BUF_SIZE]; // alps_data->fifo_info.read_max_data_byte * sizeof(uint8_t) + int16_t i, frame_count = 0, read_frame_num = 0; + int32_t ret, read_bytes; +#ifdef STK_FIFO_DATA_SUMMATION + uint64_t als0_sum = 0, als1_sum = 0, als2_sum = 0; +#endif + +#ifdef STK_CHK_XFLG + alps_data->xflag = 0; + if (alps_data->rid != 1) + { + ret = STK6D2X_REG_READ(alps_data, STK6D2X_REG_XFLAG, &alps_data->xflag); + + if (ret < 0) + { + return; + } + } +#endif + + memset(alps_data->fifo_info.fifo_data0, 0, STK_FIFO_I2C_READ_FRAME_TARGET * sizeof(uint32_t)); + memset(alps_data->fifo_info.fifo_data1, 0, STK_FIFO_I2C_READ_FRAME_TARGET * sizeof(uint32_t)); + memset(alps_data->fifo_info.fifo_data2, 0, STK_FIFO_I2C_READ_FRAME_TARGET * sizeof(uint32_t)); + memset(alps_data->fifo_info.fifo_xflag, 0, STK_FIFO_I2C_READ_FRAME_TARGET * sizeof(uint8_t)); + +#ifdef STK_FIFO_DATA_SUMMATION + if (alps_data->fifo_info.fifo_sum_cnt >= STK6D2X_ALS_SUMMATION_CNT) + { + alps_data->fifo_info.fifo_sum_cnt = 0; + } +#endif + + for (frame_count = 0 ; frame_count < frame_num ; frame_count += (alps_data->fifo_info.read_frame)) + { + read_frame_num = (int16_t)(frame_num - frame_count); + + if (read_frame_num >= alps_data->fifo_info.read_frame) + { + read_bytes = alps_data->fifo_info.read_max_data_byte; + read_frame_num = alps_data->fifo_info.read_frame; + } + else + { + read_bytes = alps_data->fifo_info.frame_byte * read_frame_num; + } + + memset(raw_data, 0, sizeof(raw_data)); + ret = STK6D2X_REG_BLOCK_READ(alps_data, STK6D2X_REG_FIFO_OUT, read_bytes, raw_data); + + if (ret < 0) + { + ALS_err("fail, err=0x%x\n", ret); + return; + } + + + switch (alps_data->fifo_info.data_type) + { + case STK6D2X_FIFO_ALS0: + case STK6D2X_FIFO_ALS1: + case STK6D2X_FIFO_ALS2: + for (i = 0, offset = 0; i < read_frame_num; i++, offset += alps_data->fifo_info.frame_byte) + { + *(alps_data->fifo_info.fifo_data0 + frame_count + i) = (*(raw_data + offset) << 8 | *(raw_data + offset + 1)); + } + + break; + + case STK6D2X_FIFO_ALS0_ALS1: + case STK6D2X_FIFO_ALS0_ALS2: + case STK6D2X_FIFO_ALS1_ALS2: + for (i = 0, offset = 0; i < read_frame_num; i++, offset += alps_data->fifo_info.frame_byte) + { + *(alps_data->fifo_info.fifo_data0 + frame_count + i) = (*(raw_data + offset) << 8 | *(raw_data + offset + 1)); + *(alps_data->fifo_info.fifo_data1 + frame_count + i) = (*(raw_data + offset + 2) << 8 | *(raw_data + offset + 3)); + } + break; + + case STK6D2X_FIFO_ALS0_ALS1_ALS2: + for (i = 0, offset = 0; i < read_frame_num; i++, offset += alps_data->fifo_info.frame_byte) + { + *(alps_data->fifo_info.fifo_data0 + frame_count + i) = (*(raw_data + offset) << 8 | *(raw_data + offset + 1)); + *(alps_data->fifo_info.fifo_data1 + frame_count + i) = (*(raw_data + offset + 2) << 8 | *(raw_data + offset + 3)); + *(alps_data->fifo_info.fifo_data2 + frame_count + i) = (*(raw_data + offset + 4) << 8 | *(raw_data + offset + 5)); + } + break; + +#ifdef STK_ALS_AGC + case STK6D2X_FIFO_STA0_ALS0: + for (i = 0, offset = 0; i < read_frame_num; i++, offset += alps_data->fifo_info.frame_byte) + { + if (alps_data->rid == 1) + { +#ifdef STK_CHK_CLK_SRC + if ((*(raw_data + offset + 1) & 0x80) == 0) + { + alps_data->fifo_info.ext_clk_chk = false; + } + else + { + alps_data->fifo_info.ext_clk_chk = true; + } +#endif +#ifdef STK_CHK_XFLG + if ((*(raw_data + offset) & 0x80)) + { + alps_data->xflag |= 0x1; + } +#endif + } + else + { +#ifdef STK_CHK_CLK_SRC + if ((*(raw_data + offset) & 0x80) == 0) + { + alps_data->fifo_info.ext_clk_chk = false; + } + else + { + alps_data->fifo_info.ext_clk_chk = true; + } +#endif + } + alps_data->als_info.als_cur_dgain[0] = stk6d2x_cal_curDGain(*(raw_data + offset) & 0x7); + alps_data->als_info.als_cur_again[0] = stk6d2x_als_get_again_multiple((*(raw_data + offset) & 0x18) >> 3); + alps_data->als_info.als_cur_pd_reduce[0] = stk6d2x_als_get_pd_multiple((*(raw_data + offset) & 0x60) >> 5, \ + alps_data->als_info.als_cur_pd_mode, STK6D2X_TYPE_ALS0); + + *(alps_data->fifo_info.fifo_data0 + frame_count + i) = (*(raw_data + offset + 2) << 8 | *(raw_data + offset + 3)); + stk6d2x_get_als_ratio(alps_data); + *(alps_data->fifo_info.fifo_data0 + frame_count + i) *= alps_data->als_info.als_cur_ratio[0]; +#ifdef STK_FIFO_DATA_SUMMATION + if (alps_data->fifo_info.fifo_sum_cnt < STK6D2X_ALS_SUMMATION_CNT) + { + als0_sum += *(alps_data->fifo_info.fifo_data0 + frame_count + i); + alps_data->fifo_info.fifo_sum_cnt ++; + } +#endif + } + break; + + case STK6D2X_FIFO_STA1_ALS1: + for (i = 0, offset = 0; i < read_frame_num; i++, offset += alps_data->fifo_info.frame_byte) + { + if (alps_data->rid == 1) + { +#ifdef STK_CHK_CLK_SRC + if ((*(raw_data + offset + 1) & 0x80) == 0) + { + alps_data->fifo_info.ext_clk_chk = false; + } + else + { + alps_data->fifo_info.ext_clk_chk = true; + } +#endif +#ifdef STK_CHK_XFLG + if ((*(raw_data + offset) & 0x80)) + { + alps_data->xflag |= 0x1; + } +#endif + } + else + { +#ifdef STK_CHK_CLK_SRC + if ((*(raw_data + offset) & 0x80) == 0) + { + alps_data->fifo_info.ext_clk_chk = false; + } + else + { + alps_data->fifo_info.ext_clk_chk = true; + } +#endif + } + alps_data->als_info.als_cur_dgain[1] = stk6d2x_cal_curDGain(*(raw_data + offset) & 0x7); + alps_data->als_info.als_cur_again[1] = stk6d2x_als_get_again_multiple((*(raw_data + offset) & 0x18) >> 3); + alps_data->als_info.als_cur_pd_reduce[1] = stk6d2x_als_get_pd_multiple((*(raw_data + offset) & 0x60) >> 5, \ + alps_data->als_info.als_cur_pd_mode, STK6D2X_TYPE_ALS1); + *(alps_data->fifo_info.fifo_data0 + frame_count + i) = (*(raw_data + offset + 2) << 8 | *(raw_data + offset + 3)); + stk6d2x_get_als_ratio(alps_data); + *(alps_data->fifo_info.fifo_data0 + frame_count + i) *= alps_data->als_info.als_cur_ratio[1]; +#ifdef STK_FIFO_DATA_SUMMATION + if (alps_data->fifo_info.fifo_sum_cnt < STK6D2X_ALS_SUMMATION_CNT) + { + als1_sum += *(alps_data->fifo_info.fifo_data0 + frame_count + i); + alps_data->fifo_info.fifo_sum_cnt ++; + } +#endif + } + break; + case STK6D2X_FIFO_STA2_ALS2: + for (i = 0, offset = 0; i < read_frame_num; i++, offset += alps_data->fifo_info.frame_byte) + { + if (alps_data->rid == 1) + { +#ifdef STK_CHK_CLK_SRC + if ((*(raw_data + offset + 1) & 0x80) == 0) + { + alps_data->fifo_info.ext_clk_chk = false; + } + else + { + alps_data->fifo_info.ext_clk_chk = true; + } +#endif +#ifdef STK_CHK_XFLG + if ((*(raw_data + offset) & 0x80)) + { + alps_data->xflag |= 0x1; + } +#endif + } + else + { +#ifdef STK_CHK_CLK_SRC + if ((*(raw_data + offset) & 0x80) == 0) + { + alps_data->fifo_info.ext_clk_chk = false; + } + else + { + alps_data->fifo_info.ext_clk_chk = true; + } +#endif + } + alps_data->als_info.als_cur_dgain[2] = stk6d2x_cal_curDGain(*(raw_data + offset) & 0x7); + alps_data->als_info.als_cur_again[2] = stk6d2x_als_get_again_multiple((*(raw_data + offset) & 0x18) >> 3); + alps_data->als_info.als_cur_pd_reduce[2] = stk6d2x_als_get_pd_multiple((*(raw_data + offset) & 0x60) >> 5, \ + alps_data->als_info.als_cur_pd_mode, STK6D2X_TYPE_ALS2); + *(alps_data->fifo_info.fifo_data0 + frame_count + i) = (*(raw_data + offset + 2) << 8 | *(raw_data + offset + 3)); + stk6d2x_get_als_ratio(alps_data); + *(alps_data->fifo_info.fifo_data0 + frame_count + i) *= alps_data->als_info.als_cur_ratio[2]; +#ifdef STK_FIFO_DATA_SUMMATION + if (alps_data->fifo_info.fifo_sum_cnt < STK6D2X_ALS_SUMMATION_CNT) + { + als2_sum += *(alps_data->fifo_info.fifo_data0 + frame_count + i); + alps_data->fifo_info.fifo_sum_cnt ++; + } +#endif + } + break; + case STK6D2X_FIFO_STA01_ALS0_ALS1: + for (i = 0, offset = 0; i < read_frame_num; i++, offset += alps_data->fifo_info.frame_byte) + { +#ifdef STK_CHK_CLK_SRC + if ((*(raw_data + offset) & 0x80) == 0) + { + alps_data->fifo_info.ext_clk_chk = false; + } + else + { + alps_data->fifo_info.ext_clk_chk = true; + } +#endif + alps_data->als_info.als_cur_dgain[0] = stk6d2x_cal_curDGain(*(raw_data + offset) & 0x7); + alps_data->als_info.als_cur_again[0] = stk6d2x_als_get_again_multiple((*(raw_data + offset) & 0x18) >> 3); + alps_data->als_info.als_cur_pd_reduce[0] = stk6d2x_als_get_pd_multiple((*(raw_data + offset) & 0x60) >> 5, \ + alps_data->als_info.als_cur_pd_mode, STK6D2X_TYPE_ALS0); + alps_data->als_info.als_cur_dgain[1] = stk6d2x_cal_curDGain(*(raw_data + offset + 1) & 0x7); + alps_data->als_info.als_cur_again[1] = stk6d2x_als_get_again_multiple((*(raw_data + offset + 1) & 0x18) >> 3); + alps_data->als_info.als_cur_pd_reduce[1] = stk6d2x_als_get_pd_multiple((*(raw_data + offset + 1) & 0x60) >> 5, \ + alps_data->als_info.als_cur_pd_mode, STK6D2X_TYPE_ALS1); + *(alps_data->fifo_info.fifo_data0 + frame_count + i) = (*(raw_data + offset + 2) << 8 | *(raw_data + offset + 3)); + *(alps_data->fifo_info.fifo_data1 + frame_count + i) = (*(raw_data + offset + 4) << 8 | *(raw_data + offset + 5)); + stk6d2x_get_als_ratio(alps_data); + *(alps_data->fifo_info.fifo_data0 + frame_count + i) *= alps_data->als_info.als_cur_ratio[0]; + *(alps_data->fifo_info.fifo_data1 + frame_count + i) *= alps_data->als_info.als_cur_ratio[1]; +#ifdef STK_FIFO_DATA_SUMMATION + if (alps_data->fifo_info.fifo_sum_cnt < STK6D2X_ALS_SUMMATION_CNT) + { + als0_sum += *(alps_data->fifo_info.fifo_data0 + frame_count + i); + als1_sum += *(alps_data->fifo_info.fifo_data1 + frame_count + i); + alps_data->fifo_info.fifo_sum_cnt ++; + } +#endif + } + break; + case STK6D2X_FIFO_STA02_ALS0_ALS2: + for (i = 0, offset = 0; i < read_frame_num; i++, offset += alps_data->fifo_info.frame_byte) + { +#ifdef STK_CHK_CLK_SRC + if ((*(raw_data + offset) & 0x80) == 0) + { + alps_data->fifo_info.ext_clk_chk = false; + } + else + { + alps_data->fifo_info.ext_clk_chk = true; + } +#endif + alps_data->als_info.als_cur_dgain[0] = stk6d2x_cal_curDGain(*(raw_data + offset) & 0x7); + alps_data->als_info.als_cur_again[0] = stk6d2x_als_get_again_multiple((*(raw_data + offset) & 0x18) >> 3); + alps_data->als_info.als_cur_pd_reduce[0] = stk6d2x_als_get_pd_multiple((*(raw_data + offset) & 0x60) >> 5, \ + alps_data->als_info.als_cur_pd_mode, STK6D2X_TYPE_ALS0); + alps_data->als_info.als_cur_dgain[2] = stk6d2x_cal_curDGain(*(raw_data + offset + 1) & 0x7); + alps_data->als_info.als_cur_again[2] = stk6d2x_als_get_again_multiple((*(raw_data + offset + 1) & 0x18) >> 3); + alps_data->als_info.als_cur_pd_reduce[2] = stk6d2x_als_get_pd_multiple((*(raw_data + offset + 1) & 0x60) >> 5, \ + alps_data->als_info.als_cur_pd_mode, STK6D2X_TYPE_ALS2); + *(alps_data->fifo_info.fifo_data0 + frame_count + i) = (*(raw_data + offset + 2) << 8 | *(raw_data + offset + 3)); + *(alps_data->fifo_info.fifo_data1 + frame_count + i) = (*(raw_data + offset + 4) << 8 | *(raw_data + offset + 5)); + stk6d2x_get_als_ratio(alps_data); + *(alps_data->fifo_info.fifo_data0 + frame_count + i) *= alps_data->als_info.als_cur_ratio[0]; + *(alps_data->fifo_info.fifo_data1 + frame_count + i) *= alps_data->als_info.als_cur_ratio[2]; +#ifdef STK_FIFO_DATA_SUMMATION + if (alps_data->fifo_info.fifo_sum_cnt < STK6D2X_ALS_SUMMATION_CNT) + { + als0_sum += *(alps_data->fifo_info.fifo_data0 + frame_count + i); + als2_sum += *(alps_data->fifo_info.fifo_data1 + frame_count + i); + alps_data->fifo_info.fifo_sum_cnt ++; + } +#endif + } + break; + case STK6D2X_FIFO_STA12_ALS1_ALS2: + for (i = 0, offset = 0; i < read_frame_num; i++, offset += alps_data->fifo_info.frame_byte) + { +#ifdef STK_CHK_CLK_SRC + if ((*(raw_data + offset) & 0x80) == 0) + { + alps_data->fifo_info.ext_clk_chk = false; + } + else + { + alps_data->fifo_info.ext_clk_chk = true; + } +#endif + alps_data->als_info.als_cur_dgain[1] = stk6d2x_cal_curDGain(*(raw_data + offset) & 0x7); + alps_data->als_info.als_cur_again[1] = stk6d2x_als_get_again_multiple((*(raw_data + offset) & 0x18) >> 3); + alps_data->als_info.als_cur_pd_reduce[1] = stk6d2x_als_get_pd_multiple((*(raw_data + offset) & 0x60) >> 5, \ + alps_data->als_info.als_cur_pd_mode, STK6D2X_TYPE_ALS1); + alps_data->als_info.als_cur_dgain[2] = stk6d2x_cal_curDGain(*(raw_data + offset + 1) & 0x7); + alps_data->als_info.als_cur_again[2] = stk6d2x_als_get_again_multiple((*(raw_data + offset + 1) & 0x18) >> 3); + alps_data->als_info.als_cur_pd_reduce[2] = stk6d2x_als_get_pd_multiple((*(raw_data + offset + 1) & 0x60) >> 5, \ + alps_data->als_info.als_cur_pd_mode, STK6D2X_TYPE_ALS2); + *(alps_data->fifo_info.fifo_data0 + frame_count + i) = (*(raw_data + offset + 2) << 8 | *(raw_data + offset + 3)); + *(alps_data->fifo_info.fifo_data1 + frame_count + i) = (*(raw_data + offset + 4) << 8 | *(raw_data + offset + 5)); + stk6d2x_get_als_ratio(alps_data); + *(alps_data->fifo_info.fifo_data0 + frame_count + i) *= alps_data->als_info.als_cur_ratio[1]; + *(alps_data->fifo_info.fifo_data1 + frame_count + i) *= alps_data->als_info.als_cur_ratio[2]; +#ifdef STK_FIFO_DATA_SUMMATION + if (alps_data->fifo_info.fifo_sum_cnt < STK6D2X_ALS_SUMMATION_CNT) + { + als1_sum += *(alps_data->fifo_info.fifo_data0 + frame_count + i); + als2_sum += *(alps_data->fifo_info.fifo_data1 + frame_count + i); + alps_data->fifo_info.fifo_sum_cnt ++; + } +#endif + } + break; + + case STK6D2X_FIFO_STA01_STA2_ALS0_ALS1_ALS2: + for (i = 0, offset = 0; i < read_frame_num; i++, offset += alps_data->fifo_info.frame_byte) + { + if (alps_data->rid == 1) + { +#ifdef STK_CHK_CLK_SRC + if ((*(raw_data + offset + 3) & 0x80) == 0) + { + alps_data->fifo_info.ext_clk_chk = false; + } + else + { + alps_data->fifo_info.ext_clk_chk = true; +#ifdef SEC_FFT_FLICKER_1024 + *(alps_data->fifo_info.fifo_xflag + frame_count + i) |= (1 << 7); //bit7 is cfalg +#endif + } +#endif +#ifdef STK_CHK_XFLG + if ((*(raw_data + offset) & 0x80)) + { + alps_data->xflag |= 0x1; +#ifdef SEC_FFT_FLICKER_1024 + *(alps_data->fifo_info.fifo_xflag + frame_count + i) |= 0x1; //bit0 is xfalg for C +#endif + } + if ((*(raw_data + offset + 1) & 0x80)) + { + alps_data->xflag |= 0x2; +#ifdef SEC_FFT_FLICKER_1024 + *(alps_data->fifo_info.fifo_xflag + frame_count + i) |= 0x2; //bit1 is xfalg for UV +#endif + } + if ((*(raw_data + offset + 2) & 0x80)) + { + alps_data->xflag |= 0x4; +#ifdef SEC_FFT_FLICKER_1024 + *(alps_data->fifo_info.fifo_xflag + frame_count + i) |= 0x4; //bit2 is xfalg for IR +#endif + } +#endif + } + else + { +#ifdef STK_CHK_CLK_SRC + if ((*(raw_data + offset) & 0x80) == 0) + { + alps_data->fifo_info.ext_clk_chk = false; + } + else + { + alps_data->fifo_info.ext_clk_chk = true; +#ifdef SEC_FFT_FLICKER_1024 + *(alps_data->fifo_info.fifo_xflag + frame_count + i) |= (1 << 7); //bit7 is cfalg +#endif + + } +#endif + } + alps_data->als_info.als_cur_dgain[0] = stk6d2x_cal_curDGain(*(raw_data + offset) & 0x7); + alps_data->als_info.als_cur_again[0] = stk6d2x_als_get_again_multiple((*(raw_data + offset) & 0x18) >> 3); + alps_data->als_info.als_cur_pd_reduce[0] = stk6d2x_als_get_pd_multiple((*(raw_data + offset) & 0x60) >> 5, \ + alps_data->als_info.als_cur_pd_mode, STK6D2X_TYPE_ALS0); + alps_data->als_info.als_cur_dgain[1] = stk6d2x_cal_curDGain(*(raw_data + offset + 1) & 0x7); + alps_data->als_info.als_cur_again[1] = stk6d2x_als_get_again_multiple((*(raw_data + offset + 1) & 0x18) >> 3); + alps_data->als_info.als_cur_pd_reduce[1] = stk6d2x_als_get_pd_multiple((*(raw_data + offset + 1) & 0x60) >> 5, \ + alps_data->als_info.als_cur_pd_mode, STK6D2X_TYPE_ALS1); + alps_data->als_info.als_cur_dgain[2] = stk6d2x_cal_curDGain(*(raw_data + offset + 2) & 0x7); + alps_data->als_info.als_cur_again[2] = stk6d2x_als_get_again_multiple((*(raw_data + offset + 2) & 0x18) >> 3); + alps_data->als_info.als_cur_pd_reduce[2] = stk6d2x_als_get_pd_multiple((*(raw_data + offset + 2) & 0x60) >> 5, \ + alps_data->als_info.als_cur_pd_mode, STK6D2X_TYPE_ALS2); + + *(alps_data->fifo_info.fifo_data0 + frame_count + i) = (*(raw_data + offset + 4) << 8 | *(raw_data + offset + 5)); + *(alps_data->fifo_info.fifo_data1 + frame_count + i) = (*(raw_data + offset + 6) << 8 | *(raw_data + offset + 7)); + *(alps_data->fifo_info.fifo_data2 + frame_count + i) = (*(raw_data + offset + 8) << 8 | *(raw_data + offset + 9)); + stk6d2x_get_als_ratio(alps_data); +#ifdef SEC_FFT_FLICKER_1024 + *(alps_data->fifo_info.fifo_data_clear + frame_count + i) = *(alps_data->fifo_info.fifo_data0 + frame_count + i); + *(alps_data->fifo_info.fifo_data_uv + frame_count + i) = *(alps_data->fifo_info.fifo_data1 + frame_count + i); + *(alps_data->fifo_info.fifo_data_ir + frame_count + i) = *(alps_data->fifo_info.fifo_data2 + frame_count + i); + + *(alps_data->fifo_info.fifo_gain_clear + frame_count + i) = stk6d2x_sec_dgain(*(raw_data + offset) & 0x7) + \ + stk6d2x_sec_again((*(raw_data + offset) & 0x18) >> 3) + \ + stk6d2x_sec_pd_multiple((*(raw_data + offset) & 0x60) >> 5, \ + alps_data->als_info.als_cur_pd_mode, STK6D2X_TYPE_ALS0); + *(alps_data->fifo_info.fifo_gain_uv + frame_count + i) = stk6d2x_sec_dgain(*(raw_data + offset + 1) & 0x7) + \ + stk6d2x_sec_again((*(raw_data + offset + 1) & 0x18) >> 3) + \ + stk6d2x_sec_pd_multiple((*(raw_data + offset + 1) & 0x60) >> 5, \ + alps_data->als_info.als_cur_pd_mode, STK6D2X_TYPE_ALS1); + *(alps_data->fifo_info.fifo_gain_ir + frame_count + i) = stk6d2x_sec_dgain(*(raw_data + offset + 2) & 0x7) + \ + stk6d2x_sec_again((*(raw_data + offset + 2) & 0x18) >> 3) + \ + stk6d2x_sec_pd_multiple((*(raw_data + offset + 2) & 0x60) >> 5, \ + alps_data->als_info.als_cur_pd_mode, STK6D2X_TYPE_ALS2); +#endif + *(alps_data->fifo_info.fifo_data0 + frame_count + i) *= alps_data->als_info.als_cur_ratio[0]; + *(alps_data->fifo_info.fifo_data1 + frame_count + i) *= alps_data->als_info.als_cur_ratio[1]; + *(alps_data->fifo_info.fifo_data2 + frame_count + i) *= alps_data->als_info.als_cur_ratio[2]; +#ifdef STK_FIFO_DATA_SUMMATION + if (alps_data->fifo_info.fifo_sum_cnt < STK6D2X_ALS_SUMMATION_CNT) + { + als0_sum += *(alps_data->fifo_info.fifo_data0 + frame_count + i); + als1_sum += *(alps_data->fifo_info.fifo_data1 + frame_count + i); + als2_sum += *(alps_data->fifo_info.fifo_data2 + frame_count + i); + alps_data->fifo_info.fifo_sum_cnt ++; + } +#endif + } + break; +#endif + default: + ALS_err("unavailable!\n"); + break; + } + } +#ifdef STK_CHK_CLK_SRC + if (!alps_data->fifo_info.ext_clk_chk) + { + /* + alps_data->fifo_info.last_frame_count = 0; + ALS_err("internal clock mode\n"); + alps_data->is_long_it = true; + memset(alps_data->fifo_info.fft_buf, 0, FFT_BUF_SIZE); + alps_data->fifo_info.fft_buf_idx = 0; + return; + */ + ALS_info("internal clock mode"); + } +#endif + +#ifdef STK_CHK_XFLG + if ((alps_data->xflag & 0x1)) + ALS_err("XFLAG ALS0 Occur\n"); + if ((alps_data->xflag & 0x2)) + ALS_err("XFLAG ALS1 Occur\n"); + if ((alps_data->xflag & 0x4)) + ALS_err("XFLAG ALS2 Occur\n"); +#endif + if (alps_data->fifo_info.pre_ext_clk_chk != alps_data->fifo_info.ext_clk_chk) + { + ALS_err("clock change skip fifo data"); + alps_data->fifo_info.pre_ext_clk_chk = alps_data->fifo_info.ext_clk_chk; + alps_data->fifo_info.fft_buf_idx = 0; + return; + } + +#ifdef STK_FIFO_DATA_SUMMATION +#ifdef STK_CHK_XFLG + //if (!(alps_data->xflag & 0x1)) +#endif + { + alps_data->fifo_info.fifo_sum_als0 = als0_sum; + } +#ifdef STK_CHK_XFLG + //if (!(alps_data->xflag & 0x2)) +#endif + { + alps_data->fifo_info.fifo_sum_als1 = als1_sum; + } +#ifdef STK_CHK_XFLG + //if (!(alps_data->xflag & 0x4)) +#endif + { + alps_data->fifo_info.fifo_sum_als2 = als2_sum; + } +#endif + alps_data->fifo_info.last_frame_count = frame_num; + +#ifdef STK_CHK_XFLG + if (/*!(alps_data->xflag & 0x1) && */frame_num < STK_FIFO_I2C_READ_FRAME_TARGET) +#endif + { +#ifdef STK_FFT_FLICKER + for (i = 0; i < frame_num; i ++) + { + alps_data->fifo_info.fft_buf[alps_data->fifo_info.fft_buf_idx] = alps_data->fifo_info.fifo_data_clear[i]; + alps_data->fifo_info.fft_uv_buf[alps_data->fifo_info.fft_buf_idx] = alps_data->fifo_info.fifo_data_uv[i]; + alps_data->fifo_info.fft_ir_buf[alps_data->fifo_info.fft_buf_idx] = alps_data->fifo_info.fifo_data_ir[i]; + alps_data->fifo_info.fft_gain_clear[alps_data->fifo_info.fft_buf_idx] = alps_data->fifo_info.fifo_gain_clear[i]; + alps_data->fifo_info.fft_gain_uv[alps_data->fifo_info.fft_buf_idx] = alps_data->fifo_info.fifo_gain_uv[i]; + alps_data->fifo_info.fft_gain_ir[alps_data->fifo_info.fft_buf_idx] = alps_data->fifo_info.fifo_gain_ir[i]; + alps_data->fifo_info.fft_xflag[alps_data->fifo_info.fft_buf_idx] = alps_data->fifo_info.fifo_xflag[i]; + alps_data->fifo_info.fft_buf_idx ++; + if (alps_data->fifo_info.fft_buf_idx >= FFT_BUF_SIZE) + { + break; + } + } + + if (alps_data->fifo_info.fft_buf_idx >= FFT_BUF_SIZE) + { + alps_data->fifo_info.fft_buf_idx = 0; + +#ifdef SEC_FFT_FLICKER_1024 + SEC_fft_entry(alps_data); +#endif + } +#endif + } +#ifdef STK_CHK_XFLG + else + { + alps_data->fifo_info.fft_buf_idx = 0; + } +#endif +} + +void stk6d2x_get_fifo_data_polling(struct stk6d2x_data *alps_data) +{ + int32_t ret; + uint8_t raw_data[2] = {0}; + uint16_t frame_num; + bool is_fifo_warning = false; + + if (alps_data->fifo_info.fifo_enable == false) + { + ALS_err("FIFO is disable\n"); + return; + } + + alps_data->fifo_info.fifo_reading = true; + alps_data->is_local_avg_update = false; + + ret = STK6D2X_REG_BLOCK_READ(alps_data, STK6D2X_REG_FIFO_FCNT1, 2, raw_data); + + if (ret < 0) + { + ALS_err("fail, err=0x%x\n", ret); + return; + } + + frame_num = (((raw_data[0] & 0x3) << 8) | raw_data[1]); + ALS_info("frame_num = %d\n", frame_num); + + if (frame_num == 0) + { + return; + } + else + { + if (alps_data->fifo_info.data_type != STK6D2X_FIFO_STA01_STA2_ALS0_ALS1_ALS2) + { + if (frame_num >= 2 * STK_FIFO_I2C_READ_FRAME_TARGET) + { + frame_num = STK_FIFO_I2C_READ_FRAME_TARGET; + is_fifo_warning = true; + } + else if(frame_num >= STK_FIFO_I2C_READ_FRAME_TARGET) + { + frame_num = STK_FIFO_I2C_READ_FRAME_TARGET; + } + } + else + { + if (frame_num >= STK_FIFO_I2C_READ_FRAME_TARGET) + { + frame_num = STK_FIFO_I2C_READ_FRAME_TARGET; + is_fifo_warning = true; + } + } + } + + stk6d2x_fifo_get_data(alps_data, frame_num); + + if (alps_data->fifo_info.last_frame_count != 0) + SEC_local_average(alps_data); + + if (is_fifo_warning) + { + ALS_info("is_fifo_warning\n"); + stk6d2x_fifo_get_data(alps_data, frame_num); + } + +#if !defined(CONFIG_AMS_ALS_COMPENSATION_FOR_AUTO_BRIGHTNESS) + if (alps_data->fifo_info.last_frame_count != 0) + SEC_local_average(alps_data); +#endif + + alps_data->fifo_info.fifo_reading = false; +} +#endif diff --git a/drivers/optics/stk6d2x_sec.c b/drivers/optics/stk6d2x_sec.c new file mode 100755 index 000000000000..c9f96bb185d0 --- /dev/null +++ b/drivers/optics/stk6d2x_sec.c @@ -0,0 +1,1547 @@ +/* + * stk6d2x_sec.c - Linux kernel modules for sensortek stk6d2x + * ambient light sensor (sec) + * + * Copyright (C) 2012~2018 Bk, sensortek 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#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_OF + #include +#endif +#include "stk6d2x.h" +#include "stk6d2x_sec.h" + +#define STR_HELPER(x) #x +#define STR(x) STR_HELPER(x) + +char driver_ver[20] = STR(STK6D2X_MAJOR) "." STR(STK6D2X_MINOR) "." STR(STK6D2X_REV); + +int als_debug = 1; +int als_info = 0; +static int probe_error; + +static int flicker_param_lpcharge = 0; +module_param(flicker_param_lpcharge, int, 0440); + +module_param(als_debug, int, S_IRUGO | S_IWUSR); +module_param(als_info, int, S_IRUGO | S_IWUSR); + +/**************************************************************************************************** +* Declaration function +****************************************************************************************************/ +#ifdef STK_ALS_CALI +static ssize_t stk6d2x_cali_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *alps_data = &stk_wrapper->alps_data; + unsigned int data; + int error; + error = kstrtouint(buf, 10, &data); + + if (error) + { + dev_err(&stk_wrapper->i2c_mgr.client->dev, "%s: kstrtoul failed, error=%d\n", + __func__, error); + return error; + } + + dev_err(&stk_wrapper->i2c_mgr.client->dev, "%s: Enable ALS calibration: %d\n", __func__, data); + + if (0x1 == data) + { + stk6d2x_cali_als(alps_data); + } + + return size; +} +#endif + +/**************************************************************************************************** +* ALS control API +****************************************************************************************************/ +static void stk_als_report(struct stk6d2x_data *alps_data, uint32_t *als, uint32_t num) +{ + //stk6d2x_wrapper *stk_wrapper = container_of(alps_data, stk6d2x_wrapper, alps_data); + int i = 0; + //input_report_abs(stk_wrapper->als_input_dev, ABS_MISC, *als); + //input_sync(stk_wrapper->als_input_dev); + + if (!num) + return; + + for (i=0; i < num; i ++) + { + //APS_ERR("als[%d] input event %d", i, *(uint32_t *)(als + i)); + } +} + +void stk_sec_report(struct stk6d2x_data *alps_data) +{ + stk6d2x_wrapper *stk_wrapper = container_of(alps_data, stk6d2x_wrapper, alps_data); + static int cnt=0; + uint32_t temp_clear = (uint32_t)(((uint64_t)(alps_data->clear_local_average)) * 35LL / 100LL); + uint32_t temp_ir = alps_data->ir_local_average; + uint32_t temp_uv = (uint32_t)(((uint64_t)(alps_data->uv_local_average)) * 79LL / 100LL); + + if (cnt++ >= 10) { + ALS_info("IR:%llu Clear:%llu UV:%llu ir_gain:%d clear_gain:%d uv_gain:%d| Flicker:%dHz\n", + alps_data->ir_local_average, alps_data->clear_local_average, alps_data->uv_local_average, + alps_data->ir_gain, alps_data->clear_gain, alps_data->uv_gain, alps_data->flicker); + ALS_info("AWB: IR*:%d Clear*:%d UV*:%d\n", temp_ir, temp_clear, temp_uv); + cnt = 0; + } + + input_report_rel(stk_wrapper->als_input_dev, REL_X, temp_ir + 1); + input_report_rel(stk_wrapper->als_input_dev, REL_RX, temp_uv + 1); + input_report_rel(stk_wrapper->als_input_dev, REL_RY, temp_clear + 1); + input_report_abs(stk_wrapper->als_input_dev, ABS_Y, alps_data->clear_gain + 1); + input_report_abs(stk_wrapper->als_input_dev, ABS_Z, alps_data->ir_gain + 1); + input_report_abs(stk_wrapper->als_input_dev, ABS_RX, alps_data->uv_gain + 1); + input_sync(stk_wrapper->als_input_dev); + +#if IS_ENABLED(CONFIG_SENSORS_FLICKER_SELF_TEST) + als_eol_update_als(temp_ir, temp_clear, temp_ir, temp_uv); +#endif +} + +int32_t stk_power_ctrl(struct stk6d2x_data *alps_data, bool en) +{ + int rc = 0; + + ALS_info("enable = %s state : %d\n", en?"ON":"OFF", alps_data->regulator_state); + + if (en) { + if (alps_data->regulator_state != 0) { + alps_data->regulator_state++; + ALS_dbg("duplicate regulator (increase state : %d)\n", alps_data->regulator_state); + return 0; + } + + if (alps_data->regulator_vbus_1p8 != NULL) { + if (!alps_data->vbus_1p8_enable) { + rc = regulator_enable(alps_data->regulator_vbus_1p8); + if (rc) { + ALS_err("enable vbus_1p8 failed, rc=%d\n", rc); + goto enable_vbus_1p8_failed; + } else { + alps_data->vbus_1p8_enable = true; + ALS_dbg("enable vbus_1p8 done, rc=%d\n", rc); + } + } else { + ALS_dbg("vbus_1p8 already enabled, en=%d\n", alps_data->vbus_1p8_enable); + } + } + + if (alps_data->regulator_vdd_1p8 != NULL) { + if (!alps_data->vdd_1p8_enable) { + rc = regulator_enable(alps_data->regulator_vdd_1p8); + if (rc) { + ALS_err("enable vdd_1p8 failed, rc=%d\n", rc); + goto enable_vdd_1p8_failed; + } else { + alps_data->vdd_1p8_enable = true; + ALS_info("enable vdd_1p8 done, (state : %d), rc=%d\n", (alps_data->regulator_state + 1), rc); + } + } else { + ALS_dbg("vdd_1p8 already enabled, en=%d\n", alps_data->vdd_1p8_enable); + } + } + + alps_data->regulator_state++; + alps_data->pm_state = PM_RESUME; + } else { + + if (alps_data->regulator_state == 0) { + ALS_dbg("already off the regulator\n"); + return 0; + } else if (alps_data->regulator_state != 1) { + alps_data->regulator_state--; + ALS_dbg("duplicate regulator (decrease state : %d)\n", alps_data->regulator_state); + return 0; + } + alps_data->regulator_state--; + + if (alps_data->regulator_vdd_1p8 != NULL) { + if (alps_data->vdd_1p8_enable) { + rc = regulator_disable(alps_data->regulator_vdd_1p8); + if (rc) { + ALS_err("disable vdd_1p8 failed, rc=%d\n", rc); + } else { + alps_data->vdd_1p8_enable = false; + ALS_dbg("disable vdd_1p8 done, (state : %d), rc=%d\n", alps_data->regulator_state, rc); + } + } else { + ALS_dbg("vdd_1p8 already disabled, en=%d\n", alps_data->vdd_1p8_enable); + } + } + + if (alps_data->regulator_vbus_1p8 != NULL) { + if (alps_data->vbus_1p8_enable) { + rc = regulator_disable(alps_data->regulator_vbus_1p8); + if (rc) { + ALS_err("disable vbus_1p8 failed, rc=%d\n", rc); + } else { + alps_data->vbus_1p8_enable = false; + ALS_dbg("disable vbus_1p8 done, rc=%d\n", rc); + } + } else { + ALS_dbg("vbus_1p8 already disabled, en=%d\n", alps_data->vbus_1p8_enable); + } + } + } + + goto done; + +enable_vdd_1p8_failed: + if (alps_data->regulator_vbus_1p8 != NULL) { + if (alps_data->vbus_1p8_enable) { + rc = regulator_disable(alps_data->regulator_vbus_1p8); + if (rc) { + ALS_err("disable vbus_1p8 failed, rc=%d\n", rc); + } else { + alps_data->vbus_1p8_enable = false; + ALS_dbg("disable vbus_1p8 done, rc=%d\n", rc); + } + } else { + ALS_dbg("vbus_1p8 already disabled, en=%d\n", alps_data->vbus_1p8_enable); + } + } + +done: +enable_vbus_1p8_failed: + return rc; +} + +static ssize_t stk_als_code_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *alps_data = &stk_wrapper->alps_data; + return scnprintf(buf, PAGE_SIZE, "%u\n", (uint32_t)alps_data->als_info.last_raw_data[0]); +} + +#if defined(CONFIG_AMS_ALS_COMPENSATION_FOR_AUTO_BRIGHTNESS) +static ssize_t stk_rear_als_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *data = &stk_wrapper->alps_data; + bool value; + + mutex_lock(&data->enable_lock); + + if (strtobool(buf, &value)) { + mutex_unlock(&data->enable_lock); + return -EINVAL; + } + + ALS_info("en : %d, c : %d\n", value, data->als_info.enable); + if (data->als_flag == value) { + mutex_unlock(&data->enable_lock); + return size; + } + + data->als_flag = value; + + if (value) + stk_als_start(data); + else + stk_als_stop(data); + + mutex_unlock(&data->enable_lock); + + return size; +} + +static ssize_t stk_rear_als_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *data = &stk_wrapper->alps_data; + + return snprintf(buf, PAGE_SIZE, "%u\n", data->als_flag); +} + +static ssize_t stk_rear_als_data_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *alps_data = &stk_wrapper->alps_data; + uint32_t rawdata[3]; + + if (alps_data->als_info.enable && alps_data->als_flag) { + if (!alps_data->flicker_flag) { + uint8_t flag_value; + + if (STK6D2X_REG_READ(alps_data, STK6D2X_REG_FLAG, &flag_value) < 0) + return snprintf(buf, PAGE_SIZE, "-6, -6\n"); + else if (flag_value & STK6D2X_FLG_ALSSAT_MASK) + return snprintf(buf, PAGE_SIZE, "-2, -2\n"); + + if (stk6d2x_als_get_data(alps_data, 0) < 0) + return snprintf(buf, PAGE_SIZE, "-6, -6\n"); + + stk6d2x_get_curGain(alps_data); + stk6d2x_get_als_ratio(alps_data); + + rawdata[0] = alps_data->als_info.last_raw_data[0] * alps_data->als_info.als_cur_ratio[0]; + rawdata[1] = alps_data->als_info.last_raw_data[1] * alps_data->als_info.als_cur_ratio[1]; + rawdata[2] = alps_data->als_info.last_raw_data[2] * alps_data->als_info.als_cur_ratio[2]; + } else { + rawdata[0] = (uint32_t)alps_data->clear_local_average * 110; /* 40ms / 360 us */ + rawdata[1] = (uint32_t)alps_data->uv_local_average * 110; + rawdata[2] = (uint32_t)alps_data->ir_local_average * 55; + } + } else { + return snprintf(buf, PAGE_SIZE, "-1, -1\n"); + } + + return snprintf(buf, PAGE_SIZE, "%u, %u\n", rawdata[0], rawdata[2]); +} +#endif /* CONFIG_AMS_ALS_COMPENSATION_FOR_AUTO_BRIGHTNESS */ + +static ssize_t stk_als_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *alps_data = &stk_wrapper->alps_data; + int32_t ret; + uint8_t data; + ret = STK6D2X_REG_READ(alps_data, STK6D2X_REG_STATE, &data); + + if (ret < 0) + return ret; + + data = (data & STK6D2X_STATE_EN_ALS_MASK) ? 1 : 0; + return scnprintf(buf, PAGE_SIZE, "%d\n", data); +} + +static ssize_t stk_als_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *alps_data = &stk_wrapper->alps_data; + unsigned int data; + int error; + + error = kstrtouint(buf, 10, &data); + if (error) { + dev_err(&stk_wrapper->i2c_mgr.client->dev, "%s: kstrtoul failed, error=%d\n", + __func__, error); + return error; + } + +#if IS_ENABLED(CONFIG_SENSORS_FLICKER_SELF_TEST) + if (alps_data->eol_enabled) { + dev_err(&stk_wrapper->i2c_mgr.client->dev, "%s: TEST RUNNING. recover %d after finish test", __func__, data); + alps_data->recover_state = data; + return size; + } +#endif + + dev_info(&stk_wrapper->i2c_mgr.client->dev, "%s: Enable ALS : %d\n", __func__, data); + + if ((1 == data) || (0 == data)) { +#if defined(CONFIG_AMS_ALS_COMPENSATION_FOR_AUTO_BRIGHTNESS) + mutex_lock(&alps_data->enable_lock); + + alps_data->flicker_flag = (bool)data; + + if (alps_data->flicker_flag && alps_data->als_flag) { + stk6d2x_enable_als(alps_data, false); + stk6d2x_enable_fifo(alps_data, false); + } +#endif + stk6d2x_alps_set_config(alps_data, data); +#if defined(CONFIG_AMS_ALS_COMPENSATION_FOR_AUTO_BRIGHTNESS) + if (!alps_data->flicker_flag && alps_data->als_flag) + stk_als_start(alps_data); + + mutex_unlock(&alps_data->enable_lock); +#endif + } + + return size; +} + +static ssize_t stk_als_lux_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *alps_data = &stk_wrapper->alps_data; + return scnprintf(buf, PAGE_SIZE, "%llu lux\n", alps_data->als_info.last_raw_data[0] * alps_data->als_info.scale / 1000); +} + +#if 0 +static ssize_t stk_als_lux_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *alps_data = &stk_wrapper->alps_data; + + unsigned long value = 0; + int ret; + ret = kstrtoul(buf, 16, &value); + + if (ret < 0) + { + ALS_err("kstrtoul failed, ret=0x%x\n", ret); + return ret; + } + + //stk_als_report(alps_data, value); + STK6D2X_ALS_REPORT(alps_data, value, 3); + return size; +} +#endif + +static ssize_t stk_als_transmittance_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *alps_data = &stk_wrapper->alps_data; + int32_t transmittance; + transmittance = alps_data->als_info.scale; + return scnprintf(buf, PAGE_SIZE, "%d\n", transmittance); +} + +static ssize_t stk_als_transmittance_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *alps_data = &stk_wrapper->alps_data; + unsigned long value = 0; + int ret; + ret = kstrtoul(buf, 10, &value); + + if (ret < 0) + { + ALS_err("kstrtoul failed, ret=0x%x\n", ret); + return ret; + } + + alps_data->als_info.scale = value; + return size; +} + +static ssize_t stk6d2x_als_registry_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *alps_data = &stk_wrapper->alps_data; + stk6d2x_update_registry(alps_data); + return scnprintf(buf, PAGE_SIZE, "als scale = %d (Ver. %d)\n", + alps_data->cali_info.cali_para.als_scale, + alps_data->cali_info.cali_para.als_version); +} + +static ssize_t stk6d2x_registry_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *alps_data = &stk_wrapper->alps_data; + unsigned long value = 0; + int ret; + ret = kstrtoul(buf, 10, &value); + + if (ret < 0) { + ALS_err("kstrtoul failed, ret=0x%x\n", ret); + return ret; + } + + ret = stk6d2x_update_registry(alps_data); + + if (ret < 0) + ALS_err("update registry fail!\n"); + else + ALS_err("update registry success!\n"); + + return size; +} + +#ifdef STK_ALS_CALI +static ssize_t stk6d2x_als_cali_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *alps_data = &stk_wrapper->alps_data; + stk6d2x_request_registry(alps_data); + return scnprintf(buf, PAGE_SIZE, "als scale = %d (Ver. %d)\n", + alps_data->cali_info.cali_para.als_scale, + alps_data->cali_info.cali_para.als_version); +} +#endif + +static ssize_t stk_recv_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + return scnprintf(buf, PAGE_SIZE, "0x%04X\n", atomic_read(&stk_wrapper->recv_reg)); +} + +static ssize_t stk_recv_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +{ + unsigned long value = 0; + int ret; + uint8_t recv_data; + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *alps_data = &stk_wrapper->alps_data; + + if ((ret = kstrtoul(buf, 16, &value)) < 0) { + ALS_err("kstrtoul failed, ret=0x%x\n", ret); + return ret; + } + + atomic_set(&stk_wrapper->recv_reg, 0); + STK6D2X_REG_READ(alps_data, value, &recv_data); + atomic_set(&stk_wrapper->recv_reg, recv_data); + return size; +} + +static ssize_t stk_send_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return 0; +} + +static ssize_t stk_send_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +{ + unsigned char addr, cmd; + unsigned long temp_addr, temp_cmd; + int32_t ret, i; + char *token[10]; + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *alps_data = &stk_wrapper->alps_data; + + for (i = 0; i < 2; i++) + token[i] = strsep((char **)&buf, " "); + + if ((ret = kstrtoul(token[0], 16, &temp_addr)) < 0) { + ALS_err("kstrtoul failed, ret=0x%x\n", ret); + return ret; + } + + if ((ret = kstrtoul(token[1], 16, &temp_cmd)) < 0) { + ALS_err("kstrtoul failed, ret=0x%x\n", ret); + return ret; + } + + addr = temp_addr & 0xFF; + cmd = temp_cmd & 0xFF; + ALS_info("write reg 0x%x=0x%x\n", addr, cmd); + ret = STK6D2X_REG_WRITE(alps_data, addr, cmd); + + if (0 != ret) { + ALS_err("stk6d2x_i2c_smbus_write_byte_data fail\n"); + return ret; + } + + return size; +} + +static ssize_t stk_name_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", STK6D2X_DEV_NAME); +} + +static ssize_t stk_flush_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *data = &stk_wrapper->alps_data; + int ret = 0; + u8 handle = 0; + + ret = kstrtou8(buf, 10, &handle); + if (ret < 0) { + ALS_err("kstrtou8 failed.(%d)\n", ret); + return ret; + } + input_report_rel(stk_wrapper->als_input_dev, REL_MISC, handle); + ALS_info("flush done\n"); + + return size; +} + +static ssize_t stk_nothing_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return 0; +} + +static ssize_t stk_nothing_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +{ + return size; +} + +static ssize_t stk_factory_cmd_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *data = &stk_wrapper->alps_data; + + ALS_dbg("isTrimmed = %d\n", data->isTrimmed); + + return snprintf(buf, PAGE_SIZE, "%d\n", data->isTrimmed); +} + +static ssize_t als_ir_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *data = &stk_wrapper->alps_data; + int waiting_cnt = 0; + + while (!data->is_local_avg_update && waiting_cnt < 100) { + waiting_cnt++; + msleep_interruptible(10); + } + + ALS_dbg("read als_ir = %llu (is_local_avg_update = %d, waiting_cnt = %d)\n", + data->ir_local_average, data->is_local_avg_update, waiting_cnt); + return snprintf(buf, PAGE_SIZE, "%llu\n", data->ir_local_average); +} + +static ssize_t als_clear_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *data = &stk_wrapper->alps_data; + uint32_t temp_clear; + int waiting_cnt = 0; + + while (!data->is_local_avg_update && waiting_cnt < 100) { + waiting_cnt++; + msleep_interruptible(10); + } + + temp_clear = (uint32_t)(((uint64_t)(data->clear_local_average)) * 35LL / 100LL); + + ALS_dbg("read als_clear = %d (is_local_avg_update = %d, waiting_cnt = %d)\n", + temp_clear, data->is_local_avg_update, waiting_cnt); + return snprintf(buf, PAGE_SIZE, "%d\n", temp_clear); +} + +static ssize_t als_wideband_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *data = &stk_wrapper->alps_data; + int waiting_cnt = 0; + + while (!data->is_local_avg_update && waiting_cnt < 100) { + waiting_cnt++; + msleep_interruptible(10); + } + + ALS_dbg("read als_wideband = %llu (is_local_avg_update = %d, waiting_cnt = %d)\n", + data->ir_local_average, data->is_local_avg_update, waiting_cnt); + return snprintf(buf, PAGE_SIZE, "%llu\n", data->ir_local_average); +} + +static ssize_t als_uv_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *data = &stk_wrapper->alps_data; + uint32_t temp_uv; + int waiting_cnt = 0; + + while (!data->is_local_avg_update && waiting_cnt < 100) { + waiting_cnt++; + msleep_interruptible(10); + } + + temp_uv = (uint32_t)(((uint64_t)(data->uv_local_average)) * 79LL / 100LL); + + ALS_dbg("read als_uv = %d (is_local_avg_update = %d, waiting_cnt = %d)\n", + temp_uv, data->is_local_avg_update, waiting_cnt); + return snprintf(buf, PAGE_SIZE, "%d\n", temp_uv); +} + +static ssize_t als_raw_data_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *data = &stk_wrapper->alps_data; + uint32_t temp_clear; + uint32_t temp_ir; + uint32_t temp_uv; + int waiting_cnt = 0; + + while (!data->is_local_avg_update && waiting_cnt < 100) { + waiting_cnt++; + msleep_interruptible(10); + } + + temp_clear = (uint32_t)(((uint64_t)(data->clear_local_average)) * 35LL / 100LL); + temp_ir = data->ir_local_average; + temp_uv = (uint32_t)(((uint64_t)(data->uv_local_average)) * 79LL / 100LL); + + ALS_dbg("read als_clear = %d als_ir = %d als_uv = %d (is_local_avg_update = %d, waiting_cnt = %d)\n",\ + temp_clear, temp_ir, temp_uv, data->is_local_avg_update, waiting_cnt); + return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n", temp_clear, temp_ir, temp_uv); +}; + +static ssize_t flicker_data_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *data = &stk_wrapper->alps_data; + int waiting_cnt = 0; + + while (!data->is_local_avg_update && waiting_cnt < 100) { + waiting_cnt++; + msleep_interruptible(10); + } + + ALS_dbg("read flicker_data = %d (is_local_avg_update = %d, waiting_cnt = %d)\n", + data->flicker, data->is_local_avg_update, waiting_cnt); + return snprintf(buf, PAGE_SIZE, "%d\n", data->flicker); +} + +#if IS_ENABLED(CONFIG_SENSORS_FLICKER_SELF_TEST) +#define FREQ_SPEC_MARGIN 10 +#define FREQ100_SPEC_IN(X) (((X > (100 - FREQ_SPEC_MARGIN)) && (X < (100 + FREQ_SPEC_MARGIN)))?"PASS":"FAIL") +#define FREQ120_SPEC_IN(X) (((X > (120 - FREQ_SPEC_MARGIN)) && (X < (120 + FREQ_SPEC_MARGIN)))?"PASS":"FAIL") + +#define WIDE_CLEAR_SPEC_MIN 0 +#define WIDE_CLEAR_SPEC_MAX 5000000 +#define WIDE_SPEC_IN(X) ((X >= WIDE_CLEAR_SPEC_MIN && X <= WIDE_CLEAR_SPEC_MAX)?"PASS":"FAIL") +#define CLEAR_SPEC_IN(X) ((X >= WIDE_CLEAR_SPEC_MIN && X <= WIDE_CLEAR_SPEC_MAX)?"PASS":"FAIL") +#define UV_SPEC_IN(X) ((X >= WIDE_CLEAR_SPEC_MIN && X <= WIDE_CLEAR_SPEC_MAX)?"PASS":"FAIL") +#define ICRATIO_SPEC_IN(X) "PASS" + +static struct result_data *eol_result = NULL; +static ssize_t stk_eol_test_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *alps_data = &stk_wrapper->alps_data; + + if (alps_data->eol_enabled) { + snprintf(buf, MAX_TEST_RESULT, "EOL_RUNNING"); + } else if (eol_result == NULL) { + snprintf(buf, MAX_TEST_RESULT, "NO_EOL_TEST"); + } else { + snprintf(buf, MAX_TEST_RESULT, "%d, %s, %d, %s, %d, %s, %d, %s, %d, %s, %d, %s, %d, %s, %d, %s, %d, %s, %d, %s\n", + eol_result->flicker[EOL_STATE_100], FREQ100_SPEC_IN(eol_result->flicker[EOL_STATE_100]), + eol_result->flicker[EOL_STATE_120], FREQ120_SPEC_IN(eol_result->flicker[EOL_STATE_120]), + eol_result->wideband[EOL_STATE_100], WIDE_SPEC_IN(eol_result->wideband[EOL_STATE_100]), + eol_result->wideband[EOL_STATE_120], WIDE_SPEC_IN(eol_result->wideband[EOL_STATE_120]), + eol_result->clear[EOL_STATE_100], CLEAR_SPEC_IN(eol_result->clear[EOL_STATE_100]), + eol_result->clear[EOL_STATE_120], CLEAR_SPEC_IN(eol_result->clear[EOL_STATE_120]), + eol_result->ratio[EOL_STATE_100], ICRATIO_SPEC_IN(eol_result->ratio[EOL_STATE_100]), + eol_result->ratio[EOL_STATE_120], ICRATIO_SPEC_IN(eol_result->ratio[EOL_STATE_120]), + eol_result->uv[EOL_STATE_100], UV_SPEC_IN(eol_result->uv[EOL_STATE_100]), + eol_result->uv[EOL_STATE_120], UV_SPEC_IN(eol_result->uv[EOL_STATE_120])); + + eol_result = NULL; + } + ALS_info("%s\n", buf); + + return MAX_TEST_RESULT; +} + +static ssize_t stk_eol_test_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *alps_data = &stk_wrapper->alps_data; + +#if defined(CONFIG_AMS_ALS_COMPENSATION_FOR_AUTO_BRIGHTNESS) + bool flicker_flag = alps_data->flicker_flag; + + if (probe_error) + return 0; + + if (!flicker_flag) { + alps_data->flicker_flag = true; + + if (alps_data->als_flag) { + stk6d2x_enable_als(alps_data, false); + stk6d2x_enable_fifo(alps_data, false); + } + } +#endif + //Start + alps_data->recover_state = alps_data->als_info.enable; + + if (!alps_data->recover_state) + stk6d2x_alps_set_config(alps_data, 1); + + alps_data->eol_enabled = true; + + als_eol_set_env(true, 80); + eol_result = als_eol_mode(); + + alps_data->eol_enabled = false; + + //Stop + stk6d2x_alps_set_config(alps_data, alps_data->recover_state); +#if defined(CONFIG_AMS_ALS_COMPENSATION_FOR_AUTO_BRIGHTNESS) + if (!flicker_flag) { + alps_data->flicker_flag = false; + + if (alps_data->als_flag) { + stk6d2x_alps_set_config(alps_data, true); + stk_als_init(alps_data); + } + } +#endif + return size; +} + +#endif +/**************************************************************************************************** +* ALS ATTR List +****************************************************************************************************/ +static DEVICE_ATTR(enable, 0664, stk_als_enable_show, stk_als_enable_store); +static DEVICE_ATTR(lux, 0664, stk_als_lux_show, NULL); +static DEVICE_ATTR(code, 0444, stk_als_code_show, NULL); +static DEVICE_ATTR(transmittance, 0664, stk_als_transmittance_show, stk_als_transmittance_store); +static DEVICE_ATTR(updateregistry, 0644, stk6d2x_als_registry_show, stk6d2x_registry_store); +static DEVICE_ATTR(recv, 0664, stk_recv_show, stk_recv_store); +static DEVICE_ATTR(send, 0664, stk_send_show, stk_send_store); +static DEVICE_ATTR(poll_delay, 0664, stk_nothing_show, stk_nothing_store); +#ifdef STK_ALS_FIR + static DEVICE_ATTR(firlen, 0644, stk_als_firlen_show, stk_als_firlen_store); +#endif +#ifdef STK_ALS_CALI + static DEVICE_ATTR(cali, 0644, stk6d2x_als_cali_show, stk6d2x_cali_store); +#endif + +static DEVICE_ATTR(name, 0444, stk_name_show, NULL); +static DEVICE_ATTR(als_flush, 0664, stk_nothing_show, stk_flush_store); +static DEVICE_ATTR(als_factory_cmd, 0444, stk_factory_cmd_show, NULL); +static DEVICE_ATTR(als_ir, 0444, als_ir_show, NULL); +static DEVICE_ATTR(als_clear, 0444, als_clear_show, NULL); +static DEVICE_ATTR(als_wideband, 0444, als_wideband_show, NULL); +static DEVICE_ATTR(als_uv, 0444, als_uv_show, NULL); +static DEVICE_ATTR(als_raw_data, 0444, als_raw_data_show, NULL); +static DEVICE_ATTR(flicker_data, 0444, flicker_data_show, NULL); +#if IS_ENABLED(CONFIG_SENSORS_FLICKER_SELF_TEST) +static DEVICE_ATTR(eol_mode, 0664, stk_eol_test_show, stk_eol_test_store); +#endif +#if defined(CONFIG_AMS_ALS_COMPENSATION_FOR_AUTO_BRIGHTNESS) +static DEVICE_ATTR(als_enable, 0664, stk_rear_als_enable_show, stk_rear_als_enable_store); +static DEVICE_ATTR(als_data, 0444, stk_rear_als_data_show, NULL); +#endif + +static struct attribute *stk_als_attrs [] = +{ + &dev_attr_enable.attr, + &dev_attr_lux.attr, + &dev_attr_code.attr, + &dev_attr_transmittance.attr, + &dev_attr_updateregistry.attr, + &dev_attr_recv.attr, + &dev_attr_send.attr, +#ifdef STK_ALS_FIR + &dev_attr_firlen.attr, +#endif +#ifdef STK_ALS_CALI + &dev_attr_cali.attr, +#endif + &dev_attr_poll_delay.attr, + NULL +}; +static struct attribute_group stk_als_attribute_group = +{ + //.name = "driver", + .attrs = stk_als_attrs, +}; + +static struct device_attribute *stk_sensor_attrs[] = { + &dev_attr_name, + &dev_attr_als_flush, + &dev_attr_als_factory_cmd, + &dev_attr_als_ir, + &dev_attr_als_clear, + &dev_attr_als_wideband, + &dev_attr_als_uv, + &dev_attr_als_raw_data, + &dev_attr_flicker_data, +#if IS_ENABLED(CONFIG_SENSORS_FLICKER_SELF_TEST) + &dev_attr_eol_mode, +#endif +#if defined(CONFIG_AMS_ALS_COMPENSATION_FOR_AUTO_BRIGHTNESS) + &dev_attr_als_enable, + &dev_attr_als_data, +#endif + NULL +}; + +#ifdef SUPPORT_SENSOR_CLASS +/**************************************************************************************************** +* Sensor class API +****************************************************************************************************/ +static int stk6d2x_cdev_enable_als(struct sensors_classdev *sensors_cdev, + unsigned int enable) +{ + struct stk6d2x_wrapper *alps_wrapper = container_of(sensors_cdev, + stk6d2x_wrapper, als_cdev); + stk6d2x_alps_set_config(&alps_wrapper->alps_data, enable); + return 0; +} + +static int stk6d2x_cdev_set_als_calibration(struct sensors_classdev *sensors_cdev, int axis, int apply_now) +{ + struct stk6d2x_wrapper *alps_wrapper = container_of(sensors_cdev, + stk6d2x_wrapper, als_cdev); + stk6d2x_cali_als(&alps_wrapper->alps_data); + return 0; +} + +static struct sensors_classdev als_cdev = +{ + .name = "stk6d2x-light", + .vendor = "sensortek", + .version = 1, + .handle = SENSORS_LIGHT_HANDLE, + .type = SENSOR_TYPE_LIGHT, + .max_range = "65536", + .resolution = "1.0", + .sensor_power = "0.25", + .min_delay = 50000, + .max_delay = 2000, + .fifo_reserved_event_count = 0, + .fifo_max_event_count = 0, + .flags = 2, + .enabled = 0, + .delay_msec = 50, + .sensors_enable = NULL, + .sensors_calibrate = NULL, +}; + +#endif + + +void stk6d2x_pin_control(struct stk6d2x_data *alps_data, bool pin_set) +{ + int status = 0; + + if (!alps_data->als_pinctrl) { + ALS_info("als_pinctrl is null\n"); + return; + } + + if (pin_set) { + if (!IS_ERR_OR_NULL(alps_data->pins_active)) { + status = pinctrl_select_state(alps_data->als_pinctrl, alps_data->pins_active); + if (status) + ALS_err("can't set pin active state\n"); + else + ALS_info("set active state\n"); + } + } else { + if (!IS_ERR_OR_NULL(alps_data->pins_sleep)) { + status = pinctrl_select_state(alps_data->als_pinctrl, alps_data->pins_sleep); + if (status) + ALS_err("can't set pin sleep state\n"); + else + ALS_info("set sleep state\n"); + } + } +} + +int stk6d2x_suspend(struct device *dev) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *alps_data = &stk_wrapper->alps_data; + + if(probe_error) + return 0; + + mutex_lock(&alps_data->enable_lock); +#if defined(CONFIG_AMS_ALS_COMPENSATION_FOR_AUTO_BRIGHTNESS) + if (alps_data->als_flag) + stk_als_stop(alps_data); +#endif + + if (alps_data->als_info.enable) { + ALS_dbg("Disable ALS\n"); + stk6d2x_alps_set_config(alps_data, 0); + } + mutex_unlock(&alps_data->enable_lock); + + stk6d2x_pin_control(alps_data, false); + stk_power_ctrl(alps_data, false); + + return 0; +} + +int stk6d2x_resume(struct device *dev) +{ + stk6d2x_wrapper *stk_wrapper = dev_get_drvdata(dev); + stk6d2x_data *alps_data = &stk_wrapper->alps_data; + + if(probe_error) + return 0; + + stk_power_ctrl(alps_data, true); + + mutex_lock(&alps_data->enable_lock); + if (alps_data->als_info.enable) { + ALS_dbg("Enable ALS\n"); + msleep_interruptible(40); + stk6d2x_alps_set_config(alps_data, 1); + } +#if defined(CONFIG_AMS_ALS_COMPENSATION_FOR_AUTO_BRIGHTNESS) + else if (alps_data->als_flag) { + msleep_interruptible(40); + stk_als_start(alps_data); + } +#endif + mutex_unlock(&alps_data->enable_lock); + + stk6d2x_pin_control(alps_data, true); + + return 0; +} + +static int stk6d2x_parse_dt(struct stk6d2x_wrapper *stk_wrapper, + struct stk6d2x_platform_data *pdata) +{ + int rc; + struct device *dev = stk_wrapper->dev; + stk6d2x_data *alps_data = &stk_wrapper->alps_data; + struct device_node *np = dev->of_node; + struct device_node *vbus_of_node = NULL; + struct device_node *vdd_of_node = NULL; + u32 temp_val; + + ALS_info("parse dt\n"); + + if (of_get_property(np, "als_rear,use_ext_clk", NULL)) { + alps_data->use_ext_clk = TRUE; + } + + if (alps_data->use_ext_clk) { +#if KERNEL_VERSION(6, 2, 0) <= LINUX_VERSION_CODE + alps_data->ext_clk_gpio = of_get_named_gpio(np, "stk,ext-clk-gpio", 0); +#else + alps_data->ext_clk_gpio = of_get_named_gpio_flags(np, "stk,ext-clk-gpio", + 0, &pdata->int_flags); +#endif + + if (alps_data->ext_clk_gpio < 0) + ALS_err("Unable to read ext_clk_gpio\n"); + else + ALS_info("alps_data->ext_clk_gpio = %d\n", alps_data->ext_clk_gpio); + } + + vbus_of_node = of_parse_phandle(np, "vbus_1p8-supply", 0); + if (vbus_of_node) { + alps_data->regulator_vbus_1p8 = regulator_get(dev, "vbus_1p8"); + if (IS_ERR(alps_data->regulator_vbus_1p8) || alps_data->regulator_vbus_1p8 == NULL) { + ALS_err("get vbus_1p8 regulator failed\n"); + alps_data->regulator_vbus_1p8 = NULL; + } else { + ALS_dbg("get vbus_1p8 regulator = %p done \n", alps_data->regulator_vbus_1p8); + } + } else { + ALS_err("get vbus_1p8 regulator failed\n"); + alps_data->regulator_vbus_1p8 = NULL; + } + + vdd_of_node = of_parse_phandle(np, "vdd_1p8-supply", 0); + if (vdd_of_node) { + alps_data->regulator_vdd_1p8 = regulator_get(dev, "vdd_1p8"); + if (IS_ERR(alps_data->regulator_vdd_1p8) || alps_data->regulator_vdd_1p8 == NULL) { + ALS_err("get vdd_1p8 regulator failed\n"); + alps_data->regulator_vdd_1p8 = NULL; + return -ENODEV; + } else { + ALS_dbg("get vdd_1p8 regulator = %p done \n", alps_data->regulator_vdd_1p8); + } + } else { + ALS_err("get vdd_1p8 regulator failed\n"); + alps_data->regulator_vdd_1p8 = NULL; + } + + alps_data->als_pinctrl = devm_pinctrl_get(dev); + + if (IS_ERR_OR_NULL(alps_data->als_pinctrl)) { + ALS_err("get pinctrl(%li) error\n", PTR_ERR(alps_data->als_pinctrl)); + alps_data->als_pinctrl = NULL; + } else { + alps_data->pins_sleep = pinctrl_lookup_state(alps_data->als_pinctrl, "sleep"); + if (IS_ERR_OR_NULL(alps_data->pins_sleep)) { + ALS_err("get pins_sleep(%li) error\n", PTR_ERR(alps_data->pins_sleep)); + alps_data->pins_sleep = NULL; + } + + alps_data->pins_active = pinctrl_lookup_state(alps_data->als_pinctrl, "active"); + if (IS_ERR_OR_NULL(alps_data->pins_active)) { + ALS_err("get pins_active(%li) error\n", PTR_ERR(alps_data->pins_active)); + alps_data->pins_active = NULL; + } + } + + rc = of_property_read_u32(np, "stk,als_scale", &temp_val); + + if (!rc) { + pdata->als_scale = temp_val; + ALS_info("stk-als_scale = %d\n", temp_val); + } else { + ALS_err("Unable to read als_scale\n"); + } + + return 0; +} + +static int stk6d2x_set_input_devices(struct stk6d2x_wrapper *stk_wrapper) +{ + int err; + /****** Create ALS ATTR ******/ + stk6d2x_data *alps_data = &stk_wrapper->alps_data; + stk_wrapper->als_input_dev = input_allocate_device(); + + if (!stk_wrapper->als_input_dev) { + ALS_err("could not allocate als device\n"); + err = -ENOMEM; + return err; + } + + stk_wrapper->als_input_dev->name = MODULE_NAME_ALS; + stk_wrapper->als_input_dev->id.bustype = BUS_I2C; + input_set_drvdata(stk_wrapper->als_input_dev, alps_data); + //set_bit(EV_ABS, alps_data->als_input_dev->evbit); + //input_set_abs_params(alps_data->als_input_dev, ABS_MISC, 0, ((1 << 16) - 1), 0, 0); + input_set_capability(stk_wrapper->als_input_dev, EV_REL, REL_X); /* ir */ + input_set_capability(stk_wrapper->als_input_dev, EV_REL, REL_RX); /* UV */ + input_set_capability(stk_wrapper->als_input_dev, EV_REL, REL_RY); /* clear */ + input_set_capability(stk_wrapper->als_input_dev, EV_REL, REL_RZ); /* flicker */ + input_set_capability(stk_wrapper->als_input_dev, EV_REL, REL_MISC); /* flush */ + input_set_capability(stk_wrapper->als_input_dev, EV_ABS, ABS_Y); /* gain_clear */ + input_set_capability(stk_wrapper->als_input_dev, EV_ABS, ABS_Z); /* gain_ir */ + input_set_capability(stk_wrapper->als_input_dev, EV_ABS, ABS_RX); /* gain_uv */ + + err = input_register_device(stk_wrapper->als_input_dev); + + if (err) { + ALS_err("can not register als input device\n"); + goto err_als_input_register; + } + +#if IS_ENABLED(CONFIG_ARCH_EXYNOS) + err = sensors_create_symlink(stk_wrapper->als_input_dev); +#else + err = sensors_create_symlink(&stk_wrapper->als_input_dev->dev.kobj, stk_wrapper->als_input_dev->name); +#endif + if (err < 0) { + ALS_err("%s - could not create_symlink\n", __func__); + goto err_sensors_create_symlink; + } + + err = sysfs_create_group(&stk_wrapper->als_input_dev->dev.kobj, &stk_als_attribute_group); + if (err < 0) { + ALS_err("could not create sysfs group for als\n"); + goto err_sysfs_create_group; + } + + input_set_drvdata(stk_wrapper->als_input_dev, stk_wrapper); +#ifdef SUPPORT_SENSOR_CLASS + stk_wrapper->als_cdev = als_cdev; + stk_wrapper->als_cdev.sensors_enable = stk6d2x_cdev_enable_als; + stk_wrapper->als_cdev.sensors_calibrate = stk6d2x_cdev_set_als_calibration; + err = sensors_classdev_register(&stk_wrapper->als_input_dev->dev, &stk_wrapper->als_cdev); + + if (err) { + ALS_err("ALS sensors class register failed.\n"); + goto err_register_als_cdev; + } + +#endif + + err = sensors_register(&stk_wrapper->sensor_dev, stk_wrapper, stk_sensor_attrs, MODULE_NAME_ALS); + if (err) { + ALS_err("%s - cound not register als_sensor(%d).\n", __func__, err); + goto als_sensor_register_failed; + } + + ALS_info("done\n"); + return 0; + +als_sensor_register_failed: +#ifdef SUPPORT_SENSOR_CLASS + sensors_classdev_unregister(&stk_wrapper->als_cdev); +err_register_als_cdev: +#endif + sysfs_remove_group(&stk_wrapper->als_input_dev->dev.kobj, &stk_als_attribute_group); +err_sysfs_create_group: +#if IS_ENABLED(CONFIG_ARCH_EXYNOS) + sensors_remove_symlink(stk_wrapper->als_input_dev); +#else + sensors_remove_symlink(&stk_wrapper->als_input_dev->dev.kobj, stk_wrapper->als_input_dev->name); +#endif +err_sensors_create_symlink: + input_unregister_device(stk_wrapper->als_input_dev); +err_als_input_register: + return err; +} + +int stk6d2x_probe(struct i2c_client *client, + struct common_function *common_fn) +{ + int err = -ENODEV; + stk6d2x_wrapper *stk_wrapper; + stk6d2x_data *alps_data; + struct stk6d2x_platform_data *plat_data; + + ALS_dbg("stk6d2x_version : %d.%d.%d\n", STK6D2X_MAJOR, + STK6D2X_MINOR, + STK6D2X_REV); + + if (!common_fn) { + dev_err(&client->dev, "%s: Operation not Supported\n", __func__); + return -EPERM; + } + + err = i2c_check_functionality(client->adapter, I2C_FUNC_I2C); + + if (!err) { + dev_err(&client->dev, "%s: SMBUS Byte Data not Supported\n", __func__); + return -EIO; + } + + stk_wrapper = kzalloc(sizeof(stk6d2x_wrapper), GFP_KERNEL); + + if (!stk_wrapper) { + ALS_err("failed to allocate stk6d2x_wrapper\n"); + return -ENOMEM; + } + + alps_data = &stk_wrapper->alps_data; + + if (!alps_data) { + ALS_err("failed to allocate stk6d2x_data\n"); + return -ENOMEM; + } + + stk_wrapper->i2c_mgr.client = client; + stk_wrapper->i2c_mgr.addr_type = ADDR_8BIT; + stk_wrapper->dev = &client->dev; + alps_data->bops = common_fn->bops; + alps_data->tops = common_fn->tops; + alps_data->gops = common_fn->gops; + i2c_set_clientdata(client, stk_wrapper); + mutex_init(&stk_wrapper->i2c_mgr.lock); + mutex_init(&alps_data->config_lock); +#if defined(CONFIG_AMS_ALS_COMPENSATION_FOR_AUTO_BRIGHTNESS) + mutex_init(&alps_data->enable_lock); +#endif + alps_data->bus_idx = alps_data->bops->init(&stk_wrapper->i2c_mgr); + + if (alps_data->bus_idx < 0) { + goto err_free_mem; + } + + // Parsing device tree + if (client->dev.of_node) { + ALS_dbg("probe with device tree\n"); + plat_data = devm_kzalloc(&client->dev, + sizeof(struct stk6d2x_platform_data), GFP_KERNEL); + + if (!plat_data) { + dev_err(&client->dev, "%s: Failed to allocate memory\n", __func__); + return -ENOMEM; + } + + err = stk6d2x_parse_dt(stk_wrapper, plat_data); + + if (err) { + dev_err(&client->dev, + "%s: stk6d2x_parse_dt ret=%d\n", __func__, err); + goto err_parse_dt; + } + } else { + ALS_err("probe with platform data\n"); + plat_data = client->dev.platform_data; + } + + if (!plat_data) { + dev_err(&client->dev, + "%s: no stk6d2x platform data!\n", __func__); + goto err_als_input_allocate; + } + + if (plat_data->als_scale == 0) { + dev_err(&client->dev, + "%s: Please set als_scale = %d\n", __func__, plat_data->als_scale); + goto err_als_input_allocate; + } + + stk_power_ctrl(alps_data, true); + msleep_interruptible(40); + + stk6d2x_pin_control(alps_data, true); + + // Register device + err = stk6d2x_set_input_devices(stk_wrapper); + + if (err < 0) + goto err_setup_input_device; + + alps_data->pdata = plat_data; + + err = stk6d2x_init_all_setting(alps_data); + dev_err(&client->dev, + "%s: err = %d\n", __func__, err); + + if (err < 0) + goto err_setup_init_reg; + + ALS_dbg("probe successfully\n"); + + stk6d2x_pin_control(alps_data, false); + probe_error = 0; + return 0; + +err_setup_init_reg: + stk6d2x_pin_control(alps_data, false); + sensors_unregister(stk_wrapper->sensor_dev, stk_sensor_attrs); +#ifdef SUPPORT_SENSOR_CLASS + sensors_classdev_unregister(&stk_wrapper->als_cdev); +#endif + sysfs_remove_group(&stk_wrapper->als_input_dev->dev.kobj, &stk_als_attribute_group); +#if IS_ENABLED(CONFIG_ARCH_EXYNOS) + sensors_remove_symlink(stk_wrapper->als_input_dev); +#else + sensors_remove_symlink(&stk_wrapper->als_input_dev->dev.kobj, stk_wrapper->als_input_dev->name); +#endif + input_unregister_device(stk_wrapper->als_input_dev); +err_setup_input_device: + stk_power_ctrl(alps_data, false); +err_als_input_allocate: +err_parse_dt: + + if (alps_data->als_pinctrl) { + devm_pinctrl_put(alps_data->als_pinctrl); + alps_data->als_pinctrl = NULL; + } + if (alps_data->pins_active) + alps_data->pins_active = NULL; + if (alps_data->pins_sleep) + alps_data->pins_sleep = NULL; + + if (alps_data->pclk) + alps_data->pclk = NULL; + +err_free_mem: +#ifdef STK_FIFO_ENABLE + STK6D2X_TIMER_REMOVE(alps_data, &alps_data->fifo_release_timer_info); +#endif + alps_data->bops->remove(&stk_wrapper->i2c_mgr); + mutex_destroy(&stk_wrapper->i2c_mgr.lock); + mutex_destroy(&alps_data->config_lock); +#if defined(CONFIG_AMS_ALS_COMPENSATION_FOR_AUTO_BRIGHTNESS) + mutex_destroy(&alps_data->enable_lock); +#endif + kfree(stk_wrapper); + probe_error = err; + return err; +} + +int stk6d2x_remove(struct i2c_client *client) +{ + stk6d2x_wrapper *stk_wrapper = i2c_get_clientdata(client); + struct stk6d2x_data *alps_data = &stk_wrapper->alps_data; + stk_power_ctrl(alps_data, false); + if (alps_data->regulator_vbus_1p8) { + if (alps_data->vbus_1p8_enable) { + regulator_disable(alps_data->regulator_vbus_1p8); + } + alps_data->regulator_vbus_1p8 = NULL; + } + + if (alps_data->regulator_vdd_1p8) { + if (alps_data->vdd_1p8_enable) { + regulator_disable(alps_data->regulator_vdd_1p8); + } + ALS_dbg("put vdd_1p8 regulator = %p done (en = %d)\n", + alps_data->regulator_vdd_1p8, alps_data->vdd_1p8_enable); + regulator_put(alps_data->regulator_vdd_1p8); + alps_data->regulator_vdd_1p8 = NULL; + } + + if (alps_data->reg) { + if (alps_data->reg_enable) { + regulator_disable(alps_data->reg); + } + ALS_dbg("put gdscr regulator = %p done (en = %d)\n", + alps_data->reg, alps_data->reg_enable); + alps_data->reg = NULL; + } + + if (alps_data->pclk) + alps_data->pclk = NULL; + + if (alps_data->als_pinctrl) { + devm_pinctrl_put(alps_data->als_pinctrl); + alps_data->als_pinctrl = NULL; + } + if (alps_data->pins_active) + alps_data->pins_active = NULL; + if (alps_data->pins_sleep) + alps_data->pins_sleep = NULL; + + device_init_wakeup(&client->dev, false); + STK6D2X_GPIO_IRQ_REMOVE(alps_data, &alps_data->gpio_info); + STK6D2X_TIMER_REMOVE(alps_data, &alps_data->alps_timer_info); + sensors_unregister(stk_wrapper->sensor_dev, stk_sensor_attrs); + sysfs_remove_group(&stk_wrapper->als_input_dev->dev.kobj, &stk_als_attribute_group); +#if IS_ENABLED(CONFIG_ARCH_EXYNOS) + sensors_remove_symlink(stk_wrapper->als_input_dev); +#else + sensors_remove_symlink(&stk_wrapper->als_input_dev->dev.kobj, stk_wrapper->als_input_dev->name); +#endif + input_unregister_device(stk_wrapper->als_input_dev); +#ifdef STK_FIFO_ENABLE + STK6D2X_TIMER_REMOVE(alps_data, &alps_data->fifo_release_timer_info); +#endif + alps_data->bops->remove(&stk_wrapper->i2c_mgr); + mutex_destroy(&stk_wrapper->i2c_mgr.lock); + mutex_destroy(&alps_data->config_lock); +#if defined(CONFIG_AMS_ALS_COMPENSATION_FOR_AUTO_BRIGHTNESS) + mutex_destroy(&alps_data->enable_lock); +#endif + kfree(stk_wrapper); + return 0; +} + +#if KERNEL_VERSION(6, 2, 0) <= LINUX_VERSION_CODE +static int stk6d2x_i2c_probe(struct i2c_client *client) +{ + struct common_function common_fn = + { + .bops = &stk_i2c_bops, + .tops = &stk_t_ops, + .gops = &stk_g_ops, + }; + return stk6d2x_probe(client, &common_fn); +} +#else +static int stk6d2x_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct common_function common_fn = + { + .bops = &stk_i2c_bops, + .tops = &stk_t_ops, + .gops = &stk_g_ops, + }; + return stk6d2x_probe(client, &common_fn); +} +#endif + +static int stk6d2x_i2c_remove(struct i2c_client *client) +{ + return stk6d2x_remove(client); +} + +int stk6d2x_i2c_suspend(struct device *dev) +{ + stk6d2x_suspend(dev); + return 0; +} + +int stk6d2x_i2c_resume(struct device *dev) +{ + stk6d2x_resume(dev); + return 0; +} + +static const struct dev_pm_ops stk6d2x_pm_ops = +{ + SET_SYSTEM_SLEEP_PM_OPS(stk6d2x_i2c_suspend, stk6d2x_i2c_resume) +}; + +static const struct i2c_device_id stk_als_id[] = +{ + { "stk_als", 0}, + {} +}; + +static struct of_device_id stk_match_table[] = +{ + { .compatible = "stk,stk6d2x", }, + { }, +}; + +static struct i2c_driver stk_als_driver = +{ + .driver = { + .name = STK6D2X_DEV_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = stk_match_table, +#endif + .pm = &stk6d2x_pm_ops, + }, + .probe = stk6d2x_i2c_probe, + .remove = stk6d2x_i2c_remove, + .id_table = stk_als_id, +}; + +static int __init stk6d2x_init(void) +{ + int ret = 0; + + if (flicker_param_lpcharge == 1) + return ret; + + ALS_dbg("start\n"); + ret = i2c_add_driver(&stk_als_driver); + ALS_dbg("Add driver ret = %d\n", ret); + + /** + * i2c_add_driver doesn't return the return value of stk6d2x_probe. + * it doesn't stop with a single probe error to keep trying to probe remaining i2c slave devices. + * + * so, check probe_error and call i2c_del_driver to remove i2c device explicitly. + * without i2c_del_driver, the remaining pm operation cause kernel panic + * __init return should be okay even if probe failure. + * @ref __driver_attach + */ + + if (probe_error) + i2c_del_driver(&stk_als_driver); + + return ret; +} +static void __exit stk6d2x_exit(void) +{ + i2c_del_driver(&stk_als_driver); +} + +module_init(stk6d2x_init); +module_exit(stk6d2x_exit); +MODULE_DEVICE_TABLE(i2c, stk_als_id); +MODULE_SOFTDEP("pre: sensors_core"); +MODULE_AUTHOR("Taka Chiu "); +MODULE_DESCRIPTION("Sensortek stk6d2x ambient Light Sensor driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRIVER_VERSION); diff --git a/drivers/optics/stk6d2x_sec.h b/drivers/optics/stk6d2x_sec.h new file mode 100755 index 000000000000..a06326249b10 --- /dev/null +++ b/drivers/optics/stk6d2x_sec.h @@ -0,0 +1,35 @@ +/* + * + * $Id: stk6d2x_sec.h + * + * Copyright (C) 2012~2018 Bk, sensortek Inc. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + * + */ + +#ifndef __STK6D2X_SEC_H__ +#define __STK6D2X_SEC_H__ + +#include +#include +#include + +// #define SUPPORT_SENSOR_CLASS + +typedef struct stk6d2x_wrapper +{ + struct i2c_manager i2c_mgr; + stk6d2x_data alps_data; + struct device *dev; + struct device *sensor_dev; +#ifdef SUPPORT_SENSOR_CLASS + struct sensors_classdev als_cdev; +#endif + struct input_dev *als_input_dev; + atomic_t recv_reg; +} stk6d2x_wrapper; + +#endif // __STK6D2X_SEC_H__ diff --git a/drivers/optics/stk6d2x_ver.h b/drivers/optics/stk6d2x_ver.h new file mode 100755 index 000000000000..c25a1cc3d3bb --- /dev/null +++ b/drivers/optics/stk6d2x_ver.h @@ -0,0 +1,38 @@ +/** + * @file stk6d2x_ver.h + * + * Copyright (c) 2020, Sensortek. + * All rights reserved. + * + ******************************************************************************/ + +/*============================================================================== + + Change Log: + + EDIT HISTORY FOR FILE + + Nov 17 2022 STK - 1.0.0 + - First Draft Version + - Basic Function + - Flicker 1Hz (1024 data bytes) + Dec 9 2022 STK - 2.0.0 + - Add IT Switch and Checking External Clock Exist or Not + - Check RID + - Add HW/SW Summation + - Add Check XFLAG + - Integrate Gain Ratio Function + - Change FIFO Mode to STA01_STA2_ALS0_ALS1_ALS2 + ============================================================================*/ + +#ifndef _STK6D2X_VER_H +#define _STK6D2X_VER_H + +// 32-bit version number represented as major[31:16].minor[15:8].rev[7:0] +#define STK6D2X_MAJOR 2 +#define STK6D2X_MINOR 0 +#define STK6D2X_REV 0 +#define VERSION_STK6D2X ((STK6D2X_MAJOR<<16) | (STK6D2X_MINOR<<8) | STK6D2X_REV) +#define DRIVER_VERSION "2.0.0" + +#endif //_STK6D2X_VER_H \ No newline at end of file diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index 55c028af4bd9..8ff782166bcf 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -284,4 +284,31 @@ source "drivers/pci/controller/Kconfig" source "drivers/pci/endpoint/Kconfig" source "drivers/pci/switch/Kconfig" +config SEC_PCIE + depends on PCI_MSM + bool "SEC_PCIE feature" + +config SEC_PCIE_DEV + depends on PCI_MSM + bool "SEC_PCIE_DEV dev feature" + +config SEC_PCIE_AER + depends on PCI_MSM + bool "SEC_PCIE_AER feature" + +config SEC_PCIE_L1SS + depends on PCI_MSM + depends on SEC_PCIE + bool "SEC_PCIE_L1SS feature" + +config SEC_PANIC_PCIE_ERR + depends on PCI_MSM + depends on SEC_PCIE + bool "SEC_PANIC_PCIE_ERR feature" + +config SEC_PCIE_KEEP_LINKBW + depends on PCI_MSM + depends on SEC_PCIE + bool "SEC_PCIE_KEEP_LINKBW feature" + endif diff --git a/drivers/pci/controller/pci-msm.c b/drivers/pci/controller/pci-msm.c index 255c9602f4a5..6e7d648c2600 100644 --- a/drivers/pci/controller/pci-msm.c +++ b/drivers/pci/controller/pci-msm.c @@ -46,6 +46,13 @@ #include "../pci.h" +#ifdef CONFIG_SEC_PCIE +#include +#include +#include +#include +#endif + #define PCIE_VENDOR_ID_QCOM (0x17cb) #define PCIE20_PARF_DBI_BASE_ADDR (0x350) @@ -191,6 +198,13 @@ #define PCIE20_TX_CPL_FC_CREDIT_STATUS_OFF (0x738) #define PCIE20_QUEUE_STATUS_OFF (0x73C) +#ifdef CONFIG_SEC_PCIE_AER +#define PCIE20_AER_CAP_REG 0x118 +#define PCIE20_AER_HEADER_LOG_REG 0x11C +#define PCIE20_AER_TLPPREFIX_LOG_REG 0x138 +#define PCI_ERR_TLPPREFIX_LOG 0x38 +#endif + #define RD (0) #define WR (1) #define MSM_PCIE_ERROR (-1) @@ -357,6 +371,12 @@ pr_err("%s: " fmt, __func__, arg); \ } while (0) +#ifdef CONFIG_SEC_PANIC_PCIE_ERR +#define PCIE_SEC_DBG_ERR PCIE_ERR +#else +#define PCIE_SEC_DBG_ERR PCIE_DBG +#endif + #define CHECK_NTN3_VERSION_MASK (0x000000FF) #define NTN3_CHIP_VERSION_1 (0x00000000) @@ -1155,6 +1175,10 @@ struct msm_pcie_dev_t { bool l1_2_aspm_supported; uint32_t l1_2_th_scale; uint32_t l1_2_th_value; +#ifdef CONFIG_SEC_PCIE_L1SS + u32 l1ss_ltr_max_snoop_latency; + u32 l1ss_tpoweron; +#endif bool common_clk_en; bool clk_power_manage_en; bool aux_clk_sync; @@ -1209,6 +1233,9 @@ struct msm_pcie_dev_t { ulong linkdown_counter; ulong link_turned_on_counter; ulong link_turned_off_counter; +#ifdef CONFIG_SEC_PCIE_AER + ulong aer_irq_counter; +#endif uint64_t l23_rdy_poll_timeout; bool suspending; ulong wake_counter; @@ -1279,15 +1306,59 @@ struct msm_pcie_dev_t { u32 l1ss_timeout_us; u32 l1ss_sleep_disable; u32 clkreq_gpio; + struct pci_host_bridge *bridge; bool no_client_based_bw_voting; #if IS_ENABLED(CONFIG_I2C) struct pcie_i2c_ctrl i2c_ctrl; #endif - +#ifdef CONFIG_SEC_PCIE_L1SS + struct mutex l1ss_ctrl_lock; + u32 l1ss_disable_flag; + bool pending_l1ss_ctrl; + bool ep_config_accessible; + bool use_ep_loaded; + bool ep_loaded; +#endif +#ifdef CONFIG_SEC_PCIE + struct workqueue_struct *pcie_error_wq; + struct delayed_work pcie_error_dwork; + u32 pcie_error_defer_ms; + u32 first_pcie_error; + ulong pcie_error; + const char *esoc_name; + /* ssr notification */ + struct notifier_block ssr_nb; + void *ssr_notifier; + bool esoc_crashed; + bool esoc_powerup; + bool ignore_pcie_error; + int subpcb_det_upper_gpio; + int subpcb_det_upper_gpio_level; + int subpcb_det_lower_gpio; + int subpcb_det_lower_gpio_level; +#endif +#ifdef CONFIG_SEC_PANIC_PCIE_ERR + u32 allow_linkup_retry; + u32 remained_linkup_retry; +#endif bool fmd_enable; }; +#ifdef CONFIG_SEC_PCIE +enum pcie_error_t { + PCIE_ERROR_NONE, + PCIE_ERROR_CLK_FAIL, + PCIE_ERROR_PHY_INIT, + PCIE_ERROR_TRAINING_FAIL, + PCIE_ERROR_LINK_SPEED_MISMATCH, + PCIE_ERROR_LINK_FAIL, + PCIE_ERROR_AER, + PCIE_ERROR_LINKDOWN, + PCIE_ERROR_L23_NOT_RECEIVED, +}; +#endif + struct msm_root_dev_t { struct msm_pcie_dev_t *pcie_dev; struct pci_dev *pci_dev; @@ -1466,6 +1537,225 @@ static int msm_pcie_drv_rpmsg_probe(struct rpmsg_device *rpdev); static void msm_pcie_drv_rpmsg_remove(struct rpmsg_device *rpdev); static int msm_pcie_drv_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len, void *priv, u32 src); +#ifdef CONFIG_SEC_PCIE +#define INCREASE_PCIE_VALUE(rc_idx, variable) \ +do { \ + p_health->pcie[rc_idx].variable++; \ + p_health->daily_pcie[rc_idx].variable++; \ +} while (0) + +static ap_health_t *p_health; +static void update_phyinit_fail_count(int rc) +{ + if (unlikely(!p_health)) + p_health = sec_qc_ap_health_data_read(); + + if (p_health) { + INCREASE_PCIE_VALUE(rc, phy_init_fail_cnt); + sec_qc_ap_health_data_write(p_health); + } + + return; +} + +static void update_linkdown_count(int rc) +{ + if (unlikely(!p_health)) + p_health = sec_qc_ap_health_data_read(); + + if (p_health) { + INCREASE_PCIE_VALUE(rc, link_down_cnt); + sec_qc_ap_health_data_write(p_health); + } + + return; +} + +static int pcie_get_cap_list_reg(struct msm_pcie_dev_t *dev, void **found_addr) +{ + u32 current_offset = readl_relaxed(dev->conf + PCIE_CAP_PTR_OFFSET) & 0xFF; + u32 val; + + while (current_offset) { + if (current_offset % 4) { + PCIE_INFO(dev, "wrong align. %d", current_offset); + return -1; + } + + val = readl_relaxed(dev->conf + current_offset); + if ((val & 0xff) == PCIE20_CAP_ID) { + *found_addr = dev->conf + current_offset; + return 0; + } + + current_offset = (val >> 8) & 0xff; + } + + PCIE_INFO(dev, "can't find cap reg. %d", current_offset); + return 1; +} + +static void pcie_get_cur_link_bw(u32 rc_idx, u32 *speed, u32 *width) +{ + struct msm_pcie_dev_t *dev = &msm_pcie_dev[rc_idx]; + u32 val; + u32 offset = PCIE20_CAP + PCI_EXP_LNKSTA; + u32 shift = offset % 4; + + if (shift) + offset = (offset >> 2) << 2; + + val = readl_relaxed(dev->dm_core + offset); + + if (speed) + *speed = (val >> (shift * 8)) & PCI_EXP_LNKSTA_CLS; + + if (width) + *width = ((val >> (shift * 8)) & PCI_EXP_LNKSTA_NLW) >> PCI_EXP_LNKSTA_NLW_SHIFT; + + return; +} + +static u32 pcie_get_target_linkspeed(u32 rc_idx, u32 bus) +{ + struct msm_pcie_dev_t *dev = &msm_pcie_dev[rc_idx]; + u32 val, shift; + u32 offset = PCI_EXP_LNKCTL2; + void *base = NULL; + + if (bus) { + if (pcie_get_cap_list_reg(dev, &base)) + return 0xFF; + } else + base = dev->dm_core + PCIE20_CAP; + + shift = (u32)(((unsigned long)base + offset) % 4UL); + val = readl_relaxed(base + offset - shift); + + return (val >> (shift * 8)) & 0xf; +} + +static u32 pcie_get_max_linkspeed(u32 rc_idx, u32 bus) +{ + struct msm_pcie_dev_t *dev = &msm_pcie_dev[rc_idx]; + u32 val, shift; + u32 offset = PCI_EXP_LNKCAP; + void *base = NULL; + + if (bus) { + if (pcie_get_cap_list_reg(dev, &base)) + return 0xFF; + } else + base = dev->dm_core + PCIE20_CAP; + + shift = (u32)(((unsigned long)base + offset) % 4UL); + val = readl_relaxed(base + offset - shift); + + return (val >> (shift * 8)) & 0xf; +} + +static int msm_pcie_esoc_ssr_notifier(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct msm_pcie_dev_t *dev = container_of(nb, struct msm_pcie_dev_t, ssr_nb); + struct qcom_ssr_notify_data *nd = data; + + PCIE_ERR(dev, "PCIe RC%d: %s notifier (action %d%s)\n", + dev->rc_idx, nd->name ? nd->name : "", action, nd->crashed ? ", crashed" : ""); + + dev->esoc_crashed = nd->crashed; + + if (action == QCOM_SSR_AFTER_POWERUP) + dev->esoc_powerup = true; + else + dev->esoc_powerup = false; + + return NOTIFY_OK; +} + +static bool is_esoc_alive(struct msm_pcie_dev_t *dev) +{ + + if (!dev->esoc_name) + return true; + + if (dev->esoc_crashed) + return false; + + if (!dev->esoc_powerup) + return false; + + return true; +} + +static bool is_need_pcie_error_oops(struct pci_dev *dev, struct msm_pcie_dev_t *msm_dev) +{ + if (msm_dev->ignore_pcie_error) + return false; + + if (msm_dev->pcie_error_wq) { + if (delayed_work_pending(&msm_dev->pcie_error_dwork)) { + PCIE_ERR(msm_dev, "RC%d pcie_error_dwork is already triggered. skip oops.\n", msm_dev->rc_idx); + return false; + } + } + + if (msm_dev->rc_idx == 0 && dev && dev->vendor == PCIE_VENDOR_ID_QCOM) { + /* This is only to avoid "RC0 "PCIe RC0 Fail(L23)" panic where RC0 is used for QC wifi */ + /* QC wifi PBL doesn't handle PME_TURNOFF_MSG. */ + /* Caution : dev is expected as null except "RC0 "PCIe RC0 Fail(L23)" panic case */ + struct mhi_controller *mhi_cntrl; + u32 domain = pci_domain_nr(dev->bus); + u32 bus = dev->bus->number; + u32 dev_id = dev->device; + u32 slot = PCI_SLOT(dev->devfn); + + PCIE_ERR(msm_dev, "RC%d MHI EE Check - Domain %d Bus %d DevID %d Slot %d\n", + msm_dev->rc_idx, domain, bus, dev_id, slot); + + mhi_cntrl = (struct mhi_controller *)dev->dev.driver_data; + if (mhi_cntrl) { + PCIE_ERR(msm_dev, "RC%d MHI EE is %d\n", msm_dev->rc_idx, mhi_cntrl->ee); + + if (mhi_cntrl->ee < MHI_EE_PBL || mhi_cntrl->ee >= MHI_EE_MAX) { + PCIE_ERR(msm_dev, "RC%d skip oops. Invalid mhi_cntrl->ee value\n", msm_dev->rc_idx); + return false; + } + + switch (mhi_cntrl->ee) { + case MHI_EE_PBL: + case MHI_EE_RDDM: + case MHI_EE_DISABLE_TRANSITION: + PCIE_ERR(msm_dev, "RC%d skip oops.\n", msm_dev->rc_idx); + return false; + default: + break; + } + } + } + + return is_esoc_alive(msm_dev); +} +#endif + +#ifdef CONFIG_SEC_PCIE_L1SS +/* for l1ss ctrl */ +struct l1ss_ctrl { + const unsigned int id; + const u32 flag; + const char *name; + const unsigned long timeout; + struct delayed_work dwork; +}; + +static struct l1ss_ctrl l1ss_ctrls[L1SS_MAX] = { + {L1SS_SYSFS, BIT(L1SS_SYSFS), "SYSFS", 0, }, + {L1SS_MST, BIT(L1SS_MST), "MST", 120, }, + {L1SS_AUDIO, BIT(L1SS_AUDIO), "AUDIO", 0, }, +}; +struct workqueue_struct *l1ss_ctrl_wq; +static int sec_pcie_enable_ep_l1(struct pci_dev *pdev, void *data); +#endif static int msm_pcie_drv_send_rpmsg(struct msm_pcie_dev_t *pcie_dev, struct msm_pcie_drv_msg *msg); @@ -3001,6 +3291,255 @@ static struct dentry *dfile_corr_counter_limit; static u32 rc_sel_max; +#ifdef CONFIG_SEC_PCIE_DEV +#define MAX_MSG_LEN (80) +#define MAX_SEC_PHY_TEST_NUM (10) +static struct dentry *dent_sec; +static struct dentry *dfile_sec_phy_test; + +typedef struct { + u32 addr; + u32 value; +} sec_phy_data_t; + +static sec_phy_data_t sec_phy_data[MAX_RC_NUM][MAX_SEC_PHY_TEST_NUM]; + +static void pcie_sec_phy_init(struct msm_pcie_dev_t *dev) +{ + u32 start = dev->res[MSM_PCIE_RES_PHY].resource->start; + u32 size = resource_size(dev->res[MSM_PCIE_RES_PHY].resource); + u32 end = start + size - 4; + int i; + + PCIE_DBG(dev, + "RC%d: %s enter\n", dev->rc_idx, __func__); + + for (i = 0; sec_phy_data[dev->rc_idx][i].addr >= start && + sec_phy_data[dev->rc_idx][i].addr <= end ; i++) { + msm_pcie_write_reg(dev->phy, sec_phy_data[dev->rc_idx][i].addr - start, + sec_phy_data[dev->rc_idx][i].value); + pr_info("PCIE SEC: write 0x%08x <- 0x%08x\n", + sec_phy_data[dev->rc_idx][i].addr, sec_phy_data[dev->rc_idx][i].value); + } +} + +static void pcie_sec_dump(struct msm_pcie_dev_t *dev) +{ + int i; + u32 size; + u32 start = dev->res[MSM_PCIE_RES_PHY].resource->start; + + size = resource_size(dev->res[MSM_PCIE_RES_PHY].resource); + if (size) + PCIE_DUMP(dev, + "------------ PCIe PHY of RC%d PHY DUMP ------------\n", + dev->rc_idx); + + for (i = 0; i < size; i += 32) { + PCIE_DUMP(dev, + "PCIe PHY of RC%d 0x%08x: %08x %08x %08x %08x %08x %08x %08x %08x\n", + dev->rc_idx, i + start, + readl_relaxed(dev->phy + i), + readl_relaxed(dev->phy + (i + 4)), + readl_relaxed(dev->phy + (i + 8)), + readl_relaxed(dev->phy + (i + 12)), + readl_relaxed(dev->phy + (i + 16)), + readl_relaxed(dev->phy + (i + 20)), + readl_relaxed(dev->phy + (i + 24)), + readl_relaxed(dev->phy + (i + 28))); + } +} + +static ssize_t pcie_sec_phy_write(struct file *file, + const char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned long ret; + char str[MAX_MSG_LEN]; + char opt[MAX_MSG_LEN]; + char *pos; + int sec_phy_cur_erase_idx = -1, rc; + size_t i; + static int sec_phy_cur_wr_idx[MAX_RC_NUM] = {0,}; + + memset(str, 0, sizeof(str)); + memset(opt, 0, sizeof(opt)); + ret = copy_from_user(str, buf, sizeof(str)-1); + if (ret || strlen(str) == 0) { + return -EFAULT; + } + + str[strlen(str) - 1] = '\0'; + pr_info("PCIE SEC: input(%s).\n", str); + + if (sscanf(str, "%c", opt) != 1) { + pr_err("PCIE SEC: first parameter (%c) is wrong.\n", opt[0]); + return -EINVAL; + } + pos = &str[0] + 2; + rc = pos[0] - '0'; + if (rc < 0 || rc > MAX_RC_NUM - 1 || pos[1] != ' ') { + pr_err("PCIE SEC: 2nd parameter (%c) is wrong.\n", pos[0]); + return -EINVAL; + } + + pos = &pos[0] + 2; + + switch (opt[0]) { + case 'w': + case 'W': + if (sec_phy_cur_wr_idx[rc] >= MAX_SEC_PHY_TEST_NUM) { + pr_err("PCIE SEC: no space.\n"); + return -ENOSPC; + } + + for (i = 0; i < strlen(pos); i++) + if ((pos[i] >= '0' && pos[i] <= '9') || + (pos[i] >= 'A' && pos[i] <= 'F') || + (pos[i] >= 'a' && pos[i] <= 'f')) + break; + + if (i == strlen(pos)) { + pr_err("PCIE SEC: invalid addr.\n"); + return -EINVAL; + } + pos = pos + i; + sec_phy_data[rc][sec_phy_cur_wr_idx[rc]].addr = + (u32)simple_strtoul(pos, &pos, 16); + + for (i = 0; i < strlen(pos); i++) + if ((pos[i] >= '0' && pos[i] <= '9') || + (pos[i] >= 'A' && pos[i] <= 'F') || + (pos[i] >= 'a' && pos[i] <= 'f')) + break; + + if (i == strlen(pos)) { + pr_err("PCIE SEC: invalid value.\n"); + return -EINVAL; + } + pos = pos + i; + sec_phy_data[rc][sec_phy_cur_wr_idx[rc]].value = + (u32)simple_strtoul(pos, 0, 16); + + pr_info("PCIE SEC: wr buff[%d] 0x%08x 0x%08x.\n", + sec_phy_cur_wr_idx[rc], + sec_phy_data[rc][sec_phy_cur_wr_idx[rc]].addr, + sec_phy_data[rc][sec_phy_cur_wr_idx[rc]].value); + + sec_phy_cur_wr_idx[rc]++; + break; + case 'e': + case 'E': + if (pos[0] == 'a' || pos[0] == 'A') { + pr_info("PCIE SEC: erase all buff\n"); + for (i = 0; i < MAX_SEC_PHY_TEST_NUM; i++) { + sec_phy_data[rc][i].addr = + sec_phy_data[rc][i].value = 0; + sec_phy_cur_wr_idx[rc] = 0; + } + goto out; + } + ret = sscanf(pos, " %u", &sec_phy_cur_erase_idx); + if (ret != 1) + return -EINVAL; + + if (sec_phy_cur_erase_idx < 0 || + sec_phy_cur_erase_idx >= MAX_SEC_PHY_TEST_NUM) { + pr_err("PCIE SEC: erase idx is wrong.\n"); + return -EINVAL; + } + + sec_phy_data[rc][sec_phy_cur_erase_idx].addr = 0; + sec_phy_data[rc][sec_phy_cur_erase_idx].value = 0; + + if (sec_phy_cur_erase_idx+1 < MAX_SEC_PHY_TEST_NUM) { + for (i = sec_phy_cur_erase_idx+1; + i < sec_phy_cur_wr_idx[rc]; i++) { + sec_phy_data[rc][i-1].addr = sec_phy_data[rc][i].addr; + sec_phy_data[rc][i-1].value = sec_phy_data[rc][i].value; + sec_phy_data[rc][i].addr = sec_phy_data[rc][i].value = 0; + } + } + + sec_phy_cur_wr_idx[rc]--; + if (sec_phy_cur_wr_idx[rc] < 0) + sec_phy_cur_wr_idx[rc] = 0; + break; + } +out: + return count; +} + +static ssize_t pcie_sec_phy_read(struct file *file, + char __user *buf, + size_t count, loff_t *ppos) +{ + char str[MAX_SEC_PHY_TEST_NUM*MAX_MSG_LEN] = {0,}; + int rc, i, offset; + size_t cnt = 0, ret; + loff_t lpos; + + if (*ppos != 0) + return 0; + + for (rc = 0; rc < MAX_RC_NUM; rc++) { + offset = 0; + lpos = 0; + + offset += sprintf(str+offset, "++ RC%d ++\n", rc); + for (i = 0; i < MAX_SEC_PHY_TEST_NUM; i++) { + offset += sprintf(str+offset, "%02d %08x %08x\n", + i, sec_phy_data[rc][i].addr, sec_phy_data[rc][i].value); + } + offset += sprintf(str+offset, "----- RC%d -----\n\n", rc); + ret = simple_read_from_buffer(buf + cnt, offset, &lpos, str, sizeof(str)); + if (ret < 0) + break; + + cnt += offset; + } + + *ppos += cnt; + return cnt; +} + +const struct file_operations pcie_sec_debug_phy_ops = { + .write = pcie_sec_phy_write, + .read = pcie_sec_phy_read, +}; + +static void pcie_sec_debugfs_init(void) +{ + if (!dent_msm_pcie) { + pr_err("PCIE SEC: skip to create the folder for debug_fs.\n"); + return; + } + + dent_sec = debugfs_create_dir("sec", dent_msm_pcie); + if (IS_ERR(dent_sec)) { + pr_err("PCIE SEC: fail to create the folder for debug_fs.\n"); + return; + } + + dfile_sec_phy_test = debugfs_create_file("phy_test", 0644, dent_sec, 0, + &pcie_sec_debug_phy_ops); + if (!dfile_sec_phy_test || IS_ERR(dfile_sec_phy_test)) { + pr_err("PCIE SEC: fail to create the file for debug_fs.\n"); + goto phy_err; + } + return; + +phy_err: + debugfs_remove(dent_sec); +} + +static void pcie_sec_debugfs_exit(void) +{ + debugfs_remove(dfile_sec_phy_test); + debugfs_remove(dent_sec); +} +#endif + static int msm_pcie_debugfs_parse_input(const char __user *buf, size_t count, unsigned int *data) { @@ -3498,6 +4037,9 @@ static void msm_pcie_debugfs_init(void) pr_err("PCIe: fail to create the file for debug_fs corr_counter_limit.\n"); goto err; } +#ifdef CONFIG_SEC_PCIE_DEV + pcie_sec_debugfs_init(); +#endif return; err: debugfs_remove_recursive(dent_msm_pcie); @@ -3506,8 +4048,20 @@ static void msm_pcie_debugfs_init(void) static void msm_pcie_debugfs_exit(void) { debugfs_remove_recursive(dent_msm_pcie); +#ifdef CONFIG_SEC_PCIE_DEV + pcie_sec_debugfs_exit(); +#endif } #else +#ifdef CONFIG_SEC_PCIE_DEV +static void pcie_sec_debugfs_init(void) +{ +} + +static void pcie_sec_debugfs_exit(void) +{ +} +#endif static void msm_pcie_debugfs_init(void) { } @@ -4735,11 +5289,19 @@ static bool pcie_phy_is_ready(struct msm_pcie_dev_t *dev) return true; } +#ifdef CONFIG_SEC_PCIE_DEV +static void pcie_sec_phy_init(struct msm_pcie_dev_t *dev); +#endif + static int pcie_phy_init(struct msm_pcie_dev_t *dev) { int i, ret; long retries = 0; struct msm_pcie_phy_info_t *phy_seq; +#ifdef CONFIG_SEC_PCIE + static int max_retries[MAX_RC_NUM]; + static long int total_enable_cnt[MAX_RC_NUM]; +#endif PCIE_DBG(dev, "PCIe: RC%d: Initializing PHY\n", dev->rc_idx); @@ -4756,6 +5318,9 @@ static int pcie_phy_init(struct msm_pcie_dev_t *dev) phy_seq++; } } +#ifdef CONFIG_SEC_PCIE_DEV + pcie_sec_phy_init(dev); +#endif usleep_range(PHY_STABILIZATION_DELAY_US_MIN, PHY_STABILIZATION_DELAY_US_MAX); @@ -4781,16 +5346,31 @@ static int pcie_phy_init(struct msm_pcie_dev_t *dev) REFCLK_STABILIZATION_DELAY_US_MAX); } while (retries < PHY_READY_TIMEOUT_COUNT); +#ifdef CONFIG_SEC_PCIE + if (max_retries[dev->rc_idx] < retries) + max_retries[dev->rc_idx] = retries; + total_enable_cnt[dev->rc_idx]++; + + PCIE_ERR(dev, "RC%d: number of PHY retries:%ld(Max:%d, Total:%ld).\n", + dev->rc_idx, retries, max_retries[dev->rc_idx], total_enable_cnt[dev->rc_idx]); +#else PCIE_DBG(dev, "PCIe: RC%d: number of PHY retries: %ld.\n", dev->rc_idx, retries); +#endif if (!pcie_phy_is_ready(dev)) { PCIE_ERR(dev, "PCIe PHY RC%d failed to come up!\n", dev->rc_idx); +#ifdef CONFIG_SEC_PCIE + update_phyinit_fail_count(dev->rc_idx); +#endif pcie_phy_dump(dev); return -ENODEV; } +#ifdef CONFIG_SEC_PCIE_DEV + pcie_sec_dump(dev); +#endif PCIE_INFO(dev, "PCIe RC%d PHY is ready!\n", dev->rc_idx); return 0; @@ -5301,6 +5881,57 @@ static int msm_pcie_get_bw_scale(struct msm_pcie_dev_t *pcie_dev) return 0; } +#ifdef CONFIG_SEC_PCIE +static int msm_pcie_get_phy_override(struct msm_pcie_dev_t *pcie_dev, int size) +{ + + int ret, size_ovr; + struct platform_device *pdev = pcie_dev->pdev; + struct msm_pcie_phy_info_t *old_sequence; + const __be32 *prop; + + prop = of_get_property(pdev->dev.of_node, "qcom,phy-sequence-override", &size_ovr); + if (!prop || !size_ovr) { + PCIE_DBG(pcie_dev, + "PCIe: RC%d: phy sequence override is not present in DT\n", + pcie_dev->rc_idx); + return 0; + } + + old_sequence = pcie_dev->phy_sequence; + pcie_dev->phy_sequence = devm_kzalloc(&pdev->dev, size + size_ovr, GFP_KERNEL); + if (!pcie_dev->phy_sequence) { + pcie_dev->phy_sequence = old_sequence; + + PCIE_DBG(pcie_dev, + "PCIe: RC%d: phy sequence override devm_kzalloc fail\n", + pcie_dev->rc_idx); + return 0; + } + + memcpy(pcie_dev->phy_sequence, old_sequence, size); + pcie_dev->phy_len += size_ovr / ((unsigned int)sizeof(*pcie_dev->phy_sequence)); + ret = of_property_read_u32_array(pdev->dev.of_node, + "qcom,phy-sequence-override", + (unsigned int *)(pcie_dev->phy_sequence + + (size / ((unsigned int)sizeof(*pcie_dev->phy_sequence)))), + size_ovr / sizeof(pcie_dev->phy_sequence->offset)); + if (ret) { + devm_kfree(&pdev->dev, pcie_dev->phy_sequence); + pcie_dev->phy_sequence = old_sequence; + pcie_dev->phy_len -= size_ovr / ((unsigned int)sizeof(*pcie_dev->phy_sequence)); + + PCIE_DBG(pcie_dev, + "PCIe: RC%d: phy sequence override is not loaded\n", + pcie_dev->rc_idx); + return 0; + } + + devm_kfree(&pdev->dev, old_sequence); + return 0; +} +#endif + static int msm_pcie_get_phy(struct msm_pcie_dev_t *pcie_dev) { int ret, size = 0; @@ -5326,7 +5957,9 @@ static int msm_pcie_get_phy(struct msm_pcie_dev_t *pcie_dev) size / sizeof(pcie_dev->phy_sequence->offset)); if (ret) return -EINVAL; - +#ifdef CONFIG_SEC_PCIE + msm_pcie_get_phy_override(pcie_dev, size); +#endif return 0; } @@ -5779,11 +6412,17 @@ static int msm_pcie_link_train(struct msm_pcie_dev_t *dev) /* configure PCIe preset */ msm_pcie_config_core_preset(dev); - if (dev->target_link_speed) + if (dev->target_link_speed) { +#ifdef CONFIG_SEC_PCIE + if (dev->target_link_speed < GEN1_SPEED) + dev->target_link_speed = GEN1_SPEED; + if (dev->target_link_speed > GEN3_SPEED) + dev->target_link_speed = GEN3_SPEED; +#endif msm_pcie_write_reg_field(dev->dm_core, PCIE20_CAP + PCI_EXP_LNKCTL2, PCI_EXP_LNKCTL2_TLS, dev->target_link_speed); - + } /* set max tlp read size */ msm_pcie_write_reg_field(dev->dm_core, PCIE20_DEVICE_CONTROL_STATUS, 0x7000, dev->tlp_rd_size); @@ -5866,7 +6505,9 @@ static int msm_pcie_check_ep_access(struct msm_pcie_dev_t *dev, unsigned long ep_up_timeout) { int ret = 0; - +#ifdef CONFIG_SEC_PCIE + u32 link_speed[3] = {0,}, link_width = 0; +#endif /* check endpoint configuration space is accessible */ while (time_before(jiffies, ep_up_timeout)) { if (readl_relaxed(dev->conf) != PCIE_LINK_DOWN) @@ -5878,6 +6519,31 @@ static int msm_pcie_check_ep_access(struct msm_pcie_dev_t *dev, PCIE_DBG(dev, "PCIe: RC%d: endpoint config space is accessible\n", dev->rc_idx); +#ifdef CONFIG_SEC_PCIE_L1SS + dev->ep_config_accessible = true; +#endif +#ifdef CONFIG_SEC_PCIE + PCIE_INFO(dev, "PCIe RC%d Max GEN%d, EP GEN%d\n", + dev->rc_idx, pcie_get_max_linkspeed(dev->rc_idx, 0), + pcie_get_max_linkspeed(dev->rc_idx, 1)); + link_speed[0] = pcie_get_target_linkspeed(dev->rc_idx, 0); + link_speed[1] = pcie_get_target_linkspeed(dev->rc_idx, 1); + PCIE_INFO(dev, "PCIe RC%d Target GEN%d, EP GEN%d\n", + dev->rc_idx, link_speed[0], link_speed[1]); + pcie_get_cur_link_bw(dev->rc_idx, &link_speed[2], &link_width); + PCIE_INFO(dev, "PCIe RC%d Current GEN%d, %d lanes\n", + dev->rc_idx, link_speed[2], link_width); + if (link_speed[0] != link_speed[2]) { + set_bit(PCIE_ERROR_LINK_SPEED_MISMATCH, &dev->pcie_error); +#ifdef CONFIG_SEC_PANIC_PCIE_ERR + panic("PCIe: RC%d: link speed fail(GEN%d -> %d)\n", + dev->rc_idx, link_speed[0], link_speed[2]); +#endif + } +#ifdef CONFIG_SEC_PANIC_PCIE_ERR + dev->remained_linkup_retry = dev->allow_linkup_retry; +#endif +#endif } else { PCIE_ERR(dev, "PCIe: RC%d: endpoint config space is not accessible\n", @@ -5885,6 +6551,12 @@ static int msm_pcie_check_ep_access(struct msm_pcie_dev_t *dev, dev->link_status = MSM_PCIE_LINK_DISABLED; dev->power_on = false; dev->link_turned_off_counter++; +#ifdef CONFIG_SEC_PANIC_PCIE_ERR + dev->remained_linkup_retry = 0; +#endif +#ifdef CONFIG_SEC_PCIE + set_bit(PCIE_ERROR_LINK_FAIL, &dev->pcie_error); +#endif ret = -ENODEV; } @@ -6179,8 +6851,15 @@ static int msm_pcie_enable_link(struct msm_pcie_dev_t *dev) /* init PCIe PHY */ ret = pcie_phy_init(dev); - if (ret) + if (ret) { +#ifdef CONFIG_SEC_PANIC_PCIE_ERR + dev->remained_linkup_retry = 0; +#endif +#ifdef CONFIG_SEC_PCIE + set_bit(PCIE_ERROR_PHY_INIT, &dev->pcie_error); +#endif return ret; + } /* switch phy aux clock source from xo to phy aux clk */ if (dev->phy_aux_clk_mux && dev->phy_aux_clk_ext_src) @@ -6232,8 +6911,16 @@ static int msm_pcie_enable_link(struct msm_pcie_dev_t *dev) #endif ret = msm_pcie_link_train(dev); - if (ret) + if (ret) { +#ifdef CONFIG_SEC_PCIE + set_bit(PCIE_ERROR_TRAINING_FAIL, &dev->pcie_error); +#endif +#ifdef CONFIG_SEC_PANIC_PCIE_ERR + if (dev->remained_linkup_retry) + dev->remained_linkup_retry--; +#endif return ret; + } dev->link_status = MSM_PCIE_LINK_ENABLED; dev->power_on = true; @@ -6353,8 +7040,14 @@ static int msm_pcie_enable(struct msm_pcie_dev_t *dev) ret = msm_pcie_clk_init(dev); /* ensure that changes propagated to the hardware */ wmb(); - if (ret) + if (ret) { +#ifdef CONFIG_SEC_PCIE + set_bit(PCIE_ERROR_CLK_FAIL, &dev->pcie_error); +#endif + PCIE_ERR(dev, "PCIe: failed to msm_pcie_clk_init(). RC%d, ret=%d\n", + dev->rc_idx, ret); goto clk_fail; + } /* Use CESTA to turn on the resources */ ret = msm_pcie_enable_cesta(dev); @@ -6402,6 +7095,27 @@ static int msm_pcie_enable(struct msm_pcie_dev_t *dev) goto out; link_fail: +#ifdef CONFIG_SEC_PANIC_PCIE_ERR + if (!dev->remained_linkup_retry) { + if (is_need_pcie_error_oops(NULL, dev)) { + if (dev->pcie_error_wq) { + if (dev->first_pcie_error == PCIE_ERROR_NONE) { + dev->first_pcie_error = PCIE_ERROR_LINK_FAIL; + PCIE_ERR(dev, "PCIe RC%d link fail. call delayed work.\n", dev->rc_idx); + queue_delayed_work(dev->pcie_error_wq, + &dev->pcie_error_dwork, msecs_to_jiffies(dev->pcie_error_defer_ms)); + } else { + PCIE_ERR(dev, "PCIe RC%d link fail.\n", dev->rc_idx); + } + } else { + panic("PCIe RC%d link fail!\n", dev->rc_idx); + } + } + } else { + PCIE_ERR(dev, "PCIe RC%d link fail. Remained retry %d\n", + dev->rc_idx, dev->remained_linkup_retry); + } +#endif if (msm_pcie_keep_resources_on & BIT(dev->rc_idx)) goto out; @@ -6420,7 +7134,11 @@ static int msm_pcie_enable(struct msm_pcie_dev_t *dev) msm_pcie_clk_deinit(dev); clk_fail: - +#ifdef CONFIG_SEC_PANIC_PCIE_ERR + if (test_bit(PCIE_ERROR_CLK_FAIL, &dev->pcie_error) + && is_need_pcie_error_oops(NULL, dev)) + panic("PCIe RC%d clk fail!\n", dev->rc_idx); +#endif msm_pcie_gdsc_deinit(dev); gdsc_fail: @@ -6464,6 +7182,10 @@ static void msm_pcie_disable(struct msm_pcie_dev_t *dev) dev->i2c_ctrl.client_i2c_reset(&dev->i2c_ctrl, true); #endif +#ifdef CONFIG_SEC_PCIE_L1SS + dev->ep_config_accessible = false; +#endif + PCIE_INFO(dev, "PCIe: Assert the reset of endpoint of RC%d.\n", dev->rc_idx); @@ -6691,7 +7413,17 @@ int msm_pcie_enumerate(u32 rc_idx) pci_host_probe(bridge); dev->enumerated = true; - +#ifdef CONFIG_SEC_PCIE + if (dev->esoc_name) { + dev->ssr_notifier = qcom_register_ssr_notifier(dev->esoc_name, &dev->ssr_nb); + if (IS_ERR(dev->ssr_notifier)) { + PCIE_ERR(dev, "PCIe: RC%d: %s: failed to register ssr notifier\n", + dev->rc_idx, dev->esoc_name); + dev->ssr_notifier = NULL; + } + dev->ssr_nb.notifier_call = msm_pcie_esoc_ssr_notifier; + } +#endif if (dev->drv_supported) schedule_work(&pcie_drv.drv_connect); @@ -7229,6 +7961,9 @@ static void msm_handle_error_source(struct pci_dev *dev, int aer = dev->aer_cap; struct msm_pcie_dev_t *rdev = info->rdev; u32 status, sev; +#ifdef CONFIG_SEC_PCIE_AER + static int aer_counter; +#endif if (!rdev->aer_dump && !rdev->suspending && rdev->link_status == MSM_PCIE_LINK_ENABLED) { @@ -7285,9 +8020,26 @@ static void msm_handle_error_source(struct pci_dev *dev, PCI_EXP_DEVSTA_CED | PCI_EXP_DEVSTA_NFED | PCI_EXP_DEVSTA_FED); +#ifdef CONFIG_SEC_PCIE_AER + if (aer_counter >= corr_counter_limit) + panic("AER error severity %d\n", info->severity); + else { + pr_err("AER error severity %d, aer_counter=%d\n", info->severity, aer_counter); + aer_counter++; + } +#endif } else { /* AER_FATAL */ +#ifdef CONFIG_SEC_PCIE_AER + if (aer_counter >= corr_counter_limit) + panic("AER error severity %d\n", info->severity); + else { + pr_err("AER error severity %d, aer_counter=%d\n", info->severity, aer_counter); + aer_counter++; + } +#else panic("AER error severity %d\n", info->severity); +#endif } pci_dev_put(dev); @@ -7445,6 +8197,10 @@ static irqreturn_t handle_aer_irq(int irq, void *data) { struct msm_pcie_dev_t *dev = data; struct aer_err_source e_src; +#ifdef CONFIG_SEC_PCIE_AER + dev->aer_irq_counter++; + +#endif if (kfifo_is_empty(&dev->aer_fifo)) return IRQ_NONE; @@ -7466,6 +8222,13 @@ static irqreturn_t handle_aer_irq(int irq, void *data) } done: +#if defined(CONFIG_SEC_PANIC_PCIE_ERR) && defined(CONFIG_SEC_PCIE_AER) + if (!dev->ignore_pcie_error && dev->aer_irq_counter >= corr_counter_limit) { + panic("PCIe RC%d AER detect(%lu)!\n", + dev->rc_idx, dev->aer_irq_counter); + } +#endif + return IRQ_HANDLED; } @@ -7564,6 +8327,24 @@ static void msm_pcie_handle_linkdown(struct msm_pcie_dev_t *dev) if (dev->link_status == MSM_PCIE_LINK_DOWN) return; +#ifdef CONFIG_SEC_PCIE + if (dev->linkdown_panic) { + if (is_need_pcie_error_oops(NULL, dev) && dev->pcie_error_wq) { + if (dev->first_pcie_error == PCIE_ERROR_NONE) { + dev->first_pcie_error = PCIE_ERROR_LINKDOWN; + queue_delayed_work(dev->pcie_error_wq, + &dev->pcie_error_dwork, msecs_to_jiffies(dev->pcie_error_defer_ms)); + } + } + } + + if (!is_esoc_alive(dev)) { + PCIE_ERR(dev, "PCIe RC%d linkdown caused by esoc crash.\n", dev->rc_idx); + } else { + set_bit(PCIE_ERROR_LINKDOWN, &dev->pcie_error); + update_linkdown_count(dev->rc_idx); + } +#endif dev->link_status = MSM_PCIE_LINK_DOWN; /* Linkdown is expected. As it must be due to card removal action. So return */ @@ -7573,17 +8354,27 @@ static void msm_pcie_handle_linkdown(struct msm_pcie_dev_t *dev) return; } +#ifdef CONFIG_SEC_PCIE + if (sec_debug_is_enabled()) { +#endif if (!dev->suspending && !dev->fmd_enable) { /* PCIe registers dump on link down */ PCIE_DUMP(dev, "PCIe:Linkdown IRQ for RC%d Dumping PCIe registers\n", dev->rc_idx); +#ifdef CONFIG_SEC_PCIE + if (dev->linkdown_panic) { +#endif pcie_phy_dump(dev); pcie_parf_dump(dev); pcie_dm_core_dump(dev); pcie_sm_dump(dev); pcie_crm_dump(dev); } +#ifdef CONFIG_SEC_PCIE + } + } +#endif /* Attempt link-down recovery instead of PERST if supported */ if (dev->linkdown_recovery_enable) { @@ -7599,12 +8390,41 @@ static void msm_pcie_handle_linkdown(struct msm_pcie_dev_t *dev) PCIE_ERR(dev, "PCIe link is down for RC%d\n", dev->rc_idx); - if (dev->linkdown_panic) + if (dev->linkdown_panic) { +#ifdef CONFIG_SEC_PCIE + if (!dev->pcie_error_wq) { + if (is_need_pcie_error_oops(NULL, dev)) + panic("PCIe RC%d : User has chosen to panic on linkdown\n", dev->rc_idx); + else + PCIE_ERR(dev, "PCIe RC%d linkdown caused by esoc crash.\n", dev->rc_idx); + } +#else panic("User has chosen to panic on linkdown\n"); +#endif + } msm_pcie_notify_client(dev, MSM_PCIE_EVENT_LINKDOWN); } +#ifdef CONFIG_SEC_PCIE +static void sec_pcie_error_worker(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct msm_pcie_dev_t *dev + = container_of(dwork, struct msm_pcie_dev_t, pcie_error_dwork); + + switch (dev->first_pcie_error) { + case PCIE_ERROR_LINKDOWN: + panic("PCIe RC%d : User has chosen to deferred panic on linkdown\n", + dev->rc_idx); + break; + case PCIE_ERROR_LINK_FAIL: + panic("PCIe RC%d link fail!\n", dev->rc_idx); + break; + } +} +#endif + static irqreturn_t handle_linkdown_irq(int irq, void *data) { struct msm_pcie_dev_t *dev = data; @@ -8092,6 +8912,14 @@ static int msm_pcie_config_l1_enable(struct pci_dev *pdev, void *dev) static void msm_pcie_config_l1_enable_all(struct msm_pcie_dev_t *dev) { +#ifdef CONFIG_SEC_PCIE_L1SS + /* make sure caller has setup_lock */ + if (dev->prevent_l1) { + PCIE_INFO(dev, "PCIe: RC%d: skip. reason - prevent_l1(%d)\n", + dev->rc_idx, dev->prevent_l1); + return; + } +#endif if (dev->l1_supported) pci_walk_bus(dev->dev->bus, msm_pcie_config_l1_enable, dev); } @@ -8102,6 +8930,10 @@ static void msm_pcie_config_l1ss(struct msm_pcie_dev_t *dev, u32 val, val2; u32 l1ss_cap_id_offset, l1ss_ctl1_offset; u32 devctl2_offset = pdev->pcie_cap + PCI_EXP_DEVCTL2; +#ifdef CONFIG_SEC_PCIE_L1SS + u32 l1ss_ctl2_offset, l1ss_ltr_cap_id_offset; + u32 bus_num = pdev->bus->number; +#endif PCIE_DBG(dev, "PCIe: RC%d: PCI device %02x:%02x.%01x %s\n", dev->rc_idx, pdev->bus->number, PCI_SLOT(pdev->devfn), @@ -8118,6 +8950,18 @@ static void msm_pcie_config_l1ss(struct msm_pcie_dev_t *dev, l1ss_ctl1_offset = l1ss_cap_id_offset + PCI_L1SS_CTL1; +#ifdef CONFIG_SEC_PCIE_L1SS + l1ss_ctl2_offset = l1ss_cap_id_offset + PCI_L1SS_CTL2; + l1ss_ltr_cap_id_offset = + pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_LTR); + if (!pci_is_root_bus(pdev->bus) && !l1ss_ltr_cap_id_offset) { + PCIE_DBG(dev, + "PCIe: RC%d:%d: PCI device does not support LTR\n", + dev->rc_idx, bus_num); + return; + } +#endif + /* Enable the AUX Clock and the Core Clk to be synchronous for L1ss */ if (pci_is_root_bus(pdev->bus) && !dev->aux_clk_sync) { if (enable) @@ -8129,6 +8973,19 @@ static void msm_pcie_config_l1ss(struct msm_pcie_dev_t *dev, } if (enable) { +#ifdef CONFIG_SEC_PCIE_L1SS + if (!pci_is_root_bus(pdev->bus) + && dev->l1ss_ltr_max_snoop_latency + && l1ss_ltr_cap_id_offset) { + msm_pcie_config_clear_set_dword(pdev, + l1ss_ltr_cap_id_offset + PCI_LTR_MAX_SNOOP_LAT, + 0, dev->l1ss_ltr_max_snoop_latency); + } + if (dev->l1ss_tpoweron) { + msm_pcie_config_clear_set_dword(pdev, + l1ss_ctl2_offset, 0, dev->l1ss_tpoweron); + } +#endif msm_pcie_config_clear_set_dword(pdev, devctl2_offset, 0, PCI_EXP_DEVCTL2_LTR_EN); @@ -8153,9 +9010,24 @@ static void msm_pcie_config_l1ss(struct msm_pcie_dev_t *dev, pci_read_config_dword(pdev, l1ss_ctl1_offset, &val); PCIE_DBG2(dev, "PCIe: RC%d: L1SUB_CONTROL1:0x%x\n", dev->rc_idx, val); +#ifdef CONFIG_SEC_PCIE_L1SS + pci_read_config_dword(pdev, l1ss_ctl2_offset, &val); + PCIE_DBG2(dev, "PCIe: RC%d:%d: L1SUB_CONTROL2:0x%x\n", + dev->rc_idx, bus_num, val); +#endif + pci_read_config_dword(pdev, devctl2_offset, &val2); PCIE_DBG2(dev, "PCIe: RC%d: DEVICE_CONTROL2_STATUS2::0x%x\n", dev->rc_idx, val2); + +#ifdef CONFIG_SEC_PCIE_L1SS + if (l1ss_ltr_cap_id_offset) { + pci_read_config_dword(pdev, + l1ss_ltr_cap_id_offset + PCI_LTR_MAX_SNOOP_LAT, &val2); + PCIE_DBG2(dev, "PCIe: RC%d:%d: LTR_MAX_SNOOP_LAT:0x%x\n", + dev->rc_idx, bus_num, val2); + } +#endif } static int msm_pcie_config_l1ss_disable(struct pci_dev *pdev, void *dev) @@ -8243,7 +9115,18 @@ static void msm_pcie_config_link_pm(struct msm_pcie_dev_t *dev, bool enable) if (enable) { msm_pcie_config_common_clock_enable_all(dev); msm_pcie_config_clock_power_management_enable_all(dev); +#ifdef CONFIG_SEC_PCIE_L1SS + mutex_lock(&dev->l1ss_ctrl_lock); + if (dev->pending_l1ss_ctrl && dev->l1ss_disable_flag) { + msm_pcie_config_l1ss_disable_all(dev, bus); + } else { +#endif msm_pcie_config_l1ss_enable_all(dev); +#ifdef CONFIG_SEC_PCIE_L1SS + } + dev->pending_l1ss_ctrl = false; + mutex_unlock(&dev->l1ss_ctrl_lock); +#endif msm_pcie_config_l1_enable_all(dev); msm_pcie_config_l0s_enable_all(dev); } else { @@ -8495,6 +9378,79 @@ static void msm_pcie_read_dt(struct msm_pcie_dev_t *pcie_dev, int rc_idx, pcie_dev->rc_idx, pcie_dev->l1_2_th_scale, pcie_dev->l1_2_th_value); +#ifdef CONFIG_SEC_PCIE_L1SS + ret = of_property_read_u32((&pdev->dev)->of_node, + "l1ss-ltr-max-snoop-latency", + &pcie_dev->l1ss_ltr_max_snoop_latency); + if (ret) { + PCIE_DBG(pcie_dev, + "RC%d: l1ss-ltr-max-snoop-latency is not found.\n", pcie_dev->rc_idx); + } else { + PCIE_DBG(pcie_dev, + "RC%d: l1ss-ltr-max-snoop-latency = 0x%x\n", pcie_dev->rc_idx, + pcie_dev->l1ss_ltr_max_snoop_latency); + } + ret = of_property_read_u32((&pdev->dev)->of_node, + "l1ss-tpoweron", + &pcie_dev->l1ss_tpoweron); + if (ret) { + PCIE_DBG(pcie_dev, + "RC%d: l1ss-tpoweron is not found.\n", pcie_dev->rc_idx); + } else { + PCIE_DBG(pcie_dev, + "RC%d: l1ss-tpoweron = 0x%x\n", pcie_dev->rc_idx, + pcie_dev->l1ss_tpoweron); + } + + pcie_dev->use_ep_loaded = false; + pcie_dev->ep_loaded = false; +#endif + +#ifdef CONFIG_SEC_PCIE + pcie_dev->ignore_pcie_error = + of_property_read_bool((&pdev->dev)->of_node, + "ignore-pcie-error"); + PCIE_DBG(pcie_dev, "RC%d ignore-pcie-error is %s set.\n", + pcie_dev->rc_idx, pcie_dev->ignore_pcie_error ? "" : "not"); + + ret = of_property_read_string(of_node, "esoc-name", + &pcie_dev->esoc_name); + if (ret) + pcie_dev->esoc_name = NULL; + + PCIE_DBG(pcie_dev, "RC%d esoc-name is %s\n", + pcie_dev->rc_idx, pcie_dev->esoc_name ? pcie_dev->esoc_name : "NULL"); + + ret = of_get_named_gpio(of_node, "subpcb-det-upper-gpio", 0); + if (ret >= 0) { + pcie_dev->subpcb_det_upper_gpio = ret; + PCIE_DBG(pcie_dev, "RC%d subpcb_det_upper_gpio:%d\n", + pcie_dev->rc_idx, pcie_dev->subpcb_det_upper_gpio); + pcie_dev->subpcb_det_upper_gpio_level = gpio_get_value( + pcie_dev->subpcb_det_upper_gpio); + } else { + pcie_dev->subpcb_det_upper_gpio = -1; + pcie_dev->subpcb_det_upper_gpio_level = 0; + } + + ret = of_get_named_gpio(of_node, "subpcb-det-lower-gpio", 0); + if (ret >= 0) { + pcie_dev->subpcb_det_lower_gpio = ret; + PCIE_DBG(pcie_dev, "RC%d subpcb_det_lower_gpio:%d\n", + pcie_dev->rc_idx, pcie_dev->subpcb_det_lower_gpio); + pcie_dev->subpcb_det_lower_gpio_level = gpio_get_value( + pcie_dev->subpcb_det_lower_gpio); + } else { + pcie_dev->subpcb_det_lower_gpio = -1; + pcie_dev->subpcb_det_lower_gpio_level = 0; + } + + PCIE_ERR(pcie_dev, "RC%d subpcb_det_upper_gpio is %s\n", pcie_dev->rc_idx, + pcie_dev->subpcb_det_upper_gpio_level ? "High" : "Low"); + PCIE_ERR(pcie_dev, "RC%d subpcb_det_lower_gpio is %s\n", pcie_dev->rc_idx, + pcie_dev->subpcb_det_lower_gpio_level ? "High" : "Low"); +#endif + pcie_dev->common_clk_en = of_property_read_bool(of_node, "qcom,common-clk-en"); PCIE_DBG(pcie_dev, "Common clock is %s enabled.\n", @@ -8538,6 +9494,15 @@ static void msm_pcie_read_dt(struct msm_pcie_dev_t *pcie_dev, int rc_idx, pcie_dev->rc_idx, pcie_dev->dt_target_link_speed); pcie_dev->target_link_speed = pcie_dev->dt_target_link_speed; +#ifdef CONFIG_SEC_PCIE + if (pcie_dev->subpcb_det_upper_gpio_level && + !pcie_dev->subpcb_det_lower_gpio_level) { + pcie_dev->target_link_speed = GEN1_SPEED; + PCIE_INFO(pcie_dev, + "PCIe: RC%d: force target link speed: %d\n", + pcie_dev->rc_idx, pcie_dev->target_link_speed); + } +#endif msm_pcie_dev[rc_idx].target_link_width = 0; of_property_read_u32(of_node, "qcom,target-link-width", @@ -8669,6 +9634,29 @@ static void msm_pcie_read_dt(struct msm_pcie_dev_t *pcie_dev, int rc_idx, PCIE_DBG(pcie_dev, "RC%d: aux clock frequency: %d.\n", pcie_dev->rc_idx, pcie_dev->aux_clk_freq); +#ifdef CONFIG_SEC_PCIE_AER + pcie_dev->aer_irq_counter = 0; +#endif + +#ifdef CONFIG_SEC_PANIC_PCIE_ERR + pcie_dev->linkdown_panic = true; + + ret = of_property_read_u32((&pdev->dev)->of_node, + "allow-linkup-retry", + &pcie_dev->allow_linkup_retry); + if (ret) { + PCIE_DBG(pcie_dev, + "RC%d: allow-linkup-retry is not found.\n", pcie_dev->rc_idx); + } else { + PCIE_DBG(pcie_dev, + "RC%d: allow-linkup-retry = 0x%x\n", pcie_dev->rc_idx, + pcie_dev->allow_linkup_retry); + } + pcie_dev->remained_linkup_retry = pcie_dev->allow_linkup_retry; +#else + pcie_dev->linkdown_panic = false; +#endif + pcie_dev->aer_enable = true; if (!of_find_property(of_node, "msi-map", NULL)) { @@ -8792,6 +9780,9 @@ static int msm_pcie_probe(struct platform_device *pdev) int rc_idx = -1, size; struct msm_pcie_dev_t *pcie_dev; struct device_node *of_node; +#ifdef CONFIG_SEC_PCIE + char rc_name[MAX_RC_NAME_LEN]; +#endif dev_info(&pdev->dev, "PCIe: %s\n", __func__); @@ -8818,6 +9809,58 @@ static int msm_pcie_probe(struct platform_device *pdev) PCIE_DBG(pcie_dev, "PCIe: RC index is %d.\n", pcie_dev->rc_idx); msm_pcie_read_dt(pcie_dev, rc_idx, pdev, of_node); + +#ifdef CONFIG_SEC_PCIE + snprintf(rc_name, MAX_RC_NAME_LEN, "pcie%d-short", pcie_dev->rc_idx); + pcie_dev->ipc_log = + ipc_log_context_create(PCIE_LOG_PAGES, rc_name, 0); + if (pcie_dev->ipc_log == NULL) + pr_err("%s: unable to create IPC log context for %s\n", + __func__, rc_name); + else + PCIE_DBG(pcie_dev, + "PCIe IPC logging is enable for RC%d\n", + pcie_dev->rc_idx); + snprintf(rc_name, MAX_RC_NAME_LEN, "pcie%d-long", pcie_dev->rc_idx); + pcie_dev->ipc_log_long = + ipc_log_context_create(PCIE_LOG_PAGES, rc_name, 0); + if (pcie_dev->ipc_log_long == NULL) + pr_err("%s: unable to create IPC log context for %s\n", + __func__, rc_name); + else + PCIE_DBG(pcie_dev, + "PCIe IPC logging %s is enable for RC%d\n", + rc_name, pcie_dev->rc_idx); + snprintf(rc_name, MAX_RC_NAME_LEN, "pcie%d-dump", pcie_dev->rc_idx); + pcie_dev->ipc_log_dump = + ipc_log_context_create(PCIE_LOG_PAGES, rc_name, 0); + if (pcie_dev->ipc_log_dump == NULL) + pr_err("%s: unable to create IPC log context for %s\n", + __func__, rc_name); + else + PCIE_DBG(pcie_dev, + "PCIe IPC logging %s is enable for RC%d\n", + rc_name, pcie_dev->rc_idx); +#endif + +#ifdef CONFIG_SEC_PCIE + if (pcie_dev->rc_idx == 0) { + pcie_dev->pcie_error_wq + = create_singlethread_workqueue("pcie_error_wq0"); + INIT_DELAYED_WORK(&pcie_dev->pcie_error_dwork, sec_pcie_error_worker); + pcie_dev->pcie_error_defer_ms = 15000; + } else if (pcie_dev->rc_idx == 1) { + pcie_dev->pcie_error_wq + = create_singlethread_workqueue("pcie_error_wq1"); + INIT_DELAYED_WORK(&pcie_dev->pcie_error_dwork, sec_pcie_error_worker); + pcie_dev->pcie_error_defer_ms = 15000; + } else if (pcie_dev->rc_idx == 2) { + pcie_dev->pcie_error_wq + = create_singlethread_workqueue("pcie_error_wq2"); + INIT_DELAYED_WORK(&pcie_dev->pcie_error_dwork, sec_pcie_error_worker); + pcie_dev->pcie_error_defer_ms = 15000; + } +#endif memcpy(pcie_dev->vreg, msm_pcie_vreg_info, sizeof(msm_pcie_vreg_info)); memcpy(pcie_dev->gpio, msm_pcie_gpio_info, sizeof(msm_pcie_gpio_info)); @@ -8866,6 +9909,21 @@ static int msm_pcie_probe(struct platform_device *pdev) msm_pcie_get_pinctrl(pcie_dev, pdev); +#ifdef CONFIG_SEC_PCIE + if (pcie_dev->subpcb_det_upper_gpio_level + && pcie_dev->subpcb_det_lower_gpio_level) { + PCIE_INFO(pcie_dev, "RC%d subpcb is not connected.\n", pcie_dev->rc_idx); + + if (pcie_dev->use_pinctrl && pcie_dev->pins_sleep) { + PCIE_INFO(pcie_dev, "RC%d pinctrl to sleep\n", pcie_dev->rc_idx); + pinctrl_select_state(pcie_dev->pinctrl, pcie_dev->pins_sleep); + } + + PCIE_INFO(pcie_dev, "RC%d set ignore_pcie_error\n", pcie_dev->rc_idx); + pcie_dev->ignore_pcie_error = true; + } +#endif + ret = msm_pcie_gpio_init(pcie_dev); if (ret) { msm_pcie_release_resources(pcie_dev); @@ -8926,6 +9984,12 @@ static int msm_pcie_probe(struct platform_device *pdev) return 0; decrease_rc_num: +#ifdef CONFIG_SEC_PCIE + if (pcie_dev->use_pinctrl && pcie_dev->pins_sleep) { + pinctrl_select_state(pcie_dev->pinctrl, + pcie_dev->pins_sleep); + } +#endif pcie_drv.rc_num--; PCIE_ERR(pcie_dev, "PCIe: RC%d: Driver probe failed. ret: %d\n", pcie_dev->rc_idx, ret); @@ -8975,6 +10039,17 @@ static int msm_pcie_remove(struct platform_device *pdev) msm_pcie_gdsc_deinit(&msm_pcie_dev[rc_idx]); msm_pcie_gpio_deinit(&msm_pcie_dev[rc_idx]); msm_pcie_release_resources(&msm_pcie_dev[rc_idx]); +#ifdef CONFIG_SEC_PCIE + if (msm_pcie_dev[rc_idx].ssr_notifier) { + ret = qcom_unregister_ssr_notifier(msm_pcie_dev[rc_idx].ssr_notifier, + &msm_pcie_dev[rc_idx].ssr_nb); + if (ret) { + dev_info(&pdev->dev, "PCIe: RC%d: %s: error %d unregistering notifier\n", + rc_idx, msm_pcie_dev[rc_idx].esoc_name, ret); + msm_pcie_dev[rc_idx].ssr_notifier = NULL; + } + } +#endif list_for_each_entry_safe(dev_info, temp, &msm_pcie_dev[rc_idx].enum_ep_list, @@ -9192,10 +10267,15 @@ void msm_pcie_allow_l1(struct pci_dev *pci_dev) { struct pci_dev *root_pci_dev; struct msm_pcie_dev_t *pcie_dev; - +#ifdef CONFIG_SEC_PCIE_L1SS + u32 ltssm, val = ~0; + struct pci_dev *ep_pci_dev = NULL; +#endif root_pci_dev = pcie_find_root_port(pci_dev); - if (!root_pci_dev) + if (!root_pci_dev) { + pr_info("[%d] skip %s root_pci_dev : null\n", current->pid, __func__); return; + } pcie_dev = PCIE_BUS_PRIV_DATA(root_pci_dev->bus); @@ -9238,13 +10318,30 @@ void msm_pcie_allow_l1(struct pci_dev *pci_dev) mutex_unlock(&pcie_dev->aspm_lock); return; } - +#ifdef CONFIG_SEC_PCIE_L1SS + ltssm = readl_relaxed(pcie_dev->parf + PCIE20_PARF_LTSSM) & MSM_PCIE_LTSSM_MASK; + if ((ltssm != MSM_PCIE_LTSSM_L0) && (ltssm != MSM_PCIE_LTSSM_L0S)) { + PCIE_INFO(pcie_dev, "PCIe RC%d: LTSSM_STATE: %s\n", + pcie_dev->rc_idx, TO_LTSSM_STR(ltssm)); + } else { + if (!list_empty(&root_pci_dev->subordinate->devices)) + ep_pci_dev = list_entry(root_pci_dev->subordinate->devices.next, + struct pci_dev, bus_list); + if (ep_pci_dev) { + pci_read_config_dword(ep_pci_dev, + ep_pci_dev->pcie_cap + PCI_EXP_LNKCTL, &val); + } + } +#endif msm_pcie_write_mask(pcie_dev->parf + PCIE20_PARF_PM_CTRL, BIT(5), 0); /* enable L1 */ msm_pcie_write_mask(pcie_dev->dm_core + (root_pci_dev->pcie_cap + PCI_EXP_LNKCTL), 0, PCI_EXP_LNKCTL_ASPM_L1); - +#ifdef CONFIG_SEC_PCIE_L1SS + if (!(val & PCI_EXP_LNKCTL_ASPM_L1)) + sec_pcie_enable_ep_l1(ep_pci_dev, pcie_dev); +#endif PCIE_DBG2(pcie_dev, "PCIe: RC%d: %02x:%02x.%01x: exit\n", pcie_dev->rc_idx, pci_dev->bus->number, PCI_SLOT(pci_dev->devfn), PCI_FUNC(pci_dev->devfn)); @@ -9261,8 +10358,10 @@ int msm_pcie_prevent_l1(struct pci_dev *pci_dev) int ret = 0; root_pci_dev = pcie_find_root_port(pci_dev); - if (!root_pci_dev) + if (!root_pci_dev) { + pr_info("[%d] skip %s root_pci_dev : null\n", current->pid, __func__); return -ENODEV; + } pcie_dev = PCIE_BUS_PRIV_DATA(root_pci_dev->bus); @@ -9388,6 +10487,11 @@ int msm_pcie_set_target_link_speed(u32 rc_idx, u32 target_link_speed, * Reject the request if it exceeds what PCIe RC is capable or if * it's greater than what was specified in DT (if present) */ +#ifdef CONFIG_SEC_PCIE_KEEP_LINKBW + PCIE_DBG(pcie_dev, "PCIe: RC%d: keep current link speed\n", pcie_dev->rc_idx); + return -EINVAL; +#endif + if (target_link_speed > pcie_dev->bw_gen_max || (pcie_dev->dt_target_link_speed && !force && target_link_speed > pcie_dev->dt_target_link_speed)) { @@ -9443,6 +10547,11 @@ int msm_pcie_set_link_bandwidth(struct pci_dev *pci_dev, u16 target_link_speed, pcie_dev = PCIE_BUS_PRIV_DATA(root_pci_dev->bus); +#ifdef CONFIG_SEC_PCIE_KEEP_LINKBW + PCIE_DBG(pcie_dev, "PCIe: RC%d: keep current link bandwidth\n", pcie_dev->rc_idx); + return -EINVAL; +#endif + if (target_link_speed > pcie_dev->bw_gen_max || (pcie_dev->target_link_speed && target_link_speed > pcie_dev->target_link_speed)) { @@ -9499,12 +10608,28 @@ int msm_pcie_set_link_bandwidth(struct pci_dev *pci_dev, u16 target_link_speed, goto out; if (pcie_dev->current_link_speed != target_link_speed) { +#ifdef CONFIG_SEC_PCIE + set_bit(PCIE_ERROR_LINK_SPEED_MISMATCH, &pcie_dev->pcie_error); +#endif PCIE_ERR(pcie_dev, "PCIe: RC%d: failed to switch bandwidth: target speed: %d\n", pcie_dev->rc_idx, target_link_speed); +#ifdef CONFIG_SEC_PANIC_PCIE_ERR + panic("PCIe: RC%d: Link BW fail. target GEN%d, current GEN%d,%dL\n", + pcie_dev->rc_idx, + target_link_speed, + link_status & PCI_EXP_LNKSTA_CLS, link_status & PCI_EXP_LNKSTA_NLW); +#endif ret = -EIO; goto out; } +#ifdef CONFIG_SEC_PCIE + else { + PCIE_INFO(pcie_dev, + "PCIe: RC%d: switched bandwidth: target speed: %d\n", + pcie_dev->rc_idx, target_link_speed); + } +#endif if (target_link_speed < current_link_speed) msm_pcie_scale_link_bandwidth(pcie_dev, target_link_speed); @@ -9849,6 +10974,300 @@ static struct platform_driver msm_pcie_driver = { }, }; +#ifdef CONFIG_SEC_PCIE_L1SS +static struct device *sec_pcie_dev; +void sec_pcie_set_use_ep_loaded(struct pci_dev *dev) +{ + /* fixme : check caller */ + struct msm_pcie_dev_t *pcie_dev = PCIE_BUS_PRIV_DATA(dev->bus); + pcie_dev->use_ep_loaded = true; +} +EXPORT_SYMBOL(sec_pcie_set_use_ep_loaded); + +void sec_pcie_set_ep_driver_loaded(struct pci_dev *dev, bool is_loaded) +{ + /* fixme : check caller */ + struct msm_pcie_dev_t *pcie_dev = PCIE_BUS_PRIV_DATA(dev->bus); + pcie_dev->ep_loaded = is_loaded; +} +EXPORT_SYMBOL(sec_pcie_set_ep_driver_loaded); + +static bool sec_pcie_check_ep_driver_up(struct msm_pcie_dev_t *dev) +{ + return dev->use_ep_loaded ? dev->ep_loaded : true; +} + +static int sec_pcie_enable_ep_l1(struct pci_dev *pdev, void *data) +{ + struct msm_pcie_dev_t *dev = data; + + if (pdev->subordinate || !pdev->bus->parent) { + PCIE_INFO(dev, "PCIe: RC%d: PCI device %02x:%02x.%01x is not EP\n", + dev->rc_idx, pdev->bus->number, PCI_SLOT(pdev->devfn), + PCI_FUNC(pdev->devfn)); + return -ENODEV; + } + + msm_pcie_config_clear_set_dword(pdev, + pdev->pcie_cap + PCI_EXP_LNKCTL, 0, + PCI_EXP_LNKCTL_ASPM_L1); + + PCIE_DBG2(dev, "PCIe: RC%d: PCI device %02x:%02x.%01x L1 enabled\n", + dev->rc_idx, pdev->bus->number, PCI_SLOT(pdev->devfn), + PCI_FUNC(pdev->devfn)); + + return 0; +} + +static ssize_t sec_show_l1ss_stat(struct device *in_dev, + struct device_attribute *attr, char *buf) +{ + struct msm_pcie_dev_t *dev = &msm_pcie_dev[0]; + struct pci_dev *pdev = dev->dev; + unsigned long irqsave_flags; + bool l1_1_cap_support, l1_2_cap_support; + u32 val; + u32 l1ss_cap_id_offset, l1ss_cap_offset; + u32 l1ss_ctl1_offset, l1ss_ctl2_offset, l1ss_ltr_cap_id_offset; + int count = 0; + u32 ltssm; + + mutex_lock(&dev->setup_lock); + spin_lock_irqsave(&dev->irq_lock, irqsave_flags); + + if (!sec_pcie_check_ep_driver_up(dev)) { + count += scnprintf(buf + count, PAGE_SIZE - count, + "RC%d: EP driver is not up.\n", dev->rc_idx); + goto out; + } + + if (dev->link_status != MSM_PCIE_LINK_ENABLED) { + count += scnprintf(buf + count, PAGE_SIZE - count, + "RC%d link is not enabled.\n", dev->rc_idx); + goto out; + } + + if (dev->suspending) { + count += scnprintf(buf + count, PAGE_SIZE - count, + "RC%d is suspending.\n", dev->rc_idx); + goto out; + } + + ltssm = readl_relaxed(dev->parf + PCIE20_PARF_LTSSM) & MSM_PCIE_LTSSM_MASK; + count += scnprintf(buf + count, PAGE_SIZE - count, + "LTSSM : %s\n", TO_LTSSM_STR(ltssm)); + + while (pdev) { + count += scnprintf(buf + count, PAGE_SIZE - count, + "%s\n", dev_name(&pdev->dev)); + + pci_read_config_dword(pdev, pdev->pcie_cap + PCI_EXP_LNKCAP, &val); + + if (!(val & BIT(10))) + count += scnprintf(buf + count, PAGE_SIZE - count, + "\tL0s - Not Support\n"); + + if (!(val & BIT(11))) + count += scnprintf(buf + count, PAGE_SIZE - count, + "\tL1 - Not Support\n"); + + pci_read_config_dword(pdev, pdev->pcie_cap + PCI_EXP_LNKCTL, &val); + count += scnprintf(buf + count, PAGE_SIZE - count, + "\tL0s %s\n", val & PCI_EXP_LNKCTL_ASPM_L0S ? "E" : "D"); + count += scnprintf(buf + count, PAGE_SIZE - count, + "\tL1 %s\n", val & PCI_EXP_LNKCTL_ASPM_L1 ? "E" : "D"); + + l1ss_cap_id_offset = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_L1SS); + if (!l1ss_cap_id_offset) { + count += scnprintf(buf + count, PAGE_SIZE - count, + "\tnot found L1ss capability register\n"); + goto out; + } + + l1ss_cap_offset = l1ss_cap_id_offset + PCI_L1SS_CAP; + l1ss_ctl1_offset = l1ss_cap_id_offset + PCI_L1SS_CTL1; + l1ss_ctl2_offset = l1ss_cap_id_offset + PCI_L1SS_CTL2; + + pci_read_config_dword(pdev, l1ss_cap_offset, &val); + l1_1_cap_support = !!(val & (PCI_L1SS_CAP_ASPM_L1_1)); + l1_2_cap_support = !!(val & (PCI_L1SS_CAP_ASPM_L1_2)); + if (!l1_1_cap_support && !l1_2_cap_support) { + count += scnprintf(buf + count, PAGE_SIZE - count, + "\tnot support L1ss\n"); + goto out; + } + + pci_read_config_dword(pdev, l1ss_ctl1_offset, &val); + count += scnprintf(buf + count, PAGE_SIZE - count, + "\tL1ss %s (L1SS_CTL1:0x%08x)\n", + (val & 0xf) == 0xf ? "E" : "D", val); + pci_read_config_dword(pdev, l1ss_ctl2_offset, &val); + count += scnprintf(buf + count, PAGE_SIZE - count, + "\t (L1SS_CTL2:0x%08x)\n", val); + pci_read_config_dword(pdev, pdev->pcie_cap + PCI_EXP_DEVCTL2, &val); + count += scnprintf(buf + count, PAGE_SIZE - count, + "\t (DEV_CTL2:0x%08x)\n", val); + + l1ss_ltr_cap_id_offset = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_LTR); + if (l1ss_ltr_cap_id_offset) { + pci_read_config_dword(pdev, + l1ss_ltr_cap_id_offset + PCI_LTR_MAX_SNOOP_LAT, &val); + count += scnprintf(buf + count, PAGE_SIZE - count, + "\t (LTR_MAX_SNOOP_LAT:0x%08x)\n", val); + } + + if (pdev->subordinate) + pdev = list_entry(pdev->subordinate->devices.next, + struct pci_dev, bus_list); + else + break; + } + +out: + spin_unlock_irqrestore(&dev->irq_lock, irqsave_flags); + mutex_unlock(&dev->setup_lock); + return (ssize_t)count; +} + +static void sec_pcie_l1ss_ctrl_worker(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct l1ss_ctrl *l1ss_ctrl = + container_of(dwork, struct l1ss_ctrl, dwork); + + sec_pcie_l1ss_enable(l1ss_ctrl->id); +} + +int sec_pcie_l1ss_enable(int ctrl_id) +{ + struct msm_pcie_dev_t *dev = &msm_pcie_dev[0]; + struct pci_dev *pdev = dev->dev; + int ret = 0; + u32 prev_flag; + unsigned long irqsave_flags; + bool suspending; + + if (ctrl_id < 0 || ctrl_id >= L1SS_MAX) { + PCIE_ERR(dev, "RC%d wrong id(%d) -> ignore\n", dev->rc_idx, ctrl_id); + return -EINVAL; + } + + mutex_lock(&dev->setup_lock); + mutex_lock(&dev->l1ss_ctrl_lock); + + prev_flag = dev->l1ss_disable_flag; + dev->l1ss_disable_flag &= ~l1ss_ctrls[ctrl_id].flag; + + PCIE_DBG(dev, "RC%d triggered by %d:%s (flag=0x%x)\n", dev->rc_idx, + ctrl_id, l1ss_ctrls[ctrl_id].name, + dev->l1ss_disable_flag); + + if (prev_flag && !dev->l1ss_disable_flag) { + spin_lock_irqsave(&dev->irq_lock, irqsave_flags); + suspending = dev->suspending; + spin_unlock_irqrestore(&dev->irq_lock, irqsave_flags); + if (suspending || !dev->ep_config_accessible) { + PCIE_DBG(dev, "RC%d pending - suspending or not accessible\n", dev->rc_idx); + dev->pending_l1ss_ctrl = true; + } else if (!sec_pcie_check_ep_driver_up(dev)) { + PCIE_DBG(dev, "RC%d pending - EP driver is not up\n", dev->rc_idx); + dev->pending_l1ss_ctrl = true; + } else { + msm_pcie_config_l1_disable_all(dev, pdev->bus); + msm_pcie_config_l1ss_enable_all(dev); + msm_pcie_config_l1_enable_all(dev); + } + } + + mutex_unlock(&dev->l1ss_ctrl_lock); + mutex_unlock(&dev->setup_lock); + + return ret; +} +EXPORT_SYMBOL(sec_pcie_l1ss_enable); + +int sec_pcie_l1ss_disable(int ctrl_id) +{ + struct msm_pcie_dev_t *dev = &msm_pcie_dev[0]; + struct pci_dev *pdev = dev->dev; + int ret = 0; + u32 prev_flag; + unsigned long irqsave_flags; + bool suspending; + + if (ctrl_id < 0 || ctrl_id >= L1SS_MAX) { + PCIE_ERR(dev, "RC%d wrong id(%d) -> ignore\n", dev->rc_idx, ctrl_id); + return -EINVAL; + } + + cancel_delayed_work_sync(&l1ss_ctrls[ctrl_id].dwork); + + mutex_lock(&dev->setup_lock); + mutex_lock(&dev->l1ss_ctrl_lock); + + prev_flag = dev->l1ss_disable_flag; + dev->l1ss_disable_flag |= l1ss_ctrls[ctrl_id].flag; + + PCIE_DBG(dev, "RC%d triggered by %d:%s (flag=0x%x)\n", dev->rc_idx, + ctrl_id, l1ss_ctrls[ctrl_id].name, + dev->l1ss_disable_flag); + + if (l1ss_ctrls[ctrl_id].timeout) { + queue_delayed_work(l1ss_ctrl_wq, + &l1ss_ctrls[ctrl_id].dwork, + l1ss_ctrls[ctrl_id].timeout * HZ); + } + + if (!prev_flag && dev->l1ss_disable_flag) { + spin_lock_irqsave(&dev->irq_lock, irqsave_flags); + suspending = dev->suspending; + spin_unlock_irqrestore(&dev->irq_lock, irqsave_flags); + if (suspending || !dev->ep_config_accessible) { + PCIE_DBG(dev, "RC%d pending - suspending or not accessible\n", dev->rc_idx); + dev->pending_l1ss_ctrl = true; + } else if (!sec_pcie_check_ep_driver_up(dev)) { + PCIE_DBG(dev, "RC%d pending - EP driver is not up\n", dev->rc_idx); + dev->pending_l1ss_ctrl = true; + } else { + msm_pcie_config_l1_disable_all(dev, pdev->bus); + msm_pcie_config_l1ss_disable_all(dev, pdev->bus); + msm_pcie_config_l1_enable_all(dev); + } + } + + mutex_unlock(&dev->l1ss_ctrl_lock); + mutex_unlock(&dev->setup_lock); + + return ret; +} +EXPORT_SYMBOL(sec_pcie_l1ss_disable); + +static ssize_t sec_write_l1ss_stat(struct device *in_dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int enable; + int ret; + + ret = kstrtouint(buf, 0, &enable); + if (ret) { + pr_err("%s : Fail get enable(%s)\n", __func__, buf); + return count; + } + + if (enable) + ret = sec_pcie_l1ss_enable(L1SS_SYSFS); + else + ret = sec_pcie_l1ss_disable(L1SS_SYSFS); + if (ret) + pr_err("%s: Fail sec_pcie_l1ss_%s(%d)\n", __func__, + enable ? "enable" : "disable", ret); + + return count; +} + +static DEVICE_ATTR(pcie_l1ss_ctrl, 0664, sec_show_l1ss_stat, sec_write_l1ss_stat); +#endif + static int msm_pcie_drv_rpmsg_probe(struct rpmsg_device *rpdev) { mutex_lock(&pcie_drv.rpmsg_lock); @@ -10152,7 +11571,9 @@ static struct i2c_driver pcie_i2c_ctrl_driver = { static int __init pcie_init(void) { int ret = 0, i; +#ifndef CONFIG_SEC_PCIE char rc_name[MAX_RC_NAME_LEN]; +#endif void __iomem *reg_addr; pr_debug("pcie:%s.\n", __func__); @@ -10162,6 +11583,7 @@ static int __init pcie_init(void) mutex_init(&pcie_drv.rpmsg_lock); for (i = 0; i < MAX_RC_NUM; i++) { +#ifndef CONFIG_SEC_PCIE scnprintf(rc_name, MAX_RC_NAME_LEN, "pcie%d-short", i); msm_pcie_dev[i].ipc_log = ipc_log_context_create(PCIE_LOG_PAGES, rc_name, 0); @@ -10192,6 +11614,7 @@ static int __init pcie_init(void) PCIE_DBG(&msm_pcie_dev[i], "PCIe IPC logging %s is enable for RC%d\n", rc_name, i); +#endif spin_lock_init(&msm_pcie_dev[i].cfg_lock); spin_lock_init(&msm_pcie_dev[i].evt_reg_list_lock); msm_pcie_dev[i].cfg_access = true; @@ -10210,6 +11633,12 @@ static int __init pcie_init(void) INIT_LIST_HEAD(&msm_pcie_dev[i].enum_ep_list); INIT_LIST_HEAD(&msm_pcie_dev[i].susp_ep_list); INIT_LIST_HEAD(&msm_pcie_dev[i].event_reg_list); +#ifdef CONFIG_SEC_PCIE_L1SS + mutex_init(&msm_pcie_dev[i].l1ss_ctrl_lock); + msm_pcie_dev[i].l1ss_disable_flag = 0; + msm_pcie_dev[i].pending_l1ss_ctrl = false; + msm_pcie_dev[i].ep_config_accessible = false; +#endif } #if IS_ENABLED(CONFIG_I2C) @@ -10256,6 +11685,26 @@ static int __init pcie_init(void) INIT_WORK(&pcie_drv.drv_connect, msm_pcie_drv_connect_worker); pcie_drv.msm_pcie_dev = msm_pcie_dev; +#ifdef CONFIG_SEC_PCIE_L1SS + l1ss_ctrl_wq = create_singlethread_workqueue("pcie_l1ss_ctrl_wq"); + + for (i = 0; i < L1SS_MAX; i++) + INIT_DELAYED_WORK(&l1ss_ctrls[i].dwork, + sec_pcie_l1ss_ctrl_worker); + + sec_pcie_dev = sec_device_create(NULL, "pcie-wifi"); + if (IS_ERR(sec_pcie_dev)) { + pr_err("%s: Failed to create pcie device\n", __func__); + goto sec_device_err; + } + + if (device_create_file(sec_pcie_dev, &dev_attr_pcie_l1ss_ctrl) < 0) { + pr_err("%s: Failed to create pcie_l1ss_ctrl\n", __func__); + goto sec_device_err; + } + +sec_device_err: +#endif ret = platform_driver_register(&msm_pcie_driver); if (ret) destroy_workqueue(mpcie_wq); @@ -10337,9 +11786,16 @@ static int msm_pcie_pm_suspend(struct pci_dev *dev, int ret_l23; unsigned long irqsave_flags; struct msm_pcie_dev_t *pcie_dev = PCIE_BUS_PRIV_DATA(dev->bus); - +#ifdef CONFIG_SEC_PCIE + u32 ltssm_pre = 0, ltssm_post = 0, rc_linkup = 0, ep_linkup = 0; +#endif PCIE_DBG(pcie_dev, "RC%d: entry\n", pcie_dev->rc_idx); - +#if defined(CONFIG_SEC_PANIC_PCIE_ERR) && defined(CONFIG_SEC_PCIE_AER) + if (!pcie_dev->ignore_pcie_error && pcie_dev->aer_irq_counter) { + panic("PCIe RC%d AER detect(%lu)!\n", + pcie_dev->rc_idx, pcie_dev->aer_irq_counter); + } +#endif spin_lock_irqsave(&pcie_dev->irq_lock, irqsave_flags); pcie_dev->suspending = true; spin_unlock_irqrestore(&pcie_dev->irq_lock, irqsave_flags); @@ -10361,6 +11817,26 @@ static int msm_pcie_pm_suspend(struct pci_dev *dev, } if (dev) { +#ifdef CONFIG_SEC_PCIE + PCIE_DBG(pcie_dev, + "dev->bus->number = %d dev->bus->primary = %d\n", + dev->bus->number, dev->bus->primary); + + rc_linkup = msm_pcie_confirm_linkup(pcie_dev, true, false, dev); + if (rc_linkup) { + ep_linkup = readl_relaxed(pcie_dev->conf); + PCIE_DBG(pcie_dev, + "PCIe: device ID and vender ID of EP of RC %d are 0x%x.\n", + pcie_dev->rc_idx, ep_linkup); + if (ep_linkup == PCIE_LINK_DOWN) { + PCIE_ERR(pcie_dev, + "PCIe: The link of RC %d is not really up; device ID and vender ID of EP of RC %d are 0x%x.\n", + pcie_dev->rc_idx, pcie_dev->rc_idx, ep_linkup); + } + + ret = pci_save_state(dev); + } +#else if (msm_pcie_confirm_linkup(pcie_dev, true, true, dev)) { PCIE_DBG(pcie_dev, "PCIe: RC%d: save config space\n", pcie_dev->rc_idx); @@ -10372,8 +11848,9 @@ static int msm_pcie_pm_suspend(struct pci_dev *dev, pcie_dev->suspending = false; return ret; } - - } else { + } +#endif + else { kfree(pcie_dev->saved_state); pcie_dev->saved_state = NULL; @@ -10401,6 +11878,24 @@ static int msm_pcie_pm_suspend(struct pci_dev *dev, spin_unlock_irqrestore(&pcie_dev->cfg_lock, pcie_dev->irqsave_flags); +#ifdef CONFIG_SEC_PCIE + ltssm_pre = readl_relaxed(pcie_dev->parf + PCIE20_PARF_LTSSM); + PCIE_INFO(pcie_dev, "PCIe RC%d: PARF LTSSM_STATE: %s\n", + pcie_dev->rc_idx, TO_LTSSM_STR(ltssm_pre & 0x3f)); + + rc_linkup = (u32)msm_pcie_confirm_linkup(pcie_dev, true, false, dev); + if (rc_linkup && (ep_linkup != PCIE_LINK_DOWN)) { + ep_linkup = readl_relaxed(pcie_dev->conf); + PCIE_DBG(pcie_dev, + "PCIe: device ID and vender ID of EP of RC %d are 0x%x.\n", + pcie_dev->rc_idx, ep_linkup); + if (ep_linkup == PCIE_LINK_DOWN) { + PCIE_ERR(pcie_dev, + "PCIe: The link of RC %d is not really up; device ID and vender ID of EP of RC %d are 0x%x.\n", + pcie_dev->rc_idx, pcie_dev->rc_idx, ep_linkup); + } + } +#endif writel_relaxed(BIT(4), pcie_dev->elbi + PCIE20_ELBI_SYS_CTRL); wmb(); /* ensure changes propagated to the hardware */ @@ -10411,6 +11906,11 @@ static int msm_pcie_pm_suspend(struct pci_dev *dev, val, PCIE_LINK_IN_L2_STATE(val), 9000, pcie_dev->l23_rdy_poll_timeout); +#ifdef CONFIG_SEC_PCIE + ltssm_post = readl_relaxed(pcie_dev->parf + PCIE20_PARF_LTSSM); + PCIE_INFO(pcie_dev, "PCIe RC%d: PARF LTSSM_STATE: %s\n", + pcie_dev->rc_idx, TO_LTSSM_STR(ltssm_post & 0x3f)); +#endif /* check L23_Ready */ PCIE_DBG(pcie_dev, "RC%d: PCIE20_PARF_PM_STTS_1 is 0x%x.\n", pcie_dev->rc_idx, @@ -10418,9 +11918,20 @@ static int msm_pcie_pm_suspend(struct pci_dev *dev, if (!ret_l23) PCIE_DBG(pcie_dev, "RC%d: PM_Enter_L23 is received\n", pcie_dev->rc_idx); - else + else { +#ifdef CONFIG_SEC_PCIE + PCIE_INFO(pcie_dev, "RC%d: PM_Enter_L23 is NOT received. RC(%s) EP(0x%08x) LTSSM(%s, %s)\n", + pcie_dev->rc_idx, rc_linkup ? "OK" : "NG", ep_linkup, TO_LTSSM_STR(ltssm_pre & 0x3f), TO_LTSSM_STR(ltssm_post & 0x3f)); +#ifdef CONFIG_SEC_PANIC_PCIE_ERR + if (is_need_pcie_error_oops((struct pci_dev *)user, pcie_dev)) + panic("PCIe RC%d Fail(L23). RC(%s) EP(0x%08x) LTSSM(%s, %s)\n", + pcie_dev->rc_idx, rc_linkup ? "OK" : "NG", ep_linkup, TO_LTSSM_STR(ltssm_pre & 0x3f), TO_LTSSM_STR(ltssm_post & 0x3f)); +#endif +#else PCIE_DBG(pcie_dev, "RC%d: PM_Enter_L23 is NOT received\n", pcie_dev->rc_idx); +#endif + } if (pcie_dev->use_pinctrl && pcie_dev->pins_sleep) pinctrl_select_state(pcie_dev->pinctrl, @@ -10474,6 +11985,9 @@ static int msm_pcie_pm_resume(struct pci_dev *dev, void *user, void *data, u32 options) { int ret; +#ifdef CONFIG_SEC_PCIE + u32 val; +#endif struct msm_pcie_dev_t *pcie_dev = PCIE_BUS_PRIV_DATA(dev->bus); PCIE_DBG(pcie_dev, "RC%d: entry\n", pcie_dev->rc_idx); @@ -10520,8 +12034,33 @@ static int msm_pcie_pm_resume(struct pci_dev *dev, pci_load_and_free_saved_state(dev, &pcie_dev->saved_state); pci_restore_state(dev); +#ifdef CONFIG_SEC_PCIE_L1SS + /* + * restore the configuratoins for l1/l1ss + * which are set during PCIe suspend period + */ + mutex_lock(&pcie_dev->l1ss_ctrl_lock); + if (pcie_dev->pending_l1ss_ctrl) { + msm_pcie_config_l1_disable_all(pcie_dev, dev->bus); + + if (pcie_dev->l1ss_disable_flag) + msm_pcie_config_l1ss_disable_all(pcie_dev, dev->bus); + else + msm_pcie_config_l1ss_enable_all(pcie_dev); + + msm_pcie_config_l1_enable_all(pcie_dev); + pcie_dev->pending_l1ss_ctrl = false; + } + mutex_unlock(&pcie_dev->l1ss_ctrl_lock); +#endif } +#ifdef CONFIG_SEC_PCIE + val = readl_relaxed(pcie_dev->dm_core + 0x4); + if (!(val & (1 << 2))) + PCIE_ERR(pcie_dev, "RC%d: BME is not set. conf[4] = 0x%08x\n", + pcie_dev->rc_idx, val); +#endif PCIE_DBG(pcie_dev, "RC%d: exit\n", pcie_dev->rc_idx); return ret; @@ -10643,6 +12182,13 @@ static int msm_pcie_drv_resume(struct msm_pcie_dev_t *pcie_dev) struct msm_pcie_clk_info_t *clk_info; u32 clkreq_override_en = 0; int ret, i, rpmsg_ret = 0; +#ifdef CONFIG_SEC_PCIE + u32 val; + u32 link_speed[3] = {0,}, link_width = 0; +#endif +#ifdef CONFIG_SEC_PCIE_L1SS + struct pci_dev *dev; +#endif mutex_lock(&pcie_dev->recovery_lock); mutex_lock(&pcie_dev->setup_lock); @@ -10804,6 +12350,54 @@ static int msm_pcie_drv_resume(struct msm_pcie_dev_t *pcie_dev) if (!pcie_dev->pcie_sm) enable_irq(pcie_dev->irq[MSM_PCIE_INT_GLOBAL_INT].num); +#ifdef CONFIG_SEC_PCIE + val = readl_relaxed(pcie_dev->parf + PCIE20_PARF_LTSSM); + PCIE_INFO(pcie_dev, "PCIe RC%d: LTSSM_STATE: %s\n", + pcie_dev->rc_idx, TO_LTSSM_STR(val & 0x3f)); + + if (readl_relaxed(pcie_dev->conf) != PCIE_LINK_DOWN) { + link_speed[0] = pcie_get_target_linkspeed(pcie_dev->rc_idx, 0); + link_speed[1] = pcie_get_target_linkspeed(pcie_dev->rc_idx, 1); + PCIE_INFO(pcie_dev, "PCIe RC%d Target GEN%d, EP GEN%d\n", + pcie_dev->rc_idx, link_speed[0], link_speed[1]); + pcie_get_cur_link_bw(pcie_dev->rc_idx, &link_speed[2], &link_width); + PCIE_INFO(pcie_dev, "PCIe RC%d Current GEN%d, %d lanes\n", + pcie_dev->rc_idx, link_speed[2], link_width); + + if (link_speed[0] != link_speed[2]) { + set_bit(PCIE_ERROR_LINK_SPEED_MISMATCH, &pcie_dev->pcie_error); +#ifdef CONFIG_SEC_PANIC_PCIE_ERR + panic("PCIe: RC%d: link speed fail(GEN%d -> %d)\n", + pcie_dev->rc_idx, link_speed[0], link_speed[2]); +#endif + } + } else { + PCIE_ERR(pcie_dev, + "PCIe: RC%d: endpoint config space is not accessible\n", + pcie_dev->rc_idx); + } +#endif +#ifdef CONFIG_SEC_PCIE_L1SS + /* + * restore the configuratoins for l1/l1ss + * which are set during PCIe suspend period + */ + mutex_lock(&pcie_dev->l1ss_ctrl_lock); + dev = pcie_dev->dev; + if (pcie_dev->pending_l1ss_ctrl) { + msm_pcie_config_l1_disable_all(pcie_dev, dev->bus); + + if (pcie_dev->l1ss_disable_flag) + msm_pcie_config_l1ss_disable_all(pcie_dev, dev->bus); + else + msm_pcie_config_l1ss_enable_all(pcie_dev); + + msm_pcie_config_l1_enable_all(pcie_dev); + pcie_dev->pending_l1ss_ctrl = false; + } + mutex_unlock(&pcie_dev->l1ss_ctrl_lock); + pcie_dev->ep_config_accessible = true; +#endif mutex_unlock(&pcie_dev->setup_lock); mutex_unlock(&pcie_dev->recovery_lock); @@ -10833,6 +12427,13 @@ static int msm_pcie_drv_suspend(struct msm_pcie_dev_t *pcie_dev, return -EINVAL; } +#ifdef CONFIG_SEC_PANIC_PCIE_ERR + if (!pcie_dev->ignore_pcie_error && pcie_dev->aer_irq_counter) { + panic("PCIe RC%d AER detect(%lu)!\n", + pcie_dev->rc_idx, pcie_dev->aer_irq_counter); + } +#endif + mutex_lock(&pcie_dev->recovery_lock); /* disable global irq - no more linkdown/aer detection */ @@ -10861,6 +12462,9 @@ static int msm_pcie_drv_suspend(struct msm_pcie_dev_t *pcie_dev, mutex_lock(&pcie_dev->aspm_lock); pcie_dev->link_status = MSM_PCIE_LINK_DRV; mutex_unlock(&pcie_dev->aspm_lock); +#ifdef CONFIG_SEC_PCIE_L1SS + pcie_dev->ep_config_accessible = false; +#endif if (pcie_dev->pcie_sm) { msm_pcie_cesta_enable_drv(pcie_dev, diff --git a/drivers/pinctrl/qcom/Kconfig b/drivers/pinctrl/qcom/Kconfig index a9107a37eed1..dc29052345aa 100644 --- a/drivers/pinctrl/qcom/Kconfig +++ b/drivers/pinctrl/qcom/Kconfig @@ -644,4 +644,16 @@ config PINCTRL_MONACO_AUTO Say Y here to compile statically, or M here to compile it as a module. If unsure, say N. +config SEC_GPIO_DVS + tristate "setting Samsung GPIO debugging and verification system" + help + To verify gpio configurations of devices, set this feature. + This feature should be enabled for user-debug mode. + +config SEC_GPIO_DUMP + bool "Help to check GPIO configuration" + help + To debug gpio state, set this feature. + This feature can be enabled for all modes. + endif diff --git a/drivers/pinctrl/qcom/Makefile b/drivers/pinctrl/qcom/Makefile index 16c7763e22a7..657e942f6b25 100644 --- a/drivers/pinctrl/qcom/Makefile +++ b/drivers/pinctrl/qcom/Makefile @@ -65,3 +65,6 @@ obj-$(CONFIG_PINCTRL_LEMANS) += pinctrl-lemans.o obj-$(CONFIG_PINCTRL_DIREWOLF) += pinctrl-direwolf.o obj-$(CONFIG_PINCTRL_MONACO_AUTO) += pinctrl-monaco_auto.o obj-$(CONFIG_PINCTRL_SM6150) += pinctrl-sm6150.o + +# Debug and verify gpio configurations. +obj-$(CONFIG_SEC_GPIO_DVS) += secgpio_dvs.o diff --git a/drivers/pinctrl/qcom/pinctrl-msm.c b/drivers/pinctrl/qcom/pinctrl-msm.c index d952beb77eb1..9d6447fae5ed 100644 --- a/drivers/pinctrl/qcom/pinctrl-msm.c +++ b/drivers/pinctrl/qcom/pinctrl-msm.c @@ -36,6 +36,10 @@ #include "pinctrl-msm.h" #include "../pinctrl-utils.h" +#if IS_ENABLED(CONFIG_SEC_GPIO_DVS) +#include "secgpio_dvs.h" +#endif + #define MAX_NR_GPIO 300 #define MAX_NR_TILES 4 #define DEFAULT_REG_SIZE_4K 1 @@ -764,6 +768,201 @@ static void msm_gpio_pin_status_get(struct msm_pinctrl *pctrl, const struct msm_ *val = !!(io_reg & BIT(g->in_bit)); } +#if IS_ENABLED(CONFIG_SEC_GPIO_DVS) || IS_ENABLED(CONFIG_SEC_GPIO_DUMP) +#define AP_GPIO_MAX 210 +#define GET_RESULT_GPIO(a, b, c) \ + ((a<<4 & 0xF0) | (b<<1 & 0xE) | (c & 0x1)) + +struct gpiomux_setting { + int func; + int drv; + int pull; + int is_out; + int val; +}; + +static void msm_gp_get_all(struct gpio_chip *chip, u32 pin_no, struct gpiomux_setting *set) +{ + const struct msm_pingroup *g; + struct msm_pinctrl *pctrl = gpiochip_get_data(chip); + u32 ctl_reg, io_reg; + int drive; + + g = &pctrl->soc->groups[pin_no]; + + ctl_reg = msm_readl_ctl(pctrl, g); + io_reg = msm_readl_io(pctrl, g); + + set->is_out = !!(ctl_reg & BIT(g->oe_bit)); + set->func = (ctl_reg >> g->mux_bit) & 7; + drive = (ctl_reg >> g->drv_bit) & 7; + set->drv = msm_regval_to_drive(drive); + set->pull = (ctl_reg >> g->pull_bit) & 3; + + if (set->is_out) + set->val = !!(io_reg & BIT(g->out_bit)); + else + set->val = !!(io_reg & BIT(g->in_bit)); +} +#endif + +#if IS_ENABLED(CONFIG_SEC_GPIO_DVS) +/****************************************************************/ +/* Pre-defined variables. (DO NOT CHANGE THIS!!) */ +static unsigned char checkgpiomap_result[GDVS_PHONE_STATUS_MAX][AP_GPIO_MAX]; +static struct gpiomap_result gpiomap_result = { + .init = checkgpiomap_result[PHONE_INIT], + .sleep = checkgpiomap_result[PHONE_SLEEP] +}; + +static void msm_check_gpio_status(unsigned char phonestate) +{ + struct gpio_chip *gp; + struct gpiomux_setting set; + int i; + u8 temp_io = 0, temp_pdpu = 0, temp_lh = 0; + + if (!msm_pinctrl_data) { + pr_err("pinctrl data is not available\n"); + return; + } + + gp = &msm_pinctrl_data->chip; + + pr_info("[dvs_%s] state : %s\n", __func__, + (phonestate == PHONE_INIT) ? "init" : "sleep"); + + for (i = 0; i < AP_GPIO_MAX; i++) { + /* If it is not valid gpio or secure, Shows IN/PD/L */ + if (!gpiochip_line_is_valid(gp, i)) { + checkgpiomap_result[phonestate][i] = + GET_RESULT_GPIO(0x1, 0x1, 0x0); + continue; + } + + msm_gp_get_all(gp, i, &set); + + if (set.func == 0) { + if (set.is_out) + temp_io = 0x02; /* GPIO_OUT */ + else + temp_io = 0x01; /* GPIO_IN */ + } else + temp_io = 0x0; /* FUNC */ + + switch (set.pull) { + case MSM_NO_PULL: + temp_pdpu = 0x00; + break; + case MSM_PULL_DOWN: + temp_pdpu = 0x01; + break; + /* + * gpiodvs is only for samsung, + * so pull definition could be different from original value. + * gpiodvs definition: PU(0x2), KEEPER(0x3) + */ + case MSM_PULL_UP: + temp_pdpu = 0x02; + break; + case MSM_KEEPER: + temp_pdpu = 0x03; + break; + default: + temp_pdpu = 0x07; + break; + } + + temp_lh = set.val; + + checkgpiomap_result[phonestate][i] = + GET_RESULT_GPIO(temp_io, temp_pdpu, temp_lh); + } + + pr_info("[dvs_%s]-\n", __func__); +} + +static struct gpio_dvs_t msm_gpio_dvs = { + .result = &gpiomap_result, + .check_gpio_status = msm_check_gpio_status, + .count = AP_GPIO_MAX, + .check_sleep = false, +}; + +const struct secgpio_dvs_data msm_gpio_dvs_data = { + .gpio_dvs = &msm_gpio_dvs, +}; +EXPORT_SYMBOL_GPL(msm_gpio_dvs_data); +#endif /* CONFIG_SEC_GPIO_DVS */ + +#if IS_ENABLED(CONFIG_SEC_GPIO_DUMP) +static const char * const gpiomux_func_str[] = { + "GPIO", + "Func_1", + "Func_2", + "Func_3", + "Func_4", + "Func_5", + "Func_6", + "Func_7", + "Func_8", + "Func_9", + "Func_a", + "Func_b", + "Func_c", + "Func_d", + "Func_e", + "Func_f", +}; + +static const char * const gpiomux_pull_str[] = { + "PULL_NONE", + "PULL_DOWN", + "PULL_KEEPER", + "PULL_UP", +}; + +static const char * const gpiomux_dir_str[] = { + "IN", + "OUT", +}; + +static const char * const gpiomux_val_str[] = { + "LOW", + "HIGH", +}; + +void sec_ap_gpio_debug_print(void) +{ + struct gpio_chip *gp; + struct gpiomux_setting set = {0,}; + int i; + + if (!msm_pinctrl_data) { + pr_err("pinctrl data is not available\n"); + return; + } + + gp = &msm_pinctrl_data->chip; + + for (i = 0; i < AP_GPIO_MAX; i++) { + if (!gpiochip_line_is_valid(gp, i)) + continue; + + msm_gp_get_all(gp, i, &set); + + pr_info("GPIO[%u] %10s %10s %13s DRV_%dmA %10s\n", + i, + gpiomux_func_str[set.func], + gpiomux_dir_str[set.is_out], + gpiomux_pull_str[set.pull], + set.drv, + gpiomux_val_str[set.val]); + } +} +EXPORT_SYMBOL(sec_ap_gpio_debug_print); +#endif /* CONFIG_SEC_GPIO_DUMP */ + #ifdef CONFIG_DEBUG_FS #include diff --git a/drivers/pinctrl/qcom/pinctrl-spmi-gpio.c b/drivers/pinctrl/qcom/pinctrl-spmi-gpio.c index f019a277e597..2e2a3105213a 100644 --- a/drivers/pinctrl/qcom/pinctrl-spmi-gpio.c +++ b/drivers/pinctrl/qcom/pinctrl-spmi-gpio.c @@ -716,6 +716,88 @@ static void pmic_gpio_config_dbg_show(struct pinctrl_dev *pctldev, } } +#if IS_ENABLED(CONFIG_SEC_GPIO_DUMP) +#define MAX_PMIC 32 +static int pmic_count; +static struct gpio_chip *pmic_gpio_chip[MAX_PMIC]; + +static void pmic_gpio_sec_dbg_print(struct pinctrl_dev *pctldev) +{ + struct pmic_gpio_state *state = pinctrl_dev_get_drvdata(pctldev); + struct pmic_gpio_pad *pad; + int val, i, ret, function; + + static const char *const biases[] = { + "pull-up 30uA", "pull-up 1.5uA", "pull-up 31.5uA", + "pull-up 1.5uA + 30uA boost", "pull-down 10uA", "no pull" + }; + static const char *const buffer_types[] = { + "push-pull", "open-drain", "open-source" + }; + static const char *const strengths[] = { + "no", "high", "medium", "low" + }; + + pr_info("%s: chip.label:%s\n", __func__, state->chip.label); + + for (i = 0; i < state->chip.ngpio; i++) { + pad = pctldev->desc->pins[i].drv_data; + val = pmic_gpio_read(state, pad, PMIC_GPIO_REG_EN_CTL); + + if (val < 0 || !(val >> PMIC_GPIO_REG_MASTER_EN_SHIFT)) + pr_info(" gpio%-2d: ---\n", i); + else { + if (pad->input_enabled) { + ret = pmic_gpio_read(state, pad, PMIC_MPP_REG_RT_STS); + if (ret < 0) + continue; + + ret &= PMIC_MPP_REG_RT_STS_VAL_MASK; + pad->out_value = ret; + } + + if (!pad->lv_mv_type && + pad->function >= PMIC_GPIO_FUNC_INDEX_FUNC3) { + function = pad->function + (PMIC_GPIO_FUNC_INDEX_DTEST1 - + PMIC_GPIO_FUNC_INDEX_FUNC3); + } else { + function = pad->function; + } + + pr_info(" gpio%-2d: %-7s %-4s vin-%d %-27s %-10s %-2s %-7s atest-%d dtest-%d\n", + i, + pmic_gpio_functions[function], + pad->output_enabled ? "OUT" : "IN", + pad->power_source, + biases[pad->pullup], + buffer_types[pad->buffer_type], + pad->out_value ? "H" : "L", + strengths[pad->strength], + pad->atest, + pad->dtest_buffer); + } + } + + pr_info("\n"); +} + +static void pmic_gpio_sec_dbg_show(struct gpio_chip *chip) +{ + struct pmic_gpio_state *state = gpiochip_get_data(chip); + + pmic_gpio_sec_dbg_print(state->ctrl); +} + +void sec_pmic_gpio_debug_print(void) +{ + unsigned int i; + + for (i = 0; i < pmic_count; i++) + pmic_gpio_sec_dbg_show(pmic_gpio_chip[i]); +} +EXPORT_SYMBOL_GPL(sec_pmic_gpio_debug_print); +#endif /* CONFIG_SEC_GPIO_DUMP */ + static const struct pinconf_ops pmic_gpio_pinconf_ops = { .is_generic = true, .pin_config_group_get = pmic_gpio_config_get, @@ -1172,6 +1254,11 @@ static int pmic_gpio_probe(struct platform_device *pdev) state->chip.of_gpio_n_cells = 2; state->chip.can_sleep = false; +#if IS_ENABLED(CONFIG_SEC_GPIO_DUMP) + pr_info("%s: [%d]chip.label:%s\n", __func__, pmic_count, state->chip.label); + pmic_gpio_chip[pmic_count++] = &(state->chip); +#endif + state->ctrl = devm_pinctrl_register(dev, pctrldesc, state); if (IS_ERR(state->ctrl)) return PTR_ERR(state->ctrl); diff --git a/drivers/pinctrl/qcom/secgpio_dvs.c b/drivers/pinctrl/qcom/secgpio_dvs.c new file mode 100644 index 000000000000..1462ef72cfd9 --- /dev/null +++ b/drivers/pinctrl/qcom/secgpio_dvs.c @@ -0,0 +1,336 @@ +/* + * Samsung Mobile VE Group. + * + * drivers/gpio/secgpio_dvs.c + * + * Drivers for samsung gpio debugging & verification. + * + * Copyright (C) 2013, Samsung Electronics. + * + * 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "secgpio_dvs.h" +#include + +/* mutex */ +static DEFINE_MUTEX(gpio_debug_lock); + +/*sys fs*/ +struct class *secgpio_dvs_class; +EXPORT_SYMBOL(secgpio_dvs_class); + +struct device *secgpio_dotest; +EXPORT_SYMBOL(secgpio_dotest); + +/* extern GPIOMAP_RESULT GpioMap_result; */ +static struct gpio_dvs_t *gdvs_info; + +static ssize_t checked_secgpio_file_read( + struct device *dev, struct device_attribute *attr, char *buf); +static ssize_t checked_sleep_secgpio_file_read( + struct device *dev, struct device_attribute *attr, char *buf); +static ssize_t checked_secgpio_init_read_details( + struct device *dev, struct device_attribute *attr, char *buf); +static ssize_t checked_secgpio_sleep_read_details( + struct device *dev, struct device_attribute *attr, char *buf); +static ssize_t secgpio_checked_sleepgpio_read( + struct device *dev, struct device_attribute *attr, char *buf); +static ssize_t checked_secgpio_init_call(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len); + +static DEVICE_ATTR(gpioinit_check, 0664, + checked_secgpio_file_read, NULL); +static DEVICE_ATTR(gpiosleep_check, 0664, + checked_sleep_secgpio_file_read, NULL); +static DEVICE_ATTR(check_init_detail, 0664, + checked_secgpio_init_read_details, NULL); +static DEVICE_ATTR(check_sleep_detail, 0664, + checked_secgpio_sleep_read_details, NULL); +static DEVICE_ATTR(checked_sleepGPIO, 0664, + secgpio_checked_sleepgpio_read, NULL); +static DEVICE_ATTR(gpioinit_call, 0664, + NULL, checked_secgpio_init_call); + +static struct attribute *secgpio_dvs_attributes[] = { + &dev_attr_gpioinit_check.attr, + &dev_attr_gpiosleep_check.attr, + &dev_attr_check_init_detail.attr, + &dev_attr_check_sleep_detail.attr, + &dev_attr_checked_sleepGPIO.attr, + &dev_attr_gpioinit_call.attr, + NULL, +}; + +static struct attribute_group secgpio_dvs_attr_group = { + .attrs = secgpio_dvs_attributes, +}; + +static ssize_t checked_secgpio_file_read( + struct device *dev, struct device_attribute *attr, char *buf) +{ + int i = 0; + char temp_buf[20]; + struct gpio_dvs_t *gdvs = dev_get_drvdata(dev); + + for (i = 0; i < gdvs->count; i++) { + memset(temp_buf, 0, sizeof(char)*20); + snprintf(temp_buf, 20, "%x ", gdvs->result->init[i]); + strlcat(buf, temp_buf, PAGE_SIZE); + } + + return strlen(buf); +} + +static ssize_t checked_sleep_secgpio_file_read( + struct device *dev, struct device_attribute *attr, char *buf) +{ + int i = 0; + char temp_buf[20]; + struct gpio_dvs_t *gdvs = dev_get_drvdata(dev); + + for (i = 0; i < gdvs->count; i++) { + memset(temp_buf, 0, sizeof(char)*20); + snprintf(temp_buf, 20, "%x ", gdvs->result->sleep[i]); + strlcat(buf, temp_buf, PAGE_SIZE); + } + + return strlen(buf); +} + +static ssize_t checked_secgpio_init_read_details( + struct device *dev, struct device_attribute *attr, char *buf) +{ + int i = 0; + char temp_buf[20]; + struct gpio_dvs_t *gdvs = dev_get_drvdata(dev); + + for (i = 0; i < gdvs->count; i++) { + memset(temp_buf, 0, sizeof(char)*20); + snprintf(temp_buf, 20, "GI[%d] - %x\n ", + i, gdvs->result->init[i]); + strlcat(buf, temp_buf, PAGE_SIZE); + } + + return strlen(buf); +} +static ssize_t checked_secgpio_sleep_read_details( + struct device *dev, struct device_attribute *attr, char *buf) +{ + int i = 0; + char temp_buf[20]; + struct gpio_dvs_t *gdvs = dev_get_drvdata(dev); + + for (i = 0; i < gdvs->count; i++) { + memset(temp_buf, 0, sizeof(char)*20); + snprintf(temp_buf, 20, "GS[%d] - %x\n ", + i, gdvs->result->sleep[i]); + strlcat(buf, temp_buf, PAGE_SIZE); + } + + return strlen(buf); + +} + + +static ssize_t secgpio_checked_sleepgpio_read( + struct device *dev, struct device_attribute *attr, char *buf) +{ + struct gpio_dvs_t *gdvs = dev_get_drvdata(dev); + + if (gdvs->check_sleep) + return snprintf(buf, PAGE_SIZE, "1"); + else + return snprintf(buf, PAGE_SIZE, "0"); +} + +static ssize_t checked_secgpio_init_call(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + /* Check init gpio status */ + gpio_dvs_check_initgpio(); + + return len; +} + +void gpio_dvs_check_initgpio(void) +{ + if (gdvs_info && gdvs_info->check_gpio_status) + gdvs_info->check_gpio_status(PHONE_INIT); +} +EXPORT_SYMBOL(gpio_dvs_check_initgpio); + +void gpio_dvs_check_sleepgpio(void) +{ + if (unlikely(gdvs_info && !gdvs_info->check_sleep)) { + gdvs_info->check_gpio_status(PHONE_SLEEP); + gdvs_info->check_sleep = true; + } +} + +#ifdef CONFIG_OF +static const struct of_device_id secgpio_dvs_dt_match[] = { + { .compatible = "samsung,secgpio-dvs", + .data = (void *)&msm_gpio_dvs_data }, + { }, +}; +MODULE_DEVICE_TABLE(of, secgpio_dvs_dt_match); + +static struct secgpio_dvs_data *secgpio_dvs_get_soc_data( + struct platform_device *pdev) +{ + const struct of_device_id *match; + struct device_node *node = pdev->dev.of_node; + struct secgpio_dvs_data *data; + + match = of_match_node(secgpio_dvs_dt_match, node); + if (!match) { + dev_err(&pdev->dev, "failed to get SoC node\n"); + return NULL; + } + + data = (struct secgpio_dvs_data *)match->data; + if (!data) { + dev_err(&pdev->dev, "failed to get SoC data\n"); + return NULL; + } + + return data; +} +#else +static struct gpio_dvs_t *secgpio_dvs_get_soc_data(struct platform_device *pdev) +{ + return dev_get_platdata(&pdev->dev); +} +#endif + +static int secgpio_dvs_probe(struct platform_device *pdev) +{ + int ret = 0; + struct class *secgpio_dvs_class; + struct device *secgpio_dotest; + struct secgpio_dvs_data *data = secgpio_dvs_get_soc_data(pdev); + struct gpio_dvs_t *gdvs; + + pr_info("[secgpio_dvs] %s has been created!!!\n", __func__); + + if (!data) + return -ENODEV; + + gdvs = data->gpio_dvs; + + secgpio_dvs_class = class_create(THIS_MODULE, "secgpio_check"); + if (IS_ERR(secgpio_dvs_class)) { + ret = PTR_ERR(secgpio_dvs_class); + pr_err("Failed to create class(secgpio_check_all)"); + goto fail_out; + } + + secgpio_dotest = device_create(secgpio_dvs_class, + NULL, 0, NULL, "secgpio_check_all"); + if (IS_ERR(secgpio_dotest)) { + ret = PTR_ERR(secgpio_dotest); + pr_err("Failed to create device(secgpio_check_all)"); + goto fail1; + } + dev_set_drvdata(secgpio_dotest, gdvs); + gdvs_info = gdvs; + + ret = sysfs_create_group(&secgpio_dotest->kobj, + &secgpio_dvs_attr_group); + if (ret) { + pr_err("Failed to create sysfs group"); + goto fail2; + } + + return ret; + +fail2: + device_destroy(secgpio_dvs_class, 0); +fail1: + class_destroy(secgpio_dvs_class); +fail_out: + if (ret) + pr_err(" (err = %d)!\n", ret); + return ret; + +} + +static int secgpio_dvs_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver secgpio_dvs = { + .probe = secgpio_dvs_probe, + .remove = secgpio_dvs_remove, + .driver = { + .name = "secgpio_dvs", + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = of_match_ptr(secgpio_dvs_dt_match), +#endif + }, +}; + +static void gpio_debug_suspend_trace_probe(void *unused, + const char *action, int val, bool start) +{ + if (start && val > 0 && !strcmp("machine_suspend", action)) { + mutex_lock(&gpio_debug_lock); + gpio_dvs_check_sleepgpio(); + mutex_unlock(&gpio_debug_lock); + } +} + +static int __init secgpio_dvs_init(void) +{ + int ret; + + ret = platform_driver_register(&secgpio_dvs); + pr_info("[secgpio_dvs] %s has been initialized!!!, ret=%d\n", __func__, ret); + + /* Register callback for cheking sleep gpio status */ + ret = register_trace_suspend_resume( + gpio_debug_suspend_trace_probe, NULL); + if (ret) { + pr_err("%s: Failed to register suspend trace callback, ret=%d\n", + __func__, ret); + return ret; + } + + return ret; +} + +static void __exit secgpio_dvs_exit(void) +{ + unregister_trace_suspend_resume( + gpio_debug_suspend_trace_probe, NULL); + + platform_driver_unregister(&secgpio_dvs); +} + +module_init(secgpio_dvs_init); +module_exit(secgpio_dvs_exit); + +MODULE_DESCRIPTION("Samsung GPIO debugging and verification"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pinctrl/qcom/secgpio_dvs.h b/drivers/pinctrl/qcom/secgpio_dvs.h new file mode 100644 index 000000000000..ca57d51d5bfa --- /dev/null +++ b/drivers/pinctrl/qcom/secgpio_dvs.h @@ -0,0 +1,50 @@ +/* + * secgpio_dvs.h -- Samsung GPIO debugging and verification system + */ + +#ifndef __SECGPIO_DVS_H +#define __SECGPIO_DVS_H + +#include + +enum gdvs_phone_status { + PHONE_INIT = 0, + PHONE_SLEEP, + GDVS_PHONE_STATUS_MAX +}; + +enum gdvs_io_value { + GDVS_IO_FUNC = 0x00, + GDVS_IO_IN, + GDVS_IO_OUT, +}; + +enum gdvs_pupd_value { + GDVS_PUPD_NP = 0x00, + GDVS_PUPD_PD, + GDVS_PUPD_PU, + GDVS_PUPD_KEEPER, + GDVS_PUPD_ERR = 0x3F +}; + +struct gpiomap_result { + unsigned char *init; + unsigned char *sleep; +}; + +struct gpio_dvs_t { + struct gpiomap_result *result; + unsigned int count; + bool check_sleep; + void (*check_gpio_status)(unsigned char phonestate); +}; + +struct secgpio_dvs_data { + struct gpio_dvs_t *gpio_dvs; +}; + +void gpio_dvs_check_initgpio(void); +void gpio_dvs_check_sleepgpio(void); + +extern const struct secgpio_dvs_data msm_gpio_dvs_data; +#endif /* __SECGPIO_DVS_H */ diff --git a/drivers/power/reset/qcom-dload-mode.c b/drivers/power/reset/qcom-dload-mode.c index fb14529c0083..297320a9351d 100644 --- a/drivers/power/reset/qcom-dload-mode.c +++ b/drivers/power/reset/qcom-dload-mode.c @@ -259,6 +259,10 @@ static int qcom_dload_panic(struct notifier_block *this, unsigned long event, struct qcom_dload *poweroff = container_of(this, struct qcom_dload, panic_nb); poweroff->in_panic = true; + + if (IS_ENABLED(CONFIG_SEC_QC_QCOM_REBOOT_REASON)) + return NOTIFY_OK; + if (enable_dump) msm_enable_dump_mode(true); return NOTIFY_OK; @@ -415,3 +419,16 @@ module_exit(qcom_dload_driver_exit); MODULE_DESCRIPTION("MSM Download Mode Driver"); MODULE_LICENSE("GPL v2"); + +#if IS_ENABLED(CONFIG_SEC_QC_QCOM_REBOOT_REASON) +void qcom_set_dload_mode(int on) +{ + if (on) + set_download_mode(QCOM_DOWNLOAD_FULLDUMP); + else + set_download_mode(QCOM_DOWNLOAD_NODUMP); + + pr_warn("set_dload_mode <%d> (%pS)\n", on, __builtin_return_address(0)); +} +EXPORT_SYMBOL(qcom_set_dload_mode); +#endif diff --git a/drivers/regulator/pmic_class/Kconfig b/drivers/regulator/pmic_class/Kconfig new file mode 100644 index 000000000000..222cb450b0fe --- /dev/null +++ b/drivers/regulator/pmic_class/Kconfig @@ -0,0 +1,8 @@ +if REGULATOR + +config DRV_SAMSUNG_PMIC + tristate "Samsung PMIC sysfs system" + help + This driver supports a Samsung PMIC sysfs. + +endif diff --git a/drivers/regulator/pmic_class/Makefile b/drivers/regulator/pmic_class/Makefile new file mode 100644 index 000000000000..60a4f5769a3c --- /dev/null +++ b/drivers/regulator/pmic_class/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_DRV_SAMSUNG_PMIC) += pmic_class.o + +ccflags-y := -Wformat diff --git a/drivers/regulator/pmic_class/pmic_class.c b/drivers/regulator/pmic_class/pmic_class.c new file mode 100644 index 000000000000..83c813c7f6e0 --- /dev/null +++ b/drivers/regulator/pmic_class/pmic_class.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include + +/* CAUTION : Do not be declared as external pmic_class */ +static struct class *pmic_class; +static atomic_t pmic_dev; + +struct device *pmic_device_create(void *drvdata, const char *fmt) +{ + struct device *dev; + + if (!pmic_class) { + pr_err("Not yet created class(pmic)!\n"); + BUG(); + } + + if (IS_ERR(pmic_class)) { + pr_err("Failed to create class(pmic) %ld\n", PTR_ERR(pmic_class)); + BUG(); + } + + dev = device_create(pmic_class, NULL, atomic_inc_return(&pmic_dev), + drvdata, "%s", fmt); + if (IS_ERR(dev)) + pr_err("Failed to create device %s %ld\n", fmt, PTR_ERR(dev)); + else + pr_debug("%s : %s : %d\n", __func__, fmt, dev->devt); + + return dev; +} +EXPORT_SYMBOL(pmic_device_create); + +void pmic_device_destroy(dev_t devt) +{ + pr_info("%s : %d\n", __func__, devt); + device_destroy(pmic_class, devt); +} +EXPORT_SYMBOL(pmic_device_destroy); + +static int __init pmic_class_create(void) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + pmic_class = class_create("pmic"); +#else + pmic_class = class_create(THIS_MODULE, "pmic"); +#endif + + if (IS_ERR(pmic_class)) { + pr_err("Failed to create class(pmic) %ld\n", PTR_ERR(pmic_class)); + return PTR_ERR(pmic_class); + } + + return 0; +} +arch_initcall(pmic_class_create); + +MODULE_DESCRIPTION("pmic sysfs"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/s2dos05/Kconfig b/drivers/regulator/s2dos05/Kconfig new file mode 100644 index 000000000000..6c1d9029b62f --- /dev/null +++ b/drivers/regulator/s2dos05/Kconfig @@ -0,0 +1,16 @@ +if REGULATOR + +config REGULATOR_S2DOS05 + tristate "Samsung S2DOS05 regulator" + depends on I2C + help + This driver controls a Samsung S2DOS05 regulator + via I2C bus. + +config REGULATOR_S2DOS05_ELVSS_FD + bool "Samsung S2DOS05 ELVSS FD" + help + If ELVSS is enabled, FD is on. + +endif + diff --git a/drivers/regulator/s2dos05/Makefile b/drivers/regulator/s2dos05/Makefile new file mode 100644 index 000000000000..764f38086150 --- /dev/null +++ b/drivers/regulator/s2dos05/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_REGULATOR_S2DOS05) += s2dos05-regulator.o +s2dos05-regulator-$(CONFIG_REGULATOR_S2DOS05) += s2dos05.o s2dos05_powermeter.o + +ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG + diff --git a/drivers/regulator/s2dos05/s2dos05.c b/drivers/regulator/s2dos05/s2dos05.c new file mode 100644 index 000000000000..85d209874954 --- /dev/null +++ b/drivers/regulator/s2dos05/s2dos05.c @@ -0,0 +1,1550 @@ +/* + * s2dos05.c - Regulator driver for the Samsung s2dos05 + * + * Copyright (C) 2016 Samsung Electronics + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) +#include +#endif +#if IS_ENABLED(CONFIG_REGULATOR_DEBUG_CONTROL) +#include +#endif +#if IS_ENABLED(CONFIG_SEC_PM) +#include +#include +#endif /* CONFIG_SEC_PM */ + +#if IS_ENABLED(CONFIG_SEC_ABC) +#include +#endif /* CONFIG_SEC_ABC */ + +#if IS_ENABLED(CONFIG_SEC_PM_QCOM) +extern int msm_drm_register_notifier_client(struct notifier_block *nb); +extern int msm_drm_unregister_notifier_client(struct notifier_block *nb); +#endif + +struct s2dos05_data { + struct s2dos05_dev *iodev; + int num_regulators; + struct regulator_dev *rdev[S2DOS05_REGULATOR_MAX]; +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) + u8 read_addr; + u8 read_val; + struct device *dev; +#endif +#if IS_ENABLED(CONFIG_SEC_PM) + struct notifier_block fb_block __maybe_unused; + struct delayed_work fd_work __maybe_unused; + bool fd_work_init; +#endif /* CONFIG_SEC_PM */ +#if IS_ENABLED(CONFIG_SEC_ABC) + atomic_t i2c_fail_count; +#endif /* CONFIG_SEC_ABC */ +}; + +#if IS_ENABLED(CONFIG_SEC_ABC) +#define s2dos05_i2c_abc_event(arg...) \ + __s2dos05_i2c_abc_event((char *)__func__, ##arg) + +static void __s2dos05_i2c_abc_event(char *func, struct s2dos05_data *info, int ret) +{ + char buf[64]; + char *type; + int count; + const int fail_threshold_cnt = 2; + +#if IS_ENABLED(CONFIG_SEC_FACTORY) + type = "INFO"; +#else + type = "WARN"; +#endif /* CONFIG_SEC_FACTORY */ + + if (ret < 0) { + count = atomic_inc_return(&info->i2c_fail_count); + if (count >= fail_threshold_cnt) { + atomic_set(&info->i2c_fail_count, 0); + snprintf(buf, sizeof(buf), "MODULE=pmic@%s=%s_fail", type, func); + sec_abc_send_event(buf); + } + } else { + atomic_set(&info->i2c_fail_count, 0); + } +} + +static void s2dos05_irq_abc_event(const char *irq_desc) +{ + char buf[64]; + char *type; + +#if IS_ENABLED(CONFIG_SEC_FACTORY) + type = "WARN"; +#else + type = "WARN"; /* Diamond: black screen issue in user binary */ +#endif /* CONFIG_SEC_FACTORY */ + + snprintf(buf, sizeof(buf), "MODULE=pmic@%s=s2dos05_%s", type, irq_desc); + sec_abc_send_event(buf); +} +#else +#define s2dos05_i2c_abc_event(arg...) do {} while (0) +#define s2dos05_irq_abc_event(arg...) do {} while (0) +#endif /* CONFIG_SEC_ABC */ + +int s2dos05_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest) +{ + struct s2dos05_data *info = i2c_get_clientdata(i2c); + struct s2dos05_dev *s2dos05 = info->iodev; + int ret; + + mutex_lock(&s2dos05->i2c_lock); + ret = i2c_smbus_read_byte_data(i2c, reg); + mutex_unlock(&s2dos05->i2c_lock); + s2dos05_i2c_abc_event(info, ret); + if (ret < 0) { + pr_info("%s:%s reg(0x%02hhx), ret(%d)\n", + MFD_DEV_NAME, __func__, reg, ret); + return ret; + } + + ret &= 0xff; + *dest = ret; + return 0; +} +EXPORT_SYMBOL_GPL(s2dos05_read_reg); + +int s2dos05_bulk_read(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct s2dos05_data *info = i2c_get_clientdata(i2c); + struct s2dos05_dev *s2dos05 = info->iodev; + int ret; + + mutex_lock(&s2dos05->i2c_lock); + ret = i2c_smbus_read_i2c_block_data(i2c, reg, count, buf); + mutex_unlock(&s2dos05->i2c_lock); + s2dos05_i2c_abc_event(info, ret); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(s2dos05_bulk_read); + +int s2dos05_read_word(struct i2c_client *i2c, u8 reg) +{ + struct s2dos05_data *info = i2c_get_clientdata(i2c); + struct s2dos05_dev *s2dos05 = info->iodev; + int ret; + + mutex_lock(&s2dos05->i2c_lock); + ret = i2c_smbus_read_word_data(i2c, reg); + mutex_unlock(&s2dos05->i2c_lock); + s2dos05_i2c_abc_event(info, ret); + if (ret < 0) + return ret; + + return ret; +} +EXPORT_SYMBOL_GPL(s2dos05_read_word); + +int s2dos05_write_reg(struct i2c_client *i2c, u8 reg, u8 value) +{ + struct s2dos05_data *info = i2c_get_clientdata(i2c); + struct s2dos05_dev *s2dos05 = info->iodev; + int ret; + + mutex_lock(&s2dos05->i2c_lock); + ret = i2c_smbus_write_byte_data(i2c, reg, value); + mutex_unlock(&s2dos05->i2c_lock); + s2dos05_i2c_abc_event(info, ret); + if (ret < 0) + pr_info("%s:%s reg(0x%02hhx), ret(%d)\n", + MFD_DEV_NAME, __func__, reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(s2dos05_write_reg); + +int s2dos05_bulk_write(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct s2dos05_data *info = i2c_get_clientdata(i2c); + struct s2dos05_dev *s2dos05 = info->iodev; + int ret; + + mutex_lock(&s2dos05->i2c_lock); + ret = i2c_smbus_write_i2c_block_data(i2c, reg, count, buf); + mutex_unlock(&s2dos05->i2c_lock); + s2dos05_i2c_abc_event(info, ret); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(s2dos05_bulk_write); + +int s2dos05_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask) +{ + struct s2dos05_data *info = i2c_get_clientdata(i2c); + struct s2dos05_dev *s2dos05 = info->iodev; + int ret; + u8 old_val, new_val; + + mutex_lock(&s2dos05->i2c_lock); + ret = i2c_smbus_read_byte_data(i2c, reg); + if (ret >= 0) { + old_val = ret & 0xff; + new_val = (val & mask) | (old_val & (~mask)); + ret = i2c_smbus_write_byte_data(i2c, reg, new_val); + } + mutex_unlock(&s2dos05->i2c_lock); + s2dos05_i2c_abc_event(info, ret); + return ret; +} +EXPORT_SYMBOL_GPL(s2dos05_update_reg); + +static int s2m_enable(struct regulator_dev *rdev) +{ + struct s2dos05_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + + return s2dos05_update_reg(i2c, rdev->desc->enable_reg, + rdev->desc->enable_mask, + rdev->desc->enable_mask); +} + +static int s2m_disable_regmap(struct regulator_dev *rdev) +{ + struct s2dos05_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + u8 val; + + if (rdev->desc->enable_is_inverted) + val = rdev->desc->enable_mask; + else + val = 0; + + return s2dos05_update_reg(i2c, rdev->desc->enable_reg, + val, rdev->desc->enable_mask); +} + +static int s2m_is_enabled_regmap(struct regulator_dev *rdev) +{ + struct s2dos05_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + int ret; + u8 val; + + ret = s2dos05_read_reg(i2c, rdev->desc->enable_reg, &val); + if (ret < 0) + return ret; + + if (rdev->desc->enable_is_inverted) + return (val & rdev->desc->enable_mask) == 0; + else + return (val & rdev->desc->enable_mask) != 0; +} + +static int s2m_get_voltage_sel_regmap(struct regulator_dev *rdev) +{ + struct s2dos05_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + int ret; + u8 val; + + ret = s2dos05_read_reg(i2c, rdev->desc->vsel_reg, &val); + if (ret < 0) + return ret; + + val &= rdev->desc->vsel_mask; + + return val; +} + +static int s2m_set_voltage_sel_regmap(struct regulator_dev *rdev, unsigned sel) +{ + struct s2dos05_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + int ret; + + ret = s2dos05_update_reg(i2c, rdev->desc->vsel_reg, + sel, rdev->desc->vsel_mask); + if (ret < 0) + goto out; + + if (rdev->desc->apply_bit) + ret = s2dos05_update_reg(i2c, rdev->desc->apply_reg, + rdev->desc->apply_bit, + rdev->desc->apply_bit); + return ret; +out: + pr_warn("%s: failed to set voltage_sel_regmap\n", rdev->desc->name); + return ret; +} + +static int s2m_set_voltage_sel_regmap_buck(struct regulator_dev *rdev, + unsigned sel) +{ + struct s2dos05_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + int ret; + + ret = s2dos05_write_reg(i2c, rdev->desc->vsel_reg, sel); + if (ret < 0) + goto out; + + if (rdev->desc->apply_bit) + ret = s2dos05_update_reg(i2c, rdev->desc->apply_reg, + rdev->desc->apply_bit, + rdev->desc->apply_bit); + return ret; +out: + pr_warn("%s: failed to set voltage_sel_regmap\n", rdev->desc->name); + return ret; +} + +static int s2m_set_voltage_time_sel(struct regulator_dev *rdev, + unsigned int old_selector, + unsigned int new_selector) +{ + int old_volt, new_volt; + + /* sanity check */ + if (!rdev->desc->ops->list_voltage) + return -EINVAL; + + old_volt = rdev->desc->ops->list_voltage(rdev, old_selector); + new_volt = rdev->desc->ops->list_voltage(rdev, new_selector); + + if (old_selector < new_selector) + return DIV_ROUND_UP(new_volt - old_volt, S2DOS05_RAMP_DELAY); + + return 0; +} + +#if IS_ENABLED(CONFIG_SEC_PM) +static int s2m_ssd_convert_uA_to_reg_val(bool enable, int min_uA, int max_uA, + u8 *val, u8 *mask) +{ + u8 sel = 0; + + if (enable) { + while (2000 * (sel + 1) < min_uA) + sel++; + + if (2000 * (sel + 1) > max_uA) + return -EINVAL; + } + + *val = (!enable << 3) | (sel << 5); + *mask = S2DOS05_ELVSS_SSD_EN_MASK | S2DOS05_ELVSS_SEL_SSD_MASK; + + return 0; +} + + +static int s2m_set_elvss_ssd_current_limit(struct regulator_dev *rdev, + int min_uA, int max_uA) +{ + struct s2dos05_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + int ret; + bool enable; + u8 val = 0, mask = 0; + + enable = min_uA || max_uA; + + ret = s2m_ssd_convert_uA_to_reg_val(enable, min_uA, max_uA, &val, &mask); + if (ret < 0) + return ret; + + ret = s2dos05_update_reg(i2c, S2DOS05_REG_SSD_TSD, val, mask); + return ret; +} + +static int s2m_get_elvss_ssd_current_limit(struct regulator_dev *rdev) +{ + struct s2dos05_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + int ret; + bool enable; + u8 val; + + ret = s2dos05_read_reg(i2c, S2DOS05_REG_SSD_TSD, &val); + if (ret < 0) + return ret; + + enable = !(val & S2DOS05_ELVSS_SSD_EN_MASK); + + if (!enable) + return 0; + + ret = (val & S2DOS05_ELVSS_SEL_SSD_MASK) >> 5; + return (ret + 1) * 2000; +} + +#if IS_ENABLED(CONFIG_ARCH_QCOM) +static int s2m_elvss_fd_is_enabled(struct regulator_dev *rdev) +{ + /* Always return false due to timing issue */ + return 0; +} +#else +static int s2m_elvss_fd_is_enabled(struct regulator_dev *rdev) +{ + struct s2dos05_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + int ret; + u8 val; + + ret = s2dos05_read_reg(i2c, S2DOS05_REG_UVLO_FD, &val); + if (ret < 0) { + /* If failed to read FD status, suppose it is not enabled */ + pr_info("%s: fail to read i2c address\n", __func__); + return 0; + } + + return !(val & 0x1); +} +#endif + +#define DEFAULT_ENABLE_FD_DELAY_MS 500 + +static int s2m_elvss_fd_enable(struct regulator_dev *rdev) +{ + int ret = 0; + struct s2dos05_data *info = rdev_get_drvdata(rdev); + unsigned int delay = info->iodev->pdata->enable_fd_delay_ms; + + if (delay) + delay = msecs_to_jiffies(delay); + + /* To guarantee fd_work is initialized */ + if (info->fd_work_init) { + ret = schedule_delayed_work(&info->fd_work, delay); + if(!ret) + pr_info("%s: schedule_delayed_work error!\n", __func__); + } + + return ret; +} + +static int s2m_elvss_fd_disable(struct regulator_dev *rdev) +{ + struct s2dos05_data *info = rdev_get_drvdata(rdev); + + if (info->fd_work_init) + cancel_delayed_work_sync(&info->fd_work); + + return 0; +} +#endif /* CONFIG_SEC_PM */ + +static struct regulator_ops s2dos05_ldo_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = s2m_is_enabled_regmap, + .enable = s2m_enable, + .disable = s2m_disable_regmap, + .get_voltage_sel = s2m_get_voltage_sel_regmap, + .set_voltage_sel = s2m_set_voltage_sel_regmap, + .set_voltage_time_sel = s2m_set_voltage_time_sel, +}; + +static struct regulator_ops s2dos05_buck_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = s2m_is_enabled_regmap, + .enable = s2m_enable, + .disable = s2m_disable_regmap, + .get_voltage_sel = s2m_get_voltage_sel_regmap, + .set_voltage_sel = s2m_set_voltage_sel_regmap_buck, + .set_voltage_time_sel = s2m_set_voltage_time_sel, +}; + +#if IS_ENABLED(CONFIG_SEC_PM) +static struct regulator_ops s2dos05_elvss_ssd_ops = { + .set_current_limit = s2m_set_elvss_ssd_current_limit, + .get_current_limit = s2m_get_elvss_ssd_current_limit, +}; + +static struct regulator_ops s2dos05_elvss_fd_ops = { + .is_enabled = s2m_elvss_fd_is_enabled, + .enable = s2m_elvss_fd_enable, + .disable = s2m_elvss_fd_disable, +}; +#endif /* CONFIG_SEC_PM */ + +#define _BUCK(macro) S2DOS05_BUCK##macro +#define _buck_ops(num) s2dos05_buck_ops##num + +#define _LDO(macro) S2DOS05_LDO##macro +#define _REG(ctrl) S2DOS05_REG##ctrl +#define _ldo_ops(num) s2dos05_ldo_ops##num +#define _MASK(macro) S2DOS05_ENABLE_MASK##macro +#define _TIME(macro) S2DOS05_ENABLE_TIME##macro + +#define BUCK_DESC(_name, _id, _ops, m, s, v, e, em, t) { \ + .name = _name, \ + .id = _id, \ + .ops = _ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = m, \ + .uV_step = s, \ + .n_voltages = S2DOS05_BUCK_N_VOLTAGES, \ + .vsel_reg = v, \ + .vsel_mask = S2DOS05_BUCK_VSEL_MASK, \ + .enable_reg = e, \ + .enable_mask = em, \ + .enable_time = t \ +} + +#define LDO_DESC(_name, _id, _ops, m, s, v, e, em, t) { \ + .name = _name, \ + .id = _id, \ + .ops = _ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = m, \ + .uV_step = s, \ + .n_voltages = S2DOS05_LDO_N_VOLTAGES, \ + .vsel_reg = v, \ + .vsel_mask = S2DOS05_LDO_VSEL_MASK, \ + .enable_reg = e, \ + .enable_mask = em, \ + .enable_time = t \ +} + +#if IS_ENABLED(CONFIG_SEC_PM) +#define ELVSS_DESC(_name, _id) { \ + .name = _name, \ + .id = _id, \ + .ops = &s2dos05_elvss_ssd_ops, \ + .type = REGULATOR_CURRENT, \ + .owner = THIS_MODULE, \ +} + +#define ELVSS_FD_DESC(_name, _id) { \ + .name = _name, \ + .id = _id, \ + .ops = &s2dos05_elvss_fd_ops, \ + .type = REGULATOR_CURRENT, \ + .owner = THIS_MODULE, \ +} +#endif /* CONFIG_SEC_PM */ + +static struct regulator_desc regulators[S2DOS05_REGULATOR_MAX] = { + /* name, id, ops, min_uv, uV_step, vsel_reg, enable_reg */ + LDO_DESC("s2dos05-ldo1", _LDO(1), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP1), _REG(_LDO1_CFG), + _REG(_EN), _MASK(_L1), _TIME(_LDO)), + LDO_DESC("s2dos05-ldo2", _LDO(2), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP1), _REG(_LDO2_CFG), + _REG(_EN), _MASK(_L2), _TIME(_LDO)), + LDO_DESC("s2dos05-ldo3", _LDO(3), &_ldo_ops(), _LDO(_MIN2), + _LDO(_STEP1), _REG(_LDO3_CFG), + _REG(_EN), _MASK(_L3), _TIME(_LDO)), + LDO_DESC("s2dos05-ldo4", _LDO(4), &_ldo_ops(), _LDO(_MIN2), + _LDO(_STEP1), _REG(_LDO4_CFG), + _REG(_EN), _MASK(_L4), _TIME(_LDO)), + BUCK_DESC("s2dos05-buck1", _BUCK(1), &_buck_ops(), _BUCK(_MIN1), + _BUCK(_STEP1), _REG(_BUCK_VOUT), + _REG(_EN), _MASK(_B1), _TIME(_BUCK)), +#if IS_ENABLED(CONFIG_SEC_PM) + ELVSS_DESC("s2dos05-elvss-ssd", S2DOS05_ELVSS_SSD), + ELVSS_FD_DESC("s2dos05-avdd-elvdd-elvss-fd", S2DOS05_ELVSS_FD), +#endif /* CONFIG_SEC_PM */ +}; + +#if IS_ENABLED(CONFIG_SEC_PM) +static void s2dos05_irq_notifier_call_chain(struct s2dos05_data *s2dos05, + u8 reg_val) +{ + int i; + int data = reg_val; + + for (i = 0; i < s2dos05->num_regulators; i++) + regulator_notifier_call_chain(s2dos05->rdev[i], + REGULATOR_EVENT_FAIL, (void *)&data); +} +#endif /* CONFIG_SEC_PM */ + +static irqreturn_t s2dos05_irq_thread(int irq, void *irq_data) +{ + struct s2dos05_data *s2dos05 = irq_data; + u8 val = 0; +#if IS_ENABLED(CONFIG_SEC_PM) + u8 scp_val[2] = { 0, }; + const char *irq_bit[] = { "ocd", "uvlo", "scp", "ssd", "tsd", "pwrmt" }; + char irq_name[32]; + ssize_t ret = 0; + unsigned long bit, tmp; +#endif /* CONFIG_SEC_PM */ + + s2dos05_read_reg(s2dos05->iodev->i2c, S2DOS05_REG_IRQ, &val); + pr_info("%s:irq(%d) S2DOS05_REG_IRQ : 0x%02hhx\n", __func__, irq, val); + +#if IS_ENABLED(CONFIG_SEC_PM) + tmp = val; + for_each_set_bit(bit, &tmp, ARRAY_SIZE(irq_bit)) { + ret += sprintf(irq_name + ret, " %s", irq_bit[bit]); + s2dos05_irq_abc_event(irq_bit[bit]); + } + + pr_info("%s: irq:%s\n", __func__, irq_name); + + /* Show which regulator's SCP occurs */ + if (0x04 & val) { + if (s2dos05->iodev->is_sm3080) { + /* SM3080 */ + s2dos05_read_reg(s2dos05->iodev->i2c, INT_STATUS1, &scp_val[0]); + pr_info("%s:INT_STATUS1(0x%02hhx)\n", __func__, scp_val[0]); + } else { + /* S2DOS05 */ + s2dos05_read_reg(s2dos05->iodev->i2c, FAULT_STATUS1, &scp_val[0]); + s2dos05_read_reg(s2dos05->iodev->i2c, FAULT_STATUS2, &scp_val[1]); + pr_info("%s:FAULT_STATUS1(0x%02hhx), FAULT_STATUS2(0x%02hhx)\n", __func__, scp_val[0], scp_val[1]); + } + } +#endif /* CONFIG_SEC_PM */ + +#if IS_ENABLED(CONFIG_SEC_PM) + s2dos05_irq_notifier_call_chain(s2dos05, val); +#endif /* CONFIG_SEC_PM */ + + return IRQ_HANDLED; +} + +#if IS_ENABLED(CONFIG_OF) +static int s2dos05_pmic_dt_parse_pdata(struct device *dev, + struct s2dos05_platform_data *pdata) +{ + struct device_node *pmic_np, *regulators_np, *reg_np; + struct s2dos05_regulator_data *rdata; + size_t i; + int ret; + u32 val; + + pmic_np = dev->of_node; + if (!pmic_np) { + dev_err(dev, "could not find pmic sub-node\n"); + return -ENODEV; + } + + pdata->dp_pmic_irq = of_get_named_gpio(pmic_np, "s2dos05,s2dos05_int", 0); + if (pdata->dp_pmic_irq < 0) + pr_err("%s error reading s2dos05_irq = %d\n", + __func__, pdata->dp_pmic_irq); + + pdata->wakeup = of_property_read_bool(pmic_np, "s2dos05,wakeup"); + +#if IS_ENABLED(CONFIG_SEC_PM) + if (!of_property_read_string(pmic_np, "sec_disp_pmic_name", + &pdata->sec_disp_pmic_name)) + dev_info(dev, "sec_disp_pmic_name: %s\n", + pdata->sec_disp_pmic_name); +#endif /* CONFIG_SEC_PM */ + + pdata->adc_mode = 0; + ret = of_property_read_u32(pmic_np, "adc_mode", &val); + if (ret) + return -EINVAL; + pdata->adc_mode = val; + + pdata->adc_sync_mode = 0; + ret = of_property_read_u32(pmic_np, "adc_sync_mode", &val); + if (ret) + return -EINVAL; + pdata->adc_sync_mode = val; + +#if IS_ENABLED(CONFIG_SEC_PM) + pdata->ocl_elvss = -1; + ret = of_property_read_u32(pmic_np, "ocl_elvss", &val); + if (!ret) { + pdata->ocl_elvss = val; + dev_info(dev, "get ocl elvss value: %d\n", pdata->ocl_elvss); + } + + pdata->enable_fd_delay_ms = DEFAULT_ENABLE_FD_DELAY_MS; + ret = of_property_read_u32(pmic_np, "enable_fd_delay_ms", &val); + if (!ret) { + pdata->enable_fd_delay_ms = val; + dev_info(dev, "enable_fd_delay_ms: %u\n", pdata->enable_fd_delay_ms); + } +#endif + + regulators_np = of_find_node_by_name(pmic_np, "regulators"); + if (!regulators_np) { + dev_err(dev, "could not find regulators sub-node\n"); + return -EINVAL; + } + + /* count the number of regulators to be supported in pmic */ + pdata->num_regulators = 0; + for_each_child_of_node(regulators_np, reg_np) { + pdata->num_regulators++; + } + + rdata = devm_kzalloc(dev, sizeof(*rdata) * + pdata->num_regulators, GFP_KERNEL); + if (!rdata) { + dev_err(dev, + "could not allocate memory for regulator data\n"); + return -ENOMEM; + } + + pdata->regulators = rdata; + pdata->num_rdata = 0; + for_each_child_of_node(regulators_np, reg_np) { + for (i = 0; i < ARRAY_SIZE(regulators); i++) + if (!of_node_cmp(reg_np->name, + regulators[i].name)) + break; + + if (i == ARRAY_SIZE(regulators)) { + dev_warn(dev, + "don't know how to configure regulator %s\n", + reg_np->name); + continue; + } + + rdata->id = i; + rdata->initdata = of_get_regulator_init_data( + dev, reg_np, + ®ulators[i]); + rdata->reg_node = reg_np; + rdata++; + pdata->num_rdata++; + } + of_node_put(regulators_np); + + return 0; +} +#else +static int s2dos05_pmic_dt_parse_pdata(struct s2dos05_dev *iodev, + struct s2dos05_platform_data *pdata) +{ + return 0; +} +#endif /* CONFIG_OF */ +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) +static ssize_t s2dos05_read_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct s2dos05_data *s2dos05 = dev_get_drvdata(dev); + int ret; + u8 val, reg_addr; + + if (buf == NULL) { + pr_info("%s: empty buffer\n", __func__); + return -1; + } + + ret = kstrtou8(buf, 0, ®_addr); + if (ret < 0) + pr_info("%s: fail to transform i2c address\n", __func__); + + ret = s2dos05_read_reg(s2dos05->iodev->i2c, reg_addr, &val); + if (ret < 0) + pr_info("%s: fail to read i2c address\n", __func__); + + pr_info("%s: reg(0x%02hhx) data(0x%02hhx)\n", __func__, reg_addr, val); + s2dos05->read_addr = reg_addr; + s2dos05->read_val = val; + + return size; +} + +static ssize_t s2dos05_read_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct s2dos05_data *s2dos05 = dev_get_drvdata(dev); + return sprintf(buf, "0x%02hhx: 0x%02hhx\n", s2dos05->read_addr, + s2dos05->read_val); +} + +static ssize_t s2dos05_write_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct s2dos05_data *s2dos05 = dev_get_drvdata(dev); + int ret; + u8 reg = 0, data = 0; + + if (buf == NULL) { + pr_info("%s: empty buffer\n", __func__); + return size; + } + + ret = sscanf(buf, "0x%02hhx 0x%02hhx", ®, &data); + if (ret != 2) { + pr_info("%s: input error\n", __func__); + return size; + } + + pr_info("%s: reg(0x%02hhx) data(0x%02hhx)\n", __func__, reg, data); + + ret = s2dos05_write_reg(s2dos05->iodev->i2c, reg, data); + if (ret < 0) + pr_info("%s: fail to write i2c addr/data\n", __func__); + + return size; +} + +static ssize_t s2dos05_write_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "echo (register addr.) (data) > s2dos05_write\n"); +} + +#define ATTR_REGULATOR (2) +static struct pmic_device_attribute regulator_attr[] = { + PMIC_ATTR(s2dos05_write, S_IRUGO | S_IWUSR, s2dos05_write_show, s2dos05_write_store), + PMIC_ATTR(s2dos05_read, S_IRUGO | S_IWUSR, s2dos05_read_show, s2dos05_read_store), +}; + +static int s2dos05_create_sysfs(struct s2dos05_data *s2dos05) +{ + struct device *s2dos05_pmic = s2dos05->dev; + struct device *dev = s2dos05->iodev->dev; + char device_name[32] = {0, }; + int err = -ENODEV, i = 0; + + pr_info("%s()\n", __func__); + s2dos05->read_addr = 0; + s2dos05->read_val = 0; + + /* Dynamic allocation for device name */ + snprintf(device_name, sizeof(device_name) - 1, "%s@%s", + dev_driver_string(dev), dev_name(dev)); + + s2dos05_pmic = pmic_device_create(s2dos05, device_name); + s2dos05->dev = s2dos05_pmic; + + /* Create sysfs entries */ + for (i = 0; i < ATTR_REGULATOR; i++) { + err = device_create_file(s2dos05_pmic, ®ulator_attr[i].dev_attr); + if (err) + goto remove_pmic_device; + } + + return 0; + +remove_pmic_device: + for (i--; i >= 0; i--) + device_remove_file(s2dos05_pmic, ®ulator_attr[i].dev_attr); + pmic_device_destroy(s2dos05_pmic->devt); + + return -1; +} +#endif + +#if IS_ENABLED(CONFIG_SEC_PM) +static ssize_t enable_fd_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct s2dos05_data *info = dev_get_drvdata(dev); + struct i2c_client *i2c = info->iodev->i2c; + u8 uvlo_fd; + bool enabled; + + s2dos05_read_reg(i2c, S2DOS05_REG_UVLO_FD, &uvlo_fd); + + dev_info(&i2c->dev, "%s: uvlo_fd(0x%02X)\n", __func__, uvlo_fd); + + enabled = !(uvlo_fd & 1); + + return sprintf(buf, "%s\n", enabled ? "enabled" : "disabled"); +} + +static ssize_t enable_fd_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct s2dos05_data *info = dev_get_drvdata(dev); + struct i2c_client *i2c = info->iodev->i2c; + int ret; + bool enable; + u8 uvlo_fd; + + ret = strtobool(buf, &enable); + if (ret) + return ret; + + dev_info(&i2c->dev, "%s: enable(%d)\n", __func__, enable); + + uvlo_fd = !enable; + + ret = s2dos05_update_reg(i2c, S2DOS05_REG_UVLO_FD, uvlo_fd, 1); + if (ret < 0) { + dev_err(&i2c->dev, "%s: Failed to update FD(%d)\n", __func__, ret); + return ret; + } + + return count; +} + +static DEVICE_ATTR(enable_fd, 0664, enable_fd_show, enable_fd_store); + +static void __maybe_unused handle_fd_work(struct work_struct *work) +{ + struct s2dos05_data *s2dos05 = + container_of(to_delayed_work(work), struct s2dos05_data, fd_work); + struct i2c_client *i2c = s2dos05->iodev->i2c; + u8 uvlo_fd = 0; + int ret; + + dev_info(&i2c->dev, "%s: Enable FD\n", __func__); + ret = s2dos05_update_reg(i2c, S2DOS05_REG_UVLO_FD, 0, 1); + if (ret < 0) + dev_err(&i2c->dev, "%s: Failed to enable FD(%d)\n", __func__, ret); + s2dos05_read_reg(i2c, S2DOS05_REG_UVLO_FD, &uvlo_fd); + dev_info(&i2c->dev, "%s: uvlo_fd(0x%02X)\n", __func__, uvlo_fd); +} + +static int __maybe_unused fb_state_change(struct notifier_block *nb, unsigned long val, + void *data) +{ + struct s2dos05_data *s2dos05 = + container_of(nb, struct s2dos05_data, fb_block); +#if !IS_ENABLED(CONFIG_SEC_PM_QCOM) + struct i2c_client *i2c = s2dos05->iodev->i2c; + struct fb_event *evdata = data; + struct fb_info *info = evdata->info; + int ret; +#endif + unsigned int blank; + + if (val != FB_EVENT_BLANK) + return 0; + + +#if IS_ENABLED(CONFIG_SEC_PM_QCOM) + blank = *(int *)data; +#else + /* + * If FBNODE is not zero, it is not primary display(LCD) + * and don't need to process these scheduling. + */ + if (info->node) + return NOTIFY_OK; + + blank = *(int *)evdata->data; +#endif + + if (blank == FB_BLANK_UNBLANK) { +#if IS_ENABLED(CONFIG_SEC_PM_QCOM) + schedule_delayed_work(&s2dos05->fd_work, msecs_to_jiffies(500)); +#else + dev_info(&i2c->dev, "%s: Enable FD\n", __func__); + ret = s2dos05_update_reg(i2c, S2DOS05_REG_UVLO_FD, 0, 1); + if (ret < 0) + dev_err(&i2c->dev, "%s: Failed to enable FD(%d)\n", + __func__, ret); +#endif + } +#if IS_ENABLED(CONFIG_SEC_PM_QCOM) + else { + cancel_delayed_work_sync(&s2dos05->fd_work); + } +#endif + + return NOTIFY_OK; +} + +#if IS_ENABLED(CONFIG_SEC_FACTORY) +#define VALID_REG S2DOS05_REG_EN /* Register address for validation */ +#define VALID_MASK 0xE0 /* NA(reserved) bit */ + +static ssize_t validation_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct s2dos05_data *s2dos05 = dev_get_drvdata(dev); + struct i2c_client *i2c = s2dos05->iodev->i2c; + int ret; + bool result = false; + u8 val; + + ret = s2dos05_read_reg(i2c, VALID_REG, &val); + if (ret < 0) { + dev_err(dev, "%s: fail to read reg\n", __func__); + goto out; + } + + dev_info(dev, "%s: initial state: reg(0x%02X) data(0x%02X)\n", __func__, + VALID_REG, val); + + ret = s2dos05_update_reg(i2c, VALID_REG, VALID_MASK, VALID_MASK); + if (ret < 0) { + dev_err(dev, "%s: fail to update reg\n", __func__); + goto out; + } + + ret = s2dos05_read_reg(i2c, VALID_REG, &val); + if (ret < 0) { + dev_err(dev, "%s: fail to read reg\n", __func__); + goto out; + } + + dev_info(dev, "%s: updated state: reg(0x%02x) data(0x%02x)\n", __func__, + VALID_REG, val); + + result = (val & VALID_MASK) == VALID_MASK; + + /* No need change to init value(0x00), but, do it */ + s2dos05_update_reg(i2c, VALID_REG, 0x00, VALID_MASK); + +out: + dev_info(dev, "%s: result: %s\n", __func__, result ? "ok" : "not ok"); + + return sprintf(buf, "%d\n", result); +} +static DEVICE_ATTR(validation, 0444, validation_show, NULL); + +/* for LDO burnt: enable/disable all regulator, support only for sm3080 due to hidden register */ +#define NUM_REG 7 /* number of regulator to control */ + +struct power_sequence { + int regulator; + int delay; /* delay after regulator control */ +}; + +static ssize_t enable_pwr_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct s2dos05_data *info = dev_get_drvdata(dev); + struct i2c_client *i2c = info->iodev->i2c; + u8 reg_status, ctrl; + u8 val, mask; + int i; + bool enable; + int ret; + + /* { regulator - delay } sequence */ + struct power_sequence en_order[NUM_REG] = { + { S2DOS05_LDO1, 0 }, + { S2DOS05_LDO4, 900 }, + { SM3080_AVDD, 100 }, + { SM3080_ELVDD, 10 }, + { SM3080_ELVSS, 1000 }, + { S2DOS05_LDO2, 0 }, + { S2DOS05_LDO3, 0 } }; + struct power_sequence dis_order[NUM_REG] = { + { S2DOS05_LDO3, 5 }, + { S2DOS05_LDO2, 450 }, + { SM3080_ELVDD, 0 }, + { SM3080_ELVSS, 50 }, + { SM3080_AVDD, 50 }, + { S2DOS05_LDO4, 50 }, + { S2DOS05_LDO1, 10 } }; + struct power_sequence *order; /* be selected by enable */ + + ret = strtobool(buf, &enable); + if (ret) + return ret; + + s2dos05_read_reg(i2c, S2DOS05_REG_STAT, ®_status); + s2dos05_read_reg(i2c, S2DOS05_REG_EN, &ctrl); + dev_info(&i2c->dev, "++%s: en(%d), status(0x%02x), ctrl(0x%02x)\n", + __func__, enable, reg_status, ctrl); + + order = enable ? en_order : dis_order; + + for (i = 0; i < NUM_REG; i++) { + val = enable << order[i].regulator; + mask = 1 << order[i].regulator; + + ret = s2dos05_update_reg(i2c, S2DOS05_REG_EN, val, mask); + + msleep(order[i].delay); + } + + /* enable fd because of el power on */ + if (enable) + ret = s2dos05_update_reg(i2c, S2DOS05_REG_UVLO_FD, 0, 1); + + s2dos05_read_reg(i2c, S2DOS05_REG_STAT, ®_status); + s2dos05_read_reg(i2c, S2DOS05_REG_EN, &ctrl); + dev_info(&i2c->dev, "--%s: en(%d), status(0x%02x), ctrl(0x%02x)\n", + __func__, enable, reg_status, ctrl); + + return count; +} + +static ssize_t enable_pwr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct s2dos05_data *s2dos05 = dev_get_drvdata(dev); + struct i2c_client *i2c = s2dos05->iodev->i2c; + u8 reg_status, ctrl; + + s2dos05_read_reg(i2c, S2DOS05_REG_STAT, ®_status); + s2dos05_read_reg(i2c, S2DOS05_REG_EN, &ctrl); + dev_info(&i2c->dev, "%s: status(0x%02x), ctrl(0x%02x)\n", + __func__, reg_status, ctrl); + + return sprintf(buf, "0x%02x\n0x%02x\n", reg_status, ctrl); +} +static DEVICE_ATTR(enable_pwr, 0664, enable_pwr_show, enable_pwr_store); +#endif /* CONFIG_SEC_FACTORY */ + +#if IS_ENABLED(CONFIG_SEC_PM) && IS_ENABLED(CONFIG_SEC_PM_QCOM) +static void sec_set_fd(struct s2dos05_data *info) +{ + info->fb_block.notifier_call = fb_state_change; + msm_drm_register_notifier_client(&info->fb_block); + INIT_DELAYED_WORK(&info->fd_work, handle_fd_work); + info->fd_work_init = false; +} +#elif IS_ENABLED(CONFIG_SEC_PM) && IS_ENABLED(CONFIG_REGULATOR_S2DOS05_ELVSS_FD) +static void sec_set_fd(struct s2dos05_data *info) +{ + INIT_DELAYED_WORK(&info->fd_work, handle_fd_work); + info->fd_work_init = true; +} +#elif IS_ENABLED(CONFIG_SEC_PM) +static void sec_set_fd(struct s2dos05_data *info) +{ + info->fb_block.notifier_call = fb_state_change; + fb_register_client(&info->fb_block); + info->fd_work_init = false; +} +#else +static void sec_set_fd(struct s2dos05_data *info) +{ + info->fd_work_init = false; +} +#endif + +static int s2dos05_sec_pm_init(struct s2dos05_data *info) +{ + struct s2dos05_dev *iodev = info->iodev; + struct device *dev = &iodev->i2c->dev; + const char *sec_disp_pmic_name = iodev->pdata->sec_disp_pmic_name; + int ret = 0; + + if (sec_disp_pmic_name) + iodev->sec_disp_pmic_dev = + sec_device_create(info, sec_disp_pmic_name); + else + iodev->sec_disp_pmic_dev = + sec_device_create(info, "disp_pmic"); + + if (unlikely(IS_ERR(iodev->sec_disp_pmic_dev))) { + ret = PTR_ERR(iodev->sec_disp_pmic_dev); + dev_err(dev, "%s: Failed to create disp_pmic(%d)\n", __func__, + ret); + return ret; + } + + dev_info(dev, "%s: Enable FD\n", __func__); + ret = s2dos05_update_reg(iodev->i2c, S2DOS05_REG_UVLO_FD, 0, 1); + if (ret < 0) { + dev_err(dev, "%s: Failed to enable FD(%d)\n", __func__, ret); + goto remove_sec_disp_pmic_dev; + } + + /* To separate FD operation */ + sec_set_fd(info); + + ret = device_create_file(iodev->sec_disp_pmic_dev, &dev_attr_enable_fd); + if (ret) { + dev_err(dev, "%s: Failed to create enable_fd(%d)\n", __func__, + ret); + goto remove_sec_disp_pmic_dev; + } + +#if IS_ENABLED(CONFIG_SEC_FACTORY) + ret = device_create_file(iodev->sec_disp_pmic_dev, &dev_attr_validation); + if (ret) { + pr_err("s2dos05_sysfs: failed to create validation file, %s\n", + dev_attr_validation.attr.name); + goto remove_sec_disp_enable_fd; + } + + if (iodev->is_sm3080) { + ret = device_create_file(iodev->sec_disp_pmic_dev, &dev_attr_enable_pwr); + if (ret) { + pr_err("s2dos05_sysfs: failed to create enable_pwr file, %s\n", + dev_attr_enable_pwr.attr.name); + goto remove_sec_disp_validation; + } + } +#endif /* CONFIG_SEC_FACTORY */ + + return 0; + +#if IS_ENABLED(CONFIG_SEC_FACTORY) +remove_sec_disp_validation: + device_remove_file(info->iodev->sec_disp_pmic_dev, &dev_attr_validation); +remove_sec_disp_enable_fd: + device_remove_file(info->iodev->sec_disp_pmic_dev, &dev_attr_enable_fd); +#endif /* CONFIG_SEC_FACTORY */ +remove_sec_disp_pmic_dev: + sec_device_destroy(iodev->sec_disp_pmic_dev->devt); + + return ret; +} + +static void s2dos05_sec_pm_deinit(struct s2dos05_data *info) +{ + device_remove_file(info->iodev->sec_disp_pmic_dev, &dev_attr_enable_fd); +#if IS_ENABLED(CONFIG_SEC_PM_QCOM) + msm_drm_unregister_notifier_client(&info->fb_block); +#endif +#if IS_ENABLED(CONFIG_SEC_FACTORY) + device_remove_file(info->iodev->sec_disp_pmic_dev, &dev_attr_validation); + device_remove_file(info->iodev->sec_disp_pmic_dev, &dev_attr_enable_pwr); +#endif /* CONFIG_SEC_FACTORY */ + sec_device_destroy(info->iodev->sec_disp_pmic_dev->devt); +} +#endif /* CONFIG_SEC_PM */ + +static int __s2dos05_pmic_probe(struct i2c_client *i2c) +{ + struct s2dos05_dev *iodev; + struct s2dos05_platform_data *pdata = i2c->dev.platform_data; + struct regulator_config config = { }; + struct s2dos05_data *s2dos05; + size_t i; + int ret = 0; + u8 val = 0, mask = 0; + + pr_info("%s:%s\n", MFD_DEV_NAME, __func__); + + iodev = devm_kzalloc(&i2c->dev, sizeof(struct s2dos05_dev), GFP_KERNEL); + if (!iodev) { + dev_err(&i2c->dev, "%s: Failed to alloc mem for s2dos05\n", + __func__); + return -ENOMEM; + } + + if (i2c->dev.of_node) { + pdata = devm_kzalloc(&i2c->dev, + sizeof(struct s2dos05_platform_data), GFP_KERNEL); + if (!pdata) { + dev_err(&i2c->dev, "Failed to allocate memory\n"); + ret = -ENOMEM; + goto err_pdata; + } + + ret = s2dos05_pmic_dt_parse_pdata(&i2c->dev, pdata); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to get device of_node\n"); + goto err_pdata; + } + + i2c->dev.platform_data = pdata; + } else + pdata = i2c->dev.platform_data; + + iodev->dev = &i2c->dev; + iodev->i2c = i2c; + + if (pdata) { + iodev->pdata = pdata; + iodev->wakeup = pdata->wakeup; + } else { + ret = -EINVAL; + goto err_pdata; + } + mutex_init(&iodev->i2c_lock); + + s2dos05 = devm_kzalloc(&i2c->dev, sizeof(struct s2dos05_data), + GFP_KERNEL); + if (!s2dos05) { + pr_info("[%s:%d] if (!s2dos05)\n", __FILE__, __LINE__); + ret = -ENOMEM; + goto err_s2dos05_data; + } + +#if IS_ENABLED(CONFIG_SEC_ABC) + atomic_set(&s2dos05->i2c_fail_count, 0); +#endif /* CONFIG_SEC_ABC */ + i2c_set_clientdata(i2c, s2dos05); + s2dos05->iodev = iodev; + s2dos05->num_regulators = pdata->num_rdata; + + for (i = 0; i < pdata->num_rdata; i++) { + int id = pdata->regulators[i].id; + config.dev = &i2c->dev; + config.init_data = pdata->regulators[i].initdata; + config.driver_data = s2dos05; + config.of_node = pdata->regulators[i].reg_node; + s2dos05->rdev[i] = devm_regulator_register(&i2c->dev, + ®ulators[id], &config); + if (IS_ERR(s2dos05->rdev[i])) { + ret = PTR_ERR(s2dos05->rdev[i]); + dev_err(&i2c->dev, "regulator init failed for %d\n", + id); + s2dos05->rdev[i] = NULL; + goto err_s2dos05_data; + } +#if IS_ENABLED(CONFIG_REGULATOR_DEBUG_CONTROL) + ret = devm_regulator_debug_register(&i2c->dev, s2dos05->rdev[i]); + if (ret) + dev_err(&i2c->dev, "failed to register debug regulator for %lu, rc=%d\n", + i, ret); +#endif + } + +#if IS_ENABLED(CONFIG_SEC_PM) + ret = s2dos05_read_reg(i2c, S2DOS05_REG_DEVICE_ID_PGM, &val); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to read DEVICE ID address\n"); + goto err_s2dos05_data; + } + + if (val & (1 << 7)) { + iodev->is_sm3080 = true; + dev_info(&i2c->dev, "SM3080 DEVICE ID: 0x%02X\n", val); + } + + /* set OCL_ELVSS */ + if (pdata->ocl_elvss > -1) { + s2dos05_update_reg(i2c, S2DOS05_REG_OCL, pdata->ocl_elvss, S2DOS05_OCL_ELVSS_MASK); + pr_info("%s: set ocl elvss: %d\n", __func__, pdata->ocl_elvss); + } + + ret = s2dos05_sec_pm_init(s2dos05); + if (ret < 0) + goto err_s2dos05_data; +#endif /* CONFIG_SEC_PM */ + + iodev->adc_mode = pdata->adc_mode; + iodev->adc_sync_mode = pdata->adc_sync_mode; + if (iodev->adc_mode > 0) + s2dos05_powermeter_init(iodev); + + val = (S2DOS05_IRQ_PWRMT_MASK | S2DOS05_IRQ_TSD_MASK + | S2DOS05_IRQ_UVLO_MASK | S2DOS05_IRQ_OCD_MASK); + mask = (S2DOS05_IRQ_PWRMT_MASK | S2DOS05_IRQ_TSD_MASK | S2DOS05_IRQ_SSD_MASK + | S2DOS05_IRQ_SCP_MASK | S2DOS05_IRQ_UVLO_MASK | S2DOS05_IRQ_OCD_MASK); + ret = s2dos05_update_reg(iodev->i2c, S2DOS05_REG_IRQ_MASK, val, mask); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to mask IRQ MASK address\n"); + return ret; + } + + if (pdata->dp_pmic_irq > 0) { + iodev->dp_pmic_irq = gpio_to_irq(pdata->dp_pmic_irq); + pr_info("%s : dp_pmic_irq = %d\n", __func__, iodev->dp_pmic_irq); + if (iodev->dp_pmic_irq > 0) { + ret = request_threaded_irq(iodev->dp_pmic_irq, + NULL, s2dos05_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "dp-pmic-irq", s2dos05); + if (ret) { + dev_err(&i2c->dev, + "%s: Failed to Request IRQ\n", __func__); + goto err_s2dos05_data; + } + + ret = enable_irq_wake(iodev->dp_pmic_irq); + if (ret < 0) + dev_err(&i2c->dev, + "%s: Failed to Enable Wakeup Source(%d)\n", + __func__, ret); + } else { + dev_err(&i2c->dev, "%s: Failed gpio_to_irq(%d)\n", + __func__, iodev->dp_pmic_irq); + goto err_s2dos05_data; + } + } +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) + ret = s2dos05_create_sysfs(s2dos05); + if (ret < 0) { + pr_err("%s: s2dos05_create_sysfs fail\n", __func__); + goto err_s2dos05_data; + } +#endif + return ret; + +err_s2dos05_data: + mutex_destroy(&iodev->i2c_lock); +err_pdata: + return ret; +} + +#if IS_ENABLED(CONFIG_OF) +static struct of_device_id s2dos05_i2c_dt_ids[] = { + { .compatible = "samsung,s2dos05pmic" }, + { }, +}; +#endif /* CONFIG_OF */ + +static int __s2dos05_pmic_remove(struct i2c_client *i2c) +{ + struct s2dos05_data *info = i2c_get_clientdata(i2c); +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) + struct device *s2dos05_pmic = info->dev; + int i = 0; + + dev_info(&i2c->dev, "%s\n", __func__); + + /* Remove sysfs entries */ + for (i = 0; i < ATTR_REGULATOR; i++) + device_remove_file(s2dos05_pmic, ®ulator_attr[i].dev_attr); + pmic_device_destroy(s2dos05_pmic->devt); +#else + dev_info(&i2c->dev, "%s\n", __func__); +#endif + if (info->iodev->adc_mode > 0) + s2dos05_powermeter_deinit(info->iodev); + +#if IS_ENABLED(CONFIG_SEC_PM) + s2dos05_sec_pm_deinit(info); +#endif /* CONFIG_SEC_PM */ + + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) +static int s2dos05_pmic_probe(struct i2c_client *i2c) +{ + return __s2dos05_pmic_probe(i2c); +} +#else +static int s2dos05_pmic_probe(struct i2c_client *i2c, + const struct i2c_device_id *dev_id) +{ + return __s2dos05_pmic_probe(i2c); +} +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +static void s2dos05_pmic_remove(struct i2c_client *i2c) +{ + __s2dos05_pmic_remove(i2c); +} +#else +static int s2dos05_pmic_remove(struct i2c_client *i2c) +{ + return __s2dos05_pmic_remove(i2c); +} +#endif + +#if IS_ENABLED(CONFIG_PM) +static int s2dos05_pmic_suspend(struct device *dev) +{ + struct i2c_client *i2c = to_i2c_client(dev); + struct s2dos05_data *info = i2c_get_clientdata(i2c); + struct s2dos05_dev *s2dos05 = info->iodev; + + pr_info("%s adc_mode : %d\n", __func__, s2dos05->adc_mode); + if (s2dos05->adc_mode > 0) { + s2dos05_read_reg(s2dos05->i2c, + S2DOS05_REG_PWRMT_CTRL2, &s2dos05->adc_en_val); + if (s2dos05->adc_en_val & 0x80) + s2dos05_update_reg(s2dos05->i2c, + S2DOS05_REG_PWRMT_CTRL2, + 0, ADC_EN_MASK); + } + + return 0; +} + +static int s2dos05_pmic_resume(struct device *dev) +{ + struct i2c_client *i2c = to_i2c_client(dev); + struct s2dos05_data *info = i2c_get_clientdata(i2c); + struct s2dos05_dev *s2dos05 = info->iodev; + + pr_info("%s adc_mode : %d\n", __func__, s2dos05->adc_mode); + if (s2dos05->adc_mode > 0) { +#if IS_ENABLED(CONFIG_SEC_PM) + int ret; + ret = s2dos05_update_reg(s2dos05->i2c, S2DOS05_REG_PWRMT_CTRL2, + s2dos05->adc_en_val & 0x80, ADC_EN_MASK); + if (ret < 0) + pr_err("%s: Failed to update_reg: %d\n", __func__, ret); +#else + s2dos05_update_reg(s2dos05->i2c, S2DOS05_REG_PWRMT_CTRL2, + s2dos05->adc_en_val & 0x80, ADC_EN_MASK); +#endif /* CONFIG_SEC_PM */ + } + + return 0; +} +#else +#define s2dos05_pmic_suspend NULL +#define s2dos05_pmic_resume NULL +#endif /* CONFIG_PM */ + +static const struct dev_pm_ops s2dos05_pmic_pm = { + .suspend = s2dos05_pmic_suspend, + .resume = s2dos05_pmic_resume, +}; + +#if IS_ENABLED(CONFIG_OF) +static const struct i2c_device_id s2dos05_pmic_id[] = { + {"s2dos05-regulator", 0}, + {}, +}; +#endif + +static struct i2c_driver s2dos05_i2c_driver = { + .driver = { + .name = "s2dos05-regulator", + .owner = THIS_MODULE, + .pm = &s2dos05_pmic_pm, +#if IS_ENABLED(CONFIG_OF) + .of_match_table = s2dos05_i2c_dt_ids, +#endif /* CONFIG_OF */ + .suppress_bind_attrs = true, + }, + .probe = s2dos05_pmic_probe, + .remove = s2dos05_pmic_remove, + .id_table = s2dos05_pmic_id, +}; + +static int __init s2dos05_i2c_init(void) +{ + pr_info("%s:%s\n", MFD_DEV_NAME, __func__); + return i2c_add_driver(&s2dos05_i2c_driver); +} +subsys_initcall(s2dos05_i2c_init); + +static void __exit s2dos05_i2c_exit(void) +{ + i2c_del_driver(&s2dos05_i2c_driver); +} +module_exit(s2dos05_i2c_exit); + +MODULE_DESCRIPTION("SAMSUNG s2dos05 Regulator Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/s2dos05/s2dos05_powermeter.c b/drivers/regulator/s2dos05/s2dos05_powermeter.c new file mode 100644 index 000000000000..193eeb1d010e --- /dev/null +++ b/drivers/regulator/s2dos05/s2dos05_powermeter.c @@ -0,0 +1,632 @@ +/* + * s2dos05_powermeter.c + * + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * http://www.samsung.com + * + * 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 +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) +#include +#endif + +#define CURRENT_METER 1 +#define POWER_METER 2 +#define RAWCURRENT_METER 3 +#define SYNC_MODE 1 +#define ASYNC_MODE 2 + +#define MICRO 100 + +struct adc_info { + struct i2c_client *i2c; + u8 adc_mode; + u8 adc_sync_mode; + unsigned long *adc_val; + u8 adc_ctrl1; +#if IS_ENABLED(CONFIG_SEC_PM) + bool is_sm3080; +#endif /* CONFIG_SEC_PM */ +}; + +enum s2dos05_adc_ch { + ADC_CH0 = 0, + ADC_CH1, + ADC_CH2, + ADC_CH3, + ADC_CH4, + ADC_CH5, + ADC_CH6, + ADC_CH7, + ADC_CH8, +}; + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) +static const unsigned long current_coeffs[S2DOS05_MAX_ADC_CHANNEL] = + {CURRENT_ELVDD, CURRENT_ELVSS, CURRENT_AVDD, + CURRENT_BUCK, CURRENT_L1, CURRENT_L2, CURRENT_L3, CURRENT_L4}; + +static const unsigned long power_coeffs[S2DOS05_MAX_ADC_CHANNEL] = + {POWER_ELVDD, POWER_ELVSS, POWER_AVDD, + POWER_BUCK, POWER_L1, POWER_L2, POWER_L3, POWER_L4}; + +static void s2m_adc_read_current_data(struct adc_info *adc_meter, int channel) +{ + u8 data_l = 0; + + s2dos05_update_reg(adc_meter->i2c, S2DOS05_REG_PWRMT_CTRL2, + channel, ADC_PTR_MASK); + s2dos05_read_reg(adc_meter->i2c, S2DOS05_REG_PWRMT_DATA, &data_l); + adc_meter->adc_val[channel] = data_l; +} + +static void s2m_adc_read_power_data(struct adc_info *adc_meter, int channel) +{ + u8 data_l = 0, data_h = 0; + + /* even channel */ + s2dos05_update_reg(adc_meter->i2c, S2DOS05_REG_PWRMT_CTRL2, + 2*channel, ADC_PTR_MASK); + s2dos05_read_reg(adc_meter->i2c, S2DOS05_REG_PWRMT_DATA, &data_l); + + /* odd channel */ + s2dos05_update_reg(adc_meter->i2c, S2DOS05_REG_PWRMT_CTRL2, + 2 * channel + 1, ADC_PTR_MASK); + s2dos05_read_reg(adc_meter->i2c, S2DOS05_REG_PWRMT_DATA, &data_h); + + adc_meter->adc_val[channel] = ((data_h & 0xff) << 8) | (data_l & 0xff); +} + +static void s2m_adc_read_data(struct device *dev, int channel) +{ + struct adc_info *adc_meter = dev_get_drvdata(dev); + size_t i; + u8 temp; + + /* ASYNCRD bit '1' --> 2ms delay --> read in case of ADC Async mode */ + if (adc_meter->adc_sync_mode == ASYNC_MODE) { + + s2dos05_read_reg(adc_meter->i2c, S2DOS05_REG_PWRMT_CTRL1, &temp); + if (!(temp & 0x40)) { + switch (adc_meter->adc_mode) { + case CURRENT_METER: + case POWER_METER: + if (channel < 0) { + for (i = 0; i < S2DOS05_MAX_ADC_CHANNEL; i++) + adc_meter->adc_val[i] = 0; + } else + adc_meter->adc_val[channel] = 0; + break; + default: + dev_err(dev, "%s: invalid adc mode(%d)\n", + __func__, adc_meter->adc_mode); + return; + } + } + s2dos05_update_reg(adc_meter->i2c, S2DOS05_REG_PWRMT_CTRL1, + ADC_ASYNCRD_MASK, ADC_ASYNCRD_MASK); + usleep_range(2000, 2100); + } + + switch (adc_meter->adc_mode) { + case CURRENT_METER: + if (channel < 0) { + for (i = 0; i < S2DOS05_MAX_ADC_CHANNEL; i++) + s2m_adc_read_current_data(adc_meter, i); + } else + s2m_adc_read_current_data(adc_meter, channel); + break; + case POWER_METER: + if (channel < 0) { + for (i = 0; i < S2DOS05_MAX_ADC_CHANNEL; i++) + s2m_adc_read_power_data(adc_meter, i); + } else + s2m_adc_read_power_data(adc_meter, channel); + break; + default: + dev_err(dev, "%s: invalid adc mode(%d)\n", + __func__, adc_meter->adc_mode); + } +#if IS_ENABLED(CONFIG_SEC_PM) + if (adc_meter->is_sm3080) { + s2dos05_update_reg(adc_meter->i2c, S2DOS05_REG_PWRMT_CTRL2, + 0, ADC_EN_MASK); + usleep_range(150, 200); + s2dos05_update_reg(adc_meter->i2c, S2DOS05_REG_PWRMT_CTRL2, + ADC_EN_MASK, ADC_EN_MASK); + dev_info(dev, "%s: power meter on/off\n", __func__); + } +#endif /* CONFIG_SEC_PM */ +} + +static unsigned long get_coeff(struct device *dev, u8 adc_reg_num) +{ + struct adc_info *adc_meter = dev_get_drvdata(dev); + unsigned long coeff; + + if (adc_meter->adc_mode == CURRENT_METER) { + if (adc_reg_num < S2DOS05_MAX_ADC_CHANNEL) + coeff = current_coeffs[adc_reg_num]; + else { + dev_err(dev, "%s: invalid adc regulator number(%d)\n", + __func__, adc_reg_num); + coeff = 0; + } + } else if (adc_meter->adc_mode == POWER_METER) { + if (adc_reg_num < S2DOS05_MAX_ADC_CHANNEL) + coeff = power_coeffs[adc_reg_num]; + else { + dev_err(dev, "%s: invalid adc regulator number(%d)\n", + __func__, adc_reg_num); + coeff = 0; + } + } else { + dev_err(dev, "%s: invalid adc mode(%d)\n", + __func__, adc_meter->adc_mode); + coeff = 0; + } + return coeff; +} + +static ssize_t adc_val_all_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adc_info *adc_meter = dev_get_drvdata(dev); + size_t i = 0; + int cnt = 0, chan = 0; + + s2m_adc_read_data(dev, -1); + for (i = 0; i < S2DOS05_MAX_ADC_CHANNEL; i++) { + chan = ADC_CH0 + i; + if (adc_meter->adc_mode == POWER_METER) + cnt += snprintf(buf + cnt, PAGE_SIZE, "CH%d:%7lu uW (%7lu) ", + chan, (adc_meter->adc_val[chan] * get_coeff(dev, chan)) / MICRO, + adc_meter->adc_val[chan]); + else + cnt += snprintf(buf + cnt, PAGE_SIZE, "CH%d:%7lu uA (%7lu) ", + chan, (adc_meter->adc_val[chan] * get_coeff(dev, chan)), + adc_meter->adc_val[chan]); + if (i == S2DOS05_MAX_ADC_CHANNEL / 2 - 1) + cnt += snprintf(buf + cnt, PAGE_SIZE, "\n"); + } + + cnt += snprintf(buf + cnt, PAGE_SIZE, "\n"); + return cnt; +} + +static ssize_t adc_en_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adc_info *adc_meter = dev_get_drvdata(dev); + u8 adc_ctrl3; + + s2dos05_read_reg(adc_meter->i2c, S2DOS05_REG_PWRMT_CTRL2, &adc_ctrl3); + if ((adc_ctrl3 & ADC_EN_MASK) == ADC_EN_MASK) + return snprintf(buf, PAGE_SIZE, "ADC enable (0x%02hhx)\n", adc_ctrl3); + else + return snprintf(buf, PAGE_SIZE, "ADC disable (0x%02hhx)\n", adc_ctrl3); +} + +static ssize_t adc_en_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adc_info *adc_meter = dev_get_drvdata(dev); + int ret; + u8 temp, val; + + ret = kstrtou8(buf, 16, &temp); + if (ret) + return -EINVAL; + + switch (temp) { + case 0: + val = 0x00; + break; + case 1: + val = ADC_EN_MASK; + break; + default: + val = 0x00; + break; + } + + s2dos05_update_reg(adc_meter->i2c, S2DOS05_REG_PWRMT_CTRL2, + val, ADC_EN_MASK); + return count; +} + +static ssize_t adc_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adc_info *adc_meter = dev_get_drvdata(dev); + u8 adc_ctrl3; + + s2dos05_read_reg(adc_meter->i2c, S2DOS05_REG_PWRMT_CTRL2, &adc_ctrl3); + + adc_ctrl3 = adc_ctrl3 & ADC_PGEN_MASK; + switch (adc_ctrl3) { + case CURRENT_MODE: + return snprintf(buf, PAGE_SIZE, "CURRENT MODE (%d)\n", CURRENT_METER); + case POWER_MODE: + return snprintf(buf, PAGE_SIZE, "POWER MODE (%d)\n", POWER_METER); + default: + return snprintf(buf, PAGE_SIZE, "error (0x%02hhx)\n", adc_ctrl3); + } +} + +static ssize_t adc_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adc_info *adc_meter = dev_get_drvdata(dev); + int ret; + u8 temp, val; + + ret = kstrtou8(buf, 16, &temp); + if (ret) + return -EINVAL; + + switch (temp) { + case CURRENT_METER: + adc_meter->adc_mode = CURRENT_METER; + val = CURRENT_MODE; + break; + case POWER_METER: + adc_meter->adc_mode = POWER_METER; + val = POWER_MODE; + break; + default: + val = CURRENT_MODE; + break; + } + s2dos05_update_reg(adc_meter->i2c, S2DOS05_REG_PWRMT_CTRL2, + (ADC_EN_MASK | val), (ADC_EN_MASK | ADC_PGEN_MASK)); + return count; + +} + +static ssize_t adc_sync_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adc_info *adc_meter = dev_get_drvdata(dev); + + switch (adc_meter->adc_sync_mode) { + case SYNC_MODE: + return snprintf(buf, PAGE_SIZE, + "SYNC_MODE (%d)\n", adc_meter->adc_sync_mode); + case ASYNC_MODE: + return snprintf(buf, PAGE_SIZE, + "ASYNC_MODE (%d)\n", adc_meter->adc_sync_mode); + default: + return snprintf(buf, PAGE_SIZE, + "error (%d)\n", adc_meter->adc_sync_mode); + } +} + +static ssize_t adc_sync_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adc_info *adc_meter = dev_get_drvdata(dev); + int ret; + u8 temp; + + ret = kstrtou8(buf, 16, &temp); + if (ret) + return -EINVAL; + + switch (temp) { + case SYNC_MODE: + adc_meter->adc_sync_mode = SYNC_MODE; + break; + case ASYNC_MODE: + adc_meter->adc_sync_mode = ASYNC_MODE; + break; + default: + adc_meter->adc_sync_mode = SYNC_MODE; + break; + } + + return count; + +} + +static int convert_adc_val(struct device *dev, char *buf, int channel) +{ + struct adc_info *adc_meter = dev_get_drvdata(dev); + + s2m_adc_read_data(dev, channel); + + if (adc_meter->adc_mode == POWER_METER) + return snprintf(buf, PAGE_SIZE, "%7lu uW\n", + (adc_meter->adc_val[channel] * get_coeff(dev, channel)) / MICRO); + else + return snprintf(buf, PAGE_SIZE, "%7lu uA\n", + adc_meter->adc_val[channel] * get_coeff(dev, channel)); + +} + +static ssize_t adc_val_0_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return convert_adc_val(dev, buf, ADC_CH0); +} + +static ssize_t adc_val_1_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return convert_adc_val(dev, buf, ADC_CH1); +} + +static ssize_t adc_val_2_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return convert_adc_val(dev, buf, ADC_CH2); +} + +static ssize_t adc_val_3_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return convert_adc_val(dev, buf, ADC_CH3); +} + +static ssize_t adc_val_4_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return convert_adc_val(dev, buf, ADC_CH4); +} + +static ssize_t adc_val_5_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return convert_adc_val(dev, buf, ADC_CH5); +} + +static ssize_t adc_val_6_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return convert_adc_val(dev, buf, ADC_CH6); +} + +static ssize_t adc_val_7_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return convert_adc_val(dev, buf, ADC_CH7); +} + +static void adc_ctrl1_update(struct device *dev) +{ + struct adc_info *adc_meter = dev_get_drvdata(dev); + /* ADC temporarily off */ + s2dos05_update_reg(adc_meter->i2c, + S2DOS05_REG_PWRMT_CTRL2, 0x00, ADC_EN_MASK); + + /* update ADC_CTRL1 register */ + s2dos05_write_reg(adc_meter->i2c, + S2DOS05_REG_PWRMT_CTRL1, adc_meter->adc_ctrl1); + + /* ADC Continuous ON */ + s2dos05_update_reg(adc_meter->i2c, + S2DOS05_REG_PWRMT_CTRL2, ADC_EN_MASK, ADC_EN_MASK); +} + +static ssize_t adc_ctrl1_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adc_info *adc_meter = dev_get_drvdata(dev); + return snprintf(buf, PAGE_SIZE, "0x%02hhx\n", adc_meter->adc_ctrl1); +} + +static ssize_t adc_ctrl1_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adc_info *adc_meter = dev_get_drvdata(dev); + int ret; + u8 temp; + + ret = kstrtou8(buf, 16, &temp); + if (ret) + return -EINVAL; + + temp &= SMPNUM_MASK; + adc_meter->adc_ctrl1 &= ~SMPNUM_MASK; + adc_meter->adc_ctrl1 |= temp; + adc_ctrl1_update(dev); + return count; +} + +#if IS_ENABLED(CONFIG_SEC_PM) +static ssize_t adc_validity_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct adc_info *adc_meter = dev_get_drvdata(dev); + u8 adc_validity; + + s2dos05_read_reg(adc_meter->i2c, S2DOS05_REG_OCL, &adc_validity); + return snprintf(buf, PAGE_SIZE, "%d\n", (adc_validity >> 7)); +} +#endif /* CONFIG_SEC_PM */ + +static struct pmic_device_attribute powermeter_attr[] = { + PMIC_ATTR(adc_val_all, S_IRUGO, adc_val_all_show, NULL), + PMIC_ATTR(adc_en, S_IRUGO | S_IWUSR, adc_en_show, adc_en_store), + PMIC_ATTR(adc_mode, S_IRUGO | S_IWUSR, adc_mode_show, adc_mode_store), + PMIC_ATTR(adc_sync_mode, S_IRUGO | S_IWUSR, adc_sync_mode_show, adc_sync_mode_store), + PMIC_ATTR(adc_val_0, S_IRUGO, adc_val_0_show, NULL), + PMIC_ATTR(adc_val_1, S_IRUGO, adc_val_1_show, NULL), + PMIC_ATTR(adc_val_2, S_IRUGO, adc_val_2_show, NULL), + PMIC_ATTR(adc_val_3, S_IRUGO, adc_val_3_show, NULL), + PMIC_ATTR(adc_val_4, S_IRUGO, adc_val_4_show, NULL), + PMIC_ATTR(adc_val_5, S_IRUGO, adc_val_5_show, NULL), + PMIC_ATTR(adc_val_6, S_IRUGO, adc_val_6_show, NULL), + PMIC_ATTR(adc_val_7, S_IRUGO, adc_val_7_show, NULL), + PMIC_ATTR(adc_ctrl1, S_IRUGO | S_IWUSR, adc_ctrl1_show, adc_ctrl1_store), +#if IS_ENABLED(CONFIG_SEC_PM) + PMIC_ATTR(adc_validity, S_IRUGO, adc_validity_show, NULL), +#endif /* CONFIG_SEC_PM */ +}; + +static int s2dos05_create_powermeter_sysfs(struct s2dos05_dev *s2dos05) +{ + struct device *s2dos05_adc_dev; + struct device *dev = s2dos05->dev; + char device_name[50] = {0, }; + int err = -ENODEV, i = 0; + + pr_info("%s()\n", __func__); + + /* Dynamic allocation for device name */ + snprintf(device_name, sizeof(device_name) - 1, "%s-powermeter@%s", + dev_driver_string(dev), dev_name(dev)); + + s2dos05_adc_dev = pmic_device_create(s2dos05->adc_meter, device_name); + s2dos05->powermeter_dev = s2dos05_adc_dev; + + /* Create sysfs entries */ + for (i = 0; i < ARRAY_SIZE(powermeter_attr); i++) { + err = device_create_file(s2dos05_adc_dev, &powermeter_attr[i].dev_attr); + if (err) + goto remove_pmic_device; + } + +#if IS_ENABLED(CONFIG_SEC_PM) + if (!IS_ERR_OR_NULL(s2dos05->sec_disp_pmic_dev)) { + err = sysfs_create_link(&s2dos05->sec_disp_pmic_dev->kobj, + &s2dos05_adc_dev->kobj, "power_meter"); + if (err) { + pr_err("%s: fail to create link for power_meter\n", + __func__); + goto remove_pmic_device; + } + } +#endif /* CONFIG_SEC_PM */ + + return 0; + +remove_pmic_device: + for (i--; i >= 0; i--) + device_remove_file(s2dos05_adc_dev, &powermeter_attr[i].dev_attr); + pmic_device_destroy(s2dos05_adc_dev->devt); + + return -1; +} +#endif + +static void s2dos05_set_smp_num(struct adc_info *adc_meter) +{ + switch (adc_meter->adc_mode) { + case CURRENT_METER: + adc_meter->adc_ctrl1 = 0x0C; + break; + case POWER_METER: + adc_meter->adc_ctrl1 = 0x0C; + break; + default: + adc_meter->adc_ctrl1 = 0x0C; + break; + } + +} + +static void s2dos05_adc_set_enable(struct adc_info *adc_meter, struct s2dos05_dev *s2dos05) +{ + switch (adc_meter->adc_mode) { + case CURRENT_METER: + s2dos05_update_reg(s2dos05->i2c, S2DOS05_REG_PWRMT_CTRL2, + (ADC_EN_MASK | CURRENT_MODE), + (ADC_EN_MASK | ADC_PGEN_MASK)); + pr_info("%s: current mode enable (0x%2x)\n", + __func__, (ADC_EN_MASK | CURRENT_MODE)); + break; + case POWER_METER: + s2dos05_update_reg(s2dos05->i2c, S2DOS05_REG_PWRMT_CTRL2, + (ADC_EN_MASK | POWER_MODE), + (ADC_EN_MASK | ADC_PGEN_MASK)); + pr_info("%s: power mode enable (0x%2x)\n", + __func__, (ADC_EN_MASK | POWER_MODE)); + break; + default: + s2dos05_update_reg(s2dos05->i2c, S2DOS05_REG_PWRMT_CTRL2, + (0x00 | CURRENT_MODE), + (ADC_EN_MASK | ADC_PGEN_MASK)); + pr_info("%s: current/power meter disable (0x%2x)\n", + __func__, (ADC_EN_MASK | CURRENT_MODE)); + + } +} + +void s2dos05_powermeter_init(struct s2dos05_dev *s2dos05) +{ + struct adc_info *adc_meter; + + adc_meter = devm_kzalloc(s2dos05->dev, + sizeof(struct adc_info), GFP_KERNEL); + if (!adc_meter) { + pr_err("%s: adc_meter alloc fail.\n", __func__); + return; + } + + adc_meter->adc_val = devm_kzalloc(s2dos05->dev, + sizeof(unsigned long) * S2DOS05_MAX_ADC_CHANNEL, + GFP_KERNEL); + + pr_info("%s: s2dos05 power meter init start\n", __func__); + + adc_meter->adc_mode = s2dos05->adc_mode; + adc_meter->adc_sync_mode = s2dos05->adc_sync_mode; + + /* POWER_METER mode needs bigger SMP_NUM to get stable value */ + s2dos05_set_smp_num(adc_meter); + + /* SMP_NUM = 1100(16384) ~16s in case of aync mode */ + if (adc_meter->adc_sync_mode == ASYNC_MODE) + adc_meter->adc_ctrl1 = 0x0C; + +#if IS_ENABLED(CONFIG_SEC_PM) + adc_meter->is_sm3080 = s2dos05->is_sm3080; + if (adc_meter->is_sm3080) + adc_meter->adc_ctrl1 = 0x0B; /* 10 seconds */ +#endif /* CONFIG_SEC_PM */ + + s2dos05_write_reg(s2dos05->i2c, + S2DOS05_REG_PWRMT_CTRL1, adc_meter->adc_ctrl1); + + /* ADC EN */ + s2dos05_adc_set_enable(adc_meter, s2dos05); + + adc_meter->i2c = s2dos05->i2c; + s2dos05->adc_meter = adc_meter; + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) + if (s2dos05_create_powermeter_sysfs(s2dos05) < 0) { + pr_info("%s: failed to create sysfs\n", __func__); + return; + } +#endif + pr_info("%s: s2dos05 power meter init end\n", __func__); +} +EXPORT_SYMBOL_GPL(s2dos05_powermeter_init); + +void s2dos05_powermeter_deinit(struct s2dos05_dev *s2dos05) +{ +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) + struct device *s2dos05_adc_dev = s2dos05->powermeter_dev; + int i = 0; + + /* Remove sysfs entries */ + for (i = 0; i < ARRAY_SIZE(powermeter_attr); i++) + device_remove_file(s2dos05_adc_dev, &powermeter_attr[i].dev_attr); + pmic_device_destroy(s2dos05_adc_dev->devt); +#endif + + /* ADC turned off */ + s2dos05_write_reg(s2dos05->i2c, S2DOS05_REG_PWRMT_CTRL2, 0); + pr_info("%s: s2dos05 power meter deinit\n", __func__); +} +EXPORT_SYMBOL_GPL(s2dos05_powermeter_deinit); diff --git a/drivers/regulator/s2dos07/Kconfig b/drivers/regulator/s2dos07/Kconfig new file mode 100644 index 000000000000..b1f09d1f86d7 --- /dev/null +++ b/drivers/regulator/s2dos07/Kconfig @@ -0,0 +1,29 @@ +if REGULATOR + +config REGULATOR_S2DOS07 + tristate "Samsung S2DOS07 regulator" + depends on I2C + help + This driver controls a Samsung S2DOS07 regulator + via I2C bus. + +endif + + +config S2DOS07_TEST_FOR_ON_DEVICE + tristate "KUnit test for s2dos07_test" + depends on SEC_KUNIT + depends on REGULATOR_S2DOS07 + help + TODO: Describe config fully. + If you run this test driver on device, SHOULD set this config as 'm' to build test driver modulraly. + +config S2DOS07_TEST_FOR_ONLY_UML + tristate "KUnit test for s2dos07_test" + depends on SEC_KUNIT + depends on UML + depends on REGULATOR_S2DOS07 + help + TODO: Describe config fully. + This CONFIG is recommended to set to y. + diff --git a/drivers/regulator/s2dos07/Makefile b/drivers/regulator/s2dos07/Makefile new file mode 100644 index 000000000000..5af7ca9244d9 --- /dev/null +++ b/drivers/regulator/s2dos07/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_REGULATOR_S2DOS07) += s2dos07.o + +ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG + + +GCOV_PROFILE_s2dos07.o := $(CONFIG_SEC_KUNIT) diff --git a/drivers/regulator/s2dos07/s2dos07.c b/drivers/regulator/s2dos07/s2dos07.c new file mode 100644 index 000000000000..3fa4fba79005 --- /dev/null +++ b/drivers/regulator/s2dos07/s2dos07.c @@ -0,0 +1,1013 @@ +/* + * s2dos07.c - Regulator driver for the Samsung s2dos07 + * + * Copyright (C) 2023 Samsung Electronics + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) +#include +#endif +#if IS_ENABLED(CONFIG_REGULATOR_DEBUG_CONTROL) +#include +#endif +#if IS_ENABLED(CONFIG_SEC_PM) +#include +#endif /* CONFIG_SEC_PM */ + +#if IS_ENABLED(CONFIG_SEC_KUNIT) +#define __visible_for_testing +#else +#define __visible_for_testing static +#endif + +struct s2dos07_data { + struct s2dos07_dev *iodev; + int num_regulators; + struct regulator_dev *rdev[S2DOS07_REGULATOR_MAX]; +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) + u8 read_addr; + u8 read_val; + struct device *dev; +#endif +}; + +int s2dos07_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest) +{ + struct s2dos07_data *info = i2c_get_clientdata(i2c); + struct s2dos07_dev *s2dos07 = info->iodev; + int ret; + + mutex_lock(&s2dos07->i2c_lock); + ret = i2c_smbus_read_byte_data(i2c, reg); + mutex_unlock(&s2dos07->i2c_lock); + if (ret < 0) { + pr_info("%s:%s reg(0x%02hhx), ret(%d)\n", + MFD_DEV_NAME, __func__, reg, ret); + return ret; + } + + ret &= 0xff; + *dest = ret; + return 0; +} +EXPORT_SYMBOL_GPL(s2dos07_read_reg); + +int s2dos07_bulk_read(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct s2dos07_data *info = i2c_get_clientdata(i2c); + struct s2dos07_dev *s2dos07 = info->iodev; + int ret; + + mutex_lock(&s2dos07->i2c_lock); + ret = i2c_smbus_read_i2c_block_data(i2c, reg, count, buf); + mutex_unlock(&s2dos07->i2c_lock); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(s2dos07_bulk_read); + +int s2dos07_read_word(struct i2c_client *i2c, u8 reg) +{ + struct s2dos07_data *info = i2c_get_clientdata(i2c); + struct s2dos07_dev *s2dos07 = info->iodev; + int ret; + + mutex_lock(&s2dos07->i2c_lock); + ret = i2c_smbus_read_word_data(i2c, reg); + mutex_unlock(&s2dos07->i2c_lock); + if (ret < 0) + return ret; + + return ret; +} +EXPORT_SYMBOL_GPL(s2dos07_read_word); + +int s2dos07_write_reg(struct i2c_client *i2c, u8 reg, u8 value) +{ + struct s2dos07_data *info = i2c_get_clientdata(i2c); + struct s2dos07_dev *s2dos07 = info->iodev; + int ret; + + mutex_lock(&s2dos07->i2c_lock); + ret = i2c_smbus_write_byte_data(i2c, reg, value); + mutex_unlock(&s2dos07->i2c_lock); + if (ret < 0) + pr_info("%s:%s reg(0x%02hhx), ret(%d)\n", + MFD_DEV_NAME, __func__, reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(s2dos07_write_reg); + +int s2dos07_bulk_write(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct s2dos07_data *info = i2c_get_clientdata(i2c); + struct s2dos07_dev *s2dos07 = info->iodev; + int ret; + + mutex_lock(&s2dos07->i2c_lock); + ret = i2c_smbus_write_i2c_block_data(i2c, reg, count, buf); + mutex_unlock(&s2dos07->i2c_lock); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(s2dos07_bulk_write); + +int s2dos07_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask) +{ + struct s2dos07_data *info = i2c_get_clientdata(i2c); + struct s2dos07_dev *s2dos07 = info->iodev; + int ret; + u8 old_val, new_val; + + mutex_lock(&s2dos07->i2c_lock); + ret = i2c_smbus_read_byte_data(i2c, reg); + if (ret >= 0) { + old_val = ret & 0xff; + new_val = (val & mask) | (old_val & (~mask)); + ret = i2c_smbus_write_byte_data(i2c, reg, new_val); + } + mutex_unlock(&s2dos07->i2c_lock); + return ret; +} +EXPORT_SYMBOL_GPL(s2dos07_update_reg); + +/* W/A: VGP-VGL bypass, VGP FD off */ +void s2dos07_set_vgl_bypass_n_fd(struct i2c_client *i2c) +{ + /* set authority */ + s2dos07_update_reg(i2c, S2DOS07_REG_0D_AUTHORITY, 0x1, 0x1); + s2dos07_update_reg(i2c, S2DOS07_REG_0D_CONTROL, 0x1, 0x1); + + s2dos07_write_reg(i2c, S2DOS07_REG_VGX_EN_CTRL, 0xd1); /* VGP-VGL bypass */ + s2dos07_write_reg(i2c, S2DOS07_REG_SS_FD_CTRL, 0xf7); /* VGL FD off */ + + /* clear authority */ + s2dos07_update_reg(i2c, S2DOS07_REG_0D_CONTROL, 0x0, 0x1); + s2dos07_update_reg(i2c, S2DOS07_REG_0D_AUTHORITY, 0x0, 0x1); +} + +static int s2m_enable(struct regulator_dev *rdev) +{ + struct s2dos07_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + int ret; + + ret = s2dos07_update_reg(i2c, rdev->desc->enable_reg, + rdev->desc->enable_mask, + rdev->desc->enable_mask); + + if (info->iodev->vgl_bypass_n_fd) + s2dos07_set_vgl_bypass_n_fd(i2c); + + return ret; +} + +static int s2m_disable_regmap(struct regulator_dev *rdev) +{ + struct s2dos07_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + u8 val; + + if (rdev->desc->enable_is_inverted) + val = rdev->desc->enable_mask; + else + val = 0; + + return s2dos07_update_reg(i2c, rdev->desc->enable_reg, + val, rdev->desc->enable_mask); +} + +static int s2m_is_enabled_regmap(struct regulator_dev *rdev) +{ + struct s2dos07_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + int ret; + u8 val; + + ret = s2dos07_read_reg(i2c, rdev->desc->enable_reg, &val); + if (ret < 0) + return ret; + + if (rdev->desc->enable_is_inverted) + return (val & rdev->desc->enable_mask) == 0; + else + return (val & rdev->desc->enable_mask) != 0; +} + +static int s2m_get_voltage_sel_regmap(struct regulator_dev *rdev) +{ + struct s2dos07_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + int ret; + u8 val; + + ret = s2dos07_read_reg(i2c, rdev->desc->vsel_reg, &val); + if (ret < 0) + return ret; + + val &= rdev->desc->vsel_mask; + + return val; +} + +static int s2m_set_voltage_sel_regmap_buck(struct regulator_dev *rdev, + unsigned sel) +{ + struct s2dos07_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + int ret; + + ret = s2dos07_update_reg(i2c, rdev->desc->vsel_reg, + sel, rdev->desc->vsel_mask); + if (ret < 0) + goto out; + + if (rdev->desc->apply_bit) + ret = s2dos07_update_reg(i2c, rdev->desc->apply_reg, + rdev->desc->apply_bit, + rdev->desc->apply_bit); + return ret; +out: + pr_warn("%s: failed to set voltage_sel_regmap\n", rdev->desc->name); + return ret; +} + +__visible_for_testing +int s2m_set_voltage_time_sel(struct regulator_dev *rdev, + unsigned int old_selector, + unsigned int new_selector) +{ + int old_volt, new_volt; + + /* sanity check */ + if (!rdev->desc->ops->list_voltage) + return -EINVAL; + + old_volt = rdev->desc->ops->list_voltage(rdev, old_selector); + new_volt = rdev->desc->ops->list_voltage(rdev, new_selector); + + if (old_selector < new_selector) + return DIV_ROUND_UP(new_volt - old_volt, S2DOS07_RAMP_DELAY); + + return 0; +} +#if IS_ENABLED(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_KUNIT(s2m_set_voltage_time_sel); +#endif + +#if IS_ENABLED(CONFIG_SEC_PM) +static int s2m_elvxx_disable(struct regulator_dev *rdev) +{ + int ret = 0; + struct s2dos07_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + u8 val = 0; + + /* set authority */ + s2dos07_update_reg(i2c, S2DOS07_REG_0D_AUTHORITY, 0x1, 0x1); + s2dos07_update_reg(i2c, S2DOS07_REG_0D_CONTROL, 0x1, 0x1); + + s2dos07_read_reg(i2c, S2DOS07_REG_EN_CTRL, &val); + dev_info(&i2c->dev, "%s: before REG_EN_CTRL(0x%02X)\n", __func__, val); + + /* disable vout12, vout3 */ + ret = s2dos07_update_reg(i2c, S2DOS07_REG_EN_CTRL, 0x0, 0x3); + if (ret < 0) + dev_err(&i2c->dev, "%s: failed to update REG_EN_CTRL(%d)\n", __func__, ret); + + s2dos07_read_reg(i2c, S2DOS07_REG_EN_CTRL, &val); + dev_info(&i2c->dev, "%s: after REG_EN_CTRL(0x%02X)\n", __func__, val); + + /* clear authority */ + s2dos07_update_reg(i2c, S2DOS07_REG_0D_CONTROL, 0x0, 0x1); + s2dos07_update_reg(i2c, S2DOS07_REG_0D_AUTHORITY, 0x0, 0x1); + + return ret; +} +#endif /* CONFIG_SEC_PM */ + +static struct regulator_ops s2dos07_buck_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = s2m_is_enabled_regmap, + .enable = s2m_enable, + .disable = s2m_disable_regmap, + .get_voltage_sel = s2m_get_voltage_sel_regmap, + .set_voltage_sel = s2m_set_voltage_sel_regmap_buck, + .set_voltage_time_sel = s2m_set_voltage_time_sel, +}; + +#if IS_ENABLED(CONFIG_SEC_PM) +static struct regulator_ops s2dos07_elvxx_ops = { + .disable = s2m_elvxx_disable, +}; +#endif /* CONFIG_SEC_PM */ + +#define _BUCK(macro) S2DOS07_BUCK##macro +#define _buck_ops(num) s2dos07_buck_ops##num +#define _REG(ctrl) S2DOS07_REG##ctrl +#define _MASK(macro) S2DOS07_ENABLE_MASK##macro +#define _TIME(macro) S2DOS07_ENABLE_TIME##macro + +#define BUCK_DESC(_name, _id, _ops, m, s, v, e, em, t) { \ + .name = _name, \ + .id = _id, \ + .ops = _ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = m, \ + .uV_step = s, \ + .n_voltages = S2DOS07_BUCK_N_VOLTAGES, \ + .vsel_reg = v, \ + .vsel_mask = S2DOS07_BUCK_VSEL_MASK, \ + .enable_reg = e, \ + .enable_mask = em, \ + .enable_time = t \ +} + +#if IS_ENABLED(CONFIG_SEC_PM) +#define ELVXX_DESC(_name, _id) { \ + .name = _name, \ + .id = _id, \ + .ops = &s2dos07_elvxx_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ +} +#endif /* CONFIG_SEC_PM */ + +__visible_for_testing +struct regulator_desc regulators[S2DOS07_REGULATOR_MAX] = { + /* name, id, ops, min_uv, uV_step, vsel_reg, enable_reg */ + BUCK_DESC("s2dos07-buck1", _BUCK(1), &_buck_ops(), _BUCK(_MIN1), + _BUCK(_STEP1), _REG(_BUCK_VOUT), + _REG(_BUCK_EN), _MASK(_B1), _TIME(_BUCK)), +#if IS_ENABLED(CONFIG_SEC_PM) + ELVXX_DESC("s2dos07-elvdd-elvss", S2DOS07_ELVXX), +#endif /* CONFIG_SEC_PM */ +}; +#if IS_ENABLED(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_KUNIT(regulators); +#endif + +static irqreturn_t s2dos07_irq_thread(int irq, void *irq_data) +{ + struct s2dos07_data *s2dos07 = irq_data; + u8 val = 0; + + s2dos07_read_reg(s2dos07->iodev->i2c, S2DOS07_REG_IRQ, &val); + pr_info("%s:irq(%d) S2DOS07_REG_IRQ : 0x%02hhx\n", __func__, irq, val); + + s2dos07_read_reg(s2dos07->iodev->i2c, S2DOS07_REG_UVP_STATUS, &val); + pr_info("%s:irq(%d) S2DOS07_REG_UVP_STATUS : 0x%02hhx\n", __func__, irq, val); + + s2dos07_read_reg(s2dos07->iodev->i2c, S2DOS07_REG_OVP_STATUS, &val); + pr_info("%s:irq(%d) S2DOS07_REG_OVP_STATUS : 0x%02hhx\n", __func__, irq, val); + + s2dos07_read_reg(s2dos07->iodev->i2c, S2DOS07_REG_OVP_MODE, &val); + pr_info("%s:irq(%d) S2DOS07_REG_OVP_MODE : 0x%02hhx\n", __func__, irq, val); + + return IRQ_HANDLED; +} + +#if IS_ENABLED(CONFIG_OF) +static int s2dos07_pmic_dt_parse_pdata(struct device *dev, + struct s2dos07_platform_data *pdata) +{ + struct device_node *pmic_np, *regulators_np, *reg_np; + struct s2dos07_regulator_data *rdata; + size_t i; + + pmic_np = dev->of_node; + if (!pmic_np) { + dev_err(dev, "could not find pmic sub-node\n"); + return -ENODEV; + } + + pdata->dp_pmic_irq = of_get_named_gpio(pmic_np, "s2dos07,s2dos07_int", 0); + if (pdata->dp_pmic_irq < 0) + pr_err("%s error reading s2dos07_irq = %d\n", + __func__, pdata->dp_pmic_irq); + + pdata->wakeup = of_property_read_bool(pmic_np, "s2dos07,wakeup"); + pdata->vgl_bypass_n_fd = of_property_read_bool(pmic_np, "s2dos07,set_vgl_bypass_n_fd"); + + regulators_np = of_find_node_by_name(pmic_np, "regulators"); + if (!regulators_np) { + dev_err(dev, "could not find regulators sub-node\n"); + return -EINVAL; + } + + /* count the number of regulators to be supported in pmic */ + pdata->num_regulators = 0; + for_each_child_of_node(regulators_np, reg_np) { + pdata->num_regulators++; + } + + rdata = devm_kzalloc(dev, sizeof(*rdata) * + pdata->num_regulators, GFP_KERNEL); + if (!rdata) { + dev_err(dev, + "could not allocate memory for regulator data\n"); + return -ENOMEM; + } + + pdata->regulators = rdata; + pdata->num_rdata = 0; + for_each_child_of_node(regulators_np, reg_np) { + for (i = 0; i < ARRAY_SIZE(regulators); i++) + if (!of_node_cmp(reg_np->name, + regulators[i].name)) + break; + + if (i == ARRAY_SIZE(regulators)) { + dev_warn(dev, + "don't know how to configure regulator %s\n", + reg_np->name); + continue; + } + + rdata->id = i; + rdata->initdata = of_get_regulator_init_data( + dev, reg_np, + ®ulators[i]); + rdata->reg_node = reg_np; + rdata++; + pdata->num_rdata++; + } + of_node_put(regulators_np); + + return 0; +} +#else +static int s2dos07_pmic_dt_parse_pdata(struct s2dos07_dev *iodev, + struct s2dos07_platform_data *pdata) +{ + return 0; +} +#endif /* CONFIG_OF */ +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) +static ssize_t s2dos07_read_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct s2dos07_data *s2dos07 = dev_get_drvdata(dev); + int ret; + u8 val, reg_addr; + + if (buf == NULL) { + pr_info("%s: empty buffer\n", __func__); + return -1; + } + + ret = kstrtou8(buf, 0, ®_addr); + if (ret < 0) + pr_info("%s: fail to transform i2c address\n", __func__); + + ret = s2dos07_read_reg(s2dos07->iodev->i2c, reg_addr, &val); + if (ret < 0) + pr_info("%s: fail to read i2c address\n", __func__); + + pr_info("%s: reg(0x%02hhx) data(0x%02hhx)\n", __func__, reg_addr, val); + s2dos07->read_addr = reg_addr; + s2dos07->read_val = val; + + return size; +} + +static ssize_t s2dos07_read_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct s2dos07_data *s2dos07 = dev_get_drvdata(dev); + return sprintf(buf, "0x%02hhx: 0x%02hhx\n", s2dos07->read_addr, + s2dos07->read_val); +} + +static ssize_t s2dos07_write_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct s2dos07_data *s2dos07 = dev_get_drvdata(dev); + int ret; + u8 reg = 0, data = 0; + + if (buf == NULL) { + pr_info("%s: empty buffer\n", __func__); + return size; + } + + ret = sscanf(buf, "0x%02hhx 0x%02hhx", ®, &data); + if (ret != 2) { + pr_info("%s: input error\n", __func__); + return size; + } + + pr_info("%s: reg(0x%02hhx) data(0x%02hhx)\n", __func__, reg, data); + + ret = s2dos07_write_reg(s2dos07->iodev->i2c, reg, data); + if (ret < 0) + pr_info("%s: fail to write i2c addr/data\n", __func__); + + return size; +} + +static ssize_t s2dos07_write_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "echo (register addr.) (data) > s2dos07_write\n"); +} + +#define ATTR_REGULATOR (2) +static struct pmic_device_attribute regulator_attr[] = { + PMIC_ATTR(write, S_IRUGO | S_IWUSR, s2dos07_write_show, s2dos07_write_store), + PMIC_ATTR(read, S_IRUGO | S_IWUSR, s2dos07_read_show, s2dos07_read_store), +}; + +static int s2dos07_create_sysfs(struct s2dos07_data *s2dos07) +{ + struct device *s2dos07_pmic = s2dos07->dev; + struct device *dev = s2dos07->iodev->dev; + char device_name[32] = {0, }; + int err = -ENODEV, i = 0; + + pr_info("%s()\n", __func__); + s2dos07->read_addr = 0; + s2dos07->read_val = 0; + + /* Dynamic allocation for device name */ + snprintf(device_name, sizeof(device_name) - 1, "%s@%s", + dev_driver_string(dev), dev_name(dev)); + + s2dos07_pmic = pmic_device_create(s2dos07, device_name); + s2dos07->dev = s2dos07_pmic; + + /* Create sysfs entries */ + for (i = 0; i < ATTR_REGULATOR; i++) { + err = device_create_file(s2dos07_pmic, ®ulator_attr[i].dev_attr); + if (err) + goto remove_pmic_device; + } + + return 0; + +remove_pmic_device: + for (i--; i >= 0; i--) + device_remove_file(s2dos07_pmic, ®ulator_attr[i].dev_attr); + pmic_device_destroy(s2dos07_pmic->devt); + + return -1; +} +#endif + +#if IS_ENABLED(CONFIG_SEC_PM) +#if IS_ENABLED(CONFIG_SEC_FACTORY) +#define VALID_REG S2DOS07_REG_IRQ_MASK /* Register address for validation */ +#define VALID_MASK 0x80 /* NA(reserved) bit */ +#define POK_MASK 0x3e /* POK bit except VGL */ + +static ssize_t validation_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct s2dos07_data *s2dos07 = dev_get_drvdata(dev); + struct i2c_client *i2c = s2dos07->iodev->i2c; + int ret; + bool result = false; + u8 val; + + ret = s2dos07_read_reg(i2c, VALID_REG, &val); + if (ret < 0) { + dev_err(dev, "%s: fail to read reg\n", __func__); + goto out; + } + + dev_info(dev, "%s: initial state: reg(0x%02X) data(0x%02X)\n", __func__, + VALID_REG, val); + + ret = s2dos07_update_reg(i2c, VALID_REG, VALID_MASK, VALID_MASK); + if (ret < 0) { + dev_err(dev, "%s: fail to update reg\n", __func__); + goto out; + } + + ret = s2dos07_read_reg(i2c, VALID_REG, &val); + if (ret < 0) { + dev_err(dev, "%s: fail to read reg\n", __func__); + goto out; + } + + dev_info(dev, "%s: updated state: reg(0x%02x) data(0x%02x)\n", __func__, + VALID_REG, val); + + result = (val & VALID_MASK) == VALID_MASK; + + /* No need change to init value(0x00), but, do it */ + s2dos07_update_reg(i2c, VALID_REG, 0x00, VALID_MASK); + +out: + dev_info(dev, "%s: result: %s\n", __func__, result ? "ok" : "not ok"); + + return sprintf(buf, "%d\n", result); +} +static ssize_t pok_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct s2dos07_data *s2dos07 = dev_get_drvdata(dev); + struct i2c_client *i2c = s2dos07->iodev->i2c; + int ret; + bool result = false; + u8 val; + + /* 1. Buck on */ + s2dos07_update_reg(i2c, S2DOS07_REG_BUCK_EN, 0x1, 0x1); + msleep(100); + + /* 2. Authority on */ + s2dos07_update_reg(i2c, S2DOS07_REG_0D_AUTHORITY, 0x1, 0x1); + s2dos07_update_reg(i2c, S2DOS07_REG_0D_CONTROL, 0x1, 0x1); + msleep(100); + + /* 3 Bypass on & FD off */ + s2dos07_write_reg(i2c, S2DOS07_REG_VGX_EN_CTRL, 0xd1); /* VGP-VGL bypass */ + s2dos07_write_reg(i2c, S2DOS07_REG_SS_FD_CTRL, 0xf7); /* VGL FD off */ + msleep(100); + + /* 4. VOUT3_EN(AVDD ON) */ + s2dos07_update_reg(i2c, S2DOS07_REG_EN_CTRL, 0x2, 0x2); + msleep(100); + + /* 5. VOUT12_EN(ELVDD ELVSS ON) */ + s2dos07_update_reg(i2c, S2DOS07_REG_EN_CTRL, 0x1, 0x1); + msleep(200); + + /* 6. REG_STATUS Read(POK) */ + ret = s2dos07_read_reg(i2c, S2DOS07_REG_STAT, &val); + dev_info(dev, "%s: S2DOS07_REG_STAT: 0x%x\n", __func__, val); + + result = ((val & POK_MASK) == POK_MASK); + + /* 7. VOUT12_EN(ELVDD ELVSS OFF) */ + s2dos07_update_reg(i2c, S2DOS07_REG_EN_CTRL, 0x0, 0x2); + + /* 8. VOUT3_EN(AVDD OFF) */ + s2dos07_update_reg(i2c, S2DOS07_REG_EN_CTRL, 0x0, 0x1); + + /* 9. Authority off */ + s2dos07_update_reg(i2c, S2DOS07_REG_0D_CONTROL, 0x0, 0x1); + s2dos07_update_reg(i2c, S2DOS07_REG_0D_AUTHORITY, 0x0, 0x1); + + /* 10. Buck off */ + s2dos07_update_reg(i2c, S2DOS07_REG_BUCK_EN, 0x0, 0x1); + + dev_info(dev, "%s: result: %s\n", __func__, result ? "POK ok" : "POK not ok"); + + return sprintf(buf, "%d\n", result); +} +#else +static ssize_t validation_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "0\n"); +} +static ssize_t pok_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "0\n"); +} +#endif /* CONFIG_SEC_FACTORY */ +static DEVICE_ATTR_RO(validation); +static DEVICE_ATTR_RO(pok); + +static int s2dos07_sec_pm_init(struct s2dos07_data *info) +{ + struct s2dos07_dev *iodev = info->iodev; + struct device *dev = &iodev->i2c->dev; + int ret = 0; + + iodev->sec_disp_pmic_dev = sec_device_create(info, "disp_pmic"); + if (unlikely(IS_ERR(iodev->sec_disp_pmic_dev))) { + ret = PTR_ERR(iodev->sec_disp_pmic_dev); + dev_err(dev, "%s: Failed to create disp_pmic(%d)\n", __func__, ret); + return ret; + } + + ret = device_create_file(iodev->sec_disp_pmic_dev, &dev_attr_validation); + if (ret) { + pr_err("s2dos07_sysfs: failed to create validation file, %s\n", + dev_attr_validation.attr.name); + goto remove_sec_disp_validation; + } + + ret = device_create_file(iodev->sec_disp_pmic_dev, &dev_attr_pok); + if (ret) { + pr_err("s2dos07_sysfs: failed to create pok file, %s\n", + dev_attr_pok.attr.name); + goto remove_sec_disp_pok; + } + + return ret; + +remove_sec_disp_pok: + device_remove_file(info->iodev->sec_disp_pmic_dev, &dev_attr_pok); + sec_device_destroy(iodev->sec_disp_pmic_dev->devt); + +remove_sec_disp_validation: + device_remove_file(info->iodev->sec_disp_pmic_dev, &dev_attr_validation); + sec_device_destroy(iodev->sec_disp_pmic_dev->devt); + + return ret; +} + +static void s2dos07_sec_pm_deinit(struct s2dos07_data *info) +{ + device_remove_file(info->iodev->sec_disp_pmic_dev, &dev_attr_validation); + device_remove_file(info->iodev->sec_disp_pmic_dev, &dev_attr_pok); + sec_device_destroy(info->iodev->sec_disp_pmic_dev->devt); +} +#endif /* CONFIG_SEC_PM */ + +static int __s2dos07_pmic_probe(struct i2c_client *i2c) +{ + struct s2dos07_dev *iodev; + struct s2dos07_platform_data *pdata = i2c->dev.platform_data; + struct regulator_config config = { }; + struct s2dos07_data *s2dos07; + size_t i; + int ret = 0; + u8 val = 0, mask = 0; + + pr_info("%s:%s\n", MFD_DEV_NAME, __func__); + + iodev = devm_kzalloc(&i2c->dev, sizeof(struct s2dos07_dev), GFP_KERNEL); + if (!iodev) { + dev_err(&i2c->dev, "%s: Failed to alloc mem for s2dos07\n", + __func__); + return -ENOMEM; + } + + if (i2c->dev.of_node) { + pdata = devm_kzalloc(&i2c->dev, + sizeof(struct s2dos07_platform_data), GFP_KERNEL); + if (!pdata) { + dev_err(&i2c->dev, "Failed to allocate memory\n"); + ret = -ENOMEM; + goto err_pdata; + } + + ret = s2dos07_pmic_dt_parse_pdata(&i2c->dev, pdata); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to get device of_node\n"); + goto err_pdata; + } + + i2c->dev.platform_data = pdata; + } else + pdata = i2c->dev.platform_data; + + iodev->dev = &i2c->dev; + iodev->i2c = i2c; + + if (pdata) { + iodev->pdata = pdata; + iodev->wakeup = pdata->wakeup; + iodev->vgl_bypass_n_fd = pdata->vgl_bypass_n_fd; + } else { + ret = -EINVAL; + goto err_pdata; + } + mutex_init(&iodev->i2c_lock); + + s2dos07 = devm_kzalloc(&i2c->dev, sizeof(struct s2dos07_data), + GFP_KERNEL); + if (!s2dos07) { + ret = -ENOMEM; + goto err_s2dos07_data; + } + + i2c_set_clientdata(i2c, s2dos07); + s2dos07->iodev = iodev; + s2dos07->num_regulators = pdata->num_rdata; + + for (i = 0; i < pdata->num_rdata; i++) { + int id = pdata->regulators[i].id; + config.dev = &i2c->dev; + config.init_data = pdata->regulators[i].initdata; + config.driver_data = s2dos07; + config.of_node = pdata->regulators[i].reg_node; + s2dos07->rdev[i] = devm_regulator_register(&i2c->dev, + ®ulators[id], &config); + if (IS_ERR(s2dos07->rdev[i])) { + ret = PTR_ERR(s2dos07->rdev[i]); + dev_err(&i2c->dev, "regulator init failed for %d\n", + id); + s2dos07->rdev[i] = NULL; + goto err_s2dos07_data; + } +#if IS_ENABLED(CONFIG_REGULATOR_DEBUG_CONTROL) + ret = devm_regulator_debug_register(&i2c->dev, s2dos07->rdev[i]); + if (ret) + dev_err(&i2c->dev, "failed to register debug regulator for %lu, rc=%d\n", + i, ret); +#endif + } + +#if IS_ENABLED(CONFIG_SEC_PM) + ret = s2dos07_sec_pm_init(s2dos07); + if (ret < 0) + goto err_s2dos07_data; +#endif + + val = (S2DOS07_IRQ_UVP_MASK | S2DOS07_IRQ_OVP_MASK | S2DOS07_IRQ_PRETSD_MASK + | S2DOS07_IRQ_TSD_MASK | S2DOS07_IRQ_SSD_MASK | S2DOS07_IRQ_UVLO_MASK); + mask = (S2DOS07_IRQ_UVP_MASK | S2DOS07_IRQ_OVP_MASK | S2DOS07_IRQ_PRETSD_MASK + | S2DOS07_IRQ_TSD_MASK | S2DOS07_IRQ_SSD_MASK | S2DOS07_IRQ_UVLO_MASK); + ret = s2dos07_update_reg(iodev->i2c, S2DOS07_REG_IRQ_MASK, val, mask); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to mask IRQ MASK address\n"); + return ret; + } + + if (pdata->dp_pmic_irq > 0) { + iodev->dp_pmic_irq = gpio_to_irq(pdata->dp_pmic_irq); + pr_info("%s : dp_pmic_irq = %d\n", __func__, iodev->dp_pmic_irq); + if (iodev->dp_pmic_irq > 0) { + ret = request_threaded_irq(iodev->dp_pmic_irq, + NULL, s2dos07_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "dp-pmic-irq", s2dos07); + if (ret) { + dev_err(&i2c->dev, + "%s: Failed to Request IRQ\n", __func__); + goto err_s2dos07_data; + } + + if (pdata->wakeup) { + ret = enable_irq_wake(iodev->dp_pmic_irq); + if (ret < 0) + dev_err(&i2c->dev, "%s: Failed to Enable Wakeup Source(%d)\n", + __func__, ret); + ret = device_init_wakeup(iodev->dev, pdata->wakeup); + if (ret < 0) + dev_err(&i2c->dev, "%s: Fail to device init wakeup fail(%d)\n", + __func__, ret); + } + } else { + dev_err(&i2c->dev, "%s: Failed gpio_to_irq(%d)\n", + __func__, iodev->dp_pmic_irq); + goto err_s2dos07_data; + } + } +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) + ret = s2dos07_create_sysfs(s2dos07); + if (ret < 0) { + pr_err("%s: s2dos07_create_sysfs fail\n", __func__); + goto err_s2dos07_data; + } +#endif + + return ret; + +err_s2dos07_data: + mutex_destroy(&iodev->i2c_lock); +err_pdata: + return ret; +} + +#if IS_ENABLED(CONFIG_OF) +static struct of_device_id s2dos07_i2c_dt_ids[] = { + { .compatible = "samsung,s2dos07pmic" }, + { }, +}; +#endif /* CONFIG_OF */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) +static int s2dos07_pmic_probe(struct i2c_client *i2c) +{ + return __s2dos07_pmic_probe(i2c); +} +#else +static int s2dos07_pmic_probe(struct i2c_client *i2c, + const struct i2c_device_id *dev_id) +{ + return __s2dos07_pmic_probe(i2c); +} +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +static void s2dos07_pmic_remove(struct i2c_client *i2c) +#else +static int s2dos07_pmic_remove(struct i2c_client *i2c) +#endif +{ +#if IS_ENABLED(CONFIG_SEC_PM) || IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) + struct s2dos07_data *info = i2c_get_clientdata(i2c); +#endif + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) + struct device *s2dos07_pmic = info->dev; + int i = 0; + + dev_info(&i2c->dev, "%s\n", __func__); + + /* Remove sysfs entries */ + for (i = 0; i < ATTR_REGULATOR; i++) + device_remove_file(s2dos07_pmic, ®ulator_attr[i].dev_attr); + pmic_device_destroy(s2dos07_pmic->devt); +#else + dev_info(&i2c->dev, "%s\n", __func__); +#endif + +#if IS_ENABLED(CONFIG_SEC_PM) + s2dos07_sec_pm_deinit(info); +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) + return; +#else + return 0; +#endif +} + +#define s2dos07_pmic_suspend NULL +#define s2dos07_pmic_resume NULL + +static const struct dev_pm_ops s2dos07_pmic_pm = { + .suspend = s2dos07_pmic_suspend, + .resume = s2dos07_pmic_resume, +}; + +#if IS_ENABLED(CONFIG_OF) +static const struct i2c_device_id s2dos07_pmic_id[] = { + {"s2dos07-regulator", 0}, + {}, +}; +#endif + +static struct i2c_driver s2dos07_i2c_driver = { + .driver = { + .name = "s2dos07-regulator", + .owner = THIS_MODULE, + .pm = &s2dos07_pmic_pm, +#if IS_ENABLED(CONFIG_OF) + .of_match_table = s2dos07_i2c_dt_ids, +#endif /* CONFIG_OF */ + .suppress_bind_attrs = true, + }, + .probe = s2dos07_pmic_probe, + .remove = s2dos07_pmic_remove, + .id_table = s2dos07_pmic_id, +}; + +static int __init s2dos07_i2c_init(void) +{ + pr_info("%s:%s\n", MFD_DEV_NAME, __func__); + return i2c_add_driver(&s2dos07_i2c_driver); +} +subsys_initcall(s2dos07_i2c_init); + +static void __exit s2dos07_i2c_exit(void) +{ + i2c_del_driver(&s2dos07_i2c_driver); +} +module_exit(s2dos07_i2c_exit); + +MODULE_DESCRIPTION("SAMSUNG s2dos07 Regulator Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/s2mpb02/Kconfig b/drivers/regulator/s2mpb02/Kconfig new file mode 100644 index 000000000000..234cf42ccccf --- /dev/null +++ b/drivers/regulator/s2mpb02/Kconfig @@ -0,0 +1,10 @@ +if REGULATOR + +config REGULATOR_S2MPB02 + tristate "Samsung S2MPB02 regulator" + depends on MFD_S2MPB02 + help + This driver controls a Samsung S2MPB02 regulator + via I2C bus. + +endif diff --git a/drivers/regulator/s2mpb02/Makefile b/drivers/regulator/s2mpb02/Makefile new file mode 100644 index 000000000000..99249b51cee0 --- /dev/null +++ b/drivers/regulator/s2mpb02/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_REGULATOR_S2MPB02) += s2mpb02-regulator.o + +ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG + diff --git a/drivers/regulator/s2mpb02/s2mpb02-regulator.c b/drivers/regulator/s2mpb02/s2mpb02-regulator.c new file mode 100644 index 000000000000..0a1cf54f322f --- /dev/null +++ b/drivers/regulator/s2mpb02/s2mpb02-regulator.c @@ -0,0 +1,891 @@ +/* + * s2mpb02_regulator.c - Regulator driver for the Samsung s2mpb02 + * + * Copyright (C) 2014 Samsung Electronics + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_REGULATOR_DEBUG_CONTROL) +#include +#endif +#include +#include +#include +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) +#include +#endif + +struct s2mpb02_data { + struct s2mpb02_dev *iodev; + int num_regulators; + struct regulator_dev *rdev[S2MPB02_REGULATOR_MAX]; + bool need_recovery; +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) + u8 read_addr; + u8 read_val; + struct device *dev; +#endif +}; + +static int s2m_enable(struct regulator_dev *rdev) +{ + struct s2mpb02_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + + return s2mpb02_update_reg(i2c, rdev->desc->enable_reg, + rdev->desc->enable_mask, + rdev->desc->enable_mask); +} + +static int s2m_disable_regmap(struct regulator_dev *rdev) +{ + struct s2mpb02_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + u8 val; + + if (rdev->desc->enable_is_inverted) + val = rdev->desc->enable_mask; + else + val = 0; + + return s2mpb02_update_reg(i2c, rdev->desc->enable_reg, + val, rdev->desc->enable_mask); +} + +static int s2m_is_enabled_regmap(struct regulator_dev *rdev) +{ + struct s2mpb02_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + int ret; + u8 val; + + ret = s2mpb02_read_reg(i2c, rdev->desc->enable_reg, &val); + if (ret < 0) + return ret; + + val &= rdev->desc->enable_mask; + + return (val == rdev->desc->enable_mask); +} + +static int s2m_get_voltage_sel_regmap(struct regulator_dev *rdev) +{ + struct s2mpb02_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + int ret; + u8 val; + + ret = s2mpb02_read_reg(i2c, rdev->desc->vsel_reg, &val); + if (ret < 0) + return ret; + + val &= rdev->desc->vsel_mask; + + return val; +} + +static int s2m_set_voltage_sel_regmap(struct regulator_dev *rdev, unsigned sel) +{ + struct s2mpb02_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + int ret; + + ret = s2mpb02_update_reg(i2c, rdev->desc->vsel_reg, + sel, rdev->desc->vsel_mask); + if (ret < 0) + goto out; + + if (rdev->desc->apply_bit) + ret = s2mpb02_update_reg(i2c, rdev->desc->apply_reg, + rdev->desc->apply_bit, + rdev->desc->apply_bit); + return ret; +out: + pr_warn("%s: failed to set voltage_sel_regmap\n", rdev->desc->name); + return ret; +} + +static int s2m_set_voltage_sel_regmap_buck(struct regulator_dev *rdev, + unsigned sel) +{ + struct s2mpb02_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + int ret; + + ret = s2mpb02_write_reg(i2c, rdev->desc->vsel_reg, sel); + if (ret < 0) + goto out; + + if (rdev->desc->apply_bit) + ret = s2mpb02_update_reg(i2c, rdev->desc->apply_reg, + rdev->desc->apply_bit, + rdev->desc->apply_bit); + return ret; +out: + pr_warn("%s: failed to set voltage_sel_regmap\n", rdev->desc->name); + return ret; +} + +static int s2m_set_voltage_time_sel(struct regulator_dev *rdev, + unsigned int old_selector, + unsigned int new_selector) +{ + int old_volt, new_volt; + + /* sanity check */ + if (!rdev->desc->ops->list_voltage) + return -EINVAL; + + old_volt = rdev->desc->ops->list_voltage(rdev, old_selector); + new_volt = rdev->desc->ops->list_voltage(rdev, new_selector); + + if (old_selector < new_selector) + return DIV_ROUND_UP(new_volt - old_volt, S2MPB02_RAMP_DELAY); + + return 0; +} + +#if IS_ENABLED(CONFIG_SEC_PM) +#define S2MPB02_BUCK_MODE_MASK (3 << 2) +#define S2MPB02_BUCK_MODE_FPWM (3 << 2) +#define S2MPB02_BUCK_MODE_AUTO (2 << 2) + +/* BUCKs & BB support [Auto/Force PWM] mode */ +static int s2m_set_buck_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct s2mpb02_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + u8 val; + + dev_info(info->iodev->dev, "%s: mode: %u\n", __func__, mode); + + if (mode == REGULATOR_MODE_FAST) + val = S2MPB02_BUCK_MODE_FPWM; + else if (mode == REGULATOR_MODE_NORMAL) + val = S2MPB02_BUCK_MODE_AUTO; + else + return -EINVAL; + + return s2mpb02_update_reg(i2c, rdev->desc->enable_reg, val, + S2MPB02_BUCK_MODE_MASK); +} + +static unsigned int s2m_get_buck_mode(struct regulator_dev *rdev) +{ + struct s2mpb02_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + int ret; + u8 val; + + ret = s2mpb02_read_reg(i2c, rdev->desc->enable_reg, &val); + if (ret < 0) + return ret; + + val = val & S2MPB02_BUCK_MODE_MASK; + dev_info(info->iodev->dev, "%s: val: %u\n", __func__, val >> 2); + + if (val == S2MPB02_BUCK_MODE_FPWM) + ret = REGULATOR_MODE_FAST; + else if (val == S2MPB02_BUCK_MODE_AUTO) + ret = REGULATOR_MODE_NORMAL; + else + ret = -EINVAL; + + return ret; +} +#endif /* CONFIG_SEC_PM */ + +static struct regulator_ops s2mpb02_ldo_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = s2m_is_enabled_regmap, + .enable = s2m_enable, + .disable = s2m_disable_regmap, + .get_voltage_sel = s2m_get_voltage_sel_regmap, + .set_voltage_sel = s2m_set_voltage_sel_regmap, + .set_voltage_time_sel = s2m_set_voltage_time_sel, +}; + +static struct regulator_ops s2mpb02_buck_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = s2m_is_enabled_regmap, + .enable = s2m_enable, + .disable = s2m_disable_regmap, + .get_voltage_sel = s2m_get_voltage_sel_regmap, + .set_voltage_sel = s2m_set_voltage_sel_regmap_buck, + .set_voltage_time_sel = s2m_set_voltage_time_sel, +#if IS_ENABLED(CONFIG_SEC_PM) + .set_mode = s2m_set_buck_mode, + .get_mode = s2m_get_buck_mode, +#endif /* CONFIG_SEC_PM */ +}; + +#if IS_ENABLED(CONFIG_SEC_PM) +static unsigned int s2mpb02_of_map_mode(unsigned int mode) +{ + switch (mode) { + case 1 ... 2: /* Forced PWM & Auto */ + return mode; + default: + return REGULATOR_MODE_INVALID; + } +} +#else +static unsigned int s2mpb02_of_map_mode(unsigned int mode) +{ + return REGULATOR_MODE_INVALID; +} +#endif /* CONFIG_SEC_PM */ + +#define _BUCK(macro) S2MPB02_BUCK##macro +#define _buck_ops(num) s2mpb02_buck_ops##num + +#define _LDO(macro) S2MPB02_LDO##macro +#define _REG(ctrl) S2MPB02_REG##ctrl +#define _ldo_ops(num) s2mpb02_ldo_ops##num +#define _TIME(macro) S2MPB02_ENABLE_TIME##macro + +#define BUCK_DESC(_name, _id, _ops, m, s, v, e, t) { \ + .name = _name, \ + .id = _id, \ + .ops = _ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = m, \ + .uV_step = s, \ + .n_voltages = S2MPB02_BUCK_N_VOLTAGES, \ + .vsel_reg = v, \ + .vsel_mask = S2MPB02_BUCK_VSEL_MASK, \ + .enable_reg = e, \ + .enable_mask = S2MPB02_BUCK_ENABLE_MASK, \ + .enable_time = t, \ + .of_map_mode = s2mpb02_of_map_mode, \ +} + +#define LDO_DESC(_name, _id, _ops, m, s, v, e, t) { \ + .name = _name, \ + .id = _id, \ + .ops = _ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = m, \ + .uV_step = s, \ + .n_voltages = S2MPB02_LDO_N_VOLTAGES, \ + .vsel_reg = v, \ + .vsel_mask = S2MPB02_LDO_VSEL_MASK, \ + .enable_reg = e, \ + .enable_mask = S2MPB02_LDO_ENABLE_MASK, \ + .enable_time = t \ +} + +static struct regulator_desc regulators[S2MPB02_REGULATOR_MAX] = { + /* name, id, ops, min_uv, uV_step, vsel_reg, enable_reg */ + LDO_DESC("s2mpb02-ldo1", _LDO(1), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP1), _REG(_L1CTRL), _REG(_L1CTRL), _TIME(_LDO)), + LDO_DESC("s2mpb02-ldo2", _LDO(2), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP1), _REG(_L2CTRL), _REG(_L2CTRL), _TIME(_LDO)), + LDO_DESC("s2mpb02-ldo3", _LDO(3), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP1), _REG(_L3CTRL), _REG(_L3CTRL), _TIME(_LDO)), + LDO_DESC("s2mpb02-ldo4", _LDO(4), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP1), _REG(_L4CTRL), _REG(_L4CTRL), _TIME(_LDO)), + LDO_DESC("s2mpb02-ldo5", _LDO(5), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP1), _REG(_L5CTRL), _REG(_L5CTRL), _TIME(_LDO)), + LDO_DESC("s2mpb02-ldo6", _LDO(6), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP2), _REG(_L6CTRL), _REG(_L6CTRL), _TIME(_LDO)), + LDO_DESC("s2mpb02-ldo7", _LDO(7), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP2), _REG(_L7CTRL), _REG(_L7CTRL), _TIME(_LDO)), + LDO_DESC("s2mpb02-ldo8", _LDO(8), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP2), _REG(_L8CTRL), _REG(_L8CTRL), _TIME(_LDO)), + LDO_DESC("s2mpb02-ldo9", _LDO(9), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP2), _REG(_L9CTRL), _REG(_L9CTRL), _TIME(_LDO)), + LDO_DESC("s2mpb02-ldo10", _LDO(10), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP2), _REG(_L10CTRL), _REG(_L10CTRL), _TIME(_LDO)), + LDO_DESC("s2mpb02-ldo11", _LDO(11), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP2), _REG(_L11CTRL), _REG(_L11CTRL), _TIME(_LDO)), + LDO_DESC("s2mpb02-ldo12", _LDO(12), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP2), _REG(_L12CTRL), _REG(_L12CTRL), _TIME(_LDO)), + LDO_DESC("s2mpb02-ldo13", _LDO(13), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP2), _REG(_L13CTRL), _REG(_L13CTRL), _TIME(_LDO)), + LDO_DESC("s2mpb02-ldo14", _LDO(14), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP2), _REG(_L14CTRL), _REG(_L14CTRL), _TIME(_LDO)), + LDO_DESC("s2mpb02-ldo15", _LDO(15), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP2), _REG(_L15CTRL), _REG(_L15CTRL), _TIME(_LDO)), + LDO_DESC("s2mpb02-ldo16", _LDO(16), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP2), _REG(_L16CTRL), _REG(_L16CTRL), _TIME(_LDO)), + LDO_DESC("s2mpb02-ldo17", _LDO(17), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP2), _REG(_L17CTRL), _REG(_L17CTRL), _TIME(_LDO)), + LDO_DESC("s2mpb02-ldo18", _LDO(18), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP2), _REG(_L18CTRL), _REG(_L18CTRL), _TIME(_LDO)), + + BUCK_DESC("s2mpb02-buck1", _BUCK(1), &_buck_ops(), _BUCK(_MIN1), + _BUCK(_STEP1), _REG(_B1CTRL2), _REG(_B1CTRL1), _TIME(_BUCK)), + BUCK_DESC("s2mpb02-buck2", _BUCK(2), &_buck_ops(), _BUCK(_MIN1), + _BUCK(_STEP1), _REG(_B2CTRL2), _REG(_B2CTRL1), _TIME(_BUCK)), + BUCK_DESC("s2mpb02-bb", S2MPB02_BB1, &_buck_ops(), _BUCK(_MIN2), + _BUCK(_STEP2), _REG(_BB1CTRL2), _REG(_BB1CTRL1), _TIME(_BB)), +}; + +#if IS_ENABLED(CONFIG_SEC_FACTORY) +/* + * Recovery logic for S2MPB02 detach test + */ +int s2mpb02_need_recovery(struct s2mpb02_data *s2mpb02) +{ + struct regulator_dev *rdev; + int i, ret = 0; + + // Check whether S2MPB02 Recovery is needed + for (i = 0; i < s2mpb02->num_regulators; i++) { + if (s2mpb02->rdev[i]) { + rdev = s2mpb02->rdev[i]; + + // If the regulator is always-on or use_count is over 0, 'enable bit' should be checked + if ((rdev->constraints->always_on) || (rdev->use_count > 0)) { + if(!s2m_is_enabled_regmap(rdev)) { + pr_info("%s: s2mpb02->rdev[%d]->desc->name(%s)\n", + __func__, i, rdev->desc->name); + ret = 1; + continue; + } + } + } + } + + return ret; +} + +int s2mpb02_recovery(struct s2mpb02_data *s2mpb02) +{ + struct regulator_dev *rdev; + int i, ret = 0; + u8 val; + unsigned int vol, reg; + + if (!s2mpb02) { + pr_info("%s: There is no local rdev data\n", __func__); + return -ENODEV; + } + + pr_info("%s: Start recovery\n", __func__); + + // S2MPB02 Recovery + for (i = 0; i < s2mpb02->num_regulators; i++) { + if (s2mpb02->rdev[i]) { + rdev = s2mpb02->rdev[i]; + + pr_info("%s: s2mpb02->rdev[%d]->desc->name(%s): max_uV(%d), min_uV(%d), always_on(%d), use_count(%d)\n", + __func__, i, rdev->desc->name, rdev->constraints->max_uV, rdev->desc->min_uV, + rdev->constraints->always_on, rdev->use_count); + + // Make sure enabled registers are cleared + s2m_disable_regmap(rdev); + + // Get and calculate voltage from regulator framework + vol = (rdev->constraints->min_uV - rdev->desc->min_uV) / rdev->desc->uV_step; + reg = s2m_get_voltage_sel_regmap(rdev); + + // Set proper voltage according to regulator type + if (rdev->desc->vsel_mask == S2MPB02_BUCK_VSEL_MASK) + ret = s2m_set_voltage_sel_regmap_buck(rdev, vol); + else if (S2MPB02_LDO_VSEL_MASK) + ret = s2m_set_voltage_sel_regmap(rdev, vol); + + if (ret < 0) + return ret; + + if (rdev->constraints->always_on) { + ret = s2mpb02_read_reg(s2mpb02->iodev->i2c, rdev->desc->enable_reg, &val); + if (ret < 0) + return ret; + + if (!(val & 0x80)) { + ret = s2m_enable(rdev); + if (ret < 0) + return ret; + } + } else { + if (rdev->use_count > 0) { + ret = s2m_enable(rdev); + if (ret < 0) + return ret; + } + } + } + } + + pr_info("%s: S2MPB02 is successfully recovered!\n", __func__); + + return ret; +} + +static int s2mpb02_regulator_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct s2mpb02_data *s2mpb02 = platform_get_drvdata(pdev); + int ret; + + if (s2mpb02->need_recovery) { + pr_info("%s: Check recovery needs\n", __func__); + ret = s2mpb02_need_recovery(s2mpb02); + + // Do recovery if needed + if (ret) + s2mpb02_recovery(s2mpb02); + } + + return 0; +} + +const struct dev_pm_ops s2mpb02_regulator_pm = { + .resume = s2mpb02_regulator_resume, +}; +#endif + +#if IS_ENABLED(CONFIG_OF) +static int s2mpb02_pmic_dt_parse_pdata(struct s2mpb02_dev *iodev, + struct s2mpb02_platform_data *pdata) +{ + struct device_node *pmic_np, *regulators_np, *reg_np; + struct s2mpb02_regulator_data *rdata; + size_t i; + + pmic_np = iodev->dev->of_node; + if (!pmic_np) { + dev_err(iodev->dev, "could not find pmic sub-node\n"); + return -ENODEV; + } + + regulators_np = of_find_node_by_name(pmic_np, "regulators"); + if (!regulators_np) { + dev_err(iodev->dev, "could not find regulators sub-node\n"); + return -EINVAL; + } + + pdata->need_recovery = of_property_read_bool(pmic_np, "s2mpb02,need_recovery"); + + /* count the number of regulators to be supported in pmic */ + pdata->num_regulators = 0; + for_each_child_of_node(regulators_np, reg_np) { + pdata->num_regulators++; + } + + rdata = devm_kzalloc(iodev->dev, sizeof(*rdata) * + pdata->num_regulators, GFP_KERNEL); + if (!rdata) { + dev_err(iodev->dev, + "could not allocate memory for regulator data\n"); + return -ENOMEM; + } + + pdata->regulators = rdata; + pdata->num_rdata = 0; + for_each_child_of_node(regulators_np, reg_np) { + for (i = 0; i < ARRAY_SIZE(regulators); i++) + if (!of_node_cmp(reg_np->name, regulators[i].name)) + break; + + if (i == ARRAY_SIZE(regulators)) { + dev_warn(iodev->dev, + "don't know how to configure regulator %s\n", + reg_np->name); + continue; + } + + rdata->id = i; + rdata->initdata = of_get_regulator_init_data(iodev->dev, reg_np, + ®ulators[i]); + rdata->reg_node = reg_np; + rdata++; + pdata->num_rdata++; + } + of_node_put(regulators_np); + + return 0; +} +#else +static int s2mpb02_pmic_dt_parse_pdata(struct s2mpb02_dev *iodev, + struct s2mpb02_platform_data *pdata) +{ + return 0; +} +#endif /* CONFIG_OF */ +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) +static ssize_t s2mpb02_read_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct s2mpb02_data *s2mpb02 = dev_get_drvdata(dev); + int ret; + u8 val, reg_addr; + + if (buf == NULL) { + pr_info("%s: empty buffer\n", __func__); + return -1; + } + + ret = kstrtou8(buf, 0, ®_addr); + if (ret < 0) + pr_info("%s: fail to transform i2c address\n", __func__); + + ret = s2mpb02_read_reg(s2mpb02->iodev->i2c, reg_addr, &val); + if (ret < 0) + pr_info("%s: fail to read i2c address\n", __func__); + + pr_info("%s: reg(0x%02hhx) data(0x%02hhx)\n", __func__, reg_addr, val); + s2mpb02->read_addr = reg_addr; + s2mpb02->read_val = val; + + return size; +} + +static ssize_t s2mpb02_read_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct s2mpb02_data *s2mpb02 = dev_get_drvdata(dev); + return sprintf(buf, "0x%02hhx: 0x%02hhx\n", s2mpb02->read_addr, + s2mpb02->read_val); +} + +static ssize_t s2mpb02_write_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct s2mpb02_data *s2mpb02 = dev_get_drvdata(dev); + int ret; + u8 reg = 0, data = 0; + + if (buf == NULL) { + pr_info("%s: empty buffer\n", __func__); + return size; + } + + ret = sscanf(buf, "0x%02hhx 0x%02hhx", ®, &data); + if (ret != 2) { + pr_info("%s: input error\n", __func__); + return size; + } + + pr_info("%s: reg(0x%02hhx) data(0x%02hhx)\n", __func__, reg, data); + + ret = s2mpb02_write_reg(s2mpb02->iodev->i2c, reg, data); + if (ret < 0) + pr_info("%s: fail to write i2c addr/data\n", __func__); + + return size; +} + +static ssize_t s2mpb02_write_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "echo (register addr.) (data) > s2mpb02_write\n"); +} + +#define ATTR_REGULATOR (2) +static struct pmic_device_attribute regulator_attr[] = { + PMIC_ATTR(s2mpb02_write, S_IRUGO | S_IWUSR, s2mpb02_write_show, s2mpb02_write_store), + PMIC_ATTR(s2mpb02_read, S_IRUGO | S_IWUSR, s2mpb02_read_show, s2mpb02_read_store), +}; + +static int s2mpb02_create_sysfs(struct s2mpb02_data *s2mpb02) +{ + struct device *s2mpb02_pmic = s2mpb02->dev; + struct device *dev = s2mpb02->iodev->dev; + char device_name[32] = {0, }; + int err = -ENODEV, i = 0; + + pr_info("%s()\n", __func__); + s2mpb02->read_addr = 0; + s2mpb02->read_val = 0; + + /* Dynamic allocation for device name */ + snprintf(device_name, sizeof(device_name) - 1, "%s@%s", + dev_driver_string(dev), dev_name(dev)); + + s2mpb02_pmic = pmic_device_create(s2mpb02, device_name); + s2mpb02->dev = s2mpb02_pmic; + + /* Create sysfs entries */ + for (i = 0; i < ATTR_REGULATOR; i++) { + err = device_create_file(s2mpb02_pmic, ®ulator_attr[i].dev_attr); + if (err) + goto remove_pmic_device; + } + + return 0; + +remove_pmic_device: + for (i--; i >= 0; i--) + device_remove_file(s2mpb02_pmic, ®ulator_attr[i].dev_attr); + pmic_device_destroy(s2mpb02_pmic->devt); + + return -1; +} +#endif + +#if IS_ENABLED(CONFIG_SEC_FACTORY) +/* 0x32:LDO_DSCH3, Bit[7~2]:unused */ +#define VALID_REG S2MPB02_REG_LDO_DSCH3 /* register address for validation */ +#define VALID_MASK 0xFC /* NA(unused) bit */ + +static int s2mpb02_verify_i2c_bus(struct s2mpb02_data* s2mpb02) +{ + u8 old_val, val; + bool result = false; + int ret; + + /* Checking S2MPB02 validation */ + /* 1. Write 1 to 0x32's bit[7~2] */ + ret = s2mpb02_read_reg(s2mpb02->iodev->i2c, VALID_REG, &val); + if (ret < 0) { + pr_info("%s: fail to read i2c address\n", __func__); + return -ENXIO; + } + pr_info("%s: %s: init state: reg(0x%02x) data(0x%02x)\n", + MFD_DEV_NAME, __func__, VALID_REG, val); + old_val = val; + + ret = s2mpb02_write_reg(s2mpb02->iodev->i2c, VALID_REG, (old_val | VALID_MASK)); + if (ret < 0) { + pr_info("%s: fail to write i2c address\n", __func__); + return -ENXIO; + } + + ret = s2mpb02_read_reg(s2mpb02->iodev->i2c, VALID_REG, &val); + if (ret < 0) { + pr_info("%s: fail to read i2c address\n", __func__); + return -ENXIO; + } + pr_info("%s: %s: updated state: reg(0x%02x) data(0x%02x)\n", + MFD_DEV_NAME, __func__, VALID_REG, val); + + if ((val & VALID_MASK) == VALID_MASK) + result = true; + + if (!result) { + pr_info("%s: %s: ERROR: state is not updated!: reg(0x%02x) data(0x%02x)\n", + MFD_DEV_NAME, __func__, VALID_REG, val); + return -ENODEV; + } + + /* 2. Write 0 to 0x32's bit[7~2] */ + ret = s2mpb02_read_reg(s2mpb02->iodev->i2c, VALID_REG, &val); + if (ret < 0) { + pr_info("%s: fail to read i2c address\n", __func__); + return -ENXIO; + } + pr_info("%s: %s: init state: reg(0x%02x) data(0x%02x)\n", + MFD_DEV_NAME, __func__, VALID_REG, val); + + ret = s2mpb02_write_reg(s2mpb02->iodev->i2c, VALID_REG, (val & 0x03)); + if (ret < 0) { + pr_info("%s: fail to write i2c address\n", __func__); + return -ENXIO; + } + + ret = s2mpb02_read_reg(s2mpb02->iodev->i2c, VALID_REG, &val); + if (ret < 0) { + pr_info("%s: fail to read i2c address\n", __func__); + return -ENXIO; + } + pr_info("%s: %s: updated state: reg(0x%02x) data(0x%02x)\n", + MFD_DEV_NAME, __func__, VALID_REG, val); + + if ((val & VALID_MASK) == 0x0) + result = true; + + if (!result) { + pr_info("%s: %s: ERROR: state is not updated!: reg(0x%02x) data(0x%02x)\n", + MFD_DEV_NAME, __func__, VALID_REG, val); + return -ENODEV; + } + + /* 3. Restore old_val to 0x32's bit[7~2] */ + ret = s2mpb02_write_reg(s2mpb02->iodev->i2c, VALID_REG, old_val); + if (ret < 0) { + pr_info("%s: fail to write i2c address\n", __func__); + return -ENXIO; + } + + ret = s2mpb02_read_reg(s2mpb02->iodev->i2c, VALID_REG, &val); + if (ret < 0) { + pr_info("%s: fail to read i2c address\n", __func__); + return -ENXIO; + } + + if (old_val != val) { + pr_info("%s: ERROR: old_val(0x%02x), val(0x%02x) are different!\n", __func__, old_val, val); + return -EIO; + } + + pr_info("%s: %s: restored value: reg(0x%02x) data(0x%02x)\n", + MFD_DEV_NAME, __func__, VALID_REG, val); + + pr_err("%s: %s: i2c operation is %s\n", MFD_DEV_NAME, __func__, + result ? "ok" : "not ok"); + + return 0; +} +#endif + +static int s2mpb02_pmic_probe(struct platform_device *pdev) +{ + struct s2mpb02_dev *iodev = dev_get_drvdata(pdev->dev.parent); + struct s2mpb02_platform_data *pdata = iodev->pdata; + struct regulator_config config = { }; + struct s2mpb02_data *s2mpb02; + struct i2c_client *i2c; + u32 i; + int ret; + + dev_info(&pdev->dev, "%s start\n", __func__); + if (!pdata) { + pr_info("[%s:%d] !pdata\n", __FILE__, __LINE__); + dev_err(pdev->dev.parent, "No platform init data supplied.\n"); + return -ENODEV; + } + + if (iodev->dev->of_node) { + ret = s2mpb02_pmic_dt_parse_pdata(iodev, pdata); + if (ret) + goto err_pdata; + } + + s2mpb02 = devm_kzalloc(&pdev->dev, sizeof(struct s2mpb02_data), + GFP_KERNEL); + if (!s2mpb02) { + pr_info("[%s:%d] if (!s2mpb02)\n", __FILE__, __LINE__); + return -ENOMEM; + } + + s2mpb02->iodev = iodev; + s2mpb02->num_regulators = pdata->num_rdata; + s2mpb02->need_recovery = pdata->need_recovery; + platform_set_drvdata(pdev, s2mpb02); + i2c = s2mpb02->iodev->i2c; + +#if IS_ENABLED(CONFIG_SEC_FACTORY) + ret = s2mpb02_verify_i2c_bus(s2mpb02); + if (ret < 0) { + pr_err("%s: check_s2mpb02_validation fail\n", __func__); + return -ENODEV; + } +#endif + + for (i = 0; i < pdata->num_rdata; i++) { + int id = pdata->regulators[i].id; + config.dev = &pdev->dev; + config.init_data = pdata->regulators[i].initdata; + config.driver_data = s2mpb02; + config.of_node = pdata->regulators[i].reg_node; + s2mpb02->rdev[i] = devm_regulator_register(&pdev->dev, + ®ulators[id], &config); + if (IS_ERR(s2mpb02->rdev[i])) { + ret = PTR_ERR(s2mpb02->rdev[i]); + dev_err(&pdev->dev, "regulator init failed for %d\n", i); + s2mpb02->rdev[i] = NULL; + goto err_s2mpb02_data; + } +#if IS_ENABLED(CONFIG_REGULATOR_DEBUG_CONTROL) + ret = devm_regulator_debug_register(&pdev->dev, s2mpb02->rdev[i]); + if (ret) + dev_err(&pdev->dev, "failed to register debug regulator for %d, rc=%d\n", + i, ret); +#endif + } +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) + ret = s2mpb02_create_sysfs(s2mpb02); + if (ret < 0) { + pr_err("%s: s2mpb02_create_sysfs fail\n", __func__); + return -ENODEV; + } +#endif + dev_info(&pdev->dev, "%s end\n", __func__); + + return 0; + +err_s2mpb02_data: +err_pdata: + return ret; +} + +static int s2mpb02_pmic_remove(struct platform_device *pdev) +{ +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) + struct s2mpb02_data *s2mpb02 = platform_get_drvdata(pdev); + struct device *s2mpb02_pmic = s2mpb02->dev; + int i = 0; + + dev_info(&pdev->dev, "%s\n", __func__); + + /* Remove sysfs entries */ + for (i = 0; i < ATTR_REGULATOR; i++) + device_remove_file(s2mpb02_pmic, ®ulator_attr[i].dev_attr); + pmic_device_destroy(s2mpb02_pmic->devt); +#else + dev_info(&pdev->dev, "%s\n", __func__); +#endif + return 0; +} + +static const struct platform_device_id s2mpb02_pmic_id[] = { + {"s2mpb02-regulator", TYPE_S2MPB02_REG_MAIN}, + {"s2mpb02-sub-reg", TYPE_S2MPB02_REG_SUB}, + {}, +}; +MODULE_DEVICE_TABLE(platform, s2mpb02_pmic_id); + +static struct platform_driver s2mpb02_pmic_driver = { + .driver = { + .name = "s2mpb02-regulator", + .owner = THIS_MODULE, +#if IS_ENABLED(CONFIG_SEC_FACTORY) + .pm = &s2mpb02_regulator_pm, +#endif + .suppress_bind_attrs = true, + }, + .probe = s2mpb02_pmic_probe, + .remove = s2mpb02_pmic_remove, + .id_table = s2mpb02_pmic_id, +}; + +static int __init s2mpb02_pmic_init(void) +{ + return platform_driver_register(&s2mpb02_pmic_driver); +} +subsys_initcall(s2mpb02_pmic_init); + +static void __exit s2mpb02_pmic_cleanup(void) +{ + platform_driver_unregister(&s2mpb02_pmic_driver); +} +module_exit(s2mpb02_pmic_cleanup); + +MODULE_DESCRIPTION("SAMSUNG s2mpb02 Regulator Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/s2mpb03/Kconfig b/drivers/regulator/s2mpb03/Kconfig new file mode 100644 index 000000000000..b5afccfa83ca --- /dev/null +++ b/drivers/regulator/s2mpb03/Kconfig @@ -0,0 +1,10 @@ +if REGULATOR + +config REGULATOR_S2MPB03 + tristate "Samsung S2MPB03 regulator" + depends on I2C + help + This driver controls a Samsung S2MPB03 regulator + via I2C bus. +endif + diff --git a/drivers/regulator/s2mpb03/Makefile b/drivers/regulator/s2mpb03/Makefile new file mode 100644 index 000000000000..72ae8ec56bc1 --- /dev/null +++ b/drivers/regulator/s2mpb03/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_REGULATOR_S2MPB03) += s2mpb03.o + + +ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG + diff --git a/drivers/regulator/s2mpb03/s2mpb03.c b/drivers/regulator/s2mpb03/s2mpb03.c new file mode 100644 index 000000000000..3400dc487c79 --- /dev/null +++ b/drivers/regulator/s2mpb03/s2mpb03.c @@ -0,0 +1,761 @@ +/* + * s2mpb03.c - Regulator driver for the Samsung s2mpb03 + * + * Copyright (C) 2016 Samsung Electronics + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_REGULATOR_DEBUG_CONTROL) +#include +#endif +#include +#include +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) +#include +#endif +#include + +struct s2mpb03_data { + struct s2mpb03_dev *iodev; + int num_regulators; + struct regulator_dev *rdev[S2MPB03_REGULATOR_MAX]; + bool need_recovery; +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) + u8 read_addr; + u8 read_val; + struct device *dev; +#endif +}; + +int s2mpb03_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest) +{ + struct s2mpb03_data *info = i2c_get_clientdata(i2c); + struct s2mpb03_dev *s2mpb03 = info->iodev; + int ret; + + mutex_lock(&s2mpb03->i2c_lock); + ret = i2c_smbus_read_byte_data(i2c, reg); + mutex_unlock(&s2mpb03->i2c_lock); + if (ret < 0) { + pr_info("%s:%s reg(0x%02hhx), ret(%d)\n", + MFD_DEV_NAME, __func__, reg, ret); + return ret; + } + + ret &= 0xff; + *dest = ret; + return 0; +} +EXPORT_SYMBOL_GPL(s2mpb03_read_reg); + +int s2mpb03_bulk_read(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct s2mpb03_data *info = i2c_get_clientdata(i2c); + struct s2mpb03_dev *s2mpb03 = info->iodev; + int ret; + + mutex_lock(&s2mpb03->i2c_lock); + ret = i2c_smbus_read_i2c_block_data(i2c, reg, count, buf); + mutex_unlock(&s2mpb03->i2c_lock); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(s2mpb03_bulk_read); + +int s2mpb03_read_word(struct i2c_client *i2c, u8 reg) +{ + struct s2mpb03_data *info = i2c_get_clientdata(i2c); + struct s2mpb03_dev *s2mpb03 = info->iodev; + int ret; + + mutex_lock(&s2mpb03->i2c_lock); + ret = i2c_smbus_read_word_data(i2c, reg); + mutex_unlock(&s2mpb03->i2c_lock); + if (ret < 0) + return ret; + + return ret; +} +EXPORT_SYMBOL_GPL(s2mpb03_read_word); + +int s2mpb03_write_reg(struct i2c_client *i2c, u8 reg, u8 value) +{ + struct s2mpb03_data *info = i2c_get_clientdata(i2c); + struct s2mpb03_dev *s2mpb03 = info->iodev; + int ret; + + mutex_lock(&s2mpb03->i2c_lock); + ret = i2c_smbus_write_byte_data(i2c, reg, value); + mutex_unlock(&s2mpb03->i2c_lock); + if (ret < 0) + pr_info("%s:%s reg(0x%02hhx), ret(%d)\n", + MFD_DEV_NAME, __func__, reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(s2mpb03_write_reg); + +int s2mpb03_bulk_write(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct s2mpb03_data *info = i2c_get_clientdata(i2c); + struct s2mpb03_dev *s2mpb03 = info->iodev; + int ret; + + mutex_lock(&s2mpb03->i2c_lock); + ret = i2c_smbus_write_i2c_block_data(i2c, reg, count, buf); + mutex_unlock(&s2mpb03->i2c_lock); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(s2mpb03_bulk_write); + +int s2mpb03_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask) +{ + struct s2mpb03_data *info = i2c_get_clientdata(i2c); + struct s2mpb03_dev *s2mpb03 = info->iodev; + int ret; + u8 old_val, new_val; + + mutex_lock(&s2mpb03->i2c_lock); + ret = i2c_smbus_read_byte_data(i2c, reg); + if (ret >= 0) { + old_val = ret & 0xff; + new_val = (val & mask) | (old_val & (~mask)); + ret = i2c_smbus_write_byte_data(i2c, reg, new_val); + } + mutex_unlock(&s2mpb03->i2c_lock); + return ret; +} +EXPORT_SYMBOL_GPL(s2mpb03_update_reg); + +static int s2m_enable(struct regulator_dev *rdev) +{ + struct s2mpb03_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + + return s2mpb03_update_reg(i2c, rdev->desc->enable_reg, + rdev->desc->enable_mask, + rdev->desc->enable_mask); +} + +static int s2m_disable_regmap(struct regulator_dev *rdev) +{ + struct s2mpb03_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + u8 val; + + if (rdev->desc->enable_is_inverted) + val = rdev->desc->enable_mask; + else + val = 0; + + return s2mpb03_update_reg(i2c, rdev->desc->enable_reg, + val, rdev->desc->enable_mask); +} + +static int s2m_is_enabled_regmap(struct regulator_dev *rdev) +{ + struct s2mpb03_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + int ret; + u8 val; + + ret = s2mpb03_read_reg(i2c, rdev->desc->enable_reg, &val); + if (ret < 0) + return ret; + + if (rdev->desc->enable_is_inverted) + return (val & rdev->desc->enable_mask) == 0; + else + return (val & rdev->desc->enable_mask) != 0; +} + +static int s2m_get_voltage_sel_regmap(struct regulator_dev *rdev) +{ + struct s2mpb03_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + int ret; + u8 val; + + ret = s2mpb03_read_reg(i2c, rdev->desc->vsel_reg, &val); + if (ret < 0) + return ret; + + val &= rdev->desc->vsel_mask; + + return val; +} + +static int s2m_set_voltage_sel_regmap(struct regulator_dev *rdev, unsigned sel) +{ + struct s2mpb03_data *info = rdev_get_drvdata(rdev); + struct i2c_client *i2c = info->iodev->i2c; + int ret; + + ret = s2mpb03_update_reg(i2c, rdev->desc->vsel_reg, + sel, rdev->desc->vsel_mask); + if (ret < 0) + goto out; + + if (rdev->desc->apply_bit) + ret = s2mpb03_update_reg(i2c, rdev->desc->apply_reg, + rdev->desc->apply_bit, + rdev->desc->apply_bit); + return ret; +out: + pr_warn("%s: failed to set voltage_sel_regmap\n", rdev->desc->name); + return ret; +} + +static int s2m_set_voltage_time_sel(struct regulator_dev *rdev, + unsigned int old_selector, + unsigned int new_selector) +{ + int old_volt, new_volt; + + /* sanity check */ + if (!rdev->desc->ops->list_voltage) + return -EINVAL; + + old_volt = rdev->desc->ops->list_voltage(rdev, old_selector); + new_volt = rdev->desc->ops->list_voltage(rdev, new_selector); + + if (old_selector < new_selector) + return DIV_ROUND_UP(new_volt - old_volt, S2MPB03_RAMP_DELAY); + + return 0; +} + +static struct regulator_ops s2mpb03_ldo_ops = { + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .is_enabled = s2m_is_enabled_regmap, + .enable = s2m_enable, + .disable = s2m_disable_regmap, + .get_voltage_sel = s2m_get_voltage_sel_regmap, + .set_voltage_sel = s2m_set_voltage_sel_regmap, + .set_voltage_time_sel = s2m_set_voltage_time_sel, +}; + +#define _LDO(macro) S2MPB03_LDO##macro +#define _REG(ctrl) S2MPB03_REG##ctrl +#define _ldo_ops(num) s2mpb03_ldo_ops##num +#define _TIME(macro) S2MPB03_ENABLE_TIME##macro + +#define LDO_DESC(_name, _id, _ops, m, s, v, e, t) { \ + .name = _name, \ + .id = _id, \ + .ops = _ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + .min_uV = m, \ + .uV_step = s, \ + .n_voltages = S2MPB03_LDO_N_VOLTAGES, \ + .vsel_reg = v, \ + .vsel_mask = S2MPB03_LDO_VSEL_MASK, \ + .enable_reg = e, \ + .enable_mask = S2MPB03_LDO_ENABLE_MASK, \ + .enable_time = t \ +} + +static struct regulator_desc regulators[S2MPB03_REGULATOR_MAX] = { + /* name, id, ops, min_uv, uV_step, vsel_reg, enable_reg */ + LDO_DESC("s2mpb03-ldo1", _LDO(1), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP2), _REG(_LDO1_CTRL), + _REG(_LDO1_CTRL), _TIME(_LDO)), + LDO_DESC("s2mpb03-ldo2", _LDO(2), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP2), _REG(_LDO2_CTRL), + _REG(_LDO2_CTRL), _TIME(_LDO)), + LDO_DESC("s2mpb03-ldo3", _LDO(3), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP1), _REG(_LDO3_CTRL), + _REG(_LDO3_CTRL), _TIME(_LDO)), + LDO_DESC("s2mpb03-ldo4", _LDO(4), &_ldo_ops(), _LDO(_MIN1), + _LDO(_STEP2), _REG(_LDO4_CTRL), + _REG(_LDO4_CTRL), _TIME(_LDO)), + LDO_DESC("s2mpb03-ldo5", _LDO(5), &_ldo_ops(), _LDO(_MIN2), + _LDO(_STEP1), _REG(_LDO5_CTRL), + _REG(_LDO5_CTRL), _TIME(_LDO)), + LDO_DESC("s2mpb03-ldo6", _LDO(6), &_ldo_ops(), _LDO(_MIN2), + _LDO(_STEP1), _REG(_LDO6_CTRL), + _REG(_LDO6_CTRL), _TIME(_LDO)), + LDO_DESC("s2mpb03-ldo7", _LDO(7), &_ldo_ops(), _LDO(_MIN2), + _LDO(_STEP1), _REG(_LDO7_CTRL), + _REG(_LDO7_CTRL), _TIME(_LDO)) +}; + +#if IS_ENABLED(CONFIG_SEC_FACTORY) +int s2mpb03_recovery(struct s2mpb03_data *s2mpb03) +{ + struct regulator_dev *rdev; + int i, ret = 0; + u8 val; + unsigned int vol; + + if (!s2mpb03) { + pr_info("%s: There is no local rdev data\n", __func__); + return -ENODEV; + } + + pr_info("%s: Start recovery\n", __func__); + + // S2MPB03 Recovery + for (i = 0; i < s2mpb03->num_regulators; i++) { + if (s2mpb03->rdev[i]) { + rdev = s2mpb03->rdev[i]; + + pr_info("%s: name(%s): max_uV(%d), min_uV(%d), always_on(%d), use_count(%d)\n", + __func__, rdev->constraints->name, rdev->constraints->max_uV, + rdev->desc->min_uV, rdev->constraints->always_on, rdev->use_count); + + // Get and calculate voltage from regulator framework + vol = (rdev->constraints->min_uV - rdev->desc->min_uV) / rdev->desc->uV_step; + ret = s2m_set_voltage_sel_regmap(rdev, vol); + + if (ret < 0) + return ret; + + if (rdev->constraints->always_on) { + ret = s2mpb03_read_reg(s2mpb03->iodev->i2c, rdev->desc->enable_reg, &val); + if (ret < 0) + return ret; + + if (!(val & 0x80)) { + ret = s2m_enable(rdev); + if (ret < 0) + return ret; + } + } else { + if (rdev->use_count > 0) { + ret = s2m_enable(rdev); + if (ret < 0) + return ret; + } + } + } + } + + pr_info("%s: S2MPB03 is successfully recovered!\n", __func__); + + return ret; +} + +static int s2mpb03_regulator_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct s2mpb03_data *s2mpb03 = platform_get_drvdata(pdev); + + if (s2mpb03->need_recovery) + s2mpb03_recovery(s2mpb03); + + return 0; +} + +const struct dev_pm_ops s2mpb03_regulator_pm = { + .resume = s2mpb03_regulator_resume, +}; +#endif + +#if IS_ENABLED(CONFIG_OF) +static int s2mpb03_pmic_dt_parse_pdata(struct device *dev, + struct s2mpb03_platform_data *pdata) +{ + struct device_node *pmic_np, *regulators_np, *reg_np; + struct s2mpb03_regulator_data *rdata; + size_t i; + + pmic_np = dev->of_node; + if (!pmic_np) { + dev_err(dev, "could not find pmic sub-node\n"); + return -ENODEV; + } + pdata->wakeup = of_property_read_bool(pmic_np, "s2mpb03,wakeup"); + + regulators_np = of_find_node_by_name(pmic_np, "regulators"); + if (!regulators_np) { + dev_err(dev, "could not find regulators sub-node\n"); + return -EINVAL; + } + + pdata->need_recovery = of_property_read_bool(pmic_np, "s2mpb03,need_recovery"); + + /* count the number of regulators to be supported in pmic */ + pdata->num_regulators = 0; + for_each_child_of_node(regulators_np, reg_np) { + pdata->num_regulators++; + } + + rdata = devm_kzalloc(dev, sizeof(*rdata) * + pdata->num_regulators, GFP_KERNEL); + if (!rdata) { + dev_err(dev, + "could not allocate memory for regulator data\n"); + return -ENOMEM; + } + + pdata->regulators = rdata; + pdata->num_rdata = 0; + for_each_child_of_node(regulators_np, reg_np) { + for (i = 0; i < ARRAY_SIZE(regulators); i++) + if (!of_node_cmp(reg_np->name, + regulators[i].name)) + break; + + if (i == ARRAY_SIZE(regulators)) { + dev_warn(dev, + "don't know how to configure regulator %s\n", + reg_np->name); + continue; + } + + rdata->id = i; + rdata->initdata = of_get_regulator_init_data( + dev, reg_np, + ®ulators[i]); + rdata->reg_node = reg_np; + rdata++; + pdata->num_rdata++; + } + of_node_put(regulators_np); + + return 0; +} +#else +static int s2mpb03_pmic_dt_parse_pdata(struct s2mpb03_dev *iodev, + struct s2mpb03_platform_data *pdata) +{ + return 0; +} +#endif /* CONFIG_OF */ +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) +static ssize_t s2mpb03_read_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct s2mpb03_data *s2mpb03 = dev_get_drvdata(dev); + int ret; + u8 val, reg_addr; + + if (buf == NULL) { + pr_info("%s: empty buffer\n", __func__); + return -1; + } + + ret = kstrtou8(buf, 0, ®_addr); + if (ret < 0) + pr_info("%s: fail to transform i2c address\n", __func__); + + ret = s2mpb03_read_reg(s2mpb03->iodev->i2c, reg_addr, &val); + if (ret < 0) + pr_info("%s: fail to read i2c address\n", __func__); + + pr_info("%s: reg(0x%02hhx) data(0x%02hhx)\n", __func__, reg_addr, val); + s2mpb03->read_addr = reg_addr; + s2mpb03->read_val = val; + + return size; +} + +static ssize_t s2mpb03_read_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct s2mpb03_data *s2mpb03 = dev_get_drvdata(dev); + return sprintf(buf, "0x%02hhx: 0x%02hhx\n", s2mpb03->read_addr, + s2mpb03->read_val); +} + +static ssize_t s2mpb03_write_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct s2mpb03_data *s2mpb03 = dev_get_drvdata(dev); + int ret; + u8 reg = 0, data = 0; + + if (buf == NULL) { + pr_info("%s: empty buffer\n", __func__); + return size; + } + + ret = sscanf(buf, "0x%02hhx 0x%02hhx", ®, &data); + if (ret != 2) { + pr_info("%s: input error\n", __func__); + return size; + } + + pr_info("%s: reg(0x%02hhx) data(0x%02hhx)\n", __func__, reg, data); + + ret = s2mpb03_write_reg(s2mpb03->iodev->i2c, reg, data); + if (ret < 0) + pr_info("%s: fail to write i2c addr/data\n", __func__); + + return size; +} + +static ssize_t s2mpb03_write_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "echo (register addr.) (data) > s2mpb03_write\n"); +} + +#define ATTR_REGULATOR (2) +static struct pmic_device_attribute regulator_attr[] = { + PMIC_ATTR(s2mpb03_write, S_IRUGO | S_IWUSR, s2mpb03_write_show, s2mpb03_write_store), + PMIC_ATTR(s2mpb03_read, S_IRUGO | S_IWUSR, s2mpb03_read_show, s2mpb03_read_store), +}; + +static int s2mpb03_create_sysfs(struct s2mpb03_data *s2mpb03) +{ + struct device *s2mpb03_pmic = s2mpb03->dev; + struct device *dev = s2mpb03->iodev->dev; + char device_name[32] = {0,}; + int err = -ENODEV, i = 0; + + pr_info("%s()\n", __func__); + s2mpb03->read_addr = 0; + s2mpb03->read_val = 0; + + /* Dynamic allocation for device name */ + snprintf(device_name, sizeof(device_name) - 1, "%s@%s", + dev_driver_string(dev), dev_name(dev)); + + s2mpb03_pmic = pmic_device_create(s2mpb03, device_name); + s2mpb03->dev = s2mpb03_pmic; + + /* Create sysfs entries */ + for (i = 0; i < ATTR_REGULATOR; i++) { + err = device_create_file(s2mpb03_pmic, ®ulator_attr[i].dev_attr); + if (err) + goto remove_pmic_device; + } + + return 0; + +remove_pmic_device: + for (i--; i >= 0; i--) + device_remove_file(s2mpb03_pmic, ®ulator_attr[i].dev_attr); + pmic_device_destroy(s2mpb03_pmic->devt); + + return -1; +} +#endif +static int __s2mpb03_pmic_probe(struct i2c_client *i2c) +{ + struct s2mpb03_dev *iodev; + struct s2mpb03_platform_data *pdata = i2c->dev.platform_data; + struct regulator_config config = { }; + struct s2mpb03_data *s2mpb03; + size_t i; + int ret = 0; + + pr_info("%s:%s\n", MFD_DEV_NAME, __func__); + + iodev = devm_kzalloc(&i2c->dev, sizeof(struct s2mpb03_dev), GFP_KERNEL); + if (!iodev) { + dev_err(&i2c->dev, "%s: Failed to alloc mem for s2mpb03\n", + __func__); + return -ENOMEM; + } + + if (i2c->dev.of_node) { + pdata = devm_kzalloc(&i2c->dev, + sizeof(struct s2mpb03_platform_data), GFP_KERNEL); + if (!pdata) { + dev_err(&i2c->dev, "Failed to allocate memory\n"); + ret = -ENOMEM; + goto err_pdata; + } + ret = s2mpb03_pmic_dt_parse_pdata(&i2c->dev, pdata); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to get device of_node\n"); + goto err_pdata; + } + + i2c->dev.platform_data = pdata; + } else + pdata = i2c->dev.platform_data; + + iodev->dev = &i2c->dev; + iodev->i2c = i2c; + + if (pdata) { + iodev->pdata = pdata; + iodev->wakeup = pdata->wakeup; + } else { + ret = -EINVAL; + goto err_pdata; + } + mutex_init(&iodev->i2c_lock); + + s2mpb03 = devm_kzalloc(&i2c->dev, sizeof(struct s2mpb03_data), + GFP_KERNEL); + if (!s2mpb03) { + pr_info("[%s:%d] if (!s2mpb03)\n", __FILE__, __LINE__); + ret = -ENOMEM; + goto err_s2mpb03_data; + } + + i2c_set_clientdata(i2c, s2mpb03); + s2mpb03->iodev = iodev; + s2mpb03->need_recovery = pdata->need_recovery; + s2mpb03->num_regulators = pdata->num_rdata; + + for (i = 0; i < pdata->num_rdata; i++) { + int id = pdata->regulators[i].id; + config.dev = &i2c->dev; + config.init_data = pdata->regulators[i].initdata; + config.driver_data = s2mpb03; + config.of_node = pdata->regulators[i].reg_node; + s2mpb03->rdev[i] = devm_regulator_register(&i2c->dev, + ®ulators[id], &config); + if (IS_ERR(s2mpb03->rdev[i])) { + ret = PTR_ERR(s2mpb03->rdev[i]); + dev_err(&i2c->dev, "regulator init failed for %d\n", + id); + s2mpb03->rdev[i] = NULL; + goto err_s2mpb03_data; + } +#if IS_ENABLED(CONFIG_REGULATOR_DEBUG_CONTROL) + ret = devm_regulator_debug_register(&i2c->dev, s2mpb03->rdev[i]); + if (ret) + dev_err(&i2c->dev, "failed to register debug regulator for %lu, rc=%d\n", + i, ret); +#endif + } +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) + ret = s2mpb03_create_sysfs(s2mpb03); + if (ret < 0) { + pr_err("%s: s2mpb03_create_sysfs fail\n", __func__); + goto err_s2mpb03_data; + } +#endif + return ret; + +err_s2mpb03_data: + mutex_destroy(&iodev->i2c_lock); +err_pdata: + pr_info("[%s:%d] err\n", __func__, __LINE__); + return ret; +} + +#if IS_ENABLED(CONFIG_OF) +static struct of_device_id s2mpb03_i2c_dt_ids[] = { + { .compatible = "samsung,s2mpb03pmic" }, + { }, +}; +#endif /* CONFIG_OF */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) +static int s2mpb03_pmic_probe(struct i2c_client *i2c) +{ + return __s2mpb03_pmic_probe(i2c); +} +#else +static int s2mpb03_pmic_probe(struct i2c_client *i2c, + const struct i2c_device_id *dev_id) +{ + return __s2mpb03_pmic_probe(i2c); +} +#endif + +static int __s2mpb03_pmic_remove(struct i2c_client *i2c) +{ +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) + struct s2mpb03_data *info = i2c_get_clientdata(i2c); + struct device *s2mpb03_pmic = info->dev; + int i = 0; + + dev_info(&i2c->dev, "%s\n", __func__); + + /* Remove sysfs entries */ + for (i = 0; i < ATTR_REGULATOR; i++) + device_remove_file(s2mpb03_pmic, ®ulator_attr[i].dev_attr); + pmic_device_destroy(s2mpb03_pmic->devt); +#else + dev_info(&i2c->dev, "%s\n", __func__); +#endif + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +static void s2mpb03_pmic_remove(struct i2c_client *i2c) +{ + __s2mpb03_pmic_remove(i2c); +} +#else +static int s2mpb03_pmic_remove(struct i2c_client *i2c) +{ + return __s2mpb03_pmic_remove(i2c); +} +#endif + +#if IS_ENABLED(CONFIG_OF) +static const struct i2c_device_id s2mpb03_pmic_id[] = { + {"s2mpb03-regulator", 0}, + {}, +}; +#endif + +static struct i2c_driver s2mpb03_i2c_driver = { + .driver = { + .name = "s2mpb03-regulator", + .owner = THIS_MODULE, +#if IS_ENABLED(CONFIG_OF) + .of_match_table = s2mpb03_i2c_dt_ids, +#endif /* CONFIG_OF */ +#if IS_ENABLED(CONFIG_SEC_FACTORY) + .pm = &s2mpb03_regulator_pm, +#endif + .suppress_bind_attrs = true, + }, + .probe = s2mpb03_pmic_probe, + .remove = s2mpb03_pmic_remove, + .id_table = s2mpb03_pmic_id, +}; + +static int __init s2mpb03_i2c_init(void) +{ + pr_info("%s:%s\n", MFD_DEV_NAME, __func__); + return i2c_add_driver(&s2mpb03_i2c_driver); +} +subsys_initcall(s2mpb03_i2c_init); + +static void __exit s2mpb03_i2c_exit(void) +{ + i2c_del_driver(&s2mpb03_i2c_driver); +} +module_exit(s2mpb03_i2c_exit); + +MODULE_DESCRIPTION("SAMSUNG s2mpb03 Regulator Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig index 47bf0617d41c..75a7969703e9 100644 --- a/drivers/remoteproc/Kconfig +++ b/drivers/remoteproc/Kconfig @@ -418,6 +418,14 @@ config MSM_QUIN_SUBSYSTEM_NOTIF_VIRT a subsystem to notify its clients of its state, when the clients are in a different virtual machine domain than the subsystem. +config SEC_SENSORS_SSC + bool "Enable Sensors Driver Support SEC features for SSC" + depends on ADSP_FACTORY + help + Say y here to enable SEC features for Snapdragon Sensor Core(SSC) + support SEC features for Qualcomm Technologies, Inc SoCs. SSC is used for + exercising sensor use-cases. + Say N if you do not want to support it. endif # REMOTEPROC endmenu diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile index 944398c88e33..a94e497205c7 100644 --- a/drivers/remoteproc/Makefile +++ b/drivers/remoteproc/Makefile @@ -3,6 +3,11 @@ # Generic framework for controlling remote processors # +ifeq ($(CONFIG_HDM),m) +# Support Knox HDM feature + ccflags-y += -DHDM_SUPPORT +endif + obj-$(CONFIG_REMOTEPROC) += remoteproc.o remoteproc-y := remoteproc_core.o remoteproc-y += remoteproc_coredump.o diff --git a/drivers/remoteproc/qcom_q6v5.c b/drivers/remoteproc/qcom_q6v5.c index b92e6847da5b..64d49c5000f5 100644 --- a/drivers/remoteproc/qcom_q6v5.c +++ b/drivers/remoteproc/qcom_q6v5.c @@ -18,6 +18,13 @@ #include "qcom_common.h" #include "qcom_q6v5.h" #include +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) +#include +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_AUDIO) +#include +#include +#endif #define Q6V5_PANIC_DELAY_MS 200 @@ -99,6 +106,9 @@ static irqreturn_t q6v5_wdog_interrupt(int irq, void *data) struct qcom_q6v5 *q6v5 = data; size_t len; char *msg; +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) || IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_AUDIO) + char *chk_name = NULL; +#endif /* Sometimes the stop triggers a watchdog rather than a stop-ack */ if (!q6v5->running) { @@ -111,6 +121,18 @@ static irqreturn_t q6v5_wdog_interrupt(int irq, void *data) if (!IS_ERR(msg) && len > 0 && msg[0]) { dev_err(q6v5->dev, "watchdog received: %s\n", msg); trace_rproc_qcom_event(dev_name(q6v5->dev), "q6v5_wdog", msg); +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) + chk_name = strstr(q6v5->rproc->name, "adsp"); + if (chk_name != NULL) + ssr_reason_call_back(msg, len); +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_AUDIO) + chk_name = strstr(q6v5->rproc->name, "adsp"); + if (chk_name != NULL) { + sdp_info_print("watchdog received: %s\n", msg); + send_adsp_silent_reset_ev(); + } +#endif } else { dev_err(q6v5->dev, "watchdog without message\n"); } @@ -137,6 +159,9 @@ static irqreturn_t q6v5_fatal_interrupt(int irq, void *data) struct qcom_q6v5 *q6v5 = data; size_t len; char *msg; +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) || IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_AUDIO) + char *chk_name = NULL; +#endif if (!q6v5->running) { dev_info(q6v5->dev, "received fatal irq while q6 is offline\n"); @@ -147,6 +172,38 @@ static irqreturn_t q6v5_fatal_interrupt(int irq, void *data) if (!IS_ERR(msg) && len > 0 && msg[0]) { dev_err(q6v5->dev, "fatal error received: %s\n", msg); trace_rproc_qcom_event(dev_name(q6v5->dev), "q6v5_fatal", msg); +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) + chk_name = strstr(q6v5->rproc->name, "adsp"); + if (chk_name != NULL) { + ssr_reason_call_back(msg, len); + if (strstr(msg, "IPLSREVOCER") +#if IS_ENABLED(CONFIG_SEC_SENSORS_RECOVERY) + || strstr(msg, "qsh_process") +#endif + || strstr(msg, "PMUDRSS")) { + q6v5->rproc->fssr = true; + q6v5->rproc->prev_recovery_disabled = + q6v5->rproc->recovery_disabled; + q6v5->rproc->recovery_disabled = false; + if (strstr(msg, "PMUDRSS")) + q6v5->rproc->fssr_dump = true; + + } else { + q6v5->rproc->fssr = false; + q6v5->rproc->fssr_dump = false; + } + dev_info(q6v5->dev, "recovery:%d,%d\n", + (int)q6v5->rproc->prev_recovery_disabled, + (int)q6v5->rproc->recovery_disabled); + } +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_AUDIO) + chk_name = strstr(q6v5->rproc->name, "adsp"); + if (chk_name != NULL) { + sdp_info_print("fatal error received: %s\n", msg); + send_adsp_silent_reset_ev(); + } +#endif } else { dev_err(q6v5->dev, "fatal error without message\n"); } diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c index b8150aa5e930..87d79f588f52 100644 --- a/drivers/remoteproc/qcom_q6v5_pas.c +++ b/drivers/remoteproc/qcom_q6v5_pas.c @@ -37,6 +37,15 @@ #include #include +#ifdef HDM_SUPPORT +#include +#endif +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) +#include +#include +#include +#include +#endif #include "qcom_common.h" #include "qcom_pil_info.h" #include "qcom_q6v5.h" @@ -47,6 +56,12 @@ #define PIL_TZ_PEAK_BW UINT_MAX #define ADSP_DECRYPT_SHUTDOWN_DELAY_MS 100 +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) +#define SENSOR_SUPPLY_NAME "sensor_vdd" +#define SUBSENSOR_SUPPLY_NAME "subsensor_vdd" +#define PROX_VDD_NAME "prox_vdd" +#define SUBSENSOR_VDD_MAX_RETRY 50 +#endif #define RPROC_HANDOVER_POLL_DELAY_MS 1 static struct icc_path *scm_perf_client; @@ -55,6 +70,16 @@ static DEFINE_MUTEX(q6v5_pas_mutex); bool timeout_disabled; static bool global_sync_mem_setup; static bool recovery_set_cb; +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) +static int sensor_supply_reg_idx = -1; +static int subsensor_supply_reg_idx = -1; +static int prox_vdd_reg_idx = -1; +static int prox_vdd_retry_cnt; +static int subsensor_vdd_retry_cnt; +static bool set_subsensor_vdd_done; +static bool is_need_subsensor; +static bool is_need_subvdd_disable; +#endif #define to_rproc(d) container_of(d, struct rproc, dev) @@ -259,6 +284,10 @@ static void adsp_minidump(struct rproc *rproc) if (rproc->dump_conf == RPROC_COREDUMP_DISABLED) goto exit; +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) + if (strstr(rproc->name, "adsp") && !rproc->fssr_dump) + goto exit; +#endif qcom_minidump(rproc, adsp->minidump_dev, adsp->minidump_id, adsp_segment_dump, adsp->both_dumps); @@ -432,12 +461,56 @@ static int adsp_load(struct rproc *rproc, const struct firmware *fw) return ret; } +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) +static void disable_regulators_sensor_vdd(struct qcom_adsp *adsp) +{ + dev_info(adsp->dev, "%s Regulator disable: %s %d uV %d uA\n", __func__, + SENSOR_SUPPLY_NAME, adsp->regs[sensor_supply_reg_idx].uV, + adsp->regs[sensor_supply_reg_idx].uA); + regulator_set_voltage(adsp->regs[sensor_supply_reg_idx].reg, 0, INT_MAX); + regulator_set_load(adsp->regs[sensor_supply_reg_idx].reg, 0); + regulator_disable(adsp->regs[sensor_supply_reg_idx].reg); + + if (subsensor_supply_reg_idx > 0) { + dev_info(adsp->dev, "%s Regulator disable: %s %d uV %d uA\n", __func__, + SUBSENSOR_SUPPLY_NAME, adsp->regs[subsensor_supply_reg_idx].uV, + adsp->regs[subsensor_supply_reg_idx].uA); + regulator_set_voltage(adsp->regs[subsensor_supply_reg_idx].reg, 0, INT_MAX); + regulator_set_load(adsp->regs[subsensor_supply_reg_idx].reg, 0); + regulator_disable(adsp->regs[subsensor_supply_reg_idx].reg); + } + + if (prox_vdd_reg_idx > 0) { + dev_info(adsp->dev, "%s Regulator disable: %s %d uV %d uA\n", __func__, + PROX_VDD_NAME, adsp->regs[prox_vdd_reg_idx].uV, + adsp->regs[prox_vdd_reg_idx].uA); + regulator_set_voltage(adsp->regs[prox_vdd_reg_idx].reg, 0, INT_MAX); + regulator_set_load(adsp->regs[prox_vdd_reg_idx].reg, 0); + regulator_disable(adsp->regs[prox_vdd_reg_idx].reg); + } +} +#endif static void disable_regulators(struct qcom_adsp *adsp) { int i; for (i = (adsp->reg_cnt - 1); i >= 0; i--) { +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) + if (!strcmp(adsp->info_name, "adsp")) { + if ((i == sensor_supply_reg_idx) + || (i == subsensor_supply_reg_idx) + || (i == prox_vdd_reg_idx)) { + dev_info(adsp->dev, "skip disabling %s, idx: %d", + SENSOR_SUPPLY_NAME, i); + continue; + } + if (IS_ERR(adsp->regs[i].reg)) { + dev_info(adsp->dev, "skip disabling idx: %d", i); + continue; + } + } +#endif regulator_set_voltage(adsp->regs[i].reg, 0, INT_MAX); regulator_set_load(adsp->regs[i].reg, 0); regulator_disable(adsp->regs[i].reg); @@ -449,6 +522,14 @@ static int enable_regulators(struct qcom_adsp *adsp) int i, rc = 0; for (i = 0; i < adsp->reg_cnt; i++) { +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) + if (!strcmp(adsp->info_name, "adsp")) { + if (IS_ERR(adsp->regs[i].reg)) { + dev_info(adsp->dev, "skip enabling idx: %d", i); + continue; + } + } +#endif regulator_set_voltage(adsp->regs[i].reg, adsp->regs[i].uV, INT_MAX); regulator_set_load(adsp->regs[i].reg, adsp->regs[i].uA); rc = regulator_enable(adsp->regs[i].reg); @@ -748,9 +829,19 @@ static int adsp_start(struct rproc *rproc) trace_rproc_qcom_event(dev_name(adsp->dev), "Q6_auth_reset", "enter"); ret = qcom_scm_pas_auth_and_reset(adsp->pas_id); +#ifdef HDM_SUPPORT + if (ret) { + // Intentionally block cp load. + if (hdm_is_cp_enabled()) + goto free_metadata; + else + panic("Panicking, auth and reset failed for remoteproc %s\n", rproc->name); + } +#else if (ret) panic("Panicking, auth and reset failed for remoteproc %s ret=%d\n", rproc->name, ret); +#endif trace_rproc_qcom_event(dev_name(adsp->dev), "Q6_auth_reset", "exit"); /* if needed, signal Q6 to continute booting */ @@ -1146,7 +1237,10 @@ static int adsp_stop(struct rproc *rproc) handover = qcom_q6v5_unprepare(&adsp->q6v5); if (handover) qcom_pas_handover(&adsp->q6v5); - +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) + if (!strcmp(adsp->info_name, "adsp") && sensor_supply_reg_idx > 0) + disable_regulators_sensor_vdd(adsp); +#endif if (adsp->smem_host_id) ret = qcom_smem_bust_hwspin_lock_by_host(adsp->smem_host_id); @@ -1337,6 +1431,30 @@ static int adsp_init_clock(struct qcom_adsp *adsp) return 0; } +static bool adsp_need_subsensor(struct device *dev) +{ + int upper_c2c_det = -1; + int gpio_level = 0; // low:Set, high:SMD + + upper_c2c_det = of_get_named_gpio(dev->of_node, + "upper-c2c-det-gpio", 0); + + if (gpio_is_valid(upper_c2c_det)) { + gpio_level = gpio_get_value(upper_c2c_det); + dev_info(dev, "%s: need subsensor(%d):%s\n", + __func__, upper_c2c_det, gpio_level ? + "No(SMD)":"Yes(SET)"); + } + + is_need_subvdd_disable = of_property_read_bool(dev->of_node, + "subvdd-disable"); + + if (is_need_subvdd_disable) + dev_info(dev, "!!! Need to disable sensor regulators during the shutdown\n"); + + return ((gpio_level > 0) ? (false):(true)); +} + static int adsp_init_regulator(struct qcom_adsp *adsp) { int len; @@ -1344,6 +1462,13 @@ static int adsp_init_regulator(struct qcom_adsp *adsp) char uv_ua[50]; u32 uv_ua_vals[2]; const char *reg_name; +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) + struct device_node *sub_sns_reg_np = + of_find_node_by_name(NULL, "adsp_subsensor_reg"); + bool is_adsp_rproc = + (strcmp(adsp->info_name, "adsp") == 0 ? true : false); + int alloc_cnt = 0, ret; +#endif adsp->reg_cnt = of_property_count_strings(adsp->dev->of_node, "reg-names"); @@ -1351,10 +1476,22 @@ static int adsp_init_regulator(struct qcom_adsp *adsp) dev_err(adsp->dev, "No regulators added!\n"); return 0; } - +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) + alloc_cnt = adsp->reg_cnt; + if (is_adsp_rproc && sub_sns_reg_np != NULL) { + is_need_subsensor = adsp_need_subsensor(adsp->dev); + alloc_cnt++; + dev_info(adsp->dev, "%s increase cnt for adsp:%d,%d\n", + __func__, adsp->reg_cnt, alloc_cnt); + } + adsp->regs = devm_kzalloc(adsp->dev, + sizeof(struct reg_info) * alloc_cnt, + GFP_KERNEL); +#else adsp->regs = devm_kzalloc(adsp->dev, sizeof(struct reg_info) * adsp->reg_cnt, GFP_KERNEL); +#endif if (!adsp->regs) return -ENOMEM; @@ -1364,6 +1501,21 @@ static int adsp_init_regulator(struct qcom_adsp *adsp) adsp->regs[i].reg = devm_regulator_get(adsp->dev, reg_name); if (IS_ERR(adsp->regs[i].reg)) { +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) + if (!strcmp(reg_name, PROX_VDD_NAME)) { + if (prox_vdd_retry_cnt > 20) { + dev_info(adsp->dev, "%s ignore %s %d\n", + __func__, reg_name, + adsp->reg_cnt--); + return 0; + } else { + prox_vdd_retry_cnt++; + pr_err("fail to get prox_vdd: cnt %d\n", + prox_vdd_retry_cnt); + return -EPROBE_DEFER; + } + } +#endif dev_err(adsp->dev, "failed to get %s reg\n", reg_name); return PTR_ERR(adsp->regs[i].reg); } @@ -1386,9 +1538,105 @@ static int adsp_init_regulator(struct qcom_adsp *adsp) adsp->regs[i].uV = uv_ua_vals[0]; if (uv_ua_vals[1] > 0) adsp->regs[i].uA = uv_ua_vals[1]; +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) + if (!strcmp(reg_name, SENSOR_SUPPLY_NAME)) { + dev_info(adsp->dev, "found %s, idx: %d\n", reg_name, i); + sensor_supply_reg_idx = i; + } else if (!strcmp(reg_name, PROX_VDD_NAME)) { + dev_info(adsp->dev, "found %s, idx: %d\n", reg_name, i); + prox_vdd_reg_idx = i; + } +#endif + } +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) + if (is_adsp_rproc && sub_sns_reg_np != NULL) { + ret = adsp_init_subsensor_regulator(adsp->rproc, sub_sns_reg_np); + if (ret) { + return ret; + } + } +#endif + return 0; +} + +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) +int adsp_init_subsensor_regulator(struct rproc *rproc, struct device_node *sub_sns_reg_np) +{ + struct qcom_adsp *adsp; + const char *reg_name; + char uv_ua[50]; + u32 uv_ua_vals[2]; + int len, rc; + + if (!rproc) { + pr_err("fail to get adsp_rproc, NULL\n"); + return -1; + } + + if (!sub_sns_reg_np) { + sub_sns_reg_np = of_find_node_by_name(NULL, "adsp_subsensor_reg"); + if (sub_sns_reg_np == NULL) { + pr_info("no subsensor vdd\n"); + return 0; + } + } + + if (!set_subsensor_vdd_done) { + adsp = (struct qcom_adsp *)rproc->priv; + of_property_read_string(sub_sns_reg_np, "reg-names", ®_name); + subsensor_supply_reg_idx = adsp->reg_cnt; + adsp->regs[subsensor_supply_reg_idx].reg = + devm_regulator_get_optional(adsp->dev, reg_name); + if (IS_ERR(adsp->regs[subsensor_supply_reg_idx].reg)) { + pr_err("failed to get subsensor:%s reg\n", reg_name); + subsensor_supply_reg_idx = -1; + subsensor_vdd_retry_cnt++; + if (subsensor_vdd_retry_cnt >= SUBSENSOR_VDD_MAX_RETRY) { + pr_err("fail to get subsensor_vdd: retry:%d\n", + subsensor_vdd_retry_cnt); + return 0; + } else { + if (is_need_subsensor) { + pr_err("not found, deferred probe,cnt:%d\n", + subsensor_vdd_retry_cnt); + return -EPROBE_DEFER; + } else { + pr_info("didn't defer in SMD\n"); + return 0; + } + } + } + + /* Read current(uA) and voltage(uV) value */ + snprintf(uv_ua, sizeof(uv_ua), "%s-uV-uA", reg_name); + if (!of_find_property(sub_sns_reg_np, uv_ua, &len)) { + pr_err("%s, uv_ua fail.\n", __func__); + return 0; + } + + rc = of_property_read_u32_array(sub_sns_reg_np, uv_ua, + uv_ua_vals, + ARRAY_SIZE(uv_ua_vals)); + if (rc) { + pr_err("Failed subsensor uVuA value(rc:%d)\n", rc); + return rc; + } + + adsp->reg_cnt++; + if (uv_ua_vals[0] > 0) + adsp->regs[subsensor_supply_reg_idx].uV = uv_ua_vals[0]; + if (uv_ua_vals[1] > 0) + adsp->regs[subsensor_supply_reg_idx].uA = uv_ua_vals[1]; + set_subsensor_vdd_done = true; + pr_info("found subsensor vdd, idx: %d, total:%d\n", + subsensor_supply_reg_idx, adsp->reg_cnt); + } else { + pr_info("subsensor vdd already set\n"); } return 0; } +EXPORT_SYMBOL_GPL(adsp_init_subsensor_regulator); +#endif static void adsp_init_bus_scaling(struct qcom_adsp *adsp) { @@ -1860,6 +2108,18 @@ static int adsp_remove(struct platform_device *pdev) return 0; } +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) +static void adsp_shutdown(struct platform_device *pdev) +{ + struct qcom_adsp *adsp = platform_get_drvdata(pdev); + + if (!strcmp(adsp->info_name, "adsp") && is_need_subvdd_disable) { + pr_info("%s - idx (main: %d, sub: %d)\n", __func__, sensor_supply_reg_idx, subsensor_supply_reg_idx); + adsp_stop(adsp->rproc); + } +} +#endif + static const struct adsp_data adsp_resource_init = { .crash_reason_smem = 423, .firmware_name = "adsp.mdt", @@ -2900,6 +3160,9 @@ MODULE_DEVICE_TABLE(of, adsp_of_match); static struct platform_driver adsp_driver = { .probe = adsp_probe, .remove = adsp_remove, +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) + .shutdown = adsp_shutdown, +#endif .driver = { .name = "qcom_q6v5_pas", .of_match_table = adsp_of_match, diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index bb63edb507da..650431d45857 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -1995,4 +1995,9 @@ config RTC_DRV_POLARFIRE_SOC This driver can also be built as a module, if so, the module will be called "rtc-mpfs". +config RTC_AUTO_PWRON + tristate "RTC Auto Power on PMICs" + help + Support for the auto power on alarm on the PMIC. + endif # RTC_CLASS diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index aab22bc63432..0eb21e501403 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -186,3 +186,4 @@ obj-$(CONFIG_RTC_DRV_WM8350) += rtc-wm8350.o obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o obj-$(CONFIG_RTC_DRV_XGENE) += rtc-xgene.o obj-$(CONFIG_RTC_DRV_ZYNQMP) += rtc-zynqmp.o +obj-$(CONFIG_RTC_AUTO_PWRON) += sec_pon_alarm.o diff --git a/drivers/rtc/rtc-pm8xxx.c b/drivers/rtc/rtc-pm8xxx.c index de2ba8bd967b..a6aef805b1cd 100644 --- a/drivers/rtc/rtc-pm8xxx.c +++ b/drivers/rtc/rtc-pm8xxx.c @@ -595,6 +595,44 @@ static int pm8xxx_rtc_suspend(struct device *dev) return 0; } +#if IS_ENABLED(CONFIG_RTC_AUTO_PWRON) +static struct rtc_wkalrm pwron_alarm; + +void pmic_rtc_setalarm(struct rtc_wkalrm *alm) +{ + memcpy(&pwron_alarm, alm, sizeof(struct rtc_wkalrm)); +} +EXPORT_SYMBOL(pmic_rtc_setalarm); + +static void pm8xxx_rtc_shutdown(struct platform_device *pdev) +{ + struct rtc_wkalrm alarm; + int rc = 0; + + if (pdev) { + pm8xxx_rtc_set_alarm(&pdev->dev, &pwron_alarm); + rc = pm8xxx_rtc_read_alarm(&pdev->dev, &alarm); + if (!rc) { + pr_info("%s: %d-%02d-%02d %02d:%02d:%02d\n", __func__, + alarm.time.tm_year + 1900, alarm.time.tm_mon + 1, alarm.time.tm_mday, + alarm.time.tm_hour, alarm.time.tm_min, alarm.time.tm_sec); + } + } else + pr_err("%s: spmi device not found\n", __func__); +} +#endif + +#if IS_ENABLED(CONFIG_SEC_SAPA_SHIPMODE) +static struct rtc_wkalrm sapa_shipmode_alarm; +void sapa_shipmode_setalarm(struct rtc_wkalrm *alm) +{ + memcpy(&sapa_shipmode_alarm, alm, sizeof(struct rtc_wkalrm)); +} +EXPORT_SYMBOL(sapa_shipmode_setalarm); + +// TODO: After Shutdown implementation is completed, add sec-sapa-shipmode logic to Shutdown function +#endif + static const struct dev_pm_ops pm8xxx_rtc_pm_ops = { .freeze = pm8xxx_rtc_freeze, .restore = pm8xxx_rtc_restore, @@ -604,6 +642,9 @@ static const struct dev_pm_ops pm8xxx_rtc_pm_ops = { static struct platform_driver pm8xxx_rtc_driver = { .probe = pm8xxx_rtc_probe, +#if IS_ENABLED(CONFIG_RTC_AUTO_PWRON) + .shutdown = pm8xxx_rtc_shutdown, +#endif .remove = pm8xxx_remove, .driver = { .name = "rtc-pm8xxx", diff --git a/drivers/rtc/sec_pon_alarm.c b/drivers/rtc/sec_pon_alarm.c new file mode 100644 index 000000000000..589505b9067a --- /dev/null +++ b/drivers/rtc/sec_pon_alarm.c @@ -0,0 +1,374 @@ +/* + * /drivers/rtc/power-on-alarm.c + * + * Copyright (C) 2021 Samsung Electronics + * + * 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 + +#define PON_ALARM_KPARAM_MAGIC 0x41504153 +#define PON_ALARM_START_POLL_TIME (10LL * NSEC_PER_SEC) /* 10 sec */ +#define PON_ALARM_BOOTING_TIME (5*60) +#define PON_ALARM_POLL_TIME (15*60) + +#define BOOTALM_BIT_EN 0 +#define BOOTALM_BIT_YEAR 1 +#define BOOTALM_BIT_MONTH 5 +#define BOOTALM_BIT_DAY 7 +#define BOOTALM_BIT_HOUR 9 +#define BOOTALM_BIT_MIN 11 +#define BOOTALM_BIT_TOTAL 13 + +enum { + PON_ALARM_DISTANT = 0, + PON_ALARM_NEAR, + PON_ALARM_EXPIRED, + PON_ALARM_OVER, + PON_ALARM_ERROR, +}; + +struct pon_alarm_info { + struct device *dev; + struct rtc_device *rtcdev; + struct rtc_wkalrm val; + struct alarm check_poll; + struct work_struct check_func; + struct wakeup_source *ws; + uint lpm_mode; + unsigned char triggered; +}; + +struct alarm_timespec { + char alarm[14]; +}; + +#define ANDROID_ALARM_BASE_CMD(cmd) (cmd & ~(_IOC(0, 0, 0xf0, 0))) +#define ANDROID_ALARM_SET_ALARM_BOOT _IOW('a', 7, struct alarm_timespec) + + +extern void pmic_rtc_setalarm(struct rtc_wkalrm *alm); + +static unsigned int __read_mostly rtcalarm; +static unsigned int __read_mostly lpcharge; +module_param(rtcalarm, uint, 0444); +module_param(lpcharge, uint, 0444); + +struct pon_alarm_info pon_alarm; + +static void pon_alarm_normalize(struct rtc_wkalrm *alarm) +{ + if (!alarm->enabled) { + /* RTC reset + 50 years = 1580518864 = 0x5e34cdd0 */ + alarm->time.tm_year = 70 + 50; + alarm->time.tm_mon = 1; + alarm->time.tm_mday = 1; + alarm->time.tm_hour = 1; + alarm->time.tm_min = 1; + alarm->time.tm_sec = 4; + } +} + +static void pon_alarm_set(struct rtc_wkalrm *alarm) +{ + time64_t secs_pwron; + unsigned int sapa[3]; + int ret; + + memcpy(&pon_alarm.val, alarm, sizeof(struct rtc_wkalrm)); + pon_alarm_normalize(&pon_alarm.val); + + pr_info("%s: reserve pmic alarm\n", __func__); + pmic_rtc_setalarm(&pon_alarm.val); + + secs_pwron = rtc_tm_to_time64(&pon_alarm.val.time); + sapa[0] = PON_ALARM_KPARAM_MAGIC; + sapa[1] = (unsigned int)pon_alarm.val.enabled; + sapa[2] = (unsigned int)secs_pwron; + ret = sec_set_param(param_index_sapa, sapa); + pr_info("%s: ret=%d, enabled=%d, alarm=%u\n", + __func__, ret, sapa[1], sapa[2]); +} + +static void pon_alarm_parse_data(char *alarm_data, struct rtc_wkalrm *alm) +{ + char buf_ptr[BOOTALM_BIT_TOTAL+1] = {0,}; + struct rtc_time rtc_tm; + time64_t rtc_sec; + time64_t rtc_alm_sec; + struct timespec64 delta; + struct timespec64 ktm_ts; + struct rtc_time ktm_tm; + int ret; + + alarm_data[BOOTALM_BIT_TOTAL] = '\0'; + strlcpy(buf_ptr, alarm_data, BOOTALM_BIT_TOTAL+1); + + /* 0|1234|56|78|90|12 */ + /* 1|2010|01|01|00|00 */ + /*en yyyy mm dd hh mm */ + alm->time.tm_sec = 0; + alm->time.tm_min = (buf_ptr[BOOTALM_BIT_MIN]-'0') * 10 + + (buf_ptr[BOOTALM_BIT_MIN+1]-'0'); + alm->time.tm_hour = (buf_ptr[BOOTALM_BIT_HOUR]-'0') * 10 + + (buf_ptr[BOOTALM_BIT_HOUR+1]-'0'); + alm->time.tm_mday = (buf_ptr[BOOTALM_BIT_DAY]-'0') * 10 + + (buf_ptr[BOOTALM_BIT_DAY+1]-'0'); + alm->time.tm_mon = (buf_ptr[BOOTALM_BIT_MONTH]-'0') * 10 + + (buf_ptr[BOOTALM_BIT_MONTH+1]-'0'); + alm->time.tm_year = (buf_ptr[BOOTALM_BIT_YEAR]-'0') * 1000 + + (buf_ptr[BOOTALM_BIT_YEAR+1]-'0') * 100 + + (buf_ptr[BOOTALM_BIT_YEAR+2]-'0') * 10 + + (buf_ptr[BOOTALM_BIT_YEAR+3]-'0'); + + alm->enabled = (*buf_ptr == '1'); + if (*buf_ptr == '2') + alm->enabled = 2; + + pr_info("%s: %s => tm(%d %04d-%02d-%02d %02d:%02d:%02d)\n", + __func__, buf_ptr, alm->enabled, + alm->time.tm_year, alm->time.tm_mon, alm->time.tm_mday, + alm->time.tm_hour, alm->time.tm_min, alm->time.tm_sec); + + if (alm->enabled) { + /* read kernel time */ + ktime_get_real_ts64(&ktm_ts); + ktm_tm = rtc_ktime_to_tm(timespec64_to_ktime(ktm_ts)); + pr_info("%s: %4d-%02d-%02d %02d:%02d:%02d\n", + __func__, ktm_tm.tm_year+1900, ktm_tm.tm_mon+1, ktm_tm.tm_mday, + ktm_tm.tm_hour, ktm_tm.tm_min, ktm_tm.tm_sec); + + alm->time.tm_mon -= 1; + alm->time.tm_year -= 1900; + pr_info("%s: %4d-%02d-%02d %02d:%02d:%02d\n", + __func__, alm->time.tm_year+1900, alm->time.tm_mon+1, alm->time.tm_mday, + alm->time.tm_hour, alm->time.tm_min, alm->time.tm_sec); + + /* read current time */ + ret = rtc_read_time(pon_alarm.rtcdev, &rtc_tm); + if (ret) { + pr_err("%s: rtc read failed, ret=%d\n", __func__, ret); + return; + } + rtc_sec = rtc_tm_to_time64(&rtc_tm); + pr_info("%s: %4d-%02d-%02d %02d:%02d:%02d -> %lu\n", + __func__, rtc_tm.tm_year, rtc_tm.tm_mon, rtc_tm.tm_mday, + rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec, rtc_sec); + + /* calculate offset */ + set_normalized_timespec64(&delta, + (time64_t)ktm_ts.tv_sec - rtc_sec, + (s64)ktm_ts.tv_nsec); + + /* UTC -> RTC */ + rtc_alm_sec = rtc_tm_to_time64(&alm->time) - delta.tv_sec; + rtc_alm_sec = (rtc_alm_sec & ~0x00000003) | ((alm->enabled-1)<<1); + alm->enabled = 1; + rtc_time64_to_tm(rtc_alm_sec, &alm->time); + pr_info("%s: %4d-%02d-%02d %02d:%02d:%02d -> %lu\n", + __func__, alm->time.tm_year, alm->time.tm_mon, alm->time.tm_mday, + alm->time.tm_hour, alm->time.tm_min, alm->time.tm_sec, rtc_alm_sec); + } +} + +static int pon_alarm_check_state(time64_t *data) +{ + struct rtc_time tm; + time64_t rtc_secs; + time64_t secs_pwron; + int res = PON_ALARM_NEAR; + int rc; + + rc = rtc_read_time(pon_alarm.rtcdev, &tm); + if (rc) { + pr_err("%s: rtc read failed.\n", __func__); + return PON_ALARM_ERROR; + } + rtc_secs = rtc_tm_to_time64(&tm); + secs_pwron = rtc_tm_to_time64(&pon_alarm.val.time); + + if (rtc_secs < secs_pwron) { + if (secs_pwron - rtc_secs > PON_ALARM_POLL_TIME) + res = PON_ALARM_DISTANT; + if (data) + *data = secs_pwron - rtc_secs; + } else if (rtc_secs <= secs_pwron + PON_ALARM_BOOTING_TIME) { + res = PON_ALARM_EXPIRED; + if (data) + *data = rtc_secs + 10; + } else + res = PON_ALARM_OVER; + + pr_info("%s: rtc:%lu, alrm:%lu[%d]\n", + __func__, rtc_secs, secs_pwron, res); + return res; +} + +static void pon_alarm_check_func(struct work_struct *work) +{ + struct pon_alarm_info *pona = container_of(work, struct pon_alarm_info, check_func); + int res; + time64_t remain; + + res = pon_alarm_check_state(&remain); + if (res <= PON_ALARM_NEAR) { + ktime_t kt; + + if (res == PON_ALARM_DISTANT) + remain = PON_ALARM_POLL_TIME; + kt = ns_to_ktime((u64)remain * NSEC_PER_SEC); + alarm_start_relative(&pona->check_poll, kt); + pr_info("%s: next %lu s\n", __func__, remain); + } else if (res == PON_ALARM_EXPIRED) { + __pm_stay_awake(pona->ws); + pona->triggered = 1; + } +} + +static enum alarmtimer_restart pon_alarm_check_callback(struct alarm *alarm, ktime_t now) +{ + struct pon_alarm_info *pona = container_of(alarm, struct pon_alarm_info, check_poll); + + schedule_work(&pona->check_func); + return ALARMTIMER_NORESTART; +} + + +static int pon_alarm_open(struct inode *inode, struct file *file) +{ + file->private_data = NULL; + return 0; +} + +static ssize_t pon_alarm_read(struct file *file, + char __user *buff, size_t count, loff_t *ppos) +{ + char trigger = (pon_alarm.triggered) ? '1':'0'; + + if (copy_to_user((void __user *)buff, &trigger, 1)) { + pr_err("%s: read failed.\n", __func__); + return -EFAULT; + } + pr_info("%s: trigger=%c\n", __func__, trigger); + return 1; +} + +static long pon_alarm_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int rv = 0; + struct alarm_timespec data; + struct rtc_wkalrm alm; + + pr_info("%s: cmd=%08x\n", __func__, cmd); + + switch (ANDROID_ALARM_BASE_CMD(cmd)) { + case ANDROID_ALARM_SET_ALARM_BOOT: + if (copy_from_user(data.alarm, (void __user *)arg, 14)) { + rv = -EFAULT; + pr_err("%s: SET ret=%s\n", __func__, rv); + return rv; + } + pon_alarm_parse_data(data.alarm, &alm); + pon_alarm_set(&alm); + break; + } + + return rv; +} + +static int pon_alarm_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static const struct file_operations pon_alarm_fops = { + .owner = THIS_MODULE, + .open = pon_alarm_open, + .read = pon_alarm_read, + .unlocked_ioctl = pon_alarm_ioctl, + .release = pon_alarm_release, +}; + +static struct miscdevice pon_alarm_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "power_on_alarm", + .fops = &pon_alarm_fops, +}; + + +static int __init pon_alarm_init(void) +{ + struct rtc_device *rtcdev; + int ret; + + rtcdev = alarmtimer_get_rtcdev(); + if (!rtcdev) { + pr_err("%s: no rtcdev (defer)\n", __func__); + return -ENODEV; + } + + ret = misc_register(&pon_alarm_device); + if (ret) { + pr_err("%s: ret=%d\n", __func__, ret); + return ret; + } + + pr_info("%s: rtcalarm=%u, lpcharge=%u\n", __func__, rtcalarm, lpcharge); + pon_alarm.rtcdev = rtcdev; + pon_alarm.lpm_mode = lpcharge; + pon_alarm.triggered = 0; + pon_alarm.val.enabled = (rtcalarm) ? 1 : 0; + rtc_time64_to_tm((time64_t)rtcalarm, &pon_alarm.val.time); + + if (pon_alarm.lpm_mode && pon_alarm.val.enabled) { + pr_info("%s: reserve pmic alarm\n", __func__); + pmic_rtc_setalarm(&pon_alarm.val); + pon_alarm.ws = wakeup_source_register(pon_alarm.dev, "PON_ALARM"); + + alarm_init(&pon_alarm.check_poll, ALARM_REALTIME, pon_alarm_check_callback); + INIT_WORK(&pon_alarm.check_func, pon_alarm_check_func); + alarm_start_relative(&pon_alarm.check_poll, ns_to_ktime(PON_ALARM_START_POLL_TIME)); + } + + pr_info("%s: done\n", __func__); + return 0; +} + +static void __exit pon_alarm_exit(void) +{ + misc_deregister(&pon_alarm_device); + if (pon_alarm.lpm_mode && pon_alarm.val.enabled) { + pr_info("%s: clear for lpm_mode\n", __func__); + cancel_work_sync(&pon_alarm.check_func); + alarm_cancel(&pon_alarm.check_poll); + wakeup_source_unregister(pon_alarm.ws); + } +} + +module_init(pon_alarm_init); +module_exit(pon_alarm_exit); + +MODULE_ALIAS("platform:sec_pon_alarm"); +MODULE_DESCRIPTION("SEC_PON_ALARM driver"); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/samsung/Kconfig b/drivers/samsung/Kconfig new file mode 100644 index 000000000000..b646f145226a --- /dev/null +++ b/drivers/samsung/Kconfig @@ -0,0 +1,4 @@ +source "drivers/samsung/bsp/Kconfig" +source "drivers/samsung/debug/Kconfig" +source "drivers/samsung/knox/Kconfig" +source "drivers/samsung/power/Kconfig" diff --git a/drivers/samsung/Makefile b/drivers/samsung/Makefile new file mode 100644 index 000000000000..f15558b92d2d --- /dev/null +++ b/drivers/samsung/Makefile @@ -0,0 +1,4 @@ +obj-y += bsp/ +obj-y += debug/ +obj-y += knox/ +obj-y += power/ diff --git a/drivers/samsung/bsp/Kconfig b/drivers/samsung/bsp/Kconfig new file mode 100644 index 000000000000..49ee3d4b9c17 --- /dev/null +++ b/drivers/samsung/bsp/Kconfig @@ -0,0 +1,27 @@ +menu "Samsung Factory Feature" +config SEC_FACTORY + bool "when it comes to sec factory mode" + default n + select SEC_PANIC_PCIE_ERR + help + It will support a sec factory mode +endmenu + +config SEC_FACTORY_INTERPOSER + bool "Samsung Factory interposer binary" + default n + help + Samsung Factory interposer binary + +config SAMSUNG_PRODUCT_SHIP + bool "set up for product shipping" + default n + +source "drivers/samsung/bsp/class/Kconfig" +source "drivers/samsung/bsp/param/Kconfig" +source "drivers/samsung/bsp/key_notifier/Kconfig" +source "drivers/samsung/bsp/reloc_gpio/Kconfig" +source "drivers/samsung/bsp/argos/Kconfig" +source "drivers/samsung/bsp/of_kunit/Kconfig" + +source "drivers/samsung/bsp/qcom/Kconfig" diff --git a/drivers/samsung/bsp/Makefile b/drivers/samsung/bsp/Makefile new file mode 100644 index 000000000000..9fac7898413b --- /dev/null +++ b/drivers/samsung/bsp/Makefile @@ -0,0 +1,8 @@ +obj-$(CONFIG_SEC_CLASS) += class/ +obj-$(CONFIG_SEC_KEY_NOTIFIER) += key_notifier/ +obj-$(CONFIG_SEC_PARAM) += param/ +obj-$(CONFIG_SEC_RELOC_GPIO) += reloc_gpio/ +obj-$(CONFIG_ARGOS) += argos/ +obj-$(CONFIG_SEC_OF_KUNIT) += of_kunit/ + +obj-y += qcom/ diff --git a/drivers/samsung/bsp/argos/Kconfig b/drivers/samsung/bsp/argos/Kconfig new file mode 100644 index 000000000000..8e61daa3fa80 --- /dev/null +++ b/drivers/samsung/bsp/argos/Kconfig @@ -0,0 +1,23 @@ +config ARGOS + tristate "Throughput monitoring Feature" + help + This option enables monitoring the data throughput and doing several actions for + enhancing the performance such as adjusting the CPU freqency, allocating the tasks + to the appropriate CPU and so on + +config ARGOS_THROUGHPUT + default y if ARGOS=m + default n if ARGOS=y + bool "argos Throughput device" + help + This option make /dev/network_throughput in sec_argos module + for substitiution of pm qos /dev/network_throughput + which is deprecated in kernel 5.4. + +config ARGOS_VOTING_DDR + default n + depends on ARGOS + bool "argos voting ddr clock" + help + This option enable ddr voting part in sec_argos module. + diff --git a/drivers/samsung/bsp/argos/Makefile b/drivers/samsung/bsp/argos/Makefile new file mode 100644 index 000000000000..a5d9d7ead5af --- /dev/null +++ b/drivers/samsung/bsp/argos/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_ARGOS) += sec_argos.o diff --git a/drivers/samsung/bsp/argos/sec_argos.c b/drivers/samsung/bsp/argos/sec_argos.c new file mode 100644 index 000000000000..ba361d35a40c --- /dev/null +++ b/drivers/samsung/bsp/argos/sec_argos.c @@ -0,0 +1,937 @@ +/* + * argos.c + * + * Copyright (c) 2012-2023 Samsung Electronics Co., Ltd + * http://www.samsung.com + * + * 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. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_ARGOS_THROUGHPUT +#include +#include +#include +#endif + +#if defined(CONFIG_ARCH_LAHAINA) || defined(CONFIG_ARGOS_VOTING_DDR) +#define ARGOS_VOTING_DDR_CLK +#include +#endif + +#if IS_ENABLED(CONFIG_CPU_FREQ_LIMIT) +#include +#include + +#ifndef CONFIG_CPU_FREQ_LIMIT_USERSPACE +#define DVFS_ARGOS_ID CFLM_ARGOS +int set_freq_limit(unsigned long id, unsigned int freq); +#endif + +#else +#define DVFS_ARGOS_ID 0 +int set_freq_limit(unsigned long id, unsigned int freq) +{ + pr_err("%s is not yet implemented\n", __func__); + return 0; +} +#endif + +#define ARGOS_NAME "argos" +#define TYPE_SHIFT 4 +#define TYPE_MASK_BIT ((1 << TYPE_SHIFT) - 1) + +#define LOCK_RELEASE 0 +#define FREQ_UNLOCK -1 +#define SKIP_FREQ_UPDATE 0 +#define FREQ_UPDATE 1 + +#define CPU_UNLOCK_FREQ -1 +#ifdef ARGOS_VOTING_DDR_CLK +#define DDR_UNLOCK_FREQ 0 +#endif + +//Refer to "include/dt-bindings/interconnect/qcom,lahaina.h" +#define MASTER_APPSS_PROC 2 +#define SLAVE_EBI1 512 +#define BUS_W 4 /* SM8350 DDR Voting('w' for DDR is 4) */ +#define MHZ_TO_KBPS(mhz, w) ((uint64_t)mhz * 1000 * w) + +static DEFINE_SPINLOCK(argos_irq_lock); +static DEFINE_SPINLOCK(argos_task_lock); +static DEFINE_SPINLOCK(argos_boost_list_lock); + +enum { + THRESHOLD, + BIG_MIN_FREQ, + BIG_MAX_FREQ, + LITTLE_MIN_FREQ, + LITTLE_MAX_FREQ, + DDR_FREQ, + RESERVED, + TASK_AFFINITY_EN, + IRQ_AFFINITY_EN, + SCHED_BOOST_EN, + ITEM_MAX, +}; + +enum { + BOOST_CPU, +#ifdef ARGOS_VOTING_DDR_CLK + BOOST_DDR, +#endif + BOOST_MAX +}; + +struct boost_table { + unsigned int items[ITEM_MAX]; +}; + +struct argos_task_affinity { + struct task_struct *p; + struct cpumask *affinity_cpu_mask; + struct cpumask *default_cpu_mask; + struct list_head entry; +}; + +struct argos_irq_affinity { + unsigned int irq; + struct cpumask *affinity_cpu_mask; + struct cpumask *default_cpu_mask; + struct list_head entry; +}; + +struct argos { + const char *desc; + struct platform_device *pdev; + struct boost_table *tables; + int ntables; + int prev_level; + struct list_head task_affinity_list; + bool task_hotplug_disable; + struct list_head irq_affinity_list; + bool irq_hotplug_disable; + bool hmpboost_enable; + bool argos_block; + struct blocking_notifier_head argos_notifier; + /* protect prev_level, qos, task/irq_hotplug_disable, hmpboost_enable */ + struct mutex level_mutex; +}; + +#ifdef CONFIG_ARGOS_THROUGHPUT +#define TPUT_MAX 16 +char argos_throughput[TPUT_MAX]; +#endif + +struct argos_platform_data { + struct argos *devices; + struct device *dev; + int ndevice; +#ifndef CONFIG_ARGOS_THROUGHPUT + struct notifier_block pm_qos_nfb; +#endif + int *boost_list[BOOST_MAX]; + int boost_max[BOOST_MAX]; +}; + +static struct argos_platform_data *argos_pdata; +static int boost_unlock_freq[BOOST_MAX] = { +CPU_UNLOCK_FREQ +#ifdef ARGOS_VOTING_DDR_CLK +, DDR_UNLOCK_FREQ +#endif +}; +#ifdef ARGOS_VOTING_DDR_CLK +struct icc_path *path_argos_bw; +int argos_icc_register = 0; +#endif +static int argos_find_index(const char *label) +{ + int i; + int dev_num = -1; + + if (!argos_pdata) { + pr_err("argos not initialized\n"); + return -1; + } + + for (i = 0; i < argos_pdata->ndevice; i++) + if (strcmp(argos_pdata->devices[i].desc, label) == 0) + dev_num = i; + return dev_num; +} + +int sec_argos_register_notifier(struct notifier_block *n, char *label) +{ + struct blocking_notifier_head *cnotifier; + int dev_num; + + dev_num = argos_find_index(label); + + if (dev_num < 0) { + pr_err("No match found for label: %d", dev_num); + return -ENODEV; + } + + cnotifier = &argos_pdata->devices[dev_num].argos_notifier; + + if (!cnotifier) { + pr_err("argos notifier not found(dev_num:%d)\n", dev_num); + return -ENXIO; + } + + pr_info("%pf(dev_num:%d)\n", n->notifier_call, dev_num); + + return blocking_notifier_chain_register(cnotifier, n); +} +EXPORT_SYMBOL_GPL(sec_argos_register_notifier); + +int sec_argos_unregister_notifier(struct notifier_block *n, char *label) +{ + struct blocking_notifier_head *cnotifier; + int dev_num; + + dev_num = argos_find_index(label); + + if (dev_num < 0) { + pr_err("No match found for label: %d", dev_num); + return -ENODEV; + } + + cnotifier = &argos_pdata->devices[dev_num].argos_notifier; + + if (!cnotifier) { + pr_err("argos notifier not found(dev_num:%d)\n", dev_num); + return -ENXIO; + } + + pr_info("%pf(dev_num:%d)\n", n->notifier_call, dev_num); + + return blocking_notifier_chain_unregister(cnotifier, n); +} +EXPORT_SYMBOL_GPL(sec_argos_unregister_notifier); + +static int argos_task_affinity_setup(struct task_struct *p, int dev_num, + struct cpumask *affinity_cpu_mask, + struct cpumask *default_cpu_mask) +{ + struct argos_task_affinity *this; + struct list_head *head; + + if (!argos_pdata) { + pr_err("argos not initialized\n"); + return -ENXIO; + } + + if (dev_num < 0 || dev_num >= argos_pdata->ndevice) { + pr_err("dev_num:%d should be dev_num:0 ~ %d in boundary\n", + dev_num, argos_pdata->ndevice - 1); + return -EINVAL; + } + + head = &argos_pdata->devices[dev_num].task_affinity_list; + + this = kzalloc(sizeof(*this), GFP_ATOMIC); + if (!this) + return -ENOMEM; + + this->p = p; + this->affinity_cpu_mask = affinity_cpu_mask; + this->default_cpu_mask = default_cpu_mask; + + spin_lock(&argos_task_lock); + list_add(&this->entry, head); + spin_unlock(&argos_task_lock); + + return 0; +} + +int argos_task_affinity_setup_label(struct task_struct *p, const char *label, + struct cpumask *affinity_cpu_mask, + struct cpumask *default_cpu_mask) +{ + int dev_num; + + dev_num = argos_find_index(label); + + return argos_task_affinity_setup(p, dev_num, affinity_cpu_mask, + default_cpu_mask); +} + +static int argos_irq_affinity_setup(unsigned int irq, int dev_num, + struct cpumask *affinity_cpu_mask, + struct cpumask *default_cpu_mask) +{ + struct argos_irq_affinity *this; + struct list_head *head; + + if (!argos_pdata) { + pr_err("argos not initialized\n"); + return -ENXIO; + } + + if (dev_num < 0 || dev_num >= argos_pdata->ndevice) { + pr_err("dev_num:%d should be dev_num:0 ~ %d in boundary\n", + dev_num, argos_pdata->ndevice - 1); + return -EINVAL; + } + + head = &argos_pdata->devices[dev_num].irq_affinity_list; + + this = kzalloc(sizeof(*this), GFP_ATOMIC); + if (!this) + return -ENOMEM; + + this->irq = irq; + this->affinity_cpu_mask = affinity_cpu_mask; + this->default_cpu_mask = default_cpu_mask; + + spin_lock(&argos_irq_lock); + list_add(&this->entry, head); + spin_unlock(&argos_irq_lock); + + return 0; +} + +int argos_irq_affinity_setup_label(unsigned int irq, const char *label, + struct cpumask *affinity_cpu_mask, + struct cpumask *default_cpu_mask) +{ + int dev_num; + + dev_num = argos_find_index(label); + + return argos_irq_affinity_setup(irq, dev_num, affinity_cpu_mask, + default_cpu_mask); +} + +int argos_task_affinity_apply(int dev_num, bool enable) +{ + struct argos_task_affinity *this; + struct list_head *head; + int result = 0; + struct cpumask *mask; + bool *hotplug_disable; + + head = &argos_pdata->devices[dev_num].task_affinity_list; + hotplug_disable = &argos_pdata->devices[dev_num].task_hotplug_disable; + + if (list_empty(head)) { + pr_debug("task_affinity_list is empty\n"); + return result; + } + + list_for_each_entry(this, head, entry) { + if (enable) { + if (!*hotplug_disable) + *hotplug_disable = true; + + mask = this->affinity_cpu_mask; + } else { + if (*hotplug_disable) + *hotplug_disable = false; + + mask = this->default_cpu_mask; + } + + result = set_cpus_allowed_ptr(this->p, mask); + + pr_info("%s affinity %s to cpu_mask:0x%X\n", + this->p->comm, + (enable ? "enable" : "disable"), + (int)*mask->bits); + } + + return result; +} + +int argos_irq_affinity_apply(int dev_num, bool enable) +{ + struct argos_irq_affinity *this; + struct list_head *head; + int result = 0; + struct cpumask *mask; + bool *hotplug_disable; + + head = &argos_pdata->devices[dev_num].irq_affinity_list; + hotplug_disable = &argos_pdata->devices[dev_num].irq_hotplug_disable; + + if (list_empty(head)) { + pr_debug("irq_affinity_list is empty\n"); + return result; + } + + list_for_each_entry(this, head, entry) { + if (enable) { + if (!*hotplug_disable) + *hotplug_disable = true; + + mask = this->affinity_cpu_mask; + } else { + if (*hotplug_disable) + *hotplug_disable = false; + + mask = this->default_cpu_mask; + } +#ifndef CONFIG_ARGOS_THROUGHPUT + result = irq_set_affinity(this->irq, mask); +#endif + pr_info("irq%d affinity %s to cpu_mask:0x%X\n", + this->irq, (enable ? "enable" : "disable"), + (int)*mask->bits); + } + + return result; +} + +int argos_hmpboost_apply(int dev_num, bool enable) +{ + bool *hmpboost_enable; + + hmpboost_enable = &argos_pdata->devices[dev_num].hmpboost_enable; + + if (enable) { + /* disable -> enable */ + if (!*hmpboost_enable) { + *hmpboost_enable = true; + pr_info("hmp boost enable [%d]\n", dev_num); + } + } else { + /* enable -> disable */ + if (*hmpboost_enable) { + *hmpboost_enable = false; + pr_info("hmp boost disable [%d]\n", dev_num); + } + } + + return 0; +} + +static int find_max(int dev_type, int *freq, int ndevice){ + int i, max = boost_unlock_freq[dev_type]; + for (i = 0; i < ndevice; i++){ + if(freq[i] > max) max = freq[i]; + } + + return max; +} + +static int check_update_freq(int boost_type, int dev_type, int target) +{ + int ret = SKIP_FREQ_UPDATE, new_max, prev; + + spin_lock(&argos_boost_list_lock); + + prev = argos_pdata->boost_list[boost_type][dev_type]; + argos_pdata->boost_list[boost_type][dev_type] = target; + + new_max = find_max(boost_type, argos_pdata->boost_list[boost_type], \ + argos_pdata->ndevice); + spin_unlock(&argos_boost_list_lock); + + if(new_max > argos_pdata->boost_max[boost_type] \ + || (prev == argos_pdata->boost_max[boost_type] \ + && new_max != argos_pdata->boost_max[boost_type])){ + argos_pdata->boost_max[boost_type] = new_max; + ret = FREQ_UPDATE; + } + return ret; +} + +static void argos_freq_lock(int type, int level) +{ + unsigned int big_min_freq, little_min_freq; + int target_freq, need_update; + struct boost_table *t = &argos_pdata->devices[type].tables[level]; + const char *cname; + + cname = argos_pdata->devices[type].desc; + + if(level != FREQ_UNLOCK){ + t = &argos_pdata->devices[type].tables[level]; + big_min_freq = t->items[BIG_MIN_FREQ]; + little_min_freq = t->items[LITTLE_MIN_FREQ]; + } + + if(level != FREQ_UNLOCK) + target_freq = (big_min_freq > little_min_freq) ? + big_min_freq : little_min_freq; + else + target_freq = boost_unlock_freq[BOOST_CPU]; + + need_update = check_update_freq(BOOST_CPU, type, target_freq); + if(need_update != SKIP_FREQ_UPDATE){ + pr_info("update cpu freq %d\n", argos_pdata->boost_max[BOOST_CPU]); + set_freq_limit(DVFS_ARGOS_ID, argos_pdata->boost_max[BOOST_CPU]); + } +#ifdef ARGOS_VOTING_DDR_CLK + if(level != FREQ_UNLOCK) + target_freq = t->items[DDR_FREQ]; + else + target_freq = boost_unlock_freq[BOOST_DDR]; + + need_update = check_update_freq(BOOST_DDR, type, target_freq); + if(need_update != SKIP_FREQ_UPDATE){ + pr_info("update ddr freq %d\n", argos_pdata->boost_max[BOOST_DDR]); + icc_set_bw(path_argos_bw, 0, MHZ_TO_KBPS(argos_pdata->boost_max[BOOST_DDR], BUS_W)); + } +#endif +} + +void argos_block_enable(char *req_name, bool set) +{ + int dev_num; + struct argos *cnode; + + dev_num = argos_find_index(req_name); + + if (dev_num < 0) { + pr_err("No match found for label: %s", req_name); + return; + } + + cnode = &argos_pdata->devices[dev_num]; + + if (set) { + cnode->argos_block = true; + mutex_lock(&cnode->level_mutex); + argos_freq_lock(dev_num, FREQ_UNLOCK); + argos_task_affinity_apply(dev_num, 0); + argos_irq_affinity_apply(dev_num, 0); + argos_hmpboost_apply(dev_num, 0); + cnode->prev_level = -1; + mutex_unlock(&cnode->level_mutex); + } else { + cnode->argos_block = false; + } + pr_info("req_name:%s block:%d\n", + req_name, cnode->argos_block); +} + +#ifndef CONFIG_ARGOS_THROUGHPUT +static int argos_cpuidle_reboot_notifier(struct notifier_block *this, + unsigned long event, void *_cmd) +{ + switch (event) { + case SYSTEM_POWER_OFF: + case SYS_RESTART: + pr_info("called\n"); + pm_qos_remove_notifier(PM_QOS_NETWORK_THROUGHPUT, + &argos_pdata->pm_qos_nfb); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block argos_cpuidle_reboot_nb = { + .notifier_call = argos_cpuidle_reboot_notifier, +}; +#endif + +#ifdef ARGOS_VOTING_DDR_CLK +static void get_icc_path(void) +{ + struct device *dev = argos_pdata->dev; + int bus_ret = 0; + + path_argos_bw = icc_get(dev, MASTER_APPSS_PROC, SLAVE_EBI1); + if (IS_ERR(path_argos_bw)) { + bus_ret = PTR_ERR(path_argos_bw); + dev_err(dev, "Failed to get path_argos_bw. ret=%d\n", bus_ret); + if (bus_ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get icc path. ret=%d\n", bus_ret); + } else { + dev_info(dev, "Success to get path_argos_bw.\n"); + argos_icc_register = 1; + } + +} +#endif + +#if defined (CONFIG_ARGOS_THROUGHPUT) +static int argos_pm_qos_notify(unsigned long speedtype) +#else +static int argos_pm_qos_notify(struct notifier_block *nfb, + unsigned long speedtype, void *arg) +#endif +{ + int type, level, prev_level; + unsigned long speed; + bool argos_blocked; + struct argos *cnode; + + type = (speedtype & TYPE_MASK_BIT) - 1; + if (type < 0 || type > argos_pdata->ndevice) { + pr_err("There is no type for devices type[%d], ndevice[%d]\n", + type, argos_pdata->ndevice); + return NOTIFY_BAD; + } + +#ifdef ARGOS_VOTING_DDR_CLK + if (argos_icc_register == 0){ + get_icc_path(); + } +#endif + + speed = speedtype >> TYPE_SHIFT; + cnode = &argos_pdata->devices[type]; + + prev_level = cnode->prev_level; + + argos_blocked = cnode->argos_block; + + if (cnode->tables[0].items[THRESHOLD] == 0) { + pr_debug("skip not used name:%s, speed:%ldMbps\n",\ + cnode->desc, speed); + goto out; + } + + /* Find proper level */ + for (level = 0; level < cnode->ntables; level++) { + struct boost_table *t = &cnode->tables[level]; + + if (speed < t->items[THRESHOLD]) { + break; + } else if (argos_pdata->devices[type].ntables == level) { + level++; + break; + } + } + + /* decrease 1 level to match proper table */ + level--; + if (!argos_blocked) { + if (level != prev_level) { + if (mutex_trylock(&cnode->level_mutex) == 0) { + /* + * If the mutex is already locked, it means this argos + * is being blocked or is handling another change. + * We don't need to wait. + */ + pr_warn("skip name:%s, speed:%ldMbps, prev level:%d, request level:%d\n", + cnode->desc, speed, prev_level, level); + goto out; + } + pr_info("name:%s, speed:%ldMbps, prev level:%d, request level:%d\n", + cnode->desc, speed, prev_level, level); + + if (level == FREQ_UNLOCK) { + if (cnode->argos_notifier.head) { + pr_debug("Call argos notifier(%s lev:%d)\n", + cnode->desc, level); + blocking_notifier_call_chain(&cnode->argos_notifier, + speed, NULL); + } + argos_freq_lock(type, FREQ_UNLOCK); + argos_task_affinity_apply(type, 0); + argos_irq_affinity_apply(type, 0); + argos_hmpboost_apply(type, 0); + } else { + unsigned int enable_flag; + + argos_freq_lock(type, level); + /* FIXME should control affinity and hmp boost */ + + enable_flag = argos_pdata->devices[type].tables[level].items[TASK_AFFINITY_EN]; + argos_task_affinity_apply(type, enable_flag); + enable_flag = argos_pdata->devices[type].tables[level].items[IRQ_AFFINITY_EN]; + argos_irq_affinity_apply(type, enable_flag); + enable_flag = + argos_pdata->devices[type].tables[level].items[SCHED_BOOST_EN]; + argos_hmpboost_apply(type, enable_flag); + + if (cnode->argos_notifier.head) { + pr_debug("Call argos notifier(%s lev:%d)\n", + cnode->desc, level); + blocking_notifier_call_chain(&cnode->argos_notifier, + speed, NULL); + } + } + + cnode->prev_level = level; + mutex_unlock(&cnode->level_mutex); + } else { + pr_debug("same level (%d) is requested", level); + } + } +out: + return NOTIFY_OK; +} + +#ifdef CONFIG_OF +static int argos_parse_dt(struct device *dev) +{ + struct argos_platform_data *pdata = dev->platform_data; + struct argos *cnode; + struct device_node *np, *cnp; + int device_count = 0, num_level = 0; + int retval = 0, i, j; + + np = dev->of_node; + pdata->ndevice = of_get_child_count(np); + if (!pdata->ndevice) + return -ENODEV; + + pdata->devices = devm_kzalloc(dev, sizeof(struct argos) * pdata->ndevice, GFP_KERNEL); + if (!pdata->devices) + return -ENOMEM; + + for_each_child_of_node(np, cnp) { + cnode = &pdata->devices[device_count]; + cnode->desc = of_get_property(cnp, "net_boost,label", NULL); + if (of_property_read_u32(cnp, "net_boost,table_size", &num_level)) { + dev_err(dev, "Failed to get table size: node not exist\n"); + retval = -EINVAL; + goto err_out; + } + cnode->ntables = num_level; + + /* Allocation for freq and time table */ + if (!cnode->tables) { + cnode->tables = devm_kzalloc(dev, + sizeof(struct boost_table) * cnode->ntables, GFP_KERNEL); + if (!cnode->tables) { + retval = -ENOMEM; + goto err_out; + } + } + + /* Get and add frequency and time table */ + for (i = 0; i < num_level; i++) { + for (j = 0; j < ITEM_MAX; j++) { + retval = of_property_read_u32_index(cnp, "net_boost,table", + i * ITEM_MAX + j, &cnode->tables[i].items[j]); + if (retval) { + dev_err(dev, "Failed to get property\n"); + retval = -EINVAL; + goto err_out; + } + } + } + + INIT_LIST_HEAD(&cnode->task_affinity_list); + INIT_LIST_HEAD(&cnode->irq_affinity_list); + cnode->task_hotplug_disable = false; + cnode->irq_hotplug_disable = false; + cnode->hmpboost_enable = false; + cnode->argos_block = false; + cnode->prev_level = -1; + mutex_init(&cnode->level_mutex); + + BLOCKING_INIT_NOTIFIER_HEAD(&cnode->argos_notifier); + + device_count++; + } + + return 0; + +err_out: + return retval; +} +#endif + +static int argos_probe(struct platform_device *pdev) +{ + int i, j, ret = 0; + struct argos_platform_data *pdata; + + pr_info("Start probe\n"); + if (pdev->dev.of_node) { + pdata = devm_kzalloc(&pdev->dev, + sizeof(struct argos_platform_data), + GFP_KERNEL); + + if (!pdata) { + dev_err(&pdev->dev, "Failed to allocate platform data\n"); + return -ENOMEM; + } + + pdev->dev.platform_data = pdata; + + ret = argos_parse_dt(&pdev->dev); + + if (ret) { + dev_err(&pdev->dev, "Failed to parse dt data\n"); + return ret; + } + pr_info("parse dt done\n"); + + for(i = 0; i < BOOST_MAX; i++){ + pdata->boost_list[i] = devm_kzalloc(&pdev->dev, sizeof(int) * pdata->ndevice, GFP_KERNEL); + if (!pdata->boost_list[i]) { + dev_err(&pdev->dev, "Failed to allocate boosting frequency list\n"); + return -ENOMEM; + } + + for (j = 0; j < pdata->ndevice; j++){ + pdata->boost_list[i][j] = boost_unlock_freq[i]; + } + pdata->boost_max[i] = boost_unlock_freq[i]; + } + } else { + pdata = pdev->dev.platform_data; + } + + if (!pdata) { + dev_err(&pdev->dev, "There are no platform data\n"); + return -EINVAL; + } + + if (!pdata->ndevice || !pdata->devices) { + dev_err(&pdev->dev, "There are no devices\n"); + return -EINVAL; + } + +#ifndef CONFIG_ARGOS_THROUGHPUT + pdata->pm_qos_nfb.notifier_call = argos_pm_qos_notify; + pm_qos_add_notifier(PM_QOS_NETWORK_THROUGHPUT, &pdata->pm_qos_nfb); + register_reboot_notifier(&argos_cpuidle_reboot_nb); +#endif + argos_pdata = pdata; + argos_pdata->dev = &pdev->dev; + platform_set_drvdata(pdev, pdata); + + return 0; +} + +static int argos_remove(struct platform_device *pdev) +{ + struct argos_platform_data *pdata = platform_get_drvdata(pdev); + + if (!pdata || !argos_pdata) + return 0; +#ifndef CONFIG_ARGOS_THROUGHPUT + pm_qos_remove_notifier(PM_QOS_NETWORK_THROUGHPUT, &pdata->pm_qos_nfb); + unregister_reboot_notifier(&argos_cpuidle_reboot_nb); +#endif +#ifdef ARGOS_VOTING_DDR_CLK + if (argos_icc_register == 1) + icc_put(path_argos_bw); +#endif + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id argos_dt_ids[] = { + { .compatible = "samsung,argos"}, + { } +}; +#endif + +static struct platform_driver argos_driver = { + .driver = { + .name = ARGOS_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = of_match_ptr(argos_dt_ids), +#endif + }, + .probe = argos_probe, + .remove = argos_remove +}; + +#ifdef CONFIG_ARGOS_THROUGHPUT +static ssize_t argos_tput_read(struct file *filep, char __user *buf, + size_t count, loff_t *ppos) +{ + int ret; + + //pr_info("argos_throughput %s\n", argos_throughput); + ret = copy_to_user(buf, (void *)argos_throughput, TPUT_MAX); + if (ret < 0) { + pr_err("fail to copy argos throughput value.\n"); + return -EINVAL; + } + + return ret; +} + +static ssize_t argos_tput_write(struct file *filep, const char __user *buf, + size_t count, loff_t *ppos) +{ + int ret; + unsigned long val; + + ret = copy_from_user(argos_throughput, buf, TPUT_MAX); + if (ret < 0) { + pr_err("fail to get argos throughput value.\n"); + return -EINVAL; + } + + ret = kstrtoul(argos_throughput, 16, &val); + if (ret < 0) { + pr_err("fail to convertet throughput unsigned long.\n"); + return -EINVAL; + } + + argos_pm_qos_notify(val); + + //pr_info("tput : %s\n", argos_throughput); + + return count; +} +static const struct file_operations argos_tput_fops = { + .owner = THIS_MODULE, + .open = NULL, + .read = argos_tput_read, + .write = argos_tput_write, + .llseek = NULL, +}; +static struct miscdevice argos_tput_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "network_throughput", + .fops = &argos_tput_fops, +}; +#endif + +static int __init argos_init(void) +{ + int ret; +#ifdef CONFIG_ARGOS_THROUGHPUT + ret = misc_register(&argos_tput_miscdev); + if (ret) { + pr_err("Failed to register miscdevice"); + goto err; + } +#endif + ret = platform_driver_register(&argos_driver); + if (ret) { + pr_err("Failed to register platform driver"); + goto err; + } + +err: + return ret; +} + +static void __exit argos_exit(void) +{ + return platform_driver_unregister(&argos_driver); +} +late_initcall(argos_init); +module_exit(argos_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("SAMSUNG Electronics"); +MODULE_DESCRIPTION("ARGOS DEVICE"); diff --git a/drivers/samsung/bsp/class/Kconfig b/drivers/samsung/bsp/class/Kconfig new file mode 100644 index 000000000000..266d3fc2724c --- /dev/null +++ b/drivers/samsung/bsp/class/Kconfig @@ -0,0 +1,9 @@ +config SEC_CLASS + tristate "Samsung sec class/sysfs Feature" + select DRV_SAMSUNG + help + Samsung sysfs name 'sec' directory create + +# legacy compatibility +config DRV_SAMSUNG + bool diff --git a/drivers/samsung/bsp/class/Makefile b/drivers/samsung/bsp/class/Makefile new file mode 100644 index 000000000000..fc564f6b43e1 --- /dev/null +++ b/drivers/samsung/bsp/class/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SEC_CLASS) += sec_class.o diff --git a/drivers/samsung/bsp/class/sec_class.c b/drivers/samsung/bsp/class/sec_class.c new file mode 100644 index 000000000000..ba33cc3a7ef9 --- /dev/null +++ b/drivers/samsung/bsp/class/sec_class.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2014-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include + +/* CAUTION : Do not be declared as external sec_class */ +static struct class *sec_class; +static atomic_t sec_dev; + +static int sec_class_match_device_by_name(struct device *dev, const void *data) +{ + const char *name = data; + + return sysfs_streq(name, dev_name(dev)); +} + +struct device *sec_dev_get_by_name(const char *name) +{ + return class_find_device(sec_class, NULL, name, + sec_class_match_device_by_name); +} +EXPORT_SYMBOL_GPL(sec_dev_get_by_name); + +struct device *sec_device_create(void *drvdata, const char *fmt) +{ + struct device *dev; + + if (unlikely(!sec_class)) { + pr_err("Not yet created class(sec)!\n"); + BUG(); + } + + if (IS_ERR(sec_class)) { + pr_err("Failed to create class(sec) %ld\n", PTR_ERR(sec_class)); + BUG(); + } + + dev = device_create(sec_class, NULL, atomic_inc_return(&sec_dev), + drvdata, "%s", fmt); + if (IS_ERR(dev)) + pr_err("Failed to create device %s %ld\n", fmt, PTR_ERR(dev)); + else + pr_debug("%s : %d\n", fmt, dev->devt); + + return dev; +} +EXPORT_SYMBOL_GPL(sec_device_create); + +void sec_device_destroy(dev_t devt) +{ + if (unlikely(!devt)) { + pr_err("Not allowed to destroy dev\n"); + } else { + pr_info("%d\n", devt); + device_destroy(sec_class, devt); + } +} +EXPORT_SYMBOL_GPL(sec_device_destroy); + +static int __init sec_class_create(void) +{ + sec_class = class_create(THIS_MODULE, "sec"); + if (IS_ERR(sec_class)) { + pr_err("Failed to create class(sec) %ld\n", PTR_ERR(sec_class)); + return PTR_ERR(sec_class); + } + + return 0; +} +core_initcall(sec_class_create); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("sec-class"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/bsp/key_notifier/Kconfig b/drivers/samsung/bsp/key_notifier/Kconfig new file mode 100644 index 000000000000..aee8c949fd11 --- /dev/null +++ b/drivers/samsung/bsp/key_notifier/Kconfig @@ -0,0 +1,4 @@ +config SEC_KEY_NOTIFIER + tristate "SEC Atomic keyboard notifierr" + help + TODO: help is not ready. diff --git a/drivers/samsung/bsp/key_notifier/Makefile b/drivers/samsung/bsp/key_notifier/Makefile new file mode 100644 index 000000000000..9b00a113b7d9 --- /dev/null +++ b/drivers/samsung/bsp/key_notifier/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SEC_KEY_NOTIFIER) += sec_key_notifier.o diff --git a/drivers/samsung/bsp/key_notifier/sec_key_notifier.c b/drivers/samsung/bsp/key_notifier/sec_key_notifier.c new file mode 100644 index 000000000000..a8a60873feaa --- /dev/null +++ b/drivers/samsung/bsp/key_notifier/sec_key_notifier.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2016-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include + +#include + +static DEFINE_SPINLOCK(sec_kn_event_lock); +static RAW_NOTIFIER_HEAD(sec_kn_notifier_list); +static int sec_kn_acceptable_event[KEY_MAX] __read_mostly; + +static void inline update_acceptable_event(unsigned int event_code, bool is_add) +{ + if (unlikely(event_code >= KEY_MAX)) { + pr_warn("event_code (%d) must be less thant KEY_MAX!\n", + event_code); + pr_warn("Caller is %pS\n", __builtin_return_address(0)); + return; + } + + if (is_add) + sec_kn_acceptable_event[event_code]++; + else + sec_kn_acceptable_event[event_code]--; + + BUG_ON(sec_kn_acceptable_event[event_code] < 0); +} + +static inline void increase_num_of_acceptable_event(unsigned int event_code) +{ + update_acceptable_event(event_code, true); +} + +int sec_kn_register_notifier(struct notifier_block *nb, + const unsigned int *events, const size_t nr_events) +{ + unsigned long flags; + size_t i; + int err; + + spin_lock_irqsave(&sec_kn_event_lock, flags); + + for (i = 0; i < nr_events; i++) + increase_num_of_acceptable_event(events[i]); + + err = raw_notifier_chain_register(&sec_kn_notifier_list, nb); + + spin_unlock_irqrestore(&sec_kn_event_lock, flags); + + return err; +} +EXPORT_SYMBOL_GPL(sec_kn_register_notifier); + +static inline void decrease_num_of_acceptable_event(unsigned int event_code) +{ + update_acceptable_event(event_code, false); +} + +int sec_kn_unregister_notifier(struct notifier_block *nb, + const unsigned int *events, const size_t nr_events) +{ + unsigned long flags; + size_t i; + int err; + + spin_lock_irqsave(&sec_kn_event_lock, flags); + + for (i = 0; i < nr_events; i++) + decrease_num_of_acceptable_event(events[i]); + + err = raw_notifier_chain_unregister(&sec_kn_notifier_list, nb); + + spin_unlock_irqrestore(&sec_kn_event_lock, flags); + + return err; +} +EXPORT_SYMBOL_GPL(sec_kn_unregister_notifier); + +static inline bool is_event_supported_locked(unsigned int event_type, + unsigned int event_code) +{ + if (event_type != EV_KEY || event_code >= KEY_MAX) + return false; + + return !!sec_kn_acceptable_event[event_code]; +} + +static void sec_kn_event(struct input_handle *handle, unsigned int event_type, + unsigned int event_code, int value) +{ + struct sec_key_notifier_param param = { + .keycode = event_code, + .down = value, + }; + + spin_lock(&sec_kn_event_lock); + + if (!is_event_supported_locked(event_type, event_code)) { + spin_unlock(&sec_kn_event_lock); + return; + } + + raw_notifier_call_chain(&sec_kn_notifier_list, 0, ¶m); + + spin_unlock(&sec_kn_event_lock); +} + +static int sec_kn_connect(struct input_handler *handler, struct input_dev *dev, + const struct input_device_id *id) +{ + struct input_handle *handle; + int error; + + handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = "sec_key_notifier"; + + error = input_register_handle(handle); + if (error) + goto err_free_handle; + + error = input_open_device(handle); + if (error) + goto err_unregister_handle; + + return 0; + +err_unregister_handle: + input_unregister_handle(handle); +err_free_handle: + kfree(handle); + return error; +} + +static void sec_kn_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +static const struct input_device_id sec_kn_ids[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT, + .evbit = { BIT_MASK(EV_KEY) }, + }, + {}, +}; + +static struct input_handler sec_kn_handler = { + .event = sec_kn_event, + .connect = sec_kn_connect, + .disconnect = sec_kn_disconnect, + .name = "sec_key_notifier", + .id_table = sec_kn_ids, +}; + +static int __init sec_kn_init(void) +{ + return input_register_handler(&sec_kn_handler); +} +#if IS_BUILTIN(CONFIG_SEC_KEY_NOTIFIER) +pure_initcall(sec_kn_init); +#else +module_init(sec_kn_init); +#endif + +static void __exit sec_kn_exit(void) +{ + input_unregister_handler(&sec_kn_handler); +} +module_exit(sec_kn_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("Atomic keyboard notifier"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/bsp/of_kunit/Kconfig b/drivers/samsung/bsp/of_kunit/Kconfig new file mode 100644 index 000000000000..d92e5dc7666f --- /dev/null +++ b/drivers/samsung/bsp/of_kunit/Kconfig @@ -0,0 +1,6 @@ +config SEC_OF_KUNIT + tristate "Samsung additional facilites for of / kunit testing" + depends on OF && KUNIT + default n + help + TODO: help is not ready. diff --git a/drivers/samsung/bsp/of_kunit/Makefile b/drivers/samsung/bsp/of_kunit/Makefile new file mode 100644 index 000000000000..270465018e27 --- /dev/null +++ b/drivers/samsung/bsp/of_kunit/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_SEC_OF_KUNIT) += sec_of_kunit.o + +CFLAGS_sec_of_kunit.o = -I$(srctree)/drivers/of diff --git a/drivers/samsung/bsp/of_kunit/sec_of_kunit.c b/drivers/samsung/bsp/of_kunit/sec_of_kunit.c new file mode 100644 index 000000000000..0dc91703426c --- /dev/null +++ b/drivers/samsung/bsp/of_kunit/sec_of_kunit.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include + +#include +#include + +#include "of_private.h" + +static int __of_kunit_prepare_device_tree(struct sec_of_kunit_data *testdata, + const char *compatible, struct sec_of_dtb_info *info) +{ + if (!compatible || !info) + return 0; + + testdata->root = sec_of_kunit_dtb_to_fdt(info); + if (!testdata->root) + return -ENODEV; + + testdata->of_node = of_find_compatible_node(testdata->root, NULL, + compatible); + if (IS_ERR_OR_NULL(testdata->of_node)) + return -ENOENT; + + return 0; +} + +int sec_of_kunit_data_init(struct sec_of_kunit_data *testdata, + const char *name, struct builder *bd, + const char *compatible, struct sec_of_dtb_info *info) +{ + struct miscdevice *misc = &testdata->misc; + int err; + + err = sec_kunit_init_miscdevice(misc, name); + if (err) + return err; + + err = __of_kunit_prepare_device_tree(testdata, compatible, info); + if (err) { + sec_kunit_exit_miscdevice(misc); + return err; + } + + bd->dev = misc->this_device; + testdata->bd = bd; + + return 0; +} +EXPORT_SYMBOL_GPL(sec_of_kunit_data_init); + +void sec_of_kunit_data_exit(struct sec_of_kunit_data *testdata) +{ + sec_kunit_exit_miscdevice(&testdata->misc); + kfree_sensitive(testdata->root); +} +EXPORT_SYMBOL_GPL(sec_of_kunit_data_exit); + +static void *__dt_alloc(u64 __size, u64 align) +{ + u64 size = ALIGN(__size, align); + void *ptr = kmalloc(size, GFP_KERNEL); + + if (!ptr) + panic("%s: Failed to allocate %llu bytes align=0x%llx\n", + __func__, size, align); + + return ptr; +} + +/* NOTE: Inspired from 'drivers/of/unittest.c'. */ +struct device_node *sec_of_kunit_dtb_to_fdt(struct sec_of_dtb_info *info) +{ + struct device_node *root; + u32 data_size; + u32 size; + + data_size = info->dtb_end - info->dtb_begin; + if (!data_size) { + pr_err("No dtb 'overlay_base' to attach\n"); + return ERR_PTR(-ENOENT); + } + + size = fdt_totalsize(info->dtb_begin); + if (size != data_size) { + pr_err("dtb 'overlay_base' header totalsize != actual size"); + return ERR_PTR(-EINVAL); + } + + __unflatten_device_tree((void *)info->dtb_begin, NULL, &root, + __dt_alloc, true); + + return root; +} +EXPORT_SYMBOL_GPL(sec_of_kunit_dtb_to_fdt); + +int __init sec_of_kunit_init(void) +{ + return 0; +} +module_init(sec_of_kunit_init); + +void __exit sec_of_kunit_exit(void) +{ +} +module_exit(sec_of_kunit_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("Samsung additional facilites for of / kunit testing"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/bsp/param/Kconfig b/drivers/samsung/bsp/param/Kconfig new file mode 100644 index 000000000000..ac7b99a36a7b --- /dev/null +++ b/drivers/samsung/bsp/param/Kconfig @@ -0,0 +1,4 @@ +config SEC_PARAM + tristate "Samsung PARAM driver" + help + TODO: help is not ready. diff --git a/drivers/samsung/bsp/param/Makefile b/drivers/samsung/bsp/param/Makefile new file mode 100644 index 000000000000..9087f277aacd --- /dev/null +++ b/drivers/samsung/bsp/param/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SEC_PARAM) += sec_param.o diff --git a/drivers/samsung/bsp/param/sec_param.c b/drivers/samsung/bsp/param/sec_param.c new file mode 100644 index 000000000000..415f6c566640 --- /dev/null +++ b/drivers/samsung/bsp/param/sec_param.c @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2011-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include + +#include +#include + +struct sec_param_drvdata { + struct builder bd; + struct sec_param_operations *ops; +}; + +static struct sec_param_drvdata *sec_param; + +static __always_inline bool __param_is_probed(void) +{ + return !!sec_param; +} + +static bool __param_get(size_t index, void *value) +{ + struct sec_param_operations *ops = sec_param->ops; + + if (!ops || !ops->read) + return false; + + return ops->read(index, value); +} + +bool sec_param_get(size_t index, void *value) +{ + if (!__param_is_probed()) + return false; + + return __param_get(index, value); +} +EXPORT_SYMBOL_GPL(sec_param_get); + +static bool __param_set(size_t index, const void *value) +{ + struct sec_param_operations *ops = sec_param->ops; + + if (!ops || !ops->write) + return false; + + return ops->write(index, value); +} + +bool sec_param_set(size_t index, const void *value) +{ + if (!__param_is_probed()) + return false; + + return __param_set(index, value); +} +EXPORT_SYMBOL_GPL(sec_param_set); + +int sec_param_register_operations(struct sec_param_operations *ops) +{ + if (!__param_is_probed()) + return -EBUSY; + + if (sec_param->ops) { + dev_warn(sec_param->bd.dev, "ops is already set (%p)\n", + sec_param->ops); + return -EPERM; + } + + sec_param->ops = ops; + + return 0; +} +EXPORT_SYMBOL_GPL(sec_param_register_operations); + +void sec_param_unregister_operations(struct sec_param_operations *ops) +{ + if (ops != sec_param->ops) { + dev_warn(sec_param->bd.dev, + "%p is not a registered ops.\n", ops); + return; + } + + sec_param->ops = NULL; +} +EXPORT_SYMBOL_GPL(sec_param_unregister_operations); + +static noinline int __param_probe_epilog(struct builder *bd) +{ + struct sec_param_drvdata *drvdata = + container_of(bd, struct sec_param_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + sec_param = drvdata; + + return 0; +} + +static noinline void __param_remove_prolog(struct builder *bd) +{ + /* FIXME: This is not a graceful exit. */ + sec_param = NULL; +} + +static const struct dev_builder __param_dev_builder[] = { + DEVICE_BUILDER(__param_probe_epilog, __param_remove_prolog), +}; + +static int __param_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct sec_param_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __param_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct sec_param_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static int sec_param_probe(struct platform_device *pdev) +{ + return __param_probe(pdev, __param_dev_builder, + ARRAY_SIZE(__param_dev_builder)); +} + +static int sec_param_remove(struct platform_device *pdev) +{ + return __param_remove(pdev, __param_dev_builder, + ARRAY_SIZE(__param_dev_builder)); +} + +static const struct of_device_id sec_param_match_table[] = { + { .compatible = "samsung,param" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_param_match_table); + +static struct platform_driver sec_param_driver = { + .driver = { + .name = "sec,param", + .of_match_table = of_match_ptr(sec_param_match_table), + }, + .probe = sec_param_probe, + .remove = sec_param_remove, +}; + +static int __init sec_param_init(void) +{ + return platform_driver_register(&sec_param_driver); +} +module_init(sec_param_init); + +static void __exit sec_param_exit(void) +{ + platform_driver_unregister(&sec_param_driver); +} +module_exit(sec_param_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("SEC PARAM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/bsp/qcom/Kconfig b/drivers/samsung/bsp/qcom/Kconfig new file mode 100644 index 000000000000..3d91eb37693b --- /dev/null +++ b/drivers/samsung/bsp/qcom/Kconfig @@ -0,0 +1 @@ +source "drivers/samsung/bsp/qcom/param/Kconfig" diff --git a/drivers/samsung/bsp/qcom/Makefile b/drivers/samsung/bsp/qcom/Makefile new file mode 100644 index 000000000000..644bbc1a58b1 --- /dev/null +++ b/drivers/samsung/bsp/qcom/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SEC_QC_PARAM) += param/ diff --git a/drivers/samsung/bsp/qcom/param/Kconfig b/drivers/samsung/bsp/qcom/param/Kconfig new file mode 100644 index 000000000000..590908ee2e4d --- /dev/null +++ b/drivers/samsung/bsp/qcom/param/Kconfig @@ -0,0 +1,22 @@ +config SEC_QC_PARAM + tristate "Samsung PARAM driver for RAW Partion & Qualcomm based devices" + depends on SEC_PARAM + help + TODO: help is not ready. + +config SEC_QC_PARAM_TEST_FOR_ON_DEVICE + tristate "KUnit test for sec_qc_param_test" + depends on KUNIT + depends on SEC_QC_PARAM + help + TODO: Describe config fully. + If you run this test driver on device, SHOULD set this config as 'm' to build test driver modulraly. + +config SEC_QC_PARAM_TEST_FOR_ONLY_UML + tristate "KUnit test for sec_qc_param_test" + depends on KUNIT + depends on UML + depends on SEC_QC_PARAM + help + TODO: Describe config fully. + This CONFIG is recommended to set to y. diff --git a/drivers/samsung/bsp/qcom/param/Makefile b/drivers/samsung/bsp/qcom/param/Makefile new file mode 100644 index 000000000000..81e4c8866d1a --- /dev/null +++ b/drivers/samsung/bsp/qcom/param/Makefile @@ -0,0 +1,4 @@ +obj-$(CONFIG_SEC_QC_PARAM) += sec_qc_param.o +CFLAGS_sec_qc_param.o = -I$(srctree) + +GCOV_PROFILE_sec_qc_param.o := $(CONFIG_KUNIT) diff --git a/drivers/samsung/bsp/qcom/param/sec_qc_param.c b/drivers/samsung/bsp/qcom/param/sec_qc_param.c new file mode 100644 index 000000000000..9aecf1332957 --- /dev/null +++ b/drivers/samsung/bsp/qcom/param/sec_qc_param.c @@ -0,0 +1,849 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2011-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include "sec_qc_param.h" + +static struct qc_param_drvdata *qc_param; + +static __always_inline bool __qc_param_is_probed(void) +{ + return !!qc_param; +} + +__ss_static bool __qc_param_verify_debuglevel(const struct qc_param_info *info, + const void *value) +{ + const unsigned int debuglevel = *(const unsigned int *)value; + bool ret; + + switch (debuglevel) { + case SEC_DEBUG_LEVEL_LOW: + case SEC_DEBUG_LEVEL_MID: + case SEC_DEBUG_LEVEL_HIGH: + ret = true; + break; + default: + ret = false; + break; + } + + return ret; +} + +__ss_static bool __qc_param_verify_sapa(const struct qc_param_info *info, + const void *value) +{ + const unsigned int sapa = *(const unsigned int *)value; + + if (sapa == SAPA_KPARAM_MAGIC || !sapa) + return true; + + return false; +} + +__ss_static bool __qc_param_verify_afc_disable(const struct qc_param_info *info, + const void *value) +{ + const char mode = *(const char *)value; + + if (mode == '0' || mode == '1') + return true; + + return false; +} + +__ss_static bool __qc_param_verify_pd_disable(const struct qc_param_info *info, + const void *value) +{ + const char mode = *(const char *)value; + + if (mode == '0' || mode == '1') + return true; + + return false; +} + +__ss_static bool __qc_param_verify_cp_reserved_mem(const struct qc_param_info *info, + const void *value) +{ + const unsigned int cp_reserved_mem = *(const unsigned int *)value; + bool ret; + + switch (cp_reserved_mem) { + case CP_MEM_RESERVE_OFF: + case CP_MEM_RESERVE_ON_1: + case CP_MEM_RESERVE_ON_2: + ret = true; + break; + default: + ret = false; + break; + } + + return ret; +} + +__ss_static bool __qc_param_verify_FMM_lock(const struct qc_param_info *info, + const void *value) +{ + const unsigned int fmm_lock_magic = *(const unsigned int *)value; + + if (fmm_lock_magic == FMMLOCK_MAGIC_NUM || !fmm_lock_magic) + return true; + + return false; +} + +__ss_static bool __qc_param_verify_fiemap_update(const struct qc_param_info *info, + const void *value) +{ + const unsigned int edtbo_fiemap_magic = *(const unsigned int *)value; + + if (edtbo_fiemap_magic == EDTBO_FIEMAP_MAGIC || !edtbo_fiemap_magic) + return true; + + return false; +} + +static const struct qc_param_info qc_param_info[] = { + QC_PARAM_INFO(param_index_debuglevel, debuglevel, __qc_param_verify_debuglevel), + QC_PARAM_INFO(param_index_uartsel, uartsel, NULL), + QC_PARAM_INFO(param_index_product_device, product_device, NULL), + QC_PARAM_INFO(param_rory_control, rory_control, NULL), + QC_PARAM_INFO(param_cp_debuglevel, cp_debuglevel, NULL), + QC_PARAM_INFO(param_index_sapa, sapa, __qc_param_verify_sapa), + QC_PARAM_INFO(param_index_normal_poweroff, normal_poweroff, NULL), + QC_PARAM_INFO(param_index_wireless_ic, wireless_ic, NULL), + QC_PARAM_INFO(param_index_wireless_charging_mode, wireless_charging_mode, NULL), + QC_PARAM_INFO(param_index_afc_disable, afc_disable, __qc_param_verify_afc_disable), + QC_PARAM_INFO(param_index_cp_reserved_mem, cp_reserved_mem, __qc_param_verify_cp_reserved_mem), + QC_PARAM_INFO(param_index_api_gpio_test, api_gpio_test, NULL), + QC_PARAM_INFO(param_index_api_gpio_test_result, api_gpio_test_result, NULL), + QC_PARAM_INFO(param_index_reboot_recovery_cause, reboot_recovery_cause, NULL), + QC_PARAM_INFO(param_index_user_partition_flashed, user_partition_flashed, NULL), + QC_PARAM_INFO(param_index_force_upload_flag, force_upload_flag, NULL), + // FIXME: QC_PARAM_INFO(param_index_cp_reserved_mem_backup, cp_reserved_mem_backup, NULL), + QC_PARAM_INFO(param_index_FMM_lock, FMM_lock, __qc_param_verify_FMM_lock), + QC_PARAM_INFO(param_index_dump_sink, dump_sink, NULL), + QC_PARAM_INFO(param_index_fiemap_update, fiemap_update, __qc_param_verify_fiemap_update), + QC_PARAM_INFO(param_index_fiemap_result, fiemap_result, NULL), + QC_PARAM_INFO(param_index_window_color, window_color, NULL), + QC_PARAM_INFO(param_index_VrrStatus, VrrStatus, NULL), + QC_PARAM_INFO(param_index_pd_hv_disable, pd_disable, __qc_param_verify_pd_disable), + QC_PARAM_INFO(param_vib_le_est, vib_le_est, NULL), +}; + +/* NOTE: see fs/pstore/blk.c of linux-5.10.y */ +static ssize_t __qc_param_blk_read(struct qc_param_drvdata *drvdata, + char *buf, size_t bytes, loff_t pos) +{ + struct block_device *bdev = drvdata->bdev; + struct file file; + struct kiocb kiocb; + struct iov_iter iter; + struct kvec iov = {.iov_base = buf, .iov_len = bytes}; + + memset(&file, 0, sizeof(struct file)); + file.f_mapping = bdev->bd_inode->i_mapping; + file.f_flags = O_DIRECT | O_SYNC | O_NOATIME; + file.f_inode = bdev->bd_inode; + file_ra_state_init(&file.f_ra, file.f_mapping); + + init_sync_kiocb(&kiocb, &file); + kiocb.ki_pos = pos; + iov_iter_kvec(&iter, READ, &iov, 1, bytes); + + return generic_file_read_iter(&kiocb, &iter); +} + +static inline bool __qc_param_is_param_data(loff_t pos) +{ + if ((pos >= qc_param->offset) || + (pos < qc_param->offset + sizeof(struct sec_qc_param_data))) + return true; + + return false; +} + +ssize_t sec_qc_param_read_raw(void *buf, size_t len, loff_t pos) +{ + if (!__qc_param_is_probed()) + return -EBUSY; + + if (__qc_param_is_param_data(pos)) + return -ENXIO; + + return __qc_param_blk_read(qc_param, buf, len, pos); +} +EXPORT_SYMBOL_GPL(sec_qc_param_read_raw); + +__ss_static bool __qc_param_is_valid_index(size_t index) +{ + size_t size; + + if (index >= ARRAY_SIZE(qc_param_info)) + return false; + + size = qc_param_info[index].size; + if (!size) + return false; + + return true; +} + +static bool __qc_param_read(struct qc_param_drvdata *drvdata, + size_t index, void *value) +{ + struct device *dev = drvdata->bd.dev; + const struct qc_param_info *info; + loff_t offset; + ssize_t read; + + info = &qc_param_info[index]; + offset = info->offset + drvdata->offset; + + read = __qc_param_blk_read(drvdata, + value, info->size, offset); + if (read < 0) { + dev_warn(dev, "read failed (idx:%zu, err:%zd)\n", index, read); + return false; + } else if (read != info->size) { + dev_warn(dev, "wrong size (idx:%zu)- requested(%zu) != read(%zd)\n", + index, info->size, read); + return false; + } + + return true; +} + +static bool sec_qc_param_read(size_t index, void *value) +{ + if (!__qc_param_is_probed()) + return false; + + if (!__qc_param_is_valid_index(index)) { + dev_warn(qc_param->bd.dev, "invalid index (%zu)\n", index); + return false; + } + + return __qc_param_read(qc_param, index, value); +} + +/* NOTE: this is a copy of 'blkdev_fsync' of 'block/fops.c' */ +static int __qc_param_blkdev_fsync(struct file *filp, loff_t start, loff_t end, + int datasync) +{ + struct block_device *bdev = filp->private_data; + int error; + + error = file_write_and_wait_range(filp, start, end); + if (error) + return error; + + /* + * There is no need to serialise calls to blkdev_issue_flush with + * i_mutex and doing so causes performance issues with concurrent + * O_SYNC writers to a block device. + */ + error = blkdev_issue_flush(bdev); + if (error == -EOPNOTSUPP) + error = 0; + + return error; +} + +/* NOTE: see fs/pstore/blk.c of linux-5.10.y */ +static ssize_t __qc_param_blk_write(struct qc_param_drvdata *drvdata, + const void *buf, size_t bytes, loff_t pos ) +{ + struct block_device *bdev = drvdata->bdev; + struct iov_iter iter; + struct kiocb kiocb; + struct file file; + ssize_t ret; + struct kvec iov = {.iov_base = (void *)buf, .iov_len = bytes}; + + /* Console/Ftrace backend may handle buffer until flush dirty zones */ + if (in_interrupt() || irqs_disabled()) + return -EBUSY; + + memset(&file, 0, sizeof(struct file)); + file.private_data = bdev; + file.f_mapping = bdev->bd_inode->i_mapping; + file.f_flags = O_DIRECT | O_SYNC | O_NOATIME; + file.f_inode = bdev->bd_inode; + file.f_iocb_flags = iocb_flags(&file); + + init_sync_kiocb(&kiocb, &file); + kiocb.ki_pos = pos; + iov_iter_kvec(&iter, WRITE, &iov, 1, bytes); + + inode_lock(bdev->bd_inode); + ret = generic_write_checks(&kiocb, &iter); + if (ret > 0) + ret = generic_perform_write(&kiocb, &iter); + inode_unlock(bdev->bd_inode); + + if (likely(ret > 0)) { + const struct file_operations f_op = { + .fsync = __qc_param_blkdev_fsync, + }; + + file.f_op = &f_op; + kiocb.ki_pos += ret; + ret = generic_write_sync(&kiocb, ret); + } + return ret; +} + +ssize_t sec_qc_param_write_raw(const void *buf, size_t len, loff_t pos) +{ + if (!__qc_param_is_probed()) + return -EBUSY; + + if (__qc_param_is_param_data(pos)) + return -ENXIO; + + return __qc_param_blk_write(qc_param, buf, len, pos); +} +EXPORT_SYMBOL_GPL(sec_qc_param_write_raw); + +static bool __qc_param_write(struct qc_param_drvdata *drvdata, + size_t index, const void *value) +{ + struct device *dev = drvdata->bd.dev; + loff_t offset; + ssize_t written; + const struct qc_param_info *info; + + info = &qc_param_info[index]; + offset = info->offset + drvdata->offset; + + if (info->verify_input && !info->verify_input(info, value)) { + dev_warn(dev, "wrong data %pS\n", + __builtin_return_address(0)); + print_hex_dump_bytes(" + ", DUMP_PREFIX_OFFSET, + value, info->size); + return false; + } + + written = __qc_param_blk_write(drvdata, + value, info->size, offset); + if (written < 0) { + dev_warn(dev, "write failed (idx:%zu, err:%zd)\n", + index, written); + return false; + } else if (written != info->size) { + dev_warn(dev, "wrong size (idx:%zu) - requested(%zu) != written(%zd)\n", + index, info->size, written); + return false; + } + + return true; +} + +static bool sec_qc_param_write(size_t index, const void *value) +{ + if (!__qc_param_is_probed()) + return false; + + if (!__qc_param_is_valid_index(index)) { + dev_warn(qc_param->bd.dev, "invalid index (%zu)\n", index); + return false; + } + + return __qc_param_write(qc_param, index, value); +} + +__ss_static noinline int __qc_param_parse_dt_bdev_path(struct builder *bd, + struct device_node *np) +{ + struct qc_param_drvdata *drvdata = + container_of(bd, struct qc_param_drvdata, bd); + + return of_property_read_string(np, "sec,bdev_path", + &drvdata->bdev_path); +} + +__ss_static noinline int __qc_param_parse_dt_negative_offset(struct builder *bd, + struct device_node *np) +{ + struct qc_param_drvdata *drvdata = + container_of(bd, struct qc_param_drvdata, bd); + u32 negative_offset; + int err; + + err = of_property_read_u32(np, "sec,negative_offset", &negative_offset); + if (err) + return -EINVAL; + + drvdata->negative_offset = (loff_t)negative_offset; + + return 0; +} + +static const struct dt_builder __qc_param_dt_builder[] = { + DT_BUILDER(__qc_param_parse_dt_bdev_path), + DT_BUILDER(__qc_param_parse_dt_negative_offset), +}; + +static noinline int __qc_param_parse_dt(struct builder *bd) +{ + return sec_director_parse_dt(bd, __qc_param_dt_builder, + ARRAY_SIZE(__qc_param_dt_builder)); +} + +static int __qc_param_sec_class_create(struct builder *bd) +{ + struct qc_param_drvdata *drvdata = + container_of(bd, struct qc_param_drvdata, bd); + struct device *param_dev; + + param_dev = sec_device_create(NULL, "sec_param"); + if (IS_ERR(param_dev)) + return PTR_ERR(param_dev); + + dev_set_drvdata(param_dev, drvdata); + + drvdata->param_dev = param_dev; + + return 0; +} + +static void __qc_param_sec_class_remove(struct builder *bd) +{ + struct qc_param_drvdata *drvdata = + container_of(bd, struct qc_param_drvdata, bd); + struct device *param_dev = drvdata->param_dev; + + if (!param_dev) + return; + + sec_device_destroy(param_dev->devt); +} + +static noinline int __qc_param_init_blkdev(struct builder *bd) +{ + struct qc_param_drvdata *drvdata = + container_of(bd, struct qc_param_drvdata, bd); + struct device *dev = bd->dev; + fmode_t mode = FMODE_READ | FMODE_WRITE; + struct block_device *bdev; + sector_t nr_sects; + int err; + + bdev = blkdev_get_by_path(drvdata->bdev_path, mode, NULL); + if (IS_ERR(bdev)) { + dev_t devt; + + devt = name_to_dev_t(drvdata->bdev_path); + if (devt == 0) { + dev_warn(dev, "'name_to_dev_t' failed!\n"); + err = -EPROBE_DEFER; + goto err_blkdev; + } + + bdev = blkdev_get_by_dev(devt, mode, NULL); + if (IS_ERR(bdev)) { + dev_warn(dev, "'blkdev_get_by_dev' failed! (%ld)\n", + PTR_ERR(bdev)); + err = -EPROBE_DEFER; + goto err_blkdev; + } + } + + nr_sects = bdev_nr_sectors(bdev); + if (!nr_sects) { + dev_err(dev, "not enough space for %s\n", + drvdata->bdev_path); + blkdev_put(bdev, mode); + return -ENOSPC; + } + + drvdata->bdev = bdev; + drvdata->offset = (loff_t)(nr_sects << SECTOR_SHIFT) + - drvdata->negative_offset; + + return 0; + +err_blkdev: + dev_err(dev, "can't find a block device - %s\n", + drvdata->bdev_path); + return err; +} + +static noinline void __qc_param_exit_blkdev(struct builder *bd) +{ + struct qc_param_drvdata *drvdata = + container_of(bd, struct qc_param_drvdata, bd); + fmode_t mode = FMODE_READ | FMODE_WRITE; + + blkdev_put(drvdata->bdev, mode); +} + +static int __qc_param_register_operations(struct builder *bd) +{ + struct qc_param_drvdata *drvdata = + container_of(bd, struct qc_param_drvdata, bd); + struct sec_param_operations *ops = &drvdata->ops; + int err; + + ops->read = sec_qc_param_read; + ops->write = sec_qc_param_write; + + err = sec_param_register_operations(ops); + if (err == -EBUSY) + return -EPROBE_DEFER; + + return err; +} + +static void __qc_param_unregister_operations(struct builder *bd) +{ + struct qc_param_drvdata *drvdata = + container_of(bd, struct qc_param_drvdata, bd); + struct sec_param_operations *ops = &drvdata->ops; + + sec_param_unregister_operations(ops); +} + +static ssize_t __used __qc_param_show_simple_uint(struct device *sec_class_dev, + struct device_attribute *attr, char *buf, + size_t index) +{ + struct qc_param_drvdata *drvdata = dev_get_drvdata(sec_class_dev); + unsigned int value; + + if (!__qc_param_read(drvdata, index, &value)) + return -EINVAL; + + return scnprintf(buf, PAGE_SIZE, "%u", value); +} + +static ssize_t __used __qc_param_show_simple_str(struct device *sec_class_dev, + struct device_attribute *attr, char *buf, + size_t index, size_t len) +{ + struct qc_param_drvdata *drvdata = dev_get_drvdata(sec_class_dev); + char *context; + int ret; + + /* NOTE: I use a boucing buffer to prevent 'buf' is not corrupted, + * when '__qc_paam_read' is failed. + */ + context = kmalloc(len, GFP_KERNEL); + if (!context) { + ret = -ENOMEM; + goto __err_nomem; + } + + if (!__qc_param_read(drvdata, index, context)) { + ret = -EINVAL; + goto __err_read_fail; + } + + ret = scnprintf(buf, PAGE_SIZE, "%s", context); + +__err_read_fail: + kfree(context); +__err_nomem: + return ret; +} + +static ssize_t api_gpio_test_show(struct device *sec_class_dev, + struct device_attribute *attr, char *buf) +{ + return __qc_param_show_simple_uint(sec_class_dev, attr, buf, + param_index_api_gpio_test); +} +DEVICE_ATTR_RO(api_gpio_test); + +static ssize_t api_gpio_test_result_show(struct device *sec_class_dev, + struct device_attribute *attr, char *buf) +{ + return __qc_param_show_simple_str(sec_class_dev, attr, buf, + param_index_api_gpio_test_result, + QC_PARAM_SIZE(api_gpio_test_result)); +} +DEVICE_ATTR_RO(api_gpio_test_result); + +static inline void __api_gpio_test_clear(struct qc_param_drvdata *drvdata) +{ + unsigned int zero = 0; + + __qc_param_write(drvdata, param_index_api_gpio_test, &zero); +} + +static inline void __api_gpio_test_result_clear( + struct qc_param_drvdata *drvdata) +{ + char empty[QC_PARAM_SIZE(api_gpio_test_result)] = { '\0', }; + + __qc_param_write(drvdata, param_index_api_gpio_test_result, &empty); +} + +static ssize_t api_gpio_test_clear_store(struct device *sec_class_dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct qc_param_drvdata *drvdata = dev_get_drvdata(sec_class_dev); + struct device *dev = drvdata->bd.dev; + int written; + int err; + + err = kstrtoint(buf, 10, &written); + if (err < 0) { + dev_warn(dev, "requested written code is malformed or wrong\n"); + print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 16, 1, + buf, count, 1); + return err; + } + + if (written != 1) + return count; + + __api_gpio_test_clear(drvdata); + __api_gpio_test_result_clear(drvdata); + + return count; +} +DEVICE_ATTR_WO(api_gpio_test_clear); + +static struct attribute *sec_qc_param_attrs[] = { + &dev_attr_api_gpio_test.attr, + &dev_attr_api_gpio_test_result.attr, + &dev_attr_api_gpio_test_clear.attr, + NULL, +}; + +static const struct attribute_group sec_qc_param_attr_group = { + .attrs = sec_qc_param_attrs, +}; + +static noinline int __qc_param_sysfs_create(struct builder *bd) +{ + struct qc_param_drvdata *drvdata = + container_of(bd, struct qc_param_drvdata, bd); + struct device *dev = drvdata->param_dev; + int err; + + err = sysfs_create_group(&dev->kobj, &sec_qc_param_attr_group); + if (err) + return err; + + return 0; +} + +static noinline void __qc_param_sysfs_remove(struct builder *bd) +{ + struct qc_param_drvdata *drvdata = + container_of(bd, struct qc_param_drvdata, bd); + struct device *dev = drvdata->param_dev; + + sysfs_remove_group(&dev->kobj, &sec_qc_param_attr_group); +} + +static noinline int __qc_param_probe_epilog(struct builder *bd) +{ + struct qc_param_drvdata *drvdata = + container_of(bd, struct qc_param_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + qc_param = drvdata; + + return 0; +} + +static noinline void __qc_param_remove_prolog(struct builder *bd) +{ + /* FIXME: This is not a graceful exit. */ + qc_param = NULL; +} + +static int __qc_param_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct qc_param_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __qc_param_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct qc_param_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +#if IS_ENABLED(CONFIG_DEBUG_FS) +static void __qc_param_dbgfs_show_bdev(struct seq_file *m) +{ + struct qc_param_drvdata *drvdata = m->private; + struct block_device *bdev = drvdata->bdev; + + seq_puts(m, "* Block Device :\n"); + seq_printf(m, " - bdevname : %pg\n", bdev); + seq_printf(m, " - uuid : %s\n", bdev->bd_meta_info->uuid); + seq_printf(m, " - volname : %s\n", bdev->bd_meta_info->volname); + seq_puts(m, "\n"); +} + +static void __qc_param_dbgfs_show_each(struct seq_file *m, size_t index) +{ + const struct qc_param_info *info = &qc_param_info[index]; + uint8_t *buf; + + if (!info->size) + return; + + seq_printf(m, "[%zu] = %s\n", index, info->name); + seq_printf(m, " - offset : %zu\n", (size_t)info->offset); + seq_printf(m, " - size : %zu\n", info->size); + + buf = kmalloc(info->size, GFP_KERNEL); + if (!sec_qc_param_read(index, buf)) { + seq_puts(m, " - failed to read param!\n"); + goto warn_read_fail; + } + + seq_hex_dump(m, " + ", DUMP_PREFIX_OFFSET, 16, 1, + buf, info->size, true); + +warn_read_fail: + seq_puts(m, "\n"); + kfree(buf); +} + +static int sec_qc_param_dbgfs_show_all(struct seq_file *m, void *unsed) +{ + size_t i; + + __qc_param_dbgfs_show_bdev(m); + + for (i = 0; i < ARRAY_SIZE(qc_param_info); i++) + __qc_param_dbgfs_show_each(m, i); + + return 0; +} + +static int sec_qc_param_dbgfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, sec_qc_param_dbgfs_show_all, + inode->i_private); +} + +static const struct file_operations sec_qc_param_dgbfs_fops = { + .open = sec_qc_param_dbgfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static noinline int __qc_param_debugfs_create(struct builder *bd) +{ + struct qc_param_drvdata *drvdata = + container_of(bd, struct qc_param_drvdata, bd); + + drvdata->dbgfs = debugfs_create_file("sec_qc_param", 0440, + NULL, drvdata, &sec_qc_param_dgbfs_fops); + + return 0; +} + +static noinline void __qc_param_debugfs_remove(struct builder *bd) +{ + struct qc_param_drvdata *drvdata = + container_of(bd, struct qc_param_drvdata, bd); + + debugfs_remove(drvdata->dbgfs); +} +#else +static noinline int __qc_param_debugfs_create(struct builder *bd) { return 0; } +static noinline void __qc_param_debugfs_remove(struct builder *bd) {} +#endif + +static const struct dev_builder __qc_param_dev_builder[] = { + DEVICE_BUILDER(__qc_param_parse_dt, NULL), + DEVICE_BUILDER(__qc_param_sec_class_create, + __qc_param_sec_class_remove), + DEVICE_BUILDER(__qc_param_init_blkdev, __qc_param_exit_blkdev), + DEVICE_BUILDER(__qc_param_register_operations, + __qc_param_unregister_operations), + DEVICE_BUILDER(__qc_param_sysfs_create, __qc_param_sysfs_remove), + DEVICE_BUILDER(__qc_param_debugfs_create, __qc_param_debugfs_remove), + DEVICE_BUILDER(__qc_param_probe_epilog, __qc_param_remove_prolog), +}; + +static int sec_qc_param_probe(struct platform_device *pdev) +{ + return __qc_param_probe(pdev, __qc_param_dev_builder, + ARRAY_SIZE(__qc_param_dev_builder)); +} + +static int sec_qc_param_remove(struct platform_device *pdev) +{ + return __qc_param_remove(pdev, __qc_param_dev_builder, + ARRAY_SIZE(__qc_param_dev_builder)); +} + +static const struct of_device_id sec_qc_param_match_table[] = { + { .compatible = "samsung,qcom-param" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_qc_param_match_table); + +static struct platform_driver sec_qc_param_driver = { + .driver = { + .name = "sec,qc-param", + .of_match_table = of_match_ptr(sec_qc_param_match_table), + }, + .probe = sec_qc_param_probe, + .remove = sec_qc_param_remove, +}; + +static int __init sec_qc_param_init(void) +{ + return platform_driver_register(&sec_qc_param_driver); +} +module_init(sec_qc_param_init); + +static void __exit sec_qc_param_exit(void) +{ + platform_driver_unregister(&sec_qc_param_driver); +} +module_exit(sec_qc_param_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("SEC PARAM driver for RAW Partion & Qualcomm based devices"); +MODULE_LICENSE("GPL v2"); + +MODULE_SOFTDEP("pre: ufs_qcom"); diff --git a/drivers/samsung/bsp/qcom/param/sec_qc_param.h b/drivers/samsung/bsp/qcom/param/sec_qc_param.h new file mode 100644 index 000000000000..87ef202deaf9 --- /dev/null +++ b/drivers/samsung/bsp/qcom/param/sec_qc_param.h @@ -0,0 +1,91 @@ +#ifndef __INTERNAL__SEC_QC_PARAM_H__ +#define __INTERNAL__SEC_QC_PARAM_H__ + +#include + +#include +#include +#include /* deprecated */ + +struct qc_param_drvdata { + struct builder bd; + struct device *param_dev; + struct sec_param_operations ops; + const char *bdev_path; + loff_t negative_offset; + struct block_device *bdev; + loff_t offset; +#if IS_ENABLED(CONFIG_DEBUG_FS) + struct dentry *dbgfs; +#endif +}; + +#define QC_PARAM_OFFSET(__member) \ + (offsetof(struct sec_qc_param_data, __member)) + +#define QC_PARAM_SIZE(__member) \ + (sizeof(((struct sec_qc_param_data *)NULL)->__member)) + +#define __QC_PARAM_INFO(__member, __verify_input) { \ + .name = #__member, \ + .offset = QC_PARAM_OFFSET(__member), \ + .size = QC_PARAM_SIZE(__member), \ + .verify_input = __verify_input, \ +} + +#define QC_PARAM_INFO(__index, __member, __verify_input) \ + [__index] = __QC_PARAM_INFO(__member, __verify_input) + +struct qc_param_info; + +typedef bool (*qc_param_verify_input_t)(const struct qc_param_info *info, + const void *value); + +struct qc_param_info { + const char *name; + loff_t offset; + size_t size; + qc_param_verify_input_t verify_input; +}; + +struct sec_qc_param_data { + unsigned int debuglevel; + unsigned int uartsel; + unsigned int rory_control; + unsigned int product_device; /* product/dev device */ + unsigned int reserved1; + unsigned int cp_debuglevel; + unsigned int reserved2; + unsigned int sapa[3]; + unsigned int normal_poweroff; + unsigned int wireless_ic; + char used0[80]; + char used1[80]; + char used2[80]; + char used3[80]; + char used4[80]; + unsigned int wireless_charging_mode; + unsigned int afc_disable; + unsigned int cp_reserved_mem; + char used5[4]; + char used6[4]; + char reserved8[8]; + char used7[16]; + unsigned int api_gpio_test; + char api_gpio_test_result[256]; + char reboot_recovery_cause[256]; + unsigned int user_partition_flashed; + unsigned int force_upload_flag; + unsigned int cp_reserved_mem_backup; + unsigned int FMM_lock; + unsigned int dump_sink; + unsigned int fiemap_update; + struct fiemap_p fiemap_result; + char used8[80]; + char window_color[2]; + char VrrStatus[16]; + unsigned int pd_disable; + unsigned int vib_le_est; +}; + +#endif /* __INTERNAL__SEC_QC_PARAM_H__ */ diff --git a/drivers/samsung/bsp/reloc_gpio/Kconfig b/drivers/samsung/bsp/reloc_gpio/Kconfig new file mode 100644 index 000000000000..9a506078178c --- /dev/null +++ b/drivers/samsung/bsp/reloc_gpio/Kconfig @@ -0,0 +1,32 @@ +config SEC_RELOC_GPIO + tristate "Samsung Legacy-Style Relocated GPIO Interface" + default m if SEC_CLASS=m + default y if SEC_CLASS=y + help + TODO: help is not ready. + +config SEC_RELOC_GPIO_EN + bool "Samsung Legacy-Style Relocated GPIO Interface for Factory Mode" + depends on SEC_RELOC_GPIO + default y if SEC_FACTORY + default n + help + TODO: help is not ready. + +config SEC_RELOC_GPIO_TEST_FOR_ON_DEVICE + tristate "KUnit test for sec_reloc_gpio_test" + depends on KUNIT + depends on SEC_RELOC_GPIO + help + TODO: Describe config fully. + If you run this test driver on device, SHOULD set this config as 'm' to build test driver modulraly. + +config SEC_RELOC_GPIO_TEST_FOR_ONLY_UML + tristate "KUnit test for sec_reloc_gpio_test" + depends on KUNIT + depends on UML + depends on SEC_RELOC_GPIO + help + TODO: Describe config fully. + This CONFIG is recommended to set to y. + diff --git a/drivers/samsung/bsp/reloc_gpio/Makefile b/drivers/samsung/bsp/reloc_gpio/Makefile new file mode 100644 index 000000000000..623ef5beed74 --- /dev/null +++ b/drivers/samsung/bsp/reloc_gpio/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_SEC_RELOC_GPIO) += sec_reloc_gpio.o + +GCOV_PROFILE_sec_reloc_gpio.o := $(CONFIG_KUNIT) diff --git a/drivers/samsung/bsp/reloc_gpio/sec_reloc_gpio.c b/drivers/samsung/bsp/reloc_gpio/sec_reloc_gpio.c new file mode 100644 index 000000000000..d5fdd2c706b6 --- /dev/null +++ b/drivers/samsung/bsp/reloc_gpio/sec_reloc_gpio.c @@ -0,0 +1,401 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2021-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "sec_reloc_gpio.h" + +__ss_static noinline int __reloc_gpio_parse_dt_reloc_base(struct builder *bd, + struct device_node *np) +{ + struct reloc_gpio_drvdata *drvdata = + container_of(bd, struct reloc_gpio_drvdata, bd); + struct device *dev = bd->dev; + int nr_chip; + struct reloc_gpio_chip *chip; + u32 base; + int i; + + nr_chip = of_property_count_elems_of_size(np, "sec,reloc-base", + sizeof(u32)); + if (nr_chip < 0) { + /* assume '0' if -EINVAL or -ENODATA is returned */ + drvdata->nr_chip = 0; + return 0; + } + + chip = devm_kmalloc_array(dev, nr_chip, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + for (i = 0; i < nr_chip; i++) { + int err = of_property_read_u32_index(np, "sec,reloc-base", + i, &base); + if (err) { + dev_err(dev, "can't read sec,reloc-base [%d]\n", i); + return err; + } + + chip[i].base = (int)base; + } + + drvdata->chip = chip; + drvdata->nr_chip = nr_chip; + + return 0; +} + +__ss_static noinline int __reloc_gpio_parse_dt_gpio_label(struct builder *bd, + struct device_node *np) +{ + struct reloc_gpio_drvdata *drvdata = + container_of(bd, struct reloc_gpio_drvdata, bd); + struct device *dev = bd->dev; + const char *label; + struct reloc_gpio_chip *chip; + int nr_chip; + int i; + + chip = drvdata->chip; + nr_chip = drvdata->nr_chip; + for (i = 0; i < nr_chip; i++) { + int err = of_property_read_string_helper(np, + "sec,gpio-label", &label, 1, i); + if (err < 0) { + dev_err(dev, "can't read sec,gpio-label [%d]\n", i); + return err; + } + + chip[i].label = label; + chip[i].label_len = strlen(label); + } + + return 0; +} + +static const struct dt_builder __reloc_gpio_dt_builder[] = { + DT_BUILDER(__reloc_gpio_parse_dt_reloc_base), + DT_BUILDER(__reloc_gpio_parse_dt_gpio_label), +}; + +static noinline int __reloc_gpio_probe_prolog(struct builder *bd) +{ + struct reloc_gpio_drvdata *drvdata = + container_of(bd, struct reloc_gpio_drvdata, bd); + + drvdata->gpio_num = -EINVAL; + + return 0; +} + +static noinline int __reloc_gpio_parse_dt(struct builder *bd) +{ + return sec_director_parse_dt(bd, __reloc_gpio_dt_builder, + ARRAY_SIZE(__reloc_gpio_dt_builder)); +} + +static int __reloc_gpio_sec_class_create(struct builder *bd) +{ + struct reloc_gpio_drvdata *drvdata = + container_of(bd, struct reloc_gpio_drvdata, bd); + struct device *reloc_gpio_dev; + + reloc_gpio_dev = sec_device_create(NULL, "gpio"); + if (IS_ERR(reloc_gpio_dev)) + return PTR_ERR(reloc_gpio_dev); + + dev_set_drvdata(reloc_gpio_dev, drvdata); + + drvdata->reloc_gpio_dev = reloc_gpio_dev; + + return 0; +} + +static void __reloc_gpio_sec_class_remove(struct builder *bd) +{ + struct reloc_gpio_drvdata *drvdata = + container_of(bd, struct reloc_gpio_drvdata, bd); + struct device *reloc_gpio_dev = drvdata->reloc_gpio_dev; + + if (!reloc_gpio_dev) + return; + + sec_device_destroy(reloc_gpio_dev->devt); +} + +__ss_static bool __reloc_gpio_is_valid_gpio_num(struct reloc_gpio_chip *chip, + int nr_gpio, int gpio_num) +{ + int min = chip->base; + int max = chip->base + nr_gpio - 1; + + if ((gpio_num >= min) && (gpio_num <= max)) + return true; + + return false; +} + +__ss_static bool __reloc_gpio_is_matched(struct gpio_chip *gc, + struct reloc_gpio_chip *chip, int gpio_num) +{ + size_t len = strnlen(gc->label, chip->label_len + 1); + + if (len != chip->label_len) + return false; + + if (strncmp(gc->label, chip->label, chip->label_len)) + return false; + + return __reloc_gpio_is_valid_gpio_num(chip, gc->ngpio, gpio_num); +} + +__ss_static int sec_reloc_gpio_is_matched_gpio_chip(struct gpio_chip *gc, + void *__drvdata) +{ + struct reloc_gpio_drvdata *drvdata = __drvdata; + struct reloc_gpio_chip *chip = drvdata->chip; + size_t nr_chip = drvdata->nr_chip; + int gpio_num = drvdata->gpio_num; + size_t i; + + for (i = 0; i < nr_chip; i++) { + struct reloc_gpio_chip *this_chip = &chip[i]; + + if (__reloc_gpio_is_matched(gc, this_chip, gpio_num)) { + drvdata->chip_idx_found = i; + return 1; + } + } + + return 0; +} + +static inline bool __reloc_gpio_test_range(struct reloc_gpio_drvdata *drvdata, + struct reloc_gpio_chip *chip, struct gpio_chip *gc) +{ + if (drvdata->gpio_num < chip->base) + return false; + + if (drvdata->gpio_num >= (chip->base + gc->ngpio)) + return false; + + return true; +} + +__ss_static int __reloc_gpio_from_legacy_number( + struct reloc_gpio_drvdata *drvdata, struct gpio_chip *gc) +{ + struct reloc_gpio_chip *chip; + + if (drvdata->nr_chip < drvdata->chip_idx_found) + return -EINVAL; + + /* drvdata->chip_idx_found is determined when 'gpiochip_find' is run. */ + chip = &drvdata->chip[drvdata->chip_idx_found]; + if (!__reloc_gpio_test_range(drvdata, chip, gc)) + return -ERANGE; + + return (drvdata->gpio_num - chip->base) + gc->base; +} + +static int __reloc_gpio_relocated_to_actual(struct reloc_gpio_drvdata *drvdata) +{ + struct gpio_chip *gc; + + if (!drvdata->nr_chip) + return drvdata->gpio_num; + + gc = gpiochip_find(drvdata, sec_reloc_gpio_is_matched_gpio_chip); + if (IS_ERR_OR_NULL(gc)) + return -ENOENT; + + return __reloc_gpio_from_legacy_number(drvdata, gc); +} + +static ssize_t check_requested_gpio_show(struct device *sec_class_dev, + struct device_attribute *attr, char *buf) +{ + struct reloc_gpio_drvdata *drvdata = dev_get_drvdata(sec_class_dev); + int gpio_actual = -EINVAL; + int val; + + if (drvdata->gpio_num < 0) { + val = -ENODEV; + goto __finally; + } + + gpio_actual = __reloc_gpio_relocated_to_actual(drvdata); + if (gpio_actual < 0) { + val = -ENOENT; + goto __finally; + } + + val = gpio_get_value(gpio_actual); + +__finally: + drvdata->gpio_num = -EINVAL; + return scnprintf(buf, PAGE_SIZE, "GPIO[%d] : [%d]", gpio_actual, val); +} + +static ssize_t check_requested_gpio_store(struct device *sec_class_dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct reloc_gpio_drvdata *drvdata = dev_get_drvdata(sec_class_dev); + struct device *dev = drvdata->bd.dev; + int gpio_num; + int err; + + err = kstrtoint(buf, 10, &gpio_num); + if (err < 0) { + dev_warn(dev, "requested gpio number is malformed or wrong\n"); + print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 16, 1, + buf, count, 1); + return err; + } + + drvdata->gpio_num = gpio_num; + + return count; +} + +static DEVICE_ATTR_RW(check_requested_gpio); + +static struct attribute *sec_reloc_gpio_attrs[] = { + &dev_attr_check_requested_gpio.attr, + NULL, +}; + +static const struct attribute_group sec_reloc_gpio_attr_group = { + .attrs = sec_reloc_gpio_attrs, +}; + +static int __reloc_gpio_sysfs_create(struct builder *bd) +{ + struct reloc_gpio_drvdata *drvdata = + container_of(bd, struct reloc_gpio_drvdata, bd); + struct device *dev = drvdata->reloc_gpio_dev; + int err; + + err = sysfs_create_group(&dev->kobj, &sec_reloc_gpio_attr_group); + if (err) + return err; + + return 0; +} + +static void __reloc_gpio_sysfs_remove(struct builder *bd) +{ + struct reloc_gpio_drvdata *drvdata = + container_of(bd, struct reloc_gpio_drvdata, bd); + struct device *dev = drvdata->reloc_gpio_dev; + + sysfs_remove_group(&dev->kobj, &sec_reloc_gpio_attr_group); +} + +static noinline int __reloc_gpio_epilog(struct builder *bd) +{ + struct reloc_gpio_drvdata *drvdata = + container_of(bd, struct reloc_gpio_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + + return 0; +} + +static int __reloc_gpio_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct reloc_gpio_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __reloc_gpio_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct reloc_gpio_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static const struct dev_builder __reloc_gpio_dev_builder[] = { + DEVICE_BUILDER(__reloc_gpio_parse_dt, NULL), + DEVICE_BUILDER(__reloc_gpio_probe_prolog, NULL), + DEVICE_BUILDER(__reloc_gpio_sec_class_create, + __reloc_gpio_sec_class_remove), + DEVICE_BUILDER(__reloc_gpio_sysfs_create, + __reloc_gpio_sysfs_remove), + DEVICE_BUILDER(__reloc_gpio_epilog, NULL), +}; + +static int sec_reloc_gpio_probe(struct platform_device *pdev) +{ + return __reloc_gpio_probe(pdev, __reloc_gpio_dev_builder, + ARRAY_SIZE(__reloc_gpio_dev_builder)); +} + +static int sec_reloc_gpio_remove(struct platform_device *pdev) +{ + return __reloc_gpio_remove(pdev, __reloc_gpio_dev_builder, + ARRAY_SIZE(__reloc_gpio_dev_builder)); +} + +static const struct of_device_id sec_reloc_gpio_match_table[] = { + { .compatible = "samsung,reloc_gpio" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_reloc_gpio_match_table); + +static struct platform_driver sec_reloc_gpio_driver = { + .driver = { + .name = "sec,reloc_gpio", + .of_match_table = of_match_ptr(sec_reloc_gpio_match_table), + }, + .probe = sec_reloc_gpio_probe, + .remove = sec_reloc_gpio_remove, +}; + +static int __init sec_reloc_gpio_init(void) +{ + if (!IS_ENABLED(CONFIG_SEC_RELOC_GPIO_EN)) + return 0; + + return platform_driver_register(&sec_reloc_gpio_driver); +} +module_init(sec_reloc_gpio_init); + +static void __exit sec_reloc_gpio_exit(void) +{ + if (!IS_ENABLED(CONFIG_SEC_RELOC_GPIO_EN)) + return; + + platform_driver_unregister(&sec_reloc_gpio_driver); +} +module_exit(sec_reloc_gpio_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("Legacy-Style Relocated GPIO Interface for Factory Mode"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/bsp/reloc_gpio/sec_reloc_gpio.h b/drivers/samsung/bsp/reloc_gpio/sec_reloc_gpio.h new file mode 100644 index 000000000000..cfd3c243263f --- /dev/null +++ b/drivers/samsung/bsp/reloc_gpio/sec_reloc_gpio.h @@ -0,0 +1,24 @@ +#ifndef __INTERNAL__SEC_RELOC_GPIO_H__ +#define __INTERNAL__SEC_RELOC_GPIO_H__ + +#include +#include + +struct reloc_gpio_chip { + const char *label; + size_t label_len; + int base; +}; + +struct reloc_gpio_drvdata { + struct builder bd; + struct device *reloc_gpio_dev; + struct reloc_gpio_chip *chip; + size_t nr_chip; + /* relocated gpio number which is request from sysfs interface */ + int gpio_num; + /* found index after calling gpiochip_find */ + int chip_idx_found; +}; + +#endif /* */ diff --git a/drivers/samsung/debug/Kconfig b/drivers/samsung/debug/Kconfig new file mode 100644 index 000000000000..1ea2f001d870 --- /dev/null +++ b/drivers/samsung/debug/Kconfig @@ -0,0 +1,16 @@ +source "drivers/samsung/debug/common/Kconfig" +source "drivers/samsung/debug/boot_stat/Kconfig" +source "drivers/samsung/debug/log_buf/Kconfig" +source "drivers/samsung/debug/pmsg/Kconfig" +source "drivers/samsung/debug/reboot_cmd/Kconfig" +source "drivers/samsung/debug/upload_cause/Kconfig" +source "drivers/samsung/debug/crashkey/Kconfig" +source "drivers/samsung/debug/crashkey_long/Kconfig" +source "drivers/samsung/debug/debug_region/Kconfig" +source "drivers/samsung/debug/rdx_bootdev/Kconfig" + +# TODO: architecture specific drivers at here +source "drivers/samsung/debug/arm64/Kconfig" + +# TODO: soc specific drivers at here +source "drivers/samsung/debug/qcom/Kconfig" diff --git a/drivers/samsung/debug/Makefile b/drivers/samsung/debug/Makefile new file mode 100644 index 000000000000..9603ab21ee2c --- /dev/null +++ b/drivers/samsung/debug/Makefile @@ -0,0 +1,14 @@ +obj-$(CONFIG_SEC_DEBUG) += common/ +obj-$(CONFIG_SEC_BOOT_STAT) += boot_stat/ +obj-$(CONFIG_SEC_LOG_BUF) += log_buf/ +obj-$(CONFIG_SEC_PMSG) += pmsg/ +obj-$(CONFIG_SEC_REBOOT_CMD) += reboot_cmd/ +obj-$(CONFIG_SEC_UPLOAD_CAUSE) += upload_cause/ +obj-$(CONFIG_SEC_CRASHKEY) += crashkey/ +obj-$(CONFIG_SEC_CRASHKEY_LONG) += crashkey_long/ +obj-$(CONFIG_SEC_DEBUG_REGION) += debug_region/ +obj-$(CONFIG_SEC_RDX_BOOTDEV) += rdx_bootdev/ + +obj-$(CONFIG_ARM64) += arm64/ + +obj-y += qcom/ diff --git a/drivers/samsung/debug/arm64/Kconfig b/drivers/samsung/debug/arm64/Kconfig new file mode 100644 index 000000000000..092b3a9ec22d --- /dev/null +++ b/drivers/samsung/debug/arm64/Kconfig @@ -0,0 +1,3 @@ +source "drivers/samsung/debug/arm64/ap_context/Kconfig" +source "drivers/samsung/debug/arm64/fsimd_debug/Kconfig" +source "drivers/samsung/debug/arm64/debug/Kconfig" diff --git a/drivers/samsung/debug/arm64/Makefile b/drivers/samsung/debug/arm64/Makefile new file mode 100644 index 000000000000..058b8a278783 --- /dev/null +++ b/drivers/samsung/debug/arm64/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_SEC_ARM64_AP_CONTEXT) += ap_context/ +obj-$(CONFIG_SEC_ARM64_FSIMD_DEBUG) += fsimd_debug/ +obj-$(CONFIG_SEC_ARM64_DEBUG) += debug/ diff --git a/drivers/samsung/debug/arm64/ap_context/Kconfig b/drivers/samsung/debug/arm64/ap_context/Kconfig new file mode 100644 index 000000000000..9c38a792fc38 --- /dev/null +++ b/drivers/samsung/debug/arm64/ap_context/Kconfig @@ -0,0 +1,5 @@ +config SEC_ARM64_AP_CONTEXT + tristate "SEC AP CORE/MMU context snaphot" + depends on SEC_DEBUG_REGION && ARM64 && ANDROID_VENDOR_HOOKS + help + TODO: help is not ready. diff --git a/drivers/samsung/debug/arm64/ap_context/Makefile b/drivers/samsung/debug/arm64/ap_context/Makefile new file mode 100644 index 000000000000..14f6085e500b --- /dev/null +++ b/drivers/samsung/debug/arm64/ap_context/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SEC_ARM64_AP_CONTEXT) += sec_arm64_ap_context.o diff --git a/drivers/samsung/debug/arm64/ap_context/sec_arm64_ap_context.c b/drivers/samsung/debug/arm64/ap_context/sec_arm64_ap_context.c new file mode 100644 index 000000000000..25e1bcc15e7d --- /dev/null +++ b/drivers/samsung/debug/arm64/ap_context/sec_arm64_ap_context.c @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2020-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#define __ap_context_read_special_reg(x) ({ \ + uint64_t val; \ + asm volatile ("mrs %0, " # x : "=r"(val)); \ + val; \ +}) + +struct ap_context_drvdata { + struct builder bd; + const char *name; + uint32_t unique_id; + struct sec_dbg_region_client *client; + struct sec_arm64_ap_context *ctx; + struct notifier_block nb_die; + struct notifier_block nb_panic; +}; + +enum { + TYPE_VH_IPI_STOP = 0, + /* */ + TYPE_VH_MAX, + TYPE_VH_UNKNOWN = -EINVAL, +}; + +struct sec_arm64_ap_context *ap_context[TYPE_VH_MAX]; + +static void __always_inline __ap_context_save_core_regs_from_pt_regs( + struct sec_arm64_ap_context *ctx, struct pt_regs *regs) +{ + memcpy_toio(&ctx->core_regs, regs, sizeof(struct pt_regs)); +} + +/* FIXME: tempoary workaround to prevent linking errors */ +void __naked __ap_context_save_core_regs_on_current(struct pt_regs *regs) +{ + asm volatile ( + "stp x1, x2, [sp, #-0x20]! \n\t" + "stp x3, x4, [sp, #0x10] \n\t" + + /* x0 ~ x28 */ + "stp x0, x1, [x0] \n\t" + "stp x2, x3, [x0, #0x10] \n\t" + "stp x4, x5, [x0, #0x20] \n\t" + "stp x6, x7, [x0, #0x30] \n\t" + "stp x8, x9, [x0, #0x40] \n\t" + "stp x10, x11, [x0, #0x50] \n\t" + "stp x12, x13, [x0, #0x60] \n\t" + "stp x14, x15, [x0, #0x70] \n\t" + "stp x16, x17, [x0, #0x80] \n\t" + "stp x18, x19, [x0, #0x90] \n\t" + "stp x20, x21, [x0, #0xA0] \n\t" + "stp x22, x23, [x0, #0xB0] \n\t" + "stp x24, x25, [x0, #0xC0] \n\t" + "stp x26, x27, [x0, #0xD0] \n\t" + "str x28, [x0, #0xE0] \n\t" + + /* pstate */ + "mrs x1, nzcv \n\t" + "bic x1, x1, #0xFFFFFFFF0FFFFFFF \n\t" + "mrs x2, daif \n\t" + "bic x2, x2, #0xFFFFFFFFFFFFFC3F \n\t" + "orr x1, x1, x2 \n\t" + "mrs x3, currentel \n\t" + "bic x3, x3, #0xFFFFFFFFFFFFFFF3 \n\t" + "orr x1, x1, x3 \n\t" + "mrs x4, spsel \n\t" + "bic x4, x4, #0xFFFFFFFFFFFFFFFE \n\t" + "orr x1, x1, x4 \n\t" + "str x1, [x0, #0x108] \n\t" + "ldp x3, x4, [sp, #0x10] \n\t" + "ldp x1, x2, [sp], #0x20 \n\t" + "ret \n\t" + ); +} + +static void __always_inline __ap_context_save_core_extra_regs( + struct sec_arm64_ap_context *ctx) +{ + uint64_t pstate, which_el; + uint64_t *regs = &ctx->core_extra_regs[0]; + + pstate = __ap_context_read_special_reg(CurrentEl); + which_el = pstate & PSR_MODE_MASK; + + regs[IDX_CORE_EXTRA_SP_EL0] = __ap_context_read_special_reg(sp_el0); + + if (which_el >= PSR_MODE_EL2t) { + regs[IDX_CORE_EXTRA_SP_EL1] = + __ap_context_read_special_reg(sp_el1); + regs[IDX_CORE_EXTRA_ELR_EL1] = + __ap_context_read_special_reg(elr_el1); + regs[IDX_CORE_EXTRA_SPSR_EL1] = + __ap_context_read_special_reg(spsr_el1); + regs[IDX_CORE_EXTRA_SP_EL2] = + __ap_context_read_special_reg(sp_el2); + regs[IDX_CORE_EXTRA_ELR_EL2] = + __ap_context_read_special_reg(elr_el2); + regs[IDX_CORE_EXTRA_SPSR_EL2] = + __ap_context_read_special_reg(spsr_el2); + } +} + +static void __always_inline __ap_context_save_mmu_regs( + struct sec_arm64_ap_context *ctx) +{ + uint64_t *mmu = &ctx->mmu_regs[0]; + + mmu[IDX_MMU_TTBR0_EL1] = __ap_context_read_special_reg(TTBR0_EL1); + mmu[IDX_MMU_TTBR1_EL1] = __ap_context_read_special_reg(TTBR1_EL1); + mmu[IDX_MMU_TCR_EL1] = __ap_context_read_special_reg(TCR_EL1); + mmu[IDX_MMU_MAIR_EL1] = __ap_context_read_special_reg(MAIR_EL1); + mmu[IDX_MMU_AMAIR_EL1] = __ap_context_read_special_reg(AMAIR_EL1); +} + +static ssize_t __ap_context_unique_id_to_type(uint32_t unique_id) +{ + ssize_t type; + + switch (unique_id) { + case SEC_ARM64_VH_IPI_STOP_MAGIC: + type = TYPE_VH_IPI_STOP; + break; + default: + type = TYPE_VH_UNKNOWN; + break; + } + + return type; +} + +static noinline int __ap_context_parse_dt_name(struct builder *bd, + struct device_node *np) +{ + struct ap_context_drvdata *drvdata = + container_of(bd, struct ap_context_drvdata, bd); + + return of_property_read_string(np, "sec,name", &drvdata->name); +} + +static noinline int __ap_context_parse_dt_unique_id(struct builder *bd, + struct device_node *np) +{ + struct ap_context_drvdata *drvdata = + container_of(bd, struct ap_context_drvdata, bd); + u32 unique_id; + int err; + + err = of_property_read_u32(np, "sec,unique_id", &unique_id); + if (err) + return -EINVAL; + + drvdata->unique_id = (uint32_t)unique_id; + + return 0; +} + +static const struct dt_builder __ap_context_dt_builder[] = { + DT_BUILDER(__ap_context_parse_dt_name), + DT_BUILDER(__ap_context_parse_dt_unique_id), +}; + +static noinline int __ap_context_parse_dt(struct builder *bd) +{ + return sec_director_parse_dt(bd, __ap_context_dt_builder, + ARRAY_SIZE(__ap_context_dt_builder)); +} + +static noinline int __ap_context_alloc_client(struct builder *bd) +{ + struct ap_context_drvdata *drvdata = + container_of(bd, struct ap_context_drvdata, bd); + size_t size = sizeof(struct sec_arm64_ap_context) * num_possible_cpus(); + struct sec_dbg_region_client *client; + ssize_t type; + + type = __ap_context_unique_id_to_type(drvdata->unique_id); + if (type >= TYPE_VH_MAX || type == TYPE_VH_UNKNOWN) + return -ERANGE; + + if (ap_context[type]) + return -EBUSY; + + client = sec_dbg_region_alloc(drvdata->unique_id, size); + if (PTR_ERR(client) == -EBUSY) + return -EPROBE_DEFER; + else if (IS_ERR_OR_NULL(client)) + return -ENOMEM; + + client->name = drvdata->name; + drvdata->client = client; + drvdata->ctx = (struct sec_arm64_ap_context *)client->virt; + + ap_context[type] = drvdata->ctx; + + return 0; +} + +static noinline void __ap_context_free_client(struct builder *bd) +{ + struct ap_context_drvdata *drvdata = + container_of(bd, struct ap_context_drvdata, bd); + ssize_t type; + + type = __ap_context_unique_id_to_type(drvdata->unique_id); + BUG_ON(type < 0 || type >= TYPE_VH_MAX); + + ap_context[type] = NULL; + + sec_dbg_region_free(drvdata->client); +} + +static void __trace_android_vh_ipi_stop(void *unused, struct pt_regs *regs) +{ + struct sec_arm64_ap_context *__ctx = ap_context[TYPE_VH_IPI_STOP]; + int cpu = smp_processor_id(); + struct sec_arm64_ap_context *ctx = &__ctx[cpu]; + + if (ctx->used) + return; + + __ap_context_save_core_regs_from_pt_regs(ctx, regs); + __ap_context_save_core_extra_regs(ctx); + __ap_context_save_mmu_regs(ctx); + + ctx->used = true; + + pr_emerg("context saved (CPU:%d)\n", cpu); +} + +static noinline int __ap_context_register_vh(struct builder *bd) +{ + struct ap_context_drvdata *drvdata = + container_of(bd, struct ap_context_drvdata, bd); + ssize_t type; + int err; + + type = __ap_context_unique_id_to_type(drvdata->unique_id); + if (type >= TYPE_VH_MAX || type == TYPE_VH_UNKNOWN) + return -ERANGE; + + switch (type) { + case TYPE_VH_IPI_STOP: + err = register_trace_android_vh_ipi_stop( + __trace_android_vh_ipi_stop, NULL); + break; + default: + err = -EINVAL; + } + + return err; +} + +static noinline void __ap_context_unregister_vh(struct builder *bd) +{ + struct ap_context_drvdata *drvdata = + container_of(bd, struct ap_context_drvdata, bd); + struct device *dev = bd->dev; + ssize_t type; + + type = __ap_context_unique_id_to_type(drvdata->unique_id); + if (type >= TYPE_VH_MAX || type == TYPE_VH_UNKNOWN) { + dev_warn(dev, "invalid type number - %zd\n", type); + return; + } + + switch (type) { + case TYPE_VH_IPI_STOP: + unregister_trace_android_vh_ipi_stop( + __trace_android_vh_ipi_stop, NULL); + break; + default: + dev_warn(dev, "%zd is not a valid vendor hook\n", type); + } +} + +static __always_inline void __ap_context_hack_core_regs_for_panic( + struct pt_regs *regs) +{ + /* FIXME: stack is corrupted by another callees of 'panic'. */ + regs->sp = (uintptr_t)__builtin_frame_address(3); + regs->regs[29] = (uintptr_t)__builtin_frame_address(3); + regs->regs[30] = (uintptr_t)__builtin_return_address(2) - AARCH64_INSN_SIZE; + regs->pc = (uintptr_t)__builtin_return_address(2) - AARCH64_INSN_SIZE; +} + +static int __used __sec_arm64_ap_context_on_panic(struct pt_regs *regs) +{ + /* NOTE: x0 MUST BE SAVED before this function is called. + * see, 'sec_arm64_ap_context_on_panic'. + */ + struct notifier_block *this = (void *)regs->regs[0]; + struct ap_context_drvdata *drvdata = + container_of(this, struct ap_context_drvdata, nb_panic); + struct sec_arm64_ap_context *__ctx = drvdata->ctx; + struct sec_arm64_ap_context *ctx; + int cpu; + + if (!__ctx) + return NOTIFY_DONE; + + cpu = smp_processor_id(); + ctx = &__ctx[cpu]; + + if (ctx->used) + return NOTIFY_DONE; + + __ap_context_hack_core_regs_for_panic(regs); + __ap_context_save_core_regs_from_pt_regs(ctx, regs); + __ap_context_save_core_extra_regs(ctx); + __ap_context_save_mmu_regs(ctx); + + ctx->used = true; + + pr_emerg("context saved (CPU:%d)\n", cpu); + + return NOTIFY_OK; +} + +static int __naked sec_arm64_ap_context_on_panic(struct notifier_block *nb, + unsigned long l, void *d) +{ + asm volatile ( + "stp x0, x30, [sp, #-0x10]! \n\t" + + /* 'sp' indicates 'struct pt_regs' */ + "sub sp, sp, %0 \n\t" + "mov x0, sp \n\t" + "bl __ap_context_save_core_regs_on_current \n\t" + + /* save 'x0' on 'struct pt_regs' before calling + * '__sec_arm64_ap_context_on_panic' + */ + "ldr x0, [sp, %0] \n\t" + "str x0, [sp] \n\t" + + /* concrete notifier */ + "mov x0, sp \n\t" + "bl __sec_arm64_ap_context_on_panic \n\t" + + "add sp, sp, %0 \n\t" + "ldp x1, x30, [sp], #0x10 \n\t" + "ret \n\t" + : + : "i"(sizeof(struct pt_regs)) + : + ); +} + +static int __ap_context_register_panic_notifier(struct builder *bd) +{ + struct ap_context_drvdata *drvdata = + container_of(bd, struct ap_context_drvdata, bd); + struct notifier_block *nb = &drvdata->nb_panic; + + nb->notifier_call = sec_arm64_ap_context_on_panic; + + return atomic_notifier_chain_register(&panic_notifier_list, nb); +} + +static void __ap_context_unregister_panic_notifier(struct builder *bd) +{ + struct ap_context_drvdata *drvdata = + container_of(bd, struct ap_context_drvdata, bd); + struct notifier_block *nb = &drvdata->nb_panic; + + atomic_notifier_chain_unregister(&panic_notifier_list, nb); +} + +static int sec_arm64_ap_context_on_die(struct notifier_block *this, + unsigned long l, void *data) +{ + struct ap_context_drvdata *drvdata = + container_of(this, struct ap_context_drvdata, nb_die); + struct die_args *args = data; + struct pt_regs *regs = args->regs; + struct sec_arm64_ap_context *__ctx = drvdata->ctx; + struct sec_arm64_ap_context *ctx; + int cpu; + + if (!__ctx) + return NOTIFY_DONE; + + cpu = smp_processor_id(); + ctx = &__ctx[cpu]; + + if (ctx->used) + return NOTIFY_DONE; + + __ap_context_save_core_regs_from_pt_regs(ctx, regs); + __ap_context_save_core_extra_regs(ctx); + __ap_context_save_mmu_regs(ctx); + + ctx->used = true; + + pr_emerg("context saved (CPU:%d)\n", cpu); + + return NOTIFY_OK; +} + +static int __ap_context_register_die_notifier(struct builder *bd) +{ + struct ap_context_drvdata *drvdata = + container_of(bd, struct ap_context_drvdata, bd); + struct notifier_block *nb = &drvdata->nb_die; + + nb->notifier_call = sec_arm64_ap_context_on_die; + + return register_die_notifier(nb); +} + +static void __ap_context_unregister_die_notifier(struct builder *bd) +{ + struct ap_context_drvdata *drvdata = + container_of(bd, struct ap_context_drvdata, bd); + struct notifier_block *nb = &drvdata->nb_die; + + unregister_die_notifier(nb); +} + +static noinline int __ap_context_probe_epilog(struct builder *bd) +{ + struct ap_context_drvdata *drvdata = + container_of(bd, struct ap_context_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + + return 0; +} + +static int __ap_context_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct ap_context_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __ap_context_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct ap_context_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static const struct dev_builder __ap_context_dev_builder[] = { + DEVICE_BUILDER(__ap_context_parse_dt, NULL), + DEVICE_BUILDER(__ap_context_alloc_client, __ap_context_free_client), + DEVICE_BUILDER(__ap_context_register_vh, __ap_context_unregister_vh), + DEVICE_BUILDER(__ap_context_register_panic_notifier, + __ap_context_unregister_panic_notifier), + DEVICE_BUILDER(__ap_context_register_die_notifier, + __ap_context_unregister_die_notifier), + DEVICE_BUILDER(__ap_context_probe_epilog, NULL), +}; + +static int sec_ap_context_probe(struct platform_device *pdev) +{ + return __ap_context_probe(pdev, __ap_context_dev_builder, + ARRAY_SIZE(__ap_context_dev_builder)); +} + +static int sec_ap_context_remove(struct platform_device *pdev) +{ + return __ap_context_remove(pdev, __ap_context_dev_builder, + ARRAY_SIZE(__ap_context_dev_builder)); +} + +static const struct of_device_id sec_ap_context_match_table[] = { + { .compatible = "samsung,arm64-ap_context" }, + { .compatible = "samsung,ap_context" }, /* TODO: should be removed in future */ + {}, +}; +MODULE_DEVICE_TABLE(of, sec_ap_context_match_table); + +static struct platform_driver sec_ap_context_driver = { + .driver = { + .name = "sec,arm64-ap_context", + .of_match_table = of_match_ptr(sec_ap_context_match_table), + }, + .probe = sec_ap_context_probe, + .remove = sec_ap_context_remove, +}; + +static int __init sec_ap_context_init(void) +{ + return platform_driver_register(&sec_ap_context_driver); +} +arch_initcall(sec_ap_context_init); + +static void __exit sec_ap_context_exit(void) +{ + platform_driver_unregister(&sec_ap_context_driver); +} +module_exit(sec_ap_context_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("AP CORE/MMU context snaphot (ARM64)"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/debug/arm64/debug/Kconfig b/drivers/samsung/debug/arm64/debug/Kconfig new file mode 100644 index 000000000000..5994bc9f0a98 --- /dev/null +++ b/drivers/samsung/debug/arm64/debug/Kconfig @@ -0,0 +1,5 @@ +config SEC_ARM64_DEBUG + tristate "SEC Common debugging feature for ARM64 based devices" + depends on ARM64 + help + TODO: help is not ready. diff --git a/drivers/samsung/debug/arm64/debug/Makefile b/drivers/samsung/debug/arm64/debug/Makefile new file mode 100644 index 000000000000..6b3e4e1bcdb0 --- /dev/null +++ b/drivers/samsung/debug/arm64/debug/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_SEC_ARM64_DEBUG) += sec_arm64_debug.o +sec_arm64_debug-objs := sec_arm64_debug_main.o \ + sec_arm64_force_err.o + +CFLAGS_REMOVE_sec_arm64_force_err.o += -mgeneral-regs-only diff --git a/drivers/samsung/debug/arm64/debug/sec_arm64_debug.h b/drivers/samsung/debug/arm64/debug/sec_arm64_debug.h new file mode 100644 index 000000000000..1bd26d13334f --- /dev/null +++ b/drivers/samsung/debug/arm64/debug/sec_arm64_debug.h @@ -0,0 +1,17 @@ +#ifndef __INTERNAL__SEC_ARM64_DEBUG_H__ +#define __INTERNAL__SEC_ARM64_DEBUG_H__ + +#include + +#include + +struct arm64_debug_drvdata { + struct builder bd; +}; + +/* sec_arm64_force_err.c */ +extern int sec_fsimd_debug_init_random_pi_work(struct builder *bd); +extern int sec_arm64_force_err_init(struct builder *bd); +extern void sec_arm64_force_err_exit(struct builder *bd); + +#endif /* __INTERNAL__SEC_ARM64_DEBUG_H__ */ diff --git a/drivers/samsung/debug/arm64/debug/sec_arm64_debug_main.c b/drivers/samsung/debug/arm64/debug/sec_arm64_debug_main.c new file mode 100644 index 000000000000..85f46c900975 --- /dev/null +++ b/drivers/samsung/debug/arm64/debug/sec_arm64_debug_main.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2022-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include + +#include "sec_arm64_debug.h" + +static const struct dev_builder __arm64_debug_dev_builder[] = { + DEVICE_BUILDER(sec_fsimd_debug_init_random_pi_work, NULL), + DEVICE_BUILDER(sec_arm64_force_err_init, sec_arm64_force_err_exit), +}; + +static int __arm64_debug_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct arm64_debug_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __arm64_debug_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct arm64_debug_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static int sec_arm64_debug_probe(struct platform_device *pdev) +{ + return __arm64_debug_probe(pdev, __arm64_debug_dev_builder, + ARRAY_SIZE(__arm64_debug_dev_builder)); +} + +static int sec_arm64_debug_remove(struct platform_device *pdev) +{ + return __arm64_debug_remove(pdev, __arm64_debug_dev_builder, + ARRAY_SIZE(__arm64_debug_dev_builder)); +} + +static const struct of_device_id sec_arm64_debug_match_table[] = { + { .compatible = "samsung,arm64-debug" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_arm64_debug_match_table); + +static struct platform_driver sec_arm64_debug_driver = { + .driver = { + .name = "sec,arm64-debug", + .of_match_table = of_match_ptr(sec_arm64_debug_match_table), + }, + .probe = sec_arm64_debug_probe, + .remove = sec_arm64_debug_remove, +}; + +static int __init sec_arm64_debug_init(void) +{ + return platform_driver_register(&sec_arm64_debug_driver); +} +module_init(sec_arm64_debug_init); + +static void __exit sec_arm64_debug_exit(void) +{ + platform_driver_unregister(&sec_arm64_debug_driver); +} +module_exit(sec_arm64_debug_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("Common debugging feature for ARM64 based devices"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/debug/arm64/debug/sec_arm64_force_err.c b/drivers/samsung/debug/arm64/debug/sec_arm64_force_err.c new file mode 100644 index 000000000000..2c91dd51129a --- /dev/null +++ b/drivers/samsung/debug/arm64/debug/sec_arm64_force_err.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2020-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include + +#include + +#include "sec_arm64_debug.h" + +static void __arm64_simulate_undef(struct force_err_handle *h) +{ + asm volatile(".word 0xDEADBEEF"); +} + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +static noinline unsigned long __fsimd_random_pi(unsigned long seed) +{ + unsigned long pi_int; + + pi_int = (unsigned long)(M_PI * seed); + + return pi_int; +} + +static void sec_arm64_fsimd_random_pi(struct work_struct *work) +{ + unsigned long pi_int, seed; + size_t i; + + for (i = 0; i < 80; i++) { + seed = get_random_long() % 100UL; + pi_int = __fsimd_random_pi(seed); + pr_info("int(M_PI * %lu) = %lu\n", + seed, pi_int); + msleep(20); + } +} + +static struct work_struct random_pi_work[10]; + +int sec_fsimd_debug_init_random_pi_work(struct builder *bd) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(random_pi_work); i++) + INIT_WORK(&random_pi_work[i], sec_arm64_fsimd_random_pi); + + return 0; +} + +static void __arm64_simulate_fsimd_error(struct force_err_handle *h) +{ + size_t i; + + pr_emerg("Simulating fsimd error in kernel space\n"); + + for (i = 0; i < ARRAY_SIZE(random_pi_work); i++) + queue_work(system_long_wq, &random_pi_work[i]); + + ssleep(1); + + /* if we reach here, simulation failed */ + pr_emerg("Simulation of fsimd error failed\n"); +} + +static void __naked __arm64_simulate_pabort(struct force_err_handle *h) +{ + asm volatile ("mov x0, %0 \n\t" + "blr x0\n\t" + "ret \n\t" + :: "r" (PAGE_OFFSET - 0x8)); +} + +static void __naked __arm64_simulate_unaligned_pc(struct force_err_handle *h) +{ + asm volatile ("mov x1, %0 \n\t" + "mov x0, x30 \n\t" + "add x0, x0, 0x1 \n\t" + "orr x0, x0, x1 \n\n" + "blr x0\n\t" + "ret \n\t" + :: "r" PAGE_OFFSET); +} + +static void __naked __arm64_simulate_bti(struct force_err_handle *h) +{ + asm volatile ("adr x0, . \n\t" + "add x0, x0, 0x10 \n\t" + "br x0 \n\t" + "eor x0, x0, x0 \n\t" + "eor x0, x0, x0 \n\t" + "eor x0, x0, x0 \n\t" + "eor x0, x0, x0 \n\t" + "ret \n\t"); +} + +static struct force_err_handle __arm64_force_err_default[] = { + FORCE_ERR_HANDLE("undef", "Generating a undefined instruction exception!", + __arm64_simulate_undef), + FORCE_ERR_HANDLE("fsimd_err", "Generating an fsimd error!", + __arm64_simulate_fsimd_error), + FORCE_ERR_HANDLE("pabort", "Generating a data abort exception!", + __arm64_simulate_pabort), + FORCE_ERR_HANDLE("unaligned_pc", "Generating an unaligned pc exception!", + __arm64_simulate_unaligned_pc), + FORCE_ERR_HANDLE("bti", "Generating an bti exception!", + __arm64_simulate_bti), +}; + +static ssize_t __arm64_force_err_add_handlers(ssize_t begin) +{ + struct force_err_handle *h; + int err = 0; + ssize_t n = ARRAY_SIZE(__arm64_force_err_default); + ssize_t i; + + for (i = begin; i < n; i++) { + h = &__arm64_force_err_default[i]; + + INIT_HLIST_NODE(&h->node); + + err = sec_force_err_add_custom_handle(h); + if (err) { + pr_err("failed to add a handler - [%zu] %ps (%d)\n", + i, h->func, err); + return -i; + } + } + + return n; +} + +static void __arm64_force_err_del_handlers(ssize_t last_failed) +{ + struct force_err_handle *h; + int err = 0; + ssize_t n = ARRAY_SIZE(__arm64_force_err_default); + ssize_t i; + + BUG_ON((last_failed < 0) || (last_failed > n)); + + for (i = last_failed - 1; i >= 0; i--) { + h = &__arm64_force_err_default[i]; + + err = sec_force_err_del_custom_handle(h); + if (err) + pr_warn("failed to delete a handler - [%zu] %ps (%d)\n", + i, h->func, err); + } +} + +int sec_arm64_force_err_init(struct builder *bd) +{ + ssize_t last_failed; + + last_failed = __arm64_force_err_add_handlers(0); + if (last_failed <= 0) { + dev_warn(bd->dev, "force err is disabled. ignored.\n"); + goto err_add_handlers; + } + + return 0; + +err_add_handlers: + __arm64_force_err_del_handlers(-last_failed); + return 0; +} + +void sec_arm64_force_err_exit(struct builder *bd) +{ + __arm64_force_err_del_handlers(ARRAY_SIZE(__arm64_force_err_default)); +} diff --git a/drivers/samsung/debug/arm64/fsimd_debug/Kconfig b/drivers/samsung/debug/arm64/fsimd_debug/Kconfig new file mode 100644 index 000000000000..e5d8fc62f543 --- /dev/null +++ b/drivers/samsung/debug/arm64/fsimd_debug/Kconfig @@ -0,0 +1,5 @@ +config SEC_ARM64_FSIMD_DEBUG + tristate "SEC Detecting undesired NEON usage in kernel" + depends on ARM64 && ANDROID_VENDOR_HOOKS + help + TODO: help is not ready. diff --git a/drivers/samsung/debug/arm64/fsimd_debug/Makefile b/drivers/samsung/debug/arm64/fsimd_debug/Makefile new file mode 100644 index 000000000000..570b998bb63d --- /dev/null +++ b/drivers/samsung/debug/arm64/fsimd_debug/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SEC_ARM64_FSIMD_DEBUG) += sec_arm64_fsimd_debug.o diff --git a/drivers/samsung/debug/arm64/fsimd_debug/sec_arm64_fsimd_debug.c b/drivers/samsung/debug/arm64/fsimd_debug/sec_arm64_fsimd_debug.c new file mode 100644 index 000000000000..4e61bdf99d7c --- /dev/null +++ b/drivers/samsung/debug/arm64/fsimd_debug/sec_arm64_fsimd_debug.c @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2019-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +struct fsimd_debug_drvdata { + struct builder bd; +}; + +static noinline int __fsimd_debug_parse_dt_check_debug_level(struct builder *bd, + struct device_node *np) +{ + struct device *dev = bd->dev; + unsigned int sec_dbg_level = sec_debug_level(); + int err; + + err = sec_of_test_debug_level(np, "sec,debug_level", sec_dbg_level); + if (err == -ENOENT) { + dev_warn(dev, "%s will be enabled all sec debug levels!\n", + dev_name(dev)); + return 0; + } else if (err < 0) + return -ENODEV; + + return 0; +} + +static const struct dt_builder __fsimd_debug_dt_builder[] = { + DT_BUILDER(__fsimd_debug_parse_dt_check_debug_level), +}; + +static noinline int __fsimd_debug_parse_dt(struct builder *bd) +{ + return sec_director_parse_dt(bd, __fsimd_debug_dt_builder, + ARRAY_SIZE(__fsimd_debug_dt_builder)); +} + +#if IS_BUILTIN(CONFIG_SEC_ARM64_FSIMD_DEBUG) +static __always_inline void __fpsimd_save_state(struct user_fpsimd_state *state) +{ + fpsimd_save_state(state); +} +#else +/* NOTE: copied from arch/arm64/kernel/entry-fpsimd.S */ +static void __naked __fpsimd_save_state(struct user_fpsimd_state *state) +{ + asm volatile ( + "stp q0, q1, [x0] \n\t" + "stp q2, q3, [x0, #32] \n\t" + "stp q4, q5, [x0, #64] \n\t" + "stp q6, q7, [x0, #96] \n\t" + "stp q8, q9, [x0, #128] \n\t" + "stp q10, q11, [x0, #160] \n\t" + "stp q12, q13, [x0, #192] \n\t" + "stp q14, q15, [x0, #224] \n\t" + "stp q16, q17, [x0, #256] \n\t" + "stp q18, q19, [x0, #288] \n\t" + "stp q20, q21, [x0, #320] \n\t" + "stp q22, q23, [x0, #352] \n\t" + "stp q24, q25, [x0, #384] \n\t" + "stp q26, q27, [x0, #416] \n\t" + "stp q28, q29, [x0, #448] \n\t" + "stp q30, q31, [x0, #480]! \n\t" + "mrs x8, fpsr \n\t" + "str w8, [x0, #32] \n\t" + "mrs x8, fpcr \n\t" + "str w8, [x0, #36] \n\t" + "ret \n\t" + ); +} +#endif + +static void __trace_android_vh_is_fpsimd_save(void *unused, + struct task_struct *prev, struct task_struct *next) +{ + struct user_fpsimd_state current_st; + struct user_fpsimd_state *saved_st = &next->thread.uw.fpsimd_state; + size_t i; + + if (test_tsk_thread_flag(next, TIF_FOREIGN_FPSTATE)) + return; + + __fpsimd_save_state(¤t_st); + + for (i = 0; i < ARRAY_SIZE(current_st.vregs); i++) + BUG_ON(current_st.vregs[i] != saved_st->vregs[i]); + + BUG_ON((current_st.fpsr != saved_st->fpsr) || + (current_st.fpcr != saved_st->fpcr)); +} + +static int __fsimd_debug_install_vendor_hook(struct builder *bd) +{ + return register_trace_android_vh_is_fpsimd_save( + __trace_android_vh_is_fpsimd_save, + NULL); +} + +static void __fsimd_debug_uninstall_vendor_hook(struct builder *bd) +{ + unregister_trace_android_vh_is_fpsimd_save( + __trace_android_vh_is_fpsimd_save, + NULL); +} + +static noinline int __fsimd_debug_probe_epilog(struct builder *bd) +{ + struct fsimd_debug_drvdata *drvdata = + container_of(bd, struct fsimd_debug_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + + return 0; +} + +static const struct dev_builder __fsimd_debug_dev_builder[] = { + DEVICE_BUILDER(__fsimd_debug_parse_dt, NULL), + DEVICE_BUILDER(__fsimd_debug_install_vendor_hook, + __fsimd_debug_uninstall_vendor_hook), + DEVICE_BUILDER(__fsimd_debug_probe_epilog, NULL), +}; + +static int __fsimd_debug_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct fsimd_debug_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __fsimd_debug_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct fsimd_debug_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static int sec_fsimd_debug_probe(struct platform_device *pdev) +{ + return __fsimd_debug_probe(pdev, __fsimd_debug_dev_builder, + ARRAY_SIZE(__fsimd_debug_dev_builder)); +} + +static int sec_fsimd_debug_remove(struct platform_device *pdev) +{ + return __fsimd_debug_remove(pdev, __fsimd_debug_dev_builder, + ARRAY_SIZE(__fsimd_debug_dev_builder)); +} + +static const struct of_device_id sec_fsimd_debug_match_table[] = { + { .compatible = "samsung,arm64-fsimd_debug" }, + { .compatible = "samsung,fsimd_debug" }, /* TODP: should be removed in future */ + {}, +}; +MODULE_DEVICE_TABLE(of, sec_fsimd_debug_match_table); + +static struct platform_driver sec_fsimd_debug_driver = { + .driver = { + .name = "sec,arm64-fsimd_debug", + .of_match_table = of_match_ptr(sec_fsimd_debug_match_table), + }, + .probe = sec_fsimd_debug_probe, + .remove = sec_fsimd_debug_remove, +}; + +static int __init sec_fsimd_debug_init(void) +{ + return platform_driver_register(&sec_fsimd_debug_driver); +} +module_init(sec_fsimd_debug_init); + +static void __exit sec_fsimd_debug_exit(void) +{ + return platform_driver_unregister(&sec_fsimd_debug_driver); +} +module_exit(sec_fsimd_debug_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("Detecting fsimd register corruption"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/debug/boot_stat/Kconfig b/drivers/samsung/debug/boot_stat/Kconfig new file mode 100644 index 000000000000..8567f2975498 --- /dev/null +++ b/drivers/samsung/debug/boot_stat/Kconfig @@ -0,0 +1,21 @@ +config SEC_BOOT_STAT + tristate "SEC Boot-stat driver" + help + TODO: help is not ready. + +config SEC_BOOT_STAT_TEST_FOR_ON_DEVICE + tristate "KUnit test for sec_boot_stat_test" + depends on KUNIT + depends on SEC_BOOT_STAT + help + TODO: Describe config fully. + If you run this test driver on device, SHOULD set this config as 'm' to build test driver modulraly. + +config SEC_BOOT_STAT_TEST_FOR_ONLY_UML + tristate "KUnit test for sec_boot_stat_test" + depends on KUNIT + depends on SEC_BOOT_STAT + depends on UML + help + TODO: Describe config fully. + This CONFIG is recommended to set to y. diff --git a/drivers/samsung/debug/boot_stat/Makefile b/drivers/samsung/debug/boot_stat/Makefile new file mode 100644 index 000000000000..8399645c715b --- /dev/null +++ b/drivers/samsung/debug/boot_stat/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_SEC_BOOT_STAT) += sec_boot_stat.o +sec_boot_stat-objs := sec_boot_stat_main.o \ + sec_boot_stat_proc.o \ + sec_enh_boot_time_proc.o + +GCOV_PROFILE_sec_boot_stat.o := $(CONFIG_KUNIT) diff --git a/drivers/samsung/debug/boot_stat/sec_boot_stat.h b/drivers/samsung/debug/boot_stat/sec_boot_stat.h new file mode 100644 index 000000000000..e7e6df4da386 --- /dev/null +++ b/drivers/samsung/debug/boot_stat/sec_boot_stat.h @@ -0,0 +1,66 @@ +#ifndef __INTERNAL__SEC_BOOT_STAT_H__ +#define __INTERNAL__SEC_BOOT_STAT_H__ + +#include +#include +#include +#include + +#include +#include + +#include "sec_boot_stat_proc.h" +#include "sec_enh_boot_time_proc.h" + +struct boot_stat_drvdata { + struct builder bd; + struct device *bsp_dev; + struct mutex soc_ops_lock; + struct sec_boot_stat_soc_operations *soc_ops; + struct boot_stat_proc boot_stat; + struct enh_boot_time_proc enh_boot_time; +}; + +static __always_inline struct device *__boot_stat_proc_to_dev( + struct boot_stat_proc *boot_stat) +{ + struct boot_stat_drvdata *drvdata = container_of(boot_stat, + struct boot_stat_drvdata, boot_stat); + + return drvdata->bd.dev; +} + +static __always_inline struct device *__enh_boot_time_proc_to_dev( + struct enh_boot_time_proc *enh_boot_time) +{ + struct boot_stat_drvdata *drvdata = container_of(enh_boot_time, + struct boot_stat_drvdata, enh_boot_time); + + return drvdata->bd.dev; +} + +static __always_inline u32 __boot_stat_hash(const char *val) +{ + u32 hash = 0; + + while (*val++) + hash += (u32)*val; + + return hash; +} + +/* sec_boot_stata_main.c */ +extern unsigned long long sec_boot_stat_ktime_to_time(unsigned long long ktime); +extern void sec_boot_stat_bootloader_stat(struct seq_file *m); + +/* sec_boot_stat_proc.c */ +extern void sec_boot_stat_add_boot_event(struct boot_stat_drvdata *drvdata, const char *log); +extern int sec_boot_stat_proc_init(struct builder *bd); +extern void sec_boot_stat_proc_exit(struct builder *bd); + +/* sec_enh_boot_time_proc.c */ +extern void sec_enh_boot_time_add_boot_event(struct boot_stat_drvdata *drvdata, const char *log); +extern int sec_enh_boot_time_init(struct builder *bd); +extern void sec_enh_boot_time_exit(struct builder *bd); + +#endif /* __INTERNAL__SEC_BOOT_STAT_H__ */ diff --git a/drivers/samsung/debug/boot_stat/sec_boot_stat_main.c b/drivers/samsung/debug/boot_stat/sec_boot_stat_main.c new file mode 100644 index 000000000000..3ac57299d039 --- /dev/null +++ b/drivers/samsung/debug/boot_stat/sec_boot_stat_main.c @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2014-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include + +#include +#include + +#include "sec_boot_stat.h" + +static struct boot_stat_drvdata *sec_boot_stat; + +static __always_inline bool __boot_stat_is_probed(void) +{ + return !!sec_boot_stat; +} + +__ss_static __ss_always_inline bool __is_for_enh_boot_time(const char *log) +{ + const struct { + char pmsg_mark[2]; /* !@ */ + uint64_t boot_prefix; + char colon; + } __packed *log_head = (const void *)log; + const union { + uint64_t raw; + char text[8]; + } boot_prefix = { + .text = { 'B', 'o', 'o', 't', '_', 'E', 'B', 'S', }, + }; + + if (log_head->boot_prefix == boot_prefix.raw) + return true; + + return false; +} + +static void __boot_stat_add(struct boot_stat_drvdata *drvdata, const char *log) +{ + if (__is_for_enh_boot_time(log)) + sec_enh_boot_time_add_boot_event(drvdata, log); + else + sec_boot_stat_add_boot_event(drvdata, log); +} + +void sec_boot_stat_add(const char *log) +{ + if (!__boot_stat_is_probed()) + return; + + __boot_stat_add(sec_boot_stat, log); +} +EXPORT_SYMBOL_GPL(sec_boot_stat_add); + +int sec_boot_stat_register_soc_ops(struct sec_boot_stat_soc_operations *soc_ops) +{ + int ret = 0; + + if (!__boot_stat_is_probed()) + return -EBUSY; + + mutex_lock(&sec_boot_stat->soc_ops_lock); + + if (sec_boot_stat->soc_ops) { + pr_warn("soc specific operations already registered\n"); + ret = -ENOENT; + goto __arleady_registered; + } + + sec_boot_stat->soc_ops = soc_ops; + +__arleady_registered: + mutex_unlock(&sec_boot_stat->soc_ops_lock); + return ret; +} +EXPORT_SYMBOL_GPL(sec_boot_stat_register_soc_ops); + +int sec_boot_stat_unregister_soc_ops(struct sec_boot_stat_soc_operations *soc_ops) +{ + int ret = 0; + + if (!__boot_stat_is_probed()) + return -EBUSY; + + mutex_lock(&sec_boot_stat->soc_ops_lock); + + if (sec_boot_stat->soc_ops != soc_ops) { + pr_warn("already unregistered or wrong soc specific operation\n"); + ret = -EINVAL; + goto __invalid_soc_ops; + } + +__invalid_soc_ops: + mutex_unlock(&sec_boot_stat->soc_ops_lock); + return ret; +} +EXPORT_SYMBOL_GPL(sec_boot_stat_unregister_soc_ops); + +unsigned long long sec_boot_stat_ktime_to_time(unsigned long long ktime) +{ + struct sec_boot_stat_soc_operations *soc_ops; + unsigned long long time; + + mutex_lock(&sec_boot_stat->soc_ops_lock); + + soc_ops = sec_boot_stat->soc_ops; + if (!soc_ops || !soc_ops->ktime_to_time) { + time = ktime; + goto __without_adjust; + } + + time = soc_ops->ktime_to_time(ktime); + +__without_adjust: + mutex_unlock(&sec_boot_stat->soc_ops_lock); + return time; +} + +void sec_boot_stat_bootloader_stat(struct seq_file *m) +{ + struct sec_boot_stat_soc_operations *soc_ops; + + mutex_lock(&sec_boot_stat->soc_ops_lock); + + soc_ops = sec_boot_stat->soc_ops; + if (!soc_ops || !soc_ops->show_on_enh_boot_stat) { + pr_warn("Wrong soc show_on_enh_boot_stat operation\n"); + goto __without_adjust; + } + + soc_ops->show_on_enh_boot_stat(m); + +__without_adjust: + mutex_unlock(&sec_boot_stat->soc_ops_lock); +} + +static noinline int __boot_stat_probe_prolog(struct builder *bd) +{ + struct boot_stat_drvdata *drvdata = + container_of(bd, struct boot_stat_drvdata, bd); + + mutex_init(&drvdata->soc_ops_lock); + + return 0; +} + +static noinline void __boot_stat_remove_epilog(struct builder *bd) +{ + struct boot_stat_drvdata *drvdata = + container_of(bd, struct boot_stat_drvdata, bd); + + mutex_destroy(&drvdata->soc_ops_lock); +} + +static int __boot_stat_sec_class_create(struct builder *bd) +{ + struct boot_stat_drvdata *drvdata = + container_of(bd, struct boot_stat_drvdata, bd); + struct device *bsp_dev; + + bsp_dev = sec_device_create(NULL, "bsp"); + if (IS_ERR(bsp_dev)) + return PTR_ERR(bsp_dev); + + dev_set_drvdata(bsp_dev, drvdata); + + drvdata->bsp_dev = bsp_dev; + + return 0; +} + +static void __boot_stat_sec_class_remove(struct builder *bd) +{ + struct boot_stat_drvdata *drvdata = + container_of(bd, struct boot_stat_drvdata, bd); + struct device *bsp_dev = drvdata->bsp_dev; + + if (!bsp_dev) + return; + + sec_device_destroy(bsp_dev->devt); +} + +static ssize_t boot_stat_store(struct device *sec_class_dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct boot_stat_drvdata *drvdata = dev_get_drvdata(sec_class_dev); + + __boot_stat_add(drvdata, buf); + + return count; +} +DEVICE_ATTR_WO(boot_stat); + +static struct attribute *sec_bsp_attrs[] = { + &dev_attr_boot_stat.attr, + NULL, +}; + +static const struct attribute_group sec_bsp_attr_group = { + .attrs = sec_bsp_attrs, +}; + +static noinline int __boot_stat_sysfs_create(struct builder *bd) +{ + struct boot_stat_drvdata *drvdata = + container_of(bd, struct boot_stat_drvdata, bd); + struct device *dev = drvdata->bsp_dev; + int err; + + err = sysfs_create_group(&dev->kobj, &sec_bsp_attr_group); + if (err) + return err; + + return 0; +} + +static noinline void __boot_stat_sysfs_remove(struct builder *bd) +{ + struct boot_stat_drvdata *drvdata = + container_of(bd, struct boot_stat_drvdata, bd); + struct device *dev = drvdata->bsp_dev; + + sysfs_remove_group(&dev->kobj, &sec_bsp_attr_group); +} + +static noinline int __boot_stat_probe_epilog(struct builder *bd) +{ + struct boot_stat_drvdata *drvdata = + container_of(bd, struct boot_stat_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + + return 0; +} + +static int __boot_stat_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct boot_stat_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + sec_boot_stat = drvdata; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __boot_stat_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct boot_stat_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static const struct dev_builder __boot_stat_dev_builder[] = { + DEVICE_BUILDER(__boot_stat_probe_prolog, __boot_stat_remove_epilog), + DEVICE_BUILDER(__boot_stat_sec_class_create, + __boot_stat_sec_class_remove), + DEVICE_BUILDER(sec_boot_stat_proc_init, sec_boot_stat_proc_exit), + DEVICE_BUILDER(sec_enh_boot_time_init, sec_enh_boot_time_exit), + DEVICE_BUILDER(__boot_stat_sysfs_create, __boot_stat_sysfs_remove), + DEVICE_BUILDER(__boot_stat_probe_epilog, NULL), +}; + +static int sec_boot_stat_probe(struct platform_device *pdev) +{ + return __boot_stat_probe(pdev, __boot_stat_dev_builder, + ARRAY_SIZE(__boot_stat_dev_builder)); +} + +static int sec_boot_stat_remove(struct platform_device *pdev) +{ + return __boot_stat_remove(pdev, __boot_stat_dev_builder, + ARRAY_SIZE(__boot_stat_dev_builder)); +} + +static const struct of_device_id sec_boot_stat_match_table[] = { + { .compatible = "samsung,boot_stat" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_boot_stat_match_table); + +static struct platform_driver sec_boot_stat_driver = { + .driver = { + .name = "sec,boot_stat", + .of_match_table = of_match_ptr(sec_boot_stat_match_table), + }, + .probe = sec_boot_stat_probe, + .remove = sec_boot_stat_remove, +}; + +static int __init sec_boot_stat_init(void) +{ + return platform_driver_register(&sec_boot_stat_driver); +} +module_init(sec_boot_stat_init); + +static void __exit sec_boot_stat_exit(void) +{ + platform_driver_unregister(&sec_boot_stat_driver); +} +module_exit(sec_boot_stat_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("Boot-stat driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/debug/boot_stat/sec_boot_stat_proc.c b/drivers/samsung/debug/boot_stat/sec_boot_stat_proc.c new file mode 100644 index 000000000000..9b53855da3bd --- /dev/null +++ b/drivers/samsung/debug/boot_stat/sec_boot_stat_proc.c @@ -0,0 +1,453 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2014-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include + +#include + +#include "sec_boot_stat.h" + +static const char *h_line = "-----------------------------------------------------------------------------------"; + +#define BOOT_PREFIX(__idx, __head) \ + [__idx] = { \ + .head = __head, \ + .head_len = sizeof(__head) - 1, \ + } + +static const struct boot_prefix boot_prefixes[] = { + BOOT_PREFIX(EVT_PLATFORM, "!@Boot: "), + BOOT_PREFIX(EVT_RIL, "!@Boot_SVC : "), + BOOT_PREFIX(EVT_DEBUG, "!@Boot_DEBUG: "), + BOOT_PREFIX(EVT_SYSTEMSERVER, "!@Boot_SystemServer: "), + BOOT_PREFIX(EVT_INVALID, ""), +}; + +enum { + SYSTEM_START_INIT_PROCESS, + PLATFORM_START_PRELOAD, + PLATFORM_END_PRELOAD, + PLATFORM_START_INIT_AND_LOOP, + PLATFORM_START_PACKAGEMANAGERSERVICE, + PLATFORM_END_PACKAGEMANAGERSERVICE, + PLATFORM_START_NETWORK, + PLATFORM_END_NETWORK, + PLATFORM_END_INIT_AND_LOOP, + PLATFORM_PERFORMENABLESCREEN, + PLATFORM_ENABLE_SCREEN, + PLATFORM_BOOT_COMPLETE, + PLATFORM_FINISH_USER_UNLOCKED_COMPLETED, + PLATFORM_SET_ICON_VISIBILITY, + PLATFORM_LAUNCHER_ONCREATE, + PLATFORM_LAUNCHER_ONRESUME, + PLATFORM_LAUNCHER_LOADERTASK_RUN, + PLATFORM_LAUNCHER_FINISHFIRSTBIND, + PLATFORM_VOICE_SVC, + PLATFORM_DATA_SVC, + PLATFORM_PHONEAPP_ONCREATE, + RIL_UNSOL_RIL_CONNECTED, + RIL_SETRADIOPOWER_ON, + RIL_SETUICCSUBSCRIPTION, + RIL_SIM_RECORDSLOADED, + RIL_RUIM_RECORDSLOADED, + RIL_SETUPDATA_RECORDSLOADED, + RIL_SETUPDATACALL, + RIL_RESPONSE_SETUPDATACALL, + RIL_DATA_CONNECTION_ATTACHED, + RIL_DCT_IMSI_READY, + RIL_COMPLETE_CONNECTION, + RIL_CS_REG, + RIL_GPRS_ATTACH, + FACTORY_BOOT_COMPLETE, + NUM_BOOT_EVENTS, +}; + +#define BOOT_EVENT(__idx, __prefix_idx, __message) \ + [__idx] = { \ + .prefix_idx = __prefix_idx, \ + .message = __message, \ + .message_len = sizeof(__message) - 1, \ + } + +__ss_static struct boot_event boot_events[] = { + BOOT_EVENT(SYSTEM_START_INIT_PROCESS, EVT_PLATFORM, "start init process"), + BOOT_EVENT(PLATFORM_START_PRELOAD, EVT_PLATFORM, "Begin of preload()"), + BOOT_EVENT(PLATFORM_END_PRELOAD, EVT_PLATFORM, "End of preload()"), + BOOT_EVENT(PLATFORM_START_INIT_AND_LOOP, EVT_PLATFORM, "Entered the Android system server!"), + BOOT_EVENT(PLATFORM_START_PACKAGEMANAGERSERVICE, EVT_PLATFORM, "Start PackageManagerService"), + BOOT_EVENT(PLATFORM_END_PACKAGEMANAGERSERVICE, EVT_PLATFORM, "End PackageManagerService"), + BOOT_EVENT(PLATFORM_START_NETWORK, EVT_DEBUG, "start networkManagement"), + BOOT_EVENT(PLATFORM_END_NETWORK, EVT_DEBUG, "end networkManagement"), + BOOT_EVENT(PLATFORM_END_INIT_AND_LOOP, EVT_PLATFORM, "Loop forever"), + BOOT_EVENT(PLATFORM_PERFORMENABLESCREEN, EVT_PLATFORM, "performEnableScreen"), + BOOT_EVENT(PLATFORM_ENABLE_SCREEN, EVT_PLATFORM, "Enabling Screen!"), + BOOT_EVENT(PLATFORM_BOOT_COMPLETE, EVT_PLATFORM, "bootcomplete"), + BOOT_EVENT(PLATFORM_FINISH_USER_UNLOCKED_COMPLETED, EVT_DEBUG, "finishUserUnlockedCompleted"), + BOOT_EVENT(PLATFORM_SET_ICON_VISIBILITY, EVT_PLATFORM, "setIconVisibility: ims_volte: [SHOW]"), + BOOT_EVENT(PLATFORM_LAUNCHER_ONCREATE, EVT_DEBUG, "Launcher.onCreate()"), + BOOT_EVENT(PLATFORM_LAUNCHER_ONRESUME, EVT_DEBUG, "Launcher.onResume()"), + BOOT_EVENT(PLATFORM_LAUNCHER_LOADERTASK_RUN, EVT_DEBUG, "Launcher.LoaderTask.run() start"), + BOOT_EVENT(PLATFORM_LAUNCHER_FINISHFIRSTBIND, EVT_DEBUG, "Launcher - FinishFirstBind"), + BOOT_EVENT(PLATFORM_VOICE_SVC, EVT_PLATFORM, "Voice SVC is acquired"), + BOOT_EVENT(PLATFORM_DATA_SVC, EVT_PLATFORM, "Data SVC is acquired"), + BOOT_EVENT(PLATFORM_PHONEAPP_ONCREATE, EVT_RIL, "PhoneApp OnCrate"), + BOOT_EVENT(RIL_UNSOL_RIL_CONNECTED, EVT_RIL, "RIL_UNSOL_RIL_CONNECTED"), + BOOT_EVENT(RIL_SETRADIOPOWER_ON, EVT_RIL, "setRadioPower on"), + BOOT_EVENT(RIL_SETUICCSUBSCRIPTION, EVT_RIL, "setUiccSubscription"), + BOOT_EVENT(RIL_SIM_RECORDSLOADED, EVT_RIL, "SIM onAllRecordsLoaded"), + BOOT_EVENT(RIL_RUIM_RECORDSLOADED, EVT_RIL, "RUIM onAllRecordsLoaded"), + BOOT_EVENT(RIL_SETUPDATA_RECORDSLOADED, EVT_RIL, "SetupDataRecordsLoaded"), + BOOT_EVENT(RIL_SETUPDATACALL, EVT_RIL, "setupDataCall"), + BOOT_EVENT(RIL_RESPONSE_SETUPDATACALL, EVT_RIL, "Response setupDataCall"), + BOOT_EVENT(RIL_DATA_CONNECTION_ATTACHED, EVT_RIL, "onDataConnectionAttached"), + BOOT_EVENT(RIL_DCT_IMSI_READY, EVT_RIL, "IMSI Ready"), + BOOT_EVENT(RIL_COMPLETE_CONNECTION, EVT_RIL, "completeConnection"), + BOOT_EVENT(RIL_CS_REG, EVT_RIL, "CS Registered"), + BOOT_EVENT(RIL_GPRS_ATTACH, EVT_RIL, "GPRS Attached"), + BOOT_EVENT(FACTORY_BOOT_COMPLETE, EVT_PLATFORM, "Factory Process [Boot Completed]"), +}; + +__ss_static __ss_always_inline bool __boot_stat_is_boot_event(const char *log) +{ + const union { + uint64_t raw; + char text[8]; + } boot_prefix = { + .text = { '!', '@', 'B', 'o', 'o', 't', '\0', '\0' }, + }; + /* NOTE: this is only valid on the 'little endian' system */ + const uint64_t boot_prefix_mask = 0x0000FFFFFFFFFFFF; + uint64_t log_prefix; + + log_prefix = (*(uint64_t *)log) & boot_prefix_mask; + if (log_prefix == boot_prefix.raw) + return true; + + return false; +} + +__ss_static __ss_always_inline ssize_t __boot_stat_get_message_offset_from_plog( + const char *log, size_t *offset) +{ + ssize_t i; + + for (i = 0; i < NUM_OF_BOOT_PREFIX; i++) { + const struct boot_prefix *prefix = &boot_prefixes[i]; + + if (unlikely(!strncmp(log, prefix->head, prefix->head_len))) { + *offset = prefix->head_len; + return i; + } + } + + return -EINVAL; +} + +static __always_inline struct boot_event *__boot_stat_find_event_locked( + struct boot_stat_proc *boot_stat, + const char *message) +{ + struct boot_event *h; + size_t msg_len = strlen(message); + u32 key = __boot_stat_hash(message); + + hash_for_each_possible(boot_stat->event_htbl, h, hlist, key) { + if (h->message_len != msg_len) + continue; + + if (!strncmp(h->message, message, msg_len)) + return h; + } + + return ERR_PTR(-ENOENT); +} + +__ss_static __ss_always_inline void __boot_stat_record_boot_event_locked( + struct boot_stat_proc *boot_stat, const char *message) +{ + struct boot_event *event = + __boot_stat_find_event_locked(boot_stat, message); + + if (IS_ERR_OR_NULL(event)) + return; + + if (event->ktime) + return; + + event->ktime = local_clock(); + + list_add_tail(&event->list, &boot_stat->boot_event_head); + boot_stat->nr_event++; +} + +#define MAX_LENGTH_OF_SYSTEMSERVER_LOG 90 + +struct systemserver_init_time_entry { + struct list_head list; + char buf[MAX_LENGTH_OF_SYSTEMSERVER_LOG]; +}; + +static __always_inline void __boot_stat_record_systemserver_init_time_locked( + struct boot_stat_proc *boot_stat, const char *message) +{ + struct systemserver_init_time_entry *entry; + struct device *dev = __boot_stat_proc_to_dev(boot_stat); + + if (likely(boot_stat->is_completed)) + return; + + entry = devm_kzalloc(dev, sizeof(*entry), GFP_KERNEL); + if (unlikely(!entry)) + return; + + strlcpy(entry->buf, message, sizeof(entry->buf)); + list_add(&entry->list, &boot_stat->systemserver_init_time_head); +} + +static __always_inline void __boot_stat_add_boot_event_locked( + struct boot_stat_proc *boot_stat, + const char *log) +{ + ssize_t prefix_idx; + size_t offset; + const char *message; + + prefix_idx = __boot_stat_get_message_offset_from_plog(log, &offset); + message = &log[offset]; + + switch (prefix_idx) { + case EVT_PLATFORM: + if (unlikely(!boot_stat->is_completed && + !strcmp(message, "bootcomplete"))) { + boot_stat->ktime_completed = local_clock(); + boot_stat->is_completed = true; + } + __boot_stat_record_boot_event_locked(boot_stat, message); + break; + case EVT_RIL: + case EVT_DEBUG: + __boot_stat_record_boot_event_locked(boot_stat, message); + break; + case EVT_SYSTEMSERVER: + __boot_stat_record_systemserver_init_time_locked(boot_stat, + message); + break; + default: + return; + } +} + +void sec_boot_stat_add_boot_event(struct boot_stat_drvdata *drvdata, + const char *log) +{ + struct boot_stat_proc *boot_stat; + + if (!__boot_stat_is_boot_event(log)) + return; + + boot_stat = &drvdata->boot_stat; + + mutex_lock(&boot_stat->lock); + __boot_stat_add_boot_event_locked(boot_stat, log); + mutex_unlock(&boot_stat->lock); +} + +static unsigned long long __boot_stat_show_boot_event_each_locked( + struct seq_file *m, + struct boot_stat_proc *boot_stat, + struct boot_event *event, unsigned long long prev_ktime) +{ + char *log; + unsigned long long msec; + unsigned long long delta; + unsigned long long time; + + log = kasprintf(GFP_KERNEL, "%s%s", + boot_prefixes[event->prefix_idx].head, event->message); + + msec = event->ktime; + do_div(msec, 1000000ULL); + + delta = event->ktime - prev_ktime; + do_div(delta, 1000000ULL); + + time = sec_boot_stat_ktime_to_time(event->ktime); + do_div(time, 1000000ULL); + + seq_printf(m, "%-46s%11llu%13llu%13llu\n", log, time, msec, delta); + + kfree(log); + + return event->ktime; +} + +static void __boot_stat_show_soc(struct seq_file *m, + struct boot_stat_proc *boot_stat) +{ + struct boot_stat_drvdata *drvdata = container_of(boot_stat, + struct boot_stat_drvdata, boot_stat); + struct sec_boot_stat_soc_operations *soc_ops; + + mutex_lock(&drvdata->soc_ops_lock); + + soc_ops = drvdata->soc_ops; + if (!soc_ops || !soc_ops->show_on_boot_stat) { + mutex_unlock(&drvdata->soc_ops_lock); + return; + } + + soc_ops->show_on_boot_stat(m); + mutex_unlock(&drvdata->soc_ops_lock); +} + +static void __boot_stat_show_boot_event_head(struct seq_file *m, + struct boot_stat_proc *boot_stat) +{ + seq_printf(m, "%-47s%11s%13s%13s\n", "Boot Events", + "time", "ktime", "delta"); + seq_printf(m, "%s\n", h_line); + + __boot_stat_show_soc(m, boot_stat); +} + +static void __boot_stat_show_boot_event_locked(struct seq_file *m, + struct boot_stat_proc *boot_stat) +{ + struct list_head *boot_event_head = &boot_stat->boot_event_head; + struct boot_event *event; + unsigned long long prev_ktime = 0ULL; + + list_for_each_entry(event, boot_event_head, list) + prev_ktime = __boot_stat_show_boot_event_each_locked( + m, boot_stat, event, prev_ktime); +} + +static void __boot_stat_show_systemserver_init_time_locked(struct seq_file *m, + struct boot_stat_proc *boot_stat) +{ + struct list_head *systemserver_init_time_head = + &boot_stat->systemserver_init_time_head; + struct systemserver_init_time_entry *init_time; + + seq_printf(m, "%s\n", h_line); + seq_puts(m, "SystemServer services that took long time\n\n"); + + list_for_each_entry(init_time, systemserver_init_time_head, list) + seq_printf(m, "%s\n", init_time->buf); +} + +static int sec_boot_stat_proc_show(struct seq_file *m, void *v) +{ + struct boot_stat_proc *boot_stat = m->private; + + __boot_stat_show_boot_event_head(m, boot_stat); + + mutex_lock(&boot_stat->lock); + __boot_stat_show_boot_event_locked(m, boot_stat); + __boot_stat_show_systemserver_init_time_locked(m, boot_stat); + mutex_unlock(&boot_stat->lock); + + return 0; +} + +static int sec_boot_stat_proc_open(struct inode *inode, struct file *file) +{ + void *__boot_stat = pde_data(inode); + + return single_open(file, sec_boot_stat_proc_show, __boot_stat); +} + +static const struct proc_ops boot_stat_pops = { + .proc_open = sec_boot_stat_proc_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = single_release, +}; + +static int __boot_stat_procfs_init(struct device *dev, + struct boot_stat_proc *boot_stat) +{ + struct proc_dir_entry *proc; + const char *node_name = "boot_stat"; + + proc = proc_create_data(node_name, 0444, NULL, &boot_stat_pops, + boot_stat); + if (!proc) { + dev_err(dev, "failed create procfs node (%s)\n", + node_name); + return -ENODEV; + } + + boot_stat->proc = proc; + + return 0; +} + +static void __boot_stat_procfs_exit(struct device *dev, + struct boot_stat_proc *boot_stat) +{ + proc_remove(boot_stat->proc); +} + +__ss_static int __boot_stat_init_boot_events(struct boot_stat_proc *boot_stat) +{ + size_t i; + + hash_init(boot_stat->event_htbl); + + for (i = 0; i < ARRAY_SIZE(boot_events); i++) { + struct boot_event *event = &boot_events[i]; + u32 key = __boot_stat_hash(event->message); + + INIT_HLIST_NODE(&event->hlist); + hash_add(boot_stat->event_htbl, &event->hlist, key); + event->ktime = 0; + } + + return 0; +} + +int sec_boot_stat_proc_init(struct builder *bd) +{ + struct boot_stat_drvdata *drvdata = + container_of(bd, struct boot_stat_drvdata, bd); + struct device *dev = bd->dev; + struct boot_stat_proc *boot_stat = &drvdata->boot_stat; + int err; + + mutex_init(&boot_stat->lock); + boot_stat->total_event = ARRAY_SIZE(boot_events); + INIT_LIST_HEAD(&boot_stat->boot_event_head); + INIT_LIST_HEAD(&boot_stat->systemserver_init_time_head); + + __boot_stat_init_boot_events(boot_stat); + + if (IS_MODULE(CONFIG_SEC_BOOT_STAT)) + sec_boot_stat_add_boot_event(drvdata, + "!@Boot: start init process"); + + err = __boot_stat_procfs_init(dev, boot_stat); + if (err) + return err; + + return 0; +} + +void sec_boot_stat_proc_exit(struct builder *bd) +{ + struct boot_stat_drvdata *drvdata = + container_of(bd, struct boot_stat_drvdata, bd); + struct device *dev = bd->dev; + struct boot_stat_proc *boot_stat = &drvdata->boot_stat; + + __boot_stat_procfs_exit(dev, boot_stat); + mutex_destroy(&boot_stat->lock); +} diff --git a/drivers/samsung/debug/boot_stat/sec_boot_stat_proc.h b/drivers/samsung/debug/boot_stat/sec_boot_stat_proc.h new file mode 100644 index 000000000000..23a11de506e8 --- /dev/null +++ b/drivers/samsung/debug/boot_stat/sec_boot_stat_proc.h @@ -0,0 +1,41 @@ +#ifndef __INTERNAL__SEC_BOOT_STAT_PROC_H__ +#define __INTERNAL__SEC_BOOT_STAT_PROC_H__ + +#define BOOT_STAT_HASH_BITS 3 + +struct boot_stat_proc { + struct proc_dir_entry *proc; + struct mutex lock; + bool is_completed; + unsigned long long ktime_completed; + struct list_head boot_event_head; + size_t total_event; + size_t nr_event; + struct list_head systemserver_init_time_head; + DECLARE_HASHTABLE(event_htbl, BOOT_STAT_HASH_BITS); +}; + +enum { + EVT_PLATFORM = 0, + EVT_RIL, + EVT_DEBUG, + EVT_SYSTEMSERVER, + EVT_INVALID, + NUM_OF_BOOT_PREFIX = EVT_INVALID, +}; + +struct boot_prefix { + const char *head; + size_t head_len; +}; + +struct boot_event { + struct hlist_node hlist; + struct list_head list; + size_t prefix_idx; + const char *message; + size_t message_len; + unsigned long long ktime; +}; + +#endif /* __INTERNAL__SEC_BOOT_STAT_PROC_H__ */ diff --git a/drivers/samsung/debug/boot_stat/sec_enh_boot_time_proc.c b/drivers/samsung/debug/boot_stat/sec_enh_boot_time_proc.c new file mode 100644 index 000000000000..a9efbc4e4059 --- /dev/null +++ b/drivers/samsung/debug/boot_stat/sec_enh_boot_time_proc.c @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2014-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include + +#include "sec_boot_stat.h" + +static const char *h_line = "-----------------------------------------------------------------------------------"; + +static __always_inline struct enh_boot_time_entry *__enh_boot_time_find_entry_locked( + struct enh_boot_time_proc *enh_boot_time, + const char *message) +{ + struct enh_boot_time_entry *h; + size_t msg_len = strlen(message); + u32 key = __boot_stat_hash(message); + + hash_for_each_possible(enh_boot_time->boot_time_htbl, h, hlist, key) { + size_t len = strnlen(h->buf, msg_len + 1); + + if (len != msg_len) + continue; + + if (!strncmp(h->buf, message, msg_len)) + return h; + } + + return ERR_PTR(-ENOENT); +} + +static __always_inline void __enh_boot_time_record_locked( + struct enh_boot_time_proc *enh_boot_time, + const char *message) +{ + struct enh_boot_time_entry *entry; + struct device *dev = __enh_boot_time_proc_to_dev(enh_boot_time); + struct enh_boot_time_entry *entry_in_hash; + u32 key; + + entry = devm_kzalloc(dev, sizeof(*entry), GFP_KERNEL); + if (unlikely(!entry)) + return; + + strlcpy(entry->buf, message, sizeof(entry->buf)); + entry_in_hash = __enh_boot_time_find_entry_locked(enh_boot_time, + entry->buf); + if (!IS_ERR(entry_in_hash)) { + devm_kfree(dev, entry); + return; + } + + entry->ktime = local_clock(); + list_add(&entry->list, &enh_boot_time->boot_time_head); + + INIT_HLIST_NODE(&entry->hlist); + key = __boot_stat_hash(entry->buf); + hash_add(enh_boot_time->boot_time_htbl, &entry->hlist, key); +} + +#define DELAY_KTIME_EBS 30000000000 /* 30 sec */ +#define MAX_EVENTS_EBS 150 + +/* NOTE: BE CAREFUL!!. 2 mutexes are used in this function. */ +static __always_inline void __enh_boot_time_update_is_finished( + struct enh_boot_time_proc *enh_boot_time, + struct boot_stat_proc *boot_stat) +{ + unsigned long long delay_ktime; + + mutex_lock(&boot_stat->lock); + if (!boot_stat->is_completed) { + mutex_unlock(&boot_stat->lock); + return; + } + mutex_unlock(&boot_stat->lock); + + mutex_lock(&enh_boot_time->lock); + if (enh_boot_time->is_finished) { + mutex_unlock(&enh_boot_time->lock); + return; + } + + /* NOTE: after 'boot_stat->is_completed' is set, 'ktime_completed' + * is never changed anymore. So, at this point, lock in not needed. + */ + delay_ktime = local_clock() - boot_stat->ktime_completed; + + if (delay_ktime >= DELAY_KTIME_EBS) + enh_boot_time->is_finished = true; + + mutex_unlock(&enh_boot_time->lock); +} + +static __always_inline void __enh_boot_time_add_boot_event_locked( + struct enh_boot_time_proc *enh_boot_time, const char *log) +{ + const struct { + char pmsg_mark[2]; /* !@ */ + uint64_t boot_prefix; + char colon; + } __packed *log_head; + + if (enh_boot_time->is_finished || + enh_boot_time->nr_event >= MAX_EVENTS_EBS) + return; + + log_head = (void *)log; + if (log_head->colon == ':') { + const size_t offset = sizeof("!@Boot_EBS: ") - 1; + __enh_boot_time_record_locked(enh_boot_time, &log[offset]); + } else if (log_head->colon == '_') { + __enh_boot_time_record_locked(enh_boot_time, log); + } else { + return; + } + + enh_boot_time->nr_event++; +} + +void sec_enh_boot_time_add_boot_event( + struct boot_stat_drvdata *drvdata, const char *log) +{ + struct enh_boot_time_proc *enh_boot_time = &drvdata->enh_boot_time; + struct boot_stat_proc *boot_stat = &drvdata->boot_stat; + + __enh_boot_time_update_is_finished(enh_boot_time, boot_stat); + + mutex_lock(&enh_boot_time->lock); + __enh_boot_time_add_boot_event_locked(enh_boot_time, log); + mutex_unlock(&enh_boot_time->lock); +} + +static unsigned long long __enh_boot_time_show_framework_each_locked( + struct seq_file *m, struct enh_boot_time_entry *entry, + unsigned long long prev_ktime) +{ + unsigned long long msec; + unsigned long long delta; + unsigned long long curr_ktime = entry->ktime; + unsigned long long time; + + msec = curr_ktime; + do_div(msec, 1000000ULL); + + time = sec_boot_stat_ktime_to_time(curr_ktime); + do_div(time, 1000000ULL); + + if (entry->buf[0] == '!') { + delta = curr_ktime - prev_ktime; + do_div(delta, 1000000ULL); + + seq_printf(m, "%-90s%7llu%7llu%7llu\n", entry->buf, + time, msec, delta); + } else { + seq_printf(m, "%-90s%7llu%7llu\n", entry->buf, + time, msec); + curr_ktime = prev_ktime; + } + + return curr_ktime; +} + +static void __enh_boot_time_show_framework_locked(struct seq_file *m, + struct enh_boot_time_proc *enh_boot_time) +{ + struct list_head *head = &enh_boot_time->boot_time_head; + struct enh_boot_time_entry *entry; + unsigned long long prev_ktime = 0; + + seq_printf(m, "%-90s%7s%7s%7s\n", "Boot Events", "time", "ktime", "delta"); + seq_printf(m, "%s\n", h_line); + seq_puts(m, "BOOTLOADER\n"); + seq_printf(m, "%s\n", h_line); + sec_boot_stat_bootloader_stat(m); + + seq_printf(m, "%s\n", h_line); + seq_puts(m, "FRAMEWORK\n"); + seq_printf(m, "%s\n", h_line); + + prev_ktime = 0; + list_for_each_entry_reverse (entry, head, list) { + prev_ktime = __enh_boot_time_show_framework_each_locked(m, + entry, prev_ktime); + } +} + +static void __enh_boot_time_show_framework(struct seq_file *m, + struct enh_boot_time_proc *enh_boot_time) +{ + mutex_lock(&enh_boot_time->lock); + __enh_boot_time_show_framework_locked(m, enh_boot_time); + mutex_unlock(&enh_boot_time->lock); +} + +static void __enh_boot_time_show_soc(struct seq_file *m, + struct enh_boot_time_proc *enh_boot_time) +{ + struct boot_stat_drvdata *drvdata = container_of(enh_boot_time, + struct boot_stat_drvdata, enh_boot_time); + struct sec_boot_stat_soc_operations *soc_ops; + + mutex_lock(&drvdata->soc_ops_lock); + + soc_ops = drvdata->soc_ops; + if (!soc_ops || !soc_ops->show_on_enh_boot_time) { + mutex_unlock(&drvdata->soc_ops_lock); + return; + } + + soc_ops->show_on_enh_boot_time(m); + mutex_unlock(&drvdata->soc_ops_lock); +} + +static int sec_enh_boot_time_proc_show(struct seq_file *m, void *v) +{ + struct enh_boot_time_proc *enh_boot_time = m->private; + + __enh_boot_time_show_soc(m, enh_boot_time); + __enh_boot_time_show_framework(m, enh_boot_time); + + return 0; +} + +static int sec_enh_boot_time_proc_open(struct inode *inode, struct file *file) +{ + void *__enh_boot_time = pde_data(inode); + + return single_open(file, sec_enh_boot_time_proc_show, __enh_boot_time); +} + +static const struct proc_ops enh_boot_time_pops = { + .proc_open = sec_enh_boot_time_proc_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = single_release, +}; + +static int __enh_boot_time_procfs_init(struct device *dev, + struct enh_boot_time_proc *enh_boot_time) +{ + struct proc_dir_entry *proc; + const char *node_name = "enhanced_boot_stat"; + + proc = proc_create_data(node_name, 0444, NULL, &enh_boot_time_pops, + enh_boot_time); + if (!proc) { + dev_err(dev, "failed create procfs node (%s)\n", + node_name); + return -ENODEV; + } + + enh_boot_time->proc = proc; + + return 0; +} + +static void __enh_boot_time_procfs_exit(struct device *dev, + struct enh_boot_time_proc *enh_boot_time) +{ + proc_remove(enh_boot_time->proc); +} + +int sec_enh_boot_time_init(struct builder *bd) +{ + struct boot_stat_drvdata *drvdata = + container_of(bd, struct boot_stat_drvdata, bd); + struct device *dev = bd->dev; + struct enh_boot_time_proc *enh_boot_time = &drvdata->enh_boot_time; + int err; + + mutex_init(&enh_boot_time->lock); + INIT_LIST_HEAD(&enh_boot_time->boot_time_head); + hash_init(enh_boot_time->boot_time_htbl); + + if (IS_MODULE(CONFIG_SEC_BOOT_STAT)) + sec_enh_boot_time_add_boot_event(drvdata, + "!@Boot_EBS_F: FirstStageMain Init"); + + err = __enh_boot_time_procfs_init(dev, enh_boot_time); + if (err) + return err; + + return 0; +} + +void sec_enh_boot_time_exit(struct builder *bd) +{ + struct boot_stat_drvdata *drvdata = + container_of(bd, struct boot_stat_drvdata, bd); + struct device *dev = bd->dev; + struct enh_boot_time_proc *enh_boot_time = &drvdata->enh_boot_time; + + __enh_boot_time_procfs_exit(dev, enh_boot_time); + mutex_destroy(&enh_boot_time->lock); +} diff --git a/drivers/samsung/debug/boot_stat/sec_enh_boot_time_proc.h b/drivers/samsung/debug/boot_stat/sec_enh_boot_time_proc.h new file mode 100644 index 000000000000..4ea7717d7b3e --- /dev/null +++ b/drivers/samsung/debug/boot_stat/sec_enh_boot_time_proc.h @@ -0,0 +1,24 @@ +#ifndef __INTERNAL__SEC_ENH_BOOT_TIME_PROC_H__ +#define __INTERNAL__SEC_ENH_BOOT_TIME_PROC_H__ + +#define BOOT_TIME_HASH_BITS 3 + +struct enh_boot_time_proc { + struct proc_dir_entry *proc; + struct mutex lock; + bool is_finished; + size_t nr_event; + struct list_head boot_time_head; + DECLARE_HASHTABLE(boot_time_htbl, BOOT_TIME_HASH_BITS); +}; + +#define MAX_LENGTH_OF_ENH_BOOT_TIME_LOG 90 + +struct enh_boot_time_entry { + struct list_head list; + struct hlist_node hlist; + char buf[MAX_LENGTH_OF_ENH_BOOT_TIME_LOG]; + unsigned long long ktime; +}; + +#endif /* __INTERNAL__SEC_ENH_BOOT_TIME_PROC_H__ */ diff --git a/drivers/samsung/debug/common/Kconfig b/drivers/samsung/debug/common/Kconfig new file mode 100644 index 000000000000..aebcfdec030c --- /dev/null +++ b/drivers/samsung/debug/common/Kconfig @@ -0,0 +1,11 @@ +menuconfig SEC_DEBUG + tristate "SEC TN Debugging Features" + help + TODO: help is not ready. + +config SEC_FORCE_ERR + bool "SEC Generating force errors" + default y if SEC_FACTORY + depends on SEC_DEBUG + help + TODO: help is not ready. diff --git a/drivers/samsung/debug/common/Makefile b/drivers/samsung/debug/common/Makefile new file mode 100644 index 000000000000..e9d5172c8572 --- /dev/null +++ b/drivers/samsung/debug/common/Makefile @@ -0,0 +1,9 @@ +obj-$(CONFIG_SEC_DEBUG) += sec_debug.o +sec_debug-objs := sec_debug_main.o \ + sec_ap_serial.o \ + sec_user_fault.o \ + sec_debug_node.o \ + sec_debug_show_stat.o \ + sec_panic_with_reason.o + +sec_debug-$(CONFIG_SEC_FORCE_ERR) += sec_force_err.o diff --git a/drivers/samsung/debug/common/sec_ap_serial.c b/drivers/samsung/debug/common/sec_ap_serial.c new file mode 100644 index 000000000000..eaced55717f0 --- /dev/null +++ b/drivers/samsung/debug/common/sec_ap_serial.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2019-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include + +#include + +#include "sec_debug.h" + +static unsigned long long ap_serial __ro_after_init; +module_param_named(ap_serial, ap_serial, ullong, 0440); + +static ssize_t SVC_AP_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%016llX\n", ap_serial); +} + +static DEVICE_ATTR_RO(SVC_AP); + +static struct attribute *SVC_AP_attrs[] = { + &dev_attr_SVC_AP.attr, + NULL, +}; + +static struct attribute_group SVC_AP_group = { + .attrs = SVC_AP_attrs, +}; + +static const struct attribute_group *SVC_AP_groups[] = { + &SVC_AP_group, + NULL, +}; + +static int __ap_serial_svc_ap_init(struct device *dev, + struct svc_ap_node *svc_ap) +{ + struct kernfs_node *kn; + struct kset *dev_ks; + struct kobject *svc_kobj; + struct device *ap_dev; + int err; + + dev_ks = dev->kobj.kset; + + svc_kobj = kobject_create_and_add("svc", &dev_ks->kobj); + if (IS_ERR_OR_NULL(svc_kobj)) { + kn = sysfs_get_dirent(dev_ks->kobj.sd, "svc"); + if (!kn) { + dev_err(dev, "failed to create sys/devices/svc\n"); + return -ENODEV; + } + svc_kobj = (struct kobject *)kn->priv; + } + + ap_dev = devm_kzalloc(dev, sizeof(struct device), GFP_KERNEL); + if (!ap_dev) { + err = -ENOMEM; + goto err_alloc_ap_dev; + } + + err = dev_set_name(ap_dev, "AP"); + if (err < 0) { + err = -ENOENT; + goto err_set_name_ap_dev; + } + + ap_dev->kobj.parent = svc_kobj; + ap_dev->groups = SVC_AP_groups; + + err = device_register(ap_dev); + if (err < 0) { + err = -EINVAL; + goto err_register_ap_dev; + } + + svc_ap->ap_dev = ap_dev; + svc_ap->svc_kobj = svc_kobj; + + return 0; + +err_register_ap_dev: +err_set_name_ap_dev: +err_alloc_ap_dev: + kobject_put(svc_kobj); + + return err; +} + +static void __ap_serial_svc_ap_exit(struct device *dev, + struct svc_ap_node *svc_ap) +{ + device_unregister(svc_ap->ap_dev); + kobject_put(svc_ap->svc_kobj); +} + +int sec_ap_serial_sysfs_init(struct builder *bd) +{ + struct sec_debug_drvdata *drvdata = container_of(bd, + struct sec_debug_drvdata, bd); + struct device *dev = bd->dev; + + return __ap_serial_svc_ap_init(dev, &drvdata->svc_ap); +} + +void sec_ap_serial_sysfs_exit(struct builder *bd) +{ + struct sec_debug_drvdata *drvdata = container_of(bd, + struct sec_debug_drvdata, bd); + struct device *dev = bd->dev; + + __ap_serial_svc_ap_exit(dev, &drvdata->svc_ap); +} diff --git a/drivers/samsung/debug/common/sec_debug.h b/drivers/samsung/debug/common/sec_debug.h new file mode 100644 index 000000000000..5390ab042eac --- /dev/null +++ b/drivers/samsung/debug/common/sec_debug.h @@ -0,0 +1,70 @@ +#ifndef __INTERNAL__SEC_DEBUG_H__ +#define __INTERNAL__SEC_DEBUG_H__ + +#include +#include +#include +#include + +#include + +struct svc_ap_node { + struct kobject *svc_kobj; + struct device *ap_dev; +}; + +#define FORCE_ERR_HASH_BITS 3 + +struct force_err { + struct mutex lock; + DECLARE_HASHTABLE(htbl, FORCE_ERR_HASH_BITS); +#if IS_ENABLED(CONFIG_DEBUG_FS) + struct dentry *dbgfs; +#endif +}; + +struct sec_debug_drvdata { + struct builder bd; + struct svc_ap_node svc_ap; + struct notifier_block nb_panic; +#if IS_ENABLED(CONFIG_SEC_FORCE_ERR) + struct force_err force_err; +#endif +#if IS_ENABLED(CONFIG_DEBUG_FS) + struct dentry *dbgfs_panic; +#endif +}; + +extern struct sec_debug_drvdata *sec_debug; + +static __always_inline bool __debug_is_probed(void) +{ + return !!sec_debug; +} + +/* sec_user_fault.c */ +extern int sec_user_fault_init(struct builder *bd); +extern void sec_user_fault_exit(struct builder *bd); + +/* sec_ap_serial.c */ +extern int sec_ap_serial_sysfs_init(struct builder *bd); +extern void sec_ap_serial_sysfs_exit(struct builder *bd); + +/* sec_force_err.c */ +extern int sec_force_err_probe_prolog(struct builder *bd); +extern void sec_force_err_remove_epilog(struct builder *bd); +extern int sec_force_err_build_htbl(struct builder *bd); +extern int sec_force_err_debugfs_create(struct builder *bd); +extern void sec_force_err_debugfs_remove(struct builder *bd); + +/* sec_debug_show_stat.c */ +extern void sec_debug_show_stat(const char *msg); + +/* sec_debug_node.c */ +extern int sec_debug_node_init_dump_sink(struct builder *bd); + +/* sec_panic_with_reason.c */ +extern int sec_panic_with_reason_init(struct builder *bd); +extern void sec_panic_with_reason_exit(struct builder *bd); + +#endif /* __INTERNAL__SEC_DEBUG_H__ */ diff --git a/drivers/samsung/debug/common/sec_debug_main.c b/drivers/samsung/debug/common/sec_debug_main.c new file mode 100644 index 000000000000..c690a928c806 --- /dev/null +++ b/drivers/samsung/debug/common/sec_debug_main.c @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2017-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "sec_debug.h" + +struct sec_debug_drvdata *sec_debug; + +static unsigned int sec_dbg_level __ro_after_init; +module_param_named(debug_level, sec_dbg_level, uint, 0440); + +static unsigned int sec_dbg_force_upload __ro_after_init; +module_param_named(force_upload, sec_dbg_force_upload, uint, 0440); + +static unsigned int enable __read_mostly = 1; +module_param_named(enable, enable, uint, 0644); + +unsigned int sec_debug_level(void) +{ + return sec_dbg_level; +} +EXPORT_SYMBOL_GPL(sec_debug_level); + +bool sec_debug_is_enabled(void) +{ + switch (sec_dbg_level) { + case SEC_DEBUG_LEVEL_LOW: +#if IS_ENABLED(CONFIG_SEC_FACTORY) + case SEC_DEBUG_LEVEL_MID: +#endif + return !!sec_dbg_force_upload; + } + + return !!enable; +} +EXPORT_SYMBOL_GPL(sec_debug_is_enabled); + +static noinline int __debug_parse_dt_panic_notifier_priority(struct builder *bd, + struct device_node *np) +{ + struct sec_debug_drvdata *drvdata = + container_of(bd, struct sec_debug_drvdata, bd); + struct notifier_block *nb = &drvdata->nb_panic; + s32 priority; + int err; + + err = of_property_read_s32(np, "sec,panic_notifier-priority", + &priority); + if (err) + return -EINVAL; + + nb->priority = (int)priority; + + return 0; +} + +static const struct dt_builder __debug_dt_builder[] = { + DT_BUILDER(__debug_parse_dt_panic_notifier_priority), +}; + +static noinline int __debug_parse_dt(struct builder *bd) +{ + return sec_director_parse_dt(bd, __debug_dt_builder, + ARRAY_SIZE(__debug_dt_builder)); +} + +static int sec_debug_panic_notifer_handler(struct notifier_block *nb, + unsigned long l, void *msg) +{ + sec_debug_show_stat((const char *)msg); + + return NOTIFY_OK; +} + +static int __debug_register_panic_notifier(struct builder *bd) +{ + struct sec_debug_drvdata *drvdata = + container_of(bd, struct sec_debug_drvdata, bd); + struct notifier_block *nb = &drvdata->nb_panic; + + nb->notifier_call = sec_debug_panic_notifer_handler; + + return atomic_notifier_chain_register(&panic_notifier_list, nb); +} + +static void __debug_unregister_panic_notifier(struct builder *bd) +{ + struct sec_debug_drvdata *drvdata = + container_of(bd, struct sec_debug_drvdata, bd); + struct notifier_block *nb = &drvdata->nb_panic; + + atomic_notifier_chain_unregister(&panic_notifier_list, nb); +} + +static noinline int __debug_probe_epilog(struct builder *bd) +{ + struct sec_debug_drvdata *drvdata = + container_of(bd, struct sec_debug_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + sec_debug = drvdata; + + return 0; +} + +static noinline void __debug_remove_epilog(struct builder *bd) +{ + /* FIXME: This is not a graceful exit. */ + sec_debug = NULL; +} + +static int __debug_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct sec_debug_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __debug_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct sec_debug_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static const struct dev_builder __debug_dev_builder[] = { + DEVICE_BUILDER(__debug_parse_dt, NULL), + DEVICE_BUILDER(sec_user_fault_init, sec_user_fault_exit), + DEVICE_BUILDER(sec_ap_serial_sysfs_init, sec_ap_serial_sysfs_exit), + DEVICE_BUILDER(sec_debug_node_init_dump_sink, NULL), + DEVICE_BUILDER(__debug_register_panic_notifier, + __debug_unregister_panic_notifier), + DEVICE_BUILDER(sec_panic_with_reason_init, sec_panic_with_reason_exit), +#if IS_ENABLED(CONFIG_SEC_FORCE_ERR) + DEVICE_BUILDER(sec_force_err_probe_prolog, sec_force_err_remove_epilog), + DEVICE_BUILDER(sec_force_err_build_htbl, NULL), + DEVICE_BUILDER(sec_force_err_debugfs_create, sec_force_err_debugfs_remove), +#endif + DEVICE_BUILDER(__debug_probe_epilog, __debug_remove_epilog), +}; + +static int sec_debug_probe(struct platform_device *pdev) +{ + return __debug_probe(pdev, __debug_dev_builder, + ARRAY_SIZE(__debug_dev_builder)); +} + +static int sec_debug_remove(struct platform_device *pdev) +{ + return __debug_remove(pdev, __debug_dev_builder, + ARRAY_SIZE(__debug_dev_builder)); +} + +static const struct of_device_id sec_debug_match_table[] = { + { .compatible = "samsung,sec_debug" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_debug_match_table); + +static struct platform_driver sec_debug_driver = { + .driver = { + .name = "sec,debug", + .of_match_table = of_match_ptr(sec_debug_match_table), + }, + .probe = sec_debug_probe, + .remove = sec_debug_remove, +}; + +static int __init sec_debug_init(void) +{ + int err; + + err = platform_driver_register(&sec_debug_driver); + if (err) + return err; + + err = __of_platform_early_populate_init(sec_debug_match_table); + if (err) + return err; + + return 0; +} +core_initcall(sec_debug_init); + +static void __exit sec_debug_exit(void) +{ + platform_driver_unregister(&sec_debug_driver); +} +module_exit(sec_debug_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("TN Debugging Feature"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/debug/common/sec_debug_node.c b/drivers/samsung/debug/common/sec_debug_node.c new file mode 100644 index 000000000000..a55cc5be5d4d --- /dev/null +++ b/drivers/samsung/debug/common/sec_debug_node.c @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2019-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "sec_debug.h" + +static long *g_allocated_phys_mem; +static long *g_allocated_virt_mem; + +static int sec_alloc_virtual_mem(const char *val, const struct kernel_param *kp) +{ + long *mem; + char *str = (char *) val; + size_t size = (size_t)memparse(str, &str); + + if (size) { + mem = vmalloc(size); + if (mem) { + pr_info("Allocated virtual memory of size: 0x%zx bytes\n", + size); + *mem = (long)g_allocated_virt_mem; + g_allocated_virt_mem = mem; + + return 0; + } + + pr_err("Failed to allocate virtual memory of size: 0x%zx bytes\n", + size); + + return -ENOMEM; + } + + pr_info("Invalid size: %s bytes\n", val); + + return -EAGAIN; +} +module_param_call(alloc_virtual_mem, &sec_alloc_virtual_mem, NULL, NULL, 0644); + +static int sec_free_virtual_mem(const char *val, const struct kernel_param *kp) +{ + long *mem; + char *str = (char *) val; + size_t free_count = (size_t)memparse(str, &str); + + if (!free_count) { + if (strncmp(val, "all", 4)) { + free_count = 10; + } else { + pr_err("Invalid free count: %s\n", val); + return -EAGAIN; + } + } + + if (free_count > 10) + free_count = 10; + + if (!g_allocated_virt_mem) { + pr_err("No virtual memory chunk to free.\n"); + return 0; + } + + while (g_allocated_virt_mem && free_count--) { + mem = (long *) *g_allocated_virt_mem; + vfree(g_allocated_virt_mem); + g_allocated_virt_mem = mem; + } + + pr_info("Freed previously allocated virtual memory chunks.\n"); + + if (g_allocated_virt_mem) + pr_info("Still, some virtual memory chunks are not freed. Try again.\n"); + + return 0; +} +module_param_call(free_virtual_mem, &sec_free_virtual_mem, NULL, NULL, 0644); + +static int sec_alloc_physical_mem(const char *val, + const struct kernel_param *kp) +{ + long *mem; + char *str = (char *) val; + size_t size = (size_t)memparse(str, &str); + + if (size) { + mem = kmalloc(size, GFP_KERNEL); + if (mem) { + pr_info("Allocated physical memory of size: 0x%zx bytes\n", + size); + *mem = (long) g_allocated_phys_mem; + g_allocated_phys_mem = mem; + + return 0; + } + + pr_err("Failed to allocate physical memory of size: 0x%zx bytes\n", + size); + + return -ENOMEM; + } + + pr_info("Invalid size: %s bytes\n", val); + + return -EAGAIN; +} +module_param_call(alloc_physical_mem, &sec_alloc_physical_mem, + NULL, NULL, 0644); + +static int sec_free_physical_mem(const char *val, const struct kernel_param *kp) +{ + long *mem; + char *str = (char *) val; + size_t free_count = (size_t)memparse(str, &str); + + if (!free_count) { + if (strncmp(val, "all", 4)) { + free_count = 10; + } else { + pr_info("Invalid free count: %s\n", val); + return -EAGAIN; + } + } + + if (free_count > 10) + free_count = 10; + + if (!g_allocated_phys_mem) { + pr_info("No physical memory chunk to free.\n"); + return 0; + } + + while (g_allocated_phys_mem && free_count--) { + mem = (long *) *g_allocated_phys_mem; + kfree(g_allocated_phys_mem); + g_allocated_phys_mem = mem; + } + + pr_info("Freed previously allocated physical memory chunks.\n"); + + if (g_allocated_phys_mem) + pr_info("Still, some physical memory chunks are not freed. Try again.\n"); + + return 0; +} +module_param_call(free_physical_mem, &sec_free_physical_mem, NULL, NULL, 0644); + +#if IS_BUILTIN(CONFIG_SEC_DEBUG) +static int dbg_set_cpu_affinity(const char *val, const struct kernel_param *kp) +{ + char *endptr; + pid_t pid; + int cpu; + struct cpumask mask; + long ret; + + pid = (pid_t)memparse(val, &endptr); + if (*endptr != '@') { + pr_info("invalid input strin: %s\n", val); + return -EINVAL; + } + + cpu = (int)memparse(++endptr, &endptr); + cpumask_clear(&mask); + cpumask_set_cpu(cpu, &mask); + pr_info("Setting %d cpu affinity to cpu%d\n", pid, cpu); + + ret = sched_setaffinity(pid, &mask); + pr_info("sched_setaffinity returned %ld\n", ret); + + return 0; +} +module_param_call(setcpuaff, &dbg_set_cpu_affinity, NULL, NULL, 0644); +#endif + +/* FIXME: backward compatibility. This value is always 1 */ +static unsigned int reboot_multicmd = 1; +module_param_named(reboot_multicmd, reboot_multicmd, uint, 0644); + +static unsigned int dump_sink; +static unsigned int *dump_sink_virt; + +static int sec_debug_set_dump_sink(const char *val, + const struct kernel_param *kp) +{ + int ret = param_set_uint(val, kp); + + if (dump_sink_virt) + *dump_sink_virt = dump_sink; + + return ret; +} + +static int sec_debug_get_dump_sink(char *buffer, const struct kernel_param *kp) +{ + BUG_ON(dump_sink_virt && (*dump_sink_virt != dump_sink)); + + return param_get_uint(buffer, kp); +} + +phys_addr_t sec_debug_get_dump_sink_phys(void) +{ + return virt_to_phys(dump_sink_virt); +} +EXPORT_SYMBOL_GPL(sec_debug_get_dump_sink_phys); + +module_param_call(dump_sink, sec_debug_set_dump_sink, sec_debug_get_dump_sink, + &dump_sink, 0644); + +int sec_debug_node_init_dump_sink(struct builder *bd) +{ + struct device *dev = bd->dev; + + dump_sink_virt = devm_kmalloc(dev, sizeof(*dump_sink_virt), GFP_KERNEL); + if (!dump_sink_virt) + return -ENOMEM; + + *dump_sink_virt = dump_sink; + + return 0; +} diff --git a/drivers/samsung/debug/common/sec_debug_show_stat.c b/drivers/samsung/debug/common/sec_debug_show_stat.c new file mode 100644 index 000000000000..c7f82673d536 --- /dev/null +++ b/drivers/samsung/debug/common/sec_debug_show_stat.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#include +#include +#include +#include +#include +#include + +static bool __debug_is_platform_lockup_suspected(const char *msg) +{ + static const char *expected[] = { + "Crash Key", + "User Crash Key", + "Long Key Press", + "Software Watchdog Timer expired", + }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(expected); i++) { + if (!strncmp(msg, expected[i], strlen(expected[i]))) + return true; + } + + return false; +} + +/* NOTE: see 'state_filter_match' function */ +static bool __debug_is_task_uninterruptible(struct task_struct *p) +{ + if (!(p->__state & TASK_UNINTERRUPTIBLE)) + return false; + + if (p->__state == TASK_IDLE) + return false; + + return true; +} + +/* NOTE: see 'show_state_filter' function */ +static void ____debug_show_task_uninterruptible(void) +{ + struct task_struct *g, *p; + + for_each_process_thread(g, p) { + if (__debug_is_task_uninterruptible(p)) + sched_show_task(p); + } +} + +static void __debug_show_task_uninterruptible(void) +{ + pr_info("\n"); + pr_info(" ---------------------------------------------------------------------------------------\n"); + + if (IS_BUILTIN(CONFIG_SEC_DEBUG)) + show_state_filter(TASK_UNINTERRUPTIBLE); + else + ____debug_show_task_uninterruptible(); + + pr_info(" ---------------------------------------------------------------------------------------\n"); +} + +/* TODO: this is a modified version of 'show_stat' in 'fs/proc/stat.c' + * this function should be adapted for each kernel version + */ +#ifndef arch_irq_stat_cpu +#define arch_irq_stat_cpu(cpu) 0 +#endif +#ifndef arch_irq_stat +#define arch_irq_stat() 0 +#endif + +static void __debug_show_cpu_stat(void) +{ + int i, j; + u64 user, nice, system, idle, iowait, irq, softirq, steal; + u64 guest, guest_nice; + u64 sum = 0; + u64 sum_softirq = 0; + unsigned int per_softirq_sums[NR_SOFTIRQS] = {0}; + struct timespec64 boottime; + + user = nice = system = idle = iowait = + irq = softirq = steal = 0; + guest = guest_nice = 0; + getboottime64(&boottime); + + for_each_possible_cpu(i) { + user += kcpustat_cpu(i).cpustat[CPUTIME_USER]; + nice += kcpustat_cpu(i).cpustat[CPUTIME_NICE]; + system += kcpustat_cpu(i).cpustat[CPUTIME_SYSTEM]; + idle += kcpustat_cpu(i).cpustat[CPUTIME_IDLE]; + iowait += kcpustat_cpu(i).cpustat[CPUTIME_IOWAIT]; + irq += kcpustat_cpu(i).cpustat[CPUTIME_IRQ]; + softirq += kcpustat_cpu(i).cpustat[CPUTIME_SOFTIRQ]; + steal += kcpustat_cpu(i).cpustat[CPUTIME_STEAL]; + guest += kcpustat_cpu(i).cpustat[CPUTIME_GUEST]; + guest_nice += kcpustat_cpu(i).cpustat[CPUTIME_GUEST_NICE]; + sum += kstat_cpu_irqs_sum(i); + sum += arch_irq_stat_cpu(i); + + for (j = 0; j < NR_SOFTIRQS; j++) { + unsigned int softirq_stat = kstat_softirqs_cpu(j, i); + + per_softirq_sums[j] += softirq_stat; + sum_softirq += softirq_stat; + } + } + sum += arch_irq_stat(); + + pr_info("\n"); + pr_info(" ---------------------------------------------------------------------------------------\n"); + pr_info(" %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s\n", + "user", "nice", "system", "idle", "iowait", "irq", + "softirq", "steal", "guest", "guest_nice"); + pr_info("cpu %8llu %8llu %8llu %8llu %8llu %8llu %8llu %8llu %8llu %8llu\n", + nsec_to_clock_t(user), + nsec_to_clock_t(nice), + nsec_to_clock_t(system), + nsec_to_clock_t(idle), + nsec_to_clock_t(iowait), + nsec_to_clock_t(irq), + nsec_to_clock_t(softirq), + nsec_to_clock_t(steal), + nsec_to_clock_t(guest), + nsec_to_clock_t(guest_nice)); + + for_each_online_cpu(i) { + /* Copy values here to work around gcc-2.95.3, gcc-2.96 */ + user = kcpustat_cpu(i).cpustat[CPUTIME_USER]; + nice = kcpustat_cpu(i).cpustat[CPUTIME_NICE]; + system = kcpustat_cpu(i).cpustat[CPUTIME_SYSTEM]; + idle = kcpustat_cpu(i).cpustat[CPUTIME_IDLE]; + iowait = kcpustat_cpu(i).cpustat[CPUTIME_IOWAIT]; + irq = kcpustat_cpu(i).cpustat[CPUTIME_IRQ]; + softirq = kcpustat_cpu(i).cpustat[CPUTIME_SOFTIRQ]; + steal = kcpustat_cpu(i).cpustat[CPUTIME_STEAL]; + guest = kcpustat_cpu(i).cpustat[CPUTIME_GUEST]; + guest_nice = kcpustat_cpu(i).cpustat[CPUTIME_GUEST_NICE]; + pr_info("cpu%-2d %8llu %8llu %8llu %8llu %8llu %8llu %8llu %8llu %8llu %8llu\n", + i, + nsec_to_clock_t(user), + nsec_to_clock_t(nice), + nsec_to_clock_t(system), + nsec_to_clock_t(idle), + nsec_to_clock_t(iowait), + nsec_to_clock_t(irq), + nsec_to_clock_t(softirq), + nsec_to_clock_t(steal), + nsec_to_clock_t(guest), + nsec_to_clock_t(guest_nice)); + } + +#if 0 +#if IS_BUILTIN(CONFIG_SEC_DEBUG) + pr_info(" ---------------------------------------------------------------------------------------\n"); + pr_info("intr %llu\n", (unsigned long long)sum); + + /* sum again ? it could be updated? */ + for_each_irq_nr(j) + if (kstat_irqs(j)) + pr_info(" irq-%d : %u\n", j, kstat_irqs(j)); + + pr_info(" ---------------------------------------------------------------------------------------\n"); + pr_info("\nctxt %llu\n" + "btime %llu\n" + "processes %lu\n" + "procs_running %lu\n" + "procs_blocked %lu\n", + nr_context_switches(), + (unsigned long long)boottime.tv_sec, + total_forks, + nr_running(), + nr_iowait()); + + pr_info(" ---------------------------------------------------------------------------------------\n"); + pr_info("softirq %llu\n", (unsigned long long)sum_softirq); + + for (i = 0; i < NR_SOFTIRQS; i++) + pr_info(" softirq-%d : %u\n", i, per_softirq_sums[i]); +#endif +#endif + + pr_info("\n"); + + pr_info(" ---------------------------------------------------------------------------------------\n"); +} + +void sec_debug_show_stat(const char *msg) +{ + if (!__debug_is_platform_lockup_suspected(msg)) + return; + + __debug_show_task_uninterruptible(); + __debug_show_cpu_stat(); +} diff --git a/drivers/samsung/debug/common/sec_force_err.c b/drivers/samsung/debug/common/sec_force_err.c new file mode 100644 index 000000000000..ac45e7fbf4df --- /dev/null +++ b/drivers/samsung/debug/common/sec_force_err.c @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2017-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "sec_debug.h" + +static u32 __force_err_hash(const char *val) +{ + u32 hash = 0; + + while (*val++) + hash += (u32)*val; + + return hash; +} + +static struct force_err_handle *__force_err_find_handle_locked( + struct force_err *force_err, const char *val) +{ + struct force_err_handle *h; + size_t val_len = strlen(val); + u32 key = __force_err_hash(val); + + hash_for_each_possible(force_err->htbl, h, node, key) { + size_t len = strnlen(h->val, val_len + 1); + + if (len != val_len) + continue; + + if (!strncmp(h->val, val, val_len)) + return h; + } + + return ERR_PTR(-ENOENT); +} + +static inline int __force_err_add_custom_handle(struct force_err *force_err, + struct force_err_handle *h) +{ + struct force_err_handle *h_old; + u32 key = __force_err_hash(h->val); + int ret = 0; + + mutex_lock(&force_err->lock); + + if (hash_empty(force_err->htbl)) { + ret = -EBUSY; + goto not_initialized; + } + + if (hash_hashed(&h->node)) { + pr_warn("The node is aready added! (%s)\n", h->val); + goto already_added; + } + + h_old = __force_err_find_handle_locked(force_err, h->val); + if (!IS_ERR(h_old)) { + pr_warn("A same handler for %s is regitered before. I'll be removed.\n", + h->val); + hash_del(&h_old->node); + } + + hash_add(force_err->htbl, &h->node, key); + +already_added: +not_initialized: + mutex_unlock(&force_err->lock); + return ret; +} + +int sec_force_err_add_custom_handle(struct force_err_handle *h) +{ + if (!__debug_is_probed()) + return -EBUSY; + + return __force_err_add_custom_handle(&sec_debug->force_err, h); +} +EXPORT_SYMBOL_GPL(sec_force_err_add_custom_handle); + +static inline int __force_err_del_custom_handle(struct force_err *force_err, + struct force_err_handle *h) +{ + int ret = 0; + + mutex_lock(&force_err->lock); + + if (hash_empty(force_err->htbl)) { + ret = -EBUSY; + goto not_initialized; + } + + if (!hash_hashed(&h->node)) + goto already_removed; + + hash_del(&h->node); + +already_removed: +not_initialized: + mutex_unlock(&force_err->lock); + return ret; +} + +int sec_force_err_del_custom_handle(struct force_err_handle *h) +{ + if (!__debug_is_probed()) + return -EBUSY; + + return __force_err_del_custom_handle(&sec_debug->force_err, h); +} +EXPORT_SYMBOL_GPL(sec_force_err_del_custom_handle); + +/* timeout for dog bark/bite */ +#define DELAY_TIME 20000 + +static void __simulate_apps_wdog_bark(struct force_err_handle *h) +{ + unsigned long time_out_jiffies; + + pr_emerg("Simulating apps watch dog bark\n"); + local_irq_disable(); + + time_out_jiffies = jiffies + msecs_to_jiffies(DELAY_TIME); + while (time_is_after_jiffies(time_out_jiffies)) + udelay(1); + + local_irq_enable(); + /* if we reach here, simulation failed */ + pr_emerg("Simulation of apps watch dog bark failed\n"); +} + +static void __simulate_bug(struct force_err_handle *h) +{ + BUG(); +} + +static void __simulate_bug_on(struct force_err_handle *h) +{ + BUG_ON(true); +} + +static void __simulate_panic(struct force_err_handle *h) +{ + panic("%s", __func__); +} + +static void __simulate_apps_wdog_bite(struct force_err_handle *h) +{ + unsigned long time_out_jiffies; + +#if IS_ENABLED(CONFIG_HOTPLUG_CPU) + int cpu; + + for_each_online_cpu(cpu) { + if (cpu == 0) + continue; + remove_cpu(cpu); + } +#endif + + pr_emerg("Simulating apps watch dog bite\n"); + local_irq_disable(); + + time_out_jiffies = jiffies + msecs_to_jiffies(DELAY_TIME); + while (time_is_after_jiffies(time_out_jiffies)) + udelay(1); + + local_irq_enable(); + /* if we reach here, simulation had failed */ + pr_emerg("Simualtion of apps watch dog bite failed\n"); +} + +static void __simulate_bus_hang(struct force_err_handle *h) +{ + void __iomem *p = NULL; + + pr_emerg("Generating Bus Hang!\n"); + + p = ioremap_wt(0xFC4B8000, 32); + *(unsigned int *)p = *(unsigned int *)p; + mb(); /* memory barriar to generate bus hang */ + + pr_info("*p = %x\n", *(unsigned int *)p); + + pr_emerg("Clk may be enabled.Try again if it reaches here!\n"); +} + +unsigned long *__dabort_buf; + +static void __simulate_dabort(struct force_err_handle *h) +{ + *__dabort_buf = 0; +} + +static void __simulate_pabort(struct force_err_handle *h) +{ + ((void (*)(void))NULL)(); +} + +static void __simulate_dblfree(struct force_err_handle *h) +{ + unsigned int *ptr = kmalloc(sizeof(unsigned int), GFP_KERNEL); + + kfree(ptr); + msleep(1000); + kfree(ptr); +} + +static void __simulate_danglingref(struct force_err_handle *h) +{ + unsigned int *ptr = kmalloc(sizeof(unsigned int), GFP_KERNEL); + + kfree(ptr); + + *ptr = 0x1234; +} + +static void __simulate_lowmem(struct force_err_handle *h) +{ + size_t i; + + for (i = 0; kmalloc(128 * 1024, GFP_KERNEL); i++) + ; + + pr_emerg("Allocated %zu KB!\n", i * 128); +} + +static void __simulate_memcorrupt(struct force_err_handle *h) +{ + unsigned int *ptr = kmalloc(sizeof(unsigned int), GFP_KERNEL); + + *ptr++ = 4; + *ptr = 2; + + panic("MEMORY CORRUPTION"); +} + +static struct force_err_handle __force_err_default[] = { + FORCE_ERR_HANDLE("appdogbark", "Generating an apps wdog bark!", + __simulate_apps_wdog_bark), + FORCE_ERR_HANDLE("appdogbite", "Generating an apps wdog bite!", + __simulate_apps_wdog_bite), + FORCE_ERR_HANDLE("dabort", "Generating a data abort exception!", + __simulate_dabort), + FORCE_ERR_HANDLE("pabort", "Generating a data abort exception!", + __simulate_pabort), + FORCE_ERR_HANDLE("bushang", "Generating a Bus Hang!", + __simulate_bus_hang), + FORCE_ERR_HANDLE("dblfree", NULL, + __simulate_dblfree), + FORCE_ERR_HANDLE("danglingref", NULL, + __simulate_danglingref), + FORCE_ERR_HANDLE("lowmem", "Allocating memory until failure!", + __simulate_lowmem), + FORCE_ERR_HANDLE("memcorrupt", NULL, + __simulate_memcorrupt), + FORCE_ERR_HANDLE("KP", "Generating a data abort exception!", + __simulate_dabort), + FORCE_ERR_HANDLE("DP", NULL, + __simulate_apps_wdog_bark), + FORCE_ERR_HANDLE("bug", "call BUG()", + __simulate_bug), + FORCE_ERR_HANDLE("bug_on", "call BUG_ON()", + __simulate_bug_on), + FORCE_ERR_HANDLE("panic", "call panic()", + __simulate_panic), +}; + +static long __force_error(struct force_err *force_err, const char *val) +{ + struct force_err_handle *h; + long err = 0; + + pr_emerg("!!!WARN forced error : %s\n", val); + + mutex_lock(&force_err->lock); + + h = __force_err_find_handle_locked(force_err, val); + if (IS_ERR(h)) { + pr_warn("%s is not supported!\n", val); + mutex_unlock(&force_err->lock); + return 0; + } + + h->func(h); + pr_emerg("No such error defined for now!\n"); + + mutex_unlock(&force_err->lock); + return err; +} + +static int force_error(const char *val, const struct kernel_param *kp) +{ + char *__trimed_val, *trimed_val; + int err; + + if (!__debug_is_probed()) + return -EBUSY; + + __trimed_val = kstrdup(val, GFP_KERNEL); + if (!__trimed_val) { + pr_err("Not enough memory!\n"); + return 0; + } + trimed_val = strim(__trimed_val); + + err = (int)__force_error(&sec_debug->force_err, trimed_val); + + kfree(__trimed_val); + + return err; +} +module_param_call(force_error, force_error, NULL, NULL, 0644); + +int sec_force_err_probe_prolog(struct builder *bd) +{ + struct sec_debug_drvdata *drvdata = + container_of(bd, struct sec_debug_drvdata, bd); + struct force_err *force_err = &drvdata->force_err; + + mutex_init(&force_err->lock); + hash_init(force_err->htbl); + + return 0; +} + +void sec_force_err_remove_epilog(struct builder *bd) +{ + struct sec_debug_drvdata *drvdata = + container_of(bd, struct sec_debug_drvdata, bd); + struct force_err *force_err = &drvdata->force_err; + + mutex_destroy(&force_err->lock); +} + +int sec_force_err_build_htbl(struct builder *bd) +{ + struct sec_debug_drvdata *drvdata = + container_of(bd, struct sec_debug_drvdata, bd); + struct force_err *force_err = &drvdata->force_err; + struct force_err_handle *h; + u32 key; + size_t i; + + for (i = 0; i < ARRAY_SIZE(__force_err_default); i++) { + h = &__force_err_default[i]; + + INIT_HLIST_NODE(&h->node); + + key = __force_err_hash(h->val); + hash_add(force_err->htbl, &h->node, key); + } + + return 0; +} + +#if IS_ENABLED(CONFIG_DEBUG_FS) +static void __force_err_dbgfs_show_each_locked(struct seq_file *m, + struct force_err_handle *h) +{ + seq_printf(m, "[<%p>] %s\n", h, h->val); + seq_printf(m, " - msg : %s\n", h->msg); + seq_printf(m, " - func : [<%p>] %ps\n", h->func, h->func); + + seq_puts(m, "\n"); +} + +static int sec_force_err_dbgfs_show_all(struct seq_file *m, void *unsed) +{ + struct force_err *force_err = m->private; + struct force_err_handle *h; + int bkt; + + mutex_lock(&force_err->lock); + hash_for_each(force_err->htbl, bkt, h, node) { + __force_err_dbgfs_show_each_locked(m, h); + } + mutex_unlock(&force_err->lock); + + return 0; +} + +static int sec_force_err_dbgfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, sec_force_err_dbgfs_show_all, + inode->i_private); +} + +static const struct file_operations sec_force_err_dgbfs_fops = { + .open = sec_force_err_dbgfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +int sec_force_err_debugfs_create(struct builder *bd) +{ + struct sec_debug_drvdata *drvdata = + container_of(bd, struct sec_debug_drvdata, bd); + struct force_err *force_err = &drvdata->force_err; + + force_err->dbgfs = debugfs_create_file("sec_force_err", 0440, + NULL, force_err, &sec_force_err_dgbfs_fops); + + return 0; +} + +void sec_force_err_debugfs_remove(struct builder *bd) +{ + struct sec_debug_drvdata *drvdata = + container_of(bd, struct sec_debug_drvdata, bd); + struct force_err *force_err = &drvdata->force_err; + + debugfs_remove(force_err->dbgfs); +} +#else +int sec_force_err_debugfs_create(struct builder *bd) { return 0; } +void sec_force_err_debugfs_remove(struct builder *bd) {} +#endif /* IS_ENABLED(CONFIG_DEBUG_FS) */ diff --git a/drivers/samsung/debug/common/sec_panic_with_reason.c b/drivers/samsung/debug/common/sec_panic_with_reason.c new file mode 100644 index 000000000000..aa9ad39c90c0 --- /dev/null +++ b/drivers/samsung/debug/common/sec_panic_with_reason.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#include "sec_debug.h" + +#define MAX_BUF_SIZE 64 + +static ssize_t panic_with_reason_trigger(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + ssize_t ret; + char panicstr[MAX_BUF_SIZE]; + + if (count > MAX_BUF_SIZE) + return -EINVAL; + + /* copy data to kernel space from user space */ + ret = simple_write_to_buffer(panicstr, sizeof(panicstr), ppos, buf, count); + if (ret < 0) + return ret; + + panicstr[ret] = '\0'; + + panic("%s", panicstr); + + return count; +} + +static const struct file_operations panic_with_reason_fops = { + .write = panic_with_reason_trigger, + .open = simple_open, + .llseek = default_llseek, +}; + + +#if IS_ENABLED(CONFIG_DEBUG_FS) +static int __panic_with_reason_init(struct builder *bd) +{ + struct sec_debug_drvdata *drvdata = + container_of(bd, struct sec_debug_drvdata, bd); + + drvdata->dbgfs_panic = debugfs_create_file("panic_with_reason", 0222, + NULL, NULL, &panic_with_reason_fops); + + return 0; +} + +static void __panic_with_reason_exit(struct builder *bd) +{ + struct sec_debug_drvdata *drvdata = + container_of(bd, struct sec_debug_drvdata, bd); + + debugfs_remove(drvdata->dbgfs_panic); +} +#else +static int __panic_with_reason_init(struct builder *bd) +{ + return 0; +} + +static void __panic_with_reason_exit(struct builder *bd) +{ +} +#endif + +int sec_panic_with_reason_init(struct builder *bd) +{ + if (!IS_ENABLED(CONFIG_SEC_FACTORY)) + return 0; + + return __panic_with_reason_init(bd); +} + +void sec_panic_with_reason_exit(struct builder *bd) +{ + if (!IS_ENABLED(CONFIG_SEC_FACTORY)) + return; + + __panic_with_reason_exit(bd); +} diff --git a/drivers/samsung/debug/common/sec_user_fault.c b/drivers/samsung/debug/common/sec_user_fault.c new file mode 100644 index 000000000000..19af9bb158d6 --- /dev/null +++ b/drivers/samsung/debug/common/sec_user_fault.c @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2019-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include + +#include +#include + +#include "sec_debug.h" + +static unsigned int enable_user = 1; +module_param_named(enable_user, enable_user, uint, 0644); + +static void sec_user_fault_dump(void) +{ + if (sec_debug_is_enabled() && enable_user) + panic("User Fault"); +} + +static ssize_t sec_user_fault_write(struct file *file, + const char __user *buffer, size_t count, loff_t *offs) +{ + char buf[100]; + + if (count > sizeof(buf) - 1) + return -EINVAL; + + if (copy_from_user(buf, buffer, count)) + return -EFAULT; + + buf[count] = '\0'; + if (!strncmp(buf, "dump_user_fault", strlen("dump_user_fault"))) + sec_user_fault_dump(); + + return count; +} + +static const struct proc_ops sec_user_fault_proc_fops = { + .proc_write = sec_user_fault_write, +}; + +static struct proc_dir_entry *proc_user_fault; + +int sec_user_fault_init(struct builder *bd) +{ + proc_user_fault = proc_create("user_fault", 0220, NULL, + &sec_user_fault_proc_fops); + if (!proc_user_fault) + return -ENOMEM; + + return 0; +} + +void sec_user_fault_exit(struct builder *bd) +{ + proc_remove(proc_user_fault); +} diff --git a/drivers/samsung/debug/crashkey/Kconfig b/drivers/samsung/debug/crashkey/Kconfig new file mode 100644 index 000000000000..a8e8ee0eeb5e --- /dev/null +++ b/drivers/samsung/debug/crashkey/Kconfig @@ -0,0 +1,22 @@ +config SEC_CRASHKEY + tristate "SEC Force key crash driver" + depends on SEC_KEY_NOTIFIER + help + TODO: help is not ready. + +config SEC_CRASHKEY_TEST_FOR_ON_DEVICE + tristate "KUnit test for sec_crashkey_test" + depends on KUNIT + depends on SEC_CRASHKEY + help + TODO: Describe config fully. + If you run this test driver on device, SHOULD set this config as 'm' to build test driver modulraly. + +config SEC_CRASHKEY_TEST_FOR_ONLY_UML + tristate "KUnit test for sec_crashkey_test" + depends on KUNIT + depends on UML + depends on SEC_CRASHKEY + help + TODO: Describe config fully. + This CONFIG is recommended to set to y. diff --git a/drivers/samsung/debug/crashkey/Makefile b/drivers/samsung/debug/crashkey/Makefile new file mode 100644 index 000000000000..1c4756a057be --- /dev/null +++ b/drivers/samsung/debug/crashkey/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_SEC_CRASHKEY) += sec_crashkey.o + +GCOV_PROFILE_sec_crashkey.o := $(CONFIG_KUNIT) diff --git a/drivers/samsung/debug/crashkey/sec_crashkey.c b/drivers/samsung/debug/crashkey/sec_crashkey.c new file mode 100644 index 000000000000..ea83a35bc7d1 --- /dev/null +++ b/drivers/samsung/debug/crashkey/sec_crashkey.c @@ -0,0 +1,584 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2019-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "sec_crashkey.h" + +static DEFINE_MUTEX(crashkey_list_lock); +static LIST_HEAD(crashkey_dev_list); + +__ss_static struct crashkey_drvdata *__crashkey_find_by_name_locked( + const char *name, struct list_head *head) +{ + struct crashkey_drvdata *drvdata; + size_t name_len = strlen(name); + + list_for_each_entry(drvdata, head, list) { + size_t len = strnlen(drvdata->name, name_len + 1); + + if (len != name_len) + continue; + + if (!strncmp(drvdata->name, name, name_len)) + return drvdata; + } + + return ERR_PTR(-ENOENT); +} + +__ss_static int __crashkey_add_preparing_panic_locked( + struct crashkey_drvdata *drvdata, struct notifier_block *nb) +{ + struct crashkey_notify *notify = &drvdata->notify; + + return raw_notifier_chain_register(¬ify->list, nb); +} + +int sec_crashkey_add_preparing_panic(struct notifier_block *nb, + const char *name) +{ + struct crashkey_drvdata *drvdata; + int err; + + mutex_lock(&crashkey_list_lock); + + drvdata = __crashkey_find_by_name_locked(name, &crashkey_dev_list); + if (IS_ERR(drvdata)) { + pr_warn("%s is not a valid drvdata device!\n", name); + err = -ENODEV; + goto err_invalid_name; + } + + err = __crashkey_add_preparing_panic_locked(drvdata, nb); + if (err) { + struct device *dev = drvdata->bd.dev; + + dev_warn(dev, "failed to add a notifier for %s (%d)!\n", + name, err); + dev_warn(dev, "Caller is %pS\n", __builtin_return_address(0)); + } + +err_invalid_name: + mutex_unlock(&crashkey_list_lock); + return err; +} +EXPORT_SYMBOL_GPL(sec_crashkey_add_preparing_panic); + +__ss_static int __crashkey_del_preparing_panic_locked( + struct crashkey_drvdata *drvdata, struct notifier_block *nb) +{ + struct crashkey_notify *notify = &drvdata->notify; + + return raw_notifier_chain_unregister(¬ify->list, nb); +} + +int sec_crashkey_del_preparing_panic(struct notifier_block *nb, + const char *name) +{ + struct crashkey_drvdata *drvdata; + int err; + + mutex_lock(&crashkey_list_lock); + + drvdata = __crashkey_find_by_name_locked(name, &crashkey_dev_list); + if (IS_ERR(drvdata)) { + pr_warn("%s is not a valid drvdata device!\n", name); + err = -ENODEV; + goto err_invalid_name; + } + + err = __crashkey_del_preparing_panic_locked(drvdata, nb); + if (err) { + struct device *dev = drvdata->bd.dev; + + dev_warn(dev, "failed to remove a notifier for %s!\n", name); + dev_warn(dev, "Caller is %pS\n", __builtin_return_address(0)); + } + +err_invalid_name: + mutex_unlock(&crashkey_list_lock); + return err; +} +EXPORT_SYMBOL_GPL(sec_crashkey_del_preparing_panic); + +static __always_inline bool __crashkey_is_same_pattern( + struct crashkey_drvdata *drvdata, + const struct sec_key_notifier_param *param) +{ + struct crashkey_kelog *keylog = &drvdata->keylog; + const struct sec_key_notifier_param *desired = + &keylog->desired[keylog->sequence]; + + if (param->keycode == desired->keycode && param->down == desired->down) { + keylog->sequence++; + return true; + } + + return false; +} + +static __always_inline void __crashkey_clear_received_state( + struct crashkey_drvdata *drvdata, + const struct sec_key_notifier_param *param) +{ + struct crashkey_kelog *keylog = &drvdata->keylog; + struct crashkey_timer *timer = &drvdata->timer; + + keylog->sequence = 0; + ratelimit_state_init(&timer->rs, timer->interval, + keylog->nr_pattern - 1); + + /* NOTE: if the current pattern is same as the 1st one of desried, + * advande a 'keylog->sequence'. + */ + if (param && __crashkey_is_same_pattern(drvdata, param)) + __ratelimit(&timer->rs); +} + +static __always_inline void __crashkey_call_crashkey_notify( + struct crashkey_drvdata *drvdata) +{ + struct crashkey_notify *notify = &drvdata->notify; + + raw_notifier_call_chain(¬ify->list, 0, NULL); +} + +__ss_static int __crashkey_notifier_call(struct crashkey_drvdata *drvdata, + const struct sec_key_notifier_param *param) +{ + struct crashkey_kelog *keylog = &drvdata->keylog; + struct crashkey_timer *timer = &drvdata->timer; + + if (!__crashkey_is_same_pattern(drvdata, param)) + goto clear_state; + + if (!timer->interval || !__ratelimit(&timer->rs)) { + if (keylog->sequence == keylog->nr_pattern) + __crashkey_call_crashkey_notify(drvdata); + else if (timer->interval) + goto clear_state; + } + + return NOTIFY_OK; + +clear_state: + __crashkey_clear_received_state(drvdata, param); + return NOTIFY_OK; +} + +static int sec_crashkey_notifier_call(struct notifier_block *this, + unsigned long type, void *data) +{ + struct crashkey_drvdata *drvdata = + container_of(this, struct crashkey_drvdata, nb); + struct sec_key_notifier_param *param = data; + + return __crashkey_notifier_call(drvdata, param); +} + +__ss_static noinline int __crashkey_parse_dt_name(struct builder *bd, + struct device_node *np) +{ + struct crashkey_drvdata *drvdata = + container_of(bd, struct crashkey_drvdata, bd); + + return of_property_read_string(np, "sec,name", &drvdata->name); +} + +__ss_static int __crashkey_test_dt_debug_level(struct crashkey_drvdata *drvdata, + struct device_node *np, unsigned int sec_dbg_level) +{ + struct device *dev = drvdata->bd.dev; + int err; + + err = sec_of_test_debug_level(np, "sec,debug_level", sec_dbg_level); + if (err == -ENOENT) { + dev_warn(dev, "this crashkey_dev (%s) will be enabled all sec debug levels!\n", + drvdata->name); + return 0; + } else if (err < 0) + return -ENODEV; + + return 0; +} + +static noinline int __crashkey_parse_dt_debug_level(struct builder *bd, + struct device_node *np) +{ + struct crashkey_drvdata *drvdata = + container_of(bd, struct crashkey_drvdata, bd); + unsigned int sec_dbg_level = sec_debug_level(); + + return __crashkey_test_dt_debug_level(drvdata, np, sec_dbg_level); +} + +__ss_static noinline int __crashkey_parse_dt_panic_msg(struct builder *bd, + struct device_node *np) +{ + struct crashkey_drvdata *drvdata = + container_of(bd, struct crashkey_drvdata, bd); + struct crashkey_notify *notify = &drvdata->notify; + + return of_property_read_string(np, "sec,panic_msg", ¬ify->panic_msg); +} + +__ss_static noinline int __crashkey_parse_dt_interval(struct builder *bd, + struct device_node *np) +{ + struct crashkey_drvdata *drvdata = + container_of(bd, struct crashkey_drvdata, bd); + struct crashkey_timer *timer = &drvdata->timer; + s32 interval; + int err; + + err = of_property_read_s32(np, "sec,interval", &interval); + if (err) + return -EINVAL; + + timer->interval = (int)interval * HZ; + + return 0; +} + +__ss_static noinline int __crashkey_parse_dt_desired_pattern(struct builder *bd, + struct device_node *np) +{ + struct crashkey_drvdata *drvdata = + container_of(bd, struct crashkey_drvdata, bd); + struct device *dev = bd->dev; + struct crashkey_kelog *keylog = &drvdata->keylog; + struct sec_key_notifier_param *desired; + int nr_pattern; + u32 keycode, down; + int i; + + nr_pattern = of_property_count_u32_elems(np, "sec,desired_pattern"); + nr_pattern /= 2; /* */ + if (nr_pattern <= 0) + return -EINVAL; + + desired = devm_kmalloc_array(dev, + nr_pattern, sizeof(*desired), GFP_KERNEL); + if (!desired) + return -ENOMEM; + + keylog->nr_pattern = (size_t)nr_pattern; + keylog->desired = desired; + + for (i = 0; i < nr_pattern; i++) { + of_property_read_u32_index(np, "sec,desired_pattern", + 2 * i, &keycode); + of_property_read_u32_index(np, "sec,desired_pattern", + 2 * i + 1, &down); + + desired[i].keycode = keycode; + desired[i].down = down; + } + + return 0; +} + +static const struct dt_builder __crashkey_dt_builder[] = { + DT_BUILDER(__crashkey_parse_dt_name), + DT_BUILDER(__crashkey_parse_dt_debug_level), + DT_BUILDER(__crashkey_parse_dt_panic_msg), + DT_BUILDER(__crashkey_parse_dt_interval), + DT_BUILDER(__crashkey_parse_dt_desired_pattern), +}; + +static noinline int __crashkey_parse_dt(struct builder *bd) +{ + return sec_director_parse_dt(bd, __crashkey_dt_builder, + ARRAY_SIZE(__crashkey_dt_builder)); +} + +__ss_static noinline int __crashkey_probe_prolog(struct builder *bd) +{ + struct crashkey_drvdata *drvdata = + container_of(bd, struct crashkey_drvdata, bd); + struct crashkey_notify *notify = &drvdata->notify; + struct crashkey_kelog *keylog = &drvdata->keylog; + struct crashkey_timer *timer = &drvdata->timer; + + RAW_INIT_NOTIFIER_HEAD(¬ify->list); + + ratelimit_state_init(&timer->rs, timer->interval, + keylog->nr_pattern - 1); + + return 0; +} + +__ss_static void __crashkey_add_to_crashkey_dev_list_locked( + struct crashkey_drvdata *drvdata, struct list_head *head) +{ + list_add(&drvdata->list, head); +} + +static int __crashkey_add_to_crashkey_dev_list(struct builder *bd) +{ + struct crashkey_drvdata *drvdata = + container_of(bd, struct crashkey_drvdata, bd); + + mutex_lock(&crashkey_list_lock); + __crashkey_add_to_crashkey_dev_list_locked(drvdata, &crashkey_dev_list); + mutex_unlock(&crashkey_list_lock); + + return 0; +} + +__ss_static void __crashkey_del_from_crashkey_dev_list_locked( + struct crashkey_drvdata *drvdata) +{ + list_del(&drvdata->list); +} + +static void __crashkey_del_from_crashkey_dev_list(struct builder *bd) +{ + struct crashkey_drvdata *drvdata = + container_of(bd, struct crashkey_drvdata, bd); + + mutex_lock(&crashkey_list_lock); + __crashkey_del_from_crashkey_dev_list_locked(drvdata); + mutex_unlock(&crashkey_list_lock); +} + +static int __crashkey_panic_on_matched(struct notifier_block *this, + unsigned long type, void *data) +{ + struct crashkey_notify *notify = + container_of(this, struct crashkey_notify, panic); + + panic("%s", notify->panic_msg); + + return NOTIFY_OK; +} + +static int __crashkey_set_panic_on_matched(struct builder *bd) +{ + struct crashkey_drvdata *drvdata = + container_of(bd, struct crashkey_drvdata, bd); + struct crashkey_notify *notify = &drvdata->notify; + int err; + + /* NOTE: register a calling kernel panic in the end of notifier chain */ + notify->panic.notifier_call = __crashkey_panic_on_matched; + notify->panic.priority = INT_MIN; + + err = sec_crashkey_add_preparing_panic(¬ify->panic, drvdata->name); + if (err) + return err; + + return 0; +} + +static void __crashkey_unset_panic_on_matched(struct builder *bd) +{ + struct crashkey_drvdata *drvdata = + container_of(bd, struct crashkey_drvdata, bd); + struct crashkey_notify *notify = &drvdata->notify; + + sec_crashkey_del_preparing_panic(¬ify->panic, drvdata->name); +} + +__ss_static noinline int __crashkey_init_used_key(struct builder *bd) +{ + struct crashkey_drvdata *drvdata = + container_of(bd, struct crashkey_drvdata, bd); + struct device *dev = bd->dev; + struct crashkey_kelog *keylog = &drvdata->keylog; + const struct sec_key_notifier_param *desired = keylog->desired; + static unsigned int *used_key; + size_t nr_used_key; + bool is_new; + size_t i, j; + + used_key = devm_kmalloc_array(dev, + keylog->nr_pattern, sizeof(*used_key), GFP_KERNEL); + if (!used_key) + return -ENOMEM; + + used_key[0] = desired[0].keycode; + nr_used_key = 1; + + for (i = 1; i < keylog->nr_pattern; i++) { + for (j = 0, is_new = true; j < nr_used_key; j++) { + if (used_key[j] == desired[i].keycode) + is_new = false; + } + + if (is_new) + used_key[nr_used_key++] = desired[i].keycode; + } + + keylog->used_key = used_key; + keylog->nr_used_key = nr_used_key; + + return 0; +} + +static int __crashkey_install_keyboard_notifier(struct builder *bd) +{ + struct crashkey_drvdata *drvdata = + container_of(bd, struct crashkey_drvdata, bd); + struct crashkey_kelog *keylog = &drvdata->keylog; + int err; + + drvdata->nb.notifier_call = sec_crashkey_notifier_call; + err = sec_kn_register_notifier(&drvdata->nb, + keylog->used_key, keylog->nr_used_key); + + return err; +} + +static void __crashkey_uninstall_keyboard_notifier(struct builder *bd) +{ + struct crashkey_drvdata *drvdata = + container_of(bd, struct crashkey_drvdata, bd); + struct crashkey_kelog *keylog = &drvdata->keylog; + + sec_kn_unregister_notifier(&drvdata->nb, + keylog->used_key, keylog->nr_used_key); +} + +static noinline int __crashkey_probe_epilog(struct builder *bd) +{ + struct crashkey_drvdata *drvdata = + container_of(bd, struct crashkey_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + + return 0; +} + +static int __crashkey_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct crashkey_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __crashkey_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct crashkey_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static const struct dev_builder __crashkey_dev_builder[] = { + DEVICE_BUILDER(__crashkey_parse_dt, NULL), + DEVICE_BUILDER(__crashkey_probe_prolog, NULL), + DEVICE_BUILDER(__crashkey_add_to_crashkey_dev_list, + __crashkey_del_from_crashkey_dev_list), + DEVICE_BUILDER(__crashkey_set_panic_on_matched, + __crashkey_unset_panic_on_matched), + DEVICE_BUILDER(__crashkey_init_used_key, NULL), + DEVICE_BUILDER(__crashkey_install_keyboard_notifier, + __crashkey_uninstall_keyboard_notifier), + DEVICE_BUILDER(__crashkey_probe_epilog, NULL), +}; + +static int sec_crashkey_probe(struct platform_device *pdev) +{ + return __crashkey_probe(pdev, __crashkey_dev_builder, + ARRAY_SIZE(__crashkey_dev_builder)); +} + +static int sec_crashkey_remove(struct platform_device *pdev) +{ + return __crashkey_remove(pdev, __crashkey_dev_builder, + ARRAY_SIZE(__crashkey_dev_builder)); +} + +static const struct of_device_id sec_crashkey_match_table[] = { + { .compatible = "samsung,crashkey" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_crashkey_match_table); + +static struct platform_driver sec_crashkey_driver = { + .driver = { + .name = "sec,crashkey", + .of_match_table = of_match_ptr(sec_crashkey_match_table), + }, + .probe = sec_crashkey_probe, + .remove = sec_crashkey_remove, +}; + +static int sec_crashkey_suspend(void) +{ + struct crashkey_drvdata *drvdata; + + list_for_each_entry(drvdata, &crashkey_dev_list, list) { + __crashkey_clear_received_state(drvdata, NULL); + } + + return 0; +} + +static struct syscore_ops sec_crashkey_syscore_ops = { + .suspend = sec_crashkey_suspend, +}; + +static int __init sec_crashkey_init(void) +{ + int err; + + err = platform_driver_register(&sec_crashkey_driver); + if (err) + return err; + + err = __of_platform_early_populate_init(sec_crashkey_match_table); + if (err) + return err; + + register_syscore_ops(&sec_crashkey_syscore_ops); + + return 0; +} +core_initcall(sec_crashkey_init); + +static void __exit sec_crashkey_exit(void) +{ + unregister_syscore_ops(&sec_crashkey_syscore_ops); + platform_driver_unregister(&sec_crashkey_driver); +} +module_exit(sec_crashkey_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("Force key crash driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/debug/crashkey/sec_crashkey.h b/drivers/samsung/debug/crashkey/sec_crashkey.h new file mode 100644 index 000000000000..2fa90e242626 --- /dev/null +++ b/drivers/samsung/debug/crashkey/sec_crashkey.h @@ -0,0 +1,40 @@ +#ifndef __INTERNAL__SEC_CRASHKEY_H__ +#define __INTERNAL__SEC_CRASHKEY_H__ + +#include +#include +#include + +#include +#include + +struct crashkey_kelog { + const struct sec_key_notifier_param *desired; + size_t nr_pattern; + size_t sequence; + unsigned int *used_key; + size_t nr_used_key; +}; + +struct crashkey_timer { + struct ratelimit_state rs; + int interval; +}; + +struct crashkey_notify { + struct raw_notifier_head list; + struct notifier_block panic; + const char *panic_msg; +}; + +struct crashkey_drvdata { + struct builder bd; + struct list_head list; + struct notifier_block nb; + const char *name; + struct crashkey_kelog keylog; + struct crashkey_timer timer; + struct crashkey_notify notify; +}; + +#endif /* __INTERNAL__SEC_CRASHKEY_H__ */ diff --git a/drivers/samsung/debug/crashkey_long/Kconfig b/drivers/samsung/debug/crashkey_long/Kconfig new file mode 100644 index 000000000000..fa796ec3e075 --- /dev/null +++ b/drivers/samsung/debug/crashkey_long/Kconfig @@ -0,0 +1,22 @@ +config SEC_CRASHKEY_LONG + tristate "SEC Long key reset driver" + depends on SEC_KEY_NOTIFIER + help + TODO: help is not ready. + +config SEC_CRASHKEY_LONG_TEST_FOR_ON_DEVICE + tristate "KUnit test for sec_crashkey_long_test" + depends on KUNIT + depends on SEC_CRASHKEY_LONG + help + TODO: Describe config fully. + If you run this test driver on device, SHOULD set this config as 'm' to build test driver modulraly. + +config SEC_CRASHKEY_LONG_TEST_FOR_ONLY_UML + tristate "KUnit test for sec_crashkey_long_test" + depends on KUNIT + depends on UML + depends on SEC_CRASHKEY_LONG + help + TODO: Describe config fully. + This CONFIG is recommended to set to y. diff --git a/drivers/samsung/debug/crashkey_long/Makefile b/drivers/samsung/debug/crashkey_long/Makefile new file mode 100644 index 000000000000..cdbf3e66f4ae --- /dev/null +++ b/drivers/samsung/debug/crashkey_long/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_SEC_CRASHKEY_LONG) += sec_crashkey_long.o + +GCOV_PROFILE_sec_crashkey_long.o := $(CONFIG_KUNIT) diff --git a/drivers/samsung/debug/crashkey_long/sec_crashkey_long.c b/drivers/samsung/debug/crashkey_long/sec_crashkey_long.c new file mode 100644 index 000000000000..3cb5d2b3345f --- /dev/null +++ b/drivers/samsung/debug/crashkey_long/sec_crashkey_long.c @@ -0,0 +1,565 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2019-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "sec_crashkey_long.h" + +static struct crashkey_long_drvdata *crashkey_long; + +static __always_inline bool __crashkey_long_is_probed(void) +{ + return !!crashkey_long; +} + +__ss_static int __crashkey_long_add_preparing_panic( + struct crashkey_long_drvdata *drvdata, struct notifier_block *nb) +{ + struct crashkey_long_notify *notify; + int err; + + notify = &drvdata->notify; + + err = raw_notifier_chain_register(¬ify->list, nb); + if (err) { + struct device *dev = drvdata->bd.dev; + + dev_warn(dev, "failed to add a notifier!\n"); + dev_warn(dev, "Caller is %pS\n", __builtin_return_address(0)); + } + + return err; +} + +int sec_crashkey_long_add_preparing_panic(struct notifier_block *nb) +{ + if (!__crashkey_long_is_probed()) + return -EBUSY; + + return __crashkey_long_add_preparing_panic(crashkey_long, nb); +} +EXPORT_SYMBOL_GPL(sec_crashkey_long_add_preparing_panic); + +__ss_static int __crashkey_long_del_preparing_panic( + struct crashkey_long_drvdata *drvdata, struct notifier_block *nb) +{ + struct crashkey_long_notify *notify; + int err; + + notify = &drvdata->notify; + + err = raw_notifier_chain_unregister(¬ify->list, nb); + if (err) { + struct device *dev = drvdata->bd.dev; + + dev_warn(dev, "failed to remove a notifier for!\n"); + dev_warn(dev, "Caller is %pS\n", __builtin_return_address(0)); + } + + return err; +} + +int sec_crashkey_long_del_preparing_panic(struct notifier_block *nb) +{ + if (!__crashkey_long_is_probed()) + return -EPROBE_DEFER; + + return __crashkey_long_del_preparing_panic(crashkey_long, nb); +} +EXPORT_SYMBOL_GPL(sec_crashkey_long_del_preparing_panic); + +static inline int __crashkey_long_connect_to_input_evnet(void) +{ + struct crashkey_long_keylog *keylog; + unsigned long flags; + int ret = 0; + + keylog = &crashkey_long->keylog; + + spin_lock_irqsave(&crashkey_long->state_lock, flags); + + if (crashkey_long->nb_connected) + goto already_connected; + + ret = sec_kn_register_notifier(&crashkey_long->nb, + keylog->used_key, keylog->nr_used_key); + + crashkey_long->nb_connected = true; + +already_connected: + spin_unlock_irqrestore(&crashkey_long->state_lock, flags); + + return ret; +} + +int sec_crashkey_long_connect_to_input_evnet(void) +{ + if (!__crashkey_long_is_probed()) + return -EBUSY; + + return __crashkey_long_connect_to_input_evnet(); +} +EXPORT_SYMBOL_GPL(sec_crashkey_long_connect_to_input_evnet); + +static inline int __crashkey_long_disconnect_from_input_event(void) +{ + struct crashkey_long_keylog *keylog; + struct crashkey_long_notify *notify; + unsigned long flags; + int ret = 0; + + keylog = &crashkey_long->keylog; + notify = &crashkey_long->notify; + + spin_lock_irqsave(&crashkey_long->state_lock, flags); + + if (crashkey_long->nb_connected) + goto already_disconnected; + + ret = sec_kn_unregister_notifier(&crashkey_long->nb, + keylog->used_key, keylog->nr_used_key); + + crashkey_long->nb_connected = false; + bitmap_zero(keylog->bitmap_received, keylog->sz_bitmap); + +already_disconnected: + spin_unlock_irqrestore(&crashkey_long->state_lock, flags); + + del_timer(¬ify->tl); + + return ret; +} + +int sec_crashkey_long_disconnect_from_input_event(void) +{ + if (!__crashkey_long_is_probed()) + return -EBUSY; + + return __crashkey_long_disconnect_from_input_event(); +} +EXPORT_SYMBOL_GPL(sec_crashkey_long_disconnect_from_input_event); + +static void sec_crashkey_long_do_on_expired(struct timer_list *tl) +{ + struct crashkey_long_notify *notify = + container_of(tl, struct crashkey_long_notify, tl); + + raw_notifier_call_chain(¬ify->list, + SEC_CRASHKEY_LONG_NOTIFY_TYPE_EXPIRED, NULL); +} + +__ss_static bool __crashkey_long_is_mached_received_pattern( + struct crashkey_long_drvdata *drvdata) +{ + struct crashkey_long_keylog *keylog = &drvdata->keylog; + size_t i; + + for (i = 0; i < keylog->nr_used_key; i++) { + if (!test_bit(keylog->used_key[i], keylog->bitmap_received)) + return false; + } + + return true; +} + +__ss_static void __crashkey_long_invoke_notifier_on_matched( + struct crashkey_long_notify *notify) +{ + raw_notifier_call_chain(¬ify->list, + SEC_CRASHKEY_LONG_NOTIFY_TYPE_MATCHED, NULL); +} + +__ss_static void __crashkey_long_invoke_timer_on_matched( + struct crashkey_long_notify *notify) +{ + unsigned long expires; + struct crashkey_long_drvdata *drvdata; + + if (timer_pending(¬ify->tl)) + return; + + expires = jiffies + msecs_to_jiffies(notify->expire_msec); + drvdata = container_of(notify, struct crashkey_long_drvdata, notify); + + timer_setup(¬ify->tl, sec_crashkey_long_do_on_expired, 0); + mod_timer(¬ify->tl, expires); + dev_info(drvdata->bd.dev, "long key timer - start"); +} + +static void __crashkey_long_on_matched(struct crashkey_long_drvdata *drvdata) +{ + struct crashkey_long_notify *notify = &drvdata->notify; + + __crashkey_long_invoke_notifier_on_matched(notify); + + if (likely(!sec_debug_is_enabled())) + return; + + __crashkey_long_invoke_timer_on_matched(notify); +} + +__ss_static void __crashkey_long_invoke_notifier_on_unmatched( + struct crashkey_long_notify *notify) +{ + raw_notifier_call_chain(¬ify->list, + SEC_CRASHKEY_LONG_NOTIFY_TYPE_UNMATCHED, NULL); +} + +__ss_static void __crashkey_long_invoke_timer_on_unmatched( + struct crashkey_long_notify *notify) +{ + if (!timer_pending(¬ify->tl)) + return; + + del_timer(¬ify->tl); + pr_info("long key timer - cancel"); +} + +static void __crashkey_long_on_unmatched(struct crashkey_long_drvdata *drvdata) +{ + struct crashkey_long_notify *notify = &drvdata->notify; + + __crashkey_long_invoke_notifier_on_unmatched(notify); + __crashkey_long_invoke_timer_on_unmatched(notify); +} + +__ss_static void __crashkey_long_update_bitmap_received( + struct crashkey_long_drvdata* drvdata, + struct sec_key_notifier_param *param) +{ + struct crashkey_long_keylog *keylog = &drvdata->keylog; + + if (param->down) + set_bit(param->keycode, keylog->bitmap_received); + else + clear_bit(param->keycode, keylog->bitmap_received); +} + +static int sec_crashkey_long_notifier_call(struct notifier_block *this, + unsigned long type, void *data) +{ + struct crashkey_long_drvdata *drvdata = + container_of(this, struct crashkey_long_drvdata, nb); + struct sec_key_notifier_param *param = data; + unsigned long flags; + + spin_lock_irqsave(&drvdata->state_lock, flags); + + if (!drvdata->nb_connected) { + spin_unlock_irqrestore(&drvdata->state_lock, flags); + return NOTIFY_DONE; + } + + __crashkey_long_update_bitmap_received(drvdata, param); + + spin_unlock_irqrestore(&drvdata->state_lock, flags); + + if (__crashkey_long_is_mached_received_pattern(drvdata)) + __crashkey_long_on_matched(drvdata); + else + __crashkey_long_on_unmatched(drvdata); + + return NOTIFY_OK; +} + +__ss_static noinline int __crashkey_long_parse_dt_panic_msg(struct builder *bd, + struct device_node *np) +{ + struct crashkey_long_drvdata *drvdata = + container_of(bd, struct crashkey_long_drvdata, bd); + struct crashkey_long_notify *notify = &drvdata->notify; + + return of_property_read_string(np, "sec,panic_msg", ¬ify->panic_msg); +} + +__ss_static noinline int __crashkey_long_parse_dt_expire_msec(struct builder *bd, + struct device_node *np) +{ + struct crashkey_long_drvdata *drvdata = + container_of(bd, struct crashkey_long_drvdata, bd); + struct crashkey_long_notify *notify = &drvdata->notify; + u32 expire_msec; + int err; + + err = of_property_read_u32(np, "sec,expire_msec", &expire_msec); + if (err) + return -EINVAL; + + notify->expire_msec = (unsigned int)expire_msec; + + return 0; +} + +__ss_static noinline int __crashkey_long_parse_dt_used_key(struct builder *bd, + struct device_node *np) +{ + struct crashkey_long_drvdata *drvdata = + container_of(bd, struct crashkey_long_drvdata, bd); + struct crashkey_long_keylog *keylog = &drvdata->keylog; + struct device *dev = bd->dev; + int nr_used_key; + unsigned int *used_key; + u32 event; + int i; + + nr_used_key = of_property_count_u32_elems(np, "sec,used_key"); + if (nr_used_key <= 0) { + dev_err(dev, "reset-key event list is not specified!\n"); + return -ENODEV; + } + + used_key = devm_kcalloc(dev, nr_used_key, sizeof(*used_key), + GFP_KERNEL); + if (!used_key) + return -ENOMEM; + + for (i = 0; i < nr_used_key; i++) { + of_property_read_u32_index(np, "sec,used_key", i, &event); + + used_key[i] = event; + } + + keylog->used_key = used_key; + keylog->nr_used_key = (size_t)nr_used_key; + + return 0; +} + +static const struct dt_builder __crashkey_long_dt_builder[] = { + DT_BUILDER(__crashkey_long_parse_dt_panic_msg), + DT_BUILDER(__crashkey_long_parse_dt_expire_msec), + DT_BUILDER(__crashkey_long_parse_dt_used_key), +}; + +static noinline int __crashkey_long_parse_dt(struct builder *bd) +{ + return sec_director_parse_dt(bd, __crashkey_long_dt_builder, + ARRAY_SIZE(__crashkey_long_dt_builder)); +} + +__ss_static noinline int __crashkey_long_probe_prolog(struct builder *bd) +{ + struct crashkey_long_drvdata *drvdata = + container_of(bd, struct crashkey_long_drvdata, bd); + struct crashkey_long_notify *notify = &drvdata->notify; + + RAW_INIT_NOTIFIER_HEAD(¬ify->list); + spin_lock_init(&drvdata->state_lock); + + return 0; +} + +__ss_static noinline int __crashkey_long_alloc_bitmap_received(struct builder *bd) +{ + struct crashkey_long_drvdata *drvdata = + container_of(bd, struct crashkey_long_drvdata, bd); + struct crashkey_long_keylog *keylog = &drvdata->keylog; + unsigned long *bitmap_received; + + bitmap_received = devm_bitmap_zalloc(bd->dev, KEY_MAX, GFP_KERNEL); + if (!bitmap_received) + return -ENOMEM; + + keylog->bitmap_received = bitmap_received; + keylog->sz_bitmap = BITS_TO_LONGS(KEY_MAX) * sizeof(unsigned long); + + return 0; +} + +static int __crashkey_long_panic_on_expired(struct notifier_block *this, + unsigned long type, void *v) +{ + struct crashkey_long_notify *notify = + container_of(this, struct crashkey_long_notify, panic); + + if (type != SEC_CRASHKEY_LONG_NOTIFY_TYPE_EXPIRED) + return NOTIFY_DONE; + + pr_err("*** Force trigger kernel panic before triggering hard reset ***\n"); + + panic("%s", notify->panic_msg); + + return NOTIFY_OK; +} + +static int __crashkey_long_set_panic_on_expired(struct builder *bd) +{ + struct crashkey_long_drvdata *drvdata = + container_of(bd, struct crashkey_long_drvdata, bd); + struct crashkey_long_notify *notify = &drvdata->notify; + int err; + + /* NOTE: register a calling kernel panic in the end of notifier chain */ + notify->panic.notifier_call = __crashkey_long_panic_on_expired; + notify->panic.priority = INT_MIN; + + err = __crashkey_long_add_preparing_panic(drvdata, ¬ify->panic); + if (err) + return err; + + return 0; +} + +static void __crashkey_long_unset_panic_on_expired(struct builder *bd) +{ + struct crashkey_long_drvdata *drvdata = + container_of(bd, struct crashkey_long_drvdata, bd); + struct crashkey_long_notify *notify = &drvdata->notify; + + __crashkey_long_del_preparing_panic(drvdata, ¬ify->panic); +} + +static int __crashkey_long_install_keyboard_notifier(struct builder *bd) +{ + struct crashkey_long_drvdata *drvdata = + container_of(bd, struct crashkey_long_drvdata, bd); + struct crashkey_long_keylog *keylog = &drvdata->keylog; + int err; + + drvdata->nb.notifier_call = sec_crashkey_long_notifier_call; + err = sec_kn_register_notifier(&drvdata->nb, + keylog->used_key, keylog->nr_used_key); + if (err) + return err; + + drvdata->nb_connected = true; + + return 0; +} + +static void __crashkey_long_uninstall_keyboard_notifier(struct builder *bd) +{ + struct crashkey_long_drvdata *drvdata = + container_of(bd, struct crashkey_long_drvdata, bd); + struct crashkey_long_keylog *keylog = &drvdata->keylog; + + sec_kn_unregister_notifier(&drvdata->nb, + keylog->used_key, keylog->nr_used_key); +} + +static int __crashkey_long_probe_epilog(struct builder *bd) +{ + struct crashkey_long_drvdata *drvdata = + container_of(bd, struct crashkey_long_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + crashkey_long = drvdata; /* set a singleton */ + + return 0; +} + +static void __crashkey_long_remove_prolog(struct builder *bd) +{ + /* FIXME: This is not a graceful exit. */ + crashkey_long = NULL; +} + +static int __crashkey_long_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct crashkey_long_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __crashkey_long_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct crashkey_long_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static const struct dev_builder __crashkey_long_dev_builder[] = { + DEVICE_BUILDER(__crashkey_long_parse_dt, NULL), + DEVICE_BUILDER(__crashkey_long_probe_prolog, NULL), + DEVICE_BUILDER(__crashkey_long_alloc_bitmap_received, NULL), + DEVICE_BUILDER(__crashkey_long_set_panic_on_expired, + __crashkey_long_unset_panic_on_expired), + DEVICE_BUILDER(__crashkey_long_install_keyboard_notifier, + __crashkey_long_uninstall_keyboard_notifier), + DEVICE_BUILDER(__crashkey_long_probe_epilog, + __crashkey_long_remove_prolog), +}; + +static int sec_crashkey_long_probe(struct platform_device *pdev) +{ + return __crashkey_long_probe(pdev, __crashkey_long_dev_builder, + ARRAY_SIZE(__crashkey_long_dev_builder)); +} + +static int sec_crashkey_long_remove(struct platform_device *pdev) +{ + return __crashkey_long_remove(pdev, __crashkey_long_dev_builder, + ARRAY_SIZE(__crashkey_long_dev_builder)); +} + +static const struct of_device_id sec_crashkeylong_match_table[] = { + { .compatible = "samsung,crashkey-long" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_crashkeylong_match_table); + +static struct platform_driver sec_crashkey_long_driver = { + .driver = { + .name = "sec,crashkey-long", + .of_match_table = of_match_ptr(sec_crashkeylong_match_table), + }, + .probe = sec_crashkey_long_probe, + .remove = sec_crashkey_long_remove, +}; + +static int __init sec_crashkey_long_init(void) +{ + int err; + + err = platform_driver_register(&sec_crashkey_long_driver); + if (err) + return err; + + err = __of_platform_early_populate_init(sec_crashkeylong_match_table); + if (err) + return err; + + return 0; +} +core_initcall(sec_crashkey_long_init); + +static void __exit sec_crashkey_long_exit(void) +{ + platform_driver_unregister(&sec_crashkey_long_driver); +} +module_exit(sec_crashkey_long_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("Long key reset driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/debug/crashkey_long/sec_crashkey_long.h b/drivers/samsung/debug/crashkey_long/sec_crashkey_long.h new file mode 100644 index 000000000000..68b5921ea876 --- /dev/null +++ b/drivers/samsung/debug/crashkey_long/sec_crashkey_long.h @@ -0,0 +1,34 @@ +#ifndef __INTERNAL__SEC_CRASHKEY_LONG_H__ +#define __INTERNAL__SEC_CRASHKEY_LONG_H__ + +#include +#include + +#include +#include + +struct crashkey_long_keylog { + unsigned long *bitmap_received; + size_t sz_bitmap; + const unsigned int *used_key; + size_t nr_used_key; +}; + +struct crashkey_long_notify { + struct timer_list tl; + unsigned int expire_msec; + struct raw_notifier_head list; + struct notifier_block panic; + const char *panic_msg; +}; + +struct crashkey_long_drvdata { + struct builder bd; + spinlock_t state_lock; /* key_notifer and input events */ + bool nb_connected; + struct notifier_block nb; + struct crashkey_long_keylog keylog; + struct crashkey_long_notify notify; +}; + +#endif /* __INTERNAL__SEC_CRASHKEY_LONG_H__ */ diff --git a/drivers/samsung/debug/debug_region/Kconfig b/drivers/samsung/debug/debug_region/Kconfig new file mode 100644 index 000000000000..c4d4a059ad75 --- /dev/null +++ b/drivers/samsung/debug/debug_region/Kconfig @@ -0,0 +1,5 @@ +config SEC_DEBUG_REGION + tristate "SEC Memory pool for debugging features" + select GENERIC_ALLOCATOR + help + TODO: help is not ready. diff --git a/drivers/samsung/debug/debug_region/Makefile b/drivers/samsung/debug/debug_region/Makefile new file mode 100644 index 000000000000..01290fc5f1a7 --- /dev/null +++ b/drivers/samsung/debug/debug_region/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_SEC_DEBUG_REGION) += sec_debug_region.o + +sec_debug_region-objs := sec_debug_region_main.o \ + sec_debug_region_pool.o \ + sec_debug_region_gen_pool.o \ + sec_debug_region_cma_pool.o diff --git a/drivers/samsung/debug/debug_region/sec_debug_region.h b/drivers/samsung/debug/debug_region/sec_debug_region.h new file mode 100644 index 000000000000..ce646319bf76 --- /dev/null +++ b/drivers/samsung/debug/debug_region/sec_debug_region.h @@ -0,0 +1,58 @@ +#ifndef __INTERNAL__SEC_DEBUG_REGION_H__ +#define __INTERNAL__SEC_DEBUG_REGION_H__ + +#include +#include +#include +#include + +#include + +struct dbg_region_drvdata; + +struct dbg_region_pool { + int (*probe)(struct dbg_region_drvdata *); + void (*remove)(struct dbg_region_drvdata *); + void *(*alloc)(struct dbg_region_drvdata *, size_t, phys_addr_t *); + void (*free)(struct dbg_region_drvdata *, size_t, void *, phys_addr_t); +}; + +struct dbg_region_root { + struct list_head clients; + uint32_t magic; + phys_addr_t __root; /* physical address of myself */ +} __packed __aligned(1); + +enum { + RMEM_TYPE_NOMAP = 0, + RMEM_TYPE_MAPPED, + RMEM_TYPE_REUSABLE, +}; + +struct dbg_region_drvdata { + struct builder bd; + struct reserved_mem *rmem; + unsigned int rmem_type; + phys_addr_t phys; + size_t size; + struct mutex lock; + unsigned long virt; + const struct dbg_region_pool *pool; + void *private; + struct dbg_region_root *root; +#if IS_ENABLED(CONFIG_DEBUG_FS) + struct dentry *dbgfs; +#endif +}; + +/* sec_debug_pool.c */ +extern int __dbg_region_pool_init(struct builder *bd); +extern void __dbg_region_pool_exit(struct builder *bd); + +/* sec_debug_region_gen_pool.c */ +extern const struct dbg_region_pool *__dbg_region_gen_pool_creator(void); + +/* sec_debug_region_cma_pool.c */ +extern const struct dbg_region_pool *__dbg_region_cma_pool_creator(void); + +#endif /* __INTERNAL__SEC_DEBUG_REGION_H__ */ diff --git a/drivers/samsung/debug/debug_region/sec_debug_region_cma_pool.c b/drivers/samsung/debug/debug_region/sec_debug_region_cma_pool.c new file mode 100644 index 000000000000..f190b41b0199 --- /dev/null +++ b/drivers/samsung/debug/debug_region/sec_debug_region_cma_pool.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2022-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include + +#include "sec_debug_region.h" + +static int dbg_region_cma_pool_probe(struct dbg_region_drvdata *drvdata) +{ + struct device *dev = drvdata->bd.dev; + struct device_node *np = dev_of_node(dev); + int err; + + err = of_reserved_mem_device_init_by_idx(dev, np, 0); + if (err) { + dev_err(dev, "failed to initialize reserved mem (%d)\n", err); + return err; + } + + return 0; +} + +static void dbg_region_cma_pool_rmove(struct dbg_region_drvdata *drvdata) +{ +} + +static void *dbg_region_cma_pool_alloc(struct dbg_region_drvdata *drvdata, + size_t size, phys_addr_t *__phys) +{ + struct device *dev = drvdata->bd.dev; + void *vaddr; + dma_addr_t phys; + + vaddr = dma_alloc_wc(dev, PAGE_ALIGN(size), &phys, GFP_KERNEL); + if (!vaddr) + return ERR_PTR(-ENOMEM); + + *__phys = (phys_addr_t)phys; + + return vaddr; +} + +static void dbg_region_cma_pool_free(struct dbg_region_drvdata *drvdata, + size_t size, void *vaddr, phys_addr_t phys) +{ + struct device *dev = drvdata->bd.dev; + + dma_free_wc(dev, PAGE_ALIGN(size), vaddr, (dma_addr_t)phys); +} + +static const struct dbg_region_pool dbg_region_cma_pool = { + .probe = dbg_region_cma_pool_probe, + .remove = dbg_region_cma_pool_rmove, + .alloc = dbg_region_cma_pool_alloc, + .free = dbg_region_cma_pool_free, +}; + +const struct dbg_region_pool *__dbg_region_cma_pool_creator(void) +{ + return &dbg_region_cma_pool; +} diff --git a/drivers/samsung/debug/debug_region/sec_debug_region_gen_pool.c b/drivers/samsung/debug/debug_region/sec_debug_region_gen_pool.c new file mode 100644 index 000000000000..4a1af644753d --- /dev/null +++ b/drivers/samsung/debug/debug_region/sec_debug_region_gen_pool.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2022-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include + +#include "sec_debug_region.h" + +static int __dbg_region_prepare_pool(struct dbg_region_drvdata *drvdata) +{ + struct device *dev = drvdata->bd.dev; + unsigned int rmem_type = drvdata->rmem_type; + void __iomem *virt; + + if (rmem_type == RMEM_TYPE_NOMAP) + virt = devm_ioremap_wc(dev, drvdata->phys, drvdata->size); + else + virt = phys_to_virt(drvdata->phys); + + if (!virt) + return -EFAULT; + + drvdata->virt = (unsigned long)virt; + + return 0; +} + +static int __dbg_region_gen_pool_create(struct dbg_region_drvdata *drvdata) +{ + struct device *dev = drvdata->bd.dev; + const int min_alloc_order = ilog2(cache_line_size()); + struct gen_pool *pool; + int err; + + pool = devm_gen_pool_create(dev, min_alloc_order, -1, "sec_dbg_region"); + if (IS_ERR(pool)) { + err = PTR_ERR(pool); + goto err_gen_pool_create; + } + + err = gen_pool_add_virt(pool, drvdata->virt, drvdata->phys, + drvdata->size, -1); + if (err) { + err = -ENOMEM; + goto err_gen_pool_add_virt; + } + + drvdata->private = pool; + + return 0; + +err_gen_pool_add_virt: + gen_pool_destroy(pool); +err_gen_pool_create: + return err; +} + +static int dbg_region_gen_pool_probe(struct dbg_region_drvdata *drvdata) +{ + int err; + + err = __dbg_region_prepare_pool(drvdata); + if (err) + return err; + + err = __dbg_region_gen_pool_create(drvdata); + if (err) + return err; + + return 0; +} + +static void dbg_region_gen_pool_remove(struct dbg_region_drvdata *drvdata) +{ + struct gen_pool *pool = drvdata->private; + + gen_pool_destroy(pool); +} + +static void *dbg_region_gen_pool_alloc(struct dbg_region_drvdata *drvdata, + size_t size, phys_addr_t *phys) +{ + struct gen_pool *pool = drvdata->private; + unsigned long virt; + + virt = gen_pool_alloc(pool, size); + if (!virt) + return ERR_PTR(-ENOMEM); + + *phys = gen_pool_virt_to_phys(pool, virt); + + return (void *)virt; +} + +static void dbg_region_gen_pool_free(struct dbg_region_drvdata *drvdata, + size_t size, void *virt, phys_addr_t phys) +{ + struct gen_pool *pool = drvdata->private; + + gen_pool_free(pool, (unsigned long)virt, size); +} + +static const struct dbg_region_pool dbg_region_gen_pool = { + .probe = dbg_region_gen_pool_probe, + .remove = dbg_region_gen_pool_remove, + .alloc = dbg_region_gen_pool_alloc, + .free = dbg_region_gen_pool_free, +}; + +const struct dbg_region_pool *__dbg_region_gen_pool_creator(void) +{ + return &dbg_region_gen_pool; +} diff --git a/drivers/samsung/debug/debug_region/sec_debug_region_main.c b/drivers/samsung/debug/debug_region/sec_debug_region_main.c new file mode 100644 index 000000000000..c74c87bc5eab --- /dev/null +++ b/drivers/samsung/debug/debug_region/sec_debug_region_main.c @@ -0,0 +1,548 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2017-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "sec_debug_region.h" + +static struct dbg_region_drvdata *dbg_region; + +static __always_inline bool __dbg_region_is_probed(void) +{ + return !!dbg_region; +} + +static void *__dbg_region_alloc(struct dbg_region_drvdata *drvdata, + size_t size, phys_addr_t *phys) +{ + const struct dbg_region_pool *pool = drvdata->pool; + + return pool->alloc(drvdata, size, phys); +} + +static void __dbg_region_free(struct dbg_region_drvdata *drvdata, + size_t size, void *virt, phys_addr_t phys) +{ + const struct dbg_region_pool *pool = drvdata->pool; + + return pool->free(drvdata, size, virt, phys); +} + +static struct sec_dbg_region_client *__dbg_region_find_locked( + struct dbg_region_drvdata *drvdata, uint32_t unique_id) +{ + struct sec_dbg_region_client *client; + + list_for_each_entry(client, &drvdata->root->clients, list) { + if (client->unique_id == unique_id) + return client; + } + + return ERR_PTR(-ENOENT); +} + +static struct sec_dbg_region_client *__dbg_region_alloc_client_data( + struct dbg_region_drvdata *drvdata) +{ + struct sec_dbg_region_client *client; + phys_addr_t __client; + + client = __dbg_region_alloc(drvdata, + sizeof(struct sec_dbg_region_client), &__client); + if (IS_ERR_OR_NULL(client)) + return ERR_PTR(-ENOMEM); + + client->__client = __client; + + return client; +} + +static void __dbg_region_free_client_data(struct dbg_region_drvdata *drvdata, + struct sec_dbg_region_client *client) +{ + __dbg_region_free(drvdata, sizeof(*client), client, client->__client); +} + +static void *__dbg_region_alloc_client_memory(struct dbg_region_drvdata *drvdata, + size_t size, phys_addr_t *phys) +{ + void *virt; + + virt = __dbg_region_alloc(drvdata, size, phys); + if (IS_ERR_OR_NULL(virt)) + return ERR_PTR(-ENOMEM); + + memset_io(virt, 0x0, size); + + return virt; +} + +static void __dbg_region_free_client_memory(struct dbg_region_drvdata *drvdata, + struct sec_dbg_region_client *client) +{ + __dbg_region_free(drvdata, client->size, + (void *)client->virt, client->phys); +} + +static inline struct sec_dbg_region_client *__dbg_region_alloc_client( + struct dbg_region_drvdata *drvdata, + uint32_t unique_id, size_t size) +{ + struct sec_dbg_region_client *client; + void *virt; + int err; + + mutex_lock(&drvdata->lock); + + client = __dbg_region_find_locked(drvdata, unique_id); + if (!IS_ERR(client)) { + err = -EINVAL; + goto err_duplicated_unique_id; + } + + client = __dbg_region_alloc_client_data(drvdata); + if (IS_ERR_OR_NULL(client)) { + err = PTR_ERR(client); + goto err_client_data; + } + + virt = __dbg_region_alloc_client_memory(drvdata, size, &client->phys); + if (IS_ERR_OR_NULL(virt)) { + err = -ENOMEM; + goto err_client_memory; + } + + client->virt = (unsigned long)virt; + client->magic = SEC_DBG_REGION_CLIENT_MAGIC; + client->unique_id = unique_id; + client->size = size; + client->name = NULL; /* optional for each client */ + + list_add(&client->list, &drvdata->root->clients); + + mutex_unlock(&drvdata->lock); + return client; + +err_client_memory: + __dbg_region_free_client_data(drvdata, client); +err_client_data: +err_duplicated_unique_id: + mutex_unlock(&drvdata->lock); + return ERR_PTR(err); +} + +struct sec_dbg_region_client *sec_dbg_region_alloc(uint32_t unique_id, + size_t size) +{ + if (!__dbg_region_is_probed()) + return ERR_PTR(-EBUSY); + + return __dbg_region_alloc_client(dbg_region, unique_id, size); +} +EXPORT_SYMBOL_GPL(sec_dbg_region_alloc); + +static inline int __dbg_region_free_client(struct dbg_region_drvdata *drvdata, + struct sec_dbg_region_client *client) +{ + mutex_lock(&drvdata->lock); + + if (client->magic != SEC_DBG_REGION_CLIENT_MAGIC) { + mutex_unlock(&drvdata->lock); + + BUG(); + } else { + list_del(&client->list); + client->magic = 0x0; + mutex_unlock(&drvdata->lock); + + __dbg_region_free_client_memory(drvdata, client); + __dbg_region_free_client_data(drvdata, client); + } + + return 0; +} + +int sec_dbg_region_free(struct sec_dbg_region_client *client) +{ + if (!__dbg_region_is_probed()) + return -EBUSY; + + return __dbg_region_free_client(dbg_region, client); +} +EXPORT_SYMBOL_GPL(sec_dbg_region_free); + +static inline const struct sec_dbg_region_client *__dbg_region_find( + struct dbg_region_drvdata *drvdata, uint32_t unique_id) +{ + const struct sec_dbg_region_client *client; + + mutex_lock(&drvdata->lock); + client = __dbg_region_find_locked(drvdata, unique_id); + mutex_unlock(&drvdata->lock); + + return client; +} + +const struct sec_dbg_region_client *sec_dbg_region_find(uint32_t unique_id) +{ + if (!__dbg_region_is_probed()) + return ERR_PTR(-EBUSY); + + return __dbg_region_find(dbg_region, unique_id); +} +EXPORT_SYMBOL_GPL(sec_dbg_region_find); + +static noinline int __dbg_region_parse_dt_memory_region(struct builder *bd, + struct device_node *np) +{ + struct dbg_region_drvdata *drvdata = + container_of(bd, struct dbg_region_drvdata, bd); + struct device *dev = bd->dev; + struct device_node *mem_np; + struct reserved_mem *rmem; + + mem_np = of_parse_phandle(np, "memory-region", 0); + if (!mem_np) + return -EINVAL; + + rmem = of_reserved_mem_lookup(mem_np); + if (!rmem) { + dev_warn(dev, "failed to get a reserved memory (%s)\n", + mem_np->name); + return -EFAULT; + } + + drvdata->rmem = rmem; + + return 0; +} + +static bool __dbg_region_is_in_reserved_mem_bound( + const struct reserved_mem *rmem, + phys_addr_t base, phys_addr_t size) +{ + phys_addr_t rmem_base = rmem->base; + phys_addr_t rmem_end = rmem_base + rmem->size - 1; + phys_addr_t end = base + size - 1; + + if ((base >= rmem_base) && (end <= rmem_end)) + return true; + + return false; +} + +static int __dbg_region_use_partial_reserved_mem( + struct dbg_region_drvdata *drvdata, struct device_node *np) +{ + struct reserved_mem *rmem = drvdata->rmem; + phys_addr_t base; + phys_addr_t size; + int err; + + err = sec_of_parse_reg_prop(np, &base, &size); + if (err) + return err; + + if (!__dbg_region_is_in_reserved_mem_bound(rmem, base, size)) + return -ERANGE; + + drvdata->phys = base; + drvdata->size = size; + + return 0; +} + +static int __dbg_region_use_entire_reserved_mem( + struct dbg_region_drvdata *drvdata) +{ + struct reserved_mem *rmem = drvdata->rmem; + + drvdata->phys = rmem->base; + drvdata->size = rmem->size; + + return 0; +} + +static noinline int __dbg_region_parse_dt_partial_reserved_mem(struct builder *bd, + struct device_node *np) +{ + struct dbg_region_drvdata *drvdata = + container_of(bd, struct dbg_region_drvdata, bd); + int err; + + if (of_property_read_bool(np, "sec,use-partial_reserved_mem")) + err = __dbg_region_use_partial_reserved_mem(drvdata, np); + else + err = __dbg_region_use_entire_reserved_mem(drvdata); + + if (err) + return -EFAULT; + + return 0; +} + +static noinline int __dbg_region_parse_dt_set_rmem_type(struct builder *bd, + struct device_node *np) +{ + struct dbg_region_drvdata *drvdata = + container_of(bd, struct dbg_region_drvdata, bd); + struct device_node *mem_np; + + mem_np = of_parse_phandle(np, "memory-region", 0); + if (!mem_np) + return -EINVAL; + + if (of_property_read_bool(mem_np, "no-map")) + drvdata->rmem_type = RMEM_TYPE_NOMAP; + else if (of_property_read_bool(mem_np, "reusable")) + drvdata->rmem_type = RMEM_TYPE_REUSABLE; + else + drvdata->rmem_type = RMEM_TYPE_MAPPED; + + return 0; +} + +static const struct dt_builder __dbg_region_dt_builder[] = { + DT_BUILDER(__dbg_region_parse_dt_memory_region), + DT_BUILDER(__dbg_region_parse_dt_partial_reserved_mem), + DT_BUILDER(__dbg_region_parse_dt_set_rmem_type), +}; + +static noinline int __dbg_region_parse_dt(struct builder *bd) +{ + return sec_director_parse_dt(bd, __dbg_region_dt_builder, + ARRAY_SIZE(__dbg_region_dt_builder)); +} + +static noinline int __dbg_region_probe_prolog(struct builder *bd) +{ + struct dbg_region_drvdata *drvdata = + container_of(bd, struct dbg_region_drvdata, bd); + + mutex_init(&drvdata->lock); + + return 0; +} + +static noinline void __dbg_region_remove_epilog(struct builder *bd) +{ + struct dbg_region_drvdata *drvdata = + container_of(bd, struct dbg_region_drvdata, bd); + + mutex_destroy(&drvdata->lock); +} + +static noinline int __dbg_region_create_root(struct builder *bd) +{ + struct dbg_region_drvdata *drvdata = + container_of(bd, struct dbg_region_drvdata, bd); + struct dbg_region_root *root; + phys_addr_t __root; + + root = __dbg_region_alloc(drvdata, + sizeof(struct dbg_region_root), &__root); + pr_debug("root = %px\n", root); + if (IS_ERR_OR_NULL(root)) + return -ENOMEM; + + root->__root = __root; + root->magic = SEC_DBG_REGION_ROOT_MAGIC; + INIT_LIST_HEAD(&root->clients); + + drvdata->root = root; + + return 0; +} + +static noinline void __dbg_region_delete_root(struct builder *bd) +{ + struct dbg_region_drvdata *drvdata = + container_of(bd, struct dbg_region_drvdata, bd); + struct dbg_region_root *root = drvdata->root; + + __dbg_region_free(drvdata, sizeof(struct dbg_region_root), + root, root->__root); +} + +#if IS_ENABLED(CONFIG_DEBUG_FS) +static int sec_dbg_region_dbgfs_show_all(struct seq_file *m, void *unsed) +{ + struct dbg_region_drvdata *drvdata = m->private; + struct dbg_region_root *root = drvdata->root; + const struct sec_dbg_region_client *client; + size_t sz_used = sizeof(*root); + + seq_printf(m, "%pa++%pa - %s\n", + &drvdata->phys, &drvdata->size, + dev_name(drvdata->bd.dev)); + + seq_puts(m, "\nclients:\n"); + + mutex_lock(&drvdata->lock); + list_for_each_entry(client, &root->clients, list) { + seq_printf(m, "%pa++%pa %7zu - %s (0x%08X)\n", + &client->phys, &client->size, client->size, + client->name, client->unique_id); + sz_used += sizeof(*client); + sz_used += client->size; + } + mutex_unlock(&drvdata->lock); + + seq_puts(m, "\n"); + seq_printf(m, " - Total : %zu\n", drvdata->size); + seq_printf(m, " - Used : %zu\n", sz_used); + seq_puts(m, "\n"); + + return 0; +} + +static int sec_dbg_region_dbgfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, sec_dbg_region_dbgfs_show_all, + inode->i_private); +} + +static const struct file_operations sec_dbg_region_dgbfs_fops = { + .open = sec_dbg_region_dbgfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static int __dbg_region_debugfs_create(struct builder *bd) +{ + struct dbg_region_drvdata *drvdata = + container_of(bd, struct dbg_region_drvdata, bd); + + drvdata->dbgfs = debugfs_create_file("sec_debug_region", 0440, + NULL, drvdata, &sec_dbg_region_dgbfs_fops); + + return 0; +} + +static void __dbg_region_debugfs_remove(struct builder *bd) +{ + struct dbg_region_drvdata *drvdata = + container_of(bd, struct dbg_region_drvdata, bd); + + debugfs_remove(drvdata->dbgfs); +} +#else +static int __dbg_region_debugfs_create(struct builder *bd) { return 0; } +static void __dbg_region_debugfs_remove(struct builder *bd) {} +#endif + +static noinline int __dbg_region_probe_epilog(struct builder *bd) +{ + struct dbg_region_drvdata *drvdata = + container_of(bd, struct dbg_region_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + dbg_region = drvdata; /* set a singleton */ + + return 0; +} + +static noinline void __dbg_region_remove_prolog(struct builder *bd) +{ + /* FIXME: This is not a graceful exit. */ + dbg_region = NULL; +} + +static int __dbg_region_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct dbg_region_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __dbg_region_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct dbg_region_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static const struct dev_builder __dbg_region_dev_builder[] = { + DEVICE_BUILDER(__dbg_region_parse_dt, NULL), + DEVICE_BUILDER(__dbg_region_probe_prolog, __dbg_region_remove_epilog), + DEVICE_BUILDER(__dbg_region_pool_init, __dbg_region_pool_exit), + DEVICE_BUILDER(__dbg_region_create_root, __dbg_region_delete_root), + DEVICE_BUILDER(__dbg_region_debugfs_create, + __dbg_region_debugfs_remove), + DEVICE_BUILDER(__dbg_region_probe_epilog, __dbg_region_remove_prolog), +}; + +static int sec_dbg_region_probe(struct platform_device *pdev) +{ + return __dbg_region_probe(pdev, __dbg_region_dev_builder, + ARRAY_SIZE(__dbg_region_dev_builder)); +} + +static int sec_dbg_region_remove(struct platform_device *pdev) +{ + return __dbg_region_remove(pdev, __dbg_region_dev_builder, + ARRAY_SIZE(__dbg_region_dev_builder)); +} + +static const struct of_device_id sec_dbg_region_match_table[] = { + { .compatible = "samsung,debug_region" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_dbg_region_match_table); + +static struct platform_driver sec_dbg_region_driver = { + .driver = { + .name = "sec,debug_region", + .of_match_table = of_match_ptr(sec_dbg_region_match_table), + }, + .probe = sec_dbg_region_probe, + .remove = sec_dbg_region_remove, +}; + +static __init int sec_dbg_region_init(void) +{ + return platform_driver_register(&sec_dbg_region_driver); +} +core_initcall_sync(sec_dbg_region_init); + +static __exit void sec_dbg_region_exit(void) +{ + platform_driver_unregister(&sec_dbg_region_driver); +} +module_exit(sec_dbg_region_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("Memory pool for debugging features"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/debug/debug_region/sec_debug_region_pool.c b/drivers/samsung/debug/debug_region/sec_debug_region_pool.c new file mode 100644 index 000000000000..9e0743422cd3 --- /dev/null +++ b/drivers/samsung/debug/debug_region/sec_debug_region_pool.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2022-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include + +#include "sec_debug_region.h" + +static bool __dbg_region_pool_sanity_check(const struct dbg_region_pool *pool) +{ + return !(!pool->probe || !pool->remove || !pool->alloc || !pool->free); +} + +static const struct dbg_region_pool * + __dbg_region_pool_creator(struct dbg_region_drvdata *drvdata) +{ + struct device *dev = drvdata->bd.dev; + unsigned int rmem_type = drvdata->rmem_type; + const struct dbg_region_pool *pool; + + switch (rmem_type) { + case RMEM_TYPE_NOMAP: + case RMEM_TYPE_MAPPED: + pool = __dbg_region_gen_pool_creator(); + break; + case RMEM_TYPE_REUSABLE: + pool = __dbg_region_cma_pool_creator(); + break; + default: + dev_warn(dev, "%u is not a supported or deprecated rmem-type\n", + rmem_type); + pool = ERR_PTR(-ENOENT); + } + + if (!__dbg_region_pool_sanity_check(pool)) + return ERR_PTR(-EINVAL); + + return pool; +} + +static void __dbg_region_pool_factory(struct dbg_region_drvdata *drvdata) +{ + drvdata->pool = __dbg_region_pool_creator(drvdata); +} + +int __dbg_region_pool_init(struct builder *bd) +{ + struct dbg_region_drvdata *drvdata = + container_of(bd, struct dbg_region_drvdata, bd); + + __dbg_region_pool_factory(drvdata); + if (IS_ERR_OR_NULL(drvdata->pool)) + return -ENODEV; + + drvdata->pool->probe(drvdata); + + return 0; +} + +void __dbg_region_pool_exit(struct builder *bd) +{ + struct dbg_region_drvdata *drvdata = + container_of(bd, struct dbg_region_drvdata, bd); + + BUG_ON(!drvdata->pool); + + drvdata->pool->remove(drvdata); +} diff --git a/drivers/samsung/debug/log_buf/Kconfig b/drivers/samsung/debug/log_buf/Kconfig new file mode 100644 index 000000000000..e5644c5e4e9b --- /dev/null +++ b/drivers/samsung/debug/log_buf/Kconfig @@ -0,0 +1,18 @@ +config SEC_LOG_BUF + tristate "SEC Kernel Log Buffer" + help + TODO: help is not ready. + +config SEC_LOG_BUF_USING_TP_CONSOLE + bool "SEC Kernel Log Buffer - Trace Console Back-End" + depends on SEC_LOG_BUF + default n + help + TODO: help is not ready. + +config SEC_LOG_BUF_USING_KPROBE + bool "SEC Kernel Log Buffer - Kprobe Back-End" + depends on SEC_LOG_BUF + default n + help + TODO: help is not ready. diff --git a/drivers/samsung/debug/log_buf/Makefile b/drivers/samsung/debug/log_buf/Makefile new file mode 100644 index 000000000000..8f249cb9fb76 --- /dev/null +++ b/drivers/samsung/debug/log_buf/Makefile @@ -0,0 +1,12 @@ +obj-$(CONFIG_SEC_LOG_BUF) += sec_log_buf.o +sec_log_buf-objs := sec_log_buf_main.o \ + sec_log_buf_logger.o \ + sec_log_buf_builtin.o \ + sec_log_buf_console.o \ + sec_log_buf_last_kmsg.o \ + sec_log_buf_ap_klog.o + +sec_log_buf-$(CONFIG_DEBUG_FS) += sec_log_buf_debugfs.o + +sec_log_buf-$(CONFIG_SEC_LOG_BUF_USING_TP_CONSOLE) += sec_log_buf_tp_console.o +sec_log_buf-$(CONFIG_SEC_LOG_BUF_USING_KPROBE) += sec_log_buf_kprobe.o diff --git a/drivers/samsung/debug/log_buf/sec_log_buf.h b/drivers/samsung/debug/log_buf/sec_log_buf.h new file mode 100644 index 000000000000..ba08745d1c49 --- /dev/null +++ b/drivers/samsung/debug/log_buf/sec_log_buf.h @@ -0,0 +1,141 @@ +#ifndef __INTERNAL__SEC_QC_LOG_BUF_H__ +#define __INTERNAL__SEC_QC_LOG_BUF_H__ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct last_kmsg_data { + char *buf; + size_t size; + bool use_compression; + const char *compressor; + struct mutex lock; + unsigned int ref_cnt; + struct crypto_comp *tfm; + char *buf_comp; + size_t size_comp; + struct proc_dir_entry *proc; +}; + +struct log_buf_drvdata; + +struct log_buf_logger { + int (*probe)(struct log_buf_drvdata *); + void (*remove)(struct log_buf_drvdata *); +}; + +struct ap_klog_proc { + char *buf; + size_t size; + struct mutex lock; + unsigned int ref_cnt; + struct proc_dir_entry *proc; + struct proc_dir_entry *symlink; +}; + +struct log_buf_drvdata { + struct builder bd; + struct reserved_mem *rmem; + phys_addr_t paddr; + size_t size; + struct kmsg_dump_iter iter; + unsigned int strategy; + union { + struct console con; + struct kprobe probe; + }; + const struct log_buf_logger *logger; + struct atomic_notifier_head sync_list; + struct last_kmsg_data last_kmsg; + struct ap_klog_proc ap_klog; +#if IS_ENABLED(CONFIG_DEBUG_FS) + struct dentry *dbgfs; +#endif +}; + +extern struct log_buf_drvdata *sec_log_buf; + +static __always_inline bool __log_buf_is_probed(void) +{ + return !!sec_log_buf; +} + +static __always_inline size_t __log_buf_print_time(u64 ts, char *buf) +{ + unsigned long rem_nsec = do_div(ts, 1000000000); + + return sprintf(buf, "\nAOSP [%5lu.%06lu]", + (unsigned long)ts, rem_nsec / 1000); +} + +static __always_inline size_t __log_buf_print_process(unsigned int cpu, + char *buf, size_t sz_buf) +{ + return scnprintf(buf, sz_buf, " %c[%2u:%15s:%5u] ", + in_interrupt() ? 'I' : ' ', + cpu, current->comm, (unsigned int)current->pid); +} + +/* sec_log_buf_main.c */ +extern bool __log_buf_is_acceptable(const char *s, size_t count); +extern void __log_buf_write(const char *s, size_t count); +extern void __log_buf_store_from_kmsg_dumper(void); +extern const struct sec_log_buf_head *__log_buf_get_header(void); +extern ssize_t __log_buf_get_buf_size(void); +extern size_t __log_buf_copy_to_buffer(void *buf); + +/* sec_log_buf_last_kmsg.c */ +extern int __last_kmsg_alloc_buffer(struct builder *bd); +extern void __last_kmsg_free_buffer(struct builder *bd); +extern int __last_kmsg_pull_last_log(struct builder *bd); +extern int __last_kmsg_procfs_create(struct builder *bd); +extern void __last_kmsg_procfs_remove(struct builder *bd); +extern int __last_kmsg_init_compression(struct builder *bd); +extern void __last_kmsg_exit_compression(struct builder *bd); + +/* sec_log_buf_debugfs.c */ +#if IS_ENABLED(CONFIG_DEBUG_FS) +extern int __log_buf_debugfs_create(struct builder *bd); +extern void __log_buf_debugfs_remove(struct builder *bd); +#endif + +/* sec_log_buf_logger.c */ +extern int __log_buf_logger_init(struct builder *bd); +extern void __log_buf_logger_exit(struct builder *bd); + +/* sec_log_buf_builtin.c */ +extern const struct log_buf_logger *__log_buf_logger_builtin_creator(void); + +/* sec_log_buf_tp_console.c */ +#if IS_ENABLED(CONFIG_SEC_LOG_BUF_USING_TP_CONSOLE) +extern const struct log_buf_logger *__log_buf_logger_tp_console_creator(void); +#else +static const inline struct log_buf_logger *__log_buf_logger_tp_console_creator(void) { return ERR_PTR(-ENODEV); } +#endif + +/* sec_log_buf_kprobe.c */ +#if IS_ENABLED(CONFIG_SEC_LOG_BUF_USING_KPROBE) +extern const struct log_buf_logger *__log_buf_logger_kprobe_creator(void); +#else +static const inline struct log_buf_logger *__log_buf_logger_kprobe_creator(void) { return ERR_PTR(-ENODEV); } +#endif + +/* sec_log_buf_console.c */ + +extern const struct log_buf_logger *__log_buf_logger_console_creator(void); + +/* sec_log_buf_ap_klog.c */ +extern int __ap_klog_proc_init(struct builder *bd); +extern void __ap_klog_proc_exit(struct builder *bd); +extern int __ap_klog_proc_create_symlink(struct builder *bd); +extern void __ap_klog_proc_remove_symlink(struct builder *bd); + +#endif /* __INTERNAL__SEC_QC_LOG_BUF_H__ */ diff --git a/drivers/samsung/debug/log_buf/sec_log_buf_ap_klog.c b/drivers/samsung/debug/log_buf/sec_log_buf_ap_klog.c new file mode 100644 index 000000000000..99fd6ff89f0c --- /dev/null +++ b/drivers/samsung/debug/log_buf/sec_log_buf_ap_klog.c @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2022-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#include "sec_log_buf.h" + +static int sec_ap_klog_open(struct inode *inode, struct file *file) +{ + struct ap_klog_proc *ap_klog = pde_data(inode); + struct log_buf_drvdata *drvdata = container_of(ap_klog, + struct log_buf_drvdata, ap_klog); + const size_t sz_buf = drvdata->size; + int err = 0; + + mutex_lock(&ap_klog->lock); + + if (ap_klog->ref_cnt) { + ap_klog->ref_cnt++; + goto already_cached; + } + + ap_klog->buf = vmalloc(sz_buf); + if (!ap_klog->buf) { + err = -ENOMEM; + goto err_vmalloc; + } + + ap_klog->size = __log_buf_copy_to_buffer(ap_klog->buf); + ap_klog->ref_cnt++; + + mutex_unlock(&ap_klog->lock); + + return 0; + +err_vmalloc: +already_cached: + mutex_unlock(&ap_klog->lock); + return err; +} + +static ssize_t sec_ap_klog_read(struct file *file, char __user * buf, + size_t len, loff_t * offset) +{ + struct ap_klog_proc *ap_klog = pde_data(file_inode(file)); + loff_t pos = *offset; + ssize_t count; + + if (pos < 0 || pos > ap_klog->size) + return 0; + + count = min(len, (size_t) (ap_klog->size - pos)); + if (copy_to_user(buf, ap_klog->buf + pos, count)) + return -EFAULT; + + *offset += count; + + return count; +} + +static loff_t sec_ap_klog_lseek(struct file *file, loff_t off, int whence) +{ + struct ap_klog_proc *ap_klog = pde_data(file_inode(file)); + + return fixed_size_llseek(file, off, whence, ap_klog->size); +} + +static int sec_ap_klog_release(struct inode *inode, struct file *file) +{ + struct ap_klog_proc *ap_klog = pde_data(inode); + + mutex_lock(&ap_klog->lock); + + ap_klog->ref_cnt--; + if (ap_klog->ref_cnt) + goto still_used; + + vfree(ap_klog->buf); + ap_klog->buf = NULL; + ap_klog->size = 0; + +still_used: + mutex_unlock(&ap_klog->lock); + + return 0; +} + +static const struct proc_ops ap_klog_pops = { + .proc_open = sec_ap_klog_open, + .proc_read = sec_ap_klog_read, + .proc_lseek = sec_ap_klog_lseek, + .proc_release = sec_ap_klog_release, +}; + +#define AP_KLOG_PROC_NODE "sec_log" + +int __ap_klog_proc_init(struct builder *bd) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + struct device *dev = bd->dev; + struct ap_klog_proc *ap_klog = &drvdata->ap_klog; + + ap_klog->proc = proc_create_data(AP_KLOG_PROC_NODE, 0444, + NULL, &ap_klog_pops, ap_klog); + if (!ap_klog->proc) { + dev_warn(dev, "failed to create proc entry\n"); + return -ENODEV; + } + + mutex_init(&ap_klog->lock); + + return 0; +} + +void __ap_klog_proc_exit(struct builder *bd) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + struct ap_klog_proc *ap_klog = &drvdata->ap_klog; + + proc_remove(ap_klog->proc); + mutex_destroy(&ap_klog->lock); +} + +#define AP_KLOG_PROC_SYMLINK "ap_klog" + +int __ap_klog_proc_create_symlink(struct builder *bd) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + struct device *dev = bd->dev; + struct ap_klog_proc *ap_klog = &drvdata->ap_klog; + + ap_klog->symlink = proc_symlink(AP_KLOG_PROC_SYMLINK, NULL, + AP_KLOG_PROC_NODE); + if (!ap_klog->symlink) { + dev_warn(dev, "failed to create proc entry\n"); + return -ENODEV; + } + + return 0; +} + +void __ap_klog_proc_remove_symlink(struct builder *bd) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + struct ap_klog_proc *ap_klog = &drvdata->ap_klog; + + proc_remove(ap_klog->symlink); +} diff --git a/drivers/samsung/debug/log_buf/sec_log_buf_builtin.c b/drivers/samsung/debug/log_buf/sec_log_buf_builtin.c new file mode 100644 index 000000000000..cdef01c4e9d0 --- /dev/null +++ b/drivers/samsung/debug/log_buf/sec_log_buf_builtin.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2010-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include + +#include "sec_log_buf.h" + +void sec_log_buf_store_on_vprintk_emit(void) +{ + if (!__log_buf_is_probed()) + return; + + if (sec_log_buf->strategy != SEC_LOG_BUF_STRATEGY_BUILTIN) + return; + + __log_buf_store_from_kmsg_dumper(); +} + +static int log_buf_logger_builtin_probe(struct log_buf_drvdata *drvdata) +{ + return 0; +} + +static void log_buf_logger_builtin_remove(struct log_buf_drvdata *drvdata) +{ +} + +static const struct log_buf_logger log_buf_logger_builtin = { + .probe = log_buf_logger_builtin_probe, + .remove = log_buf_logger_builtin_remove, +}; + +const struct log_buf_logger *__log_buf_logger_builtin_creator(void) +{ + if (IS_BUILTIN(CONFIG_SEC_LOG_BUF)) + return &log_buf_logger_builtin; + else + return ERR_PTR(-ENODEV); +} diff --git a/drivers/samsung/debug/log_buf/sec_log_buf_console.c b/drivers/samsung/debug/log_buf/sec_log_buf_console.c new file mode 100644 index 000000000000..37c2ef19c9af --- /dev/null +++ b/drivers/samsung/debug/log_buf/sec_log_buf_console.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2010-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include + +#include "sec_log_buf.h" + +static void sec_log_buf_write_console(struct console *console, const char *s, + unsigned int count) +{ + if (!__log_buf_is_acceptable(s, count)) + return; + + __log_buf_write(s, count); +} + +static int log_buf_logger_console_probe(struct log_buf_drvdata *drvdata) +{ + struct console *con = &drvdata->con; + + strlcpy(con->name, "sec_log_buf", sizeof(con->name)); + con->write = sec_log_buf_write_console; + /* NOTE: CON_PRINTBUFFER is ommitted. + * I use __log_buf_pull_early_buffer instead of it. + */ + con->flags = CON_ENABLED | CON_ANYTIME; + con->index = -1; + + register_console(con); + + return 0; +} + +static void log_buf_logger_console_remove(struct log_buf_drvdata *drvdata) +{ + struct console *con = &drvdata->con; + + unregister_console(con); +} + +static const struct log_buf_logger log_buf_logger_console = { + .probe = log_buf_logger_console_probe, + .remove = log_buf_logger_console_remove, +}; + +const struct log_buf_logger *__log_buf_logger_console_creator(void) +{ + return &log_buf_logger_console; +} diff --git a/drivers/samsung/debug/log_buf/sec_log_buf_debugfs.c b/drivers/samsung/debug/log_buf/sec_log_buf_debugfs.c new file mode 100644 index 000000000000..9e2c0ed67a24 --- /dev/null +++ b/drivers/samsung/debug/log_buf/sec_log_buf_debugfs.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2022-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include + +#include "sec_log_buf.h" + +static void __log_buf_dbgfs_show_basic(struct seq_file *m, + struct log_buf_drvdata *drvdata) +{ + seq_puts(m, "- Basic Information:\n"); + seq_printf(m, " + VA:0x%p (PA:%pa) / %zu bytes\n", __log_buf_get_header(), + &drvdata->paddr, drvdata->size); + seq_puts(m, "\n"); +} + +static void __log_buf_dbgfs_show_logger(struct seq_file *m, + struct log_buf_drvdata *drvdata) +{ + seq_puts(m, "- Logger Information:\n"); + seq_printf(m, " + logger : [%u] %ps\n", drvdata->strategy, drvdata->logger); + seq_puts(m, "\n"); +} + +static void __log_buf_dbgfs_show_last_kmsg(struct seq_file *m, + struct log_buf_drvdata *drvdata) +{ + struct last_kmsg_data *last_kmsg = &drvdata->last_kmsg; + + seq_puts(m, "- Last-KMSG Information:\n"); + seq_printf(m, " + compressor : %s\n", last_kmsg->use_compression ? + last_kmsg->compressor : "none"); + + if (!last_kmsg->use_compression) + seq_printf(m, " + VA:0x%p / %zu bytes\n", + last_kmsg->buf, last_kmsg->size); + else { + size_t ratio = (last_kmsg->size_comp * 100000) / last_kmsg->size; + + seq_printf(m, " + VA:0x%p / %zu (%zu) bytes (%zu.%03zu%%)\n", + last_kmsg->buf_comp, last_kmsg->size_comp, last_kmsg->size, + ratio / 1000, ratio % 1000); + } + + seq_puts(m, "\n"); +} + +static int sec_log_buf_dbgfs_show_all(struct seq_file *m, void *unsed) +{ + struct log_buf_drvdata *drvdata = m->private; + + __log_buf_dbgfs_show_basic(m, drvdata); + __log_buf_dbgfs_show_logger(m, drvdata); + __log_buf_dbgfs_show_last_kmsg(m, drvdata); + + return 0; +} + +static int sec_log_buf_dbgfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, sec_log_buf_dbgfs_show_all, inode->i_private); +} + +static const struct file_operations sec_log_buf_dgbfs_fops = { + .open = sec_log_buf_dbgfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +int __log_buf_debugfs_create(struct builder *bd) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + + drvdata->dbgfs = debugfs_create_file("sec_log_buf", 0440, + NULL, drvdata, &sec_log_buf_dgbfs_fops); + + return 0; +} + +void __log_buf_debugfs_remove(struct builder *bd) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + + debugfs_remove(drvdata->dbgfs); +} diff --git a/drivers/samsung/debug/log_buf/sec_log_buf_kprobe.c b/drivers/samsung/debug/log_buf/sec_log_buf_kprobe.c new file mode 100644 index 000000000000..ad65aa963e3a --- /dev/null +++ b/drivers/samsung/debug/log_buf/sec_log_buf_kprobe.c @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2010-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include + +#include "sec_log_buf.h" + +static void sec_log_buf_post_handle_on_vprintk_emit(struct kprobe *probe, + struct pt_regs *regs, unsigned long flags) +{ + __log_buf_store_from_kmsg_dumper(); +} + +static int log_buf_logger_kprobe_probe(struct log_buf_drvdata *drvdata) +{ + struct kprobe *kp = &drvdata->probe; + int err; + + kp->symbol_name = "vprintk_emit"; + kp->post_handler = sec_log_buf_post_handle_on_vprintk_emit; + + err = register_kprobe(kp); + if (err) + goto err_failed_to_register; + + return 0; + +err_failed_to_register: + return err; +} + +static void log_buf_logger_kprobe_remove(struct log_buf_drvdata *drvdata) +{ + struct kprobe *kp = &drvdata->probe; + + unregister_kprobe(kp); +} + +static const struct log_buf_logger log_buf_logger_kprobe = { + .probe = log_buf_logger_kprobe_probe, + .remove = log_buf_logger_kprobe_remove, +}; + +const struct log_buf_logger *__log_buf_logger_kprobe_creator(void) +{ + return &log_buf_logger_kprobe; +} diff --git a/drivers/samsung/debug/log_buf/sec_log_buf_last_kmsg.c b/drivers/samsung/debug/log_buf/sec_log_buf_last_kmsg.c new file mode 100644 index 000000000000..7c4df0f2e901 --- /dev/null +++ b/drivers/samsung/debug/log_buf/sec_log_buf_last_kmsg.c @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2010-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include + +#include "sec_log_buf.h" + +int __last_kmsg_alloc_buffer(struct builder *bd) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + struct last_kmsg_data *last_kmsg = &drvdata->last_kmsg; + const size_t log_buf_size = __log_buf_get_buf_size(); + + last_kmsg->buf = vmalloc(log_buf_size); + if (!last_kmsg->buf) + return -ENOMEM; + + return 0; +} + +void __last_kmsg_free_buffer(struct builder *bd) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + struct last_kmsg_data *last_kmsg = &drvdata->last_kmsg; + + vfree(last_kmsg->buf); +} + +int __last_kmsg_pull_last_log(struct builder *bd) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + struct last_kmsg_data *last_kmsg = &drvdata->last_kmsg; + char *buf = last_kmsg->buf; + + last_kmsg->size = __log_buf_copy_to_buffer(buf); + + return 0; +} + +static int __last_kmsg_decompress_buf(struct last_kmsg_data *last_kmsg) +{ + void *buf; + unsigned int size = last_kmsg->size; + unsigned int size_comp = last_kmsg->size_comp; + int err; + + buf = vmalloc(size); + if (!buf) { + pr_warn("failed to alloc buf\n"); + return -ENOMEM; + } + + err = crypto_comp_decompress(last_kmsg->tfm, + last_kmsg->buf_comp, size_comp, buf, &size); + if (err) { + pr_warn("failed to decompress (%d)\n", err); + vfree(buf); + return err; + } + + last_kmsg->buf = buf; + + return 0; +} + +static void __last_kmsg_release_buf(struct last_kmsg_data *last_kmsg) +{ + vfree(last_kmsg->buf); + last_kmsg->buf = NULL; +} + +static int sec_last_kmsg_buf_open(struct inode *inode, struct file *file) +{ + struct last_kmsg_data *last_kmsg = pde_data(inode); + int err = 0; + + if (!last_kmsg->use_compression || !last_kmsg->size) + return 0; + + mutex_lock(&last_kmsg->lock); + + if (last_kmsg->ref_cnt) { + last_kmsg->ref_cnt++; + goto already_decompressed; + } + + err = __last_kmsg_decompress_buf(last_kmsg); + if (err) { + pr_warn("failed to decompress last_kmsg (%d)\n", err); + goto err_decompress; + } + + last_kmsg->ref_cnt++; + + mutex_unlock(&last_kmsg->lock); + + return 0; + +err_decompress: +already_decompressed: + mutex_unlock(&last_kmsg->lock); + return err; +} + +static ssize_t sec_last_kmsg_buf_read(struct file *file, char __user *buf, + size_t len, loff_t *offset) +{ + struct last_kmsg_data *last_kmsg = pde_data(file_inode(file)); + loff_t pos = *offset; + ssize_t count; + + if (pos >= last_kmsg->size || !last_kmsg->buf) { + pr_warn("pos %lld, size %zu\n", pos, last_kmsg->size); + return 0; + } + + count = min(len, (size_t)(last_kmsg->size - pos)); + if (copy_to_user(buf, last_kmsg->buf + pos, count)) + return -EFAULT; + + *offset += count; + + return count; +} + +static loff_t sec_last_kmsg_buf_lseek(struct file *file, loff_t off, + int whence) +{ + struct last_kmsg_data *last_kmsg = pde_data(file_inode(file)); + + return fixed_size_llseek(file, off, whence, last_kmsg->size); +} + +static int sec_last_kmsg_buf_release(struct inode *inode, struct file *file) +{ + struct last_kmsg_data *last_kmsg = pde_data(inode); + + if (!last_kmsg->use_compression) + return 0; + + mutex_lock(&last_kmsg->lock); + + last_kmsg->ref_cnt--; + if (last_kmsg->ref_cnt) + goto still_used; + + __last_kmsg_release_buf(last_kmsg); + +still_used: + mutex_unlock(&last_kmsg->lock); + + return 0; +} + +static const struct proc_ops last_kmsg_buf_pops = { + .proc_open = sec_last_kmsg_buf_open, + .proc_read = sec_last_kmsg_buf_read, + .proc_lseek = sec_last_kmsg_buf_lseek, + .proc_release = sec_last_kmsg_buf_release, +}; + +#define LAST_LOG_BUF_NODE "last_kmsg" + +int __last_kmsg_procfs_create(struct builder *bd) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + struct device *dev = bd->dev; + struct last_kmsg_data *last_kmsg = &drvdata->last_kmsg; + + last_kmsg->proc = proc_create_data(LAST_LOG_BUF_NODE, 0444, + NULL, &last_kmsg_buf_pops, last_kmsg); + if (!last_kmsg->proc) { + dev_warn(dev, "failed to create proc entry. ram console may be present\n"); + return -ENODEV; + } + + mutex_init(&last_kmsg->lock); + proc_set_size(last_kmsg->proc, last_kmsg->size); + + return 0; +} + +void __last_kmsg_procfs_remove(struct builder *bd) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + struct last_kmsg_data *last_kmsg = &drvdata->last_kmsg; + + proc_remove(last_kmsg->proc); + mutex_destroy(&last_kmsg->lock); +} + +static void *__last_kmsg_vmalloc_compressed(struct last_kmsg_data *last_kmsg, + struct device *dev, struct crypto_comp *tfm, size_t *size_comp) +{ + unsigned int size_decomp = last_kmsg->size; + unsigned int size = last_kmsg->size * 2; + void *buf_tmp; + void *buf_comp; + int err; + + buf_tmp = vmalloc(size); + if (!buf_tmp) + return ERR_PTR(-ENOMEM); + + err = crypto_comp_compress(tfm, last_kmsg->buf, size_decomp, + buf_tmp, &size); + if (err || size >= size_decomp) { + vfree(buf_tmp); + return ERR_PTR(-EINVAL); + } + + buf_comp = vmalloc(size); + if (!buf_comp) { + vfree(buf_tmp); + return ERR_PTR(-ENOMEM); + } + + memcpy(buf_comp, buf_tmp, size); + vfree(buf_tmp); + + *size_comp = size; + + return buf_comp; +} + +static int ____last_kmsg_init_compression(struct last_kmsg_data *last_kmsg, + struct device *dev) +{ + struct crypto_comp *tfm; + void *buf_comp; + size_t size_comp; + int err; + + tfm = crypto_alloc_comp(last_kmsg->compressor, 0, 0); + if (IS_ERR(tfm)) { + err = PTR_ERR(tfm); + goto err_alloc_comp; + } + + buf_comp = __last_kmsg_vmalloc_compressed(last_kmsg, dev, tfm, &size_comp); + if (IS_ERR_OR_NULL(buf_comp)) { + err = PTR_ERR(buf_comp); + goto err_alloc_buf_comp; + } + + vfree(last_kmsg->buf); + last_kmsg->buf = NULL; + last_kmsg->buf_comp = buf_comp; + last_kmsg->size_comp = size_comp; + last_kmsg->tfm = tfm; + + return 0; + +err_alloc_buf_comp: + crypto_free_comp(tfm); +err_alloc_comp: + last_kmsg->use_compression = false; + return err; +} + +int __last_kmsg_init_compression(struct builder *bd) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + struct device *dev = bd->dev; + struct last_kmsg_data *last_kmsg = &drvdata->last_kmsg; + + if (!last_kmsg->use_compression || !last_kmsg->size) + return 0; + + return ____last_kmsg_init_compression(last_kmsg, dev); +} + +static void ____last_kmsg_exit_compression(struct last_kmsg_data *last_kmsg, + struct device *dev) +{ + vfree(last_kmsg->buf_comp); + last_kmsg->buf_comp = NULL; + + crypto_free_comp(last_kmsg->tfm); +} + +void __last_kmsg_exit_compression(struct builder *bd) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + struct device *dev = bd->dev; + struct last_kmsg_data *last_kmsg = &drvdata->last_kmsg; + + if (!last_kmsg->use_compression) + return; + + ____last_kmsg_exit_compression(last_kmsg, dev); +} diff --git a/drivers/samsung/debug/log_buf/sec_log_buf_logger.c b/drivers/samsung/debug/log_buf/sec_log_buf_logger.c new file mode 100644 index 000000000000..b9ac17e8e083 --- /dev/null +++ b/drivers/samsung/debug/log_buf/sec_log_buf_logger.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2010-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include + +#include "sec_log_buf.h" + +static const struct log_buf_logger * + __log_buf_logger_creator(struct log_buf_drvdata *drvdata) +{ + struct device *dev = drvdata->bd.dev; + const struct log_buf_logger *logger = NULL; + unsigned int strategy = drvdata->strategy; + + switch (strategy) { + case SEC_LOG_BUF_STRATEGY_BUILTIN: + logger = __log_buf_logger_builtin_creator(); + break; + case SEC_LOG_BUF_STRATEGY_TP_CONSOLE: + logger = __log_buf_logger_tp_console_creator(); + break; + case SEC_LOG_BUF_STRATEGY_KPROBE: + logger = __log_buf_logger_kprobe_creator(); + break; + case SEC_LOG_BUF_STRATEGY_CONSOLE: + logger = __log_buf_logger_console_creator(); + break; + } + + if (IS_ERR_OR_NULL(logger)) { + dev_warn(dev, "%u is not supported or deprecated. use default\n", + strategy); + logger = __log_buf_logger_console_creator(); + } + + return logger; +} + +static void __log_buf_logger_factory(struct log_buf_drvdata *drvdata) +{ + drvdata->logger = __log_buf_logger_creator(drvdata); +} + +int __log_buf_logger_init(struct builder *bd) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + + __log_buf_logger_factory(drvdata); + + return drvdata->logger->probe(drvdata); +} + +void __log_buf_logger_exit(struct builder *bd) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + + drvdata->logger->remove(drvdata); +} diff --git a/drivers/samsung/debug/log_buf/sec_log_buf_main.c b/drivers/samsung/debug/log_buf/sec_log_buf_main.c new file mode 100644 index 000000000000..43c3032d66cb --- /dev/null +++ b/drivers/samsung/debug/log_buf/sec_log_buf_main.c @@ -0,0 +1,751 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2010-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "sec_log_buf.h" + +struct log_buf_drvdata *sec_log_buf __read_mostly; + +static struct sec_log_buf_head *s_log_buf __read_mostly; +static size_t sec_log_buf_size __read_mostly; +static struct atomic_notifier_head *sync_list __read_mostly; + +const char *block_str[] = { + "init: Loading module", +}; + +static void (*__log_buf_memcpy_fromio)(void *, const void *, size_t) __read_mostly; +static void (*__log_buf_memcpy_toio)(void *, const void *, size_t) __read_mostly; + +static void notrace ____log_buf_memcpy_fromio(void *dst, const void *src, size_t cnt) +{ + memcpy_fromio(dst, src, cnt); +} + +static void notrace ____log_buf_memcpy_toio(void *dst, const void *src, size_t cnt) +{ + memcpy_toio(dst, src, cnt); +} + +static void notrace ____log_buf_memcpy(void *dst, const void *src, size_t cnt) +{ + memcpy(dst, src, cnt); +} + +const struct sec_log_buf_head *__log_buf_get_header(void) +{ + return s_log_buf; +} + +const struct sec_log_buf_head *sec_log_buf_get_header(void) +{ + if (!__log_buf_is_probed()) + return ERR_PTR(-EBUSY); + + return __log_buf_get_header(); +} +EXPORT_SYMBOL_GPL(sec_log_buf_get_header); + +ssize_t __log_buf_get_buf_size(void) +{ + return sec_log_buf_size; +} + +ssize_t sec_log_buf_get_buf_size(void) +{ + if (!__log_buf_is_probed()) + return -EBUSY; + + return __log_buf_get_buf_size(); +} +EXPORT_SYMBOL_GPL(sec_log_buf_get_buf_size); + +static int __log_buf_register_sync_handler(struct log_buf_drvdata *drvdata, + struct notifier_block *nb) +{ + return atomic_notifier_chain_register(&drvdata->sync_list, nb); +} + +int sec_log_buf_register_sync_handler(struct notifier_block *nb) +{ + if (!__log_buf_is_probed()) + return -EBUSY; + + return __log_buf_register_sync_handler(sec_log_buf, nb); +} +EXPORT_SYMBOL_GPL(sec_log_buf_register_sync_handler); + +static int __log_buf_unregister_sync_handler(struct log_buf_drvdata *drvdata, + struct notifier_block *nb) +{ + return atomic_notifier_chain_unregister(&drvdata->sync_list, nb); +} + +int sec_log_buf_unregister_sync_handler(struct notifier_block *nb) +{ + if (!__log_buf_is_probed()) + return -EBUSY; + + return __log_buf_unregister_sync_handler(sec_log_buf, nb); +} +EXPORT_SYMBOL_GPL(sec_log_buf_unregister_sync_handler); + +bool __log_buf_is_acceptable(const char *s, size_t count) +{ + static bool filter_en = !!IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP); + const char *magic_str = "init: init second stage started!"; + size_t i; + + if (likely(!filter_en)) + return true; + + if (strnstr(s, magic_str, count)) { + filter_en = false; + return true; + } + + for (i = 0; i < ARRAY_SIZE(block_str); i++) { + if (strnstr(s, block_str[i], count)) + return false; + } + + return true; +} + +void notrace __log_buf_write(const char *s, size_t count) +{ + size_t f_len, s_len, remain_space; + size_t idx; + + idx = s_log_buf->idx % sec_log_buf_size; + remain_space = sec_log_buf_size - idx; + f_len = min(count, remain_space); + __log_buf_memcpy_toio(&(s_log_buf->buf[idx]), s, f_len); + + s_len = count - f_len; + if (unlikely(s_len)) + __log_buf_memcpy_toio(s_log_buf->buf, &s[f_len], s_len); + + s_log_buf->idx += (uint32_t)count; + + atomic_notifier_call_chain(sync_list, s_log_buf->idx, s_log_buf); +} + +static __always_inline size_t __log_buf_print_kmsg(unsigned int cpu, + char *buf, size_t sz_buf) +{ + struct kmsg_dump_iter *iter = &sec_log_buf->iter; + size_t len; + + if (kmsg_dump_get_line(iter, true, buf, sz_buf, &len)) + return len; + + return 0; +} + +#define SZ_TASK_BUF 32 +#define SZ_KMSG_BUF 256 + +struct log_buf_kmsg_ctx { + char task[SZ_TASK_BUF]; + size_t task_len; + char head[SZ_KMSG_BUF]; + size_t head_len; + char *tail; + size_t tail_len; +}; + +static DEFINE_PER_CPU(struct log_buf_kmsg_ctx, kmsg_ctx); +static DEFINE_PER_CPU(struct log_buf_kmsg_ctx, kmsg_ctx_irq); + +static bool __log_buf_kmsg_check_level_text(struct log_buf_kmsg_ctx *ctx) +{ + char *head = ctx->head; + char *endp; + long l; + + if (head[0] != '<') + return false; + + /* NOTE: simple_strto{?} fucnctions are not recommended for normal cases. + * Because the position of next token is requried, kstrto{?} functions + * can not be used or more complex implementation is needed. + */ + l = simple_strtol(&head[1], &endp, 10); + if (!endp || endp[0] != '>') + return false; + + return true; +} + +static void __log_buf_kmsg_split(struct log_buf_kmsg_ctx *ctx) +{ + char *head = ctx->head; + char *tail; + const char *delim = "] "; + size_t head_len; + + tail = strnstr(head, delim, SZ_KMSG_BUF); + if (!tail) { + ctx->tail = NULL; + return; + } + + tail = &tail[2]; + head_len = tail - head - 1; + head[head_len] = '\0'; + + ctx->tail = tail; + ctx->tail_len = ctx->head_len - head_len - 1; + + ctx->head_len = head_len; +} + +static __always_inline void __log_buf_kmg_print(struct log_buf_kmsg_ctx *ctx) +{ + if (__log_buf_kmsg_check_level_text(ctx)) + __log_buf_kmsg_split(ctx); + + __log_buf_write(ctx->head, ctx->head_len); + if (ctx->tail) { + __log_buf_write(ctx->task, ctx->task_len); + __log_buf_write(ctx->tail, ctx->tail_len); + } +} + +size_t __log_buf_copy_to_buffer(void *__buf) +{ + char *buf = (char *)__buf; + const struct sec_log_buf_head *log_buf_head = __log_buf_get_header(); + const size_t log_buf_size = __log_buf_get_buf_size(); + const size_t max_size = log_buf_size; + size_t head; + size_t total; + + if (log_buf_head->idx > max_size) { + head = (size_t)log_buf_head->idx % log_buf_size; + __log_buf_memcpy_fromio(buf, &log_buf_head->buf[head], + log_buf_size - head); + if (head != 0) + __log_buf_memcpy_fromio(&buf[log_buf_size - head], + log_buf_head->buf, head); + total = max_size; + } else { + __log_buf_memcpy_fromio(buf, log_buf_head->buf, log_buf_head->idx); + total = log_buf_head->idx; + } + + return total; +} + +static noinline int __log_buf_parse_dt_strategy(struct builder *bd, + struct device_node *np) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + struct device *dev = bd->dev; + u32 strategy; + int err; + + err = of_property_read_u32(np, "sec,strategy", &strategy); + if (err) + return -EINVAL; + + if (IS_MODULE(CONFIG_SEC_LOG_BUF) && + (strategy == SEC_LOG_BUF_STRATEGY_BUILTIN)) { + dev_err(dev, "BUILTIN strategy can't be used in the kernel module!\n"); + return -EINVAL; + } + + if (strategy >= SEC_LOG_BUF_NR_STRATEGIES) { + dev_err(dev, "invalid strategy (%u)!\n", strategy); + return -EINVAL; + } + + drvdata->strategy = (unsigned int)strategy; + + return 0; +} + +static noinline int __log_buf_parse_dt_memory_region(struct builder *bd, + struct device_node *np) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + struct device *dev = bd->dev; + struct device_node *mem_np; + struct reserved_mem *rmem; + + mem_np = of_parse_phandle(np, "memory-region", 0); + if (!mem_np) + return -EINVAL; + + rmem = of_reserved_mem_lookup(mem_np); + if (!rmem) { + dev_warn(dev, "failed to get a reserved memory (%s)\n", + mem_np->name); + return -EFAULT; + } + + drvdata->rmem = rmem; + + return 0; +} + +static bool __log_buf_is_in_reserved_mem_bound( + const struct reserved_mem *rmem, + phys_addr_t base, phys_addr_t size) +{ + phys_addr_t rmem_base = rmem->base; + phys_addr_t rmem_end = rmem_base + rmem->size - 1; + phys_addr_t end = base + size - 1; + + if ((base >= rmem_base) && (end <= rmem_end)) + return true; + + return false; +} + +static int __log_buf_use_partial_reserved_mem( + struct log_buf_drvdata *drvdata, struct device_node *np) +{ + struct reserved_mem *rmem = drvdata->rmem; + phys_addr_t base; + phys_addr_t size; + int err; + + err = sec_of_parse_reg_prop(np, &base, &size); + if (err) + return err; + + if (!__log_buf_is_in_reserved_mem_bound(rmem, base, size)) + return -ERANGE; + + drvdata->paddr = base; + drvdata->size = size; + + return 0; +} + +static int __log_buf_use_entire_reserved_mem( + struct log_buf_drvdata *drvdata) +{ + struct reserved_mem *rmem = drvdata->rmem; + + drvdata->paddr = rmem->base; + drvdata->size = rmem->size; + + return 0; +} + +static noinline int __log_buf_parse_dt_partial_reserved_mem(struct builder *bd, + struct device_node *np) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + int err; + + if (of_property_read_bool(np, "sec,use-partial_reserved_mem")) + err = __log_buf_use_partial_reserved_mem(drvdata, np); + else + err = __log_buf_use_entire_reserved_mem(drvdata); + + if (err) + return -EFAULT; + + return 0; +} + +static noinline int __log_buf_parse_dt_test_no_map(struct builder *bd, + struct device_node *np) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + struct device_node *mem_np; + + mem_np = of_parse_phandle(np, "memory-region", 0); + if (!mem_np) + return -EINVAL; + + if (!of_property_read_bool(mem_np, "no-map")) { + s_log_buf = phys_to_virt(drvdata->paddr); + __log_buf_memcpy_fromio = ____log_buf_memcpy; + __log_buf_memcpy_toio = ____log_buf_memcpy; + } else { + __log_buf_memcpy_fromio = ____log_buf_memcpy_fromio; + __log_buf_memcpy_toio = ____log_buf_memcpy_toio; + } + + return 0; +} + +static noinline int __last_kmsg_parse_dt_use_last_kmsg_compression(struct builder *bd, + struct device_node *np) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + struct last_kmsg_data *last_kmsg = &drvdata->last_kmsg; + + last_kmsg->use_compression = + of_property_read_bool(np, "sec,use-last_kmsg_compression"); + + return 0; +} + +static noinline int __last_kmsg_parse_dt_last_kmsg_compressor(struct builder *bd, + struct device_node *np) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + struct last_kmsg_data *last_kmsg = &drvdata->last_kmsg; + + if (!last_kmsg->use_compression) + return 0; + + return of_property_read_string(np, "sec,last_kmsg_compressor", + &last_kmsg->compressor); +} + +static const struct dt_builder __log_buf_dt_builder[] = { + DT_BUILDER(__log_buf_parse_dt_strategy), + DT_BUILDER(__log_buf_parse_dt_memory_region), + DT_BUILDER(__log_buf_parse_dt_partial_reserved_mem), + DT_BUILDER(__log_buf_parse_dt_test_no_map), + DT_BUILDER(__last_kmsg_parse_dt_use_last_kmsg_compression), + DT_BUILDER(__last_kmsg_parse_dt_last_kmsg_compressor), +}; + +static noinline int __log_buf_parse_dt(struct builder *bd) +{ + return sec_director_parse_dt(bd, __log_buf_dt_builder, + ARRAY_SIZE(__log_buf_dt_builder)); +} + +static void __iomem *__log_buf_ioremap(struct log_buf_drvdata *drvdata) +{ + struct device *dev = drvdata->bd.dev; + + if (s_log_buf) + return s_log_buf; + + return devm_ioremap(dev, drvdata->paddr, drvdata->size); +} + +static noinline int __log_buf_probe_prolog(struct builder * bd) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + + ATOMIC_INIT_NOTIFIER_HEAD(&drvdata->sync_list); + sync_list = &drvdata->sync_list; + + return 0; +} + +static noinline inline void __log_buf_prepare_buffer_raw(struct log_buf_drvdata *drvdata) +{ + struct device *dev = drvdata->bd.dev; + + dev_warn(dev, "sec_log_magic is not valid : 0x%x at 0x%p\n", + s_log_buf->magic, &(s_log_buf->magic)); + + s_log_buf->magic = SEC_LOG_MAGIC; + s_log_buf->idx = 0; + s_log_buf->prev_idx = 0; +} + +static noinline int __log_buf_prepare_buffer(struct builder *bd) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + + s_log_buf = __log_buf_ioremap(drvdata); + if (!s_log_buf) + return -EFAULT; + + if (s_log_buf->magic != SEC_LOG_MAGIC) + __log_buf_prepare_buffer_raw(drvdata); + + sec_log_buf_size = drvdata->size - + offsetof(struct sec_log_buf_head, buf); + + return 0; +} + +static ssize_t __pull_early_buffer(struct log_buf_drvdata *drvdata, char *buf) +{ + struct kmsg_dump_iter *iter = &drvdata->iter; + ssize_t copied; + char *line; + size_t len; + + line = kvmalloc(PAGE_SIZE, GFP_KERNEL); + if (!line) + return -ENOMEM; + + memset(buf, 0x0, drvdata->size); + copied = 0; + kmsg_dump_rewind(iter); + while (kmsg_dump_get_line(iter, true, line, PAGE_SIZE, &len)) { + BUG_ON((copied + len) > drvdata->size); + memcpy_fromio(&buf[copied], line, len); + copied += len; + } + + kvfree(line); + + return copied; +} + +static size_t __remove_till_end_of_line(char *substr) +{ + size_t i = 0; + + while (substr[i] != '\n' && substr[i] != '\0') + substr[i++] = ' '; + + return i; +} + +static void ____remove_block_str(char *buf, size_t len, const char *keyword) +{ + size_t offset = 0; + char *substr; + + while (offset < len) { + substr = strnstr(&buf[offset], keyword, len); + if (!substr) + break; + + offset = substr - buf; + offset += __remove_till_end_of_line(substr); + } +} + +static void __remove_block_str(char *buf, size_t len) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(block_str); i++) + ____remove_block_str(buf, len, block_str[i]); +} + +static noinline int __log_buf_pull_early_buffer(struct builder *bd) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + char *buf; + ssize_t copied; + + buf = kvmalloc(drvdata->size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + copied = __pull_early_buffer(drvdata, buf); + if (copied < 0) { + kvfree(buf); + return copied; + } + + if (IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP)) + __remove_block_str(buf, copied); + + __log_buf_write(buf, copied); + + kvfree(buf); + + return 0; +} + +void notrace __log_buf_store_from_kmsg_dumper(void) +{ + unsigned int cpu; + struct log_buf_kmsg_ctx *ctx_pcpu; + + cpu = get_cpu(); + + if (in_irq()) + ctx_pcpu = this_cpu_ptr(&kmsg_ctx_irq); + else + ctx_pcpu = this_cpu_ptr(&kmsg_ctx); + + ctx_pcpu->head_len = __log_buf_print_kmsg(cpu, + ctx_pcpu->head, SZ_KMSG_BUF); + if (!ctx_pcpu->head_len) + goto print_nothing; + + ctx_pcpu->task_len = __log_buf_print_process(cpu, + ctx_pcpu->task, SZ_TASK_BUF); + + do { + __log_buf_kmg_print(ctx_pcpu); + + ctx_pcpu->head_len = __log_buf_print_kmsg(cpu, + ctx_pcpu->head, SZ_KMSG_BUF); + } while (ctx_pcpu->head_len); + +print_nothing: + put_cpu(); +} + +static noinline int __log_buf_probe_epilog(struct builder *bd) +{ + struct log_buf_drvdata *drvdata = + container_of(bd, struct log_buf_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + sec_log_buf = drvdata; /* set a singleton */ + + pr_debug("buf base virtual addrs 0x%p phy=%pa\n", s_log_buf, + &sec_log_buf->paddr); + + return 0; +} + +static noinline void __log_buf_remove_prolog(struct builder *bd) +{ + /* FIXME: This is not a graceful exit. + * 'sec_log_buf' can be used in some calling 'printk'. + */ + sec_log_buf = NULL; +} + +static int __log_buf_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct log_buf_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __log_buf_probe_threaded(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct log_buf_drvdata *drvdata = platform_get_drvdata(pdev); + + return sec_director_probe_dev_threaded(&drvdata->bd, builder, n, + "log_buf"); +} + +static int __log_buf_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct log_buf_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static int __log_buf_remove_threaded(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct log_buf_drvdata *drvdata = platform_get_drvdata(pdev); + struct director_threaded *drct = drvdata->bd.drct; + + sec_director_destruct_dev_threaded(drct); + + return 0; +} + +static const struct dev_builder __log_buf_dev_builder[] = { + DEVICE_BUILDER(__log_buf_parse_dt, NULL), + DEVICE_BUILDER(__log_buf_probe_prolog, NULL), + DEVICE_BUILDER(__log_buf_prepare_buffer, NULL), + DEVICE_BUILDER(__last_kmsg_alloc_buffer, __last_kmsg_free_buffer), + DEVICE_BUILDER(__last_kmsg_pull_last_log, NULL), + DEVICE_BUILDER(__last_kmsg_procfs_create, __last_kmsg_procfs_remove), + DEVICE_BUILDER(__log_buf_pull_early_buffer, NULL), + DEVICE_BUILDER(__log_buf_logger_init, __log_buf_logger_exit), + DEVICE_BUILDER(__ap_klog_proc_init, __ap_klog_proc_exit), + DEVICE_BUILDER(__ap_klog_proc_create_symlink, __ap_klog_proc_remove_symlink), + DEVICE_BUILDER(__log_buf_probe_epilog, __log_buf_remove_prolog), +}; + +static const struct dev_builder __log_buf_dev_builder_threaded[] = { +#if IS_ENABLED(CONFIG_DEBUG_FS) + DEVICE_BUILDER(__log_buf_debugfs_create, __log_buf_debugfs_remove), +#endif + DEVICE_BUILDER(__last_kmsg_init_compression, __last_kmsg_exit_compression), +}; + +static int sec_log_buf_probe(struct platform_device *pdev) +{ + int err; + + err = __log_buf_probe(pdev, __log_buf_dev_builder, + ARRAY_SIZE(__log_buf_dev_builder)); + if (err) + return err; + + return __log_buf_probe_threaded(pdev, __log_buf_dev_builder_threaded, + ARRAY_SIZE(__log_buf_dev_builder_threaded)); +} + +static int sec_log_buf_remove(struct platform_device *pdev) +{ + __log_buf_remove_threaded(pdev, __log_buf_dev_builder_threaded, + ARRAY_SIZE(__log_buf_dev_builder_threaded)); + + __log_buf_remove(pdev, __log_buf_dev_builder, + ARRAY_SIZE(__log_buf_dev_builder)); + + return 0; +} + +static const struct of_device_id sec_log_buf_match_table[] = { + { .compatible = "samsung,kernel_log_buf" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_log_buf_match_table); + +static struct platform_driver sec_log_buf_driver = { + .driver = { + .name = "sec,log_buf", + .of_match_table = of_match_ptr(sec_log_buf_match_table), + }, + .probe = sec_log_buf_probe, + .remove = sec_log_buf_remove, +}; + +static int __init sec_log_buf_init(void) +{ + return platform_driver_register(&sec_log_buf_driver); +} +/* NOTE: all compression algorithms are registered in 'subsys_initcall' stage. */ +subsys_initcall_sync(sec_log_buf_init); + +static void __exit sec_log_buf_exit(void) +{ + platform_driver_unregister(&sec_log_buf_driver); +} +module_exit(sec_log_buf_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("Kernel log buffer shared with boot loader"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/debug/log_buf/sec_log_buf_tp_console.c b/drivers/samsung/debug/log_buf/sec_log_buf_tp_console.c new file mode 100644 index 000000000000..440bd626d9ed --- /dev/null +++ b/drivers/samsung/debug/log_buf/sec_log_buf_tp_console.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include + +#include + +#include "sec_log_buf.h" + +static __always_inline size_t __trace_console_print_prefix(char *buf, size_t sz_buf) +{ + size_t len = 0; + u64 ts_nsec = local_clock(); /* NOTE: may have a skew... */ + + len += __log_buf_print_time(ts_nsec, buf + len); + len += __log_buf_print_process(smp_processor_id(), buf + len, sz_buf - len); + + return len; +} + +#define PREFIX_MAX 64 + +static __always_inline void __trace_console_locked(void *unused, + const char *text, size_t len) +{ + static char last_char = '\n'; + size_t text_len; + + if (!__log_buf_is_acceptable(text, len)) + return; + + if (unlikely(len == 0 || text[0] == '\0')) + goto skip; + + if (likely(last_char == '\n')) { + char prefix[PREFIX_MAX]; + size_t prefix_len; + + prefix_len = __trace_console_print_prefix(prefix, sizeof(prefix)); + __log_buf_write(prefix, prefix_len); + } + __log_buf_write(text, len); + +skip: + text_len = len + strnlen(&text[len], 16); + last_char = text[text_len - 1]; +} + +static void __trace_console(void *unused, const char *text, size_t len) +{ + static DEFINE_SPINLOCK(lock); + unsigned long flags; + + spin_lock_irqsave(&lock, flags); + __trace_console_locked(unused, text, len); + spin_unlock_irqrestore(&lock, flags); +} + +static int log_buf_logger_tp_console_probe(struct log_buf_drvdata *drvdata) +{ + return register_trace_console(__trace_console, NULL); +} + +static void log_buf_logger_tp_console_remove(struct log_buf_drvdata *drvdata) +{ + unregister_trace_console(__trace_console, NULL); +} + +static const struct log_buf_logger log_buf_tp_console = { + .probe = log_buf_logger_tp_console_probe, + .remove = log_buf_logger_tp_console_remove, +}; + +const struct log_buf_logger *__log_buf_logger_tp_console_creator(void) +{ + return &log_buf_tp_console; +} diff --git a/drivers/samsung/debug/pmsg/Kconfig b/drivers/samsung/debug/pmsg/Kconfig new file mode 100644 index 000000000000..6bc4c982b5d8 --- /dev/null +++ b/drivers/samsung/debug/pmsg/Kconfig @@ -0,0 +1,21 @@ +config SEC_PMSG + tristate "PSTORE backend for saving android platform log" + help + TODO: help is not ready. + +config SEC_PMSG_TEST_FOR_ON_DEVICE + tristate "KUnit test for sec_pmsg_test" + depends on KUNIT + depends on SEC_PMSG + help + TODO: Describe config fully. + If you run this test driver on device, SHOULD set this config as 'm' to build test driver modulraly. + +config SEC_PMSG_TEST_FOR_ONLY_UML + tristate "KUnit test for sec_pmsg_test" + depends on KUNIT + depends on UML + depends on SEC_PMSG + help + TODO: Describe config fully. + This CONFIG is recommended to set to y. diff --git a/drivers/samsung/debug/pmsg/Makefile b/drivers/samsung/debug/pmsg/Makefile new file mode 100644 index 000000000000..7dee07ef062a --- /dev/null +++ b/drivers/samsung/debug/pmsg/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_SEC_PMSG) += sec_pmsg.o + +GCOV_PROFILE_sec_pmsg.o := $(CONFIG_KUNIT) diff --git a/drivers/samsung/debug/pmsg/sec_pmsg.c b/drivers/samsung/debug/pmsg/sec_pmsg.c new file mode 100644 index 000000000000..099c20060076 --- /dev/null +++ b/drivers/samsung/debug/pmsg/sec_pmsg.c @@ -0,0 +1,707 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * COPYRIGHT(C) 2016-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "sec_pmsg.h" + +/* This defines are for PSTORE */ +#define SS_LOGGER_LEVEL_HEADER (1) +#define SS_LOGGER_LEVEL_PREFIX (2) +#define SS_LOGGER_LEVEL_TEXT (3) +#define SS_LOGGER_LEVEL_MAX (4) +#define SS_LOGGER_SKIP_COUNT (4) +#define SS_LOGGER_STRING_PAD (1) +#define SS_LOGGER_HEADER_SIZE (80) + +#define SS_LOG_ID_MAIN (0) +#define SS_LOG_ID_RADIO (1) +#define SS_LOG_ID_EVENTS (2) +#define SS_LOG_ID_SYSTEM (3) +#define SS_LOG_ID_CRASH (4) +#define SS_LOG_ID_KERNEL (5) + +static struct pmsg_drvdata *sec_pmsg __read_mostly; + +static char *pmsg_buf __read_mostly; +static size_t pmsg_size __read_mostly; +static size_t pmsg_idx; + +static void (*__pmsg_memcpy_toio)(void *, const void *, size_t) __read_mostly; + +static void notrace ____pmsg_memcpy_toio(void *dst, const void *src, size_t cnt) +{ + memcpy_toio(dst, src, cnt); +} + +static void notrace ____pmsg_memcpy(void *dst, const void *src, size_t cnt) +{ + memcpy(dst, src, cnt); +} + +static void notrace ____pmsg_memcpy_dummy(void *dst, const void *src, size_t cnt) +{ +} + +static inline void __pmsg_logger(const char *buf, size_t size) +{ + size_t f_len, s_len, remain_space; + size_t idx; + + idx = pmsg_idx % pmsg_size; + remain_space = pmsg_size - idx; + f_len = min(size, remain_space); + __pmsg_memcpy_toio(&(pmsg_buf[idx]), buf, f_len); + + s_len = size - f_len; + if (unlikely(s_len)) + __pmsg_memcpy_toio(pmsg_buf, &buf[f_len], s_len); + + pmsg_idx += size; +} + +__ss_static __ss_always_inline +int ____logger_level_header(struct pmsg_logger *logger, + struct logger_level_header_ctx *llhc) +{ + u64 tv_kernel = llhc->tv_kernel; + u64 rem_nsec; + struct tm tm_buf; + + rem_nsec = do_div(tv_kernel, 1000000000); + time64_to_tm(logger->tv_sec, 0, &tm_buf); + + return scnprintf(llhc->buffer, SS_LOGGER_HEADER_SIZE, + "\n[%5llu.%06llu][%d:%16s] %02d-%02d " + "%02d:%02d:%02d.%03d %5d %5d ", + (unsigned long long)tv_kernel, + (unsigned long long)rem_nsec / 1000, + llhc->cpu, llhc->comm, + tm_buf.tm_mon + 1, tm_buf.tm_mday, + tm_buf.tm_hour, tm_buf.tm_min, + tm_buf.tm_sec, logger->tv_nsec / 1000000, + logger->pid, logger->tid); +} + +static inline void __logger_level_header(struct pmsg_logger *logger, + char *buffer, size_t count) +{ + struct logger_level_header_ctx _llhc; + struct logger_level_header_ctx *llhc = &_llhc; + int buffer_len; + + if (IS_ENABLED(CONFIG_SEC_PMSG_USE_EVENT_LOG) + && logger->id == SS_LOG_ID_EVENTS) + return; + + llhc->cpu = raw_smp_processor_id(); + llhc->comm = current->comm; + llhc->tv_kernel = local_clock(); + llhc->buffer = buffer; + llhc->count = count; + + buffer_len = ____logger_level_header(logger, llhc); + + __pmsg_logger(buffer, buffer_len - 1); +} + +__ss_static __ss_inline char ____logger_level_prefix(struct pmsg_logger *logger) +{ + const char *prio_magic = "!.VDIWEFS"; + const size_t prio_magic_len = sizeof("!.VDIWEFS") - 1; + size_t prio = (size_t)logger->msg[0]; + + return prio < prio_magic_len ? prio_magic[prio] : '?'; +} + +static inline void __logger_level_prefix(struct pmsg_logger *logger, + char *buffer, size_t count) +{ + if (IS_ENABLED(CONFIG_SEC_PMSG_USE_EVENT_LOG) && + logger->id == SS_LOG_ID_EVENTS) + return; + + buffer[0] = ____logger_level_prefix(logger); + + if (IS_ENABLED(CONFIG_SEC_PMSG_USE_EVENT_LOG)) + logger->msg[0] = 0xff; + + __pmsg_logger(buffer, 1); +} + +static inline void __ss_logger_level_text_event_log(struct pmsg_logger *logger, + char *buffer, size_t count) +{ + /* TODO: CONFIG_SEC_PMSG_USE_EVENT_LOG (CONFIG_SEC_EVENT_LOG in + * a legacy implementation) is never used, yet. + * It's maybe deprecated and I'll implement it if it is required. + */ +} + +static inline void ____logger_level_text(struct pmsg_logger *logger, + char *buffer, size_t count) +{ + char *eatnl = &buffer[count - SS_LOGGER_STRING_PAD]; + + if (count == SS_LOGGER_SKIP_COUNT && *eatnl != '\0') + return; + + if (count > 1 && *(uint16_t*)buffer == *(uint16_t *)"!@") { + /* To prevent potential buffer overrun + * put a null at the end of the buffer. + */ + buffer[count - 1] = '\0'; + + /* FIXME: print without a module and a function name */ + printk(KERN_INFO "%s\n", buffer); + + sec_boot_stat_add(buffer); + } + + __pmsg_logger(buffer, count - 1); +} + +static inline void __logger_level_text(struct pmsg_logger *logger, + char *buffer, size_t count) +{ + if (unlikely(logger->id == SS_LOG_ID_EVENTS)) { + __ss_logger_level_text_event_log(logger, buffer, count); + return; + } + + ____logger_level_text(logger, buffer, count); +} + +static inline int __logger_combine_pmsg(struct pmsg_logger *logger, + char *buffer, size_t count, unsigned int level) +{ + switch (level) { + case SS_LOGGER_LEVEL_HEADER: + __logger_level_header(logger, buffer, count); + break; + case SS_LOGGER_LEVEL_PREFIX: + __logger_level_prefix(logger, buffer, count); + break; + case SS_LOGGER_LEVEL_TEXT: + __logger_level_text(logger, buffer, count); + break; + default: + pr_warn("unknown logger level : %u\n", level); + break; + } + + __pmsg_logger(" ", 1); + + return 0; +} + +static __always_inline void __logger_write_user_pmsg_log_header( + struct pmsg_logger *logger, char *buffer, size_t count) +{ + struct ss_pmsg_log_header_t *pmsg_header = + (struct ss_pmsg_log_header_t *)buffer; + + if (pmsg_header->magic != 'l') { + __logger_combine_pmsg(logger, buffer, count, SS_LOGGER_LEVEL_TEXT); + } else { + logger->pid = pmsg_header->pid; + logger->uid = pmsg_header->uid; + logger->len = pmsg_header->len; + } +} + +static __always_inline void __logger_write_user_android_log_header( + struct pmsg_logger *logger, char *buffer, size_t count) +{ + struct ss_android_log_header_t *header = + (struct ss_android_log_header_t *)buffer; + + logger->id = header->id; + logger->tid = header->tid; + logger->tv_sec = header->tv_sec; + logger->tv_nsec = header->tv_nsec; + + if (logger->id > 7) + __logger_combine_pmsg(logger, buffer, count, SS_LOGGER_LEVEL_TEXT); + else + __logger_combine_pmsg(logger, buffer, count, SS_LOGGER_LEVEL_HEADER); +} + +static __always_inline int __pmsg_write_user(struct pstore_record *record, + const char __user *buf, size_t count) +{ + struct pmsg_drvdata *drvdata = record->psi->data; + struct pmsg_logger *logger = drvdata->logger; + char *big_buffer = NULL; + char *buffer; + int err; + + if (unlikely(count > MAX_BUFFER_SIZE)) { + big_buffer = kmalloc(count, GFP_KERNEL); + if (unlikely(!big_buffer)) + return -ENOMEM; + + buffer = big_buffer; + } else { + struct pmsg_buffer *buf = + per_cpu_ptr(drvdata->buf, raw_smp_processor_id()); + buffer = &buf->buffer[0]; + } + + err = __copy_from_user(buffer, buf, count); + if (unlikely(err)) + return -EFAULT; + + switch (count) { + case sizeof(struct ss_pmsg_log_header_t): + __logger_write_user_pmsg_log_header(logger, buffer, count); + break; + case sizeof(struct ss_android_log_header_t): + __logger_write_user_android_log_header(logger, buffer, count); + break; + case sizeof(unsigned char): + logger->msg[0] = buffer[0]; + __logger_combine_pmsg(logger, buffer, count, SS_LOGGER_LEVEL_PREFIX); + break; + default: + __logger_combine_pmsg(logger, buffer, count, SS_LOGGER_LEVEL_TEXT); + break; + } + + kfree(big_buffer); + + return 0; +} + +static int notrace sec_pmsg_write_user(struct pstore_record *record, + const char __user *buf) +{ + if (unlikely(record->type != PSTORE_TYPE_PMSG)) + return -EINVAL; + + return __pmsg_write_user(record, buf, record->size); +} + +static ssize_t notrace sec_pmsg_read(struct pstore_record *record) +{ + /* FIXME: I don't do anything. */ + return 0; +} + +static int notrace sec_pmsg_write(struct pstore_record *record) +{ + /* FIXME: I don't do anything. */ + return 0; +} + +static struct pstore_info sec_pmsg_pstore = { + .owner = THIS_MODULE, + .name = "sec,pstore_pmsg", + .read = sec_pmsg_read, + .write = sec_pmsg_write, + .write_user = sec_pmsg_write_user, + .flags = PSTORE_FLAGS_PMSG, +}; + +static noinline int __pmsg_parse_dt_memory_region(struct builder *bd, + struct device_node *np) +{ + struct pmsg_drvdata *drvdata = + container_of(bd, struct pmsg_drvdata, bd); + struct device *dev = bd->dev; + struct device_node *mem_np; + struct reserved_mem *rmem; + + mem_np = of_parse_phandle(np, "memory-region", 0); + if (!mem_np) + return -EINVAL; + + rmem = of_reserved_mem_lookup(mem_np); + if (!rmem) { + dev_warn(dev, "failed to get a reserved memory (%s)\n", + mem_np->name); + return -EFAULT; + } + + drvdata->rmem = rmem; + + return 0; +} + +static bool __pmsg_is_in_reserved_mem_bound( + const struct reserved_mem *rmem, + phys_addr_t base, phys_addr_t size) +{ + phys_addr_t rmem_base = rmem->base; + phys_addr_t rmem_end = rmem_base + rmem->size - 1; + phys_addr_t end = base + size - 1; + + if ((base >= rmem_base) && (end <= rmem_end)) + return true; + + return false; +} + +static int __pmsg_use_partial_reserved_mem( + struct pmsg_drvdata *drvdata, struct device_node *np) +{ + struct reserved_mem *rmem = drvdata->rmem; + phys_addr_t base; + phys_addr_t size; + int err; + + err = sec_of_parse_reg_prop(np, &base, &size); + if (err) + return err; + + if (!__pmsg_is_in_reserved_mem_bound(rmem, base, size)) + return -ERANGE; + + drvdata->paddr = base; + drvdata->size = size; + + return 0; +} + +static int __pmsg_use_entire_reserved_mem( + struct pmsg_drvdata *drvdata) +{ + struct reserved_mem *rmem = drvdata->rmem; + + drvdata->paddr = rmem->base; + drvdata->size = rmem->size; + + return 0; +} + +static noinline int __pmsg_parse_dt_splitted_reserved_mem(struct builder *bd, + struct device_node *np) +{ + struct pmsg_drvdata *drvdata = + container_of(bd, struct pmsg_drvdata, bd); + int err; + + if (of_property_read_bool(np, "sec,use-partial_reserved_mem")) + err = __pmsg_use_partial_reserved_mem(drvdata, np); + else + err = __pmsg_use_entire_reserved_mem(drvdata); + + if (err) + return -EFAULT; + + return 0; +} + +static noinline int __pmsg_parse_dt_test_no_map(struct builder *bd, + struct device_node *np) +{ + struct pmsg_drvdata *drvdata = + container_of(bd, struct pmsg_drvdata, bd); + struct device_node *mem_np; + + mem_np = of_parse_phandle(np, "memory-region", 0); + if (!mem_np) + return -EINVAL; + + if (!of_property_read_bool(mem_np, "no-map")) { + pmsg_buf = phys_to_virt(drvdata->paddr); + __pmsg_memcpy_toio = ____pmsg_memcpy; + drvdata->nomap = false; + } else { + __pmsg_memcpy_toio = ____pmsg_memcpy_toio; + drvdata->nomap = true; + } + + return 0; +} + +#if IS_BUILTIN(CONFIG_SEC_PMSG) +static __always_inline unsigned long __free_reserved_area(void *start, void *end, int poison, const char *s) +{ + return free_reserved_area(start, end, poison, s); +} +#else +/* FIXME: this is a copy of 'free_reserved_area' of 'page_alloc.c' */ +static unsigned long __free_reserved_area(void *start, void *end, int poison, const char *s) +{ + void *pos; + unsigned long pages = 0; + + start = (void *)PAGE_ALIGN((unsigned long)start); + end = (void *)((unsigned long)end & PAGE_MASK); + for (pos = start; pos < end; pos += PAGE_SIZE, pages++) { + struct page *page = virt_to_page(pos); + void *direct_map_addr; + + direct_map_addr = page_address(page); + + direct_map_addr = kasan_reset_tag(direct_map_addr); + if ((unsigned int)poison <= 0xFF) + memset(direct_map_addr, poison, PAGE_SIZE); + + free_reserved_page(page); + } + + if (pages && s) + pr_info("Freeing %s memory: %ldK\n", + s, pages << (PAGE_SHIFT - 10)); + + return pages; +} +#endif + +static void __pmsg_free_reserved_area(struct pmsg_drvdata *drvdata) +{ + struct device *dev = drvdata->bd.dev; + uint8_t *start; + + if (drvdata->nomap) { + dev_warn(dev, "reserved_mem has 'no-map' and can't be freed\n"); + return; + } + + start = (uint8_t *)phys_to_virt(drvdata->paddr); + + __free_reserved_area(start, start + drvdata->size, -1, "sec_pmsg"); +} + +__ss_static int __pmsg_handle_dt_debug_level(struct pmsg_drvdata *drvdata, + struct device_node *np, unsigned int sec_dbg_level) +{ + int err; + + err = sec_of_test_debug_level(np, "sec,debug_level", sec_dbg_level); + if (err == -EINVAL) { + __pmsg_free_reserved_area(drvdata); + __pmsg_memcpy_toio = ____pmsg_memcpy_dummy; + + return -EPERM; + } + + return 0; +} + +static noinline int __pmsg_parse_dt_check_debug_level(struct builder *bd, + struct device_node *np) +{ + struct pmsg_drvdata *drvdata = + container_of(bd, struct pmsg_drvdata, bd); + unsigned int sec_dbg_level = sec_debug_level(); + int err; + + err = __pmsg_handle_dt_debug_level(drvdata, np, sec_dbg_level); + if (err) + dev_warn(bd->dev, "pmsg will not be stored\n"); + + return 0; +} + +static const struct dt_builder __pmsg_dt_builder[] = { + DT_BUILDER(__pmsg_parse_dt_memory_region), + DT_BUILDER(__pmsg_parse_dt_splitted_reserved_mem), + DT_BUILDER(__pmsg_parse_dt_test_no_map), + DT_BUILDER(__pmsg_parse_dt_check_debug_level), +}; + +static noinline int __pmsg_parse_dt(struct builder *bd) +{ + return sec_director_parse_dt(bd, __pmsg_dt_builder, + ARRAY_SIZE(__pmsg_dt_builder)); +} + +static noinline int __pmsg_prepare_logger(struct builder *bd) +{ + struct pmsg_drvdata *drvdata = + container_of(bd, struct pmsg_drvdata, bd); + struct device *dev = bd->dev; + struct pmsg_logger *logger; + + logger = devm_kmalloc(dev, sizeof(*drvdata->logger), GFP_KERNEL); + if (!logger) + return -ENOMEM; + + drvdata->logger = logger; + + return 0; +} + +static noinline int __pmsg_prepare_buffer(struct builder *bd) +{ + struct pmsg_drvdata *drvdata = + container_of(bd, struct pmsg_drvdata, bd); + struct device *dev = bd->dev; + struct pmsg_buffer *buf; + + buf = devm_alloc_percpu(dev, struct pmsg_buffer); + if (!buf) + return -ENOMEM; + + drvdata->buf = buf; + + return 0; +} + +static void *__pmsg_ioremap(struct pmsg_drvdata *drvdata) +{ + struct device *dev = drvdata->bd.dev; + + if (pmsg_buf) + return pmsg_buf; + +#if IS_ENABLED(CONFIG_HAS_IOMEM) + return devm_ioremap(dev, drvdata->paddr, drvdata->size); +#else + dev = dev; + return ioremap(drvdata->paddr, drvdata->size); +#endif +} + +static noinline int __pmsg_prepare_carveout(struct builder *bd) +{ + struct pmsg_drvdata *drvdata = + container_of(bd, struct pmsg_drvdata, bd); + + pmsg_buf = __pmsg_ioremap(drvdata); + if (!pmsg_buf) + return -EFAULT; + + pmsg_size = drvdata->size; + pmsg_idx = 0; + + return 0; +} + +static noinline int __pmsg_pstore_register(struct builder *bd) +{ + struct pmsg_drvdata *drvdata = + container_of(bd, struct pmsg_drvdata, bd); + + sec_pmsg_pstore.data = drvdata; + drvdata->pstore= &sec_pmsg_pstore; + + return pstore_register(drvdata->pstore); +} + +static noinline void __pmsg_pstore_unregister(struct builder *bd) +{ + struct pmsg_drvdata *drvdata = + container_of(bd, struct pmsg_drvdata, bd); + + sec_pmsg_pstore.data = NULL; + + pstore_unregister(drvdata->pstore); +} + +static noinline int __pmsg_probe_epilog(struct builder *bd) +{ + struct pmsg_drvdata *drvdata = + container_of(bd, struct pmsg_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + sec_pmsg = drvdata; + + return 0; +} + +static noinline void __pmsg_remove_prolog(struct builder *bd) +{ + sec_pmsg = NULL; +} + +static int __pmsg_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct pmsg_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __pmsg_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct pmsg_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static const struct dev_builder __pmsg_dev_builder[] = { + DEVICE_BUILDER(__pmsg_parse_dt, NULL), + DEVICE_BUILDER(__pmsg_prepare_logger, NULL), + DEVICE_BUILDER(__pmsg_prepare_buffer, NULL), + DEVICE_BUILDER(__pmsg_prepare_carveout, NULL), + DEVICE_BUILDER(__pmsg_pstore_register, __pmsg_pstore_unregister), + DEVICE_BUILDER(__pmsg_probe_epilog, __pmsg_remove_prolog), +}; + +static int sec_pmsg_probe(struct platform_device *pdev) +{ + return __pmsg_probe(pdev, __pmsg_dev_builder, + ARRAY_SIZE(__pmsg_dev_builder)); +} + +static int sec_pmsg_remove(struct platform_device *pdev) +{ + return __pmsg_remove(pdev, __pmsg_dev_builder, + ARRAY_SIZE(__pmsg_dev_builder)); +} + +static const struct of_device_id sec_pmsg_match_table[] = { + { .compatible = "samsung,pstore_pmsg" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_pmsg_match_table); + +static struct platform_driver sec_pmsg_driver = { + .driver = { + .name = "sec,pmsg", + .of_match_table = of_match_ptr(sec_pmsg_match_table), + }, + .probe = sec_pmsg_probe, + .remove = sec_pmsg_remove, +}; + +static int __init sec_pmsg_init(void) +{ + return platform_driver_register(&sec_pmsg_driver); +} +module_init(sec_pmsg_init); + +static void __exit sec_pmsg_exit(void) +{ + platform_driver_unregister(&sec_pmsg_driver); +} +module_exit(sec_pmsg_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("PSTORE backend for saving android platform log"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/debug/pmsg/sec_pmsg.h b/drivers/samsung/debug/pmsg/sec_pmsg.h new file mode 100644 index 000000000000..14387df29840 --- /dev/null +++ b/drivers/samsung/debug/pmsg/sec_pmsg.h @@ -0,0 +1,61 @@ +#ifndef __INTERNAL__SEC_PMSG_H__ +#define __INTERNAL__SEC_PMSG_H__ + +#include +#include + +#define MAX_BUFFER_SIZE 1024 + +struct ss_pmsg_log_header_t { + uint8_t magic; + uint16_t len; + uint16_t uid; + uint16_t pid; +} __attribute__((__packed__)); + +struct ss_android_log_header_t { + unsigned char id; + uint16_t tid; + int32_t tv_sec; + int32_t tv_nsec; +} __attribute__((__packed__)); + +struct pmsg_logger { + uint16_t len; + uint16_t id; + uint16_t pid; + uint16_t tid; + uint16_t uid; + uint16_t level; + int32_t tv_sec; + int32_t tv_nsec; + union { + char msg[0]; + char __msg; /* 1 byte reserved area for 'unsigned char' request from user */ + }; +}; + +struct pmsg_buffer { + char buffer[MAX_BUFFER_SIZE]; +}; + +struct pmsg_drvdata { + struct builder bd; + struct reserved_mem *rmem; + phys_addr_t paddr; + size_t size; + bool nomap; + struct pstore_info *pstore; + struct pmsg_logger *logger; + struct pmsg_buffer __percpu *buf; +}; + +struct logger_level_header_ctx { + int cpu; + const char *comm; + u64 tv_kernel; + char *buffer; + size_t count; +}; + +#endif /* __INTERNAL__SEC_PMSG_H__ */ diff --git a/drivers/samsung/debug/qcom/Kconfig b/drivers/samsung/debug/qcom/Kconfig new file mode 100644 index 000000000000..48422839471e --- /dev/null +++ b/drivers/samsung/debug/qcom/Kconfig @@ -0,0 +1,14 @@ +source "drivers/samsung/debug/qcom/debug/Kconfig" +source "drivers/samsung/debug/qcom/reboot_cmd/Kconfig" +source "drivers/samsung/debug/qcom/dbg_partition/Kconfig" +source "drivers/samsung/debug/qcom/reboot_reason/Kconfig" +source "drivers/samsung/debug/qcom/upload_cause/Kconfig" +source "drivers/samsung/debug/qcom/logger/Kconfig" +source "drivers/samsung/debug/qcom/soc_id/Kconfig" +source "drivers/samsung/debug/qcom/wdt_core/Kconfig" +source "drivers/samsung/debug/qcom/summary/Kconfig" +source "drivers/samsung/debug/qcom/user_reset/Kconfig" +source "drivers/samsung/debug/qcom/smem/Kconfig" +source "drivers/samsung/debug/qcom/hw_param/Kconfig" +source "drivers/samsung/debug/qcom/rst_exinfo/Kconfig" +source "drivers/samsung/debug/qcom/mock/Kconfig" diff --git a/drivers/samsung/debug/qcom/Makefile b/drivers/samsung/debug/qcom/Makefile new file mode 100644 index 000000000000..5f6fee826535 --- /dev/null +++ b/drivers/samsung/debug/qcom/Makefile @@ -0,0 +1,14 @@ +obj-$(CONFIG_SEC_QC_DEBUG) += debug/ +obj-$(CONFIG_SEC_QC_RBCMD) += reboot_cmd/ +obj-$(CONFIG_SEC_QC_DEBUG_PARTITION) += dbg_partition/ +obj-$(CONFIG_SEC_QC_QCOM_REBOOT_REASON) += reboot_reason/ +obj-$(CONFIG_SEC_QC_UPLOAD_CAUSE) += upload_cause/ +obj-$(CONFIG_SEC_QC_LOGGER) += logger/ +obj-$(CONFIG_SEC_QC_SOC_ID) += soc_id/ +obj-$(CONFIG_SEC_QC_QCOM_WDT_CORE) += wdt_core/ +obj-$(CONFIG_SEC_QC_SUMMARY) += summary/ +obj-$(CONFIG_SEC_QC_USER_RESET) += user_reset/ +obj-$(CONFIG_SEC_QC_SMEM) += smem/ +obj-$(CONFIG_SEC_QC_HW_PARAM) += hw_param/ +obj-$(CONFIG_SEC_QC_RST_EXINFO) += rst_exinfo/ +obj-$(CONFIG_SEC_QC_MOCK) += mock/ diff --git a/drivers/samsung/debug/qcom/dbg_partition/Kconfig b/drivers/samsung/debug/qcom/dbg_partition/Kconfig new file mode 100644 index 000000000000..60429c7521b8 --- /dev/null +++ b/drivers/samsung/debug/qcom/dbg_partition/Kconfig @@ -0,0 +1,22 @@ +config SEC_QC_DEBUG_PARTITION + tristate "SEC Debug Partition for Qualcomm SoC based models" + help + TODO: help is not ready. + +config SEC_QC_DBG_PARTITION_TEST_FOR_ON_DEVICE + tristate "KUnit test for sec_qc_dbg_partition_test" + depends on KUNIT + depends on SEC_QC_DEBUG_PARTITION + help + TODO: Describe config fully. + If you run this test driver on device, SHOULD set this config as 'm' to build test driver modulraly. + +config SEC_QC_DBG_PARTITION_TEST_FOR_ONLY_UML + tristate "KUnit test for sec_qc_dbg_partition_test" + depends on KUNIT + depends on UML + depends on SEC_QC_DEBUG_PARTITION + help + TODO: Describe config fully. + This CONFIG is recommended to set to y. + diff --git a/drivers/samsung/debug/qcom/dbg_partition/Makefile b/drivers/samsung/debug/qcom/dbg_partition/Makefile new file mode 100644 index 000000000000..ae4497feb63c --- /dev/null +++ b/drivers/samsung/debug/qcom/dbg_partition/Makefile @@ -0,0 +1,4 @@ +obj-$(CONFIG_SEC_QC_DEBUG_PARTITION) += sec_qc_dbg_partition.o +CFLAGS_sec_qc_dbg_partition.o = -I$(srctree) + +GCOV_PROFILE_sec_qc_dbg_partition.o := $(CONFIG_KUNIT) diff --git a/drivers/samsung/debug/qcom/dbg_partition/sec_qc_dbg_partition.c b/drivers/samsung/debug/qcom/dbg_partition/sec_qc_dbg_partition.c new file mode 100644 index 000000000000..3458d2a92da9 --- /dev/null +++ b/drivers/samsung/debug/qcom/dbg_partition/sec_qc_dbg_partition.c @@ -0,0 +1,674 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2016-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "sec_qc_dbg_partition.h" + +static struct qc_dbg_part_drvdata *qc_dbg_part; + +static __always_inline bool __dbg_part_is_probed(void) +{ + return !!qc_dbg_part; +} + +__ss_static struct qc_dbg_part_info dbg_part_info[DEBUG_PART_MAX_TABLE] __ro_after_init = { + QC_DBG_PART_INFO(debug_index_reset_header, + DEBUG_PART_OFFSET_FROM_DT, + sizeof(struct debug_reset_header), + O_RDWR), + QC_DBG_PART_INFO(debug_index_reset_ex_info, + DEBUG_PART_OFFSET_FROM_DT, + sizeof(rst_exinfo_t), + O_RDONLY), + QC_DBG_PART_INFO(debug_index_ap_health, + DEBUG_PART_OFFSET_FROM_DT, + sizeof(ap_health_t), + O_RDWR), + QC_DBG_PART_INFO(debug_index_lcd_debug_info, + DEBUG_PART_OFFSET_FROM_DT, + sizeof(struct lcd_debug_t), + O_RDWR), + QC_DBG_PART_INFO(debug_index_reset_history, + DEBUG_PART_OFFSET_FROM_DT, + SEC_DEBUG_RESET_HISTORY_SIZE, + O_RDONLY), + QC_DBG_PART_INFO(debug_index_onoff_history, + DEBUG_PART_OFFSET_FROM_DT, + sizeof(onoff_history_t), + O_RDWR), + QC_DBG_PART_INFO(debug_index_reset_tzlog, + DEBUG_PART_OFFSET_FROM_DT, + DEBUG_PART_SIZE_FROM_DT, + O_RDONLY), + QC_DBG_PART_INFO(debug_index_reset_extrc_info, + DEBUG_PART_OFFSET_FROM_DT, + SEC_DEBUG_RESET_EXTRC_SIZE, + O_RDONLY), + QC_DBG_PART_INFO(debug_index_auto_comment, + DEBUG_PART_OFFSET_FROM_DT, + SEC_DEBUG_AUTO_COMMENT_SIZE, + O_RDONLY), + QC_DBG_PART_INFO(debug_index_reset_rkplog, + DEBUG_PART_OFFSET_FROM_DT, + SEC_DEBUG_RESET_ETRM_SIZE, + O_RDONLY), + QC_DBG_PART_INFO(debug_index_modem_info, + DEBUG_PART_OFFSET_FROM_DT, + sizeof(struct sec_qc_summary_data_modem), + O_RDONLY), + QC_DBG_PART_INFO(debug_index_reset_klog, + DEBUG_PART_OFFSET_FROM_DT, + SEC_DEBUG_RESET_KLOG_SIZE, + O_RDWR), + QC_DBG_PART_INFO(debug_index_reset_lpm_klog, + DEBUG_PART_OFFSET_FROM_DT, + SEC_DEBUG_RESET_LPM_KLOG_SIZE, + O_WRONLY), + QC_DBG_PART_INFO(debug_index_reset_summary, + DEBUG_PART_OFFSET_FROM_DT, + DEBUG_PART_SIZE_FROM_DT, + O_RDONLY), +}; + +__ss_static bool ____dbg_part_is_valid_index(size_t index, + const struct qc_dbg_part_info *info) +{ + size_t size; + + if (index >= DEBUG_PART_MAX_TABLE) + return false; + + size = info[index].size; + if (!size || size >= DEBUG_PART_SIZE_FROM_DT) + return false; + + return true; +} + +static bool __dbg_part_is_valid_index(size_t index) +{ + return ____dbg_part_is_valid_index(index, dbg_part_info); +} + +__ss_static ssize_t __dbg_part_get_size(size_t index, + const struct qc_dbg_part_info *info) +{ + if (!____dbg_part_is_valid_index(index, info)) + return -EINVAL; + + return info[index].size; +} + +ssize_t sec_qc_dbg_part_get_size(size_t index) +{ + if (!__dbg_part_is_probed()) + return -EBUSY; + + return __dbg_part_get_size(index, dbg_part_info); +} +EXPORT_SYMBOL_GPL(sec_qc_dbg_part_get_size); + +/* NOTE: see fs/pstore/blk.c of linux-5.10.y */ +static ssize_t __dbg_part_blk_read(struct qc_dbg_part_drvdata *drvdata, + void *buf, size_t bytes, loff_t pos) +{ + struct block_device *bdev = drvdata->bdev; + struct file file; + struct kiocb kiocb; + struct iov_iter iter; + struct kvec iov = {.iov_base = buf, .iov_len = bytes}; + + memset(&file, 0, sizeof(struct file)); + file.f_mapping = bdev->bd_inode->i_mapping; + file.f_flags = O_DIRECT | O_SYNC | O_NOATIME; + file.f_inode = bdev->bd_inode; + file_ra_state_init(&file.f_ra, file.f_mapping); + + init_sync_kiocb(&kiocb, &file); + kiocb.ki_pos = pos; + iov_iter_kvec(&iter, READ, &iov, 1, bytes); + + return generic_file_read_iter(&kiocb, &iter); +} + +static bool __dbg_part_read(struct qc_dbg_part_drvdata *drvdata, + size_t index, void *value) +{ + struct device *dev = drvdata->bd.dev; + struct qc_dbg_part_info *info; + ssize_t read; + + info = &dbg_part_info[index]; + if (info->flags != O_RDONLY && info->flags != O_RDWR) { + dev_warn(dev, "read operation is not permitted for idx:%zu\n", + index); + return false; + } + + read = __dbg_part_blk_read(drvdata, + value, info->size, info->offset); + if (read < 0) { + dev_warn(dev, "read faield (idx:%zu, err:%zd)\n", index, read); + return false; + } else if (read != info->size) { + dev_warn(dev, "wrong size (idx:%zu) - requested(%zu) != read(%zd)\n", + index, info->size, read); + return false; + } + + return true; +} + +bool sec_qc_dbg_part_read(size_t index, void *value) +{ + if (!__dbg_part_is_probed()) + return false; + + if (!__dbg_part_is_valid_index(index)) + return false; + + return __dbg_part_read(qc_dbg_part, index, value); +} +EXPORT_SYMBOL_GPL(sec_qc_dbg_part_read); + +/* NOTE: this is a copy of 'blkdev_fsync' of 'block/fops.c' */ +static int __dbg_part_blkdev_fsync(struct file *filp, loff_t start, loff_t end, + int datasync) +{ + struct block_device *bdev = filp->private_data; + int error; + + error = file_write_and_wait_range(filp, start, end); + if (error) + return error; + + /* + * There is no need to serialise calls to blkdev_issue_flush with + * i_mutex and doing so causes performance issues with concurrent + * O_SYNC writers to a block device. + */ + error = blkdev_issue_flush(bdev); + if (error == -EOPNOTSUPP) + error = 0; + + return error; +} + +/* NOTE: see fs/pstore/blk.c of linux-5.10.y */ +static ssize_t __dbg_part_blk_write(struct qc_dbg_part_drvdata *drvdata, + const void *buf, size_t bytes, loff_t pos ) +{ + struct block_device *bdev = drvdata->bdev; + struct iov_iter iter; + struct kiocb kiocb; + struct file file; + ssize_t ret; + struct kvec iov = {.iov_base = (void *)buf, .iov_len = bytes}; + + /* Console/Ftrace backend may handle buffer until flush dirty zones */ + if (in_interrupt() || irqs_disabled()) + return -EBUSY; + + memset(&file, 0, sizeof(struct file)); + file.private_data = bdev; + file.f_mapping = bdev->bd_inode->i_mapping; + file.f_flags = O_DIRECT | O_SYNC | O_NOATIME; + file.f_inode = bdev->bd_inode; + file.f_iocb_flags = iocb_flags(&file); + + init_sync_kiocb(&kiocb, &file); + kiocb.ki_pos = pos; + iov_iter_kvec(&iter, WRITE, &iov, 1, bytes); + + inode_lock(bdev->bd_inode); + ret = generic_write_checks(&kiocb, &iter); + if (ret > 0) + ret = generic_perform_write(&kiocb, &iter); + inode_unlock(bdev->bd_inode); + + if (likely(ret > 0)) { + const struct file_operations f_op = { + .fsync = __dbg_part_blkdev_fsync, + }; + + file.f_op = &f_op; + kiocb.ki_pos += ret; + ret = generic_write_sync(&kiocb, ret); + } + return ret; +} + +static bool __dbg_part_write(struct qc_dbg_part_drvdata *drvdata, + size_t index, const void *value) +{ + struct device *dev = drvdata->bd.dev; + struct qc_dbg_part_info *info; + ssize_t write; + + info = &dbg_part_info[index]; + if (info->flags != O_WRONLY && info->flags != O_RDWR) { + dev_warn(dev, "write operation is not permitted for idx:%zu\n", + index); + return false; + } + + write = __dbg_part_blk_write(drvdata, + value, info->size, info->offset); + if (write < 0) { + dev_warn(dev, "write faield (idx:%zu, err:%zd)\n", index, write); + return false; + } else if (write != info->size) { + dev_warn(dev, "wrong size (idx:%zu) - requested(%zu) != write(%zd)\n", + index, info->size, write); + return false; + } + + return true; +} + +bool sec_qc_dbg_part_write(size_t index, const void *value) +{ + if (!__dbg_part_is_probed()) + return false; + + if (!__dbg_part_is_valid_index(index)) + return false; + + return __dbg_part_write(qc_dbg_part, index, value); +} +EXPORT_SYMBOL_GPL(sec_qc_dbg_part_write); + +static const char *sec_part_table = "sec,part_table"; + +static noinline int __dbg_part_parse_dt_part_table_entry(struct device *dev, + struct device_node *np, u32 idx, u32 *offsetp, u32 *sizep) +{ + u32 offset, size; + int err; + + err = of_property_read_u32_index(np, sec_part_table, idx * 2, &offset); + if (err) { + dev_err(dev, "%s %d offset read error - %d\n", + sec_part_table, idx, err); + return -EINVAL; + } + + err = of_property_read_u32_index(np, sec_part_table, idx * 2 + 1, &size); + if (err) { + dev_err(dev, "%s %d size read error - %d\n", + sec_part_table, idx, err); + return -EINVAL; + } + + if (offset + size > SEC_DEBUG_PARTITION_SIZE) { + dev_err(dev, "%s oversize 0x%x\n", + sec_part_table, offset + size); + return -EINVAL; + } + + *offsetp = offset; + *sizep = size; + + return 0; +} + +static void __dbg_part_info_set_offset(struct qc_dbg_part_info *info, + u32 offset) +{ + if (info->offset == DEBUG_PART_OFFSET_FROM_DT) + info->offset = offset; +} + +static void __dbg_part_info_set_size(struct qc_dbg_part_info *info, + u32 size) +{ + if (info->size == DEBUG_PART_SIZE_FROM_DT) + info->size = size; +} + +__ss_static noinline int ____dbg_part_parse_dt_part_table(struct builder *bd, + struct device_node *np, struct qc_dbg_part_info *info) +{ + int len; + u32 i, offset, size; + struct device *dev = bd->dev; + int err; + + of_get_property(np, sec_part_table, &len); + if (!len) { + dev_err(dev, "part-table node is not in device tree\n"); + return -ENODEV; + } + + if (len != DEBUG_PART_MAX_TABLE * 2 * sizeof(u32)) { + dev_err(dev, "%s has wrong size\n", sec_part_table); + return -EINVAL; + } + + for (i = 0; i < DEBUG_PART_MAX_TABLE; i++) { + err = __dbg_part_parse_dt_part_table_entry(dev, + np, i, &offset, &size); + if (err) + return err; + + __dbg_part_info_set_offset(&info[i], offset); + __dbg_part_info_set_size(&info[i], size); + } + + return 0; +} + +static noinline int __dbg_part_parse_dt_part_table(struct builder *bd, + struct device_node *np) +{ + return ____dbg_part_parse_dt_part_table(bd, np, dbg_part_info); +} + +__ss_static noinline int __dbg_part_parse_dt_bdev_path(struct builder *bd, + struct device_node *np) +{ + struct qc_dbg_part_drvdata *drvdata = + container_of(bd, struct qc_dbg_part_drvdata, bd); + + return of_property_read_string(np, "sec,bdev_path", + &drvdata->bdev_path); +} + +static const struct dt_builder __dbg_part_dt_builder[] = { + DT_BUILDER(__dbg_part_parse_dt_bdev_path), + DT_BUILDER(__dbg_part_parse_dt_part_table), +}; + +static noinline int __dbg_part_parse_dt(struct builder *bd) +{ + return sec_director_parse_dt(bd, __dbg_part_dt_builder, + ARRAY_SIZE(__dbg_part_dt_builder)); +} + +static noinline int __dbg_part_probe_prolog(struct builder *bd) +{ + struct qc_dbg_part_drvdata *drvdata = + container_of(bd, struct qc_dbg_part_drvdata, bd); + struct device *dev = bd->dev; + fmode_t mode = FMODE_READ | FMODE_WRITE; + struct block_device *bdev; + sector_t nr_sects; + int err; + + bdev = blkdev_get_by_path(drvdata->bdev_path, mode, NULL); + if (IS_ERR(bdev)) { + dev_t devt; + + devt = name_to_dev_t(drvdata->bdev_path); + if (devt == 0) { + dev_warn(dev, "'name_to_dev_t' failed!\n"); + err = -EPROBE_DEFER; + goto err_blkdev; + } + + bdev = blkdev_get_by_dev(devt, mode, NULL); + if (IS_ERR(bdev)) { + dev_warn(dev, "'blkdev_get_by_dev' failed! (%ld)\n", + PTR_ERR(bdev)); + err = -EPROBE_DEFER; + goto err_blkdev; + } + } + + nr_sects = bdev_nr_sectors(bdev); + if (!nr_sects) { + dev_err(dev, "not enough space for %s\n", + drvdata->bdev_path); + blkdev_put(bdev, mode); + return -ENOSPC; + } + + drvdata->bdev = bdev; + + return 0; + +err_blkdev: + dev_err(dev, "can't find a block device - %s\n", + drvdata->bdev_path); + return err; +} + +static noinline int __dbg_part_init_reset_header(struct builder *bd) +{ + struct qc_dbg_part_drvdata *drvdata = + container_of(bd, struct qc_dbg_part_drvdata, bd); + struct debug_reset_header __reset_header; + struct debug_reset_header *reset_header = &__reset_header; + bool valid; + + valid = __dbg_part_read(drvdata, + debug_index_reset_summary_info, reset_header); + if (!valid) + return -EINVAL; + + if (reset_header->magic == DEBUG_PARTITION_MAGIC) + /* OK, already initialized, skip this */ + return 0; + + /* NOTE: debug partition is not initialized. */ + memset(reset_header, 0, sizeof(*reset_header)); + reset_header->magic = DEBUG_PARTITION_MAGIC; + valid = __dbg_part_write(drvdata, + debug_index_reset_summary_info, reset_header); + if (!valid) + return -EINVAL; + + return 0; +} + +static noinline void __dbg_part_remove_epilog(struct builder *bd) +{ + struct qc_dbg_part_drvdata *drvdata = + container_of(bd, struct qc_dbg_part_drvdata, bd); + fmode_t mode = FMODE_READ | FMODE_WRITE; + + blkdev_put(drvdata->bdev, mode); +} + +static noinline int __dbg_part_probe_epilog(struct builder *bd) +{ + struct qc_dbg_part_drvdata *drvdata = + container_of(bd, struct qc_dbg_part_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + qc_dbg_part = drvdata; + + return 0; +} + +static noinline void __dbg_part_remove_prolog(struct builder *bd) +{ + /* FIXME: This is not a graceful exit. */ + qc_dbg_part = NULL; +} + +static int __dbg_part_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct qc_dbg_part_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __dbg_part_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct qc_dbg_part_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +#if IS_ENABLED(CONFIG_DEBUG_FS) +static void __dbg_part_dbgfs_show_bdev(struct seq_file *m) +{ + struct qc_dbg_part_drvdata *drvdata = m->private; + struct block_device *bdev = drvdata->bdev; + + seq_puts(m, "* Block Device :\n"); + seq_printf(m, " - bdevname : %pg\n", bdev); + seq_printf(m, " - uuid : %s\n", bdev->bd_meta_info->uuid); + seq_printf(m, " - volname : %s\n", bdev->bd_meta_info->volname); + seq_puts(m, "\n"); +} + +static void __dbg_part_dbgfs_show_each(struct seq_file *m, size_t index) +{ + struct qc_dbg_part_info *info = &dbg_part_info[index]; + uint8_t *buf; + + if (!info->size) + return; + + seq_printf(m, "[%zu] = %s\n", index, info->name); + seq_printf(m, " - offset : %zu\n", (size_t)info->offset); + seq_printf(m, " - size : %zu\n", info->size); + + buf = kvmalloc(info->size, GFP_KERNEL); + if (!sec_qc_dbg_part_read(index, buf)) { + seq_puts(m, " - failed to read debug partition!\n"); + goto warn_read_fail; + } + + seq_hex_dump(m, " + ", DUMP_PREFIX_OFFSET, 16, 1, + buf, info->size, true); + +warn_read_fail: + seq_puts(m, "\n"); + kvfree(buf); +} + +static int sec_qc_dbg_part_dbgfs_show_all(struct seq_file *m, void *unsed) +{ + size_t i; + + __dbg_part_dbgfs_show_bdev(m); + + for (i = 0; i < DEBUG_PART_MAX_TABLE; i++) + __dbg_part_dbgfs_show_each(m, i); + + return 0; +} + +static int sec_qc_dbg_part_dbgfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, sec_qc_dbg_part_dbgfs_show_all, + inode->i_private); +} + +static const struct file_operations sec_qc_dbg_part_dgbfs_fops = { + .open = sec_qc_dbg_part_dbgfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static int __dbg_part_debugfs_create(struct builder *bd) +{ + struct qc_dbg_part_drvdata *drvdata = + container_of(bd, struct qc_dbg_part_drvdata, bd); + + drvdata->dbgfs = debugfs_create_file("sec_qc_dbg_part", 0440, + NULL, drvdata, &sec_qc_dbg_part_dgbfs_fops); + + return 0; +} + +static void __dbg_part_debugfs_remove(struct builder *bd) +{ + struct qc_dbg_part_drvdata *drvdata = + container_of(bd, struct qc_dbg_part_drvdata, bd); + + debugfs_remove(drvdata->dbgfs); +} +#else +static int __dbg_part_debugfs_create(struct builder *bd) { return 0; } +static void __dbg_part_debugfs_remove(struct builder *bd) {} +#endif + +static const struct dev_builder __dbg_part_dev_builder[] = { + DEVICE_BUILDER(__dbg_part_parse_dt, NULL), + DEVICE_BUILDER(__dbg_part_probe_prolog, __dbg_part_remove_epilog), + DEVICE_BUILDER(__dbg_part_init_reset_header, NULL), + DEVICE_BUILDER(__dbg_part_debugfs_create, + __dbg_part_debugfs_remove), + DEVICE_BUILDER(__dbg_part_probe_epilog, __dbg_part_remove_prolog), +}; + +static int sec_qc_dbg_part_probe(struct platform_device *pdev) +{ + return __dbg_part_probe(pdev, __dbg_part_dev_builder, + ARRAY_SIZE(__dbg_part_dev_builder)); +} + +static int sec_qc_dbg_part_remove(struct platform_device *pdev) +{ + return __dbg_part_remove(pdev, __dbg_part_dev_builder, + ARRAY_SIZE(__dbg_part_dev_builder)); +} + +static const struct of_device_id sec_qc_dbg_part_match_table[] = { + { .compatible = "samsung,qcom-debug_partition" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_qc_dbg_part_match_table); + +static struct platform_driver sec_qc_dbg_part_driver = { + .driver = { + .name = "sec,qc-debug_partition", + .of_match_table = of_match_ptr(sec_qc_dbg_part_match_table), + }, + .probe = sec_qc_dbg_part_probe, + .remove = sec_qc_dbg_part_remove, +}; + +static int __init sec_qc_dbg_part_init(void) +{ + return platform_driver_register(&sec_qc_dbg_part_driver); +} +module_init(sec_qc_dbg_part_init); + +static void __exit sec_qc_dbg_part_exit(void) +{ + platform_driver_unregister(&sec_qc_dbg_part_driver); +} +module_exit(sec_qc_dbg_part_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("SEC debug partition & Qualcomm based devices"); +MODULE_LICENSE("GPL v2"); + +MODULE_SOFTDEP("pre: ufs_qcom"); diff --git a/drivers/samsung/debug/qcom/dbg_partition/sec_qc_dbg_partition.h b/drivers/samsung/debug/qcom/dbg_partition/sec_qc_dbg_partition.h new file mode 100644 index 000000000000..411d3680038c --- /dev/null +++ b/drivers/samsung/debug/qcom/dbg_partition/sec_qc_dbg_partition.h @@ -0,0 +1,36 @@ +#ifndef __INTERNAL__SEC_QC_DBG_PARTITION_H__ +#define __INTERNAL__SEC_QC_DBG_PARTITION_H__ + +#include +#include + +#include + +struct qc_dbg_part_drvdata { + struct builder bd; + const char *bdev_path; + struct block_device *bdev; +#if IS_ENABLED(CONFIG_DEBUG_FS) + struct dentry *dbgfs; +#endif +}; + +#define QC_DBG_PART_INFO(__index, __offset, __size, __flags) \ + [__index] = { \ + .name = #__index, \ + .offset = __offset, \ + .size = __size, \ + .flags = __flags, \ + } + +struct qc_dbg_part_info { + const char *name; + loff_t offset; + size_t size; + int flags; +}; + +#define DEBUG_PART_OFFSET_FROM_DT SEC_DEBUG_PARTITION_SIZE +#define DEBUG_PART_SIZE_FROM_DT SEC_DEBUG_PARTITION_SIZE + +#endif /* __INTERNAL__SEC_QC_DBG_PARTITION_H__ */ diff --git a/drivers/samsung/debug/qcom/debug/Kconfig b/drivers/samsung/debug/qcom/debug/Kconfig new file mode 100644 index 000000000000..66dd504136d9 --- /dev/null +++ b/drivers/samsung/debug/qcom/debug/Kconfig @@ -0,0 +1,9 @@ +config SEC_QC_DEBUG + tristate "SEC Common debugging feature for Qualcomm based devices" + help + TODO: help is not ready. + +config SEC_QC_NR_CPUS + int "Maximum number of CPUs for debugging features" + range 2 8 + default "8" diff --git a/drivers/samsung/debug/qcom/debug/Makefile b/drivers/samsung/debug/qcom/debug/Makefile new file mode 100644 index 000000000000..f5666eff291f --- /dev/null +++ b/drivers/samsung/debug/qcom/debug/Makefile @@ -0,0 +1,10 @@ +obj-$(CONFIG_SEC_QC_DEBUG) += sec_qc_debug.o +sec_qc_debug-objs := sec_qc_debug_main.o \ + sec_qc_debug_lpm_log.o \ + sec_qc_debug_reboot.o \ + sec_qc_force_err.o \ + sec_qc_boot_stat.o \ + sec_qc_cp_dump_encrypt.o \ + sec_qc_arch_vendor_hooks.o + +sec_qc_debug-$(CONFIG_ARM64) += sec_qc_arm64_vendor_hooks.o diff --git a/drivers/samsung/debug/qcom/debug/sec_qc_arch_vendor_hooks.c b/drivers/samsung/debug/qcom/debug/sec_qc_arch_vendor_hooks.c new file mode 100644 index 000000000000..0c116c838bb2 --- /dev/null +++ b/drivers/samsung/debug/qcom/debug/sec_qc_arch_vendor_hooks.c @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#include "sec_qc_debug.h" + +int __weak sec_qc_vendor_hooks_init(struct builder *bd) +{ + return 0; +} diff --git a/drivers/samsung/debug/qcom/debug/sec_qc_arm64_vendor_hooks.c b/drivers/samsung/debug/qcom/debug/sec_qc_arm64_vendor_hooks.c new file mode 100644 index 000000000000..0755f2b61f73 --- /dev/null +++ b/drivers/samsung/debug/qcom/debug/sec_qc_arm64_vendor_hooks.c @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2022-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#include + +#include + +#include "sec_qc_debug.h" + +/* NOTE: BACKPORT from android12-5.10/msm-kernel, qcom_cpu_vendor_hooks.c */ +static void sec_trace_android_rvh_do_undefinstr(void *unused, + struct pt_regs *regs, unsigned long esr) +{ + if (user_mode(regs)) + return; + + sec_qc_memdbg_dump_instr("PC", regs->pc); + sec_qc_memdbg_dump_instr("LR", ptrauth_strip_insn_pac(regs->regs[30])); + sec_qc_memdbg_show_regs_min(regs); +} + +int sec_qc_vendor_hooks_init(struct builder *bd) +{ + struct device *dev = bd->dev; + int err; + + err = register_trace_android_rvh_do_undefinstr( + sec_trace_android_rvh_do_undefinstr, NULL); + if (err) + dev_warn(dev, "Failed to android_rvh_do_undefinstr hook\n"); + + return 0; +} diff --git a/drivers/samsung/debug/qcom/debug/sec_qc_boot_stat.c b/drivers/samsung/debug/qcom/debug/sec_qc_boot_stat.c new file mode 100644 index 000000000000..13494986418b --- /dev/null +++ b/drivers/samsung/debug/qcom/debug/sec_qc_boot_stat.c @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2021-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include + +#include "sec_qc_debug.h" + +/* NOTE: see 'drivers/soc/qcom/boot_stat.c' */ +struct boot_stats { + uint32_t bootloader_start; + uint32_t bootloader_end; + uint32_t bootloader_display; + uint32_t bootloader_load_kernel; +}; + +static uint32_t bootloader_start; +static uint32_t bootloader_end; +static uint32_t bootloader_display; +static uint32_t bootloader_load_kernel; +static uint32_t clock_freq; +static unsigned long long ktime_delta; + +static int __boot_stat_init_boot_stats(void) +{ + struct device_node *np_imem; + struct boot_stats __iomem *boot_stats; + + np_imem = of_find_compatible_node(NULL, NULL, + "qcom,msm-imem-boot_stats"); + if (!np_imem) { + pr_warn("can't find qcom,msm-imem node\n"); + return -ENODEV; + } + + boot_stats = of_iomap(np_imem, 0); + if (!boot_stats) { + pr_warn("boot_stats: Can't map imem\n"); + return -ENXIO; + } + + bootloader_start = readl_relaxed(&boot_stats->bootloader_start); + bootloader_end = readl_relaxed(&boot_stats->bootloader_end); + bootloader_display = readl_relaxed(&boot_stats->bootloader_display); + bootloader_load_kernel = readl_relaxed( + &boot_stats->bootloader_load_kernel); + + iounmap(boot_stats); + + return 0; +} + +static unsigned int __boot_stat_counter_to_msec(unsigned int counter) +{ + /* (second / HZ) * 1000 */ + return counter * 1000 / clock_freq; +} + +static int __boot_stat_init_mpm_data(void) +{ + struct device_node *np_mpm2; + void __iomem *counter_base; + uint32_t counter; + unsigned long long ktime; + + np_mpm2 = of_find_compatible_node(NULL, NULL, + "qcom,mpm2-sleep-counter"); + + if (!np_mpm2) { + pr_warn("mpm_counter: can't find DT node\n"); + return -ENODEV; + } + + if (of_property_read_u32(np_mpm2, "clock-frequency", &clock_freq)) { + pr_warn("mpm_counter: can't read clock-frequncy\n"); + return -ENOENT; + } + + if (!of_get_address(np_mpm2, 0, NULL, NULL)) { + pr_warn("mpm_counter: can't find resource"); + return -ENOENT; + } + + counter_base = of_iomap(np_mpm2, 0); + if (!counter_base) { + pr_warn("mpm_counter: cant map counter base\n"); + return -ENXIO; + } + + counter = readl_relaxed(counter_base); + ktime = local_clock(); + + ktime_delta = __boot_stat_counter_to_msec(counter); + ktime_delta *= 1000000ULL; + ktime_delta -= ktime; + + return 0; +} + +static unsigned long long sec_qc_boot_stat_ktime_to_time(unsigned long long ktime) +{ + return ktime + ktime_delta; +} + +static void sec_qc_boot_stat_show_on_boot_stat(struct seq_file *m) +{ + seq_printf(m, "%-46s%11u%13u%13u\n", "!@Boot: Bootloader start", + __boot_stat_counter_to_msec(bootloader_start), 0, 0); + seq_printf(m, "%-46s%11u%13u%13u\n", "!@Boot: Bootloader display", + __boot_stat_counter_to_msec(bootloader_display), 0, 0); + seq_printf(m, "%-46s%11u%13u%13u\n", "!@Boot: Bootloader load kernel", + __boot_stat_counter_to_msec(bootloader_load_kernel), 0, 0); + seq_printf(m, "%-46s%11u%13u%13u\n", "!@Boot: Bootloader end", + __boot_stat_counter_to_msec(bootloader_end), 0, 0); +} + +static void sec_qc_enhanced_boot_stat_show_on_boot_stat(struct seq_file *m) +{ + seq_printf(m, "%-90s%7llu%7llu%7llu\n", "!@Boot_EBS_B: Bootloader start", + __boot_stat_counter_to_msec(bootloader_start), 0, __boot_stat_counter_to_msec(bootloader_start)); + seq_printf(m, "%-90s%7llu%7llu%7llu\n", "!@Boot_EBS_B: Bootloader display", + __boot_stat_counter_to_msec(bootloader_display), 0, + __boot_stat_counter_to_msec(bootloader_display) - __boot_stat_counter_to_msec(bootloader_start)); + seq_printf(m, "%-90s%7llu%7llu%7llu\n", "!@Boot_EBS_B: Bootloader load kernel", + __boot_stat_counter_to_msec(bootloader_load_kernel), 0, + __boot_stat_counter_to_msec(bootloader_load_kernel) - __boot_stat_counter_to_msec(bootloader_display)); + seq_printf(m, "%-90s%7llu%7llu%7llu\n", "!@Boot_EBS_B: Bootloader end", + __boot_stat_counter_to_msec(bootloader_end), 0, + __boot_stat_counter_to_msec(bootloader_end) - __boot_stat_counter_to_msec(bootloader_load_kernel)); +} + +int sec_qc_boot_stat_init(struct builder *bd) +{ + struct qc_debug_drvdata *drvdata = + container_of(bd, struct qc_debug_drvdata, bd); + struct device *dev = bd->dev; + struct sec_boot_stat_soc_operations *boot_stat_ops; + int err; + + err = __boot_stat_init_boot_stats(); + if (err) + /* NOTE: soc specific operations will be ignored */ + return 0; + + err = __boot_stat_init_mpm_data(); + if (err) + /* NOTE: soc specific operations will be ignored */ + return 0; + + boot_stat_ops = devm_kzalloc(dev, sizeof(*boot_stat_ops), GFP_KERNEL); + if (!boot_stat_ops) + return -ENOMEM; + + boot_stat_ops->ktime_to_time = sec_qc_boot_stat_ktime_to_time; + boot_stat_ops->show_on_boot_stat = sec_qc_boot_stat_show_on_boot_stat; + boot_stat_ops->show_on_enh_boot_stat = sec_qc_enhanced_boot_stat_show_on_boot_stat; + + err = sec_boot_stat_register_soc_ops(boot_stat_ops); + if (err == -EBUSY) + return -EPROBE_DEFER; + else if (err) + return err; + + drvdata->boot_stat_ops = boot_stat_ops; + + return 0; +} + +void sec_qc_boot_stat_exit(struct builder *bd) +{ + struct qc_debug_drvdata *drvdata = + container_of(bd, struct qc_debug_drvdata, bd); + struct sec_boot_stat_soc_operations *boot_stat_ops = + drvdata->boot_stat_ops; + + if (boot_stat_ops) + sec_boot_stat_unregister_soc_ops(boot_stat_ops); +} diff --git a/drivers/samsung/debug/qcom/debug/sec_qc_cp_dump_encrypt.c b/drivers/samsung/debug/qcom/debug/sec_qc_cp_dump_encrypt.c new file mode 100644 index 000000000000..435aa2f02d72 --- /dev/null +++ b/drivers/samsung/debug/qcom/debug/sec_qc_cp_dump_encrypt.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2022-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include + +#include + +#include "sec_qc_debug.h" + +#define ENCRYPT_CP_REGION 0x1 +#define CLEAR_CP_REGION 0x0 + +/* kernel.sec_qc_debug.sec_cp_dbg_level */ +static unsigned int sec_cp_dbg_level __ro_after_init; +module_param_named(cp_debug_level, sec_cp_dbg_level, uint, 0440); + +static unsigned int sec_qc_cp_dump_policy; +module_param_named(cp_dump_policy, sec_qc_cp_dump_policy, uint, 0440); + +static bool sec_cp_debug_is_on(void) +{ + if (sec_cp_dbg_level == 0x55FF) + return false; + return true; +} + +static int __cp_dump_encrypt_init_each( + const struct cp_dump_encrypt_entry *entry, + struct device *dev) +{ + struct device_node *np_imem; + void __iomem *cp_dump_encrypt; + unsigned int debug_level; + unsigned int policy; + + np_imem = of_find_compatible_node(NULL, NULL, entry->compatible); + if (!np_imem) { + pr_warn("can't find qcom,msm-imem node\n"); + return -ENODEV; + } + + cp_dump_encrypt = of_iomap(np_imem, 0); + if (!cp_dump_encrypt) { + dev_err(dev, "Can't map imem\n"); + return -ENXIO; + } + + debug_level = sec_debug_level(); + if ((debug_level == SEC_DEBUG_LEVEL_MID || + debug_level == SEC_DEBUG_LEVEL_HIGH) && sec_cp_debug_is_on()) + policy = entry->mid_high; + else + policy = entry->low; + + __raw_writel(policy, cp_dump_encrypt); + iounmap(cp_dump_encrypt); + + sec_qc_cp_dump_policy = policy; + + return 0; +} + +static int __cp_dump_encrypt_init(struct cp_dump_encrypt *encrypt, + struct device *dev) +{ + int i; + + for (i = 0; i < encrypt->nr_entries; i++) { + int err = __cp_dump_encrypt_init_each(&encrypt->entry[i], dev); + if (err) + return err; + } + + return 0; +} + +int sec_qc_cp_dump_encrypt_init(struct builder *bd) +{ + struct qc_debug_drvdata *drvdata = + container_of(bd, struct qc_debug_drvdata, bd); + + if (!drvdata->use_cp_dump_encrypt) + return 0; + + return __cp_dump_encrypt_init(&drvdata->cp_dump_encrypt, bd->dev); +} diff --git a/drivers/samsung/debug/qcom/debug/sec_qc_debug.h b/drivers/samsung/debug/qcom/debug/sec_qc_debug.h new file mode 100644 index 000000000000..d336e878d575 --- /dev/null +++ b/drivers/samsung/debug/qcom/debug/sec_qc_debug.h @@ -0,0 +1,57 @@ +#ifndef __INTERNAL__SEC_QC_DEBUG_H__ +#define __INTERNAL__SEC_QC_DEBUG_H__ + +#include +#include + +#include +#include + +#include "sec_qc_debug_mock.h" + +struct cp_dump_encrypt_entry { + const char *compatible; + unsigned int low; + unsigned int mid_high; +}; + +struct cp_dump_encrypt { + int nr_entries; + struct cp_dump_encrypt_entry *entry; +}; + +struct qc_debug_drvdata { + struct builder bd; + struct notifier_block reboot_nb; + struct notifier_block die_nb; + bool use_cp_dump_encrypt; + struct cp_dump_encrypt cp_dump_encrypt; + bool use_store_last_kmsg; + bool use_store_lpm_kmsg; + bool use_store_onoff_history; + bool is_lpm_mode; + struct sec_boot_stat_soc_operations *boot_stat_ops; +}; + +/* sec_qc_lpm_log.c */ +extern int sec_qc_debug_lpm_log_init(struct builder *bd); + +/* sec_qc_debug_reboot.c */ +extern int sec_qc_debug_reboot_init(struct builder *bd); +extern void sec_qc_debug_reboot_exit(struct builder *bd); + +/* sec_qc_force_err.c */ +extern int sec_qc_force_err_init(struct builder *bd); +extern void sec_qc_force_err_exit(struct builder *bd); + +/* sec_qc_boot_stat.c */ +extern int sec_qc_boot_stat_init(struct builder *bd); +extern void sec_qc_boot_stat_exit(struct builder *bd); + +/* sec_qc_cp_dump_encrypt.c */ +extern int sec_qc_cp_dump_encrypt_init(struct builder *bd); + +/* sec_qc_vendor_hooks.c */ +extern int sec_qc_vendor_hooks_init(struct builder *bd); + +#endif /* __INTERNAL__SEC_QC_DEBUG_H__ */ diff --git a/drivers/samsung/debug/qcom/debug/sec_qc_debug_lpm_log.c b/drivers/samsung/debug/qcom/debug/sec_qc_debug_lpm_log.c new file mode 100644 index 000000000000..af9648b39eeb --- /dev/null +++ b/drivers/samsung/debug/qcom/debug/sec_qc_debug_lpm_log.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2021-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "sec_qc_debug.h" + +static void __qc_lpm_log_pull_log_buf(uint8_t *dst, + const struct sec_log_buf_head *s_log_buf, ssize_t s_log_buf_sz) +{ + size_t head; + + if (s_log_buf->idx > s_log_buf_sz) { + head = s_log_buf->idx % s_log_buf_sz; + memcpy_fromio(dst, &s_log_buf->buf[head], s_log_buf_sz - head); + if (head != 0) + memcpy_fromio(&dst[s_log_buf_sz - head], + s_log_buf->buf, head); + } else { + memcpy_fromio(dst, s_log_buf->buf, s_log_buf->idx); + } +} + +static int __qc_lpm_log_store(struct qc_debug_drvdata *drvdata) +{ + struct device *dev = drvdata->bd.dev; + uint8_t *buf; + uint8_t *begin; + const struct sec_log_buf_head *s_log_buf; + ssize_t s_log_buf_sz; + bool valid; + + s_log_buf = sec_log_buf_get_header(); + if (IS_ERR(s_log_buf)) { + dev_warn(dev, "sec_log_buf is not available. (%ld)\n", + PTR_ERR(s_log_buf)); + return 0; + } + + s_log_buf_sz = sec_log_buf_get_buf_size(); + if (s_log_buf_sz <= 0) { + dev_warn(dev, "sec_log_buf size (%zd) <= 0.\n", s_log_buf_sz); + return 0; + } + + buf = vmalloc(max_t(size_t, s_log_buf_sz, + SEC_DEBUG_RESET_LPM_KLOG_SIZE)); + if (!buf) + return -ENOMEM; + + __qc_lpm_log_pull_log_buf(buf, s_log_buf, s_log_buf_sz); + + if (SEC_DEBUG_RESET_LPM_KLOG_SIZE >= s_log_buf_sz) + begin = buf; + else + begin = &buf[s_log_buf_sz - SEC_DEBUG_RESET_LPM_KLOG_SIZE]; + + valid = sec_qc_dbg_part_write(debug_index_reset_lpm_klog, begin); + if (!valid) + dev_err(dev, "failed to write lpm kmsg.\n"); + + vfree(buf); + + return 0; +} + +static int __qc_lpm_log_init(struct qc_debug_drvdata *drvdata) +{ + struct debug_reset_header __reset_header; + struct debug_reset_header *reset_header = &__reset_header; + bool valid; + + drvdata->is_lpm_mode = true; + + valid = sec_qc_dbg_part_read(debug_index_reset_summary_info, + reset_header); + if (!valid) + return -EPROBE_DEFER; + + if (reset_header->magic != DEBUG_PARTITION_MAGIC) { + dev_warn(drvdata->bd.dev, + "debug parition is not valid. storing lpm kmsg is skpped.\n"); + return 0; + } + + return __qc_lpm_log_store(drvdata); +} + +static char *boot_mode __ro_after_init; +module_param_named(boot_mode, boot_mode, charp, 0440); + +int sec_qc_debug_lpm_log_init(struct builder *bd) +{ + struct qc_debug_drvdata *drvdata = + container_of(bd, struct qc_debug_drvdata, bd); + + if (!drvdata->use_store_lpm_kmsg) + return 0; + + if (!boot_mode || strncmp(boot_mode, "charger", strlen("charger"))) + return 0; + + return __qc_lpm_log_init(drvdata); +} diff --git a/drivers/samsung/debug/qcom/debug/sec_qc_debug_main.c b/drivers/samsung/debug/qcom/debug/sec_qc_debug_main.c new file mode 100644 index 000000000000..dc41f54a1928 --- /dev/null +++ b/drivers/samsung/debug/qcom/debug/sec_qc_debug_main.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2021-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include + +#include +#include + +#include "sec_qc_debug.h" + +static noinline int __qc_debug_parse_dt_reboot_notifier_priority(struct builder *bd, + struct device_node *np) +{ + struct qc_debug_drvdata *drvdata = + container_of(bd, struct qc_debug_drvdata, bd); + s32 priority; + int err; + + err = of_property_read_s32(np, "sec,reboot_notifier-priority", + &priority); + if (err) + return -EINVAL; + + drvdata->reboot_nb.priority = (int)priority; + + return 0; +} + +static noinline int __qc_debug_parse_dt_use_cp_dump_encrypt(struct builder *bd, + struct device_node *np) +{ + struct qc_debug_drvdata *drvdata = + container_of(bd, struct qc_debug_drvdata, bd); + + drvdata->use_cp_dump_encrypt = + of_property_read_bool(np, "sec,use-cp_dump_encrypt"); + + return 0; +} + +static int __debug_parse_dt_cp_encrypt_nr_entries(struct device_node *np) +{ + int nr_entries; + + nr_entries = of_property_count_elems_of_size(np, + "sec,cp_dump_encrypt_magic", sizeof(u32)) / 2; + + if (nr_entries < 0) + return 0; + + return nr_entries; +} + +static int __debug_parse_dt_cp_encrypt_each_entry(struct cp_dump_encrypt_entry *entry, + struct device *dev, struct device_node *np, int i) +{ + const char *cp_dump_encrypt_reg = "sec,cp_dump_encrypt_reg"; + const char *cp_dump_encrypt_magic = "sec,cp_dump_encrypt_magic"; + const char *compatible; + u32 low, mid_high; + int err; + + err = of_property_read_string_helper(np, cp_dump_encrypt_reg, &compatible, 1, i); + if (err < 0) { + dev_err(dev, "can't read %s [%d]\n", compatible, i); + return err; + } + + err = of_property_read_u32_index(np, cp_dump_encrypt_magic, i * 2, &low); + if (err) { + dev_err(dev, "%s %d 'low' read error - %d\n", + cp_dump_encrypt_magic, i, err); + return -EINVAL; + } + + err = of_property_read_u32_index(np, cp_dump_encrypt_magic, i * 2 + 1, &mid_high); + if (err) { + dev_err(dev, "%s %d 'mid/high' read error - %d\n", + cp_dump_encrypt_magic, i, err); + return -EINVAL; + } + + entry->compatible = compatible; + entry->low = low; + entry->mid_high = mid_high; + + return 0; +} + +static noinline int __qc_debug_parse_dt_cp_dump_encrypt_magic(struct builder *bd, + struct device_node *np) +{ + struct qc_debug_drvdata *drvdata = + container_of(bd, struct qc_debug_drvdata, bd); + struct device *dev = bd->dev; + struct cp_dump_encrypt *encrypt; + int nr_entries; + struct cp_dump_encrypt_entry *entry; + int i; + + if (!drvdata->use_cp_dump_encrypt) + return 0; + + nr_entries = __debug_parse_dt_cp_encrypt_nr_entries(np); + if (!nr_entries) { + drvdata->use_cp_dump_encrypt = false; + return 0; + } + + entry = devm_kcalloc(dev, nr_entries, sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + for (i = 0; i < nr_entries; i++) { + int err = __debug_parse_dt_cp_encrypt_each_entry(&entry[i], dev, np, i); + + if (err) + return err; + } + + encrypt = &drvdata->cp_dump_encrypt; + + encrypt->nr_entries = nr_entries; + encrypt->entry = entry; + + return 0; +} + +static noinline int __qc_debug_parse_dt_use_store_last_kmsg(struct builder *bd, + struct device_node *np) +{ + struct qc_debug_drvdata *drvdata = + container_of(bd, struct qc_debug_drvdata, bd); + + drvdata->use_store_last_kmsg = + of_property_read_bool(np, "sec,use-store_last_kmsg"); + + return 0; +} + +static noinline int __qc_debug_parse_dt_use_store_lpm_kmsg(struct builder *bd, + struct device_node *np) +{ + struct qc_debug_drvdata *drvdata = + container_of(bd, struct qc_debug_drvdata, bd); + + drvdata->use_store_lpm_kmsg = + of_property_read_bool(np, "sec,use-store_lpm_kmsg"); + + return 0; +} + +static noinline int __qc_debug_parse_dt_use_store_onoff_history(struct builder *bd, + struct device_node *np) +{ + struct qc_debug_drvdata *drvdata = + container_of(bd, struct qc_debug_drvdata, bd); + + drvdata->use_store_onoff_history = + of_property_read_bool(np, "sec,use-store_onoff_history"); + + return 0; +} + +static const struct dt_builder __qc_debug_dt_builder[] = { + DT_BUILDER(__qc_debug_parse_dt_reboot_notifier_priority), + DT_BUILDER(__qc_debug_parse_dt_use_cp_dump_encrypt), + DT_BUILDER(__qc_debug_parse_dt_cp_dump_encrypt_magic), + DT_BUILDER(__qc_debug_parse_dt_use_store_last_kmsg), + DT_BUILDER(__qc_debug_parse_dt_use_store_lpm_kmsg), + DT_BUILDER(__qc_debug_parse_dt_use_store_onoff_history) +}; + +static noinline int __qc_debug_parse_dt(struct builder *bd) +{ + return sec_director_parse_dt(bd, __qc_debug_dt_builder, + ARRAY_SIZE(__qc_debug_dt_builder)); +} + +static noinline int __qc_debug_probe_epilog(struct builder *bd) +{ + struct qc_debug_drvdata *drvdata = + container_of(bd, struct qc_debug_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + + return 0; +} + +static const struct dev_builder __qc_debug_dev_builder[] = { + DEVICE_BUILDER(__qc_debug_parse_dt, NULL), + DEVICE_BUILDER(sec_qc_cp_dump_encrypt_init, NULL), + DEVICE_BUILDER(sec_qc_debug_lpm_log_init, NULL), + DEVICE_BUILDER(sec_qc_debug_reboot_init, sec_qc_debug_reboot_exit), + DEVICE_BUILDER(sec_qc_force_err_init, sec_qc_force_err_exit), + DEVICE_BUILDER(sec_qc_boot_stat_init, sec_qc_boot_stat_exit), + DEVICE_BUILDER(sec_qc_vendor_hooks_init, NULL), + DEVICE_BUILDER(__qc_debug_probe_epilog, NULL), +}; + +static int __qc_debug_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct qc_debug_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __qc_debug_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct qc_debug_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static int sec_qc_debug_probe(struct platform_device *pdev) +{ + return __qc_debug_probe(pdev, __qc_debug_dev_builder, + ARRAY_SIZE(__qc_debug_dev_builder)); +} + +static int sec_qc_debug_remove(struct platform_device *pdev) +{ + return __qc_debug_remove(pdev, __qc_debug_dev_builder, + ARRAY_SIZE(__qc_debug_dev_builder)); +} + +static const struct of_device_id sec_qc_debug_match_table[] = { + { .compatible = "samsung,qcom-debug" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_qc_debug_match_table); + +static struct platform_driver sec_qc_debug_driver = { + .driver = { + .name = "sec,qc-debug", + .of_match_table = of_match_ptr(sec_qc_debug_match_table), + }, + .probe = sec_qc_debug_probe, + .remove = sec_qc_debug_remove, +}; + +static int __init sec_qc_debug_init(void) +{ + return platform_driver_register(&sec_qc_debug_driver); +} +module_init(sec_qc_debug_init); + +static void __exit sec_qc_debug_exit(void) +{ + platform_driver_unregister(&sec_qc_debug_driver); +} +module_exit(sec_qc_debug_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("Common debugging feature for Qualcomm based devices"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/debug/qcom/debug/sec_qc_debug_mock.h b/drivers/samsung/debug/qcom/debug/sec_qc_debug_mock.h new file mode 100644 index 000000000000..0d3fa193d5c7 --- /dev/null +++ b/drivers/samsung/debug/qcom/debug/sec_qc_debug_mock.h @@ -0,0 +1,17 @@ +#ifndef __INTERNAL__SEC_QC_DEBUG_MOCK_H__ +#define __INTERNAL__SEC_QC_DEBUG_MOCK_H__ + +#if IS_ENABLED(CONFIG_QCOM_SCM) +#include +#else +static inline int qcom_scm_sec_wdog_trigger(void) { return -ENODEV; } +static inline bool qcom_scm_is_secure_wdog_trigger_available(void) { return false; } +#endif + +#if IS_ENABLED(CONFIG_QCOM_WDT_CORE) +#include +#else +static inline void qcom_wdt_trigger_bite(void) {} +#endif + +#endif /* __INTERNAL__SEC_QC_DEBUG_MOCK_H__ */ diff --git a/drivers/samsung/debug/qcom/debug/sec_qc_debug_reboot.c b/drivers/samsung/debug/qcom/debug/sec_qc_debug_reboot.c new file mode 100644 index 000000000000..c034d3e836a0 --- /dev/null +++ b/drivers/samsung/debug/qcom/debug/sec_qc_debug_reboot.c @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2021-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "sec_qc_debug.h" + +static inline void ____qc_reboot_recovery(const char *cmd) +{ + char recovery_cause[256] = { '\0', }; + bool valid; + + if (strcmp(cmd, "recovery")) + return; + + valid = sec_param_get(param_index_reboot_recovery_cause, + recovery_cause); + if (!valid) + pr_warn("failed to get param (idx:%u)\n", + param_index_reboot_recovery_cause); + + /* if already occupied, then skip */ + if (recovery_cause[0] || strlen(recovery_cause)) + return; + + snprintf(recovery_cause, sizeof(recovery_cause), + "%s:%d ", current->comm, task_pid_nr(current)); + + valid = sec_param_set(param_index_reboot_recovery_cause, + recovery_cause); + if (!valid) + pr_warn("failed to set param (idx:%u)\n", + param_index_reboot_recovery_cause); +} + +static inline void __qc_reboot_recovery(struct qc_debug_drvdata *drvdata, + unsigned long action, void *data) +{ + if (!data || (action != SYS_RESTART)) + return; + + ____qc_reboot_recovery((const char *)data); +} + +static inline void ____qc_reboot_param(const char *cmd) +{ + char __param_cmd[256]; + char *param_cmd = __param_cmd; + const char *param_str, *index_str, *data_str; + const char *delim = "_"; + unsigned int index; + bool valid = false; + + if (strncmp(cmd, "param", strlen("param"))) + return; + + strlcpy(__param_cmd, cmd, sizeof(__param_cmd)); + + param_str = strsep(¶m_cmd, delim); + if (!param_str) { + pr_warn("can't extract a token for 'param'!\n"); + return; + } + + index_str = strsep(¶m_cmd, delim); + if (!index_str) { + pr_warn("can't extract a token for 'index'!\n"); + return; + } + + if (kstrtouint(index_str, 0, &index)) { + pr_warn("malfomred 'param' cmd (cmd:%s)\n", cmd); + return; + } + + data_str = strsep(¶m_cmd, delim); + if (!data_str) { + pr_warn("malfomred 'param' cmd (cmd:%s)\n", cmd); + valid = false; + } else if (data_str[0] == '0') { + unsigned long value; + + if (!kstrtoul(&data_str[1], 0, &value)) + valid = sec_param_set(index, &value); + } else if (data_str[0] == '1') { + valid = sec_param_set(index, &data_str[1]); + } + + if (!valid) + pr_warn("failed to set param (idx:%u, cmd:%s)\n", + index, cmd); +} + +static inline void __qc_reboot_param(struct qc_debug_drvdata *drvdata, + unsigned long action, void *data) +{ + + if (!data || (action != SYS_RESTART)) + return; + + ____qc_reboot_param((const char *)data); +} + +static inline void ____qc_reboot_store_last_kmsg( + struct qc_debug_drvdata *drvdata, + unsigned long action, const char *cmd) +{ + struct device *dev = drvdata->bd.dev; + const struct sec_log_buf_head *s_log_buf; + bool valid; + + s_log_buf = sec_log_buf_get_header(); + if (IS_ERR(s_log_buf)) { + dev_info(dev, "sec_log_buf is not initialized.\n"); + return; + } + + dev_info(dev, "%s, %s\n", + action == SYS_RESTART ? "reboot" : "power off", + cmd); + + valid = sec_qc_dbg_part_write(debug_index_reset_klog, s_log_buf); + if (!valid) + dev_warn(dev, "failed to write sec log buf to dbg partition\n"); +} + +static inline void __qc_reboot_store_last_kmsg( + struct qc_debug_drvdata *drvdata, + unsigned long action, void *data) +{ + if (action != SYS_RESTART && action != SYS_POWER_OFF) + return; + + ____qc_reboot_store_last_kmsg(drvdata, action, data); +} + +static inline time64_t __qc_reboot_get_rtc_offset(void) +{ + struct rtc_time tm; + struct rtc_device *rtc; + + rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE); + if (!rtc) + return -ENODEV; + + if (rtc_read_time(rtc, &tm)) { + dev_warn(rtc->dev.parent, + "hctosys: unable to read the hardware clock\n"); + return -ENXIO; + } + + return rtc_tm_to_time64(&tm); +} + +static void __qc_reboot_update_onoff_history_on_lpm_mode( + struct rtc_time *local_tm, + onoff_reason_t *curr, onoff_reason_t *prev) +{ + if (curr->rtc_offset < 0) + goto err_rtc_offset; + + if (!prev) + goto err_without_prev; + + if (curr->rtc_offset < prev->rtc_offset || !prev->local_time) + goto err_invalid_prev; + + curr->local_time = prev->local_time + + (curr->rtc_offset - prev->rtc_offset); + rtc_time64_to_tm(curr->local_time, local_tm); + +err_invalid_prev: +err_without_prev: +err_rtc_offset: + curr->local_time = 0; +} + +static void __qc_reboot_update_onoff_history_on_other_mode( + struct rtc_time *local_tm, + onoff_reason_t *curr) +{ + struct timespec64 now; + time64_t local_time; + + ktime_get_real_ts64(&now); + + local_time = now.tv_sec; + local_time -= (time64_t)sys_tz.tz_minuteswest * 60; /* adjust time zone */ + curr->local_time = local_time; + + rtc_time64_to_tm(local_time, local_tm); +} + +static void __qc_reboot_update_onoff_history(struct qc_debug_drvdata *drvdata, + unsigned long action, const char *cmd, + onoff_history_t *onoff_history) +{ + struct device *dev = drvdata->bd.dev; + struct rtc_time local_tm; + uint32_t curr_idx = onoff_history->index + % SEC_DEBUG_ONOFF_HISTORY_MAX_CNT; + onoff_reason_t *curr = &onoff_history->history[curr_idx]; + const struct sec_log_buf_head *s_log_buf; + + curr->rtc_offset = __qc_reboot_get_rtc_offset(); + + if (drvdata->is_lpm_mode) { + uint32_t prev_idx = (onoff_history->index - 1) + % SEC_DEBUG_ONOFF_HISTORY_MAX_CNT; + onoff_reason_t *prev = curr_idx ? + &onoff_history->history[prev_idx] : NULL; + + __qc_reboot_update_onoff_history_on_lpm_mode(&local_tm, curr, prev); + } else + __qc_reboot_update_onoff_history_on_other_mode(&local_tm, curr); + + s_log_buf = sec_log_buf_get_header(); + curr->boot_cnt = IS_ERR(s_log_buf) ? 0 : s_log_buf->boot_cnt; + + snprintf(curr->reason, SEC_DEBUG_ONOFF_REASON_STR_SIZE, + "%s at Kernel%s%s", + action == SYS_RESTART ? "Reboot" : "Power Off", + drvdata->is_lpm_mode ? "(in LPM) " : " ", cmd); + + onoff_history->index++; + + dev_info(dev, "%ptR(TZ:%02d), %s, %s\n", + &local_tm, -sys_tz.tz_minuteswest / 60, + action == SYS_RESTART ? "reboot" : "power off", + cmd); + + dev_info(dev, "BOOT_CNT(%u) RTC(%lld)\n", + curr->boot_cnt, curr->rtc_offset); +} + +static inline void ____qc_reboot_store_onoff_history( + struct qc_debug_drvdata *drvdata, + unsigned long action, void *data) +{ + struct device *dev = drvdata->bd.dev; + onoff_history_t *onoff_history; + bool valid; + + onoff_history = kvmalloc(sizeof(onoff_history_t), GFP_KERNEL); + if (!onoff_history) + return; + + valid = sec_qc_dbg_part_read(debug_index_onoff_history, onoff_history); + if (!valid) { + dev_warn(dev, "fail to read onoff_history data\n"); + goto err_read_dbg_partition; + } + + if (onoff_history->magic != SEC_LOG_MAGIC || + onoff_history->size != sizeof(onoff_history_t)) { + dev_warn(dev, "invalid magic & size (0x%08x, %u, %zu)\n", + onoff_history->magic, + onoff_history->size, + sizeof(onoff_history_t)); + goto err_invalid_onoff_history; + } + + __qc_reboot_update_onoff_history(drvdata, action, data, + onoff_history); + + valid = sec_qc_dbg_part_write(debug_index_onoff_history, onoff_history); + if (!valid) + dev_warn(dev, "fail to wrtie onoff_history data\n"); + +err_invalid_onoff_history: +err_read_dbg_partition: + kvfree(onoff_history); +} + +static inline void __qc_reboot_store_onoff_history( + struct qc_debug_drvdata *drvdata, + unsigned long action, void *data) +{ + if (action != SYS_RESTART && action != SYS_POWER_OFF) + return; + + ____qc_reboot_store_onoff_history(drvdata, action, data); +} + +static int sec_qc_debug_reboot_handle(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct qc_debug_drvdata *drvdata = container_of(nb, + struct qc_debug_drvdata, reboot_nb); + + __qc_reboot_recovery(drvdata, action, data); + __qc_reboot_param(drvdata, action, data); + + if (drvdata->use_store_onoff_history) + __qc_reboot_store_onoff_history(drvdata, action, data); + + if (drvdata->use_store_last_kmsg) + __qc_reboot_store_last_kmsg(drvdata, action, data); + + return NOTIFY_OK; +} + +int sec_qc_debug_reboot_init(struct builder *bd) +{ + struct qc_debug_drvdata *drvdata = + container_of(bd, struct qc_debug_drvdata, bd); + struct notifier_block *nb = &drvdata->reboot_nb; + + nb->notifier_call = sec_qc_debug_reboot_handle; + + return register_reboot_notifier(nb); +} + +void sec_qc_debug_reboot_exit(struct builder *bd) +{ + struct qc_debug_drvdata *drvdata = + container_of(bd, struct qc_debug_drvdata, bd); + + unregister_reboot_notifier(&drvdata->reboot_nb); +} diff --git a/drivers/samsung/debug/qcom/debug/sec_qc_force_err.c b/drivers/samsung/debug/qcom/debug/sec_qc_force_err.c new file mode 100644 index 000000000000..0e662c90e8c4 --- /dev/null +++ b/drivers/samsung/debug/qcom/debug/sec_qc_force_err.c @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2020-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include + +#include +#include + +#include "sec_qc_debug.h" + +static void __qc_simulate_secure_wdog_bite(struct force_err_handle *h) +{ + int err; + + if (!qcom_scm_is_secure_wdog_trigger_available()) { + pr_emerg("secure wdog trigger is not available\n"); + goto __fallback; + } + + err = qcom_scm_sec_wdog_trigger(); + if (err) { + pr_emerg("secure wdog trigger is failed (%d)\n", err); + goto __fallback; + } + + return; + +__fallback: + qcom_wdt_trigger_bite(); +} + +static struct force_err_handle __qc_force_err_default[] = { + FORCE_ERR_HANDLE("secdogbite", "simulating secure watch dog bite!", + __qc_simulate_secure_wdog_bite), +}; + +static ssize_t __qc_force_err_add_handlers(ssize_t begin) +{ + struct force_err_handle *h; + int err = 0; + ssize_t n = ARRAY_SIZE(__qc_force_err_default); + ssize_t i; + + for (i = begin; i < n; i++) { + h = &__qc_force_err_default[i]; + + INIT_HLIST_NODE(&h->node); + + err = sec_force_err_add_custom_handle(h); + if (err) { + pr_err("failed to add a handler - [%zu] %ps (%d)\n", + i, h->func, err); + return -i; + } + } + + return n; +} + +static void __qc_force_err_del_handlers(ssize_t last_failed) +{ + struct force_err_handle *h; + int err = 0; + ssize_t n = ARRAY_SIZE(__qc_force_err_default); + ssize_t i; + + BUG_ON((last_failed < 0) || (last_failed > n)); + + for (i = last_failed - 1; i >= 0; i--) { + h = &__qc_force_err_default[i]; + + err = sec_force_err_del_custom_handle(h); + if (err) + pr_warn("failed to delete a handler - [%zu] %ps (%d)\n", + i, h->func, err); + } +} + +int sec_qc_force_err_init(struct builder *bd) +{ + ssize_t last_failed; + + last_failed = __qc_force_err_add_handlers(0); + if (last_failed <= 0) { + dev_warn(bd->dev, "force err is disabled. ignored.\n"); + goto err_add_handlers; + } + + return 0; + +err_add_handlers: + __qc_force_err_del_handlers(-last_failed); + return 0; +} + +void sec_qc_force_err_exit(struct builder *bd) +{ + __qc_force_err_del_handlers(ARRAY_SIZE(__qc_force_err_default)); +} diff --git a/drivers/samsung/debug/qcom/hw_param/Kconfig b/drivers/samsung/debug/qcom/hw_param/Kconfig new file mode 100644 index 000000000000..757fbe982c36 --- /dev/null +++ b/drivers/samsung/debug/qcom/hw_param/Kconfig @@ -0,0 +1,5 @@ +config SEC_QC_HW_PARAM + tristate "SEC HW Paramter Driver for Qualcomm based devices" + depends on SEC_QC_USER_RESET + help + TODO: help is not ready. diff --git a/drivers/samsung/debug/qcom/hw_param/Makefile b/drivers/samsung/debug/qcom/hw_param/Makefile new file mode 100644 index 000000000000..377b79834447 --- /dev/null +++ b/drivers/samsung/debug/qcom/hw_param/Makefile @@ -0,0 +1,12 @@ +obj-$(CONFIG_SEC_QC_HW_PARAM) += sec_qc_hw_param.o +sec_qc_hw_param-objs := sec_qc_hw_param_main.o \ + sec_qc_ap_info.o \ + sec_qc_ap_health.o \ + sec_qc_last_dcvs.o \ + sec_qc_extra_info.o \ + sec_qc_extrb_info.o \ + sec_qc_extrc_info.o \ + sec_qc_extrt_info.o \ + sec_qc_extrm_info.o \ + sec_qc_errp_extra.o \ + sec_qc_ddr_info.o diff --git a/drivers/samsung/debug/qcom/hw_param/sec_qc_ap_health.c b/drivers/samsung/debug/qcom/hw_param/sec_qc_ap_health.c new file mode 100644 index 000000000000..830d86a2a1d6 --- /dev/null +++ b/drivers/samsung/debug/qcom/hw_param/sec_qc_ap_health.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2014-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include + +#include +#include + +#include "sec_qc_hw_param.h" + +static ssize_t ap_health_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + ap_health_t *health = qc_hw_param->ap_health; + char clear; + int err; + + err = sscanf(buf, "%1c", &clear); + if ((err <= 0) || (clear != 'c' && clear != 'C')) { + dev_warn(dev, "command error\n"); + return count; + } + + dev_info(dev, "clear ap_health_data by HQM %zu\n", + sizeof(ap_health_t)); + + /*++ add here need init data by HQM ++*/ + memset(&(health->daily_cache), 0, sizeof(cache_health_t)); + memset(&(health->daily_pcie), 0, sizeof(pcie_health_t) * MAX_PCIE_NUM); + memset(&(health->daily_rr), 0, sizeof(reset_reason_t)); + + sec_qc_ap_health_data_write(health); + + return count; +} + +static inline bool __ap_health_is_last_cpu(int cpu) +{ + return cpu == (num_present_cpus() - 1); +} + +static inline ssize_t __ap_health_report_cache(const ap_health_t *health, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const cache_health_t *cache = &health->cache; + int cpu; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"L1c\":\""); + for_each_cpu(cpu, cpu_present_mask) { + const char *sep = __ap_health_is_last_cpu(cpu) ? "\"," : ","; + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%d%s", cache->edac[cpu][0].ce_cnt, sep); + } + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"L1u\":\""); + for_each_cpu(cpu, cpu_present_mask) { + const char *sep = __ap_health_is_last_cpu(cpu) ? "\"," : ","; + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%d%s", cache->edac[cpu][0].ue_cnt, sep); + } + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"L2c\":\""); + for_each_cpu(cpu, cpu_present_mask) { + const char *sep = __ap_health_is_last_cpu(cpu) ? "\"," : ","; + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%d%s", cache->edac[cpu][1].ce_cnt, sep); + } + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"L2u\":\""); + for_each_cpu(cpu, cpu_present_mask) { + const char *sep = __ap_health_is_last_cpu(cpu) ? "\"," : ","; + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%d%s", cache->edac[cpu][1].ue_cnt, sep); + } + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"L3c\":\"%d\",", cache->edac_l3.ce_cnt); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"L3u\":\"%d\",", cache->edac_l3.ue_cnt); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"EDB\":\"%d\",", cache->edac_bus_cnt); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"LDc\":\"%d\",", cache->edac_llcc_data_ram.ce_cnt); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"LDu\":\"%d\",", cache->edac_llcc_data_ram.ue_cnt); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"LTc\":\"%d\",", cache->edac_llcc_tag_ram.ce_cnt); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"LTu\":\"%d\",", cache->edac_llcc_tag_ram.ue_cnt); + + return info_size; +} + +static inline ssize_t __ap_health_report_daily_cache(const ap_health_t *health, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const cache_health_t *daily_cache = &health->daily_cache; + int cpu; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dL1c\":\""); + for_each_cpu(cpu, cpu_present_mask) { + const char *sep = __ap_health_is_last_cpu(cpu) ? "\"," : ","; + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%d%s", daily_cache->edac[cpu][0].ce_cnt, sep); + } + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dL1u\":\""); + for_each_cpu(cpu, cpu_present_mask) { + const char *sep = __ap_health_is_last_cpu(cpu) ? "\"," : ","; + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%d%s", daily_cache->edac[cpu][0].ue_cnt, sep); + } + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dL2c\":\""); + for_each_cpu(cpu, cpu_present_mask) { + const char *sep = __ap_health_is_last_cpu(cpu) ? "\"," : ","; + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%d%s", daily_cache->edac[cpu][1].ce_cnt, sep); + } + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dL2u\":\""); + for_each_cpu(cpu, cpu_present_mask) { + const char *sep = __ap_health_is_last_cpu(cpu) ? "\"," : ","; + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%d%s", daily_cache->edac[cpu][1].ue_cnt, sep); + } + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dL3c\":\"%d\",", daily_cache->edac_l3.ce_cnt); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dL3u\":\"%d\",", daily_cache->edac_l3.ue_cnt); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dEDB\":\"%d\",", daily_cache->edac_bus_cnt); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dLDc\":\"%d\",", daily_cache->edac_llcc_data_ram.ce_cnt); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dLDu\":\"%d\",", daily_cache->edac_llcc_data_ram.ue_cnt); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dLTc\":\"%d\",", daily_cache->edac_llcc_tag_ram.ce_cnt); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dLTu\":\"%d\",", daily_cache->edac_llcc_tag_ram.ue_cnt); + + return info_size; +} + +static inline ssize_t __ap_health_report_pcie(const ap_health_t *health, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const pcie_health_t *pcie = &health->pcie[0]; + const pcie_health_t *daily_pcie = &health->daily_pcie[0]; + size_t i; + + for (i = 0; i < MAX_PCIE_NUM; i++) { + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"P%zuPF\":\"%d\",", i, pcie[i].phy_init_fail_cnt); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"P%zuLD\":\"%d\",", i, pcie[i].link_down_cnt); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"P%zuLF\":\"%d\",", i, pcie[i].link_up_fail_cnt); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"P%zuLT\":\"%x\",", i, pcie[i].link_up_fail_ltssm); + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dP%zuPF\":\"%d\",", i, daily_pcie[i].phy_init_fail_cnt); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dP%zuLD\":\"%d\",", i, daily_pcie[i].link_down_cnt); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dP%zuLF\":\"%d\",", i, daily_pcie[i].link_up_fail_cnt); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dP%zuLT\":\"%x\",", i, daily_pcie[i].link_up_fail_ltssm); + } + + return info_size; +} + +static inline ssize_t __ap_health_report_daily_rr(const ap_health_t *health, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const reset_reason_t *daily_rr = &health->daily_rr; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dNP\":\"%d\",", daily_rr->np); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dRP\":\"%d\",", daily_rr->rp); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dMP\":\"%d\",", daily_rr->mp); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dKP\":\"%d\",", daily_rr->kp); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dDP\":\"%d\",", daily_rr->dp); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dWP\":\"%d\",", daily_rr->wp); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dTP\":\"%d\",", daily_rr->tp); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dSP\":\"%d\",", daily_rr->sp); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dPP\":\"%d\",", daily_rr->pp); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dCP\":\"%d\"", daily_rr->cp); + + return info_size; +} + +static ssize_t ap_health_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + const ap_health_t *health = qc_hw_param->ap_health; + const ssize_t sz_buf = DEFAULT_LEN_STR; + ssize_t info_size = 0; + + info_size = __ap_health_report_cache(health, buf, sz_buf, info_size); + info_size = __ap_health_report_daily_cache(health, buf, sz_buf, info_size); + info_size = __ap_health_report_pcie(health, buf, sz_buf, info_size); + info_size = __ap_health_report_daily_rr(health, buf, sz_buf, info_size); + + __qc_hw_param_clean_format(buf, &info_size, DEFAULT_LEN_STR); + + return info_size; +} + +DEVICE_ATTR_RW(ap_health); diff --git a/drivers/samsung/debug/qcom/hw_param/sec_qc_ap_info.c b/drivers/samsung/debug/qcom/hw_param/sec_qc_ap_info.c new file mode 100644 index 000000000000..105f503d8d73 --- /dev/null +++ b/drivers/samsung/debug/qcom/hw_param/sec_qc_ap_info.c @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2014-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include + +#include +#include + +#include "sec_qc_hw_param.h" + +static ssize_t ap_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct qc_ap_info *ap_info = &qc_hw_param->ap_info; + ssize_t info_size = 0; + const ssize_t sz_buf = DEFAULT_LEN_STR; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"HW_REV\":\"%d\"", ap_info->system_rev); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, ",\"SoC_ID\":\"%u\"", socinfo_get_id()); + + __qc_hw_param_clean_format(buf, &info_size, sz_buf); + + return info_size; +} +DEVICE_ATTR_RO(ap_info); + +static unsigned int system_rev __ro_after_init; +module_param_named(revision, system_rev, uint, 0440); + +int __qc_ap_info_init(struct builder *bd) +{ + struct qc_hw_param_drvdata *drvdata = + container_of(bd, struct qc_hw_param_drvdata, bd); + struct qc_ap_info *ap_info = &drvdata->ap_info; + + ap_info->system_rev = system_rev; + + return 0; +} diff --git a/drivers/samsung/debug/qcom/hw_param/sec_qc_ddr_info.c b/drivers/samsung/debug/qcom/hw_param/sec_qc_ddr_info.c new file mode 100644 index 000000000000..15a2445a45b8 --- /dev/null +++ b/drivers/samsung/debug/qcom/hw_param/sec_qc_ddr_info.c @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2014-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include + +#include + +#include "sec_qc_hw_param.h" + +#define for_each_dq(dq, max_dq) \ + for (dq = 0; dq < max_dq; dq++) + +#define for_each_cs(cs, max_cs) \ + for (cs = 0; cs < max_cs; cs++) + +#define for_each_cs_dq(cs, max_cs, dq, max_dq) \ + for_each_cs(cs, max_cs) \ + for_each_dq(dq, max_dq) + +#define for_each_ch(ch, max_ch) \ + for (ch = 0; ch < max_ch; ch++) + +#define for_each_ch_dq(ch, max_ch, dq, max_dq) \ + for_each_ch(ch, max_ch) \ + for_each_dq(dq, max_dq) + +#define for_each_ch_cs(ch, max_ch, cs, max_cs) \ + for_each_ch(ch, max_ch) \ + for_each_cs(cs, max_cs) + +#define for_each_ch_cs_dq(ch, max_ch, cs, max_cs, dq, max_dq) \ + for_each_ch(ch, max_ch) \ + for_each_cs_dq(cs, max_cs, dq, max_dq) + +static ssize_t __ddr_info_report_header(char *buf, + const ssize_t sz_buf, ssize_t info_size) +{ + union { + uint32_t raw; + struct { + uint32_t lv2:16; + uint32_t lv1:8; + uint32_t lv0:8; + }; + } dsf; + + dsf.raw = sec_qc_smem_lpddr_get_DSF_version(); + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"DDRV\":\"%s\",", + sec_qc_smem_lpddr_get_vendor_name()); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"DSF\":\"%u.%u.%u\",", + dsf.lv0, dsf.lv1, dsf.lv2); + + return info_size; +} + +static ssize_t ddr_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t info_size = 0; + const ssize_t sz_buf = DEFAULT_LEN_STR; + size_t ch, cs, dq; + + info_size = __ddr_info_report_header(buf, sz_buf, info_size); + + for_each_ch_cs_dq(ch, NUM_CH, cs, NUM_CS, dq, NUM_DQ_PCH) { + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"RW_%zu_%zu_%zu\":\"%d\",", + ch, cs, dq, sec_qc_smem_lpddr_get_rcw_tDQSCK(ch, cs, dq)); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"WC_%zu_%zu_%zu\":\"%d\",", + ch, cs, dq, sec_qc_smem_lpddr_get_wr_coarseCDC(ch, cs, dq)); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"WF_%zu_%zu_%zu\":\"%d\",", + ch, cs, dq, sec_qc_smem_lpddr_get_wr_fineCDC(ch, cs, dq)); + } + + /* remove the last ',' character */ + info_size--; + + __qc_hw_param_clean_format(buf, &info_size, sz_buf); + + return info_size; +} +DEVICE_ATTR_RO(ddr_info); + +static ssize_t __ddr_info_report_revision(char *buf, + const ssize_t sz_buf, ssize_t info_size) +{ + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"REV1\":\"%02x\",", + sec_qc_smem_lpddr_get_revision_id1()); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"REV2\":\"%02x\",", + sec_qc_smem_lpddr_get_revision_id2()); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"SIZE\":\"%02x\",", + sec_qc_smem_lpddr_get_total_density()); + + return info_size; +} + +static ssize_t __ddr_info_report_eye_rd_info(char *buf, + const ssize_t sz_buf, ssize_t info_size) +{ + size_t ch, cs, dq; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"CNT\":\"%d\",", + sec_qc_smem_lpddr_get_small_eye_detected()); + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"rd_width\":\"R1\","); + for_each_ch_cs_dq(ch, NUM_CH, cs, NUM_CS, dq, NUM_DQ_PCH) { + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"RW%zu_%zu_%zu\":\"%d\",", + ch, cs, dq, + sec_qc_smem_lpddr_get_rd_pr_width(ch, cs, dq)); + } + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"rd_height\":\"R2\","); + for_each_ch_cs(ch, NUM_CH, cs, NUM_CS) { + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"RH%zu_%zu_x\":\"%d\",", + ch, cs, + sec_qc_smem_lpddr_get_rd_min_eye_height(ch, cs)); + } + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"rd_vref\":\"Rx\","); + for_each_ch_cs_dq(ch, NUM_CH, cs, NUM_CS, dq, NUM_DQ_PCH) { + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"RV%zu_%zu_%zu\":\"%d\",", + ch, cs, dq, + sec_qc_smem_lpddr_get_rd_best_vref(ch, cs, dq)); + } + + return info_size; +} + +static ssize_t eye_rd_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t info_size = 0; + const ssize_t sz_buf = DEFAULT_LEN_STR; + + info_size = __ddr_info_report_header(buf, sz_buf, info_size); + info_size = __ddr_info_report_revision(buf, sz_buf, info_size); + + if (!qc_hw_param->use_ddr_eye_info) + goto early_exit_no_eye_info; + + info_size = __ddr_info_report_eye_rd_info(buf, sz_buf, info_size); + +early_exit_no_eye_info: + /* remove the last ',' character */ + info_size--; + + __qc_hw_param_clean_format(buf, &info_size, sz_buf); + + return info_size; +} +DEVICE_ATTR_RO(eye_rd_info); + +/* TODO: only for 'use_ddr_eye_info' */ +static ssize_t eye_dcc_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t info_size = 0; + const ssize_t sz_buf = DEFAULT_LEN_STR; + size_t ch, cs, dq; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dqs_dcc\":\"D3\","); + for_each_ch_dq(ch, NUM_CH, dq, NUM_DQ_PCH) { + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"DS%zu_x_%zu\":\"%d\",", + ch, dq, + sec_qc_smem_lpddr_get_dqs_dcc_adj(ch, dq)); + } + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"dq_dcc\":\"D5\","); + for_each_ch_cs_dq(ch, NUM_CH, cs, NUM_CS, dq, NUM_DQ_PCH) { + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"DQ%zu_%zu_%zu\":\"%d\",", + ch, cs, dq, + sec_qc_smem_lpddr_get_dq_dcc_abs(ch, cs, dq)); + } + + /* remove the last ',' character */ + info_size--; + + __qc_hw_param_clean_format(buf, &info_size, sz_buf); + + return info_size; +} +DEVICE_ATTR_RO(eye_dcc_info); + +/* TODO: only for 'use_ddr_eye_info' */ +static ssize_t eye_wr1_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t info_size = 0; + const ssize_t sz_buf = DEFAULT_LEN_STR; + size_t ch, cs, dq; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"wr_width\":\"W1\","); + for_each_ch_cs_dq(ch, NUM_CH, cs, NUM_CS, dq, NUM_DQ_PCH) { + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"WW%zu_%zu_%zu\":\"%d\",", + ch, cs, dq, + sec_qc_smem_lpddr_get_wr_pr_width(ch, cs, dq)); + } + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"wr_height\":\"W2\","); + for_each_ch_cs(ch, NUM_CH, cs, NUM_CS) { + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"WH%zu_%zu_x\":\"%d\",", + ch, cs, + sec_qc_smem_lpddr_get_wr_min_eye_height(ch, cs)); + } + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"wr_vref\":\"Wx\","); + for_each_ch_cs(ch, NUM_CH, cs, NUM_CS) { + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"WV%zu_%zu_x\":\"%d\",", + ch, cs, + sec_qc_smem_lpddr_get_wr_best_vref(ch, cs)); + } + + /* remove the last ',' character */ + info_size--; + + __qc_hw_param_clean_format(buf, &info_size, sz_buf); + + return info_size; +} +DEVICE_ATTR_RO(eye_wr1_info); + +/* TODO: only for 'use_ddr_eye_info' */ +static ssize_t eye_wr2_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t info_size = 0; + const ssize_t sz_buf = DEFAULT_LEN_STR; + size_t ch, cs, dq; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"wr_topw\":\"W4\","); + for_each_ch_cs_dq(ch, NUM_CH, cs, NUM_CS, dq, NUM_DQ_PCH) { + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"WT%zu_%zu_%zu\":\"%d\",", + ch, cs, dq, + sec_qc_smem_lpddr_get_wr_vmax_to_vmid(ch, cs, dq)); + } + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"wr_botw\":\"W4\","); + for_each_ch_cs_dq(ch, NUM_CH, cs, NUM_CS, dq, NUM_DQ_PCH) { + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"WB%zu_%zu_%zu\":\"%d\",", + ch, cs, dq, + sec_qc_smem_lpddr_get_wr_vmid_to_vmin(ch, cs, dq)); + } + + /* remove the last ',' character */ + info_size--; + + __qc_hw_param_clean_format(buf, &info_size, sz_buf); + + return info_size; +} +DEVICE_ATTR_RO(eye_wr2_info); diff --git a/drivers/samsung/debug/qcom/hw_param/sec_qc_errp_extra.c b/drivers/samsung/debug/qcom/hw_param/sec_qc_errp_extra.c new file mode 100644 index 000000000000..a553741fcf81 --- /dev/null +++ b/drivers/samsung/debug/qcom/hw_param/sec_qc_errp_extra.c @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2014-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "sec_qc_hw_param.h" + +#define EXTEND_RR_SIZE 150 + +static ssize_t __errp_extra_report_reset_write_cnt(char *buf, + const ssize_t sz_buf, ssize_t info_size) +{ + int reset_write_cnt; + + reset_write_cnt = sec_qc_get_reset_write_cnt(); + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"RWC\":\"%d\",", reset_write_cnt); + + return info_size; +} + +static ssize_t __errp_extra_report_reset_reason_smpl(const rst_exinfo_t *rst_exinfo, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const _kern_ex_info_t *kinfo = &rst_exinfo->kern_ex_info.info; + + if (strstr(kinfo->panic_buf, "SMPL")) + __qc_hw_param_scnprintf(buf, sz_buf, info_size, " PANIC:%s", kinfo->panic_buf); + + return info_size; +} + +static ssize_t __errp_extra_report_upload_cause(const rst_exinfo_t *rst_exinfo, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + if (rst_exinfo->uc_ex_info.magic == RESTART_REASON_SEC_DEBUG_MODE) { + __qc_hw_param_scnprintf(buf, sz_buf, info_size, " UPLOAD:%s", rst_exinfo->uc_ex_info.str); + } else { + const _kern_ex_info_t *kinfo = &rst_exinfo->kern_ex_info.info; + char upload_cause_str[80] = {0,}; + + sec_qc_upldc_type_to_cause(kinfo->upload_cause, + upload_cause_str, sizeof(upload_cause_str)); + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, " UPLOAD:%s_0x%x", upload_cause_str, kinfo->upload_cause); + } + + return info_size; +} + +static ssize_t __errp_extra_report_tz_reset_reason(const rst_exinfo_t *rst_exinfo, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const tz_exinfo_t *tz_ex_info = &rst_exinfo->tz_ex_info; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, " TZ_RR:%s", tz_ex_info->msg); + + return info_size; +} + +static ssize_t __errp_extra_report_panic_context(const rst_exinfo_t *rst_exinfo, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const _kern_ex_info_t *kinfo = &rst_exinfo->kern_ex_info.info; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, " PANIC:%s", kinfo->panic_buf); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, " PC:%s", kinfo->pc); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, " LR:%s", kinfo->lr); + + return info_size; +} + +static int __errp_extra_show(struct seq_file *m, void *v) +{ + struct device *dev = qc_hw_param->bd.dev; + const ssize_t sz_buf = EXTEND_RR_SIZE; + ssize_t info_size = 0; + char buf[EXTEND_RR_SIZE] = {0, }; + unsigned int reset_reason; + rst_exinfo_t *rst_exinfo; + + reset_reason = sec_qc_reset_reason_get(); + if (!__qc_hw_param_is_valid_reset_reason(reset_reason)) + goto err_invalid_reset_reason; + + rst_exinfo = kmalloc(sizeof(rst_exinfo_t), GFP_KERNEL); + if (!rst_exinfo) + goto err_enomem; + + if (!sec_qc_dbg_part_read(debug_index_reset_ex_info, rst_exinfo)) { + dev_warn(dev, "failed to read debug paritition.\n"); + goto err_failed_to_read; + } + + info_size = __errp_extra_report_reset_write_cnt(buf, sz_buf, info_size); + + if (reset_reason == USER_UPLOAD_CAUSE_SMPL) { + info_size = __errp_extra_report_reset_reason_smpl(rst_exinfo, buf, sz_buf, info_size); + goto early_exit_on_smpl; + } + + __errp_extra_report_upload_cause(rst_exinfo, buf, sz_buf, info_size); + + if (reset_reason == USER_UPLOAD_CAUSE_WATCHDOG) + goto early_exit_on_watchdog; + + if (reset_reason == USER_UPLOAD_CAUSE_WTSR || + reset_reason == USER_UPLOAD_CAUSE_POWER_RESET) { + info_size = __errp_extra_report_tz_reset_reason(rst_exinfo, buf, sz_buf, info_size); + goto early_exit_on_unknown; + } + + info_size = __errp_extra_report_panic_context(rst_exinfo, buf, sz_buf, info_size); + +early_exit_on_unknown: +early_exit_on_watchdog: +early_exit_on_smpl: +err_failed_to_read: + kfree(rst_exinfo); +err_enomem: + seq_puts(m, buf); +err_invalid_reset_reason: + return 0; +} + +static int sec_qc_errp_extra_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, __errp_extra_show, NULL); +} + +static const struct proc_ops __errp_extra_pops = { + .proc_open = sec_qc_errp_extra_proc_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = single_release, +}; + +int __qc_errp_extra_init(struct builder *bd) +{ + struct qc_hw_param_drvdata *drvdata = + container_of(bd, struct qc_hw_param_drvdata, bd); + struct proc_dir_entry *proc; + + proc = proc_create("extra", 0440, NULL, &__errp_extra_pops); + if (IS_ERR(proc)) { + dev_err(bd->dev, "failed to create a proc node."); + return -ENODEV; + } + + drvdata->errp_extra_proc = proc; + + return 0; +} + +void __qc_errp_extra_exit(struct builder *bd) +{ + struct qc_hw_param_drvdata *drvdata = + container_of(bd, struct qc_hw_param_drvdata, bd); + + proc_remove(drvdata->errp_extra_proc); +} diff --git a/drivers/samsung/debug/qcom/hw_param/sec_qc_extra_info.c b/drivers/samsung/debug/qcom/hw_param/sec_qc_extra_info.c new file mode 100644 index 000000000000..06e1b6266829 --- /dev/null +++ b/drivers/samsung/debug/qcom/hw_param/sec_qc_extra_info.c @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2014-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include + +#include +#include +#include + +#include "sec_qc_hw_param.h" + +static ssize_t __extra_info_report_reset_reason(unsigned int reset_reason, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const char *reset_reason_str; + int reset_write_cnt; + + reset_reason_str = sec_qc_reset_reason_to_str(reset_reason); + reset_write_cnt = sec_qc_get_reset_write_cnt(); + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"RR\":\"%s\",", reset_reason_str); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"RWC\":\"%d\",", reset_write_cnt); + + return info_size; +} + +static ssize_t __extra_info_report_task(const _kern_ex_info_t *kinfo, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + unsigned long long rem_nsec; + unsigned long long ts_nsec; + + ts_nsec = kinfo->ktime; + rem_nsec = do_div(ts_nsec, 1000000000ULL); + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"KTIME\":\"%llu.%06llu\",", ts_nsec, rem_nsec / 1000ULL); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"CPU\":\"%d\",", kinfo->cpu); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"TASK\":\"%s\",", kinfo->task_name); + + return info_size; +} + +static ssize_t __extra_info_report_smmu(const _kern_ex_info_t *kinfo, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const ex_info_smmu_t *smmu = &kinfo->smmu; + + if (!smmu->fsr) + return info_size; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"SDN\":\"%s\",", smmu->dev_name); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"FSR\":\"%x\",", smmu->fsr); + + if (!smmu->fsynr0) + return info_size; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"FSY0\":\"%x\",", smmu->fsynr0); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"FSY1\":\"%x\",", smmu->fsynr1); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"IOVA\":\"%08lx\",", smmu->iova); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"FAR\":\"%016lx\",", smmu->far); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"SMN\":\"%s\",", smmu->mas_name); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"CB\":\"%d\",", smmu->cbndx); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"SOFT\":\"%016llx\",", smmu->phys_soft); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"ATOS\":\"%016llx\",", smmu->phys_atos); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"SID\":\"%x\",", smmu->sid); + + return info_size; +} + +static ssize_t __extra_info_report_badmode(const _kern_ex_info_t *kinfo, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const ex_info_badmode_t *badmode = &kinfo->badmode; + + if (!badmode->esr) + return info_size; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"BDR\":\"%08x\",", badmode->reason); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"BDRS\":\"%s\",", badmode->handler_str); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"BDE\":\"%08x\",", badmode->esr); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"BDES\":\"%s\",", badmode->esr_str); + + return info_size; +} + +static ssize_t __extra_info_report_fault(const _kern_ex_info_t *kinfo, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const ex_info_fault_t *fault = &kinfo->fault[0]; + int cpu = kinfo->cpu; + + if ((cpu < 0) || (cpu > num_present_cpus())) + return info_size; + + if (fault[cpu].esr) { + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"ESR\":\"%08x\",", fault[cpu].esr); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"FNM\":\"%s\",", fault[cpu].str); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"FV1\":\"%016llx\",", fault[cpu].var1); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"FV2\":\"%016llx\",", fault[cpu].var2); + } + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"FAULT\":\"pgd=%016llx VA=%016llx ", fault[cpu].pte[0], fault[cpu].pte[1]); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "*pgd=%016llx *pud=%016llx ", fault[cpu].pte[2], fault[cpu].pte[3]); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "*pmd=%016llx *pte=%016llx\",", fault[cpu].pte[4], fault[cpu].pte[5]); + + return info_size; +} + +static ssize_t __extra_info_report_dying_msg(const rst_exinfo_t *rst_exinfo, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const _kern_ex_info_t *kinfo = &rst_exinfo->kern_ex_info.info; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"BUG\":\"%s\",", kinfo->bug_buf); + if (strlen(kinfo->panic_buf)) + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"PANIC\":\"%s\",", kinfo->panic_buf); + else if (rst_exinfo->uc_ex_info.magic == RESTART_REASON_SEC_DEBUG_MODE) + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"PANIC\":\"UCS-%s\",", rst_exinfo->uc_ex_info.str); + + return info_size; +} + +static ssize_t __extra_info_report_context(const _kern_ex_info_t *kinfo, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"PC\":\"%s\",", kinfo->pc); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"LR\":\"%s\",", kinfo->lr); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"UFS\":\"%s\",", kinfo->ufs_err); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"ROT\":\"W%dC%d\",", __qc_hw_param_read_param0("wb"), __qc_hw_param_read_param0("ddi")); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"STACK\":\"%s\"", kinfo->backtrace); + + return info_size; +} + +static inline bool __extra_info_kinfo_is_valid(const _kern_ex_info_t *kinfo) +{ + if (!isascii(kinfo->bug_buf[0])) + return false; + + if (kinfo->bug_buf[0] == '\0' || isprint(kinfo->bug_buf[0])) + return true; + + if (kinfo->panic_buf[0] == '\0' || isprint(kinfo->panic_buf[0])) + return true; + + return false; +} + +static ssize_t extra_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t info_size = 0; + const ssize_t sz_buf = EXTRA_LEN_STR; + unsigned int reset_reason; + rst_exinfo_t *rst_exinfo; + _kern_ex_info_t *kinfo; + + reset_reason = sec_qc_reset_reason_get(); + if (!__qc_hw_param_is_valid_reset_reason(reset_reason)) + goto err_invalid_reset_reason; + + rst_exinfo = kmalloc(sizeof(rst_exinfo_t), GFP_KERNEL); + if (!rst_exinfo) + goto err_enomem; + + if (!sec_qc_dbg_part_read(debug_index_reset_ex_info, rst_exinfo)) { + dev_warn(dev, "failed to read debug paritition.\n"); + goto err_failed_to_read; + } + + kinfo = &rst_exinfo->kern_ex_info.info; + /* FIXME: it seems like, if the 1st upload is not a kernel panic, + * 'kinfo' area is not initialized by BL. */ + if (!__extra_info_kinfo_is_valid(kinfo)) + memset(kinfo, 0x0, sizeof(*kinfo)); + + info_size = __extra_info_report_reset_reason(reset_reason, buf, sz_buf, info_size); + info_size = __extra_info_report_task(kinfo, buf, sz_buf, info_size); + info_size = __extra_info_report_smmu(kinfo, buf, sz_buf, info_size); + info_size = __extra_info_report_badmode(kinfo, buf, sz_buf, info_size); + info_size = __extra_info_report_fault(kinfo, buf, sz_buf, info_size); + info_size = __extra_info_report_dying_msg(rst_exinfo, buf, sz_buf, info_size); + info_size = __extra_info_report_context(kinfo, buf, sz_buf, info_size); + +err_failed_to_read: + kfree(rst_exinfo); +err_enomem: + __qc_hw_param_clean_format(buf, &info_size, sz_buf); +err_invalid_reset_reason: + return info_size; +} +DEVICE_ATTR_RO(extra_info); diff --git a/drivers/samsung/debug/qcom/hw_param/sec_qc_extrb_info.c b/drivers/samsung/debug/qcom/hw_param/sec_qc_extrb_info.c new file mode 100644 index 000000000000..95938493381c --- /dev/null +++ b/drivers/samsung/debug/qcom/hw_param/sec_qc_extrb_info.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2014-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include + +#include +#include + +#include "sec_qc_hw_param.h" + +static ssize_t __extrb_info_report_reset_reason(unsigned int reset_reason, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const char *reset_reason_str; + int reset_write_cnt; + + reset_reason_str = sec_qc_reset_reason_to_str(reset_reason); + reset_write_cnt = sec_qc_get_reset_write_cnt(); + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"RR\":\"%s\",", reset_reason_str); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"RWC\":\"%d\",", reset_write_cnt); + + return info_size; +} + +static ssize_t __extrb_info_report_rpm_ex_info(const rst_exinfo_t *rst_exinfo, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const rpm_exinfo_t *rpm_ex_info = &rst_exinfo->rpm_ex_info; + int idx, cnt, max_cnt; + + if (rpm_ex_info->info.magic != RPM_EX_INFO_MAGIC || + rpm_ex_info->info.nlog <= 0) + return info_size; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"RPM\":\""); + + if (rpm_ex_info->info.nlog > 5) { + idx = rpm_ex_info->info.nlog % 5; + max_cnt = 5; + } else { + idx = 0; + max_cnt = rpm_ex_info->info.nlog; + } + + for (cnt = 0; cnt < max_cnt; cnt++, idx++) { + const __rpm_log_t *rpm_log = &rpm_ex_info->info.log[idx % 5]; + unsigned long ts_nsec = (unsigned long)rpm_log->nsec; + unsigned long rem_nsec = do_div(ts_nsec, 1000000000); + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%lu.%06lu ", ts_nsec, rem_nsec / 1000); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%s ", rpm_log->msg); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%x %x %x %x", + rpm_log->arg[0], rpm_log->arg[1], rpm_log->arg[2], rpm_log->arg[3]); + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%s", (cnt == max_cnt - 1) ? "\"," : "/"); + } + + return info_size; +} + +static ssize_t __extrb_info_report_tz_ex_info(const rst_exinfo_t *rst_exinfo, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const tz_exinfo_t *tz_ex_info = &rst_exinfo->tz_ex_info; + const char *tz_cpu_status[6] = { "NA", "R", "PC", "WB", "IW", "IWT" }; + int cnt, max_cnt; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"TZ_RR\":\"%s\"", tz_ex_info->msg); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, ",\"TZBC\":\""); + + max_cnt = sizeof(tz_ex_info->cpu_status); + max_cnt = max_cnt / sizeof(tz_ex_info->cpu_status[0]); + + for (cnt = 0; cnt < max_cnt; cnt++) { + if (tz_ex_info->cpu_status[cnt] > INVALID_WARM_TERM_ENTRY_EXIT_COUNT) + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%s", tz_cpu_status[0]); + else + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%s", tz_cpu_status[tz_ex_info->cpu_status[cnt]]); + + if (cnt != max_cnt - 1) + __qc_hw_param_scnprintf(buf, sz_buf, info_size, ","); + } + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\""); + + return info_size; +} + +static ssize_t __extrb_info_report_pimem_ex_info(const rst_exinfo_t *rst_exinfo, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const pimem_exinfo_t *pimem_info = &rst_exinfo->pimem_info; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, ",\"PIMEM\":\"0x%08x,0x%08x,0x%08x,0x%08x\"", + pimem_info->esr, pimem_info->ear0, pimem_info->esr_sdi, pimem_info->ear0_sdi); + + return info_size; +} + +static ssize_t __extrb_info_report_cpu_stuck_ex_info(const rst_exinfo_t *rst_exinfo, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const cpu_stuck_exinfo_t *cpu_stuck_info = &rst_exinfo->cpu_stuck_info; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, + ",\"COST\":\"%lld,%lld,%lld,%lld,%lld,%lld,%lld,%lld\"", + cpu_stuck_info->cpu[0], cpu_stuck_info->cpu[1], + cpu_stuck_info->cpu[2], cpu_stuck_info->cpu[3], + cpu_stuck_info->cpu[4], cpu_stuck_info->cpu[5], + cpu_stuck_info->cpu[6], cpu_stuck_info->cpu[7]); + + return info_size; +} + +static ssize_t __extrb_info_report_cpu_hyp_ex_info(const rst_exinfo_t *rst_exinfo, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const hyp_exinfo_t *hyp_ex_info = &rst_exinfo->hyp_ex_info; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, ",\"HYC\":\"%d\"", hyp_ex_info->s2_fault_counter); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, ",\"HYP\":\"%s\"", hyp_ex_info->msg); + + return info_size; +} + +static ssize_t __extrb_info_report_cpu_kern_ex_info(const rst_exinfo_t *rst_exinfo, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const _kern_ex_info_t *kern_ex_info = &rst_exinfo->kern_ex_info.info; + int idx, max_cnt; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, ",\"LPM\":\""); + max_cnt = sizeof(kern_ex_info->lpm_state); + max_cnt = max_cnt / sizeof(kern_ex_info->lpm_state[0]); + for (idx = 0; idx < max_cnt; idx++) { + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%x", kern_ex_info->lpm_state[idx]); + if (idx != max_cnt - 1) + __qc_hw_param_scnprintf(buf, sz_buf, info_size, ","); + } + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\""); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, ",\"PKO\":\"%x\"", kern_ex_info->pko); + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, ",\"LR\":\""); + max_cnt = sizeof(kern_ex_info->lr_val); + max_cnt = max_cnt / sizeof(kern_ex_info->lr_val[0]); + for (idx = 0; idx < max_cnt; idx++) { + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%llx", kern_ex_info->lr_val[idx]); + if (idx != max_cnt - 1) + __qc_hw_param_scnprintf(buf, sz_buf, info_size, ","); + } + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\""); + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, ",\"PC\":\""); + max_cnt = sizeof(kern_ex_info->pc_val); + max_cnt = max_cnt / sizeof(kern_ex_info->pc_val[0]); + for (idx = 0; idx < max_cnt; idx++) { + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%llx", kern_ex_info->pc_val[idx]); + if (idx != max_cnt - 1) + __qc_hw_param_scnprintf(buf, sz_buf, info_size, ","); + } + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\""); + + return info_size; +} + +static ssize_t extrb_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t info_size = 0; + const ssize_t sz_buf = SPECIAL_LEN_STR; + unsigned int reset_reason; + rst_exinfo_t *rst_exinfo; + + reset_reason = sec_qc_reset_reason_get(); + if (!__qc_hw_param_is_valid_reset_reason(reset_reason)) + goto err_invalid_reset_reason; + + rst_exinfo = kmalloc(sizeof(rst_exinfo_t), GFP_KERNEL); + if (!rst_exinfo) + goto err_enomem; + + if (!sec_qc_dbg_part_read(debug_index_reset_ex_info, rst_exinfo)) { + dev_warn(dev, "failed to read debug paritition.\n"); + goto err_failed_to_read; + } + + info_size = __extrb_info_report_reset_reason(reset_reason, buf, sz_buf, info_size); + info_size = __extrb_info_report_rpm_ex_info(rst_exinfo, buf, sz_buf, info_size); + info_size = __extrb_info_report_tz_ex_info(rst_exinfo, buf, sz_buf, info_size); + info_size = __extrb_info_report_pimem_ex_info(rst_exinfo, buf, sz_buf, info_size); + info_size = __extrb_info_report_cpu_stuck_ex_info(rst_exinfo, buf, sz_buf, info_size); + info_size = __extrb_info_report_cpu_hyp_ex_info(rst_exinfo, buf, sz_buf, info_size); + info_size = __extrb_info_report_cpu_kern_ex_info(rst_exinfo, buf, sz_buf, info_size); + +err_failed_to_read: + kfree(rst_exinfo); +err_enomem: + __qc_hw_param_clean_format(buf, &info_size, sz_buf); +err_invalid_reset_reason: + return info_size; +} +DEVICE_ATTR_RO(extrb_info); diff --git a/drivers/samsung/debug/qcom/hw_param/sec_qc_extrc_info.c b/drivers/samsung/debug/qcom/hw_param/sec_qc_extrc_info.c new file mode 100644 index 000000000000..0870939722d1 --- /dev/null +++ b/drivers/samsung/debug/qcom/hw_param/sec_qc_extrc_info.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2014-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include + +#include +#include + +#include "sec_qc_hw_param.h" + +static ssize_t __extrc_info_report_reset_reason(unsigned int reset_reason, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const char *reset_reason_str; + int reset_write_cnt; + + reset_reason_str = sec_qc_reset_reason_to_str(reset_reason); + reset_write_cnt = sec_qc_get_reset_write_cnt(); + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"RR\":\"%s\",", reset_reason_str); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"RWC\":\"%d\",", reset_write_cnt); + + return info_size; +} + +static ssize_t extrc_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t info_size = 0; + const ssize_t sz_buf = SPECIAL_LEN_STR; + unsigned int reset_reason; + char *extrc_buf; + size_t i; + + reset_reason = sec_qc_reset_reason_get(); + if (!__qc_hw_param_is_valid_reset_reason(reset_reason)) + goto err_invalid_reset_reason; + + extrc_buf = kmalloc(SEC_DEBUG_RESET_EXTRC_SIZE, GFP_KERNEL); + if (!extrc_buf) + goto err_enomem; + + if (!sec_qc_dbg_part_read(debug_index_reset_extrc_info, extrc_buf)) { + dev_warn(dev, "failed to read debug paritition.\n"); + goto err_failed_to_read; + } + + info_size = __extrc_info_report_reset_reason(reset_reason, buf, sz_buf, info_size); + + /* check " character and then change ' character */ + for (i = 0; i < SEC_DEBUG_RESET_EXTRC_SIZE; i++) { + if (extrc_buf[i] == '"') + extrc_buf[i] = '\''; + if (extrc_buf[i] == '\0') + break; + } + extrc_buf[SEC_DEBUG_RESET_EXTRC_SIZE - 1] = '\0'; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"LKL\":\"%s\"", &extrc_buf[info_size]); + +err_failed_to_read: + kfree(extrc_buf); +err_enomem: + __qc_hw_param_clean_format(buf, &info_size, sz_buf); +err_invalid_reset_reason: + return info_size; +} +DEVICE_ATTR_RO(extrc_info); diff --git a/drivers/samsung/debug/qcom/hw_param/sec_qc_extrm_info.c b/drivers/samsung/debug/qcom/hw_param/sec_qc_extrm_info.c new file mode 100644 index 000000000000..7551af9ed546 --- /dev/null +++ b/drivers/samsung/debug/qcom/hw_param/sec_qc_extrm_info.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2014-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include + +#include +#include + +#include "sec_qc_hw_param.h" + +static ssize_t __extrm_info_report_reset_reason(unsigned int reset_reason, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const char *reset_reason_str; + int reset_write_cnt; + + reset_reason_str = sec_qc_reset_reason_to_str(reset_reason); + reset_write_cnt = sec_qc_get_reset_write_cnt(); + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"RR\":\"%s\",", reset_reason_str); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"RWC\":\"%d\",", reset_write_cnt); + + return info_size; +} + +static ssize_t extrm_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t info_size = 0; + const ssize_t sz_buf = SPECIAL_LEN_STR; + unsigned int reset_reason; + char *extrm_buf; + + reset_reason = sec_qc_reset_reason_get(); + if (!__qc_hw_param_is_valid_reset_reason(reset_reason)) + goto err_invalid_reset_reason; + + extrm_buf = kmalloc(SEC_DEBUG_RESET_ETRM_SIZE, GFP_KERNEL); + if (!extrm_buf) + goto err_enomem; + + if (!sec_qc_dbg_part_read(debug_index_reset_rkplog, extrm_buf)) { + dev_warn(dev, "failed to read debug paritition.\n"); + goto err_failed_to_read; + } + + info_size = __extrm_info_report_reset_reason(reset_reason, buf, sz_buf, info_size); + + extrm_buf[SEC_DEBUG_RESET_ETRM_SIZE - 1] = '\0'; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"RKP\":\"%s\"", &extrm_buf[info_size]); + +err_failed_to_read: + kfree(extrm_buf); +err_enomem: + __qc_hw_param_clean_format(buf, &info_size, sz_buf); +err_invalid_reset_reason: + return info_size; +} +DEVICE_ATTR_RO(extrm_info); diff --git a/drivers/samsung/debug/qcom/hw_param/sec_qc_extrt_info.c b/drivers/samsung/debug/qcom/hw_param/sec_qc_extrt_info.c new file mode 100644 index 000000000000..caf0df67f9d3 --- /dev/null +++ b/drivers/samsung/debug/qcom/hw_param/sec_qc_extrt_info.c @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2014-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include + +#include +#include + +#include "sec_qc_hw_param.h" + +static ssize_t __extrt_info_report_reset_reason(unsigned int reset_reason, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const char *reset_reason_str; + int reset_write_cnt; + + reset_reason_str = sec_qc_reset_reason_to_str(reset_reason); + reset_write_cnt = sec_qc_get_reset_write_cnt(); + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"RR\":\"%s\",", reset_reason_str); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"RWC\":\"%d\",", reset_write_cnt); + + return info_size; +} + +static ssize_t __extrt_info_report_tz_diag_info_v9_3(const struct tzdbg_t *tz_diag_info, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const struct tzdbg_log_v9_2_t *log_v9_2 = (struct tzdbg_log_v9_2_t *)((void *)tz_diag_info + + tz_diag_info->ring_off - sizeof(struct tzdbg_log_pos_v9_2_t)); + const struct tzbsp_encr_info_t *encr_info = (struct tzbsp_encr_info_t *)((void *)tz_diag_info + + tz_diag_info->v9_3.encr_info_for_log_off); + const size_t logbuf_size = encr_info->chunks[1].size_to_encr ? : 512; + size_t i; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"TZDA\":\""); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%08X%08X", + cpu_to_be32(tz_diag_info->magic_num), cpu_to_be32(tz_diag_info->version)); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%08X%08X", + cpu_to_be32(tz_diag_info->ring_off), cpu_to_be32(tz_diag_info->ring_len)); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%08X%08X", + cpu_to_be32(tz_diag_info->v9_3.encr_info_for_log_off), + cpu_to_be32(encr_info->chunks[1].size_to_encr)); + + for (i = 0; i < TZBSP_AES_256_ENCRYPTED_KEY_SIZE; i++) + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%02X", encr_info->key[i]); + + for (i = 0; i < TZBSP_NONCE_LEN; i++) + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%02X", encr_info->chunks[1].nonce[i]); + + for (i = 0; i < TZBSP_TAG_LEN; i++) + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%02X", encr_info->chunks[1].tag[i]); + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%08X%08X", + cpu_to_be32(log_v9_2->log_pos.wrap), cpu_to_be32(log_v9_2->log_pos.offset)); + + for (i = 0; i < logbuf_size; i++) + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%02X", log_v9_2->log_buf[i]); + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\""); + + return info_size; +} + +static ssize_t __extrt_info_report_tz_diag_info_v9_2(const struct tzdbg_t *tz_diag_info, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const struct tzdbg_log_v9_2_t *log_v9_2 = (struct tzdbg_log_v9_2_t *)((void *)tz_diag_info + + tz_diag_info->ring_off - sizeof(struct tzdbg_log_pos_v9_2_t)); + size_t i; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"TZDA\":\""); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%08X%08X", + tz_diag_info->magic_num, tz_diag_info->version); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%08X%08X%08X%08X", + tz_diag_info->cpu_count, tz_diag_info->ring_off, + tz_diag_info->ring_len, tz_diag_info->v9_2.num_interrupts); + + for (i = 0; i < TZBSP_AES_256_ENCRYPTED_KEY_SIZE; i++) + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%02X", tz_diag_info->v9_2.key[i]); + + for (i = 0; i < TZBSP_NONCE_LEN; i++) + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%02X", tz_diag_info->v9_2.nonce[i]); + + for (i = 0; i < TZBSP_TAG_LEN; i++) + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%02X", tz_diag_info->v9_2.tag[i]); + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%08X%08X", + log_v9_2->log_pos.wrap, log_v9_2->log_pos.offset); + + for (i = 0; i < tz_diag_info->ring_len; i++) + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%02X", log_v9_2->log_buf[i]); + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\""); + + return info_size; +} + +static ssize_t __extrt_info_report_tz_diag_info_legacy(const struct tzdbg_t *tz_diag_info, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const struct tzdbg_log_t *log = (struct tzdbg_log_t *)((void *)tz_diag_info + + tz_diag_info->ring_off - sizeof(struct tzdbg_log_pos_t)); + size_t i; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"TZDA\":\""); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%08X%08X", + tz_diag_info->magic_num, tz_diag_info->version); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%08X%08X%08X%08X", + tz_diag_info->cpu_count, tz_diag_info->ring_off, + tz_diag_info->ring_len, tz_diag_info->v9_2.num_interrupts); + + for (i = 0; i < TZBSP_AES_256_ENCRYPTED_KEY_SIZE; i++) + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%02X", tz_diag_info->v9_2.key[i]); + + for (i = 0; i < TZBSP_NONCE_LEN; i++) + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%02X", tz_diag_info->v9_2.nonce[i]); + + for (i = 0; i < TZBSP_TAG_LEN; i++) + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%02X", tz_diag_info->v9_2.tag[i]); + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%04X%04X", + log->log_pos.wrap, log->log_pos.offset); + + for (i = 0; i < tz_diag_info->ring_len; i++) + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "%02X", log->log_buf[i]); + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\""); + + return info_size; +} + +static ssize_t __extrt_info_report_tz_diag_info(const struct tzdbg_t *tz_diag_info, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + ssize_t written; + + /* TODO: I'll remove statements when it is not used anymore in future. */ + if (tz_diag_info->version > TZBSP_DIAG_VERSION_V9_2) + written = __extrt_info_report_tz_diag_info_v9_3(tz_diag_info, buf, + sz_buf, info_size); + else if (tz_diag_info->version == TZBSP_DIAG_VERSION_V9_2) + written = __extrt_info_report_tz_diag_info_v9_2(tz_diag_info, + buf, sz_buf, info_size); + else + written = __extrt_info_report_tz_diag_info_legacy(tz_diag_info, + buf, sz_buf, info_size); + + return written; +} + +static ssize_t extrt_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t info_size = 0; + const ssize_t sz_buf = SPECIAL_LEN_STR; + unsigned int reset_reason; + struct tzdbg_t *tz_diag_info; + ssize_t size; + + reset_reason = sec_qc_reset_reason_get(); + if (!__qc_hw_param_is_valid_reset_reason(reset_reason)) + goto err_invalid_reset_reason; + + size = sec_qc_dbg_part_get_size(debug_index_reset_tzlog); + if (size <= 0) + goto err_get_size; + + tz_diag_info = kmalloc(size, GFP_KERNEL); + if (!tz_diag_info) + goto err_enomem; + + if (!sec_qc_dbg_part_read(debug_index_reset_tzlog, tz_diag_info)) { + dev_warn(dev, "failed to read debug paritition.\n"); + goto err_failed_to_read; + } + + info_size = __extrt_info_report_reset_reason(reset_reason, buf, sz_buf, info_size); + + if (tz_diag_info->magic_num != TZ_DIAG_LOG_MAGIC) { + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"ERR\":\"tzlog magic num error\""); + goto err_invalid_maic; + } + + if (tz_diag_info->version > TZBSP_DIAG_VERSION_V9_2) { + struct tzbsp_encr_info_t *encr_info = (struct tzbsp_encr_info_t *)((void *)tz_diag_info + + tz_diag_info->v9_3.encr_info_for_log_off); + if (encr_info->chunks[1].size_to_encr > 0x200) {/* 512 byte */ + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"ERR\":\"tzlog size over\""); + goto err_size_over; + } + } else { + if (tz_diag_info->ring_len > 0x200) {/* 512 byte */ + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"ERR\":\"tzlog size over\""); + goto err_size_over; + } + } + + info_size = __extrt_info_report_tz_diag_info(tz_diag_info, buf, sz_buf, info_size); + +err_size_over: +err_invalid_maic: +err_failed_to_read: + kfree(tz_diag_info); +err_enomem: + __qc_hw_param_clean_format(buf, &info_size, sz_buf); +err_get_size: +err_invalid_reset_reason: + return info_size; +} +DEVICE_ATTR_RO(extrt_info); diff --git a/drivers/samsung/debug/qcom/hw_param/sec_qc_hw_param.h b/drivers/samsung/debug/qcom/hw_param/sec_qc_hw_param.h new file mode 100644 index 000000000000..502596341ed5 --- /dev/null +++ b/drivers/samsung/debug/qcom/hw_param/sec_qc_hw_param.h @@ -0,0 +1,92 @@ +#ifndef __INTERNAL__SEC_QC_HW_PARAM_H__ +#define __INTERNAL__SEC_QC_HW_PARAM_H__ + +#include +#include + +#include + +#include +#include + +#include "sec_qc_hw_param_mock.h" + +#define PARAM0_IVALID 1 +#define PARAM0_LESS_THAN_0 2 + +#define DEFAULT_LEN_STR 1023 +#define SPECIAL_LEN_STR 2047 +#define EXTRA_LEN_STR ((SPECIAL_LEN_STR) - (31 + 5)) + +#define __qc_hw_param_scnprintf(buf, size, offset, fmt, ...) \ +do { \ + if ((size) <= (offset)) \ + break; \ + offset += scnprintf(&(buf)[offset], size - (size_t)offset, fmt, \ + ##__VA_ARGS__); \ +} while (0) + +struct qc_ap_info { + struct device_node *np_hw_param; + unsigned int system_rev; +}; + +struct qc_hw_param_drvdata { + struct builder bd; + ap_health_t *ap_health; + struct device *sec_hw_param_dev; + struct qc_ap_info ap_info; + struct proc_dir_entry *errp_extra_proc; + bool use_ddr_eye_info; +}; + +extern struct qc_hw_param_drvdata *qc_hw_param; + +static __always_inline bool __qc_hw_param_is_probed(void) +{ + return !!qc_hw_param; +} + +/* TODO: internal use only */ +extern bool __qc_hw_param_is_valid_reset_reason(unsigned int reset_reason); +extern void __qc_hw_param_clean_format(char *buf, ssize_t *size, int max_len_str); +extern int __qc_hw_param_read_param0(const char *name); + +/* sec_qc_ap_info.c */ +extern struct device_attribute dev_attr_ap_info; +extern int __qc_ap_info_init(struct builder *bd); + +/* sec_qc_ap_health.c */ +extern struct device_attribute dev_attr_ap_health; + +/* sec_qc_last_dcvs.c */ +extern struct device_attribute dev_attr_last_dcvs; +extern int __qc_last_dcvs_init(struct builder *bd); + +/* sec_qc_extra_info.c */ +extern struct device_attribute dev_attr_extra_info; + +/* sec_qc_extrb_info.c */ +extern struct device_attribute dev_attr_extrb_info; + +/* sec_qc_extrc_info.c */ +extern struct device_attribute dev_attr_extrc_info; + +/* sec_qc_extrt_info.c */ +extern struct device_attribute dev_attr_extrt_info; + +/* sec_qc_extrm_info.c */ +extern struct device_attribute dev_attr_extrm_info; + +/* sec_qc_errp_extra.c */ +extern int __qc_errp_extra_init(struct builder *bd); +extern void __qc_errp_extra_exit(struct builder *bd); + +/* sec_qc_ddr_info.c */ +extern struct device_attribute dev_attr_ddr_info; +extern struct device_attribute dev_attr_eye_rd_info; +extern struct device_attribute dev_attr_eye_dcc_info; +extern struct device_attribute dev_attr_eye_wr1_info; +extern struct device_attribute dev_attr_eye_wr2_info; + +#endif /* __INTERNAL__SEC_QC_HW_PARAM_H__ */ diff --git a/drivers/samsung/debug/qcom/hw_param/sec_qc_hw_param_main.c b/drivers/samsung/debug/qcom/hw_param/sec_qc_hw_param_main.c new file mode 100644 index 000000000000..712255eecdb3 --- /dev/null +++ b/drivers/samsung/debug/qcom/hw_param/sec_qc_hw_param_main.c @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2014-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include + +#include + +#include "sec_qc_hw_param.h" + +struct qc_hw_param_drvdata *qc_hw_param; + +bool __qc_hw_param_is_valid_reset_reason(unsigned int reset_reason) +{ + if (reset_reason < USER_UPLOAD_CAUSE_MIN || + reset_reason > USER_UPLOAD_CAUSE_MAX) + return false; + + if (reset_reason == USER_UPLOAD_CAUSE_MANUAL_RESET || + reset_reason == USER_UPLOAD_CAUSE_REBOOT || + reset_reason == USER_UPLOAD_CAUSE_BOOTLOADER_REBOOT || + reset_reason == USER_UPLOAD_CAUSE_POWER_ON) + return false; + + return true; +} + +void __qc_hw_param_clean_format(char *buf, ssize_t *size, int max_len_str) +{ + int i = 0, cnt = 0, pos = 0; + + if (!buf || *size <= 0) + return; + + if (*size >= max_len_str) + *size = max_len_str - 1; + + while (i < *size && buf[i]) { + if (buf[i] == '"') { + cnt++; + pos = i; + } + + if ((buf[i] < 0x20) || (buf[i] == 0x5C) || (buf[i] > 0x7E)) + buf[i] = ' '; + i++; + } + + if (cnt % 2) { + if (pos == *size - 1) { + buf[*size - 1] = '\0'; + } else { + buf[*size - 1] = '"'; + buf[*size] = '\0'; + } + } +} + +int __qc_hw_param_read_param0(const char *name) +{ + struct qc_ap_info *ap_info = &qc_hw_param->ap_info; + struct device *dev = qc_hw_param->bd.dev; + u32 val; + int ret; + + if (!ap_info->np_hw_param) + return -PARAM0_IVALID; + + ret = of_property_read_u32(ap_info->np_hw_param, name, &val); + if (ret) { + dev_warn(dev, "failed to get %s from node\n", name); + return -PARAM0_LESS_THAN_0; + } + + return (int)val; +} + +static noinline int __hw_param_test_ap_health(struct builder *bd) +{ + struct qc_hw_param_drvdata *drvdata = + container_of(bd, struct qc_hw_param_drvdata, bd); + ap_health_t *health = sec_qc_ap_health_data_read(); + + if (PTR_ERR(health) == -EBUSY) + return -EPROBE_DEFER; + else if (IS_ERR_OR_NULL(health)) + return -ENODEV; + + drvdata->ap_health = health; + + return 0; +} + +static noinline int __hw_param_parse_dt_use_ddr_eye_info(struct builder *bd, + struct device_node *np) +{ + struct qc_hw_param_drvdata *drvdata = + container_of(bd, struct qc_hw_param_drvdata, bd); + + drvdata->use_ddr_eye_info = + of_property_read_bool(np, "sec,use-ddr_eye_info"); + + return 0; +} + +static const struct dt_builder __hw_param_dt_builder[] = { + DT_BUILDER(__hw_param_parse_dt_use_ddr_eye_info), +}; + +static noinline int __hw_param_parse_dt(struct builder *bd) +{ + return sec_director_parse_dt(bd, __hw_param_dt_builder, + ARRAY_SIZE(__hw_param_dt_builder)); +} + +static noinline int __hw_param_init_sec_class(struct builder *bd) +{ + struct qc_hw_param_drvdata *drvdata = + container_of(bd, struct qc_hw_param_drvdata, bd); + struct device *sec_hw_param_dev; + + sec_hw_param_dev = sec_device_create(NULL, "sec_hw_param"); + if (IS_ERR(sec_hw_param_dev)) { + dev_err(bd->dev, "failed to create device for sec_debug\n"); + return PTR_ERR(sec_hw_param_dev); + } + + drvdata->sec_hw_param_dev = sec_hw_param_dev; + + return 0; +} + +static noinline int __hw_param_find_sec_hw_param_node(struct builder *bd) +{ + struct qc_hw_param_drvdata *drvdata = + container_of(bd, struct qc_hw_param_drvdata, bd); + struct device *dev = drvdata->bd.dev; + struct qc_ap_info *ap_info = &drvdata->ap_info; + struct device_node *np = of_find_node_by_path("/soc/sec_hw_param"); + + if (!np) { + dev_warn(dev, "sec_hw_param node is not found\n"); + return 0; + } + + ap_info->np_hw_param = np; + + return 0; +} + +static noinline void __hw_param_exit_sec_class(struct builder *bd) +{ + struct qc_hw_param_drvdata *drvdata = + container_of(bd, struct qc_hw_param_drvdata, bd); + struct device *sec_hw_param_dev = drvdata->sec_hw_param_dev; + + sec_device_destroy(sec_hw_param_dev->devt); +} + +static struct attribute *hw_param_attrs[] = { + &dev_attr_ap_info.attr, + &dev_attr_ap_health.attr, + &dev_attr_last_dcvs.attr, + &dev_attr_extra_info.attr, + &dev_attr_extrb_info.attr, + &dev_attr_extrc_info.attr, + &dev_attr_extrt_info.attr, + &dev_attr_extrm_info.attr, + &dev_attr_ddr_info.attr, + &dev_attr_eye_rd_info.attr, + NULL, +}; + +static const struct attribute_group hw_param_attr_group = { + .attrs = hw_param_attrs, +}; + +static struct attribute *hw_param_attrs_for_ddr_eye_info[] = { + &dev_attr_eye_dcc_info.attr, + &dev_attr_eye_wr1_info.attr, + &dev_attr_eye_wr2_info.attr, + NULL, +}; + +static const struct attribute_group hw_param_attr_group_for_ddr_eye_info = { + .attrs = hw_param_attrs_for_ddr_eye_info, +}; + +static int __hw_param_sysfs_create_group(struct builder *bd) +{ + struct qc_hw_param_drvdata *drvdata = + container_of(bd, struct qc_hw_param_drvdata, bd); + struct device *sec_hw_param_dev = drvdata->sec_hw_param_dev; + int err; + + err = sysfs_create_group(&sec_hw_param_dev->kobj, + &hw_param_attr_group); + if (err) { + dev_err(bd->dev, "failed to create device files. (%d)", err); + return err; + } + + return 0; +} + +static void __hw_param_sysfs_remove_group(struct builder *bd) +{ + struct qc_hw_param_drvdata *drvdata = + container_of(bd, struct qc_hw_param_drvdata, bd); + struct device *sec_hw_param_dev = drvdata->sec_hw_param_dev; + + sysfs_remove_group(&sec_hw_param_dev->kobj, + &hw_param_attr_group); +} + +static int __hw_param_sysfs_create_group_ddr_eye_info(struct builder *bd) +{ + struct qc_hw_param_drvdata *drvdata = + container_of(bd, struct qc_hw_param_drvdata, bd); + struct device *sec_hw_param_dev = drvdata->sec_hw_param_dev; + int err; + + if (!drvdata->use_ddr_eye_info) + return 0; + + err = sysfs_create_group(&sec_hw_param_dev->kobj, + &hw_param_attr_group_for_ddr_eye_info); + if (err) { + dev_err(bd->dev, "failed to create device files. (%d)", err); + return err; + } + + return 0; +} + +static void __hw_param_sysfs_remove_group_ddr_eye_info(struct builder *bd) +{ + struct qc_hw_param_drvdata *drvdata = + container_of(bd, struct qc_hw_param_drvdata, bd); + struct device *sec_hw_param_dev = drvdata->sec_hw_param_dev; + + if (!drvdata->use_ddr_eye_info) + return; + + sysfs_remove_group(&sec_hw_param_dev->kobj, + &hw_param_attr_group_for_ddr_eye_info); +} + +static noinline int __hw_param_probe_epilog(struct builder *bd) +{ + struct qc_hw_param_drvdata *drvdata = + container_of(bd, struct qc_hw_param_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + qc_hw_param = drvdata; + + return 0; +} + +static noinline void __hw_param_remove_prolog(struct builder *bd) +{ + qc_hw_param = NULL; +} + +static const struct dev_builder __hw_param_dev_builder[] = { + DEVICE_BUILDER(__hw_param_test_ap_health, NULL), + /* TODO: deferrable concrete builders should be before here. */ + DEVICE_BUILDER(__hw_param_parse_dt, NULL), + DEVICE_BUILDER(__hw_param_init_sec_class, __hw_param_exit_sec_class), + DEVICE_BUILDER(__hw_param_find_sec_hw_param_node, NULL), + DEVICE_BUILDER(__qc_ap_info_init, NULL), + DEVICE_BUILDER(__qc_last_dcvs_init, NULL), + DEVICE_BUILDER(__hw_param_sysfs_create_group, + __hw_param_sysfs_remove_group), + DEVICE_BUILDER(__hw_param_sysfs_create_group_ddr_eye_info, + __hw_param_sysfs_remove_group_ddr_eye_info), + DEVICE_BUILDER(__qc_errp_extra_init, __qc_errp_extra_exit), + DEVICE_BUILDER(__hw_param_probe_epilog, __hw_param_remove_prolog), +}; + +static int __hw_param_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct qc_hw_param_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __hw_param_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct qc_hw_param_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static int sec_qc_hw_param_probe(struct platform_device *pdev) +{ + return __hw_param_probe(pdev, __hw_param_dev_builder, + ARRAY_SIZE(__hw_param_dev_builder)); +} + +static int sec_qc_hw_param_remove(struct platform_device *pdev) +{ + return __hw_param_remove(pdev, __hw_param_dev_builder, + ARRAY_SIZE(__hw_param_dev_builder)); +} + +static const struct of_device_id sec_qc_hw_param_match_table[] = { + { .compatible = "samsung,qcom-hw_param" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_qc_hw_param_match_table); + +static struct platform_driver sec_qc_hw_param_driver = { + .driver = { + .name = "sec,qc-hw_param", + .of_match_table = of_match_ptr(sec_qc_hw_param_match_table), + }, + .probe = sec_qc_hw_param_probe, + .remove = sec_qc_hw_param_remove, +}; + +static int __init sec_qc_hw_param_init(void) +{ + return platform_driver_register(&sec_qc_hw_param_driver); +} +module_init(sec_qc_hw_param_init); + +static void __exit sec_qc_hw_param_exit(void) +{ + platform_driver_unregister(&sec_qc_hw_param_driver); +} +module_exit(sec_qc_hw_param_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("HW Paramter Driver for Qualcomm based devices"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/debug/qcom/hw_param/sec_qc_hw_param_mock.h b/drivers/samsung/debug/qcom/hw_param/sec_qc_hw_param_mock.h new file mode 100644 index 000000000000..e9f54c526ed7 --- /dev/null +++ b/drivers/samsung/debug/qcom/hw_param/sec_qc_hw_param_mock.h @@ -0,0 +1,10 @@ +#ifndef __INTERNAL__SEC_QC_HW_PARAM_MOCK_H__ +#define __INTERNAL__SEC_QC_HW_PARAM_MOCK_H__ + +#if IS_ENABLED(CONFIG_QCOM_SOCINFO) +#include +#else +static inline uint32_t socinfo_get_id(void) { return (uint32_t)(-ENODEV); } +#endif + +#endif /* __INTERNAL__SEC_QC_HW_PARAM_MOCK_H__ */ diff --git a/drivers/samsung/debug/qcom/hw_param/sec_qc_last_dcvs.c b/drivers/samsung/debug/qcom/hw_param/sec_qc_last_dcvs.c new file mode 100644 index 000000000000..0b0b5dc81a32 --- /dev/null +++ b/drivers/samsung/debug/qcom/hw_param/sec_qc_last_dcvs.c @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2014-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include + +#include +#include + +#include "sec_qc_hw_param.h" + +static inline void __last_dcvs_update(struct qc_hw_param_drvdata *drvdata, + int cap, int volt, int temp, int curr) +{ + ap_health_t *health = drvdata->ap_health; + uint32_t tail; + + if ((health->battery.tail & 0xf) >= MAX_BATT_DCVS) + health->battery.tail = 0x10; + + tail = health->battery.tail & 0xf; + + health->battery.batt[tail].ktime = local_clock(); + health->battery.batt[tail].cap = cap; + health->battery.batt[tail].volt = volt; + health->battery.batt[tail].temp = temp; + health->battery.batt[tail].curr = curr; + + health->battery.tail++; +} + +void battery_last_dcvs(int cap, int volt, int temp, int curr) +{ + if (!__qc_hw_param_is_probed()) { + pr_warn("driver is not proved!\n"); + return; + } + + __last_dcvs_update(qc_hw_param, cap, volt, temp, curr); +} +EXPORT_SYMBOL_GPL(battery_last_dcvs); + +static ssize_t __last_dcvs_report_reset_reason(unsigned int reset_reason, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const char *reset_reason_str; + int reset_write_cnt; + + reset_reason_str = sec_qc_reset_reason_to_str(reset_reason); + reset_write_cnt = sec_qc_get_reset_write_cnt(); + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"RR\":\"%s\",", reset_reason_str); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"RWC\":\"%d\",", reset_write_cnt); + + return info_size; +} + +static ssize_t __last_dcvs_report_apps(const dcvs_info_t *last_dcvs, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const apps_dcvs_t *apps = &last_dcvs->apps[0]; + const char *prefix[MAX_CLUSTER_NUM] = { "L3", "SC", "GC", "GP" }; + size_t i; + + for (i = 0; i < MAX_CLUSTER_NUM; i++) + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"%sKHz\":\"%u\",", prefix[i], apps[i].cpu_KHz); + + return info_size; +} + +static ssize_t __last_dcvs_report_rpm(const dcvs_info_t *last_dcvs, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const rpm_dcvs_t *rpm = &last_dcvs->rpm; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"DDRKHz\":\"%u\",", rpm->ddr_KHz); + + return info_size; +} + +static ssize_t __last_dcvs_report_batt(const dcvs_info_t *last_dcvs, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const batt_dcvs_t *batt = &last_dcvs->batt; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"BCAP\":\"%d\",", batt->cap); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"BVOL\":\"%d\",", batt->volt); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"BTEM\":\"%d\",", batt->temp); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"BCUR\":\"%d\",", batt->curr); + + return info_size; +} + +static ssize_t __last_dcvs_report_pon(const dcvs_info_t *last_dcvs, + char *buf, const ssize_t sz_buf, ssize_t info_size) +{ + const pon_dcvs_t *pon = &last_dcvs->pon; + + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"PON\":\"%llx\",", pon->pon_reason); + __qc_hw_param_scnprintf(buf, sz_buf, info_size, "\"PONF\":\"%llx\",", pon->fault_reason); + + return info_size; +} + +static ssize_t last_dcvs_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned int reset_reason; + dcvs_info_t *last_dcvs; + ssize_t info_size = 0; + const ssize_t sz_buf = DEFAULT_LEN_STR; + + reset_reason = sec_qc_reset_reason_get(); + if (!__qc_hw_param_is_valid_reset_reason(reset_reason)) + goto err_invalid_reset_reason; + + last_dcvs = &qc_hw_param->ap_health->last_dcvs; + + info_size = __last_dcvs_report_reset_reason(reset_reason, buf, sz_buf, info_size); + info_size = __last_dcvs_report_apps(last_dcvs, buf, sz_buf, info_size); + info_size = __last_dcvs_report_rpm(last_dcvs, buf, sz_buf, info_size); + info_size = __last_dcvs_report_batt(last_dcvs, buf, sz_buf, info_size); + info_size = __last_dcvs_report_pon(last_dcvs, buf, sz_buf, info_size); + + /* remove the last ',' character */ + info_size--; + + __qc_hw_param_clean_format(buf, &info_size, sz_buf); + +err_invalid_reset_reason: + return info_size; +} +DEVICE_ATTR_RO(last_dcvs); + +static void __last_dcvs_clear_batt_info(struct qc_hw_param_drvdata *drvdata) +{ + ap_health_t *health = drvdata->ap_health; + + memset(&health->battery, 0x00, sizeof(battery_health_t)); +} + +int __qc_last_dcvs_init(struct builder *bd) +{ + struct qc_hw_param_drvdata *drvdata = + container_of(bd, struct qc_hw_param_drvdata, bd); + + __last_dcvs_clear_batt_info(drvdata); + + return 0; +} diff --git a/drivers/samsung/debug/qcom/logger/Kconfig b/drivers/samsung/debug/qcom/logger/Kconfig new file mode 100644 index 000000000000..fde502bee52a --- /dev/null +++ b/drivers/samsung/debug/qcom/logger/Kconfig @@ -0,0 +1,5 @@ +config SEC_QC_LOGGER + tristate "SEC Logger for Qualcomm based devices" + depends on SEC_DEBUG_REGION + help + TODO: help is not ready. diff --git a/drivers/samsung/debug/qcom/logger/Makefile b/drivers/samsung/debug/qcom/logger/Makefile new file mode 100644 index 000000000000..ce2c8d3a44bb --- /dev/null +++ b/drivers/samsung/debug/qcom/logger/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_SEC_QC_LOGGER) += sec_qc_logger.o +sec_qc_logger-objs := sec_qc_logger_main.o \ + sec_qc_sched_log.o \ + sec_qc_irq_log.o \ + sec_qc_irq_exit_log.o \ + sec_qc_msg_log.o diff --git a/drivers/samsung/debug/qcom/logger/sec_qc_irq_exit_log.c b/drivers/samsung/debug/qcom/logger/sec_qc_irq_exit_log.c new file mode 100644 index 000000000000..759c8b061531 --- /dev/null +++ b/drivers/samsung/debug/qcom/logger/sec_qc_irq_exit_log.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2020-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include + +#include +#include +#include + +#include "sec_qc_logger.h" + +static struct sec_qc_irq_exit_log_data *irq_exit_log_data __read_mostly; + +static __always_inline void __irq_exit_log(unsigned int irq, u64 start_time) +{ + struct sec_qc_irq_exit_buf *irq_exit_buf; + int cpu = smp_processor_id(); + unsigned int i; + + i = ++(irq_exit_log_data[cpu].idx) & (SEC_QC_IRQ_EXIT_LOG_MAX - 1); + irq_exit_buf = &irq_exit_log_data[cpu].buf[i]; + + irq_exit_buf->irq = irq; + irq_exit_buf->time = start_time; + irq_exit_buf->end_time = __qc_logger_cpu_clock(cpu); + irq_exit_buf->elapsed_time = irq_exit_buf->end_time - start_time; + irq_exit_buf->pid = current->pid; +} + +static int __irq_exit_log_set_irq_exit_log_data(struct builder *bd) +{ + struct qc_logger *logger = container_of(bd, struct qc_logger, bd); + struct sec_dbg_region_client *client = logger->drvdata->client; + int cpu; + + irq_exit_log_data = (void *)client->virt; + if (IS_ERR_OR_NULL(irq_exit_log_data)) + return -EFAULT; + + for_each_possible_cpu(cpu) { + irq_exit_log_data[cpu].idx = -1; + } + + return 0; +} + +static void __irq_exit_log_unset_irq_exit_log_data(struct builder *bd) +{ + irq_exit_log_data = NULL; +} + +static size_t sec_qc_irq_exit_log_get_data_size(struct qc_logger *logger) +{ + return sizeof(struct sec_qc_irq_exit_log_data) * num_possible_cpus(); +} + +static const struct dev_builder __irq_exit_log_dev_builder[] = { + DEVICE_BUILDER(__irq_exit_log_set_irq_exit_log_data, + __irq_exit_log_unset_irq_exit_log_data), +}; + +static int sec_qc_irq_exit_log_probe(struct qc_logger *logger) +{ + return __qc_logger_sub_module_probe(logger, + __irq_exit_log_dev_builder, + ARRAY_SIZE(__irq_exit_log_dev_builder)); +} + +static void sec_qc_irq_exit_log_remove(struct qc_logger *logger) +{ + __qc_logger_sub_module_remove(logger, + __irq_exit_log_dev_builder, + ARRAY_SIZE(__irq_exit_log_dev_builder)); +} + +struct qc_logger __qc_logger_irq_exit_log = { + .get_data_size = sec_qc_irq_exit_log_get_data_size, + .probe = sec_qc_irq_exit_log_probe, + .remove = sec_qc_irq_exit_log_remove, + .unique_id = SEC_QC_IRQ_EXIT_LOG_MAGIC, +}; diff --git a/drivers/samsung/debug/qcom/logger/sec_qc_irq_log.c b/drivers/samsung/debug/qcom/logger/sec_qc_irq_log.c new file mode 100644 index 000000000000..221e2ed65d21 --- /dev/null +++ b/drivers/samsung/debug/qcom/logger/sec_qc_irq_log.c @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2020-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include + +#include + +#include +#include +#include + +#include "sec_qc_logger.h" + +static struct sec_qc_irq_log_data *irq_log_data __read_mostly; + +static __always_inline void __irq_log(unsigned int irq, void *fn, + const char *name, unsigned int en) +{ + struct sec_qc_irq_buf *irq_buf; + int cpu = smp_processor_id(); + unsigned int i; + + i = ++(irq_log_data[cpu].idx) & (SEC_QC_IRQ_LOG_MAX - 1); + irq_buf = &irq_log_data[cpu].buf[i]; + + irq_buf->time = __qc_logger_cpu_clock(cpu); + irq_buf->irq = irq; + irq_buf->fn = fn; + irq_buf->name = name; + irq_buf->en = irqs_disabled(); + irq_buf->preempt_count = preempt_count(); + irq_buf->context = &cpu; + irq_buf->pid = current->pid; + irq_buf->entry_exit = en; +} + +static int __irq_log_set_irq_log_data(struct builder *bd) +{ + struct qc_logger *logger = container_of(bd, struct qc_logger, bd); + struct sec_dbg_region_client *client = logger->drvdata->client; + int cpu; + + irq_log_data = (void *)client->virt; + if (IS_ERR_OR_NULL(irq_log_data)) + return -EFAULT; + + for_each_possible_cpu(cpu) { + irq_log_data[cpu].idx = -1; + } + + return 0; +} + +static void __irq_log_unset_irq_log_data(struct builder *bd) +{ + irq_log_data = NULL; +} + +static void sec_qc_trace_irq_handler_entry(void *unused, + int irq, struct irqaction *action) +{ + __irq_log(irq, action->handler, (char *)action->name, IRQ_ENTRY); +} + +static int __irq_log_register_trace_irq_handler_entry(struct builder *bd) +{ + return register_trace_irq_handler_entry(sec_qc_trace_irq_handler_entry, + NULL); +} + +static void __irq_log_unregister_trace_irq_handler_entry(struct builder *bd) +{ + unregister_trace_irq_handler_entry(sec_qc_trace_irq_handler_entry, + NULL); +} + +static void sec_qc_trace_irq_handler_exit(void *unused, + int irq, struct irqaction *action, int res) +{ + __irq_log(irq, action->handler, (char *)action->name, IRQ_EXIT); +} + +static int __irq_log_register_trace_irq_handler_exit(struct builder *bd) +{ + return register_trace_irq_handler_exit(sec_qc_trace_irq_handler_exit, + NULL); +} + +static void __irq_log_unregister_trace_irq_handler_exit(struct builder *bd) +{ + unregister_trace_irq_handler_exit(sec_qc_trace_irq_handler_exit, NULL); +} + +#if 0 +/* NOTE: 'softirq_to_name' is not exported to the kernel moodule. + * We should use a built-in array of names. + */ +static const char * const __softirq_to_name[NR_SOFTIRQS] = { + "softirq-HI", "softirq-TIMER", "softirq-NET_TX", "softirq-NET_RX", + "softirq-BLOCK", "softirq-IRQ_POLL", "softirq-TASKLET", + "softirq-SCHED", "softirq-HRTIMER", "softirq-RCU" +}; + +static void sec_qc_trace_softirq_entry(void *unused, unsigned int vec_nr) +{ + __irq_log(-1, NULL, __softirq_to_name[vec_nr], SOFTIRQ_ENTRY); +} + +static int __irq_log_register_trace_softirq_entry(struct builder *bd) +{ + return register_trace_softirq_entry(sec_qc_trace_softirq_entry, NULL); +} + +static void __irq_log_unregister_trace_softirq_entry(struct builder *bd) +{ + unregister_trace_softirq_entry(sec_qc_trace_softirq_entry, NULL); +} + +static void sec_qc_trace_softirq_exit(void *unused, unsigned int vec_nr) +{ + __irq_log(-1, NULL, __softirq_to_name[vec_nr], SOFTIRQ_EXIT); +} + +static int __irq_log_register_trace_softirq_exit(struct builder *bd) +{ + return register_trace_softirq_exit(sec_qc_trace_softirq_exit, NULL); +} + +static void __irq_log_unregister_trace_softirq_exit(struct builder *bd) +{ + unregister_trace_softirq_exit(sec_qc_trace_softirq_exit, NULL); +} + +static void sec_qc_trace_tasklet_entry(void *unused, + int irq, struct tasklet_struct *t) +{ + __irq_log(-1, t->func, "tasklet_action", SOFTIRQ_ENTRY); +} + +static int __irq_log_register_trace_tasklet_entry(struct builder *bd) +{ + return register_trace_tasklet_entry(sec_qc_trace_tasklet_entry, NULL); +} + +static void __irq_log_unregister_trace_tasklet_entry(struct builder *bd) +{ + unregister_trace_tasklet_entry(sec_qc_trace_tasklet_entry, NULL); +} + +static void sec_qc_trace_tasklet_exit(void *unused, + int irq, struct tasklet_struct *t) +{ + __irq_log(-1, t->func, "tasklet_action", SOFTIRQ_EXIT); +} + +static int __irq_log_register_trace_tasklet_exit(struct builder *bd) +{ + return register_trace_tasklet_exit(sec_qc_trace_tasklet_exit, NULL); +} + +static void __irq_log_unregister_trace_tasklet_exit(struct builder *bd) +{ + unregister_trace_tasklet_exit(sec_qc_trace_tasklet_exit, NULL); +} +#endif + +static size_t sec_qc_irq_log_get_data_size(struct qc_logger *logger) +{ + return sizeof(struct sec_qc_irq_log_data) * num_possible_cpus(); +} + +static const struct dev_builder __irq_log_dev_builder[] = { + DEVICE_BUILDER(__irq_log_set_irq_log_data, + __irq_log_unset_irq_log_data), +}; + +static const struct dev_builder __irq_log_dev_builder_trace[] = { + DEVICE_BUILDER(__irq_log_register_trace_irq_handler_entry, + __irq_log_unregister_trace_irq_handler_entry), + DEVICE_BUILDER(__irq_log_register_trace_irq_handler_exit, + __irq_log_unregister_trace_irq_handler_exit), +#if 0 + DEVICE_BUILDER(__irq_log_register_trace_softirq_entry, + __irq_log_unregister_trace_softirq_entry), + DEVICE_BUILDER(__irq_log_register_trace_softirq_exit, + __irq_log_unregister_trace_softirq_exit), + DEVICE_BUILDER(__irq_log_register_trace_tasklet_entry, + __irq_log_unregister_trace_tasklet_entry), + DEVICE_BUILDER(__irq_log_register_trace_tasklet_exit, + __irq_log_unregister_trace_tasklet_exit), +#endif +}; + +static int sec_qc_irq_log_probe(struct qc_logger *logger) +{ + int err = __qc_logger_sub_module_probe(logger, + __irq_log_dev_builder, + ARRAY_SIZE(__irq_log_dev_builder)); + if (err) + return err; + + return __qc_logger_sub_module_probe(logger, + __irq_log_dev_builder_trace, + ARRAY_SIZE(__irq_log_dev_builder_trace)); +} + +static void sec_qc_irq_log_remove(struct qc_logger *logger) +{ + __qc_logger_sub_module_remove(logger, + __irq_log_dev_builder, + ARRAY_SIZE(__irq_log_dev_builder)); + + __qc_logger_sub_module_remove(logger, + __irq_log_dev_builder_trace, + ARRAY_SIZE(__irq_log_dev_builder_trace)); +} + +struct qc_logger __qc_logger_irq_log = { + .get_data_size = sec_qc_irq_log_get_data_size, + .probe = sec_qc_irq_log_probe, + .remove = sec_qc_irq_log_remove, + .unique_id = SEC_QC_IRQ_LOG_MAGIC, +}; diff --git a/drivers/samsung/debug/qcom/logger/sec_qc_logger.h b/drivers/samsung/debug/qcom/logger/sec_qc_logger.h new file mode 100644 index 000000000000..e036728fab2b --- /dev/null +++ b/drivers/samsung/debug/qcom/logger/sec_qc_logger.h @@ -0,0 +1,36 @@ +#ifndef __INTERNAL__SEC_QC_LOGGER_H__ +#define __INTERNAL__SEC_QC_LOGGER_H__ + +#include + +struct qc_logger; + +struct qc_logger_drvdata { + struct builder bd; + const char *name; + uint32_t unique_id; + struct sec_dbg_region_client *client; + struct qc_logger *logger; +}; + +struct qc_logger { + struct builder bd; + + size_t (*get_data_size)(struct qc_logger *logger); + int (*probe)(struct qc_logger *logger); + void (*remove)(struct qc_logger *logger); + + uint32_t unique_id; + struct qc_logger_drvdata *drvdata; +}; + +extern u64 notrace __qc_logger_cpu_clock(int cpu); +extern int __qc_logger_sub_module_probe(struct qc_logger *logger, const struct dev_builder *builder, ssize_t n); +extern void __qc_logger_sub_module_remove(struct qc_logger *logger, const struct dev_builder *builder, ssize_t n); + +extern struct qc_logger __qc_logger_sched_log; +extern struct qc_logger __qc_logger_irq_log; +extern struct qc_logger __qc_logger_irq_exit_log; +extern struct qc_logger __qc_logger_msg_log; + +#endif /* __INTERNAL__SEC_QC_LOGGER_H__ */ diff --git a/drivers/samsung/debug/qcom/logger/sec_qc_logger_main.c b/drivers/samsung/debug/qcom/logger/sec_qc_logger_main.c new file mode 100644 index 000000000000..29331e208a93 --- /dev/null +++ b/drivers/samsung/debug/qcom/logger/sec_qc_logger_main.c @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2020-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "sec_qc_logger.h" + +u64 notrace __qc_logger_cpu_clock(int cpu) +{ + static DEFINE_PER_CPU(u64, __cpu_clock); + u64 *last_clk = &per_cpu(__cpu_clock, cpu); + u64 curr_clk = cpu_clock(cpu); + + if (unlikely(*last_clk >= curr_clk)) + curr_clk = ++(*last_clk); + else + *last_clk = curr_clk; + + return curr_clk; +} + +int __qc_logger_sub_module_probe(struct qc_logger *logger, + const struct dev_builder *builder, ssize_t n) +{ + return sec_director_probe_dev(&logger->bd, builder, n); +} + +void __qc_logger_sub_module_remove(struct qc_logger *logger, + const struct dev_builder *builder, ssize_t n) +{ + sec_director_destruct_dev(&logger->bd, builder, n, n); +} + +static noinline int __qc_logger_parse_dt_name(struct builder *bd, struct device_node *np) +{ + struct qc_logger_drvdata *drvdata = + container_of(bd, struct qc_logger_drvdata, bd); + + return of_property_read_string(np, "sec,name", &drvdata->name); +} + +static noinline int __qc_logger_parse_dt_unique_id(struct builder *bd, + struct device_node *np) +{ + struct qc_logger_drvdata *drvdata = + container_of(bd, struct qc_logger_drvdata, bd); + u32 unique_id; + int err; + + err = of_property_read_u32(np, "sec,unique_id", &unique_id); + if (err) + return -EINVAL; + + drvdata->unique_id = (uint32_t)unique_id; + + return 0; +} + +static const struct dt_builder __qc_logger_dt_builder[] = { + DT_BUILDER(__qc_logger_parse_dt_name), + DT_BUILDER(__qc_logger_parse_dt_unique_id), +}; + +static noinline int __qc_logger_parse_dt(struct builder *bd) +{ + return sec_director_parse_dt(bd, __qc_logger_dt_builder, + ARRAY_SIZE(__qc_logger_dt_builder)); +} + +static DEFINE_MUTEX(__concrete_logger_lock); + +static struct qc_logger *__concrete_loggers[] = { + &__qc_logger_sched_log, + &__qc_logger_irq_log, + &__qc_logger_irq_exit_log, + &__qc_logger_msg_log, +}; + +static int __qc_logger_pick_concrete_logger(struct builder *bd) +{ + struct qc_logger_drvdata *drvdata = + container_of(bd, struct qc_logger_drvdata, bd); + struct qc_logger *logger = NULL; + int err = -ENODEV; + size_t i; + + mutex_lock(&__concrete_logger_lock); + for (i = 0; i < ARRAY_SIZE(__concrete_loggers); i++) { + logger = __concrete_loggers[i]; + + if (drvdata->unique_id == logger->unique_id) { + if (logger->drvdata) { /* already used */ + err = -EBUSY; + break; + } + + logger->drvdata = drvdata; + drvdata->logger = logger; + err = 0; + break; + } + } + mutex_unlock(&__concrete_logger_lock); + + return err; +} + +static void __qc_logger_unpick_concrete_logger(struct builder *bd) +{ + struct qc_logger_drvdata *drvdata = + container_of(bd, struct qc_logger_drvdata, bd); + struct qc_logger *logger = drvdata->logger; + + mutex_lock(&__concrete_logger_lock); + + logger->drvdata = NULL; + drvdata->logger = NULL; + + mutex_unlock(&__concrete_logger_lock); +} + +static int __qc_logger_alloc_client(struct builder *bd) +{ + struct qc_logger_drvdata *drvdata = + container_of(bd, struct qc_logger_drvdata, bd); + struct qc_logger *logger = drvdata->logger; + size_t size; + struct sec_dbg_region_client *client; + + size = logger->get_data_size(logger); + + client = sec_dbg_region_alloc(drvdata->unique_id, size); + if (PTR_ERR(client) == -EBUSY) + return -EPROBE_DEFER; + else if (IS_ERR_OR_NULL(client)) + return -ENOMEM; + + client->name = drvdata->name; + drvdata->client = client; + + return 0; +} + +static void __qc_logger_free_client(struct builder *bd) +{ + struct qc_logger_drvdata *drvdata = + container_of(bd, struct qc_logger_drvdata, bd); + + sec_dbg_region_free(drvdata->client); +} + +static int __qc_logger_call_concrete_logger_probe(struct builder *bd) +{ + struct qc_logger_drvdata *drvdata = + container_of(bd, struct qc_logger_drvdata, bd); + struct qc_logger *logger = drvdata->logger; + + return logger->probe(logger); +} + +static void __qc_logger_call_concrete_logger_remove(struct builder *bd) +{ + struct qc_logger_drvdata *drvdata = + container_of(bd, struct qc_logger_drvdata, bd); + struct qc_logger *logger = drvdata->logger; + + logger->remove(logger); +} + +static noinline int __qc_logger_probe_epilog(struct builder *bd) +{ + struct qc_logger_drvdata *drvdata = + container_of(bd, struct qc_logger_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + + return 0; +} + +static int __qc_logger_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct qc_logger_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __qc_logger_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct qc_logger_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static const struct dev_builder __qc_logger_dev_builder[] = { + DEVICE_BUILDER(__qc_logger_parse_dt, NULL), + DEVICE_BUILDER(__qc_logger_pick_concrete_logger, + __qc_logger_unpick_concrete_logger), + DEVICE_BUILDER(__qc_logger_alloc_client, + __qc_logger_free_client), + DEVICE_BUILDER(__qc_logger_call_concrete_logger_probe, + __qc_logger_call_concrete_logger_remove), + DEVICE_BUILDER(__qc_logger_probe_epilog, NULL), +}; + +static int sec_qc_logger_probe(struct platform_device *pdev) +{ + return __qc_logger_probe(pdev, __qc_logger_dev_builder, + ARRAY_SIZE(__qc_logger_dev_builder)); +} + +static int sec_qc_logger_remove(struct platform_device *pdev) +{ + return __qc_logger_remove(pdev, __qc_logger_dev_builder, + ARRAY_SIZE(__qc_logger_dev_builder)); +} + +static const struct of_device_id sec_qc_logger_match_table[] = { + { .compatible = "samsung,qcom-logger" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_qc_logger_match_table); + +static struct platform_driver sec_qc_logger_driver = { + .driver = { + .name = "sec,qc-logger", + .of_match_table = of_match_ptr(sec_qc_logger_match_table), + }, + .probe = sec_qc_logger_probe, + .remove = sec_qc_logger_remove, +}; + +static int __init sec_qc_logger_init(void) +{ + return platform_driver_register(&sec_qc_logger_driver); +} +arch_initcall(sec_qc_logger_init); + +static void __exit sec_qc_logger_exit(void) +{ + platform_driver_unregister(&sec_qc_logger_driver); +} +module_exit(sec_qc_logger_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("Logger for Qualcomm based devices"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/debug/qcom/logger/sec_qc_msg_log.c b/drivers/samsung/debug/qcom/logger/sec_qc_msg_log.c new file mode 100644 index 000000000000..20e5f93261f1 --- /dev/null +++ b/drivers/samsung/debug/qcom/logger/sec_qc_msg_log.c @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2020-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "sec_qc_logger.h" + +static struct sec_qc_msg_log_data *msg_log_data __read_mostly; + +static __always_inline int ____msg_log(void *caller, const char *fmt, + va_list args) +{ + struct sec_qc_msg_buf *qc_msg_buf; + int cpu = smp_processor_id(); + int r; + unsigned int i; + + i = ++(msg_log_data[cpu].idx) & (SEC_QC_MSG_LOG_MAX - 1); + qc_msg_buf = &msg_log_data[cpu].buf[i]; + + qc_msg_buf->time = __qc_logger_cpu_clock(cpu); + r = vsnprintf(qc_msg_buf->msg, sizeof(qc_msg_buf->msg), fmt, args); + + qc_msg_buf->caller0 = __builtin_return_address(0); + qc_msg_buf->caller1 = caller; + qc_msg_buf->task = current->comm; + + return r; +} + +static int __msg_log_set_msg_log_data(struct builder *bd) +{ + struct qc_logger *logger = container_of(bd, struct qc_logger, bd); + struct sec_dbg_region_client *client = logger->drvdata->client; + int cpu; + + msg_log_data = (void *)client->virt; + if (IS_ERR_OR_NULL(msg_log_data)) + return -EFAULT; + + for_each_possible_cpu(cpu) { + msg_log_data[cpu].idx = -1; + } + + return 0; +} + +static void __msg_log_unset_msg_log_data(struct builder *bd) +{ + msg_log_data = NULL; +} + +static int notrace __msg_log(const char *fmt, ...) +{ + va_list args; + int r; + + va_start(args, fmt); + r = ____msg_log(__builtin_return_address(1), fmt, args); + va_end(args); + + return r; +} + +static void sec_qc_msg_log_trace_hrtimer_expire_entry(void *unused, + struct hrtimer *timer, ktime_t *now) +{ + __msg_log("hrtimer %pS entry", timer->function); +} + +static int __msg_log_register_trace_hrtimer_expire_entry(struct builder *bd) +{ + return register_trace_hrtimer_expire_entry( + sec_qc_msg_log_trace_hrtimer_expire_entry, NULL); +} + +static void __msg_log_unregister_trace_hrtimer_expire_entry( + struct builder *bd) +{ + unregister_trace_hrtimer_expire_entry( + sec_qc_msg_log_trace_hrtimer_expire_entry, NULL); +} + +static void sec_qc_msg_log_trace_hrtimer_expire_exit(void *unused, + struct hrtimer *timer) +{ + __msg_log("hrtimer %pS exit", timer->function); +} + +static int __msg_log_register_trace_hrtimer_expire_exit(struct builder *bd) +{ + return register_trace_hrtimer_expire_exit( + sec_qc_msg_log_trace_hrtimer_expire_exit, NULL); +} + +static void __msg_log_unregister_trace_hrtimer_expire_exit( + struct builder *bd) +{ + unregister_trace_hrtimer_expire_exit( + sec_qc_msg_log_trace_hrtimer_expire_exit, NULL); +} + +#if 0 +static void sec_qc_msg_log_trace_timer_expire_entry(void *unused, + struct timer_list *timer, unsigned long baseclk) +{ + __msg_log("timer %pS entry", timer->function); +} + +static int __msg_log_register_trace_timer_expire_entry(struct builder *bd) +{ + return register_trace_timer_expire_entry( + sec_qc_msg_log_trace_timer_expire_entry, NULL); +} + +static void __msg_log_unregister_trace_timer_expire_entry(struct builder *bd) +{ + unregister_trace_timer_expire_entry( + sec_qc_msg_log_trace_timer_expire_entry, NULL); +} + +static void sec_qc_msg_log_trace_timer_expire_exit(void *unused, + struct timer_list *timer) +{ + __msg_log("timer %pS exit", timer->function); +} + +static int __msg_log_register_trace_timer_expire_exit(struct builder *bd) +{ + return register_trace_timer_expire_exit( + sec_qc_msg_log_trace_timer_expire_exit, NULL); +} + +static void __msg_log_unregister_trace_timer_expire_exit(struct builder *bd) +{ + unregister_trace_timer_expire_exit( + sec_qc_msg_log_trace_timer_expire_exit, NULL); +} +#endif + +static size_t sec_qc_msg_log_get_data_size(struct qc_logger *logger) +{ + return sizeof(struct sec_qc_msg_log_data) * num_possible_cpus(); +} + +static const struct dev_builder __msg_logdev_builder[] = { + DEVICE_BUILDER(__msg_log_set_msg_log_data, + __msg_log_unset_msg_log_data), +}; + +static const struct dev_builder __msg_logdev_builder_trace[] = { + DEVICE_BUILDER(__msg_log_register_trace_hrtimer_expire_entry, + __msg_log_unregister_trace_hrtimer_expire_entry), + DEVICE_BUILDER(__msg_log_register_trace_hrtimer_expire_exit, + __msg_log_unregister_trace_hrtimer_expire_exit), +#if 0 + DEVICE_BUILDER(__msg_log_register_trace_timer_expire_entry, + __msg_log_unregister_trace_timer_expire_entry), + DEVICE_BUILDER(__msg_log_register_trace_timer_expire_exit, + __msg_log_unregister_trace_timer_expire_exit), +#endif +}; + +static int sec_qc_msg_log_probe(struct qc_logger *logger) +{ + int err = __qc_logger_sub_module_probe(logger, + __msg_logdev_builder, + ARRAY_SIZE(__msg_logdev_builder)); + if (err) + return err; + + return __qc_logger_sub_module_probe(logger, + __msg_logdev_builder_trace, + ARRAY_SIZE(__msg_logdev_builder_trace)); +} + +static void sec_qc_msg_log_remove(struct qc_logger *logger) +{ + __qc_logger_sub_module_remove(logger, + __msg_logdev_builder, + ARRAY_SIZE(__msg_logdev_builder)); + + __qc_logger_sub_module_remove(logger, + __msg_logdev_builder_trace, + ARRAY_SIZE(__msg_logdev_builder_trace)); +} + +struct qc_logger __qc_logger_msg_log = { + .get_data_size = sec_qc_msg_log_get_data_size, + .probe = sec_qc_msg_log_probe, + .remove = sec_qc_msg_log_remove, + .unique_id = SEC_QC_MSG_LOG_MAGIC, +}; diff --git a/drivers/samsung/debug/qcom/logger/sec_qc_sched_log.c b/drivers/samsung/debug/qcom/logger/sec_qc_sched_log.c new file mode 100644 index 000000000000..4e7016549c6e --- /dev/null +++ b/drivers/samsung/debug/qcom/logger/sec_qc_sched_log.c @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2020-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "sec_qc_logger.h" + +static struct sec_qc_sched_log_data *sched_log_data __read_mostly; + +static __always_inline void strcpy_task_comm(char *dst, char *src) +{ + if (TASK_COMM_LEN == 16) { + struct __task_comm { + uint64_t l; + uint64_t h; + } __attribute__((aligned(8))) *__dst, *__src; + __dst = (void *)dst; + __src = (void *)src; + __dst->l = __src->l; + __dst->h = __src->h; + } else + memcpy(dst, src, TASK_COMM_LEN); +} + +static __always_inline long get_switch_state(bool preempt, + struct task_struct *p) +{ + return preempt ? TASK_RUNNING | TASK_STATE_MAX : p->__state; +} + +static void sec_qc_trace_sched_switch(void *unused, bool preempt, + struct task_struct *prev, struct task_struct *next, + unsigned prev_state) +{ + struct sec_qc_sched_buf *sched_buf; + int cpu = smp_processor_id(); + unsigned int i; + + i = ++(sched_log_data[cpu].idx) & (SEC_QC_SCHED_LOG_MAX - 1); + sched_buf = &sched_log_data[cpu].buf[i]; + + sched_buf->time = __qc_logger_cpu_clock(cpu); + + strcpy_task_comm(sched_buf->comm, next->comm); + sched_buf->pid = next->pid; + sched_buf->task = next; + strcpy_task_comm(sched_buf->prev_comm, prev->comm); + sched_buf->prio = next->prio; + sched_buf->prev_pid = prev->pid; + sched_buf->prev_state = get_switch_state(preempt, prev); + sched_buf->prev_prio = prev->prio; +} + +static int __sched_log_set_sched_log_data(struct builder *bd) +{ + struct qc_logger *logger = container_of(bd, struct qc_logger, bd); + struct sec_dbg_region_client *client = logger->drvdata->client; + int cpu; + + sched_log_data = (void *)client->virt; + if (IS_ERR_OR_NULL(sched_log_data)) + return -EFAULT; + + for_each_possible_cpu(cpu) { + sched_log_data[cpu].idx = -1; + } + + return 0; +} + +static void __sched_log_unset_sched_log_data(struct builder *bd) +{ + sched_log_data = NULL; +} + +static int notrace __sched_log_msg(char *fmt, ...) +{ + struct sec_qc_sched_buf *sched_buf; + int cpu = smp_processor_id(); + int r; + int i; + va_list args; + + i = ++(sched_log_data[cpu].idx) & (SEC_QC_SCHED_LOG_MAX - 1); + sched_buf = &sched_log_data[cpu].buf[i]; + + va_start(args, fmt); + r = vsnprintf(sched_buf->comm, sizeof(sched_buf->comm), fmt, args); + va_end(args); + + sched_buf->time = __qc_logger_cpu_clock(cpu); + sched_buf->task = NULL; + sched_buf->pid = current->pid; + + return r; +} + +static int __sched_log_register_trace_sched_switch(struct builder *bd) +{ + return register_trace_sched_switch(sec_qc_trace_sched_switch, NULL); +} + +static void __sched_log_unregister_trace_sched_switch(struct builder *bd) +{ + unregister_trace_sched_switch(sec_qc_trace_sched_switch, NULL); +} + +static void sec_qc_trace_workqueue_execute_start(void *unsed, + struct work_struct *work) +{ + struct sec_qc_sched_buf *sched_buf; + int cpu = smp_processor_id(); + int i; + + i = ++(sched_log_data[cpu].idx) & (SEC_QC_SCHED_LOG_MAX - 1); + sched_buf = &sched_log_data[cpu].buf[i]; + + sched_buf->addr = (uintptr_t)work->func; + sched_buf->time = __qc_logger_cpu_clock(cpu); + sched_buf->task = NULL; + sched_buf->pid = current->pid; +} + +static int __sched_log_register_trace_workqueue_execute_start( + struct builder *bd) +{ + return register_trace_workqueue_execute_start( + sec_qc_trace_workqueue_execute_start, NULL); +} + +static void __sched_log_unregister_trace_workqueue_execute_start( + struct builder *bd) +{ + unregister_trace_workqueue_execute_start( + sec_qc_trace_workqueue_execute_start, NULL); +} + +static size_t sec_qc_shed_log_get_data_size(struct qc_logger *logger) +{ + return sizeof(struct sec_qc_sched_log_data) * num_possible_cpus(); +} + +static int sec_qc_sched_log_die_notifier_call(struct notifier_block *this, + unsigned long l, void *d) +{ + __sched_log_msg("!!die!!"); + __sched_log_msg("!!die!!"); + + return NOTIFY_OK; +} + +static struct notifier_block sec_qc_sched_log_die_handle = { + .notifier_call = sec_qc_sched_log_die_notifier_call, + .priority = 0x7FFFFFFF, /* most high priority */ +}; + +static int __sched_log_register_die_handle(struct builder *bd) +{ + return register_die_notifier(&sec_qc_sched_log_die_handle); +} + +static void __sched_log_unregister_die_handle(struct builder *bd) +{ + unregister_die_notifier(&sec_qc_sched_log_die_handle); +} + +static int sec_qc_sched_log_panic_notifier_call(struct notifier_block *this, + unsigned long l, void *d) +{ + __sched_log_msg("!!panic!!"); + __sched_log_msg("!!panic!!"); + + return NOTIFY_OK; +} + +static struct notifier_block sec_qc_sched_log_panic_handle = { + .notifier_call = sec_qc_sched_log_panic_notifier_call, + .priority = 0x7FFFFFFF, /* most high priority */ +}; + +static int __sched_log_register_panic_handle(struct builder *bd) +{ + return atomic_notifier_chain_register(&panic_notifier_list, + &sec_qc_sched_log_panic_handle); +} + +static void __sched_log_unregister_panic_handle(struct builder *bd) +{ + atomic_notifier_chain_unregister(&panic_notifier_list, + &sec_qc_sched_log_panic_handle); +} + +static const struct dev_builder __sched_log_dev_builder[] = { + DEVICE_BUILDER(__sched_log_set_sched_log_data, + __sched_log_unset_sched_log_data), +}; + +static const struct dev_builder __sched_log_dev_builder_trace[] = { + DEVICE_BUILDER(__sched_log_register_trace_sched_switch, + __sched_log_unregister_trace_sched_switch), + DEVICE_BUILDER(__sched_log_register_trace_workqueue_execute_start, + __sched_log_unregister_trace_workqueue_execute_start), + DEVICE_BUILDER(__sched_log_register_die_handle, + __sched_log_unregister_die_handle), + DEVICE_BUILDER(__sched_log_register_panic_handle, + __sched_log_unregister_panic_handle), +}; + +static int sec_qc_sched_log_probe(struct qc_logger *logger) +{ + int err = __qc_logger_sub_module_probe(logger, + __sched_log_dev_builder, + ARRAY_SIZE(__sched_log_dev_builder)); + if (err) + return err; + + return __qc_logger_sub_module_probe(logger, + __sched_log_dev_builder_trace, + ARRAY_SIZE(__sched_log_dev_builder_trace)); +} + +static void sec_qc_sched_log_remove(struct qc_logger *logger) +{ + __qc_logger_sub_module_remove(logger, + __sched_log_dev_builder_trace, + ARRAY_SIZE(__sched_log_dev_builder_trace)); + + __qc_logger_sub_module_remove(logger, + __sched_log_dev_builder, + ARRAY_SIZE(__sched_log_dev_builder)); +} + +struct qc_logger __qc_logger_sched_log = { + .get_data_size = sec_qc_shed_log_get_data_size, + .probe = sec_qc_sched_log_probe, + .remove = sec_qc_sched_log_remove, + .unique_id = SEC_QC_SCHED_LOG_MAGIC, +}; diff --git a/drivers/samsung/debug/qcom/mock/Kconfig b/drivers/samsung/debug/qcom/mock/Kconfig new file mode 100644 index 000000000000..66a8335b6dbe --- /dev/null +++ b/drivers/samsung/debug/qcom/mock/Kconfig @@ -0,0 +1,5 @@ +config SEC_QC_MOCK + tristate "SEC Qualcomm Mock driver under QEMU based testing" + default n + help + TODO: help is not ready. diff --git a/drivers/samsung/debug/qcom/mock/Makefile b/drivers/samsung/debug/qcom/mock/Makefile new file mode 100644 index 000000000000..e29d002341b8 --- /dev/null +++ b/drivers/samsung/debug/qcom/mock/Makefile @@ -0,0 +1,13 @@ +obj-$(CONFIG_SEC_QC_MOCK) += sec_qc_mock.o + +sec_qc_mock-objs := sec_qc_mock_main.o \ + sec_qc_mock_qcom-cpufreq-hw.o \ + sec_qc_mock_kryo_arm64_edac.o \ + sec_qc_mock_qcom-dload-mode.o \ + sec_qc_mock_memory_dump_v2.o \ + sec_qc_mock_walt.o \ + sec_qc_mock_msm_rtb.o \ + sec_qc_mock_smem.o \ + sec_qc_mock_qcom_wdt_core.o \ + sec_qc_mock_epss-l3.o \ + sec_qc_mock_qcom_lpm.o diff --git a/drivers/samsung/debug/qcom/mock/sec_qc_mock.h b/drivers/samsung/debug/qcom/mock/sec_qc_mock.h new file mode 100644 index 000000000000..c54afd845efd --- /dev/null +++ b/drivers/samsung/debug/qcom/mock/sec_qc_mock.h @@ -0,0 +1,8 @@ +#ifndef __INTERNAL__SEC_QC_MOCK_H__ +#define __INTERNAL__SEC_QC_MOCK_H__ + +/* sec_qc_mock_smem.c */ +extern void __init __qc_mock_smem_init(void); +extern void __exit __qc_mock_smem_exit(void); + +#endif /* __INTERNAL__SEC_QC_MOCK_H__ */ diff --git a/drivers/samsung/debug/qcom/mock/sec_qc_mock_epss-l3.c b/drivers/samsung/debug/qcom/mock/sec_qc_mock_epss-l3.c new file mode 100644 index 000000000000..46a9fae6cb45 --- /dev/null +++ b/drivers/samsung/debug/qcom/mock/sec_qc_mock_epss-l3.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include + +#include +#include + +int __weak qcom_icc_epss_l3_cpu_set_register_notifier(struct notifier_block *nb) +{ + return 0; +} +EXPORT_SYMBOL(qcom_icc_epss_l3_cpu_set_register_notifier); + +int __weak qcom_icc_epss_l3_cpu_set_unregister_notifier(struct notifier_block *nb) +{ + return 0; +} +EXPORT_SYMBOL(qcom_icc_epss_l3_cpu_set_unregister_notifier); diff --git a/drivers/samsung/debug/qcom/mock/sec_qc_mock_kryo_arm64_edac.c b/drivers/samsung/debug/qcom/mock/sec_qc_mock_kryo_arm64_edac.c new file mode 100644 index 000000000000..461276f593d9 --- /dev/null +++ b/drivers/samsung/debug/qcom/mock/sec_qc_mock_kryo_arm64_edac.c @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include + +#include +#include + +ap_health_t * __weak qcom_kryo_arm64_edac_error_register_notifier(struct notifier_block *nb) +{ + return ERR_PTR(-ENODEV); +} + +int __weak qcom_kryo_arm64_edac_error_unregister_notifier(struct notifier_block *nb) +{ + return -ENODEV; +} diff --git a/drivers/samsung/debug/qcom/mock/sec_qc_mock_main.c b/drivers/samsung/debug/qcom/mock/sec_qc_mock_main.c new file mode 100644 index 000000000000..e11c57fc5a20 --- /dev/null +++ b/drivers/samsung/debug/qcom/mock/sec_qc_mock_main.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include + +#include "sec_qc_mock.h" + +int __init sec_qc_mock_init(void) +{ + __qc_mock_smem_init(); + + return 0; +} +#if IS_BUILTIN(CONFIG_SEC_QC_MOCK) +pure_initcall(sec_qc_mock_init); +#else +module_init(sec_qc_mock_init); +#endif + +void __exit sec_qc_mock_exit(void) +{ + __qc_mock_smem_exit(); +} +module_exit(sec_qc_mock_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("SEC Qualcomm Mock driver under QEMU based testing"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/debug/qcom/mock/sec_qc_mock_memory_dump_v2.c b/drivers/samsung/debug/qcom/mock/sec_qc_mock_memory_dump_v2.c new file mode 100644 index 000000000000..1072da998585 --- /dev/null +++ b/drivers/samsung/debug/qcom/mock/sec_qc_mock_memory_dump_v2.c @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include + +#include + +void __weak sec_qc_summary_set_msm_memdump_info(struct sec_qc_summary_data_apss *apss) +{ +} +EXPORT_SYMBOL_GPL(sec_qc_summary_set_msm_memdump_info); diff --git a/drivers/samsung/debug/qcom/mock/sec_qc_mock_msm_rtb.c b/drivers/samsung/debug/qcom/mock/sec_qc_mock_msm_rtb.c new file mode 100644 index 000000000000..9e5c9a21d067 --- /dev/null +++ b/drivers/samsung/debug/qcom/mock/sec_qc_mock_msm_rtb.c @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include + +#include + +void __weak sec_qc_summary_set_rtb_info(struct sec_qc_summary_data_apss *apss) +{ +} +EXPORT_SYMBOL_GPL(sec_qc_summary_set_rtb_info); diff --git a/drivers/samsung/debug/qcom/mock/sec_qc_mock_qcom-cpufreq-hw.c b/drivers/samsung/debug/qcom/mock/sec_qc_mock_qcom-cpufreq-hw.c new file mode 100644 index 000000000000..cee52581ed36 --- /dev/null +++ b/drivers/samsung/debug/qcom/mock/sec_qc_mock_qcom-cpufreq-hw.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include + +int __weak qcom_cpufreq_hw_target_index_register_notifier(struct notifier_block *nb) +{ + return 0; +} +EXPORT_SYMBOL_GPL(qcom_cpufreq_hw_target_index_register_notifier); + +int __weak qcom_cpufreq_hw_target_index_unregister_notifier(struct notifier_block *nb) +{ + return 0; +} +EXPORT_SYMBOL_GPL(qcom_cpufreq_hw_target_index_unregister_notifier); diff --git a/drivers/samsung/debug/qcom/mock/sec_qc_mock_qcom-dload-mode.c b/drivers/samsung/debug/qcom/mock/sec_qc_mock_qcom-dload-mode.c new file mode 100644 index 000000000000..8770e5a3a697 --- /dev/null +++ b/drivers/samsung/debug/qcom/mock/sec_qc_mock_qcom-dload-mode.c @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include + +void __weak qcom_set_dload_mode(int on) +{ + pr_info("on = %d\n", on); +} +EXPORT_SYMBOL_GPL(qcom_set_dload_mode); diff --git a/drivers/samsung/debug/qcom/mock/sec_qc_mock_qcom_lpm.c b/drivers/samsung/debug/qcom/mock/sec_qc_mock_qcom_lpm.c new file mode 100644 index 000000000000..e9f67186295b --- /dev/null +++ b/drivers/samsung/debug/qcom/mock/sec_qc_mock_qcom_lpm.c @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include + +static bool mock_sleep_disabled = true; + +void __weak qcom_lpm_set_sleep_disabled(void) +{ + mock_sleep_disabled = true; +} +EXPORT_SYMBOL_GPL(qcom_lpm_set_sleep_disabled); + +void __weak qcom_lpm_unset_sleep_disabled(void) +{ + mock_sleep_disabled = false; +} +EXPORT_SYMBOL_GPL(qcom_lpm_unset_sleep_disabled); \ No newline at end of file diff --git a/drivers/samsung/debug/qcom/mock/sec_qc_mock_qcom_wdt_core.c b/drivers/samsung/debug/qcom/mock/sec_qc_mock_qcom_wdt_core.c new file mode 100644 index 000000000000..75e950707c89 --- /dev/null +++ b/drivers/samsung/debug/qcom/mock/sec_qc_mock_qcom_wdt_core.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include + +int __weak qcom_wdt_pet_register_notifier(struct notifier_block *nb) +{ + return 0; +} +EXPORT_SYMBOL_GPL(qcom_wdt_pet_register_notifier); + +int __weak qcom_wdt_pet_unregister_notifier(struct notifier_block *nb) +{ + return 0; +} +EXPORT_SYMBOL_GPL(qcom_wdt_pet_unregister_notifier); + +int __weak qcom_wdt_bark_register_notifier(struct notifier_block *nb) +{ + return 0; +} +EXPORT_SYMBOL_GPL(qcom_wdt_bark_register_notifier); + +int __weak qcom_wdt_bark_unregister_notifier(struct notifier_block *nb) +{ + return 0; +} +EXPORT_SYMBOL_GPL(qcom_wdt_bark_unregister_notifier); diff --git a/drivers/samsung/debug/qcom/mock/sec_qc_mock_smem.c b/drivers/samsung/debug/qcom/mock/sec_qc_mock_smem.c new file mode 100644 index 000000000000..2ec85fb07cac --- /dev/null +++ b/drivers/samsung/debug/qcom/mock/sec_qc_mock_smem.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include + +struct mock_smem_entry { + struct hlist_node hlist; + struct list_head list; + unsigned item; + void *virt; + size_t size; +}; + +static DEFINE_HASHTABLE(mock_smem_htbl, 3); +static LIST_HEAD(mock_smem_list); + +static struct mock_smem_entry *__mock_smem_find(unsigned item) +{ + struct mock_smem_entry *h; + + hash_for_each_possible(mock_smem_htbl, h, hlist, item) { + if (h->item == item) + return h; + } + + return ERR_PTR(-ENOENT); +} + +int __weak qcom_smem_alloc(unsigned host, unsigned item, size_t size) +{ + struct mock_smem_entry *h; + size_t alloc_size; + + h = __mock_smem_find(item); + if (!IS_ERR_OR_NULL(h)) + return 0; + + h = kzalloc(sizeof(*h), GFP_KERNEL); + if (!h) + return -ENOMEM; + + alloc_size = PAGE_ALIGN(size); + h->virt = kzalloc(alloc_size, GFP_KERNEL); + if (!h->virt) { + kfree(h); + return -ENOMEM; + } + + INIT_HLIST_NODE(&h->hlist); + h->size = alloc_size; + h->item = item; + hash_add(mock_smem_htbl, &h->hlist, item); + list_add(&h->list, &mock_smem_list); + + return 0; +} +EXPORT_SYMBOL_GPL(qcom_smem_alloc); + +void * __weak qcom_smem_get(unsigned host, unsigned item, size_t *size) +{ + struct mock_smem_entry *h; + + h = __mock_smem_find(item); + if (IS_ERR_OR_NULL(h)) + return ERR_PTR(-ENOENT); + + *size = h->size; + + return h->virt; +} +EXPORT_SYMBOL_GPL(qcom_smem_get); + +phys_addr_t __weak qcom_smem_virt_to_phys(void *p) +{ + return virt_to_phys(p); +} +EXPORT_SYMBOL_GPL(qcom_smem_virt_to_phys); + +void __init __qc_mock_smem_init(void) +{ + hash_init(mock_smem_htbl); + INIT_LIST_HEAD(&mock_smem_list); +} + +void __exit __qc_mock_smem_exit(void) +{ + struct mock_smem_entry *pos; + struct mock_smem_entry *tmp; + + list_for_each_entry_safe(pos, tmp, &mock_smem_list, list) { + list_del(&pos->list); + kfree(pos); + } +} diff --git a/drivers/samsung/debug/qcom/mock/sec_qc_mock_walt.c b/drivers/samsung/debug/qcom/mock/sec_qc_mock_walt.c new file mode 100644 index 000000000000..5e4909a05950 --- /dev/null +++ b/drivers/samsung/debug/qcom/mock/sec_qc_mock_walt.c @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include + +#include + +void __weak sec_qc_summary_set_sched_walt_info(struct sec_qc_summary_data_apss *apss) +{ +} +EXPORT_SYMBOL_GPL(sec_qc_summary_set_sched_walt_info); diff --git a/drivers/samsung/debug/qcom/reboot_cmd/Kconfig b/drivers/samsung/debug/qcom/reboot_cmd/Kconfig new file mode 100644 index 000000000000..8be2b6e8ccd3 --- /dev/null +++ b/drivers/samsung/debug/qcom/reboot_cmd/Kconfig @@ -0,0 +1,22 @@ +config SEC_QC_RBCMD + tristate "SEC Reboot Commands for Qualcomm Based Devices" + depends on SEC_REBOOT_CMD + help + TODO: help is not ready. + +config SEC_QC_RBCMD_TEST_FOR_ON_DEVICE + tristate "KUnit test for sec_qc_rbcmd_main_test" + depends on KUNIT + depends on SEC_QC_RBCMD + help + TODO: Describe config fully. + If you run this test driver on device, SHOULD set this config as 'm' to build test driver modulraly. + +config SEC_QC_RBCMD_TEST_FOR_ONLY_UML + tristate "KUnit test for sec_qc_rbcmd_main_test" + depends on KUNIT + depends on UML + depends on SEC_QC_RBCMD + help + TODO: Describe config fully. + This CONFIG is recommended to set to y. diff --git a/drivers/samsung/debug/qcom/reboot_cmd/Makefile b/drivers/samsung/debug/qcom/reboot_cmd/Makefile new file mode 100644 index 000000000000..54359e362110 --- /dev/null +++ b/drivers/samsung/debug/qcom/reboot_cmd/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_SEC_QC_RBCMD) += sec_qc_rbcmd.o +sec_qc_rbcmd-objs := sec_qc_rbcmd_main.o \ + sec_qc_rbcmd_command.o + +GCOV_PROFILE_sec_qc_rbcmd_main.o := $(CONFIG_KUNIT) diff --git a/drivers/samsung/debug/qcom/reboot_cmd/sec_qc_rbcmd.h b/drivers/samsung/debug/qcom/reboot_cmd/sec_qc_rbcmd.h new file mode 100644 index 000000000000..378d99199c47 --- /dev/null +++ b/drivers/samsung/debug/qcom/reboot_cmd/sec_qc_rbcmd.h @@ -0,0 +1,61 @@ +#ifndef __INTERNAL__SEC_QC_RBCMD_H__ +#define __INTERNAL__SEC_QC_RBCMD_H__ + +#include + +#include +#include +#include + +struct qc_rbcmd_stage { + struct qc_rbcmd_cmd *reboot_cmd; + struct sec_reboot_cmd *default_cmd; +}; + +struct qc_rbcmd_drvdata { + struct builder bd; + bool use_on_reboot; + bool use_on_restart; + struct qc_rbcmd_stage stage[SEC_RBCMD_STAGE_MAX]; + struct raw_notifier_head pon_rr_writers; + struct raw_notifier_head sec_rr_writers; +}; + +struct qc_rbcmd_reset_reason { + unsigned int pon_rr; /* pon restart reason */ + unsigned int sec_rr; /* sec restart reason */ +}; + +enum { + QC_RBCMD_DFLT_ON_NORMAL = 0, + QC_RBCMD_DFLT_ON_PANIC, +}; + +#define DUMP_SINK_TO_SDCARD 0x73646364 +#define DUMP_SINK_TO_BOOTDEV 0x42544456 +#define DUMP_SINK_TO_USB 0x0 + +#define CDSP_SIGNOFF_BLOCK 0x2377 +#define CDSP_SIGNOFF_ON 0x7277 + +/* sec_qc_rbcmd_main. */ +extern struct qc_rbcmd_drvdata *qc_rbcmd; + +static __always_inline bool __rbcmd_is_probed(void) +{ + return !!qc_rbcmd; +} + +extern void __qc_rbcmd_write_pon_rr(unsigned int pon_rr, struct sec_reboot_param *param); +extern void __qc_rbcmd_write_sec_rr(unsigned int sec_rr, struct sec_reboot_param *param); +extern void __qc_rbcmd_set_restart_reason(unsigned int pon_rr, unsigned int sec_rr, struct sec_reboot_param *param); + +/* sec_qc_rbcmd_command.c */ +extern int __qc_rbcmd_init_on_reboot(struct builder *bd); +extern void __qc_rbcmd_exit_on_reboot(struct builder *bd); +extern int __qc_rbcmd_init_on_restart(struct builder *bd); +extern void __qc_rbcmd_exit_on_restart(struct builder *bd); +extern int __qc_rbcmd_register_panic_handle(struct builder *bd); +extern void __qc_rbcmd_unregister_panic_handle(struct builder *bd); + +#endif /* __INTERNAL__SEC_QC_RBCMD_H__ */ diff --git a/drivers/samsung/debug/qcom/reboot_cmd/sec_qc_rbcmd_command.c b/drivers/samsung/debug/qcom/reboot_cmd/sec_qc_rbcmd_command.c new file mode 100644 index 000000000000..459dae79515f --- /dev/null +++ b/drivers/samsung/debug/qcom/reboot_cmd/sec_qc_rbcmd_command.c @@ -0,0 +1,714 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2020-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "sec_qc_rbcmd.h" + +struct qc_rbcmd_cmd { + struct sec_reboot_cmd rc; + unsigned int pon_rr; /* pon restart reason */ + unsigned int sec_rr; /* sec restart reason */ +}; + +struct qc_rbcmd_suite { + struct qc_rbcmd_cmd *qrc; + size_t nr_cmd; +}; + +#define SEC_QC_RBCMD(__cmd, __pon_rr, __sec_rr, __func) \ +{ \ + .rc.cmd = __cmd, \ + .rc.func = __func, \ + .pon_rr = __pon_rr, \ + .sec_rr = __sec_rr, \ +} + +#define SEC_QC_RBCMD_HANDLER(__cmd) \ +static int sec_qc_rbcmd_##__cmd(const struct sec_reboot_cmd *rc, \ + struct sec_reboot_param *param, bool one_of_multi) \ +{ \ + struct qc_rbcmd_reset_reason rr; \ + int ret; \ +\ + if (!__rbcmd_is_probed()) \ + return SEC_RBCMD_HANDLE_DONE; \ +\ + ret = __rbcmd_##__cmd(&rr, rc, param, one_of_multi); \ + if (ret != SEC_RBCMD_HANDLE_BAD) \ + __qc_rbcmd_set_restart_reason(rr.pon_rr, rr.sec_rr, param); \ +\ + return ret; \ +} + +static unsigned int default_on = QC_RBCMD_DFLT_ON_NORMAL; + +static int sec_qc_rbcmd_panic_notifier_call(struct notifier_block *nb, + unsigned long l, void *d) +{ + default_on = QC_RBCMD_DFLT_ON_PANIC; + + return NOTIFY_OK; +} + +static struct notifier_block sec_qc_rbcmd_panic_handle = { + .notifier_call = sec_qc_rbcmd_panic_notifier_call, + .priority = 0x7FFFFFFF, /* most high priority */ +}; + +__ss_static int __rbcmd_default_reason(struct qc_rbcmd_reset_reason *rr, + struct sec_reboot_param *param, bool one_of_multi, + unsigned int on_panic) +{ + const char *cmd = param->cmd; + + switch (on_panic) { + case QC_RBCMD_DFLT_ON_PANIC: + pr_emerg("sec_debug_hw_reset on panic"); + rr->sec_rr = PON_RESTART_REASON_NOT_HANDLE; + rr->pon_rr = RESTART_REASON_SEC_DEBUG_MODE; + break; + case QC_RBCMD_DFLT_ON_NORMAL: + default: + if (!cmd || !strlen(cmd) || !strncmp(cmd, "adb", 3)) { + rr->sec_rr = RESTART_REASON_NORMAL; + rr->pon_rr = PON_RESTART_REASON_NORMALBOOT; + } else { + rr->sec_rr = RESTART_REASON_REBOOT; + rr->pon_rr = PON_RESTART_REASON_NORMALBOOT; + } + } + + return SEC_RBCMD_HANDLE_OK; +} + +static int sec_qc_rbcmd_default_reason(const struct sec_reboot_cmd *rc, + struct sec_reboot_param *param, bool one_of_multi) +{ + struct qc_rbcmd_reset_reason rr; + int ret; + + if (!__rbcmd_is_probed()) + return SEC_RBCMD_HANDLE_DONE; + + ret = __rbcmd_default_reason(&rr, param, one_of_multi, default_on); + if (ret != SEC_RBCMD_HANDLE_BAD) + __qc_rbcmd_set_restart_reason(rr.pon_rr, rr.sec_rr, param); + + return ret; +} + +__ss_static int __rbcmd_debug(struct qc_rbcmd_reset_reason *rr, + const struct sec_reboot_cmd *rc, + struct sec_reboot_param *param, bool one_of_multi) +{ + const char *cmd = param->cmd; + size_t len = strlen(rc->cmd); + unsigned int debug_level; + int err; + + err = kstrtouint(&cmd[len], 0, &debug_level); + if (err) + return SEC_RBCMD_HANDLE_BAD; + + rr->sec_rr = RESTART_REASON_NORMAL; + + switch (debug_level) { + case SEC_DEBUG_LEVEL_LOW: + rr->pon_rr = PON_RESTART_REASON_DBG_LOW; + break; + case SEC_DEBUG_LEVEL_MID: + rr->pon_rr = PON_RESTART_REASON_DBG_MID; + break; + case SEC_DEBUG_LEVEL_HIGH: + rr->pon_rr = PON_RESTART_REASON_DBG_HIGH; + break; + default: + rr->pon_rr = PON_RESTART_REASON_UNKNOWN; + break; + } + + return SEC_RBCMD_HANDLE_OK | + (one_of_multi ? SEC_RBCMD_HANDLE_ONESHOT_MASK : 0); +} + +SEC_QC_RBCMD_HANDLER(debug); + +__ss_static int __rbcmd_sud(struct qc_rbcmd_reset_reason *rr, + const struct sec_reboot_cmd *rc, + struct sec_reboot_param *param, bool one_of_multi) +{ + const char *cmd = param->cmd; + size_t len = strlen(rc->cmd); + unsigned int rory; + unsigned int pon_rr; + int err; + + err = kstrtouint(&cmd[len], 0, &rory); + if (err) + return SEC_RBCMD_HANDLE_BAD; + + pon_rr = PON_RESTART_REASON_RORY_START | rory; + if (pon_rr > PON_RESTART_REASON_RORY_END) + return SEC_RBCMD_HANDLE_BAD; + + rr->sec_rr = RESTART_REASON_NORMAL; + rr->pon_rr = pon_rr; + + return SEC_RBCMD_HANDLE_OK | + (one_of_multi ? SEC_RBCMD_HANDLE_ONESHOT_MASK : 0); +} + +SEC_QC_RBCMD_HANDLER(sud); + +__ss_static int __rbcmd_cpdebug(struct qc_rbcmd_reset_reason *rr, + const struct sec_reboot_cmd *rc, + struct sec_reboot_param *param, bool one_of_multi) +{ + const char *cmd = param->cmd; + size_t len = strlen(rc->cmd); + unsigned int cp_debug_level; + int err; + + err = kstrtouint(&cmd[len], 0, &cp_debug_level); + if (err) + return SEC_RBCMD_HANDLE_BAD; + + rr->sec_rr = RESTART_REASON_NORMAL; + + switch (cp_debug_level) { + case SEC_DEBUG_CP_DEBUG_ON: + rr->pon_rr = PON_RESTART_REASON_CP_DBG_ON; + break; + case SEC_DEBUG_CP_DEBUG_OFF: + rr->pon_rr = PON_RESTART_REASON_CP_DBG_OFF; + break; + default: + rr->pon_rr = PON_RESTART_REASON_UNKNOWN; + break; + } + + return SEC_RBCMD_HANDLE_OK | + (one_of_multi ? SEC_RBCMD_HANDLE_ONESHOT_MASK : 0); +} + +SEC_QC_RBCMD_HANDLER(cpdebug); + +__ss_static int __rbcmd_forceupload(struct qc_rbcmd_reset_reason *rr, + const struct sec_reboot_cmd *rc, + struct sec_reboot_param *param, bool one_of_multi) +{ + const char *cmd = param->cmd; + size_t len = strlen(rc->cmd); + unsigned int use_force_upload; + int err; + + err = kstrtouint(&cmd[len], 0, &use_force_upload); + if (err) + return SEC_RBCMD_HANDLE_BAD; + + rr->sec_rr = RESTART_REASON_NORMAL; + rr->pon_rr = !!use_force_upload ? + PON_RESTART_REASON_FORCE_UPLOAD_ON : + PON_RESTART_REASON_FORCE_UPLOAD_OFF; + + return SEC_RBCMD_HANDLE_OK | + (one_of_multi ? SEC_RBCMD_HANDLE_ONESHOT_MASK : 0); +} + +SEC_QC_RBCMD_HANDLER(forceupload); + +/* FIXME: maybe deprecated. */ +static int __rbcmd_swsel(struct qc_rbcmd_reset_reason *rr, + const struct sec_reboot_cmd *rc, + struct sec_reboot_param *param, bool one_of_multi) +{ + const char *cmd = param->cmd; + size_t len = strlen(rc->cmd); + unsigned int option; + unsigned int value; + int err; + + err = kstrtouint(&cmd[len], 0, &option); + if (err) + return SEC_RBCMD_HANDLE_BAD; + + value = (((option & 0x8) >> 1) | option) & 0x7; + + rr->sec_rr = RESTART_REASON_NORMAL; + rr->pon_rr = PON_RESTART_REASON_SWITCHSEL | value; + + return SEC_RBCMD_HANDLE_OK | + (one_of_multi ? SEC_RBCMD_HANDLE_ONESHOT_MASK : 0); +} + +SEC_QC_RBCMD_HANDLER(swsel); + +__ss_static int __rbcmd_multicmd(struct qc_rbcmd_reset_reason *rr, + const struct sec_reboot_cmd *rc, + struct sec_reboot_param *param, bool one_of_multi) +{ + rr->sec_rr = RESTART_REASON_NORMAL; + rr->pon_rr = PON_RESTART_REASON_MULTICMD; + + return SEC_RBCMD_HANDLE_OK | SEC_RBCMD_HANDLE_ONESHOT_MASK; +} + +SEC_QC_RBCMD_HANDLER(multicmd); + +__ss_static int __rbcmd_dump_sink(struct qc_rbcmd_reset_reason *rr, + const struct sec_reboot_cmd *rc, + struct sec_reboot_param *param, bool one_of_multi) +{ + const char *cmd = param->cmd; + size_t len = strlen(rc->cmd); + unsigned int dump_sink; + int err; + + err = kstrtouint(&cmd[len], 0, &dump_sink); + if (err) + return SEC_RBCMD_HANDLE_BAD; + + rr->sec_rr = RESTART_REASON_NORMAL; + + switch (dump_sink) { + case DUMP_SINK_TO_BOOTDEV: + rr->pon_rr = PON_RESTART_REASON_DUMP_SINK_BOOTDEV; + break; + case DUMP_SINK_TO_SDCARD: + rr->pon_rr = PON_RESTART_REASON_DUMP_SINK_SDCARD; + break; + default: + rr->pon_rr = PON_RESTART_REASON_DUMP_SINK_USB; + break; + } + + return SEC_RBCMD_HANDLE_OK | + (one_of_multi ? SEC_RBCMD_HANDLE_ONESHOT_MASK : 0); +} + +SEC_QC_RBCMD_HANDLER(dump_sink); + +__ss_static int __rbcmd_cdsp_signoff(struct qc_rbcmd_reset_reason *rr, + const struct sec_reboot_cmd *rc, + struct sec_reboot_param *param, bool one_of_multi) +{ + const char *cmd = param->cmd; + size_t len = strlen(rc->cmd); + unsigned int cdsp_signoff; + int err; + + err = kstrtouint(&cmd[len], 0, &cdsp_signoff); + if (err) + return SEC_RBCMD_HANDLE_BAD; + + rr->sec_rr = RESTART_REASON_NORMAL; + + switch (cdsp_signoff) { + case CDSP_SIGNOFF_ON: + rr->pon_rr = PON_RESTART_REASON_CDSP_ON; + break; + case CDSP_SIGNOFF_BLOCK: + rr->pon_rr = PON_RESTART_REASON_CDSP_BLOCK; + break; + default: + rr->pon_rr = PON_RESTART_REASON_UNKNOWN; + break; + } + + return SEC_RBCMD_HANDLE_OK | + (one_of_multi ? SEC_RBCMD_HANDLE_ONESHOT_MASK : 0); +} + +SEC_QC_RBCMD_HANDLER(cdsp_signoff); + +static int sec_qc_rbcmd_simple(const struct sec_reboot_cmd *rc, + struct sec_reboot_param *param, bool one_of_multi) +{ + const struct qc_rbcmd_cmd *qrc = + container_of(rc, struct qc_rbcmd_cmd, rc); + unsigned int sec_rr, pon_rr; + + sec_rr = qrc->sec_rr; + pon_rr = qrc->pon_rr; + + __qc_rbcmd_set_restart_reason(pon_rr, sec_rr, param); + + return SEC_RBCMD_HANDLE_OK | + (one_of_multi ? SEC_RBCMD_HANDLE_ONESHOT_MASK : 0); +} + +static int sec_qc_rbcmd_simple_strict(const struct sec_reboot_cmd *rc, + struct sec_reboot_param *param, bool one_of_multi) +{ + const char *cmd = param->cmd; + size_t len = strlen(rc->cmd); + + if (len != strlen(cmd)) + return SEC_RBCMD_HANDLE_DONE; + + return sec_qc_rbcmd_simple(rc, param, one_of_multi); +} + +static int sec_qc_rbcmd_qc_pre_defined(const struct sec_reboot_cmd *rc, + struct sec_reboot_param *param, bool one_of_multi) +{ + const struct qc_rbcmd_cmd *qrc = + container_of(rc, struct qc_rbcmd_cmd, rc); + unsigned int sec_rr; + const char *cmd = param->cmd; + size_t len = strlen(rc->cmd); + + if (len != strlen(cmd)) + return SEC_RBCMD_HANDLE_DONE; + + sec_rr = qrc->sec_rr; + + pr_info("%s is pre-defined command by Qualcomm. 'pon_rr' is not updated at here.\n", + cmd); + __qc_rbcmd_write_sec_rr(sec_rr, param); + + return SEC_RBCMD_HANDLE_OK | + (one_of_multi ? SEC_RBCMD_HANDLE_ONESHOT_MASK : 0); +} + +#define SEC_QC_RBCMD_SIMPLE(__cmd, __pon_rr, __sec_rr) \ + SEC_QC_RBCMD(__cmd, __pon_rr, __sec_rr, sec_qc_rbcmd_simple) + +#define SEC_QC_RBCMD_SIMPLE_STRICT(__cmd, __pon_rr, __sec_rr) \ + SEC_QC_RBCMD(__cmd, __pon_rr, __sec_rr, sec_qc_rbcmd_simple_strict) + +#define SEC_QC_RBMCD_QC_PRE_DEFINED(__cmd, __sec_rr) \ + SEC_QC_RBCMD(__cmd, 0, __sec_rr, sec_qc_rbcmd_qc_pre_defined) + +static const struct qc_rbcmd_cmd qc_rbcmd_template[] = { + /* reboot command using a custom handler */ + SEC_QC_RBCMD("debug", 0, 0, sec_qc_rbcmd_debug), + SEC_QC_RBCMD("sud", 0, 0, sec_qc_rbcmd_sud), + SEC_QC_RBCMD("cpdebug", 0, 0, sec_qc_rbcmd_cpdebug), + SEC_QC_RBCMD("forceupload", 0, 0, sec_qc_rbcmd_forceupload), + SEC_QC_RBCMD("swsel", 0, 0, sec_qc_rbcmd_swsel), + SEC_QC_RBCMD("multicmd", 0, 0, sec_qc_rbcmd_multicmd), + SEC_QC_RBCMD("dump_sink", 0, 0, sec_qc_rbcmd_dump_sink), + SEC_QC_RBCMD("signoff", 0, 0, sec_qc_rbcmd_cdsp_signoff), + + /* reboot command using a simple handler - cmd-xxx format */ + SEC_QC_RBCMD_SIMPLE("oem-", + PON_RESTART_REASON_UNKNOWN, + RESTART_REASON_NORMAL), + SEC_QC_RBCMD_SIMPLE("dram", + PON_RESTART_REASON_LIMITED_DRAM_SETTING, + RESTART_REASON_NORMAL), + + /* reboot command using a strict handler */ + SEC_QC_RBCMD_SIMPLE_STRICT("sec_debug_hw_reset", + PON_RESTART_REASON_NOT_HANDLE, + RESTART_REASON_SEC_DEBUG_MODE), + SEC_QC_RBCMD_SIMPLE_STRICT("download", + PON_RESTART_REASON_DOWNLOAD, + RESTART_REASON_NOT_HANDLE), + SEC_QC_RBCMD_SIMPLE_STRICT("nvbackup", + PON_RESTART_REASON_NVBACKUP, + RESTART_REASON_NOT_HANDLE), + SEC_QC_RBCMD_SIMPLE_STRICT("nvrestore", + PON_RESTART_REASON_NVRESTORE, + RESTART_REASON_NOT_HANDLE), + SEC_QC_RBCMD_SIMPLE_STRICT("nverase", + PON_RESTART_REASON_NVERASE, + RESTART_REASON_NOT_HANDLE), + SEC_QC_RBCMD_SIMPLE_STRICT("nvrecovery", + PON_RESTART_REASON_NVRECOVERY, + RESTART_REASON_NOT_HANDLE), + SEC_QC_RBCMD_SIMPLE_STRICT("cpmem_on", + PON_RESTART_REASON_CP_MEM_RESERVE_ON, + RESTART_REASON_NOT_HANDLE), + SEC_QC_RBCMD_SIMPLE_STRICT("cpmem_off", + PON_RESTART_REASON_CP_MEM_RESERVE_OFF, + RESTART_REASON_NOT_HANDLE), + SEC_QC_RBCMD_SIMPLE_STRICT("mbsmem_on", + PON_RESTART_REASON_MBS_MEM_RESERVE_ON, + RESTART_REASON_NOT_HANDLE), + SEC_QC_RBCMD_SIMPLE_STRICT("mbsmem_off", + PON_RESTART_REASON_MBS_MEM_RESERVE_OFF, + RESTART_REASON_NOT_HANDLE), + SEC_QC_RBCMD_SIMPLE_STRICT("GlobalActions restart", + PON_RESTART_REASON_NORMALBOOT, + RESTART_REASON_NOT_HANDLE), + SEC_QC_RBCMD_SIMPLE_STRICT("userrequested", + PON_RESTART_REASON_NORMALBOOT, + RESTART_REASON_NOT_HANDLE), + SEC_QC_RBCMD_SIMPLE_STRICT("silent.sec", + PON_RESTART_REASON_NORMALBOOT, + RESTART_REASON_NOT_HANDLE), + SEC_QC_RBCMD_SIMPLE_STRICT("peripheral_hw_reset", + PON_RESTART_REASON_SECURE_CHECK_FAIL, + RESTART_REASON_NORMAL), + SEC_QC_RBCMD_SIMPLE_STRICT("cross_fail", + PON_RESTART_REASON_CROSS_FAIL, + RESTART_REASON_NORMAL), + SEC_QC_RBCMD_SIMPLE_STRICT("user_quefi_quest", + PON_RESTART_REASON_QUEST_QUEFI_USER_START, + RESTART_REASON_NORMAL), + SEC_QC_RBCMD_SIMPLE_STRICT("user_suefi_quest", + PON_RESTART_REASON_QUEST_SUEFI_USER_START, + RESTART_REASON_NORMAL), + SEC_QC_RBCMD_SIMPLE_STRICT("user_dram_test", + PON_RESTART_REASON_USER_DRAM_TEST, + RESTART_REASON_NORMAL), + SEC_QC_RBCMD_SIMPLE_STRICT("erase_param_quest", + PON_RESTART_REASON_QUEST_ERASE_PARAM, + RESTART_REASON_NORMAL), + SEC_QC_RBCMD_SIMPLE_STRICT("user_quefi_plus_quest", + PON_RESTART_REASON_QUEST_QUEFI_PLUS_USER_START, + RESTART_REASON_NORMAL), + SEC_QC_RBCMD_SIMPLE_STRICT("user_suefi_plus_quest", + PON_RESTART_REASON_QUEST_SUEFI_PLUS_USER_START, + RESTART_REASON_NORMAL), + SEC_QC_RBCMD_SIMPLE_STRICT("user_dram_test_plus", + PON_RESTART_REASON_USER_DRAM_TEST_PLUS, + RESTART_REASON_NORMAL), + SEC_QC_RBCMD_SIMPLE_STRICT("user_flex_clk", + PON_RESTART_REASON_USER_FLEX_CLK_START, + RESTART_REASON_NORMAL), + SEC_QC_RBCMD_SIMPLE_STRICT("user_svs_clk", + PON_RESTART_REASON_USER_SVS_CLK_START, + RESTART_REASON_NORMAL), + SEC_QC_RBCMD_SIMPLE_STRICT("user_nominal_clk", + PON_RESTART_REASON_USER_NOMINAL_CLK_START, + RESTART_REASON_NORMAL), + SEC_QC_RBCMD_SIMPLE_STRICT("user_turbo_clk", + PON_RESTART_REASON_USER_TURBO_CLK_START, + RESTART_REASON_NORMAL), + + SEC_QC_RBCMD_SIMPLE_STRICT("recovery-update", + PON_RESTART_REASON_RECOVERY_UPDATE, + RESTART_REASON_NORMAL), + /* NOTE: these commands are pre-defined int the 'qcom-reboot-reason.c' file. + * In cases of these, this driver only update 'sec_rr' value + * which is only used for SAMSUNG internal. + */ + SEC_QC_RBMCD_QC_PRE_DEFINED("recovery", + RESTART_REASON_RECOVERY), +#if 0 /* TODO: "bootloader" command line option should be checked and uncommented + * these block after verified it. + */ + SEC_QC_RBMCD_QC_PRE_DEFINED("bootloader", + RESTART_REASON_NORMAL), +#endif + SEC_QC_RBMCD_QC_PRE_DEFINED("rtc", + RESTART_REASON_RTC), + SEC_QC_RBMCD_QC_PRE_DEFINED("dm-verity device corrupted", + RESTART_REASON_DMVERITY_CORRUPTED), + SEC_QC_RBMCD_QC_PRE_DEFINED("dm-verity enforcing", + RESTART_REASON_DMVERITY_ENFORCE), + SEC_QC_RBMCD_QC_PRE_DEFINED("keys clear", + RESTART_REASON_KEYS_CLEAR), +}; + +static void __rbcmd_del_for_each(enum sec_rbcmd_stage s, + struct qc_rbcmd_cmd *qrc, ssize_t n); + +static int __rbcmd_add_for_each(enum sec_rbcmd_stage s, + struct qc_rbcmd_cmd *qrc, ssize_t n) +{ + int err; + ssize_t last_failed; + ssize_t i; + + for (i = 0; i < n; i++) { + err = sec_rbcmd_add_cmd(s, &qrc[i].rc); + if (err) { + last_failed = i; + goto err_add_cmd; + } + } + + return 0; + +err_add_cmd: + __rbcmd_del_for_each(s, qrc, last_failed); + if (err == -EBUSY) + return -EPROBE_DEFER; + + return err; +} + +static void __rbcmd_del_for_each(enum sec_rbcmd_stage s, + struct qc_rbcmd_cmd *qrc, ssize_t n) +{ + ssize_t i; + + for (i = n - 1; i >= 0; i--) + sec_rbcmd_del_cmd(s, &qrc[i].rc); +} + +static int __rbcmd_set_default( + struct qc_rbcmd_drvdata *drvdata, enum sec_rbcmd_stage s) +{ + struct device *dev = drvdata->bd.dev; + struct qc_rbcmd_stage *stage = &drvdata->stage[s]; + struct sec_reboot_cmd *default_cmd; + int err; + + default_cmd = devm_kzalloc(dev, sizeof(*default_cmd), GFP_KERNEL); + if (!default_cmd) + return -ENOMEM; + + default_cmd->func = sec_qc_rbcmd_default_reason; + + err = sec_rbcmd_set_default_cmd(s, default_cmd); + if (err == -EBUSY) + return -EPROBE_DEFER; + else if (err) + return err; + + stage->default_cmd = default_cmd; + + return 0; +} + +static void __rbcmd_unset_default( + struct qc_rbcmd_drvdata *drvdata, enum sec_rbcmd_stage s) +{ + struct qc_rbcmd_stage *stage = &drvdata->stage[s]; + struct sec_reboot_cmd *default_cmd = stage->default_cmd; + + sec_rbcmd_unset_default_cmd(s, default_cmd); +} + +static int __rbcmd_register_command( + struct qc_rbcmd_drvdata *drvdata, enum sec_rbcmd_stage s) +{ + struct device *dev = drvdata->bd.dev; + struct qc_rbcmd_stage *stage = &drvdata->stage[s]; + struct qc_rbcmd_cmd *reboot_cmd; + ssize_t nr_cmd = ARRAY_SIZE(qc_rbcmd_template); + int err; + + reboot_cmd = devm_kmemdup(dev, qc_rbcmd_template, + nr_cmd * sizeof(struct qc_rbcmd_cmd), GFP_KERNEL); + if (!reboot_cmd) + return -ENOMEM; + + err = __rbcmd_add_for_each(s, reboot_cmd, nr_cmd); + if (err) + return err; + + stage->reboot_cmd = reboot_cmd; + + return 0; +} + +static int __rbcmd_init(struct builder *bd, enum sec_rbcmd_stage s) +{ + struct qc_rbcmd_drvdata *drvdata = + container_of(bd, struct qc_rbcmd_drvdata, bd); + int err; + + err = __rbcmd_set_default(drvdata, s); + if (err) + goto err_default; + + err = __rbcmd_register_command(drvdata, s); + if (err) + goto err_register; + + return 0; + +err_register: + __rbcmd_unset_default(drvdata, s); +err_default: + return err; +} + +static void __qc_rbcmd_unregister_command( + struct qc_rbcmd_drvdata *drvdata, enum sec_rbcmd_stage s) +{ + struct qc_rbcmd_stage *stage = &drvdata->stage[s]; + struct qc_rbcmd_cmd *reboot_cmd = stage->reboot_cmd; + ssize_t nr_cmd = ARRAY_SIZE(qc_rbcmd_template); + + __rbcmd_del_for_each(s, reboot_cmd, nr_cmd); +} + +static void __rbcmd_exit(struct builder *bd, enum sec_rbcmd_stage s) +{ + struct qc_rbcmd_drvdata *drvdata = + container_of(bd, struct qc_rbcmd_drvdata, bd); + + __rbcmd_unset_default(drvdata, s); + __qc_rbcmd_unregister_command(drvdata, s); +} + +int __qc_rbcmd_init_on_reboot(struct builder *bd) +{ + struct qc_rbcmd_drvdata *drvdata = + container_of(bd, struct qc_rbcmd_drvdata, bd); + enum sec_rbcmd_stage s = SEC_RBCMD_STAGE_REBOOT_NOTIFIER; + + if (!drvdata->use_on_reboot) + return 0; + + return __rbcmd_init(bd, s); +} + +void __qc_rbcmd_exit_on_reboot(struct builder *bd) +{ + struct qc_rbcmd_drvdata *drvdata = + container_of(bd, struct qc_rbcmd_drvdata, bd); + enum sec_rbcmd_stage s = SEC_RBCMD_STAGE_REBOOT_NOTIFIER; + + if (!drvdata->use_on_reboot) + return; + + __rbcmd_exit(bd, s); +} + +int __qc_rbcmd_init_on_restart(struct builder *bd) +{ + struct qc_rbcmd_drvdata *drvdata = + container_of(bd, struct qc_rbcmd_drvdata, bd); + enum sec_rbcmd_stage s = SEC_RBCMD_STAGE_RESTART_HANDLER; + + if (!drvdata->use_on_restart) + return 0; + + return __rbcmd_init(bd, s); +} + +void __qc_rbcmd_exit_on_restart(struct builder *bd) +{ + struct qc_rbcmd_drvdata *drvdata = + container_of(bd, struct qc_rbcmd_drvdata, bd); + enum sec_rbcmd_stage s = SEC_RBCMD_STAGE_RESTART_HANDLER; + + if (!drvdata->use_on_restart) + return; + + __rbcmd_exit(bd, s); +} + +int __qc_rbcmd_register_panic_handle(struct builder *bd) +{ + return atomic_notifier_chain_register(&panic_notifier_list, + &sec_qc_rbcmd_panic_handle); +} + +void __qc_rbcmd_unregister_panic_handle(struct builder *bd) +{ + atomic_notifier_chain_unregister(&panic_notifier_list, + &sec_qc_rbcmd_panic_handle); +} diff --git a/drivers/samsung/debug/qcom/reboot_cmd/sec_qc_rbcmd_main.c b/drivers/samsung/debug/qcom/reboot_cmd/sec_qc_rbcmd_main.c new file mode 100644 index 000000000000..8332d26b0720 --- /dev/null +++ b/drivers/samsung/debug/qcom/reboot_cmd/sec_qc_rbcmd_main.c @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2020-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include + +#include + +#include "sec_qc_rbcmd.h" + +struct qc_rbcmd_drvdata *qc_rbcmd; + +__ss_static int __rbcmd_register_pon_rr_writer( + struct qc_rbcmd_drvdata *drvdata, struct notifier_block *nb) +{ + return raw_notifier_chain_register(&drvdata->pon_rr_writers, nb); +} + +int sec_qc_rbcmd_register_pon_rr_writer(struct notifier_block *nb) +{ + if (!__rbcmd_is_probed()) + return -EBUSY; + + return __rbcmd_register_pon_rr_writer(qc_rbcmd, nb); +} +EXPORT_SYMBOL_GPL(sec_qc_rbcmd_register_pon_rr_writer); + +__ss_static void __rbcmd_unregister_pon_rr_writer( + struct qc_rbcmd_drvdata *drvdata, struct notifier_block *nb) +{ + raw_notifier_chain_unregister(&drvdata->pon_rr_writers, nb); +} + +void sec_qc_rbcmd_unregister_pon_rr_writer(struct notifier_block *nb) +{ + if (!__rbcmd_is_probed()) + return /* -EBUSY */; + + __rbcmd_unregister_pon_rr_writer(qc_rbcmd, nb); +} +EXPORT_SYMBOL_GPL(sec_qc_rbcmd_unregister_pon_rr_writer); + +__ss_static void __rbcmd_write_pon_rr(struct qc_rbcmd_drvdata *drvdata, + unsigned int pon_rr, struct sec_reboot_param *param) +{ + pr_warn("power on reason: 0x%08X\n", pon_rr); + + raw_notifier_call_chain(&drvdata->pon_rr_writers, pon_rr, param); +} + +void __qc_rbcmd_write_pon_rr(unsigned int pon_rr, + struct sec_reboot_param *param) +{ + if (!__rbcmd_is_probed()) + return /* -EBUSY */; + + __rbcmd_write_pon_rr(qc_rbcmd, pon_rr, param); +} + +__ss_static int __rbcmd_register_sec_rr_writer( + struct qc_rbcmd_drvdata *drvdata, struct notifier_block *nb) +{ + int err; + + err = raw_notifier_chain_register(&drvdata->sec_rr_writers, nb); + if (err) + return err; + + /* NOTE: set to 'RESTART_REASON_SEC_DEBUG_MODE' by default to detect + * sudden reset. + */ + pr_warn("default reboot mode: 0x%08X (for %pS)\n", + RESTART_REASON_SEC_DEBUG_MODE, + nb->notifier_call); + nb->notifier_call(nb, RESTART_REASON_SEC_DEBUG_MODE, NULL); + + return 0; +} + +int sec_qc_rbcmd_register_sec_rr_writer(struct notifier_block *nb) +{ + if (!__rbcmd_is_probed()) + return -EBUSY; + + return __rbcmd_register_sec_rr_writer(qc_rbcmd, nb); +} +EXPORT_SYMBOL_GPL(sec_qc_rbcmd_register_sec_rr_writer); + +__ss_static void __rbcmd_unregister_sec_rr_writer( + struct qc_rbcmd_drvdata *drvdata, struct notifier_block *nb) +{ + raw_notifier_chain_unregister(&drvdata->sec_rr_writers, nb); +} + +void sec_qc_rbcmd_unregister_sec_rr_writer(struct notifier_block *nb) +{ + if (!__rbcmd_is_probed()) + return /* -EBUSY */; + + __rbcmd_unregister_sec_rr_writer(qc_rbcmd, nb); +} +EXPORT_SYMBOL_GPL(sec_qc_rbcmd_unregister_sec_rr_writer); + +__ss_static void __rbcmd_write_sec_rr(struct qc_rbcmd_drvdata *drvdata, + unsigned int sec_rr, struct sec_reboot_param *param) +{ + pr_warn("reboot mode: 0x%08X\n", sec_rr); + + raw_notifier_call_chain(&drvdata->sec_rr_writers, sec_rr, param); +} + +void __qc_rbcmd_write_sec_rr(unsigned int sec_rr, + struct sec_reboot_param *param) +{ + if (!__rbcmd_is_probed()) + return /* -EBUSY */; + + __rbcmd_write_sec_rr(qc_rbcmd, sec_rr, param); +} + +void __qc_rbcmd_set_restart_reason(unsigned int pon_rr, unsigned int sec_rr, + struct sec_reboot_param *param) +{ + __rbcmd_write_pon_rr(qc_rbcmd, pon_rr, param); + __rbcmd_write_sec_rr(qc_rbcmd, sec_rr, param); +} + +void sec_qc_rbcmd_set_restart_reason(unsigned int pon_rr, unsigned int sec_rr, + struct sec_reboot_param *param) +{ + if (!__rbcmd_is_probed()) + return /* -EBUSY */; + + __qc_rbcmd_set_restart_reason(pon_rr, sec_rr, param); +} +EXPORT_SYMBOL_GPL(sec_qc_rbcmd_set_restart_reason); + +__ss_static noinline int __rbcmd_parse_dt_use_on_reboot(struct builder *bd, + struct device_node *np) +{ + struct qc_rbcmd_drvdata *drvdata = + container_of(bd, struct qc_rbcmd_drvdata, bd); + + drvdata->use_on_reboot = + of_property_read_bool(np, "sec,use-on_reboot"); + + return 0; +} + +__ss_static noinline int __rbcmd_parse_dt_use_on_restart(struct builder *bd, + struct device_node *np) +{ + struct qc_rbcmd_drvdata *drvdata = + container_of(bd, struct qc_rbcmd_drvdata, bd); + + drvdata->use_on_restart = + of_property_read_bool(np, "sec,use-on_restart"); + + return 0; +} + +static const struct dt_builder __qc_rbcmd_dt_builder[] = { + DT_BUILDER(__rbcmd_parse_dt_use_on_reboot), + DT_BUILDER(__rbcmd_parse_dt_use_on_restart), +}; + +static noinline int __rbcmd_parse_dt(struct builder *bd) +{ + return sec_director_parse_dt(bd, __qc_rbcmd_dt_builder, + ARRAY_SIZE(__qc_rbcmd_dt_builder)); +} + +static noinline int __rbcmd_probe_prolog(struct builder *bd) +{ + struct qc_rbcmd_drvdata *drvdata = + container_of(bd, struct qc_rbcmd_drvdata, bd); + + RAW_INIT_NOTIFIER_HEAD(&drvdata->pon_rr_writers); + RAW_INIT_NOTIFIER_HEAD(&drvdata->sec_rr_writers); + + return 0; +} + +static noinline int __rbcmd_set_drvdata(struct builder *bd) +{ + struct qc_rbcmd_drvdata *drvdata = + container_of(bd, struct qc_rbcmd_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + + return 0; +} + +static int __rbcmd_probe_epilog(struct builder *bd) +{ + struct qc_rbcmd_drvdata *drvdata = + container_of(bd, struct qc_rbcmd_drvdata, bd); + + qc_rbcmd = drvdata; + + return 0; +} + +static void __rbcmd_remove_prolog(struct builder *bd) +{ + qc_rbcmd = NULL; +} + +static int __rbcmd_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct qc_rbcmd_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __rbcmd_probe_threaded(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct qc_rbcmd_drvdata *drvdata = platform_get_drvdata(pdev); + + return sec_director_probe_dev_threaded(&drvdata->bd, builder, n, + "qc_rbcmd"); +} + +static int __rbcmd_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct qc_rbcmd_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static int __rbcmd_remove_threaded(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct qc_rbcmd_drvdata *drvdata = platform_get_drvdata(pdev); + struct director_threaded *drct = drvdata->bd.drct; + + sec_director_destruct_dev_threaded(drct); + + return 0; +} + +static const struct dev_builder __qc_rbcmd_dev_builder[] = { + DEVICE_BUILDER(__rbcmd_parse_dt, NULL), + DEVICE_BUILDER(__rbcmd_probe_prolog, NULL), + DEVICE_BUILDER(__qc_rbcmd_register_panic_handle, + __qc_rbcmd_unregister_panic_handle), + DEVICE_BUILDER(__rbcmd_set_drvdata, NULL), +}; + +static const struct dev_builder __qc_rbcmd_dev_builder_threaded[] = { + DEVICE_BUILDER(__qc_rbcmd_init_on_reboot, + __qc_rbcmd_exit_on_reboot), + DEVICE_BUILDER(__qc_rbcmd_init_on_restart, + __qc_rbcmd_exit_on_restart), + DEVICE_BUILDER(__rbcmd_probe_epilog, + __rbcmd_remove_prolog), +}; + +static int sec_qc_rbcmd_probe(struct platform_device *pdev) +{ + int err; + + err = __rbcmd_probe(pdev, __qc_rbcmd_dev_builder, + ARRAY_SIZE(__qc_rbcmd_dev_builder)); + if (err) + return err; + + return __rbcmd_probe_threaded(pdev, __qc_rbcmd_dev_builder_threaded, + ARRAY_SIZE(__qc_rbcmd_dev_builder_threaded)); +} + +static int sec_qc_rbcmd_remove(struct platform_device *pdev) +{ + __rbcmd_remove_threaded(pdev, __qc_rbcmd_dev_builder_threaded, + ARRAY_SIZE(__qc_rbcmd_dev_builder_threaded)); + + __rbcmd_remove(pdev, __qc_rbcmd_dev_builder, + ARRAY_SIZE(__qc_rbcmd_dev_builder)); + + return 0; +} + +static const struct of_device_id sec_qc_rbcmd_match_table[] = { + { .compatible = "samsung,qcom-reboot_cmd" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_qc_rbcmd_match_table); + +static struct platform_driver sec_qc_rbcmd_driver = { + .driver = { + .name = "sec,qc-reboot_cmd", + .of_match_table = of_match_ptr(sec_qc_rbcmd_match_table), + }, + .probe = sec_qc_rbcmd_probe, + .remove = sec_qc_rbcmd_remove, +}; + +static __init int sec_qc_rbcmd_init(void) +{ + return platform_driver_register(&sec_qc_rbcmd_driver); +} +arch_initcall(sec_qc_rbcmd_init); + +static __exit void sec_qc_rbcmd_exit(void) +{ + platform_driver_unregister(&sec_qc_rbcmd_driver); +} +module_exit(sec_qc_rbcmd_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("Reboot command handlers for Qualcomm based devices"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/debug/qcom/reboot_reason/Kconfig b/drivers/samsung/debug/qcom/reboot_reason/Kconfig new file mode 100644 index 000000000000..b2ba07c941f7 --- /dev/null +++ b/drivers/samsung/debug/qcom/reboot_reason/Kconfig @@ -0,0 +1,5 @@ +config SEC_QC_QCOM_REBOOT_REASON + tristate "SEC Reboot Commands - Concrete writer using qcom-reboot-reason" + depends on SEC_QC_RBCMD && POWER_RESET_QCOM_DOWNLOAD_MODE && POWER_RESET_QCOM_REBOOT_REASON + help + TODO: help is not ready. diff --git a/drivers/samsung/debug/qcom/reboot_reason/Makefile b/drivers/samsung/debug/qcom/reboot_reason/Makefile new file mode 100644 index 000000000000..28ff3c863afe --- /dev/null +++ b/drivers/samsung/debug/qcom/reboot_reason/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SEC_QC_QCOM_REBOOT_REASON) += sec_qc_qcom_reboot_reason.o diff --git a/drivers/samsung/debug/qcom/reboot_reason/sec_qc_qcom_reboot_reason.c b/drivers/samsung/debug/qcom/reboot_reason/sec_qc_qcom_reboot_reason.c new file mode 100644 index 000000000000..b1c322f34530 --- /dev/null +++ b/drivers/samsung/debug/qcom/reboot_reason/sec_qc_qcom_reboot_reason.c @@ -0,0 +1,432 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2020-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +/* NOTE: This file is a counter part of QC's + * 'drivers/power/reset/qcom-dload-mode.c' and + * 'dirvers/power/reset/qcom-reboot-reason.c'. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* TODO: implemented by SAMSUNG + * Location : drivers/power/reset/qcom-dload-mode.c + */ +extern void qcom_set_dload_mode(int on); + +struct qc_reboot_reason_drvdata { + struct builder bd; + struct nvmem_cell *nv_restart_reason; + struct nvmem_cell *nv_pon_reason; + /* NOTE: This address is used in 'msm-poweroff' module which is + * located in the imem. + */ + u32 __iomem *qcom_restart_reason; + struct notifier_block nb_pon_rr; + struct notifier_block nb_sec_rr; + struct notifier_block nb_reboot; + struct notifier_block nb_panic; + bool in_panic; +}; + +static noinline int __qc_reboot_reason_parse_dt_reboot_notifier_priority( + struct builder *bd, struct device_node *np) +{ + struct qc_reboot_reason_drvdata *drvdata = + container_of(bd, struct qc_reboot_reason_drvdata, bd); + s32 priority; + int err; + + err = of_property_read_s32(np, "sec,reboot_notifier-priority", + &priority); + if (err) + return -EINVAL; + + drvdata->nb_reboot.priority = (int)priority; + + return 0; +} + +static const struct dt_builder __qc_reboot_reason_dt_builder[] = { + DT_BUILDER(__qc_reboot_reason_parse_dt_reboot_notifier_priority), +}; + +static noinline int __qc_reboot_reason_parse_dt(struct builder *bd) +{ + return sec_director_parse_dt(bd, __qc_reboot_reason_dt_builder, + ARRAY_SIZE(__qc_reboot_reason_dt_builder)); +} + +static int __qc_reboot_reason_probe_prolog(struct builder *bd) +{ + /* NOTE: enable upload mode to debug sudden reset */ + qcom_set_dload_mode(1); + + return 0; +} + +static void __qc_reboot_reason_remove_epilog(struct builder *bd) +{ + qcom_set_dload_mode(0); +} + +static noinline int __qc_reboot_reason_get_nv_restart_reason(struct builder *bd) +{ + struct qc_reboot_reason_drvdata *drvdata = + container_of(bd, struct qc_reboot_reason_drvdata, bd); + struct device *dev = bd->dev; + struct nvmem_cell *nvmem_cell; + + nvmem_cell = devm_nvmem_cell_get(dev, "restart_reason"); + if (IS_ERR(nvmem_cell)) + return PTR_ERR(nvmem_cell); + + drvdata->nv_restart_reason = nvmem_cell; + + return 0; +} + +static noinline int __qc_reboot_reason_get_nv_pon_reason(struct builder *bd) +{ + struct qc_reboot_reason_drvdata *drvdata = + container_of(bd, struct qc_reboot_reason_drvdata, bd); + struct device *dev = bd->dev; + struct nvmem_cell *nvmem_cell; + + nvmem_cell = devm_nvmem_cell_get(dev, "pon_reason"); + if (IS_ERR(nvmem_cell)) { + dev_warn(dev, "pon_reason nvmem_cell is not ready in the devcie-tree\n"); + /* NOTE: this is an optional feature to check the power on + * reason for debuging for certaion conditions like SMPL. + */ + return 0; + } + + /* TODO: implement code checking power on reasons at here */ + + drvdata->nv_pon_reason = nvmem_cell; + + return 0; +} + +static int __qc_reboot_reason_ioremap_qcom_restart_reason(struct builder *bd) +{ + struct qc_reboot_reason_drvdata *drvdata = + container_of(bd, struct qc_reboot_reason_drvdata, bd); + struct device_node *mem_np; + struct device *dev = bd->dev; + u32 __iomem *qcom_restart_reason; + + mem_np = of_find_compatible_node(NULL, NULL, + "qcom,msm-imem-restart_reason"); + if (!mem_np) { + dev_err(dev, "unable to find DT imem restart reason node\n"); + return -ENODEV; + } + + qcom_restart_reason = of_iomap(mem_np, 0); + if (unlikely(!qcom_restart_reason)) { + dev_err(dev, "unable to map imem restart reason offset\n"); + return -ENOMEM; + } + + drvdata->qcom_restart_reason = qcom_restart_reason; + + return 0; +} + +static void __qc_reboot_reason_iounmap_qcom_restart_reason(struct builder *bd) +{ + struct qc_reboot_reason_drvdata *drvdata = + container_of(bd, struct qc_reboot_reason_drvdata, bd); + + iounmap(drvdata->qcom_restart_reason); +} + +static void __reboot_reason_write_pon_rr(struct qc_reboot_reason_drvdata *drvdata, + unsigned char pon_reason) +{ + struct device *dev = drvdata->bd.dev; + unsigned char pon_read; + const size_t max_retry = 5; + size_t retry; + void *buf; + int err; + + for (retry = 0; retry < max_retry; retry++) { + err = nvmem_cell_write(drvdata->nv_restart_reason, + &pon_reason, sizeof(pon_reason)); + if (err <= 0) + continue; + + buf = nvmem_cell_read(drvdata->nv_restart_reason, + NULL); + if (IS_ERR(buf)) { + kfree(buf); + continue; + } + + pon_read = *(unsigned char *)buf; + kfree(buf); + if (pon_read == pon_reason) { + dev_info(dev, "0x%02hhX is written successfully. (retry = %zu)\n", + pon_reason, retry); + return; + } + } + + dev_warn(dev, "pon reason was not written properly!\n"); +} + +static int sec_qc_reboot_reason_write_pon_rr(struct notifier_block *this, + unsigned long pon_rr, void *data) +{ + struct qc_reboot_reason_drvdata *drvdata = container_of(this, + struct qc_reboot_reason_drvdata, nb_pon_rr); + struct sec_reboot_param *param = data; + + if (param && param->mode == SYS_POWER_OFF) + return NOTIFY_DONE; + + if (pon_rr == PON_RESTART_REASON_NOT_HANDLE) + return NOTIFY_DONE; + + __reboot_reason_write_pon_rr(drvdata, (unsigned char)pon_rr); + + nvmem_cell_put(drvdata->nv_restart_reason); + drvdata->nv_restart_reason = NULL; + + return NOTIFY_OK; +} + +static int __qc_reboot_reason_register_pon_rr_writer(struct builder *bd) +{ + struct qc_reboot_reason_drvdata *drvdata = + container_of(bd, struct qc_reboot_reason_drvdata, bd); + int err; + + drvdata->nb_pon_rr.notifier_call = sec_qc_reboot_reason_write_pon_rr; + + err = sec_qc_rbcmd_register_pon_rr_writer(&drvdata->nb_pon_rr); + if (err == -EBUSY) + return -EPROBE_DEFER; + + return err; +} + +static void __qc_reboot_reason_unregister_pon_rr_writer(struct builder *bd) +{ + struct qc_reboot_reason_drvdata *drvdata = + container_of(bd, struct qc_reboot_reason_drvdata, bd); + + return sec_qc_rbcmd_unregister_pon_rr_writer(&drvdata->nb_pon_rr); +} + +static int sec_qc_reboot_reason_write_sec_rr(struct notifier_block *this, + unsigned long sec_rr, void *d) +{ + struct qc_reboot_reason_drvdata *drvdata = container_of(this, + struct qc_reboot_reason_drvdata, nb_sec_rr); + + if (sec_rr == RESTART_REASON_NOT_HANDLE) + return NOTIFY_DONE; + + __raw_writel((u32)sec_rr, drvdata->qcom_restart_reason); + + return NOTIFY_OK; +} + +static int __qc_reboot_reason_register_sec_rr_writer(struct builder *bd) +{ + struct qc_reboot_reason_drvdata *drvdata = + container_of(bd, struct qc_reboot_reason_drvdata, bd); + + drvdata->nb_sec_rr.notifier_call = sec_qc_reboot_reason_write_sec_rr; + + return sec_qc_rbcmd_register_sec_rr_writer(&drvdata->nb_sec_rr); +} + +static void __qc_reboot_reason_unregister_sec_rr_writer(struct builder *bd) +{ + struct qc_reboot_reason_drvdata *drvdata = + container_of(bd, struct qc_reboot_reason_drvdata, bd); + + return sec_qc_rbcmd_unregister_sec_rr_writer(&drvdata->nb_sec_rr); +} + +static int sec_qc_reboot_reason_reboot_call(struct notifier_block *this, + unsigned long l, void *d) +{ + struct qc_reboot_reason_drvdata *drvdata = container_of(this, + struct qc_reboot_reason_drvdata, nb_reboot); + + if (drvdata->in_panic) + qcom_set_dload_mode(1); + else + qcom_set_dload_mode(0); + + return NOTIFY_OK; +} + +static int __qc_reboot_reason_register_reboot_notifier(struct builder *bd) +{ + struct qc_reboot_reason_drvdata *drvdata = + container_of(bd, struct qc_reboot_reason_drvdata, bd); + int err; + + drvdata->nb_reboot.notifier_call = sec_qc_reboot_reason_reboot_call; + + err = register_reboot_notifier(&drvdata->nb_reboot); + if (err == -EBUSY) + return -EPROBE_DEFER; + + return err; +} + +static void __qc_reboot_reason_unregister_reboot_notifier(struct builder *bd) +{ + struct qc_reboot_reason_drvdata *drvdata = + container_of(bd, struct qc_reboot_reason_drvdata, bd); + + unregister_reboot_notifier(&drvdata->nb_reboot); +} + +static int sec_qc_reboot_reason_panic_call(struct notifier_block *this, + unsigned long l, void *d) +{ + struct qc_reboot_reason_drvdata *drvdata = + container_of(this, struct qc_reboot_reason_drvdata, nb_panic); + struct device *dev = drvdata->bd.dev; + + qcom_set_dload_mode(1); + + dev_warn(dev, "reboot mode: 0x%08X\n", RESTART_REASON_SEC_DEBUG_MODE); + __raw_writel(RESTART_REASON_SEC_DEBUG_MODE, drvdata->qcom_restart_reason); + + drvdata->in_panic = true; + + return NOTIFY_OK; +} + +static int __qc_reboot_reason_register_panic_notifier(struct builder *bd) +{ + struct qc_reboot_reason_drvdata *drvdata = + container_of(bd, struct qc_reboot_reason_drvdata, bd); + + drvdata->nb_panic.notifier_call = sec_qc_reboot_reason_panic_call; + + return atomic_notifier_chain_register(&panic_notifier_list, + &drvdata->nb_panic); +} + +static void __qc_reboot_reason_unregister_panic_notifier(struct builder *bd) +{ + struct qc_reboot_reason_drvdata *drvdata = + container_of(bd, struct qc_reboot_reason_drvdata, bd); + + atomic_notifier_chain_unregister(&panic_notifier_list, + &drvdata->nb_panic); +} + +static int __qc_reboot_reason_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct qc_reboot_reason_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __qc_reboot_reason_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct qc_reboot_reason_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static const struct dev_builder __qc_reboot_reason_dev_builder[] = { + DEVICE_BUILDER(__qc_reboot_reason_parse_dt, NULL), + DEVICE_BUILDER(__qc_reboot_reason_probe_prolog, + __qc_reboot_reason_remove_epilog), + DEVICE_BUILDER(__qc_reboot_reason_get_nv_restart_reason, NULL), + DEVICE_BUILDER(__qc_reboot_reason_get_nv_pon_reason, NULL), + DEVICE_BUILDER(__qc_reboot_reason_ioremap_qcom_restart_reason, + __qc_reboot_reason_iounmap_qcom_restart_reason), + DEVICE_BUILDER(__qc_reboot_reason_register_pon_rr_writer, + __qc_reboot_reason_unregister_pon_rr_writer), + DEVICE_BUILDER(__qc_reboot_reason_register_sec_rr_writer, + __qc_reboot_reason_unregister_sec_rr_writer), + DEVICE_BUILDER(__qc_reboot_reason_register_reboot_notifier, + __qc_reboot_reason_unregister_reboot_notifier), + DEVICE_BUILDER(__qc_reboot_reason_register_panic_notifier, + __qc_reboot_reason_unregister_panic_notifier), +}; + +static int sec_qc_reboot_reason_probe(struct platform_device *pdev) +{ + return __qc_reboot_reason_probe(pdev, __qc_reboot_reason_dev_builder, + ARRAY_SIZE(__qc_reboot_reason_dev_builder)); +} + +static int sec_qc_reboot_reason_remove(struct platform_device *pdev) +{ + return __qc_reboot_reason_remove(pdev, __qc_reboot_reason_dev_builder, + ARRAY_SIZE(__qc_reboot_reason_dev_builder)); +} + +static const struct of_device_id sec_qc_reboot_reason_match_table[] = { + { .compatible = "samsung,qcom-qcom_reboot_reason" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_qc_reboot_reason_match_table); + +static struct platform_driver sec_qc_reboot_reason_driver = { + .driver = { + .name = "sec,qc-qcom_reboot_reason", + .of_match_table = of_match_ptr(sec_qc_reboot_reason_match_table), + }, + .probe = sec_qc_reboot_reason_probe, + .remove = sec_qc_reboot_reason_remove, +}; + +static int __init sec_qc_reboot_reason_init(void) +{ + return platform_driver_register(&sec_qc_reboot_reason_driver); +} +arch_initcall(sec_qc_reboot_reason_init); + +static void __exit sec_qc_reboot_reason_exit(void) +{ + platform_driver_unregister(&sec_qc_reboot_reason_driver); +} +module_exit(sec_qc_reboot_reason_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("Counter-part of Qualcomm's qcom-reboot-reason and qcom-dload-mode."); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/samsung/debug/qcom/rst_exinfo/Kconfig b/drivers/samsung/debug/qcom/rst_exinfo/Kconfig new file mode 100644 index 000000000000..e378ac6594bc --- /dev/null +++ b/drivers/samsung/debug/qcom/rst_exinfo/Kconfig @@ -0,0 +1,21 @@ +config SEC_QC_RST_EXINFO + tristate "SEC Reset extra info for Qualcomm based devices" + help + TODO: help is not ready. + +config SEC_QC_RST_EXINFO_TEST_FOR_ON_DEVICE + tristate "KUnit test for sec_qc_rst_exinfo_test" + depends on KUNIT + depends on SEC_QC_RST_EXINFO + help + TODO: Describe config fully. + If you run this test driver on device, SHOULD set this config as 'm' to build test driver modulraly. + +config SEC_QC_RST_EXINFO_TEST_FOR_ONLY_UML + tristate "KUnit test for sec_qc_rst_exinfo_test" + depends on KUNIT + depends on UML + depends on SEC_QC_RST_EXINFO + help + TODO: Describe config fully. + This CONFIG is recommended to set to y. diff --git a/drivers/samsung/debug/qcom/rst_exinfo/Makefile b/drivers/samsung/debug/qcom/rst_exinfo/Makefile new file mode 100644 index 000000000000..0041430ab641 --- /dev/null +++ b/drivers/samsung/debug/qcom/rst_exinfo/Makefile @@ -0,0 +1,9 @@ +obj-$(CONFIG_SEC_QC_RST_EXINFO) += sec_qc_rst_exinfo.o +sec_qc_rst_exinfo-objs := sec_qc_rst_exinfo_main.o \ + sec_qc_arch_rst_exinfo_vh.o \ + sec_qc_rst_exinfo_fault.o + +sec_qc_rst_exinfo-$(CONFIG_ARM64) += sec_qc_arm64_rst_exinfo.o \ + sec_qc_arm64_rst_exinfo_vh.o + +GCOV_PROFILE_sec_qc_rst_exinfo.o := $(CONFIG_KUNIT) diff --git a/drivers/samsung/debug/qcom/rst_exinfo/sec_qc_arch_rst_exinfo_vh.c b/drivers/samsung/debug/qcom/rst_exinfo/sec_qc_arch_rst_exinfo_vh.c new file mode 100644 index 000000000000..72ea13fe0aa5 --- /dev/null +++ b/drivers/samsung/debug/qcom/rst_exinfo/sec_qc_arch_rst_exinfo_vh.c @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2022 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +/* This file contains '__weak' function for the UML based testing. */ + +#include + +#include + +int __weak __qc_arch_rst_exinfo_vh_init(struct builder *bd) +{ + return 0; +} + +int __weak __qc_arch_rst_exinfo_register_rvh_die_kernel_fault(struct builder *bd) +{ + return 0; +} + +int __weak __qc_arch_rst_exinfo_register_rvh_do_sp_pc_abort(struct builder *bd) +{ + return 0; +} + +int __weak __qc_arch_rst_exinfo_register_rvh_report_bug(struct builder *bd) +{ + return 0; +} diff --git a/drivers/samsung/debug/qcom/rst_exinfo/sec_qc_arm64_rst_exinfo.c b/drivers/samsung/debug/qcom/rst_exinfo/sec_qc_arm64_rst_exinfo.c new file mode 100644 index 000000000000..10937bede5eb --- /dev/null +++ b/drivers/samsung/debug/qcom/rst_exinfo/sec_qc_arm64_rst_exinfo.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2006-2022 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#include + +#include "sec_qc_rst_exinfo.h" + +void __qc_arch_rst_exinfo_die_handler(struct rst_exinfo_drvdata *drvdata, + enum die_val val, const struct die_args *args) +{ + struct pt_regs *regs = args->regs; + + __qc_rst_exinfo_die_do_mem_abort(drvdata, val, args); + + __qc_rst_exinfo_fault_print_handler(drvdata, args->str); + __qc_rst_exinfo_store_extc_idx(drvdata, false); + __qc_rst_exinfo_save_dying_msg(drvdata, args->str, + (void *)instruction_pointer(regs), + (void *)regs->regs[30]); +} diff --git a/drivers/samsung/debug/qcom/rst_exinfo/sec_qc_arm64_rst_exinfo_vh.c b/drivers/samsung/debug/qcom/rst_exinfo/sec_qc_arm64_rst_exinfo_vh.c new file mode 100644 index 000000000000..bbdd3b398e00 --- /dev/null +++ b/drivers/samsung/debug/qcom/rst_exinfo/sec_qc_arm64_rst_exinfo_vh.c @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2006-2022 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#include +#include + +#include + +#include +#include + +#include + +#include "sec_qc_rst_exinfo.h" + +static pgd_t *init_mm_pgdp; + +#define __rst_exinfo_read_special_reg(x) ({ \ + uint64_t val; \ + asm volatile ("mrs %0, " # x : "=r"(val)); \ + val; \ +}) + +int __qc_arch_rst_exinfo_vh_init(struct builder *bd) +{ + union { + struct { + uint64_t baddr:48; + uint64_t asid:16; + }; + uint64_t raw; + } ttbr1_el1; + phys_addr_t baddr_phys; + + ttbr1_el1.raw = __rst_exinfo_read_special_reg(TTBR1_EL1); + baddr_phys = ttbr1_el1.baddr & (~(PAGE_SIZE - 1)); + init_mm_pgdp = (pgd_t *)__phys_to_kimg(baddr_phys); + + return 0; +} + +static inline void __rst_exinfo_vh_store_pte(_kern_ex_info_t *kern_ex_info, + unsigned long addr, int idx) +{ + int cpu; + + cpu = get_cpu(); + + if (unlikely(idx == 0)) + memset(&kern_ex_info->fault[cpu].pte, 0, + sizeof(kern_ex_info->fault[cpu].pte)); + + kern_ex_info->fault[cpu].pte[idx] = addr; + + put_cpu(); +} + +static __always_inline void ____rst_exinfo_vh_table_walk( + _kern_ex_info_t *kern_ex_info, unsigned long addr) +{ + pgd_t *mm_pgd; + pgd_t *pgdp; + pgd_t pgd; + + if (is_ttbr0_addr(addr)) { + /* TTBR0 */ + mm_pgd = current->active_mm->pgd; + if (mm_pgd == init_mm_pgdp) + return; + } else if (is_ttbr1_addr(addr)) { + /* TTBR1 */ + mm_pgd = init_mm_pgdp; + } else { + __rst_exinfo_vh_store_pte(kern_ex_info, addr, 1); + return; + } + + __rst_exinfo_vh_store_pte(kern_ex_info, (unsigned long)mm_pgd, 0); + __rst_exinfo_vh_store_pte(kern_ex_info, addr, 1); + + pgdp = pgd_offset_pgd(mm_pgd, addr); + pgd = READ_ONCE(*pgdp); + __rst_exinfo_vh_store_pte(kern_ex_info, (unsigned long)pgd_val(pgd), 2); + + do { + p4d_t *p4dp, p4d; + pud_t *pudp, pud; + pmd_t *pmdp, pmd; + pte_t *ptep, pte; + + if (pgd_none(pgd) || pgd_bad(pgd)) + break; + + p4dp = p4d_offset(pgdp, addr); + p4d = READ_ONCE(*p4dp); + if (p4d_none(p4d) || p4d_bad(p4d)) + break; + + pudp = pud_offset(p4dp, addr); + pud = READ_ONCE(*pudp); + __rst_exinfo_vh_store_pte(kern_ex_info, + (unsigned long)pud_val(pud), 3); + if (pud_none(pud) || pud_bad(pud)) + break; + + pmdp = pmd_offset(pudp, addr); + pmd = READ_ONCE(*pmdp); + __rst_exinfo_vh_store_pte(kern_ex_info, + (unsigned long)pmd_val(pmd), 4); + if (pmd_none(pmd) || pmd_bad(pmd)) + break; + + ptep = pte_offset_map(pmdp, addr); + pte = READ_ONCE(*ptep); + pte_unmap(ptep); + __rst_exinfo_vh_store_pte(kern_ex_info, + (unsigned long)pte_val(pte), 5); + } while (0); +} + +static inline void __rst_exinfo_vh_table_walk(unsigned long addr) +{ + rst_exinfo_t *rst_exinfo; + _kern_ex_info_t *kern_ex_info; + + if (!__qc_rst_exinfo_is_probed()) + return; + + rst_exinfo = qc_rst_exinfo->rst_exinfo; + kern_ex_info = &rst_exinfo->kern_ex_info.info; + if (kern_ex_info->cpu != -1) + return; + + ____rst_exinfo_vh_table_walk(kern_ex_info, addr); +} + +static __always_inline void ____rst_exinfo_save_fault_info( + _kern_ex_info_t *kern_ex_info, unsigned long esr, + const char *str, unsigned long var1, unsigned long var2) +{ + int cpu; + + cpu = get_cpu(); + + kern_ex_info->fault[cpu].esr = esr; + snprintf(kern_ex_info->fault[cpu].str, + sizeof(kern_ex_info->fault[cpu].str), + "%s", str); + kern_ex_info->fault[cpu].var1 = var1; + kern_ex_info->fault[cpu].var2 = var2; + + put_cpu(); +} + +static inline void __rst_exinfo_save_fault_info(unsigned long esr, + const char *str, unsigned long var1, unsigned long var2) +{ + rst_exinfo_t *rst_exinfo; + _kern_ex_info_t *kern_ex_info; + + if (!__qc_rst_exinfo_is_probed()) + return; + + rst_exinfo = qc_rst_exinfo->rst_exinfo; + kern_ex_info = &rst_exinfo->kern_ex_info.info; + if (kern_ex_info->cpu != -1) + return; + + ____rst_exinfo_save_fault_info(kern_ex_info, esr, str, var1, var2); +} + +static void __rst_dump_instr(struct pt_regs *regs) +{ + sec_qc_memdbg_dump_instr("PC", regs->pc); + sec_qc_memdbg_dump_instr("LR", ptrauth_strip_insn_pac(regs->regs[30])); +} + +static void sec_qc_rst_exinfo_rvh_die_kernel_falut(void *unused, + const char *msg, unsigned long addr, unsigned long esr, + struct pt_regs *regs) +{ + sec_qc_rst_exinfo_store_extc_idx(false); + __rst_dump_instr(regs); +} + +int __qc_arch_rst_exinfo_register_rvh_die_kernel_fault(struct builder *bd) +{ + return register_trace_android_rvh_die_kernel_fault( + sec_qc_rst_exinfo_rvh_die_kernel_falut, NULL); +} + +/* FIXME: this is copied data from 'arch/arm64/kernel/traps.c' */ +static const char *__rst_exinfo_esr_class_str[] = { + [0 ... ESR_ELx_EC_MAX] = "UNRECOGNIZED EC", + [ESR_ELx_EC_UNKNOWN] = "Unknown/Uncategorized", + [ESR_ELx_EC_WFx] = "WFI/WFE", + [ESR_ELx_EC_CP15_32] = "CP15 MCR/MRC", + [ESR_ELx_EC_CP15_64] = "CP15 MCRR/MRRC", + [ESR_ELx_EC_CP14_MR] = "CP14 MCR/MRC", + [ESR_ELx_EC_CP14_LS] = "CP14 LDC/STC", + [ESR_ELx_EC_FP_ASIMD] = "ASIMD", + [ESR_ELx_EC_CP10_ID] = "CP10 MRC/VMRS", + [ESR_ELx_EC_PAC] = "PAC", + [ESR_ELx_EC_CP14_64] = "CP14 MCRR/MRRC", + [ESR_ELx_EC_BTI] = "BTI", + [ESR_ELx_EC_ILL] = "PSTATE.IL", + [ESR_ELx_EC_SVC32] = "SVC (AArch32)", + [ESR_ELx_EC_HVC32] = "HVC (AArch32)", + [ESR_ELx_EC_SMC32] = "SMC (AArch32)", + [ESR_ELx_EC_SVC64] = "SVC (AArch64)", + [ESR_ELx_EC_HVC64] = "HVC (AArch64)", + [ESR_ELx_EC_SMC64] = "SMC (AArch64)", + [ESR_ELx_EC_SYS64] = "MSR/MRS (AArch64)", + [ESR_ELx_EC_SVE] = "SVE", + [ESR_ELx_EC_ERET] = "ERET/ERETAA/ERETAB", + [ESR_ELx_EC_FPAC] = "FPAC", + [ESR_ELx_EC_SME] = "SME", + [ESR_ELx_EC_IMP_DEF] = "EL3 IMP DEF", + [ESR_ELx_EC_IABT_LOW] = "IABT (lower EL)", + [ESR_ELx_EC_IABT_CUR] = "IABT (current EL)", + [ESR_ELx_EC_PC_ALIGN] = "PC Alignment", + [ESR_ELx_EC_DABT_LOW] = "DABT (lower EL)", + [ESR_ELx_EC_DABT_CUR] = "DABT (current EL)", + [ESR_ELx_EC_SP_ALIGN] = "SP Alignment", + [ESR_ELx_EC_FP_EXC32] = "FP (AArch32)", + [ESR_ELx_EC_FP_EXC64] = "FP (AArch64)", + [ESR_ELx_EC_SERROR] = "SError", + [ESR_ELx_EC_BREAKPT_LOW] = "Breakpoint (lower EL)", + [ESR_ELx_EC_BREAKPT_CUR] = "Breakpoint (current EL)", + [ESR_ELx_EC_SOFTSTP_LOW] = "Software Step (lower EL)", + [ESR_ELx_EC_SOFTSTP_CUR] = "Software Step (current EL)", + [ESR_ELx_EC_WATCHPT_LOW] = "Watchpoint (lower EL)", + [ESR_ELx_EC_WATCHPT_CUR] = "Watchpoint (current EL)", + [ESR_ELx_EC_BKPT32] = "BKPT (AArch32)", + [ESR_ELx_EC_VECTOR32] = "Vector catch (AArch32)", + [ESR_ELx_EC_BRK64] = "BRK (AArch64)", +}; + +static const char *__rst_exinfo_esr_get_class_string(u32 esr) +{ + return __rst_exinfo_esr_class_str[ESR_ELx_EC(esr)]; +} + +int __qc_rst_exinfo_die_do_mem_abort(struct rst_exinfo_drvdata *drvdata, + enum die_val val, const struct die_args *args) +{ + const char *esr_class_string; + unsigned int esr; + const struct pt_regs *regs; + unsigned long addr; + + regs = args->regs; + if (user_mode(regs)) + return -EINVAL; + + if (strcmp(args->str, "Oops")) + return -EINVAL; + + esr = (unsigned int)args->err; + switch (esr) { + case 0 ... ESR_ELx_EC_MAX: + return -ENOENT; + } + + esr_class_string = __rst_exinfo_esr_get_class_string(esr); + if (!esr_class_string) + return -ENOENT; + + /* FIXME: arch/arm64/kernel/traps.c must be change to propagate, + * the fault address at here + */ + addr = regs->orig_x0; + + __rst_exinfo_save_fault_info(esr, esr_class_string, + regs->pc, regs->sp); + __rst_exinfo_vh_table_walk(addr); + + return 0; +} + +static void sec_qc_rst_exinfo_rvh_do_sp_pc_abort(void *unused, + unsigned long addr, unsigned long esr, + struct pt_regs *regs) +{ + __rst_exinfo_save_fault_info(esr, __rst_exinfo_esr_get_class_string(esr), + regs->pc, regs->sp); +} + +int __qc_arch_rst_exinfo_register_rvh_do_sp_pc_abort(struct builder *bd) +{ + return register_trace_android_rvh_do_sp_pc_abort( + sec_qc_rst_exinfo_rvh_do_sp_pc_abort, NULL); +} + +static void __rst_exinfo_debug_store_bug_string(const char *fmt, ...) +{ + rst_exinfo_t *rst_exinfo; + _kern_ex_info_t *kern_ex_info; + va_list args; + + if (!__qc_rst_exinfo_is_probed()) + return; + + rst_exinfo = qc_rst_exinfo->rst_exinfo; + kern_ex_info = &rst_exinfo->kern_ex_info.info; + va_start(args, fmt); + vsnprintf(kern_ex_info->bug_buf, + sizeof(kern_ex_info->bug_buf), fmt, args); + va_end(args); +} + +#define MAX_BUG_STRING_SIZE 56 + +static void sec_qc_rst_exinfo_rvh_report_bug(void *unused, + const char *file, unsigned line, unsigned long bugaddr) +{ + const char *token; + size_t length; + + if (!file) + return; + + token = strstr(file, "kernel"); + if (!token) + token = file; + + length = strlen(token); + if (length > MAX_BUG_STRING_SIZE) + __rst_exinfo_debug_store_bug_string("%s %u", + &token[length - MAX_BUG_STRING_SIZE], line); + else + __rst_exinfo_debug_store_bug_string("%s %u", token, line); +} + +int __qc_arch_rst_exinfo_register_rvh_report_bug(struct builder *bd) +{ + return register_trace_android_rvh_report_bug( + sec_qc_rst_exinfo_rvh_report_bug, NULL); +} diff --git a/drivers/samsung/debug/qcom/rst_exinfo/sec_qc_rst_exinfo.h b/drivers/samsung/debug/qcom/rst_exinfo/sec_qc_rst_exinfo.h new file mode 100644 index 000000000000..2906f14a3208 --- /dev/null +++ b/drivers/samsung/debug/qcom/rst_exinfo/sec_qc_rst_exinfo.h @@ -0,0 +1,49 @@ +#ifndef __INTERNAL__SEC_QC_RST_EXINFO_H__ +#define __INTERNAL__SEC_QC_RST_EXINFO_H__ + +#include +#include +#include +#include + +#include +#include + +#include "sec_qc_rst_exinfo_fault.h" + +struct rst_exinfo_drvdata { + struct builder bd; + struct reserved_mem *rmem; + phys_addr_t paddr; + size_t size; + rst_exinfo_t *rst_exinfo; + struct notifier_block nb_panic; + struct notifier_block nb_die; + DECLARE_HASHTABLE(die_handler_htbl, 3); +}; + +extern struct rst_exinfo_drvdata *qc_rst_exinfo; + +static __always_inline bool __qc_rst_exinfo_is_probed(void) +{ + return !!qc_rst_exinfo; +} + +/* sec_qc_rst_exinfo_main.c */ +extern void sec_qc_rst_exinfo_store_extc_idx(bool prefix); +extern void __qc_rst_exinfo_store_extc_idx(struct rst_exinfo_drvdata *drvdata, bool prefix); +extern void __qc_rst_exinfo_save_dying_msg(struct rst_exinfo_drvdata *drvdata, const char *str, const void *pc, const void *lr); +extern void __qc_arch_rst_exinfo_die_handler(struct rst_exinfo_drvdata *drvdata, enum die_val val, const struct die_args *args); + +/* sec_qc_arch_rst_exinfo_vh.c */ +extern int __qc_arch_rst_exinfo_vh_init(struct builder *bd); +extern int __qc_arch_rst_exinfo_register_rvh_die_kernel_fault(struct builder *bd); +extern int __qc_arch_rst_exinfo_register_rvh_do_sp_pc_abort(struct builder *bd); +extern int __qc_arch_rst_exinfo_register_rvh_report_bug(struct builder *bd); +extern int __qc_rst_exinfo_die_do_mem_abort(struct rst_exinfo_drvdata *drvdata, enum die_val val, const struct die_args *args); + +/* sec_qc_rst_exinfo_fault.c */ +extern void __qc_rst_exinfo_fault_print_handler(struct rst_exinfo_drvdata *drvdata, const char *msg); +extern int __qc_rst_exinfo_fault_init(struct builder *bd); + +#endif /* __INTERNAL__SEC_QC_RST_EXINFO_H__ */ diff --git a/drivers/samsung/debug/qcom/rst_exinfo/sec_qc_rst_exinfo_fault.c b/drivers/samsung/debug/qcom/rst_exinfo/sec_qc_rst_exinfo_fault.c new file mode 100644 index 000000000000..f6f13f97d908 --- /dev/null +++ b/drivers/samsung/debug/qcom/rst_exinfo/sec_qc_rst_exinfo_fault.c @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2006-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#include + +#include + +#include "sec_qc_rst_exinfo.h" + +#define MSG_TO_FAULT_HANDLER(__msg, __handler) \ + { \ + .msg = __msg, \ + .msg_len = sizeof(__msg) - 1, \ + .handler_name = #__handler, \ + .handler_code = FAULT_HANDLER_ ## __handler, \ + } + +/* TODO: cat arch/arm64/mm/fault.c | grep -E "\{[ \t]*(do_.+|early_brk64)" | perl -pe 's/^.*{\s*(\S+),.+(\".+\").+$/\tMSG_TO_FAULT_HANDLER($2, $1),/' */ +static struct msg_to_fault_handler __msg_to_fault_handler[] __ro_after_init = { + MSG_TO_FAULT_HANDLER("ttbr address size fault", do_bad), + MSG_TO_FAULT_HANDLER("level 1 address size fault", do_bad), + MSG_TO_FAULT_HANDLER("level 2 address size fault", do_bad), + MSG_TO_FAULT_HANDLER("level 3 address size fault", do_bad), + MSG_TO_FAULT_HANDLER("level 0 translation fault", do_translation_fault), + MSG_TO_FAULT_HANDLER("level 1 translation fault", do_translation_fault), + MSG_TO_FAULT_HANDLER("level 2 translation fault", do_translation_fault), + MSG_TO_FAULT_HANDLER("level 3 translation fault", do_translation_fault), + MSG_TO_FAULT_HANDLER("unknown 8", do_bad), + MSG_TO_FAULT_HANDLER("level 1 access flag fault", do_page_fault), + MSG_TO_FAULT_HANDLER("level 2 access flag fault", do_page_fault), + MSG_TO_FAULT_HANDLER("level 3 access flag fault", do_page_fault), + MSG_TO_FAULT_HANDLER("unknown 12", do_bad), + MSG_TO_FAULT_HANDLER("level 1 permission fault", do_page_fault), + MSG_TO_FAULT_HANDLER("level 2 permission fault", do_page_fault), + MSG_TO_FAULT_HANDLER("level 3 permission fault", do_page_fault), + MSG_TO_FAULT_HANDLER("synchronous external abort", do_sea), + MSG_TO_FAULT_HANDLER("synchronous tag check fault", do_tag_check_fault), + MSG_TO_FAULT_HANDLER("unknown 18", do_bad), + MSG_TO_FAULT_HANDLER("unknown 19", do_bad), + MSG_TO_FAULT_HANDLER("level 0 (translation table walk)", do_sea), + MSG_TO_FAULT_HANDLER("level 1 (translation table walk)", do_sea), + MSG_TO_FAULT_HANDLER("level 2 (translation table walk)", do_sea), + MSG_TO_FAULT_HANDLER("level 3 (translation table walk)", do_sea), + MSG_TO_FAULT_HANDLER("synchronous parity or ECC error", do_sea), + MSG_TO_FAULT_HANDLER("unknown 25", do_bad), + MSG_TO_FAULT_HANDLER("unknown 26", do_bad), + MSG_TO_FAULT_HANDLER("unknown 27", do_bad), + MSG_TO_FAULT_HANDLER("level 0 synchronous parity error (translation table walk)", do_sea), + MSG_TO_FAULT_HANDLER("level 1 synchronous parity error (translation table walk)", do_sea), + MSG_TO_FAULT_HANDLER("level 2 synchronous parity error (translation table walk)", do_sea), + MSG_TO_FAULT_HANDLER("level 3 synchronous parity error (translation table walk)", do_sea), + MSG_TO_FAULT_HANDLER("unknown 32", do_bad), + MSG_TO_FAULT_HANDLER("alignment fault", do_alignment_fault), + MSG_TO_FAULT_HANDLER("unknown 34", do_bad), + MSG_TO_FAULT_HANDLER("unknown 35", do_bad), + MSG_TO_FAULT_HANDLER("unknown 36", do_bad), + MSG_TO_FAULT_HANDLER("unknown 37", do_bad), + MSG_TO_FAULT_HANDLER("unknown 38", do_bad), + MSG_TO_FAULT_HANDLER("unknown 39", do_bad), + MSG_TO_FAULT_HANDLER("unknown 40", do_bad), + MSG_TO_FAULT_HANDLER("unknown 41", do_bad), + MSG_TO_FAULT_HANDLER("unknown 42", do_bad), + MSG_TO_FAULT_HANDLER("unknown 43", do_bad), + MSG_TO_FAULT_HANDLER("unknown 44", do_bad), + MSG_TO_FAULT_HANDLER("unknown 45", do_bad), + MSG_TO_FAULT_HANDLER("unknown 46", do_bad), + MSG_TO_FAULT_HANDLER("unknown 47", do_bad), + MSG_TO_FAULT_HANDLER("TLB conflict abort", do_bad), + MSG_TO_FAULT_HANDLER("Unsupported atomic hardware update fault", do_bad), + MSG_TO_FAULT_HANDLER("unknown 50", do_bad), + MSG_TO_FAULT_HANDLER("unknown 51", do_bad), + MSG_TO_FAULT_HANDLER("implementation fault (lockdown abort)", do_bad), + MSG_TO_FAULT_HANDLER("implementation fault (unsupported exclusive)", do_bad), + MSG_TO_FAULT_HANDLER("unknown 54", do_bad), + MSG_TO_FAULT_HANDLER("unknown 55", do_bad), + MSG_TO_FAULT_HANDLER("unknown 56", do_bad), + MSG_TO_FAULT_HANDLER("unknown 57", do_bad), + MSG_TO_FAULT_HANDLER("unknown 58", do_bad), + MSG_TO_FAULT_HANDLER("unknown 59", do_bad), + MSG_TO_FAULT_HANDLER("unknown 60", do_bad), + MSG_TO_FAULT_HANDLER("section domain fault", do_bad), + MSG_TO_FAULT_HANDLER("page domain fault", do_bad), + MSG_TO_FAULT_HANDLER("unknown 63", do_bad), + MSG_TO_FAULT_HANDLER("hardware breakpoint", do_bad), + MSG_TO_FAULT_HANDLER("hardware single-step", do_bad), + MSG_TO_FAULT_HANDLER("hardware watchpoint", do_bad), + MSG_TO_FAULT_HANDLER("unknown 3", do_bad), + MSG_TO_FAULT_HANDLER("aarch32 BKPT", do_bad), + MSG_TO_FAULT_HANDLER("aarch32 vector catch", do_bad), + MSG_TO_FAULT_HANDLER("aarch64 BRK", early_brk64), + MSG_TO_FAULT_HANDLER("unknown 7", do_bad), +}; + +static u32 __simple_hash(const char *val) +{ + u32 hash = 0; + + while (*val++) + hash += (u32)*val; + + return hash; +} + +__ss_static struct msg_to_fault_handler *__rst_exinfo_find_handler( + struct rst_exinfo_drvdata *drvdata, const char *msg) +{ + struct msg_to_fault_handler *h; + size_t msg_len = strlen(msg); + u32 key = __simple_hash(msg); + + hash_for_each_possible(drvdata->die_handler_htbl, h, node, key) { + if (h->msg_len != msg_len) + continue; + + if (!strncmp(h->msg, msg, msg_len)) + return h; + } + + return ERR_PTR(-ENOENT); +} + +void __qc_rst_exinfo_fault_print_handler(struct rst_exinfo_drvdata *drvdata, + const char *msg) +{ + struct device *dev = drvdata->bd.dev; + struct msg_to_fault_handler *h = __rst_exinfo_find_handler(drvdata, msg); + + if (IS_ERR_OR_NULL(h)) { + dev_info(dev, "fault handler : unknown\n"); + return; + } + + dev_info(dev, "fault hanlder : %s (%u)\n", h->handler_name, h->handler_code); +} + +int __qc_rst_exinfo_fault_init(struct builder *bd) +{ + struct rst_exinfo_drvdata *drvdata = + container_of(bd, struct rst_exinfo_drvdata, bd); + size_t i; + + hash_init(drvdata->die_handler_htbl); + + for (i = 0; i < ARRAY_SIZE(__msg_to_fault_handler); i++) { + struct msg_to_fault_handler *h = &__msg_to_fault_handler[i]; + u32 key = __simple_hash(h->msg); + + INIT_HLIST_NODE(&h->node); + hash_add(drvdata->die_handler_htbl, &h->node, key); + } + + return 0; +} diff --git a/drivers/samsung/debug/qcom/rst_exinfo/sec_qc_rst_exinfo_fault.h b/drivers/samsung/debug/qcom/rst_exinfo/sec_qc_rst_exinfo_fault.h new file mode 100644 index 000000000000..e2c2ecfe5f46 --- /dev/null +++ b/drivers/samsung/debug/qcom/rst_exinfo/sec_qc_rst_exinfo_fault.h @@ -0,0 +1,24 @@ +#ifndef __INTERNAL_SEC_QC_RST_EXINFO_DIE_H__ +#define __INTERNAL_SEC_QC_RST_EXINFO_DIE_H__ + +enum { + FAULT_HANDLER_do_bad, + FAULT_HANDLER_do_translation_fault, + FAULT_HANDLER_do_page_fault, + FAULT_HANDLER_do_tag_check_fault, + FAULT_HANDLER_do_sea, + FAULT_HANDLER_do_alignment_fault, + FAULT_HANDLER_early_brk64, + /* */ + FAULT_HANDLER_UNKNOWN, +}; + +struct msg_to_fault_handler { + struct hlist_node node; + const char *msg; + size_t msg_len; + const char *handler_name; + unsigned int handler_code; +}; + +#endif /* __INTERNAL_SEC_QC_RST_EXINFO_DIE_H__ */ diff --git a/drivers/samsung/debug/qcom/rst_exinfo/sec_qc_rst_exinfo_main.c b/drivers/samsung/debug/qcom/rst_exinfo/sec_qc_rst_exinfo_main.c new file mode 100644 index 000000000000..838eacc8b266 --- /dev/null +++ b/drivers/samsung/debug/qcom/rst_exinfo/sec_qc_rst_exinfo_main.c @@ -0,0 +1,450 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2006-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "sec_qc_rst_exinfo.h" + +struct rst_exinfo_drvdata *qc_rst_exinfo; + +static noinline int __rst_exinfo_parse_dt_memory_region(struct builder *bd, + struct device_node *np) +{ + struct rst_exinfo_drvdata *drvdata = + container_of(bd, struct rst_exinfo_drvdata, bd); + struct device *dev = bd->dev; + struct device_node *mem_np; + struct reserved_mem *rmem; + + mem_np = of_parse_phandle(np, "memory-region", 0); + if (!mem_np) + return -EINVAL; + + rmem = of_reserved_mem_lookup(mem_np); + if (!rmem) { + dev_warn(dev, "failed to get a reserved memory (%s)\n", + mem_np->name); + return -EFAULT; + } + + drvdata->rmem = rmem; + + return 0; +} + +static bool __rst_exinfo_is_in_reserved_mem_bound( + const struct reserved_mem *rmem, + phys_addr_t base, phys_addr_t size) +{ + phys_addr_t rmem_base = rmem->base; + phys_addr_t rmem_end = rmem_base + rmem->size - 1; + phys_addr_t end = base + size - 1; + + if ((base >= rmem_base) && (end <= rmem_end)) + return true; + + return false; +} + +static int __rst_exinfo_use_partial_reserved_mem( + struct rst_exinfo_drvdata *drvdata, struct device_node *np) +{ + struct reserved_mem *rmem = drvdata->rmem; + phys_addr_t base; + phys_addr_t size; + int err; + + err = sec_of_parse_reg_prop(np, &base, &size); + if (err) + return err; + + if (!__rst_exinfo_is_in_reserved_mem_bound(rmem, base, size)) + return -ERANGE; + + drvdata->paddr = base; + drvdata->size = size; + + return 0; +} + +static int __rst_exinfo_use_entire_reserved_mem( + struct rst_exinfo_drvdata *drvdata) +{ + struct reserved_mem *rmem = drvdata->rmem; + + drvdata->paddr = rmem->base; + drvdata->size = rmem->size; + + return 0; +} + +static noinline int __rst_exinfo_parse_dt_partial_reserved_mem(struct builder *bd, + struct device_node *np) +{ + struct rst_exinfo_drvdata *drvdata = + container_of(bd, struct rst_exinfo_drvdata, bd); + int err; + + if (of_property_read_bool(np, "sec,use-partial_reserved_mem")) + err = __rst_exinfo_use_partial_reserved_mem(drvdata, np); + else + err = __rst_exinfo_use_entire_reserved_mem(drvdata); + + if (err) + return -EFAULT; + + return 0; +} + +static void *__rst_exinfo_ioremap(struct rst_exinfo_drvdata *drvdata) +{ + struct device *dev = drvdata->bd.dev; + +#if IS_ENABLED(CONFIG_HAS_IOMEM) + return devm_ioremap_wc(dev, drvdata->paddr, drvdata->size); +#else + dev = dev; + return ioremap(drvdata->paddr, drvdata->size); +#endif +} + +static noinline int __rst_exinfo_parse_dt_test_no_map(struct builder *bd, + struct device_node *np) +{ + struct rst_exinfo_drvdata *drvdata = + container_of(bd, struct rst_exinfo_drvdata, bd); + struct device *dev = bd->dev; + struct device_node *mem_np; + rst_exinfo_t *rst_exinfo; + + mem_np = of_parse_phandle(np, "memory-region", 0); + if (!mem_np) + return -EINVAL; + + if (!of_property_read_bool(mem_np, "no-map")) + rst_exinfo = phys_to_virt(drvdata->paddr); + else + rst_exinfo = __rst_exinfo_ioremap(drvdata); + + drvdata->rst_exinfo = rst_exinfo; + + dev_info(dev, "ex info phy=%pa, size=0x%zx\n", + &drvdata->paddr, drvdata->size); + + return 0; +} + +__ss_static noinline int __rst_exinfo_dt_die_notifier_priority(struct builder *bd, + struct device_node *np) +{ + struct rst_exinfo_drvdata *drvdata = + container_of(bd, struct rst_exinfo_drvdata, bd); + s32 priority; + int err; + + err = of_property_read_s32(np, "sec,die_notifier-priority", + &priority); + if (err) + return -EINVAL; + + drvdata->nb_die.priority = (int)priority; + + return 0; +} + +__ss_static noinline int __rst_exinfo_dt_panic_notifier_priority(struct builder *bd, + struct device_node *np) +{ + struct rst_exinfo_drvdata *drvdata = + container_of(bd, struct rst_exinfo_drvdata, bd); + s32 priority; + int err; + + err = of_property_read_s32(np, "sec,panic_notifier-priority", + &priority); + if (err) + return -EINVAL; + + drvdata->nb_panic.priority = (int)priority; + + return 0; +} + +static const struct dt_builder __rst_exinfo_dt_builder[] = { + DT_BUILDER(__rst_exinfo_parse_dt_memory_region), + DT_BUILDER(__rst_exinfo_parse_dt_partial_reserved_mem), + DT_BUILDER(__rst_exinfo_dt_die_notifier_priority), + DT_BUILDER(__rst_exinfo_dt_panic_notifier_priority), + DT_BUILDER(__rst_exinfo_parse_dt_test_no_map), +}; + +static noinline int __rst_exinfo_parse_dt(struct builder *bd) +{ + return sec_director_parse_dt(bd, __rst_exinfo_dt_builder, + ARRAY_SIZE(__rst_exinfo_dt_builder)); +} + +__ss_static noinline int __rst_exinfo_init_panic_extra_info(struct builder *bd) +{ + struct rst_exinfo_drvdata *drvdata = + container_of(bd, struct rst_exinfo_drvdata, bd); + struct device *dev = bd->dev; + rst_exinfo_t *rst_exinfo = drvdata->rst_exinfo; + _kern_ex_info_t *kern_ex_info = &rst_exinfo->kern_ex_info.info; + + memset(&rst_exinfo->kern_ex_info, 0x0, + sizeof(rst_exinfo->kern_ex_info)); + kern_ex_info->cpu = -1; + + dev_info(dev, "ex_info memory initialized size[%ld]\n", + sizeof(kern_exinfo_t)); + + return 0; +} + +void __qc_rst_exinfo_store_extc_idx(struct rst_exinfo_drvdata *drvdata, + bool prefix) +{ + rst_exinfo_t *rst_exinfo = drvdata->rst_exinfo; + _kern_ex_info_t *kern_ex_info = &rst_exinfo->kern_ex_info.info; + const struct sec_log_buf_head *s_log_buf; + + if (kern_ex_info->extc_idx != 0) + return; + + s_log_buf = sec_log_buf_get_header(); + if (IS_ERR(s_log_buf)) + return; + + kern_ex_info->extc_idx = s_log_buf->idx; + + if (prefix) + kern_ex_info->extc_idx += SEC_DEBUG_RESET_EXTRC_SIZE; +} + +void sec_qc_rst_exinfo_store_extc_idx(bool prefix) +{ + if (!__qc_rst_exinfo_is_probed()) + return; + + __qc_rst_exinfo_store_extc_idx(qc_rst_exinfo, prefix); +} + +void __qc_rst_exinfo_save_dying_msg(struct rst_exinfo_drvdata *drvdata, + const char *str, const void *pc, const void *lr) +{ + rst_exinfo_t *rst_exinfo = drvdata->rst_exinfo; + _kern_ex_info_t *kern_ex_info = &rst_exinfo->kern_ex_info.info; + ssize_t len; + char *msg; + + if (kern_ex_info->cpu != -1) + return; + + kern_ex_info->cpu = smp_processor_id(); + snprintf(kern_ex_info->task_name, sizeof(kern_ex_info->task_name), + "%s", current->comm); + kern_ex_info->ktime = local_clock(); + snprintf(kern_ex_info->pc, sizeof(kern_ex_info->pc), "%pS", pc); + snprintf(kern_ex_info->lr, sizeof(kern_ex_info->lr), "%pS", lr); + + msg = kern_ex_info->panic_buf; + len = scnprintf(msg, sizeof(kern_ex_info->panic_buf), "%s", str); + if ((len >= 1) && (msg[len - 1] == '\n')) + msg[len - 1] = '\0'; +} + +void __weak __qc_arch_rst_exinfo_die_handler(struct rst_exinfo_drvdata *drvdata, + enum die_val val, const struct die_args *args) +{ +} + +static int sec_qc_rst_exinfo_die_handler(struct notifier_block *this, + unsigned long l, void *data) +{ + struct rst_exinfo_drvdata *drvdata = + container_of(this, struct rst_exinfo_drvdata, nb_die); + enum die_val val = l; + struct die_args *args = data; + + __qc_arch_rst_exinfo_die_handler(drvdata, val, args); + + return NOTIFY_OK; +} + +static int __rst_exinfo_register_die_notifier(struct builder *bd) +{ + struct rst_exinfo_drvdata *drvdata = + container_of(bd, struct rst_exinfo_drvdata, bd); + + drvdata->nb_die.notifier_call = sec_qc_rst_exinfo_die_handler; + + return register_die_notifier(&drvdata->nb_die); +} + +static void __rst_exinfo_unregister_die_notifier(struct builder *bd) +{ + struct rst_exinfo_drvdata *drvdata = + container_of(bd, struct rst_exinfo_drvdata, bd); + + unregister_die_notifier(&drvdata->nb_die); +} + +static int sec_qc_rst_exinfo_panic_handler(struct notifier_block *this, + unsigned long val, void *data) +{ + struct rst_exinfo_drvdata *drvdata = + container_of(this, struct rst_exinfo_drvdata, nb_panic); + const void *pc; + const void *lr; + const char *str; + + pc = __builtin_return_address(2); + lr = __builtin_return_address(3); + str = data; + + __qc_rst_exinfo_store_extc_idx(drvdata, false); + __qc_rst_exinfo_save_dying_msg(drvdata, str, pc, lr); + + return NOTIFY_OK; +} + +static int __rst_exinfo_register_panic_notifier(struct builder *bd) +{ + struct rst_exinfo_drvdata *drvdata = + container_of(bd, struct rst_exinfo_drvdata, bd); + + drvdata->nb_panic.notifier_call = sec_qc_rst_exinfo_panic_handler; + + return atomic_notifier_chain_register(&panic_notifier_list, + &drvdata->nb_panic); +} + +static void __rst_exinfo_unregister_panic_notifier(struct builder *bd) +{ + struct rst_exinfo_drvdata *drvdata = + container_of(bd, struct rst_exinfo_drvdata, bd); + + atomic_notifier_chain_unregister(&panic_notifier_list, + &drvdata->nb_panic); +} + +static noinline int __rst_exinfo_probe_epilog(struct builder *bd) +{ + struct rst_exinfo_drvdata *drvdata = + container_of(bd, struct rst_exinfo_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + qc_rst_exinfo = drvdata; + + return 0; +} + +static noinline void __rst_exinfo_remove_prolog(struct builder *bd) +{ + /* FIXME: This is not a graceful exit. */ + qc_rst_exinfo = NULL; +} + +static const struct dev_builder __rst_exinfo_dev_builder[] = { + DEVICE_BUILDER(__rst_exinfo_parse_dt, NULL), + DEVICE_BUILDER(__rst_exinfo_init_panic_extra_info, NULL), + DEVICE_BUILDER(__rst_exinfo_register_die_notifier, + __rst_exinfo_unregister_die_notifier), + DEVICE_BUILDER(__rst_exinfo_register_panic_notifier, + __rst_exinfo_unregister_panic_notifier), + DEVICE_BUILDER(__qc_arch_rst_exinfo_vh_init, NULL), + DEVICE_BUILDER(__qc_arch_rst_exinfo_register_rvh_do_sp_pc_abort, NULL), + DEVICE_BUILDER(__qc_arch_rst_exinfo_register_rvh_report_bug, NULL), + DEVICE_BUILDER(__qc_arch_rst_exinfo_register_rvh_die_kernel_fault, NULL), + DEVICE_BUILDER(__qc_rst_exinfo_fault_init, NULL), + DEVICE_BUILDER(__rst_exinfo_probe_epilog, __rst_exinfo_remove_prolog), +}; + +static int __rst_exinfo_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct rst_exinfo_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __rst_exinfo_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct rst_exinfo_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static int sec_qc_rst_exinfo_probe(struct platform_device *pdev) +{ + return __rst_exinfo_probe(pdev, __rst_exinfo_dev_builder, + ARRAY_SIZE(__rst_exinfo_dev_builder)); +} + +static int sec_qc_rst_exinfo_remove(struct platform_device *pdev) +{ + return __rst_exinfo_remove(pdev, __rst_exinfo_dev_builder, + ARRAY_SIZE(__rst_exinfo_dev_builder)); +} + +static const struct of_device_id sec_qc_rst_exinfo_match_table[] = { + { .compatible = "samsung,qcom-rst_exinfo" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_qc_rst_exinfo_match_table); + +static struct platform_driver sec_qc_rst_exinfo = { + .driver = { + .name = "sec,qc-rst_exinfo", + .of_match_table = of_match_ptr(sec_qc_rst_exinfo_match_table), + }, + .probe = sec_qc_rst_exinfo_probe, + .remove = sec_qc_rst_exinfo_remove, +}; + +static int sec_qc_rst_exinfo_init(void) +{ + return platform_driver_register(&sec_qc_rst_exinfo); +} +module_init(sec_qc_rst_exinfo_init); + +static void sec_qc_rst_exinfo_exit(void) +{ + platform_driver_unregister(&sec_qc_rst_exinfo); +} +module_exit(sec_qc_rst_exinfo_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("Reset extra info for Qualcomm based devices"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/debug/qcom/smem/Kconfig b/drivers/samsung/debug/qcom/smem/Kconfig new file mode 100644 index 000000000000..e41613cdec6e --- /dev/null +++ b/drivers/samsung/debug/qcom/smem/Kconfig @@ -0,0 +1,4 @@ +config SEC_QC_SMEM + tristate "SEC SMEM for Qualcomm based devices" + help + TODO: help is not ready. diff --git a/drivers/samsung/debug/qcom/smem/Makefile b/drivers/samsung/debug/qcom/smem/Makefile new file mode 100644 index 000000000000..12cbc0d3e2f6 --- /dev/null +++ b/drivers/samsung/debug/qcom/smem/Makefile @@ -0,0 +1,8 @@ +obj-$(CONFIG_SEC_QC_SMEM) += sec_qc_smem.o +sec_qc_smem-objs := sec_qc_smem_main.o \ + sec_qc_smem_id_vendor1.o \ + sec_qc_smem_id_vendor1_v5.o \ + sec_qc_smem_id_vendor1_v6.o \ + sec_qc_smem_id_vendor1_v7.o \ + sec_qc_smem_lpddr.o \ + sec_qc_smem_logger.o diff --git a/drivers/samsung/debug/qcom/smem/sec_qc_smem.h b/drivers/samsung/debug/qcom/smem/sec_qc_smem.h new file mode 100644 index 000000000000..cfe9375980d1 --- /dev/null +++ b/drivers/samsung/debug/qcom/smem/sec_qc_smem.h @@ -0,0 +1,75 @@ +#ifndef __INTERNAL__SEC_QC_SMEM_H__ +#define __INTERNAL__SEC_QC_SMEM_H__ + +#include + +#include + +#include +#include + +typedef struct { + uint64_t magic; + uint64_t version; +} sec_smem_header_t; + +#include "sec_qc_smem_external.h" +#include "sec_qc_smem_id_vendor0.h" +#include "sec_qc_smem_id_vendor1.h" + +typedef struct { + uint64_t ktime; + uint64_t qtime; + uint64_t rate; +} apps_clk_log_t; + +#define MAX_CLK_LOG_CNT 10 + +typedef struct { + uint32_t max_cnt; + uint32_t index; + apps_clk_log_t log[MAX_CLK_LOG_CNT]; +} cpuclk_log_t; + +#define MAX_CLUSTER_NUM 4 + +struct qc_smem_drvdata { + struct builder bd; + size_t smem_offset_vendor0; + size_t smem_offset_vendor1; + sec_smem_id_vendor0_v2_t *vendor0; + unsigned int vendor0_ver; + union { + void *vendor1; + /* TODO: debugging purpose only. + * Do NOT access below variables in the driver code. + * 'vendor1' can only be accessed by 'vendor1_ops' to prevent + * code smells like 'Change preventers'. + */ + const sec_smem_id_vendor1_v5_t * const __vendor1_v5; + const sec_smem_id_vendor1_v6_t * const __vendor1_v6; + const sec_smem_id_vendor1_v7_t * const __vendor1_v7; + }; + unsigned int vendor1_ver; + const struct vendor1_operations *vendor1_ops; + ap_health_t *ap_health; + cpuclk_log_t cpuclk_log[MAX_CLUSTER_NUM]; + struct notifier_block nb_cpuclk_log; + struct notifier_block nb_l3clk_log; +}; + +extern struct qc_smem_drvdata *qc_smem; + +static __always_inline bool __qc_smem_is_probed(void) +{ + return !!qc_smem; +} + +/* sec_qc_smem_logger.c */ +extern int __qc_smem_clk_osm_probe(struct builder *bd); +extern int __qc_smem_register_nb_cpuclk_log(struct builder *bd); +extern void __qc_smem_unregister_nb_cpuclk_log(struct builder *bd); +extern int __qc_smem_register_nb_l3clk_log(struct builder *bd); +extern void __qc_smem_unregister_nb_l3clk_log(struct builder *bd); + +#endif /* __INTERNAL__SEC_QC_SMEM_H__ */ diff --git a/drivers/samsung/debug/qcom/smem/sec_qc_smem_external.h b/drivers/samsung/debug/qcom/smem/sec_qc_smem_external.h new file mode 100644 index 000000000000..44d51e022c3e --- /dev/null +++ b/drivers/samsung/debug/qcom/smem/sec_qc_smem_external.h @@ -0,0 +1,20 @@ +#ifndef __INTERNAL__SEC_QC_SMEM_EXTERNAL_H__ +#define __INTERNAL__SEC_QC_SMEM_EXTERNAL_H__ + +/* implemented @ drivers/cpufreq/qcom-cpufreq-hw.c */ +#if IS_ENABLED(CONFIG_ARM_QCOM_CPUFREQ_HW) +extern int qcom_cpufreq_hw_target_index_register_notifier(struct notifier_block *nb); +extern int qcom_cpufreq_hw_target_index_unregister_notifier(struct notifier_block *nb); +#else +#include +#endif + +/* implemented @ drivers/interconnect/qcom/epss-l3.c */ +#if IS_ENABLED(CONFIG_INTERCONNECT_QCOM_EPSS_L3) +extern int qcom_icc_epss_l3_cpu_set_register_notifier(struct notifier_block *nb); +extern int qcom_icc_epss_l3_cpu_set_unregister_notifier(struct notifier_block *nb); +#else +#include +#endif + +#endif /* __INTERNAL__SEC_QC_SMEM_EXTERNAL_H__ */ diff --git a/drivers/samsung/debug/qcom/smem/sec_qc_smem_id_vendor0.h b/drivers/samsung/debug/qcom/smem/sec_qc_smem_id_vendor0.h new file mode 100644 index 000000000000..8ec044aa6421 --- /dev/null +++ b/drivers/samsung/debug/qcom/smem/sec_qc_smem_id_vendor0.h @@ -0,0 +1,6 @@ +#ifndef __INTERNAL__SEC_QC_SMEM_ID_VENDOR0_H__ +#define __INTERNAL__SEC_QC_SMEM_ID_VENDOR0_H__ + +#include "sec_qc_smem_id_vendor0_type.h" + +#endif /* __INTERNAL__SEC_QC_SMEM_ID_VENDOR0_H__ */ diff --git a/drivers/samsung/debug/qcom/smem/sec_qc_smem_id_vendor0_type.h b/drivers/samsung/debug/qcom/smem/sec_qc_smem_id_vendor0_type.h new file mode 100644 index 000000000000..299024e78def --- /dev/null +++ b/drivers/samsung/debug/qcom/smem/sec_qc_smem_id_vendor0_type.h @@ -0,0 +1,36 @@ +#ifndef __INTERNAL__SEC_QC_SMEM_ID_VENDOR0_TYPE_H__ +#define __INTERNAL__SEC_QC_SMEM_ID_VENDOR0_TYPE_H__ + +/* For SMEM_ID_VENDOR0 */ +#define SMEM_VEN0_MAGIC 0x304E4556304E4556 + +#define DDR_IFNO_REVISION_ID1 8 +#define DDR_IFNO_REVISION_ID2 16 +#define DDR_IFNO_TOTAL_DENSITY 24 + +typedef struct{ + uint64_t afe_rx_good; + uint64_t afe_rx_mute; + uint64_t afe_tx_good; + uint64_t afe_tx_mute; +} smem_afe_log_t; + +typedef struct{ + uint64_t reserved[5]; +} smem_afe_ext_t; + +typedef struct { + uint32_t ddr_vendor; + uint32_t reserved; + smem_afe_log_t afe_log; +} sec_smem_id_vendor0_v1_t; + +typedef struct { + sec_smem_header_t header; + uint32_t ddr_vendor; + uint32_t reserved; + smem_afe_log_t afe_log; + smem_afe_ext_t afe_ext; +} sec_smem_id_vendor0_v2_t; + +#endif /* __INTERNAL__SEC_QC_SMEM_ID_VENDOR0_TYPE_H__ */ \ No newline at end of file diff --git a/drivers/samsung/debug/qcom/smem/sec_qc_smem_id_vendor1.c b/drivers/samsung/debug/qcom/smem/sec_qc_smem_id_vendor1.c new file mode 100644 index 000000000000..9d606e38d409 --- /dev/null +++ b/drivers/samsung/debug/qcom/smem/sec_qc_smem_id_vendor1.c @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2015-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include + +#include "sec_qc_smem.h" + +#define __smem_id_vendor1_get_member_ptr(__drvdata, __member) \ +({ \ + const struct vendor1_operations *ops = __drvdata->vendor1_ops; \ + void *member_ptr; \ + member_ptr = ops->__member ? \ + ops->__member(__drvdata->vendor1) : ERR_PTR(-ENOENT); \ + member_ptr; \ +}) + +sec_smem_header_t *__qc_smem_id_vendor1_get_header(struct qc_smem_drvdata *drvdata) +{ + return __smem_id_vendor1_get_member_ptr(drvdata, header); +} + +sec_smem_id_vendor1_v2_t *__qc_smem_id_vendor1_get_ven1_v2( + struct qc_smem_drvdata *drvdata) +{ + return __smem_id_vendor1_get_member_ptr(drvdata, ven1_v2); +} + +sec_smem_id_vendor1_share_t *__qc_smem_id_vendor1_get_share( + struct qc_smem_drvdata *drvdata) +{ + return __smem_id_vendor1_get_member_ptr(drvdata, share); +} + +smem_ddr_stat_t *__qc_smem_id_vendor1_get_ddr_stat(struct qc_smem_drvdata *drvdata) +{ + return __smem_id_vendor1_get_member_ptr(drvdata, ddr_stat); +} + +void **__qc_smem_id_vendor1_get_ap_health(struct qc_smem_drvdata *drvdata) +{ + return __smem_id_vendor1_get_member_ptr(drvdata, ap_health); +} + +smem_apps_stat_t *__qc_smem_id_vendor1_get_apps_stat( + struct qc_smem_drvdata *drvdata) +{ + return __smem_id_vendor1_get_member_ptr(drvdata, apps_stat); +} + +smem_vreg_stat_t *__qc_smem_id_vendor1_get_vreg_stat( + struct qc_smem_drvdata *drvdata) +{ + return __smem_id_vendor1_get_member_ptr(drvdata, vreg_stat); +} + +ddr_train_t *__qc_smem_id_vendor1_get_ddr_training( + struct qc_smem_drvdata *drvdata) +{ + return __smem_id_vendor1_get_member_ptr(drvdata, ddr_training); +} + +static const struct vendor1_operations * + __smem_vendor1_ops_creator(struct qc_smem_drvdata *drvdata) +{ + struct device *dev = drvdata->bd.dev; + const struct vendor1_operations *ops; + unsigned int vendor1_ver = drvdata->vendor1_ver; + + switch (vendor1_ver) { + case 5: + ops = __qc_smem_vendor1_v5_ops_creator(); + break; + case 6: + ops = __qc_smem_vendor1_v6_ops_creator(); + break; + case 7: + ops = __qc_smem_vendor1_v7_ops_creator(); + break; + default: + ops = __qc_smem_vendor1_v7_ops_creator(); + dev_warn(dev, "v%u is not supported or deprecated. use default\n", + vendor1_ver); + break; + } + + return ops; +} + +static void __smem_vendor1_operations_factory(struct qc_smem_drvdata *drvdata) +{ + drvdata->vendor1_ops = __smem_vendor1_ops_creator(drvdata); +} + +int __qc_smem_id_vendor1_init(struct builder *bd) +{ + struct qc_smem_drvdata *drvdata = + container_of(bd, struct qc_smem_drvdata, bd); + void **ap_health; + + __smem_vendor1_operations_factory(drvdata); + + ap_health = __qc_smem_id_vendor1_get_ap_health(drvdata); + if (!IS_ERR_OR_NULL(ap_health)) + *ap_health = (void *)virt_to_phys(drvdata->ap_health); + + return 0; +} diff --git a/drivers/samsung/debug/qcom/smem/sec_qc_smem_id_vendor1.h b/drivers/samsung/debug/qcom/smem/sec_qc_smem_id_vendor1.h new file mode 100644 index 000000000000..e42c7a4e6e74 --- /dev/null +++ b/drivers/samsung/debug/qcom/smem/sec_qc_smem_id_vendor1.h @@ -0,0 +1,35 @@ +#ifndef __INTERNAL__SEC_QC_SMEM_ID_VENDOR1_H__ +#define __INTERNAL__SEC_QC_SMEM_ID_VENDOR1_H__ + +#include "sec_qc_smem_id_vendor1_type.h" + +struct qc_smem_drvdata; + +struct vendor1_operations { + sec_smem_header_t *(*header)(void *vendor1); + sec_smem_id_vendor1_v2_t *(*ven1_v2)(void *vendor1); + sec_smem_id_vendor1_share_t *(*share)(void *vendor1); + smem_ddr_stat_t *(*ddr_stat)(void *vendor1); + void **(*ap_health)(void *vendor1); + smem_apps_stat_t *(*apps_stat)(void *vendor1); + smem_vreg_stat_t *(*vreg_stat)(void *vendor1); + ddr_train_t *(*ddr_training)(void *vendor1); +}; + +/* Concrete creators for vendor1_ops */ +extern const struct vendor1_operations *__qc_smem_vendor1_v5_ops_creator(void); +extern const struct vendor1_operations *__qc_smem_vendor1_v6_ops_creator(void); +extern const struct vendor1_operations *__qc_smem_vendor1_v7_ops_creator(void); + +extern sec_smem_header_t *__qc_smem_id_vendor1_get_header(struct qc_smem_drvdata *drvdata); +extern sec_smem_id_vendor1_v2_t *__qc_smem_id_vendor1_get_ven1_v2(struct qc_smem_drvdata *drvdata); +extern sec_smem_id_vendor1_share_t *__qc_smem_id_vendor1_get_share(struct qc_smem_drvdata *drvdata); +extern smem_ddr_stat_t *__qc_smem_id_vendor1_get_ddr_stat(struct qc_smem_drvdata *drvdata); +extern void **__qc_smem_id_vendor1_get_ap_health(struct qc_smem_drvdata *drvdata); +extern smem_apps_stat_t *__qc_smem_id_vendor1_get_apps_stat(struct qc_smem_drvdata *drvdata); +extern smem_vreg_stat_t *__qc_smem_id_vendor1_get_vreg_stat(struct qc_smem_drvdata *drvdata); +extern ddr_train_t *__qc_smem_id_vendor1_get_ddr_training(struct qc_smem_drvdata *drvdata); + +extern int __qc_smem_id_vendor1_init(struct builder *bd); + +#endif /* __INTERNAL__SEC_QC_SMEM_ID_VENDOR1_H__ */ \ No newline at end of file diff --git a/drivers/samsung/debug/qcom/smem/sec_qc_smem_id_vendor1_type.h b/drivers/samsung/debug/qcom/smem/sec_qc_smem_id_vendor1_type.h new file mode 100644 index 000000000000..8c3e4f5b8aa9 --- /dev/null +++ b/drivers/samsung/debug/qcom/smem/sec_qc_smem_id_vendor1_type.h @@ -0,0 +1,137 @@ +#ifndef __INTERNAL__SEC_QC_SMEM_ID_VENDOR1_TYPE_H__ +#define __INTERNAL__SEC_QC_SMEM_ID_VENDOR1_TYPE_H__ + +/* For SMEM_ID_VENDOR1 */ +#define SMEM_VEN1_MAGIC 0x314E4556314E4556 + +typedef struct { + uint64_t hw_rev; +} sec_smem_id_vendor1_v1_t; + +typedef struct { + uint64_t hw_rev; + uint64_t ap_suspended; +} sec_smem_id_vendor1_v2_t; + +#define NUM_CH 4 +#define NUM_CS 2 +#define NUM_DQ_PCH 2 + +typedef struct { + uint8_t tDQSCK[NUM_CH][NUM_CS][NUM_DQ_PCH]; +} ddr_rcw_t; + +typedef struct { + uint8_t coarse_cdc[NUM_CH][NUM_CS][NUM_DQ_PCH]; + uint8_t fine_cdc[NUM_CH][NUM_CS][NUM_DQ_PCH]; +} ddr_wr_dqdqs_t; + +typedef struct { + /* WR */ + uint8_t wr_pr_width[NUM_CH][NUM_CS][NUM_DQ_PCH]; + uint8_t wr_min_eye_height[NUM_CH][NUM_CS]; + uint8_t wr_best_vref[NUM_CH][NUM_CS]; + uint8_t wr_vmax_to_vmid[NUM_CH][NUM_CS][NUM_DQ_PCH]; + uint8_t wr_vmid_to_vmin[NUM_CH][NUM_CS][NUM_DQ_PCH]; + /* RD */ + uint8_t rd_pr_width[NUM_CH][NUM_CS][NUM_DQ_PCH]; + uint8_t rd_min_eye_height[NUM_CH][NUM_CS]; + uint8_t rd_best_vref[NUM_CH][NUM_CS][NUM_DQ_PCH]; + /* DCC */ + uint8_t dq_dcc_abs[NUM_CH][NUM_CS][NUM_DQ_PCH]; + uint8_t dqs_dcc_adj[NUM_CH][NUM_DQ_PCH]; + + uint16_t small_eye_detected; +} ddr_dqdqs_eye_t; + +typedef struct { + uint32_t version; + ddr_rcw_t rcw; + ddr_wr_dqdqs_t wr_dqdqs; + ddr_dqdqs_eye_t dqdqs_eye; +} ddr_train_t; + +typedef struct { + uint64_t num; + void * nIndex __attribute__((aligned(8))); + void * DDRLogs __attribute__((aligned(8))); + void * DDR_STRUCT __attribute__((aligned(8))); +} smem_ddr_stat_t; + +typedef struct { + sec_smem_header_t header; + sec_smem_id_vendor1_v2_t ven1_v2; + smem_ddr_stat_t ddr_stat; +} sec_smem_id_vendor1_v3_t; + +typedef struct { + void *clk __attribute__((aligned(8))); + void *apc_cpr __attribute__((aligned(8))); +} smem_apps_stat_t; + +typedef struct { + void *vreg __attribute__((aligned(8))); +} smem_vreg_stat_t; + +typedef struct { + sec_smem_header_t header; + sec_smem_id_vendor1_v2_t ven1_v2; + smem_ddr_stat_t ddr_stat; + void * ap_health __attribute__((aligned(8))); + smem_apps_stat_t apps_stat; + smem_vreg_stat_t vreg_stat; + ddr_train_t ddr_training; +} sec_smem_id_vendor1_v4_t; + +typedef struct { + sec_smem_header_t header; + sec_smem_id_vendor1_v2_t ven1_v2; + uint8_t cover_n; + smem_ddr_stat_t ddr_stat; + void * ap_health __attribute__((aligned(8))); + smem_apps_stat_t apps_stat; + smem_vreg_stat_t vreg_stat; + ddr_train_t ddr_training; +} sec_smem_id_vendor1_v5_t; + +typedef struct { + uint32_t pcba_config; // id_code +} huaqin_t; + +typedef union { + huaqin_t hq; + uint32_t reserve[8]; +} odm_t; + +typedef struct { + odm_t odm; + sec_smem_header_t header; + sec_smem_id_vendor1_v2_t ven1_v2; + uint8_t cover_n; + smem_ddr_stat_t ddr_stat; + void * ap_health __attribute__((aligned(8))); + smem_apps_stat_t apps_stat; + smem_vreg_stat_t vreg_stat; + ddr_train_t ddr_training; +} sec_smem_id_vendor1_v6_t; + +typedef struct { + uint8_t hw_rev[4]; /* 0 : main, 1 : sub, 2 : slave */ + uint8_t reserved1[4]; + uint8_t ap_suspended; + uint8_t reserved2[7]; + uint8_t cover_n; + uint8_t reserved3[3]; +} sec_smem_id_vendor1_share_t; /* sharing with other subsystem */ + +typedef struct { + sec_smem_header_t header; + sec_smem_id_vendor1_share_t share; + smem_ddr_stat_t ddr_stat; + void * ap_health __attribute__((aligned(8))); + smem_apps_stat_t apps_stat; + smem_vreg_stat_t vreg_stat; + ddr_train_t ddr_training; +} sec_smem_id_vendor1_v7_t; + +#endif /* __INTERNAL__SEC_QC_SMEM_ID_VENDOR1_TYPE_H__ */ diff --git a/drivers/samsung/debug/qcom/smem/sec_qc_smem_id_vendor1_v5.c b/drivers/samsung/debug/qcom/smem/sec_qc_smem_id_vendor1_v5.c new file mode 100644 index 000000000000..efd19680980e --- /dev/null +++ b/drivers/samsung/debug/qcom/smem/sec_qc_smem_id_vendor1_v5.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2015-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include + +#include "sec_qc_smem.h" + +#define member_ptr_of_v5(__vendor1, __member) \ + &((sec_smem_id_vendor1_v5_t *)(__vendor1))->__member + +static sec_smem_header_t *vendor1_v5_header(void *vendor1) +{ + return member_ptr_of_v5(vendor1, header); +} + +static sec_smem_id_vendor1_v2_t *vendor1_v5_ven1_v2(void *vendor1) +{ + return member_ptr_of_v5(vendor1, ven1_v2); +} + +static smem_ddr_stat_t *vendor1_v5_ddr_stat(void *vendor1) +{ + return member_ptr_of_v5(vendor1, ddr_stat); +} + +static void **vendor1_v5_ap_health(void *vendor1) +{ + return member_ptr_of_v5(vendor1, ap_health); +} + +static smem_apps_stat_t *vendor1_v5_apps_stat(void *vendor1) +{ + return member_ptr_of_v5(vendor1, apps_stat); +} + +static smem_vreg_stat_t *vendor1_v5_vreg_stat(void *vendor1) +{ + return member_ptr_of_v5(vendor1, vreg_stat); +} + +static ddr_train_t *vendor1_v5_ddr_training(void *vendor1) +{ + return member_ptr_of_v5(vendor1, ddr_training); +} + +static const struct vendor1_operations smem_id_vendor1_v5_ops = { + .header = vendor1_v5_header, + .ven1_v2 = vendor1_v5_ven1_v2, + .ddr_stat = vendor1_v5_ddr_stat, + .ap_health = vendor1_v5_ap_health, + .apps_stat = vendor1_v5_apps_stat, + .vreg_stat = vendor1_v5_vreg_stat, + .ddr_training = vendor1_v5_ddr_training, +}; + +const struct vendor1_operations *__qc_smem_vendor1_v5_ops_creator(void) +{ + return &smem_id_vendor1_v5_ops; +} diff --git a/drivers/samsung/debug/qcom/smem/sec_qc_smem_id_vendor1_v6.c b/drivers/samsung/debug/qcom/smem/sec_qc_smem_id_vendor1_v6.c new file mode 100644 index 000000000000..bfd2401dcc31 --- /dev/null +++ b/drivers/samsung/debug/qcom/smem/sec_qc_smem_id_vendor1_v6.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2015-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include + +#include "sec_qc_smem.h" + +#define member_ptr_of_v6(__vendor1, __member) \ + &((sec_smem_id_vendor1_v6_t *)(__vendor1))->__member + +static sec_smem_header_t *vendor1_v6_header(void *vendor1) +{ + return member_ptr_of_v6(vendor1, header); +} + +static sec_smem_id_vendor1_v2_t *vendor1_v6_ven1_v2(void *vendor1) +{ + return member_ptr_of_v6(vendor1, ven1_v2); +} + +static smem_ddr_stat_t *vendor1_v6_ddr_stat(void *vendor1) +{ + return member_ptr_of_v6(vendor1, ddr_stat); +} + +static void **vendor1_v6_ap_health(void *vendor1) +{ + return member_ptr_of_v6(vendor1, ap_health); +} + +static smem_apps_stat_t *vendor1_v6_apps_stat(void *vendor1) +{ + return member_ptr_of_v6(vendor1, apps_stat); +} + +static smem_vreg_stat_t *vendor1_v6_vreg_stat(void *vendor1) +{ + return member_ptr_of_v6(vendor1, vreg_stat); +} + +static ddr_train_t *vendor1_v6_ddr_training(void *vendor1) +{ + return member_ptr_of_v6(vendor1, ddr_training); +} + +static const struct vendor1_operations smem_id_vendor1_v6_ops = { + .header = vendor1_v6_header, + .ven1_v2 = vendor1_v6_ven1_v2, + .ddr_stat = vendor1_v6_ddr_stat, + .ap_health = vendor1_v6_ap_health, + .apps_stat = vendor1_v6_apps_stat, + .vreg_stat = vendor1_v6_vreg_stat, + .ddr_training = vendor1_v6_ddr_training, +}; + +const struct vendor1_operations *__qc_smem_vendor1_v6_ops_creator(void) +{ + return &smem_id_vendor1_v6_ops; +} diff --git a/drivers/samsung/debug/qcom/smem/sec_qc_smem_id_vendor1_v7.c b/drivers/samsung/debug/qcom/smem/sec_qc_smem_id_vendor1_v7.c new file mode 100644 index 000000000000..fa80ae2250ca --- /dev/null +++ b/drivers/samsung/debug/qcom/smem/sec_qc_smem_id_vendor1_v7.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2015-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include + +#include "sec_qc_smem.h" + +#define member_ptr_of_v7(__vendor1, __member) \ + &((sec_smem_id_vendor1_v7_t *)(__vendor1))->__member + +static sec_smem_header_t *vendor1_v7_header(void *vendor1) +{ + return member_ptr_of_v7(vendor1, header); +} + +static sec_smem_id_vendor1_share_t *vendor1_v7_share(void *vendor1) +{ + return member_ptr_of_v7(vendor1, share); +} + +static smem_ddr_stat_t *vendor1_v7_ddr_stat(void *vendor1) +{ + return member_ptr_of_v7(vendor1, ddr_stat); +} + +static void **vendor1_v7_ap_health(void *vendor1) +{ + return member_ptr_of_v7(vendor1, ap_health); +} + +static smem_apps_stat_t *vendor1_v7_apps_stat(void *vendor1) +{ + return member_ptr_of_v7(vendor1, apps_stat); +} + +static smem_vreg_stat_t *vendor1_v7_vreg_stat(void *vendor1) +{ + return member_ptr_of_v7(vendor1, vreg_stat); +} + +static ddr_train_t *vendor1_v7_ddr_training(void *vendor1) +{ + return member_ptr_of_v7(vendor1, ddr_training); +} + +static const struct vendor1_operations smem_id_vendor1_v7_ops = { + .header = vendor1_v7_header, + .share = vendor1_v7_share, + .ddr_stat = vendor1_v7_ddr_stat, + .ap_health = vendor1_v7_ap_health, + .apps_stat = vendor1_v7_apps_stat, + .vreg_stat = vendor1_v7_vreg_stat, + .ddr_training = vendor1_v7_ddr_training, +}; + +const struct vendor1_operations *__qc_smem_vendor1_v7_ops_creator(void) +{ + return &smem_id_vendor1_v7_ops; +} diff --git a/drivers/samsung/debug/qcom/smem/sec_qc_smem_logger.c b/drivers/samsung/debug/qcom/smem/sec_qc_smem_logger.c new file mode 100644 index 000000000000..e2bf053f5b7d --- /dev/null +++ b/drivers/samsung/debug/qcom/smem/sec_qc_smem_logger.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2014-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include + +#include + +#include + +#include "sec_qc_smem.h" + +int __qc_smem_clk_osm_probe(struct builder *bd) +{ + struct qc_smem_drvdata *drvdata = container_of(bd, + struct qc_smem_drvdata, bd); + struct device *dev = bd->dev; + smem_apps_stat_t *apps_stat; + cpuclk_log_t *cpuclk_log = drvdata->cpuclk_log; + size_t nr_cpuclk_log = ARRAY_SIZE(drvdata->cpuclk_log); + size_t i; + + for (i = 0; i < nr_cpuclk_log; i++) + cpuclk_log[i].max_cnt = MAX_CLK_LOG_CNT; + + apps_stat = __qc_smem_id_vendor1_get_apps_stat(drvdata); + if (IS_ERR_OR_NULL(apps_stat)) { + dev_warn(dev, "apps_stat is not available for vendor1 v%u\n", + drvdata->vendor1_ver); + return 0; + } + + apps_stat->clk = (void *)virt_to_phys(cpuclk_log); + dev_info(dev, "vendor1->apps_stat.clk = %p\n", apps_stat->clk); + + return 0; +} + +static void __smem_cpuclk_log(cpuclk_log_t *cpuclk_log, + size_t slot, unsigned long rate) +{ + cpuclk_log_t *clk = &cpuclk_log[slot]; + uint64_t idx = clk->index; + apps_clk_log_t *log = &clk->log[idx]; + + log->ktime = local_clock(); + log->qtime = arch_timer_read_counter(); + log->rate = rate; + clk->index = (clk->index + 1) % clk->max_cnt; +} + +static void __smem_clk_osm_add_log_cpufreq(cpuclk_log_t *cpuclk_log, + unsigned int cpu, unsigned long rate, const char *name) +{ + unsigned int domain = cpu_topology[cpu].package_id; + + if (!WARN((domain + 1) < PWR_CLUSTER || (domain + 1) > PRIME_CLUSTER, + "%s : invalid cluster_num(%u), dbg_name(%s)\n", + __func__, domain + 1, name)) { + __smem_cpuclk_log(cpuclk_log, domain + 1, rate); + } +} + +static int sec_qc_smem_cpufreq_target_index_handler(struct notifier_block *this, + unsigned long index, void *d) +{ + struct qc_smem_drvdata *drvdata = container_of(this, + struct qc_smem_drvdata, nb_cpuclk_log); + struct cpufreq_policy *policy = d; + + __smem_clk_osm_add_log_cpufreq(drvdata->cpuclk_log, + policy->cpu, + policy->freq_table[index].frequency, + policy->kobj.name); + + return NOTIFY_OK; +} + +static __always_inline int ____qc_smem_register_nb_cpuclk_log(struct builder *bd) +{ + struct qc_smem_drvdata *drvdata = container_of(bd, + struct qc_smem_drvdata, bd); + + drvdata->nb_cpuclk_log.notifier_call = + sec_qc_smem_cpufreq_target_index_handler; + + return qcom_cpufreq_hw_target_index_register_notifier( + &drvdata->nb_cpuclk_log); +} + +int __qc_smem_register_nb_cpuclk_log(struct builder *bd) +{ + if (!IS_ENABLED(CONFIG_ARM_QCOM_CPUFREQ_HW)) + return 0; + + return ____qc_smem_register_nb_cpuclk_log(bd); +} + +static __always_inline void ____qc_smem_unregister_nb_cpuclk_log(struct builder *bd) +{ + struct qc_smem_drvdata *drvdata = container_of(bd, + struct qc_smem_drvdata, bd); + + qcom_cpufreq_hw_target_index_unregister_notifier( + &drvdata->nb_cpuclk_log); +} + +void __qc_smem_unregister_nb_cpuclk_log(struct builder *bd) +{ + if (!IS_ENABLED(CONFIG_ARM_QCOM_CPUFREQ_HW)) + return; + + ____qc_smem_unregister_nb_cpuclk_log(bd); +} + +static void __smem_clk_osm_add_log_l3(cpuclk_log_t *cpuclk_log, + unsigned long rate) +{ + __smem_cpuclk_log(cpuclk_log, L3, rate / 1000); +} + +static int sec_qc_smem_l3_cpu_set_handler(struct notifier_block *this, + unsigned long index, void *__lut_freqs) +{ + struct qc_smem_drvdata *drvdata = container_of(this, + struct qc_smem_drvdata, nb_l3clk_log); + unsigned long *lut_freqs = __lut_freqs; + + __smem_clk_osm_add_log_l3(drvdata->cpuclk_log, lut_freqs[index]); + + return NOTIFY_OK; +} + +static __always_inline int ____qc_smem_register_nb_l3clk_log(struct builder *bd) +{ + struct qc_smem_drvdata *drvdata = container_of(bd, + struct qc_smem_drvdata, bd); + + drvdata->nb_l3clk_log.notifier_call = + sec_qc_smem_l3_cpu_set_handler; + + return qcom_icc_epss_l3_cpu_set_register_notifier(&drvdata->nb_l3clk_log); +} + +int __qc_smem_register_nb_l3clk_log(struct builder *bd) +{ + if (!IS_ENABLED(CONFIG_INTERCONNECT_QCOM_EPSS_L3)) + return 0; + + return ____qc_smem_register_nb_l3clk_log(bd); +} + +static __always_inline void ____qc_smem_unregister_nb_l3clk_log(struct builder *bd) +{ + struct qc_smem_drvdata *drvdata = container_of(bd, + struct qc_smem_drvdata, bd); + + qcom_icc_epss_l3_cpu_set_unregister_notifier(&drvdata->nb_l3clk_log); +} + +void __qc_smem_unregister_nb_l3clk_log(struct builder *bd) +{ + if (!IS_ENABLED(CONFIG_INTERCONNECT_QCOM_EPSS_L3)) + return; + + ____qc_smem_unregister_nb_l3clk_log(bd); +} + diff --git a/drivers/samsung/debug/qcom/smem/sec_qc_smem_lpddr.c b/drivers/samsung/debug/qcom/smem/sec_qc_smem_lpddr.c new file mode 100644 index 000000000000..0607bbd5db59 --- /dev/null +++ b/drivers/samsung/debug/qcom/smem/sec_qc_smem_lpddr.c @@ -0,0 +1,408 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2014-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include + +#include "sec_qc_smem.h" + +static char *lpddr_manufacture_name[] = { + "NA", + "SEC"/* Samsung */, + "NA", + "NA", + "NA", + "NAN" /* Nanya */, + "HYN" /* SK hynix */, + "NA", + "WIN" /* Winbond */, + "ESM" /* ESMT */, + "NA", + "NA", + "NA", + "NA", + "NA", + "MIC" /* Micron */, +}; + +static uint32_t __lpddr_get_ddr_info(uint32_t type) +{ + sec_smem_id_vendor0_v2_t *vendor0; + + if (!__qc_smem_is_probed()) + return 0; + + vendor0 = qc_smem->vendor0; + if (IS_ERR_OR_NULL(vendor0)) { + dev_warn(qc_smem->bd.dev, "SMEM_ID_VENDOR0 get entry error\n"); + return 0; + } + + return (vendor0->ddr_vendor >> type) & 0xFF; +} + +uint8_t sec_qc_smem_lpddr_get_revision_id1(void) +{ + return __lpddr_get_ddr_info(DDR_IFNO_REVISION_ID1); +} +EXPORT_SYMBOL_GPL(sec_qc_smem_lpddr_get_revision_id1); + +uint8_t sec_qc_smem_lpddr_get_revision_id2(void) +{ + return __lpddr_get_ddr_info(DDR_IFNO_REVISION_ID2); +} +EXPORT_SYMBOL_GPL(sec_qc_smem_lpddr_get_revision_id2); + +uint8_t sec_qc_smem_lpddr_get_total_density(void) +{ + return __lpddr_get_ddr_info(DDR_IFNO_TOTAL_DENSITY); +} +EXPORT_SYMBOL_GPL(sec_qc_smem_lpddr_get_total_density); + +static const char *__lpddr_get_vendor_name(struct qc_smem_drvdata *drvdata) +{ + sec_smem_id_vendor0_v2_t *vendor0 = drvdata->vendor0; + size_t manufacture; + + if (IS_ERR_OR_NULL(vendor0)) { + dev_warn(qc_smem->bd.dev, "SMEM_ID_VENDOR0 get entry error\n"); + return "NA"; + } + + manufacture = (size_t)vendor0->ddr_vendor + % ARRAY_SIZE(lpddr_manufacture_name); + + return lpddr_manufacture_name[manufacture]; +} + +const char *sec_qc_smem_lpddr_get_vendor_name(void) +{ + if (!__qc_smem_is_probed()) + return "NA"; + + return __lpddr_get_vendor_name(qc_smem); +} +EXPORT_SYMBOL_GPL(sec_qc_smem_lpddr_get_vendor_name); + +static ddr_train_t *__lpddr_get_ddr_train(struct qc_smem_drvdata *drvdata) +{ + struct device *dev = drvdata->bd.dev; + void *vendor1 = drvdata->vendor1; + ddr_train_t *ddr_training; + + if (IS_ERR_OR_NULL(vendor1)) { + dev_warn(dev, "SMEM_ID_VENDOR1 get entry error\n"); + return ERR_PTR(-ENODEV); + } + + ddr_training = __qc_smem_id_vendor1_get_ddr_training(drvdata); + if (IS_ERR_OR_NULL(ddr_training)) { + dev_warn(dev, "SMEM_ID_VENDOR1 is invalid or wrong version (%ld)\n", + PTR_ERR(ddr_training)); + return ERR_PTR(-ENOENT); + } + + return ddr_training; +} + +static uint32_t __lpddr_get_DSF_version(struct qc_smem_drvdata *drvdata) +{ + ddr_train_t *ddr_training = __lpddr_get_ddr_train(drvdata); + + if (IS_ERR_OR_NULL(ddr_training)) + return 0; + + return ddr_training->version; +} + +uint32_t sec_qc_smem_lpddr_get_DSF_version(void) +{ + if (!__qc_smem_is_probed()) + return 0; + + return __lpddr_get_DSF_version(qc_smem); +} +EXPORT_SYMBOL_GPL(sec_qc_smem_lpddr_get_DSF_version); + + +static uint8_t __lpddr_get_rcw_tDQSCK(struct qc_smem_drvdata *drvdata, + size_t ch, size_t cs, size_t dq) +{ + ddr_train_t *ddr_training = __lpddr_get_ddr_train(drvdata); + + if (IS_ERR_OR_NULL(ddr_training)) + return 0; + + return ddr_training->rcw.tDQSCK[ch][cs][dq]; +} + +uint8_t sec_qc_smem_lpddr_get_rcw_tDQSCK(size_t ch, size_t cs, size_t dq) +{ + if (!__qc_smem_is_probed()) + return 0; + + return __lpddr_get_rcw_tDQSCK(qc_smem, ch, cs, dq); +} +EXPORT_SYMBOL_GPL(sec_qc_smem_lpddr_get_rcw_tDQSCK); + +static uint8_t __lpddr_get_wr_coarseCDC(struct qc_smem_drvdata *drvdata, + size_t ch, size_t cs, size_t dq) +{ + ddr_train_t *ddr_training = __lpddr_get_ddr_train(drvdata); + + if (IS_ERR_OR_NULL(ddr_training)) + return 0; + + return ddr_training->wr_dqdqs.coarse_cdc[ch][cs][dq]; +} + +uint8_t sec_qc_smem_lpddr_get_wr_coarseCDC(size_t ch, size_t cs, size_t dq) +{ + if (!__qc_smem_is_probed()) + return 0; + + return __lpddr_get_wr_coarseCDC(qc_smem, ch, cs, dq); +} +EXPORT_SYMBOL_GPL(sec_qc_smem_lpddr_get_wr_coarseCDC); + +static uint8_t __lpddr_get_wr_fineCDC(struct qc_smem_drvdata *drvdata, + size_t ch, size_t cs, size_t dq) +{ + ddr_train_t *ddr_training = __lpddr_get_ddr_train(drvdata); + + if (IS_ERR_OR_NULL(ddr_training)) + return 0; + + return ddr_training->wr_dqdqs.fine_cdc[ch][cs][dq]; +} + +uint8_t sec_qc_smem_lpddr_get_wr_fineCDC(size_t ch, size_t cs, size_t dq) +{ + if (!__qc_smem_is_probed()) + return 0; + + return __lpddr_get_wr_fineCDC(qc_smem, ch, cs, dq); +} +EXPORT_SYMBOL_GPL(sec_qc_smem_lpddr_get_wr_fineCDC); + +static uint8_t __lpddr_get_wr_pr_width(struct qc_smem_drvdata *drvdata, + size_t ch, size_t cs, size_t dq) +{ + ddr_train_t *ddr_training = __lpddr_get_ddr_train(drvdata); + + if (IS_ERR_OR_NULL(ddr_training)) + return 0; + + return ddr_training->dqdqs_eye.wr_pr_width[ch][cs][dq]; +} + +uint8_t sec_qc_smem_lpddr_get_wr_pr_width(size_t ch, size_t cs, size_t dq) +{ + if (!__qc_smem_is_probed()) + return 0; + + return __lpddr_get_wr_pr_width(qc_smem, ch, cs, dq); +} +EXPORT_SYMBOL_GPL(sec_qc_smem_lpddr_get_wr_pr_width); + +static uint8_t __lpddr_get_wr_min_eye_height(struct qc_smem_drvdata *drvdata, + size_t ch, size_t cs) +{ + ddr_train_t *ddr_training = __lpddr_get_ddr_train(drvdata); + + if (IS_ERR_OR_NULL(ddr_training)) + return 0; + + return ddr_training->dqdqs_eye.wr_min_eye_height[ch][cs]; +} + +uint8_t sec_qc_smem_lpddr_get_wr_min_eye_height(size_t ch, size_t cs) +{ + if (!__qc_smem_is_probed()) + return 0; + + return __lpddr_get_wr_min_eye_height(qc_smem, ch, cs); +} +EXPORT_SYMBOL_GPL(sec_qc_smem_lpddr_get_wr_min_eye_height); + +static uint8_t __lpddr_get_wr_best_vref(struct qc_smem_drvdata *drvdata, + size_t ch, size_t cs) +{ + ddr_train_t *ddr_training = __lpddr_get_ddr_train(drvdata); + + if (IS_ERR_OR_NULL(ddr_training)) + return 0; + + return ddr_training->dqdqs_eye.wr_best_vref[ch][cs]; +} + +uint8_t sec_qc_smem_lpddr_get_wr_best_vref(size_t ch, size_t cs) +{ + if (!__qc_smem_is_probed()) + return 0; + + return __lpddr_get_wr_best_vref(qc_smem, ch, cs); +} +EXPORT_SYMBOL_GPL(sec_qc_smem_lpddr_get_wr_best_vref); + +static uint8_t __lpddr_get_wr_vmax_to_vmid(struct qc_smem_drvdata *drvdata, + size_t ch, size_t cs, size_t dq) +{ + ddr_train_t *ddr_training = __lpddr_get_ddr_train(drvdata); + + if (IS_ERR_OR_NULL(ddr_training)) + return 0; + + return ddr_training->dqdqs_eye.wr_vmax_to_vmid[ch][cs][dq]; +} + +uint8_t sec_qc_smem_lpddr_get_wr_vmax_to_vmid(size_t ch, size_t cs, size_t dq) +{ + if (!__qc_smem_is_probed()) + return 0; + + return __lpddr_get_wr_vmax_to_vmid(qc_smem, ch, cs, dq); +} +EXPORT_SYMBOL_GPL(sec_qc_smem_lpddr_get_wr_vmax_to_vmid); + +static uint8_t __lpddr_get_wr_vmid_to_vmin(struct qc_smem_drvdata *drvdata, + size_t ch, size_t cs, size_t dq) +{ + ddr_train_t *ddr_training = __lpddr_get_ddr_train(drvdata); + + if (IS_ERR_OR_NULL(ddr_training)) + return 0; + + return ddr_training->dqdqs_eye.wr_vmid_to_vmin[ch][cs][dq]; +} + +uint8_t sec_qc_smem_lpddr_get_wr_vmid_to_vmin(size_t ch, size_t cs, size_t dq) +{ + if (!__qc_smem_is_probed()) + return 0; + + return __lpddr_get_wr_vmid_to_vmin(qc_smem, ch, cs, dq); +} +EXPORT_SYMBOL_GPL(sec_qc_smem_lpddr_get_wr_vmid_to_vmin); + +static uint8_t __lpddr_get_dqs_dcc_adj(struct qc_smem_drvdata *drvdata, + size_t ch, size_t dq) +{ + ddr_train_t *ddr_training = __lpddr_get_ddr_train(drvdata); + + if (IS_ERR_OR_NULL(ddr_training)) + return 0; + + return ddr_training->dqdqs_eye.dqs_dcc_adj[ch][dq]; +} + +uint8_t sec_qc_smem_lpddr_get_dqs_dcc_adj(size_t ch, size_t dq) +{ + if (!__qc_smem_is_probed()) + return 0; + + return __lpddr_get_dqs_dcc_adj(qc_smem, ch, dq); +} +EXPORT_SYMBOL_GPL(sec_qc_smem_lpddr_get_dqs_dcc_adj); + +static uint8_t __lpddr_get_rd_pr_width(struct qc_smem_drvdata *drvdata, + size_t ch, size_t cs, size_t dq) +{ + ddr_train_t *ddr_training = __lpddr_get_ddr_train(drvdata); + + if (IS_ERR_OR_NULL(ddr_training)) + return 0; + + return ddr_training->dqdqs_eye.rd_pr_width[ch][cs][dq]; +} + +uint8_t sec_qc_smem_lpddr_get_rd_pr_width(size_t ch, size_t cs, size_t dq) +{ + if (!__qc_smem_is_probed()) + return 0; + + return __lpddr_get_rd_pr_width(qc_smem, ch, cs, dq); +} +EXPORT_SYMBOL_GPL(sec_qc_smem_lpddr_get_rd_pr_width); + +static uint8_t __lpddr_get_rd_min_eye_height(struct qc_smem_drvdata *drvdata, + size_t ch, size_t cs) +{ + ddr_train_t *ddr_training = __lpddr_get_ddr_train(drvdata); + + if (IS_ERR_OR_NULL(ddr_training)) + return 0; + + return ddr_training->dqdqs_eye.rd_min_eye_height[ch][cs]; +} + +uint8_t sec_qc_smem_lpddr_get_rd_min_eye_height(size_t ch, size_t cs) +{ + if (!__qc_smem_is_probed()) + return 0; + + return __lpddr_get_rd_min_eye_height(qc_smem, ch, cs); +} +EXPORT_SYMBOL_GPL(sec_qc_smem_lpddr_get_rd_min_eye_height); + +static uint8_t __lpddr_get_rd_best_vref(struct qc_smem_drvdata *drvdata, + size_t ch, size_t cs, size_t dq) +{ + ddr_train_t *ddr_training = __lpddr_get_ddr_train(drvdata); + + if (IS_ERR_OR_NULL(ddr_training)) + return 0; + + return ddr_training->dqdqs_eye.rd_best_vref[ch][cs][dq]; +} + +uint8_t sec_qc_smem_lpddr_get_rd_best_vref(size_t ch, size_t cs, size_t dq) +{ + if (!__qc_smem_is_probed()) + return 0; + + return __lpddr_get_rd_best_vref(qc_smem, ch, cs, dq); +} +EXPORT_SYMBOL_GPL(sec_qc_smem_lpddr_get_rd_best_vref); + +static uint8_t __lpddr_get_dq_dcc_abs(struct qc_smem_drvdata *drvdata, + size_t ch, size_t cs, size_t dq) +{ + ddr_train_t *ddr_training = __lpddr_get_ddr_train(drvdata); + + if (IS_ERR_OR_NULL(ddr_training)) + return 0; + + return ddr_training->dqdqs_eye.dq_dcc_abs[ch][cs][dq]; +} + +uint8_t sec_qc_smem_lpddr_get_dq_dcc_abs(size_t ch, size_t cs, size_t dq) +{ + if (!__qc_smem_is_probed()) + return 0; + + return __lpddr_get_dq_dcc_abs(qc_smem, ch, cs, dq); +} +EXPORT_SYMBOL_GPL(sec_qc_smem_lpddr_get_dq_dcc_abs); + +static uint16_t __lpddr_get_small_eye_detected(struct qc_smem_drvdata *drvdata) +{ + ddr_train_t *ddr_training = __lpddr_get_ddr_train(drvdata); + + if (IS_ERR_OR_NULL(ddr_training)) + return 0; + + return ddr_training->dqdqs_eye.small_eye_detected; +} + +uint8_t sec_qc_smem_lpddr_get_small_eye_detected(void) +{ + if (!__qc_smem_is_probed()) + return 0; + + return __lpddr_get_small_eye_detected(qc_smem); +} +EXPORT_SYMBOL_GPL(sec_qc_smem_lpddr_get_small_eye_detected); diff --git a/drivers/samsung/debug/qcom/smem/sec_qc_smem_main.c b/drivers/samsung/debug/qcom/smem/sec_qc_smem_main.c new file mode 100644 index 000000000000..68fb519d79f1 --- /dev/null +++ b/drivers/samsung/debug/qcom/smem/sec_qc_smem_main.c @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2015-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "sec_qc_smem.h" + +struct qc_smem_drvdata *qc_smem; + +static noinline int __smem_test_ap_health(struct builder *bd) +{ + struct qc_smem_drvdata *drvdata = + container_of(bd, struct qc_smem_drvdata, bd); + ap_health_t *health = sec_qc_ap_health_data_read(); + + if (PTR_ERR(health) == -EBUSY) + return -EPROBE_DEFER; + else if (IS_ERR_OR_NULL(health)) + return -ENODEV; + + drvdata->ap_health = health; + + return 0; +} + +static void *__smem_get_ddr_smem_entry(struct qc_smem_drvdata *drvdata, + unsigned int id) +{ + struct device *dev = drvdata->bd.dev; + void *entry; + size_t size = 0; + + entry = qcom_smem_get(QCOM_SMEM_HOST_ANY, id, &size); + if (!size) + dev_warn(dev, "entry size is zero\n"); + + return entry; +} + +static noinline int __smem_test_vendor0(struct builder *bd) +{ + struct qc_smem_drvdata *drvdata = + container_of(bd, struct qc_smem_drvdata, bd); + char *vendor0; + + vendor0 = __smem_get_ddr_smem_entry(drvdata, SMEM_ID_VENDOR0); + if (PTR_ERR(vendor0) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + if (IS_ERR_OR_NULL(vendor0)) { + dev_err(bd->dev, "SMEM_ID_VENDOR0 get entry error(%ld)\n", + PTR_ERR(vendor0)); + panic("sec_smem_probe fail"); + return -EINVAL; + } + + drvdata->vendor0 = (sec_smem_id_vendor0_v2_t *)(&vendor0[drvdata->smem_offset_vendor0]); + + return 0; +} + +static noinline int __smem_test_vendor1(struct builder *bd) +{ + struct qc_smem_drvdata *drvdata = + container_of(bd, struct qc_smem_drvdata, bd); + char *vendor1; + + vendor1 = __smem_get_ddr_smem_entry(drvdata, SMEM_ID_VENDOR1); + if (PTR_ERR(vendor1) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + if (IS_ERR_OR_NULL(vendor1)) { + dev_err(bd->dev, "SMEM_ID_VENDOR1 get entry error(%ld)\n", + PTR_ERR(vendor1)); + panic("sec_smem_probe fail"); + return -EINVAL; + } + + drvdata->vendor1 = (void *)(&vendor1[drvdata->smem_offset_vendor1]); + + return 0; +} + +static noinline int __smem_parse_dt_smem_offset_vendor0(struct builder *bd, struct device_node *np) +{ + struct qc_smem_drvdata *drvdata = + container_of(bd, struct qc_smem_drvdata, bd); + u32 smem_offset; + int err; + + /* NOTE: + * android13-5.15.y and before : optional. if not found assign '0'. + * android14-6.1.y and after : mandatory. + */ + err = of_property_read_u32(np, "sec,smem_offset_vendor0", &smem_offset); + if (err) + return -EINVAL; + + drvdata->smem_offset_vendor0 = (size_t)smem_offset; + + return 0; +} + +static noinline int __smem_parse_dt_smem_offset_vendor1(struct builder *bd, struct device_node *np) +{ + struct qc_smem_drvdata *drvdata = + container_of(bd, struct qc_smem_drvdata, bd); + u32 smem_offset; + int err; + + /* NOTE: + * android13-5.15.y and before : optional. if not found assign '0'. + * android14-6.1.y and after : mandatory. + */ + err = of_property_read_u32(np, "sec,smem_offset_vendor1", &smem_offset); + if (err) + return -EINVAL; + + drvdata->smem_offset_vendor1 = (size_t)smem_offset; + + return 0; +} + +static noinline int __smem_parse_dt_vendor0_ver(struct builder *bd, + struct device_node *np) +{ + struct qc_smem_drvdata *drvdata = + container_of(bd, struct qc_smem_drvdata, bd); + u32 vendor0_ver; + int err; + + err = of_property_read_s32(np, "sec,vendor0_ver", &vendor0_ver); + if (err) + return -EINVAL; + + drvdata->vendor0_ver = (unsigned int)vendor0_ver; + + return 0; +} + +static noinline int __smem_parse_dt_vendor1_ver(struct builder *bd, + struct device_node *np) +{ + struct qc_smem_drvdata *drvdata = + container_of(bd, struct qc_smem_drvdata, bd); + u32 vendor1_ver; + int err; + + err = of_property_read_s32(np, "sec,vendor1_ver", &vendor1_ver); + if (err) + return -EINVAL; + + drvdata->vendor1_ver = (unsigned int)vendor1_ver; + + return 0; +} + +static const struct dt_builder __smem_dt_builder[] = { + DT_BUILDER(__smem_parse_dt_smem_offset_vendor0), + DT_BUILDER(__smem_parse_dt_smem_offset_vendor1), + DT_BUILDER(__smem_parse_dt_vendor0_ver), + DT_BUILDER(__smem_parse_dt_vendor1_ver), +}; + +static noinline int __smem_parse_dt(struct builder *bd) +{ + return sec_director_parse_dt(bd, __smem_dt_builder, + ARRAY_SIZE(__smem_dt_builder)); +} + +static noinline int __smem_probe_epilog(struct builder *bd) +{ + struct qc_smem_drvdata *drvdata = + container_of(bd, struct qc_smem_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + qc_smem = drvdata; + + return 0; +} + +static noinline void __smem_remove_prolog(struct builder *bd) +{ + qc_smem = NULL; +} + +static int __smem_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct qc_smem_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __smem_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct qc_smem_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static const struct dev_builder __smem_dev_builder[] = { + DEVICE_BUILDER(__smem_test_ap_health, NULL), + DEVICE_BUILDER(__smem_test_vendor0, NULL), + DEVICE_BUILDER(__smem_test_vendor1, NULL), + /* TODO: deferrable concrete builders should be before here. */ + DEVICE_BUILDER(__smem_parse_dt, NULL), + DEVICE_BUILDER(__qc_smem_id_vendor1_init, NULL), + DEVICE_BUILDER(__qc_smem_clk_osm_probe, NULL), + DEVICE_BUILDER(__qc_smem_register_nb_cpuclk_log, + __qc_smem_unregister_nb_cpuclk_log), + DEVICE_BUILDER(__qc_smem_register_nb_l3clk_log, + __qc_smem_unregister_nb_l3clk_log), + DEVICE_BUILDER(__smem_probe_epilog, __smem_remove_prolog), +}; + +static int sec_qc_smem_probe(struct platform_device *pdev) +{ + return __smem_probe(pdev, __smem_dev_builder, + ARRAY_SIZE(__smem_dev_builder)); +} + +static int sec_qc_smem_remove(struct platform_device *pdev) +{ + return __smem_remove(pdev, __smem_dev_builder, + ARRAY_SIZE(__smem_dev_builder)); +} + +#define SUSPEND 0x1 +#define RESUME 0x0 + +static int sec_qc_smem_suspend(struct device *dev) +{ + struct qc_smem_drvdata *drvdata = dev_get_drvdata(dev); + sec_smem_id_vendor1_v2_t *ven1_v2; + sec_smem_id_vendor1_share_t *share; + + ven1_v2 = __qc_smem_id_vendor1_get_ven1_v2(drvdata); + if (!IS_ERR_OR_NULL(ven1_v2)) + ven1_v2->ap_suspended = SUSPEND; + + share = __qc_smem_id_vendor1_get_share(drvdata); + if (!IS_ERR_OR_NULL(share)) + share->ap_suspended = SUSPEND; + + dev_info(dev, "smem_vendor1 ap_suspended - SUSPEND\n"); + + return 0; +} + +static int sec_qc_smem_resume(struct device *dev) +{ + struct qc_smem_drvdata *drvdata = dev_get_drvdata(dev); + sec_smem_id_vendor1_v2_t *ven1_v2; + sec_smem_id_vendor1_share_t *share; + + ven1_v2 = __qc_smem_id_vendor1_get_ven1_v2(drvdata); + if (!IS_ERR_OR_NULL(ven1_v2)) + ven1_v2->ap_suspended = RESUME; + + share = __qc_smem_id_vendor1_get_share(drvdata); + if (!IS_ERR_OR_NULL(share)) + share->ap_suspended = RESUME; + + dev_info(dev, "smem_vendor1 ap_suspended - RESUME\n"); + + return 0; +} + +static const struct dev_pm_ops sec_qc_smem_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(sec_qc_smem_suspend, sec_qc_smem_resume) +}; + +static const struct of_device_id sec_qc_smem_match_table[] = { + { .compatible = "samsung,qcom-smem" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_qc_smem_match_table); + +static struct platform_driver sec_qc_smem_driver = { + .driver = { + .name = "sec,qc-smem", + .of_match_table = of_match_ptr(sec_qc_smem_match_table), + .pm = &sec_qc_smem_pm_ops, + }, + .probe = sec_qc_smem_probe, + .remove = sec_qc_smem_remove, +}; + +static int __init sec_qcom_smem_init(void) +{ + return platform_driver_register(&sec_qc_smem_driver); +} +module_init(sec_qcom_smem_init); + +static void __exit sec_qcom_smem_exit(void) +{ + platform_driver_unregister(&sec_qc_smem_driver); +} +module_exit(sec_qcom_smem_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("SMEM for Qualcomm based devices"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/debug/qcom/soc_id/Kconfig b/drivers/samsung/debug/qcom/soc_id/Kconfig new file mode 100644 index 000000000000..ef021e660efb --- /dev/null +++ b/drivers/samsung/debug/qcom/soc_id/Kconfig @@ -0,0 +1,29 @@ +config SEC_QC_SOC_ID + tristate "SEC SoC ID for Qualcomm SoC based models" + help + TODO: help is not ready. + +config SEC_QC_SOC_ID_EN + bool "SEC SoC ID for Qualcomm SoC based models for Factory Mode" + depends on SEC_QC_SOC_ID + default y if SEC_FACTORY + default n + help + TODO: help is not ready. + +config SEC_QC_SOC_ID_TEST_FOR_ON_DEVICE + tristate "KUnit test for sec_qc_soc_id_test" + depends on KUNIT + depends on SEC_QC_SOC_ID + help + TODO: Describe config fully. + If you run this test driver on device, SHOULD set this config as 'm' to build test driver modulraly. + +config SEC_QC_SOC_ID_TEST_FOR_ONLY_UML + tristate "KUnit test for sec_qc_soc_id_test" + depends on KUNIT + depends on UML + depends on SEC_QC_SOC_ID + help + TODO: Describe config fully. + This CONFIG is recommended to set to y. diff --git a/drivers/samsung/debug/qcom/soc_id/Makefile b/drivers/samsung/debug/qcom/soc_id/Makefile new file mode 100644 index 000000000000..df329a0b4feb --- /dev/null +++ b/drivers/samsung/debug/qcom/soc_id/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_SEC_QC_SOC_ID) += sec_qc_soc_id.o + +GCOV_PROFILE_sec_qc_soc_id.o := $(CONFIG_KUNIT) diff --git a/drivers/samsung/debug/qcom/soc_id/sec_qc_soc_id.c b/drivers/samsung/debug/qcom/soc_id/sec_qc_soc_id.c new file mode 100644 index 000000000000..e3058154fea0 --- /dev/null +++ b/drivers/samsung/debug/qcom/soc_id/sec_qc_soc_id.c @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2016-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "sec_qc_soc_id.h" + +static ssize_t msm_feature_id_show(struct device *sec_class_dev, + struct device_attribute *attr, char *buf) +{ + struct qc_soc_id_drvdata *drvdata = dev_get_drvdata(sec_class_dev); + union qfprom_jtag *qfprom_jtag_data = &drvdata->qfprom_jtag_data; + + pr_debug("FEATURE_ID : 0x%08x\n", qfprom_jtag_data->feature_id); + + return scnprintf(buf, PAGE_SIZE, "%02u\n", qfprom_jtag_data->feature_id); +} + +static DEVICE_ATTR_RO(msm_feature_id); + +static ssize_t msm_jtag_id_show(struct device *sec_class_dev, + struct device_attribute *attr, char *buf) +{ + struct qc_soc_id_drvdata *drvdata = dev_get_drvdata(sec_class_dev); + struct jtag_id *jtag_id_data = &drvdata->jtag_id_data; + + pr_debug("JTAG_ID_REG : 0x%08x\n", jtag_id_data->raw); + + return scnprintf(buf, PAGE_SIZE, "%08x\n", jtag_id_data->raw); +} + +static DEVICE_ATTR_RO(msm_jtag_id); + +static struct attribute *sec_qc_soc_id_attrs[] = { + &dev_attr_msm_feature_id.attr, + &dev_attr_msm_jtag_id.attr, + NULL, +}; + +static const struct attribute_group sec_qc_soc_id_attr_group = { + .attrs = sec_qc_soc_id_attrs, +}; + +static int __qc_soc_id_sec_class_create(struct builder *bd) +{ + struct qc_soc_id_drvdata *drvdata = + container_of(bd, struct qc_soc_id_drvdata, bd); + struct device *sec_misc_dev; + + sec_misc_dev = sec_device_create(NULL, "sec_misc"); + if (IS_ERR(sec_misc_dev)) + return PTR_ERR(sec_misc_dev); + + dev_set_drvdata(sec_misc_dev, drvdata); + + drvdata->sec_misc_dev = sec_misc_dev; + + return 0; +} + +static void __qc_soc_id_sec_class_remove(struct builder *bd) +{ + struct qc_soc_id_drvdata *drvdata = + container_of(bd, struct qc_soc_id_drvdata, bd); + struct device *sec_misc_dev = drvdata->sec_misc_dev; + + if (!sec_misc_dev) + return; + + sec_device_destroy(sec_misc_dev->devt); +} + +static int __qc_soc_id_sysfs_create(struct builder *bd) +{ + struct qc_soc_id_drvdata *drvdata = + container_of(bd, struct qc_soc_id_drvdata, bd); + struct device *sec_misc_dev = drvdata->sec_misc_dev; + int err; + + err = sysfs_create_group(&sec_misc_dev->kobj, &sec_qc_soc_id_attr_group); + if (err) + return err; + + return 0; +} + +static void __qc_soc_id_sysfs_remove(struct builder *bd) +{ + struct qc_soc_id_drvdata *drvdata = + container_of(bd, struct qc_soc_id_drvdata, bd); + struct device *sec_misc_dev = drvdata->sec_misc_dev; + + sysfs_remove_group(&sec_misc_dev->kobj, &sec_qc_soc_id_attr_group); +} + +__ss_static noinline int __qc_soc_id_parse_dt_qfprom_jtag(struct builder *bd, + struct device_node *np) +{ + struct qc_soc_id_drvdata *drvdata = + container_of(bd, struct qc_soc_id_drvdata, bd); + u32 phys; + int err; + bool enabled; + + enabled = of_property_read_bool(np, "sec,use-qfprom_jtag"); + if (!enabled) + return 0; + + drvdata->use_qfprom_jtag = true; + + err = of_property_read_u32(np, "sec,qfprom_jtag-addr", &phys); + if (err) + return err; + + drvdata->qfprom_jtag_phys = phys; + + return 0; +} + +__ss_static noinline int __qc_soc_id_parse_dt_jtag_id(struct builder *bd, + struct device_node *np) +{ + struct qc_soc_id_drvdata *drvdata = + container_of(bd, struct qc_soc_id_drvdata, bd); + u32 phys; + int err; + bool enabled; + + enabled = of_property_read_bool(np, "sec,use-jtag_id"); + if (!enabled) + return 0; + + drvdata->use_jtag_id = true; + + err = of_property_read_u32(np, "sec,jtag_id-addr", &phys); + if (err) + return err; + + drvdata->jtag_id_phys = phys; + + return 0; +} + +static const struct dt_builder __qc_soc_id_dt_builder[] = { + DT_BUILDER(__qc_soc_id_parse_dt_qfprom_jtag), + DT_BUILDER(__qc_soc_id_parse_dt_jtag_id), +}; + +static noinline int __qc_soc_id_parse_dt(struct builder *bd) +{ + return sec_director_parse_dt(bd, __qc_soc_id_dt_builder, + ARRAY_SIZE(__qc_soc_id_dt_builder)); +} + +static noinline int __qc_soc_id_read_qfprom_jtag(struct builder *bd) +{ + struct qc_soc_id_drvdata *drvdata = + container_of(bd, struct qc_soc_id_drvdata, bd); + void __iomem *reg_virt; + union qfprom_jtag *qfprom_jtag_data = &drvdata->qfprom_jtag_data; + + if (!drvdata->use_qfprom_jtag) + return 0; + + reg_virt = ioremap(drvdata->qfprom_jtag_phys, PAGE_SIZE); + if (!reg_virt) + return -ENODEV; + + qfprom_jtag_data->raw = readl_relaxed(reg_virt); + iounmap(reg_virt); + + return 0; +} + +static noinline int __qc_soc_id_read_jtag_id(struct builder *bd) +{ + struct qc_soc_id_drvdata *drvdata = + container_of(bd, struct qc_soc_id_drvdata, bd); + void __iomem *reg_virt; + struct jtag_id *jtag_id_data = &drvdata->jtag_id_data; + + if (!drvdata->use_jtag_id) + return 0; + + reg_virt = ioremap(drvdata->jtag_id_phys, PAGE_SIZE); + if (!reg_virt) + return -ENODEV; + + jtag_id_data->raw = readl_relaxed(reg_virt); + iounmap(reg_virt); + + return 0; +} + +static int __qc_soc_id_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct qc_soc_id_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __qc_soc_id_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct qc_soc_id_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static const struct dev_builder __qc_soc_id_dev_builder[] = { + DEVICE_BUILDER(__qc_soc_id_parse_dt, NULL), + DEVICE_BUILDER(__qc_soc_id_read_qfprom_jtag, NULL), + DEVICE_BUILDER(__qc_soc_id_read_jtag_id, NULL), + DEVICE_BUILDER(__qc_soc_id_sec_class_create, + __qc_soc_id_sec_class_remove), + DEVICE_BUILDER(__qc_soc_id_sysfs_create, + __qc_soc_id_sysfs_remove), +}; + +static int sec_qc_soc_id_probe(struct platform_device *pdev) +{ + return __qc_soc_id_probe(pdev, __qc_soc_id_dev_builder, + ARRAY_SIZE(__qc_soc_id_dev_builder)); +} + +static int sec_qc_soc_id_remove(struct platform_device *pdev) +{ + return __qc_soc_id_remove(pdev, __qc_soc_id_dev_builder, + ARRAY_SIZE(__qc_soc_id_dev_builder)); +} + +static const struct of_device_id sec_qc_soc_id_match_table[] = { + { .compatible = "samsung,qcom-soc_id" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_qc_soc_id_match_table); + +static struct platform_driver sec_qc_soc_id_driver = { + .driver = { + .name = "sec,qc-soc_id", + .of_match_table = of_match_ptr(sec_qc_soc_id_match_table), + }, + .probe = sec_qc_soc_id_probe, + .remove = sec_qc_soc_id_remove, +}; + +static int __init sec_qc_soc_id_init(void) +{ + if (!IS_ENABLED(CONFIG_SEC_QC_SOC_ID_EN)) + return 0; + + return platform_driver_register(&sec_qc_soc_id_driver); +} +module_init(sec_qc_soc_id_init); + +static __exit void sec_qc_soc_id_exit(void) +{ + if (!IS_ENABLED(CONFIG_SEC_QC_SOC_ID_EN)) + return; + + platform_driver_unregister(&sec_qc_soc_id_driver); +} +module_exit(sec_qc_soc_id_exit); + +/* Module information */ +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("SoC ID for Qualcomm SoC based models"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/debug/qcom/soc_id/sec_qc_soc_id.h b/drivers/samsung/debug/qcom/soc_id/sec_qc_soc_id.h new file mode 100644 index 000000000000..56343e73b15b --- /dev/null +++ b/drivers/samsung/debug/qcom/soc_id/sec_qc_soc_id.h @@ -0,0 +1,32 @@ +#ifndef __INTERNAL__SEC_QC_SOC_ID_H__ +#define __INTERNAL__SEC_QC_SOC_ID_H__ + +#include + +#include + +union qfprom_jtag { + u32 raw; + struct { + u32 jtag_id:20; + u32 feature_id:8; + u32 reserved_0:4; + }; +}; + +struct jtag_id { + u32 raw; +}; + +struct qc_soc_id_drvdata { + struct builder bd; + struct device *sec_misc_dev; + bool use_qfprom_jtag; + phys_addr_t qfprom_jtag_phys; + union qfprom_jtag qfprom_jtag_data; + bool use_jtag_id; + phys_addr_t jtag_id_phys; + struct jtag_id jtag_id_data; +}; + +#endif /* __INTERNAL__SEC_QC_SOC_ID_H__ */ diff --git a/drivers/samsung/debug/qcom/summary/Kconfig b/drivers/samsung/debug/qcom/summary/Kconfig new file mode 100644 index 000000000000..c0d67bdc461a --- /dev/null +++ b/drivers/samsung/debug/qcom/summary/Kconfig @@ -0,0 +1,4 @@ +config SEC_QC_SUMMARY + tristate "SEC Debug Summary for Qualcomm SoC based models" + help + TODO: help is not ready. diff --git a/drivers/samsung/debug/qcom/summary/Makefile b/drivers/samsung/debug/qcom/summary/Makefile new file mode 100644 index 000000000000..eb292fe71829 --- /dev/null +++ b/drivers/samsung/debug/qcom/summary/Makefile @@ -0,0 +1,24 @@ +obj-$(CONFIG_SEC_QC_SUMMARY) += sec_qc_summary.o +sec_qc_summary-objs := sec_qc_summary_main.o \ + sec_qc_summary_task.o \ + sec_qc_summary_kconst.o \ + sec_qc_summary_ksyms.o \ + sec_qc_summary_sched_log.o \ + sec_qc_summary_aplpm.o \ + sec_qc_summary_dump_sink.o \ + sec_qc_summary_debug_kinfo.o \ + sec_qc_summary_kallsyms.o \ + sec_qc_summary_mock.o + +sec_qc_summary-$(CONFIG_ARM64) += sec_qc_summary_arm64_ap_context.o \ + sec_qc_summary_var_mon.o \ + sec_qc_summary_coreinfo.o + +__SEC_QC_SUMMARY_BUILD_ROOT := $(shell echo $(srctree) | sed -e 's|/android/kernel_platform/.\+$$||' -e 's|^.\+/||') +CFLAGS_sec_qc_summary_var_mon.o = -D__SEC_QC_SUMMARY_BUILD_ROOT=$(__SEC_QC_SUMMARY_BUILD_ROOT) +CFLAGS_sec_qc_summary_main.o = -I$(srctree)/drivers/soc/qcom/ + +ccflags-y += -I$(srctree)/drivers/android + +# FIXME: too tough including path +CFLAGS_sec_qc_summary_coreinfo.o = -I$(srctree) diff --git a/drivers/samsung/debug/qcom/summary/sec_qc_summary.h b/drivers/samsung/debug/qcom/summary/sec_qc_summary.h new file mode 100644 index 000000000000..007babf9adbe --- /dev/null +++ b/drivers/samsung/debug/qcom/summary/sec_qc_summary.h @@ -0,0 +1,116 @@ +#ifndef __INTERNAL__SEC_QC_SUMMARY_H__ +#define __INTERNAL__SEC_QC_SUMMARY_H__ + +#include +#include + +#include +#include +#include + +struct qc_summary_kallsyms { + const unsigned long *addresses; + const int *offsets; + const u8 *names; + unsigned int num_syms; + unsigned long relative_base; + const char *token_table; + const u16 *token_index; + const unsigned int *markers; +}; + +struct qc_summary_ap_context { + struct sec_arm64_ap_context *ap_context; + struct notifier_block nb; +}; + +struct qc_summary_drvdata { + struct builder bd; + size_t smem_offset; + struct sec_qc_summary *summary; + struct notifier_block nb_die; + struct notifier_block nb_panic; + struct reserved_mem *debug_kinfo_rmem; + struct qc_summary_kallsyms kallsyms; + struct qc_summary_ap_context ap_context; +}; + +#if !IS_ENABLED(CONFIG_ARM64) +static inline u64 __phys_to_kimg(unsigned long physaddr) +{ + return (uintptr_t)phys_to_virt(physaddr); +} +#endif + +static inline struct sec_qc_summary_data_apss *secdbg_apss( + struct qc_summary_drvdata *drvdata) +{ + struct sec_qc_summary *dbg_summary = drvdata->summary; + + return &dbg_summary->priv.apss; +} + + +static inline struct sec_qc_summary_data_modem *secdbg_modem( + struct qc_summary_drvdata *drvdata) +{ + struct sec_qc_summary *dbg_summary = drvdata->summary; + + return &dbg_summary->priv.modem; +} + +/* sec_qc_summary_var_mon.c */ +extern int __qc_summary_info_mon_init(struct builder *bd); +extern int __qc_summary_var_mon_init_last_pet(struct builder *bd); +extern int __qc_summary_var_mon_init_last_ns(struct builder *bd); +extern void __qc_summary_var_mon_exit_last_pet(struct builder *bd); + +/* sec_qc_summary_task.c */ +extern int __qc_summary_task_init(struct builder *bd); + +/* sec_qc_summary_kconst.c */ +extern int __qc_summary_kconst_init(struct builder *bd); + +/* sec_qc_summary_ksyms.c */ +/* implemented @ kernel/kallsyms.c */ +extern const unsigned long kallsyms_addresses[] __weak; +extern const int kallsyms_offsets[] __weak; +extern const u8 kallsyms_names[] __weak; +extern const unsigned long kallsyms_num_syms +__attribute__((weak, section(".rodata"))); +extern const unsigned long kallsyms_relative_base +__attribute__((weak, section(".rodata"))); +extern const u8 kallsyms_token_table[] __weak; +extern const u16 kallsyms_token_index[] __weak; +extern const unsigned long kallsyms_markers[] __weak; + +extern int __qc_summary_ksyms_init(struct builder *bd); + +/* sec_qc_summary_sched_log.c */ +extern int __qc_summary_sched_log_init(struct builder *bd); + +/* sec_qc_summary_aplmp.c */ +/* implemented @ kernel/sched/core.c */ +DECLARE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues); + +extern int __qc_summary_aplpm_init(struct builder *bd); + +/* sec_qc_summary_arm64_ap_context.c */ +extern int __qc_summary_arm64_ap_context_init(struct builder *bd); +extern void __qc_summary_arm64_ap_context_exit(struct builder *bd); + +/* sec_qc_summary_coreinfo.c */ +extern int __qc_summary_coreinfo_init(struct builder *bd); +extern void __qc_summary_coreinfo_exit(struct builder *bd); + +/* sec_qc_summary_dump_sink.c */ +extern int __qc_summary_dump_sink_init(struct builder *bd); + +/* sec_qc_summary_debug_kinfo.c */ +extern int __qc_summary_debug_kinfo_init(struct builder *bd); + +/* sec_qc_summary_kallsyms.c */ +extern int __qc_summary_kallsyms_init(struct builder *bd); +extern unsigned long __qc_summary_kallsyms_lookup_name(struct qc_summary_drvdata *drvdata, const char *name); + +#endif /* __INTERNAL__SEC_QC_SUMMARY_H__ */ diff --git a/drivers/samsung/debug/qcom/summary/sec_qc_summary_aplpm.c b/drivers/samsung/debug/qcom/summary/sec_qc_summary_aplpm.c new file mode 100644 index 000000000000..7fa6980fdaae --- /dev/null +++ b/drivers/samsung/debug/qcom/summary/sec_qc_summary_aplpm.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2016-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#include +#include + + +#include "sec_qc_summary.h" +#include "sec_qc_summary_external.h" + +static void __summary_set_lpm_info_runqueues( + struct sec_qc_summary_aplpm *aplpm) +{ + /* TODO: there is no way to know this variable in module */ + aplpm->p_runqueues = virt_to_phys((void *)&runqueues); +} + +int notrace __qc_summary_aplpm_init(struct builder *bd) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + struct sec_qc_summary_aplpm *aplpm = + &(secdbg_apss(drvdata)->aplpm); + + __summary_set_lpm_info_runqueues(aplpm); + + sec_qc_summary_set_sched_walt_info(secdbg_apss(drvdata)); + + return 0; +} diff --git a/drivers/samsung/debug/qcom/summary/sec_qc_summary_arm64_ap_context.c b/drivers/samsung/debug/qcom/summary/sec_qc_summary_arm64_ap_context.c new file mode 100644 index 000000000000..9312676d3c82 --- /dev/null +++ b/drivers/samsung/debug/qcom/summary/sec_qc_summary_arm64_ap_context.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2020-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#include +#include +#include + +#include + +#include "sec_qc_summary.h" + +struct sec_debug_mmu_reg_t { + uint64_t TTBR0_EL1; + uint64_t TTBR1_EL1; + uint64_t TCR_EL1; + uint64_t MAIR_EL1; + uint64_t ATCR_EL1; + uint64_t AMAIR_EL1; + + uint64_t HSTR_EL2; + uint64_t HACR_EL2; + uint64_t TTBR0_EL2; + uint64_t VTTBR_EL2; + uint64_t TCR_EL2; + uint64_t VTCR_EL2; + uint64_t MAIR_EL2; + uint64_t ATCR_EL2; + + uint64_t TTBR0_EL3; + uint64_t MAIR_EL3; + uint64_t ATCR_EL3; +}; + +/* ARM CORE regs mapping structure */ +struct sec_debug_core_t { + /* COMMON */ + uint64_t x0; + uint64_t x1; + uint64_t x2; + uint64_t x3; + uint64_t x4; + uint64_t x5; + uint64_t x6; + uint64_t x7; + uint64_t x8; + uint64_t x9; + uint64_t x10; + uint64_t x11; + uint64_t x12; + uint64_t x13; + uint64_t x14; + uint64_t x15; + uint64_t x16; + uint64_t x17; + uint64_t x18; + uint64_t x19; + uint64_t x20; + uint64_t x21; + uint64_t x22; + uint64_t x23; + uint64_t x24; + uint64_t x25; + uint64_t x26; + uint64_t x27; + uint64_t x28; + uint64_t x29; /* sp */ + uint64_t x30; /* lr */ + + uint64_t pc; /* pc */ + uint64_t cpsr; /* cpsr */ + + /* EL0 */ + uint64_t sp_el0; + + /* EL1 */ + uint64_t sp_el1; + uint64_t elr_el1; + uint64_t spsr_el1; + + /* EL2 */ + uint64_t sp_el2; + uint64_t elr_el2; + uint64_t spsr_el2; + + /* EL3 */ + /* uint64_t sp_el3; */ + /* uint64_t elr_el3; */ + /* uint64_t spsr_el3; */ +}; + +DEFINE_PER_CPU(struct sec_debug_core_t, sec_debug_core_reg); +DEFINE_PER_CPU(struct sec_debug_mmu_reg_t, sec_debug_mmu_reg); + +static inline void __summary_ap_context_core( + struct qc_summary_ap_context *qc_ap_context, int cpu) +{ + uint64_t pstate, which_el; + struct sec_arm64_ap_context *ap_context = + &qc_ap_context->ap_context[cpu]; + struct sec_debug_core_t *core_reg = &per_cpu(sec_debug_core_reg, cpu); + + core_reg->x0 = ap_context->core_regs.regs[0]; + core_reg->x1 = ap_context->core_regs.regs[1]; + core_reg->x2 = ap_context->core_regs.regs[2]; + core_reg->x3 = ap_context->core_regs.regs[3]; + core_reg->x4 = ap_context->core_regs.regs[4]; + core_reg->x5 = ap_context->core_regs.regs[5]; + core_reg->x6 = ap_context->core_regs.regs[6]; + core_reg->x7 = ap_context->core_regs.regs[7]; + core_reg->x8 = ap_context->core_regs.regs[8]; + core_reg->x9 = ap_context->core_regs.regs[9]; + core_reg->x10 = ap_context->core_regs.regs[10]; + core_reg->x11 = ap_context->core_regs.regs[11]; + core_reg->x12 = ap_context->core_regs.regs[12]; + core_reg->x13 = ap_context->core_regs.regs[13]; + core_reg->x14 = ap_context->core_regs.regs[14]; + core_reg->x15 = ap_context->core_regs.regs[15]; + core_reg->x16 = ap_context->core_regs.regs[16]; + core_reg->x17 = ap_context->core_regs.regs[17]; + core_reg->x18 = ap_context->core_regs.regs[18]; + core_reg->x19 = ap_context->core_regs.regs[19]; + core_reg->x20 = ap_context->core_regs.regs[20]; + core_reg->x21 = ap_context->core_regs.regs[21]; + core_reg->x22 = ap_context->core_regs.regs[22]; + core_reg->x23 = ap_context->core_regs.regs[23]; + core_reg->x24 = ap_context->core_regs.regs[24]; + core_reg->x25 = ap_context->core_regs.regs[25]; + core_reg->x26 = ap_context->core_regs.regs[26]; + core_reg->x27 = ap_context->core_regs.regs[27]; + core_reg->x28 = ap_context->core_regs.regs[28]; + core_reg->x29 = ap_context->core_regs.regs[29]; + core_reg->x30 = ap_context->core_regs.regs[30]; + + core_reg->sp_el1 = ap_context->core_regs.sp; + core_reg->pc = ap_context->core_regs.pc; + core_reg->cpsr = ap_context->core_regs.pstate; + + core_reg->sp_el0 = ap_context->core_extra_regs[IDX_CORE_EXTRA_SP_EL0]; + + pstate = ap_context->core_regs.pstate; + which_el = pstate & PSR_MODE_MASK; + + if (which_el >= PSR_MODE_EL2t) { + core_reg->sp_el1 = ap_context->core_extra_regs[IDX_CORE_EXTRA_SP_EL1]; + core_reg->elr_el1 = ap_context->core_extra_regs[IDX_CORE_EXTRA_ELR_EL1]; + core_reg->spsr_el1 = ap_context->core_extra_regs[IDX_CORE_EXTRA_SPSR_EL1]; + core_reg->sp_el2 = ap_context->core_extra_regs[IDX_CORE_EXTRA_SP_EL2]; + core_reg->elr_el2 = ap_context->core_extra_regs[IDX_CORE_EXTRA_ELR_EL2]; + core_reg->spsr_el2 = ap_context->core_extra_regs[IDX_CORE_EXTRA_SPSR_EL2]; + } +} + +static inline void __summary_ap_context_mmu( + struct qc_summary_ap_context *qc_ap_context, int cpu) +{ + struct sec_arm64_ap_context *ap_context = + &qc_ap_context->ap_context[cpu]; + struct sec_debug_mmu_reg_t *mmu_reg = &per_cpu(sec_debug_mmu_reg, cpu); + + mmu_reg->TTBR0_EL1 = ap_context->mmu_regs[IDX_MMU_TTBR0_EL1]; + mmu_reg->TTBR1_EL1 = ap_context->mmu_regs[IDX_MMU_TTBR1_EL1]; + mmu_reg->TCR_EL1 = ap_context->mmu_regs[IDX_MMU_TCR_EL1]; + mmu_reg->MAIR_EL1 = ap_context->mmu_regs[IDX_MMU_MAIR_EL1]; + mmu_reg->AMAIR_EL1 = ap_context->mmu_regs[IDX_MMU_AMAIR_EL1]; +} + +static inline int sec_qc_summary_ap_context_notifier( + struct notifier_block *this, unsigned long val, void *data) +{ + struct qc_summary_ap_context *qc_ap_context = + container_of(this, struct qc_summary_ap_context, nb); + int cpu; + + for (cpu = 0; cpu < num_possible_cpus(); cpu++) { + __summary_ap_context_core(qc_ap_context, cpu); + __summary_ap_context_mmu(qc_ap_context, cpu); + } + + return NOTIFY_OK; +} + +int notrace __qc_summary_arm64_ap_context_init(struct builder *bd) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + struct sec_qc_summary_cpu_context *cpu_reg = + &(secdbg_apss(drvdata)->cpu_reg); + struct qc_summary_ap_context *qc_ap_context = &drvdata->ap_context; + const struct sec_dbg_region_client *client; + int err; + + client = sec_dbg_region_find(SEC_ARM64_VH_IPI_STOP_MAGIC); + if (IS_ERR(client)) { + err = -EPROBE_DEFER; + goto err_find_ref_data; + } + + qc_ap_context->ap_context = (void *)client->virt; + + qc_ap_context->nb.notifier_call = sec_qc_summary_ap_context_notifier; + qc_ap_context->nb.priority = -256; /* should be run too lately */ + + err = atomic_notifier_chain_register(&panic_notifier_list, + &qc_ap_context->nb); + if (err < 0) { + err = -EINVAL; + goto err_register_panic_notifier; + } + + /* setup sec debug core reg info */ + cpu_reg->sec_debug_core_reg_paddr = virt_to_phys(&sec_debug_core_reg); + + return 0; + +err_register_panic_notifier: +err_find_ref_data: + return err; +} + +void __qc_summary_arm64_ap_context_exit(struct builder *bd) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + struct qc_summary_ap_context *qc_ap_context = &drvdata->ap_context; + + atomic_notifier_chain_unregister(&panic_notifier_list, + &qc_ap_context->nb); +} diff --git a/drivers/samsung/debug/qcom/summary/sec_qc_summary_coreinfo.c b/drivers/samsung/debug/qcom/summary/sec_qc_summary_coreinfo.c new file mode 100644 index 000000000000..4854dc8d23d0 --- /dev/null +++ b/drivers/samsung/debug/qcom/summary/sec_qc_summary_coreinfo.c @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2019-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + + +#include "sec_qc_summary.h" +#include "sec_qc_summary_coreinfo.h" +#include "sec_qc_summary_external.h" + +static char *summary_coreinfo_data; +static int32_t summary_coreinfo_size; +static void *summary_coreinfo_note; + +void sec_qc_summary_coreinfo_append_str(const char *fmt, ...) +{ + va_list args; + char *buf = &summary_coreinfo_data[summary_coreinfo_size]; + int32_t remained; + int32_t expected; + + remained = (int32_t)QC_SUMMARY_COREINFO_BYTES - summary_coreinfo_size; + if (remained < 0) { + pr_warn("No space left\n"); + return; + } + + va_start(args, fmt); + expected = (int32_t)vsnprintf(buf, remained, fmt, args); + va_end(args); + + if (expected >= remained || expected < 0) { + pr_warn("Not enough space to append %s\n", buf); + memset(buf, 0x0, remained); + return; + } + + summary_coreinfo_size += expected; +} + +static int __summary_coreinfo_alloc(void) +{ + summary_coreinfo_data = (char *)get_zeroed_page(GFP_KERNEL); + if (!summary_coreinfo_data) { + pr_err("Memory allocation for summary_coreinfo_data failed\n"); + return -ENOMEM; + } + + /* TODO: will be used in future? */ + summary_coreinfo_note = alloc_pages_exact(QC_SUMMARY_COREINFO_NOTE_SIZE, + GFP_KERNEL | __GFP_ZERO); + if (!summary_coreinfo_note) { + pr_warn("Memory allocation for summary_coreinfo_note failed\n"); + return -ENOMEM; + } + + return 0; +} + +static void __summary_coreinfo_free(void) +{ + if (!summary_coreinfo_data) { + free_page((unsigned long)summary_coreinfo_data); + summary_coreinfo_data = NULL; + } + + if (!summary_coreinfo_note) { + free_pages_exact(summary_coreinfo_note, + QC_SUMMARY_COREINFO_NOTE_SIZE); + summary_coreinfo_note = NULL; + } +} + +static void __summary_coreinfo_init_dbg_apps( + struct sec_qc_summary_data_apss *dbg_apss) +{ + dbg_apss->coreinfo.coreinfo_data = (uintptr_t)summary_coreinfo_data; + dbg_apss->coreinfo.coreinfo_size = (uintptr_t)&summary_coreinfo_size; + dbg_apss->coreinfo.coreinfo_note = (uintptr_t)summary_coreinfo_note; + + pr_info("coreinfo_data=0x%llx\n", dbg_apss->coreinfo.coreinfo_data); +} + + +#define QC_SUMMARY_COREINFO_KALLSYMS(__drvdata, __name) \ + sec_qc_summary_coreinfo_append_str("SYMBOL(%s)=0x%llx\n", #__name, \ + __qc_summary_kallsyms_lookup_name(__drvdata, #__name)) + +static void __summary_coreinfo_append_data(struct qc_summary_drvdata *drvdata) +{ + QC_SUMMARY_COREINFO_SYMBOL(runqueues); + QC_SUMMARY_COREINFO_STRUCT_SIZE(rq); + QC_SUMMARY_COREINFO_OFFSET(rq, clock); + QC_SUMMARY_COREINFO_OFFSET(rq, nr_running); + + QC_SUMMARY_COREINFO_OFFSET(task_struct, prio); + QC_SUMMARY_COREINFO_OFFSET(task_struct, mm); + QC_SUMMARY_COREINFO_OFFSET(task_struct, cred); + QC_SUMMARY_COREINFO_OFFSET(cred, uid); + + QC_SUMMARY_COREINFO_KALLSYMS(drvdata, __sched_text_start); + QC_SUMMARY_COREINFO_KALLSYMS(drvdata, __sched_text_end); + /* TODO: kernel/time/timekeeping.c + * static struct { + * seqcount_t seq; + * struct timekeeper timekeeper; + * } tk_core ____cacheline_aligned = { + * .seq = SEQCNT_ZERO(tk_core.seq), + * }; + */ + sec_qc_summary_coreinfo_append_str("OFFSET(tk_core.timekeeper)=%zu\n", + 8); + + QC_SUMMARY_COREINFO_OFFSET(timekeeper, xtime_sec); +} +/* TODO: 'struct mod_tree_root' is not exposed to the out of 'module.c' file. + * This can be modified according to the version of linux kernel. + */ +struct mod_tree_root { + struct latch_tree_root root; + unsigned long addr_min; + unsigned long addr_max; +}; + +static void __summary_coreinfo_append_module_data( + struct qc_summary_drvdata *drvdata) +{ + QC_SUMMARY_COREINFO_KALLSYMS(drvdata, mod_tree); + + QC_SUMMARY_COREINFO_OFFSET(mod_tree_root, root); + QC_SUMMARY_COREINFO_OFFSET(mod_tree_root, addr_min); + QC_SUMMARY_COREINFO_OFFSET(mod_tree_root, addr_max); + + QC_SUMMARY_COREINFO_OFFSET(module_layout, base); + QC_SUMMARY_COREINFO_OFFSET(module_layout, size); + QC_SUMMARY_COREINFO_OFFSET(module_layout, text_size); + QC_SUMMARY_COREINFO_OFFSET(module_layout, mtn); + + QC_SUMMARY_COREINFO_OFFSET(mod_tree_node, node); + + QC_SUMMARY_COREINFO_OFFSET(module, init_layout); + QC_SUMMARY_COREINFO_OFFSET(module, core_layout); + QC_SUMMARY_COREINFO_OFFSET(module, state); + QC_SUMMARY_COREINFO_OFFSET(module, name); + QC_SUMMARY_COREINFO_OFFSET(module, kallsyms); + + QC_SUMMARY_COREINFO_OFFSET(mod_kallsyms, symtab); + QC_SUMMARY_COREINFO_OFFSET(mod_kallsyms, num_symtab); + QC_SUMMARY_COREINFO_OFFSET(mod_kallsyms, strtab); + + QC_SUMMARY_COREINFO_OFFSET(latch_tree_root, seq); + QC_SUMMARY_COREINFO_OFFSET(latch_tree_root, tree); + QC_SUMMARY_COREINFO_OFFSET(seqcount, sequence); +} + +int __qc_summary_coreinfo_init(struct builder *bd) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + struct sec_qc_summary_data_apss *dbg_apss = secdbg_apss(drvdata); + int err; + + err = __summary_coreinfo_alloc(); + if (err) + goto err_coreinfo_alloc_failed; + + __summary_coreinfo_init_dbg_apps(dbg_apss); + __summary_coreinfo_append_data(drvdata); + __summary_coreinfo_append_module_data(drvdata); + + return 0; + +err_coreinfo_alloc_failed: + __summary_coreinfo_free(); + return err; +} + +void __qc_summary_coreinfo_exit(struct builder *bd) +{ + __summary_coreinfo_free(); +} diff --git a/drivers/samsung/debug/qcom/summary/sec_qc_summary_coreinfo.h b/drivers/samsung/debug/qcom/summary/sec_qc_summary_coreinfo.h new file mode 100644 index 000000000000..f7f6e7ee7167 --- /dev/null +++ b/drivers/samsung/debug/qcom/summary/sec_qc_summary_coreinfo.h @@ -0,0 +1,31 @@ +#ifndef __INTERNAL__SEC_QC_SUMMARY_COREINFO_H__ +#define __INTERNAL__SEC_QC_SUMMARY_COREINFO_H__ + +#include +#include +#include + +#define QC_SUMMARY_CORE_NOTE_NAME "CORE" +#define QC_SUMMARY_CORE_NOTE_HEAD_BYTES ALIGN(sizeof(struct elf_note), 4) + +#define QC_SUMMARY_CORE_NOTE_NAME_BYTES ALIGN(sizeof(QC_SUMMARY_CORE_NOTE_NAME), 4) +#define QC_SUMMARY_CORE_NOTE_DESC_BYTES ALIGN(sizeof(struct elf_prstatus), 4) + +#define QC_SUMMARY_CORE_NOTE_BYTES ((QC_SUMMARY_CORE_NOTE_HEAD_BYTES * 2) + QC_SUMMARY_CORE_NOTE_NAME_BYTES + QC_SUMMARY_CORE_NOTE_DESC_BYTES) + +#define QC_SUMMARY_COREINFO_BYTES (PAGE_SIZE) +#define QC_SUMMARY_COREINFO_NOTE_NAME "QC_SUMMARY_COREINFO" +#define QC_SUMMARY_COREINFO_NOTE_NAME_BYTES ALIGN(sizeof(QC_SUMMARY_COREINFO_NOTE_NAME), 4) +#define QC_SUMMARY_COREINFO_NOTE_SIZE ((QC_SUMMARY_CORE_NOTE_HEAD_BYTES * 2) + QC_SUMMARY_COREINFO_NOTE_NAME_BYTES + QC_SUMMARY_COREINFO_BYTES) + +#define QC_SUMMARY_COREINFO_SYMBOL(name) sec_qc_summary_coreinfo_append_str("SYMBOL(%s)=0x%llx\n", #name, (unsigned long long)&name) +#define QC_SUMMARY_COREINFO_SYMBOL_ARRAY(name) sec_qc_summary_coreinfo_append_str("SYMBOL(%s)=0x%llx\n", #name, (unsigned long long)name) +#define QC_SUMMARY_COREINFO_SIZE(name) sec_qc_summary_coreinfo_append_str("SIZE(%s)=%zu\n", #name, sizeof(name)) +#define QC_SUMMARY_COREINFO_STRUCT_SIZE(name) sec_qc_summary_coreinfo_append_str("SIZE(%s)=%zu\n", #name, sizeof(struct name)) +#define QC_SUMMARY_COREINFO_OFFSET(name, field) sec_qc_summary_coreinfo_append_str("OFFSET(%s.%s)=%zu\n", #name, #field, (size_t)offsetof(struct name, field)) +#define QC_SUMMARY_COREINFO_LENGTH(name, value) sec_qc_summary_coreinfo_append_str("LENGTH(%s)=%llu\n", #name, (unsigned long long)value) +#define QC_SUMMARY_COREINFO_NUMBER(name) sec_qc_summary_coreinfo_append_str("NUMBER(%s)=%lld\n", #name, (long long)name) + +void sec_qc_summary_coreinfo_append_str(const char *fmt, ...); + +#endif /* __INTERNAL__SEC_QC_SUMMARY_COREINFO_H__ */ diff --git a/drivers/samsung/debug/qcom/summary/sec_qc_summary_debug_kinfo.c b/drivers/samsung/debug/qcom/summary/sec_qc_summary_debug_kinfo.c new file mode 100644 index 000000000000..51bf453d9caf --- /dev/null +++ b/drivers/samsung/debug/qcom/summary/sec_qc_summary_debug_kinfo.c @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include + +#include + +#include "sec_qc_summary.h" + +int __qc_summary_debug_kinfo_init(struct builder *bd) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + struct kernel_all_info *all_kinfo; + + if (!IS_ENABLED(CONFIG_ANDROID_DEBUG_KINFO) || + IS_BUILTIN(CONFIG_SEC_QC_SUMMARY)) + return 0; + + if (!drvdata->debug_kinfo_rmem) + return 0; + + all_kinfo = drvdata->debug_kinfo_rmem->priv; + if (all_kinfo->magic_number != DEBUG_KINFO_MAGIC) + return -EPROBE_DEFER; + + return 0; +} diff --git a/drivers/samsung/debug/qcom/summary/sec_qc_summary_dump_sink.c b/drivers/samsung/debug/qcom/summary/sec_qc_summary_dump_sink.c new file mode 100644 index 000000000000..7c89c8113482 --- /dev/null +++ b/drivers/samsung/debug/qcom/summary/sec_qc_summary_dump_sink.c @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2016-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#include +#include + +#include + +#include "sec_qc_summary.h" + +int notrace __qc_summary_dump_sink_init(struct builder *bd) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + + secdbg_apss(drvdata)->dump_sink_paddr = sec_debug_get_dump_sink_phys(); + + return 0; +} diff --git a/drivers/samsung/debug/qcom/summary/sec_qc_summary_external.h b/drivers/samsung/debug/qcom/summary/sec_qc_summary_external.h new file mode 100644 index 000000000000..98579317552d --- /dev/null +++ b/drivers/samsung/debug/qcom/summary/sec_qc_summary_external.h @@ -0,0 +1,44 @@ +#ifndef __INTERNAL__SEC_QC_SUMMARY_EXTERNAL_H__ +#define __INTERNAL__SEC_QC_SUMMARY_EXTERNAL_H__ + +/* implemented @ drivers/soc/qcom/memory_dump_v2.c */ +#if IS_ENABLED(CONFIG_QCOM_MEMORY_DUMP_V2) +extern void sec_qc_summary_set_msm_memdump_info(struct sec_qc_summary_data_apss *apss); +#else +#include +#endif + +/* implemented @ kernel/sched/walt/walt.c */ +#if IS_ENABLED(CONFIG_SCHED_WALT) +extern void sec_qc_summary_set_sched_walt_info(struct sec_qc_summary_data_apss *apss); +#else +#include +#endif + +/* implemented @ kernel/trace/msm_rtb.c */ +#if IS_ENABLED(CONFIG_QCOM_RTB) +extern void sec_qc_summary_set_rtb_info(struct sec_qc_summary_data_apss *apss); +#else +#include +#endif + +/* implemented @ drivers/soc/qcom/smem.c */ +#if IS_ENABLED(CONFIG_QCOM_SMEM) +#include +#else +#include +#endif + +/* implemented @ drivers/soc/qcom/smem.c */ +#if IS_ENABLED(CONFIG_QCOM_WDT_CORE) +#include + +extern int qcom_wdt_pet_register_notifier(struct notifier_block *nb); +extern int qcom_wdt_pet_unregister_notifier(struct notifier_block *nb); +extern int qcom_wdt_bark_register_notifier(struct notifier_block *nb); +extern int qcom_wdt_bark_unregister_notifier(struct notifier_block *nb); +#else +#include +#endif + +#endif /* __INTERNAL__SEC_QC_SUMMARY_EXTERNAL_H__ */ diff --git a/drivers/samsung/debug/qcom/summary/sec_qc_summary_kallsyms.c b/drivers/samsung/debug/qcom/summary/sec_qc_summary_kallsyms.c new file mode 100644 index 000000000000..4203df8933c1 --- /dev/null +++ b/drivers/samsung/debug/qcom/summary/sec_qc_summary_kallsyms.c @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include + +#include + +#include "sec_qc_summary.h" + +/* NOTE: copy of 'kallsyms_expand_symbol' */ +static unsigned int __kallsyms_expand_symbol( + const struct qc_summary_kallsyms *kallsyms, + unsigned int off, char *result, size_t maxlen) +{ + int len, skipped_first = 0; + const char *tptr; + const u8 *data; + + data = &kallsyms->names[off]; + len = *data; + data++; + + off += len + 1; + + while (len) { + tptr = &kallsyms->token_table[kallsyms->token_index[*data]]; + data++; + len--; + + while (*tptr) { + if (skipped_first) { + if (maxlen <= 1) + goto tail; + *result = *tptr; + result++; + maxlen--; + } else + skipped_first = 1; + tptr++; + } + } + +tail: + if (maxlen) + *result = '\0'; + + return off; +} + +/* NOTE: copy of 'kallsyms_sym_address' */ +static unsigned long __kallsyms_sym_address( + const struct qc_summary_kallsyms *kallsyms, + int idx) +{ + if (kallsyms->addresses) + return kallsyms->addresses[idx]; + + /* values are unsigned offsets if --absolute-percpu is not in effect */ + if (kallsyms->relative_base) + return kallsyms->relative_base + (u32)kallsyms->offsets[idx]; + + /* ...otherwise, positive offsets are absolute values */ + if (kallsyms->offsets[idx] >= 0) + return kallsyms->offsets[idx]; + + /* ...and negative offsets are relative to kallsyms_relative_base - 1 */ + return kallsyms->relative_base - 1 - kallsyms->offsets[idx]; +} + +/* NOTE: copy of 'cleanup_symbol_name' */ +#if defined(CONFIG_CFI_CLANG) && defined(CONFIG_LTO_CLANG_THIN) +static inline char *__cleanup_symbol_name(char *s) +{ + char *res = NULL; + + res = strrchr(s, '$'); + if (res) + *res = '\0'; + + return res; +} +#else +static inline char *__cleanup_symbol_name(char *s) { return NULL; } +#endif + +static unsigned long __kallsyms_lookup_name( + const struct qc_summary_kallsyms *kallsyms, + const char *name) +{ + char namebuf[KSYM_NAME_LEN]; + unsigned long i; + unsigned int off; + + if (!kallsyms->names) + return 0; + + for (i = 0, off = 0; i < kallsyms->num_syms; i++) { + off = __kallsyms_expand_symbol(kallsyms, + off, namebuf, ARRAY_SIZE(namebuf)); + + if (strcmp(namebuf, name) == 0) + return __kallsyms_sym_address(kallsyms, i); + + if (__cleanup_symbol_name(namebuf) && strcmp(namebuf, name) == 0) + return __kallsyms_sym_address(kallsyms, i); + } + + return 0; +} + +int __qc_summary_kallsyms_init(struct builder *bd) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + struct qc_summary_kallsyms *kallsyms = &drvdata->kallsyms; + const struct kernel_all_info *all_kinfo = + drvdata->debug_kinfo_rmem->priv; + const struct kernel_info *kinfo = &(all_kinfo->info); + + if (IS_BUILTIN(CONFIG_SEC_QC_SUMMARY)) + return 0; + + if (!kinfo->enabled_all) + return 0; + + if (kinfo->enabled_absolute_percpu) { + kallsyms->addresses = (unsigned long *)__phys_to_kimg(kinfo->_addresses_pa); + kallsyms->relative_base = 0x0; + kallsyms->offsets = 0x0; + } else { + kallsyms->addresses = 0x0; + kallsyms->relative_base = __phys_to_kimg(kinfo->_relative_pa); + kallsyms->offsets = (int *)__phys_to_kimg(kinfo->_offsets_pa); + } + + kallsyms->names = (u8 *)__phys_to_kimg(kinfo->_names_pa); + kallsyms->num_syms = kinfo->num_syms; + kallsyms->token_table = (char *)__phys_to_kimg(kinfo->_token_table_pa); + kallsyms->token_index = (u16 *)__phys_to_kimg(kinfo->_token_index_pa); + kallsyms->markers = (unsigned int *)__phys_to_kimg(kinfo->_markers_pa); + + return 0; +} + +/* TODO: DO NOT EXPORT THIS FUNCTION!!! */ +unsigned long __qc_summary_kallsyms_lookup_name( + struct qc_summary_drvdata *drvdata, + const char *name) +{ + const struct qc_summary_kallsyms *kallsyms = &drvdata->kallsyms; + unsigned long addr; + + if (IS_BUILTIN(CONFIG_SEC_QC_SUMMARY)) + return kallsyms_lookup_name(name); + + addr = __kallsyms_lookup_name(kallsyms, name); + + return addr; +} diff --git a/drivers/samsung/debug/qcom/summary/sec_qc_summary_kconst.c b/drivers/samsung/debug/qcom/summary/sec_qc_summary_kconst.c new file mode 100644 index 000000000000..a3ff83b90884 --- /dev/null +++ b/drivers/samsung/debug/qcom/summary/sec_qc_summary_kconst.c @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2016-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#include +#include +#include +#include + +#include + +#include "sec_qc_summary.h" + +static void __summary_kconst_init_common( + struct qc_summary_drvdata *drvdata) +{ + struct sec_qc_summary_kconst *kconst = + &(secdbg_apss(drvdata)->kconst); + + kconst->nr_cpus = num_possible_cpus(); + kconst->page_offset = PAGE_OFFSET; + kconst->vmap_stack = !!IS_ENABLED(CONFIG_VMAP_STACK); + +#if IS_ENABLED(CONFIG_ARM64) + kconst->phys_offset = PHYS_OFFSET; + kconst->va_bits = VA_BITS; + kconst->kimage_vaddr = kimage_vaddr; + kconst->kimage_voffset = kimage_voffset; +#endif + +#if IS_ENABLED(CONFIG_SMP) + kconst->per_cpu_offset.pa = virt_to_phys(__per_cpu_offset); + kconst->per_cpu_offset.size = sizeof(__per_cpu_offset[0]); + kconst->per_cpu_offset.count = ARRAY_SIZE(__per_cpu_offset); +#endif +} + +static void __summary_kconst_init_builtin( + struct qc_summary_drvdata *drvdata) +{ + struct sec_qc_summary_kconst *kconst = + &(secdbg_apss(drvdata)->kconst); + + kconst->swapper_pg_dir_paddr = __pa_symbol(swapper_pg_dir); +} + +static inline void __summary_kconst_init_module( + struct qc_summary_drvdata *drvdata) +{ + struct sec_qc_summary_kconst *kconst = + &(secdbg_apss(drvdata)->kconst); + const struct kernel_all_info *all_kinfo = + drvdata->debug_kinfo_rmem->priv; + const struct kernel_info *kinfo = &(all_kinfo->info); + + kconst->swapper_pg_dir_paddr = kinfo->swapper_pg_dir_pa; +} + +int notrace __qc_summary_kconst_init(struct builder *bd) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + + __summary_kconst_init_common(drvdata); + + if (IS_BUILTIN(CONFIG_SEC_QC_SUMMARY)) + __summary_kconst_init_builtin(drvdata); + else + __summary_kconst_init_module(drvdata); + + return 0; +} diff --git a/drivers/samsung/debug/qcom/summary/sec_qc_summary_ksyms.c b/drivers/samsung/debug/qcom/summary/sec_qc_summary_ksyms.c new file mode 100644 index 000000000000..517c42f8e88b --- /dev/null +++ b/drivers/samsung/debug/qcom/summary/sec_qc_summary_ksyms.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2016-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#include +#include + +#include + +#include "sec_qc_summary.h" + +static inline void __summary_ksyms_init_builtin( + struct qc_summary_drvdata *drvdata) +{ + struct sec_qc_summary_ksyms *ksyms = + &(secdbg_apss(drvdata)->ksyms); + + if (!IS_ENABLED(CONFIG_KALLSYMS_BASE_RELATIVE)) { + ksyms->addresses_pa = __pa(kallsyms_addresses); + ksyms->relative_base = 0x0; + ksyms->offsets_pa = 0x0; + } else { + ksyms->addresses_pa = 0x0; + ksyms->relative_base = (uint64_t)kallsyms_relative_base; + ksyms->offsets_pa = __pa(kallsyms_offsets); + } + + ksyms->names_pa = __pa(kallsyms_names); + ksyms->num_syms = kallsyms_num_syms; + ksyms->token_table_pa = __pa(kallsyms_token_table); + ksyms->token_index_pa = __pa(kallsyms_token_index); + ksyms->markers_pa = __pa(kallsyms_markers); + + ksyms->sect.sinittext = (uintptr_t)_sinittext; + ksyms->sect.einittext = (uintptr_t)_einittext; + ksyms->sect.stext = (uintptr_t)_stext; + ksyms->sect.etext = (uintptr_t)_etext; + ksyms->sect.end = (uintptr_t)_end; + + ksyms->kallsyms_all = !!IS_ENABLED(CONFIG_KALLSYMS_ALL); + ksyms->magic = SEC_DEBUG_SUMMARY_MAGIC1; +} + +static inline void __summary_ksyms_init_module( + struct qc_summary_drvdata *drvdata) +{ + struct sec_qc_summary_ksyms *ksyms = + &(secdbg_apss(drvdata)->ksyms); + const struct kernel_all_info *all_kinfo = + drvdata->debug_kinfo_rmem->priv; + const struct kernel_info *kinfo = &(all_kinfo->info); + + if (kinfo->enabled_absolute_percpu) { + ksyms->addresses_pa = kinfo->_addresses_pa; + ksyms->relative_base = 0x0; + ksyms->offsets_pa = 0x0; + } else { + ksyms->addresses_pa = 0x0; + ksyms->relative_base = __phys_to_kimg(kinfo->_relative_pa); + ksyms->offsets_pa = kinfo->_offsets_pa; + } + + ksyms->names_pa = kinfo->_names_pa; + ksyms->num_syms = kinfo->num_syms; + ksyms->token_table_pa = kinfo->_token_table_pa; + ksyms->token_index_pa = kinfo->_token_index_pa; + ksyms->markers_pa = kinfo->_markers_pa; + + ksyms->sect.sinittext = __phys_to_kimg(kinfo->_sinittext_pa); + ksyms->sect.einittext = __phys_to_kimg(kinfo->_einittext_pa); + ksyms->sect.stext = __phys_to_kimg(kinfo->_stext_pa); + ksyms->sect.etext = __phys_to_kimg(kinfo->_etext_pa); + ksyms->sect.end = __phys_to_kimg(kinfo->_end_pa); + + ksyms->kallsyms_all = !!kinfo->enabled_all; + ksyms->magic = SEC_DEBUG_SUMMARY_MAGIC1; +} + +int notrace __qc_summary_ksyms_init(struct builder *bd) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + + if (IS_BUILTIN(CONFIG_SEC_QC_SUMMARY)) + __summary_ksyms_init_builtin(drvdata); + else + __summary_ksyms_init_module(drvdata); + + return 0; +} diff --git a/drivers/samsung/debug/qcom/summary/sec_qc_summary_main.c b/drivers/samsung/debug/qcom/summary/sec_qc_summary_main.c new file mode 100644 index 000000000000..3979a1ec1dbb --- /dev/null +++ b/drivers/samsung/debug/qcom/summary/sec_qc_summary_main.c @@ -0,0 +1,491 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2016-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "sec_qc_summary.h" +#include "sec_qc_summary_external.h" + +struct qc_summary_drvdata *qc_summary; + +static __always_inline bool __summary_is_probed(void) +{ + return !!qc_summary; +} + +struct sec_qc_summary_data_modem *sec_qc_summary_get_modem(void) +{ + if (!__summary_is_probed()) + return ERR_PTR(-EBUSY); + + return secdbg_modem(qc_summary); +} +EXPORT_SYMBOL_GPL(sec_qc_summary_get_modem); + +static noinline int __summary_parse_dt_die_notifier_priority(struct builder *bd, + struct device_node *np) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + s32 priority; + int err; + + err = of_property_read_s32(np, "sec,die_notifier-priority", + &priority); + if (err) + return -EINVAL; + + drvdata->nb_die.priority = (int)priority; + + return 0; +} + +static noinline int __summary_parse_dt_panic_notifier_priority(struct builder *bd, + struct device_node *np) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + s32 priority; + int err; + + err = of_property_read_s32(np, "sec,panic_notifier-priority", + &priority); + if (err) + return -EINVAL; + + drvdata->nb_panic.priority = (int)priority; + + return 0; +} + +static noinline int __summary_parse_dt_smem_offset(struct builder *bd, struct device_node *np) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + u32 smem_offset; + int err; + + /* NOTE: + * android13-5.15.y and before : optional. if not found assign '0'. + * android14-6.1.y and after : mandatory. + */ + err = of_property_read_u32(np, "sec,smem_offset", &smem_offset); + if (err) + return -EINVAL; + + drvdata->smem_offset = (size_t)smem_offset; + + return 0; +} + +/* NOTE: this fucntion doe not return error codes. + * When this function fails in this function, + * then, just, 'google,debug_kinfo' is not used by this module. + */ +static noinline int __summary_parse_dt_memory_region_google_debug_kinfo( + struct builder *bd, struct device_node *np) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + struct device *dev = bd->dev; + int idx; + struct device_node *mem_np; + struct reserved_mem *rmem; + + if (!IS_ENABLED(CONFIG_ANDROID_DEBUG_KINFO) || IS_BUILTIN(CONFIG_SEC_QC_SUMMARY)) + return 0; + + if (IS_ENABLED(CONFIG_QCOM_DEBUG_SYMBOL) && !debug_symbol_available()) + return -EPROBE_DEFER; + + idx = of_property_match_string(np, "memory-region-names", + "google,debug_kinfo"); + mem_np = of_parse_phandle(np, "memory-region", idx); + if (!mem_np) { + dev_warn(dev, "failed to parse memory-region\n"); + return 0; + } + + rmem = of_reserved_mem_lookup(mem_np); + if (!rmem) { + dev_warn(dev, "failed to get a reserved memory (%s)\n", + mem_np->name); + return 0; + } + + drvdata->debug_kinfo_rmem = rmem; + if (!rmem->priv) { + /* NOTE: ioreamp permanently to prevent 'iounmap' + * by -EPROBE_DEFER + */ + void __iomem *kinfo_va = memremap(rmem->base, rmem->size, MEMREMAP_WB); + + memset(kinfo_va, 0x0, rmem->size); + rmem->priv = kinfo_va; + return -EPROBE_DEFER; + } + + return 0; +} + +static const struct dt_builder __summary_dt_builder[] = { + DT_BUILDER(__summary_parse_dt_memory_region_google_debug_kinfo), + /* concrete builders which can return -EPROBE_DEFER should be + * placed before here. + */ + DT_BUILDER(__summary_parse_dt_die_notifier_priority), + DT_BUILDER(__summary_parse_dt_panic_notifier_priority), + DT_BUILDER(__summary_parse_dt_smem_offset), +}; + +static noinline int __summary_parse_dt(struct builder *bd) +{ + return sec_director_parse_dt(bd, __summary_dt_builder, + ARRAY_SIZE(__summary_dt_builder)); +} + +static noinline int __summary_alloc_summary_from_smem(struct builder *bd) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + struct device *dev = bd->dev; + uint8_t *smem_region; + struct sec_qc_summary *dbg_summary; + const size_t sz_summary = sizeof(struct sec_qc_summary); + const size_t sz_requested = sz_summary + drvdata->smem_offset; + size_t sz_alloc = sz_requested; + int err; + + /* set summary address in smem for other subsystems to see */ + err = qcom_smem_alloc(QCOM_SMEM_HOST_ANY, SMEM_ID_VENDOR2, sz_alloc); + if (err && err != -EEXIST) { + dev_err(dev, "smem alloc failed! (%d)\n", err); + return err; + } + + smem_region = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_ID_VENDOR2, &sz_alloc); + if (!smem_region) { + dev_err(dev, "smem get failed!\n"); + return -ENOENT; + } else if (sz_alloc < sz_requested) { + dev_err(dev, "smem is too small (%zu < %zu)!\n", sz_alloc, sz_requested); + return -ENOMEM; + } + + dbg_summary = (struct sec_qc_summary *)&smem_region[drvdata->smem_offset]; + drvdata->summary = dbg_summary; + + return 0; +} + +static noinline int __summary_probe_prolog(struct builder *bd) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + struct device *dev = bd->dev; + struct sec_qc_summary *dbg_summary = drvdata->summary; + struct sec_qc_summary_data_apss *dbg_apss = secdbg_apss(drvdata); + + memset_io(dbg_summary, 0x0, sizeof(*dbg_summary)); + + dbg_summary->_apss = qcom_smem_virt_to_phys(&dbg_summary->priv.apss); + dbg_summary->_rpm = qcom_smem_virt_to_phys(&dbg_summary->priv.rpm); + dbg_summary->_modem = qcom_smem_virt_to_phys(&dbg_summary->priv.modem); + dbg_summary->_dsps = qcom_smem_virt_to_phys(&dbg_summary->priv.dsps); + + dev_info(dev, "apss(%llx) rpm(%llx) modem(%llx) dsps(%llx)\n", + dbg_summary->_apss, dbg_summary->_rpm, + dbg_summary->_modem, dbg_summary->_dsps); + + strlcpy(dbg_apss->name, "APSS", sizeof(dbg_apss->name)); + strlcpy(dbg_apss->state, "Init", sizeof(dbg_apss->state)); + dbg_apss->nr_cpus = num_present_cpus(); + + sec_qc_summary_set_msm_memdump_info(dbg_apss); + sec_qc_summary_set_rtb_info(dbg_apss); + + return 0; +} + +static int sec_qc_summary_die_handler(struct notifier_block *this, + unsigned long val, void *data) +{ + struct qc_summary_drvdata *drvdata = + container_of(this, struct qc_summary_drvdata, nb_die); + struct sec_qc_summary_data_apss *dbg_apss = secdbg_apss(drvdata); + struct die_args *args = data; + struct pt_regs *regs = args->regs; + + snprintf(dbg_apss->excp.pc_sym, sizeof(dbg_apss->excp.pc_sym), + "%pS", (void *)instruction_pointer(regs)); +#if IS_ENABLED(CONFIG_ARM64) + snprintf(dbg_apss->excp.lr_sym, sizeof(dbg_apss->excp.lr_sym), + "%pS", (void *)regs->regs[30]); +#endif + + return NOTIFY_OK; +} + +static int __summary_register_die_notifier(struct builder *bd) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + + drvdata->nb_die.notifier_call = sec_qc_summary_die_handler; + + return register_die_notifier(&drvdata->nb_die); +} + +static void __summary_unregister_die_notifier(struct builder *bd) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + + unregister_die_notifier(&drvdata->nb_die); +} + +static int sec_qc_summary_panic_handler(struct notifier_block *this, + unsigned long val, void *data) +{ + struct qc_summary_drvdata *drvdata = + container_of(this, struct qc_summary_drvdata, nb_panic); + struct sec_qc_summary_data_apss *dbg_apss = secdbg_apss(drvdata); + const void *caller; + const char *str; + + caller = __builtin_return_address(2); + str = data; + + snprintf(dbg_apss->excp.panic_caller, + sizeof(dbg_apss->excp.panic_caller), + "%pS", (void *)caller); + snprintf(dbg_apss->excp.panic_msg, + sizeof(dbg_apss->excp.panic_msg), + "%s", str); + snprintf(dbg_apss->excp.thread, + sizeof(dbg_apss->excp.thread), + "%s:%d", current->comm, task_pid_nr(current)); + + return NOTIFY_OK; +} + +static int __summary_register_panic_notifier(struct builder *bd) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + + drvdata->nb_panic.notifier_call = sec_qc_summary_panic_handler; + + return atomic_notifier_chain_register(&panic_notifier_list, + &drvdata->nb_panic); +} + +static void __summary_unregister_panic_notifier(struct builder *bd) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + + atomic_notifier_chain_unregister(&panic_notifier_list, + &drvdata->nb_panic); +} + +static noinline int __summary_set_drvdata(struct builder *bd) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + + return 0; +} + +static void __summary_iounmap_debug_kinfo(struct qc_summary_drvdata *drvdata) +{ + if (!IS_ENABLED(CONFIG_ANDROID_DEBUG_KINFO) || IS_BUILTIN(CONFIG_SEC_QC_SUMMARY)) + return; + + if (IS_ENABLED(CONFIG_QCOM_DEBUG_SYMBOL)) + return; /* kinfo region is managed by QC's debug_symbol driver */ + + if (!sec_debug_is_enabled()) + memunmap(drvdata->debug_kinfo_rmem->priv); +} + +static noinline int __summary_probe_epilog(struct builder *bd) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + struct sec_qc_summary *dbg_summary = drvdata->summary; + + __summary_iounmap_debug_kinfo(drvdata); + + /* fill magic nubmer last to ensure data integrity when the magic + * numbers are written + */ + dbg_summary->magic[0] = SEC_DEBUG_SUMMARY_MAGIC0; + dbg_summary->magic[1] = SEC_DEBUG_SUMMARY_MAGIC1; + dbg_summary->magic[2] = SEC_DEBUG_SUMMARY_MAGIC2; + dbg_summary->magic[3] = SEC_DEBUG_SUMMARY_MAGIC3; + + qc_summary = drvdata; + + return 0; +} + +static noinline void __summary_remove_prolog(struct builder *bd) +{ + qc_summary = NULL; +} + +static const struct dev_builder __summary_dev_builder[] = { + DEVICE_BUILDER(__summary_parse_dt, NULL), + DEVICE_BUILDER(__summary_alloc_summary_from_smem, NULL), + DEVICE_BUILDER(__qc_summary_debug_kinfo_init, NULL), + DEVICE_BUILDER(__summary_probe_prolog, NULL), + DEVICE_BUILDER(__qc_summary_arm64_ap_context_init, + __qc_summary_arm64_ap_context_exit), + /* concrete builders which can return -EPROBE_DEFER should be + * placed before here. + */ + DEVICE_BUILDER(__summary_register_die_notifier, + __summary_unregister_die_notifier), + DEVICE_BUILDER(__summary_register_panic_notifier, + __summary_unregister_panic_notifier), + DEVICE_BUILDER(__summary_set_drvdata, NULL), +}; + +static const struct dev_builder __summary_dev_builder_threaded[] = { + DEVICE_BUILDER(__qc_summary_kallsyms_init, NULL), + DEVICE_BUILDER(__qc_summary_info_mon_init, NULL), + DEVICE_BUILDER(__qc_summary_var_mon_init_last_pet, + __qc_summary_var_mon_exit_last_pet), + DEVICE_BUILDER(__qc_summary_var_mon_init_last_ns, NULL), + DEVICE_BUILDER(__qc_summary_task_init, NULL), + DEVICE_BUILDER(__qc_summary_kconst_init, NULL), + DEVICE_BUILDER(__qc_summary_ksyms_init, NULL), + DEVICE_BUILDER(__qc_summary_sched_log_init, NULL), + DEVICE_BUILDER(__qc_summary_aplpm_init, NULL), + DEVICE_BUILDER(__qc_summary_coreinfo_init, + __qc_summary_coreinfo_exit), + DEVICE_BUILDER(__qc_summary_dump_sink_init, NULL), + DEVICE_BUILDER(__summary_probe_epilog, __summary_remove_prolog), +}; + +static int __summary_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct qc_summary_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __summary_probe_threaded(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct qc_summary_drvdata *drvdata = platform_get_drvdata(pdev); + + return sec_director_probe_dev_threaded(&drvdata->bd, builder, n, + "qc_summary"); +} + +static int __summary_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct qc_summary_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static int __summary_remove_threaded(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct qc_summary_drvdata *drvdata = platform_get_drvdata(pdev); + struct director_threaded *drct = drvdata->bd.drct; + + sec_director_destruct_dev_threaded(drct); + + return 0; +} + +static int sec_qc_summary_probe(struct platform_device *pdev) +{ + int err; + + err = __summary_probe(pdev, __summary_dev_builder, + ARRAY_SIZE(__summary_dev_builder)); + if (err) + return err; + + return __summary_probe_threaded(pdev, __summary_dev_builder_threaded, + ARRAY_SIZE(__summary_dev_builder_threaded)); +} + +static int sec_qc_summary_remove(struct platform_device *pdev) +{ + __summary_remove_threaded(pdev, __summary_dev_builder_threaded, + ARRAY_SIZE(__summary_dev_builder_threaded)); + + __summary_remove(pdev, __summary_dev_builder, + ARRAY_SIZE(__summary_dev_builder)); + + return 0; +} + +static const struct of_device_id sec_qc_summary_match_table[] = { + { .compatible = "samsung,qcom-summary" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_qc_summary_match_table); + +static struct platform_driver sec_qc_summary_driver = { + .driver = { + .name = "sec,qc-summary", + .of_match_table = of_match_ptr(sec_qc_summary_match_table), + }, + .probe = sec_qc_summary_probe, + .remove = sec_qc_summary_remove, +}; + +static int __init sec_qc_summary_init(void) +{ + return platform_driver_register(&sec_qc_summary_driver); +} +module_init(sec_qc_summary_init); + +static void __exit sec_qc_summary_exit(void) +{ + platform_driver_unregister(&sec_qc_summary_driver); +} +module_exit(sec_qc_summary_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("Summary for Qualcomm based devices"); +MODULE_LICENSE("GPL v2"); + +MODULE_SOFTDEP("pre: sec_arm64_ap_context"); +MODULE_SOFTDEP("pre: sec_qc_logger"); diff --git a/drivers/samsung/debug/qcom/summary/sec_qc_summary_mock.c b/drivers/samsung/debug/qcom/summary/sec_qc_summary_mock.c new file mode 100644 index 000000000000..d0fc78982916 --- /dev/null +++ b/drivers/samsung/debug/qcom/summary/sec_qc_summary_mock.c @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#include + +#include "sec_qc_summary.h" + +/* NOTE: mock functions to replace sec_qc_summary_arm64_ap_context.c */ +int __weak __qc_summary_arm64_ap_context_init(struct builder *bd) +{ + return 0; +} + +void __weak __qc_summary_arm64_ap_context_exit(struct builder *bd) +{ +} + +/* NOTE: mock functions to replace 'sec_qc_summary_var_mon.c' */ +int __weak __qc_summary_info_mon_init(struct builder *bd) +{ + return 0; +} + +int __weak __qc_summary_var_mon_init_last_pet(struct builder *bd) +{ + return 0; +} + +int __weak __qc_summary_var_mon_init_last_ns(struct builder *bd) +{ + return 0; +} + +/* NOTE: mock functions to replace 'sec_qc_summary_coreinfo.c' */ +void __weak __qc_summary_var_mon_exit_last_pet(struct builder *bd) +{ +} + +int __weak __qc_summary_coreinfo_init(struct builder *bd) +{ + return 0; +} + +void __weak __qc_summary_coreinfo_exit(struct builder *bd) +{ +} diff --git a/drivers/samsung/debug/qcom/summary/sec_qc_summary_sched_log.c b/drivers/samsung/debug/qcom/summary/sec_qc_summary_sched_log.c new file mode 100644 index 000000000000..8206718d3228 --- /dev/null +++ b/drivers/samsung/debug/qcom/summary/sec_qc_summary_sched_log.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2016-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "sec_qc_summary.h" + +static inline void __summary_set_sched_log_info( + struct sec_qc_summary_sched_log *sched_log) +{ + const struct sec_dbg_region_client *client; + + client = sec_dbg_region_find(SEC_QC_SCHED_LOG_MAGIC); + if (IS_ERR(client)) + return; + + sched_log->sched_idx_paddr = client->phys + + offsetof(struct sec_qc_sched_log_data, idx); + sched_log->sched_buf_paddr = client->phys + + offsetof(struct sec_qc_sched_log_data, buf); + sched_log->sched_struct_buf_sz = sizeof(struct sec_qc_sched_buf); + sched_log->sched_struct_log_sz = sizeof(struct sec_qc_sched_log_data); + sched_log->sched_array_cnt = SEC_QC_SCHED_LOG_MAX; +} + +static inline void __summary_set_irq_log_info( + struct sec_qc_summary_sched_log *sched_log) +{ + const struct sec_dbg_region_client *client; + + client = sec_dbg_region_find(SEC_QC_IRQ_LOG_MAGIC); + if (IS_ERR(client)) + return; + + sched_log->irq_idx_paddr = client->phys + + offsetof(struct sec_qc_irq_log_data, idx); + sched_log->irq_buf_paddr = client->phys + + offsetof(struct sec_qc_irq_log_data, buf); + sched_log->irq_struct_buf_sz = sizeof(struct sec_qc_irq_buf); + sched_log->irq_struct_log_sz = sizeof(struct sec_qc_irq_log_data); + sched_log->irq_array_cnt = SEC_QC_IRQ_LOG_MAX; +} + +static inline void __summary_set_irq_exit_log_info( + struct sec_qc_summary_sched_log *sched_log) +{ + const struct sec_dbg_region_client *client; + + client = sec_dbg_region_find(SEC_QC_IRQ_EXIT_LOG_MAGIC); + if (IS_ERR(client)) + return; + + sched_log->irq_exit_idx_paddr = client->phys + + offsetof(struct sec_qc_irq_exit_log_data, idx); + sched_log->irq_exit_buf_paddr = client->phys + + offsetof(struct sec_qc_irq_exit_log_data, buf); + sched_log->irq_exit_struct_buf_sz = sizeof(struct sec_qc_irq_exit_buf); + sched_log->irq_exit_struct_log_sz = sizeof(struct sec_qc_irq_exit_log_data); + sched_log->irq_exit_array_cnt = SEC_QC_IRQ_EXIT_LOG_MAX; +} + +static inline void __summary_set_msg_log_info( + struct sec_qc_summary_sched_log *sched_log) +{ + const struct sec_dbg_region_client *client; + + client = sec_dbg_region_find(SEC_QC_MSG_LOG_MAGIC); + if (IS_ERR(client)) + return; + + sched_log->msglog_idx_paddr = client->phys + + offsetof(struct sec_qc_msg_log_data, idx); + sched_log->msglog_buf_paddr = client->phys + + offsetof(struct sec_qc_msg_log_data, buf); + sched_log->msglog_struct_buf_sz = sizeof(struct sec_qc_msg_buf); + sched_log->msglog_struct_log_sz = sizeof(struct sec_qc_msg_log_data); + sched_log->msglog_array_cnt = SEC_QC_MSG_LOG_MAX; +} + +int notrace __qc_summary_sched_log_init(struct builder *bd) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + struct sec_qc_summary_sched_log *sched_log = + &(secdbg_apss(drvdata)->sched_log); + + __summary_set_sched_log_info(sched_log); + __summary_set_irq_log_info(sched_log); + __summary_set_irq_exit_log_info(sched_log); + __summary_set_msg_log_info(sched_log); + + return 0; +} diff --git a/drivers/samsung/debug/qcom/summary/sec_qc_summary_task.c b/drivers/samsung/debug/qcom/summary/sec_qc_summary_task.c new file mode 100644 index 000000000000..10f0eb5bf2ee --- /dev/null +++ b/drivers/samsung/debug/qcom/summary/sec_qc_summary_task.c @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2016-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#include +#include +#include + + +#include "sec_qc_summary.h" + +#define __type_member(__ptr, __type, __member) \ +{ \ + (__ptr)->size = sizeof(((__type *)0)->__member); \ + (__ptr)->offset = offsetof(__type, __member); \ +} + +#define task_struct_member(__ptr, __member) \ + __type_member(__ptr, struct task_struct, __member) + +#define thread_info_member(__ptr, __member) \ + __type_member(__ptr, struct thread_info, __member) + +static inline void __summary_task_struct_init( + struct qc_summary_drvdata *drvdata, + struct sec_qc_summary_task *task) +{ + task->stack_size = THREAD_SIZE; + task->start_sp = THREAD_SIZE; + + task->ts.struct_size = sizeof(struct task_struct); +#if IS_ENABLED(CONFIG_THREAD_INFO_IN_TASK) + task_struct_member(&task->ts.cpu, thread_info.cpu); +#endif + task_struct_member(&task->ts.state, __state); + task_struct_member(&task->ts.exit_state, exit_state); + task_struct_member(&task->ts.stack, stack); + task_struct_member(&task->ts.flags, flags); +#if IS_ENABLED(CONFIG_SMP) + task_struct_member(&task->ts.on_cpu, on_cpu); +#endif + task_struct_member(&task->ts.pid, pid); + task_struct_member(&task->ts.comm, comm); + task_struct_member(&task->ts.tasks_next, tasks.next); + task_struct_member(&task->ts.thread_group_next, thread_group.next); +#if IS_ENABLED(CONFIG_SCHED_INFO) + /* sched_info */ + task_struct_member(&task->ts.sched_info__pcount, sched_info.pcount); + task_struct_member(&task->ts.sched_info__run_delay, sched_info.run_delay); + task_struct_member(&task->ts.sched_info__last_arrival, sched_info.last_arrival); + task_struct_member(&task->ts.sched_info__last_queued, sched_info.last_queued); +#endif + + task->init_task = (uint64_t)&init_task; + task->ropp.magic = 0x0; /* FIXME: deprecated feature. */ +} + +#if IS_ENABLED(CONFIG_ARM64) +static inline void __summary_task_thread_info_init( + struct qc_summary_drvdata *drvdata, + struct sec_qc_summary_task *task) +{ + task->ti.struct_size = sizeof(struct thread_info); + thread_info_member(&task->ti.flags, flags); + + task_struct_member(&task->ts.fp, thread.cpu_context.fp); + task_struct_member(&task->ts.sp, thread.cpu_context.sp); + task_struct_member(&task->ts.pc, thread.cpu_context.pc); + +#if defined (CONFIG_CFP_ROPP) || defined(CONFIG_RKP_CFP_ROPP) + thread_info_member(&task->ti.rrk, rrk); +#endif +} + +/* arch/arm64/kernel/irq.c */ +DECLARE_PER_CPU(unsigned long *, irq_stack_ptr); +DECLARE_PER_CPU_ALIGNED(unsigned long [IRQ_STACK_SIZE/sizeof(long)], irq_stack); + +static inline void __summary_task_irq_init_builtin( + struct qc_summary_drvdata *drvdata, + struct sec_qc_summary_task *task) +{ +#if IS_ENABLED(CONFIG_VMAP_STACK) + task->irq_stack.pcpu_stack = (uint64_t)&irq_stack_ptr; +#else + task->irq_stack.pcpu_stack = (uint64_t)&irq_stack; +#endif + task->irq_stack.size = IRQ_STACK_SIZE; + task->irq_stack.start_sp = IRQ_STACK_SIZE; +} + +static inline void __summary_task_irq_init_module( + struct qc_summary_drvdata *drvdata, + struct sec_qc_summary_task *task) +{ +#if IS_ENABLED(CONFIG_VMAP_STACK) + task->irq_stack.pcpu_stack = (uint64_t) + __qc_summary_kallsyms_lookup_name(drvdata, "irq_stack_ptr"); +#else + task->irq_stack.pcpu_stack = (uint64_t) + __qc_summary_kallsyms_lookup_name(drvdata, "irq_stack"); +#endif + + task->irq_stack.size = IRQ_STACK_SIZE; + task->irq_stack.start_sp = IRQ_STACK_SIZE; +} + +static inline void __summary_task_irq_init(struct qc_summary_drvdata *drvdata, + struct sec_qc_summary_task *task) +{ + if (IS_BUILTIN(CONFIG_SEC_QC_SUMMARY)) + __summary_task_irq_init_builtin(drvdata, task); + else + __summary_task_irq_init_module(drvdata, task); +} +#else +static inline void __summary_task_thread_info_init(struct qc_summary_drvdata *drvdata, struct sec_qc_summary_task *task) {} +static inline void __summary_task_irq_init(struct qc_summary_drvdata *drvdata, struct sec_qc_summary_task *task) {} +#endif + +int notrace __qc_summary_task_init(struct builder *bd) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + struct sec_qc_summary_task *task = + &(secdbg_apss(drvdata)->task); + + __summary_task_struct_init(drvdata, task); + __summary_task_thread_info_init(drvdata, task); + __summary_task_irq_init(drvdata, task); + + return 0; +} diff --git a/drivers/samsung/debug/qcom/summary/sec_qc_summary_var_mon.c b/drivers/samsung/debug/qcom/summary/sec_qc_summary_var_mon.c new file mode 100644 index 000000000000..098c6b620e0d --- /dev/null +++ b/drivers/samsung/debug/qcom/summary/sec_qc_summary_var_mon.c @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2016-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "sec_qc_summary.h" +#include "sec_qc_summary_external.h" +#include "sec_qc_summary_var_mon.h" + +void __summary_add_var_to_info_mon( + struct sec_qc_summary_simple_var_mon *info_mon, + const char *name, phys_addr_t pa, unsigned int size) +{ + if (info_mon->idx >= ARRAY_SIZE(info_mon->var)) { + pr_warn("index variable is out of bound!\n"); + return; + } + + strlcpy(info_mon->var[info_mon->idx].name, + name, sizeof(info_mon->var[0].name)); + info_mon->var[info_mon->idx].sizeof_type = size; + info_mon->var[info_mon->idx].var_paddr = pa; + + info_mon->idx++; +} + +void __summary_add_var_to_var_mon( + struct sec_qc_summary_simple_var_mon *var_mon, + const char *name, phys_addr_t pa, unsigned int size) +{ + if (var_mon->idx >= ARRAY_SIZE(var_mon->var)) { + pr_warn("index variable is out of bound!\n"); + return; + } + + strlcpy(var_mon->var[var_mon->idx].name, name, + sizeof(var_mon->var[0].name)); + var_mon->var[var_mon->idx].sizeof_type = size; + var_mon->var[var_mon->idx].var_paddr = pa; + + var_mon->idx++; +} + +void __summary_add_array_to_var_mon( + struct sec_qc_summary_simple_var_mon *var_mon, + const char *name, phys_addr_t pa, size_t index, + unsigned int size) +{ + char *varname; + + varname = kasprintf(GFP_KERNEL, "%s_%zu", name, index); + + __summary_add_var_to_var_mon(var_mon, varname, pa, size); + + kfree(varname); +} + +static void __summary_info_mon_init_build_root( + struct qc_summary_drvdata *drvdata, + struct sec_qc_summary_simple_var_mon *info_mon) +{ + struct device *dev = drvdata->bd.dev; +#ifdef __SEC_QC_SUMMARY_BUILD_ROOT + const char __build_root[] = + __stringify(__SEC_QC_SUMMARY_BUILD_ROOT); +#else + const char __build_root[] = "UNKNOWN"; +#endif + const char *build_root; + + build_root = devm_kstrdup_const(dev, __build_root, GFP_KERNEL); + __qc_summary_add_info_mon(info_mon, "build_root", + virt_to_phys(build_root), -1); +} + +static void __summary_info_mon_init_from_saved_cmdline( + struct qc_summary_drvdata *drvdata, + struct sec_qc_summary_simple_var_mon *info_mon) +{ + if (IS_BUILTIN(CONFIG_SEC_QC_SUMMARY)) + __qc_summary_add_info_mon(info_mon, "Kernel cmdline", + virt_to_phys(saved_command_line), -1); + else { + const char **__saved_command_line_ptr = + (const char **)__qc_summary_kallsyms_lookup_name( + drvdata, "saved_command_line"); + const char *__saved_command_line = __saved_command_line_ptr ? + *__saved_command_line_ptr : NULL; + __qc_summary_add_info_mon(info_mon, "Kernel cmdline", + virt_to_phys(__saved_command_line), -1); + } +} + +static void __summary_info_mon_init_from_bootargs_of_dt( + struct qc_summary_drvdata *drvdata, + struct sec_qc_summary_simple_var_mon *info_mon) +{ + struct device *dev = drvdata->bd.dev; + struct device_node *chosen; + const char *__bootargs; + const char *bootargs; + + chosen = of_find_node_by_name(of_root, "chosen"); + if (!chosen) { + __bootargs = "chosen node is not found in the devcie treee"; + goto chosen_is_not_found; + } + + of_property_read_string(chosen, "bootargs", &__bootargs); + if (!__bootargs) { + __bootargs = "bootargs node is not found in the devcie treee"; + goto bootargs_is_not_found; + } + +chosen_is_not_found: +bootargs_is_not_found: + bootargs = devm_kstrdup_const(dev, __bootargs, GFP_KERNEL); + __qc_summary_add_info_mon(info_mon, "Kernel cmdline", + virt_to_phys(bootargs), -1); +} + +static void __summary_info_mon_init_kernel_cmdline( + struct qc_summary_drvdata *drvdata, + struct sec_qc_summary_simple_var_mon *info_mon) +{ + if (IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP)) + __summary_info_mon_init_from_bootargs_of_dt( + drvdata, info_mon); + else + __summary_info_mon_init_from_saved_cmdline( + drvdata, info_mon); +} + +static inline void __summary_whitespace_to_space(char *str) +{ + while (*str) { + if (isspace(*str)) + *str = ' '; + + str++; + } +} + +static inline void ____summary_info_mon_init_boot_config( + struct qc_summary_drvdata *drvdata, + struct sec_qc_summary_simple_var_mon *info_mon) +{ + const char **__saved_boot_config_ptr = + (const char **)__qc_summary_kallsyms_lookup_name( + drvdata, "saved_boot_config"); + struct device *dev = drvdata->bd.dev; + const char *__saved_boot_config = __saved_boot_config_ptr ? + *__saved_boot_config_ptr : NULL; + char *boot_config; + + if (!__saved_boot_config) + return; + + boot_config = devm_kstrdup(dev, __saved_boot_config, GFP_KERNEL); + __summary_whitespace_to_space(boot_config); + + __qc_summary_add_info_mon(info_mon, "Kernel boot config", + virt_to_phys(__saved_boot_config), -1); +} + +static void __summary_info_mon_init_boot_config( + struct qc_summary_drvdata *drvdata, + struct sec_qc_summary_simple_var_mon *info_mon) +{ + /* NOTE: saved_boot_config can have 'Information identifying user'. + * It should NOT be collected from the production binary. + */ + if (IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP)) + return; + + ____summary_info_mon_init_boot_config(drvdata, info_mon); +} + +static inline void __summary_init_arch_desc(char *arch_desc, size_t max_len) +{ + const char *machine_name; + int err; + + err = of_property_read_string(of_root, "model", &machine_name); + if (!err && machine_name) + goto use_machine_name_from_dt; + + strlcpy(arch_desc, + "UNKNOWN SAMSUNG Device - machine_name is not detected", + max_len); + + return; + +use_machine_name_from_dt: + snprintf(arch_desc, max_len, "%s (DT)", machine_name); +} + +static void __summary_info_mon_init_hardware_name( + struct qc_summary_drvdata *drvdata, + struct sec_qc_summary_simple_var_mon *info_mon) +{ + struct device *dev = drvdata->bd.dev; + char __arch_desc[128]; + const char *arch_desc; + + __summary_init_arch_desc(__arch_desc, sizeof(__arch_desc)); + arch_desc = devm_kstrdup_const(dev, __arch_desc, GFP_KERNEL); + __qc_summary_add_info_mon(info_mon, "Hardware name", + virt_to_phys(arch_desc), -1); +} + +static void __summary_info_mon_init_linux_banner( + struct qc_summary_drvdata *drvdata, + struct sec_qc_summary_simple_var_mon *info_mon) +{ + if (IS_BUILTIN(CONFIG_SEC_QC_SUMMARY)) + __qc_summary_add_str_to_info_mon(info_mon, linux_banner); + else { + const char *__linux_banner = + (char *)__qc_summary_kallsyms_lookup_name( + drvdata, "linux_banner"); + __qc_summary_add_info_mon(info_mon, "linux_banner", + virt_to_phys(__linux_banner), -1); + } +} + +int __qc_summary_info_mon_init(struct builder *bd) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + struct sec_qc_summary_simple_var_mon *info_mon = + &(secdbg_apss(drvdata)->info_mon); + + __summary_info_mon_init_build_root(drvdata, info_mon); + __summary_info_mon_init_kernel_cmdline(drvdata, info_mon); + __summary_info_mon_init_boot_config(drvdata, info_mon); + __summary_info_mon_init_hardware_name(drvdata, info_mon); + __summary_info_mon_init_linux_banner(drvdata, info_mon); + + return 0; +} + +static unsigned long long *qc_summary_last_pet; + +static int sec_qc_summary_pet_notifier_call(struct notifier_block *this, + unsigned long l, void *drvdata) +{ + struct msm_watchdog_data *wdog_dd = drvdata; + unsigned long long pet_ts, last_pet_ts, delta_ts; + unsigned long pet_nsec, last_pet_nsec, delta_nsec; + + last_pet_ts = *qc_summary_last_pet; + pet_ts = *qc_summary_last_pet = wdog_dd->last_pet; + delta_ts = pet_ts - last_pet_ts; + + pet_nsec = do_div(pet_ts, 1000000000); + last_pet_nsec = do_div(last_pet_ts, 1000000000); + delta_nsec = do_div(delta_ts, 1000000000); + + /* FIXME: print without a module and a function name */ + printk(KERN_WARNING "msm_watchdog_data - pet: %llu.%06lu / last_pet: %llu.%06lu (delta: %llu.%06lu)\n", + pet_ts, pet_nsec / 1000, + last_pet_ts, last_pet_nsec / 1000, + delta_ts, delta_nsec / 1000); + + return NOTIFY_OK; +} + +static struct notifier_block qc_summary_nb_pet = { + .notifier_call = sec_qc_summary_pet_notifier_call, +}; + +int __qc_summary_var_mon_init_last_pet(struct builder *bd) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + struct sec_qc_summary_simple_var_mon *var_mon = + &(secdbg_apss(drvdata)->var_mon); + struct device *dev = bd->dev; + + qc_summary_last_pet = devm_kzalloc(dev, sizeof(*qc_summary_last_pet), GFP_KERNEL); + __summary_add_var_to_var_mon(var_mon, "last_pet", + virt_to_phys(qc_summary_last_pet), + sizeof(*qc_summary_last_pet)); + + return qcom_wdt_pet_register_notifier(&qc_summary_nb_pet); +} + +int __qc_summary_var_mon_init_last_ns(struct builder *bd) +{ + struct qc_summary_drvdata *drvdata = + container_of(bd, struct qc_summary_drvdata, bd); + struct sec_qc_summary_simple_var_mon *var_mon = + &(secdbg_apss(drvdata)->var_mon); + atomic64_t *last_ns; + struct device *dev = bd->dev; + + last_ns = (atomic64_t *)__qc_summary_kallsyms_lookup_name(drvdata, + "sec_qc_summary_last_ns"); + if (!last_ns) { + dev_warn(drvdata->bd.dev, + "This kernel is maybe GKI. Use a dummy value...\n"); + last_ns = devm_kmalloc(dev, sizeof(*last_ns), GFP_KERNEL); + if (!last_ns) + return -ENOMEM; + + atomic64_set(last_ns, 1234567890); + } + + __summary_add_var_to_var_mon(var_mon, "last_ns", + virt_to_phys(last_ns), sizeof(*last_ns)); + + return 0; +} + +void __qc_summary_var_mon_exit_last_pet(struct builder *bd) +{ + qcom_wdt_pet_unregister_notifier(&qc_summary_nb_pet); +} diff --git a/drivers/samsung/debug/qcom/summary/sec_qc_summary_var_mon.h b/drivers/samsung/debug/qcom/summary/sec_qc_summary_var_mon.h new file mode 100644 index 000000000000..dde1220a93dc --- /dev/null +++ b/drivers/samsung/debug/qcom/summary/sec_qc_summary_var_mon.h @@ -0,0 +1,32 @@ +#ifndef __INTERNAL__SEC_QC_SUMMARY_VAR_MON_H__ +#define __INTERNAL__SEC_QC_SUMMARY_VAR_MON_H__ + +#if IS_ENABLED(CONFIG_ARM64) +extern void __summary_add_var_to_info_mon(struct sec_qc_summary_simple_var_mon *info_mon, const char *name, phys_addr_t pa, unsigned int size); +extern void __summary_add_var_to_var_mon(struct sec_qc_summary_simple_var_mon *var_mon, const char *name, phys_addr_t pa, unsigned int size); +extern void __summary_add_array_to_var_mon(struct sec_qc_summary_simple_var_mon *var_mon, const char *name, phys_addr_t pa, size_t index, unsigned int size); +#else +static inline void __summary_add_var_to_info_mon(struct sec_qc_summary_simple_var_mon *info_mon, const char *name, phys_addr_t pa, unsigned int size) {} +static inline void __summary_add_var_to_var_mon(struct sec_qc_summary_simple_var_mon *var_mon, const char *name, phys_addr_t pa, unsigned int size) {} +static inline void __summary_add_array_to_var_mon(struct sec_qc_summary_simple_var_mon *var_mon, const char *name, phys_addr_t pa, size_t index, unsigned int size) {} +#endif + +#define __qc_summary_add_var_to_info_mon(info_mon, var) \ + __summary_add_var_to_info_mon(info_mon, #var, __pa(&var), sizeof(var)) + +#define __qc_summary_add_str_to_info_mon(info_mon, pstr) \ + __summary_add_var_to_info_mon(info_mon, #pstr, __pa(pstr), -1) + +#define __qc_summary_add_info_mon(info_mon, pa, name, size) \ + __summary_add_var_to_info_mon(info_mon, pa, name, size) + +#define __qc_summary_add_var_to_var_mon(var_mon, var) \ + __summary_add_var_to_var_mon(var_mon, #var, __pa(&var), sizeof(var)) + +#define __qc_summary_add_array_to_var_mon(name, arr, index) \ + __summary_add_array_to_var_mon(var_mon, name, __ap(&arr), index, sizeof(arr)) + +#define __qc_summary_add_str_array_to_var_mon(name, pstrarr, index) \ + __summary_add_array_to_var_mon(var_mon, name, __ap(&pstrarr), index, -1) + +#endif /* __INTERNAL__SEC_QC_SUMMARY_VAR_MON_H__ */ diff --git a/drivers/samsung/debug/qcom/upload_cause/Kconfig b/drivers/samsung/debug/qcom/upload_cause/Kconfig new file mode 100644 index 000000000000..9b15110d2817 --- /dev/null +++ b/drivers/samsung/debug/qcom/upload_cause/Kconfig @@ -0,0 +1,5 @@ +config SEC_QC_UPLOAD_CAUSE + tristate "SEC Panic Notifier Updating Upload Cause for Qualcomm Based Devices" + depends on SEC_UPLOAD_CAUSE + help + TODO: help is not ready. diff --git a/drivers/samsung/debug/qcom/upload_cause/Makefile b/drivers/samsung/debug/qcom/upload_cause/Makefile new file mode 100644 index 000000000000..21e98d4719db --- /dev/null +++ b/drivers/samsung/debug/qcom/upload_cause/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SEC_QC_UPLOAD_CAUSE) += sec_qc_upload_cause.o diff --git a/drivers/samsung/debug/qcom/upload_cause/sec_qc_upload_cause.c b/drivers/samsung/debug/qcom/upload_cause/sec_qc_upload_cause.c new file mode 100644 index 000000000000..deee47cf753d --- /dev/null +++ b/drivers/samsung/debug/qcom/upload_cause/sec_qc_upload_cause.c @@ -0,0 +1,551 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2020-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +struct qc_upldc_drvdata { + struct builder bd; +}; + +static struct qc_upldc_drvdata *qc_upldc; + +/* This is shared with msm-power off module. */ +static void __iomem *qcom_upload_cause; + +DEFINE_PER_CPU(unsigned long, sec_debug_upload_cause); + +static __always_inline bool __qc_upldc_is_probed(void) +{ + return !!qc_upldc; +} + +static void __qc_upldc_write_cause(unsigned int type) +{ + __raw_writel(type, qcom_upload_cause); +} + +static unsigned int __qc_upldc_read_cause(void) +{ + return readl(qcom_upload_cause); +} + +void sec_qc_upldc_write_cause(unsigned int type) +{ + int cpu; + + if (!__qc_upldc_is_probed()) { + pr_warn("upload cause address unmapped.\n"); + return; + } + + cpu = get_cpu(); + per_cpu(sec_debug_upload_cause, cpu) = type; + __qc_upldc_write_cause(type); + put_cpu(); + + pr_emerg("%x\n", type); +} +EXPORT_SYMBOL_GPL(sec_qc_upldc_write_cause); + +unsigned int sec_qc_upldc_read_cause(void) +{ + if (!__qc_upldc_is_probed()) { + pr_warn("upload cause address unmapped.\n"); + return UPLOAD_CAUSE_INIT; + } + + return __qc_upldc_read_cause(); +} +EXPORT_SYMBOL_GPL(sec_qc_upldc_read_cause); + +void sec_qc_upldc_type_to_cause(unsigned int type, char *cause, size_t len) +{ + if (type == UPLOAD_CAUSE_KERNEL_PANIC) + strlcpy(cause, "kernel_panic", len); + else if (type == UPLOAD_CAUSE_INIT) + strlcpy(cause, "unknown_error", len); + else if (type == UPLOAD_CAUSE_NON_SECURE_WDOG_BARK) + strlcpy(cause, "watchdog_bark", len); + else + sec_upldc_type_to_cause(type, cause, len); +} +EXPORT_SYMBOL_GPL(sec_qc_upldc_type_to_cause); + +#define SEC_QC_UPLC(__cause, __type, __func) \ +{ \ + .cause = __cause, \ + .type = __type, \ + .func = __func, \ +} + +#define SEC_QC_UPLC_STRNCMP(__cause, __type) \ + SEC_QC_UPLC(__cause, __type, sec_qc_upldc_strncmp) + +#define SEC_QC_UPLC_STRNSTR(__cause, __type) \ + SEC_QC_UPLC(__cause, __type, sec_qc_upldc_strnstr) + +#define SEC_QC_UPLC_STRNCASECMP(__cause, __type) \ + SEC_QC_UPLC(__cause, __type, sec_qc_upldc_strncasecmp) + +static int __used sec_qc_upldc_strncmp(const struct sec_upload_cause *uc, + const char *cause) +{ + if (strncmp(cause, uc->cause, strlen(uc->cause))) + return SEC_UPLOAD_CAUSE_HANDLE_DONE; + + sec_qc_upldc_write_cause(uc->type); + + return SEC_UPLOAD_CAUSE_HANDLE_OK; +} + +static int __used sec_qc_upldc_strnstr(const struct sec_upload_cause *uc, + const char *cause) +{ + if (!strnstr(cause, uc->cause, strlen(cause))) + return SEC_UPLOAD_CAUSE_HANDLE_DONE; + + sec_qc_upldc_write_cause(uc->type); + + return SEC_UPLOAD_CAUSE_HANDLE_OK; +} + +static int __used sec_qc_upldc_strncasecmp(const struct sec_upload_cause *uc, + const char *cause) +{ + if (strncasecmp(cause, uc->cause, strlen(uc->cause))) + return SEC_UPLOAD_CAUSE_HANDLE_DONE; + + sec_qc_upldc_write_cause(uc->type); + + return SEC_UPLOAD_CAUSE_HANDLE_OK; +} + +static struct sec_upload_cause __qc_upldc_strncmp[] = { + SEC_QC_UPLC_STRNCMP("User Fault", + UPLOAD_CAUSE_USER_FAULT), + SEC_QC_UPLC_STRNCMP("Crash Key", + UPLOAD_CAUSE_FORCED_UPLOAD), + SEC_QC_UPLC_STRNCMP("User Crash Key", + UPLOAD_CAUSE_USER_FORCED_UPLOAD), + SEC_QC_UPLC_STRNCMP("Long Key Press", + UPLOAD_CAUSE_POWER_LONG_PRESS), + SEC_QC_UPLC_STRNCMP("Platform Watchdog couldnot be initialized", + UPLOAD_CAUSE_PF_WD_INIT_FAIL), + SEC_QC_UPLC_STRNCMP("Platform Watchdog couldnot be restarted", + UPLOAD_CAUSE_PF_WD_RESTART_FAIL), + SEC_QC_UPLC_STRNCMP("Platform Watchdog can't update sync_cnt", + UPLOAD_CAUSE_PF_WD_KICK_FAIL), + SEC_QC_UPLC_STRNCMP("CP Crash", + UPLOAD_CAUSE_CP_ERROR_FATAL), + SEC_QC_UPLC_STRNCMP("MDM Crash", + UPLOAD_CAUSE_MDM_ERROR_FATAL), + SEC_QC_UPLC_STRNCMP("watchdog_bark", + UPLOAD_CAUSE_NON_SECURE_WDOG_BARK) +}; + +static struct sec_upload_cause __qc_upldc_strnstr[] = { + SEC_QC_UPLC_STRNSTR("mss", + UPLOAD_CAUSE_MODEM_RST_ERR), + SEC_QC_UPLC_STRNSTR("esoc0 crashed", + UPLOAD_CAUSE_MDM_ERROR_FATAL), + SEC_QC_UPLC_STRNSTR("modem", + UPLOAD_CAUSE_MODEM_RST_ERR), + SEC_QC_UPLC_STRNSTR("external_modem", + UPLOAD_CAUSE_MDM_ERROR_FATAL), + SEC_QC_UPLC_STRNSTR("unrecoverable external_modem", + UPLOAD_CAUSE_MDM_CRITICAL_FATAL), + SEC_QC_UPLC_STRNSTR("riva", + UPLOAD_CAUSE_RIVA_RST_ERR), + SEC_QC_UPLC_STRNSTR("lpass", + UPLOAD_CAUSE_LPASS_RST_ERR), + SEC_QC_UPLC_STRNSTR("dsps", + UPLOAD_CAUSE_DSPS_RST_ERR), + SEC_QC_UPLC_STRNSTR("SMPL", + UPLOAD_CAUSE_SMPL), + SEC_QC_UPLC_STRNSTR("adsp", + UPLOAD_CAUSE_ADSP_ERROR_FATAL), + SEC_QC_UPLC_STRNSTR("slpi", + UPLOAD_CAUSE_SLPI_ERROR_FATAL), + SEC_QC_UPLC_STRNSTR("spss", + UPLOAD_CAUSE_SPSS_ERROR_FATAL), + SEC_QC_UPLC_STRNSTR("npu", + UPLOAD_CAUSE_NPU_ERROR_FATAL), + SEC_QC_UPLC_STRNSTR("cdsp", + UPLOAD_CAUSE_CDSP_ERROR_FATAL), + SEC_QC_UPLC_STRNSTR("taking too long", + UPLOAD_CAUSE_SUBSYS_IF_TIMEOUT), + SEC_QC_UPLC_STRNSTR("Software Watchdog Timer expired", + UPLOAD_CAUSE_PF_WD_BITE), +}; + +struct sec_upload_cause_suite { + struct sec_upload_cause *uc; + size_t nr_cause; +}; + +static struct sec_upload_cause_suite qc_upldc_suite[] = { + { + .uc = __qc_upldc_strncmp, + .nr_cause = ARRAY_SIZE(__qc_upldc_strncmp), + }, + { + .uc = __qc_upldc_strnstr, + .nr_cause = ARRAY_SIZE(__qc_upldc_strnstr), + }, +}; + +static void __qc_upldc_del_for_each(struct sec_upload_cause *uc, ssize_t n); + +static int __qc_upldc_add_for_each(struct sec_upload_cause *uc, ssize_t n) +{ + int err; + ssize_t last_failed; + ssize_t i; + + for (i = 0; i < n; i++) { + err = sec_upldc_add_cause(&uc[i]); + if (err) { + last_failed = i; + goto err_add_cause; + } + } + + return 0; + +err_add_cause: + __qc_upldc_del_for_each(uc, last_failed); + if (err == -EBUSY) + return -EPROBE_DEFER; + + return err; +} + +static void __qc_upldc_del_suite(struct sec_upload_cause_suite *suite, + ssize_t n); + +static int __qc_upldc_add_suite(struct sec_upload_cause_suite *suite, + ssize_t n) +{ + int err; + ssize_t last_failed; + ssize_t i; + + for (i = 0; i < n; i++) { + err = __qc_upldc_add_for_each(suite[i].uc, suite[i].nr_cause); + if (err) { + last_failed = i; + goto err_add_for_each; + } + } + + return 0; + +err_add_for_each: + __qc_upldc_del_suite(suite, last_failed); + return err; +} + +static void __qc_upldc_del_for_each(struct sec_upload_cause * uc, ssize_t n) +{ + ssize_t i; + + for (i = n - 1; i >= 0; i--) + sec_upldc_del_cause(&uc[i]); +} + +static void __qc_upldc_del_suite(struct sec_upload_cause_suite *suite, + ssize_t n) +{ + ssize_t i; + + for (i = n - 1; i >= 0; i--) + __qc_upldc_del_for_each(suite[i].uc, suite[i].nr_cause); +} + + +static int __qc_upldc_crashkey_long_on_matched(void) +{ + if (sec_qc_upldc_read_cause() == UPLOAD_CAUSE_INIT) + sec_qc_upldc_write_cause(UPLOAD_CAUSE_POWER_LONG_PRESS); + + return NOTIFY_OK; +} + +static int __qc_upldc_crashkey_long_on_unmatched(void) +{ + if (sec_qc_upldc_read_cause() == UPLOAD_CAUSE_POWER_LONG_PRESS) + sec_qc_upldc_write_cause(UPLOAD_CAUSE_INIT); + + return NOTIFY_OK; +} + +static int sec_qc_upldc_crashkey_long_call(struct notifier_block *this, + unsigned long type, void *v) +{ + int ret; + + switch (type) { + case SEC_CRASHKEY_LONG_NOTIFY_TYPE_MATCHED: + ret = __qc_upldc_crashkey_long_on_matched(); + break; + case SEC_CRASHKEY_LONG_NOTIFY_TYPE_UNMATCHED: + ret = __qc_upldc_crashkey_long_on_unmatched(); + break; + case SEC_CRASHKEY_LONG_NOTIFY_TYPE_EXPIRED: + default: + ret = NOTIFY_DONE; + } + + return ret; +} + +static int sec_qc_upldc_default_cause(const struct sec_upload_cause *uc, + const char *cause) +{ + sec_qc_upldc_write_cause(UPLOAD_CAUSE_KERNEL_PANIC); + + return SEC_UPLOAD_CAUSE_HANDLE_OK; +} + +static struct sec_upload_cause sec_qc_upldc_default_handle = { + .func = sec_qc_upldc_default_cause, +}; + +static int __qc_upldc_set_default_cause(struct builder *bd) +{ + int err = sec_upldc_set_default_cause(&sec_qc_upldc_default_handle); + + if (err == -EBUSY) + return -EPROBE_DEFER; + + return err; +} + +static void __qc_upldc_unset_default_cause(struct builder *bd) +{ + sec_upldc_unset_default_cause(&sec_qc_upldc_default_handle); +} + +static struct notifier_block sec_qc_upldc_crashkey_long_handle = { + .notifier_call = sec_qc_upldc_crashkey_long_call, +}; + +static int __qc_upldc_register_crashkey_long_handle(struct builder *bd) +{ + int err = sec_crashkey_long_add_preparing_panic( + &sec_qc_upldc_crashkey_long_handle); + + if (err == -EBUSY) + return -EPROBE_DEFER; + + return err; +} + +static void __qc_upldc_unregister_crashkey_long_handle(struct builder *bd) +{ + sec_crashkey_long_del_preparing_panic( + &sec_qc_upldc_crashkey_long_handle); +} + +static int __qc_upldc_ioremap_qcom_upload_cause(struct builder *bd) +{ + struct device_node *mem_np; + struct device *dev; + + dev = bd->dev; + + mem_np = of_find_compatible_node(NULL, NULL, + "qcom,msm-imem-upload_cause"); + if (!mem_np) { + dev_err(dev, "unable to find DT imem upload cause node\n"); + return -ENODEV; + } + + qcom_upload_cause = of_iomap(mem_np, 0); + if (unlikely(!qcom_upload_cause)) { + dev_err(dev, "unable to map imem upload cause offset\n"); + return -ENOMEM; + } + + dev_err(dev, "upload_cause addr : 0x%p(0x%llx)\n", + qcom_upload_cause, + (unsigned long long)virt_to_phys(qcom_upload_cause)); + + sec_qc_upldc_write_cause(UPLOAD_CAUSE_INIT); + + return 0; +} + +static void __qc_upldc_iounmap_qcom_upload_cause(struct builder *bd) +{ + iounmap(qcom_upload_cause); +} + +static int __qc_upldc_add_upload_cause_suite(struct builder *bd) +{ + return __qc_upldc_add_suite(qc_upldc_suite, ARRAY_SIZE(qc_upldc_suite)); +} + +static void __qc_upldc_del_upload_cause_suite(struct builder *bd) +{ + __qc_upldc_del_suite(qc_upldc_suite, + ARRAY_SIZE(qc_upldc_suite)); +} + +static noinline int __qc_upldc_set_drvdata(struct builder *bd) +{ + struct qc_upldc_drvdata *drvdata = + container_of(bd, struct qc_upldc_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + + return 0; +} + +static noinline int __qc_upldc_probe_epilog(struct builder *bd) +{ + struct qc_upldc_drvdata *drvdata = + container_of(bd, struct qc_upldc_drvdata, bd); + + qc_upldc = drvdata; + + return 0; +} + +static noinline void __qc_upldc_remove_prolog(struct builder *bd) +{ + qc_upldc = NULL; +} + +static int __qc_upldc_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct qc_upldc_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __qc_upldc_probe_threaded(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct qc_upldc_drvdata *drvdata = platform_get_drvdata(pdev); + + return sec_director_probe_dev_threaded(&drvdata->bd, builder, n, + "qc_upldc"); +} + +static int __qc_upldc_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct qc_upldc_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static int __qc_upldc_remove_threaded(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct qc_upldc_drvdata *drvdata = platform_get_drvdata(pdev); + struct director_threaded *drct = drvdata->bd.drct; + + sec_director_destruct_dev_threaded(drct); + + return 0; +} + +static const struct dev_builder __qc_upldc_dev_builder[] = { + DEVICE_BUILDER(__qc_upldc_ioremap_qcom_upload_cause, + __qc_upldc_iounmap_qcom_upload_cause), + DEVICE_BUILDER(__qc_upldc_set_drvdata, NULL), +}; + +static const struct dev_builder __qc_upldc_dev_builder_threaded[] = { + DEVICE_BUILDER(__qc_upldc_add_upload_cause_suite, + __qc_upldc_del_upload_cause_suite), + DEVICE_BUILDER(__qc_upldc_set_default_cause, + __qc_upldc_unset_default_cause), + DEVICE_BUILDER(__qc_upldc_register_crashkey_long_handle, + __qc_upldc_unregister_crashkey_long_handle), + DEVICE_BUILDER(__qc_upldc_probe_epilog, __qc_upldc_remove_prolog), +}; + +static int sec_qc_upldc_probe(struct platform_device *pdev) +{ + int err; + + err = __qc_upldc_probe(pdev, __qc_upldc_dev_builder, + ARRAY_SIZE(__qc_upldc_dev_builder)); + if (err) + return err; + + return __qc_upldc_probe_threaded(pdev, __qc_upldc_dev_builder_threaded, + ARRAY_SIZE(__qc_upldc_dev_builder_threaded)); +} + +static int sec_qc_upldc_remove(struct platform_device *pdev) +{ + __qc_upldc_remove_threaded(pdev, __qc_upldc_dev_builder_threaded, + ARRAY_SIZE(__qc_upldc_dev_builder_threaded)); + + __qc_upldc_remove(pdev, __qc_upldc_dev_builder, + ARRAY_SIZE(__qc_upldc_dev_builder)); + + return 0; +} + +static const struct of_device_id sec_qc_upldc_match_table[] = { + { .compatible = "samsung,qcom-upload_cause" }, + { }, +}; +MODULE_DEVICE_TABLE(of, sec_qc_upldc_match_table); + +static struct platform_driver sec_qc_upldc_driver = { + .driver = { + .name = "sec,qc-upload_cause", + .of_match_table = of_match_ptr(sec_qc_upldc_match_table), + }, + .probe = sec_qc_upldc_probe, + .remove = sec_qc_upldc_remove, +}; + +static int __init sec_qc_upldc_init(void) +{ + return platform_driver_register(&sec_qc_upldc_driver); +} +arch_initcall(sec_qc_upldc_init); + +static void __exit sec_qc_upldc_exit(void) +{ + platform_driver_unregister(&sec_qc_upldc_driver); +} +module_exit(sec_qc_upldc_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("Panic Notifier Updating Upload Cause for Qualcomm based devices"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/debug/qcom/user_reset/Kconfig b/drivers/samsung/debug/qcom/user_reset/Kconfig new file mode 100644 index 000000000000..ea0100444ebf --- /dev/null +++ b/drivers/samsung/debug/qcom/user_reset/Kconfig @@ -0,0 +1,5 @@ +config SEC_QC_USER_RESET + tristate "SEC User Reset Debug for Qualcomm based devices" + depends on SEC_QC_DEBUG_PARTITION + help + TODO: help is not ready. diff --git a/drivers/samsung/debug/qcom/user_reset/Makefile b/drivers/samsung/debug/qcom/user_reset/Makefile new file mode 100644 index 000000000000..375c94b88710 --- /dev/null +++ b/drivers/samsung/debug/qcom/user_reset/Makefile @@ -0,0 +1,15 @@ +obj-$(CONFIG_SEC_QC_USER_RESET) += sec_qc_user_reset.o +sec_qc_user_reset-objs := sec_qc_user_reset_main.o \ + sec_qc_ap_health.o \ + sec_qc_reset_rwc.o \ + sec_qc_reset_reason.o \ + sec_qc_reset_summary.o \ + sec_qc_reset_klog.o \ + sec_qc_reset_tzlog.o \ + sec_qc_auto_comment.o \ + sec_qc_reset_history.o \ + sec_qc_fmm_lock.o \ + sec_qc_recovery_cause.o \ + sec_qc_kryo_arm64_edac.o \ + sec_qc_store_lastkmsg.o \ + sec_qc_modem_reset_data.o diff --git a/drivers/samsung/debug/qcom/user_reset/sec_qc_ap_health.c b/drivers/samsung/debug/qcom/user_reset/sec_qc_ap_health.c new file mode 100644 index 000000000000..3608cc8e7842 --- /dev/null +++ b/drivers/samsung/debug/qcom/user_reset/sec_qc_ap_health.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2006-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include + +#include + +#include "sec_qc_user_reset.h" + +ap_health_t *__qc_ap_health_data_read(struct qc_user_reset_drvdata *drvdata) +{ + return drvdata->health; +} + +ap_health_t *sec_qc_ap_health_data_read(void) +{ + if (!__qc_user_reset_is_probed()) + return ERR_PTR(-EBUSY); + + return __qc_ap_health_data_read(qc_user_reset); +} +EXPORT_SYMBOL_GPL(sec_qc_ap_health_data_read); + +int __qc_ap_health_data_write(struct qc_user_reset_drvdata *drvdata, + ap_health_t *data) +{ + ap_health_t *health = drvdata->health; + bool valid; + + if (health != data) + memcpy(health, data, sizeof(*health)); + + valid = sec_qc_dbg_part_write(debug_index_ap_health, health); + if (!valid) + return -ENXIO; + + return 0; +} + +int sec_qc_ap_health_data_write(ap_health_t *data) +{ + if (!__qc_user_reset_is_probed()) + return -EBUSY; + + return __qc_ap_health_data_write(qc_user_reset, data); +} +EXPORT_SYMBOL_GPL(sec_qc_ap_health_data_write); + +static void sec_qc_ap_health_data_write_fn(struct work_struct *work) +{ + struct delayed_work *health_work = to_delayed_work(work); + struct qc_user_reset_drvdata *drvdata = container_of(health_work, + struct qc_user_reset_drvdata, health_work); + struct device *dev = drvdata->bd.dev; + int err; + + atomic_set(&drvdata->health_work_offline, 1); + + err = __qc_ap_health_data_write(drvdata, drvdata->health); + if (err) + dev_warn(dev, "failed to write ap_health data (%d)\n", err); +} + +void __qc_ap_health_data_write_delayed(struct qc_user_reset_drvdata *drvdata, + ap_health_t *data) +{ + if (atomic_dec_if_positive(&drvdata->health_work_offline) < 0) + return; /* already queued */ + + queue_delayed_work(system_unbound_wq, &drvdata->health_work, 0); +} + +int sec_qc_ap_health_data_write_delayed(ap_health_t *data) +{ + if (!__qc_user_reset_is_probed()) + return -EBUSY; + + __qc_ap_health_data_write_delayed(qc_user_reset, data); + + return 0; +} +EXPORT_SYMBOL_GPL(sec_qc_ap_health_data_write_delayed); + +static inline void __init_lcd_debug_data(void) +{ + struct lcd_debug_t lcd_debug; + + memset(&lcd_debug, 0, sizeof(struct lcd_debug_t)); + + sec_qc_dbg_part_write(debug_index_lcd_debug_info, &lcd_debug); +} + +static inline void __init_ap_health_data(ap_health_t *health) +{ + memset(health, 0, sizeof(ap_health_t)); + + health->header.magic = AP_HEALTH_MAGIC; + health->header.version = AP_HEALTH_VER; + health->header.size = sizeof(ap_health_t); + health->spare_magic1 = AP_HEALTH_MAGIC; + health->spare_magic2 = AP_HEALTH_MAGIC; + health->spare_magic3 = AP_HEALTH_MAGIC; + + sec_qc_dbg_part_write(debug_index_ap_health, health); +} + +static bool __ap_health_is_initialized(ap_health_t *health) +{ + if (health->header.magic != AP_HEALTH_MAGIC || + health->header.version != AP_HEALTH_VER || + health->header.size != sizeof(ap_health_t) || + health->spare_magic1 != AP_HEALTH_MAGIC || + health->spare_magic2 != AP_HEALTH_MAGIC || + health->spare_magic3 != AP_HEALTH_MAGIC) + return false; + + return true; +} + +static unsigned int is_boot_recovery __ro_after_init; +module_param_named(boot_recovery, is_boot_recovery, uint, 0444); + +int __qc_ap_health_init(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct device *dev = drvdata->bd.dev; + ap_health_t *health; + bool valid; + + health = devm_kzalloc(dev, sizeof(ap_health_t), GFP_KERNEL); + if (!health) + return -ENOMEM; + + valid = sec_qc_dbg_part_read(debug_index_ap_health, health); + if (!valid) + return -ENXIO; + + atomic_set(&drvdata->health_work_offline, 1); + INIT_DELAYED_WORK(&drvdata->health_work, + sec_qc_ap_health_data_write_fn); + + if (!__ap_health_is_initialized(health) || is_boot_recovery) { + __init_ap_health_data(health); + __init_lcd_debug_data(); + } + + drvdata->health = health; + + return 0; +} + +void __qc_ap_health_exit(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + + cancel_delayed_work_sync(&drvdata->health_work); +} diff --git a/drivers/samsung/debug/qcom/user_reset/sec_qc_auto_comment.c b/drivers/samsung/debug/qcom/user_reset/sec_qc_auto_comment.c new file mode 100644 index 000000000000..f6f3ab1ea4a5 --- /dev/null +++ b/drivers/samsung/debug/qcom/user_reset/sec_qc_auto_comment.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2016-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include + +#include + +#include "sec_qc_user_reset.h" + +static int __auto_comment_prepare_reset_header( + struct qc_user_reset_proc *auto_comment) +{ + struct debug_reset_header *reset_header; + + reset_header = sec_qc_user_reset_get_reset_header(); + if (IS_ERR_OR_NULL(reset_header)) + return -ENOENT; + + if (reset_header->auto_comment_size == 0) { + kvfree(reset_header); + return -ENODATA; + } + + auto_comment->reset_header = reset_header; + auto_comment->len = reset_header->auto_comment_size; + + return 0; +} + +static void __auto_comment_release_reset_header( + struct qc_user_reset_proc *auto_comment) +{ + struct debug_reset_header *reset_header = auto_comment->reset_header; + + __qc_user_reset_set_reset_header(reset_header); + kvfree(reset_header); + + auto_comment->reset_header = NULL; +} + +static int __auto_comment_prepare_buf( + struct qc_user_reset_proc *auto_comment) +{ + char *buf; + int ret = 0; + ssize_t size; + + size = sec_qc_dbg_part_get_size(debug_index_auto_comment); + if (size <= 0) { + ret = -EINVAL; + goto err_get_size; + } + + buf = kvmalloc(size, GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto err_nomem; + } + + if (!sec_qc_dbg_part_read(debug_index_auto_comment, buf)) { + ret = -ENXIO; + goto failed_to_read; + } + + auto_comment->buf = buf; + + return 0; + +failed_to_read: + kvfree(buf); +err_nomem: +err_get_size: + return ret; +} + +static void __auto_comment_release_buf( + struct qc_user_reset_proc *auto_comment) +{ + kvfree(auto_comment->buf); + auto_comment->buf = NULL; +} + +static int sec_qc_auto_comment_proc_open(struct inode *inode, + struct file *file) +{ + struct qc_user_reset_proc *auto_comment = pde_data(inode); + int err = 0; + + mutex_lock(&auto_comment->lock); + + if (auto_comment->ref_cnt) { + auto_comment->ref_cnt++; + goto already_cached; + } + + err = __auto_comment_prepare_reset_header(auto_comment); + if (err) { + pr_warn("failed to load reset_header (%d)\n", err); + goto err_reset_header; + } + + err = __auto_comment_prepare_buf(auto_comment); + if (err) { + pr_warn("failed to load to buffer (%d)\n", err); + goto err_buf; + } + + auto_comment->ref_cnt++; + + mutex_unlock(&auto_comment->lock); + + return 0; + +err_buf: + __auto_comment_release_reset_header(auto_comment); +err_reset_header: +already_cached: + mutex_unlock(&auto_comment->lock); + return err; +} + +static ssize_t sec_qc_auto_comment_proc_read(struct file *file, + char __user *buf, size_t nbytes, loff_t *ppos) +{ + struct qc_user_reset_proc *auto_comment = pde_data(file_inode(file)); + loff_t pos = *ppos; + + if (pos < 0 || pos > auto_comment->len) + return 0; + + nbytes = min_t(size_t, nbytes, auto_comment->len - pos); + if (copy_to_user(buf, &auto_comment->buf[pos], nbytes)) + return -EFAULT; + + *ppos += nbytes; + + return nbytes; +} + +static loff_t sec_qc_auto_comment_proc_lseek(struct file *file, loff_t off, + int whence) +{ + struct qc_user_reset_proc *auto_comment = pde_data(file_inode(file)); + + return fixed_size_llseek(file, off, whence, auto_comment->len); +} + +static int sec_qc_auto_comment_proc_release(struct inode *inode, + struct file *file) +{ + struct qc_user_reset_proc *auto_comment = pde_data(inode); + + mutex_lock(&auto_comment->lock); + + auto_comment->ref_cnt--; + if (auto_comment->ref_cnt) + goto still_used; + + auto_comment->len = 0; + __auto_comment_release_buf(auto_comment); + __auto_comment_release_reset_header(auto_comment); + +still_used: + mutex_unlock(&auto_comment->lock); + + return 0; +} + +static const struct proc_ops auto_comment_pops = { + .proc_open = sec_qc_auto_comment_proc_open, + .proc_read = sec_qc_auto_comment_proc_read, + .proc_lseek = sec_qc_auto_comment_proc_lseek, + .proc_release = sec_qc_auto_comment_proc_release, +}; + +int __qc_auto_comment_init(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct qc_user_reset_proc *auto_comment = &drvdata->auto_comment; + const char *node_name = "auto_comment"; + + mutex_init(&auto_comment->lock); + + auto_comment->proc = proc_create_data(node_name, 0444, NULL, + &auto_comment_pops, auto_comment); + if (!auto_comment->proc) { + dev_err(drvdata->bd.dev, "failed create procfs node (%s)\n", + node_name); + return -ENODEV; + } + + return 0; +} + +void __qc_auto_comment_exit(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct qc_user_reset_proc *auto_comment = &drvdata->auto_comment; + + proc_remove(auto_comment->proc); + mutex_destroy(&auto_comment->lock); +} diff --git a/drivers/samsung/debug/qcom/user_reset/sec_qc_fmm_lock.c b/drivers/samsung/debug/qcom/user_reset/sec_qc_fmm_lock.c new file mode 100644 index 000000000000..931f9f3faf5d --- /dev/null +++ b/drivers/samsung/debug/qcom/user_reset/sec_qc_fmm_lock.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2016-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include + +#include +#include + +#include "sec_qc_user_reset.h" + +static ssize_t FMM_lock_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int lock; + char str[30]; + const char *status = "UNK"; + + if (sec_param_get(param_index_FMM_lock, &lock)) + status = lock ? "ON" : "OFF"; + + return scnprintf(buf, sizeof(str),"FMM lock : [%s]\n", status); +} + +static ssize_t FMM_lock_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int lock; + + sscanf(buf, "%d", &lock); + if (lock) + lock = FMMLOCK_MAGIC_NUM; + else + lock = 0; + + dev_info(dev, "FMM lock[%s]\n", lock ? "ON" : "OFF"); + sec_param_set(param_index_FMM_lock, &lock); + + return count; +} + +static DEVICE_ATTR_RW(FMM_lock); + +int __qc_fmm_lock_init(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct device *sec_debug_dev = drvdata->sec_debug_dev; + int err; + + err = sysfs_create_file(&sec_debug_dev->kobj, &dev_attr_FMM_lock.attr); + if (err) + dev_err(bd->dev, "failed to create sysfs group\n"); + + return err; +} + +void __qc_fmm_lock_exit(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct device *sec_debug_dev = drvdata->sec_debug_dev; + + sysfs_remove_file(&sec_debug_dev->kobj, &dev_attr_FMM_lock.attr); +} diff --git a/drivers/samsung/debug/qcom/user_reset/sec_qc_kryo_arm64_edac.c b/drivers/samsung/debug/qcom/user_reset/sec_qc_kryo_arm64_edac.c new file mode 100644 index 000000000000..a40b54977706 --- /dev/null +++ b/drivers/samsung/debug/qcom/user_reset/sec_qc_kryo_arm64_edac.c @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2006-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include + +#include "sec_qc_user_reset.h" + +static int sec_qc_kryo_arm64_edac_handler(struct notifier_block *this, + unsigned long l, void *__ctx) +{ + struct qc_user_reset_drvdata *drvdata = container_of(this, + struct qc_user_reset_drvdata, nb_kryo_arm64_edac); + ap_health_t *health = drvdata->health; + + sec_qc_kryo_arm64_edac_update(health, + (struct kryo_arm64_edac_err_ctx *)__ctx); + + __qc_ap_health_data_write_delayed(drvdata, health); + + return NOTIFY_OK; +} + +static int ____qc_kryo_arm64_edac_init(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = container_of(bd, + struct qc_user_reset_drvdata, bd); + struct notifier_block *nb = &drvdata->nb_kryo_arm64_edac; + ap_health_t *health = drvdata->health; + ap_health_t *health_early; + + nb->notifier_call = sec_qc_kryo_arm64_edac_handler; + health_early = qcom_kryo_arm64_edac_error_register_notifier(nb); + if (IS_ERR_OR_NULL(health_early)) + return PTR_ERR(health_early); + + memcpy(&health->cache, &health_early->cache, sizeof(health->cache)); + memcpy(&health->daily_cache, &health_early->daily_cache, + sizeof(health->daily_cache)); + + __qc_ap_health_data_write(drvdata, health); + + return 0; +} + +int __qc_kryo_arm64_edac_init(struct builder *bd) +{ + if (!IS_ENABLED(CONFIG_EDAC_KRYO_ARM64)) + return 0; /* NOTE: do NOT block a proving */ + + return ____qc_kryo_arm64_edac_init(bd); +} + +static void ____qc_kryo_arm64_edac_exit(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = container_of(bd, + struct qc_user_reset_drvdata, bd); + struct notifier_block *nb = &drvdata->nb_kryo_arm64_edac; + + qcom_kryo_arm64_edac_error_unregister_notifier(nb); +} + +void __qc_kryo_arm64_edac_exit(struct builder *bd) +{ + if (!IS_ENABLED(CONFIG_EDAC_KRYO_ARM64)) + return; + + ____qc_kryo_arm64_edac_exit(bd); +} diff --git a/drivers/samsung/debug/qcom/user_reset/sec_qc_modem_reset_data.c b/drivers/samsung/debug/qcom/user_reset/sec_qc_modem_reset_data.c new file mode 100644 index 000000000000..f9bed9addbae --- /dev/null +++ b/drivers/samsung/debug/qcom/user_reset/sec_qc_modem_reset_data.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2006-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#include +#include + +#include +#include + +#include "sec_qc_user_reset.h" + +static int __modem_reset_data_test_last_rst_reason( + struct qc_user_reset_drvdata *drvdata) +{ + struct device *dev = drvdata->bd.dev; + ap_health_t *health; + int ret; + + health = __qc_ap_health_data_read(drvdata); + if (IS_ERR_OR_NULL(health)) { + ret = -ENODEV; + goto err_read_ap_health_data; + } + + switch (health->last_rst_reason) { + case USER_UPLOAD_CAUSE_PANIC: + case USER_UPLOAD_CAUSE_CP_CRASH: + dev_info(dev, "modem reset data will be updated...\n"); + ret = 1; + break; + default: + dev_info(dev, "reset reason is not panic or cp-crash\n"); + ret = 0; + break; + } + +err_read_ap_health_data: + return ret; +} + +static int __modem_reset_data_restore(struct qc_user_reset_drvdata *drvdata) +{ + struct device *dev = drvdata->bd.dev; + struct sec_qc_summary_data_modem *modem; + bool result; + + modem = sec_qc_summary_get_modem(); + if (PTR_ERR(modem) == -EBUSY) + return -EPROBE_DEFER; + else if (IS_ERR_OR_NULL(modem)) { + dev_warn(dev, "modem reset data is skipped."); + return 0; + } + + result = sec_qc_dbg_part_read(debug_index_modem_info, modem); + if (!result) + dev_warn(dev, "failed to read modem reset data!\n"); + + return 0; +} + +int __qc_modem_reset_data_init(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = container_of(bd, + struct qc_user_reset_drvdata, bd); + int is_required; + + is_required = __modem_reset_data_test_last_rst_reason(drvdata); + if (is_required < 0) + return is_required; + + if (!is_required) + return 0; + + return __modem_reset_data_restore(drvdata); +} diff --git a/drivers/samsung/debug/qcom/user_reset/sec_qc_recovery_cause.c b/drivers/samsung/debug/qcom/user_reset/sec_qc_recovery_cause.c new file mode 100644 index 000000000000..8eb73d82fc73 --- /dev/null +++ b/drivers/samsung/debug/qcom/user_reset/sec_qc_recovery_cause.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2016-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include + +#include +#include + +#include "sec_qc_user_reset.h" + +static ssize_t recovery_cause_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char recovery_cause[256]; + bool valid; + + valid = sec_param_get(param_index_reboot_recovery_cause, + recovery_cause); + if (valid) + dev_info(dev, "%s\n", recovery_cause); + else { + strlcpy(recovery_cause, "invalid recovery cause", + sizeof(recovery_cause)); + dev_warn(dev, "%s\n", recovery_cause); + } + + return scnprintf(buf, PAGE_SIZE, "%s", recovery_cause); +} + +static ssize_t recovery_cause_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + char recovery_cause[256]; + bool valid; + + if (count > sizeof(recovery_cause)) { + dev_err(dev, "input buffer length is out of range.\n"); + return -EINVAL; + } + + snprintf(recovery_cause, sizeof(recovery_cause), "%s:%d ", + current->comm, task_pid_nr(current)); + if (strlen(recovery_cause) + strlen(buf) >= sizeof(recovery_cause)) { + dev_err(dev, "input buffer length is out of range.\n"); + return -EINVAL; + } + + strlcat(recovery_cause, buf, sizeof(recovery_cause)); + valid = sec_param_set(param_index_reboot_recovery_cause, + recovery_cause); + if (valid) + dev_info(dev, "%s\n", recovery_cause); + else + dev_warn(dev, "failed to write recovery cause - %s", + recovery_cause); + + return count; +} + +static DEVICE_ATTR_RW(recovery_cause); + +int __qc_recovery_cause_init(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct device *sec_debug_dev = drvdata->sec_debug_dev; + int err; + + err = sysfs_create_file(&sec_debug_dev->kobj, + &dev_attr_recovery_cause.attr); + if (err) + dev_err(bd->dev, "failed to create sysfs group\n"); + + return err; +} + +void __qc_recovery_cause_exit(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct device *sec_debug_dev = drvdata->sec_debug_dev; + + sysfs_remove_file(&sec_debug_dev->kobj, &dev_attr_recovery_cause.attr); +} diff --git a/drivers/samsung/debug/qcom/user_reset/sec_qc_reset_history.c b/drivers/samsung/debug/qcom/user_reset/sec_qc_reset_history.c new file mode 100644 index 000000000000..cefb8cd9edfe --- /dev/null +++ b/drivers/samsung/debug/qcom/user_reset/sec_qc_reset_history.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2016-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "sec_qc_user_reset.h" + +static int __reset_history_prepare_reset_header( + struct qc_user_reset_proc *reset_history) +{ + struct debug_reset_header *reset_header; + + reset_header = sec_qc_user_reset_get_reset_header(); + if (IS_ERR_OR_NULL(reset_header)) + return -ENOENT; + + if (reset_header->reset_history_valid != RESTART_REASON_SEC_DEBUG_MODE || + reset_header->reset_history_cnt == 0) { + pr_warn("reset_history no data.\n"); + kvfree(reset_header); + return -ENODATA; + } + + reset_history->reset_header = reset_header; + + return 0; +} + +static void __reset_history_release_reset_header( + struct qc_user_reset_proc *reset_history) +{ + struct debug_reset_header *reset_header = reset_history->reset_header; + + __qc_user_reset_set_reset_header(reset_header); + kvfree(reset_header); + + reset_history->reset_header = NULL; +} + +struct history_data { + char context[SEC_DEBUG_AUTO_COMMENT_SIZE]; +}; + +static inline void __reset_history_trim_context(struct history_data *history) +{ + size_t i; + + for (i = 0; i < sizeof(history->context); i++) { + char *ch = &history->context[i]; + + if (!isprint(*ch) && !isspace(*ch)) { + *ch = '\0'; + return; + } + } +} + +static size_t __reset_history_copy(char *dst, char *src, + size_t max_size, size_t history_count) +{ + size_t nr_history = history_count >= SEC_DEBUG_RESET_HISTORY_MAX_CNT ? + SEC_DEBUG_RESET_HISTORY_MAX_CNT : history_count; + struct history_data *history = (void *)src; + size_t written = 0; + size_t i; + + for (i = 0; i < nr_history; i++) { + size_t idx = (history_count - 1 - i) % + SEC_DEBUG_RESET_HISTORY_MAX_CNT; + size_t len = max_size > written ? max_size - written : 0; + + if (!len) + break; + + __reset_history_trim_context(&history[idx]); + written += strlcpy(&dst[written], history[idx].context, len); + + len = max_size > written ? max_size - written : 0; + if (!len) + break; + + written += strlcat(&dst[written], "\n\n\n", len); + } + + return written > max_size ? max_size : written; +} + +static int __reset_history_prepare_buf(struct qc_user_reset_proc *reset_history) +{ + const struct debug_reset_header *reset_header = + reset_history->reset_header; + char *buf_raw; + char *buf; + int ret = 0; + ssize_t size; + const ssize_t sz_null_termination = 1; /* to guarantee null teminated string */ + + size = sec_qc_dbg_part_get_size(debug_index_reset_history); + if (size <= 0) { + ret = -EINVAL; + goto err_get_size; + } + + size = PAGE_ALIGN(size + sz_null_termination); + buf_raw = kvmalloc(size, GFP_KERNEL); + buf = kvmalloc(size, GFP_KERNEL); + if (!buf_raw || !buf) { + ret = -ENOMEM; + goto err_nomem; + } + + memset(buf_raw, 0x0, size); + if (!sec_qc_dbg_part_read(debug_index_reset_history, buf_raw)) { + ret = -ENXIO; + goto failed_to_read; + } + + reset_history->len = __reset_history_copy(buf, buf_raw, size, + reset_header->reset_history_cnt); + reset_history->buf = buf; + + kvfree(buf_raw); + + return 0; + +failed_to_read: +err_nomem: + kvfree(buf_raw); + kvfree(buf); +err_get_size: + return ret; +} + +static void __reset_history_release_buf( + struct qc_user_reset_proc *reset_history) +{ + kvfree(reset_history->buf); + reset_history->buf = NULL; +} + +static int sec_qc_reset_history_proc_open(struct inode *inode, + struct file *file) +{ + struct qc_user_reset_proc *reset_history = pde_data(inode); + int err = 0; + + mutex_lock(&reset_history->lock); + + if (reset_history->ref_cnt) { + reset_history->ref_cnt++; + goto already_cached; + } + + err = __reset_history_prepare_reset_header(reset_history); + if (err) { + pr_warn("failed to load reset_header (%d)\n", err); + goto err_reset_header; + } + + err = __reset_history_prepare_buf(reset_history); + if (err) { + pr_warn("failed to load to buffer (%d)\n", err); + goto err_buf; + } + + reset_history->ref_cnt++; + + mutex_unlock(&reset_history->lock); + + return 0; + +err_buf: + __reset_history_release_reset_header(reset_history); +err_reset_header: +already_cached: + mutex_unlock(&reset_history->lock); + return err; +} + +static ssize_t sec_qc_reset_history_proc_read(struct file *file, + char __user *buf, size_t nbytes, loff_t *ppos) +{ + struct qc_user_reset_proc *reset_history = pde_data(file_inode(file)); + loff_t pos = *ppos; + + if (pos < 0 || pos > reset_history->len) + return 0; + + nbytes = min_t(size_t, nbytes, reset_history->len - pos); + if (copy_to_user(buf, &reset_history->buf[pos], nbytes)) + return -EFAULT; + + *ppos += nbytes; + + return nbytes; +} + +static loff_t sec_qc_reset_history_proc_lseek(struct file *file, loff_t off, + int whence) +{ + struct qc_user_reset_proc *reset_history = pde_data(file_inode(file)); + + return fixed_size_llseek(file, off, whence, reset_history->len); +} + +static int sec_qc_reset_history_proc_release(struct inode *inode, + struct file *file) +{ + struct qc_user_reset_proc *reset_history = pde_data(inode); + + mutex_lock(&reset_history->lock); + + reset_history->ref_cnt--; + if (reset_history->ref_cnt) + goto still_used; + + reset_history->len = 0; + __reset_history_release_buf(reset_history); + __reset_history_release_reset_header(reset_history); + +still_used: + mutex_unlock(&reset_history->lock); + + return 0; +} + +static const struct proc_ops reset_history_pops = { + .proc_open = sec_qc_reset_history_proc_open, + .proc_read = sec_qc_reset_history_proc_read, + .proc_lseek = sec_qc_reset_history_proc_lseek, + .proc_release = sec_qc_reset_history_proc_release, +}; + +int __qc_reset_history_init(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct qc_user_reset_proc *reset_history = &drvdata->reset_history; + const char *node_name = "reset_history"; + + mutex_init(&reset_history->lock); + + reset_history->proc = proc_create_data(node_name, 0444, NULL, + &reset_history_pops, reset_history); + if (!reset_history->proc) { + dev_err(drvdata->bd.dev, "failed create procfs node (%s)\n", + node_name); + return -ENODEV; + } + + return 0; +} + +void __qc_reset_history_exit(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct qc_user_reset_proc *reset_history = &drvdata->reset_history; + + proc_remove(reset_history->proc); + mutex_destroy(&reset_history->lock); +} + diff --git a/drivers/samsung/debug/qcom/user_reset/sec_qc_reset_klog.c b/drivers/samsung/debug/qcom/user_reset/sec_qc_reset_klog.c new file mode 100644 index 000000000000..20fcbb33ef88 --- /dev/null +++ b/drivers/samsung/debug/qcom/user_reset/sec_qc_reset_klog.c @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2016-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "sec_qc_user_reset.h" + +static int __reset_klog_prepare_reset_header( + struct qc_user_reset_proc *reset_klog) +{ + struct debug_reset_header *reset_header; + + reset_header = sec_qc_user_reset_get_reset_header(); + if (IS_ERR_OR_NULL(reset_header)) + return -ENOENT; + + reset_klog->reset_header = reset_header; + + return 0; +} + +static void __reset_klog_release_reset_header( + struct qc_user_reset_proc *reset_klog) +{ + struct debug_reset_header *reset_header = reset_klog->reset_header; + + __qc_user_reset_set_reset_header(reset_header); + kvfree(reset_header); + + reset_klog->reset_header = NULL; +} + +static size_t __reset_klog_copy(const struct debug_reset_header *reset_header, + char *dst, const char *src) +{ + const struct sec_log_buf_head *s_log_buf = + (struct sec_log_buf_head *)src; + size_t last_idx, idx, len; + const char *log_src; + size_t klog_buf_max_size; + + if (s_log_buf->magic == SEC_LOG_MAGIC) { + last_idx = max_t(size_t, s_log_buf->idx, + reset_header->ap_klog_idx); + log_src = &s_log_buf->buf[0]; + } else { + last_idx = reset_header->ap_klog_idx; + log_src = src; + } + + klog_buf_max_size = SEC_DEBUG_RESET_KLOG_SIZE - + offsetof(struct sec_log_buf_head, buf); + + idx = last_idx % klog_buf_max_size; + len = 0; + if (last_idx > klog_buf_max_size) { + len = klog_buf_max_size - idx; + memcpy(dst, &log_src[idx], len); + } + + memcpy(&dst[len], log_src, idx); + + return klog_buf_max_size; +} + +static int __reset_klog_prepare_buf(struct qc_user_reset_proc *reset_klog) +{ + const struct debug_reset_header *reset_header = + reset_klog->reset_header; + char *buf_raw; + char *buf; + int ret = 0; + ssize_t size; + + size = sec_qc_dbg_part_get_size(debug_index_reset_klog); + if (size <= 0) { + ret = -EINVAL; + goto err_get_size; + } + + buf_raw = kvmalloc(size, GFP_KERNEL); + buf = kvmalloc(size, GFP_KERNEL); + if (!buf_raw || !buf) { + ret = -ENOMEM; + goto err_nomem; + } + + if (!sec_qc_dbg_part_read(debug_index_reset_klog, buf_raw)) { + ret = -ENXIO; + goto failed_to_read; + } + + reset_klog->len = __reset_klog_copy(reset_header, buf, buf_raw); + reset_klog->buf = buf; + + kvfree(buf_raw); + + return 0; + +failed_to_read: +err_nomem: + kvfree(buf_raw); + kvfree(buf); +err_get_size: + return ret; +} + +static void __reset_klog_release_buf( + struct qc_user_reset_proc *reset_klog) +{ + kvfree(reset_klog->buf); + reset_klog->buf = NULL; +} + +static int sec_qc_reset_klog_proc_open(struct inode *inode, struct file *file) +{ + struct qc_user_reset_proc *reset_klog = pde_data(inode); + int err = 0; + + mutex_lock(&reset_klog->lock); + + if (reset_klog->ref_cnt) { + reset_klog->ref_cnt++; + goto already_cached; + } + + err = __reset_klog_prepare_reset_header(reset_klog); + if (err) { + pr_warn("failed to load reset_header (%d)\n", err); + goto err_reset_header; + } + + err = __reset_klog_prepare_buf(reset_klog); + if (err) { + pr_warn("failed to load to buffer (%d)\n", err); + goto err_buf; + } + + reset_klog->ref_cnt++; + + mutex_unlock(&reset_klog->lock); + + return 0; + +err_buf: + __reset_klog_release_reset_header(reset_klog); +err_reset_header: +already_cached: + mutex_unlock(&reset_klog->lock); + return err; +} + +static ssize_t sec_qc_reset_klog_proc_read(struct file *file, + char __user *buf, size_t nbytes, loff_t *ppos) +{ + struct qc_user_reset_proc *reset_klog = pde_data(file_inode(file)); + loff_t pos = *ppos; + + if (pos < 0 || pos > reset_klog->len) + return 0; + + nbytes = min_t(size_t, nbytes, reset_klog->len - pos); + if (copy_to_user(buf, &reset_klog->buf[pos], nbytes)) + return -EFAULT; + + *ppos += nbytes; + + return nbytes; +} + +static loff_t sec_qc_reset_klog_proc_lseek(struct file *file, loff_t off, + int whence) +{ + struct qc_user_reset_proc *reset_klog = pde_data(file_inode(file)); + + return fixed_size_llseek(file, off, whence, reset_klog->len); +} + +static int sec_qc_reset_klog_proc_release(struct inode *inode, + struct file *file) +{ + struct qc_user_reset_proc *reset_klog = pde_data(inode); + + mutex_lock(&reset_klog->lock); + + reset_klog->ref_cnt--; + if (reset_klog->ref_cnt) + goto still_used; + + reset_klog->len = 0; + __reset_klog_release_buf(reset_klog); + __reset_klog_release_reset_header(reset_klog); + +still_used: + mutex_unlock(&reset_klog->lock); + + return 0; +} + +static const struct proc_ops reset_klog_pops = { + .proc_open = sec_qc_reset_klog_proc_open, + .proc_read = sec_qc_reset_klog_proc_read, + .proc_lseek = sec_qc_reset_klog_proc_lseek, + .proc_release = sec_qc_reset_klog_proc_release, +}; + +int __qc_reset_klog_init(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct qc_user_reset_proc *reset_klog = &drvdata->reset_klog; + const char *node_name = "reset_klog"; + + mutex_init(&reset_klog->lock); + + reset_klog->proc = proc_create_data(node_name, 0444, NULL, + &reset_klog_pops, reset_klog); + if (!reset_klog->proc) { + dev_err(drvdata->bd.dev, "failed create procfs node (%s)\n", + node_name); + return -ENODEV; + } + + return 0; +} + +void __qc_reset_klog_exit(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct qc_user_reset_proc *reset_klog = &drvdata->reset_klog; + + proc_remove(reset_klog->proc); + mutex_destroy(&reset_klog->lock); +} diff --git a/drivers/samsung/debug/qcom/user_reset/sec_qc_reset_reason.c b/drivers/samsung/debug/qcom/user_reset/sec_qc_reset_reason.c new file mode 100644 index 000000000000..6b5493d38a65 --- /dev/null +++ b/drivers/samsung/debug/qcom/user_reset/sec_qc_reset_reason.c @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2006-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include + +#include + +#include "sec_qc_user_reset.h" + +unsigned int sec_qc_reset_reason_get(void) +{ + struct qc_reset_reason_proc *rr_proc; + + if (!__qc_user_reset_is_probed()) + return 0xFFEEFFEE; + + rr_proc = &qc_user_reset->reset_reason; + + return rr_proc->reset_reason; +} +EXPORT_SYMBOL_GPL(sec_qc_reset_reason_get); + +static const char *reset_reason_str[] = { + [USER_UPLOAD_CAUSE_SMPL] = "SP", + [USER_UPLOAD_CAUSE_WTSR] = "WP", + [USER_UPLOAD_CAUSE_WATCHDOG] = "DP", + [USER_UPLOAD_CAUSE_PANIC] = "KP", + [USER_UPLOAD_CAUSE_MANUAL_RESET] = "MP", + [USER_UPLOAD_CAUSE_POWER_RESET] = "PP", + [USER_UPLOAD_CAUSE_REBOOT] = "RP", + [USER_UPLOAD_CAUSE_BOOTLOADER_REBOOT] = "BP", + [USER_UPLOAD_CAUSE_POWER_ON] = "NP", + [USER_UPLOAD_CAUSE_THERMAL] = "TP", + [USER_UPLOAD_CAUSE_CP_CRASH] = "CP", + [USER_UPLOAD_CAUSE_UNKNOWN] = "NP", +}; + +const char *sec_qc_reset_reason_to_str(unsigned int reason) +{ + if (reason < USER_UPLOAD_CAUSE_MIN || reason > USER_UPLOAD_CAUSE_MAX) + reason = USER_UPLOAD_CAUSE_UNKNOWN; + + return reset_reason_str[reason]; +} +EXPORT_SYMBOL_GPL(sec_qc_reset_reason_to_str); + +static void ____reset_reason_update_and_clear( + struct qc_reset_reason_proc *rr_proc, + ap_health_t *health) +{ + switch (rr_proc->reset_reason) { + case USER_UPLOAD_CAUSE_SMPL: + health->daily_rr.sp++; + health->rr.sp++; + break; + case USER_UPLOAD_CAUSE_WTSR: + health->daily_rr.wp++; + health->rr.wp++; + break; + case USER_UPLOAD_CAUSE_WATCHDOG: + health->daily_rr.dp++; + health->rr.dp++; + break; + case USER_UPLOAD_CAUSE_PANIC: + health->daily_rr.kp++; + health->rr.kp++; + break; + case USER_UPLOAD_CAUSE_MANUAL_RESET: + health->daily_rr.mp++; + health->rr.mp++; + break; + case USER_UPLOAD_CAUSE_POWER_RESET: + health->daily_rr.pp++; + health->rr.pp++; + break; + case USER_UPLOAD_CAUSE_REBOOT: + health->daily_rr.rp++; + health->rr.rp++; + break; + case USER_UPLOAD_CAUSE_THERMAL: + health->daily_rr.tp++; + health->rr.tp++; + break; + case USER_UPLOAD_CAUSE_CP_CRASH: + health->daily_rr.cp++; + health->rr.cp++; + break; + default: + health->daily_rr.np++; + health->rr.np++; + } + + health->last_rst_reason = 0; +} + +static int __reset_reason_update_and_clear(struct qc_reset_reason_proc *rr_proc) +{ + struct qc_user_reset_drvdata *drvdata = container_of(rr_proc, + struct qc_user_reset_drvdata, reset_reason); + ap_health_t *health; + bool valid; + + if (rr_proc->lpm_mode) + return 0; + + if (atomic_dec_if_positive(&rr_proc->first_run) < 0) + return 0; + + health = __qc_ap_health_data_read(drvdata); + if (IS_ERR_OR_NULL(health)) + return -ENODEV; + + ____reset_reason_update_and_clear(rr_proc, health); + + valid = __qc_ap_health_data_write(drvdata, health); + if (!valid) + return -EINVAL; + + return 0; +} + +static int sec_qc_reset_reason_proc_show(struct seq_file *m, void *v) +{ + struct qc_reset_reason_proc *rr_proc = m->private; + unsigned int reset_reason = rr_proc->reset_reason; + int err; + + seq_printf(m, "%sON\n", sec_qc_reset_reason_to_str(reset_reason)); + + err = __reset_reason_update_and_clear(rr_proc); + if (err) + pr_warn("failed to update and clear reset reason."); + + return 0; +} + +static int sec_qc_reset_reason_proc_open(struct inode *inode, struct file *file) +{ + struct qc_reset_reason_proc *rr_proc = pde_data(inode); + + return single_open(file, sec_qc_reset_reason_proc_show, rr_proc); +} + +static const struct proc_ops reset_reason_pops = { + .proc_open = sec_qc_reset_reason_proc_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = single_release, +}; + +static int __reset_reason_read(struct qc_reset_reason_proc *rr_proc) +{ + struct qc_user_reset_drvdata *drvdata = container_of(rr_proc, + struct qc_user_reset_drvdata, reset_reason); + ap_health_t *health; + int ret = 0; + + health = __qc_ap_health_data_read(drvdata); + if (IS_ERR_OR_NULL(health)) { + ret = -ENODEV; + goto err_read_ap_health_data; + } + + rr_proc->reset_reason = health->last_rst_reason; + + return 0; + +err_read_ap_health_data: + return ret; +} + +static inline int __rest_reasson_proc_create(struct qc_reset_reason_proc *rr_proc) +{ + struct qc_user_reset_drvdata *drvdata = container_of(rr_proc, + struct qc_user_reset_drvdata, reset_reason); + const char *node_name = "reset_reason"; + + rr_proc->proc = proc_create_data(node_name, 0444, NULL, + &reset_reason_pops, rr_proc); + if (!rr_proc->proc) { + dev_err(drvdata->bd.dev, "failed create procfs node (%s)\n", + node_name); + return -ENODEV; + } + + return 0; +} + +static char *boot_mode __ro_after_init; +module_param_named(boot_mode, boot_mode, charp, 0444); + +static inline void __reset_reason_chec_lpm_mode(struct qc_reset_reason_proc *rr_proc) +{ + if (!boot_mode || strncmp(boot_mode, "charger", strlen("charger"))) + rr_proc->lpm_mode = false; + else + rr_proc->lpm_mode = true; +} + +int __qc_reset_reason_init(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct qc_reset_reason_proc *rr_proc = &drvdata->reset_reason; + int err; + + atomic_set(&rr_proc->first_run, 1); + + err = __reset_reason_read(rr_proc); + if (err) + return err; + err = __rest_reasson_proc_create(rr_proc); + if (err) + return err; + + __reset_reason_chec_lpm_mode(rr_proc); + __reset_reason_update_and_clear(rr_proc); + + return 0; +} + +void __qc_reset_reason_exit(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct qc_reset_reason_proc *rr_proc = &drvdata->reset_reason; + + proc_remove(rr_proc->proc); +} diff --git a/drivers/samsung/debug/qcom/user_reset/sec_qc_reset_rwc.c b/drivers/samsung/debug/qcom/user_reset/sec_qc_reset_rwc.c new file mode 100644 index 000000000000..0f8401ef206b --- /dev/null +++ b/drivers/samsung/debug/qcom/user_reset/sec_qc_reset_rwc.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2006-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include + +#include "sec_qc_user_reset.h" + +static struct debug_reset_header *__user_reset_alloc_reset_header(void) +{ + struct debug_reset_header *header; + int err; + bool valid; + + header = kmalloc(sizeof(*header), GFP_KERNEL); + if (!header) { + err = -ENOMEM; + goto err_nomem; + } + + valid = sec_qc_dbg_part_read(debug_index_reset_summary_info, header); + if (!valid) { + err = -ENXIO; + goto failed_to_read; + } + + return header; + +failed_to_read: + kfree(header); +err_nomem: + return ERR_PTR(err); +} + +struct debug_reset_header *sec_qc_user_reset_get_reset_header(void) +{ + static int get_state = DRH_STATE_INIT; + struct debug_reset_header *header; + struct device *dev = qc_user_reset->bd.dev; + struct qc_reset_rwc_proc *reset_rwc = &qc_user_reset->reset_rwc; + struct mutex *lock = &reset_rwc->lock; + + mutex_lock(lock); + + if (get_state == DRH_STATE_INVALID) + goto err_drh_state_invalid; + + header = __user_reset_alloc_reset_header(); + if (IS_ERR_OR_NULL(header)) { + dev_warn(dev, "failed to read debug partition (%ld)", + PTR_ERR(header)); + goto err_debug_partition; + } + + if (get_state == DRH_STATE_VALID) { + mutex_unlock(lock); + return header; + } + + if (header->write_times == header->read_times) { + dev_warn(dev, "untrustworthy debug_rest_header\n"); + get_state = DRH_STATE_INVALID; + goto err_untrustworthy_header; + } + + reset_rwc->reset_write_cnt = header->write_times; + get_state = DRH_STATE_VALID; + mutex_unlock(lock); + return header; + +err_untrustworthy_header: + kfree(header); +err_debug_partition: +err_drh_state_invalid: + mutex_unlock(lock); + return NULL; +} +EXPORT_SYMBOL_GPL(sec_qc_user_reset_get_reset_header); + +int __qc_user_reset_set_reset_header(struct debug_reset_header *header) +{ + static int set_state = DRH_STATE_INIT; + struct mutex *lock = &qc_user_reset->reset_rwc.lock; + struct device *dev = qc_user_reset->bd.dev; + int ret = 0; + + mutex_lock(lock); + if (set_state == DRH_STATE_VALID) { + dev_info(dev, "debug_reset_header works well\n"); + mutex_unlock(lock); + return 0; + } + + if ((header->write_times - 1) == header->read_times) { + dev_info(dev, "debug_reset_header works well\n"); + header->read_times++; + } else { + dev_info(dev, "debug_reset_header read[%d] and write[%d] has sync error\n", + header->read_times, header->write_times); + header->read_times = header->write_times; + } + + if (!sec_qc_dbg_part_write(debug_index_reset_summary_info, header)) { + dev_warn(dev, "filed to writhe debug_reset_header\n"); + ret = -ENOENT; + } else { + set_state = DRH_STATE_VALID; + } + + mutex_unlock(lock); + + return ret; +} + +static int __qc_get_reset_write_cnt(void) +{ + struct qc_reset_rwc_proc *reset_rwc = &qc_user_reset->reset_rwc; + + return reset_rwc->reset_write_cnt; +} + +int sec_qc_get_reset_write_cnt(void) +{ + if (!__qc_user_reset_is_probed()) + return 0; + + return __qc_get_reset_write_cnt(); +} +EXPORT_SYMBOL_GPL(sec_qc_get_reset_write_cnt); + +static int sec_qc_reset_rwc_proc_show(struct seq_file *m, void *v) +{ + seq_printf(m, "%d", sec_qc_get_reset_write_cnt()); + + return 0; +} + +static int sec_qc_reset_rwc_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, sec_qc_reset_rwc_proc_show, NULL); +} + +static const struct proc_ops reset_rwc_pops = { + .proc_open = sec_qc_reset_rwc_proc_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = single_release, +}; + +int __qc_reset_rwc_init(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct qc_reset_rwc_proc *reset_rwc = &drvdata->reset_rwc; + const char *node_name = "reset_rwc"; + + mutex_init(&reset_rwc->lock); + + reset_rwc->proc = proc_create(node_name, 0444, NULL, + &reset_rwc_pops); + if (!reset_rwc->proc) { + dev_err(drvdata->bd.dev, "failed create procfs node (%s)\n", + node_name); + return -ENODEV; + } + + return 0; +} + +void __qc_reset_rwc_exit(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct qc_reset_rwc_proc *reset_rwc = &drvdata->reset_rwc; + + proc_remove(reset_rwc->proc); + mutex_destroy(&reset_rwc->lock); +} diff --git a/drivers/samsung/debug/qcom/user_reset/sec_qc_reset_summary.c b/drivers/samsung/debug/qcom/user_reset/sec_qc_reset_summary.c new file mode 100644 index 000000000000..d7dd875f663d --- /dev/null +++ b/drivers/samsung/debug/qcom/user_reset/sec_qc_reset_summary.c @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2016-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include + +#include + +#include "sec_qc_user_reset.h" + +static int __reset_summary_prepare_reset_header( + struct qc_user_reset_proc *reset_summary) +{ + struct debug_reset_header *reset_header; + ssize_t size; + + reset_header = sec_qc_user_reset_get_reset_header(); + if (IS_ERR_OR_NULL(reset_header)) + return -ENOENT; + + size = sec_qc_dbg_part_get_size(debug_index_reset_summary); + if (size <= 0 || reset_header->summary_size > size ) { + kvfree(reset_header); + return -EINVAL; + } + + reset_summary->reset_header = reset_header; + reset_summary->len = reset_header->summary_size; + + return 0; +} + +static void __reset_summary_release_reset_header( + struct qc_user_reset_proc *reset_summary) +{ + struct debug_reset_header *reset_header = reset_summary->reset_header; + + __qc_user_reset_set_reset_header(reset_header); + kvfree(reset_header); + + reset_summary->reset_header = NULL; +} + +static int __reset_summary_prepare_buf( + struct qc_user_reset_proc *reset_summary) +{ + char *buf; + int ret = 0; + ssize_t size; + + size = sec_qc_dbg_part_get_size(debug_index_reset_summary); + if (size <= 0) { + ret = -EINVAL; + goto err_get_size; + } + + buf = kvmalloc(size, GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto err_nomem; + } + + if (!sec_qc_dbg_part_read(debug_index_reset_summary, buf)) { + ret = -ENXIO; + goto failed_to_read; + } + + reset_summary->buf = buf; + + return 0; + +failed_to_read: + kvfree(buf); +err_nomem: +err_get_size: + return ret; +} + +static void __reset_summary_release_buf( + struct qc_user_reset_proc *reset_summary) +{ + kvfree(reset_summary->buf); + reset_summary->buf = NULL; +} + +static int sec_qc_reset_summary_proc_open(struct inode *inode, + struct file *file) +{ + struct qc_user_reset_proc *reset_summary = pde_data(inode); + int err = 0; + + mutex_lock(&reset_summary->lock); + + if (reset_summary->ref_cnt) { + reset_summary->ref_cnt++; + goto already_cached; + } + + err = __reset_summary_prepare_reset_header(reset_summary); + if (err) { + pr_warn("failed to load reset_header (%d)\n", err); + goto err_reset_header; + } + + err = __reset_summary_prepare_buf(reset_summary); + if (err) { + pr_warn("failed to load to buffer (%d)\n", err); + goto err_buf; + } + + reset_summary->ref_cnt++; + + mutex_unlock(&reset_summary->lock); + + return 0; + +err_buf: + __reset_summary_release_reset_header(reset_summary); +err_reset_header: +already_cached: + mutex_unlock(&reset_summary->lock); + return err; +} + +static ssize_t sec_qc_reset_summary_proc_read(struct file *file, + char __user *buf, size_t nbytes, loff_t *ppos) +{ + struct qc_user_reset_proc *reset_summary = pde_data(file_inode(file)); + loff_t pos = *ppos; + + if (pos < 0 || pos > reset_summary->len) + return 0; + + nbytes = min_t(size_t, nbytes, reset_summary->len - pos); + if (copy_to_user(buf, &reset_summary->buf[pos], nbytes)) + return -EFAULT; + + *ppos += nbytes; + + return nbytes; +} + +static loff_t sec_qc_reset_summary_proc_lseek(struct file *file, loff_t off, + int whence) +{ + struct qc_user_reset_proc *reset_summary = pde_data(file_inode(file)); + + return fixed_size_llseek(file, off, whence, reset_summary->len); +} + +static int sec_qc_reset_summary_proc_release(struct inode *inode, + struct file *file) +{ + struct qc_user_reset_proc *reset_summary = pde_data(inode); + + mutex_lock(&reset_summary->lock); + + reset_summary->ref_cnt--; + if (reset_summary->ref_cnt) + goto still_used; + + reset_summary->len = 0; + __reset_summary_release_buf(reset_summary); + __reset_summary_release_reset_header(reset_summary); + +still_used: + mutex_unlock(&reset_summary->lock); + + return 0; +} + +static const struct proc_ops reset_summary_pops = { + .proc_open = sec_qc_reset_summary_proc_open, + .proc_read = sec_qc_reset_summary_proc_read, + .proc_lseek = sec_qc_reset_summary_proc_lseek, + .proc_release = sec_qc_reset_summary_proc_release, +}; + +int __qc_reset_summary_init(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct qc_user_reset_proc *reset_summary = &drvdata->reset_summary; + const char *node_name = "reset_summary"; + + mutex_init(&reset_summary->lock); + + reset_summary->proc = proc_create_data(node_name, 0444, NULL, + &reset_summary_pops, reset_summary); + if (!reset_summary->proc) { + dev_err(drvdata->bd.dev, "failed create procfs node (%s)\n", + node_name); + return -ENODEV; + } + + return 0; +} + +void __qc_reset_summary_exit(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct qc_user_reset_proc *reset_summary = &drvdata->reset_summary; + + proc_remove(reset_summary->proc); + mutex_destroy(&reset_summary->lock); +} diff --git a/drivers/samsung/debug/qcom/user_reset/sec_qc_reset_tzlog.c b/drivers/samsung/debug/qcom/user_reset/sec_qc_reset_tzlog.c new file mode 100644 index 000000000000..431429491c94 --- /dev/null +++ b/drivers/samsung/debug/qcom/user_reset/sec_qc_reset_tzlog.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2016-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include + +#include + +#include "sec_qc_user_reset.h" + +static int __reset_tzlog_prepare_reset_header( + struct qc_user_reset_proc *reset_tzlog) +{ + struct debug_reset_header *reset_header; + ssize_t len; + int ret; + + reset_header = sec_qc_user_reset_get_reset_header(); + if (IS_ERR_OR_NULL(reset_header)) { + ret = -ENOENT; + goto err_no_reset_header; + } + + if (reset_header->stored_tzlog == 0) { + pr_warn("the target didn't run sdi\n"); + ret = -EINVAL; + goto err_no_sdi_run; + } + + len = sec_qc_dbg_part_get_size(debug_index_reset_tzlog); + if (len <= 0) { + ret = -ENODEV; + goto err_invalid_part_size; + } + + reset_tzlog->reset_header = reset_header; + reset_tzlog->len = len; + + return 0; + +err_invalid_part_size: +err_no_sdi_run: + kvfree(reset_header); +err_no_reset_header: + return ret; +} + +static void __reset_tzlog_release_reset_header( + struct qc_user_reset_proc *reset_tzlog) +{ + struct debug_reset_header *reset_header = reset_tzlog->reset_header; + + __qc_user_reset_set_reset_header(reset_header); + kvfree(reset_header); + + reset_tzlog->reset_header = NULL; +} + +static int __reset_tzlog_prepare_buf( + struct qc_user_reset_proc *reset_tzlog) +{ + char *buf; + int ret = 0; + ssize_t size; + + size = sec_qc_dbg_part_get_size(debug_index_reset_tzlog); + if (size <= 0) { + ret = -EINVAL; + goto err_get_size; + } + + buf = kvmalloc(size, GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto err_nomem; + } + + if (!sec_qc_dbg_part_read(debug_index_reset_tzlog, buf)) { + ret = -ENXIO; + goto failed_to_read; + } + + reset_tzlog->buf = buf; + + return 0; + +failed_to_read: + kvfree(buf); +err_nomem: +err_get_size: + return ret; +} + +static void __reset_tzlog_release_buf(struct qc_user_reset_proc *reset_tzlog) +{ + kvfree(reset_tzlog->buf); + reset_tzlog->buf = NULL; +} + +static int sec_qc_reset_tzlog_proc_open(struct inode *inode, struct file *file) +{ + struct qc_user_reset_proc *reset_tzlog = pde_data(inode); + int err = 0; + + mutex_lock(&reset_tzlog->lock); + + if (reset_tzlog->ref_cnt) { + reset_tzlog->ref_cnt++; + goto already_cached; + } + + err = __reset_tzlog_prepare_reset_header(reset_tzlog); + if (err) { + pr_warn("failed to load reset_header (%d)\n", err); + goto err_reset_header; + } + + err = __reset_tzlog_prepare_buf(reset_tzlog); + if (err) { + pr_warn("failed to load to buffer (%d)\n", err); + goto err_buf; + } + + reset_tzlog->ref_cnt++; + + mutex_unlock(&reset_tzlog->lock); + + return 0; + +err_buf: + __reset_tzlog_release_reset_header(reset_tzlog); +err_reset_header: +already_cached: + mutex_unlock(&reset_tzlog->lock); + return err; +} + +static ssize_t sec_qc_reset_tzlog_proc_read(struct file *file, + char __user *buf, size_t nbytes, loff_t *ppos) +{ + struct qc_user_reset_proc *reset_tzlog = pde_data(file_inode(file)); + loff_t pos = *ppos; + + if (pos < 0 || pos > reset_tzlog->len) + return 0; + + nbytes = min_t(size_t, nbytes, reset_tzlog->len - pos); + if (copy_to_user(buf, &reset_tzlog->buf[pos], nbytes)) + return -EFAULT; + + *ppos += nbytes; + + return nbytes; +} + +static loff_t sec_qc_reset_tzlog_proc_lseek(struct file *file, loff_t off, + int whence) +{ + struct qc_user_reset_proc *reset_tzlog = pde_data(file_inode(file)); + + return fixed_size_llseek(file, off, whence, reset_tzlog->len); +} + +static int sec_qc_reset_tzlog_proc_release(struct inode *inode, + struct file *file) +{ + struct qc_user_reset_proc *reset_tzlog = pde_data(inode); + + mutex_lock(&reset_tzlog->lock); + + reset_tzlog->ref_cnt--; + if (reset_tzlog->ref_cnt) + goto still_used; + + reset_tzlog->len = 0; + __reset_tzlog_release_buf(reset_tzlog); + __reset_tzlog_release_reset_header(reset_tzlog); + +still_used: + mutex_unlock(&reset_tzlog->lock); + + return 0; +} + +static const struct proc_ops reset_tzlog_pops = { + .proc_open = sec_qc_reset_tzlog_proc_open, + .proc_read = sec_qc_reset_tzlog_proc_read, + .proc_lseek = sec_qc_reset_tzlog_proc_lseek, + .proc_release = sec_qc_reset_tzlog_proc_release, +}; + +int __qc_reset_tzlog_init(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct qc_user_reset_proc *reset_tzlog = &drvdata->reset_tzlog; + const char *node_name = "reset_tzlog"; + + mutex_init(&reset_tzlog->lock); + + reset_tzlog->proc = proc_create_data(node_name, 0444, NULL, + &reset_tzlog_pops, reset_tzlog); + if (!reset_tzlog->proc) { + dev_err(drvdata->bd.dev, "failed create procfs node (%s)\n", + node_name); + return -ENODEV; + } + + return 0; +} + +void __qc_reset_tzlog_exit(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct qc_user_reset_proc *reset_tzlog = &drvdata->reset_tzlog; + + proc_remove(reset_tzlog->proc); + mutex_destroy(&reset_tzlog->lock); +} diff --git a/drivers/samsung/debug/qcom/user_reset/sec_qc_store_lastkmsg.c b/drivers/samsung/debug/qcom/user_reset/sec_qc_store_lastkmsg.c new file mode 100644 index 000000000000..f44501b5a3bb --- /dev/null +++ b/drivers/samsung/debug/qcom/user_reset/sec_qc_store_lastkmsg.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2006-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include + +#include + +#include "sec_qc_user_reset.h" + +static int sec_qc_store_lastkmsg_proc_show(struct seq_file *m, void *v) +{ + struct debug_reset_header *is_stored = + sec_qc_user_reset_get_reset_header(); + + if (IS_ERR_OR_NULL(is_stored)) + seq_puts(m, "0\n"); + else { + seq_puts(m, "1\n"); + kfree(is_stored); + } + + return 0; +} + +static int sec_qc_store_lastkmsg_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, sec_qc_store_lastkmsg_proc_show, NULL); +} + +static const struct proc_ops store_lastkmsg_pops = { + .proc_open = sec_qc_store_lastkmsg_proc_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = single_release, +}; + +int __qc_store_lastkmsg_init(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct proc_dir_entry *proc = drvdata->store_lastkmsg_proc; + const char *node_name = "store_lastkmsg"; + + proc = proc_create(node_name, 0444, NULL, &store_lastkmsg_pops); + if (!proc) { + dev_err(drvdata->bd.dev, "failed create procfs node (%s)\n", + node_name); + return -ENODEV; + } + + return 0; +} + +void __qc_store_lastkmsg_exit(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct proc_dir_entry *proc = drvdata->store_lastkmsg_proc; + + proc_remove(proc); +} diff --git a/drivers/samsung/debug/qcom/user_reset/sec_qc_user_reset.h b/drivers/samsung/debug/qcom/user_reset/sec_qc_user_reset.h new file mode 100644 index 000000000000..725107806eb8 --- /dev/null +++ b/drivers/samsung/debug/qcom/user_reset/sec_qc_user_reset.h @@ -0,0 +1,116 @@ +#ifndef __INTERNAL__SEC_QC_USER_RESET_H__ +#define __INTERNAL__SEC_QC_USER_RESET_H__ + +#include +#include + +#include +#include +#include + +#include "sec_qc_user_reset_external.h" + +struct qc_reset_rwc_proc { + struct mutex lock; + uint32_t reset_write_cnt; + struct proc_dir_entry *proc; +}; + +struct qc_user_reset_proc { + struct mutex lock; + unsigned int ref_cnt; + struct debug_reset_header *reset_header; + const char *buf; + size_t len; + struct proc_dir_entry *proc; +}; + +struct qc_reset_reason_proc { + unsigned int reset_reason; + bool lpm_mode; + atomic_t first_run; + struct proc_dir_entry *proc; +}; + +struct qc_user_reset_drvdata { + struct builder bd; + ap_health_t *health; + atomic_t health_work_offline; + struct delayed_work health_work; + struct device *sec_debug_dev; + struct qc_reset_rwc_proc reset_rwc; + struct qc_user_reset_proc reset_summary; + struct qc_user_reset_proc reset_klog; + struct qc_user_reset_proc reset_tzlog; + struct qc_user_reset_proc auto_comment; + struct qc_user_reset_proc reset_history; + struct qc_reset_reason_proc reset_reason; + struct notifier_block nb_kryo_arm64_edac; + struct proc_dir_entry *store_lastkmsg_proc; +}; + +extern struct qc_user_reset_drvdata *qc_user_reset; + +static __always_inline bool __qc_user_reset_is_probed(void) +{ + return !!qc_user_reset; +} + +/* sec_qc_dbg_part_ap_health.c */ +extern int __qc_ap_health_init(struct builder *bd); +extern void __qc_ap_health_exit(struct builder *bd); +/* TODO: internal use only */ +extern ap_health_t *__qc_ap_health_data_read(struct qc_user_reset_drvdata *drvdata); +extern int __qc_ap_health_data_write(struct qc_user_reset_drvdata *drvdata, ap_health_t *data); +extern void __qc_ap_health_data_write_delayed(struct qc_user_reset_drvdata *drvdata, ap_health_t *data); + +/* sec_qc_reset_rwc.c */ +extern int __qc_reset_rwc_init(struct builder *bd); +extern void __qc_reset_rwc_exit(struct builder *bd); +/* TODO: internal use only */ +extern int __qc_user_reset_set_reset_header(struct debug_reset_header *header); + +/* sec_qc_reset_reason.c */ +extern int __qc_reset_reason_init(struct builder *bd); +extern void __qc_reset_reason_exit(struct builder *bd); + +/* sec_qc_reset_summary.c */ +extern int __qc_reset_summary_init(struct builder *bd); +extern void __qc_reset_summary_exit(struct builder *bd); + +/* sec_qc_reset_klog.c */ +extern int __qc_reset_klog_init(struct builder *bd); +extern void __qc_reset_klog_exit(struct builder *bd); + +/* sec_qc_reset_tzlog.c */ +extern int __qc_reset_tzlog_init(struct builder *bd); +extern void __qc_reset_tzlog_exit(struct builder *bd); + +/* sec_qc_auto_comment.c */ +extern int __qc_auto_comment_init(struct builder *bd); +extern void __qc_auto_comment_exit(struct builder *bd); + +/* sec_qc_reset_history.c */ +extern int __qc_reset_history_init(struct builder *bd); +extern void __qc_reset_history_exit(struct builder *bd); + +/* sec_qc_fmm_lock.c */ +extern int __qc_fmm_lock_init(struct builder *bd); +extern void __qc_fmm_lock_exit(struct builder *bd); + +/* sec_qc_recovery_cause.c */ +extern int __qc_recovery_cause_init(struct builder *bd); +extern void __qc_recovery_cause_exit(struct builder *bd); + +/* sec_qc_kryo_arm64_edac.c */ +extern int __qc_kryo_arm64_edac_init(struct builder *bd); +extern void __qc_kryo_arm64_edac_exit(struct builder *bd); + +/* sec_qc_store_lastkmsg.c */ +extern int __qc_store_lastkmsg_init(struct builder *bd); +extern void __qc_store_lastkmsg_exit(struct builder *bd); + +/* sec_qc_modem_reset_data.c */ +extern int __qc_modem_reset_data_init(struct builder *bd); + +#endif /* __INTERNAL__SEC_QC_USER_RESET_H__ */ diff --git a/drivers/samsung/debug/qcom/user_reset/sec_qc_user_reset_external.h b/drivers/samsung/debug/qcom/user_reset/sec_qc_user_reset_external.h new file mode 100644 index 000000000000..5ff44e773c4c --- /dev/null +++ b/drivers/samsung/debug/qcom/user_reset/sec_qc_user_reset_external.h @@ -0,0 +1,12 @@ +#ifndef __INTERNAL__SEC_QC_USER_RESET_EXTERNAL_H__ +#define __INTERNAL__SEC_QC_USER_RESET_EXTERNAL_H__ + +#if IS_ENABLED(CONFIG_EDAC_KRYO_ARM64) +/* implemented @ drivers/edac/kryo_arm64_edac.c */ +extern ap_health_t *qcom_kryo_arm64_edac_error_register_notifier(struct notifier_block *nb); +extern int qcom_kryo_arm64_edac_error_unregister_notifier(struct notifier_block *nb); +#else +#include +#endif + +#endif /* __INTERNAL__SEC_QC_USER_RESET_EXTERNAL_H__ */ diff --git a/drivers/samsung/debug/qcom/user_reset/sec_qc_user_reset_main.c b/drivers/samsung/debug/qcom/user_reset/sec_qc_user_reset_main.c new file mode 100644 index 000000000000..db70790d0c7b --- /dev/null +++ b/drivers/samsung/debug/qcom/user_reset/sec_qc_user_reset_main.c @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2006-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include + +#include +#include + +#include "sec_qc_user_reset.h" + +struct qc_user_reset_drvdata *qc_user_reset; + +static noinline int __user_reset_test_dbg_partition(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct debug_reset_header __reset_header; + struct debug_reset_header *reset_header = &__reset_header; + bool valid; + + valid = sec_qc_dbg_part_read(debug_index_reset_summary_info, + reset_header); + if (!valid) + return -EPROBE_DEFER; + + if (reset_header->magic != DEBUG_PARTITION_MAGIC) { + dev_warn(drvdata->bd.dev, "debug parition is not valid.\n"); + return -ENODEV; + } + + return 0; +} + +static int __user_reset_init_sec_class(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct device *sec_debug_dev; + + sec_debug_dev = sec_device_create(NULL, "sec_debug"); + if (IS_ERR(sec_debug_dev)) { + dev_err(bd->dev, "failed to create device for sec_debug\n"); + return PTR_ERR(sec_debug_dev); + } + + drvdata->sec_debug_dev = sec_debug_dev; + + return 0; +} + +static void __user_reset_exit_sec_class(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct device *sec_debug_dev = drvdata->sec_debug_dev; + + sec_device_destroy(sec_debug_dev->devt); +} + +static noinline int __user_reset_probe_epilog(struct builder *bd) +{ + struct qc_user_reset_drvdata *drvdata = + container_of(bd, struct qc_user_reset_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + qc_user_reset = drvdata; + + return 0; +} + +static noinline void __user_reset_remove_prolog(struct builder *bd) +{ + /* FIXME: This is not a graceful exit. */ + qc_user_reset = NULL; +} + +static const struct dev_builder __user_reset_dev_builder[] = { + DEVICE_BUILDER(__user_reset_test_dbg_partition, NULL), + /* TODO: deferrable concrete builders should be before here. */ + DEVICE_BUILDER(__qc_ap_health_init, __qc_ap_health_exit), + DEVICE_BUILDER(__qc_modem_reset_data_init, NULL), + DEVICE_BUILDER(__qc_kryo_arm64_edac_init, + __qc_kryo_arm64_edac_exit), + DEVICE_BUILDER(__user_reset_init_sec_class, + __user_reset_exit_sec_class), + DEVICE_BUILDER(__qc_reset_rwc_init, __qc_reset_rwc_exit), + DEVICE_BUILDER(__qc_reset_reason_init, __qc_reset_reason_exit), + DEVICE_BUILDER(__qc_reset_summary_init, __qc_reset_summary_exit), + DEVICE_BUILDER(__qc_reset_klog_init, __qc_reset_klog_exit), + DEVICE_BUILDER(__qc_reset_tzlog_init, __qc_reset_tzlog_exit), + DEVICE_BUILDER(__qc_auto_comment_init, __qc_auto_comment_exit), + DEVICE_BUILDER(__qc_reset_history_init, __qc_reset_history_exit), + DEVICE_BUILDER(__qc_fmm_lock_init, __qc_fmm_lock_exit), + DEVICE_BUILDER(__qc_recovery_cause_init, __qc_recovery_cause_exit), + DEVICE_BUILDER(__qc_store_lastkmsg_init, __qc_store_lastkmsg_exit), + DEVICE_BUILDER(__user_reset_probe_epilog, __user_reset_remove_prolog), +}; + +static int __user_reset_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct qc_user_reset_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __user_reset_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct qc_user_reset_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static int sec_qc_user_reset_probe(struct platform_device *pdev) +{ + return __user_reset_probe(pdev, __user_reset_dev_builder, + ARRAY_SIZE(__user_reset_dev_builder)); +} + +static int sec_qc_user_reset_remove(struct platform_device *pdev) +{ + return __user_reset_remove(pdev, __user_reset_dev_builder, + ARRAY_SIZE(__user_reset_dev_builder)); +} + +static const struct of_device_id sec_qc_user_reset_match_table[] = { + { .compatible = "samsung,qcom-user_reset" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_qc_user_reset_match_table); + +static struct platform_driver sec_qc_user_reset_driver = { + .driver = { + .name = "sec,qc-user_reset", + .of_match_table = of_match_ptr(sec_qc_user_reset_match_table), + }, + .probe = sec_qc_user_reset_probe, + .remove = sec_qc_user_reset_remove, +}; + +static int __init sec_qc_user_reset_init(void) +{ + return platform_driver_register(&sec_qc_user_reset_driver); +} +module_init(sec_qc_user_reset_init); + +static void __exit sec_qc_user_reset_exit(void) +{ + platform_driver_unregister(&sec_qc_user_reset_driver); +} +module_exit(sec_qc_user_reset_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("User Reset Debug for Qualcomm based devices"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/debug/qcom/wdt_core/Kconfig b/drivers/samsung/debug/qcom/wdt_core/Kconfig new file mode 100644 index 000000000000..a76fe66b6a4d --- /dev/null +++ b/drivers/samsung/debug/qcom/wdt_core/Kconfig @@ -0,0 +1,5 @@ +config SEC_QC_QCOM_WDT_CORE + tristate "SEC Additional feature for QTI Watchdog (QCOM_WDT_CORE)" + depends on QCOM_WDT_CORE + help + TODO: help is not ready. diff --git a/drivers/samsung/debug/qcom/wdt_core/Makefile b/drivers/samsung/debug/qcom/wdt_core/Makefile new file mode 100644 index 000000000000..479d875343f9 --- /dev/null +++ b/drivers/samsung/debug/qcom/wdt_core/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SEC_QC_QCOM_WDT_CORE) += sec_qc_qcom_wdt_core.o diff --git a/drivers/samsung/debug/qcom/wdt_core/sec_qc_qcom_wdt_core.c b/drivers/samsung/debug/qcom/wdt_core/sec_qc_qcom_wdt_core.c new file mode 100644 index 000000000000..c96be1c1b5a3 --- /dev/null +++ b/drivers/samsung/debug/qcom/wdt_core/sec_qc_qcom_wdt_core.c @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2020-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "sec_qc_qcom_wdt_core.h" + +static noinline int __qc_wdt_core_parse_dt_qcom_wdt_core_dev_name(struct builder *bd, + struct device_node *np) +{ + struct qc_wdt_core_drvdata *drvdata = + container_of(bd, struct qc_wdt_core_drvdata, bd); + const char *qcom_wdt_core_dev_name; + struct device *found; + int err; + + err = of_property_read_string(np, "sec,qcom_wdt_core_dev_name", + &qcom_wdt_core_dev_name); + if (err) + return err; + + found = bus_find_device_by_name(&platform_bus_type, NULL, + qcom_wdt_core_dev_name); + if (!found) + return -EPROBE_DEFER; + + drvdata->wdog_dd = dev_get_drvdata(found); + + return 0; +} + +static noinline int __qc_wdt_core_parse_dt_panic_notifier_priority(struct builder *bd, + struct device_node *np) +{ + struct qc_wdt_core_drvdata *drvdata = + container_of(bd, struct qc_wdt_core_drvdata, bd); + s32 priority; + int err; + + err = of_property_read_s32(np, "sec,panic_notifier-priority", + &priority); + if (err) + return -EINVAL; + + drvdata->nb_panic.priority = (int)priority; + + return 0; +} + +static const struct dt_builder __qc_wdt_core_dt_builder[] = { + DT_BUILDER(__qc_wdt_core_parse_dt_qcom_wdt_core_dev_name), + DT_BUILDER(__qc_wdt_core_parse_dt_panic_notifier_priority), +}; + +static noinline int __qc_wdt_core_parse_dt(struct builder *bd) +{ + return sec_director_parse_dt(bd, __qc_wdt_core_dt_builder, + ARRAY_SIZE(__qc_wdt_core_dt_builder)); +} + +static unsigned long long last_emerg_pet __used; + +static void __qc_wdt_core_emerg_pet_watchdog(struct qc_wdt_core_drvdata *drvdata) +{ + struct msm_watchdog_data *wdog_dd = drvdata->wdog_dd; + + if (!wdog_dd->enabled) + return; + + wdog_dd->ops->enable_wdt(1, wdog_dd); + wdog_dd->ops->reset_wdt(wdog_dd); + + last_emerg_pet = sched_clock(); +} + +static int sec_qc_wdt_core_panic_notifier_call(struct notifier_block *this, + unsigned long l, void *d) +{ + struct qc_wdt_core_drvdata *drvdata = + container_of(this, struct qc_wdt_core_drvdata, nb_panic); + + __qc_wdt_core_emerg_pet_watchdog(drvdata); + + return NOTIFY_OK; +} + +static int __qc_wdt_core_register_panic_handler(struct builder *bd) +{ + struct qc_wdt_core_drvdata *drvdata = + container_of(bd, struct qc_wdt_core_drvdata, bd); + + drvdata->nb_panic.notifier_call = sec_qc_wdt_core_panic_notifier_call; + + return atomic_notifier_chain_register(&panic_notifier_list, + &drvdata->nb_panic); +} + +static void __qc_wdt_core_unregister_panic_handler(struct builder *bd) +{ + struct qc_wdt_core_drvdata *drvdata = + container_of(bd, struct qc_wdt_core_drvdata, bd); + + atomic_notifier_chain_unregister(&panic_notifier_list, + &drvdata->nb_panic); +} + +static int __qc_wdt_core_bark_notifier_call(struct notifier_block *this, + unsigned long l, void *d) +{ + struct qc_wdt_core_drvdata *drvdata = container_of(this, + struct qc_wdt_core_drvdata, nb_wdt_bark); + struct msm_watchdog_data *wdog_dd = drvdata->wdog_dd; + + sec_qc_rbcmd_set_restart_reason(PON_RESTART_REASON_NOT_HANDLE, + RESTART_REASON_SEC_DEBUG_MODE, NULL); + sec_qc_upldc_write_cause(UPLOAD_CAUSE_NON_SECURE_WDOG_BARK); + __qc_wdt_core_emerg_pet_watchdog(drvdata); + sched_show_task(wdog_dd->watchdog_task); + if (IS_BUILTIN(CONFIG_SEC_QC_QCOM_WDT_CORE)) + smp_send_stop(); + + return NOTIFY_OK; +} + +static int sec_qc_wdt_core_bark_notifier_call(struct notifier_block *this, + unsigned long l, void *d) +{ + static atomic_t cnt = ATOMIC_INIT(1); + + /* NOTE: to ensure one-shot */ + if (atomic_dec_if_positive(&cnt) < 0) + return NOTIFY_DONE; + + return __qc_wdt_core_bark_notifier_call(this, l, d); +} + +static int __qc_wdt_core_register_bark_handler(struct builder *bd) +{ + struct qc_wdt_core_drvdata *drvdata = + container_of(bd, struct qc_wdt_core_drvdata, bd); + + drvdata->nb_wdt_bark.notifier_call = sec_qc_wdt_core_bark_notifier_call; + + return qcom_wdt_bark_register_notifier(&drvdata->nb_wdt_bark); +} + +static void __qc_wdt_core_unregister_bark_handler(struct builder *bd) +{ + struct qc_wdt_core_drvdata *drvdata = + container_of(bd, struct qc_wdt_core_drvdata, bd); + + qcom_wdt_bark_unregister_notifier(&drvdata->nb_wdt_bark); +} + +static void __qc_wdt_force_watchdog_bark(struct force_err_handle *h) +{ + struct qc_wdt_core_drvdata *drvdata = + container_of(h, struct qc_wdt_core_drvdata, force_err_dp); + struct msm_watchdog_data *wdog_dd = drvdata->wdog_dd; + struct device *dev = drvdata->bd.dev; + + qcom_lpm_set_sleep_disabled(); + + wdog_dd->bark_time = 3000; + wdog_dd->ops->set_bark_time(wdog_dd->bark_time, wdog_dd); + + dev_err(dev, "force set bark time [%u]\n", wdog_dd->bark_time); +} + +static int __qc_wdt_core_add_force_err_dp(struct builder *bd) +{ + struct qc_wdt_core_drvdata *drvdata = + container_of(bd, struct qc_wdt_core_drvdata, bd); + struct force_err_handle *force_err = &drvdata->force_err_dp; + int err; + + force_err->val = "DP"; + force_err->func = __qc_wdt_force_watchdog_bark; + + err = sec_force_err_add_custom_handle(force_err); + if (err < 0) + dev_warn(bd->dev, "DP - force err is disabled. ignored.\n"); + + return 0; +} + +static void __qc_wdt_core_del_force_err_dp(struct builder *bd) +{ + struct qc_wdt_core_drvdata *drvdata = + container_of(bd, struct qc_wdt_core_drvdata, bd); + struct force_err_handle *force_err = &drvdata->force_err_dp; + + sec_force_err_del_custom_handle(force_err); +} + +static void __qc_wdt_force_watchdog_bite(struct force_err_handle *h) +{ + qcom_wdt_trigger_bite(); +} + +static int __qc_wdt_core_add_force_err_wp(struct builder *bd) +{ + struct qc_wdt_core_drvdata *drvdata = + container_of(bd, struct qc_wdt_core_drvdata, bd); + struct force_err_handle *force_err = &drvdata->force_err_wp; + int err; + + force_err->val = "WP"; + force_err->func = __qc_wdt_force_watchdog_bite; + + err = sec_force_err_add_custom_handle(force_err); + if (err < 0) + dev_warn(bd->dev, "WP - force err is disabled. ignored.\n"); + + return 0; +} + +static void __qc_wdt_core_del_force_err_wp(struct builder *bd) +{ + struct qc_wdt_core_drvdata *drvdata = + container_of(bd, struct qc_wdt_core_drvdata, bd); + struct force_err_handle *force_err = &drvdata->force_err_wp; + + sec_force_err_del_custom_handle(force_err); +} + +static int __qc_wdt_core_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct qc_wdt_core_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __qc_wdt_core_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct qc_wdt_core_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static const struct dev_builder __qc_wdt_core_dev_builder[] = { + DEVICE_BUILDER(__qc_wdt_core_parse_dt, NULL), + DEVICE_BUILDER(__qc_wdt_core_register_panic_handler, + __qc_wdt_core_unregister_panic_handler), + DEVICE_BUILDER(__qc_wdt_core_register_bark_handler, + __qc_wdt_core_unregister_bark_handler), + DEVICE_BUILDER(__qc_wdt_core_add_force_err_dp, + __qc_wdt_core_del_force_err_dp), + DEVICE_BUILDER(__qc_wdt_core_add_force_err_wp, + __qc_wdt_core_del_force_err_wp), +}; + +static int sec_qc_wdt_core_probe(struct platform_device *pdev) +{ + return __qc_wdt_core_probe(pdev, __qc_wdt_core_dev_builder, + ARRAY_SIZE(__qc_wdt_core_dev_builder)); +} + +static int sec_qc_wdt_core_remove(struct platform_device *pdev) +{ + return __qc_wdt_core_remove(pdev, __qc_wdt_core_dev_builder, + ARRAY_SIZE(__qc_wdt_core_dev_builder)); +} + +static const struct of_device_id sec_qc_wdt_core_match_table[] = { + { .compatible = "samsung,qcom-wdt_core" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_qc_wdt_core_match_table); + +static struct platform_driver sec_qc_wdt_core_driver = { + .driver = { + .name = "sec,qc-wdt_core", + .of_match_table = of_match_ptr(sec_qc_wdt_core_match_table), + }, + .probe = sec_qc_wdt_core_probe, + .remove = sec_qc_wdt_core_remove, +}; + +static __init int sec_qc_wdt_core_init(void) +{ + return platform_driver_register(&sec_qc_wdt_core_driver); +} +module_init(sec_qc_wdt_core_init); + +static __exit void sec_qc_wdt_core_exit(void) +{ + platform_driver_unregister(&sec_qc_wdt_core_driver); +} +module_exit(sec_qc_wdt_core_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("Additional feature for QTI Watchdogr"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/debug/qcom/wdt_core/sec_qc_qcom_wdt_core.h b/drivers/samsung/debug/qcom/wdt_core/sec_qc_qcom_wdt_core.h new file mode 100644 index 000000000000..9977faabc785 --- /dev/null +++ b/drivers/samsung/debug/qcom/wdt_core/sec_qc_qcom_wdt_core.h @@ -0,0 +1,31 @@ +#ifndef __INTERNAL__SEC_QC_QCOM_WDT_CORE_H__ +#define __INTERNAL__SEC_QC_QCOM_WDT_CORE_H__ + +struct msm_watchdog_data; + +struct qc_wdt_core_drvdata { + struct builder bd; + struct msm_watchdog_data* wdog_dd; + struct notifier_block nb_panic; + struct notifier_block nb_wdt_bark; + struct force_err_handle force_err_dp; + struct force_err_handle force_err_wp; +}; + +/* implemented @ drivers/soc/qcom/smem.c */ +#if IS_ENABLED(CONFIG_QCOM_WDT_CORE) +#include + +extern int qcom_wdt_pet_register_notifier(struct notifier_block *nb); +extern int qcom_wdt_pet_unregister_notifier(struct notifier_block *nb); +extern int qcom_wdt_bark_register_notifier(struct notifier_block *nb); +extern int qcom_wdt_bark_unregister_notifier(struct notifier_block *nb); + +extern void qcom_lpm_set_sleep_disabled(void); +extern void qcom_lpm_unset_sleep_disabled(void); +#else +#include +#include +#endif + +#endif /* __INTERNAL__SEC_QC_QCOM_WDT_CORE_H__ */ diff --git a/drivers/samsung/debug/rdx_bootdev/Kconfig b/drivers/samsung/debug/rdx_bootdev/Kconfig new file mode 100644 index 000000000000..cb2335acb2e2 --- /dev/null +++ b/drivers/samsung/debug/rdx_bootdev/Kconfig @@ -0,0 +1,4 @@ +config SEC_RDX_BOOTDEV + tristate "SEC RDX Boot-Dev driver for Samsung Android devices" + help + TODO: help is not ready. diff --git a/drivers/samsung/debug/rdx_bootdev/Makefile b/drivers/samsung/debug/rdx_bootdev/Makefile new file mode 100644 index 000000000000..15e71f0b4dd7 --- /dev/null +++ b/drivers/samsung/debug/rdx_bootdev/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SEC_RDX_BOOTDEV) += sec_rdx_bootdev.o diff --git a/drivers/samsung/debug/rdx_bootdev/sec_rdx_bootdev.c b/drivers/samsung/debug/rdx_bootdev/sec_rdx_bootdev.c new file mode 100644 index 000000000000..d581a995ed2b --- /dev/null +++ b/drivers/samsung/debug/rdx_bootdev/sec_rdx_bootdev.c @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2016-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +struct rdx_bootdev_drvdata { + struct builder bd; + struct resource res; + struct mutex lock; + phys_addr_t paddr; + phys_addr_t size; + struct proc_dir_entry *proc; +}; + +static noinline int __rdx_bootdev_parse_dt_memory_region(struct builder *bd, + struct device_node *np) +{ + struct rdx_bootdev_drvdata *drvdata = + container_of(bd, struct rdx_bootdev_drvdata, bd); + struct device_node *mem_np; + int err; + + mem_np = of_parse_phandle(np, "memory-region", 0); + if (!mem_np) + return -EINVAL; + + err = of_address_to_resource(mem_np, 0, &drvdata->res); + if (err) + return err; + + drvdata->paddr = (phys_addr_t)drvdata->res.start; + drvdata->size = (phys_addr_t)resource_size(&drvdata->res); + + return 0; +} + +static const struct dt_builder __rdx_bootdev_dt_builder[] = { + DT_BUILDER(__rdx_bootdev_parse_dt_memory_region), +}; + +static noinline int __rdx_bootdev_parse_dt(struct builder *bd) +{ + return sec_director_parse_dt(bd, __rdx_bootdev_dt_builder, + ARRAY_SIZE(__rdx_bootdev_dt_builder)); +} + +#if IS_BUILTIN(CONFIG_SEC_RDX_BOOTDEV) +static __always_inline unsigned long __free_reserved_area(void *start, void *end, int poison, const char *s) +{ + return free_reserved_area(start, end, poison, s); +} +#else +/* FIXME: this is a copy of 'free_reserved_area' of 'page_alloc.c' */ +static unsigned long __free_reserved_area(void *start, void *end, int poison, const char *s) +{ + void *pos; + unsigned long pages = 0; + + start = (void *)PAGE_ALIGN((unsigned long)start); + end = (void *)((unsigned long)end & PAGE_MASK); + for (pos = start; pos < end; pos += PAGE_SIZE, pages++) { + struct page *page = virt_to_page(pos); + void *direct_map_addr; + + direct_map_addr = page_address(page); + + direct_map_addr = kasan_reset_tag(direct_map_addr); + if ((unsigned int)poison <= 0xFF) + memset(direct_map_addr, poison, PAGE_SIZE); + + free_reserved_page(page); + } + + if (pages && s) + pr_info("Freeing %s memory: %ldK\n", + s, pages << (PAGE_SHIFT - 10)); + + return pages; +} +#endif + +static inline void ____rdx_bootdev_free( + struct rdx_bootdev_drvdata *drvdata, + phys_addr_t paddr, phys_addr_t size) +{ + uint8_t *vaddr = (uint8_t *)phys_to_virt(paddr); + + memset(vaddr, 0x00, size); + + memblock_free(vaddr, size); + __free_reserved_area(vaddr, vaddr + size, -1, "rdx_bootdev"); + + if (drvdata->paddr == paddr) + drvdata->paddr = 0; + + drvdata->size -= size; +} + +static void __rdx_bootdev_free(struct rdx_bootdev_drvdata *drvdata, + phys_addr_t paddr, phys_addr_t size) +{ + struct device *dev = drvdata->bd.dev; + + if (!drvdata->paddr) { + dev_warn(dev, "reserved address is NULL\n"); + return; + } + + if (!drvdata->size) { + dev_warn(dev, "reserved size is zero\n"); + return; + } + + if (paddr < drvdata->paddr) { + dev_warn(dev, "paddr is not a valid reserved address\n"); + return; + } + + if ((paddr + size) > (drvdata->paddr + drvdata->size)) { + dev_warn(dev, "invalid reserved memory size\n"); + return; + } + + ____rdx_bootdev_free(drvdata, paddr, size); +} + +static ssize_t __rdx_bootdev_free_entire(struct rdx_bootdev_drvdata *drvdata) +{ + __rdx_bootdev_free(drvdata, drvdata->paddr, drvdata->size); + + return -ENODEV; +} + +#define SHA256_DIGEST_LENGTH (256 / 8) + +static ssize_t __rdx_bootdev_free_partial(struct rdx_bootdev_drvdata *drvdata, + const char __user *buf, size_t count) +{ + struct device *dev = drvdata->bd.dev; + struct { + uint8_t sha256[SHA256_DIGEST_LENGTH]; + struct fiemap fiemap; + } __packed *shared = phys_to_virt(drvdata->paddr); + struct fiemap *pfiemap; + phys_addr_t pa_to_be_freed; + phys_addr_t sz_to_be_freed; + phys_addr_t max_mapped_extents; + + if (copy_from_user(shared, buf, count)) { + dev_warn(dev, "copy_from_user failed\n"); + return -EFAULT; + } + + pfiemap = &shared->fiemap; + + max_mapped_extents = (drvdata->size - sizeof(*shared)) + / sizeof(struct fiemap_extent); + if (pfiemap->fm_mapped_extents > max_mapped_extents) { + dev_warn(dev, "out of bound\n"); + return -ERANGE; + } + + pa_to_be_freed = virt_to_phys(&pfiemap->fm_extents[0]); + pa_to_be_freed += (pfiemap->fm_mapped_extents) * sizeof(struct fiemap_extent); + pa_to_be_freed = ALIGN(pa_to_be_freed, PAGE_SIZE); + + sz_to_be_freed = drvdata->size - + (pa_to_be_freed - drvdata->paddr); + + __rdx_bootdev_free(drvdata, pa_to_be_freed, sz_to_be_freed); + + return count; +} + +static ssize_t __rdx_bootdev_drvdata_write( + struct rdx_bootdev_drvdata *drvdata, + const char __user *buf, size_t count) +{ + /* NOTE: according to the RDX protocol, free the etire reserved memroy */ + if (!buf && !count) + return __rdx_bootdev_free_entire(drvdata); + else + return __rdx_bootdev_free_partial(drvdata, buf, count); +} + +static ssize_t sec_rdx_bootdev_proc_write(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct rdx_bootdev_drvdata *drvdata = pde_data(file_inode(file)); + struct device *dev = drvdata->bd.dev; + ssize_t ret = -ERANGE; + + mutex_lock(&drvdata->lock); + + if (!drvdata->paddr) { + dev_warn(dev, "paddr is NULL\n"); + ret = -EFAULT; + goto nothing_to_do; + } + + if (count > drvdata->size) { + dev_warn(dev, "size is wrong (%zu > %llu)\n", count, + (unsigned long long)drvdata->size); + ret = -EINVAL; + goto nothing_to_do; + } + + ret = __rdx_bootdev_drvdata_write(drvdata, buf, count); + +nothing_to_do: + mutex_unlock(&drvdata->lock); + return ret; +} + +static const struct proc_ops rdx_bootdev_pops = { + .proc_write = sec_rdx_bootdev_proc_write, +}; + +static noinline int __rdx_bootdev_test_sec_debug(struct builder *bd) +{ + struct rdx_bootdev_drvdata *drvdata = + container_of(bd, struct rdx_bootdev_drvdata, bd); + struct device *dev = bd->dev; + + if (!sec_debug_is_enabled()) { + dev_info(dev, "sec_debug is not enabled\n"); + __rdx_bootdev_free_entire(drvdata); + /* NOTE: keepgoing. create dummy proc nodes */ + } + + return 0; +} + +static int __rdx_bootdev_proc_init(struct builder *bd) +{ + struct rdx_bootdev_drvdata *drvdata = + container_of(bd, struct rdx_bootdev_drvdata, bd); + struct device *dev = bd->dev; + const char *node_name = "rdx_bootdev"; + + drvdata->proc = proc_create_data(node_name, 0220, NULL, + &rdx_bootdev_pops, drvdata); + if (!drvdata->proc) { + dev_err(dev, "failed create procfs node (%s)\n", + node_name); + return -ENODEV; + } + + return 0; +} + +static void __rdx_bootdev_proc_exit(struct builder *bd) +{ + struct rdx_bootdev_drvdata *drvdata = + container_of(bd, struct rdx_bootdev_drvdata, bd); + + proc_remove(drvdata->proc); +} + +static noinline int __rdx_boodev_probe_epilog(struct builder *bd) +{ + struct rdx_bootdev_drvdata *drvdata = + container_of(bd, struct rdx_bootdev_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + + return 0; +} + +static int __rdx_bootdev_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct rdx_bootdev_drvdata *drvdata; + int err; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + err = sec_director_probe_dev(&drvdata->bd, builder, n); + if (err) + return err; + + return 0; +} + +static int __rdx_bootdev_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct rdx_bootdev_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static const struct dev_builder __rdx_bootdev_dev_builder[] = { + DEVICE_BUILDER(__rdx_bootdev_parse_dt, NULL), + DEVICE_BUILDER(__rdx_bootdev_test_sec_debug, NULL), + DEVICE_BUILDER(__rdx_bootdev_proc_init, + __rdx_bootdev_proc_exit), + DEVICE_BUILDER(__rdx_boodev_probe_epilog, NULL), +}; + +static int sec_rdx_bootdev_probe(struct platform_device *pdev) +{ + return __rdx_bootdev_probe(pdev, __rdx_bootdev_dev_builder, + ARRAY_SIZE(__rdx_bootdev_dev_builder)); +} + +static int sec_rdx_bootdev_remove(struct platform_device *pdev) +{ + return __rdx_bootdev_remove(pdev, __rdx_bootdev_dev_builder, + ARRAY_SIZE(__rdx_bootdev_dev_builder)); +} + +static const struct of_device_id sec_rdx_bootdev_match_table[] = { + { .compatible = "samsung,rdx_bootdev" }, + { .compatible = "samsung,qcom-rdx_bootdev" }, /* TODO: should be removed in future. */ + {}, +}; +MODULE_DEVICE_TABLE(of, sec_rdx_bootdev_match_table); + +static struct platform_driver sec_rdx_bootdev_driver = { + .driver = { + .name = "sec,rdx_bootdev", + .of_match_table = of_match_ptr(sec_rdx_bootdev_match_table), + }, + .probe = sec_rdx_bootdev_probe, + .remove = sec_rdx_bootdev_remove, +}; + +static __init int sec_rdx_bootdev_init(void) +{ + return platform_driver_register(&sec_rdx_bootdev_driver); +} +module_init(sec_rdx_bootdev_init); + +static __exit void sec_rdx_bootdev_exit(void) +{ + platform_driver_unregister(&sec_rdx_bootdev_driver); +} +module_exit(sec_rdx_bootdev_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("RDX Boot-Dev driver for Samsung Android devices"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/debug/reboot_cmd/Kconfig b/drivers/samsung/debug/reboot_cmd/Kconfig new file mode 100644 index 000000000000..1f529bba22bc --- /dev/null +++ b/drivers/samsung/debug/reboot_cmd/Kconfig @@ -0,0 +1,21 @@ +config SEC_REBOOT_CMD + tristate "SEC Reboot Command Iterator" + help + TODO: help is not ready. + +config SEC_REBOOT_CMD_TEST_FOR_ON_DEVICE + tristate "KUnit test for sec_reboot_cmd_test" + depends on KUNIT + depends on SEC_REBOOT_CMD + help + TODO: Describe config fully. + If you run this test driver on device, SHOULD set this config as 'm' to build test driver modulraly. + +config SEC_REBOOT_CMD_TEST_FOR_ONLY_UML + tristate "KUnit test for sec_reboot_cmd_test" + depends on KUNIT + depends on UML + depends on SEC_REBOOT_CMD + help + TODO: Describe config fully. + This CONFIG is recommended to set to y. diff --git a/drivers/samsung/debug/reboot_cmd/Makefile b/drivers/samsung/debug/reboot_cmd/Makefile new file mode 100644 index 000000000000..7a416570dfbb --- /dev/null +++ b/drivers/samsung/debug/reboot_cmd/Makefile @@ -0,0 +1,4 @@ + +obj-$(CONFIG_SEC_REBOOT_CMD) += sec_reboot_cmd.o + +GCOV_PROFILE_sec_reboot_cmd.o := $(CONFIG_KUNIT) diff --git a/drivers/samsung/debug/reboot_cmd/sec_reboot_cmd.c b/drivers/samsung/debug/reboot_cmd/sec_reboot_cmd.c new file mode 100644 index 000000000000..17c40194d05b --- /dev/null +++ b/drivers/samsung/debug/reboot_cmd/sec_reboot_cmd.c @@ -0,0 +1,632 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2020-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "sec_reboot_cmd.h" + +static struct reboot_cmd_drvdata *reboot_cmd; + +static const char * const rbcmd_stage_name[] = { + [SEC_RBCMD_STAGE_REBOOT_NOTIFIER] = "Reboot Notifier", + [SEC_RBCMD_STAGE_RESTART_HANDLER] = "Restart Handler" +}; + +static __always_inline bool __rbcmd_is_probed(void) +{ + return !!reboot_cmd; +} + +__ss_static struct reboot_cmd_stage *__rbcmd_get_stage(struct reboot_cmd_drvdata *drvdata, + enum sec_rbcmd_stage s) +{ + BUG_ON((unsigned long)s >= (unsigned long)SEC_RBCMD_STAGE_MAX); + + return &drvdata->stage[s]; +} + +__ss_static __ss_inline int __rbcmd_add_cmd(struct reboot_cmd_drvdata *drvdata, + enum sec_rbcmd_stage s, struct sec_reboot_cmd *rc) +{ + struct reboot_cmd_stage *stage; + + stage = __rbcmd_get_stage(drvdata, s); + + mutex_lock(&stage->lock); + list_add(&rc->list, &stage->list); + mutex_unlock(&stage->lock); + + return 0; +} + +int sec_rbcmd_add_cmd(enum sec_rbcmd_stage s, struct sec_reboot_cmd *rc) +{ + if (!__rbcmd_is_probed()) + return -EBUSY; + + return __rbcmd_add_cmd(reboot_cmd, s, rc); +} +EXPORT_SYMBOL_GPL(sec_rbcmd_add_cmd); + +__ss_static __ss_inline int __rbcmd_del_cmd(struct reboot_cmd_drvdata *drvdata, + enum sec_rbcmd_stage s, struct sec_reboot_cmd *rc) +{ + struct reboot_cmd_stage *stage; + + stage = __rbcmd_get_stage(drvdata, s); + + mutex_lock(&stage->lock); + list_del(&rc->list); + mutex_unlock(&stage->lock); + + return 0; +} + +int sec_rbcmd_del_cmd(enum sec_rbcmd_stage s, struct sec_reboot_cmd *rc) +{ + if (!__rbcmd_is_probed()) + return -EBUSY; + + return __rbcmd_del_cmd(reboot_cmd, s, rc); +} +EXPORT_SYMBOL_GPL(sec_rbcmd_del_cmd); + +__ss_static __ss_inline int __rbcmd_set_default_cmd( + struct reboot_cmd_drvdata *drvdata, + enum sec_rbcmd_stage s, struct sec_reboot_cmd *rc) +{ + struct reboot_cmd_stage *stage; + int err = 0; + + stage = __rbcmd_get_stage(drvdata, s); + + mutex_lock(&stage->lock); + if (stage->dflt) { + pr_warn("default reboot cmd for %s is already registered. ignored!\n", + rbcmd_stage_name[s]); + pr_warn("Caller is %pS\n", __builtin_return_address(0)); + err = -EINVAL; + } else { + stage->dflt = rc; + } + mutex_unlock(&stage->lock); + + return err; +} + +int sec_rbcmd_set_default_cmd(enum sec_rbcmd_stage s, + struct sec_reboot_cmd *rc) +{ + if (!__rbcmd_is_probed()) + return -EBUSY; + + return __rbcmd_set_default_cmd(reboot_cmd, s, rc); +} +EXPORT_SYMBOL_GPL(sec_rbcmd_set_default_cmd); + +__ss_static __ss_inline int __rbcmd_unset_default_cmd( + struct reboot_cmd_drvdata *drvdata, + enum sec_rbcmd_stage s, struct sec_reboot_cmd *rc) +{ + struct reboot_cmd_stage *stage; + int err = 0; + + stage = __rbcmd_get_stage(drvdata, s); + + mutex_lock(&stage->lock); + if (!stage->dflt) { + pr_warn("default reason for %s is already unset - ignored.\n", + rbcmd_stage_name[s]); + err = -ENOENT; + } else if (stage->dflt != rc) { + pr_warn("current default reason for %s is different from you requested - ignored.\n", + rbcmd_stage_name[s]); + err = -EINVAL; + } else { + stage->dflt = NULL; + } + mutex_unlock(&stage->lock); + + return err; +} + +int sec_rbcmd_unset_default_cmd(enum sec_rbcmd_stage s, + struct sec_reboot_cmd *rc) +{ + if (!__rbcmd_is_probed()) + return -EBUSY; + + return __rbcmd_unset_default_cmd(reboot_cmd, s, rc); +} +EXPORT_SYMBOL_GPL(sec_rbcmd_unset_default_cmd); + +static int __rbcmd_handle_each_cmd(const struct sec_reboot_cmd *rc, + struct sec_reboot_param *param, bool one_of_multi) +{ + size_t len = strlen(rc->cmd); + + if (likely(strncmp(param->cmd, rc->cmd, len))) + return SEC_RBCMD_HANDLE_DONE; + + return rc->func(rc, param, one_of_multi); +} + +static int __rbcmd_handle_single_cmd(struct reboot_cmd_stage *stage, + struct sec_reboot_param *single, bool one_of_multi) +{ + struct sec_reboot_cmd *rc; + int handled = SEC_RBCMD_HANDLE_DONE; + + /* NOTE: mutex_lock/_unlock may not be needed when this + * function works. + */ + list_for_each_entry(rc, &stage->list, list) { + handled = __rbcmd_handle_each_cmd(rc, single, + one_of_multi); + if (handled & SEC_RBCMD_HANDLE_STOP_MASK) + break; + } + + return handled; +} + +static int __rbcmd_handle_multi_cmd(struct reboot_cmd_stage *stage, + struct sec_reboot_param *multi, const char *delim) +{ + const char *multi_cmd = multi->cmd; + static char tmp_multi_cmd[256]; /* reserve .bss for strsep */ + char *stringp; + struct sec_reboot_param __single; + struct sec_reboot_param *single = &__single; + int handled; + int ret = SEC_RBCMD_HANDLE_DONE; + + strlcpy(tmp_multi_cmd, multi_cmd, ARRAY_SIZE(tmp_multi_cmd)); + + single->mode = multi->mode; + stringp = tmp_multi_cmd; + while (true) { + single->cmd = strsep(&stringp, delim); + if (!single->cmd) + break; + + handled = __rbcmd_handle_single_cmd(stage, single, true); + if (handled == SEC_RBCMD_HANDLE_BAD) + pr_warn("bad - %s", multi_cmd); + + ret |= handled; + + if (handled & SEC_RBCMD_HANDLE_ONESHOT_MASK) + break; + } + + /* clear oneshot bti before return + * because caller does not handle this + */ + ret &= ~(SEC_RBCMD_HANDLE_ONESHOT_MASK); + + return ret; +} + +static bool __rbcmd_is_mulit_cmd(const char *cmd, const char *delim) +{ + char *pos; + + pos = strnstr(cmd, delim, strlen(cmd)); + + return pos ? true : false; +} + +__ss_static int __rbcmd_handle(struct reboot_cmd_stage *stage, + struct sec_reboot_param *param) +{ + const char *cmd = param->cmd; + const char *delim = ":"; + int handled = SEC_RBCMD_HANDLE_DONE; + + if (!cmd || !strlen(cmd) || !strncmp(cmd, "adb", 3)) + goto call_default_hanlder; + + if (__rbcmd_is_mulit_cmd(cmd, delim)) + handled = __rbcmd_handle_multi_cmd(stage, param, delim); + else + handled = __rbcmd_handle_single_cmd(stage, param, false); + + if ((handled & SEC_RBCMD_HANDLE_OK) == SEC_RBCMD_HANDLE_OK) + return handled; + +call_default_hanlder: + if (stage->dflt) { + pr_info("call default handler\n"); + handled = stage->dflt->func(stage->dflt, param, false); + } + + return handled; +} + +static int sec_rbcmd_notifier_call(struct notifier_block *this, + unsigned long mode, void *cmd) +{ + struct reboot_cmd_stage *stage = + container_of(this, struct reboot_cmd_stage, nb); + struct sec_reboot_param __param = { + .cmd = cmd, + .mode = mode, + }; + struct sec_reboot_param *param = &__param; + int err; + int ret = NOTIFY_DONE; + + pr_info("cmd : %s\n", param->cmd); + + err = __rbcmd_handle(stage, param); + switch (err) { + case SEC_RBCMD_HANDLE_DONE: + pr_warn("any reboot reason did not handle this command - %s", + param->cmd); + break; + case SEC_RBCMD_HANDLE_OK: + ret = NOTIFY_OK; + break; + default: + if (err & SEC_RBCMD_HANDLE_BAD) + pr_err("error occured during iteration\n"); + else + pr_err("unknown return value - %d\n", err); + break; + } + + flush_cache_all(); +#if IS_ENABLED(CONFIG_ARM) + outer_flush_all(); +#endif + + return ret; +} + +#if IS_ENABLED(CONFIG_DEBUG_FS) +static void __rbcmd_dbgfs_show_each_cmd_locked(struct seq_file *m, + struct sec_reboot_cmd *rc) +{ + seq_printf(m, "[<%p>] %s\n", rc, rc->cmd); + seq_printf(m, " - func : [<%p>] %ps\n", rc->func, rc->func); + + seq_puts(m, "\n"); +} + +static void __rbcmd_dbgfs_show_default_cmd_locked(struct seq_file *m, + struct reboot_cmd_stage *stage) +{ + seq_puts(m, "+ Default :\n"); + + if (stage->dflt) + __rbcmd_dbgfs_show_each_cmd_locked(m, stage->dflt); + else + seq_puts(m, "[WARN] default command is not set\n"); + + seq_puts(m, "\n"); +} + +static void __rbcmd_dbgfs_show_each_stage(struct seq_file *m, + struct reboot_cmd_stage *stage) +{ + struct sec_reboot_cmd *rc; + + seq_printf(m, "+ Priority : %d\n", stage->nb.priority); + + mutex_lock(&stage->lock); + + __rbcmd_dbgfs_show_default_cmd_locked(m, stage); + + list_for_each_entry(rc, &stage->list, list) { + __rbcmd_dbgfs_show_each_cmd_locked(m, rc); + } + + mutex_unlock(&stage->lock); +} + +static int sec_rbcmd_dbgfs_show_all(struct seq_file *m, void *unsed) +{ + struct reboot_cmd_drvdata *drvdata = m->private; + struct reboot_cmd_stage *stage; + enum sec_rbcmd_stage s; + + for (s = SEC_RBCMD_STAGE_1ST; s < SEC_RBCMD_STAGE_MAX; s++) { + seq_printf(m, "* STAGE : %s\n\n", rbcmd_stage_name[s]); + stage = __rbcmd_get_stage(drvdata, s); + __rbcmd_dbgfs_show_each_stage(m, stage); + } + + return 0; +} + +static int sec_rbcmd_dbgfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, sec_rbcmd_dbgfs_show_all, inode->i_private); +} + +static const struct file_operations sec_rbcmd_dgbfs_fops = { + .open = sec_rbcmd_dbgfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static noinline int __rbcmd_debugfs_create(struct builder *bd) +{ + struct reboot_cmd_drvdata *drvdata = + container_of(bd, struct reboot_cmd_drvdata, bd); + + drvdata->dbgfs = debugfs_create_file("sec_reboot_cmd", 0440, + NULL, drvdata, &sec_rbcmd_dgbfs_fops); + + return 0; +} + +static noinline void __rbcmd_debugfs_remove(struct builder *bd) +{ + struct reboot_cmd_drvdata *drvdata = + container_of(bd, struct reboot_cmd_drvdata, bd); + + debugfs_remove(drvdata->dbgfs); +} +#else +static noinline int __rbcmd_debugfs_create(struct builder *bd) { return 0; } +static noinline void __rbcmd_debugfs_remove(struct builder *bd) {} +#endif + +__ss_static noinline int __rbcmd_parse_dt_reboot_notifier_priority(struct builder *bd, + struct device_node *np) +{ + struct reboot_cmd_drvdata *drvdata = + container_of(bd, struct reboot_cmd_drvdata, bd); + struct reboot_cmd_stage *stage; + s32 priority; + int err; + + err = of_property_read_s32(np, "sec,reboot_notifier-priority", + &priority); + if (err) + return -EINVAL; + + stage = __rbcmd_get_stage(drvdata, SEC_RBCMD_STAGE_REBOOT_NOTIFIER); + stage->nb.priority = (int)priority; + + return 0; +} + +__ss_static noinline int __rbcmd_parse_dt_restart_handler_priority(struct builder *bd, + struct device_node *np) +{ + struct reboot_cmd_drvdata *drvdata = + container_of(bd, struct reboot_cmd_drvdata, bd); + struct reboot_cmd_stage *stage; + s32 priority; + int err; + + err = of_property_read_s32(np, "sec,restart_handler-priority", + &priority); + if (err) + return -EINVAL; + + stage = __rbcmd_get_stage(drvdata, SEC_RBCMD_STAGE_RESTART_HANDLER); + stage->nb.priority = (int)priority; + + return 0; +} + +static const struct dt_builder __rbcmd_dt_builder[] = { + DT_BUILDER(__rbcmd_parse_dt_reboot_notifier_priority), + DT_BUILDER(__rbcmd_parse_dt_restart_handler_priority), +}; + +static noinline int __rbcmd_parse_dt(struct builder *bd) +{ + return sec_director_parse_dt(bd, __rbcmd_dt_builder, + ARRAY_SIZE(__rbcmd_dt_builder)); +} + +static void __rbcmd_probe_each_stage(struct reboot_cmd_stage *stage) +{ + mutex_init(&stage->lock); + INIT_LIST_HEAD(&stage->list); +} + +static void __rbcmd_remove_each_stage(struct reboot_cmd_stage *stage) +{ + mutex_destroy(&stage->lock); +} + +__ss_static noinline int __rbcmd_probe_prolog(struct builder *bd) +{ + struct reboot_cmd_drvdata *drvdata = + container_of(bd, struct reboot_cmd_drvdata, bd); + struct reboot_cmd_stage *stage; + enum sec_rbcmd_stage s; + + for (s = SEC_RBCMD_STAGE_1ST; s < SEC_RBCMD_STAGE_MAX; s++) { + stage = __rbcmd_get_stage(drvdata, s); + __rbcmd_probe_each_stage(stage); + } + + return 0; +} + +static noinline void __rbcmd_remove_epilog(struct builder *bd) +{ + struct reboot_cmd_drvdata *drvdata = + container_of(bd, struct reboot_cmd_drvdata, bd); + struct reboot_cmd_stage *stage; + enum sec_rbcmd_stage s; + + for (s = SEC_RBCMD_STAGE_1ST; s < SEC_RBCMD_STAGE_MAX; s++) { + stage = __rbcmd_get_stage(drvdata, s); + __rbcmd_remove_each_stage(stage); + } +} + +static int __rbcmd_register_reboot_notifier(struct builder *bd) +{ + struct reboot_cmd_drvdata *drvdata = + container_of(bd, struct reboot_cmd_drvdata, bd); + struct reboot_cmd_stage *stage; + + stage = __rbcmd_get_stage(drvdata, SEC_RBCMD_STAGE_REBOOT_NOTIFIER); + + stage->nb.notifier_call = sec_rbcmd_notifier_call; + + return register_reboot_notifier(&stage->nb); +} + +static void __rbcmd_unregister_reboot_notifier(struct builder *bd) +{ + struct reboot_cmd_drvdata *drvdata = + container_of(bd, struct reboot_cmd_drvdata, bd); + struct reboot_cmd_stage *stage; + + stage = __rbcmd_get_stage(drvdata, SEC_RBCMD_STAGE_REBOOT_NOTIFIER); + + unregister_reboot_notifier(&stage->nb); +} + +static int __rbcmd_register_restart_handler(struct builder *bd) +{ + struct reboot_cmd_drvdata *drvdata = + container_of(bd, struct reboot_cmd_drvdata, bd); + struct reboot_cmd_stage *stage; + + stage = __rbcmd_get_stage(drvdata, SEC_RBCMD_STAGE_RESTART_HANDLER); + + stage->nb.notifier_call = sec_rbcmd_notifier_call; + + return register_restart_handler(&stage->nb); +} + +static void __rbcmd_unregister_restart_handler(struct builder *bd) +{ + struct reboot_cmd_drvdata *drvdata = + container_of(bd, struct reboot_cmd_drvdata, bd); + struct reboot_cmd_stage *stage; + + stage = __rbcmd_get_stage(drvdata, SEC_RBCMD_STAGE_RESTART_HANDLER); + + unregister_restart_handler(&stage->nb); +} + +static noinline int __rbcmd_probe_epilog(struct builder *bd) +{ + struct reboot_cmd_drvdata *drvdata = + container_of(bd, struct reboot_cmd_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + reboot_cmd = drvdata; /* set a singleton */ + + return 0; +} + +static noinline void __rbcmd_remove_prolog(struct builder *bd) +{ + /* FIXME: This is not a graceful exit. */ + reboot_cmd = NULL; +} + +static int __rbcmd_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct reboot_cmd_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __rbcmd_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct reboot_cmd_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static const struct dev_builder __rbcmd_dev_builder[] = { + DEVICE_BUILDER(__rbcmd_parse_dt, NULL), + DEVICE_BUILDER(__rbcmd_probe_prolog, __rbcmd_remove_epilog), + DEVICE_BUILDER(__rbcmd_register_reboot_notifier, + __rbcmd_unregister_reboot_notifier), + DEVICE_BUILDER(__rbcmd_register_restart_handler, + __rbcmd_unregister_restart_handler), + DEVICE_BUILDER(__rbcmd_debugfs_create, __rbcmd_debugfs_remove), + DEVICE_BUILDER(__rbcmd_probe_epilog, __rbcmd_remove_prolog), +}; + +static int sec_rbcmd_probe(struct platform_device *pdev) +{ + return __rbcmd_probe(pdev, __rbcmd_dev_builder, + ARRAY_SIZE(__rbcmd_dev_builder)); +} + +static int sec_rbcmd_remove(struct platform_device *pdev) +{ + return __rbcmd_remove(pdev, __rbcmd_dev_builder, + ARRAY_SIZE(__rbcmd_dev_builder)); +} + +static const struct of_device_id sec_rbcmd_match_table[] = { + { .compatible = "samsung,reboot_cmd" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_rbcmd_match_table); + +static struct platform_driver sec_rbcmd_driver = { + .driver = { + .name = "sec,reboot_cmd", + .of_match_table = of_match_ptr(sec_rbcmd_match_table), + }, + .probe = sec_rbcmd_probe, + .remove = sec_rbcmd_remove, +}; + +static __always_inline void __rbcmd_build_assert(void) +{ + BUILD_BUG_ON(ARRAY_SIZE(rbcmd_stage_name) != SEC_RBCMD_STAGE_MAX); +} + +static __init int sec_rbcmd_init(void) +{ + __rbcmd_build_assert(); + + return platform_driver_register(&sec_rbcmd_driver); +} +core_initcall(sec_rbcmd_init); + +static __exit void sec_rbcmd_exit(void) +{ + platform_driver_unregister(&sec_rbcmd_driver); +} +module_exit(sec_rbcmd_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("Reboot command iterator"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/debug/reboot_cmd/sec_reboot_cmd.h b/drivers/samsung/debug/reboot_cmd/sec_reboot_cmd.h new file mode 100644 index 000000000000..d2c905ca76cf --- /dev/null +++ b/drivers/samsung/debug/reboot_cmd/sec_reboot_cmd.h @@ -0,0 +1,27 @@ +#ifndef __INTERNAL__SEC_REBOOT_CMD_H__ +#define __INTERNAL__SEC_REBOOT_CMD_H__ + +#include +#include +#include +#include + +#include +#include + +struct reboot_cmd_stage { + struct mutex lock; + struct list_head list; + struct sec_reboot_cmd *dflt; + struct notifier_block nb; +}; + +struct reboot_cmd_drvdata { + struct builder bd; + struct reboot_cmd_stage stage[SEC_RBCMD_STAGE_MAX]; +#if IS_ENABLED(CONFIG_DEBUG_FS) + struct dentry *dbgfs; +#endif +}; + +#endif // __INTERNAL__SEC_REBOOT_CMD_H__ diff --git a/drivers/samsung/debug/upload_cause/Kconfig b/drivers/samsung/debug/upload_cause/Kconfig new file mode 100644 index 000000000000..38cd6ffdef7f --- /dev/null +++ b/drivers/samsung/debug/upload_cause/Kconfig @@ -0,0 +1,22 @@ +config SEC_UPLOAD_CAUSE + tristate "SEC Panic Notifier Updating Upload Cause" + help + TODO: help is not ready. + +config SEC_UPLOAD_CAUSE_TEST_FOR_ON_DEVICE + tristate "KUnit test for sec_upload_cause_test" + depends on KUNIT + depends on SEC_UPLOAD_CAUSE + help + TODO: Describe config fully. + If you run this test driver on device, SHOULD set this config as 'm' to build test driver modulraly. + +config SEC_UPLOAD_CAUSE_TEST_FOR_ONLY_UML + tristate "KUnit test for sec_upload_cause_test" + depends on KUNIT + depends on UML + depends on SEC_UPLOAD_CAUSE + help + TODO: Describe config fully. + This CONFIG is recommended to set to y. + diff --git a/drivers/samsung/debug/upload_cause/Makefile b/drivers/samsung/debug/upload_cause/Makefile new file mode 100644 index 000000000000..7f72d1ed467a --- /dev/null +++ b/drivers/samsung/debug/upload_cause/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_SEC_UPLOAD_CAUSE) += sec_upload_cause.o + +GCOV_PROFILE_sec_upload_cause.o := $(CONFIG_KUNIT) diff --git a/drivers/samsung/debug/upload_cause/sec_upload_cause.c b/drivers/samsung/debug/upload_cause/sec_upload_cause.c new file mode 100644 index 000000000000..ce2568e23afc --- /dev/null +++ b/drivers/samsung/debug/upload_cause/sec_upload_cause.c @@ -0,0 +1,500 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2020-2023 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "sec_upload_cause.h" + +static struct upload_cause_drvdata *upload_cause; + +static __always_inline bool __upldc_is_probed(void) +{ + return !!upload_cause; +} + +__ss_static __ss_inline int __upldc_add_cause( + struct upload_cause_drvdata *drvdata, + struct sec_upload_cause *uc) +{ + struct upload_cause_notify *notify; + + notify = &drvdata->notify; + + mutex_lock(¬ify->lock); + list_add(&uc->list, ¬ify->list); + mutex_unlock(¬ify->lock); + + return 0; +} + +int sec_upldc_add_cause(struct sec_upload_cause *uc) +{ + if (!__upldc_is_probed()) + return -EBUSY; + + return __upldc_add_cause(upload_cause, uc); +} +EXPORT_SYMBOL_GPL(sec_upldc_add_cause); + +__ss_static __ss_inline int __upldc_del_cause( + struct upload_cause_drvdata *drvdata, + struct sec_upload_cause *uc) +{ + struct upload_cause_notify *notify; + + notify = &drvdata->notify; + + mutex_lock(¬ify->lock); + list_del(&uc->list); + mutex_unlock(¬ify->lock); + + return 0; +} + +int sec_upldc_del_cause(struct sec_upload_cause *uc) +{ + if (!__upldc_is_probed()) + return -EBUSY; + + return __upldc_del_cause(upload_cause, uc); +} +EXPORT_SYMBOL_GPL(sec_upldc_del_cause); + +__ss_static __ss_inline int __upldc_set_default_cause( + struct upload_cause_drvdata *drvdata, + struct sec_upload_cause *uc) +{ + struct upload_cause_notify *notify; + int err = 0; + + notify = &drvdata->notify; + + mutex_lock(¬ify->lock); + if (notify->dflt) { + pr_warn("default handle is already registered. ignored!\n"); + pr_warn("Caller is %pS\n", __builtin_return_address(0)); + err = -EINVAL; + } else { + notify->dflt = uc; + } + mutex_unlock(¬ify->lock); + + return err; +} + +int sec_upldc_set_default_cause(struct sec_upload_cause *uc) +{ + if (!__upldc_is_probed()) + return -EBUSY; + + return __upldc_set_default_cause(upload_cause, uc); +} +EXPORT_SYMBOL_GPL(sec_upldc_set_default_cause); + +__ss_static __ss_inline int __upldc_unset_default_cause( + struct upload_cause_drvdata *drvdata, + struct sec_upload_cause *uc) +{ + struct upload_cause_notify *notify; + int err = 0; + + notify = &drvdata->notify; + + mutex_lock(¬ify->lock); + + if (!notify->dflt) { + pr_warn("default handle is already unset - ignored.\n"); + err = -ENOENT; + } else if (notify->dflt != uc) { + pr_warn("current default handle is different from you requested - ignored.\n"); + err = -EINVAL; + } else + notify->dflt = NULL; + + mutex_unlock(¬ify->lock); + + return err; +} + +int sec_upldc_unset_default_cause(struct sec_upload_cause *uc) +{ + if (!__upldc_is_probed()) + return -EBUSY; + + return __upldc_unset_default_cause(upload_cause, uc); +} +EXPORT_SYMBOL_GPL(sec_upldc_unset_default_cause); + +__ss_static __ss_inline void __upldc_type_to_cause( + struct upload_cause_drvdata *drvdata, + unsigned int type, char *cause, size_t len) +{ + struct upload_cause_notify *notify = &drvdata->notify; + struct sec_upload_cause *uc; + + mutex_lock(¬ify->lock); + list_for_each_entry(uc, ¬ify->list, list) { + if (type == uc->type) { + snprintf(cause, len, "%s", uc->cause); + mutex_unlock(¬ify->lock); + return; + } + } + mutex_unlock(¬ify->lock); + + strlcpy(cause, "unknown_reset", len); +} + +void sec_upldc_type_to_cause(unsigned int type, char *cause, size_t len) +{ + if (!__upldc_is_probed()) + return; + + __upldc_type_to_cause(upload_cause, type, cause, len); +} +EXPORT_SYMBOL_GPL(sec_upldc_type_to_cause); + +static int __upldc_handle_each_cause(const struct sec_upload_cause *uc, + const char *cause) +{ + return uc->func(uc, cause); +} + +static int __upldc_handle_single_cause( + struct upload_cause_notify *notify, const char *cause) +{ + struct sec_upload_cause *uc; + int handled = SEC_UPLOAD_CAUSE_HANDLE_DONE; + + /* NOTE: mutex_lock/_unlock may not be needed when this + * function works. + */ + list_for_each_entry(uc, ¬ify->list, list) { + handled = __upldc_handle_each_cause(uc, cause); + if (handled & SEC_UPLOAD_CAUSE_HANDLE_STOP_MASK) + break; + } + + return handled; +} + +__ss_static int __upldc_handle(struct upload_cause_notify *notify, + const char *cause) +{ + int handled; + + handled = __upldc_handle_single_cause(notify, cause); + + if ((handled & SEC_UPLOAD_CAUSE_HANDLE_OK) == SEC_UPLOAD_CAUSE_HANDLE_OK) + return handled; + + if (notify->dflt) { + pr_info("call default handler\n"); + handled = notify->dflt->func(notify->dflt, cause); + } + + return handled; +} + +static int sec_upldc_notifier_call(struct notifier_block *this, + unsigned long type, void *data) +{ + struct upload_cause_notify *notify = + container_of(this, struct upload_cause_notify, nb); + const char *cause = data; + int err; + int ret = NOTIFY_DONE; + + pr_info("cause : %s\n", cause); + + err = __upldc_handle(notify, cause); + switch (err) { + case SEC_UPLOAD_CAUSE_HANDLE_DONE: + pr_warn("any reboot reason did not handle this command - %s", + cause); + break; + case SEC_UPLOAD_CAUSE_HANDLE_OK: + ret = NOTIFY_OK; + break; + default: + if (err & SEC_UPLOAD_CAUSE_HANDLE_BAD) + pr_err("error occured during iteration\n"); + else + pr_err("unknown return value - %d\n", err); + break; + } + + return ret; +} + +#if IS_ENABLED(CONFIG_DEBUG_FS) +static void __upldc_dbgfs_show_each_cause_locked(struct seq_file *m, + struct sec_upload_cause *uc) +{ + seq_printf(m, "[<%p>] %s\n", uc, uc->cause); + seq_printf(m, " - func : [<%p>] %ps\n", uc->func, uc->func); + + seq_puts(m, "\n"); +} + +static void __upldc_dbgfs_show_default_cause_locked(struct seq_file *m, + struct upload_cause_notify *notify) +{ + seq_puts(m, "+ Default :\n"); + + if (notify->dflt) + __upldc_dbgfs_show_each_cause_locked(m, notify->dflt); + else + seq_puts(m, "[WARN] default cause is not set\n"); + + seq_puts(m, "\n"); +} + +static int sec_upldc_dbgfs_show_all(struct seq_file *m, void *unsed) +{ + struct upload_cause_drvdata *drvdata = m->private; + struct upload_cause_notify *notify = &drvdata->notify; + struct sec_upload_cause *uc; + + seq_printf(m, "+ Priority : %d\n", notify->nb.priority); + + mutex_lock(¬ify->lock); + + __upldc_dbgfs_show_default_cause_locked(m, notify); + + list_for_each_entry(uc, ¬ify->list, list) { + __upldc_dbgfs_show_each_cause_locked(m, uc); + } + + mutex_unlock(¬ify->lock); + + return 0; +} + +static int sec_upldc_dbgfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, sec_upldc_dbgfs_show_all, + inode->i_private); +} + +static const struct file_operations sec_upldc_dgbfs_fops = { + .open = sec_upldc_dbgfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static int __upldc_debugfs_create(struct builder *bd) +{ + struct upload_cause_drvdata *drvdata = + container_of(bd, struct upload_cause_drvdata, bd); + + drvdata->dbgfs = debugfs_create_file("sec_upload_cause", 0440, + NULL, drvdata, &sec_upldc_dgbfs_fops); + + return 0; +} + +static void __upldc_debugfs_remove(struct builder *bd) +{ + struct upload_cause_drvdata *drvdata = + container_of(bd, struct upload_cause_drvdata, bd); + + debugfs_remove(drvdata->dbgfs); +} +#else +static int __upldc_debugfs_create(struct builder *bd) { return 0; } +static void __upldc_debugfs_remove(struct builder *bd) {} +#endif + +__ss_static noinline int __upldc_parse_dt_panic_notifier_priority(struct builder *bd, + struct device_node *np) +{ + struct upload_cause_drvdata *drvdata = + container_of(bd, struct upload_cause_drvdata, bd); + struct upload_cause_notify *notify = &drvdata->notify; + s32 priority; + int err; + + err = of_property_read_s32(np, "sec,panic_notifier-priority", + &priority); + if (err) + return -EINVAL; + + notify->nb.priority = (int)priority; + + return 0; +} + +static const struct dt_builder __upldc_dt_builder[] = { + DT_BUILDER(__upldc_parse_dt_panic_notifier_priority), +}; + +static noinline int __upldc_parse_dt(struct builder *bd) +{ + return sec_director_parse_dt(bd, __upldc_dt_builder, + ARRAY_SIZE(__upldc_dt_builder)); +} + +__ss_static noinline int __upldc_probe_prolog(struct builder *bd) +{ + struct upload_cause_drvdata *drvdata = + container_of(bd, struct upload_cause_drvdata, bd); + struct upload_cause_notify *notify = &drvdata->notify; + + mutex_init(¬ify->lock); + INIT_LIST_HEAD(¬ify->list); + + return 0; +} + +static noinline void __upldc_remove_epilog(struct builder *bd) +{ + struct upload_cause_drvdata *drvdata = + container_of(bd, struct upload_cause_drvdata, bd); + struct upload_cause_notify *notify = &drvdata->notify; + + mutex_destroy(¬ify->lock); +} + +static int __upldc_register_panic_notifier(struct builder *bd) +{ + struct upload_cause_drvdata *drvdata = + container_of(bd, struct upload_cause_drvdata, bd); + struct upload_cause_notify *notify = &drvdata->notify; + + notify->nb.notifier_call = sec_upldc_notifier_call; + + return atomic_notifier_chain_register(&panic_notifier_list, + ¬ify->nb); +} + +static void __upldc_unregister_panic_notifier(struct builder *bd) +{ + struct upload_cause_drvdata *drvdata = + container_of(bd, struct upload_cause_drvdata, bd); + struct upload_cause_notify *notify = &drvdata->notify; + + atomic_notifier_chain_unregister(&panic_notifier_list, ¬ify->nb); +} + +static int __upldc_probe_epilog(struct builder *bd) +{ + struct upload_cause_drvdata *drvdata = + container_of(bd, struct upload_cause_drvdata, bd); + struct device *dev = bd->dev; + + dev_set_drvdata(dev, drvdata); + upload_cause = drvdata; /* set a singleton */ + + return 0; +} + +static void __upldc_remove_prolog(struct builder *bd) +{ + /* FIXME: This is not a graceful exit. */ + upload_cause = NULL; +} + +static int __upldc_probe(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct device *dev = &pdev->dev; + struct upload_cause_drvdata *drvdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->bd.dev = dev; + + return sec_director_probe_dev(&drvdata->bd, builder, n); +} + +static int __upldc_remove(struct platform_device *pdev, + const struct dev_builder *builder, ssize_t n) +{ + struct upload_cause_drvdata *drvdata = platform_get_drvdata(pdev); + + sec_director_destruct_dev(&drvdata->bd, builder, n, n); + + return 0; +} + +static const struct dev_builder __upldc_dev_builder[] = { + DEVICE_BUILDER(__upldc_parse_dt, NULL), + DEVICE_BUILDER(__upldc_probe_prolog, __upldc_remove_epilog), + DEVICE_BUILDER(__upldc_register_panic_notifier, + __upldc_unregister_panic_notifier), + DEVICE_BUILDER(__upldc_debugfs_create, + __upldc_debugfs_remove), + DEVICE_BUILDER(__upldc_probe_epilog, + __upldc_remove_prolog), +}; + +static int sec_upldc_probe(struct platform_device *pdev) +{ + return __upldc_probe(pdev, __upldc_dev_builder, + ARRAY_SIZE(__upldc_dev_builder)); +} + +static int sec_upldc_remove(struct platform_device *pdev) +{ + return __upldc_remove(pdev, __upldc_dev_builder, + ARRAY_SIZE(__upldc_dev_builder)); +} + +static const struct of_device_id sec_upldc_match_table[] = { + { .compatible = "samsung,upload_cause" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_upldc_match_table); + +static struct platform_driver sec_upldc_driver = { + .driver = { + .name = "sec,upload_cause", + .of_match_table = of_match_ptr(sec_upldc_match_table), + }, + .probe = sec_upldc_probe, + .remove = sec_upldc_remove, +}; + +static __init int sec_upldc_init(void) +{ + int err; + + err = platform_driver_register(&sec_upldc_driver); + if (err) + return err; + + err = __of_platform_early_populate_init(sec_upldc_match_table); + if (err) + return err; + + return 0; +} +core_initcall(sec_upldc_init); + +static __exit void sec_upldc_exit(void) +{ + platform_driver_unregister(&sec_upldc_driver); +} +module_exit(sec_upldc_exit); + +MODULE_AUTHOR("Samsung Electronics"); +MODULE_DESCRIPTION("Panic Notifier Updating Upload Cause"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/samsung/debug/upload_cause/sec_upload_cause.h b/drivers/samsung/debug/upload_cause/sec_upload_cause.h new file mode 100644 index 000000000000..1b5f82e3fb59 --- /dev/null +++ b/drivers/samsung/debug/upload_cause/sec_upload_cause.h @@ -0,0 +1,27 @@ +#ifndef __INTERNAL__SEC_UPLOAD_CAUSE_H__ +#define __INTERNAL__SEC_UPLOAD_CAUSE_H__ + +#include +#include +#include +#include + +#include +#include + +struct upload_cause_notify { + struct mutex lock; + struct list_head list; + struct sec_upload_cause *dflt; + struct notifier_block nb; +}; + +struct upload_cause_drvdata { + struct builder bd; + struct upload_cause_notify notify; +#if IS_ENABLED(CONFIG_DEBUG_FS) + struct dentry *dbgfs; +#endif +}; + +#endif /* __INTERNAL__SEC_UPLOAD_CAUSE_H__ */ diff --git a/drivers/samsung/knox/Kconfig b/drivers/samsung/knox/Kconfig new file mode 100644 index 000000000000..6ee585f76f69 --- /dev/null +++ b/drivers/samsung/knox/Kconfig @@ -0,0 +1 @@ +source "drivers/samsung/knox/hdm/Kconfig" diff --git a/drivers/samsung/knox/Makefile b/drivers/samsung/knox/Makefile new file mode 100644 index 000000000000..554b77e441ff --- /dev/null +++ b/drivers/samsung/knox/Makefile @@ -0,0 +1,2 @@ +# HDM driver +obj-$(CONFIG_HDM) += hdm/ diff --git a/drivers/samsung/knox/hdm/Kconfig b/drivers/samsung/knox/hdm/Kconfig new file mode 100644 index 000000000000..f853a75a7261 --- /dev/null +++ b/drivers/samsung/knox/hdm/Kconfig @@ -0,0 +1,22 @@ +# @file Kconfig +# @brief Kconfig for HDM driver +# Copyright (c) 2019, Samsung Electronics Corporation. 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. + +config HDM + tristate "HDM feature enable" + depends on !ARCH_QTI_VM + default n + help + HDM TLC uses the HDM driver to trigger the hypervisor. + And it is responsible for receiving and processing bootconfig related to HDM. + + diff --git a/drivers/samsung/knox/hdm/Makefile b/drivers/samsung/knox/hdm/Makefile new file mode 100644 index 000000000000..586e955f8586 --- /dev/null +++ b/drivers/samsung/knox/hdm/Makefile @@ -0,0 +1,23 @@ +#@file Makefile +#@brief Makefile for HDM driver +#Copyright (c) 2019, Samsung Electronics Corporation. 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. + +obj-$(CONFIG_HDM) += hdm.o + +ifeq ($(CONFIG_UH), y) + $(warning [HDM] CONFIG_UH Enabled) + hdm-objs = main.o uh_entry.o + ccflags-y += -DCONFIG_HDM_UH=true +else + hdm-objs = main.o +endif + diff --git a/drivers/samsung/knox/hdm/hdm_log.h b/drivers/samsung/knox/hdm/hdm_log.h new file mode 100644 index 000000000000..a6ba39db80e6 --- /dev/null +++ b/drivers/samsung/knox/hdm/hdm_log.h @@ -0,0 +1,38 @@ +/* + * @file hdm_log.h + * @brief Header file for HDM log + * Copyright (c) 2019, Samsung Electronics Corporation. 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 __HDM_LOG_H__ +#define __HDM_LOG_H__ + +#include + +#define TAG "[sec_hdm]" + +void hdm_printk(int level, const char *fmt, ...); + +#define DEV_ERR (1) +#define DEV_WARN (2) +#define DEV_NOTI (3) +#define DEV_INFO (4) +#define DEV_DEBUG (5) +#define HDM_LOG_LEVEL DEV_INFO + +#define hdm_err(fmt, ...) hdm_printk(DEV_ERR, fmt, ## __VA_ARGS__) +#define hdm_warn(fmt, ...) hdm_printk(DEV_WARN, fmt, ## __VA_ARGS__) +#define hdm_noti(fmt, ...) hdm_printk(DEV_NOTI, fmt, ## __VA_ARGS__) +#define hdm_info(fmt, ...) hdm_printk(DEV_INFO, fmt, ## __VA_ARGS__) +#define hdm_debug(fmt, ...) hdm_printk(DEV_DEBUG, fmt, ## __VA_ARGS__) + +#endif //__HDM_LOG_H__ diff --git a/drivers/samsung/knox/hdm/main.c b/drivers/samsung/knox/hdm/main.c new file mode 100644 index 000000000000..dc482b7d8966 --- /dev/null +++ b/drivers/samsung/knox/hdm/main.c @@ -0,0 +1,364 @@ +/* + * @file hdm.c + * @brief HDM Support + * Copyright (c) 2020, Samsung Electronics Corporation. 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 +#if defined(CONFIG_ARCH_QCOM) +#include +#include +#include +#endif +#include +#include +#include +#include +#include +#if defined(CONFIG_ARCH_QCOM) +#include +#include +#include +#include +#endif + + +#include "hdm_log.h" +#include "uh.h" + + +int hdm_log_level = HDM_LOG_LEVEL; + +static u64 supported_subsystem = 0; + +static char *status = "NONE"; +module_param(status, charp, 0444); +MODULE_PARM_DESC(status, "HDM status"); + +int hdm_wifi_support = 0; +static int hdm_cp_support = 0; +EXPORT_SYMBOL(hdm_wifi_support); + +int hdm_is_wlan_enabled(void) +{ + return hdm_wifi_support; +} +EXPORT_SYMBOL(hdm_is_wlan_enabled); + +int hdm_is_cp_enabled(void) +{ + return hdm_cp_support; +} +EXPORT_SYMBOL(hdm_is_cp_enabled); + +void hdm_printk(int level, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + if (hdm_log_level < level) + return; + + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + printk(KERN_INFO "%s %pV", TAG, &vaf); + + va_end(args); +} + + +static int __init hdm_flag_setup(void) +{ + char tmp_hdm_status[100] = {0}; + char *tmp_p = NULL; + char *token = NULL; + int cnt = 0; + long val = 0; + int err = 0; + + hdm_wifi_support = 0; + hdm_cp_support = 0; + + hdm_info("%s hdm.status = %s\n", __func__, status); + + if (status) { + snprintf(tmp_hdm_status, sizeof(tmp_hdm_status), "%s", status); + + tmp_p = tmp_hdm_status; + + token = strsep(&tmp_p, "&|"); + + while (token) { + //even = hdm applied bit + if (cnt++%2) { + err = kstrtol(token, 16, &val); + if (err) + return err; + if (val & HDM_WIFI_SUPPORT_BIT && hdm_wifi_support == 0) { + hdm_info("%s wifi bit set\n", __func__); + hdm_wifi_support = 1; + } + if (val & HDM_CP_SUPPORT_BIT && hdm_cp_support == 0) { + hdm_info("%s cp bit set\n", __func__); + hdm_cp_support = 1; + } + } + token = strsep(&tmp_p, "&|"); + } + } + + hdm_info("%s hdm_wifi_support = %d, hdm_cp_support = %d\n", __func__, hdm_wifi_support, hdm_cp_support); + + return 0; +} + +static ssize_t store_hdm_policy(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long mode = HDM_CMD_MAX; + int c, p; + + if (count == 0) { + hdm_err("%s count = 0\n", __func__); + goto error; + } + + if (kstrtoul(buf, 0, &mode)) { + goto error; + }; + + if (mode > HDM_CMD_MAX) { + hdm_err("%s command size max fail. %d\n", __func__, mode); + goto error; + } + hdm_info("%s: command id: 0x%x\n", __func__, (int)mode); + + c = (int)(mode & HDM_C_BITMASK); + p = (int)(mode & HDM_P_BITMASK); + + hdm_info("%s m:0x%x c:0x%x p:0x%x\n", __func__, (int)mode, c, p); + switch (c) { + case HDM_FLAG_SET: + hdm_info("%s HDM_FLAG_SET\n", __func__); + if ((p & HDM_WIFI_SUPPORT_BIT) == HDM_WIFI_SUPPORT_BIT) { + hdm_info("%s wifi bit set\n", __func__); + hdm_wifi_support = 1; + } + break; + case HDM_FLAG_UNSET: + hdm_info("%s HDM_FLAG_UNSET\n", __func__); + if ((p & HDM_WIFI_SUPPORT_BIT) == HDM_WIFI_SUPPORT_BIT) { + hdm_info("%s wifi bit unset\n", __func__); + hdm_wifi_support = 0; + } + break; +#if defined(CONFIG_ARCH_QCOM) + case HDM_HYP_CALL: + hdm_info("%s HDM_HYP_CALL\n", __func__); + uh_call(UH_APP_HDM, 9, 0, p, 0, 0); + break; + case HDM_HYP_CALLP: + hdm_info("%s HDM_HYP_CALLP\n", __func__); + uh_call(UH_APP_HDM, 2, 0, p, 0, 0); + break; + case HDM_HYP_INIT: + hdm_info("%s HDM_HYP_INIT\n", __func__); + uh_call(UH_APP_HDM, 3, 0, 0, 0, 0); + break; + case HDM_HYP_CLEAR: + hdm_info("%s HDM_HYP_CLEAR\n", __func__); + uh_call(UH_APP_HDM, 4, 0, 0, 0, 0); + break; +#endif + default: + goto error; + } +error: + return count; +} + +static ssize_t show_hdm_subsystem(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u32 hdm_version = 0; + + hdm_info("%s\n", __func__); + + if ( supported_subsystem == 0) { + uh_call(UH_APP_HDM, HDM_GET_SUPPORTED_SUBSYSTEM, (u64)&supported_subsystem, 0, 0, 0); + hdm_info("supported_subsystem = %06x\n", supported_subsystem); + } + + hdm_version = (supported_subsystem >> 12) & 0xFFF; + hdm_info("hdm_version = %03x\n", hdm_version); + + + return snprintf(buf, 6, "0x%03x\n", hdm_version); +} + +static ssize_t show_pad_subsystem(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u32 pad_version = 0; + + hdm_info("%s\n", __func__); + + if ( supported_subsystem == 0) { + uh_call(UH_APP_HDM, HDM_GET_SUPPORTED_SUBSYSTEM, (u64)&supported_subsystem, 0, 0, 0); + hdm_info("supported_subsystem = %06x\n", supported_subsystem); + } + + pad_version = supported_subsystem & 0xFFF; + hdm_info("pad_version = %03x\n", pad_version); + + return snprintf(buf, 6, "0x%03x\n", pad_version); +} +static DEVICE_ATTR(hdm_policy, 0220, NULL, store_hdm_policy); +static DEVICE_ATTR(hdm_subsystem, 0444, show_hdm_subsystem, NULL); +static DEVICE_ATTR(pad_subsystem, 0444, show_pad_subsystem, NULL); + + +#if defined(CONFIG_ARCH_QCOM) +int hyp_assign_phys(phys_addr_t addr, u64 size, u32 *source_vm_list, + int source_nelems, int *dest_vmids, + int *dest_perms, int dest_nelems) +{ + struct sg_table table; + int ret; + + ret = sg_alloc_table(&table, 1, GFP_KERNEL); + if (ret) + return ret; + + sg_set_page(table.sgl, phys_to_page(addr), size, 0); + + ret = hyp_assign_table(&table, source_vm_list, source_nelems, + dest_vmids, dest_perms, dest_nelems); + + sg_free_table(&table); + return ret; +} + + +static uint64_t qseelog_shmbridge_handle; +static int __init __hdm_init_of(void) +{ + struct device_node *node; + struct resource r; + int ret; + phys_addr_t addr; + u64 size; + + uint32_t ns_vmids[] = {VMID_HLOS_FREE}; + uint32_t ns_vm_perms[] = {PERM_READ | PERM_WRITE}; + uint32_t ns_vm_nums = 1; + + int src_vmids[1] = {VMID_HLOS}; + int dest_vmids[1] = {VMID_HLOS_FREE}; + int dest_perms[1] = {PERM_READ | PERM_WRITE}; + + hdm_info("%s start\n", __func__); + + node = of_find_node_by_name(NULL, "samsung,sec_hdm"); + if (!node) { + hdm_err("failed of_find_node_by_name\n"); + return -ENODEV; + } + + node = of_parse_phandle(node, "memory-region", 0); + if (!node) { + hdm_err("no memory-region specified\n"); + return -EINVAL; + } + + + ret = of_address_to_resource(node, 0, &r); + if (ret) { + hdm_err("failed of_address_to_resource\n"); + return ret; + } + + addr = r.start; + size = resource_size(&r); + + ret = hyp_assign_phys(addr, size, src_vmids, 1, dest_vmids, dest_perms, 1); + if (ret) { + hdm_err("%s: failed for %pa address of size %zx rc:%d\n", + __func__, &addr, size, ret); + } + + ret = qtee_shmbridge_register(addr, size, ns_vmids, ns_vm_perms, + ns_vm_nums, PERM_READ | PERM_WRITE, &qseelog_shmbridge_handle); + if (ret) + hdm_err("failed to create bridge for qsee_log buffer\n"); + + + hdm_info("%s done\n", __func__); + return 0; +} +#endif + +static int __init hdm_test_init(void) +{ + struct device *dev; +#if defined(CONFIG_ARCH_QCOM) + int err; +#endif + + hdm_flag_setup(); + + dev = sec_device_create(NULL, "hdm"); + WARN_ON(!dev); + if (IS_ERR(dev)) { + hdm_err("%s Failed to create devce\n", __func__); + return 0; + } + + if (device_create_file(dev, &dev_attr_hdm_policy) < 0) { + hdm_err("%s Failed to create device file\n", __func__); + return 0; + } + + if (device_create_file(dev, &dev_attr_hdm_subsystem) < 0) { + hdm_err("%s Failed to create device file\n", __func__); + return 0; + } + + if (device_create_file(dev, &dev_attr_pad_subsystem) < 0) { + hdm_err("%s Failed to create device file\n", __func__); + return 0; + } + +#if defined(CONFIG_ARCH_QCOM) + err = __hdm_init_of(); + if (err) + return err; +#endif + + hdm_info("%s end\n", __func__); + return 0; +} + +module_init(hdm_test_init); + +MODULE_AUTHOR("Taeho Kim "); +MODULE_AUTHOR("Shinjae Lee "); +MODULE_DESCRIPTION("HDM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/samsung/knox/hdm/uh.h b/drivers/samsung/knox/hdm/uh.h new file mode 100644 index 000000000000..d9f87cfd4f95 --- /dev/null +++ b/drivers/samsung/knox/hdm/uh.h @@ -0,0 +1,40 @@ +#ifndef __UH_H__ +#define __UH_H__ + +#ifndef __ASSEMBLY__ + +/* For uH Command */ +#define APP_INIT 0 +#define APP_RKP 1 +#define APP_KDP 2 +#define APP_HDM 6 + +#define UH_PREFIX UL(0xc300c000) +#define UH_APPID(APP_ID) ((UL(APP_ID) & UL(0xFF)) | UH_PREFIX) + +enum __UH_APP_ID { + UH_APP_INIT = UH_APPID(APP_INIT), + UH_APP_RKP = UH_APPID(APP_RKP), + UH_APP_KDP = UH_APPID(APP_KDP), + UH_APP_HDM = UH_APPID(APP_HDM), +}; + +struct test_case_struct { + int (*fn)(void); + char *describe; +}; + +#define UH_LOG_START 0xB0200000 +#define UH_LOG_SIZE 0x40000 + +#ifdef CONFIG_HDM_UH +unsigned long uh_call(u64 app_id, u64 command, u64 arg0, u64 arg1, u64 arg2, u64 arg3); +#else +unsigned long uh_call(u64 app_id, u64 command, u64 arg0, u64 arg1, u64 arg2, u64 arg3) +{ + return 0; +} +#endif + +#endif //__ASSEMBLY__ +#endif //__UH_H__ diff --git a/drivers/samsung/knox/hdm/uh_entry.S b/drivers/samsung/knox/hdm/uh_entry.S new file mode 100644 index 000000000000..988757ba4d12 --- /dev/null +++ b/drivers/samsung/knox/hdm/uh_entry.S @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * + * 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. + * + */ +#include +#include "uh.h" + +#if defined(CONFIG_ARCH_QCOM) + +SYM_CODE_START(uh_call) +entry: + stp x1, x0, [sp, #-16]! + stp x3, x2, [sp, #-16]! + stp x5, x4, [sp, #-16]! + stp x7, x6, [sp, #-16]! +back: + smc #0x0 + /* save smc return result to x8 */ + mov x8, x0 + /* interrupted case does not need x0-x7 be restored */ + cmp x8, #0x1 + b.eq back + + ldp x7, x6, [sp], #16 + ldp x5, x4, [sp], #16 + ldp x3, x2, [sp], #16 + ldp x1, x0, [sp], #16 + + cmp x8, #0x0 + /* busy return case does need x0-x7 be restored */ + b.ne entry + + ret +SYM_CODE_END(uh_call) + +#endif + diff --git a/drivers/samsung/pm/sec_thermistor/Kconfig b/drivers/samsung/pm/sec_thermistor/Kconfig new file mode 100644 index 000000000000..35445d6bcade --- /dev/null +++ b/drivers/samsung/pm/sec_thermistor/Kconfig @@ -0,0 +1,8 @@ +config SEC_PM_THERMISTOR + tristate "SEC Thermistor support" + depends on SEC_PM && IIO + default n + help + This driver supports SEC Thermistor sensor readinga nd its + interpretation. This driver uses the industrial I/O subsystem + and need adc-termperature mapping table for conversion. diff --git a/drivers/samsung/pm/sec_thermistor/Makefile b/drivers/samsung/pm/sec_thermistor/Makefile new file mode 100644 index 000000000000..ee5b0cfae88e --- /dev/null +++ b/drivers/samsung/pm/sec_thermistor/Makefile @@ -0,0 +1,5 @@ +# SEC THERMISTOR Feature +obj-$(CONFIG_SEC_PM_THERMISTOR) += sec_thermistor.o + +ccflags-y := -Wformat + diff --git a/drivers/samsung/pm/sec_thermistor/sec_thermistor.c b/drivers/samsung/pm/sec_thermistor/sec_thermistor.c new file mode 100644 index 000000000000..969917193125 --- /dev/null +++ b/drivers/samsung/pm/sec_thermistor/sec_thermistor.c @@ -0,0 +1,394 @@ +/* + * sec_thermistor.c - SEC Thermistor + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * Minsung Kim + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ADC_SAMPLING_CNT 1 +#define THERMISTOR_NAME_LEN 32 +#define FAKE_TEMP 300 + +struct sec_therm_info { + int id; + struct device *dev; + struct device *sec_dev; + struct sec_therm_platform_data *pdata; + struct iio_channel *chan; + char name[THERMISTOR_NAME_LEN]; + struct device_node *np; + unsigned int sampling_cnt; +}; + +#ifdef CONFIG_OF +static const struct of_device_id sec_therm_match[] = { + { .compatible = "samsung,sec-thermistor", }, + { }, +}; +MODULE_DEVICE_TABLE(of, sec_therm_match); + +static int sec_therm_parse_dt(struct platform_device *pdev) +{ + struct sec_therm_info *info = platform_get_drvdata(pdev); + struct sec_therm_platform_data *pdata; + const char *name; + int adc_arr_len, temp_arr_len; + int i; + u32 adc, tp; + + if (!info || !pdev->dev.of_node) + return -ENODEV; + + info->np = pdev->dev.of_node; + + if (of_property_read_u32(info->np, "id", &info->id)) { + dev_err(info->dev, "failed to get thermistor ID\n"); + return -EINVAL; + } + + if (!of_property_read_string(info->np, "thermistor_name", &name)) { + strlcpy(info->name, name, sizeof(info->name)); + } else { + dev_err(info->dev, "failed to get thermistor name\n"); + return -EINVAL; + } + + if (of_property_read_u32(info->np, "sampling_cnt", &info->sampling_cnt)) { + dev_info(info->dev, "set sampling_cnt by default: %d\n", + ADC_SAMPLING_CNT); + info->sampling_cnt = ADC_SAMPLING_CNT; + } + + pdata = devm_kzalloc(info->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + if (!of_get_property(info->np, "adc_array", &adc_arr_len)) + return -ENOENT; + if (!of_get_property(info->np, "temp_array", &temp_arr_len)) + return -ENOENT; + + if (adc_arr_len != temp_arr_len) { + dev_err(info->dev, "%s: invalid array length(%u,%u)\n", + __func__, adc_arr_len, temp_arr_len); + return -EINVAL; + } + + pdata->iio_processed = of_property_read_bool(info->np, "use_iio_processed"); + pdata->adc_arr_size = adc_arr_len / sizeof(u32); + pdata->adc_table = devm_kzalloc(&pdev->dev, + sizeof(*pdata->adc_table) * pdata->adc_arr_size, + GFP_KERNEL); + if (!pdata->adc_table) + return -ENOMEM; + + for (i = 0; i < pdata->adc_arr_size; i++) { + if (of_property_read_u32_index(info->np, "adc_array", i, &adc)) + return -EINVAL; + if (of_property_read_u32_index(info->np, "temp_array", i, &tp)) + return -EINVAL; + + pdata->adc_table[i].adc = (int)adc; + pdata->adc_table[i].temperature = (int)tp; + } + + info->pdata = pdata; + + return 0; +} +#else +static int sec_therm_parse_dt(struct platform_device *pdev) { return -ENODEV; } +#endif + +static int sec_therm_read_adc_data(struct sec_therm_info *info, int *adc_data) +{ + int ret; + + if (info->pdata->iio_processed) + ret = iio_read_channel_processed(info->chan, adc_data); + else + ret = iio_read_channel_raw(info->chan, adc_data); + + if (ret < 0) { + dev_err(info->dev, "%s : err(%d), adc_data(%d) returned, skip read\n", + __func__, ret, *adc_data); + } + + return ret; +} + +static int sec_therm_get_adc_data(struct sec_therm_info *info) +{ + int adc_data, ret; + int adc_max = 0, adc_min = 0, adc_total = 0; + int i; + + for (i = 0; i < info->sampling_cnt; i++) { + ret = sec_therm_read_adc_data(info, &adc_data); + if (ret < 0) + return ret; + + if (info->sampling_cnt < 3) + return adc_data; + + if (i != 0) { + if (adc_data > adc_max) + adc_max = adc_data; + else if (adc_data < adc_min) + adc_min = adc_data; + } else { + adc_max = adc_data; + adc_min = adc_data; + } + adc_total += adc_data; + } + + return (adc_total - adc_max - adc_min) / (info->sampling_cnt - 2); +} + +static bool is_using_fake_temp(struct sec_therm_info *info) +{ + return !info->pdata->adc_table || !info->pdata->adc_arr_size; +} + +static int get_closest_adc_table_idx(struct sec_therm_info *info, unsigned int adc) +{ + int low = 0; + int high = info->pdata->adc_arr_size - 1; + + if (info->pdata->adc_table[low].adc >= adc) + return low; + else if (info->pdata->adc_table[high].adc <= adc) + return high; + + return -1; +} + +static int find_appropriate_temp(int *low, int *high, int adc, struct sec_therm_info *info) +{ + int mid; + struct sec_therm_adc_table *mid_table; + + while (*low <= *high) { + mid = (*low + *high) / 2; + mid_table = &info->pdata->adc_table[mid]; + + if (mid_table->adc > adc) + *high = mid - 1; + else if (mid_table->adc < adc) + *low = mid + 1; + else + return mid_table->temperature; + } + + return -1; // Not found +} + +static int calculate_temp(int low, int high, int adc, struct sec_therm_info *info) +{ + int temp = info->pdata->adc_table[high].temperature; + int temp_diff = (info->pdata->adc_table[low].temperature - + info->pdata->adc_table[high].temperature) * + (adc - info->pdata->adc_table[high].adc); + + temp += temp_diff / + (info->pdata->adc_table[low].adc - + info->pdata->adc_table[high].adc); + + return temp; +} + +static int convert_adc_to_temp(struct sec_therm_info *info, unsigned int adc) +{ + int low = 0; + int high = info->pdata->adc_arr_size - 1; + int temp = 0; + int idx = 0; + + if (is_using_fake_temp(info)) + return FAKE_TEMP; + + idx = get_closest_adc_table_idx(info, adc); + if (idx != -1) + return info->pdata->adc_table[idx].temperature; + + temp = find_appropriate_temp(&low, &high, adc, info); + if (temp != -1) + return temp; + + temp = calculate_temp(low, high, adc, info); + + return temp; +} + +static ssize_t sec_therm_show_temperature(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sec_therm_info *info = dev_get_drvdata(dev); + int adc, temp; + + adc = sec_therm_get_adc_data(info); + + if (adc >= 0) + temp = convert_adc_to_temp(info, adc); + else + return adc; + + return sprintf(buf, "%d\n", temp); +} + +static ssize_t sec_therm_show_temp_adc(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sec_therm_info *info = dev_get_drvdata(dev); + int adc; + + adc = sec_therm_get_adc_data(info); + + return sprintf(buf, "%d\n", adc); +} + +static ssize_t sec_therm_show_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_therm_info *info = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", info->name); +} + +static DEVICE_ATTR(temperature, 0444, sec_therm_show_temperature, NULL); +static DEVICE_ATTR(temp_adc, 0444, sec_therm_show_temp_adc, NULL); +static DEVICE_ATTR(name, 0444, sec_therm_show_name, NULL); + +static struct attribute *sec_therm_attrs[] = { + &dev_attr_temperature.attr, + &dev_attr_temp_adc.attr, + &dev_attr_name.attr, + NULL +}; + +static const struct attribute_group sec_therm_group = { + .attrs = sec_therm_attrs, +}; + +static struct sec_therm_info *g_ap_therm_info; +int sec_therm_get_ap_temperature(void) +{ + int adc; + int temp; + + if (unlikely(!g_ap_therm_info)) + return -ENODEV; + + adc = sec_therm_get_adc_data(g_ap_therm_info); + + if (adc >= 0) + temp = convert_adc_to_temp(g_ap_therm_info, adc); + else + return adc; + + return temp; +} + +static int sec_therm_probe(struct platform_device *pdev) +{ + struct sec_therm_info *info; + int ret; + + dev_dbg(&pdev->dev, "%s: SEC Thermistor Driver Loading\n", __func__); + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + platform_set_drvdata(pdev, info); + info->dev = &pdev->dev; + + ret = sec_therm_parse_dt(pdev); + if (ret) { + dev_err(info->dev, "%s: fail to parse dt\n", __func__); + return ret; + } + + info->chan = iio_channel_get(info->dev, NULL); + if (IS_ERR(info->chan)) { + dev_err(info->dev, "%s: fail to get iio channel(%lu)\n", + __func__, PTR_ERR(info->chan)); + return PTR_ERR(info->chan); + } + + info->sec_dev = sec_device_create(info, info->name); + if (IS_ERR(info->sec_dev)) { + dev_err(info->dev, "%s: fail to create sec_dev\n", __func__); + return PTR_ERR(info->sec_dev); + } + + ret = sysfs_create_group(&info->sec_dev->kobj, &sec_therm_group); + if (ret) { + dev_err(info->dev, "failed to create sysfs group\n"); + goto err_create_sysfs; + } + + if (info->id == 0) + g_ap_therm_info = info; + + dev_info(info->dev, "%s successfully probed.\n", info->name); + + return 0; + +err_create_sysfs: + sec_device_destroy(info->sec_dev->devt); + return ret; +} + +static int sec_therm_remove(struct platform_device *pdev) +{ + struct sec_therm_info *info = platform_get_drvdata(pdev); + + if (!info) + return 0; + + if (info->id == 0) + g_ap_therm_info = NULL; + + sysfs_remove_group(&info->sec_dev->kobj, &sec_therm_group); + iio_channel_release(info->chan); + sec_device_destroy(info->sec_dev->devt); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver sec_thermistor_driver = { + .driver = { + .name = "sec-thermistor", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(sec_therm_match), + }, + .probe = sec_therm_probe, + .remove = sec_therm_remove, +}; + +module_platform_driver(sec_thermistor_driver); + +MODULE_DESCRIPTION("SEC Thermistor Driver"); +MODULE_AUTHOR("Minsung Kim "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:sec-thermistor"); diff --git a/drivers/samsung/power/Kconfig b/drivers/samsung/power/Kconfig new file mode 100644 index 000000000000..dac8d0bf672d --- /dev/null +++ b/drivers/samsung/power/Kconfig @@ -0,0 +1,15 @@ +config SEC_EXT_THERMAL_MONITOR + tristate "Samsung external thermal monitor" + help + This enables external thermal monitor to get hint whether if there's + external cause or not. + +config SEC_PM_LOG + tristate "Samsung Power/Thermal Log driver" + help + TODO: help is not ready. + +config SKPM + tristate "Samsung Kernel Power Monitor" + help + Monitor/Measure internal power related value \ No newline at end of file diff --git a/drivers/samsung/power/Makefile b/drivers/samsung/power/Makefile new file mode 100644 index 000000000000..f94c8703cb7a --- /dev/null +++ b/drivers/samsung/power/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_SEC_EXT_THERMAL_MONITOR) += sec_ext_tm.o +obj-$(CONFIG_SEC_PM_LOG) += sec_pm_log.o +obj-$(CONFIG_SKPM) += skpm.o diff --git a/drivers/samsung/power/sec_pm_log.c b/drivers/samsung/power/sec_pm_log.c new file mode 100644 index 000000000000..05e70db1640b --- /dev/null +++ b/drivers/samsung/power/sec_pm_log.c @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * sec_pm_log.c - SAMSUNG Power/Thermal logging. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* log for pm team */ +#define PROC_PM_DIR "sec_pm_log" +static struct proc_dir_entry *procfs_pm_dir; + +/* power/thermal log */ +#define POWER_LOG_NAME "power_log" +#define THERMAL_LOG_NAME "thermal_log" +#define PM_LOG_BUF_SIZE SZ_128K +#define MAX_STR_LEN 256 +static bool proc_init_done __read_mostly; + +/* monitoring log */ +static struct delayed_work power_log_work; +static DEFINE_MUTEX(power_log_lock); + +/* proc log implementation */ +#define SEC_PM_LOG(name) \ +static char name##_log_buf[PM_LOG_BUF_SIZE]; \ +static unsigned int name##_buf_pos; \ +static unsigned int name##_buf_full; \ + \ +void ss_##name##_print(const char *fmt, ...) \ +{ \ + char buf[MAX_STR_LEN] = {0, }; \ + unsigned int len = 0; \ + va_list args; \ + u64 time; \ + unsigned long nsec; \ + \ + if (!proc_init_done) \ + return; \ + \ + /* timestamp */ \ + time = local_clock(); \ + nsec = do_div(time, NSEC_PER_SEC); \ + len = snprintf(buf, sizeof(buf), "[%6lu.%06ld] ", \ + (unsigned long)time, nsec / NSEC_PER_USEC); \ + \ + /* append log */ \ + va_start(args, fmt); \ + len += vsnprintf(buf + len, MAX_STR_LEN - len, fmt, args); \ + va_end(args); \ + \ + /* buffer full, reset position */ \ + if (name##_buf_pos + len + 1 > PM_LOG_BUF_SIZE) { \ + name##_buf_pos = 0; \ + name##_buf_full++; \ + } \ + \ + name##_buf_pos += scnprintf(&name##_log_buf[name##_buf_pos], \ + len + 1, "%s", buf); \ +} \ +EXPORT_SYMBOL(ss_##name##_print); \ + \ +static ssize_t ss_##name##_log_read(struct file *file, char __user *buf, \ + size_t len, loff_t *offset) \ +{ \ + loff_t pos = *offset; \ + ssize_t count = 0; \ + size_t size = (name##_buf_full > 0) ? PM_LOG_BUF_SIZE : (size_t)name##_buf_pos; \ + \ + pr_debug("%s: pos(%d), full(%d), size(%d)\n", __func__, \ + name##_buf_pos, name##_buf_full, size); \ + \ + if (pos >= size) \ + return 0; \ + \ + count = min(len, size); \ + \ + if ((pos + count) > size) \ + count = size - pos; \ + \ + if (copy_to_user(buf, name##_log_buf + pos, count)) \ + return -EFAULT; \ + \ + *offset += count; \ + \ + pr_info("%s: done\n", __func__); \ + \ + return count; \ +} \ + \ +static const struct proc_ops proc_ss_##name##_log_ops = { \ + .proc_read = ss_##name##_log_read, \ +}; + +/* all-in-one define */ +SEC_PM_LOG(power); +SEC_PM_LOG(thermal); + + +/* monitoring log implementation */ +#define POLLING_DELAY (HZ * 5) +static void __ref power_log_print(struct work_struct *work) +{ + char power_log[MAX_STR_LEN]; + + mutex_lock(&power_log_lock); + pm_get_active_wakeup_sources(power_log, MAX_STR_LEN); + mutex_unlock(&power_log_lock); + + ss_power_print("pmon: %s\n", power_log); + + schedule_delayed_work(&power_log_work, POLLING_DELAY); +} + +void sec_pm_proc_log_init(void) +{ + struct proc_dir_entry *entry; + + procfs_pm_dir = proc_mkdir(PROC_PM_DIR, NULL); + if (unlikely(!procfs_pm_dir)) { + pr_err("%s: failed to make %s\n", __func__, PROC_PM_DIR); + return; + } + + /* proc power */ + entry = proc_create(POWER_LOG_NAME, 0444, + procfs_pm_dir, &proc_ss_power_log_ops); + if (unlikely(!entry)) { + pr_err("%s: proc power log fail\n", __func__); + } else { + proc_set_size(entry, PM_LOG_BUF_SIZE); + ss_power_print("%s done\n", __func__); + proc_init_done = true; + } + + /* proc thermal */ + entry = proc_create(THERMAL_LOG_NAME, 0444, + procfs_pm_dir, + &proc_ss_thermal_log_ops); + if (unlikely(!entry)) { + pr_err("%s: proc thermal log fail\n", __func__); + } else { + proc_set_size(entry, PM_LOG_BUF_SIZE); + ss_thermal_print("%s done\n", __func__); + proc_init_done = true; + } + + if (!proc_init_done) + remove_proc_subtree(PROC_PM_DIR, NULL); + else + pr_info("%s: proc init done\n", __func__); + + return; +} + +static int sec_pm_log_notify(struct notifier_block *nb, + unsigned long mode, void *_unused) +{ + struct timespec64 ts; + struct rtc_time tm; + + ktime_get_real_ts64(&ts); + rtc_time64_to_tm(ts.tv_sec, &tm); + + switch (mode) { + case PM_SUSPEND_PREPARE: + ss_power_print("soc: suspend(%d-%02d-%02d %02d:%02d:%02d.%03lu)\n", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec); + cancel_delayed_work(&power_log_work); + break; + case PM_POST_SUSPEND: + schedule_delayed_work(&power_log_work, 0); + ss_power_print("soc: resume(%d-%02d-%02d %02d:%02d:%02d.%03lu)\n", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec); + break; + default: + break; + } + + return 0; +} + +static struct notifier_block sec_pm_log_nb = { + .notifier_call = sec_pm_log_notify, +}; + +static int __init sec_pm_log_init(void) +{ + int ret = 0; + + pr_info("%s\n", __func__); + + /* proc log */ + sec_pm_proc_log_init(); + + /* monitoring log */ + INIT_DELAYED_WORK(&power_log_work, power_log_print); + schedule_delayed_work(&power_log_work, 0); + pr_info("%s: monitor init done\n", __func__); + + /* pm notifier */ + ret = register_pm_notifier(&sec_pm_log_nb); + if (ret) + pr_err("sec_pm_log: pm notifier register fail(%d).\n", ret); + + return 0; +} + +static void __exit sec_pm_log_exit(void) +{ + remove_proc_subtree(POWER_LOG_NAME, procfs_pm_dir); + remove_proc_subtree(THERMAL_LOG_NAME, procfs_pm_dir); + remove_proc_subtree(PROC_PM_DIR, NULL); +} + +module_init(sec_pm_log_init); +module_exit(sec_pm_log_exit); + +MODULE_ALIAS("platform:sec_pm_log"); +MODULE_DESCRIPTION("Samsung Power/Thermal Log driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/sdp/Kconfig b/drivers/sdp/Kconfig new file mode 100644 index 000000000000..3c9cd83ffb9b --- /dev/null +++ b/drivers/sdp/Kconfig @@ -0,0 +1,26 @@ +# +# samsung display platform +# + +config SDP + tristate "Samsung Display Platform" + help + Samsung Display Platform. + +config ADAPTIVE_MIPI_V2_TEST_FOR_ON_DEVICE + tristate "KUnit test for adaptive_mipi_v2_test" + depends on KUNIT + depends on SDP + help + TODO: Describe config fully. + If you run this test driver on device, SHOULD set this config as 'm' to build test driver modulraly. + +config ADAPTIVE_MIPI_V2_TEST_FOR_ONLY_UML + tristate "KUnit test for adaptive_mipi_v2_test" + depends on KUNIT + depends on UML + depends on SDP + help + TODO: Describe config fully. + This CONFIG is recommended to set to y. + diff --git a/drivers/sdp/Makefile b/drivers/sdp/Makefile new file mode 100644 index 000000000000..7dbac1325acd --- /dev/null +++ b/drivers/sdp/Makefile @@ -0,0 +1,6 @@ + +obj-$(CONFIG_SDP) += sdp.o +sdp-y += adaptive_mipi_v2.o \ + sdp_debug.o + +GCOV_PROFILE_adaptive_mipi_v2.o := $(CONFIG_KUNIT) diff --git a/drivers/sdp/adaptive_mipi_v2.c b/drivers/sdp/adaptive_mipi_v2.c new file mode 100644 index 000000000000..12cd8b13a7bb --- /dev/null +++ b/drivers/sdp/adaptive_mipi_v2.c @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) Samsung Electronics Co., Ltd. + * + * 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. + */ + +#include +#include +#include +#include +#include + +static struct adaptive_mipi_v2_table_element *get_matched_table_element( + struct band_info *band_info, + struct adaptive_mipi_v2_table_element *table, + int table_size) +{ + struct adaptive_mipi_v2_table_element *element; + int i; + + for (i = 0; i < table_size; i++) { + element = &table[i]; + if (band_info->rat == element->rat && + band_info->band == element->band && + band_info->channel >= element->from_ch && + band_info->channel <= element->end_ch) + return element; + } + + return NULL; +} + +static int get_optimal_osc_clock(struct cp_info *cp_msg, + struct adaptive_mipi_v2_info *info) +{ + struct adaptive_mipi_v2_table_element *element; + struct band_info *band_msg; + int i; + + if (info->osc_table_size == 0) + return INV_OSC_CLK; + + for (i = 0; i < cp_msg->cell_count; i++) { + band_msg = &cp_msg->infos[i]; + element = get_matched_table_element(band_msg, + info->osc_table, + info->osc_table_size); + if (element) + return info->osc_clocks_khz[ALTERNATIVE_OSC_ID]; + } + + return info->osc_clocks_khz[DEFAULT_OSC_ID]; +} + +static int get_status_weight(int connection_status) +{ + if (connection_status == STATUS_PRIMARY_SERVING) + return WEIGHT_PRIMARY_SERVING; + else if (connection_status == STATUS_SECONDARY_SERVING) + return WEIGHT_SECONDARY_SERVING; + + sdp_err(NULL, "invalid connection_status: %d\n", + connection_status); + + return -EINVAL; +} + +static int get_signal_weight(int sinr) +{ + if (sinr == DEFAULT_WEAK_SIGNAL) + return WEIGHT_WEAK_SIGNAL; + + if (sinr < JUDGE_STRONG_SIGNAL) + return WEIGHT_WEAK_SIGNAL; + + return WEIGHT_STRONG_SIGNAL; +} + +static int update_ratings(int connection_status, int sinr, + int *ratings, int ratings_size) +{ + int status_weight = get_status_weight(connection_status); + int signal_weight = get_signal_weight(sinr); + int i; + + if (status_weight < 0 || signal_weight < 0) + return -EINVAL; + + for (i = 0; i < ratings_size; i++) + ratings[i] *= status_weight * signal_weight; + + return 0; +} + +static int get_band_id(u32 bandwidth_khz) +{ + return bandwidth_khz <= 10000 ? BANDWIDTH_10M_IDX : BANDWIDTH_20M_IDX; +} + +static int get_min_score_clock_id(int *total_score, int score_size) +{ + int min_id = 0; + int min_score = total_score[0]; + int i; + + for (i = 1; i < score_size; i++) { + if (min_score > total_score[i]) { + min_score = total_score[i]; + min_id = i; + } + } + + return min_id; +} + +static int get_optimal_mipi_clock(struct cp_info *cp_msg, + struct adaptive_mipi_v2_info *info) +{ + struct adaptive_mipi_v2_table_element *element; + struct band_info *band_msg; + int band_id; + int ratings[MAX_MIPI_FREQ_CNT] = {0, }; + int total_score[MAX_MIPI_FREQ_CNT] = {0, }; + int min_id; + int i, j; + int ret; + + sdp_info(info->ctx, "cell_count: %d\n", cp_msg->cell_count); + for (i = 0; i < cp_msg->cell_count; i++) { + band_msg = &cp_msg->infos[i]; + band_id = get_band_id(band_msg->bandwidth); + element = get_matched_table_element(band_msg, + info->mipi_table[band_id], + info->mipi_table_size[band_id]); + if (!element) + continue; + + memcpy(ratings, element->mipi_clocks_rating, sizeof(ratings)); + ret = update_ratings(band_msg->connection_status, band_msg->sinr, + ratings, info->mipi_clocks_size); + if (ret < 0) + continue; + + for (j = 0; j < info->mipi_clocks_size; j++) + total_score[j] += ratings[j]; + + sdp_info(info->ctx, "[%d] band_info: {%d,%d,%d,%d,%d,%d}, score: {%d,%d,%d,%d} \n", + i, band_msg->rat, band_msg->band, band_msg->channel, + band_msg->connection_status, band_msg->bandwidth, band_msg->sinr, + total_score[0], total_score[1], total_score[2], total_score[3]); + } + + min_id = get_min_score_clock_id(total_score, info->mipi_clocks_size); + + return info->mipi_clocks_kbps[min_id]; +} + +static int sdp_adaptive_mipi_v2_ril_notifier_callback(struct notifier_block *nb, + unsigned long size, void *buf) +{ + struct adaptive_mipi_v2_info *info = + container_of(nb, struct adaptive_mipi_v2_info, ril_nb); + struct dev_ril_bridge_msg *ril_msg = buf; + struct cp_info *cp_msg; + + int mipi_clk_kbps; + int osc_clk_khz; + + int ret; + + if (!nb || !ril_msg) { + sdp_err(NULL, "null arg\n"); + return NOTIFY_BAD; + } + + if (ril_msg->dev_id != IPC_SYSTEM_CP_ADAPTIVE_MIPI_INFO) { + sdp_dbg(info->ctx, "unmatched sub cmd: %d\n", ril_msg->dev_id); + return NOTIFY_DONE; + } + + cp_msg = (struct cp_info *)ril_msg->data; + + if (ril_msg->data_len < sizeof(cp_msg->cell_count)) { + return NOTIFY_BAD; + } + + if (cp_msg->cell_count > MAX_BAND || cp_msg->cell_count <= 0) { + sdp_err(info->ctx, "invalid cell_count (%d)\n", cp_msg->cell_count); + return NOTIFY_BAD; + } + + if (ril_msg->data_len < sizeof(cp_msg->cell_count) + cp_msg->cell_count * sizeof(*cp_msg->infos)) { + sdp_err(info->ctx, "invalid data_len (%d) - cell_count(%d)\n", ril_msg->data_len, cp_msg->cell_count); + return NOTIFY_BAD; + } + + mipi_clk_kbps = get_optimal_mipi_clock(cp_msg, info); + if (mipi_clk_kbps < 0) + return NOTIFY_DONE; + + osc_clk_khz = get_optimal_osc_clock(cp_msg, info); + + sdp_info(info->ctx, "mipi_clk_kbps: %d, osc_clk_khz: %d\n", mipi_clk_kbps, osc_clk_khz); + + ret = info->funcs->apply_freq(mipi_clk_kbps, osc_clk_khz, info->ctx); + if (ret < 0) { + sdp_err(info->ctx, "fail to set freq: ret=%d\n", ret); + return NOTIFY_DONE; + } + + return NOTIFY_DONE; +} + +static int validate_info(struct adaptive_mipi_v2_info *info) +{ + int i; + + if (!info) { + sdp_err(NULL, "null info\n"); + return -ENODEV; + } + + if (info->mipi_clocks_size == 0 || + info->mipi_clocks_size > MAX_MIPI_FREQ_CNT) { + sdp_err(info->ctx, "invalid mipi_clocks_size (%d)\n", + info->mipi_clocks_size); + return -EINVAL; + } + + if (!info->funcs || !info->funcs->apply_freq) { + sdp_err(info->ctx, "no callback\n"); + return -EINVAL; + } + + for (i = 0; i < MAX_BANDWIDTH_IDX; i++) { + if (!info->mipi_table[i]) { + sdp_err(info->ctx, "no mipi table[%d]\n", i); + return -EINVAL; + } + } + + return 0; +} + +int sdp_init_adaptive_mipi_v2(struct adaptive_mipi_v2_info *info) +{ + int ret; + + if (validate_info(info) < 0) + return -EINVAL; + + info->ril_nb.notifier_call = sdp_adaptive_mipi_v2_ril_notifier_callback; + ret = register_dev_ril_bridge_event_notifier(&info->ril_nb); + if (ret < 0) { + sdp_err(info->ctx, "failed to register ril notifier(%d)\n", ret); + return ret; + } + + sdp_info(info->ctx, "initialized sdp adaptive mipi v2\n"); + + return 0; +} +EXPORT_SYMBOL(sdp_init_adaptive_mipi_v2); + +int sdp_cleanup_adaptive_mipi_v2(struct adaptive_mipi_v2_info *info) +{ + int ret; + + if (!info) { + sdp_err(NULL, "null info\n"); + return -ENODEV; + } + + ret = unregister_dev_ril_bridge_event_notifier(&info->ril_nb); + if (ret < 0) { + sdp_err(info->ctx, "failed to unregister ril notifier(%d)\n", ret); + return ret; + } + + sdp_info(info->ctx, "cleanup sdp adaptive mipi v2\n"); + + return 0; +} +EXPORT_SYMBOL(sdp_cleanup_adaptive_mipi_v2); + +MODULE_DESCRIPTION("sdp adaptive mipi v2"); +MODULE_LICENSE("GPL"); diff --git a/drivers/sdp/sdp_debug.c b/drivers/sdp/sdp_debug.c new file mode 100644 index 000000000000..541b87f629b2 --- /dev/null +++ b/drivers/sdp/sdp_debug.c @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) Samsung Electronics Co., Ltd. + * + * 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. + */ + +#include +#include + +int __weak sdp_get_disp_id(void *ctx) +{ + return UNKNOWN_DISP_ID; +} +EXPORT_SYMBOL(sdp_get_disp_id); + +MODULE_DESCRIPTION("sdp debug"); +MODULE_LICENSE("GPL"); diff --git a/drivers/sec_panel_notifier_v2/Kconfig b/drivers/sec_panel_notifier_v2/Kconfig new file mode 100644 index 000000000000..2119b69d97ca --- /dev/null +++ b/drivers/sec_panel_notifier_v2/Kconfig @@ -0,0 +1,26 @@ +# +# samsung panel notifier +# + +config SEC_PANEL_NOTIFIER_V2 + tristate "samsung panel notifier" + help + Samsung panel notifier. + +config SEC_PANEL_NOTIFIER_V2_TEST_FOR_ON_DEVICE + tristate "KUnit test for sec_panel_notifier_v2_test" + depends on KUNIT + depends on SEC_PANEL_NOTIFIER_V2 + help + TODO: Describe config fully. + If you run this test driver on device, SHOULD set this config as 'm' to build test driver modulraly. + +config SEC_PANEL_NOTIFIER_V2_TEST_FOR_ONLY_UML + tristate "KUnit test for sec_panel_notifier_v2_test" + depends on KUNIT + depends on UML + depends on SEC_PANEL_NOTIFIER_V2 + help + TODO: Describe config fully. + This CONFIG is recommended to set to y. + diff --git a/drivers/sec_panel_notifier_v2/Makefile b/drivers/sec_panel_notifier_v2/Makefile new file mode 100644 index 000000000000..7e80e3581c49 --- /dev/null +++ b/drivers/sec_panel_notifier_v2/Makefile @@ -0,0 +1,4 @@ + +obj-$(CONFIG_SEC_PANEL_NOTIFIER_V2) += sec_panel_notifier_v2.o + +GCOV_PROFILE_sec_panel_notifier_v2.o := $(CONFIG_KUNIT) diff --git a/drivers/sec_panel_notifier_v2/sec_panel_notifier_v2.c b/drivers/sec_panel_notifier_v2/sec_panel_notifier_v2.c new file mode 100644 index 000000000000..679d9d2557d8 --- /dev/null +++ b/drivers/sec_panel_notifier_v2/sec_panel_notifier_v2.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) Samsung Electronics Co., Ltd. + * + * 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. + */ + +#include +#include +#include +#include + +#if IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) + +static BLOCKING_NOTIFIER_HEAD(panel_notifier_list); + +/** + * panel_notifier_register - register a client notifier + * @nb: notifier block to callback on events + */ +int panel_notifier_register(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&panel_notifier_list, nb); +} +EXPORT_SYMBOL(panel_notifier_register); + +/** + * panel_notifier_unregister - unregister a client notifier + * @nb: notifier block to callback on events + */ +int panel_notifier_unregister(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&panel_notifier_list, nb); +} +EXPORT_SYMBOL(panel_notifier_unregister); + +/** + * valid_panel_notifier_event - validate panel notifier event + * @event: panel notifier event + * @ev_data : panel notifier event data + */ +static bool valid_panel_notifier_event(unsigned long event, + struct panel_notifier_event_data *ev_data) +{ + if (event >= MAX_PANEL_EVENT) { + pr_err("invalid panel event(event:%lu)\n", event); + return false; + } + + if (!ev_data) { + pr_err("panel event data is null(event:%lu)\n", event); + return false; + } + + if (event == PANEL_EVENT_PANEL_STATE_CHANGED || + event == PANEL_EVENT_UB_CON_STATE_CHANGED || + event == PANEL_EVENT_COPR_STATE_CHANGED || + event == PANEL_EVENT_TEST_MODE_STATE_CHANGED) { + if (ev_data->state == PANEL_EVENT_STATE_NONE) { + pr_err("panel event state is none(event:%lu)\n", event); + return false; + } + } + + return true; +} + +/** + * panel_notifier_call_chain - notify clients + * + */ +int panel_notifier_call_chain(unsigned long val, void *v) +{ + if (!valid_panel_notifier_event(val, v)) + return -EINVAL; + + return blocking_notifier_call_chain(&panel_notifier_list, val, v); +} +EXPORT_SYMBOL(panel_notifier_call_chain); + +#endif + +MODULE_DESCRIPTION("Samsung panel notifier"); +MODULE_LICENSE("GPL"); diff --git a/drivers/secdp/Kconfig b/drivers/secdp/Kconfig new file mode 100644 index 000000000000..59b6e689533b --- /dev/null +++ b/drivers/secdp/Kconfig @@ -0,0 +1,5 @@ +config SEC_DISPLAYPORT + tristate "SEC DISPLAYPORT feature" + default n + help + Samsung specific displayport changes. diff --git a/drivers/sensors/Kconfig b/drivers/sensors/Kconfig new file mode 100755 index 000000000000..b2ad9233706b --- /dev/null +++ b/drivers/sensors/Kconfig @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config SENSORS + tristate "Sensors Class Support" + help + This option enables the sensor sysfs class in /sys/class/sensors. + You'll need this to do anything useful with sensors. If unsure, say N. diff --git a/drivers/sensors/Makefile b/drivers/sensors/Makefile new file mode 100755 index 000000000000..bf62ccb016fd --- /dev/null +++ b/drivers/sensors/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SENSORS) += sensors_core.o diff --git a/drivers/sensors/sensors_core.c b/drivers/sensors/sensors_core.c new file mode 100755 index 000000000000..936f46cd468d --- /dev/null +++ b/drivers/sensors/sensors_core.c @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2013 Samsung Electronics. 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 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct class *sensors_class; +struct class *sensors_event_class; +static struct device *symlink_dev; +static struct device *sensor_dev; +static struct input_dev *meta_input_dev; + +static atomic_t sensor_count; + +int sensors_create_symlink(struct kobject *target, const char *name) +{ + int err = 0; + + if (symlink_dev == NULL) { + pr_err("%s, symlink_dev is NULL!!!\n", __func__); + return err; + } + + err = sysfs_create_link(&symlink_dev->kobj, target, name); + if (err < 0) { + pr_err("%s, %s failed!(%d)\n", __func__, name, err); + return err; + } + + return err; +} +EXPORT_SYMBOL_GPL(sensors_create_symlink); + +void sensors_remove_symlink(struct kobject *target, const char *name) +{ + if (symlink_dev == NULL) + pr_err("%s, symlink_dev is NULL!!!\n", __func__); + else + sysfs_remove_link(&symlink_dev->kobj, name); +} +EXPORT_SYMBOL_GPL(sensors_remove_symlink); + +static ssize_t set_flush(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + int64_t dTemp; + u8 sensor_type = 0; + + if (kstrtoll(buf, 10, &dTemp) < 0) + return -EINVAL; + + sensor_type = (u8)dTemp; + + input_report_rel(meta_input_dev, REL_DIAL, + 1); /*META_DATA_FLUSH_COMPLETE*/ + input_report_rel(meta_input_dev, REL_HWHEEL, sensor_type + 1); + input_sync(meta_input_dev); + + pr_info("[SENSOR] flush %d\n", sensor_type); + return size; +} + +static DEVICE_ATTR(flush, S_IWUSR | S_IWGRP, NULL, set_flush); + +static struct device_attribute *sensor_attr[] = { + &dev_attr_flush, + NULL, +}; + +int sensors_register(struct device **dev, void *drvdata, + struct device_attribute *attributes[], char *name) +{ + int ret = 0; + int i; + + if (sensors_class == NULL) { + sensors_class = class_create(THIS_MODULE, "sensors"); + if (IS_ERR(sensors_class)) + return PTR_ERR(sensors_class); + } + + *dev = device_create(sensors_class, NULL, 0, drvdata, "%s", name); + if (IS_ERR(*dev)) { + ret = PTR_ERR(*dev); + pr_err("[SENSORS CORE] device_create failed![%d]\n", ret); + return ret; + } + + for (i = 0; attributes[i] != NULL; i++) + if ((device_create_file(*dev, attributes[i])) < 0) + pr_err("[SENSOR CORE] fail device_create_file"\ + "(dev, attributes[%d])\n", i); + atomic_inc(&sensor_count); + + return ret; +} +EXPORT_SYMBOL_GPL(sensors_register); + +void sensors_unregister(struct device *dev, + struct device_attribute *attributes[]) +{ + int i; + + for (i = 0; attributes[i] != NULL; i++) + device_remove_file(dev, attributes[i]); +} +EXPORT_SYMBOL_GPL(sensors_unregister); + +void destroy_sensor_class(void) +{ + if (sensors_class) { + device_destroy(sensors_class, sensor_dev->devt); + class_destroy(sensors_class); + sensor_dev = NULL; + sensors_class = NULL; + } + + if (sensors_event_class) { + device_destroy(sensors_event_class, symlink_dev->devt); + class_destroy(sensors_event_class); + symlink_dev = NULL; + sensors_event_class = NULL; + } +} + +int sensors_input_init(void) +{ + int ret; + + /* Meta Input Event Initialization */ + meta_input_dev = input_allocate_device(); + if (!meta_input_dev) { + pr_err("[SENSOR CORE] failed alloc meta dev\n"); + return -ENOMEM; + } + + meta_input_dev->name = "meta_event"; + input_set_capability(meta_input_dev, EV_REL, REL_HWHEEL); + input_set_capability(meta_input_dev, EV_REL, REL_DIAL); + + ret = input_register_device(meta_input_dev); + if (ret < 0) { + pr_err("[SENSOR CORE] failed register meta dev\n"); + input_free_device(meta_input_dev); + return ret; + } + + ret = sensors_create_symlink(&meta_input_dev->dev.kobj, + meta_input_dev->name); + if (ret < 0) { + pr_err("[SENSOR CORE] failed create meta symlink\n"); + input_unregister_device(meta_input_dev); + input_free_device(meta_input_dev); + return ret; + } + + return ret; +} + +void sensors_input_clean(void) +{ + sensors_remove_symlink(&meta_input_dev->dev.kobj, + meta_input_dev->name); + input_unregister_device(meta_input_dev); + input_free_device(meta_input_dev); +} + +static int __init sensors_class_init(void) +{ + pr_info("[SENSORS CORE] sensors_class_init\n"); + + sensors_class = class_create(THIS_MODULE, "sensors"); + if (IS_ERR(sensors_class)) { + pr_err("%s, create sensors_class is failed.(err=%d)\n", + __func__, IS_ERR(sensors_class)); + return PTR_ERR(sensors_class); + } + + /* For flush sysfs */ + sensor_dev = device_create(sensors_class, NULL, 0, NULL, + "%s", "sensor_dev"); + if (IS_ERR(sensor_dev)) { + pr_err("[SENSORS CORE] sensor_dev create failed![%d]\n", + IS_ERR(sensor_dev)); + + class_destroy(sensors_class); + return PTR_ERR(sensor_dev); + } else { + if ((device_create_file(sensor_dev, *sensor_attr)) < 0) + pr_err("[SENSOR CORE] failed flush device_file\n"); + } + + /* For symbolic link */ + sensors_event_class = class_create(THIS_MODULE, "sensor_event"); + if (IS_ERR(sensors_event_class)) { + pr_err("%s, create sensors_class is failed.(err=%d)\n", + __func__, IS_ERR(sensors_event_class)); + class_destroy(sensors_class); + return PTR_ERR(sensors_event_class); + } + + symlink_dev = device_create(sensors_event_class, NULL, 0, NULL, + "%s", "symlink"); + if (IS_ERR(symlink_dev)) { + pr_err("[SENSORS CORE] symlink_dev create failed![%d]\n", + IS_ERR(symlink_dev)); + + class_destroy(sensors_class); + class_destroy(sensors_event_class); + return PTR_ERR(symlink_dev); + } + + atomic_set(&sensor_count, 0); + sensors_class->dev_uevent = NULL; + + sensors_input_init(); + + return 0; +} + +static void __exit sensors_class_exit(void) +{ + if (meta_input_dev) + sensors_input_clean(); + + if (sensors_class || sensors_event_class) { + class_destroy(sensors_class); + sensors_class = NULL; + class_destroy(sensors_event_class); + sensors_event_class = NULL; + } +} + +subsys_initcall(sensors_class_init); +module_exit(sensors_class_exit); +MODULE_DESCRIPTION("Universal sensors core class"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Samsung Electronics"); diff --git a/drivers/sensors/vl53l8/Kconfig b/drivers/sensors/vl53l8/Kconfig new file mode 100644 index 000000000000..fe7b8f8c3685 --- /dev/null +++ b/drivers/sensors/vl53l8/Kconfig @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: GPL-2.0-only + + +config SENSORS_VL53L8 + depends on SPI + tristate "VL53L8 driver" + default n + help + Say Y here if you use VL53L5. + This option enables range sensor using VL53L8. + +config SENSORS_VL53L8_SUPPORT_UAPI + tristate "STM VL53L8 ranging sensor UPAI interface" + default n + help + STM VL53L8 ranging sensor UPAI interface. + +config SENSORS_VL53L8_SUPPORT_KERNEL_INTERFACE + tristate "STM VL53L8 ranging sensor Kernel Interface" + default n + help + STM VL53L8 ranging sensor Kernel Interface. + +config SENSORS_VL53L8_QCOM + tristate "VL53L8 ranging sensor feature for Snapdragon" + default n + help + VL53L8 ranging sensor feature for Snapdragon AP. + +config SENSORS_VL53L8_SLSI + tristate "VL53L8 ranging sensor feature for Exynos" + default n + help + VL53L8 ranging sensor feature for Exynos AP. + +config SEPARATE_IO_CORE_POWER + tristate "VL53L8 ranging sensor feature for IO/CORE Power source" + default n + help + This option enables IO and CORE power separation. + +config SENSORS_LAF_FAILURE_DEBUG + tristate "VL53L8 error code collection" + default n + help + This option enables error code collection. + +config SENSORS_VL53L8_SUPPORT_RESUME_WORK + tristate "VL53L8 work func to reduce resume time" + default n + help + VL53L8 ranging sensor feature to reduce resume time. \ No newline at end of file diff --git a/drivers/sensors/vl53l8/Makefile b/drivers/sensors/vl53l8/Makefile new file mode 100644 index 000000000000..bef167aecaf8 --- /dev/null +++ b/drivers/sensors/vl53l8/Makefile @@ -0,0 +1,245 @@ +################################################################################ +# Copyright (c) 2022, STMicroelectronics - All Rights Reserved +# +# This file is part of VL53L8 Kernel Driver and is dual licensed, +# either 'STMicroelectronics Proprietary license' +# or 'BSD 3-clause "New" or "Revised" License' , at your option. +# +################################################################################ +# +# 'STMicroelectronics Proprietary license' +# +################################################################################ +# +# License terms: STMicroelectronics Proprietary in accordance with licensing +# terms at www.st.com/sla0081 +# +# STMicroelectronics confidential +# Reproduction and Communication of this document is strictly prohibited unless +# specifically authorized in writing by STMicroelectronics. +# +# +################################################################################ +# +# Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +# 'BSD 3-clause "New" or "Revised" License', in which case the following +# provisions apply instead of the ones mentioned above : +# +################################################################################ +# +# License terms: BSD 3-clause "New" or "Revised" License. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 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. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# 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 HOLDER 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. +# +# +###############################################################################/ + +# Parameters +MODULE := stmvl53l8 +CONFIG_STMVL53L8 := $(CONFIG_SENSORS_VL53L8) + +# Configure the build type : RELEASE, DEBUG +BUILD_TYPE = RELEASE +#BUILD_TYPE = DEBUG + +# Sets the servicing mode to use i.e. INTERRUPT or DEFAULT +RANGE_SERVICING = INTERRUPT + + +# Set to TRUE to enable TCDM dump +TCDM_DUMP_ENABLE = FALSE + +# Set to TRUE to enable rad2perp calibration +RAD2PERP_CAL_ENABLE = FALSE + +# Set to TRUE to enable code for internal testing +LEGACY_CODE = FALSE + +# Set to TRUE to enable logging functions. +LOG_ENABLE = FALSE + +# Set to TRUE to enable force error. +FORCE_ERROR = FALSE + +# Set to TRUE to enable legacy results data. Only use if legacy config's available + +LEGACY_RESULTS_DATA = FALSE + +# Set to TRUE to enable legacy results data. Only use if legacy config's available +EXTENDED_RESULTS_DATA = TRUE + +KDIR = /lib/modules/$(shell uname -r)/build + +ccflags-y += -I$(srctree)/drivers/sensors/vl53l8/inc +ccflags-y += -I$(srctree)/drivers/sensors/vl53l8/bare_driver/common/inc +ccflags-y += -I$(srctree)/drivers/sensors/vl53l8/bare_driver/api/inc +ccflags-y += -I$(srctree)/drivers/sensors/vl53l8/bare_driver/dci/inc +ccflags-y += -I$(srctree)/drivers/sensors/vl53l8/platform/inc + +ccflags-y += -Wall -Werror -Wno-missing-braces + +KERNEL_DRIVER_OBJS = \ + src/vl53l8_k_module.o \ + src/vl53l8_k_ioctl_controls.o \ + src/vl53l8_k_gpio_utils.o \ + src/vl53l8_k_error_converter.o \ + src/vl53l8_k_range_wait_handler.o \ + src/vl53l8_k_glare_filter.o + +PLATFORM_OBJS = \ + platform/src/vl53l5_platform.o \ + platform/src/vl53l5_platform_init.o \ + platform/src/vl53l5_platform_maths.o \ + platform/src/vl53l5_platform_log.o + +BARE_DRIVER_OBJS = \ + bare_driver/api/src/vl53l5_api_core.o \ + bare_driver/api/src/vl53l5_api_power.o \ + bare_driver/api/src/vl53l5_api_ranging.o \ + bare_driver/api/src/vl53l5_api_range_decode.o \ + bare_driver/api/src/vl53l5_api_calibration_decode.o\ + bare_driver/dci/src/vl53l5_core_decode.o\ + bare_driver/dci/src/vl53l5_dci_core.o \ + bare_driver/dci/src/vl53l5_dci_decode.o \ + bare_driver/dci/src/vl53l5_dci_helpers.o \ + bare_driver/dci/src/vl53l5_dci_ranging.o \ + bare_driver/dci/src/vl53l5_dci_utils.o \ + bare_driver/dci/src/vl53l5_decode_switch.o\ + bare_driver/dci/src/vl53l5_calibration_decode.o \ + bare_driver/dci/src/page_map_switch.o \ + bare_driver/common/src/vl53l5_checks.o \ + bare_driver/common/src/vl53l5_commands.o \ + bare_driver/common/src/vl53l5_error_handler.o \ + bare_driver/common/src/vl53l5_load_firmware.o \ + bare_driver/common/src/vl53l5_register_utils.o \ + bare_driver/common/src/vl53l5_rom_boot.o + +ifeq "$(BUILD_TYPE)" "DEBUG" + $(warning BUILD_TYPE=$(BUILD_TYPE)) + ccflags-y += -DDEBUG -DVL53L5_LOG_ENABLE +endif + +ifeq "$(BUILD_TYPE)" "RELEASE" + $(warning BUILD_TYPE=$(BUILD_TYPE)) +endif + +ifeq "$(RANGE_SERVICING)" "INTERRUPT" + $(warning RANGE_SERVICING=$(RANGE_SERVICING)) + ccflags-y += -DVL53L8_INTERRUPT + KERNEL_DRIVER_OBJS += src/vl53l8_k_interrupt.o +endif + +ifeq "$(LOG_ENABLE)" "TRUE" + $(warning LOG_ENABLE=$(LOG_ENABLE)) + ccflags-y += -DVL53L8_KERNEL_LOG +endif + +ifeq "$(TCDM_DUMP_ENABLE)" "TRUE" + $(warning TCDM_DUMP_ENABLE=$(TCDM_DUMP_ENABLE)) + ccflags-y += -DVL53L5_TCDM_DUMP + BARE_DRIVER_OBJS += bare_driver/common/src/vl53l5_tcdm_dump.o +endif + +ifeq "$(LEGACY_CODE)" "TRUE" + $(warning LEGACY_CODE=$(LEGACY_CODE)) + ccflags-y += -DSTM_VL53L8_SUPPORT_LEGACY_CODE +endif + +ifeq "$(FORCE_ERROR)" "TRUE" + $(warning FORCE_ERROR=$(FORCE_ERROR)) + ccflags-y += -DVL53L8_FORCE_ERROR_COMMAND +endif + +ifeq "$(RAD2PERP_CAL_ENABLE)" "TRUE" + $(warning RAD2PERP_CAL_ENABLE=$(RAD2PERP_CAL_ENABLE)) + ccflags-y += -DVL53L8_RAD2PERP_CAL +endif + +ifeq "$(LEGACY_RESULTS_DATA)" "TRUE" + $(warning LEGACY_RESULTS_DATA=$(LEGACY_RESULTS_DATA)) + BARE_DRIVER_OBJS += \ + bare_driver/dci/src/vl53l5_results_decode.o + ccflags-y += -I$(srctree)/drivers/sensors/vl53l8/bare_driver/results/inc + ccflags-y += -DVL53L5_RESULTS_DATA_ENABLED +endif + +ifeq "$(EXTENDED_RESULTS_DATA)" "TRUE" + $(warning EXTENDED_RESULTS_DATA=$(EXTENDED_RESULTS_DATA)) + BARE_DRIVER_OBJS += \ + bare_driver/patch/src/vl53l5_patch_api_core.o \ + bare_driver/patch/src/vl53l5_patch_core_decode.o \ + bare_driver/patch/src/vl53l5_patch_decode_switch.o \ + bare_driver/patch/src/vl53l5_tcpm_patch_0_core_decode.o \ + bare_driver/patch/src/vl53l5_tcpm_patch_0_decode_switch.o \ + bare_driver/patch/src/vl53l5_tcpm_patch_0_results_decode.o \ + bare_driver/patch/src/vl53l5_tcpm_patch_1_core_decode.o \ + bare_driver/patch/src/vl53l5_tcpm_patch_1_decode_switch.o + ccflags-y += -I$(srctree)/drivers/sensors/vl53l8/bare_driver/patch/inc + ccflags-y += -DVL53L5_PATCH_DATA_ENABLED +endif + +obj-$(CONFIG_STMVL53L8) += $(MODULE).o + +# # Kernel level objects +$(MODULE)-objs += $(KERNEL_DRIVER_OBJS) +$(MODULE)-objs += $(BARE_DRIVER_OBJS) +$(MODULE)-objs += $(PLATFORM_OBJS) + +all: + make -C $(KDIR) M=$(PWD) modules + +clean: + make -C $(KDIR) M=$(PWD) clean + +# Insert the module using insmod with the configured module parameters +insert: + sudo insmod $(MODULE).ko + +dtb: + -sudo dtoverlay -r $(MODULE) + -sudo rm -f /boot/overlays/$(MODULE).dtbo + dtc -@ -I dts -O dtb -o $(MODULE)_spi.dtbo $(MODULE)_spi.dts + sudo cp $(MODULE)_spi.dtbo /boot/overlays/$(MODULE).dtbo + sudo dtoverlay $(MODULE) + +dtb_gpio: + -sudo dtoverlay -r $(MODULE) + -sudo rm -f /boot/overlays/$(MODULE).dtbo + dtc -@ -I dts -O dtb -o $(MODULE)_spi_gpio.dtbo $(MODULE)_spi_gpio.dts + sudo cp $(MODULE)_spi_gpio.dtbo /boot/overlays/$(MODULE).dtbo + sudo dtoverlay $(MODULE) + +clean_dtb: + sudo rm -f $(MODULE).dtbo + sudo rm -f /boot/overlays/$(MODULE).dtbo + sudo dtoverlay -r $(MODULE) + +# Remove the module using rmmod +remove: + -sudo rmmod $(MODULE) diff --git a/drivers/sensors/vl53l8/bare_driver/api/inc/vl53l5_api_calibration_decode.h b/drivers/sensors/vl53l8/bare_driver/api/inc/vl53l5_api_calibration_decode.h new file mode 100644 index 000000000000..b288c54540c3 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/api/inc/vl53l5_api_calibration_decode.h @@ -0,0 +1,84 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 VL53L5_API_CALIBRATION_DECODE_H_ +#define VL53L5_API_CALIBRATION_DECODE_H_ + +#include "vl53l5_platform_user_data.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifdef VL53L5_CALIBRATION_DECODE_ON + +int32_t vl53l5_decode_calibration_data( + struct vl53l5_dev_handle_t *p_dev, + struct vl53l5_calibration_data_t *p_data, + uint8_t *buffer, + uint32_t data_size); + +#endif +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/api/inc/vl53l5_api_core.h b/drivers/sensors/vl53l8/bare_driver/api/inc/vl53l5_api_core.h new file mode 100644 index 000000000000..05bea6a35863 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/api/inc/vl53l5_api_core.h @@ -0,0 +1,170 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 VL53L5_API_CORE_H_ +#define VL53L5_API_CORE_H_ + +#include "vl53l5_platform_user_data.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define VL53L5_ASSIGN_FW_BUFF(p_dev, fw_buff_ptr, count) \ +do {\ + (p_dev)->host_dev.p_fw_buff = (fw_buff_ptr);\ + (p_dev)->host_dev.fw_buff_count = (count);\ +} while (0) + +#define VL53L5_ASSIGN_COMMS_BUFF(p_dev, comms_buff_ptr, max_count) \ +do {\ + (p_dev)->host_dev.p_comms_buff = (comms_buff_ptr);\ + (p_dev)->host_dev.comms_buff_max_count = (max_count);\ +} while (0) + +#define VL53L5_FGC_STRING_LENGTH 19 + +struct vl53l5_version_t { + struct { + + uint16_t ver_major; + + uint16_t ver_minor; + + uint16_t ver_build; + + uint16_t ver_revision; + } driver; + struct { + + uint16_t ver_major; + + uint16_t ver_minor; + + uint16_t ver_build; + + uint16_t ver_revision; + } firmware; +}; + +struct vl53l5_module_info_t { + + uint8_t fgc[VL53L5_FGC_STRING_LENGTH]; + + uint32_t module_id_lo; + + uint32_t module_id_hi; +}; + +struct vl53l5_ranging_mode_flags_t { + + uint8_t timed_interrupt : 1; + + uint8_t timed_retention : 1; +}; + +int32_t vl53l5_init(struct vl53l5_dev_handle_t *p_dev); + +int32_t vl53l5_term(struct vl53l5_dev_handle_t *p_dev); + +int32_t vl53l5_get_version( + struct vl53l5_dev_handle_t *p_dev, + struct vl53l5_version_t *p_version); + +int32_t vl53l5_get_module_info( + struct vl53l5_dev_handle_t *p_dev, + struct vl53l5_module_info_t *p_module_info); + +int32_t vl53l5_read_device_error( + struct vl53l5_dev_handle_t *p_dev, + int32_t latest_status); + +int32_t vl53l5_start( + struct vl53l5_dev_handle_t *p_dev, + struct vl53l5_ranging_mode_flags_t *p_flags); + +int32_t vl53l5_stop( + struct vl53l5_dev_handle_t *p_dev, + struct vl53l5_ranging_mode_flags_t *p_flags); + +int32_t vl53l5_get_device_parameters( + struct vl53l5_dev_handle_t *p_dev, + uint32_t *p_block_headers, + uint32_t num_block_headers); + +int32_t vl53l5_set_device_parameters( + struct vl53l5_dev_handle_t *p_dev, + uint8_t *p_buff, + uint32_t buff_count); + +int32_t vl53l5_set_ranging_rate_hz( + struct vl53l5_dev_handle_t *p_dev, + uint32_t ranging_rate_hz); + +int32_t vl53l5_set_integration_time_us( + struct vl53l5_dev_handle_t *p_dev, + uint32_t integration_time_us); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/api/inc/vl53l5_api_power.h b/drivers/sensors/vl53l8/bare_driver/api/inc/vl53l5_api_power.h new file mode 100644 index 000000000000..3f73cba39de4 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/api/inc/vl53l5_api_power.h @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 VL53L5_API_POWER_H_ +#define VL53L5_API_POWER_H_ + +#include "vl53l5_platform_user_data.h" +#include "vl53l5_power_states.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +int32_t vl53l5_set_power_mode( + struct vl53l5_dev_handle_t *p_dev, + enum vl53l5_power_states power_mode); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/api/inc/vl53l5_api_range_decode.h b/drivers/sensors/vl53l8/bare_driver/api/inc/vl53l5_api_range_decode.h new file mode 100644 index 000000000000..c49be4f97f46 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/api/inc/vl53l5_api_range_decode.h @@ -0,0 +1,98 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 VL53L5_API_RANGE_DECODE_H_ +#define VL53L5_API_RANGE_DECODE_H_ + +#include "vl53l5_platform_user_data.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" +{ +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +struct vl53l8_update_data_t { + int pass_fail; + int cmd; +}; + +struct vl53l8_cal_data_t { + char pcal_data[1800]; + int size; + int cmd; +}; + +struct vl53l8_file_list_t { + int file_list; +}; +#endif + +#if defined(VL53L5_RESULTS_DATA_ENABLED) || defined(VL53L5_PATCH_DATA_ENABLED) + +int32_t vl53l5_decode_range_data( + struct vl53l5_dev_handle_t *p_dev, + struct vl53l5_range_data_t *p_data); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/api/inc/vl53l5_api_ranging.h b/drivers/sensors/vl53l8/bare_driver/api/inc/vl53l5_api_ranging.h new file mode 100644 index 000000000000..a130fd971d74 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/api/inc/vl53l5_api_ranging.h @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 VL53L5_API_RANGING_H_ +#define VL53L5_API_RANGING_H_ + +#include "vl53l5_platform_user_data.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +int32_t vl53l5_get_range_data( + struct vl53l5_dev_handle_t *p_dev); + +int32_t vl53l5_check_data_ready(struct vl53l5_dev_handle_t *p_dev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/api/src/vl53l5_api_calibration_decode.c b/drivers/sensors/vl53l8/bare_driver/api/src/vl53l5_api_calibration_decode.c new file mode 100644 index 000000000000..2dbdbf7c893a --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/api/src/vl53l5_api_calibration_decode.c @@ -0,0 +1,117 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_api_calibration_decode.h" +#ifdef VL53L5_CALIBRATION_DECODE_ON +#include "vl53l5_globals.h" +#include "vl53l5_dci_decode.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform_log.h" + +#define trace_print(level, ...) \ + _LOG_TRACE_PRINT(VL53L5_TRACE_MODULE_API, \ + level, VL53L5_TRACE_FUNCTION_ALL, ##__VA_ARGS__) +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START(VL53L5_TRACE_MODULE_API, fmt, ##__VA_ARGS__) +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END(VL53L5_TRACE_MODULE_API, status, ##__VA_ARGS__) +#define LOG_FUNCTION_END_FLUSH(status, ...) \ + do { \ + _LOG_FUNCTION_END(VL53L5_TRACE_MODULE_API, status, ##__VA_ARGS__);\ + _FLUSH_TRACE_TO_OUTPUT();\ + } while (0) + +int32_t vl53l5_decode_calibration_data( + struct vl53l5_dev_handle_t *p_dev, + struct vl53l5_calibration_data_t *p_data, + uint8_t *buffer, + uint32_t data_size) +{ + int32_t status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_ISNULL(p_data)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + status = VL53L5_GET_VERSION_CHECK_STATUS(p_dev); + + if (status < STATUS_OK) + goto exit; + + p_dev->host_dev.pcalibration_dev = &p_data->core; + + status = vl53l5_dci_decode_data(p_dev, buffer, data_size); + +exit: + if (!VL53L5_ISNULL(p_dev)) + + p_dev->host_dev.pcalibration_dev = NULL; + + LOG_FUNCTION_END_FLUSH(status); + return status; +} +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/api/src/vl53l5_api_core.c b/drivers/sensors/vl53l8/bare_driver/api/src/vl53l5_api_core.c new file mode 100644 index 000000000000..f4f0dad93235 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/api/src/vl53l5_api_core.c @@ -0,0 +1,1207 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_api_core.h" +#include "vl53l5_dci_core.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_version.h" +#include "vl53l5_trans.h" +#include "vl53l5_rom_boot.h" +#include "vl53l5_register_utils.h" +#include "vl53l5_error_handler.h" +#include "vl53l5_load_firmware.h" +#include "vl53l5_commands.h" +#include "vl53l5_checks.h" +#include "vl53l5_globals.h" +#include "vl53l5_platform_log.h" +#include "vl53l5_core_map_bh.h" +#include "dci_structs.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform_user_config.h" +#include "vl53l5_api_power.h" + +#define trace_print(level, ...) \ + _LOG_TRACE_PRINT(VL53L5_TRACE_MODULE_DCI, \ + level, VL53L5_TRACE_FUNCTION_ALL, ##__VA_ARGS__) + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START(VL53L5_TRACE_MODULE_DCI, fmt, ##__VA_ARGS__) +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END(VL53L5_TRACE_MODULE_DCI, status, ##__VA_ARGS__) +#define LOG_FUNCTION_END_FLUSH(status, ...) \ + do { \ + _LOG_FUNCTION_END(VL53L5_TRACE_MODULE_DCI, status, ##__VA_ARGS__);\ + _FLUSH_TRACE_TO_OUTPUT();\ + } while (0) + +#ifdef VL53L5_LOG_ENABLE + +union _driver_build_t { + uint32_t bytes; + struct { + uint32_t build_type : 2; + uint32_t vl53l5_meta_data_on : 1; + uint32_t vl53l5_common_data_on : 1; + uint32_t vl53l5_rng_timing_data_on : 1; + uint32_t vl53l5_amb_rate_kcps_per_spad_on : 1; + uint32_t vl53l5_effective_spad_count_on : 1; + uint32_t vl53l5_amb_dmax_mm_on : 1; + uint32_t vl53l5_silicon_temp_degc_start_on : 1; + uint32_t vl53l5_silicon_temp_degc_end_on : 1; + uint32_t vl53l5_no_of_targets_on : 1; + uint32_t vl53l5_zone_id_on : 1; + uint32_t vl53l5_sequence_idx_on : 1; + uint32_t vl53l5_peak_rate_kcps_per_spad_on : 1; + uint32_t vl53l5_median_phase_on : 1; + uint32_t vl53l5_rate_sigma_kcps_per_spad_on : 1; + uint32_t vl53l5_range_sigma_mm_on : 1; + uint32_t vl53l5_median_range_mm_on : 1; + uint32_t vl53l5_min_range_delta_mm_on : 1; + uint32_t vl53l5_max_range_delta_mm_on : 1; + uint32_t vl53l5_target_reflectance_est_pc_on : 1; + uint32_t vl53l5_target_status_on : 1; + uint32_t vl53l5_ref_timing_data_on : 1; + uint32_t vl53l5_ref_channel_data_on : 1; + uint32_t vl53l5_ref_target_data_on : 1; + uint32_t vl53l5_zone_thresh_status_bytes_on : 1; + uint32_t vl53l5_dyn_xtalk_op_persistent_data_on : 1; + }; +}; +#endif + +#define HW_TRAP_ERROR_GROUP 0xf +#define UI_PAGE 2 + +#define TIMED_RETENTION_OR_INTERRUPT(p_flags) \ + ((p_flags)->timed_retention || (p_flags)->timed_interrupt) + +#define TIMED_RETENTION_AND_INTERRUPT(p_flags) \ + ((p_flags)->timed_retention && (p_flags)->timed_interrupt) + +#define ASSIGN_STATUS_IF_NOT_ERROR(result, status) \ +do {\ + if (status != STATUS_OK)\ + (void)result;\ + else\ + status = result;\ +} while (0) + +static int32_t _check_flags_consistent( + struct vl53l5_ranging_mode_flags_t *p_flags) +{ + int32_t status = VL53L5_ERROR_NONE; + + if (TIMED_RETENTION_AND_INTERRUPT(p_flags)) + status = VL53L5_ERROR_INVALID_PARAMS; + + return status; +} + +static void _get_driver_version(struct vl53l5_version_t *p_version) +{ + p_version->driver.ver_major = VL53L5_VERSION_MAJOR; + p_version->driver.ver_minor = VL53L5_VERSION_MINOR; + p_version->driver.ver_build = VL53L5_VERSION_BUILD; + p_version->driver.ver_revision = VL53L5_VERSION_REVISION; +} + +#ifdef VL53L5_LOG_ENABLE + +static uint32_t _get_driver_build_details(void) +{ + union _driver_build_t db = {0}; + + db.build_type = 0; +#ifdef VL53L5_META_DATA_ON + db.vl53l5_meta_data_on = 1; +#endif +#ifdef VL53L5_COMMON_DATA_ON + db.vl53l5_common_data_on = 1; +#endif +#ifdef VL53L5_RNG_TIMING_DATA_ON + db.vl53l5_rng_timing_data_on = 1; +#endif +#ifdef VL53L5_AMB_RATE_KCPS_PER_SPAD_ON + db.vl53l5_amb_rate_kcps_per_spad_on = 1; +#endif +#ifdef VL53L5_EFFECTIVE_SPAD_COUNT_ON + db.vl53l5_effective_spad_count_on = 1; +#endif +#ifdef VL53L5_AMB_DMAX_MM_ON + db.vl53l5_amb_dmax_mm_on = 1; +#endif +#ifdef VL53L5_SILICON_TEMP_DEGC_START_ON + db.vl53l5_silicon_temp_degc_start_on = 1; +#endif +#ifdef VL53L5_SILICON_TEMP_DEGC_END_ON + db.vl53l5_silicon_temp_degc_end_on = 1; +#endif +#ifdef VL53L5_NO_OF_TARGETS_ON + db.vl53l5_no_of_targets_on = 1; +#endif +#ifdef VL53L5_ZONE_ID_ON + db.vl53l5_zone_id_on = 1; +#endif +#ifdef VL53L5_SEQUENCE_IDX_ON + db.vl53l5_sequence_idx_on = 1; +#endif +#ifdef VL53L5_PEAK_RATE_KCPS_PER_SPAD_ON + db.vl53l5_peak_rate_kcps_per_spad_on = 1; +#endif +#ifdef VL53L5_MEDIAN_PHASE_ON + db.vl53l5_median_phase_on = 1; +#endif +#ifdef VL53L5_RATE_SIGMA_KCPS_PER_SPAD_ON + db.vl53l5_rate_sigma_kcps_per_spad_on = 1; +#endif +#ifdef VL53L5_RANGE_SIGMA_MM_ON + db.vl53l5_range_sigma_mm_on = 1; +#endif +#ifdef VL53L5_MEDIAN_RANGE_MM_ON + db.vl53l5_median_range_mm_on = 1; +#endif +#ifdef VL53L5_MIN_RANGE_DELTA_MM_ON + db.vl53l5_min_range_delta_mm_on = 1; +#endif +#ifdef VL53L5_MAX_RANGE_DELTA_MM_ON + db.vl53l5_max_range_delta_mm_on = 1; +#endif +#ifdef VL53L5_TARGET_REFLECTANCE_EST_PC_ON + db.vl53l5_target_reflectance_est_pc_on = 1; +#endif +#ifdef VL53L5_TARGET_STATUS_ON + db.vl53l5_target_status_on = 1; +#endif +#ifdef VL53L5_REF_TIMING_DATA_ON + db.vl53l5_ref_timing_data_on = 1; +#endif +#ifdef VL53L5_REF_CHANNEL_DATA_ON + db.vl53l5_ref_channel_data_on = 1; +#endif +#ifdef VL53L5_REF_TARGET_DATA_ON + db.vl53l5_ref_target_data_on = 1; +#endif +#ifdef VL53L5_ZONE_THRESH_STATUS_BYTES_ON + db.vl53l5_zone_thresh_status_bytes_on = 1; +#endif +#ifdef VL53L5_DYN_XTALK_OP_PERSISTENT_DATA_ON + db.vl53l5_dyn_xtalk_op_persistent_data_on = 1; +#endif + return db.bytes; +} +#endif + +static int32_t _write_byte( + struct vl53l5_dev_handle_t *p_dev, uint16_t write_index, uint8_t value) +{ + return vl53l5_write_multi(p_dev, write_index, &value, 1); +} + +static int32_t _decode_fgc( + struct vl53l5_module_info_t *p_module_info, uint8_t *p_buff) +{ + int32_t status = STATUS_OK; + uint32_t fgc_4 = 0; + uint32_t fgc_9 = 0; + + struct dci_grp__fmt_traceability_t fmt = {0}; + uint8_t *p_str = NULL; + + LOG_FUNCTION_START(""); + + fmt.st_fgc_0_u.bytes = vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + fmt.st_fgc_1_u.bytes = vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + fmt.st_fgc_2_u.bytes = vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + fmt.st_fgc_3_u.bytes = vl53l5_decode_uint32_t(BYTE_4, p_buff); + + fgc_4 = ((fmt.st_fgc_0_u.fgc_4__6_3 & 0x0f) << 3) | + (fmt.st_fgc_1_u.fgc_4__2_0 & 0x07); + fgc_9 = ((fmt.st_fgc_1_u.fgc_9__6 & 0x01) << 6) | + (fmt.st_fgc_2_u.fgc_9__5_0 & 0x3f); + + p_str = p_module_info->fgc; + *p_str++ = fmt.st_fgc_0_u.fgc_0 + 32; + *p_str++ = fmt.st_fgc_0_u.fgc_1 + 32; + *p_str++ = fmt.st_fgc_0_u.fgc_2 + 32; + *p_str++ = fmt.st_fgc_0_u.fgc_3 + 32; + *p_str++ = fgc_4 + 32; + *p_str++ = fmt.st_fgc_1_u.fgc_5 + 32; + *p_str++ = fmt.st_fgc_1_u.fgc_6 + 32; + *p_str++ = fmt.st_fgc_1_u.fgc_7 + 32; + *p_str++ = fmt.st_fgc_1_u.fgc_8 + 32; + *p_str++ = fgc_9 + 32; + *p_str++ = fmt.st_fgc_2_u.fgc_10 + 32; + *p_str++ = fmt.st_fgc_2_u.fgc_11 + 32; + + *p_str++ = 32; + *p_str++ = 32; + *p_str++ = 32; + *p_str++ = 32; + *p_str++ = 32; + *p_str++ = 32; + + *p_str = '\0'; + + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _decode_fw_version( + struct vl53l5_dev_handle_t *p_dev, struct vl53l5_version_t *p_version) +{ + int32_t status = STATUS_OK; + uint8_t *p_buff = NULL; + uint32_t block_header = 0; + const uint32_t end_of_data_footer_index = 12; + + if (p_dev->host_dev.revision_id == 0x0C) + p_buff = &VL53L5_COMMS_BUFF(p_dev)[VL53L5_UI_DUMMY_BYTES]; + else + p_buff = VL53L5_COMMS_BUFF(p_dev); + + if ((p_buff[end_of_data_footer_index] & 0x0f) != + DCI_BH__P_TYPE__END_OF_DATA) { + status = VL53L5_DCI_END_BLOCK_ERROR; + goto exit; + } + + block_header = vl53l5_decode_uint32_t(BYTE_4, p_buff); + if (block_header != VL53L5_FW_VERSION_BH) { + + status = VL53L5_VERSION_IDX_NOT_PRESENT; + goto exit; + } + p_buff += BYTE_4; + p_version->firmware.ver_revision = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + p_version->firmware.ver_build = vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + p_version->firmware.ver_minor = vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + p_version->firmware.ver_major = vl53l5_decode_uint16_t(BYTE_2, p_buff); + + trace_print(VL53L5_TRACE_LEVEL_INFO, + "Firmware version: %u.%u.%u.%u\n", + p_version->firmware.ver_major, + p_version->firmware.ver_minor, + p_version->firmware.ver_build, + p_version->firmware.ver_revision); +exit: + return status; +} + +static int32_t _decode_module_info( + struct vl53l5_dev_handle_t *p_dev, + struct vl53l5_module_info_t *p_module_info) +{ + int32_t status = STATUS_OK; + uint32_t block_header = 0; + uint8_t *p_buff = NULL; + const uint32_t end_of_data_footer_index = + VL53L5_FMT_TRACEABILITY_SZ + VL53L5_EWS_TRACEABILITY_SZ + + (BYTE_2 * VL53L5_DCI_UI_PACKED_DATA_BH_SZ); + + if (p_dev->host_dev.revision_id == 0x0C) + p_buff = &VL53L5_COMMS_BUFF(p_dev)[VL53L5_UI_DUMMY_BYTES]; + else + p_buff = VL53L5_COMMS_BUFF(p_dev); + + if ((p_buff[end_of_data_footer_index] & 0x0f) != + DCI_BH__P_TYPE__END_OF_DATA) { + status = VL53L5_DCI_END_BLOCK_ERROR; + goto exit; + } + + block_header = vl53l5_decode_uint32_t(BYTE_4, p_buff); + if (block_header != VL53L5_FMT_TRACEABILITY_BH) { + status = VL53L5_IDX_MISSING_FROM_RETURN_PACKET; + goto exit; + } + p_buff += BYTE_4; + status = _decode_fgc(p_module_info, p_buff); + + p_buff += (6 * BYTE_4); + + block_header = vl53l5_decode_uint32_t(BYTE_4, p_buff); + if (block_header != VL53L5_EWS_TRACEABILITY_BH) { + status = VL53L5_IDX_MISSING_FROM_RETURN_PACKET; + goto exit; + } + + p_buff += (BYTE_2 * BYTE_4); + + p_module_info->module_id_hi = vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + + p_module_info->module_id_lo = vl53l5_decode_uint32_t(BYTE_4, p_buff); + +exit: + return status; +} + +static int32_t _decode_start_range_return(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + uint8_t *p_buff = NULL; + uint32_t block_header = 0; + struct dci_grp__ui_rng_data_addr_t rng_data_addr = {0}; + uint32_t end_of_data_footer_index = + VL53L5_UI_RNG_DATA_ADDR_SZ + VL53L5_DCI_UI_PACKED_DATA_BH_SZ; + + if (p_dev->host_dev.revision_id == 0x0C) + p_buff = &VL53L5_COMMS_BUFF(p_dev)[VL53L5_UI_DUMMY_BYTES]; + else + p_buff = VL53L5_COMMS_BUFF(p_dev); + + if ((p_buff[end_of_data_footer_index] & 0x0f) != + DCI_BH__P_TYPE__END_OF_DATA) { + status = VL53L5_DCI_END_BLOCK_ERROR; + goto exit; + } + + block_header = vl53l5_decode_uint32_t(BYTE_4, p_buff); + if (block_header != VL53L5_UI_RNG_DATA_ADDR_BH) { + status = VL53L5_RANGE_DATA_ADDR_NOT_PRESENT; + goto exit; + } + p_buff += BYTE_4; + rng_data_addr.ui_rng_data_int_start_addr + = vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + rng_data_addr.ui_rng_data_int_end_addr + = vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + rng_data_addr.ui_rng_data_dummy_bytes + = vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + rng_data_addr.ui_rng_data_offset_bytes + = vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + rng_data_addr.ui_rng_data_size_bytes + = vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + rng_data_addr.ui_device_info_start_addr + = vl53l5_decode_uint16_t(BYTE_2, p_buff); + + if (rng_data_addr.ui_rng_data_size_bytes == 0) { + status = VL53L5_ERROR_INVALID_RETURN_DATA_SIZE; + goto exit; + } + + p_dev->host_dev.range_data_addr.dev_info_start_addr = + rng_data_addr.ui_device_info_start_addr; + + p_dev->host_dev.range_data_addr.data_start_offset_bytes = + VL53L5_DEV_INFO_BLOCK_SZ + + rng_data_addr.ui_rng_data_offset_bytes; + + p_dev->host_dev.range_data_addr.data_size_bytes = + ((rng_data_addr.ui_rng_data_size_bytes + - rng_data_addr.ui_rng_data_offset_bytes) + - VL53L5_HEADER_FOOTER_BLOCK_SZ); + + trace_print( + VL53L5_TRACE_LEVEL_DEBUG, + "True data start offset: %d, size %d\n", + p_dev->host_dev.range_data_addr.data_start_offset_bytes, + p_dev->host_dev.range_data_addr.data_size_bytes); +exit: + return status; +} + +static bool _check_if_status_requires_handler(int32_t status) +{ + bool is_required = false; + + if ((status <= VL53L5_MAX_FW_ERROR) && (status > VL53L5_MIN_FW_ERROR)) { + is_required = false; + goto exit; + } + + switch (status) { + case VL53L5_ERROR_TIME_OUT: + case VL53L5_ERROR_BOOT_COMPLETE_TIMEOUT: + case VL53L5_ERROR_MCU_IDLE_TIMEOUT: + case VL53L5_ERROR_RANGE_DATA_READY_TIMEOUT: + case VL53L5_ERROR_CMD_STATUS_TIMEOUT: + case VL53L5_ERROR_UI_FW_STATUS_TIMEOUT: + case VL53L5_ERROR_UI_RAM_WATCHDOG_TIMEOUT: + case VL53L5_ERROR_MCU_ERROR_AT_ROM_BOOT: + case VL53L5_ERROR_FALSE_MCU_ERROR_AT_ROM_BOOT: + case VL53L5_ERROR_MCU_ERROR_AT_RAM_BOOT: + case VL53L5_ERROR_FALSE_MCU_ERROR_AT_RAM_BOOT: + case VL53L5_ERROR_MCU_ERROR_POWER_STATE: + case VL53L5_ERROR_FALSE_MCU_ERROR_POWER_STATE: + case VL53L5_DEV_INFO_MCU_ERROR: + case VL53L5_FALSE_DEV_INFO_MCU_ERROR: + case VL53L5_ERROR_MCU_ERROR_WAIT_STATE: + is_required = true; + break; + default: + is_required = false; + break; + } +exit: + return is_required; +} + +static int32_t _get_error_go2_status( + struct vl53l5_dev_handle_t *p_dev, uint8_t current_page) +{ + int32_t status = STATUS_OK; + struct common_grp__status_t status_struct = {0}; + union dci_union__go2_status_0_go1_u go2_status_0; + union dci_union__go2_status_1_go1_u go2_status_1; + + LOG_FUNCTION_START(""); + + status = vl53l5_check_status_registers( + p_dev, &go2_status_0, &go2_status_1, false, + current_page); + + if (status != STATUS_OK) + goto exit; + + if (go2_status_0.mcu__hw_trap_flag_go1) { + + if (go2_status_1.bytes != 0x00) { + status_struct.status__group = HW_TRAP_ERROR_GROUP; + status_struct.status__type = go2_status_1.bytes; + } else { + goto exit; + } + } else if ((go2_status_0.mcu__error_flag_go1) || + (go2_status_1.mcu__warning_flag_go1)) { + if (current_page != UI_PAGE) { + status = vl53l5_set_page(p_dev, UI_PAGE); + if (status != STATUS_OK) + goto exit; + } + + if (go2_status_1.mcu__warning_flag_go1) + + status = vl53l5_get_secondary_warning_info( + p_dev, &status_struct); + else + + status = vl53l5_get_secondary_error_info( + p_dev, &status_struct); + + if (current_page != UI_PAGE) { + if (status != STATUS_OK) + (void)vl53l5_set_page(p_dev, current_page); + else + status = vl53l5_set_page(p_dev, current_page); + } + if (status != STATUS_OK) + goto exit; + } else { + + goto exit; + } + + if (status != STATUS_OK) + goto exit; + + trace_print( + VL53L5_TRACE_LEVEL_DEBUG, + "group:%i type:%i stage:%i debug0:%i debug1:%i debug2:%i\n", + status_struct.status__group, + status_struct.status__type, + status_struct.status__stage_id, + status_struct.status__debug_0, + status_struct.status__debug_1, + status_struct.status__debug_2); + status = vl53l5_compose_fw_status_code(p_dev, &status_struct); + +exit: + LOG_FUNCTION_END_FLUSH(status); + return status; +} + +static int32_t _get_dev_params(struct vl53l5_dev_handle_t *p_dev, + uint32_t *p_block_headers, + uint32_t num_block_headers, + bool encode_version) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + status = vl53l5_encode_block_headers( + p_dev, p_block_headers, num_block_headers, encode_version); + if (status != STATUS_OK) + goto exit; + + status = vl53l5_execute_command(p_dev, DCI_CMD_ID__GET_PARMS); + +exit: + LOG_FUNCTION_END_FLUSH(status); + return status; +} + +static int32_t _set_xshut_bypass( + struct vl53l5_dev_handle_t *p_dev, uint8_t state) +{ + int32_t status = VL53L5_ERROR_NONE; + + status = vl53l5_set_page(p_dev, 0); + if (status < STATUS_OK) + goto exit; + + status = vl53l5_set_xshut_bypass(p_dev, state); + + if (status < STATUS_OK) + (void)vl53l5_set_page(p_dev, UI_PAGE); + else + status = vl53l5_set_page(p_dev, UI_PAGE); + +exit: + return status; +} + +static int32_t _provoke_mcu_error( + struct vl53l5_dev_handle_t *p_dev, int32_t *p_boot_status) +{ + int32_t status = VL53L5_ERROR_NONE; + + status = _write_byte(p_dev, 0x15, 22); + if (status < STATUS_OK) + goto exit; + + status = _write_byte(p_dev, 0x14, 1); + if (status < STATUS_OK) + goto exit_reset_15; + + *p_boot_status = vl53l5_wait_mcu_boot( + p_dev, VL53L5_BOOT_STATE_ERROR, VL53L5_STOP_COMMAND_TIMEOUT, + VL53L5_DEFAULT_COMMS_POLLING_DELAY_MS); + if (*p_boot_status == STATUS_OK) { + status = VL53L5_ERROR_TIME_OUT; + goto exit_reset_14; + } + +exit: + return status; + +exit_reset_14: + (void)_write_byte(p_dev, 0x14, 0); + +exit_reset_15: + (void)_write_byte(p_dev, 0x15, 0); + + return status; +} + +static int32_t _undo_provoke_mcu_error(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = VL53L5_ERROR_NONE; + + status = _write_byte(p_dev, 0x14, 0); + + ASSIGN_STATUS_IF_NOT_ERROR(_write_byte(p_dev, 0x15, 0), status); + + return status; +} + +static int32_t _force_timed_mode_stop(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = VL53L5_ERROR_NONE; + int32_t boot_status = VL53L5_ERROR_NONE; + const int32_t expected_error_code = (int32_t)0xE2048200; + + status = vl53l5_set_page(p_dev, 0); + if (status != STATUS_OK) + goto exit; + + status = _provoke_mcu_error(p_dev, &boot_status); + if (status != STATUS_OK) + goto exit_xshut; + + if (!_check_if_status_requires_handler(boot_status)) { + status = boot_status; + goto exit_undo; + } + + trace_print(VL53L5_TRACE_LEVEL_INFO, + "boot_status Value: %d\n", + boot_status); + status = _get_error_go2_status(p_dev, 0); + + if (status == expected_error_code) + status = VL53L5_ERROR_NONE; + +exit_undo: + ASSIGN_STATUS_IF_NOT_ERROR(_undo_provoke_mcu_error(p_dev), status); + +exit_xshut: + ASSIGN_STATUS_IF_NOT_ERROR(vl53l5_set_xshut_bypass(p_dev, 0), status); + + ASSIGN_STATUS_IF_NOT_ERROR(vl53l5_set_page(p_dev, UI_PAGE), status); + +exit: + return status; +} + +int32_t vl53l5_init(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + +#ifdef VL53L5_LOG_ENABLE + trace_print(VL53L5_TRACE_LEVEL_INFO, + "Driver Build Value: 0x%x\n", + _get_driver_build_details()); +#endif + trace_print(VL53L5_TRACE_LEVEL_INFO, + "Max Targets Value: %d\n", + VL53L5_MAX_TARGETS); + trace_print(VL53L5_TRACE_LEVEL_INFO, + "Max Zones Value: %d\n", + VL53L5_MAX_ZONES); + trace_print(VL53L5_TRACE_LEVEL_INFO, + "Polling Delay Value: %d\n", + VL53L5_DEFAULT_COMMS_POLLING_DELAY_MS); + + trace_print( + VL53L5_TRACE_LEVEL_INFO, + "VL53L5 Bare Driver Version %d.%d.%02d.%04d\n", + VL53L5_VERSION_MAJOR, + VL53L5_VERSION_MINOR, + VL53L5_VERSION_BUILD, + VL53L5_VERSION_REVISION); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + if (VL53L5_COMMS_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + status = vl53l5_check_rom_firmware_boot(p_dev); + if (status < STATUS_OK) + goto exit; + + if (p_dev->host_dev.device_booted == true && + p_dev->host_dev.version_match.map_version_match != true) { + status = vl53l5_check_map_version(p_dev); + goto exit; + } + + if (p_dev->host_dev.device_booted == true) + goto exit; + + if (p_dev->host_dev.device_booted == false && + !VL53L5_FW_BUFF_ISNULL(p_dev)) { + status = vl53l5_load_firmware(p_dev); + if (status < STATUS_OK) + goto exit; + } else if (p_dev->host_dev.revision_id == 0x0C) { + goto exit_version; + } else { + status = VL53L5_ERROR_FW_BUFF_NOT_FOUND; + goto exit; + } + +exit_version: + + status = vl53l5_check_map_version(p_dev); + if (status != STATUS_OK) + goto exit; + + p_dev->host_dev.power_state = VL53L5_POWER_STATE_HP_IDLE; + +exit: + LOG_FUNCTION_END_FLUSH(status); + return status; +} + +int32_t vl53l5_term(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + if (VL53L5_COMMS_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + VL53L5_RESET_COMMS_BUFF(p_dev); + p_dev->host_dev.version_match.map_version_match = false; + +exit: + LOG_FUNCTION_END_FLUSH(status); + return status; +} + +int32_t vl53l5_get_version( + struct vl53l5_dev_handle_t *p_dev, struct vl53l5_version_t *p_version) +{ + int32_t status = STATUS_OK; + uint32_t block_headers[] = {VL53L5_FW_VERSION_BH}; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_ISNULL(p_version)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_COMMS_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + status = VL53L5_GET_VERSION_CHECK_STATUS(p_dev); + + if (status < STATUS_OK) + goto exit; + + _get_driver_version(p_version); + + status = _get_dev_params(p_dev, block_headers, 1, false); + if (status < STATUS_OK) + goto exit; + + status = _decode_fw_version(p_dev, p_version); + +exit: + LOG_FUNCTION_END_FLUSH(status); + return status; +} + +int32_t vl53l5_get_module_info( + struct vl53l5_dev_handle_t *p_dev, + struct vl53l5_module_info_t *p_module_info) +{ + int32_t status = STATUS_OK; + uint32_t block_headers[] = { + VL53L5_FMT_TRACEABILITY_BH, + VL53L5_EWS_TRACEABILITY_BH + }; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_ISNULL(p_module_info)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_COMMS_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + status = VL53L5_GET_VERSION_CHECK_STATUS(p_dev); + + if (status < STATUS_OK) + goto exit; + + status = _get_dev_params(p_dev, block_headers, 2, false); + if (status < STATUS_OK) + goto exit; + + status = _decode_module_info(p_dev, p_module_info); + +exit: + LOG_FUNCTION_END_FLUSH(status); + return status; +} + +int32_t vl53l5_start( + struct vl53l5_dev_handle_t *p_dev, + struct vl53l5_ranging_mode_flags_t *p_flags) +{ + int32_t status = VL53L5_ERROR_NONE; + bool bypass_set = false; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_COMMS_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + status = VL53L5_GET_VERSION_CHECK_STATUS(p_dev); + if (status < STATUS_OK) + goto exit; + + if (!VL53L5_ISNULL(p_flags)) { + + status = _check_flags_consistent(p_flags); + if (status != STATUS_OK) + goto exit; + + if (TIMED_RETENTION_OR_INTERRUPT(p_flags)) { + status = _set_xshut_bypass(p_dev, 1); + if (status != STATUS_OK) + goto exit; + + bypass_set = true; + } + } + + VL53L5_RESET_COMMS_BUFF(p_dev); + status = vl53l5_execute_command(p_dev, DCI_CMD_ID__START_RANGE); + if (status != STATUS_OK) + goto exit_reset_bypass; + + status = _decode_start_range_return(p_dev); + if (status != STATUS_OK) + goto exit_reset_bypass; + + p_dev->host_dev.ui_dev_info.dev_info__ui_stream_count = 0xFF; + +exit_reset_bypass: + if ((status != STATUS_OK) && bypass_set) + (void)_set_xshut_bypass(p_dev, 0); + +exit: + LOG_FUNCTION_END_FLUSH(status); + return status; +} + +int32_t vl53l5_stop( + struct vl53l5_dev_handle_t *p_dev, + struct vl53l5_ranging_mode_flags_t *p_flags) +{ + int32_t status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_COMMS_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + status = VL53L5_GET_VERSION_CHECK_STATUS(p_dev); + + if (status < STATUS_OK) + goto exit; + + if (!VL53L5_ISNULL(p_flags)) { + + status = _check_flags_consistent(p_flags); + if (status != STATUS_OK) + goto exit; + + if (TIMED_RETENTION_OR_INTERRUPT(p_flags)) { + status = _force_timed_mode_stop(p_dev); + if (status != STATUS_OK) + goto exit; + } else { + + VL53L5_RESET_COMMS_BUFF(p_dev); + status = vl53l5_execute_command(p_dev, + DCI_CMD_ID__STOP_RANGE); + if (status != STATUS_OK) + goto exit; + } + } else { + + VL53L5_RESET_COMMS_BUFF(p_dev); + status = vl53l5_execute_command(p_dev, DCI_CMD_ID__STOP_RANGE); + if (status != STATUS_OK) + goto exit; + } + +exit: + LOG_FUNCTION_END_FLUSH(status); + return status; +} + +int32_t vl53l5_read_device_error( + struct vl53l5_dev_handle_t *p_dev, + int32_t latest_status) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_COMMS_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + if (!_check_if_status_requires_handler(latest_status)) { + + status = latest_status; + goto exit; + } + + status = _get_error_go2_status(p_dev, UI_PAGE); + +exit: + + if (status == STATUS_OK) + status = latest_status; + + LOG_FUNCTION_END_FLUSH(status); + return status; +} + +int32_t vl53l5_get_device_parameters( + struct vl53l5_dev_handle_t *p_dev, + uint32_t *p_block_headers, + uint32_t num_block_headers) +{ + int32_t status = STATUS_OK; + uint8_t *p_buff = NULL; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_ISNULL(p_block_headers)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_COMMS_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (num_block_headers == 0) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + if (p_dev->host_dev.revision_id == 0x0C) + p_buff = &VL53L5_COMMS_BUFF(p_dev)[VL53L5_UI_DUMMY_BYTES]; + else + p_buff = VL53L5_COMMS_BUFF(p_dev); + + status = VL53L5_GET_VERSION_CHECK_STATUS(p_dev); + if (status < STATUS_OK) + goto exit; + + status = _get_dev_params( + p_dev, p_block_headers, num_block_headers, true); + if (status != STATUS_OK) + goto exit; + + status = vl53l5_test_map_version(p_dev, p_buff); + if (status != STATUS_OK) + goto exit; + +exit: + LOG_FUNCTION_END_FLUSH(status); + return status; +} + +int32_t vl53l5_set_device_parameters( + struct vl53l5_dev_handle_t *p_dev, + uint8_t *p_buff, + uint32_t buff_count) +{ + int32_t status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_ISNULL(p_buff)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_COMMS_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (buff_count == 0) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + status = VL53L5_GET_VERSION_CHECK_STATUS(p_dev); + if (status < STATUS_OK) + goto exit; + + if (buff_count > VL53L5_COMMS_BUFF_MAX_COUNT(p_dev)) { + status = VL53L5_MAX_BUFFER_SIZE_REACHED; + goto exit; + } + + if (p_buff != VL53L5_COMMS_BUFF(p_dev)) + memcpy(VL53L5_COMMS_BUFF(p_dev), p_buff, buff_count); + + VL53L5_COMMS_BUFF_COUNT(p_dev) = buff_count; + + status = vl53l5_test_map_version(p_dev, VL53L5_COMMS_BUFF(p_dev)); + if (status != STATUS_OK) + goto exit; + + status = vl53l5_execute_command(p_dev, DCI_CMD_ID__SET_PARMS); + if (status != STATUS_OK) + goto exit; + +exit: + LOG_FUNCTION_END_FLUSH(status); + return status; +} + +static int32_t _build_buffer(struct vl53l5_dev_handle_t *p_dev, + uint32_t bh, uint32_t value) +{ + int32_t status = VL53L5_ERROR_NONE; + uint8_t buffer[(BYTE_4 * BYTE_4)] = {0x40, 0x00, 0x00, 0x54, + 0x0b, 0x00, 0x05, 0x00,}; + uint8_t *p_buff = &buffer[(BYTE_2 * BYTE_4)]; + + if (value == 0) { + status = VL53L5_ERROR_INVALID_VALUE; + goto exit; + } + + vl53l5_encode_uint32_t(bh, BYTE_4, p_buff); + p_buff += BYTE_4; + vl53l5_encode_uint32_t(value, BYTE_4, p_buff); + + status = vl53l5_set_device_parameters(p_dev, buffer, sizeof(buffer)); + if (status != VL53L5_ERROR_NONE) + goto exit; + +exit: + return status; +} + +int32_t vl53l5_set_ranging_rate_hz( + struct vl53l5_dev_handle_t *p_dev, + uint32_t ranging_rate_hz) +{ + int32_t status = VL53L5_ERROR_NONE; + uint32_t rr = ranging_rate_hz << (BYTE_2 * BYTE_4); + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_COMMS_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + status = _build_buffer(p_dev, VL53L5_RNG_RATE_CFG_BH, rr); + +exit: + LOG_FUNCTION_END_FLUSH(status); + return status; +} + +int32_t vl53l5_set_integration_time_us( + struct vl53l5_dev_handle_t *p_dev, + uint32_t integration_time_us) +{ + int32_t status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_COMMS_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + status = _build_buffer( + p_dev, VL53L5_INT_MAX_CFG_BH, integration_time_us); + +exit: + LOG_FUNCTION_END_FLUSH(status); + return status; +} diff --git a/drivers/sensors/vl53l8/bare_driver/api/src/vl53l5_api_power.c b/drivers/sensors/vl53l8/bare_driver/api/src/vl53l5_api_power.c new file mode 100644 index 000000000000..b5f2d8503fc0 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/api/src/vl53l5_api_power.c @@ -0,0 +1,371 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_api_power.h" +#include "vl53l5_checks.h" +#include "vl53l5_register_utils.h" +#include "vl53l5_load_firmware.h" +#include "vl53l5_globals.h" +#include "vl53l5_trans.h" +#include "vl53l5_dci_core.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_platform.h" +#include "vl53l5_platform_log.h" +#include "vl53l5_platform_user_config.h" +#include "vl53l5_error_codes.h" + +#define trace_print(level, ...) \ + _LOG_TRACE_PRINT(VL53L5_TRACE_MODULE_API, \ + level, VL53L5_TRACE_FUNCTION_ALL, ##__VA_ARGS__) +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START(VL53L5_TRACE_MODULE_API, fmt, ##__VA_ARGS__) +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END(VL53L5_TRACE_MODULE_API, status, ##__VA_ARGS__) +#define LOG_FUNCTION_END_FMT(status, fmt, ...) \ + _LOG_FUNCTION_END_FMT(VL53L5_TRACE_MODULE_API, status, fmt,\ + ##__VA_ARGS__) +#define LOG_FUNCTION_END_FLUSH(status, ...) \ + do { \ + _LOG_FUNCTION_END(VL53L5_TRACE_MODULE_API, status, ##__VA_ARGS__);\ + _FLUSH_TRACE_TO_OUTPUT();\ + } while (0) + +#define GPIO_LOW 0 +#define GPIO_HIGH 1 + +#define REGULATOR_DISABLE 0 +#define REGULATOR_ENABLE 1 + +#define DEFAULT_PAGE 2 +#define GO2_PAGE 0 + +static int32_t _set_power_to_hp_idle(struct vl53l5_dev_handle_t *p_dev); + +static int32_t _set_power_to_lp_idle_comms(struct vl53l5_dev_handle_t *p_dev); + +static int32_t _set_power_to_ulp_idle(struct vl53l5_dev_handle_t *p_dev); + +static int32_t _go_to_hp_idle(struct vl53l5_dev_handle_t *p_dev); + +static int32_t _go_to_lp_idle_comms(struct vl53l5_dev_handle_t *p_dev); + +static int32_t _go_to_ulp_idle(struct vl53l5_dev_handle_t *p_dev); + +int32_t vl53l5_set_power_mode( + struct vl53l5_dev_handle_t *p_dev, + enum vl53l5_power_states power_mode) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + if ((VL53L5_CHECK_POWER_STATE_OFF(p_dev) && + p_dev->host_dev.device_booted == false) || + VL53L5_CHECK_POWER_STATE_RANGING(p_dev)) { + status = VL53L5_ERROR_POWER_STATE; + goto exit; + } + + switch (power_mode) { + case VL53L5_POWER_STATE_HP_IDLE: + if (p_dev->host_dev.power_state + != VL53L5_POWER_STATE_HP_IDLE) + status = _set_power_to_hp_idle(p_dev); + break; + case VL53L5_POWER_STATE_LP_IDLE_COMMS: + if (p_dev->host_dev.power_state + != VL53L5_POWER_STATE_LP_IDLE_COMMS) + status = _set_power_to_lp_idle_comms(p_dev); + break; + + case VL53L5_POWER_STATE_ULP_IDLE: + if (p_dev->host_dev.power_state + != VL53L5_POWER_STATE_ULP_IDLE) + status = _set_power_to_ulp_idle(p_dev); + break; + case VL53L5_POWER_STATE_OFF: + + case VL53L5_POWER_STATE_RANGING: + + default: + status = VL53L5_ERROR_POWER_STATE; + break; + } + +exit: + LOG_FUNCTION_END_FLUSH(status); + return status; +} + +static int32_t _go_to_hp_idle(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + if (VL53L5_CHECK_POWER_STATE_ULP_IDLE(p_dev)) { + + status = vl53l5_set_manual_xshut_state(p_dev, GPIO_HIGH); + if (status < STATUS_OK) + goto exit; + + status = vl53l5_set_regulators(p_dev, REGULATOR_ENABLE, + REGULATOR_ENABLE); + if (status < STATUS_OK) + goto exit; + + } else if (VL53L5_CHECK_POWER_STATE_LP_IDLE_COMMS(p_dev)) { + + status = vl53l5_set_manual_xshut_state(p_dev, GPIO_HIGH); + if (status < STATUS_OK) + goto exit; + } else if (VL53L5_CHECK_POWER_STATE_OFF(p_dev) && + p_dev->host_dev.device_booted == true) { + + status = vl53l5_set_manual_xshut_state(p_dev, GPIO_HIGH); + if (status < STATUS_OK) + goto exit; + } else { + status = VL53L5_ERROR_POWER_STATE; + goto exit; + } + + status = vl53l5_wait_mcu_boot(p_dev, VL53L5_BOOT_STATE_HIGH, + 0, VL53L5_HP_IDLE_WAIT_DELAY); + if (status < STATUS_OK) + goto exit; + + p_dev->host_dev.power_state = VL53L5_POWER_STATE_HP_IDLE; + +exit: + return status; +} + +static int32_t _go_to_lp_idle_comms(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + status = vl53l5_set_manual_xshut_state(p_dev, GPIO_LOW); + if (status < STATUS_OK) + goto exit; + + status = vl53l5_wait_mcu_boot(p_dev, VL53L5_BOOT_STATE_LOW, + 0, VL53L5_LP_IDLE_WAIT_DELAY); + if (status < STATUS_OK) + goto exit; + + p_dev->host_dev.power_state = VL53L5_POWER_STATE_LP_IDLE_COMMS; + +exit: + return status; +} + +static int32_t _go_to_ulp_idle(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + status = _go_to_lp_idle_comms(p_dev); + if (status < STATUS_OK) + goto exit; + + status = vl53l5_set_regulators(p_dev, REGULATOR_DISABLE, + REGULATOR_DISABLE); + if (status < STATUS_OK) + goto exit; + + p_dev->host_dev.version_match.map_version_match = false; + + p_dev->host_dev.power_state = VL53L5_POWER_STATE_ULP_IDLE; + +exit: + return status; +} + +static int32_t _set_power_to_hp_idle(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + enum vl53l5_power_states current_state = p_dev->host_dev.power_state; + + bool comms_on = true; + + if (VL53L5_CHECK_POWER_STATE_HP_IDLE(p_dev)) + goto exit; + + if (comms_on) { + + status = vl53l5_set_page(p_dev, GO2_PAGE); + if (status < STATUS_OK) + goto exit_page_changed; + } + + status = _go_to_hp_idle(p_dev); + +exit_page_changed: + + if (status != STATUS_OK) { + (void)vl53l5_set_page(p_dev, DEFAULT_PAGE); + goto exit; + } else { + status = vl53l5_set_page(p_dev, DEFAULT_PAGE); + if (status != STATUS_OK) + goto exit; + } + + if (current_state == VL53L5_POWER_STATE_ULP_IDLE) { + if (!VL53L5_FW_BUFF_ISNULL(p_dev)) { + status = vl53l5_load_firmware(p_dev); + if (status != STATUS_OK) + goto exit; + } + } + + if (current_state == VL53L5_POWER_STATE_ULP_IDLE || + p_dev->host_dev.device_booted == true) { + status = vl53l5_check_map_version(p_dev); + if (status != STATUS_OK) + goto exit; + } + +exit: + return status; +} + +static int32_t _set_power_to_lp_idle_comms(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + enum vl53l5_power_states current_state = p_dev->host_dev.power_state; + + bool comms_on = true; + + if (VL53L5_CHECK_POWER_STATE_LP_IDLE_COMMS(p_dev)) + goto exit; + + if (current_state == VL53L5_POWER_STATE_ULP_IDLE) { + status = _set_power_to_hp_idle(p_dev); + if (status < STATUS_OK) + goto exit; + } + + if (comms_on) { + + status = vl53l5_set_page(p_dev, GO2_PAGE); + if (status < STATUS_OK) + goto exit; + } + if (!VL53L5_CHECK_POWER_STATE_HP_IDLE(p_dev)) { + + status = _go_to_hp_idle(p_dev); + if (status < STATUS_OK) + goto exit_page_changed; + + } + + status = _go_to_lp_idle_comms(p_dev); + +exit_page_changed: + + if (status < STATUS_OK) + (void)vl53l5_set_page(p_dev, DEFAULT_PAGE); + else + status = vl53l5_set_page(p_dev, DEFAULT_PAGE); + +exit: + return status; +} + +static int32_t _set_power_to_ulp_idle(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = VL53L5_ERROR_NONE; + + bool comms_on = true; + + if (VL53L5_CHECK_POWER_STATE_ULP_IDLE(p_dev)) + goto exit; + + if (comms_on) { + + status = vl53l5_set_page(p_dev, GO2_PAGE); + if (status < STATUS_OK) + goto exit; + } + + if (!VL53L5_CHECK_POWER_STATE_HP_IDLE(p_dev)) { + + status = _go_to_hp_idle(p_dev); + if (status < STATUS_OK) + goto exit_page_changed; + } + + status = _go_to_ulp_idle(p_dev); + if (status < STATUS_OK) + goto exit_page_changed; + +exit_page_changed: + + if (status < STATUS_OK) + (void)vl53l5_set_page(p_dev, DEFAULT_PAGE); + else + status = vl53l5_set_page(p_dev, DEFAULT_PAGE); + +exit: + return status; +} diff --git a/drivers/sensors/vl53l8/bare_driver/api/src/vl53l5_api_range_decode.c b/drivers/sensors/vl53l8/bare_driver/api/src/vl53l5_api_range_decode.c new file mode 100644 index 000000000000..5749953377fc --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/api/src/vl53l5_api_range_decode.c @@ -0,0 +1,132 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_api_range_decode.h" +#include "vl53l5_globals.h" +#include "vl53l5_dci_decode.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform_log.h" + +#define trace_print(level, ...) \ + _LOG_TRACE_PRINT(VL53L5_TRACE_MODULE_API, \ + level, VL53L5_TRACE_FUNCTION_ALL, ##__VA_ARGS__) +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START(VL53L5_TRACE_MODULE_API, fmt, ##__VA_ARGS__) +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END(VL53L5_TRACE_MODULE_API, status, ##__VA_ARGS__) +#define LOG_FUNCTION_END_FLUSH(status, ...) \ + do { \ + _LOG_FUNCTION_END(VL53L5_TRACE_MODULE_API, status, ##__VA_ARGS__);\ + _FLUSH_TRACE_TO_OUTPUT();\ + } while (0) + +#if defined(VL53L5_RESULTS_DATA_ENABLED) || defined(VL53L5_PATCH_DATA_ENABLED) +int32_t vl53l5_decode_range_data( + struct vl53l5_dev_handle_t *p_dev, + struct vl53l5_range_data_t *p_data) +{ + int32_t status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_ISNULL(p_data)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_COMMS_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + status = VL53L5_GET_VERSION_CHECK_STATUS(p_dev); + + if (status < STATUS_OK) + goto exit; + +#ifdef VL53L5_RESULTS_DATA_ENABLED + p_dev->host_dev.presults_dev = &p_data->core; +#endif + +#ifdef VL53L5_PATCH_DATA_ENABLED + p_dev->host_dev.ptcpm_patch_0_results_dev = &p_data->tcpm_0_patch; +#endif + + status = vl53l5_dci_decode_range_data(p_dev); + +exit: + +#ifdef VL53L5_RESULTS_DATA_ENABLED + if (!VL53L5_ISNULL(p_dev)) + p_dev->host_dev.presults_dev = NULL; +#endif + +#ifdef VL53L5_PATCH_DATA_ENABLED + if (!VL53L5_ISNULL(p_dev)) + p_dev->host_dev.ptcpm_patch_0_results_dev = NULL; +#endif + + LOG_FUNCTION_END_FLUSH(status); + return status; +} +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/api/src/vl53l5_api_ranging.c b/drivers/sensors/vl53l8/bare_driver/api/src/vl53l5_api_ranging.c new file mode 100644 index 000000000000..93bf292a33a5 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/api/src/vl53l5_api_ranging.c @@ -0,0 +1,162 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_api_ranging.h" +#include "vl53l5_globals.h" +#include "vl53l5_dci_core.h" +#include "vl53l5_dci_ranging.h" +#include "vl53l5_driver_dev_path.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform_log.h" + +#define trace_print(level, ...) \ + _LOG_TRACE_PRINT(VL53L5_TRACE_MODULE_API, \ + level, VL53L5_TRACE_FUNCTION_ALL, ##__VA_ARGS__) +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START(VL53L5_TRACE_MODULE_API, fmt, ##__VA_ARGS__) +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END(VL53L5_TRACE_MODULE_API, status, ##__VA_ARGS__) +#define LOG_FUNCTION_END_FLUSH(status, ...) \ + do { \ + _LOG_FUNCTION_END(VL53L5_TRACE_MODULE_API, status, ##__VA_ARGS__);\ + _FLUSH_TRACE_TO_OUTPUT();\ + } while (0) + +int32_t vl53l5_check_data_ready(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + uint8_t stored_stream_id = 0; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_COMMS_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + status = VL53L5_GET_VERSION_CHECK_STATUS(p_dev); + if (status < STATUS_OK) + goto exit; + + stored_stream_id = VL53L5_UI_DEV_STREAM(p_dev); + + status = vl53l5_dci_get_device_info(p_dev); + if (status < STATUS_OK) + + goto exit_reset_stream_id; + + status = vl53l5_dci_check_device_info(p_dev, + stored_stream_id, + true, + true); + if (status == VL53L5_TOO_HIGH_AMBIENT_WARNING) + goto exit; + +exit_reset_stream_id: + + VL53L5_UI_DEV_STREAM(p_dev) = stored_stream_id; + +exit: + switch (status) { + case VL53L5_NO_NEW_RANGE_DATA_ERROR: + case VL53L5_TOO_HIGH_AMBIENT_WARNING: + + case STATUS_OK: + + LOG_FUNCTION_END(status); + break; + default: + + LOG_FUNCTION_END_FLUSH(status); + break; + } + + return status; +} + +int32_t vl53l5_get_range_data( + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = VL53L5_ERROR_NONE; + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_COMMS_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + status = VL53L5_GET_VERSION_CHECK_STATUS(p_dev); + + if (status < STATUS_OK) + goto exit; + + status = vl53l5_dci_read_range(p_dev); + +exit: + LOG_FUNCTION_END_FLUSH(status); + return status; +} diff --git a/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_checks.h b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_checks.h new file mode 100644 index 000000000000..8b20e20f6e40 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_checks.h @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L5_CHECKS_H_ +#define _VL53L5_CHECKS_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "vl53l5_platform_user_data.h" + +int32_t vl53l5_check_map_version(struct vl53l5_dev_handle_t *p_dev); + +int32_t vl53l5_test_map_version(struct vl53l5_dev_handle_t *p_dev, + uint8_t *p_buffer); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_commands.h b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_commands.h new file mode 100644 index 000000000000..f0908ae82964 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_commands.h @@ -0,0 +1,83 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L5_COMMANDS_H_ +#define _VL53L5_COMMANDS_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "vl53l5_platform_user_data.h" + +int32_t vl53l5_encode_block_headers( + struct vl53l5_dev_handle_t *p_dev, + uint32_t *p_block_headers, + uint32_t num_block_headers, + bool encode_version); + +int32_t vl53l5_execute_command( + struct vl53l5_dev_handle_t *p_dev, + uint8_t command_id); +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_device.h b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_device.h new file mode 100644 index 000000000000..bd231453e267 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_device.h @@ -0,0 +1,162 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L5_DEVICE_H_ +#define _VL53L5_DEVICE_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "vl53l5_globals.h" +#include "vl53l5_types.h" +#include "dci_ui_structs.h" +#include "vl53l5_power_states.h" +#include "vl53l5_core_map.h" +#include "vl53l5_patch_structs.h" +#ifdef VL53L5_CALIBRATION_DECODE_ON +#include "vl53l5_calibration_map.h" +#endif +#ifdef VL53L5_PATCH_DATA_ENABLED +#include "vl53l5_patch_core_map.h" +#include "vl53l5_tcpm_patch_0_core_map.h" +#include "vl53l5_tcpm_patch_1_core_map.h" +#include "vl53l5_tcpm_patch_0_results_map.h" +#endif +#ifdef VL53L5_RESULTS_DATA_ENABLED +#include "vl53l5_results_map.h" +#endif + +struct vl53l5_dev_handle_internal_t { + + uint8_t *p_fw_buff; + + uint32_t fw_buff_count; + + uint8_t *p_comms_buff; + + uint32_t comms_buff_max_count; + + uint32_t comms_buff_count; + + uint32_t latest_trans_id; + + struct dci_ui__cmd_info_t cmd_status; + + struct vl53l5_core_dev_t core_dev; + +#ifdef VL53L5_RESULTS_DATA_ENABLED + + struct vl53l5_range_results_t *presults_dev; +#endif + +#ifdef VL53L5_CALIBRATION_DECODE_ON + + struct vl53l5_calibration_dev_t *pcalibration_dev; +#endif + +#ifdef VL53L5_PATCH_DATA_ENABLED + + struct vl53l5_patch_core_dev_t *ppatch_core_dev; +#endif + +#ifdef VL53L5_PATCH_DATA_ENABLED + struct vl53l5_tcpm_patch_0_core_dev_t *ptcpm_patch_0_core_dev; + + struct vl53l5_tcpm_patch_1_core_dev_t *ptcpm_patch_1_core_dev; + + struct vl53l5_tcpm_patch_0_results_dev_t *ptcpm_patch_0_results_dev; +#endif + + struct dci_ui__dev_info_t ui_dev_info; + + bool mcu_warnings_on; + + enum vl53l5_power_states power_state; + + struct { + + bool map_version_match; + } version_match; + + struct { + + uint16_t dev_info_start_addr; + + uint16_t data_start_offset_bytes; + + uint16_t data_size_bytes; + } range_data_addr; + + bool device_booted; + + struct vl53l5_patch_data_t patch_data; + + uint8_t device_id; + uint8_t revision_id; + + bool firmware_load; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_driver_dev_path.h b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_driver_dev_path.h new file mode 100644 index 000000000000..11ed10485fd8 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_driver_dev_path.h @@ -0,0 +1,87 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L5_DRIVER_DEV_PATH_H_ +#define _VL53L5_DRIVER_DEV_PATH_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define VL53L5_GO2_STATUS_0(p_dev)\ + ((p_dev)->host_dev.ui_dev_info.dev_info__go2_status_0) + +#define VL53L5_GO2_STATUS_1(p_dev)\ + ((p_dev)->host_dev.ui_dev_info.dev_info__go2_status_1) + +#define VL53L5_UI_DEV_STREAM(p_dev)\ + ((p_dev)->host_dev.ui_dev_info.dev_info__ui_stream_count) + +#define VL53L5_UI_DEV_STATUS(p_dev)\ + ((p_dev)->host_dev.ui_dev_info.dev_info__device_status) + +#define VL53L5_CMD_INFO(p_dev)\ + ((p_dev)->host_dev.cmd_status.cmd_info) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_error_codes.h b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_error_codes.h new file mode 100644 index 000000000000..b849b4d19656 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_error_codes.h @@ -0,0 +1,239 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L5_ERROR_CODES_H_ +#define _VL53L5_ERROR_CODES_H_ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_Error int32_t + +#define VL53L5_ERROR_NONE ((VL53L5_Error) 0) +#define VL53L5_FAILED_TO_CREATE_CALIB_FILE ((VL53L5_Error) - 2) + +#define VL53L5_ERROR_INVALID_PARAMS ((VL53L5_Error) - 4) + +#define VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA ((VL53L5_Error) - 5) + +#define VL53L5_INVALID_IDX_ENCODE_CMD_ERROR ((VL53L5_Error) - 6) + +#define VL53L5_ERROR_TIME_OUT ((VL53L5_Error) - 7) + +#define VL53L5_CAL_TYPE_UNRECOGNISED ((VL53L5_Error) - 8) + +#define VL53L5_INVALID_IDX_DECODE_CMD_ERROR ((VL53L5_Error) - 9) + +#define VL53L5_INVALID_PAGE_ERROR ((VL53L5_Error) - 10) + +#define VL53L5_DATA_BUFFER_MISMATCH ((VL53L5_Error) - 11) + +#define VL53L5_ERROR_NULL_DEV_PTR ((VL53L5_Error) - 12) + +#define VL53L5_ERROR_CONTROL_INTERFACE ((VL53L5_Error) - 13) + +#define VL53L5_DATA_EXCEEDS_CMD_BUFFER_SIZE ((VL53L5_Error) - 14) + +#define VL53L5_ERROR_DIVISION_BY_ZERO ((VL53L5_Error) - 15) + +#define VL53L5_ERROR_INVALID_SET_COMMAND ((VL53L5_Error) - 16) + +#define VL53L5_ERROR_CALIBRATION_REQUEST_INVALID ((VL53L5_Error) - 17) + +#define VL53L5_ERROR_IDX_NOT_PRESENT ((VL53L5_Error) - 18) + +#define VL53L5_ERROR_INVALID_BH_ENCODE ((VL53L5_Error) - 21) + +#define VL53L5_ERROR_INVALID_BH_SIZE ((VL53L5_Error) - 22) + +#define VL53L5_ERROR_NULL_CALIBRATION_DEV_PTR ((VL53L5_Error) - 23) + +#define VL53L5_GPIO_UNDEFINED ((VL53L5_Error) - 24) + +#define VL53L5_DEVICE_STATE_INVALID ((VL53L5_Error) - 25) + +#define VL53L5_ERROR_NULL_RESULTS_DEV_PTR ((VL53L5_Error) - 26) + +#define VL53L5_ERROR_COMPARE_FIRMWARE_FAILURE ((VL53L5_Error) - 40) + +#define VL53L5_ERROR_NOT_IMPLEMENTED ((VL53L5_Error) - 41) + +#define VL53L5_ERROR_POWER_STATE ((VL53L5_Error) - 42) + +#define VL53L5_PLL_LOCK_FAIL ((VL53L5_Error) - 45) + +#define VL53L5_LS_WATCHDOG_FAIL ((VL53L5_Error) - 46) + +#define VL53L5_FILE_NOT_EXIST ((VL53L5_Error) - 49) + +#define VL53L5_ERROR_BOOT_COMPLETE_TIMEOUT ((VL53L5_Error) - 51) + +#define VL53L5_ERROR_MCU_IDLE_TIMEOUT ((VL53L5_Error) - 52) + +#define VL53L5_ERROR_RANGE_DATA_READY_TIMEOUT ((VL53L5_Error) - 53) + +#define VL53L5_ERROR_CMD_STATUS_TIMEOUT ((VL53L5_Error) - 54) + +#define VL53L5_ERROR_UI_FW_STATUS_TIMEOUT ((VL53L5_Error) - 55) + +#define VL53L5_ERROR_UI_RAM_WATCHDOG_TIMEOUT ((VL53L5_Error) - 56) + +#define VL53L5_ERROR_FW_BUFF_NOT_FOUND ((VL53L5_Error) - 57) + +#define VL53L5_ERROR_MCU_ERROR_AT_ROM_BOOT ((VL53L5_Error) - 60) + +#define VL53L5_ERROR_FALSE_MCU_ERROR_AT_ROM_BOOT ((VL53L5_Error) - 61) + +#define VL53L5_ERROR_MCU_ERROR_AT_RAM_BOOT ((VL53L5_Error) - 62) + +#define VL53L5_ERROR_FALSE_MCU_ERROR_AT_RAM_BOOT ((VL53L5_Error) - 63) + +#define VL53L5_ERROR_MCU_ERROR_POWER_STATE ((VL53L5_Error) - 64) + +#define VL53L5_ERROR_FALSE_MCU_ERROR_POWER_STATE ((VL53L5_Error) - 65) + +#define VL53L5_DEV_INFO_MCU_ERROR ((VL53L5_Error) - 66) + +#define VL53L5_FALSE_DEV_INFO_MCU_ERROR ((VL53L5_Error) - 67) + +#define VL53L5_ERROR_FALSE_MCU_ERROR_IN_BANK_CHECK ((VL53L5_Error) - 68) + +#define VL53L5_ERROR_PADDING_NOT_REQUIRED ((VL53L5_Error) - 70) + +#define VL53L5_BYTE_SWAP_FAIL ((VL53L5_Error) - 71) + +#define VL53L5_IDX_MISSING_FROM_RETURN_PACKET ((VL53L5_Error) - 72) + +#define VL53L5_VERSION_IDX_NOT_PRESENT ((VL53L5_Error) - 73) + +#define VL53L5_RANGE_DATA_ADDR_NOT_PRESENT ((VL53L5_Error) - 74) + +#define VL53L5_ENCODE_CMD_ERROR ((VL53L5_Error) - 75) + +#define VL53L5_DECODE_CMD_ERROR ((VL53L5_Error) - 76) + +#define VL53L5_CONFIG_ERROR_INVALID_VERSION ((VL53L5_Error) - 77) + +#define VL53L5_DCI_VERSION_ERROR ((VL53L5_Error) - 78) + +#define VL53L5_DCI_CMD_STATUS_ERROR ((VL53L5_Error) - 79) + +#define VL53L5_INVALID_CMD_ID ((VL53L5_Error) - 80) + +#define VL53L5_DCI_END_BLOCK_ERROR ((VL53L5_Error) - 81) + +#define VL53L5_INVALID_GROUP_INDEX ((VL53L5_Error) - 82) + +#define VL53L5_BOOT_TIMEOUT_BEFORE_FW_DOWNLOAD ((VL53L5_Error) - 83) + +#define VL53L5_BOOT_TIMEOUT_AFTER_FW_DOWNLOAD ((VL53L5_Error) - 84) + +#define VL53L5_BOOT_TIMEOUT_MCU_IDLE ((VL53L5_Error) - 85) + +#define VL53L5_DCI_RANGE_INTEGRITY_ERROR ((VL53L5_Error) - 86) + +#define VL53L5_ERROR_NVM_COPY ((VL53L5_Error) - 87) + +#define VL53L5_NO_NEW_RANGE_DATA_ERROR ((VL53L5_Error) - 89) + +#define VL53L5_DATA_BUFFER_TOO_LARGE ((VL53L5_Error) - 90) + +#define VL53L5_GET_PARMS_ERROR_INVALID_IDX ((VL53L5_Error) - 91) + +#define VL53L5_MAX_BUFFER_SIZE_REACHED ((VL53L5_Error) - 92) + +#define VL53L5_COMMS_ERROR ((VL53L5_Error) - 93) + +#define VL53L5_SPI_COMMS_ERROR ((VL53L5_Error) - 94) + +#define VL53L5_I2C_COMMS_ERROR ((VL53L5_Error) - 95) + +#define VL53L5_DATA_SIZE_MISMATCH ((VL53L5_Error) - 96) + +#define VL53L5_ERROR_GPIO_SET_FAIL ((VL53L5_Error) - 97) + +#define VL53L5_READ_RANGE_ERROR_CALIB_FILE_UNSPECIFIED ((VL53L5_Error) - 98) + +#define VL53L5_VER_CHECK_NOT_PERFORMED ((VL53L5_Error) - 99) + +#define VL53L5_UNKNOWN_SILICON_REVISION ((VL53L5_Error) - 100) + +#define VL53L5_TOO_HIGH_AMBIENT_WARNING ((VL53L5_Error) - 58) +#define VL53L5_ERROR_MCU_ERROR_WAIT_STATE ((VL53L5_Error) - 69) +#define VL53L5_ERROR_MCU_ERROR_HW_STATE ((VL53L5_Error) - 59) +#define VL53L5_ERROR_MCU_NVM_NOT_PROGRAMMED ((VL53L5_Error) - 50) +#define VL53L5_ERROR_INIT_FW_CHECKSUM ((VL53L5_Error) - 48) +#define VL53L5_ERROR_PATCH_SIZE_MISMATCH ((VL53L5_Error) - 30) +#define VL53L5_INVALID_SILICON_REVISION ((VL53L5_Error) - 31) +#define VL53L5_ERROR_INVALID_PATCH_BOOT_FLAG ((VL53L5_Error) - 32) +#define VL53L5_ERROR_INVALID_RETURN_DATA_SIZE ((VL53L5_Error) - 19) +#define VL53L5_ERROR_INVALID_VALUE ((VL53L5_Error) - 20) + +#define VL53L5_NEW_RANGE_DATA_PRESENT ((VL53L5_Error) 1) + +#define VL53L5_ERROR_UNDEFINED ((VL53L5_Error) - 999) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_error_handler.h b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_error_handler.h new file mode 100644 index 000000000000..b3e4eec80ee6 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_error_handler.h @@ -0,0 +1,94 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 VL53L5_ERROR_HANDLER_H_ +#define VL53L5_ERROR_HANDLER_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "common_datatype_structs.h" +#include "vl53l5_platform_user_data.h" + +int32_t vl53l5_check_status_registers( + struct vl53l5_dev_handle_t *p_dev, + union dci_union__go2_status_0_go1_u *p_go2_status_0, + union dci_union__go2_status_1_go1_u *p_go2_status_1, + bool ignore_warnings, + uint8_t current_page); + +int32_t vl53l5_compose_fw_status_code( + struct vl53l5_dev_handle_t *p_dev, + struct common_grp__status_t *p_status); + +int32_t vl53l5_get_secondary_error_info( + struct vl53l5_dev_handle_t *p_dev, + struct common_grp__status_t *p_status); + +int32_t vl53l5_get_secondary_warning_info( + struct vl53l5_dev_handle_t *p_dev, + struct common_grp__status_t *p_status); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_globals.h b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_globals.h new file mode 100644 index 000000000000..ffb7aacc417c --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_globals.h @@ -0,0 +1,218 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L5_GLOBALS_H_ +#define _VL53L5_GLOBALS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "vl53l5_types.h" +#include "vl53l5_results_config.h" +#include "dci_ui_memory_defs.h" +#include "dci_size.h" +#include "dci_ui_size.h" + +#ifdef VL53L5_PATCH_DATA_ENABLED +#ifdef VL53L5_SILICON_TEMP_DATA_ON +#define VL53L5_SILICON_TEMP_DATA_BLOCK_SZ \ + (VL53L5_SILICON_TEMPERATURE_DATA_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_SILICON_TEMP_DATA_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_ZONE_CFG_ON +#define VL53L5_ZONE_CFG_BLOCK_SZ \ + (VL53L5_ZONE_CFG_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_ZONE_CFG_BLOCK_SZ \ + 0 +#endif + +#endif +#if defined(VL53L5_RESULTS_DATA_ENABLED) && !defined(VL53L5_PATCH_DATA_ENABLED) +#define VL53L5_RESULTS_SIZE_BYTES VL53L5_RESULTS_TOTAL_SIZE_BYTES +#endif + +#if defined(VL53L5_PATCH_DATA_ENABLED) && !defined(VL53L5_RESULTS_DATA_ENABLED) +#define VL53L5_RESULTS_SIZE_BYTES \ + (VL53L5_TCPM_PATCH_0_MAP_RESULTS_TOTAL_SIZE_BYTES + \ + VL53L5_SILICON_TEMP_DATA_BLOCK_SZ + \ + VL53L5_ZONE_CFG_BLOCK_SZ) +#endif + +#if defined(VL53L5_PATCH_DATA_ENABLED) && defined(VL53L5_RESULTS_DATA_ENABLED) +#define VL53L5_TCPM_RESULTS_SIZE_BYTES \ + (VL53L5_TCPM_PATCH_0_MAP_RESULTS_TOTAL_SIZE_BYTES + \ + VL53L5_SILICON_TEMP_DATA_BLOCK_SZ + \ + VL53L5_ZONE_CFG_BLOCK_SZ) +#define VL53L5_RESULTS_SIZE_BYTES \ + (VL53L5_TCPM_RESULTS_SIZE_BYTES > VL53L5_RESULTS_TOTAL_SIZE_BYTES ? \ + VL53L5_TCPM_RESULTS_SIZE_BYTES : VL53L5_RESULTS_TOTAL_SIZE_BYTES) +#endif + +#define VL53L5_ISNULL(ptr) ((ptr) == NULL) + +#define VL53L5_COMMS_BUFF_ISNULL(p_dev)\ + ((p_dev)->host_dev.p_comms_buff == NULL) + +#define VL53L5_COMMS_BUFF_ISEMPTY(p_dev)\ + ((p_dev)->host_dev.comms_buff_count == 0) + +#define VL53L5_COMMS_BUFF(p_dev)\ + ((p_dev)->host_dev.p_comms_buff) + +#define VL53L5_COMMS_BUFF_COUNT(p_dev)\ + ((p_dev)->host_dev.comms_buff_count) + +#define VL53L5_COMMS_BUFF_MAX_COUNT(p_dev)\ + ((p_dev)->host_dev.comms_buff_max_count) + +#define VL53L5_CHECK_COMMS_BUFF_AVAILABLE_BYTES(p_dev, required_bytes)\ + ((VL53L5_COMMS_BUFF_COUNT(p_dev) + required_bytes) <\ + VL53L5_COMMS_BUFF_MAX_COUNT(p_dev)) + +#define VL53L5_COMMS_BUFF_NEXT_BYTE(p_dev)\ + (&VL53L5_COMMS_BUFF(p_dev)[VL53L5_COMMS_BUFF_COUNT(p_dev)]) + +#define VL53L5_FW_BUFF_ISNULL(p_dev)\ + ((p_dev)->host_dev.p_fw_buff == NULL) + +#define VL53L5_FW_BUFF_ISEMPTY(p_dev)\ + ((p_dev)->host_dev.fw_buff_count == 0) + +#define VL53L5_RESET_COMMS_BUFF(p_dev)\ + ((p_dev)->host_dev.comms_buff_count = 0) + +#define VL53L5_GET_VERSION_CHECK_STATUS(p_dev)\ + (((p_dev)->host_dev.version_match.map_version_match == true) ? \ + STATUS_OK : VL53L5_VER_CHECK_NOT_PERFORMED) + +#define VL53L5_DEV_RANGE_UI_SIZE_BYTES \ + (((DCI_UI__COMMAND_INFO__START_IDX & 0xFFFF) - \ + (DCI_UI__DEVICE_INFO__START_IDX & 0xFFFF)) - 4) + +#define VL53L5_MIN_FW_ERROR ((int32_t) 0xfe000000) + +#define VL53L5_MAX_FW_ERROR ((int32_t) 0xfeffffff) + +#define VL53L5_MAX_FW_STATUS_1_WARNING ((uint8_t) 0x7f) + +#define STATUS_OK 0 + +#define VL53L5_MAX_RANGE_UI_SIZE_BYTES \ + ((VL53L5_DEV_RANGE_UI_SIZE_BYTES > VL53L5_RESULTS_SIZE_BYTES) ? \ + VL53L5_RESULTS_SIZE_BYTES : VL53L5_DEV_RANGE_UI_SIZE_BYTES) + +#define VL53L5_MAX_CMD_UI_SIZE_BYTES \ + (((DCI_UI__COMMAND_INFO__START_IDX - DCI_UI__RANGE_DATA__START_IDX) \ + & 0xffff) - 4) + +#define VL53L5_DEV_INFO_BLOCK_SZ DCI_UI__DEVICE_INFO__SIZE_BYTES + +#define VL53L5_HEADER_FOOTER_BLOCK_SZ DCI_UI__DEVICE_INFO__SIZE_BYTES + +#define VL53L5_UI_DUMMY_BYTES 4 + +#define VL53L5_CONFIG_HEADER_SIZE \ + (2 * VL53L5_DCI_UI_PACKED_DATA_BH_SZ + \ + VL53L5_MAP_VERSION_SZ + \ + VL53L5_CFG_INFO_SZ) + +#define VL53L5_CALIBRATION_HEADER_SIZE \ + (VL53L5_DCI_UI_PACKED_DATA_BH_SZ + VL53L5_MAP_VERSION_SZ) + +#define VL53L5_MAX_CONF_CAL_SIZE_BYTES VL53L5_CONFIG_SIZE_BYTES + +#define VL53L5_MAX_APPEND_SIZE_BYTES 100 + +#define VL53L5_COMMS_BUFFER_SIZE_BYTES \ + ((VL53L5_MAX_CONF_CAL_SIZE_BYTES + VL53L5_MAX_APPEND_SIZE_BYTES >\ + VL53L5_MAX_RANGE_UI_SIZE_BYTES) ?\ + (VL53L5_MAX_CONF_CAL_SIZE_BYTES + VL53L5_MAX_APPEND_SIZE_BYTES) :\ + VL53L5_MAX_RANGE_UI_SIZE_BYTES) + +#define VL53L5_DEBUG_BUFFER_SIZE_BYTES VL53L5_DEV_RANGE_UI_SIZE_BYTES + +#define VL53L5_MAX_INPUT_DATA_LENGTH \ + ((VL53L5_COMMS_BUFFER_SIZE_BYTES > VL53L5_MAX_CMD_UI_SIZE_BYTES) ?\ + VL53L5_MAX_CMD_UI_SIZE_BYTES - 8 : VL53L5_COMMS_BUFFER_SIZE_BYTES - 8) + +#define BYTE_1 1 +#define BYTE_2 2 +#define BYTE_3 3 +#define BYTE_4 4 +#define BYTE_8 8 + +#define MAX_NUM_RANGE_RETURNS 0 + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +#define VL53L5_BOOT_COMPLETION_POLLING_TIMEOUT_MS 500 +#else +#define VL53L5_BOOT_COMPLETION_POLLING_TIMEOUT_MS 100 +#endif +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_load_firmware.h b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_load_firmware.h new file mode 100644 index 000000000000..de369539b6bf --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_load_firmware.h @@ -0,0 +1,76 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L5_LOAD_FIRMWARE_H_ +#define _VL53L5_LOAD_FIRMWARE_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "vl53l5_platform_user_data.h" + +int32_t vl53l5_load_firmware(struct vl53l5_dev_handle_t *p_dev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_patch_structs.h b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_patch_structs.h new file mode 100644 index 000000000000..a238917b3e4c --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_patch_structs.h @@ -0,0 +1,157 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_STRUCTS_H__ +#define __VL53L5_PATCH_STRUCTS_H__ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "vl53l5_types.h" + +#define VL53L5_PATCH_LOAD 1 +#define VL53L5_PATCH_BOOT 2 +#define VL53L5_RAM_LOAD 3 + +struct vl53l5_patch_data_t { + uint32_t patch_ver_major; + uint32_t patch_ver_minor; + uint32_t patch_ver_build; + uint32_t patch_ver_revision; + + uint32_t boot_flag; + + uint32_t patch_offset; + + uint32_t patch_size; + + uint32_t patch_checksum; + + uint32_t tcpm_offset; + + uint32_t tcpm_size; + + uint32_t tcpm_page; + + uint32_t tcpm_page_offset; + + uint32_t hooks_offset; + + uint32_t hooks_size; + + uint32_t hooks_page; + + uint32_t hooks_page_offset; + + uint32_t breakpoint_en_offset; + + uint32_t breakpoint_en_size; + + uint32_t breakpoint_en_page; + + uint32_t breakpoint_en_page_offset; + + uint32_t breakpoint_offset; + + uint32_t breakpoint_size; + + uint32_t breakpoint_page; + + uint32_t breakpoint_page_offset; + + uint32_t checksum_en_offset; + + uint32_t checksum_en_size; + + uint32_t checksum_en_page; + + uint32_t checksum_en_page_offset; + + uint32_t patch_code_offset; + + uint32_t patch_code_size; + + uint32_t patch_code_page; + + uint32_t patch_code_page_offset; + + uint32_t dci_tcpm_patch_0_offset; + + uint32_t dci_tcpm_patch_0_size; + + uint32_t dci_tcpm_patch_0_page; + + uint32_t dci_tcpm_patch_0_page_offset; + + uint32_t dci_tcpm_patch_1_offset; + + uint32_t dci_tcpm_patch_1_size; + + uint32_t dci_tcpm_patch_1_page; + + uint32_t dci_tcpm_patch_1_page_offset; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_power_states.h b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_power_states.h new file mode 100644 index 000000000000..8791459a7b47 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_power_states.h @@ -0,0 +1,107 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L5_POWER_STATES_H_ +#define _VL53L5_POWER_STATES_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define VL53L5_CHECK_POWER_STATE(p_dev, state)\ + ((p_dev)->host_dev.power_state == (state)) + +#define VL53L5_CHECK_POWER_STATE_OFF(p_dev)\ + (VL53L5_CHECK_POWER_STATE((p_dev), VL53L5_POWER_STATE_OFF)) + +#define VL53L5_CHECK_POWER_STATE_ULP_IDLE(p_dev)\ + (VL53L5_CHECK_POWER_STATE((p_dev),\ + VL53L5_POWER_STATE_ULP_IDLE)) + +#define VL53L5_CHECK_POWER_STATE_LP_IDLE_COMMS(p_dev)\ + (VL53L5_CHECK_POWER_STATE((p_dev),\ + VL53L5_POWER_STATE_LP_IDLE_COMMS)) + +#define VL53L5_CHECK_POWER_STATE_HP_IDLE(p_dev)\ + (VL53L5_CHECK_POWER_STATE((p_dev),\ + VL53L5_POWER_STATE_HP_IDLE)) + +#define VL53L5_CHECK_POWER_STATE_RANGING(p_dev)\ + (VL53L5_CHECK_POWER_STATE((p_dev),\ + VL53L5_POWER_STATE_RANGING)) + +enum vl53l5_power_states { + + VL53L5_POWER_STATE_OFF = 0, + + VL53L5_POWER_STATE_ULP_IDLE = 1, + + VL53L5_POWER_STATE_LP_IDLE_COMMS = 3, + + VL53L5_POWER_STATE_HP_IDLE = 4, + + VL53L5_POWER_STATE_RANGING = 5 +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_register_utils.h b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_register_utils.h new file mode 100644 index 000000000000..266675691af4 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_register_utils.h @@ -0,0 +1,128 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 VL53L5_REGISTER_UTILS_H_ +#define VL53L5_REGISTER_UTILS_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "vl53l5_platform_user_data.h" + +enum vl53l5_boot_state { + + VL53L5_BOOT_STATE_LOW = 0, + + VL53L5_BOOT_STATE_HIGH = 1, + + VL53L5_BOOT_STATE_ERROR = 2 +}; + +#define VL53L5_GO2_STATUS_0(p_dev) \ + ((p_dev)->host_dev.ui_dev_info.dev_info__go2_status_0) + +#define VL53L5_GO2_STATUS_1(p_dev) \ + ((p_dev)->host_dev.ui_dev_info.dev_info__go2_status_1) + +#define HW_TRAP(p_dev)\ + (VL53L5_GO2_STATUS_0(p_dev).mcu__hw_trap_flag_go1 == 1) + +#define MCU_ERROR(p_dev)\ + (VL53L5_GO2_STATUS_0(p_dev).mcu__error_flag_go1 == 1) + +#define MCU_BOOT_COMPLETE(p_dev)\ + (VL53L5_GO2_STATUS_0(p_dev).mcu__boot_complete_go1 == 1) + +#define MCU_BOOT_NOT_COMPLETE(p_dev)\ + (VL53L5_GO2_STATUS_0(p_dev).mcu__boot_complete_go1 == 0) + +#define MCU_FIRST_BOOT(p_dev)\ + (VL53L5_GO2_STATUS_1(p_dev).bytes == 0x1f) + +#define MCU_NVM_PROGRAMMED(p_dev)\ + (VL53L5_GO2_STATUS_1(p_dev).mcu__spare1 == 1) + +int32_t vl53l5_register_read_modify_write( + struct vl53l5_dev_handle_t *p_dev, uint16_t addr, + uint8_t first_and_mask, uint8_t second_or_mask); + +int32_t vl53l5_set_page(struct vl53l5_dev_handle_t *p_dev, uint8_t page); + +int32_t vl53l5_set_regulators( + struct vl53l5_dev_handle_t *p_dev, + uint8_t lp_reg_enable, + uint8_t hp_reg_enable); + +int32_t vl53l5_set_xshut_bypass( + struct vl53l5_dev_handle_t *p_dev, uint8_t state); + +int32_t vl53l5_set_manual_xshut_state( + struct vl53l5_dev_handle_t *p_dev, uint8_t state); + +int32_t vl53l5_wait_mcu_boot( + struct vl53l5_dev_handle_t *p_dev, enum vl53l5_boot_state state, + uint32_t timeout_ms, uint32_t wait_time_ms); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_results_config.h b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_results_config.h new file mode 100644 index 000000000000..11b9191cecbc --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_results_config.h @@ -0,0 +1,80 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_RESULTS_CONFIG_H__ +#define __VL53L5_RESULTS_CONFIG_H__ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifdef VL53L5_RESULTS_DATA_ENABLED +#include "vl53l5_main_results_config.h" +#endif + +#ifdef VL53L5_PATCH_DATA_ENABLED +#include "vl53l5_tcpm_patch_0_results_config.h" +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_rom_boot.h b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_rom_boot.h new file mode 100644 index 000000000000..b800b701de29 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_rom_boot.h @@ -0,0 +1,76 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 VL53L5_ROM_BOOT_H_ +#define VL53L5_ROM_BOOT_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "vl53l5_platform_user_data.h" + +int32_t vl53l5_check_rom_firmware_boot(struct vl53l5_dev_handle_t *p_dev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_tcdm_dump.h b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_tcdm_dump.h new file mode 100644 index 000000000000..a969236b5f03 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_tcdm_dump.h @@ -0,0 +1,82 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L5_TCDM_DUMP_H_ +#define _VL53L5_TCDM_DUMP_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifdef VL53L5_TCDM_ENABLE +#include "vl53l5_platform_user_data.h" + +#define VL53L5_TCDM_BUFFER_SIZE 0x10004 +#define VL53L5_TCDM_DUMP_STATUS_TIMEOUT 500 + +int32_t vl53l5_tcdm_dump(struct vl53l5_dev_handle_t *p_dev, + uint8_t *buffer, uint32_t *count); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_trans.h b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_trans.h new file mode 100644 index 000000000000..867d263bddd4 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_trans.h @@ -0,0 +1,80 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 VL53L5_TRANS_H_ +#define VL53L5_TRANS_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "vl53l5_platform_user_data.h" + +#define VL53L5_GET_CURRENT_TRANS_ID(p_dev) \ + ((p_dev)->host_dev.latest_trans_id) + +#define VL53L5_GET_NEXT_TRANS_ID(p_dev) \ + (++(p_dev)->host_dev.latest_trans_id) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_version.h b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_version.h new file mode 100644 index 000000000000..8b99c76a6e94 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/common/inc/vl53l5_version.h @@ -0,0 +1,77 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 VL53L5_VERSION_H_ +#define VL53L5_VERSION_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define VL53L5_VERSION_MAJOR 4 +#define VL53L5_VERSION_MINOR 3 +#define VL53L5_VERSION_BUILD 0 +#define VL53L5_VERSION_REVISION 1 + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/common/src/vl53l5_checks.c b/drivers/sensors/vl53l8/bare_driver/common/src/vl53l5_checks.c new file mode 100644 index 000000000000..15ad3e05824e --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/common/src/vl53l5_checks.c @@ -0,0 +1,186 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_checks.h" +#include "vl53l5_commands.h" +#include "vl53l5_register_utils.h" +#include "vl53l5_globals.h" +#include "vl53l5_platform_log.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_dci_core.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_core_map_bh.h" + +#define trace_print(level, ...) \ + _LOG_TRACE_PRINT(VL53L5_TRACE_MODULE_DCI, \ + level, VL53L5_TRACE_FUNCTION_ALL, ##__VA_ARGS__) + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START(VL53L5_TRACE_MODULE_DCI, fmt, ##__VA_ARGS__) +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END(VL53L5_TRACE_MODULE_DCI, status, ##__VA_ARGS__) + +#define TEST_NVM_REG_INDEX 0x21 + +#define PAGE_SELECT_INDEX 0x7fff + +static int32_t _test_end_block(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = VL53L5_ERROR_NONE; + uint32_t end_of_data_footer_index = + VL53L5_MAP_VERSION_SZ + VL53L5_DCI_UI_PACKED_DATA_BH_SZ; + uint8_t *p_buff = NULL; + + if (p_dev->host_dev.revision_id == 0x0C) + p_buff = &VL53L5_COMMS_BUFF(p_dev)[VL53L5_UI_DUMMY_BYTES]; + else + p_buff = VL53L5_COMMS_BUFF(p_dev); + + if ((p_buff[end_of_data_footer_index] & 0x0f) != + DCI_BH__P_TYPE__END_OF_DATA) + status = VL53L5_DCI_END_BLOCK_ERROR; + + return status; +} + +int32_t vl53l5_test_map_version(struct vl53l5_dev_handle_t *p_dev, + uint8_t *p_buffer) +{ + int32_t status = VL53L5_ERROR_NONE; + uint8_t *p_buff = p_buffer; + uint16_t map_major = 0; + uint16_t map_minor = 0; + uint32_t block_header = 0; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + if (VL53L5_COMMS_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + block_header = vl53l5_decode_uint32_t(BYTE_4, p_buff); + if (block_header != VL53L5_MAP_VERSION_BH) { + status = VL53L5_VERSION_IDX_NOT_PRESENT; + goto exit; + } + p_buff += 4; + + map_major = vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += 2; + + map_minor = vl53l5_decode_uint16_t(BYTE_2, p_buff); + + trace_print( + VL53L5_TRACE_LEVEL_INFO, "Map version: %u.%u\n", + map_major, map_minor); + + if ((map_major != MAP_VERSION_MAJOR) || + (map_minor != MAP_VERSION_MINOR)) { + status = VL53L5_DCI_VERSION_ERROR; + goto exit; + } + +exit: + LOG_FUNCTION_END(status); + return status; +} + +int32_t vl53l5_check_map_version(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = VL53L5_ERROR_NONE; + uint32_t block_headers[] = {VL53L5_MAP_VERSION_BH}; + uint8_t *p_buff = NULL; + + LOG_FUNCTION_START(""); + + p_dev->host_dev.version_match.map_version_match = false; + + status = vl53l5_encode_block_headers(p_dev, block_headers, 1, false); + if (status != VL53L5_ERROR_NONE) + goto exit; + + status = vl53l5_execute_command(p_dev, DCI_CMD_ID__GET_PARMS); + if (status != VL53L5_ERROR_NONE) + goto exit; + + status = _test_end_block(p_dev); + if (status != VL53L5_ERROR_NONE) + goto exit; + + if (p_dev->host_dev.revision_id == 0x0C) + p_buff = &VL53L5_COMMS_BUFF(p_dev)[VL53L5_UI_DUMMY_BYTES]; + else + p_buff = VL53L5_COMMS_BUFF(p_dev); + + status = vl53l5_test_map_version(p_dev, p_buff); + if (status != VL53L5_ERROR_NONE) + goto exit; + + p_dev->host_dev.version_match.map_version_match = true; + +exit: + LOG_FUNCTION_END(status); + return status; +} diff --git a/drivers/sensors/vl53l8/bare_driver/common/src/vl53l5_commands.c b/drivers/sensors/vl53l8/bare_driver/common/src/vl53l5_commands.c new file mode 100644 index 000000000000..90a6490db19e --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/common/src/vl53l5_commands.c @@ -0,0 +1,183 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_commands.h" +#include "vl53l5_globals.h" +#include "vl53l5_trans.h" +#include "vl53l5_platform_log.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_dci_core.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_core_map_bh.h" + +#define trace_print(level, ...) \ + _LOG_TRACE_PRINT(VL53L5_TRACE_MODULE_DCI, \ + level, VL53L5_TRACE_FUNCTION_ALL, ##__VA_ARGS__) + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START(VL53L5_TRACE_MODULE_DCI, fmt, ##__VA_ARGS__) +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END(VL53L5_TRACE_MODULE_DCI, status, ##__VA_ARGS__) + +#define BLOCK_HEADER_SIZE 4 + +#define COMMAND_HAS_RETURN(command_id) \ + ((command_id == DCI_CMD_ID__GET_PARMS) ||\ + (command_id == DCI_CMD_ID__START_RANGE)) + +int32_t vl53l5_encode_block_headers( + struct vl53l5_dev_handle_t *p_dev, + uint32_t *p_block_headers, + uint32_t num_block_headers, + bool encode_version) +{ + int32_t status = VL53L5_ERROR_NONE; + uint32_t num_bh = num_block_headers; + uint32_t i = 0; + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_ISNULL(p_block_headers)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (num_block_headers == 0) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_COMMS_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + if (encode_version) + num_bh++; + + if ((num_bh * BLOCK_HEADER_SIZE) > + VL53L5_COMMS_BUFF_MAX_COUNT(p_dev)) { + status = VL53L5_MAX_BUFFER_SIZE_REACHED; + goto exit; + } + + VL53L5_RESET_COMMS_BUFF(p_dev); + + if (encode_version) { + vl53l5_encode_uint32_t( + VL53L5_MAP_VERSION_BH, BLOCK_HEADER_SIZE, + VL53L5_COMMS_BUFF_NEXT_BYTE(p_dev)); + VL53L5_COMMS_BUFF_COUNT(p_dev) += BLOCK_HEADER_SIZE; + } + + for (i = 0; i < num_block_headers; i++) { + + if ((p_block_headers[i] == VL53L5_MAP_VERSION_BH) && + (encode_version)) + continue; + + vl53l5_encode_uint32_t( + p_block_headers[i], BLOCK_HEADER_SIZE, + VL53L5_COMMS_BUFF_NEXT_BYTE(p_dev)); + VL53L5_COMMS_BUFF_COUNT(p_dev) += BLOCK_HEADER_SIZE; + } + +exit: + return status; +} + +int32_t vl53l5_execute_command( + struct vl53l5_dev_handle_t *p_dev, + uint8_t command_id) +{ + int32_t status = VL53L5_ERROR_NONE; + uint8_t trans_id = 0; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_COMMS_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (command_id == DCI_CMD_ID__NULL) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + trans_id = VL53L5_GET_NEXT_TRANS_ID(p_dev); + status = vl53l5_dci_write_command(p_dev, command_id, trans_id); + if (status != STATUS_OK) + goto exit; + + status = vl53l5_dci_poll_command_status(p_dev, trans_id, 0); + if (status != STATUS_OK) + goto exit; + + if (COMMAND_HAS_RETURN(command_id)) + + status = vl53l5_dci_read_command(p_dev); + +exit: + LOG_FUNCTION_END(status); + return status; +} diff --git a/drivers/sensors/vl53l8/bare_driver/common/src/vl53l5_error_handler.c b/drivers/sensors/vl53l8/bare_driver/common/src/vl53l5_error_handler.c new file mode 100644 index 000000000000..e4fcc4b245b4 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/common/src/vl53l5_error_handler.c @@ -0,0 +1,361 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_error_handler.h" +#include "vl53l5_commands.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform.h" +#include "vl53l5_trans.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_dci_core.h" +#include "vl53l5_core_map_bh.h" + +#define trace_print(level, ...) \ + _LOG_TRACE_PRINT(VL53L5_TRACE_MODULE_POWER_API, \ + level, VL53L5_TRACE_FUNCTION_ALL, ##__VA_ARGS__) + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START(VL53L5_TRACE_MODULE_POWER_API, fmt, ##__VA_ARGS__) +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END(VL53L5_TRACE_MODULE_POWER_API, status, ##__VA_ARGS__) + +#define PAGE_SELECT 0x7FFF + +#define GO2_STATUS_0 0x6 +#define GO2_STATUS_1 0x7 + +#define BASE_FW_ERROR 0xE0000000 + +#define FW_ERROR_MIN_ID 128 + +static int32_t _get_status_struct( + struct vl53l5_dev_handle_t *p_dev, + struct common_grp__status_t *p_status, + uint32_t block_header) +{ + int32_t status = STATUS_OK; + uint32_t check_block_header = 0; + uint8_t *p_buff = NULL; + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto out; + } + if (VL53L5_ISNULL(p_status)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto out; + } + if (VL53L5_COMMS_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto out; + } + + status = vl53l5_encode_block_headers(p_dev, &block_header, 1, false); + if (status != STATUS_OK) + goto out; + + status = vl53l5_execute_command(p_dev, DCI_CMD_ID__GET_PARMS); + if (status != STATUS_OK) + goto out; + + if (p_dev->host_dev.revision_id == 0x0C) + p_buff = &VL53L5_COMMS_BUFF(p_dev)[VL53L5_UI_DUMMY_BYTES]; + else + p_buff = VL53L5_COMMS_BUFF(p_dev); + + check_block_header = vl53l5_decode_uint32_t(BYTE_4, p_buff); + if (check_block_header != block_header) { + status = VL53L5_IDX_MISSING_FROM_RETURN_PACKET; + goto out; + } + + p_buff += 4; + p_status->status__group = vl53l5_decode_int16_t(BYTE_2, p_buff); + p_buff += 2; + p_status->status__type = vl53l5_decode_int16_t(BYTE_2, p_buff); + p_buff += 2; + p_status->status__stage_id = vl53l5_decode_int16_t(BYTE_2, p_buff); + p_buff += 2; + p_status->status__debug_0 = vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += 2; + p_status->status__debug_1 = vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += 2; + p_status->status__debug_2 = vl53l5_decode_uint16_t(BYTE_2, p_buff); + + trace_print( + VL53L5_TRACE_LEVEL_DEBUG, + "group:%i type:%i stage:%i debug0:%i debug1:%i debug2:%i\n", + p_status->status__group, + p_status->status__type, + p_status->status__stage_id, + p_status->status__debug_0, + p_status->status__debug_1, + p_status->status__debug_2); +out: + LOG_FUNCTION_END(status); + return status; +} + +int32_t vl53l5_check_status_registers( + struct vl53l5_dev_handle_t *p_dev, + union dci_union__go2_status_0_go1_u *p_go2_status_0, + union dci_union__go2_status_1_go1_u *p_go2_status_1, + bool ignore_warnings, + uint8_t current_page) +{ + int32_t status = STATUS_OK; + uint8_t wr_byte = 0; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto out_no_page_change; + } + if (VL53L5_ISNULL(p_go2_status_0)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto out_no_page_change; + } + if (VL53L5_ISNULL(p_go2_status_1)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto out_no_page_change; + } + + if (current_page != 0) { + wr_byte = 0x00; + status = vl53l5_write_multi(p_dev, PAGE_SELECT, &wr_byte, 1); + if (status < STATUS_OK) + goto out_no_page_change; + } + + status = vl53l5_read_multi( + p_dev, GO2_STATUS_0, &p_go2_status_0->bytes, 1); + if (status < STATUS_OK) + goto out; + + trace_print( + VL53L5_TRACE_LEVEL_DEBUG, + "go2 status 0\n" + "bytes: 0x%x\n" + "mcu__boot_complete_go1: %x\n" + "mcu__analog_checks_ok_go1: %x\n" + "mcu__threshold_triggered_g01: %x\n" + "mcu__error_flag_go1: %x\n" + "mcu__ui_range_data_present_go1: %x\n" + "mcu__ui_new_range_data_avail_go1: %x\n" + "mcu__ui_update_blocked_go1: %x\n" + "mcu__hw_trap_flag_go1: %x\n", + p_go2_status_0->bytes, + p_go2_status_0->mcu__boot_complete_go1, + p_go2_status_0->mcu__analog_checks_ok_go1, + p_go2_status_0->mcu__threshold_triggered_g01, + p_go2_status_0->mcu__error_flag_go1, + p_go2_status_0->mcu__ui_range_data_present_go1, + p_go2_status_0->mcu__ui_new_range_data_avail_go1, + p_go2_status_0->mcu__ui_update_blocked_go1, + p_go2_status_0->mcu__hw_trap_flag_go1); + + if ((p_go2_status_0->mcu__error_flag_go1) && + (!p_go2_status_0->mcu__hw_trap_flag_go1)) { + + p_go2_status_1->bytes = 0; + goto out; + } + + status = vl53l5_read_multi( + p_dev, GO2_STATUS_1, &p_go2_status_1->bytes, 1); + if (status < STATUS_OK) + goto out; + + trace_print( + VL53L5_TRACE_LEVEL_DEBUG, + "go2 status 1\n" + "bytes: 0x%x\n" + "mcu__avdd_reg_ok_go1: %x\n" + "mcu__pll_lock_ok_go1: %x\n" + "mcu__ls_watchdog_pass_go1: %x\n" + "mcu__warning_flag_go1: %x\n" + "mcu__cp_collapse_flag_go1: %x\n" + "mcu__spare0: %x\n" + "mcu__initial_ram_boot_complete: %x\n" + "mcu__spare1: %x\n", + p_go2_status_1->bytes, + p_go2_status_1->mcu__avdd_reg_ok_go1, + p_go2_status_1->mcu__pll_lock_ok_go1, + p_go2_status_1->mcu__ls_watchdog_pass_go1, + p_go2_status_1->mcu__warning_flag_go1, + p_go2_status_1->mcu__cp_collapse_flag_go1, + p_go2_status_1->mcu__spare0, + p_go2_status_1->mcu__initial_ram_boot_complete, + p_go2_status_1->mcu__spare1); + + if (p_go2_status_0->mcu__hw_trap_flag_go1 && + (p_go2_status_1->bytes == 0)) { + status = VL53L5_ERROR_FALSE_MCU_ERROR_IN_BANK_CHECK; + goto out; + } + + if (!p_go2_status_0->mcu__hw_trap_flag_go1 && + ignore_warnings && + p_go2_status_1->mcu__warning_flag_go1) + p_go2_status_1->bytes = 0; + +out: + if (current_page != 0) { + + wr_byte = current_page; + + if (status < STATUS_OK) + (void)vl53l5_write_multi( + p_dev, PAGE_SELECT, &wr_byte, 1); + else + status = vl53l5_write_multi( + p_dev, PAGE_SELECT, &wr_byte, 1); + } + +out_no_page_change: + + if (status < STATUS_OK) { + if (!VL53L5_ISNULL(p_go2_status_0)) + p_go2_status_0->bytes = 0; + if (!VL53L5_ISNULL(p_go2_status_1)) + p_go2_status_1->bytes = 0; + } + + LOG_FUNCTION_END(status); + return status; +} + +int32_t vl53l5_get_secondary_error_info( + struct vl53l5_dev_handle_t *p_dev, + struct common_grp__status_t *p_status) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + status = _get_status_struct( + p_dev, p_status, VL53L5_DEVICE_ERROR_STATUS_BH); + if (status != STATUS_OK) + goto exit; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +int32_t vl53l5_get_secondary_warning_info( + struct vl53l5_dev_handle_t *p_dev, + struct common_grp__status_t *p_status) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + status = _get_status_struct( + p_dev, p_status, VL53L5_DEVICE_WARNING_STATUS_BH); + if (status != STATUS_OK) + goto exit; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +int32_t vl53l5_compose_fw_status_code( + struct vl53l5_dev_handle_t *p_dev, + struct common_grp__status_t *p_status) +{ + int32_t status = STATUS_OK; + uint32_t tmp = 0; + uint16_t *p_reinterpret = NULL; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto out; + } + if (VL53L5_ISNULL(p_status)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto out; + } + + if (!p_status->status__type) + goto out; + + status = BASE_FW_ERROR; + + p_reinterpret = (uint16_t *)&p_status->status__group; + tmp |= ((uint32_t)(0xff & *p_reinterpret) << 24); + + p_reinterpret = (uint16_t *)&p_status->status__type; + tmp |= ((uint32_t)(0xff & *p_reinterpret) << 16); + + p_reinterpret = (uint16_t *)&p_status->status__stage_id; + tmp |= ((uint32_t)(0xff & *p_reinterpret) << 8); + + p_reinterpret = (uint16_t *)&p_status->status__debug_2; + tmp |= (uint32_t)(0xff & *p_reinterpret); + + status += (int32_t)tmp; +out: + LOG_FUNCTION_END(status); + return status; +} diff --git a/drivers/sensors/vl53l8/bare_driver/common/src/vl53l5_load_firmware.c b/drivers/sensors/vl53l8/bare_driver/common/src/vl53l5_load_firmware.c new file mode 100644 index 000000000000..3339908108a8 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/common/src/vl53l5_load_firmware.c @@ -0,0 +1,736 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_load_firmware.h" +#include "vl53l5_platform.h" +#include "vl53l5_register_utils.h" +#include "vl53l5_platform_log.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_dci_helpers.h" +#include "dci_ui_memory_defs.h" +#include "vl53l5_platform_user_config.h" + +#define trace_print(level, ...) \ + _LOG_TRACE_PRINT(VL53L5_TRACE_MODULE_LOAD_FIRMWARE, \ + level, VL53L5_TRACE_FUNCTION_ALL, ##__VA_ARGS__) + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START(VL53L5_TRACE_MODULE_LOAD_FIRMWARE, fmt, \ + ##__VA_ARGS__) +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END(VL53L5_TRACE_MODULE_LOAD_FIRMWARE, status, \ + ##__VA_ARGS__) + +#define CHECK_FOR_TIMEOUT(status, p_dev, start_ms, end_ms, timeout_ms) \ + (status = vl53l5_check_for_timeout(\ + (p_dev), start_ms, end_ms, timeout_ms)) + +#define MAX_FW_FILE_SIZE 100000 +#define WRITE_CHUNK_SIZE(p_dev) VL53L5_COMMS_BUFF_MAX_COUNT(p_dev) + +static int32_t _write_byte( + struct vl53l5_dev_handle_t *p_dev, uint16_t address, uint8_t value) +{ + return vl53l5_write_multi(p_dev, address, &value, 1); +} + +static int32_t _read_byte( + struct vl53l5_dev_handle_t *p_dev, uint16_t address, uint8_t *p_value) +{ + return vl53l5_read_multi(p_dev, address, p_value, 1); +} + +static int32_t _write_page(struct vl53l5_dev_handle_t *p_dev, uint8_t *p_buffer, + uint16_t page_offset, uint32_t page_size, + uint32_t max_chunk_size, uint32_t *p_write_count) +{ + int32_t status = STATUS_OK; + uint32_t write_size = 0; + uint32_t remainder_size = 0; + uint8_t *p_write_buff = NULL; + + if ((page_offset + max_chunk_size) < page_size) + write_size = max_chunk_size; + else + write_size = page_size - page_offset; + + if (*p_write_count > p_dev->host_dev.fw_buff_count) { + + p_write_buff = p_dev->host_dev.p_comms_buff; + memset(p_write_buff, 0, write_size); + } else { + if ((p_dev->host_dev.fw_buff_count - *p_write_count) + < write_size) { + + p_write_buff = p_dev->host_dev.p_comms_buff; + remainder_size = + p_dev->host_dev.fw_buff_count - *p_write_count; + memcpy(p_write_buff, + p_buffer + *p_write_count, + remainder_size); + memset(p_write_buff + remainder_size, + 0, + write_size - remainder_size); + } else { + + p_write_buff = p_buffer + *p_write_count; + } + } + status = vl53l5_write_multi(p_dev, page_offset, p_write_buff, + write_size); + if (status < STATUS_OK) + goto exit; + + (*p_write_count) += write_size; + +exit: + return status; +} + +static int32_t _write_data_to_ram(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + uint16_t tcpm_page_offset = p_dev->host_dev.patch_data.tcpm_page_offset; + uint8_t tcpm_page = p_dev->host_dev.patch_data.tcpm_page; + uint8_t current_page = 0; + uint32_t tcpm_page_size = 0; + uint32_t write_count = 0; + uint32_t tcpm_offset = p_dev->host_dev.patch_data.tcpm_offset; + uint32_t tcpm_size = p_dev->host_dev.patch_data.tcpm_size; + uint32_t current_size = tcpm_size; + uint8_t *write_buff = NULL; + + p_dev->host_dev.fw_buff_count = tcpm_size; + + write_buff = &p_dev->host_dev.p_fw_buff[tcpm_offset]; + + LOG_FUNCTION_START(""); + + p_dev->host_dev.firmware_load = true; + + for (current_page = tcpm_page; current_page < 12; current_page++) { + status = _write_byte(p_dev, 0x7FFF, current_page); + if (status < STATUS_OK) + goto exit; + + if (current_page == 9) + tcpm_page_size = 0x8000; + if (current_page == 10) + tcpm_page_size = 0x8000; + if (current_page == 11) + tcpm_page_size = 0x5000; + if (current_size < tcpm_page_size) + tcpm_page_size = current_size; + + for (tcpm_page_offset = 0; tcpm_page_offset < tcpm_page_size; + tcpm_page_offset += WRITE_CHUNK_SIZE(p_dev)) { + status = _write_page(p_dev, write_buff, + tcpm_page_offset, tcpm_page_size, + WRITE_CHUNK_SIZE(p_dev), &write_count); + if (status != STATUS_OK) + goto exit; + } + + if (write_count == tcpm_size) + break; + current_size -= write_count; + } + +exit: + p_dev->host_dev.firmware_load = false; + + LOG_FUNCTION_END(status); + return status; +} + +static void _decode_patch_struct(struct vl53l5_dev_handle_t *p_dev) +{ + uint8_t *p_buff = p_dev->host_dev.p_fw_buff; + + LOG_FUNCTION_START(""); + + p_dev->host_dev.patch_data.patch_ver_major = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + trace_print(VL53L5_TRACE_LEVEL_INFO, + "p_dev->host_dev.patch_data.patch_ver_major: 0x%x\n", + p_dev->host_dev.patch_data.patch_ver_major); + + p_dev->host_dev.patch_data.patch_ver_minor = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + trace_print(VL53L5_TRACE_LEVEL_INFO, + "p_dev->host_dev.patch_data.patch_ver_minor: 0x%x\n", + p_dev->host_dev.patch_data.patch_ver_minor); + + p_dev->host_dev.patch_data.patch_ver_build = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + trace_print(VL53L5_TRACE_LEVEL_INFO, + "p_dev->host_dev.patch_data.patch_ver_build: 0x%x\n", + p_dev->host_dev.patch_data.patch_ver_build); + + p_dev->host_dev.patch_data.patch_ver_revision = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + trace_print(VL53L5_TRACE_LEVEL_INFO, + "p_dev->host_dev.patch_data.patch_ver_revision: 0x%x\n", + p_dev->host_dev.patch_data.patch_ver_revision); + + p_buff += BYTE_4; + + p_dev->host_dev.patch_data.patch_offset = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + trace_print(VL53L5_TRACE_LEVEL_INFO, + "p_dev->host_dev.patch_data.patch_offset: 0x%x\n", + p_dev->host_dev.patch_data.patch_offset); + + p_dev->host_dev.patch_data.patch_size = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + trace_print(VL53L5_TRACE_LEVEL_INFO, + "p_dev->host_dev.patch_data.patch_size: 0x%x\n", + p_dev->host_dev.patch_data.patch_size); + + p_dev->host_dev.patch_data.patch_checksum = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + trace_print(VL53L5_TRACE_LEVEL_INFO, + "p_dev->host_dev.patch_data.patch_checksum: 0x%x\n", + p_dev->host_dev.patch_data.patch_checksum); + + p_dev->host_dev.patch_data.tcpm_offset = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + trace_print(VL53L5_TRACE_LEVEL_INFO, + "p_dev->host_dev.patch_data.tcpm_offset: 0x%x\n", + p_dev->host_dev.patch_data.tcpm_offset); + + p_dev->host_dev.patch_data.tcpm_size = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + trace_print(VL53L5_TRACE_LEVEL_INFO, + "p_dev->host_dev.patch_data.tcpm_size: 0x%x\n", + p_dev->host_dev.patch_data.tcpm_size); + + p_dev->host_dev.patch_data.tcpm_page = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + trace_print(VL53L5_TRACE_LEVEL_INFO, + "p_dev->host_dev.patch_data.tcpm_page: 0x%x\n", + p_dev->host_dev.patch_data.tcpm_page); + + p_dev->host_dev.patch_data.tcpm_page_offset = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + trace_print(VL53L5_TRACE_LEVEL_INFO, + "p_dev->host_dev.patch_data.tcpm_page_offset: 0x%x\n", + p_dev->host_dev.patch_data.tcpm_page_offset); + + p_buff += 48; + + p_dev->host_dev.patch_data.checksum_en_offset = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + trace_print(VL53L5_TRACE_LEVEL_INFO, + "p_dev->host_dev.patch_data.checksum_en_offset: 0x%x\n", + p_dev->host_dev.patch_data.checksum_en_offset); + + p_dev->host_dev.patch_data.checksum_en_size = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + trace_print(VL53L5_TRACE_LEVEL_INFO, + "p_dev->host_dev.patch_data.checksum_en_size: 0x%x\n", + p_dev->host_dev.patch_data.checksum_en_size); + + p_dev->host_dev.patch_data.checksum_en_page = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + trace_print(VL53L5_TRACE_LEVEL_INFO, + "p_dev->host_dev.patch_data.checksum_en_page: 0x%x\n", + p_dev->host_dev.patch_data.checksum_en_page); + + p_dev->host_dev.patch_data.checksum_en_page_offset = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + trace_print(VL53L5_TRACE_LEVEL_INFO, + "p_dev->host_dev.patch_data.checksum_en_page_offset: 0x%x\n", + p_dev->host_dev.patch_data.checksum_en_page_offset); + + p_dev->host_dev.patch_data.patch_code_offset = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + trace_print(VL53L5_TRACE_LEVEL_INFO, + "p_dev->host_dev.patch_data.patch_code_offset: 0x%x\n", + p_dev->host_dev.patch_data.patch_code_offset); + + p_dev->host_dev.patch_data.patch_code_size = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + trace_print(VL53L5_TRACE_LEVEL_INFO, + "p_dev->host_dev.patch_data.patch_code_size: 0x%x\n", + p_dev->host_dev.patch_data.patch_code_size); + + p_dev->host_dev.patch_data.patch_code_page = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + trace_print(VL53L5_TRACE_LEVEL_INFO, + "p_dev->host_dev.patch_data.patch_code_page: 0x%x\n", + p_dev->host_dev.patch_data.patch_code_page); + + p_dev->host_dev.patch_data.patch_code_page_offset = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + trace_print(VL53L5_TRACE_LEVEL_INFO, + "p_dev->host_dev.patch_data.patch_code_page_offset: 0x%x\n", + p_dev->host_dev.patch_data.patch_code_page_offset); + + LOG_FUNCTION_END(0); +} + +static int32_t _write_patch_code( + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + uint16_t page = + (uint16_t)p_dev->host_dev.patch_data.patch_code_page; + uint16_t addr = + (uint16_t)p_dev->host_dev.patch_data.patch_code_page_offset; + uint32_t size = p_dev->host_dev.patch_data.patch_code_size; + uint32_t offset = p_dev->host_dev.patch_data.patch_code_offset; + uint8_t *reg_data = &p_dev->host_dev.p_fw_buff[offset]; + + LOG_FUNCTION_START(""); + + trace_print(VL53L5_TRACE_LEVEL_DEBUG, "page: 0x%x addr: 0x%x\n", + page, addr); + + status = _write_byte(p_dev, 0x7FFF, page); + if (status < STATUS_OK) + goto exit; + + status = vl53l5_write_multi(p_dev, addr, reg_data, size); + if (status < STATUS_OK) + goto exit; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _write_checksum_en( + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + uint16_t page = + (uint16_t)p_dev->host_dev.patch_data.checksum_en_page; + uint16_t addr = + (uint16_t)p_dev->host_dev.patch_data.checksum_en_page_offset; + uint32_t size = p_dev->host_dev.patch_data.checksum_en_size; + uint32_t offset = p_dev->host_dev.patch_data.checksum_en_offset; + uint8_t *reg_data = &p_dev->host_dev.p_fw_buff[offset]; + + LOG_FUNCTION_START(""); + + trace_print(VL53L5_TRACE_LEVEL_DEBUG, "page: 0x%x addr: 0x%x\n", + page, addr); + + status = _write_byte(p_dev, 0x7FFF, page); + if (status < STATUS_OK) + goto exit; + + status = vl53l5_write_multi(p_dev, addr, reg_data, size); + if (status < STATUS_OK) + goto exit; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _check_fw_checksum(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + uint32_t checksum = 0; + uint32_t expected_checksum = p_dev->host_dev.patch_data.patch_checksum; + uint8_t data[4] = {0}; + uint16_t ui_addr = (uint16_t)(DCI_UI__FIRMWARE_CHECKSUM_IDX & 0xFFFF); + + LOG_FUNCTION_START(""); + + data[0] = 0; + status = vl53l5_read_multi(p_dev, ui_addr, data, 4); + if (status < STATUS_OK) + goto exit; + + status = vl53l5_dci_swap_buffer_byte_ordering(data, BYTE_4); + if (status < STATUS_OK) + goto exit; + + checksum = vl53l5_decode_uint32_t(BYTE_4, data); + + trace_print(VL53L5_TRACE_LEVEL_INFO, + "Expected Checksum: 0x%x Actual Checksum: 0x%x\n", + expected_checksum, checksum); + + if (checksum != expected_checksum) { + status = VL53L5_ERROR_INIT_FW_CHECKSUM; + goto exit; + } + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _enable_host_access_to_go1_async( + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + uint32_t start_time_ms = 0; + uint32_t current_time_ms = 0; + uint8_t m_status = 0; + + LOG_FUNCTION_START(""); + + if (p_dev->host_dev.revision_id == 2) { + status = _write_byte(p_dev, 0x7FFF, 0x02); + if (status < STATUS_OK) + goto exit; + status = _write_byte(p_dev, 0x03, 0x12); + if (status < STATUS_OK) + goto exit; + status = _write_byte(p_dev, 0x7FFF, 0x01); + if (status < STATUS_OK) + goto exit; + } else { + status = _write_byte(p_dev, 0x7FFF, 0x01); + if (status < STATUS_OK) + goto exit; + status = _write_byte(p_dev, 0x06, 0x01); + if (status < STATUS_OK) + goto exit; + } + + m_status = 0; + status = vl53l5_get_tick_count(p_dev, &start_time_ms); + if (status < STATUS_OK) + goto exit; + + while ((m_status & 0x04) == 0) { + status = _read_byte(p_dev, 0x21, &m_status); + if (status < STATUS_OK) + goto exit; + + status = vl53l5_get_tick_count(p_dev, ¤t_time_ms); + if (status < STATUS_OK) + goto exit; + + CHECK_FOR_TIMEOUT( + status, p_dev, start_time_ms, current_time_ms, + VL53L5_BOOT_COMPLETION_POLLING_TIMEOUT_MS); + if (status < STATUS_OK) { + trace_print( + VL53L5_TRACE_LEVEL_ERRORS, + "ERROR: timeout waiting for mcu idle m_status %02x\n", + m_status); + status = VL53L5_ERROR_MCU_IDLE_TIMEOUT; + goto exit; + } + } + + status = _write_byte(p_dev, 0x7FFF, 0x00); + if (status < STATUS_OK) + goto exit; + + status = _write_byte(p_dev, 0x0C, 0x01); + if (status < STATUS_OK) + goto exit; +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _reset_mcu_and_wait_boot(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + uint8_t u_start[] = {0, 0, 0x42, 0}; + + LOG_FUNCTION_START(""); + + status = _write_byte(p_dev, 0x7FFF, 0x00); + if (status < STATUS_OK) + goto exit; + + status = vl53l5_write_multi(p_dev, 0x114, u_start, 4); + if (status < STATUS_OK) + goto exit; + + status = _write_byte(p_dev, 0x0B, 0x00); + if (status < STATUS_OK) + goto exit; + + status = _write_byte(p_dev, 0x0C, 0x00); + if (status < STATUS_OK) + goto exit; + + status = _write_byte(p_dev, 0x0B, 0x01); + if (status < STATUS_OK) + goto exit; + + status = vl53l5_wait_mcu_boot(p_dev, VL53L5_BOOT_STATE_HIGH, + 0, VL53L5_MCU_BOOT_WAIT_DELAY); + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _disable_pll(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + status = _write_byte(p_dev, 0x7FFF, 0x00); + if (status < STATUS_OK) + goto exit; + + status = _write_byte(p_dev, 0x400F, 0x00); + if (status < STATUS_OK) + goto exit; + + status = _write_byte(p_dev, 0x21A, 0x43); + if (status < STATUS_OK) + goto exit; + + status = _write_byte(p_dev, 0x21A, 0x03); + if (status < STATUS_OK) + goto exit; + + status = _write_byte(p_dev, 0x21A, 0x01); + if (status < STATUS_OK) + goto exit; + + status = _write_byte(p_dev, 0x21A, 0x00); + if (status < STATUS_OK) + goto exit; + + status = _write_byte(p_dev, 0x219, 0x00); + if (status < STATUS_OK) + goto exit; + + status = _write_byte(p_dev, 0x21B, 0x00); + if (status < STATUS_OK) + goto exit; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _set_to_power_on_status(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + status = _write_byte(p_dev, 0x7FFF, 0x00); + if (status < STATUS_OK) + goto exit; + + status = _write_byte(p_dev, 0x101, 0x00); + if (status < STATUS_OK) + goto exit; + + status = _write_byte(p_dev, 0x102, 0x00); + if (status < STATUS_OK) + goto exit; + + status = _write_byte(p_dev, 0x010a, 0x00); + if (status < STATUS_OK) + goto exit; + + status = _write_byte(p_dev, 0x4002, 0x01); + if (status < STATUS_OK) + goto exit; + + status = _write_byte(p_dev, 0x4002, 0x00); + if (status < STATUS_OK) + goto exit; + + status = _write_byte(p_dev, 0x103, 0x01); + if (status < STATUS_OK) + goto exit; + + status = _write_byte(p_dev, 0x010a, 0x03); + if (status < STATUS_OK) + goto exit; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _wait_for_boot_complete_before_fw_load( + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + status = _enable_host_access_to_go1_async(p_dev); + if (status < STATUS_OK) + goto exit; + + status = _set_to_power_on_status(p_dev); + if (status < STATUS_OK) + goto exit; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _wait_for_boot_complete_after_fw_load( + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + status = _disable_pll(p_dev); + if (status < STATUS_OK) + goto exit; + + status = _reset_mcu_and_wait_boot(p_dev); + if (status < STATUS_OK) + goto exit; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +int32_t vl53l5_load_firmware(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + trace_print(VL53L5_TRACE_LEVEL_INFO, "\n\n#### load_firmware ####\n\n"); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_FW_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_FW_BUFF_NOT_FOUND; + goto exit; + } + if (VL53L5_FW_BUFF_ISEMPTY(p_dev)) { + status = VL53L5_ERROR_FW_BUFF_NOT_FOUND; + goto exit; + } + if (VL53L5_COMMS_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + _decode_patch_struct(p_dev); + + status = _wait_for_boot_complete_before_fw_load(p_dev); + if (status < STATUS_OK) + goto exit_change_page; + + status = _write_data_to_ram(p_dev); + if (status < STATUS_OK) + goto exit_change_page; + + status = _write_patch_code(p_dev); + if (status < STATUS_OK) + goto exit_change_page; + + status = _write_checksum_en(p_dev); + if (status < STATUS_OK) + goto exit_change_page; + + status = _wait_for_boot_complete_after_fw_load(p_dev); + if (status < STATUS_OK) + goto exit_change_page; + + status = _write_byte(p_dev, 0x7FFF, 0x02); + if (status < STATUS_OK) + goto exit_change_page; + + status = _check_fw_checksum(p_dev); + if (status < STATUS_OK) + goto exit_change_page; + +exit_change_page: + if (status < STATUS_OK) + + (void)_write_byte(p_dev, 0x7FFF, 0x02); + +exit: + LOG_FUNCTION_END(status); + return status; +} diff --git a/drivers/sensors/vl53l8/bare_driver/common/src/vl53l5_register_utils.c b/drivers/sensors/vl53l8/bare_driver/common/src/vl53l5_register_utils.c new file mode 100644 index 000000000000..01b9566096b3 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/common/src/vl53l5_register_utils.c @@ -0,0 +1,295 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_register_utils.h" +#include "vl53l5_rom_boot.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform.h" + +#define trace_print(level, ...) \ + _LOG_TRACE_PRINT(VL53L5_TRACE_MODULE_POWER_API, \ + level, VL53L5_TRACE_FUNCTION_ALL, ##__VA_ARGS__) + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START(VL53L5_TRACE_MODULE_POWER_API, fmt, ##__VA_ARGS__) +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END(VL53L5_TRACE_MODULE_POWER_API, status, ##__VA_ARGS__) + +#define CHECK_FOR_TIMEOUT(status, p_dev, start_ms, end_ms, timeout_ms) \ + (status = vl53l5_check_for_timeout(\ + (p_dev), start_ms, end_ms, timeout_ms)) + +#define PAGE_SELECT 0x7FFF + +#define GO2_STATUS_0 0x6 +#define GO2_STATUS_1 0x7 + +#define GPIO_LOW 0 +#define GPIO_HIGH 1 + +#define REGULATOR_DISABLE 0 +#define REGULATOR_ENABLE 1 + +#define REGDVDD1V1__INDEX 0x000F +#define XSHUT_CTRL 0x0009 + +#define DEFAULT_PAGE 2 +#define GO2_PAGE 0 + +#define REGULATOR_REGISTER_OR_MASK(return_mask, lp_reg_enable, hp_reg_enable)\ +do {\ + return_mask = 0;\ + return_mask |= (lp_reg_enable ? 0x00 : 0x02);\ + return_mask |= (hp_reg_enable ? 0x00 : 0x01);\ +} while (0) + +#define REGULATOR_REGISTER_AND_MASK(return_mask)\ + (return_mask = 0xfc) + +#define MASK_XSHUT_REGISTER(reg_val, value)\ +do {\ + reg_val &= 0xf8;\ + reg_val |= (value ? 0x04 : 0x02);\ +} while (0) + +#define BOOT_STATE_MATCHED(p_dev, state) \ + (((state == VL53L5_BOOT_STATE_HIGH) && MCU_BOOT_COMPLETE(p_dev)) || \ + ((state == VL53L5_BOOT_STATE_LOW) && MCU_BOOT_NOT_COMPLETE(p_dev))) + +int32_t vl53l5_register_read_modify_write( + struct vl53l5_dev_handle_t *p_dev, uint16_t addr, + uint8_t first_and_mask, uint8_t second_or_mask) +{ + int32_t status = STATUS_OK; + uint8_t reg_val = 0; + + status = vl53l5_read_multi(p_dev, addr, ®_val, 1); + if (status != STATUS_OK) + goto out; + + reg_val &= first_and_mask; + reg_val |= second_or_mask; + + status = vl53l5_write_multi(p_dev, addr, ®_val, 1); +out: + return status; +} + +int32_t vl53l5_set_page(struct vl53l5_dev_handle_t *p_dev, uint8_t page) +{ + int32_t status = STATUS_OK; + + status = vl53l5_write_multi(p_dev, PAGE_SELECT, &page, 1); + + return status; +} + +int32_t vl53l5_set_regulators( + struct vl53l5_dev_handle_t *p_dev, + uint8_t lp_reg_enable, + uint8_t hp_reg_enable) +{ + int32_t status = STATUS_OK; + uint16_t reg_index = 0; + uint8_t and_mask = 0; + uint8_t or_mask = 0; + + REGULATOR_REGISTER_AND_MASK(and_mask); + + REGULATOR_REGISTER_OR_MASK(or_mask, lp_reg_enable, hp_reg_enable); + + reg_index = REGDVDD1V1__INDEX; + status = vl53l5_register_read_modify_write(p_dev, reg_index, + and_mask, or_mask); + + return status; +} + +int32_t vl53l5_set_xshut_bypass( + struct vl53l5_dev_handle_t *p_dev, uint8_t state) +{ + int32_t status = STATUS_OK; + uint16_t reg_index = 0; + uint8_t and_mask = 0; + uint8_t or_mask = 0; + + reg_index = 0x09; + + if (state == 1) { + and_mask = 0xff; + or_mask = 0x01; + } else { + and_mask = 0xfe; + or_mask = 0x00; + } + + status = vl53l5_register_read_modify_write( + p_dev, reg_index, and_mask, or_mask); + + return status; +} + +int32_t vl53l5_set_manual_xshut_state( + struct vl53l5_dev_handle_t *p_dev, uint8_t state) +{ + int32_t status = STATUS_OK; + uint16_t reg_index = 0; + uint8_t reg_val = 0; + + reg_index = XSHUT_CTRL; + reg_val = 0; + status = vl53l5_read_multi(p_dev, reg_index, ®_val, 1); + if (status != STATUS_OK) + goto exit; + + MASK_XSHUT_REGISTER(reg_val, state); + + status = vl53l5_write_multi(p_dev, reg_index, ®_val, 1); + +exit: + return status; +} + +int32_t vl53l5_wait_mcu_boot( + struct vl53l5_dev_handle_t *p_dev, enum vl53l5_boot_state state, + uint32_t timeout_ms, uint32_t wait_time_ms) +{ + int32_t status = STATUS_OK; + uint32_t start_time_ms = 0; + uint32_t current_time_ms = 0; + + status = vl53l5_get_tick_count(p_dev, &start_time_ms); + if (status != STATUS_OK) + goto exit; + + if (timeout_ms == 0) + timeout_ms = VL53L5_BOOT_COMPLETION_POLLING_TIMEOUT_MS; + + if (wait_time_ms > timeout_ms) + wait_time_ms = timeout_ms; + + VL53L5_GO2_STATUS_0(p_dev).bytes = 0; + VL53L5_GO2_STATUS_1(p_dev).bytes = 0; + + do { + + status = vl53l5_read_multi(p_dev, GO2_STATUS_0, + &VL53L5_GO2_STATUS_0(p_dev).bytes, 1); + if (status != STATUS_OK) + goto exit; + + if (HW_TRAP(p_dev)) { + + status = vl53l5_read_multi(p_dev, GO2_STATUS_1, + &VL53L5_GO2_STATUS_1(p_dev).bytes, 1); + if (status != STATUS_OK) + goto exit; + + if (VL53L5_GO2_STATUS_1(p_dev).bytes) + status = VL53L5_ERROR_MCU_ERROR_HW_STATE; + else + + status = + VL53L5_ERROR_FALSE_MCU_ERROR_POWER_STATE; + goto exit; + } + + if (state == VL53L5_BOOT_STATE_ERROR) { + if (MCU_ERROR(p_dev)) { + status = VL53L5_ERROR_MCU_ERROR_WAIT_STATE; + goto exit; + } + } else { + if (BOOT_STATE_MATCHED(p_dev, state)) + goto exit_error; + } + + status = vl53l5_get_tick_count(p_dev, ¤t_time_ms); + if (status != STATUS_OK) + goto exit; + + CHECK_FOR_TIMEOUT( + status, p_dev, start_time_ms, current_time_ms, + timeout_ms); + + if (status != STATUS_OK) { + status = VL53L5_ERROR_BOOT_COMPLETE_TIMEOUT; + goto exit; + } + + if (wait_time_ms) { + status = vl53l5_wait_ms(p_dev, wait_time_ms); + if (status != STATUS_OK) + goto exit; + } + } while (1); + +exit_error: + if (MCU_ERROR(p_dev)) { + (void)vl53l5_read_multi(p_dev, GO2_STATUS_1, + &VL53L5_GO2_STATUS_1(p_dev).bytes, 1); + if (MCU_NVM_PROGRAMMED(p_dev)) + status = VL53L5_ERROR_MCU_NVM_NOT_PROGRAMMED; + else + status = VL53L5_ERROR_MCU_ERROR_WAIT_STATE; + } + +exit: + return status; +} diff --git a/drivers/sensors/vl53l8/bare_driver/common/src/vl53l5_rom_boot.c b/drivers/sensors/vl53l8/bare_driver/common/src/vl53l5_rom_boot.c new file mode 100644 index 000000000000..2948105a2163 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/common/src/vl53l5_rom_boot.c @@ -0,0 +1,204 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_rom_boot.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform.h" +#include "vl53l5_register_utils.h" + +#define trace_print(level, ...) \ + _LOG_TRACE_PRINT(VL53L5_TRACE_MODULE_POWER_API, \ + level, VL53L5_TRACE_FUNCTION_ALL, ##__VA_ARGS__) + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START(VL53L5_TRACE_MODULE_POWER_API, fmt, ##__VA_ARGS__) +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END(VL53L5_TRACE_MODULE_POWER_API, status, ##__VA_ARGS__) + +#define CHECK_FOR_TIMEOUT(status, p_dev, start_ms, end_ms, timeout_ms) \ + (status = vl53l5_check_for_timeout(\ + (p_dev), start_ms, end_ms, timeout_ms)) + +#define MCU_FIRST_BOOT_COMPLETE(p_dev) \ + (VL53L5_GO2_STATUS_1(p_dev).mcu__initial_ram_boot_complete == 1) + +#define MCU_FIRST_BOOT_NOT_COMPLETE(p_dev) \ + (VL53L5_GO2_STATUS_1(p_dev).mcu__initial_ram_boot_complete == 0) + +#define MCU_FIRST_BOOT_COMPLETE_REVISION_C(p_dev) \ + (VL53L5_GO2_STATUS_1(p_dev).mcu__spare0 == 1) + +#define MCU_FIRST_BOOT_NOT_COMPLETE_CUT_1_4(p_dev) \ + (VL53L5_GO2_STATUS_1(p_dev).mcu__spare0 == 0) + +#define DEFAULT_PAGE 2 +#define GO2_PAGE 0 +#define DEVICE_ID 0x0 +#define REVISION_ID 0x1 +#define GO2_STATUS_1 0x07 +#define BYTE_SIZE_1 1 + +static int32_t _write_byte( + struct vl53l5_dev_handle_t *p_dev, uint16_t address, uint8_t value) +{ + return vl53l5_write_multi(p_dev, address, &value, BYTE_SIZE_1); +} + +static int32_t _check_device_booted(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + VL53L5_GO2_STATUS_1(p_dev).bytes = 0; + + status = vl53l5_read_multi(p_dev, GO2_STATUS_1, + &VL53L5_GO2_STATUS_1(p_dev).bytes, BYTE_SIZE_1); + if (status != STATUS_OK) + goto exit; + + if (p_dev->host_dev.revision_id == 0x0c) { + if (MCU_FIRST_BOOT_COMPLETE_REVISION_C(p_dev)) + p_dev->host_dev.device_booted = true; + else + p_dev->host_dev.device_booted = false; + } else { + if (MCU_FIRST_BOOT_COMPLETE(p_dev)) + p_dev->host_dev.device_booted = true; + else + p_dev->host_dev.device_booted = false; + } + +exit: + return status; +} + +static int32_t _check_rom_firmware_boot( + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + status = vl53l5_wait_mcu_boot(p_dev, VL53L5_BOOT_STATE_HIGH, 0, 0); + + (void)_write_byte(p_dev, 0x000E, 0x01); + + return status; +} + +int32_t vl53l5_check_rom_firmware_boot(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + status = vl53l5_set_page(p_dev, GO2_PAGE); + if (status < STATUS_OK) + goto exit; + + status = vl53l5_read_multi( + p_dev, DEVICE_ID, &p_dev->host_dev.device_id, BYTE_SIZE_1); + if (status < STATUS_OK) + goto exit_change_page; + + status = vl53l5_read_multi( + p_dev, REVISION_ID, &p_dev->host_dev.revision_id, BYTE_SIZE_1); + if (status < STATUS_OK) + goto exit_change_page; + + status = _check_device_booted(p_dev); + if (status < STATUS_OK) + goto exit_change_page; + + if (p_dev->host_dev.device_booted == true) + goto exit_change_page; + + if ((p_dev->host_dev.device_id == 0xF0) && + (p_dev->host_dev.revision_id == 0x02 || + p_dev->host_dev.revision_id == 0x0C)) { + trace_print(VL53L5_TRACE_LEVEL_INFO, + "device id 0x%x revision id 0x%x\n", + p_dev->host_dev.device_id, + p_dev->host_dev.revision_id); + status = _check_rom_firmware_boot(p_dev); + if (status < STATUS_OK) + goto exit_change_page; + } else { + trace_print(VL53L5_TRACE_LEVEL_ERRORS, + "Unsupported device id 0x%x revision id 0x%x\n", + p_dev->host_dev.device_id, + p_dev->host_dev.revision_id); + + status = VL53L5_UNKNOWN_SILICON_REVISION; + goto exit_change_page; + } + +exit_change_page: + + if (status != STATUS_OK) + (void)vl53l5_set_page(p_dev, DEFAULT_PAGE); + else + status = vl53l5_set_page(p_dev, DEFAULT_PAGE); + +exit: + LOG_FUNCTION_END(status); + return status; +} diff --git a/drivers/sensors/vl53l8/bare_driver/common/src/vl53l5_tcdm_dump.c b/drivers/sensors/vl53l8/bare_driver/common/src/vl53l5_tcdm_dump.c new file mode 100644 index 000000000000..739128069f98 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/common/src/vl53l5_tcdm_dump.c @@ -0,0 +1,286 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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. +* +* +*******************************************************************************/ + +#ifdef VL53L5_TCDM_ENABLE + +#include "vl53l5_platform.h" +#include "vl53l5_tcdm_dump.h" + +#define trace_print(level, ...) \ + _LOG_TRACE_PRINT(VL53L5_TRACE_MODULE_POWER_API, \ + level, VL53L5_TRACE_FUNCTION_ALL, ##__VA_ARGS__) + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START(VL53L5_TRACE_MODULE_POWER_API, fmt, ##__VA_ARGS__) +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END(VL53L5_TRACE_MODULE_POWER_API, status, ##__VA_ARGS__) + +#define STXP70_TEST_A0_8 0x06 +#define PAGE_SELECT 0x7FFF +#define STXP70_CTRL 0x20 +#define STXP70_STATUS 0x21 +#define MCU_BYPASS 0x0C + +#define UI_RAM_SIZE_BYTES (12 * 1024) +#define PAGE_3_SIZE_BYTES (32 * 1024) +#define PAGE_4_SIZE_BYTES (32 * 1024) + +static int32_t _write_byte( + struct vl53l5_dev_handle_t *p_dev, + uint16_t address, uint8_t value) +{ + return vl53l5_write_multi(p_dev, address, &value, 1); +} + +static int32_t _read_byte( + struct vl53l5_dev_handle_t *p_dev, + uint16_t address, uint8_t *p_value) +{ + return vl53l5_read_multi(p_dev, address, p_value, 1); +} + +static int32_t _set_page( + struct vl53l5_dev_handle_t *p_dev, uint8_t page) +{ + int32_t status = STATUS_OK; + + status = _write_byte(p_dev, PAGE_SELECT, page); + if (status < STATUS_OK) + goto exit; + +exit: + return status; +} + +static int32_t _read_page( + struct vl53l5_dev_handle_t *p_dev, + uint16_t page, uint32_t count, uint8_t *p_value) +{ + int32_t status = STATUS_OK; + + status = _set_page(p_dev, page); + if (status < STATUS_OK) + goto exit; + + status = vl53l5_read_multi(p_dev, 0, p_value, count); + if (status < STATUS_OK) + goto exit; + +exit: + return status; +} + +static int32_t mcu_go1_async_access(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + status = _set_page(p_dev, 0); + if (status < STATUS_OK) + goto exit; + + status = _write_byte(p_dev, MCU_BYPASS, 0x00); + if (status < STATUS_OK) + goto exit; + + status = _set_page(p_dev, 1); + if (status < STATUS_OK) + goto exit; + + status = _write_byte(p_dev, STXP70_CTRL, 0x07); + if (status < STATUS_OK) + goto exit; + + status = _write_byte(p_dev, STXP70_CTRL, 0x06); + if (status < STATUS_OK) + goto exit; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t host_go1_async_access(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + uint8_t m_status = 0; + uint32_t start_time_ms = 0; + uint32_t current_time_ms = 0; + + LOG_FUNCTION_START(""); + + status = _set_page(p_dev, 1); + if (status < STATUS_OK) + goto exit; + + status = _write_byte(p_dev, STXP70_TEST_A0_8, 0x01); + if (status < STATUS_OK) + goto exit; + + status = vl53l5_get_tick_count(p_dev, &start_time_ms); + if (status < STATUS_OK) + goto exit; + + do { + status = _read_byte(p_dev, STXP70_STATUS, &m_status); + if (status < STATUS_OK) + goto exit; + + status = vl53l5_get_tick_count(p_dev, ¤t_time_ms); + if (status < STATUS_OK) + goto exit; + + status = vl53l5_check_for_timeout( + p_dev, + start_time_ms, + current_time_ms, + VL53L5_TCDM_DUMP_STATUS_TIMEOUT); + if (status < STATUS_OK) + goto exit; + + } while (m_status != 0x04); + + status = _set_page(p_dev, 0); + if (status < STATUS_OK) + goto exit; + + status = _write_byte(p_dev, MCU_BYPASS, 0x01); + if (status < STATUS_OK) + goto exit; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t dump_tcdm_state(struct vl53l5_dev_handle_t *p_dev, + uint8_t *buffer, + uint32_t *pbytes_written) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + *pbytes_written = 0; + + status = _read_page(p_dev, 3, PAGE_3_SIZE_BYTES, buffer); + if (status < STATUS_OK) + goto exit; + *pbytes_written += PAGE_3_SIZE_BYTES; + + status = _read_page( + p_dev, 4, PAGE_4_SIZE_BYTES, buffer + PAGE_3_SIZE_BYTES); + if (status < STATUS_OK) + goto exit; + *pbytes_written += PAGE_4_SIZE_BYTES; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +int32_t vl53l5_tcdm_dump( + struct vl53l5_dev_handle_t *p_dev, uint8_t *buffer, uint32_t *count) +{ + int32_t status = STATUS_OK; + uint32_t bytes_written = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + if (buffer == NULL) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + if (count == NULL) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + status = host_go1_async_access(p_dev); + if (status < STATUS_OK) + goto exit_page; + + status = dump_tcdm_state(p_dev, p_buff, &bytes_written); + if (status < STATUS_OK) + goto exit_page; + + *count += bytes_written; + + status = mcu_go1_async_access(p_dev); + if (status < STATUS_OK) + goto exit_page; + +exit_page: + if (status < STATUS_OK) + (void)_set_page(p_dev, 2); + else + status = _set_page(p_dev, 2); +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/cal_defs.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/cal_defs.h new file mode 100644 index 000000000000..f1b6a3ebc19c --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/cal_defs.h @@ -0,0 +1,86 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __CAL_DEFS_H__ +#define __CAL_DEFS_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define CAL_DEF__MAX_HIST_BINS \ + ((uint32_t) 144U) +#define CAL_DEF__MAX_XTALK_SHAPE_BINS \ + ((uint32_t) 144U) +#define CAL_DEF__MAX_COLS \ + ((uint32_t) 10U) +#define CAL_DEF__MAX_ROWS \ + ((uint32_t) 8U) +#define CAL_DEF__MAX_COLS_X_MAX_ROWS \ + ((uint32_t) 64U) +#define CAL_DEF__XTALK_MON_MAX_ZONES \ + ((uint32_t) 8U) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/cal_luts.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/cal_luts.h new file mode 100644 index 000000000000..5c7d28976a29 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/cal_luts.h @@ -0,0 +1,160 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __CAL_LUTS_H__ +#define __CAL_LUTS_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define CAL_ERROR__NONE \ + ((int32_t) 0) + +#define CAL_ERROR__NO_SAMPLE_FAIL \ + ((int32_t) 1) + +#define CAL_ERROR__RATE_SIGMA_LIMIT_FAIL \ + ((int32_t) 2) + +#define CAL_ERROR__RANGE_SIGMA_LIMIT_FAIL \ + ((int32_t) 3) + +#define CAL_ERROR__MISSING_SAMPLES \ + ((int32_t) 4) + +#define CAL_ERROR__INVALID_REPLACEMENT_MODE \ + ((int32_t) 5) + +#define CAL_ERROR__MAX_FAILING_ZONES_LIMIT_FAIL \ + ((int32_t) 6) + +#define CAL_ERROR__NO_VALID_ZONES \ + ((int32_t) 7) + +#define CAL_ERROR__8x8_TO_4x4__CONVERSION_FAIL \ + ((int32_t) 8) + +#define CAL_ERROR__TARGET_TOO_CLOSE \ + ((int32_t) 9) + +#define CAL_WARNING__NONE \ + ((int32_t) 128) + +#define CAL_WARNING__NO_SAMPLE_FAIL \ + ((int32_t) 129) + +#define CAL_WARNING__RATE_SIGMA_LIMIT_FAIL \ + ((int32_t) 130) + +#define CAL_WARNING__RANGE_SIGMA_LIMIT_FAIL \ + ((int32_t) 131) + +#define CAL_WARNING__MISSING_SAMPLES \ + ((int32_t) 132) + +#define CAL_WARNING__ZONE_REPLACED_BY_MIN_XTALK \ + ((int32_t) 133) + +#define CAL_WARNING__ZONE_REPLACED_BY_FIXED_VAL_XTALK \ + ((int32_t) 134) + +#define CAL_WARNING__8x8_TO_4x4_CONVERSION__INPUT_RATE_CLIPPED \ + ((int32_t) 135) + +#define CAL_WARNING__8x8_TO_4x4_CONVERSION__ZERO_RATES_DETECTED \ + ((int32_t) 136) + +#define CAL_WARNING__8x8_TO_4x4_CONVERSION__SPAD_COUNTS_SUM_CLIPPED \ + ((int32_t) 137) + +#define CAL_WARNING__TARGET_TOO_CLOSE \ + ((int32_t) 138) + +#define CAL_STAGE__INVALID \ + ((int32_t) 0) + +#define CAL_STAGE__RANGE_OFFSET \ + ((int32_t) 1) + +#define CAL_STAGE__PEAK_RATE \ + ((int32_t) 2) + +#define CAL_STAGE__XTALK_RATE \ + ((int32_t) 3) + +#define CAL_STAGE__XTALK_SHAPE \ + ((int32_t) 4) + +#define CAL_STAGE__RANGE_OFFSET_8x8_TO_4x4_CONVERSION \ + ((int32_t) 5) + +#define CAL_STAGE__PEAK_RATE_8x8_TO_4x4_CONVERSION \ + ((int32_t) 6) + +#define CAL_STAGE__XTALK_RATE_8x8_TO_4x4_CONVERSION \ + ((int32_t) 7) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/cal_size.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/cal_size.h new file mode 100644 index 000000000000..ca8c5cc472e9 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/cal_size.h @@ -0,0 +1,202 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __CAL_SIZE_H__ +#define __CAL_SIZE_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_CAL_GRP_STATUS_SZ \ + ((uint16_t) 16) + +#define VL53L5_CAL_GRP_REF_SPAD_INFO_SZ \ + ((uint16_t) 8) + +#define VL53L5_CAL_GRP_TEMP_SENSOR_DATA_SZ \ + ((uint16_t) 4) + +#define VL53L5_CAL_GRP_OPTICAL_CENTRE_DATA_SZ \ + ((uint16_t) 4) + +#define VL53L5_CAL_GRP_OSCILLATOR_DATA_SZ \ + ((uint16_t) 8) + +#define VL53L5_CAL_GRP_VHV_DATA_SZ \ + ((uint16_t) 4) + +#define VL53L5_CAL_GRP_GRID_META_SZ \ + ((uint16_t) 12) + +#define VL53L5_CAL_GRP_GRID_DATA_RATE_KCPS_PER_SPAD_SZ \ + ((uint16_t) 256) + +#define VL53L5_CGGDRKPS_CAL_GRID_DATA_RATE_KCPS_PER_SPAD_SZ \ + ((uint16_t) 256) + +#define VL53L5_CAL_GRP_GRID_DATA_EFFECTIVE_SPAD_COUNT_SZ \ + ((uint16_t) 128) + +#define VL53L5_CGGDESC_CAL_GRID_DATA_EFFECTIVE_SPAD_COUNT_SZ \ + ((uint16_t) 128) + +#define VL53L5_CAL_GRP_GRID_DATA_RANGE_MM_SZ \ + ((uint16_t) 128) + +#define VL53L5_CGGDRM_CAL_GRID_DATA_RANGE_MM_SZ \ + ((uint16_t) 128) + +#define VL53L5_CAL_GRP_GRID_DATA_SCALE_FACTOR_SZ \ + ((uint16_t) 128) + +#define VL53L5_CGGDSF_CAL_GRID_DATA_SCALE_FACTOR_SZ \ + ((uint16_t) 128) + +#define VL53L5_CAL_GRP_XTALK_SHAPE_META_SZ \ + ((uint16_t) 12) + +#define VL53L5_CAL_GRP_XTALK_SHAPE_DATA_SZ \ + ((uint16_t) 288) + +#define VL53L5_CGXSD_CAL_XTALK_SHAPE_BIN_DATA_SZ \ + ((uint16_t) 288) + +#define VL53L5_CAL_GRP_XTALK_MON_META_DATA_SZ \ + ((uint16_t) 4) + +#define VL53L5_CAL_GRP_XTALK_MON_ZONES_SZ \ + ((uint16_t) 32) + +#define VL53L5_CGXMZ_CAL_XMON_ZONE_X_OFF_SZ \ + ((uint16_t) 8) + +#define VL53L5_CGXMZ_CAL_XMON_ZONE_Y_OFF_SZ \ + ((uint16_t) 8) + +#define VL53L5_CGXMZ_CAL_XMON_ZONE_WIDTH_SZ \ + ((uint16_t) 8) + +#define VL53L5_CGXMZ_CAL_XMON_ZONE_HEIGHT_SZ \ + ((uint16_t) 8) + +#define VL53L5_CAL_GRP_XTALK_MON_DATA_SZ \ + ((uint16_t) 48) + +#define VL53L5_CGXMD_CAL_XMON_ZONE_RATE_KCPS_SPAD_SZ \ + ((uint16_t) 32) + +#define VL53L5_CGXMD_CAL_XMON_ZONE_AVG_COUNT_SZ \ + ((uint16_t) 16) + +#define VL53L5_CAL_GRP_PHASE_STATS_SZ \ + ((uint16_t) 20) + +#define VL53L5_CAL_GRP_TEMPERATURE_STATS_SZ \ + ((uint16_t) 4) + +#define VL53L5_CAL_GRP_COMMON_SUM_DATA_SZ \ + ((uint16_t) 48) + +#define VL53L5_CAL_GRP_XMON_SUM_DATA_SZ \ + ((uint16_t) 96) + +#define VL53L5_CGXSD_CAL_XMON_SUM_RATE_KCPS_PER_SPAD_SZ \ + ((uint16_t) 64) + +#define VL53L5_CGXSD_CAL_XMON_SUM_AVG_COUNT_SZ \ + ((uint16_t) 16) + +#define VL53L5_CGXSD_CAL_XMON_SUM_SAMPLE_COUNT_SZ \ + ((uint16_t) 16) + +#define VL53L5_CAL_GRP_ZONE_SUM_DATA_SZ \ + ((uint16_t) 1088) + +#define VL53L5_CGZSD_CAL_ZONE_SUM_RATE_KCPS_PER_SPAD_SZ \ + ((uint16_t) 512) + +#define VL53L5_CGZSD_CAL_ZONE_SUM_EFFECTIVE_SPAD_COUNT_SZ \ + ((uint16_t) 256) + +#define VL53L5_CGZSD_CAL_ZONE_SUM_MEDIAN_RANGE_MM_SZ \ + ((uint16_t) 256) + +#define VL53L5_CGZSD_CAL_ZONE_SUM_SAMPLE_COUNT_SZ \ + ((uint16_t) 64) + +#define VL53L5_CAL_GRP_HIST_SUM_META_SZ \ + ((uint16_t) 8) + +#define VL53L5_CAL_GRP_HIST_SUM_DATA_SZ \ + ((uint16_t) 1440) + +#define VL53L5_CGHSD_CAL_HIST_SUM_BIN_EVENTS_SZ \ + ((uint16_t) 1152) + +#define VL53L5_CGHSD_CAL_HIST_SUM_SAMPLE_COUNT_SZ \ + ((uint16_t) 288) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/cal_structs.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/cal_structs.h new file mode 100644 index 000000000000..d7c6da7199c4 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/cal_structs.h @@ -0,0 +1,235 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __CAL_STRUCTS_H__ +#define __CAL_STRUCTS_H__ + +#include "cal_defs.h" +#include "cal_luts.h" +#include "packing_structs.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct cal_grp__status_t { + + int32_t cal__status__type; + + int32_t cal__status__stage_id; + + int32_t cal__target__idx; + + int32_t cal__zone_id; +}; + +struct cal_grp__ref_spad_info_t { + + uint8_t cal__ref_spad__offset; + + uint8_t cal__ref_spad__count; + + uint8_t cal__ref_spad__count_10x; + + uint8_t cal__ref_spad__count_100x; + + uint8_t cal__ref_spad__left_right_sel; + + uint8_t cal__ref_spad__status; + uint8_t cal__ref_spad_info__pad_0; + uint8_t cal__ref_spad_info__pad_1; +}; + +struct cal_grp__optical_centre_data_t { + + uint8_t cal__optical_centre__x; + + uint8_t cal__optical_centre__y; + uint8_t cal__optical_centre__pad_0; + uint8_t cal__optical_centre__pad_1; +}; + +struct cal_grp__grid_meta_t { + + int16_t cal__grid_meta__distance_mm; + + uint16_t cal__grid_meta__reflectance_pc; + + int8_t cal__grid_meta__silicon_temp_degc; + + uint8_t cal__grid_meta__cols; + + uint8_t cal__grid_meta__rows; + + uint8_t cal__grid_meta__x_offset_spads; + + uint8_t cal__grid_meta__y_offset_spads; + + uint8_t cal__grid_meta__x_pitch_spads; + + uint8_t cal__grid_meta__y_pitch_spads; + + uint8_t cal__grid_meta__avg_count; +}; + +struct cal_grp__grid_data__rate_kcps_per_spad_t { + + uint32_t cal__grid_data__rate_kcps_per_spad[ + CAL_DEF__MAX_COLS_X_MAX_ROWS]; +}; + +struct cal_grp__grid_data__effective_spad_count_t { + + uint16_t cal__grid_data_effective_spad_count[ + CAL_DEF__MAX_COLS_X_MAX_ROWS]; +}; + +struct cal_grp__grid_data__range_mm_t { + + int16_t cal__grid_data__range_mm[CAL_DEF__MAX_COLS_X_MAX_ROWS]; +}; + +struct cal_grp__grid_data__scale_factor_t { + + uint16_t cal__grid_data__scale_factor[CAL_DEF__MAX_COLS_X_MAX_ROWS]; +}; + +struct cal_grp__xtalk_shape_meta_t { + + uint32_t cal__xtalk_shape__median_phase; + + uint16_t cal__xtalk_shape__avg_count; + + uint8_t cal__xtalk_shape__no_of_bins; + + uint8_t cal__xtalk_shape__normalisation_power; + + int8_t cal__xtalk_shape__silicon_temp_degc; + uint8_t cal__xtalk_shape__spare_0; + uint8_t cal__xtalk_shape__spare_1; + uint8_t cal__xtalk_shape__spare_2; +}; + +struct cal_grp__xtalk_shape_data_t { + + uint16_t cal__xtalk_shape__bin_data[CAL_DEF__MAX_XTALK_SHAPE_BINS]; +}; + +struct cal_grp__xtalk_mon__meta_data_t { + + uint8_t cal__xmon__max_zones; + + uint8_t cal__xmon__no_of_zones; + + uint8_t cal__xmon__zone_idx; + + int8_t cal__xmon__silicon_temp_degc; +}; + +struct cal_grp__xtalk_mon__zones_t { + + uint8_t cal__xmon__zone__x_off[CAL_DEF__XTALK_MON_MAX_ZONES]; + + uint8_t cal__xmon__zone__y_off[CAL_DEF__XTALK_MON_MAX_ZONES]; + + uint8_t cal__xmon__zone__width[CAL_DEF__XTALK_MON_MAX_ZONES]; + + uint8_t cal__xmon__zone__height[CAL_DEF__XTALK_MON_MAX_ZONES]; +}; + +struct cal_grp__xtalk_mon__data_t { + + uint32_t cal__xmon__zone__rate_kcps_spad[CAL_DEF__XTALK_MON_MAX_ZONES]; + + uint16_t cal__xmon__zone__avg_count[CAL_DEF__XTALK_MON_MAX_ZONES]; +}; + +struct cal_grp__phase_stats_t { + + uint32_t cal__stats__avg_phase__ref; + + uint32_t cal__stats__avg_phase__rtn; + + uint32_t cal__stats__avg_phase__flex; + + uint16_t cal__stats__avg_count__ref; + + uint16_t cal__stats__avg_count__rtn; + + uint16_t cal__stats__avg_count__flex; + uint16_t cal__stats__spare_0; +}; + +struct cal_grp__temperature_stats_t { + + int8_t cal__stats__avg_temp_degc__start__ref; + + int8_t cal__stats__avg_temp_degc__end__ref; + + int8_t cal__stats__avg_temp_degc__start__rtn; + + int8_t cal__stats__avg_temp_degc__end__rtn; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/common_datatype_defs.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/common_datatype_defs.h new file mode 100644 index 000000000000..26d2a524161c --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/common_datatype_defs.h @@ -0,0 +1,80 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __COMMON_DATATYPE_DEFS_H__ +#define __COMMON_DATATYPE_DEFS_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define COMMON_MAX_CHANNELS \ + ((uint32_t) 16U) +#define COMMON_MAX_HIST_BINS \ + ((uint32_t) 144U) +#define COMMON_MAX_XTALK_SHAPE_BINS \ + ((uint32_t) 144U) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/common_datatype_luts.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/common_datatype_luts.h new file mode 100644 index 000000000000..61baa06f9ae0 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/common_datatype_luts.h @@ -0,0 +1,392 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __COMMON_DATATYPE_LUTS_H__ +#define __COMMON_DATATYPE_LUTS_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define FUNC_ERROR__NONE \ + ((int16_t) 0) + +#define FUNC_ERROR__PHOOK_OVERWRITE \ + ((int16_t) 128) + +#define FUNC_ERROR__WDOG_TMOUT \ + ((int16_t) 129) + +#define FUNC_ERROR__WDOG_FAIL \ + ((int16_t) 130) + +#define FUNC_ERROR__PLL_LOCK_TMOUT \ + ((int16_t) 131) + +#define FUNC_ERROR__CONT_TMOUT \ + ((int16_t) 132) + +#define FUNC_ERROR__CONT_FAIL \ + ((int16_t) 133) + +#define FUNC_ERROR__VCSELCP_TMOUT \ + ((int16_t) 134) + +#define FUNC_ERROR__VCSELCP_FAIL \ + ((int16_t) 135) + +#define FUNC_ERROR__POR_AVDD1V1_TMOUT \ + ((int16_t) 136) + +#define FUNC_ERROR__CONT_SKIP_VECTOR \ + ((int16_t) 137) + +#define FUNC_ERROR__INT_MGR \ + ((int16_t) 138) + +#define FUNC_ERROR__NVM_EN_FAILED \ + ((int16_t) 139) + +#define FUNC_ERROR__NVM_DONE_TMOUT \ + ((int16_t) 140) + +#define FUNC_ERROR__NVM_DISABLED_FAIL \ + ((int16_t) 13) +#define FUNC_ERROR__TIMESTAMP_TMOUT \ + ((int16_t) 141) + +#define FUNC_ERROR__NVM_CRC_CHECKSUM_FAILED \ + ((int16_t) 142) + +#define FUNC_ERROR__ZONE_INVALID_NUM \ + ((int16_t) 143) + +#define FUNC_ERROR__ZONE_OUT_OF_ACTIVE_BOUND \ + ((int16_t) 144) + +#define FUNC_ERROR__DSS_OPTICAL_CENTRE_MISALIGNED \ + ((int16_t) 17) + +#define FUNC_ERROR__DSS_MIN_SIG_RATE_FAIL \ + ((int16_t) 146) + +#define FUNC_ERROR__DSS_MP_POSITION_INVALID \ + ((int16_t) 147) + +#define FUNC_ERROR__VHV_SEARCH_FAIL \ + ((int16_t) 148) + +#define FUNC_ERROR__RANGING_TIMEOUT \ + ((int16_t) 149) + +#define FUNC_ERROR__VCSEL_PERIOD_CLIPPED \ + ((int16_t) 150) + +#define FUNC_ERROR__VCSEL_STOP_CLIPPED \ + ((int16_t) 151) + +#define FUNC_ERROR__RANGING_WINDOW_CLIPPED \ + ((int16_t) 152) + +#define FUNC_ERROR__AMBIENT_WINDOW_DURATION_CLIPPED \ + ((int16_t) 153) + +#define FUNC_ERROR__SPAD_EN_XFER_FAIL \ + ((int16_t) 154) + +#define FUNC_ERROR__REF_SPAD_EN_TMOUT \ + ((int16_t) 155) + +#define FUNC_ERROR__RTN_SPAD_EN_TMOUT \ + ((int16_t) 156) + +#define FUNC_ERROR__SPAD_REF_STATUS_CHECK \ + ((int16_t) 157) + +#define FUNC_ERROR__SPAD_RTN_STATUS_CHECK \ + ((int16_t) 158) + +#define FUNC_ERROR__GRID_COLS_ROWS_OUT_BOUNDS \ + ((int16_t) 159) + +#define FUNC_ERROR__GRID_COLS_ROWS_NOT_MULT_2 \ + ((int16_t) 160) + +#define FUNC_ERROR__GRID_PARAM_NOT_MULT_4 \ + ((int16_t) 161) + +#define FUNC_ERROR__GRID_ZONES_NOT_SQUARE \ + ((int16_t) 162) + +#define FUNC_ERROR__GRID_ZONE_SIZE_OUT_BOUNDS \ + ((int16_t) 163) + +#define FUNC_ERROR__GRID_START_OUT_BOUNDS \ + ((int16_t) 164) + +#define FUNC_ERROR__INVALID_GRID_CONFIG \ + ((int16_t) 165) + +#define FUNC_ERROR__OPTICAL_CENTRE_OUT_BOUNDS \ + ((int16_t) 166) + +#define FUNC_ERROR__GRID_SIZE_TOO_LARGE \ + ((int16_t) 167) + +#define FUNC_ERROR__REPOSITION_ARRAY_FAIL \ + ((int16_t) 40) + +#define FUNC_WARNING__CALIBRATION \ + ((int16_t) 42) + +#define FUNC_WARNING__PARAM_CLIPPED \ + ((int16_t) 43) + +#define FUNC_WARNING__PARAM_INVALID \ + ((int16_t) 44) + +#define FUNC_ERROR__MODE_NOT_SUPPORTED \ + ((int16_t) 45) + +#define FUNC_ERROR__INVALID_CMD \ + ((int16_t) 46) + +#define FUNC_ERROR__NO_GPIO_PIN \ + ((int16_t) 47) + +#define FUNC_ERROR__GPIO_FN_NOT_SUPPORTED \ + ((int16_t) 48) + +#define FUNC_ERROR__CTRL_INTERFACE \ + ((int16_t) 49) +#define FUNC_ERROR__VCSELCP_FAIL_RETRY \ + ((int16_t) 50) + +#define FUNC_ERROR__REF_SPAD_INIT \ + ((int16_t) 178) + +#define FUNC_ERROR__REF_SPAD_CHAR_NOT_ENOUGH \ + ((int16_t) 51) + +#define FUNC_ERROR__REF_SPAD_CHAR_RATE_HIGH \ + ((int16_t) 52) + +#define FUNC_ERROR__REF_SPAD_CHAR_RATE_LOW \ + ((int16_t) 53) + +#define FUNC_ERROR__REF_SPAD_APPLY_NUM_MAXED \ + ((int16_t) 182) + +#define FUNC_ERROR__REF_SPAD_APPLY_MIN_CLIP \ + ((int16_t) 55) + +#define FUNC_ERROR__XTALK_EXTRN_NO_SAMPLE_FAIL \ + ((int16_t) 56) + +#define FUNC_ERROR__XTALK_EXTRN_SIGMA_LIMIT_FAIL \ + ((int16_t) 57) + +#define FUNC_ERROR__XTALK_EXTRN_MISSING_SAMPLES \ + ((int16_t) 58) + +#define FUNC_ERROR__OFFSET_CAL_NO_SAMPLE_FAIL \ + ((int16_t) 59) + +#define FUNC_ERROR__OFFSET_CAL_NO_SPADS_EN_FAIL \ + ((int16_t) 60) + +#define FUNC_ERROR__OFFSET_CAL_MISSING_SAMPLES \ + ((int16_t) 61) + +#define FUNC_ERROR__OFFSET_CAL_SIGMA_HIGH \ + ((int16_t) 62) + +#define FUNC_ERROR__OFFSET_CAL_RATE_HIGH \ + ((int16_t) 63) + +#define FUNC_ERROR__OFFSET_CAL_SPAD_COUNT_LOW \ + ((int16_t) 64) + +#define FUNC_ERROR__LEGACY_MODE_CHECK_FAILED \ + ((int16_t) 193) + +#define FUNC_ERROR__UNIT_TEST_FAIL \ + ((int16_t) 194) + +#define FUNC_ERROR__DEBUG_DATA_REQ_UI_OVERFLOW \ + ((int16_t) 195) + +#define FUNC_ERROR__TEMPERATURE_LIMIT_REACHED \ + ((int16_t) 196) + +#define FUNC_ERROR__PIPE_ERROR \ + ((int16_t) 197) + +#define FUNC_WARNING__OUTPUT_RATE_CLIPPED \ + ((int16_t) 70) + +#define FUNC_ERROR__DYNXTALKMON_ZONES_SELECT_FAIL \ + ((int16_t) 199) + +#define FUNC_ERROR__CAL_ERROR \ + ((int16_t) 200) + +#define FUNC_ERROR__PIPE_WARNING \ + ((int16_t) 73) + +#define FUNC_ERROR__CAL_WARNING \ + ((int16_t) 74) + +#define FUNC_WARNING__INCOMPATABLE_BLANKING_INTEGRATION_MODES \ + ((int16_t) 75) + +#define FUNC_WARNING__INTEGRATION_TIME_MIN_CLIPPED \ + ((int16_t) 76) + +#define FUNC_WARNING__INTEGRATION_TIME_MAX_CLIPPED \ + ((int16_t) 77) + +#define FUNC_WARNING__CP_COLLAPSE \ + ((int16_t) 78) + +#define FUNC_ERROR__HW_NVM_COPY_LASER_SAFETY_CLIP \ + ((int16_t) 230) + +#define FUNC_ERROR__OVER_VOLT_PROT_AVDD \ + ((int16_t) 233) + +#define FUNC_ERROR__OVER_VOLT_PROT_ANODE \ + ((int16_t) 234) + +#define FUNC_ERROR__OVER_VOLT_PROT_DVDDHP \ + ((int16_t) 235) + +#define FUNC_ERROR__VCSEL_MONITOR_FAIL \ + ((int16_t) 236) + +#define FUNC_ERROR__VCSEL_CP_OVER_CURRENT \ + ((int16_t) 237) + +#define FUNC_ERROR__PLL_LOCK_LOST \ + ((int16_t) 238) + +#define FUNC_ERROR__BAND_GAP_FAIL \ + ((int16_t) 239) + +#define FUNC_ERROR__CP_COLLAPSE \ + ((int16_t) 240) + +#define FUNC_ERROR__DVIM_DIV_BY_ZERO_TRAP \ + ((int16_t) 241) + +#define FUNC_ERROR__OVERFLOW_TRAP \ + ((int16_t) 242) + +#define FUNC_ERROR__TRAP_PROTECT \ + ((int16_t) 243) + +#define FUNC_ERROR__TRAP_OPCODE \ + ((int16_t) 244) + +#define FUNC_ERROR__TRAP_GPRSIZE \ + ((int16_t) 245) + +#define FUNC_ERROR__TRAP_PMISALIGN \ + ((int16_t) 246) + +#define FUNC_ERROR__TRAP_POUTOFMEM \ + ((int16_t) 247) + +#define FUNC_ERROR__TRAP_PEXECUTE \ + ((int16_t) 248) + +#define FUNC_ERROR__TRAP_DMISALIGN \ + ((int16_t) 249) + +#define FUNC_ERROR__TRAP_DOUTOFMEM \ + ((int16_t) 250) + +#define FUNC_ERROR__TRAP_DREAD \ + ((int16_t) 251) + +#define FUNC_ERROR__TRAP_DWRITE \ + ((int16_t) 252) + +#define FUNC_ERROR__TRAP_DVOLATILE \ + ((int16_t) 253) + +#define FUNC_ERROR__TRAP_PSYSERR \ + ((int16_t) 254) + +#define FUNC_EROR__PARAM_INVALID \ + ((int16_t) 65533) + +#define FUNC_ERROR__UNDEFINED \ + ((int16_t) 65534) + +#define FUNC_ERROR__DIVIDE_BY_ZERO \ + ((int16_t) 65535) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/common_datatype_size.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/common_datatype_size.h new file mode 100644 index 000000000000..3f2dbfd986dc --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/common_datatype_size.h @@ -0,0 +1,106 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __COMMON_DATATYPE_SIZE_H__ +#define __COMMON_DATATYPE_SIZE_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_COMMON_GRP_VERSION_SZ \ + ((uint16_t) 8) + +#define VL53L5_COMMON_GRP_STATUS_SZ \ + ((uint16_t) 12) + +#define VL53L5_COMMON_GRP_CAL_XTALK_SHAPE_DATA_SZ \ + ((uint16_t) 288) + +#define VL53L5_CGCXSD_CAL_XTALK_SHAPE_BIN_DATA_SZ \ + ((uint16_t) 288) + +#define VL53L5_COMMON_GRP_HIST_BIN_DATA_SZ \ + ((uint16_t) 576) + +#define VL53L5_COMMON_GRP_HIST_BIN_DATA_PACKED_SZ \ + ((uint16_t) 432) + +#define VL53L5_CGHBD_HIST_BIN_EVENTS_SZ \ + ((uint16_t) 576) + +#define VL53L5_CGHBD_HIST_BIN_EVENTS_PACKED_SZ \ + ((uint16_t) 432) + +#define VL53L5_COMMON_GRP_HIST_BIN_DATA_64B_SZ \ + ((uint16_t) 1152) + +#define VL53L5_CGHBD6_HIST_BIN_EVENTS_64B_SZ \ + ((uint16_t) 1152) + +#define VL53L5_COMMON_GRP_PADDING_32_SZ \ + ((uint16_t) 4) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/common_datatype_structs.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/common_datatype_structs.h new file mode 100644 index 000000000000..915e93e64536 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/common_datatype_structs.h @@ -0,0 +1,113 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __COMMON_DATATYPE_STRUCTS_H__ +#define __COMMON_DATATYPE_STRUCTS_H__ + +#include "packing_structs.h" +#include "common_datatype_defs.h" +#include "common_datatype_luts.h" +#include "packing_structs.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct common_grp__version_t { + + uint16_t version__revision; + + uint16_t version__build; + + uint16_t version__minor; + + uint16_t version__major; +}; + +struct common_grp__status_t { + + int16_t status__group; + + int16_t status__type; + + int16_t status__stage_id; + + uint16_t status__debug_0; + + uint16_t status__debug_1; + + uint16_t status__debug_2; +}; + +struct common_grp__cal__xtalk_shape_data_t { + + uint16_t cal__xtalk_shape_bin_data[COMMON_MAX_XTALK_SHAPE_BINS]; +}; + +struct common_grp__hist__bin_data_64b_t { + + int64_t hist__bin_events_64b[COMMON_MAX_HIST_BINS]; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_defs.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_defs.h new file mode 100644 index 000000000000..372b485a0814 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_defs.h @@ -0,0 +1,225 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __DCI_DEFS_H__ +#define __DCI_DEFS_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define DCI_DEF__FW_WORKSPACE_SIZE \ + ((uint32_t) 2560U) + +#define DCI_DEF__UI_OUTPUT_BUFFER_SIZE \ + ((uint32_t) 2816U) + +#define DCI_DEF__UI_COMMAND_BUFFER_SIZE \ + ((uint32_t) 256U) + +#define DCI_MAX_CHANNELS \ + ((uint32_t) 16U) + +#define DCI_MAX_ZONES \ + ((uint32_t) 68U) + +#define DCI_MAX_GLOBAL_TARGETS \ + ((uint32_t) 8U) +#define DCI_MAX_TARGET_PER_ZONE \ + ((uint32_t) 4U) +#define DCI_MAX_TARGETS \ + ((uint32_t) 272U) +#define DCI_MAX_HIST_BINS \ + ((uint32_t) 144U) +#define DCI_MAX_XTALK_SHAPE_BINS \ + ((uint32_t) 144U) +#define DCI_REF_MPX_MAX_COLS \ + ((uint32_t) 4U) +#define DCI_REF_MPX_MAX_ROWS \ + ((uint32_t) 2U) +#define DCI_RTN_MPX_MAX_COLS \ + ((uint32_t) 14U) +#define DCI_RTN_MPX_MAX_ROWS \ + ((uint32_t) 10U) +#define DCI_OFFSET_CAL_MAX_COLS \ + ((uint32_t) 8U) +#define DCI_OFFSET_CAL_MAX_ROWS \ + ((uint32_t) 8U) +#define DCI_XTALK_CAL_MAX_COLS \ + ((uint32_t) 8U) +#define DCI_XTALK_CAL_MAX_ROWS \ + ((uint32_t) 8U) +#define DCI_XTALK_MON_MAX_ZONES \ + ((uint32_t) 8U) +#define DCI_INITIAL_IDX \ + ((uint32_t) 0U) +#define DCI_REF_MPX_MAX_COLS_X_MAX_ROWS \ + ((uint32_t) DCI_REF_MPX_MAX_COLS * DCI_REF_MPX_MAX_ROWS) +#define DCI_RTN_MPX_MAX_COLS_X_MAX_ROWS \ + ((uint32_t) DCI_RTN_MPX_MAX_COLS * DCI_RTN_MPX_MAX_ROWS) +#define DCI_OFFSET_CAL_MAX_COLS_X_MAX_ROWS \ + ((uint32_t) 64U) +#define DCI_XTALK_CAL_MAX_COLS_X_MAX_ROWS \ + ((uint32_t) 64U) +#define DCI_TRIG_CORR_MAX_COLS_X_MAX_ROWS \ + ((uint32_t) 64U) +#define DCI_REF_ARRAY_WIDTH_SPADS \ + ((uint32_t) 16U) +#define DCI_REF_ARRAY_HEIGHT_SPADS \ + ((uint32_t) 8U) +#define DCI_REF_ARRAY_WIDTH_X_HEIGHT_SPADS \ + ((uint32_t) DCI_REF_ARRAY_WIDTH_SPADS * DCI_REF_ARRAY_HEIGHT_SPADS) +#define DCI_REF_ARRAY_ENABLE_SIZE_BYTES \ + ((uint32_t) 16U) +#define DCI_RTN_ARRAY_WIDTH_SPADS \ + ((uint32_t) 56U) +#define DCI_RTN_ARRAY_HEIGHT_SPADS \ + ((uint32_t) 40U) +#define DCI_RTN_ARRAY_WIDTH_X_HEIGHT_SPADS \ + ((uint32_t) DCI_RTN_ARRAY_WIDTH_SPADS * DCI_RTN_ARRAY_HEIGHT_SPADS) +#define DCI_RTN_ARRAY_ENABLE_SIZE_BYTES \ + ((uint32_t) 280U) +#define DCI_RTN_MPX_BLOCK_INDEX_COUNT \ + ((uint32_t) 140U) +#define DCI_RTN_MPX_BLOCK_DEFINITIONS_COUNT \ + ((uint32_t) 16U) +#define DCI_RTN_MPX_ENABLES_SIZE_BYTES \ + ((uint32_t) 18U) + +#define DCI_RTN_MPX_ENABLES_BUFFER_BYTES \ + ((uint32_t) 20U) +#define DCI_VHV_MAX_SEARCH_STEPS \ + ((uint32_t) 64U) +#define DCI_CAL_REF_SPAD_MAX_SEARCH_STEPS \ + ((uint32_t) 64U) +#define DCI_DEF__MIN_BIN_IDX \ + ((uint32_t) 0U) +#define DCI_DEF__MAX_BIN_IDX \ + ((uint32_t) 255U) +#define DCI_DEF__MIN_RATE_KCPS_PER_SPAD \ + ((uint32_t) 0U) + +#define DCI_DEF__MAX_RATE_KCPS_PER_SPAD \ + ((uint32_t) 1073741823U) +#define DCI_DEF__MIN_RATE_MCPS \ + ((uint32_t) 0U) + +#define DCI_DEF__MAX_RATE_MCPS \ + ((uint32_t) 524287U) +#define DCI_DEF__MIN_PHASE \ + ((uint32_t) 0U) + +#define DCI_DEF__MAX_PHASE \ + ((uint32_t) 524287U) + +#define DCI_DEF__MIN_RANGE_MM \ + ((int32_t) -32768) + +#define DCI_DEF__MAX_RANGE_MM \ + ((int32_t) 32767) +#define DCI_DEF__MIN_RNG_SIGMA_EST_MM \ + ((uint32_t) 0U) + +#define DCI_DEF__MAX_RNG_SIGMA_EST_MM \ + ((uint32_t) 65535U) +#define DCI_DEF__MIN_REFLECT_EST_PC \ + ((uint32_t) 0U) + +#define DCI_DEF__MAX_REFLECT_EST_PC \ + ((uint32_t) 255U) + +#define DCI__ZONE_THRESH_STATUS_ARRAY_SIZE \ + ((uint32_t) 8U) + +#define DCI_INTERRUPT_CFG_MAX_IC_CHECKERS \ + ((uint32_t) 64U) +#define DCI_DEF__MAX_OP_BH_ENABLE_SIZE \ + ((uint32_t) 4U) +#define DCI_DEF__MAX_OP_BH_LIST_SIZE \ + ((uint32_t) 128U) + +#define DCI_DEF__PATCH_HOOK_ENABLE_WORD32 \ + ((uint32_t) 4U) +#define DCI_DEF__MIN_ZSCORE \ + ((uint32_t) 0U) + +#define DCI_DEF__MAX_ZSCORE \ + ((uint32_t) 65535U) +#define DCI_ZONE_CFG_SELECT__INVALID \ + ((uint32_t) 0U) +#define DCI_ZONE_CFG_SELECT__1X1 \ + ((uint32_t) 1U) +#define DCI_ZONE_CFG_SELECT__4X4 \ + ((uint32_t) 2U) +#define DCI_ZONE_CFG_SELECT__8X8 \ + ((uint32_t) 3U) + +#define DCI_SHARPENER_NO_RANGE_GROUP \ + ((uint8_t) 255U) + +#define DCI_SHARPENER_CONFIDENCE_ZERO \ + ((uint8_t) 0U) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_luts.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_luts.h new file mode 100644 index 000000000000..211a03371a15 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_luts.h @@ -0,0 +1,775 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __DCI_LUTS_H__ +#define __DCI_LUTS_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define DCI_BH__P_TYPE__CONSEC_PARAMS_LIST \ + ((uint8_t) 0U) + +#define DCI_BH__P_TYPE__GRP_PARAMS_START \ + ((uint8_t) 13U) + +#define DCI_BH__P_TYPE__GRP_PARAMS_END \ + ((uint8_t) 14U) + +#define DCI_BH__P_TYPE__END_OF_DATA \ + ((uint8_t) 15U) + +#define DCI_FW_STATE_VAL__NOUPDATE \ + ((uint8_t) 0U) + +#define DCI_FW_STATE_VAL__RESET_RETENTION_STATE \ + ((uint8_t) 1U) + +#define DCI_FW_STATE_VAL__BOOT_STATE \ + ((uint8_t) 2U) + +#define DCI_FW_STATE_VAL__DFT_STATE \ + ((uint8_t) 3U) + +#define DCI_FW_STATE_VAL__MAIN_STATE \ + ((uint8_t) 4U) + +#define DCI_FW_STATE_VAL__ACTIVE_STATE \ + ((uint8_t) 5U) + +#define DCI_FW_STATE_VAL__IDLE_STATE \ + ((uint8_t) 6U) + +#define DCI_FW_MODE_STATUS__NO_MODE \ + ((uint8_t) 0U) + +#define DCI_FW_MODE_STATUS__STREAMING \ + ((uint8_t) 1U) + +#define DCI_FW_MODE_STATUS__SINGLE_SHOT \ + ((uint8_t) 2U) + +#define DCI_FW_MODE_STATUS__AUTONOMOUS \ + ((uint8_t) 3U) + +#define DCI_CMD_STATUS__NOUPDATE \ + ((uint8_t) 0U) + +#define DCI_CMD_STATUS__RECEIVED \ + ((uint8_t) 1U) + +#define DCI_CMD_STATUS__PROCESSING \ + ((uint8_t) 2U) + +#define DCI_CMD_STATUS__COMPLETED \ + ((uint8_t) 3U) + +#define DCI_CMD_STATUS__ERROR \ + ((uint8_t) 4U) + +#define DCI_DEVICE_STATUS__NOUPDATE \ + ((uint8_t) 0U) + +#define DCI_DEVICE_STATUS__OK \ + ((uint8_t) 1U) + +#define DCI_DEVICE_STATUS__VCSELCONTINUITYTESTFAILURE \ + ((uint8_t) 2U) + +#define DCI_DEVICE_STATUS__VCSELWATCHDOGTESTFAILURE \ + ((uint8_t) 3U) + +#define DCI_DEVICE_STATUS__VCSELREFERENCERATEFAILURE \ + ((uint8_t) 4U) + +#define DCI_DEVICE_STATUS__NOVHVVALUEFOUND \ + ((uint8_t) 5U) + +#define DCI_DEVICE_STATUS__USERROICLIP \ + ((uint8_t) 6U) + +#define DCI_DEVICE_STATUS__MULTCLIPFAIL \ + ((uint8_t) 7U) + +#define DCI_TARGET_STATUS__NOUPDATE \ + ((uint8_t) 0U) + +#define DCI_TARGET_STATUS__MSRCNOTARGET \ + ((uint8_t) 1U) + +#define DCI_TARGET_STATUS__RANGEPHASECHECK \ + ((uint8_t) 2U) + +#define DCI_TARGET_STATUS__SIGMATHRESHOLDCHECK \ + ((uint8_t) 3U) + +#define DCI_TARGET_STATUS__PHASECONSISTENCY \ + ((uint8_t) 4U) + +#define DCI_TARGET_STATUS__RANGECOMPLETE \ + ((uint8_t) 5U) + +#define DCI_TARGET_STATUS__RANGECOMPLETE_NO_WRAP_CHECK \ + ((uint8_t) 6U) + +#define DCI_TARGET_STATUS__EVENTCONSISTENCY \ + ((uint8_t) 7U) + +#define DCI_TARGET_STATUS__MINSIGNALEVENTCHECK \ + ((uint8_t) 8U) + +#define DCI_TARGET_STATUS__RANGECOMPLETE_MERGED_PULSE \ + ((uint8_t) 9U) + +#define DCI_TARGET_STATUS__PREV_RANGE_NO_TARGETS \ + ((uint8_t) 10U) +#define DCI_TARGET_STATUS__RANGEIGNORETHRESHOLD \ + ((uint8_t) 11U) + +#define DCI_TARGET_STATUS__TARGETDUETOBLUR \ + ((uint8_t) 12U) + +#define DCI_TARGET_STATUS__TARGETDUETOLONGTAIL \ + ((uint8_t) 13U) + +#define DCI_TARGET_STATUS__TARGETSEPARATIONTOOSMALL \ + ((uint8_t) 14U) + +#define DCI_TARGET_STATUS__TARGETDUETOBLUR_NO_WRAP_CHECK \ + ((uint8_t) 15U) + +#define DCI_TARGET_STATUS__TARGETDUETOBLUR_MERGED_PULSE \ + ((uint8_t) 16U) + +#define DCI_TARGET_STATUS__RANGECOMPLETE_PILE_UP \ + ((uint8_t) 17U) + +#define DCI_CMD_ID__NULL \ + ((uint8_t) 0U) + +#define DCI_CMD_ID__SET_PARMS \ + ((uint8_t) 1U) + +#define DCI_CMD_ID__GET_PARMS \ + ((uint8_t) 2U) + +#define DCI_CMD_ID__START_RANGE \ + ((uint8_t) 3U) + +#define DCI_CMD_ID__STOP_RANGE \ + ((uint8_t) 4U) + +#define DCI_DISTANCE_MODE__NONE \ + ((uint8_t) 0U) + +#define DCI_DISTANCE_MODE__AUTO \ + ((uint8_t) 1U) + +#define DCI_DISTANCE_MODE__SHORT \ + ((uint8_t) 2U) + +#define DCI_DISTANCE_MODE__LONG \ + ((uint8_t) 3U) + +#define DCI_RNG_MODE__NONE \ + ((uint8_t) 0U) + +#define DCI_RNG_MODE__BACK_TO_BACK \ + ((uint8_t) 1U) + +#define DCI_RNG_MODE__SINGLE_SHOT \ + ((uint8_t) 2U) + +#define DCI_RNG_MODE__TIMED \ + ((uint8_t) 3U) + +#define DCI_RNG_MODE__CAL__REF_SPAD \ + ((uint8_t) 4U) + +#define DCI_RNG_MODE__CAL__REF_SPAD_WITH_VHV_SEARCH \ + ((uint8_t) 5U) + +#define DCI_RNG_MODE__CAL__XTALK__SINGLE_TARGET \ + ((uint8_t) 6U) + +#define DCI_RNG_MODE__CAL__XTALK__DUAL_TARGET_0 \ + ((uint8_t) 7U) + +#define DCI_RNG_MODE__CAL__XTALK__DUAL_TARGET_1 \ + ((uint8_t) 8U) + +#define DCI_RNG_MODE__CAL__OFFSET_AND_DMAX \ + ((uint8_t) 9U) + +#define DCI_RNG_MODE__TEST__VHV_SEARCH \ + ((uint8_t) 10U) + +#define DCI_RNG_MODE__TEST__SSC__DCR \ + ((uint8_t) 11U) + +#define DCI_RNG_MODE__TEST__SSC__LCR \ + ((uint8_t) 12U) + +#define DCI_RNG_MODE__TEST__SSC__VCR \ + ((uint8_t) 13U) + +#define DCI_RNG_MODE__BACK_TO_BACK_BLANK \ + ((uint8_t) 14U) + +#define DCI_B2BB_DISABLE__RNG_CORE \ + ((uint8_t) 1U) + +#define DCI_B2BB_DISABLE__PLL \ + ((uint8_t) 2U) + +#define DCI_LIVE_XTALK_MODE__NONE \ + ((uint8_t) 0U) + +#define DCI_LIVE_XTALK_MODE__DISABLED \ + ((uint8_t) 1U) + +#define DCI_LIVE_XTALK_MODE__ENABLED \ + ((uint8_t) 2U) + +#define DCI_INTEGRATION_MODE__NONE \ + ((uint8_t) 0U) + +#define DCI_INTEGRATION_MODE__MIN \ + ((uint8_t) 1U) + +#define DCI_INTEGRATION_MODE__MAX \ + ((uint8_t) 2U) + +#define DCI_INTEGRATION_MODE__RANGING_RATE \ + ((uint8_t) 3U) + +#define DCI_INTEGRATION_MODE__AUTO__INVERSE_DMAX \ + ((uint8_t) 4U) + +#define DCI_INTEGRATION_MODE__AUTO__RNG_SIGMA \ + ((uint8_t) 5U) + +#define DCI_BLANKING_MODE__OFF \ + ((uint8_t) 0U) + +#define DCI_BLANKING_MODE__BA \ + ((uint8_t) 1U) + +#define DCI_BLANKING_MODE__AB_BA \ + ((uint8_t) 2U) + +#define DCI_BLANKING_TYPE__INVALID \ + ((uint8_t) 0U) + +#define DCI_BLANKING_TYPE__NORMAL \ + ((uint8_t) 1U) + +#define DCI_BLANKING_TYPE__RETENTION \ + ((uint8_t) 2U) + +#define DCI_BLANKING_TYPE__INTERRUPT \ + ((uint8_t) 3U) + +#define DCI_DSS_MODE__NONE \ + ((uint8_t) 0U) + +#define DCI_DSS_MODE__AUTO \ + ((uint8_t) 1U) + +#define DCI_DSS_MODE__PAUSE \ + ((uint8_t) 2U) + +#define DCI_DSS_MODE__MANUAL \ + ((uint8_t) 3U) + +#define DCI_DSS_SPATIAL_MODE__INVALID \ + ((uint8_t) 0U) + +#define DCI_DSS_SPATIAL_MODE__OFF \ + ((uint8_t) 1U) + +#define DCI_DSS_SPATIAL_MODE__ON \ + ((uint8_t) 2U) + +#define DCI_HIST_TYPE__INVALID \ + ((uint8_t) 0U) + +#define DCI_HIST_TYPE__RTN_ARRAY \ + ((uint8_t) 1U) + +#define DCI_HIST_TYPE__REF_ARRAY \ + ((uint8_t) 2U) + +#define DCI_HIST_TYPE__XTALK \ + ((uint8_t) 3U) + +#define DCI_HIST_TYPE__OFFSET \ + ((uint8_t) 4U) + +#define DCI_HIST_TYPE__FLEX \ + ((uint8_t) 5U) + +#define DCI_HIST_TYPE__XTALK__FLEX \ + ((uint8_t) 6U) + +#define DCI_HIST_TYPE__OFFSET__FLEX \ + ((uint8_t) 7U) + +#define DCI_HIST_TYPE__ACC \ + ((uint8_t) 8U) + +#define DCI_HIST_TYPE__ACC__XTALK \ + ((uint8_t) 9U) + +#define DCI_HIST_TYPE__ACC__OFFSET \ + ((uint8_t) 10U) + +#define DCI_HIST_TYPE__ACC__PARTIAL \ + ((uint8_t) 11U) + +#define DCI_MPX_TYPE__INVALID \ + ((uint8_t) 0U) + +#define DCI_MPX_TYPE__RTN_ARRAY \ + ((uint8_t) 1U) + +#define DCI_MPX_TYPE__REF_ARRAY \ + ((uint8_t) 2U) + +#define DCI_MPX_TYPE__RTN_XTALK \ + ((uint8_t) 3U) + +#define DCI_MPX_TYPE__RTN_OFFSET \ + ((uint8_t) 4U) + +#define DCI__AMB_RATE_KCPS_PER_SPAD__EN \ + ((uint32_t) 1U) +#define DCI__AMB_RATE_MCPS__EN \ + ((uint32_t) 2U) +#define DCI__RNG__EFFECTIVE_SPAD_COUNT__EN \ + ((uint32_t) 4U) +#define DCI__AMB_DMAX_MM__EN \ + ((uint32_t) 8U) +#define DCI__RNG__NO_OF_TARGETS__EN \ + ((uint32_t) 16U) +#define DCI__RNG__ZONE_ID__EN \ + ((uint32_t) 32U) + +#define DCI__PEAK_RATE_KCPS_PER_SPAD__EN \ + ((uint32_t) 1U) +#define DCI__PEAK_RATE_MCPS__EN \ + ((uint32_t) 2U) +#define DCI__MEDIAN_RANGE_MM__EN \ + ((uint32_t) 4U) +#define DCI__MEDIAN_PHASE__EN \ + ((uint32_t) 8U) +#define DCI__TARGET_RANGING_EVENTS__EN \ + ((uint32_t) 16U) +#define DCI__TARGET_AMB_EVENTS__EN \ + ((uint32_t) 32U) +#define DCI__SIGNAL_SIGMA_EVENTS__EN \ + ((uint32_t) 64U) +#define DCI__RANGE_SIIGMA_MM__EN \ + ((uint32_t) 128U) +#define DCI__MIN_RANGE_DELTA_MM__EN \ + ((uint32_t) 256U) +#define DCI__MAX_RANGE_DELTA_MM__EN \ + ((uint32_t) 512U) +#define DCI__TARGET_STATUS__EN \ + ((uint32_t) 1024U) + +#define DCI__GBL_TGT__PEAK_RATE_KCPS_PER_SPAD__EN \ + ((uint32_t) 1U) +#define DCI__GBL_TGT__AMB_RATE_KCPS_PER_SPAD__EN \ + ((uint32_t) 2U) +#define DCI__GBL_TGT__PEAK_RATE_MCPS__EN \ + ((uint32_t) 4U) +#define DCI__GBL_TGT__AMB_RATE_MCPS__EN \ + ((uint32_t) 8U) +#define DCI__GBL_TGT__MEDIAN_RANGE_MM__EN \ + ((uint32_t) 16U) +#define DCI__GBL_TGT__MIN_RANGE_DELTA_MM__EN \ + ((uint32_t) 32U) +#define DCI__GBL_TGT__MAX_RANGE_DELTA_MM__EN \ + ((uint32_t) 64U) +#define DCI__GBL_TGT__STATUS__EN \ + ((uint32_t) 128U) +#define DCI__GBL_TGT__X_CENTRE__EN \ + ((uint32_t) 256U) +#define DCI__GBL_TGT__Y_CENTRE__EN \ + ((uint32_t) 512U) +#define DCI__GBL_TGT__WIDTH__EN \ + ((uint32_t) 1024U) +#define DCI__GBL_TGT__HEIGHT__EN \ + ((uint32_t) 2048U) +#define DCI__GBL_TGT__NO_OF_ROIS__EN \ + ((uint32_t) 4096U) + +#define DCI__RTN_MPX__PEAK_RATE_KCPS_PER_SPAD__EN \ + ((uint32_t) 1U) +#define DCI__RTN_MPX__AMB_RATE_KCPS_PER_SPAD__EN \ + ((uint32_t) 2U) +#define DCI__RTN_MPX__PEAK_RATE_MCPS__EN \ + ((uint32_t) 4U) +#define DCI__RTN_MPX__AMB_RATE_MCPS__EN \ + ((uint32_t) 8U) +#define DCI__RTN_MPX__RANGING_TOTAL_EVENTS__EN \ + ((uint32_t) 16U) +#define DCI__RTN_MPX__AMBIENT_WINDOW_EVENTS__EN \ + ((uint32_t) 32U) +#define DCI__RTN_MPX__EFFECTIVE_SPAD_COUNT__EN \ + ((uint32_t) 64U) +#define DCI__RTN_MPX__CONFIDENCE__EN \ + ((uint32_t) 128U) + +#define DCI__DYN_XTALK_LEAKAGE_FULL_IDX \ + ((uint8_t) 0U) +#define DCI__DYN_XTALK_LEAKAGE_LEFT_IDX \ + ((uint8_t) 1U) +#define DCI__DYN_XTALK_LEAKAGE_RIGHT_IDX \ + ((uint8_t) 2U) +#define DCI__DYN_XTALK_LEAKAGE_SPARE_IDX \ + ((uint8_t) 3U) +#define DCI__DYN_XTALK_MONITOR_LEFT_1_IDX \ + ((uint8_t) 4U) +#define DCI__DYN_XTALK_MONITOR_LEFT_2_IDX \ + ((uint8_t) 5U) +#define DCI__DYN_XTALK_MONITOR_RIGHT_1_IDX \ + ((uint8_t) 6U) +#define DCI__DYN_XTALK_MONITOR_RIGHT_2_IDX \ + ((uint8_t) 7U) + +#define DCI__INTERRUPT_CFG_MODE__NONE \ + ((uint8_t) 0U) +#define DCI__INTERRUPT_CFG_MODE__NEW_DATA_READY \ + ((uint8_t) 1U) +#define DCI__INTERRUPT_CFG_MODE__THRESHOLDS \ + ((uint8_t) 2U) + +#define DCI__PHOOK_EN_00_HOOK_000 \ + ((uint32_t) 1U) +#define DCI__PHOOK_EN_00_HOOK_001 \ + ((uint32_t) 2U) +#define DCI__PHOOK_EN_00_HOOK_002 \ + ((uint32_t) 4U) +#define DCI__PHOOK_EN_00_HOOK_003 \ + ((uint32_t) 8U) +#define DCI__PHOOK_EN_00_HOOK_004 \ + ((uint32_t) 16U) +#define DCI__PHOOK_EN_00_HOOK_005 \ + ((uint32_t) 32U) +#define DCI__PHOOK_EN_00_HOOK_006 \ + ((uint32_t) 64U) +#define DCI__PHOOK_EN_00_HOOK_007 \ + ((uint32_t) 128U) +#define DCI__PHOOK_EN_00_HOOK_008 \ + ((uint32_t) 256U) +#define DCI__PHOOK_EN_00_HOOK_009 \ + ((uint32_t) 512U) +#define DCI__PHOOK_EN_00_HOOK_010 \ + ((uint32_t) 1024U) +#define DCI__PHOOK_EN_00_HOOK_011 \ + ((uint32_t) 2048U) +#define DCI__PHOOK_EN_00_HOOK_012 \ + ((uint32_t) 4096U) +#define DCI__PHOOK_EN_00_HOOK_013 \ + ((uint32_t) 8192U) +#define DCI__PHOOK_EN_00_HOOK_014 \ + ((uint32_t) 16384U) +#define DCI__PHOOK_EN_00_HOOK_015 \ + ((uint32_t) 32768U) +#define DCI__PHOOK_EN_00_HOOK_016 \ + ((uint32_t) 65536U) +#define DCI__PHOOK_EN_00_HOOK_017 \ + ((uint32_t) 131072U) +#define DCI__PHOOK_EN_00_HOOK_018 \ + ((uint32_t) 262144U) +#define DCI__PHOOK_EN_00_HOOK_019 \ + ((uint32_t) 524288U) +#define DCI__PHOOK_EN_00_HOOK_020 \ + ((uint32_t) 1048576U) +#define DCI__PHOOK_EN_00_HOOK_021 \ + ((uint32_t) 2097152U) +#define DCI__PHOOK_EN_00_HOOK_022 \ + ((uint32_t) 4194304U) +#define DCI__PHOOK_EN_00_HOOK_023 \ + ((uint32_t) 8388608U) +#define DCI__PHOOK_EN_00_HOOK_024 \ + ((uint32_t) 16777216U) +#define DCI__PHOOK_EN_00_HOOK_025 \ + ((uint32_t) 33554432U) +#define DCI__PHOOK_EN_00_HOOK_026 \ + ((uint32_t) 67108864U) +#define DCI__PHOOK_EN_00_HOOK_027 \ + ((uint32_t) 134217728U) +#define DCI__PHOOK_EN_00_HOOK_028 \ + ((uint32_t) 268435456U) +#define DCI__PHOOK_EN_00_HOOK_029 \ + ((uint32_t) 536870912U) +#define DCI__PHOOK_EN_00_HOOK_030 \ + ((uint32_t) 1073741824U) +#define DCI__PHOOK_EN_00_HOOK_031 \ + ((uint32_t) 2147483648U) +#define DCI__PHOOK_EN_01_HOOK_032 \ + ((uint32_t) 1U) +#define DCI__PHOOK_EN_01_HOOK_033 \ + ((uint32_t) 2U) +#define DCI__PHOOK_EN_01_HOOK_034 \ + ((uint32_t) 4U) +#define DCI__PHOOK_EN_01_HOOK_035 \ + ((uint32_t) 8U) +#define DCI__PHOOK_EN_01_HOOK_036 \ + ((uint32_t) 16U) +#define DCI__PHOOK_EN_01_HOOK_037 \ + ((uint32_t) 32U) +#define DCI__PHOOK_EN_01_HOOK_038 \ + ((uint32_t) 64U) +#define DCI__PHOOK_EN_01_HOOK_039 \ + ((uint32_t) 128U) +#define DCI__PHOOK_EN_01_HOOK_040 \ + ((uint32_t) 256U) +#define DCI__PHOOK_EN_01_HOOK_041 \ + ((uint32_t) 512U) +#define DCI__PHOOK_EN_01_HOOK_042 \ + ((uint32_t) 1024U) +#define DCI__PHOOK_EN_01_HOOK_043 \ + ((uint32_t) 2048U) +#define DCI__PHOOK_EN_01_HOOK_044 \ + ((uint32_t) 4096U) +#define DCI__PHOOK_EN_01_HOOK_045 \ + ((uint32_t) 8192U) +#define DCI__PHOOK_EN_01_HOOK_046 \ + ((uint32_t) 16384U) +#define DCI__PHOOK_EN_01_HOOK_047 \ + ((uint32_t) 32768U) +#define DCI__PHOOK_EN_01_HOOK_048 \ + ((uint32_t) 65536U) +#define DCI__PHOOK_EN_01_HOOK_049 \ + ((uint32_t) 131072U) +#define DCI__PHOOK_EN_01_HOOK_050 \ + ((uint32_t) 262144U) +#define DCI__PHOOK_EN_01_HOOK_051 \ + ((uint32_t) 524288U) +#define DCI__PHOOK_EN_01_HOOK_052 \ + ((uint32_t) 1048576U) +#define DCI__PHOOK_EN_01_HOOK_053 \ + ((uint32_t) 2097152U) +#define DCI__PHOOK_EN_01_HOOK_054 \ + ((uint32_t) 4194304U) +#define DCI__PHOOK_EN_01_HOOK_055 \ + ((uint32_t) 8388608U) +#define DCI__PHOOK_EN_01_HOOK_056 \ + ((uint32_t) 16777216U) +#define DCI__PHOOK_EN_01_HOOK_057 \ + ((uint32_t) 33554432U) +#define DCI__PHOOK_EN_01_HOOK_058 \ + ((uint32_t) 67108864U) +#define DCI__PHOOK_EN_01_HOOK_059 \ + ((uint32_t) 134217728U) +#define DCI__PHOOK_EN_01_HOOK_060 \ + ((uint32_t) 268435456U) +#define DCI__PHOOK_EN_01_HOOK_061 \ + ((uint32_t) 536870912U) +#define DCI__PHOOK_EN_01_HOOK_062 \ + ((uint32_t) 1073741824U) +#define DCI__PHOOK_EN_01_HOOK_063 \ + ((uint32_t) 2147483648U) +#define DCI__PHOOK_EN_02_HOOK_064 \ + ((uint32_t) 1U) +#define DCI__PHOOK_EN_02_HOOK_065 \ + ((uint32_t) 2U) +#define DCI__PHOOK_EN_02_HOOK_066 \ + ((uint32_t) 4U) +#define DCI__PHOOK_EN_02_HOOK_067 \ + ((uint32_t) 8U) +#define DCI__PHOOK_EN_02_HOOK_068 \ + ((uint32_t) 16U) +#define DCI__PHOOK_EN_02_HOOK_069 \ + ((uint32_t) 32U) +#define DCI__PHOOK_EN_02_HOOK_070 \ + ((uint32_t) 64U) +#define DCI__PHOOK_EN_02_HOOK_071 \ + ((uint32_t) 128U) +#define DCI__PHOOK_EN_02_HOOK_072 \ + ((uint32_t) 256U) +#define DCI__PHOOK_EN_02_HOOK_073 \ + ((uint32_t) 512U) +#define DCI__PHOOK_EN_02_HOOK_074 \ + ((uint32_t) 1024U) +#define DCI__PHOOK_EN_02_HOOK_075 \ + ((uint32_t) 2048U) +#define DCI__PHOOK_EN_02_HOOK_076 \ + ((uint32_t) 4096U) +#define DCI__PHOOK_EN_02_HOOK_077 \ + ((uint32_t) 8192U) +#define DCI__PHOOK_EN_02_HOOK_078 \ + ((uint32_t) 16384U) +#define DCI__PHOOK_EN_02_HOOK_079 \ + ((uint32_t) 32768U) +#define DCI__PHOOK_EN_02_HOOK_080 \ + ((uint32_t) 65536U) +#define DCI__PHOOK_EN_02_HOOK_081 \ + ((uint32_t) 131072U) +#define DCI__PHOOK_EN_02_HOOK_082 \ + ((uint32_t) 262144U) +#define DCI__PHOOK_EN_02_HOOK_083 \ + ((uint32_t) 524288U) +#define DCI__PHOOK_EN_02_HOOK_084 \ + ((uint32_t) 1048576U) +#define DCI__PHOOK_EN_02_HOOK_085 \ + ((uint32_t) 2097152U) +#define DCI__PHOOK_EN_02_HOOK_086 \ + ((uint32_t) 4194304U) +#define DCI__PHOOK_EN_02_HOOK_087 \ + ((uint32_t) 8388608U) +#define DCI__PHOOK_EN_02_HOOK_088 \ + ((uint32_t) 16777216U) +#define DCI__PHOOK_EN_02_HOOK_089 \ + ((uint32_t) 33554432U) +#define DCI__PHOOK_EN_02_HOOK_090 \ + ((uint32_t) 67108864U) +#define DCI__PHOOK_EN_02_HOOK_091 \ + ((uint32_t) 134217728U) +#define DCI__PHOOK_EN_02_HOOK_092 \ + ((uint32_t) 268435456U) +#define DCI__PHOOK_EN_02_HOOK_093 \ + ((uint32_t) 536870912U) +#define DCI__PHOOK_EN_02_HOOK_094 \ + ((uint32_t) 1073741824U) +#define DCI__PHOOK_EN_02_HOOK_095 \ + ((uint32_t) 2147483648U) +#define DCI__PHOOK_EN_03_HOOK_096 \ + ((uint32_t) 1U) +#define DCI__PHOOK_EN_03_HOOK_097 \ + ((uint32_t) 2U) +#define DCI__PHOOK_EN_03_HOOK_098 \ + ((uint32_t) 4U) +#define DCI__PHOOK_EN_03_HOOK_099 \ + ((uint32_t) 8U) +#define DCI__PHOOK_EN_03_HOOK_100 \ + ((uint32_t) 16U) +#define DCI__PHOOK_EN_03_HOOK_101 \ + ((uint32_t) 32U) +#define DCI__PHOOK_EN_03_HOOK_102 \ + ((uint32_t) 64U) +#define DCI__PHOOK_EN_03_HOOK_103 \ + ((uint32_t) 128U) +#define DCI__PHOOK_EN_03_HOOK_104 \ + ((uint32_t) 256U) +#define DCI__PHOOK_EN_03_HOOK_105 \ + ((uint32_t) 512U) +#define DCI__PHOOK_EN_03_HOOK_106 \ + ((uint32_t) 1024U) +#define DCI__PHOOK_EN_03_HOOK_107 \ + ((uint32_t) 2048U) +#define DCI__PHOOK_EN_03_HOOK_108 \ + ((uint32_t) 4096U) +#define DCI__PHOOK_EN_03_HOOK_109 \ + ((uint32_t) 8192U) +#define DCI__PHOOK_EN_03_HOOK_110 \ + ((uint32_t) 16384U) +#define DCI__PHOOK_EN_03_HOOK_111 \ + ((uint32_t) 32768U) +#define DCI__PHOOK_EN_03_HOOK_112 \ + ((uint32_t) 65536U) +#define DCI__PHOOK_EN_03_HOOK_113 \ + ((uint32_t) 131072U) +#define DCI__PHOOK_EN_03_HOOK_114 \ + ((uint32_t) 262144U) +#define DCI__PHOOK_EN_03_HOOK_115 \ + ((uint32_t) 524288U) +#define DCI__PHOOK_EN_03_HOOK_116 \ + ((uint32_t) 1048576U) +#define DCI__PHOOK_EN_03_HOOK_117 \ + ((uint32_t) 2097152U) +#define DCI__PHOOK_EN_03_HOOK_118 \ + ((uint32_t) 4194304U) +#define DCI__PHOOK_EN_03_HOOK_119 \ + ((uint32_t) 8388608U) +#define DCI__PHOOK_EN_03_HOOK_120 \ + ((uint32_t) 16777216U) +#define DCI__PHOOK_EN_03_HOOK_121 \ + ((uint32_t) 33554432U) +#define DCI__PHOOK_EN_03_HOOK_122 \ + ((uint32_t) 67108864U) +#define DCI__PHOOK_EN_03_HOOK_123 \ + ((uint32_t) 134217728U) +#define DCI__PHOOK_EN_03_HOOK_124 \ + ((uint32_t) 268435456U) +#define DCI__PHOOK_EN_03_HOOK_125 \ + ((uint32_t) 536870912U) +#define DCI__PHOOK_EN_03_HOOK_126 \ + ((uint32_t) 1073741824U) +#define DCI__PHOOK_EN_03_HOOK_127 \ + ((uint32_t) 2147483648U) + +#define DCI__ZONE_ID__ACC__0 \ + ((uint8_t) 251U) + +#define DCI_ZONE_ID__REF \ + ((uint8_t) 255U) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_size.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_size.h new file mode 100644 index 000000000000..9b3e62781861 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_size.h @@ -0,0 +1,541 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __DCI_SIZE_H__ +#define __DCI_SIZE_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_FW_WORKSPACE_SZ \ + ((uint16_t) 10240) + +#define VL53L5_FW_FW_WORKSPACE_ARRAY_SZ \ + ((uint16_t) 10240) + +#define VL53L5_UI_BUFFER_SZ \ + ((uint16_t) 11264) + +#define VL53L5_UB_DCI_OUTPUT_BUFFER_SZ \ + ((uint16_t) 11264) + +#define VL53L5_MAP_VERSION_SZ \ + ((uint16_t) 4) + +#define VL53L5_FW_VERSION_SZ \ + ((uint16_t) 8) + +#define VL53L5_CFG_INFO_SZ \ + ((uint16_t) 20) + +#define VL53L5_CP_COLLAPSE_CTRL_SZ \ + ((uint16_t) 4) + +#define VL53L5_SILICON_TEMPERATURE_DATA_SZ \ + ((uint16_t) 4) + +#define VL53L5_FMT_TRACEABILITY_SZ \ + ((uint16_t) 24) + +#define VL53L5_EWS_TRACEABILITY_SZ \ + ((uint16_t) 12) + +#define VL53L5_ZONE_CFG_SZ \ + ((uint16_t) 8) + +#define VL53L5_DEVICE_MODE_CFG_SZ \ + ((uint16_t) 8) + +#define VL53L5_DSS_CFG_SZ \ + ((uint16_t) 16) + +#define VL53L5_RANGING_RATE_CFG_SZ \ + ((uint16_t) 4) + +#define VL53L5_INTEGRATION_TIME_CFG_SZ \ + ((uint16_t) 4) + +#define VL53L5_FACTORY_CAL_CFG_SZ \ + ((uint16_t) 8) + +#define VL53L5_OUTPUT_DATA_CFG_SZ \ + ((uint16_t) 4) + +#define VL53L5_INTERRUPT_CFG_SZ \ + ((uint16_t) 8) + +#define VL53L5_TIMING_CFG_SZ \ + ((uint16_t) 16) + +#define VL53L5_STATIC_TIMING_CFG_SZ \ + ((uint16_t) 8) + +#define VL53L5_VHV_CFG_SZ \ + ((uint16_t) 12) + +#define VL53L5_REF_MSR_CFG_SZ \ + ((uint16_t) 8) + +#define VL53L5_VHV_REF_SCHEDULING_CTRL_SZ \ + ((uint16_t) 8) + +#define VL53L5_CAL_REF_SPAD_CFG_SZ \ + ((uint16_t) 8) + +#define VL53L5_SSC_CFG_SZ \ + ((uint16_t) 4) + +#define VL53L5_CAL_REF_SPAD_INFO_SZ \ + ((uint16_t) 8) + +#define VL53L5_CAL_TEMP_SENSOR_DATA_SZ \ + ((uint16_t) 4) + +#define VL53L5_CAL_OPTICAL_CENTRE_DATA_SZ \ + ((uint16_t) 4) + +#define VL53L5_REF_ARRAY_META_DATA_SZ \ + ((uint16_t) 8) + +#define VL53L5_RTN_ARRAY_META_DATA_SZ \ + ((uint16_t) 8) + +#define VL53L5_REF_ARRAY_SPAD_ENABLES_SZ \ + ((uint16_t) 16) + +#define VL53L5_RASE_REF_ARRAY_SPADS_ENABLES_SZ \ + ((uint16_t) 16) + +#define VL53L5_RTN_ARRAY_SPAD_ENABLES_SZ \ + ((uint16_t) 280) + +#define VL53L5_RASE_RTN_ARRAY_SPAD_ENABLES_SZ \ + ((uint16_t) 280) + +#define VL53L5_UI_RNG_DATA_ADDR_SZ \ + ((uint16_t) 12) + +#define VL53L5_BUF_META_DATA_SZ \ + ((uint16_t) 12) + +#define VL53L5_HIST_ZONE_GRID_POINT_META_DATA_SZ \ + ((uint16_t) 8) + +#define VL53L5_HIST_ZONE_GRID_POINT_DATA_SZ \ + ((uint16_t) 136) + +#define VL53L5_HZGPD_HIST_ZONE_CENTRE_X_COORD_SZ \ + ((uint16_t) 68) + +#define VL53L5_HZGPD_HIST_ZONE_CENTRE_Y_COORD_SZ \ + ((uint16_t) 68) + +#define VL53L5_HIST_COMMON_DATA_SZ \ + ((uint16_t) 20) + +#define VL53L5_HIST_TIMING_DATA_SZ \ + ((uint16_t) 4) + +#define VL53L5_HIST_CHANNEL_DATA_SZ \ + ((uint16_t) 224) + +#define VL53L5_HIST_CHANNEL_DATA_PACKED_SZ \ + ((uint16_t) 176) + +#define VL53L5_HCD_HIST_EFFECTIVE_SPAD_COUNT_SZ \ + ((uint16_t) 64) + +#define VL53L5_HCD_HIST_EFFECTIVE_SPAD_COUNT_PACKED_SZ \ + ((uint16_t) 48) + +#define VL53L5_HCD_HIST_AMB_WINDOW_EVENTS_SZ \ + ((uint16_t) 64) + +#define VL53L5_HCD_HIST_AMB_WINDOW_EVENTS_PACKED_SZ \ + ((uint16_t) 48) + +#define VL53L5_HCD_HIST_AMB_EVENTS_PER_BIN_SZ \ + ((uint16_t) 64) + +#define VL53L5_HCD_HIST_AMB_EVENTS_PER_BIN_PACKED_SZ \ + ((uint16_t) 48) + +#define VL53L5_HCD_HIST_ZONE_ID_SZ \ + ((uint16_t) 16) + +#define VL53L5_HCD_HIST_TYPE_SZ \ + ((uint16_t) 16) + +#define VL53L5_HIST_REF_CHANNEL_DATA_SZ \ + ((uint16_t) 16) + +#define VL53L5_HIST_REF_CHANNEL_DATA_PACKED_SZ \ + ((uint16_t) 13) + +#define VL53L5_HIST_ACC_DATA_SZ \ + ((uint16_t) 8) + +#define VL53L5_MPX_META_DATA_SZ \ + ((uint16_t) 4) + +#define VL53L5_MPX_COMMON_DATA_SZ \ + ((uint16_t) 12) + +#define VL53L5_MPX_TIMING_DATA_SZ \ + ((uint16_t) 4) + +#define VL53L5_REF_MPX_DATA_SZ \ + ((uint16_t) 192) + +#define VL53L5_RMD_REF_MPX_PEAK_RATE_KCPS_PER_SPAD_SZ \ + ((uint16_t) 32) + +#define VL53L5_RMD_REF_MPX_AMB_RATE_KCPS_PER_SPAD_SZ \ + ((uint16_t) 32) + +#define VL53L5_RMD_REF_MPX_RANGING_TOTAL_EVENTS_SZ \ + ((uint16_t) 32) + +#define VL53L5_RMD_REF_MPX_AMBIENT_WINDOW_EVENTS_SZ \ + ((uint16_t) 32) + +#define VL53L5_RMD_REF_MPX_EFFECTIVE_SPAD_COUNT_SZ \ + ((uint16_t) 32) + +#define VL53L5_RMD_REF_MPX_SPATIAL_METRIC_SZ \ + ((uint16_t) 32) + +#define VL53L5_RTN_MPX_DATA_SZ \ + ((uint16_t) 3360) + +#define VL53L5_RMD_RTN_MPX_PEAK_RATE_KCPS_PER_SPAD_SZ \ + ((uint16_t) 560) + +#define VL53L5_RMD_RTN_MPX_AMB_RATE_KCPS_PER_SPAD_SZ \ + ((uint16_t) 560) + +#define VL53L5_RMD_RTN_MPX_RANGING_TOTAL_EVENTS_SZ \ + ((uint16_t) 560) + +#define VL53L5_RMD_RTN_MPX_AMBIENT_WINDOW_EVENTS_SZ \ + ((uint16_t) 560) + +#define VL53L5_RMD_RTN_MPX_EFFECTIVE_SPAD_COUNT_SZ \ + ((uint16_t) 560) + +#define VL53L5_RMD_RTN_MPX_SPATIAL_METRIC_SZ \ + ((uint16_t) 560) + +#define VL53L5_RNG_TIMING_DATA_SZ \ + ((uint16_t) 12) + +#define VL53L5_RNG_COMMON_DATA_SZ \ + ((uint16_t) 8) + +#define VL53L5_RNG_PER_ZONE_DATA_SZ \ + ((uint16_t) 1020) + +#define VL53L5_RPZD_AMB_RATE_KCPS_PER_SPAD_SZ \ + ((uint16_t) 4 * VL53L5_MAX_ZONES) + +#define VL53L5_RPZD_RNG_EFFECTIVE_SPAD_COUNT_SZ \ + ((uint16_t) 4 * VL53L5_MAX_ZONES) + +#define VL53L5_RPZD_AMB_DMAX_MM_SZ \ + ((uint16_t) 2 * VL53L5_MAX_ZONES) + +#define VL53L5_RPZD_SILICON_TEMP_DEGC_START_SZ \ + ((uint16_t) 1 * VL53L5_MAX_ZONES) + +#define VL53L5_RPZD_SILICON_TEMP_DEGC_END_SZ \ + ((uint16_t) 1 * VL53L5_MAX_ZONES) + +#define VL53L5_RPZD_RNG_NO_OF_TARGETS_SZ \ + ((uint16_t) 1 * VL53L5_MAX_ZONES) + +#define VL53L5_RPZD_RNG_ZONE_ID_SZ \ + ((uint16_t) 1 * VL53L5_MAX_ZONES) + +#define VL53L5_RPZD_RNG_SEQUENCE_IDX_SZ \ + ((uint16_t) 1 * VL53L5_MAX_ZONES) + +#define VL53L5_RNG_PER_TARGET_DATA_SZ \ + ((uint16_t) 7072) + +#define VL53L5_RPTD_PEAK_RATE_KCPS_PER_SPAD_SZ \ + ((uint16_t) 4 * VL53L5_MAX_TARGETS) + +#define VL53L5_RPTD_MEDIAN_PHASE_SZ \ + ((uint16_t) 4 * VL53L5_MAX_TARGETS) + +#define VL53L5_RPTD_RATE_SIGMA_KCPS_PER_SPAD_SZ \ + ((uint16_t) 4 * VL53L5_MAX_TARGETS) + +#define VL53L5_RPTD_TARGET_ZSCORE_SZ \ + ((uint16_t) 2 * VL53L5_MAX_TARGETS) + +#define VL53L5_RPTD_RANGE_SIGMA_MM_SZ \ + ((uint16_t) 2 * VL53L5_MAX_TARGETS) + +#define VL53L5_RPTD_MEDIAN_RANGE_MM_SZ \ + ((uint16_t) 2 * VL53L5_MAX_TARGETS) + +#define VL53L5_RPTD_START_RANGE_MM_SZ \ + ((uint16_t) 2 * VL53L5_MAX_TARGETS) + +#define VL53L5_RPTD_END_RANGE_MM_SZ \ + ((uint16_t) 2 * VL53L5_MAX_TARGETS) + +#define VL53L5_RPTD_MIN_RANGE_DELTA_MM_SZ \ + ((uint16_t) 1 * VL53L5_MAX_TARGETS) + +#define VL53L5_RPTD_MAX_RANGE_DELTA_MM_SZ \ + ((uint16_t) 1 * VL53L5_MAX_TARGETS) + +#define VL53L5_RPTD_TARGET_REFLECTANCE_EST_PC_SZ \ + ((uint16_t) 1 * VL53L5_MAX_TARGETS) + +#define VL53L5_RPTD_TARGET_STATUS_SZ \ + ((uint16_t) 1 * VL53L5_MAX_TARGETS) + +#define VL53L5_REF_CHANNEL_DATA_SZ \ + ((uint16_t) 16) + +#define VL53L5_REF_TARGET_DATA_SZ \ + ((uint16_t) 28) + +#define VL53L5_GBL_TGT_META_DATA_SZ \ + ((uint16_t) 8) + +#define VL53L5_GBL_TGT_DATA_SZ \ + ((uint16_t) 216) + +#define VL53L5_GBL_TGT_DATA_PACKED_SZ \ + ((uint16_t) 200) + +#define VL53L5_GTD_GBL_TGT_PEAK_RATE_KCPS_PER_SPAD_SZ \ + ((uint16_t) 32) + +#define VL53L5_GTD_GBL_TGT_AMB_RATE_KCPS_PER_SPAD_SZ \ + ((uint16_t) 32) + +#define VL53L5_GTD_GBL_TGT_PEAK_RATE_MCPS_SZ \ + ((uint16_t) 32) + +#define VL53L5_GTD_GBL_TGT_PEAK_RATE_MCPS_PACKED_SZ \ + ((uint16_t) 24) + +#define VL53L5_GTD_GBL_TGT_AMB_RATE_MCPS_SZ \ + ((uint16_t) 32) + +#define VL53L5_GTD_GBL_TGT_AMB_RATE_MCPS_PACKED_SZ \ + ((uint16_t) 24) + +#define VL53L5_GTD_GBL_TGT_MEDIAN_RANGE_MM_SZ \ + ((uint16_t) 16) + +#define VL53L5_GTD_GBL_TGT_MIN_RANGE_DELTA_MM_SZ \ + ((uint16_t) 8) + +#define VL53L5_GTD_GBL_TGT_MAX_RANGE_DELTA_MM_SZ \ + ((uint16_t) 8) + +#define VL53L5_GTD_GBL_TGT_REFLECT_EST_PC_SZ \ + ((uint16_t) 8) + +#define VL53L5_GTD_GBL_TGT_STATUS_SZ \ + ((uint16_t) 8) + +#define VL53L5_GTD_GBL_TGT_X_CENTRE_SZ \ + ((uint16_t) 8) + +#define VL53L5_GTD_GBL_TGT_Y_CENTRE_SZ \ + ((uint16_t) 8) + +#define VL53L5_GTD_GBL_TGT_WIDTH_SZ \ + ((uint16_t) 8) + +#define VL53L5_GTD_GBL_TGT_HEIGHT_SZ \ + ((uint16_t) 8) + +#define VL53L5_GTD_GBL_TGT_NO_OF_ROIS_SZ \ + ((uint16_t) 8) + +#define VL53L5_VHV_RESULT_DATA_SZ \ + ((uint16_t) 8) + +#define VL53L5_VHV_SEARCH_DATA_SZ \ + ((uint16_t) 384) + +#define VL53L5_VSD_VHV_SEARCH_EVENTS_SZ \ + ((uint16_t) 256) + +#define VL53L5_VSD_VHV_SEARCH_VHV_VALUE_SZ \ + ((uint16_t) 64) + +#define VL53L5_VSD_VHV_SEARCH_SILICON_TEMP_DEGC_SZ \ + ((uint16_t) 64) + +#define VL53L5_CAL_REF_SPAD_SEARCH_META_DATA_SZ \ + ((uint16_t) 4) + +#define VL53L5_CAL_REF_SPAD_SEARCH_DATA_SZ \ + ((uint16_t) 384) + +#define VL53L5_CRSSD_CAL_REF_SPAD_SEARCH_TOTAL_RATE_MCPS_SZ \ + ((uint16_t) 256) + +#define VL53L5_CRSSD_CAL_REF_SPAD_SEARCH_COUNT_SZ \ + ((uint16_t) 64) + +#define VL53L5_CRSSD_CAL_REF_SPAD_SEARCH_APERTURE_SZ \ + ((uint16_t) 64) + +#define VL53L5_SHARPENER_TARGET_DATA_SZ \ + ((uint16_t) 544) + +#define VL53L5_STD_SHARPENER_GROUP_INDEX_SZ \ + ((uint16_t) 1 * VL53L5_MAX_TARGETS) + +#define VL53L5_STD_SHARPENER_CONFIDENCE_SZ \ + ((uint16_t) 1 * VL53L5_MAX_TARGETS) + +#define VL53L5_XTALK_MON_META_DATA_SZ \ + ((uint16_t) 4) + +#define VL53L5_XTALK_MON_ZONES_SZ \ + ((uint16_t) 32) + +#define VL53L5_XMZ_XTALK_MON_ZONE_X_OFF_SZ \ + ((uint16_t) 8) + +#define VL53L5_XMZ_XTALK_MON_ZONE_Y_OFF_SZ \ + ((uint16_t) 8) + +#define VL53L5_XMZ_XTALK_MON_ZONE_WIDTH_SZ \ + ((uint16_t) 8) + +#define VL53L5_XMZ_XTALK_MON_ZONE_HEIGHT_SZ \ + ((uint16_t) 8) + +#define VL53L5_XTALK_MON_DATA_SZ \ + ((uint16_t) 112) + +#define VL53L5_XMD_XTALK_MON_PEAK_RATE_KCPS_SPAD_SZ \ + ((uint16_t) 32) + +#define VL53L5_XMD_XTALK_MON_AMB_RATE_KCPS_SPAD_SZ \ + ((uint16_t) 32) + +#define VL53L5_XMD_XTALK_MON_PEAK_RATE_SIGMA_KCPS_PER_SPAD_SZ \ + ((uint16_t) 32) + +#define VL53L5_XMD_XTALK_MON_MPX_COUNT_SZ \ + ((uint16_t) 16) + +#define VL53L5_DYN_XTALK_PERSISTENT_DATA_SZ \ + ((uint16_t) 28) + +#define VL53L5_ZONE_THRESH_STATUS_ARRAY_SZ \ + ((uint16_t) 8) + +#define VL53L5_ZTSA_ZONE_THRESH_STATUS_BYTES_SZ \ + ((uint16_t) 8) + +#define VL53L5_NVM_LASER_SAFETY_NVM2_SZ \ + ((uint16_t) 4) + +#define VL53L5_OUTPUT_BH_CFG_SZ \ + ((uint16_t) 8) + +#define VL53L5_OUTPUT_BH_ENABLES_SZ \ + ((uint16_t) 16) + +#define VL53L5_OBE_OP_BH_ENABLES_SZ \ + ((uint16_t) 16) + +#define VL53L5_OUTPUT_BH_LIST_SZ \ + ((uint16_t) 512) + +#define VL53L5_OBL_OP_BH_LIST_SZ \ + ((uint16_t) 512) + +#define VL53L5_PATCH_HOOK_ENABLES_SZ \ + ((uint16_t) 16) + +#define VL53L5_PHE_PATCH_HOOK_ENABLES_SZ \ + ((uint16_t) 16) + +#define VL53L5_ACC_ZONE_GENERAL_CFG_SZ \ + ((uint16_t) 4) + +#define VL53L5_ACC_ZONE_ARRAYED_CFG_SZ \ + ((uint16_t) 68) + +#define VL53L5_AZAC_ACC_ZONE_0_IDS_SZ \ + ((uint16_t) 68) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_structs.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_structs.h new file mode 100644 index 000000000000..912c7cb7f526 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_structs.h @@ -0,0 +1,650 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __DCI_STRUCTS_H__ +#define __DCI_STRUCTS_H__ + +#include "vl53l5_results_config.h" +#include "packing_structs.h" +#include "dci_defs.h" +#include "dci_luts.h" +#include "dci_union_structs.h" +#include "packing_structs.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct dci_grp__map_version_t { + + uint16_t map__major; + + uint16_t map__minor; +}; + +struct dci_grp__fw_version_t { + + uint16_t fw__revision; + + uint16_t fw__build; + + uint16_t fw__minor; + + uint16_t fw__major; +}; + +struct dci_grp__cfg_info_t { + + union dci_union__config_presets_u cfg__presets; + + union dci_union__config_parms_u cfg__parms; + + uint32_t cfg__timing_ranging_rate_hz; + + uint32_t cfg__results__size_bytes; + + uint16_t cfg__version_major; + + uint16_t cfg__version_minor; +}; + +struct dci_grp__silicon_temperature_data_t { + + int8_t silicon_temp_degc__start; + + int8_t silicon_temp_degc__end; + int8_t silicon_temp__pad_0; + int8_t silicon_temp__pad_1; +}; + +struct dci_grp__fmt_traceability_t { + + union dci_union__st_fgc_0_u st_fgc_0_u; + + union dci_union__st_fgc_1_u st_fgc_1_u; + + union dci_union__st_fgc_2_u st_fgc_2_u; + + union dci_union__st_fgc_3_u st_fgc_3_u; + + union dci_union__identification_0_u identification_0_u; + + union dci_union__identification_1_u identification_1_u; +}; + +struct dci_grp__ews_traceability_t { + + union dci_union__test_traceability_u test_traceability_u; + + union dci_union__die_traceability_0_u die_traceability_0_u; + + union dci_union__die_traceability_1_u die_traceability_1_u; +}; + +struct dci_grp__zone_cfg_t { + + uint8_t zone__grid_cols; + + uint8_t zone__grid_rows; + + uint8_t zone__grid_x_ll; + + uint8_t zone__grid_y_ll; + + uint8_t zone__grid_x_pitch; + + uint8_t zone__grid_y_pitch; + uint8_t zone__pad_0; + uint8_t zone__pad_1; +}; + +struct dci_grp__device_mode_cfg_t { + + uint8_t distance_mode; + + uint8_t ranging_mode; + + uint8_t live_xtalk_mode; + + uint8_t integration__mode; + + uint8_t blanking__mode; + + uint8_t blanking__type; + uint8_t zone_cfg_select; + uint8_t device_mode__spare_0; +}; + +struct dci_grp__ranging_rate_cfg_t { + + uint32_t ranging_rate_hz; +}; + +struct dci_grp__integration_time_cfg_t { + + uint32_t integration_time_us; +}; + +struct dci_grp__factory_cal_cfg_t { + + int16_t factory_cal__target_distance_mm; + + uint16_t factory_cal__target_reflectance_pc; + + uint8_t factory_cal__no_of_samples; + uint8_t factory_cal__pad_0; + uint8_t factory_cal__pad_1; + uint8_t factory_cal__pad_2; +}; + +struct dci_grp__output_data_cfg_t { + + uint8_t output__max_targets_per_zone; + + uint8_t output__dummy_bytes; + uint8_t output__spare_0; + uint8_t output__spare_1; +}; + +struct dci_grp__interrupt_cfg_t { + + union dci_union__interrupt__config_u interrupt__config; + + uint8_t interrupt__cfg_mode; + + uint8_t interrupt__cfg_num_active_checkers; + + uint8_t interrupt__cfg_spare_0; + + uint8_t interrupt__cfg_spare_1; + + uint8_t interrupt__cfg_spare_2; + + uint8_t interrupt__cfg_spare_3; + + uint8_t interrupt__cfg_spare_4; +}; + +struct dci_grp__cal__ref_spad_info_t { + + uint8_t cal__ref_spad__offset; + + uint8_t cal__ref_spad__count; + + uint8_t cal__ref_spad__count_10x; + + uint8_t cal__ref_spad__count_100x; + + uint8_t cal__ref_spad__left_right_sel; + + uint8_t cal__ref_spad__status; + uint8_t cal__ref_spad_info__pad_0; + uint8_t cal__ref_spad_info__pad_1; +}; + +struct dci_grp__cal__temp_sensor_data_t { + + uint8_t cal__temp_sensor__degc; + + uint8_t cal__temp_sensor__value; + uint8_t cal__temp_sensor__pad_0; + uint8_t cal__temp_sensor__pad_1; +}; + +struct dci_grp__cal__optical_centre_data_t { + + uint8_t cal__optical_centre__x; + + uint8_t cal__optical_centre__y; + uint8_t cal__optical_centre__pad_0; + uint8_t cal__optical_centre__pad_1; +}; + +struct dci_grp__ui_rng_data_addr_t { + + uint16_t ui_rng_data_int_start_addr; + + uint16_t ui_rng_data_int_end_addr; + + uint16_t ui_rng_data_dummy_bytes; + + uint16_t ui_rng_data_offset_bytes; + + uint16_t ui_rng_data_size_bytes; + + uint16_t ui_device_info_start_addr; +}; + +struct vl53l5_range_meta_data_t { + + uint32_t time_stamp; + + uint8_t device_status; + + uint8_t transaction_id; + + uint8_t buffer_id; + + uint8_t stream_count; + + int8_t silicon_temp_degc; + uint8_t buf_meta_data__pad_0; + uint8_t buf_meta_data__pad_1; + uint8_t buf_meta_data__pad_2; +}; + +struct vl53l5_range_timing_data_t { + + uint32_t rng__integration_time_us; + + uint32_t rng__total_periods_elapsed; + + uint32_t rng__blanking_time_us; +}; + +struct vl53l5_range_common_data_t { + + uint16_t wrap_dmax_mm; + + uint8_t rng__no_of_zones; + + uint8_t rng__max_targets_per_zone; + + uint8_t rng__acc__no_of_zones; + + uint8_t rng__acc__zone_id; + + uint8_t rng__common__spare_0; + + uint8_t rng__common__spare_1; +}; + +struct vl53l5_range_per_zone_results_t { +#ifdef VL53L5_AMB_RATE_KCPS_PER_SPAD_ON + + uint32_t amb_rate_kcps_per_spad[VL53L5_MAX_ZONES]; +#endif + +#ifdef VL53L5_EFFECTIVE_SPAD_COUNT_ON + + uint32_t rng__effective_spad_count[VL53L5_MAX_ZONES]; +#endif + +#ifdef VL53L5_AMB_DMAX_MM_ON + + uint16_t amb_dmax_mm[VL53L5_MAX_ZONES]; +#endif + +#ifdef VL53L5_SILICON_TEMP_DEGC_START_ON + + int8_t silicon_temp_degc__start[VL53L5_MAX_ZONES]; +#endif + +#ifdef VL53L5_SILICON_TEMP_DEGC_END_ON + + int8_t silicon_temp_degc__end[VL53L5_MAX_ZONES]; +#endif + +#ifdef VL53L5_NO_OF_TARGETS_ON + + uint8_t rng__no_of_targets[VL53L5_MAX_ZONES]; +#endif + +#ifdef VL53L5_ZONE_ID_ON + + uint8_t rng__zone_id[VL53L5_MAX_ZONES]; +#endif + +#ifdef VL53L5_SEQUENCE_IDX_ON + + uint8_t rng__sequence_idx[VL53L5_MAX_ZONES]; +#endif + +#if !defined(VL53L5_AMB_RATE_KCPS_PER_SPAD_ON) && \ + !defined(VL53L5_EFFECTIVE_SPAD_COUNT_ON) && \ + !defined(VL53L5_AMB_DMAX_MM_ON) && \ + !defined(VL53L5_SILICON_TEMP_DEGC_START_ON) && \ + !defined(VL53L5_SILICON_TEMP_DEGC_END_ON) && \ + !defined(VL53L5_NO_OF_TARGETS_ON) && \ + !defined(VL53L5_ZONE_ID_ON) && \ + !defined(VL53L5_SEQUENCE_IDX_ON) + + uint32_t dummy; +#endif + +}; + +struct vl53l5_range_per_tgt_results_t { +#ifdef VL53L5_PEAK_RATE_KCPS_PER_SPAD_ON + + uint32_t peak_rate_kcps_per_spad[VL53L5_MAX_TARGETS]; +#endif + +#ifdef VL53L5_MEDIAN_PHASE_ON + + uint32_t median_phase[VL53L5_MAX_TARGETS]; +#endif + +#ifdef VL53L5_RATE_SIGMA_KCPS_PER_SPAD_ON + + uint32_t rate_sigma_kcps_per_spad[VL53L5_MAX_TARGETS]; +#endif + +#ifdef VL53L5_TARGET_ZSCORE_ON + + uint16_t target_zscore[VL53L5_MAX_TARGETS]; +#endif + +#ifdef VL53L5_RANGE_SIGMA_MM_ON + + uint16_t range_sigma_mm[VL53L5_MAX_TARGETS]; +#endif + +#ifdef VL53L5_MEDIAN_RANGE_MM_ON + + int16_t median_range_mm[VL53L5_MAX_TARGETS]; +#endif + +#ifdef VL53L5_START_RANGE_MM_ON + + int16_t start_range_mm[VL53L5_MAX_TARGETS]; +#endif + +#ifdef VL53L5_END_RANGE_MM_ON + + int16_t end_range_mm[VL53L5_MAX_TARGETS]; +#endif + +#ifdef VL53L5_MIN_RANGE_DELTA_MM_ON + + uint8_t min_range_delta_mm[VL53L5_MAX_TARGETS]; +#endif + +#ifdef VL53L5_MAX_RANGE_DELTA_MM_ON + + uint8_t max_range_delta_mm[VL53L5_MAX_TARGETS]; +#endif + +#ifdef VL53L5_TARGET_REFLECTANCE_EST_PC_ON + + uint8_t target_reflectance_est_pc[VL53L5_MAX_TARGETS]; +#endif + +#ifdef VL53L5_TARGET_STATUS_ON + + uint8_t target_status[VL53L5_MAX_TARGETS]; +#endif + +#if !defined(VL53L5_PEAK_RATE_KCPS_PER_SPAD_ON) && \ + !defined(VL53L5_MEDIAN_PHASE_ON) && \ + !defined(VL53L5_RATE_SIGMA_KCPS_PER_SPAD_ON) && \ + !defined(VL53L5_TARGET_ZSCORE_ON) && \ + !defined(VL53L5_RANGE_SIGMA_MM_ON) && \ + !defined(VL53L5_MEDIAN_RANGE_MM_ON) && \ + !defined(VL53L5_START_RANGE_MM_ON) && \ + !defined(VL53L5_END_RANGE_MM_ON) && \ + !defined(VL53L5_MIN_RANGE_DELTA_MM_ON) && \ + !defined(VL53L5_MAX_RANGE_DELTA_MM_ON) && \ + !defined(VL53L5_TARGET_REFLECTANCE_EST_PC_ON) && \ + !defined(VL53L5_TARGET_STATUS_ON) + + uint32_t dummy; +#endif + +}; + +struct vl53l5_ref_channel_data_t { + + uint32_t amb_rate_kcps_per_spad; + + uint32_t rng__effective_spad_count; + + uint16_t amb_dmax_mm; + + int8_t silicon_temp_degc__start; + + int8_t silicon_temp_degc__end; + + uint8_t rng__no_of_targets; + + uint8_t rng__zone_id; + + uint8_t rng__sequence_idx; + uint8_t ref_channel_data__pad_0; +}; + +struct vl53l5_ref_target_data_t { + + uint32_t peak_rate_kcps_per_spad; + + uint32_t median_phase; + + uint32_t rate_sigma_kcps_per_spad; + + uint16_t target_zscore; + + uint16_t range_sigma_mm; + + int16_t median_range_mm; + + int16_t start_range_mm; + + int16_t end_range_mm; + + uint8_t min_range_delta_mm; + + uint8_t max_range_delta_mm; + + uint8_t target_reflectance_est_pc; + + uint8_t target_status; + uint8_t ref_target_data__pad_0; + uint8_t ref_target_data__pad_1; +}; + +struct dci_grp__gbl_tgt_meta_data_t { + + uint16_t gbl_tgt__wrap_dmax_mm; + + uint16_t gbl_tgt__amb_dmax_mm; + + uint8_t gbl_tgt__no_of_targets; + + uint8_t gbl_tgt__max_targets; + uint8_t gbl_tgt__pad_0; + uint8_t gbl_tgt__pad_1; +}; + +struct dci_grp__gbl_tgt_data_t { + uint32_t gbl_tgt__peak_rate_kcps_per_spad[DCI_MAX_GLOBAL_TARGETS]; + uint32_t gbl_tgt__amb_rate_kcps_per_spad[DCI_MAX_GLOBAL_TARGETS]; + uint32_t gbl_tgt__peak_rate_mcps[DCI_MAX_GLOBAL_TARGETS]; + uint32_t gbl_tgt__amb_rate_mcps[DCI_MAX_GLOBAL_TARGETS]; + uint16_t gbl_tgt__median_range_mm[DCI_MAX_GLOBAL_TARGETS]; + uint8_t gbl_tgt__min_range_delta_mm[DCI_MAX_GLOBAL_TARGETS]; + uint8_t gbl_tgt__max_range_delta_mm[DCI_MAX_GLOBAL_TARGETS]; + + uint8_t gbl_tgt__reflect_est_pc[DCI_MAX_GLOBAL_TARGETS]; + + uint8_t gbl_tgt__status[DCI_MAX_GLOBAL_TARGETS]; + + uint8_t gbl_tgt__x_centre[DCI_MAX_GLOBAL_TARGETS]; + + uint8_t gbl_tgt__y_centre[DCI_MAX_GLOBAL_TARGETS]; + + uint8_t gbl_tgt__width[DCI_MAX_GLOBAL_TARGETS]; + + uint8_t gbl_tgt__height[DCI_MAX_GLOBAL_TARGETS]; + uint8_t gbl_tgt__no_of_rois[DCI_MAX_GLOBAL_TARGETS]; +}; + +struct vl53l5_sharpener_target_data_t { +#ifdef VL53L5_SHARPENER_GROUP_INDEX_ON + + uint8_t sharpener__group_index[VL53L5_MAX_TARGETS]; +#endif + +#ifdef VL53L5_SHARPENER_CONFIDENCE_ON + + uint8_t sharpener__confidence[VL53L5_MAX_TARGETS]; +#endif + +#if !defined(VL53L5_SHARPENER_GROUP_INDEX_ON) && \ + !defined(VL53L5_SHARPENER_CONFIDENCE_ON) + + uint32_t dummy; +#endif + +}; + +struct vl53l5_dyn_xtalk_persistent_data_t { + + uint32_t dyn_xt__dyn_xtalk_grid_maximum_kcps_per_spad; + + uint32_t dyn_xt__dyn_xtalk_grid_max_sigma_kcps_per_spad; + + uint32_t dyn_xt__new_max_xtalk_sigma_kcps_per_spad; + + int32_t dyn_xt__calibration_gain; + + int32_t temp_comp__temp_gain; + + uint16_t dyn_xt__nb_samples; + + uint16_t dyn_xt__desired_samples; + + uint8_t dyn_xt__accumulating; + + uint8_t dyn_xt__grid_ready; + + uint8_t dyn_xt__grid_ready_internal; + + uint8_t dyn_xt__persist_spare_2; +}; + +struct vl53l5_zone_thresh_status_array_t { +#ifdef VL53L5_ZONE_THRESH_STATUS_BYTES_ON + + uint8_t zone_thresh_status_bytes[DCI__ZONE_THRESH_STATUS_ARRAY_SIZE]; +#endif + +#if !defined(VL53L5_ZONE_THRESH_STATUS_BYTES_ON) + + uint32_t dummy; +#endif + +}; + +struct dci_grp__nvm_laser_safety_nvm2_t { + + uint32_t nvm_laser_safety__regdvdd1v1_sel_lp; +}; + +struct dci_grp__output_bh_cfg_t { + + uint32_t op_bh__op_packet_size_bytes; + + uint32_t op_bh__debug_data_start; +}; + +struct dci_grp__output_bh_enables_t { + + uint32_t op_bh__enables[DCI_DEF__MAX_OP_BH_ENABLE_SIZE]; +}; + +struct dci_grp__output_bh_list_t { + + uint32_t op_bh__list[DCI_DEF__MAX_OP_BH_LIST_SIZE]; +}; + +struct dci_grp__patch_hook_enables_t { + + uint32_t patch_hook_enables[DCI_DEF__PATCH_HOOK_ENABLE_WORD32]; +}; + +struct dci_grp__gbl_tgt_data_packed_t { + uint32_t gbl_tgt__peak_rate_kcps_per_spad[DCI_MAX_GLOBAL_TARGETS]; + uint32_t gbl_tgt__amb_rate_kcps_per_spad[DCI_MAX_GLOBAL_TARGETS]; + struct bit_packed_24 gbl_tgt__peak_rate_mcps[ + DCI_MAX_GLOBAL_TARGETS / 4]; + struct bit_packed_24 gbl_tgt__amb_rate_mcps[DCI_MAX_GLOBAL_TARGETS / 4]; + uint16_t gbl_tgt__median_range_mm[DCI_MAX_GLOBAL_TARGETS]; + uint8_t gbl_tgt__min_range_delta_mm[DCI_MAX_GLOBAL_TARGETS]; + uint8_t gbl_tgt__max_range_delta_mm[DCI_MAX_GLOBAL_TARGETS]; + + uint8_t gbl_tgt__reflect_est_pc[DCI_MAX_GLOBAL_TARGETS]; + + uint8_t gbl_tgt__status[DCI_MAX_GLOBAL_TARGETS]; + + uint8_t gbl_tgt__x_centre[DCI_MAX_GLOBAL_TARGETS]; + + uint8_t gbl_tgt__y_centre[DCI_MAX_GLOBAL_TARGETS]; + + uint8_t gbl_tgt__width[DCI_MAX_GLOBAL_TARGETS]; + + uint8_t gbl_tgt__height[DCI_MAX_GLOBAL_TARGETS]; + uint8_t gbl_tgt__no_of_rois[DCI_MAX_GLOBAL_TARGETS]; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_ui_memory_defs.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_ui_memory_defs.h new file mode 100644 index 000000000000..ada30e0b4e43 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_ui_memory_defs.h @@ -0,0 +1,103 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __DCI_UI_MEMORY_DEFS_H__ +#define __DCI_UI_MEMORY_DEFS_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define DCI_UI__DEVICE_INFO__START_IDX \ + ((uint32_t) 0x810000) + +#define DCI_UI__DEVICE_INFO__SIZE_BYTES \ + ((uint32_t) 4U) + +#define DCI_UI__RANGE_DATA__START_IDX \ + ((uint32_t) 0x810008) + +#define DCI_UI__COMMAND_WRITE__END_IDX \ + ((uint32_t) 0x812FFB - 0x04) + +#define DCI_UI__COMMAND_INFO__START_IDX \ + ((uint32_t) DCI_UI__COMMAND_WRITE__END_IDX + 1) + +#define DCI_UI__COMMAND_INFO__SIZE_BYTES \ + ((uint32_t) 4U) + +#define DCI_UI__COMMAND_FOOTER__START_IDX \ + ((uint32_t) 0x812FF8 - 0x04) + +#define DCI_UI__COMMAND_FOOTER__SIZE_BYTES \ + ((uint32_t) 4U) + +#define DCI_UI__COMMAND_RETURN__START_IDX \ + ((uint32_t) 0x812C00) + +#define DCI_UI__FIRMWARE_CHECKSUM_IDX \ + ((uint32_t) 0x812FFC) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_ui_size.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_ui_size.h new file mode 100644 index 000000000000..1c20851a1b71 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_ui_size.h @@ -0,0 +1,100 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __DCI_UI_SIZE_H__ +#define __DCI_UI_SIZE_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_DCI_UI_CMD_FOOTER_SZ \ + ((uint16_t) 4) + +#define VL53L5_DCI_UI_CMD_INFO_SZ \ + ((uint16_t) 4) + +#define VL53L5_DCI_UI_DEV_INFO_SZ \ + ((uint16_t) 4) + +#define VL53L5_DCI_UI_RNG_DATA_HEADER_SZ \ + ((uint16_t) 4) + +#define VL53L5_DCI_UI_RNG_DATA_FOOTER_SZ \ + ((uint16_t) 4) + +#define VL53L5_DCI_UI_PACKED_DATA_BH_SZ \ + ((uint16_t) 4) + +#define VL53L5_DCI_UI_HOST_PACKED_DATA_BH_SZ \ + ((uint16_t) 8) + +#define VL53L5_DCI_UI_FW_BREAKPOINTS_SZ \ + ((uint16_t) 4) + +#define VL53L5_DCI_UI_FW_CHECKSUM_SZ \ + ((uint16_t) 4) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_ui_structs.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_ui_structs.h new file mode 100644 index 000000000000..71e77f080605 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_ui_structs.h @@ -0,0 +1,126 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __DCI_UI_STRUCTS_H__ +#define __DCI_UI_STRUCTS_H__ + +#include "dci_defs.h" +#include "dci_luts.h" +#include "dci_ui_union_structs.h" +#include "packing_structs.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct dci_ui__cmd_footer_t { + union dci_union__cmd_footer_u cmd_footer; +}; + +struct dci_ui__cmd_info_t { + union dci_union__cmd_info_u cmd_info; +}; + +struct dci_ui__dev_info_t { + + union dci_union__go2_status_0_go1_u dev_info__go2_status_0; + + union dci_union__go2_status_1_go1_u dev_info__go2_status_1; + + uint8_t dev_info__device_status; + + uint8_t dev_info__ui_stream_count; +}; + +struct dci_ui__rng_data__header_t { + union dci_union__rng_data__header_u rng_data_header; +}; + +struct dci_ui__rng_data__footer_t { + union dci_union__rng_data__footer_u rng_data_footer; +}; + +struct dci_ui__packed_data__bh_t { + + union dci_union__block_header_u packed_data__bh; +}; + +struct dci_ui__host_packed_data__bh_t { + + union dci_union__block_header_u packed_data__bh; + + uint32_t block_sz_bytes; +}; + +struct dci_ui__fw_breakpoints_t { + + uint32_t fw_breakpoints_status; +}; + +struct dci_ui__fw_checksum_t { + + uint32_t fw_checksum; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_ui_union_structs.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_ui_union_structs.h new file mode 100644 index 000000000000..f617f85aeb38 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_ui_union_structs.h @@ -0,0 +1,164 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __DCI_UI_UNION_STRUCTS_H__ +#define __DCI_UI_UNION_STRUCTS_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +union dci_union__go2_status_0_go1_u { + uint8_t bytes; + struct { + + uint8_t mcu__boot_complete_go1 : 1; + + uint8_t mcu__analog_checks_ok_go1 : 1; + + uint8_t mcu__threshold_triggered_g01 : 1; + uint8_t mcu__error_flag_go1 : 1; + uint8_t mcu__ui_range_data_present_go1 : 1; + uint8_t mcu__ui_new_range_data_avail_go1 : 1; + uint8_t mcu__ui_update_blocked_go1 : 1; + + uint8_t mcu__hw_trap_flag_go1 : 1; + }; +}; + +union dci_union__go2_status_1_go1_u { + uint8_t bytes; + struct { + + uint8_t mcu__avdd_reg_ok_go1 : 1; + + uint8_t mcu__pll_lock_ok_go1 : 1; + + uint8_t mcu__ls_watchdog_pass_go1 : 1; + + uint8_t mcu__warning_flag_go1 : 1; + + uint8_t mcu__cp_collapse_flag_go1 : 1; + uint8_t mcu__spare0 : 1; + uint8_t mcu__initial_ram_boot_complete : 1; + uint8_t mcu__spare1 : 1; + }; +}; + +union dci_union__block_header_u { + uint32_t bytes; + struct { + + uint32_t p_type : 4; + + uint32_t b_size__p_rep : 12; + + uint32_t b_idx__p_idx : 16; + }; +}; + +union dci_union__cmd_footer_u { + uint32_t bytes; + struct { + + uint32_t cmd__ip_data_size : 16; + + uint32_t cmd__ip_command_id : 8; + + uint32_t cmd__ip_transaction_id : 8; + }; +}; + +union dci_union__cmd_info_u { + uint32_t bytes; + struct { + + uint32_t cmd__rtn_data_size : 16; + + uint32_t cmd__rtn_command_status : 8; + + uint32_t cmd__rtn_transaction_id : 8; + }; +}; + +union dci_union__rng_data__header_u { + uint32_t bytes; + struct { + uint32_t rng_data__header__id : 16; + uint32_t rng_data__header__reserved_0 : 8; + uint32_t rng_data__header__reserved_1 : 8; + }; +}; + +union dci_union__rng_data__footer_u { + uint32_t bytes; + struct { + uint32_t rng_data__footer__id : 16; + uint32_t rng_data__footer__reserved_0 : 8; + uint32_t rng_data__footer__reserved_1 : 8; + }; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_union_structs.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_union_structs.h new file mode 100644 index 000000000000..bc4bcd0cf6f3 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_union_structs.h @@ -0,0 +1,326 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __DCI_UNION_STRUCTS_H__ +#define __DCI_UNION_STRUCTS_H__ + +#include "dci_defs.h" +#include "dci_version_defs.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +union dci_union__config_presets_u { + uint32_t bytes; + struct { + + uint32_t config__preset__0 : 4; + + uint32_t config__preset__1 : 4; + + uint32_t config__preset__2 : 4; + + uint32_t config__preset__3 : 4; + + uint32_t config__preset__4 : 4; + + uint32_t config__preset__5 : 4; + + uint32_t config__preset__6 : 4; + + uint32_t config__preset__7 : 4; + }; +}; + +union dci_union__config_parms_u { + uint32_t bytes; + struct { + + uint32_t config__parms__0 : 8; + + uint32_t config__parms__1 : 4; + + uint32_t config__parms__2 : 4; + + uint32_t config__parms__3 : 4; + + uint32_t config__parms__4 : 4; + + uint32_t config__parms__5 : 2; + + uint32_t config__parms__6 : 2; + + uint32_t config__parms__7 : 2; + + uint32_t config__parms__8 : 1; + + uint32_t config__parms__9 : 1; + }; +}; + +union dci_union__interrupt__config_u { + uint8_t bytes; + struct { + + uint8_t interrupt__config__pos_edge : 1; + uint8_t interrupt__config__pad_0 : 1; + uint8_t interrupt__config__pad_1 : 1; + uint8_t interrupt__config__pad_2 : 1; + uint8_t interrupt__config__pad_3 : 1; + uint8_t interrupt__config__pad_4 : 1; + uint8_t interrupt__config__pad_5 : 1; + uint8_t interrupt__config__pad_6 : 1; + }; +}; + +union dci_union__vhv__config_u { + uint8_t bytes; + struct { + + uint8_t stage_en : 1; + + uint8_t manual_override : 1; + + uint8_t init_mode : 1; + + uint8_t vcsel_enable : 1; + + uint8_t debug_en : 1; + + uint8_t search_source : 1; + + uint8_t search_reset : 1; + + uint8_t search_control : 1; + }; +}; + +union dci_union__ref_msr__config_u { + uint8_t bytes; + struct { + + uint8_t stage_en : 1; + + uint8_t laser_safety_check_en : 1; + uint8_t ref_msr__config__pad_0 : 1; + uint8_t ref_msr__config__pad_1 : 1; + uint8_t ref_msr__config__pad_2 : 1; + uint8_t ref_msr__config__pad_3 : 1; + uint8_t ref_msr__config__pad_4 : 1; + uint8_t ref_msr__config__pad_5 : 1; + }; +}; + +union dci_union__vhv_ref_scheduling_ctrl_u { + uint8_t bytes; + struct { + + uint8_t vhv_repeat_en : 1; + + uint8_t ref_msr_repeat_en : 1; + uint8_t vhv_ref_scheduling_pad_0 : 1; + uint8_t vhv_ref_scheduling_pad_1 : 1; + uint8_t vhv_ref_scheduling_pad_2 : 1; + uint8_t vhv_ref_scheduling_pad_3 : 1; + uint8_t vhv_ref_scheduling_pad_4 : 1; + uint8_t vhv_ref_scheduling_pad_5 : 1; + }; +}; + +union dci_union__cal__ref_spad__debug_cfg_u { + uint8_t bytes; + struct { + + uint8_t debug_en : 1; + uint8_t cal__ref_spad__debug_cfg__pad_0 : 1; + uint8_t cal__ref_spad__debug_cfg__pad_1 : 1; + uint8_t cal__ref_spad__debug_cfg__pad_2 : 1; + uint8_t cal__ref_spad__debug_cfg__pad_3 : 1; + uint8_t cal__ref_spad__debug_cfg__pad_4 : 1; + uint8_t cal__ref_spad__debug_cfg__pad_5 : 1; + uint8_t cal__ref_spad__debug_cfg__pad_6 : 1; + }; +}; + +union dci_union_cp_collapse_ctrl_u { + uint8_t bytes; + struct { + uint8_t cp_collapse_ctrl : 1; + uint8_t cp_collapse_ctrl_pad_0 : 1; + uint8_t cp_collapse_ctrl_pad_1 : 1; + uint8_t cp_collapse_ctrl_pad_2 : 1; + uint8_t cp_collapse_ctrl_pad_3 : 1; + uint8_t cp_collapse_ctrl_pad_4 : 1; + uint8_t cp_collapse_ctrl_pad_5 : 1; + uint8_t cp_collapse_ctrl_pad_6 : 1; + }; +}; + +union dci_union__st_fgc_0_u { + uint32_t bytes; + struct { + uint32_t fgc_4__6_3 : 4; + uint32_t fgc_3 : 7; + uint32_t fgc_2 : 7; + uint32_t fgc_1 : 7; + uint32_t fgc_0 : 7; + }; +}; + +union dci_union__st_fgc_1_u { + uint32_t bytes; + struct { + uint32_t fgc_9__6 : 1; + uint32_t fgc_8 : 7; + uint32_t fgc_7 : 7; + uint32_t fgc_6 : 7; + uint32_t fgc_5 : 7; + uint32_t fgc_4__2_0 : 3; + }; +}; + +union dci_union__st_fgc_2_u { + uint32_t bytes; + struct { + uint32_t fgc_13__6_2 : 5; + uint32_t fgc_12 : 7; + uint32_t fgc_11 : 7; + uint32_t fgc_10 : 7; + uint32_t fgc_9__5_0 : 6; + }; +}; + +union dci_union__st_fgc_3_u { + uint32_t bytes; + struct { + uint32_t word32_250__pad_0 : 2; + uint32_t fgc_17 : 7; + uint32_t fgc_16 : 7; + uint32_t fgc_15 : 7; + uint32_t fgc_14 : 7; + uint32_t fgc_13__1_0 : 2; + }; +}; + +union dci_union__identification_0_u { + uint32_t bytes; + struct { + uint32_t module_date_phase : 3; + uint32_t day : 5; + uint32_t month : 4; + uint32_t year : 4; + uint32_t map_minor : 5; + uint32_t map_major : 3; + uint32_t test_prog_fmt_minor : 5; + uint32_t test_mrog_fmt_major : 3; + }; +}; + +union dci_union__identification_1_u { + uint32_t bytes; + struct { + uint32_t code_site_id_fmt : 8; + uint32_t code_tester_id_fmt : 8; + uint32_t time : 16; + }; +}; + +union dci_union__test_traceability_u { + uint32_t bytes; + struct { + uint32_t word32_253__pad_0 : 8; + uint32_t tester_id_ews : 8; + uint32_t probe_card_minor : 4; + uint32_t probe_card_major : 4; + uint32_t test_prog_ews_minor : 5; + uint32_t test_prog_ews_major : 3; + }; +}; + +union dci_union__die_traceability_0_u { + uint32_t bytes; + struct { + uint32_t lot1_5_4 : 2; + uint32_t lot2 : 6; + uint32_t lot3 : 6; + uint32_t lot4 : 6; + uint32_t lot5 : 6; + uint32_t lot6 : 6; + }; +}; + +union dci_union__die_traceability_1_u { + uint32_t bytes; + struct { + uint32_t ycoord : 8; + uint32_t xcoord : 8; + uint32_t wafer : 5; + uint32_t word32_255__pad_0 : 1; + uint32_t lot0 : 6; + uint32_t lot1_3_0 : 4; + }; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_version_defs.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_version_defs.h new file mode 100644 index 000000000000..120a194540ff --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/dci_version_defs.h @@ -0,0 +1,78 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __DCI_VERSION_DEFS_H__ +#define __DCI_VERSION_DEFS_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAP_VERSION_MINOR \ + ((uint16_t) 5U) +#define MAP_VERSION_MAJOR \ + ((uint16_t) 11U) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/dyn_xtalk_defs.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/dyn_xtalk_defs.h new file mode 100644 index 000000000000..a973490d1b00 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/dyn_xtalk_defs.h @@ -0,0 +1,97 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __DYN_XTALK_DEFS_H__ +#define __DYN_XTALK_DEFS_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define TEMP_COMP__MAX_NB_LUT_ENTRIES \ + ((uint8_t) 16U) + +#define DYN_XTALK__MAX_NB_LEAKAGE_MONITORS \ + ((uint8_t) 4U) + +#define DYN_XTALK__DEGRADATION_RATE \ + ((uint32_t) 205U) + +#define DYN_XTALK__INTERP_PRECISION_REDUCTION \ + ((int8_t) 7) + +#define DYN_XTALK__TEMP_GAIN_FRACTIONAL_BITS \ + ((uint8_t) 7U) + +#define DYN_XTALK__RATE_FRACTIONAL_BITS \ + ((uint8_t) 11U) + +#define DYN_XTALK__VARIANCE_FRACTIONAL_BITS \ + ((uint8_t) 22U) + +#define DYN_XTALK__PRE_SQRT_FRACTIONAL_BITS \ + ((uint8_t) 12U) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/dyn_xtalk_luts.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/dyn_xtalk_luts.h new file mode 100644 index 000000000000..f4db0f814519 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/dyn_xtalk_luts.h @@ -0,0 +1,169 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __DYN_XTALK_LUTS_H__ +#define __DYN_XTALK_LUTS_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define DYN_XTALK_NO_GRID_UPDATE \ + ((uint8_t) 0U) +#define DYN_XTALK_GRID_UPDATE \ + ((uint8_t) 1U) + +#define DYN_XTALK_KEEP_GRID \ + ((uint8_t) 0U) +#define DYN_XTALK_DISCARD_GRID \ + ((uint8_t) 1U) +#define DYN_XTALK_FORCE_MAX_SAMPLES_KEEP_GRID \ + ((uint8_t) 2U) + +#define DYN_XTALK_DISABLE_XTALK_GRID_UPDATE \ + ((uint8_t) 0U) +#define DYN_XTALK_ENABLE_XTALK_GRID_UPDATE \ + ((uint8_t) 1U) + +#define DYN_XTALK_CONTINUE \ + ((uint8_t) 0U) +#define DYN_XTALK_RESET \ + ((uint8_t) 1U) + +#define DYN_XTALK_FALSE \ + ((uint8_t) 0U) +#define DYN_XTALK_TRUE \ + ((uint8_t) 1U) + +#define TEMP_COMP_MODE_NONE \ + ((uint8_t) 0U) +#define TEMP_COMP_MODE_DISABLED \ + ((uint8_t) 1U) +#define TEMP_COMP_MODE_ENABLED \ + ((uint8_t) 2U) + +#define DYN_XTALK_ERROR_WARN_OVERFLOW \ + ((uint8_t) 1U) +#define DYN_XTALK_ERROR_WARN_UNSUPPORTED_MODE \ + ((uint8_t) 2U) +#define DYN_XTALK_ERROR_WARN_DEGRADED_PERFORMANCE \ + ((uint8_t) 3U) + +#define DYN_XTALK_STAGE_RESET \ + ((uint8_t) 0U) +#define DYN_XTALK_STAGE_ACCUMULATOR_RESET \ + ((uint8_t) 1U) +#define DYN_XTALK_STAGE_TEMPERATURE_COMPENSATION_GAIN \ + ((uint8_t) 2U) +#define DYN_XTALK_STAGE_LINEAR_INTERPOLATION \ + ((uint8_t) 3U) +#define DYN_XTALK_STAGE_LEAKAGE_GATING \ + ((uint8_t) 4U) +#define DYN_XTALK_STAGE_LEAKAGE_COMPENSATION \ + ((uint8_t) 5U) +#define DYN_XTALK_STAGE_GRID_INDEX \ + ((uint8_t) 6U) +#define DYN_XTALK_STAGE_ACCUMULATE_MONITORS \ + ((uint8_t) 7U) +#define DYN_XTALK_STAGE_INVERSE_TEMPERATURE_COMPENSATION \ + ((uint8_t) 8U) +#define DYN_XTALK_STAGE_FORWARD_TEMPERATURE_COMPENSATION \ + ((uint8_t) 9U) +#define DYN_XTALK_STAGE_NEW_VARIANCE_GRID \ + ((uint8_t) 10U) +#define DYN_XTALK_STAGE_VARIANCE_GRID_POINT \ + ((uint8_t) 11U) +#define DYN_XTALK_STAGE_VARIANCE_TO_SIGMA \ + ((uint8_t) 12U) +#define DYN_XTALK_STAGE_DESIRED_SAMPLES_CHECK \ + ((uint8_t) 13U) +#define DYN_XTALK_STAGE_REACHED_MAX_SAMPLES_OR_FRAMES \ + ((uint8_t) 14U) +#define DYN_XTALK_STAGE_FINISHED_ACCUMULATING \ + ((uint8_t) 15U) +#define DYN_XTALK_STAGE_TX_SUB_FRAME_GRID_TO_OTF \ + ((uint8_t) 16U) +#define DYN_XTALK_STAGE_TX_OTF_GRID_TO_OUTPUT_GRID \ + ((uint8_t) 17U) + +#define DYN_XTALK_RESET_MODE__START_WITH_PREVIOUS_GRID \ + ((uint8_t) 0U) + +#define DYN_XTALK_RESET_MODE__START_WITH_CAL_GRID \ + ((uint8_t) 1U) + +#define DYN_XTALK_RESET_MODE__START_WITH_XTALK_MON \ + ((uint8_t) 2U) + +#define DYN_XTALK_MODE__NONE \ + ((uint8_t) 0U) + +#define DYN_XTALK_MODE__DISABLED \ + ((uint8_t) 1U) + +#define DYN_XTALK_MODE__ENABLED \ + ((uint8_t) 2U) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/dyn_xtalk_size.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/dyn_xtalk_size.h new file mode 100644 index 000000000000..d0b37755b66e --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/dyn_xtalk_size.h @@ -0,0 +1,133 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __DYN_XTALK_SIZE_H__ +#define __DYN_XTALK_SIZE_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_DYN_XTALK_GRP_ARRAYED_INTERNAL_SZ \ + ((uint16_t) 1152) + +#define VL53L5_DXGAI_DYN_XT_GRID_NEW_XTALK_ACCUMULATED_VARIANCE_SZ \ + ((uint16_t) 512) + +#define VL53L5_DXGAI_DYN_XT_NEW_XTALK_MON_VARIANCES_AT_CURR_TEMP_KCPS_PER_SPAD_SZ \ + ((uint16_t) 64) + +#define VL53L5_DXGAI_DYN_XT_NEW_XTALK_MON_VARIANCES_AT_CAL_TEMP_KCPS_PER_SPAD_SZ \ + ((uint16_t) 64) + +#define VL53L5_DXGAI_DYN_XT_GRID_NEW_XTALK_RATE_KCPS_PER_SPAD_SZ \ + ((uint16_t) 256) + +#define VL53L5_DXGAI_DYN_XT_GRID_NEW_XTALK_SIGMA_KCPS_PER_SPAD_SZ \ + ((uint16_t) 256) + +#define VL53L5_DYN_XTALK_GRP_GENERAL_CFG_SZ \ + ((uint16_t) 16) + +#define VL53L5_DYN_XTALK_GRP_ARRAYED_CFG_SZ \ + ((uint16_t) 32) + +#define VL53L5_DXGAC_DYN_XT_LEAKAGE_THRESHOLD_KCPS_PER_SPAD_SZ \ + ((uint16_t) 32) + +#define VL53L5_DYN_XTALK_GRP_PERSISTENT_DATA_SZ \ + ((uint16_t) 24) + +#define VL53L5_DYN_XTALK_GRP_ARRAYED_PERSISTENT_DATA_SZ \ + ((uint16_t) 168) + +#define VL53L5_DXGAPD_DYN_XT_ACCUMULATED_XMON_AT_CAL_TEMP_SZ \ + ((uint16_t) 64) + +#define VL53L5_DXGAPD_DYN_XT_ACCUMULATED_XMON_VARIANCES_AT_CAL_TEMP_SZ \ + ((uint16_t) 64) + +#define VL53L5_DXGAPD_DYN_XT_COMPENSATED_LEAKAGE_MONITORS_SZ \ + ((uint16_t) 32) + +#define VL53L5_DXGAPD_DYN_XT_UPDATE_XTALK_SZ \ + ((uint16_t) 8) + +#define VL53L5_DYN_XTALK_GRP_DYNAMIC_CFG_SZ \ + ((uint16_t) 4) + +#define VL53L5_TEMP_COMP_GRP_GENERAL_CFG_SZ \ + ((uint16_t) 8) + +#define VL53L5_TEMP_COMP_GRP_ARRAYED_CFG_SZ \ + ((uint16_t) 128) + +#define VL53L5_TCGAC_TEMP_COMP_TEMPERATURE_SZ \ + ((uint16_t) 64) + +#define VL53L5_TCGAC_TEMP_COMP_GAIN_SZ \ + ((uint16_t) 64) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/dyn_xtalk_structs.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/dyn_xtalk_structs.h new file mode 100644 index 000000000000..cab5a661af7a --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/dyn_xtalk_structs.h @@ -0,0 +1,94 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __DYN_XTALK_STRUCTS_H__ +#define __DYN_XTALK_STRUCTS_H__ + +#include "dyn_xtalk_defs.h" +#include "dci_defs.h" +#include "cal_defs.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct dyn_xtalk_grp__arrayed_internal_t { + + uint64_t dyn_xt__grid_new_xtalk__accumulated_variance[ + CAL_DEF__MAX_COLS_X_MAX_ROWS]; + + uint64_t dyn_xt__new_xtalk_mon_variances_at_curr_temp_kcps_per_spad[ + DCI_XTALK_MON_MAX_ZONES]; + + uint64_t dyn_xt__new_xtalk_mon_variances_at_cal_temp_kcps_per_spad[ + DCI_XTALK_MON_MAX_ZONES]; + + uint32_t dyn_xt__grid_new_xtalk__rate_kcps_per_spad[ + CAL_DEF__MAX_COLS_X_MAX_ROWS]; + + uint32_t dyn_xt__grid_new_xtalk__sigma_kcps_per_spad[ + CAL_DEF__MAX_COLS_X_MAX_ROWS]; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/ic_checkers_defs.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/ic_checkers_defs.h new file mode 100644 index 000000000000..38e37c21ae44 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/ic_checkers_defs.h @@ -0,0 +1,76 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __IC_CHECKERS_DEFS_H__ +#define __IC_CHECKERS_DEFS_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define IC_DEF__MAX_TGT_STATUS_LIST \ + ((uint32_t) 8U) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/ic_checkers_luts.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/ic_checkers_luts.h new file mode 100644 index 000000000000..22aba5e4e8c9 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/ic_checkers_luts.h @@ -0,0 +1,134 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __IC_CHECKERS_LUTS_H__ +#define __IC_CHECKERS_LUTS_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define CHECKER_TYPE__IN_WINDOW \ + ((uint8_t) 0U) +#define CHECKER_TYPE__OUT_OF_WINDOW \ + ((uint8_t) 1U) +#define CHECKER_TYPE__LESS_THAN_EQUAL_MIN_CHECKER \ + ((uint8_t) 2U) +#define CHECKER_TYPE__GREATER_THAN_MAX_CHECKER \ + ((uint8_t) 3U) +#define CHECKER_TYPE__EQUAL_MIN_CHECKER \ + ((uint8_t) 4U) +#define CHECKER_TYPE__NOT_EQUAL_MIN_CHECKER \ + ((uint8_t) 5U) + +#define CHECKER_PARAM_TYPE__INVALID_CHECKER \ + ((uint8_t) 0U) + +#define CHECKER_PARAM_TYPE__MEDIAN_RANGE_MM \ + ((uint8_t) 1U) + +#define CHECKER_PARAM_TYPE__PEAK_RATE_KCPS_PER_SPAD \ + ((uint8_t) 2U) + +#define CHECKER_PARAM_TYPE__RATE_SIGMA_KCPS_PER_SPAD \ + ((uint8_t) 3U) + +#define CHECKER_PARAM_TYPE__RANGE_SIGMA_MM \ + ((uint8_t) 4U) + +#define CHECKER_PARAM_TYPE__TARGET_REFLECTANCE_EST \ + ((uint8_t) 5U) + +#define CHECKER_PARAM_TYPE__MIN_RANGE \ + ((uint8_t) 6U) + +#define CHECKER_PARAM_TYPE__MAX_RANGE \ + ((uint8_t) 7U) + +#define CHECKER_PARAM_TYPE__AMB_RATE_KCPS_PER_SPAD \ + ((uint8_t) 8U) + +#define CHECKER_PARAM_TYPE__NUM_OF_TARGETS \ + ((uint8_t) 9U) + +#define CHECKER_PARAM_TYPE__DMAX_MM \ + ((uint8_t) 10U) + +#define CHECKER_PARAM_TYPE__EWOKMZ_TEMP \ + ((uint8_t) 11U) + +#define CHECKER_PARAM_TYPE__EWOKMZ_TARGET_STATUS \ + ((uint8_t) 12U) + +#define CHECKER_PARAM_TYPE__EFFECTIVE_SPAD_COUNT \ + ((uint8_t) 13U) + +#define CHECKER_PARAM_TYPE__DELTA_START_END_TEMP \ + ((uint8_t) 14U) + +#define CHECKER_PARAM_TYPE__DELTA_START_END_PHASE \ + ((uint8_t) 15U) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/ic_checkers_size.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/ic_checkers_size.h new file mode 100644 index 000000000000..ce44038d53cf --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/ic_checkers_size.h @@ -0,0 +1,94 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __IC_CHECKERS_SIZE_H__ +#define __IC_CHECKERS_SIZE_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_IC_GRP_CHECKER_CFG_SZ \ + ((uint16_t) 12) + +#define VL53L5_IC_GRP_VALID_TGT_STATUS_SZ \ + ((uint16_t) 8) + +#define VL53L5_IGVTS_VAILD_TARGET_STATUS_SZ \ + ((uint16_t) 8) + +#define VL53L5_IC_GRP_INT_STATUS_SZ \ + ((uint16_t) 4) + +#define VL53L5_IC_GRP_WORKSPACE_SZ \ + ((uint16_t) 16) + +#define VL53L5_IC_GRP_WORKSPACE_ARRAY_SZ \ + ((uint16_t) 64) + +#define VL53L5_IGWA_ZONE_INT_STATUS_SZ \ + ((uint16_t) 64) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/ic_checkers_structs.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/ic_checkers_structs.h new file mode 100644 index 000000000000..06dc46c6daa1 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/ic_checkers_structs.h @@ -0,0 +1,116 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __IC_CHECKERS_STRUCTS_H__ +#define __IC_CHECKERS_STRUCTS_H__ + +#include "ic_checkers_defs.h" +#include "ic_checkers_luts.h" +#include "ic_checkers_union_structs.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct ic_grp__checker_cfg_t { + + int32_t checker_param_low_thresh; + + int32_t checker_param_high_thresh; + + uint8_t checker_param_type; + + uint8_t checker_type; + + uint8_t zone_num; + + union ic_checkers_union__optional_checker_type_u optional_checker_type; +}; + +struct ic_grp__valid_tgt_status_t { + + uint8_t vaild_target_status[IC_DEF__MAX_TGT_STATUS_LIST]; +}; + +struct ic_grp__workspace_t { + int32_t device_value; + uint8_t i; + uint8_t current_checker_num; + uint8_t last_zone; + uint8_t array_index; + uint8_t zone_number; + uint8_t target_status_counter; + uint8_t zone_target_detected; + uint8_t target_counter; + uint8_t AndOr; + uint8_t pad_0; + uint8_t pad_1; + uint8_t pad_2; +}; + +struct ic_grp__workspace_array_t { + uint8_t zone_int_status[64]; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/ic_checkers_union_structs.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/ic_checkers_union_structs.h new file mode 100644 index 000000000000..52a1f5defc58 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/ic_checkers_union_structs.h @@ -0,0 +1,90 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __IC_CHECKERS_UNION_STRUCTS_H__ +#define __IC_CHECKERS_UNION_STRUCTS_H__ + +#include "ic_checkers_defs.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +union ic_checkers_union__optional_checker_type_u { + uint8_t bytes; + struct { + + uint8_t check_no_targets : 1; + + uint8_t and_or : 1; + uint8_t pad_0 : 1; + uint8_t pad_1 : 1; + uint8_t pad_2 : 1; + uint8_t pad_3 : 1; + uint8_t pad_4 : 1; + uint8_t pad_5 : 1; + }; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/packing_structs.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/packing_structs.h new file mode 100644 index 000000000000..779f13cd49be --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/packing_structs.h @@ -0,0 +1,82 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __PACKING_STRUCTS_H__ +#define __PACKING_STRUCTS_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct bit_packed_24 { + + uint32_t bit_pack_dword_0; + + uint32_t bit_pack_dword_1; + + uint32_t bit_pack_dword_2; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/padding_size.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/padding_size.h new file mode 100644 index 000000000000..00ca1c0aafd7 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/padding_size.h @@ -0,0 +1,76 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __PADDING_SIZE_H__ +#define __PADDING_SIZE_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_FW_GRP_PADDING_32_SZ \ + ((uint16_t) 4) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/padding_structs.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/padding_structs.h new file mode 100644 index 000000000000..1fd3593c2e65 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/padding_structs.h @@ -0,0 +1,73 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __PADDING_STRUCTS_H__ +#define __PADDING_STRUCTS_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/page_map_defs.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/page_map_defs.h new file mode 100644 index 000000000000..32c9d60ace18 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/page_map_defs.h @@ -0,0 +1,86 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __PAGE_MAP_DEFS_H__ +#define __PAGE_MAP_DEFS_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define PAGE_DEF__PAGE_VL53L5_DEV \ + ((uint16_t) 0U) +#define PAGE_DEF__PAGE_PATCH_0_DEV \ + ((uint16_t) 1U) +#define PAGE_DEF__PAGE_PATCH_1_DEV \ + ((uint16_t) 2U) +#define PAGE_DEF__PAGE_AFTERBOOT_DEV \ + ((uint16_t) 3U) +#define PAGE_DEF__PAGE_SSC_DEV \ + ((uint16_t) 4U) +#define PAGE_DEF__PAGE_FW_PIPE_WORKSPACE_DEV \ + ((uint16_t) 5U) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/page_map_switch.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/page_map_switch.h new file mode 100644 index 000000000000..f3308788a7d2 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/page_map_switch.h @@ -0,0 +1,80 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __PAGE_MAP_SWITCH_H__ +#define __PAGE_MAP_SWITCH_H__ + +#include "vl53l5_platform_user_data.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int32_t dci_page_map_switch( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev, + uint16_t page_index); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_calibration_decode.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_calibration_decode.h new file mode 100644 index 000000000000..42de79bd06ee --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_calibration_decode.h @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_CALIBRATION_DECODE_H__ +#define __VL53L5_CALIBRATION_DECODE_H__ + +#include "vl53l5_platform_user_data.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int32_t vl53l5_calibration_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_calibration_dev_path.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_calibration_dev_path.h new file mode 100644 index 000000000000..d571847705cf --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_calibration_dev_path.h @@ -0,0 +1,202 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_CALIBRATION_DEV_PATH_H__ +#define __VL53L5_CALIBRATION_DEV_PATH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_MAP_CALIBRATION_DEV(p_dev) \ + ((p_dev)->host_dev.pcalibration_dev) +#define VL53L5_CAL_REF_SPAD_INFO(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->cal_ref_spad_info +#define VL53L5_CAL_TEMP_SENSOR(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->cal_temp_sensor +#define VL53L5_CAL_OPTICAL_CENTRE(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->cal_optical_centre +#define VL53L5_CAL_OSCILLATORS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->cal_oscillators +#define VL53L5_CAL_VHV(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->cal_vhv +#define VL53L5_POFFSET_GRID_META(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->poffset_grid_meta +#define VL53L5_POFFSET_PHASE_STATS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->poffset_phase_stats +#define VL53L5_POFFSET_TEMPERATURE_STATS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->poffset_temperature_stats +#define VL53L5_POFFSET_GRID_RATE(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->poffset_grid_rate +#define VL53L5_POFFSET_GRID_SPADS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->poffset_grid_spads +#define VL53L5_POFFSET_GRID_OFFSET(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->poffset_grid_offset +#define VL53L5_POFFSET_ERROR_STATUS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->poffset_error_status +#define VL53L5_POFFSET_WARNING_STATUS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->poffset_warning_status +#define VL53L5_POFFSET_4X4_GRID_META(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->poffset_4x4_grid_meta +#define VL53L5_POFFSET_4X4_PHASE_STATS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->poffset_4x4_phase_stats +#define VL53L5_POFFSET_4X4_TEMPERATURE_STATS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->poffset_4x4_temperature_stats +#define VL53L5_POFFSET_4X4_GRID_RATE(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->poffset_4x4_grid_rate +#define VL53L5_POFFSET_4X4_GRID_SPADS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->poffset_4x4_grid_spads +#define VL53L5_POFFSET_4X4_GRID_OFFSET(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->poffset_4x4_grid_offset +#define VL53L5_POFFSET_4X4_ERROR_STATUS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->poffset_4x4_error_status +#define VL53L5_POFFSET_4X4_WARNING_STATUS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->poffset_4x4_warning_status +#define VL53L5_PXTALK_GRID_META(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pxtalk_grid_meta +#define VL53L5_PXTALK_PHASE_STATS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pxtalk_phase_stats +#define VL53L5_PXTALK_TEMPERATURE_STATS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pxtalk_temperature_stats +#define VL53L5_PXTALK_GRID_RATE(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pxtalk_grid_rate +#define VL53L5_PXTALK_GRID_SPADS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pxtalk_grid_spads +#define VL53L5_PXTALK_ERROR_STATUS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pxtalk_error_status +#define VL53L5_PXTALK_WARNING_STATUS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pxtalk_warning_status +#define VL53L5_PCURRENT_XTALK_4X4_GRID_META(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pcurrent_xtalk_4x4_grid_meta +#define VL53L5_PCURRENT_XTALK_4X4_PHASE_STATS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pcurrent_xtalk_4x4_phase_stats +#define VL53L5_PCURRENT_XTALK_4X4_TEMPERATURE_STATS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pcurrent_xtalk_4x4_temperature_stats +#define VL53L5_PCURRENT_XTALK_4X4_GRID_RATE(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pcurrent_xtalk_4x4_grid_rate +#define VL53L5_PCURRENT_XTALK_4X4_GRID_SPADS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pcurrent_xtalk_4x4_grid_spads +#define VL53L5_PCURRENT_XTALK_4X4_ERROR_STATUS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pcurrent_xtalk_4x4_error_status +#define VL53L5_PCURRENT_XTALK_4X4_WARNING_STATUS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pcurrent_xtalk_4x4_warning_status +#define VL53L5_PCURRENT_XTALK_8X8_GRID_META(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pcurrent_xtalk_8x8_grid_meta +#define VL53L5_PCURRENT_XTALK_8X8_PHASE_STATS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pcurrent_xtalk_8x8_phase_stats +#define VL53L5_PCURRENT_XTALK_8X8_TEMPERATURE_STATS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pcurrent_xtalk_8x8_temperature_stats +#define VL53L5_PCURRENT_XTALK_8X8_GRID_RATE(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pcurrent_xtalk_8x8_grid_rate +#define VL53L5_PCURRENT_XTALK_8X8_GRID_SPADS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pcurrent_xtalk_8x8_grid_spads +#define VL53L5_PCURRENT_XTALK_8X8_ERROR_STATUS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pcurrent_xtalk_8x8_error_status +#define VL53L5_PCURRENT_XTALK_8X8_WARNING_STATUS(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pcurrent_xtalk_8x8_warning_status +#define VL53L5_PXTALK_SHAPE_META(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pxtalk_shape_meta +#define VL53L5_PXTALK_SHAPE_DATA(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pxtalk_shape_data +#define VL53L5_PXTALK_MON_META(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pxtalk_mon_meta +#define VL53L5_PXTALK_MON_ZONES(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pxtalk_mon_zones +#define VL53L5_PXTALK_MON_DATA(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pxtalk_mon_data +#define VL53L5_PRAD2PERP_GRID_META(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->prad2perp_grid_meta +#define VL53L5_PRAD2PERP_GRID_DATA(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->prad2perp_grid_data +#define VL53L5_PRAD2PERP_GRID_4X4_META(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->prad2perp_grid_4x4_meta +#define VL53L5_PRAD2PERP_GRID_4X4_DATA(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->prad2perp_grid_4x4_data +#define VL53L5_PRAD2PERP_GRID_8X8_META(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->prad2perp_grid_8x8_meta +#define VL53L5_PRAD2PERP_GRID_8X8_DATA(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->prad2perp_grid_8x8_data +#define VL53L5_PNORM_GRID_META(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pnorm_grid_meta +#define VL53L5_PNORM_GRID_DATA(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pnorm_grid_data +#define VL53L5_PDYN_XTALK__SUB_FRAME_XTALK_GRID_RATE(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pdyn_xtalk__sub_frame_xtalk_grid_rate +#define VL53L5_PDYN_XTALK__OTF_XTALK_GRID_RATE(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pdyn_xtalk__otf_xtalk_grid_rate +#define VL53L5_DYN_XTALK__LAST_VALID_XMON_DATA(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->dyn_xtalk__last_valid_xmon_data +#define VL53L5_PADDING_32(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->padding_32 +#define VL53L5_PCAL_COMMON_SUM(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pcal_common_sum +#define VL53L5_PCAL_XMON_SUM(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pcal_xmon_sum +#define VL53L5_PCAL_ZONE_SUM(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pcal_zone_sum +#define VL53L5_PCAL_HIST_META(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pcal_hist_meta +#define VL53L5_PCAL_HIST_SUM(p_dev) \ + VL53L5_MAP_CALIBRATION_DEV(p_dev)->pcal_hist_sum + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_calibration_enum_type.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_calibration_enum_type.h new file mode 100644 index 000000000000..fa6aee83997c --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_calibration_enum_type.h @@ -0,0 +1,118 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_CALIBRATION_ENUM_TYPE_H__ +#define __VL53L5_CALIBRATION_ENUM_TYPE_H__ + +#include "vl53l5_dci_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef VL53L5_CALIBRATION_DECODE_ON +#define VL53L5_CAL_GRP_REF_SPAD_INFO_TYPE \ + ((enum block_format_type) 0x0) +#endif + +#ifdef VL53L5_CALIBRATION_DECODE_ON +#define VL53L5_CAL_GRP_OPTICAL_CENTRE_DATA_TYPE \ + ((enum block_format_type) 0x0) +#endif + +#ifdef VL53L5_CALIBRATION_DECODE_ON +#define VL53L5_CAL_GRP_GRID_META_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_CAL_GRP_PHASE_STATS_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_CAL_GRP_TEMPERATURE_STATS_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_CGGDRKPS_CAL_GRID_DATA_RATE_KCPS_PER_SPAD_TYPE \ + ((enum block_format_type) 0x4) +#define VL53L5_CGGDESC_CAL_GRID_DATA_EFFECTIVE_SPAD_COUNT_TYPE \ + ((enum block_format_type) 0x2) +#define VL53L5_CGGDRM_CAL_GRID_DATA_RANGE_MM_TYPE \ + ((enum block_format_type) 0x2) +#define VL53L5_CAL_GRP_STATUS_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_CAL_GRP_XTALK_SHAPE_META_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_CGXSD_CAL_XTALK_SHAPE_BIN_DATA_TYPE \ + ((enum block_format_type) 0x2) +#define VL53L5_CAL_GRP_XTALK_MON_META_DATA_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_CGXMZ_CAL_XMON_ZONE_X_OFF_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_CGXMZ_CAL_XMON_ZONE_Y_OFF_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_CGXMZ_CAL_XMON_ZONE_WIDTH_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_CGXMZ_CAL_XMON_ZONE_HEIGHT_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_CGXMD_CAL_XMON_ZONE_RATE_KCPS_SPAD_TYPE \ + ((enum block_format_type) 0x4) +#define VL53L5_CGXMD_CAL_XMON_ZONE_AVG_COUNT_TYPE \ + ((enum block_format_type) 0x2) +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_calibration_map.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_calibration_map.h new file mode 100644 index 000000000000..53a7acd4162c --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_calibration_map.h @@ -0,0 +1,156 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_CALIBRATION_MAP_H__ +#define __VL53L5_CALIBRATION_MAP_H__ + +#include "dci_defs.h" +#include "dci_structs.h" +#include "dci_union_structs.h" +#include "dci_ui_structs.h" +#include "cal_defs.h" +#include "cal_structs.h" +#include "padding_structs.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct vl53l5_calibration_dev_t { +#ifdef VL53L5_CALIBRATION_DECODE_ON + struct cal_grp__ref_spad_info_t cal_ref_spad_info; +#endif + +#ifdef VL53L5_CALIBRATION_DECODE_ON + struct cal_grp__optical_centre_data_t cal_optical_centre; +#endif + +#ifdef VL53L5_CALIBRATION_DECODE_ON + + struct cal_grp__grid_meta_t poffset_grid_meta; + + struct cal_grp__phase_stats_t poffset_phase_stats; + + struct cal_grp__temperature_stats_t poffset_temperature_stats; + + struct cal_grp__grid_data__rate_kcps_per_spad_t poffset_grid_rate; + + struct cal_grp__grid_data__effective_spad_count_t poffset_grid_spads; + + struct cal_grp__grid_data__range_mm_t poffset_grid_offset; + + struct cal_grp__status_t poffset_error_status; + + struct cal_grp__status_t poffset_warning_status; +#endif + +#ifdef VL53L5_CALIBRATION_DECODE_ON + + struct cal_grp__grid_meta_t pxtalk_grid_meta; + + struct cal_grp__phase_stats_t pxtalk_phase_stats; + + struct cal_grp__temperature_stats_t pxtalk_temperature_stats; + + struct cal_grp__grid_data__rate_kcps_per_spad_t pxtalk_grid_rate; + + struct cal_grp__grid_data__effective_spad_count_t pxtalk_grid_spads; + + struct cal_grp__status_t pxtalk_error_status; + + struct cal_grp__status_t pxtalk_warning_status; +#endif + +#ifdef VL53L5_CALIBRATION_DECODE_ON + + struct cal_grp__xtalk_shape_meta_t pxtalk_shape_meta; + + struct cal_grp__xtalk_shape_data_t pxtalk_shape_data; + + struct cal_grp__xtalk_mon__meta_data_t pxtalk_mon_meta; + + struct cal_grp__xtalk_mon__zones_t pxtalk_mon_zones; + + struct cal_grp__xtalk_mon__data_t pxtalk_mon_data; +#endif + +#ifdef VL53L5_CALIBRATION_DECODE_ON + + struct cal_grp__grid_meta_t prad2perp_grid_4x4_meta; + + struct cal_grp__grid_data__scale_factor_t prad2perp_grid_4x4_data; + + struct cal_grp__grid_meta_t prad2perp_grid_8x8_meta; + + struct cal_grp__grid_data__scale_factor_t prad2perp_grid_8x8_data; +#endif + +#ifdef VL53L5_CALIBRATION_DECODE_ON + + struct cal_grp__xtalk_mon__data_t dyn_xtalk__last_valid_xmon_data; +#endif + +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_calibration_map_bh.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_calibration_map_bh.h new file mode 100644 index 000000000000..d6eb1596c83f --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_calibration_map_bh.h @@ -0,0 +1,142 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_CALIBRATION_MAP_BH_H__ +#define __VL53L5_CALIBRATION_MAP_BH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_CAL_REF_SPAD_INFO_BH \ + ((uint32_t) 0xafdc0080U) + +#define VL53L5_CAL_OPTICAL_CENTRE_BH \ + ((uint32_t) 0xafe80040U) + +#define VL53L5_POFFSET_GRID_META_BH \ + ((uint32_t) 0xaff800c0U) +#define VL53L5_POFFSET_PHASE_STATS_BH \ + ((uint32_t) 0xb0040140U) +#define VL53L5_POFFSET_TEMPERATURE_STATS_BH \ + ((uint32_t) 0xb0180040U) +#define VL53L5_POFFSET_GRID_RATE_CAL__GRID_DATA__RATE_KCPS_PER_SPAD_BH \ + ((uint32_t) 0xb01c0404U) +#define VL53L5_POFFSET_GRID_SPADS_CAL__GRID_DATA_EFFECTIVE_SPAD_COUNT_BH \ + ((uint32_t) 0xb11c0402U) +#define VL53L5_POFFSET_GRID_OFFSET_CAL__GRID_DATA__RANGE_MM_BH \ + ((uint32_t) 0xb19c0402U) +#define VL53L5_POFFSET_ERROR_STATUS_BH \ + ((uint32_t) 0xb21c0100U) +#define VL53L5_POFFSET_WARNING_STATUS_BH \ + ((uint32_t) 0xb22c0100U) + +#define VL53L5_PXTALK_GRID_META_BH \ + ((uint32_t) 0xb48000c0U) +#define VL53L5_PXTALK_PHASE_STATS_BH \ + ((uint32_t) 0xb48c0140U) +#define VL53L5_PXTALK_TEMPERATURE_STATS_BH \ + ((uint32_t) 0xb4a00040U) +#define VL53L5_PXTALK_GRID_RATE_CAL__GRID_DATA__RATE_KCPS_PER_SPAD_BH \ + ((uint32_t) 0xb4a40404U) +#define VL53L5_PXTALK_GRID_SPADS_CAL__GRID_DATA_EFFECTIVE_SPAD_COUNT_BH \ + ((uint32_t) 0xb5a40402U) +#define VL53L5_PXTALK_ERROR_STATUS_BH \ + ((uint32_t) 0xb6240100U) +#define VL53L5_PXTALK_WARNING_STATUS_BH \ + ((uint32_t) 0xb6340100U) + +#define VL53L5_PXTALK_SHAPE_META_BH \ + ((uint32_t) 0xb9cc00c0U) +#define VL53L5_PXTALK_SHAPE_DATA_CAL__XTALK_SHAPE__BIN_DATA_BH \ + ((uint32_t) 0xb9d80902U) +#define VL53L5_PXTALK_MON_META_BH \ + ((uint32_t) 0xbaf80040U) +#define VL53L5_PXTALK_MON_ZONES_CAL__XMON__ZONE__X_OFF_BH \ + ((uint32_t) 0xbafc0081U) +#define VL53L5_PXTALK_MON_ZONES_CAL__XMON__ZONE__Y_OFF_BH \ + ((uint32_t) 0xbb040081U) +#define VL53L5_PXTALK_MON_ZONES_CAL__XMON__ZONE__WIDTH_BH \ + ((uint32_t) 0xbb0c0081U) +#define VL53L5_PXTALK_MON_ZONES_CAL__XMON__ZONE__HEIGHT_BH \ + ((uint32_t) 0xbb140081U) +#define VL53L5_PXTALK_MON_DATA_CAL__XMON__ZONE__RATE_KCPS_SPAD_BH \ + ((uint32_t) 0xbb1c0084U) +#define VL53L5_PXTALK_MON_DATA_CAL__XMON__ZONE__AVG_COUNT_BH \ + ((uint32_t) 0xbb3c0082U) + +#define VL53L5_PRAD2PERP_GRID_4X4_META_BH \ + ((uint32_t) 0xbbd800c0U) +#define VL53L5_PRAD2PERP_GRID_4X4_DATA_CAL__GRID_DATA__SCALE_FACTOR_BH \ + ((uint32_t) 0xbbe40402U) +#define VL53L5_PRAD2PERP_GRID_8X8_META_BH \ + ((uint32_t) 0xbc6400c0U) +#define VL53L5_PRAD2PERP_GRID_8X8_DATA_CAL__GRID_DATA__SCALE_FACTOR_BH \ + ((uint32_t) 0xbc700402U) + +#define VL53L5_DYN_XTALK__LAST_VALID_XMON_DATA_CAL__XMON__ZONE__RATE_KCPS_SPAD_BH \ + ((uint32_t) 0xbf7c0084U) +#define VL53L5_DYN_XTALK__LAST_VALID_XMON_DATA_CAL__XMON__ZONE__AVG_COUNT_BH \ + ((uint32_t) 0xbf9c0082U) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_calibration_map_idx.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_calibration_map_idx.h new file mode 100644 index 000000000000..92413831bfa9 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_calibration_map_idx.h @@ -0,0 +1,191 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_CALIBRATION_MAP_IDX_H__ +#define __VL53L5_CALIBRATION_MAP_IDX_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define DEV_CAL_REF_SPAD_INFO_IDX \ + ((uint16_t) 0xafdc) + +#define DEV_CAL_OPTICAL_CENTRE_IDX \ + ((uint16_t) 0xafe8) + +#define DEV_POFFSET_GRID_META_IDX \ + ((uint16_t) 0xaff8) + +#define DEV_POFFSET_PHASE_STATS_IDX \ + ((uint16_t) 0xb004) + +#define DEV_POFFSET_TEMPERATURE_STATS_IDX \ + ((uint16_t) 0xb018) + +#define DEV_POFFSET_GRID_RATE_CAL__GRID_DATA__RATE_KCPS_PER_SPAD_IDX \ + ((uint16_t) 0xb01c) +#define DEV_POFFSET_GRID_RATE_IDX \ + ((uint16_t) 0xb01c) + +#define DEV_POFFSET_GRID_SPADS_CAL__GRID_DATA_EFFECTIVE_SPAD_COUNT_IDX \ + ((uint16_t) 0xb11c) +#define DEV_POFFSET_GRID_SPADS_IDX \ + ((uint16_t) 0xb11c) + +#define DEV_POFFSET_GRID_OFFSET_CAL__GRID_DATA__RANGE_MM_IDX \ + ((uint16_t) 0xb19c) +#define DEV_POFFSET_GRID_OFFSET_IDX \ + ((uint16_t) 0xb19c) + +#define DEV_POFFSET_ERROR_STATUS_IDX \ + ((uint16_t) 0xb21c) + +#define DEV_POFFSET_WARNING_STATUS_IDX \ + ((uint16_t) 0xb22c) + +#define DEV_PXTALK_GRID_META_IDX \ + ((uint16_t) 0xb480) + +#define DEV_PXTALK_PHASE_STATS_IDX \ + ((uint16_t) 0xb48c) + +#define DEV_PXTALK_TEMPERATURE_STATS_IDX \ + ((uint16_t) 0xb4a0) + +#define DEV_PXTALK_GRID_RATE_CAL__GRID_DATA__RATE_KCPS_PER_SPAD_IDX \ + ((uint16_t) 0xb4a4) +#define DEV_PXTALK_GRID_RATE_IDX \ + ((uint16_t) 0xb4a4) + +#define DEV_PXTALK_GRID_SPADS_CAL__GRID_DATA_EFFECTIVE_SPAD_COUNT_IDX \ + ((uint16_t) 0xb5a4) +#define DEV_PXTALK_GRID_SPADS_IDX \ + ((uint16_t) 0xb5a4) + +#define DEV_PXTALK_ERROR_STATUS_IDX \ + ((uint16_t) 0xb624) + +#define DEV_PXTALK_WARNING_STATUS_IDX \ + ((uint16_t) 0xb634) + +#define DEV_PXTALK_SHAPE_META_IDX \ + ((uint16_t) 0xb9cc) + +#define DEV_PXTALK_SHAPE_DATA_CAL__XTALK_SHAPE__BIN_DATA_IDX \ + ((uint16_t) 0xb9d8) +#define DEV_PXTALK_SHAPE_DATA_IDX \ + ((uint16_t) 0xb9d8) + +#define DEV_PXTALK_MON_META_IDX \ + ((uint16_t) 0xbaf8) + +#define DEV_PXTALK_MON_ZONES_CAL__XMON__ZONE__X_OFF_IDX \ + ((uint16_t) 0xbafc) +#define DEV_PXTALK_MON_ZONES_IDX \ + ((uint16_t) 0xbafc) + +#define DEV_PXTALK_MON_ZONES_CAL__XMON__ZONE__Y_OFF_IDX \ + ((uint16_t) 0xbb04) + +#define DEV_PXTALK_MON_ZONES_CAL__XMON__ZONE__WIDTH_IDX \ + ((uint16_t) 0xbb0c) + +#define DEV_PXTALK_MON_ZONES_CAL__XMON__ZONE__HEIGHT_IDX \ + ((uint16_t) 0xbb14) + +#define DEV_PXTALK_MON_DATA_CAL__XMON__ZONE__RATE_KCPS_SPAD_IDX \ + ((uint16_t) 0xbb1c) +#define DEV_PXTALK_MON_DATA_IDX \ + ((uint16_t) 0xbb1c) + +#define DEV_PXTALK_MON_DATA_CAL__XMON__ZONE__AVG_COUNT_IDX \ + ((uint16_t) 0xbb3c) + +#define DEV_PRAD2PERP_GRID_4X4_META_IDX \ + ((uint16_t) 0xbbd8) + +#define DEV_PRAD2PERP_GRID_4X4_DATA_CAL__GRID_DATA__SCALE_FACTOR_IDX \ + ((uint16_t) 0xbbe4) +#define DEV_PRAD2PERP_GRID_4X4_DATA_IDX \ + ((uint16_t) 0xbbe4) + +#define DEV_PRAD2PERP_GRID_8X8_META_IDX \ + ((uint16_t) 0xbc64) + +#define DEV_PRAD2PERP_GRID_8X8_DATA_CAL__GRID_DATA__SCALE_FACTOR_IDX \ + ((uint16_t) 0xbc70) +#define DEV_PRAD2PERP_GRID_8X8_DATA_IDX \ + ((uint16_t) 0xbc70) + +#define DEV_DYN_XTALK__LAST_VALID_XMON_DATA_CAL__XMON__ZONE__RATE_KCPS_SPAD_IDX \ + ((uint16_t) 0xbf7c) +#define DEV_DYN_XTALK__LAST_VALID_XMON_DATA_IDX \ + ((uint16_t) 0xbf7c) + +#define DEV_DYN_XTALK__LAST_VALID_XMON_DATA_CAL__XMON__ZONE__AVG_COUNT_IDX \ + ((uint16_t) 0xbf9c) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_core_decode.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_core_decode.h new file mode 100644 index 000000000000..e363ca557cac --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_core_decode.h @@ -0,0 +1,80 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_CORE_DECODE_H__ +#define __VL53L5_CORE_DECODE_H__ + +#include "common_datatype_size.h" +#include "vl53l5_platform_user_data.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int32_t vl53l5_core_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_core_dev_path.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_core_dev_path.h new file mode 100644 index 000000000000..d98ae39d2c7f --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_core_dev_path.h @@ -0,0 +1,252 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_CORE_DEV_PATH_H__ +#define __VL53L5_CORE_DEV_PATH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_MAP_CORE_DEV(p_dev) \ + ((p_dev)->host_dev.core_dev) +#define VL53L5_MAP_VERSION(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).map_version +#define VL53L5_FW_VERSION(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).fw_version +#define VL53L5_CFG_INFO(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).cfg_info +#define VL53L5_FMT_TRACEABILITY(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).fmt_traceability +#define VL53L5_EWS_TRACEABILITY(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ews_traceability +#define VL53L5_UI_RNG_DATA_ADDR(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ui_rng_data_addr +#define VL53L5_SILICON_TEMP_DATA(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).silicon_temp_data +#define VL53L5_ZONE_CFG(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).zone_cfg +#define VL53L5_DEVICE_MODE_CFG(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).device_mode_cfg +#define VL53L5_RNG_RATE_CFG(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).rng_rate_cfg +#define VL53L5_INT_MAX_CFG(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).int_max_cfg +#define VL53L5_INT_MIN_CFG(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).int_min_cfg +#define VL53L5_INT_MPX_DELTA_CFG(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).int_mpx_delta_cfg +#define VL53L5_INT_DSS_CFG(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).int_dss_cfg +#define VL53L5_FACTORY_CAL_CFG(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).factory_cal_cfg +#define VL53L5_OUTPUT_DATA_CFG(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).output_data_cfg +#define VL53L5_OUTPUT_BH_CFG(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).output_bh_cfg +#define VL53L5_OUTPUT_BH_ENABLES(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).output_bh_enables +#define VL53L5_OUTPUT_BH_LIST(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).output_bh_list +#define VL53L5_INTERRUPT_CFG(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).interrupt_cfg +#define VL53L5_NVM_LASER_SAFETY_INFO(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).nvm_laser_safety_info +#define VL53L5_PATCH_HOOK_ENABLES_ARRAY(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).patch_hook_enables_array +#define VL53L5_DEVICE_ERROR_STATUS(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).device_error_status +#define VL53L5_DEVICE_WARNING_STATUS(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).device_warning_status +#define VL53L5_IC_CHECKER_0(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_0 +#define VL53L5_IC_CHECKER_1(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_1 +#define VL53L5_IC_CHECKER_2(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_2 +#define VL53L5_IC_CHECKER_3(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_3 +#define VL53L5_IC_CHECKER_4(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_4 +#define VL53L5_IC_CHECKER_5(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_5 +#define VL53L5_IC_CHECKER_6(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_6 +#define VL53L5_IC_CHECKER_7(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_7 +#define VL53L5_IC_CHECKER_8(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_8 +#define VL53L5_IC_CHECKER_9(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_9 +#define VL53L5_IC_CHECKER_10(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_10 +#define VL53L5_IC_CHECKER_11(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_11 +#define VL53L5_IC_CHECKER_12(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_12 +#define VL53L5_IC_CHECKER_13(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_13 +#define VL53L5_IC_CHECKER_14(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_14 +#define VL53L5_IC_CHECKER_15(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_15 +#define VL53L5_IC_CHECKER_16(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_16 +#define VL53L5_IC_CHECKER_17(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_17 +#define VL53L5_IC_CHECKER_18(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_18 +#define VL53L5_IC_CHECKER_19(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_19 +#define VL53L5_IC_CHECKER_20(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_20 +#define VL53L5_IC_CHECKER_21(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_21 +#define VL53L5_IC_CHECKER_22(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_22 +#define VL53L5_IC_CHECKER_23(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_23 +#define VL53L5_IC_CHECKER_24(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_24 +#define VL53L5_IC_CHECKER_25(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_25 +#define VL53L5_IC_CHECKER_26(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_26 +#define VL53L5_IC_CHECKER_27(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_27 +#define VL53L5_IC_CHECKER_28(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_28 +#define VL53L5_IC_CHECKER_29(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_29 +#define VL53L5_IC_CHECKER_30(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_30 +#define VL53L5_IC_CHECKER_31(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_31 +#define VL53L5_IC_CHECKER_32(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_32 +#define VL53L5_IC_CHECKER_33(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_33 +#define VL53L5_IC_CHECKER_34(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_34 +#define VL53L5_IC_CHECKER_35(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_35 +#define VL53L5_IC_CHECKER_36(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_36 +#define VL53L5_IC_CHECKER_37(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_37 +#define VL53L5_IC_CHECKER_38(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_38 +#define VL53L5_IC_CHECKER_39(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_39 +#define VL53L5_IC_CHECKER_40(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_40 +#define VL53L5_IC_CHECKER_41(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_41 +#define VL53L5_IC_CHECKER_42(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_42 +#define VL53L5_IC_CHECKER_43(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_43 +#define VL53L5_IC_CHECKER_44(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_44 +#define VL53L5_IC_CHECKER_45(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_45 +#define VL53L5_IC_CHECKER_46(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_46 +#define VL53L5_IC_CHECKER_47(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_47 +#define VL53L5_IC_CHECKER_48(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_48 +#define VL53L5_IC_CHECKER_49(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_49 +#define VL53L5_IC_CHECKER_50(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_50 +#define VL53L5_IC_CHECKER_51(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_51 +#define VL53L5_IC_CHECKER_52(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_52 +#define VL53L5_IC_CHECKER_53(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_53 +#define VL53L5_IC_CHECKER_54(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_54 +#define VL53L5_IC_CHECKER_55(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_55 +#define VL53L5_IC_CHECKER_56(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_56 +#define VL53L5_IC_CHECKER_57(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_57 +#define VL53L5_IC_CHECKER_58(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_58 +#define VL53L5_IC_CHECKER_59(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_59 +#define VL53L5_IC_CHECKER_60(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_60 +#define VL53L5_IC_CHECKER_61(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_61 +#define VL53L5_IC_CHECKER_62(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_62 +#define VL53L5_IC_CHECKER_63(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_checker_63 +#define VL53L5_IC_VALID_TGT_STATUS(p_dev) \ + VL53L5_MAP_CORE_DEV(p_dev).ic_valid_tgt_status + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_core_enum_type.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_core_enum_type.h new file mode 100644 index 000000000000..afa255af82b3 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_core_enum_type.h @@ -0,0 +1,118 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_CORE_ENUM_TYPE_H__ +#define __VL53L5_CORE_ENUM_TYPE_H__ + +#include "vl53l5_dci_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_MAP_VERSION_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_FW_VERSION_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_CFG_INFO_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_FMT_TRACEABILITY_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_EWS_TRACEABILITY_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_UI_RNG_DATA_ADDR_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_SILICON_TEMPERATURE_DATA_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_ZONE_CFG_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_DEVICE_MODE_CFG_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_RANGING_RATE_CFG_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_INTEGRATION_TIME_CFG_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_FACTORY_CAL_CFG_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_OUTPUT_DATA_CFG_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_OUTPUT_BH_CFG_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_OBE_OP_BH_ENABLES_TYPE \ + ((enum block_format_type) 0x4) +#define VL53L5_OBL_OP_BH_LIST_TYPE \ + ((enum block_format_type) 0x4) +#define VL53L5_INTERRUPT_CFG_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_NVM_LASER_SAFETY_NVM2_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_PHE_PATCH_HOOK_ENABLES_TYPE \ + ((enum block_format_type) 0x4) +#define VL53L5_COMMON_GRP_STATUS_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_IC_GRP_CHECKER_CFG_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_IGVTS_VAILD_TARGET_STATUS_TYPE \ + ((enum block_format_type) 0x1) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_core_map.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_core_map.h new file mode 100644 index 000000000000..c102425614fb --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_core_map.h @@ -0,0 +1,93 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_CORE_MAP_H__ +#define __VL53L5_CORE_MAP_H__ + +#include "dci_defs.h" +#include "dci_structs.h" +#include "dci_union_structs.h" +#include "dci_ui_structs.h" +#include "vl53l5_types.h" +#include "common_datatype_structs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct vl53l5_core_dev_t { +#ifdef VL53L5_CALIBRATION_DECODE_ON + struct dci_grp__map_version_t map_version; +#endif + +#ifdef VL53L5_SILICON_TEMP_DATA_ON + struct dci_grp__silicon_temperature_data_t silicon_temp_data; +#endif + +#ifdef VL53L5_ZONE_CFG_ON + struct dci_grp__zone_cfg_t zone_cfg; +#endif + +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_core_map_bh.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_core_map_bh.h new file mode 100644 index 000000000000..b712b493279f --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_core_map_bh.h @@ -0,0 +1,250 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_CORE_MAP_BH_H__ +#define __VL53L5_CORE_MAP_BH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_MAP_VERSION_BH \ + ((uint32_t) 0x54000040U) +#define VL53L5_FW_VERSION_BH \ + ((uint32_t) 0x54040080U) +#define VL53L5_CFG_INFO_BH \ + ((uint32_t) 0x540c0140U) +#define VL53L5_FMT_TRACEABILITY_BH \ + ((uint32_t) 0x54200180U) +#define VL53L5_EWS_TRACEABILITY_BH \ + ((uint32_t) 0x543800c0U) +#define VL53L5_UI_RNG_DATA_ADDR_BH \ + ((uint32_t) 0x544400c0U) +#define VL53L5_SILICON_TEMP_DATA_BH \ + ((uint32_t) 0x54500040U) +#define VL53L5_ZONE_CFG_BH \ + ((uint32_t) 0x54540080U) +#define VL53L5_DEVICE_MODE_CFG_BH \ + ((uint32_t) 0x545c0080U) +#define VL53L5_RNG_RATE_CFG_BH \ + ((uint32_t) 0x54640040U) +#define VL53L5_INT_MAX_CFG_BH \ + ((uint32_t) 0x54680040U) +#define VL53L5_INT_MIN_CFG_BH \ + ((uint32_t) 0x546c0040U) +#define VL53L5_INT_MPX_DELTA_CFG_BH \ + ((uint32_t) 0x54700040U) +#define VL53L5_INT_DSS_CFG_BH \ + ((uint32_t) 0x54740040U) +#define VL53L5_FACTORY_CAL_CFG_BH \ + ((uint32_t) 0x54780080U) +#define VL53L5_OUTPUT_DATA_CFG_BH \ + ((uint32_t) 0x54800040U) +#define VL53L5_OUTPUT_BH_CFG_BH \ + ((uint32_t) 0x54840080U) +#define VL53L5_OUTPUT_BH_ENABLES_OP_BH__ENABLES_BH \ + ((uint32_t) 0x548c0044U) +#define VL53L5_OUTPUT_BH_LIST_OP_BH__LIST_BH \ + ((uint32_t) 0x549c0804U) +#define VL53L5_INTERRUPT_CFG_BH \ + ((uint32_t) 0x569c0080U) +#define VL53L5_NVM_LASER_SAFETY_INFO_BH \ + ((uint32_t) 0x56a40040U) +#define VL53L5_PATCH_HOOK_ENABLES_ARRAY_PATCH_HOOK_ENABLES_BH \ + ((uint32_t) 0x56a80044U) +#define VL53L5_DEVICE_ERROR_STATUS_BH \ + ((uint32_t) 0x56b800c0U) +#define VL53L5_DEVICE_WARNING_STATUS_BH \ + ((uint32_t) 0x56c400c0U) +#define VL53L5_IC_CHECKER_0_BH \ + ((uint32_t) 0x56d000c0U) +#define VL53L5_IC_CHECKER_1_BH \ + ((uint32_t) 0x56dc00c0U) +#define VL53L5_IC_CHECKER_2_BH \ + ((uint32_t) 0x56e800c0U) +#define VL53L5_IC_CHECKER_3_BH \ + ((uint32_t) 0x56f400c0U) +#define VL53L5_IC_CHECKER_4_BH \ + ((uint32_t) 0x570000c0U) +#define VL53L5_IC_CHECKER_5_BH \ + ((uint32_t) 0x570c00c0U) +#define VL53L5_IC_CHECKER_6_BH \ + ((uint32_t) 0x571800c0U) +#define VL53L5_IC_CHECKER_7_BH \ + ((uint32_t) 0x572400c0U) +#define VL53L5_IC_CHECKER_8_BH \ + ((uint32_t) 0x573000c0U) +#define VL53L5_IC_CHECKER_9_BH \ + ((uint32_t) 0x573c00c0U) +#define VL53L5_IC_CHECKER_10_BH \ + ((uint32_t) 0x574800c0U) +#define VL53L5_IC_CHECKER_11_BH \ + ((uint32_t) 0x575400c0U) +#define VL53L5_IC_CHECKER_12_BH \ + ((uint32_t) 0x576000c0U) +#define VL53L5_IC_CHECKER_13_BH \ + ((uint32_t) 0x576c00c0U) +#define VL53L5_IC_CHECKER_14_BH \ + ((uint32_t) 0x577800c0U) +#define VL53L5_IC_CHECKER_15_BH \ + ((uint32_t) 0x578400c0U) +#define VL53L5_IC_CHECKER_16_BH \ + ((uint32_t) 0x579000c0U) +#define VL53L5_IC_CHECKER_17_BH \ + ((uint32_t) 0x579c00c0U) +#define VL53L5_IC_CHECKER_18_BH \ + ((uint32_t) 0x57a800c0U) +#define VL53L5_IC_CHECKER_19_BH \ + ((uint32_t) 0x57b400c0U) +#define VL53L5_IC_CHECKER_20_BH \ + ((uint32_t) 0x57c000c0U) +#define VL53L5_IC_CHECKER_21_BH \ + ((uint32_t) 0x57cc00c0U) +#define VL53L5_IC_CHECKER_22_BH \ + ((uint32_t) 0x57d800c0U) +#define VL53L5_IC_CHECKER_23_BH \ + ((uint32_t) 0x57e400c0U) +#define VL53L5_IC_CHECKER_24_BH \ + ((uint32_t) 0x57f000c0U) +#define VL53L5_IC_CHECKER_25_BH \ + ((uint32_t) 0x57fc00c0U) +#define VL53L5_IC_CHECKER_26_BH \ + ((uint32_t) 0x580800c0U) +#define VL53L5_IC_CHECKER_27_BH \ + ((uint32_t) 0x581400c0U) +#define VL53L5_IC_CHECKER_28_BH \ + ((uint32_t) 0x582000c0U) +#define VL53L5_IC_CHECKER_29_BH \ + ((uint32_t) 0x582c00c0U) +#define VL53L5_IC_CHECKER_30_BH \ + ((uint32_t) 0x583800c0U) +#define VL53L5_IC_CHECKER_31_BH \ + ((uint32_t) 0x584400c0U) +#define VL53L5_IC_CHECKER_32_BH \ + ((uint32_t) 0x585000c0U) +#define VL53L5_IC_CHECKER_33_BH \ + ((uint32_t) 0x585c00c0U) +#define VL53L5_IC_CHECKER_34_BH \ + ((uint32_t) 0x586800c0U) +#define VL53L5_IC_CHECKER_35_BH \ + ((uint32_t) 0x587400c0U) +#define VL53L5_IC_CHECKER_36_BH \ + ((uint32_t) 0x588000c0U) +#define VL53L5_IC_CHECKER_37_BH \ + ((uint32_t) 0x588c00c0U) +#define VL53L5_IC_CHECKER_38_BH \ + ((uint32_t) 0x589800c0U) +#define VL53L5_IC_CHECKER_39_BH \ + ((uint32_t) 0x58a400c0U) +#define VL53L5_IC_CHECKER_40_BH \ + ((uint32_t) 0x58b000c0U) +#define VL53L5_IC_CHECKER_41_BH \ + ((uint32_t) 0x58bc00c0U) +#define VL53L5_IC_CHECKER_42_BH \ + ((uint32_t) 0x58c800c0U) +#define VL53L5_IC_CHECKER_43_BH \ + ((uint32_t) 0x58d400c0U) +#define VL53L5_IC_CHECKER_44_BH \ + ((uint32_t) 0x58e000c0U) +#define VL53L5_IC_CHECKER_45_BH \ + ((uint32_t) 0x58ec00c0U) +#define VL53L5_IC_CHECKER_46_BH \ + ((uint32_t) 0x58f800c0U) +#define VL53L5_IC_CHECKER_47_BH \ + ((uint32_t) 0x590400c0U) +#define VL53L5_IC_CHECKER_48_BH \ + ((uint32_t) 0x591000c0U) +#define VL53L5_IC_CHECKER_49_BH \ + ((uint32_t) 0x591c00c0U) +#define VL53L5_IC_CHECKER_50_BH \ + ((uint32_t) 0x592800c0U) +#define VL53L5_IC_CHECKER_51_BH \ + ((uint32_t) 0x593400c0U) +#define VL53L5_IC_CHECKER_52_BH \ + ((uint32_t) 0x594000c0U) +#define VL53L5_IC_CHECKER_53_BH \ + ((uint32_t) 0x594c00c0U) +#define VL53L5_IC_CHECKER_54_BH \ + ((uint32_t) 0x595800c0U) +#define VL53L5_IC_CHECKER_55_BH \ + ((uint32_t) 0x596400c0U) +#define VL53L5_IC_CHECKER_56_BH \ + ((uint32_t) 0x597000c0U) +#define VL53L5_IC_CHECKER_57_BH \ + ((uint32_t) 0x597c00c0U) +#define VL53L5_IC_CHECKER_58_BH \ + ((uint32_t) 0x598800c0U) +#define VL53L5_IC_CHECKER_59_BH \ + ((uint32_t) 0x599400c0U) +#define VL53L5_IC_CHECKER_60_BH \ + ((uint32_t) 0x59a000c0U) +#define VL53L5_IC_CHECKER_61_BH \ + ((uint32_t) 0x59ac00c0U) +#define VL53L5_IC_CHECKER_62_BH \ + ((uint32_t) 0x59b800c0U) +#define VL53L5_IC_CHECKER_63_BH \ + ((uint32_t) 0x59c400c0U) +#define VL53L5_IC_VALID_TGT_STATUS_VAILD_TARGET_STATUS_BH \ + ((uint32_t) 0x59d00081U) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_core_map_idx.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_core_map_idx.h new file mode 100644 index 000000000000..d94914cac31d --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_core_map_idx.h @@ -0,0 +1,348 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_CORE_MAP_IDX_H__ +#define __VL53L5_CORE_MAP_IDX_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define DEV_MAP_VERSION_IDX \ + ((uint16_t) 0x5400) + +#define DEV_FW_VERSION_IDX \ + ((uint16_t) 0x5404) + +#define DEV_CFG_INFO_IDX \ + ((uint16_t) 0x540c) + +#define DEV_FMT_TRACEABILITY_IDX \ + ((uint16_t) 0x5420) + +#define DEV_EWS_TRACEABILITY_IDX \ + ((uint16_t) 0x5438) + +#define DEV_UI_RNG_DATA_ADDR_IDX \ + ((uint16_t) 0x5444) + +#define DEV_SILICON_TEMP_DATA_IDX \ + ((uint16_t) 0x5450) + +#define DEV_ZONE_CFG_IDX \ + ((uint16_t) 0x5454) + +#define DEV_DEVICE_MODE_CFG_IDX \ + ((uint16_t) 0x545c) + +#define DEV_RNG_RATE_CFG_IDX \ + ((uint16_t) 0x5464) + +#define DEV_INT_MAX_CFG_IDX \ + ((uint16_t) 0x5468) + +#define DEV_INT_MIN_CFG_IDX \ + ((uint16_t) 0x546c) + +#define DEV_INT_MPX_DELTA_CFG_IDX \ + ((uint16_t) 0x5470) + +#define DEV_INT_DSS_CFG_IDX \ + ((uint16_t) 0x5474) + +#define DEV_FACTORY_CAL_CFG_IDX \ + ((uint16_t) 0x5478) + +#define DEV_OUTPUT_DATA_CFG_IDX \ + ((uint16_t) 0x5480) + +#define DEV_OUTPUT_BH_CFG_IDX \ + ((uint16_t) 0x5484) + +#define DEV_OUTPUT_BH_ENABLES_OP_BH__ENABLES_IDX \ + ((uint16_t) 0x548c) +#define DEV_OUTPUT_BH_ENABLES_IDX \ + ((uint16_t) 0x548c) + +#define DEV_OUTPUT_BH_LIST_OP_BH__LIST_IDX \ + ((uint16_t) 0x549c) +#define DEV_OUTPUT_BH_LIST_IDX \ + ((uint16_t) 0x549c) + +#define DEV_INTERRUPT_CFG_IDX \ + ((uint16_t) 0x569c) + +#define DEV_NVM_LASER_SAFETY_INFO_IDX \ + ((uint16_t) 0x56a4) + +#define DEV_PATCH_HOOK_ENABLES_ARRAY_PATCH_HOOK_ENABLES_IDX \ + ((uint16_t) 0x56a8) +#define DEV_PATCH_HOOK_ENABLES_ARRAY_IDX \ + ((uint16_t) 0x56a8) + +#define DEV_DEVICE_ERROR_STATUS_IDX \ + ((uint16_t) 0x56b8) + +#define DEV_DEVICE_WARNING_STATUS_IDX \ + ((uint16_t) 0x56c4) + +#define DEV_IC_CHECKER_0_IDX \ + ((uint16_t) 0x56d0) + +#define DEV_IC_CHECKER_1_IDX \ + ((uint16_t) 0x56dc) + +#define DEV_IC_CHECKER_2_IDX \ + ((uint16_t) 0x56e8) + +#define DEV_IC_CHECKER_3_IDX \ + ((uint16_t) 0x56f4) + +#define DEV_IC_CHECKER_4_IDX \ + ((uint16_t) 0x5700) + +#define DEV_IC_CHECKER_5_IDX \ + ((uint16_t) 0x570c) + +#define DEV_IC_CHECKER_6_IDX \ + ((uint16_t) 0x5718) + +#define DEV_IC_CHECKER_7_IDX \ + ((uint16_t) 0x5724) + +#define DEV_IC_CHECKER_8_IDX \ + ((uint16_t) 0x5730) + +#define DEV_IC_CHECKER_9_IDX \ + ((uint16_t) 0x573c) + +#define DEV_IC_CHECKER_10_IDX \ + ((uint16_t) 0x5748) + +#define DEV_IC_CHECKER_11_IDX \ + ((uint16_t) 0x5754) + +#define DEV_IC_CHECKER_12_IDX \ + ((uint16_t) 0x5760) + +#define DEV_IC_CHECKER_13_IDX \ + ((uint16_t) 0x576c) + +#define DEV_IC_CHECKER_14_IDX \ + ((uint16_t) 0x5778) + +#define DEV_IC_CHECKER_15_IDX \ + ((uint16_t) 0x5784) + +#define DEV_IC_CHECKER_16_IDX \ + ((uint16_t) 0x5790) + +#define DEV_IC_CHECKER_17_IDX \ + ((uint16_t) 0x579c) + +#define DEV_IC_CHECKER_18_IDX \ + ((uint16_t) 0x57a8) + +#define DEV_IC_CHECKER_19_IDX \ + ((uint16_t) 0x57b4) + +#define DEV_IC_CHECKER_20_IDX \ + ((uint16_t) 0x57c0) + +#define DEV_IC_CHECKER_21_IDX \ + ((uint16_t) 0x57cc) + +#define DEV_IC_CHECKER_22_IDX \ + ((uint16_t) 0x57d8) + +#define DEV_IC_CHECKER_23_IDX \ + ((uint16_t) 0x57e4) + +#define DEV_IC_CHECKER_24_IDX \ + ((uint16_t) 0x57f0) + +#define DEV_IC_CHECKER_25_IDX \ + ((uint16_t) 0x57fc) + +#define DEV_IC_CHECKER_26_IDX \ + ((uint16_t) 0x5808) + +#define DEV_IC_CHECKER_27_IDX \ + ((uint16_t) 0x5814) + +#define DEV_IC_CHECKER_28_IDX \ + ((uint16_t) 0x5820) + +#define DEV_IC_CHECKER_29_IDX \ + ((uint16_t) 0x582c) + +#define DEV_IC_CHECKER_30_IDX \ + ((uint16_t) 0x5838) + +#define DEV_IC_CHECKER_31_IDX \ + ((uint16_t) 0x5844) + +#define DEV_IC_CHECKER_32_IDX \ + ((uint16_t) 0x5850) + +#define DEV_IC_CHECKER_33_IDX \ + ((uint16_t) 0x585c) + +#define DEV_IC_CHECKER_34_IDX \ + ((uint16_t) 0x5868) + +#define DEV_IC_CHECKER_35_IDX \ + ((uint16_t) 0x5874) + +#define DEV_IC_CHECKER_36_IDX \ + ((uint16_t) 0x5880) + +#define DEV_IC_CHECKER_37_IDX \ + ((uint16_t) 0x588c) + +#define DEV_IC_CHECKER_38_IDX \ + ((uint16_t) 0x5898) + +#define DEV_IC_CHECKER_39_IDX \ + ((uint16_t) 0x58a4) + +#define DEV_IC_CHECKER_40_IDX \ + ((uint16_t) 0x58b0) + +#define DEV_IC_CHECKER_41_IDX \ + ((uint16_t) 0x58bc) + +#define DEV_IC_CHECKER_42_IDX \ + ((uint16_t) 0x58c8) + +#define DEV_IC_CHECKER_43_IDX \ + ((uint16_t) 0x58d4) + +#define DEV_IC_CHECKER_44_IDX \ + ((uint16_t) 0x58e0) + +#define DEV_IC_CHECKER_45_IDX \ + ((uint16_t) 0x58ec) + +#define DEV_IC_CHECKER_46_IDX \ + ((uint16_t) 0x58f8) + +#define DEV_IC_CHECKER_47_IDX \ + ((uint16_t) 0x5904) + +#define DEV_IC_CHECKER_48_IDX \ + ((uint16_t) 0x5910) + +#define DEV_IC_CHECKER_49_IDX \ + ((uint16_t) 0x591c) + +#define DEV_IC_CHECKER_50_IDX \ + ((uint16_t) 0x5928) + +#define DEV_IC_CHECKER_51_IDX \ + ((uint16_t) 0x5934) + +#define DEV_IC_CHECKER_52_IDX \ + ((uint16_t) 0x5940) + +#define DEV_IC_CHECKER_53_IDX \ + ((uint16_t) 0x594c) + +#define DEV_IC_CHECKER_54_IDX \ + ((uint16_t) 0x5958) + +#define DEV_IC_CHECKER_55_IDX \ + ((uint16_t) 0x5964) + +#define DEV_IC_CHECKER_56_IDX \ + ((uint16_t) 0x5970) + +#define DEV_IC_CHECKER_57_IDX \ + ((uint16_t) 0x597c) + +#define DEV_IC_CHECKER_58_IDX \ + ((uint16_t) 0x5988) + +#define DEV_IC_CHECKER_59_IDX \ + ((uint16_t) 0x5994) + +#define DEV_IC_CHECKER_60_IDX \ + ((uint16_t) 0x59a0) + +#define DEV_IC_CHECKER_61_IDX \ + ((uint16_t) 0x59ac) + +#define DEV_IC_CHECKER_62_IDX \ + ((uint16_t) 0x59b8) + +#define DEV_IC_CHECKER_63_IDX \ + ((uint16_t) 0x59c4) + +#define DEV_IC_VALID_TGT_STATUS_VAILD_TARGET_STATUS_IDX \ + ((uint16_t) 0x59d0) +#define DEV_IC_VALID_TGT_STATUS_IDX \ + ((uint16_t) 0x59d0) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_dci_core.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_dci_core.h new file mode 100644 index 000000000000..5bca872a29fc --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_dci_core.h @@ -0,0 +1,87 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L5_DCI_CORE_H_ +#define _VL53L5_DCI_CORE_H_ + +#include "vl53l5_platform_user_data.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int32_t vl53l5_dci_write_command( + struct vl53l5_dev_handle_t *p_dev, + uint8_t cmd_id, + uint8_t trans_id); + +int32_t vl53l5_dci_read_command( + struct vl53l5_dev_handle_t *p_dev); + +int32_t vl53l5_dci_poll_command_status( + struct vl53l5_dev_handle_t *p_dev, + uint32_t trans_id, + uint32_t timeout_ms); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_dci_decode.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_dci_decode.h new file mode 100644 index 000000000000..55731a1ebd70 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_dci_decode.h @@ -0,0 +1,96 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L5_DCI_DECODE_H_ +#define _VL53L5_DCI_DECODE_H_ + +#include "vl53l5_platform_user_data.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define DEV_INFO_SZ VL53L5_DEV_INFO_BLOCK_SZ + +#define RANGE_DATA_READ_SIZE(p_dev) \ + (p_dev->host_dev.range_data_addr.data_start_offset_bytes\ + + p_dev->host_dev.range_data_addr.data_size_bytes\ + + VL53L5_HEADER_FOOTER_BLOCK_SZ) + +#define RANGE_DATA_START(p_dev) \ + (VL53L5_COMMS_BUFF(p_dev) \ + + p_dev->host_dev.range_data_addr.data_start_offset_bytes) + +#define RANGE_DATA_SIZE(p_dev)\ + p_dev->host_dev.range_data_addr.data_size_bytes + +int32_t vl53l5_dci_decode_range_data( + struct vl53l5_dev_handle_t *p_dev); + +int32_t vl53l5_dci_decode_data( + struct vl53l5_dev_handle_t *p_dev, + uint8_t *buffer, + uint32_t data_size); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_dci_helpers.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_dci_helpers.h new file mode 100644 index 000000000000..66c46d117fe8 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_dci_helpers.h @@ -0,0 +1,91 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L5_DCI_HELPERS_H_ +#define _VL53L5_DCI_HELPERS_H_ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int32_t vl53l5_dci_swap_buffer_byte_ordering( + uint8_t *p_buffer, + uint32_t buffer_count); + +int32_t vl53l5_dci_encode_block_header( + uint8_t *p_buffer, + uint32_t max_buffer_size, + uint8_t type, + uint32_t block_byte_size, + uint16_t idx); + +int32_t vl53l5_dci_decode_block_header( + uint8_t *p_buffer, + uint32_t max_buffer_size, + uint8_t *p_type, + uint32_t *p_block_byte_size, + uint16_t *p_idx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_dci_ranging.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_dci_ranging.h new file mode 100644 index 000000000000..de79f7c45cfa --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_dci_ranging.h @@ -0,0 +1,86 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L5_DCI_RANGING_H_ +#define _VL53L5_DCI_RANGING_H_ + +#include "vl53l5_platform_user_data.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int32_t vl53l5_dci_read_range( + struct vl53l5_dev_handle_t *p_dev); + +int32_t vl53l5_dci_get_device_info( + struct vl53l5_dev_handle_t *p_dev); + +int32_t vl53l5_dci_check_device_info( + struct vl53l5_dev_handle_t *p_dev, + uint8_t last_stream_id, + bool check_stream_count, + bool check_data_present); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_dci_types.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_dci_types.h new file mode 100644 index 000000000000..9910da0f7c18 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_dci_types.h @@ -0,0 +1,92 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L5_DCI_TYPES_H_ +#define _VL53L5_DCI_TYPES_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "dci_luts.h" + +enum block_format_type { + LIST = DCI_BH__P_TYPE__CONSEC_PARAMS_LIST, + ARRAY_ELEMENT_SZ_1 = 0x1, + ARRAY_ELEMENT_SZ_2 = 0x2, + ARRAY_ELEMENT_SZ_3 = 0x3, + ARRAY_ELEMENT_SZ_4 = 0x4, + ARRAY_ELEMENT_SZ_5 = 0x5, + ARRAY_ELEMENT_SZ_6 = 0x6, + ARRAY_ELEMENT_SZ_7 = 0x7, + ARRAY_ELEMENT_SZ_8 = 0x8, + ARRAY_ELEMENT_SZ_9 = 0x9, + ARRAY_ELEMENT_SZ_10 = 0xA, + ARRAY_ELEMENT_SZ_11 = 0xB, + DCI_PAGE_SELECT = 0xC, + START_OF_GROUP = DCI_BH__P_TYPE__GRP_PARAMS_START, + END_OF_GROUP = DCI_BH__P_TYPE__GRP_PARAMS_END, + END_OF_DATA = DCI_BH__P_TYPE__END_OF_DATA +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_dci_utils.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_dci_utils.h new file mode 100644 index 000000000000..ea759250f46c --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_dci_utils.h @@ -0,0 +1,128 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L5_DCI_UTILS_H +#define _VL53L5_DCI_UTILS_H + +#include "vl53l5_platform.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +void vl53l5_encode_int32_t( + int32_t value, + uint16_t count, + uint8_t *pbuffer); + +int32_t vl53l5_decode_int32_t( + uint16_t count, + uint8_t *pbuffer); + +void vl53l5_encode_uint32_t( + uint32_t value, + uint16_t count, + uint8_t *pbuffer); + +uint32_t vl53l5_decode_uint32_t( + uint16_t count, + uint8_t *pbuffer); + +void vl53l5_encode_int16_t( + int16_t value, + uint16_t count, + uint8_t *pbuffer); + +int16_t vl53l5_decode_int16_t( + uint16_t count, + uint8_t *pbuffer); + +void vl53l5_encode_uint16_t( + uint16_t value, + uint16_t count, + uint8_t *pbuffer); + +uint16_t vl53l5_decode_uint16_t( + uint16_t count, + uint8_t *pbuffer); + +void vl53l5_encode_int8_t( + int8_t value, + uint16_t count, + uint8_t *pbuffer); + +int8_t vl53l5_decode_int8_t( + uint16_t count, + uint8_t *pbuffer); + +void vl53l5_encode_uint8_t( + uint8_t value, + uint16_t count, + uint8_t *pbuffer); + +uint8_t vl53l5_decode_uint8_t( + uint16_t count, + uint8_t *pbuffer); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_decode_switch.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_decode_switch.h new file mode 100644 index 000000000000..159be58fbce8 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_decode_switch.h @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_DECODE_SWITCH_H__ +#define __VL53L5_DECODE_SWITCH_H__ + +#include "vl53l5_platform_user_data.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int32_t vl53l5_decode_switch( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_results_decode.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_results_decode.h new file mode 100644 index 000000000000..909902251692 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_results_decode.h @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_RESULTS_DECODE_H__ +#define __VL53L5_RESULTS_DECODE_H__ + +#include "vl53l5_platform_user_data.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int32_t vl53l5_results_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_results_dev_path.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_results_dev_path.h new file mode 100644 index 000000000000..9b45a51f291a --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_results_dev_path.h @@ -0,0 +1,164 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_RESULTS_DEV_PATH_H__ +#define __VL53L5_RESULTS_DEV_PATH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_MAP_RESULTS_DEV(p_dev) \ + ((p_dev)->host_dev.presults_dev) +#define VL53L5_META_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->meta_data +#define VL53L5_COMMON_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->common_data +#define VL53L5_RNG_TIMING_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->rng_timing_data +#define VL53L5_PER_ZONE_RESULTS(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->per_zone_results +#define VL53L5_PER_TGT_RESULTS(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->per_tgt_results +#define VL53L5_REF_TIMING_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->ref_timing_data +#define VL53L5_REF_CHANNEL_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->ref_channel_data +#define VL53L5_REF_TARGET_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->ref_target_data +#define VL53L5_SHARPENER_TARGET_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->sharpener_target_data +#define VL53L5_HIST_ZONE_GRID_POINT_META_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_zone_grid_point_meta_data +#define VL53L5_HIST_ZONE_GRID_POINT_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_zone_grid_point_data +#define VL53L5_HIST_COMMON_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_common_data +#define VL53L5_HIST_TIMING_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_timing_data +#define VL53L5_HIST_CHANNEL_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_channel_data +#define VL53L5_HIST_BIN_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_bin_data +#define VL53L5_HIST_REF_COMMON_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_ref_common_data +#define VL53L5_HIST_REF_TIMING_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_ref_timing_data +#define VL53L5_HIST_REF_CHANNEL_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_ref_channel_data +#define VL53L5_HIST_REF_BIN_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_ref_bin_data +#define VL53L5_HIST_ACC_COMMON_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_acc_common_data +#define VL53L5_HIST_ACC_CHANNEL_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_acc_channel_data +#define VL53L5_HIST_ACC_BIN_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_acc_bin_data +#define VL53L5_ACC_ZONE_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->acc_zone_data +#define VL53L5_REF_MPX_META_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->ref_mpx_meta_data +#define VL53L5_REF_MPX_COMMON_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->ref_mpx_common_data +#define VL53L5_REF_MPX_TIMING_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->ref_mpx_timing_data +#define VL53L5_REF_MPX_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->ref_mpx_data +#define VL53L5_RTN_MPX_META_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->rtn_mpx_meta_data +#define VL53L5_RTN_MPX_COMMON_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->rtn_mpx_common_data +#define VL53L5_RTN_MPX_TIMING_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->rtn_mpx_timing_data +#define VL53L5_RTN_MPX_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->rtn_mpx_data +#define VL53L5_XTALK_MON_META(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->xtalk_mon_meta +#define VL53L5_XTALK_MON_ZONES_MAX(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->xtalk_mon_zones_max +#define VL53L5_XTALK_MON_ZONES_ACTUAL(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->xtalk_mon_zones_actual +#define VL53L5_XTALK_MON_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->xtalk_mon_data +#define VL53L5_VHV_RESULT_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->vhv_result_data +#define VL53L5_VHV_SEARCH_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->vhv_search_data +#define VL53L5_CAL_REF_SPAD_SEARCH_META_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->cal_ref_spad_search_meta_data +#define VL53L5_CAL_REF_SPAD_SEARCH_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->cal_ref_spad_search_data +#define VL53L5_REF_ARRAY_META_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->ref_array_meta_data +#define VL53L5_RTN_ARRAY_META_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->rtn_array_meta_data +#define VL53L5_REF_ARRAY_SPAD_EN(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->ref_array_spad_en +#define VL53L5_RTN_ARRAY_SPAD_EN(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->rtn_array_spad_en +#define VL53L5_ZONE_THRESH_STATUS(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->zone_thresh_status +#define VL53L5_DYN_XTALK_OP_PERSISTENT_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->dyn_xtalk_op_persistent_data + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_results_enum_type.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_results_enum_type.h new file mode 100644 index 000000000000..73e9cd5ba97a --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_results_enum_type.h @@ -0,0 +1,133 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_RESULTS_ENUM_TYPE_H__ +#define __VL53L5_RESULTS_ENUM_TYPE_H__ + +#include "vl53l5_dci_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_BUF_META_DATA_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_RNG_COMMON_DATA_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_RNG_TIMING_DATA_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_RPZD_AMB_RATE_KCPS_PER_SPAD_TYPE \ + ((enum block_format_type) 0x4) +#define VL53L5_RPZD_RNG_EFFECTIVE_SPAD_COUNT_TYPE \ + ((enum block_format_type) 0x4) +#define VL53L5_RPZD_AMB_DMAX_MM_TYPE \ + ((enum block_format_type) 0x2) +#define VL53L5_RPZD_SILICON_TEMP_DEGC_START_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_RPZD_SILICON_TEMP_DEGC_END_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_RPZD_RNG_NO_OF_TARGETS_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_RPZD_RNG_ZONE_ID_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_RPZD_RNG_SEQUENCE_IDX_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_RPTD_PEAK_RATE_KCPS_PER_SPAD_TYPE \ + ((enum block_format_type) 0x4) +#define VL53L5_RPTD_MEDIAN_PHASE_TYPE \ + ((enum block_format_type) 0x4) +#define VL53L5_RPTD_RATE_SIGMA_KCPS_PER_SPAD_TYPE \ + ((enum block_format_type) 0x4) +#define VL53L5_RPTD_TARGET_ZSCORE_TYPE \ + ((enum block_format_type) 0x2) +#define VL53L5_RPTD_RANGE_SIGMA_MM_TYPE \ + ((enum block_format_type) 0x2) +#define VL53L5_RPTD_MEDIAN_RANGE_MM_TYPE \ + ((enum block_format_type) 0x2) +#define VL53L5_RPTD_START_RANGE_MM_TYPE \ + ((enum block_format_type) 0x2) +#define VL53L5_RPTD_END_RANGE_MM_TYPE \ + ((enum block_format_type) 0x2) +#define VL53L5_RPTD_MIN_RANGE_DELTA_MM_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_RPTD_MAX_RANGE_DELTA_MM_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_RPTD_TARGET_REFLECTANCE_EST_PC_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_RPTD_TARGET_STATUS_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_REF_CHANNEL_DATA_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_REF_TARGET_DATA_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_STD_SHARPENER_GROUP_INDEX_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_STD_SHARPENER_CONFIDENCE_TYPE \ + ((enum block_format_type) 0x1) + +#define VL53L5_ZTSA_ZONE_THRESH_STATUS_BYTES_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_DYN_XTALK_PERSISTENT_DATA_TYPE \ + ((enum block_format_type) 0x0) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_results_map.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_results_map.h new file mode 100644 index 000000000000..0f8aed390634 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_results_map.h @@ -0,0 +1,143 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_RESULTS_MAP_H__ +#define __VL53L5_RESULTS_MAP_H__ + +#include "dci_defs.h" +#include "dci_structs.h" +#include "dci_union_structs.h" +#include "dci_ui_structs.h" +#include "common_datatype_structs.h" +#include "common_datatype_defs.h" +#include "packing_structs.h" +#include "vl53l5_types.h" +#include "dyn_xtalk_structs.h" +#include "dyn_xtalk_defs.h" +#include "ic_checkers_structs.h" +#include "ic_checkers_defs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_CGHBD_HIST_BIN_EVENTS_PACKED_ARRAY_SZ \ + ((uint16_t) 6912) + +struct vl53l5_range_results_t { +#ifdef VL53L5_META_DATA_ON + + struct vl53l5_range_meta_data_t meta_data; +#endif + +#ifdef VL53L5_COMMON_DATA_ON + + struct vl53l5_range_common_data_t common_data; +#endif + +#ifdef VL53L5_RNG_TIMING_DATA_ON + + struct vl53l5_range_timing_data_t rng_timing_data; +#endif + +#ifdef VL53L5_PER_ZONE_RESULTS_ON + + struct vl53l5_range_per_zone_results_t per_zone_results; +#endif + +#ifdef VL53L5_PER_TGT_RESULTS_ON + + struct vl53l5_range_per_tgt_results_t per_tgt_results; +#endif + +#ifdef VL53L5_REF_TIMING_DATA_ON + + struct vl53l5_range_timing_data_t ref_timing_data; +#endif + +#ifdef VL53L5_REF_CHANNEL_DATA_ON + + struct vl53l5_ref_channel_data_t ref_channel_data; +#endif + +#ifdef VL53L5_REF_TARGET_DATA_ON + + struct vl53l5_ref_target_data_t ref_target_data; +#endif + +#ifdef VL53L5_SHARPENER_TARGET_DATA_ON + struct vl53l5_sharpener_target_data_t sharpener_target_data; +#endif + +#ifdef VL53L5_ZONE_THRESH_STATUS_ON + struct vl53l5_zone_thresh_status_array_t zone_thresh_status; +#endif + +#ifdef VL53L5_DYN_XTALK_OP_PERSISTENT_DATA_ON + + struct vl53l5_dyn_xtalk_persistent_data_t dyn_xtalk_op_persistent_data; +#endif + +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_results_map_bh.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_results_map_bh.h new file mode 100644 index 000000000000..0df665dd3a4b --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_results_map_bh.h @@ -0,0 +1,133 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_RESULTS_MAP_BH_H__ +#define __VL53L5_RESULTS_MAP_BH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_RNG_META_DATA_BH \ + ((uint32_t) 0x59d800c0U) +#define VL53L5_RNG_COMMON_DATA_BH \ + ((uint32_t) 0x59e40080U) +#define VL53L5_RNG_TIMING_DATA_BH \ + ((uint32_t) 0x59ec00c0U) +#define VL53L5_RNG_PER_ZONE_DATA_AMB_RATE_KCPS_PER_SPAD_BH \ + ((uint32_t) 0x59f80444U) +#define VL53L5_RNG_PER_ZONE_DATA_RNG__EFFECTIVE_SPAD_COUNT_BH \ + ((uint32_t) 0x5b080444U) +#define VL53L5_RNG_PER_ZONE_DATA_AMB_DMAX_MM_BH \ + ((uint32_t) 0x5c180442U) +#define VL53L5_RNG_PER_ZONE_DATA_SILICON_TEMP_DEGC__START_BH \ + ((uint32_t) 0x5ca00441U) +#define VL53L5_RNG_PER_ZONE_DATA_SILICON_TEMP_DEGC__END_BH \ + ((uint32_t) 0x5ce40441U) +#define VL53L5_RNG_PER_ZONE_DATA_RNG__NO_OF_TARGETS_BH \ + ((uint32_t) 0x5d280441U) +#define VL53L5_RNG_PER_ZONE_DATA_RNG__ZONE_ID_BH \ + ((uint32_t) 0x5d6c0441U) +#define VL53L5_RNG_PER_ZONE_DATA_RNG__SEQUENCE_IDX_BH \ + ((uint32_t) 0x5db00441U) +#define VL53L5_RNG_PER_TARGET_DATA_PEAK_RATE_KCPS_PER_SPAD_BH \ + ((uint32_t) 0x5df41104U) +#define VL53L5_RNG_PER_TARGET_DATA_MEDIAN_PHASE_BH \ + ((uint32_t) 0x62341104U) +#define VL53L5_RNG_PER_TARGET_DATA_RATE_SIGMA_KCPS_PER_SPAD_BH \ + ((uint32_t) 0x66741104U) +#define VL53L5_RNG_PER_TARGET_DATA_TARGET_ZSCORE_BH \ + ((uint32_t) 0x6ab41102U) +#define VL53L5_RNG_PER_TARGET_DATA_RANGE_SIGMA_MM_BH \ + ((uint32_t) 0x6cd41102U) +#define VL53L5_RNG_PER_TARGET_DATA_MEDIAN_RANGE_MM_BH \ + ((uint32_t) 0x6ef41102U) +#define VL53L5_RNG_PER_TARGET_DATA_START_RANGE_MM_BH \ + ((uint32_t) 0x71141102U) +#define VL53L5_RNG_PER_TARGET_DATA_END_RANGE_MM_BH \ + ((uint32_t) 0x73341102U) +#define VL53L5_RNG_PER_TARGET_DATA_MIN_RANGE_DELTA_MM_BH \ + ((uint32_t) 0x75541101U) +#define VL53L5_RNG_PER_TARGET_DATA_MAX_RANGE_DELTA_MM_BH \ + ((uint32_t) 0x76641101U) +#define VL53L5_RNG_PER_TARGET_DATA_TARGET_REFLECTANCE_EST_PC_BH \ + ((uint32_t) 0x77741101U) +#define VL53L5_RNG_PER_TARGET_DATA_TARGET_STATUS_BH \ + ((uint32_t) 0x78841101U) +#define VL53L5_REF_TIMING_DATA_BH \ + ((uint32_t) 0x799400c0U) +#define VL53L5_REF_CHANNEL_DATA_BH \ + ((uint32_t) 0x79a00100U) +#define VL53L5_REF_TARGET_DATA_BH \ + ((uint32_t) 0x79b001c0U) +#define VL53L5_SHARPENER_TARGET_DATA_SHARPENER__GROUP_INDEX_BH \ + ((uint32_t) 0x79cc1101U) +#define VL53L5_SHARPENER_TARGET_DATA_SHARPENER__CONFIDENCE_BH \ + ((uint32_t) 0x7adc1101U) + +#define VL53L5_ZONE_THRESH_STATUS_ZONE_THRESH_STATUS_BYTES_BH \ + ((uint32_t) 0xafb80081U) +#define VL53L5_DYN_XTALK_OP_PERSISTENT_DATA_BH \ + ((uint32_t) 0xafc001c0U) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_results_map_idx.h b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_results_map_idx.h new file mode 100644 index 000000000000..75b5cb9c946b --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/inc/vl53l5_results_map_idx.h @@ -0,0 +1,171 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_RESULTS_MAP_IDX_H__ +#define __VL53L5_RESULTS_MAP_IDX_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define DEV_RNG_META_DATA_IDX \ + ((uint16_t) 0x59d8) + +#define DEV_RNG_COMMON_DATA_IDX \ + ((uint16_t) 0x59e4) + +#define DEV_RNG_TIMING_DATA_IDX \ + ((uint16_t) 0x59ec) + +#define DEV_RNG_PER_ZONE_DATA_AMB_RATE_KCPS_PER_SPAD_IDX \ + ((uint16_t) 0x59f8) +#define DEV_RNG_PER_ZONE_DATA_IDX \ + ((uint16_t) 0x59f8) + +#define DEV_RNG_PER_ZONE_DATA_RNG__EFFECTIVE_SPAD_COUNT_IDX \ + ((uint16_t) 0x5b08) + +#define DEV_RNG_PER_ZONE_DATA_AMB_DMAX_MM_IDX \ + ((uint16_t) 0x5c18) + +#define DEV_RNG_PER_ZONE_DATA_SILICON_TEMP_DEGC__START_IDX \ + ((uint16_t) 0x5ca0) + +#define DEV_RNG_PER_ZONE_DATA_SILICON_TEMP_DEGC__END_IDX \ + ((uint16_t) 0x5ce4) + +#define DEV_RNG_PER_ZONE_DATA_RNG__NO_OF_TARGETS_IDX \ + ((uint16_t) 0x5d28) + +#define DEV_RNG_PER_ZONE_DATA_RNG__ZONE_ID_IDX \ + ((uint16_t) 0x5d6c) + +#define DEV_RNG_PER_ZONE_DATA_RNG__SEQUENCE_IDX_IDX \ + ((uint16_t) 0x5db0) + +#define DEV_RNG_PER_TARGET_DATA_PEAK_RATE_KCPS_PER_SPAD_IDX \ + ((uint16_t) 0x5df4) +#define DEV_RNG_PER_TARGET_DATA_IDX \ + ((uint16_t) 0x5df4) + +#define DEV_RNG_PER_TARGET_DATA_MEDIAN_PHASE_IDX \ + ((uint16_t) 0x6234) + +#define DEV_RNG_PER_TARGET_DATA_RATE_SIGMA_KCPS_PER_SPAD_IDX \ + ((uint16_t) 0x6674) + +#define DEV_RNG_PER_TARGET_DATA_TARGET_ZSCORE_IDX \ + ((uint16_t) 0x6ab4) + +#define DEV_RNG_PER_TARGET_DATA_RANGE_SIGMA_MM_IDX \ + ((uint16_t) 0x6cd4) + +#define DEV_RNG_PER_TARGET_DATA_MEDIAN_RANGE_MM_IDX \ + ((uint16_t) 0x6ef4) + +#define DEV_RNG_PER_TARGET_DATA_START_RANGE_MM_IDX \ + ((uint16_t) 0x7114) + +#define DEV_RNG_PER_TARGET_DATA_END_RANGE_MM_IDX \ + ((uint16_t) 0x7334) + +#define DEV_RNG_PER_TARGET_DATA_MIN_RANGE_DELTA_MM_IDX \ + ((uint16_t) 0x7554) + +#define DEV_RNG_PER_TARGET_DATA_MAX_RANGE_DELTA_MM_IDX \ + ((uint16_t) 0x7664) + +#define DEV_RNG_PER_TARGET_DATA_TARGET_REFLECTANCE_EST_PC_IDX \ + ((uint16_t) 0x7774) + +#define DEV_RNG_PER_TARGET_DATA_TARGET_STATUS_IDX \ + ((uint16_t) 0x7884) + +#define DEV_REF_TIMING_DATA_IDX \ + ((uint16_t) 0x7994) + +#define DEV_REF_CHANNEL_DATA_IDX \ + ((uint16_t) 0x79a0) + +#define DEV_REF_TARGET_DATA_IDX \ + ((uint16_t) 0x79b0) + +#define DEV_SHARPENER_TARGET_DATA_SHARPENER__GROUP_INDEX_IDX \ + ((uint16_t) 0x79cc) +#define DEV_SHARPENER_TARGET_DATA_IDX \ + ((uint16_t) 0x79cc) + +#define DEV_SHARPENER_TARGET_DATA_SHARPENER__CONFIDENCE_IDX \ + ((uint16_t) 0x7adc) + +#define DEV_ZONE_THRESH_STATUS_ZONE_THRESH_STATUS_BYTES_IDX \ + ((uint16_t) 0xafb8) +#define DEV_ZONE_THRESH_STATUS_IDX \ + ((uint16_t) 0xafb8) + +#define DEV_DYN_XTALK_OP_PERSISTENT_DATA_IDX \ + ((uint16_t) 0xafc0) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/dci/src/page_map_switch.c b/drivers/sensors/vl53l8/bare_driver/dci/src/page_map_switch.c new file mode 100644 index 000000000000..dbbbafd30519 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/src/page_map_switch.c @@ -0,0 +1,113 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "page_map_switch.h" +#include "page_map_defs.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform_log.h" +#include "vl53l5_decode_switch.h" +#ifdef VL53L5_PATCH_DATA_ENABLED +#include "vl53l5_tcpm_patch_0_decode_switch.h" +#include "vl53l5_tcpm_patch_1_decode_switch.h" +#endif + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START( \ + VL53L5_TRACE_MODULE_DCI_DECODE, fmt, ##__VA_ARGS__) + +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) + +#define LOG_FUNCTION_END_FMT(status, ...) \ + _LOG_FUNCTION_END_FMT( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) +int32_t dci_page_map_switch( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev, + uint16_t page_index) +{ + int32_t status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + switch (page_index) { + case PAGE_DEF__PAGE_VL53L5_DEV: + status = vl53l5_decode_switch(idx, buffer_size, buffer, p_dev); + break; +#ifdef VL53L5_PATCH_DATA_ENABLED + case PAGE_DEF__PAGE_PATCH_0_DEV: + status = vl53l5_tcpm_patch_0_decode_switch(idx, buffer_size, buffer, p_dev); + break; + case PAGE_DEF__PAGE_PATCH_1_DEV: + status = vl53l5_tcpm_patch_1_decode_switch(idx, buffer_size, buffer, p_dev); + break; +#endif + + default: + status = VL53L5_INVALID_PAGE_ERROR; + break; + } + + LOG_FUNCTION_END(status); + return status; + +} diff --git a/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_calibration_decode.c b/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_calibration_decode.c new file mode 100644 index 000000000000..cd2f12a89e18 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_calibration_decode.c @@ -0,0 +1,1178 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "dci_size.h" +#include "dci_ui_size.h" +#include "cal_size.h" +#include "padding_size.h" +#include "vl53l5_calibration_decode.h" +#include "vl53l5_calibration_enum_type.h" +#include "vl53l5_calibration_map_idx.h" +#include "vl53l5_calibration_dev_path.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform_log.h" + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START( \ + VL53L5_TRACE_MODULE_DCI_DECODE, fmt, ##__VA_ARGS__) + +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) + +#define LOG_FUNCTION_END_FMT(status, ...) \ + _LOG_FUNCTION_END_FMT( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) +#ifdef VL53L5_CALIBRATION_DECODE_ON +static int32_t _decode_cal_grp_ref_spad_info( + uint32_t buffer_size, + uint8_t *buffer, + struct cal_grp__ref_spad_info_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_CAL_GRP_REF_SPAD_INFO_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->cal__ref_spad__offset = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__ref_spad__count = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__ref_spad__count_10x = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__ref_spad__count_100x = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__ref_spad__left_right_sel = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__ref_spad__status = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__ref_spad_info__pad_0 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__ref_spad_info__pad_1 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_CALIBRATION_DECODE_ON +static int32_t _decode_cal_grp_optical_centre_data( + uint32_t buffer_size, + uint8_t *buffer, + struct cal_grp__optical_centre_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_CAL_GRP_OPTICAL_CENTRE_DATA_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->cal__optical_centre__x = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__optical_centre__y = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__optical_centre__pad_0 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__optical_centre__pad_1 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_CALIBRATION_DECODE_ON +static int32_t _decode_cal_grp_grid_meta( + uint32_t buffer_size, + uint8_t *buffer, + struct cal_grp__grid_meta_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_CAL_GRP_GRID_META_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->cal__grid_meta__distance_mm = + vl53l5_decode_int16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->cal__grid_meta__reflectance_pc = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->cal__grid_meta__silicon_temp_degc = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__grid_meta__cols = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__grid_meta__rows = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__grid_meta__x_offset_spads = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__grid_meta__y_offset_spads = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__grid_meta__x_pitch_spads = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__grid_meta__y_pitch_spads = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__grid_meta__avg_count = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _decode_cal_grp_phase_stats( + uint32_t buffer_size, + uint8_t *buffer, + struct cal_grp__phase_stats_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_CAL_GRP_PHASE_STATS_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->cal__stats__avg_phase__ref = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->cal__stats__avg_phase__rtn = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->cal__stats__avg_phase__flex = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->cal__stats__avg_count__ref = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->cal__stats__avg_count__rtn = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->cal__stats__avg_count__flex = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->cal__stats__spare_0 = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _decode_cal_grp_temperature_stats( + uint32_t buffer_size, + uint8_t *buffer, + struct cal_grp__temperature_stats_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_CAL_GRP_TEMPERATURE_STATS_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->cal__stats__avg_temp_degc__start__ref = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__stats__avg_temp_degc__end__ref = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__stats__avg_temp_degc__start__rtn = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__stats__avg_temp_degc__end__rtn = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _decode_cggdrkps_cal_grid_data_rate_kcps_per_spad( + uint32_t buffer_size, + uint8_t *buffer, + struct cal_grp__grid_data__rate_kcps_per_spad_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_CGGDRKPS_CAL_GRID_DATA_RATE_KCPS_PER_SPAD_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->cal__grid_data__rate_kcps_per_spad[i] = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _decode_cggdesc_cal_grid_data_effective_spad_count( + uint32_t buffer_size, + uint8_t *buffer, + struct cal_grp__grid_data__effective_spad_count_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_CGGDESC_CAL_GRID_DATA_EFFECTIVE_SPAD_COUNT_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->cal__grid_data_effective_spad_count[i] = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _decode_cal_grp_grid_data_range_mm_cal_grid_data_range_mm( + uint32_t buffer_size, + uint8_t *buffer, + struct cal_grp__grid_data__range_mm_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_CGGDRM_CAL_GRID_DATA_RANGE_MM_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->cal__grid_data__range_mm[i] = + vl53l5_decode_int16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _decode_cal_grp_status( + uint32_t buffer_size, + uint8_t *buffer, + struct cal_grp__status_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_CAL_GRP_STATUS_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->cal__status__type = + vl53l5_decode_int32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->cal__status__stage_id = + vl53l5_decode_int32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->cal__target__idx = + vl53l5_decode_int32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->cal__zone_id = + vl53l5_decode_int32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _decode_cal_grp_xtalk_shape_meta( + uint32_t buffer_size, + uint8_t *buffer, + struct cal_grp__xtalk_shape_meta_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_CAL_GRP_XTALK_SHAPE_META_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->cal__xtalk_shape__median_phase = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->cal__xtalk_shape__avg_count = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->cal__xtalk_shape__no_of_bins = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__xtalk_shape__normalisation_power = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__xtalk_shape__silicon_temp_degc = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__xtalk_shape__spare_0 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__xtalk_shape__spare_1 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__xtalk_shape__spare_2 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _decode_cal_grp_xtalk_shape_data_cal_xtalk_shape_bin_data( + uint32_t buffer_size, + uint8_t *buffer, + struct cal_grp__xtalk_shape_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_CGXSD_CAL_XTALK_SHAPE_BIN_DATA_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->cal__xtalk_shape__bin_data[i] = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _decode_cal_grp_xtalk_mon_meta_data( + uint32_t buffer_size, + uint8_t *buffer, + struct cal_grp__xtalk_mon__meta_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_CAL_GRP_XTALK_MON_META_DATA_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->cal__xmon__max_zones = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__xmon__no_of_zones = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__xmon__zone_idx = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->cal__xmon__silicon_temp_degc = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _decode_cal_grp_xtalk_mon_zones_cal_xmon_zone_x_off( + uint32_t buffer_size, + uint8_t *buffer, + struct cal_grp__xtalk_mon__zones_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_CGXMZ_CAL_XMON_ZONE_X_OFF_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->cal__xmon__zone__x_off[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _decode_cal_grp_xtalk_mon_zones_cal_xmon_zone_y_off( + uint32_t buffer_size, + uint8_t *buffer, + struct cal_grp__xtalk_mon__zones_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_CGXMZ_CAL_XMON_ZONE_Y_OFF_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->cal__xmon__zone__y_off[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _decode_cal_grp_xtalk_mon_zones_cal_xmon_zone_width( + uint32_t buffer_size, + uint8_t *buffer, + struct cal_grp__xtalk_mon__zones_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_CGXMZ_CAL_XMON_ZONE_WIDTH_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->cal__xmon__zone__width[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _decode_cal_grp_xtalk_mon_zones_cal_xmon_zone_height( + uint32_t buffer_size, + uint8_t *buffer, + struct cal_grp__xtalk_mon__zones_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_CGXMZ_CAL_XMON_ZONE_HEIGHT_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->cal__xmon__zone__height[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _decode_cal_grp_xtalk_mon_data_cal_xmon_zone_rate_kcps_spad( + uint32_t buffer_size, + uint8_t *buffer, + struct cal_grp__xtalk_mon__data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_CGXMD_CAL_XMON_ZONE_RATE_KCPS_SPAD_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->cal__xmon__zone__rate_kcps_spad[i] = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _decode_cal_grp_xtalk_mon_data_cal_xmon_zone_avg_count( + uint32_t buffer_size, + uint8_t *buffer, + struct cal_grp__xtalk_mon__data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_CGXMD_CAL_XMON_ZONE_AVG_COUNT_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->cal__xmon__zone__avg_count[i] = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _decode_cggdsf_cal_grid_data_scale_factor( + uint32_t buffer_size, + uint8_t *buffer, + struct cal_grp__grid_data__scale_factor_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_CGGDSF_CAL_GRID_DATA_SCALE_FACTOR_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->cal__grid_data__scale_factor[i] = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +int32_t vl53l5_calibration_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + +#if !defined(VL53L5_CALIBRATION_DECODE_ON) + (void)buffer_size; + (void)buffer; + (void)p_dev; +#endif + + switch (idx) { +#ifdef VL53L5_CALIBRATION_DECODE_ON + case DEV_CAL_REF_SPAD_INFO_IDX: + status = + _decode_cal_grp_ref_spad_info( + buffer_size, + buffer, + &VL53L5_CAL_REF_SPAD_INFO(p_dev)); + break; +#endif + +#ifdef VL53L5_CALIBRATION_DECODE_ON + case DEV_CAL_OPTICAL_CENTRE_IDX: + status = + _decode_cal_grp_optical_centre_data( + buffer_size, + buffer, + &VL53L5_CAL_OPTICAL_CENTRE(p_dev)); + break; +#endif + +#ifdef VL53L5_CALIBRATION_DECODE_ON + case DEV_POFFSET_GRID_META_IDX: + status = + _decode_cal_grp_grid_meta( + buffer_size, + buffer, + &VL53L5_POFFSET_GRID_META(p_dev)); + break; + case DEV_POFFSET_PHASE_STATS_IDX: + status = + _decode_cal_grp_phase_stats( + buffer_size, + buffer, + &VL53L5_POFFSET_PHASE_STATS(p_dev)); + break; + case DEV_POFFSET_TEMPERATURE_STATS_IDX: + status = + _decode_cal_grp_temperature_stats( + buffer_size, + buffer, + &VL53L5_POFFSET_TEMPERATURE_STATS(p_dev)); + break; + case DEV_POFFSET_GRID_RATE_CAL__GRID_DATA__RATE_KCPS_PER_SPAD_IDX: + status = + _decode_cggdrkps_cal_grid_data_rate_kcps_per_spad( + buffer_size, + buffer, + &VL53L5_POFFSET_GRID_RATE(p_dev)); + break; + case DEV_POFFSET_GRID_SPADS_CAL__GRID_DATA_EFFECTIVE_SPAD_COUNT_IDX: + status = + _decode_cggdesc_cal_grid_data_effective_spad_count( + buffer_size, + buffer, + &VL53L5_POFFSET_GRID_SPADS(p_dev)); + break; + case DEV_POFFSET_GRID_OFFSET_CAL__GRID_DATA__RANGE_MM_IDX: + status = + _decode_cal_grp_grid_data_range_mm_cal_grid_data_range_mm( + buffer_size, + buffer, + &VL53L5_POFFSET_GRID_OFFSET(p_dev)); + break; + case DEV_POFFSET_ERROR_STATUS_IDX: + status = + _decode_cal_grp_status( + buffer_size, + buffer, + &VL53L5_POFFSET_ERROR_STATUS(p_dev)); + break; + case DEV_POFFSET_WARNING_STATUS_IDX: + status = + _decode_cal_grp_status( + buffer_size, + buffer, + &VL53L5_POFFSET_WARNING_STATUS(p_dev)); + break; +#endif + +#ifdef VL53L5_CALIBRATION_DECODE_ON + case DEV_PXTALK_GRID_META_IDX: + status = + _decode_cal_grp_grid_meta( + buffer_size, + buffer, + &VL53L5_PXTALK_GRID_META(p_dev)); + break; + case DEV_PXTALK_PHASE_STATS_IDX: + status = + _decode_cal_grp_phase_stats( + buffer_size, + buffer, + &VL53L5_PXTALK_PHASE_STATS(p_dev)); + break; + case DEV_PXTALK_TEMPERATURE_STATS_IDX: + status = + _decode_cal_grp_temperature_stats( + buffer_size, + buffer, + &VL53L5_PXTALK_TEMPERATURE_STATS(p_dev)); + break; + case DEV_PXTALK_GRID_RATE_CAL__GRID_DATA__RATE_KCPS_PER_SPAD_IDX: + status = + _decode_cggdrkps_cal_grid_data_rate_kcps_per_spad( + buffer_size, + buffer, + &VL53L5_PXTALK_GRID_RATE(p_dev)); + break; + case DEV_PXTALK_GRID_SPADS_CAL__GRID_DATA_EFFECTIVE_SPAD_COUNT_IDX: + status = + _decode_cggdesc_cal_grid_data_effective_spad_count( + buffer_size, + buffer, + &VL53L5_PXTALK_GRID_SPADS(p_dev)); + break; + case DEV_PXTALK_ERROR_STATUS_IDX: + status = + _decode_cal_grp_status( + buffer_size, + buffer, + &VL53L5_PXTALK_ERROR_STATUS(p_dev)); + break; + case DEV_PXTALK_WARNING_STATUS_IDX: + status = + _decode_cal_grp_status( + buffer_size, + buffer, + &VL53L5_PXTALK_WARNING_STATUS(p_dev)); + break; +#endif + +#ifdef VL53L5_CALIBRATION_DECODE_ON + case DEV_PXTALK_SHAPE_META_IDX: + status = + _decode_cal_grp_xtalk_shape_meta( + buffer_size, + buffer, + &VL53L5_PXTALK_SHAPE_META(p_dev)); + break; + case DEV_PXTALK_SHAPE_DATA_CAL__XTALK_SHAPE__BIN_DATA_IDX: + status = + _decode_cal_grp_xtalk_shape_data_cal_xtalk_shape_bin_data( + buffer_size, + buffer, + &VL53L5_PXTALK_SHAPE_DATA(p_dev)); + break; + case DEV_PXTALK_MON_META_IDX: + status = + _decode_cal_grp_xtalk_mon_meta_data( + buffer_size, + buffer, + &VL53L5_PXTALK_MON_META(p_dev)); + break; + case DEV_PXTALK_MON_ZONES_CAL__XMON__ZONE__X_OFF_IDX: + status = + _decode_cal_grp_xtalk_mon_zones_cal_xmon_zone_x_off( + buffer_size, + buffer, + &VL53L5_PXTALK_MON_ZONES(p_dev)); + break; + case DEV_PXTALK_MON_ZONES_CAL__XMON__ZONE__Y_OFF_IDX: + status = + _decode_cal_grp_xtalk_mon_zones_cal_xmon_zone_y_off( + buffer_size, + buffer, + &VL53L5_PXTALK_MON_ZONES(p_dev)); + break; + case DEV_PXTALK_MON_ZONES_CAL__XMON__ZONE__WIDTH_IDX: + status = + _decode_cal_grp_xtalk_mon_zones_cal_xmon_zone_width( + buffer_size, + buffer, + &VL53L5_PXTALK_MON_ZONES(p_dev)); + break; + case DEV_PXTALK_MON_ZONES_CAL__XMON__ZONE__HEIGHT_IDX: + status = + _decode_cal_grp_xtalk_mon_zones_cal_xmon_zone_height( + buffer_size, + buffer, + &VL53L5_PXTALK_MON_ZONES(p_dev)); + break; + case DEV_PXTALK_MON_DATA_CAL__XMON__ZONE__RATE_KCPS_SPAD_IDX: + status = + _decode_cal_grp_xtalk_mon_data_cal_xmon_zone_rate_kcps_spad( + buffer_size, + buffer, + &VL53L5_PXTALK_MON_DATA(p_dev)); + break; + case DEV_PXTALK_MON_DATA_CAL__XMON__ZONE__AVG_COUNT_IDX: + status = + _decode_cal_grp_xtalk_mon_data_cal_xmon_zone_avg_count( + buffer_size, + buffer, + &VL53L5_PXTALK_MON_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_CALIBRATION_DECODE_ON + case DEV_PRAD2PERP_GRID_4X4_META_IDX: + status = + _decode_cal_grp_grid_meta( + buffer_size, + buffer, + &VL53L5_PRAD2PERP_GRID_4X4_META(p_dev)); + break; + case DEV_PRAD2PERP_GRID_4X4_DATA_CAL__GRID_DATA__SCALE_FACTOR_IDX: + status = + _decode_cggdsf_cal_grid_data_scale_factor( + buffer_size, + buffer, + &VL53L5_PRAD2PERP_GRID_4X4_DATA(p_dev)); + break; + case DEV_PRAD2PERP_GRID_8X8_META_IDX: + status = + _decode_cal_grp_grid_meta( + buffer_size, + buffer, + &VL53L5_PRAD2PERP_GRID_8X8_META(p_dev)); + break; + case DEV_PRAD2PERP_GRID_8X8_DATA_CAL__GRID_DATA__SCALE_FACTOR_IDX: + status = + _decode_cggdsf_cal_grid_data_scale_factor( + buffer_size, + buffer, + &VL53L5_PRAD2PERP_GRID_8X8_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_CALIBRATION_DECODE_ON + case DEV_DYN_XTALK__LAST_VALID_XMON_DATA_CAL__XMON__ZONE__RATE_KCPS_SPAD_IDX: + status = + _decode_cal_grp_xtalk_mon_data_cal_xmon_zone_rate_kcps_spad( + buffer_size, + buffer, + &VL53L5_DYN_XTALK__LAST_VALID_XMON_DATA(p_dev)); + break; + case DEV_DYN_XTALK__LAST_VALID_XMON_DATA_CAL__XMON__ZONE__AVG_COUNT_IDX: + status = + _decode_cal_grp_xtalk_mon_data_cal_xmon_zone_avg_count( + buffer_size, + buffer, + &VL53L5_DYN_XTALK__LAST_VALID_XMON_DATA(p_dev)); + break; +#endif + + default: + status = VL53L5_INVALID_IDX_DECODE_CMD_ERROR; + break; + } + + LOG_FUNCTION_END(status); + return status; + +} diff --git a/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_core_decode.c b/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_core_decode.c new file mode 100644 index 000000000000..ca81294f5e9e --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_core_decode.c @@ -0,0 +1,290 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "dci_size.h" +#include "dci_ui_size.h" +#include "vl53l5_core_decode.h" +#include "vl53l5_core_enum_type.h" +#include "vl53l5_core_map_idx.h" +#include "vl53l5_core_dev_path.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform_log.h" +#include "common_datatype_size.h" + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START( \ + VL53L5_TRACE_MODULE_DCI_DECODE, fmt, ##__VA_ARGS__) + +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) + +#define LOG_FUNCTION_END_FMT(status, ...) \ + _LOG_FUNCTION_END_FMT( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) +#ifdef VL53L5_CALIBRATION_DECODE_ON +static int32_t _decode_dci_grp_map_version( + uint32_t buffer_size, + uint8_t *buffer, + struct dci_grp__map_version_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_MAP_VERSION_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->map__major = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->map__minor = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_SILICON_TEMP_DATA_ON +static int32_t _decode_dci_grp_silicon_temperature_data( + uint32_t buffer_size, + uint8_t *buffer, + struct dci_grp__silicon_temperature_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_SILICON_TEMPERATURE_DATA_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->silicon_temp_degc__start = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->silicon_temp_degc__end = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->silicon_temp__pad_0 = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->silicon_temp__pad_1 = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_ZONE_CFG_ON +static int32_t _decode_dci_grp_zone_cfg( + uint32_t buffer_size, + uint8_t *buffer, + struct dci_grp__zone_cfg_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_ZONE_CFG_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->zone__grid_cols = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->zone__grid_rows = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->zone__grid_x_ll = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->zone__grid_y_ll = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->zone__grid_x_pitch = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->zone__grid_y_pitch = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->zone__pad_0 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->zone__pad_1 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +int32_t vl53l5_core_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + +#if !defined(VL53L5_CALIBRATION_DECODE_ON) && \ + !defined(VL53L5_SILICON_TEMP_DATA_ON) && \ + !defined(VL53L5_ZONE_CFG_ON) + (void)buffer_size; + (void)buffer; + (void)p_dev; +#endif + + switch (idx) { +#ifdef VL53L5_CALIBRATION_DECODE_ON + case DEV_MAP_VERSION_IDX: + status = + _decode_dci_grp_map_version( + buffer_size, + buffer, + &VL53L5_MAP_VERSION(p_dev)); + break; +#endif + +#ifdef VL53L5_SILICON_TEMP_DATA_ON + case DEV_SILICON_TEMP_DATA_IDX: + status = + _decode_dci_grp_silicon_temperature_data( + buffer_size, + buffer, + &VL53L5_SILICON_TEMP_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_ZONE_CFG_ON + case DEV_ZONE_CFG_IDX: + status = + _decode_dci_grp_zone_cfg( + buffer_size, + buffer, + &VL53L5_ZONE_CFG(p_dev)); + break; +#endif + + default: + status = VL53L5_INVALID_IDX_DECODE_CMD_ERROR; + break; + } + + LOG_FUNCTION_END(status); + return status; + +} diff --git a/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_dci_core.c b/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_dci_core.c new file mode 100644 index 000000000000..1e000f2259ff --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_dci_core.c @@ -0,0 +1,446 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_dci_core.h" +#include "vl53l5_dci_helpers.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_driver_dev_path.h" +#include "vl53l5_globals.h" +#include "vl53l5_platform.h" +#include "vl53l5_platform_log.h" +#include "vl53l5_platform_user_config.h" +#include "vl53l5_error_codes.h" +#include "dci_luts.h" + +#define trace_print(level, ...) \ + _LOG_TRACE_PRINT(VL53L5_TRACE_MODULE_DCI, \ + level, VL53L5_TRACE_FUNCTION_ALL, ##__VA_ARGS__) + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START(VL53L5_TRACE_MODULE_DCI, fmt, ##__VA_ARGS__) + +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END(VL53L5_TRACE_MODULE_DCI, status, ##__VA_ARGS__) + +#define CHECK_FOR_TIMEOUT(status, p_dev, start_ms, end_ms, timeout_ms) \ + (status = vl53l5_check_for_timeout(\ + (p_dev), start_ms, end_ms, timeout_ms)) + +#define CMD_STATUS_COMPLETE(p_dev) \ + (VL53L5_CMD_INFO(p_dev).cmd__rtn_command_status \ + == DCI_CMD_STATUS__COMPLETED) + +#define CMD_STATUS_ERROR(p_dev) \ + (VL53L5_CMD_INFO(p_dev).cmd__rtn_command_status \ + == DCI_CMD_STATUS__ERROR) + +#define TRANS_ID_MATCH(p_dev, trans_id) \ + (VL53L5_CMD_INFO(p_dev).cmd__rtn_transaction_id == trans_id) + +#define DCI_COMMAND_FOOTER_SIZE 4 +#define DCI_END_OF_DATA_FOOTER_SIZE 4 +#define DCI_END_OF_DATA_TYPE 0xf +#define DCI_COMMAND_STATUS_SIZE 4 + +#define UI_COMMAND_WRITE_END (DCI_UI__COMMAND_WRITE__END_IDX & 0xFFFF) +#define UI_COMMAND_READ_END \ + (UI_COMMAND_WRITE_END - DCI_UI__COMMAND_FOOTER__SIZE_BYTES) + +#define UI_STATUS_START \ + (DCI_UI__COMMAND_INFO__START_IDX & 0xFFFF) + +#define CMD_STATUS_TIMEOUT_MS 1100 + +static int32_t _encode_end_data_footer( + uint8_t *p_buff, uint32_t *p_count); + +static void _encode_command_footer( + uint8_t *p_buff, uint32_t *p_count, + uint8_t command_id, uint8_t transaction_id); + +static int32_t _write_comms_buffer_to_command_ui( + struct vl53l5_dev_handle_t *p_dev); + +static int32_t _read_command_ui_to_comms_buffer( + struct vl53l5_dev_handle_t *p_dev); + +static int32_t _poll_command_status_block(struct vl53l5_dev_handle_t *p_dev, + uint32_t trans_id, uint32_t timeout_ms, uint32_t start_time_ms, + bool check_trans_id, bool check_cmd_status); + +static int32_t _get_command_status(struct vl53l5_dev_handle_t *p_dev); + +int32_t vl53l5_dci_write_command( + struct vl53l5_dev_handle_t *p_dev, + uint8_t command_id, + uint8_t transaction_id) +{ + int32_t status = VL53L5_ERROR_NONE; + uint32_t available_count = 0; + uint32_t required_count = 0; + uint8_t *p_buff = 0; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_COMMS_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + if (command_id > DCI_CMD_ID__STOP_RANGE) { + status = VL53L5_INVALID_CMD_ID; + goto exit; + } + + if (VL53L5_COMMS_BUFF_MAX_COUNT(p_dev) > VL53L5_COMMS_BUFF_COUNT(p_dev)) { + available_count = VL53L5_COMMS_BUFF_MAX_COUNT(p_dev) + - VL53L5_COMMS_BUFF_COUNT(p_dev); + } + + required_count = DCI_COMMAND_FOOTER_SIZE; + if (VL53L5_COMMS_BUFF_MAX_COUNT(p_dev)) + required_count += DCI_END_OF_DATA_FOOTER_SIZE; + + if (required_count > available_count) { + status = VL53L5_MAX_BUFFER_SIZE_REACHED; + goto exit; + } else if ((VL53L5_COMMS_BUFF_COUNT(p_dev) + required_count) > + VL53L5_MAX_CMD_UI_SIZE_BYTES) { + status = VL53L5_DATA_EXCEEDS_CMD_BUFFER_SIZE; + goto exit; + } + + p_buff = VL53L5_COMMS_BUFF(p_dev) + VL53L5_COMMS_BUFF_COUNT(p_dev); + + if (VL53L5_COMMS_BUFF_COUNT(p_dev)) { + status = _encode_end_data_footer( + p_buff, &VL53L5_COMMS_BUFF_COUNT(p_dev)); + p_buff += DCI_END_OF_DATA_FOOTER_SIZE; + if (status != STATUS_OK) + goto exit; + } + + _encode_command_footer( + p_buff, &VL53L5_COMMS_BUFF_COUNT(p_dev), command_id, + transaction_id); + + status = vl53l5_dci_swap_buffer_byte_ordering( + VL53L5_COMMS_BUFF(p_dev), VL53L5_COMMS_BUFF_COUNT(p_dev)); + if (status < STATUS_OK) + goto exit; + + status = _write_comms_buffer_to_command_ui(p_dev); + +exit: + LOG_FUNCTION_END(status); + return status; +} + +int32_t vl53l5_dci_read_command( + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_COMMS_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + status = _read_command_ui_to_comms_buffer(p_dev); + if (status < STATUS_OK) + goto exit; + + status = vl53l5_dci_swap_buffer_byte_ordering( + VL53L5_COMMS_BUFF(p_dev), VL53L5_COMMS_BUFF_COUNT(p_dev)); + if (status < STATUS_OK) + goto exit; +exit: + LOG_FUNCTION_END(status); + return status; +} + +int32_t vl53l5_dci_poll_command_status( + struct vl53l5_dev_handle_t *p_dev, + uint32_t trans_id, + uint32_t timeout_ms) +{ + int32_t status = VL53L5_ERROR_NONE; + uint32_t start_time_ms = 0; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + if (timeout_ms == 0) + + timeout_ms = CMD_STATUS_TIMEOUT_MS; + + status = vl53l5_get_tick_count(p_dev, &start_time_ms); + if (status < STATUS_OK) + goto exit; + + status = _poll_command_status_block(p_dev, trans_id, timeout_ms, + start_time_ms, true, false); + if (status != STATUS_OK) + goto exit; + + status = _poll_command_status_block(p_dev, trans_id, timeout_ms, + start_time_ms, true, true); + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _encode_end_data_footer( + uint8_t *p_buff, uint32_t *p_count) +{ + int32_t status = VL53L5_ERROR_NONE; + + status = vl53l5_dci_encode_block_header( + p_buff, *p_count + DCI_END_OF_DATA_FOOTER_SIZE, + DCI_END_OF_DATA_TYPE, 0, 0); + if (status == VL53L5_ERROR_NONE) + (*p_count) += DCI_END_OF_DATA_FOOTER_SIZE; + + return status; +} + +static void _encode_command_footer( + uint8_t *p_buff, uint32_t *p_count, + uint8_t command_id, uint8_t transaction_id) +{ + struct dci_ui__cmd_footer_t ui_cmd_footer = {0}; + + ui_cmd_footer.cmd_footer.cmd__ip_data_size = *p_count; + + ui_cmd_footer.cmd_footer.cmd__ip_command_id = command_id; + + ui_cmd_footer.cmd_footer.cmd__ip_transaction_id = transaction_id; + + vl53l5_encode_uint32_t( + ui_cmd_footer.cmd_footer.bytes, BYTE_4, p_buff); + + (*p_count) += DCI_COMMAND_FOOTER_SIZE; +} + +static int32_t _write_comms_buffer_to_command_ui( + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = VL53L5_ERROR_NONE; + uint16_t start_index = (uint16_t)((UI_COMMAND_WRITE_END + - VL53L5_COMMS_BUFF_COUNT(p_dev)) + 1); + + status = vl53l5_write_multi( + p_dev, start_index, VL53L5_COMMS_BUFF(p_dev), + VL53L5_COMMS_BUFF_COUNT(p_dev)); + + return status; +} + +static int32_t _read_command_ui_to_comms_buffer( + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = VL53L5_ERROR_NONE; + uint16_t start_index = 0; + + VL53L5_RESET_COMMS_BUFF(p_dev); + + if (p_dev->host_dev.revision_id == 0x0C) + VL53L5_CMD_INFO(p_dev).cmd__rtn_data_size += + VL53L5_UI_DUMMY_BYTES; + + start_index = (uint16_t)((UI_COMMAND_READ_END + - VL53L5_CMD_INFO(p_dev).cmd__rtn_data_size) + 1); + + if (VL53L5_CMD_INFO(p_dev).cmd__rtn_data_size > + VL53L5_COMMS_BUFF_MAX_COUNT(p_dev)) { + status = VL53L5_MAX_BUFFER_SIZE_REACHED; + goto exit; + } + + if (VL53L5_CMD_INFO(p_dev).cmd__rtn_data_size > + VL53L5_MAX_CMD_UI_SIZE_BYTES) { + status = VL53L5_DATA_EXCEEDS_CMD_BUFFER_SIZE; + goto exit; + } + + if (VL53L5_CMD_INFO(p_dev).cmd__rtn_data_size == 0) { + status = VL53L5_ERROR_INVALID_RETURN_DATA_SIZE; + goto exit; + } + + if ((VL53L5_CMD_INFO(p_dev).cmd__rtn_data_size % 4) != 0) { + + status = VL53L5_BYTE_SWAP_FAIL; + goto exit; + } + + status = vl53l5_read_multi( + p_dev, start_index, VL53L5_COMMS_BUFF(p_dev), + VL53L5_CMD_INFO(p_dev).cmd__rtn_data_size); + if (status != STATUS_OK) + goto exit; + + VL53L5_COMMS_BUFF_COUNT(p_dev) = + VL53L5_CMD_INFO(p_dev).cmd__rtn_data_size; + +exit: + return status; +} + +static int32_t _get_command_status(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = VL53L5_ERROR_NONE; + uint32_t count = DCI_COMMAND_STATUS_SIZE; + uint16_t index = UI_STATUS_START; + uint8_t *p_buff = NULL; + + VL53L5_RESET_COMMS_BUFF(p_dev); + + if (p_dev->host_dev.revision_id == 0x0C) { + count += DCI_COMMAND_STATUS_SIZE; + index -= VL53L5_UI_DUMMY_BYTES; + p_buff = &VL53L5_COMMS_BUFF(p_dev)[VL53L5_UI_DUMMY_BYTES]; + } else { + p_buff = VL53L5_COMMS_BUFF(p_dev); + } + + status = vl53l5_read_multi( + p_dev, index, VL53L5_COMMS_BUFF(p_dev), count); + if (status < STATUS_OK) + goto exit; + + VL53L5_COMMS_BUFF_COUNT(p_dev) = count; + + status = vl53l5_dci_swap_buffer_byte_ordering( + VL53L5_COMMS_BUFF(p_dev), VL53L5_COMMS_BUFF_COUNT(p_dev)); + if (status < STATUS_OK) + goto exit; + + VL53L5_CMD_INFO(p_dev).bytes = vl53l5_decode_uint32_t(BYTE_4, p_buff); + +exit: + return status; +} + +static int32_t _poll_command_status_block(struct vl53l5_dev_handle_t *p_dev, + uint32_t trans_id, uint32_t timeout_ms, uint32_t start_time_ms, + bool check_trans_id, bool check_cmd_status) +{ + int32_t status = STATUS_OK; + uint32_t current_time_ms = 0; + bool success = false; + + LOG_FUNCTION_START(""); + + do { + + status = _get_command_status(p_dev); + if (status < STATUS_OK) + goto exit; + + success = ((!check_trans_id || TRANS_ID_MATCH(p_dev, trans_id)) + && (!check_cmd_status || CMD_STATUS_COMPLETE(p_dev))); + if (success) + break; + + if (check_cmd_status && CMD_STATUS_ERROR(p_dev)) { + status = VL53L5_DCI_CMD_STATUS_ERROR; + goto exit; + } + + status = vl53l5_get_tick_count(p_dev, ¤t_time_ms); + if (status < STATUS_OK) + goto exit; + + CHECK_FOR_TIMEOUT( + status, + p_dev, + start_time_ms, + current_time_ms, + timeout_ms); + if (status < STATUS_OK) { + status = VL53L5_ERROR_CMD_STATUS_TIMEOUT; + goto exit; + } + + status = vl53l5_wait_ms( + p_dev, VL53L5_DEFAULT_COMMS_POLLING_DELAY_MS); + if (status < STATUS_OK) + goto exit; + } while (1); + +exit: + LOG_FUNCTION_END(status); + return status; +} diff --git a/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_dci_decode.c b/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_dci_decode.c new file mode 100644 index 000000000000..c6afdf52cdc4 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_dci_decode.c @@ -0,0 +1,256 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_dci_decode.h" +#include "vl53l5_dci_helpers.h" +#include "vl53l5_globals.h" +#include "vl53l5_driver_dev_path.h" +#include "vl53l5_platform_log.h" +#include "vl53l5_error_codes.h" +#include "dci_luts.h" +#include "vl53l5_decode_switch.h" +#include "vl53l5_core_dev_path.h" +#ifdef VL53L5_PATCH_DATA_ENABLED +#include "page_map_switch.h" +#endif + +#define trace_print(level, ...) \ + _LOG_TRACE_PRINT(VL53L5_TRACE_MODULE_DCI, \ + level, VL53L5_TRACE_FUNCTION_ALL, ##__VA_ARGS__) + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START(VL53L5_TRACE_MODULE_DCI, fmt, ##__VA_ARGS__) + +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END(VL53L5_TRACE_MODULE_DCI, status, ##__VA_ARGS__) + +static int32_t _decode_raw_data( + struct vl53l5_dev_handle_t *p_dev, uint8_t *p_buff, + uint32_t buff_count, uint16_t *p_idx_checks, uint32_t num_idx_checks, + bool is_range_data); + +int32_t vl53l5_dci_decode_range_data( + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = VL53L5_ERROR_NONE; + uint32_t read_size = 0; + uint8_t *p_buff = NULL; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_COMMS_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + read_size = RANGE_DATA_READ_SIZE(p_dev); + + if (p_dev->host_dev.revision_id == 0x0C) { + read_size = RANGE_DATA_READ_SIZE(p_dev) + BYTE_4; + p_buff = &RANGE_DATA_START(p_dev)[VL53L5_UI_DUMMY_BYTES]; + + } else { + p_buff = RANGE_DATA_START(p_dev); + } + + if (read_size != VL53L5_COMMS_BUFF_COUNT(p_dev)) { + trace_print(VL53L5_TRACE_LEVEL_ERRORS, + "Rng data size %d does not match comms buff count %d\n", + read_size, + VL53L5_COMMS_BUFF_COUNT(p_dev)); + + status = VL53L5_DATA_BUFFER_MISMATCH; + goto exit; + } + + status = _decode_raw_data( + p_dev, p_buff, RANGE_DATA_SIZE(p_dev), NULL, 0, true); + +exit: + LOG_FUNCTION_END(status); + return status; +} + +int32_t vl53l5_dci_decode_data( + struct vl53l5_dev_handle_t *p_dev, + uint8_t *buffer, + uint32_t data_size) +{ + int32_t status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_ISNULL(buffer)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + status = _decode_raw_data(p_dev, buffer, data_size, NULL, 0, false); + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _decode_raw_data( + struct vl53l5_dev_handle_t *p_dev, uint8_t *p_buff, + uint32_t buff_count, uint16_t *p_idx_checks, uint32_t num_idx_checks, + bool is_range_data) +{ + int32_t status = VL53L5_ERROR_NONE; + uint8_t type = 0; + uint32_t block_byte_size = 0; + uint16_t idx = 0; + uint32_t bytes_decoded = 0; + +#ifdef VL53L5_PATCH_DATA_ENABLED + uint16_t page_index = 0; +#endif + + (void)p_idx_checks; + (void)num_idx_checks; + do { + trace_print( + VL53L5_TRACE_LEVEL_DEBUG, + "Decoding block header: [%02x][%02x][%02x][%02x]\n", + p_buff[bytes_decoded], + p_buff[bytes_decoded + 1], + p_buff[bytes_decoded + 2], + p_buff[bytes_decoded + 3]); + + status = vl53l5_dci_decode_block_header( + &p_buff[bytes_decoded], buff_count - bytes_decoded, + &type, &block_byte_size, &idx); + if (status < STATUS_OK) + goto exit; + + if (type == DCI_BH__P_TYPE__END_OF_DATA) + break; + + if ((type == DCI_BH__P_TYPE__GRP_PARAMS_START) || + (type == DCI_BH__P_TYPE__GRP_PARAMS_END)) { + + if ((!is_range_data) && (idx > MAX_NUM_RANGE_RETURNS)) { + status = VL53L5_INVALID_GROUP_INDEX; + goto exit; + } + + bytes_decoded += BYTE_4; + + continue; + } + + if (type == 0xC) { +#ifdef VL53L5_PATCH_DATA_ENABLED + + page_index = idx; +#else + + if (!is_range_data) { + status = VL53L5_INVALID_PAGE_ERROR; + goto exit; + } + + if (idx != 0) { + status = VL53L5_INVALID_PAGE_ERROR; + goto exit; + } +#endif + + bytes_decoded += BYTE_4; + + continue; + } + + bytes_decoded += BYTE_4; + +#ifdef VL53L5_PATCH_DATA_ENABLED + + status = dci_page_map_switch(idx, + block_byte_size, + &p_buff[bytes_decoded], + p_dev, + page_index); +#else + status = vl53l5_decode_switch( + idx, block_byte_size, &p_buff[bytes_decoded], p_dev); +#endif + if (status < STATUS_OK) + goto exit; + + bytes_decoded += block_byte_size; + + } while (bytes_decoded < buff_count); + + if (type != DCI_BH__P_TYPE__END_OF_DATA) { + status = VL53L5_DCI_END_BLOCK_ERROR; + goto exit; + } + +exit: + return status; +} diff --git a/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_dci_helpers.c b/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_dci_helpers.c new file mode 100644 index 000000000000..6b5f567ddfef --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_dci_helpers.c @@ -0,0 +1,249 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_dci_helpers.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_globals.h" +#include "vl53l5_platform.h" +#include "vl53l5_platform_log.h" +#include "vl53l5_error_codes.h" +#include "dci_luts.h" +#include "dci_ui_structs.h" + +#define trace_print(level, ...) \ + _LOG_TRACE_PRINT(VL53L5_TRACE_MODULE_DCI, \ + level, VL53L5_TRACE_FUNCTION_ALL, ##__VA_ARGS__) + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START(VL53L5_TRACE_MODULE_DCI, fmt, ##__VA_ARGS__) + +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END(VL53L5_TRACE_MODULE_DCI, status, ##__VA_ARGS__) + +#define FIRST_NON_DATA_TYPE 0x0d + +static int32_t _calculate_block_header_size_field( + uint8_t type, uint32_t size_in_bytes, uint16_t *p_size) +{ + + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + if (type > 0xF) { + status = VL53L5_ERROR_INVALID_BH_ENCODE; + goto exit; + } + + if (type == DCI_BH__P_TYPE__CONSEC_PARAMS_LIST) { + + *p_size = (uint16_t)size_in_bytes; + } else if (type < FIRST_NON_DATA_TYPE) { + + *p_size = (uint16_t)(size_in_bytes / type); + } else if (type == DCI_BH__P_TYPE__END_OF_DATA) { + + *p_size = 0; + } else { + + status = VL53L5_ERROR_INVALID_BH_ENCODE; + goto exit; + } + + if (*p_size > 0xFFF) { + status = VL53L5_ERROR_INVALID_BH_SIZE; + goto exit; + } + +exit: + LOG_FUNCTION_END(status); + return status; +} + +int32_t vl53l5_dci_encode_block_header( + uint8_t *p_buffer, + uint32_t max_buffer_size, + uint8_t type, + uint32_t block_byte_size, + uint16_t idx) +{ + int32_t status = VL53L5_ERROR_NONE; + uint16_t b_size__p_rep = 0; + struct dci_ui__packed_data__bh_t block_header = {0}; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_buffer)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (max_buffer_size < 4) { + status = VL53L5_MAX_BUFFER_SIZE_REACHED; + goto exit; + } + + status = _calculate_block_header_size_field( + type, block_byte_size, &b_size__p_rep); + if (status < STATUS_OK) + goto exit; + + block_header.packed_data__bh.p_type = (uint32_t)(type & 0xF); + block_header.packed_data__bh.b_size__p_rep = + (uint32_t)(b_size__p_rep & 0x0FFF); + block_header.packed_data__bh.b_idx__p_idx = + (uint32_t)idx; + + vl53l5_encode_uint32_t( + (uint32_t)block_header.packed_data__bh.bytes, BYTE_4, p_buffer); + +exit: + LOG_FUNCTION_END(status); + return status; +} + +int32_t vl53l5_dci_decode_block_header( + uint8_t *p_buffer, + uint32_t max_buffer_size, + uint8_t *p_type, + uint32_t *p_block_byte_size, + uint16_t *p_idx) +{ + int32_t status = VL53L5_ERROR_NONE; + struct dci_ui__packed_data__bh_t block_header = {0}; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_buffer)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_ISNULL(p_type)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_ISNULL(p_idx)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_ISNULL(p_block_byte_size)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (max_buffer_size < 4) { + status = VL53L5_MAX_BUFFER_SIZE_REACHED; + goto exit; + } + + block_header.packed_data__bh.bytes = + vl53l5_decode_uint32_t(BYTE_4, p_buffer); + + *p_type = block_header.packed_data__bh.p_type; + *p_idx = block_header.packed_data__bh.b_idx__p_idx; + + if (block_header.packed_data__bh.p_type == + DCI_BH__P_TYPE__CONSEC_PARAMS_LIST) { + + *p_block_byte_size = + (uint32_t)block_header.packed_data__bh.b_size__p_rep; + } else if (block_header.packed_data__bh.p_type < FIRST_NON_DATA_TYPE) { + + *p_block_byte_size = + (uint32_t)block_header.packed_data__bh.b_size__p_rep * + (uint32_t)block_header.packed_data__bh.p_type; + } else { + + *p_block_byte_size = 0; + } + +exit: + LOG_FUNCTION_END(status); + return status; +} + +int32_t vl53l5_dci_swap_buffer_byte_ordering( + uint8_t *p_buffer, uint32_t buffer_count) +{ + uint8_t temp_char; + uint32_t block_index = 0; + int32_t status = VL53L5_ERROR_NONE; + + if (VL53L5_ISNULL(p_buffer)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + if ((buffer_count % 4) != 0) { + status = VL53L5_BYTE_SWAP_FAIL; + goto exit; + } + + while (block_index < buffer_count) { + temp_char = *p_buffer; + *p_buffer = *(p_buffer + 3); + *(p_buffer + 3) = temp_char; + temp_char = *(p_buffer + 1); + *(p_buffer + 1) = *(p_buffer + 2); + *(p_buffer + 2) = temp_char; + block_index += 4; + p_buffer += 4; + } + +exit: + return status; +} diff --git a/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_dci_ranging.c b/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_dci_ranging.c new file mode 100644 index 000000000000..febd9f225eed --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_dci_ranging.c @@ -0,0 +1,367 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_dci_ranging.h" +#include "vl53l5_dci_helpers.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_driver_dev_path.h" +#include "vl53l5_platform.h" +#include "vl53l5_platform_log.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform_user_config.h" + +#define trace_print(level, ...) \ + _LOG_TRACE_PRINT(VL53L5_TRACE_MODULE_DCI, \ + level, VL53L5_TRACE_FUNCTION_ALL, ##__VA_ARGS__) + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START(VL53L5_TRACE_MODULE_DCI, fmt, ##__VA_ARGS__) + +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END(VL53L5_TRACE_MODULE_DCI, status, ##__VA_ARGS__) + +#define DATA_PRESENT(p_dev)\ + (VL53L5_GO2_STATUS_0(p_dev).mcu__ui_range_data_present_go1 == 1) + +#define HW_TRAP(p_dev)\ + (VL53L5_GO2_STATUS_0(p_dev).mcu__hw_trap_flag_go1 == 1) + +#define MCU_ERROR(p_dev)\ + (VL53L5_GO2_STATUS_0(p_dev).mcu__error_flag_go1 == 1) + +#define MCU_WARNING(p_dev)\ + (VL53L5_GO2_STATUS_1(p_dev).mcu__warning_flag_go1 == 1) + +#define MCU_CP_COLLAPSE(p_dev)\ + (VL53L5_GO2_STATUS_1(p_dev).mcu__cp_collapse_flag_go1 == 1) + +#define STREAM_COUNT_NEW(p_dev, last_stream_id)\ + (VL53L5_UI_DEV_STREAM(p_dev) != last_stream_id) + +#define RANGE_DATA_READ_SIZE(p_dev) \ + (p_dev->host_dev.range_data_addr.data_start_offset_bytes\ + + p_dev->host_dev.range_data_addr.data_size_bytes\ + + VL53L5_HEADER_FOOTER_BLOCK_SZ) + +static int32_t _read_range_ui( + struct vl53l5_dev_handle_t *p_dev, uint32_t count); + +static void _decode_device_info_block(struct vl53l5_dev_handle_t *p_dev); + +static int32_t _check_range_header_footer(struct vl53l5_dev_handle_t *p_dev); + +int32_t vl53l5_dci_read_range( + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = VL53L5_ERROR_NONE; + uint32_t read_size = 0; + uint8_t previous_stream_id = 0; + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_COMMS_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + if (p_dev->host_dev.range_data_addr.data_size_bytes == 0) { + status = VL53L5_ERROR_INVALID_RETURN_DATA_SIZE; + goto exit; + } + + read_size = RANGE_DATA_READ_SIZE(p_dev); + + if (p_dev->host_dev.revision_id == 0x0C) + read_size += VL53L5_UI_DUMMY_BYTES; + + if (read_size > p_dev->host_dev.comms_buff_max_count) { + + status = VL53L5_DATA_EXCEEDS_CMD_BUFFER_SIZE; + goto exit; + } + + status = _read_range_ui(p_dev, read_size); + if (status < STATUS_OK) + goto exit; + + previous_stream_id = VL53L5_UI_DEV_STREAM(p_dev); + + _decode_device_info_block(p_dev); + + status = vl53l5_dci_check_device_info(p_dev, + previous_stream_id, + true, + true); + if (status < STATUS_OK) + goto exit; + + status = _check_range_header_footer(p_dev); + +exit: + LOG_FUNCTION_END(status); + return status; +} + +int32_t vl53l5_dci_get_device_info( + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + uint32_t count = VL53L5_DEV_INFO_BLOCK_SZ; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_COMMS_BUFF_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + if (p_dev->host_dev.revision_id == 0x0C) + count += VL53L5_UI_DUMMY_BYTES; + + status = _read_range_ui(p_dev, count); + if (status < STATUS_OK) + goto exit; + + _decode_device_info_block(p_dev); + +exit: + LOG_FUNCTION_END(status); + return status; +} + +int32_t vl53l5_dci_check_device_info( + struct vl53l5_dev_handle_t *p_dev, + uint8_t last_stream_id, + bool check_stream_count, + bool check_data_present) +{ + int32_t status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if ((!check_stream_count) && (!check_data_present)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + if (HW_TRAP(p_dev)) { + if (VL53L5_GO2_STATUS_1(p_dev).bytes != 0) + + status = VL53L5_DEV_INFO_MCU_ERROR; + else + + status = VL53L5_FALSE_DEV_INFO_MCU_ERROR; + goto exit; + } + if (MCU_ERROR(p_dev)) { + + status = VL53L5_DEV_INFO_MCU_ERROR; + goto exit; + } + + if (MCU_CP_COLLAPSE(p_dev)) { + VL53L5_UI_DEV_STREAM(p_dev) = 0xff; + + status = VL53L5_TOO_HIGH_AMBIENT_WARNING; + goto exit; + } + if (MCU_WARNING(p_dev) && p_dev->host_dev.mcu_warnings_on) { + + status = VL53L5_DEV_INFO_MCU_ERROR; + goto exit; + } + + if ((check_stream_count && (!STREAM_COUNT_NEW(p_dev, last_stream_id))) + || (check_data_present && (!DATA_PRESENT(p_dev)))) { + + status = VL53L5_NO_NEW_RANGE_DATA_ERROR; + goto exit; + } + +exit: +#ifdef VL53L5_LOG_ENABLE + if ((status != VL53L5_ERROR_NONE) && + (status != VL53L5_NO_NEW_RANGE_DATA_ERROR) && + (p_dev != NULL)) { + trace_print( + VL53L5_TRACE_LEVEL_ERRORS, + "Error occurred reading device info block\n"); + trace_print( + VL53L5_TRACE_LEVEL_ERRORS, + "go2_status_0.bytes = %02x\n", + VL53L5_GO2_STATUS_0(p_dev).bytes); + trace_print( + VL53L5_TRACE_LEVEL_ERRORS, + "go2_status_0.bytes = %02x\n", + VL53L5_GO2_STATUS_1(p_dev).bytes); + trace_print( + VL53L5_TRACE_LEVEL_ERRORS, + "device_status = %02x\n", + VL53L5_UI_DEV_STATUS(p_dev)); + trace_print( + VL53L5_TRACE_LEVEL_ERRORS, + "ui_stream_count = %02x\n", + VL53L5_UI_DEV_STREAM(p_dev)); + } +#endif + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _read_range_ui(struct vl53l5_dev_handle_t *p_dev, uint32_t count) +{ + int32_t status = VL53L5_ERROR_NONE; + uint8_t *buffer = VL53L5_COMMS_BUFF(p_dev); + uint16_t index = p_dev->host_dev.range_data_addr.dev_info_start_addr; + + if (VL53L5_COMMS_BUFF_MAX_COUNT(p_dev) < count) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + VL53L5_RESET_COMMS_BUFF(p_dev); + + if (p_dev->host_dev.revision_id == 0x0C) + index -= 4; + + status = vl53l5_read_multi(p_dev, index, buffer, count); + if (status < STATUS_OK) + goto exit; + + VL53L5_COMMS_BUFF_COUNT(p_dev) = count; + + status = vl53l5_dci_swap_buffer_byte_ordering( + buffer, VL53L5_COMMS_BUFF_COUNT(p_dev)); + +exit: + return status; +} + +static void _decode_device_info_block(struct vl53l5_dev_handle_t *p_dev) +{ + uint8_t *p_buff = NULL; + + if (p_dev->host_dev.revision_id == 0x0C) + p_buff = &VL53L5_COMMS_BUFF(p_dev)[VL53L5_UI_DUMMY_BYTES]; + else + p_buff = VL53L5_COMMS_BUFF(p_dev); + + VL53L5_GO2_STATUS_0(p_dev).bytes = + vl53l5_decode_uint8_t(BYTE_1, p_buff++); + + VL53L5_GO2_STATUS_1(p_dev).bytes = + vl53l5_decode_uint8_t(BYTE_1, p_buff++); + + p_dev->host_dev.ui_dev_info.dev_info__device_status = + vl53l5_decode_uint8_t(BYTE_1, p_buff++); + + VL53L5_UI_DEV_STREAM(p_dev) = + vl53l5_decode_uint8_t(BYTE_1, p_buff++); +} + +static int32_t _check_range_header_footer(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = VL53L5_ERROR_NONE; + uint16_t header_id = 0; + uint16_t inverse_header_id = 0; + uint16_t footer_id = 0; + uint8_t *p_buff = NULL; + uint32_t start_idx = + p_dev->host_dev.range_data_addr.data_start_offset_bytes - + VL53L5_HEADER_FOOTER_BLOCK_SZ; + uint32_t end_idx = p_dev->host_dev.range_data_addr.data_size_bytes; + + if (p_dev->host_dev.revision_id == 0x0C) + p_buff = &VL53L5_COMMS_BUFF(p_dev)[VL53L5_UI_DUMMY_BYTES]; + else + p_buff = VL53L5_COMMS_BUFF(p_dev); + + p_buff += start_idx; + + header_id = vl53l5_decode_uint16_t(BYTE_2, p_buff); + inverse_header_id = ~header_id; + + p_buff += end_idx; + + footer_id = vl53l5_decode_uint16_t(BYTE_2, p_buff); + + if (inverse_header_id != footer_id) { + trace_print(VL53L5_TRACE_LEVEL_ERRORS, + "Error. Header: 0x%04x ~Header: 0x%04x Footer: 0x%04x\n", + header_id, inverse_header_id, footer_id); + status = VL53L5_DCI_RANGE_INTEGRITY_ERROR; + } else { + trace_print(VL53L5_TRACE_LEVEL_DEBUG, + "Okay. Header: 0x%04x ~Header: 0x%04x Footer: 0x%04x\n", + header_id, inverse_header_id, footer_id); + status = STATUS_OK; + } + + return status; +} diff --git a/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_dci_utils.c b/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_dci_utils.c new file mode 100644 index 000000000000..693eebf7f784 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_dci_utils.c @@ -0,0 +1,221 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_dci_utils.h" + +uint8_t vl53l5_decode_uint8_t( + uint16_t count, + uint8_t *pbuffer) +{ + (void)count; + + return (uint8_t)(*pbuffer); +} + +void vl53l5_encode_uint8_t( + uint8_t ip_value, + uint16_t count, + uint8_t *pbuffer) +{ + (void)count; + + *pbuffer = (uint8_t)(ip_value & 0x00FF); +} + +int8_t vl53l5_decode_int8_t( + uint16_t count, + uint8_t *pbuffer) +{ + (void)count; + + return (int8_t)(*pbuffer); +} + +void vl53l5_encode_int8_t( + int8_t ip_value, + uint16_t count, + uint8_t *pbuffer) +{ + (void)count; + + *pbuffer = (uint8_t)(ip_value & 0x00FF); +} + +uint16_t vl53l5_decode_uint16_t( + uint16_t count, + uint8_t *pbuffer) +{ + + uint16_t value = 0x00; + + while (count-- > 0) + value = (value << 8) | (uint16_t)pbuffer[count]; + + return value; +} + +void vl53l5_encode_uint16_t( + uint16_t ip_value, + uint16_t count, + uint8_t *pbuffer) +{ + + uint16_t i = 0; + uint16_t data = 0; + + data = ip_value; + + for (i = 0; i < count ; i++) { + pbuffer[i] = (uint8_t)(data & 0x00FF); + data = data >> 8; + } +} + +void vl53l5_encode_int16_t( + int16_t ip_value, + uint16_t count, + uint8_t *pbuffer) +{ + + uint16_t i = 0; + int16_t data = 0; + + data = ip_value; + + for (i = 0; i < count; i++) { + pbuffer[i] = (uint8_t)(data & 0xFF); + data = data >> 8; + } +} + +int16_t vl53l5_decode_int16_t( + uint16_t count, + uint8_t *pbuffer) +{ + int16_t value = 0x00; + + if (pbuffer[count - 1] & 0x80) + value = (int16_t)0xFFFF; + + while (count-- > 0) + value = (value << 8) | (int16_t)pbuffer[count]; + + return value; +} + +void vl53l5_encode_int32_t( + int32_t ip_value, + uint16_t count, + uint8_t *pbuffer) +{ + + uint16_t i = 0; + int32_t data = 0; + + data = ip_value; + + for (i = 0; i < count ; i++) { + pbuffer[i] = (uint8_t)(data & 0x00FF); + data = data >> 8; + } +} + +int32_t vl53l5_decode_int32_t( + uint16_t count, + uint8_t *pbuffer) +{ + int32_t value = 0x00; + + if (pbuffer[count - 1] & 0x80) + value = 0xFFFF; + + while (count-- > 0) + value = (value << 8) | (int32_t)pbuffer[count]; + + return value; +} + +void vl53l5_encode_uint32_t( + uint32_t ip_value, + uint16_t count, + uint8_t *pbuffer) +{ + + uint16_t i = 0; + uint32_t data = 0; + + data = ip_value; + + for (i = 0; i < count ; i++) { + pbuffer[i] = (uint8_t)(data & 0x00FF); + data = data >> 8; + } +} + +uint32_t vl53l5_decode_uint32_t( + uint16_t count, + uint8_t *pbuffer) +{ + uint32_t value = 0x00; + + while (count-- > 0) + value = (value << 8) | (uint32_t)pbuffer[count]; + + return value; +} diff --git a/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_decode_switch.c b/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_decode_switch.c new file mode 100644 index 000000000000..6cc289d86374 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_decode_switch.c @@ -0,0 +1,123 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_decode_switch.h" +#include "vl53l5_core_decode.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform_log.h" +#ifdef VL53L5_RESULTS_DATA_ENABLED +#include "vl53l5_results_decode.h" +#endif + +#ifdef VL53L5_PATCH_DATA_ENABLED +#include "vl53l5_patch_decode_switch.h" +#endif + +#ifdef VL53L5_CALIBRATION_DECODE_ON +#include "vl53l5_calibration_decode.h" +#endif + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START( \ + VL53L5_TRACE_MODULE_DCI_DECODE, fmt, ##__VA_ARGS__) + +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) + +#define LOG_FUNCTION_END_FMT(status, ...) \ + _LOG_FUNCTION_END_FMT( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) +int32_t vl53l5_decode_switch( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + if ((idx >= 0x5400) && (idx <= 0x59d0)) + status = vl53l5_core_decode_cmd( + idx, buffer_size, buffer, p_dev); +#ifdef VL53L5_RESULTS_DATA_ENABLED + else if ((idx >= 0x59d8) && (idx <= 0xafc0)) + status = vl53l5_results_decode_cmd( + idx, buffer_size, buffer, p_dev); +#endif + +#ifdef VL53L5_CALIBRATION_DECODE_ON + else if ((idx >= 0xafdc) && (idx <= 0xc908)) + status = vl53l5_calibration_decode_cmd( + idx, buffer_size, buffer, p_dev); +#endif + +#ifdef VL53L5_PATCH_DATA_ENABLED + else if (idx > 0xd4fc) + status = vl53l5_patch_decode_switch( + idx, buffer_size, buffer, p_dev); +#endif + else + status = VL53L5_INVALID_IDX_DECODE_CMD_ERROR; + + LOG_FUNCTION_END(status); + return status; + +} diff --git a/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_results_decode.c b/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_results_decode.c new file mode 100644 index 000000000000..81ab96d90540 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/dci/src/vl53l5_results_decode.c @@ -0,0 +1,1693 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "dci_size.h" +#include "dci_ui_size.h" +#include "common_datatype_size.h" +#include "dyn_xtalk_size.h" +#include "ic_checkers_size.h" +#include "vl53l5_results_decode.h" +#include "vl53l5_results_enum_type.h" +#include "vl53l5_results_map_idx.h" +#include "vl53l5_results_dev_path.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform_log.h" + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START( \ + VL53L5_TRACE_MODULE_DCI_DECODE, fmt, ##__VA_ARGS__) + +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) + +#define LOG_FUNCTION_END_FMT(status, ...) \ + _LOG_FUNCTION_END_FMT( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) +#ifdef VL53L5_META_DATA_ON +static int32_t _decode_dci_grp_buf_meta_data( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_meta_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_BUF_META_DATA_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->time_stamp = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->device_status = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->transaction_id = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->buffer_id = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->stream_count = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->silicon_temp_degc = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->buf_meta_data__pad_0 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->buf_meta_data__pad_1 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->buf_meta_data__pad_2 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_COMMON_DATA_ON +static int32_t _decode_dci_grp_rng_common_data( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_common_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RNG_COMMON_DATA_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->wrap_dmax_mm = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->rng__no_of_zones = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->rng__max_targets_per_zone = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->rng__acc__no_of_zones = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->rng__acc__zone_id = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->rng__common__spare_0 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->rng__common__spare_1 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_RNG_TIMING_DATA_ON +static int32_t _decode_dci_grp_rng_timing_data( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_timing_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RNG_TIMING_DATA_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->rng__integration_time_us = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->rng__total_periods_elapsed = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->rng__blanking_time_us = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_AMB_RATE_KCPS_PER_SPAD_ON +static int32_t _decode_dci_grp_rng_per_zone_data_amb_rate_kcps_per_spad( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_zone_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPZD_AMB_RATE_KCPS_PER_SPAD_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->amb_rate_kcps_per_spad[i] = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_EFFECTIVE_SPAD_COUNT_ON +static int32_t _decode_dci_grp_rng_per_zone_data_rng_effective_spad_count( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_zone_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPZD_RNG_EFFECTIVE_SPAD_COUNT_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->rng__effective_spad_count[i] = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_AMB_DMAX_MM_ON +static int32_t _decode_dci_grp_rng_per_zone_data_amb_dmax_mm( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_zone_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPZD_AMB_DMAX_MM_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->amb_dmax_mm[i] = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_SILICON_TEMP_DEGC_START_ON +static int32_t _decode_dci_grp_rng_per_zone_data_silicon_temp_degc_start( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_zone_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPZD_SILICON_TEMP_DEGC_START_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->silicon_temp_degc__start[i] = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_SILICON_TEMP_DEGC_END_ON +static int32_t _decode_dci_grp_rng_per_zone_data_silicon_temp_degc_end( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_zone_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPZD_SILICON_TEMP_DEGC_END_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->silicon_temp_degc__end[i] = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_NO_OF_TARGETS_ON +static int32_t _decode_dci_grp_rng_per_zone_data_rng_no_of_targets( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_zone_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPZD_RNG_NO_OF_TARGETS_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->rng__no_of_targets[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_ZONE_ID_ON +static int32_t _decode_dci_grp_rng_per_zone_data_rng_zone_id( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_zone_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPZD_RNG_ZONE_ID_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->rng__zone_id[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_SEQUENCE_IDX_ON +static int32_t _decode_dci_grp_rng_per_zone_data_rng_sequence_idx( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_zone_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPZD_RNG_SEQUENCE_IDX_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->rng__sequence_idx[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_PEAK_RATE_KCPS_PER_SPAD_ON +static int32_t _decode_dci_grp_rng_per_target_data_peak_rate_kcps_per_spad( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_PEAK_RATE_KCPS_PER_SPAD_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->peak_rate_kcps_per_spad[i] = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_MEDIAN_PHASE_ON +static int32_t _decode_dci_grp_rng_per_target_data_median_phase( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_MEDIAN_PHASE_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->median_phase[i] = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_RATE_SIGMA_KCPS_PER_SPAD_ON +static int32_t _decode_dci_grp_rng_per_target_data_rate_sigma_kcps_per_spad( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_RATE_SIGMA_KCPS_PER_SPAD_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->rate_sigma_kcps_per_spad[i] = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_TARGET_ZSCORE_ON +static int32_t _decode_dci_grp_rng_per_target_data_target_zscore( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_TARGET_ZSCORE_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->target_zscore[i] = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_RANGE_SIGMA_MM_ON +static int32_t _decode_dci_grp_rng_per_target_data_range_sigma_mm( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_RANGE_SIGMA_MM_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->range_sigma_mm[i] = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_MEDIAN_RANGE_MM_ON +static int32_t _decode_dci_grp_rng_per_target_data_median_range_mm( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_MEDIAN_RANGE_MM_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->median_range_mm[i] = + vl53l5_decode_int16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_START_RANGE_MM_ON +static int32_t _decode_dci_grp_rng_per_target_data_start_range_mm( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_START_RANGE_MM_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->start_range_mm[i] = + vl53l5_decode_int16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_END_RANGE_MM_ON +static int32_t _decode_dci_grp_rng_per_target_data_end_range_mm( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_END_RANGE_MM_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->end_range_mm[i] = + vl53l5_decode_int16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_MIN_RANGE_DELTA_MM_ON +static int32_t _decode_dci_grp_rng_per_target_data_min_range_delta_mm( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_MIN_RANGE_DELTA_MM_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->min_range_delta_mm[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_MAX_RANGE_DELTA_MM_ON +static int32_t _decode_dci_grp_rng_per_target_data_max_range_delta_mm( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_MAX_RANGE_DELTA_MM_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->max_range_delta_mm[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_TARGET_REFLECTANCE_EST_PC_ON +static int32_t _decode_dci_grp_rng_per_target_data_target_reflectance_est_pc( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_TARGET_REFLECTANCE_EST_PC_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->target_reflectance_est_pc[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_TARGET_STATUS_ON +static int32_t _decode_dci_grp_rng_per_target_data_target_status( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_TARGET_STATUS_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->target_status[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_REF_CHANNEL_DATA_ON +static int32_t _decode_dci_grp_ref_channel_data( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_ref_channel_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_REF_CHANNEL_DATA_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->amb_rate_kcps_per_spad = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->rng__effective_spad_count = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->amb_dmax_mm = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->silicon_temp_degc__start = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->silicon_temp_degc__end = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->rng__no_of_targets = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->rng__zone_id = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->rng__sequence_idx = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->ref_channel_data__pad_0 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_REF_TARGET_DATA_ON +static int32_t _decode_dci_grp_ref_target_data( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_ref_target_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_REF_TARGET_DATA_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->peak_rate_kcps_per_spad = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->median_phase = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->rate_sigma_kcps_per_spad = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->target_zscore = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->range_sigma_mm = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->median_range_mm = + vl53l5_decode_int16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->start_range_mm = + vl53l5_decode_int16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->end_range_mm = + vl53l5_decode_int16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->min_range_delta_mm = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->max_range_delta_mm = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->target_reflectance_est_pc = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->target_status = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->ref_target_data__pad_0 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->ref_target_data__pad_1 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_SHARPENER_GROUP_INDEX_ON +static int32_t _decode_dci_grp_sharpener_target_data_sharpener_group_index( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_sharpener_target_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_STD_SHARPENER_GROUP_INDEX_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->sharpener__group_index[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_SHARPENER_CONFIDENCE_ON +static int32_t _decode_dci_grp_sharpener_target_data_sharpener_confidence( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_sharpener_target_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_STD_SHARPENER_CONFIDENCE_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->sharpener__confidence[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_ZONE_THRESH_STATUS_BYTES_ON +static int32_t _decode_ztsa_zone_thresh_status_bytes( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_zone_thresh_status_array_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_ZTSA_ZONE_THRESH_STATUS_BYTES_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->zone_thresh_status_bytes[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_DYN_XTALK_OP_PERSISTENT_DATA_ON +static int32_t _decode_dci_grp_dyn_xtalk_persistent_data( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dyn_xtalk_persistent_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_DYN_XTALK_PERSISTENT_DATA_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->dyn_xt__dyn_xtalk_grid_maximum_kcps_per_spad = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->dyn_xt__dyn_xtalk_grid_max_sigma_kcps_per_spad = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->dyn_xt__new_max_xtalk_sigma_kcps_per_spad = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->dyn_xt__calibration_gain = + vl53l5_decode_int32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->temp_comp__temp_gain = + vl53l5_decode_int32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->dyn_xt__nb_samples = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->dyn_xt__desired_samples = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->dyn_xt__accumulating = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->dyn_xt__grid_ready = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->dyn_xt__grid_ready_internal = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->dyn_xt__persist_spare_2 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +int32_t vl53l5_results_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + +#if !defined(VL53L5_META_DATA_ON) && \ + !defined(VL53L5_COMMON_DATA_ON) && \ + !defined(VL53L5_RNG_TIMING_DATA_ON) && \ + !defined(VL53L5_AMB_RATE_KCPS_PER_SPAD_ON) && \ + !defined(VL53L5_EFFECTIVE_SPAD_COUNT_ON) && \ + !defined(VL53L5_AMB_DMAX_MM_ON) && \ + !defined(VL53L5_SILICON_TEMP_DEGC_START_ON) && \ + !defined(VL53L5_SILICON_TEMP_DEGC_END_ON) && \ + !defined(VL53L5_NO_OF_TARGETS_ON) && \ + !defined(VL53L5_ZONE_ID_ON) && \ + !defined(VL53L5_SEQUENCE_IDX_ON) && \ + !defined(VL53L5_PEAK_RATE_KCPS_PER_SPAD_ON) && \ + !defined(VL53L5_MEDIAN_PHASE_ON) && \ + !defined(VL53L5_RATE_SIGMA_KCPS_PER_SPAD_ON) && \ + !defined(VL53L5_TARGET_ZSCORE_ON) && \ + !defined(VL53L5_RANGE_SIGMA_MM_ON) && \ + !defined(VL53L5_MEDIAN_RANGE_MM_ON) && \ + !defined(VL53L5_START_RANGE_MM_ON) && \ + !defined(VL53L5_END_RANGE_MM_ON) && \ + !defined(VL53L5_MIN_RANGE_DELTA_MM_ON) && \ + !defined(VL53L5_MAX_RANGE_DELTA_MM_ON) && \ + !defined(VL53L5_TARGET_REFLECTANCE_EST_PC_ON) && \ + !defined(VL53L5_TARGET_STATUS_ON) && \ + !defined(VL53L5_REF_TIMING_DATA_ON) && \ + !defined(VL53L5_REF_CHANNEL_DATA_ON) && \ + !defined(VL53L5_REF_TARGET_DATA_ON) && \ + !defined(VL53L5_SHARPENER_GROUP_INDEX_ON) && \ + !defined(VL53L5_SHARPENER_CONFIDENCE_ON) && \ + !defined(VL53L5_ZONE_THRESH_STATUS_BYTES_ON) && \ + !defined(VL53L5_DYN_XTALK_OP_PERSISTENT_DATA_ON) + (void)buffer_size; + (void)buffer; + (void)p_dev; +#endif + + switch (idx) { +#ifdef VL53L5_META_DATA_ON + case DEV_RNG_META_DATA_IDX: + status = + _decode_dci_grp_buf_meta_data( + buffer_size, + buffer, + &VL53L5_META_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_COMMON_DATA_ON + case DEV_RNG_COMMON_DATA_IDX: + status = + _decode_dci_grp_rng_common_data( + buffer_size, + buffer, + &VL53L5_COMMON_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_RNG_TIMING_DATA_ON + case DEV_RNG_TIMING_DATA_IDX: + status = + _decode_dci_grp_rng_timing_data( + buffer_size, + buffer, + &VL53L5_RNG_TIMING_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_AMB_RATE_KCPS_PER_SPAD_ON + case DEV_RNG_PER_ZONE_DATA_AMB_RATE_KCPS_PER_SPAD_IDX: + status = + _decode_dci_grp_rng_per_zone_data_amb_rate_kcps_per_spad( + buffer_size, + buffer, + &VL53L5_PER_ZONE_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_EFFECTIVE_SPAD_COUNT_ON + case DEV_RNG_PER_ZONE_DATA_RNG__EFFECTIVE_SPAD_COUNT_IDX: + status = + _decode_dci_grp_rng_per_zone_data_rng_effective_spad_count( + buffer_size, + buffer, + &VL53L5_PER_ZONE_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_AMB_DMAX_MM_ON + case DEV_RNG_PER_ZONE_DATA_AMB_DMAX_MM_IDX: + status = + _decode_dci_grp_rng_per_zone_data_amb_dmax_mm( + buffer_size, + buffer, + &VL53L5_PER_ZONE_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_SILICON_TEMP_DEGC_START_ON + case DEV_RNG_PER_ZONE_DATA_SILICON_TEMP_DEGC__START_IDX: + status = + _decode_dci_grp_rng_per_zone_data_silicon_temp_degc_start( + buffer_size, + buffer, + &VL53L5_PER_ZONE_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_SILICON_TEMP_DEGC_END_ON + case DEV_RNG_PER_ZONE_DATA_SILICON_TEMP_DEGC__END_IDX: + status = + _decode_dci_grp_rng_per_zone_data_silicon_temp_degc_end( + buffer_size, + buffer, + &VL53L5_PER_ZONE_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_NO_OF_TARGETS_ON + case DEV_RNG_PER_ZONE_DATA_RNG__NO_OF_TARGETS_IDX: + status = + _decode_dci_grp_rng_per_zone_data_rng_no_of_targets( + buffer_size, + buffer, + &VL53L5_PER_ZONE_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_ZONE_ID_ON + case DEV_RNG_PER_ZONE_DATA_RNG__ZONE_ID_IDX: + status = + _decode_dci_grp_rng_per_zone_data_rng_zone_id( + buffer_size, + buffer, + &VL53L5_PER_ZONE_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_SEQUENCE_IDX_ON + case DEV_RNG_PER_ZONE_DATA_RNG__SEQUENCE_IDX_IDX: + status = + _decode_dci_grp_rng_per_zone_data_rng_sequence_idx( + buffer_size, + buffer, + &VL53L5_PER_ZONE_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_PEAK_RATE_KCPS_PER_SPAD_ON + case DEV_RNG_PER_TARGET_DATA_PEAK_RATE_KCPS_PER_SPAD_IDX: + status = + _decode_dci_grp_rng_per_target_data_peak_rate_kcps_per_spad( + buffer_size, + buffer, + &VL53L5_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_MEDIAN_PHASE_ON + case DEV_RNG_PER_TARGET_DATA_MEDIAN_PHASE_IDX: + status = + _decode_dci_grp_rng_per_target_data_median_phase( + buffer_size, + buffer, + &VL53L5_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_RATE_SIGMA_KCPS_PER_SPAD_ON + case DEV_RNG_PER_TARGET_DATA_RATE_SIGMA_KCPS_PER_SPAD_IDX: + status = + _decode_dci_grp_rng_per_target_data_rate_sigma_kcps_per_spad( + buffer_size, + buffer, + &VL53L5_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_TARGET_ZSCORE_ON + case DEV_RNG_PER_TARGET_DATA_TARGET_ZSCORE_IDX: + status = + _decode_dci_grp_rng_per_target_data_target_zscore( + buffer_size, + buffer, + &VL53L5_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_RANGE_SIGMA_MM_ON + case DEV_RNG_PER_TARGET_DATA_RANGE_SIGMA_MM_IDX: + status = + _decode_dci_grp_rng_per_target_data_range_sigma_mm( + buffer_size, + buffer, + &VL53L5_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_MEDIAN_RANGE_MM_ON + case DEV_RNG_PER_TARGET_DATA_MEDIAN_RANGE_MM_IDX: + status = + _decode_dci_grp_rng_per_target_data_median_range_mm( + buffer_size, + buffer, + &VL53L5_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_START_RANGE_MM_ON + case DEV_RNG_PER_TARGET_DATA_START_RANGE_MM_IDX: + status = + _decode_dci_grp_rng_per_target_data_start_range_mm( + buffer_size, + buffer, + &VL53L5_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_END_RANGE_MM_ON + case DEV_RNG_PER_TARGET_DATA_END_RANGE_MM_IDX: + status = + _decode_dci_grp_rng_per_target_data_end_range_mm( + buffer_size, + buffer, + &VL53L5_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_MIN_RANGE_DELTA_MM_ON + case DEV_RNG_PER_TARGET_DATA_MIN_RANGE_DELTA_MM_IDX: + status = + _decode_dci_grp_rng_per_target_data_min_range_delta_mm( + buffer_size, + buffer, + &VL53L5_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_MAX_RANGE_DELTA_MM_ON + case DEV_RNG_PER_TARGET_DATA_MAX_RANGE_DELTA_MM_IDX: + status = + _decode_dci_grp_rng_per_target_data_max_range_delta_mm( + buffer_size, + buffer, + &VL53L5_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_TARGET_REFLECTANCE_EST_PC_ON + case DEV_RNG_PER_TARGET_DATA_TARGET_REFLECTANCE_EST_PC_IDX: + status = + _decode_dci_grp_rng_per_target_data_target_reflectance_est_pc( + buffer_size, + buffer, + &VL53L5_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_TARGET_STATUS_ON + case DEV_RNG_PER_TARGET_DATA_TARGET_STATUS_IDX: + status = + _decode_dci_grp_rng_per_target_data_target_status( + buffer_size, + buffer, + &VL53L5_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_REF_TIMING_DATA_ON + case DEV_REF_TIMING_DATA_IDX: + status = + _decode_dci_grp_rng_timing_data( + buffer_size, + buffer, + &VL53L5_REF_TIMING_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_REF_CHANNEL_DATA_ON + case DEV_REF_CHANNEL_DATA_IDX: + status = + _decode_dci_grp_ref_channel_data( + buffer_size, + buffer, + &VL53L5_REF_CHANNEL_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_REF_TARGET_DATA_ON + case DEV_REF_TARGET_DATA_IDX: + status = + _decode_dci_grp_ref_target_data( + buffer_size, + buffer, + &VL53L5_REF_TARGET_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_SHARPENER_GROUP_INDEX_ON + case DEV_SHARPENER_TARGET_DATA_SHARPENER__GROUP_INDEX_IDX: + status = + _decode_dci_grp_sharpener_target_data_sharpener_group_index( + buffer_size, + buffer, + &VL53L5_SHARPENER_TARGET_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_SHARPENER_CONFIDENCE_ON + case DEV_SHARPENER_TARGET_DATA_SHARPENER__CONFIDENCE_IDX: + status = + _decode_dci_grp_sharpener_target_data_sharpener_confidence( + buffer_size, + buffer, + &VL53L5_SHARPENER_TARGET_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_ZONE_THRESH_STATUS_BYTES_ON + case DEV_ZONE_THRESH_STATUS_ZONE_THRESH_STATUS_BYTES_IDX: + status = + _decode_ztsa_zone_thresh_status_bytes( + buffer_size, + buffer, + &VL53L5_ZONE_THRESH_STATUS(p_dev)); + break; +#endif + +#ifdef VL53L5_DYN_XTALK_OP_PERSISTENT_DATA_ON + case DEV_DYN_XTALK_OP_PERSISTENT_DATA_IDX: + status = + _decode_dci_grp_dyn_xtalk_persistent_data( + buffer_size, + buffer, + &VL53L5_DYN_XTALK_OP_PERSISTENT_DATA(p_dev)); + break; +#endif + + default: + status = VL53L5_INVALID_IDX_DECODE_CMD_ERROR; + break; + } + + LOG_FUNCTION_END(status); + return status; + +} diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/dci_patch_defs.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/dci_patch_defs.h new file mode 100644 index 000000000000..0df50acd77cb --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/dci_patch_defs.h @@ -0,0 +1,98 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __DCI_PATCH_DEFS_H__ +#define __DCI_PATCH_DEFS_H__ + +#include "dci_defs.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define DCI_SZ_MAX_TARGETS_PER_ZONE \ + ((uint32_t) 4U) + +#define DCI_SZ_MAX_ZONES \ + ((uint32_t) 4U) + +#define DCI_SZ_MAX_TARGETS \ + ((uint32_t) DCI_SZ_MAX_ZONES * DCI_SZ_MAX_TARGETS_PER_ZONE) + +#define DCI_D16_PACKET_SIZE \ + ((uint32_t) 16U) + +#define DCI_D16_MAX_BH_LIST_SIZE \ + ((uint32_t) 16U) + +#define DCI_D16_MAX_PACKETS \ + ((uint32_t) 24U) + +#define DCI_D16_MAX_BUFFER_SIZE \ + ((uint32_t) DCI_D16_PACKET_SIZE * DCI_D16_MAX_PACKETS) + +#define DCI_GD2_MAX_GRID_DIM_ZONES \ + ((uint32_t) 8U) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/dci_patch_luts.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/dci_patch_luts.h new file mode 100644 index 000000000000..f94826a37da0 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/dci_patch_luts.h @@ -0,0 +1,121 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __DCI_PATCH_LUTS_H__ +#define __DCI_PATCH_LUTS_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define DCI_D16_BH_PARM_ID__END_OF_DATA \ + ((uint32_t) 0U) + +#define DCI_D16_BH_PARM_ID__EMPTY \ + ((uint32_t) 1U) + +#define DCI_D16_BH_PARM_ID__PACKET_HEADER \ + ((uint32_t) 2U) + +#define DCI_D16_BH_PARM_ID__RNG_META_DATA \ + ((uint32_t) 3U) + +#define DCI_D16_BH_PARM_ID__RNG_COMMON_DATA \ + ((uint32_t) 4U) + +#define DCI_D16_BH_PARM_ID__RNG_TIMING_DATA \ + ((uint32_t) 5U) + +#define DCI_D16_BH_PARM_ID__GLASS_DETECT \ + ((uint32_t) 6U) + +#define DCI_D16_BH_PARM_ID__DEPTH16 \ + ((uint32_t) 7U) + +#define DCI_D16_BH_PARM_ID__AMB_RATE_KCPS_PER_SPAD \ + ((uint32_t) 8U) + +#define DCI_D16_BH_PARM_ID__PEAK_RATE_KCPS_PER_SPAD \ + ((uint32_t) 9U) + +#define DCI_D16_BH_PARM_ID__TARGET_STATUS \ + ((uint32_t) 10U) + +#define DCI_D16_BH_PARM_ID__SZ__DEPTH16 \ + ((uint32_t) 11U) + +#define DCI_D16_BH_PARM_ID__SZ__AMB_RATE_KCPS_PER_SPAD \ + ((uint32_t) 12U) + +#define DCI_D16_BH_PARM_ID__SZ__PEAK_RATE_KCPS_PER_SPAD \ + ((uint32_t) 13U) + +#define DCI_D16_BH_PARM_ID__SZ__TARGET_STATUS \ + ((uint32_t) 14U) + +#define DCI_TARGET_STATUS__GLARE \ + ((uint8_t) 18U) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/dci_patch_size.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/dci_patch_size.h new file mode 100644 index 000000000000..3ca5b458f034 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/dci_patch_size.h @@ -0,0 +1,169 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __DCI_PATCH_SIZE_H__ +#define __DCI_PATCH_SIZE_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_GD_OP_STATUS_SZ \ + ((uint16_t) 12) + +#define VL53L5_GD_OP_DATA_SZ \ + ((uint16_t) 44) + +#define VL53L5_GD2_OP_DATA_SZ \ + ((uint16_t) 36) + +#define VL53L5_GD2_OP_DATA_ARR_SZ \ + ((uint16_t) 48) + +#define VL53L5_GODA_GD_UPPER_BOUNDS_MM_SZ \ + ((uint16_t) 16) + +#define VL53L5_GODA_GD_LOWER_BOUNDS_MM_SZ \ + ((uint16_t) 16) + +#define VL53L5_GODA_GD_BORDER_VARIANCE_SZ \ + ((uint16_t) 16) + +#define VL53L5_SZ_COMMON_DATA_SZ \ + ((uint16_t) 4) + +#define VL53L5_SZ_ZONE_DATA_SZ \ + ((uint16_t) 48) + +#define VL53L5_SZD_AMB_RATE_KCPS_PER_SPAD_SZ \ + ((uint16_t) 16) + +#define VL53L5_SZD_RNG_EFFECTIVE_SPAD_COUNT_SZ \ + ((uint16_t) 16) + +#define VL53L5_SZD_AMB_DMAX_MM_SZ \ + ((uint16_t) 8) + +#define VL53L5_SZD_RNG_NO_OF_TARGETS_SZ \ + ((uint16_t) 4) + +#define VL53L5_SZD_RNG_ZONE_ID_SZ \ + ((uint16_t) 4) + +#define VL53L5_SZ_TARGET_DATA_SZ \ + ((uint16_t) 144) + +#define VL53L5_STD_PEAK_RATE_KCPS_PER_SPAD_SZ \ + ((uint16_t) 64) + +#define VL53L5_STD_RANGE_SIGMA_MM_SZ \ + ((uint16_t) 32) + +#define VL53L5_STD_MEDIAN_RANGE_MM_SZ \ + ((uint16_t) 32) + +#define VL53L5_STD_TARGET_STATUS_SZ \ + ((uint16_t) 16) + +#define VL53L5_DEPTH16_VALUE_SZ \ + ((uint16_t) 4) + +#define VL53L5_D16_PER_TARGET_DATA_SZ \ + ((uint16_t) 544) + +#define VL53L5_DPTD_DEPTH16_SZ \ + ((uint16_t) 2 * VL53L5_MAX_TARGETS) + +#define VL53L5_D16_REF_TARGET_DATA_SZ \ + ((uint16_t) 4) + +#define VL53L5_D16_BUF_PACKET_HEADER_SZ \ + ((uint16_t) 4) + +#define VL53L5_D16_BUF_GLASS_DETECT_SZ \ + ((uint16_t) 4) + +#define VL53L5_D16_BLK_META_SZ \ + ((uint16_t) 16) + +#define VL53L5_D16_BUF_META_SZ \ + ((uint16_t) 8) + +#define VL53L5_D16_PKT_META_SZ \ + ((uint16_t) 16) + +#define VL53L5_D16_BUF_FORMAT_SZ \ + ((uint16_t) 64) + +#define VL53L5_DBF_BH_LIST_SZ \ + ((uint16_t) 64) + +#define VL53L5_D16_BUF_DATA_SZ \ + ((uint16_t) 1536) + +#define VL53L5_DBD_DATA_SZ \ + ((uint16_t) 1536) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/dci_patch_structs.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/dci_patch_structs.h new file mode 100644 index 000000000000..a14766f46b7a --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/dci_patch_structs.h @@ -0,0 +1,144 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __DCI_PATCH_STRUCTS_H__ +#define __DCI_PATCH_STRUCTS_H__ + +#include "vl53l5_results_config.h" +#include "dci_defs.h" +#include "dci_luts.h" +#include "dci_ui_union_structs.h" +#include "dci_patch_defs.h" +#include "dci_patch_luts.h" +#include "dci_patch_structs.h" +#include "dci_patch_union_structs.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct vl53l5_gd_op_status_t { + + uint32_t gd__rate_ratio; + + uint8_t gd__confidence; + + uint8_t gd__plane_detected; + + uint8_t gd__ratio_detected; + + uint8_t gd__glass_detected; + + uint8_t gd__tgt_behind_glass_detected; + + uint8_t gd__mirror_detected; + + uint8_t gd__op_spare_0; + + uint8_t gd__op_spare_1; +}; + +struct dci_grp__depth16_value_t { + + union dci_union__depth16_value_u depth16; + + uint16_t d16_confidence_pc; +}; + +struct vl53l5_d16_per_tgt_results_t { +#ifdef VL53L5_DEPTH16_ON + + uint16_t depth16[VL53L5_MAX_TARGETS]; +#endif + +#if !defined(VL53L5_DEPTH16_ON) + + uint32_t dummy; +#endif + +}; + +struct dci_grp__d16__ref_target_data_t { + + uint16_t depth16; + + uint16_t d16__ref_target__pad_0; +}; + +struct dci_grp__d16__buf__packet_header_t { + + union dci_union__d16__buf__packet_header_u packet_header; +}; + +struct dci_grp__d16__buf__glass_detect_t { + + union dci_union__d16__buf__glass_detect_u glass_detect; +}; + +struct dci_grp__d16__buf__format_t { + + uint32_t bh_list[DCI_D16_MAX_BH_LIST_SIZE]; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/dci_patch_union_structs.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/dci_patch_union_structs.h new file mode 100644 index 000000000000..c53c002dc665 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/dci_patch_union_structs.h @@ -0,0 +1,128 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __DCI_PATCH_UNION_STRUCTS_H__ +#define __DCI_PATCH_UNION_STRUCTS_H__ + +#include "dci_defs.h" +#include "dci_patch_defs.h" +#include "dci_patch_luts.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +union dci_union__depth16_value_u { + uint16_t bytes; + struct { + + uint16_t range_mm : 13; + + uint16_t confidence : 3; + }; +}; + +union dci_union__d16__buf__packet_header_u { + uint32_t bytes; + struct { + + uint32_t version : 4; + + uint32_t max_targets_per_zone : 2; + + uint32_t reserved_0 : 6; + + uint32_t array_width : 6; + + uint32_t array_height : 6; + + uint32_t last_packet : 1; + + uint32_t reserved_1 : 7; + }; +}; + +union dci_union__d16__buf__glass_detect_u { + uint32_t bytes; + struct { + + uint32_t reserved_0 : 3; + + uint32_t gd__mirror_detected : 1; + + uint32_t gd__tgt_behind_glass_detected : 1; + + uint32_t gd__glass_detected : 1; + + uint32_t gd__ratio_detected : 1; + + uint32_t gd__plane_detected : 1; + + uint32_t gd__confidence : 8; + + uint32_t gd__rate_ratio : 16; + }; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_api_core.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_api_core.h new file mode 100644 index 000000000000..b03b11a3fa1b --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_api_core.h @@ -0,0 +1,118 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L5_PATCH_API_CORE_H_ +#define _VL53L5_PATCH_API_CORE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "vl53l5_platform_user_data.h" +#include "vl53l5_patch_core_map.h" +#include "vl53l5_tcpm_patch_0_core_map.h" +#include "vl53l5_tcpm_patch_1_core_map.h" + +#define VL53L5_TCPM_0_PAGE_BH 0x0001000c +#define VL53L5_TCPM_1_PAGE_BH 0x0002000c + +struct vl53l5_core_data_t { + struct vl53l5_patch_core_dev_t patch; + struct vl53l5_tcpm_patch_0_core_dev_t tcpm_0_patch; + struct vl53l5_tcpm_patch_1_core_dev_t tcpm_1_patch; +}; + +struct vl53l5_patch_version_t { + struct { + + uint16_t ver_major; + + uint16_t ver_minor; + } patch_version; + struct { + + uint16_t ver_major; + + uint16_t ver_minor; + } tcpm_version; + struct { + + uint16_t ver_major; + + uint16_t ver_minor; + + uint16_t ver_build; + + uint16_t ver_revision; + } patch_driver; +}; + +int32_t vl53l5_assign_core(struct vl53l5_dev_handle_t *p_dev, + struct vl53l5_core_data_t *p_core); + +int32_t vl53l5_unassign_core(struct vl53l5_dev_handle_t *p_dev); + +int32_t vl53l5_get_patch_version(struct vl53l5_dev_handle_t *p_dev, + struct vl53l5_patch_version_t *p_version); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_calibration_decode.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_calibration_decode.h new file mode 100644 index 000000000000..7b305e0b6328 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_calibration_decode.h @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_CALIBRATION_DECODE_H__ +#define __VL53L5_PATCH_CALIBRATION_DECODE_H__ + +#include "vl53l5_platform_user_data.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int32_t vl53l5_patch_calibration_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_calibration_dev_path.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_calibration_dev_path.h new file mode 100644 index 000000000000..e4acc3eb3504 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_calibration_dev_path.h @@ -0,0 +1,74 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_CALIBRATION_DEV_PATH_H__ +#define __VL53L5_PATCH_CALIBRATION_DEV_PATH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_PATCH_CALIBRATION_DEV(p_dev) \ + ((p_dev)->host_dev.ppatch_calibration_dev) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_calibration_enum_type.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_calibration_enum_type.h new file mode 100644 index 000000000000..b38a9e7a1393 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_calibration_enum_type.h @@ -0,0 +1,73 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_CALIBRATION_ENUM_TYPE_H__ +#define __VL53L5_PATCH_CALIBRATION_ENUM_TYPE_H__ + +#include "vl53l5_dci_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_calibration_map.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_calibration_map.h new file mode 100644 index 000000000000..1823c648a8bd --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_calibration_map.h @@ -0,0 +1,80 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_CALIBRATION_MAP_H__ +#define __VL53L5_PATCH_CALIBRATION_MAP_H__ + +#include "vl53l5_patch_defs.h" +#include "vl53l5_patch_version_structs.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct vl53l5_patch_calibration_dev_t { + + uint32_t dummy; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_calibration_map_bh.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_calibration_map_bh.h new file mode 100644 index 000000000000..57b1206f8d45 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_calibration_map_bh.h @@ -0,0 +1,71 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_CALIBRATION_MAP_BH_H__ +#define __VL53L5_PATCH_CALIBRATION_MAP_BH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_calibration_map_idx.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_calibration_map_idx.h new file mode 100644 index 000000000000..01ef4bf7935c --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_calibration_map_idx.h @@ -0,0 +1,73 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_CALIBRATION_MAP_IDX_H__ +#define __VL53L5_PATCH_CALIBRATION_MAP_IDX_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_core_decode.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_core_decode.h new file mode 100644 index 000000000000..ca03b4be03bc --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_core_decode.h @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_CORE_DECODE_H__ +#define __VL53L5_PATCH_CORE_DECODE_H__ + +#include "vl53l5_platform_user_data.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int32_t vl53l5_patch_core_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_core_dev_path.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_core_dev_path.h new file mode 100644 index 000000000000..96d350d5d5d5 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_core_dev_path.h @@ -0,0 +1,78 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_CORE_DEV_PATH_H__ +#define __VL53L5_PATCH_CORE_DEV_PATH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_PATCH_CORE_DEV(p_dev) \ + ((p_dev)->host_dev.ppatch_core_dev) +#define VL53L5_PATCH_PATCH_MAP_VERSION(p_dev) \ + VL53L5_PATCH_CORE_DEV(p_dev)->patch_map_version +#define VL53L5_PATCH_PATCH_VERSION(p_dev) \ + VL53L5_PATCH_CORE_DEV(p_dev)->patch_version + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_core_enum_type.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_core_enum_type.h new file mode 100644 index 000000000000..39e18dee79bb --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_core_enum_type.h @@ -0,0 +1,78 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_CORE_ENUM_TYPE_H__ +#define __VL53L5_PATCH_CORE_ENUM_TYPE_H__ + +#include "vl53l5_dci_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_PATCH_VERSION_GRP_MAP_VERSION_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_PATCH_VERSION_GRP_PATCH_VERSION_TYPE \ + ((enum block_format_type) 0x0) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_core_map.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_core_map.h new file mode 100644 index 000000000000..97361218d047 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_core_map.h @@ -0,0 +1,80 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_CORE_MAP_H__ +#define __VL53L5_PATCH_CORE_MAP_H__ + +#include "vl53l5_patch_defs.h" +#include "vl53l5_patch_version_structs.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct vl53l5_patch_core_dev_t { + struct patch_version_grp__map_version_t patch_map_version; + struct patch_version_grp__patch_version_t patch_version; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_core_map_bh.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_core_map_bh.h new file mode 100644 index 000000000000..07634bdf3cb4 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_core_map_bh.h @@ -0,0 +1,76 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_CORE_MAP_BH_H__ +#define __VL53L5_PATCH_CORE_MAP_BH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_PATCH_MAP_VERSION_BH \ + ((uint32_t) 0xd5000040U) +#define VL53L5_PATCH_VERSION_BH \ + ((uint32_t) 0xd5040040U) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_core_map_idx.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_core_map_idx.h new file mode 100644 index 000000000000..552bd9c96b42 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_core_map_idx.h @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_CORE_MAP_IDX_H__ +#define __VL53L5_PATCH_CORE_MAP_IDX_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define DEV_PATCH_MAP_VERSION_IDX \ + ((uint16_t) 0xd500) + +#define DEV_PATCH_VERSION_IDX \ + ((uint16_t) 0xd504) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_decode_switch.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_decode_switch.h new file mode 100644 index 000000000000..394d32b214bd --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_decode_switch.h @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_DECODE_SWITCH_H__ +#define __VL53L5_PATCH_DECODE_SWITCH_H__ + +#include "vl53l5_platform_user_data.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int32_t vl53l5_patch_decode_switch( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_defs.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_defs.h new file mode 100644 index 000000000000..258f54ac92c2 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_defs.h @@ -0,0 +1,76 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_DEFS_H__ +#define __VL53L5_PATCH_DEFS_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define PATCH_IDX_OFFSET \ + ((uint32_t) 54528U) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_error_codes.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_error_codes.h new file mode 100644 index 000000000000..0924cf58f0c9 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_error_codes.h @@ -0,0 +1,78 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L5_PATCH_ERROR_CODES_H_ +#define _VL53L5_PATCH_ERROR_CODES_H_ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_Patch_Error int32_t + +#define VL53L5_PATCH_VERSION_IDX_NOT_PRESENT ((VL53L5_Patch_Error) - 201) +#define VL53L5_TCPM_VERSION_IDX_NOT_PRESENT ((VL53L5_Patch_Error) - 202) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_results_config.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_results_config.h new file mode 100644 index 000000000000..8c507c8aa789 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_results_config.h @@ -0,0 +1,92 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_RESULTS_CONFIG_H__ +#define __VL53L5_PATCH_RESULTS_CONFIG_H__ + +#include "vl53l5_results_build_config.h" +#include "dci_defs.h" +#include "dci_ui_memory_defs.h" +#include "dci_size.h" +#include "dci_ui_size.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_DUMMY_BYTES_SZ \ + VL53L5_CONFIG_DUMMY_BYTES_SZ +#define VL53L5_DUMMY_FOOTER_BYTES_SZ \ + 4U + +#define VL53L5_PATCH_MAP_RESULTS_PACKET_META_SIZE \ + (VL53L5_DCI_UI_DEV_INFO_SZ \ + + VL53L5_DUMMY_BYTES_SZ \ + + (2 * VL53L5_DCI_UI_RNG_DATA_HEADER_SZ) \ + + (3 * VL53L5_DCI_UI_PACKED_DATA_BH_SZ) \ + + VL53L5_DUMMY_FOOTER_BYTES_SZ) + +#define VL53L5_PATCH_MAP_RESULTS_TOTAL_SIZE_BYTES \ + VL53L5_PATCH_MAP_RESULTS_PACKET_META_SIZE + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_results_decode.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_results_decode.h new file mode 100644 index 000000000000..343e55c0cfd2 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_results_decode.h @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_RESULTS_DECODE_H__ +#define __VL53L5_PATCH_RESULTS_DECODE_H__ + +#include "vl53l5_platform_user_data.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int32_t vl53l5_patch_results_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_results_dev_path.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_results_dev_path.h new file mode 100644 index 000000000000..fccd7ae1fb7b --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_results_dev_path.h @@ -0,0 +1,74 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_RESULTS_DEV_PATH_H__ +#define __VL53L5_PATCH_RESULTS_DEV_PATH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_PATCH_RESULTS_DEV(p_dev) \ + ((p_dev)->host_dev.ppatch_results_dev) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_results_enum_type.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_results_enum_type.h new file mode 100644 index 000000000000..5e8661869d4b --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_results_enum_type.h @@ -0,0 +1,73 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_RESULTS_ENUM_TYPE_H__ +#define __VL53L5_PATCH_RESULTS_ENUM_TYPE_H__ + +#include "vl53l5_dci_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_results_map.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_results_map.h new file mode 100644 index 000000000000..52011bf137bc --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_results_map.h @@ -0,0 +1,80 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_RESULTS_MAP_H__ +#define __VL53L5_PATCH_RESULTS_MAP_H__ + +#include "vl53l5_patch_defs.h" +#include "vl53l5_patch_version_structs.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct vl53l5_patch_results_dev_t { + + uint32_t dummy; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_results_map_bh.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_results_map_bh.h new file mode 100644 index 000000000000..bd5e8ed50a46 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_results_map_bh.h @@ -0,0 +1,71 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_RESULTS_MAP_BH_H__ +#define __VL53L5_PATCH_RESULTS_MAP_BH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_results_map_idx.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_results_map_idx.h new file mode 100644 index 000000000000..40c3c304811b --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_results_map_idx.h @@ -0,0 +1,73 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_RESULTS_MAP_IDX_H__ +#define __VL53L5_PATCH_RESULTS_MAP_IDX_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_version.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_version.h new file mode 100644 index 000000000000..4af4b2ad77e5 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_version.h @@ -0,0 +1,77 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 VL53L5_PATCH_VERSION_H_ +#define VL53L5_PATCH_VERSION_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define VL53L5_PATCH_VERSION_MAJOR 2 +#define VL53L5_PATCH_VERSION_MINOR 1 +#define VL53L5_PATCH_VERSION_BUILD 0 +#define VL53L5_PATCH_VERSION_REVISION 0 + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_version_defs.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_version_defs.h new file mode 100644 index 000000000000..6d8707c384b1 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_version_defs.h @@ -0,0 +1,83 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_VERSION_DEFS_H__ +#define __VL53L5_PATCH_VERSION_DEFS_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define PATCH_VERSION_MAP_VERSION_MAJOR \ + ((uint32_t) 3U) +#define PATCH_VERSION_MAP_VERSION_MINOR \ + ((uint32_t) 1U) + +#define PATCH_VERSION_PATCH_VERSION_MAJOR \ + ((uint32_t) 6U) +#define PATCH_VERSION_PATCH_VERSION_MINOR \ + ((uint32_t) 0U) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_version_size.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_version_size.h new file mode 100644 index 000000000000..67c40746924a --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_version_size.h @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_VERSION_SIZE_H__ +#define __VL53L5_PATCH_VERSION_SIZE_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_PATCH_VERSION_GRP_MAP_VERSION_SZ \ + ((uint16_t) 4) + +#define VL53L5_PATCH_VERSION_GRP_PATCH_VERSION_SZ \ + ((uint16_t) 4) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_version_structs.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_version_structs.h new file mode 100644 index 000000000000..8d7ad277c00c --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_patch_version_structs.h @@ -0,0 +1,88 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_PATCH_VERSION_STRUCTS_H__ +#define __VL53L5_PATCH_VERSION_STRUCTS_H__ + +#include "packing_structs.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct patch_version_grp__map_version_t { + + uint16_t patch_map__major; + + uint16_t patch_map__minor; +}; + +struct patch_version_grp__patch_version_t { + + uint16_t patch_version__major; + + uint16_t patch_version__minor; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_calibration_decode.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_calibration_decode.h new file mode 100644 index 000000000000..a238c2a13f9b --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_calibration_decode.h @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_0_CALIBRATION_DECODE_H__ +#define __VL53L5_TCPM_PATCH_0_CALIBRATION_DECODE_H__ + +#include "vl53l5_platform_user_data.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int32_t vl53l5_tcpm_patch_0_calibration_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_calibration_dev_path.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_calibration_dev_path.h new file mode 100644 index 000000000000..7803b92193b1 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_calibration_dev_path.h @@ -0,0 +1,74 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_0_CALIBRATION_DEV_PATH_H__ +#define __VL53L5_TCPM_PATCH_0_CALIBRATION_DEV_PATH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_TCPM_PATCH_0_CALIBRATION_DEV(p_dev) \ + ((p_dev)->host_dev.ptcpm_patch_0_calibration_dev) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_calibration_enum_type.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_calibration_enum_type.h new file mode 100644 index 000000000000..f0e449a6bb28 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_calibration_enum_type.h @@ -0,0 +1,73 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_0_CALIBRATION_ENUM_TYPE_H__ +#define __VL53L5_TCPM_PATCH_0_CALIBRATION_ENUM_TYPE_H__ + +#include "vl53l5_dci_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_calibration_map.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_calibration_map.h new file mode 100644 index 000000000000..5c38128104ac --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_calibration_map.h @@ -0,0 +1,87 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_0_CALIBRATION_MAP_H__ +#define __VL53L5_TCPM_PATCH_0_CALIBRATION_MAP_H__ + +#include "vl53l5_tcpm_patch_defs.h" +#include "vl53l5_tcpm_patch_version_structs.h" +#include "dci_structs.h" +#include "dci_union_structs.h" +#include "dci_defs.h" +#include "dci_ui_union_structs.h" +#include "dci_patch_defs.h" +#include "dci_patch_structs.h" +#include "dci_patch_union_structs.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct vl53l5_tcpm_patch_0_calibration_dev_t { + + uint32_t dummy; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_calibration_map_bh.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_calibration_map_bh.h new file mode 100644 index 000000000000..af251116aeed --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_calibration_map_bh.h @@ -0,0 +1,71 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_0_CALIBRATION_MAP_BH_H__ +#define __VL53L5_TCPM_PATCH_0_CALIBRATION_MAP_BH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_calibration_map_idx.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_calibration_map_idx.h new file mode 100644 index 000000000000..efc36f047a9a --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_calibration_map_idx.h @@ -0,0 +1,73 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_0_CALIBRATION_MAP_IDX_H__ +#define __VL53L5_TCPM_PATCH_0_CALIBRATION_MAP_IDX_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_core_decode.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_core_decode.h new file mode 100644 index 000000000000..c666a4d083a8 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_core_decode.h @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_0_CORE_DECODE_H__ +#define __VL53L5_TCPM_PATCH_0_CORE_DECODE_H__ + +#include "vl53l5_platform_user_data.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int32_t vl53l5_tcpm_patch_0_core_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_core_dev_path.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_core_dev_path.h new file mode 100644 index 000000000000..d63b5c9547d5 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_core_dev_path.h @@ -0,0 +1,78 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_0_CORE_DEV_PATH_H__ +#define __VL53L5_TCPM_PATCH_0_CORE_DEV_PATH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_TCPM_PATCH_0_CORE_DEV(p_dev) \ + ((p_dev)->host_dev.ptcpm_patch_0_core_dev) +#define VL53L5_TCPM_PATCH_0_TCPM_PATCH_MAP_VERSION(p_dev) \ + VL53L5_TCPM_PATCH_0_CORE_DEV(p_dev)->tcpm_patch_map_version +#define VL53L5_TCPM_PATCH_0_TCPM_PATCH_VERSION(p_dev) \ + VL53L5_TCPM_PATCH_0_CORE_DEV(p_dev)->tcpm_patch_version + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_core_enum_type.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_core_enum_type.h new file mode 100644 index 000000000000..327de9f9e1a0 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_core_enum_type.h @@ -0,0 +1,78 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_0_CORE_ENUM_TYPE_H__ +#define __VL53L5_TCPM_PATCH_0_CORE_ENUM_TYPE_H__ + +#include "vl53l5_dci_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_TCPM_PATCH_VERSION_GRP_MAP_VERSION_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_TCPM_PATCH_VERSION_GRP_PATCH_VERSION_TYPE \ + ((enum block_format_type) 0x0) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_core_map.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_core_map.h new file mode 100644 index 000000000000..ee781b9caf9b --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_core_map.h @@ -0,0 +1,87 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_0_CORE_MAP_H__ +#define __VL53L5_TCPM_PATCH_0_CORE_MAP_H__ + +#include "vl53l5_tcpm_patch_defs.h" +#include "vl53l5_tcpm_patch_version_structs.h" +#include "dci_structs.h" +#include "dci_union_structs.h" +#include "dci_defs.h" +#include "dci_ui_union_structs.h" +#include "dci_patch_defs.h" +#include "dci_patch_structs.h" +#include "dci_patch_union_structs.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct vl53l5_tcpm_patch_0_core_dev_t { + struct tcpm_patch_version_grp__map_version_t tcpm_patch_map_version; + struct tcpm_patch_version_grp__patch_version_t tcpm_patch_version; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_core_map_bh.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_core_map_bh.h new file mode 100644 index 000000000000..2e2716d8c37c --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_core_map_bh.h @@ -0,0 +1,76 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_0_CORE_MAP_BH_H__ +#define __VL53L5_TCPM_PATCH_0_CORE_MAP_BH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_TCPM_0_TCPM_PATCH_MAP_VERSION_BH \ + ((uint32_t) 0x00000040U) +#define VL53L5_TCPM_0_TCPM_PATCH_VERSION_BH \ + ((uint32_t) 0x00040040U) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_core_map_idx.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_core_map_idx.h new file mode 100644 index 000000000000..94725ff8729b --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_core_map_idx.h @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_0_CORE_MAP_IDX_H__ +#define __VL53L5_TCPM_PATCH_0_CORE_MAP_IDX_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_TCPM_0_TCPM_PATCH_MAP_VERSION_IDX \ + ((uint16_t) 0x0000) + +#define VL53L5_TCPM_0_TCPM_PATCH_VERSION_IDX \ + ((uint16_t) 0x0004) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_decode_switch.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_decode_switch.h new file mode 100644 index 000000000000..a637edc27759 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_decode_switch.h @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_0_DECODE_SWITCH_H__ +#define __VL53L5_TCPM_PATCH_0_DECODE_SWITCH_H__ + +#include "vl53l5_platform_user_data.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int32_t vl53l5_tcpm_patch_0_decode_switch( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_results_config.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_results_config.h new file mode 100644 index 000000000000..4b14cdaa7ded --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_results_config.h @@ -0,0 +1,449 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_0_RESULTS_CONFIG_H__ +#define __VL53L5_TCPM_PATCH_0_RESULTS_CONFIG_H__ + +#include "vl53l5_results_build_config.h" +#include "dci_defs.h" +#include "dci_ui_memory_defs.h" +#include "dci_size.h" +#include "dci_ui_size.h" +#include "dci_patch_size.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if (defined(VL53L5_AMB_RATE_KCPS_PER_SPAD_ON) || \ + defined(VL53L5_EFFECTIVE_SPAD_COUNT_ON) || \ + defined(VL53L5_AMB_DMAX_MM_ON) || \ + defined(VL53L5_SILICON_TEMP_DEGC_START_ON) || \ + defined(VL53L5_SILICON_TEMP_DEGC_END_ON) || \ + defined(VL53L5_NO_OF_TARGETS_ON) || defined(VL53L5_ZONE_ID_ON) || \ + defined(VL53L5_SEQUENCE_IDX_ON)) +#define VL53L5_PER_ZONE_RESULTS_ON +#endif + +#if (defined(VL53L5_PEAK_RATE_KCPS_PER_SPAD_ON) || \ + defined(VL53L5_MEDIAN_PHASE_ON) || \ + defined(VL53L5_RATE_SIGMA_KCPS_PER_SPAD_ON) || \ + defined(VL53L5_TARGET_ZSCORE_ON) || defined(VL53L5_RANGE_SIGMA_MM_ON) \ + || defined(VL53L5_MEDIAN_RANGE_MM_ON) || \ + defined(VL53L5_START_RANGE_MM_ON) || defined(VL53L5_END_RANGE_MM_ON) \ + || defined(VL53L5_MIN_RANGE_DELTA_MM_ON) || \ + defined(VL53L5_MAX_RANGE_DELTA_MM_ON) || \ + defined(VL53L5_TARGET_REFLECTANCE_EST_PC_ON) || \ + defined(VL53L5_TARGET_STATUS_ON)) +#define VL53L5_PER_TGT_RESULTS_ON +#endif + +#if (defined(VL53L5_SHARPENER_GROUP_INDEX_ON) || \ + defined(VL53L5_SHARPENER_CONFIDENCE_ON)) +#define VL53L5_SHARPENER_TARGET_DATA_ON +#endif + +#if (defined(VL53L5_ZONE_THRESH_STATUS_BYTES_ON)) +#define VL53L5_ZONE_THRESH_STATUS_ON +#endif + +#if (defined(VL53L5_DEPTH16_ON)) +#define VL53L5_D16_PER_TARGET_DATA_ON +#endif + +#ifdef VL53L5_META_DATA_ON +#define VL53L5_META_DATA_BLOCK_SZ \ + (VL53L5_BUF_META_DATA_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_META_DATA_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_COMMON_DATA_ON +#define VL53L5_COMMON_DATA_BLOCK_SZ \ + (VL53L5_RNG_COMMON_DATA_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_COMMON_DATA_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_RNG_TIMING_DATA_ON +#define VL53L5_RNG_TIMING_DATA_BLOCK_SZ \ + (VL53L5_RNG_TIMING_DATA_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_RNG_TIMING_DATA_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_AMB_RATE_KCPS_PER_SPAD_ON +#define VL53L5_AMB_RATE_KCPS_PER_SPAD_BLOCK_SZ \ + (VL53L5_RPZD_AMB_RATE_KCPS_PER_SPAD_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_AMB_RATE_KCPS_PER_SPAD_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_EFFECTIVE_SPAD_COUNT_ON +#define VL53L5_EFFECTIVE_SPAD_COUNT_BLOCK_SZ \ + (VL53L5_RPZD_RNG_EFFECTIVE_SPAD_COUNT_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_EFFECTIVE_SPAD_COUNT_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_AMB_DMAX_MM_ON +#define VL53L5_AMB_DMAX_MM_BLOCK_SZ \ + (VL53L5_RPZD_AMB_DMAX_MM_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_AMB_DMAX_MM_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_SILICON_TEMP_DEGC_START_ON +#define VL53L5_SILICON_TEMP_DEGC_START_BLOCK_SZ \ + (VL53L5_RPZD_SILICON_TEMP_DEGC_START_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_SILICON_TEMP_DEGC_START_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_SILICON_TEMP_DEGC_END_ON +#define VL53L5_SILICON_TEMP_DEGC_END_BLOCK_SZ \ + (VL53L5_RPZD_SILICON_TEMP_DEGC_END_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_SILICON_TEMP_DEGC_END_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_NO_OF_TARGETS_ON +#define VL53L5_NO_OF_TARGETS_BLOCK_SZ \ + (VL53L5_RPZD_RNG_NO_OF_TARGETS_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_NO_OF_TARGETS_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_ZONE_ID_ON +#define VL53L5_ZONE_ID_BLOCK_SZ \ + (VL53L5_RPZD_RNG_ZONE_ID_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_ZONE_ID_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_SEQUENCE_IDX_ON +#define VL53L5_SEQUENCE_IDX_BLOCK_SZ \ + (VL53L5_RPZD_RNG_SEQUENCE_IDX_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_SEQUENCE_IDX_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_PEAK_RATE_KCPS_PER_SPAD_ON +#define VL53L5_PEAK_RATE_KCPS_PER_SPAD_BLOCK_SZ \ + (VL53L5_RPTD_PEAK_RATE_KCPS_PER_SPAD_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_PEAK_RATE_KCPS_PER_SPAD_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_MEDIAN_PHASE_ON +#define VL53L5_MEDIAN_PHASE_BLOCK_SZ \ + (VL53L5_RPTD_MEDIAN_PHASE_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_MEDIAN_PHASE_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_RATE_SIGMA_KCPS_PER_SPAD_ON +#define VL53L5_RATE_SIGMA_KCPS_PER_SPAD_BLOCK_SZ \ + (VL53L5_RPTD_RATE_SIGMA_KCPS_PER_SPAD_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_RATE_SIGMA_KCPS_PER_SPAD_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_TARGET_ZSCORE_ON +#define VL53L5_TARGET_ZSCORE_BLOCK_SZ \ + (VL53L5_RPTD_TARGET_ZSCORE_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_TARGET_ZSCORE_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_RANGE_SIGMA_MM_ON +#define VL53L5_RANGE_SIGMA_MM_BLOCK_SZ \ + (VL53L5_RPTD_RANGE_SIGMA_MM_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_RANGE_SIGMA_MM_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_MEDIAN_RANGE_MM_ON +#define VL53L5_MEDIAN_RANGE_MM_BLOCK_SZ \ + (VL53L5_RPTD_MEDIAN_RANGE_MM_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_MEDIAN_RANGE_MM_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_START_RANGE_MM_ON +#define VL53L5_START_RANGE_MM_BLOCK_SZ \ + (VL53L5_RPTD_START_RANGE_MM_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_START_RANGE_MM_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_END_RANGE_MM_ON +#define VL53L5_END_RANGE_MM_BLOCK_SZ \ + (VL53L5_RPTD_END_RANGE_MM_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_END_RANGE_MM_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_MIN_RANGE_DELTA_MM_ON +#define VL53L5_MIN_RANGE_DELTA_MM_BLOCK_SZ \ + (VL53L5_RPTD_MIN_RANGE_DELTA_MM_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_MIN_RANGE_DELTA_MM_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_MAX_RANGE_DELTA_MM_ON +#define VL53L5_MAX_RANGE_DELTA_MM_BLOCK_SZ \ + (VL53L5_RPTD_MAX_RANGE_DELTA_MM_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_MAX_RANGE_DELTA_MM_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_TARGET_REFLECTANCE_EST_PC_ON +#define VL53L5_TARGET_REFLECTANCE_EST_PC_BLOCK_SZ \ + (VL53L5_RPTD_TARGET_REFLECTANCE_EST_PC_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_TARGET_REFLECTANCE_EST_PC_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_TARGET_STATUS_ON +#define VL53L5_TARGET_STATUS_BLOCK_SZ \ + (VL53L5_RPTD_TARGET_STATUS_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_TARGET_STATUS_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_REF_TIMING_DATA_ON +#define VL53L5_REF_TIMING_DATA_BLOCK_SZ \ + (VL53L5_RNG_TIMING_DATA_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_REF_TIMING_DATA_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_REF_CHANNEL_DATA_ON +#define VL53L5_REF_CHANNEL_DATA_BLOCK_SZ \ + (VL53L5_REF_CHANNEL_DATA_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_REF_CHANNEL_DATA_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_REF_TARGET_DATA_ON +#define VL53L5_REF_TARGET_DATA_BLOCK_SZ \ + (VL53L5_REF_TARGET_DATA_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_REF_TARGET_DATA_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_SHARPENER_GROUP_INDEX_ON +#define VL53L5_SHARPENER_GROUP_INDEX_BLOCK_SZ \ + (VL53L5_STD_SHARPENER_GROUP_INDEX_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_SHARPENER_GROUP_INDEX_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_SHARPENER_CONFIDENCE_ON +#define VL53L5_SHARPENER_CONFIDENCE_BLOCK_SZ \ + (VL53L5_STD_SHARPENER_CONFIDENCE_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_SHARPENER_CONFIDENCE_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_ZONE_THRESH_STATUS_BYTES_ON +#define VL53L5_ZONE_THRESH_STATUS_BYTES_BLOCK_SZ \ + (VL53L5_ZTSA_ZONE_THRESH_STATUS_BYTES_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_ZONE_THRESH_STATUS_BYTES_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_DYN_XTALK_OP_PERSISTENT_DATA_ON +#define VL53L5_DYN_XTALK_OP_PERSISTENT_DATA_BLOCK_SZ \ + (VL53L5_DYN_XTALK_PERSISTENT_DATA_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_DYN_XTALK_OP_PERSISTENT_DATA_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_GD_OP_STATUS_ON +#define VL53L5_GD_OP_STATUS_BLOCK_SZ \ + (VL53L5_GD_OP_STATUS_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_GD_OP_STATUS_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_DEPTH16_ON +#define VL53L5_DEPTH16_BLOCK_SZ \ + (VL53L5_DPTD_DEPTH16_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_DEPTH16_BLOCK_SZ \ + 0 +#endif + +#define VL53L5_DUMMY_BYTES_SZ \ + VL53L5_CONFIG_DUMMY_BYTES_SZ +#define VL53L5_DUMMY_FOOTER_BYTES_SZ \ + 4U + +#define VL53L5_TCPM_PATCH_0_MAP_RESULTS_PACKET_META_SIZE \ + (VL53L5_DCI_UI_DEV_INFO_SZ \ + + VL53L5_DUMMY_BYTES_SZ \ + + (2 * VL53L5_DCI_UI_RNG_DATA_HEADER_SZ) \ + + (3 * VL53L5_DCI_UI_PACKED_DATA_BH_SZ) \ + + VL53L5_DUMMY_FOOTER_BYTES_SZ) + +#define VL53L5_TCPM_PATCH_0_MAP_RESULTS_TOTAL_SIZE_BYTES \ + (VL53L5_TCPM_PATCH_0_MAP_RESULTS_PACKET_META_SIZE \ + + VL53L5_META_DATA_BLOCK_SZ \ + + VL53L5_COMMON_DATA_BLOCK_SZ \ + + VL53L5_RNG_TIMING_DATA_BLOCK_SZ \ + + VL53L5_AMB_RATE_KCPS_PER_SPAD_BLOCK_SZ \ + + VL53L5_EFFECTIVE_SPAD_COUNT_BLOCK_SZ \ + + VL53L5_AMB_DMAX_MM_BLOCK_SZ \ + + VL53L5_SILICON_TEMP_DEGC_START_BLOCK_SZ \ + + VL53L5_SILICON_TEMP_DEGC_END_BLOCK_SZ \ + + VL53L5_NO_OF_TARGETS_BLOCK_SZ \ + + VL53L5_ZONE_ID_BLOCK_SZ \ + + VL53L5_SEQUENCE_IDX_BLOCK_SZ \ + + VL53L5_PEAK_RATE_KCPS_PER_SPAD_BLOCK_SZ \ + + VL53L5_MEDIAN_PHASE_BLOCK_SZ \ + + VL53L5_RATE_SIGMA_KCPS_PER_SPAD_BLOCK_SZ \ + + VL53L5_TARGET_ZSCORE_BLOCK_SZ \ + + VL53L5_RANGE_SIGMA_MM_BLOCK_SZ \ + + VL53L5_MEDIAN_RANGE_MM_BLOCK_SZ \ + + VL53L5_START_RANGE_MM_BLOCK_SZ \ + + VL53L5_END_RANGE_MM_BLOCK_SZ \ + + VL53L5_MIN_RANGE_DELTA_MM_BLOCK_SZ \ + + VL53L5_MAX_RANGE_DELTA_MM_BLOCK_SZ \ + + VL53L5_TARGET_REFLECTANCE_EST_PC_BLOCK_SZ \ + + VL53L5_TARGET_STATUS_BLOCK_SZ \ + + VL53L5_REF_TIMING_DATA_BLOCK_SZ \ + + VL53L5_REF_CHANNEL_DATA_BLOCK_SZ \ + + VL53L5_REF_TARGET_DATA_BLOCK_SZ \ + + VL53L5_SHARPENER_GROUP_INDEX_BLOCK_SZ \ + + VL53L5_SHARPENER_CONFIDENCE_BLOCK_SZ \ + + VL53L5_ZONE_THRESH_STATUS_BYTES_BLOCK_SZ \ + + VL53L5_DYN_XTALK_OP_PERSISTENT_DATA_BLOCK_SZ \ + + VL53L5_GD_OP_STATUS_BLOCK_SZ \ + + VL53L5_DEPTH16_BLOCK_SZ) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_results_decode.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_results_decode.h new file mode 100644 index 000000000000..213dd4c854c5 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_results_decode.h @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_0_RESULTS_DECODE_H__ +#define __VL53L5_TCPM_PATCH_0_RESULTS_DECODE_H__ + +#include "vl53l5_platform_user_data.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int32_t vl53l5_tcpm_patch_0_results_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_results_dev_path.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_results_dev_path.h new file mode 100644 index 000000000000..a29f081b5caa --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_results_dev_path.h @@ -0,0 +1,120 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_0_RESULTS_DEV_PATH_H__ +#define __VL53L5_TCPM_PATCH_0_RESULTS_DEV_PATH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_TCPM_PATCH_0_RESULTS_DEV(p_dev) \ + ((p_dev)->host_dev.ptcpm_patch_0_results_dev) +#define VL53L5_TCPM_PATCH_0_META_DATA(p_dev) \ + VL53L5_TCPM_PATCH_0_RESULTS_DEV(p_dev)->meta_data +#define VL53L5_TCPM_PATCH_0_COMMON_DATA(p_dev) \ + VL53L5_TCPM_PATCH_0_RESULTS_DEV(p_dev)->common_data +#define VL53L5_TCPM_PATCH_0_RNG_TIMING_DATA(p_dev) \ + VL53L5_TCPM_PATCH_0_RESULTS_DEV(p_dev)->rng_timing_data +#define VL53L5_TCPM_PATCH_0_PER_ZONE_RESULTS(p_dev) \ + VL53L5_TCPM_PATCH_0_RESULTS_DEV(p_dev)->per_zone_results +#define VL53L5_TCPM_PATCH_0_PER_TGT_RESULTS(p_dev) \ + VL53L5_TCPM_PATCH_0_RESULTS_DEV(p_dev)->per_tgt_results +#define VL53L5_TCPM_PATCH_0_REF_TIMING_DATA(p_dev) \ + VL53L5_TCPM_PATCH_0_RESULTS_DEV(p_dev)->ref_timing_data +#define VL53L5_TCPM_PATCH_0_REF_CHANNEL_DATA(p_dev) \ + VL53L5_TCPM_PATCH_0_RESULTS_DEV(p_dev)->ref_channel_data +#define VL53L5_TCPM_PATCH_0_REF_TARGET_DATA(p_dev) \ + VL53L5_TCPM_PATCH_0_RESULTS_DEV(p_dev)->ref_target_data +#define VL53L5_TCPM_PATCH_0_SHARPENER_TARGET_DATA(p_dev) \ + VL53L5_TCPM_PATCH_0_RESULTS_DEV(p_dev)->sharpener_target_data +#define VL53L5_TCPM_PATCH_0_ZONE_THRESH_STATUS(p_dev) \ + VL53L5_TCPM_PATCH_0_RESULTS_DEV(p_dev)->zone_thresh_status +#define VL53L5_TCPM_PATCH_0_DYN_XTALK_OP_PERSISTENT_DATA(p_dev) \ + VL53L5_TCPM_PATCH_0_RESULTS_DEV(p_dev)->dyn_xtalk_op_persistent_data +#define VL53L5_TCPM_PATCH_0_D16_BUF_META(p_dev) \ + VL53L5_TCPM_PATCH_0_RESULTS_DEV(p_dev)->d16_buf_meta +#define VL53L5_TCPM_PATCH_0_D16_PKT_META(p_dev) \ + VL53L5_TCPM_PATCH_0_RESULTS_DEV(p_dev)->d16_pkt_meta +#define VL53L5_TCPM_PATCH_0_D16_BLK_META(p_dev) \ + VL53L5_TCPM_PATCH_0_RESULTS_DEV(p_dev)->d16_blk_meta +#define VL53L5_TCPM_PATCH_0_D16_BUF_DATA(p_dev) \ + VL53L5_TCPM_PATCH_0_RESULTS_DEV(p_dev)->d16_buf_data +#define VL53L5_TCPM_PATCH_0_SZ_COMMON_DATA(p_dev) \ + VL53L5_TCPM_PATCH_0_RESULTS_DEV(p_dev)->sz_common_data +#define VL53L5_TCPM_PATCH_0_SZ_ZONE_DATA(p_dev) \ + VL53L5_TCPM_PATCH_0_RESULTS_DEV(p_dev)->sz_zone_data +#define VL53L5_TCPM_PATCH_0_SZ_TARGET_DATA(p_dev) \ + VL53L5_TCPM_PATCH_0_RESULTS_DEV(p_dev)->sz_target_data +#define VL53L5_TCPM_PATCH_0_GD_OP_STATUS(p_dev) \ + VL53L5_TCPM_PATCH_0_RESULTS_DEV(p_dev)->gd_op_status +#define VL53L5_TCPM_PATCH_0_GD_OP_DATA(p_dev) \ + VL53L5_TCPM_PATCH_0_RESULTS_DEV(p_dev)->gd_op_data +#define VL53L5_TCPM_PATCH_0_GD2_OP_DATA(p_dev) \ + VL53L5_TCPM_PATCH_0_RESULTS_DEV(p_dev)->gd2_op_data +#define VL53L5_TCPM_PATCH_0_GD2_OP_DATA_ARR(p_dev) \ + VL53L5_TCPM_PATCH_0_RESULTS_DEV(p_dev)->gd2_op_data_arr +#define VL53L5_TCPM_PATCH_0_D16_PER_TARGET_DATA(p_dev) \ + VL53L5_TCPM_PATCH_0_RESULTS_DEV(p_dev)->d16_per_target_data + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_results_enum_type.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_results_enum_type.h new file mode 100644 index 000000000000..2a4c4f06f05b --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_results_enum_type.h @@ -0,0 +1,138 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_0_RESULTS_ENUM_TYPE_H__ +#define __VL53L5_TCPM_PATCH_0_RESULTS_ENUM_TYPE_H__ + +#include "vl53l5_dci_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_BUF_META_DATA_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_RNG_COMMON_DATA_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_RNG_TIMING_DATA_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_RPZD_AMB_RATE_KCPS_PER_SPAD_TYPE \ + ((enum block_format_type) 0x4) +#define VL53L5_RPZD_RNG_EFFECTIVE_SPAD_COUNT_TYPE \ + ((enum block_format_type) 0x4) +#define VL53L5_RPZD_AMB_DMAX_MM_TYPE \ + ((enum block_format_type) 0x2) +#define VL53L5_RPZD_SILICON_TEMP_DEGC_START_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_RPZD_SILICON_TEMP_DEGC_END_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_RPZD_RNG_NO_OF_TARGETS_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_RPZD_RNG_ZONE_ID_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_RPZD_RNG_SEQUENCE_IDX_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_RPTD_PEAK_RATE_KCPS_PER_SPAD_TYPE \ + ((enum block_format_type) 0x4) +#define VL53L5_RPTD_MEDIAN_PHASE_TYPE \ + ((enum block_format_type) 0x4) +#define VL53L5_RPTD_RATE_SIGMA_KCPS_PER_SPAD_TYPE \ + ((enum block_format_type) 0x4) +#define VL53L5_RPTD_TARGET_ZSCORE_TYPE \ + ((enum block_format_type) 0x2) +#define VL53L5_RPTD_RANGE_SIGMA_MM_TYPE \ + ((enum block_format_type) 0x2) +#define VL53L5_RPTD_MEDIAN_RANGE_MM_TYPE \ + ((enum block_format_type) 0x2) +#define VL53L5_RPTD_START_RANGE_MM_TYPE \ + ((enum block_format_type) 0x2) +#define VL53L5_RPTD_END_RANGE_MM_TYPE \ + ((enum block_format_type) 0x2) +#define VL53L5_RPTD_MIN_RANGE_DELTA_MM_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_RPTD_MAX_RANGE_DELTA_MM_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_RPTD_TARGET_REFLECTANCE_EST_PC_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_RPTD_TARGET_STATUS_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_REF_CHANNEL_DATA_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_REF_TARGET_DATA_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_STD_SHARPENER_GROUP_INDEX_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_STD_SHARPENER_CONFIDENCE_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_ZTSA_ZONE_THRESH_STATUS_BYTES_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_DYN_XTALK_PERSISTENT_DATA_TYPE \ + ((enum block_format_type) 0x0) + +#define VL53L5_GD_OP_STATUS_TYPE \ + ((enum block_format_type) 0x0) + +#define VL53L5_DPTD_DEPTH16_TYPE \ + ((enum block_format_type) 0x2) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_results_map.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_results_map.h new file mode 100644 index 000000000000..daaca87da1e3 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_results_map.h @@ -0,0 +1,148 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_0_RESULTS_MAP_H__ +#define __VL53L5_TCPM_PATCH_0_RESULTS_MAP_H__ + +#include "vl53l5_tcpm_patch_defs.h" +#include "vl53l5_tcpm_patch_version_structs.h" +#include "dci_structs.h" +#include "dci_union_structs.h" +#include "dci_defs.h" +#include "dci_ui_union_structs.h" +#include "dci_patch_defs.h" +#include "dci_patch_structs.h" +#include "dci_patch_union_structs.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct vl53l5_tcpm_patch_0_results_dev_t { +#ifdef VL53L5_META_DATA_ON + + struct vl53l5_range_meta_data_t meta_data; +#endif + +#ifdef VL53L5_COMMON_DATA_ON + + struct vl53l5_range_common_data_t common_data; +#endif + +#ifdef VL53L5_RNG_TIMING_DATA_ON + + struct vl53l5_range_timing_data_t rng_timing_data; +#endif + +#ifdef VL53L5_PER_ZONE_RESULTS_ON + + struct vl53l5_range_per_zone_results_t per_zone_results; +#endif + +#ifdef VL53L5_PER_TGT_RESULTS_ON + + struct vl53l5_range_per_tgt_results_t per_tgt_results; +#endif + +#ifdef VL53L5_REF_TIMING_DATA_ON + + struct vl53l5_range_timing_data_t ref_timing_data; +#endif + +#ifdef VL53L5_REF_CHANNEL_DATA_ON + + struct vl53l5_ref_channel_data_t ref_channel_data; +#endif + +#ifdef VL53L5_REF_TARGET_DATA_ON + + struct vl53l5_ref_target_data_t ref_target_data; +#endif + +#ifdef VL53L5_SHARPENER_TARGET_DATA_ON + struct vl53l5_sharpener_target_data_t sharpener_target_data; +#endif + +#ifdef VL53L5_ZONE_THRESH_STATUS_ON + struct vl53l5_zone_thresh_status_array_t zone_thresh_status; +#endif + +#ifdef VL53L5_DYN_XTALK_OP_PERSISTENT_DATA_ON + + struct vl53l5_dyn_xtalk_persistent_data_t dyn_xtalk_op_persistent_data; +#endif + +#ifdef VL53L5_GD_OP_STATUS_ON + + struct vl53l5_gd_op_status_t gd_op_status; +#endif + +#ifdef VL53L5_D16_PER_TARGET_DATA_ON + + struct vl53l5_d16_per_tgt_results_t d16_per_target_data; +#endif + +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_results_map_bh.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_results_map_bh.h new file mode 100644 index 000000000000..422179d7f893 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_results_map_bh.h @@ -0,0 +1,138 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_0_RESULTS_MAP_BH_H__ +#define __VL53L5_TCPM_PATCH_0_RESULTS_MAP_BH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_TCPM_0_RNG_META_DATA_BH \ + ((uint32_t) 0x000800c0U) +#define VL53L5_TCPM_0_RNG_COMMON_DATA_BH \ + ((uint32_t) 0x00140080U) +#define VL53L5_TCPM_0_RNG_TIMING_DATA_BH \ + ((uint32_t) 0x001c00c0U) +#define VL53L5_TCPM_0_RNG_PER_ZONE_DATA_AMB_RATE_KCPS_PER_SPAD_BH \ + ((uint32_t) 0x00280444U) +#define VL53L5_TCPM_0_RNG_PER_ZONE_DATA_RNG__EFFECTIVE_SPAD_COUNT_BH \ + ((uint32_t) 0x01380444U) +#define VL53L5_TCPM_0_RNG_PER_ZONE_DATA_AMB_DMAX_MM_BH \ + ((uint32_t) 0x02480442U) +#define VL53L5_TCPM_0_RNG_PER_ZONE_DATA_SILICON_TEMP_DEGC__START_BH \ + ((uint32_t) 0x02d00441U) +#define VL53L5_TCPM_0_RNG_PER_ZONE_DATA_SILICON_TEMP_DEGC__END_BH \ + ((uint32_t) 0x03140441U) +#define VL53L5_TCPM_0_RNG_PER_ZONE_DATA_RNG__NO_OF_TARGETS_BH \ + ((uint32_t) 0x03580441U) +#define VL53L5_TCPM_0_RNG_PER_ZONE_DATA_RNG__ZONE_ID_BH \ + ((uint32_t) 0x039c0441U) +#define VL53L5_TCPM_0_RNG_PER_ZONE_DATA_RNG__SEQUENCE_IDX_BH \ + ((uint32_t) 0x03e00441U) +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_PEAK_RATE_KCPS_PER_SPAD_BH \ + ((uint32_t) 0x04241104U) +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_MEDIAN_PHASE_BH \ + ((uint32_t) 0x08641104U) +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_RATE_SIGMA_KCPS_PER_SPAD_BH \ + ((uint32_t) 0x0ca41104U) +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_TARGET_ZSCORE_BH \ + ((uint32_t) 0x10e41102U) +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_RANGE_SIGMA_MM_BH \ + ((uint32_t) 0x13041102U) +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_MEDIAN_RANGE_MM_BH \ + ((uint32_t) 0x15241102U) +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_START_RANGE_MM_BH \ + ((uint32_t) 0x17441102U) +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_END_RANGE_MM_BH \ + ((uint32_t) 0x19641102U) +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_MIN_RANGE_DELTA_MM_BH \ + ((uint32_t) 0x1b841101U) +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_MAX_RANGE_DELTA_MM_BH \ + ((uint32_t) 0x1c941101U) +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_TARGET_REFLECTANCE_EST_PC_BH \ + ((uint32_t) 0x1da41101U) +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_TARGET_STATUS_BH \ + ((uint32_t) 0x1eb41101U) +#define VL53L5_TCPM_0_REF_TIMING_DATA_BH \ + ((uint32_t) 0x1fc400c0U) +#define VL53L5_TCPM_0_REF_CHANNEL_DATA_BH \ + ((uint32_t) 0x1fd00100U) +#define VL53L5_TCPM_0_REF_TARGET_DATA_BH \ + ((uint32_t) 0x1fe001c0U) +#define VL53L5_TCPM_0_SHARPENER_TARGET_DATA_SHARPENER__GROUP_INDEX_BH \ + ((uint32_t) 0x1ffc1101U) +#define VL53L5_TCPM_0_SHARPENER_TARGET_DATA_SHARPENER__CONFIDENCE_BH \ + ((uint32_t) 0x210c1101U) +#define VL53L5_TCPM_0_ZONE_THRESH_STATUS_ZONE_THRESH_STATUS_BYTES_BH \ + ((uint32_t) 0x221c0081U) +#define VL53L5_TCPM_0_DYN_XTALK_OP_PERSISTENT_DATA_BH \ + ((uint32_t) 0x222401c0U) + +#define VL53L5_TCPM_0_GD_OP_STATUS_BH \ + ((uint32_t) 0x292c00c0U) + +#define VL53L5_TCPM_0_D16_PER_TARGET_DATA_DEPTH16_BH \ + ((uint32_t) 0x29b81102U) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_results_map_idx.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_results_map_idx.h new file mode 100644 index 000000000000..cf15338aa0e5 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_0_results_map_idx.h @@ -0,0 +1,179 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_0_RESULTS_MAP_IDX_H__ +#define __VL53L5_TCPM_PATCH_0_RESULTS_MAP_IDX_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_TCPM_0_RNG_META_DATA_IDX \ + ((uint16_t) 0x0008) + +#define VL53L5_TCPM_0_RNG_COMMON_DATA_IDX \ + ((uint16_t) 0x0014) + +#define VL53L5_TCPM_0_RNG_TIMING_DATA_IDX \ + ((uint16_t) 0x001c) + +#define VL53L5_TCPM_0_RNG_PER_ZONE_DATA_AMB_RATE_KCPS_PER_SPAD_IDX \ + ((uint16_t) 0x0028) +#define VL53L5_TCPM_0_RNG_PER_ZONE_DATA_IDX \ + ((uint16_t) 0x0028) + +#define VL53L5_TCPM_0_RNG_PER_ZONE_DATA_RNG__EFFECTIVE_SPAD_COUNT_IDX \ + ((uint16_t) 0x0138) + +#define VL53L5_TCPM_0_RNG_PER_ZONE_DATA_AMB_DMAX_MM_IDX \ + ((uint16_t) 0x0248) + +#define VL53L5_TCPM_0_RNG_PER_ZONE_DATA_SILICON_TEMP_DEGC__START_IDX \ + ((uint16_t) 0x02d0) + +#define VL53L5_TCPM_0_RNG_PER_ZONE_DATA_SILICON_TEMP_DEGC__END_IDX \ + ((uint16_t) 0x0314) + +#define VL53L5_TCPM_0_RNG_PER_ZONE_DATA_RNG__NO_OF_TARGETS_IDX \ + ((uint16_t) 0x0358) + +#define VL53L5_TCPM_0_RNG_PER_ZONE_DATA_RNG__ZONE_ID_IDX \ + ((uint16_t) 0x039c) + +#define VL53L5_TCPM_0_RNG_PER_ZONE_DATA_RNG__SEQUENCE_IDX_IDX \ + ((uint16_t) 0x03e0) + +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_PEAK_RATE_KCPS_PER_SPAD_IDX \ + ((uint16_t) 0x0424) +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_IDX \ + ((uint16_t) 0x0424) + +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_MEDIAN_PHASE_IDX \ + ((uint16_t) 0x0864) + +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_RATE_SIGMA_KCPS_PER_SPAD_IDX \ + ((uint16_t) 0x0ca4) + +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_TARGET_ZSCORE_IDX \ + ((uint16_t) 0x10e4) + +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_RANGE_SIGMA_MM_IDX \ + ((uint16_t) 0x1304) + +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_MEDIAN_RANGE_MM_IDX \ + ((uint16_t) 0x1524) + +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_START_RANGE_MM_IDX \ + ((uint16_t) 0x1744) + +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_END_RANGE_MM_IDX \ + ((uint16_t) 0x1964) + +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_MIN_RANGE_DELTA_MM_IDX \ + ((uint16_t) 0x1b84) + +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_MAX_RANGE_DELTA_MM_IDX \ + ((uint16_t) 0x1c94) + +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_TARGET_REFLECTANCE_EST_PC_IDX \ + ((uint16_t) 0x1da4) + +#define VL53L5_TCPM_0_RNG_PER_TARGET_DATA_TARGET_STATUS_IDX \ + ((uint16_t) 0x1eb4) + +#define VL53L5_TCPM_0_REF_TIMING_DATA_IDX \ + ((uint16_t) 0x1fc4) + +#define VL53L5_TCPM_0_REF_CHANNEL_DATA_IDX \ + ((uint16_t) 0x1fd0) + +#define VL53L5_TCPM_0_REF_TARGET_DATA_IDX \ + ((uint16_t) 0x1fe0) + +#define VL53L5_TCPM_0_SHARPENER_TARGET_DATA_SHARPENER__GROUP_INDEX_IDX \ + ((uint16_t) 0x1ffc) +#define VL53L5_TCPM_0_SHARPENER_TARGET_DATA_IDX \ + ((uint16_t) 0x1ffc) + +#define VL53L5_TCPM_0_SHARPENER_TARGET_DATA_SHARPENER__CONFIDENCE_IDX \ + ((uint16_t) 0x210c) + +#define VL53L5_TCPM_0_ZONE_THRESH_STATUS_ZONE_THRESH_STATUS_BYTES_IDX \ + ((uint16_t) 0x221c) +#define VL53L5_TCPM_0_ZONE_THRESH_STATUS_IDX \ + ((uint16_t) 0x221c) + +#define VL53L5_TCPM_0_DYN_XTALK_OP_PERSISTENT_DATA_IDX \ + ((uint16_t) 0x2224) + +#define VL53L5_TCPM_0_GD_OP_STATUS_IDX \ + ((uint16_t) 0x292c) + +#define VL53L5_TCPM_0_D16_PER_TARGET_DATA_DEPTH16_IDX \ + ((uint16_t) 0x29b8) +#define VL53L5_TCPM_0_D16_PER_TARGET_DATA_IDX \ + ((uint16_t) 0x29b8) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_calibration_decode.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_calibration_decode.h new file mode 100644 index 000000000000..a3a6be616e53 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_calibration_decode.h @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_1_CALIBRATION_DECODE_H__ +#define __VL53L5_TCPM_PATCH_1_CALIBRATION_DECODE_H__ + +#include "vl53l5_platform_user_data.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int32_t vl53l5_tcpm_patch_1_calibration_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_calibration_dev_path.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_calibration_dev_path.h new file mode 100644 index 000000000000..7e88cead3914 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_calibration_dev_path.h @@ -0,0 +1,74 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_1_CALIBRATION_DEV_PATH_H__ +#define __VL53L5_TCPM_PATCH_1_CALIBRATION_DEV_PATH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_TCPM_PATCH_1_CALIBRATION_DEV(p_dev) \ + ((p_dev)->host_dev.ptcpm_patch_1_calibration_dev) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_calibration_enum_type.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_calibration_enum_type.h new file mode 100644 index 000000000000..17be8767f47f --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_calibration_enum_type.h @@ -0,0 +1,73 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_1_CALIBRATION_ENUM_TYPE_H__ +#define __VL53L5_TCPM_PATCH_1_CALIBRATION_ENUM_TYPE_H__ + +#include "vl53l5_dci_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_calibration_map.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_calibration_map.h new file mode 100644 index 000000000000..b06e5d96bd41 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_calibration_map.h @@ -0,0 +1,80 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_1_CALIBRATION_MAP_H__ +#define __VL53L5_TCPM_PATCH_1_CALIBRATION_MAP_H__ + +#include "vl53l5_tcpm_patch_defs.h" +#include "vl53l5_tcpm_patch_version_structs.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct vl53l5_tcpm_patch_1_calibration_dev_t { + + uint32_t dummy; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_calibration_map_bh.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_calibration_map_bh.h new file mode 100644 index 000000000000..9926d24ccaf0 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_calibration_map_bh.h @@ -0,0 +1,71 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_1_CALIBRATION_MAP_BH_H__ +#define __VL53L5_TCPM_PATCH_1_CALIBRATION_MAP_BH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_calibration_map_idx.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_calibration_map_idx.h new file mode 100644 index 000000000000..7a75881a1917 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_calibration_map_idx.h @@ -0,0 +1,73 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_1_CALIBRATION_MAP_IDX_H__ +#define __VL53L5_TCPM_PATCH_1_CALIBRATION_MAP_IDX_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_core_decode.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_core_decode.h new file mode 100644 index 000000000000..f43fef826135 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_core_decode.h @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_1_CORE_DECODE_H__ +#define __VL53L5_TCPM_PATCH_1_CORE_DECODE_H__ + +#include "vl53l5_platform_user_data.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int32_t vl53l5_tcpm_patch_1_core_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_core_dev_path.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_core_dev_path.h new file mode 100644 index 000000000000..ba946c9eade3 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_core_dev_path.h @@ -0,0 +1,78 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_1_CORE_DEV_PATH_H__ +#define __VL53L5_TCPM_PATCH_1_CORE_DEV_PATH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_TCPM_PATCH_1_CORE_DEV(p_dev) \ + ((p_dev)->host_dev.ptcpm_patch_1_core_dev) +#define VL53L5_TCPM_PATCH_1_TCPM_PATCH_MAP_DEBUG_WORD(p_dev) \ + VL53L5_TCPM_PATCH_1_CORE_DEV(p_dev)->tcpm_patch_map_debug_word +#define VL53L5_TCPM_PATCH_1_TCPM_PATCH_MAP_DEBUG2_WORD(p_dev) \ + VL53L5_TCPM_PATCH_1_CORE_DEV(p_dev)->tcpm_patch_map_debug2_word + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_core_enum_type.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_core_enum_type.h new file mode 100644 index 000000000000..3cfb49568cc4 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_core_enum_type.h @@ -0,0 +1,78 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_1_CORE_ENUM_TYPE_H__ +#define __VL53L5_TCPM_PATCH_1_CORE_ENUM_TYPE_H__ + +#include "vl53l5_dci_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_TCPM_PATCH_VERSION_GRP_DEBUG_WORD_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_TCPM_PATCH_VERSION_GRP_DEBUG2_WORD_TYPE \ + ((enum block_format_type) 0x0) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_core_map.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_core_map.h new file mode 100644 index 000000000000..2da9e973565a --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_core_map.h @@ -0,0 +1,80 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_1_CORE_MAP_H__ +#define __VL53L5_TCPM_PATCH_1_CORE_MAP_H__ + +#include "vl53l5_tcpm_patch_defs.h" +#include "vl53l5_tcpm_patch_version_structs.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct vl53l5_tcpm_patch_1_core_dev_t { + struct tcpm_patch_version_grp__debug_word_t tcpm_patch_map_debug_word; + struct tcpm_patch_version_grp__debug2_word_t tcpm_patch_map_debug2_word; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_core_map_bh.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_core_map_bh.h new file mode 100644 index 000000000000..cfd7a7313c17 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_core_map_bh.h @@ -0,0 +1,76 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_1_CORE_MAP_BH_H__ +#define __VL53L5_TCPM_PATCH_1_CORE_MAP_BH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_TCPM_1_TCPM_PATCH_MAP_DEBUG_WORD_BH \ + ((uint32_t) 0x00000040U) +#define VL53L5_TCPM_1_TCPM_PATCH_MAP_DEBUG2_WORD_BH \ + ((uint32_t) 0x00040040U) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_core_map_idx.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_core_map_idx.h new file mode 100644 index 000000000000..4f71f1f71c0b --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_core_map_idx.h @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_1_CORE_MAP_IDX_H__ +#define __VL53L5_TCPM_PATCH_1_CORE_MAP_IDX_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_TCPM_1_TCPM_PATCH_MAP_DEBUG_WORD_IDX \ + ((uint16_t) 0x0000) + +#define VL53L5_TCPM_1_TCPM_PATCH_MAP_DEBUG2_WORD_IDX \ + ((uint16_t) 0x0004) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_decode_switch.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_decode_switch.h new file mode 100644 index 000000000000..acec4fb6d6d2 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_decode_switch.h @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_1_DECODE_SWITCH_H__ +#define __VL53L5_TCPM_PATCH_1_DECODE_SWITCH_H__ + +#include "vl53l5_platform_user_data.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int32_t vl53l5_tcpm_patch_1_decode_switch( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_results_config.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_results_config.h new file mode 100644 index 000000000000..e860dbc12e99 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_results_config.h @@ -0,0 +1,92 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_1_RESULTS_CONFIG_H__ +#define __VL53L5_TCPM_PATCH_1_RESULTS_CONFIG_H__ + +#include "vl53l5_results_build_config.h" +#include "dci_defs.h" +#include "dci_ui_memory_defs.h" +#include "dci_size.h" +#include "dci_ui_size.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_DUMMY_BYTES_SZ \ + VL53L5_CONFIG_DUMMY_BYTES_SZ +#define VL53L5_DUMMY_FOOTER_BYTES_SZ \ + 4U + +#define VL53L5_TCPM_PATCH_1_MAP_RESULTS_PACKET_META_SIZE \ + (VL53L5_DCI_UI_DEV_INFO_SZ \ + + VL53L5_DUMMY_BYTES_SZ \ + + (2 * VL53L5_DCI_UI_RNG_DATA_HEADER_SZ) \ + + (3 * VL53L5_DCI_UI_PACKED_DATA_BH_SZ) \ + + VL53L5_DUMMY_FOOTER_BYTES_SZ) + +#define VL53L5_TCPM_PATCH_1_MAP_RESULTS_TOTAL_SIZE_BYTES \ + VL53L5_TCPM_PATCH_1_MAP_RESULTS_PACKET_META_SIZE + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_results_decode.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_results_decode.h new file mode 100644 index 000000000000..420056104dfc --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_results_decode.h @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_1_RESULTS_DECODE_H__ +#define __VL53L5_TCPM_PATCH_1_RESULTS_DECODE_H__ + +#include "vl53l5_platform_user_data.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int32_t vl53l5_tcpm_patch_1_results_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_results_dev_path.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_results_dev_path.h new file mode 100644 index 000000000000..e8c36a67a683 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_results_dev_path.h @@ -0,0 +1,74 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_1_RESULTS_DEV_PATH_H__ +#define __VL53L5_TCPM_PATCH_1_RESULTS_DEV_PATH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_TCPM_PATCH_1_RESULTS_DEV(p_dev) \ + ((p_dev)->host_dev.ptcpm_patch_1_results_dev) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_results_enum_type.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_results_enum_type.h new file mode 100644 index 000000000000..517fa92f4c69 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_results_enum_type.h @@ -0,0 +1,73 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_1_RESULTS_ENUM_TYPE_H__ +#define __VL53L5_TCPM_PATCH_1_RESULTS_ENUM_TYPE_H__ + +#include "vl53l5_dci_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_results_map.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_results_map.h new file mode 100644 index 000000000000..415e6f99b520 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_results_map.h @@ -0,0 +1,80 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_1_RESULTS_MAP_H__ +#define __VL53L5_TCPM_PATCH_1_RESULTS_MAP_H__ + +#include "vl53l5_tcpm_patch_defs.h" +#include "vl53l5_tcpm_patch_version_structs.h" +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct vl53l5_tcpm_patch_1_results_dev_t { + + uint32_t dummy; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_results_map_bh.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_results_map_bh.h new file mode 100644 index 000000000000..d91798d87d40 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_results_map_bh.h @@ -0,0 +1,71 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_1_RESULTS_MAP_BH_H__ +#define __VL53L5_TCPM_PATCH_1_RESULTS_MAP_BH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_results_map_idx.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_results_map_idx.h new file mode 100644 index 000000000000..fa3bb67189f3 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_1_results_map_idx.h @@ -0,0 +1,73 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_1_RESULTS_MAP_IDX_H__ +#define __VL53L5_TCPM_PATCH_1_RESULTS_MAP_IDX_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_defs.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_defs.h new file mode 100644 index 000000000000..600b5297beac --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_defs.h @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_DEFS_H__ +#define __VL53L5_TCPM_PATCH_DEFS_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define TCPM_PATCH_0_IDX_OFFSET \ + ((uint32_t) 0U) + +#define TCPM_PATCH_1_IDX_OFFSET \ + ((uint32_t) 0U) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_version_defs.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_version_defs.h new file mode 100644 index 000000000000..e15ae193a3bb --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_version_defs.h @@ -0,0 +1,84 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_VERSION_DEFS_H__ +#define __VL53L5_TCPM_PATCH_VERSION_DEFS_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define TCPM_PATCH_VERSION_MAP_VERSION_MAJOR \ + ((uint32_t) 2U) +#define TCPM_PATCH_VERSION_MAP_VERSION_MINOR \ + ((uint32_t) 2U) + +#define TCPM_PATCH_VERSION_PATCH_VERSION_MAJOR \ + ((uint32_t) 0U) + +#define TCPM_PATCH_VERSION_PATCH_VERSION_MINOR \ + ((uint32_t) 0U) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_version_size.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_version_size.h new file mode 100644 index 000000000000..399827bf67e5 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_version_size.h @@ -0,0 +1,85 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_VERSION_SIZE_H__ +#define __VL53L5_TCPM_PATCH_VERSION_SIZE_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_TCPM_PATCH_VERSION_GRP_MAP_VERSION_SZ \ + ((uint16_t) 4) + +#define VL53L5_TCPM_PATCH_VERSION_GRP_PATCH_VERSION_SZ \ + ((uint16_t) 4) + +#define VL53L5_TCPM_PATCH_VERSION_GRP_DEBUG_WORD_SZ \ + ((uint16_t) 4) + +#define VL53L5_TCPM_PATCH_VERSION_GRP_DEBUG2_WORD_SZ \ + ((uint16_t) 4) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_version_structs.h b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_version_structs.h new file mode 100644 index 000000000000..97e4943816b7 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/inc/vl53l5_tcpm_patch_version_structs.h @@ -0,0 +1,97 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_TCPM_PATCH_VERSION_STRUCTS_H__ +#define __VL53L5_TCPM_PATCH_VERSION_STRUCTS_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct tcpm_patch_version_grp__map_version_t { + + uint16_t tcpm_patch_map__major; + + uint16_t tcpm_patch_map__minor; +}; + +struct tcpm_patch_version_grp__patch_version_t { + + uint16_t tcpm_patch_version__major; + + uint16_t tcpm_patch_version__minor; +}; + +struct tcpm_patch_version_grp__debug_word_t { + + uint32_t tcpm_patch_map__debug__word; +}; + +struct tcpm_patch_version_grp__debug2_word_t { + + uint32_t tcpm_patch_map__debug2__word; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_patch_api_core.c b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_patch_api_core.c new file mode 100644 index 000000000000..3b17f8a438f2 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_patch_api_core.c @@ -0,0 +1,246 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_patch_api_core.h" +#include "vl53l5_api_core.h" +#include "vl53l5_dci_decode.h" +#include "vl53l5_patch_core_map_bh.h" +#include "vl53l5_tcpm_patch_0_core_map_bh.h" +#include "vl53l5_tcpm_patch_1_core_map_bh.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_patch_error_codes.h" +#include "vl53l5_platform_log.h" +#include "vl53l5_patch_version.h" +#include "vl53l5_patch_version_size.h" +#include "vl53l5_tcpm_patch_version_size.h" + +#define trace_print(level, ...) \ + _LOG_TRACE_PRINT(VL53L5_TRACE_MODULE_API, \ + level, VL53L5_TRACE_FUNCTION_ALL, ##__VA_ARGS__) +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START(VL53L5_TRACE_MODULE_API, fmt, ##__VA_ARGS__) +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END(VL53L5_TRACE_MODULE_API, status, ##__VA_ARGS__) +#define LOG_FUNCTION_END_FMT(status, fmt, ...) \ + _LOG_FUNCTION_END_FMT(VL53L5_TRACE_MODULE_API, status, fmt,\ + ##__VA_ARGS__) +#define LOG_FUNCTION_END_FLUSH(status, ...) \ + do { \ + _LOG_FUNCTION_END(VL53L5_TRACE_MODULE_API, status, ##__VA_ARGS__);\ + _FLUSH_TRACE_TO_OUTPUT();\ + } while (0) + +static int32_t _decode_patch_version_structs( + struct vl53l5_dev_handle_t *p_dev, + struct vl53l5_patch_version_t *p_version); + +int32_t vl53l5_assign_core(struct vl53l5_dev_handle_t *p_dev, + struct vl53l5_core_data_t *p_core) +{ + int32_t status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_ISNULL(p_core)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + p_dev->host_dev.ppatch_core_dev = &p_core->patch; + p_dev->host_dev.ptcpm_patch_0_core_dev = &p_core->tcpm_0_patch; + p_dev->host_dev.ptcpm_patch_1_core_dev = &p_core->tcpm_1_patch; + +exit: + LOG_FUNCTION_END_FLUSH(status); + return status; +} + +int32_t vl53l5_unassign_core(struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + p_dev->host_dev.ppatch_core_dev = NULL; + p_dev->host_dev.ptcpm_patch_0_core_dev = NULL; + p_dev->host_dev.ptcpm_patch_1_core_dev = NULL; +exit: + LOG_FUNCTION_END_FLUSH(status); + return status; +} + +int32_t vl53l5_get_patch_version(struct vl53l5_dev_handle_t *p_dev, + struct vl53l5_patch_version_t *p_version) + +{ + int32_t status = VL53L5_ERROR_NONE; + uint32_t block_headers[] = { + VL53L5_PATCH_VERSION_BH, + VL53L5_TCPM_0_PAGE_BH, + VL53L5_TCPM_0_TCPM_PATCH_VERSION_BH, + }; + uint32_t num_bh = sizeof(block_headers) / sizeof(block_headers[0]); + + LOG_FUNCTION_START(""); + + if (VL53L5_ISNULL(p_dev)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + if (VL53L5_ISNULL(p_version)) { + status = VL53L5_ERROR_INVALID_PARAMS; + goto exit; + } + + status = vl53l5_get_device_parameters(p_dev, block_headers, num_bh); + if (status < STATUS_OK) + goto exit; + + status = _decode_patch_version_structs(p_dev, p_version); + +exit: + LOG_FUNCTION_END_FLUSH(status); + return status; +} + +static int32_t _decode_patch_version_structs( + struct vl53l5_dev_handle_t *p_dev, + struct vl53l5_patch_version_t *p_version) +{ + int32_t status = STATUS_OK; + uint8_t *p_buff = NULL; + uint32_t block_header = 0; + const uint32_t end_of_data_footer_index = 28; + + p_buff = &VL53L5_COMMS_BUFF(p_dev)[VL53L5_UI_DUMMY_BYTES]; + + if ((p_buff[end_of_data_footer_index] & 0x0f) != + DCI_BH__P_TYPE__END_OF_DATA) { + status = VL53L5_DCI_END_BLOCK_ERROR; + + goto exit; + } + + p_buff += 8; + + block_header = vl53l5_decode_uint32_t(BYTE_4, p_buff); + if (block_header != VL53L5_PATCH_VERSION_BH) { + status = VL53L5_PATCH_VERSION_IDX_NOT_PRESENT; + goto exit; + } + p_buff += 4; + p_version->patch_version.ver_major = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + + p_version->patch_version.ver_minor = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + + trace_print(VL53L5_TRACE_LEVEL_INFO, + "Patch version: %u.%u\n", + p_version->patch_version.ver_major, + p_version->patch_version.ver_minor); + + p_buff += 4; + + block_header = vl53l5_decode_uint32_t(4, p_buff); + if (block_header != VL53L5_TCPM_0_TCPM_PATCH_VERSION_BH) { + status = VL53L5_TCPM_VERSION_IDX_NOT_PRESENT; + goto exit; + } + p_buff += 4; + p_version->tcpm_version.ver_major = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + + p_version->tcpm_version.ver_minor = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + + trace_print(VL53L5_TRACE_LEVEL_INFO, + "TCPM version: %u.%u\n", + p_version->tcpm_version.ver_major, + p_version->tcpm_version.ver_minor); + + p_version->patch_driver.ver_major = VL53L5_PATCH_VERSION_MAJOR; + p_version->patch_driver.ver_minor = VL53L5_PATCH_VERSION_MINOR; + p_version->patch_driver.ver_build = VL53L5_PATCH_VERSION_BUILD; + p_version->patch_driver.ver_revision = VL53L5_PATCH_VERSION_REVISION; + + trace_print(VL53L5_TRACE_LEVEL_INFO, + "Patch driver version: %u.%u.%u.%u\n", + p_version->patch_driver.ver_major, + p_version->patch_driver.ver_minor, + p_version->patch_driver.ver_build, + p_version->patch_driver.ver_revision); + +exit: + return status; +} diff --git a/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_patch_calibration_decode.c b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_patch_calibration_decode.c new file mode 100644 index 000000000000..9d2270a5e18c --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_patch_calibration_decode.c @@ -0,0 +1,103 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_patch_version_size.h" +#include "vl53l5_patch_calibration_decode.h" +#include "vl53l5_patch_calibration_enum_type.h" +#include "vl53l5_patch_calibration_map_idx.h" +#include "vl53l5_patch_calibration_dev_path.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform_log.h" + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START( \ + VL53L5_TRACE_MODULE_DCI_DECODE, fmt, ##__VA_ARGS__) + +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) + +#define LOG_FUNCTION_END_FMT(status, ...) \ + _LOG_FUNCTION_END_FMT( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) +int32_t vl53l5_patch_calibration_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + (void)buffer_size; + (void)buffer; + (void)p_dev; + + switch (idx) { + default: + status = VL53L5_INVALID_IDX_DECODE_CMD_ERROR; + break; + } + + LOG_FUNCTION_END(status); + return status; + +} diff --git a/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_patch_core_decode.c b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_patch_core_decode.c new file mode 100644 index 000000000000..1af95f4e94fe --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_patch_core_decode.c @@ -0,0 +1,183 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_patch_version_size.h" +#include "vl53l5_patch_core_decode.h" +#include "vl53l5_patch_core_enum_type.h" +#include "vl53l5_patch_core_map_idx.h" +#include "vl53l5_patch_core_dev_path.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform_log.h" + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START( \ + VL53L5_TRACE_MODULE_DCI_DECODE, fmt, ##__VA_ARGS__) + +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) + +#define LOG_FUNCTION_END_FMT(status, ...) \ + _LOG_FUNCTION_END_FMT( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) +static int32_t _decode_patch_version_grp_map_version( + uint32_t buffer_size, + uint8_t *buffer, + struct patch_version_grp__map_version_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_PATCH_VERSION_GRP_MAP_VERSION_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->patch_map__major = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->patch_map__minor = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _decode_patch_version_grp_patch_version( + uint32_t buffer_size, + uint8_t *buffer, + struct patch_version_grp__patch_version_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_PATCH_VERSION_GRP_PATCH_VERSION_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->patch_version__major = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->patch_version__minor = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +int32_t vl53l5_patch_core_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + switch (idx) { + case DEV_PATCH_MAP_VERSION_IDX: + status = + _decode_patch_version_grp_map_version( + buffer_size, + buffer, + &VL53L5_PATCH_PATCH_MAP_VERSION(p_dev)); + break; + case DEV_PATCH_VERSION_IDX: + status = + _decode_patch_version_grp_patch_version( + buffer_size, + buffer, + &VL53L5_PATCH_PATCH_VERSION(p_dev)); + break; + default: + status = VL53L5_INVALID_IDX_DECODE_CMD_ERROR; + break; + } + + LOG_FUNCTION_END(status); + return status; + +} diff --git a/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_patch_decode_switch.c b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_patch_decode_switch.c new file mode 100644 index 000000000000..b79f4f114895 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_patch_decode_switch.c @@ -0,0 +1,96 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_patch_decode_switch.h" +#include "vl53l5_patch_core_decode.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform_log.h" + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START( \ + VL53L5_TRACE_MODULE_DCI_DECODE, fmt, ##__VA_ARGS__) + +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) + +#define LOG_FUNCTION_END_FMT(status, ...) \ + _LOG_FUNCTION_END_FMT( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) +int32_t vl53l5_patch_decode_switch( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + if ((idx >= 0xd500) && (idx <= 0xd504)) + status = vl53l5_patch_core_decode_cmd( + idx, buffer_size, buffer, p_dev); + + else + status = VL53L5_INVALID_IDX_DECODE_CMD_ERROR; + + LOG_FUNCTION_END(status); + return status; + +} diff --git a/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_patch_results_decode.c b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_patch_results_decode.c new file mode 100644 index 000000000000..50bcb7f788a9 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_patch_results_decode.c @@ -0,0 +1,103 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_patch_version_size.h" +#include "vl53l5_patch_results_decode.h" +#include "vl53l5_patch_results_enum_type.h" +#include "vl53l5_patch_results_map_idx.h" +#include "vl53l5_patch_results_dev_path.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform_log.h" + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START( \ + VL53L5_TRACE_MODULE_DCI_DECODE, fmt, ##__VA_ARGS__) + +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) + +#define LOG_FUNCTION_END_FMT(status, ...) \ + _LOG_FUNCTION_END_FMT( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) +int32_t vl53l5_patch_results_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + (void)buffer_size; + (void)buffer; + (void)p_dev; + + switch (idx) { + default: + status = VL53L5_INVALID_IDX_DECODE_CMD_ERROR; + break; + } + + LOG_FUNCTION_END(status); + return status; + +} diff --git a/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_tcpm_patch_0_calibration_decode.c b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_tcpm_patch_0_calibration_decode.c new file mode 100644 index 000000000000..b2d350765961 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_tcpm_patch_0_calibration_decode.c @@ -0,0 +1,105 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_tcpm_patch_version_size.h" +#include "dci_size.h" +#include "dci_patch_size.h" +#include "vl53l5_tcpm_patch_0_calibration_decode.h" +#include "vl53l5_tcpm_patch_0_calibration_enum_type.h" +#include "vl53l5_tcpm_patch_0_calibration_map_idx.h" +#include "vl53l5_tcpm_patch_0_calibration_dev_path.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform_log.h" + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START( \ + VL53L5_TRACE_MODULE_DCI_DECODE, fmt, ##__VA_ARGS__) + +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) + +#define LOG_FUNCTION_END_FMT(status, ...) \ + _LOG_FUNCTION_END_FMT( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) +int32_t vl53l5_tcpm_patch_0_calibration_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + (void)buffer_size; + (void)buffer; + (void)p_dev; + + switch (idx) { + default: + status = VL53L5_INVALID_IDX_DECODE_CMD_ERROR; + break; + } + + LOG_FUNCTION_END(status); + return status; + +} diff --git a/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_tcpm_patch_0_core_decode.c b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_tcpm_patch_0_core_decode.c new file mode 100644 index 000000000000..169353b460a1 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_tcpm_patch_0_core_decode.c @@ -0,0 +1,185 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_tcpm_patch_version_size.h" +#include "dci_size.h" +#include "dci_patch_size.h" +#include "vl53l5_tcpm_patch_0_core_decode.h" +#include "vl53l5_tcpm_patch_0_core_enum_type.h" +#include "vl53l5_tcpm_patch_0_core_map_idx.h" +#include "vl53l5_tcpm_patch_0_core_dev_path.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform_log.h" + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START( \ + VL53L5_TRACE_MODULE_DCI_DECODE, fmt, ##__VA_ARGS__) + +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) + +#define LOG_FUNCTION_END_FMT(status, ...) \ + _LOG_FUNCTION_END_FMT( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) +static int32_t _decode_vl53l5_tcpm_patch_0_tcpm_patch_version_grp_map_version( + uint32_t buffer_size, + uint8_t *buffer, + struct tcpm_patch_version_grp__map_version_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_TCPM_PATCH_VERSION_GRP_MAP_VERSION_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->tcpm_patch_map__major = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->tcpm_patch_map__minor = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _decode_vl53l5_tcpm_patch_0_tcpm_patch_version_grp_patch_version( + uint32_t buffer_size, + uint8_t *buffer, + struct tcpm_patch_version_grp__patch_version_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_TCPM_PATCH_VERSION_GRP_PATCH_VERSION_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->tcpm_patch_version__major = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->tcpm_patch_version__minor = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +int32_t vl53l5_tcpm_patch_0_core_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + switch (idx) { + case VL53L5_TCPM_0_TCPM_PATCH_MAP_VERSION_IDX: + status = + _decode_vl53l5_tcpm_patch_0_tcpm_patch_version_grp_map_version( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_TCPM_PATCH_MAP_VERSION(p_dev)); + break; + case VL53L5_TCPM_0_TCPM_PATCH_VERSION_IDX: + status = + _decode_vl53l5_tcpm_patch_0_tcpm_patch_version_grp_patch_version( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_TCPM_PATCH_VERSION(p_dev)); + break; + default: + status = VL53L5_INVALID_IDX_DECODE_CMD_ERROR; + break; + } + + LOG_FUNCTION_END(status); + return status; + +} diff --git a/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_tcpm_patch_0_decode_switch.c b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_tcpm_patch_0_decode_switch.c new file mode 100644 index 000000000000..d9d9f400353f --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_tcpm_patch_0_decode_switch.c @@ -0,0 +1,105 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_tcpm_patch_0_decode_switch.h" +#include "vl53l5_tcpm_patch_0_core_decode.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform_log.h" +#ifdef VL53L5_PATCH_DATA_ENABLED +#include "vl53l5_tcpm_patch_0_results_decode.h" +#endif + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START( \ + VL53L5_TRACE_MODULE_DCI_DECODE, fmt, ##__VA_ARGS__) + +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) + +#define LOG_FUNCTION_END_FMT(status, ...) \ + _LOG_FUNCTION_END_FMT( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) +int32_t vl53l5_tcpm_patch_0_decode_switch( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + +#ifdef VL53L5_PATCH_DATA_ENABLED + if ((idx >= 0x8) && (idx <= 0x29b8)) + status = vl53l5_tcpm_patch_0_results_decode_cmd( + idx, buffer_size, buffer, p_dev); +#endif + + else if (idx <= 0x4) + status = vl53l5_tcpm_patch_0_core_decode_cmd( + idx, buffer_size, buffer, p_dev); + + else + status = VL53L5_INVALID_IDX_DECODE_CMD_ERROR; + + LOG_FUNCTION_END(status); + return status; + +} diff --git a/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_tcpm_patch_0_results_decode.c b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_tcpm_patch_0_results_decode.c new file mode 100644 index 000000000000..c07e0717f8c2 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_tcpm_patch_0_results_decode.c @@ -0,0 +1,1821 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_tcpm_patch_version_size.h" +#include "dci_size.h" +#include "dci_patch_size.h" +#include "vl53l5_tcpm_patch_0_results_decode.h" +#include "vl53l5_tcpm_patch_0_results_enum_type.h" +#include "vl53l5_tcpm_patch_0_results_map_idx.h" +#include "vl53l5_tcpm_patch_0_results_dev_path.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform_log.h" + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START( \ + VL53L5_TRACE_MODULE_DCI_DECODE, fmt, ##__VA_ARGS__) + +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) + +#define LOG_FUNCTION_END_FMT(status, ...) \ + _LOG_FUNCTION_END_FMT( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) +#ifdef VL53L5_META_DATA_ON +static int32_t _decode_vl53l5_tcpm_patch_0_dci_grp_buf_meta_data( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_meta_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_BUF_META_DATA_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->time_stamp = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->device_status = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->transaction_id = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->buffer_id = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->stream_count = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->silicon_temp_degc = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->buf_meta_data__pad_0 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->buf_meta_data__pad_1 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->buf_meta_data__pad_2 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_COMMON_DATA_ON +static int32_t _decode_vl53l5_tcpm_patch_0_dci_grp_rng_common_data( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_common_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RNG_COMMON_DATA_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->wrap_dmax_mm = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->rng__no_of_zones = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->rng__max_targets_per_zone = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->rng__acc__no_of_zones = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->rng__acc__zone_id = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->rng__common__spare_0 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->rng__common__spare_1 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_RNG_TIMING_DATA_ON +static int32_t _decode_vl53l5_tcpm_patch_0_dci_grp_rng_timing_data( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_timing_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RNG_TIMING_DATA_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->rng__integration_time_us = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->rng__total_periods_elapsed = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->rng__blanking_time_us = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_AMB_RATE_KCPS_PER_SPAD_ON +static int32_t _decode_vl53l5_tcpm_patch_0_rpzd_amb_rate_kcps_per_spad( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_zone_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPZD_AMB_RATE_KCPS_PER_SPAD_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->amb_rate_kcps_per_spad[i] = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_EFFECTIVE_SPAD_COUNT_ON +static int32_t _decode_vl53l5_tcpm_patch_0_rpzd_rng_effective_spad_count( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_zone_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPZD_RNG_EFFECTIVE_SPAD_COUNT_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->rng__effective_spad_count[i] = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_AMB_DMAX_MM_ON +static int32_t _decode_vl53l5_tcpm_patch_0_rpzd_amb_dmax_mm( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_zone_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPZD_AMB_DMAX_MM_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->amb_dmax_mm[i] = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_SILICON_TEMP_DEGC_START_ON +static int32_t _decode_vl53l5_tcpm_patch_0_rpzd_silicon_temp_degc_start( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_zone_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPZD_SILICON_TEMP_DEGC_START_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->silicon_temp_degc__start[i] = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_SILICON_TEMP_DEGC_END_ON +static int32_t _decode_vl53l5_tcpm_patch_0_rpzd_silicon_temp_degc_end( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_zone_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPZD_SILICON_TEMP_DEGC_END_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->silicon_temp_degc__end[i] = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_NO_OF_TARGETS_ON +static int32_t _decode_vl53l5_tcpm_patch_0_rpzd_rng_no_of_targets( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_zone_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPZD_RNG_NO_OF_TARGETS_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->rng__no_of_targets[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_ZONE_ID_ON +static int32_t _decode_vl53l5_tcpm_patch_0_rpzd_rng_zone_id( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_zone_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPZD_RNG_ZONE_ID_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->rng__zone_id[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_SEQUENCE_IDX_ON +static int32_t _decode_vl53l5_tcpm_patch_0_rpzd_rng_sequence_idx( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_zone_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPZD_RNG_SEQUENCE_IDX_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->rng__sequence_idx[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_PEAK_RATE_KCPS_PER_SPAD_ON +static int32_t _decode_vl53l5_tcpm_patch_0_rptd_peak_rate_kcps_per_spad( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_PEAK_RATE_KCPS_PER_SPAD_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->peak_rate_kcps_per_spad[i] = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_MEDIAN_PHASE_ON +static int32_t _decode_vl53l5_tcpm_patch_0_rptd_median_phase( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_MEDIAN_PHASE_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->median_phase[i] = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_RATE_SIGMA_KCPS_PER_SPAD_ON +static int32_t _decode_vl53l5_tcpm_patch_0_rptd_rate_sigma_kcps_per_spad( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_RATE_SIGMA_KCPS_PER_SPAD_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->rate_sigma_kcps_per_spad[i] = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_TARGET_ZSCORE_ON +static int32_t _decode_vl53l5_tcpm_patch_0_rptd_target_zscore( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_TARGET_ZSCORE_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->target_zscore[i] = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_RANGE_SIGMA_MM_ON +static int32_t _decode_vl53l5_tcpm_patch_0_rptd_range_sigma_mm( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_RANGE_SIGMA_MM_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->range_sigma_mm[i] = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_MEDIAN_RANGE_MM_ON +static int32_t _decode_vl53l5_tcpm_patch_0_rptd_median_range_mm( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_MEDIAN_RANGE_MM_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->median_range_mm[i] = + vl53l5_decode_int16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_START_RANGE_MM_ON +static int32_t _decode_vl53l5_tcpm_patch_0_rptd_start_range_mm( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_START_RANGE_MM_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->start_range_mm[i] = + vl53l5_decode_int16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_END_RANGE_MM_ON +static int32_t _decode_vl53l5_tcpm_patch_0_rptd_end_range_mm( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_END_RANGE_MM_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->end_range_mm[i] = + vl53l5_decode_int16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_MIN_RANGE_DELTA_MM_ON +static int32_t _decode_vl53l5_tcpm_patch_0_rptd_min_range_delta_mm( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_MIN_RANGE_DELTA_MM_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->min_range_delta_mm[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_MAX_RANGE_DELTA_MM_ON +static int32_t _decode_vl53l5_tcpm_patch_0_rptd_max_range_delta_mm( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_MAX_RANGE_DELTA_MM_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->max_range_delta_mm[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_TARGET_REFLECTANCE_EST_PC_ON +static int32_t _decode_vl53l5_tcpm_patch_0_rptd_target_reflectance_est_pc( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_TARGET_REFLECTANCE_EST_PC_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->target_reflectance_est_pc[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_TARGET_STATUS_ON +static int32_t _decode_vl53l5_tcpm_patch_0_rptd_target_status( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_TARGET_STATUS_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->target_status[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_REF_CHANNEL_DATA_ON +static int32_t _decode_vl53l5_tcpm_patch_0_dci_grp_ref_channel_data( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_ref_channel_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_REF_CHANNEL_DATA_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->amb_rate_kcps_per_spad = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->rng__effective_spad_count = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->amb_dmax_mm = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->silicon_temp_degc__start = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->silicon_temp_degc__end = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->rng__no_of_targets = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->rng__zone_id = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->rng__sequence_idx = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->ref_channel_data__pad_0 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_REF_TARGET_DATA_ON +static int32_t _decode_vl53l5_tcpm_patch_0_dci_grp_ref_target_data( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_ref_target_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_REF_TARGET_DATA_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->peak_rate_kcps_per_spad = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->median_phase = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->rate_sigma_kcps_per_spad = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->target_zscore = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->range_sigma_mm = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->median_range_mm = + vl53l5_decode_int16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->start_range_mm = + vl53l5_decode_int16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->end_range_mm = + vl53l5_decode_int16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->min_range_delta_mm = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->max_range_delta_mm = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->target_reflectance_est_pc = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->target_status = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->ref_target_data__pad_0 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->ref_target_data__pad_1 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_SHARPENER_GROUP_INDEX_ON +static int32_t _decode_vl53l5_tcpm_patch_0_std_sharpener_group_index( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_sharpener_target_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_STD_SHARPENER_GROUP_INDEX_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->sharpener__group_index[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_SHARPENER_CONFIDENCE_ON +static int32_t _decode_vl53l5_tcpm_patch_0_std_sharpener_confidence( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_sharpener_target_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_STD_SHARPENER_CONFIDENCE_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->sharpener__confidence[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_ZONE_THRESH_STATUS_BYTES_ON +static int32_t _decode_vl53l5_tcpm_patch_0_ztsa_zone_thresh_status_bytes( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_zone_thresh_status_array_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_ZTSA_ZONE_THRESH_STATUS_BYTES_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->zone_thresh_status_bytes[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_DYN_XTALK_OP_PERSISTENT_DATA_ON +static int32_t _decode_vl53l5_tcpm_patch_0_dci_grp_dyn_xtalk_persistent_data( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dyn_xtalk_persistent_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_DYN_XTALK_PERSISTENT_DATA_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->dyn_xt__dyn_xtalk_grid_maximum_kcps_per_spad = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->dyn_xt__dyn_xtalk_grid_max_sigma_kcps_per_spad = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->dyn_xt__new_max_xtalk_sigma_kcps_per_spad = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->dyn_xt__calibration_gain = + vl53l5_decode_int32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->temp_comp__temp_gain = + vl53l5_decode_int32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->dyn_xt__nb_samples = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->dyn_xt__desired_samples = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->dyn_xt__accumulating = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->dyn_xt__grid_ready = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->dyn_xt__grid_ready_internal = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->dyn_xt__persist_spare_2 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_GD_OP_STATUS_ON +static int32_t _decode_vl53l5_tcpm_patch_0_dci_grp_gd_op_status( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_gd_op_status_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_GD_OP_STATUS_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->gd__rate_ratio = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->gd__confidence = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->gd__plane_detected = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->gd__ratio_detected = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->gd__glass_detected = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->gd__tgt_behind_glass_detected = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->gd__mirror_detected = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->gd__op_spare_0 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->gd__op_spare_1 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_DEPTH16_ON +static int32_t _decode_vl53l5_tcpm_patch_0_dci_grp_d16_per_target_data_depth16( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_d16_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_DPTD_DEPTH16_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->depth16[i] = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +int32_t vl53l5_tcpm_patch_0_results_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + +#if !defined(VL53L5_META_DATA_ON) && \ + !defined(VL53L5_COMMON_DATA_ON) && \ + !defined(VL53L5_RNG_TIMING_DATA_ON) && \ + !defined(VL53L5_AMB_RATE_KCPS_PER_SPAD_ON) && \ + !defined(VL53L5_EFFECTIVE_SPAD_COUNT_ON) && \ + !defined(VL53L5_AMB_DMAX_MM_ON) && \ + !defined(VL53L5_SILICON_TEMP_DEGC_START_ON) && \ + !defined(VL53L5_SILICON_TEMP_DEGC_END_ON) && \ + !defined(VL53L5_NO_OF_TARGETS_ON) && \ + !defined(VL53L5_ZONE_ID_ON) && \ + !defined(VL53L5_SEQUENCE_IDX_ON) && \ + !defined(VL53L5_PEAK_RATE_KCPS_PER_SPAD_ON) && \ + !defined(VL53L5_MEDIAN_PHASE_ON) && \ + !defined(VL53L5_RATE_SIGMA_KCPS_PER_SPAD_ON) && \ + !defined(VL53L5_TARGET_ZSCORE_ON) && \ + !defined(VL53L5_RANGE_SIGMA_MM_ON) && \ + !defined(VL53L5_MEDIAN_RANGE_MM_ON) && \ + !defined(VL53L5_START_RANGE_MM_ON) && \ + !defined(VL53L5_END_RANGE_MM_ON) && \ + !defined(VL53L5_MIN_RANGE_DELTA_MM_ON) && \ + !defined(VL53L5_MAX_RANGE_DELTA_MM_ON) && \ + !defined(VL53L5_TARGET_REFLECTANCE_EST_PC_ON) && \ + !defined(VL53L5_TARGET_STATUS_ON) && \ + !defined(VL53L5_REF_TIMING_DATA_ON) && \ + !defined(VL53L5_REF_CHANNEL_DATA_ON) && \ + !defined(VL53L5_REF_TARGET_DATA_ON) && \ + !defined(VL53L5_SHARPENER_GROUP_INDEX_ON) && \ + !defined(VL53L5_SHARPENER_CONFIDENCE_ON) && \ + !defined(VL53L5_ZONE_THRESH_STATUS_BYTES_ON) && \ + !defined(VL53L5_DYN_XTALK_OP_PERSISTENT_DATA_ON) && \ + !defined(VL53L5_GD_OP_STATUS_ON) && \ + !defined(VL53L5_DEPTH16_ON) + (void)buffer_size; + (void)buffer; + (void)p_dev; +#endif + + switch (idx) { +#ifdef VL53L5_META_DATA_ON + case VL53L5_TCPM_0_RNG_META_DATA_IDX: + status = + _decode_vl53l5_tcpm_patch_0_dci_grp_buf_meta_data( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_META_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_COMMON_DATA_ON + case VL53L5_TCPM_0_RNG_COMMON_DATA_IDX: + status = + _decode_vl53l5_tcpm_patch_0_dci_grp_rng_common_data( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_COMMON_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_RNG_TIMING_DATA_ON + case VL53L5_TCPM_0_RNG_TIMING_DATA_IDX: + status = + _decode_vl53l5_tcpm_patch_0_dci_grp_rng_timing_data( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_RNG_TIMING_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_AMB_RATE_KCPS_PER_SPAD_ON + case VL53L5_TCPM_0_RNG_PER_ZONE_DATA_AMB_RATE_KCPS_PER_SPAD_IDX: + status = + _decode_vl53l5_tcpm_patch_0_rpzd_amb_rate_kcps_per_spad( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_PER_ZONE_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_EFFECTIVE_SPAD_COUNT_ON + case VL53L5_TCPM_0_RNG_PER_ZONE_DATA_RNG__EFFECTIVE_SPAD_COUNT_IDX: + status = + _decode_vl53l5_tcpm_patch_0_rpzd_rng_effective_spad_count( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_PER_ZONE_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_AMB_DMAX_MM_ON + case VL53L5_TCPM_0_RNG_PER_ZONE_DATA_AMB_DMAX_MM_IDX: + status = + _decode_vl53l5_tcpm_patch_0_rpzd_amb_dmax_mm( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_PER_ZONE_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_SILICON_TEMP_DEGC_START_ON + case VL53L5_TCPM_0_RNG_PER_ZONE_DATA_SILICON_TEMP_DEGC__START_IDX: + status = + _decode_vl53l5_tcpm_patch_0_rpzd_silicon_temp_degc_start( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_PER_ZONE_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_SILICON_TEMP_DEGC_END_ON + case VL53L5_TCPM_0_RNG_PER_ZONE_DATA_SILICON_TEMP_DEGC__END_IDX: + status = + _decode_vl53l5_tcpm_patch_0_rpzd_silicon_temp_degc_end( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_PER_ZONE_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_NO_OF_TARGETS_ON + case VL53L5_TCPM_0_RNG_PER_ZONE_DATA_RNG__NO_OF_TARGETS_IDX: + status = + _decode_vl53l5_tcpm_patch_0_rpzd_rng_no_of_targets( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_PER_ZONE_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_ZONE_ID_ON + case VL53L5_TCPM_0_RNG_PER_ZONE_DATA_RNG__ZONE_ID_IDX: + status = + _decode_vl53l5_tcpm_patch_0_rpzd_rng_zone_id( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_PER_ZONE_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_SEQUENCE_IDX_ON + case VL53L5_TCPM_0_RNG_PER_ZONE_DATA_RNG__SEQUENCE_IDX_IDX: + status = + _decode_vl53l5_tcpm_patch_0_rpzd_rng_sequence_idx( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_PER_ZONE_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_PEAK_RATE_KCPS_PER_SPAD_ON + case VL53L5_TCPM_0_RNG_PER_TARGET_DATA_PEAK_RATE_KCPS_PER_SPAD_IDX: + status = + _decode_vl53l5_tcpm_patch_0_rptd_peak_rate_kcps_per_spad( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_MEDIAN_PHASE_ON + case VL53L5_TCPM_0_RNG_PER_TARGET_DATA_MEDIAN_PHASE_IDX: + status = + _decode_vl53l5_tcpm_patch_0_rptd_median_phase( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_RATE_SIGMA_KCPS_PER_SPAD_ON + case VL53L5_TCPM_0_RNG_PER_TARGET_DATA_RATE_SIGMA_KCPS_PER_SPAD_IDX: + status = + _decode_vl53l5_tcpm_patch_0_rptd_rate_sigma_kcps_per_spad( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_TARGET_ZSCORE_ON + case VL53L5_TCPM_0_RNG_PER_TARGET_DATA_TARGET_ZSCORE_IDX: + status = + _decode_vl53l5_tcpm_patch_0_rptd_target_zscore( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_RANGE_SIGMA_MM_ON + case VL53L5_TCPM_0_RNG_PER_TARGET_DATA_RANGE_SIGMA_MM_IDX: + status = + _decode_vl53l5_tcpm_patch_0_rptd_range_sigma_mm( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_MEDIAN_RANGE_MM_ON + case VL53L5_TCPM_0_RNG_PER_TARGET_DATA_MEDIAN_RANGE_MM_IDX: + status = + _decode_vl53l5_tcpm_patch_0_rptd_median_range_mm( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_START_RANGE_MM_ON + case VL53L5_TCPM_0_RNG_PER_TARGET_DATA_START_RANGE_MM_IDX: + status = + _decode_vl53l5_tcpm_patch_0_rptd_start_range_mm( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_END_RANGE_MM_ON + case VL53L5_TCPM_0_RNG_PER_TARGET_DATA_END_RANGE_MM_IDX: + status = + _decode_vl53l5_tcpm_patch_0_rptd_end_range_mm( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_MIN_RANGE_DELTA_MM_ON + case VL53L5_TCPM_0_RNG_PER_TARGET_DATA_MIN_RANGE_DELTA_MM_IDX: + status = + _decode_vl53l5_tcpm_patch_0_rptd_min_range_delta_mm( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_MAX_RANGE_DELTA_MM_ON + case VL53L5_TCPM_0_RNG_PER_TARGET_DATA_MAX_RANGE_DELTA_MM_IDX: + status = + _decode_vl53l5_tcpm_patch_0_rptd_max_range_delta_mm( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_TARGET_REFLECTANCE_EST_PC_ON + case VL53L5_TCPM_0_RNG_PER_TARGET_DATA_TARGET_REFLECTANCE_EST_PC_IDX: + status = + _decode_vl53l5_tcpm_patch_0_rptd_target_reflectance_est_pc( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_TARGET_STATUS_ON + case VL53L5_TCPM_0_RNG_PER_TARGET_DATA_TARGET_STATUS_IDX: + status = + _decode_vl53l5_tcpm_patch_0_rptd_target_status( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_REF_TIMING_DATA_ON + case VL53L5_TCPM_0_REF_TIMING_DATA_IDX: + status = + _decode_vl53l5_tcpm_patch_0_dci_grp_rng_timing_data( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_REF_TIMING_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_REF_CHANNEL_DATA_ON + case VL53L5_TCPM_0_REF_CHANNEL_DATA_IDX: + status = + _decode_vl53l5_tcpm_patch_0_dci_grp_ref_channel_data( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_REF_CHANNEL_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_REF_TARGET_DATA_ON + case VL53L5_TCPM_0_REF_TARGET_DATA_IDX: + status = + _decode_vl53l5_tcpm_patch_0_dci_grp_ref_target_data( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_REF_TARGET_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_SHARPENER_GROUP_INDEX_ON + case VL53L5_TCPM_0_SHARPENER_TARGET_DATA_SHARPENER__GROUP_INDEX_IDX: + status = + _decode_vl53l5_tcpm_patch_0_std_sharpener_group_index( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_SHARPENER_TARGET_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_SHARPENER_CONFIDENCE_ON + case VL53L5_TCPM_0_SHARPENER_TARGET_DATA_SHARPENER__CONFIDENCE_IDX: + status = + _decode_vl53l5_tcpm_patch_0_std_sharpener_confidence( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_SHARPENER_TARGET_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_ZONE_THRESH_STATUS_BYTES_ON + case VL53L5_TCPM_0_ZONE_THRESH_STATUS_ZONE_THRESH_STATUS_BYTES_IDX: + status = + _decode_vl53l5_tcpm_patch_0_ztsa_zone_thresh_status_bytes( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_ZONE_THRESH_STATUS(p_dev)); + break; +#endif + +#ifdef VL53L5_DYN_XTALK_OP_PERSISTENT_DATA_ON + case VL53L5_TCPM_0_DYN_XTALK_OP_PERSISTENT_DATA_IDX: + status = + _decode_vl53l5_tcpm_patch_0_dci_grp_dyn_xtalk_persistent_data( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_DYN_XTALK_OP_PERSISTENT_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_GD_OP_STATUS_ON + case VL53L5_TCPM_0_GD_OP_STATUS_IDX: + status = + _decode_vl53l5_tcpm_patch_0_dci_grp_gd_op_status( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_GD_OP_STATUS(p_dev)); + break; +#endif + +#ifdef VL53L5_DEPTH16_ON + case VL53L5_TCPM_0_D16_PER_TARGET_DATA_DEPTH16_IDX: + status = + _decode_vl53l5_tcpm_patch_0_dci_grp_d16_per_target_data_depth16( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_0_D16_PER_TARGET_DATA(p_dev)); + break; +#endif + + default: + status = VL53L5_INVALID_IDX_DECODE_CMD_ERROR; + break; + } + + LOG_FUNCTION_END(status); + return status; + +} diff --git a/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_tcpm_patch_1_calibration_decode.c b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_tcpm_patch_1_calibration_decode.c new file mode 100644 index 000000000000..3f35fec2678e --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_tcpm_patch_1_calibration_decode.c @@ -0,0 +1,103 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_tcpm_patch_version_size.h" +#include "vl53l5_tcpm_patch_1_calibration_decode.h" +#include "vl53l5_tcpm_patch_1_calibration_enum_type.h" +#include "vl53l5_tcpm_patch_1_calibration_map_idx.h" +#include "vl53l5_tcpm_patch_1_calibration_dev_path.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform_log.h" + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START( \ + VL53L5_TRACE_MODULE_DCI_DECODE, fmt, ##__VA_ARGS__) + +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) + +#define LOG_FUNCTION_END_FMT(status, ...) \ + _LOG_FUNCTION_END_FMT( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) +int32_t vl53l5_tcpm_patch_1_calibration_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + (void)buffer_size; + (void)buffer; + (void)p_dev; + + switch (idx) { + default: + status = VL53L5_INVALID_IDX_DECODE_CMD_ERROR; + break; + } + + LOG_FUNCTION_END(status); + return status; + +} diff --git a/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_tcpm_patch_1_core_decode.c b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_tcpm_patch_1_core_decode.c new file mode 100644 index 000000000000..f467577f2d8b --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_tcpm_patch_1_core_decode.c @@ -0,0 +1,173 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_tcpm_patch_version_size.h" +#include "vl53l5_tcpm_patch_1_core_decode.h" +#include "vl53l5_tcpm_patch_1_core_enum_type.h" +#include "vl53l5_tcpm_patch_1_core_map_idx.h" +#include "vl53l5_tcpm_patch_1_core_dev_path.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform_log.h" + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START( \ + VL53L5_TRACE_MODULE_DCI_DECODE, fmt, ##__VA_ARGS__) + +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) + +#define LOG_FUNCTION_END_FMT(status, ...) \ + _LOG_FUNCTION_END_FMT( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) +static int32_t _decode_vl53l5_tcpm_patch_1_tcpm_patch_version_grp_debug_word( + uint32_t buffer_size, + uint8_t *buffer, + struct tcpm_patch_version_grp__debug_word_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_TCPM_PATCH_VERSION_GRP_DEBUG_WORD_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->tcpm_patch_map__debug__word = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +static int32_t _decode_vl53l5_tcpm_patch_1_tcpm_patch_version_grp_debug2_word( + uint32_t buffer_size, + uint8_t *buffer, + struct tcpm_patch_version_grp__debug2_word_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_TCPM_PATCH_VERSION_GRP_DEBUG2_WORD_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->tcpm_patch_map__debug2__word = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} + +int32_t vl53l5_tcpm_patch_1_core_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + switch (idx) { + case VL53L5_TCPM_1_TCPM_PATCH_MAP_DEBUG_WORD_IDX: + status = + _decode_vl53l5_tcpm_patch_1_tcpm_patch_version_grp_debug_word( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_1_TCPM_PATCH_MAP_DEBUG_WORD(p_dev)); + break; + case VL53L5_TCPM_1_TCPM_PATCH_MAP_DEBUG2_WORD_IDX: + status = + _decode_vl53l5_tcpm_patch_1_tcpm_patch_version_grp_debug2_word( + buffer_size, + buffer, + &VL53L5_TCPM_PATCH_1_TCPM_PATCH_MAP_DEBUG2_WORD(p_dev)); + break; + default: + status = VL53L5_INVALID_IDX_DECODE_CMD_ERROR; + break; + } + + LOG_FUNCTION_END(status); + return status; + +} diff --git a/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_tcpm_patch_1_decode_switch.c b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_tcpm_patch_1_decode_switch.c new file mode 100644 index 000000000000..266a60c05dfb --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_tcpm_patch_1_decode_switch.c @@ -0,0 +1,95 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_tcpm_patch_1_decode_switch.h" +#include "vl53l5_tcpm_patch_1_core_decode.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform_log.h" + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START( \ + VL53L5_TRACE_MODULE_DCI_DECODE, fmt, ##__VA_ARGS__) + +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) + +#define LOG_FUNCTION_END_FMT(status, ...) \ + _LOG_FUNCTION_END_FMT( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) +int32_t vl53l5_tcpm_patch_1_decode_switch( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + if (idx <= 0x4) + status = vl53l5_tcpm_patch_1_core_decode_cmd( + idx, buffer_size, buffer, p_dev); + else + status = VL53L5_INVALID_IDX_DECODE_CMD_ERROR; + + LOG_FUNCTION_END(status); + return status; + +} diff --git a/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_tcpm_patch_1_results_decode.c b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_tcpm_patch_1_results_decode.c new file mode 100644 index 000000000000..a261a4fcf595 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/patch/src/vl53l5_tcpm_patch_1_results_decode.c @@ -0,0 +1,103 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_tcpm_patch_version_size.h" +#include "vl53l5_tcpm_patch_1_results_decode.h" +#include "vl53l5_tcpm_patch_1_results_enum_type.h" +#include "vl53l5_tcpm_patch_1_results_map_idx.h" +#include "vl53l5_tcpm_patch_1_results_dev_path.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform_log.h" + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START( \ + VL53L5_TRACE_MODULE_DCI_DECODE, fmt, ##__VA_ARGS__) + +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) + +#define LOG_FUNCTION_END_FMT(status, ...) \ + _LOG_FUNCTION_END_FMT( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) +int32_t vl53l5_tcpm_patch_1_results_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + + (void)buffer_size; + (void)buffer; + (void)p_dev; + + switch (idx) { + default: + status = VL53L5_INVALID_IDX_DECODE_CMD_ERROR; + break; + } + + LOG_FUNCTION_END(status); + return status; + +} diff --git a/drivers/sensors/vl53l8/bare_driver/results/inc/vl53l5_main_results_config.h b/drivers/sensors/vl53l8/bare_driver/results/inc/vl53l5_main_results_config.h new file mode 100644 index 000000000000..b9a12ada257a --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/results/inc/vl53l5_main_results_config.h @@ -0,0 +1,444 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_MAIN_RESULTS_CONFIG_H__ +#define __VL53L5_MAIN_RESULTS_CONFIG_H__ + +#include "vl53l5_results_build_config.h" +#include "dci_defs.h" +#include "dci_ui_memory_defs.h" +#include "dci_size.h" +#include "dci_ui_size.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if (defined(VL53L5_AMB_RATE_KCPS_PER_SPAD_ON) || \ + defined(VL53L5_EFFECTIVE_SPAD_COUNT_ON) || \ + defined(VL53L5_AMB_DMAX_MM_ON) || \ + defined(VL53L5_SILICON_TEMP_DEGC_START_ON) || \ + defined(VL53L5_SILICON_TEMP_DEGC_END_ON) || \ + defined(VL53L5_NO_OF_TARGETS_ON) || defined(VL53L5_ZONE_ID_ON) || \ + defined(VL53L5_SEQUENCE_IDX_ON)) +#define VL53L5_PER_ZONE_RESULTS_ON +#endif + +#if (defined(VL53L5_PEAK_RATE_KCPS_PER_SPAD_ON) || \ + defined(VL53L5_MEDIAN_PHASE_ON) || \ + defined(VL53L5_RATE_SIGMA_KCPS_PER_SPAD_ON) || \ + defined(VL53L5_TARGET_ZSCORE_ON) || defined(VL53L5_RANGE_SIGMA_MM_ON) \ + || defined(VL53L5_MEDIAN_RANGE_MM_ON) || \ + defined(VL53L5_START_RANGE_MM_ON) || defined(VL53L5_END_RANGE_MM_ON) \ + || defined(VL53L5_MIN_RANGE_DELTA_MM_ON) || \ + defined(VL53L5_MAX_RANGE_DELTA_MM_ON) || \ + defined(VL53L5_TARGET_REFLECTANCE_EST_PC_ON) || \ + defined(VL53L5_TARGET_STATUS_ON)) +#define VL53L5_PER_TGT_RESULTS_ON +#endif + +#if (defined(VL53L5_SHARPENER_GROUP_INDEX_ON) || \ + defined(VL53L5_SHARPENER_CONFIDENCE_ON)) +#define VL53L5_SHARPENER_TARGET_DATA_ON +#endif + +#if (defined(VL53L5_ZONE_THRESH_STATUS_BYTES_ON)) +#define VL53L5_ZONE_THRESH_STATUS_ON +#endif + +#ifdef VL53L5_SILICON_TEMP_DATA_ON +#define VL53L5_SILICON_TEMP_DATA_BLOCK_SZ \ + (VL53L5_SILICON_TEMPERATURE_DATA_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_SILICON_TEMP_DATA_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_ZONE_CFG_ON +#define VL53L5_ZONE_CFG_BLOCK_SZ \ + (VL53L5_ZONE_CFG_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_ZONE_CFG_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_META_DATA_ON +#define VL53L5_META_DATA_BLOCK_SZ \ + (VL53L5_BUF_META_DATA_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_META_DATA_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_COMMON_DATA_ON +#define VL53L5_COMMON_DATA_BLOCK_SZ \ + (VL53L5_RNG_COMMON_DATA_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_COMMON_DATA_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_RNG_TIMING_DATA_ON +#define VL53L5_RNG_TIMING_DATA_BLOCK_SZ \ + (VL53L5_RNG_TIMING_DATA_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_RNG_TIMING_DATA_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_AMB_RATE_KCPS_PER_SPAD_ON +#define VL53L5_AMB_RATE_KCPS_PER_SPAD_BLOCK_SZ \ + (VL53L5_RPZD_AMB_RATE_KCPS_PER_SPAD_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_AMB_RATE_KCPS_PER_SPAD_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_EFFECTIVE_SPAD_COUNT_ON +#define VL53L5_EFFECTIVE_SPAD_COUNT_BLOCK_SZ \ + (VL53L5_RPZD_RNG_EFFECTIVE_SPAD_COUNT_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_EFFECTIVE_SPAD_COUNT_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_AMB_DMAX_MM_ON +#define VL53L5_AMB_DMAX_MM_BLOCK_SZ \ + (VL53L5_RPZD_AMB_DMAX_MM_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_AMB_DMAX_MM_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_SILICON_TEMP_DEGC_START_ON +#define VL53L5_SILICON_TEMP_DEGC_START_BLOCK_SZ \ + (VL53L5_RPZD_SILICON_TEMP_DEGC_START_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_SILICON_TEMP_DEGC_START_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_SILICON_TEMP_DEGC_END_ON +#define VL53L5_SILICON_TEMP_DEGC_END_BLOCK_SZ \ + (VL53L5_RPZD_SILICON_TEMP_DEGC_END_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_SILICON_TEMP_DEGC_END_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_NO_OF_TARGETS_ON +#define VL53L5_NO_OF_TARGETS_BLOCK_SZ \ + (VL53L5_RPZD_RNG_NO_OF_TARGETS_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_NO_OF_TARGETS_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_ZONE_ID_ON +#define VL53L5_ZONE_ID_BLOCK_SZ \ + (VL53L5_RPZD_RNG_ZONE_ID_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_ZONE_ID_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_SEQUENCE_IDX_ON +#define VL53L5_SEQUENCE_IDX_BLOCK_SZ \ + (VL53L5_RPZD_RNG_SEQUENCE_IDX_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_SEQUENCE_IDX_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_PEAK_RATE_KCPS_PER_SPAD_ON +#define VL53L5_PEAK_RATE_KCPS_PER_SPAD_BLOCK_SZ \ + (VL53L5_RPTD_PEAK_RATE_KCPS_PER_SPAD_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_PEAK_RATE_KCPS_PER_SPAD_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_MEDIAN_PHASE_ON +#define VL53L5_MEDIAN_PHASE_BLOCK_SZ \ + (VL53L5_RPTD_MEDIAN_PHASE_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_MEDIAN_PHASE_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_RATE_SIGMA_KCPS_PER_SPAD_ON +#define VL53L5_RATE_SIGMA_KCPS_PER_SPAD_BLOCK_SZ \ + (VL53L5_RPTD_RATE_SIGMA_KCPS_PER_SPAD_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_RATE_SIGMA_KCPS_PER_SPAD_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_TARGET_ZSCORE_ON +#define VL53L5_TARGET_ZSCORE_BLOCK_SZ \ + (VL53L5_RPTD_TARGET_ZSCORE_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_TARGET_ZSCORE_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_RANGE_SIGMA_MM_ON +#define VL53L5_RANGE_SIGMA_MM_BLOCK_SZ \ + (VL53L5_RPTD_RANGE_SIGMA_MM_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_RANGE_SIGMA_MM_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_MEDIAN_RANGE_MM_ON +#define VL53L5_MEDIAN_RANGE_MM_BLOCK_SZ \ + (VL53L5_RPTD_MEDIAN_RANGE_MM_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_MEDIAN_RANGE_MM_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_START_RANGE_MM_ON +#define VL53L5_START_RANGE_MM_BLOCK_SZ \ + (VL53L5_RPTD_START_RANGE_MM_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_START_RANGE_MM_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_END_RANGE_MM_ON +#define VL53L5_END_RANGE_MM_BLOCK_SZ \ + (VL53L5_RPTD_END_RANGE_MM_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_END_RANGE_MM_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_MIN_RANGE_DELTA_MM_ON +#define VL53L5_MIN_RANGE_DELTA_MM_BLOCK_SZ \ + (VL53L5_RPTD_MIN_RANGE_DELTA_MM_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_MIN_RANGE_DELTA_MM_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_MAX_RANGE_DELTA_MM_ON +#define VL53L5_MAX_RANGE_DELTA_MM_BLOCK_SZ \ + (VL53L5_RPTD_MAX_RANGE_DELTA_MM_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_MAX_RANGE_DELTA_MM_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_TARGET_REFLECTANCE_EST_PC_ON +#define VL53L5_TARGET_REFLECTANCE_EST_PC_BLOCK_SZ \ + (VL53L5_RPTD_TARGET_REFLECTANCE_EST_PC_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_TARGET_REFLECTANCE_EST_PC_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_TARGET_STATUS_ON +#define VL53L5_TARGET_STATUS_BLOCK_SZ \ + (VL53L5_RPTD_TARGET_STATUS_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_TARGET_STATUS_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_REF_TIMING_DATA_ON +#define VL53L5_REF_TIMING_DATA_BLOCK_SZ \ + (VL53L5_RNG_TIMING_DATA_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_REF_TIMING_DATA_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_REF_CHANNEL_DATA_ON +#define VL53L5_REF_CHANNEL_DATA_BLOCK_SZ \ + (VL53L5_REF_CHANNEL_DATA_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_REF_CHANNEL_DATA_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_REF_TARGET_DATA_ON +#define VL53L5_REF_TARGET_DATA_BLOCK_SZ \ + (VL53L5_REF_TARGET_DATA_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_REF_TARGET_DATA_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_SHARPENER_GROUP_INDEX_ON +#define VL53L5_SHARPENER_GROUP_INDEX_BLOCK_SZ \ + (VL53L5_STD_SHARPENER_GROUP_INDEX_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_SHARPENER_GROUP_INDEX_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_SHARPENER_CONFIDENCE_ON +#define VL53L5_SHARPENER_CONFIDENCE_BLOCK_SZ \ + (VL53L5_STD_SHARPENER_CONFIDENCE_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_SHARPENER_CONFIDENCE_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_ZONE_THRESH_STATUS_BYTES_ON +#define VL53L5_ZONE_THRESH_STATUS_BYTES_BLOCK_SZ \ + (VL53L5_ZTSA_ZONE_THRESH_STATUS_BYTES_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_ZONE_THRESH_STATUS_BYTES_BLOCK_SZ \ + 0 +#endif + +#ifdef VL53L5_DYN_XTALK_OP_PERSISTENT_DATA_ON +#define VL53L5_DYN_XTALK_OP_PERSISTENT_DATA_BLOCK_SZ \ + (VL53L5_DYN_XTALK_PERSISTENT_DATA_SZ \ + + VL53L5_DCI_UI_PACKED_DATA_BH_SZ) +#else +#define VL53L5_DYN_XTALK_OP_PERSISTENT_DATA_BLOCK_SZ \ + 0 +#endif + +#define VL53L5_DUMMY_BYTES_SZ \ + VL53L5_CONFIG_DUMMY_BYTES_SZ +#define VL53L5_DUMMY_FOOTER_BYTES_SZ \ + 4U + +#define VL53L5_RESULTS_PACKET_META_SIZE \ + (VL53L5_DCI_UI_DEV_INFO_SZ \ + + VL53L5_DUMMY_BYTES_SZ \ + + (2 * VL53L5_DCI_UI_RNG_DATA_HEADER_SZ) \ + + (3 * VL53L5_DCI_UI_PACKED_DATA_BH_SZ) \ + + VL53L5_DUMMY_FOOTER_BYTES_SZ) + +#define VL53L5_RESULTS_TOTAL_SIZE_BYTES \ + (VL53L5_RESULTS_PACKET_META_SIZE \ + + VL53L5_SILICON_TEMP_DATA_BLOCK_SZ \ + + VL53L5_ZONE_CFG_BLOCK_SZ \ + + VL53L5_META_DATA_BLOCK_SZ \ + + VL53L5_COMMON_DATA_BLOCK_SZ \ + + VL53L5_RNG_TIMING_DATA_BLOCK_SZ \ + + VL53L5_AMB_RATE_KCPS_PER_SPAD_BLOCK_SZ \ + + VL53L5_EFFECTIVE_SPAD_COUNT_BLOCK_SZ \ + + VL53L5_AMB_DMAX_MM_BLOCK_SZ \ + + VL53L5_SILICON_TEMP_DEGC_START_BLOCK_SZ \ + + VL53L5_SILICON_TEMP_DEGC_END_BLOCK_SZ \ + + VL53L5_NO_OF_TARGETS_BLOCK_SZ \ + + VL53L5_ZONE_ID_BLOCK_SZ \ + + VL53L5_SEQUENCE_IDX_BLOCK_SZ \ + + VL53L5_PEAK_RATE_KCPS_PER_SPAD_BLOCK_SZ \ + + VL53L5_MEDIAN_PHASE_BLOCK_SZ \ + + VL53L5_RATE_SIGMA_KCPS_PER_SPAD_BLOCK_SZ \ + + VL53L5_TARGET_ZSCORE_BLOCK_SZ \ + + VL53L5_RANGE_SIGMA_MM_BLOCK_SZ \ + + VL53L5_MEDIAN_RANGE_MM_BLOCK_SZ \ + + VL53L5_START_RANGE_MM_BLOCK_SZ \ + + VL53L5_END_RANGE_MM_BLOCK_SZ \ + + VL53L5_MIN_RANGE_DELTA_MM_BLOCK_SZ \ + + VL53L5_MAX_RANGE_DELTA_MM_BLOCK_SZ \ + + VL53L5_TARGET_REFLECTANCE_EST_PC_BLOCK_SZ \ + + VL53L5_TARGET_STATUS_BLOCK_SZ \ + + VL53L5_REF_TIMING_DATA_BLOCK_SZ \ + + VL53L5_REF_CHANNEL_DATA_BLOCK_SZ \ + + VL53L5_REF_TARGET_DATA_BLOCK_SZ \ + + VL53L5_SHARPENER_GROUP_INDEX_BLOCK_SZ \ + + VL53L5_SHARPENER_CONFIDENCE_BLOCK_SZ \ + + VL53L5_ZONE_THRESH_STATUS_BYTES_BLOCK_SZ \ + + VL53L5_DYN_XTALK_OP_PERSISTENT_DATA_BLOCK_SZ) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/results/inc/vl53l5_results_decode.h b/drivers/sensors/vl53l8/bare_driver/results/inc/vl53l5_results_decode.h new file mode 100644 index 000000000000..909902251692 --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/results/inc/vl53l5_results_decode.h @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_RESULTS_DECODE_H__ +#define __VL53L5_RESULTS_DECODE_H__ + +#include "vl53l5_platform_user_data.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int32_t vl53l5_results_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/results/inc/vl53l5_results_dev_path.h b/drivers/sensors/vl53l8/bare_driver/results/inc/vl53l5_results_dev_path.h new file mode 100644 index 000000000000..9b45a51f291a --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/results/inc/vl53l5_results_dev_path.h @@ -0,0 +1,164 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_RESULTS_DEV_PATH_H__ +#define __VL53L5_RESULTS_DEV_PATH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_MAP_RESULTS_DEV(p_dev) \ + ((p_dev)->host_dev.presults_dev) +#define VL53L5_META_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->meta_data +#define VL53L5_COMMON_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->common_data +#define VL53L5_RNG_TIMING_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->rng_timing_data +#define VL53L5_PER_ZONE_RESULTS(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->per_zone_results +#define VL53L5_PER_TGT_RESULTS(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->per_tgt_results +#define VL53L5_REF_TIMING_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->ref_timing_data +#define VL53L5_REF_CHANNEL_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->ref_channel_data +#define VL53L5_REF_TARGET_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->ref_target_data +#define VL53L5_SHARPENER_TARGET_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->sharpener_target_data +#define VL53L5_HIST_ZONE_GRID_POINT_META_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_zone_grid_point_meta_data +#define VL53L5_HIST_ZONE_GRID_POINT_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_zone_grid_point_data +#define VL53L5_HIST_COMMON_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_common_data +#define VL53L5_HIST_TIMING_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_timing_data +#define VL53L5_HIST_CHANNEL_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_channel_data +#define VL53L5_HIST_BIN_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_bin_data +#define VL53L5_HIST_REF_COMMON_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_ref_common_data +#define VL53L5_HIST_REF_TIMING_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_ref_timing_data +#define VL53L5_HIST_REF_CHANNEL_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_ref_channel_data +#define VL53L5_HIST_REF_BIN_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_ref_bin_data +#define VL53L5_HIST_ACC_COMMON_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_acc_common_data +#define VL53L5_HIST_ACC_CHANNEL_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_acc_channel_data +#define VL53L5_HIST_ACC_BIN_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->hist_acc_bin_data +#define VL53L5_ACC_ZONE_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->acc_zone_data +#define VL53L5_REF_MPX_META_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->ref_mpx_meta_data +#define VL53L5_REF_MPX_COMMON_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->ref_mpx_common_data +#define VL53L5_REF_MPX_TIMING_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->ref_mpx_timing_data +#define VL53L5_REF_MPX_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->ref_mpx_data +#define VL53L5_RTN_MPX_META_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->rtn_mpx_meta_data +#define VL53L5_RTN_MPX_COMMON_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->rtn_mpx_common_data +#define VL53L5_RTN_MPX_TIMING_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->rtn_mpx_timing_data +#define VL53L5_RTN_MPX_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->rtn_mpx_data +#define VL53L5_XTALK_MON_META(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->xtalk_mon_meta +#define VL53L5_XTALK_MON_ZONES_MAX(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->xtalk_mon_zones_max +#define VL53L5_XTALK_MON_ZONES_ACTUAL(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->xtalk_mon_zones_actual +#define VL53L5_XTALK_MON_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->xtalk_mon_data +#define VL53L5_VHV_RESULT_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->vhv_result_data +#define VL53L5_VHV_SEARCH_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->vhv_search_data +#define VL53L5_CAL_REF_SPAD_SEARCH_META_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->cal_ref_spad_search_meta_data +#define VL53L5_CAL_REF_SPAD_SEARCH_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->cal_ref_spad_search_data +#define VL53L5_REF_ARRAY_META_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->ref_array_meta_data +#define VL53L5_RTN_ARRAY_META_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->rtn_array_meta_data +#define VL53L5_REF_ARRAY_SPAD_EN(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->ref_array_spad_en +#define VL53L5_RTN_ARRAY_SPAD_EN(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->rtn_array_spad_en +#define VL53L5_ZONE_THRESH_STATUS(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->zone_thresh_status +#define VL53L5_DYN_XTALK_OP_PERSISTENT_DATA(p_dev) \ + VL53L5_MAP_RESULTS_DEV(p_dev)->dyn_xtalk_op_persistent_data + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/results/inc/vl53l5_results_enum_type.h b/drivers/sensors/vl53l8/bare_driver/results/inc/vl53l5_results_enum_type.h new file mode 100644 index 000000000000..73e9cd5ba97a --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/results/inc/vl53l5_results_enum_type.h @@ -0,0 +1,133 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_RESULTS_ENUM_TYPE_H__ +#define __VL53L5_RESULTS_ENUM_TYPE_H__ + +#include "vl53l5_dci_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_BUF_META_DATA_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_RNG_COMMON_DATA_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_RNG_TIMING_DATA_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_RPZD_AMB_RATE_KCPS_PER_SPAD_TYPE \ + ((enum block_format_type) 0x4) +#define VL53L5_RPZD_RNG_EFFECTIVE_SPAD_COUNT_TYPE \ + ((enum block_format_type) 0x4) +#define VL53L5_RPZD_AMB_DMAX_MM_TYPE \ + ((enum block_format_type) 0x2) +#define VL53L5_RPZD_SILICON_TEMP_DEGC_START_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_RPZD_SILICON_TEMP_DEGC_END_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_RPZD_RNG_NO_OF_TARGETS_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_RPZD_RNG_ZONE_ID_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_RPZD_RNG_SEQUENCE_IDX_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_RPTD_PEAK_RATE_KCPS_PER_SPAD_TYPE \ + ((enum block_format_type) 0x4) +#define VL53L5_RPTD_MEDIAN_PHASE_TYPE \ + ((enum block_format_type) 0x4) +#define VL53L5_RPTD_RATE_SIGMA_KCPS_PER_SPAD_TYPE \ + ((enum block_format_type) 0x4) +#define VL53L5_RPTD_TARGET_ZSCORE_TYPE \ + ((enum block_format_type) 0x2) +#define VL53L5_RPTD_RANGE_SIGMA_MM_TYPE \ + ((enum block_format_type) 0x2) +#define VL53L5_RPTD_MEDIAN_RANGE_MM_TYPE \ + ((enum block_format_type) 0x2) +#define VL53L5_RPTD_START_RANGE_MM_TYPE \ + ((enum block_format_type) 0x2) +#define VL53L5_RPTD_END_RANGE_MM_TYPE \ + ((enum block_format_type) 0x2) +#define VL53L5_RPTD_MIN_RANGE_DELTA_MM_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_RPTD_MAX_RANGE_DELTA_MM_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_RPTD_TARGET_REFLECTANCE_EST_PC_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_RPTD_TARGET_STATUS_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_REF_CHANNEL_DATA_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_REF_TARGET_DATA_TYPE \ + ((enum block_format_type) 0x0) +#define VL53L5_STD_SHARPENER_GROUP_INDEX_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_STD_SHARPENER_CONFIDENCE_TYPE \ + ((enum block_format_type) 0x1) + +#define VL53L5_ZTSA_ZONE_THRESH_STATUS_BYTES_TYPE \ + ((enum block_format_type) 0x1) +#define VL53L5_DYN_XTALK_PERSISTENT_DATA_TYPE \ + ((enum block_format_type) 0x0) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/results/inc/vl53l5_results_map.h b/drivers/sensors/vl53l8/bare_driver/results/inc/vl53l5_results_map.h new file mode 100644 index 000000000000..2ad289be439f --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/results/inc/vl53l5_results_map.h @@ -0,0 +1,141 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_RESULTS_MAP_H__ +#define __VL53L5_RESULTS_MAP_H__ + +#include "dci_defs.h" +#include "dci_structs.h" +#include "dci_union_structs.h" +#include "dci_ui_structs.h" +#include "common_datatype_structs.h" +#include "common_datatype_defs.h" +#include "packing_structs.h" +#include "vl53l5_types.h" +#include "dyn_xtalk_structs.h" +#include "dyn_xtalk_defs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_CGHBD_HIST_BIN_EVENTS_PACKED_ARRAY_SZ \ + ((uint16_t) 6912) + +struct vl53l5_range_results_t { +#ifdef VL53L5_META_DATA_ON + + struct vl53l5_range_meta_data_t meta_data; +#endif + +#ifdef VL53L5_COMMON_DATA_ON + + struct vl53l5_range_common_data_t common_data; +#endif + +#ifdef VL53L5_RNG_TIMING_DATA_ON + + struct vl53l5_range_timing_data_t rng_timing_data; +#endif + +#ifdef VL53L5_PER_ZONE_RESULTS_ON + + struct vl53l5_range_per_zone_results_t per_zone_results; +#endif + +#ifdef VL53L5_PER_TGT_RESULTS_ON + + struct vl53l5_range_per_tgt_results_t per_tgt_results; +#endif + +#ifdef VL53L5_REF_TIMING_DATA_ON + + struct vl53l5_range_timing_data_t ref_timing_data; +#endif + +#ifdef VL53L5_REF_CHANNEL_DATA_ON + + struct vl53l5_ref_channel_data_t ref_channel_data; +#endif + +#ifdef VL53L5_REF_TARGET_DATA_ON + + struct vl53l5_ref_target_data_t ref_target_data; +#endif + +#ifdef VL53L5_SHARPENER_TARGET_DATA_ON + struct vl53l5_sharpener_target_data_t sharpener_target_data; +#endif + +#ifdef VL53L5_ZONE_THRESH_STATUS_ON + struct vl53l5_zone_thresh_status_array_t zone_thresh_status; +#endif + +#ifdef VL53L5_DYN_XTALK_OP_PERSISTENT_DATA_ON + + struct vl53l5_dyn_xtalk_persistent_data_t dyn_xtalk_op_persistent_data; +#endif + +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/results/inc/vl53l5_results_map_bh.h b/drivers/sensors/vl53l8/bare_driver/results/inc/vl53l5_results_map_bh.h new file mode 100644 index 000000000000..0df665dd3a4b --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/results/inc/vl53l5_results_map_bh.h @@ -0,0 +1,133 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_RESULTS_MAP_BH_H__ +#define __VL53L5_RESULTS_MAP_BH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define VL53L5_RNG_META_DATA_BH \ + ((uint32_t) 0x59d800c0U) +#define VL53L5_RNG_COMMON_DATA_BH \ + ((uint32_t) 0x59e40080U) +#define VL53L5_RNG_TIMING_DATA_BH \ + ((uint32_t) 0x59ec00c0U) +#define VL53L5_RNG_PER_ZONE_DATA_AMB_RATE_KCPS_PER_SPAD_BH \ + ((uint32_t) 0x59f80444U) +#define VL53L5_RNG_PER_ZONE_DATA_RNG__EFFECTIVE_SPAD_COUNT_BH \ + ((uint32_t) 0x5b080444U) +#define VL53L5_RNG_PER_ZONE_DATA_AMB_DMAX_MM_BH \ + ((uint32_t) 0x5c180442U) +#define VL53L5_RNG_PER_ZONE_DATA_SILICON_TEMP_DEGC__START_BH \ + ((uint32_t) 0x5ca00441U) +#define VL53L5_RNG_PER_ZONE_DATA_SILICON_TEMP_DEGC__END_BH \ + ((uint32_t) 0x5ce40441U) +#define VL53L5_RNG_PER_ZONE_DATA_RNG__NO_OF_TARGETS_BH \ + ((uint32_t) 0x5d280441U) +#define VL53L5_RNG_PER_ZONE_DATA_RNG__ZONE_ID_BH \ + ((uint32_t) 0x5d6c0441U) +#define VL53L5_RNG_PER_ZONE_DATA_RNG__SEQUENCE_IDX_BH \ + ((uint32_t) 0x5db00441U) +#define VL53L5_RNG_PER_TARGET_DATA_PEAK_RATE_KCPS_PER_SPAD_BH \ + ((uint32_t) 0x5df41104U) +#define VL53L5_RNG_PER_TARGET_DATA_MEDIAN_PHASE_BH \ + ((uint32_t) 0x62341104U) +#define VL53L5_RNG_PER_TARGET_DATA_RATE_SIGMA_KCPS_PER_SPAD_BH \ + ((uint32_t) 0x66741104U) +#define VL53L5_RNG_PER_TARGET_DATA_TARGET_ZSCORE_BH \ + ((uint32_t) 0x6ab41102U) +#define VL53L5_RNG_PER_TARGET_DATA_RANGE_SIGMA_MM_BH \ + ((uint32_t) 0x6cd41102U) +#define VL53L5_RNG_PER_TARGET_DATA_MEDIAN_RANGE_MM_BH \ + ((uint32_t) 0x6ef41102U) +#define VL53L5_RNG_PER_TARGET_DATA_START_RANGE_MM_BH \ + ((uint32_t) 0x71141102U) +#define VL53L5_RNG_PER_TARGET_DATA_END_RANGE_MM_BH \ + ((uint32_t) 0x73341102U) +#define VL53L5_RNG_PER_TARGET_DATA_MIN_RANGE_DELTA_MM_BH \ + ((uint32_t) 0x75541101U) +#define VL53L5_RNG_PER_TARGET_DATA_MAX_RANGE_DELTA_MM_BH \ + ((uint32_t) 0x76641101U) +#define VL53L5_RNG_PER_TARGET_DATA_TARGET_REFLECTANCE_EST_PC_BH \ + ((uint32_t) 0x77741101U) +#define VL53L5_RNG_PER_TARGET_DATA_TARGET_STATUS_BH \ + ((uint32_t) 0x78841101U) +#define VL53L5_REF_TIMING_DATA_BH \ + ((uint32_t) 0x799400c0U) +#define VL53L5_REF_CHANNEL_DATA_BH \ + ((uint32_t) 0x79a00100U) +#define VL53L5_REF_TARGET_DATA_BH \ + ((uint32_t) 0x79b001c0U) +#define VL53L5_SHARPENER_TARGET_DATA_SHARPENER__GROUP_INDEX_BH \ + ((uint32_t) 0x79cc1101U) +#define VL53L5_SHARPENER_TARGET_DATA_SHARPENER__CONFIDENCE_BH \ + ((uint32_t) 0x7adc1101U) + +#define VL53L5_ZONE_THRESH_STATUS_ZONE_THRESH_STATUS_BYTES_BH \ + ((uint32_t) 0xafb80081U) +#define VL53L5_DYN_XTALK_OP_PERSISTENT_DATA_BH \ + ((uint32_t) 0xafc001c0U) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/results/inc/vl53l5_results_map_idx.h b/drivers/sensors/vl53l8/bare_driver/results/inc/vl53l5_results_map_idx.h new file mode 100644 index 000000000000..75b5cb9c946b --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/results/inc/vl53l5_results_map_idx.h @@ -0,0 +1,171 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 __VL53L5_RESULTS_MAP_IDX_H__ +#define __VL53L5_RESULTS_MAP_IDX_H__ + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define DEV_RNG_META_DATA_IDX \ + ((uint16_t) 0x59d8) + +#define DEV_RNG_COMMON_DATA_IDX \ + ((uint16_t) 0x59e4) + +#define DEV_RNG_TIMING_DATA_IDX \ + ((uint16_t) 0x59ec) + +#define DEV_RNG_PER_ZONE_DATA_AMB_RATE_KCPS_PER_SPAD_IDX \ + ((uint16_t) 0x59f8) +#define DEV_RNG_PER_ZONE_DATA_IDX \ + ((uint16_t) 0x59f8) + +#define DEV_RNG_PER_ZONE_DATA_RNG__EFFECTIVE_SPAD_COUNT_IDX \ + ((uint16_t) 0x5b08) + +#define DEV_RNG_PER_ZONE_DATA_AMB_DMAX_MM_IDX \ + ((uint16_t) 0x5c18) + +#define DEV_RNG_PER_ZONE_DATA_SILICON_TEMP_DEGC__START_IDX \ + ((uint16_t) 0x5ca0) + +#define DEV_RNG_PER_ZONE_DATA_SILICON_TEMP_DEGC__END_IDX \ + ((uint16_t) 0x5ce4) + +#define DEV_RNG_PER_ZONE_DATA_RNG__NO_OF_TARGETS_IDX \ + ((uint16_t) 0x5d28) + +#define DEV_RNG_PER_ZONE_DATA_RNG__ZONE_ID_IDX \ + ((uint16_t) 0x5d6c) + +#define DEV_RNG_PER_ZONE_DATA_RNG__SEQUENCE_IDX_IDX \ + ((uint16_t) 0x5db0) + +#define DEV_RNG_PER_TARGET_DATA_PEAK_RATE_KCPS_PER_SPAD_IDX \ + ((uint16_t) 0x5df4) +#define DEV_RNG_PER_TARGET_DATA_IDX \ + ((uint16_t) 0x5df4) + +#define DEV_RNG_PER_TARGET_DATA_MEDIAN_PHASE_IDX \ + ((uint16_t) 0x6234) + +#define DEV_RNG_PER_TARGET_DATA_RATE_SIGMA_KCPS_PER_SPAD_IDX \ + ((uint16_t) 0x6674) + +#define DEV_RNG_PER_TARGET_DATA_TARGET_ZSCORE_IDX \ + ((uint16_t) 0x6ab4) + +#define DEV_RNG_PER_TARGET_DATA_RANGE_SIGMA_MM_IDX \ + ((uint16_t) 0x6cd4) + +#define DEV_RNG_PER_TARGET_DATA_MEDIAN_RANGE_MM_IDX \ + ((uint16_t) 0x6ef4) + +#define DEV_RNG_PER_TARGET_DATA_START_RANGE_MM_IDX \ + ((uint16_t) 0x7114) + +#define DEV_RNG_PER_TARGET_DATA_END_RANGE_MM_IDX \ + ((uint16_t) 0x7334) + +#define DEV_RNG_PER_TARGET_DATA_MIN_RANGE_DELTA_MM_IDX \ + ((uint16_t) 0x7554) + +#define DEV_RNG_PER_TARGET_DATA_MAX_RANGE_DELTA_MM_IDX \ + ((uint16_t) 0x7664) + +#define DEV_RNG_PER_TARGET_DATA_TARGET_REFLECTANCE_EST_PC_IDX \ + ((uint16_t) 0x7774) + +#define DEV_RNG_PER_TARGET_DATA_TARGET_STATUS_IDX \ + ((uint16_t) 0x7884) + +#define DEV_REF_TIMING_DATA_IDX \ + ((uint16_t) 0x7994) + +#define DEV_REF_CHANNEL_DATA_IDX \ + ((uint16_t) 0x79a0) + +#define DEV_REF_TARGET_DATA_IDX \ + ((uint16_t) 0x79b0) + +#define DEV_SHARPENER_TARGET_DATA_SHARPENER__GROUP_INDEX_IDX \ + ((uint16_t) 0x79cc) +#define DEV_SHARPENER_TARGET_DATA_IDX \ + ((uint16_t) 0x79cc) + +#define DEV_SHARPENER_TARGET_DATA_SHARPENER__CONFIDENCE_IDX \ + ((uint16_t) 0x7adc) + +#define DEV_ZONE_THRESH_STATUS_ZONE_THRESH_STATUS_BYTES_IDX \ + ((uint16_t) 0xafb8) +#define DEV_ZONE_THRESH_STATUS_IDX \ + ((uint16_t) 0xafb8) + +#define DEV_DYN_XTALK_OP_PERSISTENT_DATA_IDX \ + ((uint16_t) 0xafc0) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/bare_driver/results/src/vl53l5_results_decode.c b/drivers/sensors/vl53l8/bare_driver/results/src/vl53l5_results_decode.c new file mode 100644 index 000000000000..edf8cfa1576f --- /dev/null +++ b/drivers/sensors/vl53l8/bare_driver/results/src/vl53l5_results_decode.c @@ -0,0 +1,1692 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "dci_size.h" +#include "dci_ui_size.h" +#include "common_datatype_size.h" +#include "dyn_xtalk_size.h" +#include "vl53l5_results_decode.h" +#include "vl53l5_results_enum_type.h" +#include "vl53l5_results_map_idx.h" +#include "vl53l5_results_dev_path.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_error_codes.h" +#include "vl53l5_platform_log.h" + +#define LOG_FUNCTION_START(fmt, ...) \ + _LOG_FUNCTION_START( \ + VL53L5_TRACE_MODULE_DCI_DECODE, fmt, ##__VA_ARGS__) + +#define LOG_FUNCTION_END(status, ...) \ + _LOG_FUNCTION_END( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) + +#define LOG_FUNCTION_END_FMT(status, ...) \ + _LOG_FUNCTION_END_FMT( \ + VL53L5_TRACE_MODULE_DCI_DECODE, status, ##__VA_ARGS__) +#ifdef VL53L5_META_DATA_ON +static int32_t _decode_dci_grp_buf_meta_data( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_meta_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_BUF_META_DATA_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->time_stamp = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->device_status = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->transaction_id = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->buffer_id = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->stream_count = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->silicon_temp_degc = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->buf_meta_data__pad_0 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->buf_meta_data__pad_1 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->buf_meta_data__pad_2 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_COMMON_DATA_ON +static int32_t _decode_dci_grp_rng_common_data( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_common_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RNG_COMMON_DATA_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->wrap_dmax_mm = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->rng__no_of_zones = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->rng__max_targets_per_zone = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->rng__acc__no_of_zones = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->rng__acc__zone_id = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->rng__common__spare_0 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->rng__common__spare_1 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_RNG_TIMING_DATA_ON +static int32_t _decode_dci_grp_rng_timing_data( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_timing_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RNG_TIMING_DATA_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->rng__integration_time_us = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->rng__total_periods_elapsed = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->rng__blanking_time_us = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_AMB_RATE_KCPS_PER_SPAD_ON +static int32_t _decode_dci_grp_rng_per_zone_data_amb_rate_kcps_per_spad( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_zone_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPZD_AMB_RATE_KCPS_PER_SPAD_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->amb_rate_kcps_per_spad[i] = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_EFFECTIVE_SPAD_COUNT_ON +static int32_t _decode_dci_grp_rng_per_zone_data_rng_effective_spad_count( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_zone_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPZD_RNG_EFFECTIVE_SPAD_COUNT_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->rng__effective_spad_count[i] = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_AMB_DMAX_MM_ON +static int32_t _decode_dci_grp_rng_per_zone_data_amb_dmax_mm( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_zone_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPZD_AMB_DMAX_MM_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->amb_dmax_mm[i] = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_SILICON_TEMP_DEGC_START_ON +static int32_t _decode_dci_grp_rng_per_zone_data_silicon_temp_degc_start( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_zone_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPZD_SILICON_TEMP_DEGC_START_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->silicon_temp_degc__start[i] = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_SILICON_TEMP_DEGC_END_ON +static int32_t _decode_dci_grp_rng_per_zone_data_silicon_temp_degc_end( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_zone_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPZD_SILICON_TEMP_DEGC_END_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->silicon_temp_degc__end[i] = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_NO_OF_TARGETS_ON +static int32_t _decode_dci_grp_rng_per_zone_data_rng_no_of_targets( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_zone_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPZD_RNG_NO_OF_TARGETS_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->rng__no_of_targets[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_ZONE_ID_ON +static int32_t _decode_dci_grp_rng_per_zone_data_rng_zone_id( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_zone_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPZD_RNG_ZONE_ID_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->rng__zone_id[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_SEQUENCE_IDX_ON +static int32_t _decode_dci_grp_rng_per_zone_data_rng_sequence_idx( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_zone_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPZD_RNG_SEQUENCE_IDX_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->rng__sequence_idx[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_PEAK_RATE_KCPS_PER_SPAD_ON +static int32_t _decode_dci_grp_rng_per_target_data_peak_rate_kcps_per_spad( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_PEAK_RATE_KCPS_PER_SPAD_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->peak_rate_kcps_per_spad[i] = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_MEDIAN_PHASE_ON +static int32_t _decode_dci_grp_rng_per_target_data_median_phase( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_MEDIAN_PHASE_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->median_phase[i] = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_RATE_SIGMA_KCPS_PER_SPAD_ON +static int32_t _decode_dci_grp_rng_per_target_data_rate_sigma_kcps_per_spad( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_RATE_SIGMA_KCPS_PER_SPAD_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->rate_sigma_kcps_per_spad[i] = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_TARGET_ZSCORE_ON +static int32_t _decode_dci_grp_rng_per_target_data_target_zscore( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_TARGET_ZSCORE_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->target_zscore[i] = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_RANGE_SIGMA_MM_ON +static int32_t _decode_dci_grp_rng_per_target_data_range_sigma_mm( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_RANGE_SIGMA_MM_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->range_sigma_mm[i] = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_MEDIAN_RANGE_MM_ON +static int32_t _decode_dci_grp_rng_per_target_data_median_range_mm( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_MEDIAN_RANGE_MM_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->median_range_mm[i] = + vl53l5_decode_int16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_START_RANGE_MM_ON +static int32_t _decode_dci_grp_rng_per_target_data_start_range_mm( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_START_RANGE_MM_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->start_range_mm[i] = + vl53l5_decode_int16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_END_RANGE_MM_ON +static int32_t _decode_dci_grp_rng_per_target_data_end_range_mm( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_END_RANGE_MM_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->end_range_mm[i] = + vl53l5_decode_int16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_MIN_RANGE_DELTA_MM_ON +static int32_t _decode_dci_grp_rng_per_target_data_min_range_delta_mm( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_MIN_RANGE_DELTA_MM_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->min_range_delta_mm[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_MAX_RANGE_DELTA_MM_ON +static int32_t _decode_dci_grp_rng_per_target_data_max_range_delta_mm( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_MAX_RANGE_DELTA_MM_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->max_range_delta_mm[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_TARGET_REFLECTANCE_EST_PC_ON +static int32_t _decode_dci_grp_rng_per_target_data_target_reflectance_est_pc( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_TARGET_REFLECTANCE_EST_PC_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->target_reflectance_est_pc[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_TARGET_STATUS_ON +static int32_t _decode_dci_grp_rng_per_target_data_target_status( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_range_per_tgt_results_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_RPTD_TARGET_STATUS_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->target_status[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_REF_CHANNEL_DATA_ON +static int32_t _decode_dci_grp_ref_channel_data( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_ref_channel_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_REF_CHANNEL_DATA_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->amb_rate_kcps_per_spad = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->rng__effective_spad_count = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->amb_dmax_mm = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->silicon_temp_degc__start = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->silicon_temp_degc__end = + vl53l5_decode_int8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->rng__no_of_targets = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->rng__zone_id = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->rng__sequence_idx = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->ref_channel_data__pad_0 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_REF_TARGET_DATA_ON +static int32_t _decode_dci_grp_ref_target_data( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_ref_target_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_REF_TARGET_DATA_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->peak_rate_kcps_per_spad = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->median_phase = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->rate_sigma_kcps_per_spad = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->target_zscore = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->range_sigma_mm = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->median_range_mm = + vl53l5_decode_int16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->start_range_mm = + vl53l5_decode_int16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->end_range_mm = + vl53l5_decode_int16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->min_range_delta_mm = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->max_range_delta_mm = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->target_reflectance_est_pc = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->target_status = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->ref_target_data__pad_0 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->ref_target_data__pad_1 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_SHARPENER_GROUP_INDEX_ON +static int32_t _decode_dci_grp_sharpener_target_data_sharpener_group_index( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_sharpener_target_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_STD_SHARPENER_GROUP_INDEX_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->sharpener__group_index[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_SHARPENER_CONFIDENCE_ON +static int32_t _decode_dci_grp_sharpener_target_data_sharpener_confidence( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_sharpener_target_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_STD_SHARPENER_CONFIDENCE_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->sharpener__confidence[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_ZONE_THRESH_STATUS_BYTES_ON +static int32_t _decode_ztsa_zone_thresh_status_bytes( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_zone_thresh_status_array_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + uint32_t i = 0; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_ZTSA_ZONE_THRESH_STATUS_BYTES_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + while (buff_count < buffer_size) { + pstruct->zone_thresh_status_bytes[i] = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + i++; + } + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +#ifdef VL53L5_DYN_XTALK_OP_PERSISTENT_DATA_ON +static int32_t _decode_dci_grp_dyn_xtalk_persistent_data( + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dyn_xtalk_persistent_data_t *pstruct) +{ + int32_t status = STATUS_OK; + uint32_t buff_count = 0; + uint8_t *p_buff = buffer; + + LOG_FUNCTION_START(""); + + if (buffer_size > + (uint32_t)VL53L5_DYN_XTALK_PERSISTENT_DATA_SZ) { + status = VL53L5_BUFFER_LARGER_THAN_EXPECTED_DATA; + goto exit; + } + + pstruct->dyn_xt__dyn_xtalk_grid_maximum_kcps_per_spad = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->dyn_xt__dyn_xtalk_grid_max_sigma_kcps_per_spad = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->dyn_xt__new_max_xtalk_sigma_kcps_per_spad = + vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->dyn_xt__calibration_gain = + vl53l5_decode_int32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->temp_comp__temp_gain = + vl53l5_decode_int32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + buff_count += BYTE_4; + + pstruct->dyn_xt__nb_samples = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->dyn_xt__desired_samples = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + buff_count += BYTE_2; + + pstruct->dyn_xt__accumulating = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->dyn_xt__grid_ready = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->dyn_xt__grid_ready_internal = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + pstruct->dyn_xt__persist_spare_2 = + vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + buff_count += BYTE_1; + + if (buffer_size != buff_count) + status = VL53L5_DATA_SIZE_MISMATCH; + +exit: + LOG_FUNCTION_END(status); + return status; +} +#endif + +int32_t vl53l5_results_decode_cmd( + uint16_t idx, + uint32_t buffer_size, + uint8_t *buffer, + struct vl53l5_dev_handle_t *p_dev) +{ + int32_t status = STATUS_OK; + + LOG_FUNCTION_START(""); + +#if !defined(VL53L5_META_DATA_ON) && \ + !defined(VL53L5_COMMON_DATA_ON) && \ + !defined(VL53L5_RNG_TIMING_DATA_ON) && \ + !defined(VL53L5_AMB_RATE_KCPS_PER_SPAD_ON) && \ + !defined(VL53L5_EFFECTIVE_SPAD_COUNT_ON) && \ + !defined(VL53L5_AMB_DMAX_MM_ON) && \ + !defined(VL53L5_SILICON_TEMP_DEGC_START_ON) && \ + !defined(VL53L5_SILICON_TEMP_DEGC_END_ON) && \ + !defined(VL53L5_NO_OF_TARGETS_ON) && \ + !defined(VL53L5_ZONE_ID_ON) && \ + !defined(VL53L5_SEQUENCE_IDX_ON) && \ + !defined(VL53L5_PEAK_RATE_KCPS_PER_SPAD_ON) && \ + !defined(VL53L5_MEDIAN_PHASE_ON) && \ + !defined(VL53L5_RATE_SIGMA_KCPS_PER_SPAD_ON) && \ + !defined(VL53L5_TARGET_ZSCORE_ON) && \ + !defined(VL53L5_RANGE_SIGMA_MM_ON) && \ + !defined(VL53L5_MEDIAN_RANGE_MM_ON) && \ + !defined(VL53L5_START_RANGE_MM_ON) && \ + !defined(VL53L5_END_RANGE_MM_ON) && \ + !defined(VL53L5_MIN_RANGE_DELTA_MM_ON) && \ + !defined(VL53L5_MAX_RANGE_DELTA_MM_ON) && \ + !defined(VL53L5_TARGET_REFLECTANCE_EST_PC_ON) && \ + !defined(VL53L5_TARGET_STATUS_ON) && \ + !defined(VL53L5_REF_TIMING_DATA_ON) && \ + !defined(VL53L5_REF_CHANNEL_DATA_ON) && \ + !defined(VL53L5_REF_TARGET_DATA_ON) && \ + !defined(VL53L5_SHARPENER_GROUP_INDEX_ON) && \ + !defined(VL53L5_SHARPENER_CONFIDENCE_ON) && \ + !defined(VL53L5_ZONE_THRESH_STATUS_BYTES_ON) && \ + !defined(VL53L5_DYN_XTALK_OP_PERSISTENT_DATA_ON) + (void)buffer_size; + (void)buffer; + (void)p_dev; +#endif + + switch (idx) { +#ifdef VL53L5_META_DATA_ON + case DEV_RNG_META_DATA_IDX: + status = + _decode_dci_grp_buf_meta_data( + buffer_size, + buffer, + &VL53L5_META_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_COMMON_DATA_ON + case DEV_RNG_COMMON_DATA_IDX: + status = + _decode_dci_grp_rng_common_data( + buffer_size, + buffer, + &VL53L5_COMMON_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_RNG_TIMING_DATA_ON + case DEV_RNG_TIMING_DATA_IDX: + status = + _decode_dci_grp_rng_timing_data( + buffer_size, + buffer, + &VL53L5_RNG_TIMING_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_AMB_RATE_KCPS_PER_SPAD_ON + case DEV_RNG_PER_ZONE_DATA_AMB_RATE_KCPS_PER_SPAD_IDX: + status = + _decode_dci_grp_rng_per_zone_data_amb_rate_kcps_per_spad( + buffer_size, + buffer, + &VL53L5_PER_ZONE_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_EFFECTIVE_SPAD_COUNT_ON + case DEV_RNG_PER_ZONE_DATA_RNG__EFFECTIVE_SPAD_COUNT_IDX: + status = + _decode_dci_grp_rng_per_zone_data_rng_effective_spad_count( + buffer_size, + buffer, + &VL53L5_PER_ZONE_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_AMB_DMAX_MM_ON + case DEV_RNG_PER_ZONE_DATA_AMB_DMAX_MM_IDX: + status = + _decode_dci_grp_rng_per_zone_data_amb_dmax_mm( + buffer_size, + buffer, + &VL53L5_PER_ZONE_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_SILICON_TEMP_DEGC_START_ON + case DEV_RNG_PER_ZONE_DATA_SILICON_TEMP_DEGC__START_IDX: + status = + _decode_dci_grp_rng_per_zone_data_silicon_temp_degc_start( + buffer_size, + buffer, + &VL53L5_PER_ZONE_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_SILICON_TEMP_DEGC_END_ON + case DEV_RNG_PER_ZONE_DATA_SILICON_TEMP_DEGC__END_IDX: + status = + _decode_dci_grp_rng_per_zone_data_silicon_temp_degc_end( + buffer_size, + buffer, + &VL53L5_PER_ZONE_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_NO_OF_TARGETS_ON + case DEV_RNG_PER_ZONE_DATA_RNG__NO_OF_TARGETS_IDX: + status = + _decode_dci_grp_rng_per_zone_data_rng_no_of_targets( + buffer_size, + buffer, + &VL53L5_PER_ZONE_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_ZONE_ID_ON + case DEV_RNG_PER_ZONE_DATA_RNG__ZONE_ID_IDX: + status = + _decode_dci_grp_rng_per_zone_data_rng_zone_id( + buffer_size, + buffer, + &VL53L5_PER_ZONE_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_SEQUENCE_IDX_ON + case DEV_RNG_PER_ZONE_DATA_RNG__SEQUENCE_IDX_IDX: + status = + _decode_dci_grp_rng_per_zone_data_rng_sequence_idx( + buffer_size, + buffer, + &VL53L5_PER_ZONE_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_PEAK_RATE_KCPS_PER_SPAD_ON + case DEV_RNG_PER_TARGET_DATA_PEAK_RATE_KCPS_PER_SPAD_IDX: + status = + _decode_dci_grp_rng_per_target_data_peak_rate_kcps_per_spad( + buffer_size, + buffer, + &VL53L5_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_MEDIAN_PHASE_ON + case DEV_RNG_PER_TARGET_DATA_MEDIAN_PHASE_IDX: + status = + _decode_dci_grp_rng_per_target_data_median_phase( + buffer_size, + buffer, + &VL53L5_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_RATE_SIGMA_KCPS_PER_SPAD_ON + case DEV_RNG_PER_TARGET_DATA_RATE_SIGMA_KCPS_PER_SPAD_IDX: + status = + _decode_dci_grp_rng_per_target_data_rate_sigma_kcps_per_spad( + buffer_size, + buffer, + &VL53L5_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_TARGET_ZSCORE_ON + case DEV_RNG_PER_TARGET_DATA_TARGET_ZSCORE_IDX: + status = + _decode_dci_grp_rng_per_target_data_target_zscore( + buffer_size, + buffer, + &VL53L5_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_RANGE_SIGMA_MM_ON + case DEV_RNG_PER_TARGET_DATA_RANGE_SIGMA_MM_IDX: + status = + _decode_dci_grp_rng_per_target_data_range_sigma_mm( + buffer_size, + buffer, + &VL53L5_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_MEDIAN_RANGE_MM_ON + case DEV_RNG_PER_TARGET_DATA_MEDIAN_RANGE_MM_IDX: + status = + _decode_dci_grp_rng_per_target_data_median_range_mm( + buffer_size, + buffer, + &VL53L5_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_START_RANGE_MM_ON + case DEV_RNG_PER_TARGET_DATA_START_RANGE_MM_IDX: + status = + _decode_dci_grp_rng_per_target_data_start_range_mm( + buffer_size, + buffer, + &VL53L5_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_END_RANGE_MM_ON + case DEV_RNG_PER_TARGET_DATA_END_RANGE_MM_IDX: + status = + _decode_dci_grp_rng_per_target_data_end_range_mm( + buffer_size, + buffer, + &VL53L5_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_MIN_RANGE_DELTA_MM_ON + case DEV_RNG_PER_TARGET_DATA_MIN_RANGE_DELTA_MM_IDX: + status = + _decode_dci_grp_rng_per_target_data_min_range_delta_mm( + buffer_size, + buffer, + &VL53L5_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_MAX_RANGE_DELTA_MM_ON + case DEV_RNG_PER_TARGET_DATA_MAX_RANGE_DELTA_MM_IDX: + status = + _decode_dci_grp_rng_per_target_data_max_range_delta_mm( + buffer_size, + buffer, + &VL53L5_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_TARGET_REFLECTANCE_EST_PC_ON + case DEV_RNG_PER_TARGET_DATA_TARGET_REFLECTANCE_EST_PC_IDX: + status = + _decode_dci_grp_rng_per_target_data_target_reflectance_est_pc( + buffer_size, + buffer, + &VL53L5_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_TARGET_STATUS_ON + case DEV_RNG_PER_TARGET_DATA_TARGET_STATUS_IDX: + status = + _decode_dci_grp_rng_per_target_data_target_status( + buffer_size, + buffer, + &VL53L5_PER_TGT_RESULTS(p_dev)); + break; +#endif + +#ifdef VL53L5_REF_TIMING_DATA_ON + case DEV_REF_TIMING_DATA_IDX: + status = + _decode_dci_grp_rng_timing_data( + buffer_size, + buffer, + &VL53L5_REF_TIMING_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_REF_CHANNEL_DATA_ON + case DEV_REF_CHANNEL_DATA_IDX: + status = + _decode_dci_grp_ref_channel_data( + buffer_size, + buffer, + &VL53L5_REF_CHANNEL_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_REF_TARGET_DATA_ON + case DEV_REF_TARGET_DATA_IDX: + status = + _decode_dci_grp_ref_target_data( + buffer_size, + buffer, + &VL53L5_REF_TARGET_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_SHARPENER_GROUP_INDEX_ON + case DEV_SHARPENER_TARGET_DATA_SHARPENER__GROUP_INDEX_IDX: + status = + _decode_dci_grp_sharpener_target_data_sharpener_group_index( + buffer_size, + buffer, + &VL53L5_SHARPENER_TARGET_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_SHARPENER_CONFIDENCE_ON + case DEV_SHARPENER_TARGET_DATA_SHARPENER__CONFIDENCE_IDX: + status = + _decode_dci_grp_sharpener_target_data_sharpener_confidence( + buffer_size, + buffer, + &VL53L5_SHARPENER_TARGET_DATA(p_dev)); + break; +#endif + +#ifdef VL53L5_ZONE_THRESH_STATUS_BYTES_ON + case DEV_ZONE_THRESH_STATUS_ZONE_THRESH_STATUS_BYTES_IDX: + status = + _decode_ztsa_zone_thresh_status_bytes( + buffer_size, + buffer, + &VL53L5_ZONE_THRESH_STATUS(p_dev)); + break; +#endif + +#ifdef VL53L5_DYN_XTALK_OP_PERSISTENT_DATA_ON + case DEV_DYN_XTALK_OP_PERSISTENT_DATA_IDX: + status = + _decode_dci_grp_dyn_xtalk_persistent_data( + buffer_size, + buffer, + &VL53L5_DYN_XTALK_OP_PERSISTENT_DATA(p_dev)); + break; +#endif + + default: + status = VL53L5_INVALID_IDX_DECODE_CMD_ERROR; + break; + } + + LOG_FUNCTION_END(status); + return status; + +} diff --git a/drivers/sensors/vl53l8/inc/vl53l8_k_driver_config.h b/drivers/sensors/vl53l8/inc/vl53l8_k_driver_config.h new file mode 100644 index 000000000000..d1177ab61457 --- /dev/null +++ b/drivers/sensors/vl53l8/inc/vl53l8_k_driver_config.h @@ -0,0 +1,85 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L8_K_DRIVER_CONFIG_H_ +#define _VL53L8_K_DRIVER_CONFIG_H_ + +#define VL53L8_I2C_ADDRESS 0x52 + +#define VL53L8_CFG_MAX_DEV 1 + +#define VL53L8_K_MAX_POLL_TIME_MS 500 + +#define VL53L8_K_MAX_CALIBRATION_POLL_TIME_MS 10000 + +#define VL53L8_K_SLEEP_TIME_MS 10 + +#define VL53L8_MAX_CCI_XFER_SZ 0x500 + +#define VL53L8_MAX_SPI_XFER_SZ 0x500 + +enum vl53l8_range_servicing_mode { + VL53L8_RANGE_SERVICE_DEFAULT = 0, + VL53L8_RANGE_SERVICE_WORKER = 1, + VL53L8_RANGE_SERVICE_INTERRUPT = 2 +}; + +#define VL53L8_K_SPI_MANUAL_CHIP_SELECT 0 + +#endif diff --git a/drivers/sensors/vl53l8/inc/vl53l8_k_error_codes.h b/drivers/sensors/vl53l8/inc/vl53l8_k_error_codes.h new file mode 100644 index 000000000000..ceaf14d3ec4f --- /dev/null +++ b/drivers/sensors/vl53l8/inc/vl53l8_k_error_codes.h @@ -0,0 +1,150 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 VL53L8_K_ERROR_CODES_H_ +#define VL53L8_K_ERROR_CODES_H_ + +#include "vl53l5_error_codes.h" + +#define VL53L8_K_ERROR_DRIVER_NOT_INITIALISED ((int) -501) +#define VL53L8_K_ERROR_DEVICE_NOT_PRESENT ((int) -502) +#define VL53L8_K_ERROR_DEVICE_NOT_POWERED ((int) -503) +#define VL53L8_K_ERROR_DEVICE_NOT_INITIALISED ((int) -504) +#define VL53L8_K_ERROR_DEVICE_NOT_RANGING ((int) -507) +#define VL53L8_K_ERROR_DEVICE_IS_RANGING ((int) -508) +#define VL53L8_K_ERROR_DEVICE_IS_BUSY ((int) -509) +#define VL53L8_K_ERROR_DEVICE_STATE_INVALID ((int) -510) +#define VL53L8_K_ERROR_SLEEP_FOR_DATA_INTERRUPTED ((int) -511) +#define VL53L8_K_ERROR_WORKER_THREAD_TIMEOUT ((int) -512) +#define VL53L8_K_ERROR_FAILED_TO_ALLOCATE_WORKER_WAIT_LIST ((int) -513) +#define VL53L8_K_ERROR_SPI_BUSNUM_INVALID ((int) -514) +#define VL53L8_K_ERROR_SPI_NEW_DEVICE_FAILED ((int) -515) +#define VL53L8_K_ERROR_SPI_SETUP_FAILED ((int) -516) +#define VL53L8_K_ERROR_SPI_DRIVER_REGISTER_FAILED ((int) -517) +#define VL53L8_K_ERROR_I2C_ADAPTER_INVALID ((int) -518) +#define VL53L8_K_ERROR_I2C_NEW_DEVICE_FAILED ((int) -519) +#define VL53L8_K_ERROR_I2C_ADD_DRIVER_FAILED ((int) -520) +#define VL53L8_K_ERROR_I2C_CHECK_FAILED ((int) -521) +#define VL53L8_K_ERROR_GPIO_IS_DISABLED ((int) -522) +#define VL53L8_K_ERROR_GPIO_REQUEST_FAILED ((int) -523) +#define VL53L8_K_ERROR_GPIO_DIRECTION_SET_FAILED ((int) -524) +#define VL53L8_K_ERROR_ATTEMPT_TO_SET_DISABLED_GPIO ((int) -525) +#define VL53L8_K_ERROR_RANGE_POLLING_TIMEOUT ((int) -526) +#define VL53L8_K_ERROR_STATE_POLLING_TIMEOUT ((int) -527) +#define VL53L8_K_ERROR_FAILED_TO_ALLOCATE_PROFILE ((int) -528) +#define VL53L8_K_ERROR_FAILED_TO_ALLOCATE_FIRMWARE ((int) -529) +#define VL53L8_K_ERROR_FAILED_TO_ALLOCATE_VERSION ((int) -530) +#define VL53L8_K_ERROR_FAILED_TO_ALLOCATE_MODULE_INFO ((int) -531) +#define VL53L8_K_ERROR_FAILED_TO_ALLOCATE_RAW_DATA ((int) -532) +#define VL53L8_K_ERROR_FAILED_TO_COPY_FLAGS ((int) -533) +#define VL53L8_K_ERROR_FAILED_TO_COPY_FIRMWARE ((int) -534) +#define VL53L8_K_ERROR_FAILED_TO_COPY_VERSION ((int) -535) +#define VL53L8_K_ERROR_FAILED_TO_COPY_MODULE_INFO ((int) -536) +#define VL53L8_K_ERROR_FAILED_TO_COPY_RAW_DATA ((int) -537) +#define VL53L8_K_ERROR_FAILED_TO_COPY_RANGE_DATA ((int) -538) +#define VL53L8_K_ERROR_FAILED_TO_COPY_ERROR_INFO ((int) -539) +#define VL53L8_K_ERROR_RANGE_DATA_NOT_READY ((int) -540) +#define VL53L8_K_ERROR_FAILED_TO_COPY_PARAMETER_DATA ((int) -541) +#define VL53L8_K_ERROR_FAILED_TO_ALLOCATE_PARAMETER_DATA ((int) -542) +#define VL53L8_K_ERROR_FAILED_TO_COPY_POWER_MODE ((int) -543) +#define VL53L8_K_ERROR_INVALID_POWER_STATE ((int) -544) +#define VL53L8_K_ERROR_DEVICE_INTERRUPT_NOT_OWNED ((int) -545) +#define VL53L8_K_ERROR_FAILED_TO_COPY_CONFIG_PRESET ((int) -546) +#define VL53L8_K_ERROR_FAILED_TO_ALLOCATE_FILE_DATA ((int) -547) +#define VL53L8_K_ERROR_FAILED_TO_COPY_FILE_DATA ((int) -548) +#define VL53L8_K_ERROR_FAILED_TO_ALLOCATE_CALIBRATION ((int) -549) +#define VL53L8_K_ERROR_FAILED_TO_COPY_CALIBRATION ((int) -550) +#define VL53L8_K_ERROR_FAILED_TO_ALLOCATE_RANGE_DATA ((int) -551) +#define VL53L8_K_ERROR_INVALID_NO_OF_ZONES ((int) -552) +#define VL53L8_K_ERROR_INVALID_ROTATION ((int) -553) +#define VL53L8_K_ERROR_INVALID_CONFIG_PRESET ((int) -554) +#define VL53L8_K_ERROR_INVALID_CALIBRATION_FLOW ((int) -555) +#define VL53L8_K_ERROR_FAILED_TO_COPY_DATA ((int) -556) +#define VL53L8_K_ERROR_HAP_TESTS_FAILED ((int) -557) +#define VL53L8_K_ERROR_HAP_CONFIGURATION_INVALID ((int) -558) +#define VL53L8_K_ERROR_INVALID_VALUE ((int) -559) +#define VL53L8_K_ERROR_FAILED_TO_COPY_ASZ_TUNING ((int) -560) +#define VL53L8_K_ERROR_FAILED_TO_COPY_RANGING_RATE ((int) -561) +#define VL53L8_K_ERROR_FAILED_TO_COPY_MAX_INTEGRATION ((int) -562) +#define VL53L8_K_GF_INVALID_MAX_TARGETS_ERROR ((int) -570) +#define VL53L8_K_GF_DIVIDE_BY_ZERO_ERROR ((int) -572) + +#define VL53L8_K_ERROR_NOT_DEFINED ((int) -888) +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +#define VL53L8_IO_ERROR ((int32_t) -5) +#define VL53L8_PROBE_FAILED ((int32_t) -1000) +#define VL53L8_SHUTDOWN ((int32_t) -1001) +#define VL53L8_DELAYED_LOAD_FIRMWARE ((int32_t) -1002) +#define VL53L8_SUSPEND_IOCTL_STOP_ERROR ((int32_t) -1003) +#define VL53L8_RESUME_INIT_ERROR ((int32_t) -1004) +#define VL53L8_OPENCAL_ERROR ((int32_t) -1006) +#define VL53L8_CALFILE_LOAD_ERROR ((int32_t) -1007) + +#define VL53L8_LDO_ERROR ((int32_t) -1010) +#define VL53L8_LDO_AVDD_ERROR ((int32_t) -1011) +#define VL53L8_LDO_IOVDD_ERROR ((int32_t) -1012) +#define VL53L8_LDO_AVDD_IOVDD_ERROR ((int32_t) -1013) +#define VL53L8_LDO_COREVDD_ERROR ((int32_t) -1014) +#define VL53L8_LDO_AVDD_COREVDD_ERROR ((int32_t) -1015) +#define VL53L8_LDO_IOVDD_COREVDD_ERROR ((int32_t) -1016) +#define VL53L8_LDO_AVDD_IOVDD_COREVDD_ERROR ((int32_t) -1017) + +#endif + +#endif diff --git a/drivers/sensors/vl53l8/inc/vl53l8_k_error_converter.h b/drivers/sensors/vl53l8/inc/vl53l8_k_error_converter.h new file mode 100644 index 000000000000..dc8efac676cb --- /dev/null +++ b/drivers/sensors/vl53l8/inc/vl53l8_k_error_converter.h @@ -0,0 +1,75 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L8_K_ERROR_CONVERTER_H_ +#define _VL53L8_K_ERROR_CONVERTER_H_ + +#include "vl53l8_k_user_api.h" +#include "vl53l8_k_module_data.h" + +void vl53l8_k_store_error(struct vl53l8_k_module_t *p_module, + int32_t vl53l8_k_error); + +int32_t vl53l8_k_convert_error_to_linux_error(int32_t vl53l8_k_error); +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG +void vl53l8_last_error_counter(struct vl53l8_k_module_t *p_module, int err); +#endif +#endif +#endif diff --git a/drivers/sensors/vl53l8/inc/vl53l8_k_glare_filter.h b/drivers/sensors/vl53l8/inc/vl53l8_k_glare_filter.h new file mode 100644 index 000000000000..b90d77fae1f7 --- /dev/null +++ b/drivers/sensors/vl53l8/inc/vl53l8_k_glare_filter.h @@ -0,0 +1,110 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L8_K_GLARE_FILTER_H_ +#define _VL53L8_K_GLARE_FILTER_H_ + +#include "vl53l5_platform_user_data.h" +#include "vl53l8_k_user_data.h" + +#define VL53L8_K_D16_CONF_00PC 1 + +#define VL53L8_K_D16_CONF_14PC 2 + +#define VL53L8_K_D16_CONF_29PC 3 + +#define VL53L8_K_GAIN_SCALE_FACTOR 64U + +#define VL53L8_K_GSF_x_GSF \ + (VL53L8_K_GAIN_SCALE_FACTOR * VL53L8_K_GAIN_SCALE_FACTOR) + +#define VL53L8_K_REFLECTANCE_X256 (13 * 256) + +#define VL53L8_K_RANGE 100 + +#define VL53L8_K_SIGNAL_X16 (16 * 30000) + +#define VL53L8_K_GLARE_LUT_SIZE 8 + +#define VL53L8_K_Y_SCALE_FACTOR 64 + +#define VL53L8_K_PEAK_RATE_FRACTIONAL_BITS 11 + +#define VL53L8_K_MEDIAN_RANGE_FRACTIONAL_BITS 2 + +struct vl53l8_k_glare_filter_tuning_t { + + int32_t range_min_clip; + int32_t max_filter_range; + bool remove_glare_targets; + + int32_t range_x4[VL53L8_K_GLARE_LUT_SIZE]; + + uint32_t refl_thresh_x256[VL53L8_K_GLARE_LUT_SIZE]; + uint32_t lut_range[VL53L8_K_GLARE_LUT_SIZE]; + uint32_t threshold_numerator[VL53L8_K_GLARE_LUT_SIZE]; +}; + +int32_t vl53l8_k_glare_filter( + struct vl53l8_k_glare_filter_tuning_t *p_tuning, + struct vl53l5_range_data_t *p_results); + +void vl53l8_k_glare_detect_init(struct vl53l8_k_glare_filter_tuning_t *p_tuning); + +#endif diff --git a/drivers/sensors/vl53l8/inc/vl53l8_k_gpio_def.h b/drivers/sensors/vl53l8/inc/vl53l8_k_gpio_def.h new file mode 100644 index 000000000000..41e7d712b1ef --- /dev/null +++ b/drivers/sensors/vl53l8/inc/vl53l8_k_gpio_def.h @@ -0,0 +1,84 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L8_K_GPIO_DEF_H_ +#define _VL53L8_K_GPIO_DEF_H_ + +struct vl53l8_k_gpio_settings_t { +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE + + int power_enable; + + unsigned int power_enable_owned; + + int low_power; + + unsigned int low_power_owned; + + int comms_select; + + unsigned int comms_select_owned; +#endif + + int interrupt; + + unsigned int interrupt_owned; +}; + +#endif diff --git a/drivers/sensors/vl53l8/inc/vl53l8_k_gpio_utils.h b/drivers/sensors/vl53l8/inc/vl53l8_k_gpio_utils.h new file mode 100644 index 000000000000..5f9990e2fdea --- /dev/null +++ b/drivers/sensors/vl53l8/inc/vl53l8_k_gpio_utils.h @@ -0,0 +1,75 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L8_K_GPIO_UTILS_H_ +#define _VL53L8_K_GPIO_UTILS_H_ + +#include "vl53l8_k_module_data.h" + +int vl53l8_k_get_gpio(int *p_gpio, int *p_is_gpio_owned, int is_output); + +void vl53l8_k_put_gpio(int *p_gpio, int *p_is_gpio_owned); + +int vl53l8_k_set_gpio(int *p_gpio, uint8_t value); +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE +int vl53l8_k_assign_gpios(struct vl53l8_k_module_t *p_module); +#endif +void vl53l8_k_release_gpios(struct vl53l8_k_module_t *p_module); + +#endif diff --git a/drivers/sensors/vl53l8/inc/vl53l8_k_interrupt.h b/drivers/sensors/vl53l8/inc/vl53l8_k_interrupt.h new file mode 100644 index 000000000000..bb355083fd7c --- /dev/null +++ b/drivers/sensors/vl53l8/inc/vl53l8_k_interrupt.h @@ -0,0 +1,70 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L8_K_INTERRUPT_H_ +#define _VL53L8_K_INTERRUPT_H_ + +#include "vl53l8_k_module_data.h" +#include + +int vl53l8_k_start_interrupt(struct vl53l8_k_module_t *p_module); + +int vl53l8_k_stop_interrupt(struct vl53l8_k_module_t *p_module); + +#endif diff --git a/drivers/sensors/vl53l8/inc/vl53l8_k_ioctl_codes.h b/drivers/sensors/vl53l8/inc/vl53l8_k_ioctl_codes.h new file mode 100644 index 000000000000..b6f5e6e1122b --- /dev/null +++ b/drivers/sensors/vl53l8/inc/vl53l8_k_ioctl_codes.h @@ -0,0 +1,153 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L8_K_IOCTL_CODES_H_ +#define _VL53L8_K_IOCTL_CODES_H_ + +#include "vl53l8_k_user_data.h" + +#define VL53L8_IOCTL_CHAR 'p' + +#define VL53L8_IOCTL_INIT\ + _IO(VL53L8_IOCTL_CHAR, 0x01) + +#define VL53L8_IOCTL_TERM \ + _IO(VL53L8_IOCTL_CHAR, 0x02) + +#define VL53L8_IOCTL_GET_VERSION \ + _IOR(VL53L8_IOCTL_CHAR, 0x03, struct vl53l8_k_version_t) + +#define VL53L8_IOCTL_START \ + _IOW(VL53L8_IOCTL_CHAR, 0x04, struct vl53l5_ranging_mode_flags_t) + +#define VL53L8_IOCTL_STOP \ + _IOW(VL53L8_IOCTL_CHAR, 0x05, struct vl53l5_ranging_mode_flags_t) +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE +#define VL53L8_IOCTL_GET_RANGE \ + _IOR(VL53L8_IOCTL_CHAR, 0x06, struct vl53l5_range_data_t) +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +#define VL53L8_IOCTL_GET_RANGE \ + _IOR(VL53L8_IOCTL_CHAR, 0x06, struct range_sensor_data_t) +#endif + +#define VL53L8_IOCTL_SET_DEVICE_PARAMETERS \ + _IOW(VL53L8_IOCTL_CHAR, 0x08, int) + +#define VL53L8_IOCTL_GET_DEVICE_PARAMETERS \ + _IOWR(VL53L8_IOCTL_CHAR, 0x09, struct vl53l8_k_get_parameters_data_t) + +#define VL53L8_IOCTL_GET_MODULE_INFO \ + _IOR(VL53L8_IOCTL_CHAR, 0x0a, struct vl53l5_module_info_t) + +#define VL53L8_IOCTL_GET_ERROR_INFO \ + _IOR(VL53L8_IOCTL_CHAR, 0x0b, int) + +#define VL53L8_IOCTL_SET_POWER_MODE \ + _IOW(VL53L8_IOCTL_CHAR, 0x0c, enum vl53l5_power_states) + +#define VL53L8_IOCTL_POLL_DATA_READY \ + _IO(VL53L8_IOCTL_CHAR, 0x0d) + +#define VL53L8_IOCTL_READ_P2P_FILE \ + _IO(VL53L8_IOCTL_CHAR, 0x10) + +#define VL53L8_IOCTL_READ_SHAPE_FILE \ + _IO(VL53L8_IOCTL_CHAR, 0x13) + +#define VL53L8_IOCTL_PERFORM_CALIBRATION_300 \ + _IO(VL53L8_IOCTL_CHAR, 0x14) + +#define VL53L8_IOCTL_READ_GENERIC_SHAPE \ + _IO(VL53L8_IOCTL_CHAR, 0x15) + +#define VL53L8_IOCTL_PERFORM_CHARACTERISATION_1000 \ + _IO(VL53L8_IOCTL_CHAR, 0x19) + +#define VL53L8_IOCTL_SET_RANGING_RATE \ + _IOW(VL53L8_IOCTL_CHAR, 0x1a, int) + +#define VL53L8_IOCTL_SET_INTEGRATION_TIME_US \ + _IOW(VL53L8_IOCTL_CHAR, 0x1b, int) + +#define VL53L8_IOCTL_SET_ASZ_TUNING \ + _IOW(VL53L8_IOCTL_CHAR, 0x1c, struct vl53l8_k_asz_tuning_t) + +#define VL53L5_IOCTL_GET_CAL_DATA \ + _IOR(VL53L8_IOCTL_CHAR, 0x20, struct vl53l8_cal_data_t) + +#define VL53L5_IOCTL_SET_CAL_DATA \ + _IOW(VL53L8_IOCTL_CHAR, 0x21, struct vl53l8_cal_data_t) + +#define VL53L5_IOCTL_SET_PASS_FAIL \ + _IOW(VL53L8_IOCTL_CHAR, 0x22, struct vl53l8_update_data_t) + +#define VL53L5_IOCTL_SET_FILE_LIST \ + _IOW(VL53L8_IOCTL_CHAR, 0x23, struct vl53l8_file_list_t) + +#define VL53L8_IOCTL_SET_GLARE_FILTER_TUNING \ + _IOW(VL53L8_IOCTL_CHAR, 0x24, struct vl53l8_k_glare_filter_tuning_t) + +#define VL53L8_IOCTL_SET_TRANSFER_SPEED_HZ \ + _IOW(VL53L8_IOCTL_CHAR, 0x25, unsigned int) +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +#define VL53L8_IOCTL_GET_STATUS \ + _IOR(VL53L8_IOCTL_CHAR, 0x55, int) +#endif +#endif diff --git a/drivers/sensors/vl53l8/inc/vl53l8_k_ioctl_controls.h b/drivers/sensors/vl53l8/inc/vl53l8_k_ioctl_controls.h new file mode 100644 index 000000000000..4507ce3a95f8 --- /dev/null +++ b/drivers/sensors/vl53l8/inc/vl53l8_k_ioctl_controls.h @@ -0,0 +1,198 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L8_K_IOCTL_CONTROLS_H_ +#define _VL53L8_K_IOCTL_CONTROLS_H_ + +#include "vl53l8_k_module_data.h" +#include "vl53l8_k_user_data.h" + +int vl53l8_get_firmware_version(struct vl53l8_k_module_t *p_module, struct vl53l8_k_version_t *p_version); +int vl53l8_ioctl_get_version(struct vl53l8_k_module_t *p_module, void __user *p); +int vl53l8_ioctl_init(struct vl53l8_k_module_t *p_module); + +int vl53l8_ioctl_term(struct vl53l8_k_module_t *p_module); +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE +int vl53l8_ioctl_get_version(struct vl53l8_k_module_t *p_module, + void __user *p); +#endif +int vl53l8_ioctl_get_module_info(struct vl53l8_k_module_t *p_module, + void __user *p); + +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE +int vl53l8_ioctl_start(struct vl53l8_k_module_t *p_module, void __user *p); + +int vl53l8_ioctl_stop(struct vl53l8_k_module_t *p_module, void __user *p); +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +int vl53l8_ioctl_start(struct vl53l8_k_module_t *p_module); + +int vl53l8_ioctl_stop(struct vl53l8_k_module_t *p_module); +#endif + +int vl53l8_ioctl_get_range(struct vl53l8_k_module_t *p_module, void __user *p); + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +int vl53l8_set_device_parameters(struct vl53l8_k_module_t *p_module, + int config); +#endif +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE +int vl53l8_ioctl_set_device_parameters(struct vl53l8_k_module_t *p_module, + void __user *p); + +int vl53l8_ioctl_get_device_parameters(struct vl53l8_k_module_t *p_module, + void __user *p); + +int vl53l8_ioctl_get_error_info(struct vl53l8_k_module_t *p_module, + void __user *p); +#endif +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE +int vl53l8_ioctl_set_power_mode(struct vl53l8_k_module_t *p_module, + void __user *p); +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG +void vl53l8_last_error_counter(struct vl53l8_k_module_t *p_module, int err); +#endif +int vl53l8_ioctl_set_power_mode(struct vl53l8_k_module_t *p_module, + void __user *p, enum vl53l5_power_states state); +#endif + +int vl53l8_ioctl_poll_data_ready(struct vl53l8_k_module_t *p_module); + +int vl53l8_ioctl_read_p2p_calibration(struct vl53l8_k_module_t *p_module); + +int vl53l8_ioctl_read_shape_calibration(struct vl53l8_k_module_t *p_module); + +int vl53l8_perform_calibration(struct vl53l8_k_module_t *p_module, + int flow); + +int vl53l8_ioctl_read_generic_shape(struct vl53l8_k_module_t *p_module); + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +int vl53l8_ioctl_set_ranging_rate(struct vl53l8_k_module_t *p_module, + uint32_t rate); + +int vl53l8_ioctl_set_integration_time_us(struct vl53l8_k_module_t *p_module, + uint32_t integration); +#else +int vl53l8_ioctl_set_ranging_rate(struct vl53l8_k_module_t *p_module, + void __user *p); + +int vl53l8_ioctl_set_integration_time_us(struct vl53l8_k_module_t *p_module, + void __user *p); +#endif + +int vl53l8_ioctl_set_asz_tuning(struct vl53l8_k_module_t *p_module, + void __user *p); +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE +int vl53l8_ioctl_set_glare_filter_tuning(struct vl53l8_k_module_t *p_module, + void __user *p); + +int vl53l8_ioctl_set_transfer_speed_hz(struct vl53l8_k_module_t *p_module, + void __user *p); +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +#ifdef CONFIG_SEPARATE_IO_CORE_POWER +#define ALL_VDD_ENABLED 0x7 +#else +#define ALL_VDD_ENABLED 0x3 +#endif +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG +void vl53l8_check_ldo_onoff(struct vl53l8_k_module_t *data); +#endif +#ifdef CONFIG_SEPARATE_IO_CORE_POWER +int vl53l8_regulator_init_state(struct vl53l8_k_module_t *data); +#endif +int vl53l8_set_asz_tuning(struct vl53l8_k_module_t *p_module, struct vl53l8_k_asz_tuning_t *asz_layout); +void vl53l8_power_onoff(struct vl53l8_k_module_t *p_module, bool on); +int vl53l8_ldo_onoff(struct vl53l8_k_module_t *p_module, int io, bool on); +int vl53l8_pin_control(struct vl53l8_k_module_t *p_module, bool on); + +int vl53l8_ioctl_read_open_cal_p2p_calibration(struct vl53l8_k_module_t *p_module); + +int vl53l8_ioctl_read_open_cal_shape_calibration(struct vl53l8_k_module_t *p_module); + +int vl53l8_ioctl_get_cal_data(struct vl53l8_k_module_t *p_module, void __user *p); + +int vl53l8_ioctl_get_status(struct vl53l8_k_module_t *p_module, void __user *p); + +int vl53l8_ioctl_set_cal_data(struct vl53l8_k_module_t *p_module, void __user *p); + +int vl53l8_ioctl_set_pass_fail(struct vl53l8_k_module_t *p_module, void __user *p); + +int vl53l8_ioctl_set_file_list(struct vl53l8_k_module_t *p_module, void __user *p); + +int vl53l8_input_report(struct vl53l8_k_module_t *p_module, int type, int cmd); + +int vl53l8_set_integration_time_us(struct vl53l8_k_module_t *p_module, uint32_t integration); +int vl53l8_set_ranging_rate(struct vl53l8_k_module_t *p_module, uint32_t rate); +int vl53l8_set_asz_tuning(struct vl53l8_k_module_t *p_module, struct vl53l8_k_asz_tuning_t *asz_layout); +int vl53l8_set_transfer_speed_hz(struct vl53l8_k_module_t *p_module, unsigned int freq); + +int vl53l8_load_factory_calibration(struct vl53l8_k_module_t *p_module); +int vl53l8_load_open_calibration(struct vl53l8_k_module_t *p_module); +void vl53l8_load_calibration(struct vl53l8_k_module_t *p_module); +#ifdef VL53L5_GET_DATA_ROTATION +void vl53l8_rotate_report_data_u16(u16 *raw_data, int rotation); +void vl53l8_rotate_report_data_u32(u32 *raw_data, int rotation); +#endif +void vl53l8_rotate_report_data_int32(int32_t *raw_data, int rotation); +#endif +#endif diff --git a/drivers/sensors/vl53l8/inc/vl53l8_k_logging.h b/drivers/sensors/vl53l8/inc/vl53l8_k_logging.h new file mode 100644 index 000000000000..d54662efed9e --- /dev/null +++ b/drivers/sensors/vl53l8/inc/vl53l8_k_logging.h @@ -0,0 +1,75 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L8_K_LOGGING_H_ +#define _VL53L8_K_LOGGING_H_ + +#include "vl53l5_platform_log.h" + +#define vl53l8_k_trace_print(level, ...) \ + _LOG_TRACE_PRINT(VL53L5_TRACE_MODULE_VL53L5, \ + level, VL53L5_TRACE_FUNCTION_ALL, ##__VA_ARGS__) + +#define LOG_FUNCTION_START(fmt, args...) \ + _LOG_FUNCTION_START(VL53L5_TRACE_MODULE_VL53L5, fmt, ##args) + +#define LOG_FUNCTION_END(status, args...) \ + _LOG_FUNCTION_END(VL53L5_TRACE_MODULE_VL53L5, status, ##args) + +#endif diff --git a/drivers/sensors/vl53l8/inc/vl53l8_k_module.h b/drivers/sensors/vl53l8/inc/vl53l8_k_module.h new file mode 100644 index 000000000000..db574c564bf5 --- /dev/null +++ b/drivers/sensors/vl53l8/inc/vl53l8_k_module.h @@ -0,0 +1,89 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L8_K_MODULE_H_ +#define _VL53L8_K_MODULE_H_ + +#include +#include "vl53l8_k_module_data.h" + +long vl53l8_k_ioctl(struct file *file, unsigned int cmd, unsigned long arg); + +int vl53l8_k_open(struct inode *inode, struct file *file); + +int vl53l8_k_release(struct inode *inode, struct file *file); + +int vl53l8_k_spi_probe(struct spi_device *device); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +void vl53l8_k_spi_remove(struct spi_device *device); +#else +int vl53l8_k_spi_remove(struct spi_device *device); +#endif + + +int vl53l8_k_setup(struct vl53l8_k_module_t *p_module); + +void vl53l8_k_cleanup(struct vl53l8_k_module_t *data); + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +void vl53l8_k_spi_shutdown(struct spi_device *device); +void vl53l8_k_re_init(struct vl53l8_k_module_t *p_module); +#endif + +#endif diff --git a/drivers/sensors/vl53l8/inc/vl53l8_k_module_data.h b/drivers/sensors/vl53l8/inc/vl53l8_k_module_data.h new file mode 100644 index 000000000000..48d76bf9b7fe --- /dev/null +++ b/drivers/sensors/vl53l8/inc/vl53l8_k_module_data.h @@ -0,0 +1,286 @@ +/******************************************************************************* +* Copyright (c) 2023, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L8_K_MODULE_DATA_H_ +#define _VL53L8_K_MODULE_DATA_H_ + +#include +#include +#include "vl53l8_k_user_api.h" +#include "vl53l5_platform_user_data.h" +#include "vl53l8_k_state_def.h" +#include "vl53l8_k_spi_def.h" +#include "vl53l8_k_gpio_def.h" +#include "vl53l8_k_driver_config.h" +#include "vl53l8_k_glare_filter.h" + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +#include +#include +#ifdef CONFIG_SENSORS_VL53L8_QCOM +#include //for sec dump +#endif +#include //for input_dev +#endif +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG +#define MAX_TABLE 152 +#endif +#define AVDD 0 +#define IOVDD 1 +#define COREVDD 2 +#define VDD_MAX_CNT 3 +struct vl53l8_k_module_t { +#ifdef STM_VL53L5_KOR_CODE + struct input_dev *input_dev_ps; + /*!< input device used for sending event */ +#endif + + int id; + + char name[64]; + + struct miscdevice miscdev; + + struct vl53l5_dev_handle_t stdev; + + const char *firmware_name; + + int comms_type; + + int config_preset; + + struct mutex mutex; + + struct mutex cal_mutex; + + struct vl53l8_k_spi_data_t spi_info; + + struct vl53l8_k_gpio_settings_t gpio; + + unsigned char comms_buffer[VL53L5_COMMS_BUFFER_SIZE_BYTES]; + + enum vl53l8_k_state_preset state_preset; + + struct vl53l5_ranging_mode_flags_t ranging_flags; + + enum vl53l5_power_states power_state; + + enum vl53l8_range_servicing_mode range_mode; + + struct range_t { +#ifndef STM_VL53L5_SUPPORT_SEC_CODE + int count; +#else + unsigned long count; +#endif + + bool is_valid; + + struct vl53l5_range_data_t data; + } range; + + struct calibration_data_t { + + struct vl53l5_version_t version; + + struct vl53l5_patch_version_t patch_version; + + struct vl53l5_module_info_t info; + + struct vl53l5_calibration_data_t cal_data; + } calibration; + + int polling_count; +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + int ioctl_enable_status; +#endif + unsigned int polling_start_time_ms; + + unsigned int polling_sleep_time_ms; + + bool irq_is_active; + + bool irq_is_running; + + int irq_val; + + bool irq_wq_is_running; + + struct workqueue_struct *irq_wq; + + struct work_struct data_work; + + struct delayed_work dwork; + + struct list_head reader_list; + + wait_queue_head_t wait_queue; + + int last_driver_error; +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG + struct errdata_t { + int last_error_code; + u8 last_error_cnt; + } errdata[MAX_TABLE]; +#endif + struct vl53l5_module_info_t m_info; + + struct vl53l8_k_glare_filter_tuning_t gf_tuning; + + struct vl53l5_patch_version_t patch_ver; + + unsigned int transfer_speed_hz; + + struct vl53l8_k_bin_version bin_version; + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + struct vl53l8_cal_data_t cal_data; + struct device *factory_device; + struct input_dev *input_dev; //for input_dev + struct regulator *avdd_vreg; + struct regulator *iovdd_vreg; +#ifdef CONFIG_SEPARATE_IO_CORE_POWER + struct regulator *corevdd_vreg; + struct delayed_work power_delayed_work; +#ifdef CONFIG_SENSORS_VL53L8_SUPPORT_RESUME_WORK + struct work_struct resume_work; +#endif +#endif + struct notifier_block dump_nb; + struct vl53l5_range_fac_results_t { +#ifdef VL53L5_PER_ZONE_RESULTS_ON + struct vl53l5_range_per_zone_results_t per_zone_results; +#endif + +#ifdef VL53L5_PER_TGT_RESULTS_ON + struct vl53l5_range_per_tgt_results_t per_tgt_results; +#endif +#ifdef VL53L5_D16_PER_TARGET_DATA_ON + struct vl53l5_d16_per_tgt_results_t d16_per_target_data; +#endif +#ifdef VL53L5_GD_OP_STATUS_ON + struct vl53l5_gd_op_status_t gd_op_status; +#endif + + } range_data; + struct range_sensor_data_t af_range_data; + const char *genshape_name; + const char *avdd_vreg_name; + const char *iovdd_vreg_name; +#ifdef CONFIG_SEPARATE_IO_CORE_POWER + const char *corevdd_vreg_name; +#endif + uint32_t fac_rotation_mode; + uint32_t rotation_mode; + uint32_t force_suspend_count; + uint32_t integration_init; + uint32_t rate_init; + + int32_t data[64]; + int32_t number_of_zones; + int32_t print_counter; + int32_t min; + int32_t avg; + int32_t max; + int32_t max_peak_signal; +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG + int8_t ldo_status; +#endif + int8_t enabled; + int8_t test_mode; + int8_t failed_count; + int8_t update_flag; + int8_t pass_fail_flag; + int8_t file_list; + uint8_t ldo_prev_state[VDD_MAX_CNT]; + + bool load_calibration; + bool read_p2p_cal_data; + bool read_data_valid; + bool suspend_state; + bool probe_done; + bool sensors_register_done; +#endif + // ASZs Tuning Parameter + uint32_t asz_0_ll_zone_id; + uint32_t asz_1_ll_zone_id; + uint32_t asz_2_ll_zone_id; + uint32_t asz_3_ll_zone_id; + + // Integration time 2msec ~ 15msec + uint32_t integration; + // Sampling rate : 1hz ~15hz + uint32_t rate; +}; + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +#define CMD_READ_CAL_FILE 0 // 0x1 +#define CMD_WRITE_CAL_FILE 1 // 0x2 +#define CMD_WRITE_P2P_FILE 2 // 0x4 +#define CMD_WRITE_SHAPE_FILE 3 // 0x8 +#define CMD_READ_P2P_CAL_FILE 4 // 0x10 +#define CMD_READ_SHAPE_CAL_FILE 5 // 0x20 +#define CMD_CHECK_CAL_FILE_TYPE 6 // 0x40 +#define CMD_DELTE_OPENCAL_FILE 7 // 0x80 + + +#define TIMEOUT_CNT 50 +#endif + +#endif diff --git a/drivers/sensors/vl53l8/inc/vl53l8_k_range_wait_handler.h b/drivers/sensors/vl53l8/inc/vl53l8_k_range_wait_handler.h new file mode 100644 index 000000000000..8b6bc3faedd9 --- /dev/null +++ b/drivers/sensors/vl53l8/inc/vl53l8_k_range_wait_handler.h @@ -0,0 +1,71 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L8_K_RANGE_WAIT_HANDLER_H_ +#define _VL53L8_K_RANGE_WAIT_HANDLER_H_ + +#include "vl53l8_k_module_data.h" + +int vl53l8_k_check_data_ready(struct vl53l8_k_module_t *p_module, int *p_ready); + +int vl53l8_k_check_for_timeout(struct vl53l8_k_module_t *p_module, + int timeout_ms, + int *p_timeout_occurred); + +#endif diff --git a/drivers/sensors/vl53l8/inc/vl53l8_k_spi_def.h b/drivers/sensors/vl53l8/inc/vl53l8_k_spi_def.h new file mode 100644 index 000000000000..8f42ac7ffa79 --- /dev/null +++ b/drivers/sensors/vl53l8/inc/vl53l8_k_spi_def.h @@ -0,0 +1,74 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L8_K_SPI_DEF_H_ +#define _VL53L8_K_SPI_DEF_H_ + +#include + +struct vl53l8_k_spi_data_t { + + struct spi_device *device; + + struct kref ref; + + int is_initialised; +}; + +#endif diff --git a/drivers/sensors/vl53l8/inc/vl53l8_k_state_def.h b/drivers/sensors/vl53l8/inc/vl53l8_k_state_def.h new file mode 100644 index 000000000000..49c53ca54e3b --- /dev/null +++ b/drivers/sensors/vl53l8/inc/vl53l8_k_state_def.h @@ -0,0 +1,78 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L8_K_STATE_DEF_H_ +#define _VL53L8_K_STATE_DEF_H_ + +enum vl53l8_k_state_preset { + + VL53L8_STATE_NOT_PRESENT, + + VL53L8_STATE_PRESENT, + + VL53L8_STATE_POWERED, + + VL53L8_STATE_LOW_POWER, + + VL53L8_STATE_INITIALISED, + + VL53L8_STATE_RANGING +}; + +#endif diff --git a/drivers/sensors/vl53l8/inc/vl53l8_k_user_api.h b/drivers/sensors/vl53l8/inc/vl53l8_k_user_api.h new file mode 100644 index 000000000000..1f1edb5f617f --- /dev/null +++ b/drivers/sensors/vl53l8/inc/vl53l8_k_user_api.h @@ -0,0 +1,67 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L8_K_USER_API_H_ +#define _VL53L8_K_USER_API_H_ + +#include "vl53l8_k_ioctl_codes.h" +#include "vl53l8_k_user_def.h" +#include "vl53l8_k_error_codes.h" + +#endif diff --git a/drivers/sensors/vl53l8/inc/vl53l8_k_user_data.h b/drivers/sensors/vl53l8/inc/vl53l8_k_user_data.h new file mode 100644 index 000000000000..3b1d15ea7c5a --- /dev/null +++ b/drivers/sensors/vl53l8/inc/vl53l8_k_user_data.h @@ -0,0 +1,181 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L8_K_IOCTL_DATA_H_ +#define _VL53L8_K_IOCTL_DATA_H_ + +#include "vl53l8_k_user_def.h" +#include "vl53l5_api_ranging.h" +#include "vl53l5_api_range_decode.h" +#include "vl53l5_api_core.h" +#include "vl53l5_patch_api_core.h" +#include "vl53l5_power_states.h" + +struct vl53l8_k_raw_buffer_data_t { + + uint8_t buffer[VL53L8_K_CONFIG_DATA_MAX_SIZE]; + + uint32_t count; +}; + +struct vl53l8_k_get_parameters_data_t { + struct vl53l8_k_raw_buffer_data_t raw_data; + uint32_t headers[VL53L8_K_MAX_NUM_GET_HEADERS]; + uint32_t header_count; +}; + +struct vl53l8_k_asz_t { + uint8_t asz_0[8]; + uint8_t asz_1[8]; + uint8_t asz_2[8]; + uint8_t asz_3[8]; +}; + +struct vl53l8_k_asz_tuning_t { + + uint32_t asz_0_ll_zone_id; + + uint32_t asz_1_ll_zone_id; + + uint32_t asz_2_ll_zone_id; + + uint32_t asz_3_ll_zone_id; +}; + +struct vl53l8_k_bin_version { + unsigned int fw_ver_major; + unsigned int fw_ver_minor; + unsigned int config_ver_major; + unsigned int config_ver_minor; +}; +struct vl53l8_k_version_t { + struct vl53l5_version_t driver; + struct vl53l5_patch_version_t patch; + struct { + uint16_t ver_major; + uint16_t ver_minor; + uint16_t ver_build; + uint16_t ver_revision; + } kernel; + struct vl53l8_k_bin_version bin_version; +}; + +enum vl53l8_k_config_preset { + VL53L8_CAL__NULL_XTALK_SHAPE = 0, + VL53L8_CAL__GENERIC_XTALK_SHAPE, + VL53L8_CAL__XTALK_GRID_SHAPE_MON, + VL53L8_CAL__DYN_XTALK_MONITOR, + VL53L8_CFG__STATIC__SS_0PC, + VL53L8_CFG__STATIC__SS_1PC, + VL53L8_CFG__STATIC__SS_2PC, + VL53L8_CFG__STATIC__SS_4PC, + VL53L8_CFG__XTALK_GEN1_8X8_1000, + VL53L8_CFG__XTALK_GEN2_8X8_300, + VL53L8_CFG__B2BWB_8X8_OPTION_0, + VL53L8_CFG__B2BWB_8X8_OPTION_1, + VL53L8_CFG__B2BWB_8X8_OPTION_2, + VL53L8_CFG__B2BWB_8X8_OPTION_3, + VL53L8_CFG__PATCH_DEFAULT_UCP4, +}; +/* +VL53L8_CFG__B2BWB_8X8_OPTION_0 => D16 data only + MODE1 +VL53L8_CFG__B2BWB_8X8_OPTION_1 => D16 data only + MODE3 +VL53L8_CFG__B2BWB_8X8_OPTION_2 => D16 data + DMAX + AMB + PEAK + GD + TMP + MODE1 +VL53L8_CFG__B2BWB_8X8_OPTION_3 => D16 data + DMAX + AMB + PEAK + GD + TMP + MODE3 +*/ +struct vl53l8_fw_header_t { + unsigned int fw_ver_major; + unsigned int fw_ver_minor; + unsigned int config_ver_major; + unsigned int config_ver_minor; + unsigned int fw_offset; + unsigned int fw_size; + unsigned int buf00_offset; + unsigned int buf00_size; + unsigned int buf01_offset; + unsigned int buf01_size; + unsigned int buf02_offset; + unsigned int buf02_size; + unsigned int buf03_offset; + unsigned int buf03_size; + unsigned int buf04_offset; + unsigned int buf04_size; + unsigned int buf05_offset; + unsigned int buf05_size; + unsigned int buf06_offset; + unsigned int buf06_size; + unsigned int buf07_offset; + unsigned int buf07_size; + unsigned int buf08_offset; + unsigned int buf08_size; + unsigned int buf09_offset; + unsigned int buf09_size; + unsigned int buf10_offset; + unsigned int buf10_size; + unsigned int buf11_offset; + unsigned int buf11_size; + unsigned int buf12_offset; + unsigned int buf12_size; + unsigned int buf13_offset; + unsigned int buf13_size; + unsigned int buf14_offset; + unsigned int buf14_size; +}; + +#endif diff --git a/drivers/sensors/vl53l8/inc/vl53l8_k_user_def.h b/drivers/sensors/vl53l8/inc/vl53l8_k_user_def.h new file mode 100644 index 000000000000..f6827c3d26e8 --- /dev/null +++ b/drivers/sensors/vl53l8/inc/vl53l8_k_user_def.h @@ -0,0 +1,220 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L8_K_USER_DEF_H_ +#define _VL53L8_K_USER_DEF_H_ + +#include "vl53l5_globals.h" +#include "vl53l5_calibration_map_bh.h" +#include "vl53l5_core_map_bh.h" + +#define STM_VL53L8_SPI_DEFAULT_TRANSFER_SPEED_HZ 3000000 + +#define VL53L8_K_CONFIG_DATA_MAX_SIZE VL53L5_COMMS_BUFFER_SIZE_BYTES + +#define VL53L8_K_FIRMWARE_MAX_SIZE 100000 + +#define VL53L8_K_MAX_NUM_GET_HEADERS 32 +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE +#define VL53L8_K_DRIVER_NAME "stmvl53l8" +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +#define VL53L8_K_DRIVER_NAME "range_sensor" +#endif +#define VL53L8_K_COMMS_TYPE_I2C 0 + +#define VL53L8_K_COMMS_TYPE_SPI 1 + +#define VL53L8_K_P2P_FILE_SIZE 1520U + +#define VL53L8_K_P2P_BH_SZ 28U +#define VL53L8_K_P2P_CAL_BLOCK_HEADERS { \ + VL53L5_MAP_VERSION_BH, \ + VL53L5_CAL_REF_SPAD_INFO_BH,\ + VL53L5_POFFSET_GRID_META_BH, \ + VL53L5_POFFSET_PHASE_STATS_BH, \ + VL53L5_POFFSET_TEMPERATURE_STATS_BH, \ + VL53L5_POFFSET_GRID_RATE_CAL__GRID_DATA__RATE_KCPS_PER_SPAD_BH, \ + VL53L5_POFFSET_GRID_SPADS_CAL__GRID_DATA_EFFECTIVE_SPAD_COUNT_BH, \ + VL53L5_POFFSET_GRID_OFFSET_CAL__GRID_DATA__RANGE_MM_BH, \ + VL53L5_POFFSET_ERROR_STATUS_BH, \ + VL53L5_POFFSET_WARNING_STATUS_BH, \ + VL53L5_PXTALK_GRID_META_BH, \ + VL53L5_PXTALK_PHASE_STATS_BH, \ + VL53L5_PXTALK_TEMPERATURE_STATS_BH, \ + VL53L5_PXTALK_GRID_RATE_CAL__GRID_DATA__RATE_KCPS_PER_SPAD_BH, \ + VL53L5_PXTALK_GRID_SPADS_CAL__GRID_DATA_EFFECTIVE_SPAD_COUNT_BH, \ + VL53L5_PXTALK_ERROR_STATUS_BH, \ + VL53L5_PXTALK_WARNING_STATUS_BH, \ + VL53L5_PXTALK_MON_META_BH, \ + VL53L5_PXTALK_MON_ZONES_CAL__XMON__ZONE__X_OFF_BH, \ + VL53L5_PXTALK_MON_ZONES_CAL__XMON__ZONE__Y_OFF_BH, \ + VL53L5_PXTALK_MON_ZONES_CAL__XMON__ZONE__WIDTH_BH, \ + VL53L5_PXTALK_MON_ZONES_CAL__XMON__ZONE__HEIGHT_BH, \ + VL53L5_PXTALK_MON_DATA_CAL__XMON__ZONE__RATE_KCPS_SPAD_BH, \ + VL53L5_PXTALK_MON_DATA_CAL__XMON__ZONE__AVG_COUNT_BH, \ + VL53L5_PRAD2PERP_GRID_4X4_META_BH, \ + VL53L5_PRAD2PERP_GRID_4X4_DATA_CAL__GRID_DATA__SCALE_FACTOR_BH, \ + VL53L5_PRAD2PERP_GRID_8X8_META_BH, \ + VL53L5_PRAD2PERP_GRID_8X8_DATA_CAL__GRID_DATA__SCALE_FACTOR_BH} + +#define VL53L8_K_SHAPE_FILE_SIZE 316U + +#define VL53L8_K_SHAPE_CAL_BLOCK_HEADERS_SZ 3 + +#define VL53L8_K_SHAPE_CAL_BLOCK_HEADERS { \ + VL53L5_MAP_VERSION_BH, \ + VL53L5_PXTALK_SHAPE_META_BH, \ + VL53L5_PXTALK_SHAPE_DATA_CAL__XTALK_SHAPE__BIN_DATA_BH} + +#define VL53L8_K_DEVICE_INFO_SZ 59U + +#ifdef VL53L8_RAD2PERP_CAL +#define VL53L8_K_RAD2PERP_DEFAULT {\ + 0x40, 0x00, 0x00, 0x54,\ + 0x0B, 0x00, 0x05, 0x00,\ + 0xC0, 0x00, 0xD8, 0xBB,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x17, 0x04, 0x04, 0x0F,\ + 0x07, 0x10, 0x10, 0x01,\ + 0x02, 0x04, 0xE4, 0xBB,\ + 0x84, 0x0E, 0x1A, 0x0F,\ + 0x1A, 0x0F, 0x84, 0x0E,\ + 0x1A, 0x0F, 0xC3, 0x0F,\ + 0xC3, 0x0F, 0x1A, 0x0F,\ + 0x1A, 0x0F, 0xC3, 0x0F,\ + 0xC3, 0x0F, 0x1A, 0x0F,\ + 0x84, 0x0E, 0x1A, 0x0F,\ + 0x1A, 0x0F, 0x84, 0x0E,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00,\ + 0xC0, 0x00, 0x64, 0xBC,\ + 0x00, 0x00, 0x00, 0x00,\ + 0x17, 0x08, 0x08, 0x0B,\ + 0x03, 0x08, 0x08, 0x01,\ + 0x02, 0x04, 0x70, 0xBC,\ + 0x1E, 0x0E, 0x84, 0x0E,\ + 0xCC, 0x0E, 0xF2, 0x0E,\ + 0xF2, 0x0E, 0xCC, 0x0E,\ + 0x84, 0x0E, 0x1E, 0x0E,\ + 0x84, 0x0E, 0xF2, 0x0E,\ + 0x42, 0x0F, 0x6C, 0x0F,\ + 0x6C, 0x0F, 0x42, 0x0F,\ + 0xF2, 0x0E, 0x84, 0x0E,\ + 0xCC, 0x0E, 0x42, 0x0F,\ + 0x97, 0x0F, 0xC3, 0x0F,\ + 0xC3, 0x0F, 0x97, 0x0F,\ + 0x42, 0x0F, 0xCC, 0x0E,\ + 0xF2, 0x0E, 0x6C, 0x0F,\ + 0xC3, 0x0F, 0xF1, 0x0F,\ + 0xF1, 0x0F, 0xC3, 0x0F,\ + 0x6C, 0x0F, 0xF2, 0x0E,\ + 0xF2, 0x0E, 0x6C, 0x0F,\ + 0xC3, 0x0F, 0xF1, 0x0F,\ + 0xF1, 0x0F, 0xC3, 0x0F,\ + 0x6C, 0x0F, 0xF2, 0x0E,\ + 0xCC, 0x0E, 0x42, 0x0F,\ + 0x97, 0x0F, 0xC3, 0x0F,\ + 0xC3, 0x0F, 0x97, 0x0F,\ + 0x42, 0x0F, 0xCC, 0x0E,\ + 0x84, 0x0E, 0xF2, 0x0E,\ + 0x42, 0x0F, 0x6C, 0x0F,\ + 0x6C, 0x0F, 0x42, 0x0F,\ + 0xF2, 0x0E, 0x84, 0x0E,\ + 0x1E, 0x0E, 0x84, 0x0E,\ + 0xCC, 0x0E, 0xF2, 0x0E,\ + 0xF2, 0x0E, 0xCC, 0x0E,\ + 0x84, 0x0E, 0x1E, 0x0E} +#endif + +#define VL53L8_CAL_P2P_FILENAME \ + "/home/pi/Documents/vl53l8_cal_p2p.bin" + +#define VL53L8_CAL_SHAPE_FILENAME \ + "/home/pi/Documents/vl53l8_cal_shape.bin" + +#define VL53L8_GENERIC_SHAPE_FILENAME \ + "/home/pi/Documents/vl53l8_generic_xtalk_shape.bin" + +#define VL53L8_TCDM_FILENAME \ + "/home/pi/Documents/vl53l8_tcdm.bin" + +#endif diff --git a/drivers/sensors/vl53l8/inc/vl53l8_k_version.h b/drivers/sensors/vl53l8/inc/vl53l8_k_version.h new file mode 100644 index 000000000000..4634fbc4618e --- /dev/null +++ b/drivers/sensors/vl53l8/inc/vl53l8_k_version.h @@ -0,0 +1,71 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L8_K_VERSION_H_ +#define _VL53L8_K_VERSION_H_ + +#define VL53L8_K_VER_MAJOR 1 + +#define VL53L8_K_VER_MINOR 4 + +#define VL53L8_K_VER_BUILD 2 + +#define VL53L8_K_VER_REVISION 0 + +#endif diff --git a/drivers/sensors/vl53l8/platform/inc/vl53l5_algo_build_config.h b/drivers/sensors/vl53l8/platform/inc/vl53l5_algo_build_config.h new file mode 100644 index 000000000000..53faa9eb7947 --- /dev/null +++ b/drivers/sensors/vl53l8/platform/inc/vl53l5_algo_build_config.h @@ -0,0 +1,83 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L5_ALGO_BUILD_CONFIG_H_ +#define _VL53L5_ALGO_BUILD_CONFIG_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define VL53L5_ALGO_ANTI_FLICKER_FILTER_ON +#define VL53L5_ALGO_GLASS_DETECTION_ON +#define VL53L5_ALGO_RANGE_ROTATION_ON +#define VL53L5_ALGO_LONG_TAIL_FILTER_ON +#define VL53L5_ALGO_OUTPUT_TARGET_SORT_ON +// #define VL53L5_ALGO_DEPTH16_ON +// #define VL53L5_ALGO_DEPTH16_BUFFER_ON + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/platform/inc/vl53l5_platform.h b/drivers/sensors/vl53l8/platform/inc/vl53l5_platform.h new file mode 100644 index 000000000000..dd55407fc055 --- /dev/null +++ b/drivers/sensors/vl53l8/platform/inc/vl53l5_platform.h @@ -0,0 +1,357 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L5_PLATFORM_H_ +#define _VL53L5_PLATFORM_H_ + +#include "vl53l5_platform_log.h" +#include "vl53l5_platform_user_data.h" +#include "vl53l5_error_codes.h" + +/** + * @file vl53l5_platform.h + * + * @brief Customer implemented platform interface + * + */ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup VL53L5_platform_group VL53L5 Platform + * @brief Platform comms initialisation, termination, timing, and gpio + * functions. + * @{ + */ + +#define GPIO_LOW 0 +#define GPIO_HIGH 1 + +/** + * @defgroup VL53L5_platform_group VL53L5 Platform + * @brief Platform comms initialisation, termination, timing, and gpio + * functions. + * @{ + */ +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE +/** + * @brief Performs any initialisation required by the platform specific I2C or + * GPIO functions + * + * @param[in,out] p_dev pointer to instance of device handle struct + * + * @return status (error code if < 0) + */ +int32_t vl53l5_comms_initialise(struct vl53l5_dev_handle_t *p_dev); + +/** @brief Closes any platform specific handles opened with + * vl53l5_comms_initialise() + * + * @param[in,out] p_dev pointer to instance of device handle struct + * + * @return status (error code if < 0) + */ +int32_t vl53l5_comms_close(struct vl53l5_dev_handle_t *p_dev); +#endif +/** @brief Writes one or more bytes to the VL53L5 device using the I2C bus + * + * @param[in] p_dev pointer to instance of device handle struct + * @param[in] index I2C 16-bit register index + * @param[in] pdata pointer to a buffer containing the bytes to + * write on the I2C bus + * @param[in] count number of bytes to write to the I2C bus + * + * @return status (error code if < 0) + */ +int32_t vl53l5_write_multi( + struct vl53l5_dev_handle_t *p_dev, uint16_t index, uint8_t *pdata, + uint32_t count); + +/** @brief Read one or more bytes from the VL53L5 device using the I2C bus + * + * @param[in] p_dev pointer to instance of device handle struct + * @param[in] index I2C 16-bit register index + * @param[out] pdata pointer to a buffer to be populated with + * bytes read from the device on the I2C bus + * @param[in] count number of bytes to read from the I2C bus + * + * @return status (error code if < 0) + */ +int32_t vl53l5_read_multi( + struct vl53l5_dev_handle_t *p_dev, uint16_t index, uint8_t *pdata, + uint32_t count); + +/** @brief A platform specific delay function to wait for wait_us microseconds + * + * @param[in] p_dev pointer to instance of device handle struct + * @param[in] wait_us time in microseconds to wait + * + * @return status (error code if < 0) + */ +int32_t vl53l5_wait_us(struct vl53l5_dev_handle_t *p_dev, uint32_t wait_us); + +/** @brief A platform specific delay function to wait for wait_ms milliseconds + * + * @param[in] p_dev pointer to instance of device handle struct + * @param[in] wait_ms time in milliseconds to wait + * + * @return status (error code if < 0) + */ +int32_t vl53l5_wait_ms(struct vl53l5_dev_handle_t *p_dev, uint32_t wait_ms); +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE +/** + * @brief Implements support for the Low Power Control GPIO pin of the VL53L5 + * device + * + * @param[in] p_dev pointer to instance of device handle struct + * @param[in] value Low Power Pin GPIO state. 0 = GPIO low, 1 = GPIO high + * + * @return status (error code if < 0) + * + * @note This function is optional if fixed supplies are in use, or for + * alternative control of device power supplies.
+ * See data sheet for more information. + */ +int32_t vl53l5_gpio_low_power_control( + struct vl53l5_dev_handle_t *p_dev, uint8_t value); + +/** + * @brief Implements support power enable or disable to the VL53L5 device. + * + * @param[in] p_dev pointer to instance of device handle struct + * @param[in] value Power Enable GPIO state. 0 = GPIO low, 1 = GPIO high + * + * @return status (error code if < 0) + * + * @note This function is optional if fixed supplies are in use, or for + * alternative control of device power supplies.
+ * See data sheet for more information. + */ +int32_t vl53l5_gpio_power_enable( + struct vl53l5_dev_handle_t *p_dev, uint8_t value); + +/** + * @brief Implements support for comms select to the VL53L5 device. + * + * @param[in] p_dev pointer to instance of device handle struct + * @param[in] value Comms select GPIO state. 0 = I2C, 1 = SPI + * + * @return status (error code if < 0) + * + * @note This function is optional if fixed supplies are in use, or for + * alternative control of device power supplies.
+ * See data sheet for more information. + */ +int32_t vl53l5_gpio_comms_select( + struct vl53l5_dev_handle_t *p_dev, uint8_t value); +#endif +/** + * @brief Implements support for interrupt handler enabling on GPIO2 + * + * @param[in] p_dev pointer to instance of device handle struct + * @param[in] function a function callback supplied by the caller, for + * interrupt notification + * @param[in] edge_type falling edge or rising edge interrupt detection + * + * @return status (error code if < 0) + */ +int32_t vl53l8_gpio_interrupt_enable( + struct vl53l5_dev_handle_t *p_dev, void (*function)(void), + uint8_t edge_type); + +/** + * @brief Implements support for interrupt handler disabling on GPIO2 + * + * @param[in] p_dev pointer to instance of device handle struct + * + * @return status (error code if < 0) + */ +int32_t vl53l8_gpio_interrupt_disable(struct vl53l5_dev_handle_t *p_dev); + +/** + * @brief Get a pointer to a debug buffer. If no persistent debug buffer is + * implemented, this function shall allocate memory for the debug buffer. + * The length of the buffer must be VL53L5_COMMS_BUFFER_SIZE_BYTES bytes. + * + * @param[in] p_dev pointer to instance of device handle struct + * @param[out] pp_comms_buffer : pointer to comms buffer pointer + * + * @return VL53L5_ERROR_NONE Success + * @return "Other error code" See ::vl53l5_Error + */ +int32_t vl53l8_get_comms_buffer( + struct vl53l5_dev_handle_t *p_dev, + uint8_t **pp_comms_buffer); + +/** + * @brief Free the comms buffer. This function should only release memory + * allocated by vl53l8_get_comms_buffer. If a persistent (static) comms + * buffer is in use, this function shall only return VL53L5_ERROR_NONE. + * + * @param[in] p_dev pointer to instance of device handle struct + * @param[out] p_comms_buffer : comms buffer pointer + * + * @return VL53L5_ERROR_NONE Success + * @return "Other error code" See ::vl53l5_Error + */ +int32_t vl53l8_free_comms_buffer( + struct vl53l5_dev_handle_t *p_dev, + uint8_t *p_comms_buffer); + +/** + * @brief Get a pointer to a debug buffer. If no persistent debug buffer is + * implemented, this function shall allocate memory for the debug buffer. + * The length of the buffer must be VL53L5_DEBUG_BUFFER_SIZE_BYTES bytes. + * + * @param[in] p_dev pointer to instance of device handle struct + * @param[out] pp_debug_buffer : pointer to debug buffer pointer + * + * @return VL53L5_ERROR_NONE Success + * @return "Other error code" See ::vl53l5_Error + */ +int32_t vl53l8_get_debug_buffer( + struct vl53l5_dev_handle_t *p_dev, + uint8_t **pp_debug_buffer); + +/** + * @brief Free the debug buffer. This function should only release memory + * allocated by vl53l8_get_debug_buffer. If a persistent (static) debug + * buffer is in use, this function shall only return VL53L5_ERROR_NONE. + * + * @param[in] p_dev pointer to instance of device handle struct + * @param[out] p_debug_buffer : debug buffer pointer + * + * @return VL53L5_ERROR_NONE Success + * @return "Other error code" See ::vl53l5_Error + */ +int32_t vl53l8_free_debug_buffer( + struct vl53l5_dev_handle_t *p_dev, + uint8_t *p_debug_buffer); + +/** + * @brief Returns the value (in milliseconds) of a free running system timer + * or clock. + * + * @param[in] p_dev pointer to instance of device handle struct + * @param[out] ptime_ms pointer to variable to receive the current time in + * milliseconds + * + * @return status (error code if < 0) + */ +int32_t vl53l5_get_tick_count( + struct vl53l5_dev_handle_t *p_dev, uint32_t *ptime_ms); + + +/** + * @brief Implements a comparison of timeout values which is safe from timer + * roll over. + * + * @param[in] p_dev pointer to instance of device handle struct + * @param[in] start_time_ms start time in milliseconds + * @param[in] end_time_ms end time in milliseconds + * @param[in] timeout_ms timeout in milliseconds + * + * @return status (VL53L5_ERROR_TIME_OUT if timeout occurred) + */ +int32_t vl53l5_check_for_timeout( + struct vl53l5_dev_handle_t *p_dev, uint32_t start_time_ms, + uint32_t end_time_ms, uint32_t timeout_ms); + +/** + * @brief Implements reading of a file in the kernel + * @param[in] p_dev pointer to instance of device handle struct + * @param[in] data pointer to buffer for data to be loaded into + * @param[in] size size of data to be read + * @param[in] path path to file to be read + * + * @return status (error code if < 0) + */ +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE +int32_t vl53l5_read_file(struct vl53l5_dev_handle_t *pdev, char *data, + int size, const char *path); + +/** + * @brief Implements saving of a file in the kernel + * @param[in] p_dev pointer to instance of device handle struct + * @param[in] data pointer to buffer for data to be saved from + * @param[in] size size of calibration data to be saved + * @param[in] path path to file to be saved + * @param[in] device_info pointer to buffer of device info data + * @param[in] info_size size of device info data to be saved + * + * @return status (error code if < 0) + */ +int32_t vl53l5_write_file(struct vl53l5_dev_handle_t *pdev, char *data, + int size, const char *path, char *device_info, + int info_size); +#endif + +#ifdef __cplusplus +} +#endif + +/** @} VL53L5_platform_group */ + +#endif diff --git a/drivers/sensors/vl53l8/platform/inc/vl53l5_platform_algo_limits.h b/drivers/sensors/vl53l8/platform/inc/vl53l5_platform_algo_limits.h new file mode 100644 index 000000000000..63ad5d0938fd --- /dev/null +++ b/drivers/sensors/vl53l8/platform/inc/vl53l5_platform_algo_limits.h @@ -0,0 +1,112 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 VL53L5_PLATFORM_ALGO_LIMITS_H +#define VL53L5_PLATFORM_ALGO_LIMITS_H + +#include "vl53l5_types.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup PLATFORM_ALGO_LIMITS Platform Algo Limits + * Platform algo limit definitions + * @{ + */ + + +/** uint32_t maximum value */ +#define VL53L5_UINT32_MAX ((uint32_t) 4294967295U) +/** uint32_t minimum value */ +#define VL53L5_UINT32_MIN ((uint32_t) 0U) +/** int32_t maximum value */ +#define VL53L5_INT32_MAX ((int32_t) 2147483647) +/** int32_t minimum value */ +#define VL53L5_INT32_MIN ((int32_t) (-VL53L5_INT32_MAX - 1)) +/** uint16_t maximum value */ +#define VL53L5_UINT16_MAX ((uint16_t) 65535U) +/** uint16_t minimum value */ +#define VL53L5_UINT16_MIN ((uint16_t) 0U) +/** int16_t maximum value */ +#define VL53L5_INT16_MAX ((int16_t) 32767) +/** int16_t minimum value */ +#define VL53L5_INT16_MIN ((int16_t) (-VL53L5_INT16_MAX - 1)) +/** uint8_t maximum value */ +#define VL53L5_UINT8_MAX ((uint8_t) 255U) +/** uint8_t minimum value */ +#define VL53L5_UINT8_MIN ((uint8_t) 0U) +/** int8_t maximum value */ +#define VL53L5_INT8_MAX ((int8_t) 127) +/** int8_t minimum value */ +#define VL53L5_INT8_MIN ((int8_t) (-VL53L5_INT8_MAX - 1)) + + +/** }@ */ + +#ifdef __cplusplus +} +#endif + +#endif /* VL53L5_PLATFORM_ALGO_LIMITS_H */ diff --git a/drivers/sensors/vl53l8/platform/inc/vl53l5_platform_algo_macros.h b/drivers/sensors/vl53l8/platform/inc/vl53l5_platform_algo_macros.h new file mode 100644 index 000000000000..8b72f2cdf763 --- /dev/null +++ b/drivers/sensors/vl53l8/platform/inc/vl53l5_platform_algo_macros.h @@ -0,0 +1,160 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 VL53L5_PLATFORM_ALGO_MACROS_H +#define VL53L5_PLATFORM_ALGO_MACROS_H + + +#include +#include +#include "vl53l5_platform_maths.h" + + +#ifdef __cplusplus +extern "C" +{ +#endif + + +/** @defgroup VL53L5_PLATFORM_ALGOS_MACROS module + * Platform algo macro definitions for math functions + * @{ + */ + + +/** + * @def DIV_64D64_64 + * @brief customer supplied division operation - 64-bit unsigned + * + * @param dividend unsigned 64-bit numerator + * @param divisor unsigned 64-bit denominator + * + * @return res : 64-bit result + */ + +#define DIV_64D64_64(dividend, divisor) div64_u64(dividend, divisor) + +/** + * @def DIVS_64D64_64 + * @brief customer supplied division operation - 64-bit signed + * + * @param dividend signed 64-bit numerator + * @param divisor signed 64-bit denominator + * + * @return res : 64-bit result + */ +#define DIVS_64D64_64(dividend, divisor) div64_s64(dividend, divisor) + +/** + * @brief Calculates the 64-bit result for the multiplication of two 32-bit + * unsigned numbers + * + * @param[in] a0 : input unsigned 32-bit integer + * @param[in] a1 : input unsigned 32-bit integer + * + * @return res : unsigned 64-bit result + */ +#define MUL_32X32_64(a0, a1) mul_u64_u64_shr((uint64_t)a0, (uint64_t)a1, 0) + +/** + * @brief Calculates the 64-bit result for the multiplication of two 32-bit + * signed numbers + * + * @param[in] a0 : input signed 32-bit integer + * @param[in] a1 : input signed 32-bit integer + * + * @return res : signed 64-bit result + */ + +#define MULS_32X32_64(a0, a1) (int64_t)((int64_t)a0 * (int64_t)a1) + + +/** + * @brief Calculates the square root of the input integer + * + * Reference : http://en.wikipedia.org/wiki/Methods_of_computing_square_roots + * + * @param[in] num : input unsigned 32-bit integer + * + * @return res : square root result - unsigned 32-bit integer + */ +#define INTEGER_SQRT(num) int_sqrt(num) + +/** + * @brief Returns the number of leading zero bits in the input unsigned 32-bit integer, + * starting from bit 31 down to bit 0 + * + * @param[in] num : input unsigned 32-bit integer + * + * @return res : number of leading zero bits - signed 32-bit integer + */ + +#define LEADING_ZERO_COUNT(num) __builtin_clz(num) + + +#ifdef __cplusplus +} +#endif + +/** @} */ + +#endif /* VL53L5_PLATFORM_ALGO_MACROS_H */ diff --git a/drivers/sensors/vl53l8/platform/inc/vl53l5_platform_init.h b/drivers/sensors/vl53l8/platform/inc/vl53l5_platform_init.h new file mode 100644 index 000000000000..1f00af5bd57d --- /dev/null +++ b/drivers/sensors/vl53l8/platform/inc/vl53l5_platform_init.h @@ -0,0 +1,112 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _vl53l5_platform_init_H_ +#define _vl53l5_platform_init_H_ + +#include "vl53l5_platform_user_data.h" + +/** + * @file vl53l5_platform_init.h + * + * @brief Customer implemented platform interface + * + */ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** @addtogroup VL53L5_platform_group + * @{ + */ + + +/** @brief Main device and platform initialisation function + * + * @param[in,out] pdev pointer to instance of device handle struct + * + * @return status (error code if < 0) + * + */ + +int32_t vl53l5_platform_init(struct vl53l5_dev_handle_t *pdev); + + +/** @brief Main device and platform termination function + * + * @param[in,out] pdev pointer to instance of device handle struct + * + * @return status (error code if < 0) + * + */ + +int32_t vl53l5_platform_terminate(struct vl53l5_dev_handle_t *pdev); + +/** @} VL53L5_platform_group */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/platform/inc/vl53l5_platform_log.h b/drivers/sensors/vl53l8/platform/inc/vl53l5_platform_log.h new file mode 100644 index 000000000000..3e36f8a30ed8 --- /dev/null +++ b/drivers/sensors/vl53l8/platform/inc/vl53l5_platform_log.h @@ -0,0 +1,251 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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. +* +* +*******************************************************************************/ + +/** + * @file vl53l5_platform_log.h + * + * @brief Platform logging functions + */ + + +#ifndef _VL53L5_PLATFORM_LOG_H_ +#define _VL53L5_PLATFORM_LOG_H_ + +#include "vl53l5_platform_user_config.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef VL53L8_KERNEL_LOG +#define VL53L8_MAX_STRING_LENGTH 0x8000 + +/** + * @brief Set the level, output and specific functions for module logging. + * + * + * @param filename - full path of output log file, NULL for print to stdout + * + * @param modules - Module or None or All to trace + * VL53L5_TRACE_MODULE_NONE + * VL53L5_TRACE_MODULE_API + * VL53L5_TRACE_MODULE_CORE + * VL53L5_TRACE_MODULE_TUNING + * VL53L5_TRACE_MODULE_CHARACTERISATION + * VL53L5_TRACE_MODULE_PLATFORM + * VL53L5_TRACE_MODULE_ALL + * + * @param level - trace level + * VL53L5_TRACE_LEVEL_NONE + * VL53L5_TRACE_LEVEL_ERRORS + * VL53L5_TRACE_LEVEL_WARNING + * VL53L5_TRACE_LEVEL_INFO + * VL53L5_TRACE_LEVEL_DEBUG + * VL53L5_TRACE_LEVEL_ALL + * VL53L5_TRACE_LEVEL_IGNORE + * + * @param functions - function level to trace; + * VL53L5_TRACE_FUNCTION_NONE + * VL53L5_TRACE_FUNCTION_I2C + * VL53L5_TRACE_FUNCTION_ALL + * + * @return status - always VL53L5_ERROR_NONE + * + */ + +#define VL53L5_TRACE_LEVEL_NONE 0x00000000 +#define VL53L5_TRACE_LEVEL_ERRORS 0x00000001 +#define VL53L5_TRACE_LEVEL_WARNING 0x00000002 +#define VL53L5_TRACE_LEVEL_INFO 0x00000004 +#define VL53L5_TRACE_LEVEL_DEBUG 0x00000008 +#define VL53L5_TRACE_LEVEL_ALL 0x00000010 +#define VL53L5_TRACE_LEVEL_IGNORE 0x00000020 + +#define VL53L5_TRACE_FUNCTION_NONE 0x00000000 +#define VL53L5_TRACE_FUNCTION_I2C 0x00000001 +#define VL53L5_TRACE_FUNCTION_ALL 0x7fffffff + +#define VL53L5_TRACE_MODULE_NONE 0x00000000 +#define VL53L5_TRACE_MODULE_API 0x00000001 +#define VL53L5_TRACE_MODULE_STTOF 0x00000002 +#define VL53L5_TRACE_MODULE_STTOF_HAL 0x00000004 +#define VL53L5_TRACE_MODULE_STTOF_CONTROLS 0x00000008 +#define VL53L5_TRACE_MODULE_HAL_VL53L5 0x00000010 +#define VL53L5_TRACE_MODULE_REGISTER_FUNCS 0x00000020 +#define VL53L5_TRACE_MODULE_DCI 0x00000040 +#define VL53L5_TRACE_MODULE_DCI_DECODE 0x00000080 +#define VL53L5_TRACE_MODULE_DCI_ENCODE 0x00000100 +#define VL53L5_TRACE_MODULE_DCI_INIT_DATA 0x00000200 +#define VL53L5_TRACE_MODULE_LOAD_FIRMWARE 0x00000400 +#define VL53L5_TRACE_MODULE_LOAD_CONFIG 0x00000800 +#define VL53L5_TRACE_MODULE_POWER_API 0x00001000 + +#define VL53L5_TRACE_MODULE_CUSTOMER_API 0x40000000 +#define VL53L5_TRACE_MODULE_PLATFORM 0x40000010 +#define VL53L5_TRACE_MODULE_ALL 0x7fffffff + +extern uint32_t _trace_level; + +/* + * NOTE: dynamically exported if we enable logging. + * this way, Python interfaces can access this function, but we don't + * need to include it in the .def files. + */ +VL53L5_API int8_t vl53l8_trace_config( + char *filename, + uint32_t modules, + uint32_t level, + uint32_t functions); + +/* + * NOTE: dynamically exported if we enable logging. + * this way, Python interfaces can access this function, but we don't + * need to include it in the .def files. + */ +VL53L5_API void vl53l8_trace_close(void); + +/** + * @brief Get global _trace_functions parameter + * + * @return _trace_functions + */ + +uint32_t vl53l8_get_trace_functions(void); + +/** + * @brief Set global _trace_functions parameter + * + * @param[in] function : new function code + */ + +void vl53l8_set_trace_functions(uint32_t function); + + +/* +* @brief Returns the current system tick count in [ms] +* +* @return time_ms : current time in [ms] +* +*/ +uint32_t vl53l8_clock(void); + +#define LOG_GET_TIME() \ + ((int)vl53l8_clock()) + +#define vl53l8_k_log_error(fmt, args...) \ + pr_err("%s: " fmt, __func__, ##args) + +#define vl53l8_k_log_debug(fmt, args...) \ + pr_debug("%s: " fmt, __func__, ##args) + +#define vl53l8_k_log_info(fmt, args...) \ + pr_info("%s: " fmt, __func__, ##args) + +#define vl53l8_k_log_warning(fmt, args...) \ + pr_warn("%s: " fmt, __func__, ##args) + +#define _LOG_TRACE_PRINT(module, level, function, fmt, args...) \ + vl53l8_k_log_debug("" fmt "", ##args) + +#define _LOG_FUNCTION_START(module, fmt, args...) \ + vl53l8_k_log_debug("START " fmt "\n", ##args) + +#define _LOG_FUNCTION_END(module, status, args...)\ + vl53l8_k_log_debug("END %d\n", (int)status, ##args) + +#define _LOG_GET_TRACE_FUNCTIONS()\ + vl53l8_get_trace_functions() + +#define _LOG_SET_TRACE_FUNCTIONS(functions)\ + vl53l8_set_trace_functions(functions) + +#define _FLUSH_TRACE_TO_OUTPUT() + +#define _LOG_STRING_BUFFER(x) char x[VL53L8_MAX_STRING_LENGTH] + +#else /* VL53L8_KERNEL_LOG - no logging */ + +#define _LOG_TRACE_PRINT(module, level, function, ...) ((void)0) +#define _LOG_FUNCTION_START(module, fmt, ...) ((void)0) +#define _LOG_FUNCTION_END(module, status, ...) ((void)0) +#define _FLUSH_TRACE_TO_OUTPUT() ((void)0) + +#define vl53l8_k_log_error(fmt, args...) \ + pr_err("LAF %s: " fmt, __func__, ##args) + +#define vl53l8_k_log_debug(fmt, args...) \ + pr_debug("LAF %s: " fmt, __func__, ##args) + +#define vl53l8_k_log_info(fmt, args...) \ + pr_info("LAF %s: " fmt, __func__, ##args) + +#define vl53l8_k_log_warning(fmt, args...) \ + pr_warn("LAF %s: " fmt, __func__, ##args) + + +#endif +#ifdef __cplusplus +} +#endif + +#endif /* _VL53L5_PLATFORM_LOG_H_ */ diff --git a/drivers/sensors/vl53l8/platform/inc/vl53l5_platform_maths.h b/drivers/sensors/vl53l8/platform/inc/vl53l5_platform_maths.h new file mode 100644 index 000000000000..64f8f10fb611 --- /dev/null +++ b/drivers/sensors/vl53l8/platform/inc/vl53l5_platform_maths.h @@ -0,0 +1,78 @@ +/* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_types.h" + +#ifndef _VL53L5_PLATFORM_MATHS_H_ +#define _VL53L5_PLATFORM_MATHS_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif +uint32_t integer_sqrt(uint32_t num); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/platform/inc/vl53l5_platform_user_config.h b/drivers/sensors/vl53l8/platform/inc/vl53l5_platform_user_config.h new file mode 100644 index 000000000000..b93a31799f41 --- /dev/null +++ b/drivers/sensors/vl53l8/platform/inc/vl53l5_platform_user_config.h @@ -0,0 +1,105 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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. +* +* +*******************************************************************************/ + +/** + * @file vl53l5_platform_user_config.h + * + * @brief VL53L5 compile time user modifiable configuration + */ + + +#ifndef _VL53L5_PLATFORM_USER_CONFIG_H_ +#define _VL53L5_PLATFORM_USER_CONFIG_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * Used to define export tags if required + * #define VL53L5_API __declspec(dllexport) + */ +#define VL53L5_API + +/** Default device comms wait period used in comms polling mechanisms (ms) */ +#define VL53L5_DEFAULT_COMMS_POLLING_DELAY_MS 10 +/** Default device stop command timout for retention mode (ms) */ +#define VL53L5_STOP_COMMAND_TIMEOUT 1000 +/** Default device wait delay during MCU boot loop (ms) */ +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE +#define VL53L5_MCU_BOOT_WAIT_DELAY 50 +#else +#define VL53L5_MCU_BOOT_WAIT_DELAY 6 +#endif +/** Default device wait delay during transition to high power idle (ms) */ +#define VL53L5_HP_IDLE_WAIT_DELAY 0 +/** Default device wait delay during transition to low power idle (ms) */ +#define VL53L5_LP_IDLE_WAIT_DELAY 0 +/** Default device wait during SPI read to allow device interrupt handlin (us) */ +#define VL53L5_RANGE_WAIT 600 + +#ifdef __cplusplus +} +#endif + +#endif /* _VL53L5_PLATFORM_USER_CONFIG_H_ */ diff --git a/drivers/sensors/vl53l8/platform/inc/vl53l5_platform_user_data.h b/drivers/sensors/vl53l8/platform/inc/vl53l5_platform_user_data.h new file mode 100644 index 000000000000..8a2ced727cd9 --- /dev/null +++ b/drivers/sensors/vl53l8/platform/inc/vl53l5_platform_user_data.h @@ -0,0 +1,127 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L5_PLATFORM_USER_DATA_H_ +#define _VL53L5_PLATFORM_USER_DATA_H_ + +#include "vl53l5_device.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** @addtogroup VL53L5_platform_group + * @{ + */ + + +/** @brief VL53L5 range data struct + */ +struct vl53l5_range_data_t { +#ifdef VL53L5_RESULTS_DATA_ENABLED + /** MANDATORY: Range data information required throughout + * VL53L5 driver. Platform independent. + */ + struct vl53l5_range_results_t core; +#endif +#ifdef VL53L5_PATCH_DATA_ENABLED + /** MANDATORY: Range data information required throughout + * VL53L5 driver. Platform independent. + */ + struct vl53l5_tcpm_patch_0_results_dev_t tcpm_0_patch; +#endif +}; + +/** + * MANDATORY if required calibration decode + * Platform independent. + */ +#ifdef VL53L5_CALIBRATION_DECODE_ON +struct vl53l5_calibration_data_t { + struct vl53l5_calibration_dev_t core; +}; +#endif + +/** @brief VL53L5 device handle struct + */ +struct vl53l5_dev_handle_t { + /** MANDATORY: Internal dev handle information required throughout + * VL53L5 driver. Platform independent. + */ + struct vl53l5_dev_handle_internal_t host_dev; +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + int32_t status_using; + int32_t last_dev_error; + + int8_t status_probe; // 1: probed, others: error code + int8_t status_cal; // 0: no error, others: error code +#endif +}; +/** @} vl53l5_platform_group */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/sensors/vl53l8/platform/inc/vl53l5_results_build_config.h b/drivers/sensors/vl53l8/platform/inc/vl53l5_results_build_config.h new file mode 100644 index 000000000000..d951e2283360 --- /dev/null +++ b/drivers/sensors/vl53l8/platform/inc/vl53l5_results_build_config.h @@ -0,0 +1,109 @@ +/* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of the VL53L5 Bare Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, the VL53L5 Bare Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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. +* +* +******************************************************************************** +* +*/ + +/* + * @file : ../../config_fw/platform/inc/vl53l5_results_build_config.h + * @date : 09:39:21 28/10/2022 + * @note : + * @brief : User build configuration options for the results struct and decoder. + */ + +#ifndef _VL53L5_RESULTS_BUILD_CONFIG_H_ +#define _VL53L5_RESULTS_BUILD_CONFIG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The configuration options for the result struct are specified here. + * Each array member of the results structs can be individually configured. + * Also, the values of VL53L5_MAX_TARGETS and VL53L5_MAX_ZONES can be set as per + * the maximum required configuration. + * + * The configuration specified here is a custom setup. + */ + +#define VL53L5_CONFIG_DUMMY_BYTES_SZ 32 +#define VL53L5_CONFIG_SIZE_BYTES 1824 + +#define VL53L5_MAX_TARGETS 68 +#define VL53L5_MAX_ZONES 68 +#define VL53L5_SILICON_TEMP_DATA_ON +#define VL53L5_META_DATA_ON +#define VL53L5_COMMON_DATA_ON +#define VL53L5_AMB_RATE_KCPS_PER_SPAD_ON +#define VL53L5_AMB_DMAX_MM_ON +#define VL53L5_PEAK_RATE_KCPS_PER_SPAD_ON +#define VL53L5_GD_OP_STATUS_ON +#define VL53L5_DEPTH16_ON + +/** Define to enable calibration data decode */ +#define VL53L5_CALIBRATION_DECODE_ON + +#ifdef __cplusplus +} +#endif + +#endif /* _VL53L5_RESULTS_BUILD_CONFIG_H_ */ diff --git a/drivers/sensors/vl53l8/platform/inc/vl53l5_types.h b/drivers/sensors/vl53l8/platform/inc/vl53l5_types.h new file mode 100644 index 000000000000..ae421e6202d7 --- /dev/null +++ b/drivers/sensors/vl53l8/platform/inc/vl53l5_types.h @@ -0,0 +1,135 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 _VL53L5_TYPES_H_ +#define _VL53L5_TYPES_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifdef __KERNEL__ +#include +#include +#endif + +#include + +#ifndef __KERNEL__ +#include +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) +#include +#endif +#endif + +#ifndef NULL +#error "Error NULL definition should be done. Please add required include " +#endif + + +#define STM_VL53L5_SUPPORT_SEC_CODE +#if defined(__BORLANDC__) +#include +#endif + +#if !defined(_MSC_VER) && !defined(__BORLANDC__) +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) +#include +#endif +#else +#pragma once + +#define false 0 +#define true 1 + +#define bool int +#endif + +#if !defined(STDINT_H) && !defined(_STDINT_H) && !defined(__STDINT_H) && \ + !defined(_GCC_STDINT_H) && !defined(__STDINT_DECLS) && \ + !defined(_GCC_WRAP_STDINT_H) && !defined(_STDINT) && \ + !defined(_LINUX_TYPES_H) + +typedef u64 uint64_t; + +typedef i64 int64_t; + +typedef u32 uint32_t; + +typedef i32 int32_t; + +typedef u16 uint16_t; + +typedef i16 int16_t; + +typedef u8 uint8_t; + +typedef i8 int8_t; + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _VL53L5_TYPES_H_ */ diff --git a/drivers/sensors/vl53l8/platform/src/vl53l5_platform.c b/drivers/sensors/vl53l8/platform/src/vl53l5_platform.c new file mode 100644 index 000000000000..495d2898629e --- /dev/null +++ b/drivers/sensors/vl53l8/platform/src/vl53l5_platform.c @@ -0,0 +1,507 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 +#include +#include + +#include "vl53l5_platform.h" +#include "vl53l5_platform_log.h" +#include "vl53l8_k_logging.h" +#include "vl53l8_k_module_data.h" +#include "vl53l8_k_user_data.h" +#include "vl53l8_k_driver_config.h" +#include "vl53l8_k_gpio_utils.h" +#include "vl53l5_platform_user_data.h" + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) +#include +#endif + +#define GET_CHUNKS_REQD(size, chunk_size) \ + ((size / chunk_size) + ((size % chunk_size) != 0)) + +#define CURRENT_CHUNK_BYTES(chunk, chunks_reqd, size, chunk_size) \ + (((chunk + 1) * chunk_size) > size ? \ + (size - chunk * chunk_size) : \ + chunk_size) + +#define SPI_READWRITE_BIT 0x8000 + +#define SPI_WRITE_MASK(x) (x | SPI_READWRITE_BIT) +#define SPI_READ_MASK(x) (x & ~SPI_READWRITE_BIT) + +static uint32_t _calculate_twos_complement_uint32(uint32_t value) +{ + uint32_t twos_complement = 0; + + twos_complement = ~value + 0x01; + + return twos_complement; +} + +static int platform_spi_write(struct vl53l8_k_module_t *p_module, int index, + uint8_t *data, uint16_t len, uint32_t speed_hz) +{ + int status = VL53L5_ERROR_NONE; + uint8_t index_bytes[2] = {0}; + struct spi_message m; + struct spi_transfer t[2]; + + uint8_t *spi_buffer = NULL; + + spi_message_init(&m); + memset(&t, 0, sizeof(t)); + + if (len > 64) { + spi_buffer = kzalloc(len, GFP_KERNEL | GFP_DMA); + if (spi_buffer == NULL) { + status = -ENOMEM; + goto out; + } + memcpy(spi_buffer, data, len); + } + + index_bytes[0] = ((SPI_WRITE_MASK(index) & 0xff00) >> 8); + index_bytes[1] = (SPI_WRITE_MASK(index) & 0xff); + + t[0].tx_buf = index_bytes; + t[0].len = 2; + t[0].speed_hz = speed_hz; + + if (len > 64) + t[1].tx_buf = spi_buffer; + else + t[1].tx_buf = data; + + t[1].len = (unsigned int)len; + t[1].speed_hz = speed_hz; + + spi_message_add_tail(&t[0], &m); + spi_message_add_tail(&t[1], &m); + + status = spi_sync(p_module->spi_info.device, &m); + if (status != VL53L5_ERROR_NONE) { + vl53l8_k_log_error("spi_sync failed. %d", status); + goto out; + } + +out: + kfree(spi_buffer); + if (spi_buffer != NULL) + spi_buffer = NULL; + + return status; +} + +static int platform_spi_read(struct vl53l8_k_module_t *module, int index, + uint8_t *data, uint16_t len, uint32_t speed_hz) +{ + int status = VL53L5_ERROR_NONE; + uint8_t index_bytes[2] = {0}; + struct spi_message m; + struct spi_transfer t[2]; + + spi_message_init(&m); + memset(&t, 0, sizeof(t)); + + index_bytes[0] = ((SPI_READ_MASK(index) >> 8) & 0xff); + index_bytes[1] = (SPI_READ_MASK(index) & 0xff); + + t[0].tx_buf = index_bytes; + t[0].len = 2; + t[0].speed_hz = speed_hz; + + t[1].rx_buf = data; + t[1].len = (unsigned int)len; + t[1].speed_hz = speed_hz; + + spi_message_add_tail(&t[0], &m); + spi_message_add_tail(&t[1], &m); + + status = spi_sync(module->spi_info.device, &m); + if (status != VL53L5_ERROR_NONE) { + vl53l8_k_log_error("spi_sync failed. %d", status); + goto out; + } + +out: + return status; +} +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE +/* Comms close handled by module init and setup. Return 0 */ +int32_t vl53l5_comms_initialise(struct vl53l5_dev_handle_t *pdev) +{ + int32_t status = VL53L5_ERROR_NONE; + + (void)pdev; + + return status; +} + +/* Comms close handled by module exit and cleanup. Return 0 */ +int32_t vl53l5_comms_close(struct vl53l5_dev_handle_t *pdev) +{ + int32_t status = VL53L5_ERROR_NONE; + + (void)pdev; + + return status; +} +#endif +/** + * Reads the requested number of bytes from the device in multi byte format + */ +int32_t vl53l5_read_multi(struct vl53l5_dev_handle_t *pdev, uint16_t index, + uint8_t *pdata, uint32_t count) +{ + int32_t status = 0; + struct vl53l8_k_module_t *p_module; + uint16_t chunk_size = 0; + uint16_t chunks_reqd = 0; + uint16_t cur_bytes = 0; + uint16_t offset = 0; + uint16_t i = 0; + uint32_t speed_hz = 0; + + p_module = (struct vl53l8_k_module_t *) + container_of(pdev, struct vl53l8_k_module_t, stdev); + + if (p_module->stdev.host_dev.firmware_load) + speed_hz = p_module->spi_info.device->max_speed_hz; + else + speed_hz = p_module->transfer_speed_hz; + + chunk_size = VL53L8_MAX_SPI_XFER_SZ; + + chunks_reqd = GET_CHUNKS_REQD(count, chunk_size); + + for (i = 0; i < chunks_reqd; i++) { + cur_bytes = CURRENT_CHUNK_BYTES(i, chunks_reqd, count, + chunk_size); + offset = i * chunk_size; + status = platform_spi_read(p_module, index + offset, + pdata + offset, cur_bytes, speed_hz); + if (status != VL53L5_ERROR_NONE) + vl53l8_k_log_error("platform_spi_read failure: %d", + status); + } + + return status; +} + +/** + * Writes the supplied byte buffer to the device in multi byte format + */ +int32_t vl53l5_write_multi(struct vl53l5_dev_handle_t *pdev, uint16_t index, + uint8_t *pdata, uint32_t count) +{ + int32_t status = 0; + struct vl53l8_k_module_t *p_module; + uint16_t chunks_reqd = 0; + uint16_t chunk_size = 0; + uint16_t cur_bytes = 0; + uint16_t offset = 0; + uint16_t i = 0; + uint32_t speed_hz = 0; + + p_module = (struct vl53l8_k_module_t *) + container_of(pdev, struct vl53l8_k_module_t, stdev); + + if (p_module->stdev.host_dev.firmware_load) + speed_hz = p_module->spi_info.device->max_speed_hz; + else + speed_hz = p_module->transfer_speed_hz; + + chunk_size = VL53L8_MAX_SPI_XFER_SZ; + + chunks_reqd = GET_CHUNKS_REQD(count, chunk_size); + + for (i = 0; i < chunks_reqd; i++) { + cur_bytes = CURRENT_CHUNK_BYTES(i, chunks_reqd, count, + chunk_size); + offset = i * chunk_size; + status = platform_spi_write(p_module, index + offset, + pdata + offset, cur_bytes, + speed_hz); + if (status != VL53L5_ERROR_NONE) + vl53l8_k_log_error("platform_spi_write failure: %d", + status); + } + + return status; +} + +/** + * Implement a programmable wait in us + */ +int32_t vl53l5_wait_us(struct vl53l5_dev_handle_t *pdev, uint32_t wait_us) +{ + int32_t status = VL53L5_ERROR_NONE; + + /* follow Documentation/timers/timers-howto.txt recommendations */ + if (wait_us < 10) + udelay(wait_us); + else if (wait_us < 20000) + usleep_range(wait_us, wait_us + 1); + else + msleep(wait_us / 1000); + + return status; +} + +/** + * Implement a programmable wait in ms + */ +int32_t vl53l5_wait_ms(struct vl53l5_dev_handle_t *pdev, uint32_t wait_ms) +{ + int32_t status = VL53L5_ERROR_NONE; + + status = vl53l5_wait_us(pdev, wait_ms * 1000); + + return status; +} + +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE +/** + * Sets and clears the low power pin on the VL53L5 + */ +int32_t vl53l5_gpio_low_power_control(struct vl53l5_dev_handle_t *pdev, + uint8_t value) +{ + int32_t status = VL53L5_ERROR_NONE; + struct vl53l8_k_module_t *p_module = NULL; + + LOG_FUNCTION_START(""); + + p_module = (struct vl53l8_k_module_t *) + container_of(pdev, struct vl53l8_k_module_t, stdev); + + /* turn on power */ + vl53l8_k_log_debug("Setting gpio low power (%d) to %d\n", + p_module->gpio.low_power, value); + status = vl53l8_k_set_gpio(&p_module->gpio.low_power, value); + + LOG_FUNCTION_END(status); + return status; +} + +/** + * Enables and disables the power of the platform + */ +int32_t vl53l5_gpio_power_enable(struct vl53l5_dev_handle_t *pdev, + uint8_t value) +{ + int32_t status = VL53L5_ERROR_NONE; + struct vl53l8_k_module_t *p_module = NULL; + + LOG_FUNCTION_START(""); + + p_module = (struct vl53l8_k_module_t *) + container_of(pdev, struct vl53l8_k_module_t, stdev); + + /* turn on power */ + vl53l8_k_log_debug("Setting gpio power enable (%d) to %d\n", + p_module->gpio.power_enable, value); + status = vl53l8_k_set_gpio(&p_module->gpio.power_enable, value); + + LOG_FUNCTION_END(status); + return status; +} + +/** + * Enables and disables the power of the platform. + * In this implementation, value is tokenistic. The actual value is obtained + * from the module struct. + */ +int32_t vl53l5_gpio_comms_select(struct vl53l5_dev_handle_t *pdev, + uint8_t value) +{ + int32_t status = VL53L5_ERROR_NONE; + struct vl53l8_k_module_t *p_module = NULL; + + LOG_FUNCTION_START(""); + + p_module = (struct vl53l8_k_module_t *) + container_of(pdev, struct vl53l8_k_module_t, stdev); + + /* select comms */ + vl53l8_k_log_debug("Setting gpio comms select (%d) to %d\n", + p_module->gpio.comms_select, p_module->comms_type); + status = vl53l8_k_set_gpio(&p_module->gpio.comms_select, + p_module->comms_type); + + LOG_FUNCTION_END(status); + return status; +} +#endif + +/* + * Gets current system tick count in [ms] + */ +int32_t vl53l5_get_tick_count( + struct vl53l5_dev_handle_t *pdev, uint32_t *ptime_ms) +{ + *ptime_ms = jiffies_to_msecs(jiffies); + return 0; +} + +/* + * Evaluates a timeout condition based on two tick counts and a timeout value + * [all in ms]. Accounts for possible timer overflow. + */ +int32_t vl53l5_check_for_timeout( + struct vl53l5_dev_handle_t *pdev, uint32_t start_time_ms, + uint32_t end_time_ms, uint32_t timeout_ms) +{ + int32_t status = VL53L5_ERROR_NONE; + uint32_t time_diff_ms; + + if (start_time_ms <= end_time_ms) { + time_diff_ms = end_time_ms - start_time_ms; + } else { + time_diff_ms = _calculate_twos_complement_uint32(start_time_ms) + + end_time_ms; + } + if (time_diff_ms > timeout_ms) + status = VL53L5_ERROR_TIME_OUT; + + return status; +} +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE +int32_t vl53l5_read_file(struct vl53l5_dev_handle_t *pdev, char *data, + int size, const char *path) +{ + int32_t status = VL53L5_ERROR_NONE; + loff_t offset = 0; + struct file *filp; + + filp = filp_open(path, O_RDONLY, 0664); + if (IS_ERR(filp)) { + status = PTR_ERR(filp); + goto out; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,14,0) + status = kernel_read(filp, data, size, &offset); +#else + status = kernel_read(filp, offset, data, size); +#endif + if (status > VL53L5_ERROR_NONE) { + vl53l8_k_log_debug("Size of data read: %i", status); + status = VL53L5_ERROR_NONE; + } + + filp_close(filp, NULL); +out: + return status; +} + +int32_t vl53l5_write_file(struct vl53l5_dev_handle_t *pdev, char *data, + int size, const char *path, char *device_info, + int info_size) +{ + int32_t status = VL53L5_ERROR_NONE; + loff_t offset = 0; + struct file *filp; + + filp = filp_open(path, O_WRONLY|O_CREAT, 0664); + if (IS_ERR(filp)) { + status = PTR_ERR(filp); + goto out; + } + + if (device_info != NULL) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,14,0) + status = kernel_write(filp, device_info, info_size, &offset); +#else + status = kernel_write(filp, device_info, info_size, offset); +#endif + if (status > VL53L5_ERROR_NONE) { + vl53l8_k_log_debug("Size of data written: %i", status); + status = VL53L5_ERROR_NONE; + } +#if LINUX_VERSION_CODE <= KERNEL_VERSION(4,14,0) + offset += info_size; +#endif + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,14,0) + status = kernel_write(filp, data, size, &offset); +#else + status = kernel_write(filp, data, size, offset); +#endif + if (status > VL53L5_ERROR_NONE) { + vl53l8_k_log_debug("Size of data written: %i", status); + status = VL53L5_ERROR_NONE; + } + + filp_close(filp, NULL); +out: + return status; +} +#endif diff --git a/drivers/sensors/vl53l8/platform/src/vl53l5_platform_init.c b/drivers/sensors/vl53l8/platform/src/vl53l5_platform_init.c new file mode 100644 index 000000000000..7fef3aac54a3 --- /dev/null +++ b/drivers/sensors/vl53l8/platform/src/vl53l5_platform_init.c @@ -0,0 +1,141 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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. +* +* +*******************************************************************************/ + +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE +/** + * @file vl53l5_platform_init.c + * @brief Customer implemented platform interface + * + */ + +#include "vl53l5_platform_init.h" +#include "vl53l5_platform.h" +#include "vl53l8_k_user_data.h" +#include "vl53l8_k_module_data.h" + + +int32_t vl53l5_platform_init(struct vl53l5_dev_handle_t *pdev) +{ + int32_t status = VL53L5_ERROR_NONE; + struct vl53l8_k_module_t *p_module; + + p_module = (struct vl53l8_k_module_t *) + container_of(pdev, struct vl53l8_k_module_t, stdev); + + status = vl53l5_gpio_low_power_control(pdev, GPIO_LOW); + if (status < VL53L5_ERROR_NONE) + goto exit; + + status = vl53l5_gpio_power_enable(pdev, GPIO_LOW); + if (status < VL53L5_ERROR_NONE) + goto exit; + + status = vl53l5_gpio_comms_select(pdev, GPIO_LOW); + if (status < VL53L5_ERROR_NONE) + goto exit; + + status = vl53l5_wait_us(pdev, 1000); + if (status < VL53L5_ERROR_NONE) + goto exit; + + if (p_module->comms_type == VL53L8_K_COMMS_TYPE_SPI) { + status = vl53l5_gpio_comms_select(pdev, GPIO_HIGH); + if (status < VL53L5_ERROR_NONE) + goto exit; + } + + status = vl53l5_gpio_low_power_control(pdev, GPIO_HIGH); + if (status < VL53L5_ERROR_NONE) + goto exit; + + status = vl53l5_gpio_power_enable(pdev, GPIO_HIGH); + if (status < VL53L5_ERROR_NONE) + goto exit; + + status = vl53l5_wait_us(pdev, 200); + if (status < VL53L5_ERROR_NONE) + goto exit; + +exit: + return status; +} + +int32_t vl53l5_platform_terminate(struct vl53l5_dev_handle_t *pdev) +{ + int32_t status = VL53L5_ERROR_NONE; + + status = vl53l5_gpio_comms_select(pdev, GPIO_LOW); + if (status < VL53L5_ERROR_NONE) + goto exit; + + status = vl53l5_gpio_low_power_control(pdev, GPIO_LOW); + if (status < VL53L5_ERROR_NONE) + goto exit; + + status = vl53l5_gpio_power_enable(pdev, GPIO_LOW); + if (status < VL53L5_ERROR_NONE) + goto exit; + +exit: + return status; +} +#endif diff --git a/drivers/sensors/vl53l8/platform/src/vl53l5_platform_log.c b/drivers/sensors/vl53l8/platform/src/vl53l5_platform_log.c new file mode 100644 index 000000000000..1a384552ab47 --- /dev/null +++ b/drivers/sensors/vl53l8/platform/src/vl53l5_platform_log.c @@ -0,0 +1,107 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_platform_log.h" +#include "vl53l5_platform_user_config.h" +#include + + +#ifdef VL53L8_KERNEL_LOG + +uint32_t _trace_level = VL53L5_TRACE_LEVEL_WARNING; +static uint32_t _trace_modules = VL53L5_TRACE_MODULE_ALL; +static uint32_t _trace_functions = VL53L5_TRACE_FUNCTION_ALL; + +int8_t vl53l8_trace_config( + char *filename, + uint32_t modules, + uint32_t level, + uint32_t functions) +{ + int8_t status = 0; + + _trace_modules = modules; + _trace_level = level; + _trace_functions = functions; + + return status; +} + +void vl53l8_trace_close(void) +{ + _trace_modules = 0; + _trace_level = 0; + _trace_functions = 0; +} + +uint32_t vl53l8_get_trace_functions(void) +{ + return _trace_functions; +} + + +void vl53l8_set_trace_functions(uint32_t function) +{ + _trace_functions = function; +} + +#endif diff --git a/drivers/sensors/vl53l8/platform/src/vl53l5_platform_maths.c b/drivers/sensors/vl53l8/platform/src/vl53l5_platform_maths.c new file mode 100644 index 000000000000..b851c5a77045 --- /dev/null +++ b/drivers/sensors/vl53l8/platform/src/vl53l5_platform_maths.c @@ -0,0 +1,88 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l5_platform_maths.h" + +uint32_t integer_sqrt(uint32_t num) +{ + /* + * Implements a generic integer square root + * + * From: http->en.wikipedia.org/wiki/Methods_of_computing_square_roots + */ + uint32_t res = 0; + uint32_t bit = (uint32_t)1 << 30U; + + while (bit > num) + bit >>= 2U; + + while (bit != 0U) { + if (num >= (res + bit)) { + num -= (res + bit); + res = (res >> 1U) + bit; + } else { + res >>= 1U; + } + bit >>= 2U; + } + return res; +} diff --git a/drivers/sensors/vl53l8/src/vl53l8_k_error_converter.c b/drivers/sensors/vl53l8/src/vl53l8_k_error_converter.c new file mode 100644 index 000000000000..c60998493d93 --- /dev/null +++ b/drivers/sensors/vl53l8/src/vl53l8_k_error_converter.c @@ -0,0 +1,201 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l8_k_error_converter.h" +#include "vl53l8_k_error_codes.h" +#include "vl53l5_platform_log.h" + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG +#define MAX_ERR_CNT 255 +int vl53l8_hash_func(struct vl53l8_k_module_t *p_module, int err) +{ + unsigned int hash = 5381; + int cnt = 0; + + vl53l8_k_log_info("debug err %d\n", err); + hash = ((((hash << 4) + hash) + err) % (MAX_TABLE-1)) + 1; + + while (cnt++ < MAX_TABLE-1) { + if (hash >= MAX_TABLE) + return -1; + if (p_module->errdata[hash].last_error_code == 0 + || err == p_module->errdata[hash].last_error_code) + return hash; + if (++hash == MAX_TABLE) + hash = 1; + } + return -1; +} + +void vl53l8_error_counter_by_hash(struct vl53l8_k_module_t *p_module, int err) +{ + int key = vl53l8_hash_func(p_module, err); + + if (key > 0 && key < MAX_TABLE) { + p_module->errdata[key].last_error_code = err; + if (p_module->errdata[key].last_error_cnt < MAX_ERR_CNT) + p_module->errdata[key].last_error_cnt++; + return; + } + vl53l8_k_log_error("key err %d", key); +} + +void vl53l8_last_error_counter(struct vl53l8_k_module_t *p_module, int err) +{ + if (err == VL53L8_DELAYED_LOAD_FIRMWARE) + return; + + if (p_module->ldo_status != 0) + err = VL53L8_LDO_ERROR - p_module->ldo_status; + + if (err >= 0) + return; + + vl53l8_error_counter_by_hash(p_module, err); +} +#endif +#endif + +void vl53l8_k_store_error(struct vl53l8_k_module_t *p_module, + int32_t vl53l8_k_error) +{ + + if (p_module->last_driver_error != vl53l8_k_error) + p_module->last_driver_error = vl53l8_k_error; +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + if (p_module->last_driver_error != VL53L8_PROBE_FAILED) + p_module->last_driver_error = vl53l8_k_error; +#endif +} + +int32_t vl53l8_k_convert_error_to_linux_error(int32_t vl53l8_k_error) +{ + int32_t linux_error = VL53L5_ERROR_NONE; + + switch (vl53l8_k_error) { + case VL53L5_ERROR_NONE: + break; + case VL53L8_K_ERROR_DRIVER_NOT_INITIALISED: + case VL53L8_K_ERROR_DEVICE_NOT_PRESENT: + case VL53L8_K_ERROR_DEVICE_NOT_POWERED: + case VL53L8_K_ERROR_DEVICE_NOT_INITIALISED: + case VL53L8_K_ERROR_DEVICE_NOT_RANGING: + linux_error = -EPERM; + break; + case VL53L8_K_ERROR_I2C_CHECK_FAILED: + case VL53L8_K_ERROR_GPIO_IS_DISABLED: + case VL53L8_K_ERROR_GPIO_REQUEST_FAILED: + case VL53L8_K_ERROR_GPIO_DIRECTION_SET_FAILED: + case VL53L8_K_ERROR_ATTEMPT_TO_SET_DISABLED_GPIO: + linux_error = -EIO; + break; + case VL53L8_K_ERROR_RANGE_DATA_NOT_READY: + linux_error = -EAGAIN; + break; + case VL53L8_K_ERROR_FAILED_TO_ALLOCATE_WORKER_WAIT_LIST: + case VL53L8_K_ERROR_FAILED_TO_ALLOCATE_PROFILE: + case VL53L8_K_ERROR_FAILED_TO_ALLOCATE_FIRMWARE: + case VL53L8_K_ERROR_FAILED_TO_ALLOCATE_VERSION: + case VL53L8_K_ERROR_FAILED_TO_ALLOCATE_MODULE_INFO: + case VL53L8_K_ERROR_FAILED_TO_ALLOCATE_RAW_DATA: + case VL53L8_K_ERROR_FAILED_TO_ALLOCATE_PARAMETER_DATA: + linux_error = -ENOMEM; + break; + case VL53L8_K_ERROR_FAILED_TO_COPY_FIRMWARE: + case VL53L8_K_ERROR_FAILED_TO_COPY_VERSION: + case VL53L8_K_ERROR_FAILED_TO_COPY_MODULE_INFO: + case VL53L8_K_ERROR_FAILED_TO_COPY_RAW_DATA: + case VL53L8_K_ERROR_FAILED_TO_COPY_RANGE_DATA: + case VL53L8_K_ERROR_FAILED_TO_COPY_ERROR_INFO: + case VL53L8_K_ERROR_FAILED_TO_COPY_PARAMETER_DATA: + case VL53L8_K_ERROR_FAILED_TO_COPY_POWER_MODE: + linux_error = -EFAULT; + break; + case VL53L8_K_ERROR_DEVICE_IS_RANGING: + case VL53L8_K_ERROR_DEVICE_IS_BUSY: + linux_error = -EBUSY; + break; + case VL53L8_K_ERROR_DEVICE_STATE_INVALID: + case VL53L8_K_ERROR_SPI_BUSNUM_INVALID: + case VL53L8_K_ERROR_SPI_NEW_DEVICE_FAILED: + case VL53L8_K_ERROR_SPI_SETUP_FAILED: + case VL53L8_K_ERROR_SPI_DRIVER_REGISTER_FAILED: + case VL53L8_K_ERROR_I2C_ADAPTER_INVALID: + case VL53L8_K_ERROR_I2C_NEW_DEVICE_FAILED: + case VL53L8_K_ERROR_I2C_ADD_DRIVER_FAILED: + case VL53L8_K_ERROR_DEVICE_INTERRUPT_NOT_OWNED: + linux_error = -EINVAL; + break; + case VL53L8_K_ERROR_SLEEP_FOR_DATA_INTERRUPTED: + linux_error = -ERESTARTSYS; + break; + case VL53L8_K_ERROR_WORKER_THREAD_TIMEOUT: + case VL53L8_K_ERROR_RANGE_POLLING_TIMEOUT: + case VL53L8_K_ERROR_STATE_POLLING_TIMEOUT: + linux_error = -ETIMEDOUT; + break; + default: + linux_error = -EPERM; + break; + } + + return linux_error; +} diff --git a/drivers/sensors/vl53l8/src/vl53l8_k_glare_filter.c b/drivers/sensors/vl53l8/src/vl53l8_k_glare_filter.c new file mode 100644 index 000000000000..f5946bf98ac7 --- /dev/null +++ b/drivers/sensors/vl53l8/src/vl53l8_k_glare_filter.c @@ -0,0 +1,547 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l8_k_glare_filter.h" +#include "vl53l8_k_error_codes.h" +#include "dci_patch_union_structs.h" +#include "vl53l5_platform_algo_limits.h" +#include "vl53l5_results_build_config.h" +#include "vl53l8_k_logging.h" + +#define VL53L8_TARGET_STATUS__TARGETDUETOGLARE ((uint8_t) 18U) + +static uint32_t _interpolate_threshold(int32_t tgt_x, int32_t x1, int32_t x2, + uint32_t y1, uint32_t y2) +{ + uint32_t value = 0; + int32_t diff0 = 0; + int32_t diff1 = 0; + int32_t denom = 0; + + if (VL53L8_K_Y_SCALE_FACTOR == 0) { + value = VL53L8_K_GF_DIVIDE_BY_ZERO_ERROR; + goto out; + } + + diff0 = tgt_x - x1; + diff1 = ((int32_t)y2 - (int32_t)y1) / VL53L8_K_Y_SCALE_FACTOR; + value = diff0 * diff1; + denom = x2 - x1; + + if (denom == 0) { + value = VL53L8_K_GF_DIVIDE_BY_ZERO_ERROR; + goto out; + } + + value = value / denom; + value *= VL53L8_K_Y_SCALE_FACTOR; + value += (int32_t)y1; + + vl53l8_k_log_debug("%d, %d, %u, %u, %d, %d\n", + x1, x2, y1, y2, tgt_x, value); + +out: + return value; +} + +static int32_t vl53l8_k_glare_detect(int32_t tgt_range, uint32_t tgt_peak_x16, + struct vl53l8_k_glare_filter_tuning_t *p_tuning, + uint8_t *glare_detected) +{ + + int32_t status = VL53L5_ERROR_NONE; + uint32_t peak_threshold_x16 = 0; + int32_t clipped_range = 0; + uint32_t glare_threshold_numerator = 0; + uint32_t i = 0; + + if (tgt_range > p_tuning->max_filter_range) { + *glare_detected = 0; + goto out; + } + + if (tgt_range < p_tuning->lut_range[0]) { + glare_threshold_numerator = + p_tuning->threshold_numerator[0]; + } else if (tgt_range >= p_tuning->lut_range[VL53L8_K_GLARE_LUT_SIZE - 1]) { + glare_threshold_numerator = + p_tuning->threshold_numerator[VL53L8_K_GLARE_LUT_SIZE - 1]; + } else { + + for (i = 1; i < (VL53L8_K_GLARE_LUT_SIZE - 1); i++) { + if (p_tuning->lut_range[i] > tgt_range) + break; + } + i--; + + glare_threshold_numerator = _interpolate_threshold( + tgt_range, + p_tuning->lut_range[i], + p_tuning->lut_range[i + 1], + p_tuning->threshold_numerator[i], + p_tuning->threshold_numerator[i + 1]); + } + + if (tgt_range < p_tuning->range_min_clip) + clipped_range = p_tuning->range_min_clip; + else + clipped_range = tgt_range; + + peak_threshold_x16 = glare_threshold_numerator / + (clipped_range * clipped_range); + vl53l8_k_log_debug( + "tgt_range=%u, tgt_peak_x16=%u, peak_threshold_x16=%u\n", + tgt_range, tgt_peak_x16, peak_threshold_x16); + + if (tgt_peak_x16 < peak_threshold_x16) + *glare_detected = 1; + else + *glare_detected = 0; + + vl53l8_k_log_debug("glare detected: %u", *glare_detected); +out: + if (status < VL53L5_ERROR_NONE) + vl53l8_k_log_error("ERROR: %i", status); + return status; +}; + +#if defined(VL53L5_TARGET_STATUS_ON) && \ + defined(VL53L5_MEDIAN_RANGE_MM_ON) && \ + defined(VL53L5_PEAK_RATE_KCPS_PER_SPAD_ON) && \ + defined(VL53L5_NO_OF_TARGETS_ON) + +static void copy_target_data(struct vl53l5_range_per_tgt_results_t *pper_tgt, + int dest_idx, int src_idx) +{ +#ifdef VL53L5_PEAK_RATE_KCPS_PER_SPAD_ON + pper_tgt->peak_rate_kcps_per_spad[dest_idx] = + pper_tgt->peak_rate_kcps_per_spad[src_idx]; +#endif +#ifdef VL53L5_MEDIAN_PHASE_ON + pper_tgt->median_phase[dest_idx] = + pper_tgt->median_phase[src_idx]; +#endif +#ifdef VL53L5_RATE_SIGMA_KCPS_PER_SPAD_ON + pper_tgt->rate_sigma_kcps_per_spad[dest_idx] = + pper_tgt->rate_sigma_kcps_per_spad[src_idx]; +#endif +#ifdef VL53L5_TARGET_ZSCORE_ON + pper_tgt->target_zscore[dest_idx] = pper_tgt->target_zscore[src_idx]; +#endif +#ifdef VL53L5_RANGE_SIGMA_MM_ON + pper_tgt->range_sigma_mm[dest_idx] = pper_tgt->range_sigma_mm[src_idx]; +#endif +#ifdef VL53L5_MEDIAN_RANGE_MM_ON + pper_tgt->median_range_mm[dest_idx] = pper_tgt->median_range_mm[src_idx]; +#endif +#ifdef VL53L5_START_RANGE_MM_ON + pper_tgt->start_range_mm[dest_idx] = pper_tgt->start_range_mm[src_idx]; +#endif +#ifdef VL53L5_END_RANGE_MM_ON + pper_tgt->end_range_mm[dest_idx] = pper_tgt->end_range_mm[src_idx]; +#endif +#ifdef VL53L5_MIN_RANGE_DELTA_MM_ON + pper_tgt->min_range_delta_mm[dest_idx] = + pper_tgt->min_range_delta_mm[src_idx]; +#endif +#ifdef VL53L5_MAX_RANGE_DELTA_MM_ON + pper_tgt->max_range_delta_mm[dest_idx] = + pper_tgt->max_range_delta_mm[src_idx]; +#endif +#ifdef VL53L5_TARGET_REFLECTANCE_EST_PC_ON + pper_tgt->target_reflectance_est_pc[dest_idx] = + pper_tgt->target_reflectance_est_pc[src_idx]; +#endif +#ifdef VL53L5_TARGET_STATUS_ON + pper_tgt->target_status[dest_idx] = pper_tgt->target_status[src_idx]; +#endif + + return; +} + +static int32_t vl53l8_k_median_glare_filter( + struct vl53l8_k_glare_filter_tuning_t *p_tuning, + struct vl53l5_range_data_t *p_results) +{ + int32_t status = VL53L5_ERROR_NONE; + uint32_t row = 0; + uint32_t col = 0; + uint32_t zone = 0; + uint32_t idx = 0; + uint32_t asz = 0; + uint32_t grid_size = 0; + uint32_t tgt_idx = 0; + int32_t range = 0; + uint32_t peak_rate_x16 = 0; + uint8_t glare_detected = 0; +#if defined(VL53L5_RESULTS_DATA_ENABLED) && !defined(VL53L5_PATCH_DATA_ENABLED) + struct vl53l5_range_results_t *presults = &p_results->core; +#endif +#if !defined(VL53L5_RESULTS_DATA_ENABLED) && defined(VL53L5_PATCH_DATA_ENABLED) + struct vl53l5_tcpm_patch_0_results_dev_t *presults = + &p_results->tcpm_0_patch; +#endif + uint32_t no_of_zones = + (uint32_t)presults->common_data.rng__no_of_zones; + uint32_t no_of_asz_zones = + (uint32_t)presults->common_data.rng__acc__no_of_zones; + uint32_t max_targets_per_zone = + (uint32_t)presults->common_data.rng__max_targets_per_zone; + struct vl53l5_range_per_tgt_results_t *pper_tgt = + &presults->per_tgt_results; + struct vl53l5_range_per_zone_results_t *pper_zone = + &presults->per_zone_results; + + if (no_of_zones == 64) + grid_size = 8; + else + grid_size = 4; + + for (tgt_idx = 0 ; tgt_idx < max_targets_per_zone ; tgt_idx++) { + for (row = 0; row < grid_size; row++) { + for (col = 0; col < grid_size; col++) { + zone = (row * grid_size) + col; + idx = (zone * max_targets_per_zone) + tgt_idx; + if (tgt_idx < pper_zone->rng__no_of_targets[zone]) { + range = pper_tgt->median_range_mm[idx] >> + VL53L8_K_MEDIAN_RANGE_FRACTIONAL_BITS; + peak_rate_x16 = pper_tgt->peak_rate_kcps_per_spad[idx] >> + (VL53L8_K_PEAK_RATE_FRACTIONAL_BITS - 4); + + status = vl53l8_k_glare_detect( + range, peak_rate_x16, p_tuning, + &glare_detected); + if (status < VL53L5_ERROR_NONE) + goto out; + if (glare_detected) + pper_tgt->target_status[idx] = + VL53L8_TARGET_STATUS__TARGETDUETOGLARE; + + } + } + } + for (asz = 0; asz < no_of_asz_zones; asz++) { + zone = (grid_size * grid_size) + asz; + idx = (zone * max_targets_per_zone) + tgt_idx; + if (tgt_idx < pper_zone->rng__no_of_targets[zone]) { + range = pper_tgt->median_range_mm[idx] >> + VL53L8_K_MEDIAN_RANGE_FRACTIONAL_BITS; + peak_rate_x16 = pper_tgt->peak_rate_kcps_per_spad[idx] >> + (VL53L8_K_PEAK_RATE_FRACTIONAL_BITS - 4); + status = vl53l8_k_glare_detect( + range, peak_rate_x16, p_tuning, + &glare_detected); + if (status < VL53L5_ERROR_NONE) + goto out; + if (glare_detected) + pper_tgt->target_status[idx] = + VL53L8_TARGET_STATUS__TARGETDUETOGLARE; + } + } + } +out: + if (status < VL53L5_ERROR_NONE) + vl53l8_k_log_error("ERROR: %i", status); + + return status; +} + +static int32_t vl53l8_k_median_glare_remove( + struct vl53l8_k_glare_filter_tuning_t *p_tuning, + struct vl53l5_range_data_t *p_results) +{ + int32_t status = VL53L5_ERROR_NONE; + uint32_t row = 0; + uint32_t col = 0; + uint32_t zone = 0; + uint32_t idx = 0; + uint32_t asz = 0; + uint32_t grid_size = 0; + uint32_t tgt_idx = 0; + uint8_t tgt_status = 0; +#if defined(VL53L5_RESULTS_DATA_ENABLED) && !defined(VL53L5_PATCH_DATA_ENABLED) + struct vl53l5_range_results_t *presults = &p_results->core; +#endif +#if !defined(VL53L5_RESULTS_DATA_ENABLED) && defined(VL53L5_PATCH_DATA_ENABLED) + struct vl53l5_tcpm_patch_0_results_dev_t *presults = + &p_results->tcpm_0_patch; +#endif + uint32_t no_of_zones = + (uint32_t)presults->common_data.rng__no_of_zones; + uint32_t no_of_asz_zones = + (uint32_t)presults->common_data.rng__acc__no_of_zones; + uint32_t max_targets_per_zone = + (uint32_t)presults->common_data.rng__max_targets_per_zone; + struct vl53l5_range_per_tgt_results_t *pper_tgt = + &presults->per_tgt_results; + struct vl53l5_range_per_zone_results_t *pper_zone = + &presults->per_zone_results; + + if (p_tuning->remove_glare_targets && (max_targets_per_zone > 2)) { + vl53l8_k_log_error("ERROR: only supports a maximum of 2 targets"); + status = VL53L8_K_GF_INVALID_MAX_TARGETS_ERROR; + goto out; + } + + if (no_of_zones == 64) + grid_size = 8; + else + grid_size = 4; + + for (tgt_idx = 0 ; tgt_idx < max_targets_per_zone ; tgt_idx++) { + for (row = 0; row < grid_size; row++) { + for (col = 0; col < grid_size; col++) { + zone = (row * grid_size) + col; + idx = (zone * max_targets_per_zone) + tgt_idx; + if (tgt_idx < pper_zone->rng__no_of_targets[zone]) { + tgt_status = pper_tgt->target_status[idx]; + if (tgt_status == VL53L8_TARGET_STATUS__TARGETDUETOGLARE) { + if (tgt_idx == 0 && pper_zone->rng__no_of_targets[zone] == 2) + copy_target_data(pper_tgt, idx, idx+1); + + pper_zone->rng__no_of_targets[zone]--; + } + } + } + } + + for (asz = 0; asz < no_of_asz_zones; asz++) { + zone = (grid_size * grid_size) + asz; + idx = (zone * max_targets_per_zone) + tgt_idx; + if (tgt_idx < pper_zone->rng__no_of_targets[zone]) { + tgt_status = pper_tgt->target_status[idx]; + if (tgt_status == VL53L8_TARGET_STATUS__TARGETDUETOGLARE) { + if (tgt_idx == 0 && pper_zone->rng__no_of_targets[zone] == 2) + copy_target_data(pper_tgt, idx, idx+1); + + pper_zone->rng__no_of_targets[zone]--; + } + } + } + } +out: + if (status < VL53L5_ERROR_NONE) + vl53l8_k_log_error("ERROR: %i", status); + + return status; +} + +#endif + +#if defined(VL53L5_PATCH_DATA_ENABLED) && \ + defined(VL53L5_DEPTH16_ON) && \ + defined(VL53L5_PEAK_RATE_KCPS_PER_SPAD_ON) +static int32_t vl53l8_k_d16_glare_filter( + struct vl53l8_k_glare_filter_tuning_t *p_tuning, + struct vl53l5_tcpm_patch_0_results_dev_t *p_results) +{ + int32_t status = VL53L5_ERROR_NONE; + union dci_union__depth16_value_u d16_data = {0}; +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + uint32_t grid_size = 0; +#endif + uint32_t zone = 0; + uint32_t tgt = 0; + uint32_t idx = 0; + uint32_t peak_rate_x16 = 0; + uint8_t glare_detected = 0; + uint32_t no_of_zones = + (uint32_t)p_results->common_data.rng__no_of_zones; + uint32_t no_of_asz_zones = + (uint32_t)p_results->common_data.rng__acc__no_of_zones; + uint32_t max_targets_per_zone = + (uint32_t)p_results->common_data.rng__max_targets_per_zone; + uint32_t total_zones = no_of_zones + no_of_asz_zones; + uint32_t *rate_kcps = p_results->per_tgt_results.peak_rate_kcps_per_spad; + uint16_t *depth16 = p_results->d16_per_target_data.depth16; + + if (p_tuning->remove_glare_targets && (max_targets_per_zone > 2)) { + vl53l8_k_log_error("ERROR: only supports a maximum of 2 targets"); + status = VL53L8_K_GF_INVALID_MAX_TARGETS_ERROR; + goto out; + } +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + if (no_of_zones == 64) + grid_size = 8; + else + grid_size = 4; +#endif + for (zone = 0; zone < total_zones; zone++) { + for (tgt = 0; tgt < max_targets_per_zone; tgt++) { + d16_data.bytes = depth16[idx]; + if ((d16_data.confidence == VL53L8_K_D16_CONF_00PC) || + (d16_data.confidence == VL53L8_K_D16_CONF_14PC)) { + + continue; + } else { + peak_rate_x16 = rate_kcps[idx] >> + (VL53L8_K_PEAK_RATE_FRACTIONAL_BITS - 4); + + status = vl53l8_k_glare_detect( + d16_data.range_mm, peak_rate_x16, + p_tuning, &glare_detected); + if (status < VL53L5_ERROR_NONE) + goto out; + if (glare_detected) { + if (!p_tuning->remove_glare_targets) { + + d16_data.confidence = + VL53L8_K_D16_CONF_29PC; + depth16[idx] = d16_data.bytes; + } else { + + d16_data.confidence = + VL53L8_K_D16_CONF_00PC; + depth16[idx] = d16_data.bytes; + if ((tgt == 0) && (tgt < + (max_targets_per_zone - + 1))) { + + depth16[idx] = + depth16[idx + 1]; + rate_kcps[idx] = + rate_kcps[idx + 1]; + + d16_data.confidence = + VL53L8_K_D16_CONF_00PC; + depth16[idx + 1] = + d16_data.bytes; + + tgt--; + idx--; + } + } + } + } + idx++; + } + } + +out: + if (status < VL53L5_ERROR_NONE) + vl53l8_k_log_error("ERROR: %i", status); + + return status; +} +#endif + +int32_t vl53l8_k_glare_filter( + struct vl53l8_k_glare_filter_tuning_t *p_tuning, + struct vl53l5_range_data_t *p_results) +{ + int32_t status = VL53L5_ERROR_NONE; +#if !defined(VL53L5_RESULTS_DATA_ENABLED) && defined(VL53L5_PATCH_DATA_ENABLED) +#if defined(VL53L5_DEPTH16_ON) && \ + defined(VL53L5_PEAK_RATE_KCPS_PER_SPAD_ON) + status = vl53l8_k_d16_glare_filter(p_tuning, &p_results->tcpm_0_patch); + if (status != VL53L5_ERROR_NONE) + goto out; +#endif +#if defined(VL53L5_TARGET_STATUS_ON) && \ + defined(VL53L5_MEDIAN_RANGE_MM_ON) && \ + defined(VL53L5_PEAK_RATE_KCPS_PER_SPAD_ON) + status = vl53l8_k_median_glare_filter(p_tuning, p_results); + if (status != VL53L5_ERROR_NONE) + goto out; + if (p_tuning->remove_glare_targets) { + status = vl53l8_k_median_glare_remove(p_tuning, p_results); + if (status != VL53L5_ERROR_NONE) + goto out; + } +#endif +#endif + +#if defined(VL53L5_RESULTS_DATA_ENABLED) && !defined(VL53L5_PATCH_DATA_ENABLED) +#if defined(VL53L5_TARGET_STATUS_ON) && \ + defined(VL53L5_MEDIAN_RANGE_MM_ON) && \ + defined(VL53L5_PEAK_RATE_KCPS_PER_SPAD_ON) + status = vl53l8_k_median_glare_filter(p_tuning, p_results); + if (status != VL53L5_ERROR_NONE) + goto out; + if (p_tuning->remove_glare_targets) { + status = vl53l8_k_median_glare_remove(p_tuning, p_results); + if (status != VL53L5_ERROR_NONE) + goto out; + } +#endif +#endif +out: + if (status < VL53L5_ERROR_NONE) + vl53l8_k_log_error("ERROR: %i", status); + + return status; +} + +void vl53l8_k_glare_detect_init(struct vl53l8_k_glare_filter_tuning_t *p_tuning) +{ + uint32_t i = 0; + + for (i = 0; i < VL53L8_K_GLARE_LUT_SIZE; i++) { + p_tuning->lut_range[i] = p_tuning->range_x4[i] / 4; + + p_tuning->threshold_numerator[i] = VL53L8_K_RANGE * + (((p_tuning->refl_thresh_x256[i] / 16) * + + VL53L8_K_SIGNAL_X16 * VL53L8_K_RANGE) / + (VL53L8_K_REFLECTANCE_X256 / 16)); + vl53l8_k_log_debug("i=%d range=%d numerator=%u", + i, p_tuning->lut_range[i], + p_tuning->threshold_numerator[i]); + } +} diff --git a/drivers/sensors/vl53l8/src/vl53l8_k_gpio_utils.c b/drivers/sensors/vl53l8/src/vl53l8_k_gpio_utils.c new file mode 100644 index 000000000000..6ca81686a6fd --- /dev/null +++ b/drivers/sensors/vl53l8/src/vl53l8_k_gpio_utils.c @@ -0,0 +1,269 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l8_k_gpio_utils.h" +#include "vl53l8_k_logging.h" +#include "vl53l8_k_error_codes.h" + +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE +static int get_power_enable(struct vl53l8_k_module_t *p_module) +{ + return vl53l8_k_get_gpio(&p_module->gpio.power_enable, + &p_module->gpio.power_enable_owned, 1); +} + +static void put_power_enable(struct vl53l8_k_module_t *p_module) +{ + vl53l8_k_put_gpio(&p_module->gpio.power_enable, + &p_module->gpio.power_enable_owned); +} + +static int get_low_power(struct vl53l8_k_module_t *p_module) +{ + return vl53l8_k_get_gpio(&p_module->gpio.low_power, + &p_module->gpio.low_power_owned, 1); +} + +static void put_low_power(struct vl53l8_k_module_t *p_module) +{ + vl53l8_k_put_gpio(&p_module->gpio.low_power, + &p_module->gpio.low_power_owned); +} + +static int get_comms_select(struct vl53l8_k_module_t *p_module) +{ + return vl53l8_k_get_gpio(&p_module->gpio.comms_select, + &p_module->gpio.comms_select_owned, 1); +} + +static void put_comms_select(struct vl53l8_k_module_t *p_module) +{ + vl53l8_k_put_gpio(&p_module->gpio.comms_select, + &p_module->gpio.comms_select_owned); +} + +static int get_interrupt(struct vl53l8_k_module_t *p_module) +{ + return vl53l8_k_get_gpio(&p_module->gpio.interrupt, + &p_module->gpio.interrupt_owned, 0); +} +#endif +static void put_interrupt(struct vl53l8_k_module_t *p_module) +{ + vl53l8_k_put_gpio(&p_module->gpio.interrupt, + &p_module->gpio.interrupt_owned); +} +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE +int vl53l8_k_get_gpio(int *p_gpio, int *p_is_gpio_owned, int is_output) +{ + int status = 0; + + *p_is_gpio_owned = 0; + if (*p_gpio == -1) { + vl53l8_k_log_warning("Gpio is disabled"); + status = VL53L8_K_ERROR_GPIO_IS_DISABLED; + goto no_gpio; + } + + vl53l8_k_log_debug("Requesting gpio: %d", *p_gpio); + status = gpio_request(*p_gpio, "vl53l8_k_pwren"); + if (status) { + vl53l8_k_log_error("Request gpio failed gpio %d: %d", + *p_gpio, status); + status = VL53L8_K_ERROR_GPIO_REQUEST_FAILED; + goto request_failed; + } + + if (is_output) { + vl53l8_k_log_debug( + "Setting gpio %d direction to output", *p_gpio); + status = gpio_direction_output(*p_gpio, 0); + } else { + vl53l8_k_log_debug( + "Setting gpio %d direction to input", *p_gpio); + status = gpio_direction_input(*p_gpio); + } + + if (status) { + vl53l8_k_log_error("Set output direction failed gpio %d: %d", + *p_gpio, status); + status = VL53L8_K_ERROR_GPIO_DIRECTION_SET_FAILED; + goto direction_failed; + } + *p_is_gpio_owned = 1; + + return status; + +direction_failed: + gpio_free(*p_gpio); + +request_failed: +no_gpio: + return status; +} +#endif +void vl53l8_k_put_gpio(int *p_gpio, int *p_is_gpio_owned) +{ + if (*p_is_gpio_owned) { + vl53l8_k_log_debug("Releasing gpio %d", *p_gpio); + gpio_free(*p_gpio); + *p_is_gpio_owned = 0; + } +} + +int vl53l8_k_set_gpio(int *p_gpio, uint8_t value) +{ + int status = VL53L5_ERROR_NONE; + + if (*p_gpio != -1) { + gpio_set_value(*p_gpio, value); + } else { + vl53l8_k_log_error("Failed. Gpio not available"); + status = VL53L8_K_ERROR_ATTEMPT_TO_SET_DISABLED_GPIO; + } + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + if (*p_gpio >= 0) { + gpio_set_value(*p_gpio, value); + } else { + vl53l8_k_log_error("Failed. Gpio not available"); + status = STATUS_OK; + } +#endif + + return status; +} +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE +int vl53l8_k_assign_gpios(struct vl53l8_k_module_t *p_module) +{ + int status = 0; + + LOG_FUNCTION_START(""); +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE + if (p_module->gpio.power_enable != -1) { + vl53l8_k_log_debug("Get power enable"); + status = get_power_enable(p_module); + if (status) { + vl53l8_k_log_debug("Failed to get power enable"); + goto no_pwren; + } + } + + if (p_module->gpio.low_power != -1) { + vl53l8_k_log_debug("Get low power"); + status = get_low_power(p_module); + if (status) { + vl53l8_k_log_debug("Failed to get low power"); + goto no_lpn; + } + } + + if (p_module->gpio.comms_select != -1) { + vl53l8_k_log_debug("Get comms select"); + status = get_comms_select(p_module); + if (status) { + vl53l8_k_log_debug("Failed to get comms select"); + goto no_csel; + } + } +#endif + if (p_module->gpio.interrupt != -1) { + vl53l8_k_log_debug("Get interrupt"); + status = get_interrupt(p_module); + if (status) { + vl53l8_k_log_debug("Failed to get interrupt"); + goto no_int; + } + } + LOG_FUNCTION_END(status); + return status; + +no_int: + put_interrupt(p_module); +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE +no_csel: + put_comms_select(p_module); +no_lpn: + put_low_power(p_module); +no_pwren: + put_power_enable(p_module); +#endif + LOG_FUNCTION_END(status); + return status; +} +#endif +void vl53l8_k_release_gpios(struct vl53l8_k_module_t *p_module) +{ + LOG_FUNCTION_START(""); +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE + vl53l8_k_log_debug("Release power enable"); + put_power_enable(p_module); + + vl53l8_k_log_debug("Release low power"); + put_low_power(p_module); + + vl53l8_k_log_debug("Release comms select"); + put_comms_select(p_module); +#endif + vl53l8_k_log_debug("Release interrupt"); + put_interrupt(p_module); + + LOG_FUNCTION_END(0); +} diff --git a/drivers/sensors/vl53l8/src/vl53l8_k_interrupt.c b/drivers/sensors/vl53l8/src/vl53l8_k_interrupt.c new file mode 100644 index 000000000000..a89c52d6b5eb --- /dev/null +++ b/drivers/sensors/vl53l8/src/vl53l8_k_interrupt.c @@ -0,0 +1,235 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l8_k_interrupt.h" +#include "vl53l8_k_driver_config.h" +#include "vl53l8_k_logging.h" +#include "vl53l8_k_error_converter.h" +#include "vl53l5_api_ranging.h" +#include "vl53l5_error_codes.h" +#include "vl53l8_k_range_wait_handler.h" +#include "vl53l8_k_glare_filter.h" + +static int _interrupt_get_range_data(struct vl53l8_k_module_t *p_module) +{ + int status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + vl53l8_k_log_debug("disable irq"); + + disable_irq(p_module->irq_val); + p_module->range.is_valid = 0; + + status = vl53l5_get_range_data(&p_module->stdev); + if (status == VL53L5_NO_NEW_RANGE_DATA_ERROR || + status == VL53L5_TOO_HIGH_AMBIENT_WARNING) { + vl53l8_k_log_error("skip %d", status); + status = VL53L5_ERROR_NONE; + goto out; + } else if (status != VL53L5_ERROR_NONE) + goto out; + + status = vl53l5_decode_range_data(&p_module->stdev, + &p_module->range.data); + if (status != VL53L5_ERROR_NONE) + goto out; + + if (p_module->patch_ver.patch_version.ver_major < 6 && + p_module->patch_ver.patch_version.ver_minor >= 0) { + status = vl53l8_k_glare_filter(&p_module->gf_tuning, + &p_module->range.data); + if (status != VL53L5_ERROR_NONE) + goto out; + } + + p_module->range.count++; + p_module->range.is_valid = 1; + p_module->polling_count = 0; + +out: + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE + vl53l8_k_log_error("Failed: %d", status); +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + if (p_module->irq_is_active) + vl53l8_k_log_error("Failed: %d", status); + else + vl53l8_k_log_info("%d", status); +#endif + } + + vl53l8_k_store_error(p_module, status); + + enable_irq(p_module->irq_val); + vl53l8_k_log_debug("enable irq"); + + LOG_FUNCTION_END(status); + return status; +} + +static irqreturn_t vl53l8_interrupt_handler(int irq, void *dev_id) +{ + + struct vl53l8_k_module_t *p_module = (struct vl53l8_k_module_t *)dev_id; + + if (!p_module->irq_wq_is_running && p_module->irq_is_active) + queue_work(p_module->irq_wq, &p_module->data_work); + + return IRQ_HANDLED; +} + +static void vl53l8_irq_workqueue(struct work_struct *work) +{ + struct vl53l8_k_module_t *p_module = + container_of(work, struct vl53l8_k_module_t, data_work); + int status = VL53L5_ERROR_NONE; + + vl53l8_k_log_debug("Lock"); + mutex_lock(&p_module->mutex); + p_module->irq_wq_is_running = true; + + if (p_module->state_preset == VL53L8_STATE_RANGING) { + status = _interrupt_get_range_data(p_module); + if (status == VL53L5_ERROR_NONE) { + vl53l8_k_log_debug("Interrupt handled"); + } else { +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + if (p_module->irq_is_active) + vl53l8_k_log_error("Interrupt not handled %d", status); +#endif + vl53l8_k_log_info("err getdata"); + } + + } + p_module->irq_wq_is_running = false; + vl53l8_k_log_debug("Unlock"); + mutex_unlock(&p_module->mutex); +} + +int vl53l8_k_start_interrupt(struct vl53l8_k_module_t *p_module) +{ + int status = VL53L5_ERROR_NONE; + const char *p_dev_name = p_module->name; + + LOG_FUNCTION_START(""); + + if (p_module->gpio.interrupt < 0) { + status = VL53L8_K_ERROR_DEVICE_INTERRUPT_NOT_OWNED; + goto out; + } + + p_module->irq_val = gpio_to_irq(p_module->gpio.interrupt); + + if (p_module->irq_wq == NULL) { + p_module->irq_wq = alloc_workqueue("vl53l8_irq_workqueue", WQ_MEM_RECLAIM, 1); + if (!p_module->irq_wq) { + status = -ENOMEM; + vl53l8_k_log_error("could not create irq work"); + goto out; + } else { + INIT_WORK(&p_module->data_work, vl53l8_irq_workqueue); + } + } + + status = request_threaded_irq(p_module->irq_val, + NULL, + vl53l8_interrupt_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + p_dev_name, + p_module); + if (status) { + vl53l8_k_log_error("Unable to assign IRQ: %d", + p_module->irq_val); + goto out; + } else { + vl53l8_k_log_debug("IRQ %d now assigned", + p_module->irq_val); + p_module->gpio.interrupt_owned = 1; + p_module->irq_is_running = true; + } + +out: + LOG_FUNCTION_END(status); + return status; +} + +int vl53l8_k_stop_interrupt(struct vl53l8_k_module_t *p_module) +{ + int status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + if (p_module->irq_is_active) + disable_irq(p_module->irq_val); + + vl53l8_k_log_debug("disable irq"); + + free_irq(p_module->irq_val, p_module); + vl53l8_k_log_debug("IRQ %d now free", p_module->irq_val); + p_module->irq_val = 0; + p_module->irq_is_running = false; + + LOG_FUNCTION_END(status); + return status; +} diff --git a/drivers/sensors/vl53l8/src/vl53l8_k_ioctl_controls.c b/drivers/sensors/vl53l8/src/vl53l8_k_ioctl_controls.c new file mode 100644 index 000000000000..9f1c47fc8759 --- /dev/null +++ b/drivers/sensors/vl53l8/src/vl53l8_k_ioctl_controls.c @@ -0,0 +1,3735 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l8_k_ioctl_controls.h" +#include "vl53l8_k_driver_config.h" +#include "vl53l8_k_logging.h" +#include "vl53l8_k_error_codes.h" +#include "vl53l8_k_range_wait_handler.h" +#include "vl53l8_k_version.h" +#include "vl53l8_k_user_data.h" +#include "vl53l5_platform.h" +#include "vl53l5_api_ranging.h" +#include "vl53l5_api_power.h" +#include "vl53l5_api_core.h" +#include "vl53l5_api_range_decode.h" +#include "vl53l5_dci_utils.h" +#include "vl53l5_api_calibration_decode.h" +#include "vl53l5_platform_algo_limits.h" +#include "vl53l5_platform_algo_macros.h" +#include "vl53l5_patch_api_core.h" +#include "vl53l8_k_glare_filter.h" + +#ifdef VL53L5_TCDM_ENABLE +#include "vl53l5_tcdm_dump.h" +#endif + + +#ifdef VL53L8_INTERRUPT +#include "vl53l8_k_interrupt.h" +#endif + + +#if defined(STM_VL53L5_SUPPORT_SEC_CODE) +#include +#include +#define MAX_FAIL_COUNT 60 // 33 * 60 = 2980 msec +#define FAC_CAL 1 +#define USER_CAL 2 + +#endif +#define VL53L8_ASZ_0_BH 0xdfa00081U +#define VL53L8_ASZ_1_BH 0xdfe40081U +#define VL53L8_ASZ_2_BH 0xe0280081U +#define VL53L8_ASZ_3_BH 0xe06c0081U + +#ifdef VL53L8_RAD2PERP_CAL + +#define VL53L8_K_R2P_DEF_LENS_UM \ + ((uint32_t) 293801U) + +#define VL53L8_K_R2P_SPAD_PITCH_UM \ + ((uint32_t) 7895U) +#define VL53L8_K_R2P_GRID_8X8_DATA_SCALE_FACTOR_OFF \ + ((uint32_t)176U) +#define VL53L8_K_R2P_GRID_4X4_DATA_SCALE_FACTOR_OFF \ + ((uint32_t)28U) +#endif + +int p2p_bh[] = VL53L8_K_P2P_CAL_BLOCK_HEADERS; +int shape_bh[] = VL53L8_K_SHAPE_CAL_BLOCK_HEADERS; + +#ifdef VL53L8_FORCE_ERROR_COMMAND +#define VL53L8_FW_ERROR_HANDLER_DEBUG_BH ((unsigned int) 0xd2700040U) + +#define ERR_DBG__RANGING_CTRL__RANGING_TMOUT ((unsigned int) 34U) +#endif + +#ifdef VL53L8_RAD2PERP_CAL +static int _perform_rad2perp_calibration(struct vl53l8_k_module_t *p_module); +#endif + +static int vl53l8_k_assign_asz(struct vl53l8_k_asz_t *p_asz, + struct vl53l8_k_asz_tuning_t *p_tuning, + uint8_t *p_buffer, uint32_t *p_count); + +static void _copy_buffer(uint8_t *pcomms_buff, + uint8_t *pconfig_buff, + uint32_t buff_size, + uint32_t *p_count) +{ + memcpy(&pcomms_buff[*p_count], pconfig_buff, buff_size); + *p_count += buff_size; +} + +static int _check_state(struct vl53l8_k_module_t *p_module, + enum vl53l8_k_state_preset expected_state) +{ + enum vl53l8_k_state_preset current_state; + int is_state = VL53L5_ERROR_NONE; + + current_state = p_module->state_preset; + + vl53l8_k_log_debug("current state: %i expected state: %i", + current_state, expected_state); +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + if (current_state != expected_state) + is_state = VL53L8_K_ERROR_DEVICE_STATE_INVALID; +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + if (current_state < expected_state) { + vl53l8_k_log_error("current state: %i expected state: %i", + current_state, expected_state); + is_state = VL53L8_K_ERROR_DEVICE_STATE_INVALID; + } +#endif + + return is_state; +} + +static int _poll_for_new_data(struct vl53l8_k_module_t *p_module, + int sleep_time_ms, + int timeout_ms) +{ + int status = VL53L5_ERROR_NONE; + int data_ready = 0; + int timeout_occurred = 0; + + LOG_FUNCTION_START(""); + + p_module->polling_count = 0; + status = vl53l5_get_tick_count( + &p_module->stdev, &p_module->polling_start_time_ms); + if (status != VL53L5_ERROR_NONE) + goto out; + + do { + + status = vl53l8_k_check_data_ready(p_module, &data_ready); + if ((data_ready) || (status != VL53L5_ERROR_NONE)) + goto out; + + status = vl53l8_k_check_for_timeout(p_module, + timeout_ms, + &timeout_occurred); + if (status != VL53L5_ERROR_NONE) + goto out; + + if (timeout_occurred) + goto out; + + status = vl53l5_wait_ms( + &p_module->stdev, p_module->polling_sleep_time_ms); + if (status != VL53L5_ERROR_NONE) + goto out; + + } while (1); + +out: + + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } + + if (status == VL53L5_ERROR_TIME_OUT) + status = VL53L8_K_ERROR_RANGE_POLLING_TIMEOUT; + + LOG_FUNCTION_END(status); + return status; +} + +static int _set_device_params(struct vl53l8_k_module_t *p_module) +{ + int status = VL53L5_ERROR_NONE; + const char *fw_path; + struct spi_device *spi; + const struct vl53l8_fw_header_t *header; + unsigned char *fw_data; + const struct firmware *fw_entry; + unsigned int count = 0; +#ifdef VL53L8_FORCE_ERROR_COMMAND + unsigned int force_error_block[] = { + VL53L5_MAP_VERSION_BH, + ((MAP_VERSION_MINOR << 16) | MAP_VERSION_MAJOR), + VL53L5_FW_ERROR_HANDLER_DEBUG_BH, + ERR_DBG__RANGING_CTRL__RANGING_TMOUT}; +#endif + + LOG_FUNCTION_START(""); + + spi = p_module->spi_info.device; + fw_path = p_module->firmware_name; + + vl53l8_k_log_debug("Req FW : %s", fw_path); + status = request_firmware(&fw_entry, fw_path, &spi->dev); + if (status) { + vl53l8_k_log_error("FW %s not available", fw_path); + goto out; + } + + fw_data = (unsigned char *)fw_entry->data; + header = (struct vl53l8_fw_header_t *)fw_data; + + vl53l8_k_log_debug("Bin config ver %i.%i", + header->config_ver_major, header->config_ver_minor); + + switch (p_module->config_preset) { + case VL53L8_CAL__NULL_XTALK_SHAPE: + vl53l8_k_log_info( + "NULL_XTALK_SHAPE offset: 0x%08x size: 0x%08x", + header->buf00_offset, + header->buf00_size); + _copy_buffer(p_module->stdev.host_dev.p_comms_buff, + &fw_data[header->buf00_offset], + header->buf00_size, + &count); + break; + case VL53L8_CAL__GENERIC_XTALK_SHAPE: + vl53l8_k_log_info( + "GENERIC_XTALK_SHAPE offset: 0x%08x size: 0x%08x", + header->buf01_offset, + header->buf01_size); + _copy_buffer(p_module->stdev.host_dev.p_comms_buff, + &fw_data[header->buf01_offset], + header->buf01_size, + &count); + break; + case VL53L8_CAL__XTALK_GRID_SHAPE_MON: + vl53l8_k_log_info( + "XTALK_GRID_SHAPE_MON offset: 0x%08x size: 0x%08x", + header->buf02_offset, + header->buf02_size); + _copy_buffer(p_module->stdev.host_dev.p_comms_buff, + &fw_data[header->buf02_offset], + header->buf02_size, + &count); + break; + case VL53L8_CAL__DYN_XTALK_MONITOR: + vl53l8_k_log_info( + "DYN_XTALK_MONITOR offset: 0x%08x size: 0x%08x", + header->buf03_offset, + header->buf03_size); + _copy_buffer(p_module->stdev.host_dev.p_comms_buff, + &fw_data[header->buf03_offset], + header->buf03_size, + &count); + break; + case VL53L8_CFG__STATIC__SS_0PC: + vl53l8_k_log_info( + "STATIC__SS_0PC offset: 0x%08x size: 0x%08x", + header->buf04_offset, + header->buf04_size); + _copy_buffer(p_module->stdev.host_dev.p_comms_buff, + &fw_data[header->buf04_offset], + header->buf04_size, + &count); + break; + case VL53L8_CFG__STATIC__SS_1PC: + vl53l8_k_log_info( + "STATIC__SS_1PC offset: 0x%08x size: 0x%08x", + header->buf05_offset, + header->buf05_size); + _copy_buffer(p_module->stdev.host_dev.p_comms_buff, + &fw_data[header->buf05_offset], + header->buf05_size, + &count); + break; + case VL53L8_CFG__STATIC__SS_2PC: + vl53l8_k_log_info( + "STATIC__SS_2PC offset: 0x%08x size: 0x%08x", + header->buf06_offset, + header->buf06_size); + _copy_buffer(p_module->stdev.host_dev.p_comms_buff, + &fw_data[header->buf06_offset], + header->buf06_size, + &count); + break; + case VL53L8_CFG__STATIC__SS_4PC: + vl53l8_k_log_info( + "STATIC__SS_4PC offset: 0x%08x size: 0x%08x", + header->buf07_offset, + header->buf07_size); + _copy_buffer(p_module->stdev.host_dev.p_comms_buff, + &fw_data[header->buf07_offset], + header->buf07_size, + &count); + break; + case VL53L8_CFG__XTALK_GEN1_8X8_1000: + vl53l8_k_log_info( + "XTALK_GEN1_8X8_1000 offset: 0x%08x size: 0x%08x", + header->buf08_offset, + header->buf08_size); + _copy_buffer(p_module->stdev.host_dev.p_comms_buff, + &fw_data[header->buf08_offset], + header->buf08_size, + &count); + break; + case VL53L8_CFG__XTALK_GEN2_8X8_300: + vl53l8_k_log_info( + "XTALK_GEN2_8X8_300 offset: 0x%08x size: 0x%08x", + header->buf09_offset, + header->buf09_size); + _copy_buffer(p_module->stdev.host_dev.p_comms_buff, + &fw_data[header->buf09_offset], + header->buf09_size, + &count); + break; + case VL53L8_CFG__B2BWB_8X8_OPTION_0: + vl53l8_k_log_info( + "B2BWB_8X8_OPTION_0 offset: 0x%08x size: 0x%08x", + header->buf10_offset, + header->buf10_size); + _copy_buffer(p_module->stdev.host_dev.p_comms_buff, + &fw_data[header->buf10_offset], + header->buf10_size, + &count); + break; + case VL53L8_CFG__B2BWB_8X8_OPTION_1: + vl53l8_k_log_info( + "B2BWB_8X8_OPTION_1 offset: 0x%08x size: 0x%08x", + header->buf11_offset, + header->buf11_size); + _copy_buffer(p_module->stdev.host_dev.p_comms_buff, + &fw_data[header->buf11_offset], + header->buf11_size, + &count); + break; + case VL53L8_CFG__B2BWB_8X8_OPTION_2: + vl53l8_k_log_info( + "B2BWB_8X8_OPTION_2 offset: 0x%08x size: 0x%08x", + header->buf12_offset, + header->buf12_size); + _copy_buffer(p_module->stdev.host_dev.p_comms_buff, + &fw_data[header->buf12_offset], + header->buf12_size, + &count); + break; + case VL53L8_CFG__B2BWB_8X8_OPTION_3: + vl53l8_k_log_info( + "B2BWB_8X8_OPTION_3 offset: 0x%08x size: 0x%08x", + header->buf13_offset, + header->buf13_size); + _copy_buffer(p_module->stdev.host_dev.p_comms_buff, + &fw_data[header->buf13_offset], + header->buf13_size, + &count); + break; +#ifdef VL53L8_FORCE_ERROR_COMMAND + case VL53L8_CFG__FORCE_ERROR: + vl53l8_k_log_info("VL53L8_CFG__FORCE_ERROR"); + + status = vl53l5_set_device_parameters(&p_module->stdev, + (unsigned char *)force_error_block, + (unsigned int)sizeof(force_error_block)); + break; +#endif + default: + vl53l8_k_log_error("Invalid preset: %i", + p_module->config_preset); + status = VL53L8_K_ERROR_INVALID_CONFIG_PRESET; + break; + }; + + if (status != VL53L5_ERROR_NONE) + goto out_release; + + status = vl53l5_set_device_parameters( + &p_module->stdev, + p_module->stdev.host_dev.p_comms_buff, + count); + +out_release: + vl53l8_k_log_debug("Release FW"); + release_firmware(fw_entry); +out: + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } + + LOG_FUNCTION_END(status); + return status; +} + +static int _start_poll_stop(struct vl53l8_k_module_t *p_module) +{ + int status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + vl53l8_k_log_debug("Calibration vl53l5_start"); + status = vl53l5_start(&p_module->stdev, NULL); + if (status != VL53L5_ERROR_NONE) { + vl53l8_k_log_error("start failed"); + goto out; + } + + vl53l8_k_log_debug("Poll data"); + status = _poll_for_new_data( + p_module, p_module->polling_sleep_time_ms, + VL53L8_K_MAX_CALIBRATION_POLL_TIME_MS); + if (status != VL53L5_ERROR_NONE) + goto out_stop; + + status = vl53l5_stop(&p_module->stdev, NULL); + if (status != VL53L5_ERROR_NONE) { + vl53l8_k_log_error("stop failed"); + goto out; + } + +out: + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } + LOG_FUNCTION_END(status); + return status; + +out_stop: + (void)vl53l5_stop(&p_module->stdev, NULL); + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } + LOG_FUNCTION_END(status); + return status; +} + +static void _encode_device_info(struct calibration_data_t *pcal, char *buffer) +{ + char *p_buff = buffer; + int i = 0; + + vl53l5_encode_uint16_t(pcal->version.driver.ver_major, BYTE_2, p_buff); + p_buff += BYTE_2; + vl53l5_encode_uint16_t(pcal->version.driver.ver_minor, BYTE_2, p_buff); + p_buff += BYTE_2; + vl53l5_encode_uint16_t(pcal->version.driver.ver_build, BYTE_2, p_buff); + p_buff += BYTE_2; + vl53l5_encode_uint16_t( + pcal->version.driver.ver_revision, BYTE_2, p_buff); + p_buff += BYTE_2; + vl53l5_encode_uint16_t( + pcal->version.firmware.ver_major, BYTE_2, p_buff); + p_buff += BYTE_2; + vl53l5_encode_uint16_t( + pcal->version.firmware.ver_minor, BYTE_2, p_buff); + p_buff += BYTE_2; + vl53l5_encode_uint16_t( + pcal->version.firmware.ver_build, BYTE_2, p_buff); + p_buff += BYTE_2; + vl53l5_encode_uint16_t( + pcal->version.firmware.ver_revision, BYTE_2, p_buff); + p_buff += BYTE_2; + + vl53l5_encode_uint16_t( + pcal->patch_version.patch_version.ver_major, BYTE_2, p_buff); + p_buff += BYTE_2; + vl53l5_encode_uint16_t( + pcal->patch_version.patch_version.ver_minor, BYTE_2, p_buff); + p_buff += BYTE_2; + + vl53l5_encode_uint16_t( + pcal->patch_version.tcpm_version.ver_major, BYTE_2, p_buff); + p_buff += BYTE_2; + vl53l5_encode_uint16_t( + pcal->patch_version.tcpm_version.ver_minor, BYTE_2, p_buff); + p_buff += BYTE_2; + + vl53l5_encode_uint16_t( + pcal->patch_version.patch_driver.ver_major, BYTE_2, p_buff); + p_buff += BYTE_2; + vl53l5_encode_uint16_t( + pcal->patch_version.patch_driver.ver_minor, BYTE_2, p_buff); + p_buff += BYTE_2; + vl53l5_encode_uint16_t( + pcal->patch_version.patch_driver.ver_build, BYTE_2, p_buff); + p_buff += BYTE_2; + vl53l5_encode_uint16_t( + pcal->patch_version.patch_driver.ver_revision, BYTE_2, p_buff); + p_buff += BYTE_2; + + while (i < VL53L5_FGC_STRING_LENGTH) { + vl53l5_encode_uint8_t( + pcal->info.fgc[i / BYTE_1], + BYTE_1, + p_buff); + p_buff += BYTE_1; + i += BYTE_1; + } + vl53l5_encode_uint32_t(pcal->info.module_id_lo, BYTE_4, p_buff); + p_buff += BYTE_4; + vl53l5_encode_uint32_t(pcal->info.module_id_hi, BYTE_4, p_buff); + +} + +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE +static int _write_p2p_calibration(struct vl53l8_k_module_t *p_module) +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +static int _write_p2p_calibration(struct vl53l8_k_module_t *p_module, int flow) +#endif //STM_VL53L5_SUPPORT_SEC_CODE + +{ + int status = VL53L5_ERROR_NONE; + int num_bh = VL53L8_K_P2P_BH_SZ; + int *p_buffer[] = {&p2p_bh[0]}; + char buffer[VL53L8_K_DEVICE_INFO_SZ] = {0}; + + LOG_FUNCTION_START(""); + + status = _check_state(p_module, VL53L8_STATE_INITIALISED); + if (status != VL53L5_ERROR_NONE) + goto out_state; + + status = vl53l5_get_version(&p_module->stdev, + &p_module->calibration.version); + + if (status != VL53L5_ERROR_NONE) + goto out; + + status = vl53l5_get_patch_version(&p_module->stdev, + &p_module->calibration.patch_version); + + if (status != VL53L5_ERROR_NONE) + goto out; + + status = vl53l5_get_module_info(&p_module->stdev, + &p_module->calibration.info); + + if (status != VL53L5_ERROR_NONE) + goto out; + + status = vl53l5_get_device_parameters(&p_module->stdev, + p_buffer[0], + num_bh); + + if (status != VL53L5_ERROR_NONE) + goto out; + + _encode_device_info(&p_module->calibration, buffer); + +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + vl53l8_k_log_debug("filename %s", VL53L8_CAL_P2P_FILENAME); + vl53l8_k_log_debug("file size %i", VL53L8_K_P2P_FILE_SIZE + 4); + status = vl53l5_write_file(&p_module->stdev, + p_module->stdev.host_dev.p_comms_buff, + VL53L8_K_P2P_FILE_SIZE + 4, + VL53L8_CAL_P2P_FILENAME, + buffer, + VL53L8_K_DEVICE_INFO_SZ); + + if (status < VL53L5_ERROR_NONE) { + vl53l8_k_log_error("write file %s failed: %d ", + VL53L8_CAL_P2P_FILENAME, status); + goto out; + } +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + mutex_lock(&p_module->cal_mutex); + memset(&p_module->cal_data, 0, sizeof(struct vl53l8_cal_data_t)); + memcpy(p_module->cal_data.pcal_data, buffer, VL53L8_K_DEVICE_INFO_SZ); + memcpy(p_module->cal_data.pcal_data + VL53L8_K_DEVICE_INFO_SZ, + &p_module->stdev.host_dev.p_comms_buff[4], + VL53L8_K_P2P_FILE_SIZE + 4); + + p_module->cal_data.size = VL53L8_K_DEVICE_INFO_SZ + VL53L8_K_P2P_FILE_SIZE + 4; + + if (flow == 1) + p_module->cal_data.cmd = CMD_WRITE_P2P_FILE; + else + p_module->cal_data.cmd = CMD_WRITE_CAL_FILE; + + mutex_unlock(&p_module->cal_mutex); + + vl53l8_k_log_info("cmd %d shape size %d", p_module->cal_data.cmd, p_module->cal_data.size); + status = vl53l8_input_report(p_module, 2, p_module->cal_data.cmd); +#endif + +out: + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } +out_state: + LOG_FUNCTION_END(status); + return status; +} + +int _write_shape_calibration(struct vl53l8_k_module_t *p_module) +{ + int status = VL53L5_ERROR_NONE; + int num_bh = VL53L8_K_SHAPE_CAL_BLOCK_HEADERS_SZ; + int *p_buffer[] = {&shape_bh[0]}; + + LOG_FUNCTION_START(""); + + status = _check_state(p_module, VL53L8_STATE_INITIALISED); + if (status != VL53L5_ERROR_NONE) + goto out_state; + + status = vl53l5_get_device_parameters(&p_module->stdev, + p_buffer[0], + num_bh); + + if (status != VL53L5_ERROR_NONE) + goto out; + +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + vl53l8_k_log_debug("filename %s", VL53L8_CAL_SHAPE_FILENAME); + vl53l8_k_log_debug("file size %i", VL53L8_K_SHAPE_FILE_SIZE + 4); + status = vl53l5_write_file(&p_module->stdev, + p_module->stdev.host_dev.p_comms_buff, + VL53L8_K_SHAPE_FILE_SIZE + 4, + VL53L8_CAL_SHAPE_FILENAME, + NULL, + 0); + + if (status < VL53L5_ERROR_NONE) { + vl53l8_k_log_error( + "write file %s failed: %d ", + VL53L8_CAL_SHAPE_FILENAME, status); + goto out; + } +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + mutex_lock(&p_module->cal_mutex); + + memset(&p_module->cal_data, 0, sizeof(struct vl53l8_cal_data_t)); + + memcpy(p_module->cal_data.pcal_data, + &p_module->stdev.host_dev.p_comms_buff[4], + VL53L8_K_SHAPE_FILE_SIZE + 4); + + p_module->cal_data.size = VL53L8_K_SHAPE_FILE_SIZE + 4; + p_module->cal_data.cmd = CMD_WRITE_SHAPE_FILE; + + mutex_unlock(&p_module->cal_mutex); + + vl53l8_k_log_info("shape size %d", p_module->cal_data.size); + status = vl53l8_input_report(p_module, 2, p_module->cal_data.cmd); +#endif + +out: + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } +out_state: + LOG_FUNCTION_END(status); + return status; +} + +#ifdef VL53L5_TCDM_ENABLE +static void _tcdm_dump(struct vl53l8_k_module_t *p_module) +{ + int status = VL53L5_ERROR_NONE; + char *p_data; + int count = 0; + int buff_size = 100000 * sizeof(char); + + LOG_FUNCTION_START(""); + + p_data = kzalloc(buff_size, GFP_KERNEL); + if (p_data == NULL) { + vl53l8_k_log_error("Allocate failed"); + goto out; + } + + status = vl53l5_tcdm_dump(&p_module->stdev, p_data, &count); + + if (status < VL53L5_ERROR_NONE) + goto out_free; + + status = vl53l5_write_file(&p_module->stdev, + p_data, + count, + VL53L8_TCDM_FILENAME, + NULL, + 0); + + if (status < VL53L5_ERROR_NONE) { + vl53l8_k_log_error( + "write file %s failed: %d ", + VL53L8_TCDM_FILENAME, status); + goto out_free; + } +out_free: + kfree(p_data); +out: + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("failed %i", status); + } + LOG_FUNCTION_END(status); +} +#endif + +static void _decode_device_info(struct calibration_data_t *pcal, + char *p_comms_buff) +{ + int i = 0; + char *p_buff = p_comms_buff; + + pcal->version.driver.ver_major = vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + pcal->version.driver.ver_minor = vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + pcal->version.driver.ver_build = vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + pcal->version.driver.ver_revision = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + + pcal->version.firmware.ver_major = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + pcal->version.firmware.ver_minor = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + pcal->version.firmware.ver_build = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + pcal->version.firmware.ver_revision = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + + pcal->patch_version.patch_version.ver_major = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + pcal->patch_version.patch_version.ver_minor = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + + pcal->patch_version.tcpm_version.ver_major = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + pcal->patch_version.tcpm_version.ver_minor = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + + pcal->patch_version.patch_driver.ver_major = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + pcal->patch_version.patch_driver.ver_minor = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + pcal->patch_version.patch_driver.ver_build = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + pcal->patch_version.patch_driver.ver_revision = + vl53l5_decode_uint16_t(BYTE_2, p_buff); + p_buff += BYTE_2; + + while (i < VL53L5_FGC_STRING_LENGTH) { + pcal->info.fgc[i] = vl53l5_decode_uint8_t(BYTE_1, p_buff); + p_buff += BYTE_1; + i++; + } + + pcal->info.module_id_lo = vl53l5_decode_uint32_t(BYTE_4, p_buff); + p_buff += BYTE_4; + pcal->info.module_id_hi = vl53l5_decode_uint32_t(BYTE_4, p_buff); + + vl53l8_k_log_debug( + "API Ver: %d.%d.%d.%d", + pcal->version.driver.ver_major, + pcal->version.driver.ver_minor, + pcal->version.driver.ver_build, + pcal->version.driver.ver_revision); + vl53l8_k_log_debug( + "FW Ver : %d.%d.%d.%d", + pcal->version.firmware.ver_major, + pcal->version.firmware.ver_minor, + pcal->version.firmware.ver_build, + pcal->version.firmware.ver_revision); + vl53l8_k_log_debug( + "Patch API Ver: %d.%d.%d.%d", + pcal->patch_version.patch_driver.ver_major, + pcal->patch_version.patch_driver.ver_minor, + pcal->patch_version.patch_driver.ver_build, + pcal->patch_version.patch_driver.ver_revision); + vl53l8_k_log_debug( + "Patch Ver: %d.%d", + pcal->patch_version.patch_version.ver_major, + pcal->patch_version.patch_version.ver_minor); + vl53l8_k_log_debug( + "Patch TCPM Ver: %d.%d", + pcal->patch_version.tcpm_version.ver_major, + pcal->patch_version.tcpm_version.ver_minor); + + vl53l8_k_log_debug("fgc %s", (char *)pcal->info.fgc); + + vl53l8_k_log_debug("module hi %u lo %u", pcal->info.module_id_hi, + pcal->info.module_id_lo); +} + +int vl53l8_ioctl_init(struct vl53l8_k_module_t *p_module) +{ + int status = VL53L5_ERROR_NONE; + int _comms_buff_count = sizeof(p_module->comms_buffer); + const char *fw_path; + struct spi_device *spi; + const struct vl53l8_fw_header_t *header; + unsigned char *fw_data; + const struct firmware *fw_entry; + + LOG_FUNCTION_START(""); + +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + vl53l58_k_log_debug("Lock"); + mutex_lock(&p_module->mutex); + status = _check_state(p_module, VL53L8_STATE_PRESENT); + if (status != VL53L5_ERROR_NONE) + goto out_state; +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + p_module->stdev.status_probe = -1; + if (p_module->last_driver_error == VL53L8_PROBE_FAILED) + return VL53L8_PROBE_FAILED; + + memset(&p_module->stdev, 0, sizeof(struct vl53l5_dev_handle_t)); + memset(&p_module->comms_buffer, 0, VL53L5_COMMS_BUFFER_SIZE_BYTES); +#endif + + if (!p_module->firmware_name) { + vl53l8_k_log_error("FW name not in dts"); +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + p_module->stdev.status_probe = -2; +#endif + goto out; + } + + spi = p_module->spi_info.device; + fw_path = p_module->firmware_name; + vl53l8_k_log_debug("Load FW : %s", fw_path); + + vl53l8_k_log_debug("Req FW"); + status = request_firmware(&fw_entry, fw_path, &spi->dev); + if (status) { +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + p_module->stdev.status_probe = -3; +#endif + vl53l8_k_log_error("FW %s not available", fw_path); + goto out; + } + + fw_data = (unsigned char *)fw_entry->data; + header = (struct vl53l8_fw_header_t *)fw_data; + + vl53l8_k_log_info("Bin FW ver %i.%i", + header->fw_ver_major, header->fw_ver_minor); + + vl53l8_k_log_info("Bin config ver %i.%i", + header->config_ver_major, header->config_ver_minor); + + p_module->bin_version.fw_ver_major = header->fw_ver_major; + p_module->bin_version.fw_ver_minor = header->fw_ver_minor; + p_module->bin_version.config_ver_major = header->config_ver_major; + p_module->bin_version.config_ver_minor = header->config_ver_minor; + p_module->stdev.host_dev.p_fw_buff = &fw_data[header->fw_offset]; + p_module->stdev.host_dev.fw_buff_count = header->fw_size; + + p_module->last_driver_error = 0; + + VL53L5_ASSIGN_COMMS_BUFF(&p_module->stdev, p_module->comms_buffer, + _comms_buff_count); + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +#ifndef CONFIG_SEPARATE_IO_CORE_POWER + vl53l8_power_onoff(p_module, false); + usleep_range(2000, 2100); + + vl53l8_power_onoff(p_module, true); + usleep_range(5000, 5100); +#endif +#endif + status = vl53l5_init(&p_module->stdev); + + if (status != VL53L5_ERROR_NONE) + goto out_powerdown; + + vl53l8_k_log_debug("Release FW"); + release_firmware(fw_entry); + + p_module->stdev.host_dev.p_fw_buff = NULL; + p_module->stdev.host_dev.fw_buff_count = 0; + + status = vl53l5_get_patch_version(&p_module->stdev, &p_module->patch_ver); + if (status != VL53L5_ERROR_NONE) + goto out; + +#ifdef VL53L8_INTERRUPT + if (p_module->irq_val == 0) { + status = vl53l8_k_start_interrupt(p_module); + if (status != VL53L5_ERROR_NONE) { + vl53l8_k_log_error("Failed to start interrupt: %d", status); + goto out; + } + disable_irq(p_module->irq_val); + p_module->irq_is_active = false; + vl53l8_k_log_info("disable irq"); + } +#endif + p_module->state_preset = VL53L8_STATE_INITIALISED; +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + vl53l8_ioctl_get_module_info(p_module, NULL); + + p_module->stdev.last_dev_error = VL53L5_ERROR_NONE; + p_module->stdev.status_probe = STATUS_OK; +#endif + p_module->last_driver_error = STATUS_OK; + +out: + if (status != VL53L5_ERROR_NONE) { +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG + vl53l8_last_error_counter(p_module, status); +#endif + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE +out_state: + vl53l8_k_log_debug("Unlock"); + mutex_unlock(&p_module->mutex); +#endif + LOG_FUNCTION_END(status); + return status; +out_powerdown: + vl53l8_k_log_debug("Release FW"); + release_firmware(fw_entry); + + if (status != VL53L5_ERROR_NONE) { +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG + vl53l8_last_error_counter(p_module, status); +#endif + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + vl53l8_k_log_debug("Unlock"); + mutex_unlock(&p_module->mutex); +#endif + (void)vl53l8_ioctl_term(p_module); + vl53l8_power_onoff(p_module, false); + LOG_FUNCTION_END(status); + return status; +} + +int vl53l8_ioctl_term(struct vl53l8_k_module_t *p_module) +{ + int status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + vl53l8_k_log_debug("Lock"); + mutex_lock(&p_module->mutex); +#endif + status = _check_state(p_module, VL53L8_STATE_RANGING); + if (status != VL53L5_ERROR_NONE) { + status = _check_state(p_module, VL53L8_STATE_INITIALISED); + if (status != VL53L5_ERROR_NONE) { +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + status = _check_state(p_module, VL53L8_STATE_LOW_POWER); + if (status != VL53L5_ERROR_NONE) + goto out_state; +#else + goto out_state; +#endif + } + } else { + status = vl53l5_stop(&p_module->stdev, + &p_module->ranging_flags); + if (status != VL53L5_ERROR_NONE) + goto out; + } +#ifdef VL53L8_INTERRUPT + disable_irq(p_module->gpio.interrupt); + status = vl53l8_k_stop_interrupt(p_module); + if (status != VL53L5_ERROR_NONE) { + vl53l8_k_log_error( + "Failed to stop interrupt: %d", status); + goto out; + } +#endif + + status = vl53l5_term(&p_module->stdev); + if (status != VL53L5_ERROR_NONE) + goto out; + + p_module->state_preset = VL53L8_STATE_PRESENT; + +out: + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } +out_state: +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + vl53l8_k_log_debug("Unlock"); + mutex_unlock(&p_module->mutex); +#endif + LOG_FUNCTION_END(status); + return status; +} + +int vl53l8_ioctl_get_version(struct vl53l8_k_module_t *p_module, void __user *p) +{ + int status = VL53L5_ERROR_NONE; + struct vl53l8_k_version_t *p_version = NULL; + + LOG_FUNCTION_START(""); +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + vl53l8_k_log_debug("Lock"); + mutex_lock(&p_module->mutex); +#endif + status = _check_state(p_module, VL53L8_STATE_INITIALISED); + if (status != VL53L5_ERROR_NONE) + goto out_state; + + p_version = kzalloc(sizeof(struct vl53l8_k_version_t), GFP_KERNEL); + if (p_version == NULL) { + vl53l8_k_log_error("Allocate Failed"); + status = VL53L8_K_ERROR_FAILED_TO_ALLOCATE_VERSION; + goto out; + } + + status = vl53l5_get_version(&p_module->stdev, &p_version->driver); + if (status != VL53L5_ERROR_NONE) + goto out_free; + + status = vl53l5_get_patch_version(&p_module->stdev, &p_version->patch); + if (status != VL53L5_ERROR_NONE) + goto out_free; + + p_version->kernel.ver_major = VL53L8_K_VER_MAJOR; + p_version->kernel.ver_minor = VL53L8_K_VER_MINOR; + p_version->kernel.ver_build = VL53L8_K_VER_BUILD; + p_version->kernel.ver_revision = VL53L8_K_VER_REVISION; + + memcpy(&p_version->bin_version, &p_module->bin_version, sizeof(struct vl53l8_k_bin_version)); + + vl53l8_k_log_info( + "Driver Ver: %d.%d.%d.%d", + VL53L8_K_VER_MAJOR, + VL53L8_K_VER_MINOR, + VL53L8_K_VER_BUILD, + VL53L8_K_VER_REVISION); + vl53l8_k_log_info( + "API Ver: %d.%d.%d.%d", + p_version->driver.driver.ver_major, + p_version->driver.driver.ver_minor, + p_version->driver.driver.ver_build, + p_version->driver.driver.ver_revision); + vl53l8_k_log_info( + "FW Ver : %d.%d.%d.%d", + p_version->driver.firmware.ver_major, + p_version->driver.firmware.ver_minor, + p_version->driver.firmware.ver_build, + p_version->driver.firmware.ver_revision); + vl53l8_k_log_info( + "Patch API Ver: %d.%d.%d.%d", + p_version->patch.patch_driver.ver_major, + p_version->patch.patch_driver.ver_minor, + p_version->patch.patch_driver.ver_build, + p_version->patch.patch_driver.ver_revision); + vl53l8_k_log_info( + "Patch Ver: %d.%d", + p_version->patch.patch_version.ver_major, + p_version->patch.patch_version.ver_minor); + vl53l8_k_log_info( + "TCPM Ver: %d.%d", + p_version->patch.tcpm_version.ver_major, + p_version->patch.tcpm_version.ver_minor); + + vl53l8_k_log_info( + "BIN Config Ver: %d.%d", + p_version->bin_version.config_ver_major, + p_version->bin_version.config_ver_minor); + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + if (p != NULL) + status = copy_to_user(p, p_version, sizeof(struct vl53l8_k_version_t)); + else + status = STATUS_OK; +#else + status = copy_to_user(p, p_version, sizeof(struct vl53l8_k_version_t)); +#endif //STM_VL53L5_SUPPORT_SEC_CODE + + if (status != VL53L5_ERROR_NONE) { + status = VL53L8_K_ERROR_FAILED_TO_COPY_VERSION; + goto out; + } +out: + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } +out_free: + kfree(p_version); +out_state: +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + vl53l8_k_log_debug("Unlock"); + mutex_unlock(&p_module->mutex); +#endif + LOG_FUNCTION_END(status); + return status; +} + +int vl53l8_ioctl_get_module_info(struct vl53l8_k_module_t *p_module, + void __user *p) +{ + int status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + //vl53l8_k_log_debug("Lock"); + //mutex_lock(&p_module->mutex); + status = _check_state(p_module, VL53L8_STATE_INITIALISED); + if (status != VL53L5_ERROR_NONE) + goto out_state; + + status = vl53l5_get_module_info(&p_module->stdev, &p_module->m_info); + if (status != VL53L5_ERROR_NONE) + goto out; +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE + vl53l8_k_log_info("FGC: %s", (char *)p_module->m_info.fgc); + vl53l8_k_log_info("ID : %x.%x", + p_module->m_info.module_id_hi, + p_module->m_info.module_id_lo); +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + vl53l8_k_log_info(" %x.%x", + p_module->m_info.module_id_hi, + p_module->m_info.module_id_lo); + if (p != NULL) + status = copy_to_user(p, &p_module->m_info, sizeof(struct vl53l5_module_info_t)); + else + status = STATUS_OK; +#else + status = copy_to_user(p, &p_module->m_info, + sizeof(struct vl53l5_module_info_t)); + +#endif //STM_VL53L5_SUPPORT_SEC_CODE + + + if (status != VL53L5_ERROR_NONE) { + status = VL53L8_K_ERROR_FAILED_TO_COPY_MODULE_INFO; + goto out; + } + +out: + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } + +out_state: + + //vl53l8_k_log_debug("Unlock"); + //mutex_unlock(&p_module->mutex); + LOG_FUNCTION_END(status); + return status; +} + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +void vl53l8_load_calibration(struct vl53l8_k_module_t *p_module) +{ + int ret; + int status = 0; + int cal_type = 0; + + status = vl53l8_input_report(p_module, 6, CMD_CHECK_CAL_FILE_TYPE); + if (status < 0) + vl53l8_k_log_error("could not find file_list"); + + if ((p_module->file_list & 3) == 3) { + vl53l8_k_log_info("Do user cal: %d", p_module->file_list); + cal_type = USER_CAL; + } else { + vl53l8_k_log_info("Do fac cal: %d", p_module->file_list); + cal_type = FAC_CAL; + } + + if (cal_type != USER_CAL) + ret = vl53l8_load_factory_calibration(p_module); + else + ret = vl53l8_load_open_calibration(p_module); + + if (ret < 0) { + vl53l8_k_log_error("Cal data load fail"); +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG + vl53l8_last_error_counter(p_module, VL53L8_CALFILE_LOAD_ERROR); +#endif + } +} + +int vl53l8_load_open_calibration(struct vl53l8_k_module_t *p_module) +{ + int p2p, cha; + + vl53l8_k_log_info("Open CAL Load\n"); + + p_module->stdev.status_cal = 0; + p_module->load_calibration = false; + p_module->read_p2p_cal_data = false; + + vl53l8_ioctl_set_power_mode(p_module, NULL, VL53L5_POWER_STATE_HP_IDLE); + usleep_range(2000, 2100); + + cha = vl53l8_ioctl_read_open_cal_shape_calibration(p_module); + msleep(100); + + p2p = vl53l8_ioctl_read_open_cal_p2p_calibration(p_module); + usleep_range(2000, 2100); + + vl53l8_k_log_error("cha %d, p2p %d\n", cha, p2p); + + if (cha == STATUS_OK) + p_module->load_calibration = true; + if (p2p == STATUS_OK) + p_module->read_p2p_cal_data = true; + + msleep(20); + + if (cha != STATUS_OK) + return cha; + else if (p2p != STATUS_OK) + return p2p; + else + return STATUS_OK; +} + +int vl53l8_load_factory_calibration(struct vl53l8_k_module_t *p_module) +{ + int cha, p2p; + + vl53l8_k_log_info("Load CAL\n"); + + p_module->stdev.status_cal = 0; + p_module->load_calibration = false; + p_module->read_p2p_cal_data = false; + + vl53l8_ioctl_set_power_mode(p_module, NULL, VL53L5_POWER_STATE_HP_IDLE); + + usleep_range(2000, 2100); + + cha = vl53l8_ioctl_read_generic_shape(p_module); + usleep_range(2000, 2100); + + p2p = vl53l8_ioctl_read_p2p_calibration(p_module); + usleep_range(2000, 2100); + + vl53l8_k_log_error("cha %d, p2p %d\n", cha, p2p); + + if (cha != STATUS_OK) + return cha; + + p_module->load_calibration = true; + + if (p2p != STATUS_OK) + return p2p; + + p_module->read_p2p_cal_data = true; + + return STATUS_OK; +} +#endif + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG +void vl53l8_check_ldo_onoff(struct vl53l8_k_module_t *data) +{ + int ldo_status = 0, comp = ALL_VDD_ENABLED; + int reg_enabled = 0; + + if (data->avdd_vreg) { + reg_enabled = regulator_is_enabled(data->avdd_vreg); + vl53l8_k_log_info("avdd reg_enabled=%d\n", reg_enabled); + ldo_status |= (reg_enabled & 0x01) << AVDD; + } else + vl53l8_k_log_error("avdd error\n"); + + if (data->iovdd_vreg) { + reg_enabled = regulator_is_enabled(data->iovdd_vreg); + vl53l8_k_log_info("iovdd reg_enabled=%d\n", reg_enabled); + ldo_status |= (reg_enabled & 0x01) << IOVDD; + } else + vl53l8_k_log_error("vdd_vreg err\n"); + +#ifdef CONFIG_SEPARATE_IO_CORE_POWER + if (data->corevdd_vreg) { + reg_enabled = regulator_is_enabled(data->corevdd_vreg); + vl53l8_k_log_info("corevdd reg_enabled=%d\n", reg_enabled); + ldo_status |= (reg_enabled & 0x01) << COREVDD; + } else + vl53l8_k_log_error("vdd_vreg err\n"); +#endif + data->ldo_status = comp ^ ldo_status; + vl53l8_last_error_counter(data, data->ldo_status); + data->ldo_status = 0; + + return; +} +#endif +int vl53l8_ioctl_start(struct vl53l8_k_module_t *p_module) +#else +int vl53l8_ioctl_start(struct vl53l8_k_module_t *p_module, void __user *p) +#endif +{ + int status = VL53L5_ERROR_NONE; + + struct vl53l8_k_asz_tuning_t asz_tuning; + + LOG_FUNCTION_START(""); + + if (p_module->state_preset == VL53L8_STATE_RANGING) + return status; + if (p_module->stdev.status_probe < 0) + return VL53L5_DEVICE_STATE_INVALID; +#ifdef CONFIG_SENSORS_VL53L8_SUPPORT_RESUME_WORK + cancel_work_sync(&p_module->resume_work); +#endif +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + vl53l8_k_log_debug("Lock"); + mutex_lock(&p_module->mutex); +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +#ifdef VL53L8_INTERRUPT + if (!p_module->irq_is_active) { + enable_irq(p_module->irq_val); + vl53l8_k_log_info("enable_irq"); + } +#endif + memset(&p_module->range.data.tcpm_0_patch.d16_per_target_data, 1, sizeof(struct vl53l5_d16_per_tgt_results_t)); + + vl53l8_ioctl_set_power_mode(p_module, NULL, VL53L5_POWER_STATE_HP_IDLE); + + status = vl53l8_set_device_parameters(p_module, VL53L8_CFG__B2BWB_8X8_OPTION_3); + if (status != STATUS_OK) { + vl53l8_k_log_error("set param err"); +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG + vl53l8_last_error_counter(p_module, status); +#endif + } + status = vl53l8_ioctl_set_integration_time_us(p_module, p_module->integration); + if (status != STATUS_OK) + vl53l8_k_log_error("set integ time err"); + + status = vl53l8_ioctl_set_ranging_rate(p_module, p_module->rate); + if (status != STATUS_OK) + vl53l8_k_log_error("set raging rate err"); + + asz_tuning.asz_0_ll_zone_id = p_module->asz_0_ll_zone_id; + asz_tuning.asz_1_ll_zone_id = p_module->asz_1_ll_zone_id; + asz_tuning.asz_2_ll_zone_id = p_module->asz_2_ll_zone_id; + asz_tuning.asz_3_ll_zone_id = p_module->asz_3_ll_zone_id; + vl53l8_set_asz_tuning(p_module, &asz_tuning); + + vl53l8_load_calibration(p_module); +#endif + status = _check_state(p_module, VL53L8_STATE_INITIALISED); + if (status != VL53L5_ERROR_NONE) + goto out_state; + + vl53l8_k_log_debug("glare : patch version major =%d, minor =%d", p_module->patch_ver.patch_version.ver_major, p_module->patch_ver.patch_version.ver_minor); + + if (p_module->patch_ver.patch_version.ver_major < 6 && + p_module->patch_ver.patch_version.ver_minor >= 0) { + p_module->gf_tuning.range_min_clip = 20; + p_module->gf_tuning.max_filter_range = 500; + p_module->gf_tuning.remove_glare_targets = true; + p_module->gf_tuning.range_x4[0] = 0;// 0 * 4; + p_module->gf_tuning.range_x4[1] = 12;// 3 * 4; + p_module->gf_tuning.range_x4[2] = 16;// 4 * 4; + p_module->gf_tuning.range_x4[3] = 40;// 10 * 4; + p_module->gf_tuning.range_x4[4] = 44;// 11 * 4; + p_module->gf_tuning.range_x4[5] = 400;// 100 * 4; + p_module->gf_tuning.range_x4[6] = 1200;// 300 * 4; + p_module->gf_tuning.range_x4[7] = 1204;// 301 * 4; + p_module->gf_tuning.refl_thresh_x256[0] = 0; + p_module->gf_tuning.refl_thresh_x256[0] = 0; + p_module->gf_tuning.refl_thresh_x256[0] = 16; + p_module->gf_tuning.refl_thresh_x256[0] = 16; + p_module->gf_tuning.refl_thresh_x256[0] = 512;// 2 * 256; + p_module->gf_tuning.refl_thresh_x256[0] = 512;// 2 * 256; + p_module->gf_tuning.refl_thresh_x256[0] = 0; + p_module->gf_tuning.refl_thresh_x256[0] = 0; + + vl53l8_k_log_debug("glare : Glare Init"); + vl53l8_k_glare_detect_init(&p_module->gf_tuning); + } + +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + if (p != NULL) { + status = copy_from_user( + &p_module->ranging_flags, p, + sizeof(struct vl53l5_ranging_mode_flags_t)); + if (status != VL53L5_ERROR_NONE) { + status = VL53L8_K_ERROR_FAILED_TO_COPY_FLAGS; + goto out; + } + } +#endif + + status = vl53l5_start(&p_module->stdev, &p_module->ranging_flags); + if (status != VL53L5_ERROR_NONE) { + vl53l8_k_log_debug("vl53l5_start fail"); + goto out; + } + p_module->range.count = 0; + p_module->range.is_valid = 0; + p_module->polling_start_time_ms = 0; + +#ifdef VL53L8_INTERRUPT + p_module->irq_is_active = true; +#endif + + p_module->state_preset = VL53L8_STATE_RANGING; + +out: + if (status != VL53L5_ERROR_NONE) { +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG + vl53l8_last_error_counter(p_module, status); +#endif + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + vl53l8_ioctl_stop(p_module); + } +out_state: +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + vl53l8_k_log_debug("Unlock"); + mutex_unlock(&p_module->mutex); +#endif + LOG_FUNCTION_END(status); + return status; +} + +#define FIRST_TARGET 0 +#define SECOND_TARGET 1 +#define UPPER_LEFT 0 +#define UPPER_RIGHT 7 +#define BOTTOM_LEFT 56 +#define BOTTOM_RIGHT 63 + +#define ASZ_1 64 +#define ASZ_2 65 +#define ASZ_3 66 +#define ASZ_4 67 + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +#ifdef VL53L5_GET_DATA_ROTATION +void vl53l8_rotate_report_data_u16(u16 *raw_data, int rotation) +{ + u16 data[8][8]; + int i, j; + + if (rotation == 0) + return; + + for (i = 0; i < 8 ; i++) { + for (j = 0; j < 8 ; j++) + data[i][j] = raw_data[8*j + i]; + } + + if (rotation == 90) { //Clockwise 90 degree rotation + for (i = 0; i < 8 ; i++) { + for (j = 0; j < 8 ; j++) + raw_data[8*i + 7-j] = data[i][j]; + } + } else if (rotation == 180) { //Clockwise 180 degree rotation + for (i = 0; i < 8 ; i++) { + for (j = 0; j < 8 ; j++) + raw_data[8*(7-j) + 7-i] = data[i][j]; + } + } else if (rotation == 270) { //Clockwise 270 degree rotation + for (i = 0; i < 8 ; i++) { + for (j = 0; j < 8 ; j++) + raw_data[8*(7-i) + j] = data[i][j]; + } + } +} + +void vl53l8_rotate_report_data_u32(u32 *raw_data, int rotation) +{ + u32 data[8][8]; + int i, j; + + if (rotation == 0) + return; + + for (i = 0; i < 8 ; i++) { + for (j = 0; j < 8 ; j++) + data[i][j] = raw_data[8*j + i]; + } + + if (rotation == 90) { //Clockwise 90 degree rotation + for (i = 0; i < 8 ; i++) { + for (j = 0; j < 8 ; j++) + raw_data[8*i + 7-j] = data[i][j]; + } + } else if (rotation == 180) { //Clockwise 180 degree rotation + for (i = 0; i < 8 ; i++) { + for (j = 0; j < 8 ; j++) + raw_data[8*(7-j) + 7-i] = data[i][j]; + } + } else if (rotation == 270) { //Clockwise 270 degree rotation + for (i = 0; i < 8 ; i++) { + for (j = 0; j < 8 ; j++) + raw_data[8*(7-i) + j] = data[i][j]; + } + } +} +#endif + +void vl53l8_rotate_report_data_int32(int32_t *raw_data, int rotation) +{ + int32_t data[8][8]; + int i, j; + + if (rotation == 0) + return; + + for (i = 0; i < 8 ; i++) { + for (j = 0; j < 8 ; j++) + data[i][j] = raw_data[8*j + i]; + } + + if (rotation == 90) { //Clockwise 90 degree rotation + for (i = 0; i < 8 ; i++) { + for (j = 0; j < 8 ; j++) + raw_data[8*i + 7-j] = data[i][j]; + } + } else if (rotation == 180) { //Clockwise 180 degree rotation + for (i = 0; i < 8 ; i++) { + for (j = 0; j < 8 ; j++) + raw_data[8*(7-j) + 7-i] = data[i][j]; + } + } else if (rotation == 270) { //Clockwise 270 degree rotation + for (i = 0; i < 8 ; i++) { + for (j = 0; j < 8 ; j++) + raw_data[8*(7-i) + j] = data[i][j]; + } + } +} + +void vl53l8_copy_report_data(struct vl53l8_k_module_t *p_module) +{ +#ifdef VL53L5_TEST_ENABLE + int idx = 0; + int i, j = 0; +#endif + + mutex_lock(&p_module->mutex); + /*depth16*/ + memcpy(p_module->af_range_data.depth16, + &p_module->range.data.tcpm_0_patch.d16_per_target_data, + sizeof(p_module->af_range_data.depth16)); + + /*dmax*/ + memcpy(p_module->af_range_data.dmax, + p_module->range.data.tcpm_0_patch.per_zone_results.amb_dmax_mm, + sizeof(p_module->af_range_data.dmax)); + + /*peak signal*/ + memcpy(p_module->af_range_data.peak_signal, + p_module->range.data.tcpm_0_patch.per_tgt_results.peak_rate_kcps_per_spad, + sizeof(p_module->af_range_data.peak_signal)); + mutex_unlock(&p_module->mutex); + + /*glass detection*/ + p_module->af_range_data.glass_detection_flag = p_module->range.data.tcpm_0_patch.gd_op_status.gd__glass_detected; + + /*ASZ*/ + p_module->af_range_data.depth16[UPPER_LEFT] = + p_module->range.data.tcpm_0_patch.d16_per_target_data.depth16[ASZ_1]; + p_module->af_range_data.depth16[UPPER_RIGHT] = + p_module->range.data.tcpm_0_patch.d16_per_target_data.depth16[ASZ_2]; + p_module->af_range_data.depth16[BOTTOM_LEFT] = + p_module->range.data.tcpm_0_patch.d16_per_target_data.depth16[ASZ_3]; + p_module->af_range_data.depth16[BOTTOM_RIGHT] = + p_module->range.data.tcpm_0_patch.d16_per_target_data.depth16[ASZ_4]; + + p_module->af_range_data.dmax[UPPER_LEFT] = + p_module->range.data.tcpm_0_patch.per_zone_results.amb_dmax_mm[ASZ_1]; + p_module->af_range_data.dmax[UPPER_RIGHT] = + p_module->range.data.tcpm_0_patch.per_zone_results.amb_dmax_mm[ASZ_2]; + p_module->af_range_data.dmax[BOTTOM_LEFT] = + p_module->range.data.tcpm_0_patch.per_zone_results.amb_dmax_mm[ASZ_3]; + p_module->af_range_data.dmax[BOTTOM_RIGHT] = + p_module->range.data.tcpm_0_patch.per_zone_results.amb_dmax_mm[ASZ_4]; + + p_module->af_range_data.peak_signal[UPPER_LEFT] = + p_module->range.data.tcpm_0_patch.per_tgt_results.peak_rate_kcps_per_spad[ASZ_1]; + p_module->af_range_data.peak_signal[UPPER_RIGHT] = + p_module->range.data.tcpm_0_patch.per_tgt_results.peak_rate_kcps_per_spad[ASZ_2]; + p_module->af_range_data.peak_signal[BOTTOM_LEFT] = + p_module->range.data.tcpm_0_patch.per_tgt_results.peak_rate_kcps_per_spad[ASZ_3]; + p_module->af_range_data.peak_signal[BOTTOM_RIGHT] = + p_module->range.data.tcpm_0_patch.per_tgt_results.peak_rate_kcps_per_spad[ASZ_4]; + + +#ifdef VL53L5_GET_DATA_ROTATION + vl53l8_rotate_report_data_u16(p_module->af_range_data.depth16, p_module->rotation_mode); + vl53l8_rotate_report_data_u16(p_module->af_range_data.dmax, p_module->rotation_mode); + vl53l8_rotate_report_data_u32(p_module->af_range_data.peak_signal, p_module->rotation_mode); +#endif + +#ifdef VL53L5_TEST_ENABLE + for (i = 0; i < p_module->print_counter; i++) { + for (j = 0; j < p_module->print_counter; j++) { + idx = (i * p_module->print_counter + j); + p_module->data[idx] = (p_module->af_range_data.depth16[idx]) & 0x1FFFU; + } + } + + for (i = 0; i < 64; i += 8) { + vl53l8_k_log_info("ALL dist[0]:%d, %d, %d, %d, %d, %d, %d, %d\n", + p_module->data[i], p_module->data[i+1], p_module->data[i+2], + p_module->data[i+3], p_module->data[i+4], p_module->data[i+5], + p_module->data[i+6], p_module->data[i+7]); + } + + for (i = 0; i < p_module->print_counter; i++) { + for (j = 0; j < p_module->print_counter; j++) { + idx = (i * p_module->print_counter + j); + p_module->data[idx] = (p_module->af_range_data.peak_signal[idx] >> 11); + } + } + + for (i = 0; i < 64; i += 8) { + vl53l8_k_log_info("ALL peak[0]:%d, %d, %d, %d, %d, %d, %d, %d\n", + p_module->data[i], p_module->data[i+1], p_module->data[i+2], + p_module->data[i+3], p_module->data[i+4], p_module->data[i+5], + p_module->data[i+6], p_module->data[i+7]); + } + + + for (i = 0; i < p_module->print_counter; i++) { + for (j = 0; j < p_module->print_counter; j++) { + idx = (i * p_module->print_counter + j); + p_module->data[idx] = (p_module->af_range_data.dmax[idx]); + } + } + + for (i = 0; i < 64; i += 8) { + vl53l8_k_log_info("ALL dmax[0]:%d, %d, %d, %d, %d, %d, %d, %d\n", + p_module->data[i], p_module->data[i+1], p_module->data[i+2], + p_module->data[i+3], p_module->data[i+4], p_module->data[i+5], + p_module->data[i+6], p_module->data[i+7]); + } +#endif +} +#endif + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +int vl53l8_ioctl_stop(struct vl53l8_k_module_t *p_module) +#else +int vl53l8_ioctl_stop(struct vl53l8_k_module_t *p_module, void __user *p) +#endif +{ + int status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + vl53l8_k_log_debug("Lock"); + mutex_lock(&p_module->mutex); +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + if (p_module->state_preset == VL53L8_STATE_LOW_POWER) + return status; + + status = _check_state(p_module, VL53L8_STATE_INITIALISED); +#else + status = _check_state(p_module, VL53L8_STATE_RANGING); +#endif + if (status != VL53L5_ERROR_NONE) + goto out_state; +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + if (p != NULL) { + status = copy_from_user( + &p_module->ranging_flags, p, + sizeof(struct vl53l5_ranging_mode_flags_t)); + if (status != VL53L5_ERROR_NONE) { + status = VL53L8_K_ERROR_FAILED_TO_COPY_FLAGS; + goto out; + } + } + +#ifdef VL53L8_INTERRUPT + if (p_module->irq_is_active) + disable_irq(p_module->irq_val); + + vl53l8_k_log_info("disable irq"); + p_module->irq_is_active = false; +#endif +#endif + + status = vl53l5_stop(&p_module->stdev, &p_module->ranging_flags); +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG + if (status != VL53L5_ERROR_NONE) + vl53l8_last_error_counter(p_module, status); +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +#ifdef VL53L8_INTERRUPT + usleep_range(10000, 10100); + if (p_module->irq_is_active) + disable_irq(p_module->irq_val); + vl53l8_k_log_info("disable irq"); + p_module->irq_is_active = false; +#endif +#endif + if (status != VL53L5_ERROR_NONE) + goto out; + + p_module->range.count = 0; + p_module->range.is_valid = 0; + p_module->polling_start_time_ms = 0; + p_module->state_preset = VL53L8_STATE_INITIALISED; + p_module->rate = p_module->rate_init; + p_module->integration = p_module->integration_init; + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + vl53l8_ioctl_set_power_mode(p_module, NULL, VL53L5_POWER_STATE_ULP_IDLE); +#endif + +out: + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } +out_state: +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + vl53l8_k_log_debug("Unlock"); + mutex_unlock(&p_module->mutex); +#endif + LOG_FUNCTION_END(status); + return status; +} + + + +int vl53l8_ioctl_get_range(struct vl53l8_k_module_t *p_module, void __user *p) +{ + int status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + //vl53l8_k_log_debug("Lock"); + //mutex_lock(&p_module->mutex); + status = _check_state(p_module, VL53L8_STATE_RANGING); + if (status != VL53L5_ERROR_NONE) + goto out_state; + +#ifndef VL53L8_INTERRUPT + if (p_module->range_mode == VL53L8_RANGE_SERVICE_DEFAULT) { + status = vl53l5_get_range_data(&p_module->stdev); + if (status == VL53L5_NO_NEW_RANGE_DATA_ERROR || + status == VL53L5_TOO_HIGH_AMBIENT_WARNING) + status = VL53L5_ERROR_NONE; + if (status != VL53L5_ERROR_NONE) + goto out; + + status = vl53l5_decode_range_data(&p_module->stdev, + &p_module->range.data); + if (status != VL53L5_ERROR_NONE) + goto out; + + if (p_module->patch_ver.patch_version.ver_major < 6 && + p_module->patch_ver.patch_version.ver_minor >= 0) { + status = vl53l8_k_glare_filter(&p_module->gf_tuning, + &p_module->range.data); + if (status != VL53L5_ERROR_NONE) + goto out; + } + + p_module->range.count++; + p_module->range.is_valid = 1; + } +#endif +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + if (p != NULL) { + status = copy_to_user( + p, &p_module->range.data, sizeof(struct vl53l5_range_data_t)); + if (status != VL53L5_ERROR_NONE) { + status = VL53L8_K_ERROR_FAILED_TO_COPY_RANGE_DATA; + goto out; + } + } +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + if (status == STATUS_OK) { + vl53l8_copy_report_data(p_module); + if (p != NULL) { + status = copy_to_user( + p, &p_module->af_range_data, sizeof(struct range_sensor_data_t)); + } + if (status != STATUS_OK) + status = VL53L8_K_ERROR_FAILED_TO_COPY_RANGE_DATA; + } +#endif +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE +out: +#endif + if (status != VL53L5_ERROR_NONE) + status = vl53l5_read_device_error(&p_module->stdev, status); +#ifdef VL53L8_INTERRUPT + if (p_module->last_driver_error != VL53L5_ERROR_NONE && + p_module->last_driver_error != status) { + status = p_module->last_driver_error; +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG + vl53l8_last_error_counter(p_module, p_module->last_driver_error); +#endif + } + if (status != VL53L5_ERROR_NONE) { +#endif +#ifdef VL53L8_INTERRUPT +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE + vl53l8_k_log_debug("Disable IRQ"); + disable_irq(p_module->gpio.interrupt); + p_module->irq_is_active = false; +#endif +#endif + +#ifdef VL53L8_INTERRUPT + } +#endif + if (status != VL53L5_ERROR_NONE) { + vl53l8_k_log_error("Failed: %d", status); +#ifdef VL53L5_TCDM_ENABLE + if (status < VL53L8_K_ERROR_NOT_DEFINED) { + vl53l8_k_log_info("TCDM Dump"); + _tcdm_dump(p_module); + } +#endif + } +out_state: + + //vl53l8_k_log_debug("Unlock"); + //mutex_unlock(&p_module->mutex); + + LOG_FUNCTION_END(status); + return status; +} + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +int vl53l8_get_firmware_version(struct vl53l8_k_module_t *p_module, struct vl53l8_k_version_t *p_version) +{ + int status = VL53L5_ERROR_NONE;; + + status = vl53l5_get_version(&p_module->stdev, &p_version->driver); + if (status != VL53L5_ERROR_NONE) + return status; + + status = vl53l5_get_patch_version(&p_module->stdev, &p_version->patch); + if (status != VL53L5_ERROR_NONE) + return status; + + memcpy(&p_version->bin_version, &p_module->bin_version, sizeof(struct vl53l8_k_bin_version)); + + return status; +} + +int vl53l8_set_device_parameters(struct vl53l8_k_module_t *p_module, + int config) +{ + int status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + //vl53l8_k_log_debug("Lock"); + //mutex_lock(&p_module->mutex); + status = _check_state(p_module, VL53L8_STATE_INITIALISED); + if (status != VL53L5_ERROR_NONE) + goto out_state; + + p_module->config_preset = config; + status = STATUS_OK; + + if (status != VL53L5_ERROR_NONE) { + status = VL53L8_K_ERROR_FAILED_TO_COPY_CONFIG_PRESET; + goto out; + } + + status = _set_device_params(p_module); + +out: + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } +out_state: + + //vl53l8_k_log_debug("Unlock"); + //mutex_unlock(&p_module->mutex); + LOG_FUNCTION_END(status); + return status; +} + +#ifdef CONFIG_SEPARATE_IO_CORE_POWER +int vl53l8_regulator_init_state(struct vl53l8_k_module_t *data) +{ + int voltage; + + if (data->avdd_vreg_name) { + if (data->avdd_vreg == NULL) { + data->avdd_vreg = regulator_get(&data->spi_info.device->dev, data->avdd_vreg_name); + if (IS_ERR(data->avdd_vreg)) { + data->avdd_vreg = NULL; + return VL53L8_LDO_AVDD_ERROR; + } + if (data->avdd_vreg) { + voltage = regulator_get_voltage(data->avdd_vreg); + if (voltage < 0) { + vl53l8_k_log_error("avdd dummy voltage=%d\n", voltage); + data->avdd_vreg = NULL; + return VL53L8_LDO_AVDD_ERROR; + } + } + } + } + + if (data->iovdd_vreg_name) { + if (data->iovdd_vreg == NULL) { + data->iovdd_vreg = regulator_get(&data->spi_info.device->dev, data->iovdd_vreg_name); + if (IS_ERR(data->iovdd_vreg)) { + data->iovdd_vreg = NULL; + return VL53L8_LDO_AVDD_ERROR; + } + if (data->iovdd_vreg) { + voltage = regulator_get_voltage(data->iovdd_vreg); + if (voltage < 0) { + vl53l8_k_log_error("iovdd_vreg dummy voltage=%d\n", voltage); + data->iovdd_vreg = NULL; + return VL53L8_LDO_IOVDD_ERROR; + } + } + } + } + + if (data->corevdd_vreg_name) { + if (data->corevdd_vreg == NULL) { + data->corevdd_vreg = regulator_get(&data->spi_info.device->dev, data->corevdd_vreg_name); + if (IS_ERR(data->corevdd_vreg)) { + data->corevdd_vreg = NULL; + return VL53L8_LDO_AVDD_ERROR; + } + if (data->corevdd_vreg) { + voltage = regulator_get_voltage(data->corevdd_vreg); + if (voltage < 0) { + vl53l8_k_log_error("corevdd_vreg dummy voltage=%d\n", voltage); + data->corevdd_vreg = NULL; + return VL53L8_LDO_COREVDD_ERROR; + } + } + } + } + + return STATUS_OK; +} +#endif + +int vl53l8_ldo_onoff(struct vl53l8_k_module_t *data, int io, bool on) +{ + int ret = 0; +#ifndef CONFIG_SENSORS_VL53L8_SUPPORT_RESUME_WORK + int voltage = 0; + int reg_enabled = 0; +#endif + + vl53l8_k_log_info("%d %s\n", io, on ? "on" : "off"); + + if (io == AVDD) { + if (data->avdd_vreg_name) { + if (data->avdd_vreg == NULL) { + vl53l8_k_log_error("avdd is null\n"); + + data->avdd_vreg = regulator_get(&data->spi_info.device->dev, data->avdd_vreg_name); + if (IS_ERR(data->avdd_vreg)) { + data->avdd_vreg = NULL; + vl53l8_k_log_error("failed avdd %s\n", data->avdd_vreg_name); + } + } + } + + if (data->avdd_vreg) { +#ifndef CONFIG_SENSORS_VL53L8_SUPPORT_RESUME_WORK + voltage = regulator_get_voltage(data->avdd_vreg); + reg_enabled = regulator_is_enabled(data->avdd_vreg); + vl53l8_k_log_debug("avdd reg_enabled=%d voltage=%d\n", reg_enabled, voltage); +#endif + + if (on) { +#ifndef CONFIG_SEPARATE_IO_CORE_POWER + if (reg_enabled == 0) +#endif + { + vl53l8_k_log_info("avdd on\n"); + ret = regulator_enable(data->avdd_vreg); + if (ret) { + vl53l8_k_log_error("avdd enable fail\n"); + return ret; + } + } + } else { +#ifndef CONFIG_SEPARATE_IO_CORE_POWER + if (reg_enabled == 1) +#endif + { + vl53l8_k_log_info("avdd off\n"); + ret = regulator_disable(data->avdd_vreg); + if (ret) { + vl53l8_k_log_error("avdd disable fail\n"); + return ret; + } + } + } + } else + vl53l8_k_log_info("avdd error\n"); + } else if (io == IOVDD) { + if (data->iovdd_vreg_name) { + if (data->iovdd_vreg == NULL) { + vl53l8_k_log_error("iovdd is null\n"); + + data->iovdd_vreg = regulator_get(&data->spi_info.device->dev, data->iovdd_vreg_name); + if (IS_ERR(data->iovdd_vreg)) { + data->iovdd_vreg = NULL; + vl53l8_k_log_error("failed iovdd %s\n", data->iovdd_vreg_name); + } + } + } + + if (data->iovdd_vreg) { +#ifndef CONFIG_SENSORS_VL53L8_SUPPORT_RESUME_WORK + voltage = regulator_get_voltage(data->iovdd_vreg); + reg_enabled = regulator_is_enabled(data->iovdd_vreg); + vl53l8_k_log_debug("iovdd reg_enabled=%d voltage=%d\n", reg_enabled, voltage); +#endif + if (on) { +#ifndef CONFIG_SEPARATE_IO_CORE_POWER + if (reg_enabled == 0) +#endif + { + vl53l8_k_log_info("iovdd on\n"); + ret = regulator_enable(data->iovdd_vreg); + if (ret) { + vl53l8_k_log_error("iovdd enable err\n"); + return ret; + } + } + } else { +#ifndef CONFIG_SEPARATE_IO_CORE_POWER + if (reg_enabled == 1) +#endif + { + vl53l8_k_log_info("iovdd off\n"); + ret = regulator_disable(data->iovdd_vreg); + if (ret) { + vl53l8_k_log_error("iovdd disable err\n"); + return ret; + } + } + } + } else + vl53l8_k_log_info("vdd_vreg err\n"); +#ifdef CONFIG_SEPARATE_IO_CORE_POWER + } else if (io == COREVDD) { + if (data->corevdd_vreg_name) { + if (data->corevdd_vreg == NULL) { + vl53l8_k_log_error("corevdd is null\n"); + + data->corevdd_vreg = regulator_get(&data->spi_info.device->dev, data->corevdd_vreg_name); + if (IS_ERR(data->corevdd_vreg)) { + data->corevdd_vreg = NULL; + vl53l8_k_log_error("failed corevdd %s\n", data->corevdd_vreg_name); + } + } + } + + if (data->corevdd_vreg) { +#ifndef CONFIG_SENSORS_VL53L8_SUPPORT_RESUME_WORK + voltage = regulator_get_voltage(data->corevdd_vreg); + reg_enabled = regulator_is_enabled(data->corevdd_vreg); + vl53l8_k_log_info("corevdd reg_enabled=%d voltage=%d\n", reg_enabled, voltage); +#endif + if (on) { + vl53l8_k_log_info("corevdd on\n"); + ret = regulator_enable(data->corevdd_vreg); + if (ret) { + vl53l8_k_log_error("corevdd enable err\n"); + return ret; + } + } else { + vl53l8_k_log_info("corevdd off\n"); + ret = regulator_disable(data->corevdd_vreg); + if (ret) { + vl53l8_k_log_error("corevdd disable err\n"); + return ret; + } + } + } else + vl53l8_k_log_info("vdd_vreg err\n"); +#endif + } else { + vl53l8_k_log_info("wrong io %d\n", io); + } + + return ret; +} + +void vl53l8_ldo_control(struct vl53l8_k_module_t *data, int regulator, bool on) +{ + int ret = 0; + + if (data->ldo_prev_state[regulator] != on) + ret = vl53l8_ldo_onoff(data, regulator, on); + + if (ret < 0) { + vl53l8_k_log_error("ldo[%d] on:%d err:%d\n", regulator, on, ret); + return; + } + + data->ldo_prev_state[regulator] = on; +} + +void vl53l8_power_onoff(struct vl53l8_k_module_t *p_module, bool on) +{ + if (!on) + p_module->state_preset = VL53L8_STATE_PRESENT; + + vl53l8_ldo_control(p_module, IOVDD, on); + vl53l8_ldo_control(p_module, AVDD, on); +#ifdef CONFIG_SEPARATE_IO_CORE_POWER + vl53l8_ldo_control(p_module, COREVDD, on); +#endif + +#ifndef CONFIG_SENSORS_VL53L8_SUPPORT_RESUME_WORK + if (on) { + int status; + + usleep_range(1000, 1100); + p_module->stdev.host_dev.p_fw_buff = NULL; + status = vl53l5_init(&p_module->stdev); + if (status < 0) { +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG + vl53l8_last_error_counter(p_module, status); +#endif + vl53l8_k_log_error("resume init err"); + p_module->stdev.last_dev_error = VL53L8_RESUME_INIT_ERROR; + return; + } + + p_module->stdev.last_dev_error = VL53L5_ERROR_NONE; + p_module->stdev.status_probe = STATUS_OK; + p_module->last_driver_error = STATUS_OK; + + p_module->state_preset = VL53L8_STATE_INITIALISED; + p_module->power_state = VL53L5_POWER_STATE_HP_IDLE; + vl53l8_k_log_info("VL53L8_STATE_INITIALISED"); + } +#endif +} + +int vl53l8_ioctl_get_status(struct vl53l8_k_module_t *p_module, void __user *p) +{ + int status = STATUS_OK; + + if (p != NULL) { + status = copy_to_user(p, &p_module->ioctl_enable_status, sizeof(int)); + } else { + vl53l8_k_log_error("failed to get enable status"); + status = VL53L5_ERROR_INVALID_PARAMS; + } + + return status; +} + +int vl53l8_ioctl_get_cal_data(struct vl53l8_k_module_t *p_module, void __user *p) +{ + int status = STATUS_OK; + + if (p != NULL) { + status = copy_to_user(p, &p_module->cal_data, sizeof(struct vl53l8_cal_data_t)); + vl53l8_k_log_info("get data %d %d", p_module->cal_data.cmd, p_module->cal_data.size); + } else { + vl53l8_k_log_info("get is null"); + status = VL53L5_ERROR_INVALID_PARAMS; + } + + return status; +} + +int vl53l8_ioctl_set_cal_data(struct vl53l8_k_module_t *p_module, void __user *p) +{ + int status = VL53L5_ERROR_INVALID_PARAMS; + + mutex_lock(&p_module->cal_mutex); + if (p != NULL) { + status = copy_from_user(&p_module->cal_data, p, sizeof(struct vl53l8_cal_data_t)); + vl53l8_k_log_info("set cal cmd %d, %d", p_module->cal_data.cmd, p_module->cal_data.size); + + if ((p_module->cal_data.size > 0) + && (p_module->cal_data.size <= p_module->stdev.host_dev.comms_buff_max_count)) { + memset(p_module->stdev.host_dev.p_comms_buff, 0, p_module->stdev.host_dev.comms_buff_max_count); + memcpy(p_module->stdev.host_dev.p_comms_buff, p_module->cal_data.pcal_data, p_module->cal_data.size); + + p_module->pass_fail_flag |= 1 << p_module->cal_data.cmd; + p_module->update_flag |= 1 << p_module->cal_data.cmd; + + status = STATUS_OK; + } else { + vl53l8_k_log_error("Over Size %d", p_module->stdev.host_dev.comms_buff_max_count); +#ifdef VL53L5_TEST_ENABLE + panic("Cal Data Size Error"); +#endif + } + } else { + vl53l8_k_log_info("set cal data is null"); + } + + mutex_unlock(&p_module->cal_mutex); + return status; +} + +int vl53l8_ioctl_set_pass_fail(struct vl53l8_k_module_t *p_module, + void __user *p) +{ + int status = STATUS_OK; + + if (p != NULL) { + struct vl53l8_update_data_t result; + + status = copy_from_user(&result, p, sizeof(struct vl53l8_update_data_t)); + if (status == STATUS_OK) { + p_module->pass_fail_flag |= result.pass_fail << result.cmd; + p_module->update_flag |= 1 << result.cmd; + vl53l8_k_log_info("update %x, %d", p_module->update_flag, result.pass_fail); + } else { + vl53l8_k_log_error("copy from user err"); + } + } else + status = VL53L5_ERROR_INVALID_PARAMS; + + return status; +} + +int vl53l8_ioctl_set_file_list(struct vl53l8_k_module_t *p_module, + void __user *p) +{ + int status = STATUS_OK; + + if (p != NULL) { + struct vl53l8_file_list_t list; + + status = copy_from_user(&list, p, sizeof(struct vl53l8_file_list_t)); + if (status == STATUS_OK) + p_module->file_list = list.file_list; + else + vl53l8_k_log_error("copy from user err"); + } else + status = VL53L5_ERROR_INVALID_PARAMS; + + return status; +} + +int vl53l8_input_report(struct vl53l8_k_module_t *p_module, int type, int cmd) +{ + int status = STATUS_OK; + int cnt = 0; + + if (cmd >= 8) { + status = VL53L8_IO_ERROR; + vl53l8_k_log_info("Invalid cmd %d", cmd); + return status; + } + + if (p_module->input_dev) { + p_module->update_flag &= ~(1 << cmd); + p_module->pass_fail_flag &= ~(1 << cmd); + + vl53l8_k_log_info("send event %d", type); + input_report_rel(p_module->input_dev, REL_MISC, type); + input_sync(p_module->input_dev); + + while (!(p_module->update_flag & 1 << cmd) + && cnt++ < TIMEOUT_CNT) + usleep_range(2000, 2100); + + vl53l8_k_log_info("cnt %d / %x,%x", cnt, p_module->update_flag, p_module->pass_fail_flag); + p_module->update_flag &= ~(1 << cmd); + if (p_module->pass_fail_flag & (1 << cmd)) + p_module->pass_fail_flag &= ~(1 << cmd); + else + status = VL53L8_IO_ERROR; + vl53l8_k_log_info("cmd %d / %x,%x", cmd, p_module->update_flag, p_module->pass_fail_flag); + } else { + vl53l8_k_log_error("input_dev err %d %d", type, cmd); + } + return status; +} +#endif +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE +int vl53l8_ioctl_set_device_parameters(struct vl53l8_k_module_t *p_module, + void __user *p) +{ + int status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + vl53l8_k_log_debug("Lock"); + mutex_lock(&p_module->mutex); + status = _check_state(p_module, VL53L8_STATE_INITIALISED); + if (status != VL53L5_ERROR_NONE) + goto out_state; + + status = copy_from_user(&p_module->config_preset, p, + sizeof(int)); + if (status != VL53L5_ERROR_NONE) { + status = VL53L8_K_ERROR_FAILED_TO_COPY_CONFIG_PRESET; + goto out; + } + + status = _set_device_params(p_module); + +out: + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } +out_state: + + vl53l8_k_log_debug("Unlock"); + mutex_unlock(&p_module->mutex); + LOG_FUNCTION_END(status); + return status; +} + +int vl53l8_ioctl_get_device_parameters(struct vl53l8_k_module_t *p_module, + void __user *p) +{ + int status; + struct vl53l8_k_get_parameters_data_t *p_get_data; + int count = 0; + + LOG_FUNCTION_START(""); + + vl53l8_k_log_debug("Lock"); + mutex_lock(&p_module->mutex); + status = _check_state(p_module, VL53L8_STATE_INITIALISED); + if (status != VL53L5_ERROR_NONE) + goto out_state; + + p_get_data = kzalloc( + sizeof(struct vl53l8_k_get_parameters_data_t), GFP_KERNEL); + if (p_get_data == NULL) { + vl53l8_k_log_error("Allocate Failed"); + status = VL53L8_K_ERROR_FAILED_TO_ALLOCATE_PARAMETER_DATA; + goto out; + } + + status = copy_from_user( + p_get_data, p, sizeof(struct vl53l8_k_get_parameters_data_t)); + if (status != VL53L5_ERROR_NONE) { + status = VL53L8_K_ERROR_FAILED_TO_COPY_PARAMETER_DATA; + goto out_free; + } + + status = vl53l5_get_device_parameters(&p_module->stdev, + p_get_data->headers, + p_get_data->header_count); + if (status != VL53L5_ERROR_NONE) + goto out; + + count = (p_module->stdev.host_dev.comms_buff_count - 4); + + memcpy(p_get_data->raw_data.buffer, + p_module->stdev.host_dev.p_comms_buff, + count); + p_get_data->raw_data.count = count; + + status = copy_to_user( + p, p_get_data, sizeof(struct vl53l8_k_get_parameters_data_t)); + +out: + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + p_module->last_driver_error = status; +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG + vl53l8_last_error_counter(p_module, p_module->last_driver_error); +#endif + } +out_free: + kfree(p_get_data); +out_state: + + vl53l8_k_log_debug("Unlock"); + mutex_unlock(&p_module->mutex); + LOG_FUNCTION_END(status); + return status; +} + +int vl53l8_ioctl_get_error_info(struct vl53l8_k_module_t *p_module, + void __user *p) +{ + int status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + vl53l8_k_log_debug("Lock"); + mutex_lock(&p_module->mutex); + + status = copy_to_user(p, &p_module->last_driver_error, sizeof(int)); + if (status != VL53L5_ERROR_NONE) { + status = VL53L8_K_ERROR_FAILED_TO_COPY_ERROR_INFO; + vl53l8_k_log_error("Failed: %d", status); + goto out; + } + + p_module->last_driver_error = VL53L5_ERROR_NONE; +out: + + vl53l8_k_log_debug("Unlock"); + mutex_unlock(&p_module->mutex); + + LOG_FUNCTION_END(status); + return status; +} +#endif +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE +int vl53l8_ioctl_set_power_mode(struct vl53l8_k_module_t *p_module, + void __user *p) +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +int vl53l8_ioctl_set_power_mode(struct vl53l8_k_module_t *p_module, + void __user *p, enum vl53l5_power_states state) +#endif +{ + int status = VL53L5_ERROR_NONE; + enum vl53l8_k_state_preset current_state; + const char *fw_path; + struct spi_device *spi; + const struct vl53l8_fw_header_t *header; + unsigned char *fw_data; + const struct firmware *fw_entry = NULL; + enum vl53l5_power_states current_power_state; + + LOG_FUNCTION_START(""); + + //vl53l8_k_log_debug("Lock"); + //mutex_lock(&p_module->mutex); + + current_state = p_module->state_preset; + current_power_state = p_module->power_state; + + if (current_state < VL53L8_STATE_LOW_POWER) { + //vl53l8_k_log_debug("Unlock"); + //mutex_unlock(&p_module->mutex); + status = VL53L8_K_ERROR_DEVICE_STATE_INVALID; + goto out_state; + } + + vl53l8_k_log_debug("Current state : %i", p_module->power_state); + + if (p_module->power_state == VL53L5_POWER_STATE_ULP_IDLE) { + spi = p_module->spi_info.device; + fw_path = p_module->firmware_name; + vl53l8_k_log_debug("Load FW : %s", fw_path); + + vl53l8_k_log_debug("Req FW"); + status = request_firmware(&fw_entry, fw_path, &spi->dev); + if (status) { + vl53l8_k_log_error("FW %s not available", + fw_path); + goto out; + } + + fw_data = (unsigned char *)fw_entry->data; + header = (struct vl53l8_fw_header_t *)fw_data; + + vl53l8_k_log_info("Binary FW ver %i.%i", + header->fw_ver_major, header->fw_ver_minor); + + p_module->stdev.host_dev.p_fw_buff = + &fw_data[header->fw_offset]; + p_module->stdev.host_dev.fw_buff_count = header->fw_size; + } +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + status = copy_from_user(&p_module->power_state, p, + sizeof(enum vl53l5_power_states)); +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + if (p != NULL) { + status = copy_from_user(&p_module->power_state, p, + sizeof(enum vl53l5_power_states)); + } else { + p_module->power_state = state; + status = STATUS_OK; + } +#endif + + if (status != VL53L5_ERROR_NONE) { + status = VL53L8_K_ERROR_FAILED_TO_COPY_POWER_MODE; + goto out; + } + + vl53l8_k_log_debug("Req state : %i", p_module->power_state); + + status = vl53l5_set_power_mode(&p_module->stdev, + p_module->power_state); + if (status == VL53L5_ERROR_POWER_STATE) { + vl53l8_k_log_error("Invalid state %i", p_module->power_state); + + p_module->power_state = current_power_state; + (void)vl53l5_set_power_mode(&p_module->stdev, + p_module->power_state); + status = VL53L8_K_ERROR_INVALID_POWER_STATE; + goto out; + } else if (status != VL53L5_ERROR_NONE) + goto out; + + switch (p_module->power_state) { + case VL53L5_POWER_STATE_ULP_IDLE: + case VL53L5_POWER_STATE_LP_IDLE_COMMS: + p_module->state_preset = VL53L8_STATE_LOW_POWER; + break; + case VL53L5_POWER_STATE_HP_IDLE: + p_module->state_preset = VL53L8_STATE_INITIALISED; + break; + default: + vl53l8_k_log_error("Invalid state %i", p_module->power_state); + + p_module->state_preset = current_state; + status = VL53L8_K_ERROR_INVALID_POWER_STATE; + break; + } + +out: + + vl53l8_k_log_debug("Release FW"); + release_firmware(fw_entry); + + p_module->stdev.host_dev.p_fw_buff = NULL; + p_module->stdev.host_dev.fw_buff_count = 0; + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } +out_state: + //vl53l8_k_log_debug("Unlock"); + //mutex_unlock(&p_module->mutex); + //LOG_FUNCTION_END(status); + return status; +} + + +int vl53l8_ioctl_poll_data_ready(struct vl53l8_k_module_t *p_module) +{ + int status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + //vl53l8_k_log_debug("Lock"); + //mutex_lock(&p_module->mutex); + + status = _check_state(p_module, VL53L8_STATE_RANGING); + if (status != VL53L5_ERROR_NONE) + goto out_state; + + status = _poll_for_new_data( + p_module, p_module->polling_sleep_time_ms, + VL53L8_K_MAX_CALIBRATION_POLL_TIME_MS); + + if (status != VL53L5_ERROR_NONE) { + if ((status == VL53L8_K_ERROR_DEVICE_NOT_INITIALISED) || + (status == VL53L8_K_ERROR_RANGE_POLLING_TIMEOUT)) + status = VL53L8_K_ERROR_DEVICE_NOT_RANGING; + goto out; + } +out: + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } +out_state: + vl53l8_k_log_debug("Unlock"); + //mutex_unlock(&p_module->mutex); + + LOG_FUNCTION_END(status); + return status; +} + +int vl53l8_ioctl_read_p2p_calibration(struct vl53l8_k_module_t *p_module) +{ + int status = VL53L5_ERROR_NONE; +// unsigned int file_size = VL53L8_K_P2P_FILE_SIZE + 4 + +// VL53L8_K_DEVICE_INFO_SZ; + + LOG_FUNCTION_START(""); + + //vl53l8_k_log_debug("Lock"); + //mutex_lock(&p_module->mutex); + + status = _check_state(p_module, VL53L8_STATE_INITIALISED); + if (status != VL53L5_ERROR_NONE) { +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + if (!p_module->stdev.status_cal) + p_module->stdev.status_cal = -10; +#endif + goto out_state; + } + +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE + vl53l8_k_log_info("Read filename %s", VL53L8_CAL_P2P_FILENAME); + vl53l8_k_log_debug("Read file size %i", file_size); + + status = vl53l5_read_file(&p_module->stdev, + p_module->stdev.host_dev.p_comms_buff, + file_size, + VL53L8_CAL_P2P_FILENAME); + + if (status < VL53L5_ERROR_NONE) { + vl53l8_k_log_error("read file %s failed: %d ", + VL53L8_CAL_P2P_FILENAME, status); + goto out; + } +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + status = vl53l8_input_report(p_module, 1, CMD_READ_CAL_FILE); + if (status != STATUS_OK) { + vl53l8_k_log_error("Read Cal Failed"); + if (!p_module->stdev.status_cal) + p_module->stdev.status_cal = -11; + goto out; + } else { + vl53l8_k_log_info("Read file size %d", p_module->cal_data.size); + } +#endif + + _decode_device_info(&p_module->calibration, + p_module->stdev.host_dev.p_comms_buff); + + status = vl53l5_decode_calibration_data( + &p_module->stdev, + &p_module->calibration.cal_data, + &p_module->stdev.host_dev.p_comms_buff[VL53L8_K_DEVICE_INFO_SZ], + VL53L8_K_P2P_FILE_SIZE + 4); + + if (status != VL53L5_ERROR_NONE) { + vl53l8_k_log_error("Fail vl53l8_decode_calibration_data %d", status); +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + if (!p_module->stdev.status_cal) + p_module->stdev.status_cal = -12; +#endif + goto out; + } + +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE + vl53l8_k_log_info("FGC: %s", (char *)p_module->calibration.info.fgc); + vl53l8_k_log_info("CAL ID : %x.%x", + p_module->calibration.info.module_id_hi, + p_module->calibration.info.module_id_lo); +#else + vl53l8_k_log_info("ID %x.%x", + p_module->calibration.info.module_id_hi, + p_module->calibration.info.module_id_lo); +#endif + if ((p_module->calibration.info.module_id_hi == + p_module->m_info.module_id_hi) && + (p_module->calibration.info.module_id_lo == + p_module->m_info.module_id_lo)) { + status = vl53l5_set_device_parameters( + &p_module->stdev, + &p_module->stdev.host_dev.p_comms_buff[VL53L8_K_DEVICE_INFO_SZ], + VL53L8_K_P2P_FILE_SIZE); + + if (status != VL53L5_ERROR_NONE) { +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + if (!p_module->stdev.status_cal) + p_module->stdev.status_cal = -13; +#endif + goto out; + } + } else { + vl53l8_k_log_error("device and cal id was not matched %x.%x ", + p_module->m_info.module_id_hi, + p_module->m_info.module_id_lo); +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + status = VL53L5_CONFIG_ERROR_INVALID_VERSION; + if (!p_module->stdev.status_cal) + p_module->stdev.status_cal = -14; +#endif + } + +out: + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } +out_state: + //vl53l8_k_log_debug("Unlock"); + //mutex_unlock(&p_module->mutex); + + LOG_FUNCTION_END(status); + return status; +} +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE +int vl53l8_ioctl_read_shape_calibration(struct vl53l8_k_module_t *p_module) +{ + int status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + //vl53l8_k_log_debug("Lock"); + //mutex_lock(&p_module->mutex); + + status = _check_state(p_module, VL53L8_STATE_INITIALISED); + if (status != VL53L5_ERROR_NONE) + goto out_state; + + vl53l8_k_log_debug("Read filename %s", VL53L8_CAL_SHAPE_FILENAME); + vl53l8_k_log_debug("Read file size %i", VL53L8_K_SHAPE_FILE_SIZE); +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + status = vl53l5_read_file(&p_module->stdev, + p_module->stdev.host_dev.p_comms_buff, + VL53L8_K_SHAPE_FILE_SIZE + 4, + VL53L8_CAL_SHAPE_FILENAME); +#endif + if (status < VL53L5_ERROR_NONE) { + vl53l8_k_log_error("read file %s failed: %d ", + VL53L8_CAL_SHAPE_FILENAME, status); + goto out; + } + + status = vl53l5_decode_calibration_data( + &p_module->stdev, + &p_module->calibration.cal_data, + p_module->stdev.host_dev.p_comms_buff, + VL53L8_K_P2P_FILE_SIZE + 4); + + status = vl53l5_set_device_parameters( + &p_module->stdev, + p_module->stdev.host_dev.p_comms_buff, + VL53L8_K_P2P_FILE_SIZE); + + if (status != VL53L5_ERROR_NONE) + goto out; + +out: + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } +out_state: + //vl53l8_k_log_debug("Unlock"); + //mutex_unlock(&p_module->mutex); + + LOG_FUNCTION_END(status); + return status; +} +#endif + +int vl53l8_ioctl_read_open_cal_p2p_calibration(struct vl53l8_k_module_t *p_module) +{ + int status = STATUS_OK; + unsigned char *p_buff = NULL; + + LOG_FUNCTION_START(""); + + status = _check_state(p_module, VL53L8_STATE_INITIALISED); + if (status != STATUS_OK) { + if (!p_module->stdev.status_cal) + p_module->stdev.status_cal = -15; + goto out_state; + } + + status = vl53l8_input_report(p_module, 4, CMD_READ_P2P_CAL_FILE); + if (status != STATUS_OK) { + vl53l8_k_log_error("Read Cal Failed"); + if (!p_module->stdev.status_cal) + p_module->stdev.status_cal = -16; + goto out; + } else { + vl53l8_k_log_info("Read file size %d", p_module->cal_data.size); + } + + _decode_device_info(&p_module->calibration, + p_module->stdev.host_dev.p_comms_buff); + + status = vl53l5_decode_calibration_data( + &p_module->stdev, + &p_module->calibration.cal_data, + &p_module->stdev.host_dev.p_comms_buff[VL53L8_K_DEVICE_INFO_SZ], + VL53L8_K_P2P_FILE_SIZE + 4); + + if (status != STATUS_OK) { + vl53l8_k_log_info("Fail vl53l5_decode_calibration_data %d", status); + if (!p_module->stdev.status_cal) + p_module->stdev.status_cal = -17; + goto out; + } + vl53l8_k_log_info("CAL ID : %x.%x", + p_module->calibration.info.module_id_hi, + p_module->calibration.info.module_id_lo); + + if ((p_module->calibration.info.module_id_hi + == p_module->m_info.module_id_hi) + && (p_module->calibration.info.module_id_lo + == p_module->m_info.module_id_lo)) { + p_buff = p_module->stdev.host_dev.p_comms_buff; + p_buff += VL53L8_K_DEVICE_INFO_SZ; + status = vl53l5_set_device_parameters(&p_module->stdev, p_buff, + VL53L8_K_P2P_FILE_SIZE); + + if (status != STATUS_OK) { + if (!p_module->stdev.status_cal) + p_module->stdev.status_cal = -18; + goto out; + } + } else { + vl53l8_k_log_error("device and cal id was not matched %x.%x", + p_module->m_info.module_id_hi, + p_module->m_info.module_id_lo); + status = VL53L5_CONFIG_ERROR_INVALID_VERSION; + if (!p_module->stdev.status_cal) + p_module->stdev.status_cal = -19; + } + +out: + if (status != STATUS_OK) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } +out_state: + LOG_FUNCTION_END(status); + return status; +} + +int vl53l8_ioctl_read_open_cal_shape_calibration(struct vl53l8_k_module_t *p_module) +{ + int status = STATUS_OK; + + LOG_FUNCTION_START(""); + + status = _check_state(p_module, VL53L8_STATE_INITIALISED); + if (status != STATUS_OK) { + if (!p_module->stdev.status_cal) + p_module->stdev.status_cal = -20; + goto out_state; + } + + status = vl53l8_input_report(p_module, 5, CMD_READ_SHAPE_CAL_FILE); + if (status != STATUS_OK) { + vl53l8_k_log_error("Read Cal Failed"); + if (!p_module->stdev.status_cal) + p_module->stdev.status_cal = -21; + goto out; + } else { + vl53l8_k_log_info("Read file size %d", p_module->cal_data.size); + } + + _decode_device_info(&p_module->calibration, + p_module->stdev.host_dev.p_comms_buff); + + status = vl53l5_decode_calibration_data( + &p_module->stdev, + &p_module->calibration.cal_data, + p_module->stdev.host_dev.p_comms_buff, + VL53L8_K_SHAPE_FILE_SIZE + 4); + + if (status != STATUS_OK) { + vl53l8_k_log_info("Fail vl53l8_decode_calibration_data %d", status); + if (!p_module->stdev.status_cal) + p_module->stdev.status_cal = -22; + goto out; + } + + status = vl53l5_set_device_parameters( + &p_module->stdev, + p_module->stdev.host_dev.p_comms_buff, + VL53L8_K_SHAPE_FILE_SIZE); + + if (status != STATUS_OK) + goto out; +out: + if (status != STATUS_OK) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } +out_state: + LOG_FUNCTION_END(status); + return status; +} + + +int vl53l8_perform_calibration(struct vl53l8_k_module_t *p_module, int flow) +{ + int status = VL53L5_ERROR_NONE; + int file_status = VL53L5_ERROR_NONE; + int xtalk = 0; + + LOG_FUNCTION_START(""); + + //vl53l8_k_log_debug("Lock"); + //mutex_lock(&p_module->mutex); + + status = _check_state(p_module, VL53L8_STATE_INITIALISED); + if (status != VL53L5_ERROR_NONE) + goto out_state; + + switch (flow) { + case 0: + xtalk = VL53L8_CFG__XTALK_GEN2_8X8_300; + break; + case 1: + xtalk = VL53L8_CFG__XTALK_GEN1_8X8_1000; + break; + default: + vl53l8_k_log_error("Invalid cal flow: %d", flow); + status = VL53L8_K_ERROR_INVALID_CALIBRATION_FLOW; + goto out; + }; + +#ifdef VL53L8_RAD2PERP_CAL + status = _perform_rad2perp_calibration(p_module); + + if (status != VL53L5_ERROR_NONE) { + vl53l8_k_log_error("Rad2Perp Failed"); + goto out; + } +#endif + + p_module->config_preset = xtalk; + status = _set_device_params(p_module); + + if (status != VL53L5_ERROR_NONE) + goto out; + + status = _start_poll_stop(p_module); + + if (status != VL53L5_ERROR_NONE) { + vl53l8_k_log_error("Xtalk Failed"); + goto out; + } + + if (flow == 1) { + file_status = _write_shape_calibration(p_module); + if (file_status != VL53L5_ERROR_NONE) + goto out; + } + file_status = _write_p2p_calibration(p_module, flow); + if (file_status != VL53L5_ERROR_NONE) + goto out; + +out: + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); +#ifdef VL53L5_TCDM_ENABLE + if (status < VL53L8_K_ERROR_NOT_DEFINED) { + vl53l8_k_log_info("TCDM Dump"); + _tcdm_dump(p_module); + } +#endif + } + if (status == VL53L5_ERROR_NONE && file_status != VL53L5_ERROR_NONE) { + status = file_status; + vl53l8_k_log_error("Failed: %d", status); + } +out_state: + //vl53l8_k_log_debug("Unlock"); + //mutex_unlock(&p_module->mutex); + + LOG_FUNCTION_END(status); + return status; +} + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +int vl53l8_readfile_genshape(struct vl53l8_k_module_t *p_module, + char *dst, size_t size) +{ + int status = STATUS_OK; + struct spi_device *spi; + const struct firmware *fw_entry = NULL; + + LOG_FUNCTION_START(""); + if (!p_module->genshape_name || dst == NULL) { + vl53l8_k_log_error( + "generic shape name does not declared"); + p_module->stdev.status_cal = -1; + status = -EPERM; + goto out; + } + + spi = p_module->spi_info.device; + status = request_firmware(&fw_entry, p_module->genshape_name, &spi->dev); + if (status) { + vl53l8_k_log_error("Firmware %s not available", p_module->genshape_name); + p_module->stdev.status_cal = -2; + status = -2; + goto out; + } + + if (size > fw_entry->size) { + size = fw_entry->size; + vl53l8_k_log_error("Firmware size: %d, req size: %d", (int)fw_entry->size, (int)size); + } + memcpy(dst, (char *)fw_entry->data, size); + vl53l8_k_log_info("Read genshape %s done", p_module->genshape_name); + release_firmware(fw_entry); +out: + LOG_FUNCTION_END(status); + return status; + +} +#endif + + +int vl53l8_ioctl_read_generic_shape(struct vl53l8_k_module_t *p_module) +{ + int status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + //vl53l8_k_log_debug("Lock"); + //mutex_lock(&p_module->mutex); + + status = _check_state(p_module, VL53L8_STATE_INITIALISED); + if (status != VL53L5_ERROR_NONE) { +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + if (!p_module->stdev.status_cal) + p_module->stdev.status_cal = -5; +#endif + goto out_state; + + } + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + status = vl53l8_readfile_genshape(p_module, p_module->stdev.host_dev.p_comms_buff, + VL53L8_K_SHAPE_FILE_SIZE + 4); + if (status != STATUS_OK) + goto out; + + _decode_device_info(&p_module->calibration, + p_module->stdev.host_dev.p_comms_buff); + + status = vl53l5_decode_calibration_data( + &p_module->stdev, + &p_module->calibration.cal_data, + p_module->stdev.host_dev.p_comms_buff, + VL53L8_K_SHAPE_FILE_SIZE + 4); + + if (status != STATUS_OK) { +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + if (!p_module->stdev.status_cal) + p_module->stdev.status_cal = -7; +#endif + goto out; + } +#endif + +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE + vl53l8_k_log_info("Read filename %s", VL53L8_GENERIC_SHAPE_FILENAME); + + vl53l8_k_log_debug("Read file size %i", VL53L8_K_SHAPE_FILE_SIZE + 4); + + status = vl53l5_read_file(&p_module->stdev, + p_module->stdev.host_dev.p_comms_buff, + VL53L8_K_SHAPE_FILE_SIZE + 4, + VL53L8_GENERIC_SHAPE_FILENAME); + + if (status < VL53L5_ERROR_NONE) { + vl53l8_k_log_error("read file %s failed: %d ", + VL53L8_GENERIC_SHAPE_FILENAME, status); + goto out; + } +#endif + + status = vl53l5_set_device_parameters( + &p_module->stdev, + p_module->stdev.host_dev.p_comms_buff, + VL53L8_K_SHAPE_FILE_SIZE); + + if (status != VL53L5_ERROR_NONE) { +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + if (!p_module->stdev.status_cal) + p_module->stdev.status_cal = -8; +#endif + goto out; + } + +out: + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } +out_state: + //vl53l8_k_log_debug("Unlock"); + //mutex_unlock(&p_module->mutex); + + LOG_FUNCTION_END(status); + return status; +} + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +int vl53l8_ioctl_set_ranging_rate(struct vl53l8_k_module_t *p_module, + uint32_t rate) +#else +int vl53l8_ioctl_set_ranging_rate(struct vl53l8_k_module_t *p_module, + void __user *p) +#endif +{ + int status = VL53L5_ERROR_NONE; + uint32_t ranging_rate = 0; + + LOG_FUNCTION_START(""); +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE + vl53l8_k_log_debug("Lock"); + mutex_lock(&p_module->mutex); +#endif + status = _check_state(p_module, VL53L8_STATE_INITIALISED); + if (status != VL53L5_ERROR_NONE) + goto out_state; + +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE + if (p != NULL) { + status = copy_from_user(&ranging_rate, p, sizeof(int)); + if (status != VL53L5_ERROR_NONE) { + status = VL53L8_K_ERROR_FAILED_TO_COPY_RANGING_RATE; + goto out; + } + } +#else + ranging_rate = rate; +#endif + + status = vl53l5_set_ranging_rate_hz(&p_module->stdev, ranging_rate); + +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE +out: +#endif + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } + +out_state: +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE + vl53l8_k_log_debug("Unlock"); + mutex_unlock(&p_module->mutex); +#endif + LOG_FUNCTION_END(status); + return status; +} + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +int vl53l8_ioctl_set_integration_time_us(struct vl53l8_k_module_t *p_module, + uint32_t integration) +#else +int vl53l8_ioctl_set_integration_time_us(struct vl53l8_k_module_t *p_module, + void __user *p) +#endif +{ + int status = VL53L5_ERROR_NONE; + uint32_t max_integration = 0; + +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE + LOG_FUNCTION_START(""); + + vl53l8_k_log_debug("Lock"); + mutex_lock(&p_module->mutex); + + + if (p != NULL) { + status = copy_from_user(&max_integration, p, sizeof(int)); + if (status != VL53L5_ERROR_NONE) { + status = VL53L8_K_ERROR_FAILED_TO_COPY_MAX_INTEGRATION; + goto out; + } + } +#else + max_integration = integration; +#endif + + status = vl53l5_set_integration_time_us(&p_module->stdev, max_integration); +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE +out: +#endif + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE + vl53l8_k_log_debug("Unlock"); + mutex_unlock(&p_module->mutex); + + LOG_FUNCTION_END(status); +#endif + return status; +} + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +int vl53l8_set_asz_tuning(struct vl53l8_k_module_t *p_module, struct vl53l8_k_asz_tuning_t *asz_layout) +{ + int status = VL53L5_ERROR_NONE; + uint8_t buffer[56] = {0}; + uint8_t map_buffer[BYTE_8] = { + 0x40, 0x00, 0x00, 0x54, + 0x0b, 0x00, 0x05, 0x00,}; + uint32_t count = 0; + struct vl53l8_k_asz_tuning_t asz_tuning = {0}; + struct vl53l8_k_asz_t asz_struct = {0}; + + LOG_FUNCTION_START(""); + + //vl53l8_k_log_debug("Lock"); + //mutex_lock(&p_module->mutex); + + status = _check_state(p_module, VL53L8_STATE_INITIALISED); + if (status != VL53L5_ERROR_NONE) + goto out_state; + + memcpy(&asz_tuning, asz_layout, sizeof(struct vl53l8_k_asz_tuning_t)); + + if (status != VL53L5_ERROR_NONE) { + status = VL53L8_K_ERROR_FAILED_TO_COPY_ASZ_TUNING; + goto out; + } + + _copy_buffer(buffer, map_buffer, BYTE_8, &count); + + status = vl53l8_k_assign_asz(&asz_struct, &asz_tuning, buffer, &count); + if (status != VL53L5_ERROR_NONE) + goto out; + + status = vl53l5_set_device_parameters(&p_module->stdev, buffer, count); +out: + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } + +out_state: + //vl53l8_k_log_debug("Unlock"); + //mutex_unlock(&p_module->mutex); + + LOG_FUNCTION_END(status); + return status; +} +#endif + +int vl53l8_ioctl_set_asz_tuning(struct vl53l8_k_module_t *p_module, + void __user *p) +{ + int status = VL53L5_ERROR_NONE; + uint8_t buffer[56] = {0}; + uint8_t map_buffer[BYTE_8] = { + 0x40, 0x00, 0x00, 0x54, + 0x0b, 0x00, 0x05, 0x00,}; + uint32_t count = 0; + struct vl53l8_k_asz_tuning_t asz_tuning = {0}; + struct vl53l8_k_asz_t asz_struct = {0}; + +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE + LOG_FUNCTION_START(""); + + vl53l8_k_log_debug("Lock"); + mutex_lock(&p_module->mutex); +#endif + + status = _check_state(p_module, VL53L8_STATE_INITIALISED); + if (status != VL53L5_ERROR_NONE) + goto out_state; + + status = copy_from_user( + &asz_tuning, p, sizeof(struct vl53l8_k_asz_tuning_t)); + if (status != VL53L5_ERROR_NONE) { + status = VL53L8_K_ERROR_FAILED_TO_COPY_ASZ_TUNING; + goto out; + } + + _copy_buffer(buffer, map_buffer, BYTE_8, &count); + + status = vl53l8_k_assign_asz(&asz_struct, &asz_tuning, buffer, &count); + if (status != VL53L5_ERROR_NONE) + goto out; + + status = vl53l5_set_device_parameters(&p_module->stdev, buffer, count); +out: + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } + +out_state: +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE + vl53l8_k_log_debug("Unlock"); + mutex_unlock(&p_module->mutex); + + LOG_FUNCTION_END(status); +#endif + return status; +} + +static void _build_asz_buffer(uint8_t *p_buffer, uint32_t *p_count, + struct vl53l8_k_asz_t *p_asz) +{ + uint8_t asz_buffer[48] = {0}; + uint8_t *p_buff = asz_buffer; + + vl53l5_encode_uint32_t(VL53L8_ASZ_0_BH, BYTE_4, p_buff); + p_buff += BYTE_4; + memcpy(p_buff, p_asz->asz_0, BYTE_8); + p_buff += BYTE_8; + vl53l5_encode_uint32_t(VL53L8_ASZ_1_BH, BYTE_4, p_buff); + p_buff += BYTE_4; + memcpy(p_buff, p_asz->asz_1, BYTE_8); + p_buff += BYTE_8; + vl53l5_encode_uint32_t(VL53L8_ASZ_2_BH, BYTE_4, p_buff); + p_buff += BYTE_4; + memcpy(p_buff, p_asz->asz_2, BYTE_8); + p_buff += BYTE_8; + vl53l5_encode_uint32_t(VL53L8_ASZ_3_BH, BYTE_4, p_buff); + p_buff += BYTE_4; + memcpy(p_buff, p_asz->asz_3, BYTE_8); + + _copy_buffer(p_buffer, asz_buffer, sizeof(asz_buffer), p_count); +} + +static int _populate_asz_struct(uint8_t *p_zone, uint32_t zone_idx) +{ + int status = VL53L5_ERROR_NONE; + uint32_t c = 0; + uint32_t r = 0; + uint32_t i = 0; + uint32_t asz_size = 2; + uint32_t grid_size = 8; + + for (i = 1; i < grid_size; i++) { + if (zone_idx == ((grid_size * i) - 1) || (zone_idx >= 55)) { + vl53l8_k_log_info("Zone id %i is invalid", zone_idx); + status = VL53L8_K_ERROR_INVALID_VALUE; + goto out; + } + } + + for (i = 0; i < 4; i++) { + c = i % asz_size; + r = i / asz_size; + p_zone[i] = (uint8_t)(zone_idx + c + (r * grid_size)); + vl53l8_k_log_debug("zone[%i] %i", i, p_zone[i]); + } + + for (i = 4; i < 8; i++) + p_zone[i] = (uint8_t)255; + +out: + return status; +} + +static int vl53l8_k_assign_asz(struct vl53l8_k_asz_t *p_asz, + struct vl53l8_k_asz_tuning_t *p_tuning, + uint8_t *p_buffer, uint32_t *p_count) +{ + int status = VL53L5_ERROR_NONE; + + vl53l8_k_log_debug("asz 0 zone idx %i", p_tuning->asz_0_ll_zone_id); + status = _populate_asz_struct(p_asz->asz_0, p_tuning->asz_0_ll_zone_id); + if (status != VL53L5_ERROR_NONE) + goto out; + + vl53l8_k_log_debug("asz 2 zone idx %i", p_tuning->asz_1_ll_zone_id); + status = _populate_asz_struct(p_asz->asz_1, p_tuning->asz_1_ll_zone_id); + if (status != VL53L5_ERROR_NONE) + goto out; + + vl53l8_k_log_debug("asz 3 zone idx %i", p_tuning->asz_2_ll_zone_id); + status = _populate_asz_struct(p_asz->asz_2, p_tuning->asz_2_ll_zone_id); + if (status != VL53L5_ERROR_NONE) + goto out; + + vl53l8_k_log_debug("asz 4 zone idx %i", p_tuning->asz_3_ll_zone_id); + status = _populate_asz_struct(p_asz->asz_3, p_tuning->asz_3_ll_zone_id); + if (status != VL53L5_ERROR_NONE) + goto out; + + _build_asz_buffer(p_buffer, p_count, p_asz); +out: + return status; +} + +#ifdef VL53L8_RAD2PERP_CAL +static void _buf_r2p_offsets(uint32_t cal__optical_centre__x, + uint32_t cal__optical_centre__y, + int32_t *pr2p_init__centre_offset__x, + int32_t *pr2p_init__centre_offset__y) +{ + + int32_t fmt_x = (int32_t)cal__optical_centre__x; + int32_t fmt_y = (int32_t)cal__optical_centre__y; + int32_t offset_x = 0; + int32_t offset_y = 0; + + if (fmt_x > 0 && fmt_y > 0) { + + offset_x = 16 * ((fmt_x + 8) / 16); + offset_y = 16 * ((fmt_y + 8) / 16); + + offset_x -= fmt_x; + offset_y -= fmt_y; + } + + *pr2p_init__centre_offset__x = offset_x - 62; + *pr2p_init__centre_offset__y = offset_y - 62; +} + +static uint32_t _buf_r2p_common(uint32_t dpos2, + uint32_t sq_spad_pitch, + uint64_t sq_efl) +{ + + uint64_t tans2 = 0U; + uint32_t tmp = 0U; + uint32_t r2p_coeff = 0U; + + tans2 = MUL_32X32_64(dpos2, sq_spad_pitch); + tans2 = tans2 << 20U; + tans2 += (sq_efl >> 1U); + tans2 = DIV_64D64_64(tans2, sq_efl); + + tans2 += 1 << 24U; + if (tans2 > (uint64_t)VL53L5_UINT32_MAX) + tans2 = (uint64_t)VL53L5_UINT32_MAX; + + tmp = INTEGER_SQRT((uint32_t)tans2); + r2p_coeff = (1U << 24U) + (tmp >> 1U); + r2p_coeff /= tmp; + + return r2p_coeff; +} + +static uint32_t _buf_r2p_calc3(uint32_t zone_id, + uint32_t grid_size, + uint32_t grid_pitch, + int32_t x_offset_spads, + int32_t y_offset_spads, + uint32_t spad_pitch_um, + uint32_t efl_um) +{ + + uint32_t col = zone_id % grid_size; + uint32_t row = zone_id / grid_size; + int32_t xs = 0; + int32_t ys = 0; + int32_t xe = 0; + int32_t ye = 0; + int32_t x = 0; + int32_t y = 0; + int32_t hpos2 = 0; + int32_t vpos2 = 0; + uint32_t dpos2 = 0; + uint32_t dpos2_zone = 0U; + uint32_t no_of_spads = grid_pitch * grid_pitch; + uint32_t sq_spad_pitch = spad_pitch_um * spad_pitch_um; + uint64_t sq_efl = MUL_32X32_64(efl_um, efl_um); + uint32_t r2p_coeff = 0U; + + xs = ((int32_t)(col * grid_pitch) * 4) + x_offset_spads; + ys = ((int32_t)(row * grid_pitch) * 4) + y_offset_spads; + xe = xs + 4 * (int32_t)grid_pitch; + ye = ys + 4 * (int32_t)grid_pitch; + + for (y = ys; y < ye; y += 4) { + + vpos2 = y * y; + + for (x = xs; x < xe; x += 4) { + hpos2 = x * x; + dpos2 = (uint32_t)(hpos2) + (uint32_t)vpos2; + + dpos2_zone += (uint32_t)dpos2; + } + } + + dpos2_zone += (no_of_spads >> 1U); + dpos2_zone = dpos2_zone / no_of_spads; + + r2p_coeff = _buf_r2p_common(dpos2_zone, sq_spad_pitch, sq_efl); + + return r2p_coeff; +} + +static void _buf_r2p_base(uint32_t grid_size, uint32_t grid_pitch, + int32_t x_offset_spads, + int32_t y_offset_spads, uint32_t efl_um, + uint8_t *pbuffer) +{ + uint32_t z = 0U; + uint32_t r2p_coeff = 0U; + uint8_t *buff = pbuffer; + + for (z = 0U; z < (grid_size * grid_size); z++) { + r2p_coeff = 4096U; + r2p_coeff = _buf_r2p_calc3(z, grid_size, grid_pitch, + x_offset_spads, + y_offset_spads, + VL53L8_K_R2P_SPAD_PITCH_UM, + efl_um); + vl53l5_encode_uint16_t((uint16_t)r2p_coeff, BYTE_2, buff); + buff += BYTE_2; + } +} + +static void _perform_buf_r2p(uint32_t r2p_optical_centre_x, + uint32_t r2p_optical_centre_y, + uint8_t *pbuffer) +{ + uint32_t lens_efl_um = VL53L8_K_R2P_DEF_LENS_UM; + int32_t r2p_init__centre_offset__x = -62; + int32_t r2p_init__centre_offset__y = -62; + uint8_t *buff = pbuffer; + + _buf_r2p_offsets(r2p_optical_centre_x, + r2p_optical_centre_y, + &r2p_init__centre_offset__x, + &r2p_init__centre_offset__y); + + _buf_r2p_base(8U, 4U, r2p_init__centre_offset__x, + r2p_init__centre_offset__y, lens_efl_um, + &buff[VL53L8_K_R2P_GRID_8X8_DATA_SCALE_FACTOR_OFF]); + + _buf_r2p_base(4U, 8U, r2p_init__centre_offset__x, + r2p_init__centre_offset__y, lens_efl_um, + &buff[VL53L8_K_R2P_GRID_4X4_DATA_SCALE_FACTOR_OFF]); + +} + +static int _get_optical_center(struct vl53l8_k_module_t *p_module, + uint8_t *cal__optical_centre__x, + uint8_t *cal__optical_centre__y) +{ + int32_t status = VL53L5_ERROR_NONE; + int buffer[] = {VL53L5_CAL_OPTICAL_CENTRE_BH}; + + status = vl53l5_get_device_parameters(&p_module->stdev, buffer, 1); + + if (status != VL53L5_ERROR_NONE) + goto out; + + status = vl53l5_decode_calibration_data( + &p_module->stdev, + &p_module->calibration.cal_data, + p_module->stdev.host_dev.p_comms_buff, + p_module->stdev.host_dev.comms_buff_count); + + *cal__optical_centre__x = + p_module->calibration.cal_data.core.cal_optical_centre.cal__optical_centre__x; + *cal__optical_centre__y = + p_module->calibration.cal_data.core.cal_optical_centre.cal__optical_centre__y; +out: + return status; +} + +static int _perform_rad2perp_calibration(struct vl53l8_k_module_t *p_module) +{ + int32_t status = VL53L5_ERROR_NONE; + uint8_t cal__optical_centre__x = 0; + uint8_t cal__optical_centre__y = 0; + uint8_t r2p_buffer[] = VL53L8_K_RAD2PERP_DEFAULT; + + status = _get_optical_center(p_module, + &cal__optical_centre__x, + &cal__optical_centre__y); + if (status != VL53L5_ERROR_NONE) + goto out; + + _perform_buf_r2p(cal__optical_centre__x, cal__optical_centre__y, + r2p_buffer); + + status = vl53l5_set_device_parameters(&p_module->stdev, + r2p_buffer, sizeof(r2p_buffer)); + +out: + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } + + return status; +} +#endif +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE +int vl53l8_ioctl_set_glare_filter_tuning(struct vl53l8_k_module_t *p_module, + void __user *p) +{ + int32_t status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + vl53l8_k_log_debug("Lock"); + mutex_lock(&p_module->mutex); + + status = _check_state(p_module, VL53L8_STATE_INITIALISED); + if (status != VL53L5_ERROR_NONE) + goto out_state; + + status = copy_from_user(&p_module->gf_tuning, p, + sizeof(struct vl53l8_k_glare_filter_tuning_t)); + if (status != VL53L5_ERROR_NONE) { + status = VL53L8_K_ERROR_FAILED_TO_COPY_CALIBRATION; + goto out; + } + + vl53l8_k_glare_detect_init(&p_module->gf_tuning); + +out: + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } + +out_state: + vl53l8_k_log_debug("Unlock"); + mutex_unlock(&p_module->mutex); + + LOG_FUNCTION_END(status); + return status; +} + +int vl53l8_ioctl_set_transfer_speed_hz(struct vl53l8_k_module_t *p_module, + void __user *p) +{ + int32_t status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + vl53l8_k_log_debug("Lock"); + mutex_lock(&p_module->mutex); + + status = _check_state(p_module, VL53L8_STATE_INITIALISED); + if (status != VL53L5_ERROR_NONE) + goto out_state; + + status = copy_from_user(&p_module->transfer_speed_hz, p, + sizeof(unsigned int)); + + vl53l8_k_log_debug("transfer hz %d", p_module->transfer_speed_hz); + + + if (status != VL53L5_ERROR_NONE) { + status = VL53L8_K_ERROR_FAILED_TO_COPY_CALIBRATION; + goto out; + } + +out: + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("Failed: %d", status); + } + +out_state: + vl53l8_k_log_debug("Unlock"); + mutex_unlock(&p_module->mutex); + + LOG_FUNCTION_END(status); + return status; + +} +#endif + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +int vl53l8_set_transfer_speed_hz(struct vl53l8_k_module_t *p_module, unsigned int freq) +{ + int32_t status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + p_module->transfer_speed_hz = freq; + + vl53l8_k_log_debug("transfer hz %d", p_module->transfer_speed_hz); + + LOG_FUNCTION_END(status); + return status; +} +#endif diff --git a/drivers/sensors/vl53l8/src/vl53l8_k_module.c b/drivers/sensors/vl53l8/src/vl53l8_k_module.c new file mode 100644 index 000000000000..ee4c3b29833d --- /dev/null +++ b/drivers/sensors/vl53l8/src/vl53l8_k_module.c @@ -0,0 +1,2842 @@ +/******************************************************************************* +* Copyright (c) 2023, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l8_k_module.h" +#include "vl53l8_k_driver_config.h" +#include "vl53l8_k_gpio_utils.h" +#include "vl53l8_k_logging.h" +#include "vl53l8_k_ioctl_controls.h" +#include "vl53l8_k_error_converter.h" +#include "vl53l8_k_error_codes.h" +#include "vl53l8_k_ioctl_codes.h" +#include "vl53l8_k_version.h" +#include "vl53l5_results_config.h" +#include "vl53l5_platform.h" +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE +#include "vl53l5_platform_init.h" +#endif +#ifdef VL53L8_INTERRUPT +#include "vl53l8_k_interrupt.h" +#endif + + +#if defined(STM_VL53L5_SUPPORT_SEC_CODE) +#include +#include +#include "vl53l8_k_range_wait_handler.h" + +#include +#include +#include + +#define NUM_OF_ZONE 64 +#define NUM_OF_TARGET 1 + +#ifdef VL53L8_INTERRUPT +#define MAX_READ_COUNT 45 +#else +#define MAX_READ_COUNT 10 +#endif + +#define ENTER 1 +#define END 2 + +#define NUM_OF_PRINT 8 + +#define FAIL_GET_RANGE -1 +#define EXCEED_AMBIENT -2 +#define EXCEED_XTALK -3 + +#define MAX_FAILING_ZONES_LIMIT_FAIL -452581885 +#define NO_VALID_ZONES -452516349 + +#define FAC_CAL 1 +#define USER_CAL 2 + +#define DEFAULT_SAMPING_RATE 15 +#define MIN_SAMPLING_RATE 5 +#define MAX_SAMPLING_RATE 30 + +#define DEFAULT_INTEGRATION_TIME 5000 +#define MIN_INTEGRATION_TIME 2000 +#define MAX_INTEGRATION_TIME 15000 + +#define MODULE_NAME "range_sensor" + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +static const struct of_device_id vl53l8_k_match_table[] = { + { .compatible = "vl53l8",}, + { .compatible = VL53L8_K_DRIVER_NAME,}, + {}, +}; +#endif + +#ifdef CONFIG_SENSORS_VL53L8_SLSI +extern int sensors_register(struct device **dev, void *drvdata, + struct device_attribute *attributes[], char *name); +extern int sensordump_notifier_register(struct notifier_block *nb); +#endif + +#define TEST_300_MM_MAX_ZONE 64 +#define TEST_500_MM_MAX_ZONE 16 + +#endif + +static struct spi_device *vl53l8_k_spi_device; + +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE +static struct vl53l8_k_module_t *module_table[VL53L8_CFG_MAX_DEV]; +#endif + +static const struct spi_device_id vl53l8_k_spi_id[] = { + { VL53L8_K_DRIVER_NAME, 0 }, + { }, +}; +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +#ifdef CONFIG_SENSORS_VL53L8_SUPPORT_RESUME_WORK +void vl53l8_initialize_state_for_resume(struct vl53l8_k_module_t *p_module) +{ + int status = 0; + + usleep_range(1000, 1100); + p_module->stdev.host_dev.p_fw_buff = NULL; + status = vl53l5_init(&p_module->stdev); + if (status < 0) { + vl53l8_k_log_error("resume init err: %d\n", status); +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG + vl53l8_last_error_counter(p_module, status); +#endif + p_module->stdev.last_dev_error = VL53L8_RESUME_INIT_ERROR; + return; + } + + p_module->stdev.last_dev_error = VL53L5_ERROR_NONE; + p_module->stdev.status_probe = STATUS_OK; + p_module->last_driver_error = STATUS_OK; + + p_module->state_preset = VL53L8_STATE_INITIALISED; + p_module->power_state = VL53L5_POWER_STATE_HP_IDLE; + vl53l8_k_log_info("VL53L8_STATE_INITIALISED"); +} + +static void resume_work_func(struct work_struct *work) +{ + struct vl53l8_k_module_t *p_module = container_of((struct work_struct *)work, + struct vl53l8_k_module_t, resume_work); + + vl53l8_initialize_state_for_resume(p_module); + vl53l8_ioctl_set_power_mode(p_module, NULL, VL53L5_POWER_STATE_ULP_IDLE); +} +#endif +static int vl53l8_suspend(struct device *dev) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + int status; +#ifdef CONFIG_SENSORS_VL53L8_SUPPORT_RESUME_WORK + cancel_work_sync(&p_module->resume_work); +#endif + vl53l8_k_log_info("fac en %d, state %d", + p_module->enabled, + p_module->state_preset); + + p_module->suspend_state = true; + + if (p_module->state_preset >= VL53L8_STATE_INITIALISED) { + vl53l8_k_log_info("force stop"); + status = vl53l8_ioctl_stop(p_module); + if (status != STATUS_OK) { + p_module->last_driver_error = VL53L8_SUSPEND_IOCTL_STOP_ERROR; +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG + vl53l8_last_error_counter(p_module, p_module->last_driver_error); +#endif + vl53l8_k_log_info("force stop fail"); + } else { + vl53l8_k_log_info("stop success"); + } + p_module->force_suspend_count++; + } + + vl53l8_power_onoff(p_module, false); + + return 0; +} + +static int vl53l8_resume(struct device *dev) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + + p_module->suspend_state = false; + vl53l8_k_log_info("err %d, force stop count %u", p_module->last_driver_error, p_module->force_suspend_count); + + vl53l8_power_onoff(p_module, true); + +#ifdef CONFIG_SENSORS_VL53L8_SUPPORT_RESUME_WORK + schedule_work(&p_module->resume_work); +#else + vl53l8_ioctl_set_power_mode(p_module, NULL, VL53L5_POWER_STATE_ULP_IDLE); +#endif + + return 0; +} + +static const struct dev_pm_ops vl53l8_pm_ops = { + .suspend = vl53l8_suspend, + .resume = vl53l8_resume, +}; + +int set_sampling_rate(struct vl53l8_k_module_t *p_module, uint32_t sampling_rate) +{ + if ((sampling_rate < MIN_SAMPLING_RATE) + || (sampling_rate > MAX_SAMPLING_RATE)) { + vl53l8_k_log_error("Out of Rate"); + return -EINVAL; + } + + p_module->rate = sampling_rate; + return STATUS_OK; +} + +int set_integration_time(struct vl53l8_k_module_t *p_module, uint32_t integration_time) +{ + if ((integration_time < MIN_INTEGRATION_TIME) + || (integration_time > MAX_INTEGRATION_TIME)) { + vl53l8_k_log_error("Out of Integration"); + return -EINVAL; + } + + p_module->integration = integration_time; + return STATUS_OK; +} +#endif + + +static struct spi_driver vl53l8_k_spi_driver = { + .driver = { + .name = VL53L8_K_DRIVER_NAME, + .owner = THIS_MODULE, +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + .of_match_table = vl53l8_k_match_table, + .pm = &vl53l8_pm_ops +#endif + + }, + .probe = vl53l8_k_spi_probe, +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + .shutdown = vl53l8_k_spi_shutdown, +#endif + .remove = vl53l8_k_spi_remove, + .id_table = vl53l8_k_spi_id, +}; + +static const struct file_operations ranging_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = vl53l8_k_ioctl, + .open = vl53l8_k_open, + .release = vl53l8_k_release +}; + +MODULE_DEVICE_TABLE(of, vl53l8_k_match_table); +MODULE_DEVICE_TABLE(spi, vl53l8_k_spi_id); + +static DEFINE_MUTEX(dev_table_mutex); + +struct vl53l8_k_module_t *global_p_module; +#ifdef CONFIG_SENSORS_VL53L8_SUPPORT_KERNEL_INTERFACE + +enum vl53l8_external_control { + VL53L8_EXT_START, + VL53L8_EXT_STOP, + VL53L8_EXT_GET_RANGE, + VL53L8_EXT_SET_SAMPLING_RATE, + VL53L8_EXT_SET_INTEG_TIME, +}; + +int vl53l8_ext_control(enum vl53l8_external_control input, void *data, u32 *size) +{ + int status = STATUS_OK; + uint32_t value = 0; + + if (global_p_module == NULL) { + if ((input == VL53L8_EXT_START) + || (input == VL53L8_EXT_STOP)) + vl53l8_k_log_error("probe failed"); + if (size != NULL) + *size = 0; + return -EPERM; + } else if ((global_p_module->last_driver_error == VL53L8_PROBE_FAILED) + || (global_p_module->last_driver_error == VL53L8_DELAYED_LOAD_FIRMWARE) + || (global_p_module->last_driver_error == VL53L8_SHUTDOWN) + || (global_p_module->suspend_state == true)) { + if ((input == VL53L8_EXT_START) + || (input == VL53L8_EXT_STOP)) + vl53l8_k_log_error("failed %d", global_p_module->last_driver_error); + else + if (size) + *size = 0; + if (global_p_module->suspend_state == true) + vl53l8_k_log_error("cmd %d is called in suspend", input); + return -EPERM; + } + + switch (input) { + case VL53L8_EXT_START: + vl53l8_k_log_debug("Lock"); + mutex_lock(&global_p_module->mutex); + status = vl53l8_ioctl_start(global_p_module); + if ((status != STATUS_OK) || (global_p_module->last_driver_error != STATUS_OK)) { + status = vl53l8_ioctl_init(global_p_module); + vl53l8_k_log_error("fail reset %d", status); + status = vl53l8_ioctl_start(global_p_module); + } + + if (status != STATUS_OK) { + vl53l8_k_log_error("start err"); + mutex_unlock(&global_p_module->mutex); + goto out_state; + } else { + global_p_module->enabled = 1; + mutex_unlock(&global_p_module->mutex); + } + break; + + case VL53L8_EXT_STOP: + vl53l8_k_log_debug("Lock"); + mutex_lock(&global_p_module->mutex); + global_p_module->enabled = 0; + status = vl53l8_ioctl_stop(global_p_module); + if ((status != STATUS_OK) || (global_p_module->last_driver_error != STATUS_OK)) { + status = vl53l8_ioctl_init(global_p_module); + vl53l8_k_log_error("reset err %d", status); + status = vl53l8_ioctl_stop(global_p_module); + } + + mutex_unlock(&global_p_module->mutex); + if (status != STATUS_OK) { + vl53l8_k_log_error("stop err"); + goto out_state; + } else + vl53l8_k_log_info("stop"); + break; + + case VL53L8_EXT_GET_RANGE: + if ((global_p_module->state_preset != VL53L8_STATE_RANGING) + || (data == NULL)) { + goto out_state; + } + status = vl53l8_ioctl_get_range(global_p_module, NULL); + if (status != STATUS_OK) { + *size = 0; + vl53l8_k_log_error("get_range err %d", *size); + goto out_state; + } else { + memcpy(data, &global_p_module->af_range_data, sizeof(global_p_module->af_range_data)); + *size = sizeof(global_p_module->af_range_data); + } + break; + + case VL53L8_EXT_SET_SAMPLING_RATE: + memcpy(&value, data, sizeof(uint32_t)); + status = set_sampling_rate(global_p_module, value); + break; + + case VL53L8_EXT_SET_INTEG_TIME: + memcpy(&value, data, sizeof(uint32_t)); + status = set_integration_time(global_p_module, value); + if (status != VL53L5_ERROR_NONE) + break; + + status = vl53l8_ioctl_set_integration_time_us(global_p_module, global_p_module->integration); + if (status != VL53L5_ERROR_NONE) + vl53l8_k_log_error("Fail %d", status); + break; + + default: + vl53l8_k_log_error("invalid %d", input); + break; + } + + return status; +out_state: + + vl53l8_k_log_error("Err %d", status); + return status; +} +EXPORT_SYMBOL_GPL(vl53l8_ext_control); +#endif + +static void memory_release(struct kref *kref) +{ + struct vl53l8_k_module_t *p_module = + container_of(kref, struct vl53l8_k_module_t, spi_info.ref); + + LOG_FUNCTION_START(""); + + kfree(p_module); + + LOG_FUNCTION_END(0); +} + +static int vl53l8_parse_dt(struct device *dev, + struct vl53l8_k_module_t *p_module) +{ + struct device_node *np = dev->of_node; + int status = 0; +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + vl53l8_k_log_info("start"); +#else + vl53l8_k_log_debug("start"); +#endif + + if (of_find_property(np, "spi-cpha", NULL)) + p_module->spi_info.device->mode |= SPI_CPHA; + if (of_find_property(np, "spi-cpol", NULL)) + p_module->spi_info.device->mode |= SPI_CPOL; + + p_module->comms_type = VL53L8_K_COMMS_TYPE_SPI; + + p_module->gpio.interrupt = -1; + +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE + p_module->gpio.comms_select = -1; + p_module->gpio.power_enable_owned = 0; + p_module->gpio.power_enable = + of_get_named_gpio(np, "stm,power_enable", 0); + if (gpio_is_valid(p_module->gpio.power_enable)) { + status = gpio_request(p_module->gpio.power_enable, + "vl53l8_k_pwren"); + if (!status) { + vl53l8_k_log_debug( + "Set gpio %d direction", + p_module->gpio.power_enable); + p_module->gpio.power_enable_owned = 1; + status = gpio_direction_output( + p_module->gpio.power_enable, 0); + if (status) { + vl53l8_k_log_error( + "Set direction failed %d: %d", + p_module->gpio.power_enable, status); + } + } else + vl53l8_k_log_error("Request gpio failed %d: %d", + p_module->gpio.power_enable, status); + } else + vl53l8_k_log_info( + "Power enable is not configured."); + + p_module->gpio.low_power_owned = 0; + p_module->gpio.low_power = of_get_named_gpio(np, "stm,low_power", 0); + if (gpio_is_valid(p_module->gpio.low_power)) { + status = gpio_request( + p_module->gpio.low_power, "vl53l8_k_lp"); + if (!status) { + vl53l8_k_log_debug( + "Set gpio %d direction", + p_module->gpio.low_power); + p_module->gpio.low_power_owned = 1; + status = gpio_direction_output( + p_module->gpio.low_power, 0); + if (status) { + vl53l8_k_log_error( + "Set direction failed %d: %d", + p_module->gpio.low_power, status); + } + } else + vl53l8_k_log_error("Request gpio failed %d: %d", + p_module->gpio.low_power, status); + } else + vl53l8_k_log_info( + "The GPIO setting of Low power is not configured."); + + p_module->gpio.comms_select_owned = 0; + p_module->gpio.comms_select = of_get_named_gpio( + np, "stm,comms_select", 0); + if (gpio_is_valid(p_module->gpio.comms_select)) { + status = gpio_request( + p_module->gpio.comms_select, "vl53l8_k_cs"); + if (!status) { + vl53l8_k_log_debug( + "Set gpio %d direction", + p_module->gpio.comms_select); + p_module->gpio.comms_select_owned = 1; + status = gpio_direction_output( + p_module->gpio.comms_select, 0); + if (status) { + vl53l8_k_log_error( + "Set direction failed gpio %d: %d", + p_module->gpio.comms_select, status); + } + } else + vl53l8_k_log_error("Request gpio failed %d: %d", + p_module->gpio.comms_select, status); + } else + vl53l8_k_log_info( + "The GPIO setting of comms select is not configured."); +#endif +#ifdef VL53L8_INTERRUPT + p_module->gpio.interrupt_owned = 0; + p_module->gpio.interrupt = of_get_named_gpio( + np, "stm,interrupt", 0); + if (gpio_is_valid(p_module->gpio.interrupt)) { + status = gpio_request( + p_module->gpio.interrupt, "vl53l8_k_interrupt"); + if (!status) { + vl53l8_k_log_debug( + "Set gpio %d direction", + p_module->gpio.interrupt); + p_module->gpio.interrupt_owned = 1; + status = gpio_direction_input( + p_module->gpio.interrupt); + if (status) { + p_module->gpio.interrupt_owned = 0; + vl53l8_k_log_error( + "Set input direction failed gpio %d: %d", + p_module->gpio.interrupt, status); + } + } else + vl53l8_k_log_error("Request gpio failed %d: %d", + p_module->gpio.interrupt, status); + } else + vl53l8_k_log_info( + "The GPIO setting of comms select is not configured."); +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +#ifdef CONFIG_SEPARATE_IO_CORE_POWER + if (of_property_read_string_index(np, "stm,core_vdd", 0, + (const char **)&p_module->corevdd_vreg_name)) { + p_module->corevdd_vreg_name = NULL; + } + vl53l8_k_log_info("%s core_vdd %s\n", __func__, p_module->corevdd_vreg_name); +#endif + if (of_property_read_string_index(np, "stm,io_vdd", 0, + (const char **)&p_module->iovdd_vreg_name)) { + p_module->iovdd_vreg_name = NULL; + } + vl53l8_k_log_info("%s io_vdd %s\n", __func__, p_module->iovdd_vreg_name); + + if (of_property_read_string_index(np, "stm,a_vdd", 0, + (const char **)&p_module->avdd_vreg_name)) { + p_module->avdd_vreg_name = NULL; + } + vl53l8_k_log_info("%s: a_vdd %s\n", __func__, p_module->avdd_vreg_name); +#endif + + of_property_read_string(np, "stm,firmware_name", + &p_module->firmware_name); +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + if (!p_module->firmware_name) { + vl53l8_k_log_error( + "%s firmware is not set", __func__); + p_module->firmware_name = "range_sensor/vl53l8.bin"; + } + + of_property_read_string(np, "stm,genshape_name", + &p_module->genshape_name); + if (!p_module->genshape_name) { + vl53l8_k_log_error( + "%s generic is not set", __func__); + p_module->genshape_name = "range_sensor/generic_xtalk_shape.bin"; + } + + if (of_property_read_u32(np, "stm,fac_rotation_mode", &p_module->fac_rotation_mode)) + p_module->fac_rotation_mode = 0; + else + vl53l8_k_log_info("fac_rotation %d", p_module->fac_rotation_mode); + + if (of_property_read_u32(np, "stm,rotation_mode", &p_module->rotation_mode)) + p_module->rotation_mode = 0; + else + vl53l8_k_log_info("rotation %d", p_module->rotation_mode); + + if (of_property_read_u32(np, "stm,integration_time", &p_module->integration_init)) + p_module->integration_init = DEFAULT_INTEGRATION_TIME; + else + vl53l8_k_log_info("integ_t %d", p_module->integration_init); + + if (of_property_read_u32(np, "stm,sampling_rate", &p_module->rate_init)) + p_module->rate_init = DEFAULT_SAMPING_RATE; + else + vl53l8_k_log_info("rate %d", p_module->rate); + + p_module->rate = p_module->rate_init; + p_module->integration = p_module->integration_init; + + /* ASZ Setting*/ + if (of_property_read_u32(np, "stm,asz_0_zone_id", &p_module->asz_0_ll_zone_id)) + p_module->asz_0_ll_zone_id = 16; + else + vl53l8_k_log_info("asz_0 %d", p_module->asz_0_ll_zone_id); + if (of_property_read_u32(np, "stm,asz_1_zone_id", &p_module->asz_1_ll_zone_id)) + p_module->asz_1_ll_zone_id = 18; + else + vl53l8_k_log_info("asz_1 %d", p_module->asz_1_ll_zone_id); + if (of_property_read_u32(np, "stm,asz_2_zone_id", &p_module->asz_2_ll_zone_id)) + p_module->asz_2_ll_zone_id = 20; + else + vl53l8_k_log_info("asz_2 %d", p_module->asz_2_ll_zone_id); + if (of_property_read_u32(np, "stm,asz_3_zone_id", &p_module->asz_3_ll_zone_id)) + p_module->asz_3_ll_zone_id = 22; + else + vl53l8_k_log_info("asz_3 %d", p_module->asz_3_ll_zone_id); +#endif + + return status; +} + +static void vl53l8_k_clean_up_spi(void) +{ + LOG_FUNCTION_START(""); + if (vl53l8_k_spi_device != NULL) { + vl53l8_k_log_debug("Unregistering spi device"); + spi_unregister_device(vl53l8_k_spi_device); + } + LOG_FUNCTION_END(0); +} + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +void vl53l8_k_re_init(struct vl53l8_k_module_t *p_module) +{ + if (p_module->last_driver_error == VL53L8_PROBE_FAILED + || p_module->last_driver_error == VL53L8_DELAYED_LOAD_FIRMWARE) { + int status; + + vl53l8_k_log_error("last err %d", p_module->last_driver_error); + p_module->last_driver_error = STATUS_OK; + status = vl53l8_ioctl_init(p_module); + + p_module->enabled = 0; + if (status != STATUS_OK) { + vl53l8_k_log_error("fail err %d", status); + p_module->last_driver_error = VL53L8_PROBE_FAILED; +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG + vl53l8_last_error_counter(p_module, p_module->last_driver_error); +#endif + vl53l8_power_onoff(p_module, false); + } else { + status = vl53l8_ioctl_stop(p_module); + if (status != STATUS_OK) + vl53l8_k_log_error("stop err"); + } + } +} + +int vl53l8_check_calibration_condition(struct vl53l8_k_module_t *p_module, int seq) +{ + int status = STATUS_OK, prev_state = VL53L8_STATE_RANGING; + int count = 0; + int ret = 0; + int max_ambient = 0, max_xtalk = 0, max_peaksignal = 0; + int i = 0, j = 0, idx = 0; + + vl53l8_k_log_info("state_preset %d, power_state %d", p_module->state_preset, p_module->power_state); + if (p_module->state_preset != VL53L8_STATE_RANGING) { + prev_state = p_module->state_preset; + + status = vl53l8_ioctl_start(p_module); + if (status != STATUS_OK) { + status = vl53l8_ioctl_init(p_module); + vl53l8_k_log_info("restart %d", status); + status = vl53l8_ioctl_start(p_module); + } + + if (status == STATUS_OK) { + p_module->enabled = 1; + } else { + vl53l8_k_log_error("start err"); + vl53l8_k_store_error(p_module, status); +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG + vl53l8_last_error_counter(p_module, p_module->last_driver_error); +#endif + ret = FAIL_GET_RANGE; + goto out_result; + } + } + msleep(180); + + memset(&p_module->range_data, 0, sizeof(p_module->range_data)); + + while ((p_module->range.count == 0) && (count < MAX_READ_COUNT)) { + usleep_range(10000, 10100); + ++count; + } + + memcpy(&p_module->range_data.per_zone_results, + &p_module->range.data.tcpm_0_patch.per_zone_results, + sizeof(struct vl53l5_range_per_zone_results_t)); + memcpy(&p_module->range_data.per_tgt_results, + &p_module->range.data.tcpm_0_patch.per_tgt_results, + sizeof(struct vl53l5_range_per_tgt_results_t)); + memcpy(&p_module->range_data.d16_per_target_data, + &p_module->range.data.tcpm_0_patch.d16_per_target_data, + sizeof(struct vl53l5_d16_per_tgt_results_t)); + + for (i = 0; i < NUM_OF_PRINT; i++) { + for (j = 0; j < NUM_OF_PRINT; j++) { + idx = (i * NUM_OF_PRINT + j); + if (seq == END && max_xtalk < (p_module->calibration.cal_data.core.pxtalk_grid_rate.cal__grid_data__rate_kcps_per_spad[idx] >> 11)) + max_xtalk = p_module->calibration.cal_data.core.pxtalk_grid_rate.cal__grid_data__rate_kcps_per_spad[idx] >> 11; + if (max_peaksignal < (p_module->range_data.per_tgt_results.peak_rate_kcps_per_spad[idx * NUM_OF_TARGET] >> 11)) + max_peaksignal = p_module->range_data.per_tgt_results.peak_rate_kcps_per_spad[idx * NUM_OF_TARGET] >> 11; + if (max_ambient < (p_module->range_data.per_zone_results.amb_rate_kcps_per_spad[idx] >> 11)) + max_ambient = p_module->range_data.per_zone_results.amb_rate_kcps_per_spad[idx] >> 11; + } + } + + p_module->max_peak_signal = max_peaksignal; + + if (max_ambient > 35) + ret = EXCEED_AMBIENT; + else if (seq == END && max_xtalk > 1200) + ret = EXCEED_XTALK; + + vl53l8_k_log_info("max: ambient %d xtalk %d peak %d ret %d\n", max_ambient, max_xtalk, max_peaksignal, ret); + +out_result: + if (prev_state > VL53L8_STATE_LOW_POWER) { + p_module->enabled = 0; + vl53l8_ioctl_stop(p_module); + } + + return ret; +} + + +int vl53l8_perform_open_calibration(struct vl53l8_k_module_t *p_module) +{ + int ret; + + vl53l8_k_log_info("Do open CAL\n"); + vl53l8_ioctl_set_power_mode(p_module, NULL, VL53L5_POWER_STATE_HP_IDLE); + ret = vl53l8_perform_calibration(p_module, 1); + usleep_range(5000, 5100); + vl53l8_ioctl_set_power_mode(p_module, NULL, VL53L5_POWER_STATE_ULP_IDLE); + + return ret; +} +#if IS_ENABLED(CONFIG_SENSORS_VL53L8_QCOM) || IS_ENABLED(CONFIG_SENSORS_VL53L8_SLSI) +#define DISTANCE_300MM_MIN_SPEC 255 +#define DISTANCE_300MM_MAX_SPEC 345 + +static ssize_t vl53l8_distance_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + uint32_t i = 0, j = 0, idx = 0; + int min = 0, max = 0, avg = 0; + int status = STATUS_OK; +#ifdef VL53L8_INTERRUPT + int count = 0; +#endif + + p_module->read_data_valid = false; +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + if ((p_module->state_preset != VL53L8_STATE_RANGING) + || (p_module->last_driver_error == VL53L8_PROBE_FAILED)) { + vl53l8_k_log_error("state %d err %d", p_module->state_preset, p_module->last_driver_error); +#ifdef VL53L5_TEST_ENABLE + msleep(50); + panic("probe err %d", p_module->last_driver_error); +#endif + goto out; + } +#endif + +#ifdef VL53L8_INTERRUPT + while ((p_module->range.count == 0) && (count < MAX_READ_COUNT)) { + usleep_range(10000, 10100); + ++count; + } +#endif + +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + vl53l8_k_log_debug("Lock"); + mutex_lock(&p_module->mutex); + + vl53l8_k_log_info("get_range %d", p_module->fac_rotation_mode); + status = vl53l8_ioctl_get_range(p_module, NULL); + + vl53l8_k_log_debug("unLock"); + mutex_unlock(&p_module->mutex); +#endif + + if (status != STATUS_OK) { + vl53l8_k_log_info("get_range err %d", status); +#ifdef VL53L5_TCDM_ENABLE + + _tcdm_dump(p_module); + +#endif +#ifdef VL53L5_TEST_ENABLE + msleep(50); + panic("get_range err %d", p_module->last_driver_error); +#endif + } else { + vl53l8_k_log_info("get_range %d\n", p_module->print_counter); + p_module->read_data_valid = true; + } +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +out: +#endif + mutex_lock(&p_module->mutex); + memcpy(&p_module->range_data.per_zone_results, + &p_module->range.data.tcpm_0_patch.per_zone_results, + sizeof(struct vl53l5_range_per_zone_results_t)); + memcpy(&p_module->range_data.per_tgt_results, + &p_module->range.data.tcpm_0_patch.per_tgt_results, + sizeof(struct vl53l5_range_per_tgt_results_t)); + memcpy(&p_module->range_data.d16_per_target_data, + &p_module->range.data.tcpm_0_patch.d16_per_target_data, + sizeof(struct vl53l5_d16_per_tgt_results_t)); + mutex_unlock(&p_module->mutex); + + if (p_module->read_data_valid) { + for (i = 0; i < p_module->print_counter; i++) { + for (j = 0; j < p_module->print_counter; j++) { + uint16_t confidence; + + idx = (i * p_module->print_counter + j); + confidence = (p_module->range_data.d16_per_target_data.depth16[idx] & 0xE000U) >> 13; + + if (0 == confidence || 5 <= confidence) + p_module->data[idx] = p_module->range_data.d16_per_target_data.depth16[idx] & 0x1FFFU; + else + p_module->data[idx] = 0; + } + } + } else { + for (i = 0 ; i < NUM_OF_ZONE; i++) + p_module->data[i] = -1; + } + + vl53l8_rotate_report_data_int32(p_module->data, p_module->fac_rotation_mode); + + min = max = p_module->data[1]; + + for (i = 1; i < NUM_OF_ZONE; i++) { + if ((i == 7) || (i == 56) || (i == 63)) + continue; + if (p_module->data[i] < min) + min = p_module->data[i]; + if (max < p_module->data[i]) + max = p_module->data[i]; + avg += p_module->data[i]; + } + + for (i = 0; i < 64; i += 8) { + vl53l8_k_log_info("%d, %d, %d, %d, %d, %d, %d, %d\n", + p_module->data[i], p_module->data[i+1], p_module->data[i+2], + p_module->data[i+3], p_module->data[i+4], p_module->data[i+5], + p_module->data[i+6], p_module->data[i+7]); + } + + if (NUM_OF_ZONE - 4 != 0) + avg = avg / (NUM_OF_ZONE - 4); + else + avg = 0; + + i = j = 0; + + j += snprintf(buf + j, PAGE_SIZE - j, "Distance,%d,%d,%d,%d,%d,", + DISTANCE_300MM_MIN_SPEC, DISTANCE_300MM_MAX_SPEC, min, max, avg); + for (; i < TEST_300_MM_MAX_ZONE; i++) { + if ((i != 0) && (i != 7) && (i != 56) && (i != 63)) + j += snprintf(buf + j, PAGE_SIZE - j, "%d", p_module->data[i]); + else + j += snprintf(buf + j, PAGE_SIZE - j, "N"); + + if (i != p_module->number_of_zones - 1) + j += snprintf(buf + j, PAGE_SIZE - j, ","); + else + j += snprintf(buf + j, PAGE_SIZE - j, "\n"); + } + + return j; +} + + +static ssize_t vl53l8_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", p_module->enabled); +} + +static ssize_t vl53l8_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + + u8 val; + int status; + + + vl53l8_k_log_debug("Lock"); + mutex_lock(&p_module->mutex); + + vl53l8_k_re_init(p_module); + + if (p_module->last_driver_error <= VL53L8_PROBE_FAILED) { + vl53l8_k_log_error("enable err %d", p_module->last_driver_error); +#ifdef VL53L5_TEST_ENABLE + msleep(50); + panic("enable err %d", p_module->last_driver_error); +#endif + vl53l8_k_log_debug("unLock"); + mutex_unlock(&p_module->mutex); + return count; + } + + status = kstrtou8(buf, 10, &val); + + switch (val) { + case 1: + vl53l8_k_log_info("enable start\n"); + if (p_module->enabled < 0 || p_module->last_driver_error == VL53L8_PROBE_FAILED) + vl53l8_k_log_info("probe fail\n"); + else { + status = vl53l8_ioctl_start(p_module); + +#ifdef VL53L5_TEST_ENABLE + if (status == STATUS_OK) { + vl53l8_k_log_error("test for reset"); + status = -EPERM; + } +#endif + if (status != STATUS_OK) { + status = vl53l8_ioctl_init(p_module); + vl53l8_k_log_error("reset err %d", status); + status = vl53l8_ioctl_start(p_module); + } + + if (status == STATUS_OK) + p_module->enabled = 1; + else { + vl53l8_k_log_error("start err"); + vl53l8_k_store_error(p_module, status); +#ifdef VL53L5_TEST_ENABLE + msleep(50); + panic("enable err %d", p_module->last_driver_error); +#endif + } + } + vl53l8_k_log_info("enable done\n"); + break; + default: + vl53l8_k_log_info("disable start\n"); + if (p_module->enabled < 0 || p_module->last_driver_error == VL53L8_PROBE_FAILED) + vl53l8_k_log_info("probe err\n"); + else { + p_module->enabled = 0; + status = vl53l8_ioctl_stop(p_module); +#ifdef VL53L5_TEST_ENABLE + if (status == STATUS_OK) { + vl53l8_k_log_error("test for reset"); + status = -EPERM; + } +#endif + if (status != STATUS_OK) { + status = vl53l8_ioctl_init(p_module); + vl53l8_k_log_error("reset err%d", status); + status = vl53l8_ioctl_stop(p_module); + } + + if (status != STATUS_OK) { + vl53l8_k_store_error(p_module, status); +#ifdef VL53L5_TEST_ENABLE + msleep(50); + panic("disable err %d", p_module->last_driver_error); +#endif + } + + } + vl53l8_k_log_info("disable done\n"); + break; + } + + vl53l8_k_log_debug("unLock"); + mutex_unlock(&p_module->mutex); + return count; +} + +// JUSHIN : add for input sys for STM +static ssize_t vl53l8_calibration_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + + if (p_module->read_p2p_cal_data == true) + return snprintf(buf, PAGE_SIZE, "O\n"); + else + return snprintf(buf, PAGE_SIZE, "X\n"); +} + +int vl53l8_reset(struct vl53l8_k_module_t *p_module) +{ + int status = STATUS_OK; + + status = vl53l8_ioctl_set_power_mode(p_module, NULL, VL53L5_POWER_STATE_ULP_IDLE); + if (status != STATUS_OK) { + vl53l8_k_log_error("Set ulp err"); + goto out; + } + usleep_range(2000, 2100); + + status = vl53l8_ioctl_set_power_mode(p_module, NULL, VL53L5_POWER_STATE_HP_IDLE); + if (status != STATUS_OK) { + vl53l8_k_log_error("Set hp err"); + goto out; + } + + if (p_module->load_calibration) { + vl53l8_ioctl_read_generic_shape(p_module); + if (status != STATUS_OK) + vl53l8_k_log_error("generic set err"); + usleep_range(1000, 1100); + } + + usleep_range(1000, 1100); + return status; +out: + vl53l8_k_log_error("Reset err %d", status); + vl53l8_k_store_error(p_module, status); +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG + vl53l8_last_error_counter(p_module, p_module->last_driver_error); +#endif + return status; +} + +static ssize_t vl53l8_calibration_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + u8 val = 0; + int ret; + + ret = kstrtou8(buf, 10, &val); + + vl53l8_k_log_debug("Lock"); + mutex_lock(&p_module->mutex); + + switch (val) { + case 0: /* Calibration Load */ + vl53l8_k_re_init(p_module); + if (p_module->last_driver_error <= VL53L8_PROBE_FAILED) { + vl53l8_k_log_error("err %d", p_module->last_driver_error); +#ifdef VL53L5_TEST_ENABLE + panic("failed %d", p_module->last_driver_error); +#endif + mutex_unlock(&p_module->mutex); + return count; + } + break; + + case 1: /* Factory Calibration */ + vl53l8_k_log_info("Do CAL start"); + if (p_module->last_driver_error <= VL53L8_PROBE_FAILED) { + vl53l8_k_log_error("err %d", p_module->last_driver_error); + vl53l8_k_re_init(p_module); + } + + vl53l8_reset(p_module); + + ret = vl53l8_perform_calibration(p_module, 0); + vl53l8_k_log_info("Do CAL %d\n", p_module->load_calibration); + if (ret != STATUS_OK) { + vl53l8_k_log_info("Do CAL err %d\n", ret); + p_module->read_p2p_cal_data = false; + } else { + p_module->load_calibration = true; + ret = vl53l8_ioctl_read_p2p_calibration(p_module); + vl53l8_k_log_info("Do CAL Success\n"); + if (ret == STATUS_OK) + p_module->read_p2p_cal_data = true; + else + p_module->read_p2p_cal_data = false; + } + break; + + case 2: /* Cal Erase */ + vl53l8_k_log_info("Erase CAL\n"); + val = 0; + + mutex_lock(&p_module->cal_mutex); + + memset(&p_module->cal_data, 0, sizeof(struct vl53l8_cal_data_t)); + memcpy(p_module->cal_data.pcal_data, &val, 1); + p_module->cal_data.size = 1; + p_module->cal_data.cmd = CMD_WRITE_CAL_FILE; + + mutex_unlock(&p_module->cal_mutex); + + vl53l8_input_report(p_module, 2, p_module->cal_data.cmd); + + p_module->read_p2p_cal_data = false; + vl53l8_reset(p_module); + break; + + case 3: + ret = vl53l8_perform_open_calibration(p_module); + if (ret < 0) + vl53l8_k_log_error("Do open cal err\n"); + break; + + case 4: /* Calibration Load */ + ret = vl53l8_load_open_calibration(p_module); + if (ret < 0) + vl53l8_k_log_error("Load open cal err\n"); + break; + + default: + vl53l8_k_log_info("Not support %d\n", val); + break; + } + + ret = vl53l8_ioctl_stop(p_module); + if (ret != STATUS_OK) + vl53l8_k_log_error("stop err"); + + vl53l8_k_log_debug("unLock"); + mutex_unlock(&p_module->mutex); + + return count; +} + +#define CAL_XTALK_MIN_SPEC 0 +#define CAL_XTALK_MAX_SPEC 1200 + +static ssize_t vl53l8_cal_xtalk_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + uint32_t i = 0; + uint32_t j = 0; + uint32_t idx = 0; + int32_t min = 0; + int32_t max = 0; + int32_t avg = 0; + + for (i = 0; i < p_module->print_counter; i++) { + for (j = 0; j < p_module->print_counter; j++) { + idx = (i * p_module->print_counter + j); + if (p_module->read_p2p_cal_data == true) + p_module->data[idx] = p_module->calibration.cal_data.core.pxtalk_grid_rate.cal__grid_data__rate_kcps_per_spad[idx] >> 11; + else + p_module->data[idx] = -1; + } + } + + vl53l8_rotate_report_data_int32(p_module->data, p_module->fac_rotation_mode); + + min = max = avg = p_module->data[0]; + for (i = 1; i < NUM_OF_ZONE; i++) { + if (p_module->data[i] < min) + min = p_module->data[i]; + if (max < p_module->data[i]) + max = p_module->data[i]; + avg += p_module->data[i]; + } + + if (p_module->read_p2p_cal_data == true) + avg = avg / p_module->number_of_zones; + else + avg = 0; + + i = j = 0; + j += snprintf(buf + j, PAGE_SIZE - j, "crosstalk,%d,%d,%d,%d,%d,", + CAL_XTALK_MIN_SPEC, CAL_XTALK_MAX_SPEC, min, max, avg); + for (; i < NUM_OF_ZONE; i++) { + j += snprintf(buf + j, PAGE_SIZE - j, "%d", p_module->data[i]); + if (i != p_module->number_of_zones - 1) + j += snprintf(buf + j, PAGE_SIZE - j, ","); + else + j += snprintf(buf + j, PAGE_SIZE - j, "\n"); + } + + return j; +} + +#define MIN_CAL_OFFSET_SPEC -500 +#define MAX_CAL_OFFSET_SPEC 500 + +static ssize_t vl53l8_cal_offset_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + uint32_t i = 0, j = 0, idx = 0; + int min = 0, max = 0, avg = 0; + + for (i = 0; i < p_module->print_counter; i++) { + for (j = 0; j < p_module->print_counter; j++) { + idx = (i * p_module->print_counter + j); + if (p_module->read_p2p_cal_data == true) + p_module->data[idx] = p_module->calibration.cal_data.core.poffset_grid_offset.cal__grid_data__range_mm[idx] >> 2; + else + p_module->data[idx] = -999999; + } + } + + vl53l8_rotate_report_data_int32(p_module->data, p_module->fac_rotation_mode); + + min = max = avg = p_module->data[0]; + for (i = 1; i < NUM_OF_ZONE; i++) { + if (p_module->data[i] < min) + min = p_module->data[i]; + if (max < p_module->data[i]) + max = p_module->data[i]; + avg += p_module->data[i]; + } + + if (p_module->read_p2p_cal_data == true) + avg = avg / p_module->number_of_zones; + else + avg = 0; + + i = j = 0; + j += snprintf(buf + j, PAGE_SIZE - j, "offset,%d,%d,%d,%d,%d,", + MIN_CAL_OFFSET_SPEC, MAX_CAL_OFFSET_SPEC, min, max, avg); + for (; i < NUM_OF_ZONE; i++) { + j += snprintf(buf + j, PAGE_SIZE - j, "%d", p_module->data[i]); + if (i != p_module->number_of_zones - 1) + j += snprintf(buf + j, PAGE_SIZE - j, ","); + else + j += snprintf(buf + j, PAGE_SIZE - j, "\n"); + } + + return j; +} + +static ssize_t vl53l8_open_calibration_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + int ret = 0; + int status = 0; + int cal_status; + + mutex_lock(&p_module->mutex); + cal_status = vl53l8_check_calibration_condition(p_module, ENTER); + if (cal_status < 0) { + mutex_unlock(&p_module->mutex); + vl53l8_k_log_info("Cal Condition err\n"); + p_module->last_driver_error = VL53L8_OPENCAL_ERROR; +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG + vl53l8_last_error_counter(p_module, p_module->last_driver_error); +#endif + if (cal_status == FAIL_GET_RANGE) + return snprintf(buf, PAGE_SIZE, "FAIL_MODULE_CONNECTOR\n"); + else + return snprintf(buf, PAGE_SIZE, "FAIL_AMBIENT\n"); + } + ret = vl53l8_perform_open_calibration(p_module); + if (ret == STATUS_OK) + ret = vl53l8_load_open_calibration(p_module); + + cal_status = vl53l8_check_calibration_condition(p_module, END); + if (cal_status < 0 || ret < 0) { + vl53l8_k_log_info("cal err\n"); + status = vl53l8_input_report(p_module, 7, CMD_DELTE_OPENCAL_FILE); + if (status < 0) + vl53l8_k_log_error("delete cal err\n"); + + mutex_unlock(&p_module->mutex); + p_module->last_driver_error = VL53L8_OPENCAL_ERROR; +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG + vl53l8_last_error_counter(p_module, p_module->last_driver_error); +#endif + if (cal_status == EXCEED_AMBIENT) + return snprintf(buf, PAGE_SIZE, "FAIL_AMBIENT\n"); + else if ((ret == MAX_FAILING_ZONES_LIMIT_FAIL || ret == NO_VALID_ZONES) && p_module->max_peak_signal > 200) + return snprintf(buf, PAGE_SIZE, "FAIL_CLOSED\n"); + else if ((ret == MAX_FAILING_ZONES_LIMIT_FAIL || ret == NO_VALID_ZONES) && p_module->max_peak_signal <= 200) + return snprintf(buf, PAGE_SIZE, "FAIL_BG\n"); + else if (cal_status == EXCEED_XTALK) + return snprintf(buf, PAGE_SIZE, "FAIL_CLOSED_DUST\n"); + else + return snprintf(buf, PAGE_SIZE, "FAIL_MODULE_CONNECTOR\n"); + } + + mutex_unlock(&p_module->mutex); + vl53l8_k_log_info("cal done\n"); + return snprintf(buf, PAGE_SIZE, "PASS\n"); + +} + + +#ifdef STM_VL53L5_FORCE_ERROR_COMMAND +static ssize_t vl53l8_force_error(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int status = STATUS_OK; + + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + + vl53l8_set_device_parameters(p_module, VL53L5_CFG__FORCE_ERROR); + + return status; +} +#endif +static ssize_t vl53l8_integration_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%i\n", p_module->integration); + +} + +static ssize_t vl53l8_integration_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + uint32_t integration = 0; + int ret; + + ret = kstrtou32(buf, 10, &integration); + + if (ret) { + vl53l8_k_log_error("Invalid\n"); + return count; + } + + ret = set_integration_time(p_module, integration); + if (ret != STATUS_OK) + return count; + mutex_lock(&p_module->mutex); + vl53l8_ioctl_set_integration_time_us(p_module, p_module->integration); + mutex_unlock(&p_module->mutex); + return count; +} + +static ssize_t vl53l8_spi_clock_speed_hz(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + unsigned int freq = 0; + int ret = 0; + + ret = kstrtou32(buf, 10, &freq); + if (ret < 0) { + vl53l8_k_log_error("Invalid\n"); + return count; + } + vl53l8_k_log_debug("spi clock %d", freq); + + mutex_lock(&p_module->mutex); + vl53l8_set_transfer_speed_hz(p_module, freq); + mutex_unlock(&p_module->mutex); + + return count; +} + +static ssize_t vl53l8_ranging_rate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%i\n", p_module->rate); +} + +static ssize_t vl53l8_ranging_rate_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + uint32_t rate = 0; + int ret; + + ret = kstrtou32(buf, 10, &rate); + if (ret) { + vl53l8_k_log_error("Invalid\n"); + return count; + } + + ret = set_sampling_rate(p_module, rate); + if (ret != STATUS_OK) + return count; + + if (p_module->state_preset == VL53L8_STATE_RANGING) { + vl53l8_k_log_info("Skip set rate"); + return count; + } + + p_module->rate = rate; + return count; +} + +static ssize_t vl53l8_asz_tuning_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "asz_0:%i, asz_1:%i, asz_2:%i, asz_3:%i\n", + p_module->asz_0_ll_zone_id, p_module->asz_1_ll_zone_id, + p_module->asz_2_ll_zone_id, p_module->asz_3_ll_zone_id); + +} + +static ssize_t vl53l8_asz_tuning_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + int ret; + uint32_t asz_0 = 0, asz_1 = 0, asz_2 = 0, asz_3 = 0; + + LOG_FUNCTION_START(""); + + ret = sscanf(buf, "%d %d %d %d", &asz_0, &asz_1, &asz_2, &asz_3); + if (ret < 0) { + vl53l8_k_log_error("Invalid\n"); + return count; + } + + vl53l8_k_log_info("ASZ %d %d %d %d", asz_0, asz_1, asz_2, asz_3); + + p_module->asz_0_ll_zone_id = asz_0; + p_module->asz_1_ll_zone_id = asz_1; + p_module->asz_2_ll_zone_id = asz_2; + p_module->asz_3_ll_zone_id = asz_3; + + return count; +} + +// Samsung specific code +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +static ssize_t vl53l8_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "vl53l8\n"); +} + +static ssize_t vl53l8_vendor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "STM\n"); +} + +static ssize_t vl53l8_firmware_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + struct vl53l8_k_version_t p_version; + int status = STATUS_OK; + enum vl53l8_k_state_preset prev_state; + + prev_state = p_module->state_preset; + + mutex_lock(&p_module->mutex); + if (p_module->state_preset <= VL53L8_STATE_LOW_POWER) + vl53l8_ioctl_set_power_mode(p_module, NULL, VL53L5_POWER_STATE_HP_IDLE); + + status = vl53l8_get_firmware_version(p_module, &p_version); + + vl53l8_k_log_info("fw read %d", status); + + if (prev_state <= VL53L8_STATE_LOW_POWER) + vl53l8_ioctl_set_power_mode(p_module, NULL, VL53L5_POWER_STATE_ULP_IDLE); + mutex_unlock(&p_module->mutex); + + return snprintf(buf, PAGE_SIZE, "FW:%d.%d.%d.%d, Patch:%d.%d, Config:%d.%d\n", + p_version.driver.firmware.ver_major, + p_version.driver.firmware.ver_minor, + p_version.driver.firmware.ver_build, + p_version.driver.firmware.ver_revision, + p_version.patch.patch_version.ver_major, + p_version.patch.patch_version.ver_minor, + p_version.bin_version.config_ver_major, + p_version.bin_version.config_ver_minor); +} + +#define MIN_TEMP_SPEC 10 +#define MAX_TEMP_SPEC 80 + +static ssize_t vl53l8_temp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + int prev_state = VL53L8_STATE_RANGING, count = 7; + int8_t temp = -100; + + vl53l8_k_log_debug("Lock"); + mutex_lock(&p_module->mutex); + vl53l8_k_log_info("state_preset %d, power_state %d", p_module->state_preset, p_module->power_state); + if (p_module->state_preset != VL53L8_STATE_RANGING) { + prev_state = p_module->state_preset; + vl53l8_ioctl_start(p_module); + } + p_module->range.data.tcpm_0_patch.meta_data.silicon_temp_degc = temp; + vl53l8_k_log_debug("unLock"); + mutex_unlock(&p_module->mutex); + + msleep(130); + + while (count > 0) { + if (p_module->range.data.tcpm_0_patch.meta_data.silicon_temp_degc == temp) + msleep(20); + else + break; + count--; + } + + vl53l8_k_log_debug("Lock"); + mutex_lock(&p_module->mutex); + + temp = p_module->range.data.tcpm_0_patch.meta_data.silicon_temp_degc; + vl53l8_k_log_info("temp %d", temp); + + if (prev_state != VL53L8_STATE_RANGING) + vl53l8_ioctl_stop(p_module); + + vl53l8_k_log_debug("unLock"); + mutex_unlock(&p_module->mutex); + + return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n", MIN_TEMP_SPEC, MAX_TEMP_SPEC, temp); +} + +#define MIN_SPEC 0 +#define MAX_SPEC 100000 + +static ssize_t vl53l8_ambient_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + uint32_t i = 0, j = 0, idx = 0; + int min = 0, max = 0, avg = 0; + + for (i = 0; i < p_module->print_counter; i++) { + for (j = 0; j < p_module->print_counter; j++) { + idx = i * p_module->print_counter + j; + p_module->data[idx] = p_module->range_data.per_zone_results.amb_rate_kcps_per_spad[idx] >> 11; + } + } + + min = max = avg = p_module->data[0]; + for (i = 1; i < NUM_OF_ZONE; i++) { + if (p_module->data[i] < min) + min = p_module->data[i]; + if (max < p_module->data[i]) + max = p_module->data[i]; + avg += p_module->data[i]; + } + avg = avg / p_module->number_of_zones; + + for (i = 0; i < 64; i += 8) { + vl53l8_k_log_info("%d, %d, %d, %d, %d, %d, %d, %d\n", + p_module->data[i], p_module->data[i+1], p_module->data[i+2], + p_module->data[i+3], p_module->data[i+4], p_module->data[i+5], + p_module->data[i+6], p_module->data[i+7]); + } + + i = j = 0; + j += snprintf(buf + j, PAGE_SIZE - j, "Ambient,%d,%d,%d,%d,%d,", + MIN_SPEC, MAX_SPEC, min, max, avg); + for (; i < NUM_OF_ZONE; i++) { + j += snprintf(buf + j, PAGE_SIZE - j, "%d", p_module->data[i]); + if (i != p_module->number_of_zones - 1) + j += snprintf(buf + j, PAGE_SIZE - j, ","); + else + j += snprintf(buf + j, PAGE_SIZE - j, "\n"); + } + + return j; +} + +#define MIN_MAX_RANGE_SPEC 500 +#define MAX_MAX_RANGE_SPEC 10500 + +static ssize_t vl53l8_max_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + uint32_t i = 0, j = 0, idx = 0; + int32_t min = 0, max = 0, avg = 0; + + if (p_module->read_data_valid) { + for (i = 0; i < p_module->print_counter; i++) { + for (j = 0; j < p_module->print_counter; j++) { + idx = i * p_module->print_counter + j; + p_module->data[idx] = p_module->range_data.per_zone_results.amb_dmax_mm[idx]; + } + } + } else { + for (i = 0 ; i < NUM_OF_ZONE; i++) + p_module->data[i] = -1; + } + + vl53l8_rotate_report_data_int32(p_module->data, p_module->fac_rotation_mode); + + min = max = avg = p_module->data[0]; + + for (i = 1; i < NUM_OF_ZONE; i++) { + if (p_module->data[i] < min) + min = p_module->data[i]; + if (max < p_module->data[i]) + max = p_module->data[i]; + avg += p_module->data[i]; + } + + avg = avg / p_module->number_of_zones; + + for (i = 0; i < 64; i += 8) { + vl53l8_k_log_info("%d, %d, %d, %d, %d, %d, %d, %d\n", + p_module->data[i], p_module->data[i+1], p_module->data[i+2], + p_module->data[i+3], p_module->data[i+4], p_module->data[i+5], + p_module->data[i+6], p_module->data[i+7]); + } + + i = j = 0; + j += snprintf(buf + j, PAGE_SIZE - j, "Maxrange,%d,%d,%d,%d,%d,", + MIN_MAX_RANGE_SPEC, MAX_MAX_RANGE_SPEC, min, max, avg); + + for (; i < TEST_300_MM_MAX_ZONE; i++) { + j += snprintf(buf + j, PAGE_SIZE - j, "%d", p_module->data[i]); + if (i != TEST_300_MM_MAX_ZONE - 1) + j += snprintf(buf + j, PAGE_SIZE - j, ","); + else + j += snprintf(buf + j, PAGE_SIZE - j, "\n"); + } + + return j; +} + +#define MIN_PEAK_SIGNAL_SPEC 0 +#define MAX_PEAK_SIGNAL_SPEC 3000 + +static ssize_t vl53l8_peak_signal_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + uint32_t i = 0, j = 0, idx = 0; + int32_t min = 0, max = 0, avg = 0; + + if (p_module->read_data_valid) { + for (i = 0; i < p_module->print_counter; i++) { + for (j = 0; j < p_module->print_counter; j++) { + idx = (i * p_module->print_counter + j); + p_module->data[idx] = p_module->range_data.per_tgt_results.peak_rate_kcps_per_spad[idx] >> 11; + } + } + } else { + for (i = 0 ; i < NUM_OF_ZONE; i++) + p_module->data[i] = -1; + } + + min = max = avg = p_module->data[0]; + + for (i = 1; i < NUM_OF_ZONE; i++) { + if (p_module->data[i] < min) + min = p_module->data[i]; + if (max < p_module->data[i]) + max = p_module->data[i]; + avg += p_module->data[i]; + } + + avg = avg / p_module->number_of_zones; + + for (i = 0; i < NUM_OF_ZONE; i += 8) { + vl53l8_k_log_info("%d, %d, %d, %d, %d, %d, %d, %d\n", + p_module->data[i], p_module->data[i+1], p_module->data[i+2], + p_module->data[i+3], p_module->data[i+4], p_module->data[i+5], + p_module->data[i+6], p_module->data[i+7]); + } + + vl53l8_rotate_report_data_int32(p_module->data, p_module->fac_rotation_mode); + + i = j = 0; + j += snprintf(buf + j, PAGE_SIZE - j, "Peaksignal,%d,%d,%d,%d,%d,", + MIN_PEAK_SIGNAL_SPEC, MAX_PEAK_SIGNAL_SPEC, min, max, avg); + + for (; i < TEST_300_MM_MAX_ZONE; i++) { + j += snprintf(buf + j, PAGE_SIZE - j, "%d", p_module->data[i]); + if (i != TEST_300_MM_MAX_ZONE - 1) + j += snprintf(buf + j, PAGE_SIZE - j, ","); + else + j += snprintf(buf + j, PAGE_SIZE - j, "\n"); + } + + return j; +} + +static ssize_t vl53l8_target_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + uint32_t i = 0, j = 0; + int min = 0, max = 0, avg = 0; + int idx; + + for (i = 0; i < p_module->print_counter; i++) { + for (j = 0; j < p_module->print_counter; j++) { + idx = (i * p_module->print_counter + j); + p_module->data[idx] = (uint16_t)(p_module->range_data.d16_per_target_data.depth16[idx] & 0xE000U) >> 13; + } + } + + min = max = avg = p_module->data[0]; + for (i = 1; i < NUM_OF_ZONE; i++) { + if (p_module->data[i] < min) + min = p_module->data[i]; + if (max < p_module->data[i]) + max = p_module->data[i]; + avg += p_module->data[i]; + } + + + vl53l8_rotate_report_data_int32(p_module->data, p_module->fac_rotation_mode); + avg = avg / p_module->number_of_zones; + + for (i = 0; i < NUM_OF_ZONE; i += 8) { + vl53l8_k_log_info("%d, %d, %d, %d, %d, %d, %d, %d\n", + p_module->data[i], p_module->data[i+1], p_module->data[i+2], + p_module->data[i+3], p_module->data[i+4], p_module->data[i+5], + p_module->data[i+6], p_module->data[i+7]); + } + + i = j = 0; + j += snprintf(buf + j, PAGE_SIZE - j, "Targetstatus,%d,%d,%d,%d,%d,", + MIN_SPEC, MAX_SPEC, min, max, avg); + for (; i < NUM_OF_ZONE; i++) { + j += snprintf(buf + j, PAGE_SIZE - j, "%d", p_module->data[i]); + if (i != p_module->number_of_zones - 1) + j += snprintf(buf + j, PAGE_SIZE - j, ","); + else + j += snprintf(buf + j, PAGE_SIZE - j, "\n"); + } + + return j; +} + +static ssize_t vl53l8_asz_distance_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + u16 asz_distance[4], asz_status[4]; + u8 i = 0; + + for (; i < 4; i++) { + asz_distance[i] = + (uint16_t)(p_module->range.data.tcpm_0_patch.d16_per_target_data.depth16[64 + i] & 0x1FFFU); + asz_status[i] = + (uint16_t)(p_module->range.data.tcpm_0_patch.d16_per_target_data.depth16[64 + i] & 0xE000U) >> 13; + } + + return snprintf(buf, PAGE_SIZE, "%d, %d, %d, %d, %d, %d, %d, %d\n", + asz_distance[0], asz_status[0], asz_distance[1], asz_status[1], + asz_distance[2], asz_status[2], asz_distance[3], asz_status[3]); +} + +static ssize_t vl53l8_test_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d,%d\n", p_module->test_mode, TEST_300_MM_MAX_ZONE); +} + +static ssize_t vl53l8_test_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + int ret; + u8 val; + + ret = kstrtou8(buf, 10, &val); + if (ret) { + vl53l8_k_log_error("Invalid\n"); + return count; + } + + switch (val) { + case 1: + vl53l8_k_log_info("Set 500 mm\n"); + break; + default: + vl53l8_k_log_info("Set 300 mm\n"); + } + p_module->test_mode = 0; + + return count; +} + +static ssize_t vl53l8_uid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + + vl53l8_k_log_info("%x,%x\n", p_module->m_info.module_id_hi, p_module->m_info.module_id_lo); + return snprintf(buf, PAGE_SIZE, "%x%x\n", p_module->m_info.module_id_hi, p_module->m_info.module_id_lo); +} + +static ssize_t vl53l8_cal_uid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + enum vl53l8_k_state_preset prev_state; + + if (p_module->read_p2p_cal_data == false) { + prev_state = p_module->state_preset; + + mutex_lock(&p_module->mutex); + if (p_module->state_preset <= VL53L8_STATE_LOW_POWER) + vl53l8_ioctl_set_power_mode(p_module, NULL, VL53L5_POWER_STATE_HP_IDLE); + + vl53l8_load_calibration(p_module); + + if (prev_state <= VL53L8_STATE_LOW_POWER) + vl53l8_ioctl_set_power_mode(p_module, NULL, VL53L5_POWER_STATE_ULP_IDLE); + mutex_unlock(&p_module->mutex); + } + + if (p_module->read_p2p_cal_data == true) + vl53l8_k_log_info("%x,%x\n", + p_module->calibration.info.module_id_hi, + p_module->calibration.info.module_id_lo); + + return snprintf(buf, PAGE_SIZE, "%x%x\n", p_module->calibration.info.module_id_hi, p_module->calibration.info.module_id_lo); +} + +static ssize_t vl53l8_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "continuos\n"); +} + +static ssize_t vl53l8_frame_rate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "15\n"); +} + + +static ssize_t vl53l8_zone_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "8,8\n"); +} + +static ssize_t vl53l8_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", p_module->last_driver_error); +} + +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG +void sort_error_code(int *top5_error_code, u8 *top5_error_cnt) +{ + int i; + int temp; + + for (i = 4; i > 0; i--) { + if (top5_error_cnt[i] > top5_error_cnt[i-1]) { + temp = top5_error_cnt[i]; + top5_error_cnt[i] = top5_error_cnt[i-1]; + top5_error_cnt[i-1] = temp; + + temp = top5_error_code[i]; + top5_error_code[i] = top5_error_code[i-1]; + top5_error_code[i-1] = temp; + } else { + break; + } + } +} + +static ssize_t vl53l8_error_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + + int i = 0; + int j = 0; + int top5_error_code[5] = {0}; + u8 top5_error_cnt[5] = {0}; + + vl53l8_last_error_counter(p_module, p_module->last_driver_error); + vl53l8_check_ldo_onoff(p_module); + + for (; i < MAX_TABLE; i++) { + if (p_module->errdata[i].last_error_cnt == 0) + continue; + if (top5_error_cnt[4] < p_module->errdata[i].last_error_cnt) { + top5_error_cnt[4] = p_module->errdata[i].last_error_cnt; + top5_error_code[4] = p_module->errdata[i].last_error_code; + sort_error_code(top5_error_code, top5_error_cnt); + } + } + j += snprintf(buf + j, PAGE_SIZE, "\"RS_UID\":\"%x%x\"", p_module->m_info.module_id_hi, p_module->m_info.module_id_lo); + for (i = 0; i < 5; i++) { + j += snprintf(buf + j, PAGE_SIZE - j, ","); + j += snprintf(buf + j, PAGE_SIZE - j, "\"errno%d\":\"%d\",\"cnt%d\":\"%d\"", i, top5_error_code[i], i, top5_error_cnt[i]); + } + j += snprintf(buf + j, PAGE_SIZE - j, "\n"); + + memset(&p_module->errdata, 0, sizeof(p_module->errdata)); + + return j; +} +#endif +static ssize_t vl53l8_interrupt_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vl53l8_k_module_t *p_module = dev_get_drvdata(dev); + int count = 0; + + while ((p_module->range.count == 0) && (count < MAX_READ_COUNT)) { + usleep_range(10000, 10100); + ++count; + } + + if (p_module->range.count > 0) + return snprintf(buf, PAGE_SIZE, "1,%lu\n", p_module->range.count); + else + return snprintf(buf, PAGE_SIZE, "0,%lu\n", p_module->range.count); +} +#endif + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +static DEVICE_ATTR(name, 0440, vl53l8_name_show, NULL); +static DEVICE_ATTR(vendor, 0440, vl53l8_vendor_show, NULL); +static DEVICE_ATTR(fw_version, 0440, vl53l8_firmware_version_show, NULL); +static DEVICE_ATTR(enable, 0660, vl53l8_enable_show, vl53l8_enable_store); +static DEVICE_ATTR(asz_test, 0440, vl53l8_asz_distance_show, NULL); +static DEVICE_ATTR(test_mode, 0660, vl53l8_test_mode_show, vl53l8_test_mode_store); +static DEVICE_ATTR(temp, 0440, vl53l8_temp_show, NULL); +static DEVICE_ATTR(test01, 0440, vl53l8_distance_show, NULL); +static DEVICE_ATTR(test02, 0440, vl53l8_peak_signal_show, NULL); +static DEVICE_ATTR(test03, 0440, vl53l8_max_range_show, NULL); +static DEVICE_ATTR(ambient, 0440, vl53l8_ambient_show, NULL); +static DEVICE_ATTR(target_status, 0440, vl53l8_target_status_show, NULL); +static DEVICE_ATTR(uid, 0440, vl53l8_uid_show, NULL); +static DEVICE_ATTR(cal_uid, 0440, vl53l8_cal_uid_show, NULL); +static DEVICE_ATTR(mode, 0440, vl53l8_mode_show, NULL); +static DEVICE_ATTR(zone, 0440, vl53l8_zone_show, NULL); +static DEVICE_ATTR(frame_rate, 0440, vl53l8_frame_rate_show, NULL); +static DEVICE_ATTR(cal01, 0440, vl53l8_cal_offset_show, NULL); +static DEVICE_ATTR(cal02, 0440, vl53l8_cal_xtalk_show, NULL); +static DEVICE_ATTR(status, 0440, vl53l8_status_show, NULL); +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG +static DEVICE_ATTR(error, 0440, vl53l8_error_show, NULL); +#endif +static DEVICE_ATTR(calibration, 0660, vl53l8_calibration_show, vl53l8_calibration_store); +static DEVICE_ATTR(open_calibration, 0440, vl53l8_open_calibration_show, NULL); +static DEVICE_ATTR(asz, 0660, vl53l8_asz_tuning_show, vl53l8_asz_tuning_store); +static DEVICE_ATTR(ranging_rate, 0660, vl53l8_ranging_rate_show, vl53l8_ranging_rate_store); +static DEVICE_ATTR(integration, 0660, vl53l8_integration_show, vl53l8_integration_store); +static DEVICE_ATTR(spi_clock_speed_hz, 0220, NULL, vl53l8_spi_clock_speed_hz); +static DEVICE_ATTR(interrupt, 0440, vl53l8_interrupt_show, NULL); +#endif + +// Samsung specific code +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +static struct device_attribute *sensor_attrs[] = { + &dev_attr_name, + &dev_attr_vendor, + &dev_attr_fw_version, + &dev_attr_enable, + &dev_attr_asz_test, + &dev_attr_temp, + &dev_attr_test01, + &dev_attr_test02, + &dev_attr_test03, + &dev_attr_ambient, + &dev_attr_target_status, + &dev_attr_cal_uid, + &dev_attr_uid, + &dev_attr_mode, + &dev_attr_zone, + &dev_attr_frame_rate, + &dev_attr_cal01, + &dev_attr_cal02, + &dev_attr_calibration, + &dev_attr_open_calibration, + &dev_attr_status, +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG + &dev_attr_error, +#endif + &dev_attr_test_mode, + &dev_attr_ranging_rate, + &dev_attr_asz, + &dev_attr_integration, + &dev_attr_spi_clock_speed_hz, + &dev_attr_interrupt, + NULL, +}; +//for sec dump ----- +int vl53l5_dump_data_notify(struct notifier_block *nb, + unsigned long val, void *v) +{ + struct vl53l8_k_module_t *p_module = container_of(nb, struct vl53l8_k_module_t, dump_nb); + + if ((val == 1) && (p_module != NULL)) { + vl53l8_k_log_info("probe status %d", p_module->stdev.status_probe); + vl53l8_k_log_info("cal status %d", !(p_module->stdev.status_cal)?1:p_module->stdev.status_cal); + vl53l8_k_log_info("cal load %d", p_module->load_calibration); + vl53l8_k_log_info("cal p2p %d", p_module->read_p2p_cal_data); + vl53l8_k_log_info("last_driver_err %d", p_module->last_driver_error); + vl53l8_k_log_info("device id 0x%02X", p_module->stdev.host_dev.device_id); + vl53l8_k_log_info("revision id 0x%02X", p_module->stdev.host_dev.revision_id); + vl53l8_k_log_info("uid %x,%x\n", p_module->m_info.module_id_hi, p_module->m_info.module_id_lo); + vl53l8_k_log_info("state %d force count %u", p_module->state_preset, p_module->force_suspend_count); + } + return 0; +} +//----- for sec dump +#endif +#endif +#endif + +void vl53l8_k_prelaod(struct vl53l8_k_module_t *p_module) +{ + if (p_module != NULL) { + int ret; + + if (p_module->input_dev == NULL) { + p_module->input_dev = input_allocate_device(); + + if (p_module->input_dev != NULL) { + p_module->input_dev->name = MODULE_NAME; + + input_set_capability(p_module->input_dev, EV_REL, REL_MISC); + input_set_drvdata(p_module->input_dev, p_module); + + ret = input_register_device(p_module->input_dev); + vl53l8_k_log_info("input_register_device err %d\n", ret); + } else { + vl53l8_k_log_error("input alloc fail"); + } + } else { + vl53l8_k_log_info("already done input"); + } + + if (!p_module->sensors_register_done) { + ret = sensors_register(&p_module->factory_device, + p_module, sensor_attrs, MODULE_NAME); + if (ret) + vl53l8_k_log_error("could not register sensor-%d\n", ret); + else { + vl53l8_k_log_info("create success"); + p_module->sensors_register_done = true; + } + } else { + vl53l8_k_log_info("already done register"); + } + } +} + +#ifdef CONFIG_SEPARATE_IO_CORE_POWER +static void power_delayed_work_func(struct work_struct *work) +{ + static int retry; + int status = STATUS_OK; + struct delayed_work *delayed_work = to_delayed_work(work); + struct vl53l8_k_module_t *p_module = container_of(delayed_work, + struct vl53l8_k_module_t, power_delayed_work); + + status = vl53l8_regulator_init_state(p_module); + if (status < 0 && retry++ < 10) { + vl53l8_k_log_error("regulator not ready : %d retry : %d\n", status, retry); + schedule_delayed_work(&p_module->power_delayed_work, msecs_to_jiffies(10000)); + return; + } + + vl53l8_power_onoff(p_module, true); +} +#endif + +int vl53l8_k_spi_probe(struct spi_device *spi) +{ + int status = 0; + struct vl53l8_k_module_t *p_module = NULL; +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + unsigned char device_id = 0; + unsigned char revision_id = 0; + unsigned char page = 0; +#endif + u8 ldo_num = 0; + + LOG_FUNCTION_START(""); + + vl53l8_k_log_debug("Allocate module data"); +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + p_module = kzalloc(sizeof(struct vl53l8_k_module_t), GFP_KERNEL); + if (!p_module) { + vl53l8_k_log_error("Failed."); + status = -ENOMEM; + goto done; + } +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + if (global_p_module == NULL) + p_module = kzalloc(sizeof(struct vl53l8_k_module_t), GFP_KERNEL); + else + p_module = global_p_module; +#endif + + if (!p_module) { + vl53l8_k_log_error("alloc fail"); + status = -ENOMEM; + goto done; + } + + vl53l8_k_log_debug("Assign data to spi handle"); + p_module->spi_info.device = spi; + + vl53l8_k_log_debug("Set client data"); + spi_set_drvdata(spi, p_module); + + vl53l8_k_log_debug("Init kref"); + kref_init(&p_module->spi_info.ref); + + if (spi->dev.of_node) { + status = vl53l8_parse_dt(&spi->dev, p_module); + if (status) { + vl53l8_k_log_error("%s - Failed to parse DT", __func__); + goto done_cleanup; + } + } + +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE + status = vl53l5_platform_init(&p_module->stdev); + if (status != VL53L5_ERROR_NONE) + goto done; + + status = vl53l5_write_multi(&p_module->stdev, 0x7FFF, &page, 1); + if (status < VL53L5_ERROR_NONE) + goto done; + + status = vl53l5_read_multi(&p_module->stdev, 0x00, &device_id, 1); + if (status < VL53L5_ERROR_NONE) + goto done_change_page; + + status = vl53l5_read_multi(&p_module->stdev, 0x01, &revision_id, 1); + if (status < VL53L5_ERROR_NONE) + goto done_change_page; + + vl53l8_k_log_info("device_id (0x%02X), revision_id (0x%02X)", + device_id, revision_id); + + if (device_id != 0xF0 || revision_id != 0x0c) + vl53l8_k_log_error("Unsupported device type"); +#endif + + vl53l8_k_log_debug("Set driver"); + status = vl53l8_k_setup(p_module); + if (status != 0) + goto done_freemem; + + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + for (ldo_num = 0; ldo_num < VDD_MAX_CNT; ldo_num++) + p_module->ldo_prev_state[ldo_num] = 0xFF; + p_module->number_of_zones = NUM_OF_ZONE; + p_module->print_counter = 8; + vl53l8_k_log_info("print_counter = %d", + p_module->print_counter); + + vl53l8_k_prelaod(p_module); +#if IS_ENABLED(CONFIG_SENSORS_VL53L8_QCOM) || IS_ENABLED(CONFIG_SENSORS_VL53L8_SLSI) + //for sec dump ----- + p_module->dump_nb.notifier_call = vl53l5_dump_data_notify; + p_module->dump_nb.priority = 1; + + { + int ret; + + ret = sensordump_notifier_register(&p_module->dump_nb); + vl53l8_k_log_info("notifier %d", ret); + } + //----- for sec dump +#endif +#endif +#ifdef CONFIG_SEPARATE_IO_CORE_POWER +#ifdef CONFIG_SENSORS_VL53L8_SUPPORT_RESUME_WORK + INIT_WORK(&p_module->resume_work, resume_work_func); +#endif + INIT_DELAYED_WORK(&p_module->power_delayed_work, power_delayed_work_func); + schedule_delayed_work(&p_module->power_delayed_work, msecs_to_jiffies(5000)); +#endif + +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE +done_change_page: +page = 2; + if (status != VL53L5_ERROR_NONE) + (void)vl53l5_write_multi(&p_module->stdev, 0x7FFF, &page, 1); + else + status = vl53l5_write_multi(&p_module->stdev, 0x7FFF, &page, 1); +#endif +done: +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + if (p_module != NULL) + p_module->probe_done = true; +#endif + LOG_FUNCTION_END(status); + return status; + +done_cleanup: + vl53l8_k_cleanup(p_module); + +done_freemem: + kfree(p_module); + + LOG_FUNCTION_END(status); + return status; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +void vl53l8_k_spi_remove(struct spi_device *device) +#else +int vl53l8_k_spi_remove(struct spi_device *device) +#endif +{ + int status = 0; + struct vl53l8_k_module_t *p_module = spi_get_drvdata(device); + + LOG_FUNCTION_START(""); + + vl53l8_k_log_debug("Driver cleanup"); + vl53l8_k_cleanup(p_module); + + kref_put(&p_module->spi_info.ref, memory_release); + + LOG_FUNCTION_END(status); + status = vl53l8_k_convert_error_to_linux_error(status); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) + return; +#else + return status; +#endif +} + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +void vl53l8_k_spi_shutdown(struct spi_device *device) +{ + struct vl53l8_k_module_t *p_module = spi_get_drvdata(device); + + vl53l8_k_log_info("state %d err %d", p_module->state_preset, p_module->last_driver_error); +#ifdef CONFIG_SENSORS_VL53L8_SUPPORT_RESUME_WORK + cancel_work_sync(&p_module->resume_work); +#endif + p_module->last_driver_error = VL53L8_SHUTDOWN; +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG + vl53l8_last_error_counter(p_module, p_module->last_driver_error); +#endif + if (p_module->state_preset == VL53L8_STATE_RANGING) + vl53l8_ioctl_stop(p_module); + +#ifdef CONFIG_SENSORS_VL53L8_SUPPORT_KERNEL_INTERFACE + global_p_module = NULL; +#endif + + vl53l8_power_onoff(p_module, false); +} +#endif + +static int setup_miscdev(struct vl53l8_k_module_t *p_module) +{ + int status = 0; + + vl53l8_k_log_debug("Setting up misc dev"); + p_module->miscdev.minor = MISC_DYNAMIC_MINOR; +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + if (p_module->id == 0) + strcpy(p_module->name, VL53L8_K_DRIVER_NAME); + else + sprintf(p_module->name, "%s_%d", VL53L8_K_DRIVER_NAME, + p_module->id); +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + strcpy(p_module->name, VL53L8_K_DRIVER_NAME); +#endif + + p_module->miscdev.name = p_module->name; + p_module->miscdev.fops = &ranging_fops; + vl53l8_k_log_debug("Misc device reg:%s", p_module->miscdev.name); + vl53l8_k_log_debug("Reg misc device"); + status = misc_register(&p_module->miscdev); + if (status != 0) { + vl53l8_k_log_error("Failed to register misc dev: %d", status); + goto done; + } + +#ifdef STM_VL53L5_SUPPORT_SEC_CODE +#ifdef CONFIG_SENSORS_VL53L5_SUPPORT_KERNEL_INTERFACE + if (global_p_module == NULL) + global_p_module = p_module; +#endif + p_module->last_driver_error = VL53L8_DELAYED_LOAD_FIRMWARE; +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG + vl53l8_last_error_counter(p_module, p_module->last_driver_error); +#endif +#endif +done: + return status; +} + +static void cleanup_miscdev(struct vl53l8_k_module_t *p_module) +{ + if (!IS_ERR(p_module->miscdev.this_device) && + p_module->miscdev.this_device != NULL) { + misc_deregister(&p_module->miscdev); + } +} + +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE +static void deallocate_dev_id(int id) +{ + mutex_lock(&dev_table_mutex); + + module_table[id] = NULL; + + mutex_unlock(&dev_table_mutex); +} + +static void allocate_dev_id(struct vl53l8_k_module_t *p_module) +{ + int i = 0; + + mutex_lock(&dev_table_mutex); + + while ((i < VL53L8_CFG_MAX_DEV) && (module_table[i])) + i++; + + i = i < VL53L8_CFG_MAX_DEV ? i : -1; + + p_module->id = i; + + if (i != -1) { + vl53l8_k_log_debug("Obtained device id %d", p_module->id); + module_table[p_module->id] = p_module; + } + + mutex_unlock(&dev_table_mutex); +} +#endif +static int vl53l8_k_ioctl_handler(struct vl53l8_k_module_t *p_module, + unsigned int cmd, unsigned long arg, + void __user *p) +{ + int status = 0; +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + + if (!p_module) { + status = -EINVAL; + goto exit; + } + vl53l8_k_log_debug("cmd%d", cmd); +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + int value = 0; + + if (p_module == NULL) { + if ((cmd == VL53L8_IOCTL_START) + || (cmd == VL53L8_IOCTL_STOP)) + vl53l8_k_log_error("probe fail"); + + status = -EINVAL; + goto exit; + } else if ((p_module->last_driver_error == VL53L8_PROBE_FAILED) + || (p_module->last_driver_error == VL53L8_DELAYED_LOAD_FIRMWARE) + || (p_module->last_driver_error == VL53L8_SHUTDOWN) + || (p_module->suspend_state == true)) { + if ((cmd == VL53L8_IOCTL_START) + || (cmd == VL53L8_IOCTL_STOP)) + vl53l8_k_log_error("err %d", p_module->last_driver_error); + status = p_module->last_driver_error; + if (p_module->suspend_state == true) + vl53l8_k_log_error("cmd %d is called in suspend", cmd); + goto exit; + } +#endif + + switch (cmd) { +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE + case VL53L8_IOCTL_INIT: + vl53l8_k_log_debug("VL53L8_IOCTL_INIT"); + status = vl53l8_ioctl_init(p_module); + break; + case VL53L8_IOCTL_TERM: + vl53l8_k_log_debug("VL53L8_IOCTL_TERM"); + status = vl53l8_ioctl_term(p_module); + break; + case VL53L8_IOCTL_GET_VERSION: + vl53l8_k_log_debug("VL53L8_IOCTL_GET_VERSION"); + status = vl53l8_ioctl_get_version(p_module, p); + break; +#endif + + case VL53L8_IOCTL_START: + vl53l8_k_log_debug("Lock"); + mutex_lock(&p_module->mutex); + vl53l8_k_log_info("VL53L8_IOCTL_START"); +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + status = vl53l8_ioctl_start(p_module); + if ((status != STATUS_OK) || (p_module->last_driver_error != STATUS_OK)) { + status = vl53l8_ioctl_init(p_module); + vl53l8_k_log_error("re init %d", status); + status = vl53l8_ioctl_start(p_module); + } +#else + status = vl53l8_ioctl_start(p_module, p); +#endif + if (status == STATUS_OK) { + p_module->enabled = 1; +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + p_module->ioctl_enable_status = 1; +#endif + } + vl53l8_k_log_debug("unLock"); + mutex_unlock(&p_module->mutex); + break; + + case VL53L8_IOCTL_STOP: + vl53l8_k_log_debug("Lock"); + mutex_lock(&p_module->mutex); + vl53l8_k_log_debug("VL53L8_IOCTL_STOP"); +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + p_module->enabled = 0; + p_module->ioctl_enable_status = 0; + status = vl53l8_ioctl_stop(p_module); + vl53l8_k_log_info("STOP"); + if ((status != STATUS_OK) || (p_module->last_driver_error != STATUS_OK)) { + status = vl53l8_ioctl_init(p_module); + vl53l8_k_log_error("re init %d", status); + status = vl53l8_ioctl_stop(p_module); + } +#else + status = vl53l8_ioctl_stop(p_module, p); +#endif + vl53l8_k_log_debug("unLock"); + mutex_unlock(&p_module->mutex); + break; + case VL53L8_IOCTL_GET_RANGE: + vl53l8_k_log_debug("VL53L8_IOCTL_GET_RANGE"); + status = vl53l8_ioctl_get_range(p_module, p); + break; +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + case VL53L8_IOCTL_GET_STATUS: + status = vl53l8_ioctl_get_status(p_module, p); + break; +#endif +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE + case VL53L8_IOCTL_SET_DEVICE_PARAMETERS: + vl53l8_k_log_debug("VL53L8_IOCTL_SET_DEVICE_PARAMETERS"); + status = vl53l8_ioctl_set_device_parameters(p_module, p); + break; + case VL53L8_IOCTL_GET_DEVICE_PARAMETERS: + vl53l8_k_log_debug("VL53L8_IOCTL_GET_DEVICE_PARAMETERS"); + status = vl53l8_ioctl_get_device_parameters(p_module, p); + break; + case VL53L8_IOCTL_GET_MODULE_INFO: + vl53l8_k_log_debug("VL53L8_IOCTL_GET_MODULE_INFO"); + status = vl53l8_ioctl_get_module_info(p_module, p); + break; + case VL53L8_IOCTL_GET_ERROR_INFO: + vl53l8_k_log_debug("VL53L8_IOCTL_GET_ERROR_INFO"); + status = vl53l8_ioctl_get_error_info(p_module, p); + break; + case VL53L8_IOCTL_SET_POWER_MODE: + vl53l8_k_log_debug("VL53L8_IOCTL_SET_POWER_MODE"); + status = vl53l8_ioctl_set_power_mode(p_module, p); + break; + case VL53L8_IOCTL_POLL_DATA_READY: + vl53l8_k_log_debug("VL53L8_IOCTL_POLL_DATA_READY"); + status = vl53l8_ioctl_poll_data_ready(p_module); + break; + case VL53L8_IOCTL_READ_P2P_FILE: + vl53l8_k_log_debug("VL53L8_IOCTL_READ_P2P_FILE"); + status = vl53l8_ioctl_read_p2p_calibration(p_module); + break; + case VL53L8_IOCTL_PERFORM_CALIBRATION_300: + vl53l8_k_log_debug("VL53L8_IOCTL_PERFORM_CALIBRATION_300"); + status = vl53l8_perform_calibration(p_module, 0); + break; + case VL53L8_IOCTL_READ_SHAPE_FILE: + vl53l8_k_log_debug("VL53L8_IOCTL_READ_SHAPE_FILE"); + status = vl53l8_ioctl_read_shape_calibration(p_module); + break; + case VL53L8_IOCTL_READ_GENERIC_SHAPE: + vl53l8_k_log_debug("VL53L8_IOCTL_READ_GENERIC_SHAPE"); + status = vl53l8_ioctl_read_generic_shape(p_module); + break; + case VL53L8_IOCTL_PERFORM_CHARACTERISATION_1000: + vl53l8_k_log_debug( + "VL53L8_IOCTL_PERFORM_CHARACTERISATION_1000"); + status = vl53l8_perform_calibration(p_module, 1); + break; +#endif +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + case VL53L5_IOCTL_GET_CAL_DATA: + vl53l8_k_log_info("GET_CAL_DATA"); + status = vl53l8_ioctl_get_cal_data(p_module, p); + break; + case VL53L5_IOCTL_SET_CAL_DATA: + vl53l8_k_log_info("SET_CAL_DATA"); + status = vl53l8_ioctl_set_cal_data(p_module, p); + break; + case VL53L5_IOCTL_SET_PASS_FAIL: + vl53l8_k_log_info("SET_PASS_FAIL"); + status = vl53l8_ioctl_set_pass_fail(p_module, p); + break; + case VL53L5_IOCTL_SET_FILE_LIST: + vl53l8_k_log_info("SET_FILE_LIST"); + status = vl53l8_ioctl_set_file_list(p_module, p); + break; +#endif + case VL53L8_IOCTL_SET_RANGING_RATE: + vl53l8_k_log_debug("VL53L8_IOCTL_SET_RANGING_RATE"); +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + status = copy_from_user(&value, p, sizeof(int)); + if (status != VL53L5_ERROR_NONE) { + status = VL53L8_K_ERROR_FAILED_TO_COPY_RANGING_RATE; + return status; + } + status = set_sampling_rate(p_module, (uint32_t)value); + if (status != STATUS_OK) { + vl53l8_k_log_error("RR Fail %d", status); + return status; + } else + vl53l8_k_log_info("RR %d", value); +#else + status = vl53l8_ioctl_set_ranging_rate(p_module, p); +#endif + break; + case VL53L8_IOCTL_SET_INTEGRATION_TIME_US: + vl53l8_k_log_debug("VL53L8_IOCTL_SET_INTEGRATION_TIME_US"); +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + status = copy_from_user(&value, p, sizeof(int)); + if (status != VL53L5_ERROR_NONE) { + status = VL53L8_K_ERROR_FAILED_TO_COPY_MAX_INTEGRATION; + return status; + } + status = set_integration_time(p_module, (uint32_t)value); + if (status != STATUS_OK) + return status; + mutex_lock(&p_module->mutex); + status = vl53l8_ioctl_set_integration_time_us(p_module, p_module->integration); + mutex_unlock(&p_module->mutex); + if (status != VL53L5_ERROR_NONE) { + status = vl53l5_read_device_error(&p_module->stdev, status); + vl53l8_k_log_error("IT Fail %d", status); + } else + vl53l8_k_log_info("IT %d", p_module->integration); +#else + status = vl53l8_ioctl_set_integration_time_us(p_module, p); +#endif + break; + case VL53L8_IOCTL_SET_ASZ_TUNING: + vl53l8_k_log_debug("VL53L8_IOCTL_SET_ASZ_TUNING"); + status = vl53l8_ioctl_set_asz_tuning(p_module, p); + break; +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE + case VL53L8_IOCTL_SET_GLARE_FILTER_TUNING: + vl53l8_k_log_debug("VL53L8_IOCTL_SET_GLARE_FILTER_TUNING"); + status = vl53l8_ioctl_set_glare_filter_tuning(p_module, p); + break; + case VL53L8_IOCTL_SET_TRANSFER_SPEED_HZ: + vl53l8_k_log_debug("VL53L8_IOCTL_SET_TRANSFER_SPEED_HZ"); + status = vl53l8_ioctl_set_transfer_speed_hz(p_module, p); + break; +#endif + default: + status = -EINVAL; + } + +exit: + return status; +} + +int vl53l8_k_open(struct inode *inode, struct file *file) +{ + int status = VL53L5_ERROR_NONE; + struct vl53l8_k_module_t *p_module = NULL; + + LOG_FUNCTION_START(""); + + p_module = container_of(file->private_data, struct vl53l8_k_module_t, + miscdev); + + vl53l8_k_log_debug("Get SPI bus"); + + kref_get(&p_module->spi_info.ref); + + LOG_FUNCTION_END(status); + +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + vl53l8_k_store_error(p_module, status); +#endif + status = vl53l8_k_convert_error_to_linux_error(status); + return status; +} + +int vl53l8_k_release(struct inode *inode, struct file *file) +{ + int status = VL53L5_ERROR_NONE; + struct vl53l8_k_module_t *p_module = NULL; + + LOG_FUNCTION_START(""); + + p_module = container_of(file->private_data, struct vl53l8_k_module_t, + miscdev); + + kref_put(&p_module->spi_info.ref, memory_release); + + LOG_FUNCTION_END(status); + +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + vl53l8_k_store_error(p_module, status); +#endif + status = vl53l8_k_convert_error_to_linux_error(status); + return status; +} + +long vl53l8_k_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int status = VL53L5_ERROR_NONE; + struct vl53l8_k_module_t *p_module = NULL; + + LOG_FUNCTION_START(""); + + p_module = container_of(file->private_data, struct vl53l8_k_module_t, + miscdev); + + vl53l8_k_log_debug("Lock"); + + status = vl53l8_k_ioctl_handler(p_module, cmd, arg, (void __user *)arg); + + vl53l8_k_log_debug("unLock"); + + LOG_FUNCTION_END(status); +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + vl53l8_k_store_error(p_module, status); +#else + vl53l8_k_store_error(p_module, status); +#ifdef CONFIG_SENSORS_LAF_FAILURE_DEBUG + vl53l8_last_error_counter(p_module, p_module->last_driver_error); +#endif +#endif + status = vl53l8_k_convert_error_to_linux_error(status); + return status; +} + +void vl53l8_k_cleanup(struct vl53l8_k_module_t *p_module) +{ + LOG_FUNCTION_START(""); + + vl53l8_k_log_debug("Unregistering misc device"); + cleanup_miscdev(p_module); + + if (p_module->state_preset == VL53L8_STATE_RANGING) { + vl53l8_k_log_debug("Stop ranging"); + (void)vl53l5_stop(&p_module->stdev, &p_module->ranging_flags); +#ifdef VL53L8_INTERRUPT + vl53l8_k_log_debug("Stop interrupt"); + vl53l8_k_stop_interrupt(p_module); +#endif + + } + + (void)vl53l8_ioctl_term(p_module); + + vl53l8_k_log_debug("Acquiring lock"); + mutex_lock(&p_module->mutex); +#ifdef STM_VL53L8_SUPPORT_LEGACY_CODE + vl53l8_k_log_debug("Deallocating dev id"); + deallocate_dev_id(p_module->id); + + vl53l8_k_log_debug("Setting state flags"); + p_module->state_preset = VL53L8_STATE_NOT_PRESENT; + + vl53l8_k_log_debug("Platform terminate"); + vl53l5_platform_terminate(&p_module->stdev); +#endif + + vl53l8_k_log_debug("Releasing all gpios"); + vl53l8_k_release_gpios(p_module); + + vl53l8_k_log_debug("Releasing mutex"); + mutex_unlock(&p_module->mutex); + + LOG_FUNCTION_END(0); +} + +int vl53l8_k_setup(struct vl53l8_k_module_t *p_module) +{ + int status = 0; + + LOG_FUNCTION_START(""); +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + vl53l8_k_log_debug("Acquire device id"); + allocate_dev_id(p_module); + if (p_module->id < 0) { + vl53l8_k_log_error("Failed"); + status = -EPERM; + goto done; + } +#endif + + vl53l8_k_log_debug("Init device mutex"); + mutex_init(&p_module->mutex); + mutex_init(&p_module->cal_mutex); + + vl53l8_k_log_debug("Acquired lock"); + mutex_lock(&p_module->mutex); + + vl53l8_k_log_debug("Set default sleep time"); + p_module->polling_sleep_time_ms = VL53L8_K_SLEEP_TIME_MS; + p_module->range_mode = VL53L8_RANGE_SERVICE_DEFAULT; + p_module->transfer_speed_hz = STM_VL53L8_SPI_DEFAULT_TRANSFER_SPEED_HZ; + +#ifdef VL53L8_INTERRUPT + p_module->range_mode = VL53L8_RANGE_SERVICE_INTERRUPT; +#endif + vl53l8_k_log_debug("Range mode %d", p_module->range_mode); + + vl53l8_k_log_debug("INIT waitqueue"); + INIT_LIST_HEAD(&p_module->reader_list); + init_waitqueue_head(&p_module->wait_queue); + + vl53l8_k_log_debug("Set state flags"); + p_module->state_preset = VL53L8_STATE_PRESENT; + + vl53l8_k_log_debug("Releasing mutex"); + mutex_unlock(&p_module->mutex); + + status = setup_miscdev(p_module); + if (status != 0) { + vl53l8_k_log_error("Misc dev setup failed: %d", status); + goto done; + } + +done: + LOG_FUNCTION_END(status); + return status; +} + +static int __init vl53l8_k_init(void) +{ + int status = 0; + + LOG_FUNCTION_START(""); + + vl53l8_k_log_info("Init %s driver", VL53L8_K_DRIVER_NAME); +#ifdef STM_VL53L5_SUPPORT_SEC_CODE + global_p_module = NULL; + global_p_module = kzalloc(sizeof(struct vl53l8_k_module_t), GFP_KERNEL); + if (!global_p_module) + vl53l8_k_log_error("Failed."); + + vl53l8_k_prelaod(global_p_module); + vl53l8_k_log_debug("Init spi bus"); + status = spi_register_driver(&vl53l8_k_spi_driver); + if (status != 0) { + vl53l8_k_log_error("Failed init bus: %d", status); + if (global_p_module != NULL) + kfree(global_p_module); + goto done; + } +#endif +#ifdef STM_VL53L5_SUPPORT_LEGACY_CODE + vl53l8_k_log_debug("Init spi bus"); + status = spi_register_driver(&vl53l8_k_spi_driver); + if (status != 0) { + vl53l8_k_log_error("Failed init bus: %d", status); + goto done; + } +#endif + + vl53l8_k_log_info("Kernel driver version: %d.%d.%d.%d", + VL53L8_K_VER_MAJOR, + VL53L8_K_VER_MINOR, + VL53L8_K_VER_BUILD, + VL53L8_K_VER_REVISION); + +done: + LOG_FUNCTION_END(status); + status = vl53l8_k_convert_error_to_linux_error(status); + return status; +} + +static void __exit vl53l8_k_exit(void) +{ + LOG_FUNCTION_START(""); + + vl53l8_k_log_info("Exiting %s driver", VL53L8_K_DRIVER_NAME); + + vl53l8_k_log_debug("Exiting spi bus"); + spi_unregister_driver(&vl53l8_k_spi_driver); + vl53l8_k_log_debug("Cleaning up"); + vl53l8_k_clean_up_spi(); + + LOG_FUNCTION_END(0); +} + +module_init(vl53l8_k_init); +module_exit(vl53l8_k_exit); + +MODULE_DESCRIPTION("Sample VL53L8 ToF client driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/sensors/vl53l8/src/vl53l8_k_range_wait_handler.c b/drivers/sensors/vl53l8/src/vl53l8_k_range_wait_handler.c new file mode 100644 index 000000000000..65283c3e8f14 --- /dev/null +++ b/drivers/sensors/vl53l8/src/vl53l8_k_range_wait_handler.c @@ -0,0 +1,123 @@ +/******************************************************************************* +* Copyright (c) 2022, STMicroelectronics - All Rights Reserved +* +* This file is part of VL53L8 Kernel Driver and is dual licensed, +* either 'STMicroelectronics Proprietary license' +* or 'BSD 3-clause "New" or "Revised" License' , at your option. +* +******************************************************************************** +* +* 'STMicroelectronics Proprietary license' +* +******************************************************************************** +* +* License terms: STMicroelectronics Proprietary in accordance with licensing +* terms at www.st.com/sla0081 +* +* STMicroelectronics confidential +* Reproduction and Communication of this document is strictly prohibited unless +* specifically authorized in writing by STMicroelectronics. +* +* +******************************************************************************** +* +* Alternatively, VL53L8 Kernel Driver may be distributed under the terms of +* 'BSD 3-clause "New" or "Revised" License', in which case the following +* provisions apply instead of the ones mentioned above : +* +******************************************************************************** +* +* License terms: BSD 3-clause "New" or "Revised" License. +* +* 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. Neither the name of the copyright holder nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* 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 HOLDER 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 "vl53l8_k_module_data.h" +#include "vl53l8_k_error_codes.h" +#include "vl53l8_k_logging.h" +#include "vl53l8_k_range_wait_handler.h" +#include "vl53l5_platform.h" + +int vl53l8_k_check_data_ready(struct vl53l8_k_module_t *p_module, int *p_ready) +{ + int status = VL53L5_ERROR_NONE; + + LOG_FUNCTION_START(""); + + p_module->polling_count++; + + status = vl53l5_check_data_ready(&p_module->stdev); + if (status == VL53L5_ERROR_NONE) { + *p_ready = 1; + } else { + *p_ready = 0; + if (status == VL53L5_NO_NEW_RANGE_DATA_ERROR || + status == VL53L5_TOO_HIGH_AMBIENT_WARNING) + status = 0; + else + + status = vl53l5_read_device_error(&p_module->stdev, + status); + } + + if (status != VL53L5_ERROR_NONE) + status = vl53l5_read_device_error(&p_module->stdev, status); + + LOG_FUNCTION_END(status); + return status; +} + +int vl53l8_k_check_for_timeout(struct vl53l8_k_module_t *p_module, + int timeout_ms, + int *p_timeout_occurred) +{ + int status = VL53L5_ERROR_NONE; + unsigned int current_time_ms = 0; + + LOG_FUNCTION_START(""); + + status = vl53l5_get_tick_count(&p_module->stdev, ¤t_time_ms); + if (status != VL53L5_ERROR_NONE) + goto out; + + status = vl53l5_check_for_timeout( + &p_module->stdev, p_module->polling_start_time_ms, + current_time_ms, timeout_ms); + if (status == VL53L5_ERROR_TIME_OUT) + *p_timeout_occurred = 1; + else + *p_timeout_occurred = 0; + +out: + + if (status != VL53L5_ERROR_NONE) + status = vl53l5_read_device_error(&p_module->stdev, status); + + LOG_FUNCTION_END(status); + return status; +} diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 67b3cd43ad8e..62a17402ba45 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -1187,4 +1187,10 @@ config QCOM_SECURE_HIBERNATION pages at the kernel. If not sure, say N. +config SEC_CDSP_NO_CRASH_FOR_ENG + bool "crash trigger for USER/USERDEBUG only" + default n + help + This option protects crash for the ENG Binary. + endmenu diff --git a/drivers/soc/qcom/adsp_sleepmon.c b/drivers/soc/qcom/adsp_sleepmon.c index bc4c047804bd..d35397cf8607 100644 --- a/drivers/soc/qcom/adsp_sleepmon.c +++ b/drivers/soc/qcom/adsp_sleepmon.c @@ -41,6 +41,14 @@ #include #include <../../remoteproc/qcom_common.h> #include +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) && !IS_ENABLED(CONFIG_SEC_FACTORY) +#include +#include +#include +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_AUDIO) +#include +#endif #define ADSPSLEEPMON_SMEM_ADSP_PID 2 #define ADSPSLEEPMON_SLEEPSTATS_ADSP_SMEM_ID 606 @@ -259,6 +267,15 @@ struct sleepmon_request { bool busy; }; +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) && !IS_ENABLED(CONFIG_SEC_FACTORY) +struct sleepmon_ap_sleep_check { + struct timespec64 suspend_ts; + struct timespec64 resume_ts; + u32 no_sleep_count; + u32 frequent_wakeup_count; +}; +#endif + struct adspsleepmon { bool b_enable; bool timer_event; @@ -275,6 +292,10 @@ struct adspsleepmon { bool b_config_adsp_panic_lpm_overall; bool b_config_adsp_ssr_enable; bool b_rpmsg_register; +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) && !IS_ENABLED(CONFIG_SEC_FACTORY) + bool b_config_enable_adsp_sleep_checker_ss; + u32 adsp_crash_ssr; +#endif u32 lpm_wait_time; u32 lpi_wait_time; u32 lpm_wait_time_overall; @@ -309,6 +330,10 @@ struct adspsleepmon { struct dentry *debugfs_read_adsp_panic_state; phandle adsp_rproc_phandle; struct rproc *adsp_rproc; +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) && !IS_ENABLED(CONFIG_SEC_FACTORY) + struct sleepmon_ap_sleep_check ap_sleep_checker; + struct sleep_stats suspend_prepare_stats; +#endif }; static struct adspsleepmon g_adspsleepmon; @@ -422,6 +447,10 @@ static int adspsleepmon_suspend_notify(struct notifier_block *nb, * Not acquiring mutex here, see if it is needed! */ pr_info("PM_POST_SUSPEND\n"); +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) && !IS_ENABLED(CONFIG_SEC_FACTORY) + g_adspsleepmon.ap_sleep_checker.resume_ts = + ktime_to_timespec64(ktime_get_boottime()); +#endif if (!g_adspsleepmon.audio_stats.num_sessions || (g_adspsleepmon.audio_stats.num_sessions == g_adspsleepmon.audio_stats.num_lpi_sessions)) { @@ -430,6 +459,17 @@ static int adspsleepmon_suspend_notify(struct notifier_block *nb, } break; } +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) && !IS_ENABLED(CONFIG_SEC_FACTORY) + case PM_SUSPEND_PREPARE: + { + memcpy(&g_adspsleepmon.suspend_prepare_stats, + g_adspsleepmon.lpm_stats, + sizeof(struct sleep_stats)); + g_adspsleepmon.ap_sleep_checker.suspend_ts = + ktime_to_timespec64(ktime_get_boottime()); + break; + } +#endif default: /* * Not handling other PM states, just return @@ -1075,6 +1115,108 @@ static void adspsleepmon_lpm_adsp_panic_overall(void) } } +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) && !IS_ENABLED(CONFIG_SEC_FACTORY) +extern void send_ssc_recovery_command(int type); +static void adspsleepmon_adsp_sleep_check_panic(void) +{ + pr_err("adsp_crash_ssr:%u \n", g_adspsleepmon.adsp_crash_ssr); + if (g_adspsleepmon.adsp_crash_ssr == 1) { // panic + panic("ADSP sleep issue detected, panic"); + } else if (g_adspsleepmon.adsp_crash_ssr == 2) { // ssr_dump + send_ssc_recovery_command(1); + } else if (g_adspsleepmon.adsp_crash_ssr == 3) { // ssr + send_ssc_recovery_command(0); + } else { //nothing + pr_err("ADSP sleep issue detected, But ignore\n"); + } +} + +static void sleepmon_lpm_dsp_sleep_check(struct sleep_stats* curr_lpm_stats) +{ + time64_t diff_kts = -1; + + if (g_adspsleepmon.ap_sleep_checker.resume_ts.tv_sec > + g_adspsleepmon.ap_sleep_checker.suspend_ts.tv_sec) + diff_kts = g_adspsleepmon.ap_sleep_checker.resume_ts.tv_sec - + g_adspsleepmon.ap_sleep_checker.suspend_ts.tv_sec; + + // check when ap sleep more than 10s + if (diff_kts > 10) { + struct dsppm_stats curr_dsppm_stats; + struct sysmon_event_stats sysmon_event_stats; + bool is_audio_active = false; + + memcpy(&curr_dsppm_stats,g_adspsleepmon.dsppm_stats, + sizeof(struct dsppm_stats)); + is_audio_active = sleepmon_is_audio_active(&curr_dsppm_stats); + memcpy(&sysmon_event_stats, + g_adspsleepmon.sysmon_event_stats, + sizeof(struct sysmon_event_stats)); + + pr_info("suspend ts:%d, resume ts:%d, diff_kts:%d, a:%d, l:%d\n", + (int)g_adspsleepmon.ap_sleep_checker.suspend_ts.tv_sec, + (int)g_adspsleepmon.ap_sleep_checker.resume_ts.tv_sec, + (int)diff_kts, (int)is_audio_active, + (int)sysmon_event_stats.sleep_latency); + + if (!is_audio_active && sysmon_event_stats.sleep_latency == 0) { + int wakeup_rate = 0; + u32 diff_count = 0; + u64 accumulated; + + pr_info("count:%d:%d, no_sleep_count:%d \n", + (int)curr_lpm_stats->count, + (int)g_adspsleepmon.suspend_prepare_stats.count, + (int)g_adspsleepmon.ap_sleep_checker.no_sleep_count); + // no adsp island + accumulated = curr_lpm_stats->accumulated; + if (curr_lpm_stats->last_entered_at > + curr_lpm_stats->last_exited_at) { + accumulated += arch_timer_read_counter() - + curr_lpm_stats->last_entered_at; + } + + pr_info("accumulated:%u:%u, %u:%u \n", + (uint32_t)(g_adspsleepmon.suspend_prepare_stats.accumulated >> 32), + (uint32_t)(g_adspsleepmon.suspend_prepare_stats.accumulated & 0xFFFFFFFF), + (uint32_t)(accumulated >> 32), + (uint32_t)(accumulated & 0xFFFFFFFF)); + + if (curr_lpm_stats->count != 0 && + (g_adspsleepmon.suspend_prepare_stats.count == + curr_lpm_stats->count) && + (g_adspsleepmon.suspend_prepare_stats.accumulated == + accumulated)) { + g_adspsleepmon.ap_sleep_checker.no_sleep_count++; + if (g_adspsleepmon.ap_sleep_checker.no_sleep_count > 2) { + panic("ADSP sleep issue detected, no adsp island\n"); + adspsleepmon_adsp_sleep_check_panic(); + } + } else { + g_adspsleepmon.ap_sleep_checker.no_sleep_count = 0; + } + + // frequent adsp wakeup + if (curr_lpm_stats->count > g_adspsleepmon.suspend_prepare_stats.count) + diff_count = curr_lpm_stats->count - + g_adspsleepmon.suspend_prepare_stats.count; + if (diff_count > 0) + wakeup_rate = (int)(diff_count/diff_kts); + + pr_info("diff_count:%d, wakeup_rate:%d \n", + (int)diff_count, wakeup_rate); + // panic if adsp wakeup more than 50hz from island. + if (wakeup_rate > 50) { + panic("ADSP sleep issue detected, frequent adsp wakeup\n"); + adspsleepmon_adsp_sleep_check_panic(); + } + } else { + g_adspsleepmon.ap_sleep_checker.no_sleep_count = 0; + } + } +} +#endif + static void sleepmon_lpm_exception_check(u64 curr_timestamp, u64 elapsed_time) { struct sleep_stats curr_lpm_stats; @@ -1122,10 +1264,20 @@ static void sleepmon_lpm_exception_check(u64 curr_timestamp, u64 elapsed_time) ((curr_lpm_stats.accumulated - g_adspsleepmon.backup_lpm_stats.accumulated) / ADSPSLEEPMON_SYS_CLK_TICKS_PER_MILLISEC)); +#if IS_ENABLED(CONFIG_SEC_FACTORY) + if (sysmon_event_stats.sleep_latency == 5000) + panic("Detected ADSP sleep issue"); +#endif is_audio_active = sleepmon_is_audio_active(&curr_dsppm_stats); sleepmon_get_dsppm_clients(); - +#if IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_AUDIO) + sdp_info_print("Detected ADSP sleep issue:\n"); + sdp_info_print("ADSP clock: %u, sleep latency: %u, is_audio_active: %d\n", + sysmon_event_stats.core_clk, + sysmon_event_stats.sleep_latency, + is_audio_active); +#endif if ((g_adspsleepmon.b_panic_lpm || g_adspsleepmon.b_config_adsp_panic_lpm) && is_audio_active) @@ -1158,6 +1310,11 @@ static void sleepmon_lpm_exception_check(u64 curr_timestamp, u64 elapsed_time) sizeof(struct sleep_stats)); g_adspsleepmon.backup_lpm_timestamp = __arch_counter_get_cntvct(); +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) && !IS_ENABLED(CONFIG_SEC_FACTORY) + if (g_adspsleepmon.b_config_enable_adsp_sleep_checker_ss) + if (!g_adspsleepmon.timer_event && g_adspsleepmon.suspend_event) + sleepmon_lpm_dsp_sleep_check(&curr_lpm_stats); +#endif } static void sleepmon_lpi_exception_check(u64 curr_timestamp, u64 elapsed_time) @@ -1209,6 +1366,13 @@ static void sleepmon_lpi_exception_check(u64 curr_timestamp, u64 elapsed_time) is_audio_active = sleepmon_is_audio_active(&curr_dsppm_stats); sleepmon_get_dsppm_clients(); +#if IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_AUDIO) + sdp_info_print("Detected ADSP LPI issue:\n"); + sdp_info_print("ADSP clock: %u, sleep latency: %u, is_audio_active: %d\n", + sysmon_event_stats.core_clk, + sysmon_event_stats.sleep_latency, + is_audio_active); +#endif sleepmon_send_lpi_issue_command(); } } @@ -1789,6 +1953,12 @@ static int adspsleepmon_driver_probe(struct platform_device *pdev) g_adspsleepmon.b_config_adsp_panic_lpm_overall = of_property_read_bool(dev->of_node, "qcom,enable_adsp_panic_lpm_overall"); +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) && !IS_ENABLED(CONFIG_SEC_FACTORY) + g_adspsleepmon.b_config_enable_adsp_sleep_checker_ss = of_property_read_bool(dev->of_node, + "qcom,enable_adsp_sleep_checker_ss"); + of_property_read_u32(dev->of_node, "qcom,adsp_crash_ssr_ss", + &g_adspsleepmon.adsp_crash_ssr); +#endif of_property_read_u32(dev->of_node, "qcom,wait_time_lpm", &g_adspsleepmon.lpm_wait_time); diff --git a/drivers/soc/qcom/dcc_v2.c b/drivers/soc/qcom/dcc_v2.c index b958bc6f3ec2..4dfa9277aa79 100644 --- a/drivers/soc/qcom/dcc_v2.c +++ b/drivers/soc/qcom/dcc_v2.c @@ -1901,6 +1901,8 @@ static void dcc_sram_dev_exit(struct dcc_drvdata *drvdata) dcc_sram_dev_deregister(drvdata); } +static bool is_valid_for_sec_debug_level(const struct device_node *np); + static int dcc_dt_parse(struct dcc_drvdata *drvdata, struct device_node *np) { int i, ret = -1; @@ -1909,6 +1911,12 @@ static int dcc_dt_parse(struct dcc_drvdata *drvdata, struct device_node *np) uint32_t curr_link_list; const char *data_sink; + if (!is_valid_for_sec_debug_level(np)) { + dev_warn(drvdata->dev, "List not suitable for this debug level (%s)\n", + np->name); + return 0; /* should return '0' to keep going. */ + } + ret = of_property_read_u32(np, "qcom,curr-link-list", &curr_link_list); if (ret) @@ -2390,3 +2398,22 @@ module_platform_driver(dcc_driver); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("MSM data capture and compare engine"); + +#if IS_ENABLED(CONFIG_SEC_QC_DEBUG) +#include +#include + +static bool is_valid_for_sec_debug_level(const struct device_node *node) +{ + unsigned int sec_dbg_level = sec_debug_level(); + int err; + + err = sec_of_test_debug_level(node, "sec,debug_level", sec_dbg_level); + if (err == -EINVAL) + return false; + + return true; +} +#else +static bool is_valid_for_sec_debug_level(const struct device_node *node) { return true; } +#endif diff --git a/drivers/soc/qcom/memory_dump_v2.c b/drivers/soc/qcom/memory_dump_v2.c index 9b00ffc70b36..f10fe5c0bd86 100644 --- a/drivers/soc/qcom/memory_dump_v2.c +++ b/drivers/soc/qcom/memory_dump_v2.c @@ -1069,6 +1069,9 @@ static int cpuss_dump_init(struct platform_device *pdev, return initialized; } +static bool test_sec_debug_level(const struct device_node *node); +static bool test_sec_eneable_any_debug_level(const struct device_node *child_node, bool matched_debug_level); + #define MSM_DUMP_DATA_SIZE sizeof(struct msm_dump_data) static int mem_dump_alloc(struct platform_device *pdev) { @@ -1087,6 +1090,7 @@ static int mem_dump_alloc(struct platform_device *pdev) uint32_t ns_vm_perms[] = {PERM_READ | PERM_WRITE}; u64 shm_bridge_handle; int initialized = 0; + bool matched_debug_level = test_sec_debug_level(node); if (mem_dump_reserve_mem(&pdev->dev) != 0) return -ENOMEM; @@ -1094,6 +1098,12 @@ static int mem_dump_alloc(struct platform_device *pdev) /* For dump table registration with IMEM */ total_size = sizeof(struct msm_dump_table) * 2; for_each_available_child_of_node(node, child_node) { + if (!test_sec_eneable_any_debug_level(child_node, matched_debug_level)) { + dev_dbg(&pdev->dev, "Skip for current debug levle - %s\n", + child_node->name); + continue; + } + ret = of_property_read_u32(child_node, "qcom,dump-size", &size); if (ret) { dev_err(&pdev->dev, "Unable to find size for %s\n", @@ -1146,6 +1156,9 @@ static int mem_dump_alloc(struct platform_device *pdev) dump_vaddr += (sizeof(struct msm_dump_table) * 2); phys_addr += (sizeof(struct msm_dump_table) * 2); for_each_available_child_of_node(node, child_node) { + if (!test_sec_eneable_any_debug_level(child_node, matched_debug_level)) + continue; + ret = of_property_read_u32(child_node, "qcom,dump-size", &size); if (ret) continue; @@ -1223,3 +1236,46 @@ module_platform_driver(mem_dump_driver); MODULE_DESCRIPTION("Memory Dump V2 Driver"); MODULE_LICENSE("GPL v2"); + +#if IS_ENABLED(CONFIG_SEC_QC_SUMMARY) +#include + +void sec_qc_summary_set_msm_memdump_info(struct sec_qc_summary_data_apss *apss) +{ + apss->msm_memdump_paddr = (uint64_t)memdump.table_phys; + pr_info("%s : 0x%llx\n", __func__, apss->msm_memdump_paddr); +} +EXPORT_SYMBOL(sec_qc_summary_set_msm_memdump_info); +#endif + +#if IS_ENABLED(CONFIG_SEC_QC_DEBUG) +#include +#include + +static bool test_sec_debug_level(const struct device_node *node) +{ + unsigned int sec_dbg_level = sec_debug_level(); + int err; + + err = sec_of_test_debug_level(node, "sec,debug_level", sec_dbg_level); + if (err == -EINVAL) + return false; + + return true; +} + +static bool test_sec_eneable_any_debug_level(const struct device_node *child_node, + bool matched_debug_level) +{ + if (matched_debug_level) + return true; + + if (of_property_read_bool(child_node, "sec,eneable-any_debug_level")) + return true; + + return false; +} +#else +static bool test_sec_debug_level(const struct device_node *node) { return true; } +static bool test_sec_eneable_any_debug_level(const struct device_node *child_node, bool matched_debug_level) { return true; } +#endif diff --git a/drivers/soc/qcom/minidump_log.c b/drivers/soc/qcom/minidump_log.c index 5c2b8493d730..59f07540bcdd 100644 --- a/drivers/soc/qcom/minidump_log.c +++ b/drivers/soc/qcom/minidump_log.c @@ -29,6 +29,9 @@ #include #include #include "debug_symbol.h" + +#include + #ifdef CONFIG_QCOM_MINIDUMP_PSTORE #include #include @@ -1519,7 +1522,7 @@ static void register_pstore_info(void) } #endif -int msm_minidump_log_init(void) +static inline int __msm_minidump_log_init(void) { register_kernel_sections(); is_vmap_stack = IS_ENABLED(CONFIG_VMAP_STACK); @@ -1545,3 +1548,11 @@ int msm_minidump_log_init(void) #endif return 0; } + +int msm_minidump_log_init(void) +{ + if (!sec_debug_is_enabled()) + return 0; + + return __msm_minidump_log_init(); +} diff --git a/drivers/soc/qcom/qcom_stats.c b/drivers/soc/qcom/qcom_stats.c index 4b9bb3141f95..8e6e32d61ead 100644 --- a/drivers/soc/qcom/qcom_stats.c +++ b/drivers/soc/qcom/qcom_stats.c @@ -23,6 +23,18 @@ #include #include +#if IS_ENABLED(CONFIG_SEC_PM) +#include + +#define MAX_BUF_LEN 512 +#define MSM_ARCH_TIMER_FREQ 19200000 +#define GET_SEC(A) ((A) / (MSM_ARCH_TIMER_FREQ)) +#define GET_MSEC(A) (((A) / (MSM_ARCH_TIMER_FREQ / 1000)) % 1000) +#endif /* CONFIG_SEC_PM */ +#if IS_ENABLED(CONFIG_SEC_PM_LOG) +#include +#endif + #define RPM_DYNAMIC_ADDR 0x14 #define RPM_DYNAMIC_ADDR_MASK 0xFFFF @@ -175,6 +187,16 @@ struct island_stats { u32 reserved[3]; }; +#if IS_ENABLED(CONFIG_SEC_PM) +#define MAX_SLEEP_STATS_COUNT 10 +#define MAX_SLEEP_STATS_NAME 16 + +static char sys_names[MAX_SLEEP_STATS_COUNT][MAX_SLEEP_STATS_NAME]; + +static int max_subsys_count; +static int subsys_idx[MAX_SLEEP_STATS_COUNT]; +#endif + static bool subsystem_stats_debug_on; /* Subsystem stats before and after suspend */ static struct sleep_stats *b_subsystem_stats; @@ -184,6 +206,30 @@ static struct sleep_stats *b_system_stats; static struct sleep_stats *a_system_stats; static DEFINE_MUTEX(sleep_stats_mutex); +#define DSP_SLEEP_DEBUG_ON + +#if defined(DSP_SLEEP_DEBUG_ON) +#include +#include +#include +#define MAX_COUNT 10 + +struct blocking_notifier_head cdsp_event_chain; +EXPORT_SYMBOL(cdsp_event_chain); + +struct _dsp_entry { + char name[4]; + uint64_t entry_sec; + uint64_t entry_msec; + uint64_t prev_exit_sec; + uint64_t prev_exit_msec; + uint64_t error_count; + struct timespec64 interval; + int (*ssr)(void); +} DSP_ENTRY[1]; // 0 : CDSP, 1 : ADSP - adsp is disabled for the time being. + +#endif + static inline void get_sleep_stat_name(u32 type, char *stat_type) { int i; @@ -970,6 +1016,10 @@ static void qcom_create_soc_sleep_stat_files(struct dentry *root, void __iomem * get_sleep_stat_name(type, stat_type); debugfs_create_file(stat_type, 0400, root, &d[i], &qcom_soc_sleep_stats_fops); +#if IS_ENABLED(CONFIG_SEC_PM) + /* Store each system's name */ + strcpy(sys_names[i], stat_type); +#endif offset += sizeof(struct sleep_stats); if (d[i].appended_stats_avail) @@ -999,12 +1049,250 @@ static void qcom_create_subsystem_stat_files(struct dentry *root, debugfs_create_file(subsystems[j].name, 0400, root, (void *)&subsystems[j], &qcom_subsystem_sleep_stats_fops); +#if IS_ENABLED(CONFIG_SEC_PM) + /* Store each subsystem's idx */ + subsys_idx[max_subsys_count++] = j; +#endif break; } } } } +#if IS_ENABLED(CONFIG_SEC_PM) +u64 last_accumulated[5]; + +static void sec_sleep_stats_show(const char *annotation) +{ + char buf[MAX_BUF_LEN]; + char *buf_ptr = buf; + unsigned int duration_sec, duration_msec; + +#if IS_ENABLED(CONFIG_SEC_PM_LOG) + char pm_log_buf[MAX_BUF_LEN]; + char *pm_log_buf_ptr = pm_log_buf; +#endif + +#if defined(DSP_SLEEP_DEBUG_ON) + struct _dsp_entry *dsp_entry = NULL; + int is_debug_low = 0; + unsigned int debug_level = 0; +#endif + + char stat_type[sizeof(u32) + 1] = {0}; + size_t stats_offset = drv->config->stats_offset; + u32 offset = 0, type; + int i, j; + + bool is_exit = (!strcmp("exit", annotation)) ? true : false; + + mutex_lock(&sleep_stats_mutex); + + /* + * Print soc_stats + */ + if (drv->config->dynamic_offset) { + stats_offset = readl(drv->base + RPM_DYNAMIC_ADDR); + stats_offset &= RPM_DYNAMIC_ADDR_MASK; + } + + buf_ptr += sprintf(buf_ptr, "PM: %s: ", annotation); +#if IS_ENABLED(CONFIG_SEC_PM_LOG) + pm_log_buf_ptr += sprintf(pm_log_buf_ptr, "soc: %s: ", (is_exit ? "ex" : "en")); +#endif + + for (i = 0; i < drv->config->num_records; i++) { + struct sleep_stats stat; + u64 accumulated; + + /* START: Print soc_stat's name */ + drv->d[i].base = drv->base + offset + stats_offset; + + type = readl(drv->d[i].base); + for (j = 0; j < sizeof(u32); j++) { + stat_type[j] = type & 0xff; + type = type >> 8; + } + + strim(stat_type); + /* END: Print soc_stat's name */ + + /* START: get soc_stat's info */ + memcpy_fromio(&stat, drv->d[i].base, sizeof(stat)); + + accumulated = stat.accumulated; + if (stat.last_entered_at > stat.last_exited_at) + accumulated += arch_timer_read_counter() + - stat.last_entered_at; + + /* Check non-sleep issue */ + if (is_exit && accumulated == last_accumulated[i]) + buf_ptr += sprintf(buf_ptr, "*"); + last_accumulated[i] = accumulated; + + /* Calculate accumulated duration */ + duration_sec = GET_SEC(accumulated); + duration_msec = GET_MSEC(accumulated); + + buf_ptr += sprintf(buf_ptr, "%s(%d, %u.%u), ", + stat_type, + stat.count, + duration_sec, duration_msec); +#if IS_ENABLED(CONFIG_SEC_PM_LOG) + pm_log_buf_ptr += sprintf(pm_log_buf_ptr, "(%d, %u.%u)", + stat.count, duration_sec, (duration_msec / 100)); +#endif + /* END: get soc_stat's info */ + + /* Move to next soc_stat */ + offset += sizeof(struct sleep_stats); + if (drv->d[i].appended_stats_avail) + offset += sizeof(struct appended_stats); + } + buf_ptr += sprintf(buf_ptr, "\n"); + + /* + * Print subsystem_stats + */ + buf_ptr += sprintf(buf_ptr, "PM: %s: ", annotation); +#if IS_ENABLED(CONFIG_SEC_PM_LOG) + pm_log_buf_ptr += sprintf(pm_log_buf_ptr, "/"); +#endif + + for (i = 0; i < max_subsys_count; i++) { + struct subsystem_data *subsystem; + struct sleep_stats *stat; + u64 accumulated; + int idx = subsys_idx[i]; + + /* Get each subsystem's info */ + subsystem = &subsystems[idx]; + stat = qcom_smem_get(subsystem->pid, subsystem->smem_item, NULL); + if (IS_ERR(stat)) { + pr_err("%s: Failed to get qcom_smem for %s, ret=%d\n", __func__, + subsystems[idx].name, PTR_ERR(stat)); + + /* Even though getting info from smem is failed, next subsystem should be checked */ + continue; + } + + /* Calculate accumulated duration */ + accumulated = stat->accumulated; + if (stat->last_entered_at > stat->last_exited_at) + accumulated += arch_timer_read_counter() + - stat->last_entered_at; + + duration_sec = GET_SEC(accumulated); + duration_msec = GET_MSEC(accumulated); +#if defined(DSP_SLEEP_DEBUG_ON) +#if 0 + dsp_entry = (!strcmp(subsystem->name, "cdsp")) ? &DSP_ENTRY[0] : + (!strcmp(subsystem->name, "adsp") ? &DSP_ENTRY[1] : NULL); +#else + dsp_entry = (!strcmp(subsystem->name, "cdsp")) ? &DSP_ENTRY[0] : NULL; +#endif + if (dsp_entry != NULL) { + if (!is_exit) { + // entry + dsp_entry->entry_sec = duration_sec; + dsp_entry->entry_msec = duration_msec; + } else { + //exit + /* Error detected if exit duration is same as entry */ + if((duration_sec == dsp_entry->entry_sec && + duration_msec == dsp_entry->entry_msec) && + (duration_sec == dsp_entry->prev_exit_sec && + duration_msec == dsp_entry->prev_exit_msec)) { + struct timespec64 curr_kts = ktime_to_timespec64(ktime_get_boottime()); + if (dsp_entry->interval.tv_sec != 0) { + time64_t diff_kts = curr_kts.tv_sec - dsp_entry->interval.tv_sec; + if (diff_kts > 60) { // don't update error count within 1 min + dsp_entry->error_count++; + printk("entry error cnt : %d\n", dsp_entry->error_count); + dsp_entry->interval = ktime_to_timespec64(ktime_get_boottime()); + } + } else { + dsp_entry->interval = ktime_to_timespec64(ktime_get_boottime()); + } + } else { + dsp_entry->error_count = 0; + } + dsp_entry->prev_exit_sec = duration_sec; + dsp_entry->prev_exit_msec = duration_msec; + } + } +#endif + buf_ptr += sprintf(buf_ptr, "%s(%d, %u.%u), ", + subsystem->name, + stat->count, + duration_sec, duration_msec); +#if IS_ENABLED(CONFIG_SEC_PM_LOG) + pm_log_buf_ptr += sprintf(pm_log_buf_ptr, "(%d, %u.%u)", + stat->count, duration_sec, (duration_msec / 100)); +#endif + } + + buf_ptr--; + buf_ptr--; + buf_ptr += sprintf(buf_ptr, "\n"); + +#if IS_ENABLED(CONFIG_SEC_PM_LOG) + ss_power_print("%s\n", pm_log_buf); +#endif + mutex_unlock(&sleep_stats_mutex); + + pr_info("%s", buf); + +#if defined(DSP_SLEEP_DEBUG_ON) + // 0 : CDSP, 1 : ADSP + for (i = 0; i < sizeof(DSP_ENTRY) / sizeof(struct _dsp_entry); i++) { + dsp_entry = &DSP_ENTRY[i]; + if(dsp_entry->error_count > MAX_COUNT) { + debug_level = sec_debug_level(); + + switch (debug_level) { + case SEC_DEBUG_LEVEL_LOW: + is_debug_low = 1; + break; + case SEC_DEBUG_LEVEL_MID: + is_debug_low = 0; + break; + } + + if (!is_debug_low) { + pr_err("entry error cnt : %d\n", dsp_entry->error_count); + pr_err("Intentional crash for %s\n", dsp_entry->name); + BUG_ON(1); + } else { + dsp_entry->error_count = 0; + pr_err("reset entry error cnt : %d\n", dsp_entry->error_count); + pr_err("Intentional cdsp subsystem restart\n"); + blocking_notifier_call_chain(&cdsp_event_chain, 0, (void *)0x0); + } + } + } +#endif +} + +static void qcom_stats_debug_suspend_trace_probe(void *unused, + const char *action, int val, bool start) +{ + /* + * SUSPEND + * start(1), val(1), action(machine_suspend) + */ + if (start && val > 0 && !strcmp("machine_suspend", action)) + sec_sleep_stats_show("entry"); + + /* + * RESUME + *start(0), val(1), action(machine_suspend) + */ + if (!start && val > 0 && !strcmp("machine_suspend", action)) + sec_sleep_stats_show("exit"); +} +#endif + static int qcom_stats_probe(struct platform_device *pdev) { void __iomem *reg; @@ -1014,6 +1302,16 @@ static int qcom_stats_probe(struct platform_device *pdev) int i; int ret; +#if IS_ENABLED(CONFIG_SEC_PM) + /* Register callback for cheking subsystem stats */ + ret = register_trace_suspend_resume( + qcom_stats_debug_suspend_trace_probe, NULL); + if (ret) { + pr_err("%s: Failed to register suspend trace callback, ret=%d\n", + __func__, ret); + } +#endif + config = device_get_match_data(&pdev->dev); if (!config) return -ENODEV; @@ -1091,6 +1389,11 @@ static int qcom_stats_probe(struct platform_device *pdev) platform_set_drvdata(pdev, drv); +#if defined(DSP_SLEEP_DEBUG_ON) + BLOCKING_INIT_NOTIFIER_HEAD(&cdsp_event_chain); + strncpy(DSP_ENTRY[0].name, "cdsp", 4); +#endif + return 0; fail: @@ -1114,6 +1417,11 @@ static int qcom_stats_remove(struct platform_device *pdev) debugfs_remove_recursive(drv->root); +#if IS_ENABLED(CONFIG_SEC_PM) + unregister_trace_suspend_resume( + qcom_stats_debug_suspend_trace_probe, NULL); +#endif + return 0; } diff --git a/drivers/soc/qcom/qcom_wdt_core.c b/drivers/soc/qcom/qcom_wdt_core.c index bbf61af37b31..62b7aa1666f0 100644 --- a/drivers/soc/qcom/qcom_wdt_core.c +++ b/drivers/soc/qcom/qcom_wdt_core.c @@ -550,6 +550,8 @@ static void qcom_wdt_user_pet_bite(struct timer_list *t) } } +static void __wdog_pet_call_notifier_chain(struct msm_watchdog_data *wdog_dd); + static __ref int qcom_wdt_kthread(void *arg) { struct msm_watchdog_data *wdog_dd = arg; @@ -583,6 +585,8 @@ static __ref int qcom_wdt_kthread(void *arg) delay_time = msecs_to_jiffies(wdog_dd->pet_time); wdog_dd->ops->reset_wdt(wdog_dd); wdog_dd->last_pet = sched_clock(); + + __wdog_pet_call_notifier_chain(wdog_dd); } /* Check again before scheduling * Could have been changed on other cpu @@ -687,6 +691,8 @@ void qcom_wdt_trigger_bite(void) } EXPORT_SYMBOL(qcom_wdt_trigger_bite); +static void __wdog_bark_call_notifier_chain(struct msm_watchdog_data *wdog_dd); + static irqreturn_t qcom_wdt_bark_handler(int irq, void *dev_id) { struct msm_watchdog_data *wdog_dd = dev_id; @@ -706,6 +712,8 @@ static irqreturn_t qcom_wdt_bark_handler(int irq, void *dev_id) if (wdog_dd->freeze_in_progress) dev_info(wdog_dd->dev, "Suspend in progress\n"); + __wdog_bark_call_notifier_chain(wdog_dd); + md_dump_process(); qcom_wdt_trigger_bite(); @@ -916,3 +924,46 @@ EXPORT_SYMBOL(qcom_wdt_register); MODULE_DESCRIPTION("QCOM Watchdog Driver Core"); MODULE_LICENSE("GPL v2"); + +#if IS_ENABLED(CONFIG_SEC_QC_QCOM_WDT_CORE) +static ATOMIC_NOTIFIER_HEAD(wdog_pet_notifier_list); + +int qcom_wdt_pet_register_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_register(&wdog_pet_notifier_list, nb); +} +EXPORT_SYMBOL(qcom_wdt_pet_register_notifier); + +int qcom_wdt_pet_unregister_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_unregister(&wdog_pet_notifier_list, nb); +} +EXPORT_SYMBOL(qcom_wdt_pet_unregister_notifier); + +static void __wdog_pet_call_notifier_chain(struct msm_watchdog_data *wdog_dd) +{ + atomic_notifier_call_chain(&wdog_pet_notifier_list, 0, wdog_dd); +} + +static ATOMIC_NOTIFIER_HEAD(wdog_bark_notifier_list); + +int qcom_wdt_bark_register_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_register(&wdog_bark_notifier_list, nb); +} +EXPORT_SYMBOL(qcom_wdt_bark_register_notifier); + +int qcom_wdt_bark_unregister_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_unregister(&wdog_bark_notifier_list, nb); +} +EXPORT_SYMBOL(qcom_wdt_bark_unregister_notifier); + +static void __wdog_bark_call_notifier_chain(struct msm_watchdog_data *wdog_dd) +{ + atomic_notifier_call_chain(&wdog_bark_notifier_list, 0, wdog_dd); +} +#else +static void __wdog_pet_call_notifier_chain(struct msm_watchdog_data *wdog_dd) {} +static void __wdog_bark_call_notifier_chain(struct msm_watchdog_data *wdog_dd) {} +#endif diff --git a/drivers/soc/qcom/socinfo.c b/drivers/soc/qcom/socinfo.c index 3b3d87c5f84f..dfd2a2a5744c 100644 --- a/drivers/soc/qcom/socinfo.c +++ b/drivers/soc/qcom/socinfo.c @@ -18,6 +18,7 @@ #include #include +#include /* * SoC version type with major number in the upper 16 bits and minor @@ -30,6 +31,15 @@ #define SMEM_SOCINFO_BUILD_ID_LENGTH 32 #define SMEM_SOCINFO_CHIP_ID_LENGTH 32 +#define SMEM_IMAGE_VERSION_BLOCKS_COUNT 32 +#define SMEM_IMAGE_VERSION_SINGLE_BLOCK_SIZE 128 +#define SMEM_IMAGE_VERSION_SIZE 4096 +#define SMEM_IMAGE_VERSION_NAME_SIZE 75 +#define SMEM_IMAGE_VERSION_VARIANT_SIZE 20 +#define SMEM_IMAGE_VERSION_VARIANT_OFFSET SMEM_IMAGE_VERSION_NAME_SIZE +#define SMEM_IMAGE_VERSION_OEM_SIZE 32 +#define SMEM_IMAGE_VERSION_OEM_OFFSET (SMEM_IMAGE_VERSION_VARIANT_OFFSET+SMEM_IMAGE_VERSION_VARIANT_SIZE) + static uint32_t socinfo_format; static const char *sku; @@ -1179,8 +1189,81 @@ msm_get_feature_code(struct device *dev, } ATTR_DEFINE(feature_code); +static ssize_t +msm_get_crash(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret = 0; + int is_debug_low = 0; + + unsigned int debug_level = sec_debug_level(); + + switch (debug_level) { + case SEC_DEBUG_LEVEL_LOW: + is_debug_low = 1; + break; + case SEC_DEBUG_LEVEL_MID: + is_debug_low = 0; + break; + } + + if (!is_debug_low) { +#ifndef CONFIG_SEC_CDSP_NO_CRASH_FOR_ENG + BUG_ON(1); +#endif + } + return ret; +} +ATTR_DEFINE(crash); + /* End Sysfs Interfaces */ +static char *socinfo_get_image_version_base_address(void) +{ + size_t size; + + return qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_IMAGE_VERSION_TABLE, + &size); +} + +static ssize_t msm_get_images(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int pos = 0; + int image; + char *image_address; + + image_address = socinfo_get_image_version_base_address(); + if (IS_ERR_OR_NULL(image_address)) + return scnprintf(buf, PAGE_SIZE, "Unavailable"); + + *buf = '\0'; + for (image = 0; image < SMEM_IMAGE_VERSION_BLOCKS_COUNT; image++) { + if (*image_address == '\0') { + image_address += SMEM_IMAGE_VERSION_SINGLE_BLOCK_SIZE; + continue; + } + + pos += scnprintf(buf + pos, PAGE_SIZE - pos, "%d:\n", + image); + pos += scnprintf(buf + pos, PAGE_SIZE - pos, + "\tCRM:\t\t%-.75s\n", image_address); + pos += scnprintf(buf + pos, PAGE_SIZE - pos, + "\tVariant:\t%-.20s\n", + image_address + SMEM_IMAGE_VERSION_VARIANT_OFFSET); + pos += scnprintf(buf + pos, PAGE_SIZE - pos, + "\tVersion:\t%-.33s\n", + image_address + SMEM_IMAGE_VERSION_OEM_OFFSET); + + image_address += SMEM_IMAGE_VERSION_SINGLE_BLOCK_SIZE; + } + + return pos; +} + +ATTR_DEFINE(images); + static umode_t soc_info_attribute(struct kobject *kobj, struct attribute *attr, int index) @@ -1241,6 +1324,8 @@ static void socinfo_populate_sysfs(struct qcom_socinfo *qcom_socinfo) fallthrough; case SOCINFO_VERSION(0, 12): msm_custom_socinfo_attrs[i++] = &dev_attr_chip_family.attr; + msm_custom_socinfo_attrs[i++] = &dev_attr_crash.attr; + msm_custom_socinfo_attrs[i++] = &dev_attr_images.attr; fallthrough; case SOCINFO_VERSION(0, 11): case SOCINFO_VERSION(0, 10): diff --git a/drivers/staging/android/switch/Kconfig b/drivers/staging/android/switch/Kconfig new file mode 100755 index 000000000000..092cda599216 --- /dev/null +++ b/drivers/staging/android/switch/Kconfig @@ -0,0 +1,10 @@ +menuconfig ANDROID_SWITCH + tristate "Android Switch class support" + help + Say Y here to enable Android switch class support. + This allows monitoring switches by userspace via sysfs and uevent. + +config ANDROID_SWITCH_GPIO + tristate "Android GPIO Switch support" + help + Say Y here to enable GPIO based switch support. diff --git a/drivers/staging/android/switch/Makefile b/drivers/staging/android/switch/Makefile new file mode 100755 index 000000000000..ca55836756ca --- /dev/null +++ b/drivers/staging/android/switch/Makefile @@ -0,0 +1,5 @@ +# Android Switch Class Driver +subdir-ccflags-y := -Wformat +obj-$(CONFIG_ANDROID_SWITCH) += sec_switch_class.o +sec_switch_class-y := switch_class.o +sec_switch_class-$(CONFIG_ANDROID_SWITCH_GPIO) += switch_gpio.o diff --git a/drivers/staging/android/switch/switch_class.c b/drivers/staging/android/switch/switch_class.c new file mode 100755 index 000000000000..ba6a4d6b7f76 --- /dev/null +++ b/drivers/staging/android/switch/switch_class.c @@ -0,0 +1,176 @@ +/* + * switch_class.c + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * + * 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 + +struct class *switch_class; +static atomic_t device_count; + +static ssize_t state_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct switch_dev *sdev = (struct switch_dev *) + dev_get_drvdata(dev); + + if (sdev->print_state) { + int ret = sdev->print_state(sdev, buf); + + if (ret >= 0) + return ret; + } + return sprintf(buf, "%d\n", sdev->state); +} + +static ssize_t name_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct switch_dev *sdev = (struct switch_dev *) + dev_get_drvdata(dev); + + if (sdev->print_name) { + int ret = sdev->print_name(sdev, buf); + + if (ret >= 0) + return ret; + } + return sprintf(buf, "%s\n", sdev->name); +} + +static DEVICE_ATTR_RO(state); +static DEVICE_ATTR_RO(name); + +void switch_set_state(struct switch_dev *sdev, int state) +{ + char name_buf[120]; + char state_buf[120]; + char *prop_buf; + char *envp[3]; + int env_offset = 0; + int length; + + if (sdev->state != state) { + sdev->state = state; + + prop_buf = (char *)get_zeroed_page(GFP_KERNEL); + if (prop_buf) { + length = name_show(sdev->dev, NULL, prop_buf); + if (length > 0) { + if (prop_buf[length - 1] == '\n') + prop_buf[length - 1] = 0; + snprintf(name_buf, sizeof(name_buf), + "SWITCH_NAME=%s", prop_buf); + envp[env_offset++] = name_buf; + } + length = state_show(sdev->dev, NULL, prop_buf); + if (length > 0) { + if (prop_buf[length - 1] == '\n') + prop_buf[length - 1] = 0; + snprintf(state_buf, sizeof(state_buf), + "SWITCH_STATE=%s", prop_buf); + envp[env_offset++] = state_buf; + } + envp[env_offset] = NULL; + kobject_uevent_env(&sdev->dev->kobj, KOBJ_CHANGE, envp); + free_page((unsigned long)prop_buf); + } else { + pr_err("out of memory in %s\n", __func__); + kobject_uevent(&sdev->dev->kobj, KOBJ_CHANGE); + } + } +} +EXPORT_SYMBOL_GPL(switch_set_state); + +static int create_switch_class(void) +{ + if (!switch_class) { + switch_class = class_create(THIS_MODULE, "switch"); + if (IS_ERR(switch_class)) + return PTR_ERR(switch_class); + atomic_set(&device_count, 0); + } + + return 0; +} + +int switch_dev_register(struct switch_dev *sdev) +{ + int ret; + + if (!switch_class) { + ret = create_switch_class(); + if (ret < 0) + return ret; + } + + sdev->index = atomic_inc_return(&device_count); + sdev->dev = device_create(switch_class, NULL, + MKDEV(0, sdev->index), NULL, "%s", sdev->name); + if (IS_ERR(sdev->dev)) + return PTR_ERR(sdev->dev); + + ret = device_create_file(sdev->dev, &dev_attr_state); + if (ret < 0) + goto err_create_file_1; + ret = device_create_file(sdev->dev, &dev_attr_name); + if (ret < 0) + goto err_create_file_2; + + dev_set_drvdata(sdev->dev, sdev); + sdev->state = 0; + return 0; + +err_create_file_2: + device_remove_file(sdev->dev, &dev_attr_state); +err_create_file_1: + device_destroy(switch_class, MKDEV(0, sdev->index)); + pr_err("switch: Failed to register driver %s\n", sdev->name); + + return ret; +} +EXPORT_SYMBOL_GPL(switch_dev_register); + +void switch_dev_unregister(struct switch_dev *sdev) +{ + device_remove_file(sdev->dev, &dev_attr_name); + device_remove_file(sdev->dev, &dev_attr_state); + dev_set_drvdata(sdev->dev, NULL); + device_destroy(switch_class, MKDEV(0, sdev->index)); +} +EXPORT_SYMBOL_GPL(switch_dev_unregister); + +static int __init switch_class_init(void) +{ + return create_switch_class(); +} + +static void __exit switch_class_exit(void) +{ + class_destroy(switch_class); +} + +module_init(switch_class_init); +module_exit(switch_class_exit); + +MODULE_AUTHOR("Mike Lockwood "); +MODULE_DESCRIPTION("Switch class driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/android/switch/switch_gpio.c b/drivers/staging/android/switch/switch_gpio.c new file mode 100755 index 000000000000..b4f68e344e85 --- /dev/null +++ b/drivers/staging/android/switch/switch_gpio.c @@ -0,0 +1,173 @@ +/* + * switch_gpio.c + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * + * 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 + +struct gpio_switch_data { + struct switch_dev sdev; + unsigned int gpio; + const char *name_on; + const char *name_off; + const char *state_on; + const char *state_off; + int irq; + struct work_struct work; +}; + +static void gpio_switch_work(struct work_struct *work) +{ + int state; + struct gpio_switch_data *data = + container_of(work, struct gpio_switch_data, work); + + state = gpio_get_value(data->gpio); + switch_set_state(&data->sdev, state); +} + +static irqreturn_t gpio_irq_handler(int irq, void *dev_id) +{ + struct gpio_switch_data *switch_data = + (struct gpio_switch_data *)dev_id; + + schedule_work(&switch_data->work); + return IRQ_HANDLED; +} + +static ssize_t switch_gpio_print_state(struct switch_dev *sdev, char *buf) +{ + struct gpio_switch_data *switch_data = + container_of(sdev, struct gpio_switch_data, sdev); + const char *state; + + if (switch_get_state(sdev)) + state = switch_data->state_on; + else + state = switch_data->state_off; + + if (state) + return sprintf(buf, "%s\n", state); + return -1; +} + +static int gpio_switch_probe(struct platform_device *pdev) +{ + struct gpio_switch_platform_data *pdata = pdev->dev.platform_data; + struct gpio_switch_data *switch_data; + int ret = 0; + + if (!pdata) + return -EBUSY; + + switch_data = kzalloc(sizeof(struct gpio_switch_data), GFP_KERNEL); + if (!switch_data) + return -ENOMEM; + + switch_data->sdev.name = pdata->name; + switch_data->gpio = pdata->gpio; + switch_data->name_on = pdata->name_on; + switch_data->name_off = pdata->name_off; + switch_data->state_on = pdata->state_on; + switch_data->state_off = pdata->state_off; + switch_data->sdev.print_state = switch_gpio_print_state; + + ret = switch_dev_register(&switch_data->sdev); + if (ret < 0) + goto err_switch_dev_register; + + ret = gpio_request(switch_data->gpio, pdev->name); + if (ret < 0) + goto err_request_gpio; + + ret = gpio_direction_input(switch_data->gpio); + if (ret < 0) + goto err_set_gpio_input; + + INIT_WORK(&switch_data->work, gpio_switch_work); + + switch_data->irq = gpio_to_irq(switch_data->gpio); + if (switch_data->irq < 0) { + ret = switch_data->irq; + goto err_detect_irq_num_failed; + } + + ret = request_irq(switch_data->irq, gpio_irq_handler, + IRQF_TRIGGER_LOW, pdev->name, switch_data); + if (ret < 0) + goto err_request_irq; + + /* Perform initial detection */ + gpio_switch_work(&switch_data->work); + + return 0; + +err_request_irq: +err_detect_irq_num_failed: +err_set_gpio_input: + gpio_free(switch_data->gpio); +err_request_gpio: + switch_dev_unregister(&switch_data->sdev); +err_switch_dev_register: + kfree(switch_data); + + return ret; +} + +static int gpio_switch_remove(struct platform_device *pdev) +{ + struct gpio_switch_data *switch_data = platform_get_drvdata(pdev); + + cancel_work_sync(&switch_data->work); + gpio_free(switch_data->gpio); + switch_dev_unregister(&switch_data->sdev); + kfree(switch_data); + + return 0; +} + +static struct platform_driver gpio_switch_driver = { + .probe = gpio_switch_probe, + .remove = gpio_switch_remove, + .driver = { + .name = "switch-gpio", + .owner = THIS_MODULE, + }, +}; + +static int __init gpio_switch_init(void) +{ + return platform_driver_register(&gpio_switch_driver); +} + +static void __exit gpio_switch_exit(void) +{ + platform_driver_unregister(&gpio_switch_driver); +} + +module_init(gpio_switch_init); +module_exit(gpio_switch_exit); + +MODULE_AUTHOR("Mike Lockwood "); +MODULE_DESCRIPTION("GPIO Switch driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/sti/abc/Kconfig b/drivers/sti/abc/Kconfig new file mode 100644 index 000000000000..b87554b921be --- /dev/null +++ b/drivers/sti/abc/Kconfig @@ -0,0 +1,132 @@ +comment "Samsung ABC Options" +config SEC_ABC + tristate "Samsung ABC Feature" + depends on SEC_STI + default n + help + Samsung ABC(Abnormal Behavior Catcher) Driver Feature + This driver is to catch abnormal behavior in each device drivers + It depends on SEC_STI Feature + +comment "Samsung ABC Common Options" +config SEC_ABC_COMMON + tristate "Samsung ABC COMMON Feature" + depends on SEC_ABC + default n + help + Samsung ABC(Abnormal Behavior Catcher) Driver Common Feature + This config is for ABC Driver Core Code + It depends on SEC_ABC Feature + +comment "Samsung ABC Spec manager type1 options" +config SEC_ABC_SPEC_TYPE1 + tristate "Samsung ABC_SPEC_TYPE1 Feature" + depends on SEC_ABC + default n + help + Samsung ABC(Abnormal Behavior Catcher)Driver's Spec manager type1 Feature + It has logic and spec of judgment of error type1. + It depends on SEC_ABC Feature + +comment "Samsung ABC MOTTO Options" +config SEC_ABC_MOTTO + tristate "Samsung ABC MOTTO Feature" + depends on SEC_ABC + default n + help + Samsung ABC(Abnormal Behavior Catcher) MOTTO Function Feature + This config is to support MOTTO Function + It depends on SEC_ABC Feature + +comment "Samsung ABC_COMMON KUNIT Options" +config SEC_ABC_COMMON_KUNIT + tristate "Samsung ABC_COMMON KUNIT Feature" + depends on SEC_ABC && SEC_KUNIT + default n + help + Samsung ABC(Abnormal Behavior Catcher) KUNIT Function Feature + This config is to support ABC_COMMON KUNIT Function + It depends on SEC_ABC && SEC_KUNIT Feature + +comment "Samsung ABC Hub Options" +config SEC_ABC_HUB + tristate "Samsung ABC Hub Feature" + depends on SEC_ABC + default n + help + Samsung ABC(Abnormal Behavior Catcher) Hub Driver Feature + This config controls ABC Hub driver which supports ABC Driver + And it consists of abc_hub_core & abc_hub_cond & abc_hub_bootc. + It depends on SEC_ABC Feature + +comment "Samsung ABC Hub Core Options" +config SEC_ABC_HUB_CORE + tristate "Samsung ABC Hub Core Feature" + depends on SEC_ABC_HUB + default n + help + Samsung ABC(Abnormal Behavior Catcher) Hub Driver's Core Feature + This function controls sub modules for supporting ABC Driver + It depends on SEC_ABC_HUB Feature + +comment "Samsung ABC Hub Booting Time Check Options" +config SEC_ABC_HUB_BOOTC + tristate "Samsung ABC Hub Booting Time Check Feature" + depends on SEC_ABC_HUB + default n + help + Samsung BOOTC(Booting Time Check) Feature + This function is to check booting time + It is sub module of ABC Hub Driver + It depends on SEC_ABC_HUB Feature + +comment "Samsung ABC Hub Booting Time Check eng mode Options" +config SEC_ABC_HUB_BOOTC_ENG + tristate "Samsung ABC Hub Booting Time Check eng mode Feature" + depends on SEC_ABC_HUB_BOOTC + default n + help + Samsung BOOTC(Booting Time Check) eng mode Feature + This function is to apply eng binary booting time spec + It depends on SEC_ABC_HUB_BOOTC Feature + +comment "Samsung ABC_HUB KUNIT Options" +config SEC_ABC_HUB_KUNIT + tristate "Samsung ABC_HUB KUNIT Feature" + depends on SEC_ABC_HUB && SEC_KUNIT + default n + help + Samsung ABC(Abnormal Behavior Catcher) Hub KUNIT Function Feature + This config is to support ABC Hub KUNIT Function + It depends on SEC_ABC_HUB && SEC_KUNIT Feature + +comment "Samsung ABC Hub Connect Detect for ABC HUB" +config SEC_ABC_HUB_COND + tristate "Samsung ABC Hub Connect Detect Feature" + depends on SEC_ABC_HUB + help + Samsung COND(Connect Detect) Feature + This function is to detect connect h/w problem + It is sub module of ABC Hub Driver + It depends on SEC_ABC_HUB Feature + +comment "Samsung ABC Hub Connect Detect Kunit Test FEATURE" +config SEC_ABC_DETECT_KUNIT + tristate "Samsung Connector Detector Kunit TEST" + help + Feature for checking connector detection code . + This feature should be enabled only for testing while development. + +comment "Samsung ABC Hub Connect Detect FEATURE" +config SEC_ABC_DETECT_CONN + tristate "Samsung Connector Detector feature" + help + Feature for checking connector status. + This feature should be enabled in factory mode. + +comment "Samsung ABC Hub Connect Detect for QCOM, MTK" +config QCOM_SEC_ABC_DETECT + tristate "Samsung Connector Detector Qcom feature" + help + Feature for checking QCOM Chipset. + This feature should be enabled for QCOM chipset in factory mode. diff --git a/drivers/sti/abc/Makefile b/drivers/sti/abc/Makefile new file mode 100644 index 000000000000..7bd617499c1a --- /dev/null +++ b/drivers/sti/abc/Makefile @@ -0,0 +1,42 @@ +# Samsung sec ABC Feature +obj-$(CONFIG_SEC_ABC) += abc.o + +# Samsung sec ABC COMMON Feature +abc-$(CONFIG_SEC_ABC_COMMON) += abc_common.o + +abc-$(CONFIG_SEC_ABC_COMMON) += abc_spec_manager.o + +abc-$(CONFIG_SEC_ABC_SPEC_TYPE1) += abc_spec_manager_type1.o + +# Samsung sec ABC MOTTO Feature +abc-$(CONFIG_SEC_ABC_MOTTO) += abc_motto.o + +# Samsung sec ABC Kunit test Feature + + +GCOV_PROFILE_abc_common.o := $(CONFIG_SEC_KUNIT) + +GCOV_PROFILE_abc_spec_manager.o := $(CONFIG_SEC_KUNIT) + +GCOV_PROFILE_abc_spec_manager_type1.o := $(CONFIG_SEC_KUNIT) + +# Samsung sec ABC_HUB Feature +obj-$(CONFIG_SEC_ABC_HUB) += abc_hub.o + +# Samsung sec ABC_HUB Core Feature +abc_hub-$(CONFIG_SEC_ABC_HUB_CORE) += abc_hub_core.o + +# Samsung sec ABC_HUB Sub Module(Booting time check) Feature +abc_hub-$(CONFIG_SEC_ABC_HUB_BOOTC) += abc_hub_bootc.o + + +GCOV_PROFILE_abc_hub_core.o := $(CONFIG_SEC_KUNIT) + +GCOV_PROFILE_abc_hub_bootc.o := $(CONFIG_SEC_KUNIT) + +subdir-ccflags-y := -Wformat + +# Samsung Connector Check Feature +obj-$(CONFIG_SEC_ABC_DETECT_CONN) += sec_abc_detect_conn.o +sec_abc_detect_conn-objs := connector_setup.o connector_state.o connector_disconnected_count.o +ccflags-y := -Wformat diff --git a/drivers/sti/abc/abc_common.c b/drivers/sti/abc/abc_common.c new file mode 100644 index 000000000000..83106039640b --- /dev/null +++ b/drivers/sti/abc/abc_common.c @@ -0,0 +1,1026 @@ +/* abc_common.c + * + * Abnormal Behavior Catcher Common Driver + * + * Copyright (C) 2017 Samsung Electronics + * + * + * 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. + * + */ +#include +#include +#if IS_ENABLED(CONFIG_SEC_KUNIT) +#include +#endif +#define DEBUG_ABC +#define WARNING_REPORT +struct abc_info *pinfo; +EXPORT_SYMBOL_KUNIT(pinfo); +struct list_head abc_pre_event_list; +EXPORT_SYMBOL_KUNIT(abc_pre_event_list); +int abc_pre_event_cnt; +EXPORT_SYMBOL_KUNIT(abc_pre_event_cnt); +bool abc_save_pre_event; +EXPORT_SYMBOL_KUNIT(abc_save_pre_event); + +struct device *sec_abc; +EXPORT_SYMBOL_KUNIT(sec_abc); +int abc_enable_mode; +EXPORT_SYMBOL_KUNIT(abc_enable_mode); +int abc_init; +EXPORT_SYMBOL_KUNIT(abc_init); +int REGISTERED_ABC_EVENT_TOTAL; +EXPORT_SYMBOL_KUNIT(REGISTERED_ABC_EVENT_TOTAL); + +#if IS_ENABLED(CONFIG_SEC_KUNIT) +char abc_common_kunit_test_work_str[ABC_TEST_UEVENT_MAX][ABC_TEST_STR_MAX] = {"", }; +EXPORT_SYMBOL_KUNIT(abc_common_kunit_test_work_str); + +char sec_abc_kunit_test_log_str[ABC_TEST_STR_MAX]; +EXPORT_SYMBOL_KUNIT(sec_abc_kunit_test_log_str); + +char abc_hub_kunit_test_uevent_str[ABC_HUB_TEST_STR_MAX]; +EXPORT_SYMBOL_KUNIT(abc_hub_kunit_test_uevent_str); +#endif + +/* "module_name", "error_name", "host", on, singular_spec, error_count */ +struct registered_abc_event_struct abc_event_list[] = { + {"audio", "spk_amp", "it", true, true, 0, ABC_GROUP_NONE}, + {"audio", "spk_amp_short", "it", true, true, 0, ABC_GROUP_NONE}, + {"battery", "dc_batt_ov", "it", true, true, 0, ABC_GROUP_NONE}, + {"battery", "dc_i2c_fail", "it", true, true, 0, ABC_GROUP_NONE}, + {"battery", "over_voltage", "it", true, true, 0, ABC_GROUP_NONE}, + {"battery", "pd_input_ocp", "it", true, true, 0, ABC_GROUP_NONE}, + {"battery", "safety_timer", "it", true, true, 0, ABC_GROUP_NONE}, + {"battery", "pp_open", "it", true, true, 0, ABC_GROUP_NONE}, + {"battery", "lim_stuck", "it", true, true, 0, ABC_GROUP_NONE}, + {"battery", "lim_i2c_fail", "it", true, true, 0, ABC_GROUP_NONE}, + {"battery", "vsys_ovp", "it", true, true, 0, ABC_GROUP_NONE}, + {"battery", "dc_current", "it", true, true, 0, ABC_GROUP_NONE}, + {"battery", "store_fg_asoc0", "it", true, true, 0, ABC_GROUP_NONE}, + {"battery", "show_fg_asoc0", "it", true, true, 0, ABC_GROUP_NONE}, + {"battery", "charger_irq_error", "", true, true, 0, ABC_GROUP_NONE}, + {"bootc", "boot_time_fail", "", true, true, 0, ABC_GROUP_NONE}, + {"camera", "camera_error", "", true, true, 0, ABC_GROUP_NONE}, + {"camera", "i2c_fail", "", true, false, 0, ABC_GROUP_NONE}, + {"camera", "icp_error", "", true, true, 0, ABC_GROUP_NONE}, + {"camera", "ipp_overflow", "", true, true, 0, ABC_GROUP_NONE}, + {"camera", "mipi_overflow", "", true, false, 0, ABC_GROUP_NONE}, +#if IS_ENABLED(CONFIG_SEC_FACTORY) + {"camera", "mipi_error_rw1", "", true, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL}, + {"camera", "mipi_error_rs1", "", true, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL}, + {"camera", "mipi_error_rt1", "", true, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL}, + {"camera", "mipi_error_rt2", "", true, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL}, + {"camera", "mipi_error_fw1", "", true, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL}, + {"camera", "mipi_error_uw1", "", true, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL}, + {"camera", "mipi_error_rm1", "", true, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL}, + {"camera", "mipi_error_rb1", "", true, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL}, + {"camera", "mipi_error_fs1", "", true, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL}, +#else + {"camera", "mipi_error_rw1", "", false, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL}, + {"camera", "mipi_error_rs1", "", false, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL}, + {"camera", "mipi_error_rt1", "", false, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL}, + {"camera", "mipi_error_rt2", "", false, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL}, + {"camera", "mipi_error_fw1", "", false, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL}, + {"camera", "mipi_error_uw1", "", false, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL}, + {"camera", "mipi_error_rm1", "", false, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL}, + {"camera", "mipi_error_rb1", "", false, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL}, + {"camera", "mipi_error_fs1", "", false, false, 0, ABC_GROUP_CAMERA_MIPI_ERROR_ALL}, +#endif + {"cond", "CAM_CONNECT", "", true, true, 0, ABC_GROUP_NONE}, + {"cond", "LOWER_C2C_DETECT", "", true, true, 0, ABC_GROUP_NONE}, + {"cond", "MAIN_BAT_DETECT", "", true, true, 0, ABC_GROUP_NONE}, + {"cond", "MAIN_DIGITIZER_DETECT", "", true, true, 0, ABC_GROUP_NONE}, + {"cond", "SUB_BAT_DETECT", "", true, true, 0, ABC_GROUP_NONE}, + {"cond", "SUB_CONNECT", "", true, true, 0, ABC_GROUP_NONE}, + {"cond", "SUB_LOWER_DETECT", "", true, true, 0, ABC_GROUP_NONE}, + {"cond", "SUB_UB_DETECT", "", true, true, 0, ABC_GROUP_NONE}, + {"cond", "TOF_CONNECT", "", true, true, 0, ABC_GROUP_NONE}, + {"cond", "UPPER_C2C_DETECT", "", true, true, 0, ABC_GROUP_NONE}, + {"decon", "fence_timeout", "", true, true, 0, ABC_GROUP_NONE}, + {"display", "act_section_dsierr0", "", true, true, 0, ABC_GROUP_NONE}, + {"display", "act_section_dsierr1", "", true, true, 0, ABC_GROUP_NONE}, + {"display", "panel_main_no_te", "", true, true, 0, ABC_GROUP_NONE}, + {"display", "panel_sub_no_te", "", true, true, 0, ABC_GROUP_NONE}, + {"gpu", "gpu_fault", "", true, false, 0, ABC_GROUP_NONE}, + {"gpu", "gpu_job_timeout", "", true, false, 0, ABC_GROUP_NONE}, + {"gpu_qc", "gpu_fault", "", true, false, 0, ABC_GROUP_NONE}, + {"gpu_qc", "gpu_page_fault", "", true, false, 0, ABC_GROUP_NONE}, + {"mm", "venus_data_corrupt", "", true, true, 0, ABC_GROUP_NONE}, + {"mm", "venus_fw_load_fail", "", true, true, 0, ABC_GROUP_NONE}, + {"mm", "venus_hung", "", true, false, 0, ABC_GROUP_NONE}, + {"mm", "vidc_sys_err_type2", "", true, true, 0, ABC_GROUP_NONE}, + {"mm", "vidc_sys_err_type3", "", true, true, 0, ABC_GROUP_NONE}, + {"muic", "afc_hv_fail", "", true, true, 0, ABC_GROUP_NONE}, + {"muic", "cable_short", "", true, true, 0, ABC_GROUP_NONE}, + {"muic", "qc_hv_fail", "", true, true, 0, ABC_GROUP_NONE}, + {"npu", "npu_fw_warning", "", true, true, 0, ABC_GROUP_NONE}, + {"pdic", "i2c_fail", "it", true, true, 0, ABC_GROUP_NONE}, + {"pdic", "water_det", "", true, true, 0, ABC_GROUP_NONE}, + {"pdic", "ccic_stuck", "it", true, true, 0, ABC_GROUP_NONE}, + {"pmic", "s2dos05_bulk_read", "it", true, true, 0, ABC_GROUP_NONE}, + {"pmic", "s2dos05_bulk_write", "it", true, true, 0, ABC_GROUP_NONE}, + {"pmic", "s2dos05_read_reg", "it", true, true, 0, ABC_GROUP_NONE}, + {"pmic", "s2dos05_read_word", "it", true, true, 0, ABC_GROUP_NONE}, + {"pmic", "s2dos05_update_reg", "it", true, true, 0, ABC_GROUP_NONE}, + {"pmic", "s2dos05_write_reg", "it", true, true, 0, ABC_GROUP_NONE}, + {"pmic", "s2dos05_bulk_read_fail", "it", true, true, 0, ABC_GROUP_NONE}, + {"pmic", "s2dos05_bulk_write_fail", "it", true, true, 0, ABC_GROUP_NONE}, + {"pmic", "s2dos05_read_reg_fail", "it", true, true, 0, ABC_GROUP_NONE}, + {"pmic", "s2dos05_read_word_fail", "it", true, true, 0, ABC_GROUP_NONE}, + {"pmic", "s2dos05_update_reg_fail", "it", true, true, 0, ABC_GROUP_NONE}, + {"pmic", "s2dos05_write_reg_fail", "it", true, true, 0, ABC_GROUP_NONE}, + {"pmic", "s2dos05_scp", "", true, true, 0, ABC_GROUP_NONE}, + {"pmic", "s2dos05_ssd", "", true, true, 0, ABC_GROUP_NONE}, + {"storage", "mmc_hwreset_err", "", true, true, 0, ABC_GROUP_NONE}, + {"storage", "sd_removed_err", "", true, true, 0, ABC_GROUP_NONE}, + {"storage", "ufs_hwreset_err", "it", true, true, 0, ABC_GROUP_NONE}, + {"storage", "ufs_medium_err", "it", true, true, 0, ABC_GROUP_NONE}, + {"storage", "ufs_hardware_err", "it", true, true, 0, ABC_GROUP_NONE}, + {"tsp", "tsp_int_fault", "", true, false, 0, ABC_GROUP_NONE}, + {"tsp_sub", "tsp_int_fault", "", true, false, 0, ABC_GROUP_NONE}, + {"ub_main", "ub_disconnected", "it", true, true, 0, ABC_GROUP_NONE}, + {"ub_sub", "ub_disconnected", "it", true, true, 0, ABC_GROUP_NONE}, + {"vib", "fw_load_fail", "it", true, true, 0, ABC_GROUP_NONE}, + {"vib", "int_gnd_short", "", true, false, 0, ABC_GROUP_NONE}, + {"wacom", "digitizer_not_connected", "", true, true, 0, ABC_GROUP_NONE}, +#if IS_ENABLED(CONFIG_SEC_KUNIT) + {"kunit", "test_warn", "", true, true, 0, ABC_GROUP_NONE}, + {"kunit", "test_info", "", true, true, 0, ABC_GROUP_NONE}, + {"kunit", "test_error", "", true, true, 0, ABC_GROUP_NONE}, +#endif +}; +EXPORT_SYMBOL_KUNIT(abc_event_list); + +struct abc_enable_cmd_struct enable_cmd_list[] = { + {ERROR_REPORT_MODE_ENABLE, ERROR_REPORT_MODE_BIT, "ERROR_REPORT=1"}, + {ERROR_REPORT_MODE_DISABLE, ERROR_REPORT_MODE_BIT, "ERROR_REPORT=0"}, + {ALL_REPORT_MODE_ENABLE, ALL_REPORT_MODE_BIT, "ALL_REPORT=1"}, + {ALL_REPORT_MODE_DISABLE, ALL_REPORT_MODE_BIT, "ALL_REPORT=0"}, + {PRE_EVENT_ENABLE, PRE_EVENT_ENABLE_BIT, "PRE_EVENT=1"}, + {PRE_EVENT_DISABLE, PRE_EVENT_ENABLE_BIT, "PRE_EVENT=0"}, +}; + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id sec_abc_dt_match[] = { + { .compatible = "samsung,sec_abc" }, + { } +}; +#endif + +#if IS_ENABLED(CONFIG_SEC_KUNIT) +void abc_common_test_get_work_str(char *utest_event_str[]) +{ + int i; + + for (i = 0; i < ABC_TEST_UEVENT_MAX; i++) { + if (utest_event_str[i]) { + if (i >= 2 && !strncmp(utest_event_str[i], TIME_KEYWORD, strlen(TIME_KEYWORD))) + strlcpy(abc_common_kunit_test_work_str[i], + TIME_KEYWORD, ABC_TEST_STR_MAX); + else + strlcpy(abc_common_kunit_test_work_str[i], + utest_event_str[i], ABC_TEST_STR_MAX); + } + } +} +EXPORT_SYMBOL_KUNIT(abc_common_test_get_work_str); + +void abc_common_test_get_log_str(char *log_str) +{ + strlcpy(sec_abc_kunit_test_log_str, log_str, sizeof(sec_abc_kunit_test_log_str)); +} +EXPORT_SYMBOL_KUNIT(abc_common_test_get_log_str); +#endif + +static int sec_abc_resume(struct device *dev) +{ + return 0; +} + +static int sec_abc_remove(struct platform_device *pdev) +{ + return 0; +} + +static const struct dev_pm_ops sec_abc_pm = { + .resume = sec_abc_resume, +}; + +int sec_abc_get_idx_of_registered_event(char *module_name, char *error_name) +{ + int i; + + for (i = 0; i < REGISTERED_ABC_EVENT_TOTAL; i++) + if (!strncmp(module_name, abc_event_list[i].module_name, ABC_EVENT_STR_MAX) && + !strncmp(error_name, abc_event_list[i].error_name, ABC_EVENT_STR_MAX)) + return i; + + return -1; +} +EXPORT_SYMBOL_KUNIT(sec_abc_get_idx_of_registered_event); + +#if !IS_ENABLED(CONFIG_SEC_FACTORY) && !IS_ENABLED(CONFIG_SEC_KUNIT) +static int sec_abc_get_error_count(char *module_name, char *error_name) +{ + int i = sec_abc_get_idx_of_registered_event(module_name, error_name); + + if (i >= 0) + return abc_event_list[i].error_count; + return 0; +} + +static void sec_abc_update_error_count(char *module_name, char *error_name) +{ + int i = sec_abc_get_idx_of_registered_event(module_name, error_name); + + if (i >= 0) + abc_event_list[i].error_count++; +} + +static void sec_abc_reset_error_count(void) +{ + int i; + + for (i = 0; i < REGISTERED_ABC_EVENT_TOTAL; i++) + abc_event_list[i].error_count = 0; +} + +static bool sec_abc_is_skip_event(char *abc_str) +{ + struct abc_key_data key_data; + int count; + + if (sec_abc_make_key_data(&key_data, abc_str)) { + ABC_PRINT("Event string isn't valid. Check Input : %s", abc_str); + return true; + } + + count = sec_abc_get_error_count(key_data.event_module, key_data.event_name); + + if (count >= ABC_SKIP_EVENT_COUNT_THRESHOLD) { + if (count == ABC_SKIP_EVENT_COUNT_THRESHOLD) { + ABC_PRINT("[%s-%s] ABC Error already detected %d times! It is skipped from now on!", + key_data.event_module, key_data.event_name, ABC_SKIP_EVENT_COUNT_THRESHOLD); + sec_abc_update_error_count(key_data.event_module, key_data.event_name); + } + return true; + } + return false; +} +#endif + +void sec_abc_send_uevent(struct abc_key_data *key_data, char *uevent_type) +{ + char *uevent_str[ABC_UEVENT_MAX] = {0,}; + char uevent_module_str[ABC_EVENT_STR_MAX + 7]; + char uevent_event_str[ABC_EVENT_STR_MAX + ABC_TYPE_STR_MAX]; + char uevent_host_str[ABC_EVENT_STR_MAX]; + char uevent_ext_log_str[ABC_EVENT_STR_MAX]; + char timestamp[TIME_STAMP_STR_MAX]; + int idx; + + if (!sec_abc_get_enabled()) { + ABC_PRINT("ABC isn't enabled. Save pre_event"); + sec_abc_save_pre_events(key_data, uevent_type); + return; + } + + snprintf(uevent_module_str, ABC_EVENT_STR_MAX, "MODULE=%s", key_data->event_module); + snprintf(uevent_event_str, ABC_EVENT_STR_MAX, "%s=%s", uevent_type, key_data->event_name); + snprintf(timestamp, TIME_STAMP_STR_MAX, "TIMESTAMP=%d", key_data->cur_time); + + uevent_str[0] = uevent_module_str; + uevent_str[1] = uevent_event_str; + uevent_str[2] = timestamp; + if (abc_event_list[key_data->idx].host[0]) { + snprintf(uevent_host_str, ABC_EVENT_STR_MAX, "HOST=%s", abc_event_list[key_data->idx].host); + uevent_str[3] = uevent_host_str; + } + + if (key_data->ext_log[0]) { + snprintf(uevent_ext_log_str, ABC_EVENT_STR_MAX, "EXT_LOG=%s", key_data->ext_log); + uevent_str[4] = uevent_ext_log_str; + } + + for (idx = 0; uevent_str[idx]; idx++) + ABC_PRINT("%s", uevent_str[idx]); +#if IS_ENABLED(CONFIG_SEC_KUNIT) + abc_common_test_get_work_str(uevent_str); + complete(&pinfo->test_uevent_done); +#endif + +#if !IS_ENABLED(CONFIG_SEC_FACTORY) && !IS_ENABLED(CONFIG_SEC_KUNIT) + sec_abc_update_error_count(key_data->event_module, key_data->event_name); +#endif + kobject_uevent_env(&sec_abc->kobj, KOBJ_CHANGE, uevent_str); +} +EXPORT_SYMBOL_KUNIT(sec_abc_send_uevent); + +__visible_for_testing +struct abc_pre_event *sec_abc_get_pre_event_node(struct abc_key_data *key_data) +{ + struct abc_pre_event *pre_event; + + list_for_each_entry(pre_event, &abc_pre_event_list, node) { + if (!strcmp(pre_event->key_data.event_module, key_data->event_module) && + !strcmp(pre_event->key_data.event_name, key_data->event_name)) { + ABC_PRINT("return matched node"); + return pre_event; + } + } + + pre_event = NULL; + + if (abc_pre_event_cnt < ABC_PREOCCURRED_EVENT_MAX) { + + pre_event = kzalloc(sizeof(*pre_event), GFP_KERNEL); + + if (pre_event) { + list_add_tail(&pre_event->node, &abc_pre_event_list); + abc_pre_event_cnt++; + ABC_PRINT("return new node"); + } else + ABC_PRINT("failed to get node"); + } + + return pre_event; +} +EXPORT_SYMBOL_KUNIT(sec_abc_get_pre_event_node); + +__visible_for_testing +int sec_abc_clear_pre_events(void) +{ + struct abc_pre_event *pre_event; + int cnt = 0; + + ABC_PRINT("start"); + mutex_lock(&pinfo->pre_event_mutex); + + while (!list_empty(&abc_pre_event_list)) { + pre_event = list_first_entry(&abc_pre_event_list, + struct abc_pre_event, + node); + list_del(&pre_event->node); + kfree(pre_event); + cnt++; + } + + abc_pre_event_cnt = 0; + + /* Once Pre_events were cleared, don't save pre_event anymore. */ + abc_save_pre_event = false; + abc_enable_mode &= ~(PRE_EVENT_ENABLE_BIT); + +#if IS_ENABLED(CONFIG_SEC_KUNIT) + complete(&pinfo->test_work_done); +#endif + mutex_unlock(&pinfo->pre_event_mutex); + ABC_PRINT("end"); + return cnt; +} +EXPORT_SYMBOL_KUNIT(sec_abc_clear_pre_events); + +__visible_for_testing +int sec_abc_process_pre_events(void) +{ + struct abc_pre_event *pre_event; + int i, cnt = 0; + + ABC_PRINT("start"); + + mutex_lock(&pinfo->pre_event_mutex); + list_for_each_entry(pre_event, &abc_pre_event_list, node) { + + if (abc_enable_mode & ALL_REPORT_MODE_BIT) { + ABC_PRINT("All report mode. Send uevent"); + cnt += pre_event->all_cnt; + + for (i = 0; i < pre_event->all_cnt; i++) + sec_abc_send_uevent(&pre_event->key_data, pre_event->key_data.event_type); + + } + + cnt += pre_event->error_cnt; + for (i = 0; i < pre_event->error_cnt; i++) + if (abc_event_list[pre_event->key_data.idx].enabled) + sec_abc_send_uevent(&pre_event->key_data, "ERROR"); + } + mutex_unlock(&pinfo->pre_event_mutex); + ABC_PRINT("pre_event cnt : %d end", cnt); + + return cnt; +} +EXPORT_SYMBOL_KUNIT(sec_abc_process_pre_events); + +int sec_abc_save_pre_events(struct abc_key_data *key_data, char *uevent_type) +{ + struct abc_pre_event *pre_event; + int ret = 0; + + mutex_lock(&pinfo->pre_event_mutex); + + ABC_PRINT("start Module(%s) Event(%s) Type(%s)", + key_data->event_module, + key_data->event_name, + uevent_type); + + pre_event = sec_abc_get_pre_event_node(key_data); + + if (!pre_event) { + ABC_PRINT_KUNIT("Failed to add Pre_event"); + ret = -EINVAL; + goto out; + } + + pre_event->key_data = *key_data; + + if (!strncmp(uevent_type, "ERROR", 5)) + pre_event->error_cnt++; + else + pre_event->all_cnt++; + +out: + mutex_unlock(&pinfo->pre_event_mutex); + ABC_PRINT("end"); + return ret; +} +EXPORT_SYMBOL_KUNIT(sec_abc_save_pre_events); + +__visible_for_testing +void sec_abc_process_changed_enable_mode(void) +{ + if (sec_abc_get_enabled()) { + if (abc_enable_mode & PRE_EVENT_ENABLE_BIT) { + sec_abc_process_pre_events(); + ABC_PRINT_KUNIT("Pre_events processed"); + } else { + ABC_PRINT_KUNIT("ABC enabled. Pre_event disabled"); + } + complete(&pinfo->enable_done); + } else { + ABC_PRINT_KUNIT("ABC is disabled. Clear events"); + sec_abc_reset_all_buffer(); +#if !IS_ENABLED(CONFIG_SEC_FACTORY) && !IS_ENABLED(CONFIG_SEC_KUNIT) + sec_abc_reset_error_count(); +#endif + } + + ABC_PRINT("%d Pre_events cleared", sec_abc_clear_pre_events()); +} +EXPORT_SYMBOL_KUNIT(sec_abc_process_changed_enable_mode); + +/* Change ABC driver's enable mode. + * + * Interface with ACT : write "1" or "0" + * Interfcae with LABO + * ex) "ERROR_REPORT=1,PRE_EVENT=1", "ALL_REPORT=1,ERROR_REPORT=0,PRE_EVENT=0" ... + * + */ +__visible_for_testing +ssize_t enabled_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + char *c, *p, *enable_cmd[ABC_CMD_MAX]; + char temp[ABC_CMD_STR_MAX * 3]; + int items, i, j, idx = 0; + bool chk = false; + int origin_enable_mode = abc_enable_mode; + + if (!strncmp(buf, "1", 1)) { + /* Interface with ACT */ + ABC_PRINT("Error report mode enabled"); + abc_enable_mode |= ERROR_REPORT_MODE_BIT; + } else if (!strncmp(buf, "0", 1)) { + ABC_PRINT("Error report mode disabled"); + abc_enable_mode &= (~ERROR_REPORT_MODE_BIT); + } else { + + strlcpy(temp, buf, ABC_CMD_STR_MAX * 3); + p = temp; + items = ARRAY_SIZE(enable_cmd_list); + + while ((c = strsep(&p, ",")) != NULL && idx < ABC_CMD_MAX) { + enable_cmd[idx] = c; + idx++; + } + + for (i = 0; i < idx; i++) { + chk = false; + for (j = 0; j < items; j++) { + if (!strncmp(enable_cmd[i], + enable_cmd_list[j].abc_cmd_str, + strlen(enable_cmd_list[j].abc_cmd_str))) { + if (strstr(enable_cmd_list[j].abc_cmd_str, "=1")) + abc_enable_mode |= enable_cmd_list[j].enable_value; + else + abc_enable_mode &= ~(enable_cmd_list[j].enable_value); + chk = true; + } + } + if (!chk) { + ABC_PRINT_KUNIT("Invalid string. Check the Input"); + abc_enable_mode = origin_enable_mode; + return count; + } + } + } + + sec_abc_process_changed_enable_mode(); + return count; +} +EXPORT_SYMBOL_KUNIT(enabled_store); + +__visible_for_testing +ssize_t enabled_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + if (sec_abc_get_enabled()) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} +EXPORT_SYMBOL_KUNIT(enabled_show); + +static DEVICE_ATTR_RW(enabled); + +__visible_for_testing +ssize_t spec_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + mutex_lock(&pinfo->spec_mutex); + + sec_abc_change_spec(buf); + + mutex_unlock(&pinfo->spec_mutex); + + return count; +} +EXPORT_SYMBOL_KUNIT(spec_store); + +__visible_for_testing +ssize_t spec_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int count = 0; + + mutex_lock(&pinfo->spec_mutex); + + count = sec_abc_read_spec(buf); + + mutex_unlock(&pinfo->spec_mutex); + + return count; +} +EXPORT_SYMBOL_KUNIT(spec_show); + +static DEVICE_ATTR_RW(spec); + +__visible_for_testing +ssize_t features_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + return count; +} + +__visible_for_testing +ssize_t features_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int count = 0; + + count += scnprintf(buf, PAGE_SIZE, "spec_control\nhost\n"); + + return count; +} +EXPORT_SYMBOL_KUNIT(features_show); + +static DEVICE_ATTR_RW(features); + +__visible_for_testing +ssize_t list_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + return count; +} +EXPORT_SYMBOL_KUNIT(list_store); + +__visible_for_testing +ssize_t list_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int count = 0; + int i; + + for (i = 0; i < REGISTERED_ABC_EVENT_TOTAL; i++) + { + count += scnprintf(buf + count, PAGE_SIZE - count, "%s,%s\n", + abc_event_list[i].module_name, abc_event_list[i].error_name); + } + ABC_PRINT("%d", count); + + return count; +} +EXPORT_SYMBOL_KUNIT(list_show); + +static DEVICE_ATTR_RW(list); + +static struct attribute *sec_abc_attr[] = { + &dev_attr_enabled.attr, + &dev_attr_spec.attr, + &dev_attr_features.attr, + &dev_attr_list.attr, + NULL, +}; + +static struct attribute_group sec_abc_attr_group = { + .attrs = sec_abc_attr, +}; + +int sec_abc_get_enabled(void) +{ + return (abc_enable_mode & (ERROR_REPORT_MODE_BIT | ALL_REPORT_MODE_BIT)); +} +EXPORT_SYMBOL(sec_abc_get_enabled); + +static void sec_abc_work_func_clear_pre_events(struct work_struct *work) +{ + ABC_DEBUG("start"); + + sec_abc_clear_pre_events(); + + ABC_DEBUG("end"); +} + +#if IS_ENABLED(CONFIG_UML) +static void sec_abc_init_key_data(struct abc_key_data *key_data) +{ + memset(key_data->event_module, 0, sizeof(key_data->event_module)); + memset(key_data->event_name, 0, sizeof(key_data->event_name)); + memset(key_data->event_type, 0, sizeof(key_data->event_type)); + memset(key_data->ext_log, 0, sizeof(key_data->ext_log)); +} +#endif + +static void sec_abc_work_func(struct work_struct *work) +{ + struct abc_event_work *event_work_data; + struct abc_key_data key_data; + int idx; + + mutex_lock(&pinfo->work_mutex); + + event_work_data = container_of(work, struct abc_event_work, work); + + ABC_DEBUG("work start. str : %s", event_work_data->abc_str); + +#if IS_ENABLED(CONFIG_UML) + sec_abc_init_key_data(&key_data); +#endif + + if (sec_abc_make_key_data(&key_data, event_work_data->abc_str)) { + ABC_PRINT("Event string isn't valid. Check Input : %s", event_work_data->abc_str); + goto abc_work_end; + } + + idx = sec_abc_get_idx_of_registered_event(key_data.event_module, key_data.event_name); + + if (idx < 0) { + ABC_PRINT_KUNIT("%s : %s isn't registered", key_data.event_module, key_data.event_name); + goto abc_work_end; + } + + key_data.idx = idx; + +#if IS_ENABLED(CONFIG_SEC_ABC_MOTTO) +#if !IS_ENABLED(CONFIG_UML) + motto_send_device_info(key_data.event_module, key_data.event_name); +#endif +#endif + + if (abc_enable_mode & (ALL_REPORT_MODE_BIT | PRE_EVENT_ENABLE_BIT)) { + ABC_PRINT("All report mode may be enabled. Send uevent"); + sec_abc_send_uevent(&key_data, key_data.event_type); + } + + if (!abc_event_list[idx].enabled || !strncmp(key_data.event_type, "INFO", 4)) { + ABC_PRINT_KUNIT("Don't send error report"); + goto abc_work_end; + } + + if (abc_event_list[idx].singular_spec) { + ABC_PRINT("Send uevent : %s", event_work_data->abc_str); + sec_abc_send_uevent(&key_data, "ERROR"); + } else { + + sec_abc_enqueue_event_data(&key_data); + + if (sec_abc_reached_spec(&key_data)) { + ABC_PRINT("Send uevent : %s", event_work_data->abc_str); + sec_abc_send_uevent(&key_data, "ERROR"); + sec_abc_reset_event_buffer(&key_data); + } + } + +abc_work_end: + ABC_DEBUG("work done"); + mutex_unlock(&pinfo->work_mutex); +#if IS_ENABLED(CONFIG_SEC_KUNIT) + complete(&pinfo->test_work_done); +#endif +} + +/* event string format + * + * ex) + * MODULE=tsp@WARN=power_status_mismatch + * MODULE=gpu@INFO=gpu_fault + * MODULE=tsp@ERROR=power_status_mismatch@EXT_LOG=fw_ver(0108) + * + */ +__visible_for_testing +void sec_abc_enqueue_work(struct abc_event_work work_data[], char *str) +{ + int idx; + + for (idx = 0; idx < ABC_WORK_MAX; idx++) { + if (!work_pending(&work_data[idx].work)) { + ABC_DEBUG("Event %s use work[%d]", str, idx); + strlcpy(work_data[idx].abc_str, str, ABC_BUFFER_MAX); + queue_work(pinfo->workqueue, &work_data[idx].work); + return; + } + } + + ABC_PRINT("Failed. All works are in queue"); +} + +void sec_abc_send_event(char *str) +{ +#if !IS_ENABLED(CONFIG_SEC_FACTORY) && !IS_ENABLED(CONFIG_SEC_KUNIT) + if (sec_abc_is_skip_event(str)) + return; +#endif + + if (!abc_init) { + ABC_PRINT_KUNIT("ABC driver is not initialized!(%s)", str); + return; + } + + if (!sec_abc_get_enabled() && !abc_save_pre_event) { + ABC_PRINT_KUNIT("ABC is disabled and pre_event is disabled.(%s)", str); + return; + } + + ABC_PRINT("ABC is working. Queue work.(%s)", str); + sec_abc_enqueue_work(pinfo->event_work_data, str); + +} +EXPORT_SYMBOL(sec_abc_send_event); + +/** + * sec_abc_wait_enable() - wait for abc enable done + * Return : 0 for success, -1 for fail(timeout or abc not initialized) + */ +int sec_abc_wait_enabled(void) +{ + unsigned long timeout; + + if (!abc_init) { + ABC_PRINT_KUNIT("ABC driver is not initialized!"); + return -1; + } + + if (sec_abc_get_enabled()) + return 0; + + reinit_completion(&pinfo->enable_done); + + timeout = wait_for_completion_timeout(&pinfo->enable_done, + msecs_to_jiffies(ABC_WAIT_ENABLE_TIMEOUT)); + + if (timeout == 0) { + ABC_PRINT_KUNIT("timeout!"); + return -1; + } + + return 0; +} +EXPORT_SYMBOL(sec_abc_wait_enabled); + +__visible_for_testing +void sec_abc_init_work(struct abc_info *pinfo) +{ + int idx; + + pinfo->workqueue = create_singlethread_workqueue("sec_abc_wq"); + INIT_DELAYED_WORK(&pinfo->clear_pre_events, sec_abc_work_func_clear_pre_events); + + /* After timeout clear abc events occurred when abc disalbed. */ + queue_delayed_work(pinfo->workqueue, + &pinfo->clear_pre_events, + msecs_to_jiffies(ABC_CLEAR_EVENT_TIMEOUT)); + + /* Work for abc_events & pre_events (events occurred before enabled) */ + for (idx = 0; idx < ABC_WORK_MAX; idx++) + INIT_WORK(&pinfo->event_work_data[idx].work, sec_abc_work_func); +} +EXPORT_SYMBOL_KUNIT(sec_abc_init_work); + +__visible_for_testing +int sec_abc_get_registered_abc_event_total(void) +{ + return ARRAY_SIZE(abc_event_list); +} +EXPORT_SYMBOL_KUNIT(sec_abc_get_registered_abc_event_total); + +static int sec_abc_probe(struct platform_device *pdev) +{ + struct abc_platform_data *pdata; + int ret = 0; + + ABC_PRINT("start"); + + abc_init = false; + REGISTERED_ABC_EVENT_TOTAL = sec_abc_get_registered_abc_event_total(); + + if (pdev->dev.of_node) { + pdata = devm_kzalloc(&pdev->dev, + sizeof(struct abc_platform_data), GFP_KERNEL); + + if (!pdata) { + dev_err(&pdev->dev, "Failed to allocate platform data"); + ret = -ENOMEM; + goto out; + } + + pdev->dev.platform_data = pdata; + ret = abc_parse_dt(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Failed to parse dt data"); + goto err_parse_dt; + } + + ABC_PRINT("parse dt done"); + } else { + pdata = pdev->dev.platform_data; + } + + if (!pdata) { + dev_err(&pdev->dev, "There are no platform data"); + ret = -EINVAL; + goto out; + } + + pinfo = kzalloc(sizeof(*pinfo), GFP_KERNEL); + + if (!pinfo) { + ret = -ENOMEM; + goto err_alloc_pinfo; + } + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + pinfo->dev = sec_device_create(pinfo, "sec_abc"); +#else + pinfo->dev = device_create(sec_class, NULL, 0, NULL, "sec_abc"); +#endif + if (IS_ERR(pinfo->dev)) { + pr_err("%s Failed to create device(sec_abc)!", __func__); + ret = -ENODEV; + goto err_create_device; + } + + sec_abc = pinfo->dev; + + ret = sysfs_create_group(&pinfo->dev->kobj, &sec_abc_attr_group); + if (ret) { + pr_err("%s: Failed to create device attribute group", __func__); + goto err_create_abc_attr_group; + } + + INIT_LIST_HEAD(&abc_pre_event_list); + + sec_abc_init_work(pinfo); + + if (!pinfo->workqueue) + goto err_create_abc_wq; + +#if IS_ENABLED(CONFIG_SEC_KUNIT) + init_completion(&pinfo->test_uevent_done); + init_completion(&pinfo->test_work_done); +#endif + init_completion(&pinfo->enable_done); + mutex_init(&pinfo->pre_event_mutex); + mutex_init(&pinfo->enable_mutex); + mutex_init(&pinfo->work_mutex); + mutex_init(&pinfo->spec_mutex); + pinfo->pdata = pdata; + platform_set_drvdata(pdev, pinfo); +#if IS_ENABLED(CONFIG_SEC_ABC_MOTTO) + motto_init(pdev); +#endif + abc_init = true; + abc_enable_mode |= PRE_EVENT_ENABLE_BIT; + abc_save_pre_event = true; + ABC_PRINT("success"); + return ret; +err_create_abc_wq: + sysfs_remove_group(&pinfo->dev->kobj, &sec_abc_attr_group); +err_create_abc_attr_group: +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + sec_device_destroy(sec_abc->devt); +#else + device_destroy(sec_class, sec_abc->devt); +#endif +err_create_device: + kfree(pinfo); +err_alloc_pinfo: +err_parse_dt: + devm_kfree(&pdev->dev, pdata); + pdev->dev.platform_data = NULL; +out: + return ret; +} + +void sec_abc_free_pre_events(void) +{ + struct abc_pre_event *pre_event; + + while (!list_empty(&abc_pre_event_list)) { + + pre_event = list_first_entry( + &abc_pre_event_list, + struct abc_pre_event, + node); + + list_del(&pre_event->node); + kfree(pre_event); + } +} +EXPORT_SYMBOL_KUNIT(sec_abc_free_pre_events); + +__visible_for_testing +void sec_abc_free_allocated_memory(void) +{ + sec_abc_free_pre_events(); + sec_abc_free_spec_buffer(); +} +EXPORT_SYMBOL_KUNIT(sec_abc_free_allocated_memory); + +static struct platform_driver sec_abc_driver = { + .probe = sec_abc_probe, + .remove = sec_abc_remove, + .driver = { + .name = "sec_abc", + .owner = THIS_MODULE, +#if IS_ENABLED(CONFIG_PM) + .pm = &sec_abc_pm, +#endif +#if IS_ENABLED(CONFIG_OF) + .of_match_table = of_match_ptr(sec_abc_dt_match), +#endif + }, +}; + +static int __init sec_abc_init(void) +{ + ABC_PRINT("start"); + + return platform_driver_register(&sec_abc_driver); +} + +static void __exit sec_abc_exit(void) +{ + ABC_PRINT("exit"); + sec_abc_free_allocated_memory(); + + return platform_driver_unregister(&sec_abc_driver); +} + +module_init(sec_abc_init); +module_exit(sec_abc_exit); + +MODULE_DESCRIPTION("Samsung ABC Driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/sti/abc/abc_hub_bootc.c b/drivers/sti/abc/abc_hub_bootc.c new file mode 100644 index 000000000000..8c7e301bff55 --- /dev/null +++ b/drivers/sti/abc/abc_hub_bootc.c @@ -0,0 +1,153 @@ +/* abc_hub_bootc.c + * + * Abnormal Behavior Catcher Hub Driver Sub Module(Booting Time Check) + * + * Copyright (C) 2017 Samsung Electronics + * + * Sangsu Ha + * + * 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. + * + */ + +#include +#include +#if IS_ENABLED(CONFIG_SEC_KUNIT) +#include +#endif + +char bootc_offset_module[BOOTC_OFFSET_DATA_CNT][BOOTC_OFFSET_STR_MAX] = {"fsck"}; +EXPORT_SYMBOL_KUNIT(bootc_offset_module); + +__visible_for_testing +int abc_hub_bootc_get_total_offset(struct sub_bootc_pdata *bootc_pdata) +{ + int total_offset = 0; + int i; + + for (i = 0; i < BOOTC_OFFSET_DATA_CNT; i++) + total_offset += bootc_pdata->offset_data[i].offset; + + return total_offset; +} +EXPORT_SYMBOL_KUNIT(abc_hub_bootc_get_total_offset); + +static void abc_hub_bootc_work_func(struct work_struct *work) +{ + struct sub_bootc_pdata *bootc_pdata = container_of(work, struct sub_bootc_pdata, bootc_work.work); + int fixed_time_spec; + + bootc_pdata->time_spec_offset = abc_hub_bootc_get_total_offset(bootc_pdata); + fixed_time_spec = bootc_pdata->time_spec + bootc_pdata->time_spec_offset; + + ABC_PRINT("bootc_time : %d, time_spec : %d(%d + %d))", + bootc_pdata->bootc_time, fixed_time_spec, + bootc_pdata->time_spec, bootc_pdata->time_spec_offset); + + if (bootc_pdata->bootc_time < 0) { + ABC_PRINT("boot_time_parse fail(%d)", bootc_pdata->bootc_time); + } else { +#if IS_ENABLED(CONFIG_SEC_ABC_MOTTO) + motto_send_bootcheck_info(bootc_pdata->bootc_time / MSEC_PER_SEC); +#endif + if (bootc_pdata->bootc_time > fixed_time_spec) { + ABC_PRINT_KUNIT("booting time is spec out"); +#if IS_ENABLED(CONFIG_SEC_FACTORY) + abc_hub_send_event("MODULE=bootc@INFO=boot_time_fail"); +#else + abc_hub_send_event("MODULE=bootc@WARN=boot_time_fail"); +#endif + } + } +} + +int parse_bootc_data(struct device *dev, + struct abc_hub_platform_data *pdata, + struct device_node *np) +{ + struct device_node *bootc_np; + + bootc_np = of_find_node_by_name(np, "bootc"); + +#if IS_ENABLED(CONFIG_SEC_FACTORY) + if (of_property_read_u32(bootc_np, "bootc,time_spec_fac", &pdata->bootc_pdata.time_spec)) { + dev_err(dev, "Failed to get bootc,time_spec_fac: node not exist"); + return -EINVAL; + } + ABC_PRINT("time_spec(factory binary) - %d", pdata->bootc_pdata.time_spec); +#elif IS_ENABLED(CONFIG_SEC_ABC_HUB_BOOTC_ENG) + if (of_property_read_u32(bootc_np, "bootc,time_spec_eng", &pdata->bootc_pdata.time_spec)) { + dev_err(dev, "Failed to get bootc,time_spec_eng: node not exist"); + return -EINVAL; + } + ABC_PRINT("time_spec(user binary eng build) - %d", pdata->bootc_pdata.time_spec); +#else + if (of_property_read_u32(bootc_np, "bootc,time_spec_user", &pdata->bootc_pdata.time_spec)) { + dev_err(dev, "Failed to get bootc,time_spec_user: node not exist"); + return -EINVAL; + } + ABC_PRINT("time_spec(user binary user build) - %d", pdata->bootc_pdata.time_spec); +#endif + + return 0; +} + +void abc_hub_bootc_enable(struct device *dev, int enable) +{ + /* common sequence */ + + + abc_hub_pinfo->pdata->bootc_pdata.enabled = enable; + + /* custom sequence */ + ABC_PRINT("enable(%d)", enable); + if (enable == ABC_HUB_ENABLED) + queue_delayed_work(abc_hub_pinfo->pdata->bootc_pdata.workqueue, + &abc_hub_pinfo->pdata->bootc_pdata.bootc_work, msecs_to_jiffies(2000)); +} + +int abc_hub_bootc_init_work(void) +{ + + INIT_DELAYED_WORK(&abc_hub_pinfo->pdata->bootc_pdata.bootc_work, abc_hub_bootc_work_func); + abc_hub_pinfo->pdata->bootc_pdata.workqueue = create_singlethread_workqueue("bootc_wq"); + + if (!abc_hub_pinfo->pdata->bootc_pdata.workqueue) { + ABC_PRINT("fail"); + return -1; + } + + return 0; + +} +EXPORT_SYMBOL_KUNIT(abc_hub_bootc_init_work); + +int abc_hub_bootc_init(struct device *dev) +{ + + int i; + + for (i = 0; i < BOOTC_OFFSET_DATA_CNT; i++) { + strcpy(abc_hub_pinfo->pdata->bootc_pdata.offset_data[i].module, bootc_offset_module[i]); + abc_hub_pinfo->pdata->bootc_pdata.offset_data[i].offset = 0; + } + abc_hub_pinfo->pdata->bootc_pdata.bootc_time = -1; + + if (abc_hub_bootc_init_work()) + return -1; + + ABC_PRINT("success"); + return 0; +} + +MODULE_DESCRIPTION("Samsung ABC Hub Sub Module(bootc) Driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/sti/abc/abc_hub_core.c b/drivers/sti/abc/abc_hub_core.c new file mode 100644 index 000000000000..a9cc84f8af00 --- /dev/null +++ b/drivers/sti/abc/abc_hub_core.c @@ -0,0 +1,430 @@ +/* abc_hub.c + * + * Abnormal Behavior Catcher Hub Driver + * + * Copyright (C) 2017 Samsung Electronics + * + * Sangsu Ha + * + * 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. + * + */ + +#include +#if IS_ENABLED(CONFIG_SEC_KUNIT) +#include +#endif + +__visible_for_testing +struct device *abc_hub_dev; +EXPORT_SYMBOL_KUNIT(abc_hub_dev); +struct abc_hub_info *abc_hub_pinfo; +EXPORT_SYMBOL_KUNIT(abc_hub_pinfo); +__visible_for_testing +int abc_hub_probed; +EXPORT_SYMBOL_KUNIT(abc_hub_probed); + +#if IS_ENABLED(CONFIG_OF) +static int abc_hub_parse_dt(struct device *dev) +{ + struct abc_hub_platform_data *pdata = dev->platform_data; + struct device_node *np; +#if IS_ENABLED(CONFIG_SEC_ABC_HUB_COND) + int cond_pin_cnt = 0; +#endif + + np = dev->of_node; + pdata->nSub = of_get_child_count(np); + if (!pdata->nSub) { + dev_err(dev, "There is no dt of sub module\n"); + return -ENODEV; + } + +#if IS_ENABLED(CONFIG_SEC_ABC_HUB_COND) + cond_pin_cnt = of_gpio_named_count(np, "sec,det_conn_gpios"); +#if IS_ENABLED(CONFIG_QCOM_SEC_ABC_DETECT) + /* Setting PM gpio for QC */ + cond_pin_cnt = cond_pin_cnt + of_gpio_named_count(np, "sec,det_pm_conn_gpios"); +#endif + if (cond_pin_cnt) { + pdata->cond.init = 1; + } else { + pdata->cond.init = 0; + ABC_PRINT("sub module(cond) is not supported\n"); + } +#endif +#if IS_ENABLED(CONFIG_SEC_ABC_HUB_BOOTC) + if (parse_bootc_data(dev, pdata, np) < 0) + ABC_PRINT("sub module(bootc) is not supported\n"); + else + pdata->bootc_pdata.init = 1; +#endif + return 0; +} +#endif + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id abc_hub_dt_match[] = { + { .compatible = "samsung,abc_hub" }, + { } +}; +#endif + +static int abc_hub_suspend(struct device *dev) +{ + int ret = 0; + return ret; +} + +static int abc_hub_resume(struct device *dev) +{ + int ret = 0; + return ret; +} + +static int abc_hub_remove(struct platform_device *pdev) +{ + return 0; +} + +static const struct dev_pm_ops abc_hub_pm = { + .suspend = abc_hub_suspend, + .resume = abc_hub_resume, +}; + +__visible_for_testing +ssize_t enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + /* The enabel will be managed for each sub module when sub module becomes over two. */ + if (!strncmp(buf, "1", 1)) { + ABC_PRINT("abc_hub driver enabled.\n"); + + if (sec_abc_wait_enabled() < 0) + ABC_PRINT("abc driver is not enabled\n"); + + abc_hub_pinfo->enabled = ABC_HUB_ENABLED; +#if IS_ENABLED(CONFIG_SEC_ABC_HUB_COND) && !IS_ENABLED(CONFIG_SEC_FACTORY) + if (abc_hub_pinfo->pdata->cond.init) + abc_hub_cond_enable(dev, abc_hub_pinfo->enabled); +#endif +#if IS_ENABLED(CONFIG_SEC_ABC_HUB_BOOTC) + if (abc_hub_pinfo->pdata->bootc_pdata.init) + abc_hub_bootc_enable(dev, abc_hub_pinfo->enabled); +#endif + } else if (!strncmp(buf, "0", 1)) { + ABC_PRINT("abc_hub driver disabled.\n"); + abc_hub_pinfo->enabled = ABC_HUB_DISABLED; +#if IS_ENABLED(CONFIG_SEC_ABC_HUB_COND) && !IS_ENABLED(CONFIG_SEC_FACTORY) + if (abc_hub_pinfo->pdata->cond.init) + abc_hub_cond_enable(dev, abc_hub_pinfo->enabled); +#endif +#if IS_ENABLED(CONFIG_SEC_ABC_HUB_BOOTC) + if (abc_hub_pinfo->pdata->bootc_pdata.init) + abc_hub_bootc_enable(dev, abc_hub_pinfo->enabled); +#endif + } + return count; +} +EXPORT_SYMBOL_KUNIT(enable_store); + +__visible_for_testing +ssize_t enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", abc_hub_pinfo->enabled); +} +EXPORT_SYMBOL_KUNIT(enable_show); + +static DEVICE_ATTR_RW(enable); + +#if IS_ENABLED(CONFIG_SEC_ABC_HUB_BOOTC) +__visible_for_testing +ssize_t bootc_offset_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + char module[BOOTC_OFFSET_STR_MAX] = {0,}; + char *cur; + char delim = ','; + int offset = 0; + int i = 0; + + if (strlen(buf) >= BOOTC_OFFSET_STR_MAX || count >= (unsigned int)BOOTC_OFFSET_STR_MAX) { + ABC_PRINT("buf length is over(MAX:%d)!!\n", BOOTC_OFFSET_STR_MAX); + return count; + } + + cur = strchr(buf, (int)delim); + if (cur) { + memcpy(module, buf, cur - buf); + if (kstrtoint(cur + 1, 0, &offset)) { + ABC_PRINT("failed to get offest\n"); + return count; + } + } else { + ABC_PRINT("failed to get module\n"); + return count; + } + + ABC_PRINT("module(%s), offset(%d)\n", module, offset); + + if (offset >= 0) { + for (i = 0; i < BOOTC_OFFSET_DATA_CNT; i++) { + if (strcmp(module, abc_hub_pinfo->pdata->bootc_pdata.offset_data[i].module) == 0) { + abc_hub_pinfo->pdata->bootc_pdata.offset_data[i].offset = offset; + break; + } + } + } + + return count; +} +EXPORT_SYMBOL_KUNIT(bootc_offset_store); + +__visible_for_testing +ssize_t bootc_offset_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int res = 0; + int i; + + for (i = 0; i < BOOTC_OFFSET_DATA_CNT; i++) { + res += sprintf(buf, "%s,%d\n", abc_hub_pinfo->pdata->bootc_pdata.offset_data[i].module, + abc_hub_pinfo->pdata->bootc_pdata.offset_data[i].offset); + } + + return res; +} +EXPORT_SYMBOL_KUNIT(bootc_offset_show); + +static DEVICE_ATTR_RW(bootc_offset); + +__visible_for_testing +ssize_t bootc_time_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + if (kstrtoint(buf, 0, &(abc_hub_pinfo->pdata->bootc_pdata.bootc_time))) { + ABC_PRINT("failed to get bootc_time\n"); + abc_hub_pinfo->pdata->bootc_pdata.bootc_time = -1; + return count; + } + + ABC_PRINT("bootc_time(%d)\n", abc_hub_pinfo->pdata->bootc_pdata.bootc_time); + + return count; +} +EXPORT_SYMBOL_KUNIT(bootc_time_store); + +__visible_for_testing +ssize_t bootc_time_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int res = sprintf(buf, "%d\n", abc_hub_pinfo->pdata->bootc_pdata.bootc_time); + + return res; +} +EXPORT_SYMBOL_KUNIT(bootc_time_show); + +static DEVICE_ATTR_RW(bootc_time); +#endif + +int abc_hub_get_enabled(void) +{ + + if (!abc_hub_probed) + return 0; + + return abc_hub_pinfo->enabled; +} +EXPORT_SYMBOL(abc_hub_get_enabled); + +/* event string format + * + * ex) MODULE=tsp@ERROR=power_status_mismatch + * MODULE=tsp@ERROR=power_status_mismatch@EXT_LOG=fw_ver(0108) + * + */ +void abc_hub_send_event(char *str) +{ + + if (!abc_hub_probed) { + ABC_PRINT_KUNIT("ABC Hub driver is not initialized!\n"); + return; + } + + if (abc_hub_pinfo->enabled == ABC_HUB_DISABLED) { + ABC_PRINT_KUNIT("ABC Hub is disabled!\n"); + return; + } +#if IS_ENABLED(CONFIG_SEC_KUNIT) + ABC_PRINT_KUNIT("%s", str); + return; +#endif + /* It just sends event to abc driver. The function will be added for gathering hw param big data. */ + sec_abc_send_event(str); +} +EXPORT_SYMBOL(abc_hub_send_event); + +static int abc_hub_probe(struct platform_device *pdev) +{ + struct abc_hub_platform_data *pdata; + int ret = 0; + + ABC_PRINT("start"); + + abc_hub_probed = false; + + if (pdev->dev.of_node) { + pdata = devm_kzalloc(&pdev->dev, + sizeof(struct abc_hub_platform_data), GFP_KERNEL); + + if (!pdata) { + dev_err(&pdev->dev, "Failed to allocate platform data\n"); + ret = -ENOMEM; + goto out; + } + + pdev->dev.platform_data = pdata; + ret = abc_hub_parse_dt(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Failed to parse dt data\n"); + goto err_parse_dt; + } + + ABC_PRINT("parse dt done\n"); + } else { + pdata = pdev->dev.platform_data; + } + + if (!pdata) { + dev_err(&pdev->dev, "There are no platform data\n"); + ret = -EINVAL; + goto out; + } + + abc_hub_pinfo = kzalloc(sizeof(*abc_hub_pinfo), GFP_KERNEL); + + if (!abc_hub_pinfo) { + ret = -ENOMEM; + goto err_alloc_abc_hub_pinfo; + } +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + abc_hub_pinfo->dev = sec_device_create(abc_hub_pinfo, "sec_abc_hub"); +#else + abc_hub_pinfo->dev = device_create(sec_class, NULL, 0, NULL, "sec_abc_hub"); +#endif + if (IS_ERR(abc_hub_pinfo->dev)) { + ABC_PRINT("Failed to create device(sec_abc_hub)!\n"); + ret = -ENODEV; + goto err_create_device; + } + abc_hub_dev = abc_hub_pinfo->dev; + + ret = device_create_file(abc_hub_pinfo->dev, &dev_attr_enable); + if (ret) { + ABC_PRINT("Failed to create device enabled file\n"); + ret = -ENODEV; + goto err_create_abc_hub_enable_sysfs; + } + +#if IS_ENABLED(CONFIG_SEC_ABC_HUB_BOOTC) + ret = device_create_file(abc_hub_pinfo->dev, &dev_attr_bootc_offset); + if (ret) { + ABC_PRINT("Failed to create device bootc_offset file\n"); + ret = -ENODEV; + goto err_create_abc_hub_bootc_offset_sysfs; + } + + ret = device_create_file(abc_hub_pinfo->dev, &dev_attr_bootc_time); + if (ret) { + ABC_PRINT("Failed to create device bootc_time file\n"); + ret = -ENODEV; + goto err_create_abc_hub_bootc_time_sysfs; + } +#endif + abc_hub_pinfo->pdata = pdata; + platform_set_drvdata(pdev, abc_hub_pinfo); + +#if IS_ENABLED(CONFIG_SEC_ABC_HUB_BOOTC) + if (pdata->bootc_pdata.init) { + if (abc_hub_bootc_init(abc_hub_dev) < 0) { + dev_err(&pdev->dev, "abc_hub_booc_init fail\n"); + pdata->bootc_pdata.init = 0; + } + } +#endif + + abc_hub_probed = true; + ABC_PRINT("Success"); + return ret; + +#if IS_ENABLED(CONFIG_SEC_ABC_HUB_BOOTC) +err_create_abc_hub_bootc_time_sysfs: + device_remove_file(abc_hub_pinfo->dev, &dev_attr_bootc_offset); +err_create_abc_hub_bootc_offset_sysfs: + device_remove_file(abc_hub_pinfo->dev, &dev_attr_enable); +#endif +err_create_abc_hub_enable_sysfs: +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + sec_device_destroy(abc_hub_dev->devt); +#else + device_destroy(sec_class, abc_hub_dev->devt); +#endif +err_create_device: + kfree(abc_hub_pinfo); +err_alloc_abc_hub_pinfo: +err_parse_dt: + devm_kfree(&pdev->dev, pdata); + pdev->dev.platform_data = NULL; +out: + return ret; +} + +static struct platform_driver abc_hub_driver = { + .probe = abc_hub_probe, + .remove = abc_hub_remove, + .driver = { + .name = "abc_hub", + .owner = THIS_MODULE, +#if IS_ENABLED(CONFIG_PM) + .pm = &abc_hub_pm, +#endif +#if IS_ENABLED(CONFIG_OF) + .of_match_table = of_match_ptr(abc_hub_dt_match), +#endif + }, +}; + +static int __init abc_hub_init(void) +{ + ABC_PRINT("init"); + + return platform_driver_register(&abc_hub_driver); +} + +static void __exit abc_hub_exit(void) +{ + return platform_driver_unregister(&abc_hub_driver); +} + +module_init(abc_hub_init); +module_exit(abc_hub_exit); + +MODULE_DESCRIPTION("Samsung ABC Hub Driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/sti/abc/abc_motto.c b/drivers/sti/abc/abc_motto.c new file mode 100644 index 000000000000..c9b4c72c211a --- /dev/null +++ b/drivers/sti/abc/abc_motto.c @@ -0,0 +1,196 @@ +/* abc_motto.c + * + * Abnormal Behavior Catcher MOTTO Support + * + * Copyright (C) 2020 Samsung Electronics + * + * 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. + * + */ +#include + +extern struct device *sec_abc; + +#if defined(CONFIG_ARCH_QCOM) +static unsigned int qet_loaded; +module_param(qet_loaded, uint, 0440); +#endif + +struct motto_event_type motto_event_type_list[] = { + {MOTTO_MODULE_GPU, "gpu", "gpu_fault"}, + {MOTTO_MODULE_GPU, "gpu_qc", "gpu_fault"}, + {MOTTO_MODULE_DECON, "decon", "fence_timeout"}, + {MOTTO_MODULE_CAMERA, "camera", "camera_error"}, + {MOTTO_MODULE_CAMERA, "camera", "i2c_fail"}, + {MOTTO_MODULE_NPU, "npu", "npu_fw_warning"}, + {MOTTO_MODULE_NPU, "camera", "mipi_overflow"}, + // we can add more like + // {MOTTO_MODULE_GPU, "gpu_qc", "gpu_new_event"} + // {MOTTO_MODULE_CAMERA, "camera", "camera_new_event"}, + // {MOTTO_MODULE_NEW, "new", "new_something"} +}; + +static void motto_update_event(enum motto_event_module module) +{ + struct abc_info *pinfo; + struct abc_motto_data *cmotto; + + pinfo = dev_get_drvdata(sec_abc); + cmotto = pinfo->pdata->motto_data; + + if (cmotto->dev_err_count[module] == 0xff) // 0xff means max value of 8bit variable dev_err_count[module] + return; + + cmotto->dev_err_count[module]++; + + return; +} + +static enum motto_event_module motto_event_to_idx(char *module_str, char *event) +{ + int i, items = sizeof(motto_event_type_list) / sizeof(motto_event_type_list[0]); + size_t module_str_len = strlen(module_str); + + for (i = 0; i < items; i++) { + if (strlen(motto_event_type_list[i].motto_event_module_name) == module_str_len && + !strncmp(motto_event_type_list[i].motto_event_module_name, module_str, module_str_len) && + !strcmp(motto_event_type_list[i].motto_event_type_str, event)) + return motto_event_type_list[i].module; + } + return MOTTO_MODULE_NONE; +} +void get_motto_uevent_str(char *uevent_str) +{ + struct abc_info *pinfo; + struct abc_motto_data *cmotto; + char temp[9]; + int i; + + pinfo = dev_get_drvdata(sec_abc); + cmotto = pinfo->pdata->motto_data; + + sprintf(uevent_str,"AME=%08x", cmotto->boot_time); + + for (i=0;idev_err_count[i]); + strcat(uevent_str, temp); + } + + return; +} +void motto_send_uevent(void) +{ + char temp[ABC_BUFFER_MAX]; + char *uevent_str[3] = {0,}; + + get_motto_uevent_str(temp); + + uevent_str[0] = temp; + //uevent_str[1] = ×tamp[0]; + uevent_str[2] = NULL; + + kobject_uevent_env(&sec_abc->kobj, KOBJ_CHANGE, uevent_str); +} +void motto_send_device_info(char *module_str, char *event_type) +{ + enum motto_event_module module = motto_event_to_idx(module_str, event_type); + + if (module > MOTTO_MODULE_NONE) { + ABC_PRINT("%s\n", event_type); + motto_update_event(module); + + motto_send_uevent(); + } +} +static ssize_t show_abc_motto_info(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct abc_motto_data *cmotto; + struct abc_info *pinfo = dev_get_drvdata(dev); + char temp[ABC_BUFFER_MAX]; + char retstr[ABC_BUFFER_MAX], tempcat[24]; + int i; + + get_motto_uevent_str(temp); + + cmotto = pinfo->pdata->motto_data; + ABC_PRINT("%s\n", temp); + sprintf(retstr,"%d boot %d", sec_abc_get_enabled(), cmotto->boot_time); + for (i=0;idev_err_count[i]); + strcat(retstr, tempcat); + } + ABC_PRINT("%s\n", retstr); + + return sprintf(buf, "%s\n", retstr); +} +static DEVICE_ATTR(motto_info, 0444, show_abc_motto_info, NULL); +#if defined(CONFIG_ARCH_QCOM) +static ssize_t show_abc_motto_motto_qet_loaded(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", qet_loaded); +} +static DEVICE_ATTR(motto_qc_qet_loaded, 0444, show_abc_motto_motto_qet_loaded, NULL); +#endif +void motto_init(struct platform_device *pdev) +{ + struct abc_info *pinfo; + int ret = 0; + + pinfo = dev_get_drvdata(sec_abc); + + ret = device_create_file(pinfo->dev, &dev_attr_motto_info); + if (ret) { + pr_err("%s: Failed to create device motto_info file\n", __func__); + goto err_create_abc_motto_info_sysfs; + } +#if defined(CONFIG_ARCH_QCOM) + ret = device_create_file(pinfo->dev, &dev_attr_motto_qc_qet_loaded); + if (ret) { + pr_err("%s: Failed to create device motto_qc_qet_loaded file\n", __func__); + goto err_create_abc_motto_info_sysfs; + } +#endif + pinfo->pdata->motto_data = devm_kzalloc(&pdev->dev, + sizeof(struct abc_motto_data), GFP_KERNEL); + if (pinfo->pdata->motto_data == NULL) + goto err_alloc_motto_data; + + return; + +err_alloc_motto_data: + device_remove_file(pinfo->dev, &dev_attr_motto_info); +err_create_abc_motto_info_sysfs: + return; +} + +void motto_send_bootcheck_info(int boot_time) +{ + struct abc_info *pinfo; + struct abc_motto_data *cmotto; + + ABC_PRINT("%d\n", boot_time); + pinfo = dev_get_drvdata(sec_abc); + cmotto = pinfo->pdata->motto_data; + cmotto->boot_time = (boot_time >= 0xFF) ? 0xFF : (u32)boot_time; + + motto_send_uevent(); +} +EXPORT_SYMBOL(motto_send_bootcheck_info); + +MODULE_DESCRIPTION("Samsung ABC Driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/sti/abc/abc_spec_manager.c b/drivers/sti/abc/abc_spec_manager.c new file mode 100644 index 000000000000..247ad66eb0da --- /dev/null +++ b/drivers/sti/abc/abc_spec_manager.c @@ -0,0 +1,504 @@ +/* abc_spec_manager.c + * + * Abnormal Behavior Catcher's spec manager. + * + * Copyright 2021 Samsung Electronics + * + * 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. + * + */ +#include +#if IS_ENABLED(CONFIG_SEC_KUNIT) +#include +#endif +struct list_head abc_spec_list; +EXPORT_SYMBOL_KUNIT(abc_spec_list); + +struct abc_event_group_struct abc_event_group_list[] = { + {ABC_GROUP_CAMERA_MIPI_ERROR_ALL, "camera", "mipi_error_all"}, +}; + +#if IS_ENABLED(CONFIG_OF) +int abc_parse_dt(struct device *dev) +{ + struct abc_platform_data *pdata = dev->platform_data; + struct spec_data_type1 *spec_type1; + struct device_node *np; + struct device_node *type1_np; + int idx, rc; + + np = dev->of_node; + pdata->nItem = of_get_child_count(np); + if (!pdata->nItem) { + dev_err(dev, "There are no items"); + return -ENODEV; + } + + /* spec_type_1 */ + type1_np = of_find_node_by_name(np, "abc_spec_type1"); + rc = of_property_count_strings(type1_np, ERROR_KEY); + INIT_LIST_HEAD(&abc_spec_list); + + for (idx = 0; idx < rc; idx++) { + spec_type1 = devm_kzalloc(dev, sizeof(struct spec_data_type1), GFP_KERNEL); + + if (!spec_type1) + return -ENOMEM; + + if (abc_parse_dt_type1(dev, type1_np, idx, spec_type1)) { + ABC_PRINT("failed parse dt spec_type1 idx : %d", idx); + continue; + } + list_add_tail(&spec_type1->node, &abc_spec_list); + } + return 0; +} +#endif + +int sec_abc_get_normal_token_value(char *dst, char *src, char *token) +{ + int token_len = strlen(token); + + if (strncmp(src, token, token_len) || !*(src + token_len)) { + ABC_DEBUG("Invalid input : src-%s, token-%s", src, token); + return -EINVAL; + } + + strlcpy(dst, src + token_len, ABC_EVENT_STR_MAX); + return 0; +} +EXPORT_SYMBOL_KUNIT(sec_abc_get_normal_token_value); + +int sec_abc_get_event_module(char *dst, char *src) +{ + return sec_abc_get_normal_token_value(dst, src, "MODULE="); +} +EXPORT_SYMBOL_KUNIT(sec_abc_get_event_module); + +int sec_abc_get_ext_log(char *dst, char *src) +{ + return sec_abc_get_normal_token_value(dst, src, "EXT_LOG="); +} +EXPORT_SYMBOL_KUNIT(sec_abc_get_ext_log); + +int sec_abc_get_event_name(char *dst, char *src) +{ + int ret_info = 0, ret_warn = 0; + + ret_info = sec_abc_get_normal_token_value(dst, src, "INFO="); + ret_warn = sec_abc_get_normal_token_value(dst, src, "WARN="); + + return (ret_info & ret_warn) ? -EINVAL : 0; +} +EXPORT_SYMBOL_KUNIT(sec_abc_get_event_name); + +int sec_abc_get_event_type(char *dst, char *src) +{ + if (strncmp(src, "WARN", 4) && strncmp(src, "INFO", 4)) { + ABC_PRINT("Invalid input : %s", src); + return -EINVAL; + } + + strlcpy(dst, src, 5); + return 0; +} +EXPORT_SYMBOL_KUNIT(sec_abc_get_event_type); + +int sec_abc_get_count(int *dst, char *src) +{ + if (strncmp(src, "COUNT=", 6) || !*(src + 6)) { + ABC_PRINT("Invalid input : %s", src); + return -EINVAL; + } + + if (!strncmp(src + 6, "DEFAULT", 7)) { + *dst = ABC_DEFAULT_COUNT; + return 0; + } + + if (kstrtoint(src + 6, 0, dst) || *dst <= 0 || *dst > ABC_EVENT_BUFFER_MAX) { + ABC_PRINT("Invalid input : %s", src); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_KUNIT(sec_abc_get_count); + +unsigned int sec_abc_get_ktime_ms(void) +{ + u64 ktime; + + /* Calculate current kernel time */ + ktime = local_clock(); + do_div(ktime, NSEC_PER_MSEC); + + return (unsigned int)ktime; +} +EXPORT_SYMBOL_KUNIT(sec_abc_get_ktime_ms); + +int sec_abc_make_key_data(struct abc_key_data *key_data, char *str) +{ + char *event_strings[ABC_UEVENT_MAX] = {0,}; + char temp[ABC_BUFFER_MAX]; + char *c, *p; + int idx = 0, i; + + ABC_DEBUG("start : %s", str); + + strlcpy(temp, str, ABC_BUFFER_MAX); + p = temp; + + while ((c = strsep(&p, "@")) != NULL && idx < ABC_UEVENT_MAX) { + event_strings[idx] = c; + idx++; + } + + if (idx >= ABC_UEVENT_MAX) + return -EINVAL; + + if (sec_abc_get_event_module(key_data->event_module, event_strings[0])) + return -EINVAL; + + if (sec_abc_get_event_name(key_data->event_name, event_strings[1])) + return -EINVAL; + + if (sec_abc_get_event_type(key_data->event_type, event_strings[1])) + return -EINVAL; + + for (i = 2; i < idx; i++) { + if (!strncmp(event_strings[i], "EXT_LOG=", 8)) { + if (sec_abc_get_ext_log(key_data->ext_log, event_strings[i])) + return -EINVAL; + } else + return -EINVAL; + } + + key_data->cur_time = sec_abc_get_ktime_ms(); + + ABC_DEBUG("Module(%s) Level(%s) Event(%s) EXT_LOG(%s) cur_time(%d)", + key_data->event_module, + key_data->event_type, + key_data->event_name, + key_data->ext_log, + key_data->cur_time); + return 0; +} +EXPORT_SYMBOL_KUNIT(sec_abc_make_key_data); + +struct abc_common_spec_data *sec_abc_get_matched_common_spec(char *module_name, char *error_name) +{ + return sec_abc_get_matched_common_spec_type1(module_name, error_name); +} +EXPORT_SYMBOL_KUNIT(sec_abc_get_matched_common_spec); + +int sec_abc_get_buffer_size_from_threshold_cnt(int threshold_cnt) +{ + int i, min_buffer_size = 16; + + for (i = 1; i < threshold_cnt; i *= 2) + ; + + return min_buffer_size > i ? min_buffer_size : i; +} +EXPORT_SYMBOL_KUNIT(sec_abc_get_buffer_size_from_threshold_cnt); + +void sec_abc_enqueue_event_data(struct abc_key_data *key_data) +{ + struct abc_common_spec_data *common_spec = NULL; + + common_spec = sec_abc_get_matched_common_spec(key_data->event_module, key_data->event_name); + + if (!common_spec || !strcmp(key_data->event_type, "INFO")) { + ABC_PRINT_KUNIT("There is no matched buffer"); + } else { + ABC_DEBUG_KUNIT("There is a matched buffer. Enqueue data"); + sec_abc_enqueue_event_data_type1(common_spec, key_data->cur_time); + } + +} +EXPORT_SYMBOL_KUNIT(sec_abc_enqueue_event_data); + +void sec_abc_reset_event_buffer(struct abc_key_data *key_data) +{ + struct abc_common_spec_data *common_spec; + struct spec_data_type1 *spec_type1; + + common_spec = sec_abc_get_matched_common_spec(key_data->event_module, key_data->event_name); + + if (!common_spec || !strcmp(key_data->event_type, "INFO")) { + ABC_PRINT_KUNIT("There is no matched buffer"); + } else { + ABC_PRINT_KUNIT("There is a matched buffer. Reset buffer"); + spec_type1 = container_of(common_spec, struct spec_data_type1, common_spec); + sec_abc_reset_buffer_type1(spec_type1); + } +} +EXPORT_SYMBOL_KUNIT(sec_abc_reset_event_buffer); + +bool sec_abc_reached_spec(struct abc_key_data *key_data) +{ + struct abc_common_spec_data *common_spec; + + if (!strcmp(key_data->event_type, "INFO")) { + ABC_PRINT("INFO doesn't have spec"); + return false; + } + + common_spec = sec_abc_get_matched_common_spec(key_data->event_module, key_data->event_name); + if (!common_spec) + return true; + + return sec_abc_reached_spec_type1(common_spec, key_data->cur_time); +} +EXPORT_SYMBOL_KUNIT(sec_abc_reached_spec); + +int sec_abc_parse_spec_cmd(char *str, struct abc_spec_cmd *abc_spec) +{ + int idx = 0; + char temp[ABC_BUFFER_MAX]; + char *c, *p; + char *sys_input_strings[3] = { 0, }; + + strlcpy(temp, str, ABC_BUFFER_MAX); + p = temp; + + while ((c = strsep(&p, "@")) != NULL && idx < ABC_SPEC_CMD_STR_MAX) { + sys_input_strings[idx] = c; + idx++; + } + + if (idx != ABC_SPEC_CMD_STR_MAX) + return -EINVAL; + + if (sec_abc_get_event_module(abc_spec->module, sys_input_strings[0])) + return -EINVAL; + + if (sec_abc_get_event_name(abc_spec->name, sys_input_strings[1])) + return -EINVAL; + + strlcpy(abc_spec->spec, sys_input_strings[2], ABC_EVENT_STR_MAX); + + return 0; +} +EXPORT_SYMBOL_KUNIT(sec_abc_parse_spec_cmd); + +int sec_abc_apply_changed_spec(char *module_name, char *error_name, char *spec) +{ + struct abc_common_spec_data *common_spec; + struct spec_data_type1 *spec_type1; + int idx = 0, count = 0; + + ABC_PRINT("start : %s %s %s", module_name, error_name, spec); + idx = sec_abc_get_idx_of_registered_event(module_name, error_name); + + if (idx < 0) + return -EINVAL; + + if (!strcmp(spec, "OFF")) { + abc_event_list[idx].enabled = false; + return 0; + } + + if (sec_abc_get_count(&count, spec)) + return -EINVAL; + + if (abc_event_list[idx].singular_spec) { + if (count == ABC_DEFAULT_COUNT) { + abc_event_list[idx].enabled = true; + return 0; + } else + return -EINVAL; + } + + common_spec = sec_abc_get_matched_common_spec(module_name, error_name); + + if (!common_spec) + return -EINVAL; + + abc_event_list[idx].enabled = false; + spec_type1 = container_of(common_spec, struct spec_data_type1, common_spec); + + if (count == ABC_DEFAULT_COUNT) + count = spec_type1->default_count; + + spec_type1->threshold_cnt = count; + spec_type1->buffer.size = count + 1; + + if (abc_alloc_memory_to_buffer_type1(spec_type1, spec_type1->buffer.size)) + return -ENOMEM; + + sec_abc_reset_buffer_type1(spec_type1); + abc_event_list[idx].enabled = true; + + ABC_PRINT("MODULE(%s) ERROR(%s) COUNT(%d) TIME(%d) Enabled(%d)", + common_spec->module_name, + common_spec->error_name, + spec_type1->threshold_cnt, + spec_type1->threshold_time, + abc_event_list[idx].enabled); + return 0; +} +EXPORT_SYMBOL_KUNIT(sec_abc_apply_changed_spec); + +enum abc_event_group sec_abc_get_group(char *module, char *name) +{ + int idx = 0; + + for (idx = 0; idx < ARRAY_SIZE(abc_event_group_list); idx++) { + if (strcmp(module, abc_event_group_list[idx].module) == 0 && + strcmp(name, abc_event_group_list[idx].name) == 0) { + return abc_event_group_list[idx].group; + } + } + + return ABC_GROUP_NONE; +} +EXPORT_SYMBOL_KUNIT(sec_abc_get_group); + +int sec_abc_apply_changed_group_spec(enum abc_event_group group, char *spec) +{ + int idx = 0; + + ABC_PRINT("start : %d %s", group, spec); + + for (idx = 0; idx < REGISTERED_ABC_EVENT_TOTAL; idx++) { + if (group == abc_event_list[idx].group) { + if (sec_abc_apply_changed_spec(abc_event_list[idx].module_name, + abc_event_list[idx].error_name, spec)) { + return -EINVAL; + } + } + } + + return 0; +} +EXPORT_SYMBOL_KUNIT(sec_abc_apply_changed_group_spec); + +void sec_abc_change_spec(const char *str) +{ + char *cmd_string, *p; + char temp[ABC_BUFFER_MAX * 5]; + int cnt = 0; + enum abc_event_group group; + struct abc_spec_cmd abc_spec; + + ABC_PRINT("start : %s", str); + + if (!strncmp(str, "reset", 5)) { + sec_abc_reset_all_spec(); + ABC_PRINT("end : %s", str); + return; + } + + strlcpy(temp, str, ABC_BUFFER_MAX * 5); + p = temp; + + while ((cmd_string = strsep(&p, ",\n")) != NULL && cnt < REGISTERED_ABC_EVENT_TOTAL) { + + cnt++; + if (!cmd_string[0]) + continue; + if (sec_abc_parse_spec_cmd(cmd_string, &abc_spec)) { + ABC_PRINT_KUNIT("Invalid change cmd. Check the Input"); + break; + } + group = sec_abc_get_group(abc_spec.module, abc_spec.name); + if (group == ABC_GROUP_NONE) { + if (sec_abc_apply_changed_spec(abc_spec.module, abc_spec.name, abc_spec.spec)) { + ABC_PRINT_KUNIT("Invalid change cmd. Check the Input"); + break; + } + } else { + if (sec_abc_apply_changed_group_spec(group, abc_spec.spec)) { + ABC_PRINT_KUNIT("Invalid change cmd. Check the Input"); + break; + } + } + } +} + +void sec_abc_reset_all_buffer(void) +{ + struct spec_data_type1 *spec_type1; + + list_for_each_entry(spec_type1, &abc_spec_list, node) { + sec_abc_reset_buffer_type1(spec_type1); + } +} +EXPORT_SYMBOL_KUNIT(sec_abc_reset_all_buffer); + +void sec_abc_reset_all_spec(void) +{ + struct spec_data_type1 *spec_type1; + + list_for_each_entry(spec_type1, &abc_spec_list, node) { + spec_type1->threshold_cnt = spec_type1->default_count; + spec_type1->buffer.size = spec_type1->threshold_cnt + 1; + abc_event_list[spec_type1->common_spec.idx].enabled = spec_type1->default_enabled; + sec_abc_reset_buffer_type1(spec_type1); + } +} +EXPORT_SYMBOL_KUNIT(sec_abc_reset_all_spec); + +int sec_abc_read_spec(char *buf) +{ + struct spec_data_type1 *spec_type1; + int len = 0, idx; + + len += scnprintf(buf + len, PAGE_SIZE - len, "spec type1\n"); + + list_for_each_entry(spec_type1, &abc_spec_list, node) { + + len += scnprintf(buf + len, PAGE_SIZE - len, "MODULE=%s ", + spec_type1->common_spec.module_name); + len += scnprintf(buf + len, PAGE_SIZE - len, "WARN=%s ", + spec_type1->common_spec.error_name); + len += scnprintf(buf + len, PAGE_SIZE - len, "THRESHOLD_CNT=%d ", + spec_type1->threshold_cnt); + len += scnprintf(buf + len, PAGE_SIZE - len, "THRESHOLD_TIME=%d ", + spec_type1->threshold_time); + idx = spec_type1->common_spec.idx; + len += scnprintf(buf + len, PAGE_SIZE - len, "ENABLE=%s", + ((abc_event_list[idx].enabled) ? "ON" : "OFF")); + len += scnprintf(buf + len, PAGE_SIZE - len, "\n"); + } + + for (idx = 0; idx < ARRAY_SIZE(abc_event_group_list); idx++) { + len += scnprintf(buf + len, PAGE_SIZE - len, "MODULE=%s ", + abc_event_group_list[idx].module); + len += scnprintf(buf + len, PAGE_SIZE - len, "WARN=%s ", + abc_event_group_list[idx].name); + len += scnprintf(buf + len, PAGE_SIZE - len, "THRESHOLD_CNT=group "); + len += scnprintf(buf + len, PAGE_SIZE - len, "THRESHOLD_TIME=group "); + len += scnprintf(buf + len, PAGE_SIZE - len, "ENABLE=group"); + len += scnprintf(buf + len, PAGE_SIZE - len, "\n"); + } + + ABC_PRINT("%d", len); + return len; +} + +void sec_abc_free_spec_buffer(void) +{ + struct spec_data_type1 *spec_buf; + + list_for_each_entry(spec_buf, &abc_spec_list, node) { + kfree(spec_buf->buffer.abc_element); + spec_buf->buffer.abc_element = NULL; + spec_buf->buffer.buffer_max = 0; + } +} +EXPORT_SYMBOL_KUNIT(sec_abc_free_spec_buffer); + +MODULE_DESCRIPTION("Samsung ABC Driver's spec manager"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/sti/abc/abc_spec_manager_type1.c b/drivers/sti/abc/abc_spec_manager_type1.c new file mode 100644 index 000000000000..62f347506059 --- /dev/null +++ b/drivers/sti/abc/abc_spec_manager_type1.c @@ -0,0 +1,250 @@ +/* abc_spec_manager_type1.c + * + * Abnormal Behavior Catcher's spec(type1) manager + * + * Copyright 2021 Samsung Electronics + * + * 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. + * + */ +#include +#if IS_ENABLED(CONFIG_SEC_KUNIT) +#include +#endif +int abc_alloc_memory_to_buffer_type1(struct spec_data_type1 *spec_type1, int size) +{ + struct abc_fault_info *temp_abc_elements; + int buffer_max; + + buffer_max = sec_abc_get_buffer_size_from_threshold_cnt(size); + + if (spec_type1->buffer.buffer_max >= buffer_max) { + ABC_PRINT("Doesn't need to alloc memory"); + return 0; + } + + if (!spec_type1->buffer.abc_element) { + ABC_PRINT("allocate new memory"); + temp_abc_elements = kcalloc(buffer_max, sizeof(spec_type1->buffer.abc_element[0]), GFP_KERNEL); + } else { + ABC_PRINT("reallocate memory"); + temp_abc_elements = krealloc(spec_type1->buffer.abc_element, + sizeof(spec_type1->buffer.abc_element[0]) * buffer_max, GFP_KERNEL); + } + + if (unlikely(ZERO_OR_NULL_PTR(temp_abc_elements))) { + ABC_PRINT("Failed to allocate memory to buffer"); + spec_type1->buffer.abc_element = NULL; + spec_type1->buffer.buffer_max = 0; + spec_type1->buffer.size = 0; + return -ENOMEM; + } + + spec_type1->buffer.abc_element = temp_abc_elements; + spec_type1->buffer.buffer_max = buffer_max; + + return 0; +} +EXPORT_SYMBOL_KUNIT(abc_alloc_memory_to_buffer_type1); + +#if IS_ENABLED(CONFIG_OF) +int abc_parse_dt_type1(struct device *dev, + struct device_node *np, + int idx, + struct spec_data_type1 *spec_type1) +{ + int registered_idx; + + if (of_property_read_string_index(np, MODULE_KEY, idx, (const char **)&spec_type1->common_spec.module_name)) { + dev_err(dev, "Failed to get module name : node not exist"); + return -EINVAL; + } + + if (of_property_read_string_index(np, ERROR_KEY, idx, (const char **)&spec_type1->common_spec.error_name)) { + dev_err(dev, "Failed to get error name : node not exist"); + return -EINVAL; + } + + registered_idx = sec_abc_get_idx_of_registered_event((char *)spec_type1->common_spec.module_name, + (char *)spec_type1->common_spec.error_name); + + if (registered_idx < 0) { + ABC_PRINT("Unregistered Event. %s : %s", spec_type1->common_spec.module_name, + spec_type1->common_spec.error_name); + return -EINVAL; + } + + spec_type1->common_spec.idx = registered_idx; + + if (of_property_read_u32_index(np, THRESHOLD_CNT_KEY, idx, &spec_type1->threshold_cnt)) { + dev_err(dev, "Failed to get threshold count : node not exist"); + return -EINVAL; + } + + if (of_property_read_u32_index(np, THRESHOLD_TIME_KEY, idx, &spec_type1->threshold_time)) { + dev_err(dev, "Failed to get threshold time : node not exist"); + return -EINVAL; + } + + if (spec_type1->threshold_time == 0) + spec_type1->threshold_time = INT_MAX; + + if (abc_alloc_memory_to_buffer_type1(spec_type1, spec_type1->threshold_cnt + 1)) + return -ENOMEM; + + spec_type1->default_count = spec_type1->threshold_cnt; + spec_type1->buffer.size = spec_type1->threshold_cnt + 1; + spec_type1->default_enabled = abc_event_list[spec_type1->common_spec.idx].enabled; + sec_abc_reset_buffer_type1(spec_type1); + + ABC_PRINT("type1-spec : module(%s) error(%s) threshold_cnt(%d) threshold_time(%d) enabled(%s)", + spec_type1->common_spec.module_name, + spec_type1->common_spec.error_name, + spec_type1->threshold_cnt, + spec_type1->threshold_time, + ((abc_event_list[spec_type1->common_spec.idx].enabled) ? "ON" : "OFF")); + + return 0; +} +#endif + +struct abc_common_spec_data *sec_abc_get_matched_common_spec_type1(char *module_name, char *error_name) +{ + struct spec_data_type1 *spec_type1; + + list_for_each_entry(spec_type1, &abc_spec_list, node) { + if (!strcmp(spec_type1->common_spec.module_name, module_name) + && !strcmp(spec_type1->common_spec.error_name, error_name)) + return &(spec_type1->common_spec); + } + return NULL; +} + +bool sec_abc_reached_spec_type1(struct abc_common_spec_data *common_spec, unsigned int cur_time) +{ + struct spec_data_type1 *spec_type1; + + spec_type1 = container_of(common_spec, struct spec_data_type1, common_spec); + + do_div(cur_time, MSEC_PER_SEC); + + ABC_DEBUG("MODULE(%s) WARN(%s) warn_cnt(%d) time(%d)", + spec_type1->common_spec.module_name, + spec_type1->common_spec.error_name, + spec_type1->buffer.warn_cnt, + cur_time); + + if (spec_type1->buffer.warn_cnt >= spec_type1->threshold_cnt) { + if (sec_abc_get_diff_time_type1(&spec_type1->buffer) < spec_type1->threshold_time) { + ABC_PRINT("%s occurred. Send uevent", spec_type1->common_spec.error_name); + return true; + } + sec_abc_dequeue_event_data_type1(common_spec); + } + return false; +} +EXPORT_SYMBOL_KUNIT(sec_abc_reached_spec_type1); + +void sec_abc_reset_buffer_type1(struct spec_data_type1 *spec_type1) +{ + spec_type1->buffer.rear = 0; + spec_type1->buffer.front = 0; + spec_type1->buffer.warn_cnt = 0; +} +EXPORT_SYMBOL_KUNIT(sec_abc_reset_buffer_type1); + +bool sec_abc_is_full_type1(struct abc_event_buffer *buffer) +{ + if ((buffer->rear + 1) % buffer->size == buffer->front) + return true; + else + return false; +} +EXPORT_SYMBOL_KUNIT(sec_abc_is_full_type1); + +bool sec_abc_is_empty_type1(struct abc_event_buffer *buffer) +{ + if (buffer->front == buffer->rear) + return true; + else + return false; +} +EXPORT_SYMBOL_KUNIT(sec_abc_is_empty_type1); + +void sec_abc_enqueue_type1(struct abc_event_buffer *buffer, struct abc_fault_info in) +{ + if (sec_abc_is_full_type1(buffer)) { + ABC_PRINT_KUNIT("queue is full"); + } else { + buffer->rear = (buffer->rear + 1) % buffer->size; + buffer->abc_element[buffer->rear] = in; + } +} +EXPORT_SYMBOL_KUNIT(sec_abc_enqueue_type1); + +struct abc_fault_info sec_abc_dequeue_type1(struct abc_event_buffer *buffer) +{ + struct abc_fault_info out; + + if (sec_abc_is_empty_type1(buffer)) { + ABC_PRINT_KUNIT("queue is empty"); + out.cur_time = out.cur_cnt = 0; + } else { + buffer->front = (buffer->front + 1) % buffer->size; + out = buffer->abc_element[buffer->front]; + } + return out; +} +EXPORT_SYMBOL_KUNIT(sec_abc_dequeue_type1); + +void sec_abc_enqueue_event_data_type1(struct abc_common_spec_data *common_spec, unsigned int cur_time) +{ + struct spec_data_type1 *spec_type1 = container_of(common_spec, struct spec_data_type1, common_spec); + struct abc_fault_info in; + + do_div(cur_time, MSEC_PER_SEC); + + in.cur_time = (int)cur_time; + in.cur_cnt = spec_type1->buffer.warn_cnt++; + + sec_abc_enqueue_type1(&spec_type1->buffer, in); +} + +void sec_abc_dequeue_event_data_type1(struct abc_common_spec_data *common_spec) +{ + struct spec_data_type1 *spec_type1 = container_of(common_spec, struct spec_data_type1, common_spec); + struct abc_fault_info out; + + out = sec_abc_dequeue_type1(&spec_type1->buffer); + spec_type1->buffer.warn_cnt--; +} + +int sec_abc_get_diff_time_type1(struct abc_event_buffer *buffer) +{ + int front_time, rear_time; + + front_time = buffer->abc_element[(buffer->front + 1) % buffer->size].cur_time; + rear_time = buffer->abc_element[buffer->rear].cur_time; + + ABC_DEBUG("front time : %d sec (%d) rear_time %d sec (%d) diff : %d", + front_time, + buffer->front + 1, + rear_time, + buffer->rear, + rear_time - front_time); + + return rear_time - front_time; +} +EXPORT_SYMBOL_KUNIT(sec_abc_get_diff_time_type1); + +MODULE_DESCRIPTION("Samsung ABC Driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/sti/abc/connector_disconnected_count.c b/drivers/sti/abc/connector_disconnected_count.c new file mode 100644 index 000000000000..af6e7b48bc84 --- /dev/null +++ b/drivers/sti/abc/connector_disconnected_count.c @@ -0,0 +1,128 @@ + +/* + * connector_disconnected_count.c + * + * Copyright (C) 2022 Samsung Electronics + * + * 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. + * + */ + +#include + +#if defined(CONFIG_SEC_KUNIT) +#define __visible_for_testing +#else +#define __visible_for_testing static +#endif + +static int connector_disconnected_count[DET_CONN_MAX_NUM_GPIOS]; + +/* + * Increase disconnect count + */ +void increase_connector_disconnected_count(int index, struct sec_det_conn_info *pinfo) +{ + if ((index >= DET_CONN_MAX_NUM_GPIOS) || (index < 0)) + return; + if (!gpio_get_value(pinfo->pdata->irq_gpio[index])) + return; + + SEC_CONN_PRINT("%s status changed [disconnected]\n", + pinfo->pdata->name[index]); + if ((connector_disconnected_count[index] >= 0) && (connector_disconnected_count[index] < 9999)) + connector_disconnected_count[index]++; +} + +/* + * Get disconnect count + */ +int get_connector_disconnected_count(int index) +{ + if ((index >= DET_CONN_MAX_NUM_GPIOS) || (index < 0)) + return 0; + + return connector_disconnected_count[index]; +} + +/* + * Clear Accumulated all disconnected count when character 'c','C' is stored. + */ +__visible_for_testing ssize_t connector_disconnected_count_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sec_det_conn_p_data *pdata; + int i; + int buf_len; + + if (gpinfo == 0) + return count; + pdata = gpinfo->pdata; + + buf_len = strlen(buf); + SEC_CONN_PRINT("buf = %s, buf_len = %d\n", buf, buf_len); + + /* disable irq when "enabled" value set to 0*/ + if ((strncmp(buf, "c", 1) == 0) || (strncmp(buf, "C", 1) == 0)) { + for (i = 0; i < pdata->gpio_total_cnt; i++) + connector_disconnected_count[i] = 0; + } + return count; +} + +/* + * Return the "name":"value" pair for connector disconnected count. + * value 1 or more means connector disconnected, and 0 means connected. + * "SUB_CONNECTOR":"3","UPPER_C2C_DET":"0" + */ +__visible_for_testing ssize_t connector_disconnected_count_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sec_det_conn_p_data *pdata; + char connector_disconnected_count_str[1024] = {0, }; + char gpio_value_str[12] = {0, }; + int i; + + if (gpinfo == 0) + return 0; + pdata = gpinfo->pdata; + + for (i = 0; i < pdata->gpio_total_cnt; i++) { + if (i > 0) + strlcat(connector_disconnected_count_str, ",", 1024); + strlcat(connector_disconnected_count_str, "\"", 1024); + strlcat(connector_disconnected_count_str, pdata->name[i], 1024); + strlcat(connector_disconnected_count_str, "\":\"", 1024); + sprintf(gpio_value_str, "%d", get_connector_disconnected_count(i)); + strlcat(connector_disconnected_count_str, gpio_value_str, 1024); + strlcat(connector_disconnected_count_str, "\"", 1024); + } + connector_disconnected_count_str[strlen(connector_disconnected_count_str)] = '\0'; + SEC_CONN_PRINT("connector_disconnected_count : %s\n", connector_disconnected_count_str); + + return snprintf(buf, 1020, "%s", connector_disconnected_count_str); +} +static DEVICE_ATTR_RW(connector_disconnected_count); + +/* + * Create sys node for connector disconnected count for bigdata + */ +void create_connector_disconnected_count_sysnode_file(struct sec_det_conn_info *pinfo) +{ + int ret = 0; + + /* Create sys node /sys/class/sec/sec_detect_conn/connector_disconnected_count */ + ret = device_create_file(pinfo->dev, &dev_attr_connector_disconnected_count); + + if (ret) + pr_err("%s: Failed to create connector_disconnected_count.\n", __func__); +} diff --git a/drivers/sti/abc/connector_setup.c b/drivers/sti/abc/connector_setup.c new file mode 100644 index 000000000000..ebe51bad721f --- /dev/null +++ b/drivers/sti/abc/connector_setup.c @@ -0,0 +1,667 @@ +/* + * connector_setup.c + * + * Copyright (C) 2017 Samsung Electronics + * + * 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. + * + */ + +#include +#if defined(CONFIG_SEC_KUNIT) +#define __mockable __weak +#define __visible_for_testing +#define KUNIT_TEST_STRLEN 30 +struct device *sec_abc_detect_conn_dev; +char kunit_test_str[KUNIT_TEST_STRLEN]; +#else +#define __mockable +#define __visible_for_testing static +#endif + +__visible_for_testing int gsec_abc_detect_conn_enabled; +struct sec_det_conn_info *gpinfo; + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id sec_abc_detect_conn_dt_match[] = { + { .compatible = "samsung,sec_abc_detect_conn" }, + { } +}; +#endif //CONFIG_OF + +#if IS_ENABLED(CONFIG_PM) +static int sec_abc_detect_conn_pm_suspend(struct device *dev) +{ + return 0; +} + +static int sec_abc_detect_conn_pm_resume(struct device *dev) +{ + return 0; +} + +static const struct dev_pm_ops sec_abc_detect_conn_pm = { + .suspend = sec_abc_detect_conn_pm_suspend, + .resume = sec_abc_detect_conn_pm_resume, +}; +#endif //CONFIG_PM + +/* + * Send uevent from irq handler. + */ +void sec_abc_detect_send_uevent_by_num(int num, + struct sec_det_conn_info *pinfo, + int level) +{ + char *uevent_conn_str[3] = {"", "", NULL}; + char uevent_dev_str[UEVENT_CONN_MAX_DEV_NAME]; + char uevent_dev_type_str[UEVENT_CONN_MAX_DEV_NAME]; + + /* Send Uevent Data */ + snprintf(uevent_dev_str, UEVENT_CONN_MAX_DEV_NAME, "CONNECTOR_NAME=%s", + pinfo->pdata->name[num]); + uevent_conn_str[0] = uevent_dev_str; + + if (level == 1) + snprintf(uevent_dev_type_str, UEVENT_CONN_MAX_DEV_NAME, + "CONNECTOR_TYPE=HIGH_LEVEL"); + else + snprintf(uevent_dev_type_str, UEVENT_CONN_MAX_DEV_NAME, + "CONNECTOR_TYPE=LOW_LEVEL"); + + uevent_conn_str[1] = uevent_dev_type_str; + + kobject_uevent_env(&pinfo->dev->kobj, KOBJ_CHANGE, uevent_conn_str); + + SEC_CONN_PRINT("send uevent pin[%d]:CONNECTOR_NAME=%s, TYPE=[%d].\n", + num, pinfo->pdata->name[num], level); +#if defined(CONFIG_SEC_KUNIT) + snprintf(kunit_test_str, KUNIT_TEST_STRLEN, "CONNECTOR_TYPE=HIGH_LEVEL"); +#endif +} + +#if IS_ENABLED(CONFIG_SEC_FACTORY) +/* + * Check gpio and send uevent. + */ +__visible_for_testing void check_gpio_and_send_uevent(int i, + struct sec_det_conn_info *pinfo) +{ + if (gpio_get_value(pinfo->pdata->irq_gpio[i])) { + SEC_CONN_PRINT("%s status changed [disconnected]\n", + pinfo->pdata->name[i]); + sec_abc_detect_send_uevent_by_num(i, pinfo, 1); + } else { + SEC_CONN_PRINT("%s status changed [connected]\n", + pinfo->pdata->name[i]); + sec_abc_detect_send_uevent_by_num(i, pinfo, 0); + } +} +#endif + +#if IS_ENABLED(CONFIG_SEC_ABC_HUB) && IS_ENABLED(CONFIG_SEC_ABC_HUB_COND) +/** + * Send an ABC_event about given gpio pin number. + */ +void send_ABC_event_by_num(int i, struct sec_det_conn_info *pinfo) +{ + char ABC_event_dev_str[ABCEVENT_CONN_MAX_DEV_STRING]; + + if (sec_abc_get_enabled() == 0) + return; + + /*Send ABC Event Data*/ + if (gpio_get_value(pinfo->pdata->irq_gpio[i])) { +#if IS_ENABLED(CONFIG_SEC_FACTORY) + snprintf(ABC_event_dev_str, ABCEVENT_CONN_MAX_DEV_STRING, + "MODULE=cond@INFO=%s", pinfo->pdata->name[i]); +#else + snprintf(ABC_event_dev_str, ABCEVENT_CONN_MAX_DEV_STRING, + "MODULE=cond@WARN=%s", pinfo->pdata->name[i]); +#endif + sec_abc_send_event(ABC_event_dev_str); + + SEC_CONN_PRINT("send ABC cond event %s status is [disconnected] : %s\n", + pinfo->pdata->name[i], ABC_event_dev_str); + } else { + SEC_CONN_PRINT("do not send ABC cond event %s status is [connected]\n", + pinfo->pdata->name[i]); + } +} +#endif + +/* + * Check and send uevent from irq handler. + */ +void sec_abc_detect_send_uevent_irq(int irq, + struct sec_det_conn_info *pinfo, + int type) +{ + int i; + + for (i = 0; i < pinfo->pdata->gpio_total_cnt; i++) { + if (irq == pinfo->pdata->irq_number[i]) { + /* apply s/w debounce time */ + usleep_range(DET_CONN_DEBOUNCE_TIME_MS * 1000, DET_CONN_DEBOUNCE_TIME_MS * 1000); +#if IS_ENABLED(CONFIG_SEC_FACTORY) + check_gpio_and_send_uevent(i, pinfo); +#endif +#if IS_ENABLED(CONFIG_SEC_ABC_HUB) && IS_ENABLED(CONFIG_SEC_ABC_HUB_COND) + send_ABC_event_by_num(i, pinfo); +#endif + increase_connector_disconnected_count(i, pinfo); + } + } +} + +/* + * Called when the connector pin state changes. + */ +static irqreturn_t sec_abc_detect_conn_interrupt_handler(int irq, void *handle) +{ + int type; + struct sec_det_conn_info *pinfo = handle; + + if (gsec_abc_detect_conn_enabled != 0) { + SEC_CONN_PRINT("%s\n", __func__); + + type = irq_get_trigger_type(irq); + sec_abc_detect_send_uevent_irq(irq, pinfo, type); + } + return IRQ_HANDLED; +} + +/* + * Enable all gpio pin IRQ which is from Device Tree. + */ +int detect_conn_irq_enable(struct sec_det_conn_info *pinfo, bool enable, + int pin) +{ + int ret = 0; + int i; + + if (!enable) { + for (i = 0; i < pinfo->pdata->gpio_total_cnt; i++) { + if (pinfo->irq_enabled[i]) { + disable_irq(pinfo->pdata->irq_number[i]); + pinfo->irq_enabled[i] = DET_CONN_GPIO_IRQ_DISABLED; + } + } + return ret; + } + + if (pin >= pinfo->pdata->gpio_total_cnt) + return ret; + + if (pinfo->irq_enabled[pin] == DET_CONN_GPIO_IRQ_NOT_INIT) { + ret = request_threaded_irq(pinfo->pdata->irq_number[pin], NULL, + sec_abc_detect_conn_interrupt_handler, + pinfo->pdata->irq_type[pin] | IRQF_ONESHOT, + pinfo->pdata->name[pin], pinfo); + + if (ret) { + SEC_CONN_PRINT("%s: Failed to request irq %d.\n", __func__, + ret); + return ret; + } + + SEC_CONN_PRINT("%s: Succeeded to request threaded irq %d:\n", + __func__, ret); + } else if (pinfo->irq_enabled[pin] == DET_CONN_GPIO_IRQ_DISABLED) { + enable_irq(pinfo->pdata->irq_number[pin]); + } + SEC_CONN_PRINT("irq_num[%d], type[%x],name[%s].\n", + pinfo->pdata->irq_number[pin], + pinfo->pdata->irq_type[pin], pinfo->pdata->name[pin]); + + pinfo->irq_enabled[pin] = DET_CONN_GPIO_IRQ_ENABLED; + return ret; +} + +/* + * Check and send an uevent if the pin level is high. + * And then enable gpio pin interrupt. + */ +__visible_for_testing int one_gpio_irq_enable(int i, struct sec_det_conn_p_data *pdata, + const char *buf) +{ + int ret = 0; + + SEC_CONN_PRINT("%s driver enabled.\n", buf); + gsec_abc_detect_conn_enabled |= (1 << i); + + SEC_CONN_PRINT("gpio [%d] level %d\n", pdata->irq_gpio[i], + gpio_get_value(pdata->irq_gpio[i])); + + /*get level value of the gpio pin.*/ + /*if there's gpio low pin, send uevent*/ +#if IS_ENABLED(CONFIG_SEC_FACTORY) + check_gpio_and_send_uevent(i, pdata->pinfo); +#endif +#if IS_ENABLED(CONFIG_SEC_ABC_HUB) && IS_ENABLED(CONFIG_SEC_ABC_HUB_COND) + send_ABC_event_by_num(i, pdata->pinfo); +#endif + increase_connector_disconnected_count(i, pdata->pinfo); + + /*Enable interrupt.*/ + ret = detect_conn_irq_enable(pdata->pinfo, true, i); + + if (ret < 0) + SEC_CONN_PRINT("%s Interrupt not enabled.\n", buf); + + return ret; +} + + +/* + * Triggered when "enabled" node is set. + * Check and send an uevent if the pin level is high. + * And then gpio pin interrupt is enabled. + */ +__visible_for_testing ssize_t enabled_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sec_det_conn_p_data *pdata; + struct sec_det_conn_info *pinfo; + int ret; + int i; + int buf_len; + int pin_name_len; + + if (gpinfo == 0) + return count; + + pinfo = gpinfo; + pdata = pinfo->pdata; + + buf_len = strlen(buf); + + SEC_CONN_PRINT("buf = %s, buf_len = %d\n", buf, buf_len); + + /* disable irq when "enabled" value set to 0*/ + if (!strncmp(buf, "0", 1)) { + SEC_CONN_PRINT("sec detect connector driver disable.\n"); + gsec_abc_detect_conn_enabled = 0; + + ret = detect_conn_irq_enable(pinfo, false, 0); + + if (ret) { + SEC_CONN_PRINT("Interrupt not disabled.\n"); + return ret; + } + return count; + } + for (i = 0; i < pdata->gpio_total_cnt; i++) { + pin_name_len = strlen(pdata->name[i]); + SEC_CONN_PRINT("pinName = %s\n", pdata->name[i]); + SEC_CONN_PRINT("pin_name_len = %d\n", pin_name_len); + + if (pin_name_len == buf_len) { + if (!strncmp(buf, pdata->name[i], buf_len)) { + /* buf sting is equal to pdata->name[i] */ + ret = one_gpio_irq_enable(i, pdata, buf); + if (ret < 0) + return ret; + } + } + + /* + * For ALL_CONNECT input, + * enable all nodes except already enabled node. + */ + if (buf_len >= 11) { + if (strncmp(buf, "ALL_CONNECT", 11)) { + /* buf sting is not equal to ALL_CONNECT */ + continue; + } + /* buf sting is equal to pdata->name[i] */ + if (!(gsec_abc_detect_conn_enabled & (1 << i))) { + ret = one_gpio_irq_enable(i, pdata, buf); + if (ret < 0) + return ret; + } + } + } + return count; +} + +__visible_for_testing ssize_t enabled_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, 12, "%d\n", gsec_abc_detect_conn_enabled); +} +static DEVICE_ATTR_RW(enabled); + +__visible_for_testing ssize_t available_pins_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sec_det_conn_p_data *pdata; + struct sec_det_conn_info *pinfo; + char available_pins_string[1024] = {0, }; + int i; + + if (gpinfo == 0) + return 0; + + pinfo = gpinfo; + pdata = pinfo->pdata; + + for (i = 0; i < pdata->gpio_total_cnt; i++) { + SEC_CONN_PRINT("pinName = %s\n", pdata->name[i]); + strlcat(available_pins_string, pdata->name[i], 1024); + strlcat(available_pins_string, "/", 1024); + } + available_pins_string[strlen(available_pins_string) - 1] = '\0'; + + return snprintf(buf, 1024, "%s\n", available_pins_string); +} +static DEVICE_ATTR_RO(available_pins); + +/** + * Fill out the gpio cnt + */ +static void fill_gpio_cnt(struct sec_det_conn_p_data *pdata, struct device_node *np, char *gpio_name) +{ + int gpio_name_cnt; + + gpio_name_cnt = of_gpio_named_count(np, gpio_name); + if (gpio_name_cnt < 1) { + SEC_CONN_PRINT("fill gpio cnt failed %s < 1 %d.\n", + gpio_name, + gpio_name_cnt); + gpio_name_cnt = 0; + } + pdata->gpio_last_cnt = pdata->gpio_total_cnt; + pdata->gpio_total_cnt = pdata->gpio_total_cnt + gpio_name_cnt; + SEC_CONN_PRINT("fill out %s gpio cnt, gpio_total_count = %d", gpio_name, pdata->gpio_total_cnt); +} + +/** + * Fill out the gpio name + */ +static void fill_gpio_name(struct sec_det_conn_p_data *pdata, + struct device_node *np, + char *conn_name, + int offset, + int index) +{ + /*Get connector name*/ + of_property_read_string_index(np, conn_name, offset, &pdata->name[index]); +} + +/** + * Fill out the irq info + */ +static int fill_irq_info(struct sec_det_conn_p_data *pdata, + struct device_node *np, + char *gpio_name, + int offset, + int index) +{ + /* get connector gpio number*/ + pdata->irq_gpio[index] = of_get_named_gpio(np, gpio_name, offset); + + if (!gpio_is_valid(pdata->irq_gpio[index])) { + SEC_CONN_PRINT("[%s] gpio[%d] is not valid\n", gpio_name, offset); + return 0; + } + pdata->irq_number[index] = gpio_to_irq(pdata->irq_gpio[index]); + pdata->irq_type[index] = IRQ_TYPE_EDGE_BOTH; + SEC_CONN_PRINT("i = %d, irq_gpio[i] = %d, irq_num[i] = %d, level = %d\n", index, + pdata->irq_gpio[index], + pdata->irq_number[index], + gpio_get_value(pdata->irq_gpio[index])); + return 1; +} + +/** + * Parse the AP device tree and get gpio number, irq type. + * Request gpio + */ +static void parse_ap_dt(struct sec_det_conn_p_data *pdata, struct device_node *np) +{ + int i; + + fill_gpio_cnt(pdata, np, "sec,det_conn_gpios"); + + for (i = pdata->gpio_last_cnt; i < pdata->gpio_total_cnt; i++) { + fill_gpio_name(pdata, np, "sec,det_conn_name", i - pdata->gpio_last_cnt, i); + if (!fill_irq_info(pdata, np, "sec,det_conn_gpios", i - pdata->gpio_last_cnt, i)) + break; + } +} + +#if IS_ENABLED(CONFIG_QCOM_SEC_ABC_DETECT) +/** + * gpio pinctrl by pinctrl-name + */ +static void set_pinctrl_by_pinctrl_name(struct device *dev, char *pinctrl_name) +{ + struct pinctrl *conn_pinctrl; + + conn_pinctrl = devm_pinctrl_get_select(dev, pinctrl_name); + if (IS_ERR_OR_NULL(conn_pinctrl)) + SEC_CONN_PRINT("%s detect_pinctrl_init failed.\n", pinctrl_name); + else + SEC_CONN_PRINT("%s detect_pinctrl_init passed.\n", pinctrl_name); +} + +/** + * Parse the Expander device tree and get gpio number, irq type. + * Request gpio + */ +static void parse_exp_dt(struct sec_det_conn_p_data *pdata, struct device_node *np) +{ + int i; + + fill_gpio_cnt(pdata, np, "sec,det_exp_conn_gpios"); + + for (i = pdata->gpio_last_cnt; i < pdata->gpio_total_cnt; i++) { + fill_gpio_name(pdata, np, "sec,det_exp_conn_name", i - pdata->gpio_last_cnt, i); + if (!fill_irq_info(pdata, np, "sec,det_exp_conn_gpios", i - pdata->gpio_last_cnt, i)) + break; + } +} + +/** + * Parse the PM device tree and get gpio number, irq type. + * Request gpio + */ +static void parse_pmic_dt(struct sec_det_conn_p_data *pdata, struct device_node *np) +{ + int i; + + fill_gpio_cnt(pdata, np, "sec,det_pm_conn_gpios"); + + for (i = pdata->gpio_last_cnt; i < pdata->gpio_total_cnt; i++) { + fill_gpio_name(pdata, np, "sec,det_pm_conn_name", i - pdata->gpio_last_cnt, i); + if (!fill_irq_info(pdata, np, "sec,det_pm_conn_gpios", i - pdata->gpio_last_cnt, i)) + break; + } +} +#endif + +#if IS_ENABLED(CONFIG_OF) +/** + * Parse the device tree and get gpio number, irq type. + * Request gpio + */ +static int parse_detect_conn_dt(struct device *dev) +{ + struct sec_det_conn_p_data *pdata = dev->platform_data; + struct device_node *np = dev->of_node; +#if IS_ENABLED(CONFIG_QCOM_SEC_ABC_DETECT) + set_pinctrl_by_pinctrl_name(dev, "det_ap_connect"); +#endif + parse_ap_dt(pdata, np); +#if IS_ENABLED(CONFIG_QCOM_SEC_ABC_DETECT) + set_pinctrl_by_pinctrl_name(dev, "det_exp_connect"); + parse_exp_dt(pdata, np); + set_pinctrl_by_pinctrl_name(dev, "det_pm_connect"); + set_pinctrl_by_pinctrl_name(dev, "det_pm_2_connect"); + parse_pmic_dt(pdata, np); +#endif + return 0; +} +#endif //CONFIG_OF + +static int sec_abc_detect_conn_probe(struct platform_device *pdev) +{ + struct sec_det_conn_p_data *pdata; + struct sec_det_conn_info *pinfo; + int ret = 0; + + SEC_CONN_PRINT("%s\n", __func__); + + /* First Get the GPIO pins; if it fails, we'll defer the probe. */ + if (pdev->dev.of_node) { + pdata = devm_kzalloc(&pdev->dev, + sizeof(struct sec_det_conn_p_data), + GFP_KERNEL); + + if (!pdata) { + dev_err(&pdev->dev, "Failed to allocate pdata.\n"); + return -ENOMEM; + } + + pdev->dev.platform_data = pdata; + +#if IS_ENABLED(CONFIG_OF) + ret = parse_detect_conn_dt(&pdev->dev); +#endif + if (ret) { + dev_err(&pdev->dev, "Failed to parse dt data.\n"); + return ret; + } + + pr_info("%s: parse dt done.\n", __func__); + } else { + pdata = pdev->dev.platform_data; + } + + if (!pdata) { + dev_err(&pdev->dev, "There are no platform data.\n"); + return -EINVAL; + } + + pinfo = devm_kzalloc(&pdev->dev, sizeof(struct sec_det_conn_info), + GFP_KERNEL); + + if (!pinfo) { + SEC_CONN_PRINT("pinfo : failed to allocate pinfo.\n"); + return -ENOMEM; + } + +#if defined(CONFIG_SEC_KUNIT) + sec_abc_detect_conn_dev = pinfo->dev; +#endif + /* Create sys device /sys/class/sec/sec_detect_conn */ + pinfo->dev = sec_device_create(pinfo, "sec_detect_conn"); + + if (IS_ERR(pinfo->dev)) { + pr_err("%s Failed to create device(sec_abc_detect_conn).\n", + __func__); + ret = -ENODEV; + goto out; + } + + /* Create sys node /sys/class/sec/sec_detect_conn/enabled */ + ret = device_create_file(pinfo->dev, &dev_attr_enabled); + + if (ret) { + dev_err(&pdev->dev, "%s: Failed to create device file.\n", + __func__); + goto err_create_detect_conn_sysfs; + } + + /* Create sys node /sys/class/sec/sec_detect_conn/available_pins */ + ret = device_create_file(pinfo->dev, &dev_attr_available_pins); + + if (ret) { + dev_err(&pdev->dev, "%s: Failed to create device file.\n", + __func__); + goto err_create_detect_conn_sysfs; + } + + create_current_connection_state_sysnode_files(pinfo); + create_connector_disconnected_count_sysnode_file(pinfo); + + /*save pinfo data to pdata to interrupt enable*/ + pdata->pinfo = pinfo; + + /*save pdata data to pinfo for enable node*/ + pinfo->pdata = pdata; + + /* save pinfo to gpinfo to enabled node*/ + gpinfo = pinfo; + +#if !IS_ENABLED(CONFIG_SEC_FACTORY) + /* enable gpio irq for Big data */ + enabled_store(pinfo->dev, (struct device_attribute *)0, "ALL_CONNECT", 0); +#endif + + return ret; + +err_create_detect_conn_sysfs: + sec_device_destroy(pinfo->dev->devt); +out: + gpinfo = 0; + kfree(pinfo); + kfree(pdata); + return ret; +} + +static int sec_abc_detect_conn_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver sec_abc_detect_conn_driver = { + .probe = sec_abc_detect_conn_probe, + .remove = sec_abc_detect_conn_remove, + .driver = { + .name = "sec_abc_detect_conn", + .owner = THIS_MODULE, +#if IS_ENABLED(CONFIG_PM) + .pm = &sec_abc_detect_conn_pm, +#endif +#if IS_ENABLED(CONFIG_OF) + .of_match_table = of_match_ptr(sec_abc_detect_conn_dt_match), +#endif + }, +}; + +static int __init sec_abc_detect_conn_init(void) +{ + SEC_CONN_PRINT("%s gogo\n", __func__); + return platform_driver_register(&sec_abc_detect_conn_driver); +} + +static void __exit sec_abc_detect_conn_exit(void) +{ + return platform_driver_unregister(&sec_abc_detect_conn_driver); +} +#if IS_ENABLED(CONFIG_SEC_ABC_HUB) && IS_ENABLED(CONFIG_SEC_ABC_HUB_COND) +/** + * This function is not used for ABC driver due to the Big Data always on Concept + */ +void abc_hub_cond_enable(struct device *dev, int enable) +{ +} +EXPORT_SYMBOL(abc_hub_cond_enable); +#endif +module_init(sec_abc_detect_conn_init); +module_exit(sec_abc_detect_conn_exit); + +MODULE_DESCRIPTION("Samsung Detecting Connector Driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/sti/abc/connector_state.c b/drivers/sti/abc/connector_state.c new file mode 100644 index 000000000000..26ed16d87cf5 --- /dev/null +++ b/drivers/sti/abc/connector_state.c @@ -0,0 +1,110 @@ +/* + * connection_state.c + * + * Copyright (C) 2022 Samsung Electronics + * + * 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. + * + */ + +#include + +#if defined(CONFIG_SEC_KUNIT) +#define __visible_for_testing +#else +#define __visible_for_testing static +#endif + +/* + * Get gpio value. + */ +int get_gpio_value(int gpio) +{ +#if defined(CONFIG_SEC_KUNIT) + return 1; +#else + return gpio_get_value(gpio); +#endif +} + +/* + * Return connector total count. + */ +__visible_for_testing ssize_t connector_count_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sec_det_conn_p_data *pdata; + + if (gpinfo == 0) + return 0; + pdata = gpinfo->pdata; + + SEC_CONN_PRINT("read connector_count = %d\n", pdata->gpio_total_cnt); + return snprintf(buf, 12, "%d\n", pdata->gpio_total_cnt); +} +static DEVICE_ATTR_RO(connector_count); + +/* + * Check all connector gpio state and return the "name":"value" pair. + * value 1 means disconnected, and 0 means connected. + * "SUB_CONNECTOR":"1","UPPER_C2C_DET":"0" + */ +__visible_for_testing ssize_t connector_state_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sec_det_conn_p_data *pdata; + char connector_state[1024] = {0, }; + char gpio_value_str[12] = {0, }; + int i; + + if (gpinfo == 0) + return 0; + pdata = gpinfo->pdata; + + for (i = 0; i < pdata->gpio_total_cnt; i++) { + if (i > 0) + strlcat(connector_state, ",", 1024); + strlcat(connector_state, "\"", 1024); + strlcat(connector_state, pdata->name[i], 1024); + strlcat(connector_state, "\":\"", 1024); + SEC_CONN_PRINT("get value : %s[%d]\n", pdata->name[i], pdata->irq_gpio[i]); + sprintf(gpio_value_str, "%d", get_gpio_value(pdata->irq_gpio[i])); + strlcat(connector_state, gpio_value_str, 1024); + strlcat(connector_state, "\"", 1024); + } + connector_state[strlen(connector_state)] = '\0'; + SEC_CONN_PRINT("connector_state : %s\n", connector_state); + + return snprintf(buf, 1024, "%s\n", connector_state); +} +static DEVICE_ATTR_RO(connector_state); + +/* + * Create sys node for connector connection state and connector count. + */ +void create_current_connection_state_sysnode_files(struct sec_det_conn_info *pinfo) +{ + int ret = 0; + + /* Create sys node /sys/class/sec/sec_detect_conn/connector_count */ + ret = device_create_file(pinfo->dev, &dev_attr_connector_count); + + if (ret) + pr_err("%s: Failed to create connector_count.\n", __func__); + + /* Create sys node /sys/class/sec/sec_detect_conn/connector_state */ + ret = device_create_file(pinfo->dev, &dev_attr_connector_state); + + if (ret) + pr_err("%s: Failed to create connector_state.\n", __func__); +} diff --git a/drivers/sti/common/Kconfig b/drivers/sti/common/Kconfig new file mode 100644 index 000000000000..0132b553ff82 --- /dev/null +++ b/drivers/sti/common/Kconfig @@ -0,0 +1,9 @@ +comment "Samsung STI Options" +config SEC_STI + tristate "Samsung STI(System Test Infra)" + default n + help + Config to support Samsung STI(System Test Infra) solutions. + There are many sub configs to support + ABC driver, ABC hub driver, LABO driver and so on. + All sub configs depends on config SEC_STI. diff --git a/drivers/thermal/qcom/bcl_pmic5.c b/drivers/thermal/qcom/bcl_pmic5.c new file mode 100644 index 000000000000..8356f47b2b68 --- /dev/null +++ b/drivers/thermal/qcom/bcl_pmic5.c @@ -0,0 +1,1004 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2021-2023, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "thermal_zone_internal.h" + +#define BCL_DRIVER_NAME "bcl_pmic5" +#define BCL_MONITOR_EN 0x46 +#define BCL_IRQ_STATUS 0x08 +#define BCL_REVISION1 0x0 +#define BCL_REVISION2 0x01 +#define BCL_PARAM_1 0x0e +#define BCL_PARAM_2 0x0f + +#define BCL_IBAT_HIGH 0x4B +#define BCL_IBAT_TOO_HIGH 0x4C +#define BCL_IBAT_TOO_HIGH_REV4 0x4D +#define BCL_IBAT_READ 0x86 +#define BCL_IBAT_SCALING_UA 78127 +#define BCL_IBAT_CCM_SCALING_UA 15625 +#define BCL_IBAT_SCALING_REV4_UA 93753 + +#define BCL_VBAT_READ 0x76 +#define BCL_VBAT_ADC_LOW 0x48 +#define BCL_VBAT_COMP_LOW 0x49 +#define BCL_VBAT_COMP_TLOW 0x4A +#define BCL_VBAT_CONV_REQ 0x72 + +#define BCL_GEN3_MAJOR_REV 4 +#define BCL_PARAM_HAS_ADC BIT(0) +#define BCL_PARAM_HAS_IBAT_ADC BIT(2) + +#define BCL_IRQ_L0 0x1 +#define BCL_IRQ_L1 0x2 +#define BCL_IRQ_L2 0x4 + +/* + * 49827 = 64.879uV (one bit value) * 3 (voltage divider) + * * 256 (8 bit shift for MSB) + */ +#define BCL_VBAT_SCALING_UV 49827 +#define BCL_VBAT_NO_READING 127 +#define BCL_VBAT_BASE_MV 2000 +#define BCL_VBAT_INC_MV 25 +#define BCL_VBAT_MAX_MV 3600 +#define BCL_VBAT_THRESH_BASE 0x8CA + +#define BCL_IBAT_CCM_OFFSET 800 +#define BCL_IBAT_CCM_LSB 100 +#define BCL_IBAT_CCM_MAX_VAL 14 + +#define MAX_PERPH_COUNT 2 +#define IPC_LOGPAGES 2 + +#define BCL_IPC(dev, msg, args...) do { \ + if ((dev) && (dev)->ipc_log) { \ + ipc_log_string((dev)->ipc_log, \ + "[%s]: %s: " msg, \ + current->comm, __func__, args); \ + } \ + } while (0) + +enum bcl_dev_type { + BCL_IBAT_LVL0, + BCL_IBAT_LVL1, + BCL_VBAT_LVL0, + BCL_VBAT_LVL1, + BCL_VBAT_LVL2, + BCL_LVL0, + BCL_LVL1, + BCL_LVL2, + BCL_2S_IBAT_LVL0, + BCL_2S_IBAT_LVL1, + BCL_TYPE_MAX, +}; + +static char bcl_int_names[BCL_TYPE_MAX][25] = { + "bcl-ibat-lvl0", + "bcl-ibat-lvl1", + "bcl-vbat-lvl0", + "bcl-vbat-lvl1", + "bcl-vbat-lvl2", + "bcl-lvl0", + "bcl-lvl1", + "bcl-lvl2", + "bcl-2s-ibat-lvl0", + "bcl-2s-ibat-lvl1", +}; + +enum bcl_ibat_ext_range_type { + BCL_IBAT_RANGE_LVL0, + BCL_IBAT_RANGE_LVL1, + BCL_IBAT_RANGE_LVL2, + BCL_IBAT_RANGE_MAX, +}; + +static uint32_t bcl_ibat_ext_ranges[BCL_IBAT_RANGE_MAX] = { + 10, /* default range factor */ + 20, + 25 +}; + +struct bcl_device; + +struct bcl_peripheral_data { + int irq_num; + int status_bit_idx; + long trip_thresh; + int last_val; + struct mutex state_trans_lock; + bool irq_enabled; + enum bcl_dev_type type; + struct thermal_zone_device_ops ops; + struct thermal_zone_device *tz_dev; + struct bcl_device *dev; +}; + +struct bcl_device { + struct device *dev; + struct regmap *regmap; + uint16_t fg_bcl_addr; + uint8_t dig_major; + uint8_t dig_minor; + uint8_t bcl_param_1; + uint8_t bcl_type; + void *ipc_log; + bool ibat_ccm_enabled; + bool ibat_use_qg_adc; + bool no_bit_shift; + uint32_t ibat_ext_range_factor; + struct bcl_peripheral_data param[BCL_TYPE_MAX]; +}; + +static struct bcl_device *bcl_devices[MAX_PERPH_COUNT]; +static int bcl_device_ct; + +static int bcl_read_register(struct bcl_device *bcl_perph, int16_t reg_offset, + unsigned int *data) +{ + int ret = 0; + + if (!bcl_perph) { + pr_err("BCL device not initialized\n"); + return -EINVAL; + } + ret = regmap_read(bcl_perph->regmap, + (bcl_perph->fg_bcl_addr + reg_offset), + data); + if (ret < 0) + pr_err("Error reading register 0x%04x err:%d\n", + bcl_perph->fg_bcl_addr + reg_offset, ret); + else + pr_debug("Read register:0x%04x value:0x%02x\n", + bcl_perph->fg_bcl_addr + reg_offset, + *data); + + return ret; +} + +static int bcl_write_register(struct bcl_device *bcl_perph, + int16_t reg_offset, uint8_t data) +{ + int ret = 0; + uint8_t *write_buf = &data; + uint16_t base; + + if (!bcl_perph) { + pr_err("BCL device not initialized\n"); + return -EINVAL; + } + base = bcl_perph->fg_bcl_addr; + ret = regmap_write(bcl_perph->regmap, (base + reg_offset), *write_buf); + if (ret < 0) { + pr_err("Error reading register:0x%04x val:0x%02x err:%d\n", + base + reg_offset, data, ret); + return ret; + } + pr_debug("wrote 0x%02x to 0x%04x\n", data, base + reg_offset); + + return ret; +} + +static void convert_adc_to_vbat_thresh_val(struct bcl_device *bcl_perph, int *val) +{ + /* + * Threshold register can be bit shifted from ADC MSB. + * So the scaling factor is half in those cases. + */ + if (bcl_perph->no_bit_shift) + *val = (*val * BCL_VBAT_SCALING_UV) / 1000; + else + *val = (*val * BCL_VBAT_SCALING_UV) / 2000; +} + +static void convert_adc_to_vbat_val(int *val) +{ + *val = (*val * BCL_VBAT_SCALING_UV) / 1000; +} + +static void convert_ibat_to_adc_val(struct bcl_device *bcl_perph, int *val, int scaling_factor) +{ + /* + * Threshold register can be bit shifted from ADC MSB. + * So the scaling factor is half in those cases. + */ + if (bcl_perph->ibat_use_qg_adc) + *val = (int)div_s64(*val * 2000 * 2, scaling_factor); + else if (bcl_perph->no_bit_shift) + *val = (int)div_s64(*val * 1000 * bcl_ibat_ext_ranges[BCL_IBAT_RANGE_LVL0], + scaling_factor); + else + *val = (int)div_s64(*val * 2000 * bcl_ibat_ext_ranges[BCL_IBAT_RANGE_LVL0], + scaling_factor); + +} + +static void convert_adc_to_ibat_val(struct bcl_device *bcl_perph, int *val, int scaling_factor) +{ + /* Scaling factor will be half if ibat_use_qg_adc is true */ + if (bcl_perph->ibat_use_qg_adc) + *val = (int)div_s64(*val * scaling_factor, 2 * 1000); + else + *val = (int)div_s64(*val * scaling_factor, + 1000 * bcl_ibat_ext_ranges[BCL_IBAT_RANGE_LVL0]); +} + +static int8_t convert_ibat_to_ccm_val(int ibat) +{ + int8_t val = BCL_IBAT_CCM_MAX_VAL; + + val = (int8_t)((ibat - BCL_IBAT_CCM_OFFSET) / BCL_IBAT_CCM_LSB); + + if (val > BCL_IBAT_CCM_MAX_VAL) { + pr_err( + "CCM thresh:%d is invalid, use MAX supported threshold\n", + ibat); + val = BCL_IBAT_CCM_MAX_VAL; + } + + return val; +} + +static int bcl_set_ibat(struct thermal_zone_device *tz, int low, int high) +{ + int ret = 0, ibat_ua, thresh_value; + int8_t val = 0; + int16_t addr; + struct bcl_peripheral_data *bat_data = + (struct bcl_peripheral_data *)tz->devdata; + + mutex_lock(&bat_data->state_trans_lock); + thresh_value = high; + if (bat_data->trip_thresh == thresh_value) + goto set_trip_exit; + + if (bat_data->irq_num && bat_data->irq_enabled) { + disable_irq_nosync(bat_data->irq_num); + bat_data->irq_enabled = false; + } + if (thresh_value == INT_MAX) { + bat_data->trip_thresh = thresh_value; + goto set_trip_exit; + } + + ibat_ua = thresh_value; + if (bat_data->dev->ibat_ccm_enabled) + convert_ibat_to_adc_val(bat_data->dev, &thresh_value, + BCL_IBAT_CCM_SCALING_UA * + bat_data->dev->ibat_ext_range_factor); + else if (bat_data->dev->dig_major >= BCL_GEN3_MAJOR_REV) + convert_ibat_to_adc_val(bat_data->dev, &thresh_value, + BCL_IBAT_SCALING_REV4_UA * + bat_data->dev->ibat_ext_range_factor); + else + convert_ibat_to_adc_val(bat_data->dev, &thresh_value, + BCL_IBAT_SCALING_UA * + bat_data->dev->ibat_ext_range_factor); + val = (int8_t)thresh_value; + switch (bat_data->type) { + case BCL_IBAT_LVL0: + case BCL_2S_IBAT_LVL0: + addr = BCL_IBAT_HIGH; + pr_debug("ibat high threshold:%d mA ADC:0x%02x\n", + ibat_ua, val); + break; + case BCL_IBAT_LVL1: + case BCL_2S_IBAT_LVL1: + addr = BCL_IBAT_TOO_HIGH; + if (bat_data->dev->dig_major >= BCL_GEN3_MAJOR_REV && + bat_data->dev->bcl_param_1 & BCL_PARAM_HAS_IBAT_ADC) + addr = BCL_IBAT_TOO_HIGH_REV4; + if (bat_data->dev->ibat_ccm_enabled) + val = convert_ibat_to_ccm_val(ibat_ua); + pr_debug("ibat too high threshold:%d mA ADC:0x%02x\n", + ibat_ua, val); + break; + default: + goto set_trip_exit; + } + ret = bcl_write_register(bat_data->dev, addr, val); + if (ret) + goto set_trip_exit; + bat_data->trip_thresh = ibat_ua; + + if (bat_data->irq_num && !bat_data->irq_enabled) { + enable_irq(bat_data->irq_num); + bat_data->irq_enabled = true; + } + +set_trip_exit: + mutex_unlock(&bat_data->state_trans_lock); + + return ret; +} + +static int bcl_read_ibat(struct thermal_zone_device *tz, int *adc_value) +{ + int ret = 0; + unsigned int val = 0; + struct bcl_peripheral_data *bat_data = + (struct bcl_peripheral_data *)tz->devdata; + + *adc_value = val; + ret = bcl_read_register(bat_data->dev, BCL_IBAT_READ, &val); + if (ret) + goto bcl_read_exit; + /* IBat ADC reading is in 2's compliment form */ + *adc_value = sign_extend32(val, 7); + if (val == 0) { + /* + * The sensor sometime can read a value 0 if there is + * consequtive reads + */ + *adc_value = bat_data->last_val; + } else { + if (bat_data->dev->ibat_ccm_enabled) + convert_adc_to_ibat_val(bat_data->dev, adc_value, + BCL_IBAT_CCM_SCALING_UA * bat_data->dev->ibat_ext_range_factor); + else if (bat_data->dev->dig_major >= BCL_GEN3_MAJOR_REV) + convert_adc_to_ibat_val(bat_data->dev, adc_value, + BCL_IBAT_SCALING_REV4_UA * + bat_data->dev->ibat_ext_range_factor); + else + convert_adc_to_ibat_val(bat_data->dev, adc_value, + BCL_IBAT_SCALING_UA * bat_data->dev->ibat_ext_range_factor); + bat_data->last_val = *adc_value; + } + pr_debug("ibat:%d mA ADC:0x%02x\n", bat_data->last_val, val); + BCL_IPC(bat_data->dev, "ibat:%d mA ADC:0x%02x\n", + bat_data->last_val, val); + +bcl_read_exit: + return ret; +} + +static int bcl_get_vbat_trip(struct thermal_zone_device *tzd, + int type, int *trip) +{ + int ret = 0; + unsigned int val = 0; + struct bcl_peripheral_data *bat_data = + (struct bcl_peripheral_data *)tzd->devdata; + int16_t addr; + + *trip = 0; + switch (type + BCL_VBAT_LVL0) { + case BCL_VBAT_LVL0: + addr = BCL_VBAT_ADC_LOW; + break; + case BCL_VBAT_LVL1: + addr = BCL_VBAT_COMP_LOW; + break; + case BCL_VBAT_LVL2: + addr = BCL_VBAT_COMP_TLOW; + break; + default: + return -ENODEV; + } + + ret = bcl_read_register(bat_data->dev, addr, &val); + if (ret) + return ret; + + if (addr == BCL_VBAT_ADC_LOW) { + *trip = val; + convert_adc_to_vbat_thresh_val(bat_data->dev, trip); + pr_debug("vbat trip: %d mV ADC:0x%02x\n", *trip, val); + } else { + *trip = BCL_VBAT_THRESH_BASE + val * 25; + if (*trip > BCL_VBAT_MAX_MV) + *trip = BCL_VBAT_MAX_MV; + pr_debug("vbat-%s-low trip: %d mV ADC:0x%02x\n", + (addr == BCL_VBAT_COMP_LOW) ? + "too" : "critical", + *trip, val); + } + + return 0; +} + +static int bcl_read_vbat_tz(struct thermal_zone_device *tzd, int *adc_value) +{ + int ret = 0; + unsigned int val = 0; + struct bcl_peripheral_data *bat_data = + (struct bcl_peripheral_data *)tzd->devdata; + + *adc_value = val; + ret = bcl_read_register(bat_data->dev, BCL_VBAT_READ, &val); + if (ret) + goto bcl_read_exit; + *adc_value = val; + if (*adc_value == BCL_VBAT_NO_READING) { + *adc_value = bat_data->last_val; + } else { + convert_adc_to_vbat_val(adc_value); + bat_data->last_val = *adc_value; + } + pr_debug("vbat:%d mv\n", bat_data->last_val); + BCL_IPC(bat_data->dev, "vbat:%d mv ADC:0x%02x\n", + bat_data->last_val, val); + +bcl_read_exit: + return ret; +} + +static int bcl_read_vbat_type(struct thermal_zone_device *tzd, int trip, + enum thermal_trip_type *type) +{ + *type = THERMAL_TRIP_PASSIVE; + return 0; +} + +static struct thermal_zone_device_ops vbat_tzd_ops = { + .get_temp = bcl_read_vbat_tz, + .get_trip_temp = bcl_get_vbat_trip, + .get_trip_type = bcl_read_vbat_type, +}; + +static struct thermal_zone_params vbat_tzp = { + .governor_name = "step_wise", + .no_hwmon = true, + .num_tbps = 0, + .tbp = NULL, + .sustainable_power = 0, + .k_po = 0, + .k_pu = 0, + .k_i = 0, + .k_d = 0, + .integral_cutoff = 0, + .slope = 1, + .offset = 0 +}; + +static int bcl_get_trend(struct thermal_zone_device *tz, int trip, enum thermal_trend *trend) +{ + struct bcl_peripheral_data *bat_data = + (struct bcl_peripheral_data *)tz->devdata; + + mutex_lock(&bat_data->state_trans_lock); + if (!bat_data->last_val) + *trend = THERMAL_TREND_DROPPING; + else + *trend = THERMAL_TREND_RAISING; + mutex_unlock(&bat_data->state_trans_lock); + + return 0; +} + +static int bcl_set_lbat(struct thermal_zone_device *tz, int low, int high) +{ + struct bcl_peripheral_data *bat_data = + (struct bcl_peripheral_data *)tz->devdata; + + mutex_lock(&bat_data->state_trans_lock); + + if (high == INT_MAX && + bat_data->irq_num && bat_data->irq_enabled) { + disable_irq_nosync(bat_data->irq_num); + disable_irq_wake(bat_data->irq_num); + bat_data->irq_enabled = false; + pr_debug("lbat[%d]: disable irq:%d\n", + bat_data->type, + bat_data->irq_num); + } else if (high != INT_MAX && + bat_data->irq_num && !bat_data->irq_enabled) { + enable_irq(bat_data->irq_num); + enable_irq_wake(bat_data->irq_num); + bat_data->irq_enabled = true; + pr_debug("lbat[%d]: enable irq:%d\n", + bat_data->type, + bat_data->irq_num); + } + + mutex_unlock(&bat_data->state_trans_lock); + + return 0; +} + +static int bcl_read_lbat(struct thermal_zone_device *tz, int *adc_value) +{ + int ret = 0; + int ibat = 0, vbat = 0; + unsigned int val = 0; + struct bcl_peripheral_data *bat_data = + (struct bcl_peripheral_data *)tz->devdata; + struct bcl_device *bcl_perph = bat_data->dev; + + *adc_value = val; + ret = bcl_read_register(bcl_perph, BCL_IRQ_STATUS, &val); + if (ret) + goto bcl_read_exit; + switch (bat_data->type) { + case BCL_LVL0: + *adc_value = val & BCL_IRQ_L0; + break; + case BCL_LVL1: + *adc_value = val & BCL_IRQ_L1; + break; + case BCL_LVL2: + *adc_value = val & BCL_IRQ_L2; + break; + default: + pr_err("Invalid sensor type:%d\n", bat_data->type); + ret = -ENODEV; + goto bcl_read_exit; + } + bat_data->last_val = *adc_value; + pr_debug("lbat:%d val:%d\n", bat_data->type, + bat_data->last_val); + if (bcl_perph->param[BCL_IBAT_LVL0].tz_dev) + bcl_read_ibat(bcl_perph->param[BCL_IBAT_LVL0].tz_dev, &ibat); + else if (bcl_perph->param[BCL_2S_IBAT_LVL0].tz_dev) + bcl_read_ibat(bcl_perph->param[BCL_2S_IBAT_LVL0].tz_dev, &ibat); + if (bcl_perph->param[BCL_VBAT_LVL0].tz_dev) + bcl_read_vbat_tz(bcl_perph->param[BCL_VBAT_LVL0].tz_dev, &vbat); + BCL_IPC(bcl_perph, "LVLbat:%d val:%d\n", bat_data->type, + bat_data->last_val); + +bcl_read_exit: + return ret; +} + +static int panic_lvl = BCL_TYPE_MAX; +module_param(panic_lvl, int, 0644); + +static irqreturn_t bcl_handle_irq(int irq, void *data) +{ + struct bcl_peripheral_data *perph_data = + (struct bcl_peripheral_data *)data; + unsigned int irq_status = 0; + int ibat = 0, vbat = 0; + struct bcl_device *bcl_perph; + + if (!perph_data->tz_dev) + return IRQ_HANDLED; + bcl_perph = perph_data->dev; + bcl_read_register(bcl_perph, BCL_IRQ_STATUS, &irq_status); + if (bcl_perph->param[BCL_IBAT_LVL0].tz_dev) + bcl_read_ibat(bcl_perph->param[BCL_IBAT_LVL0].tz_dev, &ibat); + else if (bcl_perph->param[BCL_2S_IBAT_LVL0].tz_dev) + bcl_read_ibat(bcl_perph->param[BCL_2S_IBAT_LVL0].tz_dev, &ibat); + if (bcl_perph->param[BCL_VBAT_LVL0].tz_dev) + bcl_read_vbat_tz(bcl_perph->param[BCL_VBAT_LVL0].tz_dev, &vbat); + + if (irq_status & perph_data->status_bit_idx) { + pr_info( + "Irq:%d triggered for bcl type:%s. status:%u ibat=%d vbat=%d\n", + irq, bcl_int_names[perph_data->type], + irq_status, ibat, vbat); + + /* trigger panic on given panic level */ + if (perph_data->type >= BCL_LVL0 && perph_data->type <= BCL_LVL2 + && perph_data->type == BCL_LVL0 + panic_lvl) { + panic("bcl type:%s forced panic", bcl_int_names[perph_data->type]); + } + + BCL_IPC(bcl_perph, + "Irq:%d triggered for bcl type:%s. status:%u ibat=%d vbat=%d\n", + irq, bcl_int_names[perph_data->type], + irq_status, ibat, vbat); + thermal_zone_device_update(perph_data->tz_dev, + THERMAL_TRIP_VIOLATED); + } + + return IRQ_HANDLED; +} + +static int bcl_get_ibat_ext_range_factor(struct platform_device *pdev, + uint32_t *ibat_range_factor) +{ + int ret = 0; + const char *name; + struct nvmem_cell *cell; + size_t len; + char *buf; + uint32_t ext_range_index = 0; + + ret = of_property_read_string(pdev->dev.of_node, "nvmem-cell-names", &name); + if (ret) { + *ibat_range_factor = bcl_ibat_ext_ranges[BCL_IBAT_RANGE_LVL0]; + pr_debug("Default ibat range factor enabled %u\n", *ibat_range_factor); + return 0; + } + + cell = nvmem_cell_get(&pdev->dev, name); + if (IS_ERR(cell)) { + dev_err(&pdev->dev, "failed to get nvmem cell %s\n", name); + return PTR_ERR(cell); + } + + buf = nvmem_cell_read(cell, &len); + nvmem_cell_put(cell); + if (IS_ERR_OR_NULL(buf)) { + dev_err(&pdev->dev, "failed to read nvmem cell %s\n", name); + return PTR_ERR(buf); + } + + if (len <= 0 || len > sizeof(uint32_t)) { + dev_err(&pdev->dev, "nvmem cell length out of range %d\n", len); + kfree(buf); + return -EINVAL; + } + memcpy(&ext_range_index, buf, min(len, sizeof(ext_range_index))); + kfree(buf); + + if (ext_range_index >= BCL_IBAT_RANGE_MAX) { + dev_err(&pdev->dev, "invalid BCL ibat scaling factor %d\n", ext_range_index); + return -EINVAL; + } + + *ibat_range_factor = bcl_ibat_ext_ranges[ext_range_index]; + pr_debug("ext_range_index %u, ibat range factor %u\n", + ext_range_index, *ibat_range_factor); + + return 0; +} + +static int bcl_get_devicetree_data(struct platform_device *pdev, + struct bcl_device *bcl_perph) +{ + int ret = 0; + const __be32 *prop = NULL; + struct device_node *dev_node = pdev->dev.of_node; + + prop = of_get_address(dev_node, 0, NULL, NULL); + if (prop) { + bcl_perph->fg_bcl_addr = be32_to_cpu(*prop); + pr_debug("fg_bcl@%04x\n", bcl_perph->fg_bcl_addr); + } else { + dev_err(&pdev->dev, "No fg_bcl registers found\n"); + return -ENODEV; + } + + bcl_perph->ibat_use_qg_adc = of_property_read_bool(dev_node, + "qcom,ibat-use-qg-adc-5a"); + bcl_perph->no_bit_shift = of_property_read_bool(dev_node, + "qcom,pmic7-threshold"); + bcl_perph->ibat_ccm_enabled = of_property_read_bool(dev_node, + "qcom,ibat-ccm-hw-support"); + + ret = bcl_get_ibat_ext_range_factor(pdev, + &bcl_perph->ibat_ext_range_factor); + + return ret; +} + +static void bcl_fetch_trip(struct platform_device *pdev, enum bcl_dev_type type, + struct bcl_peripheral_data *data, + irqreturn_t (*handle)(int, void *)) +{ + int ret = 0, irq_num = 0; + char *int_name = bcl_int_names[type]; + + mutex_lock(&data->state_trans_lock); + data->irq_num = 0; + data->irq_enabled = false; + irq_num = platform_get_irq_byname(pdev, int_name); + if (irq_num > 0 && handle) { + ret = devm_request_threaded_irq(&pdev->dev, + irq_num, NULL, handle, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + int_name, data); + if (ret) { + dev_err(&pdev->dev, + "Error requesting trip irq. err:%d\n", + ret); + mutex_unlock(&data->state_trans_lock); + return; + } + disable_irq_nosync(irq_num); + data->irq_num = irq_num; + } else if (irq_num > 0 && !handle) { + disable_irq_nosync(irq_num); + data->irq_num = irq_num; + } + mutex_unlock(&data->state_trans_lock); +} + +static void bcl_vbat_init(struct platform_device *pdev, + enum bcl_dev_type type, struct bcl_device *bcl_perph) +{ + struct bcl_peripheral_data *vbat = &bcl_perph->param[type]; + unsigned int val = 0; + int ret; + + mutex_init(&vbat->state_trans_lock); + vbat->dev = bcl_perph; + vbat->irq_num = 0; + vbat->irq_enabled = false; + vbat->tz_dev = NULL; + + /* If revision 4 or above && bcl support adc, then only enable vbat */ + if (bcl_perph->dig_major >= BCL_GEN3_MAJOR_REV) { + if (!(bcl_perph->bcl_param_1 & BCL_PARAM_HAS_ADC)) + return; + } else { + ret = bcl_read_register(bcl_perph, BCL_VBAT_CONV_REQ, &val); + if (ret || !val) + return; + } + vbat->tz_dev = thermal_zone_device_register("vbat", 3, 0, vbat, + &vbat_tzd_ops, &vbat_tzp, 0, 0); + if (IS_ERR(vbat->tz_dev)) { + pr_debug("vbat[%s] register failed. err:%ld\n", + bcl_int_names[type], + PTR_ERR(vbat->tz_dev)); + vbat->tz_dev = NULL; + return; + } + + ret = thermal_zone_device_enable(vbat->tz_dev); + if (ret) { + thermal_zone_device_unregister(vbat->tz_dev); + vbat->tz_dev = NULL; + } +} + +static void bcl_probe_vbat(struct platform_device *pdev, + struct bcl_device *bcl_perph) +{ + bcl_vbat_init(pdev, BCL_VBAT_LVL0, bcl_perph); +} + +static void bcl_ibat_init(struct platform_device *pdev, + enum bcl_dev_type type, struct bcl_device *bcl_perph) +{ + struct bcl_peripheral_data *ibat = &bcl_perph->param[type]; + + mutex_init(&ibat->state_trans_lock); + ibat->type = type; + ibat->dev = bcl_perph; + ibat->irq_num = 0; + ibat->irq_enabled = false; + ibat->ops.get_temp = bcl_read_ibat; + ibat->ops.set_trips = bcl_set_ibat; + ibat->tz_dev = devm_thermal_of_zone_register(&pdev->dev, + type, ibat, &ibat->ops); + if (IS_ERR(ibat->tz_dev)) { + pr_debug("ibat:[%s] register failed. err:%ld\n", + bcl_int_names[type], + PTR_ERR(ibat->tz_dev)); + ibat->tz_dev = NULL; + return; + } + thermal_zone_device_update(ibat->tz_dev, THERMAL_DEVICE_UP); +} + +static int bcl_get_ibat_config(struct platform_device *pdev, + uint32_t *ibat_config) +{ + int ret = 0; + const char *name; + struct nvmem_cell *cell; + size_t len; + char *buf; + + ret = of_property_read_string(pdev->dev.of_node, "nvmem-cell-names", &name); + if (ret) { + *ibat_config = 0; + pr_debug("Default ibat config enabled %u\n", *ibat_config); + return 0; + } + + cell = nvmem_cell_get(&pdev->dev, name); + if (IS_ERR(cell)) { + dev_err(&pdev->dev, "failed to get nvmem cell %s\n", name); + return PTR_ERR(cell); + } + + buf = nvmem_cell_read(cell, &len); + nvmem_cell_put(cell); + if (IS_ERR_OR_NULL(buf)) { + dev_err(&pdev->dev, "failed to read nvmem cell %s\n", name); + return PTR_ERR(buf); + } + + if (len <= 0 || len > sizeof(uint32_t)) { + dev_err(&pdev->dev, "nvmem cell length out of range %d\n", len); + kfree(buf); + return -EINVAL; + } + memcpy(ibat_config, buf, min(len, sizeof(*ibat_config))); + kfree(buf); + + return 0; +} +static void bcl_probe_ibat(struct platform_device *pdev, + struct bcl_device *bcl_perph) +{ + uint32_t bcl_config = 0; + + bcl_get_ibat_config(pdev, &bcl_config); + + if (bcl_config == 1) { + bcl_ibat_init(pdev, BCL_2S_IBAT_LVL0, bcl_perph); + bcl_ibat_init(pdev, BCL_2S_IBAT_LVL1, bcl_perph); + } else { + bcl_ibat_init(pdev, BCL_IBAT_LVL0, bcl_perph); + bcl_ibat_init(pdev, BCL_IBAT_LVL1, bcl_perph); + } +} + +static void bcl_lvl_init(struct platform_device *pdev, + enum bcl_dev_type type, int sts_bit_idx, struct bcl_device *bcl_perph) +{ + struct bcl_peripheral_data *lbat = &bcl_perph->param[type]; + + mutex_init(&lbat->state_trans_lock); + lbat->type = type; + lbat->dev = bcl_perph; + lbat->status_bit_idx = sts_bit_idx; + bcl_fetch_trip(pdev, type, lbat, bcl_handle_irq); + if (lbat->irq_num <= 0) + return; + + lbat->ops.get_temp = bcl_read_lbat; + lbat->ops.set_trips = bcl_set_lbat; + lbat->ops.get_trend = bcl_get_trend; + + lbat->tz_dev = devm_thermal_of_zone_register(&pdev->dev, + type, lbat, &lbat->ops); + if (IS_ERR(lbat->tz_dev)) { + pr_debug("lbat:[%s] register failed. err:%ld\n", + bcl_int_names[type], + PTR_ERR(lbat->tz_dev)); + lbat->tz_dev = NULL; + return; + } + thermal_zone_device_update(lbat->tz_dev, THERMAL_DEVICE_UP); + qti_update_tz_ops(lbat->tz_dev, true); +} + +static void bcl_probe_lvls(struct platform_device *pdev, + struct bcl_device *bcl_perph) +{ + bcl_lvl_init(pdev, BCL_LVL0, BCL_IRQ_L0, bcl_perph); + bcl_lvl_init(pdev, BCL_LVL1, BCL_IRQ_L1, bcl_perph); + bcl_lvl_init(pdev, BCL_LVL2, BCL_IRQ_L2, bcl_perph); +} + +static int bcl_version_init(struct bcl_device *bcl_perph) +{ + int ret = 0; + unsigned int val = 0; + + ret = bcl_read_register(bcl_perph, BCL_REVISION2, &val); + if (ret < 0) + return ret; + + bcl_perph->dig_major = val; + ret = bcl_read_register(bcl_perph, BCL_REVISION1, &val); + if (ret >= 0) + bcl_perph->dig_minor = val; + + if (bcl_perph->dig_major >= BCL_GEN3_MAJOR_REV) { + ret = bcl_read_register(bcl_perph, BCL_PARAM_1, &val); + if (ret < 0) + return ret; + bcl_perph->bcl_param_1 = val; + + val = 0; + bcl_read_register(bcl_perph, BCL_PARAM_2, &val); + bcl_perph->bcl_type = val; + } else { + bcl_perph->bcl_param_1 = 0; + bcl_perph->bcl_type = 0; + } + + return 0; +} + +static void bcl_configure_bcl_peripheral(struct bcl_device *bcl_perph) +{ + bcl_write_register(bcl_perph, BCL_MONITOR_EN, BIT(7)); +} + +static int bcl_remove(struct platform_device *pdev) +{ + int i = 0; + struct bcl_device *bcl_perph = + (struct bcl_device *)dev_get_drvdata(&pdev->dev); + + for (; i < BCL_TYPE_MAX; i++) { + if (!bcl_perph->param[i].tz_dev) + continue; + qti_update_tz_ops(bcl_perph->param[i].tz_dev, false); + } + + return 0; +} + +static int bcl_probe(struct platform_device *pdev) +{ + struct bcl_device *bcl_perph = NULL; + char bcl_name[40]; + int err = 0; + + if (bcl_device_ct >= MAX_PERPH_COUNT) { + dev_err(&pdev->dev, "Max bcl peripheral supported already.\n"); + return -EINVAL; + } + bcl_devices[bcl_device_ct] = devm_kzalloc(&pdev->dev, + sizeof(*bcl_devices[0]), GFP_KERNEL); + if (!bcl_devices[bcl_device_ct]) + return -ENOMEM; + bcl_perph = bcl_devices[bcl_device_ct]; + bcl_perph->dev = &pdev->dev; + + bcl_perph->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!bcl_perph->regmap) { + dev_err(&pdev->dev, "Couldn't get parent's regmap\n"); + return -EINVAL; + } + + bcl_device_ct++; + err = bcl_get_devicetree_data(pdev, bcl_perph); + if (err) { + bcl_device_ct--; + return err; + } + err = bcl_version_init(bcl_perph); + if (err) { + bcl_device_ct--; + return err; + } + bcl_probe_vbat(pdev, bcl_perph); + bcl_probe_ibat(pdev, bcl_perph); + bcl_probe_lvls(pdev, bcl_perph); + bcl_configure_bcl_peripheral(bcl_perph); + + dev_set_drvdata(&pdev->dev, bcl_perph); + + snprintf(bcl_name, sizeof(bcl_name), "bcl_0x%04x_%d", + bcl_perph->fg_bcl_addr, + bcl_device_ct - 1); + + bcl_perph->ipc_log = ipc_log_context_create(IPC_LOGPAGES, + bcl_name, 0); + if (!bcl_perph->ipc_log) + pr_err("%s: unable to create IPC Logging for %s\n", + __func__, bcl_name); + + return 0; +} + +static const struct of_device_id bcl_match[] = { + { + .compatible = "qcom,bcl-v5", + }, + {}, +}; + +static struct platform_driver bcl_driver = { + .probe = bcl_probe, + .remove = bcl_remove, + .driver = { + .name = BCL_DRIVER_NAME, + .of_match_table = bcl_match, + }, +}; + +module_platform_driver(bcl_driver); +MODULE_LICENSE("GPL"); diff --git a/drivers/thermal/qcom/cpu_hotplug.c b/drivers/thermal/qcom/cpu_hotplug.c index 0bb93d72d706..c094e479a239 100644 --- a/drivers/thermal/qcom/cpu_hotplug.c +++ b/drivers/thermal/qcom/cpu_hotplug.c @@ -10,6 +10,9 @@ #include #include #include +#if IS_ENABLED(CONFIG_SEC_PM_LOG) +#include +#endif #define CPU_HOTPLUG_LEVEL 1 @@ -153,8 +156,12 @@ static void cpu_hot_execute_cdev(struct work_struct *work) mutex_lock(&cpu_hot_lock); if (ret < 0) pr_err("CPU:%d offline error:%d\n", cpu, ret); - else + else { +#if IS_ENABLED(CONFIG_SEC_PM_LOG) + ss_thermal_print("offline cpu%d\n", cpu); +#endif cpu_hot_cdev->cpu_cur_state = false; + } } else { if (cpu_hot_cdev->cpu_cur_state) goto unlock_exit; @@ -163,8 +170,12 @@ static void cpu_hot_execute_cdev(struct work_struct *work) mutex_lock(&cpu_hot_lock); if (ret) pr_err("CPU:%d online error:%d\n", cpu, ret); - else + else { +#if IS_ENABLED(CONFIG_SEC_PM_LOG) + ss_thermal_print("online cpu%d\n", cpu); +#endif cpu_hot_cdev->cpu_cur_state = true; + } } unlock_exit: mutex_unlock(&cpu_hot_lock); diff --git a/drivers/thermal/qcom/cpu_voltage_cooling.c b/drivers/thermal/qcom/cpu_voltage_cooling.c index e6b9a840eb53..76c2d0dfd563 100644 --- a/drivers/thermal/qcom/cpu_voltage_cooling.c +++ b/drivers/thermal/qcom/cpu_voltage_cooling.c @@ -15,6 +15,13 @@ #include #include +#if IS_ENABLED(CONFIG_CPU_FREQ_LIMIT) +#include + +struct freq_voltage_base cflm_vbf; +EXPORT_SYMBOL(cflm_vbf); +#endif + #define CPU_MAP_CT 2 #define CC_CDEV_DRIVER "CPU-voltage-cdev" @@ -138,6 +145,10 @@ static int build_unified_table(struct cc_limits_data *cc_cdev, { struct limits_freq_map *freq_map = NULL; int idx = 0, idy = 0, idz = 0, min_idx = 0, max_v = 0, max_idx = 0; +#if IS_ENABLED(CONFIG_CPU_FREQ_LIMIT) + int prime = 0; /* cpu7 */ + int gold = 1; /* cpu2 */ +#endif for (idx = 0; idx < cpu_ct; idx++) { int table_v = table[idx][table_ct[idx] - 1].volt; @@ -158,6 +169,16 @@ static int build_unified_table(struct cc_limits_data *cc_cdev, if (!freq_map) return -ENOMEM; pr_info("CPU1:%d CPU2:%d\n", cc_cdev->cpu_map[0], cc_cdev->cpu_map[1]); + +#if IS_ENABLED(CONFIG_CPU_FREQ_LIMIT) + /* swap condition */ + if (cc_cdev->cpu_map[0] == 2) { + prime = 1; + gold = 0; + } + + cflm_vbf.count = 0; +#endif for (idx = table_ct[max_idx] - 1, idy = table_ct[min_idx] - 1, idz = 0; idx >= 0 && idz < table_ct[max_idx]; idx--, idz++) { int volt = table[max_idx][idx].volt; @@ -172,6 +193,15 @@ static int build_unified_table(struct cc_limits_data *cc_cdev, freq_map[idz].frequency[1] = table[min_idx][idy].frequency; pr_info("freq1:%u freq2:%u\n", freq_map[idz].frequency[0], freq_map[idz].frequency[1]); + +#if IS_ENABLED(CONFIG_CPU_FREQ_LIMIT) + cflm_vbf.table[prime][cflm_vbf.count] = freq_map[idz].frequency[0]; + cflm_vbf.table[gold][cflm_vbf.count] = freq_map[idz].frequency[1]; + pr_info("vbf: prime:%u gold:%u\n", + cflm_vbf.table[PRIME_CPU][cflm_vbf.count], + cflm_vbf.table[GOLD_CPU][cflm_vbf.count]); + cflm_vbf.count++; +#endif } cc_cdev->map_freq = freq_map; @@ -187,6 +217,9 @@ static struct cc_limits_data *opp_init(int *cpus) int table_ct[CPU_MAP_CT], ret = 0; struct cc_limits_data *cc_cdev = NULL; +#if IS_ENABLED(CONFIG_CPU_FREQ_LIMIT) + cflm_vbf.count = 0; +#endif cpu1 = cpus[0]; cpu2 = cpus[1]; cpu1_dev = get_cpu_device(cpu1); diff --git a/drivers/thermal/qcom/thermal_pause.c b/drivers/thermal/qcom/thermal_pause.c index 6ab6c36ccc97..290576c9d54b 100644 --- a/drivers/thermal/qcom/thermal_pause.c +++ b/drivers/thermal/qcom/thermal_pause.c @@ -15,6 +15,9 @@ #include #include #include "thermal_zone_internal.h" +#if IS_ENABLED(CONFIG_SEC_PM_LOG) +#include +#endif enum thermal_pause_levels { THERMAL_NO_CPU_PAUSE, @@ -101,6 +104,9 @@ static int thermal_pause_work(struct thermal_pause_cdev *thermal_pause_cdev) cpumask_copy(&cpus_to_pause, &thermal_pause_cdev->cpu_mask); pr_debug("Pause:%*pbl\n", cpumask_pr_args(&thermal_pause_cdev->cpu_mask)); +#if IS_ENABLED(CONFIG_SEC_PM_LOG) + ss_thermal_print("Pause:%*pbl\n", cpumask_pr_args(&thermal_pause_cdev->cpu_mask)); +#endif mutex_unlock(&cpus_pause_lock); ret = walt_pause_cpus(&cpus_to_pause, PAUSE_THERMAL); @@ -148,6 +154,9 @@ static int thermal_resume_work(struct thermal_pause_cdev *thermal_pause_cdev) cpumask_copy(&cpus_to_unpause, &thermal_pause_cdev->cpu_mask); pr_debug("Unpause:%*pbl\n", cpumask_pr_args(&cpus_to_unpause)); +#if IS_ENABLED(CONFIG_SEC_PM_LOG) + ss_thermal_print("Unpause:%*pbl\n", cpumask_pr_args(&thermal_pause_cdev->cpu_mask)); +#endif mutex_unlock(&cpus_pause_lock); ret = walt_resume_cpus(&cpus_to_unpause, PAUSE_THERMAL); diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index b4a05c1d6c4b..695695a8e54a 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -25,6 +25,16 @@ #include "tsens.h" #include "thermal_zone_internal.h" +#if IS_ENABLED(CONFIG_SEC_PM) +#define MAX_TSENS_CONTROLLER 3 +#define DEFAULT_PERIOD 5000 // 5s + +static struct delayed_work ts_print_work; +struct tsens_priv *ts_priv[MAX_TSENS_CONTROLLER]; + +static int ts_print_count; +#endif + /** * struct tsens_irq_data - IRQ status and temperature violations * @up_viol: upper threshold violated @@ -776,6 +786,27 @@ static int dbg_version_show(struct seq_file *s, void *data) DEFINE_SHOW_ATTRIBUTE(dbg_version); DEFINE_SHOW_ATTRIBUTE(dbg_sensors); +#if IS_ENABLED(CONFIG_SEC_PM) +static unsigned int polling_period; + +static int polling_period_get(void *data, u64 *val) +{ + *val = polling_period; + + return 0; +} + +static int polling_period_set(void *data, u64 val) +{ + polling_period = val; + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(polling_period_fops, polling_period_get, + polling_period_set, "%llu\n"); +#endif + static void tsens_debug_init(struct platform_device *pdev) { struct tsens_priv *priv = platform_get_drvdata(pdev); @@ -793,6 +824,15 @@ static void tsens_debug_init(struct platform_device *pdev) debugfs_create_file("version", 0444, priv->debug_root, pdev, &dbg_version_fops); +#if IS_ENABLED(CONFIG_SEC_PM) + file = debugfs_lookup("polling_period", priv->debug_root); + if (!file) + debugfs_create_file_unsafe("polling_period", 0644, priv->debug_root, + pdev, &polling_period_fops); + + polling_period = DEFAULT_PERIOD; +#endif + /* A directory for each instance of the TSENS IP */ priv->debug = debugfs_create_dir(dev_name(&pdev->dev), priv->debug_root); debugfs_create_file("sensors", 0444, priv->debug, pdev, &dbg_sensors_fops); @@ -1080,6 +1120,40 @@ static int tsens_get_temp(struct thermal_zone_device *tz, int *temp) return priv->ops->get_temp(s, temp); } +#if IS_ENABLED(CONFIG_SEC_PM) +static void __ref ts_print(struct work_struct *work) +{ + struct tsens_sensor *ts_sensor; + int temp = 0; + size_t i, j; + int added = 0, ret = 0; + char buffer[500] = { 0, }; + bool work_flag = true; + + ret = snprintf(buffer + added, sizeof(buffer) - added, "tsens["); + added += ret; + + for (i = 0; i < ts_print_count; i++) { + for (j = 0; j < ts_priv[i]->num_sensors; j++) { + ts_sensor = &ts_priv[i]->sensor[j]; + ts_priv[i]->ops->get_temp(ts_sensor, &temp); + ret = snprintf(buffer + added, sizeof(buffer) - added, + "%d,", temp / 100); + added += ret; + } + } + + buffer[added - 1] = ']'; + pr_info("%s: %s\n", __func__, buffer); + +#if IS_ENABLED(CONFIG_DEBUG_FS) + work_flag = schedule_delayed_work(&ts_print_work, msecs_to_jiffies(polling_period)); +#else + work_flag = schedule_delayed_work(&ts_print_work, HZ * 5); +#endif +} +#endif + static int __maybe_unused tsens_suspend(struct device *dev) { struct tsens_priv *priv = dev_get_drvdata(dev); @@ -1483,7 +1557,22 @@ static int tsens_probe(struct platform_device *pdev) priv->tm_disable_on_suspend = of_property_read_bool(np, "tm-disable-on-suspend"); +#if IS_ENABLED(CONFIG_SEC_PM) + ret = tsens_register(priv); + + ts_priv[ts_print_count++] = priv; + + + if (ts_print_count == MAX_TSENS_CONTROLLER) { + pr_info("%s: set schedule work\n", __func__); + INIT_DELAYED_WORK(&ts_print_work, ts_print); + schedule_delayed_work(&ts_print_work, 0); + } + + return ret; +#else return tsens_register(priv); +#endif } static int tsens_remove(struct platform_device *pdev) @@ -1491,6 +1580,10 @@ static int tsens_remove(struct platform_device *pdev) struct tsens_priv *priv = platform_get_drvdata(pdev); int i; +#if IS_ENABLED(CONFIG_SEC_PM) + cancel_delayed_work_sync(&ts_print_work); +#endif + debugfs_remove_recursive(priv->debug_root); tsens_disable_irq(priv); if (priv->ops->disable) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index fa838001e6a7..8395bf52b53b 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -29,6 +29,9 @@ #include "thermal_core.h" #include "thermal_hwmon.h" +/* SEC_PM: cooling device state */ +static struct delayed_work cdev_print_work; + static DEFINE_IDA(thermal_tz_ida); static DEFINE_IDA(thermal_cdev_ida); @@ -320,9 +323,17 @@ void thermal_zone_device_critical(struct thermal_zone_device *tz) * Its a must for forced_emergency_poweroff_work to be scheduled. */ int poweroff_delay_ms = CONFIG_THERMAL_EMERGENCY_POWEROFF_DELAY_MS; + struct thermal_zone_device *pos; + + /* SEC_PM */ + mutex_lock(&thermal_list_lock); + list_for_each_entry(pos, &thermal_tz_list, node) + pr_err("%s: %d\n", pos->type, pos->temperature); + mutex_unlock(&thermal_list_lock); dev_emerg(&tz->device, "%s: critical temperature reached, " "shutting down\n", tz->type); + panic("Thermal Critical : %s, %d", tz->type, tz->temperature); hw_protection_shutdown("Temperature too high", poweroff_delay_ms); } @@ -653,8 +664,7 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz, if (result) goto release_ida; - snprintf(dev->attr_name, sizeof(dev->attr_name), "cdev%d_trip_point", - dev->id); + sprintf(dev->attr_name, "cdev%d_trip_point", dev->id); sysfs_attr_init(&dev->attr.attr); dev->attr.attr.name = dev->attr_name; dev->attr.attr.mode = 0444; @@ -663,8 +673,7 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz, if (result) goto remove_symbol_link; - snprintf(dev->weight_attr_name, sizeof(dev->weight_attr_name), - "cdev%d_weight", dev->id); + sprintf(dev->weight_attr_name, "cdev%d_weight", dev->id); sysfs_attr_init(&dev->weight_attr.attr); dev->weight_attr.attr.name = dev->weight_attr_name; dev->weight_attr.attr.mode = S_IWUSR | S_IRUGO; @@ -1450,16 +1459,48 @@ struct thermal_zone_device *thermal_zone_get_zone_by_name(const char *name) } EXPORT_SYMBOL_GPL(thermal_zone_get_zone_by_name); +/* SEC_PM */ +#define BUF_SIZE SZ_1K +static void __ref cdev_print(struct work_struct *work) +{ + struct thermal_cooling_device *cdev; + unsigned long cur_state = 0; + int added = 0, ret = 0; + char buffer[BUF_SIZE] = { 0, }; + + mutex_lock(&thermal_list_lock); + list_for_each_entry(cdev, &thermal_cdev_list, node) { + if (cdev->ops->get_cur_state) + cdev->ops->get_cur_state(cdev, &cur_state); + + if (cur_state) { + ret = snprintf(buffer + added, sizeof(buffer) - added, + "[%s:%ld]", cdev->type, cur_state); + added += ret; + + if (added >= BUF_SIZE) + break; + } + } + mutex_unlock(&thermal_list_lock); + + pr_info("thermal: cdev%s\n", buffer); + + schedule_delayed_work(&cdev_print_work, HZ * 5); +} + static int thermal_pm_notify(struct notifier_block *nb, unsigned long mode, void *_unused) { struct thermal_zone_device *tz; - int irq_wakeable = 0; switch (mode) { case PM_HIBERNATION_PREPARE: case PM_RESTORE_PREPARE: case PM_SUSPEND_PREPARE: + /* SEC_PM */ + cancel_delayed_work(&cdev_print_work); + atomic_set(&in_suspend, 1); break; case PM_POST_HIBERNATION: @@ -1467,15 +1508,18 @@ static int thermal_pm_notify(struct notifier_block *nb, case PM_POST_SUSPEND: atomic_set(&in_suspend, 0); list_for_each_entry(tz, &thermal_tz_list, node) { - - trace_android_vh_thermal_pm_notify_suspend(tz, &irq_wakeable); - if (irq_wakeable) + /* SEC_PM: to optimize wakeup time */ + if (tz->polling_delay_jiffies == 0) continue; thermal_zone_device_init(tz); thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); } + + /* SEC_PM */ + schedule_delayed_work(&cdev_print_work, 0); + break; default: break; @@ -1508,6 +1552,10 @@ static int __init thermal_init(void) pr_warn("Thermal: Can not register suspend notifier, return %d\n", result); + /* SEC_PM */ + INIT_DELAYED_WORK(&cdev_print_work, cdev_print); + schedule_delayed_work(&cdev_print_work, 0); + return 0; unregister_governors: diff --git a/drivers/tty/serial/msm_geni_serial.c b/drivers/tty/serial/msm_geni_serial.c index faccd6c390a6..601a34afe32a 100644 --- a/drivers/tty/serial/msm_geni_serial.c +++ b/drivers/tty/serial/msm_geni_serial.c @@ -33,6 +33,10 @@ #include #include +#if IS_ENABLED(CONFIG_SEC_FACTORY) +#include "msm_geni_serial_proc_log.h" +#endif + static bool con_enabled = IS_ENABLED(CONFIG_SERIAL_MSM_GENI_CONSOLE_DEFAULT_ENABLED); /* UART specific GENI registers */ @@ -337,6 +341,11 @@ struct msm_geni_serial_ver_info { int s_fw_ver; }; +#if IS_ENABLED(CONFIG_SEC_FACTORY) +#undef DMA_RX_BUF_SIZE +#define DMA_RX_BUF_SIZE (4096) +#endif + struct msm_geni_serial_rsc { struct device *ctrl_dev; struct device *wrapper_dev; @@ -5435,6 +5444,10 @@ static void msm_geni_check_stop_engine(struct uart_port *uport) } } +#if IS_ENABLED(CONFIG_SEC_FACTORY) +#include "msm_geni_serial_proc_log.c" +#endif + static int msm_geni_serial_probe(struct platform_device *pdev) { int ret = 0; @@ -5627,6 +5640,17 @@ static int msm_geni_serial_probe(struct platform_device *pdev) pr_info("boot_kpi: M - DRIVER GENI_CONSOLE_%d Ready\n", line); else pr_info("boot_kpi: M - DRIVER GENI_HS_UART_%d Ready\n", line); +#if IS_ENABLED(CONFIG_SEC_FACTORY) + if (!dev_port->is_console && dev_port->is_clk_aon) { + register_serial_ipc_log_context((struct ipc_log_context *)dev_port->ipc_log_pwr); + register_serial_ipc_log_context((struct ipc_log_context *)dev_port->ipc_log_misc); + register_serial_ipc_log_context((struct ipc_log_context *)dev_port->ipc_log_rx); + register_serial_ipc_log_context((struct ipc_log_context *)dev_port->ipc_log_tx); + ret = create_proc_log_file(); + if (ret < 0) + dev_err(&pdev->dev, "Failed to register serial ipc log context: %d\n", ret); + } +#endif exit_geni_serial_probe: UART_LOG_DBG(dev_port->ipc_log_misc, &pdev->dev, "%s: ret:%d\n", diff --git a/drivers/tty/serial/msm_geni_serial_proc_log.c b/drivers/tty/serial/msm_geni_serial_proc_log.c new file mode 100644 index 000000000000..8b1c7b4be1e3 --- /dev/null +++ b/drivers/tty/serial/msm_geni_serial_proc_log.c @@ -0,0 +1,176 @@ + +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Samsung TN debugging code + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "msm_geni_serial_proc_log.h" + + + +struct ipc_log_context_entry { + struct list_head list; + struct ipc_log_context *ctxt; +}; + +#define IPC_ALL_DATA_MAX_SIZE (1024*1024) + +struct serial_ipc_ctxt_export_data { + char *ipc_all_data_buffer; + size_t ipc_all_data_buffer_len; + atomic_t serial_proc_usage; +}; + + +static DEFINE_MUTEX(ipc_log_context_list_lock); +static struct list_head serial_ipc_log_ctxt_list = + LIST_HEAD_INIT(serial_ipc_log_ctxt_list); + +static struct serial_ipc_ctxt_export_data proc_data; + +static int debug_log(struct ipc_log_context *ctxt, + char *buff, int size) +{ + return ipc_log_extract(ctxt, buff, size - 1); +} + +static ssize_t uart_serial_log_file_read(struct file *file, char __user *buf, + size_t len, loff_t *offset) +{ + loff_t pos = *offset; + ssize_t count; + + if (pos > proc_data.ipc_all_data_buffer_len) + return 0; + + count = min(len, (size_t)(proc_data.ipc_all_data_buffer_len - pos)); + if (copy_to_user(buf, proc_data.ipc_all_data_buffer + pos, count)) + return -EFAULT; + + *offset += count; + return count; +} + +static int uart_serial_log_file_open(struct inode *inode, struct file *file) +{ +#define IPC_NAME_MARK_LEN 15 +#define IPC_ONETIME_CHUCK_SIZE (4*PAGE_SIZE) + + int bsize; + int offset = 0; + struct ipc_log_context_entry *entry; + + if (atomic_read(&proc_data.serial_proc_usage) > 0) + return -ENODEV; + + atomic_inc(&proc_data.serial_proc_usage); + + proc_data.ipc_all_data_buffer = kvmalloc(IPC_ALL_DATA_MAX_SIZE, GFP_KERNEL); + if (!proc_data.ipc_all_data_buffer) + return -ENOMEM; + + mutex_lock(&ipc_log_context_list_lock); + list_for_each_entry(entry, &serial_ipc_log_ctxt_list, list) { + offset += snprintf((char *)proc_data.ipc_all_data_buffer + offset, + IPC_LOG_MAX_CONTEXT_NAME_LEN + IPC_NAME_MARK_LEN, "[*****%s*****]\n", entry->ctxt->name); + do { + bsize = debug_log(entry->ctxt, (char *)proc_data.ipc_all_data_buffer + offset, IPC_ONETIME_CHUCK_SIZE); + offset += bsize; + } while(bsize > 0 && offset < IPC_ALL_DATA_MAX_SIZE); + } + mutex_unlock(&ipc_log_context_list_lock); + + if (offset >= IPC_ALL_DATA_MAX_SIZE) { + pr_info("[%s] ipc data is over %d\n", __func__, IPC_ALL_DATA_MAX_SIZE); + return -ENOMEM; + } + + + proc_data.ipc_all_data_buffer_len = offset; + return 0; +} + +static int uart_serial_log_file_release(struct inode *inode, struct file *file) +{ + if (proc_data.ipc_all_data_buffer) + kvfree(proc_data.ipc_all_data_buffer); + + atomic_dec(&proc_data.serial_proc_usage); + return 0; +} + +static const struct proc_ops proc_log_file_ops = { + .proc_read = uart_serial_log_file_read, + .proc_open = uart_serial_log_file_open, + .proc_release = uart_serial_log_file_release, +}; + +int register_serial_ipc_log_context(struct ipc_log_context *ctxt) +{ + + struct ipc_log_context_entry *entry; + + if (!ctxt) + return -EINVAL; + + entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + entry->ctxt = ctxt; + + mutex_lock(&ipc_log_context_list_lock); + list_add(&entry->list, &serial_ipc_log_ctxt_list); + mutex_unlock(&ipc_log_context_list_lock); + + return 0; +} + +int create_proc_log_file(void) +{ + struct proc_dir_entry *entry; + struct proc_dir_entry *proc_serial; + struct proc_dir_entry *proc_serial_uart; + + atomic_set(&proc_data.serial_proc_usage, 0); + proc_serial = proc_mkdir("serial", NULL); + if (!proc_serial) { + pr_info("[%s] failed to make directory /proc/serial, ret:%d\n", __func__, proc_serial); + return -ENOENT; + } + + proc_serial_uart = proc_mkdir("uart", proc_serial); + if (!proc_serial_uart) { + pr_info("[%s] failed to make directory /proc/serial/uart, ret:%d\n", __func__, proc_serial_uart); + return -ENOENT; + } + + entry = proc_create("log", S_IFREG | 0444, proc_serial_uart, &proc_log_file_ops); + if (!entry) { + pr_info("[%s] failed to proc file /proc/serial/uart/log ret:%d\n", __func__, entry); + return -ENOENT; + } + + pr_info("%s: success to create proc entry\n", __func__); + return 0; +} +MODULE_LICENSE("GPL v2"); diff --git a/drivers/tty/serial/msm_geni_serial_proc_log.h b/drivers/tty/serial/msm_geni_serial_proc_log.h new file mode 100644 index 000000000000..d3db193c3547 --- /dev/null +++ b/drivers/tty/serial/msm_geni_serial_proc_log.h @@ -0,0 +1,28 @@ + +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Samsung TN debugging code + * + * 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. + */ + +#ifndef __MSM_GENI_SERIAL_EXPORT_PROC_HEADER__ +#define __MSM_GENI_SERIAL_EXPORT_PROC_HEADER__ +#include +#include +#include +#include + +#include +#include + +#include "../../../kernel/trace/ipc_logging_private.h" + +int register_serial_ipc_log_context(struct ipc_log_context *ctxt); +int create_proc_log_file(void); + +#endif diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index f2eea7945f5b..3711ba8793f0 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -5975,9 +5975,14 @@ static void ufshcd_bkops_exception_event_handler(struct ufs_hba *hba) if (curr_status < BKOPS_STATUS_PERF_IMPACT) { dev_err(hba->dev, "%s: device raised urgent BKOPS exception for bkops status %d\n", __func__, curr_status); - /* update the current status as the urgent bkops level */ - hba->urgent_bkops_lvl = curr_status; - hba->is_urgent_bkops_lvl_checked = true; + /* + * SEC does not follow this policy that BKOPS is enabled for these events + * update the current status as the urgent bkops level + */ + //hba->urgent_bkops_lvl = curr_status; + //hba->is_urgent_bkops_lvl_checked = true; + + goto out; } enable_auto_bkops: diff --git a/drivers/ufs/host/Kconfig b/drivers/ufs/host/Kconfig index 37497b76a486..7c1af79c7c62 100644 --- a/drivers/ufs/host/Kconfig +++ b/drivers/ufs/host/Kconfig @@ -161,3 +161,22 @@ config SCSI_UFS_SPRD Select this if you have UFS controller on Unisoc chipset. If unsure, say N. + +config SEC_UFS_FEATURE + bool "SEC specific UFS feature" + default n + depends on SCSI_UFSHCD + help + Enable Samsung feature support + Enabling this allows kernel to use SEC specific feature + defined and implemented by SEC. + +config SCSI_UFS_TEST_MODE + bool "Samsung UFS TEST feature" + default n + depends on SEC_UFS_FEATURE + help + This selects support for test mode for debugging. + Select this option if this feature is needed on working. + + If unsure, say N. diff --git a/drivers/ufs/host/Makefile b/drivers/ufs/host/Makefile index b9d66d72099d..2ead748fc80d 100644 --- a/drivers/ufs/host/Makefile +++ b/drivers/ufs/host/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_SCSI_UFS_CDNS_PLATFORM) += cdns-pltfrm.o obj-$(CONFIG_SCSI_UFS_QCOM) += ufs_qcom.o ufs_qcom-y += ufs-qcom.o ufs_qcom-$(CONFIG_SCSI_UFS_CRYPTO) += ufs-qcom-ice.o +ufs_qcom-$(CONFIG_SEC_UFS_FEATURE) += ufs-sec-feature.o ufs-sec-sysfs.o obj-$(CONFIG_SCSI_UFS_EXYNOS) += ufs-exynos.o obj-$(CONFIG_SCSI_UFSHCD_PCI) += ufshcd-pci.o obj-$(CONFIG_SCSI_UFSHCD_PLATFORM) += ufshcd-pltfrm.o diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c index 7551409c51e0..a46c336fb30a 100644 --- a/drivers/ufs/host/ufs-qcom.c +++ b/drivers/ufs/host/ufs-qcom.c @@ -43,6 +43,10 @@ #include #include +#if IS_ENABLED(CONFIG_SEC_UFS_FEATURE) +#include "ufs-sec-feature.h" +#endif + #define MCQ_QCFGPTR_MASK GENMASK(7, 0) #define MCQ_QCFGPTR_UNIT 0x200 #define MCQ_SQATTR_OFFSET(c) \ @@ -785,6 +789,11 @@ static int ufs_qcom_host_reset(struct ufs_hba *hba) host->reset_in_progress = true; +#if IS_ENABLED(CONFIG_SEC_UFS_FEATURE) + /* check device_stuck info and call panic before host reset */ + ufs_sec_check_device_stuck(); +#endif + if (!host->core_reset) { dev_warn(hba->dev, "%s: reset control not set\n", __func__); goto out; @@ -1818,6 +1827,11 @@ static int ufs_qcom_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op, ufs_qcom_ice_disable(host); cancel_dwork_unvote_cpufreq(hba); + +#if IS_ENABLED(CONFIG_SEC_UFS_FEATURE) + if (pm_op == UFS_SYSTEM_PM) + ufs_sec_print_err(); +#endif return err; } @@ -2398,6 +2412,8 @@ struct ufs_qcom_dev_params ufs_qcom_cap; /* update BER threshold depends on gear mode */ if (!override_ber_threshold && !ret) ber_threshold = ber_table[dev_req_params->gear_rx].ber_threshold; + + host->skip_flush = false; break; default: ret = -EINVAL; @@ -2571,6 +2587,16 @@ static int ufs_qcom_apply_dev_quirks(struct ufs_hba *hba) if (hba->dev_info.wmanufacturerid == UFS_VENDOR_MICRON) hba->dev_quirks |= UFS_DEVICE_QUIRK_DELAY_BEFORE_LPM; +#if IS_ENABLED(CONFIG_SEC_UFS_FEATURE) + /* check only at the first init */ + if (!(hba->eh_flags || hba->pm_op_in_progress)) { + /* sec special features */ + ufs_sec_set_features(hba); + } + + ufs_sec_config_features(hba); +#endif + return err; } @@ -2618,6 +2644,12 @@ static void ufs_qcom_advertise_quirks(struct ufs_hba *hba) | UFSHCD_QUIRK_BROKEN_PA_RXHSUNTERMCAP); } +#if IS_ENABLED(CONFIG_SEC_UFS_FEATURE) + /* no use broken_ahit_wa */ + host->broken_ahit_wa = false; + hba->quirks |= UFSHCD_QUIRK_BROKEN_AUTO_HIBERN8; +#endif + if (host->disable_lpm || host->broken_ahit_wa) hba->quirks |= UFSHCD_QUIRK_BROKEN_AUTO_HIBERN8; @@ -3878,6 +3910,9 @@ static int ufs_qcom_init(struct ufs_hba *hba) ufs_qcom_parse_broken_ahit_workaround_flag(host); ufs_qcom_set_caps(hba); ufs_qcom_advertise_quirks(hba); +#if IS_ENABLED(CONFIG_SEC_UFS_FEATURE) + ufs_sec_adjust_caps_quirks(hba); +#endif err = ufs_qcom_shared_ice_init(hba); if (err) @@ -4416,6 +4451,10 @@ static void ufs_qcom_event_notify(struct ufs_hba *hba, default: break; } + +#if IS_ENABLED(CONFIG_SEC_UFS_FEATURE) + ufs_sec_inc_op_err(hba, evt, data); +#endif } void ufs_qcom_print_hw_debug_reg_all(struct ufs_hba *hba, @@ -4981,6 +5020,17 @@ static int ufs_qcom_device_reset(struct ufs_hba *hba) struct ufs_qcom_host *host = ufshcd_get_variant(hba); int ret = 0; + /* guarantee device internal cache flush */ + if (hba->eh_flags && !host->skip_flush) { + dev_info(hba->dev, "%s: Waiting for device internal cache flush\n", + __func__); + ssleep(2); + host->skip_flush = true; +#if IS_ENABLED(CONFIG_SEC_UFS_FEATURE) + ufs_sec_inc_hwrst_cnt(); +#endif + } + /* Reset UFS Host Controller and PHY */ ret = ufs_qcom_host_reset(hba); if (ret) @@ -5837,7 +5887,9 @@ static void ufs_qcom_hook_check_int_errors(void *param, struct ufs_hba *hba, static void ufs_qcom_update_sdev(void *param, struct scsi_device *sdev) { +#if !IS_ENABLED(CONFIG_SEC_UFS_FEATURE) sdev->broken_fua = 1; +#endif } /* @@ -5856,6 +5908,10 @@ static void ufs_qcom_register_hooks(void) register_trace_android_vh_ufs_check_int_errors( ufs_qcom_hook_check_int_errors, NULL); register_trace_android_vh_ufs_update_sdev(ufs_qcom_update_sdev, NULL); + +#if IS_ENABLED(CONFIG_SEC_UFS_FEATURE) + ufs_sec_register_vendor_hooks(); +#endif } #ifdef CONFIG_ARM_QCOM_CPUFREQ_HW @@ -5973,6 +6029,9 @@ static int ufs_qcom_probe(struct platform_device *pdev) return err; } +#if IS_ENABLED(CONFIG_SEC_UFS_FEATURE) + ufs_sec_init_logging(dev); +#endif /* Perform generic probe */ err = ufshcd_pltfrm_init(pdev, &ufs_hba_qcom_vops); if (err) @@ -6014,6 +6073,10 @@ static int ufs_qcom_remove(struct platform_device *pdev) atomic_notifier_chain_unregister(&panic_notifier_list, &host->ufs_qcom_panic_nb); +#if IS_ENABLED(CONFIG_SEC_UFS_FEATURE) + ufs_sec_remove_features(hba); +#endif + ufshcd_remove(hba); platform_msi_domain_free_irqs(hba->dev); return 0; diff --git a/drivers/ufs/host/ufs-qcom.h b/drivers/ufs/host/ufs-qcom.h index 775670b26d9f..422a327dbf97 100644 --- a/drivers/ufs/host/ufs-qcom.h +++ b/drivers/ufs/host/ufs-qcom.h @@ -637,6 +637,7 @@ struct ufs_qcom_host { bool broken_ahit_wa; unsigned long active_cmds; + bool skip_flush; }; static inline u32 diff --git a/drivers/ufs/host/ufs-sec-feature.c b/drivers/ufs/host/ufs-sec-feature.c new file mode 100644 index 000000000000..3bfc163b170c --- /dev/null +++ b/drivers/ufs/host/ufs-sec-feature.c @@ -0,0 +1,1531 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Samsung Specific feature + * + * Copyright (C) 2023 Samsung Electronics Co., Ltd. + * + * Authors: + * Storage Driver + */ + +#include "ufs-sec-feature.h" +#include "ufs-sec-sysfs.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define NOTI_WORK_DELAY_MS 500 + +void (*ufs_sec_wb_reset_notify)(void); + +struct ufs_sec_feature_info ufs_sec_features; + +static void ufs_sec_print_evt_hist(struct ufs_hba *hba); + +static inline int ufs_sec_read_unit_desc_param(struct ufs_hba *hba, + int lun, + enum unit_desc_param param_offset, + u8 *param_read_buf, + u32 param_size) +{ + /* + * Unit descriptors are only available for general purpose LUs (LUN id + * from 0 to 7) and RPMB Well known LU. + */ + if (!ufs_is_valid_unit_desc_lun(&hba->dev_info, lun)) + return -EOPNOTSUPP; + + return ufshcd_read_desc_param(hba, QUERY_DESC_IDN_UNIT, lun, + param_offset, param_read_buf, param_size); +} + +static void ufs_sec_set_unique_number(struct ufs_hba *hba, u8 *desc_buf) +{ + struct ufs_vendor_dev_info *vdi = ufs_sec_features.vdi; + u8 manid = desc_buf[DEVICE_DESC_PARAM_MANF_ID + 1]; + u8 serial_num_index = desc_buf[DEVICE_DESC_PARAM_SN]; + u8 snum_buf[SERIAL_NUM_SIZE]; + u8 *str_desc_buf = NULL; + int err; + + /* read string desc */ + str_desc_buf = kzalloc(QUERY_DESC_MAX_SIZE, GFP_KERNEL); + if (!str_desc_buf) + return; + + /* spec is unicode but sec uses hex data */ + err = ufshcd_read_desc_param(hba, + QUERY_DESC_IDN_STRING, serial_num_index, 0, + str_desc_buf, QUERY_DESC_MAX_SIZE); + if (err) { + dev_err(hba->dev, "%s: Failed reading string descriptor. err %d", + __func__, err); + goto out; + } + + /* setup unique_number */ + memset(snum_buf, 0, sizeof(snum_buf)); + memcpy(snum_buf, str_desc_buf + QUERY_DESC_HDR_SIZE, SERIAL_NUM_SIZE); + memset(vdi->unique_number, 0, sizeof(vdi->unique_number)); + + sprintf(vdi->unique_number, "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", + manid, + desc_buf[DEVICE_DESC_PARAM_MANF_DATE], + desc_buf[DEVICE_DESC_PARAM_MANF_DATE + 1], + snum_buf[0], snum_buf[1], snum_buf[2], snum_buf[3], + snum_buf[4], snum_buf[5], snum_buf[6]); + + /* Null terminate the unique number string */ + vdi->unique_number[UFS_UN_20_DIGITS] = '\0'; + + dev_dbg(hba->dev, "%s: ufs un : %s\n", __func__, vdi->unique_number); +out: + kfree(str_desc_buf); +} + +void ufs_sec_get_health_desc(struct ufs_hba *hba) +{ + struct ufs_vendor_dev_info *vdi = ufs_sec_features.vdi; + u8 *desc_buf = NULL; + int err; + + desc_buf = kzalloc(QUERY_DESC_MAX_SIZE, GFP_KERNEL); + if (!desc_buf) + return; + + err = ufshcd_read_desc_param(hba, + QUERY_DESC_IDN_HEALTH, 0, 0, + desc_buf, QUERY_DESC_MAX_SIZE); + if (err) { + dev_err(hba->dev, "%s: Failed reading health descriptor. err %d", + __func__, err); + goto out; + } + + /* getting Life Time, Firmware block Life Time and EOL info at Device Health DESC*/ + vdi->lt = desc_buf[HEALTH_DESC_PARAM_LIFE_TIME_EST_A]; + vdi->flt = desc_buf[HEALTH_DESC_PARAM_VENDOR_LIFE_TIME_EST]; + vdi->eli = desc_buf[HEALTH_DESC_PARAM_EOL_INFO]; + + dev_info(hba->dev, "LT: 0x%02x, FLT: 0x%02x, ELI: 0x%01x\n", + ((desc_buf[HEALTH_DESC_PARAM_LIFE_TIME_EST_A] << 4) | + desc_buf[HEALTH_DESC_PARAM_LIFE_TIME_EST_B]), + vdi->flt, vdi->eli); +out: + kfree(desc_buf); +} + +/* + * get dExtendedUFSFeaturesSupport from device descriptor + * + * checking device spec version for UFS v2.2 , v3.1 or later + */ +static void ufs_sec_get_ext_feature(struct ufs_hba *hba, u8 *desc_buf) +{ + struct ufs_dev_info *dev_info = &hba->dev_info; + + if (!(dev_info->wspecversion >= 0x310 || + dev_info->wspecversion == 0x220 || + (hba->dev_quirks & UFS_DEVICE_QUIRK_SUPPORT_EXTENDED_FEATURES))) { + ufs_sec_features.ext_ufs_feature_sup = 0x0; + return; + } + + ufs_sec_features.ext_ufs_feature_sup = get_unaligned_be32(desc_buf + + DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP); +} + +/* SEC next WB : begin */ +#define UFS_WB_DISABLE_THRESHOLD_LT 9 + +static void ufs_sec_wb_update_err(void) +{ + struct ufs_sec_wb_info *wb_info = ufs_sec_features.ufs_wb; + + wb_info->err_cnt++; +} + +static void ufs_sec_wb_update_info(struct ufs_hba *hba, int write_transfer_len) +{ + struct ufs_sec_wb_info *wb_info = ufs_sec_features.ufs_wb; + enum ufs_sec_wb_state wb_state = hba->dev_info.wb_enabled; + + if (write_transfer_len) { + /* + * write_transfer_len : Byte + * wb_info->amount_kb : KB + */ + wb_info->amount_kb += (unsigned long)(write_transfer_len >> 10); + return; + } + + switch (wb_state) { + case WB_OFF: + wb_info->enable_ms += jiffies_to_msecs(jiffies - wb_info->state_ts); + wb_info->state_ts = jiffies; + wb_info->disable_cnt++; + break; + case WB_ON: + wb_info->disable_ms += jiffies_to_msecs(jiffies - wb_info->state_ts); + wb_info->state_ts = jiffies; + wb_info->enable_cnt++; + break; + default: + break; + } +} + +bool ufs_sec_is_wb_supported(void) +{ + struct ufs_sec_wb_info *ufs_wb = ufs_sec_features.ufs_wb; + struct ufs_vendor_dev_info *vdi = ufs_sec_features.vdi; + + if (!ufs_wb) + return false; + + if (vdi->lt >= UFS_WB_DISABLE_THRESHOLD_LT) + ufs_wb->support = false; + + return ufs_wb->support; +} +EXPORT_SYMBOL(ufs_sec_is_wb_supported); + +static bool ufs_sec_check_ext_feature(u32 mask) +{ + if (ufs_sec_features.ext_ufs_feature_sup & mask) + return true; + + return false; +} + +static u32 ufs_sec_wb_buf_alloc(struct ufs_hba *hba, + struct ufs_dev_info *dev_info, u8 *desc_buf) +{ + u32 wb_buf_alloc = 0; + u8 lun; + + dev_info->wb_buffer_type = desc_buf[DEVICE_DESC_PARAM_WB_TYPE]; + + if (dev_info->wb_buffer_type == WB_BUF_MODE_SHARED && + (hba->dev_info.wspecversion >= 0x310 || + hba->dev_info.wspecversion == 0x220)) { + wb_buf_alloc = get_unaligned_be32(desc_buf + + DEVICE_DESC_PARAM_WB_SHARED_ALLOC_UNITS); + } else { + for (lun = 0; lun < UFS_UPIU_MAX_WB_LUN_ID; lun++) { + wb_buf_alloc = 0; + ufs_sec_read_unit_desc_param(hba, + lun, + UNIT_DESC_PARAM_WB_BUF_ALLOC_UNITS, + (u8 *)&wb_buf_alloc, + sizeof(wb_buf_alloc)); + if (wb_buf_alloc) { + dev_info->wb_dedicated_lu = lun; + break; + } + } + } + + return wb_buf_alloc; +} + +static int __ufs_sec_wb_ctrl(bool enable) +{ + struct ufs_hba *hba = get_vdi_member(hba); + enum query_opcode opcode; + int ret = 0; + u8 index; + + if (!ufs_sec_is_wb_supported()) + return -EOPNOTSUPP; + + if (enable) + opcode = UPIU_QUERY_OPCODE_SET_FLAG; + else + opcode = UPIU_QUERY_OPCODE_CLEAR_FLAG; + + index = ufshcd_wb_get_query_index(hba); + ret = ufshcd_query_flag_retry(hba, opcode, + QUERY_FLAG_IDN_WB_EN, index, NULL); + if (!ret) { + hba->dev_info.wb_enabled = enable; + ufs_sec_wb_update_info(hba, 0); + } else + ufs_sec_wb_update_err(); + + pr_info("%s(%s) is %s, ret=%d.\n", __func__, + enable ? "enable" : "disable", + ret ? "failed" : "done", ret); + + return ret; +} + +static inline int ufs_sec_shost_in_recovery(struct Scsi_Host *shost) +{ + return shost->shost_state == SHOST_RECOVERY || + shost->shost_state == SHOST_CANCEL_RECOVERY || + shost->shost_state == SHOST_DEL_RECOVERY || + shost->tmf_in_progress; +} + +int ufs_sec_wb_ctrl(bool enable) +{ + struct ufs_hba *hba = get_vdi_member(hba); + int ret = 0; + unsigned long flags; + struct Scsi_Host *shost = hba->host; + + spin_lock_irqsave(hba->host->host_lock, flags); + if (hba->pm_op_in_progress || hba->is_sys_suspended) { + pr_err("%s: ufs is suspended.\n", __func__); + ret = -EBUSY; + goto out; + } + + if (hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL) { + pr_err("%s: UFS Host state=%d.\n", __func__, hba->ufshcd_state); + ret = -EBUSY; + goto out; + } + + if (ufs_sec_shost_in_recovery(shost)) { + ret = -EBUSY; + goto out; + } + + if (!(enable ^ hba->dev_info.wb_enabled)) { + pr_info("%s: write booster is already %s\n", + __func__, enable ? "enabled" : "disabled"); + ret = 0; + goto out; + } + spin_unlock_irqrestore(hba->host->host_lock, flags); + + ufshcd_rpm_get_sync(hba); + ret = __ufs_sec_wb_ctrl(enable); + ufshcd_rpm_put(hba); + + return ret; +out: + spin_unlock_irqrestore(hba->host->host_lock, flags); + return ret; +} +EXPORT_SYMBOL(ufs_sec_wb_ctrl); + +static void ufs_sec_wb_toggle_flush_during_h8(struct ufs_hba *hba, bool set) +{ + enum query_opcode opcode; + u8 index; + int ret = 0; + + if (!ufs_sec_is_wb_supported()) + return; + + if (set) + opcode = UPIU_QUERY_OPCODE_SET_FLAG; + else + opcode = UPIU_QUERY_OPCODE_CLEAR_FLAG; + + index = ufshcd_wb_get_query_index(hba); + ret = ufshcd_query_flag_retry(hba, opcode, + QUERY_FLAG_IDN_WB_BUFF_FLUSH_DURING_HIBERN8, + index, NULL); + + dev_info(hba->dev, "%s: %s WB flush during H8 is %s.\n", __func__, + set ? "set" : "clear", + ret ? "failed" : "done"); +} + +static void ufs_sec_wb_probe(struct ufs_hba *hba, u8 *desc_buf) +{ + struct ufs_dev_info *dev_info = &hba->dev_info; + struct ufs_sec_wb_info *ufs_wb = NULL; + struct ufs_sec_wb_info *ufs_wb_backup = NULL; + u32 wb_buf_alloc = 0; + + if (!ufs_sec_check_ext_feature(UFS_DEV_WRITE_BOOSTER_SUP)) { + dev_err(hba->dev, "%s: Failed check_ext_feature", __func__); + goto wb_disabled; + } + + dev_info->b_presrv_uspc_en = + desc_buf[DEVICE_DESC_PARAM_WB_PRESRV_USRSPC_EN]; + if (!dev_info->b_presrv_uspc_en) { + dev_err(hba->dev, "%s: Failed preserved user space en value", + __func__); + goto wb_disabled; + } + + wb_buf_alloc = ufs_sec_wb_buf_alloc(hba, dev_info, desc_buf); + if (!wb_buf_alloc) { + dev_err(hba->dev, "%s: Failed wb_buf_alloc", __func__); + goto wb_disabled; + } + + ufs_wb = devm_kzalloc(hba->dev, sizeof(struct ufs_sec_wb_info), + GFP_KERNEL); + if (!ufs_wb) { + dev_err(hba->dev, "%s: Failed allocating ufs_wb(%lu)", + __func__, sizeof(struct ufs_sec_wb_info)); + goto wb_disabled; + } + + ufs_wb_backup = devm_kzalloc(hba->dev, sizeof(struct ufs_sec_wb_info), + GFP_KERNEL); + if (!ufs_wb_backup) { + dev_err(hba->dev, "%s: Failed allocating ufs_wb_backup(%lu)", + __func__, sizeof(struct ufs_sec_wb_info)); + goto wb_disabled; + } + + ufs_wb->support = true; + ufs_wb->state_ts = jiffies; + ufs_wb_backup->state_ts = jiffies; + + ufs_sec_features.ufs_wb = ufs_wb; + ufs_sec_features.ufs_wb_backup = ufs_wb_backup; + + hba->dev_info.wb_enabled = WB_OFF; + + dev_info(hba->dev, "%s: SEC WB is supported. type=%s%d, size=%u.\n", + __func__, + (dev_info->wb_buffer_type == WB_BUF_MODE_LU_DEDICATED) ? + " dedicated LU" : " shared", + (dev_info->wb_buffer_type == WB_BUF_MODE_LU_DEDICATED) ? + dev_info->wb_dedicated_lu : 0, wb_buf_alloc); +wb_disabled: + return; +} + +/* Called by blk-sec-wb */ +void ufs_sec_wb_register_reset_notify(void *func) +{ + ufs_sec_wb_reset_notify = func; +} +EXPORT_SYMBOL(ufs_sec_wb_register_reset_notify); + +void ufs_sec_wb_config(struct ufs_hba *hba) +{ + /* + * 1. Default WB state is WB_OFF when UFS is initialized. + * 2. If UFS error handling occurs in WB_ON state, + * It needs to be reset to WB_OFF state, + * due to UFS reset. + */ + if (hba->dev_info.wb_enabled == WB_ON) { + hba->dev_info.wb_enabled = WB_OFF; + + /* Call ssg's reset_noti func. */ + if (ufs_sec_wb_reset_notify != NULL) + (*ufs_sec_wb_reset_notify)(); + } + + ufs_sec_wb_toggle_flush_during_h8(hba, true); +} +/* SEC next WB : end */ + +/* SEC test mode : begin */ +#if IS_ENABLED(CONFIG_SCSI_UFS_TEST_MODE) +#define UFS_SEC_TESTMODE_CMD_LOG_MAX 10 +static void ufs_sec_print_cmdlog(struct ufs_hba *hba) +{ + struct ufs_sec_cmd_log_info *ufs_cmd_log = + ufs_sec_features.ufs_cmd_log; + struct ufs_sec_cmd_log_entry *entry = NULL; + int i = (ufs_cmd_log->pos + UFS_SEC_CMD_LOGGING_MAX + - UFS_SEC_TESTMODE_CMD_LOG_MAX); + int idx = 0; + + if (!ufs_cmd_log) + return; + + dev_err(hba->dev, "UFS CMD hist\n"); + dev_err(hba->dev, "%02s: %10s: %2s %3s %4s %9s %6s %16s\n", + "No", "log string", "lu", "tag", + "c_id", "lba", "length", "time"); + + for (idx = 0; idx < UFS_SEC_TESTMODE_CMD_LOG_MAX; idx++, i++) { + i %= UFS_SEC_CMD_LOGGING_MAX; + entry = &ufs_cmd_log->entries[i]; + dev_err(hba->dev, "%2d: %10s: %2d %3d 0x%02x %9u %6d %16llu\n", + idx, + entry->str, entry->lun, entry->tag, + entry->cmd_id, entry->lba, + entry->transfer_len, entry->tstamp); + } +} + +static void ufs_sec_trigger_bug(struct ufs_hba *hba) +{ + ufs_sec_print_evt_hist(hba); + + ufs_sec_print_cmdlog(hba); + + BUG(); +} +#endif // CONFIG_SCSI_UFS_TEST_MODE +/* SEC test mode : end */ + +/* SEC error info : begin */ +inline bool ufs_sec_is_err_cnt_allowed(void) +{ + return ufs_sec_features.ufs_err && ufs_sec_features.ufs_err_backup; +} + +void ufs_sec_inc_hwrst_cnt(void) +{ + if (!ufs_sec_is_err_cnt_allowed()) + return; + + SEC_UFS_OP_ERR_CNT_INC(HW_RESET_cnt, UINT_MAX); + +#if IS_ENABLED(CONFIG_SEC_ABC) + { + struct SEC_UFS_op_cnt *op_cnt = &get_err_member(op_cnt); + + if ((op_cnt->HW_RESET_cnt % 3) == 0) + sec_abc_send_event("MODULE=storage@WARN=ufs_hwreset_err"); + } +#endif +} + +static void ufs_sec_inc_link_startup_error_cnt(void) +{ + if (!ufs_sec_is_err_cnt_allowed()) + return; + + SEC_UFS_OP_ERR_CNT_INC(link_startup_cnt, UINT_MAX); +} + +static void ufs_sec_inc_uic_cmd_error(u32 cmd) +{ + struct SEC_UFS_UIC_cmd_cnt *uiccmd_cnt = NULL; + + if (!ufs_sec_is_err_cnt_allowed()) + return; + + uiccmd_cnt = &get_err_member(UIC_cmd_cnt); + + switch (cmd & COMMAND_OPCODE_MASK) { + case UIC_CMD_DME_GET: + SEC_UFS_ERR_CNT_INC(uiccmd_cnt->DME_GET_err, U8_MAX); + break; + case UIC_CMD_DME_SET: + SEC_UFS_ERR_CNT_INC(uiccmd_cnt->DME_SET_err, U8_MAX); + break; + case UIC_CMD_DME_PEER_GET: + SEC_UFS_ERR_CNT_INC(uiccmd_cnt->DME_PEER_GET_err, U8_MAX); + break; + case UIC_CMD_DME_PEER_SET: + SEC_UFS_ERR_CNT_INC(uiccmd_cnt->DME_PEER_SET_err, U8_MAX); + break; + case UIC_CMD_DME_POWERON: + SEC_UFS_ERR_CNT_INC(uiccmd_cnt->DME_POWERON_err, U8_MAX); + break; + case UIC_CMD_DME_POWEROFF: + SEC_UFS_ERR_CNT_INC(uiccmd_cnt->DME_POWEROFF_err, U8_MAX); + break; + case UIC_CMD_DME_ENABLE: + SEC_UFS_ERR_CNT_INC(uiccmd_cnt->DME_ENABLE_err, U8_MAX); + break; + case UIC_CMD_DME_RESET: + SEC_UFS_ERR_CNT_INC(uiccmd_cnt->DME_RESET_err, U8_MAX); + break; + case UIC_CMD_DME_END_PT_RST: + SEC_UFS_ERR_CNT_INC(uiccmd_cnt->DME_END_PT_RST_err, U8_MAX); + break; + case UIC_CMD_DME_LINK_STARTUP: + SEC_UFS_ERR_CNT_INC(uiccmd_cnt->DME_LINK_STARTUP_err, U8_MAX); + break; + case UIC_CMD_DME_HIBER_ENTER: + SEC_UFS_ERR_CNT_INC(uiccmd_cnt->DME_HIBER_ENTER_err, U8_MAX); + SEC_UFS_OP_ERR_CNT_INC(Hibern8_enter_cnt, UINT_MAX); + break; + case UIC_CMD_DME_HIBER_EXIT: + SEC_UFS_ERR_CNT_INC(uiccmd_cnt->DME_HIBER_EXIT_err, U8_MAX); + SEC_UFS_OP_ERR_CNT_INC(Hibern8_exit_cnt, UINT_MAX); + break; + case UIC_CMD_DME_TEST_MODE: + SEC_UFS_ERR_CNT_INC(uiccmd_cnt->DME_TEST_MODE_err, U8_MAX); + break; + default: + break; + } + + SEC_UFS_ERR_CNT_INC(uiccmd_cnt->UIC_cmd_err, UINT_MAX); +} + +static void ufs_sec_inc_uic_fatal(u32 errors) +{ + struct SEC_UFS_Fatal_err_cnt *f_ec = &get_err_member(Fatal_err_cnt); + + if (!ufs_sec_is_err_cnt_allowed()) + return; + + if (errors & DEVICE_FATAL_ERROR) { + SEC_UFS_ERR_CNT_INC(f_ec->DFE, U8_MAX); + SEC_UFS_ERR_CNT_INC(f_ec->Fatal_err, UINT_MAX); + } + if (errors & CONTROLLER_FATAL_ERROR) { + SEC_UFS_ERR_CNT_INC(f_ec->CFE, U8_MAX); + SEC_UFS_ERR_CNT_INC(f_ec->Fatal_err, UINT_MAX); + } + if (errors & SYSTEM_BUS_FATAL_ERROR) { + SEC_UFS_ERR_CNT_INC(f_ec->SBFE, U8_MAX); + SEC_UFS_ERR_CNT_INC(f_ec->Fatal_err, UINT_MAX); + } + if (errors & CRYPTO_ENGINE_FATAL_ERROR) { + SEC_UFS_ERR_CNT_INC(f_ec->CEFE, U8_MAX); + SEC_UFS_ERR_CNT_INC(f_ec->Fatal_err, UINT_MAX); + } + /* UIC_LINK_LOST : can not be checked in ufshcd.c */ + if (errors & UIC_LINK_LOST) { + SEC_UFS_ERR_CNT_INC(f_ec->LLE, U8_MAX); + SEC_UFS_ERR_CNT_INC(f_ec->Fatal_err, UINT_MAX); + } +} + +static void ufs_sec_inc_uic_error(enum ufs_event_type evt, u32 reg) +{ + struct SEC_UFS_UIC_err_cnt *uicerr_cnt = &get_err_member(UIC_err_cnt); + unsigned int bit_count = 0; + int val = 0; + + if (!ufs_sec_is_err_cnt_allowed()) + return; + + switch (evt) { + case UFS_EVT_PA_ERR: + SEC_UFS_ERR_CNT_INC(uicerr_cnt->PAERR_cnt, U8_MAX); + SEC_UFS_ERR_CNT_INC(uicerr_cnt->UIC_err, UINT_MAX); + + if (reg & UIC_PHY_ADAPTER_LAYER_GENERIC_ERROR) + SEC_UFS_ERR_CNT_INC(uicerr_cnt->PAERR_linereset, + UINT_MAX); + + val = reg & UIC_PHY_ADAPTER_LAYER_LANE_ERR_MASK; + if (val) + SEC_UFS_ERR_CNT_INC(uicerr_cnt->PAERR_lane[val - 1], + UINT_MAX); + break; + case UFS_EVT_DL_ERR: + if (reg & UIC_DATA_LINK_LAYER_ERROR_PA_INIT) + SEC_UFS_ERR_CNT_INC(uicerr_cnt->DL_PA_INIT_ERR_cnt, U8_MAX); + if (reg & UIC_DATA_LINK_LAYER_ERROR_NAC_RECEIVED) + SEC_UFS_ERR_CNT_INC(uicerr_cnt->DL_NAC_RCVD_ERR_cnt, U8_MAX); + if (reg & UIC_DATA_LINK_LAYER_ERROR_TCx_REPLAY_TIMEOUT) + SEC_UFS_ERR_CNT_INC(uicerr_cnt->DL_TC_REPLAY_ERR_cnt, U8_MAX); + if (reg & UIC_DATA_LINK_LAYER_ERROR_FCX_PRO_TIMER_EXP) + SEC_UFS_ERR_CNT_INC(uicerr_cnt->DL_FC_PROTECT_ERR_cnt, U8_MAX); + + reg &= UIC_DATA_LINK_LAYER_ERROR_CODE_MASK; + + bit_count = __builtin_popcount(reg); + + SEC_UFS_ERR_CNT_ADD(uicerr_cnt->DLERR_cnt, bit_count, UINT_MAX); + SEC_UFS_ERR_CNT_ADD(uicerr_cnt->UIC_err, bit_count, UINT_MAX); + break; + case UFS_EVT_NL_ERR: + SEC_UFS_ERR_CNT_INC(uicerr_cnt->NLERR_cnt, U8_MAX); + SEC_UFS_ERR_CNT_INC(uicerr_cnt->UIC_err, UINT_MAX); + break; + case UFS_EVT_TL_ERR: + SEC_UFS_ERR_CNT_INC(uicerr_cnt->TLERR_cnt, U8_MAX); + SEC_UFS_ERR_CNT_INC(uicerr_cnt->UIC_err, UINT_MAX); + break; + case UFS_EVT_DME_ERR: + SEC_UFS_ERR_CNT_INC(uicerr_cnt->DMEERR_cnt, U8_MAX); + SEC_UFS_ERR_CNT_INC(uicerr_cnt->UIC_err, UINT_MAX); + break; + default: + break; + } +} + +static void ufs_sec_inc_tm_error(u8 tm_cmd) +{ + struct SEC_UFS_UTP_cnt *utp_err = &get_err_member(UTP_cnt); +#if !IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP) + struct ufs_vendor_dev_info *vdi = ufs_sec_features.vdi; + + if (vdi && (tm_cmd == UFS_LOGICAL_RESET)) + vdi->device_stuck = true; +#endif + + if (!ufs_sec_is_err_cnt_allowed()) + return; + + switch (tm_cmd) { + case UFS_QUERY_TASK: + SEC_UFS_ERR_CNT_INC(utp_err->UTMR_query_task_cnt, U8_MAX); + break; + case UFS_ABORT_TASK: + SEC_UFS_ERR_CNT_INC(utp_err->UTMR_abort_task_cnt, U8_MAX); + break; + case UFS_LOGICAL_RESET: + SEC_UFS_ERR_CNT_INC(utp_err->UTMR_logical_reset_cnt, U8_MAX); + break; + default: + break; + } + + SEC_UFS_ERR_CNT_INC(utp_err->UTP_err, UINT_MAX); +} + +static void ufs_sec_inc_utp_error(struct ufs_hba *hba, int tag) +{ + struct SEC_UFS_UTP_cnt *utp_err = &get_err_member(UTP_cnt); + struct ufshcd_lrb *lrbp = NULL; + int opcode = 0; + + if (!ufs_sec_is_err_cnt_allowed()) + return; + + if (tag >= hba->nutrs) + return; + + lrbp = &hba->lrb[tag]; + if (!lrbp || !lrbp->cmd || (lrbp->task_tag != tag)) + return; + + opcode = lrbp->cmd->cmnd[0]; + + switch (opcode) { + case WRITE_10: + SEC_UFS_ERR_CNT_INC(utp_err->UTR_write_err, U8_MAX); + break; + case READ_10: + case READ_16: + SEC_UFS_ERR_CNT_INC(utp_err->UTR_read_err, U8_MAX); + break; + case SYNCHRONIZE_CACHE: + SEC_UFS_ERR_CNT_INC(utp_err->UTR_sync_cache_err, U8_MAX); + break; + case UNMAP: + SEC_UFS_ERR_CNT_INC(utp_err->UTR_unmap_err, U8_MAX); + break; + default: + SEC_UFS_ERR_CNT_INC(utp_err->UTR_etc_err, U8_MAX); + break; + } + + SEC_UFS_ERR_CNT_INC(utp_err->UTP_err, UINT_MAX); +} + +static enum utp_ocs ufshcd_sec_get_tr_ocs(struct ufshcd_lrb *lrbp, + struct cq_entry *cqe) +{ + if (cqe) + return le32_to_cpu(cqe->status) & MASK_OCS; + + return le32_to_cpu(lrbp->utr_descriptor_ptr->header.dword_2) & MASK_OCS; +} + +static void ufs_sec_inc_query_error(struct ufs_hba *hba, + struct ufshcd_lrb *lrbp, bool timeout) +{ + struct SEC_UFS_QUERY_cnt *query_cnt = NULL; + struct ufs_query_req *request = &hba->dev_cmd.query.request; + struct ufs_hw_queue *hwq = NULL; + struct cq_entry *cqe = NULL; + enum query_opcode opcode = request->upiu_req.opcode; + enum dev_cmd_type cmd_type = hba->dev_cmd.type; + enum utp_ocs ocs; + + if (!ufs_sec_is_err_cnt_allowed()) + return; + + if (is_mcq_enabled(hba)) { + hwq = hba->dev_cmd_queue; + cqe = ufshcd_mcq_cur_cqe(hwq); + } + + ocs = ufshcd_sec_get_tr_ocs(lrbp, cqe); + + if (!timeout && (ocs == OCS_SUCCESS)) + return; + + /* get last query cmd information when timeout occurs */ + if (timeout) { + opcode = ufs_sec_features.last_qcmd; + cmd_type = ufs_sec_features.qcmd_type; + } + + query_cnt = &get_err_member(Query_cnt); + + if (cmd_type == DEV_CMD_TYPE_NOP) { + SEC_UFS_ERR_CNT_INC(query_cnt->NOP_err, U8_MAX); + } else { + switch (opcode) { + case UPIU_QUERY_OPCODE_READ_DESC: + SEC_UFS_ERR_CNT_INC(query_cnt->R_Desc_err, U8_MAX); + break; + case UPIU_QUERY_OPCODE_WRITE_DESC: + SEC_UFS_ERR_CNT_INC(query_cnt->W_Desc_err, U8_MAX); + break; + case UPIU_QUERY_OPCODE_READ_ATTR: + SEC_UFS_ERR_CNT_INC(query_cnt->R_Attr_err, U8_MAX); + break; + case UPIU_QUERY_OPCODE_WRITE_ATTR: + SEC_UFS_ERR_CNT_INC(query_cnt->W_Attr_err, U8_MAX); + break; + case UPIU_QUERY_OPCODE_READ_FLAG: + SEC_UFS_ERR_CNT_INC(query_cnt->R_Flag_err, U8_MAX); + break; + case UPIU_QUERY_OPCODE_SET_FLAG: + SEC_UFS_ERR_CNT_INC(query_cnt->Set_Flag_err, U8_MAX); + break; + case UPIU_QUERY_OPCODE_CLEAR_FLAG: + SEC_UFS_ERR_CNT_INC(query_cnt->Clear_Flag_err, U8_MAX); + break; + case UPIU_QUERY_OPCODE_TOGGLE_FLAG: + SEC_UFS_ERR_CNT_INC(query_cnt->Toggle_Flag_err, + U8_MAX); + break; + default: + break; + } + } + + SEC_UFS_ERR_CNT_INC(query_cnt->Query_err, UINT_MAX); +} + +void ufs_sec_inc_op_err(struct ufs_hba *hba, enum ufs_event_type evt, + void *data) +{ + u32 error_val = *(u32 *)data; + + switch (evt) { + case UFS_EVT_LINK_STARTUP_FAIL: + ufs_sec_inc_link_startup_error_cnt(); + break; + case UFS_EVT_DEV_RESET: + break; + case UFS_EVT_PA_ERR: + case UFS_EVT_DL_ERR: + case UFS_EVT_NL_ERR: + case UFS_EVT_TL_ERR: + case UFS_EVT_DME_ERR: + if (error_val) + ufs_sec_inc_uic_error(evt, error_val); + break; + case UFS_EVT_FATAL_ERR: + if (error_val) + ufs_sec_inc_uic_fatal(error_val); + break; + case UFS_EVT_ABORT: + ufs_sec_inc_utp_error(hba, (int)error_val); + break; + case UFS_EVT_HOST_RESET: + break; + case UFS_EVT_SUSPEND_ERR: + case UFS_EVT_RESUME_ERR: + break; + case UFS_EVT_AUTO_HIBERN8_ERR: + SEC_UFS_OP_ERR_CNT_INC(AH8_err_cnt, UINT_MAX); + break; + default: + break; + } + +#if IS_ENABLED(CONFIG_SCSI_UFS_TEST_MODE) +#define UFS_TEST_COUNT 3 + if (evt == UFS_EVT_PA_ERR || evt == UFS_EVT_DL_ERR || + evt == UFS_EVT_LINK_STARTUP_FAIL) { + struct ufs_event_hist *e = &hba->ufs_stats.event[evt]; + + if (e->cnt > UFS_TEST_COUNT) + ufs_sec_trigger_bug(hba); + } +#endif +} + +static void ufs_sec_inc_sense_err(struct ufshcd_lrb *lrbp, + struct ufs_sec_cmd_info *ufs_cmd) +{ + struct SEC_SCSI_SENSE_cnt *sense_err = NULL; + u8 sense_key = 0; + u8 asc = 0; + u8 ascq = 0; + bool secdbgMode = false; + + sense_key = lrbp->ucd_rsp_ptr->sr.sense_data[2] & 0x0F; + if (sense_key != MEDIUM_ERROR && sense_key != HARDWARE_ERROR) + return; + +#if IS_ENABLED(CONFIG_SEC_DEBUG) + secdbgMode = sec_debug_is_enabled(); +#endif + asc = lrbp->ucd_rsp_ptr->sr.sense_data[12]; + ascq = lrbp->ucd_rsp_ptr->sr.sense_data[13]; + + pr_err("UFS: LU%u: sense key 0x%x(asc 0x%x, ascq 0x%x)," + "opcode 0x%x, lba 0x%x, len 0x%x.\n", + ufs_cmd->lun, sense_key, asc, ascq, + ufs_cmd->opcode, ufs_cmd->lba, ufs_cmd->transfer_len); + + if (!ufs_sec_is_err_cnt_allowed()) + goto out; + + sense_err = &get_err_member(sense_cnt); + + if (sense_key == MEDIUM_ERROR) { + sense_err->scsi_medium_err++; +#if IS_ENABLED(CONFIG_SEC_ABC) + sec_abc_send_event("MODULE=storage@WARN=ufs_medium_err"); +#endif + } else { + sense_err->scsi_hw_err++; +#if IS_ENABLED(CONFIG_SEC_ABC) + sec_abc_send_event("MODULE=storage@WARN=ufs_hardware_err"); +#endif + } + +out: + if (secdbgMode) + panic("ufs %s error\n", (sense_key == MEDIUM_ERROR) ? + "medium" : "hardware"); +} + +static void ufs_sec_init_error_logging(struct device *dev) +{ + struct ufs_sec_err_info *ufs_err = NULL; + struct ufs_sec_err_info *ufs_err_backup = NULL; + struct ufs_sec_err_info *ufs_err_hist = NULL; + + ufs_err = devm_kzalloc(dev, sizeof(struct ufs_sec_err_info), + GFP_KERNEL); + ufs_err_backup = devm_kzalloc(dev, sizeof(struct ufs_sec_err_info), + GFP_KERNEL); + ufs_err_hist = devm_kzalloc(dev, sizeof(struct ufs_sec_err_info), + GFP_KERNEL); + if (!ufs_err || !ufs_err_backup || !ufs_err_hist) { + dev_err(dev, "%s: Failed allocating ufs_err(backup)(%lu)", + __func__, sizeof(struct ufs_sec_err_info)); + devm_kfree(dev, ufs_err); + devm_kfree(dev, ufs_err_backup); + devm_kfree(dev, ufs_err_hist); + return; + } + + ufs_sec_features.ufs_err = ufs_err; + ufs_sec_features.ufs_err_backup = ufs_err_backup; + ufs_sec_features.ufs_err_hist = ufs_err_hist; + + ufs_sec_features.ucmd_complete = true; + ufs_sec_features.qcmd_complete = true; +} +/* SEC error info : end */ + +void ufs_sec_check_device_stuck(void) +{ +#if IS_ENABLED(CONFIG_SCSI_UFS_TEST_MODE) + struct ufs_vendor_dev_info *vdi = ufs_sec_features.vdi; + + if (!vdi) + return; + + /* + * do not recover system if test mode is enabled + * reset recovery is in progress from ufshcd_err_handler + */ + if (vdi->hba && vdi->hba->eh_flags) + ufs_sec_trigger_bug(vdi->hba); +#endif +} + +/* SEC cmd log : begin */ +static void __ufs_sec_log_cmd(struct ufs_hba *hba, int str_idx, + unsigned int tag, u8 cmd_id, u8 idn, u8 lun, u32 lba, + int transfer_len) +{ + struct ufs_sec_cmd_log_info *ufs_cmd_log = + ufs_sec_features.ufs_cmd_log; + struct ufs_sec_cmd_log_entry *entry = + &ufs_cmd_log->entries[ufs_cmd_log->pos]; + + int cpu = raw_smp_processor_id(); + + entry->lun = lun; + entry->str = ufs_sec_log_str[str_idx]; + entry->cmd_id = cmd_id; + entry->lba = lba; + entry->transfer_len = transfer_len; + entry->idn = idn; + entry->tag = tag; + entry->tstamp = cpu_clock(cpu); + entry->outstanding_reqs = hba->outstanding_reqs; + ufs_cmd_log->pos = + (ufs_cmd_log->pos + 1) % UFS_SEC_CMD_LOGGING_MAX; +} + +static void ufs_sec_log_cmd(struct ufs_hba *hba, struct ufshcd_lrb *lrbp, + int str_t, struct ufs_sec_cmd_info *ufs_cmd, u8 cmd_id) +{ + u8 opcode = 0; + u8 idn = 0; + + if (!ufs_sec_features.ufs_cmd_log) + return; + + switch (str_t) { + case UFS_SEC_CMD_SEND: + case UFS_SEC_CMD_COMP: + __ufs_sec_log_cmd(hba, str_t, lrbp->task_tag, ufs_cmd->opcode, + 0, lrbp->lun, ufs_cmd->lba, + ufs_cmd->transfer_len); + break; + case UFS_SEC_QUERY_SEND: + case UFS_SEC_QUERY_COMP: + opcode = hba->dev_cmd.query.request.upiu_req.opcode; + idn = hba->dev_cmd.query.request.upiu_req.idn; + __ufs_sec_log_cmd(hba, str_t, lrbp->task_tag, opcode, + idn, lrbp->lun, 0, 0); + break; + case UFS_SEC_NOP_SEND: + case UFS_SEC_NOP_COMP: + __ufs_sec_log_cmd(hba, str_t, lrbp->task_tag, 0, + 0, lrbp->lun, 0, 0); + break; + case UFS_SEC_TM_SEND: + case UFS_SEC_TM_COMP: + case UFS_SEC_TM_ERR: + case UFS_SEC_UIC_SEND: + case UFS_SEC_UIC_COMP: + __ufs_sec_log_cmd(hba, str_t, 0, cmd_id, 0, 0, 0, 0); + break; + default: + break; + } +} + +static void ufs_sec_init_cmd_logging(struct device *dev) +{ + struct ufs_sec_cmd_log_info *ufs_cmd_log = NULL; + + ufs_cmd_log = devm_kzalloc(dev, sizeof(struct ufs_sec_cmd_log_info), + GFP_KERNEL); + if (!ufs_cmd_log) { + dev_err(dev, "%s: Failed allocating ufs_cmd_log(%lu)", + __func__, + sizeof(struct ufs_sec_cmd_log_info)); + return; + } + + ufs_cmd_log->entries = devm_kcalloc(dev, UFS_SEC_CMD_LOGGING_MAX, + sizeof(struct ufs_sec_cmd_log_entry), GFP_KERNEL); + if (!ufs_cmd_log->entries) { + dev_err(dev, "%s: Failed allocating cmd log entry(%lu)", + __func__, + sizeof(struct ufs_sec_cmd_log_entry) + * UFS_SEC_CMD_LOGGING_MAX); + devm_kfree(dev, ufs_cmd_log); + return; + } + + pr_info("SEC UFS cmd logging is initialized.\n"); + + ufs_sec_features.ufs_cmd_log = ufs_cmd_log; +} +/* SEC cmd log : end */ + +/* panic notifier : begin */ +static void ufs_sec_print_evt(struct ufs_hba *hba, u32 id, + char *err_name) +{ + int i; + bool found = false; + struct ufs_event_hist *e; + + if (id >= UFS_EVT_CNT) + return; + + e = &hba->ufs_stats.event[id]; + + for (i = 0; i < UFS_EVENT_HIST_LENGTH; i++) { + int p = (i + e->pos) % UFS_EVENT_HIST_LENGTH; + + // do not print if dev_reset[0]'s tstamp is in 2 sec. + // because that happened on booting seq. + if ((id == UFS_EVT_DEV_RESET) && (p == 0) && + (ktime_to_us(e->tstamp[p]) < 2000000)) + continue; + + if (e->tstamp[p] == 0) + continue; + + dev_err(hba->dev, "%s[%d] = 0x%x at %lld us\n", err_name, p, + e->val[p], ktime_to_us(e->tstamp[p])); + found = true; + } + + if (!found) + dev_err(hba->dev, "No record of %s\n", err_name); + else + dev_err(hba->dev, "%s: total cnt=%llu\n", err_name, e->cnt); +} + +static void ufs_sec_print_evt_hist(struct ufs_hba *hba) +{ + ufshcd_dump_regs(hba, 0, UFSHCI_REG_SPACE_SIZE, "host_regs: "); + + ufs_sec_print_evt(hba, UFS_EVT_PA_ERR, "pa_err"); + ufs_sec_print_evt(hba, UFS_EVT_DL_ERR, "dl_err"); + ufs_sec_print_evt(hba, UFS_EVT_NL_ERR, "nl_err"); + ufs_sec_print_evt(hba, UFS_EVT_TL_ERR, "tl_err"); + ufs_sec_print_evt(hba, UFS_EVT_DME_ERR, "dme_err"); + ufs_sec_print_evt(hba, UFS_EVT_AUTO_HIBERN8_ERR, + "auto_hibern8_err"); + ufs_sec_print_evt(hba, UFS_EVT_FATAL_ERR, "fatal_err"); + ufs_sec_print_evt(hba, UFS_EVT_LINK_STARTUP_FAIL, + "link_startup_fail"); + ufs_sec_print_evt(hba, UFS_EVT_RESUME_ERR, "resume_fail"); + ufs_sec_print_evt(hba, UFS_EVT_SUSPEND_ERR, + "suspend_fail"); + ufs_sec_print_evt(hba, UFS_EVT_DEV_RESET, "dev_reset"); + ufs_sec_print_evt(hba, UFS_EVT_HOST_RESET, "host_reset"); + ufs_sec_print_evt(hba, UFS_EVT_ABORT, "task_abort"); +} + +/* + * ufs_sec_check_and_is_err - Check and compare UFS Error counts + * @buf: has to be filled by using SEC_UFS_ERR_SUM() or SEC_UFS_ERR_HIST_SUM() + * + * Returns + * 0 - If the buf has no error counts + * other - If the buf is invalid or has error count value + */ +static int ufs_sec_check_and_is_err(char *buf) +{ + const char *no_err = "U0I0H0L0X0Q0R0W0F0SM0SH0"; + + if (!buf || strlen(buf) < strlen(no_err)) + return -EINVAL; + + return strncmp(buf, no_err, strlen(no_err)); +} + +/** + * If there is a UFS error, it should be printed out. + * Print UFS Error info (previous boot's error & current boot's error) + * + * Format : U0I0H0L0X0Q0R0W0F0SM0SH0(HB0) + * U : UTP cmd error count + * I : UIC error count + * H : HWRESET count + * L : Link startup failure count + * X : Link Lost Error count + * Q : UTMR QUERY_TASK error count + * R : READ error count + * W : WRITE error count + * F : Device Fatal Error count + * SM : Sense Medium error count + * SH : Sense Hardware error count + * HB : Hibern8 enter/exit error count + Auto-H8 error count + **/ +void ufs_sec_print_err(void) +{ + char err_buf[ERR_SUM_SIZE]; + char hist_buf[ERR_HIST_SUM_SIZE]; + unsigned int HB_value = + SEC_UFS_ERR_INFO_GET_VALUE(op_cnt, Hibern8_enter_cnt) + + SEC_UFS_ERR_INFO_GET_VALUE(op_cnt, Hibern8_exit_cnt) + + SEC_UFS_ERR_INFO_GET_VALUE(op_cnt, AH8_err_cnt); + + SEC_UFS_ERR_SUM(err_buf); + SEC_UFS_ERR_HIST_SUM(hist_buf); + + if (ufs_sec_check_and_is_err(hist_buf)) + pr_info("ufs: %sHB%u hist: %s", + err_buf, (HB_value > 9) ? 9 : HB_value, hist_buf); +} + +/** + * ufs_sec_panic_callback - Print UFS Error summary when panic + * + * If FS panic occurs, + * print the UFS event history additionally + **/ +static int ufs_sec_panic_callback(struct notifier_block *nfb, + unsigned long event, void *panic_msg) +{ + struct ufs_vendor_dev_info *vdi = ufs_sec_features.vdi; + char *str = (char *)panic_msg; + bool is_FSpanic = !strncmp(str, "F2FS", 4) || !strncmp(str, "EXT4", 4); + char err_buf[ERR_SUM_SIZE]; + + ufs_sec_print_err(); + + SEC_UFS_ERR_SUM(err_buf); + + if (vdi && is_FSpanic && ufs_sec_check_and_is_err(err_buf)) + ufs_sec_print_evt_hist(vdi->hba); + + return NOTIFY_OK; +} + +static struct notifier_block ufs_sec_panic_notifier = { + .notifier_call = ufs_sec_panic_callback, + .priority = 1, +}; +/* panic notifier : end */ + +/* reboot notifier : begin */ +static int ufs_sec_reboot_notify(struct notifier_block *notify_block, + unsigned long event, void *unused) +{ + ufs_sec_print_err(); + + return NOTIFY_OK; +} +/* reboot notifier : end */ + +/* I/O error uevent : begin */ +static void ufs_sec_err_noti_work(struct work_struct *work) +{ + int ret; + + ufs_sec_print_err(); + + ret = kobject_uevent(&sec_ufs_node_dev->kobj, KOBJ_CHANGE); + if (ret) + pr_err("%s: Failed to send uevent with err %d\n", __func__, ret); +} + +static int ufs_sec_err_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + char buf[ERR_SUM_SIZE]; + + add_uevent_var(env, "DEVNAME=%s", dev->kobj.name); + add_uevent_var(env, "NAME=UFSINFO"); + + SEC_UFS_ERR_SUM(buf); + return add_uevent_var(env, "DATA=%s", buf); +} + +static struct device_type ufs_type = { + .uevent = ufs_sec_err_uevent, +}; + +static inline bool ufs_sec_is_uevent_condition(u32 hw_rst_cnt) +{ + return ((hw_rst_cnt == 1) || !(hw_rst_cnt % 10)); +} + +static void ufs_sec_trigger_err_noti_uevent(struct ufs_hba *hba) +{ + u32 hw_rst_cnt = SEC_UFS_ERR_INFO_GET_VALUE(op_cnt, HW_RESET_cnt); + char buf[ERR_SUM_SIZE]; + + /* eh_flags is only set during error handling */ + if (!hba->eh_flags) + return; + + SEC_UFS_ERR_SUM(buf); + + if (!ufs_sec_check_and_is_err(buf)) + return; + + if (!ufs_sec_is_uevent_condition(hw_rst_cnt)) + return; + + if (sec_ufs_node_dev) + schedule_delayed_work(&ufs_sec_features.noti_work, + msecs_to_jiffies(NOTI_WORK_DELAY_MS)); +} +/* I/O error uevent : end */ + +void ufs_sec_config_features(struct ufs_hba *hba) +{ + ufs_sec_wb_config(hba); + ufs_sec_trigger_err_noti_uevent(hba); +} + +void ufs_sec_adjust_caps_quirks(struct ufs_hba *hba) +{ + hba->caps &= ~UFSHCD_CAP_WB_EN; + hba->caps &= ~UFSHCD_CAP_WB_WITH_CLK_SCALING; +} + +void ufs_sec_init_logging(struct device *dev) +{ + ufs_sec_init_error_logging(dev); + + ufs_sec_init_cmd_logging(dev); +} + +void ufs_sec_set_features(struct ufs_hba *hba) +{ + struct ufs_vendor_dev_info *vdi = NULL; + u8 *desc_buf = NULL; + int err; + + if (ufs_sec_features.vdi) + return; + + vdi = devm_kzalloc(hba->dev, sizeof(struct ufs_vendor_dev_info), + GFP_KERNEL); + if (!vdi) { + dev_err(hba->dev, "%s: Failed allocating ufs_vdi(%lu)", + __func__, sizeof(struct ufs_vendor_dev_info)); + return; + } + + vdi->hba = hba; + + ufs_sec_features.vdi = vdi; + + desc_buf = kzalloc(QUERY_DESC_MAX_SIZE, GFP_KERNEL); + if (!desc_buf) + return; + + err = ufshcd_read_desc_param(hba, + QUERY_DESC_IDN_DEVICE, 0, 0, + desc_buf, QUERY_DESC_MAX_SIZE); + if (err) { + dev_err(hba->dev, "%s: Failed reading device desc. err %d", + __func__, err); + goto out; + } + + ufs_sec_set_unique_number(hba, desc_buf); + ufs_sec_get_health_desc(hba); + ufs_sec_get_ext_feature(hba, desc_buf); + + ufs_sec_wb_probe(hba, desc_buf); + + ufs_sec_add_sysfs_nodes(hba); + + atomic_notifier_chain_register(&panic_notifier_list, + &ufs_sec_panic_notifier); + + ufs_sec_features.reboot_notify.notifier_call = ufs_sec_reboot_notify; + register_reboot_notifier(&ufs_sec_features.reboot_notify); + sec_ufs_node_dev->type = &ufs_type; + INIT_DELAYED_WORK(&ufs_sec_features.noti_work, ufs_sec_err_noti_work); + +out: +#if IS_ENABLED(CONFIG_SCSI_UFS_TEST_MODE) + dev_info(hba->dev, "UFS test mode enabled\n"); +#endif + + kfree(desc_buf); +} + +void ufs_sec_remove_features(struct ufs_hba *hba) +{ + ufs_sec_remove_sysfs_nodes(hba); + unregister_reboot_notifier(&ufs_sec_features.reboot_notify); +} + +static bool ufs_sec_get_scsi_cmd_info(struct ufshcd_lrb *lrbp, + struct ufs_sec_cmd_info *ufs_cmd) +{ + struct scsi_cmnd *cmd; + + if (!lrbp || !lrbp->cmd || !ufs_cmd) + return false; + + cmd = lrbp->cmd; + + ufs_cmd->opcode = (u8)(*cmd->cmnd); + ufs_cmd->lba = ((cmd->cmnd[2] << 24) | (cmd->cmnd[3] << 16) | + (cmd->cmnd[4] << 8) | cmd->cmnd[5]); + ufs_cmd->transfer_len = (cmd->cmnd[7] << 8) | cmd->cmnd[8]; + ufs_cmd->lun = ufshcd_scsi_to_upiu_lun(cmd->device->lun); + + return true; +} + +static void ufs_sec_customize_upiu_flags(struct ufshcd_lrb *lrbp) +{ + u8 upiu_flags = 0x0; + struct request *rq; + + if (!lrbp->cmd || !lrbp->ucd_req_ptr) + return; + + rq = scsi_cmd_to_rq(lrbp->cmd); + switch (req_op(rq)) { + case REQ_OP_READ: + upiu_flags |= UPIU_CMD_PRIO_HIGH; + break; + case REQ_OP_WRITE: + if (rq->cmd_flags & REQ_SYNC) + upiu_flags |= UPIU_CMD_PRIO_HIGH; + break; + case REQ_OP_FLUSH: + upiu_flags |= UPIU_TASK_ATTR_HEADQ; + break; + case REQ_OP_DISCARD: + upiu_flags |= UPIU_TASK_ATTR_ORDERED; + break; + default: + break; + } + + lrbp->ucd_req_ptr->header.dword_0 |= + UPIU_HEADER_DWORD(0, upiu_flags, 0, 0); +} + +static void sec_android_vh_ufs_send_command(void *data, + struct ufs_hba *hba, struct ufshcd_lrb *lrbp) +{ + struct ufs_sec_cmd_info ufs_cmd = { 0, }; + struct ufs_query_req *request = NULL; + enum dev_cmd_type cmd_type; + enum query_opcode opcode; + bool is_scsi_cmd = false; + + is_scsi_cmd = ufs_sec_get_scsi_cmd_info(lrbp, &ufs_cmd); + + if (is_scsi_cmd) { + ufs_sec_customize_upiu_flags(lrbp); + ufs_sec_log_cmd(hba, lrbp, UFS_SEC_CMD_SEND, &ufs_cmd, 0); + } else { + if (hba->dev_cmd.type == DEV_CMD_TYPE_NOP) + ufs_sec_log_cmd(hba, lrbp, UFS_SEC_NOP_SEND, NULL, 0); + else if (hba->dev_cmd.type == DEV_CMD_TYPE_QUERY) + ufs_sec_log_cmd(hba, lrbp, UFS_SEC_QUERY_SEND, NULL, 0); + + /* in timeout error case, last cmd is not completed */ + if (!ufs_sec_features.qcmd_complete) + ufs_sec_inc_query_error(hba, lrbp, true); + + request = &hba->dev_cmd.query.request; + opcode = request->upiu_req.opcode; + cmd_type = hba->dev_cmd.type; + + ufs_sec_features.last_qcmd = opcode; + ufs_sec_features.qcmd_type = cmd_type; + ufs_sec_features.qcmd_complete = false; + } +} + +static void sec_android_vh_ufs_compl_command(void *data, + struct ufs_hba *hba, struct ufshcd_lrb *lrbp) +{ + struct ufs_sec_cmd_info ufs_cmd = { 0, }; + bool is_scsi_cmd = false; + int transfer_len = 0; + + is_scsi_cmd = ufs_sec_get_scsi_cmd_info(lrbp, &ufs_cmd); + + if (is_scsi_cmd) { + ufs_sec_log_cmd(hba, lrbp, UFS_SEC_CMD_COMP, &ufs_cmd, 0); + ufs_sec_inc_sense_err(lrbp, &ufs_cmd); + + /* + * check hba->req_abort_count, if the cmd is aborting + * it's the one way to check aborting + * hba->req_abort_count is cleared in queuecommand and after + * error handling + */ + if (hba->req_abort_count > 0) + ufs_sec_inc_utp_error(hba, lrbp->task_tag); + + if (hba->dev_info.wb_enabled == WB_ON + && ufs_cmd.opcode == WRITE_10) { + transfer_len = be32_to_cpu(lrbp->ucd_req_ptr->sc.exp_data_transfer_len); + ufs_sec_wb_update_info(hba, transfer_len); + } + } else { + if (hba->dev_cmd.type == DEV_CMD_TYPE_NOP) + ufs_sec_log_cmd(hba, lrbp, UFS_SEC_NOP_COMP, NULL, 0); + else if (hba->dev_cmd.type == DEV_CMD_TYPE_QUERY) + ufs_sec_log_cmd(hba, lrbp, UFS_SEC_QUERY_COMP, NULL, 0); + + ufs_sec_features.qcmd_complete = true; + + /* check and count error, except timeout */ + ufs_sec_inc_query_error(hba, lrbp, false); + } +} + +static void sec_android_vh_ufs_send_uic_command(void *data, + struct ufs_hba *hba, const struct uic_command *ucmd, int str_t) +{ + u32 cmd; + u8 cmd_id; + + if (str_t == UFS_CMD_SEND) { + /* in timeout error case, last cmd is not completed */ + if (!ufs_sec_features.ucmd_complete) + ufs_sec_inc_uic_cmd_error(ufs_sec_features.last_ucmd); + + cmd = ucmd->command; + ufs_sec_features.last_ucmd = cmd; + ufs_sec_features.ucmd_complete = false; + + cmd_id = (u8)(cmd & COMMAND_OPCODE_MASK); + ufs_sec_log_cmd(hba, NULL, UFS_SEC_UIC_SEND, NULL, cmd_id); + } else { + cmd = ufshcd_readl(hba, REG_UIC_COMMAND); + + ufs_sec_features.ucmd_complete = true; + + if (((hba->active_uic_cmd->argument2 & MASK_UIC_COMMAND_RESULT) + != UIC_CMD_RESULT_SUCCESS) || + (str_t == UFS_CMD_ERR)) + ufs_sec_inc_uic_cmd_error(cmd); + + cmd_id = (u8)(cmd & COMMAND_OPCODE_MASK); + ufs_sec_log_cmd(hba, NULL, UFS_SEC_UIC_COMP, NULL, cmd_id); + } +} + +static void sec_android_vh_ufs_send_tm_command(void *data, + struct ufs_hba *hba, int tag, int str_t) +{ + struct utp_task_req_desc treq = { { 0 }, }; + u8 tm_func = 0; + int sec_log_str_t = 0; + + memcpy(&treq, hba->utmrdl_base_addr + tag, sizeof(treq)); + + tm_func = (be32_to_cpu(treq.upiu_req.req_header.dword_1) >> 16) & 0xFF; + + if (str_t == UFS_TM_SEND) + sec_log_str_t = UFS_SEC_TM_SEND; + else if (str_t == UFS_TM_COMP) + sec_log_str_t = UFS_SEC_TM_COMP; + else if (str_t == UFS_TM_ERR) { + sec_log_str_t = UFS_SEC_TM_ERR; + ufs_sec_inc_tm_error(tm_func); + } else { + dev_err(hba->dev, "%s: undefined ufs tm cmd\n", __func__); + return; + } + + ufs_sec_log_cmd(hba, NULL, sec_log_str_t, NULL, tm_func); + +#if IS_ENABLED(CONFIG_SCSI_UFS_TEST_MODE) + if (str_t == UFS_TM_COMP && !hba->eh_flags) { + dev_err(hba->dev, + "%s: ufs tm cmd is succeeded and forced BUG called\n", __func__); + ssleep(2); + ufs_sec_trigger_bug(hba); + } +#endif +} + +static void sec_android_vh_ufs_update_sdev(void *data, struct scsi_device *sdev) +{ + blk_queue_rq_timeout(sdev->request_queue, SCSI_UFS_TIMEOUT); +} + +void ufs_sec_register_vendor_hooks(void) +{ + register_trace_android_vh_ufs_send_command(sec_android_vh_ufs_send_command, NULL); + register_trace_android_vh_ufs_compl_command(sec_android_vh_ufs_compl_command, NULL); + register_trace_android_vh_ufs_send_uic_command(sec_android_vh_ufs_send_uic_command, NULL); + register_trace_android_vh_ufs_send_tm_command(sec_android_vh_ufs_send_tm_command, NULL); + register_trace_android_vh_ufs_update_sdev(sec_android_vh_ufs_update_sdev, NULL); +} + diff --git a/drivers/ufs/host/ufs-sec-feature.h b/drivers/ufs/host/ufs-sec-feature.h new file mode 100644 index 000000000000..02f2c91dadbc --- /dev/null +++ b/drivers/ufs/host/ufs-sec-feature.h @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Samsung Specific feature + * + * Copyright (C) 2023 Samsung Electronics Co., Ltd. + * + * Authors: + * Storage Driver + */ + +#ifndef __UFS_SEC_FEATURE_H__ +#define __UFS_SEC_FEATURE_H__ + +#include "../core/ufshcd-priv.h" +#include +#include +#include + +#if IS_ENABLED(CONFIG_SEC_ABC) +#include +#endif + +/*unique number*/ +#define UFS_UN_20_DIGITS 20 +#define UFS_UN_MAX_DIGITS (UFS_UN_20_DIGITS + 1) + +#define SERIAL_NUM_SIZE 7 + +#define SCSI_UFS_TIMEOUT (10 * HZ) + +#define HEALTH_DESC_PARAM_VENDOR_LIFE_TIME_EST 0x22 + +struct ufs_vendor_dev_info { + struct ufs_hba *hba; + char unique_number[UFS_UN_MAX_DIGITS]; + u8 lt; + u8 flt; + u8 eli; + unsigned int ic; + char s_info[512]; + char shi[256]; + bool device_stuck; +}; + +struct ufs_sec_cmd_info { + u8 opcode; + u32 lba; + int transfer_len; + u8 lun; +}; + +enum ufs_sec_wb_state { + WB_OFF = 0, + WB_ON +}; + +struct ufs_sec_wb_info { + bool support; + u64 state_ts; + u64 enable_ms; + u64 disable_ms; + u64 amount_kb; + u64 enable_cnt; + u64 disable_cnt; + u64 err_cnt; +}; + +enum ufs_sec_log_str_t { + UFS_SEC_CMD_SEND, + UFS_SEC_CMD_COMP, + UFS_SEC_QUERY_SEND, + UFS_SEC_QUERY_COMP, + UFS_SEC_NOP_SEND, + UFS_SEC_NOP_COMP, + UFS_SEC_TM_SEND, + UFS_SEC_TM_COMP, + UFS_SEC_TM_ERR, + UFS_SEC_UIC_SEND, + UFS_SEC_UIC_COMP, +}; + +static const char * const ufs_sec_log_str[] = { + [UFS_SEC_CMD_SEND] = "scsi_send", + [UFS_SEC_CMD_COMP] = "scsi_cmpl", + [UFS_SEC_QUERY_SEND] = "query_send", + [UFS_SEC_QUERY_COMP] = "query_cmpl", + [UFS_SEC_NOP_SEND] = "nop_send", + [UFS_SEC_NOP_COMP] = "nop_cmpl", + [UFS_SEC_TM_SEND] = "tm_send", + [UFS_SEC_TM_COMP] = "tm_cmpl", + [UFS_SEC_TM_ERR] = "tm_err", + [UFS_SEC_UIC_SEND] = "uic_send", + [UFS_SEC_UIC_COMP] = "uic_cmpl", +}; + +struct ufs_sec_cmd_log_entry { + const char *str; /* ufs_sec_log_str */ + u8 lun; + u8 cmd_id; + u32 lba; + int transfer_len; + u8 idn; /* used only for query idn */ + unsigned long outstanding_reqs; + unsigned int tag; + u64 tstamp; +}; + +#define UFS_SEC_CMD_LOGGING_MAX 200 +#define UFS_SEC_CMD_LOGNODE_MAX 64 +struct ufs_sec_cmd_log_info { + struct ufs_sec_cmd_log_entry *entries; + int pos; +}; + +struct ufs_sec_feature_info { + struct ufs_vendor_dev_info *vdi; + struct ufs_sec_wb_info *ufs_wb; + struct ufs_sec_wb_info *ufs_wb_backup; + struct ufs_sec_err_info *ufs_err; + struct ufs_sec_err_info *ufs_err_backup; + struct ufs_sec_err_info *ufs_err_hist; + struct ufs_sec_cmd_log_info *ufs_cmd_log; + + struct notifier_block reboot_notify; + struct delayed_work noti_work; + + u32 ext_ufs_feature_sup; + + u32 last_ucmd; + bool ucmd_complete; + + enum query_opcode last_qcmd; + enum dev_cmd_type qcmd_type; + bool qcmd_complete; +}; + +extern struct device *sec_ufs_node_dev; + +/* call by vendor module */ +void ufs_sec_config_features(struct ufs_hba *hba); +void ufs_sec_adjust_caps_quirks(struct ufs_hba *hba); +void ufs_sec_init_logging(struct device *dev); +void ufs_sec_set_features(struct ufs_hba *hba); +void ufs_sec_remove_features(struct ufs_hba *hba); +void ufs_sec_register_vendor_hooks(void); + +void ufs_sec_get_health_desc(struct ufs_hba *hba); + +bool ufs_sec_is_wb_supported(void); +int ufs_sec_wb_ctrl(bool enable); +void ufs_sec_wb_register_reset_notify(void *func); + +inline bool ufs_sec_is_err_cnt_allowed(void); +void ufs_sec_inc_hwrst_cnt(void); +void ufs_sec_inc_op_err(struct ufs_hba *hba, enum ufs_event_type evt, void *data); +void ufs_sec_check_device_stuck(void); +void ufs_sec_print_err(void); +#endif diff --git a/drivers/ufs/host/ufs-sec-sysfs.c b/drivers/ufs/host/ufs-sec-sysfs.c new file mode 100644 index 000000000000..49f27d68ebe7 --- /dev/null +++ b/drivers/ufs/host/ufs-sec-sysfs.c @@ -0,0 +1,821 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Samsung Specific feature : sysfs-nodes + * + * Copyright (C) 2023 Samsung Electronics Co., Ltd. + * + * Authors: + * Storage Driver + */ + +#include "ufs-sec-sysfs.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "ufs-qcom.h" + +/* sec specific vendor sysfs nodes */ +struct device *sec_ufs_node_dev; + +/* SEC next WB : begin */ +static void ufs_sec_wb_info_backup(struct ufs_sec_wb_info *backup) +{ + SEC_UFS_WB_INFO_BACKUP(enable_cnt); + SEC_UFS_WB_INFO_BACKUP(disable_cnt); + SEC_UFS_WB_INFO_BACKUP(amount_kb); + SEC_UFS_WB_INFO_BACKUP(err_cnt); + + backup->state_ts = jiffies; +} + +static ssize_t ufs_sec_wb_info_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ufs_sec_wb_info *wb_info_backup = ufs_sec_features.ufs_wb_backup; + struct ufs_sec_wb_info *wb_info = ufs_sec_features.ufs_wb; + long hours = 0; + int len = 0; + + wb_info->state_ts = jiffies; + hours = jiffies_to_msecs(wb_info->state_ts - wb_info_backup->state_ts) / 1000; /* sec */ + hours = (hours + 60) / (60 * 60); /* round up to hours */ + + len = sprintf(buf, "\"TWCTRLCNT\":\"%llu\"," + "\"TWCTRLERRCNT\":\"%llu\"," + "\"TWDAILYMB\":\"%llu\"," + "\"TWTOTALMB\":\"%llu\"," + "\"TWhours\":\"%ld\"\n", + (wb_info->enable_cnt + wb_info->disable_cnt), + wb_info->err_cnt, /* total error count */ + (wb_info->amount_kb >> 10), /* WB write daily : MB */ + (wb_info_backup->amount_kb >> 10), /* WB write total : MB */ + hours); + + ufs_sec_wb_info_backup(wb_info_backup); + return len; +} +static DEVICE_ATTR(SEC_UFS_TW_info, 0444, ufs_sec_wb_info_show, NULL); + +/* SEC next WB : end */ + +/* UFS info nodes : begin */ +static ssize_t ufs_sec_unique_number_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", get_vdi_member(unique_number)); +} +static DEVICE_ATTR(un, 0440, ufs_sec_unique_number_show, NULL); + +static ssize_t ufs_sec_lt_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ufs_hba *hba = get_vdi_member(hba); + + if (!hba) { + dev_err(dev, "skipping ufs lt read\n"); + get_vdi_member(lt) = 0; + } else if (hba->ufshcd_state == UFSHCD_STATE_OPERATIONAL) { + ufshcd_rpm_get_sync(hba); + ufs_sec_get_health_desc(hba); + ufshcd_rpm_put(hba); + } else { + /* return previous LT value if not operational */ + dev_info(hba->dev, "ufshcd_state: %d, old LT: %01x\n", + hba->ufshcd_state, get_vdi_member(lt)); + } + + return snprintf(buf, PAGE_SIZE, "%01x\n", get_vdi_member(lt)); +} +static DEVICE_ATTR(lt, 0444, ufs_sec_lt_show, NULL); + +static ssize_t ufs_sec_flt_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ufs_hba *hba = get_vdi_member(hba); + + if (!hba) { + dev_err(dev, "skipping ufs flt read\n"); + get_vdi_member(flt) = 0; + } else if (hba->ufshcd_state == UFSHCD_STATE_OPERATIONAL) { + ufshcd_rpm_get_sync(hba); + ufs_sec_get_health_desc(hba); + ufshcd_rpm_put(hba); + } else { + /* return previous FLT value if not operational */ + dev_info(hba->dev, "ufshcd_state : %d, old FLT: %u\n", + hba->ufshcd_state, get_vdi_member(flt)); + } + + return snprintf(buf, PAGE_SIZE, "%u\n", get_vdi_member(flt)); +} +static DEVICE_ATTR(flt, 0444, ufs_sec_flt_show, NULL); + +static ssize_t ufs_sec_eli_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ufs_hba *hba = get_vdi_member(hba); + + if (!hba) { + dev_err(dev, "skipping ufs eli read\n"); + get_vdi_member(eli) = 0; + } else if (hba->ufshcd_state == UFSHCD_STATE_OPERATIONAL) { + ufshcd_rpm_get_sync(hba); + ufs_sec_get_health_desc(hba); + ufshcd_rpm_put(hba); + } else { + /* return previous ELI value if not operational */ + dev_info(hba->dev, "ufshcd_state: %d, old eli: %01x\n", + hba->ufshcd_state, get_vdi_member(eli)); + } + + return sprintf(buf, "%u\n", get_vdi_member(eli)); +} +static DEVICE_ATTR(eli, 0444, ufs_sec_eli_show, NULL); + +static ssize_t ufs_sec_ic_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%u\n", get_vdi_member(ic)); +} + +static ssize_t ufs_sec_ic_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int value; + + if (kstrtou32(buf, 0, &value)) + return -EINVAL; + + get_vdi_member(ic) = value; + + return count; +} +static DEVICE_ATTR(ic, 0664, ufs_sec_ic_show, ufs_sec_ic_store); + +static ssize_t ufs_sec_shi_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%s\n", get_vdi_member(shi)); +} + +static ssize_t ufs_sec_shi_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int ret; + char shi_buf[256] = {0, }; + + ret = sscanf(buf, "%255[^\n]%*c", shi_buf); + + if (ret != 1) + return -EINVAL; + + snprintf(get_vdi_member(shi), 256, "%s", shi_buf); + + return count; +} +static DEVICE_ATTR(shi, 0664, ufs_sec_shi_show, ufs_sec_shi_store); + +static ssize_t ufs_sec_hist_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return SEC_UFS_ERR_HIST_SUM(buf); +} + +static bool is_valid_hist_info(const char *buf, size_t count) +{ + int i; + + if (count != ERR_SUM_SIZE) + return false; + + if (buf[0] != 'U' || buf[2] != 'I' || buf[4] != 'H' || + buf[6] != 'L' || buf[8] != 'X' || buf[10] != 'Q' || + buf[12] != 'R' || buf[14] != 'W' || buf[16] != 'F' || + buf[18] != 'S' || buf[19] != 'M' || buf[21] != 'S' || + buf[22] != 'H') + return false; + + for (i = 1; i < ERR_SUM_SIZE; i += 2) { + if (buf[i] - '0' < 0 || buf[i] - '0' >= 10) + return false; + /* increase index for "SM", "SH" */ + if (i == 17 || i == 20) + i++; + } + + return true; +} + +static ssize_t ufs_sec_hist_info_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + if (!is_valid_hist_info(buf, count)) { + pr_err("%s: %s, len(%lu)\n", __func__, buf, count); + return -EINVAL; + } + + SEC_UFS_ERR_INFO_HIST_SET_VALUE(UTP_cnt, UTP_err, buf[1]); + SEC_UFS_ERR_INFO_HIST_SET_VALUE(UIC_err_cnt, UIC_err, buf[3]); + SEC_UFS_ERR_INFO_HIST_SET_VALUE(op_cnt, HW_RESET_cnt, buf[5]); + SEC_UFS_ERR_INFO_HIST_SET_VALUE(op_cnt, link_startup_cnt, buf[7]); + SEC_UFS_ERR_INFO_HIST_SET_VALUE(Fatal_err_cnt, LLE, buf[9]); + SEC_UFS_ERR_INFO_HIST_SET_VALUE(UTP_cnt, UTMR_query_task_cnt, buf[11]); + SEC_UFS_ERR_INFO_HIST_SET_VALUE(UTP_cnt, UTR_read_err, buf[13]); + SEC_UFS_ERR_INFO_HIST_SET_VALUE(UTP_cnt, UTR_write_err, buf[15]); + SEC_UFS_ERR_INFO_HIST_SET_VALUE(Fatal_err_cnt, DFE, buf[17]); + SEC_UFS_ERR_INFO_HIST_SET_VALUE(sense_cnt, scsi_medium_err, buf[20]); + SEC_UFS_ERR_INFO_HIST_SET_VALUE(sense_cnt, scsi_hw_err, buf[23]); + + return count; +} +static DEVICE_ATTR(hist, 0664, ufs_sec_hist_info_show, ufs_sec_hist_info_store); + +static ssize_t ufs_sec_man_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ufs_hba *hba = get_vdi_member(hba); + + if (!hba) { + dev_err(dev, "skipping ufs manid read\n"); + return -EINVAL; + } + + return snprintf(buf, PAGE_SIZE, "%04x\n", hba->dev_info.wmanufacturerid); +} +static DEVICE_ATTR(man_id, 0444, ufs_sec_man_id_show, NULL); + +static bool ufs_sec_wait_for_clear_pending(struct ufs_hba *hba, u64 timeout_us) +{ + struct scsi_device *sdp; + unsigned long flags; + unsigned int tm_pending = 0; + unsigned int tr_pending = 0; + bool timeout = true; + ktime_t start; + + ufshcd_hold(hba, false); + + start = ktime_get(); + + do { + spin_lock_irqsave(hba->host->host_lock, flags); + + tr_pending = 0; + + tm_pending = ufshcd_readl(hba, REG_UTP_TASK_REQ_DOOR_BELL); + __shost_for_each_device(sdp, hba->host) + tr_pending += sbitmap_weight(&sdp->budget_map); + + spin_unlock_irqrestore(hba->host->host_lock, flags); + + if (!tm_pending && !tr_pending) { + dev_info(hba->dev, "doorbell clr complete.\n"); + timeout = false; + break; + } + + usleep_range(5000, 5100); + } while (ktime_to_us(ktime_sub(ktime_get(), start)) < timeout_us); + + ufshcd_release(hba); + + return timeout; +} + +static int ufs_sec_send_pon(struct ufs_hba *hba) +{ + struct scsi_device *sdp = hba->ufs_device_wlun; + const unsigned char cdb[6] = { START_STOP, 0, 0, 0, UFS_POWERDOWN_PWR_MODE << 4, 0 }; + struct scsi_sense_hdr sshdr; + const struct scsi_exec_args args = { + .sshdr = &sshdr, + .req_flags = BLK_MQ_REQ_PM, + .scmd_flags = SCMD_FAIL_IF_RECOVERING, + }; + int retries; + int ret; + + for (retries = 3; retries > 0; --retries) { + ret = scsi_execute_cmd(sdp, cdb, REQ_OP_DRV_IN, NULL, + 0, 10 * HZ, 0, &args); + if (ret <= 0) + break; + } + + if (ret) { + if (ret > 0) { + if (scsi_sense_valid(&sshdr)) + scsi_print_sense_hdr(sdp, NULL, &sshdr); + } + } else { + dev_info(hba->dev, "pon done.\n"); + hba->curr_dev_pwr_mode = UFS_POWERDOWN_PWR_MODE; + } + + return ret; +} + +static void ufs_sec_reset_device(struct ufs_hba *hba) +{ + struct ufs_qcom_host *host = ufshcd_get_variant(hba); + unsigned long flags; + + spin_lock_irqsave(hba->host->host_lock, flags); + + hba->force_reset = true; + host->skip_flush = true; + hba->ufshcd_state = UFSHCD_STATE_EH_SCHEDULED_FATAL; + + queue_work(hba->eh_wq, &hba->eh_work); + + spin_unlock_irqrestore(hba->host->host_lock, flags); + + flush_work(&hba->eh_work); + + dev_info(hba->dev, "reset done.\n"); + + if (host->skip_flush) + host->skip_flush = false; +} + +static ssize_t ufs_sec_post_ffu_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct ufs_hba *hba = get_vdi_member(hba); + struct scsi_device *sdp_wlu = hba->ufs_device_wlun; + struct scsi_device *sdp; + u32 ahit_backup = hba->ahit; + unsigned long flags; + int ret; + + /* check product name string */ + if (strncmp(buf, (char *)hba->dev_info.model, strlen(hba->dev_info.model))) + return -EINVAL; + + dev_info(hba->dev, "post_ffu start\n"); + + ufshcd_rpm_get_sync(hba); + + spin_lock_irqsave(hba->host->host_lock, flags); + + if (sdp_wlu && scsi_device_online(sdp_wlu)) + ret = scsi_device_get(sdp_wlu); + else + ret = -ENODEV; + + spin_unlock_irqrestore(hba->host->host_lock, flags); + + if (ret) { + ufshcd_rpm_put(hba); + return ret; + } + + /* set SDEV_QUIESCE */ + shost_for_each_device(sdp, hba->host) + scsi_device_quiesce(sdp); + + /* wait for clear outstanding requests after queue quiesce */ + if (ufs_sec_wait_for_clear_pending(hba, USEC_PER_SEC)) + dev_err(dev, "post_ffu: doorbell clr timedout 1s.\n"); + + /* disable AH8 */ + ufshcd_auto_hibern8_update(hba, 0); + + /* reset and recovery UFS, even if the PON fails */ + if (ufs_sec_send_pon(hba)) + dev_err(dev, "post_ffu: pon failed.\n"); + + /* reset UFS by eh_work */ + ufs_sec_reset_device(hba); + + /* enable AH8 after UFS reset */ + ufshcd_auto_hibern8_update(hba, ahit_backup); + + /* set SDEV_RUNNING */ + shost_for_each_device(sdp, hba->host) + scsi_device_resume(sdp); + + scsi_device_put(sdp_wlu); + + ufshcd_rpm_put(hba); + + dev_info(hba->dev, "post_ffu finish\n"); + + return count; +} +static DEVICE_ATTR(post_ffu, 0220, NULL, ufs_sec_post_ffu_store); + +static struct attribute *sec_ufs_info_attributes[] = { + &dev_attr_un.attr, + &dev_attr_lt.attr, + &dev_attr_flt.attr, + &dev_attr_eli.attr, + &dev_attr_ic.attr, + &dev_attr_shi.attr, + &dev_attr_hist.attr, + &dev_attr_man_id.attr, + &dev_attr_post_ffu.attr, + NULL +}; + +static struct attribute_group sec_ufs_info_attribute_group = { + .attrs = sec_ufs_info_attributes, +}; +/* UFS info nodes : end */ + +/* SEC s_info : begin */ +static ssize_t SEC_UFS_s_info_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int ret; + char s_buf[512] = {0, }; + + ret = sscanf(buf, "%511s", s_buf); + + if (ret != 1) + return -EINVAL; + + snprintf(get_vdi_member(s_info), 512, "%s", s_buf); + + return count; +} + +SEC_UFS_DATA_ATTR_RW(SEC_UFS_s_info, "%s\n", get_vdi_member(s_info)); +/* SEC s_info : end */ + +/* SEC error info : begin */ +static ssize_t SEC_UFS_op_cnt_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + if ((buf[0] != 'C' && buf[0] != 'c') || (count != 1)) + return -EINVAL; + + SEC_UFS_ERR_INFO_BACKUP(op_cnt, HW_RESET_cnt); + SEC_UFS_ERR_INFO_BACKUP(op_cnt, link_startup_cnt); + SEC_UFS_ERR_INFO_BACKUP(op_cnt, Hibern8_enter_cnt); + SEC_UFS_ERR_INFO_BACKUP(op_cnt, Hibern8_exit_cnt); + SEC_UFS_ERR_INFO_BACKUP(op_cnt, AH8_err_cnt); + + return count; +} + +static ssize_t SEC_UFS_uic_cmd_cnt_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + if ((buf[0] != 'C' && buf[0] != 'c') || (count != 1)) + return -EINVAL; + + SEC_UFS_ERR_INFO_BACKUP(UIC_cmd_cnt, DME_TEST_MODE_err); + SEC_UFS_ERR_INFO_BACKUP(UIC_cmd_cnt, DME_GET_err); + SEC_UFS_ERR_INFO_BACKUP(UIC_cmd_cnt, DME_SET_err); + SEC_UFS_ERR_INFO_BACKUP(UIC_cmd_cnt, DME_PEER_GET_err); + SEC_UFS_ERR_INFO_BACKUP(UIC_cmd_cnt, DME_PEER_SET_err); + SEC_UFS_ERR_INFO_BACKUP(UIC_cmd_cnt, DME_POWERON_err); + SEC_UFS_ERR_INFO_BACKUP(UIC_cmd_cnt, DME_POWEROFF_err); + SEC_UFS_ERR_INFO_BACKUP(UIC_cmd_cnt, DME_ENABLE_err); + SEC_UFS_ERR_INFO_BACKUP(UIC_cmd_cnt, DME_RESET_err); + SEC_UFS_ERR_INFO_BACKUP(UIC_cmd_cnt, DME_END_PT_RST_err); + SEC_UFS_ERR_INFO_BACKUP(UIC_cmd_cnt, DME_LINK_STARTUP_err); + SEC_UFS_ERR_INFO_BACKUP(UIC_cmd_cnt, DME_HIBER_ENTER_err); + SEC_UFS_ERR_INFO_BACKUP(UIC_cmd_cnt, DME_HIBER_EXIT_err); + + return count; +} + +static ssize_t SEC_UFS_uic_err_cnt_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + if ((buf[0] != 'C' && buf[0] != 'c') || (count != 1)) + return -EINVAL; + + SEC_UFS_ERR_INFO_BACKUP(UIC_err_cnt, PAERR_cnt); + SEC_UFS_ERR_INFO_BACKUP(UIC_err_cnt, DLERR_cnt); + SEC_UFS_ERR_INFO_BACKUP(UIC_err_cnt, DL_PA_INIT_ERR_cnt); + SEC_UFS_ERR_INFO_BACKUP(UIC_err_cnt, DL_NAC_RCVD_ERR_cnt); + SEC_UFS_ERR_INFO_BACKUP(UIC_err_cnt, DL_TC_REPLAY_ERR_cnt); + SEC_UFS_ERR_INFO_BACKUP(UIC_err_cnt, DL_FC_PROTECT_ERR_cnt); + SEC_UFS_ERR_INFO_BACKUP(UIC_err_cnt, NLERR_cnt); + SEC_UFS_ERR_INFO_BACKUP(UIC_err_cnt, TLERR_cnt); + SEC_UFS_ERR_INFO_BACKUP(UIC_err_cnt, DMEERR_cnt); + + return count; +} + +static ssize_t SEC_UFS_fatal_cnt_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + if ((buf[0] != 'C' && buf[0] != 'c') || (count != 1)) + return -EINVAL; + + SEC_UFS_ERR_INFO_BACKUP(Fatal_err_cnt, DFE); + SEC_UFS_ERR_INFO_BACKUP(Fatal_err_cnt, CFE); + SEC_UFS_ERR_INFO_BACKUP(Fatal_err_cnt, SBFE); + SEC_UFS_ERR_INFO_BACKUP(Fatal_err_cnt, CEFE); + SEC_UFS_ERR_INFO_BACKUP(Fatal_err_cnt, LLE); + + return count; +} + +static ssize_t SEC_UFS_utp_cnt_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + if ((buf[0] != 'C' && buf[0] != 'c') || (count != 1)) + return -EINVAL; + + SEC_UFS_ERR_INFO_BACKUP(UTP_cnt, UTMR_query_task_cnt); + SEC_UFS_ERR_INFO_BACKUP(UTP_cnt, UTMR_abort_task_cnt); + SEC_UFS_ERR_INFO_BACKUP(UTP_cnt, UTR_read_err); + SEC_UFS_ERR_INFO_BACKUP(UTP_cnt, UTR_write_err); + SEC_UFS_ERR_INFO_BACKUP(UTP_cnt, UTR_sync_cache_err); + SEC_UFS_ERR_INFO_BACKUP(UTP_cnt, UTR_unmap_err); + SEC_UFS_ERR_INFO_BACKUP(UTP_cnt, UTR_etc_err); + + return count; +} + +static ssize_t SEC_UFS_query_cnt_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + if ((buf[0] != 'C' && buf[0] != 'c') || (count != 1)) + return -EINVAL; + + SEC_UFS_ERR_INFO_BACKUP(Query_cnt, NOP_err); + SEC_UFS_ERR_INFO_BACKUP(Query_cnt, R_Desc_err); + SEC_UFS_ERR_INFO_BACKUP(Query_cnt, W_Desc_err); + SEC_UFS_ERR_INFO_BACKUP(Query_cnt, R_Attr_err); + SEC_UFS_ERR_INFO_BACKUP(Query_cnt, W_Attr_err); + SEC_UFS_ERR_INFO_BACKUP(Query_cnt, R_Flag_err); + SEC_UFS_ERR_INFO_BACKUP(Query_cnt, Set_Flag_err); + SEC_UFS_ERR_INFO_BACKUP(Query_cnt, Clear_Flag_err); + SEC_UFS_ERR_INFO_BACKUP(Query_cnt, Toggle_Flag_err); + + return count; +} + +static ssize_t SEC_UFS_err_sum_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + if ((buf[0] != 'C' && buf[0] != 'c') || (count != 1)) + return -EINVAL; + + SEC_UFS_ERR_INFO_BACKUP(op_cnt, op_err); + SEC_UFS_ERR_INFO_BACKUP(UIC_cmd_cnt, UIC_cmd_err); + SEC_UFS_ERR_INFO_BACKUP(UIC_err_cnt, UIC_err); + SEC_UFS_ERR_INFO_BACKUP(Fatal_err_cnt, Fatal_err); + SEC_UFS_ERR_INFO_BACKUP(UTP_cnt, UTP_err); + SEC_UFS_ERR_INFO_BACKUP(Query_cnt, Query_err); + + return count; +} + +static ssize_t sense_err_count_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + if ((buf[0] != 'C' && buf[0] != 'c') || (count != 1)) + return -EINVAL; + + SEC_UFS_ERR_INFO_BACKUP(sense_cnt, scsi_medium_err); + SEC_UFS_ERR_INFO_BACKUP(sense_cnt, scsi_hw_err); + + return count; +} + +SEC_UFS_DATA_ATTR_RW(SEC_UFS_op_cnt, "\"HWRESET\":\"%u\",\"LINKFAIL\":\"%u\"" + ",\"H8ENTERFAIL\":\"%u\",\"H8EXITFAIL\":\"%u\"" + ",\"AH8ERR\":\"%u\"\n", + get_err_member(op_cnt).HW_RESET_cnt, + get_err_member(op_cnt).link_startup_cnt, + get_err_member(op_cnt).Hibern8_enter_cnt, + get_err_member(op_cnt).Hibern8_exit_cnt, + get_err_member(op_cnt).AH8_err_cnt); + +SEC_UFS_DATA_ATTR_RW(SEC_UFS_uic_cmd_cnt, "\"TESTMODE\":\"%u\"" + ",\"DME_GET\":\"%u\",\"DME_SET\":\"%u\",\"DME_PGET\":\"%u\"" + ",\"DME_PSET\":\"%u\",\"PWRON\":\"%u\",\"PWROFF\":\"%u\"" + ",\"DME_EN\":\"%u\",\"DME_RST\":\"%u\",\"EPRST\":\"%u\"" + ",\"LINKSTARTUP\":\"%u\",\"H8ENTER\":\"%u\"" + ",\"H8EXIT\":\"%u\"\n", + get_err_member(UIC_cmd_cnt).DME_TEST_MODE_err, + get_err_member(UIC_cmd_cnt).DME_GET_err, + get_err_member(UIC_cmd_cnt).DME_SET_err, + get_err_member(UIC_cmd_cnt).DME_PEER_GET_err, + get_err_member(UIC_cmd_cnt).DME_PEER_SET_err, + get_err_member(UIC_cmd_cnt).DME_POWERON_err, + get_err_member(UIC_cmd_cnt).DME_POWEROFF_err, + get_err_member(UIC_cmd_cnt).DME_ENABLE_err, + get_err_member(UIC_cmd_cnt).DME_RESET_err, + get_err_member(UIC_cmd_cnt).DME_END_PT_RST_err, + get_err_member(UIC_cmd_cnt).DME_LINK_STARTUP_err, + get_err_member(UIC_cmd_cnt).DME_HIBER_ENTER_err, + get_err_member(UIC_cmd_cnt).DME_HIBER_EXIT_err); + +SEC_UFS_DATA_ATTR_RW(SEC_UFS_uic_err_cnt, "\"PAERR\":\"%u\"" + ",\"DLERR\":\"%u\"" + ",\"DLPAINITERROR\":\"%u\",\"DLNAC\":\"%u\"" + ",\"DLTCREPLAY\":\"%u\",\"DLFCX\":\"%u\"" + ",\"NLERR\":\"%u\",\"TLERR\":\"%u\"" + ",\"DMEERR\":\"%u\"\n", + get_err_member(UIC_err_cnt).PAERR_cnt, + get_err_member(UIC_err_cnt).DLERR_cnt, + get_err_member(UIC_err_cnt).DL_PA_INIT_ERR_cnt, + get_err_member(UIC_err_cnt).DL_NAC_RCVD_ERR_cnt, + get_err_member(UIC_err_cnt).DL_TC_REPLAY_ERR_cnt, + get_err_member(UIC_err_cnt).DL_FC_PROTECT_ERR_cnt, + get_err_member(UIC_err_cnt).NLERR_cnt, + get_err_member(UIC_err_cnt).TLERR_cnt, + get_err_member(UIC_err_cnt).DMEERR_cnt); + +SEC_UFS_DATA_ATTR_RW(SEC_UFS_fatal_cnt, "\"DFE\":\"%u\",\"CFE\":\"%u\"" + ",\"SBFE\":\"%u\",\"CEFE\":\"%u\",\"LLE\":\"%u\"\n", + get_err_member(Fatal_err_cnt).DFE, + get_err_member(Fatal_err_cnt).CFE, + get_err_member(Fatal_err_cnt).SBFE, + get_err_member(Fatal_err_cnt).CEFE, + get_err_member(Fatal_err_cnt).LLE); + +SEC_UFS_DATA_ATTR_RW(SEC_UFS_utp_cnt, "\"UTMRQTASK\":\"%u\"" + ",\"UTMRATASK\":\"%u\",\"UTRR\":\"%u\",\"UTRW\":\"%u\"" + ",\"UTRSYNCCACHE\":\"%u\",\"UTRUNMAP\":\"%u\"" + ",\"UTRETC\":\"%u\"\n", + get_err_member(UTP_cnt).UTMR_query_task_cnt, + get_err_member(UTP_cnt).UTMR_abort_task_cnt, + get_err_member(UTP_cnt).UTR_read_err, + get_err_member(UTP_cnt).UTR_write_err, + get_err_member(UTP_cnt).UTR_sync_cache_err, + get_err_member(UTP_cnt).UTR_unmap_err, + get_err_member(UTP_cnt).UTR_etc_err); + +SEC_UFS_DATA_ATTR_RW(SEC_UFS_query_cnt, "\"NOPERR\":\"%u\",\"R_DESC\":\"%u\"" + ",\"W_DESC\":\"%u\",\"R_ATTR\":\"%u\",\"W_ATTR\":\"%u\"" + ",\"R_FLAG\":\"%u\",\"S_FLAG\":\"%u\",\"C_FLAG\":\"%u\"" + ",\"T_FLAG\":\"%u\"\n", + get_err_member(Query_cnt).NOP_err, + get_err_member(Query_cnt).R_Desc_err, + get_err_member(Query_cnt).W_Desc_err, + get_err_member(Query_cnt).R_Attr_err, + get_err_member(Query_cnt).W_Attr_err, + get_err_member(Query_cnt).R_Flag_err, + get_err_member(Query_cnt).Set_Flag_err, + get_err_member(Query_cnt).Clear_Flag_err, + get_err_member(Query_cnt).Toggle_Flag_err); + +/* daily err sum */ +SEC_UFS_DATA_ATTR_RW(SEC_UFS_err_sum, "\"OPERR\":\"%u\",\"UICCMD\":\"%u\"" + ",\"UICERR\":\"%u\",\"FATALERR\":\"%u\",\"UTPERR\":\"%u\"" + ",\"QUERYERR\":\"%u\"\n", + get_err_member(op_cnt).op_err, + get_err_member(UIC_cmd_cnt).UIC_cmd_err, + get_err_member(UIC_err_cnt).UIC_err, + get_err_member(Fatal_err_cnt).Fatal_err, + get_err_member(UTP_cnt).UTP_err, + get_err_member(Query_cnt).Query_err); + +SEC_UFS_DATA_ATTR_RW(sense_err_count, "\"MEDIUM\":\"%u\",\"HWERR\":\"%u\"\n", + get_err_member(sense_cnt).scsi_medium_err, + get_err_member(sense_cnt).scsi_hw_err); + +/* accumulated err sum */ +SEC_UFS_DATA_ATTR_RO(SEC_UFS_err_summary, + "OPERR : %u, UICCMD : %u, UICERR : %u, FATALERR : %u" + ", UTPERR : %u, QUERYERR : %u\n" + "MEDIUM : %u, HWERR : %u\n", + SEC_UFS_ERR_INFO_GET_VALUE(op_cnt, op_err), + SEC_UFS_ERR_INFO_GET_VALUE(UIC_cmd_cnt, UIC_cmd_err), + SEC_UFS_ERR_INFO_GET_VALUE(UIC_err_cnt, UIC_err), + SEC_UFS_ERR_INFO_GET_VALUE(Fatal_err_cnt, Fatal_err), + SEC_UFS_ERR_INFO_GET_VALUE(UTP_cnt, UTP_err), + SEC_UFS_ERR_INFO_GET_VALUE(Query_cnt, Query_err), + SEC_UFS_ERR_INFO_GET_VALUE(sense_cnt, scsi_medium_err), + SEC_UFS_ERR_INFO_GET_VALUE(sense_cnt, scsi_hw_err)); + +static struct attribute *sec_ufs_error_attributes[] = { + &dev_attr_SEC_UFS_op_cnt.attr, + &dev_attr_SEC_UFS_uic_cmd_cnt.attr, + &dev_attr_SEC_UFS_uic_err_cnt.attr, + &dev_attr_SEC_UFS_fatal_cnt.attr, + &dev_attr_SEC_UFS_utp_cnt.attr, + &dev_attr_SEC_UFS_query_cnt.attr, + &dev_attr_SEC_UFS_err_sum.attr, + &dev_attr_sense_err_count.attr, + &dev_attr_SEC_UFS_err_summary.attr, + &dev_attr_SEC_UFS_TW_info.attr, + &dev_attr_SEC_UFS_s_info.attr, + NULL +}; + +static struct attribute_group sec_ufs_error_attribute_group = { + .attrs = sec_ufs_error_attributes, +}; +/* SEC error info : end */ + +/* SEC cmd log : begin */ +static ssize_t ufs_sec_cmd_log_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ufs_sec_cmd_log_info *ufs_cmd_log = + ufs_sec_features.ufs_cmd_log; + struct ufs_sec_cmd_log_entry *entry = NULL; + int i = (ufs_cmd_log->pos + UFS_SEC_CMD_LOGGING_MAX + - UFS_SEC_CMD_LOGNODE_MAX); + int idx = 0; + int len = 0; + + len += snprintf(buf + len, PAGE_SIZE - len, + "%2s: %10s: %2s %3s %4s %9s %6s %16s\n", + "No", "log string", "lu", "tag", + "c_id", "lba", "length", "time"); + + for (idx = 0; idx < UFS_SEC_CMD_LOGNODE_MAX; idx++, i++) { + i %= UFS_SEC_CMD_LOGGING_MAX; + entry = &ufs_cmd_log->entries[i]; + len += snprintf(buf + len, PAGE_SIZE - len, + "%2d: %10s: %2d %3d 0x%02x %9u %6d %16llu\n", + idx, + entry->str, entry->lun, entry->tag, + entry->cmd_id, entry->lba, + entry->transfer_len, entry->tstamp); + } + + return len; +} +static DEVICE_ATTR(cmd_log, 0440, ufs_sec_cmd_log_show, NULL); + +static struct attribute *sec_ufs_cmd_log_attributes[] = { + &dev_attr_cmd_log.attr, + NULL +}; + +static struct attribute_group sec_ufs_cmd_log_attribute_group = { + .attrs = sec_ufs_cmd_log_attributes, +}; +/* SEC cmd log : end */ + +static int ufs_sec_create_sysfs_dev(struct ufs_hba *hba) +{ + /* sec specific vendor sysfs nodes */ + if (!sec_ufs_node_dev) { +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + sec_ufs_node_dev = sec_device_create(hba, "ufs"); +#else + pr_err("Fail to create dev node\n"); +#endif + } + + if (IS_ERR(sec_ufs_node_dev)) { + pr_err("Fail to create sysfs dev\n"); + return -ENODEV; + } + + return 0; +} + +static void ufs_sec_create_sysfs_group(struct ufs_hba *hba, struct device **dev, + const struct attribute_group *dev_attr_group, const char *group_name) +{ + int ret = 0; + + ret = sysfs_create_group(&(*dev)->kobj, dev_attr_group); + if (ret) + dev_err(hba->dev, "%s: Failed to create %s sysfs group (err = %d)\n", + __func__, group_name, ret); +} + +void ufs_sec_add_sysfs_nodes(struct ufs_hba *hba) +{ + struct device *shost_dev = &(hba->host->shost_dev); + + if (ufs_sec_is_err_cnt_allowed()) + ufs_sec_create_sysfs_group(hba, &shost_dev, + &sec_ufs_error_attribute_group, "sec_ufs_err"); + + if (!ufs_sec_create_sysfs_dev(hba)) { + ufs_sec_create_sysfs_group(hba, &sec_ufs_node_dev, + &sec_ufs_info_attribute_group, "sec_ufs_info"); + if (ufs_sec_features.ufs_cmd_log) + ufs_sec_create_sysfs_group(hba, &sec_ufs_node_dev, + &sec_ufs_cmd_log_attribute_group, + "sec_ufs_cmd_log"); + } +} + +void ufs_sec_remove_sysfs_nodes(struct ufs_hba *hba) +{ + struct device *shost_dev = &(hba->host->shost_dev); + + if (sec_ufs_node_dev) { + sysfs_remove_group(&sec_ufs_node_dev->kobj, + &sec_ufs_info_attribute_group); + sysfs_remove_group(&sec_ufs_node_dev->kobj, + &sec_ufs_cmd_log_attribute_group); + } + + if (shost_dev) + sysfs_remove_group(&shost_dev->kobj, + &sec_ufs_error_attribute_group); +} + diff --git a/drivers/ufs/host/ufs-sec-sysfs.h b/drivers/ufs/host/ufs-sec-sysfs.h new file mode 100644 index 000000000000..11875f3e1cd6 --- /dev/null +++ b/drivers/ufs/host/ufs-sec-sysfs.h @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Samsung Specific feature : sysfs-nodes + * + * Copyright (C) 2023 Samsung Electronics Co., Ltd. + * + * Authors: + * Storage Driver + */ + +#ifndef __UFS_SEC_SYSFS_H__ +#define __UFS_SEC_SYSFS_H__ + +#include "ufs-sec-feature.h" + +#include +#include + +void ufs_sec_add_sysfs_nodes(struct ufs_hba *hba); +void ufs_sec_remove_sysfs_nodes(struct ufs_hba *hba); + +extern struct ufs_sec_feature_info ufs_sec_features; + +/* SEC error info : begin */ +/* UFSHCD UIC layer error flags : in ufshcd.c */ +enum { + UFSHCD_UIC_DL_PA_INIT_ERROR = (1 << 0), /* Data link layer error */ + UFSHCD_UIC_DL_NAC_RECEIVED_ERROR = (1 << 1), /* Data link layer error */ + UFSHCD_UIC_DL_TCx_REPLAY_ERROR = (1 << 2), /* Data link layer error */ + UFSHCD_UIC_NL_ERROR = (1 << 3), /* Network layer error */ + UFSHCD_UIC_TL_ERROR = (1 << 4), /* Transport Layer error */ + UFSHCD_UIC_DME_ERROR = (1 << 5), /* DME error */ + UFSHCD_UIC_PA_GENERIC_ERROR = (1 << 6), /* Generic PA error */ +}; + +struct SEC_UFS_op_cnt { + unsigned int HW_RESET_cnt; + unsigned int link_startup_cnt; + unsigned int Hibern8_enter_cnt; + unsigned int Hibern8_exit_cnt; + unsigned int AH8_err_cnt; + unsigned int op_err; +}; + +struct SEC_UFS_UIC_cmd_cnt { + u8 DME_GET_err; + u8 DME_SET_err; + u8 DME_PEER_GET_err; + u8 DME_PEER_SET_err; + u8 DME_POWERON_err; + u8 DME_POWEROFF_err; + u8 DME_ENABLE_err; + u8 DME_RESET_err; + u8 DME_END_PT_RST_err; + u8 DME_LINK_STARTUP_err; + u8 DME_HIBER_ENTER_err; + u8 DME_HIBER_EXIT_err; + u8 DME_TEST_MODE_err; + unsigned int UIC_cmd_err; +}; + +struct SEC_UFS_UIC_err_cnt { + u8 PAERR_cnt; + u8 DL_PA_INIT_ERR_cnt; + u8 DL_NAC_RCVD_ERR_cnt; + u8 DL_TC_REPLAY_ERR_cnt; + u8 DL_FC_PROTECT_ERR_cnt; + u8 NLERR_cnt; + u8 TLERR_cnt; + u8 DMEERR_cnt; + unsigned int DLERR_cnt; + unsigned int UIC_err; + unsigned int PAERR_linereset; + unsigned int PAERR_lane[3]; +}; + +struct SEC_UFS_Fatal_err_cnt { + u8 DFE; // Device_Fatal + u8 CFE; // Controller_Fatal + u8 SBFE; // System_Bus_Fatal + u8 CEFE; // Crypto_Engine_Fatal + u8 LLE; // Link Lost + unsigned int Fatal_err; +}; + +struct SEC_UFS_UTP_cnt { + u8 UTMR_query_task_cnt; + u8 UTMR_abort_task_cnt; + u8 UTMR_logical_reset_cnt; + u8 UTR_read_err; + u8 UTR_write_err; + u8 UTR_sync_cache_err; + u8 UTR_unmap_err; + u8 UTR_etc_err; + unsigned int UTP_err; +}; + +struct SEC_UFS_QUERY_cnt { + u8 NOP_err; + u8 R_Desc_err; + u8 W_Desc_err; + u8 R_Attr_err; + u8 W_Attr_err; + u8 R_Flag_err; + u8 Set_Flag_err; + u8 Clear_Flag_err; + u8 Toggle_Flag_err; + unsigned int Query_err; +}; + +struct SEC_SCSI_SENSE_cnt { + unsigned int scsi_medium_err; + unsigned int scsi_hw_err; +}; + +struct ufs_sec_err_info { + struct SEC_UFS_op_cnt op_cnt; + struct SEC_UFS_UIC_cmd_cnt UIC_cmd_cnt; + struct SEC_UFS_UIC_err_cnt UIC_err_cnt; + struct SEC_UFS_Fatal_err_cnt Fatal_err_cnt; + struct SEC_UFS_UTP_cnt UTP_cnt; + struct SEC_UFS_QUERY_cnt Query_cnt; + struct SEC_SCSI_SENSE_cnt sense_cnt; +}; + +#define get_err_member(member) ufs_sec_features.ufs_err->member +#define get_err_backup_member(member) ufs_sec_features.ufs_err_backup->member +#define get_err_hist_member(member) ufs_sec_features.ufs_err_hist->member +#define get_vdi_member(member) ufs_sec_features.vdi->member + +#define SEC_UFS_ERR_INFO_BACKUP(err_cnt, member) ({ \ + get_err_backup_member(err_cnt).member += get_err_member(err_cnt).member; \ + get_err_member(err_cnt).member = 0; }) + +/* Get the sum of error count about current booting */ +#define SEC_UFS_ERR_INFO_GET_VALUE(err_cnt, member) \ + (get_err_backup_member(err_cnt).member + get_err_member(err_cnt).member) + +/* Get the sum of error count about current and previous booting */ +#define SEC_UFS_ERR_INFO_HIST_SUM_GET_VALUE(err_cnt, member) \ + (SEC_UFS_ERR_INFO_GET_VALUE(err_cnt, member) + get_err_hist_member(err_cnt).member) + +#define SEC_UFS_ERR_INFO_HIST_SET_VALUE(err_cnt, member, value) \ + (get_err_hist_member(err_cnt).member = (value - '0')) + +#define SEC_UFS_DATA_ATTR_RO(name, fmt, args...) \ +static ssize_t name##_show(struct device *dev, struct device_attribute *attr, char *buf) \ +{ \ + return sprintf(buf, fmt, args); \ +} \ +static DEVICE_ATTR_RO(name) + +/* store function has to be defined */ +#define SEC_UFS_DATA_ATTR_RW(name, fmt, args...) \ +static ssize_t name##_show(struct device *dev, struct device_attribute *attr, char *buf) \ +{ \ + return sprintf(buf, fmt, args); \ +} \ +static DEVICE_ATTR(name, 0664, name##_show, name##_store) + +#define SEC_UFS_ERR_CNT_INC(count, max) ((count) += ((count) < (max)) ? 1 : 0) + +#define SEC_UFS_OP_ERR_CNT_INC(member, max) ({ \ + struct SEC_UFS_op_cnt *op_cnt = &get_err_member(op_cnt); \ + \ + SEC_UFS_ERR_CNT_INC(op_cnt->member, max); \ + SEC_UFS_ERR_CNT_INC(op_cnt->op_err, UINT_MAX); \ + }) + +#define SEC_UFS_ERR_CNT_ADD(count, value, max) \ + ((count) += (count < max) ? (((count + value) < (max)) ? value : (max - count)) : 0) + +#define get_min_errinfo(type, min_val, err_cnt, member) \ + min_t(type, min_val, SEC_UFS_ERR_INFO_GET_VALUE(err_cnt, member)) + +#define get_min_errinfo_hist(type, min_val, err_cnt, member) \ + min_t(type, min_val, SEC_UFS_ERR_INFO_HIST_SUM_GET_VALUE(err_cnt, member)) + +#define ERR_SUM_SIZE 25 +#define ERR_HIST_SUM_SIZE 26 +/** + * UFS Error Information + * + * Format : U0I0H0L0X0Q0R0W0F0SM0SH0 + * U : UTP cmd error count + * I : UIC error count + * H : HWRESET count + * L : Link startup failure count + * X : Link Lost Error count + * Q : UTMR QUERY_TASK error count + * R : READ error count + * W : WRITE error count + * F : Device Fatal Error count + * SM : Sense Medium error count + * SH : Sense Hardware error count + **/ +#define SEC_UFS_ERR_SUM(buf) \ + sprintf(buf, "U%uI%uH%uL%uX%uQ%uR%uW%uF%uSM%uSH%u", \ + get_min_errinfo(u32, 9, UTP_cnt, UTP_err), \ + get_min_errinfo(u32, 9, UIC_err_cnt, UIC_err), \ + get_min_errinfo(u32, 9, op_cnt, HW_RESET_cnt), \ + get_min_errinfo(u32, 9, op_cnt, link_startup_cnt), \ + get_min_errinfo(u8, 9, Fatal_err_cnt, LLE), \ + get_min_errinfo(u8, 9, UTP_cnt, UTMR_query_task_cnt), \ + get_min_errinfo(u8, 9, UTP_cnt, UTR_read_err), \ + get_min_errinfo(u8, 9, UTP_cnt, UTR_write_err), \ + get_min_errinfo(u8, 9, Fatal_err_cnt, DFE), \ + get_min_errinfo(u32, 9, sense_cnt, scsi_medium_err), \ + get_min_errinfo(u32, 9, sense_cnt, scsi_hw_err)) +/** + * UFS Error Information + * previous boot's error count + current boot's error count + **/ +#define SEC_UFS_ERR_HIST_SUM(buf) \ + sprintf(buf, "U%uI%uH%uL%uX%uQ%uR%uW%uF%uSM%uSH%u\n", \ + get_min_errinfo_hist(u32, 9, UTP_cnt, UTP_err), \ + get_min_errinfo_hist(u32, 9, UIC_err_cnt, UIC_err), \ + get_min_errinfo_hist(u32, 9, op_cnt, HW_RESET_cnt), \ + get_min_errinfo_hist(u32, 9, op_cnt, link_startup_cnt), \ + get_min_errinfo_hist(u8, 9, Fatal_err_cnt, LLE), \ + get_min_errinfo_hist(u8, 9, UTP_cnt, UTMR_query_task_cnt), \ + get_min_errinfo_hist(u8, 9, UTP_cnt, UTR_read_err), \ + get_min_errinfo_hist(u8, 9, UTP_cnt, UTR_write_err), \ + get_min_errinfo_hist(u8, 9, Fatal_err_cnt, DFE), \ + get_min_errinfo_hist(u32, 9, sense_cnt, scsi_medium_err), \ + get_min_errinfo_hist(u32, 9, sense_cnt, scsi_hw_err)) + +/* SEC error info : end */ + +/* SEC next WB : begin */ +#define SEC_UFS_WB_INFO_BACKUP(member) ({ \ + ufs_sec_features.ufs_wb_backup->member += ufs_sec_features.ufs_wb->member; \ + ufs_sec_features.ufs_wb->member = 0; }) +/* SEC next WB : end */ +#endif diff --git a/drivers/uh/Makefile b/drivers/uh/Makefile new file mode 100755 index 000000000000..2e800d9f1bb5 --- /dev/null +++ b/drivers/uh/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_UH) += uh_debug_log.o +obj-$(CONFIG_RKP) += rkp.o +obj-$(CONFIG_RKP) += rkp_module_support.o +obj-$(CONFIG_RKP_TEST) += rkp_test.o +obj-$(CONFIG_KDP) += kdp.o +obj-$(CONFIG_KDP_TEST) += kdp_test.o diff --git a/drivers/uh/kdp.c b/drivers/uh/kdp.c new file mode 100755 index 000000000000..afbaef2f218b --- /dev/null +++ b/drivers/uh/kdp.c @@ -0,0 +1,744 @@ +#include +#include +#include "../../mm/slab.h" +#include +#include + +#include +#include +#include +#include +#include +#include "../../fs/mount.h" + +#define VERITY_PARAM_LENGTH 20 +#define KDP_CRED_SYS_ID 1000 + +/* security/selinux/include/objsec.h */ +struct task_security_struct { + u32 osid; /* SID prior to last execve */ + u32 sid; /* current SID */ + u32 exec_sid; /* exec SID */ + u32 create_sid; /* fscreate SID */ + u32 keycreate_sid; /* keycreate SID */ + u32 sockcreate_sid; /* fscreate SID */ + void *bp_cred; +}; +/* security/selinux/hooks.c */ +struct task_security_struct init_sec __kdp_ro; + +bool kdp_enable __kdp_ro = false; +static int __check_verifiedboot __kdp_ro = 0; +static int __is_kdp_recovery __kdp_ro = 0; + +static char verifiedbootstate[VERITY_PARAM_LENGTH]; + +void __init kdp_init(void) +{ + struct kdp_init cred; + + memset((void *)&cred, 0, sizeof(kdp_init)); + + cred._srodata = (u64)__start_rodata; + cred._erodata = (u64)__end_rodata; + cred.init_mm_pgd = (u64)swapper_pg_dir; + cred.credSize = sizeof(struct cred_kdp); + cred.sp_size = sizeof(struct task_security_struct); + cred.pgd_mm = offsetof(struct mm_struct, pgd); + cred.uid_cred = offsetof(struct cred, uid); + cred.euid_cred = offsetof(struct cred, euid); + cred.gid_cred = offsetof(struct cred, gid); + cred.egid_cred = offsetof(struct cred, egid); + + cred.bp_pgd_cred = offsetof(struct cred_kdp, bp_pgd); + cred.bp_task_cred = offsetof(struct cred_kdp, bp_task); + cred.type_cred = offsetof(struct cred_kdp, type); + cred.security_cred = offsetof(struct cred, security); + cred.usage_cred = offsetof(struct cred_kdp, use_cnt); + cred.cred_task = offsetof(struct task_struct, cred); + cred.mm_task = offsetof(struct task_struct, mm); + + cred.pid_task = offsetof(struct task_struct, pid); + cred.rp_task = offsetof(struct task_struct, real_parent); + cred.comm_task = offsetof(struct task_struct, comm); + cred.bp_cred_secptr = offsetof(struct task_security_struct, bp_cred); + cred.verifiedbootstate = (u64)verifiedbootstate; + + uh_call(UH_APP_KDP, KDP_INIT, (u64)&cred, 0, 0, 0); +} + +static int __init verifiedboot_state_setup(char *str) +{ + strlcpy(verifiedbootstate, str, sizeof(verifiedbootstate)); + + if (!strncmp(verifiedbootstate, "orange", sizeof("orange"))) + __check_verifiedboot = 1; + return 0; +} +__setup("androidboot.verifiedbootstate=", verifiedboot_state_setup); + +static int __init boot_recovery(char *str) +{ + int temp = 0; + + if (get_option(&str, &temp)) { + __is_kdp_recovery = temp; + return 0; + } + return -EINVAL; +} +early_param("androidboot.boot_recovery", boot_recovery); + +#ifdef CONFIG_KDP_CRED +/*------------------------------------------------ + * CRED + *------------------------------------------------ + */ +struct cred_kdp_init { + atomic_t use_cnt; + struct ro_rcu_head ro_rcu_head_init; +}; + +struct cred_kdp_init init_cred_use_cnt = { + .use_cnt = ATOMIC_INIT(4), + .ro_rcu_head_init = { + .non_rcu = 0, + .bp_cred = NULL, + }, +}; + +struct cred_kdp init_cred_kdp __kdp_ro = { +//struct cred_kdp init_cred_kdp = { + .use_cnt = (atomic_t *)&init_cred_use_cnt, + .bp_task = &init_task, + .bp_pgd = NULL, + .type = 0, +}; + +static struct kmem_cache *cred_jar_ro; +static struct kmem_cache *tsec_jar; +static struct kmem_cache *usecnt_jar; + +/* Dummy constructor to make sure we have separate slabs caches. */ +static void cred_ctor(void *data) {} +static void sec_ctor(void *data) {} +static void usecnt_ctor(void *data) {} + +void __init kdp_cred_init(void) +{ + slab_flags_t flags = SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_ACCOUNT; + + if (!kdp_enable) { + return; + } + + cred_jar_ro = kmem_cache_create("cred_jar_ro", + sizeof(struct cred_kdp), + 0, flags, cred_ctor); + if (!cred_jar_ro) + panic("Unable to create RO Cred cache\n"); + + tsec_jar = kmem_cache_create("tsec_jar", + sizeof(struct task_security_struct), + 0, flags, sec_ctor); + if (!tsec_jar) + panic("Unable to create RO security cache\n"); + + usecnt_jar = kmem_cache_create("usecnt_jar", + sizeof(struct cred_kdp_init), + 0, flags, usecnt_ctor); + if (!usecnt_jar) + panic("Unable to create use count jar\n"); + + uh_call(UH_APP_KDP, JARRO_TSEC_SIZE, (u64)cred_jar_ro->size, + (u64)tsec_jar->size, 0, 0); +} + +unsigned int kdp_get_usecount(struct cred *cred) +{ + int ret = is_kdp_protect_addr((unsigned long)cred); + + if (ret == PROTECT_INIT) + return (unsigned int)atomic_read(init_cred_kdp.use_cnt); + else if (ret == PROTECT_KMEM) + return (unsigned int)atomic_read(((struct cred_kdp *)cred)->use_cnt); + else + return atomic_read(&cred->usage); +} + +void kdp_usecount_inc(struct cred *cred) +{ + int ret = is_kdp_protect_addr((unsigned long)cred); + + if (ret == PROTECT_INIT) + atomic_inc(init_cred_kdp.use_cnt); + else if (ret == PROTECT_KMEM) + atomic_inc(((struct cred_kdp *)cred)->use_cnt); + else + atomic_inc(&cred->usage); +} + +unsigned int kdp_usecount_inc_not_zero(struct cred *cred) +{ + int ret = is_kdp_protect_addr((unsigned long)cred); + + if (ret == PROTECT_INIT) + return (unsigned int)atomic_inc_not_zero(init_cred_kdp.use_cnt); + else if (ret == PROTECT_KMEM) + return (unsigned int)atomic_inc_not_zero(((struct cred_kdp *)cred)->use_cnt); + else + return atomic_inc_not_zero(&cred->usage); +} + +unsigned int kdp_usecount_dec_and_test(struct cred *cred) +{ + int ret = is_kdp_protect_addr((unsigned long)cred); + + if (ret == PROTECT_INIT) + return (unsigned int)atomic_dec_and_test(init_cred_kdp.use_cnt); + else if (ret == PROTECT_KMEM) + return (unsigned int)atomic_dec_and_test(((struct cred_kdp *)cred)->use_cnt); + else + return atomic_dec_and_test(&cred->usage); +} + +void kdp_set_cred_non_rcu(struct cred *cred, int val) +{ + if (is_kdp_protect_addr((unsigned long)cred)) + GET_ROCRED_RCU(cred)->non_rcu = val; + else + cred->non_rcu = val; +} + +/* match for kernel/cred.c function */ +inline void set_kdp_cred_subscribers(struct cred *cred, int n) +{ +#ifdef CONFIG_DEBUG_CREDENTIALS + atomic_set(&cred->subscribers, n); +#endif +} + +/* Check whether the address belong to Cred Area */ +int is_kdp_protect_addr(unsigned long addr) +{ + struct kmem_cache *s; + struct page *page; + struct slab *p_slab; + void *objp = (void *)addr; + + if (!objp) + return 0; + + if (!kdp_enable) + return 0; + + if ((addr == ((unsigned long)&init_cred)) || + (addr == ((unsigned long)&init_sec))) + return PROTECT_INIT; + + page = virt_to_head_page(objp); + p_slab = page_slab(page); + s = p_slab->slab_cache; + if (s && (s == cred_jar_ro || s == tsec_jar)) + return PROTECT_KMEM; + + return 0; +} + +/* We use another function to free protected creds. */ +extern void security_cred_free_hook(struct cred *cred); +void put_rocred_rcu(struct rcu_head *rcu) +{ + struct cred *cred = container_of(rcu, struct ro_rcu_head, rcu)->bp_cred; + + if (atomic_read(((struct cred_kdp *)cred)->use_cnt) != 0) + panic("RO_CRED: put_rocred_rcu() sees %p with usage %d\n", + cred, atomic_read(((struct cred_kdp *)cred)->use_cnt)); + + security_cred_free_hook(cred); + kdp_free_security((unsigned long)cred->security); + + key_put(cred->session_keyring); + key_put(cred->process_keyring); + key_put(cred->thread_keyring); + key_put(cred->request_key_auth); + if (cred->group_info) + put_group_info(cred->group_info); + free_uid(cred->user); + if (cred->ucounts) + put_ucounts(cred->ucounts); + put_user_ns(cred->user_ns); + if (((struct cred_kdp *)cred)->use_cnt) + kmem_cache_free(usecnt_jar, (void *)((struct cred_kdp *)cred)->use_cnt); + kmem_cache_free(cred_jar_ro, cred); +} + +void kdp_put_cred_rcu(struct cred *cred, void *put_cred_rcu) +{ + if (is_kdp_protect_addr((unsigned long)cred)) { + if (GET_ROCRED_RCU(cred)->non_rcu) + put_rocred_rcu(&(GET_ROCRED_RCU(cred)->rcu)); + else + call_rcu(&(GET_ROCRED_RCU(cred)->rcu), put_rocred_rcu); + } else { + void (*f)(struct rcu_head *) = put_cred_rcu; + if (cred->non_rcu) + f(&cred->rcu); + else + call_rcu(&cred->rcu, f); + } +} + +/* prepare_ro_creds - Prepare a new set of credentials which is protected by KDP */ +struct cred *prepare_ro_creds(struct cred *old, int kdp_cmd, u64 p) +{ + u64 pgd = (u64)(current->mm ? current->mm->pgd : swapper_pg_dir); + struct cred_kdp temp_old; + struct cred_kdp *new_ro = NULL; + struct cred_param param_data; + void *use_cnt_ptr = NULL; + void *rcu_ptr = NULL; + void *tsec = NULL; + + new_ro = kmem_cache_alloc(cred_jar_ro, GFP_KERNEL | __GFP_NOFAIL); + if (!new_ro) + panic("[%d] : kmem_cache_alloc() failed", kdp_cmd); + + use_cnt_ptr = kmem_cache_alloc(usecnt_jar, GFP_KERNEL | __GFP_NOFAIL); + if (!use_cnt_ptr) + panic("[%d] : Unable to allocate usage pointer\n", kdp_cmd); + + // get_usecnt_rcu + rcu_ptr = (struct ro_rcu_head *)((atomic_t *)use_cnt_ptr + 1); + ((struct ro_rcu_head *)rcu_ptr)->bp_cred = (void *)new_ro; + + tsec = kmem_cache_alloc(tsec_jar, GFP_KERNEL | __GFP_NOFAIL); + if (!tsec) + panic("[%d] : Unable to allocate security pointer\n", kdp_cmd); + + // make cred_kdp 'temp_old' + if ((u64)current->cred == (u64)&init_cred) + memcpy(&temp_old, &init_cred_kdp, sizeof(struct cred_kdp)); + else + memcpy(&temp_old, current->cred, sizeof(struct cred_kdp)); + + memcpy(&temp_old, old, sizeof(struct cred)); + + // init + memset((void *)¶m_data, 0, sizeof(struct cred_param)); + param_data.cred = &temp_old; + param_data.cred_ro = new_ro; + param_data.use_cnt_ptr = use_cnt_ptr; + param_data.sec_ptr = tsec; + param_data.type = kdp_cmd; + param_data.use_cnt = (u64)p; + + uh_call(UH_APP_KDP, PREPARE_RO_CRED, (u64)¶m_data, (u64)current, (u64)&init_cred, (u64)&init_cred_kdp); + if (kdp_cmd == CMD_COPY_CREDS) { + if ((new_ro->bp_task != (void *)p) || + new_ro->cred.security != tsec || + new_ro->use_cnt != use_cnt_ptr) { + panic("[%d]: KDP Call failed task=0x%lx:0x%lx, sec=0x%lx:0x%lx, usecnt=0x%lx:0x%lx", + kdp_cmd, (unsigned long) new_ro->bp_task, (unsigned long) p, + (unsigned long) new_ro->cred.security, (unsigned long) tsec, (unsigned long) new_ro->use_cnt, (unsigned long) use_cnt_ptr); + } + } else { + if ((new_ro->bp_task != current) || + (current->mm && new_ro->bp_pgd != (void *)pgd) || + (new_ro->cred.security != tsec) || + (new_ro->use_cnt != use_cnt_ptr)) { + panic("[%d]: KDP Call failed task=0x%lx:0x%lx, sec=0x%lx:0x%lx, usecnt=0x%lx:0x%lx, pgd=0x%lx:0x%lx", + kdp_cmd, (unsigned long) new_ro->bp_task, (unsigned long) current, (unsigned long) new_ro->cred.security, (unsigned long) tsec, + (unsigned long) new_ro->use_cnt, (unsigned long) use_cnt_ptr, (unsigned long) new_ro->bp_pgd, (unsigned long) pgd); + } + } + + GET_ROCRED_RCU(new_ro)->non_rcu = old->non_rcu; + GET_ROCRED_RCU(new_ro)->reflected_cred = 0; + atomic_set(new_ro->use_cnt, 2); + + set_kdp_cred_subscribers((struct cred *)new_ro, 0); + get_group_info(new_ro->cred.group_info); + get_uid(new_ro->cred.user); + get_user_ns(new_ro->cred.user_ns); + +#ifdef CONFIG_KEYS + key_get(new_ro->cred.session_keyring); + key_get(new_ro->cred.process_keyring); + key_get(new_ro->cred.thread_keyring); + key_get(new_ro->cred.request_key_auth); +#endif + if (!get_ucounts(new_ro->cred.ucounts)) + panic("[KDP] : ucount is NULL\n"); + + validate_creds((struct cred *)new_ro); + return (struct cred *)new_ro; +} + +/* security/selinux/hooks.c */ +static bool is_kdp_tsec_jar(unsigned long addr) +{ + struct kmem_cache *s; + struct page *page; + struct slab *p_slab; + void *objp = (void *)addr; + + if (!objp) + return false; + + page = virt_to_head_page(objp); + p_slab = page_slab(page); + s = p_slab->slab_cache; + if (s && s == tsec_jar) + return true; + return false; +} + +static inline int chk_invalid_kern_ptr(u64 tsec) +{ + return (((u64)tsec >> 39) != (u64)0x1FFFFFF); +} + +void kdp_free_security(unsigned long tsec) +{ + if (!tsec || chk_invalid_kern_ptr(tsec)) + return; + + if (is_kdp_tsec_jar(tsec)) + kmem_cache_free(tsec_jar, (void *)tsec); + else + kfree((void *)tsec); +} + +void kdp_assign_pgd(struct task_struct *p) +{ + struct cred_kdp *p_cred = (struct cred_kdp *)p->cred; + u64 pgd = (u64)(p->mm ? p->mm->pgd : swapper_pg_dir); + + if (p_cred->bp_pgd == (void *)pgd) + return; + + uh_call(UH_APP_KDP, SET_CRED_PGD, (u64)p_cred, (u64)pgd, 0, 0); +} + +void set_rocred_ucounts(struct cred *cred, struct ucounts *new_ucounts) +{ + if (is_kdp_protect_addr((u64)cred)) { + if (cred == &init_cred) + uh_call(UH_APP_KDP, SET_CRED_UCOUNTS, (u64)cred, (u64)&init_cred, (u64)&(init_cred.ucounts), (u64)new_ucounts); + else + uh_call(UH_APP_KDP, SET_CRED_UCOUNTS, (u64)cred, (u64)&init_cred, (u64)&(cred->ucounts), (u64)new_ucounts); + } else { + cred->ucounts = new_ucounts; + } +} + +struct task_security_struct init_sec __kdp_ro; +static inline unsigned int cmp_sec_integrity(const struct cred *cred, struct mm_struct *mm) +{ + if (cred == &init_cred) { + if (init_cred_kdp.bp_task != current) + printk(KERN_ERR "[KDP] init_cred_kdp.bp_task: 0x%lx, current: 0x%lx\n", + (unsigned long) init_cred_kdp.bp_task, (unsigned long) current); + + if (mm && (init_cred_kdp.bp_pgd != swapper_pg_dir) && (init_cred_kdp.bp_pgd != mm->pgd )) + printk(KERN_ERR "[KDP] mm: 0x%lx, init_cred_kdp.bp_pgd: 0x%lx, swapper_pg_dir: %p, mm->pgd: 0x%lx\n", + (unsigned long) mm, (unsigned long) init_cred_kdp.bp_pgd, swapper_pg_dir, (unsigned long) mm->pgd); + + return ((init_cred_kdp.bp_task != current) || + (mm && (!(in_interrupt() || in_softirq())) && + (init_cred_kdp.bp_pgd != swapper_pg_dir) && + (init_cred_kdp.bp_pgd != mm->pgd))); + } else { + if (((struct cred_kdp *)cred)->bp_task != current) + printk(KERN_ERR "[KDP] cred->bp_task: 0x%lx, current: 0x%lx\n", + (unsigned long) ((struct cred_kdp *)cred)->bp_task, (unsigned long) current); + + if (mm && (((struct cred_kdp *)cred)->bp_pgd != swapper_pg_dir) && + (((struct cred_kdp *)cred)->bp_pgd != mm->pgd)) + printk(KERN_ERR "[KDP] mm: 0x%lx, cred->bp_pgd: 0x%lx, swapper_pg_dir: %p, mm->pgd: 0x%lx\n", + (unsigned long) mm, (unsigned long) ((struct cred_kdp *)cred)->bp_pgd, swapper_pg_dir, (unsigned long) mm->pgd); + + return ((((struct cred_kdp *)cred)->bp_task != current) || + (mm && (!(in_interrupt() || in_softirq())) && + (((struct cred_kdp *)cred)->bp_pgd != swapper_pg_dir) && + (((struct cred_kdp *)cred)->bp_pgd != mm->pgd))); + } + // Want to not reaching + return 1; +} + +static inline bool is_kdp_invalid_cred_sp(u64 cred, u64 sec_ptr) +{ + struct task_security_struct *tsec = (struct task_security_struct *)sec_ptr; + u64 cred_size = sizeof(struct cred_kdp); + u64 tsec_size = sizeof(struct task_security_struct); + + if (cred == (u64)&init_cred) + cred_size = sizeof(struct cred); + + if ((cred == (u64)&init_cred) && (sec_ptr == (u64)&init_sec)) + return false; + + if (!is_kdp_protect_addr(cred) || + !is_kdp_protect_addr(cred + cred_size) || + !is_kdp_protect_addr(sec_ptr) || + !is_kdp_protect_addr(sec_ptr + tsec_size)) { + //printk(KERN_ERR, "[KDP] cred: %d, cred + sizeof(cred): %d, sp: %d, sp + sizeof(tsec): %d", + // is_kdp_protect_addr(cred), + // is_kdp_protect_addr(cred + cred_size), + // is_kdp_protect_addr(sec_ptr), + // is_kdp_protect_addr(sec_ptr + tsec_size)); + return true; + } + + if ((u64)tsec->bp_cred != cred) { + //printk(KERN_ERR, "[KDP] %s: tesc->bp_cred: %lx, cred: %lx\n", + // __func__, (u64)tsec->bp_cred, cred); + return true; + } + + return false; +} + +inline int kdp_restrict_fork(struct filename *path) +{ + struct cred *shellcred; + const struct cred_kdp *cred_kdp = (const struct cred_kdp *)(current->cred); + + if (!strcmp(path->name, "/system/bin/patchoat") || + !strcmp(path->name, "/system/bin/idmap2")) { + return 0; + } + + if ((cred_kdp->type) >> 1 & 1) { + shellcred = prepare_creds(); + if (!shellcred) + return 1; + + shellcred->uid.val = 2000; + shellcred->gid.val = 2000; + shellcred->euid.val = 2000; + shellcred->egid.val = 2000; + + commit_creds(shellcred); + } + return 0; +} +#endif + +/* This function is related to Namespace */ +#ifdef CONFIG_KDP_NS +static unsigned int cmp_ns_integrity(void) +{ + struct kdp_mount *root = NULL; + struct nsproxy *nsp = NULL; + + if (in_interrupt() || in_softirq()) + return 0; + + nsp = current->nsproxy; + if (!nsp || !nsp->mnt_ns) + return 0; + + root = (struct kdp_mount *)current->nsproxy->mnt_ns->root; + if (root != (struct kdp_mount *)((struct kdp_vfsmount *)root->mnt)->bp_mount) { + printk(KERN_ERR "[KDP] NameSpace Mismatch %lx != %lx\n nsp: 0x%lx, mnt_ns: 0x%lx\n", + (unsigned long) root, (unsigned long) ((struct kdp_vfsmount *)root->mnt)->bp_mount, (unsigned long) nsp, (unsigned long) nsp->mnt_ns); + return 1; + } + + return 0; +} + +/*------------------------------------------------ + * Namespace + *------------------------------------------------ + */ +static DEFINE_SPINLOCK(mnt_vfsmnt_lock); +static struct kmem_cache *vfsmnt_cache __read_mostly; + +void cred_ctor_vfsmount(void *data) +{ + /* Dummy constructor to make sure we have separate slabs caches. */ +} +void __init kdp_mnt_init(void) +{ + struct ns_param nsparam; + + vfsmnt_cache = kmem_cache_create("vfsmnt_cache", sizeof(struct kdp_vfsmount), + 0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, cred_ctor_vfsmount); + + if (!vfsmnt_cache) + panic("Failed to allocate vfsmnt_cache\n"); + + memset((void *)&nsparam, 0, sizeof(struct ns_param)); + nsparam.ns_buff_size = (u64)vfsmnt_cache->size; + nsparam.ns_size = (u64)sizeof(struct kdp_vfsmount); + nsparam.bp_offset = (u64)offsetof(struct kdp_vfsmount, bp_mount); + nsparam.sb_offset = (u64)offsetof(struct kdp_vfsmount, mnt.mnt_sb); + nsparam.flag_offset = (u64)offsetof(struct kdp_vfsmount, mnt.mnt_flags); + nsparam.userns_offset = (u64)offsetof(struct kdp_vfsmount, mnt.mnt_userns); + /* mnt.data deprecated + * + * nsparam.data_offset = (u64)offsetof(struct kdp_vfsmount, mnt.data); + */ + + uh_call(UH_APP_KDP, NS_INIT, (u64)&nsparam, 0, 0, 0); +} + +bool is_kdp_vfsmnt_cache(unsigned long addr) +{ + static void *objp; + static struct kmem_cache *s; + static struct page *page; + struct slab *p_slab; + + objp = (void *)addr; + + if (!objp) + return false; + + page = virt_to_head_page(objp); + p_slab = page_slab(page); + s = p_slab->slab_cache; + if (s && s == vfsmnt_cache) + return true; + return false; +} + +inline void kdp_set_mnt_root_sb(struct vfsmount *mnt, struct dentry *mnt_root, struct super_block *mnt_sb) +{ + uh_call(UH_APP_KDP, SET_NS_ROOT_SB, (u64)mnt, (u64)mnt_root, (u64)mnt_sb, 0); +} + +inline void kdp_assign_mnt_flags(struct vfsmount *mnt, int flags) +{ + if (mnt->mnt_flags == flags) + return; + uh_call(UH_APP_KDP, SET_NS_FLAGS, (u64)mnt, (u64)flags, 0, 0); +} + +inline void kdp_clear_mnt_flags(struct vfsmount *mnt, int flags) +{ + int f = mnt->mnt_flags; + + f &= ~flags; + kdp_assign_mnt_flags(mnt, f); +} + +void kdp_set_mnt_flags(struct vfsmount *mnt, int flags) +{ + int f = mnt->mnt_flags; + + f |= flags; + + kdp_assign_mnt_flags(mnt, f); +} + +void kdp_set_mnt_userns(struct vfsmount *mnt, struct user_namespace *userns) +{ + if (mnt->mnt_userns == userns) + return; + + uh_call(UH_APP_KDP, SET_NS_USERNS, (u64)mnt, (u64)userns, 0, 0); +} + +/* mnt.data deprecated */ +void kdp_set_ns_data(struct vfsmount *mnt, void *data) +{ + uh_call(UH_APP_KDP, SET_NS_DATA, (u64)mnt, (u64)data, 0, 0); +} + +int kdp_mnt_alloc_vfsmount(struct mount *mnt) +{ + struct kdp_vfsmount *vfsmnt = NULL; + + vfsmnt = kmem_cache_alloc(vfsmnt_cache, GFP_KERNEL); + if (!vfsmnt) + return 1; + + spin_lock(&mnt_vfsmnt_lock); + uh_call(UH_APP_KDP, SET_NS_BP, (u64)vfsmnt, (u64)mnt, 0, 0); + ((struct kdp_mount *)mnt)->mnt = (struct vfsmount *)vfsmnt; + spin_unlock(&mnt_vfsmnt_lock); + + return 0; +} + +void kdp_free_vfsmount(void *objp) +{ + kmem_cache_free(vfsmnt_cache, objp); +} +#endif + +#ifdef CONFIG_KDP_CRED +/* Main function to verify cred security context of a process */ +int security_integrity_current(void) +{ + const struct cred *cur_cred = current_cred(); + + rcu_read_lock(); + if (kdp_enable && + (is_kdp_invalid_cred_sp((u64)cur_cred, (u64)cur_cred->security) + || cmp_sec_integrity(cur_cred, current->mm) +#ifdef CONFIG_KDP_NS + || cmp_ns_integrity() +#endif + )) { + rcu_read_unlock(); + panic("KDP CRED PROTECTION VIOLATION\n"); + } + rcu_read_unlock(); + return 0; +} +#endif + +inline int get_kdp_kmem_cache_type(const char *name) +{ + if (name) { +#ifdef CONFIG_KDP_CRED + if (!strncmp(name, CRED_JAR_RO, strlen(CRED_JAR_RO))) + return CRED_JAR_TYPE; + if (!strncmp(name, TSEC_JAR, strlen(TSEC_JAR))) + return TSEC_JAR_TYPE; +#endif +#ifdef CONFIG_KDP_NS + if (!strncmp(name, VFSMNT_JAR, strlen(VFSMNT_JAR))) + return VFSMNT_JAR_TYPE; +#endif + } + return UNKNOWN_JAR_TYPE; +} + +inline bool is_kdp_kmem_cache_name(const char *name) +{ + if (name) { +#ifdef CONFIG_KDP_CRED + if (!strncmp(name, CRED_JAR_RO, strlen(CRED_JAR_RO)) || + !strncmp(name, TSEC_JAR, strlen(TSEC_JAR))) + return true; +#endif +#ifdef CONFIG_KDP_NS + if (!strncmp(name, VFSMNT_JAR, strlen(VFSMNT_JAR))) + return true; +#endif + } + return false; +} + +inline bool is_kdp_kmem_cache(struct kmem_cache *s) +{ + if (!s->name) + return false; + +#ifdef CONFIG_KDP_CRED + if (s == cred_jar_ro || s == tsec_jar) + return true; +#endif +#ifdef CONFIG_KDP_NS + if (s == vfsmnt_cache) + return true; +#endif + return false; +} diff --git a/drivers/uh/kdp_test.c b/drivers/uh/kdp_test.c new file mode 100755 index 000000000000..466f6e8f5506 --- /dev/null +++ b/drivers/uh/kdp_test.c @@ -0,0 +1,397 @@ +#include +#include +#include +#include +#include +#include +#include <../../fs/mount.h> +#include + +#include + +/* Never enable this flag*/ +//#define CONFIG_KDP_SEC_TEST + +struct task_security_struct { + u32 osid; /* SID prior to last execve */ + u32 sid; /* current SID */ + u32 exec_sid; /* exec SID */ + u32 create_sid; /* fscreate SID */ + u32 keycreate_sid; /* keycreate SID */ + u32 sockcreate_sid; /* fscreate SID */ + void *bp_cred; +}; + +struct test_case { + int (*fn)(void); + char *describe; +}; + +enum __KDP_TEST { + CMD_ID_CRED = 0, + CMD_ID_SEC_CONTEXT, + CMD_ID_NS, +}; + +#define KDP_PA_READ 0 +#define KDP_PA_WRITE 1 + +/* BUF define */ +#define KDP_BUF_SIZE 8192 +#define KDP_LINE_MAX 80 +static char kdp_test_buf[KDP_BUF_SIZE]; +static unsigned long kdp_test_len; + +static DEFINE_RAW_SPINLOCK(par_lock); +static u64 *ha1; + +static void kdp_print(const char *fmt, ...) +{ + va_list aptr; + + if (kdp_test_len > KDP_BUF_SIZE - KDP_LINE_MAX) + return; + + va_start(aptr, fmt); + kdp_test_len += vsprintf(kdp_test_buf + kdp_test_len, fmt, aptr); + va_end(aptr); +} + +static struct vfsmount *get_vfsmnt(struct task_struct *p) +{ + if (!p || !(p->nsproxy) || + !(p->nsproxy->mnt_ns) || + !(p->nsproxy->mnt_ns->root)) + return NULL; + + return ((struct kdp_mount *)p->nsproxy->mnt_ns->root)->mnt; +} + +static bool hyp_check_page_ro(u64 va) +{ + unsigned long flags; + u64 par = 0; + + raw_spin_lock_irqsave(&par_lock, flags); + uh_call(UH_APP_KDP, TEST_GET_PAR, (unsigned long)va, KDP_PA_WRITE, 0, 0); + par = *ha1; + raw_spin_unlock_irqrestore(&par_lock, flags); + + return (par & 0x1) ? true : false; +} + +static int test_case_kdp_ro(int cmd_id) +{ + struct task_struct *p = NULL; + u64 ro = 0, rw = 0, dst; + + for_each_process(p) { + switch (cmd_id) { + case CMD_ID_CRED: + /* Here dst points to struct cred */ + dst = (u64)__task_cred(p); + break; + case CMD_ID_SEC_CONTEXT: + /* Here dst points to process security context */ + dst = (u64)__task_cred(p)->security; + break; + case CMD_ID_NS: + /* Here dst points to process security context */ + dst = (u64)get_vfsmnt(p); + break; + } + + if (!dst) + continue; + + if (hyp_check_page_ro(dst)) + ro++; + else + rw++; + } + + kdp_print("ro: %llu, rw: %llu\n", ro, rw); + return rw ? 1 : 0; +} + +static int cred_match(struct task_struct *p, const struct cred *cred) +{ + struct mm_struct *mm = p->mm; + pgd_t *tgt = NULL; + + if (((struct cred_kdp *)cred)->bp_task != p) { + kdp_print("KDP_WARN task: #%s# cred: %p, task: %p bp_task: %p\n", + p->comm, cred, p, ((struct cred_kdp *)cred)->bp_task); + return 0; + } + + if (!(in_interrupt() || in_softirq())) + return 1; + + tgt = mm ? mm->pgd : init_mm.pgd; + if (((struct cred_kdp *)cred)->bp_pgd != tgt) { + kdp_print("KDP_WARN task: #%s# cred: %p, mm: %p, init_mm: %p, pgd: %p, bp_pgd: %p\n", + p->comm, cred, mm, init_mm.pgd, tgt, ((struct cred_kdp *)cred)->bp_pgd); + return 0; + } + + return 1; +} + +static int sec_context_match(const struct cred *cred) +{ + struct task_security_struct *tsec = (struct task_security_struct *)cred->security; + + if ((u64)tsec->bp_cred != (u64)cred) + return 0; + + return 1; +} + +static int test_case_match_bp(int cmd_id) +{ + struct task_struct *p = NULL; + u64 match = 0, mismatch = 0, ret = 0; + + for_each_process(p) { + switch (cmd_id) { + case CMD_ID_CRED: + /*Here dst points to struct cred*/ + ret = cred_match(p, __task_cred(p)); + break; + case CMD_ID_SEC_CONTEXT: + /*Here dst points to process security context*/ + ret = sec_context_match(__task_cred(p)); + break; + } + ret ? match++ : mismatch++; + } + kdp_print("match: %llu, mismatch: %llu\n", match, mismatch); + return mismatch ? 1 : 0; +} + +static int test_case_cred_ro(void) +{ + kdp_print("CRED PROTECTION "); + return test_case_kdp_ro(CMD_ID_CRED); +} + +static int test_case_sec_context_ro(void) +{ + kdp_print("SECURITY CONTEXT PROTECTION "); + return test_case_kdp_ro(CMD_ID_SEC_CONTEXT); +} + +static int test_case_cred_match_bp(void) +{ + kdp_print("CRED Back Poiner check "); + return test_case_match_bp(CMD_ID_CRED); +} + +static int test_case_sec_context_match_bp(void) +{ + kdp_print("Security Context Back Poiner check "); + return test_case_match_bp(CMD_ID_SEC_CONTEXT); +} + +static int test_case_ns_ro(void) +{ + kdp_print("NAMESPACE PROTECTION "); + return test_case_kdp_ro(CMD_ID_NS); +} + +#ifdef CONFIG_KDP_SEC_TEST +enum { + CMD_ID_COMMIT_CRED, + CMD_ID_OVERRIDE_CRED, + CMD_ID_REVERT_CRED, +}; + +enum { + CMD_ID_SEC_WRITE_CRED, + CMD_ID_SEC_WRITE_SP, + CMD_ID_SEC_DIRECT_PE, + CMD_ID_SEC_INDIRECT_PE_COMMIT, + CMD_ID_SEC_INDIRECT_PE_OVERRIDE, + CMD_ID_SEC_INDIRECT_PE_REVERT, +}; + +#define PROCFS_MAX_SIZE 64 +void write_ro(u64 *p) +{ + memcpy(p, "c", 1); +} +static void sec_test_cred(void) +{ + write_ro((u64 *)current_cred()); +} +static void sec_test_sp(void) +{ + write_ro((u64 *)current_cred()->security); +} + +struct cred *get_root_cred(void) +{ + struct cred *cred; + + cred = prepare_creds(); + cred->uid.val = 0; + cred->gid.val = 0; + cred->euid.val = 0; + cred->egid.val = 0; + + return cred; +} + +static void sec_test_cred_direct_pe(void) +{ + struct cred *rcred; + + rcred = get_root_cred(); + current->cred = rcred; +} + +static void sec_test_cred_indirect_pe(int cmd_id) +{ + struct cred *rcred; + + rcred = get_root_cred(); + pr_info("RKP_SEC_TEST #%d# BEFORE current cred uid = %llx euid = %llx gid = %llx egid = %llx Root Cred%llx\n", + cmd_id, current->cred->uid.val, current->cred->euid.val, current->cred->gid.val, + current->cred->egid.val, (u64)rcred); + + switch (cmd_id) { + case CMD_ID_COMMIT_CRED: + commit_creds(rcred); + break; + case CMD_ID_OVERRIDE_CRED: + override_creds(rcred); + break; + case CMD_ID_REVERT_CRED: + revert_creds(rcred); + break; + } + + pr_info("RKP_SEC_TEST#%d# AFTER current cred uid = %llx euid = %llx gid = %llx egid = %llx Root Cred %llx\n", + cmd_id, current->cred->uid.val, current->cred->euid.val, current->cred->gid.val, + current->cred->egid.val, (u64)rcred); +} + +ssize_t kdp_test_write(struct file *filep, const char __user *buffer, size_t len, loff_t *offset) +{ + char procfs_buffer[PROCFS_MAX_SIZE]; + int buff_size; + int tcase; + + buff_size = (len > PROCFS_MAX_SIZE)?PROCFS_MAX_SIZE:len; + + if (copy_from_user(procfs_buffer, buffer, buff_size)) + return -EFAULT; + + sscanf(procfs_buffer, "%d", &tcase); + switch (tcase) { + case CMD_ID_SEC_WRITE_CRED: + sec_test_cred(); + break; + case CMD_ID_SEC_WRITE_SP: + sec_test_sp(); + break; + case CMD_ID_SEC_DIRECT_PE: + sec_test_cred_direct_pe(); + break; + case CMD_ID_SEC_INDIRECT_PE_COMMIT: + sec_test_cred_indirect_pe(CMD_ID_COMMIT_CRED); + break; + case CMD_ID_SEC_INDIRECT_PE_OVERRIDE: + sec_test_cred_indirect_pe(CMD_ID_OVERRIDE_CRED); + break; + case CMD_ID_SEC_INDIRECT_PE_REVERT: + sec_test_cred_indirect_pe(CMD_ID_REVERT_CRED); + break; + } + return len; +} +#endif /* CONFIG_KDP_SEC_TEST*/ + +ssize_t kdp_test_read(struct file *filep, char __user *buffer, size_t count, loff_t *ppos) +{ + int ret = 0, temp_ret = 0, i = 0; + struct test_case tc_funcs[] = { + {test_case_cred_ro, "TEST TASK_CRED_RO"}, + {test_case_sec_context_ro, "TEST TASK_SECURITY_CONTEXT_RO"}, + {test_case_cred_match_bp, "TEST CRED_MATCH_BACKPOINTERS"}, + {test_case_sec_context_match_bp, "TEST TASK_SEC_CONTEXT_BACKPOINTER"}, + {test_case_ns_ro, "TEST NAMESPACE_RO"}, + }; + int tc_num = sizeof(tc_funcs)/sizeof(struct test_case); + + static bool done; + + if (done) + return 0; + + done = true; + + for (i = 0; i < tc_num; i++) { + kdp_print("KDP_TEST_CASE %d ===========> RUNNING %s\n", i, tc_funcs[i].describe); + temp_ret = tc_funcs[i].fn(); + + if (temp_ret) { + kdp_print("KDP_TEST_CASE %d ===========> %s FAILED WITH %d ERRORS\n", + i, tc_funcs[i].describe, temp_ret); + } else { + kdp_print("KDP_TEST_CASE %d ===========> %s PASSED\n", i, tc_funcs[i].describe); + } + + ret += temp_ret; + } + + if (ret) + kdp_print("KDP_TEST SUMMARY: FAILED WITH %d ERRORS\n", ret); + else + kdp_print("KDP_TEST SUMMARY: PASSED\n"); + + return simple_read_from_buffer(buffer, count, ppos, kdp_test_buf, kdp_test_len); +} + +static const struct proc_ops kdp_proc_ops = { + .proc_read = kdp_test_read, +#ifdef CONFIG_KDP_SEC_TEST + .proc_write = kdp_test_write, +#endif +}; + +static int __init kdp_test_init(void) +{ + u64 va; + +#ifndef CONFIG_KDP_SEC_TEST + if (proc_create("kdp_test", 0444, NULL, &kdp_proc_ops) == NULL) { +#else + if (proc_create("kdp_test", 0777, NULL, &kdp_proc_ops) == NULL) { +#endif + pr_err("KDP_TEST: Error creating proc entry"); + return -1; + } + + va = __get_free_page(GFP_KERNEL | __GFP_ZERO); + if (!va) + return -1; + + uh_call(UH_APP_KDP, TEST_INIT, va, 0, 0, 1); + + ha1 = (u64 *)va; + + return 0; +} + +static void __exit kdp_test_exit(void) +{ + uh_call(UH_APP_KDP, TEST_EXIT, (u64)ha1, 0, 0, 0); + free_page((unsigned long)ha1); + + remove_proc_entry("kdp_test", NULL); +} + +module_init(kdp_test_init); +module_exit(kdp_test_exit); diff --git a/drivers/uh/rkp.c b/drivers/uh/rkp.c new file mode 100755 index 000000000000..cf296bed1cee --- /dev/null +++ b/drivers/uh/rkp.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include + +bool rkp_started __rkp_ro = false; +static u64 robuffer_base __rkp_ro; +static u64 robuffer_size __rkp_ro; + +/* init/main.c */ +void __init rkp_init(void) +{ + struct rkp_init init_data; + + memset((void *)&init_data, 0, sizeof(struct rkp_init)); + /* initialized rkp_init struct */ + init_data.magic = RKP_INIT_MAGIC; + init_data.vmalloc_start = VMALLOC_START; +#ifndef CONFIG_RKP_FIMC_CHECK + init_data.no_fimc_verify = 1; +#endif + init_data.fimc_phys_addr = 0; + init_data._text = (u64)_stext; + init_data._etext = (u64)_etext; + init_data._srodata = (u64)__start_rodata; + init_data._erodata = (u64)__end_rodata; + init_data.large_memory = 0; + + init_data.vmalloc_end = (u64)high_memory; + init_data.init_mm_pgd = (u64)__pa(swapper_pg_dir); + init_data.id_map_pgd = (u64)__pa(idmap_pg_dir); + init_data.zero_pg_addr = (u64)__pa(empty_zero_page); + +#ifdef CONFIG_UNMAP_KERNEL_AT_EL0 + init_data.tramp_pgd = (u64)__pa(tramp_pg_dir); + init_data.tramp_valias = (u64)TRAMP_VALIAS; +#endif + uh_call(UH_APP_RKP, RKP_START, (u64)&init_data, (u64)kimage_voffset, 0, 0); + rkp_started = true; +} + +/* init/main.c */ +void rkp_deferred_init(void) +{ + uh_call(UH_APP_RKP, RKP_DEFERRED_START, 0, 0, 0, 0); +} + +/* RO BUFFER */ +void rkp_robuffer_init(void) +{ + uh_call(UH_APP_RKP, RKP_GET_RO_INFO, (u64)&robuffer_base, (u64)&robuffer_size, 0, 0); +} + +inline phys_addr_t rkp_ro_alloc_phys(int shift) +{ + phys_addr_t alloc_addr = 0; + + uh_call(UH_APP_RKP, RKP_ROBUFFER_ALLOC, (u64)&alloc_addr, 1, 0, 0); + + return alloc_addr; +} + +inline phys_addr_t rkp_ro_alloc_phys_for_text(void) +{ + phys_addr_t alloc_addr = 0; + + uh_call(UH_APP_RKP, RKP_ROBUFFER_ALLOC, (u64)&alloc_addr, 1, 1, 0); + + return alloc_addr; +} + +inline void *rkp_ro_alloc(void) +{ + void *addr = NULL; + + uh_call(UH_APP_RKP, RKP_ROBUFFER_ALLOC, (u64)&addr, 1, 0, 0); + if (!addr) + return 0; + + return (void *)__phys_to_virt(addr); +} + +inline void rkp_ro_free(void *addr) +{ + uh_call(UH_APP_RKP, RKP_ROBUFFER_FREE, (u64)addr, 0, 0, 0); +} + +/* +inline bool is_rkp_ro_buffer(u64 addr) +{ + u64 pa = (u64)virt_to_phys((void *)addr); + + if ((robuffer_base <= pa) && (pa < robuffer_base + robuffer_size)) + return true; + else + return false; +} +*/ + +inline bool is_rkp_ro_buffer(u64 addr) +{ + u64 va = (u64)phys_to_virt(robuffer_base); + + if ((va <= addr) && (addr < va + robuffer_size)) + return true; + else + return false; +} diff --git a/drivers/uh/rkp_module_support.c b/drivers/uh/rkp_module_support.c new file mode 100644 index 000000000000..7c164fcc8644 --- /dev/null +++ b/drivers/uh/rkp_module_support.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include +#include +#include + +void *__vmalloc_node_range_for_module(unsigned long core_layout_size, unsigned long core_text_size, + unsigned long align, unsigned long start, unsigned long end, gfp_t gfp_mask, + pgprot_t prot, unsigned long vm_flags, int node, + const void *caller); + +void *module_alloc_by_rkp(unsigned int core_layout_size, unsigned int core_text_size) +{ + u64 module_alloc_end = module_alloc_base + MODULES_VSIZE; + gfp_t gfp_mask = GFP_KERNEL; + void *p; + + /* Silence the initial allocation */ + if (IS_ENABLED(CONFIG_ARM64_MODULE_PLTS)) + gfp_mask |= __GFP_NOWARN; + + if (IS_ENABLED(CONFIG_KASAN_GENERIC) || + IS_ENABLED(CONFIG_KASAN_SW_TAGS)) + /* don't exceed the static module region - see below */ + module_alloc_end = MODULES_END; + + p = __vmalloc_node_range_for_module(core_layout_size, core_text_size, MODULE_ALIGN, module_alloc_base, + module_alloc_end, gfp_mask, PAGE_KERNEL, VM_DEFER_KMEMLEAK, + NUMA_NO_NODE, __builtin_return_address(0)); + + if (!p && IS_ENABLED(CONFIG_ARM64_MODULE_PLTS) && + (IS_ENABLED(CONFIG_KASAN_VMALLOC) || + (!IS_ENABLED(CONFIG_KASAN_GENERIC) && + !IS_ENABLED(CONFIG_KASAN_SW_TAGS)))) + /* + * KASAN without KASAN_VMALLOC can only deal with module + * allocations being served from the reserved module region, + * since the remainder of the vmalloc region is already + * backed by zero shadow pages, and punching holes into it + * is non-trivial. Since the module region is not randomized + * when KASAN is enabled without KASAN_VMALLOC, it is even + * less likely that the module region gets exhausted, so we + * can simply omit this fallback in that case. + */ + p = __vmalloc_node_range_for_module(core_layout_size, core_text_size, MODULE_ALIGN, module_alloc_base, + module_alloc_base + SZ_2G, GFP_KERNEL, + PAGE_KERNEL, 0, NUMA_NO_NODE, + __builtin_return_address(0)); + + if (p && (kasan_alloc_module_shadow(p, core_layout_size, gfp_mask) < 0)) { + vfree(p); + return NULL; + } + + /* Memory is intended to be executable, reset the pointer tag. */ + return kasan_reset_tag(p); +} diff --git a/drivers/uh/rkp_test.c b/drivers/uh/rkp_test.c new file mode 100755 index 000000000000..b09987e01747 --- /dev/null +++ b/drivers/uh/rkp_test.c @@ -0,0 +1,638 @@ +#include +#include +#include +#include +#include +#include +#include + +/* + * BIT[0:1] TYPE PXN BIT + * 01 BLOCK 53 For LEVEL 0, 1, 2 //defined by L012_BLOCK_PXN + * 11 TABLE 59 For LEVEL 0, 1, 2 //defined by L012_TABLE_PXN + * 11 PAGE 53 For LEVEL 3 //defined by L3_PAGE_PXN + */ +#define L012_BLOCK_PXN (_AT(pmdval_t, 1) << 53) +#define L012_TABLE_PXN (_AT(pmdval_t, 1) << 59) +#define L3_PAGE_PXN (_AT(pmdval_t, 1) << 53) + +#define MEM_END 0xfffffffffffff000 /* 4K aligned */ +#define DESC_MASK 0xFFFFFFFFF000 + +#define RKP_PA_READ 0 +#define RKP_PA_WRITE 1 + +/* BUF define */ +#define RKP_BUF_SIZE 8192 +#define RKP_LINE_MAX 80 + +/* FIMC */ +#define CDH_SIZE SZ_128K /* CDH : Camera Debug Helper */ +#define IS_RCHECKER_SIZE_RO (SZ_4M + SZ_1M) +#define IS_RCHECKER_SIZE_RW (SZ_256K) +#define RCHECKER_SIZE (IS_RCHECKER_SIZE_RO + IS_RCHECKER_SIZE_RW) + +#ifdef CONFIG_KASAN +#define LIB_OFFSET (VMALLOC_START + 0xF6000000 - 0x8000000) +#else +#define LIB_OFFSET (VMALLOC_START + 0x1000000000UL + 0xF6000000 - 0x8000000) +#endif + +#define __LIB_START (LIB_OFFSET + 0x04000000 - CDH_SIZE) +#define LIB_START (__LIB_START) + +#define VRA_LIB_ADDR (LIB_START + CDH_SIZE) +#define VRA_LIB_SIZE (SZ_512K + SZ_256K) + +#define DDK_LIB_ADDR (LIB_START + VRA_LIB_SIZE + CDH_SIZE) +#define DDK_LIB_SIZE ((SZ_2M + SZ_1M + SZ_256K) + SZ_1M + RCHECKER_SIZE) + +#define RTA_LIB_ADDR (LIB_START + VRA_LIB_SIZE + DDK_LIB_SIZE + CDH_SIZE) +#define RTA_LIB_SIZE (SZ_2M + SZ_2M) + +#define VRA_CODE_SIZE SZ_512K +#define VRA_DATA_SIZE SZ_256K + +#define DDK_CODE_SIZE (SZ_2M + SZ_1M + SZ_256K + IS_RCHECKER_SIZE_RO) +#define DDK_DATA_SIZE SZ_1M + +#define RTA_CODE_SIZE SZ_2M +#define RTA_DATA_SIZE SZ_2M + +#define LIB_END (RTA_LIB_ADDR + RTA_CODE_SIZE + RTA_DATA_SIZE) + +static char rkp_test_buf[RKP_BUF_SIZE]; +static unsigned long rkp_test_len = 0; +static unsigned long prot_user_l2 = 1; + +static DEFINE_RAW_SPINLOCK(par_lock); +static u64 *ha1; +static u64 *ha2; + +struct test_data { + u64 iter; + u64 pxn; + u64 no_pxn; + u64 read; + u64 write; + u64 cred_bkptr_match; + u64 cred_bkptr_mismatch; +}; + +static void buf_print(const char *fmt, ...) +{ + va_list aptr; + + if (rkp_test_len > RKP_BUF_SIZE - RKP_LINE_MAX) { + pr_err("RKP_TEST: Error Maximum buf"); + return; + } + va_start(aptr, fmt); + rkp_test_len += vsprintf(rkp_test_buf+rkp_test_len, fmt, aptr); + va_end(aptr); +} + +//if RO, return true; RW return false +static bool hyp_check_page_ro(u64 va) +{ + unsigned long flags; + u64 par = 0; + + raw_spin_lock_irqsave(&par_lock, flags); + uh_call(UH_APP_RKP, RKP_TEST_GET_PAR, (unsigned long)va, RKP_PA_WRITE, 0, 0); + par = *ha1; + raw_spin_unlock_irqrestore(&par_lock, flags); + + return (par & 0x1) ? true : false; +} + +static void hyp_check_l23pgt_rw(u64 *pg_l, unsigned int level, struct test_data *test) +{ + unsigned int i; + + // Level is 1 2 + if (level >= 3) + return; + + for (i = 0; i < 512; i++) { + if ((pg_l[i] & 3) == 3) { + test[level].iter++; + if (hyp_check_page_ro((u64)phys_to_virt(pg_l[i] & DESC_MASK))) + test[level].read++; + else + test[level].write++; + + hyp_check_l23pgt_rw((u64 *) (phys_to_virt(pg_l[i] & DESC_MASK)), level + 1, test); + } + } +} + +static pmd_t *get_addr_pmd(struct mm_struct *mm, unsigned long addr) +{ + pgd_t *pgd; + pud_t *pud; + pmd_t *pmd; + + pgd = pgd_offset(mm, addr); + if (pgd_none(*pgd)) + return NULL; + + pud = pud_offset((p4d_t *)pgd, addr); + if (pud_none(*pud)) + return NULL; + + pmd = pmd_offset(pud, addr); + if (pmd_none(*pmd)) + return NULL; + + return pmd; +} + +static int test_case_user_pgtable_ro(void) +{ + struct task_struct *task; + struct test_data test[3] = {{0}, {0}, {0} }; + struct mm_struct *mm = NULL; + int i; + + for_each_process(task) { + mm = task->active_mm; + if (!(mm) || !(mm->context.id.counter) || !(mm->pgd)) + continue; + + if (hyp_check_page_ro((u64)(mm->pgd))) + test[0].read++; + else + test[0].write++; + + test[0].iter++; + hyp_check_l23pgt_rw(((u64 *) (mm->pgd)), 1, test); + } + + for (i = 0; i < 3; i++) { + buf_print("\t\tL%d TOTAL PAGES %6llu | READ ONLY %6llu | WRITABLE %6llu\n", + i+1, test[i].iter, test[i].read, test[i].write); + } + + //L1 and L2 pgtable should be RO + if ((!prot_user_l2) && (test[0].write == 0)) + return 0; + + if ((test[0].write == 0) && (test[1].write == 0)) + return 0; //pass + else + return 1; //fail +} + +static int test_case_kernel_pgtable_ro(void) +{ + struct test_data test[3] = {{0}, {0}, {0} }; + int i = 0; + // Check for swapper_pg_dir + test[0].iter++; + if (hyp_check_page_ro((u64)swapper_pg_dir)) + test[0].read++; + else + test[0].write++; + + hyp_check_l23pgt_rw((u64 *)swapper_pg_dir, 1, test); + + for (i = 0; i < 3; i++) + buf_print("\t\tL%d TOTAL PAGE TABLES %6llu | READ ONLY %6llu |WRITABLE %6llu\n", + i+1, test[i].iter, test[i].read, test[i].write); + + if ((test[0].write == 0) && (test[1].write == 0)) + return 0; + else + return 1; +} + +static int test_case_kernel_l3pgt_ro(void) +{ + int rw = 0, ro = 0, i = 0; + u64 addrs[] = { + (u64)_text, + (u64)_etext + }; + int len = sizeof(addrs)/sizeof(u64); + + pmd_t *pmd; + u64 pgt_addr; + + for (i = 0; i < len; i++) { + pmd = get_addr_pmd(&init_mm, addrs[i]); + + pgt_addr = (u64)phys_to_virt(((u64)(pmd_val(*pmd))) & DESC_MASK); + if (hyp_check_page_ro(pgt_addr)) + ro++; + else + rw++; + } + + buf_print("\t\tKERNEL TEXT HEAD TAIL L3PGT | RO %6u | RW %6u\n", ro, rw); + return (rw == 0) ? 0 : 1; +} + +// return true if addr mapped, otherwise return false +static bool page_pxn_set(unsigned long addr, u64 *xn, u64 *x, u64 *v_x) +{ + pgd_t *pgd; + pud_t *pud; + pmd_t *pmd; + pte_t *pte; + + pgd = pgd_offset_k(addr); + if (pgd_none(*pgd)) + return false; + + pud = pud_offset((p4d_t *)pgd, addr); + if (pud_none(*pud)) + return false; + + if (pud_sect(*pud)) { + if ((pud_val(*pud) & L012_BLOCK_PXN) > 0) + *xn += 1; + else + *x += 1; + return true; + } + + pmd = pmd_offset(pud, addr); + if (pmd_none(*pmd)) + return false; + + if (pmd_sect(*pmd)) { + if ((pmd_val(*pmd) & L012_BLOCK_PXN) > 0) + *xn += 1; + else + *x += 1; + return true; + } + + if ((pmd_val(*pmd) & L012_TABLE_PXN) > 0) { + *xn += 1; + return true; + } + + // If pmd is table, such as kernel text head and tail, need to check L3 + pte = pte_offset_kernel(pmd, addr); + if (pte_none(*pte)) + return false; + + if ((pte_val(*pte) & L3_PAGE_PXN) > 0) + *xn += 1; + else { + if (addr >= (u64)__end_rodata) { + u64 res = 0; + uh_call(UH_APP_RKP, RKP_TEST_TEXT_VALID, addr, (u64)&res, 0, 0); + if (res) + *v_x += 1; + } + *x += 1; + } + return true; +} + +static void count_pxn(unsigned long pxn, int level, struct test_data *test) +{ + test[level].iter++; + if (pxn) + test[level].pxn++; + else + test[level].no_pxn++; +} + +static void walk_pte(pmd_t *pmd, int level, struct test_data *test) +{ + pte_t *pte = pte_offset_kernel(pmd, 0UL); + unsigned int i; + unsigned long prot; + + for (i = 0; i < PTRS_PER_PTE; i++, pte++) { + if (pte_none(*pte)) { + continue; + } else { + prot = pte_val(*pte) & L3_PAGE_PXN; + count_pxn(prot, level, test); + } + } +} + +static void walk_pmd(pud_t *pud, int level, struct test_data *test) +{ + pmd_t *pmd = pmd_offset(pud, 0UL); + unsigned int i; + unsigned long prot; + + for (i = 0; i < PTRS_PER_PMD; i++, pmd++) { + if (pmd_none(*pmd)) { + continue; + } else if (pmd_sect(*pmd)) { + prot = pmd_val(*pmd) & L012_BLOCK_PXN; + count_pxn(prot, level, test); + } else { + /* + * For user space, all L2 should have PXN, including block and + * table. Only kernel text head and tail L2 table can have no + * pxn, and kernel text middle L2 blocks can have no pxn + */ + BUG_ON(pmd_bad(*pmd)); + prot = pmd_val(*pmd) & L012_TABLE_PXN; + count_pxn(prot, level, test); + walk_pte(pmd, level+1, test); + } + } +} + +static void walk_pud(pgd_t *pgd, int level, struct test_data *test) +{ + pud_t *pud = pud_offset((p4d_t *)pgd, 0UL); + unsigned int i; + + for (i = 0; i < PTRS_PER_PUD; i++, pud++) { + if (pud_none(*pud) || pud_sect(*pud)) { + continue; + } else { + BUG_ON(pud_bad(*pud)); + walk_pmd(pud, level, test); + } + } +} + +#define rkp_pgd_table (_AT(pgdval_t, 1) << 1) +#define rkp_pgd_bad(pgd) (!(pgd_val(pgd) & rkp_pgd_table)) +static void walk_pgd(struct mm_struct *mm, int level, struct test_data *test) +{ + pgd_t *pgd = pgd_offset(mm, 0UL); + unsigned int i; + unsigned long prot; + + for (i = 0; i < PTRS_PER_PGD; i++, pgd++) { + if (rkp_pgd_bad(*pgd)) { + continue; + } else { //table + prot = pgd_val(*pgd) & L012_TABLE_PXN; + count_pxn(prot, level, test); + walk_pud(pgd, level+1, test); + } + } +} + +#define MB (1024 * 1024) +#define ROBUFS_MAX (20 * MB / PAGE_SIZE) +#define ALLOC_DELAY 10 +#define ALLOC_TRY_MAX 20 + +static int test_case_guest_mem_alloc_and_free(void) { + u64* robufs; + int i, size = 0, alloc_try_cnt; + + robufs = kmalloc_array(ROBUFS_MAX, sizeof(u64), GFP_KERNEL); + + buf_print("guest_mem allocation start\n"); + for (i = 0; i < ROBUFS_MAX; i++) { + alloc_try_cnt = 0; + robufs[i] = (u64)rkp_ro_alloc(); + + while (!robufs[i] && alloc_try_cnt < ALLOC_TRY_MAX) { + alloc_try_cnt++; + + printk("ALLOC_TRY_CNT %d, Let's wait %d ms", alloc_try_cnt, ALLOC_DELAY); + buf_print("ALLOC_TRY_CNT %d, Let's wait %d ms\n", alloc_try_cnt, ALLOC_DELAY); + + msleep(ALLOC_DELAY); + robufs[i] = (u64)rkp_ro_alloc(); + } + + if (alloc_try_cnt >= ALLOC_TRY_MAX) { + break; + } + } + + size = i; + printk("guest_mem allocation done. allocated size: 0x%lx", size * PAGE_SIZE); + buf_print("guest_mem allocation done. allocated size: 0x%lx\n", size * PAGE_SIZE); + + msleep(100); + + printk("guest_mem free start"); + buf_print("guest_mem free start\n"); + + for (i = 0; i < size; i++) { + rkp_ro_free((void *) robufs[i]); + } + + printk("guest_mem free done."); + buf_print("guest_mem free done.\n"); + + kfree(robufs); + + return 0; +} + +static int test_case_user_pxn(void) +{ + struct task_struct *task = NULL; + struct mm_struct *mm = NULL; + struct test_data test[3] = {{0}, {0}, {0} }; + int i = 0; + + for_each_process(task) { + mm = task->active_mm; + if (!(mm) || !(mm->context.id.counter) || !(mm->pgd)) + continue; + + /* Check if PXN bit is set */ + walk_pgd(mm, 0, test); + } + + for (i = 0; i < 3; i++) { + buf_print("\t\tL%d TOTAL ENTRIES %6llu | PXN %6llu | NO_PXN %6llu\n", + i+1, test[i].iter, test[i].pxn, test[i].no_pxn); + } + + //all 2nd level entries should be PXN + if (test[0].no_pxn == 0) { + prot_user_l2 = 0; + return 0; + } else if (test[1].no_pxn == 0) { + prot_user_l2 = 1; + return 0; + } else { + return 1; + } +} + +struct mem_range { + u64 start_va; + u64 size; //in bytes + char *info; + bool no_rw; + bool no_x; +}; + +struct test_case { + int (*fn)(void); + char *describe; +}; + +static int test_case_kernel_range_rwx(void) +{ + int ret = 0; + u64 ro = 0, rw = 0; + u64 xn = 0, x = 0; + u64 v_x = 0; + int i; + u64 j; + bool mapped = false; + u64 va_temp; + + struct mem_range test_ranges[] = { + {(u64)VMALLOC_START, ((u64)_text) - ((u64)VMALLOC_START), "VMALLOC - STEXT", false, true}, + {((u64)_text), ((u64)_etext) - ((u64)_text), "STEXT - ETEXT", true, false}, + {((u64)_etext), ((u64) __end_rodata) - ((u64)_etext), "ETEXT - ERODATA", true, true}, +#ifdef CONFIG_USE_DIRECT_IS_CONTROL /* FIMC */ + {((u64) __end_rodata), VRA_LIB_ADDR-((u64) __end_rodata), "ERODATA - S_FIMC", false, true}, + {VRA_LIB_ADDR, VRA_CODE_SIZE, "VRA CODE", true, false}, + {VRA_LIB_ADDR + VRA_CODE_SIZE, VRA_DATA_SIZE, "VRA DATA", false, true}, + {DDK_LIB_ADDR, DDK_CODE_SIZE, "DDK CODE", true, false}, + {DDK_LIB_ADDR + DDK_CODE_SIZE, DDK_DATA_SIZE, "DDK_DATA", false, true}, + {RTA_LIB_ADDR, RTA_CODE_SIZE, "RTA CODE", true, false}, + {RTA_LIB_ADDR + RTA_CODE_SIZE, RTA_DATA_SIZE, "RTA DATA", false, true}, + {LIB_END, MEM_END - LIB_END, "E_FIMC - MEM END", false, true}, +#else + {((u64) __end_rodata), MEM_END-((u64) __end_rodata), "ERODATA -MEM_END", false, true}, +#endif + + }; + int len = sizeof(test_ranges)/sizeof(struct mem_range); + + buf_print("\t\t| MEMORY RANGES | %16s - %16s | %8s %8s %8s %8s\n", + "START", "END", "RO", "RW", "PXN", "PX"); + for (i = 0; i < len; i++) { + for (j = 0; j < test_ranges[i].size/PAGE_SIZE; j++) { + va_temp = test_ranges[i].start_va + j*PAGE_SIZE; + mapped = page_pxn_set(va_temp, &xn, &x, &v_x); + if (!mapped) + continue; + // only for mapped pages + if (hyp_check_page_ro(va_temp)) + ro += 1; + else + rw += 1; + } + + buf_print("\t\t|%s| %016llx - %016llx | %8llu %8llu %8llu %8llu\n", + test_ranges[i].info, test_ranges[i].start_va, + test_ranges[i].start_va + test_ranges[i].size, + ro, rw, xn, x); + + if (test_ranges[i].no_rw && (rw != 0)) { + buf_print("RKP_TEST FAILED, NO RW PAGE ALLOWED, rw=%llu\n", rw); + ret++; + } + + if (test_ranges[i].no_x && (x != 0)) { + if (x == v_x) + break; + buf_print("RKP_TEST FAILED, NO X PAGE ALLOWED, x=%llu\n", x); + ret++; + } + + if ((rw != 0) && (x != 0)) { + if (x == v_x) + break; + buf_print("RKP_TEST FAILED, NO RWX PAGE ALLOWED, rw=%llu, x=%llu\n", rw, x); + ret++; + } + + ro = 0; rw = 0; + xn = 0; x = 0; + v_x = 0; + } + + return ret; +} + +ssize_t rkp_read(struct file *filep, char __user *buffer, size_t count, loff_t *ppos) +{ + int ret = 0, temp_ret = 0, i = 0; + struct test_case tc_funcs[] = { + {test_case_user_pxn, "TEST USER_PXN"}, + {test_case_user_pgtable_ro, "TEST USER_PGTABLE_RO"}, + {test_case_kernel_pgtable_ro, "TEST KERNEL_PGTABLE_RO"}, + {test_case_kernel_l3pgt_ro, "TEST KERNEL TEXT HEAD TAIL L3PGT RO"}, + {test_case_kernel_range_rwx, "TEST KERNEL_RANGE_RWX"}, + {test_case_guest_mem_alloc_and_free, "TEST GUEST_MEM_ALLOC_AND_FREE"}, + }; + int tc_num = sizeof(tc_funcs)/sizeof(struct test_case); + + static bool done = false; + + if (done) + return 0; + done = true; + + if ((!ha1) || (!ha2)) { + buf_print("ERROR RKP_TEST ha1 is NULL\n"); + goto error; + } + + for (i = 0; i < tc_num; i++) { + buf_print("RKP_TEST_CASE %d ===========> RUNNING %s\n", i, tc_funcs[i].describe); + temp_ret = tc_funcs[i].fn(); + + if (temp_ret) { + buf_print("RKP_TEST_CASE %d ===========> %s FAILED WITH %d ERRORS\n", + i, tc_funcs[i].describe, temp_ret); + } else { + buf_print("RKP_TEST_CASE %d ===========> %s PASSED\n", i, tc_funcs[i].describe); + } + + ret += temp_ret; + } + + if (ret) + buf_print("RKP_TEST SUMMARY: FAILED WITH %d ERRORS\n", ret); + else + buf_print("RKP_TEST SUMMARY: PASSED\n"); + +error: + return simple_read_from_buffer(buffer, count, ppos, rkp_test_buf, rkp_test_len); +} + +static const struct proc_ops rkp_proc_fops = { + .proc_read = rkp_read, +}; + +static int __init rkp_test_init(void) +{ + u64 va; + + if (proc_create("rkp_test", 0444, NULL, &rkp_proc_fops) == NULL) { + pr_err("RKP_TEST: Error creating proc entry"); + return -1; + } + + va = __get_free_page(GFP_KERNEL | __GFP_ZERO); + if (!va) + return -1; + uh_call(UH_APP_RKP, RKP_TEST_INIT, va, 0, 0, 0); + + ha1 = (u64 *)va; + ha2 = (u64 *)(va + 8); + + return 0; +} + +static void __exit rkp_test_exit(void) +{ + uh_call(UH_APP_RKP, RKP_TEST_EXIT, (u64)ha1, 0, 0, 0); + free_page((unsigned long)ha1); + + remove_proc_entry("rkp_test", NULL); +} + +module_init(rkp_test_init); +module_exit(rkp_test_exit); + diff --git a/drivers/uh/uh_debug_log.c b/drivers/uh/uh_debug_log.c new file mode 100755 index 000000000000..a689122d5822 --- /dev/null +++ b/drivers/uh/uh_debug_log.c @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include +#include + +ssize_t uh_log_read(struct file *filep, char __user *buf, size_t size, loff_t *offset) +{ + static size_t log_buf_size; + unsigned long *log_addr = 0; + + if (!strcmp(filep->f_path.dentry->d_iname, "uh_log")) + log_addr = (unsigned long *)phys_to_virt(UH_LOG_START); + else + return -EINVAL; + + /* To print s2 table status */ + uh_call(UH_APP_INIT, 13, 0, 0, 0, 0); + + if (!*offset) { + log_buf_size = 0; + while (log_buf_size < UH_LOG_SIZE && ((char *)log_addr)[log_buf_size] != 0) + log_buf_size++; + } + + if (*offset >= log_buf_size) + return 0; + + if (*offset + size > log_buf_size) + size = log_buf_size - *offset; + + if (copy_to_user(buf, (const char *)log_addr + (*offset), size)) + return -EFAULT; + + *offset += size; + return size; +} + +static const struct proc_ops uh_proc_ops = { + .proc_read = uh_log_read, +}; + +static int __init uh_log_init(void) +{ + struct proc_dir_entry *entry; + + entry = proc_create("uh_log", 0644, NULL, &uh_proc_ops); + if (!entry) { + pr_err("uh_log: Error creating proc entry\n"); + return -ENOMEM; + } + + pr_info("uh_log : create /proc/uh_log\n"); + return 0; +} + +static void __exit uh_log_exit(void) +{ + remove_proc_entry("uh_log", NULL); +} + +module_init(uh_log_init); +module_exit(uh_log_exit); diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 8e0d1313e7ed..0d03d3b93d6a 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -26,6 +26,15 @@ config USB_UHCI_BIG_ENDIAN_MMIO config USB_UHCI_BIG_ENDIAN_DESC bool +config USB_HOST_SAMSUNG_FEATURE + bool "USB Host Samsung Feature" + depends on USB + help + USB Host Samsung Feature. + It is different from LSI BSP code. + If samsung engineer changes kernel code, + use this feature, + menuconfig USB_SUPPORT bool "USB support" depends on HAS_IOMEM diff --git a/drivers/usb/common/vbus_notifier/Kconfig b/drivers/usb/common/vbus_notifier/Kconfig new file mode 100644 index 000000000000..bc6ee7f37ab4 --- /dev/null +++ b/drivers/usb/common/vbus_notifier/Kconfig @@ -0,0 +1,9 @@ + +comment "USB common vbus notifier configs" + +config VBUS_NOTIFIER + tristate "VBUS notifier support" + default n + help + If you say yes here you will get support for + the VBUS status change notification. diff --git a/drivers/usb/common/vbus_notifier/Makefile b/drivers/usb/common/vbus_notifier/Makefile new file mode 100644 index 000000000000..89dc9f4a49ee --- /dev/null +++ b/drivers/usb/common/vbus_notifier/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for vbus notifier +# +subdir-ccflags-y := -Wformat +obj-$(CONFIG_VBUS_NOTIFIER) += vbus_notifier.o diff --git a/drivers/usb/common/vbus_notifier/vbus_notifier.c b/drivers/usb/common/vbus_notifier/vbus_notifier.c new file mode 100644 index 000000000000..f34dada50390 --- /dev/null +++ b/drivers/usb/common/vbus_notifier/vbus_notifier.c @@ -0,0 +1,123 @@ +#include +#include + +#include +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) +#include +#endif +#include + +#define SET_VBUS_NOTIFIER_BLOCK(nb, fn, dev) do { \ + (nb)->notifier_call = (fn); \ + (nb)->priority = (dev); \ + } while (0) + +#define DESTROY_VBUS_NOTIFIER_BLOCK(nb) \ + SET_VBUS_NOTIFIER_BLOCK(nb, NULL, -1) + +static struct vbus_notifier_struct vbus_notifier; +struct blocking_notifier_head vbus_notifier_head = + BLOCKING_NOTIFIER_INIT(vbus_notifier_head); +static DEFINE_MUTEX(vbus_mutex); + +int vbus_notifier_register(struct notifier_block *nb, notifier_fn_t notifier, + vbus_notifier_device_t listener) +{ + int ret = 0; + + pr_info("%s: listener=%d register\n", __func__, listener); + + SET_VBUS_NOTIFIER_BLOCK(nb, notifier, listener); + ret = blocking_notifier_chain_register(&vbus_notifier_head, nb); + if (ret < 0) + pr_err("%s: blocking_notifier_chain_register error(%d)\n", + __func__, ret); + + mutex_lock(&vbus_mutex); + if (vbus_notifier.vbus_type != STATUS_VBUS_UNKNOWN) + nb->notifier_call(nb, vbus_notifier.cmd, &(vbus_notifier.vbus_type)); + mutex_unlock(&vbus_mutex); + + return ret; +} +EXPORT_SYMBOL(vbus_notifier_register); + +int vbus_notifier_unregister(struct notifier_block *nb) +{ + int ret = 0; + + pr_info("%s: listener=%d unregister\n", __func__, nb->priority); + + ret = blocking_notifier_chain_unregister(&vbus_notifier_head, nb); + if (ret < 0) + pr_err("%s: blocking_notifier_chain_unregister error(%d)\n", + __func__, ret); + DESTROY_VBUS_NOTIFIER_BLOCK(nb); + + return ret; +} +EXPORT_SYMBOL(vbus_notifier_unregister); + +static int vbus_notifier_notify(void) +{ + int ret = 0; + + pr_info("%s: CMD=%d, DATA=%d\n", __func__, vbus_notifier.cmd, + vbus_notifier.vbus_type); + + ret = blocking_notifier_call_chain(&vbus_notifier_head, + vbus_notifier.cmd, &(vbus_notifier.vbus_type)); + + switch (ret) { + case NOTIFY_STOP_MASK: + case NOTIFY_BAD: + pr_err("%s: notify error occur(0x%x)\n", __func__, ret); + break; + case NOTIFY_DONE: + case NOTIFY_OK: + pr_info("%s: notify done(0x%x)\n", __func__, ret); + break; + default: + pr_info("%s: notify status unknown(0x%x)\n", __func__, ret); + break; + } + + return ret; +} + +void vbus_notifier_handle(vbus_status_t new_dev) +{ + pr_info("%s: (%d)->(%d)\n", __func__, vbus_notifier.vbus_type, new_dev); + + if (vbus_notifier.vbus_type == new_dev) + return; + + mutex_lock(&vbus_mutex); + if (new_dev == STATUS_VBUS_HIGH) + vbus_notifier.cmd = VBUS_NOTIFY_CMD_RISING; + else if (new_dev == STATUS_VBUS_LOW) + vbus_notifier.cmd = VBUS_NOTIFY_CMD_FALLING; + + vbus_notifier.vbus_type = new_dev; + mutex_unlock(&vbus_mutex); + + /* vbus attach broadcast */ + vbus_notifier_notify(); +} +EXPORT_SYMBOL(vbus_notifier_handle); + +static int __init vbus_notifier_init(void) +{ + return 0; +} + +static void __exit vbus_notifier_exit(void) +{ +} + +module_init(vbus_notifier_init); +module_exit(vbus_notifier_exit); + +MODULE_AUTHOR("Samsung USB Team"); +MODULE_DESCRIPTION("Vbus Notifier"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index cbad2af5fd88..939bd7073446 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -1026,6 +1026,10 @@ static struct usb_device *usbdev_lookup_by_devt(dev_t devt) return to_usb_device(dev); } +#ifdef CONFIG_USB_DEBUG_DETAILED_LOG +static unsigned int prev_cmd; +static int prev_ret; +#endif /* * file operations */ @@ -1076,6 +1080,10 @@ static int usbdev_open(struct inode *inode, struct file *file) usb_unlock_device(dev); snoop(&dev->dev, "opened by process %d: %s\n", task_pid_nr(current), current->comm); +#ifdef CONFIG_USB_DEBUG_DETAILED_LOG + prev_cmd = 0; + prev_ret = 0; +#endif return ret; out_unlock_device: @@ -1403,6 +1411,9 @@ static int proc_resetep(struct usb_dev_state *ps, void __user *arg) ret = checkintf(ps, ret); if (ret) return ret; +#ifdef CONFIG_USB_DEBUG_DETAILED_LOG + dev_info(&ps->dev->dev, "%s epnum %d\n", __func__, ep); +#endif check_reset_of_active_ep(ps->dev, ep, "RESETEP"); usb_reset_endpoint(ps->dev, ep); return 0; @@ -1506,6 +1517,9 @@ static int proc_resetdevice(struct usb_dev_state *ps) * privilege to do such things and any of the interfaces are * currently claimed. */ +#ifdef CONFIG_USB_DEBUG_DETAILED_LOG + dev_info(&ps->dev->dev, "%s\n", __func__); +#endif if (ps->privileges_dropped && actconfig) { for (i = 0; i < actconfig->desc.bNumInterfaces; ++i) { interface = actconfig->interface[i]; @@ -2296,6 +2310,9 @@ static int proc_claiminterface(struct usb_dev_state *ps, void __user *arg) if (get_user(ifnum, (unsigned int __user *)arg)) return -EFAULT; +#ifdef CONFIG_USB_DEBUG_DETAILED_LOG + dev_info(&ps->dev->dev, "%s: ifnum %d\n", __func__, ifnum); +#endif return claimintf(ps, ifnum); } @@ -2306,6 +2323,9 @@ static int proc_releaseinterface(struct usb_dev_state *ps, void __user *arg) if (get_user(ifnum, (unsigned int __user *)arg)) return -EFAULT; +#ifdef CONFIG_USB_DEBUG_DETAILED_LOG + dev_info(&ps->dev->dev, "%s: ifnum %d\n", __func__, ifnum); +#endif ret = releaseintf(ps, ifnum); if (ret < 0) return ret; @@ -2349,6 +2369,9 @@ static int proc_ioctl(struct usb_dev_state *ps, struct usbdevfs_ioctl *ctl) retval = -EINVAL; else switch (ctl->ioctl_code) { +#ifdef CONFIG_USB_DEBUG_DETAILED_LOG + dev_info(&ps->dev->dev, "%s ioctl_code %d\n", __func__, ctl->ioctl_code); +#endif /* disconnect kernel driver from interface */ case USBDEVFS_DISCONNECT: if (intf->dev.driver) { @@ -2485,7 +2508,12 @@ static int proc_disconnect_claim(struct usb_dev_state *ps, void __user *arg) sizeof(dc.driver)) == 0) return -EBUSY; +#ifdef CONFIG_USB_DEBUG_DETAILED_LOG + dev_info(&intf->dev, "%s,intfnum %d disconnect by usbfs\n", + __func__, dc.interface); +#else dev_dbg(&intf->dev, "disconnect by usbfs\n"); +#endif usb_driver_release_interface(driver, intf); } @@ -2808,12 +2836,102 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd, return ret; } +#ifdef CONFIG_USB_DEBUG_DETAILED_LOG +static int usbdev_log(unsigned int cmd, int ret) +{ + char *cmd_string; + + switch (cmd) { + case USBDEVFS_CONTROL: + cmd_string = "CONTROL"; + break; + case USBDEVFS_BULK: + cmd_string = "BULK"; + break; + case USBDEVFS_RESETEP: + cmd_string = "RESETEP"; + break; + case USBDEVFS_RESET: + cmd_string = "RESET"; + break; + case USBDEVFS_CLEAR_HALT: + cmd_string = "CLEAR_HALT"; + break; + case USBDEVFS_GETDRIVER: + cmd_string = "GETDRIVER"; + break; + case USBDEVFS_CONNECTINFO: + cmd_string = "CONNECTINFO"; + break; + case USBDEVFS_SETINTERFACE: + cmd_string = "SETINTERFACE"; + break; + case USBDEVFS_SETCONFIGURATION: + cmd_string = "SETCONFIGURATION"; + break; + case USBDEVFS_SUBMITURB: + cmd_string = "SUBMITURB"; + break; + case USBDEVFS_DISCARDURB: + cmd_string = "DISCARDURB"; + break; + case USBDEVFS_REAPURB: + cmd_string = "REAPURB"; + break; + case USBDEVFS_REAPURBNDELAY: + cmd_string = "REAPURBNDELAY"; + break; + case USBDEVFS_DISCSIGNAL: + cmd_string = "DISCSIGNAL"; + break; + case USBDEVFS_CLAIMINTERFACE: + cmd_string = "CLAIMINTERFACE"; + break; + case USBDEVFS_RELEASEINTERFACE: + cmd_string = "RELEASEINTERFACE"; + break; + case USBDEVFS_IOCTL: + cmd_string = "IOCTL"; + break; + case USBDEVFS_CLAIM_PORT: + cmd_string = "CLAIM_PORT"; + break; + case USBDEVFS_RELEASE_PORT: + cmd_string = "RELEASE_PORT"; + break; + case USBDEVFS_GET_CAPABILITIES: + cmd_string = "GET_CAPABILITIES"; + break; + case USBDEVFS_DISCONNECT_CLAIM: + cmd_string = "DISCONNECT_CLAIM"; + break; + default: + cmd_string = "DEFAULT"; + break; + } + if ((prev_cmd != cmd) || (prev_ret != ret)) { + printk(KERN_ERR "%s: %s error ret=%d\n", __func__, cmd_string, ret); + prev_cmd = cmd; + prev_ret = ret; + } + return 0; +} +#endif + static long usbdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int ret; ret = usbdev_do_ioctl(file, cmd, (void __user *)arg); +#ifdef CONFIG_USB_DEBUG_DETAILED_LOG + if (ret < 0) + usbdev_log(cmd, ret); + else { + prev_cmd = 0; + prev_ret = 0; + } +#endif return ret; } diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index bbf27f446677..707d399bf933 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -35,6 +35,9 @@ #include #include #include +#if IS_ENABLED(CONFIG_USB_VENDOR_NOTIFY) +#include +#endif #include "hub.h" #include "otg_productlist.h" @@ -128,6 +131,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev); static int hub_port_disable(struct usb_hub *hub, int port1, int set_state); static bool hub_port_warm_reset_required(struct usb_hub *hub, int port1, u16 portstatus); +static void hub_set_initial_usb2_lpm_policy(struct usb_device *udev); static inline char *portspeed(struct usb_hub *hub, int portstatus) { @@ -2486,6 +2490,11 @@ static int usb_enumerate_device(struct usb_device *udev) usb_detect_interface_quirks(udev); +#ifdef CONFIG_USB_INTERFACE_LPM_LIST + if (usb_detect_interface_lpm(udev)) + hub_set_initial_usb2_lpm_policy(udev); +#endif + return 0; } @@ -2595,6 +2604,11 @@ int usb_new_device(struct usb_device *udev) dev_dbg(&udev->dev, "udev %d, busnum %d, minor = %d\n", udev->devnum, udev->bus->busnum, (((udev->bus->busnum-1) * 128) + (udev->devnum-1))); +#if IS_ENABLED(CONFIG_USB_VENDOR_NOTIFY) + err = send_usb_vendor_notify_new_device(udev); + if (err) + goto fail; +#endif /* export the usbdev device-node for libusb */ udev->dev.devt = MKDEV(USB_DEVICE_MAJOR, (((udev->bus->busnum-1) * 128) + (udev->devnum-1))); @@ -5174,7 +5188,9 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1, /* notify HCD that we have a device connected and addressed */ if (hcd->driver->update_device) hcd->driver->update_device(hcd, udev); +#ifndef CONFIG_USB_INTERFACE_LPM_LIST hub_set_initial_usb2_lpm_policy(udev); +#endif fail: if (retval) { hub_port_disable(hub, port1, 0); diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index 15e9bd180a1d..deaaa9a703f6 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -733,3 +733,31 @@ void usb_release_quirk_list(void) quirk_list = NULL; mutex_unlock(&quirk_mutex); } + +#ifdef CONFIG_USB_INTERFACE_LPM_LIST +static const struct usb_device_id usb_interface_list_lpm[] = { + { .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS, + .bInterfaceClass = USB_CLASS_AUDIO}, + { } /* Terminating entry */ +}; + +int usb_detect_interface_lpm(struct usb_device *udev) +{ + const struct usb_device_id *id = usb_interface_list_lpm; + int l1_enable = 0; + + for (; id->match_flags; id++) { + if (!usb_match_device(udev, id)) + continue; + + if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_INFO) && + !usb_match_any_interface(udev, id)) + continue; + + l1_enable = 1; + } + + pr_info("%s:Device will %s L1\n", __func__, l1_enable?"enable":"disable"); + return l1_enable; +} +#endif diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index 3bb2e1db42b5..4944086e53de 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -36,6 +36,9 @@ extern void usb_deauthorize_interface(struct usb_interface *); extern void usb_authorize_interface(struct usb_interface *); extern void usb_detect_quirks(struct usb_device *udev); extern void usb_detect_interface_quirks(struct usb_device *udev); +#ifdef CONFIG_USB_INTERFACE_LPM_LIST +extern int usb_detect_interface_lpm(struct usb_device *udev); +#endif extern void usb_release_quirk_list(void); extern bool usb_endpoint_is_ignored(struct usb_device *udev, struct usb_host_interface *intf, diff --git a/drivers/usb/dwc3/dwc3-msm-core.c b/drivers/usb/dwc3/dwc3-msm-core.c index f2d348736247..7a152bff3cc5 100644 --- a/drivers/usb/dwc3/dwc3-msm-core.c +++ b/drivers/usb/dwc3/dwc3-msm-core.c @@ -49,12 +49,30 @@ #include #include #include +#include #include "core.h" #include "gadget.h" #include "debug.h" #include "xhci.h" #include "debug-ipc.h" +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) +#include +#endif +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) +#include +#endif + +#if IS_ENABLED(CONFIG_USB_CONFIGFS_F_SS_MON_GADGET) +#include +#endif +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) +#include +#endif + +#if IS_ENABLED(CONFIG_USB_DEBUG_DETAILED_LOG) +#include +#endif #define NUM_LOG_PAGES 12 @@ -115,6 +133,10 @@ #define PWR_EVNT_LPM_OUT_RX_ELECIDLE_IRQ_MASK BIT(12) #define PWR_EVNT_LPM_OUT_L1_MASK BIT(13) + +#define DWC31_LINK_LLUCTL(n) (0xd024 + ((n) * 0x80)) +#define FORCE_GEN1_MASK BIT(10) + #define EXTRA_INP_SS_DISABLE BIT(5) /* QSCRATCH_GENERAL_CFG register bit offset */ @@ -306,6 +328,8 @@ enum usb_gsi_reg { GSI_REG_MAX, }; +struct device *msm_dwc3; + struct dload_struct { u32 pid; char serial_number[SERIAL_NUMBER_LENGTH]; @@ -313,18 +337,6 @@ struct dload_struct { u32 serial_magic; }; -struct usb_udc { - struct usb_gadget_driver *driver; - struct usb_gadget *gadget; - struct device dev; - struct list_head list; - bool vbus; - bool started; - bool allow_connect; - struct work_struct vbus_work; - struct mutex connect_lock; -}; - struct dwc3_hw_ep { struct dwc3_ep *dep; enum usb_hw_ep_mode mode; @@ -387,6 +399,13 @@ enum dwc3_id_state { DWC3_ID_FLOAT, }; +/* for type c cable */ +enum plug_orientation { + ORIENTATION_NONE, + ORIENTATION_CC_A, + ORIENTATION_CC_B, +}; + enum msm_usb_irq { HS_PHY_IRQ, PWR_EVNT_IRQ, @@ -547,6 +566,7 @@ struct dwc3_msm { bool dbm_is_1p4; bool resume_pending; + bool core_init_done; atomic_t pm_suspended; struct usb_irq wakeup_irq[USB_MAX_IRQ]; int core_irq; @@ -560,7 +580,6 @@ struct dwc3_msm { unsigned long inputs; enum dwc3_drd_state drd_state; enum usb_dr_mode dr_mode; - bool otg_capable; enum bus_vote default_bus_vote; enum bus_vote override_bus_vote; struct icc_path *icc_paths[3]; @@ -572,7 +591,9 @@ struct dwc3_msm { bool vbus_active; bool eud_active; bool suspend; + bool tunnel_mode; bool use_pdc_interrupts; + bool use_hrtimer_for_dump; enum dwc3_id_state id_state; unsigned long use_pwr_event_for_wakeup; #define PWR_EVENT_SS_WAKEUP BIT(0) @@ -580,13 +601,13 @@ struct dwc3_msm { bool host_poweroff_in_pm_suspend; bool disable_host_ssphy_powerdown; bool hibernate_skip_thaw; - bool enable_host_slow_suspend; unsigned long lpm_flags; unsigned int vbus_draw; #define MDWC3_SS_PHY_SUSPEND BIT(0) #define MDWC3_ASYNC_IRQ_WAKE_CAPABILITY BIT(1) #define MDWC3_POWER_COLLAPSE BIT(2) #define MDWC3_USE_PWR_EVENT_IRQ_FOR_WAKEUP BIT(3) +#define MDWC3_SUSP_EARLY_EXIT BIT(4) struct extcon_nb *extcon; int ext_idx; struct notifier_block host_nb; @@ -605,9 +626,17 @@ struct dwc3_msm { int pm_qos_latency_dbg; struct pm_qos_request pm_qos_req_dma; struct delayed_work perf_vote_work; + struct delayed_work retry_resume_work; +#if IS_ENABLED(CONFIG_USB_DEBUG_DETAILED_LOG) + struct delayed_work suspend_check_work; +#endif struct mutex suspend_resume_mutex; struct mutex role_switch_mutex; + enum plug_orientation typec_orientation; + int dwc3_msm_probe_done; + int dwc3_msm_current_speed_mode; + enum usb_device_speed override_usb_speed; enum usb_device_speed max_hw_supp_speed; u32 *gsi_reg; @@ -635,6 +664,7 @@ struct dwc3_msm { phys_addr_t ebc_desc_addr; bool dis_sending_cm_l1_quirk; bool use_eusb2_phy; + bool force_gen1; bool cached_dis_u1_entry_quirk; bool cached_dis_u2_entry_quirk; int refcnt_dp_usb; @@ -646,6 +676,7 @@ struct dwc3_msm { bool wcd_usbss; bool dynamic_disable; bool read_u1u2; + struct hrtimer task_timer; struct dentry *dbg_dir; #define PM_QOS_REQ_DYNAMIC 0 @@ -659,8 +690,33 @@ struct dwc3_msm { int repeater_rev; bool force_disconnect; +#if IS_ENABLED(CONFIG_USB_NOTIFIER) + bool restarting_host_mode; +#endif +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + struct usb_dev usb_d; + struct if_cb_manager *man; +#endif + int cc_dir; }; +static void toggle_timer(struct dwc3_msm *mdwc, bool toggle) +{ + if (!mdwc) + return; + + if (!hrtimer_active(&mdwc->task_timer) + && mdwc->use_hrtimer_for_dump + && toggle) { + dbg_event(0xFF, "timer started", 0); + hrtimer_start(&mdwc->task_timer, 11000000000, + HRTIMER_MODE_REL_SOFT); + } else if (mdwc->use_hrtimer_for_dump && !toggle) { + dbg_event(0xFF, "timer stopped", 0); + hrtimer_cancel(&mdwc->task_timer); + } +} + #define USB_HSPHY_3P3_VOL_MIN 3050000 /* uV */ #define USB_HSPHY_3P3_VOL_MAX 3300000 /* uV */ #define USB_HSPHY_3P3_HPM_LOAD 16000 /* uA */ @@ -673,6 +729,11 @@ struct dwc3_msm { #define USB_SSPHY_1P8_VOL_MAX 1800000 /* uV */ #define USB_SSPHY_1P8_HPM_LOAD 23000 /* uA */ +int speed_setting; + +#undef dev_dbg +#define dev_dbg dev_err + /* unfortunately, dwc3 core doesn't manage multiple dwc3 instances for trace */ void *dwc_trace_ipc_log_ctxt; @@ -1118,6 +1179,24 @@ static bool dwc3_msm_is_ss_rhport_connected(struct dwc3_msm *mdwc) return false; } +static int dwc3_msm_is_ports_suspended(struct dwc3_msm *mdwc) +{ + int i, num_ports; + u32 reg; + + reg = dwc3_msm_read_reg(mdwc->base, USB3_HCSPARAMS1); + num_ports = HCS_MAX_PORTS(reg); + + for (i = 0; i < num_ports; i++) { + reg = dwc3_msm_read_reg(mdwc->base, USB3_PORTSC + i*0x10); + pr_err("%s reg = %x\n", __func__, reg); + if (((reg & PORT_PLS_MASK) == XDEV_U3)) + return 1; + } + + return 0; +} + static bool dwc3_msm_is_host_superspeed(struct dwc3_msm *mdwc) { int i, num_ports; @@ -1161,6 +1240,8 @@ static int dwc3_msm_dbm_disable_updxfer(struct dwc3 *dwc, u8 usb_ep) u32 data; int dbm_ep; + dev_dbg(mdwc->dev, "%s\n", __func__); + dbm_ep = find_matching_dbm_ep(mdwc, usb_ep); if (dbm_ep < 0) return dbm_ep; @@ -1414,6 +1495,8 @@ int msm_data_fifo_config(struct usb_ep *ep, unsigned long addr, u32 lo = lower_32_bits(addr); u32 hi = upper_32_bits(addr); + dev_dbg(mdwc->dev, "%s\n", __func__); + if (dbm_ep >= DBM_1_5_NUM_EP) { dev_err(mdwc->dev, "Invalid DBM EP num:%d\n", dbm_ep); return -EINVAL; @@ -3048,6 +3131,8 @@ static void dwc3_restart_usb_work(struct work_struct *w) restart_usb_work); unsigned int timeout = 50; + dev_dbg(mdwc->dev, "%s\n", __func__); + if (atomic_read(&mdwc->in_lpm) || mdwc->dr_mode != USB_DR_MODE_OTG) { dev_dbg(mdwc->dev, "%s failed!!!\n", __func__); return; @@ -3490,13 +3575,6 @@ void dwc3_msm_notify_event(struct dwc3 *dwc, case DWC3_CONTROLLER_CONNDONE_EVENT: dev_dbg(mdwc->dev, "DWC3_CONTROLLER_CONNDONE_EVENT received\n"); - if ((dwc->speed != DWC3_DSTS_SUPERSPEED) && - (dwc->speed != DWC3_DSTS_SUPERSPEED_PLUS)) { - reg = dwc3_msm_read_reg(mdwc->base, DWC3_GUSB3PIPECTL(0)); - reg |= DWC3_GUSB3PIPECTL_SUSPHY; - dwc3_msm_write_reg(mdwc->base, DWC3_GUSB3PIPECTL(0), reg); - } - /* * SW WA for CV9 RESET DEVICE TEST(TD 9.23) compliance failure. * Visit eUSB2 phy driver for more details. @@ -3594,16 +3672,6 @@ void dwc3_msm_notify_event(struct dwc3 *dwc, break; case DWC3_CONTROLLER_NOTIFY_CLEAR_DB: dev_dbg(mdwc->dev, "DWC3_CONTROLLER_NOTIFY_CLEAR_DB\n"); - - /* - * Clear the susphy bit here to ensure it is not set during - * the course of controller initialisation process. - */ - reg = dwc3_msm_read_reg(mdwc->base, DWC3_GUSB3PIPECTL(0)); - reg &= ~(DWC3_GUSB3PIPECTL_SUSPHY); - dwc3_msm_write_reg(mdwc->base, DWC3_GUSB3PIPECTL(0), reg); - udelay(1000); - handle_gsi_clear_db(dwc); break; case DWC3_IMEM_UPDATE_PID: @@ -3681,6 +3749,13 @@ static void dwc3_dis_sleep_mode(struct dwc3_msm *mdwc) dwc3_msm_write_reg(mdwc->base, DWC3_GUCTL1, reg); } +/* Force Gen1 speed on Gen2 controller if required */ +static void dwc3_force_gen1(struct dwc3_msm *mdwc) +{ + if (mdwc->force_gen1 && (mdwc->ip == DWC31_IP)) + dwc3_msm_write_reg_field(mdwc->base, DWC3_LLUCTL, DWC3_LLUCTL_FORCE_GEN1, 1); +} + static int dwc3_msm_power_collapse_por(struct dwc3_msm *mdwc) { struct dwc3 *dwc = NULL; @@ -3730,6 +3805,7 @@ static int dwc3_msm_power_collapse_por(struct dwc3_msm *mdwc) mdwc3_dis_sending_cm_l1(mdwc); } + dwc3_force_gen1(mdwc); return 0; } @@ -3846,6 +3922,13 @@ static void dwc3_set_ssphy_orientation_flag(struct dwc3_msm *mdwc) if (mdwc->orientation_override) { mdwc->ss_phy->flags |= mdwc->orientation_override; +#if IS_ENABLED(CONFIG_USB_NOTIFIER) + } else if (mdwc->typec_orientation) { + if (mdwc->typec_orientation == ORIENTATION_CC_A) + mdwc->ss_phy->flags |= PHY_LANE_A; + else /* if (mdwc->typec_orientation == ORIENTATION_CC_B) */ + mdwc->ss_phy->flags |= PHY_LANE_B; +#endif } else if (mdwc->has_orientation_gpio) { orientation = gpio_get_value(mdwc->orientation_gpio); if (orientation == 0) @@ -3942,14 +4025,12 @@ static void configure_usb_wakeup_interrupts(struct dwc3_msm *mdwc, bool enable) */ configure_usb_wakeup_interrupt(mdwc, &mdwc->wakeup_irq[DP_HS_PHY_IRQ], - mdwc->in_host_mode && !(mdwc->use_pwr_event_for_wakeup - & PWR_EVENT_HS_WAKEUP) ? + mdwc->in_host_mode ? (IRQF_TRIGGER_HIGH | IRQ_TYPE_LEVEL_HIGH) : IRQ_TYPE_EDGE_RISING, true); configure_usb_wakeup_interrupt(mdwc, &mdwc->wakeup_irq[DM_HS_PHY_IRQ], - mdwc->in_host_mode && !(mdwc->use_pwr_event_for_wakeup - & PWR_EVENT_HS_WAKEUP) ? + mdwc->in_host_mode ? (IRQF_TRIGGER_HIGH | IRQ_TYPE_LEVEL_HIGH) : IRQ_TYPE_EDGE_RISING, true); } @@ -4220,12 +4301,36 @@ static int dwc3_msm_suspend(struct dwc3_msm *mdwc, bool force_power_collapse) dwc = platform_get_drvdata(mdwc->dwc3); mutex_lock(&mdwc->suspend_resume_mutex); + if (atomic_read(&mdwc->in_lpm)) { dev_dbg(mdwc->dev, "%s: Already suspended\n", __func__); mutex_unlock(&mdwc->suspend_resume_mutex); return 0; } + if (mdwc->tunnel_mode && !dwc3_msm_is_ports_suspended(mdwc)) { + dbg_event(0xFF, "suspend exit early", mdwc->tunnel_mode); + /* + * Enable core_irq as wakeup able, so that non-audio data can + * still be properly received and handled. + */ + enable_irq_wake(mdwc->core_irq); + mdwc->lpm_flags |= MDWC3_SUSP_EARLY_EXIT; + atomic_set(&mdwc->in_lpm, 1); + + /* Allow PM suspend if tunnel mode is active */ +#if IS_ENABLED(CONFIG_USB_DEBUG_DETAILED_LOG) + if (sec_debug_level() < SEC_DEBUG_LEVEL_LOW) { + dev_dbg(mdwc->dev, "%s: stop suspend_check_work at tunnel mode\n", __func__); + cancel_delayed_work_sync(&mdwc->suspend_check_work); + } +#endif + pm_relax(mdwc->dev); + mutex_unlock(&mdwc->suspend_resume_mutex); + return 0; + } + + msm_dwc3_perf_vote_enable(mdwc, false); if (dwc) { if (!mdwc->in_host_mode) { evt = dwc->ev_buf; @@ -4282,8 +4387,6 @@ static int dwc3_msm_suspend(struct dwc3_msm *mdwc, bool force_power_collapse) return ret; } - msm_dwc3_perf_vote_enable(mdwc, false); - /* disable power event irq, hs and ss phy irq is used as wake up src */ disable_irq_nosync(mdwc->wakeup_irq[PWR_EVNT_IRQ].irq); @@ -4321,6 +4424,12 @@ static int dwc3_msm_suspend(struct dwc3_msm *mdwc, bool force_power_collapse) mdwc->lpm_to_suspend_delay); pm_wakeup_event(mdwc->dev, mdwc->lpm_to_suspend_delay); } else { +#if IS_ENABLED(CONFIG_USB_DEBUG_DETAILED_LOG) + if (sec_debug_level() < SEC_DEBUG_LEVEL_LOW) { + dev_dbg(mdwc->dev, "%s: stop suspend_check_work at normal msm susepnd\n", __func__); + cancel_delayed_work_sync(&mdwc->suspend_check_work); + } +#endif pm_relax(mdwc->dev); } @@ -4352,6 +4461,7 @@ static int dwc3_msm_suspend(struct dwc3_msm *mdwc, bool force_power_collapse) dev_info(mdwc->dev, "DWC3 in low power mode\n"); dbg_event(0xFF, "Ctl Sus", atomic_read(&mdwc->in_lpm)); + toggle_timer(mdwc, false); /* kick_sm if it is waiting for lpm sequence to finish */ if (test_and_clear_bit(WAIT_FOR_LPM, &mdwc->inputs)) queue_work(mdwc->sm_usb_wq, &mdwc->sm_work); @@ -4387,8 +4497,29 @@ static int dwc3_msm_resume(struct dwc3_msm *mdwc) return 0; } +#if IS_ENABLED(CONFIG_USB_DEBUG_DETAILED_LOG) + if (sec_debug_level() < SEC_DEBUG_LEVEL_LOW) { + if(!mdwc->in_host_mode && !mdwc->in_device_mode && mdwc->core_init_done) { + cancel_delayed_work_sync(&mdwc->suspend_check_work); + dev_dbg(mdwc->dev, "%s: start suspend_check_work at normal msm resume\n", __func__); + schedule_delayed_work(&mdwc->suspend_check_work, msecs_to_jiffies(1000*60)); + } + } +#endif pm_stay_awake(mdwc->dev); + if ((mdwc->lpm_flags & MDWC3_SUSP_EARLY_EXIT)) { + dbg_event(0xFF, "resume exit early", mdwc->tunnel_mode); + disable_irq_wake(mdwc->core_irq); + mdwc->lpm_flags &= ~MDWC3_SUSP_EARLY_EXIT; + atomic_set(&mdwc->in_lpm, 0); + mutex_unlock(&mdwc->suspend_resume_mutex); + + /* Wake up device chain to check for pending port events */ + queue_work(mdwc->dwc3_wq, &mdwc->resume_work); + return 0; + } + if (mdwc->in_host_mode && mdwc->max_rh_port_speed == USB_SPEED_HIGH) dwc3_msm_update_bus_bw(mdwc, BUS_VOTE_SVS); else @@ -4514,7 +4645,14 @@ static int dwc3_msm_resume(struct dwc3_msm *mdwc) error: dwc3_msm_update_bus_bw(mdwc, BUS_VOTE_NONE); +#if IS_ENABLED(CONFIG_USB_DEBUG_DETAILED_LOG) + if (sec_debug_level() < SEC_DEBUG_LEVEL_LOW) { + dev_dbg(mdwc->dev, "%s: stop suspend_check_work at error handling\n", __func__); + cancel_delayed_work_sync(&mdwc->suspend_check_work); + } +#endif pm_relax(mdwc->dev); + toggle_timer(mdwc, false); clear_bit(WAIT_FOR_LPM, &mdwc->inputs); mutex_unlock(&mdwc->suspend_resume_mutex); return ret; @@ -4604,10 +4742,13 @@ static void dwc3_ext_event_notify(struct dwc3_msm *mdwc) queue_work(mdwc->sm_usb_wq, &mdwc->sm_work); } +void dwc3_max_speed_setting(int speed); + static void dwc3_resume_work(struct work_struct *w) { struct dwc3_msm *mdwc = container_of(w, struct dwc3_msm, resume_work); struct dwc3 *dwc = NULL; +#if !IS_ENABLED(CONFIG_USB_NOTIFIER) union extcon_property_value val; unsigned int extcon_id; struct extcon_dev *edev = NULL; @@ -4676,6 +4817,46 @@ static void dwc3_resume_work(struct work_struct *w) if (mdwc->drd_state == DRD_STATE_UNDEFINED && !edev && !mdwc->resume_pending) return; +#else + int cc_state; + + if (mdwc->dwc3) + dwc = platform_get_drvdata(mdwc->dwc3); + + if (!dwc) { + pr_err("%s: Warning! dwc is NULL, retry resume_work\n", __func__); + schedule_delayed_work(&mdwc->retry_resume_work, + msecs_to_jiffies(1000)); + return; + } else { + cancel_delayed_work_sync(&mdwc->retry_resume_work); + } + + dev_dbg(mdwc->dev, "%s: dwc3 resume work\n", __func__); + if (mdwc->cc_dir) { + cc_state = gpio_get_value(mdwc->cc_dir); + pr_debug("%s: cc_state: %d\n", __func__, cc_state); + } else { + cc_state = 0; + pr_info("%s: set cc_state to default value(%d)\n", __func__, cc_state); + } + mdwc->typec_orientation = cc_state ? ORIENTATION_CC_B : ORIENTATION_CC_A; + dwc->maximum_speed = (speed_setting == 1) ? USB_SPEED_HIGH : mdwc->max_hw_supp_speed; + dev_info(mdwc->dev, "%s : max_speed = %d, typec_orientation = %d\n", + __func__, dwc->maximum_speed, mdwc->typec_orientation); + + if (mdwc->dwc3_msm_current_speed_mode != USB_SPEED_UNKNOWN && + dwc->maximum_speed != mdwc->dwc3_msm_current_speed_mode) { + pr_err("%s: Warning! phy is already resumed! current speed(%d), ignore speed(%d)\n", + __func__, mdwc->dwc3_msm_current_speed_mode, dwc->maximum_speed); + dwc->maximum_speed = mdwc->dwc3_msm_current_speed_mode; + if (dwc->maximum_speed == USB_SPEED_HIGH) + dwc3_max_speed_setting(1); + else + dwc3_max_speed_setting(0); + } +#endif + /* * exit LPM first to meet resume timeline from device side. * resume_pending flag would prevent calling @@ -4762,6 +4943,8 @@ static irqreturn_t msm_dwc3_pwr_irq_thread(int irq, void *_mdwc) { struct dwc3_msm *mdwc = _mdwc; + dev_dbg(mdwc->dev, "%s\n", __func__); + if (atomic_read(&mdwc->in_lpm)) dwc3_resume_work(&mdwc->resume_work); else @@ -4905,6 +5088,15 @@ static int dwc3_msm_get_clk_gdsc(struct dwc3_msm *mdwc) return 0; } +void dwc3_max_speed_setting(int speed) +{ + // speed 0 , it means Super speed + // speed 1 , it means High speed restrict enable + speed_setting = speed; + pr_info("%s : speed_setting = %d\n", __func__, speed_setting); +} +EXPORT_SYMBOL(dwc3_max_speed_setting); + static int dwc3_msm_id_notifier(struct notifier_block *nb, unsigned long event, void *ptr) { @@ -4937,6 +5129,99 @@ static int dwc3_msm_id_notifier(struct notifier_block *nb, return NOTIFY_DONE; } +int dwc_msm_id_event(bool enable) +{ + struct dwc3_msm *mdwc; + struct dwc3 *dwc; + enum dwc3_id_state id; + + mdwc = dev_get_drvdata(msm_dwc3); + if (mdwc == NULL) { + pr_info("%s(): mdwc is not initialized.\n", __func__); + return 1; + } + dwc = platform_get_drvdata(mdwc->dwc3); + + id = enable ? DWC3_ID_GROUND : DWC3_ID_FLOAT; + +#if IS_ENABLED(CONFIG_USB_NOTIFIER) + dev_info(mdwc->dev, "host: enable=%d (id:%d) event received, restarting_host_mode = %d\n", + enable, id, mdwc->restarting_host_mode); + + while (mdwc->restarting_host_mode) + msleep(50); +#endif + + if (!enable) + mdwc->typec_orientation = ORIENTATION_NONE; + + if (mdwc->id_state != id) { + mdwc->id_state = id; + dbg_event(0xFF, "id_state", mdwc->id_state); + queue_work(mdwc->dwc3_wq, &mdwc->resume_work); + } + + return NOTIFY_DONE; + +} +EXPORT_SYMBOL(dwc_msm_id_event); + +int dwc_msm_vbus_event(bool enable) +{ + struct dwc3_msm *mdwc; + struct dwc3 *dwc; + + mdwc = dev_get_drvdata(msm_dwc3); + dwc = platform_get_drvdata(mdwc->dwc3); + + if (!enable) + mdwc->typec_orientation = ORIENTATION_NONE; + + if (mdwc->vbus_active == enable) + return NOTIFY_DONE; + + mdwc->vbus_active = enable; + + if (mdwc->dr_mode == USB_DR_MODE_OTG && !mdwc->in_restart) { + dbg_event(0xFF, "Q RW (vbus)", mdwc->vbus_active); + queue_work(mdwc->dwc3_wq, &mdwc->resume_work); + } + return NOTIFY_DONE; +} +EXPORT_SYMBOL(dwc_msm_vbus_event); + +int is_dwc3_msm_probe_done(void) +{ + struct dwc3_msm *mdwc; + + if (msm_dwc3 == NULL) { + pr_info("%s(): msm_dwc3 is not initialized.\n", __func__); + return 0; + } + + mdwc = dev_get_drvdata(msm_dwc3); + + if (mdwc == NULL) { + pr_info("%s(): mdwc is not initialized.\n", __func__); + return 0; + } + + pr_info("%s: %d\n", __func__, mdwc->dwc3_msm_probe_done); + return mdwc->dwc3_msm_probe_done; +} +EXPORT_SYMBOL(is_dwc3_msm_probe_done); + +int gadget_speed(void) +{ + struct dwc3_msm *mdwc; + struct dwc3 *dwc; + + mdwc = dev_get_drvdata(msm_dwc3); + dwc = platform_get_drvdata(mdwc->dwc3); + return dwc->gadget->speed; +} +EXPORT_SYMBOL(gadget_speed); + static void dwc3_override_vbus_status(struct dwc3_msm *mdwc, bool vbus_present); static int dwc3_msm_vbus_notifier(struct notifier_block *nb, unsigned long event, void *ptr) @@ -5032,6 +5317,10 @@ static int dwc3_msm_extcon_register(struct dwc3_msm *mdwc) bool check_vbus_state, check_id_state, phandle_found = false; char *eud_str; const char *edev_name; + +#if IS_ENABLED(CONFIG_USB_NOTIFIER) + return 0; +#endif extcon_cnt = of_count_phandle_with_args(node, "extcon", NULL); if (extcon_cnt < 0) { @@ -5278,6 +5567,11 @@ static ssize_t mode_store(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR_RW(mode); static void msm_dwc3_perf_vote_work(struct work_struct *w); +static void msm_dwc3_retry_resume_work(struct work_struct *w); +static enum hrtimer_restart msm_dwc3_dbg_check_task(struct hrtimer *data); +#if IS_ENABLED(CONFIG_USB_DEBUG_DETAILED_LOG) +static void msm_dwc3_suspend_check_work(struct work_struct *w); +#endif /* This node only shows max speed supported dwc3 and it should be * same as what is reported in udc/core.c max_speed node. For current @@ -5362,7 +5656,7 @@ static ssize_t bus_vote_store(struct device *dev, mdwc->override_bus_vote = BUS_VOTE_MIN; } else if (sysfs_streq(buf, "max")) { bv_fixed = true; - mdwc->override_bus_vote = BUS_VOTE_NOMINAL; + mdwc->override_bus_vote = BUS_VOTE_MAX; } else if (sysfs_streq(buf, "cancel")) { bv_fixed = false; mdwc->override_bus_vote = BUS_VOTE_NONE; @@ -5394,10 +5688,6 @@ static ssize_t enable_l1_suspend_show(struct device *dev, struct dwc3_msm *mdwc = dev_get_drvdata(dev); struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); - /* gadget lpm capability only when otg/gadget mode is supported */ - if (!(mdwc->otg_capable) && (dwc->dr_mode == USB_DR_MODE_HOST)) - return -EPERM; - return scnprintf(buf, PAGE_SIZE, "%d\n", dwc->gadget->lpm_capable); } @@ -5411,10 +5701,6 @@ static ssize_t enable_l1_suspend_store(struct device *dev, bool enable_l1; int ret; - /* gadget lpm capability only when otg/gadget mode is supported */ - if (!(mdwc->otg_capable) && (dwc->dr_mode == USB_DR_MODE_HOST)) - return -EPERM; - ret = kstrtobool(buf, &enable_l1); if (ret < 0) { dev_err(dwc->dev, "%s: can't get entered value: %d\n", @@ -5709,16 +5995,6 @@ int dwc3_msm_set_dp_mode(struct device *dev, bool dp_connected, int lanes) dbg_log_string("Set DP lanes:%d refcnt:%d\n", lanes, mdwc->refcnt_dp_usb); if (lanes == 2) { - if (!mdwc->in_host_mode) { - msleep(20); - /* - * There are scenarios where DP driver calls this API before dwc3-msm - * gets the role information, hence return if host mode hasn't started. - * DP Altmode driver has retry mechanism we return -EAGAIN or -EBUSY. - */ - return -EAGAIN; - } - mutex_lock(&mdwc->role_switch_mutex); mdwc->dp_state = DP_2_LANE; mdwc->refcnt_dp_usb++; @@ -5774,6 +6050,34 @@ int dwc3_msm_set_dp_mode(struct device *dev, bool dp_connected, int lanes) } EXPORT_SYMBOL(dwc3_msm_set_dp_mode); +int dwc3_msm_set_dp_mode_for_ss(bool dp_connected) +{ + struct dwc3_msm *mdwc; + + mdwc = dev_get_drvdata(msm_dwc3); + + pr_info("%s : ++\n", __func__); + if (!mdwc || !mdwc->dwc3) { + pr_err("dwc3-msm is not initialized yet.\n"); + return -EAGAIN; + } + + /* flush any pending work */ + flush_work(&mdwc->resume_work); + flush_workqueue(mdwc->sm_usb_wq); + /* + * Special case for HOST mode, as we need to ensure that the DWC3 + * max speed is set before moving back into gadget/device mode. + * This is because, dwc3_gadget_init() will set the max speed + * for the USB gadget driver. + */ + dwc3_msm_clear_dp_only_params(mdwc); + mdwc->ss_phy->flags &= ~PHY_USB_DP_CONCURRENT_MODE; + pr_info("%s : --\n", __func__); + return 0; +} +EXPORT_SYMBOL(dwc3_msm_set_dp_mode_for_ss); + static int qos_rec_irq_read(struct seq_file *s, void *p) { struct dwc3_msm *mdwc = s->private; @@ -5958,10 +6262,13 @@ static int dwc3_msm_core_init(struct dwc3_msm *mdwc) if (!mdwc->xhci_pm_ops) goto free_dwc_pm_ops; + dwc3_force_gen1(mdwc); dwc3_msm_notify_event(dwc, DWC3_GSI_EVT_BUF_ALLOC, 0); pm_runtime_set_autosuspend_delay(dwc->dev, 0); pm_runtime_allow(dwc->dev); + mdwc->core_init_done = true; + return 0; free_dwc_pm_ops: @@ -6133,8 +6440,6 @@ static int dwc3_msm_parse_params(struct dwc3_msm *mdwc, struct device_node *node mdwc->hibernate_skip_thaw = of_property_read_bool(node, "qcom,hibernate-skip-thaw"); - mdwc->enable_host_slow_suspend = of_property_read_bool(node, - "qcom,enable_host_slow_suspend"); mdwc->dis_sending_cm_l1_quirk = of_property_read_bool(node, "qcom,dis-sending-cm-l1-quirk"); @@ -6163,6 +6468,8 @@ static int dwc3_msm_parse_params(struct dwc3_msm *mdwc, struct device_node *node of_property_read_u32(node, "qcom,pm-qos-latency", &mdwc->pm_qos_latency); + mdwc->force_gen1 = of_property_read_bool(node, "qcom,force-gen1"); + diag_node = of_find_compatible_node(NULL, NULL, "qcom,msm-imem-diag-dload"); if (!diag_node) pr_warn("diag: failed to find diag_dload imem node\n"); @@ -6177,44 +6484,134 @@ static int dwc3_msm_parse_params(struct dwc3_msm *mdwc, struct device_node *node return 0; } -static int dwc3_msm_register_interrupts(struct platform_device *pdev) + +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + +int msm_dwc3_gadget_get_link_state(struct dwc3_msm *mdwc) { - struct dwc3_msm *mdwc = platform_get_drvdata(pdev); - int ret = 0; - int i; + u32 reg; - for (i = 0; i < USB_MAX_IRQ; i++) { - mdwc->wakeup_irq[i].irq = platform_get_irq_byname(pdev, - usb_irq_info[i].name); - if (mdwc->wakeup_irq[i].irq < 0) { - /* pwr_evnt_irq is only mandatory irq */ - if (usb_irq_info[i].required) { - dev_err(&pdev->dev, "get_irq for %s failed\n\n", - usb_irq_info[i].name); - return -EINVAL; - } - mdwc->wakeup_irq[i].irq = 0; - } else { - irq_set_status_flags(mdwc->wakeup_irq[i].irq, - IRQ_NOAUTOEN); + reg = dwc3_msm_read_reg(mdwc->base, DWC3_DSTS); - ret = devm_request_threaded_irq(&pdev->dev, - mdwc->wakeup_irq[i].irq, - msm_dwc3_pwr_irq, - msm_dwc3_pwr_irq_thread, - usb_irq_info[i].irq_type, - usb_irq_info[i].name, mdwc); - if (ret) { - dev_err(&pdev->dev, "irq req %s failed: %d\n\n", - usb_irq_info[i].name, ret); - return ret; - } - } + return DWC3_DSTS_USBLNKST(reg); +} + +/** + * dwc3_gadget_get_cmply_link_state - Gets current state of USB Link + * @dwc: pointer to our context structure + * + * extern module can check dwc3 core link state This function will + * return 1 link is on compliance of loopback mode else 0. + */ +static int dwc3_gadget_get_cmply_link_state(void *dev) +{ + + struct dwc3_msm *mdwc = (struct dwc3_msm *)dev; + struct dwc3 *dwc; + u32 reg; + u32 ret = -ENODEV; + + if (!mdwc->dwc3) { + dev_dbg(mdwc->dev, "%s: dwc3 is null\n", __func__); + return ret; } - return 0; + dwc = platform_get_drvdata(mdwc->dwc3); + if (!dwc) { + pr_err("%s: Warning! dwc is NULL, do nothing\n", __func__); + return ret; + } + + if (dwc->pullups_connected) { + reg = msm_dwc3_gadget_get_link_state(mdwc); + + dev_dbg(mdwc->dev, "%s: link state = %d\n", __func__, reg); + if ((reg == DWC3_LINK_STATE_CMPLY) || (reg == DWC3_LINK_STATE_LPBK)) + ret = 1; + else + ret = 0; + } + + return ret; } +static struct typec_manager_gadget_ops typec_manager_dwc3_gadget_ops = { + .get_cmply_link_state = dwc3_gadget_get_cmply_link_state, +}; +#endif + +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) +int dwc3_restart_usb_host_mode(void *data, int lanes) +{ + struct dwc3_msm *mdwc; + struct dwc3 *dwc = NULL; + int ret = -EINVAL; + + pr_info("%s, lanes=%d", __func__, lanes); + + mdwc = dev_get_drvdata(msm_dwc3); + + if (mdwc == NULL) { + pr_info("%s, dwc3-msm is not initialized.\n", __func__); + return -EAGAIN; + } + + dwc = platform_get_drvdata(mdwc->dwc3); + if (dwc == NULL) { + pr_info("%s, dwc3 controller is not initialized.\n", __func__); + return -EAGAIN; + } + + if (dwc->maximum_speed == USB_SPEED_HIGH) + return 0; + + if (!mdwc->in_host_mode) + goto err; + + dbg_event(0xFF, "ss_lane_release", 0); + /* flush any pending work */ + flush_work(&mdwc->resume_work); + flush_workqueue(mdwc->sm_usb_wq); + + if (lanes == 2) { + pr_info("%s, 2 lanes\n", __func__); + pm_runtime_get_sync(&mdwc->dwc3->dev); + mdwc->ss_phy->flags |= PHY_USB_DP_CONCURRENT_MODE; + pm_runtime_put_sync(&mdwc->dwc3->dev); + return 0; + } + +#if IS_ENABLED(CONFIG_USB_NOTIFIER) + mdwc->restarting_host_mode = true; +#endif + pm_runtime_set_autosuspend_delay(mdwc->dev, 0); + mutex_lock(&mdwc->role_switch_mutex); + pr_info("%s, stop_host_mode, maximum_speed=%d", __func__, dwc->maximum_speed); + /* stop USB host mode */ + ret = dwc3_start_stop_host(mdwc, false); + pm_runtime_set_autosuspend_delay(mdwc->dev, 1000); + if (ret) + goto exit; + pr_info("%s, USB_lpm_state, dwc->in_lpm=%d", __func__, atomic_read(&mdwc->in_lpm)); + dwc3_max_speed_setting(1); + /* restart USB host mode into high speed */ + dwc3_msm_set_dp_only_params(mdwc); + dwc3_start_stop_host(mdwc, true); + pr_info("%s, complete_host_change, max_speed=%d", __func__, dwc->maximum_speed); +exit: + mutex_unlock(&mdwc->role_switch_mutex); +err: +#if IS_ENABLED(CONFIG_USB_NOTIFIER) + mdwc->restarting_host_mode = false; +#endif + return ret; +} + +struct usb_ops ops_usb = { + .usb_restart_host_mode = dwc3_restart_usb_host_mode, +}; +#endif + static int dwc3_msm_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node, *dwc3_node; @@ -6230,13 +6627,22 @@ static int dwc3_msm_probe(struct platform_device *pdev) platform_set_drvdata(pdev, mdwc); mdwc->dev = &pdev->dev; + dev_set_drvdata(msm_dwc3, mdwc); INIT_LIST_HEAD(&mdwc->req_complete_list); INIT_WORK(&mdwc->resume_work, dwc3_resume_work); INIT_WORK(&mdwc->restart_usb_work, dwc3_restart_usb_work); INIT_WORK(&mdwc->sm_work, dwc3_otg_sm_work); INIT_DELAYED_WORK(&mdwc->perf_vote_work, msm_dwc3_perf_vote_work); + INIT_DELAYED_WORK(&mdwc->retry_resume_work, msm_dwc3_retry_resume_work); +#if IS_ENABLED(CONFIG_USB_DEBUG_DETAILED_LOG) + if (sec_debug_level() < SEC_DEBUG_LEVEL_LOW) { + INIT_DELAYED_WORK(&mdwc->suspend_check_work, msm_dwc3_suspend_check_work); + } +#endif + hrtimer_init(&mdwc->task_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + mdwc->task_timer.function = msm_dwc3_dbg_check_task; dwc3_msm_debug_init(mdwc); mdwc->dwc3_wq = alloc_ordered_workqueue("dwc3_wq", 0); @@ -6284,9 +6690,44 @@ static int dwc3_msm_probe(struct platform_device *pdev) mdwc->lpm_to_suspend_delay = 0; } - ret = dwc3_msm_register_interrupts(pdev); - if (ret) - goto err; + mdwc->cc_dir = of_get_named_gpio(node, "samsung,cc_dir", 0); + if (!gpio_is_valid(mdwc->cc_dir)) { + mdwc->cc_dir = 0; + pr_err("%s: cc_dir gpio not specified\n", __func__); + } else { + gpio_direction_input(mdwc->cc_dir); + pr_debug("%s : mdwc->cc_dir: %d\n", __func__, mdwc->cc_dir); + } + + for (i = 0; i < USB_MAX_IRQ; i++) { + mdwc->wakeup_irq[i].irq = platform_get_irq_byname(pdev, + usb_irq_info[i].name); + if (mdwc->wakeup_irq[i].irq < 0) { + /* pwr_evnt_irq is only mandatory irq */ + if (usb_irq_info[i].required) { + dev_err(&pdev->dev, "get_irq for %s failed\n\n", + usb_irq_info[i].name); + ret = -EINVAL; + goto err; + } + mdwc->wakeup_irq[i].irq = 0; + } else { + irq_set_status_flags(mdwc->wakeup_irq[i].irq, + IRQ_NOAUTOEN); + + ret = devm_request_threaded_irq(&pdev->dev, + mdwc->wakeup_irq[i].irq, + msm_dwc3_pwr_irq, + msm_dwc3_pwr_irq_thread, + usb_irq_info[i].irq_type, + usb_irq_info[i].name, mdwc); + if (ret) { + dev_err(&pdev->dev, "irq req %s failed: %d\n\n", + usb_irq_info[i].name, ret); + goto err; + } + } + } res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core_base"); if (!res) { @@ -6366,6 +6807,14 @@ static int dwc3_msm_probe(struct platform_device *pdev) if (ret < 0) goto err; +#if IS_ENABLED(CONFIG_USB_DEBUG_DETAILED_LOG) + if (sec_debug_level() < SEC_DEBUG_LEVEL_LOW) { + mdwc->use_hrtimer_for_dump = of_property_read_bool(node, + "qcom,use-hrtimer-for-dump"); + } + pr_info("%s: qcom,use-hrtimer-for-dump=%d\n", __func__, mdwc->use_hrtimer_for_dump); +#endif + if (dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64))) { dev_err(&pdev->dev, "setting DMA mask to 64 failed.\n"); if (dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32))) { @@ -6413,7 +6862,6 @@ static int dwc3_msm_probe(struct platform_device *pdev) .allow_userspace_control = true, }; - mdwc->otg_capable = true; role_desc.fwnode = dev_fwnode(&pdev->dev); mdwc->role_switch = usb_role_switch_register(mdwc->dev, &role_desc); @@ -6454,8 +6902,16 @@ static int dwc3_msm_probe(struct platform_device *pdev) } } +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + mdwc->usb_d.ops = &ops_usb; + mdwc->usb_d.data = (void *)mdwc; + mdwc->man = register_usb(&(mdwc->usb_d)); +#endif + if (!mdwc->role_switch && (!mdwc->extcon || !dwc3_msm_extcon_is_valid_source(mdwc))) { + +#if !IS_ENABLED(CONFIG_USB_NOTIFIER) switch (mdwc->dr_mode) { case USB_DR_MODE_OTG: if (of_property_read_bool(node, @@ -6484,15 +6940,30 @@ static int dwc3_msm_probe(struct platform_device *pdev) } dwc3_ext_event_notify(mdwc); +#endif } +#if IS_ENABLED(CONFIG_USB_NOTIFIER) + mdwc->core_init_done = false; + queue_work(mdwc->sm_usb_wq, &mdwc->sm_work); + mdwc->restarting_host_mode = false; +#endif + + mdwc->dwc3_msm_probe_done = 1; + mdwc->dwc3_msm_current_speed_mode = USB_SPEED_UNKNOWN; + +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + enable_usb_notify(); +#endif + + pr_info("%s : dwc3_msm_probe_done = %d\n", __func__, mdwc->dwc3_msm_probe_done); + +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + typec_manager_dwc3_gadget_ops.driver_data = mdwc; + probe_typec_manager_gadget_ops(&typec_manager_dwc3_gadget_ops); +#endif + mdwc->force_disconnect = false; - - /* Add pm_qos with default mode intially */ - if (mdwc->pm_qos_latency) - cpu_latency_qos_add_request(&mdwc->pm_qos_req_dma, - PM_QOS_DEFAULT_VALUE); - return 0; put_dwc3: @@ -6504,6 +6975,7 @@ static int dwc3_msm_probe(struct platform_device *pdev) destroy_workqueue(mdwc->sm_usb_wq); destroy_workqueue(mdwc->dwc3_wq); usb_put_redriver(mdwc->redriver); + dev_set_drvdata(msm_dwc3, NULL); return ret; } @@ -6543,10 +7015,6 @@ static int dwc3_msm_remove(struct platform_device *pdev) msm_dwc3_perf_vote_enable(mdwc, false); cancel_work_sync(&mdwc->sm_work); - /* Remove pm_qos request */ - if (mdwc->pm_qos_latency) - cpu_latency_qos_remove_request(&mdwc->pm_qos_req_dma); - if (mdwc->hs_phy) mdwc->hs_phy->flags &= ~PHY_HOST_MODE; dwc3_msm_notify_event(dwc, DWC3_GSI_EVT_BUF_FREE, 0); @@ -6599,7 +7067,9 @@ static int dwc3_msm_host_ss_powerdown(struct dwc3_msm *mdwc) { u32 reg; - if (mdwc->disable_host_ssphy_powerdown || mdwc->dp_state || + dev_err(mdwc->dev, "%s\n", __func__); + + if (mdwc->disable_host_ssphy_powerdown || dwc3_msm_get_max_speed(mdwc) < USB_SPEED_SUPER) return 0; @@ -6619,6 +7089,8 @@ static int dwc3_msm_host_ss_powerup(struct dwc3_msm *mdwc) { u32 reg; + dev_err(mdwc->dev, "%s\n", __func__); + dbg_log_string("start: speed:%d\n", dwc3_msm_get_max_speed(mdwc)); if (!mdwc->in_host_mode || mdwc->disable_host_ssphy_powerdown || @@ -6698,6 +7170,31 @@ static void dwc3_msm_update_interfaces(struct usb_device *udev) } } +static int is_audio(struct usb_device *udev) +{ + int i, j; + int num_configs, num_intf; + struct usb_host_config *c; + + c = udev->config; + num_configs = udev->descriptor.bNumConfigurations; + for (i = 0; i < num_configs; (i++, c++)) { + struct usb_interface_descriptor *desc = NULL; + + if (c->desc.bNumInterfaces <= 0) + continue; + num_intf = c->desc.bNumInterfaces; + + for (j = 0; j < num_intf; j++) { + desc = &c->intf_cache[j]->altsetting->desc; + if (desc->bInterfaceClass == USB_CLASS_AUDIO) + return 1; + } + } + + return 0; +} + static int dwc3_msm_host_notifier(struct notifier_block *nb, unsigned long event, void *ptr) { @@ -6705,17 +7202,6 @@ static int dwc3_msm_host_notifier(struct notifier_block *nb, struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); struct usb_device *udev = ptr; - if (event == USB_BUS_ADD && mdwc->enable_host_slow_suspend) { - struct usb_bus *ubus = ptr; - struct usb_hcd *hcd = bus_to_hcd(ubus); - struct xhci_hcd *xhci = hcd_to_xhci(hcd); - - if (usb_hcd_is_primary_hcd(hcd)) { - dev_dbg(ubus->controller, "enable slow suspend\n"); - xhci->quirks |= XHCI_SLOW_SUSPEND; - } - } - if (event != USB_DEVICE_ADD && event != USB_DEVICE_REMOVE) return NOTIFY_DONE; @@ -6726,8 +7212,11 @@ static int dwc3_msm_host_notifier(struct notifier_block *nb, * relies on the DWC3 MSM to issue the PM runtime resume to wake up * the entire host device chain. */ - if (event == USB_DEVICE_ADD) + if (event == USB_DEVICE_ADD) { dev_pm_syscore_device(&udev->dev, true); + if (is_audio(udev)) + mdwc->tunnel_mode = true; + } /* * For direct-attach devices, new udev is direct child of root hub * i.e. dwc -> xhci -> root_hub -> udev @@ -6736,7 +7225,7 @@ static int dwc3_msm_host_notifier(struct notifier_block *nb, if (udev->parent && !udev->parent->parent && udev->dev.parent->parent == &dwc->xhci->dev) { if (event == USB_DEVICE_ADD) { - if (!dwc3_msm_is_ss_rhport_connected(mdwc)) { + if (!dwc3_msm_is_ss_rhport_connected(mdwc) && is_audio(udev)) { /* * Core clock rate can be reduced only if root * hub SS port is not enabled/connected. @@ -6767,7 +7256,7 @@ static int dwc3_msm_host_notifier(struct notifier_block *nb, mdwc->max_rh_port_speed = USB_SPEED_UNKNOWN; dwc3_msm_update_bus_bw(mdwc, mdwc->default_bus_vote); dwc3_msm_host_ss_powerup(mdwc); - + mdwc->tunnel_mode = false; if (udev->parent->speed >= USB_SPEED_SUPER) usb_redriver_host_powercycle(mdwc->redriver); @@ -6838,6 +7327,14 @@ static void msm_dwc3_perf_vote_work(struct work_struct *w) schedule_delayed_work(&mdwc->perf_vote_work, msecs_to_jiffies(delay)); } +static void msm_dwc3_retry_resume_work(struct work_struct *w) +{ + struct dwc3_msm *mdwc = container_of(w, struct dwc3_msm, + retry_resume_work.work); + pr_err("%s\n", __func__); + queue_work(mdwc->dwc3_wq, &mdwc->resume_work); +} + static void msm_dwc3_perf_vote_enable(struct dwc3_msm *mdwc, bool enable) { struct irq_desc *irq_desc = irq_to_desc(mdwc->core_irq); @@ -6858,16 +7355,34 @@ static void msm_dwc3_perf_vote_enable(struct dwc3_msm *mdwc, bool enable) /* make sure when enable work, save a valid start irq count */ mdwc->irq_cnt = irq_desc->tot_count; + /* start default mode intially */ + cpu_latency_qos_add_request(&mdwc->pm_qos_req_dma, + PM_QOS_DEFAULT_VALUE); schedule_delayed_work(&mdwc->perf_vote_work, msecs_to_jiffies(PM_QOS_DEFAULT_SAMPLE_MS)); } else { - if (!mdwc->pm_qos_latency_cfg) - return; cancel_delayed_work_sync(&mdwc->perf_vote_work); msm_dwc3_perf_vote_update(mdwc, false); + cpu_latency_qos_remove_request(&mdwc->pm_qos_req_dma); } } +#if IS_ENABLED(CONFIG_USB_DEBUG_DETAILED_LOG) +static void msm_dwc3_suspend_check_work(struct work_struct *w) +{ + struct dwc3_msm *mdwc = container_of(w, struct dwc3_msm, + suspend_check_work.work); +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + struct otg_notify *o_notify = get_otg_notify(); + + pr_info("%s: dwc3-msm in_lpm=%d\n", + __func__, atomic_read(&mdwc->in_lpm)); + set_request_action(o_notify, USB_REQUEST_DUMPSTATE); +#endif + panic("dwc3-msm didn't enter the suspend mode!"); +} +#endif + /** * dwc3_otg_start_host - helper function for starting/stopping the host * controller driver. @@ -6877,6 +7392,42 @@ static void msm_dwc3_perf_vote_enable(struct dwc3_msm *mdwc, bool enable) * * Returns 0 on success otherwise negative errno. */ + +static enum hrtimer_restart msm_dwc3_dbg_check_task(struct hrtimer *data) +{ + struct dwc3_msm *mdwc = container_of(data, + struct dwc3_msm, task_timer); + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + + pr_info("dwc3_msm=%d \t dwc3=%d\n", + atomic_read(&mdwc->dev->power.usage_count), + atomic_read(&dwc->dev->power.usage_count)); + + dbg_event(0xFF, "DWC3_DSTS=%x", + dwc3_msm_read_reg(mdwc->base, DWC3_DSTS)); + dbg_event(0xFF, "DWC3_DCFG=%x", + dwc3_msm_read_reg(mdwc->base, DWC3_DCFG)); + dbg_event(0xFF, "DWC3_DCTL=%x", + dwc3_msm_read_reg(mdwc->base, DWC3_DCTL)); + dbg_event(0xFF, "DWC3_GCTL=%x", + dwc3_msm_read_reg(mdwc->base, DWC3_GCTL)); + dbg_event(0xFF, "DWC3_GSTS=%x", + dwc3_msm_read_reg(mdwc->base, DWC3_GSTS)); + dbg_event(0xFF, "DWC3_GUSB3PIPECTL=%x", + dwc3_msm_read_reg(mdwc->base, DWC3_GUSB3PIPECTL(0))); + dbg_event(0xFF, "DWC3_GUSB2PHYCFG=%x", + dwc3_msm_read_reg(mdwc->base, DWC3_GUSB2PHYCFG(0))); + dbg_event(0xFF, "DWC3_DEVTEN=%x", + dwc3_msm_read_reg(mdwc->base, DWC3_DEVTEN)); + dbg_event(0xFF, "DWC3_DALEPENA=%x", + dwc3_msm_read_reg(mdwc->base, DWC3_DALEPENA)); + + dbg_event(0xFF, "Dwc3 isn't going to lpm!", 0); + BUG_ON(1); + + return HRTIMER_NORESTART; +} + static int dwc3_otg_start_host(struct dwc3_msm *mdwc, int on) { int ret = 0; @@ -6885,6 +7436,7 @@ static int dwc3_otg_start_host(struct dwc3_msm *mdwc, int on) if (on) { dev_dbg(mdwc->dev, "%s: turn on host\n", __func__); + toggle_timer(mdwc, false); mdwc->hs_phy->flags |= PHY_HOST_MODE; dbg_event(0xFF, "hs_phy_flag:", mdwc->hs_phy->flags); @@ -6898,6 +7450,12 @@ static int dwc3_otg_start_host(struct dwc3_msm *mdwc, int on) pm_runtime_set_suspended(mdwc->dev); return ret; } +#if IS_ENABLED(CONFIG_USB_DEBUG_DETAILED_LOG) + if (sec_debug_level() < SEC_DEBUG_LEVEL_LOW) { + dev_dbg(mdwc->dev, "%s: stop suspend_check_work at turn on host\n", __func__); + cancel_delayed_work_sync(&mdwc->suspend_check_work); + } +#endif dbg_event(0xFF, "StrtHost gync", atomic_read(&mdwc->dev->power.usage_count)); clk_set_rate(mdwc->core_clk, mdwc->core_clk_rate); @@ -6957,6 +7515,12 @@ static int dwc3_otg_start_host(struct dwc3_msm *mdwc, int on) flush_work(&dwc->drd_work); dwc3_msm_override_pm_ops(&dwc->xhci->dev, mdwc->xhci_pm_ops, true); mdwc->in_host_mode = true; + + /* disable host gen2 */ + dwc3_msm_write_reg_field(mdwc->base, DWC31_LINK_LLUCTL(0), FORCE_GEN1_MASK, 1); + dev_info(mdwc->dev, "Turn on host: FORCE_GEN1_MASK = %d\n", + dwc3_msm_read_reg_field(mdwc->base, DWC31_LINK_LLUCTL(0), FORCE_GEN1_MASK)); + pm_runtime_use_autosuspend(&dwc->xhci->dev); pm_runtime_set_autosuspend_delay(&dwc->xhci->dev, 0); pm_runtime_allow(&dwc->xhci->dev); @@ -7005,34 +7569,33 @@ static int dwc3_otg_start_host(struct dwc3_msm *mdwc, int on) * gadget will be set to HS only. */ mdwc->in_host_mode = false; +#if IS_ENABLED(CONFIG_USB_DEBUG_DETAILED_LOG) + if (sec_debug_level() < SEC_DEBUG_LEVEL_LOW) { + if(!mdwc->in_host_mode && !mdwc->in_device_mode) { + cancel_delayed_work_sync(&mdwc->suspend_check_work); + dev_dbg(mdwc->dev, "%s: start suspend_check_work at turn off host\n", __func__); + schedule_delayed_work(&mdwc->suspend_check_work, msecs_to_jiffies(1000*60)); + } + } +#endif if (!mdwc->ss_release_called) { dwc3_msm_host_ss_powerup(mdwc); dwc3_msm_clear_dp_only_params(mdwc); } - /* - * Performing phy disconnect before flush work to - * address TypeC certification--TD 4.7.4 failure. - * In order to avoid any controller start/halt - * sequences, switch to the UTMI as the clk source - * as the notify_disconnect() callback to the QMP - * PHY will power down the PIPE clock. - */ - dwc3_msm_switch_utmi(mdwc, true); + usb_role_switch_set_role(mdwc->dwc3_drd_sw, USB_ROLE_DEVICE); + if (dwc->dr_mode == USB_DR_MODE_OTG) + flush_work(&dwc->drd_work); + + usb_phy_notify_disconnect(mdwc->hs_phy, USB_SPEED_HIGH); if (mdwc->ss_phy->flags & PHY_HOST_MODE) { usb_phy_notify_disconnect(mdwc->ss_phy, USB_SPEED_SUPER); mdwc->ss_phy->flags &= ~PHY_HOST_MODE; } usb_redriver_notify_disconnect(mdwc->redriver); - usb_phy_notify_disconnect(mdwc->hs_phy, USB_SPEED_HIGH); - usb_role_switch_set_role(mdwc->dwc3_drd_sw, USB_ROLE_DEVICE); - if (dwc->dr_mode == USB_DR_MODE_OTG) - flush_work(&dwc->drd_work); - - dwc3_msm_switch_utmi(mdwc, false); mdwc->hs_phy->flags &= ~PHY_HOST_MODE; usb_unregister_notify(&mdwc->host_nb); @@ -7049,6 +7612,9 @@ static int dwc3_otg_start_host(struct dwc3_msm *mdwc, int on) if (mdwc->wcd_usbss) wcd_usbss_switch_update(WCD_USBSS_USB, WCD_USBSS_CABLE_DISCONNECT); } +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + usbpd_set_host_on(mdwc->man, on); +#endif return 0; } @@ -7095,13 +7661,24 @@ static int dwc3_otg_start_peripheral(struct dwc3_msm *mdwc, int on) dbg_event(0xFF, "StrtGdgt gsync", atomic_read(&mdwc->dev->power.usage_count)); +#if IS_ENABLED(CONFIG_USB_CONFIGFS_F_SS_MON_GADGET) + vbus_session_notify(dwc->gadget, on, EAGAIN); +#endif + if (on) { dev_dbg(mdwc->dev, "%s: turn on gadget\n", __func__); if (mdwc->wcd_usbss) wcd_usbss_switch_update(WCD_USBSS_USB, WCD_USBSS_CABLE_CONNECT); + toggle_timer(mdwc, false); pm_runtime_get_sync(&mdwc->dwc3->dev); +#if IS_ENABLED(CONFIG_USB_DEBUG_DETAILED_LOG) + if (sec_debug_level() < SEC_DEBUG_LEVEL_LOW) { + dev_dbg(mdwc->dev, "%s: stop suspend_check_work at turn on gadget\n", __func__); + cancel_delayed_work_sync(&mdwc->suspend_check_work); + } +#endif /* * Ensure DWC3 DRD switch routine is complete before continuing. * The DWC3 DRD sequence will execute a global and core soft @@ -7152,8 +7729,8 @@ static int dwc3_otg_start_peripheral(struct dwc3_msm *mdwc, int on) * Check udc->driver to find out if we are bound to udc or not. */ spin_lock_irqsave(&dwc->lock, flags); - if ((mdwc->force_disconnect) && (!dwc->softconnect) && - (dwc->gadget) && (dwc->gadget->udc->driver)) { + if ((mdwc->force_disconnect) && (dwc->gadget_driver) && + (!dwc->softconnect)) { spin_unlock_irqrestore(&dwc->lock, flags); dbg_event(0xFF, "Force Pullup", 0); usb_gadget_connect(dwc->gadget); @@ -7166,6 +7743,15 @@ static int dwc3_otg_start_peripheral(struct dwc3_msm *mdwc, int on) dwc3_override_vbus_status(mdwc, false); mdwc->in_device_mode = false; +#if IS_ENABLED(CONFIG_USB_DEBUG_DETAILED_LOG) + if (sec_debug_level() < SEC_DEBUG_LEVEL_LOW) { + if(!mdwc->in_host_mode && !mdwc->in_device_mode) { + cancel_delayed_work_sync(&mdwc->suspend_check_work); + dev_dbg(mdwc->dev, "%s: start suspend_check_work at turn off gadget\n", __func__); + schedule_delayed_work(&mdwc->suspend_check_work, msecs_to_jiffies(1000*60)); + } + } +#endif usb_phy_notify_disconnect(mdwc->hs_phy, USB_SPEED_HIGH); usb_phy_set_power(mdwc->hs_phy, 0); usb_phy_notify_disconnect(mdwc->ss_phy, USB_SPEED_SUPER); @@ -7297,6 +7883,7 @@ static void dwc3_otg_sm_work(struct work_struct *w) if (test_bit(WAIT_FOR_LPM, &mdwc->inputs)) { dev_dbg(mdwc->dev, "still not in lpm, wait.\n"); dbg_event(0xFF, "WAIT_FOR_LPM", 0); + toggle_timer(mdwc, true); break; } @@ -7435,6 +8022,9 @@ static int dwc3_msm_pm_suspend(struct device *dev) struct dwc3_msm *mdwc = dev_get_drvdata(dev); struct dwc3 *dwc = NULL; + if (mdwc->tunnel_mode) + return 0; + if (mdwc->dwc3) { dwc = platform_get_drvdata(mdwc->dwc3); @@ -7666,6 +8256,7 @@ MODULE_SOFTDEP("pre: phy-generic phy-msm-snps-hs phy-msm-ssusb-qmp eud"); static int dwc3_msm_init(void) { + msm_dwc3 = sec_device_create(NULL, "msm_dwc3"); dwc3_msm_kretprobe_init(); return platform_driver_register(&dwc3_msm_driver); } diff --git a/drivers/usb/dwc3/dwc3-msm-ops.c b/drivers/usb/dwc3/dwc3-msm-ops.c index 2c05df7fe1f7..1cbfe68d3140 100644 --- a/drivers/usb/dwc3/dwc3-msm-ops.c +++ b/drivers/usb/dwc3/dwc3-msm-ops.c @@ -17,6 +17,13 @@ #include "debug-ipc.h" #include "gadget.h" +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) +#include +#endif +#if IS_ENABLED(CONFIG_USB_CONFIGFS_F_SS_MON_GADGET) +#include +#endif + struct kprobe_data { struct dwc3 *dwc; int xi0; @@ -70,9 +77,17 @@ static int exit_usb_ep_set_maxpacket_limit(struct kretprobe_instance *ri, static int entry_dwc3_gadget_run_stop(struct kretprobe_instance *ri, struct pt_regs *regs) { +#if IS_ENABLED(CONFIG_USB_CONFIGFS_F_SS_MON_GADGET) + struct kprobe_data *data = (struct kprobe_data *)ri->data; +#endif struct dwc3 *dwc = (struct dwc3 *)regs->regs[0]; int is_on = (int)regs->regs[1]; +#if IS_ENABLED(CONFIG_USB_CONFIGFS_F_SS_MON_GADGET) + data->dwc = dwc; + data->xi0 = is_on; +#endif + if (is_on) { /* * DWC3 gadget IRQ uses a threaded handler which normally runs @@ -104,6 +119,27 @@ static int entry_dwc3_gadget_run_stop(struct kretprobe_instance *ri, return 0; } +#if IS_ENABLED(CONFIG_USB_CONFIGFS_F_SS_MON_GADGET) +static int exit_dwc3_gadget_run_stop(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + unsigned long long retval = regs_return_value(regs); + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct dwc3 *dwc = data->dwc; + int is_on; + + is_on = data->xi0; + + vbus_session_notify(dwc->gadget, is_on, retval); + + if (retval) { + pr_info("usb: dwc3_gadget_run_stop : dwc3_gadget %s failed (%d)\n", + is_on ? "ON" : "OFF", retval); + } + return 0; +} +#endif + static int entry_dwc3_send_gadget_ep_cmd(struct kretprobe_instance *ri, struct pt_regs *regs) { @@ -138,6 +174,11 @@ static int entry_dwc3_gadget_reset_interrupt(struct kretprobe_instance *ri, struct dwc3 *dwc = (struct dwc3 *)regs->regs[0]; dwc3_msm_notify_event(dwc, DWC3_CONTROLLER_NOTIFY_CLEAR_DB, 0); + +#if IS_ENABLED(CONFIG_USB_CONFIGFS_F_SS_MON_GADGET) + usb_reset_notify(dwc->gadget); +#endif + return 0; } @@ -255,7 +296,11 @@ static int entry_trace_event_raw_event_dwc3_log_ep(struct kretprobe_instance *ri } static struct kretprobe dwc3_msm_probes[] = { +#if IS_ENABLED(CONFIG_USB_CONFIGFS_F_SS_MON_GADGET) + ENTRY_EXIT(dwc3_gadget_run_stop), +#else ENTRY(dwc3_gadget_run_stop), +#endif ENTRY(dwc3_send_gadget_ep_cmd), ENTRY(dwc3_gadget_reset_interrupt), ENTRY(__dwc3_gadget_ep_enable), diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index b9b3d0bb5ace..b7d513c5489f 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -249,6 +249,15 @@ config USB_F_FS_IPC_LOGGING userspace client. Separate IPC log contexts are created for each function instance at mount time. +config USB_F_CONN_GADGET + tristate + +config USB_F_SS_MON_GADGET + tristate + +config USB_F_SS_ACM + tristate + # this first set of drivers all depend on bulk-capable hardware. config USB_CONFIGFS @@ -350,6 +359,30 @@ config USB_CONFIGFS_RNDIS XP, you'll need to download drivers from Microsoft's website; a URL is given in comments found in that info file. +config USB_CONFIGFS_F_CONN_GADGET + tristate "Samsung Sidesync function" + depends on USB_CONFIGFS + select USB_F_CONN_GADGET + help + USB Sidesync function support + +config USB_CONFIGFS_F_SS_MON_GADGET + tristate "F_SAMSUNG gadget" + depends on USB_CONFIGFS + select USB_F_SS_MON_GADGET + help + Driver for control to Samsung Usb device layer + For Samung working scenario and saving debug log, etc + +config USB_CONFIGFS_F_SS_ACM + tristate "F_SAMSUNG ACM" + depends on USB_CONFIGFS + depends on TTY + select USB_U_SERIAL + select USB_F_SS_ACM + help + Samsung ACM function support + config USB_CONFIGFS_EEM bool "Ethernet Emulation Model (EEM)" depends on USB_CONFIGFS @@ -412,6 +445,13 @@ config USB_CONFIGFS_F_FS implemented in kernel space (for instance Ethernet, serial or mass storage) and other are implemented in user space. +config F_CONN_GADGET_DEBUGFS + bool "Conn gadget debugfs" + depends on USB_CONFIGFS_F_CONN_GADGET + default n + help + SydeSync Debugging + config USB_CONFIGFS_F_ACC bool "Accessory gadget" depends on USB_CONFIGFS diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile index 63d7800afe51..ef47c11547ce 100644 --- a/drivers/usb/gadget/function/Makefile +++ b/drivers/usb/gadget/function/Makefile @@ -68,3 +68,9 @@ endif obj-$(CONFIG_USB_F_GSI) += usb_f_gsi.o usb_f_diag-y := f_diag.o obj-$(CONFIG_USB_F_DIAG) += usb_f_diag.o +usb_f_conn_gadget-y := f_conn_gadget.o +obj-$(CONFIG_USB_F_CONN_GADGET) += usb_f_conn_gadget.o +usb_f_ss_mon_gadget-y := f_ss_mon_gadget.o +obj-$(CONFIG_USB_F_SS_MON_GADGET) += usb_f_ss_mon_gadget.o +usb_f_ss_acm-y := f_ss_acm.o +obj-$(CONFIG_USB_F_SS_ACM) += usb_f_ss_acm.o diff --git a/drivers/usb/gadget/function/f_conn_gadget.c b/drivers/usb/gadget/function/f_conn_gadget.c new file mode 100644 index 000000000000..f3dc5e11c6be --- /dev/null +++ b/drivers/usb/gadget/function/f_conn_gadget.c @@ -0,0 +1,1505 @@ +/* + * Gadget Driver for Android Connectivity Gadget + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * Copyright (C) 2013 DEVGURU CO.,LTD. + * + * 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. + * + * ChangeLog: + * 20130819 - rename 'adb' to 'conn_gadget', shortname to 'android_ssusbconn' + * 20130821 - fixed return actual read size + * 20130822 - rework with 3.4.39 version's adb base source. + * 20130822 - remove unused callbacks + * 20130913 - add async read logic + * 20130913 - use kfifo as read buffer queue + * 20130913 - add polling handler + * 20130914 - fix ep read condition check mistake + * 20130923 - change CONN_GADGET_BULK_BUFFER_SIZE to 32KB + * 20131004 - support USB 3.0 (SuperSpeed) + * 20131009 - change CONN_GADGET_BULK_BUFFER_SIZE to 4KB + * 20131010 - fix typo. SuperSpeed support related. + * 20140113 - expose 'usb_buffer_size' attribute + * 20140113 - change kmalloc to vmalloc for kfifo buffer + * 20140211 - support multiple read URB.request + * 20140221 - remove fifo_lock + * - disable change transfer_size when online + * 20140228 - add android_usb_function callback registration code. + * - conditional conn_gadget_shortname for `android` and `tizen` + * 20140311 - add ioctl to communicate to userland application + * 20140318 - add flush to wakeup ioctl sleep before handle close + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* platform specific definitions */ +/* ex) #define __ANDROID__ */ + +/* platform specific pre-processing */ +#define CONN_GADGET_SHORTNAME "android_ssusbcon" + +/* logging macros */ +#define CONN_GADGET_ERR(_fmt_, _arg_...) printk(KERN_ERR "%s() " _fmt_, __func__, ## _arg_) +#define CONN_GADGET_DBG(_fmt_, _arg_...) //printk(KERN_ERR "%s() " _fmt_, __func__, ## _arg_) + +/* number of tx requests to allocate */ +#define CONN_GADGET_TX_REQ_MAX 4 +#define CONN_GADGET_RX_REQ_MAX 4 + +#define CONN_GADGET_DEFAULT_TRANSFER_SIZE (4 * 1024) +#define CONN_GADGET_DEFAULT_Q_SIZE (16 * (CONN_GADGET_RX_REQ_MAX)) +#define MAX_INST_NAME_LEN 40 + +/* ioctl */ +#include "f_conn_gadget.ioctl.h" + +static const char conn_gadget_shortname[] = CONN_GADGET_SHORTNAME; + +struct conn_gadget_dev { + struct usb_function function; + struct usb_composite_dev *cdev; + spinlock_t lock; + + struct usb_ep *ep_in; + struct usb_ep *ep_out; + + int online; + int error; + + + atomic_t read_excl; + atomic_t write_excl; + atomic_t open_excl; + atomic_t ep_out_excl; + + struct list_head tx_idle; + struct list_head rx_idle; + struct list_head rx_busy; + + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + wait_queue_head_t unbind_wq; + + struct kfifo rd_queue; + void *rd_queue_buf; + + int transfer_size; + int rd_queue_size; //byte + + wait_queue_head_t ioctl_wq; + + /* store privious `online` value + * for notificate to app about bind/unbind state + * through IOCTL */ + int memorized; + + /* flag variable that save flush call status + * to check wakeup reason */ + atomic_t flush; + + struct kref kref; +}; + +static struct usb_interface_descriptor conn_gadget_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = 0xFF, + .bInterfaceSubClass = 0x40, + .bInterfaceProtocol = 2, +}; + +static struct usb_endpoint_descriptor conn_gadget_superspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor conn_gadget_superspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor conn_gadget_superspeed_bulk_comp_desc = { + .bLength = sizeof(conn_gadget_superspeed_bulk_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_endpoint_descriptor conn_gadget_highspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor conn_gadget_highspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor conn_gadget_fullspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor conn_gadget_fullspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *fs_conn_gadget_descs[] = { + (struct usb_descriptor_header *) &conn_gadget_interface_desc, + (struct usb_descriptor_header *) &conn_gadget_fullspeed_in_desc, + (struct usb_descriptor_header *) &conn_gadget_fullspeed_out_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_conn_gadget_descs[] = { + (struct usb_descriptor_header *) &conn_gadget_interface_desc, + (struct usb_descriptor_header *) &conn_gadget_highspeed_in_desc, + (struct usb_descriptor_header *) &conn_gadget_highspeed_out_desc, + NULL, +}; + +static struct usb_descriptor_header *ss_conn_gadget_descs[] = { + (struct usb_descriptor_header *) &conn_gadget_interface_desc, + (struct usb_descriptor_header *) &conn_gadget_superspeed_in_desc, + (struct usb_descriptor_header *) &conn_gadget_superspeed_bulk_comp_desc, + (struct usb_descriptor_header *) &conn_gadget_superspeed_out_desc, + (struct usb_descriptor_header *) &conn_gadget_superspeed_bulk_comp_desc, + NULL, +}; + +void conn_gadget_req_move(struct conn_gadget_dev *dev, struct list_head *from_head, + struct list_head *to_head, struct usb_request *req); + +struct usb_request *conn_gadget_req_get_ex(struct conn_gadget_dev *dev, struct list_head *head, int delete); +struct usb_request *conn_gadget_req_get_from_rx_idle(struct conn_gadget_dev *dev); + +/* temporary variable used between conn_gadget_open() and conn_gadget_gadget_bind() */ +static struct conn_gadget_dev *_conn_gadget_dev; + +struct conn_gadget_instance { + struct usb_function_instance func_inst; + const char *name; +}; + +static void conn_gadget_cleanup(struct kref *kref); + +static inline struct conn_gadget_dev *func_to_conn_gadget(struct usb_function *f) +{ + return container_of(f, struct conn_gadget_dev, function); +} + + +static struct usb_request *conn_gadget_request_new(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + + if (!req) + return NULL; + + /* now allocate buffers for the requests */ + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + return req; +} + +static void conn_gadget_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +static inline int conn_gadget_lock(atomic_t *excl) +{ + if (atomic_inc_return(excl) == 1) { + return 0; + } else { + atomic_dec(excl); + return -1; + } +} + +static inline void conn_gadget_unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +/* add a request to the tail of a list */ +void conn_gadget_req_put(struct conn_gadget_dev *dev, struct list_head *head, + struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* remove a request from the head of a list */ +struct usb_request *conn_gadget_req_get(struct conn_gadget_dev *dev, struct list_head *head) +{ + return conn_gadget_req_get_ex(dev, head, 1); +} + +struct usb_request *conn_gadget_req_get_ex(struct conn_gadget_dev *dev, struct list_head *head, int delete) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(head)) { + req = 0; + } else { + req = list_first_entry(head, struct usb_request, list); + if (delete) + list_del(&req->list); + } + spin_unlock_irqrestore(&dev->lock, flags); + return req; +} + +struct usb_request *conn_gadget_req_get_from_rx_idle(struct conn_gadget_dev *dev) +{ + if (kfifo_len(&dev->rd_queue) > dev->rd_queue_size / 2) { + CONN_GADGET_DBG("rd_queue has much buffer already\n"); + return NULL; + } + + return conn_gadget_req_get(dev, &dev->rx_idle); +} + +static int conn_gadget_request_ep_out(struct conn_gadget_dev *dev) +{ + struct usb_request *req; + int ret; + + if (conn_gadget_lock(&dev->ep_out_excl)) { + CONN_GADGET_ERR("request ep_out failed, because it is currently being unbinded\n"); + return 0; + } + + while ((req = conn_gadget_req_get_from_rx_idle(dev))) { + req->length = dev->transfer_size; + + conn_gadget_req_put(dev, &dev->rx_busy, req); + CONN_GADGET_DBG("rx %pK queue\n", req); + + ret = usb_ep_queue(dev->ep_out, req, GFP_ATOMIC); + if (ret < 0) { + CONN_GADGET_ERR("failed to queue req %pK (%d)\n", req, ret); + conn_gadget_req_move(dev, &dev->rx_busy, &dev->rx_idle, req); + break; + } + } + + conn_gadget_unlock(&dev->ep_out_excl); + CONN_GADGET_DBG("unbind_wq wkup\n"); + wake_up(&dev->unbind_wq); + return 0; +} + +/* remove a request from it's list and add a request to other list */ +void conn_gadget_req_move(struct conn_gadget_dev *dev, struct list_head *from_head, + struct list_head *to_head, struct usb_request *req) +{ + unsigned long flags; + (void)(from_head); //not used. but for readability + + spin_lock_irqsave(&dev->lock, flags); + list_move_tail(&req->list, to_head); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* check state of list + return value: + - empty : 1 + */ +int conn_gadget_empty(struct conn_gadget_dev *dev, struct list_head *head) +{ + int empty = 0; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(head)) { + empty = 1; + } + spin_unlock_irqrestore(&dev->lock, flags); + + return empty; +} + + + + +static void conn_gadget_complete_in(struct usb_ep *ep, struct usb_request *req) +{ + struct conn_gadget_dev *dev = _conn_gadget_dev; + + CONN_GADGET_DBG("status %d\n", req->status); + + if (req->status != 0) { + dev->error = 1; + pr_debug("%s: error %d\n", __func__, dev->error); + CONN_GADGET_ERR("req->status f %d\n", req->status); + } + + conn_gadget_req_put(dev, &dev->tx_idle, req); + + wake_up(&dev->write_wq); +} + +static void conn_gadget_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct conn_gadget_dev *dev = _conn_gadget_dev; + int size = 0; + + CONN_GADGET_DBG("enter\n"); + + if (req->status != 0) { + if (req->status != -ECONNRESET) { + dev->error = 1; + pr_debug("%s: error %d\n", __func__, dev->error); + } + + CONN_GADGET_ERR("req->status f %d\n", req->status); + goto done; + } else { + size = kfifo_in(&dev->rd_queue, req->buf, req->actual); + if (size != req->actual) + CONN_GADGET_ERR("!!! kfifo_in size(%d) is different with req->actual(%d) !!!\n", size, req->actual); + } + +done: + conn_gadget_req_move(dev, &dev->rx_busy, &dev->rx_idle, req); //move req from rx_busy list to rx_idle + CONN_GADGET_DBG("rd_wq wkup\n"); + wake_up(&dev->read_wq); +} + +static int conn_gadget_create_bulk_endpoints(struct conn_gadget_dev *dev, + struct usb_endpoint_descriptor *in_desc, + struct usb_endpoint_descriptor *out_desc) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + struct usb_ep *ep; + int i; + + pr_debug("create_bulk_endpoints dev: %pK\n", dev); + + ep = usb_ep_autoconfig(cdev->gadget, in_desc); + if (!ep) { + printk(KERN_ERR "usb_ep_autoconfig for ep_in failed\n"); + return -ENODEV; + } + pr_debug("usb_ep_autoconfig for ep_in got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + printk(KERN_ERR "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + pr_debug("usb_ep_autoconfig for conn_gadget ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + /* now allocate requests for our endpoints */ + for (i = 0; i < CONN_GADGET_RX_REQ_MAX; i++) { + req = conn_gadget_request_new(dev->ep_out, + (dev->transfer_size)?dev->transfer_size:CONN_GADGET_DEFAULT_TRANSFER_SIZE); //use default value + if (!req) + goto fail; + req->complete = conn_gadget_complete_out; + conn_gadget_req_put(dev, &dev->rx_idle, req); + } + + for (i = 0; i < CONN_GADGET_TX_REQ_MAX; i++) { + req = conn_gadget_request_new(dev->ep_in, + (dev->transfer_size)?dev->transfer_size:CONN_GADGET_DEFAULT_TRANSFER_SIZE); //use default value + if (!req) + goto fail; + req->complete = conn_gadget_complete_in; + conn_gadget_req_put(dev, &dev->tx_idle, req); + } + + return 0; + +fail: + CONN_GADGET_ERR("conn_gadget_bind() could not allocate requests\n"); + return -1; +} + +static unsigned int conn_gadget_poll(struct file *fp, poll_table *wait) +{ + unsigned int mask; + struct conn_gadget_dev *dev = fp->private_data; + + CONN_GADGET_DBG("enter\n"); + + poll_wait(fp, &dev->read_wq, wait); + poll_wait(fp, &dev->write_wq, wait); + + mask = 0; + + if (!_conn_gadget_dev) { + CONN_GADGET_ERR("_conn_gadget_dev is NULL\n"); + mask |= (POLLNVAL | POLLERR); + return mask; + } + + if (!_conn_gadget_dev->online) { + CONN_GADGET_ERR("_conn_gadget_dev is offlined\n"); + return mask; + } + + if (!conn_gadget_lock(&dev->read_excl)) { + unsigned int len = kfifo_len(&dev->rd_queue); + + if (len) + mask |= (POLLIN | POLLRDNORM); + + conn_gadget_unlock(&dev->read_excl); + } + + if (!conn_gadget_empty(dev, &dev->tx_idle)) + mask |= (POLLOUT | POLLWRNORM); + + CONN_GADGET_DBG("exit\n"); + return mask; +} + +static ssize_t conn_gadget_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct conn_gadget_dev *dev = fp->private_data; + int r = count, xfer; + int ret; + + CONN_GADGET_DBG("%s(%d)\n", __func__, count); + + if (!_conn_gadget_dev) { + CONN_GADGET_ERR("_conn_gadget_dev is NULL\n"); + return -ENODEV; + } + + if (count >= dev->transfer_size) { + CONN_GADGET_ERR("count is too large (%d)\n", (int)count); + return -EINVAL; + } + + if (conn_gadget_lock(&dev->read_excl)) { + CONN_GADGET_ERR("conn_gadget_lock(read_excl) f\n"); + return -EBUSY; + } + + /* we will block until we're online */ + while (!(dev->online || dev->error)) { + CONN_GADGET_ERR("waiting for online state\n"); + ret = wait_event_interruptible(dev->read_wq, + (dev->online || dev->error)); + if (ret < 0) { + CONN_GADGET_ERR("wait_event_interruptible f %d\n", ret); + conn_gadget_unlock(&dev->read_excl); + return ret; + } + } + if (dev->error) { + r = -EIO; + CONN_GADGET_ERR("dev->error has value\n"); + goto done; + } + + //if there is a ready buffer, then copy to user. + do { + xfer = kfifo_len(&dev->rd_queue); + if (!xfer) + break; + xfer = (xfer < count) ? xfer : count; + ret = kfifo_to_user(&dev->rd_queue, buf, xfer, &r); //assign copied byte to r + } while (0); + + if (!xfer) { + //r = -EAGAIN; + r = 0; + CONN_GADGET_ERR("zero queue\n"); + goto req; + } + + if (ret < 0) { + r = -EFAULT; + CONN_GADGET_ERR("kfifo_to_user f %d\n", ret); + goto done; + } + +req: + //if there is a rx_idle, then usb_ep_queue + CONN_GADGET_DBG("conn_gadget_request_ep_out\n"); + if (dev->error || !dev->online) + printk("usb: conn_gadget skip conn_gadget_request_ep_out\n"); + else + conn_gadget_request_ep_out(dev); + +done: + conn_gadget_unlock(&dev->read_excl); + CONN_GADGET_DBG("%s returning %d\n", __func__, r); + return r; +} + +static ssize_t conn_gadget_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct conn_gadget_dev *dev = fp->private_data; + struct usb_request *req = 0; + int r = count, xfer; + int ret; + + if (!_conn_gadget_dev) { + CONN_GADGET_ERR("_conn_gadget_dev is NULL\n"); + return -ENODEV; + } + + CONN_GADGET_DBG("%s(%d)\n", __func__, count); + + if (conn_gadget_lock(&dev->write_excl)) { + CONN_GADGET_ERR("conn_gadget_lock(write_excl) f\n"); + return -EBUSY; + } + + + while (count > 0) { + CONN_GADGET_DBG("in the loop (user count %d)\n", (int)count); + + if (dev->error) { + r = -EIO; + CONN_GADGET_ERR("conn_gadget_write dev->error\n"); + break; + } + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + (req = conn_gadget_req_get(dev, &dev->tx_idle)) || dev->error); + + if (ret < 0) { + r = ret; + printk(KERN_ERR "%s: wait_event_interruptible(wrwq,reqget) failed %d\n", __func__, ret); + break; + } + + if (dev->error) { + r = -EIO; + printk(KERN_ERR "%s: wait_event_interruptible(), dev->error\n", __func__); + break; + } + + if (req != 0) { + if (count > dev->transfer_size) + xfer = dev->transfer_size; + else + xfer = count; + + if (copy_from_user(req->buf, buf, xfer)) { + r = -EFAULT; + printk(KERN_ERR "%s: copy_from_user failed\n", __func__); + break; + } + + req->length = xfer; + + ret = usb_ep_queue(dev->ep_in, req, GFP_ATOMIC); + if (ret < 0) { + dev->error = 1; + pr_debug("%s: error %d\n", __func__, dev->error); + r = -EIO; + CONN_GADGET_ERR("xfer error %d\n", ret); + break; + } + + buf += xfer; + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + } + + if (req) { + pr_debug("%s: req_put\n", __func__); + conn_gadget_req_put(dev, &dev->tx_idle, req); + } + + conn_gadget_unlock(&dev->write_excl); + CONN_GADGET_DBG("%s returning %d\n", __fucn__, r); + + + return r; +} + +static int conn_gadget_open(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "%s\n", __func__); + + if (!_conn_gadget_dev) { + CONN_GADGET_ERR("_conn_gadget_dev is NULL\n"); + return -ENODEV; + } + + if (atomic_read(&_conn_gadget_dev->flush)) { //doing flush-ing + CONN_GADGET_ERR("handle closing now. open again\n"); + return -EAGAIN; + } + + if (conn_gadget_lock(&_conn_gadget_dev->open_excl)) { + CONN_GADGET_ERR("conn_gadget_lock(open_excl) f\n"); + return -EBUSY; + } + + if (!kref_get_unless_zero(&_conn_gadget_dev->kref)) { + CONN_GADGET_ERR("already device removed\n"); + return -ENODEV; + } + + fp->private_data = _conn_gadget_dev; + + /* clear the error latch */ + _conn_gadget_dev->error = 0; + + if (_conn_gadget_dev->online) { + CONN_GADGET_ERR("_conn_gaddget_dev onlined\n"); + + //if there is a rx_idle, then usb_ep_queue + CONN_GADGET_DBG("conn_gadget_request_ep_out\n"); + conn_gadget_request_ep_out(_conn_gadget_dev); + } + + + //memorized status is not necessary before handle opened. + _conn_gadget_dev->memorized = _conn_gadget_dev->online; + + CONN_GADGET_DBG("end\n"); + return 0; +} + +static int conn_gadget_flush(struct file *fp, fl_owner_t id) +{ + struct conn_gadget_dev *dev = _conn_gadget_dev; + + printk(KERN_INFO "%s\n", __func__); + + if (!dev) { + CONN_GADGET_ERR("_conn_gadget_dev is invalid\n"); + return -ENODEV; + } + + atomic_set(&dev->flush, 1); + + CONN_GADGET_DBG("ioctl_wq wkup\n"); + wake_up(&dev->ioctl_wq); + + return 0; +} + +static int conn_gadget_release(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "%s\n", __func__); + + atomic_set(&_conn_gadget_dev->flush, 0); + + conn_gadget_unlock(&_conn_gadget_dev->open_excl); + + kref_put(&_conn_gadget_dev->kref, conn_gadget_cleanup); + return 0; +} + +static int conn_gadget_bind_status_copy_to_user(unsigned long value, int online) +{ + int err; + unsigned long status = CONN_GADGET_IOCTL_BIND_STATUS_UNDEFINED; + + status = (online)?CONN_GADGET_IOCTL_BIND_STATUS_BIND : CONN_GADGET_IOCTL_BIND_STATUS_UNBIND; + err = copy_to_user((void __user *)value, (const void *)&status, sizeof(status)); + if (err) { + CONN_GADGET_ERR("copy_to_user f %d\n", err); + err = -EFAULT; + } else { + CONN_GADGET_DBG("online value %d\n", online); + } + + return err; +} + +static long conn_gadget_ioctl(struct file *fp, unsigned int cmd, + unsigned long value) +{ + struct conn_gadget_dev *dev = NULL; + int size; + int flushed = 0; //is closing + int err = 0; //success + const int IOCTL_ARRAY[CONN_GADGET_IOCTL_MAX_NR+1] = { + CONN_GADGET_IOCTL_SUPPORT_LIST, + CONN_GADGET_IOCTL_BIND_WAIT_NOTIFY, + CONN_GADGET_IOCTL_BIND_GET_STATUS + }; + + if (_IOC_TYPE(cmd) != CONN_GADGET_IOCTL_MAGIC_SIG) { + CONN_GADGET_ERR("cmd is not proper ioctl type %c\n", + _IOC_TYPE(cmd)); + return -EINVAL; + } + + if (_IOC_NR(cmd) >= CONN_GADGET_IOCTL_MAX_NR) { + CONN_GADGET_ERR("cmd is not proper ioctl number %d\n", + _IOC_NR(cmd)); + return -ENOTTY; + } + + size = _IOC_SIZE(cmd); + if (!size) { + CONN_GADGET_ERR("cmd has no buffer\n"); + return -EINVAL; + } + + if (!(_IOC_DIR(cmd) & _IOC_READ)) { + CONN_GADGET_ERR("cmd has invalid direction\n"); + return -EINVAL; + } + + if (!_conn_gadget_dev) { + CONN_GADGET_ERR("_conn_gadget_dev is NULL\n"); + return -ENODEV; + } else { + dev = _conn_gadget_dev; + } + + switch (cmd) { + case CONN_GADGET_IOCTL_SUPPORT_LIST: + err = copy_to_user((void __user *)value, (const void *)IOCTL_ARRAY, sizeof(IOCTL_ARRAY)); + if (err) { + CONN_GADGET_ERR("SUPPORT_LIST copy_to_user f %d\n", err); + err = -EFAULT; + } + break; +/** NOTE: +I think, memorized and online vairiable should be atomic variable. talk to choi */ + case CONN_GADGET_IOCTL_BIND_WAIT_NOTIFY: + CONN_GADGET_DBG("in wait_event\n"); + wait_event_interruptible(dev->ioctl_wq, + (dev->memorized != dev->online) + || (flushed = atomic_read(&dev->flush))); + dev->memorized = dev->online; + CONN_GADGET_DBG("out wait_event\n"); + + if (flushed) { + CONN_GADGET_ERR("close called\n"); + err = -EINTR; + break; + } + + err = conn_gadget_bind_status_copy_to_user(value, dev->online); + if (err) + CONN_GADGET_ERR("WAIT_NOTIFY copy_to_user f %d\n", err); + break; + + case CONN_GADGET_IOCTL_BIND_GET_STATUS: + err = conn_gadget_bind_status_copy_to_user(value, dev->online); + if (err) + CONN_GADGET_ERR("GET_STATUS copy_to_user f %d\n", err); + break; + } + + return err; +} + +/* file operations for conn_gadget device /dev/android_ssusbcon */ +static const struct file_operations conn_gadget_fops = { + .owner = THIS_MODULE, + .read = conn_gadget_read, + .write = conn_gadget_write, + .poll = conn_gadget_poll, + .unlocked_ioctl = conn_gadget_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = conn_gadget_ioctl, +#endif + .open = conn_gadget_open, + .release = conn_gadget_release, + .flush = conn_gadget_flush, +}; + +static struct miscdevice conn_gadget_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = conn_gadget_shortname, + .fops = &conn_gadget_fops, +}; + + + + +static int +conn_gadget_function_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct conn_gadget_dev *dev = func_to_conn_gadget(f); + int id; + int ret; + + dev->cdev = cdev; + printk(KERN_ERR "conn_gadget_function_bind dev: %pK\n", dev); + + /* allocate interface ID(s) */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + conn_gadget_interface_desc.bInterfaceNumber = id; + + /* allocate endpoints */ + ret = conn_gadget_create_bulk_endpoints(dev, &conn_gadget_fullspeed_in_desc, + &conn_gadget_fullspeed_out_desc); + if (ret) + return ret; + + /* support high speed hardware */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + conn_gadget_highspeed_in_desc.bEndpointAddress = + conn_gadget_fullspeed_in_desc.bEndpointAddress; + conn_gadget_highspeed_out_desc.bEndpointAddress = + conn_gadget_fullspeed_out_desc.bEndpointAddress; + } + + /* support super speed hardware */ + if (gadget_is_superspeed(c->cdev->gadget)) { + conn_gadget_superspeed_in_desc.bEndpointAddress = + conn_gadget_fullspeed_in_desc.bEndpointAddress; + conn_gadget_superspeed_out_desc.bEndpointAddress = + conn_gadget_fullspeed_out_desc.bEndpointAddress; + } + + printk(KERN_ERR "%s speed %s: IN/%s, OUT/%s\n", + gadget_is_superspeed(c->cdev->gadget) ? "super" : + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + f->name, dev->ep_in->name, dev->ep_out->name); + return 0; +} + +static void +conn_gadget_function_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct conn_gadget_dev *dev = func_to_conn_gadget(f); + struct usb_request *req; + int ep_out_excl_locked = 0; + + printk(KERN_ERR "conn_gadget_function_unbind\n"); + + dev->memorized = dev->online; + dev->online = 0; + dev->error = 1; + pr_debug("%s: error %d\n", __func__, dev->error); + + CONN_GADGET_DBG("ioctl_wq wkup\n"); + wake_up(&dev->ioctl_wq); + + CONN_GADGET_DBG("rd_wq wkup\n"); + wake_up(&dev->read_wq); + + if (conn_gadget_lock(&dev->ep_out_excl)) { + CONN_GADGET_ERR("waiting for request_ep_out to complete\n"); + wait_event(dev->unbind_wq, (0 == atomic_read(&dev->ep_out_excl))); + CONN_GADGET_ERR("request_ep_out finished\n"); + } else { + ep_out_excl_locked = 1; + } + + while ((req = conn_gadget_req_get(dev, &dev->rx_idle))) + conn_gadget_request_free(req, dev->ep_out); + + while ((req = conn_gadget_req_get(dev, &dev->rx_busy))) + conn_gadget_request_free(req, dev->ep_out); + + while ((req = conn_gadget_req_get(dev, &dev->tx_idle))) + conn_gadget_request_free(req, dev->ep_in); + if (ep_out_excl_locked) { + conn_gadget_unlock(&dev->ep_out_excl); + } +} + +static int conn_gadget_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct conn_gadget_dev *dev = func_to_conn_gadget(f); + struct usb_composite_dev *cdev = f->config->cdev; + int ret; + + printk(KERN_ERR "%s: intf: %d alt: %d\n", __func__, intf, alt); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_in); + if (ret) + return ret; + ret = usb_ep_enable(dev->ep_in); + if (ret) + return ret; +#else + ret = usb_ep_enable(dev->ep_in, + ep_choose(cdev->gadget, + &conn_gadget_highspeed_in_desc, + &conn_gadget_fullspeed_in_desc)); + if (ret) + return ret; +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_out); + if (ret) { + usb_ep_disable(dev->ep_in); + return ret; + } + ret = usb_ep_enable(dev->ep_out); + if (ret) { + usb_ep_disable(dev->ep_in); + return ret; + } +#else + ret = usb_ep_enable(dev->ep_out, + ep_choose(cdev->gadget, + &conn_gadget_highspeed_out_desc, + &conn_gadget_fullspeed_out_desc)); + if (ret) { + usb_ep_disable(dev->ep_in); + return ret; + } +#endif + + dev->memorized = dev->online; + dev->online = 1; + dev->error = 0; + + CONN_GADGET_ERR("kfifo_reset\n"); + kfifo_reset(&_conn_gadget_dev->rd_queue); + + //if there is a rx_idle, then usb_ep_queue + CONN_GADGET_DBG("conn_gadget_request_ep_out\n"); + conn_gadget_request_ep_out(_conn_gadget_dev); + + CONN_GADGET_DBG("ioctl_wq wkup\n"); + wake_up(&dev->ioctl_wq); + + /* readers may be blocked waiting for us to go online */ + CONN_GADGET_DBG("rd_wq wkup\n"); + wake_up(&dev->read_wq); + return 0; +} + +static void conn_gadget_function_disable(struct usb_function *f) +{ + struct conn_gadget_dev *dev = func_to_conn_gadget(f); + struct usb_composite_dev *cdev = dev->cdev; + + printk(KERN_ERR "conn_gadget_function_disable cdev %pK\n", cdev); + dev->memorized = dev->online; + dev->online = 0; + dev->error = 1; + pr_debug("%s: error %d\n", __func__, dev->error); + usb_ep_disable(dev->ep_in); + usb_ep_disable(dev->ep_out); + + CONN_GADGET_DBG("ioctl_wq wkup\n"); + wake_up(&dev->ioctl_wq); + + /* readers may be blocked waiting for us to go online */ + CONN_GADGET_DBG("rd_wq wkup\n"); + wake_up(&dev->read_wq); + + pr_debug("%s disabled\n", dev->function.name); +} + +#if 0 +static int conn_gadget_bind_config(struct usb_configuration *c) +{ + struct conn_gadget_dev *dev = _conn_gadget_dev; + + printk(KERN_INFO "conn_gadget_bind_config\n"); + + dev->cdev = c->cdev; + dev->function.name = "conn_gadget"; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + dev->function.fs_descriptors = fs_conn_gadget_descs; +#else + dev->function.descriptors = fs_conn_gadget_descs; +#endif + + dev->function.hs_descriptors = hs_conn_gadget_descs; + dev->function.ss_descriptors = ss_conn_gadget_descs; + dev->function.bind = conn_gadget_function_bind; + dev->function.unbind = conn_gadget_function_unbind; + dev->function.set_alt = conn_gadget_function_set_alt; + dev->function.disable = conn_gadget_function_disable; + + return usb_add_function(c, &dev->function); +} +#endif + +#if IS_ENABLED(CONFIG_F_CONN_GADGET_DEBUGFS) +static ssize_t conn_gadget_usb_buffer_size_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (!_conn_gadget_dev) { + CONN_GADGET_ERR("_conn_gadget_dev is NULL\n"); + return -ENODEV; + } + + return sprintf(buf, "%d\n", (_conn_gadget_dev->transfer_size / 1024)); +} + +static ssize_t conn_gadget_usb_buffer_size_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + void *rd_queue_buf = NULL; + int value; + int kfifo_size; + + if (!_conn_gadget_dev) { + CONN_GADGET_ERR("_conn_gadget_dev is NULL\n"); + return size; + } + + if (_conn_gadget_dev->online) { + CONN_GADGET_ERR("_conn_gaddget_dev onlined\n"); + return size; + } + + if (sscanf(buf, "%d", &value) != 1) + return -EINVAL; + + if (value <= 0 || value > 128) + return -EINVAL; + + kfifo_size = (value * 1024) * CONN_GADGET_DEFAULT_Q_SIZE; + + rd_queue_buf = vmalloc(kfifo_size); + if (!rd_queue_buf) { + CONN_GADGET_ERR("rd_queue_buf vmalloc f\n"); + return size; + } + + if (_conn_gadget_dev->rd_queue_buf) + vfree(_conn_gadget_dev->rd_queue_buf); + + _conn_gadget_dev->transfer_size = value * 1024; + _conn_gadget_dev->rd_queue_size = kfifo_size; + _conn_gadget_dev->rd_queue_buf = rd_queue_buf; + + kfifo_reset(&_conn_gadget_dev->rd_queue); + kfifo_init(&_conn_gadget_dev->rd_queue, + _conn_gadget_dev->rd_queue_buf, + _conn_gadget_dev->rd_queue_size); + + /* T/O/D/O: renew allocate requests for our endpoints */ + /* No need to reallocate in this time, + * Because at alt_set time, requests are newly allocated. */ + + return size; +} + +static ssize_t conn_gadget_out_max_packet_size_show( + struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (!_conn_gadget_dev || !_conn_gadget_dev->ep_out) { + CONN_GADGET_ERR("_conn_gadget_dev is NULL\n"); + return -ENODEV; + } + + return sprintf(buf, "%d\n", (_conn_gadget_dev->ep_out->maxpacket)); +} + +static ssize_t conn_gadget_out_max_packet_size_store( + struct device *dev, + struct device_attribute *attr, const char *buf, + size_t size) +{ + CONN_GADGET_DBG("not supported\n"); + return size; +} + +static ssize_t conn_gadget_in_max_packet_size_show( + struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (!_conn_gadget_dev || !_conn_gadget_dev->ep_in) { + CONN_GADGET_ERR("_conn_gadget_dev is NULL\n"); + return -ENODEV; + } + + return sprintf(buf, "%d\n", (_conn_gadget_dev->ep_in->maxpacket)); +} + +static ssize_t conn_gadget_in_max_packet_size_store( + struct device *dev, + struct device_attribute *attr, const char *buf, + size_t size) +{ + CONN_GADGET_DBG("not supported\n"); + return size; +} + + +static DEVICE_ATTR(usb_buffer_size, S_IRUGO | S_IWUSR, + conn_gadget_usb_buffer_size_show, + conn_gadget_usb_buffer_size_store); + +static DEVICE_ATTR(out_max_packet_size, S_IRUGO | S_IWUSR, + conn_gadget_out_max_packet_size_show, + conn_gadget_out_max_packet_size_store); + +static DEVICE_ATTR(in_max_packet_size, S_IRUGO | S_IWUSR, + conn_gadget_in_max_packet_size_show, + conn_gadget_in_max_packet_size_store); +#endif + +static ssize_t conn_gadget_bInterfaceProtocol_show( + struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (!_conn_gadget_dev) { + CONN_GADGET_ERR("_conn_gadget_dev is NULL\n"); + return -ENODEV; + } + + return sprintf(buf, "%d\n", (conn_gadget_interface_desc.bInterfaceProtocol)); +} + +static ssize_t conn_gadget_bInterfaceProtocol_store( + struct device *dev, + struct device_attribute *attr, const char *buf, + size_t size) +{ + int value; + + if (!_conn_gadget_dev) { + CONN_GADGET_ERR("_conn_gadget_dev is NULL\n"); + return -ENODEV; + } + + if (sscanf(buf, "%d", &value) != 1) + return -EINVAL; + + if (value < 1 || value > 3) + return -EINVAL; + + conn_gadget_interface_desc.bInterfaceProtocol = value; + return size; +} + +static DEVICE_ATTR(bInterfaceProtocol, S_IRUGO | S_IWUSR, + conn_gadget_bInterfaceProtocol_show, + conn_gadget_bInterfaceProtocol_store); + +static struct device_attribute *conn_gadget_function_attributes[] = { +#if IS_ENABLED(CONFIG_F_CONN_GADGET_DEBUGFS) + &dev_attr_usb_buffer_size, + &dev_attr_out_max_packet_size, + &dev_attr_in_max_packet_size, +#endif + &dev_attr_bInterfaceProtocol, + NULL +}; + +extern struct device *create_function_device(char *name); + +static int conn_gadget_setup(struct conn_gadget_instance *fi_conn_gadget) +{ + struct conn_gadget_dev *dev; + struct device *android_dev; + int ret; + struct device_attribute **attrs; + struct device_attribute *attr; + int err = 0; + + printk(KERN_INFO "conn_gadget_setup\n"); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + printk(KERN_ERR "alloc conn_gadget_dev F\n"); + return -ENOMEM; + } + + spin_lock_init(&dev->lock); + + init_waitqueue_head(&dev->read_wq); + init_waitqueue_head(&dev->write_wq); + init_waitqueue_head(&dev->ioctl_wq); + init_waitqueue_head(&dev->unbind_wq); + + atomic_set(&dev->open_excl, 0); + atomic_set(&dev->read_excl, 0); + atomic_set(&dev->write_excl, 0); + atomic_set(&dev->flush, 0); + atomic_set(&dev->ep_out_excl, 0); + + kref_init(&dev->kref); + + INIT_LIST_HEAD(&dev->tx_idle); + INIT_LIST_HEAD(&dev->rx_idle); + INIT_LIST_HEAD(&dev->rx_busy); + + dev->transfer_size = CONN_GADGET_DEFAULT_TRANSFER_SIZE; + dev->rd_queue_size = dev->transfer_size * CONN_GADGET_DEFAULT_Q_SIZE; + dev->rd_queue_buf = vmalloc(dev->rd_queue_size); + if (!dev->rd_queue_buf) { + printk(KERN_ERR "%s: error rd_queue vmalloc\n", __func__); + ret = -ENOMEM; + goto err_; + } + kfifo_init(&dev->rd_queue, dev->rd_queue_buf, dev->rd_queue_size); + + _conn_gadget_dev = dev; + + ret = misc_register(&conn_gadget_device); + if (ret) { + printk(KERN_ERR "%s: misc_register f %d\n", __func__, ret); + goto err_; + } + + android_dev = create_function_device("f_conn_gadget"); + if (IS_ERR(android_dev)) + return PTR_ERR(android_dev); + + attrs = conn_gadget_function_attributes; + + if (attrs) { + while ((attr = *attrs++) && !err) + err = device_create_file(android_dev, attr); + if (err) { + device_destroy(android_dev->class, android_dev->devt); + goto err_; + } + } + + return 0; +err_: + + if (dev->rd_queue_buf) + vfree(dev->rd_queue_buf); + + _conn_gadget_dev = NULL; + kfree(dev); + CONN_GADGET_ERR("conn_gadget gadget driver failed to initialize\n"); + return ret; +} + +static void conn_gadget_cleanup(struct kref *kref) +{ + printk(KERN_INFO "conn_gadget_cleanup\n"); + + if (!_conn_gadget_dev) { + CONN_GADGET_ERR("_conn_gadget_dev is not allocated\n"); + return ; + } + + misc_deregister(&conn_gadget_device); + + if (_conn_gadget_dev->rd_queue_buf) + vfree(_conn_gadget_dev->rd_queue_buf); + + kfree(_conn_gadget_dev); + _conn_gadget_dev = NULL; +} + +static int conn_gadget_setup_configfs(struct conn_gadget_instance *fi_conn_gadget) +{ + return conn_gadget_setup(fi_conn_gadget); +} + +static struct conn_gadget_instance *to_conn_gadget_instance(struct config_item *item) +{ + return container_of(to_config_group(item), struct conn_gadget_instance, + func_inst.group); +} + +static void conn_gadget_attr_release(struct config_item *item) +{ + struct conn_gadget_instance *fi_conn_gadget = to_conn_gadget_instance(item); + usb_put_function_instance(&fi_conn_gadget->func_inst); +} + + +static struct configfs_item_operations conn_gadget_item_ops = { + .release = conn_gadget_attr_release, +}; + +static struct config_item_type conn_gadget_func_type = { + .ct_item_ops = &conn_gadget_item_ops, + .ct_owner = THIS_MODULE, +}; + +static struct conn_gadget_instance *to_fi_conn_gadget(struct usb_function_instance *fi) +{ + return container_of(fi, struct conn_gadget_instance, func_inst); +} + +static int conn_gadget_set_inst_name(struct usb_function_instance *fi, const char *name) +{ + struct conn_gadget_instance *fi_conn_gadget; + char *ptr; + int name_len; + + name_len = strlen(name) + 1; + if (name_len > MAX_INST_NAME_LEN) + return -ENAMETOOLONG; + + ptr = kstrndup(name, name_len, GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + fi_conn_gadget = to_fi_conn_gadget(fi); + fi_conn_gadget->name = ptr; + + return 0; +} + +static void conn_gadget_free_inst(struct usb_function_instance *fi) +{ + struct conn_gadget_instance *fi_conn_gadget; + + fi_conn_gadget = to_fi_conn_gadget(fi); + kfree(fi_conn_gadget->name); + kfree(fi_conn_gadget); + kref_put(&_conn_gadget_dev->kref, conn_gadget_cleanup); +} + +struct usb_function_instance *alloc_inst_conn_gadget(void) +{ + struct conn_gadget_instance *fi_conn; + int err; + + fi_conn = kzalloc(sizeof(*fi_conn), GFP_KERNEL); + + if (!fi_conn) + return ERR_PTR(-ENOMEM); + + fi_conn->func_inst.set_inst_name = conn_gadget_set_inst_name; + fi_conn->func_inst.free_func_inst = conn_gadget_free_inst; + + err = conn_gadget_setup_configfs(fi_conn); + + if (err) { + kfree(fi_conn); + pr_err("Error setting conn gadget\n"); + return ERR_PTR(err); + } + + config_group_init_type_name(&fi_conn->func_inst.group, + "", &conn_gadget_func_type); + + return &fi_conn->func_inst; + +} +EXPORT_SYMBOL_GPL(alloc_inst_conn_gadget); + +static struct usb_function_instance *conn_gadget_alloc_inst(void) +{ + return alloc_inst_conn_gadget(); +} + +static void conn_gadget_free(struct usb_function *f) +{ + /*NO-OP: no function specific resource allocation in conn_gadget_alloc*/ +} + +static struct usb_function *conn_gadget_alloc(struct usb_function_instance *fi) +{ + struct conn_gadget_dev *dev = _conn_gadget_dev; + + dev->function.name = "conn_gadget"; + dev->function.fs_descriptors = fs_conn_gadget_descs; + dev->function.hs_descriptors = hs_conn_gadget_descs; + dev->function.ss_descriptors = ss_conn_gadget_descs; + dev->function.bind = conn_gadget_function_bind; + dev->function.unbind = conn_gadget_function_unbind; + dev->function.set_alt = conn_gadget_function_set_alt; + dev->function.disable = conn_gadget_function_disable; + dev->function.free_func = conn_gadget_free; + + return &dev->function; +} + +DECLARE_USB_FUNCTION_INIT(conn_gadget, conn_gadget_alloc_inst, conn_gadget_alloc); +MODULE_LICENSE("GPL"); + +/* +static int conn_gadget_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev) +{ + printk(KERN_ERR "%s(#) call conn_gadget_setup\n", __func__); + return conn_gadget_setup(); +} + +static void conn_gadget_function_cleanup(struct android_usb_function *f) +{ + printk(KERN_ERR "%s(#) call conn_gadget_cleanup\n", __func__); + conn_gadget_cleanup(); +} + +static int conn_gadget_function_bind_config(struct android_usb_function *f, struct usb_configuration *c) +{ + printk(KERN_ERR "%s(#) call conn_gadget_bind_config\n", __func__); + return conn_gadget_bind_config(c); +} +*/ + + + diff --git a/drivers/usb/gadget/function/f_conn_gadget.ioctl.h b/drivers/usb/gadget/function/f_conn_gadget.ioctl.h new file mode 100644 index 000000000000..d1c2b1f15492 --- /dev/null +++ b/drivers/usb/gadget/function/f_conn_gadget.ioctl.h @@ -0,0 +1,42 @@ +/* + *Gadget Driver's IOCTL for Android Connectivity Gadget + * + *Copyright (C) 2013 DEVGURU CO.,LTD + * + *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. + * + *ChangeLog: + * 20140311 - add ioctl to communicate to userland application +*/ +#ifndef __CONN_GADGET_IOCTL_DEFINE__ +#define __CONN_GADGET_IOCTL_DEFINE__ + +enum { + CONN_GADGET_IOCTL_BIND_STATUS_UNDEFINED = 0, + CONN_GADGET_IOCTL_BIND_STATUS_BIND = 1, + CONN_GADGET_IOCTL_BIND_STATUS_UNBIND = 2 +}; + +enum { + CONN_GADGET_IOCTL_NR_0 = 0, + CONN_GADGET_IOCTL_NR_1, + CONN_GADGET_IOCTL_NR_2, + CONN_GADGET_IOCTL_NR_MAX +}; + +#define IOCTL_SUPPORT_LIST_ARRAY_MAX 255 + +/* ioctl */ +#define CONN_GADGET_IOCTL_MAGIC_SIG 's' +#define CONN_GADGET_IOCTL_SUPPORT_LIST _IOR(CONN_GADGET_IOCTL_MAGIC_SIG, CONN_GADGET_IOCTL_NR_0, int*) +#define CONN_GADGET_IOCTL_BIND_WAIT_NOTIFY _IOR(CONN_GADGET_IOCTL_MAGIC_SIG, CONN_GADGET_IOCTL_NR_1, int) +#define CONN_GADGET_IOCTL_BIND_GET_STATUS _IOR(CONN_GADGET_IOCTL_MAGIC_SIG, CONN_GADGET_IOCTL_NR_2, int) +#define CONN_GADGET_IOCTL_MAX_NR CONN_GADGET_IOCTL_NR_MAX +#endif diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c index 7b9a4cf9b100..37d6f3deb5dd 100644 --- a/drivers/usb/gadget/function/f_mass_storage.c +++ b/drivers/usb/gadget/function/f_mass_storage.c @@ -197,6 +197,13 @@ #include "configfs.h" +#define _SUPPORT_CDFS_ /* support CDFS */ +#define _SUPPORT_MAC_ /* support to recognize CDFS on OSX (MAC PC) */ + +#ifdef _SUPPORT_MAC_ +#define VENDER_CMD_VERSION_INFO 0xfa /* Image version info */ +#define READ_CD 0xbe +#endif /*------------------------------------------------------------------------*/ @@ -286,6 +293,12 @@ struct fsg_common { void *private_data; char inquiry_string[INQUIRY_STRING_LEN]; +#ifdef _SUPPORT_CDFS_ + char vendor_string[8 + 1]; + char product_string[16 + 1]; + /* Additional image version info for SUA */ + char version_string[100 + 1]; +#endif }; struct fsg_dev { @@ -305,6 +318,81 @@ struct fsg_dev { struct usb_ep *bulk_out; }; +#ifdef _SUPPORT_CDFS_ +static int send_message(struct fsg_common *common, char *msg) +{ + char name_buf[120]; + char state_buf[120]; + char *envp[3]; + int env_offset = 0; + struct usb_gadget *gadget = common->gadget; + + DBG(common, "%s called\n", __func__); + pr_info("%s (%s)\n", __func__, msg); + + if (gadget) { + snprintf(name_buf, sizeof(name_buf), + "SWITCH_NAME=USB_MESSAGE"); + envp[env_offset++] = name_buf; + + snprintf(state_buf, sizeof(state_buf), + "SWITCH_STATE=%s", msg); + envp[env_offset++] = state_buf; + + envp[env_offset] = NULL; + + if (!gadget->dev.class) { + gadget->dev.class = class_create(THIS_MODULE, + "usb_msg"); + if (IS_ERR(gadget->dev.class)) + return -1; + } + + DBG(common, "Send cd eject message to daemon\n"); + + kobject_uevent_env(&gadget->dev.kobj, KOBJ_CHANGE, envp); + } + + return 0; +} + +static int do_timer_stop(struct fsg_common *common) +{ + pr_info("%s called\n", __func__); + send_message(common, "time stop"); + + return 0; +} + +static int do_timer_reset(struct fsg_common *common) +{ + pr_info("%s called\n", __func__); + send_message(common, "time reset"); + + return 0; +} + +static int get_version_info(struct fsg_common *common, struct fsg_buffhd *bh) +{ + + u8 *buf = (u8 *) bh->buf; + u8 return_size = common->data_size_from_cmnd; + + pr_info("usb: %s : common->version_string[%d]=%s\r\n", + __func__, common->data_size_from_cmnd, common->version_string); + + memset(buf, 0, common->data_size_from_cmnd); + if (return_size > sizeof(common->version_string)) { + /* driver version infor reply */ + memcpy(buf, common->version_string, sizeof(common->version_string)); + return_size = sizeof(common->version_string); + } else { + memcpy(buf, common->version_string, return_size); + } + return return_size; +} +#endif /* _SUPPORT_CDFS_ */ + static inline int __fsg_is_set(struct fsg_common *common, const char *func, unsigned line) { @@ -582,7 +670,166 @@ static int sleep_thread(struct fsg_common *common, bool can_freeze, BUF_STATE_EMPTY); return rc ? -EINTR : 0; } +#ifdef _SUPPORT_MAC_ +static void _lba_to_msf(u8 *buf, int lba) +{ + lba += 150; + buf[0] = (lba / 75) / 60; + buf[1] = (lba / 75) % 60; + buf[2] = lba % 75; +} +static void cd_data_to_raw(u8 *buf, int lba) +{ + /* sync bytes */ + buf[0] = 0x00; + memset(buf + 1, 0xff, 10); + buf[11] = 0x00; + buf += 12; + /* MSF */ + _lba_to_msf(buf, lba); + buf[3] = 0x01; /* mode 1 data */ + buf += 4; + /* data */ + buf += 2048; + /* XXX: ECC not computed */ + memset(buf, 0, 288); +} + +static int do_read_cd(struct fsg_common *common) +{ + struct fsg_lun *curlun = common->curlun; + u32 lba; + struct fsg_buffhd *bh; + int rc; + u32 amount_left; + loff_t file_offset, file_offset_tmp; + unsigned int amount; + unsigned int partial_page; + ssize_t nread; + + u32 nb_sectors, transfer_request; + + nb_sectors = (common->cmnd[6] << 16) | + (common->cmnd[7] << 8) | common->cmnd[8]; + lba = get_unaligned_be32(&common->cmnd[2]); + + if (nb_sectors == 0) + return 0; + + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + + transfer_request = common->cmnd[9]; + if ((transfer_request & 0xf8) == 0xf8) { + file_offset = ((loff_t) lba) << 11; + /* read all data - 2352 byte */ + amount_left = 2352; + } else { + file_offset = ((loff_t) lba) << 9; + /* Carry out the file reads */ + amount_left = common->data_size_from_cmnd; + } + + if (unlikely(amount_left == 0)) + return -EIO; /* No default reply */ + + for (;;) { + + /* Figure out how much we need to read: + * Try to read the remaining amount. + * But don't read more than the buffer size. + * And don't try to read past the end of the file. + * Finally, if we're not at a page boundary, don't read past + * the next page. + * If this means reading 0 then we were asked to read past + * the end of file. */ + amount = min(amount_left, FSG_BUFLEN); + amount = min((loff_t) amount, + curlun->file_length - file_offset); + partial_page = file_offset & (PAGE_SIZE - 1); + if (partial_page > 0) + amount = min(amount, (unsigned int) PAGE_SIZE - + partial_page); + + /* Wait for the next buffer to become available */ + bh = common->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(common, true, bh); + if (rc) + return rc; + } + + /* If we were asked to read past the end of file, + * end with an empty buffer. */ + if (amount == 0) { + curlun->sense_data = + SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + curlun->sense_data_info = file_offset >> 9; + curlun->info_valid = 1; + bh->inreq->length = 0; + bh->state = BUF_STATE_FULL; + break; + } + + /* Perform the read */ + file_offset_tmp = file_offset; + if ((transfer_request & 0xf8) == 0xf8) { + nread = vfs_read(curlun->filp, + ((char __user *)bh->buf)+16, + amount, &file_offset_tmp); + } else { + nread = vfs_read(curlun->filp, + (char __user *)bh->buf, + amount, &file_offset_tmp); + } + VLDBG(curlun, "file read %u @ %llu -> %d\n", amount, + (unsigned long long) file_offset, + (int) nread); + if (signal_pending(current)) + return -EINTR; + + if (nread < 0) { + LDBG(curlun, "error in file read: %d\n", + (int) nread); + nread = 0; + } else if (nread < amount) { + LDBG(curlun, "partial file read: %d/%u\n", + (int) nread, amount); + nread -= (nread & 511); /* Round down to a block */ + } + file_offset += nread; + amount_left -= nread; + common->residue -= nread; + bh->inreq->length = nread; + bh->state = BUF_STATE_FULL; + + /* If an error occurred, report it and its position */ + if (nread < amount) { + curlun->sense_data = SS_UNRECOVERED_READ_ERROR; + curlun->sense_data_info = file_offset >> 9; + curlun->info_valid = 1; + break; + } + + if (amount_left == 0) + break; /* No more left to read */ + + /* Send this buffer and go read some more */ + if (!start_in_transfer(common, bh)) + /* Don't know what to do if common->fsg is NULL */ + return -EIO; + common->next_buffhd_to_fill = bh->next; + } + + if ((transfer_request & 0xf8) == 0xf8) + cd_data_to_raw(bh->buf, lba); + + return -EIO; /* No default reply */ +} +#endif /* _SUPPORT_MAC_ */ /*-------------------------------------------------------------------------*/ @@ -1038,6 +1285,10 @@ static int do_inquiry(struct fsg_common *common, struct fsg_buffhd *bh) struct fsg_lun *curlun = common->curlun; u8 *buf = (u8 *) bh->buf; +#ifdef _SUPPORT_CDFS_ + static char new_product_name[16 + 1]; +#endif + if (!curlun) { /* Unsupported LUNs are okay */ common->bad_lun_okay = 1; memset(buf, 0, 36); @@ -1054,6 +1305,24 @@ static int do_inquiry(struct fsg_common *common, struct fsg_buffhd *bh) buf[5] = 0; /* No special options */ buf[6] = 0; buf[7] = 0; + +#ifdef _SUPPORT_CDFS_ + strncpy(new_product_name, common->product_string, 16); + new_product_name[16] = '\0'; + if (strlen(common->product_string) <= 11 && + /* check string length */ + common->lun > 0) { + strncat(new_product_name, " Card", 16); + new_product_name[16] = '\0'; + } + + snprintf(common->inquiry_string, + sizeof(common->inquiry_string), + "%-8s%-16s%04x", + common->vendor_string, + new_product_name, 1); +#endif + if (curlun->inquiry_string[0]) memcpy(buf + 8, curlun->inquiry_string, sizeof(curlun->inquiry_string)); @@ -1323,6 +1592,18 @@ static int do_mode_sense(struct fsg_common *common, struct fsg_buffhd *bh) /* Maximum prefetch ceiling */ } buf += 12; +#ifdef _SUPPORT_CDFS_ + } else if (page_code == 0x2A) { + valid_page = 1; + buf[0] = 0x2A; /* Page code */ + buf[1] = 26; /* Page length */ + memset(buf+2, 0, 26);/* None of the fields are changeable */ + buf[2] = 0x02; + buf[3] = 0x02; + buf[4] = 0x04; + buf[6] = 0x28; + buf += 28; +#endif } /* @@ -1367,6 +1648,11 @@ static int do_start_stop(struct fsg_common *common) * available for use as soon as it is loaded. */ if (start) { +#ifdef _SUPPORT_CDFS_ + if (loej) + send_message(common, "Load AT"); +#endif + if (!fsg_lun_is_open(curlun)) { curlun->sense_data = SS_MEDIUM_NOT_PRESENT; return -EINVAL; @@ -1390,6 +1676,10 @@ static int do_start_stop(struct fsg_common *common) up_write(&common->filesem); down_read(&common->filesem); +#ifdef _SUPPORT_CDFS_ + send_message(common, "Load User"); +#endif + return 0; } @@ -2117,6 +2407,23 @@ static int do_scsi_command(struct fsg_common *common) if (reply == 0) reply = do_write(common); break; +#ifdef _SUPPORT_MAC_ + case READ_CD: + common->data_size_from_cmnd = ((common->cmnd[6] << 16) + | (common->cmnd[7] << 8) + | (common->cmnd[8])) << 9; + reply = check_command(common, 12, DATA_DIR_TO_HOST, + (0xf<<2) | (7<<7), 1, + "READ CD"); + if (reply == 0) + reply = do_read_cd(common); + break; + /* reply current image version */ + case VENDER_CMD_VERSION_INFO: + common->data_size_from_cmnd = common->cmnd[4]; + reply = get_version_info(common, bh); + break; +#endif /* _SUPPORT_MAC_ */ /* * Some mandatory commands that we recognize but don't implement. @@ -2126,8 +2433,19 @@ static int do_scsi_command(struct fsg_common *common) */ case FORMAT_UNIT: case RELEASE: +#ifdef _SUPPORT_CDFS_ + /* SUA Timer Stop : 0x17 */ + reply = do_timer_stop(common); + break; +#endif case RESERVE: +#ifdef _SUPPORT_CDFS_ + /* SUA Timer Reset : 0x16 */ + reply = do_timer_reset(common); + break; +#endif case SEND_DIAGNOSTIC: + fallthrough; default: unknown_cmnd: @@ -2976,6 +3294,14 @@ void fsg_common_set_inquiry_string(struct fsg_common *common, const char *vn, ? "File-CD Gadget" : "File-Stor Gadget"), i); +#ifdef _SUPPORT_CDFS_ + /* Default INQUIRY strings */ + strncpy(common->vendor_string, "SAMSUNG", + sizeof(common->vendor_string) - 1); + strncpy(common->product_string, "File-Stor Gadget", + sizeof(common->product_string) - 1); + common->product_string[16] = '\0'; +#endif } EXPORT_SYMBOL_GPL(fsg_common_set_inquiry_string); @@ -3462,6 +3788,168 @@ static const struct config_item_type fsg_func_type = { .ct_owner = THIS_MODULE, }; +#if defined(CONFIG_USB_CONFIGFS_UEVENT) +extern struct device *create_function_device(char *name); +static ssize_t mass_storage_inquiry_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_function_instance *f = dev_get_drvdata(dev); + struct fsg_opts *fsg_opts = fsg_opts_from_func_inst(f); + + return snprintf(buf, PAGE_SIZE, "%s\n", fsg_opts->common->inquiry_string); +} + +static ssize_t mass_storage_inquiry_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct usb_function_instance *f = dev_get_drvdata(dev); + struct fsg_opts *fsg_opts = fsg_opts_from_func_inst(f); + + + if (size >= sizeof(fsg_opts->common->inquiry_string)) + return -EINVAL; + + if (sscanf(buf, "%28s", fsg_opts->common->inquiry_string) != 1) + return -EINVAL; + + return size; +} + +static DEVICE_ATTR(inquiry_string, S_IRUGO | S_IWUSR, + mass_storage_inquiry_show, + mass_storage_inquiry_store); + +static ssize_t mass_storage_vendor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_function_instance *f = dev_get_drvdata(dev); + struct fsg_opts *fsg_opts = fsg_opts_from_func_inst(f); + + return sprintf(buf, "%s\n", fsg_opts->common->vendor_string); +} + +static ssize_t mass_storage_vendor_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct usb_function_instance *f = dev_get_drvdata(dev); + struct fsg_opts *fsg_opts = fsg_opts_from_func_inst(f); + + if (size < strlen(buf)) + return -EINVAL; + if (size >= sizeof(fsg_opts->common->vendor_string)) + return -EINVAL; + if (sscanf(buf, "%s", fsg_opts->common->vendor_string) != 1) + return -EINVAL; + + pr_info("%s: vendor %s", __func__, + fsg_opts->common->vendor_string); + return size; +} + +static DEVICE_ATTR(vendor_string, S_IRUGO | S_IWUSR, + mass_storage_vendor_show, + mass_storage_vendor_store); + +static ssize_t mass_storage_product_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_function_instance *f = dev_get_drvdata(dev); + struct fsg_opts *fsg_opts = fsg_opts_from_func_inst(f); + + return sprintf(buf, "%s\n", fsg_opts->common->product_string); +} + +static ssize_t mass_storage_product_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct usb_function_instance *f = dev_get_drvdata(dev); + struct fsg_opts *fsg_opts = fsg_opts_from_func_inst(f); + + if (size < strlen(buf)) + return -EINVAL; + if (size >= sizeof(fsg_opts->common->product_string)) + return -EINVAL; + if (sscanf(buf, "%s", fsg_opts->common->product_string) != 1) + return -EINVAL; + + pr_info("%s: product %s", __func__, + fsg_opts->common->product_string); + return size; +} + +static DEVICE_ATTR(product_string, S_IRUGO | S_IWUSR, + mass_storage_product_show, + mass_storage_product_store); + +static ssize_t sua_version_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_function_instance *f = dev_get_drvdata(dev); + struct fsg_opts *fsg_opts = fsg_opts_from_func_inst(f); + + int ret; + + ret = sprintf(buf, "%s\r\n", fsg_opts->common->version_string); + pr_info("usb: %s version %s\n", __func__, buf); + return ret; +} + +//sys/class/android_usb/android0/f_mass_storage/sua_version_info + +static ssize_t sua_version_info_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct usb_function_instance *f = dev_get_drvdata(dev); + struct fsg_opts *fsg_opts = fsg_opts_from_func_inst(f); + + int len = 0; + + if (size < sizeof(fsg_opts->common->version_string)) + memcpy(fsg_opts->common->version_string, buf, size); + else { + len = sizeof(fsg_opts->common->version_string); + memcpy(fsg_opts->common->version_string, buf, len-1); + } + pr_info("usb: %s, %s\n", __func__, fsg_opts->common->version_string); + return size; +} + +static DEVICE_ATTR(sua_version_info, S_IRUGO | S_IWUSR, + sua_version_info_show, sua_version_info_store); + +static struct device_attribute *mass_storage_function_attributes[] = { + &dev_attr_inquiry_string, + &dev_attr_vendor_string, + &dev_attr_product_string, + &dev_attr_sua_version_info, + NULL +}; + +static int create_mass_storage_device(struct usb_function_instance *fi) +{ + struct device *dev; + struct device_attribute **attrs; + struct device_attribute *attr; + int err = 0; + + dev = create_function_device("f_mass_storage"); + + if (IS_ERR(dev)) + return PTR_ERR(dev); + + attrs = mass_storage_function_attributes; + if (attrs) { + while ((attr = *attrs++) && !err) + err = device_create_file(dev, attr); + if (err) { + device_destroy(dev->class, dev->devt); + return -EINVAL; + } + } + dev_set_drvdata(dev, fi); + return 0; +} +#endif /*CONFIG_USB_CONFIGFS_UEVENT */ static void fsg_free_inst(struct usb_function_instance *fi) { struct fsg_opts *opts; @@ -3508,6 +3996,14 @@ static struct usb_function_instance *fsg_alloc_inst(void) config_group_init_type_name(&opts->func_inst.group, "", &fsg_func_type); config_group_init_type_name(&opts->lun0.group, "lun.0", &fsg_lun_type); + +#if defined(CONFIG_USB_CONFIGFS_UEVENT) + if (create_mass_storage_device(&opts->func_inst)) { + rc = -ENODEV; + goto release_buffers; + } +#endif + configfs_add_default_group(&opts->lun0.group, &opts->func_inst.group); return &opts->func_inst; diff --git a/drivers/usb/gadget/function/f_ss_acm.c b/drivers/usb/gadget/function/f_ss_acm.c new file mode 100644 index 000000000000..64cdfeb459c0 --- /dev/null +++ b/drivers/usb/gadget/function/f_ss_acm.c @@ -0,0 +1,863 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * f_ss_acm.c -- Samsung USB CDC serial (ACM) function driver + * + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include + +#include "u_serial.h" + + +/* + * This function support Samsung specific serial function support. + */ + +struct f_ss_acm { + struct gserial port; + u8 ctrl_id, data_id; + u8 port_num; + + u8 pending; + + /* lock is mostly for pending and notify_req ... they get accessed + * by callbacks both from tty (open/close/break) under its spinlock, + * and notify_req.complete() which can't use that lock. + */ + spinlock_t lock; + + struct usb_ep *notify; + struct usb_request *notify_req; + + struct usb_cdc_line_coding port_line_coding; /* 8-N-1 etc */ + + /* SetControlLineState request -- CDC 1.1 section 6.2.14 (INPUT) */ + u16 port_handshake_bits; +#define ACM_CTRL_RTS (1 << 1) /* unused with full duplex */ +#define ACM_CTRL_DTR (1 << 0) /* host is ready for data r/w */ + + /* SerialState notification -- CDC 1.1 section 6.3.5 (OUTPUT) */ + u16 serial_state; +#define ACM_CTRL_OVERRUN (1 << 6) +#define ACM_CTRL_PARITY (1 << 5) +#define ACM_CTRL_FRAMING (1 << 4) +#define ACM_CTRL_RI (1 << 3) +#define ACM_CTRL_BRK (1 << 2) +#define ACM_CTRL_DSR (1 << 1) +#define ACM_CTRL_DCD (1 << 0) +}; + +static inline struct f_ss_acm *func_to_acm(struct usb_function *f) +{ + return container_of(f, struct f_ss_acm, port.func); +} + +static inline struct f_ss_acm *port_to_acm(struct gserial *p) +{ + return container_of(p, struct f_ss_acm, port); +} + +/*-------------------------------------------------------------------------*/ + +/* notification endpoint uses smallish and infrequent fixed-size messages */ + +#define GS_NOTIFY_INTERVAL_MS 32 +#define GS_NOTIFY_MAXPACKET 10 /* notification + 2 bytes */ + +/* interface and class descriptors: */ + +static struct usb_interface_assoc_descriptor +acm_iad_descriptor = { + .bLength = sizeof acm_iad_descriptor, + .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, + + /* .bFirstInterface = DYNAMIC, */ + .bInterfaceCount = 2, // control + data + .bFunctionClass = USB_CLASS_COMM, + .bFunctionSubClass = USB_CDC_SUBCLASS_ACM, + .bFunctionProtocol = USB_CDC_ACM_PROTO_AT_V25TER, + /* .iFunction = DYNAMIC */ +}; + + +static struct usb_interface_descriptor acm_control_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, + .bInterfaceProtocol = USB_CDC_ACM_PROTO_AT_V25TER, + /* .iInterface = DYNAMIC */ +}; + +static struct usb_interface_descriptor acm_data_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + /* .iInterface = DYNAMIC */ +}; + +static struct usb_cdc_header_desc acm_header_desc = { + .bLength = sizeof(acm_header_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + .bcdCDC = cpu_to_le16(0x0110), +}; + +static struct usb_cdc_call_mgmt_descriptor +acm_call_mgmt_descriptor = { + .bLength = sizeof(acm_call_mgmt_descriptor), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE, + .bmCapabilities = 0, + /* .bDataInterface = DYNAMIC */ +}; + +static struct usb_cdc_acm_descriptor acm_descriptor = { + .bLength = sizeof(acm_descriptor), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_ACM_TYPE, + .bmCapabilities = USB_CDC_CAP_LINE, +}; + +static struct usb_cdc_union_desc acm_union_desc = { + .bLength = sizeof(acm_union_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + /* .bMasterInterface0 = DYNAMIC */ + /* .bSlaveInterface0 = DYNAMIC */ +}; + +/* full speed support: */ + +static struct usb_endpoint_descriptor acm_fs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(GS_NOTIFY_MAXPACKET), + .bInterval = GS_NOTIFY_INTERVAL_MS, +}; + +static struct usb_endpoint_descriptor acm_fs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor acm_fs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *acm_fs_function[] = { + (struct usb_descriptor_header *) &acm_iad_descriptor, + (struct usb_descriptor_header *) &acm_control_interface_desc, + (struct usb_descriptor_header *) &acm_header_desc, + (struct usb_descriptor_header *) &acm_call_mgmt_descriptor, + (struct usb_descriptor_header *) &acm_descriptor, + (struct usb_descriptor_header *) &acm_union_desc, + (struct usb_descriptor_header *) &acm_fs_notify_desc, + (struct usb_descriptor_header *) &acm_data_interface_desc, + (struct usb_descriptor_header *) &acm_fs_in_desc, + (struct usb_descriptor_header *) &acm_fs_out_desc, + NULL, +}; + +/* high speed support: */ +static struct usb_endpoint_descriptor acm_hs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(GS_NOTIFY_MAXPACKET), + .bInterval = USB_MS_TO_HS_INTERVAL(GS_NOTIFY_INTERVAL_MS), +}; + +static struct usb_endpoint_descriptor acm_hs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor acm_hs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_descriptor_header *acm_hs_function[] = { + (struct usb_descriptor_header *) &acm_iad_descriptor, + (struct usb_descriptor_header *) &acm_control_interface_desc, + (struct usb_descriptor_header *) &acm_header_desc, + (struct usb_descriptor_header *) &acm_call_mgmt_descriptor, + (struct usb_descriptor_header *) &acm_descriptor, + (struct usb_descriptor_header *) &acm_union_desc, + (struct usb_descriptor_header *) &acm_hs_notify_desc, + (struct usb_descriptor_header *) &acm_data_interface_desc, + (struct usb_descriptor_header *) &acm_hs_in_desc, + (struct usb_descriptor_header *) &acm_hs_out_desc, + NULL, +}; + +static struct usb_endpoint_descriptor acm_ss_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor acm_ss_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor acm_ss_bulk_comp_desc = { + .bLength = sizeof acm_ss_bulk_comp_desc, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, +}; + +static struct usb_descriptor_header *acm_ss_function[] = { + (struct usb_descriptor_header *) &acm_iad_descriptor, + (struct usb_descriptor_header *) &acm_control_interface_desc, + (struct usb_descriptor_header *) &acm_header_desc, + (struct usb_descriptor_header *) &acm_call_mgmt_descriptor, + (struct usb_descriptor_header *) &acm_descriptor, + (struct usb_descriptor_header *) &acm_union_desc, + (struct usb_descriptor_header *) &acm_hs_notify_desc, + (struct usb_descriptor_header *) &acm_ss_bulk_comp_desc, + (struct usb_descriptor_header *) &acm_data_interface_desc, + (struct usb_descriptor_header *) &acm_ss_in_desc, + (struct usb_descriptor_header *) &acm_ss_bulk_comp_desc, + (struct usb_descriptor_header *) &acm_ss_out_desc, + (struct usb_descriptor_header *) &acm_ss_bulk_comp_desc, + NULL, +}; + +/* string descriptors: */ + +#define ACM_CTRL_IDX 0 +#define ACM_DATA_IDX 1 +#define ACM_IAD_IDX 2 + +/* static strings, in UTF-8 */ +static struct usb_string acm_string_defs[] = { + [ACM_CTRL_IDX].s = "CDC Abstract Control Model (ACM)", + [ACM_DATA_IDX].s = "CDC ACM Data", + [ACM_IAD_IDX].s = "CDC Serial", + { } /* end of list */ +}; + +static struct usb_gadget_strings acm_string_table = { + .language = 0x0409, /* en-us */ + .strings = acm_string_defs, +}; + +static struct usb_gadget_strings *acm_strings[] = { + &acm_string_table, + NULL, +}; + +/*-------------------------------------------------------------------------*/ + +/* ACM control ... data handling is delegated to tty library code. + * The main task of this function is to activate and deactivate + * that code based on device state; track parameters like line + * speed, handshake state, and so on; and issue notifications. + */ + +static void acm_complete_set_line_coding(struct usb_ep *ep, + struct usb_request *req) +{ + struct f_ss_acm *acm = ep->driver_data; + struct usb_composite_dev *cdev = acm->port.func.config->cdev; + + if (req->status != 0) { + dev_dbg(&cdev->gadget->dev, "acm ttyGS%d completion, err %d\n", + acm->port_num, req->status); + return; + } + + /* normal completion */ + if (req->actual != sizeof(acm->port_line_coding)) { + dev_dbg(&cdev->gadget->dev, "acm ttyGS%d short resp, len %d\n", + acm->port_num, req->actual); + usb_ep_set_halt(ep); + } else { + struct usb_cdc_line_coding *value = req->buf; + + /* REVISIT: we currently just remember this data. + * If we change that, (a) validate it first, then + * (b) update whatever hardware needs updating, + * (c) worry about locking. This is information on + * the order of 9600-8-N-1 ... most of which means + * nothing unless we control a real RS232 line. + */ + acm->port_line_coding = *value; + } +} + +static int acm_cdc_notify(struct f_ss_acm *acm, u8 type, u16 value, void *data, unsigned length); + +static int acm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct f_ss_acm *acm = func_to_acm(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + /* composite driver infrastructure handles everything except + * CDC class messages; interface activation uses set_alt(). + * + * Note CDC spec table 4 lists the ACM request profile. It requires + * encapsulated command support ... we don't handle any, and respond + * to them by stalling. Options include get/set/clear comm features + * (not that useful) and SEND_BREAK. + */ + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + + /* SET_LINE_CODING ... just read and save what the host sends */ + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_SET_LINE_CODING: + if (w_length != sizeof(struct usb_cdc_line_coding) + || w_index != acm->ctrl_id) + goto invalid; + + value = w_length; + cdev->gadget->ep0->driver_data = acm; + req->complete = acm_complete_set_line_coding; + break; + + /* GET_LINE_CODING ... return what host sent, or initial value */ + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_GET_LINE_CODING: + if (w_index != acm->ctrl_id) + goto invalid; + + value = min_t(unsigned, w_length, + sizeof(struct usb_cdc_line_coding)); + memcpy(req->buf, &acm->port_line_coding, value); + break; + + /* SET_CONTROL_LINE_STATE ... save what the host sent */ + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_SET_CONTROL_LINE_STATE: + if (w_index != acm->ctrl_id) + goto invalid; + + value = 0; + + /* FIXME we should not allow data to flow until the + * host sets the ACM_CTRL_DTR bit; and when it clears + * that bit, we should return to that no-flow state. + */ + acm->port_handshake_bits = w_value; + spin_lock(&acm->lock); + if ((acm->port_handshake_bits & ACM_CTRL_DTR) && acm->pending) { + int status; + __le16 serial_state; + + if (acm->notify_req) { + serial_state = cpu_to_le16(acm->serial_state); + status = acm_cdc_notify(acm, USB_CDC_NOTIFY_SERIAL_STATE, + 0, &serial_state, sizeof(acm->serial_state)); + } + } + spin_unlock(&acm->lock); + pr_debug("%s: USB_CDC_REQ_SET_CONTROL_LINE_STATE: DTR:%d RST:%d\n", + __func__, w_value & ACM_CTRL_DTR ? 1 : 0, + w_value & ACM_CTRL_RTS ? 1 : 0); + break; + + default: +invalid: + dev_vdbg(&cdev->gadget->dev, + "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + dev_dbg(&cdev->gadget->dev, + "acm ttyGS%d req%02x.%02x v%04x i%04x l%d\n", + acm->port_num, ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = 0; + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + ERROR(cdev, "acm response on ttyGS%d, err %d\n", + acm->port_num, value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + +static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_ss_acm *acm = func_to_acm(f); + struct usb_composite_dev *cdev = f->config->cdev; + + /* we know alt == 0, so this is an activation or a reset */ + + if (intf == acm->ctrl_id) { + if (acm->notify->enabled) { + dev_vdbg(&cdev->gadget->dev, + "reset acm control interface %d\n", intf); + usb_ep_disable(acm->notify); + } + + if (!acm->notify->desc) + if (config_ep_by_speed(cdev->gadget, f, acm->notify)) + return -EINVAL; + + usb_ep_enable(acm->notify); + + } else if (intf == acm->data_id) { + if (acm->notify->enabled) { + dev_info(&cdev->gadget->dev, + "reset acm ttyGS%d\n", acm->port_num); + gserial_disconnect(&acm->port); + } + if (!acm->port.in->desc || !acm->port.out->desc) { + dev_info(&cdev->gadget->dev, + "activate acm ttyGS%d\n", acm->port_num); + if (config_ep_by_speed(cdev->gadget, f, + acm->port.in) || + config_ep_by_speed(cdev->gadget, f, + acm->port.out)) { + acm->port.in->desc = NULL; + acm->port.out->desc = NULL; + return -EINVAL; + } + } + gserial_connect(&acm->port, acm->port_num); + + } else + return -EINVAL; + + return 0; +} + +static void acm_disable(struct usb_function *f) +{ + struct f_ss_acm *acm = func_to_acm(f); + struct usb_composite_dev *cdev = f->config->cdev; + + pr_err("%s: acm ttyGS%d deactivated\n", __func__, acm->port_num); + dev_dbg(&cdev->gadget->dev, "acm ttyGS%d deactivated\n", acm->port_num); + gserial_disconnect(&acm->port); + usb_ep_disable(acm->notify); +} + +/*-------------------------------------------------------------------------*/ + +/** + * acm_cdc_notify - issue CDC notification to host + * @acm: wraps host to be notified + * @type: notification type + * @value: Refer to cdc specs, wValue field. + * @data: data to be sent + * @length: size of data + * Context: irqs blocked, acm->lock held, acm_notify_req non-null + * + * Returns zero on success or a negative errno. + * + * See section 6.3.5 of the CDC 1.1 specification for information + * about the only notification we issue: SerialState change. + */ +static int acm_cdc_notify(struct f_ss_acm *acm, u8 type, u16 value, + void *data, unsigned length) +{ + struct usb_ep *ep = acm->notify; + struct usb_request *req; + struct usb_cdc_notification *notify; + const unsigned len = sizeof(*notify) + length; + void *buf; + int status; + + req = acm->notify_req; + acm->notify_req = NULL; + acm->pending = false; + + req->length = len; + notify = req->buf; + buf = notify + 1; + + notify->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS + | USB_RECIP_INTERFACE; + notify->bNotificationType = type; + notify->wValue = cpu_to_le16(value); + notify->wIndex = cpu_to_le16(acm->ctrl_id); + notify->wLength = cpu_to_le16(length); + memcpy(buf, data, length); + + /* ep_queue() can complete immediately if it fills the fifo... */ + spin_unlock(&acm->lock); + status = usb_ep_queue(ep, req, GFP_ATOMIC); + spin_lock(&acm->lock); + + if (status < 0) { + ERROR(acm->port.func.config->cdev, + "acm ttyGS%d can't notify serial state, %d\n", + acm->port_num, status); + acm->notify_req = req; + } + + return status; +} + +static int acm_notify_serial_state(struct f_ss_acm *acm) +{ + struct usb_composite_dev *cdev = acm->port.func.config->cdev; + int status; + __le16 serial_state; + + spin_lock(&acm->lock); + if (acm->notify_req && (acm->port_handshake_bits & ACM_CTRL_DTR)) { + dev_dbg(&cdev->gadget->dev, "acm ttyGS%d serial state %04x\n", + acm->port_num, acm->serial_state); + serial_state = cpu_to_le16(acm->serial_state); + status = acm_cdc_notify(acm, USB_CDC_NOTIFY_SERIAL_STATE, + 0, &serial_state, sizeof(acm->serial_state)); + } else { + acm->pending = true; + status = 0; + } + spin_unlock(&acm->lock); + return status; +} + +static void acm_cdc_notify_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_ss_acm *acm = req->context; + u8 doit = false; + + /* on this call path we do NOT hold the port spinlock, + * which is why ACM needs its own spinlock + */ + spin_lock(&acm->lock); + if (req->status != -ESHUTDOWN) + doit = acm->pending; + acm->notify_req = req; + spin_unlock(&acm->lock); + + if (doit) + acm_notify_serial_state(acm); +} + +/* connect == the TTY link is open */ + +static void acm_connect(struct gserial *port) +{ + struct f_ss_acm *acm = port_to_acm(port); + + acm->serial_state |= ACM_CTRL_DSR | ACM_CTRL_DCD; + acm_notify_serial_state(acm); +} + +static void acm_disconnect(struct gserial *port) +{ + struct f_ss_acm *acm = port_to_acm(port); + + acm->serial_state &= ~(ACM_CTRL_DSR | ACM_CTRL_DCD); + acm_notify_serial_state(acm); +} + +static int acm_send_break(struct gserial *port, int duration) +{ + struct f_ss_acm *acm = port_to_acm(port); + u16 state; + + state = acm->serial_state; + state &= ~ACM_CTRL_BRK; + if (duration) + state |= ACM_CTRL_BRK; + + acm->serial_state = state; + return acm_notify_serial_state(acm); +} + +/*-------------------------------------------------------------------------*/ + +/* ACM function driver setup/binding */ +static int +acm_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_ss_acm *acm = func_to_acm(f); + struct usb_string *us; + int status; + struct usb_ep *ep; + + /* REVISIT might want instance-specific strings to help + * distinguish instances ... + */ + + /* maybe allocate device-global string IDs, and patch descriptors */ + us = usb_gstrings_attach(cdev, acm_strings, + ARRAY_SIZE(acm_string_defs)); + if (IS_ERR(us)) + return PTR_ERR(us); + acm_control_interface_desc.iInterface = us[ACM_CTRL_IDX].id; + acm_data_interface_desc.iInterface = us[ACM_DATA_IDX].id; + acm_iad_descriptor.iFunction = us[ACM_IAD_IDX].id; + + /* allocate instance-specific interface IDs, and patch descriptors */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + acm->ctrl_id = status; + acm_iad_descriptor.bFirstInterface = status; + + acm_control_interface_desc.bInterfaceNumber = status; + acm_union_desc .bMasterInterface0 = status; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + acm->data_id = status; + + acm_data_interface_desc.bInterfaceNumber = status; + acm_union_desc.bSlaveInterface0 = status; + acm_call_mgmt_descriptor.bDataInterface = status; + + status = -ENODEV; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_in_desc); + if (!ep) + goto fail; + acm->port.in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_out_desc); + if (!ep) + goto fail; + acm->port.out = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_notify_desc); + if (!ep) + goto fail; + acm->notify = ep; + + /* allocate notification */ + acm->notify_req = gs_alloc_req(ep, + sizeof(struct usb_cdc_notification) + 2, + GFP_KERNEL); + if (!acm->notify_req) + goto fail; + + acm->notify_req->complete = acm_cdc_notify_complete; + acm->notify_req->context = acm; + + /* support all relevant hardware speeds... we expect that when + * hardware is dual speed, all bulk-capable endpoints work at + * both speeds + */ + acm_hs_in_desc.bEndpointAddress = acm_fs_in_desc.bEndpointAddress; + acm_hs_out_desc.bEndpointAddress = acm_fs_out_desc.bEndpointAddress; + acm_hs_notify_desc.bEndpointAddress = + acm_fs_notify_desc.bEndpointAddress; + + acm_ss_in_desc.bEndpointAddress = acm_fs_in_desc.bEndpointAddress; + acm_ss_out_desc.bEndpointAddress = acm_fs_out_desc.bEndpointAddress; + + status = usb_assign_descriptors(f, acm_fs_function, acm_hs_function, + acm_ss_function, acm_ss_function); + if (status) + goto fail; + + pr_err("%s: acm ttyGS%d\n", __func__, acm->port_num); + dev_dbg(&cdev->gadget->dev, + "acm ttyGS%d: %s speed IN/%s OUT/%s NOTIFY/%s\n", + acm->port_num, + gadget_is_superspeed(c->cdev->gadget) ? "super" : + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + acm->port.in->name, acm->port.out->name, + acm->notify->name); + return 0; + +fail: + if (acm->notify_req) + gs_free_req(acm->notify, acm->notify_req); + + ERROR(cdev, "%s/%p: can't bind, err %d\n", f->name, f, status); + + return status; +} + +static void acm_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_ss_acm *acm = func_to_acm(f); + + pr_err("%s: acm ttyGS%d\n", __func__, acm->port_num); + acm_string_defs[0].id = 0; + /* Ensure port is disconnected before unbinding */ + gserial_disconnect(&acm->port); + usb_free_all_descriptors(f); + if (acm->notify_req) + gs_free_req(acm->notify, acm->notify_req); +} + +static void acm_free_func(struct usb_function *f) +{ + struct f_ss_acm *acm = func_to_acm(f); + + kfree(acm); +} + +static void acm_resume(struct usb_function *f) +{ + struct f_ss_acm *acm = func_to_acm(f); + + gserial_resume(&acm->port); +} + +static void acm_suspend(struct usb_function *f) +{ + struct f_ss_acm *acm = func_to_acm(f); + + gserial_suspend(&acm->port); +} + +static struct usb_function *ss_acm_alloc_func(struct usb_function_instance *fi) +{ + struct f_serial_opts *opts; + struct f_ss_acm *acm; + + acm = kzalloc(sizeof(*acm), GFP_KERNEL); + if (!acm) + return ERR_PTR(-ENOMEM); + + spin_lock_init(&acm->lock); + + acm->port.connect = acm_connect; + acm->port.disconnect = acm_disconnect; + acm->port.send_break = acm_send_break; + + acm->port.func.name = "ss_acm"; + acm->port.func.strings = acm_strings; + /* descriptors are per-instance copies */ + acm->port.func.bind = acm_bind; + acm->port.func.set_alt = acm_set_alt; + acm->port.func.setup = acm_setup; + acm->port.func.disable = acm_disable; + + opts = container_of(fi, struct f_serial_opts, func_inst); + acm->port_num = opts->port_num; + acm->port.func.unbind = acm_unbind; + acm->port.func.free_func = acm_free_func; + acm->port.func.resume = acm_resume; + acm->port.func.suspend = acm_suspend; + + return &acm->port.func; +} + +static inline struct f_serial_opts *to_f_serial_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_serial_opts, + func_inst.group); +} + +static void acm_attr_release(struct config_item *item) +{ + struct f_serial_opts *opts = to_f_serial_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static struct configfs_item_operations acm_item_ops = { + .release = acm_attr_release, +}; + +#ifdef CONFIG_U_SERIAL_CONSOLE + +static ssize_t f_acm_console_store(struct config_item *item, + const char *page, size_t count) +{ + return gserial_set_console(to_f_serial_opts(item)->port_num, + page, count); +} + +static ssize_t f_acm_console_show(struct config_item *item, char *page) +{ + return gserial_get_console(to_f_serial_opts(item)->port_num, page); +} + +CONFIGFS_ATTR(f_acm_, console); + +#endif /* CONFIG_U_SERIAL_CONSOLE */ + +static ssize_t f_acm_port_num_show(struct config_item *item, char *page) +{ + return sprintf(page, "%u\n", to_f_serial_opts(item)->port_num); +} + +CONFIGFS_ATTR_RO(f_acm_, port_num); + +static struct configfs_attribute *acm_attrs[] = { +#ifdef CONFIG_U_SERIAL_CONSOLE + &f_acm_attr_console, +#endif + &f_acm_attr_port_num, + NULL, +}; + +static const struct config_item_type acm_func_type = { + .ct_item_ops = &acm_item_ops, + .ct_attrs = acm_attrs, + .ct_owner = THIS_MODULE, +}; + +static void acm_free_instance(struct usb_function_instance *fi) +{ + struct f_serial_opts *opts; + + opts = container_of(fi, struct f_serial_opts, func_inst); + gserial_free_line(opts->port_num); + kfree(opts); +} + +static struct usb_function_instance *ss_acm_alloc_instance(void) +{ + struct f_serial_opts *opts; + int ret; + + opts = kzalloc(sizeof(*opts), GFP_KERNEL); + if (!opts) + return ERR_PTR(-ENOMEM); + opts->func_inst.free_func_inst = acm_free_instance; + ret = gserial_alloc_line(&opts->port_num); + if (ret) { + kfree(opts); + return ERR_PTR(ret); + } + config_group_init_type_name(&opts->func_inst.group, "", + &acm_func_type); + return &opts->func_inst; +} +DECLARE_USB_FUNCTION_INIT(ss_acm, ss_acm_alloc_instance, ss_acm_alloc_func); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/f_ss_mon_gadget.c b/drivers/usb/gadget/function/f_ss_mon_gadget.c new file mode 100644 index 000000000000..c0b4bf2080aa --- /dev/null +++ b/drivers/usb/gadget/function/f_ss_mon_gadget.c @@ -0,0 +1,1036 @@ +/* + * f_ss_mon_gadget.c - generic USB serial function driver + * + * Copyright (C) 2021 by samsung Corporation + * + * This software is distributed under the terms of the GNU General + * Public License ("GPL") as published by the Free Software Foundation, + * either version 2 of that License or (at your option) any later version. + */ + +#include +#include +#include +#include + +#include +#include + +#include "../configfs.h" +#include +#include +#include + +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) +#include +#endif + +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) +#include +#endif + +#include + +#define MAX_INST_NAME_LEN 40 +#define MAX_NAME_LEN 40 + +#define DRIVER_NAME "usb_mtp_gadget" +#define MAX_GUID_SIZE 0x28 +static const char mtpg_longname[] = "mtp"; +static const char shortname[] = DRIVER_NAME; + +static char guid_info[MAX_GUID_SIZE+1]; +/* ID for Microsoft MTP OS String */ +#define MTPG_OS_STRING_ID 0xEE +#define GADGET_MTP 0x01 +#define GADGET_RNDIS 0x02 +#define GADGET_ACCESSORY 0x04 +#define GADGET_ADB 0x08 +#define GADGET_ACM 0x10 +#define GADGET_DM 0x20 +#define GADGET_MIDI 0x40 +#define GADGET_CONN_GADGET 0x80 + +struct usb_mtp_avd_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubType; + + __u16 bDAU1_Type; + __u16 bDAU1_Length; + __u8 bDAU1_Value; +} __attribute__ ((packed)); + +static struct usb_mtp_avd_descriptor mtp_avd_descriptor = { + .bLength = 0x08, + .bDescriptorType = 0x24, + .bDescriptorSubType = 0x80, + + /* First DAU = MTU Size */ + .bDAU1_Type = 0x000C, + .bDAU1_Length = 0x0001, + .bDAU1_Value = 0x01, +}; + +static struct usb_descriptor_header *log_fs_function[] = { + (struct usb_descriptor_header *) &mtp_avd_descriptor, + NULL, +}; + +/* high speed support: */ +static struct usb_descriptor_header *log_hs_function[] = { + (struct usb_descriptor_header *) &mtp_avd_descriptor, + NULL, +}; + +/* super speed support: */ +static struct usb_descriptor_header *log_ss_function[] = { + (struct usb_descriptor_header *) &mtp_avd_descriptor, + NULL, +}; + +#define ACCESSORY_STRING_MASK ( \ + 1 << ACCESSORY_STRING_MANUFACTURER | \ + 1 << ACCESSORY_STRING_MODEL | \ + 1 << ACCESSORY_STRING_VERSION) + +enum { + RELEASE = 0, + NOTIFY = 1, +}; + +struct f_ss_monitor { + struct usb_function function; + int current_usb_mode; + u8 intreface_id; + int accessory_string; + u8 aoa_start_cmd; + int usb_function_info; + char usb_mode[50]; + int vbus_current; + bool is_bind; + struct ss_monitor_instance *func_inst; + struct work_struct set_vbus_current_work; +}; + +static inline struct f_ss_monitor *func_to_ss_monitor(struct usb_function *f) +{ + return container_of(f, struct f_ss_monitor, function); +} + +struct aoa_connect_status { + struct delayed_work usb_reset_event_work; + ktime_t rst_time_before; + ktime_t rst_time_first; + int rst_err_cnt; + bool rst_err_noti; + bool event_state; + bool acc_dev_status; + bool acc_online; +}; + +struct ss_monitor_instance { + struct usb_function_instance func_inst; + char *name; + struct f_ss_monitor *ss_monitor; + struct aoa_connect_status aoa_reset; + bool vbus_session_called; +}; + +static inline struct ss_monitor_instance *to_fi_ss_monitor(struct usb_function_instance *fi) +{ + return container_of(fi, struct ss_monitor_instance, func_inst); +} + +static struct ss_monitor_instance *g_ss_monitor; + +#define ERR_RESET_CNT 3 + +static inline int _lock(atomic_t *excl) +{ + pr_info("[%s] \tline = [%d]\n", __func__, __LINE__); + + if (atomic_inc_return(excl) == 1) + return 0; + + atomic_dec(excl); + return -1; +} + +static inline void _unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +struct mtpg_dev { + int error; + atomic_t open_excl; +}; +static struct mtpg_dev *the_mtpg; + +static int mtpg_open(struct inode *ip, struct file *fp) +{ + pr_info("[USB] %s: \tline = [%d]\n", __func__, __LINE__); + + if (_lock(&the_mtpg->open_excl)) { + pr_err("[USB] %s: fn mtpg device busy\n", __func__); + return -EBUSY; + } + + fp->private_data = the_mtpg; + + /* clear the error latch */ + the_mtpg->error = 0; + + return 0; +} + +static int mtpg_release_device(struct inode *ip, struct file *fp) +{ + pr_info("[USB] %s: \tline = [%d]\n", __func__, __LINE__); + if (the_mtpg != NULL) + _unlock(&the_mtpg->open_excl); + return 0; +} + +/* file operations for MTP device /dev/usb_mtp_gadget */ +static const struct file_operations mtpg_fops = { + .owner = THIS_MODULE, + .open = mtpg_open, + .release = mtpg_release_device, +}; + +static struct miscdevice mtpg_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = shortname, + .fops = &mtpg_fops, +}; + +/*-------------------------------------------------------------------------*/ +static int ss_monitor_set_alt(struct usb_function *f, + unsigned int intf, unsigned int alt) +{ + struct f_ss_monitor *ss_monitor; + + ss_monitor = func_to_ss_monitor(f); + g_ss_monitor->aoa_reset.acc_dev_status = false; + if (ss_monitor->usb_function_info & GADGET_ACCESSORY) { + pr_info("[USB] %s: aoa connect\n", __func__); + g_ss_monitor->aoa_reset.acc_online = true; + g_ss_monitor->aoa_reset.rst_err_cnt = 0; + } + return 0; +} + +static void ss_monitor_disable(struct usb_function *f) +{ + struct f_ss_monitor *ss_monitor; + char aoa_check[12] = {0,}; + + ss_monitor = func_to_ss_monitor(f); + if (!ss_monitor) + goto ret; + ss_monitor->vbus_current = USB_CURRENT_UNCONFIGURED; + if (ss_monitor->accessory_string && !ss_monitor->aoa_start_cmd) { + snprintf(aoa_check, sizeof(aoa_check), "AOA_ERR_%x", ss_monitor->accessory_string); +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + store_usblog_notify(NOTIFY_USBMODE_EXTRA, (void *)aoa_check, NULL); +#endif + } + ss_monitor->accessory_string = 0; + ss_monitor->aoa_start_cmd = 0; +ret: + if (g_ss_monitor->aoa_reset.acc_online) { + g_ss_monitor->aoa_reset.acc_online = false; + g_ss_monitor->aoa_reset.acc_dev_status = true; + } + + memset(guid_info, 0, sizeof(guid_info)); +} + +/* for BC1.2 spec */ +static int set_vbus_current(int state) +{ + struct power_supply *psy; + union power_supply_propval pval = {0}; + + pr_info("[USB] %s: %dmA\n", __func__, state); + psy = power_supply_get_by_name("battery"); + if (!psy) { + pr_err("[USB] %s: fail to get battery power_supply\n", __func__); + return -1; + } + + pval.intval = state; + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_USB_CONFIGURE, pval); + power_supply_put(psy); + return 0; +} + +/* notify current change */ +static void set_vbus_current_work(struct work_struct *w) +{ + struct f_ss_monitor *ss_monitor; +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + struct otg_notify *o_notify = get_otg_notify(); +#endif + + ss_monitor = container_of(w, struct f_ss_monitor, set_vbus_current_work); + + switch (ss_monitor->vbus_current) { + case USB_CURRENT_SUSPENDED: +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + /* set vbus current for suspend state is called in usb_notify. */ + send_otg_notify(o_notify, NOTIFY_EVENT_USBD_SUSPENDED, 1); +#endif + goto skip; + case USB_CURRENT_UNCONFIGURED: +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + send_otg_notify(o_notify, NOTIFY_EVENT_USBD_UNCONFIGURED, 1); +#endif + break; + case USB_CURRENT_HIGH_SPEED: + case USB_CURRENT_SUPER_SPEED: +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + send_otg_notify(o_notify, NOTIFY_EVENT_USBD_CONFIGURED, 1); +#endif + break; + default: + break; + } + set_vbus_current(ss_monitor->vbus_current); +skip: + return; +} + +/*-------------------------------------------------------------------------*/ +static void +mtp_complete_get_guid(struct usb_ep *ep, struct usb_request *req) +{ + int size; + if (the_mtpg) + pr_info("[USB] %s: \tline = [%d]\n", __func__, __LINE__); + else + pr_info("[USB] %s: \tline = [%d] / mtpg misc driver load fails\n", __func__, __LINE__); + + if (req->status != 0) { + pr_info("[USB] %s: req->status !=0\tline = [%d]\n", __func__, __LINE__); + return; + } + + if (req->actual >= sizeof(guid_info)) + size = sizeof(guid_info)-1; + else + size = req->actual; + memset(guid_info, 0, sizeof(guid_info)); + memcpy(guid_info, req->buf, size); +} + +static ssize_t guid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("[USB] %s: \tline = [%d]\n", __func__, __LINE__); + memcpy(buf, guid_info, MAX_GUID_SIZE); + return MAX_GUID_SIZE; +} + +static ssize_t guid_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int value; + + pr_info("[USB] %s: \tline = [%d]\n", __func__, __LINE__); + if (size > MAX_GUID_SIZE) + return -EINVAL; + + value = strlcpy(guid_info, buf, size); + return value; +} + +DEVICE_ATTR_RW(guid); + +#if defined(CONFIG_USB_NOTIFY_PROC_LOG) +static bool is_aoa_string_come(int string_index) +{ + if ((string_index & (1<aoa_reset.event_state); + +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + if (g_ss_monitor->aoa_reset.event_state) + send_usb_err_uevent(USB_ERR_ABNORMAL_RESET, NOTIFY); + else + send_usb_err_uevent(USB_ERR_ABNORMAL_RESET, RELEASE); +#endif +} + +void vbus_session_notify(struct usb_gadget *gadget, int is_active, int ret) +{ + if (!g_ss_monitor) + return; + + if (ret == EAGAIN) { + g_ss_monitor->vbus_session_called = true; + return; + } else if (g_ss_monitor->vbus_session_called) { + pr_info("[USB] %s : vbus_session %s run_stop(%s)\n", + __func__, is_active ? "On" : "Off", ret ? "fail" : "success"); + g_ss_monitor->vbus_session_called = false; + if (is_active) { +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + if (!ret) + store_usblog_notify(NOTIFY_USBSTATE, + (void *)"USB_STATE=VBUS:EN:SUCCESS", NULL); + else + store_usblog_notify(NOTIFY_USBSTATE, + (void *)"USB_STATE=VBUS:EN:FAIL", NULL); +#endif + } else { + + if (g_ss_monitor->aoa_reset.rst_err_noti) { + g_ss_monitor->aoa_reset.event_state = RELEASE; + g_ss_monitor->aoa_reset.rst_err_noti = false; + schedule_delayed_work(&g_ss_monitor->aoa_reset.usb_reset_event_work, + msecs_to_jiffies(0)); + } + g_ss_monitor->aoa_reset.rst_err_cnt = 0; + g_ss_monitor->aoa_reset.acc_dev_status = 0; + +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + if (!ret) + store_usblog_notify(NOTIFY_USBSTATE, + (void *)"USB_STATE=VBUS:DIS:SUCCESS", NULL); + else + store_usblog_notify(NOTIFY_USBSTATE, + (void *)"USB_STATE=VBUS:DIS:FAIL", NULL); +#endif + } + } +} +EXPORT_SYMBOL(vbus_session_notify); + +void usb_reset_notify(struct usb_gadget *gadget) +{ + ktime_t current_time; + + if (!g_ss_monitor) + return; + +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + switch (gadget->speed) { + case USB_SPEED_SUPER: + store_usblog_notify(NOTIFY_USBSTATE, + (void *)"USB_STATE=RESET:SUPER", NULL); + break; + case USB_SPEED_HIGH: + store_usblog_notify(NOTIFY_USBSTATE, + (void *)"USB_STATE=RESET:HIGH", NULL); + break; + case USB_SPEED_FULL: + store_usblog_notify(NOTIFY_USBSTATE, + (void *)"USB_STATE=RESET:FULL", NULL); + break; + default: + break; + } +#endif + + if (g_ss_monitor->ss_monitor == NULL) + return; + + g_ss_monitor->ss_monitor->vbus_current = USB_CURRENT_UNCONFIGURED; + schedule_work(&g_ss_monitor->ss_monitor->set_vbus_current_work); + + if (g_ss_monitor->aoa_reset.acc_dev_status && (g_ss_monitor->aoa_reset.rst_err_noti == false)) { + current_time = ktime_to_ms(ktime_get_boottime()); + + if (g_ss_monitor->aoa_reset.rst_err_cnt == 0) { + if ((current_time - g_ss_monitor->aoa_reset.rst_time_before) < 1000) { + g_ss_monitor->aoa_reset.rst_err_cnt++; + g_ss_monitor->aoa_reset.rst_time_first = g_ss_monitor->aoa_reset.rst_time_before; + } + } else { + if ((current_time - g_ss_monitor->aoa_reset.rst_time_first) < 1000) + g_ss_monitor->aoa_reset.rst_err_cnt++; + else + g_ss_monitor->aoa_reset.rst_err_cnt = 0; + } + + if (g_ss_monitor->aoa_reset.rst_err_cnt >= ERR_RESET_CNT) { + g_ss_monitor->aoa_reset.event_state = NOTIFY; + schedule_delayed_work(&g_ss_monitor->aoa_reset.usb_reset_event_work, msecs_to_jiffies(0)); + g_ss_monitor->aoa_reset.rst_err_noti = true; + } + + pr_info("[USB] %s rst_err_cnt: %d, time_current: %lld, time_before: %lld\n", + __func__, g_ss_monitor->aoa_reset.rst_err_cnt, current_time, + g_ss_monitor->aoa_reset.rst_time_before); + + g_ss_monitor->aoa_reset.rst_time_before = current_time; + } + +} +EXPORT_SYMBOL(usb_reset_notify); + +static int ss_monitor_setup(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct f_ss_monitor *ss_monitor; + struct usb_composite_dev *cdev; + struct usb_request *req; + +#if defined(CONFIG_USB_NOTIFY_PROC_LOG) + char log_buf[30] = {0}; + char *usb_speed = NULL; +#endif + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + ss_monitor = func_to_ss_monitor(f); + + if (f->config != NULL && f->config->cdev != NULL) { + cdev = f->config->cdev; + req = cdev->req; + } else + goto unknown; + + if (ctrl->bRequestType == + (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE) + && ctrl->bRequest == USB_REQ_GET_DESCRIPTOR + && (w_value >> 8) == USB_DT_STRING + && (w_value & 0xFF) == MTPG_OS_STRING_ID) { + + pr_info("[USB] %s: MS OS String Descriptor\n", __func__); + } else if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR) { + /* Usb EP0 Vendor Requset */ + if (ctrl->bRequestType & USB_DIR_IN) { + pr_info("[USB] %s: vendor requset: %02x.%02x v%04x i%04x l%u\n", + __func__, ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } else { + /* samsung AVD operation: GUID for MTP */ + if (ctrl->bRequest == 0xA3) { + pr_info("[USB] %s: (mtp) RECEIVE PC GUID / line[%d]\n", + __func__, __LINE__); + if (w_length > MAX_GUID_SIZE) { + pr_info("usb: [%s] Invalid GUID size / line[%d]\n", + __func__, __LINE__); + goto unknown; + } + + value = w_length; + req->complete = mtp_complete_get_guid; + req->zero = 0; + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, + req, GFP_ATOMIC); + + if (value < 0) { + pr_err("[USB] %s: (mtp) Error usb_ep_queue / line[%d]\n", + __func__, __LINE__); + } + } else { + if (ctrl->bRequest == ACCESSORY_START) { + ss_monitor->aoa_start_cmd = 1; +#if defined(CONFIG_USB_NOTIFY_PROC_LOG) + if (!is_aoa_string_come(ss_monitor->accessory_string)) + store_usblog_notify(NOTIFY_USBMODE_EXTRA, + (void *)"AOA_STR_ERR", NULL); + else + store_usblog_notify(NOTIFY_USBSTATE, + (void *)"ACCESSORY=START", NULL); +#endif + } else if (ctrl->bRequest == ACCESSORY_SEND_STRING) { + ss_monitor->accessory_string |= 0x1 << w_index; + } + + pr_info("[USB] %s: vendor requset: %02x.%02x v%04x i%04x l%u\n", + __func__, ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + } + + } else if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + switch (ctrl->bRequest) { + case USB_REQ_GET_DESCRIPTOR: + if (ctrl->bRequestType != USB_DIR_IN) + goto unknown; +#if defined(CONFIG_USB_NOTIFY_PROC_LOG) + switch (w_value >> 8) { + case USB_DT_DEVICE: + if (w_length == USB_DT_DEVICE_SIZE) { + usb_speed = get_usb_speed(cdev->gadget->speed); + sprintf(log_buf, "USB_STATE=ENUM:GET:DES:%s", usb_speed); + pr_info("[USB] %s: GET_DES(%s)\n", __func__, usb_speed); + store_usblog_notify(NOTIFY_USBSTATE, + (void *)"USB_STATE=ENUM:GET:DES", NULL); +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + set_usb_enumeration_state(cdev->gadget->speed); +#endif + } + break; + case USB_DT_CONFIG: + pr_info("[USB] %s: GET_CONFIG (%d)\n", __func__, w_index); + break; + } +#endif + break; + case USB_REQ_SET_CONFIGURATION: +#if defined(CONFIG_USB_NOTIFY_PROC_LOG) + pr_info("[USB] %s: SET_CONFIG (%d)\n", __func__, w_value); + if (cdev->gadget->speed >= USB_SPEED_SUPER) + ss_monitor->vbus_current = USB_CURRENT_SUPER_SPEED; + else + ss_monitor->vbus_current = USB_CURRENT_HIGH_SPEED; + schedule_work(&ss_monitor->set_vbus_current_work); + store_usblog_notify(NOTIFY_USBSTATE, + (void *)"USB_STATE=ENUM:SET:CON", NULL); +#endif + if (ss_monitor->usb_function_info & GADGET_ACCESSORY) { + g_ss_monitor->aoa_reset.acc_online = true; + g_ss_monitor->aoa_reset.rst_err_cnt = 0; + } + break; + } + } +unknown: + return value; +} + +void make_suspend_current_event(void) +{ + struct usb_function f; + + pr_info("[USB] %s\n", __func__); + + if (!g_ss_monitor) { + pr_info("[USB] %s, g_ss_monitor is NULL\n", __func__); + return; + } + + if (g_ss_monitor->ss_monitor && g_ss_monitor->ss_monitor->is_bind) { + f = g_ss_monitor->ss_monitor->function; + if (f.config->cdev && f.config->cdev->suspended) { + pr_info("[USB] %s, cdev->suspended!\n", __func__); + g_ss_monitor->ss_monitor->vbus_current = USB_CURRENT_SUSPENDED; + schedule_work(&g_ss_monitor->ss_monitor->set_vbus_current_work); + } + } +} +EXPORT_SYMBOL(make_suspend_current_event); + +static void ss_monitor_suspend(struct usb_function *f) +{ + struct f_ss_monitor *ss_monitor; + + pr_info("[USB] %s: usb suspend enter\n", __func__); + ss_monitor = func_to_ss_monitor(f); + ss_monitor->vbus_current = USB_CURRENT_SUSPENDED; + schedule_work(&ss_monitor->set_vbus_current_work); +} + +static void ss_monitor_resume(struct usb_function *f) +{ + struct f_ss_monitor *ss_monitor; + struct usb_composite_dev *cdev = f->config->cdev; + + pr_info("[USB] %s: usb suspend exit\n", __func__); + ss_monitor = func_to_ss_monitor(f); + if (cdev->gadget->speed >= USB_SPEED_SUPER) + ss_monitor->vbus_current = USB_CURRENT_SUPER_SPEED; + else + ss_monitor->vbus_current = USB_CURRENT_HIGH_SPEED; + schedule_work(&ss_monitor->set_vbus_current_work); +} + +#if defined(CONFIG_USB_NOTIFY_PROC_LOG) +static void update_usb_gadet_function(char *f_name, struct f_ss_monitor *p_monitor) +{ + if (!strcmp(f_name, "mtp")) { + p_monitor->usb_function_info |= GADGET_MTP; + goto ret; + } + if (!strcmp(f_name, "acm")) { + p_monitor->usb_function_info |= GADGET_ACM; + goto ret; + } + if (!strcmp(f_name, "conn_gadget")) { + p_monitor->usb_function_info |= GADGET_CONN_GADGET; + goto ret; + } + + if (!strcmp(f_name, "Function FS Gadget") || !strcmp(f_name, "adb")) { + p_monitor->usb_function_info |= GADGET_ADB; + goto ret; + } + if (!strcmp(f_name, "rndis")) { + p_monitor->usb_function_info |= GADGET_RNDIS; + goto ret; + } + if (!strcmp(f_name, "accessory")) { + p_monitor->usb_function_info |= GADGET_ACCESSORY; + goto ret; + } + if (!strcmp(f_name, "gmidi function")) { + p_monitor->usb_function_info |= GADGET_MIDI; + goto ret; + } + if (!strcmp(f_name, "dm") || !strcmp(f_name, "diag") || !strcmp(f_name, "diag_mdm")) { + p_monitor->usb_function_info |= GADGET_DM; + goto ret; + } +ret: + return; +} + +static int copy_usb_mode(char *f_name, char *usb_mode) +{ + int length; + + length = strlen(f_name); + if (length > 3) { + length = 3; + strncat(usb_mode, f_name, 3); + } else { + strncat(usb_mode, f_name, length); + } + length += 1; + strcat(usb_mode, ","); + + return length; +} + +static int usb_configuration_name(struct usb_configuration *config, struct usb_function *f_ss) +{ + struct usb_function *f; + struct ss_monitor_instance *opts; + int length = 0; + char *f_name; + int name_len; + int use_ffs_num = 0; + struct f_ss_monitor *ss_monitor = func_to_ss_monitor(f_ss); + + opts = container_of(f_ss->fi, struct ss_monitor_instance, func_inst); + + if (!strcmp(opts->name, "mtp") || !strcmp(opts->name, "ptp") || !strcmp(opts->name, "diag1")) + use_ffs_num = 1; + else if (!strcmp(opts->name, "diag2")) + use_ffs_num = 2; + + list_for_each_entry(f, &config->functions, list) { + if (!strcmp(f->name, "ss_mon")) { + break; + } else if (!strcmp(f->name, "Function FS Gadget")) { + if (use_ffs_num) { + use_ffs_num--; + if (use_ffs_num < 0) + use_ffs_num = 0; + name_len = strlen(opts->name); + f_name = kstrndup(opts->name, name_len, GFP_KERNEL); + if (!f_name) + return -ENOMEM; + } else { + name_len = 3; + f_name = kmemdup_nul("adb", name_len, GFP_KERNEL); + if (!f_name) + return -ENOMEM; + } + } else { + if (!strcmp(f->name, "mtp") || !strcmp(f->name, "ptp") || !strcmp(f->name, "diag")) + use_ffs_num = 0; + name_len = strlen(f->name); + if (name_len > MAX_INST_NAME_LEN) + return -ENAMETOOLONG; + + f_name = kstrndup(f->name, name_len, GFP_KERNEL); + if (!f_name) + return -ENOMEM; + } + update_usb_gadet_function(f_name, ss_monitor); + if (name_len > 3) + name_len = 3; + if (length + name_len > sizeof(ss_monitor->usb_mode)) { + pr_info("[USB] %s: overflow usb mode buffer\n", __func__); + break; + } + length += copy_usb_mode(f_name, ss_monitor->usb_mode); + kfree(f_name); + } + + if (length) + ss_monitor->usb_mode[length-1] = 0; + else + strncat(ss_monitor->usb_mode, "func:empty", 11); + + store_usblog_notify(NOTIFY_USBMODE_EXTRA, (void *)ss_monitor->usb_mode, NULL); + pr_info("[USB] %s: usb_mode: %s\n", __func__, ss_monitor->usb_mode); + + return 0; +} +#endif + +static int +ss_monitor_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_ss_monitor *ss_monitor = func_to_ss_monitor(f); + struct ss_monitor_instance *opts; + int rc = -1; + + opts = container_of(f->fi, struct ss_monitor_instance, func_inst); + + /* copy descriptors, and track endpoint copies */ + f->fs_descriptors = usb_copy_descriptors(log_fs_function); + + /* support all relevant hardware speeds... we expect that when + * hardware is dual speed, all bulk-capable endpoints work at + * both speeds + */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + /* copy descriptors, and track endpoint copies */ + f->hs_descriptors = usb_copy_descriptors(log_hs_function); + } + + if (gadget_is_superspeed(c->cdev->gadget)) { + /* copy descriptors, and track endpoint copies */ + f->ss_descriptors = usb_copy_descriptors(log_ss_function); + if (!f->ss_descriptors) + goto fail; + + /* copy descriptors, and track endpoint copies for SSP */ + f->ssp_descriptors = usb_copy_descriptors(log_ss_function); + if (!f->ssp_descriptors) + goto fail; + } + + /* save usb mode information */ +#if defined(CONFIG_USB_NOTIFY_PROC_LOG) + usb_configuration_name(c, f); + store_usblog_notify(NOTIFY_USBSTATE, + (void *)"USB_STATE=PULLUP:EN:SUCCESS", NULL); +#endif + INIT_WORK(&ss_monitor->set_vbus_current_work, set_vbus_current_work); + +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + set_usb_enable_state(); +#endif + + f->setup = ss_monitor_setup; + ss_monitor->is_bind = 1; + pr_info("[USB] %s: ss_mon.%s bind\n", __func__, opts->name); + return 0; + +fail: +#if defined(CONFIG_USB_NOTIFY_PROC_LOG) + store_usblog_notify(NOTIFY_USBSTATE, + (void *)"USB_STATE=PULLUP:EN:FAIL", NULL); +#endif + pr_err("[USB] %s: ss_mon.%s bind fail\n", __func__, opts->name); + return rc; + +} + +static void +ss_monitor_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_ss_monitor *ss_monitor = func_to_ss_monitor(f); + struct ss_monitor_instance *opts; + + opts = container_of(f->fi, struct ss_monitor_instance, func_inst); + f->setup = NULL; +#if defined(CONFIG_USB_NOTIFY_PROC_LOG) + store_usblog_notify(NOTIFY_USBSTATE, + (void *)"USB_STATE=PULLUP:DIS", NULL); +#endif + opts->aoa_reset.rst_err_cnt = 0; + ss_monitor->usb_function_info = 0; + ss_monitor->is_bind = 0; + + cancel_delayed_work_sync(&opts->aoa_reset.usb_reset_event_work); + flush_work(&ss_monitor->set_vbus_current_work); + + pr_info("[USB] %s: ss_mon.%s unbind\n", __func__, opts->name); +} + +static struct ss_monitor_instance *to_ss_monitor_instance(struct config_item *item) +{ + return container_of(to_config_group(item), struct ss_monitor_instance, + func_inst.group); +} + +static void ss_monitor_attr_release(struct config_item *item) +{ + struct ss_monitor_instance *fi_ss_monitor = to_ss_monitor_instance(item); + + usb_put_function_instance(&fi_ss_monitor->func_inst); +} + +static struct configfs_item_operations ss_monitor_item_ops = { + .release = ss_monitor_attr_release, +}; + +static struct config_item_type ss_monitor_func_type = { + .ct_item_ops = &ss_monitor_item_ops, + .ct_owner = THIS_MODULE, +}; + +static int ss_monitor_set_inst_name(struct usb_function_instance *fi, const char *name) +{ + struct ss_monitor_instance *fi_ss_monitor; + char *ptr; + int name_len; + + name_len = strlen(name) + 1; + if (name_len > MAX_INST_NAME_LEN) + return -ENAMETOOLONG; + + ptr = kstrndup(name, name_len, GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + fi_ss_monitor = to_fi_ss_monitor(fi); + fi_ss_monitor->name = ptr; + + return 0; +} + +static void ss_monitor_free_inst(struct usb_function_instance *fi) +{ + struct ss_monitor_instance *fi_ss_monitor; + + fi_ss_monitor = to_fi_ss_monitor(fi); + if (the_mtpg) { + device_remove_file(mtpg_device.this_device, &dev_attr_guid); + misc_deregister(&mtpg_device); + kfree(the_mtpg); + the_mtpg = NULL; + } + kfree(fi_ss_monitor->name); + kfree(fi_ss_monitor); +} + +static struct usb_function_instance *ss_mon_alloc_inst(void) +{ + struct ss_monitor_instance *fi_ss_monitor; + int rc = -ENODEV; + + /* scenario for MTP */ + if (the_mtpg == NULL) { + rc = misc_register(&mtpg_device); + if (rc != 0) { + pr_err("[USB] %s misc_register of mtpg Failed(%d)\n", __func__, rc); + } else { + rc = device_create_file(mtpg_device.this_device, &dev_attr_guid); + if (rc) { + pr_err("[USB] %s failed to create guid attr\n", __func__); + misc_deregister(&mtpg_device); + } else { + the_mtpg = kzalloc(sizeof(*the_mtpg), GFP_KERNEL); + if (!the_mtpg) { + device_remove_file(mtpg_device.this_device, &dev_attr_guid); + misc_deregister(&mtpg_device); + } + } + } + } + + fi_ss_monitor = kzalloc(sizeof(*fi_ss_monitor), GFP_KERNEL); + if (!fi_ss_monitor) + goto err_misc_deregister; + + fi_ss_monitor->func_inst.set_inst_name = ss_monitor_set_inst_name; + fi_ss_monitor->func_inst.free_func_inst = ss_monitor_free_inst; + + config_group_init_type_name(&fi_ss_monitor->func_inst.group, + "", &ss_monitor_func_type); + + INIT_DELAYED_WORK(&fi_ss_monitor->aoa_reset.usb_reset_event_work, ss_mon_usb_event_work); + g_ss_monitor = fi_ss_monitor; + + return &fi_ss_monitor->func_inst; + +err_misc_deregister: + if (the_mtpg) { + device_remove_file(mtpg_device.this_device, &dev_attr_guid); + misc_deregister(&mtpg_device); + kfree(the_mtpg); + the_mtpg = NULL; + } + pr_err("[USB] %s: ss_mon_alloc_inst fail\n", __func__); + return ERR_PTR(-ENOMEM); + +} + +static void ss_monitor_free(struct usb_function *f) +{ + struct f_ss_monitor *ss_monitor = func_to_ss_monitor(f); + struct ss_monitor_instance *opts = container_of(f->fi, struct ss_monitor_instance, func_inst); + + opts->func_inst.f = NULL; + g_ss_monitor->ss_monitor = NULL; + kfree(ss_monitor); +} + +static struct usb_function *ss_mon_alloc(struct usb_function_instance *fi) +{ + struct ss_monitor_instance *fi_ss_monitor = to_fi_ss_monitor(fi); + struct f_ss_monitor *ss_monitor; + + /* allocate and initialize one new instance */ + ss_monitor = kzalloc(sizeof(*ss_monitor), GFP_KERNEL); + if (!ss_monitor) + return ERR_PTR(-ENOMEM); + + ss_monitor->function.name = "ss_mon"; + ss_monitor->function.bind = ss_monitor_bind; + ss_monitor->function.unbind = ss_monitor_unbind; + ss_monitor->function.set_alt = ss_monitor_set_alt; + ss_monitor->function.disable = ss_monitor_disable; + ss_monitor->function.free_func = ss_monitor_free; + // ss_monitor->function.setup = ss_monitor_setup; + ss_monitor->function.suspend = ss_monitor_suspend; + ss_monitor->function.resume = ss_monitor_resume; + fi_ss_monitor->ss_monitor = ss_monitor; + fi->f = &ss_monitor->function; + ss_monitor->func_inst = fi_ss_monitor; + g_ss_monitor->ss_monitor = ss_monitor; + + return &ss_monitor->function; +} + +DECLARE_USB_FUNCTION_INIT(ss_mon, ss_mon_alloc_inst, ss_mon_alloc); +MODULE_AUTHOR("Samsung USB Team"); +MODULE_DESCRIPTION("usb device monitor gadget"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/lvstest.c b/drivers/usb/misc/lvstest.c index 6afc03ae50d6..340807fec9a0 100644 --- a/drivers/usb/misc/lvstest.c +++ b/drivers/usb/misc/lvstest.c @@ -17,6 +17,9 @@ #include #include #include +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) +#include +#endif struct lvs_rh { /* root hub interface */ @@ -35,14 +38,86 @@ struct lvs_rh { struct work_struct rh_work; /* RH port status */ struct usb_port_status port_status; + int test_mode; + int test_stat; +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + struct lvs_dev lvs_d; + struct if_cb_manager *man; +#endif }; +enum lvs_status { + STAT_DISC = 0, + STAT_CON, + STAT_RESET +}; + +static int lvs_rh_set_port_feature(struct usb_device *hdev, + int port1, int feature) +{ + return usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), + USB_REQ_SET_FEATURE, USB_RT_PORT, feature, port1, + NULL, 0, 1000); +} + +static int send_hot_reset(struct usb_interface *intf) +{ + struct usb_device *hdev = interface_to_usbdev(intf); + struct lvs_rh *lvs = usb_get_intfdata(intf); + int ret; + + pr_info("lvs:%s\n", __func__); + ret = lvs_rh_set_port_feature(hdev, lvs->portnum, + USB_PORT_FEAT_RESET); + if (ret < 0) + pr_err("lvs:can't issue hot reset %d\n", ret); + return ret; +} + +static void prepare_speed_info(struct usb_device *udev, int speed) +{ + switch (speed) { + case USB_SPEED_SUPER: + pr_info("lvs:%s:super speed\n", __func__); + udev->speed = USB_SPEED_SUPER; + udev->ep0.desc.wMaxPacketSize = cpu_to_le16(512); + break; + case USB_SPEED_HIGH: + pr_info("lvs:%s: high speed\n", __func__); + udev->speed = USB_SPEED_HIGH; + udev->ep0.desc.wMaxPacketSize = cpu_to_le16(64); + break; + case USB_SPEED_FULL: + pr_info("lvs:%s:full speed\n", __func__); + udev->speed = USB_SPEED_FULL; + udev->ep0.desc.wMaxPacketSize = cpu_to_le16(64); + break; + case USB_SPEED_LOW: + pr_info("lvs:%s:low speed\n", __func__); + udev->speed = USB_SPEED_LOW; + udev->ep0.desc.wMaxPacketSize = cpu_to_le16(8); + break; + default: + pr_info("lvs:%s:default super speed\n", __func__); + udev->speed = USB_SPEED_SUPER; + udev->ep0.desc.wMaxPacketSize = cpu_to_le16(512); + break; + } +} + static struct usb_device *create_lvs_device(struct usb_interface *intf) { struct usb_device *udev, *hdev; struct usb_hcd *hcd; struct lvs_rh *lvs = usb_get_intfdata(intf); + struct usb_port_status *port_status = &lvs->port_status; + u16 portchange; + u16 portstatus; + int ret; + pr_info("%s+\n", __func__); + if (lvs->test_mode) + pr_info("%s test_mode:%d\n", __func__, lvs->test_mode); if (!lvs->present) { dev_err(&intf->dev, "No LVS device is present\n"); return NULL; @@ -56,18 +131,51 @@ static struct usb_device *create_lvs_device(struct usb_interface *intf) dev_err(&intf->dev, "Could not allocate lvs udev\n"); return NULL; } - udev->speed = USB_SPEED_SUPER; - udev->ep0.desc.wMaxPacketSize = cpu_to_le16(512); - usb_set_device_state(udev, USB_STATE_DEFAULT); + prepare_speed_info(udev, lvs->test_mode); + + if (lvs->test_mode) { + if (lvs->test_stat == STAT_CON) { + ret = send_hot_reset(intf); + if (ret < 0) + return NULL; + lvs->test_stat = STAT_RESET; + msleep(50); + ret = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0), + USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, lvs->portnum, + port_status, sizeof(*port_status), 1000); + if (ret >= 4) { + portchange = le16_to_cpu(port_status->wPortChange); + portstatus = le16_to_cpu(port_status->wPortStatus); + dev_info(&intf->dev, "%s, after hotreset portchange=0x%x portstatus=0x%x\n", + __func__, portchange, portstatus); + + if (lvs->test_mode == USB_SPEED_SUPER) + udev->speed = USB_SPEED_SUPER; + else if (portstatus & USB_PORT_STAT_HIGH_SPEED) + udev->speed = USB_SPEED_HIGH; + else if (portstatus & USB_PORT_STAT_LOW_SPEED) + udev->speed = USB_SPEED_LOW; + else + udev->speed = USB_SPEED_FULL; + prepare_speed_info(udev, udev->speed); + } else { + dev_err(&intf->dev, "after hotreset get status error.\n"); + } + } + } + /* add 100 to notify that it is called from the lvs driver. */ + usb_set_device_state(udev, USB_STATE_DEFAULT + 100); if (hcd->driver->enable_device) { if (hcd->driver->enable_device(hcd, udev) < 0) { dev_err(&intf->dev, "Failed to enable\n"); + if (hcd->driver->free_dev) + hcd->driver->free_dev(hcd, udev); usb_put_dev(udev); return NULL; } } - + pr_info("%s-\n", __func__); return udev; } @@ -90,14 +198,6 @@ static int lvs_rh_clear_port_feature(struct usb_device *hdev, NULL, 0, 1000); } -static int lvs_rh_set_port_feature(struct usb_device *hdev, - int port1, int feature) -{ - return usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), - USB_REQ_SET_FEATURE, USB_RT_PORT, feature, port1, - NULL, 0, 1000); -} - static ssize_t u3_entry_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { @@ -177,6 +277,7 @@ static ssize_t hot_reset_store(struct device *dev, int port; int ret; + pr_info("lvs:%s\n", __func__); if (kstrtoint(buf, 0, &port) || port < 1 || port > 255) port = lvs->portnum; @@ -200,6 +301,7 @@ static ssize_t warm_reset_store(struct device *dev, int port; int ret; + pr_info("lvs:%s\n", __func__); if (kstrtoint(buf, 0, &port) || port < 1 || port > 255) port = lvs->portnum; @@ -280,6 +382,7 @@ static ssize_t get_dev_desc_store(struct device *dev, struct usb_device_descriptor *descriptor; int ret, port; + pr_info("lvs: %s\n", __func__); if (!kstrtoint(buf, 0, &port) && port >= 1 && port <= 255) { lvs->portnum = port; lvs->present = true; @@ -300,8 +403,17 @@ static ssize_t get_dev_desc_store(struct device *dev, USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, USB_DT_DEVICE << 8, 0, descriptor, sizeof(*descriptor), USB_CTRL_GET_TIMEOUT); - if (ret < 0) + if (ret < 0) { dev_err(dev, "can't read device descriptor %d\n", ret); + if (lvs->test_mode) + lvs->test_stat = STAT_CON; + } else { +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + usbpd_wait_entermode(lvs->man, 0); +#endif + dev_info(dev, "send device descriptor success\n"); + } + destroy_lvs_device(udev); free_desc: @@ -323,6 +435,7 @@ static ssize_t enable_compliance_store(struct device *dev, int port; int ret; + pr_info("lvs:%s\n", __func__); if (kstrtoint(buf, 0, &port) || port < 1 || port > 255) port = lvs->portnum; @@ -338,6 +451,19 @@ static ssize_t enable_compliance_store(struct device *dev, } static DEVICE_ATTR_WO(enable_compliance); +static ssize_t test_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct lvs_rh *lvs = usb_get_intfdata(intf); + int ret; + + ret = kstrtoint(buf, 0, &lvs->test_mode); + pr_info("lvs: %s: %d\n", __func__, lvs->test_mode); + return count; +} +static DEVICE_ATTR_WO(test_mode); + static struct attribute *lvs_attrs[] = { &dev_attr_get_dev_desc.attr, &dev_attr_u1_timeout.attr, @@ -347,6 +473,7 @@ static struct attribute *lvs_attrs[] = { &dev_attr_u3_entry.attr, &dev_attr_u3_exit.attr, &dev_attr_enable_compliance.attr, + &dev_attr_test_mode.attr, NULL }; ATTRIBUTE_GROUPS(lvs); @@ -361,7 +488,9 @@ static void lvs_rh_work(struct work_struct *work) struct usb_port_status *port_status = &lvs->port_status; int i, ret = 0; u16 portchange; + u16 portstatus; + pr_info("%s\n", __func__); /* Examine each root port */ for (i = 1; i <= descriptor->bNbrPorts; i++) { ret = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0), @@ -371,6 +500,8 @@ static void lvs_rh_work(struct work_struct *work) continue; portchange = le16_to_cpu(port_status->wPortChange); + portstatus = le16_to_cpu(port_status->wPortStatus); + pr_info("%s, portchange=0x%x portstatus=0x%x\n", __func__, portchange, portstatus); if (portchange & USB_PORT_STAT_C_LINK_STATE) lvs_rh_clear_port_feature(hdev, i, @@ -392,16 +523,33 @@ static void lvs_rh_work(struct work_struct *work) USB_PORT_STAT_CONNECTION) { lvs->present = true; lvs->portnum = i; + lvs->test_stat = STAT_CON; + pr_info("%s, portnum=%d hcd->usb_phy=%pK\n", __func__, i, hcd->usb_phy); if (hcd->usb_phy) usb_phy_notify_connect(hcd->usb_phy, USB_SPEED_SUPER); } else { lvs->present = false; + lvs->portnum = 0; + lvs->test_stat = STAT_DISC; if (hcd->usb_phy) usb_phy_notify_disconnect(hcd->usb_phy, USB_SPEED_SUPER); } break; + } else { + if (!lvs->present) { + if (le16_to_cpu(port_status->wPortStatus) & + USB_PORT_STAT_CONNECTION) { + lvs->present = true; + lvs->portnum = i; + lvs->test_stat = STAT_CON; + pr_info("%s, portnum=%d hcd->usb_phy=%pK\n", __func__, i, hcd->usb_phy); + if (hcd->usb_phy) + usb_phy_notify_connect(hcd->usb_phy, + USB_SPEED_SUPER); + } + } } } @@ -414,6 +562,7 @@ static void lvs_rh_irq(struct urb *urb) { struct lvs_rh *lvs = urb->context; + pr_info("%s\n", __func__); schedule_work(&lvs->rh_work); } @@ -427,6 +576,7 @@ static int lvs_rh_probe(struct usb_interface *intf, unsigned int pipe; int ret, maxp; + pr_info("%s\n", __func__); hdev = interface_to_usbdev(intf); desc = intf->cur_altsetting; @@ -434,11 +584,13 @@ static int lvs_rh_probe(struct usb_interface *intf, if (ret) return ret; +#if 0 /* valid only for SS root hub */ if (hdev->descriptor.bDeviceProtocol != USB_HUB_PR_SS || hdev->parent) { dev_err(&intf->dev, "Bind LVS driver with SS root Hub only\n"); return -EINVAL; } +#endif lvs = devm_kzalloc(&intf->dev, sizeof(*lvs), GFP_KERNEL); if (!lvs) @@ -474,6 +626,12 @@ static int lvs_rh_probe(struct usb_interface *intf, dev_err(&intf->dev, "couldn't submit lvs urb %d\n", ret); goto free_urb; } +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + lvs->lvs_d.ops = NULL; + lvs->lvs_d.data = (void *)lvs; + lvs->man = register_lvs(&lvs->lvs_d); + usbpd_wait_entermode(lvs->man, 1); +#endif return ret; @@ -486,6 +644,11 @@ static void lvs_rh_disconnect(struct usb_interface *intf) { struct lvs_rh *lvs = usb_get_intfdata(intf); + pr_info("%s\n", __func__); +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + usbpd_wait_entermode(lvs->man, 0); + register_lvs(NULL); +#endif usb_poison_urb(lvs->urb); /* used in scheduled work */ flush_work(&lvs->rh_work); usb_free_urb(lvs->urb); diff --git a/drivers/usb/notify/Kconfig b/drivers/usb/notify/Kconfig new file mode 100755 index 000000000000..3a602bb7d41e --- /dev/null +++ b/drivers/usb/notify/Kconfig @@ -0,0 +1,187 @@ +# +# USB notify configuration +# + +comment "USB Notify features" + +config USB_HOST_NOTIFY + bool "USB Host notify Driver" + depends on USB_NOTIFY_LAYER + help + Android framework needs uevents for usb host operation. + Host notify Driver serves uevent format + that is used by usb host or otg. + The uevent include host connect, disconnect, source, sink. + +config USB_NOTIFY_LAYER + tristate "USB notify layer" + depends on USB + help + Added usb notify layer on gadget,host,otg drivers. + The usb notify layer controls otg mode and vbus draw + and vbus detect irq. + It is made by samsung. + +config USB_NOTIFIER + tristate "USB notifier" + depends on USB_NOTIFY_LAYER + help + Added platform driver to call usb notify. + The usb_notifier.c can be fixed for each models. + It is not common code. + It may be different for each ap vendor. + +config USB_DEBUG_DETAILED_LOG + bool "USB detailed log for debugging" + depends on USB + help + Add detailed log for debugging. + It is made by samsung. + It is feature for usb host class drivers. + .. + +config USB_STORAGE_DETECT + bool "USB storage detect function" + depends on USB && SCSI + depends on USB_STORAGE + help + This feature enables to detect inserting or removing + sd card in sd reader device. + This must to be defined in /usb/storage/Kconfig + directory originally. But this feature is defined + in this Kconfig to gather samsung feature about usb host. + +config USB_HMT_SAMSUNG_INPUT + bool "USB HMT inputs for samsung hmt devices" + depends on HID + help + Some samsung smart phones support gear vr. + The samsung gear vr need some special hid input codes. + This feature enables special hid input codes. + .. + +config USB_EXTERNAL_NOTIFY + bool "USB external notify" + depends on USB_NOTIFY_LAYER + help + Added usb external notify. + The usb external notify broadcast special situation + to muic and charger. + And broadcasting to out of usb notify layer. + +config USB_NOTIFY_PROC_LOG + bool "USB proc log" + depends on USB_NOTIFY_LAYER + depends on USB_HOST_NOTIFY + help + Added proc usblog. + The USB STATE, The USB MODE, Cable event + are saved in usblog heap. + It is remained in dumpdate. + +config USB_HOST_SAMSUNG_FEATURE + bool "USB Host Samsung Feature" + depends on USB + help + USB Host Samsung Feature. + It is different from LSI BSP code. + If samsung engineer changes kernel code, + use this feature, + +config USB_HW_PARAM + bool "USB H/W Parameter" + depends on USB_NOTIFY_LAYER + help + Added USB H/W Parameter for DQA Agent. + You need more works for USB PDIC driver. + It is made by samsung. + .. + +config USB_INTERFACE_LPM_LIST + bool "USB interface class lpm Feature" + depends on USB + help + The device needs to support L1 for audio class devices + But L1 mode has some issues. + So we will apply L1 mode to the device that has specific interface classes + Now, It is only enabled for audio class. + +config USB_CCIC_NOTIFIER_USING_QC + bool "USB CCIC Notifier using QC PMIC" + depends on USB_NOTIFY_LAYER + help + When USB type C model uses QC PMIC without IFPMIC, + add to get notification from QC PMIC. + .. + .. + +config USB_AUDIO_ENHANCED_DETECT_TIME + bool "USB audio enhanced detect time" + depends on USB_NOTIFY_LAYER + default y + help + Added uevent for usb audio device(bundle) + uevent includes path, card number, vid, pid + The usb framework gets this uevent. + It is made by samsung. + +config USB_HOST_CERTI + bool "USB Embedded host certification" + depends on USB_NOTIFY_LAYER + depends on !SEC_FACTORY + help + Added uevent for usb embedded host certification + uevent includes information of notification + The usb framework gets this uevent. + It is made by samsung. + +config USB_AUDIO_POWER_SAVING + bool "Use function saving power with VBUS provided to USB audio" + default n + help + If you want to use functionality to save power + for specific USB earphone, + enable this feature. + It is made by samsung. + +config USB_USING_ADVANCED_USBLOG + bool "Use advanced usb log logic" + default n + help + If you want to use advanced usb log, enable this feature. + you can use printk_usb saved specific buffer. + and can use store_tcpc_name + This is new feature by usb notify layer v4.0 + +config USB_LPM_CHARGING_SYNC + bool "LPM charging type sync" + depends on USB_NOTIFY_LAYER + help + There is a requirement that LPM animation confirm + the final charging method and show it only once. + It is a function that syncs with the driver stage through sysfs + to let the LPM know the final charging type. + +config DISABLE_LOCKSCREEN_USB_RESTRICTION + bool "disable usb restriction during lockscreen" + depends on USB_NOTIFY_LAYER + help + Restricting the usb function on the lock screen is a concept applied for usb security + it has been applied since v os (oneui 7.0) or higher + This feature is to avoid applying the concept. + Enable this feature to enable usb communication on the lock screen. + +# +# USB notifier configuration +# + +comment "USB Notifier features" + +config USB_NOTIFIER + tristate "USB notifier" + depends on USB_NOTIFY_LAYER + help + Added platform driver to call usb notify. + The usb_notifier.c can be fixed for each models. + It is not common code. + It may be different for each ap vendor. diff --git a/drivers/usb/notify/Makefile b/drivers/usb/notify/Makefile new file mode 100755 index 000000000000..3b61c38aac5c --- /dev/null +++ b/drivers/usb/notify/Makefile @@ -0,0 +1,12 @@ + +# usb notify driver +obj-$(CONFIG_USB_NOTIFY_LAYER) += usb_notify_layer.o +usb_notify_layer-y := usb_notify.o usb_notify_sysfs.o dock_notify.o +usb_notify_layer-$(CONFIG_USB_HOST_NOTIFY) += host_notify_class.o +usb_notify_layer-$(CONFIG_USB_EXTERNAL_NOTIFY) += external_notify.o +usb_notify_layer-$(CONFIG_USB_NOTIFY_PROC_LOG) += usblog_proc_notify.o +obj-$(CONFIG_USB_NOTIFIER) += usb_notifier.o + + +# usb notifier +obj-$(CONFIG_USB_NOTIFIER) += usb_notifier.o diff --git a/drivers/usb/notify/dock_notify.c b/drivers/usb/notify/dock_notify.c new file mode 100755 index 000000000000..62680a1efbfe --- /dev/null +++ b/drivers/usb/notify/dock_notify.c @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2015-2023 Samsung Electronics Co. Ltd. + * + * 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. + */ + + /* usb notify layer v4.0 */ + +#define pr_fmt(fmt) "usb_notify: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#define SMARTDOCK_INDEX 1 +#define MMDOCK_INDEX 2 + +struct dev_table { + struct usb_device_id dev; + int index; +}; + +static struct dev_table enable_notify_hub_table[] = { + { .dev = { USB_DEVICE(0x0424, 0x2514), }, + .index = SMARTDOCK_INDEX, + }, /* SMART DOCK HUB 1 */ + { .dev = { USB_DEVICE(0x1a40, 0x0101), }, + .index = SMARTDOCK_INDEX, + }, /* SMART DOCK HUB 2 */ + { .dev = { USB_DEVICE(0x0424, 0x9512), }, + .index = MMDOCK_INDEX, + }, /* SMSC USB LAN HUB 9512 */ + {} +}; + +static struct dev_table essential_device_table[] = { + { .dev = { USB_DEVICE(0x08bb, 0x2704), }, + .index = SMARTDOCK_INDEX, + }, /* TI USB Audio DAC 1 */ + { .dev = { USB_DEVICE(0x08bb, 0x27c4), }, + .index = SMARTDOCK_INDEX, + }, /* TI USB Audio DAC 2 */ + { .dev = { USB_DEVICE(0x0424, 0xec00), }, + .index = MMDOCK_INDEX, + }, /* SMSC LAN Driver */ + {} +}; + +static struct dev_table unsupport_device_table[] = { + { .dev = { USB_DEVICE(0x1a0a, 0x0201), }, + }, /* The device for usb certification */ + {} +}; + +static int check_essential_device(struct usb_device *dev, int index) +{ + struct dev_table *id; + int ret = 0; + + /* check VID, PID */ + for (id = essential_device_table; id->dev.match_flags; id++) { + if ((id->dev.match_flags & USB_DEVICE_ID_MATCH_VENDOR) && + (id->dev.match_flags & USB_DEVICE_ID_MATCH_PRODUCT) && + id->dev.idVendor == le16_to_cpu(dev->descriptor.idVendor) && + id->dev.idProduct == le16_to_cpu(dev->descriptor.idProduct) && + id->index == index) { + ret = 1; + break; + } + } + return ret; +} + +static int check_gamepad_device(struct usb_device *dev) +{ + int ret = 0; + + unl_info("%s : product=%s\n", __func__, dev->product); + + if (!dev->product) + return ret; + + if (!strncmp(dev->product, "Gamepad for SAMSUNG", 19)) + ret = 1; + + return ret; +} + +static int check_lanhub_device(struct usb_device *dev) +{ + int ret = 0; + + unl_info("%s : product=%s\n", __func__, dev->product); + + if (!dev->product) + return ret; + + if (!strncmp(dev->product, "LAN9512", 8)) + ret = 1; + + return ret; +} + +static int is_notify_hub(struct usb_device *dev) +{ + struct dev_table *id; + struct usb_device *hdev; + int ret = 0; + + hdev = dev->parent; + if (!hdev) + goto skip; + /* check VID, PID */ + for (id = enable_notify_hub_table; id->dev.match_flags; id++) { + if ((id->dev.match_flags & USB_DEVICE_ID_MATCH_VENDOR) && + (id->dev.match_flags & USB_DEVICE_ID_MATCH_PRODUCT) && + id->dev.idVendor == le16_to_cpu(hdev->descriptor.idVendor) && + id->dev.idProduct == le16_to_cpu(hdev->descriptor.idProduct)) { + ret = (hdev->parent && + (hdev->parent == dev->bus->root_hub)) ? id->index : 0; + break; + } + } +skip: + return ret; +} + +static int call_battery_notify(struct usb_device *dev, bool on) +{ + struct usb_device *hdev; + struct usb_device *udev; + struct otg_notify *o_notify = get_otg_notify(); + int index = 0; + int count = 0; + int port; + + index = is_notify_hub(dev); + if (!index) + goto skip; + if (!check_essential_device(dev, index)) + goto skip; + + hdev = dev->parent; + if (!hdev) + goto skip; + + usb_hub_for_each_child(hdev, port, udev) { + if (check_essential_device(udev, index)) { + if (!on && (udev == dev)) + continue; + else + count++; + } + } + + unl_info("%s : VID : 0x%x, PID : 0x%x, on=%d, count=%d\n", __func__, + dev->descriptor.idVendor, dev->descriptor.idProduct, + on, count); + if (on) { + if (count == 1) { + if (index == SMARTDOCK_INDEX) + send_otg_notify(o_notify, + NOTIFY_EVENT_SMTD_EXT_CURRENT, 1); + else if (index == MMDOCK_INDEX) + send_otg_notify(o_notify, + NOTIFY_EVENT_MMD_EXT_CURRENT, 1); + } + } else { + if (!count) { + if (index == SMARTDOCK_INDEX) + send_otg_notify(o_notify, + NOTIFY_EVENT_SMTD_EXT_CURRENT, 0); + else if (index == MMDOCK_INDEX) + send_otg_notify(o_notify, + NOTIFY_EVENT_MMD_EXT_CURRENT, 0); + } + } +skip: + return 0; +} + +static void seek_usb_interface(struct usb_device *dev) +{ + struct usb_interface *intf; + int i; + + if (!dev) { + unl_err("%s no dev\n", __func__); + goto done; + } + + if (!dev->actconfig) { + unl_info("%s no set config\n", __func__); + goto done; + } + + for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) { + intf = dev->actconfig->interface[i]; + /* You can use this function for various purposes */ + store_usblog_notify(NOTIFY_PORT_CLASS, + (void *)&dev->descriptor.bDeviceClass, + (void *)&intf->cur_altsetting->desc.bInterfaceClass); + } +done: + return; +} + +static void disconnect_usb_driver(struct usb_device *dev) +{ + struct usb_interface *intf = NULL; + struct usb_driver *driver = NULL; + int i; + + if (!dev) { + pr_err("%s no dev\n", __func__); + goto done; + } + + if (!dev->actconfig) { + pr_err("%s no set config\n", __func__); + goto done; + } + + for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) { + intf = dev->actconfig->interface[i]; + if (intf->dev.driver) { + driver = to_usb_driver(intf->dev.driver); + usb_driver_release_interface(driver, intf); + } + } +done: + return; +} + +static int call_device_notify(struct usb_device *dev, int connect) +{ + struct otg_notify *o_notify = get_otg_notify(); + + if (dev->bus->root_hub != dev) { + if (connect) { + unl_info("%s device\n", __func__); + send_otg_notify(o_notify, + NOTIFY_EVENT_DEVICE_CONNECT, 1); + + if (check_gamepad_device(dev)) + send_otg_notify(o_notify, + NOTIFY_EVENT_GAMEPAD_CONNECT, 1); + else if (check_lanhub_device(dev)) + send_otg_notify(o_notify, + NOTIFY_EVENT_LANHUB_CONNECT, 1); + else + ; + store_usblog_notify(NOTIFY_PORT_CONNECT, + (void *)&dev->descriptor.idVendor, + (void *)&dev->descriptor.idProduct); + + seek_usb_interface(dev); + + if (!usb_check_whitelist_for_mdm(dev)) { + unl_info("This device will be disabled.\n"); + disconnect_usb_driver(dev); + usb_set_device_state(dev, USB_STATE_NOTATTACHED); + goto done; + } + + switch (usb_check_whitelist_enable_state()) { + case NOTIFY_MDM_NONE: + /* whitelist_for_serial OFF, whitelist_for_id OFF */ + break; + case NOTIFY_MDM_SERIAL: + /* whitelist_for_serial ON, whitelist_for_id OFF */ + if (!usb_check_whitelist_for_serial(dev)) { + unl_info("This device will be disabled.\n"); + disconnect_usb_driver(dev); + usb_set_device_state(dev, USB_STATE_NOTATTACHED); + } + break; + case NOTIFY_MDM_ID: + /* whitelist_for_serial OFF, whitelist_for_id ON */ + if (!usb_check_whitelist_for_id(dev)) { + unl_info("This device will be disabled.\n"); + disconnect_usb_driver(dev); + usb_set_device_state(dev, USB_STATE_NOTATTACHED); + } + break; + case NOTIFY_MDM_ID_AND_SERIAL: + /* whitelist_for_serial ON, whitelist_for_id ON */ + if (!usb_check_whitelist_for_id(dev) && + !usb_check_whitelist_for_serial(dev)) { + unl_info("This device will be disabled.\n"); + disconnect_usb_driver(dev); + usb_set_device_state(dev, USB_STATE_NOTATTACHED); + } + break; + default: + break; + } +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + /* normal hub is not blocked, only allow in vpid list */ + if (!is_usbhub(dev)) { + if (!usb_check_allowlist_for_lockscreen_enabled_id(dev)) { + unl_info("This device will be disabled.\n"); + disconnect_usb_driver(dev); + usb_set_device_state(dev, USB_STATE_NOTATTACHED); + dev->authorized = 0; + } + } +#endif + } else { + send_otg_notify(o_notify, + NOTIFY_EVENT_DEVICE_CONNECT, 0); + store_usblog_notify(NOTIFY_PORT_DISCONNECT, + (void *)&dev->descriptor.idVendor, + (void *)&dev->descriptor.idProduct); +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + if (!dev->authorized) + disconnect_unauthorized_device(dev); +#endif + } + } else { + if (connect) + unl_info("%s root hub\n", __func__); + } +done: + return 0; +} + +static void check_roothub_device(struct usb_device *dev, bool on) +{ + struct otg_notify *o_notify = get_otg_notify(); + struct usb_device *hdev; + struct usb_device *udev; + int port = 0; + int speed = USB_SPEED_UNKNOWN; + int pr_speed = USB_SPEED_UNKNOWN; + static int hs_hub; + static int ss_hub; + int con_hub = 0; + + if (!o_notify) { + unl_err("%s otg_notify is null\n", __func__); + return; + } + + pr_speed = get_con_dev_max_speed(o_notify); + + hdev = dev->parent; + if (!hdev) + return; + + hdev = dev->bus->root_hub; + if (!hdev) + return; + + usb_hub_for_each_child(hdev, port, udev) { + if (!on && (udev == dev)) + continue; +#if defined(CONFIG_USB_HW_PARAM) + if (is_known_usbaudio(udev)) + inc_hw_param(o_notify, USB_HOST_CLASS_AUDIO_SAMSUNG_COUNT); +#endif + if (is_usbhub(udev)) + con_hub = 1; + + if (udev->speed > speed) + speed = udev->speed; + } + + if (hdev->speed >= USB_SPEED_SUPER) { + if (speed > USB_SPEED_UNKNOWN) + ss_hub = 1; + else + ss_hub = 0; + } else if (hdev->speed > USB_SPEED_UNKNOWN + && hdev->speed != USB_SPEED_WIRELESS) { + if (speed > USB_SPEED_UNKNOWN) + hs_hub = 1; + else + hs_hub = 0; + } else + ; + + if (ss_hub || hs_hub) { + if (speed > pr_speed) + set_con_dev_max_speed(o_notify, speed); + } else + set_con_dev_max_speed(o_notify, USB_SPEED_UNKNOWN); + + unl_info("%s : dev->speed %s %s\n", __func__, + usb_speed_string(dev->speed), on ? "on" : "off"); + + unl_info("%s : o_notify->speed %s\n", __func__, + usb_speed_string(get_con_dev_max_speed(o_notify))); + + set_con_dev_hub(o_notify, speed, con_hub); +} + +#if defined(CONFIG_USB_HW_PARAM) +static int set_hw_param(struct usb_device *dev) +{ + struct otg_notify *o_notify = get_otg_notify(); + int ret = 0; + int bInterfaceClass = 0, speed = 0; + + if (o_notify == NULL || dev->config->interface[0] == NULL) { + ret = -EFAULT; + goto err; + } + + if (dev->bus->root_hub != dev) { + bInterfaceClass + = dev->config->interface[0] + ->cur_altsetting->desc.bInterfaceClass; + speed = dev->speed; + + unl_info("%s USB device connected - Class : 0x%x, speed : 0x%x\n", + __func__, bInterfaceClass, speed); + + if (bInterfaceClass == USB_CLASS_AUDIO) + inc_hw_param(o_notify, USB_HOST_CLASS_AUDIO_COUNT); + else if (bInterfaceClass == USB_CLASS_COMM) + inc_hw_param(o_notify, USB_HOST_CLASS_COMM_COUNT); + else if (bInterfaceClass == USB_CLASS_HID) + inc_hw_param(o_notify, USB_HOST_CLASS_HID_COUNT); + else if (bInterfaceClass == USB_CLASS_PHYSICAL) + inc_hw_param(o_notify, USB_HOST_CLASS_PHYSICAL_COUNT); + else if (bInterfaceClass == USB_CLASS_STILL_IMAGE) + inc_hw_param(o_notify, USB_HOST_CLASS_IMAGE_COUNT); + else if (bInterfaceClass == USB_CLASS_PRINTER) + inc_hw_param(o_notify, USB_HOST_CLASS_PRINTER_COUNT); + else if (bInterfaceClass == USB_CLASS_MASS_STORAGE) { + inc_hw_param(o_notify, USB_HOST_CLASS_STORAGE_COUNT); + if (speed == USB_SPEED_SUPER) + inc_hw_param(o_notify, + USB_HOST_STORAGE_SUPER_COUNT); + else if (speed == USB_SPEED_HIGH) + inc_hw_param(o_notify, + USB_HOST_STORAGE_HIGH_COUNT); + else if (speed == USB_SPEED_FULL) + inc_hw_param(o_notify, + USB_HOST_STORAGE_FULL_COUNT); + } else if (bInterfaceClass == USB_CLASS_HUB) + inc_hw_param(o_notify, USB_HOST_CLASS_HUB_COUNT); + else if (bInterfaceClass == USB_CLASS_CDC_DATA) + inc_hw_param(o_notify, USB_HOST_CLASS_CDC_COUNT); + else if (bInterfaceClass == USB_CLASS_CSCID) + inc_hw_param(o_notify, USB_HOST_CLASS_CSCID_COUNT); + else if (bInterfaceClass == USB_CLASS_CONTENT_SEC) + inc_hw_param(o_notify, USB_HOST_CLASS_CONTENT_COUNT); + else if (bInterfaceClass == USB_CLASS_VIDEO) + inc_hw_param(o_notify, USB_HOST_CLASS_VIDEO_COUNT); + else if (bInterfaceClass == USB_CLASS_WIRELESS_CONTROLLER) + inc_hw_param(o_notify, USB_HOST_CLASS_WIRELESS_COUNT); + else if (bInterfaceClass == USB_CLASS_MISC) + inc_hw_param(o_notify, USB_HOST_CLASS_MISC_COUNT); + else if (bInterfaceClass == USB_CLASS_APP_SPEC) + inc_hw_param(o_notify, USB_HOST_CLASS_APP_COUNT); + else if (bInterfaceClass == USB_CLASS_VENDOR_SPEC) + inc_hw_param(o_notify, USB_HOST_CLASS_VENDOR_COUNT); + + if (speed == USB_SPEED_SUPER) + inc_hw_param(o_notify, USB_HOST_SUPER_SPEED_COUNT); + else if (speed == USB_SPEED_HIGH) + inc_hw_param(o_notify, USB_HOST_HIGH_SPEED_COUNT); + else if (speed == USB_SPEED_FULL) + inc_hw_param(o_notify, USB_HOST_FULL_SPEED_COUNT); + else if (speed == USB_SPEED_LOW) + inc_hw_param(o_notify, USB_HOST_LOW_SPEED_COUNT); + } +err: + return ret; +} +#endif + +static void check_unsupport_device(struct usb_device *dev) +{ + struct dev_table *id; + + /* check VID, PID */ + for (id = unsupport_device_table; id->dev.match_flags; id++) { + if ((id->dev.match_flags & USB_DEVICE_ID_MATCH_VENDOR) && + (id->dev.match_flags & USB_DEVICE_ID_MATCH_PRODUCT) && + id->dev.idVendor == le16_to_cpu(dev->descriptor.idVendor) && + id->dev.idProduct == le16_to_cpu(dev->descriptor.idProduct)) { +#if defined(CONFIG_USB_HOST_CERTI) + send_usb_certi_uevent(USB_CERTI_UNSUPPORT_ACCESSORY); +#endif + break; + } + } +} + +static int dev_notify(struct notifier_block *self, + unsigned long action, void *dev) +{ + switch (action) { + case USB_DEVICE_ADD: + call_device_notify(dev, 1); + call_battery_notify(dev, 1); + check_roothub_device(dev, 1); +#if defined(CONFIG_USB_HW_PARAM) + set_hw_param(dev); +#endif + check_unsupport_device(dev); + check_usbaudio(dev); + check_usbgroup(dev); + break; + case USB_DEVICE_REMOVE: + call_device_notify(dev, 0); + call_battery_notify(dev, 0); + check_roothub_device(dev, 0); + break; + } + return NOTIFY_OK; +} + +static struct notifier_block dev_nb = { + .notifier_call = dev_notify, +}; + +void register_usbdev_notify(void) +{ + usb_register_notify(&dev_nb); +} +EXPORT_SYMBOL(register_usbdev_notify); + +void unregister_usbdev_notify(void) +{ + usb_unregister_notify(&dev_nb); +} +EXPORT_SYMBOL(unregister_usbdev_notify); + diff --git a/drivers/usb/notify/dock_notify.h b/drivers/usb/notify/dock_notify.h new file mode 100755 index 000000000000..8c22215e8102 --- /dev/null +++ b/drivers/usb/notify/dock_notify.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2015-2023 Samsung Electronics Co. Ltd. + * + * 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. + */ + + /* usb notify layer v4.0 */ + +#ifndef __LINUX_DOCK_NOTIFY_H__ +#define __LINUX_DOCK_NOTIFY_H__ + +extern void register_usbdev_notify(void); +extern void unregister_usbdev_notify(void); +#endif + diff --git a/drivers/usb/notify/external_notify.c b/drivers/usb/notify/external_notify.c new file mode 100755 index 000000000000..4dc94f9b2c09 --- /dev/null +++ b/drivers/usb/notify/external_notify.c @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2016-2023 Samsung Electronics Co. Ltd. + * + * 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. + */ + + /* usb notify layer v4.0 */ + +#define pr_fmt(fmt) "usb_notify: " fmt + +#include +#include +#include +#include +#include + +struct external_notify_struct { + struct blocking_notifier_head notifier_call_chain; + int call_chain_init; +}; + +#define SET_EXTERNAL_NOTIFY_BLOCK(nb, fn, dev) do { \ + (nb)->notifier_call = (fn); \ + (nb)->priority = (dev); \ + } while (0) + +#define DESTROY_EXTERNAL_NOTIFY_BLOCK(nb) \ + SET_EXTERNAL_NOTIFY_BLOCK(nb, NULL, -1) + +static struct external_notify_struct external_notifier; + +static const char *cmd_string(unsigned long cmd) +{ + switch (cmd) { + case EXTERNAL_NOTIFY_3S_NODEVICE: + return "3s_no_device"; + case EXTERNAL_NOTIFY_DEVICE_CONNECT: + return "device_connect"; + case EXTERNAL_NOTIFY_HOSTBLOCK_PRE: + return "host_block_pre"; + case EXTERNAL_NOTIFY_HOSTBLOCK_POST: + return "host_block_post"; + case EXTERNAL_NOTIFY_MDMBLOCK_PRE: + return "mdm_block_pre"; + case EXTERNAL_NOTIFY_MDMBLOCK_POST: + return "mdm_block_post"; + case EXTERNAL_NOTIFY_POWERROLE: + return "power_role_notify"; + case EXTERNAL_NOTIFY_DEVICEADD: + return "host_mode_device_added"; + case EXTERNAL_NOTIFY_HOSTBLOCK_EARLY: + return "host_block_pre_fast"; + case EXTERNAL_NOTIFY_VBUS_RESET: + return "vbus_reset"; + case EXTERNAL_NOTIFY_POSSIBLE_USB: + return "possible_usb"; + default: + return "undefined"; + } +} + +static const char *listener_string(int listener) +{ + switch (listener) { + case EXTERNAL_NOTIFY_DEV_MUIC: + return "muic"; + case EXTERNAL_NOTIFY_DEV_CHARGER: + return "charger"; + case EXTERNAL_NOTIFY_DEV_PDIC: + return "pdic"; + case EXTERNAL_NOTIFY_DEV_MANAGER: + return "manager"; + default: + return "undefined"; + } +} + +static int create_external_notify(void) +{ + if (!external_notifier.call_chain_init) { + unl_info("%s\n", __func__); + BLOCKING_INIT_NOTIFIER_HEAD + (&(external_notifier.notifier_call_chain)); + external_notifier.call_chain_init = 1; + } + return 0; +} + +int usb_external_notify_register(struct notifier_block *nb, + notifier_fn_t notifier, int listener) +{ + int ret = 0; + + unl_info("%s: listener=(%s)%d register\n", __func__, + listener_string(listener), listener); + + create_external_notify(); + + SET_EXTERNAL_NOTIFY_BLOCK(nb, notifier, listener); + ret = blocking_notifier_chain_register + (&(external_notifier.notifier_call_chain), nb); + if (ret < 0) + unl_err("%s: blocking_notifier_chain_register error(%d)\n", + __func__, ret); + + return ret; +} +EXPORT_SYMBOL(usb_external_notify_register); + +int usb_external_notify_unregister(struct notifier_block *nb) +{ + int ret = 0; + + unl_info("%s: listener=(%s)%d unregister\n", __func__, + listener_string(nb->priority), + nb->priority); + + ret = blocking_notifier_chain_unregister + (&(external_notifier.notifier_call_chain), nb); + if (ret < 0) + unl_err("%s: blocking_notifier_chain_unregister error(%d)\n", + __func__, ret); + DESTROY_EXTERNAL_NOTIFY_BLOCK(nb); + + return ret; +} +EXPORT_SYMBOL(usb_external_notify_unregister); + +int send_external_notify(unsigned long cmd, int data) +{ + int ret = 0; + + unl_info("%s: cmd=%s(%lu), data=%d\n", __func__, cmd_string(cmd), + cmd, data); + + create_external_notify(); + + ret = blocking_notifier_call_chain + (&(external_notifier.notifier_call_chain), + cmd, (void *)&(data)); + + switch (ret) { + case NOTIFY_STOP_MASK: + case NOTIFY_BAD: + unl_err("%s: notify error occur(0x%x)\n", __func__, ret); + break; + case NOTIFY_DONE: + case NOTIFY_OK: + unl_info("%s: notify done(0x%x)\n", __func__, ret); + break; + default: + unl_info("%s: notify status unknown(0x%x)\n", __func__, ret); + break; + } + + return ret; +} +EXPORT_SYMBOL(send_external_notify); + +void external_notifier_init(void) +{ + unl_info("%s\n", __func__); + create_external_notify(); +} +EXPORT_SYMBOL(external_notifier_init); + diff --git a/drivers/usb/notify/host_notify_class.c b/drivers/usb/notify/host_notify_class.c new file mode 100755 index 000000000000..ecf87c604146 --- /dev/null +++ b/drivers/usb/notify/host_notify_class.c @@ -0,0 +1,379 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * + * Copyright (C) 2011-2023 Samsung, Inc. + * Author: Dongrak Shin + * + */ + + /* usb notify layer v4.0 */ + +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(CONFIG_USB_HW_PARAM) +#include +#endif + +struct notify_data { + struct class *host_notify_class; + atomic_t device_count; + struct mutex host_notify_lock; +}; + +static struct notify_data host_notify; + +static ssize_t mode_show( + struct device *dev, struct device_attribute *attr, char *buf) +{ + struct host_notify_dev *ndev = (struct host_notify_dev *) + dev_get_drvdata(dev); + char *mode; + + switch (ndev->mode) { + case NOTIFY_HOST_MODE: + mode = "HOST"; + break; + case NOTIFY_PERIPHERAL_MODE: + mode = "PERIPHERAL"; + break; + case NOTIFY_TEST_MODE: + mode = "TEST"; + break; + case NOTIFY_NONE_MODE: + default: + mode = "NONE"; + break; + } + + return sprintf(buf, "%s\n", mode); +} + +static ssize_t mode_store( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct host_notify_dev *ndev = (struct host_notify_dev *) + dev_get_drvdata(dev); + + char *mode; + size_t ret = -ENOMEM; + int sret = 0; + + if (size < strlen(buf)) + goto error; + mode = kzalloc(size+1, GFP_KERNEL); + if (!mode) + goto error; + + sret = sscanf(buf, "%s", mode); + if (sret != 1) + goto error1; + + if (ndev->set_mode) { + unl_info("host_notify: set mode %s\n", mode); + if (!strncmp(mode, "HOST", 4)) + ndev->set_mode(NOTIFY_SET_ON); + else if (!strncmp(mode, "NONE", 4)) + ndev->set_mode(NOTIFY_SET_OFF); + } + ret = size; +error1: + kfree(mode); +error: + return ret; +} + +static ssize_t booster_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct host_notify_dev *ndev = (struct host_notify_dev *) + dev_get_drvdata(dev); + char *booster; + + switch (ndev->booster) { + case NOTIFY_POWER_ON: + booster = "ON"; + break; + case NOTIFY_POWER_OFF: + default: + booster = "OFF"; + break; + } + + unl_info("host_notify: read booster %s\n", booster); + return sprintf(buf, "%s\n", booster); +} + +static ssize_t booster_store( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct host_notify_dev *ndev = (struct host_notify_dev *) + dev_get_drvdata(dev); + + char *booster; + size_t ret = -ENOMEM; + int sret = 0; + + if (size < strlen(buf)) + goto error; + booster = kzalloc(size+1, GFP_KERNEL); + if (!booster) + goto error; + + sret = sscanf(buf, "%s", booster); + if (sret != 1) + goto error1; + + if (ndev->set_booster) { + unl_info("host_notify: set booster %s\n", booster); + if (!strncmp(booster, "ON", 2)) { + ndev->set_booster(NOTIFY_SET_ON); + ndev->mode = NOTIFY_TEST_MODE; + } else if (!strncmp(booster, "OFF", 3)) { + ndev->set_booster(NOTIFY_SET_OFF); + ndev->mode = NOTIFY_NONE_MODE; + } + } + ret = size; +error1: + kfree(booster); +error: + return ret; +} + +static DEVICE_ATTR_RW(mode); +static DEVICE_ATTR_RW(booster); + +static struct attribute *host_notify_attrs[] = { + &dev_attr_mode.attr, + &dev_attr_booster.attr, + NULL, +}; + +static struct attribute_group host_notify_attr_grp = { + .attrs = host_notify_attrs, +}; + +char *host_state_string(int type) +{ + switch (type) { + case NOTIFY_HOST_NONE: return "none"; + case NOTIFY_HOST_ADD: return "add"; + case NOTIFY_HOST_REMOVE: return "remove"; + case NOTIFY_HOST_OVERCURRENT: return "overcurrent"; + case NOTIFY_HOST_LOWBATT: return "lowbatt"; + case NOTIFY_HOST_BLOCK: return "block"; + case NOTIFY_HOST_SOURCE: return "source"; + case NOTIFY_HOST_SINK: return "sink"; + case NOTIFY_HOST_UNKNOWN: + default: return "unknown"; + } +} + +static int check_state_type(int state) +{ + int ret = 0; + + switch (state) { + case NOTIFY_HOST_ADD: + case NOTIFY_HOST_REMOVE: + case NOTIFY_HOST_BLOCK: + ret = NOTIFY_HOST_STATE; + break; + case NOTIFY_HOST_OVERCURRENT: + case NOTIFY_HOST_LOWBATT: + case NOTIFY_HOST_SOURCE: + case NOTIFY_HOST_SINK: + ret = NOTIFY_POWER_STATE; + break; + case NOTIFY_HOST_NONE: + case NOTIFY_HOST_UNKNOWN: + default: + ret = NOTIFY_UNKNOWN_STATE; + break; + } + return ret; +} + +int host_state_notify(struct host_notify_dev *ndev, int state) +{ + int type = 0; + + if (!ndev->dev) { + unl_err("host_notify: %s ndev->dev is NULL\n", __func__); + return -ENXIO; + } + + mutex_lock(&host_notify.host_notify_lock); + + unl_info("host_notify: ndev name=%s: state=%s\n", + ndev->name, host_state_string(state)); + + type = check_state_type(state); + + if (type == NOTIFY_HOST_STATE) { + if (ndev->host_state != state) { + unl_info("host_notify: host_state (%s->%s)\n", + host_state_string(ndev->host_state), + host_state_string(state)); + ndev->host_state = state; + ndev->host_change = 1; + kobject_uevent(&ndev->dev->kobj, KOBJ_CHANGE); + ndev->host_change = 0; +#if defined(CONFIG_USB_HW_PARAM) + if (state == NOTIFY_HOST_ADD) + inc_hw_param_host(ndev, USB_CCIC_OTG_USE_COUNT); +#endif + } + } else if (type == NOTIFY_POWER_STATE) { + if (ndev->power_state != state) { + unl_info("host_notify: power_state (%s->%s)\n", + host_state_string(ndev->power_state), + host_state_string(state)); + ndev->power_state = state; + ndev->power_change = 1; + kobject_uevent(&ndev->dev->kobj, KOBJ_CHANGE); + ndev->power_change = 0; +#if defined(CONFIG_USB_HW_PARAM) + if (state == NOTIFY_HOST_OVERCURRENT) + inc_hw_param_host(ndev, USB_CCIC_OVC_COUNT); +#endif + } + } else { + ndev->host_state = state; + ndev->power_state = state; + } + + mutex_unlock(&host_notify.host_notify_lock); + return 0; +} +EXPORT_SYMBOL_GPL(host_state_notify); + +static int +host_notify_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct host_notify_dev *ndev = (struct host_notify_dev *) + dev_get_drvdata(dev); + char *state; + int state_type; + + if (!ndev) { + /* this happens when the device is first created */ + return 0; + } + + if (ndev->host_change) + state_type = ndev->host_state; + else if (ndev->power_change) + state_type = ndev->power_state; + else + state_type = NOTIFY_HOST_NONE; + + switch (state_type) { + case NOTIFY_HOST_ADD: + state = "ADD"; + break; + case NOTIFY_HOST_REMOVE: + state = "REMOVE"; + break; + case NOTIFY_HOST_OVERCURRENT: + state = "OVERCURRENT"; + break; + case NOTIFY_HOST_LOWBATT: + state = "LOWBATT"; + break; + case NOTIFY_HOST_BLOCK: + state = "BLOCK"; + break; + case NOTIFY_HOST_SOURCE: + state = "SOURCE"; + break; + case NOTIFY_HOST_SINK: + state = "SINK"; + break; + case NOTIFY_HOST_UNKNOWN: + state = "UNKNOWN"; + break; + case NOTIFY_HOST_NONE: + default: + return 0; + } + if (add_uevent_var(env, "DEVNAME=%s", ndev->dev->kobj.name)) + return -ENOMEM; + if (add_uevent_var(env, "STATE=%s", state)) + return -ENOMEM; + return 0; +} + +static int create_notify_class(void) +{ + if (!host_notify.host_notify_class) { + host_notify.host_notify_class + = class_create(THIS_MODULE, "host_notify"); + if (IS_ERR(host_notify.host_notify_class)) + return PTR_ERR(host_notify.host_notify_class); + atomic_set(&host_notify.device_count, 0); + mutex_init(&host_notify.host_notify_lock); + host_notify.host_notify_class->dev_uevent = host_notify_uevent; + } + + return 0; +} + +int host_notify_dev_register(struct host_notify_dev *ndev) +{ + int ret; + + if (!host_notify.host_notify_class) { + ret = create_notify_class(); + if (ret < 0) + return ret; + } + + ndev->index = atomic_inc_return(&host_notify.device_count); + ndev->dev = device_create(host_notify.host_notify_class, NULL, + MKDEV(0, ndev->index), NULL, "%s", ndev->name); + if (IS_ERR(ndev->dev)) + return PTR_ERR(ndev->dev); + + ret = sysfs_create_group(&ndev->dev->kobj, &host_notify_attr_grp); + if (ret < 0) { + device_destroy(host_notify.host_notify_class, + MKDEV(0, ndev->index)); + return ret; + } + + dev_set_drvdata(ndev->dev, ndev); + ndev->host_state = NOTIFY_HOST_NONE; + ndev->power_state = NOTIFY_HOST_SINK; + return 0; +} +EXPORT_SYMBOL_GPL(host_notify_dev_register); + +void host_notify_dev_unregister(struct host_notify_dev *ndev) +{ + ndev->host_state = NOTIFY_HOST_NONE; + ndev->power_state = NOTIFY_HOST_SINK; + sysfs_remove_group(&ndev->dev->kobj, &host_notify_attr_grp); + dev_set_drvdata(ndev->dev, NULL); + device_destroy(host_notify.host_notify_class, MKDEV(0, ndev->index)); + ndev->dev = NULL; +} +EXPORT_SYMBOL_GPL(host_notify_dev_unregister); + +int notify_class_init(void) +{ + return create_notify_class(); +} + +void notify_class_exit(void) +{ + class_destroy(host_notify.host_notify_class); +} diff --git a/drivers/usb/notify/host_notify_class.h b/drivers/usb/notify/host_notify_class.h new file mode 100755 index 000000000000..016cf24f10b5 --- /dev/null +++ b/drivers/usb/notify/host_notify_class.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2011-2023 Samsung, 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. + */ + + /* usb notify layer v4.0 */ +#ifndef __LINUX_HOST_NOTIFY_CALSS_H__ +#define __LINUX_HOST_NOTIFY_CALSS_H__ + +#ifdef CONFIG_USB_HOST_NOTIFY +extern int notify_class_init(void); +extern void notify_class_exit(void); +#else +static inline int notify_class_init(void) {return 0; } +static inline void notify_class_exit(void) {} +#endif +#endif + diff --git a/drivers/usb/notify/usb_notifier.c b/drivers/usb/notify/usb_notifier.c new file mode 100755 index 000000000000..730b8c8f0f94 --- /dev/null +++ b/drivers/usb/notify/usb_notifier.c @@ -0,0 +1,654 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019-2023 Samsung Electronics Co. Ltd. + * + * 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. + */ +#define pr_fmt(fmt) "usb_notifier: " fmt + +#include +#include +#include +#ifdef CONFIG_OF +#include +#include +#endif +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) +#include +#endif +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) +#include +#include +#endif +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) +#include +#endif +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) +#include +#endif + +#include +#if IS_ENABLED(CONFIG_COMBO_REDRIVER_PS5169) +#include +#endif +#include "usb_notifier.h" + + +struct usb_notifier_platform_data { +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + struct notifier_block ccic_usb_nb; + int is_host; +#endif +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + struct notifier_block muic_usb_nb; +#endif +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) + struct notifier_block vbus_nb; +#endif + int gpio_redriver_en; + int disable_control_en; + int unsupport_host_en; + int support_reverse_bypass_en; +}; + +#ifdef CONFIG_OF +static void of_get_usb_redriver_dt(struct device_node *np, + struct usb_notifier_platform_data *pdata) +{ + pdata->gpio_redriver_en = of_get_named_gpio(np, "qcom,gpios_redriver_en", 0); + if (pdata->gpio_redriver_en < 0) + pr_info("There is not USB 3.0 redriver pin\n"); + + if (!gpio_is_valid(pdata->gpio_redriver_en)) + pr_err("%s: usb30_redriver_en: Invalied gpio pins\n", __func__); + + pr_info("redriver_en : %d\n", pdata->gpio_redriver_en); +} + +static void of_get_disable_control_en_dt(struct device_node *np, + struct usb_notifier_platform_data *pdata) +{ + of_property_read_u32(np, "qcom,disable_control_en", &pdata->disable_control_en); + + pr_info("disable_control_en : %d\n", pdata->disable_control_en); +} + +static void of_get_unsupport_host_dt(struct device_node *np, + struct usb_notifier_platform_data *pdata) +{ + of_property_read_u32(np, "qcom,unsupport_host_en", &pdata->unsupport_host_en); + + pr_info("unsupport_host_en : %d\n", pdata->unsupport_host_en); +} + +static void of_get_support_reverse_bypass_dt(struct device_node *np, + struct usb_notifier_platform_data *pdata) +{ + pdata->support_reverse_bypass_en = of_property_read_bool(np, "support_reverse_bypass_en"); + + pr_info("support_reverse_bypass_en : %d\n", pdata->support_reverse_bypass_en); +} + +static int of_usb_notifier_dt(struct device *dev, + struct usb_notifier_platform_data *pdata) +{ + struct device_node *np = dev->of_node; + + if (!np) + return -EINVAL; + + of_get_usb_redriver_dt(np, pdata); + of_get_disable_control_en_dt(np, pdata); + of_get_unsupport_host_dt(np, pdata); + of_get_support_reverse_bypass_dt(np, pdata); + return 0; +} +#endif + +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) +static int ccic_usb_handle_notification(struct notifier_block *nb, + unsigned long action, void *data) +{ + PD_NOTI_USB_STATUS_TYPEDEF usb_status = *(PD_NOTI_USB_STATUS_TYPEDEF *)data; + struct otg_notify *o_notify = get_otg_notify(); + struct usb_notifier_platform_data *pdata = + container_of(nb, struct usb_notifier_platform_data, ccic_usb_nb); + + if (usb_status.dest != PDIC_NOTIFY_DEV_USB) + return 0; + + switch (usb_status.drp) { + case USB_STATUS_NOTIFY_ATTACH_DFP: + pr_info("%s: Turn On Host(DFP), max speed restrict = %d\n", __func__, usb_status.sub3); + +//UFP = 0, DFP = 1 +#if IS_ENABLED(CONFIG_COMBO_REDRIVER_PS5169) + ps5169_config(USB_ONLY_MODE, 1); +#endif + dwc3_max_speed_setting(usb_status.sub3); + send_otg_notify(o_notify, NOTIFY_EVENT_HOST, 1); + pdata->is_host = 1; + break; + case USB_STATUS_NOTIFY_ATTACH_UFP: + pr_info("%s: Turn On Device(UFP)\n", __func__); + dwc3_max_speed_setting(usb_status.sub3); + send_otg_notify(o_notify, NOTIFY_EVENT_VBUS, 1); +#ifdef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + if (is_blocked(o_notify, NOTIFY_BLOCK_TYPE_CLIENT)) + return -EPERM; +#endif + break; + case USB_STATUS_NOTIFY_DETACH: + if (pdata->is_host) { + pr_info("%s: Turn Off Host(DFP)\n", __func__); + send_otg_notify(o_notify, NOTIFY_EVENT_HOST, 0); + pdata->is_host = 0; + } else { + pr_info("%s: Turn Off Device(UFP)\n", __func__); + send_otg_notify(o_notify, NOTIFY_EVENT_VBUS, 0); + } +#if IS_ENABLED(CONFIG_COMBO_REDRIVER_PS5169) + ps5169_config(CLEAR_STATE, 0); +#endif + break; + default: + pr_info("%s: unsupported DRP type : %d.\n", __func__, usb_status.drp); + break; + } + return 0; +} +#endif + +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) +static int muic_usb_handle_notification(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct otg_notify *o_notify = get_otg_notify(); +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + PD_NOTI_ATTACH_TYPEDEF *p_noti = (PD_NOTI_ATTACH_TYPEDEF *)data; + muic_attached_dev_t attached_dev = p_noti->cable_type; + + pr_info("%s action=%lu, attached_dev=%d\n", + __func__, action, attached_dev); + + switch (attached_dev) { + case ATTACHED_DEV_USB_MUIC: + case ATTACHED_DEV_CDP_MUIC: + case ATTACHED_DEV_UNOFFICIAL_ID_USB_MUIC: + case ATTACHED_DEV_UNOFFICIAL_ID_CDP_MUIC: + if (action == MUIC_NOTIFY_CMD_DETACH) + send_otg_notify(o_notify, NOTIFY_EVENT_USB_CABLE, 0); + else if (action == MUIC_NOTIFY_CMD_ATTACH) + send_otg_notify(o_notify, NOTIFY_EVENT_USB_CABLE, 1); + else + pr_err("%s - ACTION Error!\n", __func__); + break; + default: + break; + } +#else + muic_attached_dev_t attached_dev = *(muic_attached_dev_t *)data; + + pr_info("%s action=%lu, attached_dev=%d\n", + __func__, action, attached_dev); + + switch (attached_dev) { + case ATTACHED_DEV_USB_MUIC: + case ATTACHED_DEV_CDP_MUIC: + case ATTACHED_DEV_UNOFFICIAL_ID_USB_MUIC: + case ATTACHED_DEV_UNOFFICIAL_ID_CDP_MUIC: + if (action == MUIC_NOTIFY_CMD_DETACH) + send_otg_notify(o_notify, NOTIFY_EVENT_USB_CABLE, 0); + else if (action == MUIC_NOTIFY_CMD_ATTACH) + send_otg_notify(o_notify, NOTIFY_EVENT_USB_CABLE, 1); + else + ; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) + fallthrough; +#endif + case ATTACHED_DEV_JIG_USB_OFF_MUIC: + case ATTACHED_DEV_JIG_USB_ON_MUIC: + if (action == MUIC_NOTIFY_CMD_DETACH) + send_otg_notify(o_notify, NOTIFY_EVENT_VBUS, 0); + else if (action == MUIC_NOTIFY_CMD_ATTACH) + send_otg_notify(o_notify, NOTIFY_EVENT_VBUS, 1); + else + pr_err("%s - ACTION Error!\n", __func__); + break; + case ATTACHED_DEV_OTG_MUIC: + if (action == MUIC_NOTIFY_CMD_DETACH) + send_otg_notify(o_notify, NOTIFY_EVENT_HOST, 0); + else if (action == MUIC_NOTIFY_CMD_ATTACH) + send_otg_notify(o_notify, NOTIFY_EVENT_HOST, 1); + else + pr_err("%s - ACTION Error!\n", __func__); + break; + case ATTACHED_DEV_HMT_MUIC: + if (action == MUIC_NOTIFY_CMD_DETACH) + send_otg_notify(o_notify, NOTIFY_EVENT_HMT, 0); + else if (action == MUIC_NOTIFY_CMD_ATTACH) + send_otg_notify(o_notify, NOTIFY_EVENT_HMT, 1); + else + pr_err("%s - ACTION Error!\n", __func__); + break; + case ATTACHED_DEV_JIG_UART_OFF_VB_OTG_MUIC: + if (action == MUIC_NOTIFY_CMD_DETACH) + pr_info("%s - USB_HOST_TEST_DETACHED\n", __func__); + else if (action == MUIC_NOTIFY_CMD_ATTACH) + pr_info("%s - USB_HOST_TEST_ATTACHED\n", __func__); + else + pr_err("%s - ACTION Error!\n", __func__); + break; + case ATTACHED_DEV_SMARTDOCK_TA_MUIC: + if (action == MUIC_NOTIFY_CMD_DETACH) + send_otg_notify(o_notify, NOTIFY_EVENT_SMARTDOCK_TA, 0); + else if (action == MUIC_NOTIFY_CMD_ATTACH) + send_otg_notify(o_notify, NOTIFY_EVENT_SMARTDOCK_TA, 1); + else + pr_err("%s - ACTION Error!\n", __func__); + break; + case ATTACHED_DEV_SMARTDOCK_USB_MUIC: + if (action == MUIC_NOTIFY_CMD_DETACH) + send_otg_notify + (o_notify, NOTIFY_EVENT_SMARTDOCK_USB, 0); + else if (action == MUIC_NOTIFY_CMD_ATTACH) + send_otg_notify + (o_notify, NOTIFY_EVENT_SMARTDOCK_USB, 1); + else + pr_err("%s - ACTION Error!\n", __func__); + break; + case ATTACHED_DEV_AUDIODOCK_MUIC: + if (action == MUIC_NOTIFY_CMD_DETACH) + send_otg_notify(o_notify, NOTIFY_EVENT_AUDIODOCK, 0); + else if (action == MUIC_NOTIFY_CMD_ATTACH) + send_otg_notify(o_notify, NOTIFY_EVENT_AUDIODOCK, 1); + else + pr_err("%s - ACTION Error!\n", __func__); + break; + case ATTACHED_DEV_UNIVERSAL_MMDOCK_MUIC: + if (action == MUIC_NOTIFY_CMD_DETACH) + send_otg_notify(o_notify, NOTIFY_EVENT_MMDOCK, 0); + else if (action == MUIC_NOTIFY_CMD_ATTACH) + send_otg_notify(o_notify, NOTIFY_EVENT_MMDOCK, 1); + else + pr_err("%s - ACTION Error!\n", __func__); + break; + case ATTACHED_DEV_USB_LANHUB_MUIC: + if (action == MUIC_NOTIFY_CMD_DETACH) + send_otg_notify(o_notify, NOTIFY_EVENT_LANHUB, 0); + else if (action == MUIC_NOTIFY_CMD_ATTACH) { + send_otg_notify(o_notify, NOTIFY_EVENT_DRIVE_VBUS, 0); + send_otg_notify(o_notify, NOTIFY_EVENT_LANHUB, 1); + } else + pr_err("%s - ACTION Error!\n", __func__); + break; + case ATTACHED_DEV_GAMEPAD_MUIC: + if (action == MUIC_NOTIFY_CMD_DETACH) + send_otg_notify(o_notify, NOTIFY_EVENT_GAMEPAD, 0); + else if (action == MUIC_NOTIFY_CMD_ATTACH) + send_otg_notify(o_notify, NOTIFY_EVENT_GAMEPAD, 1); + else + pr_err("%s - ACTION Error!\n", __func__); + break; + default: + break; + } +#endif + return 0; +} +#endif +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) +static int vbus_handle_notification(struct notifier_block *nb, + unsigned long cmd, void *data) +{ + vbus_status_t vbus_type = *(vbus_status_t *)data; + struct otg_notify *o_notify; + + o_notify = get_otg_notify(); + + pr_info("%s cmd=%lu, vbus_type=%s\n", + __func__, cmd, vbus_type == STATUS_VBUS_HIGH ? "HIGH" : "LOW"); + + switch (vbus_type) { + case STATUS_VBUS_HIGH: + send_otg_notify(o_notify, NOTIFY_EVENT_VBUSPOWER, 1); + break; + case STATUS_VBUS_LOW: + send_otg_notify(o_notify, NOTIFY_EVENT_VBUSPOWER, 0); + break; + default: + break; + } + return 0; +} +#endif + +static int otg_accessory_power(bool enable) +{ + struct power_supply *psy_otg; + union power_supply_propval val; + int on = !!enable; + int ret = 0; + + pr_info("%s %d, enable=%d\n", __func__, __LINE__, enable); + /* otg psy test */ + psy_otg = get_power_supply_by_name("otg"); + + if (psy_otg) { + val.intval = enable; + ret = psy_otg->desc->set_property(psy_otg, POWER_SUPPLY_PROP_ONLINE, &val); + } else { + pr_err("%s: Fail to get psy battery\n", __func__); + return -1; + } + if (ret) { + pr_err("%s: fail to set power_suppy ONLINE property(%d)\n", + __func__, ret); + } else { + pr_info("otg accessory power = %d\n", on); + } + + return ret; +} + +static int set_online(int event, int state) +{ + union power_supply_propval value; + struct power_supply *psy, *psy_otg; + + pr_info("%s: %d, %d\n", __func__, event, state); + + psy = power_supply_get_by_name("battery"); + if (!psy) { + pr_err("%s: fail to get battery power_supply\n", __func__); + return -1; + } + psy_otg = get_power_supply_by_name("otg"); + if (!psy_otg) { + pr_err("%s: fail to get battery power_supply_otg\n", __func__); + return -1; + } + + if (event == NOTIFY_EVENT_HMD_EXT_CURRENT) { + value.intval = state; + psy_otg->desc->set_property(psy_otg, POWER_SUPPLY_PROP_VOLTAGE_MAX, &value); + } else { + if (state) + value.intval = SEC_BATTERY_CABLE_SMART_OTG; + else + value.intval = SEC_BATTERY_CABLE_SMART_NOTG; + + psy->desc->set_property(psy, POWER_SUPPLY_PROP_ONLINE, &value); + } + + return 0; +} + +static int qcom_set_host(bool enable) +{ + dwc_msm_id_event(enable); + return 0; +} + +static int qcom_set_peripheral(bool enable) +{ +#if IS_ENABLED(CONFIG_COMBO_REDRIVER_PS5169) + if (enable) + ps5169_config(USB_ONLY_MODE, 0); +#endif + dwc_msm_vbus_event(enable); + return 0; +} + +static int qcom_get_gadget_speed(void) +{ + return gadget_speed(); +} + +static int usb_set_chg_current(int state) +{ + union power_supply_propval val; + struct device_node *np_charger = NULL; + char *charger_name; + + np_charger = of_find_node_by_name(NULL, "battery"); + if (!np_charger) { + pr_err("%s: failed to get the battery device node\n", __func__); + return 0; + } else { + if (!of_property_read_string(np_charger, "battery,charger_name", + (char const **)&charger_name)) { + pr_info("%s: charger_name = %s\n", __func__, + charger_name); + } else { + pr_err("%s: failed to get the charger name\n", __func__); + return 0; + } + } + /* current setting */ + pr_info("usb : charing current set = %d\n", state); + + switch (state) { + case NOTIFY_USB_SUSPENDED: + val.intval = USB_CURRENT_SUSPENDED; + break; + case NOTIFY_USB_UNCONFIGURED: + val.intval = USB_CURRENT_UNCONFIGURED; + break; + case NOTIFY_USB_CONFIGURED: + val.intval = USB_CURRENT_HIGH_SPEED; + break; + default: + val.intval = USB_CURRENT_HIGH_SPEED; + break; + } + + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_USB_CONFIGURE, val); + + return 0; +} + +#if IS_ENABLED(CONFIG_USB_HW_PARAM) +static int is_skip_list(int index) +{ + int ret = 0; + + switch (index) { + case USB_CCIC_VR_USE_COUNT: + case USB_HOST_CLASS_COMM_COUNT: + case USB_HOST_CLASS_PHYSICAL_COUNT: + case USB_HOST_CLASS_IMAGE_COUNT: + case USB_HOST_CLASS_PRINTER_COUNT: + case USB_HOST_CLASS_CDC_COUNT: + case USB_HOST_CLASS_CSCID_COUNT: + case USB_HOST_CLASS_CONTENT_COUNT: + case USB_HOST_CLASS_VIDEO_COUNT: + case USB_HOST_CLASS_WIRELESS_COUNT: + case USB_HOST_CLASS_MISC_COUNT: + case USB_HOST_CLASS_APP_COUNT: + case USB_HALL_FOLDING_COUNT: + case USB_HOST_CLASS_VENDOR_COUNT: + case USB_CCIC_FWUP_ERROR_COUNT: + case USB_CCIC_VERSION: + ret = 1; + break; + default: + break; + } + + return ret; +} +#endif + +static int reverse_bypass_power(int mode) +{ + union power_supply_propval val; + int ret = 0; + + pr_info("%s %d, mode=%d\n", __func__, __LINE__, mode); + + if (mode) + val.intval = TURN_OTG_OFF_RB_ON; + else + val.intval = TURN_RB_OFF; + ret = psy_do_property("otg", set, POWER_SUPPLY_EXT_PROP_OTG_VBUS_CTRL, val); + if (ret < 0) { + pr_err("%s: Fail to control reverse bypass\n", __func__); + return -1; + } + + return ret; +} + +static int get_support_reverse_bypass_en(void *data) +{ + struct usb_notifier_platform_data *pdata = + (struct usb_notifier_platform_data *)(data); + + return pdata->support_reverse_bypass_en; +} + +static struct otg_notify sec_otg_notify = { + .vbus_drive = otg_accessory_power, + .reverse_bypass_drive = reverse_bypass_power, + .get_support_reverse_bypass_en = get_support_reverse_bypass_en, + .set_host = qcom_set_host, + .set_peripheral = qcom_set_peripheral, + .get_gadget_speed = qcom_get_gadget_speed, + + .vbus_detect_gpio = -1, + .is_host_wakelock = 0, + .is_wakelock = 1, + .unsupport_host = 0, +#if IS_ENABLED(CONFIG_COMBO_REDRIVER_PS5169) + .booting_delay_sec = 16, +#else + .booting_delay_sec = 10, +#endif +#if !IS_ENABLED(CONFIG_PDIC_NOTIFIER) + .auto_drive_vbus = NOTIFY_OP_PRE, +#endif + .disable_control = 1, + .device_check_sec = 3, + .set_battcall = set_online, + .set_chg_current = usb_set_chg_current, +#if IS_ENABLED(CONFIG_USB_HW_PARAM) + .is_skip_list = is_skip_list, +#endif + .pre_peri_delay_us = 6, +}; + +static int usb_notifier_probe(struct platform_device *pdev) +{ + struct usb_notifier_platform_data *pdata = NULL; + int ret = 0; + + pr_info("notifier_probe\n"); + + if (pdev->dev.of_node) { + pdata = devm_kzalloc(&pdev->dev, + sizeof(struct usb_notifier_platform_data), GFP_KERNEL); + if (!pdata) { + dev_err(&pdev->dev, "Failed to allocate memory\n"); + return -ENOMEM; + } + + ret = of_usb_notifier_dt(&pdev->dev, pdata); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to get device of_node\n"); + return ret; + } + + pdev->dev.platform_data = pdata; + } else + pdata = pdev->dev.platform_data; + + sec_otg_notify.redriver_en_gpio = pdata->gpio_redriver_en; + if (pdata->disable_control_en == 1) + sec_otg_notify.disable_control = 1; + if (pdata->unsupport_host_en == 1) + sec_otg_notify.unsupport_host = 1; + set_otg_notify(&sec_otg_notify); + set_notify_data(&sec_otg_notify, pdata); + sec_otg_notify.booting_delay_sync_usb = is_dwc3_msm_probe_done() ? 0 : 1; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + pdata->is_host = 0; +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + manager_notifier_register(&pdata->ccic_usb_nb, ccic_usb_handle_notification, + MANAGER_NOTIFY_PDIC_USB); +#else + ccic_notifier_register(&pdata->ccic_usb_nb, ccic_usb_handle_notification, + PDIC_NOTIFY_DEV_USB); +#endif +#endif +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + muic_notifier_register(&pdata->muic_usb_nb, muic_usb_handle_notification, + MUIC_NOTIFY_DEV_USB); +#endif +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) + vbus_notifier_register(&pdata->vbus_nb, vbus_handle_notification, + VBUS_NOTIFY_DEV_USB); +#endif + dev_info(&pdev->dev, "usb notifier probe\n"); + return 0; +} + +static int usb_notifier_remove(struct platform_device *pdev) +{ + struct usb_notifier_platform_data *pdata = dev_get_platdata(&pdev->dev); +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + manager_notifier_unregister(&pdata->ccic_usb_nb); +#else + ccic_notifier_unregister(&pdata->ccic_usb_nb); +#endif +#elif IS_ENABLED(CONFIG_MUIC_NOTIFIER) + muic_notifier_unregister(&pdata->muic_usb_nb); +#endif +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) + vbus_notifier_unregister(&pdata->vbus_nb); +#endif + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id usb_notifier_dt_ids[] = { + { .compatible = "samsung,usb-notifier", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, usb_notifier_dt_ids); +#endif + +static struct platform_driver usb_notifier_driver = { + .probe = usb_notifier_probe, + .remove = usb_notifier_remove, + .driver = { + .name = "usb_notifier", + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = of_match_ptr(usb_notifier_dt_ids), +#endif + }, +}; + +static int __init usb_notifier_init(void) +{ + return platform_driver_register(&usb_notifier_driver); +} + +static void __exit usb_notifier_exit(void) +{ + platform_driver_unregister(&usb_notifier_driver); +} + +late_initcall(usb_notifier_init); +module_exit(usb_notifier_exit); + +MODULE_AUTHOR("Samsung USB Team"); +MODULE_DESCRIPTION("USB Notifier"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/notify/usb_notifier.h b/drivers/usb/notify/usb_notifier.h new file mode 100755 index 000000000000..07a42140083f --- /dev/null +++ b/drivers/usb/notify/usb_notifier.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2014 Samsung Electronics Co. Ltd. + * + * 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. + */ +#ifndef __LINUX_USB_NOTIFIER_H__ +#define __LINUX_USB_NOTIFIER_H__ + +extern int dwc_msm_vbus_event(bool enable); +extern void dwc3_max_speed_setting(int speed); +extern int dwc_msm_id_event(bool enable); +extern int gadget_speed(void); +extern int is_dwc3_msm_probe_done(void); + +#endif diff --git a/drivers/usb/notify/usb_notify.c b/drivers/usb/notify/usb_notify.c new file mode 100755 index 000000000000..5136484af95a --- /dev/null +++ b/drivers/usb/notify/usb_notify.c @@ -0,0 +1,4159 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2014-2023 Samsung Electronics Co. Ltd. + * + * 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. + */ + + /* usb notify layer v4.0 */ +#define NOTIFY_VERSION "4.0" + +#define pr_fmt(fmt) "usb_notify: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "host_notify_class.h" +#include "dock_notify.h" +#include "usb_notify_sysfs.h" + +#define DEFAULT_OVC_POLL_SEC 3 +#define MAX_SECURE_CONNECTION 10 +#define MAX_VAL 0x7FFFFFFF + +struct ovc { + struct otg_notify *o_notify; + wait_queue_head_t delay_wait; + struct completion scanning_done; + struct task_struct *th; + struct mutex ovc_lock; + int thread_remove; + int can_ovc; + int poll_period; + int prev_state; + void *data; + int (*check_state)(void *data); +}; + +struct vbus_gpio { + spinlock_t lock; + int gpio_status; +}; + +struct otg_state_work { + struct otg_notify *o_notify; + struct work_struct otg_work; + unsigned long event; + int enable; +}; + +struct otg_booting_delay { + struct delayed_work booting_work; + unsigned long reserve_state; +}; + +struct typec_info { + int data_role; + int power_role; + int pd; + int doing_drswap; + int doing_prswap; +}; + +struct usb_gadget_info { + int bus_state; + int usb_cable_connect; +}; + +struct usb_notify { + struct otg_notify *o_notify; + struct atomic_notifier_head otg_notifier; + struct blocking_notifier_head extra_notifier; + struct notifier_block otg_nb; + struct notifier_block extra_nb; + struct vbus_gpio v_gpio; + struct host_notify_dev ndev; + struct usb_notify_dev udev; + struct workqueue_struct *notifier_wq; + struct wakeup_source ws; + struct otg_booster *booster; + struct ovc ovc_info; + struct otg_booting_delay b_delay; + struct delayed_work check_work; + struct work_struct reverse_bypass_on_work; + struct typec_info typec_status; + struct usb_gadget_info gadget_status; + struct mutex state_lock; + spinlock_t event_spin_lock; + wait_queue_head_t init_delay; + int is_device; + int cond_max_speed; + int check_work_complete; + int oc_noti; + int disable_v_drive; + unsigned long c_type; + int c_status; + int sec_whitelist_enable; + int sec_whitelist_enable_for_id; + int sec_whitelist_enable_for_serial; + int reserve_vbus_booster; + int disable_state; + int reverse_bypass_status; + int lock_state; + int restricted; + int allowlist_restricted; + int cond_sshub; + int cond_hshub; + int skip_possible_usb; + unsigned int secure_connect_group[USB_GROUP_MAX]; +#if defined(CONFIG_USB_HW_PARAM) + unsigned long long hw_param[USB_CCIC_HW_PARAM_MAX]; +#endif +}; + +struct usb_notify_core { + struct otg_notify *o_notify; + unsigned int lpm_charging_type_done; +}; + +static struct usb_notify_core *u_notify_core; + +/* + * Define event types. + * NOTIFY_EVENT_STATE can be called in both interrupt context + * and process context. But it executes queue_work. + * NOTIFY_EVENT_EXTRA can be called directly without queue_work. + * But it must be called in process context. + * NOTIFY_EVENT_DELAY events can not run inner booting delay. + * NOTIFY_EVENT_NEED_VBUSDRIVE events need to drive 5v out + * from phone charger ic + * NOTIFY_EVENT_NOBLOCKING events are not blocked by disable sysfs. + * NOTIFY_EVENT_NOSAVE events are not saved in cable type. + */ +static int check_event_type(enum otg_notify_events event) +{ + int ret = 0; + + switch (event) { + case NOTIFY_EVENT_OVERCURRENT: + case NOTIFY_EVENT_VBUSPOWER: + case NOTIFY_EVENT_SMSC_OVC: + case NOTIFY_EVENT_SMTD_EXT_CURRENT: + case NOTIFY_EVENT_MMD_EXT_CURRENT: + case NOTIFY_EVENT_HMD_EXT_CURRENT: + case NOTIFY_EVENT_DEVICE_CONNECT: + case NOTIFY_EVENT_GAMEPAD_CONNECT: + case NOTIFY_EVENT_LANHUB_CONNECT: + case NOTIFY_EVENT_POWER_SOURCE: + case NOTIFY_EVENT_PD_CONTRACT: + case NOTIFY_EVENT_VBUS_RESET: + case NOTIFY_EVENT_RESERVE_BOOSTER: + case NOTIFY_EVENT_USB_CABLE: + case NOTIFY_EVENT_USBD_SUSPENDED: + case NOTIFY_EVENT_USBD_UNCONFIGURED: + case NOTIFY_EVENT_USBD_CONFIGURED: + case NOTIFY_EVENT_DR_SWAP: + case NOTIFY_EVENT_REVERSE_BYPASS_DEVICE_CONNECT: + case NOTIFY_EVENT_REVERSE_BYPASS_DEVICE_ATTACH: + ret |= NOTIFY_EVENT_EXTRA; + break; + case NOTIFY_EVENT_VBUS: + case NOTIFY_EVENT_SMARTDOCK_USB: + ret |= (NOTIFY_EVENT_STATE | NOTIFY_EVENT_DELAY + | NOTIFY_EVENT_NEED_CLIENT); + break; + case NOTIFY_EVENT_HOST: + case NOTIFY_EVENT_HMT: + case NOTIFY_EVENT_GAMEPAD: + ret |= (NOTIFY_EVENT_STATE | NOTIFY_EVENT_NEED_VBUSDRIVE + | NOTIFY_EVENT_DELAY | NOTIFY_EVENT_NEED_HOST); + break; + case NOTIFY_EVENT_POGO: + ret |= (NOTIFY_EVENT_STATE | NOTIFY_EVENT_DELAY + | NOTIFY_EVENT_NEED_HOST); + break; + case NOTIFY_EVENT_HOST_RELOAD: + ret |= (NOTIFY_EVENT_STATE | NOTIFY_EVENT_NEED_HOST + | NOTIFY_EVENT_NOSAVE); + break; + case NOTIFY_EVENT_ALL_DISABLE: + case NOTIFY_EVENT_HOST_DISABLE: + case NOTIFY_EVENT_CLIENT_DISABLE: + case NOTIFY_EVENT_MDM_ON_OFF: + case NOTIFY_EVENT_MDM_ON_OFF_FOR_ID: + case NOTIFY_EVENT_MDM_ON_OFF_FOR_SERIAL: + ret |= (NOTIFY_EVENT_STATE | NOTIFY_EVENT_NOBLOCKING + | NOTIFY_EVENT_NOSAVE); + break; + case NOTIFY_EVENT_DRIVE_VBUS: + case NOTIFY_EVENT_LANHUB_TA: + ret |= (NOTIFY_EVENT_STATE | NOTIFY_EVENT_NOSAVE + | NOTIFY_EVENT_NEED_HOST); + break; + case NOTIFY_EVENT_SMARTDOCK_TA: + case NOTIFY_EVENT_AUDIODOCK: + case NOTIFY_EVENT_LANHUB: + case NOTIFY_EVENT_MMDOCK: + ret |= (NOTIFY_EVENT_DELAY | NOTIFY_EVENT_NEED_HOST); + fallthrough; + case NOTIFY_EVENT_CHARGER: + case NOTIFY_EVENT_NONE: + fallthrough; + default: + ret |= NOTIFY_EVENT_STATE; + break; + } + return ret; +} + +static int check_same_event_type(enum otg_notify_events event1, + enum otg_notify_events event2) +{ + return (check_event_type(event1) + == check_event_type(event2)); +} + +const char *event_string(enum otg_notify_events event) +{ + int virt; + + virt = IS_VIRTUAL(event); + event = PHY_EVENT(event); + + switch (event) { + case NOTIFY_EVENT_NONE: + return "none"; + case NOTIFY_EVENT_VBUS: + return virt ? "vbus(virtual)" : "vbus"; + case NOTIFY_EVENT_HOST: + return virt ? "host_id(virtual)" : "host_id"; + case NOTIFY_EVENT_CHARGER: + return virt ? "charger(virtual)" : "charger"; + case NOTIFY_EVENT_SMARTDOCK_TA: + return virt ? "smartdock_ta(virtual)" : "smartdock_ta"; + case NOTIFY_EVENT_SMARTDOCK_USB: + return virt ? "smartdock_usb(virtual)" : "smartdock_usb"; + case NOTIFY_EVENT_AUDIODOCK: + return virt ? "audiodock(virtual)" : "audiodock"; + case NOTIFY_EVENT_LANHUB: + return virt ? "lanhub(virtual)" : "lanhub"; + case NOTIFY_EVENT_LANHUB_TA: + return virt ? "lanhub_ta(virtual)" : "lanhub_ta"; + case NOTIFY_EVENT_MMDOCK: + return virt ? "mmdock(virtual)" : "mmdock"; + case NOTIFY_EVENT_HMT: + return virt ? "hmt(virtual)" : "hmt"; + case NOTIFY_EVENT_GAMEPAD: + return virt ? "gamepad(virtual)" : "gamepad"; + case NOTIFY_EVENT_POGO: + return virt ? "pogo(virtual)" : "pogo"; + case NOTIFY_EVENT_HOST_RELOAD: + return virt ? "host_reload(virtual)" : "host_reload"; + case NOTIFY_EVENT_DRIVE_VBUS: + return "drive_vbus"; + case NOTIFY_EVENT_ALL_DISABLE: + return "disable_all_notify"; + case NOTIFY_EVENT_HOST_DISABLE: + return "disable_host_notify"; + case NOTIFY_EVENT_CLIENT_DISABLE: + return "disable_client_notify"; + case NOTIFY_EVENT_MDM_ON_OFF: + return "mdm control_notify"; + case NOTIFY_EVENT_MDM_ON_OFF_FOR_ID: + return "mdm control_notify_for_id"; + case NOTIFY_EVENT_MDM_ON_OFF_FOR_SERIAL: + return "mdm control_notify_for_serial"; + case NOTIFY_EVENT_OVERCURRENT: + return "overcurrent"; + case NOTIFY_EVENT_VBUSPOWER: + return "vbus_power"; + case NOTIFY_EVENT_SMSC_OVC: + return "smsc_ovc"; + case NOTIFY_EVENT_SMTD_EXT_CURRENT: + return "smtd_ext_current"; + case NOTIFY_EVENT_MMD_EXT_CURRENT: + return "mmd_ext_current"; + case NOTIFY_EVENT_HMD_EXT_CURRENT: + return "hmd_ext_current"; + case NOTIFY_EVENT_DEVICE_CONNECT: + return "device_connect"; + case NOTIFY_EVENT_GAMEPAD_CONNECT: + return "gamepad_connect"; + case NOTIFY_EVENT_LANHUB_CONNECT: + return "lanhub_connect"; + case NOTIFY_EVENT_POWER_SOURCE: + return "power_role_source"; + case NOTIFY_EVENT_PD_CONTRACT: + return "pd_contract"; + case NOTIFY_EVENT_VBUS_RESET: + return "host_accessory_restart"; + case NOTIFY_EVENT_RESERVE_BOOSTER: + return "reserve_booster"; + case NOTIFY_EVENT_USB_CABLE: + return "usb_cable"; + case NOTIFY_EVENT_USBD_SUSPENDED: + return "usb_d_suspended"; + case NOTIFY_EVENT_USBD_UNCONFIGURED: + return "usb_d_unconfigured"; + case NOTIFY_EVENT_USBD_CONFIGURED: + return "usb_d_configured"; + case NOTIFY_EVENT_DR_SWAP: + return "dr_swap"; + case NOTIFY_EVENT_REVERSE_BYPASS_DEVICE_CONNECT: + return "reverse_bypass_device_connect"; + case NOTIFY_EVENT_REVERSE_BYPASS_DEVICE_ATTACH: + return "reverse_bypass_device_attach"; + default: + return "undefined"; + } +} +EXPORT_SYMBOL(event_string); + +const char *status_string(enum otg_notify_event_status status) +{ + switch (status) { + case NOTIFY_EVENT_DISABLED: + return "disabled"; + case NOTIFY_EVENT_DISABLING: + return "disabling"; + case NOTIFY_EVENT_ENABLED: + return "enabled"; + case NOTIFY_EVENT_ENABLING: + return "enabling"; + case NOTIFY_EVENT_BLOCKED: + return "blocked"; + case NOTIFY_EVENT_BLOCKING: + return "blocking"; + default: + return "undefined"; + } +} +EXPORT_SYMBOL(status_string); + +static const char *block_string(enum otg_notify_block_type type) +{ + switch (type) { + case NOTIFY_BLOCK_TYPE_NONE: + return "block_off"; + case NOTIFY_BLOCK_TYPE_HOST: + return "block_host"; + case NOTIFY_BLOCK_TYPE_CLIENT: + return "block_client"; + case NOTIFY_BLOCK_TYPE_ALL: + return "block_all"; + default: + return "undefined"; + } +} + +static int create_usb_notify(void) +{ + int ret = 0; + + if (u_notify_core) + goto err; + + u_notify_core = kzalloc(sizeof(struct usb_notify_core), GFP_KERNEL); + if (!u_notify_core) { + ret = -ENOMEM; + goto err; + } + + register_usblog_proc(); + + ret = notify_class_init(); + if (ret) { + unl_err("unable to do host_notify_class_init\n"); + goto err1; + } + + ret = usb_notify_class_init(); + if (ret) { + unl_err("unable to do usb_notify_class_init\n"); + goto err2; + } + external_notifier_init(); + + return 0; +err2: + notify_class_exit(); +err1: + unregister_usblog_proc(); + kfree(u_notify_core); +err: + return ret; +} + +static bool is_host_cable_block(struct otg_notify *n) +{ + struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify); + + if ((check_event_type(u_notify->c_type) + & NOTIFY_EVENT_NEED_HOST) && + (u_notify->c_status == NOTIFY_EVENT_BLOCKED + || u_notify->c_status == NOTIFY_EVENT_BLOCKING)) + return true; + else + return false; +} + +static bool is_host_cable_enable(struct otg_notify *n) +{ + struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify); + + if ((check_event_type(u_notify->c_type) + & NOTIFY_EVENT_NEED_HOST) && + (u_notify->c_status == NOTIFY_EVENT_ENABLED + || u_notify->c_status == NOTIFY_EVENT_ENABLING)) + return true; + else + return false; +} + +static bool is_client_cable_block(struct otg_notify *n) +{ + struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify); + + if ((check_event_type(u_notify->c_type) + & NOTIFY_EVENT_NEED_CLIENT) && + (u_notify->c_status == NOTIFY_EVENT_BLOCKED + || u_notify->c_status == NOTIFY_EVENT_BLOCKING)) + return true; + else + return false; +} + +static bool is_client_cable_enable(struct otg_notify *n) +{ + struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify); + + if ((check_event_type(u_notify->c_type) + & NOTIFY_EVENT_NEED_CLIENT) && + (u_notify->c_status == NOTIFY_EVENT_ENABLED + || u_notify->c_status == NOTIFY_EVENT_ENABLING)) + return true; + else + return false; +} + +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION +static bool is_hub_connected(struct otg_notify *n) +{ + struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify); + + return (u_notify->cond_hshub || u_notify->cond_sshub); +} +#endif + +static bool check_block_event(struct otg_notify *n, unsigned long event) +{ + struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify); + + if ((test_bit(NOTIFY_BLOCK_TYPE_HOST, &u_notify->udev.disable_state) + && (check_event_type(event) & NOTIFY_EVENT_NEED_HOST)) + || (test_bit(NOTIFY_BLOCK_TYPE_CLIENT, + &u_notify->udev.disable_state) + && (check_event_type(event) & NOTIFY_EVENT_NEED_CLIENT))) + return true; + else + return false; +} + +static void notify_event_lock_init(struct usb_notify *u_noti) +{ + spin_lock_init(&u_noti->event_spin_lock); +} + +static void notify_event_lock(struct usb_notify *u_noti, int type) +{ + if (type & NOTIFY_EVENT_STATE) + spin_lock(&u_noti->event_spin_lock); +} + +static void notify_event_unlock(struct usb_notify *u_noti, int type) +{ + if (type & NOTIFY_EVENT_STATE) + spin_unlock(&u_noti->event_spin_lock); +} + +static void enable_ovc(struct usb_notify *u_noti, int enable) +{ + u_noti->ovc_info.can_ovc = enable; +} + +static int ovc_scan_thread(void *data) +{ + struct ovc *ovcinfo = (struct ovc *)data; + struct otg_notify *o_notify = ovcinfo->o_notify; + struct usb_notify *u_notify = (struct usb_notify *)(o_notify->u_notify); + int state = 0, event = 0; + + while (!kthread_should_stop()) { + wait_event_interruptible_timeout(ovcinfo->delay_wait, + ovcinfo->thread_remove, (ovcinfo->poll_period)*HZ); + if (ovcinfo->thread_remove) + break; + mutex_lock(&ovcinfo->ovc_lock); + if (ovcinfo->check_state + && ovcinfo->data + && ovcinfo->can_ovc) { + + state = ovcinfo->check_state(ovcinfo->data); + + if (ovcinfo->prev_state != state) { + if (state == HNOTIFY_LOW) { + unl_err("%s overcurrent detected\n", + __func__); + host_state_notify(&u_notify->ndev, + NOTIFY_HOST_OVERCURRENT); + event + = NOTIFY_EXTRA_USBHOST_OVERCURRENT; + store_usblog_notify(NOTIFY_EXTRA, + (void *)&event, NULL); + } else if (state == HNOTIFY_HIGH) { + unl_info("%s vbus draw detected\n", + __func__); + host_state_notify(&u_notify->ndev, + NOTIFY_HOST_NONE); + } + } + ovcinfo->prev_state = state; + } + mutex_unlock(&ovcinfo->ovc_lock); + if (!ovcinfo->can_ovc) + ovcinfo->thread_remove = 1; + } + + unl_info("%s exit\n", __func__); +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 17, 0) + complete_and_exit(&ovcinfo->scanning_done, 0); +#else + kthread_complete_and_exit(&ovcinfo->scanning_done, 0); +#endif + return 0; +} + +void ovc_start(struct usb_notify *u_noti) +{ + struct otg_notify *o_notify = u_noti->o_notify; + + if (!u_noti->ovc_info.can_ovc) + goto skip; + + u_noti->ovc_info.prev_state = HNOTIFY_INITIAL; + u_noti->ovc_info.poll_period = (o_notify->smsc_ovc_poll_sec) ? + o_notify->smsc_ovc_poll_sec : DEFAULT_OVC_POLL_SEC; + reinit_completion(&u_noti->ovc_info.scanning_done); + u_noti->ovc_info.thread_remove = 0; + u_noti->ovc_info.th = kthread_run(ovc_scan_thread, + &u_noti->ovc_info, "ovc-scan-thread"); + if (IS_ERR(u_noti->ovc_info.th)) { + unl_err("Unable to start the ovc-scanning thread\n"); + complete(&u_noti->ovc_info.scanning_done); + } + unl_info("%s on\n", __func__); + return; +skip: + complete(&u_noti->ovc_info.scanning_done); + unl_info("%s skip\n", __func__); +} + +void ovc_stop(struct usb_notify *u_noti) +{ + u_noti->ovc_info.thread_remove = 1; + wake_up_interruptible(&u_noti->ovc_info.delay_wait); + wait_for_completion(&u_noti->ovc_info.scanning_done); + mutex_lock(&u_noti->ovc_info.ovc_lock); + u_noti->ovc_info.check_state = NULL; + u_noti->ovc_info.data = 0; + mutex_unlock(&u_noti->ovc_info.ovc_lock); + unl_info("%s\n", __func__); +} + +static void ovc_init(struct usb_notify *u_noti) +{ + init_waitqueue_head(&u_noti->ovc_info.delay_wait); + init_completion(&u_noti->ovc_info.scanning_done); + mutex_init(&u_noti->ovc_info.ovc_lock); + u_noti->ovc_info.prev_state = HNOTIFY_INITIAL; + u_noti->ovc_info.o_notify = u_noti->o_notify; + unl_info("%s\n", __func__); +} + +static irqreturn_t vbus_irq_isr(int irq, void *data) +{ + struct otg_notify *notify = (struct otg_notify *)(data); + struct usb_notify *u_notify = (struct usb_notify *)(notify->u_notify); + unsigned long flags = 0; + int gpio_value = 0; + irqreturn_t ret = IRQ_NONE; + + spin_lock_irqsave(&u_notify->v_gpio.lock, flags); + gpio_value = gpio_get_value(notify->vbus_detect_gpio); + if (u_notify->v_gpio.gpio_status != gpio_value) { + u_notify->v_gpio.gpio_status = gpio_value; + ret = IRQ_WAKE_THREAD; + } else + ret = IRQ_HANDLED; + spin_unlock_irqrestore(&u_notify->v_gpio.lock, flags); + + return ret; +} + +static irqreturn_t vbus_irq_thread(int irq, void *data) +{ + struct otg_notify *notify = (struct otg_notify *)(data); + struct usb_notify *u_notify = (struct usb_notify *)(notify->u_notify); + unsigned long flags = 0; + int gpio_value = 0, event = 0; + + spin_lock_irqsave(&u_notify->v_gpio.lock, flags); + gpio_value = u_notify->v_gpio.gpio_status; + spin_unlock_irqrestore(&u_notify->v_gpio.lock, flags); + + if (gpio_value) { + u_notify->ndev.booster = NOTIFY_POWER_ON; + unl_info("vbus on detect\n"); + if (notify->post_vbus_detect) + notify->post_vbus_detect(NOTIFY_POWER_ON); + } else { + if ((u_notify->ndev.mode == NOTIFY_HOST_MODE) + && (u_notify->ndev.booster == NOTIFY_POWER_ON + && u_notify->oc_noti)) { + host_state_notify(&u_notify->ndev, + NOTIFY_HOST_OVERCURRENT); + event = NOTIFY_EXTRA_USBHOST_OVERCURRENT; + store_usblog_notify(NOTIFY_EXTRA, + (void *)&event, NULL); + unl_err("OTG overcurrent!!!!!!\n"); + } else { + unl_info("vbus off detect\n"); + if (notify->post_vbus_detect) + notify->post_vbus_detect(NOTIFY_POWER_OFF); + } + u_notify->ndev.booster = NOTIFY_POWER_OFF; + } + return IRQ_HANDLED; +} + +int register_gpios(struct otg_notify *n) +{ + struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify); + int ret = 0; + int vbus_irq = 0; + int vbus_gpio = -1; + int redriver_gpio = -1; + + if (!gpio_is_valid(n->vbus_detect_gpio)) + goto redriver_en_gpio_phase; + + vbus_gpio = n->vbus_detect_gpio; + + spin_lock_init(&u_notify->v_gpio.lock); + + if (n->pre_gpio) + n->pre_gpio(vbus_gpio, NOTIFY_VBUS); + + ret = gpio_request(vbus_gpio, "vbus_detect_notify"); + if (ret) { + unl_err("failed to request %d\n", vbus_gpio); + goto err; + } + gpio_direction_input(vbus_gpio); + + u_notify->v_gpio.gpio_status + = gpio_get_value(vbus_gpio); + vbus_irq = gpio_to_irq(vbus_gpio); + ret = request_threaded_irq(vbus_irq, + vbus_irq_isr, + vbus_irq_thread, + (IRQF_TRIGGER_FALLING | + IRQF_TRIGGER_RISING | + IRQF_ONESHOT), + "vbus_irq_notify", + n); + if (ret) { + unl_err("Failed to register IRQ\n"); + goto err; + } + if (n->post_gpio) + n->post_gpio(vbus_gpio, NOTIFY_VBUS); + + unl_info("vbus detect gpio %d is registered.\n", vbus_gpio); +redriver_en_gpio_phase: + if (!gpio_is_valid(n->redriver_en_gpio)) + goto err; + + redriver_gpio = n->redriver_en_gpio; + + if (n->pre_gpio) + n->pre_gpio(redriver_gpio, NOTIFY_REDRIVER); + + ret = gpio_request(redriver_gpio, "usb30_redriver_en"); + if (ret) { + unl_err("failed to request %d\n", redriver_gpio); + goto err; + } + gpio_direction_output(redriver_gpio, 0); + if (n->post_gpio) + n->post_gpio(redriver_gpio, NOTIFY_REDRIVER); + + unl_info("redriver en gpio %d is registered.\n", redriver_gpio); +err: + return ret; +} + +int do_notify_blockstate(struct otg_notify *n, unsigned long event, + int type, int enable) +{ + struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify); + int ret = 0; + + switch (event) { + case NOTIFY_EVENT_NONE: + case NOTIFY_EVENT_CHARGER: + break; + case NOTIFY_EVENT_SMARTDOCK_USB: + case NOTIFY_EVENT_VBUS: +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + if (enable && (u_notify->lock_state == USB_NOTIFY_LOCK_USB_RESTRICT)) + send_usb_restrict_uevent(USB_TIME_SECURE_RESTRICTED); +#endif + if (enable) + if (n->set_chg_current) + n->set_chg_current(NOTIFY_USB_CONFIGURED); + break; + case NOTIFY_EVENT_LANHUB: + case NOTIFY_EVENT_HMT: + case NOTIFY_EVENT_HOST: + case NOTIFY_EVENT_MMDOCK: + case NOTIFY_EVENT_SMARTDOCK_TA: + case NOTIFY_EVENT_AUDIODOCK: + case NOTIFY_EVENT_GAMEPAD: + case NOTIFY_EVENT_POGO: + if (n->unsupport_host) { + unl_err("This model doesn't support usb host\n"); + goto skip; + } +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + if (enable && (u_notify->lock_state == USB_NOTIFY_LOCK_USB_RESTRICT)) + send_usb_restrict_uevent(USB_TIME_SECURE_RESTRICTED); +#endif + if (enable) + host_state_notify(&u_notify->ndev, NOTIFY_HOST_BLOCK); + else + host_state_notify(&u_notify->ndev, NOTIFY_HOST_NONE); + break; + case NOTIFY_EVENT_DRIVE_VBUS: + ret = -ESRCH; + break; + default: + break; + } + +skip: + return ret; +} + +static void update_cable_status(struct otg_notify *n, unsigned long event, + int virtual, int enable, int start) +{ + struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify); + + if (enable) { + u_notify->c_type = event; + if (check_block_event(n, event) || + (check_event_type(u_notify->c_type) + & NOTIFY_EVENT_NEED_HOST && + (n->unsupport_host || u_notify->restricted))) + u_notify->c_status = (start) ? + NOTIFY_EVENT_BLOCKING : NOTIFY_EVENT_BLOCKED; + else + u_notify->c_status = (start) ? + NOTIFY_EVENT_ENABLING : NOTIFY_EVENT_ENABLED; + } else { + if (virtual) + u_notify->c_status = (start) ? + NOTIFY_EVENT_BLOCKING : NOTIFY_EVENT_BLOCKED; + else { + u_notify->c_type = NOTIFY_EVENT_NONE; + u_notify->c_status = (start) ? + NOTIFY_EVENT_DISABLING : NOTIFY_EVENT_DISABLED; +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + if (!start) + send_usb_restrict_uevent(USB_SECURE_RELEASE); +#endif + } + } +} + +static void reserve_state_check(struct work_struct *work) +{ + struct otg_booting_delay *o_b_d = + container_of(to_delayed_work(work), + struct otg_booting_delay, booting_work); + struct usb_notify *u_noti = container_of(o_b_d, + struct usb_notify, b_delay); + int enable = 1, type = 0; + unsigned long state = 0; + + unl_info("%s +\n", __func__); + +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + /* We can wait up to two minutes. */ + wait_event_interruptible_timeout(u_noti->init_delay, + (u_noti->lock_state != USB_NOTIFY_INIT_STATE + || u_noti->b_delay.reserve_state == NOTIFY_EVENT_VBUS), + 2*60*HZ); + + unl_info("%s after wait\n", __func__); +#endif + + if (u_noti->o_notify->booting_delay_sync_usb) { + unl_info("%s wait dwc3 probe done -\n", __func__); + return; + } + + notify_event_lock(u_noti, NOTIFY_EVENT_STATE); + + u_noti->o_notify->booting_delay_sec = 0; + + state = u_noti->b_delay.reserve_state; + type = check_event_type(state); + + u_noti->b_delay.reserve_state = NOTIFY_EVENT_NONE; + unl_info("%s booting delay finished\n", __func__); + + if (state != NOTIFY_EVENT_NONE) { + unl_info("%s event=%s(%lu) enable=%d\n", __func__, + event_string(state), state, enable); + + if (type & NOTIFY_EVENT_STATE) + atomic_notifier_call_chain + (&u_noti->otg_notifier, + state, &enable); + else + ; + } + + notify_event_unlock(u_noti, NOTIFY_EVENT_STATE); + +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + if (!u_noti->skip_possible_usb) + send_external_notify(EXTERNAL_NOTIFY_POSSIBLE_USB, 1); +#else + send_external_notify(EXTERNAL_NOTIFY_POSSIBLE_USB, 1); +#endif + + unl_info("%s -\n", __func__); +} + +static void device_connect_check(struct work_struct *work) +{ + struct usb_notify *u_notify = container_of(to_delayed_work(work), + struct usb_notify, check_work); + + unl_info("%s start. is_device=%d\n", __func__, u_notify->is_device); + if (!u_notify->is_device) { + send_external_notify(EXTERNAL_NOTIFY_3S_NODEVICE, 1); + + if (u_notify->o_notify->vbus_drive) + u_notify->o_notify->vbus_drive(0); + u_notify->typec_status.power_role = HNOTIFY_SINK; + } + u_notify->check_work_complete = 1; + unl_info("%s finished\n", __func__); +} + +static int set_notify_disable(struct usb_notify_dev *udev, int disable) +{ + struct otg_notify *n = udev->o_notify; + struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify); + unsigned long usb_notify; + int usb_notify_state; + + if (!n->disable_control) { + unl_err("%s disable_control is not supported\n", __func__); + goto skip; + } + + unl_info("%s prev=%s(%d) => disable=%s(%d)\n", __func__, + block_string(u_notify->disable_state), u_notify->disable_state, + block_string(disable), disable); + + if (u_notify->disable_state == disable) { + unl_err("%s duplicated state\n", __func__); + goto skip; + } + + u_notify->disable_state = disable; + +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + switch (disable) { + case NOTIFY_BLOCK_TYPE_ALL: + send_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_EARLY, 1); + if (is_host_cable_enable(n) || + is_client_cable_enable(n)) { + unl_info("%s event=%s(%lu) disable\n", __func__, + event_string(VIRT_EVENT(u_notify->c_type)), + VIRT_EVENT(u_notify->c_type)); + send_otg_notify(n, VIRT_EVENT(u_notify->c_type), 0); + if (u_notify->lock_state == USB_NOTIFY_LOCK_USB_RESTRICT) + send_usb_restrict_uevent(USB_TIME_SECURE_RESTRICTED); + } + + send_otg_notify(n, NOTIFY_EVENT_ALL_DISABLE, 1); + + usb_notify = NOTIFY_EVENT_ALL_DISABLE; + usb_notify_state = NOTIFY_EVENT_BLOCKED; + store_usblog_notify(NOTIFY_EVENT, + (void *)&usb_notify, (void *)&usb_notify_state); + + if (n->booting_delay_sec) + u_notify->skip_possible_usb = 1; + wake_up_interruptible(&u_notify->init_delay); + break; + case NOTIFY_BLOCK_TYPE_HOST: + send_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_EARLY, 1); + if (is_host_cable_enable(n)) { + + unl_info("%s event=%s(%lu) disable\n", __func__, + event_string(VIRT_EVENT(u_notify->c_type)), + VIRT_EVENT(u_notify->c_type)); + + send_otg_notify(n, VIRT_EVENT(u_notify->c_type), 0); + } + + send_otg_notify(n, NOTIFY_EVENT_HOST_DISABLE, 1); + + usb_notify = NOTIFY_EVENT_HOST_DISABLE; + usb_notify_state = NOTIFY_EVENT_BLOCKED; + store_usblog_notify(NOTIFY_EVENT, + (void *)&usb_notify, (void *)&usb_notify_state); + + if (!is_client_cable_block(n)) + goto skip; + + unl_info("%s event=%s(%lu) enable\n", __func__, + event_string(VIRT_EVENT(u_notify->c_type)), + VIRT_EVENT(u_notify->c_type)); + + send_otg_notify(n, VIRT_EVENT(u_notify->c_type), 1); + break; + case NOTIFY_BLOCK_TYPE_CLIENT: + if (is_client_cable_enable(n)) { + + unl_info("%s event=%s(%lu) disable\n", __func__, + event_string(VIRT_EVENT(u_notify->c_type)), + VIRT_EVENT(u_notify->c_type)); + + send_otg_notify(n, VIRT_EVENT(u_notify->c_type), 0); + } + + send_otg_notify(n, NOTIFY_EVENT_CLIENT_DISABLE, 1); + + usb_notify = NOTIFY_EVENT_CLIENT_DISABLE; + usb_notify_state = NOTIFY_EVENT_BLOCKED; + store_usblog_notify(NOTIFY_EVENT, + (void *)&usb_notify, (void *)&usb_notify_state); + + if (!is_host_cable_block(n)) + goto skip; + + if (n->unsupport_host) + goto skip; + + unl_info("%s event=%s(%lu) enable\n", __func__, + event_string(VIRT_EVENT(u_notify->c_type)), + VIRT_EVENT(u_notify->c_type)); + + send_otg_notify(n, VIRT_EVENT(u_notify->c_type), 1); + break; + case NOTIFY_BLOCK_TYPE_NONE: + if (u_notify->restricted) + u_notify->restricted = 0; + send_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_EARLY, 0); + send_otg_notify(n, NOTIFY_EVENT_ALL_DISABLE, 0); + + usb_notify = NOTIFY_EVENT_ALL_DISABLE; + usb_notify_state = NOTIFY_EVENT_DISABLED; + store_usblog_notify(NOTIFY_EVENT, + (void *)&usb_notify, (void *)&usb_notify_state); + + if (!is_host_cable_block(n) && !is_client_cable_block(n)) { + if (u_notify->typec_status.power_role + == HNOTIFY_SOURCE) + send_otg_notify(n, NOTIFY_EVENT_DRIVE_VBUS, 1); + goto skip; + } + + if (check_event_type(u_notify->c_type) + & NOTIFY_EVENT_NEED_HOST && n->unsupport_host) + goto skip; + unl_info("%s event=%s(%lu) enable\n", __func__, + event_string(VIRT_EVENT(u_notify->c_type)), + VIRT_EVENT(u_notify->c_type)); + if (is_host_cable_block(n)) { + if (!n->auto_drive_vbus && + (u_notify->typec_status.power_role + == HNOTIFY_SOURCE) && + check_event_type(u_notify->c_type) + & NOTIFY_EVENT_NEED_VBUSDRIVE) { + send_otg_notify(n, NOTIFY_EVENT_DRIVE_VBUS, 1); + } + } else { + if (u_notify->typec_status.power_role + == HNOTIFY_SOURCE) + send_otg_notify(n, NOTIFY_EVENT_DRIVE_VBUS, 1); + } + send_otg_notify(n, VIRT_EVENT(u_notify->c_type), 1); + + if (u_notify->skip_possible_usb) { + send_external_notify(EXTERNAL_NOTIFY_POSSIBLE_USB, 1); + u_notify->skip_possible_usb = 0; + } + + break; + } +#else /* CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION */ + switch (disable) { + case NOTIFY_BLOCK_TYPE_ALL: + send_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_EARLY, 1); + if (is_host_cable_enable(n) || + is_client_cable_enable(n)) { + unl_info("%s event=%s(%lu) disable\n", __func__, + event_string(VIRT_EVENT(u_notify->c_type)), + VIRT_EVENT(u_notify->c_type)); + if (is_host_cable_enable(n)) { + if (!n->auto_drive_vbus && + (u_notify->typec_status.power_role + == HNOTIFY_SOURCE) && + check_event_type(u_notify->c_type) + & NOTIFY_EVENT_NEED_VBUSDRIVE) + send_otg_notify(n, + NOTIFY_EVENT_DRIVE_VBUS, 0); + } else { + if (u_notify->typec_status.power_role + == HNOTIFY_SOURCE) + send_otg_notify(n, + NOTIFY_EVENT_DRIVE_VBUS, 0); + } + send_otg_notify(n, VIRT_EVENT(u_notify->c_type), 0); + } else { + if (u_notify->typec_status.power_role + == HNOTIFY_SOURCE) + send_otg_notify(n, + NOTIFY_EVENT_DRIVE_VBUS, 0); + } + send_otg_notify(n, NOTIFY_EVENT_ALL_DISABLE, 1); + + usb_notify = NOTIFY_EVENT_ALL_DISABLE; + usb_notify_state = NOTIFY_EVENT_BLOCKED; + store_usblog_notify(NOTIFY_EVENT, + (void *)&usb_notify, (void *)&usb_notify_state); + break; + case NOTIFY_BLOCK_TYPE_HOST: + send_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_EARLY, 1); + if (is_host_cable_enable(n)) { + + unl_info("%s event=%s(%lu) disable\n", __func__, + event_string(VIRT_EVENT(u_notify->c_type)), + VIRT_EVENT(u_notify->c_type)); + + if (!n->auto_drive_vbus && + (u_notify->typec_status.power_role + == HNOTIFY_SOURCE) && + check_event_type(u_notify->c_type) + & NOTIFY_EVENT_NEED_VBUSDRIVE) + send_otg_notify(n, NOTIFY_EVENT_DRIVE_VBUS, 0); + + send_otg_notify(n, VIRT_EVENT(u_notify->c_type), 0); + } else { + if (u_notify->typec_status.power_role + == HNOTIFY_SOURCE) + send_otg_notify(n, NOTIFY_EVENT_DRIVE_VBUS, 0); + } + send_otg_notify(n, NOTIFY_EVENT_HOST_DISABLE, 1); + + usb_notify = NOTIFY_EVENT_HOST_DISABLE; + usb_notify_state = NOTIFY_EVENT_BLOCKED; + store_usblog_notify(NOTIFY_EVENT, + (void *)&usb_notify, (void *)&usb_notify_state); + + if (!is_client_cable_block(n)) + goto skip; + + unl_info("%s event=%s(%lu) enable\n", __func__, + event_string(VIRT_EVENT(u_notify->c_type)), + VIRT_EVENT(u_notify->c_type)); + + send_otg_notify(n, VIRT_EVENT(u_notify->c_type), 1); + break; + case NOTIFY_BLOCK_TYPE_CLIENT: + if (is_client_cable_enable(n)) { + + unl_info("%s event=%s(%lu) disable\n", __func__, + event_string(VIRT_EVENT(u_notify->c_type)), + VIRT_EVENT(u_notify->c_type)); + + send_otg_notify(n, VIRT_EVENT(u_notify->c_type), 0); + } + + send_otg_notify(n, NOTIFY_EVENT_CLIENT_DISABLE, 1); + + usb_notify = NOTIFY_EVENT_CLIENT_DISABLE; + usb_notify_state = NOTIFY_EVENT_BLOCKED; + store_usblog_notify(NOTIFY_EVENT, + (void *)&usb_notify, (void *)&usb_notify_state); + + if (!is_host_cable_block(n)) + goto skip; + + if (n->unsupport_host) + goto skip; + + unl_info("%s event=%s(%lu) enable\n", __func__, + event_string(VIRT_EVENT(u_notify->c_type)), + VIRT_EVENT(u_notify->c_type)); + if (!n->auto_drive_vbus && + (u_notify->typec_status.power_role + == HNOTIFY_SOURCE) && + check_event_type(u_notify->c_type) + & NOTIFY_EVENT_NEED_VBUSDRIVE) + send_otg_notify(n, NOTIFY_EVENT_DRIVE_VBUS, 1); + + send_otg_notify(n, VIRT_EVENT(u_notify->c_type), 1); + break; + case NOTIFY_BLOCK_TYPE_NONE: + send_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_EARLY, 0); + send_otg_notify(n, NOTIFY_EVENT_ALL_DISABLE, 0); + + usb_notify = NOTIFY_EVENT_ALL_DISABLE; + usb_notify_state = NOTIFY_EVENT_DISABLED; + store_usblog_notify(NOTIFY_EVENT, + (void *)&usb_notify, (void *)&usb_notify_state); + + if (!is_host_cable_block(n) && !is_client_cable_block(n)) { + if (u_notify->typec_status.power_role + == HNOTIFY_SOURCE) + send_otg_notify(n, NOTIFY_EVENT_DRIVE_VBUS, 1); + goto skip; + } + + if (check_event_type(u_notify->c_type) + & NOTIFY_EVENT_NEED_HOST && n->unsupport_host) + goto skip; + unl_info("%s event=%s(%lu) enable\n", __func__, + event_string(VIRT_EVENT(u_notify->c_type)), + VIRT_EVENT(u_notify->c_type)); + if (is_host_cable_block(n)) { + if (!n->auto_drive_vbus && + (u_notify->typec_status.power_role + == HNOTIFY_SOURCE) && + check_event_type(u_notify->c_type) + & NOTIFY_EVENT_NEED_VBUSDRIVE) + send_otg_notify(n, NOTIFY_EVENT_DRIVE_VBUS, 1); + } else { + if (u_notify->typec_status.power_role + == HNOTIFY_SOURCE) + send_otg_notify(n, NOTIFY_EVENT_DRIVE_VBUS, 1); + } + send_otg_notify(n, VIRT_EVENT(u_notify->c_type), 1); + break; + } +#endif +skip: + return 0; +} + +static void set_notify_mdm(struct usb_notify_dev *udev, int disable) +{ + struct otg_notify *n = udev->o_notify; + + switch (disable) { + case NOTIFY_MDM_TYPE_ON: + send_otg_notify(n, NOTIFY_EVENT_MDM_ON_OFF, 1); + if (is_host_cable_enable(n)) { + unl_info("%s event=%s(%d)\n", __func__, + event_string( + VIRT_EVENT(NOTIFY_EVENT_HOST_RELOAD)), + VIRT_EVENT(NOTIFY_EVENT_HOST_RELOAD)); + send_otg_notify(n, + VIRT_EVENT(NOTIFY_EVENT_HOST_RELOAD), 1); + } + break; + case NOTIFY_MDM_TYPE_OFF: + send_otg_notify(n, NOTIFY_EVENT_MDM_ON_OFF, 0); + break; + } +} + +void set_notify_mdm_for_id(struct usb_notify_dev *udev, int disable) +{ + struct otg_notify *n = udev->o_notify; + + switch (disable) { + case NOTIFY_MDM_TYPE_ON: + send_otg_notify(n, NOTIFY_EVENT_MDM_ON_OFF_FOR_ID, 1); + if (is_host_cable_enable(n)) { + unl_info("%s event=%s(%d)\n", __func__, + event_string( + VIRT_EVENT(NOTIFY_EVENT_HOST_RELOAD)), + VIRT_EVENT(NOTIFY_EVENT_HOST_RELOAD)); + send_otg_notify(n, + VIRT_EVENT(NOTIFY_EVENT_HOST_RELOAD), 1); + } + break; + case NOTIFY_MDM_TYPE_OFF: + send_otg_notify(n, NOTIFY_EVENT_MDM_ON_OFF_FOR_ID, 0); + break; + } +} + +void set_notify_mdm_for_serial(struct usb_notify_dev *udev, int disable) +{ + struct otg_notify *n = udev->o_notify; + + switch (disable) { + case NOTIFY_MDM_TYPE_ON: + send_otg_notify(n, NOTIFY_EVENT_MDM_ON_OFF_FOR_SERIAL, 1); + if (is_host_cable_enable(n)) { + unl_info("%s event=%s(%d)\n", __func__, + event_string( + VIRT_EVENT(NOTIFY_EVENT_HOST_RELOAD)), + VIRT_EVENT(NOTIFY_EVENT_HOST_RELOAD)); + send_otg_notify(n, + VIRT_EVENT(NOTIFY_EVENT_HOST_RELOAD), 1); + } + break; + case NOTIFY_MDM_TYPE_OFF: + send_otg_notify(n, NOTIFY_EVENT_MDM_ON_OFF_FOR_SERIAL, 0); + break; + } +} + +static int control_usb_maximum_speed(struct usb_notify_dev *udev, int speed) +{ + struct otg_notify *n = udev->o_notify; + int ret = 0; + + if (n->usb_maximum_speed) + ret = n->usb_maximum_speed(speed); + + return ret; +} + +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION +static int set_notify_lock_state(struct usb_notify_dev *udev) +{ + struct otg_notify *n = udev->o_notify; + struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify); + int reserve_state = u_notify->b_delay.reserve_state; + int i, noti = 0, recover = 0, reload = 0, vdm_start = 0; + + unl_info("%s +\n", __func__); + + notify_event_lock(u_notify, NOTIFY_EVENT_STATE); + + u_notify->lock_state = udev->secure_lock; + + unl_info("%s lock_state=%d\n", __func__, u_notify->lock_state); + + switch (u_notify->lock_state) { + case USB_NOTIFY_LOCK_USB_RESTRICT: + unl_info("%s lock. reserve_state(%s)\n", __func__, event_string(reserve_state)); + if (n->booting_delay_sec && reserve_state != NOTIFY_EVENT_NONE + && check_event_type(reserve_state) & NOTIFY_EVENT_STATE) + noti = 1; + break; + case USB_NOTIFY_LOCK_USB_WORK: + wake_up_interruptible(&u_notify->init_delay); + break; + case USB_NOTIFY_UNLOCK: + wake_up_interruptible(&u_notify->init_delay); + for (i = 0; i < USB_GROUP_MAX; i++) + u_notify->secure_connect_group[i] = 0; + unl_info("%s host block=%d,host enable=%d,restricted=%d,allowlist_restricted=%d\n", + __func__, + is_host_cable_block(n), is_host_cable_enable(n), + u_notify->restricted, u_notify->allowlist_restricted); + if (u_notify->restricted) + vdm_start = 1; + if (is_host_cable_block(n) && u_notify->restricted) { + u_notify->restricted = 0; + recover = 1; + } else + u_notify->restricted = 0; + unl_info("%s is_hub_connected=%d, pd contract=%d\n", + __func__, + is_hub_connected(n), get_typec_status(n, NOTIFY_EVENT_PD_CONTRACT)); + if (is_host_cable_enable(n) && !is_hub_connected(n) + && !get_typec_status(n, NOTIFY_EVENT_PD_CONTRACT) + && u_notify->allowlist_restricted) + reload = 1; + break; + default: + break; + } + + notify_event_unlock(u_notify, NOTIFY_EVENT_STATE); + + if (noti) + send_usb_restrict_uevent(USB_TIME_SECURE_RESTRICTED); + + if (recover && is_host_cable_block(n)) + send_otg_notify(n, VIRT_EVENT(u_notify->c_type), 1); + + if (reload && is_host_cable_enable(n)) + send_otg_notify(n, VIRT_EVENT(NOTIFY_EVENT_HOST_RELOAD), 1); + + if (vdm_start) { + send_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_PRE, 0); + send_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_POST, 0); + } + + unl_info("%s -\n", __func__); + return 0; +} +#else +static int set_notify_lock_state(struct usb_notify_dev *udev) +{ + struct otg_notify *n = udev->o_notify; + struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify); + int i; + + u_notify->lock_state = udev->secure_lock; + + if (udev->secure_lock) { + unl_info("%s lock\n", __func__); + } else { + for (i = 0; i < USB_GROUP_MAX; i++) + u_notify->secure_connect_group[i] = 0; + unl_info("%s unlock host cable=%d, restricted=%d\n", __func__, + is_host_cable_block(n), u_notify->restricted); + if (is_host_cable_block(n) && u_notify->restricted) { + u_notify->restricted = 0; + send_otg_notify(n, VIRT_EVENT(u_notify->c_type), 1); + } else + u_notify->restricted = 0; + } + + return 0; +} +#endif + +void send_usb_mdm_uevent(void) +{ + struct otg_notify *o_notify = get_otg_notify(); + char *envp[4]; + char *type = {"TYPE=usbmdm"}; + char *state = {"STATE=ADD"}; + char *words = {"WORDS=no_whitelist"}; + int index = 0; + + if (!o_notify) { + unl_err("%s o_notify is null\n", __func__); + goto err; + } + + envp[index++] = type; + envp[index++] = state; + + envp[index++] = words; + + envp[index++] = NULL; + + if (send_usb_notify_uevent(o_notify, envp)) { + unl_err("%s error\n", __func__); + goto err; + } + unl_info("%s\n", __func__); +err: + return; +} +EXPORT_SYMBOL(send_usb_mdm_uevent); + +void send_usb_restrict_uevent(int usb_restrict) +{ + struct otg_notify *o_notify = get_otg_notify(); + char *envp[4]; + char *type = {"TYPE=usbrestrict"}; + char *state = {"STATE=ADD"}; + char *words; + int index = 0; + + if (!o_notify) { + unl_err("%s o_notify is null\n", __func__); + goto err; + } + + envp[index++] = type; + envp[index++] = state; + + switch (usb_restrict) { + case USB_SECURE_RESTRICTED: + words = "WORDS=securerestrict"; + break; + case USB_TIME_SECURE_RESTRICTED: + words = "WORDS=timesecurerestrict"; + break; + case USB_SECURE_RELEASE: + words = "WORDS=securerelease"; + break; + default: + unl_err("%s invalid input\n", __func__); + goto err; + } + + envp[index++] = words; + + envp[index++] = NULL; + + if (send_usb_notify_uevent(o_notify, envp)) { + unl_err("%s error\n", __func__); + goto err; + } + unl_info("%s: %s(%d)\n", __func__, words, usb_restrict); +err: + return; +} +EXPORT_SYMBOL(send_usb_restrict_uevent); + +void send_usb_certi_uevent(int usb_certi) +{ + struct otg_notify *o_notify = get_otg_notify(); + char *envp[4]; + char *type = {"TYPE=usbcerti"}; + char *state = {"STATE=ADD"}; + char *words; + int index = 0; + static DEFINE_RATELIMIT_STATE(rs_warm_reset, 5 * HZ, 1); + + if (!o_notify) { + unl_err("%s o_notify is null\n", __func__); + goto err; + } + + envp[index++] = type; + envp[index++] = state; + + switch (usb_certi) { + case USB_CERTI_UNSUPPORT_ACCESSORY: + words = "WORDS=unsupport_accessory"; + break; + case USB_CERTI_NO_RESPONSE: + words = "WORDS=no_response"; + break; + case USB_CERTI_HUB_DEPTH_EXCEED: + words = "WORDS=hub_depth_exceed"; + break; + case USB_CERTI_HUB_POWER_EXCEED: + words = "WORDS=hub_power_exceed"; + break; + case USB_CERTI_HOST_RESOURCE_EXCEED: + words = "WORDS=host_resource_exceed"; + break; + case USB_CERTI_WARM_RESET: + if (!__ratelimit(&rs_warm_reset)) + goto err; + words = "WORDS=no_response"; + break; + default: + unl_err("%s invalid input\n", __func__); + goto err; + } + + envp[index++] = words; + + envp[index++] = NULL; + + if (send_usb_notify_uevent(o_notify, envp)) { + unl_err("%s error\n", __func__); + goto err; + } + unl_info("%s: %s(%d)\n", __func__, words, usb_certi); +err: + return; +} +EXPORT_SYMBOL(send_usb_certi_uevent); + +void send_usb_err_uevent(int err_type, int mode) +{ + struct otg_notify *o_notify = get_otg_notify(); + char *envp[4]; + char *type = {"TYPE=usberr"}; + char *state; + char *words; + int index = 0; + + if (!o_notify) { + unl_err("%s o_notify is null\n", __func__); + goto err; + } + + if (mode) + state = "STATE=ADD"; + else + state = "STATE=REMOVE"; + + envp[index++] = type; + envp[index++] = state; + + switch (err_type) { + case USB_ERR_ABNORMAL_RESET: + words = "WORDS=abnormal_reset"; +#if defined(CONFIG_USB_HW_PARAM) + if (mode) + inc_hw_param(o_notify, + USB_CLIENT_ANDROID_AUTO_RESET_POPUP_COUNT); +#endif + break; + default: + unl_err("%s invalid input\n", __func__); + goto err; + } + + envp[index++] = words; + envp[index++] = NULL; + + if (send_usb_notify_uevent(o_notify, envp)) { + unl_err("%s error\n", __func__); + goto err; + } + unl_info("%s: %s\n", __func__, words); +err: + return; +} +EXPORT_SYMBOL(send_usb_err_uevent); + +void send_usb_itracker_uevent(int err_type) +{ + struct otg_notify *o_notify = get_otg_notify(); + char *envp[4]; + char *type = {"TYPE=usbtracker"}; + char *state = {"STATE=ADD"}; + char *words; + int index = 0; + + if (!o_notify) { + unl_err("%s o_notify is null\n", __func__); + goto err; + } + + envp[index++] = type; + envp[index++] = state; + + switch (err_type) { + case NOTIFY_USB_CC_REPEAT: + words = "WORDS=repeat_ccirq"; + break; + default: + unl_err("%s invalid input\n", __func__); + goto err; + } + + envp[index++] = words; + envp[index++] = NULL; + + if (send_usb_notify_uevent(o_notify, envp)) { + unl_err("%s error\n", __func__); + goto err; + } + unl_info("%s: %s\n", __func__, words); +err: + return; +} +EXPORT_SYMBOL(send_usb_itracker_uevent); + +int get_class_index(int ch9_class_num) +{ + int internal_class_index; + + switch (ch9_class_num) { + case USB_CLASS_PER_INTERFACE: + internal_class_index = U_CLASS_PER_INTERFACE; + break; + case USB_CLASS_AUDIO: + internal_class_index = U_CLASS_AUDIO; + break; + case USB_CLASS_COMM: + internal_class_index = U_CLASS_COMM; + break; + case USB_CLASS_HID: + internal_class_index = U_CLASS_HID; + break; + case USB_CLASS_PHYSICAL: + internal_class_index = U_CLASS_PHYSICAL; + break; + case USB_CLASS_STILL_IMAGE: + internal_class_index = U_CLASS_STILL_IMAGE; + break; + case USB_CLASS_PRINTER: + internal_class_index = U_CLASS_PRINTER; + break; + case USB_CLASS_MASS_STORAGE: + internal_class_index = U_CLASS_MASS_STORAGE; + break; + case USB_CLASS_HUB: + internal_class_index = U_CLASS_HUB; + break; + case USB_CLASS_CDC_DATA: + internal_class_index = U_CLASS_CDC_DATA; + break; + case USB_CLASS_CSCID: + internal_class_index = U_CLASS_CSCID; + break; + case USB_CLASS_CONTENT_SEC: + internal_class_index = U_CLASS_CONTENT_SEC; + break; + case USB_CLASS_VIDEO: + internal_class_index = U_CLASS_VIDEO; + break; + case USB_CLASS_WIRELESS_CONTROLLER: + internal_class_index = U_CLASS_WIRELESS_CONTROLLER; + break; + case USB_CLASS_MISC: + internal_class_index = U_CLASS_MISC; + break; + case USB_CLASS_APP_SPEC: + internal_class_index = U_CLASS_APP_SPEC; + break; + case USB_CLASS_VENDOR_SPEC: + internal_class_index = U_CLASS_VENDOR_SPEC; + break; + default: + internal_class_index = 0; + break; + } + return internal_class_index; +} + +static bool usb_match_any_interface_for_mdm(struct usb_device *udev, + int *whitelist_array) +{ + unsigned int i; + + for (i = 0; i < udev->descriptor.bNumConfigurations; ++i) { + struct usb_host_config *cfg = &udev->config[i]; + unsigned int j; + + for (j = 0; j < cfg->desc.bNumInterfaces; ++j) { + struct usb_interface_cache *cache; + struct usb_host_interface *intf; + int intf_class; + + cache = cfg->intf_cache[j]; + if (cache->num_altsetting == 0) + continue; + + intf = &cache->altsetting[0]; + intf_class = intf->desc.bInterfaceClass; + if (!whitelist_array[get_class_index(intf_class)]) { + unl_info("%s : FAIL,%x interface, it's not in whitelist\n", + __func__, intf_class); + store_usblog_notify(NOTIFY_PORT_CLASS_BLOCK, + (void *)&udev->descriptor.bDeviceClass, + (void *)&intf_class); + return false; + } + unl_info("%s : SUCCESS,%x interface, it's in whitelist\n", + __func__, intf_class); + } + } + return true; +} + +int usb_check_whitelist_for_mdm(struct usb_device *dev) +{ + int *whitelist_array; + struct otg_notify *o_notify; + struct usb_notify *u_notify; + /* return 1 if the enumeration will be going . */ + /* return 0 if the enumeration will be skept . */ + o_notify = get_otg_notify(); + if (o_notify == NULL) { + unl_err("o_notify is NULL\n"); + return 1; + } + + u_notify = (struct usb_notify *)(o_notify->u_notify); + if (u_notify == NULL) { + unl_err("u_notify is NULL\n"); + return 1; + } + + if (u_notify->sec_whitelist_enable) { + whitelist_array = u_notify->udev.whitelist_array_for_mdm; + if (usb_match_any_interface_for_mdm(dev, whitelist_array)) { + dev_info(&dev->dev, "the device is matched with allowlist!\n"); + return 1; + } + return 0; + } + return 1; +} +EXPORT_SYMBOL(usb_check_whitelist_for_mdm); + +static bool usb_match_any_interface_for_id(struct usb_device *udev, + int *whitelist_array) +{ + int i = 0; + + unl_info("%s : New USB device found, idVendor=%04x, idProduct=%04x\n", __func__, + le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct)); + + + while (whitelist_array[i] && (i < MAX_WHITELIST_STR_LEN)) { + unl_info("%s : whitelist_array[%d]=%04x, whitelist_array[%d]=%04x\n", + __func__, i, whitelist_array[i], i+1, whitelist_array[i+1]); + if (whitelist_array[i] == le16_to_cpu(udev->descriptor.idVendor) + && whitelist_array[i+1] == le16_to_cpu(udev->descriptor.idProduct)) { + unl_info("%s : SUCCESS, it's in whitelist\n", __func__); + return true; + } + i += 2; + } + + unl_info("%s : FAIL, it's not in whitelist\n", __func__); + return false; +} + +int usb_check_whitelist_for_id(struct usb_device *dev) +{ + int *whitelist_array; + struct otg_notify *o_notify; + struct usb_notify *u_notify; + /* return 1 if the enumeration will be going . */ + /* return 0 if the enumeration will be skept . */ + o_notify = get_otg_notify(); + if (o_notify == NULL) { + unl_err("o_notify is NULL\n"); + return 1; + } + + u_notify = (struct usb_notify *)(o_notify->u_notify); + if (u_notify == NULL) { + unl_err("u_notify is NULL\n"); + return 1; + } + + if (u_notify->sec_whitelist_enable_for_id) { + whitelist_array = u_notify->udev.whitelist_array_for_mdm_for_id; + if (usb_match_any_interface_for_id(dev, whitelist_array)) { + dev_info(&dev->dev, "the device is matched with whitelist!\n"); + return 1; + } + return 0; + } + return 1; +} +EXPORT_SYMBOL(usb_check_whitelist_for_id); + +static bool usb_match_any_interface_for_serial(struct usb_device *udev, + char *whitelist_array) +{ + char *ptr_serial = NULL; + + unl_info("%s : New USB device found, SerialNumber: %s, whitelist_array: %s\n", + __func__, udev->serial, whitelist_array); + + if (!udev->serial) { + unl_info("%s : FAIL, serial is null\n", __func__); + return false; + } + + while ((ptr_serial = strsep(&whitelist_array, ":")) != NULL) { + if (!strcmp(ptr_serial, udev->serial)) { + unl_info("%s : SUCCESS, it's in whitelist\n", __func__); + return true; + } + } + + unl_info("%s : FAIL, it's not in whitelist\n", __func__); + return false; +} + +int usb_check_whitelist_for_serial(struct usb_device *dev) +{ + char whitelist_array[MAX_WHITELIST_STR_LEN]; + struct otg_notify *o_notify; + struct usb_notify *u_notify; + /* return 1 if the enumeration will be going . */ + /* return 0 if the enumeration will be skept . */ + o_notify = get_otg_notify(); + if (o_notify == NULL) { + unl_err("o_notify is NULL\n"); + return 1; + } + + u_notify = (struct usb_notify *)(o_notify->u_notify); + if (u_notify == NULL) { + unl_err("u_notify is NULL\n"); + return 1; + } + + if (u_notify->sec_whitelist_enable_for_serial) { + strncpy(whitelist_array, + u_notify->udev.whitelist_array_for_mdm_for_serial, sizeof(whitelist_array)-1); + if (usb_match_any_interface_for_serial(dev, whitelist_array)) { + dev_info(&dev->dev, "the device is matched with whitelist!\n"); + return 1; + } + return 0; + } + return 1; +} +EXPORT_SYMBOL(usb_check_whitelist_for_serial); + +int usb_check_whitelist_enable_state(void) +{ + struct otg_notify *o_notify; + struct usb_notify *u_notify; + + o_notify = get_otg_notify(); + if (o_notify == NULL) { + unl_err("o_notify is NULL\n"); + return 0; + } + + u_notify = (struct usb_notify *)(o_notify->u_notify); + if (u_notify == NULL) { + unl_err("u_notify is NULL\n"); + return 0; + } + + if (u_notify->sec_whitelist_enable_for_id && + u_notify->sec_whitelist_enable_for_serial) + return NOTIFY_MDM_ID_AND_SERIAL; + else if (u_notify->sec_whitelist_enable_for_id) + return NOTIFY_MDM_ID; + else if (u_notify->sec_whitelist_enable_for_serial) + return NOTIFY_MDM_SERIAL; + else + return NOTIFY_MDM_NONE; +} +EXPORT_SYMBOL(usb_check_whitelist_enable_state); + +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION +int usb_check_allowlist_for_lockscreen_enabled_id(struct usb_device *dev) +{ + int *allowlist_array; + struct otg_notify *o_notify; + struct usb_notify *u_notify; + int ret = 1, noti = 0; + + o_notify = get_otg_notify(); + if (o_notify == NULL) { + unl_err("o_notify is NULL\n"); + return 0; + } + + u_notify = (struct usb_notify *)(o_notify->u_notify); + if (u_notify == NULL) { + unl_err("u_notify is NULL\n"); + return 0; + } + + mutex_lock(&u_notify->udev.lockscreen_enabled_lock); + notify_event_lock(u_notify, NOTIFY_EVENT_STATE); + if (u_notify->lock_state == USB_NOTIFY_LOCK_USB_RESTRICT) { + allowlist_array = u_notify->udev.allowlist_array_lockscreen_enabled_id; + unl_info("%s allowlist : %s\n", __func__, + u_notify->udev.allowlist_str_lockscreen_enabled_id); + if (usb_match_any_interface_for_id(dev, allowlist_array)) { + unl_info("the device is matched with allowlist for lockscreen!\n"); + goto done; + } else { + unl_info("the device is unmatched with allowlist for lockscreen!\n"); + noti = 1; + if (u_notify->allowlist_restricted < MAX_VAL) + u_notify->allowlist_restricted++; + ret = 0; + } + } +done: + notify_event_unlock(u_notify, NOTIFY_EVENT_STATE); + mutex_unlock(&u_notify->udev.lockscreen_enabled_lock); + if (noti) + send_usb_restrict_uevent(USB_TIME_SECURE_RESTRICTED); + return ret; +} +EXPORT_SYMBOL(usb_check_allowlist_for_lockscreen_enabled_id); +#endif + +int usb_otg_restart_accessory(struct usb_device *dev) +{ + struct otg_notify *o_notify; + int res = 0; + + unl_info("%s\n", __func__); + o_notify = get_otg_notify(); + if (o_notify == NULL) { + unl_err("o_notify is NULL\n"); + return -1; + } + + send_otg_notify(o_notify, NOTIFY_EVENT_VBUS_RESET, 0); + return res; +} +EXPORT_SYMBOL(usb_otg_restart_accessory); + +static void otg_notify_state(struct otg_notify *n, + unsigned long event, int enable) +{ + struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify); + int type = 0; + int virtual = 0; + int status = 0; + unsigned long prev_c_type = 0; + + unl_info("%s+ event=%s(%lu), enable=%s\n", __func__, + event_string(event), event, enable == 0 ? "off" : "on"); + + prev_c_type = u_notify->c_type; + virtual = IS_VIRTUAL(event); + event = PHY_EVENT(event); + + type = check_event_type(event); + + if (virtual && enable) { + if (check_event_type(event) & NOTIFY_EVENT_NEED_HOST) { + if (!(check_event_type(u_notify->c_type) + & NOTIFY_EVENT_NEED_HOST)) { + unl_err("event skip. mismatch cable type(%s)\n", + event_string(u_notify->c_type)); + goto no_save_event; + } + } + } + + if (!(type & NOTIFY_EVENT_NOSAVE)) { + update_cable_status(n, event, virtual, enable, 1); + + store_usblog_notify(NOTIFY_EVENT, + (void *)&event, (void *)&u_notify->c_status); + + } else { + if (enable) + status = NOTIFY_EVENT_ENABLING; + else + status = NOTIFY_EVENT_DISABLING; + store_usblog_notify(NOTIFY_EVENT, + (void *)&event, (void *)&status); + } + + if (check_block_event(n, event) && + !(type & NOTIFY_EVENT_NOBLOCKING)) { + unl_err("%s usb notify is blocked. cause %s\n", __func__, + u_notify->udev.disable_state_cmd); + if (do_notify_blockstate(n, event, type, enable)) + goto no_save_event; + else + goto err; + } + + switch (event) { + case NOTIFY_EVENT_NONE: + break; + case NOTIFY_EVENT_SMARTDOCK_USB: + case NOTIFY_EVENT_VBUS: + if (enable) { + mutex_lock(&u_notify->state_lock); + u_notify->ndev.mode = NOTIFY_PERIPHERAL_MODE; + u_notify->typec_status.doing_drswap = 0; + mutex_unlock(&u_notify->state_lock); + if (n->is_wakelock) + __pm_stay_awake(&u_notify->ws); + if (gpio_is_valid(n->redriver_en_gpio)) + gpio_direction_output + (n->redriver_en_gpio, 1); + if (n->pre_peri_delay_us) + usleep_range(n->pre_peri_delay_us * 1000, + n->pre_peri_delay_us * 1000); + if (n->set_peripheral) + n->set_peripheral(true); + } else { + mutex_lock(&u_notify->state_lock); + u_notify->ndev.mode = NOTIFY_NONE_MODE; + u_notify->gadget_status.bus_state + = NOTIFY_USB_UNCONFIGURED; + mutex_unlock(&u_notify->state_lock); + if (n->set_peripheral) + n->set_peripheral(false); + if (gpio_is_valid(n->redriver_en_gpio)) + gpio_direction_output + (n->redriver_en_gpio, 0); + if (n->is_wakelock) + __pm_relax(&u_notify->ws); + } + break; + case NOTIFY_EVENT_LANHUB_TA: + u_notify->disable_v_drive = enable; + if (enable) + u_notify->oc_noti = 0; + if (n->set_lanhubta) + n->set_lanhubta(enable); + break; + case NOTIFY_EVENT_LANHUB: + if (n->unsupport_host) { + unl_err("This model doesn't support usb host\n"); + goto err; + } + u_notify->disable_v_drive = enable; + if (enable) { + u_notify->oc_noti = 0; + u_notify->ndev.mode = NOTIFY_HOST_MODE; + host_state_notify(&u_notify->ndev, NOTIFY_HOST_ADD); + if (gpio_is_valid(n->redriver_en_gpio)) + gpio_direction_output + (n->redriver_en_gpio, 1); + if (n->set_host) + n->set_host(true); + } else { + u_notify->ndev.mode = NOTIFY_NONE_MODE; + if (n->set_host) + n->set_host(false); + if (gpio_is_valid(n->redriver_en_gpio)) + gpio_direction_output + (n->redriver_en_gpio, 0); + host_state_notify(&u_notify->ndev, NOTIFY_HOST_REMOVE); + } + break; + case NOTIFY_EVENT_HMT: + case NOTIFY_EVENT_HOST: + case NOTIFY_EVENT_GAMEPAD: + if (n->unsupport_host) { + unl_err("This model doesn't support usb host\n"); + goto err; + } + u_notify->disable_v_drive = 0; + if (enable) { + if (check_same_event_type(prev_c_type, event) + && !virtual) { + unl_err("now host mode, skip this command\n"); + goto err; + } + + if (u_notify->restricted) { + send_usb_restrict_uevent(USB_SECURE_RESTRICTED); + unl_err("now restricted, skip this command\n"); + goto err; + } + + mutex_lock(&u_notify->state_lock); + u_notify->ndev.mode = NOTIFY_HOST_MODE; + u_notify->typec_status.doing_drswap = 0; + mutex_unlock(&u_notify->state_lock); + host_state_notify(&u_notify->ndev, NOTIFY_HOST_ADD); + if (gpio_is_valid(n->redriver_en_gpio)) + gpio_direction_output + (n->redriver_en_gpio, 1); + if (n->auto_drive_vbus == NOTIFY_OP_PRE) { + u_notify->oc_noti = 1; + if (n->vbus_drive) + n->vbus_drive(1); + u_notify->typec_status.power_role + = HNOTIFY_SOURCE; + } + if (n->set_host) + n->set_host(true); + if (n->auto_drive_vbus == NOTIFY_OP_POST) { + u_notify->oc_noti = 1; + if (n->vbus_drive) + n->vbus_drive(1); + u_notify->typec_status.power_role + = HNOTIFY_SOURCE; + } + if (n->auto_drive_vbus == NOTIFY_OP_OFF) { + mutex_lock(&u_notify->state_lock); + if ((u_notify->typec_status.power_role + == HNOTIFY_SOURCE) + && u_notify->reserve_vbus_booster + && !is_blocked(n, + NOTIFY_BLOCK_TYPE_HOST)) { + unl_info("reserved vbus turn on\n"); + if (n->vbus_drive) + n->vbus_drive(1); + u_notify->reserve_vbus_booster = 0; + } + mutex_unlock(&u_notify->state_lock); + } + } else { /* disable */ + u_notify->ndev.mode = NOTIFY_NONE_MODE; + if (n->auto_drive_vbus == NOTIFY_OP_POST) { + u_notify->oc_noti = 0; + if (n->vbus_drive) + n->vbus_drive(0); + u_notify->typec_status.power_role + = HNOTIFY_SINK; + } + if (n->set_host) + n->set_host(false); + if (n->auto_drive_vbus == NOTIFY_OP_PRE) { + u_notify->oc_noti = 0; + if (n->vbus_drive) + n->vbus_drive(0); + u_notify->typec_status.power_role + = HNOTIFY_SINK; + } + if (gpio_is_valid(n->redriver_en_gpio)) + gpio_direction_output + (n->redriver_en_gpio, 0); + host_state_notify(&u_notify->ndev, NOTIFY_HOST_REMOVE); +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + u_notify->allowlist_restricted = 0; +#endif + } + break; + case NOTIFY_EVENT_CHARGER: + if (n->set_charger) + n->set_charger(enable); + break; + case NOTIFY_EVENT_MMDOCK: + enable_ovc(u_notify, enable); + /* To detect overcurrent, ndev state is initialized */ + if (enable) + host_state_notify(&u_notify->ndev, + NOTIFY_HOST_NONE); + fallthrough; + case NOTIFY_EVENT_POGO: + case NOTIFY_EVENT_SMARTDOCK_TA: + case NOTIFY_EVENT_AUDIODOCK: + if (n->unsupport_host) { + unl_err("This model doesn't support usb host\n"); + goto err; + } + u_notify->disable_v_drive = enable; + if (enable) { + u_notify->ndev.mode = NOTIFY_HOST_MODE; + if (n->set_host) + n->set_host(true); + } else { + u_notify->ndev.mode = NOTIFY_NONE_MODE; + if (n->set_host) + n->set_host(false); + } + break; + case NOTIFY_EVENT_HOST_RELOAD: + if (u_notify->ndev.mode != NOTIFY_HOST_MODE) { + unl_err("mode is not host. skip host reload.\n"); + goto no_save_event; + } + if (n->unsupport_host) { + unl_err("This model doesn't support usb host\n"); + goto no_save_event; + } + if (n->set_host) { + n->set_host(false); + msleep(100); + n->set_host(true); + } + goto no_save_event; + case NOTIFY_EVENT_DRIVE_VBUS: + if (n->unsupport_host) { + unl_err("This model doesn't support usb host\n"); + goto no_save_event; + } + if (u_notify->disable_v_drive) { + unl_info("cable type=%s disable vbus draw\n", + event_string(u_notify->c_type)); + goto no_save_event; + } + u_notify->oc_noti = enable; + if (n->vbus_drive) + n->vbus_drive((bool)enable); + mutex_lock(&u_notify->state_lock); + if (n->reverse_bypass_drive && !enable) { + n->reverse_bypass_drive(0); + u_notify->reverse_bypass_status = NOTIFY_EVENT_REVERSE_BYPASS_PREPARE; + } + mutex_unlock(&u_notify->state_lock); + goto no_save_event; + case NOTIFY_EVENT_ALL_DISABLE: + if (!n->disable_control) { + unl_err("This model doesn't support disable_control\n"); + goto no_save_event; + } + if (enable) { + send_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_PRE, 1); + + set_bit(NOTIFY_BLOCK_TYPE_HOST, + &u_notify->udev.disable_state); + set_bit(NOTIFY_BLOCK_TYPE_CLIENT, + &u_notify->udev.disable_state); + + send_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_POST, 1); + } else { + send_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_PRE, 0); + + clear_bit(NOTIFY_BLOCK_TYPE_HOST, + &u_notify->udev.disable_state); + clear_bit(NOTIFY_BLOCK_TYPE_CLIENT, + &u_notify->udev.disable_state); + + send_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_POST, 0); + } + goto no_save_event; + case NOTIFY_EVENT_HOST_DISABLE: + if (!n->disable_control) { + unl_err("This model doesn't support disable_control\n"); + goto no_save_event; + } + if (enable) { + send_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_PRE, 1); + + clear_bit(NOTIFY_BLOCK_TYPE_CLIENT, + &u_notify->udev.disable_state); + set_bit(NOTIFY_BLOCK_TYPE_HOST, + &u_notify->udev.disable_state); + + send_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_POST, 1); + } + goto no_save_event; + case NOTIFY_EVENT_CLIENT_DISABLE: + if (!n->disable_control) { + unl_err("This model doesn't support disable_control\n"); + goto no_save_event; + } + if (enable) { + clear_bit(NOTIFY_BLOCK_TYPE_HOST, + &u_notify->udev.disable_state); + set_bit(NOTIFY_BLOCK_TYPE_CLIENT, + &u_notify->udev.disable_state); + } + goto no_save_event; + case NOTIFY_EVENT_MDM_ON_OFF: + unl_info("%s : mdm block enable for usb whiltelist = %d\n", + __func__, enable); + if (enable) { + send_external_notify(EXTERNAL_NOTIFY_MDMBLOCK_PRE, 1); + /*whilte list start*/ + u_notify->sec_whitelist_enable = 1; + send_external_notify(EXTERNAL_NOTIFY_MDMBLOCK_POST, 1); + } else { + /*whilte list end*/ + u_notify->sec_whitelist_enable = 0; + } + goto no_save_event; + case NOTIFY_EVENT_MDM_ON_OFF_FOR_ID: + unl_info("%s : mdm block enable for usb whiltelist = %d\n", + __func__, enable); + if (enable) { + send_external_notify(EXTERNAL_NOTIFY_MDMBLOCK_PRE, 1); + /*whilte list start*/ + u_notify->sec_whitelist_enable_for_id = 1; + send_external_notify(EXTERNAL_NOTIFY_MDMBLOCK_POST, 1); + } else { + /*whilte list end*/ + u_notify->sec_whitelist_enable_for_id = 0; + } + goto no_save_event; + case NOTIFY_EVENT_MDM_ON_OFF_FOR_SERIAL: + unl_info("%s : mdm block enable for usb whiltelist = %d\n", + __func__, enable); + if (enable) { + send_external_notify(EXTERNAL_NOTIFY_MDMBLOCK_PRE, 1); + /*whilte list start*/ + u_notify->sec_whitelist_enable_for_serial = 1; + send_external_notify(EXTERNAL_NOTIFY_MDMBLOCK_POST, 1); + } else { + /*whilte list end*/ + u_notify->sec_whitelist_enable_for_serial = 0; + } + goto no_save_event; + default: + break; + } + + if (((type & NOTIFY_EVENT_NEED_VBUSDRIVE) + && event != NOTIFY_EVENT_HOST) + || event == NOTIFY_EVENT_POGO) { + if (enable) { + if (n->device_check_sec) { + if (prev_c_type != NOTIFY_EVENT_HOST) + u_notify->is_device = 0; + u_notify->check_work_complete = 0; + schedule_delayed_work(&u_notify->check_work, + n->device_check_sec*HZ); + unl_info("%s check work start\n", __func__); + } + } else { + if (n->device_check_sec && + !u_notify->check_work_complete) { + unl_info("%s check work cancel\n", __func__); + cancel_delayed_work_sync(&u_notify->check_work); + } + u_notify->is_device = 0; + } + } + if (type & NOTIFY_EVENT_NEED_HOST) { + if (!enable) { +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + u_notify->allowlist_restricted = 0; + u_notify->cond_hshub = 0; + u_notify->cond_sshub = 0; +#endif + u_notify->is_device = 0; + unl_info("%s end host\n", __func__); + send_external_notify(EXTERNAL_NOTIFY_DEVICEADD, 0); + } + } +err: + update_cable_status(n, event, virtual, enable, 0); + +no_save_event: + unl_info("%s- event=%s, cable=%s\n", __func__, + event_string(event), + event_string(u_notify->c_type)); +} + +static void extra_notify_state(struct otg_notify *n, + unsigned long event, int enable) +{ + struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify); + int status = 0; + + unl_info("%s+ event=%s(%lu), enable=%s\n", __func__, + event_string(event), event, enable == 0 ? "off" : "on"); + + switch (event) { + case NOTIFY_EVENT_NONE: + break; + case NOTIFY_EVENT_OVERCURRENT: + if (!u_notify->ndev.dev) { + unl_err("ndev is NULL. Maybe usb host is not supported.\n"); + break; + } + host_state_notify(&u_notify->ndev, + NOTIFY_HOST_OVERCURRENT); + unl_err("OTG overcurrent!!!!!!\n"); + status = NOTIFY_EXTRA_USBHOST_OVERCURRENT; + store_usblog_notify(NOTIFY_EXTRA, + (void *)&status, NULL); + break; + case NOTIFY_EVENT_VBUSPOWER: + if (enable) { + u_notify->ndev.booster = NOTIFY_POWER_ON; + status = NOTIFY_EVENT_ENABLED; + } else { + u_notify->ndev.booster = NOTIFY_POWER_OFF; + status = NOTIFY_EVENT_DISABLED; + } + + store_usblog_notify(NOTIFY_EVENT, + (void *)&event, (void *)&status); + break; + case NOTIFY_EVENT_SMSC_OVC: + if (enable) + ovc_start(u_notify); + else + ovc_stop(u_notify); + break; + case NOTIFY_EVENT_SMTD_EXT_CURRENT: + if (u_notify->c_type != NOTIFY_EVENT_SMARTDOCK_TA) { + unl_err("No smart dock!!!!!!\n"); + break; + } + if (n->set_battcall) + n->set_battcall + (NOTIFY_EVENT_SMTD_EXT_CURRENT, enable); + break; + case NOTIFY_EVENT_MMD_EXT_CURRENT: + if (u_notify->c_type != NOTIFY_EVENT_MMDOCK) { + unl_err("No mmdock!!!!!!\n"); + break; + } + if (n->set_battcall) + n->set_battcall + (NOTIFY_EVENT_MMD_EXT_CURRENT, enable); + break; + case NOTIFY_EVENT_HMD_EXT_CURRENT: + if (n->set_battcall) + n->set_battcall + (NOTIFY_EVENT_HMD_EXT_CURRENT, enable); + break; + case NOTIFY_EVENT_DEVICE_CONNECT: + if (enable) { + if (!u_notify->is_device) { + u_notify->is_device = 1; + send_external_notify(EXTERNAL_NOTIFY_DEVICEADD, 1); + } + } + if ((u_notify->lock_state == USB_NOTIFY_LOCK_USB_WORK) + || u_notify->lock_state == USB_NOTIFY_LOCK_USB_RESTRICT) { + if (!enable) + detect_illegal_condition(NOTIFY_EVENT_SECURE_DISCONNECTION); + } + break; + case NOTIFY_EVENT_GAMEPAD_CONNECT: + if (u_notify->c_type == NOTIFY_EVENT_HOST || + u_notify->c_type == NOTIFY_EVENT_GAMEPAD) + send_external_notify(EXTERNAL_NOTIFY_DEVICE_CONNECT, + EXTERNAL_NOTIFY_GPAD); + break; + case NOTIFY_EVENT_LANHUB_CONNECT: + if (u_notify->c_type == NOTIFY_EVENT_HOST || + u_notify->c_type == NOTIFY_EVENT_LANHUB) + send_external_notify(EXTERNAL_NOTIFY_DEVICE_CONNECT, + EXTERNAL_NOTIFY_LANHUB); + break; + case NOTIFY_EVENT_REVERSE_BYPASS_DEVICE_CONNECT: + mutex_lock(&u_notify->state_lock); + if (n->reverse_bypass_drive && + u_notify->reverse_bypass_status == NOTIFY_EVENT_REVERSE_BYPASS_PREPARE) { + u_notify->reverse_bypass_status = NOTIFY_EVENT_REVERSE_BYPASS_ON; + n->reverse_bypass_drive(1); + } + mutex_unlock(&u_notify->state_lock); + break; + case NOTIFY_EVENT_REVERSE_BYPASS_DEVICE_ATTACH: + mutex_lock(&u_notify->state_lock); + if (enable) { + u_notify->reverse_bypass_status = NOTIFY_EVENT_REVERSE_BYPASS_PREPARE; + } else { + u_notify->reverse_bypass_status = NOTIFY_EVENT_REVERSE_BYPASS_OFF; + if (n->reverse_bypass_drive) + n->reverse_bypass_drive(0); + } + mutex_unlock(&u_notify->state_lock); + break; + case NOTIFY_EVENT_POWER_SOURCE: + if (enable) { + u_notify->typec_status.power_role = HNOTIFY_SOURCE; + host_state_notify(&u_notify->ndev, + NOTIFY_HOST_SOURCE); + } else { + u_notify->typec_status.power_role = HNOTIFY_SINK; + host_state_notify(&u_notify->ndev, + NOTIFY_HOST_SINK); + } + send_external_notify(EXTERNAL_NOTIFY_POWERROLE, + u_notify->typec_status.power_role); + break; + case NOTIFY_EVENT_PD_CONTRACT: + if (enable) + u_notify->typec_status.pd = enable; + else + u_notify->typec_status.pd = 0; + break; + case NOTIFY_EVENT_VBUS_RESET: + send_external_notify(EXTERNAL_NOTIFY_VBUS_RESET, 0); + break; + case NOTIFY_EVENT_RESERVE_BOOSTER: + mutex_lock(&u_notify->state_lock); + if (enable) + u_notify->reserve_vbus_booster = 1; + else + u_notify->reserve_vbus_booster = 0; + mutex_unlock(&u_notify->state_lock); + break; + case NOTIFY_EVENT_USB_CABLE: + mutex_lock(&u_notify->state_lock); + if (enable) + u_notify->gadget_status.usb_cable_connect = 1; + else + u_notify->gadget_status.usb_cable_connect = 0; + + if (u_notify->ndev.mode == NOTIFY_PERIPHERAL_MODE + && !u_notify->typec_status.doing_drswap) { + if ((u_notify->gadget_status.bus_state + == NOTIFY_USB_SUSPENDED) + && u_notify->gadget_status.usb_cable_connect) { + if (n->set_chg_current) + n->set_chg_current + (NOTIFY_USB_SUSPENDED); + } + } + mutex_unlock(&u_notify->state_lock); + break; + case NOTIFY_EVENT_USBD_SUSPENDED: + mutex_lock(&u_notify->state_lock); + if (u_notify->ndev.mode == NOTIFY_PERIPHERAL_MODE + && !u_notify->typec_status.doing_drswap) { + u_notify->gadget_status.bus_state + = NOTIFY_USB_SUSPENDED; + if (u_notify->gadget_status.usb_cable_connect + && u_notify->typec_status.power_role != HNOTIFY_SOURCE) { + if (n->set_chg_current) + n->set_chg_current + (NOTIFY_USB_SUSPENDED); + } + } + mutex_unlock(&u_notify->state_lock); + break; + case NOTIFY_EVENT_USBD_UNCONFIGURED: + mutex_lock(&u_notify->state_lock); + if (u_notify->ndev.mode == NOTIFY_PERIPHERAL_MODE) + u_notify->gadget_status.bus_state + = NOTIFY_USB_UNCONFIGURED; + mutex_unlock(&u_notify->state_lock); + break; + case NOTIFY_EVENT_USBD_CONFIGURED: + mutex_lock(&u_notify->state_lock); + if (u_notify->ndev.mode == NOTIFY_PERIPHERAL_MODE) + u_notify->gadget_status.bus_state + = NOTIFY_USB_CONFIGURED; + mutex_unlock(&u_notify->state_lock); + break; + case NOTIFY_EVENT_DR_SWAP: + mutex_lock(&u_notify->state_lock); + if (enable) + u_notify->typec_status.doing_drswap = 1; + else + u_notify->typec_status.doing_drswap = 0; + mutex_unlock(&u_notify->state_lock); + break; + default: + break; + } + unl_info("%s- event=%s(%lu), cable=%s\n", __func__, + event_string(event), event, + event_string(u_notify->c_type)); +} + +static void otg_notify_work(struct work_struct *data) +{ + struct otg_state_work *state_work = + container_of(data, struct otg_state_work, otg_work); + + otg_notify_state(state_work->o_notify, + state_work->event, state_work->enable); + + kfree(state_work); +} + +static int otg_notifier_callback(struct notifier_block *nb, + unsigned long event, void *param) +{ + struct usb_notify *u_noti = container_of(nb, + struct usb_notify, otg_nb); + struct otg_notify *n = NULL; + struct otg_state_work *state_work = NULL; + + n = u_noti->o_notify; + + unl_info("%s event=%s(%lu)\n", __func__, + event_string(event), event); + + if (event > VIRT_EVENT(NOTIFY_EVENT_VBUSPOWER)) { + unl_err("%s event is invalid\n", __func__); + return NOTIFY_DONE; + } + + state_work = kmalloc(sizeof(struct otg_state_work), GFP_ATOMIC); + if (!state_work) + return notifier_from_errno(-ENOMEM); + + INIT_WORK(&state_work->otg_work, otg_notify_work); + state_work->o_notify = n; + state_work->event = event; + state_work->enable = *(int *)param; + queue_work(u_noti->notifier_wq, &state_work->otg_work); + return NOTIFY_OK; +} + +static int extra_notifier_callback(struct notifier_block *nb, + unsigned long event, void *param) +{ + struct usb_notify *u_noti = container_of(nb, + struct usb_notify, extra_nb); + struct otg_notify *n = NULL; + + n = u_noti->o_notify; + + unl_info("%s event=%s(%lu)\n", __func__, + event_string(event), event); + + if (event > VIRT_EVENT(NOTIFY_EVENT_VBUSPOWER)) { + unl_err("%s event is invalid\n", __func__); + return NOTIFY_DONE; + } + + extra_notify_state(n, event, *(int *)param); + + return NOTIFY_OK; +} + +void send_otg_notify(struct otg_notify *n, + unsigned long event, int enable) +{ + struct usb_notify *u_notify = NULL; + int type = 0; +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + int noti = 0; +#endif + + if (!n) { + unl_err("%s otg_notify is null\n", __func__); + goto end; + } + + u_notify = (struct usb_notify *)(n->u_notify); + if (!u_notify) { + unl_err("%s u_notify structure is null\n", __func__); + goto end; + } + unl_info("%s event=%s(%lu) enable=%d\n", __func__, + event_string(event), event, enable); + + type = check_event_type(event); + + notify_event_lock(u_notify, type); + + if ((type & NOTIFY_EVENT_DELAY) && (type & NOTIFY_EVENT_STATE)) { + if (n->booting_delay_sec) { + if (u_notify) { + u_notify->b_delay.reserve_state = + (enable) ? event : NOTIFY_EVENT_NONE; +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + if (enable && (check_event_type(event) & NOTIFY_EVENT_NEED_CLIENT)) + wake_up_interruptible(&u_notify->init_delay); + + if (u_notify->lock_state == USB_NOTIFY_LOCK_USB_RESTRICT) + noti = 1; +#endif + unl_info("%s reserve event\n", __func__); + } else + unl_err("%s u_notify is null\n", __func__); + goto before_unlock; + } + } + + if (type & NOTIFY_EVENT_EXTRA) + blocking_notifier_call_chain + (&u_notify->extra_notifier, event, &enable); + else if (type & NOTIFY_EVENT_STATE) + atomic_notifier_call_chain + (&u_notify->otg_notifier, event, &enable); + else + goto before_unlock; + +before_unlock: + notify_event_unlock(u_notify, type); +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + if (noti) { + if (enable) + send_usb_restrict_uevent(USB_TIME_SECURE_RESTRICTED); + else + send_usb_restrict_uevent(USB_SECURE_RELEASE); + } +#endif +end: + return; +} +EXPORT_SYMBOL(send_otg_notify); + +int get_typec_status(struct otg_notify *n, int event) +{ + struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify); + int ret = -ENODEV; + + if (u_notify == NULL) { + unl_err("u_notify is NULL\n"); + goto end; + } + + if (event == NOTIFY_EVENT_POWER_SOURCE) { + /* SINK == 0, SOURCE == 1 */ + ret = u_notify->typec_status.power_role; + } else + ret = u_notify->typec_status.pd; +end: + return ret; +} +EXPORT_SYMBOL(get_typec_status); + +struct otg_booster *find_get_booster(struct otg_notify *n) +{ + struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify); + int ret = 0; + + if (!u_notify) { + unl_err("%s u_notify structure is null\n", __func__); + goto err; + } + + if (!u_notify_core) { + ret = create_usb_notify(); + if (ret) { + unl_err("unable create_usb_notify\n"); + goto err; + } + } + + if (!u_notify->booster) { + unl_err("error. No matching booster\n"); + goto err; + } + + return u_notify->booster; +err: + return NULL; +} +EXPORT_SYMBOL(find_get_booster); + +int register_booster(struct otg_notify *n, struct otg_booster *b) +{ + struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify); + int ret = 0; + + if (!u_notify) { + unl_err("%s u_notify structure is null\n", __func__); + goto err; + } + + u_notify->booster = b; +err: + return ret; +} +EXPORT_SYMBOL(register_booster); + +int register_ovc_func(struct otg_notify *n, + int (*check_state)(void *), void *data) +{ + struct usb_notify *u_notify; + int ret = 0; + + if (!n) { + unl_err("%s otg_notify is null\n", __func__); + return -ENODEV; + } + + u_notify = (struct usb_notify *)(n->u_notify); + if (!u_notify) { + unl_err("%s u_notify structure is null\n", __func__); + return -EFAULT; + } + + mutex_lock(&u_notify->ovc_info.ovc_lock); + u_notify->ovc_info.check_state = check_state; + u_notify->ovc_info.data = data; + mutex_unlock(&u_notify->ovc_info.ovc_lock); + unl_info("%s\n", __func__); + return ret; +} +EXPORT_SYMBOL(register_ovc_func); + +int get_booster(struct otg_notify *n) +{ + struct usb_notify *u_notify; + int ret = 0; + + if (!n) { + unl_err("%s otg_notify is null\n", __func__); + return -ENODEV; + } + + u_notify = (struct usb_notify *)(n->u_notify); + if (!u_notify) { + unl_err("%s u_notify structure is null\n", __func__); + return NOTIFY_NONE_MODE; + } + + if (!u_notify_core) { + ret = create_usb_notify(); + if (ret) { + unl_err("unable create_usb_notify\n"); + return -EFAULT; + } + } + unl_info("%s usb booster=%d\n", __func__, u_notify->ndev.booster); + ret = u_notify->ndev.booster; + return ret; +} +EXPORT_SYMBOL(get_booster); + +int get_usb_mode(struct otg_notify *n) +{ + struct usb_notify *u_notify; + int ret = 0; + + if (!n) { + unl_err("%s otg_notify is null\n", __func__); + return -ENODEV; + } + + u_notify = (struct usb_notify *)(n->u_notify); + if (!u_notify) { + unl_err("%s u_notify structure is null\n", __func__); + return NOTIFY_NONE_MODE; + } + + if (!u_notify_core) { + ret = create_usb_notify(); + if (ret) { + unl_err("unable create_usb_notify\n"); + return -EFAULT; + } + } + unl_info("%s usb mode=%d\n", __func__, u_notify->ndev.mode); + ret = u_notify->ndev.mode; + return ret; +} +EXPORT_SYMBOL(get_usb_mode); + +unsigned long get_cable_type(struct otg_notify *n) +{ + struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify); + unsigned long ret = 0; + int noti_ret = 0; + + if (!u_notify) { + unl_err("%s u_notify structure is null\n", __func__); + return NOTIFY_EVENT_NONE; + } + + if (!u_notify_core) { + noti_ret = create_usb_notify(); + if (noti_ret) { + unl_err("unable create_usb_notify\n"); + return NOTIFY_EVENT_NONE; + } + } + unl_info("%s cable type =%s\n", __func__, + event_string(u_notify->c_type)); + ret = u_notify->c_type; + return ret; +} +EXPORT_SYMBOL(get_cable_type); + +int is_usb_host(struct otg_notify *n) +{ + struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify); + int ret = 0; + int noti_ret = 0; + + if (!u_notify) { + unl_err("%s u_notify structure is null\n", __func__); + return NOTIFY_EVENT_NONE; + } + + if (!u_notify_core) { + noti_ret = create_usb_notify(); + if (noti_ret) { + unl_err("unable create_usb_notify\n"); + return NOTIFY_EVENT_NONE; + } + } + + if (n->unsupport_host || !IS_ENABLED(CONFIG_USB_HOST_NOTIFY)) + ret = 0; + else + ret = 1; + + unl_info("%s %d\n", __func__, ret); + return ret; +} +EXPORT_SYMBOL(is_usb_host); + +bool is_blocked(struct otg_notify *n, int type) +{ + struct usb_notify *u_notify = NULL; + int ret = 0; + + if (!n) { + unl_err("%s otg_notify is null\n", __func__); + goto end; + } + u_notify = (struct usb_notify *)(n->u_notify); + + if (!u_notify) { + unl_err("%s u_notify structure is null\n", __func__); + goto end; + } + + if (!u_notify_core) { + ret = create_usb_notify(); + if (ret) { + unl_err("unable create_usb_notify\n"); + goto end; + } + } + unl_info("%s type=%d, disable_state=%lu\n", + __func__, type, u_notify->udev.disable_state); + + if (type == NOTIFY_BLOCK_TYPE_HOST) { + if (test_bit(NOTIFY_BLOCK_TYPE_HOST, + &u_notify->udev.disable_state)) + goto end2; + } else if (type == NOTIFY_BLOCK_TYPE_CLIENT) { + if (test_bit(NOTIFY_BLOCK_TYPE_CLIENT, + &u_notify->udev.disable_state)) + goto end2; + } else if (type == NOTIFY_BLOCK_TYPE_ALL) { + if (test_bit(NOTIFY_BLOCK_TYPE_HOST, + &u_notify->udev.disable_state) && + test_bit(NOTIFY_BLOCK_TYPE_CLIENT, + &u_notify->udev.disable_state)) + goto end2; + } + +end: + return false; +end2: + return true; +} +EXPORT_SYMBOL(is_blocked); + +bool is_snkdfp_usb_device_connected(struct otg_notify *n) +{ + struct usb_notify *u_notify = NULL; + + if (!n) { + unl_err("%s otg_notify is null\n", __func__); + return false; + } + u_notify = (struct usb_notify *)(n->u_notify); + + if (!u_notify) { + unl_err("%s u_notify structure is null\n", __func__); + return false; + } + + unl_info("%s is_device = %d, power_role = %d\n", + __func__, u_notify->is_device, + u_notify->typec_status.power_role); + if (u_notify->is_device && + u_notify->typec_status.power_role == HNOTIFY_SINK) { + return true; + } + + return false; +} +EXPORT_SYMBOL(is_snkdfp_usb_device_connected); + +int get_con_dev_max_speed(struct otg_notify *n) +{ + struct usb_notify *u_notify = NULL; + + if (!n) { + unl_err("%s otg_notify is null\n", __func__); + return false; + } + u_notify = (struct usb_notify *)(n->u_notify); + + if (!u_notify) { + unl_err("%s u_notify structure is null\n", __func__); + return false; + } + + unl_info("%s device max speed=%s\n", __func__, + usb_speed_string(u_notify->cond_max_speed)); + return u_notify->cond_max_speed; +} +EXPORT_SYMBOL(get_con_dev_max_speed); + +void set_con_dev_max_speed(struct otg_notify *n, int speed) +{ + struct usb_notify *u_notify = NULL; + + if (!n) { + unl_err("%s otg_notify is null\n", __func__); + return; + } + u_notify = (struct usb_notify *)(n->u_notify); + + if (!u_notify) { + unl_err("%s u_notify structure is null\n", __func__); + return; + } + + u_notify->cond_max_speed = speed; + + unl_info("%s device max speed=%s\n", __func__, + usb_speed_string(speed)); +} +EXPORT_SYMBOL(set_con_dev_max_speed); + +void set_con_dev_hub(struct otg_notify *n, int speed, int conn) +{ + struct usb_notify *u_notify = NULL; + + if (!n) { + unl_err("%s otg_notify is null\n", __func__); + return; + } + + u_notify = (struct usb_notify *)(n->u_notify); + + if (!u_notify) { + unl_err("%s u_notify structure is null\n", __func__); + return; + } + + if (speed >= USB_SPEED_SUPER) { + u_notify->cond_sshub = !!conn; + } else if (speed > USB_SPEED_UNKNOWN + && speed != USB_SPEED_WIRELESS) { + u_notify->cond_hshub = !!conn; + } else ; + + unl_info("%s speed(%s), conn(%d)\n", __func__, + usb_speed_string(speed), conn); +} +EXPORT_SYMBOL(set_con_dev_hub); + +void set_request_action(struct otg_notify *n, unsigned int request_action) +{ + struct usb_notify *u_notify = NULL; + + if (!n) { + unl_err("%s o_notify is null\n", __func__); + goto err; + } + u_notify = (struct usb_notify *)(n->u_notify); + + if (!u_notify) { + unl_err("%s u_notify structure is null\n", + __func__); + goto err; + } + + unl_info("%s prev action = %u set action as=%u\n", + __func__, u_notify->udev.request_action, request_action); + + u_notify->udev.request_action = request_action; +err: + return; +} +EXPORT_SYMBOL(set_request_action); + +struct dev_table { + struct usb_device_id dev; + int index; +}; + +static struct dev_table known_usbaudio_device_table[] = { + { .dev = { USB_DEVICE(0x04e8, 0xa051), }, + }, + { .dev = { USB_DEVICE(0x04e8, 0xa054), }, + }, + { .dev = { USB_DEVICE(0x04e8, 0xa05b), }, + }, + { .dev = { USB_DEVICE(0x04e8, 0xa058), }, + }, + { .dev = { USB_DEVICE(0x04e8, 0xa057), }, + }, + { .dev = { USB_DEVICE(0x04e8, 0xa059), }, + }, + { .dev = { USB_DEVICE(0x04e8, 0xa05e), }, + }, + {} +}; + +static struct dev_table reverse_bypass_device_table[] = { + { .dev = { USB_DEVICE(0x04e8, 0xa051), }, + }, /* The device for reverse bypass */ + {} +}; + +static int check_audio_id(struct usb_device *dev) +{ + struct dev_table *id; + int ret = 0; + + /* check VID, PID */ + for (id = known_usbaudio_device_table; id->dev.match_flags; id++) { + if ((id->dev.match_flags & USB_DEVICE_ID_MATCH_VENDOR) && + (id->dev.match_flags & USB_DEVICE_ID_MATCH_PRODUCT) && + id->dev.idVendor == le16_to_cpu(dev->descriptor.idVendor) && + id->dev.idProduct == le16_to_cpu(dev->descriptor.idProduct)) { + ret = 1; + break; + } + } + if (ret) + unl_info("%s find\n", __func__); + + return ret; +} + +static int check_audio_descriptor(struct usb_device *dev) +{ + struct usb_interface *intf; + struct usb_host_interface *alts; + struct usb_endpoint_descriptor *endpt; + unsigned int i, j; + int ret = 0; + __u8 play_intf = 0, cap_intf = 0; + __u8 aud_con_cnt = 0, out_ep = 0, in_ep = 0; + +/* 1. check samsung vid */ + if (le16_to_cpu(dev->descriptor.idVendor) != 0x04e8) + goto done; + +/* 2. If set config is not execute, return false */ + if (!dev->actconfig) { + unl_info("%s no set config\n", __func__); + goto done; + } + + for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) { + intf = dev->actconfig->interface[i]; + alts = intf->cur_altsetting; + + if (alts->desc.bInterfaceClass == USB_CLASS_AUDIO) { + if (alts->desc.bInterfaceSubClass + == USB_SUBCLASS_AUDIOCONTROL) + aud_con_cnt++; + if (alts->desc.bInterfaceSubClass + != USB_SUBCLASS_AUDIOSTREAMING && + alts->desc.bInterfaceSubClass + != USB_CLASS_VENDOR_SPEC) + continue; + + out_ep = 0; + in_ep = 0; + for (j = 0; j < intf->num_altsetting; j++) { + alts = &intf->altsetting[j]; + + if (alts->desc.bNumEndpoints < 1) + continue; + + endpt = &alts->endpoint[0].desc; + /* + * If there is endpoint[1], + * it will be sync endpoint(feedback). + */ + + if (!endpt) + continue; + + if (endpt->bEndpointAddress & USB_DIR_IN) { + if (!in_ep) + in_ep = endpt->bEndpointAddress; + else if (in_ep != + endpt->bEndpointAddress) { + unl_info("%s in_ep 2 or more\n", + __func__); + goto done; + } else + continue; + } else { + if (!out_ep) + out_ep = + endpt->bEndpointAddress; + else if (out_ep != + endpt->bEndpointAddress) { + unl_info("%s out_ep 2 or more\n", + __func__); + goto done; + } else + continue; + } + } + if (out_ep) + play_intf++; + else if (in_ep) + cap_intf++; + else { + unl_err("%s no ep\n", __func__); + goto done; + } + } + } +/* 3. final check. AUDIOCONTROL 1. playback 1. capture 1 */ + if (aud_con_cnt == 1 && play_intf == 1 && cap_intf == 1) + ret = 1; +done: + if (aud_con_cnt) + unl_info("%s ret=%d,aud_con_cnt=%d,play_intf=%d,cap_intf=%d\n", + __func__, ret, aud_con_cnt, play_intf, cap_intf); + return ret; +} + +int is_known_usbaudio(struct usb_device *dev) +{ + int ret = 0; + + ret = check_audio_id(dev); + if (ret) + goto done; + + ret = check_audio_descriptor(dev); + if (ret) + goto done; + +done: + return ret; +} +EXPORT_SYMBOL(is_known_usbaudio); + +#define MAX_C_D_L (2048) +int check_usbaudio(struct usb_device *dev) +{ + struct otg_notify *o_notify = get_otg_notify(); + struct usb_notify *u_notify = NULL; + struct usb_interface *intf; + struct usb_host_interface *alts; + unsigned int i; + int ret = 0; + u16 total_length; + + if (!o_notify) { + unl_err("%s o_notify is null\n", __func__); + goto done; + } + u_notify = (struct usb_notify *)(o_notify->u_notify); + + if (!u_notify) { + unl_err("%s u_notify structure is null\n", + __func__); + goto done; + } + + if (u_notify->lock_state == USB_NOTIFY_UNLOCK) + goto done; + + if (!dev->actconfig) { + unl_info("%s no set config\n", __func__); + goto done; + } + + for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) { + intf = dev->actconfig->interface[i]; + alts = intf->cur_altsetting; + + if (alts->desc.bInterfaceClass == USB_CLASS_AUDIO) { + total_length = le16_to_cpu(dev->actconfig->desc.wTotalLength); + if (total_length > MAX_C_D_L) { + unl_info("%s total_length %u\n", __func__, total_length); + detect_illegal_condition(NOTIFY_EVENT_AUDIO_DESCRIPTOR); + ret = -EACCES; + break; + } + } + } +done: + return ret; +} +EXPORT_SYMBOL(check_usbaudio); + +int check_usbgroup(struct usb_device *dev) +{ + struct otg_notify *o_notify = get_otg_notify(); + struct usb_notify *u_notify = NULL; + struct usb_interface *intf; + struct usb_host_interface *alts; + struct usb_device *hdev; + unsigned int i; + int ret = 0; + bool is_audio_group = false; + + if (!o_notify) { + unl_err("%s o_notify is null\n", __func__); + goto done; + } + u_notify = (struct usb_notify *)(o_notify->u_notify); + + if (!u_notify) { + unl_err("%s u_notify structure is null\n", + __func__); + goto done; + } + + hdev = dev->parent; + if (!hdev) { + unl_err("%s root hub is not counted\n", + __func__); + goto done; + } + + if (u_notify->lock_state == USB_NOTIFY_UNLOCK) + goto done; + + if (!dev->actconfig) { + unl_info("%s no set config\n", __func__); + goto done; + } + + for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) { + intf = dev->actconfig->interface[i]; + alts = intf->cur_altsetting; + + if (alts->desc.bInterfaceClass == USB_CLASS_AUDIO) { + is_audio_group = true; + break; + } + } + + if (is_audio_group) { + if (u_notify->secure_connect_group[USB_GROUP_AUDIO] < MAX_VAL) + u_notify->secure_connect_group[USB_GROUP_AUDIO]++; + } else { + if (u_notify->secure_connect_group[USB_GROUP_OTEHR] < MAX_VAL) + u_notify->secure_connect_group[USB_GROUP_OTEHR]++; + } + + unl_info("%s current audio_cnt=%d, other_cnt=%d\n", __func__, + u_notify->secure_connect_group[USB_GROUP_AUDIO], + u_notify->secure_connect_group[USB_GROUP_OTEHR]); + +done: + return ret; +} +EXPORT_SYMBOL(check_usbgroup); + +int is_usbhub(struct usb_device *dev) +{ + struct otg_notify *o_notify = get_otg_notify(); + struct usb_notify *u_notify = NULL; + struct usb_interface *intf; + struct usb_host_interface *alts; + unsigned int i; + int ret = 0; + + if (!o_notify) { + unl_err("%s o_notify is null\n", __func__); + goto done; + } + u_notify = (struct usb_notify *)(o_notify->u_notify); + + if (!u_notify) { + unl_err("%s u_notify structure is null\n", + __func__); + goto done; + } + + if (!dev->actconfig) { + unl_info("%s no set config\n", __func__); + goto done; + } + + for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) { + intf = dev->actconfig->interface[i]; + alts = intf->cur_altsetting; + + if (alts->desc.bInterfaceClass == USB_CLASS_HUB) { + ret = 1; + break; + } + } +done: + return ret; +} +EXPORT_SYMBOL(is_usbhub); + +int disconnect_unauthorized_device(struct usb_device *dev) +{ + struct otg_notify *o_notify = get_otg_notify(); + struct usb_notify *u_notify = NULL; + int ret = 0; + + if (!o_notify) { + unl_err("%s o_notify is null\n", __func__); + goto done; + } + u_notify = (struct usb_notify *)(o_notify->u_notify); + + if (!u_notify) { + unl_err("%s u_notify structure is null\n", + __func__); + goto done; + } + + if (u_notify->allowlist_restricted) { + u_notify->allowlist_restricted--; + if (u_notify->allowlist_restricted == 0) + send_usb_restrict_uevent(USB_SECURE_RELEASE); + } + unl_info("%s allowlist_restricted(%d)\n", __func__, u_notify->allowlist_restricted); +done: + return ret; +} +EXPORT_SYMBOL(disconnect_unauthorized_device); + +void set_usb_audio_cardnum(int card_num, int bundle, int attach) +{ + struct otg_notify *o_notify = get_otg_notify(); + struct usb_notify *u_notify = NULL; + + if (!o_notify) { + unl_err("%s o_notify is null\n", __func__); + goto err; + } + u_notify = (struct usb_notify *)(o_notify->u_notify); + + if (!u_notify) { + unl_err("%s u_notify structure is null\n", + __func__); + goto err; + } + + unl_info("%s card=%d attach=%d\n", __func__, card_num, attach); + + if (attach) { + u_notify->udev.usb_audio_cards[card_num].cards = 1; + if (bundle) + u_notify->udev.usb_audio_cards[card_num].bundle = 1; + } else { + u_notify->udev.usb_audio_cards[card_num].cards = 0; + u_notify->udev.usb_audio_cards[card_num].bundle = 0; + } +err: + return; +} +EXPORT_SYMBOL(set_usb_audio_cardnum); + +#ifdef CONFIG_USB_AUDIO_ENHANCED_DETECT_TIME +int __weak get_next_snd_card_number(struct module *module) +{ + int idx = 0; + + unl_info("%s call weak function\n", __func__); + return idx; +} +#endif + +void send_usb_audio_uevent(struct usb_device *dev, + int card_num, int attach) +{ + struct otg_notify *o_notify = get_otg_notify(); + char *envp[6]; + char *type = {"TYPE=usbaudio"}; + char *state_add = {"STATE=ADD"}; + char *state_remove = {"STATE=REMOVE"}; + char vidpid_vuf[15]; + char path_buf[50]; + int index = 0; +#ifdef CONFIG_USB_AUDIO_ENHANCED_DETECT_TIME + char cardnum_buf[10]; + int cardnum = 0; +#endif + + if (!o_notify) { + unl_err("%s o_notify is null\n", __func__); + goto err; + } + + if (!is_known_usbaudio(dev)) + goto err; + + envp[index++] = type; + + if (attach) + envp[index++] = state_add; + else + envp[index++] = state_remove; + + snprintf(vidpid_vuf, sizeof(vidpid_vuf), + "ID=%04X/%04X", le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct)); + + envp[index++] = vidpid_vuf; + + snprintf(path_buf, sizeof(path_buf), + "PATH=/dev/bus/usb/%03d/%03d", dev->bus->busnum, dev->devnum); + + envp[index++] = path_buf; + +#ifdef CONFIG_USB_AUDIO_ENHANCED_DETECT_TIME + if (attach && !card_num) { + cardnum = get_next_snd_card_number(THIS_MODULE); + if (cardnum < 0) { + unl_err("%s cardnum error\n", __func__); + goto err; + } + } else + cardnum = card_num; + + set_usb_audio_cardnum(cardnum, 1, attach); + + snprintf(cardnum_buf, sizeof(cardnum_buf), + "CARDNUM=%d", cardnum); + + envp[index++] = cardnum_buf; +#endif + + envp[index++] = NULL; + + if (send_usb_notify_uevent(o_notify, envp)) { + unl_err("%s error\n", __func__); + goto err; + } + unl_info("%s\n", __func__); +err: + return; +} +EXPORT_SYMBOL(send_usb_audio_uevent); + +int send_usb_notify_uevent(struct otg_notify *n, char *envp_ext[]) +{ + struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify); + int ret = 0; + + if (!u_notify) { + unl_err("%s u_notify is null\n", __func__); + ret = -EFAULT; + goto err; + } + + ret = usb_notify_dev_uevent(&u_notify->udev, envp_ext); +err: + return ret; +} +EXPORT_SYMBOL(send_usb_notify_uevent); + +static int check_reverse_bypass_device(struct usb_device *dev) +{ + struct dev_table *id; + + /* check VID, PID */ + for (id = reverse_bypass_device_table; id->dev.match_flags; id++) { + if ((id->dev.match_flags & USB_DEVICE_ID_MATCH_VENDOR) && + (id->dev.match_flags & USB_DEVICE_ID_MATCH_PRODUCT) && + id->dev.idVendor == le16_to_cpu(dev->descriptor.idVendor) && + id->dev.idProduct == le16_to_cpu(dev->descriptor.idProduct)) { + unl_info("%s found\n", __func__); + return 1; + } + } + return 0; +} + +static int check_reverse_bypass_status(struct otg_notify *n) +{ + struct usb_notify *u_notify = NULL; + + if (!n) { + unl_err("%s otg_notify is null\n", __func__); + return false; + } + u_notify = (struct usb_notify *)(n->u_notify); + + if (!u_notify) { + unl_err("%s u_notify structure is null\n", __func__); + return false; + } + + unl_info("%s reverse bypass flag=%d\n", __func__, u_notify->reverse_bypass_status); + + return u_notify->reverse_bypass_status; +} + +static void reverse_bypass_drive_on_work(struct work_struct *w) +{ + struct otg_notify *o_notify = get_otg_notify(); + + send_otg_notify(o_notify, NOTIFY_EVENT_REVERSE_BYPASS_DEVICE_CONNECT, 1); +#if defined(CONFIG_USB_HW_PARAM) + inc_hw_param(o_notify, USB_HOST_REVERSE_BYPASS_COUNT); +#endif +} + +int check_new_device_added(struct usb_device *udev) +{ + struct otg_notify *o_notify = get_otg_notify(); + struct usb_notify *u_notify; + void *pdata; + int support_reverse_bypass_en; + struct usb_device *hdev; + struct usb_device *dev; + int port = 0, ret = 0; + + if (!o_notify) { + pr_err("%s otg_notify is null\n", __func__); + return ret; + } + + u_notify = (struct usb_notify *)(o_notify->u_notify); + if (!u_notify) { + pr_err("%s usb_notify is null\n", __func__); + return ret; + } + + pdata = get_notify_data(o_notify); + if (!pdata) { + pr_err("%s pdata is null\n", __func__); + return ret; + } + + if (!o_notify->get_support_reverse_bypass_en) { + pr_err("%s get_support_reverse_bypass_en is null\n", __func__); + return ret; + } + support_reverse_bypass_en = + o_notify->get_support_reverse_bypass_en(pdata); + unl_info("%s support_reverse_bypass_en : %d\n", __func__, + support_reverse_bypass_en); + + hdev = udev->parent; + if (!hdev) + return ret; + + hdev = udev->bus->root_hub; + if (!hdev) + return ret; + + usb_hub_for_each_child(hdev, port, dev) { + if (support_reverse_bypass_en && check_reverse_bypass_device(dev)) { + switch (check_reverse_bypass_status(o_notify)) { + case NOTIFY_EVENT_REVERSE_BYPASS_OFF: + ret = -1; + break; + case NOTIFY_EVENT_REVERSE_BYPASS_PREPARE: + schedule_work(&u_notify->reverse_bypass_on_work); + ret = -1; + break; + case NOTIFY_EVENT_REVERSE_BYPASS_ON: + break; + default: + break; + } + return ret; + } + } + + return ret; +} +EXPORT_SYMBOL(check_new_device_added); + +int set_lpm_charging_type_done(struct otg_notify *n, unsigned int state) +{ + struct usb_notify *u_notify = NULL; + int ret = 0; + + if (!u_notify_core) { + pr_err("%s u_notify_core is null\n", __func__); + ret = -EFAULT; + goto err; + } + + unl_info("%s state %u\n", __func__, state); + + u_notify_core->lpm_charging_type_done = state; + + if (!n) { + pr_err("%s otg_notify is null\n", __func__); + ret = -EFAULT; + goto err; + } + + u_notify = (struct usb_notify *)(n->u_notify); + + if (!u_notify) { + pr_err("%s u_notify is null\n", __func__); + ret = -EFAULT; + goto err; + } + + u_notify->udev.lpm_charging_type_done = state; +err: + return ret; +} +EXPORT_SYMBOL(set_lpm_charging_type_done); + +static int check_secure_connection(struct usb_notify *u_notify) +{ + int i; + + for (i = 0; i < USB_GROUP_MAX; i++) { + if (u_notify->secure_connect_group[i] >= MAX_SECURE_CONNECTION) + return true; + } + return false; +} + +int detect_illegal_condition(int type) +{ + struct otg_notify *o_notify = get_otg_notify(); + struct usb_notify *u_notify; + int ret = 0, restricted = 0; + + if (!o_notify) { + pr_err("%s otg_notify is null\n", __func__); + return ret; + } + + u_notify = (struct usb_notify *)(o_notify->u_notify); + if (!u_notify) { + pr_err("%s usb_notify is null\n", __func__); + return ret; + } + + unl_info("%s type %d +\n", __func__, type); + + switch (type) { + case NOTIFY_EVENT_AUDIO_DESCRIPTOR: + restricted = 1; +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_HOST_OVER_AUDIO_DESCRIPTOR_COUNT); +#endif + break; + case NOTIFY_EVENT_SECURE_DISCONNECTION: + if (check_secure_connection(u_notify)) + restricted = 1; + break; + default: + break; + } + + if (restricted) { + u_notify->restricted = 1; +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_HOST_SB_COUNT); +#endif + if (is_host_cable_enable(o_notify)) + send_otg_notify(o_notify, VIRT_EVENT(u_notify->c_type), 0); + + send_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_PRE, 1); + send_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_POST, 1); + } + + unl_info("%s type %d restricted=%d -\n", __func__, type, restricted); + + return ret; +} +EXPORT_SYMBOL(detect_illegal_condition); + +#if defined(CONFIG_USB_HW_PARAM) +unsigned long long *get_hw_param(struct otg_notify *n, + enum usb_hw_param index) +{ + struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify); + int ret = 0; + + if (index < 0 || index >= USB_CCIC_HW_PARAM_MAX) { + unl_err("%s usb_hw_param is out of bound\n", __func__); + return NULL; + } + + if (!u_notify) { + unl_err("%s u_notify structure is null\n", __func__); + return NULL; + } + + if (!u_notify_core) { + ret = create_usb_notify(); + if (ret) { + unl_err("unable create_usb_notify\n"); + return NULL; + } + } + return &(u_notify->hw_param[index]); +} +EXPORT_SYMBOL(get_hw_param); + +int inc_hw_param(struct otg_notify *n, + enum usb_hw_param index) +{ + struct usb_notify *u_notify; + int ret = 0; + + if (!n) { + unl_err("%s otg_notify is null\n", __func__); + return -ENODEV; + } + + u_notify = (struct usb_notify *)(n->u_notify); + + if (index < 0 || index >= USB_CCIC_HW_PARAM_MAX) { + unl_err("%s usb_hw_param is out of bound\n", __func__); + ret = -ENOMEM; + return ret; + } + + if (!u_notify) { + unl_err("%s u_notify structure is null\n", __func__); + ret = -ENOENT; + return ret; + } + + if (!u_notify_core) { + ret = create_usb_notify(); + if (ret) { + unl_err("unable create_usb_notify\n"); + return ret; + } + } + u_notify->hw_param[index]++; + return ret; +} +EXPORT_SYMBOL(inc_hw_param); + +int inc_hw_param_host(struct host_notify_dev *dev, + enum usb_hw_param index) +{ + struct usb_notify *u_notify = container_of(dev, + struct usb_notify, ndev); + int ret = 0; + + if (index < 0 || index >= USB_CCIC_HW_PARAM_MAX) { + unl_err("%s usb_hw_param is out of bound\n", __func__); + ret = -ENOMEM; + return ret; + } + + if (!u_notify) { + unl_err("%s u_notify structure is null\n", __func__); + ret = -ENOENT; + return ret; + } + + if (!u_notify_core) { + ret = create_usb_notify(); + if (ret) { + unl_err("unable create_usb_notify\n"); + return ret; + } + } + u_notify->hw_param[index]++; + return ret; +} +EXPORT_SYMBOL(inc_hw_param_host); + +int register_hw_param_manager(struct otg_notify *n, unsigned long (*fptr)(int)) +{ + struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify); + int ret = 0; + + if (!u_notify) { + unl_err("%s u_notify structure is null\n", __func__); + ret = -ENOENT; + goto err; + } + + if (!u_notify_core) { + ret = create_usb_notify(); + if (ret) { + unl_err("unable create_usb_notify\n"); + goto err; + } + } + u_notify->udev.fp_hw_param_manager = fptr; + unl_info("%s\n", __func__); +err: + return ret; +} +EXPORT_SYMBOL(register_hw_param_manager); +#endif + +void *get_notify_data(struct otg_notify *n) +{ + if (n) + return n->o_data; + else + return NULL; +} +EXPORT_SYMBOL(get_notify_data); + +void set_notify_data(struct otg_notify *n, void *data) +{ + n->o_data = data; +} +EXPORT_SYMBOL(set_notify_data); + +struct otg_notify *get_otg_notify(void) +{ + if (!u_notify_core) + return NULL; + if (!u_notify_core->o_notify) + return NULL; + return u_notify_core->o_notify; +} +EXPORT_SYMBOL(get_otg_notify); + +void enable_usb_notify(void) +{ + struct otg_notify *o_notify = get_otg_notify(); + struct usb_notify *u_notify = NULL; + + if (!o_notify) { + unl_err("%s o_notify is null\n", __func__); + return; + } + u_notify = (struct usb_notify *)(o_notify->u_notify); + + if (!u_notify) { + unl_err("%s u_notify structure is null\n", + __func__); + return; + } + + if (!o_notify->booting_delay_sync_usb) { + unl_err("%s booting_delay_sync_usb is not setting\n", + __func__); + return; + } + + o_notify->booting_delay_sync_usb = 0; + if (!delayed_work_pending(&u_notify->b_delay.booting_work)) + schedule_delayed_work(&u_notify->b_delay.booting_work, 0); + else + unl_err("%s wait booting_delay\n", __func__); +} +EXPORT_SYMBOL(enable_usb_notify); + +static int otg_notify_reboot(struct notifier_block *nb, + unsigned long event, void *cmd) +{ + struct otg_notify *o_notify = get_otg_notify(); + struct usb_notify *u_notify = NULL; + + if (!o_notify) { + unl_err("%s o_notify is null\n", __func__); + goto err; + } + u_notify = (struct usb_notify *)(o_notify->u_notify); + + if (!u_notify) { + unl_err("%s u_notify structure is null\n", + __func__); + goto err; + } + + if (is_host_cable_enable(o_notify)) + send_otg_notify(o_notify, + VIRT_EVENT(u_notify->c_type), 0); +err: + return NOTIFY_DONE; +} + +static struct notifier_block otg_notify_reboot_nb = { + .notifier_call = otg_notify_reboot, +}; + +int set_otg_notify(struct otg_notify *n) +{ + struct usb_notify *u_notify; + int ret = 0; + + if (!u_notify_core) { + ret = create_usb_notify(); + if (ret) { + pr_err("unable create_usb_notify\n"); + goto err; + } + } + + if (u_notify_core->o_notify && n) { + pr_err("error : already set o_notify\n"); + goto err; + } + + unl_info("registered otg_notify +\n"); + if (!n) { + pr_err("otg notify structure is null\n"); + ret = -EFAULT; + goto err1; + } + u_notify_core->o_notify = n; + + u_notify = kzalloc(sizeof(struct usb_notify), GFP_KERNEL); + if (!u_notify) { + ret = -ENOMEM; + goto err1; + } + + u_notify->o_notify = n; + + n->u_notify = (void *)u_notify; + + u_notify->notifier_wq + = create_singlethread_workqueue("usb_notify"); + if (!u_notify->notifier_wq) { + unl_err("%s failed to create work queue\n", __func__); + ret = -ENOMEM; + goto err2; + } + + ovc_init(u_notify); + notify_event_lock_init(u_notify); + mutex_init(&u_notify->state_lock); + + ATOMIC_INIT_NOTIFIER_HEAD(&u_notify->otg_notifier); + u_notify->otg_nb.notifier_call = otg_notifier_callback; + ret = atomic_notifier_chain_register(&u_notify->otg_notifier, + &u_notify->otg_nb); + if (ret < 0) { + unl_err("atomic_notifier_chain_register failed\n"); + goto err3; + } + + BLOCKING_INIT_NOTIFIER_HEAD(&u_notify->extra_notifier); + u_notify->extra_nb.notifier_call = extra_notifier_callback; + ret = blocking_notifier_chain_register + (&u_notify->extra_notifier, &u_notify->extra_nb); + if (ret < 0) { + unl_err("blocking_notifier_chain_register failed\n"); + goto err4; + } + + if (!n->unsupport_host) { + u_notify->ndev.name = "usb_otg"; + u_notify->ndev.set_booster = n->vbus_drive; + u_notify->ndev.set_mode = n->set_host; + ret = host_notify_dev_register(&u_notify->ndev); + if (ret < 0) { + unl_err("host_notify_dev_register is failed\n"); + goto err5; + } + + if (!n->vbus_drive) { + unl_err("vbus_drive is null\n"); + goto err6; + } + } + + u_notify->udev.name = "usb_control"; + u_notify->udev.set_disable = set_notify_disable; + u_notify->udev.set_mdm = set_notify_mdm; + u_notify->udev.set_mdm_for_id = set_notify_mdm_for_id; + u_notify->udev.set_mdm_for_serial = set_notify_mdm_for_serial; + u_notify->udev.control_usb_max_speed = control_usb_maximum_speed; + u_notify->udev.fp_hw_param_manager = NULL; + u_notify->udev.set_lock_state = set_notify_lock_state; + u_notify->udev.o_notify = n; + + ret = usb_notify_dev_register(&u_notify->udev); + if (ret < 0) { + unl_err("usb_notify_dev_register is failed\n"); + goto err6; + } + + u_notify->udev.lpm_charging_type_done + = u_notify_core->lpm_charging_type_done; + u_notify->udev.secure_lock = USB_NOTIFY_INIT_STATE; + + if (gpio_is_valid(n->vbus_detect_gpio) || + gpio_is_valid(n->redriver_en_gpio)) { + ret = register_gpios(n); + if (ret < 0) { + unl_err("register_gpios is failed\n"); + goto err7; + } + } + + if (n->is_wakelock) { + u_notify->ws.name = "usb_notify"; + wakeup_source_add(&u_notify->ws); + } + +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + init_waitqueue_head(&u_notify->init_delay); +#endif + + if (n->booting_delay_sec) { + u_notify->lock_state = USB_NOTIFY_INIT_STATE; + INIT_DELAYED_WORK(&u_notify->b_delay.booting_work, + reserve_state_check); + schedule_delayed_work(&u_notify->b_delay.booting_work, + n->booting_delay_sec*HZ); + } + + if (n->device_check_sec) + INIT_DELAYED_WORK(&u_notify->check_work, + device_connect_check); + + INIT_WORK(&u_notify->reverse_bypass_on_work, + reverse_bypass_drive_on_work); + + register_usbdev_notify(); + + register_reboot_notifier(&otg_notify_reboot_nb); + + unl_info("registered otg_notify -\n"); + return 0; +err7: + usb_notify_dev_unregister(&u_notify->udev); +err6: + if (!n->unsupport_host) + host_notify_dev_unregister(&u_notify->ndev); +err5: + blocking_notifier_chain_unregister(&u_notify->extra_notifier, + &u_notify->extra_nb); +err4: + atomic_notifier_chain_unregister(&u_notify->otg_notifier, + &u_notify->otg_nb); +err3: + flush_workqueue(u_notify->notifier_wq); + destroy_workqueue(u_notify->notifier_wq); +err2: + u_notify->o_notify = NULL; + kfree(u_notify); +err1: + u_notify_core->o_notify = NULL; +err: + return ret; +} +EXPORT_SYMBOL(set_otg_notify); + +void put_otg_notify(struct otg_notify *n) +{ + struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify); + + if (!u_notify) { + unl_err("%s u_notify structure is null\n", __func__); + return; + } + unregister_reboot_notifier(&otg_notify_reboot_nb); + unregister_usbdev_notify(); + if (n->booting_delay_sec) + cancel_delayed_work_sync(&u_notify->b_delay.booting_work); + if (n->is_wakelock) + wakeup_source_remove(&u_notify->ws); + + if (gpio_is_valid(n->redriver_en_gpio)) + gpio_free(n->redriver_en_gpio); + + if (gpio_is_valid(n->vbus_detect_gpio)) { + free_irq(gpio_to_irq(n->vbus_detect_gpio), NULL); + gpio_free(n->vbus_detect_gpio); + } + + usb_notify_dev_unregister(&u_notify->udev); + if (!n->unsupport_host) + host_notify_dev_unregister(&u_notify->ndev); + blocking_notifier_chain_unregister(&u_notify->extra_notifier, + &u_notify->extra_nb); + atomic_notifier_chain_unregister(&u_notify->otg_notifier, + &u_notify->otg_nb); + flush_workqueue(u_notify->notifier_wq); + destroy_workqueue(u_notify->notifier_wq); + u_notify->o_notify = NULL; + kfree(u_notify); +} +EXPORT_SYMBOL(put_otg_notify); + +static int __init usb_notify_init(void) +{ + return create_usb_notify(); +} + +static void __exit usb_notify_exit(void) +{ + if (!u_notify_core) + return; + usb_notify_class_exit(); + notify_class_exit(); + unregister_usblog_proc(); + kfree(u_notify_core); +} + +module_init(usb_notify_init); +module_exit(usb_notify_exit); + +MODULE_AUTHOR("Samsung USB Team"); +MODULE_DESCRIPTION("USB Notify Layer"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(NOTIFY_VERSION); diff --git a/drivers/usb/notify/usb_notify_sysfs.c b/drivers/usb/notify/usb_notify_sysfs.c new file mode 100755 index 000000000000..b50c718bfb4e --- /dev/null +++ b/drivers/usb/notify/usb_notify_sysfs.c @@ -0,0 +1,1458 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * + * Copyright (C) 2015-2023 Samsung, Inc. + * Author: Dongrak Shin + * + */ + + /* usb notify layer v4.0 */ + +#define pr_fmt(fmt) "usb_notify: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "usb_notify_sysfs.h" + +#define MAX_STRING_LEN 20 + +#if defined(CONFIG_USB_HW_PARAM) +const char +usb_hw_param_print[USB_CCIC_HW_PARAM_MAX][MAX_HWPARAM_STRING] = { + {"CC_WATER"}, + {"CC_DRY"}, + {"CC_I2C"}, + {"CC_OVC"}, + {"CC_OTG"}, + {"CC_DP"}, + {"CC_VR"}, + {"H_SUPER"}, + {"H_HIGH"}, + {"H_FULL"}, + {"H_LOW"}, + {"C_SUPER"}, + {"C_HIGH"}, + {"H_AUDIO"}, + {"H_AUDSS"}, + {"H_RBYPS"}, + {"H_COMM"}, + {"H_HID"}, + {"H_PHYSIC"}, + {"H_IMAGE"}, + {"H_PRINTER"}, + {"H_STORAGE"}, + {"H_STO_S"}, + {"H_STO_H"}, + {"H_STO_F"}, + {"H_HUB"}, + {"H_CDC"}, + {"H_CSCID"}, + {"H_CONTENT"}, + {"H_VIDEO"}, + {"H_WIRE"}, + {"H_MISC"}, + {"H_APP"}, + {"H_VENDOR"}, + {"CC_DEX"}, + {"CC_WTIME"}, + {"CC_WVBUS"}, + {"CC_WVTIME"}, + {"CC_WLVBS"}, + {"CC_WLVTM"}, + {"CC_CSHORT"}, + {"CC_SVSHT"}, + {"CC_SGSHT"}, + {"M_AFCNAK"}, + {"M_AFCERR"}, + {"M_DCDTMO"}, + {"F_CNT"}, + {"CC_KILLER"}, + {"CC_FWERR"}, + {"M_B12RS"}, + {"CC_PRS"}, + {"CC_DRS"}, + {"C_ARP"}, + {"CC_UMVS"}, + {"CC_STUCK"}, + {"H_SB"}, + {"H_OAD"}, + {"CC_VER"}, +}; +#endif /* CONFIG_USB_HW_PARAM */ + +struct notify_data { + struct class *usb_notify_class; + atomic_t device_count; +}; + +static struct notify_data usb_notify_data; + +static int is_valid_cmd(char *cur_cmd, char *prev_cmd) +{ + unl_info("%s : current state=%s, previous state=%s\n", + __func__, cur_cmd, prev_cmd); + + if (!strcmp(cur_cmd, "ON") || + !strncmp(cur_cmd, "ON_ALL_", 7)) { + if (!strcmp(prev_cmd, "ON") || + !strncmp(prev_cmd, "ON_ALL_", 7)) { + goto ignore; + } else if (!strncmp(prev_cmd, "ON_HOST_", 8)) { + goto all; + } else if (!strncmp(prev_cmd, "ON_CLIENT_", 10)) { + goto all; + } else if (!strcmp(prev_cmd, "OFF")) { + goto all; + } else { + goto invalid; + } + } else if (!strcmp(cur_cmd, "OFF")) { + if (!strcmp(prev_cmd, "ON") || + !strncmp(prev_cmd, "ON_ALL_", 7)) { + goto off; + } else if (!strncmp(prev_cmd, "ON_HOST_", 8)) { + goto off; + } else if (!strncmp(prev_cmd, "ON_CLIENT_", 10)) { + goto off; + } else if (!strcmp(prev_cmd, "OFF")) { + goto ignore; + } else { + goto invalid; + } + } else if (!strncmp(cur_cmd, "ON_HOST_", 8)) { + if (!strcmp(prev_cmd, "ON") || + !strncmp(prev_cmd, "ON_ALL_", 7)) { + goto host; + } else if (!strncmp(prev_cmd, "ON_HOST_", 8)) { + goto ignore; + } else if (!strncmp(prev_cmd, "ON_CLIENT_", 10)) { + goto host; + } else if (!strcmp(prev_cmd, "OFF")) { + goto host; + } else { + goto invalid; + } + } else if (!strncmp(cur_cmd, "ON_CLIENT_", 10)) { + if (!strcmp(prev_cmd, "ON") || + !strncmp(prev_cmd, "ON_ALL_", 7)) { + goto client; + } else if (!strncmp(prev_cmd, "ON_HOST_", 8)) { + goto client; + } else if (!strncmp(prev_cmd, "ON_CLIENT_", 10)) { + goto ignore; + } else if (!strcmp(prev_cmd, "OFF")) { + goto client; + } else { + goto invalid; + } + } else { + goto invalid; + } +host: + unl_info("%s cmd=%s is accepted.\n", __func__, cur_cmd); + return NOTIFY_BLOCK_TYPE_HOST; +client: + unl_info("%s cmd=%s is accepted.\n", __func__, cur_cmd); + return NOTIFY_BLOCK_TYPE_CLIENT; +all: + unl_info("%s cmd=%s is accepted.\n", __func__, cur_cmd); + return NOTIFY_BLOCK_TYPE_ALL; +off: + unl_info("%s cmd=%s is accepted.\n", __func__, cur_cmd); + return NOTIFY_BLOCK_TYPE_NONE; +ignore: + unl_err("%s cmd=%s is ignored but saved.\n", __func__, cur_cmd); + return -EEXIST; +invalid: + unl_err("%s cmd=%s is invalid.\n", __func__, cur_cmd); + return -EINVAL; +} + +static ssize_t disable_show( + struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + + unl_info("read disable_state %s\n", udev->disable_state_cmd); + return sprintf(buf, "%s\n", udev->disable_state_cmd); +} + +static ssize_t disable_store( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + + char *disable; + int sret, param = -EINVAL; + size_t ret = -ENOMEM; + + if (size > MAX_DISABLE_STR_LEN) { + unl_err("%s size(%zu) is too long.\n", __func__, size); + goto error; + } + + if (size < strlen(buf)) + goto error; + disable = kzalloc(size+1, GFP_KERNEL); + if (!disable) + goto error; + + sret = sscanf(buf, "%s", disable); + if (sret != 1) + goto error1; + + if (udev->set_disable) { + param = is_valid_cmd(disable, udev->disable_state_cmd); + if (param == -EINVAL) { + ret = param; + } else { + if (param != -EEXIST) { +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + udev->first_restrict = false; +#endif + udev->set_disable(udev, param); + } + strncpy(udev->disable_state_cmd, + disable, sizeof(udev->disable_state_cmd)-1); + ret = size; + } + } else + unl_err("set_disable func is NULL\n"); +error1: + kfree(disable); +error: + return ret; +} + +static ssize_t usb_data_enabled_show( + struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + + unl_info("read usb_data_enabled %lu\n", udev->usb_data_enabled); + return sprintf(buf, "%lu\n", udev->usb_data_enabled); +} + +static ssize_t usb_data_enabled_store( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + size_t ret = -ENOMEM; + int sret = -EINVAL; + int param = 0; + char *usb_data_enabled; + + if (size > PAGE_SIZE) { + unl_err("%s size(%zu) is too long.\n", __func__, size); + goto error; + } + + usb_data_enabled = kzalloc(size+1, GFP_KERNEL); + if (!usb_data_enabled) + goto error; + + sret = sscanf(buf, "%s", usb_data_enabled); + if (sret != 1) + goto error1; + + if (udev->set_disable) { + if (strcmp(usb_data_enabled, "0") == 0) { + param = NOTIFY_BLOCK_TYPE_ALL; + udev->usb_data_enabled = 0; + } else if (strcmp(usb_data_enabled, "1") == 0) { + param = NOTIFY_BLOCK_TYPE_NONE; + udev->usb_data_enabled = 1; + } else { + unl_err("%s usb_data_enabled(%s) error.\n", + __func__, usb_data_enabled); + goto error1; + } + unl_info("%s usb_data_enabled=%s\n", + __func__, usb_data_enabled); + udev->set_disable(udev, param); + ret = size; + } else { + unl_err("%s set_disable func is NULL\n", __func__); + } +error1: + kfree(usb_data_enabled); +error: + return ret; +} + +static ssize_t support_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + struct otg_notify *n = udev->o_notify; + char *support; + + if (n->unsupport_host || !IS_ENABLED(CONFIG_USB_HOST_NOTIFY)) + support = "CLIENT"; + else + support = "ALL"; + + unl_info("read support %s\n", support); + return sprintf(buf, "%s\n", support); +} + +static ssize_t otg_speed_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + struct otg_notify *n = udev->o_notify; + int dev_max_speed = 0; + char *speed; + + dev_max_speed = get_con_dev_max_speed(n); + + switch (dev_max_speed) { + case USB_SPEED_SUPER_PLUS: + speed = "SUPER PLUS"; + break; + case USB_SPEED_SUPER: + speed = "SUPER"; + break; + case USB_SPEED_HIGH: + speed = "HIGH"; + break; + case USB_SPEED_FULL: + speed = "FULL"; + break; + case USB_SPEED_LOW: + speed = "LOW"; + break; + default: + speed = "UNKNOWN"; + break; + } + unl_info("%s : read otg speed %s\n", __func__, speed); + return sprintf(buf, "%s\n", speed); +} + +static ssize_t gadget_speed_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + struct otg_notify *n = udev->o_notify; + const char *speed; + + if (n->get_gadget_speed) + speed = usb_speed_string(n->get_gadget_speed()); + else + speed = "UNKNOWN"; + + unl_info("%s : read gadget speed %s\n", __func__, speed); + return snprintf(buf, MAX_STRING_LEN, "%s\n", speed); +} + +static const char *const max_speed_str[] = { + [USB_SPEED_UNKNOWN] = "UNKNOWN", + [USB_SPEED_LOW] = "low-speed", + [USB_SPEED_FULL] = "full-speed", + [USB_SPEED_HIGH] = "high-speed", + [USB_SPEED_WIRELESS] = "wireless-usb", + [USB_SPEED_SUPER] = "super-speed", + [USB_SPEED_SUPER_PLUS] = "super-speed+", +}; + +static ssize_t usb_maximum_speed_show( + struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + int ret = 0; + + ret = udev->control_usb_max_speed(udev, -1); + + return sprintf(buf, "%s\n", max_speed_str[ret]); +} + +static ssize_t usb_maximum_speed_store( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + int max_speed_idx = USB_SPEED_UNKNOWN; + char *max_speed; + size_t ret = -ENOMEM, i, sret; + + unl_info("%s\n", __func__); + + if (size > MAX_USB_SPEED_STR_LEN) { + unl_err("%s size(%zu) is too long.\n", __func__, size); + goto error; + } + + max_speed = kzalloc(size+1, GFP_KERNEL); + if (!max_speed) + goto error; + + sret = sscanf(buf, "%s", max_speed); + if (sret != 1) + goto error1; + + for (i = 0; i < ARRAY_SIZE(max_speed_str); i++) { + if (strncmp(max_speed, max_speed_str[i], + strlen(max_speed_str[i])) == 0) { + max_speed_idx = i; + break; + } + } + + if (max_speed_idx == USB_SPEED_UNKNOWN) { + ret = -EINVAL; + goto error1; + } else { + sret = udev->control_usb_max_speed(udev, max_speed_idx); + } + + unl_info("%s req=%s now=%s\n", __func__, max_speed, + max_speed_str[max_speed_idx]); + ret = size; +error1: + kfree(max_speed); +error: + return ret; +} + +#if defined(CONFIG_USB_HW_PARAM) +static unsigned long long strtoull(char *ptr, char **end, int base) +{ + unsigned long long ret = 0; + + if (base > 36) + goto out; + + while (*ptr) { + int digit; + + if (*ptr >= '0' && *ptr <= '9' && *ptr < '0' + base) + digit = *ptr - '0'; + else if (*ptr >= 'A' && *ptr < 'A' + base - 10) + digit = *ptr - 'A' + 10; + else if (*ptr >= 'a' && *ptr < 'a' + base - 10) + digit = *ptr - 'a' + 10; + else + break; + + ret *= base; + ret += digit; + ptr++; + } + +out: + if (end) + *end = (char *)ptr; + + return ret; +} + +static ssize_t usb_hw_param_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + struct otg_notify *n = udev->o_notify; + int index, ret = 0; + unsigned long long *p_param = NULL; + + if (udev->fp_hw_param_manager) { + p_param = get_hw_param(n, USB_CCIC_WATER_INT_COUNT); + if (p_param) + *p_param += udev->fp_hw_param_manager + (USB_CCIC_WATER_INT_COUNT); + p_param = get_hw_param(n, USB_CCIC_DRY_INT_COUNT); + if (p_param) + *p_param += udev->fp_hw_param_manager + (USB_CCIC_DRY_INT_COUNT); + p_param = get_hw_param(n, USB_CLIENT_SUPER_SPEED_COUNT); + if (p_param) + *p_param += udev->fp_hw_param_manager + (USB_CLIENT_SUPER_SPEED_COUNT); + p_param = get_hw_param(n, USB_CLIENT_HIGH_SPEED_COUNT); + if (p_param) + *p_param += udev->fp_hw_param_manager + (USB_CLIENT_HIGH_SPEED_COUNT); + p_param = get_hw_param(n, USB_CCIC_WATER_TIME_DURATION); + if (p_param) + *p_param += udev->fp_hw_param_manager + (USB_CCIC_WATER_TIME_DURATION); + p_param = get_hw_param(n, USB_CCIC_WATER_VBUS_COUNT); + if (p_param) + *p_param += udev->fp_hw_param_manager + (USB_CCIC_WATER_VBUS_COUNT); + p_param = get_hw_param(n, USB_CCIC_WATER_LPM_VBUS_COUNT); + if (p_param) + *p_param += udev->fp_hw_param_manager + (USB_CCIC_WATER_LPM_VBUS_COUNT); + p_param = get_hw_param(n, USB_CCIC_WATER_VBUS_TIME_DURATION); + if (p_param) + *p_param += udev->fp_hw_param_manager + (USB_CCIC_WATER_VBUS_TIME_DURATION); + p_param = get_hw_param(n, + USB_CCIC_WATER_LPM_VBUS_TIME_DURATION); + if (p_param) + *p_param += udev->fp_hw_param_manager + (USB_CCIC_WATER_LPM_VBUS_TIME_DURATION); + } + p_param = get_hw_param(n, USB_CCIC_VERSION); + if (p_param) + *p_param = show_ccic_version(); + for (index = 0; index < USB_CCIC_HW_PARAM_MAX - 1; index++) { + p_param = get_hw_param(n, index); + if (p_param) + ret += sprintf(buf + ret, "%llu ", *p_param); + else + ret += sprintf(buf + ret, "0 "); + } + p_param = get_hw_param(n, index); + if (p_param) + ret += sprintf(buf + ret, "%llu\n", *p_param); + else + ret += sprintf(buf + ret, "0\n"); + unl_info("%s - ret : %d\n", __func__, ret); + + return ret; +} + +static ssize_t usb_hw_param_store( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + struct otg_notify *n = udev->o_notify; + unsigned long long prev_hw_param[USB_CCIC_HW_PARAM_MAX] = {0, }; + unsigned long long *p_param = NULL; + int index = 0; + size_t ret = -ENOMEM; + char *token, *str = (char *)buf; + + if (size > MAX_HWPARAM_STR_LEN) { + unl_err("%s size(%zu) is too long.\n", __func__, size); + goto error; + } + ret = size; + if (size < USB_CCIC_HW_PARAM_MAX) { + unl_err("%s efs file is not created correctly.\n", __func__); + goto error; + } + + for (index = 0; index < (USB_CCIC_HW_PARAM_MAX - 1); index++) { + token = strsep(&str, " "); + if (token) + prev_hw_param[index] = strtoull(token, NULL, 10); + + if (!token || (prev_hw_param[index] > HWPARAM_DATA_LIMIT)) + goto error; + } + + for (index = 0; index < (USB_CCIC_HW_PARAM_MAX - 1); index++) { + p_param = get_hw_param(n, index); + if (p_param) + *p_param += prev_hw_param[index]; + } + unl_info("%s - ret : %zu\n", __func__, ret); +error: + return ret; +} + +static int is_skip_list(struct otg_notify *n, int index) +{ + if (!n) + return 0; + + if (n->is_skip_list) + return n->is_skip_list(index); + + return 0; +} + +static ssize_t hw_param_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + struct otg_notify *n = udev->o_notify; + int index = 0, ret = 0; + unsigned long long *p_param = NULL; + + if (udev->fp_hw_param_manager) { + p_param = get_hw_param(n, USB_CCIC_WATER_INT_COUNT); + if (p_param) + *p_param += udev->fp_hw_param_manager + (USB_CCIC_WATER_INT_COUNT); + p_param = get_hw_param(n, USB_CCIC_DRY_INT_COUNT); + if (p_param) + *p_param += udev->fp_hw_param_manager + (USB_CCIC_DRY_INT_COUNT); + p_param = get_hw_param(n, USB_CLIENT_SUPER_SPEED_COUNT); + if (p_param) + *p_param += udev->fp_hw_param_manager + (USB_CLIENT_SUPER_SPEED_COUNT); + p_param = get_hw_param(n, USB_CLIENT_HIGH_SPEED_COUNT); + if (p_param) + *p_param += udev->fp_hw_param_manager + (USB_CLIENT_HIGH_SPEED_COUNT); + p_param = get_hw_param(n, USB_CCIC_WATER_TIME_DURATION); + if (p_param) + *p_param += udev->fp_hw_param_manager + (USB_CCIC_WATER_TIME_DURATION); + p_param = get_hw_param(n, USB_CCIC_WATER_VBUS_COUNT); + if (p_param) + *p_param += udev->fp_hw_param_manager + (USB_CCIC_WATER_VBUS_COUNT); + p_param = get_hw_param(n, USB_CCIC_WATER_LPM_VBUS_COUNT); + if (p_param) + *p_param += udev->fp_hw_param_manager + (USB_CCIC_WATER_LPM_VBUS_COUNT); + p_param = get_hw_param(n, USB_CCIC_WATER_VBUS_TIME_DURATION); + if (p_param) + *p_param += udev->fp_hw_param_manager + (USB_CCIC_WATER_VBUS_TIME_DURATION); + p_param = get_hw_param(n, + USB_CCIC_WATER_LPM_VBUS_TIME_DURATION); + if (p_param) + *p_param += udev->fp_hw_param_manager + (USB_CCIC_WATER_LPM_VBUS_TIME_DURATION); + } + if (!is_skip_list(n, USB_CCIC_VERSION)) { + p_param = get_hw_param(n, USB_CCIC_VERSION); + if (p_param) + *p_param = show_ccic_version(); + } + for (index = 0; index < USB_CCIC_HW_PARAM_MAX - 1; index++) { + if (!is_skip_list(n, index)) { + p_param = get_hw_param(n, index); + if (p_param) + ret += sprintf(buf + ret, "\"%s\":\"%llu\",", + usb_hw_param_print[index], *p_param); + else + ret += sprintf(buf + ret, "\"%s\":\"0\",", + usb_hw_param_print[index]); + } + } + if (!is_skip_list(n, USB_CCIC_VERSION)) { + /* CCIC FW version */ + ret += sprintf(buf + ret, "\"%s\":\"", + usb_hw_param_print[USB_CCIC_VERSION]); + p_param = get_hw_param(n, USB_CCIC_VERSION); + if (p_param) { + /* HW Version */ + ret += sprintf(buf + ret, "%02X%02X%02X%02X", + *((unsigned char *)p_param + 3), + *((unsigned char *)p_param + 2), + *((unsigned char *)p_param + 1), + *((unsigned char *)p_param)); + /* SW Main Version */ + ret += sprintf(buf + ret, "%02X%02X%02X", + *((unsigned char *)p_param + 6), + *((unsigned char *)p_param + 5), + *((unsigned char *)p_param + 4)); + /* SW Boot Version */ + ret += sprintf(buf + ret, "%02X", + *((unsigned char *)p_param + 7)); + ret += sprintf(buf + ret, "\"\n"); + } else { + ret += sprintf(buf + ret, "0000000000000000\"\n"); + } + } else { + ret += sprintf(buf + ret - 1, "\n"); + } + unl_info("%s - ret : %d\n", __func__, ret); + return ret; +} + +static ssize_t hw_param_store( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + struct otg_notify *n = udev->o_notify; + int index = 0; + size_t ret = -ENOMEM; + char *str = (char *)buf; + unsigned long long *p_param = NULL; + + if (size > 2) { + unl_err("%s size(%zu) is too long.\n", __func__, size); + goto error; + } + ret = size; + unl_info("%s : %s\n", __func__, str); + if (!strncmp(str, "c", 1)) + for (index = 0; index < USB_CCIC_HW_PARAM_MAX; index++) { + p_param = get_hw_param(n, index); + if (p_param) + *p_param = 0; + } +error: + return ret; +} +#endif + +char interface_class_name[USB_CLASS_VENDOR_SPEC][4] = { + [U_CLASS_PER_INTERFACE] = {"PER"}, + [U_CLASS_AUDIO] = {"AUD"}, + [U_CLASS_COMM] = {"COM"}, + [U_CLASS_HID] = {"HID"}, + [U_CLASS_PHYSICAL] = {"PHY"}, + [U_CLASS_STILL_IMAGE] = {"STI"}, + [U_CLASS_PRINTER] = {"PRI"}, + [U_CLASS_MASS_STORAGE] = {"MAS"}, + [U_CLASS_HUB] = {"HUB"}, + [U_CLASS_CDC_DATA] = {"CDC"}, + [U_CLASS_CSCID] = {"CSC"}, + [U_CLASS_CONTENT_SEC] = {"CON"}, + [U_CLASS_VIDEO] = {"VID"}, + [U_CLASS_WIRELESS_CONTROLLER] = {"WIR"}, + [U_CLASS_MISC] = {"MIS"}, + [U_CLASS_APP_SPEC] = {"APP"}, + [U_CLASS_VENDOR_SPEC] = {"VEN"} +}; + +void init_usb_whitelist_array(int *whitelist_array) +{ + int i; + + for (i = 1; i <= MAX_CLASS_TYPE_NUM; i++) + whitelist_array[i] = 0; +} + +void init_usb_whitelist_array_for_id(int *whitelist_array, int size) +{ + int i; + + for (i = 0; i < size; i++) + whitelist_array[i] = 0; +} + +int set_usb_allowlist_array(const char *buf, int *whitelist_array) +{ + int valid_class_count = 0; + char *ptr = NULL; + int i; + char *source; + + source = (char *)buf; + while ((ptr = strsep(&source, ":")) != NULL) { + if (strlen(ptr) < 3) + continue; + unl_info("%s token = %c%c%c!\n", __func__, + ptr[0], ptr[1], ptr[2]); + for (i = U_CLASS_PER_INTERFACE; i <= U_CLASS_VENDOR_SPEC; i++) { + if (!strncmp(ptr, interface_class_name[i], 3)) + whitelist_array[i] = 1; + } + } + + for (i = U_CLASS_PER_INTERFACE; i <= U_CLASS_VENDOR_SPEC; i++) { + if (whitelist_array[i]) + valid_class_count++; + } + unl_info("%s valid_class_count = %d!\n", __func__, valid_class_count); + return valid_class_count; +} + +int set_usb_allowlist_array_for_id(const char *buf, int *whitelist_array) +{ + int valid_product_count = 0; + int vid = 0, pid = 0, ret = 0; + char *ptr_vid = NULL; + char *ptr_pid = NULL; + char *source; + + source = (char *)buf; + while ((ptr_vid = strsep(&source, ":")) != NULL) { + if (strlen(ptr_vid) < 4) { + unl_err("%s short strlen(vid)\n", __func__); + break; + } + + ptr_pid = strsep(&source, ":"); + + if (ptr_pid == NULL || strlen(ptr_pid) < 4) { + unl_err("%s short strlen(pid)\n", __func__); + break; + } + + if (!ptr_vid[0] || !ptr_vid[1] || !ptr_vid[2] || !ptr_vid[3] || + !ptr_pid[0] || !ptr_pid[1] || !ptr_pid[2] || !ptr_pid[3]) + break; + + ret = kstrtoint(ptr_vid, 16, &vid); + if (ret) { + unl_err("%s ptr_vid error. ret %d\n", __func__, ret); + break; + } + + whitelist_array[valid_product_count] = vid; + + ret = kstrtoint(ptr_pid, 16, &pid); + if (ret) { + unl_err("%s ptr_pid error. ret %d\n", __func__, ret); + break; + } + + whitelist_array[valid_product_count+1] = pid; + + unl_info("%s : allowlist_array[%d]=%04x, allowlist_array[%d]=%04x\n", + __func__, valid_product_count, whitelist_array[valid_product_count], + valid_product_count+1, whitelist_array[valid_product_count+1]); + + valid_product_count += 2; + } + + valid_product_count /= 2; + + unl_info("%s valid_product_count = %d!\n", __func__, valid_product_count); + return valid_product_count; +} + +static ssize_t whitelist_for_mdm_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + + if (udev == NULL) { + unl_err("udev is NULL\n"); + return -EINVAL; + } + unl_info("allowlist_for_mdm read allowlist_classes %s\n", + udev->whitelist_str); + return sprintf(buf, "%s\n", udev->whitelist_str); +} + +static ssize_t whitelist_for_mdm_store( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + char *disable; + int sret; + size_t ret = -ENOMEM; + int mdm_disable; + int valid_whilelist_count; + + if (udev == NULL) { + unl_err("udev is NULL\n"); + ret = -EINVAL; + goto error; + } + + if (size < 3) { + unl_err("allowlist usage was wrong. The size(%zu) is too short.\n", size); + goto error; + } + if (size < strlen(buf)) + goto error; + + disable = kzalloc(size+1, GFP_KERNEL); + if (!disable) + goto error; + + sret = sscanf(buf, "%s", disable); + if (sret != 1) + goto error1; + +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + if (!strncmp(buf, "VPID:", ALLOWLIST_PREFIX_SIZE)) { + + unl_info("allowlist_for_mdm_store VID, PID buf=%s\n", disable); + + if (size >= MAX_ALLOWLIST_BUFFER) { + unl_err("allowlist_for_lockscreen size(%zu) is invalid.\n", size); + goto error1; + } + + mutex_lock(&udev->lockscreen_enabled_lock); + init_usb_whitelist_array_for_id(udev->allowlist_array_lockscreen_enabled_id, + MAX_ALLOWLIST_DEVICE_BUFFER_INDEX); + + valid_whilelist_count = set_usb_allowlist_array_for_id + (buf+ALLOWLIST_PREFIX_SIZE, udev->allowlist_array_lockscreen_enabled_id); + + // for furture use ex:) show function + strncpy(udev->allowlist_str_lockscreen_enabled_id, + disable, sizeof(udev->allowlist_str_lockscreen_enabled_id)-1); + mutex_unlock(&udev->lockscreen_enabled_lock); + + ret = size; + + unl_info("%s vpid allowlist update done!\n", __func__); + } else { +#endif + unl_info("allowlist_for_mdm_store interface buf=%s\n", disable); + + /* To active displayport, hub class must be enabled */ + if (size > MAX_WHITELIST_STR_LEN) { + unl_err("allowlist_for_mdm_store size(%zu) is invalid.\n", size); + goto error1; + } + + init_usb_whitelist_array(udev->whitelist_array_for_mdm); + + if (!strncmp(buf, "ABL", 3)) { + udev->whitelist_array_for_mdm[U_CLASS_HUB] = 1; + mdm_disable = NOTIFY_MDM_TYPE_ON; + } else if (!strncmp(buf, "OFF", 3)) + mdm_disable = NOTIFY_MDM_TYPE_OFF; + else { + valid_whilelist_count = set_usb_allowlist_array + (buf, udev->whitelist_array_for_mdm); + if (valid_whilelist_count > 0) { + udev->whitelist_array_for_mdm[U_CLASS_HUB] = 1; + mdm_disable = NOTIFY_MDM_TYPE_ON; + } else + mdm_disable = NOTIFY_MDM_TYPE_OFF; + } + + strncpy(udev->whitelist_str, + disable, sizeof(udev->whitelist_str)-1); + + if (udev->set_mdm) { + udev->set_mdm(udev, mdm_disable); + ret = size; + } else { + unl_err("set_mdm func is NULL\n"); + ret = -EINVAL; + } +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + } +#endif +error1: + kfree(disable); +error: + return ret; +} + +static ssize_t whitelist_for_disa_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + + if (udev == NULL) { + unl_err("udev is NULL\n"); + return -EINVAL; + } + unl_info("%s read allowlist_classes %s\n", + __func__, udev->whitelist_str_for_id); + return sprintf(buf, "%s\n", udev->whitelist_str_for_id); +} + +static ssize_t whitelist_for_disa_store( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + char *disable; + int sret; + size_t ret = -ENOMEM; + int mdm_disable; + int valid_whilelist_count; + + if (udev == NULL) { + unl_err("udev is NULL\n"); + ret = -EINVAL; + goto error; + } + + if (size > MAX_WHITELIST_STR_LEN) { + unl_err("%s size(%zu) is too long.\n", __func__, size); + goto error; + } + + if (size < strlen(buf)) + goto error; + disable = kzalloc(size+1, GFP_KERNEL); + if (!disable) + goto error; + + sret = sscanf(buf, "%s", disable); + if (sret != 1) + goto error1; + unl_info("allowlist_for_disa_store buf=%s\n", disable); + + init_usb_whitelist_array_for_id(udev->whitelist_array_for_mdm_for_id, MAX_WHITELIST_STR_LEN); + /* To active displayport, hub class must be enabled */ + if (!strncmp(buf, "OFF", 3)) { + unl_info("%s OFF\n", __func__); + mdm_disable = NOTIFY_MDM_TYPE_OFF; + } else { + unl_info("%s ALLOWLIST\n", __func__); + valid_whilelist_count = set_usb_allowlist_array_for_id + (buf, udev->whitelist_array_for_mdm_for_id); + if (valid_whilelist_count > 0) + mdm_disable = NOTIFY_MDM_TYPE_ON; + else + mdm_disable = NOTIFY_MDM_TYPE_OFF; + } + + strncpy(udev->whitelist_str_for_id, + disable, sizeof(udev->whitelist_str_for_id)-1); + + if (udev->set_mdm_for_id) { + udev->set_mdm_for_id(udev, mdm_disable); + ret = size; + } else { + unl_err("set_mdm_for_id func is NULL\n"); + ret = -EINVAL; + } +error1: + kfree(disable); +error: + return ret; +} + +static ssize_t whitelist_for_serial_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + + if (udev == NULL) { + unl_err("udev is NULL\n"); + return -EINVAL; + } + unl_info("%s read allowlist_classes %s\n", + __func__, udev->whitelist_array_for_mdm_for_serial); + return sprintf(buf, "%s\n", udev->whitelist_array_for_mdm_for_serial); +} + +static ssize_t whitelist_for_serial_store( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + char *disable; + int sret; + size_t ret = -ENOMEM; + int mdm_disable; + + if (udev == NULL) { + unl_err("udev is NULL\n"); + ret = -EINVAL; + goto error; + } + + if (size > MAX_WHITELIST_STR_LEN) { + unl_err("%s size(%zu) is too long.\n", __func__, size); + goto error; + } + + if (size < strlen(buf)) + goto error; + disable = kzalloc(size+1, GFP_KERNEL); + if (!disable) + goto error; + + sret = sscanf(buf, "%s", disable); + if (sret != 1) + goto error1; + unl_info("allowlist_for_serial_store buf=%s\n", disable); + + strncpy(udev->whitelist_array_for_mdm_for_serial, + disable, sizeof(udev->whitelist_array_for_mdm_for_serial)-1); + + /* To active displayport, hub class must be enabled */ + if (!strncmp(buf, "OFF", 3)) { + unl_info("%s OFF\n", __func__); + mdm_disable = NOTIFY_MDM_TYPE_OFF; + } else { + unl_info("%s ALLOWLIST\n", __func__); + mdm_disable = NOTIFY_MDM_TYPE_ON; + } + + if (udev->set_mdm_for_serial) { + udev->set_mdm_for_serial(udev, mdm_disable); + ret = size; + } else { + unl_err("set_mdm_for_serial func is NULL\n"); + ret = -EINVAL; + } +error1: + kfree(disable); +error: + return ret; +} + +static ssize_t usb_request_action_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + + if (udev == NULL) { + unl_err("udev is NULL\n"); + return -EINVAL; + } + unl_info("%s request_action = %u\n", + __func__, udev->request_action); + + return sprintf(buf, "%u\n", udev->request_action); +} + +static ssize_t usb_request_action_store( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) + +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + unsigned int request_action = 0; + int sret = -EINVAL; + size_t ret = -ENOMEM; + + if (udev == NULL) { + unl_err("udev is NULL\n"); + return -EINVAL; + } + if (size > PAGE_SIZE) { + unl_err("%s size(%zu) is too long.\n", __func__, size); + goto error; + } + + sret = sscanf(buf, "%u", &request_action); + if (sret != 1) + goto error; + + udev->request_action = request_action; + + unl_info("%s request_action = %d\n", + __func__, udev->request_action); + ret = size; + +error: + return ret; +} + +static ssize_t cards_show( + struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + char card_strings[MAX_CARD_STR_LEN] = {0,}; + char buf_card[15] = {0,}; + int i; + int cnt = 0; + + for (i = 0; i < MAX_USB_AUDIO_CARDS; i++) { + if (udev->usb_audio_cards[i].cards) { + cnt += snprintf(buf_card, sizeof(buf_card), + "<%scard%d>", + udev->usb_audio_cards[i].bundle ? "*" : "", i); + if (cnt < 0) { + unl_err("%s snprintf return %d\n", + __func__, cnt); + continue; + } + if (cnt >= MAX_CARD_STR_LEN) { + unl_err("%s overflow\n", __func__); + goto err; + } + strlcat(card_strings, buf_card, sizeof(card_strings)); + } + } +err: + unl_info("card_strings %s\n", card_strings); + return sprintf(buf, "%s\n", card_strings); +} + +int usb_notify_dev_uevent(struct usb_notify_dev *udev, char *envp_ext[]) +{ + int ret = 0; + + if (!udev || !udev->dev) { + unl_err("%s udev or udev->dev NULL\n", __func__); + ret = -EINVAL; + goto err; + } + + if (strncmp("TYPE", envp_ext[0], 4)) { + unl_err("%s error.first array must be filled TYPE\n", + __func__); + ret = -EINVAL; + goto err; + } + + if (strncmp("STATE", envp_ext[1], 5)) { + unl_err("%s error.second array must be filled STATE\n", + __func__); + ret = -EINVAL; + goto err; + } + + kobject_uevent_env(&udev->dev->kobj, KOBJ_CHANGE, envp_ext); + unl_info("%s\n", __func__); + +err: + return ret; +} +EXPORT_SYMBOL_GPL(usb_notify_dev_uevent); + +#if defined(CONFIG_USB_LPM_CHARGING_SYNC) +static ssize_t lpm_charging_type_done_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + + if (udev == NULL) { + pr_err("udev is NULL\n"); + return -EINVAL; + } + + return sprintf(buf, "%u\n", udev->lpm_charging_type_done); +} +#endif + +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION +static const char *lock_string(enum usb_lock_state lock_state) +{ + switch (lock_state) { + case USB_NOTIFY_INIT_STATE: + return "init"; + case USB_NOTIFY_UNLOCK: + return "unlock"; + case USB_NOTIFY_LOCK_USB_WORK: + return "usb work lock"; + case USB_NOTIFY_LOCK_USB_RESTRICT: + return "usb restrict lock"; + default: + return "undefined"; + } +} +#endif + +static ssize_t usb_sl_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + + if (udev == NULL) { + unl_err("udev is NULL\n"); + return -EINVAL; + } + unl_info("%s secure_lock = %lu\n", + __func__, udev->secure_lock); + + return sprintf(buf, "%lu\n", udev->secure_lock); +} + +static ssize_t usb_sl_store( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) + +{ + struct usb_notify_dev *udev = (struct usb_notify_dev *) + dev_get_drvdata(dev); + unsigned long secure_lock = 0; +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + unsigned long prev_secure_lock = 0; +#endif + int sret = -EINVAL; + size_t ret = -ENOMEM; + + if (udev == NULL) { + unl_err("udev is NULL\n"); + return -EINVAL; + } + if (size > PAGE_SIZE) { + unl_err("%s size(%zu) is too long.\n", __func__, size); + goto error; + } + +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + unl_info("%s before secure_lock = %s first_restrict = %d +\n", + __func__, lock_string(udev->secure_lock), udev->first_restrict); +#else + unl_info("%s before secure_lock = %lu +\n", + __func__, udev->secure_lock); +#endif + + sret = sscanf(buf, "%lu", &secure_lock); + if (sret != 1) + goto error; + +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + prev_secure_lock = udev->secure_lock; +#endif + udev->secure_lock = secure_lock; + udev->set_lock_state(udev); + +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + if (prev_secure_lock == USB_NOTIFY_INIT_STATE + && secure_lock == USB_NOTIFY_LOCK_USB_RESTRICT) { + if (udev->set_disable) { + udev->set_disable(udev, NOTIFY_BLOCK_TYPE_ALL); + udev->first_restrict = true; + } + } else if (udev->first_restrict && prev_secure_lock == USB_NOTIFY_LOCK_USB_RESTRICT + && (secure_lock == USB_NOTIFY_UNLOCK + || secure_lock == USB_NOTIFY_LOCK_USB_WORK)) { + if (udev->set_disable) { + udev->set_disable(udev, NOTIFY_BLOCK_TYPE_NONE); + udev->first_restrict = false; + } + } + + unl_info("%s after secure_lock = %s -\n", + __func__, lock_string(udev->secure_lock)); +#else + unl_info("%s after secure_lock = %lu -\n", + __func__, udev->secure_lock); +#endif + ret = size; + +error: + return ret; +} + +static DEVICE_ATTR_RW(disable); +static DEVICE_ATTR_RW(usb_data_enabled); +static DEVICE_ATTR_RO(support); +static DEVICE_ATTR_RO(otg_speed); +static DEVICE_ATTR_RO(gadget_speed); +static DEVICE_ATTR_RW(usb_maximum_speed); +static DEVICE_ATTR_RW(whitelist_for_mdm); +static DEVICE_ATTR_RW(whitelist_for_disa); +static DEVICE_ATTR_RW(whitelist_for_serial); +static DEVICE_ATTR_RO(cards); +#if defined(CONFIG_USB_HW_PARAM) +static DEVICE_ATTR_RW(usb_hw_param); +static DEVICE_ATTR_RW(hw_param); +#endif +static DEVICE_ATTR_RW(usb_request_action); +#if defined(CONFIG_USB_LPM_CHARGING_SYNC) +static DEVICE_ATTR_RO(lpm_charging_type_done); +#endif +static DEVICE_ATTR_RW(usb_sl); + +static struct attribute *usb_notify_attrs[] = { + &dev_attr_disable.attr, + &dev_attr_usb_data_enabled.attr, + &dev_attr_support.attr, + &dev_attr_otg_speed.attr, + &dev_attr_gadget_speed.attr, + &dev_attr_usb_maximum_speed.attr, + &dev_attr_whitelist_for_mdm.attr, + &dev_attr_whitelist_for_disa.attr, + &dev_attr_whitelist_for_serial.attr, + &dev_attr_cards.attr, +#if defined(CONFIG_USB_HW_PARAM) + &dev_attr_usb_hw_param.attr, + &dev_attr_hw_param.attr, +#endif + &dev_attr_usb_request_action.attr, +#if defined(CONFIG_USB_LPM_CHARGING_SYNC) + &dev_attr_lpm_charging_type_done.attr, +#endif + &dev_attr_usb_sl.attr, + NULL, +}; + +static struct attribute_group usb_notify_attr_grp = { + .attrs = usb_notify_attrs, +}; + +static int create_usb_notify_class(void) +{ + if (!usb_notify_data.usb_notify_class) { + usb_notify_data.usb_notify_class + = class_create(THIS_MODULE, "usb_notify"); + if (IS_ERR(usb_notify_data.usb_notify_class)) + return PTR_ERR(usb_notify_data.usb_notify_class); + atomic_set(&usb_notify_data.device_count, 0); + } + + return 0; +} + +int usb_notify_dev_register(struct usb_notify_dev *udev) +{ + int ret; + + if (!usb_notify_data.usb_notify_class) { + ret = create_usb_notify_class(); + if (ret < 0) + return ret; + } + +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + mutex_init(&udev->lockscreen_enabled_lock); +#endif + + udev->index = atomic_inc_return(&usb_notify_data.device_count); + udev->dev = device_create(usb_notify_data.usb_notify_class, NULL, + MKDEV(0, udev->index), NULL, "%s", udev->name); + if (IS_ERR(udev->dev)) + return PTR_ERR(udev->dev); + + udev->disable_state = 0; + udev->usb_data_enabled = 1; + strncpy(udev->disable_state_cmd, "OFF", + sizeof(udev->disable_state_cmd)-1); + dev_set_drvdata(udev->dev, udev); + + ret = sysfs_create_group(&udev->dev->kobj, &usb_notify_attr_grp); + if (ret < 0) { + device_destroy(usb_notify_data.usb_notify_class, + MKDEV(0, udev->index)); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(usb_notify_dev_register); + +void usb_notify_dev_unregister(struct usb_notify_dev *udev) +{ + sysfs_remove_group(&udev->dev->kobj, &usb_notify_attr_grp); + device_destroy(usb_notify_data.usb_notify_class, MKDEV(0, udev->index)); + dev_set_drvdata(udev->dev, NULL); +} +EXPORT_SYMBOL_GPL(usb_notify_dev_unregister); + +int usb_notify_class_init(void) +{ + return create_usb_notify_class(); +} +EXPORT_SYMBOL_GPL(usb_notify_class_init); + +void usb_notify_class_exit(void) +{ + class_destroy(usb_notify_data.usb_notify_class); +} +EXPORT_SYMBOL_GPL(usb_notify_class_exit); + diff --git a/drivers/usb/notify/usb_notify_sysfs.h b/drivers/usb/notify/usb_notify_sysfs.h new file mode 100755 index 000000000000..9e4e27db9150 --- /dev/null +++ b/drivers/usb/notify/usb_notify_sysfs.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2015-2023 Samsung Electronics Co. Ltd. + * + * 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. + */ + + /* usb notify layer v4.0 */ + +#ifndef __LINUX_USB_NOTIFY_SYSFS_H__ +#define __LINUX_USB_NOTIFY_SYSFS_H__ + +#define MAX_DISABLE_STR_LEN 32 +#define MAX_WHITELIST_STR_LEN 256 +#define MAX_USB_AUDIO_CARDS 15 +/* one card needs 9 byte ex) */ +#define MAX_CARD_STR_LEN (MAX_USB_AUDIO_CARDS * 9) +#define MAX_CLASS_TYPE_NUM USB_CLASS_VENDOR_SPEC +#define MAX_USB_SPEED_STR_LEN 15 + +#define ALLOWLIST_PREFIX_SIZE 5 +#define MAX_VID_PID_STRING 10 +#define MAX_ALLOWLIST_DEVICE_COUNT 100 +#define MAX_ALLOWLIST_DEVICE_BUFFER_INDEX (MAX_ALLOWLIST_DEVICE_COUNT*2) +#define MAX_ALLOWLIST_BUFFER (MAX_VID_PID_STRING * MAX_ALLOWLIST_DEVICE_COUNT + ALLOWLIST_PREFIX_SIZE) + +enum u_interface_class_type { + U_CLASS_PER_INTERFACE = 1, + U_CLASS_AUDIO, + U_CLASS_COMM, + U_CLASS_HID, + U_CLASS_PHYSICAL, + U_CLASS_STILL_IMAGE, + U_CLASS_PRINTER, + U_CLASS_MASS_STORAGE, + U_CLASS_HUB, + U_CLASS_CDC_DATA, + U_CLASS_CSCID, + U_CLASS_CONTENT_SEC, + U_CLASS_VIDEO, + U_CLASS_WIRELESS_CONTROLLER, + U_CLASS_MISC, + U_CLASS_APP_SPEC, + U_CLASS_VENDOR_SPEC, +}; + +struct usb_audio_info { + int cards; + int bundle; +}; + +struct usb_notify_dev { + const char *name; + struct device *dev; + struct otg_notify *o_notify; + int index; + unsigned int request_action; + unsigned int lpm_charging_type_done; + unsigned long usb_data_enabled; + unsigned long disable_state; + unsigned long secure_lock; + bool first_restrict; + int (*set_disable)(struct usb_notify_dev *udev, int param); + void (*set_mdm)(struct usb_notify_dev *udev, int mdm_disable); + void (*set_mdm_for_id)(struct usb_notify_dev *udev, int mdm_disable); + void (*set_mdm_for_serial)(struct usb_notify_dev *udev, int mdm_disable); + int (*control_usb_max_speed)(struct usb_notify_dev *udev, int speed); + unsigned long (*fp_hw_param_manager)(int param); + int (*set_lock_state)(struct usb_notify_dev *udev); + char disable_state_cmd[MAX_DISABLE_STR_LEN]; + char whitelist_str[MAX_WHITELIST_STR_LEN]; + int whitelist_array_for_mdm[MAX_CLASS_TYPE_NUM+1]; + int whitelist_array_for_mdm_for_id[MAX_WHITELIST_STR_LEN]; + char whitelist_str_for_id[MAX_WHITELIST_STR_LEN]; + char whitelist_array_for_mdm_for_serial[MAX_WHITELIST_STR_LEN]; + struct usb_audio_info usb_audio_cards[MAX_USB_AUDIO_CARDS]; + int allowlist_array_lockscreen_enabled_id[MAX_ALLOWLIST_DEVICE_BUFFER_INDEX]; + char allowlist_str_lockscreen_enabled_id[MAX_ALLOWLIST_BUFFER]; + struct mutex lockscreen_enabled_lock; +}; + +extern int usb_notify_dev_uevent(struct usb_notify_dev *udev, + char *envp_ext[]); +extern int usb_notify_dev_register(struct usb_notify_dev *ndev); +extern void usb_notify_dev_unregister(struct usb_notify_dev *ndev); +extern int usb_notify_class_init(void); +extern void usb_notify_class_exit(void); +#endif + diff --git a/drivers/usb/notify/usblog_proc_notify.c b/drivers/usb/notify/usblog_proc_notify.c new file mode 100755 index 000000000000..841c9511a768 --- /dev/null +++ b/drivers/usb/notify/usblog_proc_notify.c @@ -0,0 +1,2003 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * + * Copyright (C) 2016-2023 Samsung, Inc. + * Author: Dongrak Shin + * + */ + + /* usb notify layer v4.0 */ + + #define pr_fmt(fmt) "usb_notify: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* clock.h should be included since kernel 4.14 */ +#include +#include + +#define USBLOG_MAX_BUF_SIZE (1 << 6) /* 64 */ +#define USBLOG_MAX_BUF2_SIZE (1 << 7) /* 128 */ +#define USBLOG_MAX_BUF3_SIZE (1 << 8) /* 256 */ +#define USBLOG_MAX_BUF4_SIZE (1 << 9) /* 512 */ +#define USBLOG_MAX_STRING_SIZE (1 << 5) /* 32 */ +#define USBLOG_CMP_INDEX 3 +#define USBLOG_MAX_STORE_PORT (1 << 6) /* 64 */ + +#define USBLOG_CCIC_BUFFER_SIZE USBLOG_MAX_BUF4_SIZE +#define USBLOG_MODE_BUFFER_SIZE USBLOG_MAX_BUF_SIZE +#define USBLOG_STATE_BUFFER_SIZE USBLOG_MAX_BUF3_SIZE +#define USBLOG_EVENT_BUFFER_SIZE USBLOG_MAX_BUF_SIZE +#define USBLOG_PORT_BUFFER_SIZE USBLOG_MAX_BUF2_SIZE +#define USBLOG_PCM_BUFFER_SIZE USBLOG_MAX_BUF_SIZE +#define USBLOG_EXTRA_BUFFER_SIZE USBLOG_MAX_BUF2_SIZE + +#define USBLOG_INDEX 0xABCDABCD + +#define USBLOG_CC1 0xffffffff00000000 +#define USBLOG_CC2 0x00000000ffffffff + +#define PRINTK_USB_BUF_SIZE SZ_64K +#define PRINTK_USB_BOOT_BUF_SIZE SZ_64K +#define PRINTK_USB_MAX_STRING_SIZE (1 << 8) /* 256 */ + +struct usblog_rtc_time { + int tm_sec; + int tm_min; + int tm_hour; + int tm_mday; + int tm_mon; +}; + +struct ccic_buf { + unsigned long long ts_nsec; + int cc_type; + uint64_t noti; +}; + +struct mode_buf { + unsigned long long ts_nsec; + char usbmode_str[USBLOG_MAX_STRING_SIZE]; +}; + +struct state_buf { + unsigned long long ts_nsec; + int usbstate; +}; + +struct event_buf { + unsigned long long ts_nsec; + unsigned long event; + int enable; +}; + +struct port_buf { + unsigned long long ts_nsec; + int type; + uint16_t param1; + uint16_t param2; + uint16_t count; +}; + +struct port_count { + uint16_t vid; + uint16_t pid; + uint16_t count; +}; + +struct pcm_buf { + struct usblog_rtc_time rt; + unsigned long long ts_nsec; + int type; + int enable; +}; + +struct extra_buf { + unsigned long long ts_nsec; + int event; +}; + +struct usblog_rtc_buf { + struct usblog_rtc_time ccic_buf_rt[USBLOG_CCIC_BUFFER_SIZE]; + struct usblog_rtc_time mode_buf_rt[USBLOG_MODE_BUFFER_SIZE]; + struct usblog_rtc_time state_buf_rt[USBLOG_STATE_BUFFER_SIZE]; + struct usblog_rtc_time event_buf_rt[USBLOG_EVENT_BUFFER_SIZE]; + struct usblog_rtc_time port_buf_rt[USBLOG_PORT_BUFFER_SIZE]; + struct usblog_rtc_time extra_buf_rt[USBLOG_EXTRA_BUFFER_SIZE]; +}; + +struct usblog_buf { + unsigned long long ccic_count; + unsigned long long mode_count; + unsigned long long state_count; + unsigned long long event_count; + unsigned long long port_count; + unsigned long long extra_count; + unsigned long ccic_index; + unsigned long mode_index; + unsigned long state_index; + unsigned long event_index; + unsigned long port_index; + unsigned long extra_index; + struct ccic_buf ccic_buffer[USBLOG_CCIC_BUFFER_SIZE]; + struct mode_buf mode_buffer[USBLOG_MODE_BUFFER_SIZE]; + struct state_buf state_buffer[USBLOG_STATE_BUFFER_SIZE]; + struct event_buf event_buffer[USBLOG_EVENT_BUFFER_SIZE]; + struct port_buf port_buffer[USBLOG_PORT_BUFFER_SIZE]; + struct port_count store_port_cnt[USBLOG_MAX_STORE_PORT]; + struct extra_buf extra_buffer[USBLOG_EXTRA_BUFFER_SIZE]; +}; + +struct usblog_vm_buf { + unsigned long long pcm_count; + unsigned long pcm_index; + struct pcm_buf pcm_buffer[USBLOG_PCM_BUFFER_SIZE]; +}; + +struct ccic_version { + unsigned char hw_version[4]; + unsigned char sw_main[3]; + unsigned char sw_boot; +}; + +struct printk_usb_data { + unsigned long usb_index; + unsigned long usb_tail; + unsigned long usb_snap_index; + unsigned long usb_snap_tail; + unsigned long usb_boot_index; + bool usb_full; + bool usb_snap_full; + bool usb_boot_full; + bool snap_done; +}; + +struct usblog_root_str { + unsigned long usblog_index; + struct usblog_buf *usblog_buffer; + struct usblog_vm_buf *usblog_vm_buffer; + struct usblog_rtc_buf *usblog_rtc_buffer; + struct ccic_version ccic_ver; + struct ccic_version ccic_bin_ver; + struct printk_usb_data prk_usb; + char tcpc_name[USBLOG_MAX_STRING_SIZE]; + spinlock_t usblog_lock; + int init; +}; + +struct ccic_type { + uint64_t src:4; + uint64_t dest:4; + uint64_t id:8; + uint64_t sub1:16; + uint64_t sub2:16; + uint64_t sub3:16; +}; + +static struct usblog_root_str usblog_root; +static char printk_usb_boot_buf[PRINTK_USB_BOOT_BUF_SIZE]; +static char printk_usb_buf[PRINTK_USB_BUF_SIZE]; +static char printk_usb_snap_buf[PRINTK_USB_BUF_SIZE]; + + +static const char *usbstate_string(enum usblog_state usbstate) +{ + switch (usbstate) { + case NOTIFY_CONFIGURED: + return "CONFIGURED"; + case NOTIFY_CONNECTED: + return "CONNECTED"; + case NOTIFY_DISCONNECTED: + return "DISCONNECTED"; + case NOTIFY_RESET: + return "RESET"; + case NOTIFY_RESET_FULL: + return "RESET : FULL"; + case NOTIFY_RESET_HIGH: + return "RESET: HIGH"; + case NOTIFY_RESET_SUPER: + return "RESET : SUPER"; + case NOTIFY_PULLUP: + return "VBUS_PULLUP (EN OR DIS)"; + case NOTIFY_PULLUP_ENABLE: + return "VBUS_PULLUP_EN"; + case NOTIFY_PULLUP_EN_SUCCESS: + return "VBUS_PULLUP_EN : S"; + case NOTIFY_PULLUP_EN_FAIL: + return "VBUS_PULLUP_EN : F"; + case NOTIFY_PULLUP_DISABLE: + return "VBUS_PULLUP_DIS"; + case NOTIFY_PULLUP_DIS_SUCCESS: + return "VBUS_PULLUP_DIS : S"; + case NOTIFY_PULLUP_DIS_FAIL: + return "VBUS_PULLUP_DIS : F"; + case NOTIFY_VBUS_SESSION: + return "VBUS_SESSION (EN OR DIS)"; + case NOTIFY_VBUS_SESSION_ENABLE: + return "VBUS_SESSION_EN"; + case NOTIFY_VBUS_EN_SUCCESS: + return "VBUS_SESSION_EN : S"; + case NOTIFY_VBUS_EN_FAIL: + return "VBUS_SESSION_EN : F"; + case NOTIFY_VBUS_SESSION_DISABLE: + return "VBUS_SESSIOIN_DIS"; + case NOTIFY_VBUS_DIS_SUCCESS: + return "VBUS_SESSION_DIS : S"; + case NOTIFY_VBUS_DIS_FAIL: + return "VBUS_SESSION_DIS : F"; + case NOTIFY_ACCSTART: + return "ACCSTART"; + case NOTIFY_HIGH: + return "HIGH SPEED"; + case NOTIFY_SUPER: + return "SUPER SPEED"; + case NOTIFY_GET_DES: + return "GET_DES"; + case NOTIFY_SET_CON: + return "SET_CON"; + case NOTIFY_CONNDONE_SSP: + return "CONNDONE SSP"; + case NOTIFY_CONNDONE_SS: + return "CONNDONE SS"; + case NOTIFY_CONNDONE_HS: + return "CONNDONE HS"; + case NOTIFY_CONNDONE_FS: + return "CONNDONE FS"; + case NOTIFY_CONNDONE_LS: + return "CONNDONE LS"; + default: + return "UNDEFINED"; + } +} + +static const char *usbstatus_string(enum usblog_status usbstatus) +{ + switch (usbstatus) { + case NOTIFY_DETACH: + return "DETACH"; + case NOTIFY_ATTACH_DFP: + return "ATTACH_DFP"; + case NOTIFY_ATTACH_UFP: + return "ATTACH_UFP"; + case NOTIFY_ATTACH_DRP: + return "ATTACH_DRP"; + default: + return "UNDEFINED"; + } +} + +static const char *ccic_dev_string(enum ccic_device dev) +{ + switch (dev) { + case NOTIFY_DEV_INITIAL: + return "INITIAL"; + case NOTIFY_DEV_USB: + return "USB"; + case NOTIFY_DEV_BATTERY: + return "BATTERY"; + case NOTIFY_DEV_PDIC: + return "PDIC"; + case NOTIFY_DEV_MUIC: + return "MUIC"; + case NOTIFY_DEV_CCIC: + return "CCIC"; + case NOTIFY_DEV_MANAGER: + return "MANAGER"; + case NOTIFY_DEV_DP: + return "DP"; + case NOTIFY_DEV_USB_DP: + return "USB_DP"; + case NOTIFY_DEV_SUB_BATTERY: + return "BATTERY2"; + case NOTIFY_DEV_SECOND_MUIC: + return "MUIC2"; + case NOTIFY_DEV_DEDICATED_MUIC: + return "DEDICATED MUIC"; + case NOTIFY_DEV_ALL: + return "DEV ALL"; + default: + return "UNDEFINED"; + } +} + +static const char *ccic_id_string(enum ccic_id id) +{ + switch (id) { + case NOTIFY_ID_INITIAL: + return "ID_INITIAL"; + case NOTIFY_ID_ATTACH: + return "ID_CONNECT"; + case NOTIFY_ID_RID: + return "ID_RID"; + case NOTIFY_ID_USB: + return "ID_USB"; + case NOTIFY_ID_POWER_STATUS: + return "ID_POWER_STATUS"; + case NOTIFY_ID_WATER: + return "ID_WATER"; + case NOTIFY_ID_VCONN: + return "ID_VCONN"; + case NOTIFY_ID_OTG: + return "ID_OTG"; + case NOTIFY_ID_TA: + return "ID_TA"; + case NOTIFY_ID_DP_CONNECT: + return "ID_DP_CONNECT"; + case NOTIFY_ID_DP_HPD: + return "ID_DP_HPD"; + case NOTIFY_ID_DP_LINK_CONF: + return "ID_DP_LINK_CONF"; + case NOTIFY_ID_USB_DP: + return "ID_USB_DP"; + case NOTIFY_ID_ROLE_SWAP: + return "ID_ROLE_SWAP"; + case NOTIFY_ID_FAC: + return "ID_FAC"; + case NOTIFY_ID_CC_PIN_STATUS: + return "ID_PIN_STATUS"; + case NOTIFY_ID_WATER_CABLE: + return "ID_WATER_CABLE"; + case NOTIFY_ID_POFF_WATER: + return "ID_POWEROFF_WATER"; + case NOTIFY_ID_DEVICE_INFO: + return "ID_DEVICE_INFO"; + case NOTIFY_ID_SVID_INFO: + return "ID_SVID_INFO"; + case NOTIFY_ID_CLEAR_INFO: + return "ID_CLEAR_INFO"; + default: + return "UNDEFINED"; + } +} + +static const char *ccic_rid_string(enum ccic_rid rid) +{ + switch (rid) { + case NOTIFY_RID_UNDEFINED: + return "RID_UNDEFINED"; +#if defined(CONFIG_USB_CCIC_NOTIFIER_USING_QC) + case NOTIFY_RID_GND: + return "RID_GND"; + case NOTIFY_RID_056K: + return "RID_056K"; +#else + case NOTIFY_RID_000K: + return "RID_000K"; + case NOTIFY_RID_001K: + return "RID_001K"; +#endif + case NOTIFY_RID_255K: + return "RID_255K"; + case NOTIFY_RID_301K: + return "RID_301K"; + case NOTIFY_RID_523K: + return "RID_523K"; + case NOTIFY_RID_619K: + return "RID_619K"; + case NOTIFY_RID_OPEN: + return "RID_OPEN"; + default: + return "UNDEFINED"; + } +} + +static const char *ccic_con_string(enum ccic_con con) +{ + switch (con) { + case NOTIFY_CON_DETACH: + return "DETACHED"; + case NOTIFY_CON_ATTACH: + return "ATTACHED"; + default: + return "UNDEFINED"; + } +} + +static const char *ccic_rprd_string(enum ccic_rprd rprd) +{ + switch (rprd) { + case NOTIFY_RD: + return "RD"; + case NOTIFY_RP: + return "RP"; + default: + return "UNDEFINED"; + } +} + +static const char *ccic_rpstatus_string(enum ccic_rpstatus rprd) +{ + switch (rprd) { + case NOTIFY_RP_NONE: + return "NONE"; + case NOTIFY_RP_56K: + return "RP_56K"; + case NOTIFY_RP_22K: + return "RP_22K"; + case NOTIFY_RP_10K: + return "RP_10K"; + case NOTIFY_RP_ABNORMAL: + return "RP_ABNORMAL"; + default: + return "UNDEFINED"; + } +} + +static const char *ccic_hpd_string(enum ccic_hpd hpd) +{ + switch (hpd) { + case NOTIFY_HPD_LOW: + return "LOW"; + case NOTIFY_HPD_HIGH: + return "HIGH"; + case NOTIFY_HPD_IRQ: + return "IRQ"; + default: + return "UNDEFINED"; + } +} + +static const char *ccic_pin_string(enum ccic_pin_assignment pin) +{ + switch (pin) { + case NOTIFY_DP_PIN_UNKNOWN: + return "UNKNOWN"; + case NOTIFY_DP_PIN_A: + return "DP_PIN_A"; + case NOTIFY_DP_PIN_B: + return "DP_PIN_B"; + case NOTIFY_DP_PIN_C: + return "DP_PIN_C"; + case NOTIFY_DP_PIN_D: + return "DP_PIN_D"; + case NOTIFY_DP_PIN_E: + return "DP_PIN_E"; + case NOTIFY_DP_PIN_F: + return "DP_PIN_F"; + default: + return "UNKNOWN"; + } +} + +static const char *ccic_alternatemode_string(uint64_t id) +{ + if ((id & ALTERNATE_MODE_READY) && (id & ALTERNATE_MODE_START)) + return "READY & START"; + else if ((id & ALTERNATE_MODE_READY) && (id & ALTERNATE_MODE_STOP)) + return "READY & STOP"; + else if (id & ALTERNATE_MODE_READY) + return "MODE READY"; + else if (id & ALTERNATE_MODE_START) + return "START"; + else if (id & ALTERNATE_MODE_STOP) + return "STOP"; + else if (id & ALTERNATE_MODE_RESET) + return "RESET"; + else + return "UNDEFINED"; +} + +static const char *ccic_voltage_string(uint8_t cc) +{ + switch (cc) { + case NOTIFY_CC_VOLT_OPEN: + return "open"; + case NOTIFY_CC_VOLT_RA: + return "ra"; + case NOTIFY_CC_VOLT_RD: + return "rd"; + case NOTIFY_CC_VOLT_SNK_DFT: + return "rp 56k"; + case NOTIFY_CC_VOLT_SNK_1_5: + return "rp 22k"; + case NOTIFY_CC_VOLT_SNK_3_0: + return "rp 10k"; + case NOTIFY_CC_DRP_TOGGLING: + return "cc toggle"; + default: + return "undefined"; + } +} + +static const char *ccic_pinstatus_string(enum ccic_pin_status pinstatus) +{ + switch (pinstatus) { + case NOTIFY_PIN_NOTERMINATION: + return "NO TERMINATION"; + case NOTIFY_PIN_CC1_ACTIVE: + return "CC1_ACTIVE"; + case NOTIFY_PIN_CC2_ACTIVE: + return "CC2_ACTIVE"; + case NOTIFY_PIN_AUDIO_ACCESSORY: + return "AUDIO_ACCESSORY"; + default: + return "ETC"; + } +} + +static const char *extra_string(enum extra event) +{ + switch (event) { + case NOTIFY_EXTRA_USBKILLER: + return "USB_KILLER"; + case NOTIFY_EXTRA_HARDRESET_SENT: + return "PDIC HARDRESET_SENT"; + case NOTIFY_EXTRA_HARDRESET_RECEIVED: + return "PDIC HARDRESET_RECEIVED"; + case NOTIFY_EXTRA_SYSERROR_BOOT_WDT: + return "PDIC WDT RESET"; + case NOTIFY_EXTRA_SYSMSG_BOOT_POR: + return "PDIC POR RESET"; + case NOTIFY_EXTRA_SYSMSG_CC_SHORT: + return "CC VBUS SHORT"; + case NOTIFY_EXTRA_SYSMSG_SBU_GND_SHORT: + return "SBU GND SHORT"; + case NOTIFY_EXTRA_SYSMSG_SBU_VBUS_SHORT: + return "SBU VBUS SHORT"; + case NOTIFY_EXTRA_UVDM_TIMEOUT: + return "UVDM TIMEOUT"; + case NOTIFY_EXTRA_CCOPEN_REQ_SET: + return "CC OPEN SET"; + case NOTIFY_EXTRA_CCOPEN_REQ_CLEAR: + return "CC OPEN CLEAR"; + case NOTIFY_EXTRA_USB_ANALOGAUDIO: + return "USB ANALOG AUDIO"; + case NOTIFY_EXTRA_USBHOST_OVERCURRENT: + return "USB HOST OVERCURRENT"; + case NOTIFY_EXTRA_ROOTHUB_SUSPEND_FAIL: + return "ROOTHUB SUSPEND FAIL"; + case NOTIFY_EXTRA_PORT_SUSPEND_FAIL: + return "PORT SUSPEND FAIL"; + case NOTIFY_EXTRA_PORT_SUSPEND_WAKEUP_FAIL: + return "PORT REMOTE WAKEUP FAIL"; + case NOTIFY_EXTRA_PORT_SUSPEND_LTM_FAIL: + return "PORT LTM FAIL"; + case NOTIFY_EXTRA_VIB_FW_LOAD_SUCCESS: + return "VIBRATOR FIRMWARE LOAD SUCCESS"; + default: + return "ETC"; + } +} + +static const char * const tcpm_states[] = { + NOTIFY_FOREACH_STATE(NOTIFY_GENERATE_STRING) +}; + +static void usblog_get_rt(struct usblog_rtc_time *rt) +{ + struct rtc_time tm; + struct timespec64 ts64; + + ktime_get_real_ts64(&ts64); + rtc_time64_to_tm(ts64.tv_sec - (sys_tz.tz_minuteswest * 60), &tm); + rt->tm_mon = tm.tm_mon + 1; + rt->tm_mday = tm.tm_mday; + rt->tm_hour = tm.tm_hour; + rt->tm_min = tm.tm_min; + rt->tm_sec = tm.tm_sec; +} + +static void print_ccic_event(struct seq_file *m, unsigned long long ts, + unsigned long rem_nsec, int cc_type, uint64_t *noti) +{ + struct ccic_type type = *(struct ccic_type *)noti; + uint8_t cc1 = 0, cc2 = 0; + int cable = type.sub3; + + switch (cc_type) { + case NOTIFY_FUNCSTATE: + seq_printf(m, "[%5lu.%06lu] function state = %llu\n", + (unsigned long)ts, rem_nsec / 1000, *noti); + break; + case NOTIFY_TCPMSTATE: + if (*noti >= ARRAY_SIZE(tcpm_states)) { + seq_printf(m, "[%5lu.%06lu] tcpm state = %llu\n", + (unsigned long)ts, rem_nsec / 1000, *noti); + } else { + seq_printf(m, "[%5lu.%06lu] tcpm state = %s(%llu)\n", + (unsigned long)ts, rem_nsec / 1000, + tcpm_states[*noti], *noti); + } + break; + case NOTIFY_CCSTATE: + cc1 = (*noti & USBLOG_CC1) >> 32; + cc2 = (*noti & USBLOG_CC2); + + seq_printf(m, "[%5lu.%06lu] cc1=%s cc2=%s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_voltage_string(cc1), ccic_voltage_string(cc2)); + break; + case NOTIFY_ALTERNATEMODE: + seq_printf(m, "[%5lu.%06lu] ccic alternate mode is %s 0x%04llx\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_alternatemode_string(*noti), *noti); + break; + case NOTIFY_CCIC_EVENT: + if (type.id == NOTIFY_ID_ATTACH) { + if (type.src == NOTIFY_DEV_MUIC + || type.src == NOTIFY_DEV_SECOND_MUIC + || type.src == NOTIFY_DEV_DEDICATED_MUIC) + seq_printf(m, + "[%5lu.%06lu] ccic notify: id=%s src=%s dest=%s rprd=%s cable=%d %s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + ccic_rprd_string(type.sub2), + cable, ccic_con_string(type.sub1)); + else + seq_printf(m, + "[%5lu.%06lu] ccic notify: id=%s src=%s dest=%s rprd=%s rp status=%s %s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + ccic_rprd_string(type.sub2), + ccic_rpstatus_string(type.sub3), + ccic_con_string(type.sub1)); + } else if (type.id == NOTIFY_ID_RID) + seq_printf(m, "[%5lu.%06lu] ccic notify: id=%s src=%s dest=%s rid=%s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + ccic_rid_string(type.sub1)); + else if (type.id == NOTIFY_ID_USB) + seq_printf(m, "[%5lu.%06lu] ccic notify: id=%s src=%s dest=%s status=%s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + usbstatus_string(type.sub2)); + else if (type.id == NOTIFY_ID_POWER_STATUS) + seq_printf(m, "[%5lu.%06lu] ccic notify: id=%s src=%s dest=%s %s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + ccic_con_string(type.sub1)); + else if (type.id == NOTIFY_ID_WATER) + seq_printf(m, "[%5lu.%06lu] ccic notify: id=%s src=%s dest=%s %s detected\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + type.sub1 ? "WATER":"DRY"); + else if (type.id == NOTIFY_ID_VCONN) + seq_printf(m, "[%5lu.%06lu] ccic notify: id=%s src=%s dest=%s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest)); + else if (type.id == NOTIFY_ID_OTG) + seq_printf(m, "[%5lu.%06lu] ccic notify: id=%s src=%s dest=%s %s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + type.sub2 ? "Enable":"Disable"); + else if (type.id == NOTIFY_ID_TA) + seq_printf(m, "[%5lu.%06lu] ccic notify: id=%s src=%s dest=%s %s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + ccic_con_string(type.sub1)); + else if (type.id == NOTIFY_ID_DP_CONNECT) + seq_printf(m, "[%5lu.%06lu] ccic notify: id=%s src=%s dest=%s 0x%04x/0x%04x %s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + type.sub2, + type.sub3, + ccic_con_string(type.sub1)); + else if (type.id == NOTIFY_ID_DP_HPD) + seq_printf(m, "[%5lu.%06lu] ccic notify: id=%s src=%s dest=%s hpd=%s irq=%s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + ccic_hpd_string(type.sub1), + type.sub2 ? "VALID":"NONE"); + else if (type.id == NOTIFY_ID_DP_LINK_CONF) + seq_printf(m, "[%5lu.%06lu] ccic notify: id=%s src=%s dest=%s PIN-assign=%s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + ccic_pin_string(type.sub1)); + else if (type.id == NOTIFY_ID_USB_DP) + seq_printf(m, "[%5lu.%06lu] ccic notify: id=%s src=%s dest=%s CON=%d HS=%d\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + type.sub1, + type.sub2); + else if (type.id == NOTIFY_ID_ROLE_SWAP) + seq_printf(m, "[%5lu.%06lu] ccic notify: id=%s src=%s dest=%s sub1=%d sub2=%d\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + type.sub1, + type.sub2); + else if (type.id == NOTIFY_ID_FAC) + seq_printf(m, "[%5lu.%06lu] ccic notify: id=%s src=%s dest=%s ErrState=%d\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + type.sub1); + else if (type.id == NOTIFY_ID_CC_PIN_STATUS) + seq_printf(m, "[%5lu.%06lu] ccic notify: id=%s src=%s dest=%s pinstatus=%s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + ccic_pinstatus_string(type.sub1)); + else if (type.id == NOTIFY_ID_WATER_CABLE) + seq_printf(m, "[%5lu.%06lu] ccic notify: id=%s src=%s dest=%s %s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + ccic_con_string(type.sub1)); + else if (type.id == NOTIFY_ID_POFF_WATER) + seq_printf(m, "[%5lu.%06lu] ccic notify: id=%s src=%s dest=%s POWEROFF %s detected\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + type.sub1 ? "WATER":"DRY"); + else if (type.id == NOTIFY_ID_DEVICE_INFO) + seq_printf(m, "[%5lu.%06lu] ccic notify: id=%s src=%s dest=%s vid=%04x pid=%04x bcd=%04x\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + type.sub1, type.sub2, type.sub3); + else if (type.id == NOTIFY_ID_SVID_INFO) + seq_printf(m, "[%5lu.%06lu] ccic notify: id=%s src=%s dest=%s svid=%04x\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + type.sub1); + else if (type.id == NOTIFY_ID_CLEAR_INFO) + seq_printf(m, "[%5lu.%06lu] ccic notify: id=%s src=%s dest=%s clear %s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + (type.sub1 == NOTIFY_ID_DEVICE_INFO) ? "DEVICE INFO" : "SVID INFO"); + else + seq_printf(m, "[%5lu.%06lu] ccic notify: id=%s src=%s dest=%s rprd=%s %s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + ccic_rprd_string(type.sub2), + ccic_con_string(type.sub1)); + break; + case NOTIFY_MANAGER: + if (type.id == NOTIFY_ID_ATTACH) { + if (type.src == NOTIFY_DEV_MUIC + || type.src == NOTIFY_DEV_SECOND_MUIC + || type.src == NOTIFY_DEV_DEDICATED_MUIC) + seq_printf(m, + "[%5lu.%06lu] manager notify: id=%s src=%s dest=%s rprd=%s cable=%d %s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + ccic_rprd_string(type.sub2), + cable, ccic_con_string(type.sub1)); + else + seq_printf(m, + "[%5lu.%06lu] manager notify: id=%s src=%s dest=%s rprd=%s rp status=%s %s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + ccic_rprd_string(type.sub2), + ccic_rpstatus_string(type.sub3), + ccic_con_string(type.sub1)); + } else if (type.id == NOTIFY_ID_RID) + seq_printf(m, "[%5lu.%06lu] manager notify: id=%s src=%s dest=%s rid=%s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + ccic_rid_string(type.sub1)); + else if (type.id == NOTIFY_ID_USB) + seq_printf(m, "[%5lu.%06lu] manager notify: id=%s src=%s dest=%s status=%s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + usbstatus_string(type.sub2)); + else if (type.id == NOTIFY_ID_POWER_STATUS) + seq_printf(m, "[%5lu.%06lu] manager notify: id=%s src=%s dest=%s %s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + ccic_con_string(type.sub1)); + else if (type.id == NOTIFY_ID_WATER) + seq_printf(m, "[%5lu.%06lu] manager notify: id=%s src=%s dest=%s %s detected\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + type.sub1 ? "WATER":"DRY"); + else if (type.id == NOTIFY_ID_VCONN) + seq_printf(m, "[%5lu.%06lu] manager notify: id=%s src=%s dest=%s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest)); + else if (type.id == NOTIFY_ID_OTG) + seq_printf(m, "[%5lu.%06lu] ccic notify: id=%s src=%s dest=%s %s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + type.sub2 ? "Enable":"Disable"); + else if (type.id == NOTIFY_ID_TA) + seq_printf(m, "[%5lu.%06lu] ccic notify: id=%s src=%s dest=%s %s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + ccic_con_string(type.sub1)); + else if (type.id == NOTIFY_ID_DP_CONNECT) + seq_printf(m, "[%5lu.%06lu] manager notify: id=%s src=%s dest=%s 0x%04x/0x%04x %s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + type.sub2, + type.sub3, + ccic_con_string(type.sub1)); + else if (type.id == NOTIFY_ID_DP_HPD) + seq_printf(m, "[%5lu.%06lu] manager notify: id=%s src=%s dest=%s hpd=%s irq=%s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + ccic_hpd_string(type.sub1), + type.sub2 ? "VALID":"NONE"); + else if (type.id == NOTIFY_ID_DP_LINK_CONF) + seq_printf(m, "[%5lu.%06lu] manager notify: id=%s src=%s dest=%s PIN-assign=%s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + ccic_pin_string(type.sub1)); + else if (type.id == NOTIFY_ID_USB_DP) + seq_printf(m, "[%5lu.%06lu] manager notify: id=%s src=%s dest=%s CON=%d HS=%d\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + type.sub1, + type.sub2); + else if (type.id == NOTIFY_ID_ROLE_SWAP) + seq_printf(m, "[%5lu.%06lu] manager notify: id=%s src=%s dest=%s sub1=%d sub2=%d\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + type.sub1, + type.sub2); + else if (type.id == NOTIFY_ID_FAC) + seq_printf(m, "[%5lu.%06lu] manager notify: id=%s src=%s dest=%s ErrState=%d\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + type.sub1); + else if (type.id == NOTIFY_ID_CC_PIN_STATUS) + seq_printf(m, "[%5lu.%06lu] manager notify: id=%s src=%s dest=%s pinstatus=%s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + ccic_pinstatus_string(type.sub1)); + else if (type.id == NOTIFY_ID_WATER_CABLE) + seq_printf(m, "[%5lu.%06lu] manager notify: id=%s src=%s dest=%s %s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + ccic_con_string(type.sub1)); + else if (type.id == NOTIFY_ID_DEVICE_INFO) + seq_printf(m, "[%5lu.%06lu] manager notify: id=%s src=%s dest=%s vid=%04x pid=%04x bcd=%04x\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + type.sub1, type.sub2, type.sub3); + else if (type.id == NOTIFY_ID_SVID_INFO) + seq_printf(m, "[%5lu.%06lu] manager notify: id=%s src=%s dest=%s svid=%04x\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + type.sub1); + else if (type.id == NOTIFY_ID_CLEAR_INFO) + seq_printf(m, "[%5lu.%06lu] manager notify: id=%s src=%s dest=%s clear %s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + (type.sub1 == NOTIFY_ID_DEVICE_INFO) ? "DEVICE INFO" : "SVID INFO"); + else + seq_printf(m, "[%5lu.%06lu] manager notify: id=%s src=%s dest=%s rprd=%s %s\n", + (unsigned long)ts, rem_nsec / 1000, + ccic_id_string(type.id), + ccic_dev_string(type.src), + ccic_dev_string(type.dest), + ccic_rprd_string(type.sub2), + ccic_con_string(type.sub1)); + break; + default: + seq_printf(m, "[%5lu.%06lu] undefined event\n", + (unsigned long)ts, rem_nsec / 1000); + break; + } +} + +static void print_port_string(struct seq_file *m, unsigned long long ts, + unsigned long rem_nsec, int type, uint16_t param1, + uint16_t param2, uint16_t cnt) +{ + switch (type) { + case NOTIFY_PORT_CONNECT: + seq_printf(m, "[%5lu.%06lu] port connect - VID:0x%04x PID:0x%04x cnt:%d\n", + (unsigned long)ts, rem_nsec / 1000, + param1, param2, cnt); + break; + case NOTIFY_PORT_DISCONNECT: + seq_printf(m, "[%5lu.%06lu] port disconnect - VID:0x%04x PID:0x%04x\n", + (unsigned long)ts, rem_nsec / 1000, param1, param2); + break; + case NOTIFY_PORT_CLASS: + seq_printf(m, "[%5lu.%06lu] device class %d, interface class %d\n", + (unsigned long)ts, rem_nsec / 1000, param1, param2); + break; + case NOTIFY_PORT_CLASS_BLOCK: + seq_printf(m, "[%5lu.%06lu] block device class %d, interface class %d\n", + (unsigned long)ts, rem_nsec / 1000, param1, param2); + break; + default: + seq_printf(m, "[%5lu.%06lu] undefined event\n", + (unsigned long)ts, rem_nsec / 1000); + break; + } +} + +static uint16_t set_port_count(uint16_t vid, uint16_t pid) +{ + int i; + uint16_t ret = 0; + struct port_count *temp_port; + + for (i = 0; i < USBLOG_MAX_STORE_PORT; i++) { + temp_port = &usblog_root.usblog_buffer->store_port_cnt[i]; + if ((temp_port->vid == vid) + && temp_port->pid == pid) { + temp_port->count++; + ret = temp_port->count; + break; + } + + if (!temp_port->vid && !temp_port->pid) { + temp_port->vid = vid; + temp_port->pid = pid; + temp_port->count++; + ret = temp_port->count; + break; + } + } + + if (i == USBLOG_MAX_STORE_PORT) + unl_err("%s store port overflow\n", __func__); + + return ret; +} + +static void print_pcm_string(struct seq_file *m, struct usblog_rtc_time *rt, + unsigned long long ts, unsigned long rem_nsec, int type, int enable) +{ + switch (type) { + case NOTIFY_PCM_PLAYBACK: + seq_printf(m, "[%02d-%02d %02d:%02d:%02d][%5lu.%06lu] USB PCM PLAYBACK %s\n", + rt->tm_mon, rt->tm_mday, rt->tm_hour, rt->tm_min, rt->tm_sec, + (unsigned long)ts, rem_nsec / 1000, + enable ? "OPEN" : "CLOSE"); + break; + case NOTIFY_PCM_CAPTURE: + seq_printf(m, "[%02d-%02d %02d:%02d:%02d][%5lu.%06lu] USB PCM CAPTURE %s\n", + rt->tm_mon, rt->tm_mday, rt->tm_hour, rt->tm_min, rt->tm_sec, + (unsigned long)ts, rem_nsec / 1000, + enable ? "OPEN" : "CLOSE"); + break; + default: + seq_printf(m, "[%02d-%02d %02d:%02d:%02d][%5lu.%06lu] undefined event\n", + rt->tm_mon, rt->tm_mday, rt->tm_hour, rt->tm_min, rt->tm_sec, + (unsigned long)ts, rem_nsec / 1000); + break; + } +} + +static int usblog_proc_show(struct seq_file *m, void *v) +{ + struct usblog_buf *temp_usblog_buffer; + struct usblog_vm_buf *temp_usblog_vm_buffer; + struct usblog_rtc_buf *temp_usblog_rt_buffer; + struct usblog_rtc_time rt; + unsigned long long ts; + unsigned long rem_nsec; + unsigned long i; + unsigned long flags = 0; + + spin_lock_irqsave(&usblog_root.usblog_lock, flags); + + temp_usblog_buffer = usblog_root.usblog_buffer; + temp_usblog_vm_buffer = usblog_root.usblog_vm_buffer; + temp_usblog_rt_buffer = usblog_root.usblog_rtc_buffer; + + if (!temp_usblog_buffer || !temp_usblog_vm_buffer || !temp_usblog_rt_buffer) + goto err; + + usblog_get_rt(&rt); + ts = local_clock(); + rem_nsec = do_div(ts, 1000000000); + + seq_printf(m, + "time sync: [%02d-%02d %02d:%02d:%02d][%5lu.%06lu]\n", + rt.tm_mon, rt.tm_mday, rt.tm_hour, rt.tm_min, rt.tm_sec, + (unsigned long)ts, rem_nsec / 1000); + + seq_printf(m, + "usblog CC IC info:\n"); + + seq_printf(m, + "tcpc name: %s\n", usblog_root.tcpc_name); + + seq_printf(m, + "hw version =%2x %2x %2x %2x\n", + usblog_root.ccic_ver.hw_version[3], + usblog_root.ccic_ver.hw_version[2], + usblog_root.ccic_ver.hw_version[1], + usblog_root.ccic_ver.hw_version[0]); + + seq_printf(m, + "sw version =%2x %2x %2x %2x\n", + usblog_root.ccic_ver.sw_main[2], + usblog_root.ccic_ver.sw_main[1], + usblog_root.ccic_ver.sw_main[0], + usblog_root.ccic_ver.sw_boot); + + seq_printf(m, + "bin version =%2x %2x %2x %2x\n", + usblog_root.ccic_bin_ver.sw_main[2], + usblog_root.ccic_bin_ver.sw_main[1], + usblog_root.ccic_bin_ver.sw_main[0], + usblog_root.ccic_bin_ver.sw_boot); + + + seq_printf(m, + "\n\n"); + seq_printf(m, + "usblog CCIC EVENT: count=%llu maxline=%d\n", + temp_usblog_buffer->ccic_count, + USBLOG_CCIC_BUFFER_SIZE); + + if (temp_usblog_buffer->ccic_count >= USBLOG_CCIC_BUFFER_SIZE) { + for (i = temp_usblog_buffer->ccic_index; + i < USBLOG_CCIC_BUFFER_SIZE; i++) { + rt = temp_usblog_rt_buffer->ccic_buf_rt[i]; + ts = temp_usblog_buffer->ccic_buffer[i].ts_nsec; + rem_nsec = do_div(ts, 1000000000); + seq_printf(m, "[%02d-%02d %02d:%02d:%02d] ", + rt.tm_mon, rt.tm_mday, rt.tm_hour, rt.tm_min, rt.tm_sec); + print_ccic_event(m, ts, rem_nsec, + temp_usblog_buffer->ccic_buffer[i].cc_type, + &temp_usblog_buffer->ccic_buffer[i].noti); + } + } + + for (i = 0; i < temp_usblog_buffer->ccic_index; i++) { + rt = temp_usblog_rt_buffer->ccic_buf_rt[i]; + ts = temp_usblog_buffer->ccic_buffer[i].ts_nsec; + rem_nsec = do_div(ts, 1000000000); + seq_printf(m, "[%02d-%02d %02d:%02d:%02d] ", + rt.tm_mon, rt.tm_mday, rt.tm_hour, rt.tm_min, rt.tm_sec); + print_ccic_event(m, ts, rem_nsec, + temp_usblog_buffer->ccic_buffer[i].cc_type, + &temp_usblog_buffer->ccic_buffer[i].noti); + } + + seq_printf(m, + "\n\n"); + seq_printf(m, + "usblog USB_MODE: count=%llu maxline=%d\n", + temp_usblog_buffer->mode_count, + USBLOG_MODE_BUFFER_SIZE); + + if (temp_usblog_buffer->mode_count >= USBLOG_MODE_BUFFER_SIZE) { + for (i = temp_usblog_buffer->mode_index; + i < USBLOG_MODE_BUFFER_SIZE; i++) { + rt = temp_usblog_rt_buffer->mode_buf_rt[i]; + ts = temp_usblog_buffer->mode_buffer[i].ts_nsec; + rem_nsec = do_div(ts, 1000000000); + seq_printf(m, "[%02d-%02d %02d:%02d:%02d] [%5lu.%06lu] %s\n", + rt.tm_mon, rt.tm_mday, rt.tm_hour, rt.tm_min, rt.tm_sec, + (unsigned long)ts, rem_nsec / 1000, + temp_usblog_buffer->mode_buffer[i].usbmode_str); + } + } + + for (i = 0; i < temp_usblog_buffer->mode_index; i++) { + rt = temp_usblog_rt_buffer->mode_buf_rt[i]; + ts = temp_usblog_buffer->mode_buffer[i].ts_nsec; + rem_nsec = do_div(ts, 1000000000); + seq_printf(m, "[%02d-%02d %02d:%02d:%02d] [%5lu.%06lu] %s\n", + rt.tm_mon, rt.tm_mday, rt.tm_hour, rt.tm_min, rt.tm_sec, + (unsigned long)ts, rem_nsec / 1000, + temp_usblog_buffer->mode_buffer[i].usbmode_str); + } + + seq_printf(m, + "\n\n"); + seq_printf(m, + "usblog USB STATE: count=%llu maxline=%d\n", + temp_usblog_buffer->state_count, + USBLOG_STATE_BUFFER_SIZE); + + if (temp_usblog_buffer->state_count >= USBLOG_STATE_BUFFER_SIZE) { + for (i = temp_usblog_buffer->state_index; + i < USBLOG_STATE_BUFFER_SIZE; i++) { + rt = temp_usblog_rt_buffer->state_buf_rt[i]; + ts = temp_usblog_buffer->state_buffer[i].ts_nsec; + rem_nsec = do_div(ts, 1000000000); + seq_printf(m, "[%02d-%02d %02d:%02d:%02d] [%5lu.%06lu] %s\n", + rt.tm_mon, rt.tm_mday, rt.tm_hour, rt.tm_min, rt.tm_sec, + (unsigned long)ts, rem_nsec / 1000, + usbstate_string(temp_usblog_buffer + ->state_buffer[i].usbstate)); + } + } + + for (i = 0; i < temp_usblog_buffer->state_index; i++) { + rt = temp_usblog_rt_buffer->state_buf_rt[i]; + ts = temp_usblog_buffer->state_buffer[i].ts_nsec; + rem_nsec = do_div(ts, 1000000000); + seq_printf(m, "[%02d-%02d %02d:%02d:%02d] [%5lu.%06lu] %s\n", + rt.tm_mon, rt.tm_mday, rt.tm_hour, rt.tm_min, rt.tm_sec, + (unsigned long)ts, rem_nsec / 1000, + usbstate_string(temp_usblog_buffer + ->state_buffer[i].usbstate)); + } + + seq_printf(m, + "\n\n"); + seq_printf(m, + "usblog USB EVENT: count=%llu maxline=%d\n", + temp_usblog_buffer->event_count, + USBLOG_EVENT_BUFFER_SIZE); + + if (temp_usblog_buffer->event_count >= USBLOG_EVENT_BUFFER_SIZE) { + for (i = temp_usblog_buffer->event_index; + i < USBLOG_EVENT_BUFFER_SIZE; i++) { + rt = temp_usblog_rt_buffer->event_buf_rt[i]; + ts = temp_usblog_buffer->event_buffer[i].ts_nsec; + rem_nsec = do_div(ts, 1000000000); + seq_printf(m, "[%02d-%02d %02d:%02d:%02d] [%5lu.%06lu] %s %s\n", + rt.tm_mon, rt.tm_mday, rt.tm_hour, rt.tm_min, rt.tm_sec, + (unsigned long)ts, rem_nsec / 1000, + event_string(temp_usblog_buffer->event_buffer[i].event), + status_string(temp_usblog_buffer + ->event_buffer[i].enable)); + } + } + + for (i = 0; i < temp_usblog_buffer->event_index; i++) { + rt = temp_usblog_rt_buffer->event_buf_rt[i]; + ts = temp_usblog_buffer->event_buffer[i].ts_nsec; + rem_nsec = do_div(ts, 1000000000); + seq_printf(m, "[%02d-%02d %02d:%02d:%02d] [%5lu.%06lu] %s %s\n", + rt.tm_mon, rt.tm_mday, rt.tm_hour, rt.tm_min, rt.tm_sec, + (unsigned long)ts, rem_nsec / 1000, + event_string(temp_usblog_buffer->event_buffer[i].event), + status_string(temp_usblog_buffer->event_buffer[i].enable)); + } + + seq_printf(m, + "\n\n"); + seq_printf(m, + "usblog PORT: count=%llu maxline=%d\n", + temp_usblog_buffer->port_count, + USBLOG_PORT_BUFFER_SIZE); + + if (temp_usblog_buffer->port_count >= USBLOG_PORT_BUFFER_SIZE) { + for (i = temp_usblog_buffer->port_index; + i < USBLOG_PORT_BUFFER_SIZE; i++) { + rt = temp_usblog_rt_buffer->port_buf_rt[i]; + ts = temp_usblog_buffer->port_buffer[i].ts_nsec; + rem_nsec = do_div(ts, 1000000000); + seq_printf(m, "[%02d-%02d %02d:%02d:%02d] ", + rt.tm_mon, rt.tm_mday, rt.tm_hour, rt.tm_min, rt.tm_sec); + print_port_string(m, ts, rem_nsec, + temp_usblog_buffer->port_buffer[i].type, + temp_usblog_buffer->port_buffer[i].param1, + temp_usblog_buffer->port_buffer[i].param2, + temp_usblog_buffer->port_buffer[i].count); + } + } + + for (i = 0; i < temp_usblog_buffer->port_index; i++) { + rt = temp_usblog_rt_buffer->port_buf_rt[i]; + ts = temp_usblog_buffer->port_buffer[i].ts_nsec; + rem_nsec = do_div(ts, 1000000000); + seq_printf(m, "[%02d-%02d %02d:%02d:%02d] ", + rt.tm_mon, rt.tm_mday, rt.tm_hour, rt.tm_min, rt.tm_sec); + print_port_string(m, ts, rem_nsec, + temp_usblog_buffer->port_buffer[i].type, + temp_usblog_buffer->port_buffer[i].param1, + temp_usblog_buffer->port_buffer[i].param2, + temp_usblog_buffer->port_buffer[i].count); + } + + seq_printf(m, + "\n\n"); + seq_printf(m, + "usblog PCM: count=%llu maxline=%d\n", + temp_usblog_vm_buffer->pcm_count, + USBLOG_PCM_BUFFER_SIZE); + + if (temp_usblog_vm_buffer->pcm_count >= USBLOG_PCM_BUFFER_SIZE) { + for (i = temp_usblog_vm_buffer->pcm_index; + i < USBLOG_PCM_BUFFER_SIZE; i++) { + ts = temp_usblog_vm_buffer->pcm_buffer[i].ts_nsec; + rem_nsec = do_div(ts, 1000000000); + print_pcm_string(m, &temp_usblog_vm_buffer->pcm_buffer[i].rt, + ts, rem_nsec, + temp_usblog_vm_buffer->pcm_buffer[i].type, + temp_usblog_vm_buffer->pcm_buffer[i].enable); + } + } + + for (i = 0; i < temp_usblog_vm_buffer->pcm_index; i++) { + ts = temp_usblog_vm_buffer->pcm_buffer[i].ts_nsec; + rem_nsec = do_div(ts, 1000000000); + print_pcm_string(m, &temp_usblog_vm_buffer->pcm_buffer[i].rt, + ts, rem_nsec, + temp_usblog_vm_buffer->pcm_buffer[i].type, + temp_usblog_vm_buffer->pcm_buffer[i].enable); + } + + seq_printf(m, + "\n\n"); + seq_printf(m, + "usblog EXTRA: count=%llu maxline=%d\n", + temp_usblog_buffer->extra_count, + USBLOG_EXTRA_BUFFER_SIZE); + + if (temp_usblog_buffer->extra_count >= USBLOG_EXTRA_BUFFER_SIZE) { + for (i = temp_usblog_buffer->extra_index; + i < USBLOG_EXTRA_BUFFER_SIZE; i++) { + rt = temp_usblog_rt_buffer->extra_buf_rt[i]; + ts = temp_usblog_buffer->extra_buffer[i].ts_nsec; + rem_nsec = do_div(ts, 1000000000); + seq_printf(m, "[%02d-%02d %02d:%02d:%02d] [%5lu.%06lu] %s\n", + rt.tm_mon, rt.tm_mday, rt.tm_hour, rt.tm_min, rt.tm_sec, + (unsigned long)ts, rem_nsec / 1000, + extra_string(temp_usblog_buffer + ->extra_buffer[i].event)); + } + } + + for (i = 0; i < temp_usblog_buffer->extra_index; i++) { + rt = temp_usblog_rt_buffer->extra_buf_rt[i]; + ts = temp_usblog_buffer->extra_buffer[i].ts_nsec; + rem_nsec = do_div(ts, 1000000000); + seq_printf(m, "[%02d-%02d %02d:%02d:%02d] [%5lu.%06lu] %s\n", + rt.tm_mon, rt.tm_mday, rt.tm_hour, rt.tm_min, rt.tm_sec, + (unsigned long)ts, rem_nsec / 1000, + extra_string(temp_usblog_buffer->extra_buffer[i].event)); + } + + seq_printf(m, + "\n\n"); + + pr_info("%s boot_idx=%lu, usb_idx=%lu, usb_tail=%lu, b_full=%d, u_full=%d\n", + __func__, + usblog_root.prk_usb.usb_boot_index, usblog_root.prk_usb.usb_index, + usblog_root.prk_usb.usb_tail, usblog_root.prk_usb.usb_boot_full, + usblog_root.prk_usb.usb_full); + + seq_printf(m, + "printk_usb boot:\n"); + + seq_write(m, printk_usb_boot_buf, usblog_root.prk_usb.usb_boot_index); + + seq_printf(m, + "\n\n"); + seq_printf(m, + "printk_usb_snapshot:\n"); + + if (usblog_root.prk_usb.snap_done) { + if (usblog_root.prk_usb.usb_snap_full) + seq_write(m, printk_usb_snap_buf + usblog_root.prk_usb.usb_snap_index, + usblog_root.prk_usb.usb_snap_tail - usblog_root.prk_usb.usb_snap_index); + + seq_write(m, printk_usb_snap_buf, usblog_root.prk_usb.usb_snap_index); + } + + seq_printf(m, + "\n\n"); + seq_printf(m, + "printk_usb:\n"); + + if (usblog_root.prk_usb.usb_full) + seq_write(m, printk_usb_buf + usblog_root.prk_usb.usb_index, + usblog_root.prk_usb.usb_tail - usblog_root.prk_usb.usb_index); + + if (usblog_root.prk_usb.usb_boot_full) + seq_write(m, printk_usb_buf, usblog_root.prk_usb.usb_index); + +err: + spin_unlock_irqrestore(&usblog_root.usblog_lock, flags); + + return 0; +} + +static int usblog_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, usblog_proc_show, NULL); +} + +static const struct proc_ops usblog_proc_fops = { + .proc_open = usblog_proc_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = single_release, +}; + +void ccic_store_usblog_notify(int type, uint64_t *param1) +{ + struct ccic_buf *ccic_buffer; + struct usblog_rtc_time *ccic_rtc; + unsigned long long *target_count; + unsigned long *target_index; + + target_count = &usblog_root.usblog_buffer->ccic_count; + target_index = &usblog_root.usblog_buffer->ccic_index; + ccic_buffer = &usblog_root.usblog_buffer->ccic_buffer[*target_index]; + ccic_rtc = &usblog_root.usblog_rtc_buffer->ccic_buf_rt[*target_index]; + if (ccic_buffer == NULL) { + unl_err("%s target_buffer error\n", __func__); + goto err; + } + usblog_get_rt(ccic_rtc); + ccic_buffer->ts_nsec = local_clock(); + ccic_buffer->cc_type = type; + ccic_buffer->noti = *param1; + + *target_index = (*target_index+1)%USBLOG_CCIC_BUFFER_SIZE; + (*target_count)++; +err: + return; +} + +void mode_store_usblog_notify(int type, char *param1) +{ + struct mode_buf *md_buffer; + struct usblog_rtc_time *mode_rtc; + unsigned long long *target_count; + unsigned long *target_index; + char buf[256], buf2[4]; + char *b, *name; + int param_len; + + target_count = &usblog_root.usblog_buffer->mode_count; + target_index = &usblog_root.usblog_buffer->mode_index; + md_buffer = &usblog_root.usblog_buffer->mode_buffer[*target_index]; + mode_rtc = &usblog_root.usblog_rtc_buffer->mode_buf_rt[*target_index]; + if (md_buffer == NULL) { + unl_err("%s target_buffer error\n", __func__); + goto err; + } + usblog_get_rt(mode_rtc); + md_buffer->ts_nsec = local_clock(); + + strscpy(buf, param1, sizeof(buf)); + b = strim(buf); + + if (type == NOTIFY_USBMODE_EXTRA) { + param_len = strlen(b); + if (param_len >= USBLOG_MAX_STRING_SIZE) + param_len = USBLOG_MAX_STRING_SIZE-1; + strncpy(md_buffer->usbmode_str, b, + sizeof(md_buffer->usbmode_str)-1); + } else if (type == NOTIFY_USBMODE) { + if (b) { + name = strsep(&b, ","); + strscpy(buf2, name, sizeof(buf2)); + strncpy(md_buffer->usbmode_str, buf2, + sizeof(md_buffer->usbmode_str)-1); + } + while (b) { + name = strsep(&b, ","); + if (!name) + continue; + if (USBLOG_MAX_STRING_SIZE + - strlen(md_buffer->usbmode_str) < 5) { + strncpy(md_buffer->usbmode_str, "overflow", + sizeof(md_buffer->usbmode_str)-1); + b = NULL; + } else { + strncat(md_buffer->usbmode_str, ",", 1); + strncat(md_buffer->usbmode_str, name, 3); + } + } + } + + *target_index = (*target_index+1)%USBLOG_MODE_BUFFER_SIZE; + (*target_count)++; +err: + return; +} + +void state_store_usblog_notify(int type, char *param1) +{ + struct state_buf *st_buffer; + struct usblog_rtc_time *state_rtc; + unsigned long long *target_count; + unsigned long *target_index; + char buf[256], index, index2, index3; + char *b, *name; + int usbstate = 0; + + target_count = &usblog_root.usblog_buffer->state_count; + target_index = &usblog_root.usblog_buffer->state_index; + st_buffer = &usblog_root.usblog_buffer->state_buffer[*target_index]; + state_rtc = &usblog_root.usblog_rtc_buffer->state_buf_rt[*target_index]; + if (st_buffer == NULL) { + unl_err("%s target_buffer error\n", __func__); + goto err; + } + usblog_get_rt(state_rtc); + st_buffer->ts_nsec = local_clock(); + + strscpy(buf, param1, sizeof(buf)); + b = strim(buf); + name = strsep(&b, "="); + + index = *(b+USBLOG_CMP_INDEX); + + switch (index) { + case 'F': /* CONFIGURED */ + usbstate = NOTIFY_CONFIGURED; + break; + case 'N': /* CONNECTED */ + usbstate = NOTIFY_CONNECTED; + break; + case 'C': /* DISCONNECTED */ + usbstate = NOTIFY_DISCONNECTED; + break; + case 'E': /* RESET */ + name = strsep(&b, ":"); + if (b) { + index2 = *b; + switch (index2) { + case 'F': /* FULL SPEED */ + usbstate = NOTIFY_RESET_FULL; + break; + case 'H': /* HIGH SPEED */ + usbstate = NOTIFY_RESET_HIGH; + break; + case 'S': /* SUPER SPEED */ + usbstate = NOTIFY_RESET_SUPER; + break; + default: + usbstate = NOTIFY_RESET; + break; + } + } else + usbstate = NOTIFY_RESET; + break; + case 'L': /* GADGET PULL UP/DN */ + name = strsep(&b, ":"); + if (b) { + index2 = *b; + name = strsep(&b, ":"); + if (b) + index3 = *b; + else /* X means none */ + index3 = 'X'; + } else /* X means none */ + index2 = 'X'; + + switch (index2) { + case 'E': /* VBUS SESSION ENABLE */ + if (index3 == 'S') + usbstate = NOTIFY_PULLUP_EN_SUCCESS; + else if (index3 == 'F') + usbstate = NOTIFY_PULLUP_EN_FAIL; + else + usbstate = NOTIFY_PULLUP_ENABLE; + break; + case 'D': /* VBUS SESSION DISABLE */ + if (index3 == 'S') + usbstate = NOTIFY_PULLUP_DIS_SUCCESS; + else if (index3 == 'F') + usbstate = NOTIFY_PULLUP_DIS_FAIL; + else + usbstate = NOTIFY_PULLUP_DISABLE; + break; + default: + usbstate = NOTIFY_PULLUP; + break; + } + break; + case 'R': /* ACCESSORY START */ + usbstate = NOTIFY_ACCSTART; + break; + case 'S': /* GADGET_VBUS EN/DN*/ + name = strsep(&b, ":"); + if (b) { + index2 = *b; + name = strsep(&b, ":"); + if (b) + index3 = *b; + else /* X means none */ + index3 = 'X'; + } else /* X means none */ + index2 = 'X'; + + switch (index2) { + case 'E': /* VBUS SESSION ENABLE */ + if (index3 == 'S') + usbstate = NOTIFY_VBUS_EN_SUCCESS; + else if (index3 == 'F') + usbstate = NOTIFY_VBUS_EN_FAIL; + else + usbstate = NOTIFY_VBUS_SESSION_ENABLE; + break; + case 'D': /* VBUS SESSION DISABLE */ + if (index3 == 'S') + usbstate = NOTIFY_VBUS_DIS_SUCCESS; + else if (index3 == 'F') + usbstate = NOTIFY_VBUS_DIS_FAIL; + else + usbstate = NOTIFY_VBUS_SESSION_DISABLE; + break; + default: + usbstate = NOTIFY_VBUS_SESSION; + break; + } + break; + case 'D': /* SUPER SPEED */ + usbstate = NOTIFY_SUPER; + break; + case 'H': /*HIGH SPEED */ + usbstate = NOTIFY_HIGH; + break; + case 'M': /* USB ENUMERATION */ + name = strsep(&b, ":"); + if (b) { + index2 = *b; + name = strsep(&b, ":"); + if (b) + index3 = *b; + else /* X means none */ + index3 = 'X'; + } else /* X means none */ + index2 = 'X'; + + switch (index2) { + case 'C': /* CONNDONE */ + if (index3 == 'P') /* SUPERSPEED_PLUS */ + usbstate = NOTIFY_CONNDONE_SSP; + else if (index3 == 'S') /* SUPERSPEED */ + usbstate = NOTIFY_CONNDONE_SS; + else if (index3 == 'H') /* HIGHSPEED */ + usbstate = NOTIFY_CONNDONE_HS; + else if (index3 == 'F') /* FULLSPEED */ + usbstate = NOTIFY_CONNDONE_FS; + else if (index3 == 'L') /* LOWSPEED */ + usbstate = NOTIFY_CONNDONE_LS; + break; + case 'G': /* GET */ + if (index3 == 'D') /* GET_DES */ + usbstate = NOTIFY_GET_DES; + break; + case 'S': /* SET */ + if (index3 == 'C') /* SET_CON */ + usbstate = NOTIFY_SET_CON; + break; + default: + break; + } + break; + default: + unl_err("%s state param error. state=%s\n", __func__, param1); + goto err; + } + + st_buffer->usbstate = usbstate; + + *target_index = (*target_index+1)%USBLOG_STATE_BUFFER_SIZE; + (*target_count)++; +err: + return; +} + +void event_store_usblog_notify(int type, unsigned long *param1, int *param2) +{ + struct event_buf *ev_buffer; + struct usblog_rtc_time *event_rtc; + unsigned long long *target_count; + unsigned long *target_index; + + target_count = &usblog_root.usblog_buffer->event_count; + target_index = &usblog_root.usblog_buffer->event_index; + ev_buffer = &usblog_root.usblog_buffer->event_buffer[*target_index]; + event_rtc = &usblog_root.usblog_rtc_buffer->event_buf_rt[*target_index]; + if (ev_buffer == NULL) { + unl_err("%s target_buffer error\n", __func__); + goto err; + } + usblog_get_rt(event_rtc); + ev_buffer->ts_nsec = local_clock(); + ev_buffer->event = *param1; + ev_buffer->enable = *param2; + + *target_index = (*target_index+1)%USBLOG_EVENT_BUFFER_SIZE; + (*target_count)++; +err: + return; +} + +void port_store_usblog_notify(int type, void *param1, void *param2) +{ + struct port_buf *pt_buffer; + struct usblog_rtc_time *port_rtc; + unsigned long long *target_count; + unsigned long *target_index; + + target_count = &usblog_root.usblog_buffer->port_count; + target_index = &usblog_root.usblog_buffer->port_index; + pt_buffer = &usblog_root.usblog_buffer->port_buffer[*target_index]; + port_rtc = &usblog_root.usblog_rtc_buffer->port_buf_rt[*target_index]; + if (pt_buffer == NULL) { + unl_err("%s target_buffer error\n", __func__); + goto err; + } + usblog_get_rt(port_rtc); + pt_buffer->ts_nsec = local_clock(); + pt_buffer->type = type; + if (type == NOTIFY_PORT_CONNECT) { + pt_buffer->param1 = le16_to_cpu(*(__le16 *)(param1)); + pt_buffer->param2 = le16_to_cpu(*(__le16 *)(param2)); + pt_buffer->count + = set_port_count(pt_buffer->param1, pt_buffer->param2); + } else if (type == NOTIFY_PORT_DISCONNECT) { + pt_buffer->param1 = le16_to_cpu(*(__le16 *)(param1)); + pt_buffer->param2 = le16_to_cpu(*(__le16 *)(param2)); + } else { + pt_buffer->param1 = (uint16_t)(*(__u8 *)(param1)); + pt_buffer->param2 = (uint16_t)(*(__u8 *)(param2)); + } + + *target_index = (*target_index+1)%USBLOG_PORT_BUFFER_SIZE; + (*target_count)++; +err: + return; +} + +void pcm_store_usblog_notify(int type, int *param1) +{ + struct pcm_buf *pcm_buffer; + unsigned long long *target_count; + unsigned long *target_index; + + target_count = &usblog_root.usblog_vm_buffer->pcm_count; + target_index = &usblog_root.usblog_vm_buffer->pcm_index; + pcm_buffer = &usblog_root.usblog_vm_buffer->pcm_buffer[*target_index]; + if (pcm_buffer == NULL) { + unl_err("%s target_buffer error\n", __func__); + goto err; + } + usblog_get_rt(&pcm_buffer->rt); + pcm_buffer->ts_nsec = local_clock(); + pcm_buffer->type = type; + pcm_buffer->enable = *param1; + + *target_index = (*target_index+1)%USBLOG_PCM_BUFFER_SIZE; + (*target_count)++; +err: + return; +} + +void extra_store_usblog_notify(int type, int *param1) +{ + struct extra_buf *ex_buffer; + struct usblog_rtc_time *extra_rtc; + unsigned long long *target_count; + unsigned long *target_index; + + target_count = &usblog_root.usblog_buffer->extra_count; + target_index = &usblog_root.usblog_buffer->extra_index; + ex_buffer = &usblog_root.usblog_buffer->extra_buffer[*target_index]; + extra_rtc = &usblog_root.usblog_rtc_buffer->extra_buf_rt[*target_index]; + if (ex_buffer == NULL) { + unl_err("%s target_buffer error\n", __func__); + goto err; + } + usblog_get_rt(extra_rtc); + ex_buffer->ts_nsec = local_clock(); + ex_buffer->event = *param1; + + *target_index = (*target_index+1)%USBLOG_EXTRA_BUFFER_SIZE; + (*target_count)++; +err: + return; +} + +void store_usblog_notify(int type, void *param1, void *param2) +{ + unsigned long flags = 0; + uint64_t temp = 0; + + if (!usblog_root.init) + register_usblog_proc(); + + spin_lock_irqsave(&usblog_root.usblog_lock, flags); + + if (!usblog_root.usblog_buffer) { + unl_err("%s usblog_buffer is null\n", __func__); + spin_unlock_irqrestore(&usblog_root.usblog_lock, flags); + return; + } + + if (!usblog_root.usblog_vm_buffer) { + unl_err("%s usblog_vm_buffer is null\n", __func__); + spin_unlock_irqrestore(&usblog_root.usblog_lock, flags); + return; + } + + if (type == NOTIFY_FUNCSTATE || type == NOTIFY_TCPMSTATE + || type == NOTIFY_ALTERNATEMODE) { + temp = *(int *)param1; + ccic_store_usblog_notify(type, &temp); + } else if (type == NOTIFY_CCSTATE || type == NOTIFY_CCIC_EVENT + || type == NOTIFY_MANAGER) + ccic_store_usblog_notify(type, (uint64_t *)param1); + else if (type == NOTIFY_EVENT) + event_store_usblog_notify(type, + (unsigned long *)param1, (int *)param2); + else if (type == NOTIFY_USBMODE + || type == NOTIFY_USBMODE_EXTRA) + mode_store_usblog_notify(type, (char *)param1); + else if (type == NOTIFY_USBSTATE) + state_store_usblog_notify(type, (char *)param1); + else if (type == NOTIFY_PORT_CONNECT || + type == NOTIFY_PORT_DISCONNECT || + type == NOTIFY_PORT_CLASS || + type == NOTIFY_PORT_CLASS_BLOCK) + port_store_usblog_notify(type, param1, param2); + else if (type == NOTIFY_PCM_PLAYBACK || + type == NOTIFY_PCM_CAPTURE) + pcm_store_usblog_notify(type, (int *)param1); + else if (type == NOTIFY_EXTRA) + extra_store_usblog_notify(type, (int *)param1); + else + unl_err("%s type error %d\n", __func__, type); + + spin_unlock_irqrestore(&usblog_root.usblog_lock, flags); +} +EXPORT_SYMBOL(store_usblog_notify); + +void store_ccic_version(unsigned char *hw, unsigned char *sw_main, + unsigned char *sw_boot) +{ + if (!hw || !sw_main || !sw_boot) { + unl_err("%s null buffer\n", __func__); + return; + } + + memcpy(&usblog_root.ccic_ver.hw_version, hw, 4); + memcpy(&usblog_root.ccic_ver.sw_main, sw_main, 3); + memcpy(&usblog_root.ccic_ver.sw_boot, sw_boot, 1); +} +EXPORT_SYMBOL(store_ccic_version); + +void store_ccic_bin_version(const unsigned char *sw_main, + const unsigned char *sw_boot) +{ + if (!sw_main || !sw_boot) { + unl_err("%s null buffer\n", __func__); + return; + } + + memcpy(&usblog_root.ccic_bin_ver.sw_main, sw_main, 3); + memcpy(&usblog_root.ccic_bin_ver.sw_boot, sw_boot, 1); +} +EXPORT_SYMBOL(store_ccic_bin_version); + +void store_tcpc_name(char *name) +{ + strncpy(usblog_root.tcpc_name, name, + sizeof(usblog_root.tcpc_name)-1); +} +EXPORT_SYMBOL(store_tcpc_name); + +unsigned long long show_ccic_version(void) +{ + unsigned long long ret = 0; + + memcpy(&ret, &usblog_root.ccic_ver, sizeof(unsigned long long)); + return ret; +} +EXPORT_SYMBOL(show_ccic_version); + +void printk_usb(int snapshot, char *fmt, ...) +{ + struct usblog_rtc_time rt; + unsigned long long ts; + unsigned long rem_nsec; + char buf[PRINTK_USB_MAX_STRING_SIZE] = {0,}; + int len = 0; + va_list args; + bool *boot_full, *usb_full; + unsigned long *boot_index, *usb_index; + unsigned long flags = 0; + + if (!usblog_root.init) { + pr_err("%s usblog_root.init is null\n", __func__); + return; + } + + spin_lock_irqsave(&usblog_root.usblog_lock, flags); + + boot_full = &usblog_root.prk_usb.usb_boot_full; + boot_index = &usblog_root.prk_usb.usb_boot_index; + usb_full = &usblog_root.prk_usb.usb_full; + usb_index = &usblog_root.prk_usb.usb_index; + + usblog_get_rt(&rt); + ts = local_clock(); + rem_nsec = do_div(ts, 1000000000); + + len = snprintf(buf, sizeof(buf), "[%02d-%02d %02d:%02d:%02d][%5lu.%06lu] ", + rt.tm_mon, rt.tm_mday, rt.tm_hour, rt.tm_min, rt.tm_sec, + (unsigned long)ts, rem_nsec / 1000); + + va_start(args, fmt); + len += vscnprintf(buf + len, PRINTK_USB_MAX_STRING_SIZE - len, fmt, args); + va_end(args); + + if (*boot_full == false) { + if (*boot_index + len + 1 > PRINTK_USB_BOOT_BUF_SIZE) { + *boot_full = true; + *usb_index = scnprintf(printk_usb_buf, len + 1, "%s", buf); + } else { + *boot_index += scnprintf(printk_usb_boot_buf + *boot_index, + len + 1, "%s", buf); + } + } else { + if (*usb_index + len + 1 > PRINTK_USB_BUF_SIZE) { + usblog_root.prk_usb.usb_tail = *usb_index; + memset(printk_usb_buf + *usb_index, 0, PRINTK_USB_BUF_SIZE - *usb_index); + *usb_index = scnprintf(printk_usb_buf, len + 1, "%s", buf); + + if (*usb_index > usblog_root.prk_usb.usb_tail) + usblog_root.prk_usb.usb_tail = *usb_index; + + *usb_full = true; + } else { + *usb_index += scnprintf(printk_usb_buf + *usb_index, + len + 1, "%s", buf); + if (*usb_index > usblog_root.prk_usb.usb_tail) + usblog_root.prk_usb.usb_tail = *usb_index; + } + } + + if (*boot_full && snapshot && !usblog_root.prk_usb.snap_done) { + memcpy(printk_usb_snap_buf, printk_usb_buf, PRINTK_USB_BUF_SIZE); + if (*usb_full) { + usblog_root.prk_usb.usb_snap_full = true; + usblog_root.prk_usb.usb_snap_tail = usblog_root.prk_usb.usb_tail; + } + usblog_root.prk_usb.usb_snap_index = *usb_index; + usblog_root.prk_usb.snap_done = true; + pr_info("%s snapshot done!\n", __func__); + } + spin_unlock_irqrestore(&usblog_root.usblog_lock, flags); +} +EXPORT_SYMBOL(printk_usb); + +int register_usblog_proc(void) +{ + int ret = 0; + + if (usblog_root.init) { + pr_err("%s already registered\n", __func__); + goto skip; + } + + spin_lock_init(&usblog_root.usblog_lock); + + usblog_root.usblog_index = USBLOG_INDEX; + usblog_root.init = 1; + + proc_create("usblog", 0444, NULL, &usblog_proc_fops); + + usblog_root.usblog_buffer + = kzalloc(sizeof(struct usblog_buf), GFP_KERNEL); + if (!usblog_root.usblog_buffer) { + ret = -ENOMEM; + goto err; + } + + usblog_root.usblog_rtc_buffer = vzalloc(sizeof(struct usblog_rtc_buf)); + if (!usblog_root.usblog_rtc_buffer) { + ret = -ENOMEM; + goto err1; + } else + pr_info("usb: %s: usblog_root.usblog_rtc_buffer = %zu\n", + __func__, sizeof(struct usblog_rtc_buf)); + + usblog_root.usblog_vm_buffer + = vzalloc(sizeof(struct usblog_vm_buf)); + if (!usblog_root.usblog_vm_buffer) { + ret = -ENOMEM; + goto err2; + } + unl_info("%s size=%zu\n", __func__, sizeof(struct usblog_buf)); +skip: + return ret; +err2: + vfree(usblog_root.usblog_rtc_buffer); + usblog_root.usblog_rtc_buffer = NULL; +err1: + kfree(usblog_root.usblog_buffer); + usblog_root.usblog_buffer = NULL; +err: + remove_proc_entry("usblog", NULL); + usblog_root.init = 0; + pr_err("%s error\n", __func__); + return ret; +} +EXPORT_SYMBOL(register_usblog_proc); + +void unregister_usblog_proc(void) +{ + vfree(usblog_root.usblog_rtc_buffer); + usblog_root.usblog_rtc_buffer = NULL; + vfree(usblog_root.usblog_vm_buffer); + usblog_root.usblog_vm_buffer = NULL; + kfree(usblog_root.usblog_buffer); + usblog_root.usblog_buffer = NULL; + remove_proc_entry("usblog", NULL); + usblog_root.init = 0; +} +EXPORT_SYMBOL(unregister_usblog_proc); + diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 5c192ac9488b..0dfb6490183e 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -247,4 +247,21 @@ config USB_MSM_EUSB2_PHY To compile this driver as a module, choose M here. +config USB_PHY_TUNING_QCOM + bool "USB PHY TUNING QCOM" + depends on ARCH_QCOM + help + Enable this to tune usb phy register. + It is for only test. + Do not enable user build binary. + +config USB_PHY_SETTING_QCOM + bool "USB PHY SETTING QCOM" + depends on ARCH_QCOM + help + Enable this if you need to distinguish settings + between two data roles. + You will be able to set different usb phy values for host/client mode. + This is only supported in QCOM AP chipset models. + endmenu diff --git a/drivers/usb/phy/phy-msm-snps-eusb2.c b/drivers/usb/phy/phy-msm-snps-eusb2.c index 0a5a1b23dbe5..5538f2d25c5d 100644 --- a/drivers/usb/phy/phy-msm-snps-eusb2.c +++ b/drivers/usb/phy/phy-msm-snps-eusb2.c @@ -25,6 +25,9 @@ #include #include #include +#if IS_ENABLED(CONFIG_USB_CONFIGFS_F_SS_MON_GADGET) +#include +#endif #define USB_PHY_UTMI_CTRL0 (0x3c) #define OPMODE_MASK (0x3 << 3) @@ -151,6 +154,9 @@ #define USB_HSPHY_1P2_HPM_LOAD 5905 /* uA */ #define USB_HSPHY_VDD_HPM_LOAD 7757 /* uA */ +#undef dev_dbg +#define dev_dbg dev_err + struct msm_eusb2_phy { struct usb_phy phy; void __iomem *base; @@ -665,7 +671,17 @@ static int msm_eusb2_repeater_reset_and_init(struct msm_eusb2_phy *phy) ret = usb_repeater_reset(phy->ur, true); if (ret) dev_err(phy->phy.dev, "repeater reset failed.\n"); - +#if IS_ENABLED(CONFIG_USB_PHY_SETTING_QCOM) + if (phy->ur) { + if (phy->phy.flags & PHY_HOST_MODE) + phy->ur->is_host = true; + else + phy->ur->is_host = false; + } else + dev_err(phy->phy.dev, "phy->ur is null.\n"); + /* device start up time. TI 3ms, NXP 1ms */ + usleep_range(3000, 3500); +#endif ret = usb_repeater_init(phy->ur); if (ret) dev_err(phy->phy.dev, "repeater init failed.\n"); @@ -882,7 +898,13 @@ static void msm_eusb2_phy_vbus_draw_work(struct work_struct *w) return; } } - +#if IS_ENABLED(CONFIG_USB_CONFIGFS_F_SS_MON_GADGET) + /* USB SUSPEND CURRENT SETTINGS */ + if (phy->vbus_draw == 2) { + pr_err("[USB] make suspend currrent event\n"); + make_suspend_current_event(); + } +#endif dev_info(phy->phy.dev, "Avail curr from USB = %u\n", phy->vbus_draw); /* Set max current limit in uA */ val.intval = 1000 * phy->vbus_draw; diff --git a/drivers/usb/phy/phy-msm-ssusb-qmp.c b/drivers/usb/phy/phy-msm-ssusb-qmp.c index 1b138a5476a3..5c378bdcd749 100644 --- a/drivers/usb/phy/phy-msm-ssusb-qmp.c +++ b/drivers/usb/phy/phy-msm-ssusb-qmp.c @@ -18,6 +18,9 @@ #include #include #include +#if IS_ENABLED(CONFIG_USB_PHY_TUNING_QCOM) +#include +#endif enum core_ldo_levels { CORE_LEVEL_NONE = 0, @@ -80,6 +83,13 @@ enum core_ldo_levels { /* USB3_DP_COM_TYPEC_STATUS */ #define PORTSELECT_RAW BIT(0) +#if IS_ENABLED(CONFIG_USB_PHY_TUNING_QCOM) +#define ADDRESS_START 0 +#define ADDRESS_END 0x1FFC +#define TUNE_BUF_COUNT 20 +#define TUNE_BUF_SIZE 25 +#endif + enum qmp_phy_rev_reg { USB3_PHY_PCS_STATUS, USB3_PHY_AUTONOMOUS_MODE_CTRL, @@ -151,6 +161,13 @@ struct msm_ssphy_qmp { u32 *qmp_phy_init_seq; int init_seq_len; enum qmp_phy_type phy_type; +#if IS_ENABLED(CONFIG_USB_PHY_TUNING_QCOM) + struct mutex phy_tune_lock; + u32 tune_addr; + int tune_buf_cnt; + int tune_buf[TUNE_BUF_COUNT][2]; + bool ssphy_tune_init_done; +#endif }; static const struct of_device_id msm_usb_id_table[] = { @@ -173,6 +190,144 @@ static const struct of_device_id msm_usb_id_table[] = { }; MODULE_DEVICE_TABLE(of, msm_usb_id_table); +#undef dev_dbg +#define dev_dbg dev_err + +#if IS_ENABLED(CONFIG_USB_PHY_TUNING_QCOM) +static void ssphy_tune_buf_init(struct msm_ssphy_qmp *phy) +{ + int i; + + for (i = 0; i < TUNE_BUF_COUNT; i++) + phy->tune_buf[i][0] = phy->tune_buf[i][1] = 0; +} + +static void ssphy_tune_set(struct msm_ssphy_qmp *phy) +{ + int i; + + mutex_lock(&phy->phy_tune_lock); + for (i = 0; i < phy->tune_buf_cnt; i++) { + writel_relaxed(phy->tune_buf[i][1], phy->base + phy->tune_buf[i][0]); + usleep_range(1, 10); + pr_info("%s(): [%d] 0x%x 0x%x (%d/%d)\n", __func__, i, phy->tune_buf[i][0], + (readl_relaxed(phy->base + phy->tune_buf[i][0]) & 0xff), phy->tune_buf_cnt, TUNE_BUF_COUNT); + usleep_range(1, 2); + } + mutex_unlock(&phy->phy_tune_lock); +} + + +static ssize_t ssphy_read_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct msm_ssphy_qmp *phy = dev_get_drvdata(dev); + + if (!phy) { + pr_err("ssphy is NULL\n"); + return -ENODEV; + } + + return sprintf(buf, "0x%x 0x%x\n", phy->tune_addr, + (readl_relaxed(phy->base + phy->tune_addr) & 0xff)); +} +static ssize_t ssphy_read_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct msm_ssphy_qmp *phy = dev_get_drvdata(dev); + u32 addr; + + if (!phy) { + pr_err("ssphy is NULL\n"); + return -ENODEV; + } + + sscanf(buf, "%x", &addr); + pr_info("%s(): tuning address is set to 0x%x\n", __func__, addr); + if (addr >= ADDRESS_START && addr <= ADDRESS_END && !(addr & 0x3)) + phy->tune_addr = addr; + + return size; +} +static DEVICE_ATTR_RW(ssphy_read); + +static ssize_t ssphy_set_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct msm_ssphy_qmp *phy = dev_get_drvdata(dev); + char str[(TUNE_BUF_SIZE * TUNE_BUF_COUNT) + 35] = {0, }; + int i; + + if (!phy) { + pr_err("ssphy is NULL\n"); + return -ENODEV; + } + mutex_lock(&phy->phy_tune_lock); + sprintf(str, "\n Address Value Input [%2d/%2d]\n", phy->tune_buf_cnt, TUNE_BUF_COUNT); + for (i = 0; i < phy->tune_buf_cnt; i++) { + sprintf(str, "%s#%2d 0x%4x 0x%2x 0x%2x\n", str, i + 1, phy->tune_buf[i][0], + (readl_relaxed(phy->base + phy->tune_buf[i][0]) & 0xff), phy->tune_buf[i][1]); + } + mutex_unlock(&phy->phy_tune_lock); + + return sprintf(buf, "%s\n", str); +} +static ssize_t ssphy_set_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct msm_ssphy_qmp *phy = dev_get_drvdata(dev); + u32 addr, val; + int i; + + if (!phy) { + pr_err("ssphy is NULL\n"); + return -ENODEV; + } + sscanf(buf, "%x %x", &addr, &val); + val = val & 0xff; + mutex_lock(&phy->phy_tune_lock); + if (addr >= ADDRESS_START && addr <= ADDRESS_END && !(addr & 0x3)) { + for (i = 0; i < phy->tune_buf_cnt; i++) { + if (phy->tune_buf[i][0] == addr) { + writel_relaxed(val, phy->base + addr); + phy->tune_buf[i][1] = val; + usleep_range(1, 2); + pr_info("%s(): [%d] 0x%x 0x%x (%d/%d)\n", __func__, i, addr, + (readl_relaxed(phy->base + addr) & 0xff), phy->tune_buf_cnt, TUNE_BUF_COUNT); + mutex_unlock(&phy->phy_tune_lock); + return size; + } + } + if (phy->tune_buf_cnt < TUNE_BUF_COUNT) { + writel_relaxed(val, phy->base + addr); + phy->tune_buf[i][0] = addr; + phy->tune_buf[i][1] = val; + usleep_range(1, 2); + pr_info("%s(): [%d] 0x%x 0x%x (%d/%d)\n", __func__, i, addr, + (readl_relaxed(phy->base + addr) & 0xff), phy->tune_buf_cnt, TUNE_BUF_COUNT); + phy->tune_buf_cnt++; + } else + pr_info("%s(): tuning count is full\n", __func__); + } else { + pr_info("%s(): tuning address is invalid : 0x%x\n", __func__, addr); + } + mutex_unlock(&phy->phy_tune_lock); + + return size; +} +static DEVICE_ATTR_RW(ssphy_set); + +static struct attribute *ssphy_attrs[] = { + &dev_attr_ssphy_read.attr, + &dev_attr_ssphy_set.attr, + NULL, +}; + +static struct attribute_group ssphy_attr_grp = { + .attrs = ssphy_attrs, +}; +#endif + static void usb_qmp_powerup_phy(struct msm_ssphy_qmp *phy); static void msm_ssphy_qmp_enable_clks(struct msm_ssphy_qmp *phy, bool on); static int msm_ssphy_qmp_reset(struct usb_phy *uphy); @@ -619,6 +774,10 @@ static int msm_ssphy_qmp_init(struct usb_phy *uphy) ret = -EBUSY; goto fail; } +#if IS_ENABLED(CONFIG_USB_PHY_TUNING_QCOM) + if (phy->tune_buf_cnt && phy->ssphy_tune_init_done) + ssphy_tune_set(phy); +#endif return ret; fail: @@ -1227,6 +1386,18 @@ static int msm_ssphy_qmp_probe(struct platform_device *pdev) phy->phy.notify_connect = msm_ssphy_qmp_notify_connect; phy->phy.notify_disconnect = msm_ssphy_qmp_notify_disconnect; +#if IS_ENABLED(CONFIG_USB_PHY_TUNING_QCOM) + phy->tune_addr = 0; + phy->tune_buf_cnt = 0; + phy->ssphy_tune_init_done = true; + ssphy_tune_buf_init(phy); + mutex_init(&phy->phy_tune_lock); + ret = sysfs_create_group(&pdev->dev.kobj, &ssphy_attr_grp); + if (ret) { + phy->ssphy_tune_init_done = false; + pr_err("%s: ssphy sysfs fail, ret %d", __func__, ret); + } +#endif ret = usb_add_phy_dev(&phy->phy); ret = usb3_get_regulators(phy); if (ret) @@ -1245,6 +1416,11 @@ static int msm_ssphy_qmp_remove(struct platform_device *pdev) usb_remove_phy(&phy->phy); msm_ssphy_qmp_enable_clks(phy, false); msm_ssusb_qmp_ldo_enable(phy, 0); +#if IS_ENABLED(CONFIG_USB_PHY_TUNING_QCOM) + if (phy->ssphy_tune_init_done) + sysfs_remove_group(&pdev->dev.kobj, &ssphy_attr_grp); + mutex_destroy(&phy->phy_tune_lock); +#endif return 0; } diff --git a/drivers/usb/repeater/repeater-i2c-eusb2.c b/drivers/usb/repeater/repeater-i2c-eusb2.c index 3473b11af97a..4b598954ee40 100644 --- a/drivers/usb/repeater/repeater-i2c-eusb2.c +++ b/drivers/usb/repeater/repeater-i2c-eusb2.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2024, Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved. */ #include @@ -18,6 +18,10 @@ #include #include #include +#if IS_ENABLED(CONFIG_USB_PHY_TUNING_QCOM) +#include +#include +#endif #define EUSB2_3P0_VOL_MIN 3075000 /* uV */ #define EUSB2_3P0_VOL_MAX 3300000 /* uV */ @@ -37,6 +41,7 @@ #define USB2_TX_CONTROL1 0x07 #define USB2_TX_CONTROL2 0x08 #define USB2_HS_TERMINATION 0x09 +#define USB2_HS_DISCONNECT_THRESHOLD 0x0A #define RAP_SIGNATURE 0x0D #define VDX_CONTROL 0x0E #define DEVICE_STATUS 0x0F @@ -67,6 +72,57 @@ #define INT_STATUS_1 0xA3 #define INT_STATUS_2 0xA4 +#if IS_ENABLED(CONFIG_USB_PHY_TUNING_QCOM) +#define ADDRESS_START eUSB2_RX_CONTROL +#define ADDRESS_END USB2_HS_DISCONNECT_THRESHOLD +#define TUNE_BUF_COUNT 20 +#define TUNE_BUF_SIZE 25 +#define TUNE_MAX_NXP 17 +#define TUNE_MAX_TI 19 + +static u8 tune_map_nxp[TUNE_MAX_NXP] = { + RESET_CONTROL, + LINK_CONTROL1, + LINK_CONTROL2, + eUSB2_RX_CONTROL, + eUSB2_TX_CONTROL, + USB2_RX_CONTROL, + USB2_TX_CONTROL1, + USB2_TX_CONTROL2, + USB2_HS_TERMINATION, + USB2_HS_DISCONNECT_THRESHOLD, + RAP_SIGNATURE, + DEVICE_STATUS, + LINK_STATUS, + REVISION_ID, + CHIP_ID_0, + CHIP_ID_1, + CHIP_ID_2, +}; + +static u8 tune_map_ti[TUNE_MAX_TI] = { + GPIO0_CONFIG, + GPIO1_CONFIG, + UART_PORT1, + EXTRA_PORT1, + U_TX_ADJUST_PORT1, + U_HS_TX_PRE_EMPHASIS_P1, + U_RX_ADJUST_PORT1, + U_DISCONNECT_SQUELCH_PORT1, + E_HS_TX_PRE_EMPHASIS_P1, + E_TX_ADJUST_PORT1, + E_RX_ADJUST_PORT1, + REV_ID, + GLOBAL_CONFIG, + INT_ENABLE_1, + INT_ENABLE_2, + BC_CONTROL, + BC_STATUS_1, + INT_STATUS_1, + INT_STATUS_2, +}; +#endif + enum eusb2_repeater_type { TI_REPEATER, NXP_REPEATER, @@ -89,6 +145,16 @@ struct eusb2_repeater { struct gpio_desc *reset_gpiod; u32 *param_override_seq; u8 param_override_seq_cnt; +#if IS_ENABLED(CONFIG_USB_PHY_SETTING_QCOM) + u32 *param_host_override_seq; + u8 param_host_override_seq_cnt; +#endif +#if IS_ENABLED(CONFIG_USB_PHY_TUNING_QCOM) + struct mutex er_tune_lock; + int tune_buf_cnt; + u8 tune_buf[TUNE_BUF_COUNT][2]; + bool er_tune_init_done; +#endif }; static const struct regmap_config eusb2_i2c_regmap = { @@ -97,12 +163,27 @@ static const struct regmap_config eusb2_i2c_regmap = { .max_register = 0xff, }; +#if IS_ENABLED(CONFIG_USB_PHY_TUNING_QCOM) + struct eusb2_repeater *ter = NULL; +#endif + +#undef dev_dbg +#define dev_dbg dev_err + static int eusb2_i2c_read_reg(struct eusb2_repeater *er, u8 reg, u8 *val) { int ret; unsigned int reg_val; + int i; ret = regmap_read(er->regmap, reg, ®_val); + + for (i = 0; i < 3 && ret < 0; i++) { + dev_err(er->dev, "Failed to read reg:0x%02x ret=%d\n", reg, ret); + usleep_range(400, 450); + ret = regmap_read(er->regmap, reg, ®_val); + } + if (ret < 0) { dev_err(er->dev, "Failed to read reg:0x%02x ret=%d\n", reg, ret); return ret; @@ -117,8 +198,16 @@ static int eusb2_i2c_read_reg(struct eusb2_repeater *er, u8 reg, u8 *val) static int eusb2_i2c_write_reg(struct eusb2_repeater *er, u8 reg, u8 val) { int ret; + int i; ret = regmap_write(er->regmap, reg, val); + + for (i = 0; i < 3 && ret < 0; i++) { + dev_err(er->dev, "failed to write 0x%02x to reg: 0x%02x ret=%d\n", val, reg, ret); + usleep_range(400, 450); + ret = regmap_write(er->regmap, reg, val); + } + if (ret < 0) { dev_err(er->dev, "failed to write 0x%02x to reg: 0x%02x ret=%d\n", val, reg, ret); return ret; @@ -139,6 +228,155 @@ static void eusb2_repeater_update_seq(struct eusb2_repeater *er, u32 *seq, u8 cn eusb2_i2c_write_reg(er, seq[i+1], seq[i]); } } +#if IS_ENABLED(CONFIG_USB_PHY_TUNING_QCOM) +static void eusb2_repeater_tune_buf_init(void) +{ + int i; + + for (i = 0; i < TUNE_BUF_COUNT; i++) + ter->tune_buf[i][0] = ter->tune_buf[i][1] = 0; +} + +static void eusb2_repeater_tune_set(void) +{ + int i; + u8 reg_val; + + mutex_lock(&ter->er_tune_lock); + for (i = 0; i < ter->tune_buf_cnt; i++) { +#if IS_ENABLED(CONFIG_USB_PHY_SETTING_QCOM) + if (!ter->ur.is_host && ter->chip->repeater_type == NXP_REPEATER && + ter->tune_buf[i][0] == 0x2 && ter->tune_buf[i][1] == 0x03) + pr_info("%s(): skip host test mode setting in NXP USB client mode\n", __func__); + else +#endif + eusb2_i2c_write_reg(ter, ter->tune_buf[i][0], ter->tune_buf[i][1]); + + usleep_range(1, 10); + eusb2_i2c_read_reg(ter, ter->tune_buf[i][0], ®_val); + pr_info("%s(): [%d] 0x%x 0x%x (%d/%d)\n", __func__, i, ter->tune_buf[i][0], + reg_val, ter->tune_buf_cnt, TUNE_BUF_COUNT); + usleep_range(1, 2); + } + mutex_unlock(&ter->er_tune_lock); +} + +static ssize_t eusb2_repeater_tune_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char str[(TUNE_BUF_SIZE * TUNE_BUF_COUNT) + 35] = {0, }; + char str2[(TUNE_BUF_SIZE * TUNE_BUF_COUNT) + 35] = {0, }; + int i, ret; + u8 reg_val; + + if (!ter) { + pr_err("eusb2 repeater is NULL\n"); + return -ENODEV; + } + mutex_lock(&ter->er_tune_lock); + sprintf(str, "\n Address Value - %s\n", ter->chip->repeater_type ? "NXP":"TI"); + if (ter->chip->repeater_type == NXP_REPEATER) { + for (i = 0; i < TUNE_MAX_NXP; i++) { + strcpy(str2, str); + ret = eusb2_i2c_read_reg(ter, tune_map_nxp[i], ®_val); + if (ret < 0) { + dev_err(ter->dev, "Failed to read reg:0x%02x ret=%d\n", tune_map_nxp[i], ret); + mutex_unlock(&ter->er_tune_lock); + return sprintf(buf, "Failed to read reg\n"); + } + sprintf(str, "%s 0x%2x 0x%2x\n", str2, tune_map_nxp[i], reg_val); + } + } else { + for (i = 0; i < TUNE_MAX_TI; i++) { + strcpy(str2, str); + ret = eusb2_i2c_read_reg(ter, tune_map_ti[i], ®_val); + if (ret < 0) { + dev_err(ter->dev, "Failed to read reg:0x%02x ret=%d\n", tune_map_ti[i], ret); + mutex_unlock(&ter->er_tune_lock); + return sprintf(buf, "Failed to read reg\n"); + } + sprintf(str, "%s 0x%2x 0x%2x\n", str2, tune_map_ti[i], reg_val); + } + } + mutex_unlock(&ter->er_tune_lock); + + return sprintf(buf, "%s\n", str); +} + +static ssize_t eusb2_repeater_tune_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + u8 reg, val, reg_val; + int i, ret; + + pr_info("%s buf=%s\n", __func__, buf); + if (!ter) { + pr_err("eusb2 repeater is NULL\n"); + return -ENODEV; + } + sscanf(buf, "%hhx %hhx", ®, &val); + mutex_lock(&ter->er_tune_lock); + + for (i = 0; i < ter->tune_buf_cnt; i++) { + if (ter->tune_buf[i][0] == reg) { + ret = eusb2_i2c_write_reg(ter, reg, val); + if (ret < 0) { + dev_err(ter->dev, "failed to write 0x%02x to reg: 0x%02x ret=%d\n", val, reg, ret); + mutex_unlock(&ter->er_tune_lock); + return ret; + } + ter->tune_buf[i][1] = val; + usleep_range(1, 2); + ret = eusb2_i2c_read_reg(ter, reg, ®_val); + if (ret < 0) { + dev_err(ter->dev, "Failed to read reg:0x%02x ret=%d\n", reg, ret); + mutex_unlock(&ter->er_tune_lock); + return ret; + } + pr_info("%s(): [%d] 0x%x 0x%x (%d/%d)\n", __func__, i, reg, + reg_val, ter->tune_buf_cnt, TUNE_BUF_COUNT); + mutex_unlock(&ter->er_tune_lock); + return size; + } + } + if (ter->tune_buf_cnt < TUNE_BUF_COUNT) { + ret = eusb2_i2c_write_reg(ter, reg, val); + if (ret < 0) { + dev_err(ter->dev, "failed to write 0x%02x to reg: 0x%02x ret=%d\n", val, reg, ret); + mutex_unlock(&ter->er_tune_lock); + return ret; + } + ter->tune_buf[i][0] = reg; + ter->tune_buf[i][1] = val; + usleep_range(1, 2); + ret = eusb2_i2c_read_reg(ter, reg, ®_val); + if (ret < 0) { + dev_err(ter->dev, "Failed to read reg:0x%02x ret=%d\n", reg, ret); + mutex_unlock(&ter->er_tune_lock); + return ret; + } + pr_info("%s(): [%d] 0x%x 0x%x (%d/%d)\n", __func__, i, reg, + reg_val, ter->tune_buf_cnt, TUNE_BUF_COUNT); + ter->tune_buf_cnt++; + } else + pr_info("%s(): tuning count is full\n", __func__); + + mutex_unlock(&ter->er_tune_lock); + + return size; +} + +static DEVICE_ATTR_RW(eusb2_repeater_tune); + +static struct attribute *eusb2_repeater_attributes[] = { + &dev_attr_eusb2_repeater_tune.attr, + NULL +}; + +const struct attribute_group eusb2_repeater_sysfs_group = { + .attrs = eusb2_repeater_attributes, +}; +#endif static int eusb2_repeater_power(struct eusb2_repeater *er, bool on) { @@ -265,9 +503,21 @@ static int eusb2_repeater_init(struct usb_repeater *ur) dev_info(er->ur.dev, "eUSB2 repeater version = 0x%x ur->flags:0x%x\n", reg_val, ur->flags); /* override init sequence using devicetree based values */ +#if IS_ENABLED(CONFIG_USB_PHY_SETTING_QCOM) + dev_info(er->ur.dev, "%s %s mode\n", + er->chip->repeater_type ? "NXP":"TI", er->ur.is_host ? "HOST":"CLIENT"); + if (er->param_host_override_seq_cnt && er->ur.is_host) + eusb2_repeater_update_seq(er, er->param_host_override_seq, + er->param_host_override_seq_cnt); + else +#endif if (er->param_override_seq_cnt) eusb2_repeater_update_seq(er, er->param_override_seq, er->param_override_seq_cnt); +#if IS_ENABLED(CONFIG_USB_PHY_TUNING_QCOM) + if (er->tune_buf_cnt && er->er_tune_init_done) + eusb2_repeater_tune_set(); +#endif dev_info(er->ur.dev, "eUSB2 repeater init\n"); @@ -329,7 +579,11 @@ static int eusb2_repeater_i2c_probe(struct i2c_client *client) struct device *dev = &client->dev; const struct of_device_id *match; int ret = 0, num_elem; +#if IS_ENABLED(CONFIG_USB_PHY_TUNING_QCOM) + struct device *eusb2_repeater_device; +#endif + pr_info("%s\n", __func__); er = devm_kzalloc(dev, sizeof(*er), GFP_KERNEL); if (!er) { ret = -ENOMEM; @@ -405,6 +659,36 @@ static int eusb2_repeater_i2c_probe(struct i2c_client *client) } } +#if IS_ENABLED(CONFIG_USB_PHY_SETTING_QCOM) + num_elem = of_property_count_elems_of_size(dev->of_node, "qcom,param-host-override-seq", + sizeof(*er->param_host_override_seq)); + if (num_elem > 0) { + if (num_elem % 2) { + dev_err(dev, "invalid param_host_override_seq_len\n"); + ret = -EINVAL; + goto err_probe; + } + + er->param_host_override_seq_cnt = num_elem; + er->param_host_override_seq = devm_kcalloc(dev, + er->param_host_override_seq_cnt, + sizeof(*er->param_host_override_seq), GFP_KERNEL); + if (!er->param_host_override_seq) { + ret = -ENOMEM; + goto err_probe; + } + + ret = of_property_read_u32_array(dev->of_node, + "qcom,param-host-override-seq", + er->param_host_override_seq, + er->param_host_override_seq_cnt); + if (ret) { + dev_err(dev, "qcom,param-host-override-seq read failed %d\n", + ret); + goto err_probe; + } + } +#endif er->ur.dev = dev; @@ -417,9 +701,26 @@ static int eusb2_repeater_i2c_probe(struct i2c_client *client) if (ret) goto err_probe; +#if IS_ENABLED(CONFIG_USB_PHY_TUNING_QCOM) + ter = er; + er->tune_buf_cnt = 0; + er->er_tune_init_done = true; + eusb2_repeater_tune_buf_init(); + mutex_init(&er->er_tune_lock); + eusb2_repeater_device = sec_device_create(NULL, "usb_repeater"); + if (IS_ERR(eusb2_repeater_device)) + pr_err("%s Failed to create device(usb_repeater)!\n", __func__); + + + ret = sysfs_create_group(&eusb2_repeater_device->kobj, &eusb2_repeater_sysfs_group); + if (ret) + pr_err("%s: usb_repeater sysfs_create_group fail, ret %d", __func__, ret); +#endif + pr_info("%s %s done\n", __func__, er->chip->repeater_type ? "NXP":"TI"); return 0; err_probe: + pr_info("%s failed. ret(%d)\n", __func__, ret); return ret; } @@ -427,8 +728,15 @@ static void eusb2_repeater_i2c_remove(struct i2c_client *client) { struct eusb2_repeater *er = i2c_get_clientdata(client); + if (!er) + return; + +#if IS_ENABLED(CONFIG_USB_PHY_TUNING_QCOM) + mutex_destroy(&er->er_tune_lock); +#endif usb_remove_repeater_dev(&er->ur); eusb2_repeater_power(er, false); + return; } static struct i2c_driver eusb2_i2c_repeater_driver = { @@ -443,4 +751,4 @@ static struct i2c_driver eusb2_i2c_repeater_driver = { module_i2c_driver(eusb2_i2c_repeater_driver); MODULE_DESCRIPTION("eUSB2 i2c repeater driver"); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c index ed7c6ad96a74..ef57c05c2b84 100644 --- a/drivers/usb/storage/usb.c +++ b/drivers/usb/storage/usb.c @@ -224,16 +224,113 @@ EXPORT_SYMBOL_GPL(usb_stor_reset_resume); int usb_stor_pre_reset(struct usb_interface *iface) { +#if defined(CONFIG_USB_HOST_SAMSUNG_FEATURE) + struct us_data *us; + unsigned long jiffies_expire = jiffies + HZ; + int mu_lock = 1; + + pr_info("%s +\n", __func__); + + us = usb_get_intfdata(iface); + + /* Make sure no command runs during the reset */ + while (!mutex_trylock(&us->dev_mutex)) { + + /* If we can't acquire the lock after waiting one second, + * we're probably deadlocked */ + if (time_after(jiffies, jiffies_expire)) + goto busy; + + msleep(15); + if (us->pusb_dev->state == USB_STATE_NOTATTACHED) { + mu_lock = 0; + goto skip; + } + if (us->pusb_dev->state == USB_STATE_SUSPENDED) { + mu_lock = 0; + goto skip; + } + if (iface->condition == USB_INTERFACE_UNBINDING || + iface->condition == USB_INTERFACE_UNBOUND) { + mu_lock = 0; + goto skip; + } + } + + goto skip; + +busy: + pr_info("%s busy\n", __func__); + set_bit(US_FLIDX_ABORTING, &us->dflags); + usb_stor_stop_transport(us); + /* wait 6 seconds. usb unlink may be spend 5 sec. */ + jiffies_expire = jiffies + 6*HZ; + pr_info("%s try lock again\n", __func__); + while (!mutex_trylock(&us->dev_mutex)) { + + /* If we can't acquire the lock after waiting one second, + * we're probably deadlocked */ + if (time_after(jiffies, jiffies_expire)) { + mu_lock = 0; + goto skip; + } + + msleep(15); + if (us->pusb_dev->state == USB_STATE_NOTATTACHED) { + mu_lock = 0; + goto skip; + } + if (us->pusb_dev->state == USB_STATE_SUSPENDED) { + mu_lock = 0; + goto skip; + } + if (iface->condition == USB_INTERFACE_UNBINDING || + iface->condition == USB_INTERFACE_UNBOUND) { + mu_lock = 0; + goto skip; + } + } +skip: + set_bit(US_FLIDX_RESETTING, &us->dflags); + if (mu_lock) + set_bit(US_FLIDX_MUTEX_LOCK, &us->dflags); + + pr_info("%s -\n", __func__); + return 0; +#else struct us_data *us = usb_get_intfdata(iface); /* Make sure no command runs during the reset */ mutex_lock(&us->dev_mutex); return 0; +#endif } EXPORT_SYMBOL_GPL(usb_stor_pre_reset); int usb_stor_post_reset(struct usb_interface *iface) { +#if defined(CONFIG_USB_HOST_SAMSUNG_FEATURE) + struct us_data *us = usb_get_intfdata(iface); + + pr_info("%s +\n", __func__); + /* Report the reset to the SCSI core */ + usb_stor_report_bus_reset(us); + + /* + * If any of the subdrivers implemented a reinitialization scheme, + * this is where the callback would be invoked. + */ + + clear_bit(US_FLIDX_RESETTING, &us->dflags); + clear_bit(US_FLIDX_ABORTING, &us->dflags); + if (test_bit(US_FLIDX_MUTEX_LOCK, &us->dflags)) { + mutex_unlock(&us->dev_mutex); + clear_bit(US_FLIDX_MUTEX_LOCK, &us->dflags); + pr_info("%s mutex_unlock\n", __func__); + } + pr_info("%s -\n", __func__); + return 0; +#else struct us_data *us = usb_get_intfdata(iface); /* Report the reset to the SCSI core */ @@ -246,6 +343,7 @@ int usb_stor_post_reset(struct usb_interface *iface) mutex_unlock(&us->dev_mutex); return 0; +#endif } EXPORT_SYMBOL_GPL(usb_stor_post_reset); @@ -404,6 +502,9 @@ static int usb_stor_control_thread(void * __us) /* Allow USB transfers to resume */ clear_bit(US_FLIDX_ABORTING, &us->dflags); clear_bit(US_FLIDX_TIMED_OUT, &us->dflags); +#ifdef CONFIG_USB_DEBUG_DETAILED_LOG + pr_err("usb_storage: %s clear TIMED_OUT\n", __func__); +#endif } /* finished working on this command */ @@ -1084,8 +1185,17 @@ void usb_stor_disconnect(struct usb_interface *intf) { struct us_data *us = usb_get_intfdata(intf); +#ifdef CONFIG_USB_DEBUG_DETAILED_LOG + pr_info("%s enter\n", __func__); +#endif quiesce_and_remove_host(us); +#ifdef CONFIG_USB_DEBUG_DETAILED_LOG + pr_info("%s doing\n", __func__); +#endif release_everything(us); +#ifdef CONFIG_USB_DEBUG_DETAILED_LOG + pr_info("%s exit\n", __func__); +#endif } EXPORT_SYMBOL_GPL(usb_stor_disconnect); diff --git a/drivers/usb/storage/usb.h b/drivers/usb/storage/usb.h index 0451fac1adce..5a235fcdece2 100644 --- a/drivers/usb/storage/usb.h +++ b/drivers/usb/storage/usb.h @@ -61,6 +61,7 @@ struct us_unusual_dev { #define US_FLIDX_SCAN_PENDING 6 /* scanning not yet done */ #define US_FLIDX_REDO_READ10 7 /* redo READ(10) command */ #define US_FLIDX_READ10_WORKED 8 /* previous READ(10) succeeded */ +#define US_FLIDX_MUTEX_LOCK 9 /* pre reset lock */ #define USB_STOR_STRING_LEN 32 diff --git a/drivers/usb/typec/common/Kconfig b/drivers/usb/typec/common/Kconfig new file mode 100644 index 000000000000..ce58cb1a7d75 --- /dev/null +++ b/drivers/usb/typec/common/Kconfig @@ -0,0 +1,58 @@ +# +# USB PDIC common driver +# + +comment "USB PDIC common configs" + +config PDIC_NOTIFIER + tristate "PDIC notifier support" + depends on I2C + default n + help + If you say yes here you will get support for + the PDIC attached device status change notification. + +config PDIC_POLICY + bool "PDIC policy common" + default n + help + If you want to use common policy api, + enable this feature. + It should not be enabled in previos ic driver. + Use to bringup new ic driver. + +config USB_ARCH_EXYNOS + bool "Using exynos" + default n + help + If you say yes here you will get support for + using exynos. + For distinguish with Qcom + +config PDIC_USE_MODULE_PARAM + bool "Using module param" + depends on PDIC_NOTIFIER + default n + help + If this feature is enabled, pdic_param will use module_param directly. + Should not use extern variable and __setup for param. + need to add cmdline in bootloader. + (ex: pdic_notifier_module.xxx) + +config PDIC_MISC_TEST + bool "KUnit test for pdic_misc_test" + depends on SEC_KUNIT + help + This driver is for pdic_misc test driver + pdic_misc kunit test + If you want to add some functions, + Please make the test case. + +config PDIC_SYSFS_TEST + bool "KUnit test for pdic_sysfs_test" + depends on SEC_KUNIT + help + This driver is for pdic_sysfs test driver + pdic_sysfs kunit test + If you want to add some functions, + Please make the test case. \ No newline at end of file diff --git a/drivers/usb/typec/common/Makefile b/drivers/usb/typec/common/Makefile new file mode 100644 index 000000000000..fa2bfab11e2d --- /dev/null +++ b/drivers/usb/typec/common/Makefile @@ -0,0 +1,14 @@ +# +# Makefile for usbpd common drivers +# +subdir-ccflags-y := -Wformat +obj-$(CONFIG_PDIC_NOTIFIER) += pdic_notifier_module.o +pdic_notifier_module-y := pdic_notifier.o pdic_sysfs.o pdic_core.o pdic_misc.o pdic_param.o +pdic_notifier_module-$(CONFIG_PDIC_POLICY) += pdic_policy.o + +ifeq ($(CONFIG_SEC_KUNIT), y) +obj-$(CONFIG_PDIC_MISC_TEST) += pdic_misc.o +obj-$(CONFIG_PDIC_SYSFS_TEST) += pdic_sysfs.o +GCOV_PROFILE_pdic_misc.o := $(CONFIG_SEC_KUNIT) +GCOV_PROFILE_pdic_sysfs.o := $(CONFIG_SEC_KUNIT) +endif diff --git a/drivers/usb/typec/common/pdic_core.c b/drivers/usb/typec/common/pdic_core.c new file mode 100644 index 000000000000..67e99f2d495e --- /dev/null +++ b/drivers/usb/typec/common/pdic_core.c @@ -0,0 +1,182 @@ +/* + * + * Copyright (C) 2017-2019 Samsung Electronics + * + * Author:Wookwang Lee. , + * Author:Guneet Singh Khurana , + * 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, see . + * + */ + +#include +#include +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) +#include +#endif +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_ANDROID_SWITCH) || IS_ENABLED(CONFIG_SWITCH) +#include +#endif + +static struct device *pdic_device; +#if IS_ENABLED(CONFIG_ANDROID_SWITCH) || IS_ENABLED(CONFIG_SWITCH) +static struct switch_dev switch_dock = { + .name = "ccic_dock", +}; +#endif + +int pdic_core_init(void); +struct device *get_pdic_device(void) +{ + if (!pdic_device) + pdic_core_init(); + return pdic_device; +} +EXPORT_SYMBOL(get_pdic_device); + +int pdic_register_switch_device(int mode) +{ + int ret = 0; + + pr_info("%s:%d\n", __func__, mode); +#if IS_ENABLED(CONFIG_ANDROID_SWITCH) || IS_ENABLED(CONFIG_SWITCH) + if (mode) { + ret = switch_dev_register(&switch_dock); + if (ret < 0) { + pr_err("%s: Failed to register dock switch(%d)\n", + __func__, ret); + return -ENODEV; + } + } else { + switch_dev_unregister(&switch_dock); + } + pr_info("%s-\n", __func__); +#endif + return ret; +} +EXPORT_SYMBOL(pdic_register_switch_device); + +void pdic_send_dock_intent(int type) +{ + pr_info("%s: PDIC dock type(%d)", __func__, type); +#if IS_ENABLED(CONFIG_ANDROID_SWITCH) || IS_ENABLED(CONFIG_SWITCH) + switch_set_state(&switch_dock, type); +#endif +} +EXPORT_SYMBOL(pdic_send_dock_intent); + +void pdic_send_dock_uevent(u32 vid, u32 pid, int state) +{ + char switch_string[32]; + char pd_ids_string[32]; + char *envp[3] = { switch_string, pd_ids_string, NULL }; + + pr_info("%s: PDIC dock : USBPD_IDS=%04x:%04x SWITCH_STATE=%d\n", + __func__, le16_to_cpu(vid), le16_to_cpu(pid), state); + + if (IS_ERR(pdic_device)) { + pr_err("%s PDIC ERROR: Failed to send a dock uevent!\n", + __func__); + return; + } + + snprintf(switch_string, 32, "SWITCH_STATE=%d", state); + snprintf(pd_ids_string, 32, "USBPD_IDS=%04x:%04x", + le16_to_cpu(vid), le16_to_cpu(pid)); + kobject_uevent_env(&pdic_device->kobj, KOBJ_CHANGE, envp); +} +EXPORT_SYMBOL(pdic_send_dock_uevent); + +int pdic_core_register_chip(ppdic_data_t ppdic_data) +{ + int ret = 0; + + pr_info("%s+\n", __func__); + if (IS_ERR_OR_NULL(pdic_device)) { + pr_err("%s pdic_device is not present try again\n", __func__); + ret = -ENODEV; + goto out; + } + + dev_set_drvdata(pdic_device, ppdic_data); + + /* create sysfs group */ + ret = sysfs_create_group(&pdic_device->kobj, &pdic_sysfs_group); + if (ret) + pr_err("%s: pdic sysfs fail, ret %d", __func__, ret); + pr_info("%s-\n", __func__); +out: + return ret; +} +EXPORT_SYMBOL(pdic_core_register_chip); + +void pdic_core_unregister_chip(void) +{ + pr_info("%s+\n", __func__); + if (IS_ERR(pdic_device)) { + pr_err("%s pdic_device is not present try again\n", __func__); + return; + } + sysfs_remove_group(&pdic_device->kobj, &pdic_sysfs_group); + dev_set_drvdata(pdic_device, NULL); + pr_info("%s-\n", __func__); +} +EXPORT_SYMBOL(pdic_core_unregister_chip); + +int pdic_core_init(void) +{ + int ret = 0; + + if (pdic_device) + return 0; + + pr_info("%s\n", __func__); + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + pdic_device = sec_device_create(NULL, "ccic"); +#endif + + if (IS_ERR_OR_NULL(pdic_device)) { + pr_err("%s Failed to create device(switch)!\n", __func__); + ret = -ENODEV; + goto out; + } + dev_set_drvdata(pdic_device, NULL); + pdic_sysfs_init_attrs(); +out: + return ret; +} + +void *pdic_core_get_drvdata(void) +{ + ppdic_data_t ppdic_data = NULL; + + pr_info("%s\n", __func__); + if (!pdic_device) { + pr_err("%s pdic_device is not present try again\n", __func__); + return NULL; + } + ppdic_data = dev_get_drvdata(pdic_device); + if (!ppdic_data) { + pr_err("%s drv data is null in pdic device\n", __func__); + return NULL; + } + pr_info("%s-\n", __func__); + + return (ppdic_data->drv_data); +} diff --git a/drivers/usb/typec/common/pdic_misc.c b/drivers/usb/typec/common/pdic_misc.c new file mode 100644 index 000000000000..abe526175248 --- /dev/null +++ b/drivers/usb/typec/common/pdic_misc.c @@ -0,0 +1,715 @@ +/* + * + * Copyright (C) 2017-2019 Samsung Electronics + * Author: Wookwang Lee + * + * 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, see . + * + */ +#include +#include +#include +#include +#include +#include +#include + +#define MAX_BUF 255 +#define MAX_FW_SIZE (1024*1024) +#define NODE_OF_MISC "ccic_misc" +#define NODE_OF_UMS "pdic_fwupdate" +#define PDIC_IOCTL_UVDM _IOWR('C', 0, struct uvdm_data) +#ifdef CONFIG_COMPAT +#define PDIC_IOCTL_UVDM_32 _IOWR('C', 0, struct uvdm_data_32) +#endif + +static struct pdic_misc_core *p_m_core; + +int get_checksum(const char *data, int start_addr, int size) +{ + int pos, checksum = 0; + + if (!data || start_addr < 0 || size < 0 + || start_addr > MAX_BUF_DATA || size > MAX_BUF_DATA) + return -EINVAL; + + for (pos = 0; pos < size; pos++) + checksum += data[start_addr + pos]; + + return checksum; +} +EXPORT_SYMBOL(get_checksum); + +int get_data_size(bool is_first_data, int data_size) +{ + int max_size = SEC_UVDM_MAXDATA_NORMAL; + + if (data_size < 0) + return -EINVAL; + + if (is_first_data) + max_size = SEC_UVDM_MAXDATA_FIRST; + + return data_size <= max_size ? data_size : max_size; +} +EXPORT_SYMBOL(get_data_size); + +int set_endian(const char *src, char *dest, int size) +{ + int loop, cnt, total_loop, src_pos, dest_pos; + + if (!src || !dest || size < 0 || size > MAX_BUF_DATA) + return -EINVAL; + + total_loop = size / SEC_UVDM_ALIGN; + if (size % SEC_UVDM_ALIGN) + total_loop++; + + memset(dest, 0, total_loop * SEC_UVDM_ALIGN); + + for (loop = 0 ; loop < total_loop ; loop++) { + for (cnt = 0 ; cnt < SEC_UVDM_ALIGN ; cnt++) { + src_pos = SEC_UVDM_ALIGN * loop + cnt; + dest_pos = SEC_UVDM_ALIGN * loop + SEC_UVDM_ALIGN - cnt - 1; + dest[dest_pos] = src[src_pos]; + } + } + return 0; +} +EXPORT_SYMBOL(set_endian); + +int set_uvdmset_count(int size) +{ + int cnt, len = size - SEC_UVDM_MAXDATA_FIRST; + + if (size < 0) + return -EINVAL; + + if (len <= 0) + return 1; + + cnt = len / SEC_UVDM_MAXDATA_NORMAL; + + if (len % SEC_UVDM_MAXDATA_NORMAL == 0) + return cnt + 1; + + return cnt + 2; +} +EXPORT_SYMBOL(set_uvdmset_count); + +void set_msg_header(void *data, int msg_type, int obj_num) +{ + msg_header_type *msg_hdr; + uint8_t *SendMSG = (uint8_t *)data; + + msg_hdr = (msg_header_type *)&SendMSG[0]; + msg_hdr->msg_type = msg_type; + msg_hdr->num_data_objs = obj_num; + msg_hdr->port_data_role = USBPD_DFP; +} +EXPORT_SYMBOL(set_msg_header); + +void set_uvdm_header(void *data, int vid, int vdm_type) +{ + uvdm_header *uvdm_hdr; + uint8_t *SendMSG = (uint8_t *)data; + + uvdm_hdr = (uvdm_header *)&SendMSG[0]; + uvdm_hdr->vendor_id = SAMSUNG_VENDOR_ID; + uvdm_hdr->vdm_type = vdm_type; + uvdm_hdr->vendor_defined = SEC_UVDM_UNSTRUCTURED_VDM; + uvdm_hdr->BITS.VDM_command = 4; /* from s2mm005 concept */ +} +EXPORT_SYMBOL(set_uvdm_header); + +void set_sec_uvdm_header(void *data, int pid, bool data_type, int cmd_type, + bool dir, int total_set_num, uint8_t received_data) +{ + s_uvdm_header *SEC_UVDM_HEADER; + uint8_t *SendMSG = (uint8_t *)data; + + SEC_UVDM_HEADER = (s_uvdm_header *)&SendMSG[4]; + + SEC_UVDM_HEADER->pid = pid; + SEC_UVDM_HEADER->data_type = data_type; + SEC_UVDM_HEADER->cmd_type = cmd_type; + SEC_UVDM_HEADER->direction = dir; + SEC_UVDM_HEADER->total_set_num = total_set_num; + SEC_UVDM_HEADER->data = received_data; + + pr_info("%s : pid=0x%x, data_type=%d, cmd_type=%d, dir=%d\n", __func__, + SEC_UVDM_HEADER->pid, SEC_UVDM_HEADER->data_type, + SEC_UVDM_HEADER->cmd_type, SEC_UVDM_HEADER->direction); +} +EXPORT_SYMBOL(set_sec_uvdm_header); + +void set_sec_uvdm_tx_header(void *data, + int first_set, int cur_set, int total_size, int remained_size) +{ + s_tx_header *SEC_TX_HAEDER; + uint8_t *SendMSG = (uint8_t *)data; + + if (first_set) + SEC_TX_HAEDER = (s_tx_header *)&SendMSG[8]; + else + SEC_TX_HAEDER = (s_tx_header *)&SendMSG[4]; + SEC_TX_HAEDER->cur_size = get_data_size(first_set, remained_size); + SEC_TX_HAEDER->total_size = total_size; + SEC_TX_HAEDER->order_cur_set = cur_set; +} +EXPORT_SYMBOL(set_sec_uvdm_tx_header); + +void set_sec_uvdm_tx_tailer(void *data) +{ + s_tx_tailer *SEC_TX_TAILER; + uint8_t *SendMSG = (uint8_t *)data; + + SEC_TX_TAILER = (s_tx_tailer *)&SendMSG[24]; + SEC_TX_TAILER->checksum = + get_checksum(SendMSG, 4, SEC_UVDM_CHECKSUM_COUNT); +} +EXPORT_SYMBOL(set_sec_uvdm_tx_tailer); + +void set_sec_uvdm_rx_header(void *data, int cur_num, int cur_set, int ack) +{ + s_rx_header *SEC_RX_HEADER; + uint8_t *SendMSG = (uint8_t *)data; + + SEC_RX_HEADER = (s_rx_header *)&SendMSG[4]; + SEC_RX_HEADER->order_cur_set = cur_num; + SEC_RX_HEADER->rcv_data_size = cur_set; + SEC_RX_HEADER->result_value = ack; +} +EXPORT_SYMBOL(set_sec_uvdm_rx_header); + +struct pdic_misc_dev *get_pdic_misc_dev(void) +{ + struct pdic_misc_dev *c_dev; + + if (!p_m_core) + return NULL; + c_dev = &p_m_core->c_dev; + return c_dev; +} +EXPORT_SYMBOL(get_pdic_misc_dev); + +static inline int _lock(atomic_t *excl) +{ + if (atomic_inc_return(excl) == 1) + return 0; + else { + atomic_dec(excl); + return -1; + } +} + +static inline void _unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +static int pdic_misc_open(struct inode *inode, struct file *file) +{ + int ret = 0; + + pr_info("%s + open success\n", __func__); + if (!p_m_core) { + pr_err("%s - error : p_m_core is NULL\n", __func__); + ret = -ENODEV; + goto err; + } + + if (_lock(&p_m_core->c_dev.open_excl)) { + pr_err("%s - error : device busy\n", __func__); + ret = -EBUSY; + goto err; + } + + /* stop direct charging(pps) for uvdm and wait latest psrdy done for 1 second */ + if (p_m_core->c_dev.pps_control) { + if (!p_m_core->c_dev.pps_control(0)) { + _unlock(&p_m_core->c_dev.open_excl); + pr_err("%s - error : psrdy is not done\n", __func__); + ret = -EBUSY; + goto err; + } + } + + /* check if there is some connection */ + if (!p_m_core->c_dev.uvdm_ready || + !p_m_core->c_dev.uvdm_ready()) { + _unlock(&p_m_core->c_dev.open_excl); + pr_err("%s - error : uvdm is not ready\n", __func__); + ret = -EBUSY; + goto err; + } + + pr_info("%s - open success\n", __func__); + + return 0; +err: + return ret; +} + +static int pdic_misc_close(struct inode *inode, struct file *file) +{ + if (!p_m_core) { + pr_err("%s - error : p_m_core is NULL\n", __func__); + return -ENODEV; + } + + if (p_m_core) + _unlock(&p_m_core->c_dev.open_excl); + p_m_core->c_dev.uvdm_close(); + if (p_m_core->c_dev.pps_control) + p_m_core->c_dev.pps_control(1); /* start direct charging(pps) */ + + pr_info("%s - close success\n", __func__); + return 0; +} + +static int send_uvdm_message(void *data, int size) +{ + int ret = 0; + + if (!p_m_core) { + ret = -ENODEV; + return ret; + } + + if (p_m_core->c_dev.uvdm_write) + ret = p_m_core->c_dev.uvdm_write(data, size); + + pr_info("%s - size : %d, ret : %d\n", __func__, size, ret); + return ret; +} + +static int receive_uvdm_message(void *data, int size) +{ + int ret = 0; + + if (!p_m_core) { + ret = -ENODEV; + return ret; + } + + if (p_m_core->c_dev.uvdm_read) + ret = p_m_core->c_dev.uvdm_read(data); + + pr_info("%s - size : %d, ret : %d\n", __func__, size, ret); + return ret; +} + +static long +pdic_misc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int ret = 0; + void *buf = NULL; + + if (_lock(&p_m_core->c_dev.ioctl_excl)) { + pr_err("%s - error : ioctl busy - cmd : %d\n", __func__, cmd); + ret = -EBUSY; + goto err2; + } + + if (!p_m_core->c_dev.uvdm_ready()) { + pr_err("%s - error : uvdm is not ready\n", __func__); + ret = -EACCES; + goto err1; + } + + switch (cmd) { + case PDIC_IOCTL_UVDM: + pr_info("%s - PDIC_IOCTL_UVDM cmd\n", __func__); + if (copy_from_user(&p_m_core->c_dev.u_data, + (void __user *) arg, sizeof(struct uvdm_data))) { + ret = -EIO; + pr_err("%s - copy_from_user error\n", __func__); + goto err1; + } + + buf = kzalloc(MAX_BUF, GFP_KERNEL); + if (!buf) { + ret = -EINVAL; + pr_err("%s - kzalloc error\n", __func__); + goto err1; + } + + if (p_m_core->c_dev.u_data.size > MAX_BUF) { + ret = -ENOMEM; + pr_err("%s - user data size is %d error\n", + __func__, p_m_core->c_dev.u_data.size); + goto err; + } + + if (p_m_core->c_dev.u_data.dir == DIR_OUT) { + if (copy_from_user(buf, p_m_core->c_dev.u_data.pData, p_m_core->c_dev.u_data.size)) { + ret = -EIO; + pr_err("%s - copy_from_user error\n", __func__); + goto err; + } + ret = send_uvdm_message(buf, p_m_core->c_dev.u_data.size); + if (ret < 0) { + pr_err("%s - send_uvdm_message error\n", __func__); + ret = -EINVAL; + goto err; + } + } else { + ret = receive_uvdm_message(buf, p_m_core->c_dev.u_data.size); + if (ret < 0) { + pr_err("%s - receive_uvdm_message error\n", __func__); + ret = -EINVAL; + goto err; + } + if (copy_to_user((void __user *)p_m_core->c_dev.u_data.pData, + buf, ret)) { + ret = -EIO; + pr_err("%s - copy_to_user error\n", __func__); + goto err; + } + } + break; +#ifdef CONFIG_COMPAT + case PDIC_IOCTL_UVDM_32: + pr_info("%s - PDIC_IOCTL_UVDM_32 cmd\n", __func__); + if (copy_from_user(&p_m_core->c_dev.u_data_32, compat_ptr(arg), + sizeof(struct uvdm_data_32))) { + ret = -EIO; + pr_err("%s - copy_from_user error\n", __func__); + goto err1; + } + + buf = kzalloc(MAX_BUF, GFP_KERNEL); + if (!buf) { + ret = -EINVAL; + pr_err("%s - kzalloc error\n", __func__); + goto err1; + } + + if (p_m_core->c_dev.u_data_32.size > MAX_BUF) { + ret = -ENOMEM; + pr_err("%s - user data size is %d error\n", __func__, p_m_core->c_dev.u_data_32.size); + goto err; + } + + if (p_m_core->c_dev.u_data_32.dir == DIR_OUT) { + if (copy_from_user(buf, compat_ptr(p_m_core->c_dev.u_data_32.pData), + p_m_core->c_dev.u_data_32.size)) { + ret = -EIO; + pr_err("%s - copy_from_user error\n", __func__); + goto err; + } + ret = send_uvdm_message(buf, p_m_core->c_dev.u_data_32.size); + if (ret < 0) { + pr_err("%s - send_uvdm_message error\n", __func__); + ret = -EINVAL; + goto err; + } + } else { + ret = receive_uvdm_message(buf, p_m_core->c_dev.u_data_32.size); + if (ret < 0) { + pr_err("%s - receive_uvdm_message error\n", __func__); + ret = -EINVAL; + goto err; + } + if (copy_to_user(compat_ptr(p_m_core->c_dev.u_data_32.pData), + buf, ret)) { + ret = -EIO; + pr_err("%s - copy_to_user error\n", __func__); + goto err; + } + } + break; +#endif + default: + pr_err("%s - unknown ioctl cmd : %d\n", __func__, cmd); + ret = -ENOIOCTLCMD; + goto err; + } +err: + kfree(buf); +err1: + _unlock(&p_m_core->c_dev.ioctl_excl); +err2: + return ret; +} + +#ifdef CONFIG_COMPAT +static long +pdic_misc_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int ret = 0; + + pr_info("%s - cmd : %d\n", __func__, cmd); + ret = pdic_misc_ioctl(file, cmd, (unsigned long)compat_ptr(arg)); + + return ret; +} +#endif + +static const struct file_operations pdic_misc_fops = { + .owner = THIS_MODULE, + .open = pdic_misc_open, + .release = pdic_misc_close, + .llseek = no_llseek, + .unlocked_ioctl = pdic_misc_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = pdic_misc_compat_ioctl, +#endif +}; + +static int ums_update_open(struct inode *ip, struct file *fp) +{ + struct pdic_fwupdate_data *fw_data; + size_t p_fw_size = 0; + void *fw_buf, *misc_data; + int ret = 0; + + pr_info("%s +\n", __func__); + + if (!p_m_core) { + pr_err("%s - error : p_m_core is NULL\n", __func__); + ret = -ENODEV; + goto err; + } + + fw_data = &p_m_core->fw_data; + + if (_lock(&fw_data->opened)) { + pr_err("%s - error : device busy\n", __func__); + ret = -EBUSY; + goto err; + } + + if (fw_data->ic_data->get_prev_fw_size) + p_fw_size = fw_data->ic_data->get_prev_fw_size + (fw_data->ic_data->data); + + if (p_fw_size <= 0 || p_fw_size > MAX_FW_SIZE) { + ret = -EFAULT; + pr_err("%s p_fw_size is %lu error\n", __func__, p_fw_size); + goto err1; + } + + /* alloc fw size +20% and align PAGE_SIZE */ + p_fw_size += p_fw_size/5; + p_fw_size = (p_fw_size/PAGE_SIZE)*PAGE_SIZE; + + pr_info("%s fw_buf size=%lu\n", __func__, p_fw_size); + + misc_data = kzalloc(sizeof(struct pdic_misc_data), GFP_KERNEL); + if (!misc_data) { + ret = -ENOMEM; + goto err1; + } + + fw_buf = vzalloc(p_fw_size); + if (!fw_buf) { + ret = -ENOMEM; + goto err2; + } + + fw_data->misc_data = misc_data; + fw_data->misc_data->fw_buf = fw_buf; + fw_data->misc_data->offset = 0; + fw_data->misc_data->fw_buf_size = p_fw_size; + + fp->private_data = fw_data; + pr_info("%s -\n", __func__); + return 0; +err2: + kfree(misc_data); +err1: + _unlock(&fw_data->opened); +err: + pr_info("%s error -\n", __func__); + return ret; +} + +static int ums_update_close(struct inode *ip, struct file *fp) +{ + struct pdic_fwupdate_data *fw_data; + void *fw_buf; + size_t fw_size = 0; + int fw_error = 0; + int ret = 0; + + pr_info("%s +\n", __func__); + + fw_data = fp->private_data; + + fw_buf = fw_data->misc_data->fw_buf; + fw_size = fw_data->misc_data->offset; + fw_error = fw_data->misc_data->is_error; + + pr_info("firmware_update fw_size=%lu fw_error=%d\n", + fw_size, fw_error); + + if (!fw_error && fw_data->ic_data->firmware_update) { + fw_data->ic_data->firmware_update(fw_data->ic_data->data, + fw_buf, fw_size); + } + + vfree(fw_data->misc_data->fw_buf); + kfree(fw_data->misc_data); + + _unlock(&fw_data->opened); + + pr_info("%s -\n", __func__); + return ret; +} + +static ssize_t ums_update_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + ssize_t ret = count; + + pr_info("%s\n", __func__); + return ret; +} + +static ssize_t ums_update_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct pdic_fwupdate_data *fw_data = fp->private_data; + ssize_t ret = count; + void *fw_buf; + void *fw_partial_buf; + size_t fw_partial_size = 0; + size_t fw_offset = 0; + + pr_info("%s start\n", __func__); + fw_partial_size = count; + + if (fw_partial_size <= 0) { + pr_err("%s count=%zu\n", __func__, count); + ret = -EFAULT; + goto err; + } + + fw_partial_buf = kzalloc(count, GFP_KERNEL); + if (!fw_partial_buf) { + ret = -ENOMEM; + goto err; + } + + if (copy_from_user(fw_partial_buf, buf, fw_partial_size)) { + pr_err("%s copy_from_user error.\n", __func__); + ret = -EFAULT; + goto err1; + } + + fw_buf = fw_data->misc_data->fw_buf; + fw_offset = fw_data->misc_data->offset; + if (fw_offset + fw_partial_size > fw_data->misc_data->fw_buf_size) { + pr_err("%s buf size=%lu overrun error\n", + __func__, fw_offset + fw_partial_size); + ret = -EFAULT; + goto err1; + } + memcpy(fw_buf + fw_offset, fw_partial_buf, fw_partial_size); + + fw_data->misc_data->offset += fw_partial_size; + +err1: + kfree(fw_partial_buf); +err: + if (ret < 0) + fw_data->misc_data->is_error = 1; + + pr_info("%s end\n", __func__); + return ret; +} + +static const struct file_operations ums_update_fops = { + .owner = THIS_MODULE, + .open = ums_update_open, + .release = ums_update_close, + .read = ums_update_read, + .write = ums_update_write, +}; + +static struct miscdevice pdic_misc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = NODE_OF_MISC, + .fops = &pdic_misc_fops, +}; + +static struct miscdevice ums_update_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = NODE_OF_UMS, + .fops = &ums_update_fops, +}; + +int pdic_misc_init(ppdic_data_t ppdic_data) +{ + int ret = 0; + + p_m_core = kzalloc(sizeof(struct pdic_misc_core), GFP_KERNEL); + if (!p_m_core) { + ret = -ENOMEM; + pr_err("%s - kzalloc failed : %d\n", __func__, ret); + goto err; + } + atomic_set(&p_m_core->c_dev.open_excl, 0); + atomic_set(&p_m_core->c_dev.ioctl_excl, 0); + atomic_set(&p_m_core->fw_data.opened, 0); + + if (ppdic_data) { + ppdic_data->misc_dev = &p_m_core->c_dev; + p_m_core->fw_data.ic_data = &ppdic_data->fw_data; + } else { + ret = -ENOENT; + pr_err("%s - ppdic_data error : %d\n", __func__, ret); + goto err1; + } + + ret = misc_register(&pdic_misc_device); + if (ret) { + pr_err("%s - pdic_misc return error : %d\n", + __func__, ret); + goto err1; + } + + if (p_m_core->fw_data.ic_data->firmware_update) { + ret = misc_register(&ums_update_device); + if (ret) { + pr_err("%s - ums_update return error : %d\n", + __func__, ret); + goto err2; + } + } + + pr_info("%s - register success\n", __func__); + return 0; +err2: + misc_deregister(&pdic_misc_device); +err1: + kfree(p_m_core); +err: + return ret; +} +EXPORT_SYMBOL(pdic_misc_init); + +void pdic_misc_exit(void) +{ + pr_info("%s() called\n", __func__); + if (p_m_core) { + if (p_m_core->fw_data.ic_data->firmware_update) + misc_deregister(&ums_update_device); + misc_deregister(&pdic_misc_device); + kfree(p_m_core); + } +} +EXPORT_SYMBOL(pdic_misc_exit); diff --git a/drivers/usb/typec/common/pdic_notifier.c b/drivers/usb/typec/common/pdic_notifier.c new file mode 100644 index 000000000000..8e261c263c35 --- /dev/null +++ b/drivers/usb/typec/common/pdic_notifier.c @@ -0,0 +1,497 @@ +/* + * Copyrights (C) 2016-2019 Samsung Electronics, 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. + */ + +#include +#include +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) +#include +#endif +#if IS_ENABLED(CONFIG_SEC_PD) +#include +#elif defined(CONFIG_BATTERY_NOTIFIER) +#include +#endif +#include +#include +#include +#define DRIVER_DESC "PDIC Notifier driver" + +#define SET_PDIC_NOTIFIER_BLOCK(nb, fn, dev) do { \ + (nb)->notifier_call = (fn); \ + (nb)->priority = (dev); \ + } while (0) + +#define DESTROY_PDIC_NOTIFIER_BLOCK(nb) \ + SET_PDIC_NOTIFIER_BLOCK(nb, NULL, -1) + +static struct pdic_notifier_data pdic_notifier; + +static int pdic_notifier_init_done; + +const char *pdic_event_src_string(pdic_notifier_device src) +{ + /* enum pdic_notifier_device */ + switch (src) { + case PDIC_NOTIFY_DEV_INITIAL: + return "INITIAL"; + case PDIC_NOTIFY_DEV_USB: + return "USB"; + case PDIC_NOTIFY_DEV_BATT: + return "BATTERY"; + case PDIC_NOTIFY_DEV_PDIC: + return "PDIC"; + case PDIC_NOTIFY_DEV_MUIC: + return "MUIC"; + case PDIC_NOTIFY_DEV_CCIC: + return "CCIC"; + case PDIC_NOTIFY_DEV_MANAGER: + return "MANAGER"; + case PDIC_NOTIFY_DEV_DP: + return "DP"; + case PDIC_NOTIFY_DEV_USB_DP: + return "USBDP"; + case PDIC_NOTIFY_DEV_SUB_BATTERY: + return "BATTERY2"; + case PDIC_NOTIFY_DEV_SECOND_MUIC: + return "MUIC2"; + case PDIC_NOTIFY_DEV_DEDICATED_MUIC: + return "DMUIC"; + case PDIC_NOTIFY_DEV_ALL: + return "ALL"; + default: + return "UNDEFINED"; + } +} +EXPORT_SYMBOL(pdic_event_src_string); + +const char *pdic_event_dest_string(pdic_notifier_device dest) +{ + return pdic_event_src_string(dest); +} +EXPORT_SYMBOL(pdic_event_dest_string); + +const char *pdic_event_id_string(pdic_notifier_id_t id) +{ + /* enum pdic_notifier_id_t */ + switch (id) { + case PDIC_NOTIFY_ID_INITIAL: + return "ID_INITIAL"; + case PDIC_NOTIFY_ID_ATTACH: + return "ID_ATTACH"; + case PDIC_NOTIFY_ID_RID: + return "ID_RID"; + case PDIC_NOTIFY_ID_USB: + return "ID_USB"; + case PDIC_NOTIFY_ID_POWER_STATUS: + return "ID_POWER_STATUS"; + case PDIC_NOTIFY_ID_WATER: + return "ID_WATER"; + case PDIC_NOTIFY_ID_VCONN: + return "ID_VCONN"; + case PDIC_NOTIFY_ID_OTG: + return "ID_OTG"; + case PDIC_NOTIFY_ID_TA: + return "ID_TA"; + case PDIC_NOTIFY_ID_DP_CONNECT: + return "ID_DP_CONNECT"; + case PDIC_NOTIFY_ID_DP_HPD: + return "ID_DP_HPD"; + case PDIC_NOTIFY_ID_DP_LINK_CONF: + return "ID_DP_LINK_CONF"; + case PDIC_NOTIFY_ID_USB_DP: + return "ID_USB_DP"; + case PDIC_NOTIFY_ID_ROLE_SWAP: + return "ID_ROLE_SWAP"; + case PDIC_NOTIFY_ID_FAC: + return "ID_FAC"; + case PDIC_NOTIFY_ID_CC_PIN_STATUS: + return "ID_PIN_STATUS"; + case PDIC_NOTIFY_ID_WATER_CABLE: + return "ID_WATER_CABLE"; + case PDIC_NOTIFY_ID_POFF_WATER: + return "ID_POFF_WATER"; + case PDIC_NOTIFY_ID_DEVICE_INFO: + return "ID_DEVICE_INFO"; + case PDIC_NOTIFY_ID_SVID_INFO: + return "ID_SVID_INFO"; + case PDIC_NOTIFY_ID_CLEAR_INFO: + return "ID_CLEAR_INFO"; +#if IS_ENABLED(CONFIG_MUIC_POGO) + case PDIC_NOTIFY_ID_POGO: + return "ID_POGO"; +#endif + default: + return "UNDEFINED"; + } +} +EXPORT_SYMBOL(pdic_event_id_string); + +const char *pdic_rid_string(pdic_notifier_rid_t rid) +{ + switch (rid) { + case RID_UNDEFINED: + return "RID_UNDEFINED"; + case RID_000K: + return "RID_000K"; + case RID_001K: + return "RID_001K"; + case RID_255K: + return "RID_255K"; + case RID_301K: + return "RID_301K"; + case RID_523K: + return "RID_523K"; + case RID_619K: + return "RID_619K"; + case RID_OPEN: + return "RID_OPEN"; + default: + return "RID_UNDEFINED"; + } +} +EXPORT_SYMBOL(pdic_rid_string); + +const char *pdic_usbstatus_string(USB_STATUS usbstatus) +{ + switch (usbstatus) { + case USB_STATUS_NOTIFY_DETACH: + return "USB_DETACH"; + case USB_STATUS_NOTIFY_ATTACH_DFP: + return "USB_ATTACH_DFP"; + case USB_STATUS_NOTIFY_ATTACH_UFP: + return "USB_ATTACH_UFP"; + case USB_STATUS_NOTIFY_ATTACH_DRP: + return "USB_ATTACH_DRP"; + default: + return "UNDEFINED"; + } +} +EXPORT_SYMBOL(pdic_usbstatus_string); + +const char *pdic_ccpinstatus_string(pdic_notifier_pin_status_t ccpinstatus) +{ + switch (ccpinstatus) { + case PDIC_NOTIFY_PIN_STATUS_NO_DETERMINATION: + return "NO_DETERMINATION"; + case PDIC_NOTIFY_PIN_STATUS_CC1_ACTIVE: + return "CC1_ACTIVE"; + case PDIC_NOTIFY_PIN_STATUS_CC2_ACTIVE: + return "CC2_ACTIVE"; + case PDIC_NOTIFY_PIN_STATUS_AUDIO_ACCESSORY: + return "AUDIO_ACCESSORY"; + case PDIC_NOTIFY_PIN_STATUS_DEBUG_ACCESSORY: + return "DEBUG_ACCESSORY"; + case PDIC_NOTIFY_PIN_STATUS_PDIC_ERROR: + return "CCIC_ERROR"; + case PDIC_NOTIFY_PIN_STATUS_DISABLED: + return "DISABLED"; + case PDIC_NOTIFY_PIN_STATUS_RFU: + return "RFU"; + case PDIC_NOTIFY_PIN_STATUS_NOCC_USB_ACTIVE: + return "NOCC_USB_ACTIVE"; + default: + return "NO_DETERMINATION"; + } +} + +int pdic_notifier_register(struct notifier_block *nb, notifier_fn_t notifier, + pdic_notifier_device listener) +{ + int ret = 0; + struct device *pdic_device = get_pdic_device(); + + if (!pdic_device) { + pr_err("%s: pdic_device is null.\n", __func__); + return -ENODEV; + } + pr_info("%s: listener=%d register\n", __func__, listener); + +#if IS_BUILTIN(CONFIG_PDIC_NOTIFIER) + /* Check if PDIC Notifier is ready. */ + if (!pdic_notifier_init_done) + pdic_notifier_init(); +#endif + + SET_PDIC_NOTIFIER_BLOCK(nb, notifier, listener); + ret = blocking_notifier_chain_register(&(pdic_notifier.notifier_call_chain), nb); + if (ret < 0) + pr_err("%s: blocking_notifier_chain_register error(%d)\n", + __func__, ret); + + /* current pdic's attached_device status notify */ + mutex_lock(&pdic_notifier.notify_mutex); + nb->notifier_call(nb, 0, + &(pdic_notifier.pdic_template)); + mutex_unlock(&pdic_notifier.notify_mutex); + + return ret; +} +EXPORT_SYMBOL(pdic_notifier_register); + +int pdic_notifier_unregister(struct notifier_block *nb) +{ + int ret = 0; + + pr_info("%s: listener=%d unregister\n", __func__, nb->priority); + + ret = blocking_notifier_chain_unregister(&(pdic_notifier.notifier_call_chain), nb); + if (ret < 0) + pr_err("%s: blocking_notifier_chain_unregister error(%d)\n", + __func__, ret); + DESTROY_PDIC_NOTIFIER_BLOCK(nb); + + return ret; +} +EXPORT_SYMBOL(pdic_notifier_unregister); + +void pdic_uevent_work(int id, int state) +{ + char *water[2] = { "CCIC=WATER", NULL }; + char *dry[2] = { "CCIC=DRY", NULL }; + char *vconn[2] = { "CCIC=VCONN", NULL }; +#if defined(CONFIG_SEC_FACTORY) + char pdicrid[15] = {0,}; + char *rid[2] = {pdicrid, NULL}; + char pdicFacErr[20] = {0,}; + char *facErr[2] = {pdicFacErr, NULL}; + char pdicPinStat[20] = {0,}; + char *pinStat[2] = {pdicPinStat, NULL}; +#endif + struct device *pdic_device = get_pdic_device(); + + if (!pdic_device) { + pr_info("pdic_dev is null\n"); + return; + } + + pr_info("usb: %s: id=%s state=%d\n", __func__, pdic_event_id_string(id), state); + + switch (id) { + case PDIC_NOTIFY_ID_WATER: + if (state) + kobject_uevent_env(&pdic_device->kobj, KOBJ_CHANGE, water); + else + kobject_uevent_env(&pdic_device->kobj, KOBJ_CHANGE, dry); + break; + case PDIC_NOTIFY_ID_VCONN: + kobject_uevent_env(&pdic_device->kobj, KOBJ_CHANGE, vconn); + break; +#if defined(CONFIG_SEC_FACTORY) + case PDIC_NOTIFY_ID_RID: + snprintf(pdicrid, sizeof(pdicrid), "%s", pdic_rid_string(state)); + kobject_uevent_env(&pdic_device->kobj, KOBJ_CHANGE, rid); + break; + case PDIC_NOTIFY_ID_FAC: + snprintf(pdicFacErr, sizeof(pdicFacErr), "%s:%d", "ERR_STATE", state); + kobject_uevent_env(&pdic_device->kobj, KOBJ_CHANGE, facErr); + break; + case PDIC_NOTIFY_ID_CC_PIN_STATUS: + snprintf(pdicPinStat, sizeof(pdicPinStat), "%s", pdic_ccpinstatus_string(state)); + kobject_uevent_env(&pdic_device->kobj, KOBJ_CHANGE, pinStat); + break; +#endif + default: + break; + } +} + +/* pdic's attached_device attach broadcast */ +int pdic_notifier_notify(PD_NOTI_TYPEDEF *p_noti, void *pd, int pdic_attach) +{ + int ret = 0; + +#if IS_BUILTIN(CONFIG_PDIC_NOTIFIER) + /* Check if PDIC Notifier is ready. */ + if (!pdic_notifier_init_done) + pdic_notifier_init(); +#endif + + mutex_lock(&pdic_notifier.notify_mutex); + pdic_notifier.pdic_template = *p_noti; + + switch (p_noti->id) { +#if IS_ENABLED(CONFIG_SEC_PD) || defined(CONFIG_BATTERY_NOTIFIER) + case PDIC_NOTIFY_ID_POWER_STATUS: /* PDIC_NOTIFY_EVENT_PD_SINK */ + pr_info("%s: src:%01x dest:%01x id:%02x " + "attach:%02x cable_type:%02x rprd:%01x\n", __func__, + ((PD_NOTI_ATTACH_TYPEDEF *)p_noti)->src, + ((PD_NOTI_ATTACH_TYPEDEF *)p_noti)->dest, + ((PD_NOTI_ATTACH_TYPEDEF *)p_noti)->id, + ((PD_NOTI_ATTACH_TYPEDEF *)p_noti)->attach, + ((PD_NOTI_ATTACH_TYPEDEF *)p_noti)->cable_type, + ((PD_NOTI_ATTACH_TYPEDEF *)p_noti)->rprd); + + if (pd != NULL) { + pdic_notifier.pdic_template.pd = pd; + pr_info("%s: PD event:%d, num:%d, sel:%d \n", __func__, + ((struct pdic_notifier_struct *)pd)->event, + ((struct pdic_notifier_struct *)pd)->sink_status.available_pdo_num, + ((struct pdic_notifier_struct *)pd)->sink_status.selected_pdo_num); + } + break; +#endif + case PDIC_NOTIFY_ID_ATTACH: + pr_info("%s: src:%01x dest:%01x id:%02x " + "attach:%02x cable_type:%02x rprd:%01x\n", __func__, + ((PD_NOTI_ATTACH_TYPEDEF *)p_noti)->src, + ((PD_NOTI_ATTACH_TYPEDEF *)p_noti)->dest, + ((PD_NOTI_ATTACH_TYPEDEF *)p_noti)->id, + ((PD_NOTI_ATTACH_TYPEDEF *)p_noti)->attach, + ((PD_NOTI_ATTACH_TYPEDEF *)p_noti)->cable_type, + ((PD_NOTI_ATTACH_TYPEDEF *)p_noti)->rprd); + break; + case PDIC_NOTIFY_ID_RID: + pr_info("%s: src:%01x dest:%01x id:%02x rid:%02x\n", __func__, + ((PD_NOTI_RID_TYPEDEF *)p_noti)->src, + ((PD_NOTI_RID_TYPEDEF *)p_noti)->dest, + ((PD_NOTI_RID_TYPEDEF *)p_noti)->id, + ((PD_NOTI_RID_TYPEDEF *)p_noti)->rid); +#if defined(CONFIG_SEC_FACTORY) + pdic_uevent_work(PDIC_NOTIFY_ID_RID, ((PD_NOTI_RID_TYPEDEF *)p_noti)->rid); +#endif + break; +#ifdef CONFIG_SEC_FACTORY + case PDIC_NOTIFY_ID_FAC: + pr_info("%s: src:%01x dest:%01x id:%02x ErrState:%02x\n", __func__, + p_noti->src, p_noti->dest, p_noti->id, p_noti->sub1); + pdic_uevent_work(PDIC_NOTIFY_ID_FAC, p_noti->sub1); + mutex_unlock(&pdic_notifier.notify_mutex); + return 0; +#endif + case PDIC_NOTIFY_ID_WATER: + pr_info("%s: src:%01x dest:%01x id:%02x attach:%02x\n", __func__, + ((PD_NOTI_ATTACH_TYPEDEF *)p_noti)->src, + ((PD_NOTI_ATTACH_TYPEDEF *)p_noti)->dest, + ((PD_NOTI_ATTACH_TYPEDEF *)p_noti)->id, + ((PD_NOTI_ATTACH_TYPEDEF *)p_noti)->attach); + pdic_uevent_work(PDIC_NOTIFY_ID_WATER, ((PD_NOTI_ATTACH_TYPEDEF *)p_noti)->attach); +#ifdef CONFIG_SEC_FACTORY + pr_info("%s: Do not notifier, just return\n", __func__); + mutex_unlock(&pdic_notifier.notify_mutex); + return 0; +#endif + break; + case PDIC_NOTIFY_ID_VCONN: + pdic_uevent_work(PDIC_NOTIFY_ID_VCONN, 0); + break; + case PDIC_NOTIFY_ID_ROLE_SWAP: + pr_info("%s: src:%01x dest:%01x id:%02x sub1:%02x\n", __func__, + ((PD_NOTI_ATTACH_TYPEDEF *)p_noti)->src, + ((PD_NOTI_ATTACH_TYPEDEF *)p_noti)->dest, + ((PD_NOTI_ATTACH_TYPEDEF *)p_noti)->id, + ((PD_NOTI_ATTACH_TYPEDEF *)p_noti)->attach); + break; +#ifdef CONFIG_SEC_FACTORY + case PDIC_NOTIFY_ID_CC_PIN_STATUS: + pr_info("%s: src:%01x dest:%01x id:%02x pinStatus:%02x\n", __func__, + p_noti->src, p_noti->dest, p_noti->id, p_noti->sub1); + pdic_uevent_work(PDIC_NOTIFY_ID_CC_PIN_STATUS, p_noti->sub1); + mutex_unlock(&pdic_notifier.notify_mutex); + return 0; +#endif + case PDIC_NOTIFY_ID_DEVICE_INFO: + pr_info("%s: src:%01x dest:%01x id:%02x vendor_id:%04x product_id:%04x ifpmic_index:%02x version:%02x\n", + __func__, + ((PD_NOTI_DEVICE_INFO_TYPEDEF *)p_noti)->src, + ((PD_NOTI_DEVICE_INFO_TYPEDEF *)p_noti)->dest, + ((PD_NOTI_DEVICE_INFO_TYPEDEF *)p_noti)->id, + ((PD_NOTI_DEVICE_INFO_TYPEDEF *)p_noti)->vendor_id, + ((PD_NOTI_DEVICE_INFO_TYPEDEF *)p_noti)->product_id, + ((PD_NOTI_DEVICE_INFO_TYPEDEF *)p_noti)->ifpmic_index, + ((PD_NOTI_DEVICE_INFO_TYPEDEF *)p_noti)->version); + break; + case PDIC_NOTIFY_ID_SVID_INFO: + pr_info("%s: src:%01x dest:%01x id:%02x standard_vendor_id:%04x\n", + __func__, + ((PD_NOTI_SVID_INFO_TYPEDEF *)p_noti)->src, + ((PD_NOTI_SVID_INFO_TYPEDEF *)p_noti)->dest, + ((PD_NOTI_SVID_INFO_TYPEDEF *)p_noti)->id, + ((PD_NOTI_SVID_INFO_TYPEDEF *)p_noti)->standard_vendor_id); + break; + case PDIC_NOTIFY_ID_CLEAR_INFO: + pr_info("%s: src:%01x dest:%01x id:%02x clear_id:%04x\n", + __func__, + ((PD_NOTI_CLEAR_INFO_TYPEDEF *)p_noti)->src, + ((PD_NOTI_CLEAR_INFO_TYPEDEF *)p_noti)->dest, + ((PD_NOTI_CLEAR_INFO_TYPEDEF *)p_noti)->id, + ((PD_NOTI_CLEAR_INFO_TYPEDEF *)p_noti)->clear_id); + break; + default: + pr_info("%s: src:%01x dest:%01x id:%02x " + "sub1:%d sub2:%02x sub3:%02x\n", __func__, + ((PD_NOTI_TYPEDEF *)p_noti)->src, + ((PD_NOTI_TYPEDEF *)p_noti)->dest, + ((PD_NOTI_TYPEDEF *)p_noti)->id, + ((PD_NOTI_TYPEDEF *)p_noti)->sub1, + ((PD_NOTI_TYPEDEF *)p_noti)->sub2, + ((PD_NOTI_TYPEDEF *)p_noti)->sub3); + break; + } +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + if (p_noti->id != PDIC_NOTIFY_ID_POWER_STATUS) + store_usblog_notify(NOTIFY_CCIC_EVENT, (void *)p_noti, NULL); +#endif + ret = blocking_notifier_call_chain(&(pdic_notifier.notifier_call_chain), + p_noti->id, &(pdic_notifier.pdic_template)); + + switch (ret) { + case NOTIFY_STOP_MASK: + case NOTIFY_BAD: + pr_err("%s: notify error occur(0x%x)\n", __func__, ret); + break; + case NOTIFY_DONE: + case NOTIFY_OK: + pr_info("%s: notify done(0x%x)\n", __func__, ret); + break; + default: + pr_info("%s: notify status unknown(0x%x)\n", __func__, ret); + break; + } + + mutex_unlock(&pdic_notifier.notify_mutex); + return ret; +} +EXPORT_SYMBOL(pdic_notifier_notify); + +int pdic_notifier_init(void) +{ + int ret = 0; + + pr_info("%s\n", __func__); + if (pdic_notifier_init_done) { + pr_err("%s already registered\n", __func__); + goto out; + } + pdic_notifier_init_done = 1; + pdic_core_init(); + BLOCKING_INIT_NOTIFIER_HEAD(&(pdic_notifier.notifier_call_chain)); + mutex_init(&pdic_notifier.notify_mutex); + +out: + return ret; +} + +static void __exit pdic_notifier_exit(void) +{ + mutex_destroy(&pdic_notifier.notify_mutex); + pr_info("%s: exit\n", __func__); +} + +device_initcall(pdic_notifier_init); +module_exit(pdic_notifier_exit); + +MODULE_AUTHOR("Samsung USB Team"); +MODULE_DESCRIPTION("Pdic Notifier"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION(DRIVER_DESC); diff --git a/drivers/usb/typec/common/pdic_param.c b/drivers/usb/typec/common/pdic_param.c new file mode 100644 index 000000000000..b9b2560e973e --- /dev/null +++ b/drivers/usb/typec/common/pdic_param.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * + * Copyright (C) 2021 Samsung Electronics + * + * 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, see . + * + */ +#define pr_fmt(fmt) "pdic_param: " fmt + +#include +#include +#include +#include + +static const char * const pdic_param_mode[] = { + [PDIC_PARAM_MODE_NO] = "Normal", + [PDIC_PARAM_MODE_OB] = "OB", + [PDIC_PARAM_MODE_IB] = "IB", + [PDIC_PARAM_MODE_DL] = "DL", + [PDIC_PARAM_MODE_LC] = "LC", +}; + +#if IS_ENABLED(CONFIG_USB_FACTORY_MODE) && ((IS_MODULE(CONFIG_SEC_PARAM) || IS_ENABLED(CONFIG_SEC_MPARAM)) && !IS_ENABLED(CONFIG_PDIC_USE_MODULE_PARAM)) +extern char *f_mode; +#else +static char *f_mode; +#endif + +#if ((IS_MODULE(CONFIG_SEC_PARAM) || IS_ENABLED(CONFIG_SEC_MPARAM)) && !IS_ENABLED(CONFIG_PDIC_USE_MODULE_PARAM)) +extern unsigned int lpcharge; +extern int factory_mode; +#else +static unsigned int lpcharge; +static int factory_mode; +#endif + +static int pdic_param_lpcharge = -1; +module_param(pdic_param_lpcharge, int, 0444); + +static int pdic_param_factory_mode = -1; +module_param(pdic_param_factory_mode, int, 0444); + +static char __read_mostly *f_usb_mode; +module_param(f_usb_mode, charp, 0444); + +static int pdic_param_recovery_mode; +module_param(pdic_param_recovery_mode, int, 0444); + +static unsigned int usb_mode = PDIC_PARAM_MODE_NO; +#if IS_BUILTIN(CONFIG_PDIC_NOTIFIER) +static int __init read_f_mode(char *str) +{ + if (strncmp(str, "LC", 2) == 0) + usb_mode = PDIC_PARAM_MODE_LC; + else if (strncmp(str, "IB", 2) == 0) + usb_mode = PDIC_PARAM_MODE_IB; + else if (strncmp(str, "OB", 2) == 0) + usb_mode = PDIC_PARAM_MODE_OB; + else if (strncmp(str, "DL", 2) == 0) + usb_mode = PDIC_PARAM_MODE_DL; + else + usb_mode = PDIC_PARAM_MODE_NO; + return 0; +} +early_param("f_mode", read_f_mode); + +static int __init set_pdic_param_lpm_charge(char *str) +{ + if (strncmp(str, "charger", 7) == 0) + pdic_param_lpcharge = 1; + else + pdic_param_lpcharge = 0; + pr_info("%s: Low power charging mode: %d\n", + __func__, pdic_param_lpcharge); + + return 0; +} +early_param("androidboot.mode", set_pdic_param_lpm_charge); +#endif + +int check_factory_mode_boot(void) +{ + int factory = 0; + + if (f_mode) { + if (!strncmp(f_mode, "LC", 2)) + factory = 1; + else if (!strncmp(f_mode, "IB", 2)) + factory = 1; + else if (!strncmp(f_mode, "OB", 2)) + factory = 1; + else if (!strncmp(f_mode, "DL", 2)) + factory = 1; + else + factory = 0; + goto ret; + } + + if (usb_mode != PDIC_PARAM_MODE_NO) + factory = 1; + + if (f_usb_mode) { + pr_info("%s f_usb_mode=%s\n", __func__, f_usb_mode); + factory = 1; + } + +ret: + pr_info("%s factory=%d\n", __func__, factory); + return factory; +} +EXPORT_SYMBOL_GPL(check_factory_mode_boot); + +int get_usb_factory_mode(void) +{ + int mode = PDIC_PARAM_MODE_NO; + + if (f_mode) { + if (!strncmp(f_mode, "LC", 2)) + mode = PDIC_PARAM_MODE_LC; + else if (!strncmp(f_mode, "IB", 2)) + mode = PDIC_PARAM_MODE_IB; + else if (!strncmp(f_mode, "OB", 2)) + mode = PDIC_PARAM_MODE_OB; + else if (!strncmp(f_mode, "DL", 2)) + mode = PDIC_PARAM_MODE_DL; + else + mode = PDIC_PARAM_MODE_NO; + goto ret; + } + + mode = usb_mode; + +ret: + pr_info("%s mode=%s\n", __func__, pdic_param_mode[mode]); + return mode; +} +EXPORT_SYMBOL_GPL(get_usb_factory_mode); + +int is_lpcharge_pdic_param(void) +{ + int ret = 0; + + if (pdic_param_lpcharge != -1) { + ret = pdic_param_lpcharge; + goto out; + } + + ret = lpcharge; + +out: + pr_info("%s lpcharge=%d\n", __func__, ret); + return ret; +} +EXPORT_SYMBOL_GPL(is_lpcharge_pdic_param); + +int is_factory_mode_pdic_param(void) +{ + int ret = 0; + + if (pdic_param_factory_mode != -1) { + ret = pdic_param_factory_mode; + goto out; + } + + ret = factory_mode; + +out: + pr_info("%s factory_mode=%d\n", __func__, ret); + return ret; +} +EXPORT_SYMBOL_GPL(is_factory_mode_pdic_param); + +int is_recovery_mode_pdic_param(void) +{ + if (pdic_param_recovery_mode) + pr_info("%s recovery_mode=%d\n", __func__, pdic_param_recovery_mode); + return pdic_param_recovery_mode; +} +EXPORT_SYMBOL_GPL(is_recovery_mode_pdic_param); + diff --git a/drivers/usb/typec/common/pdic_policy.c b/drivers/usb/typec/common/pdic_policy.c new file mode 100755 index 000000000000..101b3ddb4f1d --- /dev/null +++ b/drivers/usb/typec/common/pdic_policy.c @@ -0,0 +1,2207 @@ +/* + * + * Copyright (C) 2021 Samsung Electronics + * + * 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, see . + * + */ +#define pr_fmt(fmt) "pdic_policy: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) +#include +#endif +#if IS_ENABLED(CONFIG_BATTERY_NOTIFIER) +#include +#else +#include +#endif +#include +#include + +#define ROLE_SWAP_TIME_MS 5000 +#define HOST_ON_WAIT_TIME_SEC 3 + +enum pp_status { + PP_GOOD, + PP_ERROR, +}; + +enum cc_on { + PP_CCOFF, + PP_CCON, +}; + +enum cc_state { + PP_TOGGLE, + PP_CCRP, + PP_CCRD, + PP_CCAUDIO, + PP_CCDEBUG, +}; + +enum cc_direction { + PP_CC1, + PP_CC2, +}; + +enum cc_rp_current { + PP_56K = 1, + PP_22K, + PP_10K, +}; + +enum pp_power_role { + PP_NO_POW_ROLE, + PP_SINK, + PP_SOURCE, +}; + +enum pp_data_role { + PP_NO_DATA_ROLE, + PP_UFP, + PP_DFP, +}; + +enum pp_vbus { + PP_VBUSOFF, + PP_VBUSON, +}; + +enum pp_rid { + PP_NORID, + PP_R301K, + PP_R255K, + PP_R523K, + PP_R619K, +}; + +enum pp_uid { + PP_NOUID, + PP_U301K, + PP_U255K, + PP_U523K, + PP_U619K, +}; + +enum pp_water { + PP_DRY, + PP_WATER, +}; + +enum pp_ccshort { + PP_CC_NORMAL, + PP_CC_SHORT, +}; + +enum pp_sbushort { + PP_SBU_NORMAL, + PP_SBU_SHORT, +}; + +enum explicit_contract { + PP_NO_EXCNT, + PP_EXCNT, +}; + +enum pp_killer { + PP_NO_KILLER, + PP_KILLER, +}; + +struct role_swap { + struct completion swap_coml; + int try_power_role; + int try_data_role; + int try_port_type; + int status; +}; + +struct usb_host_val { + wait_queue_head_t host_turn_on_wait_q; + int host_turn_on_event; + int host_turn_on_wait_time; + int detach_done_wait; + int device_add; +}; + +struct pdic_policy { + struct pp_ic_data *ic_data; + struct typec_port *port; + struct typec_partner *partner; + struct usb_pd_identity partner_identity; + struct typec_capability typec_cap; + struct pdic_notifier_struct pd_noti; + struct mutex p_lock; + struct workqueue_struct *pdic_wq; + struct delayed_work dischar_work; + struct role_swap pp_r_s; + struct usb_host_val usb_host; + struct pdic_alt_info alt_info; +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + struct usbpd_dev usbpd_d; + struct if_cb_manager *man; +#endif + struct notifier_block usb_ext_noti_nb; + int cc_on; + int cc_state; + int cc_direction; + int cc_rp_current; + int power_role; + int data_role; + int vbus; + int rid; + int uid; + int water; + int cc_short; + int sbu_short; + int usb_killer; + int explicit_contract; + int support_pd; + int acc_type; + int temp_alt_mode_stop; +}; + +struct pdic_policy_pd_noti { + struct pdic_policy *pp_data; + struct pdic_notifier_struct *pd_noti; +}; + +struct pdic_policy_pd_noti pp_pd_noti; + +static const char * const pp_msg[] = { + [MSG_INITIAL] = "initail", + [MSG_SRC] = "SRC", + [MSG_SNK] = "SNK", + [MSG_VBUSON] = "VBUSON", + [MSG_VBUSOFF] = "VBUSOFF", + [MSG_RP56K] = "RP56K", + [MSG_RP22K] = "RP22K", + [MSG_RP10K] = "RP10K", + [MSG_UFP] = "UFP", + [MSG_DFP] = "DFP", + [MSG_AUDIO] = "AUDIO", + [MSG_DEBUG] = "DEBUG", + [MSG_CC1] = "CC1", + [MSG_CC2] = "CC2", + [MSG_NOCC_WAKE] = "NOCC_WAKE", + [MSG_CC_SHORT] = "CC_SHORT", + [MSG_SBU_SHORT] = "SBU_SHORT", + [MSG_WATER] = "WATER", + [MSG_DRY] = "DRY", + [MSG_ROPEN] = "ROPEN", + [MSG_R301K] = "R301K", + [MSG_R255K] = "R255K", + [MSG_R523K] = "R523K", + [MSG_R619K] = "R619K", + [MSG_UOPEN] = "UOPEN", + [MSG_U301K] = "U301K", + [MSG_U255K] = "U255K", + [MSG_U523K] = "U523K", + [MSG_U619K] = "U619K", + [MSG_FAC_ERR] = "FAC ERROR", + [MSG_EX_CNT] = "EXPLICIT CONTRACT", + [MSG_KILLER] = "USB_KILLER", + [MSG_DCOVER] = "DISCOVER ALT DEVICE", + [MSG_DP_CONN] = "DP CONNECT", + [MSG_DP_DISCONN] = "DP DISCONNECT", + [MSG_DP_LINK_CONF] = "DP LINK CONFIGURATION", + [MSG_DP_HPD] = "DP HPD", + [MSG_DEVICE_INFO] = "DEVICE INFO", + [MSG_SVID_INFO] = "SVID_INFO", + [MSG_SELECT_PDO] = "SELECT_PDO", + [MSG_CURRENT_PDO] = "CURRENT_PDO", + [MSG_PD_POWER_STATUS] = "PD_POWER_STATUS", + [MSG_FAC_MODE_NOTI_TO_MUIC] = "FAC_MODE_NOTI_TO_MUIC", + [MSG_GET_ROLESWAP_CHECK] = "GET_ROLESWAP_CHECK", + [MSG_GET_ACC] = "GET_ACC", + [MSG_MUIC_SET_BC12] = "MUIC_SET_BC12", + [MSG_SHUTDOWN] = "SHUTDOWN", + [MSG_CCOFF] = "CC OFF", + [MSG_MAX] = "MSG MAX", +}; + +static const char * const pd_p_cc_on[] = { + [PP_CCOFF] = "ccoff", + [PP_CCON] = "ccon", +}; + +static const char * const pd_p_cc_state[] = { + [PP_TOGGLE] = "cc_toggle", + [PP_CCRP] = "cc_rp", + [PP_CCRD] = "cc_rd", + [PP_CCAUDIO] = "cc_audio", + [PP_CCDEBUG] = "cc_debug", +}; + +static const char * const pd_p_cc_direction[] = { + [PP_CC1] = "cc1", + [PP_CC2] = "cc2", +}; + +static const char * const pd_p_cc_rp_current[] = { + [PP_56K] = "rp56k", + [PP_22K] = "rp22k", + [PP_10K] = "rp10k", +}; + +static const char * const pd_p_power_role[] = { + [PP_NO_POW_ROLE] = "detach", + [PP_SINK] = "sink", + [PP_SOURCE] = "source", +}; + +static const char * const pd_p_data_role[] = { + [PP_NO_DATA_ROLE] = "detach", + [PP_UFP] = "UFP", + [PP_DFP] = "DFP", +}; + +static const char * const pd_p_vbus[] = { + [PP_VBUSOFF] = "vbus_off", + [PP_VBUSON] = "vbus_on", +}; + +static const char * const pd_p_rid[] = { + [PP_NORID] = "NO RID", + [PP_R301K] = "R301K", + [PP_R255K] = "R255K", + [PP_R523K] = "R523K", + [PP_R619K] = "R619K", +}; + +static const char * const pd_p_uid[] = { + [PP_NOUID] = "NO RID", + [PP_U301K] = "U301K", + [PP_U255K] = "U255K", + [PP_U523K] = "U523K", + [PP_U619K] = "U619K", +}; + +static const char * const pd_p_water[] = { + [PP_DRY] = "dry", + [PP_WATER] = "water", +}; + +static const char * const pd_p_ccshort[] = { + [PP_CC_NORMAL] = "normal", + [PP_CC_SHORT] = "cc_short", +}; + +static const char * const pd_p_sbushort[] = { + [PP_SBU_NORMAL] = "normal", + [PP_SBU_SHORT] = "sbu_short", +}; + +static const char * const pd_p_contract[] = { + [PP_NO_EXCNT] = "no contract", + [PP_EXCNT] = "explicit contract", +}; + +#ifdef CONFIG_TYPEC +static const char * const pd_p_typec_roles[] = { + [TYPEC_SINK] = "sink", + [TYPEC_SOURCE] = "source", +}; + +static const char * const pd_p_data_roles[] = { + [TYPEC_DEVICE] = "device", + [TYPEC_HOST] = "host", +}; + +static const char * const pd_p_port_types[] = { + [TYPEC_PORT_SRC] = "source", + [TYPEC_PORT_SNK] = "sink", + [TYPEC_PORT_DRP] = "dual", +}; +#endif + +static const char * const pd_noti_dp_pin[] = { + [PDIC_NOTIFY_DP_PIN_UNKNOWN] = "unknown", + [PDIC_NOTIFY_DP_PIN_A] = "Pin A", + [PDIC_NOTIFY_DP_PIN_B] = "Pin B", + [PDIC_NOTIFY_DP_PIN_C] = "Pin C", + [PDIC_NOTIFY_DP_PIN_D] = "Pin D", + [PDIC_NOTIFY_DP_PIN_E] = "Pin E", + [PDIC_NOTIFY_DP_PIN_F] = "Pin F", +}; + +static void pdic_policy_event_notifier + (struct work_struct *data) +{ + struct pdic_state_work *event_work = + container_of(data, struct pdic_state_work, pdic_work); + PD_NOTI_TYPEDEF pdic_noti; + + switch(event_work->dest){ + case PDIC_NOTIFY_DEV_USB : + pr_info("usb:%s, dest=%s, id=%s, attach=%s, drp=%s\n", __func__, + pdic_event_dest_string(event_work->dest), + pdic_event_id_string(event_work->id), + event_work->attach? "Attached": "Detached", + pdic_usbstatus_string(event_work->event)); + break; + default : + pr_info("usb:%s, dest=%s, id=%s, attach=%d, event=%d\n", __func__, + pdic_event_dest_string(event_work->dest), + pdic_event_id_string(event_work->id), + event_work->attach, + event_work->event); + break; + } + + pdic_noti.src = PDIC_NOTIFY_DEV_PDIC; + pdic_noti.dest = event_work->dest; + pdic_noti.id = event_work->id; + pdic_noti.sub1 = event_work->attach; + pdic_noti.sub2 = event_work->event; + pdic_noti.sub3 = event_work->sub; +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + pdic_noti.pd = pp_pd_noti.pd_noti; +#endif + pdic_notifier_notify((PD_NOTI_TYPEDEF*)&pdic_noti, NULL, 0); + + kfree(event_work); +} + +void pdic_policy_event_work(void *data, int dest, + int id, int attach, int event, int sub) +{ + struct pdic_policy *pp_data = data; + struct pdic_state_work * event_work; + + pr_info("usb: %s\n", __func__); + event_work = kmalloc(sizeof(struct pdic_state_work), GFP_ATOMIC); + if (!event_work) { + pr_err("%s: failed to alloc for event_work\n", __func__); + return; + } + INIT_WORK(&event_work->pdic_work, pdic_policy_event_notifier); + + event_work->dest = dest; + event_work->id = id; + event_work->attach = attach; + event_work->event = event; + event_work->sub = sub; + + queue_work(pp_data->pdic_wq, &event_work->pdic_work); +} + +#define PDIC_POLICY_SEND_NOTI(data, dest, id, attach, event, sub) \ + pdic_policy_event_work(data, dest, id, attach, event, sub) + +static bool is_short(struct pdic_policy *pp_data) +{ + if (pp_data->cc_short == PP_CC_SHORT || + pp_data->sbu_short == PP_SBU_SHORT) + return true; + else + return false; +} + +static void vbus_discharging_work(struct work_struct *work) +{ + struct pdic_policy *pp_data = container_of(to_delayed_work(work), + struct pdic_policy, dischar_work); + int val1 = 0, val2 = 0; + + val1 = gpio_get_value(pp_data->ic_data->vbus_dischar_gpio); + gpio_set_value(pp_data->ic_data->vbus_dischar_gpio, 0); + val2 = gpio_get_value(pp_data->ic_data->vbus_dischar_gpio); + pr_info("%s vbus_discharging %d->%d\n", __func__, val1, val2); +} + +#ifdef CONFIG_TYPEC +static int get_typec_pwr_mode(struct pdic_policy *pp_data) +{ + enum typec_pwr_opmode mode = TYPEC_PWR_MODE_USB; + + if (pp_data->support_pd && pp_data->explicit_contract) + mode = TYPEC_PWR_MODE_PD; + else if (pp_data->cc_rp_current == PP_22K) + mode = TYPEC_PWR_MODE_1_5A; + else if (pp_data->cc_rp_current == PP_10K) + mode = TYPEC_PWR_MODE_3_0A; + else + ; + + return mode; +} + +static void process_typec_data_role(struct pdic_policy *pp_data, + int set_role) +{ + struct typec_partner_desc desc; + int mode = TYPEC_PWR_MODE_USB; + int power = 0, data = 0; + + if (pp_data->ic_data->typec_implemented) + goto done; + + if (set_role == PP_DFP) + data = TYPEC_HOST; + else + data = TYPEC_DEVICE; + + if (pp_data->pp_r_s.try_data_role != PP_NO_DATA_ROLE) { + if (set_role == pp_data->pp_r_s.try_data_role) + pp_data->pp_r_s.status = PP_GOOD; + else + pp_data->pp_r_s.status = PP_ERROR; + + complete(&pp_data->pp_r_s.swap_coml); + } + + if (set_role == PP_NO_DATA_ROLE) { + if (!IS_ERR(pp_data->partner)) + typec_unregister_partner(pp_data->partner); + pp_data->partner = NULL; + typec_set_data_role(pp_data->port, TYPEC_DEVICE); + typec_set_pwr_opmode(pp_data->port, TYPEC_PWR_MODE_USB); + goto done; + } + + typec_set_data_role(pp_data->port, data); + if (!pp_data->partner) { + if (pp_data->power_role == PP_SOURCE) + power = TYPEC_SOURCE; + else + power = TYPEC_SINK; + typec_set_pwr_role(pp_data->port, power); + mode = get_typec_pwr_mode(pp_data); + desc.usb_pd = (mode == TYPEC_PWR_MODE_PD) ? 1 : 0; + desc.accessory = TYPEC_ACCESSORY_NONE; + desc.identity = NULL; + pp_data->partner = typec_register_partner(pp_data->port, &desc); + typec_set_pwr_opmode(pp_data->port, mode); + pr_info("%s registerd partner. usb_pd=%d\n", + __func__, desc.usb_pd); + } +done: + return; +} + +static void process_typec_power_role(struct pdic_policy *pp_data, + int set_role) +{ + int power = 0, try_role = PP_NO_POW_ROLE; + int compl_cond = 0; + + if (pp_data->ic_data->typec_implemented) + goto done; + + if (set_role == PP_SOURCE) + power = TYPEC_SOURCE; + else + power = TYPEC_SINK; + + if (pp_data->pp_r_s.try_power_role != PP_NO_POW_ROLE) { + try_role = pp_data->pp_r_s.try_power_role; + compl_cond = 1; + } else if (pp_data->pp_r_s.try_port_type != PP_NO_POW_ROLE) { + try_role = pp_data->pp_r_s.try_port_type; + if (set_role != PP_NO_POW_ROLE) + compl_cond = 1; + } else + ; + + if (compl_cond) { + if (set_role == try_role) + pp_data->pp_r_s.status = PP_GOOD; + else + pp_data->pp_r_s.status = PP_ERROR; + + complete(&pp_data->pp_r_s.swap_coml); + } + + typec_set_pwr_role(pp_data->port, power); +done: + return; +} +#else +inline int get_typec_pwr_mode(struct pdic_policy *pp_data) {return 0; } +inline void process_typec_data_role(struct pdic_policy *pp_data, int msg) {} +inline void process_typec_power_role(struct pdic_policy *pp_data, int msg) {} +#endif + +static void pdic_policy_alt_dev_detach(struct pdic_policy *pp_data) +{ + struct pp_ic_data *ic_data; + + pr_info("%s\n", __func__); + + ic_data = pp_data->ic_data; + if (!ic_data) { + pr_err("%s ic_data in null\n", __func__); + goto err; + } + + if (pp_data->acc_type != PDIC_DOCK_DETACHED) { + if (pp_data->acc_type != PDIC_DOCK_NEW) + pdic_send_dock_intent(PDIC_DOCK_DETACHED); + pdic_send_dock_uevent(pp_data->alt_info.vendor_id, + pp_data->alt_info.product_id, + PDIC_DOCK_DETACHED); + memset(&pp_data->alt_info, 0, sizeof(struct pdic_alt_info)); + pp_data->acc_type = PDIC_DOCK_DETACHED; + if (ic_data->p_ops && ic_data->p_ops->alt_info_clear) + ic_data->p_ops->alt_info_clear(ic_data->drv_data); + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_ALL, + PDIC_NOTIFY_ID_CLEAR_INFO, PDIC_NOTIFY_ID_DEVICE_INFO, 0, 0); + } +err: + return; +} + +static void pdic_policy_dp_detach(struct pdic_policy *pp_data) +{ + struct pp_ic_data *ic_data; + + pr_info("%s\n", __func__); + + ic_data = pp_data->ic_data; + if (!ic_data) { + pr_err("%s ic_data in null\n", __func__); + goto err; + } + + PDIC_POLICY_SEND_NOTI(pp_data, + PDIC_NOTIFY_DEV_USB_DP, PDIC_NOTIFY_ID_USB_DP, + 0/*dp_is_connect*/, 0/*dp_hs_connect*/, 0); + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_DP, + PDIC_NOTIFY_ID_DP_CONNECT, 0/*attach*/, 0/*drp*/, 0); + + if (ic_data->p_ops && ic_data->p_ops->dp_info_clear) + ic_data->p_ops->dp_info_clear(ic_data->drv_data); +err: + return; +} + +static void pdic_policy_pd_initial(struct pdic_notifier_struct *pd_noti) +{ + if (pd_noti->sink_status.available_pdo_num) + memset(pd_noti->sink_status.power_list, 0, + (sizeof(POWER_LIST) * (MAX_PDO_NUM + 1))); + pd_noti->sink_status.has_apdo = false; + pd_noti->sink_status.available_pdo_num = 0; + pd_noti->sink_status.selected_pdo_num = 0; + pd_noti->sink_status.current_pdo_num = 0; +#if !IS_ENABLED(CONFIG_BATTERY_NOTIFIER) + pd_noti->sink_status.vid = 0; + pd_noti->sink_status.pid = 0; + pd_noti->sink_status.xid = 0; +#endif + pd_noti->sink_status.pps_voltage = 0; + pd_noti->sink_status.pps_current = 0; + pd_noti->sink_status.rp_currentlvl = RP_CURRENT_LEVEL_NONE; +} + +static void pdic_policy_pd_detach(struct pdic_policy *pp_data) +{ + struct pdic_notifier_struct *pd_noti = NULL; + + if (!pp_pd_noti.pd_noti) { + pr_info("%s pd_noti is null\n", __func__); + goto skip; + } + + pd_noti = pp_pd_noti.pd_noti; + + pdic_policy_pd_initial(pd_noti); + + pd_noti->event = PDIC_NOTIFY_EVENT_DETACH; + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_POWER_STATUS, + 0, 0, 0); +skip: + return; +} + +static void process_policy_cc_attach(struct pdic_policy *pp_data, int msg) +{ + struct otg_notify *o_notify = get_otg_notify(); + struct pdic_notifier_struct *pd_noti = NULL; + + int val1 = 0, val2 = 0; + int event = 0; + + pp_data->cc_on = PP_CCON; + + if (gpio_is_valid(pp_data->ic_data->vbus_dischar_gpio)) { + if (delayed_work_pending(&pp_data->dischar_work)) { + val1 = gpio_get_value(pp_data->ic_data->vbus_dischar_gpio); + gpio_set_value(pp_data->ic_data->vbus_dischar_gpio, 0); + val2 = gpio_get_value(pp_data->ic_data->vbus_dischar_gpio); + cancel_delayed_work_sync(&pp_data->dischar_work); + pr_info("%s vbus_discharging %d->%d\n", __func__, val1, val2); + } + } + + if (msg == MSG_SRC) { + if (pp_data->power_role == PP_SINK && + pp_data->cc_state == PP_CCRP) { + if (pp_pd_noti.pd_noti) { + pd_noti = pp_pd_noti.pd_noti; + + pdic_policy_pd_initial(pd_noti); + + pd_noti->event = PDIC_NOTIFY_EVENT_PD_PRSWAP_SNKTOSRC; + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_POWER_STATUS, + 0, 0, 0); + } + } + pp_data->cc_state = PP_CCRD; + pp_data->power_role = PP_SOURCE; + send_otg_notify(o_notify, NOTIFY_EVENT_POWER_SOURCE, 1); +#ifdef CONFIG_TYPEC + process_typec_power_role(pp_data, PP_SOURCE); +#endif + } else if (msg == MSG_SNK) { + if (pp_data->power_role == PP_SOURCE && + pp_data->cc_state == PP_CCRD) { + if (pp_pd_noti.pd_noti) { + pd_noti = pp_pd_noti.pd_noti; + pdic_policy_pd_initial(pd_noti); + pd_noti->event = PDIC_NOTIFY_EVENT_PD_PRSWAP_SRCTOSNK; + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_POWER_STATUS, + 0, 0, 0); + } + } + pp_data->cc_state = PP_CCRP; + pp_data->power_role = PP_SINK; + send_otg_notify(o_notify, NOTIFY_EVENT_POWER_SOURCE, 0); +#ifdef CONFIG_TYPEC + process_typec_power_role(pp_data, PP_SINK); +#endif + } else if (msg == MSG_AUDIO) { + pp_data->cc_state = PP_CCAUDIO; + + event = NOTIFY_EXTRA_USB_ANALOGAUDIO; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); + } else if (msg == MSG_DEBUG) { + pp_data->cc_state = PP_CCDEBUG; + } else + ; +} + +static void vbus_turn_on_ctrl(struct pdic_policy *pp_data, bool enable) +{ + struct otg_notify *o_notify = get_otg_notify(); + struct power_supply *psy_otg; + union power_supply_propval val; + int on = !!enable; + int ret = 0; + bool must_block_host = 0; + bool unsupport_host = 0; + static int reserve_booster; + int val1 = 0, val2 = 0; + + if (!o_notify) { + pr_err("%s o_notify is null\n", __func__); + goto skip_notify; + } + + must_block_host = is_blocked(o_notify, NOTIFY_BLOCK_TYPE_HOST); + unsupport_host = !is_usb_host(o_notify); + + pr_info("%s : enable=%d, must_block_host=%d unsupport_host=%d\n", + __func__, enable, must_block_host, unsupport_host); + + if (enable) { + if (must_block_host || unsupport_host) { + on = false; + pr_info("%s : turn off vbus because of blocked host\n", + __func__); + } + } + + if (o_notify->booting_delay_sec && on) { + pr_info("%s is booting_delay_sec. skip to control booster\n", + __func__); + reserve_booster = 1; + send_otg_notify(o_notify, NOTIFY_EVENT_RESERVE_BOOSTER, 1); + goto err; + } + + if (reserve_booster && !on) { + reserve_booster = 0; + send_otg_notify(o_notify, NOTIFY_EVENT_RESERVE_BOOSTER, 0); + } + +skip_notify: + + pr_info("%s on=%d\n", __func__, on); + + psy_otg = power_supply_get_by_name("otg"); + if (psy_otg) { + val.intval = on; + ret = psy_otg->desc->set_property(psy_otg, + POWER_SUPPLY_PROP_ONLINE, &val); + if (ret) { + pr_err("%s: fail to set power_suppy ONLINE property(%d)\n", + __func__, ret); + goto err; + } + } else { + pr_err("%s: Fail to get psy battery\n", __func__); + goto err; + } + + pr_info("otg accessory power = %d\n", on); + + if (pp_data->power_role == PP_SOURCE && !on && + gpio_is_valid(pp_data->ic_data->vbus_dischar_gpio)) { + val1 = gpio_get_value(pp_data->ic_data->vbus_dischar_gpio); + gpio_set_value(pp_data->ic_data->vbus_dischar_gpio, 1); + val2 = gpio_get_value(pp_data->ic_data->vbus_dischar_gpio); + schedule_delayed_work + (&pp_data->dischar_work, msecs_to_jiffies(160)); + pr_info("%s vbus_discharging %d->%d\n", __func__, val1, val2); + } +err: + return; +} + +static void process_policy_vbus(struct pdic_policy *pp_data, int msg) +{ + if (msg == MSG_VBUSON) + pp_data->vbus = PP_VBUSON; + else + pp_data->vbus = PP_VBUSOFF; + + vbus_turn_on_ctrl(pp_data, pp_data->vbus); +} + +static void process_policy_rpcurrent(struct pdic_policy *pp_data, int msg) +{ + struct pdic_notifier_struct *pd_noti = NULL; +#if IS_ENABLED(CONFIG_TYPEC) + enum typec_pwr_opmode mode = TYPEC_PWR_MODE_USB; +#endif + + pr_info("%s %s (%s)\n", __func__, pp_msg[msg], + pd_p_contract[pp_data->explicit_contract]); + + if (pp_pd_noti.pd_noti) { + pd_noti = pp_pd_noti.pd_noti; + if (msg == MSG_RP56K) { + pd_noti->sink_status.rp_currentlvl = RP_CURRENT_LEVEL_DEFAULT; + pp_data->cc_rp_current = PP_56K; + } else if (msg == MSG_RP22K) { + pd_noti->sink_status.rp_currentlvl = RP_CURRENT_LEVEL2; + pp_data->cc_rp_current = PP_22K; +#if IS_ENABLED(CONFIG_TYPEC) + mode = TYPEC_PWR_MODE_1_5A; +#endif + } else if (msg == MSG_RP10K) { + pd_noti->sink_status.rp_currentlvl = RP_CURRENT_LEVEL3; + pp_data->cc_rp_current = PP_10K; +#if IS_ENABLED(CONFIG_TYPEC) + mode = TYPEC_PWR_MODE_3_0A; +#endif + } else + ; + pd_noti->event = PDIC_NOTIFY_EVENT_PDIC_ATTACH; + + if (pp_data->explicit_contract == PP_NO_EXCNT) { +#if IS_ENABLED(CONFIG_TYPEC) + typec_set_pwr_opmode(pp_data->port, mode); +#endif + if (msg == MSG_RP56K && !is_short(pp_data)) { + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_MUIC, + PDIC_NOTIFY_ID_TA, + 1/*attach*/, 0/*rprd*/, 0); + } + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_POWER_STATUS, + 0/* no power nego*/, 0, 0); + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_MUIC, + PDIC_NOTIFY_ID_RPLEVEL, + PDIC_NOTIFY_ATTACH, + USB_STATUS_NOTIFY_DETACH, + pd_noti->sink_status.rp_currentlvl); + } + } +} + +static void process_policy_data_role(struct pdic_policy *pp_data, int msg) +{ + pr_info("%s msg=%s p_data=%s p_power=%s +\n", __func__, pp_msg[msg], + pd_p_data_role[pp_data->data_role], pd_p_power_role[pp_data->power_role]); + + if (msg == MSG_UFP) { + if (pp_data->data_role == PP_UFP) + goto skip; + if (pp_data->data_role == PP_DFP) { + pdic_policy_alt_dev_detach(pp_data); + pdic_policy_dp_detach(pp_data); + + PDIC_POLICY_SEND_NOTI(pp_data, + PDIC_NOTIFY_DEV_USB, PDIC_NOTIFY_ID_USB, + 0/*attach*/, USB_STATUS_NOTIFY_DETACH/*drp*/, 0); + } + + pp_data->data_role = PP_UFP; + + process_typec_data_role(pp_data, PP_UFP); + + PDIC_POLICY_SEND_NOTI(pp_data, + PDIC_NOTIFY_DEV_MUIC, PDIC_NOTIFY_ID_ATTACH, + 1/*attach*/, 0/*rprd*/, 0); + PDIC_POLICY_SEND_NOTI(pp_data, + PDIC_NOTIFY_DEV_USB, PDIC_NOTIFY_ID_USB, + 1/*attach*/, USB_STATUS_NOTIFY_ATTACH_UFP/*drp*/, 0); + } else if (msg == MSG_DFP) { + if (pp_data->data_role == PP_DFP) + goto skip; + if (pp_data->data_role == PP_UFP) { + PDIC_POLICY_SEND_NOTI(pp_data, + PDIC_NOTIFY_DEV_USB, PDIC_NOTIFY_ID_USB, + 0/*attach*/, USB_STATUS_NOTIFY_DETACH/*drp*/, 0); + } + + pp_data->data_role = PP_DFP; + + process_typec_data_role(pp_data, PP_DFP); + + PDIC_POLICY_SEND_NOTI(pp_data, + PDIC_NOTIFY_DEV_MUIC, + PDIC_NOTIFY_ID_ATTACH, 1/*attach*/, 1/*rprd*/, 0); + PDIC_POLICY_SEND_NOTI(pp_data, + PDIC_NOTIFY_DEV_USB, PDIC_NOTIFY_ID_USB, + 1/*attach*/, USB_STATUS_NOTIFY_ATTACH_DFP/*drp*/, 0); + } else { + pp_data->data_role = PP_NO_DATA_ROLE; + + PDIC_POLICY_SEND_NOTI(pp_data, + PDIC_NOTIFY_DEV_MUIC, PDIC_NOTIFY_ID_ATTACH, + 0/*attach*/, 0/*rprd*/, 0); + /* USB */ + PDIC_POLICY_SEND_NOTI(pp_data, + PDIC_NOTIFY_DEV_USB, PDIC_NOTIFY_ID_USB, + 0/*attach*/, USB_STATUS_NOTIFY_DETACH/*drp*/, 0); + } +skip: + pr_info("%s msg=%s p_data=%s p_power=%s -\n", __func__, pp_msg[msg], + pd_p_data_role[pp_data->data_role], pd_p_power_role[pp_data->power_role]); + return; +} + +static void process_policy_cc_active(struct pdic_policy *pp_data, int msg) +{ + if (msg == MSG_CC1) { + pp_data->cc_direction = PP_CC1; + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_PDIC, + PDIC_NOTIFY_ID_CC_PIN_STATUS, PDIC_NOTIFY_PIN_STATUS_CC1_ACTIVE, + 0, 0); + } else if (msg == MSG_CC2) { + pp_data->cc_direction = PP_CC2; + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_PDIC, + PDIC_NOTIFY_ID_CC_PIN_STATUS, PDIC_NOTIFY_PIN_STATUS_CC2_ACTIVE, + 0, 0); + } else if (msg == MSG_NOCC_WAKE) { + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_PDIC, + PDIC_NOTIFY_ID_CC_PIN_STATUS, PDIC_NOTIFY_PIN_STATUS_NOCC_USB_ACTIVE, + 0, 0); + } else + ; +} + +static void process_policy_water(struct pdic_policy *pp_data, int msg) +{ + if (msg == MSG_WATER) { + pp_data->water = PP_WATER; + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_BATT, PDIC_NOTIFY_ID_WATER, + 1/*attach*/, 0, 0); + } else if (msg == MSG_DRY) { + pp_data->water = PP_DRY; + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_BATT, PDIC_NOTIFY_ID_WATER, + 0/*attach*/, 0, 0); + } else + ; +} + +static void process_policy_rid(struct pdic_policy *pp_data, int msg) +{ + int rid = 0; + int no_usb = 0; + + pr_info("%s msg=%s previous rid=%s\n", __func__, + pp_msg[msg], pd_p_rid[pp_data->rid]); + + switch (msg) { + case MSG_ROPEN: + rid = RID_OPEN; + pp_data->rid = PP_NORID; + break; + case MSG_R301K: + rid = RID_301K; + pp_data->rid = PP_R301K; + break; + case MSG_R255K: + rid = RID_255K; + pp_data->rid = PP_R255K; + break; + case MSG_R523K: + rid = RID_523K; + no_usb = 1; + pp_data->rid = PP_R523K; + break; + case MSG_R619K: + rid = RID_619K; + no_usb = 1; + pp_data->rid = PP_R619K; + break; + default: + pp_data->rid = PP_NORID; + break; + } + + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_MUIC, PDIC_NOTIFY_ID_RID, + rid/*rid*/, 0, 0); + + if (no_usb) + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_USB, PDIC_NOTIFY_ID_USB, + 0/*attach*/, USB_STATUS_NOTIFY_DETACH/*drp*/, 0); +} + +static void process_policy_explicit_contract + (struct pdic_policy *pp_data, int msg, int skip_event) +{ + struct pdic_notifier_struct *pd_noti = NULL; + int mode = TYPEC_PWR_MODE_USB; + + if (msg == MSG_EX_CNT) { + if (pp_data->explicit_contract != PP_EXCNT) { + pp_data->explicit_contract = PP_EXCNT; +#if IS_ENABLED(CONFIG_TYPEC) + if (!pp_data->ic_data->typec_implemented) { + mode = get_typec_pwr_mode(pp_data); + typec_set_pwr_opmode(pp_data->port, mode); + } +#endif + } + if (pp_data->power_role == PP_SINK) { + if (pp_pd_noti.pd_noti) { + if (!skip_event) { + pd_noti = pp_pd_noti.pd_noti; + pd_noti->event = PDIC_NOTIFY_EVENT_PD_SINK; + } + + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_POWER_STATUS, + 1, 0, 0); + } + } + } +} + +static void process_policy_usb_killer(struct pdic_policy *pp_data) +{ + int event = 0; + struct otg_notify *o_notify = get_otg_notify(); + + pp_data->usb_killer = PP_KILLER; + + event = NOTIFY_EXTRA_USBKILLER; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_USB_KILLER_COUNT); +#endif +} + +static void process_policy_fac_err(struct pdic_policy *pp_data, int err) +{ + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_PDIC, + PDIC_NOTIFY_ID_FAC, err, 0, 0); +} + +static void process_policy_fac_mode_noti_to_muic(struct pdic_policy *pp_data, int attach) +{ + PDIC_POLICY_SEND_NOTI(pp_data, + PDIC_NOTIFY_DEV_MUIC, PDIC_NOTIFY_ID_ATTACH, + attach/*attach*/, + USB_STATUS_NOTIFY_DETACH/*rprd*/, 0); +} + +static void process_policy_sbu_short(struct pdic_policy *pp_data, int need_pd_detach) +{ + pp_data->sbu_short = PP_SBU_SHORT; + + if (need_pd_detach) + PDIC_POLICY_SEND_NOTI(pp_data, + PDIC_NOTIFY_DEV_MUIC, PDIC_NOTIFY_ID_ATTACH, + PDIC_NOTIFY_DETACH/*attach*/, + USB_STATUS_NOTIFY_DETACH/*rprd*/, 0); +} + +static int process_policy_get_ppdata(struct pdic_policy *pp_data, int msg) +{ + int ret = -EINVAL; + + switch (msg) { + case MSG_GET_ROLESWAP_CHECK: + if (pp_data->pp_r_s.try_power_role || pp_data->pp_r_s.try_data_role || + pp_data->pp_r_s.try_port_type) + ret = 1; + else + ret = 0; + break; + case MSG_GET_ACC: + ret = pp_data->acc_type; + break; + default: + break; + } + + return ret; +} + +static void process_policy_cc_short(struct pdic_policy *pp_data, int need_pd_noti) +{ + struct pdic_notifier_struct *pd_noti = NULL; + + pp_data->cc_short = PP_CC_SHORT; + + if (need_pd_noti && pp_pd_noti.pd_noti) { + pd_noti = pp_pd_noti.pd_noti; + if (pd_noti->sink_status.rp_currentlvl != RP_CURRENT_ABNORMAL) { + pd_noti->sink_status.rp_currentlvl = RP_CURRENT_ABNORMAL; + pd_noti->event = PDIC_NOTIFY_EVENT_PDIC_ATTACH; + pr_info("%s : rp_currentlvl(%d)\n", __func__, + pd_noti->sink_status.rp_currentlvl); + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_POWER_STATUS, + 0/* no power nego*/, 0, 0); + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_MUIC, + PDIC_NOTIFY_ID_RPLEVEL, + PDIC_NOTIFY_ATTACH, + USB_STATUS_NOTIFY_DETACH, + pd_noti->sink_status.rp_currentlvl); + } + } +} + +static void process_policy_shutdown(struct pdic_policy *pp_data) +{ + int val1 = 0, val2 = 0; + + if (gpio_is_valid(pp_data->ic_data->vbus_dischar_gpio)) { + if (delayed_work_pending(&pp_data->dischar_work)) { + val1 = gpio_get_value(pp_data->ic_data->vbus_dischar_gpio); + gpio_set_value(pp_data->ic_data->vbus_dischar_gpio, 0); + val2 = gpio_get_value(pp_data->ic_data->vbus_dischar_gpio); + cancel_delayed_work_sync(&pp_data->dischar_work); + pr_info("%s vbus_discharging %d->%d\n", __func__, val1, val2); + } + } +} + +static int pdic_policy_get_alt_info(struct pdic_policy *pp_data) +{ + struct pp_ic_data *ic_data; + int ret = 0, i = 0; + + ic_data = pp_data->ic_data; + if (!ic_data) { + pr_err("%s ic_data in null\n", __func__); + goto err; + } + + if (ic_data->p_ops && ic_data->p_ops->get_alt_info) { + ret = ic_data->p_ops->get_alt_info(ic_data->drv_data, + &pp_data->alt_info); + if (ret < 0) { + pr_err("%s get_alt_info error. ret %d\n", __func__, ret); + goto err; + } + pr_info("%s :\n", __func__); + pr_info("vendor_id 0x%02x\n", pp_data->alt_info.vendor_id); + pr_info("product_id 0x%02x\n", pp_data->alt_info.product_id); + pr_info("bcd_device 0x%02x\n", pp_data->alt_info.bcd_device); + for (i = 0; i < 12; i++) { + if (pp_data->alt_info.svid[i] == 0) + break; + pr_info("svid 0x%02x\n", pp_data->alt_info.svid[i]); + } + pr_info("dp_device %d\n", pp_data->alt_info.dp_device); + pr_info("dp_pin_assignment 0x%x\n", pp_data->alt_info.dp_pin_assignment); + pr_info("dp_selected_pin %s\n", + pd_noti_dp_pin[pp_data->alt_info.dp_selected_pin]); + pr_info("hpd_state %d\n", pp_data->alt_info.hpd_state); + pr_info("hpd_irq %d\n", pp_data->alt_info.hpd_irq); + } else + pr_err("%s func is not defined\n", __func__); + return 0; +err: + return ret; +} + +static void process_policy_dicover_alt_dev(struct pdic_policy *pp_data) +{ + struct pdic_alt_info *alt_info; + struct otg_notify *o_notify = get_otg_notify(); + uint16_t vid = 0, pid = 0, acc_type = 0; + int ret = 0; + + ret = pdic_policy_get_alt_info(pp_data); + if (ret < 0) + goto err; + + alt_info = &pp_data->alt_info; + + vid = alt_info->vendor_id; + pid = alt_info->product_id; + + if (vid == SAMSUNG_VENDOR_ID) { + switch (pid) { + /* GearVR: Reserved GearVR PID+6 */ + case GEARVR_PRODUCT_ID: + case GEARVR_PRODUCT_ID_1: + case GEARVR_PRODUCT_ID_2: + case GEARVR_PRODUCT_ID_3: + case GEARVR_PRODUCT_ID_4: + case GEARVR_PRODUCT_ID_5: + acc_type = PDIC_DOCK_HMT; + pr_info("Samsung Gear VR connected.\n"); +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_VR_USE_COUNT); +#endif + break; + case DEXDOCK_PRODUCT_ID: + acc_type = PDIC_DOCK_DEX; + pr_info("Samsung DEX connected.\n"); +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_DEX_USE_COUNT); +#endif + break; + case DEXPAD_PRODUCT_ID: + acc_type = PDIC_DOCK_DEXPAD; + pr_info("Samsung DEX PAD connected.\n"); +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_DEX_USE_COUNT); +#endif + break; + case HDMI_PRODUCT_ID: + acc_type = PDIC_DOCK_HDMI; + pr_info("Samsung HDMI connected.\n"); + break; + default: + pr_info("default device connected.\n"); + acc_type = PDIC_DOCK_NEW; + break; + } + } else if (vid == SAMSUNG_MPA_VENDOR_ID) { + switch (pid) { + case MPA_PRODUCT_ID: + acc_type = PDIC_DOCK_MPA; + pr_info("Samsung MPA connected.\n"); + break; + default: + pr_info("default device connected.\n"); + acc_type = PDIC_DOCK_NEW; + break; + } + } else { + pr_info("unknown device connected.\n"); + acc_type = PDIC_DOCK_NEW; + } + pp_data->acc_type = acc_type; + + if (acc_type != PDIC_DOCK_NEW) + pdic_send_dock_intent(acc_type); + + pdic_send_dock_uevent(vid, pid, acc_type); +err: + return; +} + +static void process_policy_dp(struct pdic_policy *pp_data, int msg) +{ + struct pdic_alt_info *alt_info; + struct otg_notify *o_notify = get_otg_notify(); + uint16_t vid = 0, pid = 0, timeleft = 0; + int ret = 0; + + pr_info("%s msg=%s +\n", __func__, pp_msg[msg]); + + ret = pdic_policy_get_alt_info(pp_data); + if (ret < 0) + goto err; + + alt_info = &pp_data->alt_info; + + vid = alt_info->vendor_id; + pid = alt_info->product_id; + + switch (msg) { + case MSG_DP_CONN: + PDIC_POLICY_SEND_NOTI(pp_data, + PDIC_NOTIFY_DEV_DP, PDIC_NOTIFY_ID_DP_CONNECT, + PDIC_NOTIFY_ATTACH, vid, pid); +#if defined(CONFIG_USB_HW_PARAM) + inc_hw_param(o_notify, USB_CCIC_DP_USE_COUNT); +#endif + pr_info("%s wait_event %ds\n", __func__, + (pp_data->usb_host.host_turn_on_wait_time)); + + timeleft = wait_event_interruptible_timeout( + pp_data->usb_host.host_turn_on_wait_q, + pp_data->usb_host.host_turn_on_event && + !pp_data->usb_host.detach_done_wait, + (pp_data->usb_host.host_turn_on_wait_time)*HZ); + pr_info("%s host turn on wait = %d\n", __func__, timeleft); + + PDIC_POLICY_SEND_NOTI(pp_data, + PDIC_NOTIFY_DEV_USB_DP, PDIC_NOTIFY_ID_USB_DP, + 1/*dp_is_connect*/, 1/*dp_hs_connect*/, 0); + break; + case MSG_DP_DISCONN: + pp_data->usb_host.detach_done_wait = 1; + PDIC_POLICY_SEND_NOTI(pp_data, + PDIC_NOTIFY_DEV_USB_DP, PDIC_NOTIFY_ID_USB_DP, + 0/*dp_is_connect*/, 0/*dp_hs_connect*/, 0); + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_DP, + PDIC_NOTIFY_ID_DP_CONNECT, 0/*attach*/, 0/*drp*/, 0); + break; + case MSG_DP_LINK_CONF: + if (alt_info->dp_selected_pin == PDIC_NOTIFY_DP_PIN_C || + alt_info->dp_selected_pin == PDIC_NOTIFY_DP_PIN_E || + alt_info->dp_selected_pin == PDIC_NOTIFY_DP_PIN_A) + usb_restart_host_mode(pp_data->man, 4); + else + usb_restart_host_mode(pp_data->man, 2); + + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_DP, + PDIC_NOTIFY_ID_DP_LINK_CONF, + alt_info->dp_selected_pin, 0, 0); + break; + case MSG_DP_HPD: + PDIC_POLICY_SEND_NOTI(pp_data, + PDIC_NOTIFY_DEV_DP, PDIC_NOTIFY_ID_DP_HPD, + alt_info->hpd_state, + alt_info->hpd_irq, 0); + break; + default: + break; + } +err: + pr_info("%s msg=%s -\n", __func__, pp_msg[msg]); + return; +} + +static void process_policy_device_info(struct pdic_policy *pp_data) +{ + struct pdic_alt_info *alt_info; + uint16_t vid = 0, pid = 0, bcd_device = 0; + int ret = 0; + + ret = pdic_policy_get_alt_info(pp_data); + if (ret < 0) + goto err; + + alt_info = &pp_data->alt_info; + + vid = alt_info->vendor_id; + pid = alt_info->product_id; + bcd_device = alt_info->bcd_device; + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_ALL, + PDIC_NOTIFY_ID_DEVICE_INFO, vid, + pid, bcd_device); +err: + return; +} + +static void process_policy_svid_info(struct pdic_policy *pp_data, int svid) +{ + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_ALL, + PDIC_NOTIFY_ID_SVID_INFO, svid, 0, 0); +} + +static void pdic_policy_update_pdo_num(void *data, int msg, int pdo_num) +{ + struct pdic_notifier_struct *pd_noti = NULL; + + pr_info("%s pdo_num=%d\n", __func__, pdo_num); + + if (!data) { + pr_err("%s data is NULL\n", __func__); + goto skip; + } + + if (!pp_pd_noti.pd_noti) { + pr_info("%s pd_noti is null\n", __func__); + goto skip; + } + + pd_noti = pp_pd_noti.pd_noti; + + if (msg == MSG_SELECT_PDO) + pd_noti->sink_status.selected_pdo_num = pdo_num; + else if (msg == MSG_CURRENT_PDO) + pd_noti->sink_status.current_pdo_num = pdo_num; + else + ; + +skip: + return; +} + +static void pdic_policy_pd_power_status(struct pdic_policy *pp_data, int attach, int event) +{ + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_POWER_STATUS, attach, event, 0); +} + +static void process_policy_cc_detach(struct pdic_policy *pp_data) +{ + struct otg_notify *o_notify = get_otg_notify(); + int val1 = 0, val2 = 0; + + if (pp_data->cc_on == PP_CCOFF) { + pr_err("%s. prev cc_on=CCOFF. skip.\n", __func__); + goto skip; + } + + if (gpio_is_valid(pp_data->ic_data->vbus_dischar_gpio)) { + val1 = gpio_get_value(pp_data->ic_data->vbus_dischar_gpio); + gpio_set_value(pp_data->ic_data->vbus_dischar_gpio, 1); + val2 = gpio_get_value(pp_data->ic_data->vbus_dischar_gpio); + schedule_delayed_work + (&pp_data->dischar_work, msecs_to_jiffies(120)); + pr_info("%s vbus_discharging %d->%d\n", __func__, val1, val2); + } + + pp_data->usb_host.detach_done_wait = 1; + pp_data->cc_on = PP_CCOFF; + pp_data->cc_state = PP_TOGGLE; + pp_data->cc_direction = PP_CC1; + pp_data->cc_rp_current = PP_56K; + pp_data->power_role = PP_NO_POW_ROLE; + pp_data->data_role = PP_NO_DATA_ROLE; + pp_data->rid = PP_NORID; + pp_data->cc_short = PP_CC_NORMAL; + pp_data->sbu_short = PP_SBU_NORMAL; + pp_data->explicit_contract = PP_NO_EXCNT; + pp_data->usb_killer = PP_NO_KILLER; + send_otg_notify(o_notify, NOTIFY_EVENT_POWER_SOURCE, 0); +#ifdef CONFIG_TYPEC + process_typec_data_role(pp_data, PP_NO_DATA_ROLE); + process_typec_power_role(pp_data, PP_NO_POW_ROLE); +#endif + pdic_policy_pd_detach(pp_data); + + pdic_policy_alt_dev_detach(pp_data); + + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_ALL, + PDIC_NOTIFY_ID_CLEAR_INFO, PDIC_NOTIFY_ID_SVID_INFO, 0, 0); + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_MUIC, PDIC_NOTIFY_ID_ATTACH, + 0/*attach*/, 0/*rprd*/, 0); + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_USB, PDIC_NOTIFY_ID_USB, + 0/*attach*/, USB_STATUS_NOTIFY_DETACH/*drp*/, 0); + PDIC_POLICY_SEND_NOTI(pp_data, PDIC_NOTIFY_DEV_PDIC, + PDIC_NOTIFY_ID_CC_PIN_STATUS, PDIC_NOTIFY_PIN_STATUS_NO_DETERMINATION, + 0, 0); + if (pp_data->temp_alt_mode_stop) { + if (pp_data->ic_data->p_ops && pp_data->ic_data->p_ops->set_alt_mode) + pp_data->ic_data->p_ops->set_alt_mode(ALTERNATE_MODE_START); + } +skip: + return; +} + +void pdic_policy_select_pdo(int num) +{ + struct pdic_notifier_struct *pd_noti = NULL; + + pr_info("%s pdo num(%d) +\n", __func__, num); + + if (!pp_pd_noti.pd_noti) { + pr_info("%s pd_noti is null\n", __func__); + goto skip; + } + + pd_noti = pp_pd_noti.pd_noti; + + if (pd_noti->sink_status.selected_pdo_num == num) { + if (pp_pd_noti.pp_data->explicit_contract == PP_EXCNT) { + pd_noti->event = PDIC_NOTIFY_EVENT_PD_SINK; + + PDIC_POLICY_SEND_NOTI(pp_pd_noti.pp_data, + PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_POWER_STATUS, + 1, 0, 0); + } + } else if (num > pd_noti->sink_status.available_pdo_num) + pd_noti->sink_status.selected_pdo_num = + pd_noti->sink_status.available_pdo_num; + else if (num < 1) + pd_noti->sink_status.selected_pdo_num = 1; + else + pd_noti->sink_status.selected_pdo_num = num; + +skip: + pr_info("%s pdo num(%d) -\n", __func__, num); + return; +} + +int pdic_policy_update_pdo_list(void *data, int max_v, int min_v, + int max_icl, int cnt, int num) +{ + struct pdic_policy *pp_data; + struct pdic_notifier_struct *pd_noti = NULL; + int ret = 0; + + if (!data) { + pr_err("%s data is NULL\n", __func__); + ret = -ENOENT; + goto err; + } + + pr_info("%s +\n", __func__); + + pp_data = data; + + mutex_lock(&pp_data->p_lock); + + if (pp_pd_noti.pd_noti) { + pd_noti = pp_pd_noti.pd_noti; + + pd_noti->sink_status.available_pdo_num = num; + + pd_noti->sink_status.power_list[cnt].max_voltage = max_v; + pd_noti->sink_status.power_list[cnt].min_voltage = min_v; + pd_noti->sink_status.power_list[cnt].max_current = max_icl; + pd_noti->sink_status.power_list[cnt].accept = + (max_v > AVAILABLE_VOLTAGE) ? false : true; + pr_info("%s: receive[PDO %d] max_v:%d, min_v:%d, max_icl:%d\n", + __func__, cnt, max_v, min_v, max_icl); + } + mutex_unlock(&pp_data->p_lock); + pr_info("%s -\n", __func__); +err: + return ret; +} +EXPORT_SYMBOL_GPL(pdic_policy_update_pdo_list); + +int pdic_policy_send_msg(void *data, int msg, int param1, int param2) +{ + struct pdic_policy *pp_data; + int ret = 0; + + if (!data) { + pr_err("%s data is NULL\n", __func__); + ret = -ENOENT; + goto err; + } + + pp_data = data; + + if (msg >= MSG_MAX) { + pr_err("%s msg is invalid. (%d)\n", __func__, msg); + ret = -E2BIG; + goto err; + } + + pr_info("%s msg=(%s) +\n", __func__, pp_msg[msg]); + + mutex_lock(&pp_data->p_lock); + + switch (msg) { + case MSG_SRC: + case MSG_SNK: + case MSG_AUDIO: + case MSG_DEBUG: + process_policy_cc_attach(pp_data, msg); + break; + case MSG_VBUSON: + case MSG_VBUSOFF: + process_policy_vbus(pp_data, msg); + break; + case MSG_RP56K: + case MSG_RP22K: + case MSG_RP10K: + process_policy_rpcurrent(pp_data, msg); + break; + case MSG_CC_SHORT: + process_policy_cc_short(pp_data, param1); + break; + case MSG_SBU_SHORT: + process_policy_sbu_short(pp_data, param1); + break; + case MSG_UFP: + case MSG_DFP: + process_policy_data_role(pp_data, msg); + break; + case MSG_CC1: + case MSG_CC2: + case MSG_NOCC_WAKE: + process_policy_cc_active(pp_data, msg); + case MSG_WATER: + case MSG_DRY: + process_policy_water(pp_data, msg); + break; + case MSG_ROPEN: + case MSG_R301K: + case MSG_R255K: + case MSG_R523K: + case MSG_R619K: + process_policy_rid(pp_data, msg); + break; + case MSG_FAC_ERR: + process_policy_fac_err(pp_data, param1); + break; + case MSG_EX_CNT: + process_policy_explicit_contract(pp_data, msg, param1); + break; + case MSG_KILLER: + process_policy_usb_killer(pp_data); + break; + case MSG_DCOVER: + process_policy_dicover_alt_dev(pp_data); + break; + case MSG_DP_CONN: + case MSG_DP_DISCONN: + case MSG_DP_LINK_CONF: + case MSG_DP_HPD: + process_policy_dp(pp_data, msg); + break; + case MSG_DEVICE_INFO: + process_policy_device_info(pp_data); + case MSG_SVID_INFO: + process_policy_svid_info(pp_data, param1); + case MSG_SELECT_PDO: + case MSG_CURRENT_PDO: + pdic_policy_update_pdo_num(pp_data, msg, param1); + break; + case MSG_PD_POWER_STATUS: + pdic_policy_pd_power_status(pp_data, param1, param2); + break; + case MSG_FAC_MODE_NOTI_TO_MUIC: + process_policy_fac_mode_noti_to_muic(pp_data, param1); + break; + case MSG_GET_ROLESWAP_CHECK: + case MSG_GET_ACC: + ret = process_policy_get_ppdata(pp_data, msg); + break; + case MSG_MUIC_SET_BC12: + muic_set_bc12(pp_data->man, param1); + break; + case MSG_SHUTDOWN: + process_policy_shutdown(pp_data); + break; + case MSG_CCOFF: + process_policy_cc_detach(pp_data); + break; + default: + break; + } + + mutex_unlock(&pp_data->p_lock); + pr_info("%s msg=(%s) -\n", __func__, pp_msg[msg]); +err: + return ret; +} +EXPORT_SYMBOL_GPL(pdic_policy_send_msg); + +#ifdef CONFIG_TYPEC +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) +int port_to_data(struct typec_port *port, + struct pdic_policy **p_pp_data, struct pp_ic_data **p_ic_data) +{ + struct pdic_policy *pp_data = typec_get_drvdata(port); + struct pp_ic_data *ic_data; + if (!pp_data) + goto err; + + *p_pp_data = pp_data; + + ic_data = pp_data->ic_data; + if (!ic_data) + goto err; + + *p_ic_data = ic_data; + return 0; +err: + return -EINVAL; +} +#endif + +void pp_role_swap_init(struct role_swap *pp_r_s) +{ + pp_r_s->status = PP_GOOD; + pp_r_s->try_data_role = PP_NO_DATA_ROLE; + pp_r_s->try_power_role = PP_NO_POW_ROLE; + pp_r_s->try_port_type = PP_NO_POW_ROLE; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) +int pdic_policy_dr_set(struct typec_port *port, enum typec_data_role role) +#else +int pdic_policy_dr_set(const struct typec_capability *cap, + enum typec_data_role role) +#endif +{ + struct pdic_policy *pp_data = NULL; + struct pp_ic_data *ic_data = NULL; + int ret = 0, wait = 0; + unsigned long time = 0; + + if (role < TYPEC_DEVICE || role > TYPEC_HOST) { + pr_err("%s role=%d +\n", __func__, role); + ret = -EINVAL; + goto err; + } + + pr_info("%s role=%s +\n", __func__, pd_p_data_roles[role]); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) + if (port_to_data(port, &pp_data, &ic_data)) { + ret = -ENOENT; + goto err; + } +#else + pp_data = container_of(cap, struct pdic_policy, typec_cap); + ic_data = pp_data->ic_data; + if (!ic_data) { + ret = -ENOENT; + goto err; + } +#endif + + if (!ic_data->p_ops || !ic_data->p_ops->dr_set) { + ret = -ECHILD; + goto err; + } + + if (role == TYPEC_HOST) { + pp_data->pp_r_s.try_data_role = PP_DFP; + wait = 1; + } else if (role == TYPEC_DEVICE) { + pp_data->pp_r_s.try_data_role = PP_UFP; + wait = 1; + } else { + wait = 0; + goto skip; + } + + reinit_completion(&pp_data->pp_r_s.swap_coml); + pp_data->pp_r_s.status = PP_GOOD; + ret = ic_data->p_ops->dr_set(ic_data->drv_data, role); + if (ret) + goto err; + + if (wait) { + time = wait_for_completion_timeout(&pp_data->pp_r_s.swap_coml, + msecs_to_jiffies(ROLE_SWAP_TIME_MS)); + if (time == 0) { + ret = -ETIMEDOUT; + goto err; + } else if (pp_data->pp_r_s.status == PP_ERROR) { + ret = -EPROTO; + goto err; + } + } +skip: +err: + pp_role_swap_init(&pp_data->pp_r_s); + + pr_info("%s ret=%d -\n", __func__, ret); + return ret; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) +int pdic_policy_pr_set(struct typec_port *port, enum typec_role role) +#else +int pdic_policy_pr_set(const struct typec_capability *cap, enum typec_role role) +#endif +{ + struct pdic_policy *pp_data = NULL; + struct pp_ic_data *ic_data = NULL; + int ret = 0, wait = 0; + unsigned long time = 0; + + if (role < TYPEC_SINK || role > TYPEC_SOURCE) { + pr_err("%s role=%d +\n", __func__, role); + ret = -EINVAL; + goto err; + } + + pr_info("%s role=%s +\n", __func__, pd_p_typec_roles[role]); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) + if (port_to_data(port, &pp_data, &ic_data)) { + ret = -ENOENT; + goto err; + } +#else + pp_data = container_of(cap, struct pdic_policy, typec_cap); + ic_data = pp_data->ic_data; + if (!ic_data) { + ret = -ENOENT; + goto err; + } +#endif + + if (!ic_data->p_ops || !ic_data->p_ops->pr_set) { + ret = -ECHILD; + goto err; + } + + if (role == TYPEC_SOURCE) { + pp_data->pp_r_s.try_power_role = PP_SOURCE; + wait = 1; + } else if (role == TYPEC_SINK) { + pp_data->pp_r_s.try_power_role = PP_SINK; + wait = 1; + } else { + wait = 0; + goto skip; + } + + reinit_completion(&pp_data->pp_r_s.swap_coml); + pp_data->pp_r_s.status = PP_GOOD; + ret = ic_data->p_ops->pr_set(ic_data->drv_data, role); + if (ret) + goto err; + + if (wait) { + time = wait_for_completion_timeout(&pp_data->pp_r_s.swap_coml, + msecs_to_jiffies(ROLE_SWAP_TIME_MS)); + if (time == 0) { + ret = -ETIMEDOUT; + goto err; + } else if (pp_data->pp_r_s.status == PP_ERROR) { + ret = -EPROTO; + goto err; + } + } +skip: +err: + pp_role_swap_init(&pp_data->pp_r_s); + + pr_info("%s ret=%d -\n", __func__, ret); + return ret; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) +int pdic_policy_vconn_set(struct typec_port *port, enum typec_role role) +#else +int pdic_policy_vconn_set(const struct typec_capability *cap, + enum typec_role role) +#endif +{ + struct pdic_policy *pp_data = NULL; + struct pp_ic_data *ic_data = NULL; + int ret = 0; + + if (role < TYPEC_SINK || role > TYPEC_SOURCE) { + pr_err("%s role=%d +\n", __func__, role); + ret = -EINVAL; + goto err; + } + + pr_info("%s role=%s +\n", __func__, pd_p_typec_roles[role]); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) + if (port_to_data(port, &pp_data, &ic_data)) { + ret = -ENOENT; + goto err; + } +#else + pp_data = container_of(cap, struct pdic_policy, typec_cap); + ic_data = pp_data->ic_data; + if (!ic_data) { + ret = -ENOENT; + goto err; + } +#endif + + if (!ic_data->p_ops || !ic_data->p_ops->vconn_set) { + ret = -ECHILD; + goto err; + } + + ret = ic_data->p_ops->vconn_set(ic_data->drv_data, role); + if (ret) + goto err; + +err: + pr_info("%s ret=%d -\n", __func__, ret); + return ret; + +} + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) +int pdic_policy_port_type_set(struct typec_port *port, + enum typec_port_type role) +#else +int pdic_policy_port_type_set(const struct typec_capability *cap, + enum typec_port_type role) +#endif +{ + struct pdic_policy *pp_data = NULL; + struct pp_ic_data *ic_data = NULL; + int ret = 0, wait = 0; + unsigned long time = 0; + + if (role < TYPEC_PORT_SRC || role > TYPEC_PORT_DRP) { + pr_err("%s role=%d +\n", __func__, role); + ret = -EINVAL; + goto err; + } + + pr_info("%s role=%s +\n", __func__, pd_p_port_types[role]); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) + if (port_to_data(port, &pp_data, &ic_data)) { + ret = -ENOENT; + goto err; + } +#else + pp_data = container_of(cap, struct pdic_policy, typec_cap); + ic_data = pp_data->ic_data; + if (!ic_data) { + ret = -ENOENT; + goto err; + } +#endif + + if (!ic_data->p_ops || !ic_data->p_ops->port_type_set) { + ret = -ECHILD; + goto err; + } + + if (role == TYPEC_PORT_SRC) { + pp_data->pp_r_s.try_port_type = PP_SOURCE; + wait = 1; + } else if (role == TYPEC_PORT_SNK) { + pp_data->pp_r_s.try_port_type = PP_SINK; + wait = 1; + } else { + wait = 0; + } + + reinit_completion(&pp_data->pp_r_s.swap_coml); + pp_data->pp_r_s.status = PP_GOOD; + ret = ic_data->p_ops->port_type_set(ic_data->drv_data, role); + if (ret) + goto err; + + if (wait) { + time = wait_for_completion_timeout(&pp_data->pp_r_s.swap_coml, + msecs_to_jiffies(ROLE_SWAP_TIME_MS)); + if (time == 0) { + ret = -ETIMEDOUT; + goto err; + } else if (pp_data->pp_r_s.status == PP_ERROR) { + ret = -EPROTO; + goto err; + } + } +err: + pp_role_swap_init(&pp_data->pp_r_s); + + pr_info("%s ret=%d -\n", __func__, ret); + return ret; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) +static const struct typec_operations pp_typec_ops = { + .dr_set = pdic_policy_dr_set, + .pr_set = pdic_policy_pr_set, + .vconn_set = pdic_policy_vconn_set, + .port_type_set = pdic_policy_port_type_set, +}; +#endif +#endif + +static void pdic_policy_usbpd_set_host_on(void *data, int mode) +{ + struct pdic_policy *pp_data = data; + + if (!pp_data) { + pr_err("%s pp_data is null\n", __func__); + goto err; + } + + pr_info("%s : current_set is %d!\n", __func__, mode); + if (mode) { + pp_data->usb_host.device_add = 0; + pp_data->usb_host.detach_done_wait = 0; + pp_data->usb_host.host_turn_on_event = 1; + wake_up_interruptible(&pp_data->usb_host.host_turn_on_wait_q); + } else { + pp_data->usb_host.device_add = 0; + pp_data->usb_host.detach_done_wait = 0; + pp_data->usb_host.host_turn_on_event = 0; + } +err: + return; +} + +static int pdic_policy_usbpd_sbu_test_read(void *data) +{ + struct pdic_policy *pp_data = data; + struct pp_ic_data *ic_data = pp_data->ic_data; + int ret = 0; + + if (!ic_data) { + ret = -ENOENT; + goto err; + } + + ret = ic_data->p_ops->usbpd_sbu_test_read(ic_data->drv_data); + +err: + return ret; +} + +static void pdic_policy_cc_control_command(void *data, int is_off) +{ + struct pdic_policy *pp_data = data; + struct pp_ic_data *ic_data = pp_data->ic_data; + + if (!ic_data) + goto err; + + ic_data->p_ops->cc_control_command(ic_data->drv_data, is_off); +err: + return; +} + +struct usbpd_ops ops_usbpd = { + .usbpd_set_host_on = pdic_policy_usbpd_set_host_on, + .usbpd_sbu_test_read = pdic_policy_usbpd_sbu_test_read, + .usbpd_cc_control_command = pdic_policy_cc_control_command, +}; + +static int pdic_policy_handle_usb_ext_noti(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct pdic_policy *pp_data = container_of(nb, + struct pdic_policy, usb_ext_noti_nb); + struct pp_ic_data *ic_data = pp_data->ic_data; + struct pdic_alt_info *alt_info = &pp_data->alt_info; + int ret = 0; + int enable = *(int *)data; + + pr_info("%s action=%lu enable=%d +\n", __func__, action, enable); + switch (action) { + case EXTERNAL_NOTIFY_HOSTBLOCK_PRE: + ret = pdic_policy_get_alt_info(pp_data); + if (ret < 0) + goto err; + if (enable) { + if (ic_data->p_ops && ic_data->p_ops->set_alt_mode) + ic_data->p_ops->set_alt_mode(ALTERNATE_MODE_STOP); + if (alt_info->dp_device) { + mutex_lock(&pp_data->p_lock); + pdic_policy_dp_detach(pp_data); + mutex_unlock(&pp_data->p_lock); + } + } else { + if (alt_info->dp_device) { + mutex_lock(&pp_data->p_lock); + pdic_policy_dp_detach(pp_data); + mutex_unlock(&pp_data->p_lock); + } + } + break; + case EXTERNAL_NOTIFY_HOSTBLOCK_POST: + if (enable) { + } else { + if (ic_data->p_ops && ic_data->p_ops->set_alt_mode) + ic_data->p_ops->set_alt_mode(ALTERNATE_MODE_START); + } + break; + case EXTERNAL_NOTIFY_DEVICEADD: + if (enable) + pp_data->usb_host.device_add = 1; + break; + case EXTERNAL_NOTIFY_MDMBLOCK_PRE: + ret = pdic_policy_get_alt_info(pp_data); + if (ret < 0) + goto err; + if (enable) { + if (alt_info->dp_device) { + pp_data->temp_alt_mode_stop = 1; + if (ic_data->p_ops && ic_data->p_ops->set_alt_mode) + ic_data->p_ops->set_alt_mode(ALTERNATE_MODE_STOP); + mutex_lock(&pp_data->p_lock); + pdic_policy_dp_detach(pp_data); + mutex_unlock(&pp_data->p_lock); + } + } else + ; + break; + case EXTERNAL_NOTIFY_MDMBLOCK_POST: + if (enable) + ; + else { + if (pp_data->temp_alt_mode_stop) { + pp_data->temp_alt_mode_stop = 0; + if (ic_data->p_ops && ic_data->p_ops->set_alt_mode) + ic_data->p_ops->set_alt_mode(ALTERNATE_MODE_START); + } + } + default: + break; + } + pr_info("%s action=%lu enable=%d -\n", __func__, action, enable); + return 0; +err: + pr_err("%s action=%lu,enable=%d error. ret=%d -\n", __func__, + action, enable, ret); + return ret; +} + +void *pdic_policy_init(struct pp_ic_data *ic_data) +{ + struct pdic_policy *pp_data; + struct otg_notify *o_notify = get_otg_notify(); + int ret = 0; + + pp_data = kzalloc(sizeof(struct pdic_policy), GFP_KERNEL); + if (!pp_data || !ic_data) { + pr_err("%s alloc fail\n", __func__); + goto err; + } + + mutex_init(&pp_data->p_lock); + init_completion(&pp_data->pp_r_s.swap_coml); + INIT_DELAYED_WORK(&pp_data->dischar_work, vbus_discharging_work); + + pp_data->pdic_wq + = create_singlethread_workqueue("pdic_policy_wq"); + if (!pp_data->pdic_wq) { + pr_err("%s failed to create work queue\n", __func__); + goto err1; + } + + pp_data->ic_data = ic_data; + ic_data->pp_data = pp_data; + pp_data->acc_type = PDIC_DOCK_DETACHED; + if (ic_data->pd_noti) { + pp_pd_noti.pd_noti = ic_data->pd_noti; + pr_info("%s ic_data pd_noti registered\n", __func__); + } else { +#if !IS_ENABLED(CONFIG_BATTERY_NOTIFIER) + pp_data->pd_noti.sink_status.fp_sec_pd_select_pdo + = pdic_policy_select_pdo; + pp_pd_noti.pd_noti = &pp_data->pd_noti; + pr_info("%s ic_data pd_noti is none.\n", __func__); +#endif + } + pp_pd_noti.pp_data = pp_data; + + pp_data->support_pd = ic_data->support_pd; + + pdic_register_switch_device(1); + +#if IS_ENABLED(CONFIG_TYPEC) + if (!pp_data->ic_data->typec_implemented) { + pp_data->typec_cap.revision = USB_TYPEC_REV_1_2; + pp_data->typec_cap.pd_revision = 0x300; + pp_data->typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) + pp_data->typec_cap.driver_data = pp_data; + pp_data->typec_cap.ops = &pp_typec_ops; +#else + pp_data->typec_cap.dr_set = pdic_policy_dr_set; + pp_data->typec_cap.pr_set = pdic_policy_pr_set; + pp_data->typec_cap.vconn_set = pdic_policy_vconn_set; + pp_data->typec_cap.port_type_set = pdic_policy_port_type_set; +#endif + + pp_data->typec_cap.type = TYPEC_PORT_DRP; + pp_data->typec_cap.data = TYPEC_PORT_DRD; + + pp_data->port = typec_register_port(ic_data->dev, + &pp_data->typec_cap); + if (IS_ERR(pp_data->port)) { + pr_err("unable to register typec_register_port\n"); + goto err2; + } + } +#endif + +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + init_waitqueue_head(&pp_data->usb_host.host_turn_on_wait_q); + pdic_policy_usbpd_set_host_on(pp_data, 0); + pp_data->usb_host.host_turn_on_wait_time = HOST_ON_WAIT_TIME_SEC; + + pp_data->usbpd_d.ops = &ops_usbpd; + pp_data->usbpd_d.data = (void *)pp_data; + pp_data->man = register_usbpd(&pp_data->usbpd_d); +#endif + + ret = usb_external_notify_register(&pp_data->usb_ext_noti_nb, + pdic_policy_handle_usb_ext_noti, EXTERNAL_NOTIFY_DEV_PDIC); + if (ret < 0) { + pr_err("unable usb_external_notify_register\n"); + goto err2; + } + + send_otg_notify(o_notify, NOTIFY_EVENT_POWER_SOURCE, 0); + + return pp_data; +err2: + flush_workqueue(pp_data->pdic_wq); + destroy_workqueue(pp_data->pdic_wq); +err1: + mutex_destroy(&pp_data->p_lock); + kfree(pp_data); +err: + return NULL; +} +EXPORT_SYMBOL_GPL(pdic_policy_init); + +void pdic_policy_deinit(struct pp_ic_data *ic_data) +{ + struct pdic_policy *pp_data; + if (ic_data && ic_data->pp_data) { + pp_data = ic_data->pp_data; + + usb_external_notify_unregister(&pp_data->usb_ext_noti_nb); +#ifdef CONFIG_TYPEC + if (!pp_data->ic_data->typec_implemented) + typec_unregister_port(pp_data->port); +#endif + flush_workqueue(pp_data->pdic_wq); + destroy_workqueue(pp_data->pdic_wq); + + mutex_destroy(&pp_data->p_lock); + kfree(pp_data); + } +} +EXPORT_SYMBOL_GPL(pdic_policy_deinit); + diff --git a/drivers/usb/typec/common/pdic_sysfs.c b/drivers/usb/typec/common/pdic_sysfs.c new file mode 100644 index 000000000000..adaab4da68fd --- /dev/null +++ b/drivers/usb/typec/common/pdic_sysfs.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * + * Copyright (C) 2017-2021 Samsung Electronics + * + * Author:Wookwang Lee. , + * Author:Guneet Singh Khurana , + * 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, see . + * + */ + +#include +#include +#include +#include +#if defined(CONFIG_SEC_KUNIT) +#include "kunit_test/pdic_sysfs_test.h" +#else +#define __visible_for_testing static +#endif + +static ssize_t pdic_sysfs_show_property(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t pdic_sysfs_store_property(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +#define PDIC_SYSFS_ATTR(_name) \ +{ \ + .attr = { .name = #_name }, \ + .show = pdic_sysfs_show_property, \ + .store = pdic_sysfs_store_property, \ +} + +static struct device_attribute pdic_attributes[] = { + PDIC_SYSFS_ATTR(chip_name), + PDIC_SYSFS_ATTR(cur_version), + PDIC_SYSFS_ATTR(src_version), + PDIC_SYSFS_ATTR(lpm_mode), + PDIC_SYSFS_ATTR(state), + PDIC_SYSFS_ATTR(rid), + PDIC_SYSFS_ATTR(ccic_control_option), + PDIC_SYSFS_ATTR(booting_dry), + PDIC_SYSFS_ATTR(fw_update), + PDIC_SYSFS_ATTR(fw_update_status), + PDIC_SYSFS_ATTR(water), + PDIC_SYSFS_ATTR(dex_fan_uvdm), + PDIC_SYSFS_ATTR(acc_device_version), + PDIC_SYSFS_ATTR(debug_opcode), + PDIC_SYSFS_ATTR(control_gpio), + PDIC_SYSFS_ATTR(usbpd_ids), + PDIC_SYSFS_ATTR(usbpd_type), + PDIC_SYSFS_ATTR(cc_pin_status), + PDIC_SYSFS_ATTR(ram_test), + PDIC_SYSFS_ATTR(sbu_adc), + PDIC_SYSFS_ATTR(cc_adc), + PDIC_SYSFS_ATTR(vsafe0v_status), + PDIC_SYSFS_ATTR(ovp_ic_shutdown), + PDIC_SYSFS_ATTR(hmd_power), + PDIC_SYSFS_ATTR(water_threshold), + PDIC_SYSFS_ATTR(water_check), + PDIC_SYSFS_ATTR(15mode_watertest_type), + PDIC_SYSFS_ATTR(vbus_adc), + PDIC_SYSFS_ATTR(usb_boot_mode), + PDIC_SYSFS_ATTR(dp_sbu_sw_sel), + PDIC_SYSFS_ATTR(novbus_rp22k), +}; + +__visible_for_testing ssize_t get_pdic_sysfs_property(ppdic_data_t ppdic_data, + const char *attr_name, ptrdiff_t off, char *buf) +{ + ssize_t ret = 0; + ppdic_sysfs_property_t ppdic_sysfs; + + if (!ppdic_data || !attr_name || off < 0 || !buf) + return -EINVAL; + + if (off == PDIC_SYSFS_PROP_CHIP_NAME) + return snprintf(buf, PAGE_SIZE, "%s\n", ppdic_data->name); + + ppdic_sysfs = (ppdic_sysfs_property_t)ppdic_data->pdic_sysfs_prop; + ret = ppdic_sysfs->get_property(ppdic_data, off, buf); + if (ret < 0) + pr_err("%s : driver failed to report `%s' property: %zd\n", + __func__, attr_name, ret); + + return ret; +} + +static ssize_t pdic_sysfs_show_property(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ppdic_data_t ppdic_data = dev_get_drvdata(dev); + const ptrdiff_t off = attr - pdic_attributes; + + return get_pdic_sysfs_property(ppdic_data, attr->attr.name, off, buf); +} + +__visible_for_testing ssize_t set_pdic_sysfs_property(ppdic_data_t ppdic_data, + const char *attr_name, ptrdiff_t off, const char *buf, size_t count) +{ + ssize_t ret = 0; + ppdic_sysfs_property_t ppdic_sysfs; + + if (!ppdic_data || !attr_name || off < 0 || !buf) + return -EINVAL; + + ppdic_sysfs = (ppdic_sysfs_property_t)ppdic_data->pdic_sysfs_prop; + ret = ppdic_sysfs->set_property(ppdic_data, off, buf, count); + if (ret < 0) + pr_err("%s : driver failed to set `%s' property: %zd\n", + __func__, attr_name, ret); + + return ret; +} + +static ssize_t pdic_sysfs_store_property(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + ppdic_data_t ppdic_data = dev_get_drvdata(dev); + const ptrdiff_t off = attr - pdic_attributes; + + return set_pdic_sysfs_property(ppdic_data, attr->attr.name, off, buf, count); +} + +__visible_for_testing umode_t get_pdic_sysfs_attr(ppdic_data_t ppdic_data, + int attrno) +{ + ppdic_sysfs_property_t ppdic_sysfs; + umode_t mode = RO_PERM; + int cnt, num_properties, property; + + if (!ppdic_data || attrno < 0) + return 0; + + ppdic_sysfs = (ppdic_sysfs_property_t)ppdic_data->pdic_sysfs_prop; + num_properties = ppdic_sysfs->num_properties; + + for (cnt = 0; cnt < num_properties; cnt++) { + property = ppdic_sysfs->properties[cnt]; + if (property != attrno) + continue; + if (ppdic_sysfs->property_is_writeable && + ppdic_sysfs->property_is_writeable(ppdic_data, property)) + mode |= WO_PERM; + if (ppdic_sysfs->property_is_writeonly && + ppdic_sysfs->property_is_writeonly(ppdic_data, property)) + mode = WO_PERM; + return mode; + } + return 0; +} + +static umode_t pdic_sysfs_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int attrno) +{ + struct device *dev = container_of(kobj, struct device, kobj); + ppdic_data_t ppdic_data = dev_get_drvdata(dev); + + return get_pdic_sysfs_attr(ppdic_data, attrno); +} + +static struct attribute *__pdic_sysfs_attrs[ARRAY_SIZE(pdic_attributes) + 1]; + +const struct attribute_group pdic_sysfs_group = { + .attrs = __pdic_sysfs_attrs, + .is_visible = pdic_sysfs_attr_is_visible, +}; + +void pdic_sysfs_init_attrs(void) +{ + int i; + + for (i = 0; i < (int)ARRAY_SIZE(pdic_attributes); i++) + __pdic_sysfs_attrs[i] = &pdic_attributes[i].attr; +} diff --git a/drivers/usb/typec/manager/Kconfig b/drivers/usb/typec/manager/Kconfig new file mode 100644 index 000000000000..bc33adefc497 --- /dev/null +++ b/drivers/usb/typec/manager/Kconfig @@ -0,0 +1,37 @@ +# +# USB TypeC Manager driver +# + +comment "USB TypeC Manager configs" + +config USB_TYPEC_MANAGER_NOTIFIER + tristate "USB TypeC Manager driver" + help + If you say yes here you will get support for + USB TypeC Manager function + +config IF_CB_MANAGER + tristate "IF CallBack function Manager" + default n + help + If you say yes here you will use if_cb_manager structure + that makes functions to callback functions + +config USB_TYPEC_MANAGER_NOTIFIER_TEST + tristate "KUnit test for usb_typec_manager_notifier_test" + depends on SEC_KUNIT + depends on USB_TYPEC_MANAGER_NOTIFIER + default y + + help + TODO: Describe config fully. + +config SBU_SWITCH_CONTROL + bool "Support controlling SBU switch open/close" + depends on IF_CB_MANAGER + default n + help + If you say yes here you will use if_cb_manager function + to control SBU switch (controlled by displayport TG) + This config indicates that switch will be controlled + manually by drivers, not automatically by FW or HW. diff --git a/drivers/usb/typec/manager/Makefile b/drivers/usb/typec/manager/Makefile new file mode 100644 index 000000000000..e56341fbf020 --- /dev/null +++ b/drivers/usb/typec/manager/Makefile @@ -0,0 +1,17 @@ +# +# Makefile for usb typec manager drivers +# +subdir-ccflags-y := -Wformat +obj-$(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) += usb_typec_manager.o +usb_typec_manager-y := usb_typec_manager_notifier.o +usb_typec_manager-$(CONFIG_USB_HW_PARAM) += usb_typec_manager_hwparam.o + +ifeq ($(CONFIG_SEC_KUNIT), y) +ifeq ($(CONFIG_UML), y) +else +endif +GCOV_PROFILE_usb_typec_manager_notifier.o := $(CONFIG_SEC_KUNIT) +endif + +obj-$(CONFIG_IF_CB_MANAGER) += if_cb_manager.o + diff --git a/drivers/usb/typec/manager/if_cb_manager.c b/drivers/usb/typec/manager/if_cb_manager.c new file mode 100644 index 000000000000..ddb0fae49fcf --- /dev/null +++ b/drivers/usb/typec/manager/if_cb_manager.c @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2018-2019 Samsung Electronics + * + * 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, see . + * + */ + +#include +#include +#include + +static struct if_cb_manager *man_core; + +struct if_cb_manager *create_alloc_if_cb_manager(void) +{ + man_core = kzalloc(sizeof(struct if_cb_manager), GFP_KERNEL); + + return man_core; +} + +struct if_cb_manager *get_if_cb_manager(void) +{ + if (!man_core) + create_alloc_if_cb_manager(); + + return man_core; +} + +struct if_cb_manager *register_usb(struct usb_dev *usb) +{ + struct if_cb_manager *man_core; + + man_core = get_if_cb_manager(); + man_core->usb_d = usb; + + return man_core; +} +EXPORT_SYMBOL(register_usb); + +struct if_cb_manager *register_muic(struct muic_dev *muic) +{ + struct if_cb_manager *man_core; + + man_core = get_if_cb_manager(); + man_core->muic_d = muic; + + return man_core; +} +EXPORT_SYMBOL(register_muic); + +struct if_cb_manager *register_usbpd(struct usbpd_dev *usbpd) +{ + struct if_cb_manager *man_core; + + man_core = get_if_cb_manager(); + man_core->usbpd_d = usbpd; + + return man_core; +} +EXPORT_SYMBOL(register_usbpd); + +struct if_cb_manager *register_lvs(struct lvs_dev *lvs) +{ + struct if_cb_manager *man_core; + + man_core = get_if_cb_manager(); + man_core->lvs_d = lvs; + + return man_core; +} +EXPORT_SYMBOL(register_lvs); + +void usb_set_vbus_current(struct if_cb_manager *man_core, int state) +{ + if (man_core == NULL || man_core->usb_d == NULL || + man_core->usb_d->ops == NULL || + man_core->usb_d->ops->usb_set_vbus_current == NULL) { + pr_err("%s : Member of if_cb_manager is NULL\n", __func__); + return; + } + + man_core->usb_d->ops->usb_set_vbus_current( + man_core->usb_d->data, state); +} +EXPORT_SYMBOL(usb_set_vbus_current); + +int usb_restart_host_mode(struct if_cb_manager *man_core, int lanes) +{ + if (man_core == NULL || man_core->usb_d == NULL || + man_core->usb_d->ops == NULL || + man_core->usb_d->ops->usb_restart_host_mode == NULL) { + pr_err("%s : Member of if_cb_manager is NULL\n", __func__); + return -ENXIO; + } + + return man_core->usb_d->ops->usb_restart_host_mode( + man_core->usb_d->data, lanes); +} +EXPORT_SYMBOL(usb_restart_host_mode); + +int muic_check_usb_killer(struct if_cb_manager *man_core) +{ + if (man_core == NULL || man_core->muic_d == NULL || + man_core->muic_d->ops == NULL || + man_core->muic_d->ops->muic_check_usb_killer == NULL) { + pr_err("%s : Member of if_cb_manager is NULL\n", __func__); + return 0; + } + + return man_core->muic_d->ops->muic_check_usb_killer( + man_core->muic_d->data); +} +EXPORT_SYMBOL(muic_check_usb_killer); + +void muic_set_bc12(struct if_cb_manager *man_core, int enable) +{ + if (man_core == NULL || man_core->muic_d == NULL || + man_core->muic_d->ops == NULL || + man_core->muic_d->ops->muic_set_bc12 == NULL) { + pr_err("%s : Member of if_cb_manager is NULL\n", __func__); + return; + } + + man_core->muic_d->ops->muic_set_bc12( + man_core->muic_d->data, enable); +} +EXPORT_SYMBOL(muic_set_bc12); + +int usbpd_sbu_test_read(struct if_cb_manager *man_core) +{ + if (man_core == NULL || man_core->usbpd_d == NULL || + man_core->usbpd_d->ops == NULL || + man_core->usbpd_d->ops->usbpd_sbu_test_read == NULL) { + pr_err("%s : Member of if_cb_manager is NULL\n", __func__); + return -ENXIO; + } + + return man_core->usbpd_d->ops->usbpd_sbu_test_read( + man_core->usbpd_d->data); +} +EXPORT_SYMBOL(usbpd_sbu_test_read); + +void usbpd_set_host_on(struct if_cb_manager *man_core, int mode) +{ + if (man_core == NULL || man_core->usbpd_d == NULL || + man_core->usbpd_d->ops == NULL || + man_core->usbpd_d->ops->usbpd_set_host_on == NULL) { + pr_err("%s : Member of if_cb_manager is NULL\n", __func__); + return; + } + + man_core->usbpd_d->ops->usbpd_set_host_on( + man_core->usbpd_d->data, mode); +} +EXPORT_SYMBOL(usbpd_set_host_on); + +void usbpd_cc_control_command(struct if_cb_manager *man_core, int is_off) +{ + if (man_core == NULL || man_core->usbpd_d == NULL || + man_core->usbpd_d->ops == NULL || + man_core->usbpd_d->ops->usbpd_cc_control_command == NULL) { + pr_err("%s : Member of if_cb_manager is NULL\n", __func__); + return; + } + + man_core->usbpd_d->ops->usbpd_cc_control_command( + man_core->usbpd_d->data, is_off); +} +EXPORT_SYMBOL(usbpd_cc_control_command); + +void usbpd_wait_entermode(struct if_cb_manager *man_core, int on) +{ + if (man_core == NULL || man_core->usbpd_d == NULL || + man_core->usbpd_d->ops == NULL || + man_core->usbpd_d->ops->usbpd_wait_entermode == NULL) { + pr_err("%s : Member of if_cb_manager is NULL\n", __func__); + return; + } + + man_core->usbpd_d->ops->usbpd_wait_entermode(man_core->usbpd_d->data, on); +} +EXPORT_SYMBOL(usbpd_wait_entermode); + +void usbpd_sbu_switch_control(int on) +{ + struct if_cb_manager *man_core = get_if_cb_manager(); + + if (!IS_ENABLED(CONFIG_SBU_SWITCH_CONTROL) || man_core == NULL || + man_core->usbpd_d == NULL || man_core->usbpd_d->ops == NULL || + man_core->usbpd_d->ops->usbpd_sbu_switch_control == NULL) { + pr_err("%s : This function isn't supported\n", __func__); + return; + } + + man_core->usbpd_d->ops->usbpd_sbu_switch_control(man_core->usbpd_d->data, on); +} +EXPORT_SYMBOL(usbpd_sbu_switch_control); + +static int __init if_cb_manager_init(void) +{ + if (!man_core) + create_alloc_if_cb_manager(); + return 0; +} + +static void __exit if_cb_manager_exit(void) +{ + kfree(man_core); + man_core = NULL; +} + +module_init(if_cb_manager_init); +module_exit(if_cb_manager_exit); + +MODULE_AUTHOR("Samsung USB Team"); +MODULE_DESCRIPTION("Interface Callback Manager"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/manager/usb_typec_manager_hwparam.c b/drivers/usb/typec/manager/usb_typec_manager_hwparam.c new file mode 100644 index 000000000000..f68e33d95f52 --- /dev/null +++ b/drivers/usb/typec/manager/usb_typec_manager_hwparam.c @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2018-2020 Samsung Electronics Co. Ltd. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include + +struct typec_manager_hwparam { + struct delayed_work rtctime_update_work; + int water_detection; + int water_det_count; + int water_dry_count; + int water_Chg_count; + unsigned long water_det_duration; + unsigned long water_detected_time; + unsigned long wVbus_detected; + unsigned long wVbus_duration; + unsigned long wVbusHigh_time; + + int usb_highspeed_count; + int usb_superspeed_count; +}; +static struct typec_manager_hwparam manager_hwparam; + +static void calc_duration_time(unsigned long sTime, unsigned long eTime, unsigned long *dTime) +{ + unsigned long calcDtime; + + calcDtime = eTime - sTime; + + /* check for exception case. */ + if (calcDtime > 86400) + calcDtime = 0; + + *dTime += calcDtime; +} + +void water_dry_time_update(int mode) +{ + struct timespec64 time; + static int time_update_check = 1; + + manager_hwparam.water_detection = mode; + if (mode) + manager_hwparam.water_det_count++; + else + manager_hwparam.water_dry_count++; + + ktime_get_real_ts64(&time); + + if (time_update_check) { + time_update_check = 0; + if (time.tv_sec < 31536000) { // 31536000s = 1 year + schedule_delayed_work(&manager_hwparam.rtctime_update_work, msecs_to_jiffies(5000)); + } + } + + if (mode) { + /* WATER */ + manager_hwparam.water_detected_time = time.tv_sec; + } else { + /* DRY */ + calc_duration_time(manager_hwparam.water_detected_time, + time.tv_sec, &manager_hwparam.water_det_duration); + } +} + +static void water_det_rtc_time_update(struct work_struct *work) +{ + struct timespec64 time; + static int max_retry = 1; + + ktime_get_real_ts64(&time); + if ((time.tv_sec < 31536000) && (max_retry < 5)) { + if (manager_hwparam.wVbus_detected) { + calc_duration_time(manager_hwparam.wVbusHigh_time, + time.tv_sec, &manager_hwparam.wVbus_duration); + manager_hwparam.wVbusHigh_time = time.tv_sec; + } + max_retry++; + schedule_delayed_work(&manager_hwparam.rtctime_update_work, msecs_to_jiffies(5000)); + } else { + if (manager_hwparam.water_detection) { + manager_hwparam.water_detected_time = time.tv_sec; + manager_hwparam.water_det_duration += max_retry*5; + if (manager_hwparam.wVbus_detected) { + manager_hwparam.wVbusHigh_time = time.tv_sec; + manager_hwparam.wVbus_duration += 5; + } + } + } +} + +void wVbus_time_update(int status) +{ + struct timespec64 time; + + manager_hwparam.wVbus_detected = status; + ktime_get_real_ts64(&time); + + if (status) { + /* WVBUS HIGH */ + manager_hwparam.water_Chg_count++; + manager_hwparam.wVbusHigh_time = time.tv_sec; + } else { + /* WVBUS LOW */ + calc_duration_time(manager_hwparam.wVbusHigh_time, + time.tv_sec, &manager_hwparam.wVbus_duration); + } +} + +static unsigned long get_waterdet_duration(void) +{ + unsigned long ret = 0; + struct timespec64 time; + + if (manager_hwparam.water_detection) { + ktime_get_real_ts64(&time); + calc_duration_time(manager_hwparam.water_detected_time, + time.tv_sec, &manager_hwparam.water_det_duration); + manager_hwparam.water_detected_time = time.tv_sec; + } + + ret = manager_hwparam.water_det_duration/60; /* min */ + manager_hwparam.water_det_duration -= ret*60; + + return ret; +} + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) +static unsigned long get_wvbus_duration(void) +{ + unsigned long ret = 0; + struct timespec64 time; + + if (manager_hwparam.wVbus_detected) { + ktime_get_real_ts64(&time); /* time.tv_sec */ + calc_duration_time(manager_hwparam.wVbusHigh_time, + time.tv_sec, &manager_hwparam.wVbus_duration); + manager_hwparam.wVbusHigh_time = time.tv_sec; + } + + ret = manager_hwparam.wVbus_duration; /* sec */ + manager_hwparam.wVbus_duration = 0; + + return ret; +} +#endif + +void usb_enum_hw_param_data_update(int speed) +{ + if (speed >= USB_SPEED_SUPER) + manager_hwparam.usb_superspeed_count++; + else if (speed >= USB_SPEED_HIGH) + manager_hwparam.usb_highspeed_count++; +} + +unsigned long manager_hw_param_update(int param) +{ + unsigned long ret = 0; + + switch (param) { + case USB_CCIC_WATER_INT_COUNT: + ret = manager_hwparam.water_det_count; + manager_hwparam.water_det_count = 0; + break; + case USB_CCIC_DRY_INT_COUNT: + ret = manager_hwparam.water_dry_count; + manager_hwparam.water_dry_count = 0; + break; + case USB_CLIENT_SUPER_SPEED_COUNT: + ret = manager_hwparam.usb_superspeed_count; + manager_hwparam.usb_superspeed_count = 0; + break; + case USB_CLIENT_HIGH_SPEED_COUNT: + ret = manager_hwparam.usb_highspeed_count; + manager_hwparam.usb_highspeed_count = 0; + break; + case USB_CCIC_WATER_TIME_DURATION: + ret = get_waterdet_duration(); + break; + case USB_CCIC_WATER_VBUS_COUNT: + if (!is_lpcharge_pdic_param()) { + ret = manager_hwparam.water_Chg_count; + manager_hwparam.water_Chg_count = 0; + } + break; + case USB_CCIC_WATER_LPM_VBUS_COUNT: + if (is_lpcharge_pdic_param()) { + ret = manager_hwparam.water_Chg_count; + manager_hwparam.water_Chg_count = 0; + } + break; + case USB_CCIC_WATER_VBUS_TIME_DURATION: +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + if (!is_lpcharge_pdic_param()) + ret = get_wvbus_duration(); +#endif + break; + case USB_CCIC_WATER_LPM_VBUS_TIME_DURATION: +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + if (is_lpcharge_pdic_param()) + ret = get_wvbus_duration(); +#endif + break; + default: + break; + } + + return ret; +} + +void manager_hw_param_init(void) +{ + manager_hwparam.water_det_count = 0; + manager_hwparam.water_dry_count = 0; + manager_hwparam.water_det_duration = 0; + manager_hwparam.water_Chg_count = 0; + manager_hwparam.wVbus_duration = 0; + manager_hwparam.usb_highspeed_count = 0; + manager_hwparam.usb_superspeed_count = 0; + + INIT_DELAYED_WORK(&manager_hwparam.rtctime_update_work, + water_det_rtc_time_update); +} + +MODULE_AUTHOR("Samsung USB Team"); +MODULE_DESCRIPTION("USB Typec Manager HWPARAM"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/manager/usb_typec_manager_notifier.c b/drivers/usb/typec/manager/usb_typec_manager_notifier.c new file mode 100644 index 000000000000..98d7f5baa5f3 --- /dev/null +++ b/drivers/usb/typec/manager/usb_typec_manager_notifier.c @@ -0,0 +1,1779 @@ +/* + * Copyright (C) 2018-2020 Samsung Electronics Co. Ltd. + * + * 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. + * + */ + +#define pr_fmt(fmt) "TCM: " fmt + +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) +#include +#endif +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) +#include +#endif +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) +#include +#endif +#include +#include +#include +#include +#include +#if IS_MODULE(CONFIG_BATTERY_SAMSUNG) || IS_MODULE(CONFIG_BATTERY_SAMSUNG_MODULE) +#include +#elif defined(CONFIG_BATTERY_SAMSUNG) +#include "../../../battery/common/sec_charging_common.h" +#endif +#if IS_ENABLED(CONFIG_CABLE_TYPE_NOTIFIER) +#include +#endif +#if IS_ENABLED(CONFIG_SEC_PD) +#include +#elif defined(CONFIG_BATTERY_NOTIFIER) +#include +#endif +#include +#if defined(CONFIG_USB_HW_PARAM) +#include +#endif + +#if IS_ENABLED(CONFIG_SEC_ABC) +#include +#endif +#if defined(CONFIG_SEC_FACTORY) +#include +#endif + +#if defined(CONFIG_SEC_KUNIT) +#include +#include + +int event_index; +EXPORT_SYMBOL_KUNIT(event_index); +MANAGER_NOTI_TYPEDEF_REF verify_event[5]; +EXPORT_SYMBOL_KUNIT(verify_event); +int flag_kunit_test; +EXPORT_SYMBOL_KUNIT(flag_kunit_test); + +#else +#define __visible_for_testing static +#endif + +static int manager_notifier_init_done; +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) && defined(CONFIG_SEC_FACTORY) +static struct device *manager_device; +#endif +__visible_for_testing manager_data_t typec_manager; +#if defined(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_KUNIT(typec_manager); +#endif +static bool is_hiccup_event_saved; +static bool manager_notify_pdic_battery_init; + +static int manager_notifier_init(void); +__visible_for_testing void manager_usb_enum_state_check(uint time_ms); +static void manager_event_processing_by_vbus(bool run); + +#if defined(CONFIG_PDIC_SWITCH) +static bool is_factory_jig; +#endif + +static const char *manager_notify_string(int mns) +{ + switch (mns) { + case MANAGER_NOTIFY_MUIC_NONE: return "muic_none"; + case MANAGER_NOTIFY_MUIC_USB: return "muic_usb"; + case MANAGER_NOTIFY_MUIC_OTG: return "muic_otg"; + case MANAGER_NOTIFY_MUIC_CHARGER: return "muic_charger"; + case MANAGER_NOTIFY_MUIC_TIMEOUT_OPEN_DEVICE: + return "muic_timeout_open_device"; + case MANAGER_NOTIFY_MUIC_UART: return "muic_uart"; + + case MANAGER_NOTIFY_PDIC_INITIAL: return "pdic_initial"; + case MANAGER_NOTIFY_PDIC_WACOM: return "pdic_wacom"; + case MANAGER_NOTIFY_PDIC_SENSORHUB: return "pdic_sensorhub"; + case MANAGER_NOTIFY_PDIC_USBDP: return "pdic_usbdp"; + case MANAGER_NOTIFY_PDIC_DP: return "pdic_dp"; + case MANAGER_NOTIFY_PDIC_SUB_BATTERY: return "pdic_sub_battery"; + case MANAGER_NOTIFY_PDIC_BATTERY: return "pdic_battery"; + case MANAGER_NOTIFY_PDIC_USB: return "pdic_usb"; + case MANAGER_NOTIFY_PDIC_MUIC: return "pdic_muic"; + case MANAGER_NOTIFY_PDIC_DELAY_DONE: return "pdic_delay_done"; + default: + return "undefined"; + } +} + +void manager_dp_state_update(MANAGER_NOTI_TYPEDEF event) +{ + switch (event.dest) { + case PDIC_NOTIFY_DEV_DP: + switch (event.id) { + case PDIC_NOTIFY_ID_DP_CONNECT: + typec_manager.dp.attach_state = event.sub1; + typec_manager.dp.is_connect = 0; + typec_manager.dp.hs_connect = 0; + break; + case PDIC_NOTIFY_ID_DP_HPD: + typec_manager.dp.hpd_state = event.sub1; + break; + case PDIC_NOTIFY_ID_DP_LINK_CONF: + typec_manager.dp.cable_type = event.sub1; + break; + } + break; + case PDIC_NOTIFY_DEV_USB_DP: + if (event.id == PDIC_NOTIFY_ID_USB_DP) { + typec_manager.dp.is_connect = event.sub1; + typec_manager.dp.hs_connect = event.sub2; + } + break; + default: + break; + } +} + +#if defined(CONFIG_SEC_KUNIT) +__visible_for_testing void manager_event_save(struct typec_manager_event_work *event_work) +{ + verify_event[event_index].src = event_work->event.src; + verify_event[event_index].dest = event_work->event.dest; + verify_event[event_index].id = event_work->event.id; + verify_event[event_index].sub1 = event_work->event.sub1; + verify_event[event_index].sub2 = event_work->event.sub2; + verify_event[event_index].sub3 = event_work->event.sub3; + event_index++; +} +EXPORT_SYMBOL_KUNIT(manager_event_save); +#endif + +static void manager_event_notify(struct work_struct *data) +{ + struct typec_manager_event_work *event_work = + container_of(data, struct typec_manager_event_work, typec_manager_work); + int ret = 0; + + utmanager_info("%s: src:%s dest:%s id:%s sub1:%d sub2:%d sub3:%d\n", __func__, + pdic_event_src_string(event_work->event.src), + pdic_event_dest_string(event_work->event.dest), + pdic_event_id_string(event_work->event.id), + event_work->event.sub1, event_work->event.sub2, event_work->event.sub3); + + switch (event_work->event.dest) { + case PDIC_NOTIFY_DEV_BATT: + if (event_work->event.sub3 == typec_manager.water.report_type) { + if (typec_manager.water.wVbus_det != event_work->event.sub1) { + typec_manager.water.wVbus_det = event_work->event.sub1; +#if defined(CONFIG_USB_HW_PARAM) + wVbus_time_update(typec_manager.water.wVbus_det); +#endif + } else if (!is_hiccup_event_saved) + return; + } + break; + case PDIC_NOTIFY_DEV_USB: + if (event_work->event.sub2 == typec_manager.usb.notified_dr) { + utmanager_info("%s: Duplicate event(%s)\n", __func__, pdic_usbstatus_string(event_work->event.sub2)); + return; + } + typec_manager.usb.notified_dr = event_work->event.sub2; + break; + case PDIC_NOTIFY_DEV_DP: + case PDIC_NOTIFY_DEV_USB_DP: + manager_dp_state_update(event_work->event); + break; + default: + break; + } + +#if defined(CONFIG_SEC_KUNIT) + if (flag_kunit_test) { + manager_event_save(event_work); + return; + } +#endif + +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + if (event_work->event.id != PDIC_NOTIFY_ID_POWER_STATUS) + store_usblog_notify(NOTIFY_MANAGER, (void *)&(event_work->event), NULL); +#endif + + if(!manager_notify_pdic_battery_init) { + utmanager_info("%s: pdic battery notifier was not init\n", __func__); + is_hiccup_event_saved = true; + } else + is_hiccup_event_saved = false; + + ret = blocking_notifier_call_chain(&(typec_manager.manager_notifier), + event_work->event.id, &(event_work->event)); + + switch (ret) { + case NOTIFY_DONE: + case NOTIFY_OK: + if (event_work->event.dest == PDIC_NOTIFY_DEV_USB + && event_work->event.sub2 == USB_STATUS_NOTIFY_ATTACH_UFP) { + if (typec_manager.classified_cable_type == MANAGER_NOTIFY_MUIC_TIMEOUT_OPEN_DEVICE) + manager_usb_enum_state_check(MIN_USB_DWORK_TIME); + else + manager_usb_enum_state_check(MAX_USB_DWORK_TIME); + } + utmanager_info("%s: notify done(0x%x)\n", __func__, ret); + break; + case NOTIFY_STOP_MASK: + case NOTIFY_BAD: + default: + if (event_work->event.dest == PDIC_NOTIFY_DEV_USB) + utmanager_info("%s: UPSM case (0x%x)\n", __func__, ret); + else + utmanager_info("%s: notify error occur(0x%x)\n", __func__, ret); + break; + } + + kfree(event_work); +} + +static void manager_muic_event_notify(struct work_struct *data) +{ + struct typec_manager_event_work *event_work = + container_of(data, struct typec_manager_event_work, typec_manager_work); + int ret = 0; + + utmanager_info("%s: id:%s sub1:%d sub2:%d sub3:%d\n", __func__, + pdic_event_id_string(event_work->event.id), + event_work->event.sub1, event_work->event.sub2, event_work->event.sub3); +#if defined(CONFIG_SEC_KUNIT) + if (flag_kunit_test) { + manager_event_save(event_work); + return; + } +#endif + +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + store_usblog_notify(NOTIFY_MANAGER, (void *)&(event_work->event), NULL); +#endif + ret = blocking_notifier_call_chain(&(typec_manager.manager_muic_notifier), + event_work->event.id, &(event_work->event)); + + utmanager_info("%s: done(0x%x)\n", __func__, ret); + kfree(event_work); +} + +static void manager_event_work(int src, int dest, int id, int sub1, int sub2, int sub3) +{ + struct typec_manager_event_work *event_work; + + utmanager_info("%s src:%s dest:%s\n", __func__, + pdic_event_src_string(src), pdic_event_dest_string(dest)); + + if (!typec_manager.manager_noti_wq) { + utmanager_err("%s: manager_noti_wq is null\n", __func__); + return; + } + + if (!typec_manager.manager_muic_noti_wq) { + utmanager_err("%s: manager_muic_noti_wq is null\n", __func__); + return; + } + + event_work = kmalloc(sizeof(struct typec_manager_event_work), GFP_ATOMIC); + if (!event_work) { + utmanager_err("%s: failed to alloc for event_work\n", __func__); + return; + } + + event_work->event.src = src; + event_work->event.dest = dest; + event_work->event.id = id; + event_work->event.sub1 = sub1; + event_work->event.sub2 = sub2; + event_work->event.sub3 = sub3; + event_work->event.pd = typec_manager.pd; + + if (event_work->event.dest == PDIC_NOTIFY_DEV_MUIC) { + INIT_WORK(&event_work->typec_manager_work, manager_muic_event_notify); + queue_work(typec_manager.manager_muic_noti_wq, &event_work->typec_manager_work); + } else { + INIT_WORK(&event_work->typec_manager_work, manager_event_notify); + queue_work(typec_manager.manager_noti_wq, &event_work->typec_manager_work); + } +} + +void probe_typec_manager_gadget_ops (struct typec_manager_gadget_ops *ops) +{ + typec_manager.gadget_ops = ops; +} +EXPORT_SYMBOL(probe_typec_manager_gadget_ops); + +static void manager_delayed_usb_event_work(struct work_struct *work) +{ + int state = typec_manager.manager_usb_event_delayed_work.data; + + typec_manager.manager_usb_event_delayed_work.pending = false; + manager_event_work(PDIC_NOTIFY_DEV_MANAGER, PDIC_NOTIFY_DEV_USB, + PDIC_NOTIFY_ID_USB, !!state, state, 0); +} + +static void manager_handle_usb_event(int state) +{ + int event_delay = 0; + u64 cur_time; + unsigned int event_interval; + + switch (state) { + case USB_STATUS_NOTIFY_ATTACH_UFP: + case USB_STATUS_NOTIFY_ATTACH_DFP: + cur_time = get_jiffies_64(); + if (time_before64(cur_time, typec_manager.usb.event_time_stamp)) { + event_interval = jiffies_to_msecs((unsigned long)(typec_manager.usb.event_time_stamp - cur_time)); + utmanager_info("%s: event interval=%u(ms)\n", __func__, event_interval); + event_delay = USB_EVENT_INTERVAL_CHECK_TIME - event_interval; + } + typec_manager.usb.event_time_stamp = cur_time + msecs_to_jiffies(USB_EVENT_INTERVAL_CHECK_TIME); + break; + case USB_STATUS_NOTIFY_DETACH: + default: + break; + } + + if (typec_manager.manager_usb_event_delayed_work.pending) { + cancel_delayed_work(&typec_manager.manager_usb_event_delayed_work.dwork); + event_delay = USB_EVENT_INTERVAL_CHECK_TIME; + } + + if (event_delay) { + typec_manager.manager_usb_event_delayed_work.pending = true; + typec_manager.manager_usb_event_delayed_work.data = state; + schedule_delayed_work(&typec_manager.manager_usb_event_delayed_work.dwork, + msecs_to_jiffies(event_delay)); + } else + manager_event_work(PDIC_NOTIFY_DEV_MANAGER, PDIC_NOTIFY_DEV_USB, + PDIC_NOTIFY_ID_USB, !!state, state, 0); +} + +static void manager_usb_event_send(int state) +{ + if (typec_manager.usb.dr == state) { + utmanager_info("%s(%s): Duplicate event\n", __func__, pdic_usbstatus_string(state)); + return; + } + + switch (state) { + case USB_STATUS_NOTIFY_ATTACH_UFP: + if (typec_manager.usb_factory) { + utmanager_info("%s. usb_factory mode. we don't care cable type\n", + __func__); + break; + } +#if !IS_ENABLED(CONFIG_MTK_CHARGER) + if (typec_manager.classified_cable_type != MANAGER_NOTIFY_MUIC_USB + && typec_manager.classified_cable_type != MANAGER_NOTIFY_MUIC_TIMEOUT_OPEN_DEVICE + && typec_manager.classified_cable_type != MANAGER_NOTIFY_MUIC_OTG) { + utmanager_info("%s(%s): Skip event (%s)\n", __func__, pdic_usbstatus_string(state), + manager_notify_string(typec_manager.classified_cable_type)); + return; + } +#else + if (typec_manager.classified_cable_type == MANAGER_NOTIFY_MUIC_NONE) + typec_manager.classified_cable_type = MANAGER_NOTIFY_MUIC_USB; +#endif + break; + case USB_STATUS_NOTIFY_ATTACH_DFP: + if (typec_manager.classified_cable_type == MANAGER_NOTIFY_MUIC_NONE) + typec_manager.classified_cable_type = MANAGER_NOTIFY_MUIC_OTG; + break; + case USB_STATUS_NOTIFY_DETACH: + manager_usb_enum_state_check(CANCEL_USB_DWORK); + manager_event_processing_by_vbus(false); + set_usb_enumeration_state(0); + manager_event_work(PDIC_NOTIFY_DEV_MANAGER, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_USB, 0, 0, PD_NONE_TYPE); +#if IS_ENABLED(CONFIG_MTK_CHARGER) + if (!typec_manager.muic.attach_state) + typec_manager.classified_cable_type = MANAGER_NOTIFY_MUIC_NONE; +#endif + break; + default: + utmanager_info("%s(%s): Invalid event\n", __func__, pdic_usbstatus_string(state)); + return; + } + + utmanager_info("%s(%s)\n", __func__, pdic_usbstatus_string(state)); + typec_manager.usb.dr = state; + +#if IS_ENABLED(CONFIG_MUIC_POGO) + cancel_delayed_work(&typec_manager.usb_event_by_pogo.dwork); + if (typec_manager.is_muic_pogo) { + if (state == USB_STATUS_NOTIFY_ATTACH_DFP) { + utmanager_info("%s: send DETCH to restart DFP. (muic pogo)\n", __func__); + manager_event_work(PDIC_NOTIFY_DEV_MANAGER, PDIC_NOTIFY_DEV_USB, + PDIC_NOTIFY_ID_USB, PDIC_NOTIFY_DETACH, USB_STATUS_NOTIFY_DETACH, 0); + schedule_delayed_work(&typec_manager.usb_event_by_pogo.dwork, msecs_to_jiffies(600)); + } else + utmanager_info("%s(%s) skip. (muic pogo)\n", __func__, pdic_usbstatus_string(state)); + return; + } +#endif /* CONFIG_MUIC_POGO */ + + manager_handle_usb_event(state); +} + +int __weak dwc3_gadget_get_cmply_link_state_wrapper(void) +{ + if (typec_manager.gadget_ops && typec_manager.gadget_ops->get_cmply_link_state) { + void *dev = typec_manager.gadget_ops->driver_data; + + return typec_manager.gadget_ops->get_cmply_link_state(dev); + } + return -ENODEV; +} + +static void manager_usb_enum_state_check_work(struct work_struct *work) +{ + int dwc3_link_check = 0; + + typec_manager.usb_enum_check.pending = false; + dwc3_link_check= dwc3_gadget_get_cmply_link_state_wrapper(); + +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) && !IS_ENABLED(CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION) + if (is_blocked(get_otg_notify(), NOTIFY_BLOCK_TYPE_CLIENT)) { + utmanager_info("%s usb device is blocked. skip.\n", __func__); + return; + } +#endif + + if ((typec_manager.usb.dr != USB_STATUS_NOTIFY_ATTACH_UFP) + || (dwc3_link_check == 1)) { + utmanager_info("%s: skip case : dwc3_link = %d\n", __func__, dwc3_link_check); + return; + } + utmanager_info("%s: usb=0x%X, pd=%d dwc3_link=%d\n", __func__, + typec_manager.usb.enum_state, typec_manager.pd_con_state, dwc3_link_check); + + if (!typec_manager.usb.enum_state) { +#ifdef CONFIG_USB_CONFIGFS_F_MBIM + /* Make usb soft reconnection */ + utmanager_info("%s: For 5G module : Try usb reconnect here\n", __func__); + manager_usb_event_send(USB_STATUS_NOTIFY_DETACH); + msleep(600); + manager_usb_event_send(USB_STATUS_NOTIFY_ATTACH_UFP); +#else + manager_usb_event_send(USB_STATUS_NOTIFY_DETACH); + manager_event_work(PDIC_NOTIFY_DEV_MANAGER, PDIC_NOTIFY_DEV_MUIC, + PDIC_NOTIFY_ID_USB, PDIC_NOTIFY_DETACH, USB_STATUS_NOTIFY_DETACH, 0); +#endif + } else { +#ifndef CONFIG_USB_CONFIGFS_F_MBIM + /* PD-USB cable Type */ + if (typec_manager.pd_con_state) + manager_event_work(PDIC_NOTIFY_DEV_MANAGER, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_USB, 0, 0, PD_USB_TYPE); +#endif + } +} + +__visible_for_testing void manager_usb_enum_state_check(uint time_ms) +{ +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) && !IS_ENABLED(CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION) + struct otg_notify *o_notify = get_otg_notify(); + int enum_check_skip = 0; + + if ((o_notify && o_notify->booting_delay_sec) || is_blocked(o_notify, NOTIFY_BLOCK_TYPE_CLIENT)) + enum_check_skip = 1; +#endif + + if (typec_manager.usb_factory) { + utmanager_info("%s skip. usb_factory mode.\n", __func__); + return; + } + + if (typec_manager.usb.enable_state) { + if (typec_manager.usb_enum_check.pending) + cancel_delayed_work(&typec_manager.usb_enum_check.dwork); + + if (time_ms && typec_manager.usb.dr == USB_STATUS_NOTIFY_ATTACH_UFP) { +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) && !IS_ENABLED(CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION) + if (enum_check_skip) { + utmanager_info("%s skip. booting_delay(%d)\n", __func__, o_notify->booting_delay_sec); + return; + } +#endif + typec_manager.usb_enum_check.pending = true; + schedule_delayed_work(&typec_manager.usb_enum_check.dwork, msecs_to_jiffies(time_ms)); + } else + typec_manager.usb_enum_check.pending = false; + } +} +#if defined(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_KUNIT(manager_usb_enum_state_check); +#endif + +bool get_usb_enumeration_state(void) +{ + return typec_manager.usb.enum_state ? 1 : 0; +} +EXPORT_SYMBOL(get_usb_enumeration_state); + +void set_usb_enumeration_state(int state) +{ + if (typec_manager.usb.enum_state != state) { + typec_manager.usb.enum_state = state; +#if defined(CONFIG_USB_HW_PARAM) + usb_enum_hw_param_data_update(typec_manager.usb.enum_state); +#endif + } +} +EXPORT_SYMBOL(set_usb_enumeration_state); + +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) +static int manager_check_vbus_by_otg(void) +{ + int otg_power = 0; +#ifdef MANAGER_DEBUG + u64 cur_stamp; + int otg_power_time = 0; +#endif +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + union power_supply_propval val; + + val.intval = 0; + psy_do_property("otg", get, + POWER_SUPPLY_PROP_ONLINE, val); + otg_power = val.intval; +#endif + + if (typec_manager.otg_stamp) { +#ifdef MANAGER_DEBUG + cur_stamp = get_jiffies_64(); + otg_power_time = time_before64(cur_stamp, + typec_manager.otg_stamp+msecs_to_jiffies(OTG_VBUS_CHECK_TIME)); + utmanager_info("%s [OTG Accessory VBUS] otg power? %d\n", __func__, otg_power_time); + if (otg_power_time) { + typec_manager.vbus_by_otg_detection = 1; + } +#else + if (time_is_after_jiffies64(typec_manager.otg_stamp+msecs_to_jiffies(OTG_VBUS_CHECK_TIME))) + typec_manager.vbus_by_otg_detection = 1; +#endif + typec_manager.otg_stamp = 0; + } + + otg_power |= typec_manager.vbus_by_otg_detection; + +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + utmanager_info("%s otg power? %d (otg?%d, vbusTimeCheck?%d)\n", __func__, + otg_power, val.intval, typec_manager.vbus_by_otg_detection); +#endif + return otg_power; +} + +static int manager_get_otg_power_mode(void) +{ + int otg_power = 0; +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + union power_supply_propval val; + + val.intval = 0; + psy_do_property("otg", get, + POWER_SUPPLY_PROP_ONLINE, val); + otg_power = val.intval | typec_manager.vbus_by_otg_detection; + + utmanager_info("%s otg power? %d (otg?%d, vbusTimeCheck?%d)\n", __func__, + otg_power, val.intval, typec_manager.vbus_by_otg_detection); +#endif + return otg_power; +} +#endif + +void set_usb_enable_state(void) +{ + if (!typec_manager.usb.enable_state) { + typec_manager.usb.enable_state = true; + manager_usb_enum_state_check(BOOT_USB_DWORK_TIME); + } +} +EXPORT_SYMBOL(set_usb_enable_state); + +void manager_notifier_usbdp_support(void) +{ + + if (typec_manager.dp.check_done == 1) { + manager_event_work(PDIC_NOTIFY_DEV_MANAGER, PDIC_NOTIFY_DEV_USB_DP, + PDIC_NOTIFY_ID_USB_DP, typec_manager.dp.is_connect, typec_manager.dp.hs_connect, 0); + + typec_manager.dp.check_done = 0; + } +} +EXPORT_SYMBOL(manager_notifier_usbdp_support); + +static void manager_set_alternate_mode(int listener) +{ + ppdic_data_t ppdic_data; + struct device *pdic_device = get_pdic_device(); + + utmanager_info("%s : listener=%s(%d)\n", __func__, + manager_notify_string(listener), listener); + + if (listener == MANAGER_NOTIFY_PDIC_BATTERY) + typec_manager.alt_is_support |= PDIC_BATTERY; + else if (listener == MANAGER_NOTIFY_PDIC_USB) + typec_manager.alt_is_support |= PDIC_USB; + else if (listener == MANAGER_NOTIFY_PDIC_DP) + typec_manager.alt_is_support |= PDIC_DP; + else if (listener == MANAGER_NOTIFY_PDIC_DELAY_DONE) + typec_manager.alt_is_support |= PDIC_DELAY_DONE; + else + utmanager_info("no support driver to start alternate mode\n"); + + if (!pdic_device) { + utmanager_err("%s: pdic_device is null.\n", __func__); + return; + } + + ppdic_data = dev_get_drvdata(pdic_device); + if (!ppdic_data) { + utmanager_err("there is no ppdic_data for set_enable_alternate_mode\n"); + return; + } + if (!ppdic_data->set_enable_alternate_mode) { + utmanager_err("there is no set_enable_alternate_mode\n"); + return; + } + + utmanager_info("%s : dp_is_support %d, alt_is_support %d\n", __func__, + typec_manager.dp.is_support, + typec_manager.alt_is_support); + +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + if (typec_manager.dp.is_support) { + if (typec_manager.alt_is_support == (PDIC_DP|PDIC_USB|PDIC_BATTERY|PDIC_DELAY_DONE)) + ppdic_data->set_enable_alternate_mode(ALTERNATE_MODE_READY | ALTERNATE_MODE_START); + } else { + if (typec_manager.alt_is_support == (PDIC_USB|PDIC_BATTERY|PDIC_DELAY_DONE)) + ppdic_data->set_enable_alternate_mode(ALTERNATE_MODE_READY | ALTERNATE_MODE_START); + } +#endif +} + +#ifdef CONFIG_USB_EXTERNAL_NOTIFY +static int manager_external_notifier_notification(struct notifier_block *nb, + unsigned long action, void *data) +{ + int ret = 0; + int enable = *(int *)data; + + switch (action) { + case EXTERNAL_NOTIFY_DEVICEADD: + utmanager_info("%s EXTERNAL_NOTIFY_DEVICEADD, enable=%d\n", __func__, enable); + utmanager_info("dr_state: %s, muic.attach_state: %d\n", + pdic_usbstatus_string(typec_manager.usb.dr), + typec_manager.muic.attach_state); + if (enable && +#if !IS_ENABLED(CONFIG_CABLE_TYPE_NOTIFIER) && !IS_ENABLED(CONFIG_MTK_CHARGER) + typec_manager.muic.attach_state != MUIC_NOTIFY_CMD_DETACH && +#endif + typec_manager.usb.dr == USB_STATUS_NOTIFY_ATTACH_DFP) { + utmanager_info("%s: a usb device is added in host mode\n", __func__); + /* USB Cable type */ + manager_event_work(PDIC_NOTIFY_DEV_MANAGER, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_USB, 0, 0, PD_USB_TYPE); + } + break; + case EXTERNAL_NOTIFY_POSSIBLE_USB: + utmanager_info("%s EXTERNAL_NOTIFY_POSSIBLE_USB, enable=%d\n", __func__, enable); + manager_set_alternate_mode(MANAGER_NOTIFY_PDIC_DELAY_DONE); + break; + default: + break; + } + return ret; +} +#endif + +#if IS_ENABLED(CONFIG_MUIC_POGO) +static void manager_event_processing_by_pogo_work(struct work_struct *work) +{ + utmanager_info("%s: dr=%s, muic pogo:%d\n", __func__, + pdic_usbstatus_string(typec_manager.usb.dr), typec_manager.is_muic_pogo); + + if (typec_manager.is_muic_pogo || typec_manager.usb.dr == USB_STATUS_NOTIFY_ATTACH_DFP) { + manager_event_work(PDIC_NOTIFY_DEV_MANAGER, PDIC_NOTIFY_DEV_USB, + PDIC_NOTIFY_ID_USB, PDIC_NOTIFY_ATTACH, USB_STATUS_NOTIFY_ATTACH_DFP, 0); + } else if (typec_manager.usb.dr == USB_STATUS_NOTIFY_ATTACH_UFP) { + manager_event_work(PDIC_NOTIFY_DEV_MANAGER, PDIC_NOTIFY_DEV_USB, + PDIC_NOTIFY_ID_USB, PDIC_NOTIFY_ATTACH, USB_STATUS_NOTIFY_ATTACH_UFP, 0); + } else { + utmanager_info("%s: Not supported", __func__); + } +} +#endif /* CONFIG_MUIC_POGO */ + +static void manager_event_processing_by_vbus(bool run) +{ +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) + if (typec_manager.usb_event_by_vbus.pending) + cancel_delayed_work(&typec_manager.usb_event_by_vbus.dwork); + if (run && typec_manager.usb.dr == USB_STATUS_NOTIFY_ATTACH_UFP) { + typec_manager.usb_event_by_vbus.pending = true; + schedule_delayed_work(&typec_manager.usb_event_by_vbus.dwork, + msecs_to_jiffies(VBUS_USB_OFF_TIMEOUT)); + } else + typec_manager.usb_event_by_vbus.pending = false; +#else + utmanager_info("%s: Not supported", __func__); +#endif +} + +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) +static void manager_event_processing_by_vbus_work(struct work_struct *work) +{ + utmanager_info("%s: dr=%s, rid=%s\n", __func__, + pdic_usbstatus_string(typec_manager.usb.dr), + pdic_rid_string(typec_manager.pdic_rid_state)); + + typec_manager.usb_event_by_vbus.pending = false; + if (typec_manager.pdic_rid_state == RID_523K || typec_manager.pdic_rid_state == RID_619K + || typec_manager.classified_cable_type == MANAGER_NOTIFY_MUIC_UART + || typec_manager.usb.dr == USB_STATUS_NOTIFY_ATTACH_DFP + || typec_manager.vbus_state == STATUS_VBUS_HIGH) { + return; + } else if (typec_manager.muic.attach_state == MUIC_NOTIFY_CMD_DETACH) { + utmanager_info("%s: force usb detach", __func__); + manager_usb_event_send(USB_STATUS_NOTIFY_DETACH); + return; + } + + if (typec_manager.pdic_attach_state == PDIC_NOTIFY_DETACH) { + utmanager_info("%s: force pdic detach event", __func__); + manager_event_work(PDIC_NOTIFY_DEV_MANAGER, PDIC_NOTIFY_DEV_MUIC, + PDIC_NOTIFY_ID_ATTACH, PDIC_NOTIFY_DETACH, 0, typec_manager.muic.cable_type); + } +} +#endif + +__visible_for_testing void manager_water_status_update(int status) +{ + if (status) { /* attach */ + if (!typec_manager.water.detected) { + typec_manager.water.detected = 1; +#if defined(CONFIG_USB_HW_PARAM) + /*update water time */ + water_dry_time_update(status); +#endif + +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) + mutex_lock(&typec_manager.mo_lock); + if (typec_manager.vbus_state == STATUS_VBUS_HIGH + && !manager_get_otg_power_mode()) + manager_event_work(PDIC_NOTIFY_DEV_PDIC, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_WATER, status, 0, typec_manager.water.report_type); + mutex_unlock(&typec_manager.mo_lock); +#endif + } +#if IS_ENABLED(CONFIG_SEC_ABC) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=pdic@INFO=water_det"); +#else + sec_abc_send_event("MODULE=pdic@WARN=water_det"); +#endif +#endif + } else { + typec_manager.water.detected = 0; + typec_manager.water.detOnPowerOff = 0; +#if defined(CONFIG_USB_HW_PARAM) + /* update run_dry time */ + water_dry_time_update(status); +#endif + if (typec_manager.water.wVbus_det) + manager_event_work(PDIC_NOTIFY_DEV_PDIC, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_WATER, status, 0, typec_manager.water.report_type); + } +} +#if defined(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_KUNIT(manager_water_status_update); +#endif +__visible_for_testing int manager_handle_pdic_notification(struct notifier_block *nb, + unsigned long action, void *data) +{ + MANAGER_NOTI_TYPEDEF p_noti = *(MANAGER_NOTI_TYPEDEF *)data; + int ret = 0; + + utmanager_info("%s: src:%s dest:%s id:%s sub1:%d sub2:%d sub3:%d\n", __func__, + pdic_event_src_string(p_noti.src), + pdic_event_dest_string(p_noti.dest), + pdic_event_id_string(p_noti.id), + p_noti.sub1, p_noti.sub2, p_noti.sub3); + + if (p_noti.src != PDIC_NOTIFY_ID_INITIAL) + manager_event_processing_by_vbus(false); + + switch (p_noti.id) { + case PDIC_NOTIFY_ID_POWER_STATUS: +#if IS_ENABLED(CONFIG_MTK_CHARGER) + if (typec_manager.water.detected) { + utmanager_err("%s: PD event is invalid in water state", __func__); + return 0; + } +#endif + if (p_noti.sub1 && !typec_manager.pd_con_state) { +#if IS_ENABLED(CONFIG_MTK_CHARGER) + if (!typec_manager.pdic_attach_state) { + utmanager_err("%s: PD event is invalid in cc detach state", __func__); + return 0; + } +#endif + typec_manager.pd_con_state = 1; +#ifdef CONFIG_USB_LPM_CHARGING_SYNC + set_lpm_charging_type_done(get_otg_notify(), 1); +#endif +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + store_usblog_notify(NOTIFY_MANAGER, (void *)&p_noti, NULL); +#endif + } + p_noti.dest = PDIC_NOTIFY_DEV_BATT; + if (typec_manager.pd == NULL) + typec_manager.pd = p_noti.pd; + break; + case PDIC_NOTIFY_ID_ATTACH: // for MUIC + if (typec_manager.pdic_attach_state != p_noti.sub1) { + typec_manager.pdic_attach_state = p_noti.sub1; + if (typec_manager.pdic_attach_state == PDIC_NOTIFY_ATTACH) { + utmanager_info("%s: PDIC_NOTIFY_ATTACH\n", __func__); + if (typec_manager.water.detected) + manager_water_status_update(0); + typec_manager.pd_con_state = 0; + if (p_noti.sub2) + typec_manager.otg_stamp = get_jiffies_64(); + } + } + + if (typec_manager.pdic_attach_state == PDIC_NOTIFY_DETACH) { + utmanager_info("%s: PDIC_NOTIFY_DETACH (pd=%d)\n", __func__, + typec_manager.pd_con_state); + if (typec_manager.pd_con_state) { + typec_manager.pd_con_state = 0; + manager_event_work(p_noti.src, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_ATTACH, PDIC_NOTIFY_DETACH, 0, ATTACHED_DEV_UNOFFICIAL_ID_ANY_MUIC); + } + manager_event_work(PDIC_NOTIFY_DEV_MANAGER, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_USB, 0, 0, PD_NONE_TYPE); +#ifdef CONFIG_USB_LPM_CHARGING_SYNC + set_lpm_charging_type_done(get_otg_notify(), 1); +#endif + } + break; + case PDIC_NOTIFY_ID_RID: + typec_manager.pdic_rid_state = p_noti.sub1; + break; + case PDIC_NOTIFY_ID_USB: + manager_usb_event_send(p_noti.sub2); + return 0; + case PDIC_NOTIFY_ID_WATER: + if (p_noti.sub1) { /* attach */ + if (!typec_manager.water.detected) { +#if IS_ENABLED(CONFIG_MTK_CHARGER) + if (typec_manager.pd_con_state) { + typec_manager.pd_con_state = 0; + manager_event_work(p_noti.src, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_ATTACH, PDIC_NOTIFY_DETACH, 0, ATTACHED_DEV_UNOFFICIAL_ID_ANY_MUIC); + } +#endif + manager_event_work(p_noti.src, PDIC_NOTIFY_DEV_MUIC, + PDIC_NOTIFY_ID_WATER, p_noti.sub1, p_noti.sub2, p_noti.sub3); + } + manager_water_status_update(p_noti.sub1); + } else { + manager_event_work(p_noti.src, PDIC_NOTIFY_DEV_MUIC, + PDIC_NOTIFY_ID_WATER, p_noti.sub1, p_noti.sub2, p_noti.sub3); + manager_water_status_update(p_noti.sub1); + } + return 0; + case PDIC_NOTIFY_ID_POFF_WATER: + if (p_noti.sub1) { /* power off water detect */ + typec_manager.water.detOnPowerOff = 1; + utmanager_info("%s: power off water case.\n", __func__); + return 0; + } else + return 0; + case PDIC_NOTIFY_ID_WATER_CABLE: + /* Ignore no water case */ + if (!typec_manager.water.detected) + return 0; + + manager_event_work(p_noti.src, PDIC_NOTIFY_DEV_MUIC, + p_noti.id, p_noti.sub1, p_noti.sub2, p_noti.sub3); + + if (p_noti.sub1) { + mutex_lock(&typec_manager.mo_lock); + /* Send water cable event to battery */ + manager_event_work(p_noti.src, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_WATER, PDIC_NOTIFY_ATTACH, p_noti.sub2, + typec_manager.water.report_type); + +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) + if (typec_manager.vbus_state == STATUS_VBUS_LOW) +#endif + /* make detach event like hiccup case*/ + manager_event_work(p_noti.src, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_WATER, PDIC_NOTIFY_DETACH, p_noti.sub2, + typec_manager.water.report_type); + mutex_unlock(&typec_manager.mo_lock); + } + return 0; + case PDIC_NOTIFY_ID_DEVICE_INFO: + if (typec_manager.pd == NULL) + typec_manager.pd = p_noti.pd; + break; + case PDIC_NOTIFY_ID_SVID_INFO: + if (typec_manager.pd == NULL) + typec_manager.pd = p_noti.pd; + typec_manager.svid_info = p_noti.sub1; + break; + case PDIC_NOTIFY_ID_CLEAR_INFO: + if (p_noti.sub1 == PDIC_NOTIFY_ID_SVID_INFO) + typec_manager.svid_info = -1; + break; + case PDIC_NOTIFY_ID_INITIAL: + return 0; + default: + break; + } + + manager_event_work(p_noti.src, p_noti.dest, + p_noti.id, p_noti.sub1, p_noti.sub2, p_noti.sub3); + + return ret; +} +#if defined(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_KUNIT(manager_handle_pdic_notification); +#endif + +#if !IS_ENABLED(CONFIG_CABLE_TYPE_NOTIFIER) +static void manager_handle_dedicated_muic(PD_NOTI_ATTACH_TYPEDEF muic_evt) +{ +#ifdef CONFIG_USE_DEDICATED_MUIC +#if defined(CONFIG_PDIC_SWITCH) + bool usb_en = false; + bool is_jig_board = !!get_pdic_info(); + bool skip = false; + + switch (muic_evt.cable_type) { + case ATTACHED_DEV_JIG_UART_OFF_MUIC: + case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC: + case ATTACHED_DEV_JIG_UART_ON_MUIC: + is_factory_jig = true; + break; + case ATTACHED_DEV_JIG_USB_OFF_MUIC: + case ATTACHED_DEV_JIG_USB_ON_MUIC: + is_factory_jig = true; + usb_en = true; + break; + case ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC: + case ATTACHED_DEV_AFC_CHARGER_5V_MUIC: + case ATTACHED_DEV_AFC_CHARGER_9V_MUIC: + case ATTACHED_DEV_QC_CHARGER_PREPARE_MUIC: + case ATTACHED_DEV_QC_CHARGER_5V_MUIC: + case ATTACHED_DEV_QC_CHARGER_9V_MUIC: + skip = true; + break; + default: + if (!is_factory_jig && !is_jig_board) + usb_en = true; + break; + } + + if (!skip) { + if (usb_en) + manager_event_work(PDIC_NOTIFY_DEV_MANAGER, + PDIC_NOTIFY_DEV_USB, PDIC_NOTIFY_ID_USB, + PDIC_NOTIFY_ATTACH, USB_STATUS_NOTIFY_ATTACH_UFP, 0); + else + manager_event_work(PDIC_NOTIFY_DEV_MANAGER, + PDIC_NOTIFY_DEV_USB, PDIC_NOTIFY_ID_USB, + PDIC_NOTIFY_DETACH, USB_STATUS_NOTIFY_DETACH, 0); + } +#endif + manager_event_work(muic_evt.src, PDIC_NOTIFY_DEV_SUB_BATTERY, + muic_evt.id, muic_evt.attach, muic_evt.rprd, muic_evt.cable_type); +#else + utmanager_info("%s: Not supported", __func__); +#endif +} + +static void manager_handle_second_muic(PD_NOTI_ATTACH_TYPEDEF muic_evt) +{ +#ifdef CONFIG_USE_SECOND_MUIC + typec_manager.second_muic.attach_state = muic_evt.attach; + typec_manager.second_muic.cable_type = muic_evt.cable_type; + manager_event_work(muic_evt.src, PDIC_NOTIFY_DEV_SUB_BATTERY, + muic_evt.id, muic_evt.attach, muic_evt.rprd, muic_evt.cable_type); +#else + utmanager_info("%s: Not supported", __func__); +#endif +} + +#ifdef CONFIG_USB_LPM_CHARGING_SYNC +int check_lpm_charging_type_confirm(uint64_t cable_type) +{ + switch (cable_type) { + case ATTACHED_DEV_USB_MUIC: + case ATTACHED_DEV_CDP_MUIC: + case ATTACHED_DEV_TA_MUIC: + case ATTACHED_DEV_UNOFFICIAL_MUIC: + case ATTACHED_DEV_UNOFFICIAL_TA_MUIC: + case ATTACHED_DEV_UNOFFICIAL_ID_MUIC: + case ATTACHED_DEV_UNOFFICIAL_ID_TA_MUIC: + case ATTACHED_DEV_UNOFFICIAL_ID_ANY_MUIC: + case ATTACHED_DEV_UNOFFICIAL_ID_USB_MUIC: + case ATTACHED_DEV_UNOFFICIAL_ID_CDP_MUIC: + case ATTACHED_DEV_UNDEFINED_CHARGING_MUIC: + case ATTACHED_DEV_TYPE1_CHG_MUIC: + case ATTACHED_DEV_TYPE2_CHG_MUIC: + case ATTACHED_DEV_TYPE3_MUIC: + case ATTACHED_DEV_TYPE3_MUIC_TA: + case ATTACHED_DEV_TYPE3_ADAPTER_MUIC: + case ATTACHED_DEV_TYPE3_CHARGER_MUIC: + case ATTACHED_DEV_UNSUPPORTED_ID_MUIC: + case ATTACHED_DEV_UNSUPPORTED_ID_VB_MUIC: + case ATTACHED_DEV_UNDEFINED_RANGE_MUIC: + case ATTACHED_DEV_TIMEOUT_OPEN_MUIC: + return 0; + default: + return 1; + } +} +#endif + +static void manager_handle_muic(PD_NOTI_ATTACH_TYPEDEF muic_evt) +{ + typec_manager.muic.attach_state = muic_evt.attach; + typec_manager.muic.cable_type = muic_evt.cable_type; + + if (typec_manager.water.detected) { + utmanager_info("%s: Just returned because the moisture was detected\n", __func__); + return; + } + + if (!muic_evt.attach) + typec_manager.classified_cable_type = MANAGER_NOTIFY_MUIC_NONE; + +#ifdef CONFIG_USB_LPM_CHARGING_SYNC + if (muic_evt.attach) { + if (check_lpm_charging_type_confirm(muic_evt.cable_type)) + set_lpm_charging_type_done(get_otg_notify(), 1); + } +#endif + + switch (muic_evt.cable_type) { + case ATTACHED_DEV_JIG_USB_OFF_MUIC: + case ATTACHED_DEV_JIG_USB_ON_MUIC: + utmanager_info("%s: JIG USB(%d) %s, PDIC: %s\n", __func__, + muic_evt.cable_type, muic_evt.attach ? "Attached" : "Detached", + typec_manager.pdic_attach_state ? "Attached" : "Detached"); + + if (muic_evt.attach) { + typec_manager.classified_cable_type = MANAGER_NOTIFY_MUIC_USB; + manager_usb_event_send(USB_STATUS_NOTIFY_ATTACH_UFP); + } else + manager_usb_event_send(USB_STATUS_NOTIFY_DETACH); + break; + + case ATTACHED_DEV_USB_MUIC: + case ATTACHED_DEV_CDP_MUIC: + case ATTACHED_DEV_UNOFFICIAL_ID_USB_MUIC: + case ATTACHED_DEV_UNOFFICIAL_ID_CDP_MUIC: + utmanager_info("%s: USB(%d) %s, PDIC: %s, MODE: %s\n", __func__, + muic_evt.cable_type, muic_evt.attach ? "Attached" : "Detached", + typec_manager.pdic_attach_state ? "Attached" : "Detached", + typec_manager.usb_factory ? "USB Factory" : "Normal"); + + if (muic_evt.attach) { + typec_manager.classified_cable_type = MANAGER_NOTIFY_MUIC_USB; + if (typec_manager.pdic_attach_state + || typec_manager.usb_factory) { + if (typec_manager.usb.dr != USB_STATUS_NOTIFY_ATTACH_DFP) + manager_usb_event_send(USB_STATUS_NOTIFY_ATTACH_UFP); + } + } else + if (!typec_manager.pdic_attach_state + || typec_manager.usb_factory) + manager_usb_event_send(USB_STATUS_NOTIFY_DETACH); + break; + + case ATTACHED_DEV_OTG_MUIC: + if (muic_evt.attach) + typec_manager.classified_cable_type = MANAGER_NOTIFY_MUIC_OTG; + break; + + case ATTACHED_DEV_JIG_UART_OFF_MUIC: + case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC: + case ATTACHED_DEV_JIG_UART_OFF_VB_OTG_MUIC: + case ATTACHED_DEV_JIG_UART_OFF_VB_FG_MUIC: + case ATTACHED_DEV_JIG_UART_ON_MUIC: + case ATTACHED_DEV_JIG_UART_ON_VB_MUIC: + if (muic_evt.attach) + typec_manager.classified_cable_type = MANAGER_NOTIFY_MUIC_UART; + break; + + case ATTACHED_DEV_TA_MUIC: + utmanager_info("%s: TA(%d) %s\n", __func__, muic_evt.cable_type, + muic_evt.attach ? "Attached" : "Detached"); + + if (muic_evt.attach) + typec_manager.classified_cable_type = MANAGER_NOTIFY_MUIC_CHARGER; + break; + + case ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC: + case ATTACHED_DEV_QC_CHARGER_PREPARE_MUIC: + utmanager_info("%s: AFC or QC Prepare(%d) %s\n", __func__, + muic_evt.cable_type, muic_evt.attach ? "Attached" : "Detached"); + + if (muic_evt.attach) + typec_manager.classified_cable_type = MANAGER_NOTIFY_MUIC_CHARGER; + break; + + case ATTACHED_DEV_TIMEOUT_OPEN_MUIC: + utmanager_info("%s: DCD Timeout device is detected(%d) %s, cable:%s pdic:%s\n", + __func__, muic_evt.cable_type, + muic_evt.attach ? "Attached" : "Detached", + pdic_usbstatus_string(typec_manager.usb.dr), + typec_manager.pdic_attach_state ? "Attached" : "Detached"); + + if (muic_evt.attach) { + typec_manager.classified_cable_type = MANAGER_NOTIFY_MUIC_TIMEOUT_OPEN_DEVICE; + if (typec_manager.usb.dr != USB_STATUS_NOTIFY_ATTACH_DFP + && typec_manager.pdic_attach_state) + manager_usb_event_send(USB_STATUS_NOTIFY_ATTACH_UFP); + } else { + if (!typec_manager.pdic_attach_state) + manager_usb_event_send(USB_STATUS_NOTIFY_DETACH); + } + break; + + default: + utmanager_info("%s: Cable(%d) %s\n", __func__, muic_evt.cable_type, + muic_evt.attach ? "Attached" : "Detached"); + break; + } + + if (muic_evt.dest == PDIC_NOTIFY_DEV_PDIC) { + utmanager_info("%s: dest is PDIC\n", __func__); + } else if (!(muic_evt.attach) && typec_manager.pd_con_state && + muic_evt.cable_type != typec_manager.water.report_type) { + utmanager_info("%s: Don't send MUIC detach event when PD charger is connected\n", __func__); + } else { + manager_event_work(PDIC_NOTIFY_DEV_MUIC, PDIC_NOTIFY_DEV_BATT, + muic_evt.id, muic_evt.attach, muic_evt.rprd, muic_evt.cable_type); + } +} + +#if IS_ENABLED(CONFIG_MUIC_POGO) +static void manager_handle_muic_pogo(PD_NOTI_ATTACH_TYPEDEF muic_evt) +{ + switch (muic_evt.cable_type) { + case ATTACHED_DEV_POGO_DOCK_34K_MUIC: + case ATTACHED_DEV_POGO_DOCK_49_9K_MUIC: + utmanager_info("%s: Pogo dock(%d) %s, dr_state: %s\n", __func__, muic_evt.cable_type, + muic_evt.attach ? "Attached" : "Detached", pdic_usbstatus_string(typec_manager.usb.dr)); + cancel_delayed_work(&typec_manager.usb_event_by_pogo.dwork); + typec_manager.is_muic_pogo = muic_evt.attach; + switch (typec_manager.usb.dr) { + case USB_STATUS_NOTIFY_ATTACH_UFP: + manager_event_work(PDIC_NOTIFY_DEV_MANAGER, PDIC_NOTIFY_DEV_USB, + PDIC_NOTIFY_ID_USB, PDIC_NOTIFY_DETACH, USB_STATUS_NOTIFY_DETACH, 0); + schedule_delayed_work(&typec_manager.usb_event_by_pogo.dwork, msecs_to_jiffies(600)); + break; + case USB_STATUS_NOTIFY_DETACH: + if (typec_manager.is_muic_pogo) + manager_event_work(PDIC_NOTIFY_DEV_MANAGER, PDIC_NOTIFY_DEV_USB, + PDIC_NOTIFY_ID_USB, PDIC_NOTIFY_ATTACH, USB_STATUS_NOTIFY_ATTACH_DFP, 0); + else + manager_event_work(PDIC_NOTIFY_DEV_MANAGER, PDIC_NOTIFY_DEV_USB, + PDIC_NOTIFY_ID_USB, PDIC_NOTIFY_DETACH, USB_STATUS_NOTIFY_DETACH, 0); + break; + default: + break; + } + manager_event_work(PDIC_NOTIFY_DEV_MUIC, PDIC_NOTIFY_DEV_BATT, + muic_evt.id, muic_evt.attach, muic_evt.rprd, muic_evt.cable_type); + break; + default: + break; + } +} +#endif /* CONFIG_MUIC_POGO */ + +__visible_for_testing int manager_handle_muic_notification(struct notifier_block *nb, + unsigned long action, void *data) +{ + PD_NOTI_ATTACH_TYPEDEF muic_event = *(PD_NOTI_ATTACH_TYPEDEF *)data; + + utmanager_info("%s: src:%s id:%s attach:%d, cable_type:%d\n", __func__, + pdic_event_src_string(muic_event.src), + pdic_event_id_string(muic_event.id), + muic_event.attach, muic_event.cable_type); + + switch (muic_event.src) { + case PDIC_NOTIFY_DEV_DEDICATED_MUIC: + manager_handle_dedicated_muic(muic_event); + break; + case PDIC_NOTIFY_DEV_SECOND_MUIC: + manager_handle_second_muic(muic_event); + break; + case PDIC_NOTIFY_DEV_MUIC: + default: + manager_handle_muic(muic_event); + break; + } + +#if IS_ENABLED(CONFIG_MUIC_POGO) + if (muic_event.id == PDIC_NOTIFY_ID_POGO) + manager_handle_muic_pogo(muic_event); +#endif + + return 0; +} +#if defined(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_KUNIT(manager_handle_muic_notification); +#endif +#endif + +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) +__visible_for_testing int manager_handle_vbus_notification(struct notifier_block *nb, + unsigned long action, void *data) +{ + vbus_status_t vbus_type = *(vbus_status_t *)data; + + mutex_lock(&typec_manager.mo_lock); + utmanager_info("%s: cmd=%lu, vbus_type=%s, WATER DET=%d ATTACH=%s\n", __func__, + action, vbus_type == STATUS_VBUS_HIGH ? "HIGH" : "LOW", typec_manager.water.detected, + typec_manager.pdic_attach_state == PDIC_NOTIFY_ATTACH ? "ATTACH":"DETACH"); + + typec_manager.vbus_state = vbus_type; + + switch (vbus_type) { + case STATUS_VBUS_HIGH: + if (!manager_check_vbus_by_otg() && typec_manager.water.detected) + manager_event_work(PDIC_NOTIFY_DEV_MANAGER, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_WATER, PDIC_NOTIFY_ATTACH, 0, typec_manager.water.report_type); + manager_event_processing_by_vbus(false); + break; + case STATUS_VBUS_LOW: + typec_manager.vbus_by_otg_detection = 0; + if (typec_manager.water.detected) + manager_event_work(PDIC_NOTIFY_DEV_MANAGER, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_ATTACH, PDIC_NOTIFY_DETACH, 0, typec_manager.water.report_type); + manager_event_processing_by_vbus(true); + break; + default: + break; + } + + mutex_unlock(&typec_manager.mo_lock); + return 0; +} +#if defined(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_KUNIT(manager_handle_vbus_notification); +#endif +#endif + +#if IS_ENABLED(CONFIG_CABLE_TYPE_NOTIFIER) +static int manager_cable_type_handle_notification(struct notifier_block *nb, + unsigned long action, void *data) +{ + cable_type_attached_dev_t attached_dev = *(cable_type_attached_dev_t *)data; + + utmanager_info("%s action=%lu, attached_dev=%d\n", + __func__, action, attached_dev); + + if (!action) + typec_manager.classified_cable_type = MANAGER_NOTIFY_MUIC_NONE; + + switch (attached_dev) { + case CABLE_TYPE_USB: + case CABLE_TYPE_USB_SDP: + case CABLE_TYPE_USB_CDP: + if (action) { + typec_manager.classified_cable_type = MANAGER_NOTIFY_MUIC_USB; + manager_usb_event_send(USB_STATUS_NOTIFY_ATTACH_UFP); + } else { + manager_usb_event_send(USB_STATUS_NOTIFY_DETACH); + } + break; + case CABLE_TYPE_OTG: + if (action) { + typec_manager.classified_cable_type = MANAGER_NOTIFY_MUIC_OTG; + manager_usb_event_send(USB_STATUS_NOTIFY_ATTACH_DFP); + } else { + manager_usb_event_send(USB_STATUS_NOTIFY_DETACH); + } + break; + default: + manager_usb_event_send(USB_STATUS_NOTIFY_DETACH); + break; + } + + return 0; +} +#endif + + +int manager_notifier_register(struct notifier_block *nb, notifier_fn_t notifier, + manager_notifier_device_t listener) +{ + int ret = 0; + MANAGER_NOTI_TYPEDEF m_noti = {0, }; +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif + + utmanager_info("%s: listener=%s(%d) register\n", __func__, + manager_notify_string(listener), listener); + +#if IS_BUILTIN(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + if (!manager_notifier_init_done) + manager_notifier_init(); +#endif + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) && defined(CONFIG_SEC_FACTORY) + /* Check if MANAGER Notifier is ready. */ + if (!manager_device) { + utmanager_err("%s: Not Initialized...\n", __func__); + return -1; + } +#endif + + if (listener == MANAGER_NOTIFY_PDIC_MUIC || listener == MANAGER_NOTIFY_PDIC_SENSORHUB) { + SET_MANAGER_NOTIFIER_BLOCK(nb, notifier, listener); + ret = blocking_notifier_chain_register(&(typec_manager.manager_muic_notifier), nb); + if (ret < 0) + utmanager_err("%s: muic blocking_notifier_chain_register error(%d)\n", + __func__, ret); + } else { + SET_MANAGER_NOTIFIER_BLOCK(nb, notifier, listener); + ret = blocking_notifier_chain_register(&(typec_manager.manager_notifier), nb); + if (ret < 0) + utmanager_err("%s: manager blocking_notifier_chain_register error(%d)\n", + __func__, ret); + } + + switch (listener) { + case MANAGER_NOTIFY_PDIC_BATTERY: +#if !IS_ENABLED(CONFIG_CABLE_TYPE_NOTIFIER) && !IS_ENABLED(CONFIG_MTK_CHARGER) + m_noti.src = PDIC_NOTIFY_DEV_MANAGER; + m_noti.dest = PDIC_NOTIFY_DEV_BATT; + m_noti.pd = typec_manager.pd; + if (typec_manager.water.detected) { + if (typec_manager.muic.attach_state +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) + || typec_manager.water.detOnPowerOff + || typec_manager.vbus_state == STATUS_VBUS_HIGH +#endif + ) { + utmanager_info("%s: [BATTERY] power_off_water_det:%d , vbus_state:%d\n", __func__, + typec_manager.water.detOnPowerOff, typec_manager.vbus_state); + + m_noti.id = PDIC_NOTIFY_ID_WATER; + m_noti.sub1 = PDIC_NOTIFY_ATTACH; + m_noti.sub3 = typec_manager.water.report_type; +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) + if (typec_manager.vbus_state != STATUS_VBUS_HIGH) { + nb->notifier_call(nb, m_noti.id, &(m_noti)); + m_noti.sub1 = PDIC_NOTIFY_DETACH; + } +#endif + } + } else { + m_noti.id = PDIC_NOTIFY_ID_ATTACH; + if (typec_manager.pd_con_state) { + m_noti.id = PDIC_NOTIFY_ID_POWER_STATUS; + m_noti.sub1 = PDIC_NOTIFY_ATTACH; + } else if (typec_manager.muic.attach_state) { + m_noti.sub1 = PDIC_NOTIFY_ATTACH; + m_noti.sub3 = typec_manager.muic.cable_type; + } + } + utmanager_info("%s: [BATTERY] id:%s, cable_type=%d %s\n", __func__, + pdic_event_id_string(m_noti.id), + m_noti.sub3, m_noti.sub1 ? "Attached" : "Detached"); + nb->notifier_call(nb, m_noti.id, &(m_noti)); + if (typec_manager.svid_info >= 0) { + m_noti.dest = PDIC_NOTIFY_DEV_ALL; + m_noti.id = PDIC_NOTIFY_ID_SVID_INFO; + m_noti.sub1 = typec_manager.svid_info; + m_noti.sub2 = 0; + m_noti.sub3 = 0; + nb->notifier_call(nb, m_noti.id, &(m_noti)); + } +#else + if (typec_manager.muic.attach_state) { + m_noti.src = PDIC_NOTIFY_DEV_MANAGER; + m_noti.dest = PDIC_NOTIFY_DEV_BATT; + m_noti.pd = typec_manager.pd; + m_noti.id = PDIC_NOTIFY_ID_ATTACH; + m_noti.sub1 = PDIC_NOTIFY_ATTACH; + m_noti.sub3 = typec_manager.muic.cable_type; + + utmanager_info("%s: [BATTERY] id:%s, cable_type=%d %s\n", __func__, + pdic_event_id_string(m_noti.id), + m_noti.sub3, m_noti.sub1 ? "Attached" : "Detached"); + nb->notifier_call(nb, m_noti.id, &(m_noti)); + } + utmanager_info("%s: [BATTERY] Registration completed\n", __func__); +#endif + manager_notify_pdic_battery_init = true; + manager_set_alternate_mode(listener); + break; + case MANAGER_NOTIFY_PDIC_SUB_BATTERY: + m_noti.src = PDIC_NOTIFY_DEV_MANAGER; + m_noti.dest = PDIC_NOTIFY_DEV_SUB_BATTERY; + m_noti.id = PDIC_NOTIFY_ID_ATTACH; +#ifdef CONFIG_USE_SECOND_MUIC + if (typec_manager.second_muic.attach_state) { + m_noti.sub1 = PDIC_NOTIFY_ATTACH; + m_noti.sub3 = typec_manager.second_muic.cable_type; + } +#endif + utmanager_info("%s: [BATTERY2] cable_type=%d %s\n", __func__, + m_noti.sub3, m_noti.sub1 ? "Attached" : "Detached"); + nb->notifier_call(nb, m_noti.id, &(m_noti)); + break; + case MANAGER_NOTIFY_PDIC_USB: + m_noti.src = PDIC_NOTIFY_DEV_MANAGER; + m_noti.dest = PDIC_NOTIFY_DEV_USB; + m_noti.id = PDIC_NOTIFY_ID_USB; + if (typec_manager.usb_factory) { + utmanager_info("%s. Forced to run usb(usb factory mode)\n", + __func__); + typec_manager.usb.dr = USB_STATUS_NOTIFY_ATTACH_UFP; + } + + if (typec_manager.usb.dr) { + m_noti.sub1 = PDIC_NOTIFY_ATTACH; + m_noti.sub2 = typec_manager.usb.dr; + } else if (typec_manager.classified_cable_type == MANAGER_NOTIFY_MUIC_USB) { + m_noti.sub1 = PDIC_NOTIFY_ATTACH; + typec_manager.usb.dr = USB_STATUS_NOTIFY_ATTACH_UFP; + m_noti.sub2 = USB_STATUS_NOTIFY_ATTACH_UFP; +#if IS_ENABLED(CONFIG_MUIC_POGO) + } else if (typec_manager.is_muic_pogo) { + m_noti.sub1 = typec_manager.muic.attach_state; + m_noti.sub2 = USB_STATUS_NOTIFY_ATTACH_DFP; +#endif /* CONFIG_MUIC_POGO */ + } + utmanager_info("%s: [USB] %s\n", __func__, pdic_usbstatus_string(m_noti.sub2)); + typec_manager.usb.notified_dr = m_noti.sub2; + nb->notifier_call(nb, m_noti.id, &(m_noti)); + manager_set_alternate_mode(listener); +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + register_hw_param_manager(o_notify, manager_hw_param_update); +#endif + break; + case MANAGER_NOTIFY_PDIC_DP: + utmanager_info("%s: [DP] dp.attach_state=%d\n", __func__, + typec_manager.dp.attach_state); + m_noti.src = PDIC_NOTIFY_DEV_MANAGER; + m_noti.dest = PDIC_NOTIFY_DEV_DP; + if (typec_manager.dp.attach_state == PDIC_NOTIFY_ATTACH) { + m_noti.id = PDIC_NOTIFY_ID_DP_CONNECT; + m_noti.sub1 = typec_manager.dp.attach_state; + nb->notifier_call(nb, m_noti.id, &(m_noti)); + + m_noti.id = PDIC_NOTIFY_ID_DP_LINK_CONF; + m_noti.sub1 = typec_manager.dp.cable_type; + nb->notifier_call(nb, m_noti.id, &(m_noti)); + + if (typec_manager.dp.hpd_state == PDIC_NOTIFY_HIGH) { + m_noti.id = PDIC_NOTIFY_ID_DP_HPD; + m_noti.sub1 = typec_manager.dp.hpd_state; + nb->notifier_call(nb, m_noti.id, &(m_noti)); + } + } + manager_set_alternate_mode(listener); + break; + default: + break; + } + + return ret; +} +EXPORT_SYMBOL(manager_notifier_register); + +int manager_notifier_unregister(struct notifier_block *nb) +{ + int ret = 0; + + utmanager_info("%s: listener=%s unregister\n", __func__, manager_notify_string(nb->priority)); + if (nb->priority == MANAGER_NOTIFY_PDIC_MUIC || + nb->priority == MANAGER_NOTIFY_PDIC_SENSORHUB) { + ret = blocking_notifier_chain_unregister(&(typec_manager.manager_muic_notifier), nb); + if (ret < 0) + utmanager_err("%s: muic blocking_notifier_chain_unregister error(%d)\n", + __func__, ret); + DESTROY_MANAGER_NOTIFIER_BLOCK(nb); + } else { + ret = blocking_notifier_chain_unregister(&(typec_manager.manager_notifier), nb); + if (ret < 0) + utmanager_err("%s: pdic blocking_notifier_chain_unregister error(%d)\n", + __func__, ret); + DESTROY_MANAGER_NOTIFIER_BLOCK(nb); + } + return ret; +} +EXPORT_SYMBOL(manager_notifier_unregister); + +static int manager_handle_notification_init(void) +{ +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) + if (!(typec_manager.confirm_notifier_register & VBUS_NOTIFIER)) + if (!vbus_notifier_register(&typec_manager.vbus_nb, + manager_handle_vbus_notification, VBUS_NOTIFY_DEV_MANAGER)) + typec_manager.confirm_notifier_register |= VBUS_NOTIFIER; +#else + typec_manager.confirm_notifier_register |= VBUS_NOTIFIER; +#endif + + if (!(typec_manager.confirm_notifier_register & PDIC_NOTIFIER)) + if (!pdic_notifier_register(&typec_manager.pdic_nb, + manager_handle_pdic_notification, PDIC_NOTIFY_DEV_MANAGER)) + typec_manager.confirm_notifier_register |= PDIC_NOTIFIER; + + if (!(typec_manager.confirm_notifier_register & MUIC_NOTIFIER)) { +#if IS_ENABLED(CONFIG_CABLE_TYPE_NOTIFIER) + if (!cable_type_notifier_register(&typec_manager.cable_type_nb, + manager_cable_type_handle_notification, CABLE_TYPE_NOTIFY_DEV_USB)) + typec_manager.confirm_notifier_register |= MUIC_NOTIFIER; +#else + if (!muic_notifier_register(&typec_manager.muic_nb, + manager_handle_muic_notification, MUIC_NOTIFY_DEV_MANAGER)) + typec_manager.confirm_notifier_register |= MUIC_NOTIFIER; +#endif + } + + utmanager_info("%s : %s(%d)\n", __func__, + (typec_manager.confirm_notifier_register == ALL_NOTIFIER) ? "success" : "fail", + typec_manager.confirm_notifier_register); + + typec_manager.notifier_register_try_count++; + return typec_manager.confirm_notifier_register != ALL_NOTIFIER; +} + +static void delayed_manger_notifier_init(struct work_struct *work) +{ + utmanager_info("%s : %d = times!\n", __func__, typec_manager.notifier_register_try_count); + if (manager_handle_notification_init()) { + if (typec_manager.notifier_register_try_count != NOTIFIER_REG_RETRY_COUNT) + schedule_delayed_work(&typec_manager.manager_init_work, + msecs_to_jiffies(NOTIFIER_REG_RETRY_TIME)); + else + utmanager_err("fail to init manager notifier\n"); + } else + utmanager_info("%s : done!\n", __func__); +} + +static void manager_dp_state_init(void) +{ + typec_manager.dp.is_connect = 0; + typec_manager.dp.hs_connect = 0; + typec_manager.dp.check_done = 1; + + if (IS_ENABLED(CONFIG_SEC_DISPLAYPORT)) { + utmanager_info("%s: usb_host: support DP\n", __func__); + typec_manager.dp.is_support = 1; + } else { + utmanager_info("%s: usb_host: no support DP\n", __func__); + typec_manager.dp.is_support = 0; + } +} + +#if defined(CONFIG_SEC_FACTORY) +static ssize_t fac_usb_control_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + char *cmd; + int ret; + + cmd = kzalloc(size+1, GFP_KERNEL); + if (!cmd) + goto error2; + + ret = sscanf(buf, "%s", cmd); + if (ret != 1) + goto error1; + + utmanager_info("%s cmd=%s\n", __func__, cmd); + if (!strcmp(cmd, "Off_All")) + manager_usb_event_send(USB_STATUS_NOTIFY_DETACH); + else if (!strcmp(cmd, "On_DEVICE")) + manager_usb_event_send(USB_STATUS_NOTIFY_ATTACH_UFP); + + strncpy(typec_manager.fac_control, + cmd, sizeof(typec_manager.fac_control)-1); + +error1: + kfree(cmd); +error2: + return size; +} + +static ssize_t fac_usb_control_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + utmanager_info("%s typec_manager.fac_control %s\n", + __func__, typec_manager.fac_control); + return sprintf(buf, "%s\n", typec_manager.fac_control); +} + +static DEVICE_ATTR(fac_usb_control, 0664, + fac_usb_control_show, fac_usb_control_store); + +static struct attribute *typec_manager_attributes[] = { + &dev_attr_fac_usb_control.attr, + NULL +}; + +const struct attribute_group typec_manager_sysfs_group = { + .attrs = typec_manager_attributes, +}; +#endif + +static int manager_notifier_init(void) +{ + struct device *pdic_device = get_pdic_device(); +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + ppdic_data_t ppdic_data = NULL; +#endif + int ret = 0; + + utmanager_info("%s (ver %d.%d)\n", __func__, + TYPEC_MANAGER_MAJ_VERSION, + TYPEC_MANAGER_MIN_VERSION); + + if (manager_notifier_init_done) { + utmanager_err("%s already registered\n", __func__); + goto out; + } + manager_notifier_init_done = 1; + +#if IS_BUILTIN(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + pdic_notifier_init(); +#endif + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) && defined(CONFIG_SEC_FACTORY) + manager_device = sec_device_create(NULL, "typec_manager"); + + if (IS_ERR(manager_device)) { + utmanager_err("%s Failed to create device(switch)!\n", __func__); + ret = -ENODEV; + goto out; + } +#endif + + typec_manager.svid_info = -1; + typec_manager.usb_factory = check_factory_mode_boot(); +#if (IS_ENABLED(CONFIG_HICCUP_CHARGER) || IS_ENABLED(CONFIG_SEC_HICCUP)) && IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + typec_manager.water.report_type = is_lpcharge_pdic_param() ? + ATTACHED_DEV_UNDEFINED_RANGE_MUIC : + ATTACHED_DEV_HICCUP_MUIC; +#else + typec_manager.water.report_type = ATTACHED_DEV_UNDEFINED_RANGE_MUIC; +#endif +#if defined(CONFIG_USB_HW_PARAM) + manager_hw_param_init(); +#endif + manager_dp_state_init(); + + typec_manager.manager_noti_wq = + alloc_ordered_workqueue("typec_manager_event", WQ_FREEZABLE | WQ_MEM_RECLAIM); + if (!typec_manager.manager_noti_wq) + utmanager_err("%s: Failed to allocate noti workqueue\n", __func__); + typec_manager.manager_muic_noti_wq = + alloc_ordered_workqueue("typec_manager_muic_event", WQ_FREEZABLE | WQ_MEM_RECLAIM); + if (!typec_manager.manager_muic_noti_wq) + pr_err("%s: Failed to allocate muic noti workqueue\n", __func__); + + BLOCKING_INIT_NOTIFIER_HEAD(&(typec_manager.manager_notifier)); + BLOCKING_INIT_NOTIFIER_HEAD(&(typec_manager.manager_muic_notifier)); + + INIT_DELAYED_WORK(&typec_manager.manager_init_work, + delayed_manger_notifier_init); + INIT_DELAYED_WORK(&typec_manager.usb_enum_check.dwork, + manager_usb_enum_state_check_work); +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) + INIT_DELAYED_WORK(&typec_manager.usb_event_by_vbus.dwork, + manager_event_processing_by_vbus_work); +#endif +#if IS_ENABLED(CONFIG_MUIC_POGO) + INIT_DELAYED_WORK(&typec_manager.usb_event_by_pogo.dwork, + manager_event_processing_by_pogo_work); +#endif /* CONFIG_MUIC_POGO */ + INIT_DELAYED_WORK(&typec_manager.manager_usb_event_delayed_work.dwork, + manager_delayed_usb_event_work); + + if (!pdic_device) + utmanager_err("%s: pdic_device is null.\n", __func__); + else { +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + ppdic_data = dev_get_drvdata(pdic_device); + + if (ppdic_data && ppdic_data->set_enable_alternate_mode) + ppdic_data->set_enable_alternate_mode(ALTERNATE_MODE_NOT_READY); +#endif + } + + mutex_init(&typec_manager.mo_lock); + +#ifdef CONFIG_USB_EXTERNAL_NOTIFY + usb_external_notify_register(&typec_manager.manager_external_notifier_nb, + manager_external_notifier_notification, EXTERNAL_NOTIFY_DEV_MANAGER); +#endif + + if (manager_handle_notification_init()) + schedule_delayed_work(&typec_manager.manager_init_work, + msecs_to_jiffies(NOTIFIER_REG_RETRY_TIME)); + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) && defined(CONFIG_SEC_FACTORY) + strncpy(typec_manager.fac_control, + "On_All", sizeof(typec_manager.fac_control)-1); + + /* create sysfs group */ + ret = sysfs_create_group(&manager_device->kobj, &typec_manager_sysfs_group); +#endif + + utmanager_info("%s end\n", __func__); +out: + return ret; +} + +static void __exit manager_notifier_exit(void) +{ + utmanager_info("%s exit\n", __func__); + mutex_destroy(&typec_manager.mo_lock); +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) + vbus_notifier_unregister(&typec_manager.vbus_nb); +#endif + pdic_notifier_unregister(&typec_manager.pdic_nb); +#if IS_ENABLED(CONFIG_CABLE_TYPE_NOTIFIER) + cable_type_notifier_unregister(&typec_manager.cable_type_nb); +#else + muic_notifier_unregister(&typec_manager.muic_nb); +#endif +#ifdef CONFIG_USB_EXTERNAL_NOTIFY + usb_external_notify_unregister(&typec_manager.manager_external_notifier_nb); +#endif +} + +device_initcall(manager_notifier_init); +module_exit(manager_notifier_exit); + +MODULE_AUTHOR("Samsung USB Team"); +MODULE_DESCRIPTION("USB Typec Manager Notifier"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/maxim/Kconfig b/drivers/usb/typec/maxim/Kconfig new file mode 100644 index 000000000000..ee432833f5cd --- /dev/null +++ b/drivers/usb/typec/maxim/Kconfig @@ -0,0 +1,209 @@ +# +# CCIC devices +# + +comment "CCIC configs" + +config CCIC_MAX77705 + tristate "CCIC 77705" + depends on I2C + default n + help + If you say yes here you will get support for + max77705 ccic full version chipset + +config MAXIM_CCIC_ALTERNATE_MODE + bool "support alternate mode" + depends on CCIC_MAX77705 + default n + help + If you say yes here you will get support for + alternate mode function with max77705 ccic chipset + +config CCIC_MAX77705_DEBUG + bool "CCIC 77705 DEBUG" + depends on CCIC_MAX77705 + default n + help + If you say yes here you will get support for + debugging feature with max77705 ccic chipset + +config MAX77705_FW_PID03_SUPPORT + bool "support MAX77705 FW PID 0x03" + depends on CCIC_MAX77705 + default n + help + If you say yes here you will get support for + specified firmware with max77705 ccic chipset + +config MUIC_MAX77705 + tristate "Using MAX77705 MUIC" + depends on USE_MUIC + default n + help + If you say yes here you will get support for + the MAX77705 MUIC chip. + To enable this driver, + USE_MUIC should be enabled. + +config HV_MUIC_MAX77705_AFC + tristate "Using MAX77705 AFC MUIC" + default n + help + If you say yes here you will get support for + the MAX77705 AFC MUIC. + To enable this driver, + MUIC_MAX77705 should be enabled. + +config MUIC_MAX77705_PDIC + tristate "Using MAX77705 MUIC supports CCIC chip interface" + default n + help + If you say yes here you will get support for + the CCIC chip with MAX77705 MUIC. + To enable this driver, + MUIC_MAX77705 and MUIC_SUPPORT_CCIC should be enabled. + +config AFC + bool "Using MAX77705 AFC" + default n + help + If you say yes here you will get support for + the MAX77705 AFC. + To enable this driver, + MUIC_MAX77705 should be enabled. + +config SUPPORT_QC30 + bool "SUPPORT_QC30" + default n + help + If you say yes here you will get support for the QC3.0 charger. + +config MUIC_QC_DISABLE + bool "Using QC charging" + depends on USE_MUIC + default n + help + If you say yes here you will not get support for the QC charging + +config UPGRADED_WATER_DETECT + bool "Using improved water detection" + depends on CCIC_MAX77705 + default n + help + If you say yes here you will get support for extra water detection + driver will check if it is water + no VBUS + no hiccup state, + and if the condition is satisfied, it will reset IC. + +config GET_UNMASK_VBUS_HWPARAM + bool "Using get unmask vbus hw param" + depends on CCIC_MAX77705 + default n + help + If you say yes here you will get support for getting unmask vbus hw param + driver will check if it is attach/detach repeat state, + and if the condition is satisfied, it will get the hw param. + +config MUIC_DISABLE_CHGDET + bool "Using disable chgdet" + default n + help + If you say yes here you will get support for disabling chgdet. + +config MUIC_USE_SISO_OVP + bool "Using SISO OVP" + default n + help + If you say yes here you will get support for using siso ovp + +# +# CCIC devices +# + +comment "CCIC configs" + +config CCIC_MAX77775 + tristate "CCIC 77775" + depends on I2C + default n + help + If you say yes here you will get support for + max77775 ccic full version chipset + +config MAX77775_CCIC_ALTERNATE_MODE + bool "support alternate mode" + depends on CCIC_MAX77775 + default n + help + If you say yes here you will get support for + alternate mode function with max77775 ccic chipset + +config MUIC_MAX77775 + tristate "Using MAX77775 MUIC" + depends on USE_MUIC + default n + help + If you say yes here you will get support for + the MAX77775 MUIC chip. + To enable this driver, + USE_MUIC should be enabled. + +config HV_MUIC_MAX77775_AFC + tristate "Using MAX77775 AFC MUIC" + default n + help + If you say yes here you will get support for + the MAX77775 AFC MUIC. + To enable this driver, + MUIC_MAX77775 should be enabled. + +config MAX77775_MUIC_QC_DISABLE + bool "Using QC charging" + depends on USE_MUIC + default n + help + If you say yes here you will not get support for the QC charging + +config MAX77775_UPGRADED_WATER_DETECT + bool "Using improved water detection" + depends on CCIC_MAX77775 + default n + help + If you say yes here you will get support for extra water detection + driver will check if it is water + no VBUS + no hiccup state, + and if the condition is satisfied, it will reset IC. + +config MAX77775_GET_UNMASK_VBUS_HWPARAM + bool "Using get unmask vbus hw param" + depends on CCIC_MAX77775 + default n + help + If you say yes here you will get support for getting unmask vbus hw param + driver will check if it is attach/detach repeat state, + and if the condition is satisfied, it will get the hw param. + +config MAX77775_ALTERNATE_TEST_FOR_ON_DEVICE + tristate "KUnit test for max77775_alternate_test" + depends on KUNIT + depends on CCIC_MAX77775 + help + This is feature for max77775_alternate.c file. + If you run this test driver on device, SHOULD set this config as 'm' to build test driver modulraly. + +config MAX77775_ALTERNATE_TEST_FOR_ONLY_UML + tristate "KUnit test for max77775_alternate_test" + depends on KUNIT + depends on UML + depends on CCIC_MAX77775 + help + This is feature for max77775_alternate.c file. (UML) + This CONFIG is recommended to set to y. + +config MAX77775_CCOPEN_AFTER_WATERCABLE + bool "Using ccopen water detection" + depends on CCIC_MAX77775 + default n + help + If you say yes here you will get support for ccopen on water detection + cc line open if the water + cable is connected. + To protect the connector by completely blocking VBUS diff --git a/drivers/usb/typec/maxim/Makefile b/drivers/usb/typec/maxim/Makefile new file mode 100644 index 000000000000..49c2d2b28e63 --- /dev/null +++ b/drivers/usb/typec/maxim/Makefile @@ -0,0 +1,21 @@ +# +# Makefile for ccic devices +# +subdir-ccflags-y := -Wformat +obj-$(CONFIG_CCIC_MAX77705) += pdic_max77705.o +pdic_max77705-y := max77705_cc.o max77705_pd.o max77705_usbc.o max77705_alternate.o +pdic_max77705-$(CONFIG_CCIC_MAX77705_DEBUG) += max77705_debug.o +pdic_max77705-$(CONFIG_MUIC_MAX77705) += max77705-muic.o +pdic_max77705-$(CONFIG_HV_MUIC_MAX77705_AFC) += max77705-muic-afc.o +pdic_max77705-$(CONFIG_MUIC_MAX77705_PDIC) += max77705-muic-ccic.o + +# +# Makefile for ccic devices +# +subdir-ccflags-y := -Wformat +obj-$(CONFIG_CCIC_MAX77775) += pdic_max77775.o +pdic_max77775-y := max77775_cc.o max77775_pd.o max77775_usbc.o max77775_alternate.o +pdic_max77775-$(CONFIG_MUIC_MAX77775) += max77775-muic.o max77775-muic-ccic.o +pdic_max77775-$(CONFIG_HV_MUIC_MAX77775_AFC) += max77775-muic-afc.o + +GCOV_PROFILE_max77775_alternate.o := $(CONFIG_SEC_KUNIT) diff --git a/drivers/usb/typec/maxim/max77705-muic-afc.c b/drivers/usb/typec/maxim/max77705-muic-afc.c new file mode 100755 index 000000000000..220d2dfe3df0 --- /dev/null +++ b/drivers/usb/typec/maxim/max77705-muic-afc.c @@ -0,0 +1,776 @@ +/* + * max77705-muic.c - MUIC driver for the Maxim 77705 + * + * Copyright (C) 2015 Samsung Electronics + * Insun Choi + * + * 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, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* MUIC header file */ +#include +#include +#include + +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) +#include +#endif /* CONFIG_MUIC_NOTIFIER */ + +#if defined(CONFIG_USB_HW_PARAM) +#include +#endif + +#if IS_ENABLED(CONFIG_SEC_ABC) +#include +#endif +#include + +#define RETRY_COUNT 3 + +bool max77705_muic_check_is_enable_afc(struct max77705_muic_data *muic_data, muic_attached_dev_t new_dev) +{ + struct max77705_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + int ret = false; + + if (new_dev == ATTACHED_DEV_TA_MUIC || new_dev == ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC || + muic_data->is_usb_fail) { + if (!muic_data->is_charger_ready) { + pr_info("%s Charger is not ready(%d), skip AFC\n", + __func__, muic_data->is_charger_ready); + } else if (muic_data->is_charger_mode) { + pr_info("%s is_charger_mode(%d), skip AFC\n", + __func__, muic_data->is_charger_mode); +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + } else if (usbc_pdata->fac_water_enable) { + pr_info("%s fac_water_enable(%d), skip AFC\n", __func__, + usbc_pdata->fac_water_enable); +#endif /* CONFIG_PDIC_NOTIFIER */ +#if IS_ENABLED(CONFIG_MUIC_MAX77705_PDIC) + } else if (muic_data->afc_water_disable) { + pr_info("%s water detected(%d), skip AFC\n", __func__, + muic_data->afc_water_disable); +#endif /* CONFIG_MUIC_MAX77705_PDIC */ + } else if (usbc_pdata->pd_support) { + pr_info("%s PD TA detected(%d), skip AFC\n", __func__, + usbc_pdata->pd_support); + } else { + ret = true; + } + } + + return ret; +} + +static void max77705_muic_afc_reset(struct max77705_muic_data *muic_data) +{ + struct max77705_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + usbc_cmd_data write_data; + + pr_info("%s:%s\n", MUIC_DEV_NAME, __func__); + muic_data->is_afc_reset = true; + + init_usbc_cmd_data(&write_data); + write_data.opcode = COMMAND_BC_CTRL2_WRITE; + write_data.write_length = 1; + write_data.write_data[0] = 0x13; /* DPDNMan enable, DP GND, DM Open */ + write_data.read_length = 0; + + max77705_usbc_opcode_write(usbc_pdata, &write_data); +} + +void max77705_muic_check_afc_disabled(struct max77705_muic_data *muic_data) +{ + struct muic_platform_data *pdata = muic_data->pdata; + muic_attached_dev_t new_attached_dev = (muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_DISABLED_MUIC || + muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_9V_MUIC) ? + ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC : ATTACHED_DEV_TA_MUIC; + pr_info("%s:%s\n", MUIC_DEV_NAME, __func__); + + if ((!pdata->afc_disable && (muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_DISABLED_MUIC || + muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_5V_MUIC)) || + (pdata->afc_disable && (muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_9V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_9V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_5V_MUIC + ))) { + + pr_info("%s:%s change charger (%d) -> (%d)\n", MUIC_DEV_NAME, __func__, + muic_data->attached_dev, new_attached_dev); + + muic_data->attached_dev = new_attached_dev; +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + muic_notifier_attach_attached_dev(new_attached_dev); +#endif + + cancel_delayed_work_sync(&(muic_data->afc_work)); + schedule_delayed_work(&(muic_data->afc_work), msecs_to_jiffies(500)); + } +} + +static void max77705_muic_afc_hv_tx_byte_set(struct max77705_muic_data *muic_data, u8 tx_byte) +{ + struct max77705_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + usbc_cmd_data write_data; + + pr_info("%s:%s tx_byte(0x%02x)\n", MUIC_DEV_NAME, __func__, tx_byte); + + init_usbc_cmd_data(&write_data); + write_data.opcode = COMMAND_AFC_RESULT_READ; + write_data.write_length = 1; + write_data.write_data[0] = tx_byte; + write_data.read_length = 10; + + max77705_usbc_opcode_write(usbc_pdata, &write_data); +} + +void max77705_muic_clear_hv_control(struct max77705_muic_data *muic_data) +{ + struct max77705_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + usbc_cmd_data write_data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = COMMAND_HV_CONTROL_WRITE; + write_data.write_length = 1; + write_data.write_data[0] = 0; + + max77705_usbc_opcode_write(usbc_pdata, &write_data); +} + +int max77705_muic_afc_hv_set(struct max77705_muic_data *muic_data, int voltage) +{ + struct max77705_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + usbc_cmd_data write_data; + u8 tx_byte; + + switch (voltage) { + case 5: + tx_byte = 0x08; + break; + case 9: + tx_byte = 0x46; + break; + default: + pr_info("%s:%s invalid value(%d), return\n", MUIC_DEV_NAME, + __func__, voltage); + return -EINVAL; + } + + pr_info("%s:%s voltage(%d)\n", MUIC_DEV_NAME, __func__, voltage); + + init_usbc_cmd_data(&write_data); + write_data.opcode = COMMAND_AFC_RESULT_READ; + write_data.write_length = 1; + write_data.write_data[0] = tx_byte; + write_data.read_length = 10; + + return max77705_usbc_opcode_write(usbc_pdata, &write_data); +} + +int max77705_muic_qc_hv_set(struct max77705_muic_data *muic_data, int voltage) +{ + struct max77705_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + usbc_cmd_data write_data; + u8 dpdndrv; + + switch (voltage) { + case 5: + dpdndrv = 0x04; + break; + case 9: + dpdndrv = 0x09; + break; + default: + pr_info("%s:%s invalid value(%d), return\n", MUIC_DEV_NAME, + __func__, voltage); + return -EINVAL; + } + + pr_info("%s:%s voltage(%d)\n", MUIC_DEV_NAME, __func__, voltage); + + init_usbc_cmd_data(&write_data); + write_data.opcode = COMMAND_QC_2_0_SET; + write_data.write_length = 1; + write_data.write_data[0] = dpdndrv; + write_data.read_length = 2; + + return max77705_usbc_opcode_write(usbc_pdata, &write_data); +} + +#if !defined(CONFIG_MUIC_QC_DISABLE) +static void max77705_muic_handle_detect_dev_mpnack(struct max77705_muic_data *muic_data) +{ + struct max77705_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + usbc_cmd_data write_data; + u8 dpdndrv = 0x09; + + init_usbc_cmd_data(&write_data); + write_data.opcode = COMMAND_QC_2_0_SET; + write_data.write_length = 1; + write_data.write_data[0] = dpdndrv; + write_data.read_length = 2; + + max77705_usbc_opcode_write(usbc_pdata, &write_data); +} +#endif +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) +static void max77705_muic_handle_afc_retry(struct max77705_muic_data *muic_data, + muic_attached_dev_t current_attached_dev) +{ + pr_info("%s:%s current_attached_dev: %d\n", MUIC_DEV_NAME, __func__, current_attached_dev); + + muic_data->attached_dev = current_attached_dev; + if (muic_data->attached_dev != ATTACHED_DEV_TIMEOUT_OPEN_MUIC) { + muic_data->attached_dev = ATTACHED_DEV_RETRY_TIMEOUT_OPEN_MUIC; +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + muic_notifier_detach_attached_dev(current_attached_dev); + muic_notifier_attach_attached_dev(muic_data->attached_dev); +#endif + } +} +#endif +void max77705_muic_handle_detect_dev_afc(struct max77705_muic_data *muic_data, unsigned char *data) +{ + int result = data[1]; + int vbadc = data[2]; + int vbadc2 = (muic_data->status1 & USBC_STATUS1_VBADC_MASK) >> USBC_STATUS1_VBADC_SHIFT; + muic_attached_dev_t new_afc_dev = muic_data->attached_dev; +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + bool noti = true; +#endif +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); + bool afc_err = false; +#endif + bool afc_nack = false; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + bool afc_retry_fail = false; + muic_attached_dev_t current_attached_dev = muic_data->attached_dev; +#endif + int i = 0; + int ret = 0; + + /* W/A: vbadc of opcode result is 0, but vbadc register value is not 0 */ + if (vbadc == 0 && vbadc2 > 0) + vbadc = data[2] = vbadc2; + + pr_info("%s:%s result:0x%x vbadc:0x%x rxbyte:0x%x %x %x %x %x %x %x %x\n", MUIC_DEV_NAME, + __func__, data[1], data[2], data[3], data[4], data[5], + data[6], data[7], data[8], data[9], data[10]); + + switch (result) { + case 0: + pr_info("%s:%s AFC Success, vbadc(%d)\n", MUIC_DEV_NAME, __func__, vbadc); + muic_data->afc_retry = 0; + + if (vbadc >= MAX77705_VBADC_4_5V_TO_5_5V && + vbadc <= MAX77705_VBADC_6_5V_TO_7_5V) { + if (muic_data->pdata->afc_disable) { + pr_info("%s:%s AFC disabled, set cable type to AFC_CHARGER_DISABLED\n", MUIC_DEV_NAME, __func__); + new_afc_dev = ATTACHED_DEV_AFC_CHARGER_DISABLED_MUIC; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + if (muic_data->is_usb_fail) + new_afc_dev = ATTACHED_DEV_RETRY_AFC_CHARGER_5V_MUIC; +#endif + } else { + new_afc_dev = ATTACHED_DEV_AFC_CHARGER_5V_MUIC; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + if (muic_data->is_usb_fail) + new_afc_dev = ATTACHED_DEV_RETRY_AFC_CHARGER_5V_MUIC; +#endif + } + } else if (vbadc >= MAX77705_VBADC_7_5V_TO_8_5V && + vbadc <= MAX77705_VBADC_9_5V_TO_10_5V) { + new_afc_dev = ATTACHED_DEV_AFC_CHARGER_9V_MUIC; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + if (muic_data->is_usb_fail) + new_afc_dev = ATTACHED_DEV_RETRY_AFC_CHARGER_9V_MUIC; +#endif + } +#if defined(CONFIG_USB_HW_PARAM) + else + afc_err = true; +#endif + + if (new_afc_dev != muic_data->attached_dev) { +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + if (muic_data->is_usb_fail) + muic_notifier_detach_attached_dev(muic_data->attached_dev); +#endif + muic_notifier_attach_attached_dev(new_afc_dev); +#endif /* CONFIG_MUIC_NOTIFIER */ + muic_data->attached_dev = new_afc_dev; + } + break; + case 1: + pr_info("%s:%s No CHGIN\n", MUIC_DEV_NAME, __func__); + if (muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_9V_MUIC || +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + muic_data->attached_dev == ATTACHED_DEV_RETRY_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_RETRY_AFC_CHARGER_9V_MUIC || +#endif + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_9V_MUIC) + afc_nack = true; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + else if (muic_data->is_usb_fail) + afc_retry_fail = true; +#endif + break; + case 2: + pr_info("%s:%s Not High Voltage DCP\n", MUIC_DEV_NAME, __func__); + if (muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_9V_MUIC || +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + muic_data->attached_dev == ATTACHED_DEV_RETRY_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_RETRY_AFC_CHARGER_9V_MUIC || +#endif + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_9V_MUIC) + afc_nack = true; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + else if (muic_data->is_usb_fail) + afc_retry_fail = true; +#endif + break; + case 3: + pr_info("%s:%s Not DCP\n", MUIC_DEV_NAME, __func__); + if (muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_9V_MUIC || +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + muic_data->attached_dev == ATTACHED_DEV_RETRY_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_RETRY_AFC_CHARGER_9V_MUIC || +#endif + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_9V_MUIC) + afc_nack = true; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + else if (muic_data->is_usb_fail) + afc_retry_fail = true; +#endif + break; + case 4: + pr_info("%s:%s MPing NACK\n", MUIC_DEV_NAME, __func__); + if (muic_data->pdata->afc_disable) { + pr_info("%s:%s skip checking QC TA by afc disable, just return!\n", MUIC_DEV_NAME, __func__); +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + if (muic_data->is_usb_fail) + afc_retry_fail = true; +#endif + } +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + else if (muic_data->is_usb_fail) { + afc_retry_fail = true; + pr_info("%s:%s skip checking QC TA by usb fail, just return!\n", MUIC_DEV_NAME, __func__); + } +#endif +#if !defined(CONFIG_MUIC_QC_DISABLE) + else { + pr_info("%s:%s checking QC TA!\n", MUIC_DEV_NAME, __func__); + max77705_muic_handle_detect_dev_mpnack(muic_data); + } +#endif + break; + case 5: + pr_info("%s:%s Unsupported TX data\n", MUIC_DEV_NAME, __func__); + if (muic_data->afc_retry++ < RETRY_COUNT) { + pr_info("%s:%s Retry(%d)\n", MUIC_DEV_NAME, __func__, muic_data->afc_retry); + for (i = 3; (i <= 10) && (data[i] != 0); i++) { + if ((muic_data->hv_voltage == 9) && ((data[i] & 0xF0) == 0x40)) { + /* 9V case */ + pr_info("%s:%s seleted tx byte = 0x%02x", MUIC_DEV_NAME, + __func__, data[i]); + max77705_muic_afc_hv_tx_byte_set(muic_data, data[i]); + break; + } else if ((muic_data->hv_voltage == 5) && ((data[i] & 0xF0) == 0x0)) { + /* 5V case */ + pr_info("%s:%s seleted tx byte = 0x%02x", MUIC_DEV_NAME, + __func__, data[i]); + max77705_muic_afc_hv_tx_byte_set(muic_data, data[i]); + break; + } + } + } + break; + case 6: + pr_info("%s:%s Vbus is not changed with 3 continuous ping\n", + MUIC_DEV_NAME, __func__); + afc_nack = true; + break; + case 7: + pr_info("%s:%s Vbus is not changed in 1sec\n", + MUIC_DEV_NAME, __func__); + afc_nack = true; + break; + case 8: + pr_info("%s:%s CC-Vbus Short case\n", MUIC_DEV_NAME, __func__); + + muic_data->attached_dev = ATTACHED_DEV_TA_MUIC; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + if (muic_data->is_usb_fail) { + afc_retry_fail = true; + noti = false; + } +#endif +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + if (noti) + muic_notifier_attach_attached_dev(muic_data->attached_dev); +#endif /* CONFIG_MUIC_NOTIFIER */ +#if IS_ENABLED(CONFIG_SEC_ABC) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=muic@INFO=cable_short"); +#else + sec_abc_send_event("MODULE=muic@WARN=cable_short"); +#endif +#endif + break; + case 9: + pr_info("%s:%s SBU-Gnd Short case\n", MUIC_DEV_NAME, __func__); + + muic_data->attached_dev = ATTACHED_DEV_TA_MUIC; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + if (muic_data->is_usb_fail) { + afc_retry_fail = true; + noti = false; + } +#endif +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + if (noti) + muic_notifier_attach_attached_dev(muic_data->attached_dev); +#endif /* CONFIG_MUIC_NOTIFIER */ +#if IS_ENABLED(CONFIG_SEC_ABC) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=muic@INFO=cable_short"); +#else + sec_abc_send_event("MODULE=muic@WARN=cable_short"); +#endif +#endif + break; + case 10: + pr_info("%s:%s SBU-Vbus Short case\n", MUIC_DEV_NAME, __func__); + + muic_data->attached_dev = ATTACHED_DEV_TA_MUIC; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + if (muic_data->is_usb_fail) { + afc_retry_fail = true; + noti = false; + } +#endif +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + if (noti) + muic_notifier_attach_attached_dev(muic_data->attached_dev); +#endif /* CONFIG_MUIC_NOTIFIER */ +#if IS_ENABLED(CONFIG_SEC_ABC) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=muic@INFO=cable_short"); +#else + sec_abc_send_event("MODULE=muic@WARN=cable_short"); +#endif +#endif + break; + case 11: + pr_info("%s:%s Not Rp 56K\n", MUIC_DEV_NAME, __func__); + if (muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_9V_MUIC || +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + muic_data->attached_dev == ATTACHED_DEV_RETRY_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_RETRY_AFC_CHARGER_9V_MUIC || +#endif + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_9V_MUIC) + afc_nack = true; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + else if (muic_data->is_usb_fail) + afc_retry_fail = true; +#endif + break; + case 16: + pr_info("%s:%s A parity check failed during resceiving data\n", + MUIC_DEV_NAME, __func__); + afc_nack = true; + break; + case 17: + pr_info("%s:%s The slave does not respond to the master ping\n", + MUIC_DEV_NAME, __func__); + afc_nack = true; + break; + case 18: + pr_info("%s:%s RX buffer overflow is detected\n", + MUIC_DEV_NAME, __func__); + afc_nack = true; + break; + default: + pr_info("%s:%s AFC error(%d)\n", MUIC_DEV_NAME, __func__, result); + afc_nack = true; + break; + } + + if (afc_nack) { + if (muic_data->afc_retry++ < RETRY_COUNT) { + pr_info("%s:%s Retry(%d)\n", MUIC_DEV_NAME, __func__, muic_data->afc_retry); +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + /* Charging TG's request, send PREPARE noti */ + if (!muic_data->is_usb_fail) + muic_notifier_attach_attached_dev(ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC); +#endif /* CONFIG_MUIC_NOTIFIER */ + max77705_muic_afc_hv_set(muic_data, muic_data->hv_voltage); + } else { + pr_info("%s:%s Retry Done, do not retry\n", MUIC_DEV_NAME, __func__); + if (vbadc >= MAX77705_VBADC_7_5V_TO_8_5V) { + max77705_muic_afc_reset(muic_data); + muic_data->afc_retry = 0; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + if (muic_data->is_usb_fail) + afc_retry_fail = true; +#endif + } else { +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + /* Send attached device noti to clear prepare noti */ + if (muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_9V_MUIC) + muic_notifier_attach_attached_dev(muic_data->attached_dev); +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + else if (muic_data->is_usb_fail) + afc_retry_fail = true; +#endif + else + muic_notifier_attach_attached_dev(ATTACHED_DEV_AFC_CHARGER_ERR_V_MUIC); +#endif /* CONFIG_MUIC_NOTIFIER */ + } + mutex_lock(&muic_data->afc_lock); + muic_data->pdata->afc_disabled_updated &= MAX77705_MUIC_AFC_DISABLE_CHANGE_DURING_WORK_END; + muic_data->pdata->afc_disabled_updated &= MAX77705_MUIC_AFC_SET_VOLTAGE_CHANGE_DURING_WORK_END; + muic_data->pdata->afc_disabled_updated &= MAX77705_MUIC_AFC_WORK_PROCESS_END; + mutex_unlock(&muic_data->afc_lock); +#if IS_ENABLED(CONFIG_SEC_ABC) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=muic@INFO=afc_hv_fail"); +#else + sec_abc_send_event("MODULE=muic@WARN=afc_hv_fail"); +#endif +#endif + } + } else { + mutex_lock(&muic_data->afc_lock); + if (muic_data->pdata->afc_disabled_updated & MAX77705_MUIC_AFC_DISABLE_CHANGE_DURING_WORK) { + max77705_muic_check_afc_disabled(muic_data); + muic_data->pdata->afc_disabled_updated &= MAX77705_MUIC_AFC_DISABLE_CHANGE_DURING_WORK_END; + } + + muic_data->pdata->afc_disabled_updated &= MAX77705_MUIC_AFC_WORK_PROCESS_END; + + if (muic_data->pdata->afc_disabled_updated & MAX77705_MUIC_AFC_SET_VOLTAGE_CHANGE_DURING_WORK) { + muic_data->pdata->afc_disabled_updated |= MAX77705_MUIC_AFC_WORK_PROCESS; + + ret = __max77705_muic_afc_set_voltage(muic_data, muic_data->reserve_hv_voltage); + if (ret < 0) + muic_data->pdata->afc_disabled_updated &= MAX77705_MUIC_AFC_WORK_PROCESS_END; + + muic_data->pdata->afc_disabled_updated &= MAX77705_MUIC_AFC_SET_VOLTAGE_CHANGE_DURING_WORK_END; + } + mutex_unlock(&muic_data->afc_lock); + } + +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) { + if (muic_data->is_skip_bigdata) + goto afc_out; + + if (afc_err && !afc_nack) + inc_hw_param(o_notify, USB_MUIC_AFC_ERROR_COUNT); + if (afc_nack) { + inc_hw_param(o_notify, USB_MUIC_AFC_ERROR_COUNT); + inc_hw_param(o_notify, USB_MUIC_AFC_NACK_COUNT); + muic_data->is_skip_bigdata = true; + } + } +afc_out: +#endif +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + if (afc_retry_fail) + max77705_muic_handle_afc_retry(muic_data, current_attached_dev); +#endif /* CONFIG_MUIC_AFC_RETRY */ +} + +void max77705_muic_handle_detect_dev_qc(struct max77705_muic_data *muic_data, unsigned char *data) +{ + int result = data[1]; + int vbadc = data[2]; + int vbadc2 = (muic_data->status1 & USBC_STATUS1_VBADC_MASK) >> USBC_STATUS1_VBADC_SHIFT; + muic_attached_dev_t new_afc_dev = muic_data->attached_dev; +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); + bool afc_err = false; +#endif + bool afc_nack = false; + int ret = 0; + + /* W/A: vbadc of opcode result is 0, but vbadc register value is not 0 */ + if (vbadc == 0 && vbadc2 > 0) + vbadc = data[2] = vbadc2; + + pr_info("%s:%s result:0x%x vbadc:0x%x\n", MUIC_DEV_NAME, + __func__, data[1], data[2]); + + switch (result) { + case 0: + pr_info("%s:%s QC2.0 Success, vbadc(%d)\n", MUIC_DEV_NAME, __func__, vbadc); + muic_data->afc_retry = 0; + + if (vbadc >= MAX77705_VBADC_4_5V_TO_5_5V && + vbadc <= MAX77705_VBADC_6_5V_TO_7_5V) + new_afc_dev = ATTACHED_DEV_QC_CHARGER_5V_MUIC; + else if (vbadc >= MAX77705_VBADC_7_5V_TO_8_5V && + vbadc <= MAX77705_VBADC_9_5V_TO_10_5V) + new_afc_dev = ATTACHED_DEV_QC_CHARGER_9V_MUIC; +#if defined(CONFIG_USB_HW_PARAM) + else + afc_err = true; +#endif + + if (new_afc_dev != muic_data->attached_dev) { +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + muic_notifier_attach_attached_dev(new_afc_dev); +#endif /* CONFIG_MUIC_NOTIFIER */ + muic_data->attached_dev = new_afc_dev; + } + break; + case 1: + pr_info("%s:%s No CHGIN\n", MUIC_DEV_NAME, __func__); + if (muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_9V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_9V_MUIC) + afc_nack = true; + break; + case 2: + pr_info("%s:%s Not High Voltage DCP\n", + MUIC_DEV_NAME, __func__); + if (muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_9V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_9V_MUIC) + afc_nack = true; + break; + case 3: + pr_info("%s:%s Not DCP\n", MUIC_DEV_NAME, __func__); + if (muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_9V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_9V_MUIC) + afc_nack = true; + break; + case 6: + pr_info("%s:%s Vbus is not changed with 3 continuous ping\n", + MUIC_DEV_NAME, __func__); + afc_nack = true; + break; + case 7: + pr_info("%s:%s Vbus is not changed in 1 sec\n", + MUIC_DEV_NAME, __func__); + afc_nack = true; + break; + default: + pr_info("%s:%s QC2.0 error(%d)\n", MUIC_DEV_NAME, __func__, result); + afc_nack = true; + break; + } + + if (afc_nack) { + if (muic_data->afc_retry++ < RETRY_COUNT) { + pr_info("%s:%s Retry(%d)\n", MUIC_DEV_NAME, __func__, muic_data->afc_retry); +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + /* Charging TG's request, send PREPARE noti */ + muic_notifier_attach_attached_dev(ATTACHED_DEV_QC_CHARGER_PREPARE_MUIC); +#endif /* CONFIG_MUIC_NOTIFIER */ + max77705_muic_qc_hv_set(muic_data, muic_data->hv_voltage); + } else { + pr_info("%s:%s Retry Done, do not retry\n", MUIC_DEV_NAME, __func__); + if (vbadc >= MAX77705_VBADC_7_5V_TO_8_5V) { + max77705_muic_afc_reset(muic_data); + muic_data->afc_retry = 0; + } else { +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + /* Send attached device noti to clear prepare noti */ + if (muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_9V_MUIC) + muic_notifier_attach_attached_dev(muic_data->attached_dev); + else + muic_notifier_attach_attached_dev(ATTACHED_DEV_QC_CHARGER_ERR_V_MUIC); +#endif /* CONFIG_MUIC_NOTIFIER */ + } + mutex_lock(&muic_data->afc_lock); + muic_data->pdata->afc_disabled_updated &= MAX77705_MUIC_AFC_DISABLE_CHANGE_DURING_WORK_END; + muic_data->pdata->afc_disabled_updated &= MAX77705_MUIC_AFC_SET_VOLTAGE_CHANGE_DURING_WORK_END; + muic_data->pdata->afc_disabled_updated &= MAX77705_MUIC_AFC_WORK_PROCESS_END; + mutex_unlock(&muic_data->afc_lock); +#if IS_ENABLED(CONFIG_SEC_ABC) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=muic@INFO=qc_hv_fail"); +#else + sec_abc_send_event("MODULE=muic@WARN=qc_hv_fail"); +#endif +#endif + } + } else { + mutex_lock(&muic_data->afc_lock); + if (muic_data->pdata->afc_disabled_updated & MAX77705_MUIC_AFC_DISABLE_CHANGE_DURING_WORK) { + max77705_muic_check_afc_disabled(muic_data); + muic_data->pdata->afc_disabled_updated &= MAX77705_MUIC_AFC_DISABLE_CHANGE_DURING_WORK_END; + } + + muic_data->pdata->afc_disabled_updated &= MAX77705_MUIC_AFC_WORK_PROCESS_END; + + if (muic_data->pdata->afc_disabled_updated & MAX77705_MUIC_AFC_SET_VOLTAGE_CHANGE_DURING_WORK) { + muic_data->pdata->afc_disabled_updated |= MAX77705_MUIC_AFC_WORK_PROCESS; + + ret = __max77705_muic_afc_set_voltage(muic_data, muic_data->reserve_hv_voltage); + if (ret < 0) + muic_data->pdata->afc_disabled_updated &= MAX77705_MUIC_AFC_WORK_PROCESS_END; + + muic_data->pdata->afc_disabled_updated &= MAX77705_MUIC_AFC_SET_VOLTAGE_CHANGE_DURING_WORK_END; + } + mutex_unlock(&muic_data->afc_lock); + } + +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) { + if (muic_data->is_skip_bigdata) + goto qc_out; + + if (afc_err && !afc_nack) + inc_hw_param(o_notify, USB_MUIC_AFC_ERROR_COUNT); + if (afc_nack) { + inc_hw_param(o_notify, USB_MUIC_AFC_ERROR_COUNT); + inc_hw_param(o_notify, USB_MUIC_AFC_NACK_COUNT); + muic_data->is_skip_bigdata = true; + } + } +qc_out: + return; +#endif +} diff --git a/drivers/usb/typec/maxim/max77705-muic-ccic.c b/drivers/usb/typec/maxim/max77705-muic-ccic.c new file mode 100755 index 000000000000..42dd3df8bd5c --- /dev/null +++ b/drivers/usb/typec/maxim/max77705-muic-ccic.c @@ -0,0 +1,306 @@ +/* + * muic_ccic.c + * + * Copyright (C) 2014 Samsung Electronics + * Thomas Ryu + * + * 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, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_USB_HOST_NOTIFY +#include +#endif +#include + +#include +#include +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) +#include +#endif + +#if IS_ENABLED(CONFIG_MUIC_SUPPORT_PDIC) +#include +#endif +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) +#include +#endif + +static void max77705_muic_init_ccic_info_data(struct max77705_muic_data *muic_data) +{ + pr_info("%s\n", __func__); + muic_data->ccic_info_data.ccic_evt_rid = RID_OPEN; + muic_data->ccic_info_data.ccic_evt_rprd = 0; + muic_data->ccic_info_data.ccic_evt_roleswap = 0; + muic_data->ccic_info_data.ccic_evt_dcdcnt = 0; + muic_data->ccic_info_data.ccic_evt_attached = MUIC_PDIC_NOTI_UNDEFINED; +} + +static void max77705_muic_handle_ccic_detach(struct max77705_muic_data *muic_data) +{ + pr_info("%s\n", __func__); + muic_data->ccic_info_data.ccic_evt_rprd = 0; + muic_data->ccic_info_data.ccic_evt_roleswap = 0; + muic_data->ccic_info_data.ccic_evt_dcdcnt = 0; + muic_data->ccic_info_data.ccic_evt_attached = MUIC_PDIC_NOTI_DETACH; +} + +static int max77705_muic_handle_ccic_ATTACH(struct max77705_muic_data *muic_data, PD_NOTI_ATTACH_TYPEDEF *pnoti) +{ + bool need_to_run_work = false; + + pr_info("%s: src:%d dest:%d id:%d attach:%d cable_type:%d rprd:%d\n", __func__, + pnoti->src, pnoti->dest, pnoti->id, pnoti->attach, pnoti->cable_type, pnoti->rprd); + + /* Attached */ + if (pnoti->attach) { + pr_info("%s: Attach, cable type=%d\n", __func__, pnoti->cable_type); + + muic_data->ccic_info_data.ccic_evt_attached = MUIC_PDIC_NOTI_ATTACH; + + if (muic_data->ccic_info_data.ccic_evt_roleswap) { + pr_info("%s: roleswap event, attach USB\n", __func__); + muic_data->ccic_info_data.ccic_evt_roleswap = 0; + need_to_run_work = true; + } + + if (pnoti->rprd) { + pr_info("%s: RPRD\n", __func__); + muic_data->ccic_info_data.ccic_evt_rprd = 1; + need_to_run_work = true; + } + + /* CCIC ATTACH means NO WATER */ + if (muic_data->afc_water_disable) { + muic_data->afc_water_disable = false; + muic_data->ccic_evt_id = PDIC_NOTIFY_ID_WATER; + need_to_run_work = true; + } + } else { + if (pnoti->rprd) { + /* Role swap detach: attached=0, rprd=1 */ + pr_info("%s: role swap event\n", __func__); + muic_data->ccic_info_data.ccic_evt_roleswap = 1; + } else { + /* Detached */ + if (muic_data->ccic_info_data.ccic_evt_rprd) + need_to_run_work = true; + max77705_muic_handle_ccic_detach(muic_data); + } + } + + /* run muic event handler */ + if (need_to_run_work) { + pr_info("%s: do workqueue\n", __func__); + schedule_work(&(muic_data->ccic_info_data_work)); + } + + return 0; +} + +static int max77705_muic_handle_ccic_RID(struct max77705_muic_data *muic_data, PD_NOTI_RID_TYPEDEF *pnoti) +{ + int prev_rid = muic_data->ccic_info_data.ccic_evt_rid; + int rid = pnoti->rid; + + pr_info("%s: src:%d dest:%d id:%d rid:%d sub2:%d sub3:%d\n", __func__, + pnoti->src, pnoti->dest, pnoti->id, pnoti->rid, pnoti->sub2, pnoti->sub3); + + if (rid > RID_OPEN || rid <= RID_UNDEFINED) { + pr_info("%s: Out of range of RID(%d)\n", __func__, rid); + return 0; + } + + muic_data->ccic_info_data.ccic_evt_rid = rid; + + switch (rid) { + case RID_000K: + case RID_255K: + case RID_301K: + case RID_523K: + case RID_619K: + case RID_OPEN: + if (prev_rid != rid) { + pr_info("%s: do workqueue\n", __func__); + schedule_work(&(muic_data->ccic_info_data_work)); + } + break; + default: + pr_err("%s:Not determined now\n", __func__); + break; + } + + return 0; +} + +static int max77705_muic_handle_ccic_WATER(struct max77705_muic_data *muic_data, PD_NOTI_ATTACH_TYPEDEF *pnoti) +{ + pr_info("%s: src:%d dest:%d id:%d attach:%d cable_type:%d rprd:%d\n", __func__, + pnoti->src, pnoti->dest, pnoti->id, pnoti->attach, pnoti->cable_type, pnoti->rprd); + + if (pnoti->attach == PDIC_NOTIFY_ATTACH) { + muic_data->afc_water_disable = true; + pr_info("%s: Water detect, do workqueue\n", __func__); + schedule_work(&(muic_data->ccic_info_data_work)); + } else if (pnoti->attach == PDIC_NOTIFY_DETACH) { + muic_data->afc_water_disable = false; + muic_data->ccic_evt_id = PDIC_NOTIFY_ID_WATER; + schedule_work(&(muic_data->ccic_info_data_work)); + pr_info("%s: Dry detect, do workqueue\n", __func__); + } + + return 0; +} + +static void max77705_muic_handle_ccic_usb(struct max77705_muic_data *muic_data, + PD_NOTI_USB_STATUS_TYPEDEF *pnoti) +{ + if (pnoti->attach == PDIC_NOTIFY_ATTACH) { + pr_info("%s attach, return\n", __func__); + return; + } + + switch (muic_data->attached_dev) { + case ATTACHED_DEV_USB_MUIC: + case ATTACHED_DEV_CDP_MUIC: + case ATTACHED_DEV_TIMEOUT_OPEN_MUIC: + muic_data->is_usb_fail = true; +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77705_AFC) && IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + if (max77705_muic_check_is_enable_afc(muic_data, muic_data->attached_dev)) { + pr_info("%s afc work 500ms\n", __func__); + + __pm_wakeup_event(muic_data->afc_retry_ws, 1500); + cancel_delayed_work_sync(&(muic_data->afc_work)); + schedule_delayed_work(&(muic_data->afc_work), msecs_to_jiffies(500)); + } +#endif + break; + default: + break; + } +} + +#if defined(CONFIG_HICCUP_CHARGER) +static int max77705_muic_handle_ccic_hiccup(struct max77705_muic_data *muic_data, PD_NOTI_ATTACH_TYPEDEF *pnoti) +{ + pr_info("%s: src:%d dest:%d id:%d attach:%d cable_type:%d rprd:%d\n", __func__, + pnoti->src, pnoti->dest, pnoti->id, pnoti->attach, pnoti->cable_type, pnoti->rprd); + + if (muic_data->pdata->muic_set_hiccup_mode_cb) { + if (pnoti->attach == PDIC_NOTIFY_ATTACH) + muic_data->pdata->muic_set_hiccup_mode_cb(MUIC_HICCUP_MODE_ON); + else + muic_data->pdata->muic_set_hiccup_mode_cb(MUIC_HICCUP_MODE_OFF); + } + + return 0; +} +#endif +static int max77705_muic_handle_ccic_notification(struct notifier_block *nb, + unsigned long action, void *data) +{ + PD_NOTI_TYPEDEF *pnoti = (PD_NOTI_TYPEDEF *)data; +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + struct max77705_muic_data *muic_data = container_of(nb, struct max77705_muic_data, manager_nb); +#else + struct max77705_muic_data *muic_data = container_of(nb, struct max77705_muic_data, ccic_nb); +#endif + + pr_info("%s: Rcvd Noti=> action: %d src:%d dest:%d id:%d sub[%d %d %d]\n", __func__, + (int)action, pnoti->src, pnoti->dest, pnoti->id, pnoti->sub1, pnoti->sub2, pnoti->sub3); + +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + if (pnoti->dest != PDIC_NOTIFY_DEV_MUIC) { + pr_info("%s destination id is invalid\n", __func__); + return 0; + } +#endif + muic_data->ccic_evt_id = pnoti->id; + + switch (pnoti->id) { + case PDIC_NOTIFY_ID_ATTACH: + pr_info("%s: PDIC_NOTIFY_ID_ATTACH: %s\n", __func__, + pnoti->sub1 ? "Attached" : "Detached"); + max77705_muic_handle_ccic_ATTACH(muic_data, (PD_NOTI_ATTACH_TYPEDEF *)pnoti); + break; + case PDIC_NOTIFY_ID_RID: + pr_info("%s: PDIC_NOTIFY_ID_RID\n", __func__); + max77705_muic_handle_ccic_RID(muic_data, (PD_NOTI_RID_TYPEDEF *)pnoti); + break; + case PDIC_NOTIFY_ID_WATER: + pr_info("%s: PDIC_NOTIFY_ID_WATER\n", __func__); + max77705_muic_handle_ccic_WATER(muic_data, (PD_NOTI_ATTACH_TYPEDEF *)pnoti); + break; + case PDIC_NOTIFY_ID_WATER_CABLE: + pr_info("%s: PDIC_NOTIFY_ID_WATER_CABLE\n", __func__); +#if defined(CONFIG_HICCUP_CHARGER) + max77705_muic_handle_ccic_hiccup(muic_data, (PD_NOTI_ATTACH_TYPEDEF *)pnoti); +#endif + break; + case PDIC_NOTIFY_ID_USB: + pr_info("%s: PDIC_NOTIFY_ID_USB\n", __func__); + max77705_muic_handle_ccic_usb(muic_data, (PD_NOTI_USB_STATUS_TYPEDEF *)pnoti); + break; + default: + pr_info("%s: Undefined Noti. ID\n", __func__); + return NOTIFY_DONE; + } + + return NOTIFY_DONE; +} + +void max77705_muic_register_ccic_notifier(struct max77705_muic_data *muic_data) +{ + int ret = 0; + + pr_info("%s: Registering PDIC_NOTIFY_DEV_MUIC.\n", __func__); + + max77705_muic_init_ccic_info_data(muic_data); +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + ret = manager_notifier_register(&muic_data->manager_nb, + max77705_muic_handle_ccic_notification, MANAGER_NOTIFY_PDIC_MUIC); +#else + ret = ccic_notifier_register(&muic_data->ccic_nb, + max77705_muic_handle_ccic_notification, PDIC_NOTIFY_DEV_MUIC); +#endif + if (ret < 0) { + pr_info("%s: PDIC Noti. is not ready\n", __func__); + return; + } + + pr_info("%s: done.\n", __func__); +} + +void max77705_muic_unregister_ccic_notifier(struct max77705_muic_data *muic_data) +{ + int ret = 0; + +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + ret = manager_notifier_unregister(&muic_data->manager_nb); +#else + ret = ccic_notifier_unregister(&muic_data->ccic_nb); +#endif + if (ret < 0) { + pr_info("%s: PDIC Noti. is not ready\n", __func__); + return; + } + + pr_info("%s: done.\n", __func__); +} diff --git a/drivers/usb/typec/maxim/max77705-muic.c b/drivers/usb/typec/maxim/max77705-muic.c new file mode 100755 index 000000000000..d2147b7823bd --- /dev/null +++ b/drivers/usb/typec/maxim/max77705-muic.c @@ -0,0 +1,2811 @@ +/* + * max77705-muic.c - MUIC driver for the Maxim 77705 + * + * Copyright (C) 2015 Samsung Electronics + * Insun Choi + * + * 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, see . + */ + + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_ANDROID_SWITCH) || IS_ENABLED(CONFIG_SWITCH) +#include +#endif +/* MUIC header file */ +#include +#include +#include +#include +#include +#include + +#if IS_ENABLED(CONFIG_MUIC_SUPPORT_PDIC) +#include +#endif + +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) +#include +#endif /* CONFIG_MUIC_NOTIFIER */ + +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) +#include +#endif /* CONFIG_VBUS_NOTIFIER */ + +#if IS_ENABLED(CONFIG_USB_EXTERNAL_NOTIFY) +#include +#endif + +#include + +#if IS_ENABLED(CONFIG_ARCH_QCOM) && !defined(CONFIG_USB_ARCH_EXYNOS) && !defined(CONFIG_ARCH_EXYNOS) +#include +#endif + +#include + +struct max77705_muic_data *g_muic_data; + +/* for the bringup, should be fixed */ +void __init_usbc_cmd_data(usbc_cmd_data *cmd_data) +{ + pr_warn("%s is not defined!\n", __func__); +} +int __max77705_usbc_opcode_write(struct max77705_usbc_platform_data *usbc_data, + usbc_cmd_data *write_op) +{ + pr_warn("%s is not defined!\n", __func__); + return 0; +} +void init_usbc_cmd_data(usbc_cmd_data *cmd_data) + __attribute__((weak, alias("__init_usbc_cmd_data"))); +int max77705_usbc_opcode_write(struct max77705_usbc_platform_data *usbc_data, + usbc_cmd_data *write_op) + __attribute__((weak, alias("__max77705_usbc_opcode_write"))); + +static bool debug_en_vps; +static void max77705_muic_detect_dev(struct max77705_muic_data *muic_data, int irq); + +struct max77705_muic_vps_data { + int adc; + int vbvolt; + int chgtyp; + int muic_switch; + const char *vps_name; + const muic_attached_dev_t attached_dev; +}; + +static int max77705_muic_read_reg + (struct i2c_client *i2c, u8 reg, u8 *value) +{ + int ret = max77705_read_reg(i2c, reg, value); + + return ret; +} + +#if 0 +static int max77705_muic_write_reg + (struct i2c_client *i2c, u8 reg, u8 value, bool debug_en) +{ + int ret = max77705_write_reg(i2c, reg, value); + + if (debug_en) + pr_info("%s Reg[0x%02x]: 0x%02x\n", + __func__, reg, value); + + return ret; +} +#endif + +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) +static void max77705_muic_handle_vbus(struct max77705_muic_data *muic_data) +{ + int vbvolt = muic_data->status3 & BC_STATUS_VBUSDET_MASK; + vbus_status_t status = (vbvolt > 0) ? + STATUS_VBUS_HIGH : STATUS_VBUS_LOW; + + pr_info("%s <%d>\n", __func__, status); + + vbus_notifier_handle(status); +} +#endif + +static const struct max77705_muic_vps_data muic_vps_table[] = { + { + .adc = MAX77705_UIADC_523K, + .vbvolt = VB_LOW, + .chgtyp = CHGTYP_NO_VOLTAGE, + .muic_switch = COM_UART, + .vps_name = "JIG UART OFF", + .attached_dev = ATTACHED_DEV_JIG_UART_OFF_MUIC, + }, + { + .adc = MAX77705_UIADC_523K, + .vbvolt = VB_HIGH, + .chgtyp = CHGTYP_DONTCARE, + .muic_switch = COM_UART, + .vps_name = "JIG UART OFF/VB", + .attached_dev = ATTACHED_DEV_JIG_UART_OFF_VB_MUIC, + }, + { + .adc = MAX77705_UIADC_619K, + .vbvolt = VB_LOW, + .chgtyp = CHGTYP_NO_VOLTAGE, + .muic_switch = COM_UART, + .vps_name = "JIG UART ON", + .attached_dev = ATTACHED_DEV_JIG_UART_ON_MUIC, + }, + { + .adc = MAX77705_UIADC_619K, + .vbvolt = VB_HIGH, + .chgtyp = CHGTYP_DONTCARE, + .muic_switch = COM_UART, + .vps_name = "JIG UART ON/VB", + .attached_dev = ATTACHED_DEV_JIG_UART_ON_VB_MUIC, + }, + { + .adc = MAX77705_UIADC_255K, +#if IS_ENABLED(CONFIG_SEC_FACTORY) + .vbvolt = VB_DONTCARE, +#else + .vbvolt = VB_HIGH, +#endif + .chgtyp = CHGTYP_DONTCARE, + .muic_switch = COM_USB, + .vps_name = "JIG USB OFF", + .attached_dev = ATTACHED_DEV_JIG_USB_OFF_MUIC, + }, + { + .adc = MAX77705_UIADC_301K, + .vbvolt = VB_HIGH, + .chgtyp = CHGTYP_DONTCARE, + .muic_switch = COM_USB, + .vps_name = "JIG USB ON", + .attached_dev = ATTACHED_DEV_JIG_USB_ON_MUIC, + }, + { + .adc = MAX77705_UIADC_OPEN, + .vbvolt = VB_HIGH, + .chgtyp = CHGTYP_DEDICATED_CHARGER, + .muic_switch = COM_OPEN, + .vps_name = "TA", + .attached_dev = ATTACHED_DEV_TA_MUIC, + }, + { + .adc = MAX77705_UIADC_OPEN, + .vbvolt = VB_HIGH, + .chgtyp = CHGTYP_USB, + .muic_switch = COM_USB, + .vps_name = "USB", + .attached_dev = ATTACHED_DEV_USB_MUIC, + }, + { + .adc = MAX77705_UIADC_OPEN, + .vbvolt = VB_HIGH, + .chgtyp = CHGTYP_TIMEOUT_OPEN, + .muic_switch = COM_USB, + .vps_name = "DCD Timeout", + .attached_dev = ATTACHED_DEV_TIMEOUT_OPEN_MUIC, + }, + { + .adc = MAX77705_UIADC_OPEN, + .vbvolt = VB_HIGH, + .chgtyp = CHGTYP_CDP, + .muic_switch = COM_USB, + .vps_name = "CDP", + .attached_dev = ATTACHED_DEV_CDP_MUIC, + }, + { + .adc = MAX77705_UIADC_GND, + .vbvolt = VB_DONTCARE, + .chgtyp = CHGTYP_DONTCARE, + .muic_switch = COM_USB, + .vps_name = "OTG", + .attached_dev = ATTACHED_DEV_OTG_MUIC, + }, + { + .adc = MAX77705_UIADC_OPEN, + .vbvolt = VB_HIGH, + .chgtyp = CHGTYP_UNOFFICIAL_CHARGER, + .muic_switch = COM_OPEN, + .vps_name = "Unofficial TA", + .attached_dev = ATTACHED_DEV_UNOFFICIAL_TA_MUIC, + }, +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) + { + .adc = MAX77705_UIADC_OPEN, + .vbvolt = VB_HIGH, + .chgtyp = CHGTYP_HICCUP_MODE, + .muic_switch = COM_USB_CP, + .vps_name = "Hiccup mode", + .attached_dev = ATTACHED_DEV_HICCUP_MUIC, + }, +#endif +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77705_AFC) + { + .adc = MAX77705_UIADC_OPEN, + .vbvolt = VB_HIGH, + .chgtyp = CHGTYP_DEDICATED_CHARGER, + .muic_switch = COM_OPEN, + .vps_name = "AFC Charger", + .attached_dev = ATTACHED_DEV_AFC_CHARGER_9V_MUIC, + }, + { + .adc = MAX77705_UIADC_OPEN, + .vbvolt = VB_HIGH, + .chgtyp = CHGTYP_DEDICATED_CHARGER, + .muic_switch = COM_OPEN, + .vps_name = "AFC Charger", + .attached_dev = ATTACHED_DEV_AFC_CHARGER_5V_MUIC, + }, + { + .adc = MAX77705_UIADC_OPEN, + .vbvolt = VB_HIGH, + .chgtyp = CHGTYP_DEDICATED_CHARGER, + .muic_switch = COM_OPEN, + .vps_name = "QC Charger", + .attached_dev = ATTACHED_DEV_QC_CHARGER_9V_MUIC, + }, + { + .adc = MAX77705_UIADC_OPEN, + .vbvolt = VB_HIGH, + .chgtyp = CHGTYP_DEDICATED_CHARGER, + .muic_switch = COM_OPEN, + .vps_name = "QC Charger", + .attached_dev = ATTACHED_DEV_QC_CHARGER_5V_MUIC, + }, +#endif +}; + +static int muic_lookup_vps_table(muic_attached_dev_t new_dev, + struct max77705_muic_data *muic_data) +{ + int i; + struct i2c_client *i2c = muic_data->i2c; + u8 reg_data; + const struct max77705_muic_vps_data *tmp_vps; + + max77705_muic_read_reg(i2c, MAX77705_USBC_REG_USBC_STATUS2, ®_data); + reg_data = reg_data & USBC_STATUS2_SYSMSG_MASK; + pr_info("%s Last sysmsg = 0x%02x\n", __func__, reg_data); + + for (i = 0; i < (int)ARRAY_SIZE(muic_vps_table); i++) { + tmp_vps = &(muic_vps_table[i]); + + if (tmp_vps->attached_dev != new_dev) + continue; + + pr_info("%s (%d) vps table match found at i(%d), %s\n", + __func__, new_dev, i, + tmp_vps->vps_name); + + return i; + } + + pr_info("%s can't find (%d) on vps table\n", + __func__, new_dev); + + return -ENODEV; +} + +static bool muic_check_support_dev(struct max77705_muic_data *muic_data, + muic_attached_dev_t attached_dev) +{ + bool ret = muic_data->muic_support_list[attached_dev]; + + if (debug_en_vps) + pr_info("%s [%c]\n", __func__, ret ? 'T':'F'); + + return ret; +} + +static void max77705_switch_path(struct max77705_muic_data *muic_data, + u8 reg_val) +{ + struct max77705_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + usbc_cmd_data write_data; + + pr_info("%s value(0x%x)\n", __func__, reg_val); + +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + if (muic_data->usbc_pdata->fac_water_enable) { + pr_info("%s fac_water_enable(%d), skip\n", __func__, + muic_data->usbc_pdata->fac_water_enable); + return; + } +#endif + + init_usbc_cmd_data(&write_data); + write_data.opcode = COMMAND_CONTROL1_WRITE; + write_data.write_length = 1; + write_data.write_data[0] = reg_val; + write_data.read_length = 0; + + max77705_usbc_opcode_write(usbc_pdata, &write_data); +} + +static void com_to_open(struct max77705_muic_data *muic_data) +{ + u8 reg_val; + + pr_info("%s\n", __func__); + + reg_val = COM_OPEN; + + /* write command - switch */ + max77705_switch_path(muic_data, reg_val); +} + +static int com_to_usb_ap(struct max77705_muic_data *muic_data) +{ + u8 reg_val; + int ret = 0; + + pr_info("%s\n", __func__); + +#if IS_ENABLED(CONFIG_MUIC_POGO) + if (muic_data->pogo_adc ==ADC_HMT) { + pr_info("%s: pogo adc is 49.9K just return\n", __func__); + return ret; + } +#endif + + reg_val = COM_USB; + + /* write command - switch */ + max77705_switch_path(muic_data, reg_val); + + return ret; +} + +static int com_to_usb_cp(struct max77705_muic_data *muic_data) +{ + u8 reg_val; + int ret = 0; + + pr_info("%s\n", __func__); + + reg_val = COM_USB_CP; + + /* write command - switch */ + max77705_switch_path(muic_data, reg_val); + + return ret; +} + +static void com_to_uart_ap(struct max77705_muic_data *muic_data) +{ + u8 reg_val; +#if IS_ENABLED(CONFIG_MUIC_MAX77705_PDIC) + if ((muic_data->pdata->opmode == OPMODE_MUIC) && muic_data->pdata->rustproof_on) +#else + if (muic_data->pdata->rustproof_on) +#endif + reg_val = COM_OPEN; + else + reg_val = COM_UART; + + pr_info("%s(%d)\n", __func__, reg_val); + + /* write command - switch */ + max77705_switch_path(muic_data, reg_val); +} + +static void com_to_uart_cp(struct max77705_muic_data *muic_data) +{ + u8 reg_val; + +#if IS_ENABLED(CONFIG_MUIC_MAX77705_PDIC) + if ((muic_data->pdata->opmode == OPMODE_MUIC) && muic_data->pdata->rustproof_on) +#else + if (muic_data->pdata->rustproof_on) +#endif + reg_val = COM_OPEN; + else + reg_val = COM_UART_CP; + + pr_info("%s(%d)\n", __func__, reg_val); + + /* write command - switch */ + max77705_switch_path(muic_data, reg_val); +} + +static int write_vps_regs(struct max77705_muic_data *muic_data, + muic_attached_dev_t new_dev) +{ + const struct max77705_muic_vps_data *tmp_vps; + int vps_index; + u8 prev_switch; + + vps_index = muic_lookup_vps_table(muic_data->attached_dev, muic_data); + if (vps_index < 0) { + pr_info("%s: prev cable is none.\n", __func__); + prev_switch = COM_OPEN; + } else { + /* Prev cable information. */ + tmp_vps = &(muic_vps_table[vps_index]); + prev_switch = tmp_vps->muic_switch; + } + + if (prev_switch == muic_data->switch_val) + pr_info("%s Duplicated(0x%02x), just ignore\n", + __func__, muic_data->switch_val); +#if 0 + else { + /* write command - switch */ + max77705_switch_path(muic_data, muic_data->switch_val); + } +#endif + + return 0; +} + +/* muic uart path control function */ +static int switch_to_ap_uart(struct max77705_muic_data *muic_data, + muic_attached_dev_t new_dev) +{ + int ret = 0; + + switch (new_dev) { + case ATTACHED_DEV_JIG_UART_OFF_MUIC: + case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC: + case ATTACHED_DEV_JIG_UART_ON_MUIC: + case ATTACHED_DEV_JIG_UART_ON_VB_MUIC: + com_to_uart_ap(muic_data); + break; + default: + pr_warn("%s current attached is (%d) not Jig UART Off\n", + __func__, muic_data->attached_dev); + break; + } + + return ret; +} + +static int switch_to_cp_uart(struct max77705_muic_data *muic_data, + muic_attached_dev_t new_dev) +{ + int ret = 0; + + switch (new_dev) { + case ATTACHED_DEV_JIG_UART_OFF_MUIC: + case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC: + case ATTACHED_DEV_JIG_UART_ON_MUIC: + case ATTACHED_DEV_JIG_UART_ON_VB_MUIC: + com_to_uart_cp(muic_data); + break; + default: + pr_warn("%s current attached is (%d) not Jig UART Off\n", + __func__, muic_data->attached_dev); + break; + } + + return ret; +} + +void max77705_muic_enable_detecting_short(struct max77705_muic_data *muic_data) +{ + + struct max77705_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + usbc_cmd_data write_data; + + pr_info("%s\n", __func__); + + init_usbc_cmd_data(&write_data); + write_data.opcode = 0x56; + write_data.write_length = 1; + /* + * bit 0: Enable detecting vbus-cc short + * bit 1: Enable detecting sbu-gnd short + * bit 2: Enable detecting vbus-sbu short + */ +#if !defined(CONFIG_SEC_FACTORY) + write_data.write_data[0] = 0x7; +#else + /* W/A, in factory mode, sbu-gnd short disable */ + write_data.write_data[0] = 0x5; +#endif + write_data.read_length = 1; + + max77705_usbc_opcode_write(usbc_pdata, &write_data); + +} + +static void max77705_muic_dp_reset(struct max77705_muic_data *muic_data) +{ + struct max77705_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + usbc_cmd_data update_data; + + pr_info("%s\n", __func__); + + init_usbc_cmd_data(&update_data); + update_data.opcode = COMMAND_BC_CTRL2_READ; + update_data.mask = BC_CTRL2_DPDNMan_MASK | BC_CTRL2_DPDrv_MASK; + update_data.val = 0x10; + + max77705_usbc_opcode_update(usbc_pdata, &update_data); +} + +static void max77705_muic_enable_chgdet(struct max77705_muic_data *muic_data) +{ + struct max77705_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + usbc_cmd_data update_data; + + pr_info("%s\n", __func__); + + init_usbc_cmd_data(&update_data); + update_data.opcode = COMMAND_BC_CTRL1_READ; + update_data.mask = BC_CTRL1_CHGDetEn_MASK | BC_CTRL1_CHGDetMan_MASK; + update_data.val = 0xff; + + max77705_usbc_opcode_update(usbc_pdata, &update_data); +} + +#if defined(CONFIG_MUIC_DISABLE_CHGDET) +static void max77705_muic_disable_chgdet(struct max77705_muic_data *muic_data) +{ + struct max77705_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + usbc_cmd_data update_data; + + pr_info("%s\n", __func__); + + init_usbc_cmd_data(&update_data); + update_data.opcode = COMMAND_BC_CTRL1_READ; + update_data.mask = BC_CTRL1_CHGDetEn_MASK; + update_data.val = 0x0; + + max77705_usbc_opcode_update(usbc_pdata, &update_data); +} +#endif + +static u8 max77705_muic_get_adc_value(struct max77705_muic_data *muic_data) +{ + u8 status; + u8 adc = MAX77705_UIADC_ERROR; + int ret; + + ret = max77705_muic_read_reg(muic_data->i2c, + MAX77705_USBC_REG_USBC_STATUS1, &status); + if (ret) + pr_err("%s fail to read muic reg(%d)\n", + __func__, ret); + else + adc = status & USBC_STATUS1_UIADC_MASK; + + return adc; +} + +static u8 max77705_muic_get_vbadc_value(struct max77705_muic_data *muic_data) +{ + u8 status; + u8 vbadc = 0; + int ret; + + ret = max77705_muic_read_reg(muic_data->i2c, + MAX77705_USBC_REG_USBC_STATUS1, &status); + if (ret) + pr_err("%s fail to read muic reg(%d)\n", + __func__, ret); + else + vbadc = (status & USBC_STATUS1_VBADC_MASK) >> USBC_STATUS1_VBADC_SHIFT; + + return vbadc; +} + +static ssize_t max77705_muic_show_uart_sel(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct max77705_muic_data *muic_data = dev_get_drvdata(dev); + struct muic_platform_data *pdata = muic_data->pdata; + const char *mode = "UNKNOWN\n"; + + switch (pdata->uart_path) { + case MUIC_PATH_UART_AP: + mode = "AP\n"; + break; + case MUIC_PATH_UART_CP: + mode = "CP\n"; + break; + default: + break; + } + + pr_info("%s %s", __func__, mode); + return sprintf(buf, "%s", mode); +} + +static ssize_t max77705_muic_set_uart_sel(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct max77705_muic_data *muic_data = dev_get_drvdata(dev); + struct muic_platform_data *pdata = muic_data->pdata; + + mutex_lock(&muic_data->muic_mutex); + + if (!strncasecmp(buf, "AP", 2)) { + pdata->uart_path = MUIC_PATH_UART_AP; + switch_to_ap_uart(muic_data, muic_data->attached_dev); + } else if (!strncasecmp(buf, "CP", 2)) { + pdata->uart_path = MUIC_PATH_UART_CP; + switch_to_cp_uart(muic_data, muic_data->attached_dev); + } else { + pr_warn("%s invalid value\n", __func__); + } + + pr_info("%s uart_path(%d)\n", __func__, + pdata->uart_path); + + mutex_unlock(&muic_data->muic_mutex); + + return count; +} + +static ssize_t max77705_muic_show_usb_sel(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct max77705_muic_data *muic_data = dev_get_drvdata(dev); + struct muic_platform_data *pdata = muic_data->pdata; + const char *mode = "UNKNOWN\n"; + + switch (pdata->usb_path) { + case MUIC_PATH_USB_AP: + mode = "PDA\n"; + break; + case MUIC_PATH_USB_CP: + mode = "MODEM\n"; + break; + default: + break; + } + + pr_debug("%s %s", __func__, mode); + return sprintf(buf, "%s", mode); +} + +static ssize_t max77705_muic_set_usb_sel(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct max77705_muic_data *muic_data = dev_get_drvdata(dev); + struct muic_platform_data *pdata = muic_data->pdata; + + if (!strncasecmp(buf, "PDA", 3)) + pdata->usb_path = MUIC_PATH_USB_AP; + else if (!strncasecmp(buf, "MODEM", 5)) + pdata->usb_path = MUIC_PATH_USB_CP; + else + pr_warn("%s invalid value\n", __func__); + + pr_info("%s usb_path(%d)\n", __func__, + pdata->usb_path); + + return count; +} + +static ssize_t max77705_muic_show_uart_en(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct max77705_muic_data *muic_data = dev_get_drvdata(dev); + struct muic_platform_data *pdata = muic_data->pdata; + + if (!pdata->rustproof_on) { + pr_info("%s UART ENABLE\n", __func__); + return sprintf(buf, "1\n"); + } + + pr_info("%s UART DISABLE", __func__); + return sprintf(buf, "0\n"); +} + +static ssize_t max77705_muic_set_uart_en(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct max77705_muic_data *muic_data = dev_get_drvdata(dev); + struct muic_platform_data *pdata = muic_data->pdata; + + if (!strncasecmp(buf, "1", 1)) + pdata->rustproof_on = false; + else if (!strncasecmp(buf, "0", 1)) + pdata->rustproof_on = true; + else + pr_warn("%s invalid value\n", __func__); + + pr_info("%s uart_en(%d)\n", __func__, + !pdata->rustproof_on); + + return count; +} + +static ssize_t max77705_muic_show_adc(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct max77705_muic_data *muic_data = dev_get_drvdata(dev); + u8 adc; + + adc = max77705_muic_get_adc_value(muic_data); + pr_info("%s adc(0x%02x)\n", __func__, adc); + + if (adc == MAX77705_UIADC_ERROR) { + pr_err("%s fail to read adc value\n", + __func__); + return sprintf(buf, "UNKNOWN\n"); + } + + switch (adc) { + case MAX77705_UIADC_GND: + adc = 0; + break; + case MAX77705_UIADC_255K: + adc = 0x18; + break; + case MAX77705_UIADC_301K: + adc = 0x19; + break; + case MAX77705_UIADC_523K: + adc = 0x1c; + break; + case MAX77705_UIADC_619K: + adc = 0x1d; + break; + case MAX77705_UIADC_OPEN: + adc = 0x1f; + break; + default: + adc = 0xff; + } + + return sprintf(buf, "adc: 0x%x\n", adc); +} + +static ssize_t max77705_muic_show_usb_state(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct max77705_muic_data *muic_data = dev_get_drvdata(dev); + + pr_debug("%s attached_dev(%d)\n", __func__, + muic_data->attached_dev); + + switch (muic_data->attached_dev) { + case ATTACHED_DEV_USB_MUIC: + return sprintf(buf, "USB_STATE_CONFIGURED\n"); + default: + break; + } + + return sprintf(buf, "USB_STATE_NOTCONFIGURED\n"); +} + +static ssize_t max77705_muic_show_attached_dev(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct max77705_muic_data *muic_data = dev_get_drvdata(dev); + const struct max77705_muic_vps_data *tmp_vps; + int vps_index; + + vps_index = muic_lookup_vps_table(muic_data->attached_dev, muic_data); + if (vps_index < 0) { +#if IS_ENABLED(CONFIG_MUIC_POGO) + if (muic_data->pogo_adc == ADC_HMT) + return sprintf(buf, "POGO Dock 49.9K\n"); + else if (muic_data->pogo_adc == ADC_INCOMPATIBLE_VZW) + return sprintf(buf, "POGO Dock 34K\n"); +#endif /* CONFIG_MUIC_POGO */ + return sprintf(buf, "No VPS\n"); + } + + tmp_vps = &(muic_vps_table[vps_index]); + +#if IS_ENABLED(CONFIG_MUIC_POGO) + if (muic_data->pogo_adc == ADC_HMT) + return sprintf(buf, "POGO Dock 49.9K+%s\n", tmp_vps->vps_name); + else if (muic_data->pogo_adc == ADC_INCOMPATIBLE_VZW) + return sprintf(buf, "POGO Dock 34K+%s\n", tmp_vps->vps_name); +#endif /* CONFIG_MUIC_POGO */ + + return sprintf(buf, "%s\n", tmp_vps->vps_name); +} + +static ssize_t max77705_muic_show_otg_test(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct max77705_muic_data *muic_data = dev_get_drvdata(dev); + int ret = -ENODEV; + + if (muic_check_support_dev(muic_data, ATTACHED_DEV_OTG_MUIC)) { + if (muic_data->is_otg_test == true) + ret = 0; + else + ret = 1; + pr_info("%s ret:%d buf:%s\n", __func__, ret, buf); + } + + return ret; +} + +static ssize_t max77705_muic_set_otg_test(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct max77705_muic_data *muic_data = dev_get_drvdata(dev); + int ret = -ENODEV; + + mutex_lock(&muic_data->muic_mutex); + + if (muic_check_support_dev(muic_data, ATTACHED_DEV_OTG_MUIC)) { + pr_info("%s buf:%s\n", __func__, buf); + if (!strncmp(buf, "0", 1)) { + muic_data->is_otg_test = true; + ret = 0; + } else if (!strncmp(buf, "1", 1)) { + muic_data->is_otg_test = false; + ret = 1; + } else { + pr_warn("%s Wrong command\n", __func__); + mutex_unlock(&muic_data->muic_mutex); + return count; + } + + pr_info("%s ret: %d\n", __func__, ret); + + mutex_unlock(&muic_data->muic_mutex); + return count; + } + + mutex_unlock(&muic_data->muic_mutex); + return ret; +} + +static ssize_t max77705_muic_show_apo_factory(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct max77705_muic_data *muic_data = dev_get_drvdata(dev); + const char *mode; + + /* true: Factory mode, false: not Factory mode */ + if (muic_data->is_factory_start) + mode = "FACTORY_MODE"; + else + mode = "NOT_FACTORY_MODE"; + + pr_info("%s apo factory=%s\n", __func__, mode); + + return sprintf(buf, "%s\n", mode); +} + +static ssize_t max77705_muic_set_apo_factory(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ +#if IS_ENABLED(CONFIG_SEC_FACTORY) + struct max77705_muic_data *muic_data = dev_get_drvdata(dev); +#endif /* CONFIG_SEC_FACTORY */ + const char *mode; + + pr_info("%s buf:%s\n", __func__, buf); + + /* "FACTORY_START": factory mode */ + if (!strncmp(buf, "FACTORY_START", 13)) { +#if IS_ENABLED(CONFIG_SEC_FACTORY) + muic_data->is_factory_start = true; +#endif /* CONFIG_SEC_FACTORY */ + mode = "FACTORY_MODE"; + } else { + pr_warn("%s Wrong command\n", __func__); + return count; + } + + pr_info("%s apo factory=%s\n", __func__, mode); + + return count; +} + +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77705_AFC) || defined(CONFIG_SUPPORT_QC30) +static ssize_t max77705_muic_show_afc_disable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct max77705_muic_data *muic_data = dev_get_drvdata(dev); + struct muic_platform_data *pdata = muic_data->pdata; + + if (pdata->afc_disable) { + pr_info("%s AFC DISABLE\n", __func__); + return sprintf(buf, "1\n"); + } + + pr_info("%s AFC ENABLE", __func__); + return sprintf(buf, "0\n"); +} + +static ssize_t max77705_muic_set_afc_disable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct max77705_muic_data *muic_data = dev_get_drvdata(dev); + struct muic_platform_data *pdata = muic_data->pdata; + int param_val, ret = 0; + bool curr_val = pdata->afc_disable; + union power_supply_propval psy_val; + + if (!strncasecmp(buf, "1", 1)) { + /* Disable AFC */ + pdata->afc_disable = true; + muic_afc_request_cause_clear(); + } else if (!strncasecmp(buf, "0", 1)) { + /* Enable AFC */ + pdata->afc_disable = false; + } else { + pr_warn("%s invalid value\n", __func__); + } + + param_val = pdata->afc_disable ? '1' : '0'; + pr_info("%s: param_val:%d\n", __func__, param_val); + + if (ret < 0) { + pr_info("%s:set_param failed - %02x:%02x(%d)\n", __func__, + param_val, curr_val, ret); + + pdata->afc_disable = curr_val; + + return -EIO; + } else { + mutex_lock(&muic_data->afc_lock); + pr_info("%s: afc_disable:%d (AFC %s)\n", __func__, + pdata->afc_disable, pdata->afc_disable ? "Disabled" : "Enabled"); + + if (pdata->afc_disabled_updated & MAX77705_MUIC_AFC_WORK_PROCESS) + pdata->afc_disabled_updated |= MAX77705_MUIC_AFC_DISABLE_CHANGE_DURING_WORK; + else + max77705_muic_check_afc_disabled(muic_data); + mutex_unlock(&muic_data->afc_lock); + } + + pr_info("%s afc_disable(%d)\n", __func__, pdata->afc_disable); + + psy_val.intval = param_val; + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_HV_DISABLE, psy_val); + + return count; +} +#endif /* CONFIG_HV_MUIC_MAX77705_AFC */ + +static ssize_t max77705_muic_show_vbus_value(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct max77705_muic_data *muic_data = dev_get_drvdata(dev); + u8 vbadc; + + vbadc = max77705_muic_get_vbadc_value(muic_data); + pr_info("%s vbadc(0x%02x)\n", __func__, vbadc); + + switch (vbadc) { + case MAX77705_VBADC_3_8V_UNDER: + vbadc = 0; + break; + case MAX77705_VBADC_3_8V_TO_4_5V ... MAX77705_VBADC_6_5V_TO_7_5V: + vbadc = 5; + break; + case MAX77705_VBADC_7_5V_TO_8_5V ... MAX77705_VBADC_8_5V_TO_9_5V: + vbadc = 9; + break; + default: + vbadc += 3; + } + + return sprintf(buf, "%d\n", vbadc); +} + +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) +static ssize_t hiccup_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "ENABLE\n"); +} + +static ssize_t hiccup_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct max77705_muic_data *muic_data = dev_get_drvdata(dev); + + if (!strncasecmp(buf, "DISABLE", 7)) { + pr_info("%s\n", __func__); + pdic_manual_ccopen_request(0); + com_to_open(muic_data); + muic_data->is_hiccup_mode = MUIC_HICCUP_MODE_OFF; + } else + pr_warn("%s invalid com : %s\n", __func__, buf); + + return count; +} +#endif /* CONFIG_HICCUP_CHARGER */ + +static DEVICE_ATTR(uart_sel, 0664, max77705_muic_show_uart_sel, + max77705_muic_set_uart_sel); +static DEVICE_ATTR(usb_sel, 0664, max77705_muic_show_usb_sel, + max77705_muic_set_usb_sel); +static DEVICE_ATTR(uart_en, 0660, max77705_muic_show_uart_en, + max77705_muic_set_uart_en); +static DEVICE_ATTR(adc, 0444, max77705_muic_show_adc, NULL); +static DEVICE_ATTR(usb_state, 0444, max77705_muic_show_usb_state, NULL); +static DEVICE_ATTR(attached_dev, 0444, max77705_muic_show_attached_dev, NULL); +static DEVICE_ATTR(otg_test, 0664, + max77705_muic_show_otg_test, max77705_muic_set_otg_test); +static DEVICE_ATTR(apo_factory, 0664, + max77705_muic_show_apo_factory, max77705_muic_set_apo_factory); +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77705_AFC) +static DEVICE_ATTR(afc_disable, 0664, + max77705_muic_show_afc_disable, max77705_muic_set_afc_disable); +#endif /* CONFIG_HV_MUIC_MAX77705_AFC */ +static DEVICE_ATTR(vbus_value, 0444, max77705_muic_show_vbus_value, NULL); +static DEVICE_ATTR(vbus_value_pd, 0444, max77705_muic_show_vbus_value, NULL); +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) +static DEVICE_ATTR_RW(hiccup); +#endif /* CONFIG_HICCUP_CHARGER */ + +static struct attribute *max77705_muic_attributes[] = { + &dev_attr_uart_sel.attr, + &dev_attr_usb_sel.attr, + &dev_attr_uart_en.attr, + &dev_attr_adc.attr, + &dev_attr_usb_state.attr, + &dev_attr_attached_dev.attr, + &dev_attr_otg_test.attr, + &dev_attr_apo_factory.attr, +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77705_AFC) + &dev_attr_afc_disable.attr, +#endif /* CONFIG_HV_MUIC_MAX77705_AFC */ + &dev_attr_vbus_value.attr, + &dev_attr_vbus_value_pd.attr, +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) + &dev_attr_hiccup.attr, +#endif /* CONFIG_HICCUP_CHARGER */ + NULL +}; + +static const struct attribute_group max77705_muic_group = { + .attrs = max77705_muic_attributes, +}; + +void max77705_muic_read_register(struct i2c_client *i2c) +{ + const enum max77705_usbc_reg regfile[] = { + MAX77705_USBC_REG_UIC_HW_REV, + MAX77705_USBC_REG_USBC_STATUS1, + MAX77705_USBC_REG_USBC_STATUS2, + MAX77705_USBC_REG_BC_STATUS, + MAX77705_USBC_REG_UIC_INT_M, + }; + u8 val; + int i, ret; + + pr_info("%s read register--------------\n", __func__); + for (i = 0; i < (int)ARRAY_SIZE(regfile); i++) { + ret = max77705_muic_read_reg(i2c, regfile[i], &val); + if (ret) { + pr_err("%s fail to read muic reg(0x%02x), ret=%d\n", + __func__, regfile[i], ret); + continue; + } + + pr_info("%s reg(0x%02x)=[0x%02x]\n", + __func__, regfile[i], val); + } + pr_info("%s end register---------------\n", __func__); +} + +static int max77705_muic_attach_uart_path(struct max77705_muic_data *muic_data, + muic_attached_dev_t new_dev) +{ + struct muic_platform_data *pdata = muic_data->pdata; + int ret = 0; + + pr_info("%s\n", __func__); + + if (pdata->uart_path == MUIC_PATH_UART_AP) + ret = switch_to_ap_uart(muic_data, new_dev); + else if (pdata->uart_path == MUIC_PATH_UART_CP) + ret = switch_to_cp_uart(muic_data, new_dev); + else + pr_warn("%s invalid uart_path\n", __func__); + + return ret; +} + +static int max77705_muic_attach_usb_path(struct max77705_muic_data *muic_data, + muic_attached_dev_t new_dev) +{ + struct muic_platform_data *pdata = muic_data->pdata; + int ret = 0; + + pr_info("%s usb_path=%d\n", __func__, pdata->usb_path); + + if (pdata->usb_path == MUIC_PATH_USB_AP) + ret = com_to_usb_ap(muic_data); + else if (pdata->usb_path == MUIC_PATH_USB_CP) + ret = com_to_usb_cp(muic_data); + else + pr_warn("%s invalid usb_path\n", __func__); + + return ret; +} + +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77705_AFC) +void max77705_muic_disable_afc_protocol(struct max77705_muic_data *muic_data) +{ + struct i2c_client *pmic_i2c = muic_data->usbc_pdata->max77705->i2c; + struct i2c_client *debug_i2c = muic_data->usbc_pdata->max77705->debug; + + pr_info("%s\n", __func__); + + /* + * MAXIM's request. + * This is workaround of D- high during AFC charging issue, + * set hidden register. + */ + max77705_write_reg(pmic_i2c, 0xFE, 0xC5); /* Unlock TKEY */ + max77705_write_reg(debug_i2c, 0x0F, 0x04); /* Force FC clock always ON */ + max77705_write_reg(debug_i2c, 0x0F, 0x00); /* Force FC clock always OFF */ + max77705_write_reg(pmic_i2c, 0xFE, 0x00); /* Lock TKEY */ +} +#endif + +static int max77705_muic_handle_detach(struct max77705_muic_data *muic_data, int irq) +{ + int ret = 0; +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + bool noti = true; + muic_attached_dev_t attached_dev = muic_data->attached_dev; +#endif /* CONFIG_MUIC_NOTIFIER */ + +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77705_AFC) + /* Do workaround if Vbusdet goes to low status */ + if (muic_data->is_check_hv && irq == muic_data->irq_vbusdet && + (muic_data->status3 & BC_STATUS_VBUSDET_MASK) == 0) { + muic_data->is_check_hv = false; + max77705_muic_disable_afc_protocol(muic_data); + } +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) + muic_data->is_hiccup_mode = MUIC_HICCUP_MODE_OFF; +#endif + muic_data->hv_voltage = 0; + muic_data->afc_retry = 0; + muic_data->is_afc_reset = false; + muic_data->is_skip_bigdata = false; + muic_data->is_usb_fail = false; + cancel_delayed_work_sync(&(muic_data->afc_work)); + muic_data->pdata->afc_disabled_updated = MAX77705_MUIC_AFC_STATUS_CLEAR; + muic_afc_request_cause_clear(); +#endif + + if (muic_data->attached_dev == ATTACHED_DEV_NONE_MUIC) { + pr_info("%s Duplicated(%d), just ignore\n", + __func__, muic_data->attached_dev); + goto out_without_noti; + } + + muic_data->dcdtmo_retry = 0; +#if 0 + /* Enable Charger Detection */ + max77705_muic_enable_chgdet(muic_data); +#endif + + muic_lookup_vps_table(muic_data->attached_dev, muic_data); + + switch (muic_data->attached_dev) { + case ATTACHED_DEV_TA_MUIC: + case ATTACHED_DEV_UNOFFICIAL_TA_MUIC: + case ATTACHED_DEV_AFC_CHARGER_5V_MUIC: + case ATTACHED_DEV_AFC_CHARGER_9V_MUIC: + case ATTACHED_DEV_QC_CHARGER_5V_MUIC: + case ATTACHED_DEV_QC_CHARGER_9V_MUIC: + if ((muic_data->status3 & BC_STATUS_VBUSDET_MASK) > 0) { + /* W/A for chgtype 0 irq when CC pin is only detached */ + pr_info("%s Vbus is high, keep the current state(%d)\n", __func__, + muic_data->attached_dev); + return 0; + } + break; + case ATTACHED_DEV_JIG_UART_OFF_MUIC: + case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC: + case ATTACHED_DEV_JIG_UART_OFF_VB_OTG_MUIC: + case ATTACHED_DEV_JIG_UART_OFF_VB_FG_MUIC: + case ATTACHED_DEV_JIG_UART_ON_MUIC: + case ATTACHED_DEV_JIG_UART_ON_VB_MUIC: + case ATTACHED_DEV_JIG_USB_OFF_MUIC: + case ATTACHED_DEV_JIG_USB_ON_MUIC: + case ATTACHED_DEV_NONE_MUIC: + com_to_open(muic_data); + break; + case ATTACHED_DEV_USB_MUIC: + case ATTACHED_DEV_CDP_MUIC: + case ATTACHED_DEV_OTG_MUIC: + case ATTACHED_DEV_TIMEOUT_OPEN_MUIC: + if (muic_data->ccic_info_data.ccic_evt_attached == MUIC_PDIC_NOTI_DETACH) + com_to_open(muic_data); + break; + case ATTACHED_DEV_UNOFFICIAL_ID_MUIC: + goto out_without_noti; + default: + break; + } + +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + if (noti) { + muic_data->attached_dev = ATTACHED_DEV_NONE_MUIC; + muic_notifier_detach_attached_dev(attached_dev); + } +#endif /* CONFIG_MUIC_NOTIFIER */ + +out_without_noti: + muic_data->attached_dev = ATTACHED_DEV_NONE_MUIC; + + return ret; +} + +static int max77705_muic_logically_detach(struct max77705_muic_data *muic_data, + muic_attached_dev_t new_dev) +{ +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + bool noti = true; +#endif /* CONFIG_MUIC_NOTIFIER */ + bool force_path_open = true; + int ret = 0; + + switch (muic_data->attached_dev) { + case ATTACHED_DEV_OTG_MUIC: + break; + case ATTACHED_DEV_USB_MUIC: + case ATTACHED_DEV_CDP_MUIC: + case ATTACHED_DEV_TIMEOUT_OPEN_MUIC: + if (new_dev == ATTACHED_DEV_OTG_MUIC) { + pr_info("%s: data role changed, not detach\n", __func__); + force_path_open = false; + goto out; + } + break; + case ATTACHED_DEV_UNDEFINED_CHARGING_MUIC: + case ATTACHED_DEV_JIG_USB_OFF_MUIC: + case ATTACHED_DEV_JIG_USB_ON_MUIC: + break; + case ATTACHED_DEV_JIG_UART_OFF_MUIC: + case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC: + case ATTACHED_DEV_JIG_UART_ON_MUIC: + case ATTACHED_DEV_JIG_UART_ON_VB_MUIC: + case ATTACHED_DEV_UNKNOWN_MUIC: + if (new_dev == ATTACHED_DEV_JIG_UART_OFF_MUIC || + new_dev == ATTACHED_DEV_JIG_UART_OFF_VB_MUIC || + new_dev == ATTACHED_DEV_JIG_UART_ON_MUIC || + new_dev == ATTACHED_DEV_JIG_UART_ON_VB_MUIC) + force_path_open = false; + break; + case ATTACHED_DEV_TA_MUIC: +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77705_AFC) + case ATTACHED_DEV_AFC_CHARGER_9V_MUIC: + case ATTACHED_DEV_AFC_CHARGER_5V_MUIC: + case ATTACHED_DEV_QC_CHARGER_9V_MUIC: + case ATTACHED_DEV_QC_CHARGER_5V_MUIC: +#endif +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) + if (new_dev == ATTACHED_DEV_HICCUP_MUIC) { + pr_info("%s hiccup charger, do not logically detach\n", __func__); + force_path_open = false; + goto out; + } +#endif + break; + case ATTACHED_DEV_UNOFFICIAL_TA_MUIC: + break; +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) + case ATTACHED_DEV_HICCUP_MUIC: + break; +#endif /* CONFIG_HICCUP_CHARGER */ + case ATTACHED_DEV_NONE_MUIC: + force_path_open = false; + goto out; + default: + pr_warn("%s try to attach without logically detach\n", + __func__); + goto out; + } + + pr_info("%s attached(%d)!=new(%d), assume detach\n", + __func__, muic_data->attached_dev, new_dev); + +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + if (noti) { + muic_notifier_detach_attached_dev(muic_data->attached_dev); + muic_data->attached_dev = ATTACHED_DEV_NONE_MUIC; + } +#endif /* CONFIG_MUIC_NOTIFIER */ + +out: + if (force_path_open) + com_to_open(muic_data); + + return ret; +} + +static int max77705_muic_handle_attach(struct max77705_muic_data *muic_data, + muic_attached_dev_t new_dev, int irq) +{ +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + bool notify_skip = false; +#endif /* CONFIG_MUIC_NOTIFIER */ +#if IS_ENABLED(CONFIG_CCIC_MAX77705) + int fw_update_state = muic_data->usbc_pdata->max77705->fw_update_state; +#endif /* CONFIG_CCIC_MAX77705 */ +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif /* CONFIG_USB_HW_PARAM */ + int ret = 0; + + pr_info("%s\n", __func__); + + if (new_dev == muic_data->attached_dev) { + if (new_dev == ATTACHED_DEV_OTG_MUIC) { + /* W/A for setting usb path */ + pr_info("%s:%s Duplicated(%d), Not ignore\n", + MUIC_DEV_NAME, __func__, muic_data->attached_dev); + goto handle_attach; + } + + if (new_dev == ATTACHED_DEV_HICCUP_MUIC) + goto handle_attach; + + pr_info("%s Duplicated(%d), just ignore\n", + __func__, muic_data->attached_dev); + return ret; + } + + ret = max77705_muic_logically_detach(muic_data, new_dev); + if (ret) + pr_warn("%s fail to logically detach(%d)\n", + __func__, ret); + +handle_attach: + switch (new_dev) { + case ATTACHED_DEV_OTG_MUIC: + ret = com_to_usb_ap(muic_data); + break; + case ATTACHED_DEV_JIG_UART_OFF_MUIC: + case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC: + ret = max77705_muic_attach_uart_path(muic_data, new_dev); + break; + case ATTACHED_DEV_JIG_UART_ON_MUIC: + case ATTACHED_DEV_JIG_UART_ON_VB_MUIC: + ret = max77705_muic_attach_uart_path(muic_data, new_dev); + break; + case ATTACHED_DEV_TA_MUIC: + case ATTACHED_DEV_UNDEFINED_CHARGING_MUIC: + case ATTACHED_DEV_UNOFFICIAL_TA_MUIC: + ret = write_vps_regs(muic_data, new_dev); + break; + case ATTACHED_DEV_JIG_USB_ON_MUIC: + case ATTACHED_DEV_JIG_USB_OFF_MUIC: + case ATTACHED_DEV_USB_MUIC: + case ATTACHED_DEV_CDP_MUIC: + ret = max77705_muic_attach_usb_path(muic_data, new_dev); + break; + case ATTACHED_DEV_TIMEOUT_OPEN_MUIC: + pr_info("%s DCD_TIMEOUT system_state = 0x%x\n", __func__, system_state); +#if IS_ENABLED(CONFIG_CCIC_MAX77705) + if (fw_update_state == FW_UPDATE_END && system_state < SYSTEM_RUNNING) { + /* TA Reset, D+ gnd */ + max77705_muic_dp_reset(muic_data); + + max77705_muic_enable_chgdet(muic_data); + goto out; + } +#endif /* CONFIG_CCIC_MAX77705 */ + ret = max77705_muic_attach_usb_path(muic_data, new_dev); + break; +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) + case ATTACHED_DEV_HICCUP_MUIC: + ret = com_to_usb_cp(muic_data); + if (!(muic_data->status3 & BC_STATUS_VBUSDET_MASK)) + notify_skip = true; + break; +#endif /* CONFIG_HICCUP_CHARGER */ + default: + pr_warn("%s unsupported dev(%d)\n", __func__, + new_dev); + ret = -ENODEV; + goto out; + } + +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + if (notify_skip) { + pr_info("%s: noti\n", __func__); + } else { + muic_notifier_attach_attached_dev(new_dev); + muic_data->attached_dev = new_dev; + } +#endif /* CONFIG_MUIC_NOTIFIER */ + + muic_data->attached_dev = new_dev; + +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77705_AFC) + if (max77705_muic_check_is_enable_afc(muic_data, new_dev)) { + /* Maxim's request, wait 500ms for checking HVDCP */ + pr_info("%s afc work after 500ms\n", __func__); + cancel_delayed_work_sync(&(muic_data->afc_work)); + schedule_delayed_work(&(muic_data->afc_work), msecs_to_jiffies(500)); + } +#endif /* CONFIG_HV_MUIC_MAX77705_AFC */ + +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify && + muic_data->bc1p2_retry_count >= 2 && + muic_data->dcdtmo_retry > 0 && + muic_data->dcdtmo_retry < muic_data->bc1p2_retry_count && + new_dev != ATTACHED_DEV_TIMEOUT_OPEN_MUIC) + inc_hw_param(o_notify, USB_MUIC_BC12_RETRY_SUCCESS_COUNT); +#endif /* CONFIG_USB_HW_PARAM */ + +out: + return ret; +} + +static bool muic_check_vps_adc + (const struct max77705_muic_vps_data *tmp_vps, u8 adc) +{ + bool ret = false; + + if (tmp_vps->adc == adc) { + ret = true; + goto out; + } + + if (tmp_vps->adc == MAX77705_UIADC_DONTCARE) + ret = true; + +out: + if (debug_en_vps) { + pr_info("%s vps(%s) adc(0x%02x) ret(%c)\n", + __func__, tmp_vps->vps_name, + adc, ret ? 'T' : 'F'); + } + + return ret; +} + +static bool muic_check_vps_vbvolt(const struct max77705_muic_vps_data *tmp_vps, + u8 vbvolt) +{ + bool ret = false; + + if (tmp_vps->vbvolt == vbvolt) { + ret = true; + goto out; + } + + if (tmp_vps->vbvolt == VB_DONTCARE) + ret = true; + +out: + if (debug_en_vps) { + pr_debug("%s vps(%s) vbvolt(0x%02x) ret(%c)\n", + __func__, tmp_vps->vps_name, + vbvolt, ret ? 'T' : 'F'); + } + + return ret; +} + +static bool muic_check_vps_chgtyp(const struct max77705_muic_vps_data *tmp_vps, + u8 chgtyp) +{ + bool ret = false; + + if (tmp_vps->chgtyp == chgtyp) { + ret = true; + goto out; + } + + if (tmp_vps->chgtyp == CHGTYP_ANY) { + if (chgtyp > CHGTYP_NO_VOLTAGE) { + ret = true; + goto out; + } + } + + if (tmp_vps->chgtyp == CHGTYP_DONTCARE) + ret = true; + +out: + if (debug_en_vps) { + pr_info("%s vps(%s) chgtyp(0x%02x) ret(%c)\n", + __func__, tmp_vps->vps_name, + chgtyp, ret ? 'T' : 'F'); + } + + return ret; +} + +#if IS_ENABLED(CONFIG_MUIC_MAX77705_PDIC) +static u8 max77705_muic_update_adc_with_rid(struct max77705_muic_data *muic_data, + u8 adc) +{ + u8 new_adc = adc; + + if (muic_data->pdata->opmode & OPMODE_PDIC) { + switch (muic_data->ccic_info_data.ccic_evt_rid) { + case RID_000K: + new_adc = MAX77705_UIADC_GND; + break; + case RID_255K: + new_adc = MAX77705_UIADC_255K; + break; + case RID_301K: + new_adc = MAX77705_UIADC_301K; + break; + case RID_523K: + new_adc = MAX77705_UIADC_523K; + break; + case RID_619K: + new_adc = MAX77705_UIADC_619K; + break; + default: + new_adc = MAX77705_UIADC_OPEN; + break; + } + + if (muic_data->ccic_info_data.ccic_evt_rprd) + new_adc = MAX77705_UIADC_GND; + + pr_info("%s: adc(0x%x->0x%x) rid(%d) rprd(%d)\n", + __func__, adc, new_adc, + muic_data->ccic_info_data.ccic_evt_rid, + muic_data->ccic_info_data.ccic_evt_rprd); + } + + return new_adc; +} +#endif /* CONFIG_MUIC_MAX77705_PDIC */ + +static u8 max77705_resolve_chgtyp(struct max77705_muic_data *muic_data, u8 chgtyp, + u8 spchgtyp, u8 dcdtmo, int irq) +{ + u8 ret = chgtyp; + u8 ccistat = 0; + + max77705_read_reg(muic_data->i2c, REG_CC_STATUS0, &ccistat); + ccistat = (ccistat & BIT_CCIStat) >> FFS(BIT_CCIStat); + +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) + /* Check hiccup mode */ + if (muic_data->is_hiccup_mode > MUIC_HICCUP_MODE_OFF) { + pr_info("%s is_hiccup_mode(%d)\n", __func__, muic_data->is_hiccup_mode); + return CHGTYP_HICCUP_MODE; + } +#endif /* CONFIG_HICCUP_CHARGER */ + +#if IS_ENABLED(CONFIG_MUIC_MAX77705_PDIC) + /* Check chgtype and ccic attach don't exist */ + if (irq == MUIC_IRQ_VBUS_WA && chgtyp == CHGTYP_NO_VOLTAGE && spchgtyp == CHGTYP_NO_VOLTAGE + && muic_data->ccic_info_data.ccic_evt_attached != MUIC_PDIC_NOTI_ATTACH) { + ret = CHGTYP_TIMEOUT_OPEN; + goto out; + } +#endif /* CONFIG_MUIC_MAX77705_PDIC */ + + /* Check DCD timeout */ + if (dcdtmo && chgtyp == CHGTYP_USB && + (irq == muic_data->irq_chgtyp || irq == MUIC_IRQ_INIT_DETECT)) { + if (irq == MUIC_IRQ_INIT_DETECT) { + ret = CHGTYP_TIMEOUT_OPEN; + + if (ccistat == CCI_500mA) { + ret = CHGTYP_NO_VOLTAGE; + muic_data->dcdtmo_retry = muic_data->bc1p2_retry_count; + pr_info("%s: DCD_TIMEOUT retry in init\n", __func__); + max77705_muic_enable_chgdet(muic_data); + } + } else { + ret = (muic_data->dcdtmo_retry >= muic_data->bc1p2_retry_count) ? CHGTYP_TIMEOUT_OPEN : CHGTYP_NO_VOLTAGE; + } + goto out; + } + + /* Check Special chgtyp */ + switch (spchgtyp) { + case PRCHGTYP_SAMSUNG_2A: + case PRCHGTYP_APPLE_500MA: + case PRCHGTYP_APPLE_1A: + case PRCHGTYP_APPLE_2A: + case PRCHGTYP_APPLE_12W: + if (chgtyp == CHGTYP_USB || chgtyp == CHGTYP_CDP) { + ret = CHGTYP_UNOFFICIAL_CHARGER; + goto out; + } + break; + default: + break; + } + +out: + if (ret != chgtyp) + pr_info("%s chgtyp(0x%x) spchgtyp(0x%x) dcdtmo(0x%x) -> chgtyp(0x%x)", + __func__, chgtyp, spchgtyp, dcdtmo, ret); + + return ret; +} + +muic_attached_dev_t max77705_muic_check_new_dev(struct max77705_muic_data *muic_data, + int *intr, int irq) +{ + const struct max77705_muic_vps_data *tmp_vps; + muic_attached_dev_t new_dev = ATTACHED_DEV_NONE_MUIC; + u8 adc = muic_data->status1 & USBC_STATUS1_UIADC_MASK; + u8 vbvolt = muic_data->status3 & BC_STATUS_VBUSDET_MASK; + u8 chgtyp = muic_data->status3 & BC_STATUS_CHGTYP_MASK; + u8 spchgtyp = (muic_data->status3 & BC_STATUS_PRCHGTYP_MASK) >> BC_STATUS_PRCHGTYP_SHIFT; + u8 dcdtmo = (muic_data->status3 & BC_STATUS_DCDTMO_MASK) >> BC_STATUS_DCDTMO_SHIFT; + unsigned long i; + + chgtyp = max77705_resolve_chgtyp(muic_data, chgtyp, spchgtyp, dcdtmo, irq); + +#if !defined(CONFIG_SEC_FACTORY) + if (adc != MAX77705_UIADC_OPEN) { + pr_info("%s set adc to open (%d) -> (%d)\n", __func__, adc, MAX77705_UIADC_OPEN); + } + adc = MAX77705_UIADC_OPEN; +#endif + +#if IS_ENABLED(CONFIG_MUIC_MAX77705_PDIC) + adc = max77705_muic_update_adc_with_rid(muic_data, adc); + /* Do not check vbus if CCIC RID/UID is 523K */ + if (muic_data->mfd_pdata->siso_ovp) { + if (adc == MAX77705_UIADC_523K) { + chgtyp = 0; + spchgtyp = 0; + vbvolt = 0; + } + } else { + if ((muic_data->pdata->opmode & OPMODE_PDIC) && (adc == MAX77705_UIADC_523K)) + vbvolt = 0; + } +#endif /* CONFIG_MUIC_MAX77705_PDIC */ + + for (i = 0; i < (int)ARRAY_SIZE(muic_vps_table); i++) { + tmp_vps = &(muic_vps_table[i]); + + if (!(muic_check_vps_adc(tmp_vps, adc))) + continue; + + if (!(muic_check_vps_vbvolt(tmp_vps, vbvolt))) + continue; + + if (!(muic_check_vps_chgtyp(tmp_vps, chgtyp))) + continue; + + pr_info("%s vps table match found at i(%lu), %s\n", + __func__, i, tmp_vps->vps_name); + + new_dev = tmp_vps->attached_dev; + muic_data->switch_val = tmp_vps->muic_switch; + + *intr = MUIC_INTR_ATTACH; + break; + } + + pr_info("%s %d->%d switch_val[0x%02x]\n", __func__, + muic_data->attached_dev, new_dev, muic_data->switch_val); + + return new_dev; +} + +static void max77705_muic_detect_dev(struct max77705_muic_data *muic_data, + int irq) +{ + struct i2c_client *i2c = muic_data->i2c; +#if !defined(CONFIG_SEC_FACTORY) + struct max77705_usbc_platform_data *usbpd_data = muic_data->usbc_pdata; +#endif + muic_attached_dev_t new_dev = ATTACHED_DEV_NONE_MUIC; + int intr = MUIC_INTR_DETACH; + u8 status[5]; + u8 adc, vbvolt, chgtyp, spchgtyp, sysmsg, vbadc, dcdtmo, ccstat; + static unsigned long killer_stamp; + int ret; +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + int event; +#endif +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif + + ret = max77705_bulk_read(i2c, + MAX77705_USBC_REG_USBC_STATUS1, 5, status); + if (ret) { + pr_err("%s fail to read muic reg(%d)\n", + __func__, ret); + return; + } + + pr_info("%s USBC1:0x%02x, USBC2:0x%02x, BC:0x%02x\n", + __func__, status[0], status[1], status[2]); + + /* attached status */ + muic_data->status1 = status[0]; + muic_data->status2 = status[1]; + muic_data->status3 = status[2]; + + adc = status[0] & USBC_STATUS1_UIADC_MASK; + sysmsg = status[1] & USBC_STATUS2_SYSMSG_MASK; + vbvolt = (status[2] & BC_STATUS_VBUSDET_MASK) >> BC_STATUS_VBUSDET_SHIFT; + chgtyp = status[2] & BC_STATUS_CHGTYP_MASK; + spchgtyp = (status[2] & BC_STATUS_PRCHGTYP_MASK) >> BC_STATUS_PRCHGTYP_SHIFT; + vbadc = (status[0] & USBC_STATUS1_VBADC_MASK) >> USBC_STATUS1_VBADC_SHIFT; + dcdtmo = (status[2] & BC_STATUS_DCDTMO_MASK) >> BC_STATUS_DCDTMO_SHIFT; + ccstat = (status[4] & BIT_CCStat) >> FFS(BIT_CCStat); + + pr_info("%s adc:0x%x vbvolt:0x%x chgtyp:0x%x spchgtyp:0x%x sysmsg:0x%x vbadc:0x%x dcdtmo:0x%x\n", + __func__, adc, vbvolt, chgtyp, spchgtyp, sysmsg, vbadc, dcdtmo); + + /* Set the fake_vbus charger type */ + muic_data->fake_chgtyp = chgtyp; + + if (irq == muic_data->irq_vbadc) { + if (vbadc == MAX77705_VBADC_3_8V_TO_4_5V && + ccstat == cc_No_Connection) { + /* W/A of CC is detached but Vbus is valid(3.8~4.5V) */ + vbvolt = 0; + muic_data->status3 = muic_data->status3 & ~(BC_STATUS_VBUSDET_MASK); + pr_info("%s vbadc(0x%x), ccstat(0x%x), set vbvolt to 0 => BC(0x%x)\n", + __func__, vbadc, ccstat, muic_data->status3); +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77705_AFC) + } else if (vbadc > MAX77705_VBADC_3_8V_TO_4_5V && + vbadc <= MAX77705_VBADC_6_5V_TO_7_5V && + muic_data->is_afc_reset) { + muic_data->is_afc_reset = false; + pr_info("%s afc reset is done\n", __func__); + + switch (muic_data->attached_dev) { + case ATTACHED_DEV_AFC_CHARGER_5V_MUIC: + case ATTACHED_DEV_AFC_CHARGER_9V_MUIC: + muic_data->attached_dev = ATTACHED_DEV_AFC_CHARGER_5V_MUIC; +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + muic_notifier_attach_attached_dev(muic_data->attached_dev); +#endif /* CONFIG_MUIC_NOTIFIER */ + break; + case ATTACHED_DEV_QC_CHARGER_5V_MUIC: + case ATTACHED_DEV_QC_CHARGER_9V_MUIC: + muic_data->attached_dev = ATTACHED_DEV_QC_CHARGER_5V_MUIC; +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + muic_notifier_attach_attached_dev(muic_data->attached_dev); +#endif /* CONFIG_MUIC_NOTIFIER */ + break; + default: + break; + } + return; +#endif /* CONFIG_HV_MUIC_MAX77705_AFC */ + } else { + pr_info("%s vbadc irq(%d), return\n", + __func__, muic_data->irq_vbadc); + return; + } + } + + if (irq == muic_data->irq_dcdtmo && dcdtmo) { + muic_data->dcdtmo_retry++; + pr_info("%s:%s DCD_TIMEOUT retry count: %d\n", MUIC_DEV_NAME, __func__, muic_data->dcdtmo_retry); + } + +#if !defined(CONFIG_SEC_FACTORY) + if (!usbpd_data->manual_lpm_mode) { + if (irq == muic_data->irq_vbusdet || irq == MUIC_IRQ_INIT_DETECT) { + __pm_relax(muic_data->muic_ws); + cancel_delayed_work(&(muic_data->vbus_wa_work)); + if (vbvolt > 0 && ccstat == cc_No_Connection) { + __pm_wakeup_event(muic_data->muic_ws, 2100); + schedule_delayed_work(&(muic_data->vbus_wa_work), msecs_to_jiffies(2000)); + } + } else if (irq == muic_data->irq_chgtyp && chgtyp > 0) { + __pm_relax(muic_data->muic_ws); + cancel_delayed_work(&(muic_data->vbus_wa_work)); + } + } +#endif + +#if IS_ENABLED(CONFIG_MUIC_MAX77705_PDIC) + if (!is_lpcharge_pdic_param() && !muic_data->is_factory_start) { + if ((irq == MUIC_IRQ_PDIC_HANDLER) && + (muic_data->ccic_evt_id == PDIC_NOTIFY_ID_WATER)) { + /* Force path open once at water state */ + if (muic_data->afc_water_disable) + com_to_open(muic_data); + } + +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) + if (muic_data->afc_water_disable && !muic_data->is_hiccup_mode) { + if (vbvolt > 0) { + pr_info("%s water hiccup mode, Aux USB path\n", __func__); + com_to_usb_cp(muic_data); + } else { + /* Clear muic deive type and hiccup at water state (booting with water) */ + if (muic_data->attached_dev != ATTACHED_DEV_NONE_MUIC) { + pr_info("%s initialize hiccup state and device type(%d) at hiccup booting\n", + __func__, muic_data->attached_dev); + muic_notifier_detach_attached_dev(muic_data->attached_dev); + muic_data->attached_dev = ATTACHED_DEV_NONE_MUIC; + com_to_open(muic_data); + } + } +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) + max77705_muic_handle_vbus(muic_data); +#endif + + return; + } +#endif /* CONFIG_HICCUP_CHARGER */ + } +#endif /* CONFIG_MUIC_MAX77705_PDIC */ + + if (irq == muic_data->irq_chgtyp && vbvolt == 0 && chgtyp > 0) { + killer_stamp = jiffies; + } else if (irq == muic_data->irq_vbusdet && chgtyp > 0 && vbvolt > 0 && + time_before(jiffies, killer_stamp + msecs_to_jiffies(500))) { + pr_info("%s: this is checking killer, retry bc12\n", __func__); + max77705_muic_enable_chgdet(muic_data); + goto out; + } else if (irq == muic_data->irq_vbusdet && vbvolt == 0) { + pr_info("%s: killer time stamp reset\n", __func__); + killer_stamp = 0; + } + + new_dev = max77705_muic_check_new_dev(muic_data, &intr, irq); + + if (intr == MUIC_INTR_ATTACH) { + pr_info("%s ATTACHED\n", __func__); + + ret = max77705_muic_handle_attach(muic_data, new_dev, irq); + if (ret) + pr_err("%s cannot handle attach(%d)\n", __func__, ret); + } else { + pr_info("%s DETACHED\n", __func__); + + if (vbvolt == 0 && chgtyp == CHGTYP_DEDICATED_CHARGER) { + pr_info("[MUIC] %s USB Killer Detected!!!\n", __func__); +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_USBKILLER; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_USB_KILLER_COUNT); +#endif + } + + ret = max77705_muic_handle_detach(muic_data, irq); + if (ret) + pr_err("%s cannot handle detach(%d)\n", __func__, ret); + } +out: +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) + max77705_muic_handle_vbus(muic_data); +#endif +} + +static irqreturn_t max77705_muic_irq(int irq, void *data) +{ + struct max77705_muic_data *muic_data = data; + struct irq_desc *desc = irq_to_desc(irq); + + if (!desc) { + pr_info("%s desc is null\n", __func__); + goto out; + } + + if (!muic_data) { + pr_err("%s muic_data is null(%s)\n", __func__, desc->action->name); + goto out; + } + + pr_info("%s irq:%d (%s)\n", __func__, irq, desc->action->name); + + mutex_lock(&muic_data->muic_mutex); + if (muic_data->is_muic_ready == true) + max77705_muic_detect_dev(muic_data, irq); + else + pr_info("%s MUIC is not ready, just return\n", __func__); + mutex_unlock(&muic_data->muic_mutex); + +out: + return IRQ_HANDLED; +} + +static void max77705_muic_vbus_wa_work(struct work_struct *work) +{ + struct max77705_muic_data *muic_data = + container_of(work, struct max77705_muic_data, vbus_wa_work.work); + u8 vbvolt = (muic_data->status3 & BC_STATUS_VBUSDET_MASK) >> BC_STATUS_VBUSDET_SHIFT; + int ccic_attach = muic_data->ccic_info_data.ccic_evt_attached; + + pr_info("%s vbvolt(%d) ccic_attach(%d)\n", __func__, vbvolt, ccic_attach); + + mutex_lock(&muic_data->muic_mutex); + if (muic_data->is_muic_ready == true) { + if (vbvolt > 0 && ccic_attach != MUIC_PDIC_NOTI_ATTACH) + max77705_muic_detect_dev(muic_data, MUIC_IRQ_VBUS_WA); + } else { + pr_info("%s MUIC is not ready, just return\n", __func__); + } + mutex_unlock(&muic_data->muic_mutex); +} + +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77705_AFC) +static void max77705_muic_afc_work(struct work_struct *work) +{ + struct max77705_muic_data *muic_data = + container_of(work, struct max77705_muic_data, afc_work.work); + + pr_info("%s\n", __func__); + + if (max77705_muic_check_is_enable_afc(muic_data, muic_data->attached_dev)) { + mutex_lock(&muic_data->afc_lock); + muic_data->pdata->afc_disabled_updated |= MAX77705_MUIC_AFC_WORK_PROCESS; + + if (!muic_data->pdata->afc_disable && muic_is_enable_afc_request()) { + muic_data->is_check_hv = true; + muic_data->hv_voltage = 9; + max77705_muic_afc_hv_set(muic_data, 9); + } else { + muic_data->is_check_hv = true; + muic_data->hv_voltage = 5; + max77705_muic_afc_hv_set(muic_data, 5); + } + mutex_unlock(&muic_data->afc_lock); + } +} + +static int max77705_muic_hv_charger_disable(bool en) +{ + struct max77705_muic_data *muic_data = g_muic_data; + + muic_data->is_charger_mode = en; + + mutex_lock(&muic_data->afc_lock); + if (muic_data->pdata->afc_disabled_updated & MAX77705_MUIC_AFC_WORK_PROCESS) + muic_data->pdata->afc_disabled_updated |= MAX77705_MUIC_AFC_DISABLE_CHANGE_DURING_WORK; + else + schedule_delayed_work(&(muic_data->afc_work), msecs_to_jiffies(0)); + mutex_unlock(&muic_data->afc_lock); + + return 0; +} + +int __max77705_muic_afc_set_voltage(struct max77705_muic_data *muic_data, int voltage) +{ + int now_voltage = 0, ret = 0; + + switch (voltage) { + case 5: + case 9: + break; + default: + pr_err("%s: invalid value %d, return\n", __func__, voltage); + ret = -EINVAL; + goto err; + } + + switch (muic_data->attached_dev) { + case ATTACHED_DEV_AFC_CHARGER_5V_MUIC: + case ATTACHED_DEV_QC_CHARGER_5V_MUIC: + now_voltage = 5; + break; + case ATTACHED_DEV_AFC_CHARGER_9V_MUIC: + case ATTACHED_DEV_QC_CHARGER_9V_MUIC: + now_voltage = 9; + break; + default: + break; + } + + if (voltage == now_voltage) { + pr_err("%s: same with current voltage, return\n", __func__); + ret = -EINVAL; + goto err; + } + + muic_data->hv_voltage = voltage; + + switch (muic_data->attached_dev) { + case ATTACHED_DEV_AFC_CHARGER_5V_MUIC: + case ATTACHED_DEV_AFC_CHARGER_9V_MUIC: + ret = max77705_muic_afc_hv_set(muic_data, voltage); + break; + case ATTACHED_DEV_QC_CHARGER_5V_MUIC: + case ATTACHED_DEV_QC_CHARGER_9V_MUIC: + ret = max77705_muic_qc_hv_set(muic_data, voltage); + break; + default: + pr_err("%s: not a HV Charger %d, return\n", __func__, muic_data->attached_dev); + ret = -EINVAL; + goto err; + } +err: + return ret; +} + +static int max77705_muic_afc_set_voltage(int voltage) +{ + struct max77705_muic_data *muic_data = g_muic_data; + int ret = 0; + + mutex_lock(&muic_data->afc_lock); + + if (muic_data->pdata->afc_disabled_updated & MAX77705_MUIC_AFC_WORK_PROCESS) { + muic_data->pdata->afc_disabled_updated |= MAX77705_MUIC_AFC_SET_VOLTAGE_CHANGE_DURING_WORK; + muic_data->reserve_hv_voltage = voltage; + goto skip; + } + + muic_data->pdata->afc_disabled_updated |= MAX77705_MUIC_AFC_WORK_PROCESS; + muic_data->reserve_hv_voltage = 0; + + ret = __max77705_muic_afc_set_voltage(muic_data, voltage); + if (ret < 0) + muic_data->pdata->afc_disabled_updated &= MAX77705_MUIC_AFC_WORK_PROCESS_END; +skip: + mutex_unlock(&muic_data->afc_lock); + return ret; +} + +static int max77705_muic_hv_charger_init(void) +{ + struct max77705_muic_data *muic_data = g_muic_data; + + if (!muic_data || !muic_data->pdata || + !test_bit(MUIC_PROBE_DONE, &muic_data->pdata->driver_probe_flag)) { + pr_info("[%s:%s] skip\n", MUIC_DEV_NAME, __func__); + return 0; + } + + if (muic_data->is_charger_ready) { + pr_info("%s: charger is already ready(%d), return\n", + __func__, muic_data->is_charger_ready); + return -EINVAL; + } + + muic_data->is_charger_ready = true; + + if (max77705_muic_check_is_enable_afc(muic_data, muic_data->attached_dev)) { + pr_info("%s afc work start\n", __func__); + cancel_delayed_work_sync(&(muic_data->afc_work)); + schedule_delayed_work(&(muic_data->afc_work), msecs_to_jiffies(0)); + } + + return 0; +} + +static void max77705_muic_detect_dev_hv_work(struct work_struct *work) +{ + struct max77705_muic_data *muic_data = container_of(work, + struct max77705_muic_data, afc_handle_work); + unsigned char opcode = muic_data->afc_op_dataout[0]; + + mutex_lock(&muic_data->muic_mutex); + if (!max77705_muic_check_is_enable_afc(muic_data, muic_data->attached_dev)) { + switch (muic_data->attached_dev) { + case ATTACHED_DEV_AFC_CHARGER_5V_MUIC: + case ATTACHED_DEV_AFC_CHARGER_9V_MUIC: + case ATTACHED_DEV_QC_CHARGER_5V_MUIC: + case ATTACHED_DEV_QC_CHARGER_9V_MUIC: + pr_info("%s high voltage value is changed\n", __func__); + break; + default: + pr_info("%s status is changed, return\n", __func__); + goto out; + } + } + + if (opcode == COMMAND_AFC_RESULT_READ) + max77705_muic_handle_detect_dev_afc(muic_data, muic_data->afc_op_dataout); + else if (opcode == COMMAND_QC_2_0_SET) + max77705_muic_handle_detect_dev_qc(muic_data, muic_data->afc_op_dataout); + else + pr_info("%s undefined opcode(%d)\n", __func__, opcode); + +out: + mutex_unlock(&muic_data->muic_mutex); +} + +void max77705_muic_handle_detect_dev_hv(struct max77705_muic_data *muic_data, unsigned char *data) +{ + int i; + + for (i = 0; i < AFC_OP_OUT_LEN; i++) + muic_data->afc_op_dataout[i] = data[i]; + + schedule_work(&(muic_data->afc_handle_work)); +} +#endif /* CONFIG_HV_MUIC_MAX77705_AFC */ + +#if IS_ENABLED(CONFIG_MUIC_POGO) +static int max77705_muic_set_pogo_adc(int adc) +{ + struct max77705_muic_data *muic_data = g_muic_data; + + pr_info("%s adc(0x%x)\n", __func__, adc); + muic_data->pogo_adc = adc; + +#if defined(CONFIG_MUIC_DISABLE_CHGDET) + if (adc == ADC_HMT) { + pr_info("%s adc is POGO pogo keyboard, path open, bc12 off\n", __func__); + com_to_open(muic_data); + + max77705_muic_disable_chgdet(muic_data); + } + + if (adc == ADC_OPEN) { + pr_info("%s adc is open, bc12 on\n", __func__); + max77705_muic_enable_chgdet(muic_data); + } +#endif + + return 0; +} +#endif /* CONFIG_MUIC_POGO */ + +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) +static int max77705_muic_set_hiccup_mode(int on_off) +{ + struct max77705_muic_data *muic_data = g_muic_data; + + pr_info("%s (%d)\n", __func__, on_off); + + switch (on_off) { + case MUIC_HICCUP_MODE_OFF: + case MUIC_HICCUP_MODE_ON: + if (muic_data->is_hiccup_mode != on_off) { + muic_data->is_hiccup_mode = on_off; +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77705_AFC) + if (muic_data->is_check_hv) + max77705_muic_clear_hv_control(muic_data); +#endif /* CONFIG_HV_MUIC_MAX77705_AFC */ +#if IS_ENABLED(CONFIG_MUIC_MAX77705_PDIC) + schedule_work(&(muic_data->ccic_info_data_work)); +#endif /* CONFIG_MUIC_MAX77705_PDIC */ + } + break; + default: + pr_err("%s undefined value(%d), return\n", __func__, on_off); + return -EINVAL; + } + + return 0; +} +#endif /* CONFIG_HICCUP_CHARGER */ + +static void max77705_muic_print_reg_log(struct work_struct *work) +{ + struct max77705_muic_data *muic_data = + container_of(work, struct max77705_muic_data, debug_work.work); + struct i2c_client *i2c = muic_data->i2c; + struct i2c_client *pmic_i2c = muic_data->usbc_pdata->i2c; + u8 status[12] = {0, }; + + max77705_muic_read_reg(i2c, MAX77705_USBC_REG_USBC_STATUS1, &status[0]); + max77705_muic_read_reg(i2c, MAX77705_USBC_REG_USBC_STATUS2, &status[1]); + max77705_muic_read_reg(i2c, MAX77705_USBC_REG_BC_STATUS, &status[2]); + max77705_muic_read_reg(i2c, MAX77705_USBC_REG_CC_STATUS0, &status[3]); + max77705_muic_read_reg(i2c, MAX77705_USBC_REG_CC_STATUS1, &status[4]); + max77705_muic_read_reg(i2c, MAX77705_USBC_REG_PD_STATUS0, &status[5]); + max77705_muic_read_reg(i2c, MAX77705_USBC_REG_PD_STATUS1, &status[6]); + max77705_muic_read_reg(i2c, MAX77705_USBC_REG_UIC_INT_M, &status[7]); + max77705_muic_read_reg(i2c, MAX77705_USBC_REG_CC_INT_M, &status[8]); + max77705_muic_read_reg(i2c, MAX77705_USBC_REG_PD_INT_M, &status[9]); + max77705_muic_read_reg(i2c, MAX77705_USBC_REG_VDM_INT_M, &status[10]); + max77705_muic_read_reg(pmic_i2c, MAX77705_PMIC_REG_INTSRC_MASK, &status[11]); + + pr_info("%s USBC1:0x%02x, USBC2:0x%02x, BC:0x%02x, CC0:0x%x, CC1:0x%x, PD0:0x%x, PD1:0x%x attached_dev:%d\n", + __func__, status[0], status[1], status[2], status[3], status[4], status[5], status[6], + muic_data->attached_dev); + pr_info("%s UIC_INT_M:0x%x, CC_INT_M:0x%x, PD_INT_M:0x%x, VDM_INT_M:0x%x, PMIC_MASK:0x%x, WDT:%d, POR:%d\n", + __func__, status[7], status[8], status[9], status[10], status[11], + muic_data->usbc_pdata->watchdog_count, muic_data->usbc_pdata->por_count); + + schedule_delayed_work(&(muic_data->debug_work), + msecs_to_jiffies(60000)); +} + +#if IS_ENABLED(CONFIG_MUIC_MAX77705_PDIC) +static void max77705_muic_handle_ccic_event(struct work_struct *work) +{ + struct max77705_muic_data *muic_data = container_of(work, + struct max77705_muic_data, ccic_info_data_work); + + pr_info("%s\n", __func__); + + mutex_lock(&muic_data->muic_mutex); + if (muic_data->is_muic_ready == true) + max77705_muic_detect_dev(muic_data, MUIC_IRQ_PDIC_HANDLER); + else + pr_info("%s MUIC is not ready, just return\n", __func__); + mutex_unlock(&muic_data->muic_mutex); +} +#endif /* CONFIG_MUIC_MAX77705_PDIC */ + +#define REQUEST_IRQ(_irq, _dev_id, _name) \ +do { \ + ret = request_threaded_irq(_irq, NULL, max77705_muic_irq, \ + IRQF_NO_SUSPEND, _name, _dev_id); \ + if (ret < 0) { \ + pr_err("%s Failed to request IRQ #%d: %d\n", \ + __func__, _irq, ret); \ + _irq = 0; \ + } \ +} while (0) + +static int max77705_muic_irq_init(struct max77705_muic_data *muic_data) +{ + int ret = 0; + + pr_info("%s\n", __func__); + + if (muic_data->mfd_pdata && (muic_data->mfd_pdata->irq_base > 0)) { + int irq_base = muic_data->mfd_pdata->irq_base; + + /* request MUIC IRQ */ + muic_data->irq_uiadc = irq_base + MAX77705_USBC_IRQ_UIDADC_INT; + REQUEST_IRQ(muic_data->irq_uiadc, muic_data, "muic-uiadc"); + + muic_data->irq_chgtyp = irq_base + MAX77705_USBC_IRQ_CHGT_INT; + REQUEST_IRQ(muic_data->irq_chgtyp, muic_data, "muic-chgtyp"); + + muic_data->irq_dcdtmo = irq_base + MAX77705_USBC_IRQ_DCD_INT; + REQUEST_IRQ(muic_data->irq_dcdtmo, muic_data, "muic-dcdtmo"); + + muic_data->irq_vbadc = irq_base + MAX77705_USBC_IRQ_VBADC_INT; + REQUEST_IRQ(muic_data->irq_vbadc, muic_data, "muic-vbadc"); + + muic_data->irq_vbusdet = irq_base + MAX77705_USBC_IRQ_VBUS_INT; + REQUEST_IRQ(muic_data->irq_vbusdet, muic_data, "muic-vbusdet"); + } + + pr_info("%s uiadc(%d), chgtyp(%d), dcdtmo(%d), vbadc(%d), vbusdet(%d)\n", + __func__, muic_data->irq_uiadc, + muic_data->irq_chgtyp, muic_data->irq_dcdtmo, + muic_data->irq_vbadc, muic_data->irq_vbusdet); + return ret; +} + +#define FREE_IRQ(_irq, _dev_id, _name) \ +do { \ + if (_irq) { \ + free_irq(_irq, _dev_id); \ + pr_info("%s IRQ(%d):%s free done\n", \ + __func__, _irq, _name); \ + } \ +} while (0) + +static void max77705_muic_free_irqs(struct max77705_muic_data *muic_data) +{ + pr_info("%s\n", __func__); + + disable_irq(muic_data->irq_uiadc); + disable_irq(muic_data->irq_chgtyp); + disable_irq(muic_data->irq_dcdtmo); + disable_irq(muic_data->irq_vbadc); + disable_irq(muic_data->irq_vbusdet); + + /* free MUIC IRQ */ + FREE_IRQ(muic_data->irq_uiadc, muic_data, "muic-uiadc"); + FREE_IRQ(muic_data->irq_chgtyp, muic_data, "muic-chgtyp"); + FREE_IRQ(muic_data->irq_dcdtmo, muic_data, "muic-dcdtmo"); + FREE_IRQ(muic_data->irq_vbadc, muic_data, "muic-vbadc"); + FREE_IRQ(muic_data->irq_vbusdet, muic_data, "muic-vbusdet"); +} + +static void max77705_muic_init_detect(struct max77705_muic_data *muic_data) +{ + pr_info("%s\n", __func__); + + mutex_lock(&muic_data->muic_mutex); + muic_data->is_muic_ready = true; + + max77705_muic_detect_dev(muic_data, MUIC_IRQ_INIT_DETECT); + max77705_muic_enable_detecting_short(muic_data); + + mutex_unlock(&muic_data->muic_mutex); +} + +static void max77705_set_bc1p2_retry_count(struct max77705_muic_data *muic_data) +{ + struct device_node *np = NULL; + int count; + + np = of_find_compatible_node(NULL, NULL, "maxim,max77705"); + + if (np && !of_property_read_u32(np, "max77705,bc1p2_retry_count", &count)) + muic_data->bc1p2_retry_count = count; + else + muic_data->bc1p2_retry_count = 1; /* default retry count */ + + pr_info("%s:%s BC1p2 Retry count: %d\n", MUIC_DEV_NAME, + __func__, muic_data->bc1p2_retry_count); +} + +#if IS_ENABLED(CONFIG_OF) +static int of_max77705_muic_dt(struct max77705_muic_data *muic_data) +{ + struct device_node *np_muic; + const char *prop_support_list; + int i, j, prop_num; + int ret = 0; + + np_muic = of_find_node_by_path("/muic"); + if (np_muic == NULL) + return -EINVAL; + + prop_num = of_property_count_strings(np_muic, "muic,support-list"); + if (prop_num < 0) { + pr_warn("%s Cannot parse 'muic support list dt node'[%d]\n", + __func__, prop_num); + ret = prop_num; + goto err; + } + + /* for debug */ + for (i = 0; i < prop_num; i++) { + ret = of_property_read_string_index(np_muic, + "muic,support-list", i, &prop_support_list); + if (ret) { + pr_err("%s Cannot find string at [%d], ret[%d]\n", + __func__, i, ret); + goto err; + } + + pr_debug("%s prop_support_list[%d] is %s\n", __func__, + i, prop_support_list); + + for (j = 0; j < (int)ARRAY_SIZE(muic_vps_table); j++) { + if (!strcmp(muic_vps_table[j].vps_name, prop_support_list)) { + muic_data->muic_support_list[(muic_vps_table[j].attached_dev)] = true; + break; + } + } + } + + /* for debug */ + for (i = 0; i < ATTACHED_DEV_NUM; i++) + pr_debug("%s pmuic_support_list[%d] = %c\n", __func__, + i, (muic_data->muic_support_list[i] ? 'T' : 'F')); + +err: + of_node_put(np_muic); + + return ret; +} +#endif /* CONFIG_OF */ + +static void max77705_muic_clear_interrupt(struct max77705_muic_data *muic_data) +{ + struct i2c_client *i2c = muic_data->i2c; + u8 interrupt; + int ret; + + pr_info("%s\n", __func__); + + ret = max77705_muic_read_reg(i2c, + MAX77705_USBC_REG_UIC_INT, &interrupt); + if (ret) + pr_err("%s fail to read muic INT1 reg(%d)\n", + __func__, ret); + + pr_info("%s CLEAR!! UIC_INT:0x%02x\n", + __func__, interrupt); +} + +static int max77705_muic_init_regs(struct max77705_muic_data *muic_data) +{ + int ret; + + pr_info("%s\n", __func__); + + max77705_muic_clear_interrupt(muic_data); + + ret = max77705_muic_irq_init(muic_data); + if (ret < 0) { + pr_err("%s Failed to initialize MUIC irq:%d\n", + __func__, ret); + max77705_muic_free_irqs(muic_data); + } + + return ret; +} + +#if IS_ENABLED(CONFIG_USB_EXTERNAL_NOTIFY) +static int muic_handle_usb_notification(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct max77705_muic_data *pmuic = + container_of(nb, struct max77705_muic_data, usb_nb); + + switch (action) { + /* Abnormal device */ + case EXTERNAL_NOTIFY_3S_NODEVICE: + pr_info("%s: 3S_NODEVICE(USB HOST Connection timeout)\n", + __func__); + if (pmuic->attached_dev == ATTACHED_DEV_HMT_MUIC) + muic_send_dock_intent(MUIC_DOCK_ABNORMAL); + break; + + /* Gamepad device connected */ + case EXTERNAL_NOTIFY_DEVICE_CONNECT: + pr_info("%s: DEVICE_CONNECT(Gamepad)\n", __func__); + break; + + default: + break; + } + + return NOTIFY_DONE; +} + + +static void muic_register_usb_notifier(struct max77705_muic_data *pmuic) +{ + int ret = 0; + + pr_info("%s\n", __func__); + + + ret = usb_external_notify_register(&pmuic->usb_nb, + muic_handle_usb_notification, EXTERNAL_NOTIFY_DEV_MUIC); + if (ret < 0) { + pr_info("%s: USB Noti. is not ready.\n", __func__); + return; + } + + pr_info("%s: done.\n", __func__); +} + +static void muic_unregister_usb_notifier(struct max77705_muic_data *pmuic) +{ + int ret = 0; + + pr_info("%s\n", __func__); + + ret = usb_external_notify_unregister(&pmuic->usb_nb); + if (ret < 0) { + pr_info("%s: USB Noti. unregister error.\n", __func__); + return; + } + + pr_info("%s: done.\n", __func__); +} +#else +static void muic_register_usb_notifier(struct max77705_muic_data *pmuic) {} +static void muic_unregister_usb_notifier(struct max77705_muic_data *pmuic) {} +#endif + +int max77705_muic_probe(struct max77705_usbc_platform_data *usbc_data) +{ + struct max77705_platform_data *mfd_pdata = usbc_data->max77705_data; + struct max77705_muic_data *muic_data; + int ret = 0; +#if IS_ENABLED(CONFIG_ARCH_QCOM) && !defined(CONFIG_USB_ARCH_EXYNOS) + u8 pogo_adc = 0; +#endif + pr_info("%s\n", __func__); + + muic_data = devm_kzalloc(usbc_data->dev, sizeof(struct max77705_muic_data), GFP_KERNEL); + if (!muic_data) { + ret = -ENOMEM; + goto err_return; + } + + if (!mfd_pdata) { + pr_err("%s: failed to get mfd platform data\n", __func__); + ret = -ENOMEM; + goto err_return; + } + +#if IS_ENABLED(CONFIG_OF) + ret = of_max77705_muic_dt(muic_data); + if (ret < 0) + pr_err("%s not found muic dt! ret[%d]\n", __func__, ret); +#endif /* CONFIG_OF */ + + mutex_init(&muic_data->muic_mutex); + mutex_init(&muic_data->afc_lock); + muic_data->muic_ws = wakeup_source_register(usbc_data->dev, "muic-irq"); +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + muic_data->afc_retry_ws = wakeup_source_register(usbc_data->dev, "muic-afc-retry"); +#endif + muic_data->i2c = usbc_data->muic; + muic_data->mfd_pdata = mfd_pdata; + muic_data->usbc_pdata = usbc_data; + muic_data->pdata = &muic_pdata; + muic_data->attached_dev = ATTACHED_DEV_NONE_MUIC; + muic_data->is_muic_ready = false; + muic_data->is_otg_test = false; + muic_data->is_factory_start = false; + muic_data->switch_val = COM_OPEN; + muic_data->is_charger_mode = false; + muic_data->dcdtmo_retry = 0; + + usbc_data->muic_data = muic_data; + g_muic_data = muic_data; + +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + muic_data->muic_d.ops = NULL; + muic_data->muic_d.data = (void *)muic_data; + muic_data->man = register_muic(&(muic_data->muic_d)); +#endif + if (muic_data->pdata->init_gpio_cb) { +#if IS_MODULE(CONFIG_MUIC_NOTIFIER) + ret = muic_data->pdata->init_gpio_cb(get_switch_sel()); +#else + ret = muic_data->pdata->init_gpio_cb(); +#endif + if (ret) { + pr_err("%s: failed to init gpio(%d)\n", __func__, ret); + goto fail; + } + } + + mutex_lock(&muic_data->muic_mutex); + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + /* create sysfs group */ + ret = sysfs_create_group(&switch_device->kobj, &max77705_muic_group); + if (ret) { + pr_err("%s: failed to create attribute group. error: %d\n", + __func__, ret); + goto fail_sysfs_create; + } + dev_set_drvdata(switch_device, muic_data); +#endif + + if (muic_data->pdata->init_switch_dev_cb) + muic_data->pdata->init_switch_dev_cb(); + + ret = max77705_muic_init_regs(muic_data); + if (ret < 0) { + pr_err("%s Failed to initialize MUIC irq:%d\n", + __func__, ret); + goto fail_init_irq; + } + + mutex_unlock(&muic_data->muic_mutex); + + INIT_DELAYED_WORK(&(muic_data->vbus_wa_work), max77705_muic_vbus_wa_work); + +#if IS_ENABLED(CONFIG_MUIC_MAX77705_PDIC) +#if IS_ENABLED(CONFIG_USB_ARCH_EXYNOS) + muic_data->pdata->opmode = get_pdic_info() & 0x0f; +#else + ret = max77705_read_reg(muic_data->i2c, MAX77705_USBC_REG_USBC_STATUS1, &pogo_adc); + pogo_adc &= USBC_STATUS1_UIADC_MASK; + pr_info("%s:%s POGO_ADC : 0x%02x\n", MUIC_DEV_NAME, __func__, pogo_adc); + + if (pogo_adc == 0x07) + muic_data->pdata->opmode = OPMODE_PDIC; + else + muic_data->pdata->opmode = OPMODE_MUIC; +#endif + if (muic_data->pdata->opmode & OPMODE_PDIC) { + max77705_muic_register_ccic_notifier(muic_data); + INIT_WORK(&(muic_data->ccic_info_data_work), + max77705_muic_handle_ccic_event); + } + + muic_data->afc_water_disable = false; +#endif /* CONFIG_MUIC_MAX77705_PDIC */ + +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77705_AFC) + if (get_afc_mode() == CH_MODE_AFC_DISABLE_VAL) { + pr_info("%s: afc_disable: AFC disabled\n", __func__); + muic_data->pdata->afc_disable = true; + } else { + pr_info("%s: afc_disable: AFC enabled\n", __func__); + muic_data->pdata->afc_disable = false; + } + muic_data->pdata->afc_disabled_updated = 0; + + INIT_DELAYED_WORK(&(muic_data->afc_work), + max77705_muic_afc_work); + INIT_WORK(&(muic_data->afc_handle_work), + max77705_muic_detect_dev_hv_work); + + /* set MUIC afc voltage switching function */ + muic_data->pdata->muic_afc_set_voltage_cb = max77705_muic_afc_set_voltage; + muic_data->pdata->muic_hv_charger_disable_cb = max77705_muic_hv_charger_disable; + +#if IS_ENABLED(CONFIG_MUIC_POGO) + muic_data->pdata->muic_set_pogo_adc_cb = max77705_muic_set_pogo_adc; + muic_data->pogo_adc = ADC_OPEN; +#endif /* CONFIG_MUIC_POGO */ + + /* set MUIC check charger init function */ + muic_data->pdata->muic_hv_charger_init_cb = max77705_muic_hv_charger_init; + muic_data->is_charger_ready = false; + muic_data->is_check_hv = false; + muic_data->hv_voltage = 0; + muic_data->afc_retry = 0; + muic_data->is_afc_reset = false; + muic_data->is_skip_bigdata = false; + muic_data->is_usb_fail = false; +#endif /* CONFIG_HV_MUIC_MAX77705_AFC */ + +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) + muic_data->is_hiccup_mode = 0; + muic_data->pdata->muic_set_hiccup_mode_cb = max77705_muic_set_hiccup_mode; +#endif /* CONFIG_HICCUP_CHARGER */ + + /* set bc1p2 retry count */ + max77705_set_bc1p2_retry_count(muic_data); + + /* initial cable detection */ + max77705_muic_init_detect(muic_data); + + /* register usb external notifier */ + muic_register_usb_notifier(muic_data); + + INIT_DELAYED_WORK(&(muic_data->debug_work), + max77705_muic_print_reg_log); + schedule_delayed_work(&(muic_data->debug_work), + msecs_to_jiffies(10000)); + + + /* hv charger init */ + set_bit(MUIC_PROBE_DONE, &muic_data->pdata->driver_probe_flag); + if (test_bit(CHARGER_PROBE_DONE, &muic_data->pdata->driver_probe_flag)) + max77705_muic_hv_charger_init(); + + return 0; + +fail_init_irq: + if (muic_data->pdata->cleanup_switch_dev_cb) + muic_data->pdata->cleanup_switch_dev_cb(); +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + sysfs_remove_group(&switch_device->kobj, &max77705_muic_group); +#endif +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) +fail_sysfs_create: +#endif + mutex_unlock(&muic_data->muic_mutex); +fail: + mutex_destroy(&muic_data->muic_mutex); +err_return: + return ret; +} + +int max77705_muic_remove(struct max77705_usbc_platform_data *usbc_data) +{ + struct max77705_muic_data *muic_data = usbc_data->muic_data; + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + sysfs_remove_group(&switch_device->kobj, &max77705_muic_group); +#endif + + if (muic_data) { + pr_info("%s\n", __func__); + + max77705_muic_free_irqs(muic_data); + + cancel_delayed_work(&(muic_data->vbus_wa_work)); + +#if IS_ENABLED(CONFIG_MUIC_MAX77705_PDIC) + if (muic_data->pdata->opmode & OPMODE_PDIC) { + max77705_muic_unregister_ccic_notifier(muic_data); + cancel_work_sync(&(muic_data->ccic_info_data_work)); + } +#endif +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77705_AFC) + cancel_delayed_work_sync(&(muic_data->afc_work)); + cancel_work_sync(&(muic_data->afc_handle_work)); +#endif + + muic_unregister_usb_notifier(muic_data); + cancel_delayed_work_sync(&(muic_data->debug_work)); + + if (muic_data->pdata->cleanup_switch_dev_cb) + muic_data->pdata->cleanup_switch_dev_cb(); + + mutex_destroy(&muic_data->muic_mutex); + wakeup_source_unregister(muic_data->muic_ws); +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + wakeup_source_unregister(muic_data->afc_retry_ws); +#endif + } + + return 0; +} + +void max77705_muic_shutdown(struct max77705_usbc_platform_data *usbc_data) +{ + struct max77705_muic_data *muic_data = usbc_data->muic_data; + + pr_info("%s +\n", __func__); +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) + sysfs_remove_group(&switch_device->kobj, &max77705_muic_group); +#endif + + if (!muic_data) { + pr_err("%s no drvdata\n", __func__); + goto out; + } + + com_to_open(muic_data); + + max77705_muic_free_irqs(muic_data); + + cancel_delayed_work(&(muic_data->vbus_wa_work)); + +#if IS_ENABLED(CONFIG_MUIC_MAX77705_PDIC) + if ((muic_data->pdata) && (muic_data->pdata->opmode & OPMODE_PDIC)) { + max77705_muic_unregister_ccic_notifier(muic_data); + cancel_work_sync(&(muic_data->ccic_info_data_work)); + } +#endif +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77705_AFC) + /* clear MUIC afc voltage switching function */ + muic_data->pdata->muic_afc_set_voltage_cb = NULL; + /* clear MUIC check charger init function */ + muic_data->pdata->muic_hv_charger_init_cb = NULL; + cancel_delayed_work_sync(&(muic_data->afc_work)); + cancel_work_sync(&(muic_data->afc_handle_work)); +#endif +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) + muic_data->pdata->muic_set_hiccup_mode_cb = NULL; +#endif /* CONFIG_HICCUP_CHARGER */ + muic_unregister_usb_notifier(muic_data); + cancel_delayed_work_sync(&(muic_data->debug_work)); + +out: + pr_info("%s -\n", __func__); +} + +int max77705_muic_suspend(struct max77705_usbc_platform_data *usbc_data) +{ + struct max77705_muic_data *muic_data = usbc_data->muic_data; + + pr_info("%s\n", __func__); + cancel_delayed_work_sync(&(muic_data->debug_work)); + + return 0; +} + +int max77705_muic_resume(struct max77705_usbc_platform_data *usbc_data) +{ + struct max77705_muic_data *muic_data = usbc_data->muic_data; + + pr_info("%s\n", __func__); + schedule_delayed_work(&(muic_data->debug_work), msecs_to_jiffies(1000)); + + return 0; +} diff --git a/drivers/usb/typec/maxim/max77705_alternate.c b/drivers/usb/typec/maxim/max77705_alternate.c new file mode 100644 index 000000000000..6f1ac7b58129 --- /dev/null +++ b/drivers/usb/typec/maxim/max77705_alternate.c @@ -0,0 +1,1897 @@ +/* + * Copyrights (C) 2017 Samsung Electronics, Inc. + * Copyrights (C) 2017 Maxim Integrated Products, 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) +#include +#endif +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) +#include +#include +#endif +#include +#include + +#define UVDM_DEBUG (0) +#define SEC_UVDM_ALIGN (4) +#define MAX_DATA_FIRST_UVDMSET 12 +#define MAX_DATA_NORMAL_UVDMSET 16 +#define CHECKSUM_DATA_COUNT 20 +#define MAX_INPUT_DATA (255) + +extern struct max77705_usbc_platform_data *g_usbc_data; + +const struct DP_DP_DISCOVER_IDENTITY DP_DISCOVER_IDENTITY = { + { + .BITS.Num_Of_VDO = 1, + .BITS.Cmd_Type = ACK, + .BITS.Reserved = 0 + }, + + { + .BITS.VDM_command = Discover_Identity, + .BITS.Rsvd2_VDM_header = 0, + .BITS.VDM_command_type = REQ, + .BITS.Object_Position = 0, + .BITS.Rsvd_VDM_header = 0, + .BITS.Structured_VDM_Version = Version_1_0, + .BITS.VDM_Type = STRUCTURED_VDM, + .BITS.Standard_Vendor_ID = 0xFF00 + } + +}; +const struct DP_DP_DISCOVER_ENTER_MODE DP_DISCOVER_ENTER_MODE = { + { + .BITS.Num_Of_VDO = 1, + .BITS.Cmd_Type = ACK, + .BITS.Reserved = 0 + }, + { + .BITS.VDM_command = Enter_Mode, + .BITS.Rsvd2_VDM_header = 0, + .BITS.VDM_command_type = REQ, + .BITS.Object_Position = 1, + .BITS.Rsvd_VDM_header = 0, + .BITS.Structured_VDM_Version = Version_1_0, + .BITS.VDM_Type = STRUCTURED_VDM, + .BITS.Standard_Vendor_ID = 0xFF01 + } +}; + +struct DP_DP_CONFIGURE DP_CONFIGURE = { + { + .BITS.Num_Of_VDO = 2, + .BITS.Cmd_Type = ACK, + .BITS.Reserved = 0 + }, + { + .BITS.VDM_command = 17, /* SVID Specific Command */ + .BITS.Rsvd2_VDM_header = 0, + .BITS.VDM_command_type = REQ, + .BITS.Object_Position = 1, + .BITS.Rsvd_VDM_header = 0, + .BITS.Structured_VDM_Version = Version_1_0, + .BITS.VDM_Type = STRUCTURED_VDM, + .BITS.Standard_Vendor_ID = 0xFF01 + }, + { + .BITS.SEL_Configuration = num_Cfg_UFP_U_as_UFP_D, + .BITS.Select_DP_V1p3 = 1, + .BITS.Select_USB_Gen2 = 0, + .BITS.Select_Reserved_1 = 0, + .BITS.Select_Reserved_2 = 0, + .BITS.DFP_D_PIN_Assign_A = 0, + .BITS.DFP_D_PIN_Assign_B = 0, + .BITS.DFP_D_PIN_Assign_C = 0, + .BITS.DFP_D_PIN_Assign_D = 1, + .BITS.DFP_D_PIN_Assign_E = 0, + .BITS.DFP_D_PIN_Assign_F = 0, + .BITS.DFP_D_PIN_Reserved = 0, + .BITS.UFP_D_PIN_Assign_A = 0, + .BITS.UFP_D_PIN_Assign_B = 0, + .BITS.UFP_D_PIN_Assign_C = 0, + .BITS.UFP_D_PIN_Assign_D = 0, + .BITS.UFP_D_PIN_Assign_E = 0, + .BITS.UFP_D_PIN_Assign_F = 0, + .BITS.UFP_D_PIN_Reserved = 0, + .BITS.DP_MODE_Reserved = 0 + } +}; + +struct SS_DEX_DISCOVER_MODE SS_DEX_DISCOVER_MODE_MODE = { + { + .BITS.Num_Of_VDO = 1, + .BITS.Cmd_Type = ACK, + .BITS.Reserved = 0 + }, + { + .BITS.VDM_command = Discover_Modes, + .BITS.Rsvd2_VDM_header = 0, + .BITS.VDM_command_type = REQ, + .BITS.Object_Position = 0, + .BITS.Rsvd_VDM_header = 0, + .BITS.Structured_VDM_Version = Version_1_0, + .BITS.VDM_Type = STRUCTURED_VDM, + .BITS.Standard_Vendor_ID = SAMSUNG_VENDOR_ID + } +}; + +struct SS_DEX_ENTER_MODE SS_DEX_DISCOVER_ENTER_MODE = { + { + .BITS.Num_Of_VDO = 1, + .BITS.Cmd_Type = ACK, + .BITS.Reserved = 0 + }, + { + .BITS.VDM_command = Enter_Mode, + .BITS.Rsvd2_VDM_header = 0, + .BITS.VDM_command_type = REQ, + .BITS.Object_Position = 1, + .BITS.Rsvd_VDM_header = 0, + .BITS.Structured_VDM_Version = Version_1_0, + .BITS.VDM_Type = STRUCTURED_VDM, + .BITS.Standard_Vendor_ID = SAMSUNG_VENDOR_ID + } +}; + +struct SS_UNSTRUCTURED_VDM_MSG SS_DEX_UNSTRUCTURED_VDM; + +static char VDM_MSG_IRQ_State_Print[9][40] = { + {"bFLAG_Vdm_Reserve_b0"}, + {"bFLAG_Vdm_Discover_ID"}, + {"bFLAG_Vdm_Discover_SVIDs"}, + {"bFLAG_Vdm_Discover_MODEs"}, + {"bFLAG_Vdm_Enter_Mode"}, + {"bFLAG_Vdm_Exit_Mode"}, + {"bFLAG_Vdm_Attention"}, + {"bFlag_Vdm_DP_Status_Update"}, + {"bFlag_Vdm_DP_Configure"}, +}; +static char DP_Pin_Assignment_Print[7][40] = { + {"DP_Pin_Assignment_None"}, + {"DP_Pin_Assignment_A"}, + {"DP_Pin_Assignment_B"}, + {"DP_Pin_Assignment_C"}, + {"DP_Pin_Assignment_D"}, + {"DP_Pin_Assignment_E"}, + {"DP_Pin_Assignment_F"}, +}; + +static uint8_t DP_Pin_Assignment_Data[7] = { + DP_PIN_ASSIGNMENT_NODE, + DP_PIN_ASSIGNMENT_A, + DP_PIN_ASSIGNMENT_B, + DP_PIN_ASSIGNMENT_C, + DP_PIN_ASSIGNMENT_D, + DP_PIN_ASSIGNMENT_E, + DP_PIN_ASSIGNMENT_F, +}; + +bool max77705_check_hmd_dev(struct max77705_usbc_platform_data *usbpd_data) +{ + struct max77705_hmd_power_dev *hmd_list; + int i; + bool ret = false; + uint16_t vid = usbpd_data->Vendor_ID; + uint16_t pid = usbpd_data->Product_ID; + + if (!vid && !pid) + return ret; + hmd_list = usbpd_data->hmd_list; + if (!hmd_list) { + msg_maxim("hmd_list is null!"); + return ret; + } + for (i = 0; i < MAX_NUM_HMD; i++) { + if (strlen(hmd_list[i].hmd_name) > 0) + msg_maxim("%s,0x%04x,0x%04x", + hmd_list[i].hmd_name, + hmd_list[i].vid, + hmd_list[i].pid); + } + for (i = 0; i < MAX_NUM_HMD; i++) { + if (hmd_list[i].hmd_name[0]) { + if (vid == hmd_list[i].vid && pid == hmd_list[i].pid) { + msg_maxim("hmd found %s,0x%04x,0x%04x", + hmd_list[i].hmd_name, + hmd_list[i].vid, + hmd_list[i].pid); + ret = true; + break; + } + continue; + } + break; + } + + return ret; +} + +int max77705_process_check_accessory(void *data) +{ + struct max77705_usbc_platform_data *usbpd_data = data; +#if defined(CONFIG_USB_HW_PARAM) || IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + struct otg_notify *o_notify = get_otg_notify(); +#endif + uint16_t vid = usbpd_data->Vendor_ID; + uint16_t pid = usbpd_data->Product_ID; + uint16_t acc_type = PDIC_DOCK_DETACHED; + + /* detect Gear VR */ + if (usbpd_data->acc_type == PDIC_DOCK_DETACHED) { + if (vid == SAMSUNG_VENDOR_ID) { + switch (pid) { + /* GearVR: Reserved GearVR PID+6 */ + case GEARVR_PRODUCT_ID: + case GEARVR_PRODUCT_ID_1: + case GEARVR_PRODUCT_ID_2: + case GEARVR_PRODUCT_ID_3: + case GEARVR_PRODUCT_ID_4: + case GEARVR_PRODUCT_ID_5: + acc_type = PDIC_DOCK_HMT; + msg_maxim("Samsung Gear VR connected."); +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_VR_USE_COUNT); +#endif + break; + case DEXDOCK_PRODUCT_ID: + acc_type = PDIC_DOCK_DEX; + msg_maxim("Samsung DEX connected."); +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_DEX_USE_COUNT); +#endif + break; + case DEXPAD_PRODUCT_ID: + acc_type = PDIC_DOCK_DEXPAD; + msg_maxim("Samsung DEX PAD connected."); +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_DEX_USE_COUNT); +#endif + break; + case HDMI_PRODUCT_ID: + acc_type = PDIC_DOCK_HDMI; + msg_maxim("Samsung HDMI connected."); + break; + default: + msg_maxim("default device connected."); + acc_type = PDIC_DOCK_NEW; + break; + } + } else if (vid == SAMSUNG_MPA_VENDOR_ID) { + switch (pid) { + case MPA_PRODUCT_ID: + acc_type = PDIC_DOCK_MPA; + msg_maxim("Samsung MPA connected."); + break; + default: + msg_maxim("default device connected."); + acc_type = PDIC_DOCK_NEW; + break; + } + } else { + msg_maxim("unknown device connected."); + acc_type = PDIC_DOCK_NEW; + } + usbpd_data->acc_type = acc_type; + } else + acc_type = usbpd_data->acc_type; + + if (acc_type != PDIC_DOCK_NEW) + pdic_send_dock_intent(acc_type); + + pdic_send_dock_uevent(vid, pid, acc_type); + + mutex_lock(&usbpd_data->hmd_power_lock); + if (max77705_check_hmd_dev(usbpd_data)) { +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + if (o_notify) + send_otg_notify(o_notify, NOTIFY_EVENT_HMD_EXT_CURRENT, 1); +#endif + } + mutex_unlock(&usbpd_data->hmd_power_lock); + + return 1; +} + +void max77705_vdm_process_printf(char *type, char *vdm_data, int len) +{ +#if 0 + int i = 0; + + for (i = 2; i < len; i++) + msg_maxim("[%s] , %d, [0x%x]", type, i, vdm_data[i]); +#endif +} + +void max77705_vdm_process_set_identity_req(void *data) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + /* send the opcode */ + usbc_cmd_data write_data; + int len = sizeof(DP_DISCOVER_IDENTITY); + int vdm_header_num = sizeof(UND_DATA_MSG_VDM_HEADER_Type); + int vdo0_num = 0; + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_VDM_DISCOVER_SET_VDM_REQ; + memcpy(write_data.write_data, &DP_DISCOVER_IDENTITY, sizeof(DP_DISCOVER_IDENTITY)); + write_data.write_length = len; + vdo0_num = DP_DISCOVER_IDENTITY.byte_data.BITS.Num_Of_VDO * 4; + write_data.read_length = OPCODE_SIZE + OPCODE_HEADER_SIZE + vdm_header_num + vdo0_num; + max77705_usbc_opcode_write(usbpd_data, &write_data); +} + +void max77705_vdm_process_set_DP_enter_mode_req(void *data) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + usbc_cmd_data write_data; + int len = sizeof(DP_DISCOVER_ENTER_MODE); + int vdm_header_num = sizeof(UND_DATA_MSG_VDM_HEADER_Type); + int vdo0_num = 0; + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_VDM_DISCOVER_SET_VDM_REQ; + memcpy(write_data.write_data, &DP_DISCOVER_ENTER_MODE, sizeof(DP_DISCOVER_ENTER_MODE)); + write_data.write_length = len; + vdo0_num = DP_DISCOVER_ENTER_MODE.byte_data.BITS.Num_Of_VDO * 4; + write_data.read_length = OPCODE_SIZE + OPCODE_HEADER_SIZE + vdm_header_num + vdo0_num; + max77705_usbc_opcode_push(usbpd_data, &write_data); +} + +void max77705_vdm_process_set_DP_configure_mode_req(void *data, uint8_t W_DATA) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + usbc_cmd_data write_data; + int len = sizeof(DP_CONFIGURE); + int vdm_header_num = sizeof(UND_DATA_MSG_VDM_HEADER_Type); + int vdo0_num = 0; + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_VDM_DISCOVER_SET_VDM_REQ; + memcpy(write_data.write_data, &DP_CONFIGURE, sizeof(DP_CONFIGURE)); + write_data.write_data[6] = W_DATA; + write_data.write_length = len; + vdo0_num = DP_CONFIGURE.byte_data.BITS.Num_Of_VDO * 4; + write_data.read_length = OPCODE_SIZE + OPCODE_HEADER_SIZE + vdm_header_num + vdo0_num; + max77705_usbc_opcode_push(usbpd_data, &write_data); +} + +void max77705_vdm_process_set_Dex_Disover_mode_req(void *data) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + usbc_cmd_data write_data; + int len = sizeof(SS_DEX_DISCOVER_MODE_MODE); + int vdm_header_num = sizeof(UND_DATA_MSG_VDM_HEADER_Type); + int vdo0_num = 0; + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_VDM_DISCOVER_SET_VDM_REQ; + memcpy(write_data.write_data, &SS_DEX_DISCOVER_MODE_MODE, sizeof(SS_DEX_DISCOVER_MODE_MODE)); + write_data.write_length = len; + vdo0_num = SS_DEX_DISCOVER_MODE_MODE.byte_data.BITS.Num_Of_VDO * 4; + write_data.read_length = OPCODE_SIZE + OPCODE_HEADER_SIZE + vdm_header_num + vdo0_num; + max77705_usbc_opcode_push(usbpd_data, &write_data); +} + +void max77705_vdm_process_set_Dex_enter_mode_req(void *data) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + usbc_cmd_data write_data; + int len = sizeof(SS_DEX_DISCOVER_ENTER_MODE); + int vdm_header_num = sizeof(UND_DATA_MSG_VDM_HEADER_Type); + int vdo0_num = 0; + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_VDM_DISCOVER_SET_VDM_REQ; + memcpy(write_data.write_data, &SS_DEX_DISCOVER_ENTER_MODE, sizeof(SS_DEX_DISCOVER_ENTER_MODE)); + write_data.write_length = len; + vdo0_num = SS_DEX_DISCOVER_ENTER_MODE.byte_data.BITS.Num_Of_VDO * 4; + write_data.read_length = OPCODE_SIZE + OPCODE_HEADER_SIZE + vdm_header_num + vdo0_num; + max77705_usbc_opcode_push(usbpd_data, &write_data); +} + +static int max77705_vdm_process_discover_svids(void *data, char *vdm_data, int len) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + int i = 0; +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + int timeleft = 0; + struct otg_notify *o_notify = get_otg_notify(); +#endif + + uint16_t svid = 0; + DIS_MODE_DP_CAPA_Type *pDP_DIS_MODE = (DIS_MODE_DP_CAPA_Type *)&vdm_data[0]; + /* Number_of_obj has msg_header & vdm_header, each vdo has 2 svids */ + /* This logic can work until Max VDOs 12 */ + int num_of_vdos = (pDP_DIS_MODE->MSG_HEADER.BITS.Number_of_obj - 2) * 2; + UND_VDO1_Type *DATA_MSG_VDO1 = (UND_VDO1_Type *)&vdm_data[8]; + usbpd_data->SVID_DP = 0; + usbpd_data->SVID_0 = DATA_MSG_VDO1->BITS.SVID_0; + usbpd_data->SVID_1 = DATA_MSG_VDO1->BITS.SVID_1; + + for (i = 0; i < num_of_vdos; i++) { + memcpy(&svid, &vdm_data[8 + i * 2], 2); + if (svid == TypeC_DP_SUPPORT) { + msg_maxim("svid_%d : 0x%X", i, svid); + usbpd_data->SVID_DP = svid; + break; + } + } + + if (usbpd_data->SVID_DP == TypeC_DP_SUPPORT) { + if (usbpd_data->is_client == CLIENT_ON) { + max77705_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_MUIC, + PDIC_NOTIFY_ID_ATTACH, 0/*attach*/, 0/*rprd*/, 0); + max77705_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_USB, PDIC_NOTIFY_ID_USB, + 0/*attach*/, USB_STATUS_NOTIFY_DETACH/*drp*/, 0); + usbpd_data->is_client = CLIENT_OFF; + } + + if (usbpd_data->is_host == HOST_OFF) { + /* muic */ + max77705_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_MUIC, + PDIC_NOTIFY_ID_ATTACH, 1/*attach*/, 1/*rprd*/, 0); + /* otg */ + usbpd_data->is_host = HOST_ON; + max77705_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_USB, PDIC_NOTIFY_ID_USB, + 1/*attach*/, USB_STATUS_NOTIFY_ATTACH_DFP/*drp*/, 0); + } + usbpd_data->dp_is_connect = 1; + /* If you want to support USB SuperSpeed when you connect + * Display port dongle, You should change dp_hs_connect depend + * on Pin assignment.If DP use 4lane(Pin Assignment C,E,A), + * dp_hs_connect is 1. USB can support HS.If DP use 2lane(Pin Assigment B,D,F), dp_hs_connect is 0. USB + * can support SS + */ + usbpd_data->dp_hs_connect = 1; + +#if 1 + max77705_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_DP, PDIC_NOTIFY_ID_DP_CONNECT, + PDIC_NOTIFY_ATTACH, usbpd_data->Vendor_ID, usbpd_data->Product_ID); +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + if (o_notify) { +#if defined(CONFIG_USB_HW_PARAM) + inc_hw_param(o_notify, USB_CCIC_DP_USE_COUNT); +#endif + msg_maxim("%s wait_event %d\n", __func__, + (usbpd_data->host_turn_on_wait_time)*HZ); + + timeleft = wait_event_interruptible_timeout(usbpd_data->host_turn_on_wait_q, + usbpd_data->host_turn_on_event && !usbpd_data->detach_done_wait +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + && !usbpd_data->wait_entermode +#endif + , (usbpd_data->host_turn_on_wait_time)*HZ); + msg_maxim("%s host turn on wait = %d\n", __func__, timeleft); + } +#endif + max77705_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_USB_DP, PDIC_NOTIFY_ID_USB_DP, + usbpd_data->dp_is_connect /*attach*/, usbpd_data->dp_hs_connect, 0); +#endif + } + msg_maxim("SVID_0 : 0x%X, SVID_1 : 0x%X", + usbpd_data->SVID_0, usbpd_data->SVID_1); + return 0; +} + +static int max77705_vdm_process_discover_mode(void *data, char *vdm_data, int len) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + DIS_MODE_DP_CAPA_Type *pDP_DIS_MODE = (DIS_MODE_DP_CAPA_Type *)&vdm_data[0]; + UND_DATA_MSG_VDM_HEADER_Type *DATA_MSG_VDM = (UND_DATA_MSG_VDM_HEADER_Type *)&vdm_data[4]; + + msg_maxim("vendor_id = 0x%04x , svid_1 = 0x%04x", DATA_MSG_VDM->BITS.Standard_Vendor_ID, usbpd_data->SVID_1); + if (DATA_MSG_VDM->BITS.Standard_Vendor_ID == TypeC_DP_SUPPORT && usbpd_data->SVID_DP == TypeC_DP_SUPPORT) { + /* pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.BITS. */ + msg_maxim("pDP_DIS_MODE->MSG_HEADER.DATA = 0x%08X", pDP_DIS_MODE->MSG_HEADER.DATA); + msg_maxim("pDP_DIS_MODE->DATA_MSG_VDM_HEADER.DATA = 0x%08X", pDP_DIS_MODE->DATA_MSG_VDM_HEADER.DATA); + msg_maxim("pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.DATA = 0x%08X", pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.DATA); + + if (pDP_DIS_MODE->MSG_HEADER.BITS.Number_of_obj > 1) { + if ((pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.BITS.Port_Capability == num_UFP_D_Capable) + && (pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.BITS.Receptacle_Indication == num_USB_TYPE_C_Receptacle)) { + usbpd_data->pin_assignment = pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.BITS.UFP_D_Pin_Assignments; + msg_maxim("1. support UFP_D 0x%08x", usbpd_data->pin_assignment); + } else if (((pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.BITS.Port_Capability == num_UFP_D_Capable) + && (pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.BITS.Receptacle_Indication == num_USB_TYPE_C_PLUG))) { + usbpd_data->pin_assignment = pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.BITS.DFP_D_Pin_Assignments; + msg_maxim("2. support DFP_D 0x%08x", usbpd_data->pin_assignment); + } else if (pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.BITS.Port_Capability == num_DFP_D_and_UFP_D_Capable) { + if (pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.BITS.Receptacle_Indication == num_USB_TYPE_C_PLUG) { + usbpd_data->pin_assignment = pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.BITS.DFP_D_Pin_Assignments; + msg_maxim("3. support DFP_D 0x%08x", usbpd_data->pin_assignment); + } else { + usbpd_data->pin_assignment = pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.BITS.UFP_D_Pin_Assignments; + msg_maxim("4. support UFP_D 0x%08x", usbpd_data->pin_assignment); + } + } else if (pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.BITS.Port_Capability == num_DFP_D_Capable) { + usbpd_data->pin_assignment = DP_PIN_ASSIGNMENT_NODE; + msg_maxim("do not support Port_Capability num_DFP_D_Capable!!!"); + return -EINVAL; + } else { + usbpd_data->pin_assignment = DP_PIN_ASSIGNMENT_NODE; + msg_maxim("there is not valid object information!!!"); + return -EINVAL; + } + } + } + + if (DATA_MSG_VDM->BITS.Standard_Vendor_ID == SAMSUNG_VENDOR_ID) { + msg_maxim("dex mode discover_mode ack status!"); + /* pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.BITS. */ + msg_maxim("pDP_DIS_MODE->MSG_HEADER.DATA = 0x%08X", pDP_DIS_MODE->MSG_HEADER.DATA); + msg_maxim("pDP_DIS_MODE->DATA_MSG_VDM_HEADER.DATA = 0x%08X", pDP_DIS_MODE->DATA_MSG_VDM_HEADER.DATA); + msg_maxim("pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.DATA = 0x%08X", pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.DATA); + /*Samsung Enter Mode */ + if (usbpd_data->send_enter_mode_req == 0) { + msg_maxim("dex: second enter mode request"); + usbpd_data->send_enter_mode_req = 1; + max77705_vdm_process_set_Dex_enter_mode_req(usbpd_data); + } + } else { + max77705_vdm_process_set_DP_enter_mode_req(usbpd_data); + } + + return 0; +} + +static int max77705_vdm_process_enter_mode(void *data, char *vdm_data, int len) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + + UND_DATA_MSG_VDM_HEADER_Type *DATA_MSG_VDM = (UND_DATA_MSG_VDM_HEADER_Type *)&vdm_data[4]; + + if (DATA_MSG_VDM->BITS.VDM_command_type == 1) { + msg_maxim("EnterMode ACK."); + if (DATA_MSG_VDM->BITS.Standard_Vendor_ID == SAMSUNG_VENDOR_ID) { + usbpd_data->is_samsung_accessory_enter_mode = 1; + msg_maxim("dex mode enter_mode ack status!"); + } + } else { + msg_maxim("EnterMode NAK."); + } + return 0; +} + +static int max77705_vdm_dp_select_pin(void *data, int multi) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + int pin_sel = 0; + + if (multi) { + if (usbpd_data->pin_assignment & DP_PIN_ASSIGNMENT_D) + pin_sel = PDIC_NOTIFY_DP_PIN_D; + else if (usbpd_data->pin_assignment & DP_PIN_ASSIGNMENT_B) + pin_sel = PDIC_NOTIFY_DP_PIN_B; + else if (usbpd_data->pin_assignment & DP_PIN_ASSIGNMENT_F) + pin_sel = PDIC_NOTIFY_DP_PIN_F; + else if (usbpd_data->pin_assignment & DP_PIN_ASSIGNMENT_C) + pin_sel = PDIC_NOTIFY_DP_PIN_C; + else if (usbpd_data->pin_assignment & DP_PIN_ASSIGNMENT_E) + pin_sel = PDIC_NOTIFY_DP_PIN_E; + else if (usbpd_data->pin_assignment & DP_PIN_ASSIGNMENT_A) + pin_sel = PDIC_NOTIFY_DP_PIN_A; + else + msg_maxim("wrong pin assignment value"); + } else { + if (usbpd_data->pin_assignment & DP_PIN_ASSIGNMENT_C) + pin_sel = PDIC_NOTIFY_DP_PIN_C; + else if (usbpd_data->pin_assignment & DP_PIN_ASSIGNMENT_E) + pin_sel = PDIC_NOTIFY_DP_PIN_E; + else if (usbpd_data->pin_assignment & DP_PIN_ASSIGNMENT_A) + pin_sel = PDIC_NOTIFY_DP_PIN_A; + else if (usbpd_data->pin_assignment & DP_PIN_ASSIGNMENT_D) + pin_sel = PDIC_NOTIFY_DP_PIN_D; + else if (usbpd_data->pin_assignment & DP_PIN_ASSIGNMENT_B) + pin_sel = PDIC_NOTIFY_DP_PIN_B; + else if (usbpd_data->pin_assignment & DP_PIN_ASSIGNMENT_F) + pin_sel = PDIC_NOTIFY_DP_PIN_F; + else + msg_maxim("wrong pin assignment value"); + } +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + if (pin_sel == PDIC_NOTIFY_DP_PIN_C || + pin_sel == PDIC_NOTIFY_DP_PIN_E || + pin_sel == PDIC_NOTIFY_DP_PIN_A) + usb_restart_host_mode(usbpd_data->man, 4); + else + usb_restart_host_mode(usbpd_data->man, 2); +#endif + + return pin_sel; +} + +static int max77705_vdm_dp_status_update(void *data, char *vdm_data, int len) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + int i; + uint8_t multi_func = 0; + int pin_sel = 0; + int hpd = 0; + int hpdirq = 0; + VDO_MESSAGE_Type *VDO_MSG; + DP_STATUS_UPDATE_Type *DP_STATUS; + uint8_t W_DATA = 0x0; + + if (usbpd_data->SVID_DP == TypeC_DP_SUPPORT) { + DP_STATUS = (DP_STATUS_UPDATE_Type *)&vdm_data[0]; + + msg_maxim("DP_STATUS_UPDATE = 0x%08X", DP_STATUS->DATA_DP_STATUS_UPDATE.DATA); + + if (DP_STATUS->DATA_DP_STATUS_UPDATE.BITS.Port_Connected == 0x00) { + msg_maxim("port disconnected!"); + } else { + if (usbpd_data->is_sent_pin_configuration == 0) { + multi_func = DP_STATUS->DATA_DP_STATUS_UPDATE.BITS.Multi_Function_Preference; + pin_sel = max77705_vdm_dp_select_pin(usbpd_data, multi_func); + usbpd_data->dp_selected_pin = pin_sel; + W_DATA = DP_Pin_Assignment_Data[pin_sel]; + + msg_maxim("multi_func_preference %d, %s, W_DATA : %d", + multi_func, DP_Pin_Assignment_Print[pin_sel], W_DATA); + + max77705_vdm_process_set_DP_configure_mode_req(data, W_DATA); + + usbpd_data->is_sent_pin_configuration = 1; + } else { + msg_maxim("pin configuration is already sent as %s!", + DP_Pin_Assignment_Print[usbpd_data->dp_selected_pin]); + } + } + + if (DP_STATUS->DATA_DP_STATUS_UPDATE.BITS.HPD_State == 1) + hpd = PDIC_NOTIFY_HIGH; + else if (DP_STATUS->DATA_DP_STATUS_UPDATE.BITS.HPD_State == 0) + hpd = PDIC_NOTIFY_LOW; + + if (DP_STATUS->DATA_DP_STATUS_UPDATE.BITS.HPD_Interrupt == 1) + hpdirq = PDIC_NOTIFY_IRQ; + +#if 1 + max77705_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_DP, PDIC_NOTIFY_ID_DP_HPD, + hpd, hpdirq, 0); +#endif + } else { + /* need to check F/W code */ + VDO_MSG = (VDO_MESSAGE_Type *)&vdm_data[8]; + for (i = 0; i < 6; i++) + msg_maxim("VDO_%d : %d", i+1, VDO_MSG->VDO[i]); + } + return 0; +} + +static int max77705_vdm_dp_attention(void *data, char *vdm_data, int len) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + int i; + int hpd = 0; + int hpdirq = 0; + uint8_t multi_func = 0; + int pin_sel = 0; + + VDO_MESSAGE_Type *VDO_MSG; + DIS_ATTENTION_MESSAGE_DP_STATUS_Type *DP_ATTENTION; + uint8_t W_DATA = 0; + + if (usbpd_data->SVID_DP == TypeC_DP_SUPPORT) { + DP_ATTENTION = (DIS_ATTENTION_MESSAGE_DP_STATUS_Type *)&vdm_data[0]; + + msg_maxim("%s DP_ATTENTION = 0x%08X\n", __func__, + DP_ATTENTION->DATA_MSG_DP_STATUS.DATA); + if (usbpd_data->is_sent_pin_configuration == 0) { + multi_func = DP_ATTENTION->DATA_MSG_DP_STATUS.BITS.Multi_Function_Preference; + pin_sel = max77705_vdm_dp_select_pin(usbpd_data, multi_func); + usbpd_data->dp_selected_pin = pin_sel; + W_DATA = DP_Pin_Assignment_Data[pin_sel]; + + msg_maxim("multi_func_preference %d, %s, W_DATA : %d\n", + multi_func, DP_Pin_Assignment_Print[pin_sel], W_DATA); + + max77705_vdm_process_set_DP_configure_mode_req(data, W_DATA); + usbpd_data->is_sent_pin_configuration = 1; + } else { + msg_maxim("%s : pin configuration is already sent as %s!\n", __func__, + DP_Pin_Assignment_Print[usbpd_data->dp_selected_pin]); + } + if (DP_ATTENTION->DATA_MSG_DP_STATUS.BITS.HPD_State == 1) + hpd = PDIC_NOTIFY_HIGH; + else if (DP_ATTENTION->DATA_MSG_DP_STATUS.BITS.HPD_State == 0) + hpd = PDIC_NOTIFY_LOW; + + if (DP_ATTENTION->DATA_MSG_DP_STATUS.BITS.HPD_Interrupt == 1) + hpdirq = PDIC_NOTIFY_IRQ; + + max77705_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_DP, PDIC_NOTIFY_ID_DP_HPD, + hpd, hpdirq, 0); + } else { + /* need to check the F/W code. */ + VDO_MSG = (VDO_MESSAGE_Type *)&vdm_data[8]; + + for (i = 0; i < 6; i++) + msg_maxim("%s : VDO_%d : %d\n", __func__, + i+1, VDO_MSG->VDO[i]); + } + + return 0; +} + +static int max77705_vdm_dp_configure(void *data, char *vdm_data, int len) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + UND_DATA_MSG_VDM_HEADER_Type *DATA_MSG_VDM = (UND_DATA_MSG_VDM_HEADER_Type *)&vdm_data[4]; + + msg_maxim("vendor_id = 0x%04x , svid_1 = 0x%04x", DATA_MSG_VDM->BITS.Standard_Vendor_ID, usbpd_data->SVID_1); + if (usbpd_data->SVID_DP == TypeC_DP_SUPPORT) + schedule_work(&usbpd_data->dp_configure_work); + if (DATA_MSG_VDM->BITS.Standard_Vendor_ID == TypeC_DP_SUPPORT && usbpd_data->SVID_1 == TypeC_Dex_SUPPORT) { + /* Samsung Discover Modes packet */ + usbpd_data->send_enter_mode_req = 0; + max77705_vdm_process_set_Dex_Disover_mode_req(usbpd_data); + } + + return 0; +} + +void max77705_vdm_message_handler(struct max77705_usbc_platform_data *usbpd_data, + char *opcode_data, int len) +{ + unsigned char vdm_data[OPCODE_DATA_LENGTH] = {0,}; + UND_DATA_MSG_ID_HEADER_Type *DATA_MSG_ID = NULL; + UND_PRODUCT_VDO_Type *DATA_MSG_PRODUCT = NULL; + UND_DATA_MSG_VDM_HEADER_Type vdm_header; + + memset(&vdm_header, 0, sizeof(UND_DATA_MSG_VDM_HEADER_Type)); + memcpy(vdm_data, opcode_data, len); + memcpy(&vdm_header, &vdm_data[4], sizeof(vdm_header)); + if ((vdm_header.BITS.VDM_command_type) == SEC_UVDM_RESPONDER_NAK) { + msg_maxim("IGNORE THE NAK RESPONSE !!![%d]", vdm_data[1]); + return; + } + + switch (vdm_data[1]) { + case OPCODE_ID_VDM_DISCOVER_IDENTITY: + max77705_vdm_process_printf("VDM_DISCOVER_IDENTITY", vdm_data, len); + /* Message Type Definition */ + DATA_MSG_ID = (UND_DATA_MSG_ID_HEADER_Type *)&vdm_data[8]; + DATA_MSG_PRODUCT = (UND_PRODUCT_VDO_Type *)&vdm_data[16]; + usbpd_data->is_sent_pin_configuration = 0; + usbpd_data->is_samsung_accessory_enter_mode = 0; + usbpd_data->Vendor_ID = DATA_MSG_ID->BITS.USB_Vendor_ID; + usbpd_data->Product_ID = DATA_MSG_PRODUCT->BITS.Product_ID; + usbpd_data->Device_Version = DATA_MSG_PRODUCT->BITS.Device_Version; + msg_maxim("Vendor_ID : 0x%X, Product_ID : 0x%X Device Version 0x%X", + usbpd_data->Vendor_ID, usbpd_data->Product_ID, usbpd_data->Device_Version); + max77705_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_ALL, PDIC_NOTIFY_ID_DEVICE_INFO, + usbpd_data->Vendor_ID, usbpd_data->Product_ID, usbpd_data->Device_Version); + if (max77705_process_check_accessory(usbpd_data)) + msg_maxim("Samsung Accessory Connected."); + break; + case OPCODE_ID_VDM_DISCOVER_SVIDS: + max77705_vdm_process_printf("VDM_DISCOVER_SVIDS", vdm_data, len); + max77705_vdm_process_discover_svids(usbpd_data, vdm_data, len); + break; + case OPCODE_ID_VDM_DISCOVER_MODES: + max77705_vdm_process_printf("VDM_DISCOVER_MODES", vdm_data, len); + vdm_data[0] = vdm_data[2]; + vdm_data[1] = vdm_data[3]; + max77705_vdm_process_discover_mode(usbpd_data, vdm_data, len); + break; + case OPCODE_ID_VDM_ENTER_MODE: + max77705_vdm_process_printf("VDM_ENTER_MODE", vdm_data, len); + max77705_vdm_process_enter_mode(usbpd_data, vdm_data, len); + break; + case OPCODE_ID_VDM_SVID_DP_STATUS: + max77705_vdm_process_printf("VDM_SVID_DP_STATUS", vdm_data, len); + vdm_data[0] = vdm_data[2]; + vdm_data[1] = vdm_data[3]; + max77705_vdm_dp_status_update(usbpd_data, vdm_data, len); + break; + case OPCODE_ID_VDM_SVID_DP_CONFIGURE: + max77705_vdm_process_printf("VDM_SVID_DP_CONFIGURE", vdm_data, len); + max77705_vdm_dp_configure(usbpd_data, vdm_data, len); + break; + case OPCODE_ID_VDM_ATTENTION: + max77705_vdm_process_printf("VDM_ATTENTION", vdm_data, len); + vdm_data[0] = vdm_data[2]; + vdm_data[1] = vdm_data[3]; + max77705_vdm_dp_attention(usbpd_data, vdm_data, len); + break; + case OPCODE_ID_VDM_EXIT_MODE: + + break; + + default: + break; + } +} + +static int max77705_process_discover_identity(void *data) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + /* send the opcode */ + usbc_cmd_data write_data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_VDM_DISCOVER_GET_VDM_RESP; + write_data.write_data[0] = OPCODE_ID_VDM_DISCOVER_IDENTITY; + write_data.write_length = 0x1; + write_data.read_length = 31; + max77705_usbc_opcode_write(usbpd_data, &write_data); + return 0; +} + +static int max77705_process_discover_svids(void *data) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + /* send the opcode */ + usbc_cmd_data write_data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_VDM_DISCOVER_GET_VDM_RESP; + write_data.write_data[0] = OPCODE_ID_VDM_DISCOVER_SVIDS; + write_data.write_length = 0x1; + write_data.read_length = 31; + max77705_usbc_opcode_write(usbpd_data, &write_data); + return 0; +} + +static int max77705_process_discover_modes(void *data) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + /* send the opcode */ + usbc_cmd_data write_data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_VDM_DISCOVER_GET_VDM_RESP; + write_data.write_data[0] = OPCODE_ID_VDM_DISCOVER_MODES; + write_data.write_length = 0x1; + write_data.read_length = 11; + max77705_usbc_opcode_write(usbpd_data, &write_data); + return 0; +} + +static int max77705_process_enter_mode(void *data) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + /* send the opcode */ + usbc_cmd_data write_data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_VDM_DISCOVER_GET_VDM_RESP; + write_data.write_data[0] = OPCODE_ID_VDM_ENTER_MODE; + write_data.write_length = 0x1; + write_data.read_length = 7; + max77705_usbc_opcode_write(usbpd_data, &write_data); + return 0; +} + +static int max77705_process_attention(void *data) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + /* send the opcode */ + usbc_cmd_data write_data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_VDM_DISCOVER_GET_VDM_RESP; + write_data.write_data[0] = OPCODE_ID_VDM_ATTENTION; + write_data.write_length = 0x1; + write_data.read_length = 11; + max77705_usbc_opcode_write(usbpd_data, &write_data); + return 0; +} + +static int max77705_process_dp_status_update(void *data) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + /* send the opcode */ + usbc_cmd_data write_data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_VDM_DISCOVER_GET_VDM_RESP; + write_data.write_data[0] = OPCODE_ID_VDM_SVID_DP_STATUS; + write_data.write_length = 0x1; + write_data.read_length = 11; + max77705_usbc_opcode_write(usbpd_data, &write_data); + return 0; +} + +static int max77705_process_dp_configure(void *data) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + /* send the opcode */ + usbc_cmd_data write_data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_VDM_DISCOVER_GET_VDM_RESP; + write_data.write_data[0] = OPCODE_ID_VDM_SVID_DP_CONFIGURE; + write_data.write_length = 0x1; + write_data.read_length = 11; + max77705_usbc_opcode_write(usbpd_data, &write_data); + return 0; +} + +static void max77705_process_alternate_mode(void *data) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + uint32_t mode = usbpd_data->alternate_state; + int ret = 0; +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + struct otg_notify *o_notify = get_otg_notify(); +#endif + + if (mode) { + msg_maxim("mode : 0x%x", mode); +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + if (is_blocked(o_notify, NOTIFY_BLOCK_TYPE_HOST)) { + msg_maxim("host is blocked, skip all the alternate mode."); + goto process_error; + } +#endif + if (usbpd_data->mdm_block) { + msg_maxim("is blocked by mdm, skip all the alternate mode."); + goto process_error; + } + + if (mode & VDM_DISCOVER_ID) + ret = max77705_process_discover_identity(usbpd_data); + if (ret) + goto process_error; + if (mode & VDM_DISCOVER_SVIDS) + ret = max77705_process_discover_svids(usbpd_data); + if (ret) + goto process_error; + if (mode & VDM_DISCOVER_MODES) + ret = max77705_process_discover_modes(usbpd_data); + if (ret) + goto process_error; + if (mode & VDM_ENTER_MODE) + ret = max77705_process_enter_mode(usbpd_data); + if (ret) + goto process_error; + if (mode & VDM_DP_STATUS_UPDATE) + ret = max77705_process_dp_status_update(usbpd_data); + if (ret) + goto process_error; + if (mode & VDM_DP_CONFIGURE) + ret = max77705_process_dp_configure(usbpd_data); + if (ret) + goto process_error; + if (mode & VDM_ATTENTION) + ret = max77705_process_attention(usbpd_data); +process_error: + usbpd_data->alternate_state = 0; + } +} + +void max77705_receive_alternate_message(struct max77705_usbc_platform_data *data, MAX77705_VDM_MSG_IRQ_STATUS_Type *VDM_MSG_IRQ_State) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + static int last_alternate = 0; + +DISCOVER_ID: + if (VDM_MSG_IRQ_State->BITS.Vdm_Flag_Discover_ID) { + msg_maxim(": %s", &VDM_MSG_IRQ_State_Print[1][0]); + usbpd_data->alternate_state |= VDM_DISCOVER_ID; + last_alternate = VDM_DISCOVER_ID; + } + +DISCOVER_SVIDS: + if (VDM_MSG_IRQ_State->BITS.Vdm_Flag_Discover_SVIDs) { + msg_maxim(": %s", &VDM_MSG_IRQ_State_Print[2][0]); + if (last_alternate != VDM_DISCOVER_ID) { + msg_maxim("%s vdm miss\n", __func__); + VDM_MSG_IRQ_State->BITS.Vdm_Flag_Discover_ID = 1; + goto DISCOVER_ID; + } + usbpd_data->alternate_state |= VDM_DISCOVER_SVIDS; + last_alternate = VDM_DISCOVER_SVIDS; + } + +DISCOVER_MODES: + if (VDM_MSG_IRQ_State->BITS.Vdm_Flag_Discover_MODEs) { + msg_maxim(": %s", &VDM_MSG_IRQ_State_Print[3][0]); + if (last_alternate != VDM_DISCOVER_SVIDS && + last_alternate != VDM_DP_CONFIGURE) { + msg_maxim("%s vdm miss\n", __func__); + VDM_MSG_IRQ_State->BITS.Vdm_Flag_Discover_SVIDs = 1; + goto DISCOVER_SVIDS; + } + usbpd_data->alternate_state |= VDM_DISCOVER_MODES; + last_alternate = VDM_DISCOVER_MODES; + } + + if (VDM_MSG_IRQ_State->BITS.Vdm_Flag_Enter_Mode) { + msg_maxim(": %s", &VDM_MSG_IRQ_State_Print[4][0]); + if (last_alternate != VDM_DISCOVER_MODES) { + msg_maxim("%s vdm miss\n", __func__); + VDM_MSG_IRQ_State->BITS.Vdm_Flag_Discover_MODEs = 1; + goto DISCOVER_MODES; + } + usbpd_data->alternate_state |= VDM_ENTER_MODE; + last_alternate = VDM_ENTER_MODE; + } + if (VDM_MSG_IRQ_State->BITS.Vdm_Flag_Exit_Mode) { + msg_maxim(": %s", &VDM_MSG_IRQ_State_Print[5][0]); + usbpd_data->alternate_state |= VDM_EXIT_MODE; + } + if (VDM_MSG_IRQ_State->BITS.Vdm_Flag_Attention) { + msg_maxim(": %s", &VDM_MSG_IRQ_State_Print[6][0]); + usbpd_data->alternate_state |= VDM_ATTENTION; + } + if (VDM_MSG_IRQ_State->BITS.Vdm_Flag_DP_Status_Update) { + msg_maxim(": %s", &VDM_MSG_IRQ_State_Print[7][0]); + usbpd_data->alternate_state |= VDM_DP_STATUS_UPDATE; + last_alternate = VDM_DP_STATUS_UPDATE; + } + if (VDM_MSG_IRQ_State->BITS.Vdm_Flag_DP_Configure) { + msg_maxim(": %s", &VDM_MSG_IRQ_State_Print[8][0]); + usbpd_data->alternate_state |= VDM_DP_CONFIGURE; + last_alternate = VDM_DP_CONFIGURE; + } + + max77705_process_alternate_mode(usbpd_data); +} + +void max77705_send_dex_fan_unstructured_vdm_message(void *data, int cmd) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + uint8_t SendMSG[32] = {0,}; + uint32_t message = (uint32_t)cmd; + usbc_cmd_data write_data; + int len = sizeof(SS_DEX_UNSTRUCTURED_VDM); + VDO_MESSAGE_Type *VDO_MSG = (VDO_MESSAGE_Type *)&SendMSG[5]; + + SS_DEX_UNSTRUCTURED_VDM.byte_data.BITS.Num_Of_VDO = 2; + SS_DEX_UNSTRUCTURED_VDM.byte_data.BITS.Cmd_Type = ACK; + SS_DEX_UNSTRUCTURED_VDM.byte_data.BITS.Reserved = 0; + SS_DEX_UNSTRUCTURED_VDM.VDO_MSG.VDO[0] = 0x04E80000; + SS_DEX_UNSTRUCTURED_VDM.VDO_MSG.VDO[1] = 0xA0200110; + pr_info("%s: cmd : 0x%x\n", __func__, cmd); + memcpy(SendMSG, &SS_DEX_UNSTRUCTURED_VDM, len); + + VDO_MSG->VDO[0] = message; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_VDM_DISCOVER_SET_VDM_REQ; + + memcpy(write_data.write_data, SendMSG, sizeof(SendMSG)); + write_data.write_length = len; + write_data.read_length = 31; + max77705_usbc_opcode_write(usbpd_data, &write_data); + msg_maxim("message : 0x%x", message); +} + +void max77705_acc_detach_check(struct work_struct *wk) +{ + struct delayed_work *delay_work = + container_of(wk, struct delayed_work, work); + struct max77705_usbc_platform_data *usbpd_data = + container_of(delay_work, struct max77705_usbc_platform_data, acc_detach_work); +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + struct otg_notify *o_notify = get_otg_notify(); +#endif + + pr_info("%s : pd_state : %d, acc_type : %d\n", __func__, + usbpd_data->pd_state, usbpd_data->acc_type); + if (usbpd_data->pd_state == max77705_State_PE_Initial_detach) { + if (usbpd_data->acc_type != PDIC_DOCK_DETACHED) { + if (usbpd_data->acc_type != PDIC_DOCK_NEW) + pdic_send_dock_intent(PDIC_DOCK_DETACHED); + pdic_send_dock_uevent(usbpd_data->Vendor_ID, + usbpd_data->Product_ID, + PDIC_DOCK_DETACHED); + usbpd_data->acc_type = PDIC_DOCK_DETACHED; + usbpd_data->Vendor_ID = 0; + usbpd_data->Product_ID = 0; + usbpd_data->send_enter_mode_req = 0; + usbpd_data->Device_Version = 0; + max77705_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_ALL, PDIC_NOTIFY_ID_CLEAR_INFO, + PDIC_NOTIFY_ID_DEVICE_INFO, 0, 0); +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + if (o_notify) + send_otg_notify(o_notify, NOTIFY_EVENT_HMD_EXT_CURRENT, 0); +#endif + } + } +} + +void max77705_vdm_process_set_samsung_alternate_mode(void *data, int mode) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + usbc_cmd_data write_data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_SET_ALTERNATEMODE; + write_data.write_data[0] = mode; + write_data.write_length = 0x1; + write_data.read_length = 0x1; + max77705_usbc_opcode_write(usbpd_data, &write_data); +} + +void max77705_set_enable_alternate_mode(int mode) +{ + struct max77705_usbc_platform_data *usbpd_data = NULL; + static int check_is_driver_loaded; + static int prev_alternate_mode; + int is_first_booting = 0; + struct max77705_pd_data *pd_data = NULL; + u8 status[11] = {0, }; + + usbpd_data = g_usbc_data; + + if (!usbpd_data) + return; + is_first_booting = usbpd_data->is_first_booting; + pd_data = usbpd_data->pd_data; + + msg_maxim("is_first_booting : %x mode %x", + usbpd_data->is_first_booting, mode); +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + store_usblog_notify(NOTIFY_ALTERNATEMODE, (void *)&mode, NULL); +#endif + usbpd_data->set_altmode = mode; + + if ((mode & ALTERNATE_MODE_NOT_READY) && + (mode & ALTERNATE_MODE_READY)) { + msg_maxim("mode is invalid!"); + return; + } + if ((mode & ALTERNATE_MODE_START) && (mode & ALTERNATE_MODE_STOP)) { + msg_maxim("mode is invalid!"); + return; + } + if (mode & ALTERNATE_MODE_RESET) { + msg_maxim("mode is reset! check_is_driver_loaded=%d, prev_alternate_mode=%d", + check_is_driver_loaded, prev_alternate_mode); + if (check_is_driver_loaded && + (prev_alternate_mode == ALTERNATE_MODE_START)) { + + msg_maxim("[No process] alternate mode is reset as start!"); + prev_alternate_mode = ALTERNATE_MODE_START; + } else if (check_is_driver_loaded && + (prev_alternate_mode == ALTERNATE_MODE_STOP)) { + msg_maxim("[No process] alternate mode is reset as stop!"); + prev_alternate_mode = ALTERNATE_MODE_STOP; + } else { + ; + } + } else { + if (mode & ALTERNATE_MODE_NOT_READY) { + check_is_driver_loaded = 0; + msg_maxim("alternate mode is not ready!"); + } else if (mode & ALTERNATE_MODE_READY) { + check_is_driver_loaded = 1; + msg_maxim("alternate mode is ready!"); + } else { + ; + } + + if (check_is_driver_loaded) { + switch (is_first_booting) { + case 0: /*this routine is calling after complete a booting.*/ + if (mode & ALTERNATE_MODE_START) { + max77705_vdm_process_set_samsung_alternate_mode(usbpd_data, + MAXIM_ENABLE_ALTERNATE_SRC_VDM); + msg_maxim("[NO BOOTING TIME] !!!alternate mode is started!"); + if (usbpd_data->cc_data->current_pr == SNK && (pd_data->current_dr == DFP)) { + max77705_vdm_process_set_identity_req(usbpd_data); + msg_maxim("[NO BOOTING TIME] SEND THE PACKET (DEX HUB) "); + } + + } else if (mode & ALTERNATE_MODE_STOP) { + max77705_vdm_process_set_samsung_alternate_mode(usbpd_data, + MAXIM_ENABLE_ALTERNATE_SRCCAP); + msg_maxim("[NO BOOTING TIME] alternate mode is stopped!"); + } + + break; + case 1: + if (mode & ALTERNATE_MODE_START) { + msg_maxim("[ON BOOTING TIME] !!!alternate mode is started!"); + prev_alternate_mode = ALTERNATE_MODE_START; + max77705_vdm_process_set_samsung_alternate_mode(usbpd_data, + MAXIM_ENABLE_ALTERNATE_SRC_VDM); + msg_maxim("!![ON BOOTING TIME] SEND THE PACKET REGARDING IN CASE OF VR/DP "); + /* FOR THE DEX FUNCTION. */ + max77705_read_reg(usbpd_data->muic, MAX77705_USBC_REG_USBC_STATUS1, &status[0]); + max77705_read_reg(usbpd_data->muic, MAX77705_USBC_REG_USBC_STATUS2, &status[1]); + max77705_read_reg(usbpd_data->muic, MAX77705_USBC_REG_BC_STATUS, &status[2]); + max77705_read_reg(usbpd_data->muic, MAX77705_USBC_REG_CC_STATUS0, &status[3]); + max77705_read_reg(usbpd_data->muic, MAX77705_USBC_REG_CC_STATUS1, &status[4]); + max77705_read_reg(usbpd_data->muic, MAX77705_USBC_REG_PD_STATUS0, &status[5]); + max77705_read_reg(usbpd_data->muic, MAX77705_USBC_REG_PD_STATUS1, &status[6]); + max77705_read_reg(usbpd_data->muic, MAX77705_USBC_REG_UIC_INT_M, &status[7]); + max77705_read_reg(usbpd_data->muic, MAX77705_USBC_REG_CC_INT_M, &status[8]); + max77705_read_reg(usbpd_data->muic, MAX77705_USBC_REG_PD_INT_M, &status[9]); + max77705_read_reg(usbpd_data->muic, MAX77705_USBC_REG_VDM_INT_M, &status[10]); + msg_maxim("USBC1:0x%02x, USBC2:0x%02x, BC:0x%02x", + status[0], status[1], status[2]); + msg_maxim("CC_STATUS0:0x%x, CC_STATUS1:0x%x, PD_STATUS0:0x%x, PD_STATUS1:0x%x", + status[3], status[4], status[5], status[6]); + msg_maxim("UIC_INT_M:0x%x, CC_INT_M:0x%x, PD_INT_M:0x%x, VDM_INT_M:0x%x", + status[7], status[8], status[9], status[10]); + if (usbpd_data->cc_data->current_pr == SNK && (pd_data->current_dr == DFP) + && usbpd_data->is_first_booting) { + max77705_vdm_process_set_identity_req(usbpd_data); + msg_maxim("[ON BOOTING TIME] SEND THE PACKET (DEX HUB) "); + } + max77705_write_reg(usbpd_data->muic, REG_VDM_INT_M, 0xF0); + max77705_write_reg(usbpd_data->muic, REG_PD_INT_M, 0x0); + max77705_read_reg(usbpd_data->muic, MAX77705_USBC_REG_PD_INT_M, &status[9]); + max77705_read_reg(usbpd_data->muic, MAX77705_USBC_REG_VDM_INT_M, &status[10]); + msg_maxim("UIC_INT_M:0x%x, CC_INT_M:0x%x, PD_INT_M:0x%x, VDM_INT_M:0x%x", + status[7], status[8], status[9], status[10]); + usbpd_data->is_first_booting = 0; + } else if (mode & ALTERNATE_MODE_STOP) { + msg_maxim("[ON BOOTING TIME] alternate mode is stopped!"); + } + break; + + default: + msg_maxim("Never calling"); + msg_maxim("[Never calling] is_first_booting [ %d]", is_first_booting); + break; + + } + } + } +} + +static void set_sec_uvdm_txdataheader(void *data, int first_set, int cur_uvdmset, + int total_data_size, int remained_data_size) +{ + U_SEC_TX_DATA_HEADER *SEC_TX_DATA_HEADER; + uint8_t *SendMSG = (uint8_t *)data; + + if (first_set) + SEC_TX_DATA_HEADER = (U_SEC_TX_DATA_HEADER *)&SendMSG[12]; + else + SEC_TX_DATA_HEADER = (U_SEC_TX_DATA_HEADER *)&SendMSG[8]; + + SEC_TX_DATA_HEADER->BITS.data_size_of_current_set = + get_data_size(first_set, remained_data_size); + SEC_TX_DATA_HEADER->BITS.total_data_size = total_data_size; + SEC_TX_DATA_HEADER->BITS.order_of_current_uvdm_set = cur_uvdmset; + SEC_TX_DATA_HEADER->BITS.reserved = 0; +} + +static void set_msgheader(void *data, int msg_type, int obj_num) +{ + /* Common : Fill the VDM OpCode MSGHeader */ + SEND_VDM_BYTE_DATA *MSG_HDR; + uint8_t *SendMSG = (uint8_t *)data; + + MSG_HDR = (SEND_VDM_BYTE_DATA *)&SendMSG[0]; + MSG_HDR->BITS.Num_Of_VDO = obj_num; + if (msg_type == NAK) + MSG_HDR->BITS.Reserved = 7; + MSG_HDR->BITS.Cmd_Type = msg_type; +} + +static void set_uvdmheader(void *data, int vendor_id, int vdm_type) +{ + /* Common : Fill the UVDMHeader */ + UND_UNSTRUCTURED_VDM_HEADER_Type *UVDM_HEADER; + U_DATA_MSG_VDM_HEADER_Type *VDM_HEADER; + uint8_t *SendMSG = (uint8_t *)data; + + UVDM_HEADER = (UND_UNSTRUCTURED_VDM_HEADER_Type *)&SendMSG[4]; + UVDM_HEADER->BITS.USB_Vendor_ID = vendor_id; + UVDM_HEADER->BITS.VDM_TYPE = vdm_type; + VDM_HEADER = (U_DATA_MSG_VDM_HEADER_Type *)&SendMSG[4]; + VDM_HEADER->BITS.VDM_command = 4; //from s2mm005 concept +} + +static void set_sec_uvdmheader(void *data, int pid, bool data_type, int cmd_type, + bool dir, int total_uvdmset_num, uint8_t received_data) +{ + U_SEC_UVDM_HEADER *SEC_VDM_HEADER; + uint8_t *SendMSG = (uint8_t *)data; + + SEC_VDM_HEADER = (U_SEC_UVDM_HEADER *)&SendMSG[8]; + SEC_VDM_HEADER->BITS.pid = pid; + SEC_VDM_HEADER->BITS.data_type = data_type; + SEC_VDM_HEADER->BITS.command_type = cmd_type; + SEC_VDM_HEADER->BITS.direction = dir; + if (dir == DIR_OUT) + SEC_VDM_HEADER->BITS.total_number_of_uvdm_set = total_uvdmset_num; + if (data_type == TYPE_SHORT) + SEC_VDM_HEADER->BITS.data = received_data; + else + SEC_VDM_HEADER->BITS.data = 0; +#if 0 + msg_maxim("pid = 0x%x data_type=%d ,cmd_type =%d,direction= %d, total_num_of_uvdm_set = %d", + SEC_VDM_HEADER->BITS.pid, + SEC_VDM_HEADER->BITS.data_type, + SEC_VDM_HEADER->BITS.command_type, + SEC_VDM_HEADER->BITS.direction, + SEC_VDM_HEADER->BITS.total_number_of_uvdm_set); +#endif +} + +static void set_sec_uvdm_txdata_tailer(void *data) +{ + U_SEC_TX_DATA_TAILER *SEC_TX_DATA_TAILER; + uint8_t *SendMSG = (uint8_t *)data; + + SEC_TX_DATA_TAILER = (U_SEC_TX_DATA_TAILER *)&SendMSG[28]; + SEC_TX_DATA_TAILER->BITS.checksum = get_checksum(SendMSG, 8, SAMSUNGUVDM_CHECKSUM_DATA_COUNT); + SEC_TX_DATA_TAILER->BITS.reserved = 0; +} + +static void set_sec_uvdm_rxdata_header(void *data, int cur_uvdmset_num, int cur_uvdmset_data, int ack) +{ + U_SEC_RX_DATA_HEADER *SEC_UVDM_RX_HEADER; + uint8_t *SendMSG = (uint8_t *)data; + + SEC_UVDM_RX_HEADER = (U_SEC_RX_DATA_HEADER *)&SendMSG[8]; + SEC_UVDM_RX_HEADER->BITS.order_of_current_uvdm_set = cur_uvdmset_num; + SEC_UVDM_RX_HEADER->BITS.received_data_size_of_current_set = cur_uvdmset_data; + SEC_UVDM_RX_HEADER->BITS.result_value = ack; + SEC_UVDM_RX_HEADER->BITS.reserved = 0; +} + +static int check_is_wait_ack_accessroy(int vid, int pid, int svid) +{ + int should_wait = true; + + if (vid == SAMSUNG_VENDOR_ID && pid == DEXDOCK_PRODUCT_ID && svid == TypeC_DP_SUPPORT) { + msg_maxim("no need to wait ack response!"); + should_wait = false; + } + return should_wait; +} + +static int max77705_send_vdm_write_message(void *data) +{ + struct SS_UNSTRUCTURED_VDM_MSG vdm_opcode_msg; + struct max77705_usbc_platform_data *usbpd_data = g_usbc_data; + uint8_t *SendMSG = (uint8_t *)data; + usbc_cmd_data write_data; + int len = sizeof(struct SS_UNSTRUCTURED_VDM_MSG); + + memset(&vdm_opcode_msg, 0, len); + /* Common : MSGHeader */ + memcpy(&vdm_opcode_msg.byte_data, &SendMSG[0], 1); + /* 8. Copy the data from SendMSG buffer */ + memcpy(&vdm_opcode_msg.VDO_MSG.VDO[0], &SendMSG[4], 28); + /* 9. Write SendMSG buffer via I2C */ + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_VDM_DISCOVER_SET_VDM_REQ; + write_data.is_uvdm = 1; + memcpy(write_data.write_data, &vdm_opcode_msg, sizeof(vdm_opcode_msg)); + write_data.write_length = len; + write_data.read_length = len; + max77705_usbc_opcode_write(usbpd_data, &write_data); + msg_maxim("opcode is sent"); + return 0; +} + +static int max77705_send_sec_unstructured_short_vdm_message(void *data, void *buf, size_t size) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + uint8_t SendMSG[32] = {0,}; + /* Message Type Definition */ + uint8_t received_data = 0; + int time_left; +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + int event; +#endif + + if ((buf == NULL) || size <= 0) { + msg_maxim("given data is not valid !"); + return -EINVAL; + } + + if (!usbpd_data->is_samsung_accessory_enter_mode) { + msg_maxim("samsung_accessory mode is not ready!"); + return -ENXIO; + } + /* 1. Calc the receivced data size and determin the uvdm set count and last data of uvdm set. */ + received_data = *(char *)buf; + /* 2. Common : Fill the MSGHeader */ + set_msgheader(SendMSG, ACK, 2); + /* 3. Common : Fill the UVDMHeader*/ + set_uvdmheader(SendMSG, SAMSUNG_VENDOR_ID, 0); + /* 4. Common : Fill the First SEC_VDMHeader*/ + set_sec_uvdmheader(SendMSG, usbpd_data->Product_ID, TYPE_SHORT, SEC_UVDM_ININIATOR, DIR_OUT, 1, received_data); + usbpd_data->is_in_first_sec_uvdm_req = true; + + usbpd_data->uvdm_error = 0; + max77705_send_vdm_write_message(SendMSG); + + if (check_is_wait_ack_accessroy(usbpd_data->Vendor_ID, usbpd_data->Product_ID, usbpd_data->SVID_DP)) { + reinit_completion(&usbpd_data->uvdm_longpacket_out_wait); + /* Wait Response*/ + time_left = + wait_for_completion_interruptible_timeout(&usbpd_data->uvdm_longpacket_out_wait, + msecs_to_jiffies(SAMSUNGUVDM_WAIT_MS)); + if (time_left <= 0) { + usbpd_data->is_in_first_sec_uvdm_req = false; +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_UVDM_TIMEOUT; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + return -ETIME; + } + if (usbpd_data->uvdm_error) { + usbpd_data->is_in_first_sec_uvdm_req = false; + return usbpd_data->uvdm_error; + } + } + msg_maxim("exit : short data transfer complete!"); + usbpd_data->is_in_first_sec_uvdm_req = false; + return size; +} + +static int max77705_send_sec_unstructured_long_vdm_message(void *data, void *buf, size_t size) +{ + struct max77705_usbc_platform_data *usbpd_data; + uint8_t SendMSG[32] = {0,}; + uint8_t *SEC_DATA; + int need_uvdmset_count = 0; + int cur_uvdmset_data = 0; + int cur_uvdmset_num = 0; + int accumulated_data_size = 0; + int remained_data_size = 0; + uint8_t received_data[MAX_INPUT_DATA] = {0,}; + int time_left; + int i; + int received_data_index; +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + int event; +#endif + + usbpd_data = data; + if (!usbpd_data) + return -ENXIO; + + if (!buf) { + msg_maxim("given data is not valid !"); + return -EINVAL; + } + + /* 1. Calc the receivced data size and determin the uvdm set count and last data of uvdm set. */ + set_endian(buf, received_data, size); + need_uvdmset_count = set_uvdmset_count(size); + msg_maxim("need_uvdmset_count = %d", need_uvdmset_count); + usbpd_data->is_in_first_sec_uvdm_req = true; + usbpd_data->is_in_sec_uvdm_out = DIR_OUT; + cur_uvdmset_num = 1; + accumulated_data_size = 0; + remained_data_size = size; + received_data_index = 0; + /* 2. Common : Fill the MSGHeader */ + set_msgheader(SendMSG, ACK, 7); + /* 3. Common : Fill the UVDMHeader*/ + set_uvdmheader(SendMSG, SAMSUNG_VENDOR_ID, 0); + /* 4. Common : Fill the First SEC_VDMHeader*/ + if (usbpd_data->is_in_first_sec_uvdm_req) + set_sec_uvdmheader(SendMSG, usbpd_data->Product_ID, + TYPE_LONG, SEC_UVDM_ININIATOR, DIR_OUT, need_uvdmset_count, 0); + + while (cur_uvdmset_num <= need_uvdmset_count) { + cur_uvdmset_data = 0; + time_left = 0; + + set_sec_uvdm_txdataheader(SendMSG, usbpd_data->is_in_first_sec_uvdm_req, + cur_uvdmset_num, size, remained_data_size); + + cur_uvdmset_data = get_data_size(usbpd_data->is_in_first_sec_uvdm_req, remained_data_size); + msg_maxim("data_size_of_current_set = %d ,total_data_size = %ld, order_of_current_uvdm_set = %d", + cur_uvdmset_data, size, cur_uvdmset_num); + /* 6. Common : Fill the DATA */ + if (usbpd_data->is_in_first_sec_uvdm_req) { + SEC_DATA = (uint8_t *)&SendMSG[8+8]; + for (i = 0; i < SAMSUNGUVDM_MAXDATA_FIRST_UVDMSET; i++) + SEC_DATA[i] = received_data[received_data_index++]; + } else { + SEC_DATA = (uint8_t *)&SendMSG[8+4]; + for (i = 0; i < SAMSUNGUVDM_MAXDATA_NORMAL_UVDMSET; i++) + SEC_DATA[i] = received_data[received_data_index++]; + } + /* 7. Common : Fill the TX_DATA_Tailer */ + set_sec_uvdm_txdata_tailer(SendMSG); + /* 8. Send data to PDIC */ + usbpd_data->uvdm_error = 0; + max77705_send_vdm_write_message(SendMSG); + /* 9. Wait Response*/ + reinit_completion(&usbpd_data->uvdm_longpacket_out_wait); + time_left = + wait_for_completion_interruptible_timeout(&usbpd_data->uvdm_longpacket_out_wait, + msecs_to_jiffies(SAMSUNGUVDM_WAIT_MS)); + if (time_left <= 0) { +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_UVDM_TIMEOUT; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + return -ETIME; + } + + if (usbpd_data->uvdm_error) + return usbpd_data->uvdm_error; + + accumulated_data_size += cur_uvdmset_data; + remained_data_size -= cur_uvdmset_data; + if (usbpd_data->is_in_first_sec_uvdm_req) + usbpd_data->is_in_first_sec_uvdm_req = false; + cur_uvdmset_num++; + } + return size; +} + +void max77705_sec_unstructured_message_handler(struct max77705_usbc_platform_data *usbpd_data, + char *opcode_data, int len) +{ + int unstructured_len = sizeof(struct SS_UNSTRUCTURED_VDM_MSG); + uint8_t ReadMSG[32] = {0,}; + U_SEC_UVDM_RESPONSE_HEADER *SEC_UVDM_RESPONSE_HEADER; + U_SEC_RX_DATA_HEADER *SEC_UVDM_RX_HEADER; + U_SEC_TX_DATA_HEADER *SEC_UVDM_TX_HEADER; + + if (len != unstructured_len + OPCODE_SIZE) { + msg_maxim("This isn't UVDM message!"); + return; + } + + memcpy(ReadMSG, opcode_data, OPCODE_DATA_LENGTH); + usbpd_data->uvdm_error = 0; + + switch (usbpd_data->is_in_sec_uvdm_out) { + case DIR_OUT: + if (usbpd_data->is_in_first_sec_uvdm_req) { + SEC_UVDM_RESPONSE_HEADER = (U_SEC_UVDM_RESPONSE_HEADER *)&ReadMSG[6]; +#if (UVDM_DEBUG) + msg_maxim("DIR_OUT SEC_UVDM_RESPONSE_HEADER : 0x%x", SEC_UVDM_RESPONSE_HEADER->data); +#endif + if (SEC_UVDM_RESPONSE_HEADER->BITS.data_type == TYPE_LONG) { + if (SEC_UVDM_RESPONSE_HEADER->BITS.command_type == SEC_UVDM_RESPONDER_ACK) { + SEC_UVDM_RX_HEADER = (U_SEC_RX_DATA_HEADER *)&ReadMSG[10]; +#if (UVDM_DEBUG) + msg_maxim("DIR_OUT SEC_UVDM_RX_HEADER : 0x%x", SEC_UVDM_RX_HEADER->data); +#endif + if (SEC_UVDM_RX_HEADER->BITS.result_value == RX_ACK) { + /* do nothing */ + } else if (SEC_UVDM_RX_HEADER->BITS.result_value == RX_NAK) { + msg_maxim("DIR_OUT RX_NAK received"); + usbpd_data->uvdm_error = -ENODATA; + } else if (SEC_UVDM_RX_HEADER->BITS.result_value == RX_BUSY) { + msg_maxim("DIR_OUT RX_BUSY received"); + usbpd_data->uvdm_error = -EBUSY; + } else { + msg_maxim("DIR_OUT Undefined RX value"); + usbpd_data->uvdm_error = -EPROTO; + } + } else if (SEC_UVDM_RESPONSE_HEADER->BITS.command_type == SEC_UVDM_RESPONDER_NAK) { + msg_maxim("DIR_OUT SEC_UVDM_RESPONDER_NAK received"); + usbpd_data->uvdm_error = -ENODATA; + } else if (SEC_UVDM_RESPONSE_HEADER->BITS.command_type == SEC_UVDM_RESPONDER_BUSY) { + msg_maxim("DIR_OUT SEC_UVDM_RESPONDER_BUSY received"); + usbpd_data->uvdm_error = -EBUSY; + } else { + msg_maxim("DIR_OUT Undefined RESPONDER value"); + usbpd_data->uvdm_error = -EPROTO; + } + } else { /* TYPE_SHORT */ + if (SEC_UVDM_RESPONSE_HEADER->BITS.command_type == SEC_UVDM_RESPONDER_ACK) { + /* do nothing */ + } else if (SEC_UVDM_RESPONSE_HEADER->BITS.command_type == SEC_UVDM_RESPONDER_NAK) { + msg_maxim("DIR_OUT SHORT SEC_UVDM_RESPONDER_NAK received"); + usbpd_data->uvdm_error = -ENODATA; + } else if (SEC_UVDM_RESPONSE_HEADER->BITS.command_type == SEC_UVDM_RESPONDER_BUSY) { + msg_maxim("DIR_OUT SHORT SEC_UVDM_RESPONDER_BUSY received"); + usbpd_data->uvdm_error = -EBUSY; + } else { + msg_maxim("DIR_OUT Undefined RESPONDER value"); + usbpd_data->uvdm_error = -EPROTO; + } + } + } else { /* after 2nd packet for TYPE_LONG */ + SEC_UVDM_RX_HEADER = (U_SEC_RX_DATA_HEADER *)&ReadMSG[6]; + if (SEC_UVDM_RX_HEADER->BITS.result_value == RX_ACK) { + /* do nothing */ + } else if (SEC_UVDM_RX_HEADER->BITS.result_value == RX_NAK) { + msg_maxim("DIR_OUT RX_NAK received"); + usbpd_data->uvdm_error = -ENODATA; + } else if (SEC_UVDM_RX_HEADER->BITS.result_value == RX_BUSY) { + msg_maxim("DIR_OUT RX_BUSY received"); + usbpd_data->uvdm_error = -EBUSY; + } else { + msg_maxim("DIR_OUT Undefined RX value"); + usbpd_data->uvdm_error = -EPROTO; + } + } + complete(&usbpd_data->uvdm_longpacket_out_wait); + msg_maxim("DIR_OUT complete!"); + break; + case DIR_IN: + if (usbpd_data->is_in_first_sec_uvdm_req) { /* LONG and SHORT response is same */ + SEC_UVDM_RESPONSE_HEADER = (U_SEC_UVDM_RESPONSE_HEADER *)&ReadMSG[6]; + SEC_UVDM_TX_HEADER = (U_SEC_TX_DATA_HEADER *)&ReadMSG[10]; +#if (UVDM_DEBUG) + msg_maxim("DIR_IN data_type = %d , command_type = %d, direction=%d, total_number_of_uvdm_set=%d", + SEC_UVDM_RESPONSE_HEADER->BITS.data_type, + SEC_UVDM_RESPONSE_HEADER->BITS.command_type, + SEC_UVDM_RESPONSE_HEADER->BITS.direction, + SEC_UVDM_RESPONSE_HEADER->BITS.total_number_of_uvdm_set); +#endif + if (SEC_UVDM_RESPONSE_HEADER->BITS.command_type == SEC_UVDM_RESPONDER_ACK) { + memcpy(usbpd_data->ReadMSG, ReadMSG, OPCODE_DATA_LENGTH); +#if (UVDM_DEBUG) + msg_maxim("DIR_IN order_of_current_uvdm_set = %d , total_data_size = %d, data_size_of_current_set=%d", + SEC_UVDM_TX_HEADER->BITS.order_of_current_uvdm_set, + SEC_UVDM_TX_HEADER->BITS.total_data_size, + SEC_UVDM_TX_HEADER->BITS.data_size_of_current_set); +#endif + msg_maxim("DIR_IN 1st Response"); + } else if (SEC_UVDM_RESPONSE_HEADER->BITS.command_type == SEC_UVDM_RESPONDER_NAK) { + msg_maxim("DIR_IN SEC_UVDM_RESPONDER_NAK received"); + usbpd_data->uvdm_error = -ENODATA; + } else if (SEC_UVDM_RESPONSE_HEADER->BITS.command_type == SEC_UVDM_RESPONDER_BUSY) { + msg_maxim("DIR_IN SEC_UVDM_RESPONDER_BUSY received"); + usbpd_data->uvdm_error = -EBUSY; + } else { + msg_maxim("DIR_IN Undefined RESPONDER value"); + usbpd_data->uvdm_error = -EPROTO; + } + } else { + /* don't have ack packet after 2nd sec_tx_data_header */ + memcpy(usbpd_data->ReadMSG, ReadMSG, OPCODE_DATA_LENGTH); + SEC_UVDM_TX_HEADER = (U_SEC_TX_DATA_HEADER *)&usbpd_data->ReadMSG[6]; +#if (UVDM_DEBUG) + msg_maxim("DIR_IN order_of_current_uvdm_set = %d , total_data_size = %d, data_size_of_current_set=%d", + SEC_UVDM_TX_HEADER->BITS.order_of_current_uvdm_set, + SEC_UVDM_TX_HEADER->BITS.total_data_size, + SEC_UVDM_TX_HEADER->BITS.data_size_of_current_set); +#endif + if (SEC_UVDM_TX_HEADER->data != 0) + msg_maxim("DIR_IN %dth Response", SEC_UVDM_TX_HEADER->BITS.order_of_current_uvdm_set); + else + msg_maxim("DIR_IN Last Response. It's zero"); + } + complete(&usbpd_data->uvdm_longpacket_in_wait); + msg_maxim("DIR_IN complete!"); + break; + default: + msg_maxim("Never Call!!!"); + break; + } +} + +int max77705_sec_uvdm_out_request_message(void *data, int size) +{ + struct max77705_usbc_platform_data *usbpd_data; + struct i2c_client *i2c; + int ret; + + usbpd_data = g_usbc_data; + if (!usbpd_data) + return -ENXIO; + + i2c = usbpd_data->muic; + if (i2c == NULL) { + msg_maxim("usbpd_data->i2c is not valid!"); + return -EINVAL; + } + + if (data == NULL) { + msg_maxim("given data is not valid !"); + return -EINVAL; + } + + if (size >= SAMSUNGUVDM_MAX_LONGPACKET_SIZE) { + msg_maxim("size %d is too big to send data", size); + ret = -EFBIG; + } else if (size <= SAMSUNGUVDM_MAX_SHORTPACKET_SIZE) + ret = max77705_send_sec_unstructured_short_vdm_message(usbpd_data, data, size); + else + ret = max77705_send_sec_unstructured_long_vdm_message(usbpd_data, data, size); + + return ret; +} + +int max77705_sec_uvdm_in_request_message(void *data) +{ + struct max77705_usbc_platform_data *usbpd_data; + uint8_t SendMSG[32] = {0,}; + uint8_t ReadMSG[32] = {0,}; + uint8_t IN_DATA[MAX_INPUT_DATA] = {0,}; + + /* Send Request */ + /* 1 Message Type Definition */ + U_SEC_UVDM_RESPONSE_HEADER *SEC_RES_HEADER; + U_SEC_TX_DATA_HEADER *SEC_UVDM_TX_HEADER; + U_SEC_TX_DATA_TAILER *SEC_UVDM_TX_TAILER; + + int cur_uvdmset_data = 0; + int cur_uvdmset_num = 0; + int total_uvdmset_num = 0; + int received_data_size = 0; + int total_received_data_size = 0; + int ack = 0; + int size = 0; + int time_left = 0; + int cal_checksum = 0; + int i = 0; +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + int event; +#endif + + usbpd_data = g_usbc_data; + if (!usbpd_data) + return -ENXIO; + + if (data == NULL) { + msg_maxim("%s given data is not valid !", __func__); + return -EINVAL; + } + + usbpd_data->is_in_sec_uvdm_out = DIR_IN; + usbpd_data->is_in_first_sec_uvdm_req = true; + + /* 1. Common : Fill the MSGHeader */ + set_msgheader(SendMSG, ACK, 2); + /* 2. Common : Fill the UVDMHeader*/ + set_uvdmheader(SendMSG, SAMSUNG_VENDOR_ID, 0); + /* 3. Common : Fill the First SEC_VDMHeader*/ + if (usbpd_data->is_in_first_sec_uvdm_req) + set_sec_uvdmheader(SendMSG, usbpd_data->Product_ID, TYPE_LONG, SEC_UVDM_ININIATOR, DIR_IN, 0, 0); + /* 8. Send data to PDIC */ + usbpd_data->uvdm_error = 0; + max77705_send_vdm_write_message(SendMSG); + + cur_uvdmset_num = 0; + total_uvdmset_num = 1; + + do { + reinit_completion(&usbpd_data->uvdm_longpacket_in_wait); + time_left = + wait_for_completion_interruptible_timeout(&usbpd_data->uvdm_longpacket_in_wait, + msecs_to_jiffies(SAMSUNGUVDM_WAIT_MS)); + + if (time_left <= 0) { + msg_maxim("timeout"); +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_UVDM_TIMEOUT; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + return -ETIME; + } + if (usbpd_data->uvdm_error) + return usbpd_data->uvdm_error; + /* read data */ + memset(ReadMSG, 0, 32); + memcpy(ReadMSG, usbpd_data->ReadMSG, OPCODE_DATA_LENGTH); + + if (usbpd_data->is_in_first_sec_uvdm_req) { + SEC_RES_HEADER = (U_SEC_UVDM_RESPONSE_HEADER *)&ReadMSG[6]; + SEC_UVDM_TX_HEADER = (U_SEC_TX_DATA_HEADER *)&ReadMSG[10]; +#if (UVDM_DEBUG) + msg_maxim("SEC_RES_HEADER : 0x%x, SEC_UVDM_TX_HEADER : 0x%x", SEC_RES_HEADER->data, SEC_UVDM_TX_HEADER->data); +#endif + if (SEC_RES_HEADER->BITS.data_type == TYPE_LONG) { + /* 1. check the data size received */ + size = SEC_UVDM_TX_HEADER->BITS.total_data_size; + cur_uvdmset_data = SEC_UVDM_TX_HEADER->BITS.data_size_of_current_set; + cur_uvdmset_num = SEC_UVDM_TX_HEADER->BITS.order_of_current_uvdm_set; + total_uvdmset_num = + SEC_RES_HEADER->BITS.total_number_of_uvdm_set; + + usbpd_data->is_in_first_sec_uvdm_req = false; + /* 2. copy data to buffer */ + for (i = 0; i < SAMSUNGUVDM_MAXDATA_FIRST_UVDMSET; i++) + IN_DATA[received_data_size++] = ReadMSG[14 + i]; + + total_received_data_size += cur_uvdmset_data; + usbpd_data->is_in_first_sec_uvdm_req = false; + } else { + IN_DATA[received_data_size++] = SEC_RES_HEADER->BITS.data; + return received_data_size; + } + } else { + SEC_UVDM_TX_HEADER = (U_SEC_TX_DATA_HEADER *)&ReadMSG[6]; + cur_uvdmset_data = SEC_UVDM_TX_HEADER->BITS.data_size_of_current_set; + cur_uvdmset_num = SEC_UVDM_TX_HEADER->BITS.order_of_current_uvdm_set; + /* 2. copy data to buffer */ + for (i = 0 ; i < SAMSUNGUVDM_MAXDATA_NORMAL_UVDMSET ; i++) + IN_DATA[received_data_size++] = ReadMSG[10 + i]; + total_received_data_size += cur_uvdmset_data; + } + /* 3. Check Checksum */ + SEC_UVDM_TX_TAILER = (U_SEC_TX_DATA_TAILER *)&ReadMSG[26]; + cal_checksum = get_checksum(ReadMSG, 6, SAMSUNGUVDM_CHECKSUM_DATA_COUNT); + ack = (cal_checksum == SEC_UVDM_TX_TAILER->BITS.checksum) ? + SEC_UVDM_RX_HEADER_ACK : SEC_UVDM_RX_HEADER_NAK; +#if (UVDM_DEBUG) + msg_maxim("SEC_UVDM_TX_TAILER : 0x%x, cal_checksum : 0x%x, size : %d", SEC_UVDM_TX_TAILER->data, cal_checksum, size); +#endif + /* 1. Common : Fill the MSGHeader */ + if (cur_uvdmset_num == total_uvdmset_num) + set_msgheader(SendMSG, NAK, 2); + else + set_msgheader(SendMSG, ACK, 2); + /* 2. Common : Fill the UVDMHeader*/ + set_uvdmheader(SendMSG, SAMSUNG_VENDOR_ID, 0); + /* 5-3. Common : Fill the SEC RXHeader */ + set_sec_uvdm_rxdata_header(SendMSG, cur_uvdmset_num, cur_uvdmset_data, ack); + + /* 8. Send data to PDIC */ + usbpd_data->uvdm_error = 0; + max77705_send_vdm_write_message(SendMSG); + } while (cur_uvdmset_num < total_uvdmset_num); + set_endian(IN_DATA, data, size); + + reinit_completion(&usbpd_data->uvdm_longpacket_in_wait); + time_left = + wait_for_completion_interruptible_timeout(&usbpd_data->uvdm_longpacket_in_wait, + msecs_to_jiffies(SAMSUNGUVDM_WAIT_MS)); + if (time_left <= 0) { + msg_maxim("last in request timeout"); +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_UVDM_TIMEOUT; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + return -ETIME; + } + if (usbpd_data->uvdm_error) + return usbpd_data->uvdm_error; + + return size; +} + +int max77705_sec_uvdm_ready(void) +{ + int uvdm_ready = false; + struct max77705_usbc_platform_data *usbpd_data; + + usbpd_data = g_usbc_data; + + msg_maxim("power_nego is%s done, accessory_enter_mode is%s done", + usbpd_data->pn_flag ? "" : " not", + usbpd_data->is_samsung_accessory_enter_mode? "" : " not"); + + if (usbpd_data->pn_flag && + usbpd_data->is_samsung_accessory_enter_mode) + uvdm_ready = true; + + msg_maxim("uvdm ready = %d", uvdm_ready); + return uvdm_ready; +} + +void max77705_sec_uvdm_close(void) +{ + struct max77705_usbc_platform_data *usbpd_data; + + usbpd_data = g_usbc_data; + complete(&usbpd_data->uvdm_longpacket_out_wait); + complete(&usbpd_data->uvdm_longpacket_in_wait); + msg_maxim("success"); +} diff --git a/drivers/usb/typec/maxim/max77705_cc.c b/drivers/usb/typec/maxim/max77705_cc.c new file mode 100644 index 000000000000..1a3a3203810c --- /dev/null +++ b/drivers/usb/typec/maxim/max77705_cc.c @@ -0,0 +1,878 @@ +/* + * Copyrights (C) 2017 Samsung Electronics, Inc. + * Copyrights (C) 2017 Maxim Integrated Products, 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) +#include +#endif +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) +#include +#include +#endif +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_COMBO_REDRIVER_PTN36502) +#include +#endif + +extern struct max77705_usbc_platform_data *g_usbc_data; + +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) +static void max77705_ccic_event_notifier(struct work_struct *data) +{ + struct pdic_state_work *event_work = + container_of(data, struct pdic_state_work, pdic_work); + PD_NOTI_TYPEDEF ccic_noti; + + switch (event_work->dest) { + case PDIC_NOTIFY_DEV_USB: + msg_maxim("usb: dest=%s, id=%s, attach=%s, drp=%s", + pdic_event_dest_string(event_work->dest), + pdic_event_id_string(event_work->id), + event_work->attach ? "Attached" : "Detached", + pdic_usbstatus_string(event_work->event)); + break; + default: + msg_maxim("usb: dest=%s, id=%s, attach=%d, event=%d", + pdic_event_dest_string(event_work->dest), + pdic_event_id_string(event_work->id), + event_work->attach, + event_work->event); + break; + } + + ccic_noti.src = PDIC_NOTIFY_DEV_CCIC; + ccic_noti.dest = event_work->dest; + ccic_noti.id = event_work->id; + ccic_noti.sub1 = event_work->attach; + ccic_noti.sub2 = event_work->event; + ccic_noti.sub3 = event_work->sub; +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + ccic_noti.pd = &g_usbc_data->pd_data->pd_noti; +#endif + pdic_notifier_notify((PD_NOTI_TYPEDEF *) &ccic_noti, NULL, 0); + + kfree(event_work); +} + +void max77705_ccic_event_work(void *data, int dest, int id, int attach, int event, int sub) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + struct pdic_state_work *event_work; + struct typec_partner_desc desc; + enum typec_pwr_opmode mode = TYPEC_PWR_MODE_USB; + + msg_maxim("usb: DIAES %d-%d-%d-%d-%d", dest, id, attach, event, sub); + event_work = kmalloc(sizeof(struct pdic_state_work), GFP_KERNEL); + if (!event_work) { + msg_maxim("failed to allocate event_work"); + return; + } + INIT_WORK(&event_work->pdic_work, max77705_ccic_event_notifier); + + event_work->dest = dest; + event_work->id = id; + event_work->attach = attach; + event_work->event = event; + event_work->sub = sub; + + if (id == PDIC_NOTIFY_ID_USB) { + if (usbpd_data->partner == NULL) { + msg_maxim("typec_register_partner, typec_power_role=%d typec_data_role=%d event=%d", + usbpd_data->typec_power_role,usbpd_data->typec_data_role, event); + if (event == USB_STATUS_NOTIFY_ATTACH_UFP) { + mode = max77705_get_pd_support(usbpd_data); + typec_set_pwr_opmode(usbpd_data->port, mode); + desc.usb_pd = mode == TYPEC_PWR_MODE_PD; + desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ + desc.identity = NULL; + usbpd_data->typec_data_role = TYPEC_DEVICE; + typec_set_pwr_role(usbpd_data->port, usbpd_data->typec_power_role); + typec_set_data_role(usbpd_data->port, usbpd_data->typec_data_role); + usbpd_data->partner = typec_register_partner(usbpd_data->port, &desc); + } else if (event == USB_STATUS_NOTIFY_ATTACH_DFP) { + mode = max77705_get_pd_support(usbpd_data); + typec_set_pwr_opmode(usbpd_data->port, mode); + desc.usb_pd = mode == TYPEC_PWR_MODE_PD; + desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ + desc.identity = NULL; + usbpd_data->typec_data_role = TYPEC_HOST; + typec_set_pwr_role(usbpd_data->port, usbpd_data->typec_power_role); + typec_set_data_role(usbpd_data->port, usbpd_data->typec_data_role); + usbpd_data->partner = typec_register_partner(usbpd_data->port, &desc); + } else + msg_maxim("detach case"); + } else { + msg_maxim("data_role changed, typec_power_role=%d typec_data_role=%d, event=%d", + usbpd_data->typec_power_role,usbpd_data->typec_data_role, event); + if (event == USB_STATUS_NOTIFY_ATTACH_UFP) { + usbpd_data->typec_data_role = TYPEC_DEVICE; + typec_set_data_role(usbpd_data->port, usbpd_data->typec_data_role); + } else if (event == USB_STATUS_NOTIFY_ATTACH_DFP) { + usbpd_data->typec_data_role = TYPEC_HOST; + typec_set_data_role(usbpd_data->port, usbpd_data->typec_data_role); + } else + msg_maxim("detach case"); + } + } + + queue_work(usbpd_data->ccic_wq, &event_work->pdic_work); +} +#endif + +static void max77705_set_unmask_vbus(struct max77705_usbc_platform_data *usbc_data, u8 ccstat) +{ + u8 bc_status = 0, vbus = 0; + u64 time_gap = 0; + usbc_cmd_data write_data; + int latest_idx = 0, cmp_idx = 0; +#if defined(CONFIG_USB_HW_PARAM) && defined(CONFIG_GET_UNMASK_VBUS_HWPARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif + + if (usbc_data->rid_check == true) + return; + + if (ccstat == cc_SINK) { + max77705_read_reg(usbc_data->muic, MAX77705_USBC_REG_BC_STATUS, &bc_status); + vbus = (bc_status & BIT_VBUSDet) >> FFS(BIT_VBUSDet); + + if (!vbus) { + usbc_data->time_lapse[usbc_data->lapse_idx] = get_jiffies_64(); + + pr_info("%s: lapse_idx: %d, time_lapse: %llu\n", + __func__, usbc_data->lapse_idx, usbc_data->time_lapse[usbc_data->lapse_idx]); + + usbc_data->lapse_idx = (usbc_data->lapse_idx + 1) % MAX_NVCN_CNT; + } + } else if (ccstat == cc_No_Connection) { + if (usbc_data->time_lapse[0] == 0) + return; + + latest_idx = (usbc_data->lapse_idx + MAX_NVCN_CNT - 1) % MAX_NVCN_CNT; + cmp_idx = usbc_data->lapse_idx; + + time_gap = usbc_data->time_lapse[latest_idx] - usbc_data->time_lapse[cmp_idx]; + + if (time_gap <= (HZ * MAX_CHK_TIME)) { + pr_info("%s: time_gap: %llu, Opcode write 0x1f 0x30\n", + __func__, time_gap); + + init_usbc_cmd_data(&write_data); + write_data.opcode = 0x1F; + write_data.write_length = 1; + write_data.write_data[0] = 0x30; + write_data.read_length = 0; + max77705_usbc_opcode_write(usbc_data, &write_data); +#if defined(CONFIG_USB_HW_PARAM) && defined(CONFIG_GET_UNMASK_VBUS_HWPARAM) + inc_hw_param(o_notify, USB_CCIC_UNMASK_VBUS_COUNT); +#endif + } + } +} + +void max77705_dp_detach(void *data) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + + pr_info("%s: dp_is_connect %d\n", __func__, usbpd_data->dp_is_connect); + + max77705_ccic_event_work(usbpd_data, PDIC_NOTIFY_DEV_USB_DP, + PDIC_NOTIFY_ID_USB_DP, 0/*attach*/, usbpd_data->dp_hs_connect/*drp*/, 0); + max77705_ccic_event_work(usbpd_data, PDIC_NOTIFY_DEV_DP, + PDIC_NOTIFY_ID_DP_CONNECT, 0/*attach*/, 0/*drp*/, 0); + + usbpd_data->dp_is_connect = 0; + usbpd_data->dp_hs_connect = 0; + usbpd_data->is_sent_pin_configuration = 0; +} + +void max77705_notify_dr_status(struct max77705_usbc_platform_data *usbpd_data, uint8_t attach) +{ + struct max77705_pd_data *pd_data = usbpd_data->pd_data; + + msg_maxim("Data Role: %s Power Role: %s State: %s", + pd_data->current_dr ? "DFP":"UFP", + usbpd_data->cc_data->current_pr ? "SRC":"SNK", + attach ? "ATTACHED":"DETACHED"); + + if (attach == PDIC_NOTIFY_ATTACH) { + if (usbpd_data->current_connstat == WATER) { + pr_info("%s: blocked by WATER\n", __func__); + return; + } + if (usbpd_data->shut_down) { + pr_info("%s: blocked by shutdown\n", __func__); + return; + } + if (pd_data->current_dr == UFP) { + if (usbpd_data->is_host == HOST_ON) { + msg_maxim("pd_state:%02d, turn off host", + usbpd_data->pd_state); + if (usbpd_data->dp_is_connect == 1) + max77705_dp_detach(usbpd_data); + max77705_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_USB, PDIC_NOTIFY_ID_USB, + 0/*attach*/, USB_STATUS_NOTIFY_DETACH/*drp*/, 0); + usbpd_data->is_host = HOST_OFF; + usbpd_data->send_enter_mode_req = 0; + } + if (usbpd_data->is_client == CLIENT_OFF) { + usbpd_data->is_client = CLIENT_ON; + /* muic */ + max77705_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_MUIC, PDIC_NOTIFY_ID_ATTACH, + 1/*attach*/, 0/*rprd*/, 0); + /* USB */ + max77705_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_USB, PDIC_NOTIFY_ID_USB, + 1/*attach*/, USB_STATUS_NOTIFY_ATTACH_UFP/*drp*/, 0); + } + + } else if (pd_data->current_dr == DFP) { + if (usbpd_data->is_client == CLIENT_ON) { + msg_maxim("pd_state:%02d, turn off client", + usbpd_data->pd_state); + max77705_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_USB, PDIC_NOTIFY_ID_USB, + 0/*attach*/, USB_STATUS_NOTIFY_DETACH/*drp*/, 0); + usbpd_data->is_client = CLIENT_OFF; +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) && IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + usb_set_vbus_current(usbpd_data->man, USB_CURRENT_CLEAR); +#endif + } + if (usbpd_data->is_host == HOST_OFF) { + usbpd_data->is_host = HOST_ON; + /* muic */ + max77705_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_MUIC, + PDIC_NOTIFY_ID_ATTACH, 1/*attach*/, 1/*rprd*/, 0); + /* USB */ + max77705_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_USB, PDIC_NOTIFY_ID_USB, + 1/*attach*/, USB_STATUS_NOTIFY_ATTACH_DFP/*drp*/, 0); + } + + } else { + msg_maxim("Unknown dr type (%d) no action", + pd_data->current_dr); + } + } else { /* PDIC_NOTIFY_DETACH */ + if (usbpd_data->dp_is_connect == 1) + max77705_dp_detach(usbpd_data); + if (usbpd_data->acc_type != PDIC_DOCK_DETACHED) { + pr_info("%s: schedule_delayed_work - pd_state : %d\n", + __func__, usbpd_data->pd_state); + if (usbpd_data->acc_type == PDIC_DOCK_HMT) + schedule_delayed_work(&usbpd_data->acc_detach_work, + msecs_to_jiffies(GEAR_VR_DETACH_WAIT_MS)); + else + schedule_delayed_work(&usbpd_data->acc_detach_work, + msecs_to_jiffies(0)); + } + usbpd_data->mdm_block = 0; + usbpd_data->is_host = HOST_OFF; + usbpd_data->is_client = CLIENT_OFF; + /* muic */ + max77705_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_MUIC, PDIC_NOTIFY_ID_ATTACH, + 0/*attach*/, 0/*rprd*/, 0); + /* USB */ + max77705_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_USB, PDIC_NOTIFY_ID_USB, + 0/*attach*/, USB_STATUS_NOTIFY_DETACH/*drp*/, 0); + } + +} + +static irqreturn_t max77705_vconncop_irq(int irq, void *data) +{ + struct max77705_usbc_platform_data *usbc_data = data; + struct max77705_cc_data *cc_data = usbc_data->cc_data; + + max77705_read_reg(usbc_data->muic, REG_CC_STATUS1, &cc_data->cc_status1); + cc_data->vconnocp = (cc_data->cc_status1 & BIT_VCONNOCPI) + >> FFS(BIT_VCONNOCPI); + msg_maxim("New VCONNOCP Status Interrupt (%d)", + cc_data->vconnocp); + + return IRQ_HANDLED; +} + +static irqreturn_t max77705_vsafe0v_irq(int irq, void *data) +{ + struct max77705_usbc_platform_data *usbc_data = data; + struct max77705_cc_data *cc_data = usbc_data->cc_data; + u8 ccpinstat = 0; + u8 connstat = 0; + + pr_debug("%s: IRQ(%d)_IN\n", __func__, irq); + max77705_read_reg(usbc_data->muic, REG_BC_STATUS, &cc_data->bc_status); + max77705_read_reg(usbc_data->muic, REG_CC_STATUS0, &cc_data->cc_status0); + max77705_read_reg(usbc_data->muic, REG_CC_STATUS1, &cc_data->cc_status1); + ccpinstat = (cc_data->cc_status0 & BIT_CCPinStat) + >> FFS(BIT_CCPinStat); + cc_data->vsafe0v = (cc_data->cc_status1 & BIT_VSAFE0V) + >> FFS(BIT_VSAFE0V); + connstat = (cc_data->cc_status1 & BIT_ConnStat) + >> FFS(BIT_ConnStat); + + msg_maxim("New VSAFE0V Status Interrupt (%d)", + cc_data->vsafe0v); + pr_debug("%s: IRQ(%d)_OUT\n", __func__, irq); + + return IRQ_HANDLED; +} + + +static irqreturn_t max77705_vconnsc_irq(int irq, void *data) +{ + struct max77705_usbc_platform_data *usbc_data = data; + struct max77705_cc_data *cc_data = usbc_data->cc_data; + u8 connstat = 0; + + pr_debug("%s: IRQ(%d)_IN\n", __func__, irq); + max77705_read_reg(usbc_data->muic, REG_CC_STATUS1, &cc_data->cc_status1); + connstat = (cc_data->cc_status1 & BIT_ConnStat) + >> FFS(BIT_ConnStat); + + switch (connstat) { + case DRY: + msg_maxim("== WATER RUN-DRY DETECT =="); + if (usbc_data->current_connstat != DRY) { + usbc_data->prev_connstat = usbc_data->current_connstat; + usbc_data->current_connstat = DRY; + if(!usbc_data->max77705->blocking_waterevent) + max77705_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_WATER, + 0/*attach*/, + 0, + 0); + } + break; + + case WATER: + msg_maxim("== WATER DETECT =="); + + if (usbc_data->current_connstat != WATER) { + usbc_data->prev_connstat = usbc_data->current_connstat; + usbc_data->current_connstat = WATER; + if(!usbc_data->max77705->blocking_waterevent) + max77705_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_WATER, + 1/*attach*/, + 0, + 0); + } + break; + default: + break; + + } + + pr_debug("%s: IRQ(%d)_OUT\n", __func__, irq); + + return IRQ_HANDLED; +} + +static irqreturn_t max77705_ccpinstat_irq(int irq, void *data) +{ + struct max77705_usbc_platform_data *usbc_data = data; + struct max77705_cc_data *cc_data = usbc_data->cc_data; + u8 ccpinstat = 0; + + max77705_read_reg(usbc_data->muic, REG_CC_STATUS0, &cc_data->cc_status0); + + pr_debug("%s: IRQ(%d)_IN\n", __func__, irq); + ccpinstat = (cc_data->cc_status0 & BIT_CCPinStat) + >> FFS(BIT_CCPinStat); + + switch (ccpinstat) { + case NO_DETERMINATION: + msg_maxim("CCPINSTAT (NO_DETERMINATION)"); +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + if (usbc_data->ccrp_state) { + usbc_data->ccrp_state = 0; + max77705_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_BATT, PDIC_NOTIFY_ID_WATER_CABLE, + PDIC_NOTIFY_DETACH, 0/*rprd*/, 0); + } + max77705_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_ALL, PDIC_NOTIFY_ID_CLEAR_INFO, + PDIC_NOTIFY_ID_SVID_INFO, 0, 0); +#endif + break; + case CC1_ACTIVE: + msg_maxim("CCPINSTAT (CC1_ACTIVE)"); + break; + + case CC2_ACTVIE: + msg_maxim("CCPINSTAT (CC2_ACTIVE)"); + break; + + case AUDIO_ACCESSORY: + msg_maxim("CCPINSTAT (AUDIO_ACCESSORY)"); + break; + + default: + msg_maxim("CCPINSTAT [%d]", ccpinstat); + break; + + } + cc_data->ccpinstat = ccpinstat; + usbc_data->cc_pin_status = ccpinstat; +#if IS_ENABLED(CONFIG_SEC_FACTORY) + max77705_ccic_event_work(usbc_data, PDIC_NOTIFY_DEV_CCIC, + PDIC_NOTIFY_ID_CC_PIN_STATUS, ccpinstat, 0, 0); +#endif + pr_debug("%s: IRQ(%d)_OUT\n", __func__, irq); + + return IRQ_HANDLED; +} + +static irqreturn_t max77705_ccistat_irq(int irq, void *data) +{ + struct max77705_usbc_platform_data *usbc_data = data; + struct max77705_cc_data *cc_data = usbc_data->cc_data; + struct max77705_pd_data *pd_data = usbc_data->pd_data; + u8 ccistat = 0; + enum typec_pwr_opmode mode = TYPEC_PWR_MODE_USB; + usbc_cmd_data value; + + max77705_read_reg(usbc_data->muic, REG_CC_STATUS0, &cc_data->cc_status0); + pr_debug("%s: IRQ(%d)_IN\n", __func__, irq); + ccistat = (cc_data->cc_status0 & BIT_CCIStat) >> FFS(BIT_CCIStat); + switch (ccistat) { + case NOT_IN_UFP_MODE: + msg_maxim("Not in UFP"); + break; + + case CCI_500mA: + msg_maxim("Vbus Current is 500mA!"); + break; + + case CCI_1_5A: + msg_maxim("Vbus Current is 1.5A!"); + mode = TYPEC_PWR_MODE_1_5A; + break; + + case CCI_3_0A: + msg_maxim("Vbus Current is 3.0A!"); + mode = TYPEC_PWR_MODE_3_0A; + + if (usbc_data->srcccap_request_retry) { + usbc_data->pn_flag = false; + usbc_data->srcccap_request_retry = false; + init_usbc_cmd_data(&value); + value.opcode = OPCODE_SRCCAP_REQUEST; + value.write_data[0] = pd_data->pd_noti.sink_status.selected_pdo_num; + value.write_length = 1; + value.read_length = 1; + max77705_usbc_opcode_write(usbc_data, &value); + pr_info("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d) NUM(%d)\n", + __func__, value.opcode, value.write_length, value.read_length, + pd_data->pd_noti.sink_status.selected_pdo_num); + } + break; + + default: + msg_maxim("CCINSTAT(Never Call this routine) !"); + break; + + } + cc_data->ccistat = ccistat; + pr_debug("%s: IRQ(%d)_OUT\n", __func__, irq); + + max77705_notify_rp_current_level(usbc_data); + + if (!usbc_data->pd_support) { + usbc_data->pwr_opmode = mode; + typec_set_pwr_opmode(usbc_data->port, mode); + } + + return IRQ_HANDLED; +} + + +static irqreturn_t max77705_ccvnstat_irq(int irq, void *data) +{ + struct max77705_usbc_platform_data *usbc_data = data; + struct max77705_cc_data *cc_data = usbc_data->cc_data; + u8 ccvcnstat = 0; + + max77705_read_reg(usbc_data->muic, REG_CC_STATUS0, &cc_data->cc_status0); + + pr_debug("%s: IRQ(%d)_IN\n", __func__, irq); + ccvcnstat = (cc_data->cc_status0 & BIT_CCVcnStat) >> FFS(BIT_CCVcnStat); + + switch (ccvcnstat) { + case 0: + msg_maxim("Vconn Disabled"); + if (cc_data->current_vcon != OFF) { + cc_data->previous_vcon = cc_data->current_vcon; + cc_data->current_vcon = OFF; + } + break; + + case 1: + msg_maxim("Vconn Enabled"); + if (cc_data->current_vcon != ON) { + cc_data->previous_vcon = cc_data->current_vcon; + cc_data->current_vcon = ON; + } + break; + + default: + msg_maxim("ccvnstat(Never Call this routine) !"); + break; + + } + cc_data->ccvcnstat = ccvcnstat; + pr_debug("%s: IRQ(%d)_OUT\n", __func__, irq); + + + return IRQ_HANDLED; +} + +static void max77705_ccstat_irq_handler(void *data, int irq) +{ + struct power_supply *psy_charger; + union power_supply_propval val; + struct max77705_usbc_platform_data *usbc_data = data; + struct max77705_cc_data *cc_data = usbc_data->cc_data; + u8 ccstat = 0; + + int prev_power_role = usbc_data->typec_power_role; + +#if IS_ENABLED(CONFIG_USB_HOST_NOTIFY) + struct otg_notify *o_notify = get_otg_notify(); +#endif +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + int event; +#endif + + max77705_read_reg(usbc_data->muic, REG_CC_STATUS0, &cc_data->cc_status0); + ccstat = (cc_data->cc_status0 & BIT_CCStat) >> FFS(BIT_CCStat); + if (irq == CCIC_IRQ_INIT_DETECT) { + if (ccstat == cc_SINK) + msg_maxim("initial time : SNK"); + else + return; + } + + max77705_set_unmask_vbus(usbc_data, ccstat); + +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + if (ccstat == cc_No_Connection) + usbc_data->pd_state = max77705_State_PE_Initial_detach; + else if (ccstat == cc_SOURCE) + usbc_data->pd_state = max77705_State_PE_SRC_Send_Capabilities; + else if (ccstat == cc_SINK) + usbc_data->pd_state = max77705_State_PE_SNK_Wait_for_Capabilities; +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + store_usblog_notify(NOTIFY_FUNCSTATE, (void *)&usbc_data->pd_state, NULL); +#endif +#endif + if (!ccstat) { + if (usbc_data->plug_attach_done) { + msg_maxim("PLUG_DETACHED ---"); + if (usbc_data->partner) { + msg_maxim("ccstat : typec_unregister_partner"); + if (!IS_ERR(usbc_data->partner)) + typec_unregister_partner(usbc_data->partner); + usbc_data->partner = NULL; + usbc_data->typec_power_role = TYPEC_SINK; + usbc_data->typec_data_role = TYPEC_DEVICE; + usbc_data->pwr_opmode = TYPEC_PWR_MODE_USB; + } + if (usbc_data->typec_try_state_change == TRY_ROLE_SWAP_PR || + usbc_data->typec_try_state_change == TRY_ROLE_SWAP_DR) { + /* Role change try and new mode detected */ + msg_maxim("typec_reverse_completion, detached while pd_swap"); + usbc_data->typec_try_state_change = TRY_ROLE_SWAP_NONE; + complete(&usbc_data->typec_reverse_completion); + } + max77705_notify_dr_status(usbc_data, 0); + usbc_data->plug_attach_done = 0; + usbc_data->cc_data->current_pr = 0xFF; + usbc_data->pd_data->current_dr = 0xFF; + usbc_data->cc_data->current_vcon = 0xFF; + usbc_data->detach_done_wait = 1; +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + send_otg_notify(o_notify, NOTIFY_EVENT_PD_CONTRACT, 0); +#if defined(CONFIG_USB_AUDIO_POWER_SAVING) + send_otg_notify(o_notify, NOTIFY_EVENT_REVERSE_BYPASS_DEVICE_ATTACH, 0); +#endif +#endif +#if IS_ENABLED(CONFIG_COMBO_REDRIVER_PTN36502) + ptn36502_config(SAFE_STATE, 0); +#endif + } + } else { + if (!usbc_data->plug_attach_done) { + msg_maxim("PLUG_ATTACHED +++"); + usbc_data->plug_attach_done = 1; + } + } + + switch (ccstat) { + case cc_No_Connection: + msg_maxim("ccstat : cc_No_Connection"); + usbc_data->pd_data->cc_status = CC_NO_CONN; + wake_up_interruptible(&usbc_data->device_add_wait_q); + usbc_data->is_samsung_accessory_enter_mode = 0; + usbc_data->pn_flag = false; + usbc_data->pd_support = false; + usbc_data->srcccap_request_retry = false; + + if (!usbc_data->typec_try_state_change) + max77705_usbc_clear_queue(usbc_data); + + usbc_data->typec_power_role = TYPEC_SINK; + +#if IS_ENABLED(CONFIG_USB_HOST_NOTIFY) + send_otg_notify(o_notify, NOTIFY_EVENT_POWER_SOURCE, 0); + send_otg_notify(o_notify, NOTIFY_EVENT_DR_SWAP, 0); +#endif + max77705_detach_pd(usbc_data); + usbc_data->pd_pr_swap = cc_No_Connection; + max77705_vbus_turn_on_ctrl(usbc_data, OFF, false); +#if IS_ENABLED(CONFIG_SEC_FACTORY) + factory_execute_monitor(FAC_ABNORMAL_REPEAT_STATE); +#endif + cancel_delayed_work(&usbc_data->vbus_hard_reset_work); + break; + case cc_SINK: + msg_maxim("ccstat : cc_SINK, keep awake for a second."); + /* keep awake during pd communication */ + pm_wakeup_ws_event(&cc_data->ccstat_ws, 1000, false); + usbc_data->pd_data->cc_status = CC_SNK; + usbc_data->pn_flag = false; + + usbc_data->typec_power_role = TYPEC_SINK; + typec_set_pwr_role(usbc_data->port, TYPEC_SINK); +#if IS_ENABLED(CONFIG_USB_HOST_NOTIFY) + send_otg_notify(o_notify, NOTIFY_EVENT_POWER_SOURCE, 0); +#endif + if (cc_data->current_pr != SNK) { + cc_data->previous_pr = cc_data->current_pr; + cc_data->current_pr = SNK; + if (prev_power_role == TYPEC_SOURCE) + max77705_vbus_turn_on_ctrl(usbc_data, OFF, true); + } + psy_charger = power_supply_get_by_name("max77705-charger"); + if (psy_charger) { + val.intval = 1; + psy_do_property("max77705-charger", set, POWER_SUPPLY_EXT_PROP_CHGINSEL, val); + } else { + pr_err("%s: Fail to get psy charger\n", __func__); + } + max77705_notify_rp_current_level(usbc_data); +#if IS_ENABLED(CONFIG_SEC_FACTORY) + factory_execute_monitor(FAC_ABNORMAL_REPEAT_STATE); +#endif + break; + case cc_SOURCE: + msg_maxim("ccstat : cc_SOURCE"); + usbc_data->pd_data->cc_status = CC_SRC; + usbc_data->pn_flag = false; + usbc_data->srcccap_request_retry = false; + + usbc_data->typec_power_role = TYPEC_SOURCE; + typec_set_pwr_role(usbc_data->port, TYPEC_SOURCE); +#if IS_ENABLED(CONFIG_USB_HOST_NOTIFY) + send_otg_notify(o_notify, NOTIFY_EVENT_POWER_SOURCE, 1); +#endif +#if defined(CONFIG_USB_AUDIO_POWER_SAVING) + send_otg_notify(o_notify, NOTIFY_EVENT_REVERSE_BYPASS_DEVICE_ATTACH, 1); +#endif + if (cc_data->current_pr != SRC) { + cc_data->previous_pr = cc_data->current_pr; + cc_data->current_pr = SRC; + + if (prev_power_role == TYPEC_SINK) + max77705_vbus_turn_on_ctrl(usbc_data, ON, false); +#if IS_ENABLED(CONFIG_DUAL_ROLE_USB_INTF) + else if (prev_power_role == DUAL_ROLE_PROP_PR_SNK) + max77705_vbus_turn_on_ctrl(usbc_data, ON, true); +#endif + } + break; + case cc_Audio_Accessory: + msg_maxim("ccstat : cc_Audio_Accessory"); + usbc_data->acc_type = PDIC_DOCK_UNSUPPORTED_AUDIO; + max77705_process_check_accessory(usbc_data); +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_USB_ANALOGAUDIO; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + break; + case cc_Debug_Accessory: + msg_maxim("ccstat : cc_Debug_Accessory"); + break; + case cc_Error: + msg_maxim("ccstat : cc_Error"); + break; + case cc_Disabled: + msg_maxim("ccstat : cc_Disabled"); + break; + case cc_RFU: + msg_maxim("ccstat : cc_RFU"); + break; + default: + break; + } +} + +static irqreturn_t max77705_ccstat_irq(int irq, void *data) +{ + pr_debug("%s: IRQ(%d)_IN\n", __func__, irq); + max77705_ccstat_irq_handler(data, irq); + pr_debug("%s: IRQ(%d)_OUT\n", __func__, irq); + return IRQ_HANDLED; +} + +int max77705_cc_init(struct max77705_usbc_platform_data *usbc_data) +{ + struct max77705_cc_data *cc_data = NULL; + int ret; + + msg_maxim("IN"); + + cc_data = usbc_data->cc_data; + cc_data->ccstat_ws.name = "max77705-ccstat"; + wakeup_source_add(&cc_data->ccstat_ws); + + cc_data->irq_vconncop = usbc_data->irq_base + MAX77705_CC_IRQ_VCONNCOP_INT; + if (cc_data->irq_vconncop) { + ret = request_threaded_irq(cc_data->irq_vconncop, + NULL, max77705_vconncop_irq, + 0, + "cc-vconncop-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + goto err_irq; + } + } + + cc_data->irq_vsafe0v = usbc_data->irq_base + MAX77705_CC_IRQ_VSAFE0V_INT; + if (cc_data->irq_vsafe0v) { + ret = request_threaded_irq(cc_data->irq_vsafe0v, + NULL, max77705_vsafe0v_irq, + 0, + "cc-vsafe0v-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + goto err_irq; + } + } + + cc_data->irq_vconnsc = usbc_data->irq_base + MAX77705_CC_IRQ_VCONNSC_INT; + if (cc_data->irq_vconnsc) { + ret = request_threaded_irq(cc_data->irq_vconnsc, + NULL, max77705_vconnsc_irq, + 0, + "cc-vconnsc-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + goto err_irq; + } + } + cc_data->irq_ccpinstat = usbc_data->irq_base + MAX77705_CC_IRQ_CCPINSTAT_INT; + if (cc_data->irq_ccpinstat) { + ret = request_threaded_irq(cc_data->irq_ccpinstat, + NULL, max77705_ccpinstat_irq, + 0, + "cc-ccpinstat-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + goto err_irq; + } + } + cc_data->irq_ccistat = usbc_data->irq_base + MAX77705_CC_IRQ_CCISTAT_INT; + if (cc_data->irq_ccistat) { + ret = request_threaded_irq(cc_data->irq_ccistat, + NULL, max77705_ccistat_irq, + 0, + "cc-ccistat-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + goto err_irq; + } + } + cc_data->irq_ccvcnstat = usbc_data->irq_base + MAX77705_CC_IRQ_CCVCNSTAT_INT; + if (cc_data->irq_ccvcnstat) { + ret = request_threaded_irq(cc_data->irq_ccvcnstat, + NULL, max77705_ccvnstat_irq, + 0, + "cc-ccvcnstat-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + goto err_irq; + } + } + cc_data->irq_ccstat = usbc_data->irq_base + MAX77705_CC_IRQ_CCSTAT_INT; + if (cc_data->irq_ccstat) { + ret = request_threaded_irq(cc_data->irq_ccstat, + NULL, max77705_ccstat_irq, + 0, + "cc-ccstat-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + goto err_irq; + } + } + /* check CC Pin state for cable attach booting scenario */ + max77705_ccstat_irq_handler(usbc_data, CCIC_IRQ_INIT_DETECT); + max77705_read_reg(usbc_data->muic, REG_CC_STATUS1, &cc_data->cc_status1); + usbc_data->current_connstat = (cc_data->cc_status1 & BIT_ConnStat) + >> FFS(BIT_ConnStat); + pr_info("%s: water state : %s\n", __func__, usbc_data->current_connstat ? "WATER" : "DRY"); + + if (usbc_data->current_connstat) { + if(!usbc_data->max77705->blocking_waterevent) + max77705_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_WATER, + 1/*attach*/, + 0, + 0); + } + msg_maxim("OUT"); + + return 0; + +err_irq: + kfree(cc_data); + return ret; + +} diff --git a/drivers/usb/typec/maxim/max77705_debug.c b/drivers/usb/typec/maxim/max77705_debug.c new file mode 100644 index 000000000000..79d6cb82bef7 --- /dev/null +++ b/drivers/usb/typec/maxim/max77705_debug.c @@ -0,0 +1,507 @@ +/* + * Copyright (c) 2017 Maxim Integrated Products, 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 + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_COMPAT +#include +#endif /* CONFIG_COMPAT */ +#include + +#define DEBUG_MXIM +#ifdef DEBUG_MXIM +#define msg_maxim(format, args...) \ +pr_info("[MXIM] %s: " format "\n", __func__, ## args) +#else +#define msg_maxim(format, args...) +#endif /* DEBUG_MXIM */ + +struct mxim_debug_registers mxim_debug_reg[] = { + { + .reg = MXIM_REG_UIC_HW_REV, + .val = 0, + .ignore = 0, + }, + { + .reg = MXIM_REG_UIC_FW_REV, + .val = 0, + .ignore = 0, + }, + { + .reg = MXIM_REG_USBC_IRQ, + .val = 0, + .ignore = 1, + }, + { + .reg = MXIM_REG_CC_IRQ, + .val = 0, + .ignore = 1, + }, + { + .reg = MXIM_REG_PD_IRQ, + .val = 0, + .ignore = 1, + }, + { + .reg = MXIM_REG_RSVD1, + .val = 0, + .ignore = 0, + }, + { + .reg = MXIM_REG_USBC_STATUS1, + .val = 0, + .ignore = 0, + }, + { + .reg = MXIM_REG_USBC_STATUS2, + .val = 0, + .ignore = 0, + }, + { + .reg = MXIM_REG_BC_STATUS, + .val = 0, + .ignore = 0, + }, + { + .reg = MXIM_REG_RSVD2, + .val = 0, + .ignore = 0, + }, + { + .reg = MXIM_REG_CC_STATUS1, + .val = 0, + .ignore = 0, + }, + { + .reg = MXIM_REG_CC_STATUS2, + .val = 0, + .ignore = 0, + }, + { + .reg = MXIM_REG_PD_STATUS1, + .val = 0, + .ignore = 0, + }, + { + .reg = MXIM_REG_PD_STATUS2, + .val = 0, + .ignore = 0, + }, + { + .reg = MXIM_REG_USBC_IRQM, + .val = 0, + .ignore = 0, + }, + { + .reg = MXIM_REG_CC_IRQM, + .val = 0, + .ignore = 0, + }, + { + .reg = MXIM_REG_PD_IRQM, + .val = 0, + .ignore = 0, + }, +}; + +struct mxim_debug_pdev *mxim_pdev; + +inline int mxim_debug_i2c_burst_write(unsigned char command, + unsigned char length, unsigned char *value) +{ + int ret; + + /* Max length 32 bytes */ + ret = i2c_smbus_write_i2c_block_data(mxim_pdev->client, + command, length, value); + if (ret < 0) + msg_maxim("Failed to write as burst mode"); + + return ret; +} + +inline int mxim_debug_i2c_write(unsigned char command, + unsigned char value) +{ + if (!mxim_pdev) + return -ENXIO; + + if (mxim_pdev->client == NULL) + return -ENXIO; + + return i2c_smbus_write_byte_data(mxim_pdev->client, + command, value); +} + +static int mxim_debug_i2c_burst_read(unsigned char command, + unsigned char length, unsigned char *value) +{ + int ret; + + /* Max length 32 bytes */ + ret = i2c_smbus_read_i2c_block_data(mxim_pdev->client, + command, length, value); + if (ret < 0) + msg_maxim("Failed to read as burst mode"); + + return ret; +} + +static int mxim_debug_i2c_read(unsigned char reg, + unsigned char *value) +{ + int ret = 0; + + if (!mxim_pdev) + return -ENXIO; + + if (mxim_pdev->client == NULL) + return -ENXIO; + + ret = i2c_smbus_read_byte_data(mxim_pdev->client, reg); + if (ret < 0) { + msg_maxim("Failed to read [reg:%02x]. ret=%d", + reg, ret); + } + + *value = ret; + + return ret; +} + +static ssize_t mxim_debug_reg_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char token[12] = { 0, }; + unsigned char dump[12 * (MXIM_REG_MAX + 1)] = { 0, }; + int index; + + sprintf(token, "reg val\n"); + strcat(dump, token); + for (index = 0; index < MXIM_REG_MAX; index++) { + if (mxim_debug_reg[index].ignore) + continue; + memset(token, 0x00, sizeof(token)); + mxim_debug_i2c_read(mxim_debug_reg[index].reg, + &mxim_debug_reg[index].val); + sprintf(token, "0x%02x 0x%02x\n", + mxim_debug_reg[index].reg, + mxim_debug_reg[index].val); + strcat(dump, token); + } + + return sprintf(buf, "%s", dump); +} + +static ssize_t mxim_debug_reg_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + unsigned int reg = 0; + unsigned int value = 0; + unsigned char prev = 0; + unsigned char curr = 0; + int ret; + + ret = sscanf(buf, "%x%x", ®, &value); + if (!ret) { + msg_maxim("Failed to convert user data"); + goto error; + } + + mxim_debug_i2c_read((unsigned char)reg, &prev); + mxim_debug_i2c_write((unsigned char)reg, (unsigned char)value); + mxim_debug_i2c_read((unsigned char)reg, &curr); + +error: + return size; +} + +static DEVICE_ATTR(reg, 0664, + mxim_debug_reg_show, mxim_debug_reg_store); + +static ssize_t mxim_debug_opcode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data[OPCODE_READ_MAX_LENGTH] = { 0, }; + unsigned char dummy = 0x00; + char data_str[OPCODE_READ_MAX_LENGTH * 5 + 1] = { 0, }; + char dummy_str[5 + 1] = { 0, }; /* add null character */ + int index = 0; + + for (index = 0; index < OPCODE_READ_MAX_LENGTH; index++) { + data[index] = mxim_debug_i2c_read( + OPCODE_READ_START_ADDR + index, + &dummy); + sprintf(dummy_str, "0x%02x ", data[index]); + strcat(data_str, dummy_str); + } + + return sprintf(buf, "%s\n", data_str); +} + +static ssize_t mxim_debug_opcode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + unsigned int data[OPCODE_WRITE_MAX_LENGTH] = { 0, }; + int index; + int ret; + + ret = sscanf(buf, + "%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x", + &data[0], /* AP_DATAOUT0 */ + &data[1], &data[2], &data[3], &data[4], + &data[5], &data[6], &data[7], &data[8], + &data[9], &data[10], &data[11], &data[12], + &data[13], &data[14], &data[15], &data[16], + &data[17], &data[18], &data[19], &data[20], + &data[21], &data[22], &data[23], &data[24], + &data[25], &data[26], &data[27], &data[28], + &data[29], &data[30], &data[31], &data[32]); + if (!ret) { + msg_maxim("Failed to convert user data"); + goto error; + } + + for (index = 0; index < OPCODE_WRITE_MAX_LENGTH; index++) + mxim_debug_i2c_write(OPCODE_WRITE_START_ADDR + index, + (unsigned char)data[index]); + +error: + return size; +} + +static DEVICE_ATTR(opcode, 0664, + mxim_debug_opcode_show, mxim_debug_opcode_store); + +static struct attribute *mxim_debug_attr[] = { + &dev_attr_opcode.attr, + &dev_attr_reg.attr, + NULL, +}; + +static struct attribute_group mxim_debug_attr_grp = { + .attrs = mxim_debug_attr, +}; + +static int mxim_debug_open(struct inode *inode, struct file *filep) +{ + return 0; +} + +static long mxim_debug_ioctl_handler(struct file *file, + unsigned int cmd, unsigned int arg, + void __user *argp) +{ + unsigned char reg = 0; + unsigned char val = 0; + int ret = -EINVAL; + + mutex_lock(&mxim_pdev->lock); + + switch (cmd) { + case MXIM_DEBUG_OPCODE_WRITE: + /* copy wdata from user */ + if (copy_from_user(mxim_pdev->opcode_wdata, + argp, OPCODE_WRITE_MAX_LENGTH)) { + msg_maxim("Failed to copy data buffer from user"); + ret = -EFAULT; + goto error; + } + + /* OPCode */ + mxim_debug_i2c_write(OPCODE_WRITE_START_ADDR, + mxim_pdev->opcode_wdata[0]); + + /* AP OUT Data */ + ret = mxim_debug_i2c_burst_write(OPCODE_WRITE_START_ADDR + 1, + (OPCODE_WRITE_MAX_LENGTH - 1), + &mxim_pdev->opcode_wdata[1]); + break; + case MXIM_DEBUG_OPCODE_READ: + /* OPCode */ + mxim_debug_i2c_read(OPCODE_READ_START_ADDR, + &mxim_pdev->opcode_rdata[0]); + + /* AP IN Data */ + ret = mxim_debug_i2c_burst_read(OPCODE_READ_START_ADDR + 1, + (OPCODE_READ_MAX_LENGTH - 1), + &mxim_pdev->opcode_rdata[1]); + + /* copy rdata to user */ + if (copy_to_user(argp, mxim_pdev->opcode_rdata, + OPCODE_READ_MAX_LENGTH)) { + msg_maxim("Failed to copy data buffer to user"); + ret = -EFAULT; + goto error; + } + break; + case MXIM_DEBUG_REG_WRITE: + reg = (unsigned char)(arg & 0x000000FF); + val = (unsigned char)((arg & 0x0000FF00) >> 8); + ret = mxim_debug_i2c_write(reg, val); + break; + case MXIM_DEBUG_REG_READ: + reg = (unsigned int)(arg & 0x000000FF); + mxim_debug_i2c_read(reg, &val); + ret = val; + if (copy_to_user(argp, &val, sizeof(val))) + goto error; + break; + case MXIM_DEBUG_REG_DUMP: + break; + default: + break; + } +error: + mutex_unlock(&mxim_pdev->lock); + return ret; +} + +static long mxim_debug_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + return mxim_debug_ioctl_handler(file, cmd, arg, + (void __user *)arg); +} + +#ifdef CONFIG_COMPAT +static long mxim_debug_compat_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + return mxim_debug_ioctl_handler(file, cmd, arg, + (void __user *)(unsigned long)arg); +} +#endif + +static ssize_t mxim_debug_read(struct file *filep, char __user *buf, + size_t count, loff_t *ppos) +{ + int ret = 0; + + if (!mxim_pdev) + goto error; + + ret = copy_to_user(buf, mxim_pdev->opcode_rdata, + count > OPCODE_READ_MAX_LENGTH ? + OPCODE_READ_MAX_LENGTH : count); + if (ret) + msg_maxim("Failed to copy user buffer"); + +error: + return ret; +} + +static ssize_t mxim_debug_write(struct file *filep, const char __user *buf, + size_t count, loff_t *ppos) +{ + int ret = 0; + + if (!mxim_pdev) + goto error; + + if (copy_from_user(mxim_pdev->opcode_wdata, buf, + OPCODE_READ_MAX_LENGTH)) { + msg_maxim("Failed to copy user buffer"); + goto error; + } + +error: + return ret; +} + +void mxim_debug_set_i2c_client(struct i2c_client *client) +{ + if (mxim_pdev) { + mxim_pdev->client = client; + msg_maxim("Set i2c_client. %pK", mxim_pdev->client); + } else + msg_maxim("Failed to set i2c_client."); +} +EXPORT_SYMBOL_GPL(mxim_debug_set_i2c_client); + +static const struct file_operations mxim_debug_fops = { + .owner = THIS_MODULE, + .open = mxim_debug_open, + .release = NULL, + .unlocked_ioctl = mxim_debug_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = mxim_debug_compat_ioctl, +#endif /* CONFIG_COMPAT */ + .read = mxim_debug_read, + .write = mxim_debug_write, + .mmap = NULL, + .poll = NULL, + .fasync = NULL, + .llseek = NULL, +}; +static struct miscdevice mxim_debug_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "mxim_dev", + .fops = &mxim_debug_fops, +}; + +void mxim_debug_exit(void) +{ + if (mxim_pdev) { + mutex_destroy(&mxim_pdev->lock); + sysfs_remove_group(&mxim_pdev->dev->kobj, &mxim_debug_attr_grp); + device_destroy(mxim_pdev->class, 1); + class_destroy(mxim_pdev->class); + kfree(mxim_pdev); + mxim_pdev = NULL; + } + + misc_deregister(&mxim_debug_miscdev); +} +EXPORT_SYMBOL_GPL(mxim_debug_exit); + +int mxim_debug_init(void) +{ + struct mxim_debug_pdev *pdev; + int ret; + + ret = misc_register(&mxim_debug_miscdev); + if (ret) { + msg_maxim("Failed to register miscdevice"); + goto error; + } + + pdev = kzalloc(sizeof(struct mxim_debug_pdev), GFP_KERNEL); + if (!pdev) { + msg_maxim("Failed to allocate memory"); + ret = -ENOMEM; + goto error; + } + + mxim_pdev = pdev; + + mutex_init(&pdev->lock); + + pdev->class = class_create(THIS_MODULE, "mxim"); + if (pdev->class) { + pdev->dev = device_create(pdev->class, NULL, 1, NULL, "debug0"); + if (!IS_ERR(pdev->dev)) { + if (sysfs_create_group(&pdev->dev->kobj, + &mxim_debug_attr_grp)) + msg_maxim("Failed to create sysfs group. %d", + ret); + } + } + +error: + return ret; +} +EXPORT_SYMBOL_GPL(mxim_debug_init); diff --git a/drivers/usb/typec/maxim/max77705_pd.c b/drivers/usb/typec/maxim/max77705_pd.c new file mode 100644 index 000000000000..53e28b552f82 --- /dev/null +++ b/drivers/usb/typec/maxim/max77705_pd.c @@ -0,0 +1,1981 @@ +/* + * Copyrights (C) 2017 Samsung Electronics, Inc. + * Copyrights (C) 2017 Maxim Integrated Products, 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) +#include +#endif +#include +#include +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) +#include +#include +#endif +#include +#include +#include +#include +#include + +#if IS_ENABLED(CONFIG_SEC_MPARAM) || (IS_MODULE(CONFIG_SEC_PARAM) && defined(CONFIG_ARCH_EXYNOS)) +#if defined(CONFIG_SEC_FACTORY) +extern int factory_mode; +#endif +#else +#if defined(CONFIG_SEC_FACTORY) +static int __read_mostly factory_mode; +module_param(factory_mode, int, 0444); +#endif +#endif + +extern struct max77705_usbc_platform_data *g_usbc_data; +extern void max77705_set_CCForceError(struct max77705_usbc_platform_data *usbpd_data); +void max77705_set_enable_pps(bool bPPS_on, bool enable, int ppsVol, int ppsCur); + +#if defined(CONFIG_SEC_FACTORY) +static int max77705_get_facmode(void) { return factory_mode; } +#endif + +static void max77705_process_pd(struct max77705_usbc_platform_data *usbc_data) +{ + struct max77705_pd_data *pd_data = usbc_data->pd_data; + + if (pd_data->cc_sbu_short) { + pd_data->pd_noti.sink_status.available_pdo_num = 1; + pd_data->pd_noti.sink_status.power_list[1].max_current = + pd_data->pd_noti.sink_status.power_list[1].max_current > 1800 ? + 1800 : pd_data->pd_noti.sink_status.power_list[1].max_current; + pd_data->pd_noti.sink_status.has_apdo = false; + } + + pr_info("%s : current_pdo_num(%d), available_pdo_num(%d), has_apdo(%d)\n", __func__, + pd_data->pd_noti.sink_status.current_pdo_num, pd_data->pd_noti.sink_status.available_pdo_num, + pd_data->pd_noti.sink_status.has_apdo); + + max77705_ccic_event_work(usbc_data, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_POWER_STATUS, 1/*attach*/, 0, 0); +} +static void max77705_send_new_src_cap(struct max77705_usbc_platform_data *pusbpd, + int auth, int d2d_type); +void max77705_vbus_turn_on_ctrl(struct max77705_usbc_platform_data *usbc_data, bool enable, bool swaped); +void max77705_response_req_pdo(struct max77705_usbc_platform_data *usbc_data, + unsigned char *data) +{ + u8 sel_pdo = 0x00; + int auth_type = usbc_data->pd_data->auth_type; + int d2d_type = usbc_data->pd_data->d2d_type; + + if ((d2d_type == D2D_NONE) || (auth_type == AUTH_NONE)) + return; + + sel_pdo = ((data[1] >> 3) & 0x07); + + if (d2d_type == D2D_SRCSNK) { + if (sel_pdo == 1) { + /* 2.5w fpdo */ + usbc_data->pd_data->req_pdo_type = PDO_TYPE_FIXED; + } else if ((sel_pdo >= 2) && + (auth_type == AUTH_HIGH_PWR)) { + /* 15w vpdo */ + usbc_data->pd_data->req_pdo_type = PDO_TYPE_VARIABLE; + } + /* TEST */ + + //if (auth_type == AUTH_HIGH_PWR) + // usbc_data->pd_data->req_pdo_type = PDO_TYPE_VARIABLE; + //else + // usbc_data->pd_data->req_pdo_type = PDO_TYPE_FIXED; + + /* TEST */ + } else + usbc_data->pd_data->req_pdo_type = PDO_TYPE_FIXED; + + pr_info("%s : snk pdo(%d, %d)\n", __func__, sel_pdo, usbc_data->pd_data->req_pdo_type); + max77705_vbus_turn_on_ctrl(usbc_data, ON, false); +} + +void max77705_check_req_pdo(struct max77705_usbc_platform_data *usbc_data) +{ + usbc_cmd_data value; + + init_usbc_cmd_data(&value); + value.opcode = OPCODE_SNK_SELECTED_PDO; + value.write_length = 0x0; + value.read_length = 31; + max77705_usbc_opcode_read(usbc_data, &value); + + pr_info("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d)\n", + __func__, value.opcode, value.write_length, value.read_length); +} +void max77705_select_pdo(int num) +{ + struct max77705_pd_data *pd_data = g_usbc_data->pd_data; + usbc_cmd_data value; + u8 temp; + + if (pd_data->pd_noti.event == PDIC_NOTIFY_EVENT_DETACH) { + pr_info("%s : PD TA already detached. Doesn't select pdo(%d)\n", __func__, num); + return; + } + + max77705_set_enable_pps(pd_data->bPPS_on, false, 0, 0); + + init_usbc_cmd_data(&value); + pr_info("%s : NUM(%d)\n", __func__, num); + + temp = num; + + pd_data->pd_noti.sink_status.selected_pdo_num = num; + + if (pd_data->pd_noti.event != PDIC_NOTIFY_EVENT_PD_SINK) + pd_data->pd_noti.event = PDIC_NOTIFY_EVENT_PD_SINK; + + if (pd_data->pd_noti.sink_status.current_pdo_num == pd_data->pd_noti.sink_status.selected_pdo_num) { + max77705_process_pd(g_usbc_data); + } else { + g_usbc_data->pn_flag = false; + value.opcode = OPCODE_SRCCAP_REQUEST; + value.write_data[0] = temp; + value.write_length = 1; + value.read_length = 1; + max77705_usbc_opcode_write(g_usbc_data, &value); + } + + pr_info("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d) NUM(%d)\n", + __func__, value.opcode, value.write_length, value.read_length, num); +} + +void max77705_response_pdo_request(struct max77705_usbc_platform_data *usbc_data, + unsigned char *data) +{ + u8 result = data[1]; + + pr_info("%s: %s (0x%02X)\n", __func__, result ? "Error," : "Sent,", result); + + switch (result) { + case 0x00: + pr_info("%s: Sent PDO Request Message to Port Partner(0x%02X)\n", __func__, result); + break; + case 0xFE: + pr_info("%s: Error, SinkTxNg(0x%02X)\n", __func__, result); + break; + case 0xFF: + pr_info("%s: Error, Not in SNK Ready State(0x%02X)\n", __func__, result); + break; + default: + break; + } + + /* retry if the state of sink is not stable yet */ + if (result == 0xFE || result == 0xFF) { + cancel_delayed_work(&usbc_data->pd_data->retry_work); + queue_delayed_work(usbc_data->pd_data->wqueue, &usbc_data->pd_data->retry_work, 0); + } +} + +void max77705_set_enable_pps(bool bPPS_on, bool enable, int ppsVol, int ppsCur) +{ + usbc_cmd_data value; + + if (bPPS_on == enable) + return; + + init_usbc_cmd_data(&value); + value.opcode = OPCODE_SET_PPS; + if (enable) { + value.write_data[0] = 0x1; //PPS_ON On + value.write_data[1] = (ppsVol / 20) & 0xFF; //Default Output Voltage (Low), 20mV + value.write_data[2] = ((ppsVol / 20) >> 8) & 0xFF; //Default Output Voltage (High), 20mV + value.write_data[3] = (ppsCur / 50) & 0x7F; //Default Operating Current, 50mA + value.write_length = 4; + value.read_length = 1; + pr_info("%s : PPS_On (Vol:%dmV, Cur:%dmA)\n", __func__, ppsVol, ppsCur); + } else { + value.write_data[0] = 0x0; //PPS_ON Off + value.write_length = 1; + value.read_length = 1; + pr_info("%s : PPS_Off\n", __func__); + } + max77705_usbc_opcode_write(g_usbc_data, &value); +} + +void max77705_response_set_pps(struct max77705_usbc_platform_data *usbc_data, + unsigned char *data) +{ + u8 result = data[1]; + + if (result & 0x01) + usbc_data->pd_data->bPPS_on = true; + else + usbc_data->pd_data->bPPS_on = false; + + pr_info("%s : PPS_%s (0x%02X)\n", + __func__, usbc_data->pd_data->bPPS_on ? "On" : "Off", result); +} + +void max77705_response_apdo_request(struct max77705_usbc_platform_data *usbc_data, + unsigned char *data) +{ + u8 result = data[1]; + u8 status[5]; + u8 vbvolt; + + pr_info("%s: %s (0x%02X)\n", __func__, result ? "Error," : "Sent,", result); + + switch (result) { + case 0x00: + pr_info("%s: Sent APDO Request Message to Port Partner(0x%02X)\n", __func__, result); + break; + case 0x01: + pr_info("%s: Error, Invalid APDO position(0x%02X)\n", __func__, result); + break; + case 0x02: + pr_info("%s: Error, Invalid Output Voltage(0x%02X)\n", __func__, result); + break; + case 0x03: + pr_info("%s: Error, Invalid Operating Current(0x%02X)\n", __func__, result); + break; + case 0x04: + pr_info("%s: Error, PPS Function Off(0x%02X)\n", __func__, result); + break; + case 0x05: + pr_info("%s: Error, Not in SNK Ready State(0x%02X)\n", __func__, result); + break; + case 0x06: + pr_info("%s: Error, PD2.0 Contract(0x%02X)\n", __func__, result); + break; + case 0x07: + pr_info("%s: Error, SinkTxNg(0x%02X)\n", __func__, result); + break; + default: + break; + } + + max77705_bulk_read(usbc_data->muic, MAX77705_USBC_REG_USBC_STATUS1, 5, status); + vbvolt = (status[2] & BC_STATUS_VBUSDET_MASK) >> BC_STATUS_VBUSDET_SHIFT; + if (vbvolt != 0x01) + pr_info("%s: Error, VBUS isn't above 5V(0x%02X)\n", __func__, vbvolt); + + /* retry if the state of sink is not stable yet */ + if ((result == 0x05 || result == 0x07) && vbvolt == 0x1) { + cancel_delayed_work(&usbc_data->pd_data->retry_work); + queue_delayed_work(usbc_data->pd_data->wqueue, &usbc_data->pd_data->retry_work, 0); + } +} + +int max77705_select_pps(int num, int ppsVol, int ppsCur) +{ + struct max77705_pd_data *pd_data = g_usbc_data->pd_data; + usbc_cmd_data value; + + /* [dchg] TODO: check more below option */ + if (num > pd_data->pd_noti.sink_status.available_pdo_num) { + pr_info("%s: request pdo num(%d) is higher taht available pdo.\n", __func__, num); + return -EINVAL; + } + + if (!(pd_data->pd_noti.sink_status.power_list[num].pdo_type == APDO_TYPE)) { + pr_info("%s: request pdo num(%d) is not apdo.\n", __func__, num); + return -EINVAL; + } else + pd_data->pd_noti.sink_status.selected_pdo_num = num; + + if (ppsVol > pd_data->pd_noti.sink_status.power_list[num].max_voltage) { + pr_info("%s: ppsVol is over(%d, max:%d)\n", + __func__, ppsVol, pd_data->pd_noti.sink_status.power_list[num].max_voltage); + ppsVol = pd_data->pd_noti.sink_status.power_list[num].max_voltage; + } else if (ppsVol < pd_data->pd_noti.sink_status.power_list[num].min_voltage) { + pr_info("%s: ppsVol is under(%d, min:%d)\n", + __func__, ppsVol, pd_data->pd_noti.sink_status.power_list[num].min_voltage); + ppsVol = pd_data->pd_noti.sink_status.power_list[num].min_voltage; + } + + if (ppsCur > pd_data->pd_noti.sink_status.power_list[num].max_current) { + pr_info("%s: ppsCur is over(%d, max:%d)\n", + __func__, ppsCur, pd_data->pd_noti.sink_status.power_list[num].max_current); + ppsCur = pd_data->pd_noti.sink_status.power_list[num].max_current; + } else if (ppsCur < 0) { + pr_info("%s: ppsCur is under(%d, 0)\n", + __func__, ppsCur); + ppsCur = 0; + } + + pd_data->pd_noti.sink_status.pps_voltage = ppsVol; + pd_data->pd_noti.sink_status.pps_current = ppsCur; + + pr_info(" %s : PPS PDO(%d), voltage(%d), current(%d) is selected to change\n", + __func__, pd_data->pd_noti.sink_status.selected_pdo_num, ppsVol, ppsCur); + + max77705_set_enable_pps(pd_data->bPPS_on, true, 5000, 1000); /* request as default 5V when enable first */ + + init_usbc_cmd_data(&value); + + g_usbc_data->pn_flag = false; + value.opcode = OPCODE_APDO_SRCCAP_REQUEST; + value.write_data[0] = (num & 0xFF); /* APDO Position */ + value.write_data[1] = (ppsVol / 20) & 0xFF; /* Output Voltage(Low) */ + value.write_data[2] = ((ppsVol / 20) >> 8) & 0xFF; /* Output Voltage(High) */ + value.write_data[3] = (ppsCur / 50) & 0x7F; /* Operating Current */ + value.write_length = 4; + value.read_length = 1; /* Result */ + max77705_usbc_opcode_write(g_usbc_data, &value); + +/* [dchg] TODO: add return value */ + return 0; +} + +void max77705_pd_retry_work(struct work_struct *work) +{ + struct max77705_pd_data *pd_data = g_usbc_data->pd_data; + usbc_cmd_data value; + u8 num; + + if (pd_data->pd_noti.event == PDIC_NOTIFY_EVENT_DETACH) + return; + + init_usbc_cmd_data(&value); + num = pd_data->pd_noti.sink_status.selected_pdo_num; + pr_info("%s : latest selected_pdo_num(%d)\n", __func__, num); + g_usbc_data->pn_flag = false; + + if (pd_data->pd_noti.sink_status.power_list[num].pdo_type == APDO_TYPE) { + value.opcode = OPCODE_APDO_SRCCAP_REQUEST; + value.write_data[0] = (num & 0xFF); /* APDO Position */ + value.write_data[1] = (pd_data->pd_noti.sink_status.pps_voltage / 20) & 0xFF; /* Output Voltage(Low) */ + value.write_data[2] = ((pd_data->pd_noti.sink_status.pps_voltage / 20) >> 8) & 0xFF; /* Output Voltage(High) */ + value.write_data[3] = (pd_data->pd_noti.sink_status.pps_current / 50) & 0x7F; /* Operating Current */ + value.write_length = 4; + value.read_length = 1; /* Result */ + max77705_usbc_opcode_write(g_usbc_data, &value); + } else { + value.opcode = OPCODE_SRCCAP_REQUEST; + value.write_data[0] = num; + value.write_length = 1; + value.read_length = 1; + max77705_usbc_opcode_write(g_usbc_data, &value); + } + + pr_info("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d) NUM(%d)\n", + __func__, value.opcode, value.write_length, value.read_length, num); +} + +void max77705_usbc_icurr(u8 curr) +{ + usbc_cmd_data value; + + init_usbc_cmd_data(&value); + value.opcode = OPCODE_CHGIN_ILIM_W; + value.write_data[0] = curr; + value.write_length = 1; + value.read_length = 0; + max77705_usbc_opcode_write(g_usbc_data, &value); + + pr_info("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d) USBC_ILIM(0x%x)\n", + __func__, value.opcode, value.write_length, value.read_length, curr); + +} +EXPORT_SYMBOL(max77705_usbc_icurr); + +void max77705_set_gpio5_control(int direction, int output) +{ + usbc_cmd_data value; + u8 op_data1, op_data2; + + if (direction) + op_data1 = 0xFF; // OUTPUT + else + op_data1 = 0x00; // INPUT + + if (output) + op_data2 = 0xFF; // HIGH + else + op_data2 = 0x00; // LOW + + init_usbc_cmd_data(&value); + value.opcode = OPCODE_SAMSUNG_GPIO5_CONTROL; + value.write_data[0] = op_data1; + value.write_data[1] = op_data2; + value.write_length = 2; + value.read_length = 0; + max77705_usbc_opcode_write(g_usbc_data, &value); + + pr_info("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d) gpio5(0x%x, 0x%x)\n", + __func__, value.opcode, value.write_length, value.read_length, op_data1, op_data2); +} + +void max77705_set_fw_noautoibus(int enable) +{ + usbc_cmd_data value; + u8 op_data = 0x00; + + switch (enable) { + case MAX77705_AUTOIBUS_FW_AT_OFF: + op_data = 0x03; /* usbc fw off & auto off(manual on) */ + break; + case MAX77705_AUTOIBUS_FW_OFF: + op_data = 0x02; /* usbc fw off & auto on(manual off) */ + break; + case MAX77705_AUTOIBUS_AT_OFF: + op_data = 0x01; /* usbc fw on & auto off(manual on) */ + break; + case MAX77705_AUTOIBUS_ON: + default: + op_data = 0x00; /* usbc fw on & auto on(manual off) */ + break; + } + +#if defined(CONFIG_SEC_FACTORY) + if (max77705_get_facmode()) { + pr_info("%s: Factory Mode set AUTOIBUS_FW_AT_OFF\n", __func__); + op_data = 0x03; /* usbc fw off & auto off(manual on) */ + } +#endif + + init_usbc_cmd_data(&value); + value.opcode = OPCODE_SAMSUNG_FW_AUTOIBUS; + value.write_data[0] = op_data; + value.write_length = 1; + value.read_length = 0; + max77705_usbc_opcode_write(g_usbc_data, &value); + + pr_info("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d) AUTOIBUS(0x%x)\n", + __func__, value.opcode, value.write_length, value.read_length, op_data); +} +EXPORT_SYMBOL(max77705_set_fw_noautoibus); + +#if defined(CONFIG_SUPPORT_SHIP_MODE) +void max77705_set_fw_ship_mode(int enable) +{ + struct max77705_usbc_platform_data *pusbpd = g_usbc_data; + usbc_cmd_data value; + + if (!enable) { + pr_info("%s: not support disable. current ship_mode_en(%d)\n", + __func__, pusbpd->ship_mode_en); + return; + } + + init_usbc_cmd_data(&value); + + value.opcode = OPCODE_SAMSUNG_SHIPMODE_EN; + value.write_data[0] = 0x38; + value.write_length = 1; + value.read_length = 2; + max77705_usbc_opcode_write(pusbpd, &value); + + pr_info("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d) ship_mone(0x%x)\n", + __func__, value.opcode, value.write_length, value.read_length, value.write_data[0]); +} +EXPORT_SYMBOL(max77705_set_fw_ship_mode); + +int max77705_get_fw_ship_mode(void) +{ + if (!g_usbc_data) + return 0; + + return g_usbc_data->ship_mode_en; +} +EXPORT_SYMBOL(max77705_get_fw_ship_mode); +#endif + +static void max77705_set_snkcap(struct max77705_usbc_platform_data *usbc_data) +{ + struct device_node *np = NULL; + u8 *snkcap_data; + int len = 0, ret = 0; + usbc_cmd_data value; + int i; + char *str = NULL; + + np = of_find_compatible_node(NULL, NULL, "maxim,max77705"); + if (!np) { + pr_info("%s : np is NULL\n", __func__); + return; + } + + if (!of_get_property(np, "max77705,snkcap_data", &len)) { + pr_info("%s : max77705,snkcap_data is Empty !!\n", __func__); + return; + } + + len = len / sizeof(u8); + snkcap_data = kzalloc(sizeof(*snkcap_data) * len, GFP_KERNEL); + if (!snkcap_data) { + pr_err("%s : Failed to allocate memory (snkcap_data)\n", __func__); + return; + } + + ret = of_property_read_u8_array(np, "max77705,snkcap_data", + snkcap_data, len); + if (ret) { + pr_info("%s : max77705,snkcap_data is Empty\n", __func__); + goto err_free_snkcap_data; + } + + init_usbc_cmd_data(&value); + + if (len) + memcpy(value.write_data, snkcap_data, len); + + str = kzalloc(sizeof(char) * 1024, GFP_KERNEL); + if (str) { + for (i = 0; i < len; i++) + sprintf(str + strlen(str), "0x%02x ", value.write_data[i]); + pr_info("%s: SNK_CAP : %s\n", __func__, str); + } + + value.opcode = OPCODE_SET_SNKCAP; + value.write_length = len; + value.read_length = 0; + max77705_usbc_opcode_write(usbc_data, &value); + + kfree(str); +err_free_snkcap_data: + kfree(snkcap_data); +} + +bool max77705_check_boost_enable(int auth_t, int req_pdo, int d2d_t) +{ + if ((auth_t == AUTH_NONE) || (d2d_t != D2D_SRCSNK)) + return false; + + if (req_pdo == PDO_TYPE_VARIABLE) + return true; + + return false; +} + +bool max77705_check_boost_off(int auth_t, int req_pdo, int d2d_t) +{ + if ((auth_t == AUTH_NONE) || (d2d_t != D2D_SRCSNK)) + return false; + + if (req_pdo == PDO_TYPE_FIXED) + return true; + + return false; +} + +bool max77705_check_src_otg_type(bool enable, int auth_t, int req_pdo, int d2d_t) +{ + if ((auth_t == AUTH_NONE) || (d2d_t != D2D_SRCSNK)) + return enable; + + if (req_pdo == PDO_TYPE_VARIABLE) + return false; + + return enable; +} + +void max77705_vbus_turn_on_ctrl(struct max77705_usbc_platform_data *usbc_data, bool enable, bool swaped) +{ + struct power_supply *psy_otg; + union power_supply_propval val; + int on = !!enable; + int ret = 0; + int count = 5; + int auth_type = usbc_data->pd_data->auth_type; + int req_pdo_type = usbc_data->pd_data->req_pdo_type; + int d2d_type = usbc_data->pd_data->d2d_type; +#if defined(CONFIG_USB_HOST_NOTIFY) + struct otg_notify *o_notify = get_otg_notify(); + bool must_block_host = 0; + static int reserve_booster = 0; + + if (o_notify) + must_block_host = is_blocked(o_notify, NOTIFY_BLOCK_TYPE_HOST); + + pr_info("%s : enable=%d, auto_vbus_en=%d, must_block_host=%d, swaped=%d\n", + __func__, enable, usbc_data->auto_vbus_en, must_block_host, swaped); + // turn on + if (enable) { + // auto-mode + if (usbc_data->auto_vbus_en) { + // mpsm + if (must_block_host) { + if (swaped) { + // turn off vbus because of swaped and blocked host + enable = false; + pr_info("%s : turn off vbus because of blocked host\n", + __func__); + } else { + enable = false; + pr_info("%s : turn off vbus because of blocked host\n", + __func__); + } + } else { + // don't turn on because of auto-mode + return; + } + // non auto-mode + } else { + if (must_block_host) { + if (swaped) { + enable = false; + pr_info("%s : turn off vbus because of blocked host\n", + __func__); + } else { + enable = false; + pr_info("%s : turn off vbus because of blocked host\n", + __func__); + } + } + } + // turn off + } else { + // don't turn off because of auto-mode or blocked (already off) + if (usbc_data->auto_vbus_en || must_block_host) + return; + } +#endif + + pr_info("%s : enable=%d\n", __func__, enable); + +#if defined(CONFIG_USB_HOST_NOTIFY) + if (o_notify && o_notify->booting_delay_sec && enable) { + pr_info("%s %d, is booting_delay_sec. skip to control booster\n", + __func__, __LINE__); + reserve_booster = 1; + send_otg_notify(o_notify, NOTIFY_EVENT_RESERVE_BOOSTER, 1); + return; + } + if (!enable) { + if (reserve_booster) { + reserve_booster = 0; + send_otg_notify(o_notify, NOTIFY_EVENT_RESERVE_BOOSTER, 0); + } + } +#endif + + while (count) { + psy_otg = power_supply_get_by_name("otg"); + if (psy_otg) { + if (max77705_check_boost_off(auth_type, req_pdo_type, d2d_type)) { + val.intval = 0; + /* disable dc reverse boost before otg on */ + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL, val); + } + + val.intval = max77705_check_src_otg_type(enable, auth_type, req_pdo_type, d2d_type); +#if defined(CONFIG_USE_SECOND_MUIC) + muic_hv_charger_disable(enable); +#endif + ret = psy_otg->desc->set_property(psy_otg, POWER_SUPPLY_PROP_ONLINE, &val); + if (ret == -ENODEV) { + pr_err("%s: fail to set power_suppy ONLINE property %d) retry (%d)\n",__func__, ret, count); + count--; + } else { + if (ret) { + pr_err("%s: fail to set power_suppy ONLINE property(%d) \n",__func__, ret); + } else { + pr_info("otg accessory power = %d\n", on); + } + if (max77705_check_boost_enable(auth_type, req_pdo_type, d2d_type)) { + val.intval = enable; /* set dc reverse boost after otg off */ + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL, val); + } + break; + } + } else { + pr_err("%s: fail to get psy battery\n", __func__); + count--; + msleep(200); + } + } +} + +void max77705_pdo_list(struct max77705_usbc_platform_data *usbc_data, unsigned char *data) +{ + struct max77705_pd_data *pd_data = usbc_data->pd_data; + u8 temp = 0x00; + int i; + bool do_power_nego = false; + pd_data->pd_noti.event = PDIC_NOTIFY_EVENT_PD_SINK; + + temp = (data[1] >> 5); + + if (temp > MAX_PDO_NUM) { + pr_info("%s : update available_pdo_num[%d -> %d]", + __func__, temp, MAX_PDO_NUM); + temp = MAX_PDO_NUM; + } + + pd_data->pd_noti.sink_status.available_pdo_num = temp; + pr_info("%s : Temp[0x%02x] Data[0x%02x] available_pdo_num[%d]\n", + __func__, temp, data[1], pd_data->pd_noti.sink_status.available_pdo_num); + + for (i = 0; i < temp; i++) { + u32 pdo_temp; + int max_current = 0, max_voltage = 0; + + pdo_temp = (data[2 + (i * 4)] + | (data[3 + (i * 4)] << 8) + | (data[4 + (i * 4)] << 16) + | (data[5 + (i * 4)] << 24)); + + pr_info("%s : PDO[%d] = 0x%x\n", __func__, i, pdo_temp); + + max_current = (0x3FF & pdo_temp); + max_voltage = (0x3FF & (pdo_temp >> 10)); + + if (!(do_power_nego) && + (pd_data->pd_noti.sink_status.power_list[i + 1].max_current != max_current * UNIT_FOR_CURRENT || + pd_data->pd_noti.sink_status.power_list[i + 1].max_voltage != max_voltage * UNIT_FOR_VOLTAGE)) + do_power_nego = true; + + pd_data->pd_noti.sink_status.power_list[i + 1].max_current = max_current * UNIT_FOR_CURRENT; + pd_data->pd_noti.sink_status.power_list[i + 1].max_voltage = max_voltage * UNIT_FOR_VOLTAGE; + + pr_info("%s : PDO_Num[%d] MAX_CURR(%d) MAX_VOLT(%d), AVAILABLE_PDO_Num(%d)\n", __func__, + i, pd_data->pd_noti.sink_status.power_list[i + 1].max_current, + pd_data->pd_noti.sink_status.power_list[i + 1].max_voltage, + pd_data->pd_noti.sink_status.available_pdo_num); + } + + if (usbc_data->pd_data->pdo_list && do_power_nego) { + pr_info("%s : PDO list is changed, so power negotiation is need\n", + __func__); + pd_data->pd_noti.sink_status.selected_pdo_num = 0; + pd_data->pd_noti.event = PDIC_NOTIFY_EVENT_PD_SINK_CAP; + } + + if (pd_data->pd_noti.sink_status.current_pdo_num != pd_data->pd_noti.sink_status.selected_pdo_num) { + if (pd_data->pd_noti.sink_status.selected_pdo_num == 0) + pr_info("%s : PDO is not selected yet by default\n", __func__); + } + + usbc_data->pd_data->pdo_list = true; + max77705_process_pd(usbc_data); +} + +bool is_accept_pdo(POWER_LIST* pPower_list) +{ + int pdo_type = pPower_list->pdo_type; + int max_volt = pPower_list->max_voltage; + int min_volt = pPower_list->min_voltage; + + if (max_volt < min_volt) + return false; + + if ((pdo_type == FPDO_TYPE) || (pdo_type == VPDO_TYPE)) { + if ((max_volt < DEFAULT_VOLTAGE) || (max_volt > AVAILABLE_VOLTAGE)) + return false; + } + + return true; +} + +void max77705_abnormal_pdo_work(struct work_struct *work) +{ + struct max77705_usbc_platform_data *usbc_data = g_usbc_data; + + pr_info("%s\n", __func__); + //executes the ErroryRecovery. + max77705_set_CCForceError(usbc_data); +} + +void max77705_send_identity_work(struct work_struct *work) +{ + struct max77705_usbc_platform_data *usbc_data = g_usbc_data; + + pr_info("%s\n", __func__); + max77705_vdm_process_set_identity_req(usbc_data); +} + +void max77705_current_pdo(struct max77705_usbc_platform_data *usbc_data, unsigned char *data) +{ + struct max77705_pd_data *pd_data = usbc_data->pd_data; + u8 sel_pdo_pos = 0x00, num_of_pdo = 0x00; + int i, available_pdo_num = 0; + bool do_power_nego = false, is_abnormal_pdo = true; + U_SEC_PDO_OBJECT pdo_obj; + POWER_LIST* pPower_list; + POWER_LIST prev_power_list; + + if (!pd_data->pd_noti.sink_status.available_pdo_num) + do_power_nego = true; + + sel_pdo_pos = ((data[1] >> 3) & 0x07); + pd_data->pd_noti.sink_status.current_pdo_num = sel_pdo_pos; + + num_of_pdo = (data[1] & 0x07); + if (num_of_pdo > MAX_PDO_NUM) { + pr_info("%s : update available_pdo_num[%d -> %d]", + __func__, num_of_pdo, MAX_PDO_NUM); + num_of_pdo = MAX_PDO_NUM; + } + + pd_data->pd_noti.sink_status.has_apdo = false; + + for (i = 0; i < num_of_pdo; ++i) { + pPower_list = &pd_data->pd_noti.sink_status.power_list[available_pdo_num + 1]; + + pdo_obj.data = (data[2 + (i * 4)] + | (data[3 + (i * 4)] << 8) + | (data[4 + (i * 4)] << 16) + | (data[5 + (i * 4)] << 24)); + + if (!do_power_nego) + prev_power_list = pd_data->pd_noti.sink_status.power_list[available_pdo_num + 1]; + + switch (pdo_obj.BITS_supply.type) { + case PDO_TYPE_FIXED: + pPower_list->apdo = false; + pPower_list->pdo_type = FPDO_TYPE; + pPower_list->max_voltage = pdo_obj.BITS_pdo_fixed.voltage * UNIT_FOR_VOLTAGE; + pPower_list->min_voltage = 0; + pPower_list->max_current = pdo_obj.BITS_pdo_fixed.max_current * UNIT_FOR_CURRENT; + pPower_list->comm_capable = pdo_obj.BITS_pdo_fixed.usb_communications_capable; + pPower_list->suspend = pdo_obj.BITS_pdo_fixed.usb_suspend_supported; + available_pdo_num++; + break; + case PDO_TYPE_APDO: + pd_data->pd_noti.sink_status.has_apdo = true; + pPower_list->apdo = true; + pPower_list->pdo_type = APDO_TYPE; + pPower_list->max_voltage = pdo_obj.BITS_pdo_programmable.max_voltage * UNIT_FOR_APDO_VOLTAGE; + pPower_list->min_voltage = pdo_obj.BITS_pdo_programmable.min_voltage * UNIT_FOR_APDO_VOLTAGE; + pPower_list->max_current = pdo_obj.BITS_pdo_programmable.max_current * UNIT_FOR_APDO_CURRENT; + available_pdo_num++; + break; + case PDO_TYPE_VARIABLE: + pPower_list->apdo = false; + pPower_list->pdo_type = VPDO_TYPE; + pPower_list->max_voltage = pdo_obj.BITS_pdo_variable.max_voltage * UNIT_FOR_VOLTAGE; + pPower_list->min_voltage = pdo_obj.BITS_pdo_variable.min_voltage * UNIT_FOR_VOLTAGE; + pPower_list->max_current = pdo_obj.BITS_pdo_variable.max_current * UNIT_FOR_CURRENT; + available_pdo_num++; + break; + default: + break; + } + + if (!(do_power_nego) && + (pPower_list->max_current != prev_power_list.max_current || + pPower_list->max_voltage != prev_power_list.max_voltage || + pPower_list->min_voltage != prev_power_list.min_voltage)) + do_power_nego = true; + } + + + if (!do_power_nego && (pd_data->pd_noti.sink_status.available_pdo_num != available_pdo_num)) + do_power_nego = true; + + pd_data->pd_noti.sink_status.available_pdo_num = available_pdo_num; + pr_info("%s : current_pdo_num(%d), available_pdo_num(%d/%d), comm(%d), suspend(%d)\n", __func__, + pd_data->pd_noti.sink_status.current_pdo_num, + pd_data->pd_noti.sink_status.available_pdo_num, num_of_pdo, + pd_data->pd_noti.sink_status.power_list[sel_pdo_pos].comm_capable, + pd_data->pd_noti.sink_status.power_list[sel_pdo_pos].suspend); + + pd_data->pd_noti.event = PDIC_NOTIFY_EVENT_PD_SINK; + + if (usbc_data->pd_data->pdo_list && do_power_nego) { + pr_info("%s : PDO list is changed, so power negotiation is need\n", __func__); + pd_data->pd_noti.sink_status.selected_pdo_num = 0; + pd_data->pd_noti.event = PDIC_NOTIFY_EVENT_PD_SINK_CAP; + } + + if (pd_data->pd_noti.sink_status.current_pdo_num != pd_data->pd_noti.sink_status.selected_pdo_num) { + if (pd_data->pd_noti.sink_status.selected_pdo_num == 0) + pr_info("%s : PDO is not selected yet by default\n", __func__); + } + + if (do_power_nego || pd_data->pd_noti.sink_status.selected_pdo_num == 0) { + for (i = 0; i < num_of_pdo; ++i) { + pdo_obj.data = (data[2 + (i * 4)] + | (data[3 + (i * 4)] << 8) + | (data[4 + (i * 4)] << 16) + | (data[5 + (i * 4)] << 24)); + pr_info("%s : PDO[%d] = 0x%08X\n", __func__, i + 1, pdo_obj.data); + } + + for (i = 0; i < pd_data->pd_noti.sink_status.available_pdo_num; ++i) { + pPower_list = &pd_data->pd_noti.sink_status.power_list[i + 1]; + pPower_list->accept = is_accept_pdo(pPower_list); + + pr_info("%s : PDO[%d,%s,%s] max_vol(%dmV),min_vol(%dmV),max_cur(%dmA)\n", + __func__, i + 1, + pPower_list->pdo_type ? ((pPower_list->pdo_type == APDO_TYPE) ? "APDO" : "VPDO") : "FIXED", + pPower_list->accept ? "O" : "X", + pPower_list->max_voltage, pPower_list->min_voltage, pPower_list->max_current); + + if (pPower_list->accept) + is_abnormal_pdo = false; + } + } else { + is_abnormal_pdo = false; + } + + usbc_data->pd_data->pdo_list = true; + if (is_abnormal_pdo) { + if (!delayed_work_pending(&usbc_data->pd_data->abnormal_pdo_work)) { + union power_supply_propval val = {0,}; + + for (i = 0; i < num_of_pdo; ++i) { + pdo_obj.data = (data[2 + (i * 4)] + | (data[3 + (i * 4)] << 8) + | (data[4 + (i * 4)] << 16) + | (data[5 + (i * 4)] << 24)); + val.intval = (int)pdo_obj.data; + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_ABNORMAL_SRCCAP, val); + } + queue_delayed_work(usbc_data->pd_data->wqueue, + &usbc_data->pd_data->abnormal_pdo_work, 0); + } + } else { + max77705_process_pd(usbc_data); + } +} + +void max77705_detach_pd(struct max77705_usbc_platform_data *usbc_data) +{ + struct max77705_pd_data *pd_data = usbc_data->pd_data; + + pr_info("%s : Detach PD CHARGER\n", __func__); + + if (pd_data->pd_noti.event != PDIC_NOTIFY_EVENT_DETACH) { + cancel_delayed_work(&usbc_data->pd_data->retry_work); + cancel_delayed_work(&usbc_data->pd_data->abnormal_pdo_work); + cancel_delayed_work(&usbc_data->pd_data->send_identity_work); + if (pd_data->pd_noti.sink_status.available_pdo_num) + memset(pd_data->pd_noti.sink_status.power_list, 0, (sizeof(POWER_LIST) * (MAX_PDO_NUM + 1))); + pd_data->pd_noti.sink_status.rp_currentlvl = RP_CURRENT_LEVEL_NONE; + pd_data->pd_noti.sink_status.selected_pdo_num = 0; + pd_data->pd_noti.sink_status.available_pdo_num = 0; + pd_data->pd_noti.sink_status.current_pdo_num = 0; + pd_data->pd_noti.sink_status.pps_voltage = 0; + pd_data->pd_noti.sink_status.pps_current = 0; + pd_data->pd_noti.sink_status.has_apdo = false; + max77705_set_enable_pps(pd_data->bPPS_on, false, 0, 0); + pd_data->pd_noti.event = PDIC_NOTIFY_EVENT_DETACH; + usbc_data->pd_data->psrdy_received = false; + usbc_data->pd_data->pdo_list = false; + usbc_data->pd_data->cc_sbu_short = false; + pd_data->auth_type = AUTH_NONE; + max77705_ccic_event_work(usbc_data, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_POWER_STATUS, 0/*attach*/, 0, 0); + } +} + +static void max77705_notify_prswap(struct max77705_usbc_platform_data *usbc_data, u8 pd_msg) +{ + struct max77705_pd_data *pd_data = usbc_data->pd_data; + + pr_info("%s : PR SWAP pd_msg [%x]\n", __func__, pd_msg); + + switch(pd_msg) { + case PRSWAP_SNKTOSWAP: + pd_data->pd_noti.event = PDIC_NOTIFY_EVENT_PD_PRSWAP_SNKTOSRC; + pd_data->pd_noti.sink_status.selected_pdo_num = 0; + pd_data->pd_noti.sink_status.available_pdo_num = 0; + pd_data->pd_noti.sink_status.current_pdo_num = 0; + usbc_data->pd_data->psrdy_received = false; + usbc_data->pd_data->pdo_list = false; + usbc_data->pd_data->cc_sbu_short = false; + max77705_ccic_event_work(usbc_data, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_POWER_STATUS, 0/*attach*/, 0, 0); + break; + case PRSWAP_SRCTOSWAP: + pd_data->pd_noti.event = PDIC_NOTIFY_EVENT_PD_PRSWAP_SRCTOSNK; + pd_data->pd_noti.sink_status.selected_pdo_num = 0; + pd_data->pd_noti.sink_status.available_pdo_num = 0; + pd_data->pd_noti.sink_status.current_pdo_num = 0; + usbc_data->pd_data->psrdy_received = false; + usbc_data->pd_data->pdo_list = false; + usbc_data->pd_data->cc_sbu_short = false; + max77705_ccic_event_work(usbc_data, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_POWER_STATUS, 0/*attach*/, 0, 0); + break; + default: + break; + } +} + +void max77705_check_pdo(struct max77705_usbc_platform_data *usbc_data) +{ + usbc_cmd_data value; + + init_usbc_cmd_data(&value); + value.opcode = OPCODE_CURRENT_SRCCAP; + value.write_length = 0x0; + value.read_length = 31; + max77705_usbc_opcode_read(usbc_data, &value); + + pr_info("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d)\n", + __func__, value.opcode, value.write_length, value.read_length); +} + +void max77705_notify_rp_current_level(struct max77705_usbc_platform_data *usbc_data) +{ + struct max77705_pd_data *pd_data = usbc_data->pd_data; + unsigned int rp_currentlvl; + + switch (usbc_data->cc_data->ccistat) { + case CCI_500mA: + rp_currentlvl = RP_CURRENT_LEVEL_DEFAULT; + break; + case CCI_1_5A: + rp_currentlvl = RP_CURRENT_LEVEL2; + break; + case CCI_3_0A: + rp_currentlvl = RP_CURRENT_LEVEL3; + break; + case CCI_SHORT: + rp_currentlvl = RP_CURRENT_ABNORMAL; + break; + default: + rp_currentlvl = RP_CURRENT_LEVEL_NONE; + break; + } + + if (usbc_data->plug_attach_done && !usbc_data->pd_data->psrdy_received && + usbc_data->cc_data->current_pr == SNK && + usbc_data->pd_state == max77705_State_PE_SNK_Wait_for_Capabilities && + pd_data->pdsmg != SRC_CAP_RECEIVED && // fw changes for advertise Rp22k for CtoC + rp_currentlvl != pd_data->pd_noti.sink_status.rp_currentlvl && + rp_currentlvl >= RP_CURRENT_LEVEL_DEFAULT) { + pd_data->pd_noti.sink_status.rp_currentlvl = rp_currentlvl; + pd_data->pd_noti.event = PDIC_NOTIFY_EVENT_PDIC_ATTACH; + pr_info("%s : rp_currentlvl(%d)\n", __func__, pd_data->pd_noti.sink_status.rp_currentlvl); + max77705_ccic_event_work(usbc_data, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_POWER_STATUS, 0/*attach*/, 0, 0); + } +} + +static int max77705_get_chg_info(struct max77705_usbc_platform_data *usbc_data) +{ + usbc_cmd_data value; + POWER_LIST *pPower_list; + + pPower_list = &usbc_data->pd_data->pd_noti.sink_status.power_list[1]; + + if ((usbc_data->pd_data->sent_chg_info) || + (pPower_list->max_current < 2000)) + return 0; + + if (usbc_data->pd_data->sent_chg_info) + return 0; + + init_usbc_cmd_data(&value); + value.opcode = OPCODE_SEND_GET_REQUEST; + value.write_data[0] = OPCODE_GET_SRC_CAP_EXT; + value.write_data[1] = 0; /* */ + value.write_data[2] = 0; /* */ + value.write_length = 3; + value.read_length = 1; /* Result */ + max77705_usbc_opcode_write(g_usbc_data, &value); + + pr_info("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d)\n", + __func__, value.opcode, value.write_length, value.read_length); + + usbc_data->pd_data->sent_chg_info = true; + return 0; +} + +static void clear_chg_info(struct max77705_usbc_platform_data *usbc_data) +{ + SEC_PD_SINK_STATUS *snk_sts = &usbc_data->pd_data->pd_noti.sink_status; + + usbc_data->pd_data->sent_chg_info = false; + snk_sts->pid = 0; + snk_sts->vid = 0; + snk_sts->xid = 0; +} + +static void max77705_pd_check_pdmsg(struct max77705_usbc_platform_data *usbc_data, u8 pd_msg) +{ + struct power_supply *psy_charger; + union power_supply_propval val; + usbc_cmd_data value; + /*int dr_swap, pr_swap, vcon_swap = 0; u8 value[2], rc = 0;*/ + MAX77705_VDM_MSG_IRQ_STATUS_Type VDM_MSG_IRQ_State; +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + int event; +#endif +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + struct otg_notify *o_notify = get_otg_notify(); +#endif +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + struct power_supply *psy; +#endif + + VDM_MSG_IRQ_State.DATA = 0x0; + init_usbc_cmd_data(&value); + msg_maxim(" pd_msg [%x]", pd_msg); + + switch (pd_msg) { + case Nothing_happened: + usbc_data->pd_data->src_cap_done = CC_SNK; + usbc_data->pd_data->req_pdo_type = PDO_TYPE_FIXED; + usbc_data->pd_data->psrdy_sent = false; + clear_chg_info(usbc_data); + break; + case Sink_PD_PSRdy_received: + max77705_get_chg_info(usbc_data); + /* currently, do nothing + * calling max77705_check_pdo() has been moved to max77705_psrdy_irq() + * for specific PD charger issue + */ + break; + case Sink_PD_Error_Recovery: + break; + case Sink_PD_SenderResponseTimer_Timeout: + msg_maxim("Sink_PD_SenderResponseTimer_Timeout received."); + /* queue_work(usbc_data->op_send_queue, &usbc_data->op_send_work); */ + break; + case Source_PD_PSRdy_Sent: + if (usbc_data->mpsm_mode && (usbc_data->pd_pr_swap == cc_SOURCE)) { + max77705_usbc_disable_auto_vbus(usbc_data); + max77705_vbus_turn_on_ctrl(usbc_data, OFF, false); + } + if ((usbc_data->pd_data->src_cap_done == CC_SRC) && + (usbc_data->pd_data->d2d_type != D2D_NONE)) + max77705_check_req_pdo(usbc_data); + usbc_data->pd_data->psrdy_sent = true; + break; + case Source_PD_Error_Recovery: + break; + case Source_PD_SenderResponseTimer_Timeout: + max77705_vbus_turn_on_ctrl(usbc_data, OFF, false); + schedule_delayed_work(&usbc_data->vbus_hard_reset_work, msecs_to_jiffies(800)); + break; + case PD_DR_Swap_Request_Received: + msg_maxim("DR_SWAP received."); +#if IS_ENABLED(CONFIG_USB_HOST_NOTIFY) + send_otg_notify(o_notify, NOTIFY_EVENT_DR_SWAP, 1); +#endif + /* currently, do nothing + * calling max77705_check_pdo() has been moved to max77705_psrdy_irq() + * for specific PD charger issue + */ + break; + case PD_PR_Swap_Request_Received: + msg_maxim("PR_SWAP received."); + break; + case PD_VCONN_Swap_Request_Received: + msg_maxim("VCONN_SWAP received."); + break; + case Received_PD_Message_in_illegal_state: + break; + case Samsung_Accessory_is_attached: + break; + case VDM_Attention_message_Received: + break; + case Sink_PD_Disabled: +#if 0 + /* to do */ + /* AFC HV */ + value[0] = 0x20; + rc = max77705_ccpd_write_command(chip, value, 1); + if (rc > 0) + pr_err("failed to send command\n"); +#endif + break; + case Source_PD_Disabled: + break; + case Prswap_Snktosrc_Sent: + usbc_data->pd_pr_swap = cc_SOURCE; + break; + case Prswap_Srctosnk_Sent: + usbc_data->pd_pr_swap = cc_SINK; + break; + case HARDRESET_RECEIVED: + max77705_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_ALL, PDIC_NOTIFY_ID_CLEAR_INFO, + PDIC_NOTIFY_ID_DEVICE_INFO, 0, 0); + max77705_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_ALL, PDIC_NOTIFY_ID_CLEAR_INFO, + PDIC_NOTIFY_ID_SVID_INFO, 0, 0); + usbc_data->send_enter_mode_req = 0; + /*turn off the vbus both Source and Sink*/ + if (usbc_data->cc_data->current_pr == SRC) { + max77705_vbus_turn_on_ctrl(usbc_data, OFF, false); + schedule_delayed_work(&usbc_data->vbus_hard_reset_work, msecs_to_jiffies(760)); + } else if (usbc_data->cc_data->current_pr == SNK) { + usbc_data->detach_done_wait = 1; +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + psy = power_supply_get_by_name("battery"); + if (psy) { + val.intval = 0; + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_HARDRESET_OCCUR, val); + } else { + pr_err("%s: Fail to get psy battery\n", __func__); + } +#endif + } +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_HARDRESET_RECEIVED; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + cancel_delayed_work(&usbc_data->pd_data->send_identity_work); + break; + case HARDRESET_SENT: + max77705_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_ALL, PDIC_NOTIFY_ID_CLEAR_INFO, + PDIC_NOTIFY_ID_DEVICE_INFO, 0, 0); + max77705_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_ALL, PDIC_NOTIFY_ID_CLEAR_INFO, + PDIC_NOTIFY_ID_SVID_INFO, 0, 0); + usbc_data->send_enter_mode_req = 0; + /*turn off the vbus both Source and Sink*/ + if (usbc_data->cc_data->current_pr == SRC) { + max77705_vbus_turn_on_ctrl(usbc_data, OFF, false); + schedule_delayed_work(&usbc_data->vbus_hard_reset_work, msecs_to_jiffies(760)); + } else if (usbc_data->cc_data->current_pr == SNK) { + usbc_data->detach_done_wait = 1; +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + psy = power_supply_get_by_name("battery"); + if (psy) { + val.intval = 1; + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_HARDRESET_OCCUR, val); + } else { + pr_err("%s: Fail to get psy battery\n", __func__); + } +#endif + } +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_HARDRESET_SENT; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + cancel_delayed_work(&usbc_data->pd_data->send_identity_work); + break; + case Get_Vbus_turn_on: + break; + case Get_Vbus_turn_off: + max77705_vbus_turn_on_ctrl(usbc_data, OFF, false); + break; + case PRSWAP_SRCTOSWAP: + usbc_data->pd_data->req_pdo_type = PDO_TYPE_FIXED; + max77705_notify_prswap(usbc_data, PRSWAP_SRCTOSWAP); + max77705_vbus_turn_on_ctrl(usbc_data, OFF, false); + msg_maxim("PRSWAP_SRCTOSWAP : [%x]", pd_msg); + break; + case PRSWAP_SWAPTOSNK: + max77705_vbus_turn_on_ctrl(usbc_data, OFF, false); + msg_maxim("PRSWAP_SWAPTOSNK : [%x]", pd_msg); + break; + case PRSWAP_SNKTOSWAP: + msg_maxim("PRSWAP_SNKTOSWAP : [%x]", pd_msg); + max77705_notify_prswap(usbc_data, PRSWAP_SNKTOSWAP); + /* CHGINSEL disable */ + psy_charger = power_supply_get_by_name("max77705-charger"); + if (psy_charger) { + val.intval = 0; + psy_do_property("max77705-charger", set, POWER_SUPPLY_EXT_PROP_CHGINSEL, val); + } else { + pr_err("%s: Fail to get psy charger\n", __func__); + } + break; + case PRSWAP_SWAPTOSRC: + if (usbc_data->pd_data->d2d_type == D2D_SRCSNK) + max77705_send_new_src_cap(g_usbc_data, + usbc_data->pd_data->auth_type, usbc_data->pd_data->d2d_type); + max77705_vbus_turn_on_ctrl(usbc_data, ON, false); + msg_maxim("PRSWAP_SNKTOSRC : [%x]", pd_msg); + break; + case Current_Cable_Connected: + max77705_manual_jig_on(usbc_data, 1); + usbc_data->manual_lpm_mode = 1; + msg_maxim("Current_Cable_Connected : [%x]", pd_msg); + break; + case SRC_CAP_RECEIVED: + break; + case Status_Received: + value.opcode = OPCODE_SAMSUNG_READ_MESSAGE; + value.write_data[0] = 0x02; + value.write_length = 1; + value.read_length = 32; + max77705_usbc_opcode_write(usbc_data, &value); + msg_maxim("@TA_ALERT: Status Receviced : [%x]", pd_msg); + break; + case Alert_Message: + value.opcode = OPCODE_SAMSUNG_READ_MESSAGE; + value.write_data[0] = 0x01; + value.write_length = 1; + value.read_length = 32; + max77705_usbc_opcode_write(usbc_data, &value); + msg_maxim("@TA_ALERT: Alert Message : [%x]", pd_msg); + break; + case PDMSG_DP_ENTER_MODE: + /* To check SVID of enter mode */ + value.opcode = OPCODE_SAMSUNG_READ_MESSAGE; + value.write_data[0] = 0x03; + value.write_length = 1; + value.read_length = 32; + max77705_usbc_opcode_write(usbc_data, &value); + msg_maxim("Enter mode Receviced : [%x]", pd_msg); + break; + case PDMSG_SRC_ACCEPT: + if ((usbc_data->pd_data->src_cap_done == CC_SRC) && + (usbc_data->pd_data->d2d_type != D2D_NONE)) + max77705_check_req_pdo(usbc_data); + msg_maxim("SRC ACCEPT : [%x]", pd_msg); + break; + default: + break; + } +} + +void max77705_pd_check_pdmsg_callback(void *data, u8 pdmsg) +{ + struct max77705_usbc_platform_data *usbc_data = data; + union power_supply_propval val; + + if (!usbc_data) { + msg_maxim("usbc_data is null"); + return; + } + + if (!usbc_data->pd_data->psrdy_received && + (pdmsg == Sink_PD_PSRdy_received || pdmsg == SRC_CAP_RECEIVED)) { + msg_maxim("pdmsg=%x", pdmsg); + val.intval = 1; + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_SRCCAP, val); + } else if (usbc_data->pd_data->psrdy_received && (pdmsg == SRC_CAP_RECEIVED)) { + msg_maxim("pdmsg=%x", pdmsg); + val.intval = 0; + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_SRCCAP, val); + } +} + +static void max77705_pd_rid(struct max77705_usbc_platform_data *usbc_data, u8 fct_id) +{ + struct max77705_pd_data *pd_data = usbc_data->pd_data; + + u8 prev_rid = pd_data->device; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + static int rid = RID_OPEN; +#endif + + switch (fct_id) { + case FCT_GND: + msg_maxim(" RID_000K"); + pd_data->device = DEV_FCT_0; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + rid = RID_000K; +#endif + break; + case FCT_1Kohm: + msg_maxim(" RID_001K"); + pd_data->device = DEV_FCT_1K; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + rid = RID_001K; +#endif + break; + case FCT_255Kohm: + msg_maxim(" RID_255K"); + pd_data->device = DEV_FCT_255K; + usbc_data->rid_check = true; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + rid = RID_255K; +#endif + break; + case FCT_301Kohm: + msg_maxim(" RID_301K"); + pd_data->device = DEV_FCT_301K; + usbc_data->rid_check = true; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + rid = RID_301K; +#endif + break; + case FCT_523Kohm: + msg_maxim(" RID_523K"); + pd_data->device = DEV_FCT_523K; + usbc_data->rid_check = true; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + rid = RID_523K; +#endif + break; + case FCT_619Kohm: + msg_maxim(" RID_619K"); + pd_data->device = DEV_FCT_619K; + usbc_data->rid_check = true; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + rid = RID_619K; +#endif + break; + case FCT_OPEN: + msg_maxim(" RID_OPEN"); + pd_data->device = DEV_FCT_OPEN; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + rid = RID_OPEN; +#endif + break; + default: + msg_maxim(" RID_UNDEFINED"); + pd_data->device = DEV_UNKNOWN; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + rid = RID_UNDEFINED; +#endif + break; + } + + if (prev_rid != pd_data->device) { +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + /* RID */ + max77705_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_MUIC, PDIC_NOTIFY_ID_RID, + rid, 0, 0); + usbc_data->cur_rid = rid; + /* turn off USB */ + if (pd_data->device == DEV_FCT_OPEN || pd_data->device == DEV_UNKNOWN + || pd_data->device == DEV_FCT_523K || pd_data->device == DEV_FCT_619K) { + + usbc_data->typec_power_role = TYPEC_SINK; + + /* usb or otg */ + max77705_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_USB, PDIC_NOTIFY_ID_USB, + 0/*attach*/, USB_STATUS_NOTIFY_DETACH/*drp*/, 0); + } +#endif + } +} + +static irqreturn_t max77705_pdmsg_irq(int irq, void *data) +{ + struct max77705_usbc_platform_data *usbc_data = data; + struct max77705_pd_data *pd_data = usbc_data->pd_data; + u8 pdmsg = 0; + + max77705_read_reg(usbc_data->muic, REG_PD_STATUS0, &pd_data->pd_status0); + pdmsg = pd_data->pd_status0; + msg_maxim("IRQ(%d)_IN pdmsg: %02x", irq, pdmsg); + max77705_pd_check_pdmsg(usbc_data, pdmsg); + pd_data->pdsmg = pdmsg; + msg_maxim("IRQ(%d)_OUT", irq); + + return IRQ_HANDLED; +} + +static irqreturn_t max77705_psrdy_irq(int irq, void *data) +{ + struct max77705_usbc_platform_data *usbc_data = data; + u8 psrdy_received = 0; + enum typec_pwr_opmode mode = TYPEC_PWR_MODE_USB; +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + struct otg_notify *o_notify = get_otg_notify(); +#endif + + msg_maxim("IN"); + max77705_read_reg(usbc_data->muic, REG_PD_STATUS1, &usbc_data->pd_status1); + psrdy_received = (usbc_data->pd_status1 & BIT_PD_PSRDY) + >> FFS(BIT_PD_PSRDY); + + if (psrdy_received && !usbc_data->pd_support + && usbc_data->pd_data->cc_status != CC_NO_CONN) + usbc_data->pd_support = true; + + if (usbc_data->typec_try_state_change == TRY_ROLE_SWAP_PR && + usbc_data->pd_support) { + msg_maxim("typec_reverse_completion"); + usbc_data->typec_try_state_change = TRY_ROLE_SWAP_NONE; + complete(&usbc_data->typec_reverse_completion); + } + msg_maxim("psrdy_received=%d, usbc_data->pd_support=%d, cc_status=%d, src_cp_dn=%d", + psrdy_received, usbc_data->pd_support, usbc_data->pd_data->cc_status, + usbc_data->pd_data->src_cap_done); + + mode = max77705_get_pd_support(usbc_data); + typec_set_pwr_opmode(usbc_data->port, mode); +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + if (mode == TYPEC_PWR_MODE_PD) + send_otg_notify(o_notify, NOTIFY_EVENT_PD_CONTRACT, 1); + else + send_otg_notify(o_notify, NOTIFY_EVENT_PD_CONTRACT, 0); +#endif + + if (usbc_data->pd_data->cc_status == CC_SNK && psrdy_received) { + max77705_check_pdo(usbc_data); + usbc_data->pd_data->psrdy_received = true; + usbc_data->pd_data->src_cap_done = CC_SNK; + } + + if (psrdy_received && usbc_data->pd_data->cc_status != CC_NO_CONN) { + if (usbc_data->pd_data->cc_status == CC_SRC) { + if (usbc_data->pd_data->src_cap_done != CC_SRC) { + cancel_delayed_work(&usbc_data->pd_data->d2d_work); + /* send the PD message after 1000ms. */ + queue_delayed_work(usbc_data->pd_data->wqueue, + &usbc_data->pd_data->d2d_work, msecs_to_jiffies(1000)); + } + } + usbc_data->pn_flag = true; + complete(&usbc_data->psrdy_wait); + } + + msg_maxim("OUT"); + return IRQ_HANDLED; +} + +bool max77705_sec_pps_control(int en) +{ + struct max77705_usbc_platform_data *pusbpd = g_usbc_data; + union power_supply_propval val = {0,}; + + msg_maxim(": %d", en); + + val.intval = en; /* 0: stop pps, 1: start pps */ + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_DIRECT_SEND_UVDM, val); + if (!en && !pusbpd->pn_flag) { + reinit_completion(&pusbpd->psrdy_wait); + if (!wait_for_completion_timeout(&pusbpd->psrdy_wait, msecs_to_jiffies(1000))) { + msg_maxim("PSRDY COMPLETION TIMEOUT"); + return false; + } + } + return true; +} + +static void max77705_datarole_irq_handler(void *data, int irq) +{ + struct max77705_usbc_platform_data *usbc_data = data; + struct max77705_pd_data *pd_data = usbc_data->pd_data; + u8 datarole = 0; + + max77705_read_reg(usbc_data->muic, REG_PD_STATUS1, &pd_data->pd_status1); + datarole = (pd_data->pd_status1 & BIT_PD_DataRole) + >> FFS(BIT_PD_DataRole); + /* abnormal data role without setting power role */ + if (usbc_data->cc_data->current_pr == 0xFF) { + msg_maxim("INVALID IRQ IRQ(%d)_OUT", irq); + return; + } + + if (irq == CCIC_IRQ_INIT_DETECT) { + if (usbc_data->pd_data->cc_status == CC_SNK) + msg_maxim("initial time : SNK"); + else + return; + } + + switch (datarole) { + case UFP: + if (pd_data->current_dr != UFP) { + pd_data->previous_dr = pd_data->current_dr; + pd_data->current_dr = UFP; + if (pd_data->previous_dr != 0xFF) + msg_maxim("%s detach previous usb connection\n", __func__); + max77705_notify_dr_status(usbc_data, 1); + if (usbc_data->typec_try_state_change == TRY_ROLE_SWAP_DR || + usbc_data->typec_try_state_change == TRY_ROLE_SWAP_TYPE) { + msg_maxim("typec_reverse_completion"); + usbc_data->typec_try_state_change = TRY_ROLE_SWAP_NONE; + complete(&usbc_data->typec_reverse_completion); + } + } + msg_maxim(" UFP"); + break; + + case DFP: + if (pd_data->current_dr != DFP) { + pd_data->previous_dr = pd_data->current_dr; + pd_data->current_dr = DFP; + if (pd_data->previous_dr != 0xFF) + msg_maxim("%s detach previous usb connection\n", __func__); + + max77705_notify_dr_status(usbc_data, 1); + if (usbc_data->typec_try_state_change == TRY_ROLE_SWAP_DR || + usbc_data->typec_try_state_change == TRY_ROLE_SWAP_TYPE) { + msg_maxim("typec_reverse_completion"); + usbc_data->typec_try_state_change = TRY_ROLE_SWAP_NONE; + complete(&usbc_data->typec_reverse_completion); + } + + if (usbc_data->cc_data->current_pr == SNK && !(usbc_data->is_first_booting)) { + cancel_delayed_work(&pd_data->send_identity_work); + queue_delayed_work(pd_data->wqueue, + &pd_data->send_identity_work, + msecs_to_jiffies(300)); + msg_maxim("SEND THE IDENTITY REQUEST FROM DFP HANDLER after 300ms"); + } + } + msg_maxim(" DFP"); + break; + + default: + msg_maxim(" DATAROLE(Never Call this routine)"); + break; + } +} + +static irqreturn_t max77705_datarole_irq(int irq, void *data) +{ + pr_debug("%s: IRQ(%d)_IN\n", __func__, irq); + max77705_datarole_irq_handler(data, irq); + pr_debug("%s: IRQ(%d)_OUT\n", __func__, irq); + return IRQ_HANDLED; +} + +static irqreturn_t max77705_ssacc_irq(int irq, void *data) +{ + struct max77705_usbc_platform_data *usbc_data = data; + struct max77705_pd_data *pd_data = usbc_data->pd_data; + + pr_debug("%s: IRQ(%d)_IN\n", __func__, irq); + msg_maxim(" SSAcc command received"); + /* Read through Opcode command 0x50 */ + pd_data->ssacc = 1; + pr_debug("%s: IRQ(%d)_OUT\n", __func__, irq); + return IRQ_HANDLED; +} + +static void max77705_check_cc_sbu_short(void *data) +{ + u8 cc_status1 = 0; + + struct max77705_usbc_platform_data *usbc_data = data; + struct max77705_pd_data *pd_data = usbc_data->pd_data; + + max77705_read_reg(usbc_data->muic, REG_CC_STATUS1, &cc_status1); + /* 0b01: CC-5V, 0b10: SBU-5V, 0b11: SBU-GND Short */ + cc_status1 = (cc_status1 & BIT_CCSBUSHORT) >> FFS(BIT_CCSBUSHORT); + if (cc_status1) + pd_data->cc_sbu_short = true; + + msg_maxim("%s cc_status1 : %x, cc_sbu_short : %d\n", __func__, cc_status1, pd_data->cc_sbu_short); +} + +static u8 max77705_check_rid(void *data) +{ + u8 fct_id = 0; + struct max77705_usbc_platform_data *usbc_data = data; + struct max77705_pd_data *pd_data = usbc_data->pd_data; + + max77705_read_reg(usbc_data->muic, REG_PD_STATUS1, &pd_data->pd_status1); + fct_id = (pd_data->pd_status1 & BIT_FCT_ID) >> FFS(BIT_FCT_ID); +#if defined(CONFIG_SEC_FACTORY) + factory_execute_monitor(FAC_ABNORMAL_REPEAT_RID); +#endif + max77705_pd_rid(usbc_data, fct_id); + pd_data->fct_id = fct_id; + msg_maxim("%s rid : %d, fct_id : %d\n", __func__, usbc_data->cur_rid, fct_id); + return fct_id; +} + +static irqreturn_t max77705_fctid_irq(int irq, void *data) +{ + pr_debug("%s: IRQ(%d)_IN\n", __func__, irq); + max77705_check_rid(data); + pr_debug("%s: IRQ(%d)_OUT\n", __func__, irq); + return IRQ_HANDLED; +} + +void set_src_pdo_data(usbc_cmd_data *value, + int pdo_n, int p_type, int curr) +{ + value->opcode = OPCODE_SET_SRCCAP; + value->write_data[1 + (pdo_n * 4)] = (u8)(curr / 10); + + if (p_type == VPDO_TYPE) { + /* 7~9v vpdo */ + value->write_data[2 + (pdo_n * 4)] = 0x30; + value->write_data[3 + (pdo_n * 4)] = 0x42; + value->write_data[4 + (pdo_n * 4)] = 0x8B; + } else { + /* 5v fpdo */ + value->write_data[2 + (pdo_n * 4)] = 0x90; + value->write_data[3 + (pdo_n * 4)] = 0x01; + value->write_data[4 + (pdo_n * 4)] = 0x36; + } +} + +void set_varible_pdo_data(usbc_cmd_data *value, int auth_t, int d2d_t) +{ + value->opcode = OPCODE_SET_SRCCAP; + if ((d2d_t == D2D_SRCSNK) && + (auth_t == AUTH_HIGH_PWR)) { + value->write_data[0] = 0x2; + // 0x36019032, //5V, 500mA + // 0x36019064, //5V, 1 A + // 0x3602D0C8, //9V, 2 A + set_src_pdo_data(value, 0, FPDO_TYPE, 500); + // 0x8B4230AA, //Variable :7V~9V Max15W + set_src_pdo_data(value, 1, VPDO_TYPE, 1650); + value->write_length = 12; + value->read_length = 1; + } else if ((d2d_t == D2D_SNKONLY) && + (auth_t == AUTH_HIGH_PWR)) { + value->write_data[0] = 0x1; + // 0x36019096, //5V, 1.5A + set_src_pdo_data(value, 0, FPDO_TYPE, 1500); + value->write_length = 7; + value->read_length = 1; + } else { + value->write_data[0] = 0x1; + // 0x36019032, //5V, 500mA + set_src_pdo_data(value, 0, FPDO_TYPE, 500); + value->write_length = 7; + value->read_length = 1; + } +} + +void max77705_set_fpdo_srccap(usbc_cmd_data *value, int max_cur) +{ + value->write_data[0] = 0x1; + set_src_pdo_data(value, 0, FPDO_TYPE, max_cur); + value->write_length = 7; + value->read_length = 1; +} + +void max77705_forced_change_srccap(int max_cur) +{ + usbc_cmd_data value; + + init_usbc_cmd_data(&value); + + max77705_set_fpdo_srccap(&value, max_cur); + max77705_usbc_opcode_write(g_usbc_data, &value); + + pr_info("%s : write => OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d)\n", + __func__, value.opcode, value.write_length, value.read_length); +} + +static void max77705_send_new_src_cap(struct max77705_usbc_platform_data *pusbpd, + int auth, int d2d_type) +{ + usbc_cmd_data value; + init_usbc_cmd_data(&value); + set_varible_pdo_data(&value, auth, d2d_type); + max77705_usbc_opcode_write(pusbpd, &value); + + pr_info("%s : write => OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d)\n", + __func__, value.opcode, value.write_length, value.read_length); +} + +void max77705_send_new_src_cap_push(struct max77705_usbc_platform_data *pusbpd, + int auth, int d2d_type) +{ + usbc_cmd_data value; + init_usbc_cmd_data(&value); + set_varible_pdo_data(&value, auth, d2d_type); + max77705_usbc_opcode_push(pusbpd, &value); + + pr_info("%s : push => OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d)\n", + __func__, value.opcode, value.write_length, value.read_length); +} + +static void max77705_send_srcap_work(struct work_struct *work) +{ + struct max77705_pd_data *pd_data = g_usbc_data->pd_data; + int auth = pd_data->auth_type; + int d2d_type = pd_data->d2d_type; + + if ((pd_data->src_cap_done != CC_SRC) && + (auth == AUTH_HIGH_PWR && d2d_type != D2D_NONE)) { + max77705_send_new_src_cap(g_usbc_data, auth, d2d_type); + pd_data->src_cap_done = CC_SRC; + pr_info("%s\n", __func__); + } else { + pr_info("%s Donot Send the new SRC_CAP\n", __func__); + } +} + +void max77705_vpdo_auth(int auth, int d2d_type) +{ + struct max77705_pd_data *pd_data = g_usbc_data->pd_data; + + if (d2d_type == D2D_NONE) + return; + + if (pd_data->cc_status == CC_SRC) { + if (((pd_data->auth_type == AUTH_HIGH_PWR) && (auth == AUTH_LOW_PWR)) || + ((pd_data->auth_type == AUTH_LOW_PWR) && (auth == AUTH_HIGH_PWR))) { + max77705_send_new_src_cap(g_usbc_data, auth, d2d_type); + pd_data->src_cap_done = CC_SRC; + pr_info("%s: change src %s -> %s\n", __func__, + (auth == AUTH_LOW_PWR) ? "HIGH PWR" : "LOW PWR", + (auth == AUTH_LOW_PWR) ? "LOW PWR" : "HIGH PWR"); + } + } else if ((pd_data->cc_status == CC_SNK) && + (auth == AUTH_HIGH_PWR)) { + pr_info("%s: preset vpdo auth for prswap snk to src\n", __func__); + } + + /* set default src cap for detach or hard reset case */ + if (pd_data->cc_status != CC_SNK) { + if ((pd_data->auth_type == AUTH_HIGH_PWR) && (auth == AUTH_NONE)) { + max77705_send_new_src_cap(g_usbc_data, auth, d2d_type); + pr_info("%s: set to default src cap\n", __func__); + } + } + + pr_info("%s: vpdo auth set (%d, %d)\n", __func__, auth, d2d_type); + pd_data->auth_type = auth; + pd_data->d2d_type = d2d_type; +} + +static void max77705_check_enter_mode(void *data) +{ + u8 pd_status1 = 0, enter_mode = 0; + int ret; + usbc_cmd_data value; + struct max77705_usbc_platform_data *usbc_data = data; + + init_usbc_cmd_data(&value); + + ret = max77705_read_reg(usbc_data->muic, REG_PD_STATUS1, &pd_status1); + + if (ret) { + pr_err("%s fail to read REG_PD_STATUS1 reg\n", __func__); + return; + } + + /* 0b01: Enter Mode : 0b00 : Not Enter mode */ + enter_mode = (pd_status1 & BIT_PD_ENTER_MODE) >> FFS(BIT_PD_ENTER_MODE); + if (enter_mode) { + value.opcode = OPCODE_SAMSUNG_READ_MESSAGE; + value.write_data[0] = 0x03; + value.write_length = 1; + value.read_length = 32; + max77705_usbc_opcode_write(usbc_data, &value); + } + msg_maxim("%s pd_status1 : %x, enter_mode : %d\n", __func__, pd_status1, enter_mode); +} + +int max77705_pd_init(struct max77705_usbc_platform_data *usbc_data) +{ + struct max77705_pd_data *pd_data = usbc_data->pd_data; + int ret = 0; + + msg_maxim(" IN(%d)", pd_data->pd_noti.sink_status.rp_currentlvl); + + /* skip below codes for detecting incomplete connection cable. */ + /* pd_data->pd_noti.sink_status.rp_currentlvl = RP_CURRENT_LEVEL_NONE; */ + pd_data->pd_noti.sink_status.available_pdo_num = 0; + pd_data->pd_noti.sink_status.selected_pdo_num = 0; + pd_data->pd_noti.sink_status.current_pdo_num = 0; + pd_data->pd_noti.sink_status.pps_voltage = 0; + pd_data->pd_noti.sink_status.pps_current = 0; + pd_data->pd_noti.sink_status.has_apdo = false; + pd_data->pd_noti.sink_status.fp_sec_pd_select_pdo = max77705_select_pdo; + pd_data->pd_noti.sink_status.fp_sec_pd_select_pps = max77705_select_pps; + pd_data->pd_noti.sink_status.fp_sec_pd_vpdo_auth = max77705_vpdo_auth; + pd_data->pd_noti.sink_status.fp_sec_pd_manual_ccopen_req = pdic_manual_ccopen_request; + pd_data->pd_noti.sink_status.fp_sec_pd_change_src = max77705_forced_change_srccap; + + /* skip below codes for detecting incomplete connection cable. */ + /* pd_data->pd_noti.event = PDIC_NOTIFY_EVENT_DETACH; */ + pd_data->pdo_list = false; + pd_data->psrdy_received = false; + pd_data->cc_sbu_short = false; + + pd_data->wqueue = create_singlethread_workqueue("max77705_pd"); + if (!pd_data->wqueue) { + pr_err("%s: Fail to Create Workqueue\n", __func__); + goto err_irq; + } + + INIT_DELAYED_WORK(&pd_data->d2d_work, max77705_send_srcap_work); + INIT_DELAYED_WORK(&pd_data->retry_work, max77705_pd_retry_work); + INIT_DELAYED_WORK(&pd_data->abnormal_pdo_work, max77705_abnormal_pdo_work); + INIT_DELAYED_WORK(&pd_data->send_identity_work, max77705_send_identity_work); + + pd_data->irq_pdmsg = usbc_data->irq_base + MAX77705_PD_IRQ_PDMSG_INT; + if (pd_data->irq_pdmsg) { + ret = request_threaded_irq(pd_data->irq_pdmsg, + NULL, max77705_pdmsg_irq, + 0, + "pd-pdmsg-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + goto err_irq; + } + } + + pd_data->irq_psrdy = usbc_data->irq_base + MAX77705_PD_IRQ_PS_RDY_INT; + if (pd_data->irq_psrdy) { + ret = request_threaded_irq(pd_data->irq_psrdy, + NULL, max77705_psrdy_irq, + 0, + "pd-psrdy-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + goto err_irq; + } + } + + pd_data->irq_datarole = usbc_data->irq_base + MAX77705_PD_IRQ_DATAROLE_INT; + if (pd_data->irq_datarole) { + ret = request_threaded_irq(pd_data->irq_datarole, + NULL, max77705_datarole_irq, + 0, + "pd-datarole-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + goto err_irq; + } + } + + pd_data->irq_ssacc = usbc_data->irq_base + MAX77705_PD_IRQ_SSACCI_INT; + if (pd_data->irq_ssacc) { + ret = request_threaded_irq(pd_data->irq_ssacc, + NULL, max77705_ssacc_irq, + 0, + "pd-ssacci-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + goto err_irq; + } + } + pd_data->irq_fct_id = usbc_data->irq_base + MAX77705_PD_IRQ_FCTIDI_INT; + if (pd_data->irq_fct_id) { + ret = request_threaded_irq(pd_data->irq_fct_id, + NULL, max77705_fctid_irq, + 0, + "pd-fctid-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + goto err_irq; + } + } + /* check RID value for booting time */ + max77705_check_rid(usbc_data); + max77705_set_fw_noautoibus(MAX77705_AUTOIBUS_AT_OFF); + max77705_set_snkcap(usbc_data); + /* check CC Pin state for cable attach booting scenario */ + max77705_datarole_irq_handler(usbc_data, CCIC_IRQ_INIT_DETECT); + max77705_check_cc_sbu_short(usbc_data); + + max77705_check_enter_mode(usbc_data); + max77705_register_pdmsg_func(usbc_data->max77705, + max77705_pd_check_pdmsg_callback, (void *)usbc_data); + + msg_maxim(" OUT(%d)", pd_data->pd_noti.sink_status.rp_currentlvl); + return 0; + +err_irq: + kfree(pd_data); + return ret; +} diff --git a/drivers/usb/typec/maxim/max77705_usbc.c b/drivers/usb/typec/maxim/max77705_usbc.c new file mode 100644 index 000000000000..bfffbed50e57 --- /dev/null +++ b/drivers/usb/typec/maxim/max77705_usbc.c @@ -0,0 +1,4209 @@ +/* + * Copyrights (C) 2017 Samsung Electronics, Inc. + * Copyrights (C) 2017 Maxim Integrated Products, 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. + */ + +#include +#include +#include +#ifdef CONFIG_OF +#include +#endif +#include +#include +#include +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) +#include +#endif +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) +#include +#include +#include +#endif +#include +#include +#include +#include +#if defined(CONFIG_CCIC_MAX77705_DEBUG) +#include +#endif +#include +#include +#ifdef MAX77705_SYS_FW_UPDATE +#include +#if IS_ENABLED(CONFIG_SPU_VERIFY) +#include +#endif +#endif +#include + +static enum pdic_sysfs_property max77705_sysfs_properties[] = { + PDIC_SYSFS_PROP_CHIP_NAME, + PDIC_SYSFS_PROP_CUR_VERSION, + PDIC_SYSFS_PROP_SRC_VERSION, + PDIC_SYSFS_PROP_LPM_MODE, + PDIC_SYSFS_PROP_STATE, + PDIC_SYSFS_PROP_RID, + PDIC_SYSFS_PROP_CTRL_OPTION, + PDIC_SYSFS_PROP_BOOTING_DRY, + PDIC_SYSFS_PROP_FW_UPDATE, + PDIC_SYSFS_PROP_FW_UPDATE_STATUS, + PDIC_SYSFS_PROP_FW_WATER, + PDIC_SYSFS_PROP_DEX_FAN_UVDM, + PDIC_SYSFS_PROP_ACC_DEVICE_VERSION, + PDIC_SYSFS_PROP_DEBUG_OPCODE, + PDIC_SYSFS_PROP_CONTROL_GPIO, + PDIC_SYSFS_PROP_USBPD_IDS, + PDIC_SYSFS_PROP_USBPD_TYPE, + PDIC_SYSFS_PROP_CC_PIN_STATUS, + PDIC_SYSFS_PROP_RAM_TEST, + PDIC_SYSFS_PROP_SBU_ADC, + PDIC_SYSFS_PROP_CC_ADC, + PDIC_SYSFS_PROP_VSAFE0V_STATUS, + PDIC_SYSFS_PROP_OVP_IC_SHUTDOWN, + PDIC_SYSFS_PROP_HMD_POWER, +#if defined(CONFIG_SEC_FACTORY) + PDIC_SYSFS_PROP_15MODE_WATERTEST_TYPE, +#endif + PDIC_SYSFS_PROP_MAX_COUNT, +}; +#define DRIVER_VER "1.2VER" + +#define MAX77705_MAX_APDCMD_TIME (10*HZ) + +#define MAX77705_PMIC_REG_INTSRC_MASK 0x23 +#define MAX77705_PMIC_REG_INTSRC 0x22 + +#define MAX77705_IRQSRC_CHG (1 << 0) +#define MAX77705_IRQSRC_FG (1 << 2) +#define MAX77705_IRQSRC_MUIC (1 << 3) + +#define MAX77705_RAM_TEST + +#ifdef MAX77705_RAM_TEST +#define MAX77705_RAM_TEST_RETRY_COUNT 1 +#define MAX77705_RAM_TEST_SUCCESS 0xA1 +#define MAX77705_RAM_TEST_FAIL 0x51 + +enum MAX77705_RAM_TEST_MODE { + MAX77705_RAM_TEST_STOP_MODE, + MAX77705_RAM_TEST_START_MODE, + MAX77705_RAM_TEST_RETRY_MODE, +}; + +enum MAX77705_RAM_TEST_RESULT { + MAX77705_RAM_TEST_RESULT_SUCCESS, + MAX77705_RAM_TEST_RESULT_FAIL_USBC_FUELGAUAGE, + MAX77705_RAM_TEST_RESULT_FAIL_USBC, + MAX77705_RAM_TEST_RESULT_FAIL_FUELGAUAGE, +}; +#endif + +struct max77705_usbc_platform_data *g_usbc_data; + +#ifdef MAX77705_SYS_FW_UPDATE +#define MAXIM_DEFAULT_FW "secure_max77705.bin" +#define MAXIM_SPU_FW "/pdic/pdic_fw.bin" + +struct pdic_fw_update { + char id[10]; + char path[50]; + int need_verfy; + int enforce_do; +}; +#endif + +static void max77705_usbc_mask_irq(struct max77705_usbc_platform_data *usbc_data); +static void max77705_usbc_umask_irq(struct max77705_usbc_platform_data *usbc_data); +static void max77705_get_version_info(struct max77705_usbc_platform_data *usbc_data); + +#ifdef CONFIG_MAX77705_GRL_ENABLE +static int max77705_i2c_master_write(struct max77705_usbc_platform_data *usbpd_data, + int slave_addr, u8 *reg_addr) +{ + int err; + int tries = 0; + u8 buffer[2] = { reg_addr[0], reg_addr[1]}; + + struct i2c_msg msgs[] = { + { + .addr = slave_addr, + .flags = usbpd_data->muic->flags & I2C_M_TEN, + .len = 2, + .buf = buffer, + }, + }; + + do { + err = i2c_transfer(usbpd_data->muic->adapter, msgs, 1); + if (err < 0) + msg_maxim("i2c_transfer error:%d, addr : %x ,data : %x\n", err, reg_addr[0], reg_addr[1]); + } while ((err != 1) && (++tries < 20)); + + if (err != 1) { + msg_maxim("write transfer error:%d, addr : %x ,data : %x\n", err, reg_addr[0], reg_addr[1]); + err = -EIO; + return err; + } + + return 1; +} +#endif + +#ifdef MAX77705_RAM_TEST +static void max77705_verify_ram_bist_write(struct max77705_usbc_platform_data *usbc_data) +{ + usbc_cmd_data write_data; + u8 irq_reg[MAX77705_IRQ_GROUP_NR] = {0}; + write_data.opcode = OPCODE_RAM_TEST_COMMAND; + write_data.write_data[0] = 0x0; + write_data.write_length = 0x1; + write_data.read_length = 0x6; + write_data.is_uvdm = 0x0; + /* clear all interrpts */ + max77705_bulk_read(usbc_data->muic, MAX77705_USBC_REG_UIC_INT, + 4, &irq_reg[USBC_INT]); + msg_maxim("[MAX77705] irq_reg, %x, %x, %x, %x", irq_reg[USBC_INT], irq_reg[CC_INT], irq_reg[PD_INT], irq_reg[VDM_INT]); + max77705_write_reg(usbc_data->muic, REG_UIC_INT_M, 0x3F); + max77705_write_reg(usbc_data->muic, REG_CC_INT_M, 0xFF); + max77705_write_reg(usbc_data->muic, REG_PD_INT_M, 0xFF); + max77705_write_reg(usbc_data->muic, REG_VDM_INT_M, 0xFF); + + max77705_usbc_opcode_write(usbc_data, &write_data); + if(usbc_data->ram_test_enable == MAX77705_RAM_TEST_STOP_MODE) { + usbc_data->ram_test_enable = MAX77705_RAM_TEST_START_MODE; + usbc_data->ram_test_retry = 0x0; + } +} +#endif + +int max77705_current_pr_state(struct max77705_usbc_platform_data *usbc_data) +{ + int current_pr = usbc_data->cc_data->current_pr; + return current_pr; + +} + +void blocking_auto_vbus_control(int enable) +{ + int current_pr = 0; + + msg_maxim("disable : %d", enable); + + if (enable) { + current_pr = max77705_current_pr_state(g_usbc_data); + switch (current_pr) { + case SRC: + /* turn off the vbus */ + max77705_vbus_turn_on_ctrl(g_usbc_data, OFF, false); + break; + default: + break; + } + g_usbc_data->mpsm_mode = MPSM_ON; + } else { + current_pr = max77705_current_pr_state(g_usbc_data); + switch (current_pr) { + case SRC: + max77705_vbus_turn_on_ctrl(g_usbc_data, ON, false); + break; + default: + break; + + } + g_usbc_data->mpsm_mode = MPSM_OFF; + } + msg_maxim("current_pr : %x disable : %x", current_pr, enable); +} +EXPORT_SYMBOL(blocking_auto_vbus_control); + +static void vbus_control_hard_reset(struct work_struct *work) +{ + struct max77705_usbc_platform_data *usbpd_data = g_usbc_data; + + msg_maxim("current_pr=%d", usbpd_data->cc_data->current_pr); + + if (usbpd_data->cc_data->current_pr == SRC) + max77705_vbus_turn_on_ctrl(usbpd_data, ON, false); +} + +void max77705_usbc_enable_auto_vbus(struct max77705_usbc_platform_data *usbc_data) +{ + usbc_cmd_data write_data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_SAMSUNG_FACTORY_TEST; + write_data.write_data[0] = 0x2; + write_data.write_length = 0x1; + write_data.read_length = 0x1; + max77705_usbc_opcode_write(usbc_data, &write_data); + msg_maxim("TURN ON THE AUTO VBUS"); + usbc_data->auto_vbus_en = true; +} + +void max77705_usbc_disable_auto_vbus(struct max77705_usbc_platform_data *usbc_data) +{ + usbc_cmd_data write_data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_SAMSUNG_FACTORY_TEST; + write_data.write_data[0] = 0x0; + write_data.write_length = 0x1; + write_data.read_length = 0x1; + max77705_usbc_opcode_write(usbc_data, &write_data); + msg_maxim("TURN OFF THE AUTO VBUS"); + usbc_data->auto_vbus_en = false; +} + +void max77705_usbc_enable_audio(struct max77705_usbc_platform_data *usbc_data) +{ + usbc_cmd_data write_data; + + /* we need new function for BIT_CCDbgEn */ + usbc_data->op_ctrl1_w |= (BIT_CCDbgEn | BIT_CCAudEn); + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_CCCTRL1_W; + write_data.write_data[0] = usbc_data->op_ctrl1_w; + write_data.write_length = 0x1; + write_data.read_length = 0x1; + max77705_usbc_opcode_write(usbc_data, &write_data); + msg_maxim("Enable Audio Detect"); +} + +static void max77705_usbc_debug_function(struct max77705_usbc_platform_data *usbc_data) +{ + usbc_cmd_data write_data; + + msg_maxim("called"); + + init_usbc_cmd_data(&write_data); + write_data.opcode = 0x74; + write_data.write_data[0] = 0x0; + write_data.write_length = 0x1; + write_data.read_length = 0xA; + max77705_usbc_opcode_write(usbc_data, &write_data); +} + +static int max77705_usbc_gpio5_direction_output( + struct max77705_usbc_platform_data *usbc_data, int value) +{ + int i = 0; + usbc_cmd_data write_data; + + usbc_data->ovp_gpio = 0xf; /* invalid value */ + + reinit_completion(&usbc_data->ccic_sysfs_completion); + + msg_maxim("gpio5: %s", value ? "High" : "Low"); + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_SAMSUNG_GPIO5_CONTROL; + write_data.write_length = 0x2; + write_data.write_data[0] = 0x1; /* output */ + write_data.write_data[1] = !!value; + write_data.read_length = 0x2; + max77705_usbc_opcode_write(usbc_data, &write_data); + + i = wait_for_completion_timeout(&usbc_data->ccic_sysfs_completion, + msecs_to_jiffies(1000 * 5)); + if (i == 0) + msg_maxim("CCIC SYSFS COMPLETION TIMEOUT"); + + if (usbc_data->ovp_gpio != !!value) { + msg_maxim("Value is different"); + return -1; + } + + msg_maxim("gpio5: %s, done", usbc_data->ovp_gpio ? "High" : "Low"); + + return 0; +} + +#if !IS_ENABLED(CONFIG_SEC_FACTORY) +static void max77705_usbc_disable_uiden(struct max77705_usbc_platform_data *usbc_data) +{ + usbc_cmd_data read_data; + + msg_maxim("%s\n", __func__); + + init_usbc_cmd_data(&read_data); + read_data.opcode = COMMAND_BC_CTRL1_READ; + read_data.mask = BC_CTRL1_UIDEN_MASK; + read_data.val = 0x0; + + max77705_usbc_opcode_update(usbc_data, &read_data); +} +#endif + +static void max77705_usbc_gpio5_read_complete( + struct max77705_usbc_platform_data *usbc_data, + unsigned char *data) +{ + u8 direction = data[1]; + u8 value = data[2]; + + msg_maxim("gpio5: Direction:%s, Value:%s", + direction ? "Output" : "Input", + value ? "High" : "Low"); + + if (direction == 1) + usbc_data->ovp_gpio = value; + + complete(&usbc_data->ccic_sysfs_completion); +} + +#ifdef CONFIG_MAX77705_GRL_ENABLE +static void max77705_set_forcetrimi(struct max77705_usbc_platform_data *usbc_data) +{ + u8 ArrSendData[2] = {0x00, 0x00}; + + msg_maxim("IN++"); + mutex_lock(&usbc_data->max77705->i2c_lock); +// ArrSendData[0] = 0xFE; +// ArrSendData[1] = 0xC5; +// max77705_i2c_master_write(usbc_data, 0x66, ArrSendData); +// ArrSendData[0] = 0xb3; +// ArrSendData[1] = 0x0c; +// max77705_i2c_master_write(usbc_data, 0x62, ArrSendData); + ArrSendData[0] = 0x1F; + ArrSendData[1] = 0x04; + max77705_i2c_master_write(usbc_data, 0x62, ArrSendData); + msleep(100); + mutex_unlock(&usbc_data->max77705->i2c_lock); + msg_maxim("OUT"); +} +#endif + +static void max77705_send_role_swap_message(struct max77705_usbc_platform_data *usbpd_data, u8 mode) +{ + usbc_cmd_data write_data; + + max77705_usbc_clear_queue(usbpd_data); + init_usbc_cmd_data(&write_data); + write_data.opcode = 0x37; + /* 0x1 : DR_SWAP, 0x2 : PR_SWAP, 0x4: Manual Role Swap */ + write_data.write_data[0] = mode; + write_data.write_length = 0x1; + write_data.read_length = 0x1; + max77705_usbc_opcode_write(usbpd_data, &write_data); +} + +void max77705_rprd_mode_change(struct max77705_usbc_platform_data *usbpd_data, u8 mode) +{ + msg_maxim("mode = 0x%x", mode); + + switch (mode) { + case TYPE_C_ATTACH_DFP: + case TYPE_C_ATTACH_UFP: + max77705_send_role_swap_message(usbpd_data, MANUAL_ROLE_SWAP); + msleep(1000); + break; + default: + break; + }; +} + +void max77705_power_role_change(struct max77705_usbc_platform_data *usbpd_data, int power_role) +{ + msg_maxim("power_role = 0x%x", power_role); + + switch (power_role) { + case TYPE_C_ATTACH_SRC: + case TYPE_C_ATTACH_SNK: + max77705_send_role_swap_message(usbpd_data, POWER_ROLE_SWAP); + break; + }; +} + +void max77705_data_role_change(struct max77705_usbc_platform_data *usbpd_data, int data_role) +{ + msg_maxim("data_role = 0x%x", data_role); + + switch (data_role) { + case TYPE_C_ATTACH_DFP: + case TYPE_C_ATTACH_UFP: + max77705_send_role_swap_message(usbpd_data, DATA_ROLE_SWAP); + break; + }; +} + +static int max77705_dr_set(struct typec_port *port, enum typec_data_role role) +{ + struct max77705_usbc_platform_data *usbpd_data = typec_get_drvdata(port); +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif /* CONFIG_USB_HW_PARAM */ + + if (!usbpd_data) + return -EINVAL; + msg_maxim("typec_power_role=%d, typec_data_role=%d, role=%d", + usbpd_data->typec_power_role, usbpd_data->typec_data_role, role); + + if (usbpd_data->typec_data_role != TYPEC_DEVICE + && usbpd_data->typec_data_role != TYPEC_HOST) + return -EPERM; + else if (usbpd_data->typec_data_role == role) + return -EPERM; + + reinit_completion(&usbpd_data->typec_reverse_completion); + if (role == TYPEC_DEVICE) { + msg_maxim("try reversing, from DFP to UFP"); + usbpd_data->typec_try_state_change = TRY_ROLE_SWAP_DR; + max77705_data_role_change(usbpd_data, TYPE_C_ATTACH_UFP); + } else if (role == TYPEC_HOST) { + msg_maxim("try reversing, from UFP to DFP"); + usbpd_data->typec_try_state_change = TRY_ROLE_SWAP_DR; + max77705_data_role_change(usbpd_data, TYPE_C_ATTACH_DFP); + } else { + msg_maxim("invalid typec_role"); + return -EIO; + } + if (!wait_for_completion_timeout(&usbpd_data->typec_reverse_completion, + msecs_to_jiffies(TRY_ROLE_SWAP_WAIT_MS))) { + usbpd_data->typec_try_state_change = TRY_ROLE_SWAP_NONE; + return -ETIMEDOUT; + } +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_DR_SWAP_COUNT); +#endif /* CONFIG_USB_HW_PARAM */ + return 0; +} + +static int max77705_pr_set(struct typec_port *port, enum typec_role role) +{ + struct max77705_usbc_platform_data *usbpd_data = typec_get_drvdata(port); +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif /* CONFIG_USB_HW_PARAM */ + + if (!usbpd_data) + return -EINVAL; + + msg_maxim("typec_power_role=%d, typec_data_role=%d, role=%d", + usbpd_data->typec_power_role, usbpd_data->typec_data_role, role); + + if (usbpd_data->typec_power_role != TYPEC_SINK + && usbpd_data->typec_power_role != TYPEC_SOURCE) + return -EPERM; + else if (usbpd_data->typec_power_role == role) + return -EPERM; + + reinit_completion(&usbpd_data->typec_reverse_completion); + if (role == TYPEC_SINK) { + msg_maxim("try reversing, from Source to Sink"); + usbpd_data->typec_try_state_change = TRY_ROLE_SWAP_PR; + max77705_power_role_change(usbpd_data, TYPE_C_ATTACH_SNK); + } else if (role == TYPEC_SOURCE) { + msg_maxim("try reversing, from Sink to Source"); + usbpd_data->typec_try_state_change = TRY_ROLE_SWAP_PR; + max77705_power_role_change(usbpd_data, TYPE_C_ATTACH_SRC); + } else { + msg_maxim("invalid typec_role"); + return -EIO; + } + if (!wait_for_completion_timeout(&usbpd_data->typec_reverse_completion, + msecs_to_jiffies(TRY_ROLE_SWAP_WAIT_MS))) { + usbpd_data->typec_try_state_change = TRY_ROLE_SWAP_NONE; + if (usbpd_data->typec_power_role != role) + return -ETIMEDOUT; + } +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_PR_SWAP_COUNT); +#endif + return 0; +} + +static int max77705_port_type_set(struct typec_port *port, enum typec_port_type port_type) +{ + struct max77705_usbc_platform_data *usbpd_data = typec_get_drvdata(port); + + if (!usbpd_data) + return -EINVAL; + + msg_maxim("typec_power_role=%d, typec_data_role=%d, port_type=%d", + usbpd_data->typec_power_role, usbpd_data->typec_data_role, port_type); + + reinit_completion(&usbpd_data->typec_reverse_completion); + if (port_type == TYPEC_PORT_DFP) { + msg_maxim("try reversing, from UFP(Sink) to DFP(Source)"); + usbpd_data->typec_try_state_change = TRY_ROLE_SWAP_TYPE; + max77705_rprd_mode_change(usbpd_data, TYPE_C_ATTACH_DFP); + } else if (port_type == TYPEC_PORT_UFP) { + msg_maxim("try reversing, from DFP(Source) to UFP(Sink)"); +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + max77705_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_MUIC, PDIC_NOTIFY_ID_ATTACH, + 0/*attach*/, 0/*rprd*/, 0); +#endif + usbpd_data->typec_try_state_change = TRY_ROLE_SWAP_TYPE; + max77705_rprd_mode_change(usbpd_data, TYPE_C_ATTACH_UFP); + } else { + msg_maxim("invalid typec_role"); + return 0; + } + + if (!wait_for_completion_timeout(&usbpd_data->typec_reverse_completion, + msecs_to_jiffies(TRY_ROLE_SWAP_WAIT_MS))) { + usbpd_data->typec_try_state_change = TRY_ROLE_SWAP_NONE; + return -ETIMEDOUT; + } + return 0; +} + +static const struct typec_operations max77705_ops = { + .dr_set = max77705_dr_set, + .pr_set = max77705_pr_set, + .port_type_set = max77705_port_type_set +}; + +int max77705_get_pd_support(struct max77705_usbc_platform_data *usbc_data) +{ + bool support_pd_role_swap = false; + struct device_node *np = NULL; + + np = of_find_compatible_node(NULL, NULL, "maxim,max77705_pdic"); + + if (np) + support_pd_role_swap = of_property_read_bool(np, "support_pd_role_swap"); + else + msg_maxim("np is null"); + + msg_maxim("TYPEC_CLASS: support_pd_role_swap is %d, usbc_data->pd_support : %d", + support_pd_role_swap, usbc_data->pd_support); + + if (support_pd_role_swap && usbc_data->pd_support) + return TYPEC_PWR_MODE_PD; + + return usbc_data->pwr_opmode; +} + +#if defined(MAX77705_SYS_FW_UPDATE) +static int max77705_firmware_update_sys(struct max77705_usbc_platform_data *data, int fw_dir) +{ + struct max77705_usbc_platform_data *usbc_data = data; + max77705_fw_header *fw_header; + const struct firmware *fw_entry; + int fw_size, ret = 0; +#if IS_ENABLED(CONFIG_SPU_VERIFY) + long fw_verify_size; +#endif + struct pdic_fw_update fwup[FWUP_CMD_MAX] = { + {"BUILT_IN", "", 0, 1}, + {"UMS", MAXIM_DEFAULT_FW, 0, 1}, + {"SPU", MAXIM_SPU_FW, 1, 0}, + {"SPU_V", MAXIM_SPU_FW, 1, 0} + }; + + if (!usbc_data) { + msg_maxim("usbc_data is null!!"); + return -ENODEV; + } + + switch (fw_dir) { +#if defined(CONFIG_CCIC_MAX77705_DEBUG) + case UMS: + break; +#endif +#if IS_ENABLED(CONFIG_SPU_VERIFY) + case SPU: + case SPU_VERIFICATION: + break; +#endif + case BUILT_IN: + max77705_usbc_fw_setting(usbc_data->max77705, fwup[fw_dir].enforce_do); + return 0; + default: + return -EINVAL; + } + + ret = request_firmware(&fw_entry, fwup[fw_dir].path, usbc_data->dev); + if (ret) { + pr_info("%s: firmware is not available %d\n", __func__, ret); + return ret; + } + + fw_size = (int)fw_entry->size; + fw_header = (max77705_fw_header *)fw_entry->data; + msg_maxim("Req fw %02X.%02X, length=%ld", fw_header->major, fw_header->minor, fw_entry->size); + +#if IS_ENABLED(CONFIG_SPU_VERIFY) + if (fwup[fw_dir].need_verfy) { + fw_size = (int)fw_entry->size - SPU_METADATA_SIZE(PDIC); + fw_verify_size = spu_firmware_signature_verify("PDIC", fw_entry->data, fw_entry->size); + if (fw_verify_size != fw_size) { + pr_info("%s: signature verify failed, verify_ret:%ld, ori_size:%d\n", + __func__, fw_verify_size, fw_size); + ret = -EPERM; + goto out; + } + if (fw_dir == SPU_VERIFICATION) + goto out; + } +#endif + + switch (usbc_data->max77705->pmic_rev) { + case MAX77705_PASS4: + case MAX77705_PASS5: + ret = max77705_usbc_fw_update(usbc_data->max77705, fw_entry->data, + fw_size, fwup[fw_dir].enforce_do); + break; + default: + msg_maxim("FAILED PMIC_REVISION isn't valid (pmic_rev : 0x%x)\n", + usbc_data->max77705->pmic_rev); + break; + } +#if IS_ENABLED(CONFIG_SPU_VERIFY) +out: +#endif + release_firmware(fw_entry); + return ret; +} +#endif + +static int max77705_firmware_update_misc(struct max77705_usbc_platform_data *data, + void *fw_data, size_t fw_size) +{ + struct max77705_usbc_platform_data *usbc_data = data; + max77705_fw_header *fw_header; + int ret = 0; + const u8 *fw_bin; + size_t fw_bin_len; + u8 pmic_rev = 0;/* pmic Rev */ + u8 fw_enable = 0; + + if (!usbc_data) { + msg_maxim("usbc_data is null!!"); + ret = -ENOMEM; + goto out; + } + + pmic_rev = usbc_data->max77705->pmic_rev; + + if (fw_size > 0) { + msg_maxim("start, size %ld Bytes", fw_size); + + fw_bin_len = fw_size; + fw_bin = fw_data; + fw_header = (max77705_fw_header *)fw_bin; + max77705_read_reg(usbc_data->muic, + REG_UIC_FW_REV, &usbc_data->FW_Revision); + max77705_read_reg(usbc_data->muic, + REG_UIC_FW_MINOR, &usbc_data->FW_Minor_Revision); + usbc_data->FW_Minor_Revision &= MINOR_VERSION_MASK; + msg_maxim("chip %02X.%02X, fw %02X.%02X", + usbc_data->FW_Revision, usbc_data->FW_Minor_Revision, + fw_header->major, fw_header->minor); + switch (pmic_rev) { + case MAX77705_PASS4: + case MAX77705_PASS5: + fw_enable = 1; + break; + default: + msg_maxim("FAILED F/W via SYS and PMIC_REVISION isn't valid"); + break; + }; + + if (fw_enable) + ret = max77705_usbc_fw_update(usbc_data->max77705, fw_bin, (int)fw_bin_len, 2); + else + msg_maxim("FAILED F/W MISMATCH pmic_rev : 0x%x, fw_header->major : 0x%x", + pmic_rev, fw_header->major); + } +out: + return ret; +} + +void max77705_manual_jig_on(struct max77705_usbc_platform_data *usbpd_data, int mode) +{ + usbc_cmd_data read_data; + usbc_cmd_data write_data; + + msg_maxim("usb: mode=%s", mode ? "High" : "Low"); + + init_usbc_cmd_data(&read_data); + init_usbc_cmd_data(&write_data); + read_data.opcode = OPCODE_CTRL3_R; + read_data.write_length = 0x0; + read_data.read_length = 0x1; + write_data.opcode = OPCODE_CTRL3_W; + if (mode) + write_data.write_data[0] = 0x1; + else + write_data.write_data[0] = 0x0; + + write_data.write_data[1] = 0x1; + write_data.write_length = 0x2; + + write_data.read_length = 0x0; + + max77705_usbc_opcode_read(usbpd_data, &read_data); + max77705_usbc_opcode_write(usbpd_data, &write_data); +} + +void max77705_control_option_command(struct max77705_usbc_platform_data *usbpd_data, int cmd) +{ + struct max77705_cc_data *cc_data = usbpd_data->cc_data; + u8 ccstat = 0; + usbc_cmd_data write_data; + + /* for maxim request : they want to check ccstate here */ + max77705_read_reg(usbpd_data->muic, REG_CC_STATUS0, &cc_data->cc_status0); + ccstat = (cc_data->cc_status0 & BIT_CCStat) >> FFS(BIT_CCStat); + msg_maxim("usb: cmd=0x%x ccstat : %d", cmd, ccstat); + + init_usbc_cmd_data(&write_data); + /* 1 : Vconn control option command ON */ + /* 2 : Vconn control option command OFF */ + /* 3 : Water Detect option command ON */ + /* 4 : Water Detect option command OFF */ + if (cmd == 1) + usbpd_data->vconn_test = 0; + else if (cmd == 2) + usbpd_data->vconn_test = 0; /* do nothing */ + else if (cmd == 3) { /* if SBU pin is low, water interrupt is happened. */ + write_data.opcode = 0x54; + write_data.write_data[0] = 0x3; + write_data.write_length = 0x1; + write_data.read_length = 0x0; + max77705_usbc_opcode_write(usbpd_data, &write_data); + } else if (cmd == 4) { + write_data.opcode = 0x54; + write_data.write_data[0] = 0x2; + write_data.write_length = 0x1; + write_data.read_length = 0x0; + max77705_usbc_opcode_write(usbpd_data, &write_data); + } + if ((cmd & 0xF) == 0x3) + usbpd_data->fac_water_enable = 1; + else if ((cmd & 0xF) == 0x4) + usbpd_data->fac_water_enable = 0; +} + +void max77705_response_sbu_read(struct max77705_usbc_platform_data *usbpd_data, unsigned char *data) +{ + u8 sbu1 = 0, sbu2 = 0; + + sbu1 = data[1]; + sbu2 = data[2]; + + msg_maxim("SBU1 = 0x%x, SBU2 = 0x%x", sbu1, sbu2); + + if (sbu1 == 0x0) + usbpd_data->sbu[0] = 0; + else + usbpd_data->sbu[0] = 1; + if (sbu2 == 0x0) + usbpd_data->sbu[1] = 0; + else + usbpd_data->sbu[1] = 1; + complete(&usbpd_data->ccic_sysfs_completion); +} + +void max77705_request_sbu_read(struct max77705_usbc_platform_data *usbpd_data) +{ + usbc_cmd_data write_data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_READ_SBU; + write_data.write_data[0] = 0x1; + write_data.write_length = 0x1; + write_data.read_length = 0x2; + max77705_usbc_opcode_write(usbpd_data, &write_data); +} + +void max77705_response_cc_read(struct max77705_usbc_platform_data *usbpd_data, unsigned char *data) +{ + u8 cc1 = 0, cc2 = 0; + + cc1 = data[1]; + cc2 = data[2]; + + msg_maxim("CC1 = 0x%x, CC2 = 0x%x", cc1, cc2); + + usbpd_data->cc[0] = cc1; + usbpd_data->cc[1] = cc2; + + complete(&usbpd_data->ccic_sysfs_completion); +} + +void max77705_request_cc_read(struct max77705_usbc_platform_data *usbpd_data) +{ + usbc_cmd_data write_data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_READ_CC; + write_data.write_length = 0x0; + write_data.read_length = 0x2; + max77705_usbc_opcode_write(usbpd_data, &write_data); +} + +void max77705_request_control3_reg_read(struct max77705_usbc_platform_data *usbpd_data) +{ + usbc_cmd_data read_data; + + init_usbc_cmd_data(&read_data); + read_data.opcode = OPCODE_CTRLREG3_R; + read_data.write_length = 0x0; + read_data.read_length = 0x1; + max77705_usbc_opcode_read(g_usbc_data, &read_data); +} + +void max77705_set_CCForceError(struct max77705_usbc_platform_data *usbpd_data) +{ + usbc_cmd_data write_data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_CCCTRL2_W; + write_data.write_data[0] = 0x84; + write_data.write_length = 0x1; + write_data.read_length = 0x0; + max77705_usbc_opcode_write(usbpd_data, &write_data); +} + +void max77705_set_lockerroren(struct max77705_usbc_platform_data *usbpd_data, + unsigned char data, u8 en) +{ + usbc_cmd_data write_data; + u8 control3_reg = data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_CTRLREG3_W; + control3_reg &= ~(0x1 << 1); + write_data.write_data[0] = control3_reg | ((en & 0x1) << 1); + write_data.write_length = 0x1; + write_data.read_length = 0x0; + max77705_usbc_opcode_write(usbpd_data, &write_data); +} + +void max77705_control3_read_complete(struct max77705_usbc_platform_data *usbpd_data, + unsigned char *data) +{ + usbpd_data->control3_reg = data[1]; + complete(&usbpd_data->op_completion); +} + +void pdic_manual_ccopen_request(int is_on) +{ + struct max77705_usbc_platform_data *usbpd_data = g_usbc_data; + + msg_maxim("is_on %d > %d", usbpd_data->cc_open_req, is_on); + if (usbpd_data->cc_open_req != is_on) { + usbpd_data->cc_open_req = is_on; + schedule_work(&usbpd_data->cc_open_req_work); + } +} +EXPORT_SYMBOL(pdic_manual_ccopen_request); + +static void max77705_cc_open_work_func( + struct work_struct *work) +{ + struct max77705_usbc_platform_data *usbc_data; + u8 lock_err_en; +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + int event; +#endif + + usbc_data = container_of(work, struct max77705_usbc_platform_data, cc_open_req_work); + msg_maxim("%s", usbc_data->cc_open_req? "set":"clear"); + + if (usbc_data->cc_open_req) { + reinit_completion(&usbc_data->op_completion); + max77705_request_control3_reg_read(usbc_data); /* ref 0x65 -> write 0x67*/ + if (!wait_for_completion_timeout(&usbc_data->op_completion, msecs_to_jiffies(1000))) { + msg_maxim("OPCMD COMPLETION TIMEOUT"); + return; + } + lock_err_en = GET_CONTROL3_LOCK_ERROR_EN(usbc_data->control3_reg); + msg_maxim("data: 0x%x lock_err_en=%d", usbc_data->control3_reg, lock_err_en); + if (!lock_err_en) { +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_CCOPEN_REQ_SET; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + max77705_set_lockerroren(usbc_data, usbc_data->control3_reg, 1); + } + max77705_set_CCForceError(usbc_data); + } else { + reinit_completion(&usbc_data->op_completion); + max77705_request_control3_reg_read(usbc_data); + if (!wait_for_completion_timeout(&usbc_data->op_completion, msecs_to_jiffies(1000))) + msg_maxim("OPCMD COMPLETION TIMEOUT"); + + lock_err_en = GET_CONTROL3_LOCK_ERROR_EN(usbc_data->control3_reg); + msg_maxim("data: 0x%x lock_err_en=%d", usbc_data->control3_reg, lock_err_en); + if (lock_err_en) { +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_CCOPEN_REQ_CLEAR; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + max77705_set_lockerroren(usbc_data, usbc_data->control3_reg, 0); + } + } +} + +static void max77705_dp_configure_work_func( + struct work_struct *work) +{ + struct max77705_usbc_platform_data *usbpd_data; +#if !IS_ENABLED(CONFIG_ARCH_QCOM) || !defined(CONFIG_SEC_FACTORY) + int timeleft = 0; +#endif + + usbpd_data = container_of(work, struct max77705_usbc_platform_data, dp_configure_work); +#if !IS_ENABLED(CONFIG_ARCH_QCOM) || !defined(CONFIG_SEC_FACTORY) + timeleft = wait_event_interruptible_timeout(usbpd_data->device_add_wait_q, + usbpd_data->device_add || usbpd_data->pd_data->cc_status == CC_NO_CONN, HZ/2); + msg_maxim("%s timeleft = %d\n", __func__, timeleft); +#endif + if (usbpd_data->pd_data->cc_status != CC_NO_CONN) + max77705_ccic_event_work(usbpd_data, PDIC_NOTIFY_DEV_DP, + PDIC_NOTIFY_ID_DP_LINK_CONF, usbpd_data->dp_selected_pin, 0, 0); +} + +void max77705_response_selftest_read(struct max77705_usbc_platform_data *usbpd_data, unsigned char *data) +{ + u8 cc = 0; + + cc = data[1]; + usbpd_data->sbu[0] = data[2]; + usbpd_data->sbu[1] = data[3]; + + msg_maxim("SELFTEST CC = %x SBU1 = 0x%x, SBU2 = 0x%x", cc, + usbpd_data->sbu[0], usbpd_data->sbu[1]); + complete(&usbpd_data->ccic_sysfs_completion); +} + +void max77705_request_selftest_read(struct max77705_usbc_platform_data *usbpd_data) +{ + usbc_cmd_data write_data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_READ_SELFTEST; + write_data.write_length = 0x1; + write_data.write_data[0] = 0x1; + write_data.read_length = 0x3; + max77705_usbc_opcode_write(usbpd_data, &write_data); +} + +#if defined(MAX77705_SYS_FW_UPDATE) +static int max77705_firmware_update_sysfs(struct max77705_usbc_platform_data *usbpd_data, int fw_dir) +{ + int ret = 0; + usbpd_data->fw_update = 1; + max77705_usbc_mask_irq(usbpd_data); + max77705_write_reg(usbpd_data->muic, REG_PD_INT_M, 0xFF); + max77705_write_reg(usbpd_data->muic, REG_CC_INT_M, 0xFF); + max77705_write_reg(usbpd_data->muic, REG_UIC_INT_M, 0xFF); + max77705_write_reg(usbpd_data->muic, REG_VDM_INT_M, 0xFF); + ret = max77705_firmware_update_sys(usbpd_data, fw_dir); + max77705_write_reg(usbpd_data->muic, REG_UIC_INT_M, REG_UIC_INT_M_INIT); + max77705_write_reg(usbpd_data->muic, REG_CC_INT_M, REG_CC_INT_M_INIT); + max77705_write_reg(usbpd_data->muic, REG_PD_INT_M, REG_PD_INT_M_INIT); + max77705_write_reg(usbpd_data->muic, REG_VDM_INT_M, REG_VDM_INT_M_INIT); + // max77705_usbc_enable_auto_vbus(usbpd_data); + max77705_set_enable_alternate_mode(ALTERNATE_MODE_START); + max77705_usbc_umask_irq(usbpd_data); + if (ret) + usbpd_data->fw_update = 2; + else + usbpd_data->fw_update = 0; + return ret; +} +#endif + +static int max77705_firmware_update_callback(void *data, + void *fw_data, size_t fw_size) +{ + struct max77705_usbc_platform_data *usbpd_data + = (struct max77705_usbc_platform_data *)data; + int ret = 0; + + usbpd_data->fw_update = 1; + max77705_usbc_mask_irq(usbpd_data); + max77705_write_reg(usbpd_data->muic, REG_PD_INT_M, 0xFF); + max77705_write_reg(usbpd_data->muic, REG_CC_INT_M, 0xFF); + max77705_write_reg(usbpd_data->muic, REG_UIC_INT_M, 0xFF); + max77705_write_reg(usbpd_data->muic, REG_VDM_INT_M, 0xFF); + ret = max77705_firmware_update_misc(usbpd_data, fw_data, fw_size); + max77705_write_reg(usbpd_data->muic, REG_UIC_INT_M, REG_UIC_INT_M_INIT); + max77705_write_reg(usbpd_data->muic, REG_CC_INT_M, REG_CC_INT_M_INIT); + max77705_write_reg(usbpd_data->muic, REG_PD_INT_M, REG_PD_INT_M_INIT); + max77705_write_reg(usbpd_data->muic, REG_VDM_INT_M, REG_VDM_INT_M_INIT); + // max77705_usbc_enable_auto_vbus(usbpd_data); + max77705_set_enable_alternate_mode(ALTERNATE_MODE_START); + max77705_usbc_umask_irq(usbpd_data); + if (ret) + usbpd_data->fw_update = 2; + else + usbpd_data->fw_update = 0; + return ret; +} + +static unsigned long max77705_get_firmware_size(void *data) +{ + struct max77705_usbc_platform_data *usbpd_data + = (struct max77705_usbc_platform_data *)data; + unsigned long ret = 0; + + ret = usbpd_data->max77705->fw_size; + + return ret; +} + +#if defined(MAX77705_SYS_FW_UPDATE) +static void max77705_firmware_update_sysfs_work(struct work_struct *work) +{ + struct max77705_usbc_platform_data *usbpd_data = container_of(work, + struct max77705_usbc_platform_data, fw_update_work); + + max77705_firmware_update_sysfs(usbpd_data, BUILT_IN); +} +#endif + +int max77705_request_vsafe0v_read(struct max77705_usbc_platform_data *usbpd_data) +{ + u8 cc_status1 = 0; + int vsafe0v = 0; + + max77705_read_reg(usbpd_data->muic, REG_CC_STATUS1, &cc_status1); + + vsafe0v = (cc_status1 & BIT_VSAFE0V) >> FFS(BIT_VSAFE0V); + pr_info("%s: ccstatus1: 0x%x %d \n", __func__, cc_status1, vsafe0v); + return vsafe0v; +} + +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) +static int max77705_sysfs_get_local_prop(struct _pdic_data_t *ppdic_data, + enum pdic_sysfs_property prop, + char *buf) +{ + int retval = -ENODEV, i = 0; + u8 cur_major = 0, cur_minor = 0, src_major = 0, src_minor = 0; + struct max77705_usbc_platform_data *usbpd_data = + (struct max77705_usbc_platform_data *)ppdic_data->drv_data; + + if (!usbpd_data) { + msg_maxim("usbpd_data is null : request prop = %d", prop); + return -ENODEV; + } + + switch (prop) { + case PDIC_SYSFS_PROP_CUR_VERSION: + retval = max77705_read_reg(usbpd_data->muic, REG_UIC_FW_REV, &cur_major); + if (retval < 0) { + msg_maxim("Failed to read FW_REV"); + return retval; + } + retval = max77705_read_reg(usbpd_data->muic, REG_UIC_FW_MINOR, &cur_minor); + if (retval < 0) { + msg_maxim("Failed to read FW_MINOR_REV"); + return retval; + } + cur_minor &= MINOR_VERSION_MASK; + retval = sprintf(buf, "%02X.%02X\n", cur_major, cur_minor); + msg_maxim("usb: PDIC_SYSFS_PROP_CUR_VERSION : %02X.%02X", + cur_major, cur_minor); + break; + case PDIC_SYSFS_PROP_SRC_VERSION: + if (usbpd_data->max77705->pmic_rev == MAX77705_PASS5) { + src_major = BOOT_FLASH_FW_PASS2[4]; + src_minor = BOOT_FLASH_FW_PASS2[5] & MINOR_VERSION_MASK; + } else { + src_major = 0xFF; + src_minor = 0xFF; + } + retval = sprintf(buf, "%02X.%02X\n", src_major, src_minor); + msg_maxim("usb: PDIC_SYSFS_PROP_SRC_VERSION : %02X.%02X", + src_major, src_minor); + break; + case PDIC_SYSFS_PROP_LPM_MODE: + retval = sprintf(buf, "%d\n", usbpd_data->manual_lpm_mode); + msg_maxim("usb: PDIC_SYSFS_PROP_LPM_MODE : %d", + usbpd_data->manual_lpm_mode); + break; + case PDIC_SYSFS_PROP_STATE: + retval = sprintf(buf, "%d\n", usbpd_data->pd_state); + msg_maxim("usb: PDIC_SYSFS_PROP_STATE : %d", + usbpd_data->pd_state); + break; + case PDIC_SYSFS_PROP_RID: + retval = sprintf(buf, "%d\n", usbpd_data->cur_rid); + msg_maxim("usb: PDIC_SYSFS_PROP_RID : %d", + usbpd_data->cur_rid); + break; + case PDIC_SYSFS_PROP_BOOTING_DRY: + usbpd_data->sbu[0] = 0;usbpd_data->sbu[1] = 0; + reinit_completion(&usbpd_data->ccic_sysfs_completion); + max77705_request_selftest_read(usbpd_data); + i = wait_for_completion_timeout(&usbpd_data->ccic_sysfs_completion, msecs_to_jiffies(1000 * 5)); + if (i == 0) + msg_maxim("CCIC SYSFS COMPLETION TIMEOUT"); + msg_maxim("usb: PDIC_SYSFS_PROP_BOOTING_DRY timeout : %d", i); + if (usbpd_data->sbu[0] >= 7 && usbpd_data->sbu[1] >= 7) + retval = sprintf(buf, "%d\n", 1); + else + retval = sprintf(buf, "%d\n", 0); + break; + case PDIC_SYSFS_PROP_FW_UPDATE_STATUS: + + retval = sprintf(buf, "%s\n", usbpd_data->fw_update == 1 ? "UPDATE" : + usbpd_data->fw_update == 2 ? "NG" : "OK"); + msg_maxim("usb: PDIC_SYSFS_PROP_FW_UPDATE_STATUS : %s", buf); + break; + case PDIC_SYSFS_PROP_FW_WATER: + retval = sprintf(buf, "%d\n", usbpd_data->current_connstat == WATER ? 1 : 0); + msg_maxim("usb: PDIC_SYSFS_PROP_FW_WATER : %d", + usbpd_data->current_connstat == WATER ? 1 : 0); + break; + case PDIC_SYSFS_PROP_ACC_DEVICE_VERSION: + retval = sprintf(buf, "%04x\n", usbpd_data->Device_Version); + msg_maxim("usb: PDIC_SYSFS_PROP_ACC_DEVICE_VERSION : %d", + usbpd_data->Device_Version); + break; + case PDIC_SYSFS_PROP_CONTROL_GPIO: + usbpd_data->sbu[0] = 0;usbpd_data->sbu[1] = 0; + reinit_completion(&usbpd_data->ccic_sysfs_completion); + max77705_request_sbu_read(usbpd_data); + i = wait_for_completion_timeout(&usbpd_data->ccic_sysfs_completion, msecs_to_jiffies(200 * 5)); + if (i == 0) + msg_maxim("CCIC SYSFS COMPLETION TIMEOUT"); + /* compare SBU1, SBU2 values after interrupt */ + msg_maxim("usb: PDIC_SYSFS_PROP_CONTROL_GPIO SBU1 = 0x%x ,SBU2 = 0x%x timeout:%d", + usbpd_data->sbu[0], usbpd_data->sbu[1], i); + retval = sprintf(buf, "%d %d\n", usbpd_data->sbu[0], usbpd_data->sbu[1]); + break; + case PDIC_SYSFS_PROP_USBPD_IDS: + retval = sprintf(buf, "%04x:%04x\n", + le16_to_cpu(usbpd_data->Vendor_ID), + le16_to_cpu(usbpd_data->Product_ID)); + msg_maxim("usb: PDIC_SYSFS_USBPD_IDS : %s", buf); + break; + case PDIC_SYSFS_PROP_USBPD_TYPE: + retval = sprintf(buf, "%d\n", usbpd_data->acc_type); + msg_maxim("usb: PDIC_SYSFS_USBPD_TYPE : %d", + usbpd_data->acc_type); + break; + case PDIC_SYSFS_PROP_CC_PIN_STATUS: + retval = sprintf(buf, "%d\n", usbpd_data->cc_pin_status); + msg_maxim("usb: PDIC_SYSFS_PROP_PIN_STATUS : %d", + usbpd_data->cc_pin_status); + break; +#ifdef MAX77705_RAM_TEST + case PDIC_SYSFS_PROP_RAM_TEST: + max77705_verify_ram_bist_write(usbpd_data); + for (i = 0; i < 300; i++) { + msleep(10); + if (usbpd_data->ram_test_enable == MAX77705_RAM_TEST_STOP_MODE) { + msleep(3000); + break; + } + } + msg_maxim("usb: PDIC_SYSFS_PROP_RAM_TEST : %d", usbpd_data->ram_test_result); + retval = sprintf(buf, "%d\n", usbpd_data->ram_test_result); + break; +#endif + case PDIC_SYSFS_PROP_SBU_ADC: + usbpd_data->sbu[0] = 0;usbpd_data->sbu[1] = 0; + reinit_completion(&usbpd_data->ccic_sysfs_completion); + max77705_request_selftest_read(usbpd_data); + i = wait_for_completion_timeout(&usbpd_data->ccic_sysfs_completion, msecs_to_jiffies(1000 * 5)); + if (i == 0) + msg_maxim("CCIC SYSFS COMPLETION TIMEOUT"); + msg_maxim("usb: PDIC_SYSFS_PROP_SBU_ADC : %d %d timeout : %d", + usbpd_data->sbu[0], usbpd_data->sbu[1], i); + retval = sprintf(buf, "%d %d\n", usbpd_data->sbu[0], usbpd_data->sbu[1]); + break; + case PDIC_SYSFS_PROP_CC_ADC: + usbpd_data->cc[0] = 0;usbpd_data->cc[1] = 0; + reinit_completion(&usbpd_data->ccic_sysfs_completion); + max77705_request_cc_read(usbpd_data); + i = wait_for_completion_timeout(&usbpd_data->ccic_sysfs_completion, msecs_to_jiffies(1000 * 5)); + if (i == 0) + msg_maxim("CCIC SYSFS COMPLETION TIMEOUT"); + msg_maxim("usb: PDIC_SYSFS_PROP_CC_ADC : %d %d timeout : %d", + usbpd_data->cc[0], usbpd_data->cc[1], i); + retval = sprintf(buf, "%d %d\n", usbpd_data->cc[0], usbpd_data->cc[1]); + break; + case PDIC_SYSFS_PROP_VSAFE0V_STATUS: + usbpd_data->vsafe0v_status = max77705_request_vsafe0v_read(usbpd_data); + retval = sprintf(buf, "%d\n", usbpd_data->vsafe0v_status); + msg_maxim("usb: PDIC_SYSFS_PROP_VSAFE0V_STATUS : %d", + usbpd_data->vsafe0v_status); + break; +#if defined(CONFIG_SEC_FACTORY) + case PDIC_SYSFS_PROP_15MODE_WATERTEST_TYPE: +#if 0 //use unsupport water pid fw feature (ex CONFIG_MAX77705_FW_PID05_SUPPORT) + retval = sprintf(buf, "unsupport\n"); +#else + retval = sprintf(buf, "uevent\n"); +#endif + pr_info("%s : PDIC_SYSFS_PROP_15MODE_WATERTEST_TYPE : %s", __func__, buf); + break; +#endif + default: + msg_maxim("prop read not supported prop (%d)", prop); + retval = -ENODATA; + break; + } + + return retval; +} + +/* + * assume that 1 HMD device has name(14),vid(4),pid(4) each, then + * max 32 HMD devices(name,vid,pid) need 806 bytes including TAG, NUM, comba + */ +#define MAX_HMD_POWER_STORE_LEN 1024 +enum { + HMD_POWER_MON = 0, /* monitor name field */ + HMD_POWER_VID, /* vid field */ + HMD_POWER_PID, /* pid field */ + HMD_POWER_FIELD_MAX, +}; + +/* convert VID/PID string to uint in hexadecimal */ +static int _max77705_strtoint(char *tok, uint *result) +{ + int ret = 0; + + if (!tok || !result) { + msg_maxim("invalid arg!"); + ret = -EINVAL; + goto end; + } + + if (strlen(tok) == 5 && tok[4] == 0xa/*LF*/) { + /* continue since it's ended with line feed */ + } else if (strlen(tok) != 4) { + msg_maxim("%s should have 4 len, but %lu!", tok, strlen(tok)); + ret = -EINVAL; + goto end; + } + + ret = kstrtouint(tok, 16, result); + if (ret) { + msg_maxim("fail to convert %s! ret:%d", tok, ret); + goto end; + } +end: + return ret; +} + +int max77705_store_hmd_dev(struct max77705_usbc_platform_data *usbc_data, char *str, size_t len, int num_hmd) +{ + struct max77705_hmd_power_dev *hmd_list; + char *tok; + int i, j, ret = 0, rmdr; + uint value; + + if (num_hmd <= 0 || num_hmd > MAX_NUM_HMD) { + msg_maxim("invalid num_hmd! %d", num_hmd); + ret = -EINVAL; + goto end; + } + + hmd_list = usbc_data->hmd_list; + if (!hmd_list) { + msg_maxim("hmd_list is null!"); + ret = -ENOMEM; + goto end; + } + + msg_maxim("+++ %s, %lu, %d", str, len, num_hmd); + + /* reset */ + for (i = 0; i < MAX_NUM_HMD; i++) { + memset(hmd_list[i].hmd_name, 0, NAME_LEN_HMD); + hmd_list[i].vid = 0; + hmd_list[i].pid = 0; + } + + tok = strsep(&str, ","); + i = 0, j = 0; + while (tok != NULL && *tok != 0xa/*LF*/) { + if (i >= num_hmd * HMD_POWER_FIELD_MAX) { + msg_maxim("num of tok cannot exceed <%dx%d>!", + num_hmd, HMD_POWER_FIELD_MAX); + break; + } + if (j >= MAX_NUM_HMD) { + msg_maxim("num of HMD cannot exceed %d!", + MAX_NUM_HMD); + break; + } + + rmdr = i % HMD_POWER_FIELD_MAX; + + switch (rmdr) { + case HMD_POWER_MON: + strlcpy(hmd_list[j].hmd_name, tok, NAME_LEN_HMD); + break; + + case HMD_POWER_VID: + case HMD_POWER_PID: + ret = _max77705_strtoint(tok, &value); + if (ret) + goto end; + + if (rmdr == HMD_POWER_VID) { + hmd_list[j].vid = value; + } else { + hmd_list[j].pid = value; + j++; /* move next */ + } + break; + } + + tok = strsep(&str, ","); + i++; + } + for (i = 0; i < MAX_NUM_HMD; i++) { + if (strlen(hmd_list[i].hmd_name) > 0) + msg_maxim("%s,0x%04x,0x%04x", + hmd_list[i].hmd_name, + hmd_list[i].vid, + hmd_list[i].pid); + } + +end: + return ret; +} + +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) +static void max77705_control_gpio_for_sbu(int onoff) +{ + struct otg_notify *o_notify = get_otg_notify(); + struct usb_notifier_platform_data *pdata = get_notify_data(o_notify); + + if (o_notify && o_notify->set_ldo_onoff) + o_notify->set_ldo_onoff(pdata, onoff); +} +#endif + +static ssize_t max77705_sysfs_set_prop(struct _pdic_data_t *ppdic_data, + enum pdic_sysfs_property prop, + const char *buf, size_t size) +{ + ssize_t retval = size; + int mode = 0; +#ifdef MAX77705_SYS_FW_UPDATE + u8 FW_Revision = 0, FW_Minor_Revision = 0; +#endif + int ret = 0; + struct max77705_usbc_platform_data *usbpd_data = + (struct max77705_usbc_platform_data *)ppdic_data->drv_data; + int rv, len; + char str[MAX_HMD_POWER_STORE_LEN] = {0,}, *p, *tok; +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + struct otg_notify *o_notify = get_otg_notify(); +#endif + + if (!usbpd_data) { + msg_maxim("usbpd_data is null : request prop = %d", prop); + return -ENODEV; + } + switch (prop) { + case PDIC_SYSFS_PROP_LPM_MODE: + rv = sscanf(buf, "%d", &mode); + msg_maxim("usb: PDIC_SYSFS_PROP_LPM_MODE mode=%d", mode); + switch (mode) { + case 0: + /* Disable Low Power Mode for App (SW JIGON Disable) */ + max77705_manual_jig_on(usbpd_data, 0); + usbpd_data->manual_lpm_mode = 0; + break; + case 1: + /* Enable Low Power Mode for App (SW JIGON Enable) */ + max77705_manual_jig_on(usbpd_data, 1); + usbpd_data->manual_lpm_mode = 1; + break; + case 2: + /* SW JIGON Enable */ + max77705_manual_jig_on(usbpd_data, 1); + usbpd_data->manual_lpm_mode = 1; + break; + default: + /* SW JIGON Disable */ + max77705_manual_jig_on(usbpd_data, 0); + usbpd_data->manual_lpm_mode = 0; + break; + } + break; + case PDIC_SYSFS_PROP_CTRL_OPTION: + rv = sscanf(buf, "%d", &mode); + msg_maxim("usb: PDIC_SYSFS_PROP_CTRL_OPTION mode=%d", mode); + max77705_control_option_command(usbpd_data, mode); + break; + case PDIC_SYSFS_PROP_FW_UPDATE: + rv = sscanf(buf, "%d", &mode); + msg_maxim("PDIC_SYSFS_PROP_FW_UPDATE mode=%d", mode); + +#ifdef MAX77705_SYS_FW_UPDATE + retval = max77705_read_reg(usbpd_data->muic, REG_UIC_FW_REV, &FW_Revision); + if (retval < 0) { + msg_maxim("Failed to read FW_REV"); + return retval; + } + retval = max77705_read_reg(usbpd_data->muic, REG_UIC_FW_MINOR, &FW_Minor_Revision); + if (retval < 0) { + msg_maxim("Failed to read FW_MINOR_REV"); + return retval; + } + FW_Minor_Revision &= MINOR_VERSION_MASK; + pr_info("%s before : FW_REV %02X.%02X\n", __func__, FW_Revision, FW_Minor_Revision); + + /* Factory cmd for firmware update + * argument represent what is source of firmware like below. + * + * 0 : [BUILT_IN] Getting firmware from source. + * 1 : [UMS] Getting firmware from sd card. + * 2 : [SPU] Getting firmware from SPU APP. + */ + switch (mode) { + case BUILT_IN: + schedule_work(&usbpd_data->fw_update_work); + break; + case UMS: + case SPU: + case SPU_VERIFICATION: + ret = max77705_firmware_update_sysfs(usbpd_data, mode); + break; + default: + ret = -EINVAL; + msg_maxim("Not support command[%d]", mode); + break; + } + if (ret < 0) { + msg_maxim("Failed to update FW"); + return ret; + } + + max77705_get_version_info(usbpd_data); +#else + return -EINVAL; +#endif + break; + case PDIC_SYSFS_PROP_DEX_FAN_UVDM: + rv = sscanf(buf, "%d", &mode); + msg_maxim("PDIC_SYSFS_PROP_DEX_FAN_UVDM mode=%d", mode); + max77705_send_dex_fan_unstructured_vdm_message(usbpd_data, mode); + break; + case PDIC_SYSFS_PROP_DEBUG_OPCODE: + rv = sscanf(buf, "%d", &mode); + msg_maxim("PDIC_SYSFS_PROP_DEBUG_OPCODE mode=%d", mode); + if (mode) + max77705_usbc_debug_function(usbpd_data); + break; + case PDIC_SYSFS_PROP_CONTROL_GPIO: + rv = sscanf(buf, "%d", &mode); + msg_maxim("PDIC_SYSFS_PROP_CONTROL_GPIO mode=%d. do nothing for control gpio.", mode); + /* orignal concept : mode 0 : SBU1/SBU2 set as open-drain status + * mode 1 : SBU1/SBU2 set as default status - Pull up + * But, max77705 is always open-drain status so we don't need to control it. + */ +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + max77705_control_gpio_for_sbu(!mode); +#endif + break; + case PDIC_SYSFS_PROP_OVP_IC_SHUTDOWN: + rv = sscanf(buf, "%d", &mode); + msg_maxim("PDIC_SYSFS_PROP_OVP_IC_SHUTDOWN mode=%d", mode); + ret = max77705_usbc_gpio5_direction_output(usbpd_data, mode); + if (ret) + return -ENODATA; + break; + case PDIC_SYSFS_PROP_HMD_POWER: + if (size >= MAX_HMD_POWER_STORE_LEN) { + msg_maxim("too long args! %lu", size); + return -EOVERFLOW; + } + mutex_lock(&usbpd_data->hmd_power_lock); + memcpy(str, buf, size); + p = str; + tok = strsep(&p, ","); + if (tok) { + len = strlen(tok); + msg_maxim("tok: %s, len: %d", tok, len); + } else { + len = 0; + msg_maxim("tok: len: 0"); + } + + if (!strncmp(TAG_HMD, tok, len)) { + /* called by HmtManager to inform list of supported HMD devices + * + * Format : + * HMD,NUM,NAME01,VID01,PID01,NAME02,VID02,PID02,... + * + * HMD : tag + * NUM : num of HMD dev ..... max 2 bytes to decimal (max 32) + * NAME : name of HMD ...... max 14 bytes, char string + * VID : vendor id ....... 4 bytes to hexadecimal + * PID : product id ....... 4 bytes to hexadecimal + * + * ex) HMD,2,PicoVR,2d40,0000,Nreal light,0486,573c + * + * call hmd store function with tag(HMD),NUM removed + */ + int num_hmd = 0, sz = 0; + + tok = strsep(&p, ","); + if (tok) { + sz = strlen(tok); + ret = kstrtouint(tok, 10, &num_hmd); + if (ret) + msg_maxim("fail to convert %s! ret:%d", tok, ret); + } + + msg_maxim("HMD num: %d, sz:%d", num_hmd, sz); + + max77705_store_hmd_dev(usbpd_data, str + (len + sz + 2), size - (len + sz + 2), + num_hmd); + + if (usbpd_data->acc_type == PDIC_DOCK_NEW && max77705_check_hmd_dev(usbpd_data)) { +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + if (o_notify) + send_otg_notify(o_notify, NOTIFY_EVENT_HMD_EXT_CURRENT, 1); +#endif + } + mutex_unlock(&usbpd_data->hmd_power_lock); + return size; + } + mutex_unlock(&usbpd_data->hmd_power_lock); + break; + default: + pr_info("%s prop write not supported prop (%d)\n", __func__, prop); + retval = -ENODATA; + return retval; + } + return size; +} + +static int max77705_sysfs_is_writeable(struct _pdic_data_t *ppdic_data, + enum pdic_sysfs_property prop) +{ + switch (prop) { + case PDIC_SYSFS_PROP_LPM_MODE: + case PDIC_SYSFS_PROP_CTRL_OPTION: + case PDIC_SYSFS_PROP_DEBUG_OPCODE: + case PDIC_SYSFS_PROP_CONTROL_GPIO: + return 1; + default: + return 0; + } +} + +static int max77705_sysfs_is_writeonly(struct _pdic_data_t *ppdic_data, + enum pdic_sysfs_property prop) +{ + switch (prop) { + case PDIC_SYSFS_PROP_FW_UPDATE: + case PDIC_SYSFS_PROP_DEX_FAN_UVDM: + case PDIC_SYSFS_PROP_OVP_IC_SHUTDOWN: + case PDIC_SYSFS_PROP_HMD_POWER: + return 1; + default: + return 0; + } +} +#endif +static ssize_t max77705_fw_update(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + unsigned int start_fw_update = 0; + usbc_cmd_data read_data; + usbc_cmd_data write_data; + + init_usbc_cmd_data(&read_data); + init_usbc_cmd_data(&write_data); + read_data.opcode = OPCODE_CTRL1_R; + read_data.write_length = 0x0; + read_data.read_length = 0x1; + + write_data.opcode = OPCODE_CTRL1_W; + write_data.write_data[0] = 0x09; + write_data.write_length = 0x1; + write_data.read_length = 0x0; + + if (kstrtou32(buf, 0, &start_fw_update)) { + dev_err(dev, + "%s: Failed converting from str to u32.", __func__); + } + + msg_maxim("start_fw_update %d", start_fw_update); + + max77705_usbc_opcode_rw(g_usbc_data, &read_data, &write_data); + + switch (start_fw_update) { + case 1: + max77705_usbc_opcode_rw(g_usbc_data, &read_data, &write_data); + break; + case 2: + max77705_usbc_opcode_read(g_usbc_data, &read_data); + break; + case 3: + max77705_usbc_opcode_write(g_usbc_data, &write_data); + + break; +#ifdef CONFIG_MAX77705_GRL_ENABLE + case 11: + msg_maxim("SYSTEM MESSAGE GRL COMMAND!!!"); + write_data.opcode = OPCODE_GRL_COMMAND; + write_data.write_data[0] = 0x1; + write_data.write_length = 0x1; + write_data.read_length = 0x2; + max77705_usbc_opcode_write(g_usbc_data, &write_data); +#endif +#ifdef MAX77705_RAM_TEST + case 15: + max77705_verify_ram_bist_write(g_usbc_data); +#endif + break; + default: + break; + } + return size; +} +static DEVICE_ATTR(fw_update, S_IRUGO | S_IWUSR | S_IWGRP, + NULL, max77705_fw_update); + +static struct attribute *max77705_attr[] = { + &dev_attr_fw_update.attr, + NULL, +}; + +static struct attribute_group max77705_attr_grp = { + .attrs = max77705_attr, +}; + +static void max77705_get_version_info(struct max77705_usbc_platform_data *usbc_data) +{ + u8 hw_rev[4] = {0, }; + u8 sw_main[3] = {0, }; +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + u8 sw_boot = 0; +#endif + + max77705_read_reg(usbc_data->muic, REG_UIC_HW_REV, &hw_rev[0]); + max77705_read_reg(usbc_data->muic, REG_UIC_FW_MINOR, &sw_main[1]); + max77705_read_reg(usbc_data->muic, REG_UIC_FW_REV, &sw_main[0]); + + usbc_data->HW_Revision = hw_rev[0]; + usbc_data->FW_Minor_Revision = sw_main[1] & MINOR_VERSION_MASK; + usbc_data->FW_Revision = sw_main[0]; + + /* H/W, Minor, Major, Boot */ + msg_maxim("HW rev is %02Xh, FW rev is %02X.%02X!", + usbc_data->HW_Revision, usbc_data->FW_Revision, usbc_data->FW_Minor_Revision); +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + store_ccic_version(&hw_rev[0], &sw_main[0], &sw_boot); +#endif +} + +static void max77705_init_opcode + (struct max77705_usbc_platform_data *usbc_data, int reset) +{ + struct max77705_platform_data *pdata = usbc_data->max77705_data; + + max77705_usbc_disable_auto_vbus(usbc_data); +#if !IS_ENABLED(CONFIG_SEC_FACTORY) + max77705_usbc_disable_uiden(usbc_data); +#endif + if (pdata && pdata->support_audio) + max77705_usbc_enable_audio(usbc_data); + if (reset) { + max77705_set_enable_alternate_mode(ALTERNATE_MODE_START); + max77705_muic_enable_detecting_short(usbc_data->muic_data); + } +} + +static bool max77705_check_recover_opcode(u8 opcode) +{ + bool ret = false; + + switch (opcode) { + case OPCODE_CCCTRL1_W: + case OPCODE_SAMSUNG_FACTORY_TEST: + case OPCODE_SET_ALTERNATEMODE: + case OPCODE_ENABLE_DETECTING_SHORT: + ret = true; + break; + default: + ret = false; + break; + } + return ret; +} + +static void max77705_recover_opcode + (struct max77705_usbc_platform_data *usbc_data, bool opcode_list[]) +{ + int i; + + for (i = 0; i < OPCODE_NONE; i++) { + if (opcode_list[i]) { + msg_maxim("opcode = 0x%02x", i); + switch (i) { + case OPCODE_CCCTRL1_W: + if (usbc_data->op_ctrl1_w & BIT_CCAudEn) + max77705_usbc_enable_audio(usbc_data); + break; + case OPCODE_SAMSUNG_FACTORY_TEST: + if (usbc_data->auto_vbus_en) + max77705_usbc_enable_auto_vbus(usbc_data); + else + max77705_usbc_disable_auto_vbus(usbc_data); + break; + case OPCODE_SET_ALTERNATEMODE: + max77705_set_enable_alternate_mode + (usbc_data->set_altmode); + break; + case OPCODE_ENABLE_DETECTING_SHORT: + max77705_muic_enable_detecting_short + (usbc_data->muic_data); + break; + default: + break; + } + opcode_list[i] = false; + } + } +} + +void init_usbc_cmd_data(usbc_cmd_data *cmd_data) +{ + cmd_data->opcode = OPCODE_NONE; + cmd_data->prev_opcode = OPCODE_NONE; + cmd_data->response = OPCODE_NONE; + cmd_data->val = REG_NONE; + cmd_data->mask = REG_NONE; + cmd_data->reg = REG_NONE; + cmd_data->noti_cmd = OPCODE_NOTI_NONE; + cmd_data->write_length = 0; + cmd_data->read_length = 0; + cmd_data->seq = 0; + cmd_data->is_uvdm = 0; + memset(cmd_data->write_data, REG_NONE, OPCODE_DATA_LENGTH); + memset(cmd_data->read_data, REG_NONE, OPCODE_DATA_LENGTH); +} + +static void init_usbc_cmd_node(usbc_cmd_node *usbc_cmd_node) +{ + usbc_cmd_data *cmd_data = &(usbc_cmd_node->cmd_data); + + pr_debug("%s:%s\n", "MAX77705", __func__); + + usbc_cmd_node->next = NULL; + + init_usbc_cmd_data(cmd_data); +} + +static void copy_usbc_cmd_data(usbc_cmd_data *from, usbc_cmd_data *to) +{ + to->opcode = from->opcode; + to->response = from->response; + memcpy(to->read_data, from->read_data, OPCODE_DATA_LENGTH); + memcpy(to->write_data, from->write_data, OPCODE_DATA_LENGTH); + to->reg = from->reg; + to->mask = from->mask; + to->val = from->val; + to->seq = from->seq; + to->read_length = from->read_length; + to->write_length = from->write_length; + to->prev_opcode = from->prev_opcode; + to->is_uvdm = from->is_uvdm; +} + +bool is_empty_usbc_cmd_queue(usbc_cmd_queue_t *usbc_cmd_queue) +{ + bool ret = false; + + if (usbc_cmd_queue->front == NULL) + ret = true; + + if (ret) + msg_maxim("usbc_cmd_queue Empty(%c)", ret ? 'T' : 'F'); + + return ret; +} + +void enqueue_usbc_cmd(usbc_cmd_queue_t *usbc_cmd_queue, usbc_cmd_data *cmd_data) +{ + usbc_cmd_node *temp_node = kzalloc(sizeof(usbc_cmd_node), GFP_KERNEL); + + if (!temp_node) { + msg_maxim("failed to allocate usbc command queue"); + return; + } + + init_usbc_cmd_node(temp_node); + + copy_usbc_cmd_data(cmd_data, &(temp_node->cmd_data)); + + if (is_empty_usbc_cmd_queue(usbc_cmd_queue)) { + usbc_cmd_queue->front = temp_node; + usbc_cmd_queue->rear = temp_node; + } else { + usbc_cmd_queue->rear->next = temp_node; + usbc_cmd_queue->rear = temp_node; + } + +#if defined(CONFIG_QCOM_IFPMIC_SUSPEND) + if (g_usbc_data && g_usbc_data->max77705) + g_usbc_data->max77705->is_usbc_queue = 1; +#endif +} + +static void dequeue_usbc_cmd + (usbc_cmd_queue_t *usbc_cmd_queue, usbc_cmd_data *cmd_data) +{ + usbc_cmd_node *temp_node; + + if (is_empty_usbc_cmd_queue(usbc_cmd_queue)) { + msg_maxim("Queue, Empty!"); + return; + } + + temp_node = usbc_cmd_queue->front; + copy_usbc_cmd_data(&(temp_node->cmd_data), cmd_data); + + msg_maxim("Opcode(0x%02x) Response(0x%02x)", cmd_data->opcode, cmd_data->response); + + if (usbc_cmd_queue->front->next == NULL) { + msg_maxim("front->next = NULL"); + usbc_cmd_queue->front = NULL; + } else + usbc_cmd_queue->front = usbc_cmd_queue->front->next; + + if (is_empty_usbc_cmd_queue(usbc_cmd_queue)) + usbc_cmd_queue->rear = NULL; + + kfree(temp_node); +} + +static bool front_usbc_cmd + (usbc_cmd_queue_t *cmd_queue, usbc_cmd_data *cmd_data) +{ + if (is_empty_usbc_cmd_queue(cmd_queue)) { + msg_maxim("Queue, Empty!"); + return false; + } + + copy_usbc_cmd_data(&(cmd_queue->front->cmd_data), cmd_data); + msg_maxim("Opcode(0x%02x)", cmd_data->opcode); + return true; +} + +static bool is_usbc_notifier_opcode(u8 opcode) +{ + bool noti = false; + + return noti; +} + +#if defined(CONFIG_QCOM_IFPMIC_SUSPEND) +bool check_usbc_opcode_queue(void) +{ + struct max77705_usbc_platform_data *usbpd_data = g_usbc_data; + usbc_cmd_queue_t *cmd_queue = NULL; + bool ret = true; + + if (usbpd_data == NULL) + goto err; + + cmd_queue = &(usbpd_data->usbc_cmd_queue); + + if (cmd_queue == NULL) + goto err; + + ret = is_empty_usbc_cmd_queue(cmd_queue); + +err: + return ret; +} +EXPORT_SYMBOL(check_usbc_opcode_queue); +#endif + +/* + * max77705_i2c_opcode_write - SMBus "opcode write" protocol + * @chip: max77705 platform data + * @command: OPcode + * @values: Byte array into which data will be read; big enough to hold + * the data returned by the slave. + * + * This executes the SMBus "opcode read" protocol, returning negative errno + * else the number of data bytes in the slave's response. + */ +int max77705_i2c_opcode_write(struct max77705_usbc_platform_data *usbc_data, + u8 opcode, u8 length, u8 *values) +{ + u8 write_values[OPCODE_MAX_LENGTH] = { 0, }; + int ret = 0; + + if (length > OPCODE_DATA_LENGTH) + return -EMSGSIZE; + + write_values[0] = opcode; + if (length) + memcpy(&write_values[1], values, length); + +#if 0 + int i = 0; // To use this, move int i to the top to avoid build error + for (i = 0; i < length + OPCODE_SIZE; i++) + msg_maxim("[%d], 0x[%x]", i, write_values[i]); +#else + msg_maxim("opcode 0x%x, write_length %d", + opcode, length + OPCODE_SIZE); + print_hex_dump(KERN_INFO, "max77705: opcode_write: ", + DUMP_PREFIX_OFFSET, 16, 1, write_values, + length + OPCODE_SIZE, false); +#endif + + /* Write opcode and data */ + ret = max77705_bulk_write(usbc_data->muic, OPCODE_WRITE, + length + OPCODE_SIZE, write_values); + /* Write end of data by 0x00 */ + if (length < OPCODE_DATA_LENGTH) + max77705_write_reg(usbc_data->muic, OPCODE_WRITE_END, 0x00); + + if (opcode == OPCODE_SET_ALTERNATEMODE) + usbc_data->set_altmode_error = ret; + + if (ret == 0) + usbc_data->opcode_stamp = jiffies; + + return ret; +} + +/** + * max77705_i2c_opcode_read - SMBus "opcode read" protocol + * @chip: max77705 platform data + * @command: OPcode + * @values: Byte array into which data will be read; big enough to hold + * the data returned by the slave. + * + * This executes the SMBus "opcode read" protocol, returning negative errno + * else the number of data bytes in the slave's response. + */ +int max77705_i2c_opcode_read(struct max77705_usbc_platform_data *usbc_data, + u8 opcode, u8 length, u8 *values) +{ + int size = 0; + + if (length > OPCODE_DATA_LENGTH) + return -EMSGSIZE; + + /* + * We don't need to use opcode to get any feedback + */ + + /* Read opcode data */ + max77705_bulk_read(usbc_data->muic, OPCODE_READ, OPCODE_SIZE, values); + if (length > 0) + size = max77705_bulk_read(usbc_data->muic, OPCODE_READ + OPCODE_SIZE, + length, values + OPCODE_SIZE); + +#if 0 + int i = 0; // To use this, move int i to the top to avoid build error + for (i = 0; i < length + OPCODE_SIZE; i++) + msg_maxim("[%d], 0x[%x]", i, values[i]); +#else + msg_maxim("opcode 0x%x, read_length %d, ret_error %d", + opcode, length + OPCODE_SIZE, size); + print_hex_dump(KERN_INFO, "max77705: opcode_read: ", + DUMP_PREFIX_OFFSET, 16, 1, values, + length + OPCODE_SIZE, false); +#endif + return size; +} + +static void max77705_notify_execute(struct max77705_usbc_platform_data *usbc_data, + const usbc_cmd_data *cmd_data) +{ + /* to do */ +} + +static void max77705_handle_update_opcode(struct max77705_usbc_platform_data *usbc_data, + const usbc_cmd_data *cmd_data, unsigned char *data) +{ + usbc_cmd_data write_data; + u8 read_value = data[1]; + u8 write_value = (read_value & (~cmd_data->mask)) | (cmd_data->val & cmd_data->mask); + u8 opcode = cmd_data->response + 1; /* write opcode = read opocde + 1 */ + + pr_info("%s: value update [0x%x]->[0x%x] at OPCODE(0x%x)\n", __func__, + read_value, write_value, opcode); + + init_usbc_cmd_data(&write_data); + write_data.opcode = opcode; + write_data.write_length = 1; + write_data.write_data[0] = write_value; + write_data.read_length = 0; + + max77705_usbc_opcode_push(usbc_data, &write_data); +} + +static void max77705_request_response(struct max77705_usbc_platform_data *usbc_data) +{ + usbc_cmd_data value; + + init_usbc_cmd_data(&value); + value.opcode = OPCODE_READ_RESPONSE_FOR_GET_REQUEST; + value.read_length = 28; + max77705_usbc_opcode_push(usbc_data, &value); + + pr_info("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d)\n", + __func__, value.opcode, value.write_length, value.read_length); +} + +#define UNKNOWN_VID 0xFFFF +void max77705_send_get_request(struct max77705_usbc_platform_data *usbc_data, unsigned char *data) +{ + enum { + SENT_REQ_MSG = 0, + ERR_SNK_RDY = 5, + ERR_PD20, + ERR_SNKTXNG, + }; + SEC_PD_SINK_STATUS *snk_sts = &usbc_data->pd_data->pd_noti.sink_status; + + if (data[1] == SENT_REQ_MSG) { + max77705_request_response(usbc_data); + } else { /* ERROR case */ + /* Mark Error in xid */ + snk_sts->xid = (UNKNOWN_VID << 16) | (data[1] << 8); + msg_maxim("%s, Err : %d", __func__, data[1]); + } +} + +void max77705_extend_msg_process(struct max77705_usbc_platform_data *usbc_data, unsigned char *data, + unsigned char len) +{ + SEC_PD_SINK_STATUS *snk_sts = &usbc_data->pd_data->pd_noti.sink_status; + + snk_sts->vid = *(unsigned short *)(data + 2); + snk_sts->pid = *(unsigned short *)(data + 4); + snk_sts->xid = *(unsigned int *)(data + 6); + msg_maxim("%s, %04x, %04x, %08x", + __func__, snk_sts->vid, snk_sts->pid, snk_sts->xid); + if (snk_sts->fp_sec_pd_ext_cb) + snk_sts->fp_sec_pd_ext_cb(snk_sts->vid, snk_sts->pid); +} + +void max77705_read_response(struct max77705_usbc_platform_data *usbc_data, unsigned char *data) +{ + SEC_PD_SINK_STATUS *snk_sts = &usbc_data->pd_data->pd_noti.sink_status; + + switch (data[1] >> 5) { + case OPCODE_GET_SRC_CAP_EXT: + max77705_extend_msg_process(usbc_data, data+2, data[1] & 0x1F); + break; + default: + snk_sts->xid = (UNKNOWN_VID << 16) | data[1]; + msg_maxim("%s, Err : %d", __func__, data[1]); + break; + } +} + +static void max77705_irq_execute(struct max77705_usbc_platform_data *usbc_data, + const usbc_cmd_data *cmd_data) +{ + int len = cmd_data->read_length; + unsigned char data[OPCODE_DATA_LENGTH + OPCODE_SIZE] = {0,}; + u8 response = 0xff; + u8 vdm_opcode_header = 0x0; + UND_DATA_MSG_VDM_HEADER_Type vdm_header; + u8 vdm_command = 0x0; + u8 vdm_type = 0x0; + u8 vdm_response = 0x0; + u8 reqd_vdm_command = 0; + uint8_t W_DATA = 0x0; + + memset(&vdm_header, 0, sizeof(UND_DATA_MSG_VDM_HEADER_Type)); + max77705_i2c_opcode_read(usbc_data, cmd_data->response, + len, data); + + /* opcode identifying the messsage type. (0x51)*/ + response = data[0]; + + if (response != cmd_data->response) { + msg_maxim("Response [0x%02x] != [0x%02x]", + response, cmd_data->response); +#if !defined (MAX77705_GRL_ENABLE) + if (cmd_data->response == OPCODE_FW_OPCODE_CLEAR) { + msg_maxim("Response after FW opcode cleared, just return"); + return; + } +#endif + } + + /* to do(read switch case) */ + switch (response) { + case OPCODE_BCCTRL1_R: + case OPCODE_BCCTRL2_R: + case OPCODE_CTRL1_R: + case OPCODE_CTRL2_R: + case OPCODE_CTRL3_R: + case OPCODE_CCCTRL1_R: + case OPCODE_CCCTRL2_R: + case OPCODE_CCCTRL3_R: + case OPCODE_HVCTRL_R: + case OPCODE_OPCODE_VCONN_ILIM_R: + case OPCODE_CHGIN_ILIM_R: + case OPCODE_CHGIN_ILIM2_R: + if (cmd_data->seq == OPCODE_UPDATE_SEQ) + max77705_handle_update_opcode(usbc_data, cmd_data, data); + break; +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77705_AFC) + case COMMAND_AFC_RESULT_READ: + case COMMAND_QC_2_0_SET: + max77705_muic_handle_detect_dev_hv(usbc_data->muic_data, data); + break; +#endif + case OPCODE_CURRENT_SRCCAP: + max77705_current_pdo(usbc_data, data); + break; + case OPCODE_GET_SRCCAP: + max77705_pdo_list(usbc_data, data); + break; + case OPCODE_SEND_GET_REQUEST: + max77705_send_get_request(usbc_data, data); + break; + case OPCODE_READ_RESPONSE_FOR_GET_REQUEST: + max77705_read_response(usbc_data, data); + break; + case OPCODE_SRCCAP_REQUEST: + /* + * If response of Source_Capablities message is SinkTxNg(0xFE) or Not in Ready State(0xFF) + * It means that the message can not be sent to Port Partner. + * After Attaching Rp 3.0A, send again the message. + */ + if (data[1] == 0xfe || data[1] == 0xff){ + usbc_data->srcccap_request_retry = true; + pr_info("%s : srcccap_request_retry is set\n", __func__); + } + break; + case OPCODE_APDO_SRCCAP_REQUEST: + max77705_response_apdo_request(usbc_data, data); + break; + case OPCODE_SET_PPS: + max77705_response_set_pps(usbc_data, data); + break; + case OPCODE_SAMSUNG_READ_MESSAGE: + pr_info("@TA_ALERT: %s : OPCODE[%x] Data[1] = 0x%x Data[2] = 0x%x Data[3] = 0x%x Data[7] = 0x%x Data[9] = 0x%x\n", + __func__, OPCODE_SAMSUNG_READ_MESSAGE, data[1], data[2], data[3], data[7], data[9]); +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + if ((data[0] == 0x5D) && + /* OCP would be set to Alert or Status message */ + ((data[1] == 0x01 && data[7] == 0x04) || (data[1] == 0x02 && (data[9] & 0x02)))) { + union power_supply_propval value = {0,}; + value.intval = true; + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_DIRECT_TA_ALERT, value); + } +#endif + if ((data[0] == 0x5D) && (data[1] == 0x03) && (data[2] == 0xE8) && (data[3] == 0x04)){ + /* + * Check SVID of enter mode if the value is 0x04e8. + * 0x5D of data[0] means OPCODE_SAMSUNG_READ_MESSAGE. + * When data[0] is 0x5D, 0x03 of data[1] means SVID of enter mode. + */ + max77705_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_ALL, PDIC_NOTIFY_ID_SVID_INFO, + SAMSUNG_VENDOR_ID, 0, 0); + } + break; + case OPCODE_VDM_DISCOVER_GET_VDM_RESP: + max77705_vdm_message_handler(usbc_data, data, len + OPCODE_SIZE); + break; + case OPCODE_READ_SBU: + max77705_response_sbu_read(usbc_data, data); + break; + case OPCODE_READ_CC: + max77705_response_cc_read(usbc_data, data); + break; + case OPCODE_VDM_DISCOVER_SET_VDM_REQ: + vdm_opcode_header = data[1]; + switch (vdm_opcode_header) { + case 0xFF: + msg_maxim("This isn't invalid response(OPCODE : 0x48, HEADER : 0xFF)"); + break; + default: + memcpy(&vdm_header, &data[2], sizeof(vdm_header)); + vdm_type = vdm_header.BITS.VDM_Type; + vdm_command = vdm_header.BITS.VDM_command; + vdm_response = vdm_header.BITS.VDM_command_type; + msg_maxim("vdm_type[%x], vdm_command[%x], vdm_response[%x]", + vdm_type, vdm_command, vdm_response); + switch (vdm_type) { + case STRUCTURED_VDM: + if (vdm_response == SEC_UVDM_RESPONDER_ACK) { + switch (vdm_command) { + case Discover_Identity: + msg_maxim("ignore Discover_Identity"); + break; + case Discover_SVIDs: + msg_maxim("ignore Discover_SVIDs"); + break; + case Discover_Modes: + /* work around. The discover mode irq is not happened */ + if (vdm_header.BITS.Standard_Vendor_ID + == SAMSUNG_VENDOR_ID) { + if (usbc_data->send_enter_mode_req == 0) { + /*Samsung Enter Mode */ + msg_maxim("dex: second enter mode request"); + usbc_data->send_enter_mode_req = 1; + max77705_vdm_process_set_Dex_enter_mode_req(usbc_data); + } + } else + msg_maxim("ignore Discover_Modes"); + break; + case Enter_Mode: + /* work around. The enter mode irq is not happened */ + if (vdm_header.BITS.Standard_Vendor_ID + == SAMSUNG_VENDOR_ID) { + usbc_data->is_samsung_accessory_enter_mode = 1; + msg_maxim("dex mode enter_mode ack status!"); + } else + msg_maxim("ignore Enter_Mode"); + break; + case Exit_Mode: + msg_maxim("ignore Exit_Mode"); + break; + case Attention: + msg_maxim("ignore Attention"); + break; + case Configure: + break; + default: + msg_maxim("vdm_command isn't valid[%x]", vdm_command); + break; + }; + } else if (vdm_response == SEC_UVDM_ININIATOR) { + switch (vdm_command) { + case Attention: + /* Attention message is not able to be received via 0x48 OPCode */ + /* Check requested vdm command and responded vdm command */ + { + /* Read requested vdm command */ + max77705_read_reg(usbc_data->muic, 0x23, &reqd_vdm_command); + reqd_vdm_command &= 0x1F; /* Command bit, b4...0 */ + + if (reqd_vdm_command == Configure) { + W_DATA = 1 << (usbc_data->dp_selected_pin - 1); + /* Retry Configure message */ + msg_maxim("Retry Configure message, W_DATA = %x, dp_selected_pin = %d", + W_DATA, usbc_data->dp_selected_pin); + max77705_vdm_process_set_DP_configure_mode_req(usbc_data, W_DATA); + } + } + break; + case Discover_Identity: + case Discover_SVIDs: + case Discover_Modes: + case Enter_Mode: + case Configure: + default: + /* Nothing */ + break; + }; + } else + msg_maxim("vdm_response is error value[%x]", vdm_response); + break; + case UNSTRUCTURED_VDM: + max77705_sec_unstructured_message_handler(usbc_data, data, len + OPCODE_SIZE); + break; + default: + msg_maxim("vdm_type isn't valid error"); + break; + }; + break; + }; + break; + case OPCODE_SET_ALTERNATEMODE: + usbc_data->max77705->set_altmode_en = 1; + break; + case OPCODE_READ_SELFTEST: + max77705_response_selftest_read(usbc_data, data); + break; +#ifdef CONFIG_MAX77705_GRL_ENABLE + case OPCODE_GRL_COMMAND: + max77705_set_forcetrimi(usbc_data); + break; +#else + case OPCODE_FW_OPCODE_CLEAR: + msg_maxim("Cleared FW OPCODE"); + break; +#endif +#ifdef MAX77705_RAM_TEST + case OPCODE_RAM_TEST_COMMAND: + msg_maxim("MXIM DEBUG : [%x], [%x], [%x]", data[0], data[1], data[2]); + if (data[1] == MAX77705_RAM_TEST_SUCCESS && data[2] == MAX77705_RAM_TEST_SUCCESS) { + msg_maxim("MXIM DEBUG : the RAM Testing is OK"); + usbc_data->ram_test_result = MAX77705_RAM_TEST_RESULT_SUCCESS; + usbc_data->ram_test_retry = 0; + } else { + if (data[1] == MAX77705_RAM_TEST_FAIL && data[2] == MAX77705_RAM_TEST_FAIL) { + msg_maxim("MXIM DEBUG : the RAM Testing is FAIL : [%x], [%x], [%x]",data[0], data[1],data[2]); + usbc_data->ram_test_result = MAX77705_RAM_TEST_RESULT_FAIL_USBC_FUELGAUAGE; + usbc_data->ram_test_retry = 0; + } else if (data[1] == MAX77705_RAM_TEST_SUCCESS && data[2] == MAX77705_RAM_TEST_FAIL) { + msg_maxim("MXIM DEBUG : the USBC RAM Testing is FAIL : [%x], [%x], [%x]",data[0], data[1],data[2]); + usbc_data->ram_test_result = MAX77705_RAM_TEST_RESULT_FAIL_USBC; + usbc_data->ram_test_retry = 0; + } else if (data[1] == MAX77705_RAM_TEST_FAIL && data[2] == MAX77705_RAM_TEST_SUCCESS) { + msg_maxim("MXIM DEBUG : the FG RAM Testing is FAIL : [%x], [%x], [%x]",data[0], data[1],data[2]); + usbc_data->ram_test_result = MAX77705_RAM_TEST_RESULT_FAIL_FUELGAUAGE; + usbc_data->ram_test_retry = 0; + } else { + msg_maxim("MXIM DEBUG : the RAM Testing is WRONG : [%x], [%x], [%x], [%x], [%x],mode: [%x], cnt : [%x]", + data[0], data[1],data[2], data[3], data[4], + usbc_data->ram_test_enable,usbc_data->ram_test_retry); + if (usbc_data->ram_test_enable == MAX77705_RAM_TEST_START_MODE) { + usbc_data->ram_test_retry = MAX77705_RAM_TEST_RETRY_COUNT; + usbc_data->ram_test_enable = MAX77705_RAM_TEST_RETRY_MODE; + } + if (!usbc_data->ram_test_retry) { + usbc_data->ram_test_enable = MAX77705_RAM_TEST_STOP_MODE; + usbc_data->ram_test_retry = 0; + msg_maxim("MXIM DEBUG : the RAM Testing is FAIL : [%x], [%x], [%x], [%x], [%x], cnt : [%x]", + data[0], data[1],data[2],data[3], data[4],usbc_data->ram_test_retry); + } + usbc_data->ram_test_retry--; + } + + } + break; +#endif + case OPCODE_CTRLREG3_R: + max77705_control3_read_complete(usbc_data, data); + break; + case OPCODE_SAMSUNG_GPIO5_CONTROL: + max77705_usbc_gpio5_read_complete(usbc_data, data); + break; +#if defined(CONFIG_SUPPORT_SHIP_MODE) + case OPCODE_SAMSUNG_SHIPMODE_EN: + if (data[2] == 0x01) + usbc_data->ship_mode_en = 1; + pr_info("%s: Response SHIPMODE_EN, [0x%x], [0x%x:%s], ship_mode_en(%d)\n", + __func__, data[1], data[2], (data[2] == 0x01) ? "Enable" : "Fail", + usbc_data->ship_mode_en); + break; +#endif + case OPCODE_SET_SRCCAP: + if ((data[1] == 0xff) && (usbc_data->pd_data->src_cap_done == CC_SRC) && + usbc_data->pd_data->psrdy_sent) + max77705_send_new_src_cap_push(g_usbc_data, + usbc_data->pd_data->auth_type, usbc_data->pd_data->d2d_type); + break; + case OPCODE_SNK_SELECTED_PDO: + if (usbc_data->pd_data->src_cap_done == CC_SRC) + max77705_response_req_pdo(usbc_data, data); + break; + default: + break; + } +} + +void max77705_usbc_dequeue_queue(struct max77705_usbc_platform_data *usbc_data) +{ + usbc_cmd_data cmd_data; + usbc_cmd_queue_t *cmd_queue = NULL; + + cmd_queue = &(usbc_data->usbc_cmd_queue); + + init_usbc_cmd_data(&cmd_data); + + if (is_empty_usbc_cmd_queue(cmd_queue)) { + msg_maxim("Queue, Empty"); + return; + } + + dequeue_usbc_cmd(cmd_queue, &cmd_data); + msg_maxim("!! Dequeue queue : opcode : %x, 1st data : %x. 2st data : %x", + cmd_data.write_data[0], + cmd_data.read_data[0], + cmd_data.val); +} + +#if !defined (MAX77705_GRL_ENABLE) +static void max77705_usbc_clear_fw_queue(struct max77705_usbc_platform_data *usbc_data) +{ + usbc_cmd_data write_data; + + msg_maxim("called"); + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_FW_OPCODE_CLEAR; + max77705_usbc_opcode_write(usbc_data, &write_data); +} +#endif + +void max77705_usbc_clear_queue(struct max77705_usbc_platform_data *usbc_data) +{ + usbc_cmd_data cmd_data; + usbc_cmd_queue_t *cmd_queue = NULL; + + mutex_lock(&usbc_data->op_lock); + msg_maxim("IN"); + cmd_queue = &(usbc_data->usbc_cmd_queue); + + while (!is_empty_usbc_cmd_queue(cmd_queue)) { + init_usbc_cmd_data(&cmd_data); + dequeue_usbc_cmd(cmd_queue, &cmd_data); + if (max77705_check_recover_opcode(cmd_data.opcode)) + usbc_data->recover_opcode_list[cmd_data.opcode] + = usbc_data->need_recover = true; + } + usbc_data->opcode_stamp = 0; + msg_maxim("OUT"); + mutex_unlock(&usbc_data->op_lock); +#if !defined (MAX77705_GRL_ENABLE) + /* also clear fw opcode queue to sync with driver */ + max77705_usbc_clear_fw_queue(usbc_data); +#endif +} + +static void max77705_usbc_cmd_run(struct max77705_usbc_platform_data *usbc_data) +{ + usbc_cmd_queue_t *cmd_queue = NULL; + usbc_cmd_node *run_node; + usbc_cmd_data cmd_data; + int ret = 0; + + cmd_queue = &(usbc_data->usbc_cmd_queue); + + + run_node = kzalloc(sizeof(usbc_cmd_node), GFP_KERNEL); + if (!run_node) { + msg_maxim("failed to allocate muic command queue"); + return; + } + + init_usbc_cmd_node(run_node); + + init_usbc_cmd_data(&cmd_data); + + if (is_empty_usbc_cmd_queue(cmd_queue)) { + msg_maxim("Queue, Empty"); + kfree(run_node); + return; + } + + dequeue_usbc_cmd(cmd_queue, &cmd_data); + + if (is_usbc_notifier_opcode(cmd_data.opcode)) { + max77705_notify_execute(usbc_data, &cmd_data); + max77705_usbc_cmd_run(usbc_data); + } else if (cmd_data.opcode == OPCODE_NONE) {/* Apcmdres isr */ + msg_maxim("Apcmdres ISR !!!"); + max77705_irq_execute(usbc_data, &cmd_data); + usbc_data->opcode_stamp = 0; + max77705_usbc_cmd_run(usbc_data); + } else { /* No ISR */ + msg_maxim("No ISR"); + copy_usbc_cmd_data(&cmd_data, &(usbc_data->last_opcode)); + ret = max77705_i2c_opcode_write(usbc_data, cmd_data.opcode, + cmd_data.write_length, cmd_data.write_data); + if (ret < 0) { + msg_maxim("i2c write fail. dequeue opcode"); + max77705_usbc_dequeue_queue(usbc_data); + } + } + kfree(run_node); +} + +int max77705_usbc_opcode_write(struct max77705_usbc_platform_data *usbc_data, + usbc_cmd_data *write_op) +{ + struct max77705_dev *max77705 = usbc_data->max77705; + usbc_cmd_queue_t *cmd_queue = &(usbc_data->usbc_cmd_queue); + usbc_cmd_data execute_cmd_data; + usbc_cmd_data current_cmd; + + if (max77705->suspended == true) { + msg_maxim("max77705 in suspend state, just return"); + return -EACCES; + } + + mutex_lock(&usbc_data->op_lock); + init_usbc_cmd_data(¤t_cmd); + + /* the messages sent to USBC. */ + init_usbc_cmd_data(&execute_cmd_data); + execute_cmd_data.opcode = write_op->opcode; + execute_cmd_data.write_length = write_op->write_length; + execute_cmd_data.is_uvdm = write_op->is_uvdm; + memcpy(execute_cmd_data.write_data, write_op->write_data, OPCODE_DATA_LENGTH); + execute_cmd_data.seq = OPCODE_WRITE_SEQ; + enqueue_usbc_cmd(cmd_queue, &execute_cmd_data); + + /* the messages recevied From USBC. */ + init_usbc_cmd_data(&execute_cmd_data); + execute_cmd_data.response = write_op->opcode; + execute_cmd_data.read_length = write_op->read_length; + execute_cmd_data.is_uvdm = write_op->is_uvdm; + execute_cmd_data.seq = OPCODE_WRITE_SEQ; + enqueue_usbc_cmd(cmd_queue, &execute_cmd_data); + + msg_maxim("W->W opcode[0x%02x] write_length[%d] read_length[%d]", + write_op->opcode, write_op->write_length, write_op->read_length); + + front_usbc_cmd(cmd_queue, ¤t_cmd); + if (current_cmd.opcode == write_op->opcode) + max77705_usbc_cmd_run(usbc_data); + else { + msg_maxim("!!!current_cmd.opcode [0x%02x][0x%02x], read_op->opcode[0x%02x]", + current_cmd.opcode, current_cmd.response, write_op->opcode); + if (usbc_data->opcode_stamp != 0 && current_cmd.opcode == OPCODE_NONE) { + if (time_after(jiffies, + usbc_data->opcode_stamp + MAX77705_MAX_APDCMD_TIME)) { + usbc_data->opcode_stamp = 0; + msg_maxim("error. we will dequeue response data"); + max77705_usbc_dequeue_queue(usbc_data); + max77705_usbc_cmd_run(usbc_data); + } + } + } + mutex_unlock(&usbc_data->op_lock); + + return 0; +} + +int max77705_usbc_opcode_read(struct max77705_usbc_platform_data *usbc_data, + usbc_cmd_data *read_op) +{ + struct max77705_dev *max77705 = usbc_data->max77705; + usbc_cmd_queue_t *cmd_queue = &(usbc_data->usbc_cmd_queue); + usbc_cmd_data execute_cmd_data; + usbc_cmd_data current_cmd; + + if (max77705->suspended == true) { + msg_maxim("max77705 in suspend state, just return"); + return -EACCES; + } + + mutex_lock(&usbc_data->op_lock); + init_usbc_cmd_data(¤t_cmd); + + /* the messages sent to USBC. */ + init_usbc_cmd_data(&execute_cmd_data); + execute_cmd_data.opcode = read_op->opcode; + execute_cmd_data.write_length = read_op->write_length; + execute_cmd_data.is_uvdm = read_op->is_uvdm; + memcpy(execute_cmd_data.write_data, read_op->write_data, read_op->write_length); + execute_cmd_data.seq = OPCODE_READ_SEQ; + enqueue_usbc_cmd(cmd_queue, &execute_cmd_data); + + /* the messages recevied From USBC. */ + init_usbc_cmd_data(&execute_cmd_data); + execute_cmd_data.response = read_op->opcode; + execute_cmd_data.read_length = read_op->read_length; + execute_cmd_data.is_uvdm = read_op->is_uvdm; + execute_cmd_data.seq = OPCODE_READ_SEQ; + enqueue_usbc_cmd(cmd_queue, &execute_cmd_data); + + msg_maxim("R->R opcode[0x%02x] write_length[%d] read_length[%d]", + read_op->opcode, read_op->write_length, read_op->read_length); + + front_usbc_cmd(cmd_queue, ¤t_cmd); + if (current_cmd.opcode == read_op->opcode) + max77705_usbc_cmd_run(usbc_data); + else { + msg_maxim("!!!current_cmd.opcode [0x%02x][0x%02x], read_op->opcode[0x%02x]", + current_cmd.opcode, current_cmd.response, read_op->opcode); + if (usbc_data->opcode_stamp != 0 && current_cmd.opcode == OPCODE_NONE) { + if (time_after(jiffies, + usbc_data->opcode_stamp + MAX77705_MAX_APDCMD_TIME)) { + usbc_data->opcode_stamp = 0; + msg_maxim("error. we will dequeue response data"); + max77705_usbc_dequeue_queue(usbc_data); + max77705_usbc_cmd_run(usbc_data); + } + } + } + + mutex_unlock(&usbc_data->op_lock); + + return 0; +} + +int max77705_usbc_opcode_update(struct max77705_usbc_platform_data *usbc_data, + usbc_cmd_data *update_op) +{ + struct max77705_dev *max77705 = usbc_data->max77705; + usbc_cmd_queue_t *cmd_queue = &(usbc_data->usbc_cmd_queue); + usbc_cmd_data execute_cmd_data; + usbc_cmd_data current_cmd; + + if (max77705->suspended == true) { + msg_maxim("max77705 in suspend state, just return"); + return -EACCES; + } + + switch (update_op->opcode) { + case OPCODE_BCCTRL1_R: + case OPCODE_BCCTRL2_R: + case OPCODE_CTRL1_R: + case OPCODE_CTRL2_R: + case OPCODE_CTRL3_R: + case OPCODE_CCCTRL1_R: + case OPCODE_CCCTRL2_R: + case OPCODE_CCCTRL3_R: + case OPCODE_HVCTRL_R: + case OPCODE_OPCODE_VCONN_ILIM_R: + case OPCODE_CHGIN_ILIM_R: + case OPCODE_CHGIN_ILIM2_R: + break; + default: + pr_err("%s: invalid usage(0x%x), return\n", __func__, update_op->opcode); + return -EINVAL; + } + + mutex_lock(&usbc_data->op_lock); + init_usbc_cmd_data(¤t_cmd); + + /* the messages sent to USBC. */ + init_usbc_cmd_data(&execute_cmd_data); + execute_cmd_data.opcode = update_op->opcode; + execute_cmd_data.write_length = 0; + execute_cmd_data.is_uvdm = update_op->is_uvdm; + memcpy(execute_cmd_data.write_data, update_op->write_data, update_op->write_length); + execute_cmd_data.seq = OPCODE_UPDATE_SEQ; + enqueue_usbc_cmd(cmd_queue, &execute_cmd_data); + + /* the messages recevied From USBC. */ + init_usbc_cmd_data(&execute_cmd_data); + execute_cmd_data.response = update_op->opcode; + execute_cmd_data.read_length = 1; + execute_cmd_data.seq = OPCODE_UPDATE_SEQ; + execute_cmd_data.val = update_op->val; + execute_cmd_data.mask = update_op->mask; + execute_cmd_data.is_uvdm = update_op->is_uvdm; + enqueue_usbc_cmd(cmd_queue, &execute_cmd_data); + + msg_maxim("U->U opcode[0x%02x] write_length[%d] read_length[%d]", + update_op->opcode, update_op->write_length, update_op->read_length); + + front_usbc_cmd(cmd_queue, ¤t_cmd); + if (current_cmd.opcode == update_op->opcode) + max77705_usbc_cmd_run(usbc_data); + else { + msg_maxim("!!! current_cmd.opcode [0x%02x], update_op->opcode[0x%02x]", + current_cmd.opcode, update_op->opcode); + if (usbc_data->opcode_stamp != 0 && current_cmd.opcode == OPCODE_NONE) { + if (time_after(jiffies, + usbc_data->opcode_stamp + MAX77705_MAX_APDCMD_TIME)) { + usbc_data->opcode_stamp = 0; + msg_maxim("error. we will dequeue response data"); + max77705_usbc_dequeue_queue(usbc_data); + max77705_usbc_cmd_run(usbc_data); + } + } + } + + mutex_unlock(&usbc_data->op_lock); + + return 0; +} + +int max77705_usbc_opcode_push(struct max77705_usbc_platform_data *usbc_data, + usbc_cmd_data *read_op) +{ + struct max77705_dev *max77705 = usbc_data->max77705; + usbc_cmd_queue_t *cmd_queue = &(usbc_data->usbc_cmd_queue); + usbc_cmd_data execute_cmd_data; + usbc_cmd_data current_cmd; + + if (max77705->suspended == true) { + msg_maxim("max77705 in suspend state, just return"); + return -EACCES; + } + + init_usbc_cmd_data(¤t_cmd); + + /* the messages sent to USBC. */ + init_usbc_cmd_data(&execute_cmd_data); + execute_cmd_data.opcode = read_op->opcode; + execute_cmd_data.write_length = read_op->write_length; + execute_cmd_data.is_uvdm = read_op->is_uvdm; + memcpy(execute_cmd_data.write_data, read_op->write_data, read_op->write_length); + execute_cmd_data.seq = OPCODE_PUSH_SEQ; + enqueue_usbc_cmd(cmd_queue, &execute_cmd_data); + + /* the messages recevied From USBC. */ + init_usbc_cmd_data(&execute_cmd_data); + execute_cmd_data.response = read_op->opcode; + execute_cmd_data.read_length = read_op->read_length; + execute_cmd_data.is_uvdm = read_op->is_uvdm; + execute_cmd_data.seq = OPCODE_PUSH_SEQ; + enqueue_usbc_cmd(cmd_queue, &execute_cmd_data); + + msg_maxim("P->P opcode[0x%02x] write_length[%d] read_length[%d]", + read_op->opcode, read_op->write_length, read_op->read_length); + + return 0; +} + +int max77705_usbc_opcode_rw(struct max77705_usbc_platform_data *usbc_data, + usbc_cmd_data *read_op, usbc_cmd_data *write_op) +{ + struct max77705_dev *max77705 = usbc_data->max77705; + usbc_cmd_queue_t *cmd_queue = &(usbc_data->usbc_cmd_queue); + usbc_cmd_data execute_cmd_data; + usbc_cmd_data current_cmd; + + if (max77705->suspended == true) { + msg_maxim("max77705 in suspend state, just return"); + return -EACCES; + } + + mutex_lock(&usbc_data->op_lock); + init_usbc_cmd_data(¤t_cmd); + + /* the messages sent to USBC. */ + init_usbc_cmd_data(&execute_cmd_data); + execute_cmd_data.opcode = read_op->opcode; + execute_cmd_data.write_length = read_op->write_length; + execute_cmd_data.is_uvdm = read_op->is_uvdm; + memcpy(execute_cmd_data.write_data, read_op->write_data, read_op->write_length); + execute_cmd_data.seq = OPCODE_RW_SEQ; + enqueue_usbc_cmd(cmd_queue, &execute_cmd_data); + + /* the messages recevied From USBC. */ + init_usbc_cmd_data(&execute_cmd_data); + execute_cmd_data.response = read_op->opcode; + execute_cmd_data.read_length = read_op->read_length; + execute_cmd_data.is_uvdm = read_op->is_uvdm; + execute_cmd_data.seq = OPCODE_RW_SEQ; + enqueue_usbc_cmd(cmd_queue, &execute_cmd_data); + + /* the messages sent to USBC. */ + init_usbc_cmd_data(&execute_cmd_data); + execute_cmd_data.opcode = write_op->opcode; + execute_cmd_data.write_length = write_op->write_length; + execute_cmd_data.is_uvdm = write_op->is_uvdm; + memcpy(execute_cmd_data.write_data, write_op->write_data, OPCODE_DATA_LENGTH); + execute_cmd_data.seq = OPCODE_RW_SEQ; + enqueue_usbc_cmd(cmd_queue, &execute_cmd_data); + + /* the messages recevied From USBC. */ + init_usbc_cmd_data(&execute_cmd_data); + execute_cmd_data.response = write_op->opcode; + execute_cmd_data.read_length = write_op->read_length; + execute_cmd_data.is_uvdm = write_op->is_uvdm; + execute_cmd_data.seq = OPCODE_RW_SEQ; + enqueue_usbc_cmd(cmd_queue, &execute_cmd_data); + + msg_maxim("RW->R opcode[0x%02x] write_length[%d] read_length[%d]", + read_op->opcode, read_op->write_length, read_op->read_length); + msg_maxim("RW->W opcode[0x%02x] write_length[%d] read_length[%d]", + write_op->opcode, write_op->write_length, write_op->read_length); + + front_usbc_cmd(cmd_queue, ¤t_cmd); + if (current_cmd.opcode == read_op->opcode) + max77705_usbc_cmd_run(usbc_data); + else { + msg_maxim("!!! current_cmd.opcode [0x%02x], read_op->opcode[0x%02x]", + current_cmd.opcode, read_op->opcode); + if (usbc_data->opcode_stamp != 0 && current_cmd.opcode == OPCODE_NONE) { + if (time_after(jiffies, + usbc_data->opcode_stamp + MAX77705_MAX_APDCMD_TIME)) { + usbc_data->opcode_stamp = 0; + msg_maxim("error. we will dequeue response data"); + max77705_usbc_dequeue_queue(usbc_data); + max77705_usbc_cmd_run(usbc_data); + } + } + } + + mutex_unlock(&usbc_data->op_lock); + + return 0; +} + +/* + * max77705_uic_op_send_work_func func - Send OPCode + * @work: work_struct of max77705_i2c + * + * Wait for OPCode response + */ +static void max77705_uic_op_send_work_func( + struct work_struct *work) +{ + struct max77705_usbc_platform_data *usbc_data; + struct max77705_opcode opcodes[] = { + { + .opcode = OPCODE_BCCTRL1_R, + .data = { 0, }, + .write_length = 0, + .read_length = 1, + }, + { + .opcode = OPCODE_BCCTRL1_W, + .data = { 0x20, }, + .write_length = 1, + .read_length = 0, + }, + { + .opcode = OPCODE_BCCTRL2_R, + .data = { 0, }, + .write_length = 0, + .read_length = 1, + }, + { + .opcode = OPCODE_BCCTRL2_W, + .data = { 0x10, }, + .write_length = 1, + .read_length = 0, + }, + { + .opcode = OPCODE_CURRENT_SRCCAP, + .data = { 0, }, + .write_length = 1, + .read_length = 4, + }, + { + .opcode = OPCODE_AFC_HV_W, + .data = { 0x46, }, + .write_length = 1, + .read_length = 2, + }, + { + .opcode = OPCODE_SRCCAP_REQUEST, + .data = { 0x00, }, + .write_length = 1, + .read_length = 32, + }, + }; + int op_loop; + + usbc_data = container_of(work, struct max77705_usbc_platform_data, op_send_work); + + msg_maxim("IN"); + for (op_loop = 0; op_loop < ARRAY_SIZE(opcodes); op_loop++) { + if (usbc_data->op_code == opcodes[op_loop].opcode) { + max77705_i2c_opcode_write(usbc_data, opcodes[op_loop].opcode, + opcodes[op_loop].write_length, opcodes[op_loop].data); + break; + } + } + msg_maxim("OUT"); +} + +static void max77705_reset_ic(struct max77705_usbc_platform_data *usbc_data) +{ + struct max77705_dev *max77705 = usbc_data->max77705; + + //gurantee to block i2c trasaction during ccic reset + mutex_lock(&max77705->i2c_lock); + max77705_write_reg_nolock(usbc_data->muic, 0x80, 0x0F); +#if defined(CONFIG_IFPMIC_CRC_CHECK) + msleep(300); +#else + msleep(100); /* need 100ms delay */ +#endif + mutex_unlock(&max77705->i2c_lock); +} + +void max77705_usbc_check_sysmsg(struct max77705_usbc_platform_data *usbc_data, u8 sysmsg) +{ + usbc_cmd_queue_t *cmd_queue = &(usbc_data->usbc_cmd_queue); + bool is_empty_queue = is_empty_usbc_cmd_queue(cmd_queue); + usbc_cmd_data cmd_data; + usbc_cmd_data next_cmd_data; + u8 next_opcode = 0xFF; + u8 interrupt; +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + int event; +#endif + int ret = 0; + + if (usbc_data->shut_down) { + msg_maxim("IGNORE SYSTEM_MSG IN SHUTDOWN MODE!!"); + return; + } + + switch (sysmsg) { + case SYSERROR_NONE: + break; + case SYSERROR_BOOT_WDT: + usbc_data->watchdog_count++; + msg_maxim("SYSERROR_BOOT_WDT: %d", usbc_data->watchdog_count); + max77705_usbc_mask_irq(usbc_data); + max77705_write_reg(usbc_data->muic, REG_UIC_INT_M, REG_UIC_INT_M_INIT); + max77705_write_reg(usbc_data->muic, REG_CC_INT_M, REG_CC_INT_M_INIT); + max77705_write_reg(usbc_data->muic, REG_PD_INT_M, REG_PD_INT_M_INIT); + max77705_write_reg(usbc_data->muic, REG_VDM_INT_M, REG_VDM_INT_M_INIT); + /* clear UIC_INT to prevent infinite sysmsg irq*/ + g_usbc_data->max77705->enable_nested_irq = 1; + max77705_read_reg(usbc_data->muic, MAX77705_USBC_REG_UIC_INT, &interrupt); + g_usbc_data->max77705->usbc_irq = interrupt & 0xBF; //clear the USBC SYSTEM IRQ + max77705_usbc_clear_queue(usbc_data); + usbc_data->is_first_booting = 1; + usbc_data->pd_data->bPPS_on = false; + max77705_init_opcode(usbc_data, 1); + max77705_usbc_umask_irq(usbc_data); +#ifdef MAX77705_RAM_TEST + if(usbc_data->ram_test_enable == MAX77705_RAM_TEST_RETRY_MODE) { + mdelay(100); + max77705_verify_ram_bist_write(usbc_data); + } else { + usbc_data->ram_test_enable = MAX77705_RAM_TEST_STOP_MODE; + } +#endif +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_SYSERROR_BOOT_WDT; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + break; + case SYSERROR_BOOT_SWRSTREQ: + break; + case SYSMSG_BOOT_POR: + usbc_data->por_count++; + max77705_usbc_mask_irq(usbc_data); + max77705_reset_ic(usbc_data); + max77705_write_reg(usbc_data->muic, REG_UIC_INT_M, REG_UIC_INT_M_INIT); + max77705_write_reg(usbc_data->muic, REG_CC_INT_M, REG_CC_INT_M_INIT); + max77705_write_reg(usbc_data->muic, REG_PD_INT_M, REG_PD_INT_M_INIT); + max77705_write_reg(usbc_data->muic, REG_VDM_INT_M, REG_VDM_INT_M_INIT); + /* clear UIC_INT to prevent infinite sysmsg irq*/ + g_usbc_data->max77705->enable_nested_irq = 1; + max77705_read_reg(usbc_data->muic, MAX77705_USBC_REG_UIC_INT, &interrupt); + g_usbc_data->max77705->usbc_irq = interrupt & 0xBF; //clear the USBC SYSTEM IRQ + msg_maxim("SYSERROR_BOOT_POR: %d, UIC_INT:0x%02x", usbc_data->por_count, interrupt); + max77705_usbc_clear_queue(usbc_data); + usbc_data->is_first_booting = 1; + usbc_data->pd_data->bPPS_on = false; + max77705_init_opcode(usbc_data, 1); + max77705_usbc_umask_irq(usbc_data); +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_SYSMSG_BOOT_POR; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + break; + case SYSERROR_HV_NOVBUS: + break; + case SYSERROR_HV_FMETHOD_RXPERR: + break; + case SYSERROR_HV_FMETHOD_RXBUFOW: + break; + case SYSERROR_HV_FMETHOD_RXTFR: + break; + case SYSERROR_HV_FMETHOD_MPNACK: + break; + case SYSERROR_HV_FMETHOD_RESET_FAIL: + break; + case SYSMsg_AFC_Done: + break; + case SYSERROR_SYSPOS: + break; + case SYSERROR_APCMD_UNKNOWN: + break; + case SYSERROR_APCMD_INPROGRESS: + break; + case SYSERROR_APCMD_FAIL: + + init_usbc_cmd_data(&cmd_data); + init_usbc_cmd_data(&next_cmd_data); + + if (front_usbc_cmd(cmd_queue, &next_cmd_data)) + next_opcode = next_cmd_data.response; + + if (!is_empty_queue) { + copy_usbc_cmd_data(&(usbc_data->last_opcode), &cmd_data); + +#ifdef CONFIG_MAX77705_GRL_ENABLE + if (cmd_data.opcode == OPCODE_GRL_COMMAND || next_opcode == OPCODE_VDM_DISCOVER_SET_VDM_REQ) { +#else + if (next_opcode == OPCODE_VDM_DISCOVER_SET_VDM_REQ) { +#endif + usbc_data->opcode_stamp = 0; + max77705_usbc_dequeue_queue(usbc_data); + cmd_data.opcode = OPCODE_NONE; + } + + if ((cmd_data.opcode != OPCODE_NONE) && (cmd_data.opcode == next_opcode)) { + if (next_opcode != OPCODE_VDM_DISCOVER_SET_VDM_REQ) { + ret = max77705_i2c_opcode_write(usbc_data, + cmd_data.opcode, + cmd_data.write_length, + cmd_data.write_data); + if (ret) { + msg_maxim("i2c write fail. dequeue opcode"); + max77705_usbc_dequeue_queue(usbc_data); + } else + msg_maxim("RETRY SUCCESS : %x, %x", cmd_data.opcode, next_opcode); + } else + msg_maxim("IGNORE COMMAND : %x, %x", cmd_data.opcode, next_opcode); + } else { + msg_maxim("RETRY FAILED : %x, %x", cmd_data.opcode, next_opcode); + } + + } + +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77705_AFC) + max77705_muic_disable_afc_protocol(usbc_data->muic_data); +#endif + /* TO DO DEQEUE MSG. */ + break; +#ifdef CONFIG_MAX77705_GRL_ENABLE + case SYSMSG_SET_GRL: + max77705_usbc_clear_queue(usbc_data); + msg_maxim("SYSTEM MESSAGE GRL COMMAND!!!"); + + init_usbc_cmd_data(&cmd_data); + cmd_data.opcode = OPCODE_GRL_COMMAND; + cmd_data.write_data[0] = 0x1; + cmd_data.write_length = 0x1; + cmd_data.read_length = 0x2; + max77705_usbc_opcode_write(usbc_data, &cmd_data); +// max77705_set_forcetrimi(usbc_data); + + break; +#endif + case SYSMSG_CCx_5V_SHORT: + msg_maxim("CC-VBUS SHORT"); +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_VBUS_CC_SHORT_COUNT); +#endif +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_SYSMSG_CC_SHORT; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + usbc_data->cc_data->ccistat = CCI_SHORT; + max77705_notify_rp_current_level(usbc_data); + break; + case SYSMSG_SBUx_GND_SHORT: + msg_maxim("SBU-GND SHORT"); +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_GND_SBU_SHORT_COUNT); +#endif +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_SYSMSG_SBU_GND_SHORT; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + break; + case SYSMSG_SBUx_5V_SHORT: + msg_maxim("SBU-VBUS SHORT"); +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_VBUS_SBU_SHORT_COUNT); +#endif +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_SYSMSG_SBU_VBUS_SHORT; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + break; + case SYSMSG_PD_CCx_5V_SHORT: + msg_maxim("PD_CC-VBUS SHORT"); + usbc_data->pd_data->cc_sbu_short = true; +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_VBUS_CC_SHORT_COUNT); +#endif +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_SYSMSG_CC_SHORT; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + break; + case SYSMSG_PD_SBUx_5V_SHORT: + msg_maxim("PD_SBU-VBUS SHORT"); + usbc_data->pd_data->cc_sbu_short = true; +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_VBUS_SBU_SHORT_COUNT); +#endif +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_SYSMSG_SBU_VBUS_SHORT; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + break; + case SYSMSG_PD_SHORT_NONE: + msg_maxim("Cable detach"); + usbc_data->pd_data->cc_sbu_short = false; + break; + case SYSERROR_DROP5V_SRCRDY: + msg_maxim("vbus drop during source ready"); + break; + case SYSERROR_DROP5V_SNKRDY: + msg_maxim("vbus drop during sink ready"); + break; + case SYSMSG_PD_GENDER_SHORT: + msg_maxim("PD_GENDER_SHORT"); + usbc_data->pd_data->cc_sbu_short = true; + break; + case SYSERROR_POWER_NEGO: + if (!usbc_data->last_opcode.is_uvdm) { /* structured vdm */ + if (usbc_data->last_opcode.opcode == OPCODE_VDM_DISCOVER_SET_VDM_REQ) { + max77705_usbc_opcode_write(usbc_data, &usbc_data->last_opcode); + msg_maxim("SYSMSG PWR NEGO ERR : VDM request retry"); + } + } else { /* unstructured vdm */ + usbc_data->uvdm_error = -EACCES; + msg_maxim("SYSMSG PWR NEGO ERR : UVDM request error - dir : %d", + usbc_data->is_in_sec_uvdm_out); + if (usbc_data->is_in_sec_uvdm_out == DIR_OUT) + complete(&usbc_data->uvdm_longpacket_out_wait); + else if (usbc_data->is_in_sec_uvdm_out == DIR_IN) + complete(&usbc_data->uvdm_longpacket_in_wait); + else + ; + } + break; +#if defined(CONFIG_SEC_FACTORY) + case SYSERROR_FACTORY_RID0: + factory_execute_monitor(FAC_ABNORMAL_REPEAT_RID0); + break; +#endif + case SYSERROR_CCRP_HIGH: + msg_maxim("CCRP HIGH"); +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + if (usbc_data->ccrp_state != 1) { + usbc_data->ccrp_state = 1; + max77705_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_BATT, PDIC_NOTIFY_ID_WATER_CABLE, + PDIC_NOTIFY_ATTACH, 0/*rprd*/, 0); + } +#endif + break; + case SYSERROR_CCRP_LOW: + msg_maxim("CCRP LOW"); +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + if (usbc_data->ccrp_state != 0) { + usbc_data->ccrp_state = 0; + max77705_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_BATT, PDIC_NOTIFY_ID_WATER_CABLE, + PDIC_NOTIFY_DETACH, 0/*rprd*/, 0); + } +#endif + break; + case SYSMSG_10K_TO_22K ... SYSMSG_22K_TO_56K: + msg_maxim("TypeC earphone is attached"); + usbc_data->pd_data->cc_sbu_short = true; + max77705_check_pdo(usbc_data); + break; + case SYSMSG_56K_TO_22K ... SYSMSG_22K_TO_10K: + msg_maxim("TypeC earphone is detached"); + usbc_data->pd_data->cc_sbu_short = false; + max77705_check_pdo(usbc_data); + break; + default: + break; + } +} + + +static irqreturn_t max77705_apcmd_irq(int irq, void *data) +{ + struct max77705_usbc_platform_data *usbc_data = data; + u8 sysmsg = 0; + + msg_maxim("IRQ(%d)_IN", irq); + max77705_read_reg(usbc_data->muic, REG_USBC_STATUS2, &usbc_data->usbc_status2); + sysmsg = usbc_data->usbc_status2; + msg_maxim(" [IN] sysmsg : %d", sysmsg); + + mutex_lock(&usbc_data->op_lock); + max77705_usbc_cmd_run(usbc_data); + mutex_unlock(&usbc_data->op_lock); + + if (usbc_data->need_recover) { + max77705_recover_opcode(usbc_data, + usbc_data->recover_opcode_list); + usbc_data->need_recover = false; + } + + msg_maxim("IRQ(%d)_OUT", irq); + + return IRQ_HANDLED; +} + +static irqreturn_t max77705_sysmsg_irq(int irq, void *data) +{ + struct max77705_usbc_platform_data *usbc_data = data; + u8 sysmsg = 0; + u8 i = 0; + u8 raw_data[3] = {0, }; + u8 usbc_status2 = 0; + u8 dump_reg[10] = {0, }; + + for (i = 0; i < 3; i++) { + usbc_status2 = 0; + max77705_read_reg(usbc_data->muic, REG_USBC_STATUS2, &usbc_status2); + raw_data[i] = usbc_status2; + } + if((raw_data[0] == raw_data[1]) && (raw_data[0] == raw_data[2])){ + sysmsg = raw_data[0]; + } else { + max77705_bulk_read(usbc_data->muic, REG_USBC_STATUS1, + 8, dump_reg); + msg_maxim("[ERROR ]sys_reg, %x, %x, %x", raw_data[0], raw_data[1],raw_data[2]); + msg_maxim("[ERROR ]dump_reg, %x, %x, %x, %x, %x, %x, %x, %x\n", dump_reg[0], dump_reg[1], + dump_reg[2], dump_reg[3], dump_reg[4], dump_reg[5], dump_reg[6], dump_reg[7]); + sysmsg = 0x6D; + } + msg_maxim("IRQ(%d)_IN sysmsg: %x", irq, sysmsg); + max77705_usbc_check_sysmsg(usbc_data, sysmsg); + usbc_data->sysmsg = sysmsg; + msg_maxim("IRQ(%d)_OUT sysmsg: %x", irq, sysmsg); + + return IRQ_HANDLED; +} + + +static irqreturn_t max77705_vdm_identity_irq(int irq, void *data) +{ + struct max77705_usbc_platform_data *usbc_data = data; + MAX77705_VDM_MSG_IRQ_STATUS_Type VDM_MSG_IRQ_State; + + memset(&VDM_MSG_IRQ_State, 0, sizeof(VDM_MSG_IRQ_State)); + msg_maxim("IRQ(%d)_IN", irq); + VDM_MSG_IRQ_State.BITS.Vdm_Flag_Discover_ID = 1; + max77705_receive_alternate_message(usbc_data, &VDM_MSG_IRQ_State); + msg_maxim("IRQ(%d)_OUT", irq); + + return IRQ_HANDLED; +} + +static irqreturn_t max77705_vdm_svids_irq(int irq, void *data) +{ + struct max77705_usbc_platform_data *usbc_data = data; + MAX77705_VDM_MSG_IRQ_STATUS_Type VDM_MSG_IRQ_State; + + memset(&VDM_MSG_IRQ_State, 0, sizeof(VDM_MSG_IRQ_State)); + msg_maxim("IRQ(%d)_IN", irq); + VDM_MSG_IRQ_State.BITS.Vdm_Flag_Discover_SVIDs = 1; + max77705_receive_alternate_message(usbc_data, &VDM_MSG_IRQ_State); + msg_maxim("IRQ(%d)_OUT", irq); + return IRQ_HANDLED; +} + +static irqreturn_t max77705_vdm_discover_mode_irq(int irq, void *data) +{ + struct max77705_usbc_platform_data *usbc_data = data; + MAX77705_VDM_MSG_IRQ_STATUS_Type VDM_MSG_IRQ_State; + + memset(&VDM_MSG_IRQ_State, 0, sizeof(VDM_MSG_IRQ_State)); + msg_maxim("IRQ(%d)_IN", irq); + VDM_MSG_IRQ_State.BITS.Vdm_Flag_Discover_MODEs = 1; + max77705_receive_alternate_message(usbc_data, &VDM_MSG_IRQ_State); + msg_maxim("IRQ(%d)_OUT", irq); + return IRQ_HANDLED; +} + +static irqreturn_t max77705_vdm_enter_mode_irq(int irq, void *data) +{ + struct max77705_usbc_platform_data *usbc_data = data; + MAX77705_VDM_MSG_IRQ_STATUS_Type VDM_MSG_IRQ_State; + + memset(&VDM_MSG_IRQ_State, 0, sizeof(VDM_MSG_IRQ_State)); + msg_maxim("IRQ(%d)_IN", irq); + VDM_MSG_IRQ_State.BITS.Vdm_Flag_Enter_Mode = 1; + max77705_receive_alternate_message(usbc_data, &VDM_MSG_IRQ_State); + msg_maxim("IRQ(%d)_OUT", irq); + return IRQ_HANDLED; +} + +static irqreturn_t max77705_vdm_dp_status_irq(int irq, void *data) +{ + struct max77705_usbc_platform_data *usbc_data = data; + MAX77705_VDM_MSG_IRQ_STATUS_Type VDM_MSG_IRQ_State; + + memset(&VDM_MSG_IRQ_State, 0, sizeof(VDM_MSG_IRQ_State)); + msg_maxim("IRQ(%d)_IN", irq); + VDM_MSG_IRQ_State.BITS.Vdm_Flag_DP_Status_Update = 1; + max77705_receive_alternate_message(usbc_data, &VDM_MSG_IRQ_State); + msg_maxim("IRQ(%d)_OUT", irq); + return IRQ_HANDLED; +} + +static irqreturn_t max77705_vdm_dp_configure_irq(int irq, void *data) +{ + struct max77705_usbc_platform_data *usbc_data = data; + MAX77705_VDM_MSG_IRQ_STATUS_Type VDM_MSG_IRQ_State; + + memset(&VDM_MSG_IRQ_State, 0, sizeof(VDM_MSG_IRQ_State)); + msg_maxim("IRQ(%d)_IN", irq); + VDM_MSG_IRQ_State.BITS.Vdm_Flag_DP_Configure = 1; + max77705_receive_alternate_message(usbc_data, &VDM_MSG_IRQ_State); + msg_maxim("IRQ(%d)_OUT", irq); + return IRQ_HANDLED; +} + +static irqreturn_t max77705_vdm_attention_irq(int irq, void *data) +{ + struct max77705_usbc_platform_data *usbc_data = data; + MAX77705_VDM_MSG_IRQ_STATUS_Type VDM_MSG_IRQ_State; + + memset(&VDM_MSG_IRQ_State, 0, sizeof(VDM_MSG_IRQ_State)); + msg_maxim("IRQ(%d)_IN", irq); + VDM_MSG_IRQ_State.BITS.Vdm_Flag_Attention = 1; + max77705_receive_alternate_message(usbc_data, &VDM_MSG_IRQ_State); + msg_maxim("IRQ(%d)_OUT", irq); + return IRQ_HANDLED; +} + +static irqreturn_t max77705_vir_altmode_irq(int irq, void *data) +{ + struct max77705_usbc_platform_data *usbc_data = data; + + msg_maxim("max77705_vir_altmode_irq"); + + if (usbc_data->shut_down) { + msg_maxim("%s doing shutdown. skip set alternate mode", __func__); + goto skip; + } + + max77705_set_enable_alternate_mode + (usbc_data->set_altmode); + +skip: + return IRQ_HANDLED; +} + +int max77705_init_irq_handler(struct max77705_usbc_platform_data *usbc_data) +{ + int ret = 0; + + usbc_data->irq_apcmd = usbc_data->irq_base + MAX77705_USBC_IRQ_APC_INT; + if (usbc_data->irq_apcmd) { + ret = request_threaded_irq(usbc_data->irq_apcmd, + NULL, max77705_apcmd_irq, + 0, + "usbc-apcmd-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + return ret; + } + } + + usbc_data->irq_sysmsg = usbc_data->irq_base + MAX77705_USBC_IRQ_SYSM_INT; + if (usbc_data->irq_sysmsg) { + ret = request_threaded_irq(usbc_data->irq_sysmsg, + NULL, max77705_sysmsg_irq, + 0, + "usbc-sysmsg-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + return ret; + } + } + + usbc_data->irq_vdm0 = usbc_data->irq_base + MAX77705_IRQ_VDM_DISCOVER_ID_INT; + if (usbc_data->irq_vdm0) { + ret = request_threaded_irq(usbc_data->irq_vdm0, + NULL, max77705_vdm_identity_irq, + 0, + "usbc-vdm0-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + return ret; + } + } + + usbc_data->irq_vdm1 = usbc_data->irq_base + MAX77705_IRQ_VDM_DISCOVER_SVIDS_INT; + if (usbc_data->irq_vdm1) { + ret = request_threaded_irq(usbc_data->irq_vdm1, + NULL, max77705_vdm_svids_irq, + 0, + "usbc-vdm1-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + return ret; + } + } + + usbc_data->irq_vdm2 = usbc_data->irq_base + MAX77705_IRQ_VDM_DISCOVER_MODES_INT; + if (usbc_data->irq_vdm2) { + ret = request_threaded_irq(usbc_data->irq_vdm2, + NULL, max77705_vdm_discover_mode_irq, + 0, + "usbc-vdm2-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + return ret; + } + } + + usbc_data->irq_vdm3 = usbc_data->irq_base + MAX77705_IRQ_VDM_ENTER_MODE_INT; + if (usbc_data->irq_vdm3) { + ret = request_threaded_irq(usbc_data->irq_vdm3, + NULL, max77705_vdm_enter_mode_irq, + 0, + "usbc-vdm3-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + return ret; + } + } + + usbc_data->irq_vdm4 = usbc_data->irq_base + MAX77705_IRQ_VDM_DP_STATUS_UPDATE_INT; + if (usbc_data->irq_vdm4) { + ret = request_threaded_irq(usbc_data->irq_vdm4, + NULL, max77705_vdm_dp_status_irq, + 0, + "usbc-vdm4-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + return ret; + } + } + + usbc_data->irq_vdm5 = usbc_data->irq_base + MAX77705_IRQ_VDM_DP_CONFIGURE_INT; + if (usbc_data->irq_vdm5) { + ret = request_threaded_irq(usbc_data->irq_vdm5, + NULL, max77705_vdm_dp_configure_irq, + 0, + "usbc-vdm5-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + return ret; + } + } + + usbc_data->irq_vdm6 = usbc_data->irq_base + MAX77705_IRQ_VDM_ATTENTION_INT; + if (usbc_data->irq_vdm6) { + ret = request_threaded_irq(usbc_data->irq_vdm6, + NULL, max77705_vdm_attention_irq, + 0, + "usbc-vdm6-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + return ret; + } + } + + usbc_data->irq_vir0 = usbc_data->irq_base + MAX77705_VIR_IRQ_ALTERROR_INT; + if (usbc_data->irq_vir0) { + ret = request_threaded_irq(usbc_data->irq_vir0, + NULL, max77705_vir_altmode_irq, + 0, + "usbc-vir0-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + return ret; + } + } + + return ret; +} + +static void max77705_usbc_umask_irq(struct max77705_usbc_platform_data *usbc_data) +{ + int ret = 0; + u8 i2c_data = 0; + /* Unmask max77705 interrupt */ + ret = max77705_read_reg(usbc_data->i2c, 0x23, + &i2c_data); + if (ret) { + pr_err("%s fail to read muic reg\n", __func__); + return; + } + + i2c_data &= ~((1 << 3)); /* Unmask muic interrupt */ + max77705_write_reg(usbc_data->i2c, 0x23, + i2c_data); +} +static void max77705_usbc_mask_irq(struct max77705_usbc_platform_data *usbc_data) +{ + int ret = 0; + u8 i2c_data = 0; + /* Unmask max77705 interrupt */ + ret = max77705_read_reg(usbc_data->i2c, 0x23, + &i2c_data); + if (ret) { + pr_err("%s fail to read muic reg\n", __func__); + return; + } + + i2c_data |= ((1 << 3)); /* Unmask muic interrupt */ + max77705_write_reg(usbc_data->i2c, 0x23, + i2c_data); +} + +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) +static int pdic_handle_usb_external_notifier_notification(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct max77705_usbc_platform_data *usbpd_data = g_usbc_data; + int ret = 0; + int enable = *(int *)data; + + pr_info("%s : action=%lu , enable=%d\n", __func__, action, enable); + switch (action) { + case EXTERNAL_NOTIFY_HOSTBLOCK_PRE: + if (enable) { + max77705_set_enable_alternate_mode(ALTERNATE_MODE_STOP); + if (usbpd_data->dp_is_connect) + max77705_dp_detach(usbpd_data); + } else { + if (usbpd_data->dp_is_connect) + max77705_dp_detach(usbpd_data); + } + break; + case EXTERNAL_NOTIFY_HOSTBLOCK_POST: + if (enable) { + } else { + max77705_set_enable_alternate_mode(ALTERNATE_MODE_START); + } + break; + case EXTERNAL_NOTIFY_DEVICEADD: + if (enable) { + usbpd_data->device_add = 1; + wake_up_interruptible(&usbpd_data->device_add_wait_q); + } + break; + case EXTERNAL_NOTIFY_MDMBLOCK_PRE: + if (enable && usbpd_data->dp_is_connect) { + usbpd_data->mdm_block = 1; + max77705_dp_detach(usbpd_data); + } + break; + default: + break; + } + + return ret; +} + +static void delayed_external_notifier_init(struct work_struct *work) +{ + int ret = 0; + static int retry_count = 1; + int max_retry_count = 5; + struct max77705_usbc_platform_data *usbpd_data = g_usbc_data; + + pr_info("%s : %d = times!\n", __func__, retry_count); + + /* Register ccic handler to ccic notifier block list */ + ret = usb_external_notify_register(&usbpd_data->usb_external_notifier_nb, + pdic_handle_usb_external_notifier_notification, EXTERNAL_NOTIFY_DEV_PDIC); + if (ret < 0) { + pr_err("Manager notifier init time is %d.\n", retry_count); + if (retry_count++ != max_retry_count) + schedule_delayed_work(&usbpd_data->usb_external_notifier_register_work, msecs_to_jiffies(2000)); + else + pr_err("fail to init external notifier\n"); + } else + pr_info("%s : external notifier register done!\n", __func__); +} +#endif + +#if defined(CONFIG_SEC_FACTORY) +static void factory_check_abnormal_state(struct work_struct *work) +{ + struct max77705_usbc_platform_data *usbpd_data = g_usbc_data; + int state_cnt = usbpd_data->factory_mode.FAC_Abnormal_Repeat_State; + + msg_maxim("IN "); + + if (state_cnt >= FAC_ABNORMAL_REPEAT_STATE) { + msg_maxim("Notify the abnormal state [STATE] [ %d]", state_cnt); + max77705_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_CCIC, PDIC_NOTIFY_ID_FAC, 1, 0, 0); + } else + msg_maxim("[STATE] cnt : [%d]", state_cnt); + usbpd_data->factory_mode.FAC_Abnormal_Repeat_State = 0; + msg_maxim("OUT "); +} + +static void factory_check_normal_rid(struct work_struct *work) +{ + struct max77705_usbc_platform_data *usbpd_data = g_usbc_data; + int rid_cnt = usbpd_data->factory_mode.FAC_Abnormal_Repeat_RID; + + msg_maxim("IN "); + + if (rid_cnt >= FAC_ABNORMAL_REPEAT_RID) { + msg_maxim("Notify the abnormal state [RID] [ %d]", rid_cnt); + max77705_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_CCIC, PDIC_NOTIFY_ID_FAC, 1 << 1, 0, 0); + } else + msg_maxim("[RID] cnt : [%d]", rid_cnt); + + usbpd_data->factory_mode.FAC_Abnormal_Repeat_RID = 0; + msg_maxim("OUT "); +} + +void factory_execute_monitor(int type) +{ + struct max77705_usbc_platform_data *usbpd_data = g_usbc_data; + uint32_t state_cnt = usbpd_data->factory_mode.FAC_Abnormal_Repeat_State; + uint32_t rid_cnt = usbpd_data->factory_mode.FAC_Abnormal_Repeat_RID; + uint32_t rid0_cnt = usbpd_data->factory_mode.FAC_Abnormal_RID0; + + switch (type) { + case FAC_ABNORMAL_REPEAT_RID0: + if (!rid0_cnt) { + msg_maxim("Notify the abnormal state [RID0] [%d]!!", rid0_cnt); + usbpd_data->factory_mode.FAC_Abnormal_RID0++; + max77705_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_CCIC, PDIC_NOTIFY_ID_FAC, 1 << 2, 0, 0); + } else { + usbpd_data->factory_mode.FAC_Abnormal_RID0 = 0; + } + break; + case FAC_ABNORMAL_REPEAT_RID: + if (!rid_cnt) { + schedule_delayed_work(&usbpd_data->factory_rid_work, msecs_to_jiffies(1000)); + msg_maxim("start the factory_rid_work"); + } + usbpd_data->factory_mode.FAC_Abnormal_Repeat_RID++; + break; + case FAC_ABNORMAL_REPEAT_STATE: + if (!state_cnt) { + schedule_delayed_work(&usbpd_data->factory_state_work, msecs_to_jiffies(1000)); + msg_maxim("start the factory_state_work"); + } + usbpd_data->factory_mode.FAC_Abnormal_Repeat_State++; + break; + default: + msg_maxim("Never Calling [%d]", type); + break; + } +} +#endif + +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) +static void max77705_usbpd_set_host_on(void *data, int mode) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + + if (!usbpd_data) + return; + + pr_info("%s : current_set is %d!\n", __func__, mode); + if (mode) { + usbpd_data->device_add = 0; + usbpd_data->detach_done_wait = 0; + usbpd_data->host_turn_on_event = 1; + wake_up_interruptible(&usbpd_data->host_turn_on_wait_q); + } else { + usbpd_data->device_add = 0; + usbpd_data->detach_done_wait = 0; + usbpd_data->host_turn_on_event = 0; + } +} + +static void max77705_usbpd_wait_entermode(void *data, int on) +{ + struct max77705_usbc_platform_data *usbpd_data = data; + + if (!usbpd_data) + return; + + pr_info("%s : %d!\n", __func__, on); + if (on) { + usbpd_data->wait_entermode = 1; + } else { + usbpd_data->wait_entermode = 0; + wake_up_interruptible(&usbpd_data->host_turn_on_wait_q); + } +} + +struct usbpd_ops ops_usbpd = { + .usbpd_set_host_on = max77705_usbpd_set_host_on, + .usbpd_wait_entermode = max77705_usbpd_wait_entermode, +}; +#endif + +static int max77705_usbc_probe(struct platform_device *pdev) +{ + struct max77705_dev *max77705 = dev_get_drvdata(pdev->dev.parent); + struct max77705_platform_data *pdata = dev_get_platdata(max77705->dev); + struct max77705_usbc_platform_data *usbc_data = NULL; + int ret = 0, i = 0; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + ppdic_data_t ppdic_data; + ppdic_sysfs_property_t ppdic_sysfs_prop; +#endif +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + struct otg_notify *o_notify = get_otg_notify(); +#endif +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + struct usbpd_dev *usbpd_d; +#endif + struct max77705_hmd_power_dev *hmd_list; + + msg_maxim("Probing : %d", max77705->irq); + usbc_data = kzalloc(sizeof(struct max77705_usbc_platform_data), GFP_KERNEL); + if (!usbc_data) + return -ENOMEM; + +#if defined(CONFIG_QCOM_IFPMIC_SUSPEND) + max77705->check_usbc_opcode_queue = check_usbc_opcode_queue; +#endif + g_usbc_data = usbc_data; + usbc_data->dev = &pdev->dev; + usbc_data->max77705 = max77705; + usbc_data->muic = max77705->muic; + usbc_data->charger = max77705->charger; + usbc_data->i2c = max77705->i2c; + usbc_data->max77705_data = pdata; + usbc_data->irq_base = pdata->irq_base; + + usbc_data->pd_data = kzalloc(sizeof(struct max77705_pd_data), GFP_KERNEL); + if (!usbc_data->pd_data) + return -ENOMEM; + + usbc_data->cc_data = kzalloc(sizeof(struct max77705_cc_data), GFP_KERNEL); + if (!usbc_data->cc_data) + return -ENOMEM; + + platform_set_drvdata(pdev, usbc_data); + +#if defined(CONFIG_CCIC_MAX77705_DEBUG) + mxim_debug_init(); + mxim_debug_set_i2c_client(usbc_data->muic); +#endif + + ret = sysfs_create_group(&max77705->dev->kobj, &max77705_attr_grp); + msg_maxim("created sysfs. ret=%d", ret); + + usbc_data->HW_Revision = 0x0; + usbc_data->FW_Revision = 0x0; + usbc_data->plug_attach_done = 0x0; + usbc_data->cc_data->current_pr = 0xFF; + usbc_data->pd_data->current_dr = 0xFF; + usbc_data->cc_data->current_vcon = 0xFF; + usbc_data->op_code_done = 0x0; + usbc_data->current_connstat = 0xFF; + usbc_data->prev_connstat = 0xFF; + usbc_data->usbc_cmd_queue.front = NULL; + usbc_data->usbc_cmd_queue.rear = NULL; + usbc_data->opcode_stamp = 0; + mutex_init(&usbc_data->op_lock); +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + ppdic_data = devm_kzalloc(usbc_data->dev, sizeof(pdic_data_t), GFP_KERNEL); + if (!ppdic_data) + return -ENOMEM; + + ppdic_sysfs_prop = devm_kzalloc(usbc_data->dev, sizeof(pdic_sysfs_property_t), GFP_KERNEL); + if (!ppdic_sysfs_prop) + return -ENOMEM; + + ppdic_sysfs_prop->get_property = max77705_sysfs_get_local_prop; + ppdic_sysfs_prop->set_property = max77705_sysfs_set_prop; + ppdic_sysfs_prop->property_is_writeable = max77705_sysfs_is_writeable; + ppdic_sysfs_prop->property_is_writeonly = max77705_sysfs_is_writeonly; + ppdic_sysfs_prop->properties = max77705_sysfs_properties; + ppdic_sysfs_prop->num_properties = ARRAY_SIZE(max77705_sysfs_properties); + ppdic_data->pdic_sysfs_prop = ppdic_sysfs_prop; + ppdic_data->drv_data = usbc_data; + ppdic_data->name = "max77705"; + ppdic_data->set_enable_alternate_mode = max77705_set_enable_alternate_mode; + pdic_core_register_chip(ppdic_data); + usbc_data->vconn_en = 1; + usbc_data->cur_rid = RID_OPEN; + usbc_data->cc_pin_status = NO_DETERMINATION; +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + usbpd_d = kzalloc(sizeof(struct usbpd_dev), GFP_KERNEL); + if (!usbpd_d) { + pr_err("%s: failed to allocate usbpd data\n", __func__); + ret = -ENOMEM; + return ret; + } + + usbpd_d->ops = &ops_usbpd; + usbpd_d->data = (void *)usbc_data; + usbc_data->man = register_usbpd(usbpd_d); +#endif + usbc_data->typec_cap.revision = USB_TYPEC_REV_1_2; + usbc_data->typec_cap.pd_revision = 0x300; + usbc_data->typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE; + + usbc_data->typec_cap.driver_data = usbc_data; + usbc_data->typec_cap.ops = &max77705_ops; + + usbc_data->typec_cap.type = TYPEC_PORT_DRP; + usbc_data->typec_cap.data = TYPEC_PORT_DRD; + + usbc_data->typec_power_role = TYPEC_SINK; + usbc_data->typec_try_state_change = TRY_ROLE_SWAP_NONE; + + usbc_data->port = typec_register_port(usbc_data->dev, &usbc_data->typec_cap); + if (IS_ERR(usbc_data->port)) + pr_err("unable to register typec_register_port\n"); + else + msg_maxim("success typec_register_port port=%pK", usbc_data->port); + init_completion(&usbc_data->typec_reverse_completion); + + usbc_data->auto_vbus_en = false; + usbc_data->is_first_booting = 1; + usbc_data->pd_support = false; + usbc_data->ccrp_state = 0; + usbc_data->set_altmode = 0; + usbc_data->set_altmode_error = 0; + usbc_data->need_recover = false; + usbc_data->op_ctrl1_w = (BIT_CCSrcSnk | BIT_CCSnkSrc | BIT_CCDetEn); + usbc_data->srcccap_request_retry = false; +#if defined(CONFIG_SUPPORT_SHIP_MODE) + usbc_data->ship_mode_en = 0; +#endif + usbc_data->rid_check = false; + usbc_data->lapse_idx = 0; + for (i = 0; i < MAX_NVCN_CNT; i++) + usbc_data->time_lapse[i] = 0; + +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + send_otg_notify(o_notify, NOTIFY_EVENT_POWER_SOURCE, 0); +#endif +#endif + init_completion(&usbc_data->op_completion); + init_completion(&usbc_data->ccic_sysfs_completion); + init_completion(&usbc_data->psrdy_wait); + usbc_data->op_wait_queue = create_singlethread_workqueue("op_wait"); + if (usbc_data->op_wait_queue == NULL) + return -ENOMEM; + usbc_data->op_send_queue = create_singlethread_workqueue("op_send"); + if (usbc_data->op_send_queue == NULL) + return -ENOMEM; + + INIT_WORK(&usbc_data->op_send_work, max77705_uic_op_send_work_func); + INIT_WORK(&usbc_data->cc_open_req_work, max77705_cc_open_work_func); + INIT_WORK(&usbc_data->dp_configure_work, max77705_dp_configure_work_func); +#if defined(MAX77705_SYS_FW_UPDATE) + INIT_WORK(&usbc_data->fw_update_work, + max77705_firmware_update_sysfs_work); +#endif + +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + /* Create a work queue for the ccic irq thread */ + usbc_data->ccic_wq + = create_singlethread_workqueue("ccic_irq_event"); + if (!usbc_data->ccic_wq) { + pr_err("%s failed to create work queue\n", __func__); + return -ENOMEM; + } +#endif +#if defined(CONFIG_SEC_FACTORY) + INIT_DELAYED_WORK(&usbc_data->factory_state_work, + factory_check_abnormal_state); + INIT_DELAYED_WORK(&usbc_data->factory_rid_work, + factory_check_normal_rid); +#endif + max77705_get_version_info(usbc_data); + max77705_init_irq_handler(usbc_data); + max77705_muic_probe(usbc_data); + max77705_cc_init(usbc_data); + max77705_pd_init(usbc_data); + max77705_write_reg(usbc_data->muic, REG_PD_INT_M, 0x1C); + max77705_write_reg(usbc_data->muic, REG_VDM_INT_M, 0xFF); + max77705_init_opcode(usbc_data, 0); + INIT_DELAYED_WORK(&usbc_data->vbus_hard_reset_work, + vbus_control_hard_reset); + /* turn on the VBUS automatically. */ + // max77705_usbc_enable_auto_vbus(usbc_data); + INIT_DELAYED_WORK(&usbc_data->acc_detach_work, max77705_acc_detach_check); + + pdic_register_switch_device(1); +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + INIT_DELAYED_WORK(&usbc_data->usb_external_notifier_register_work, + delayed_external_notifier_init); +#endif + init_completion(&usbc_data->uvdm_longpacket_out_wait); + init_completion(&usbc_data->uvdm_longpacket_in_wait); +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + ppdic_data->fw_data.data = (void *)usbc_data; + ppdic_data->fw_data.firmware_update = max77705_firmware_update_callback; + ppdic_data->fw_data.get_prev_fw_size = max77705_get_firmware_size; + ret = pdic_misc_init(ppdic_data); + if (ret) { + pr_err("pdic_misc_init is failed, error %d\n", ret); + } else { + ppdic_data->misc_dev->uvdm_read = max77705_sec_uvdm_in_request_message; + ppdic_data->misc_dev->uvdm_write = max77705_sec_uvdm_out_request_message; + ppdic_data->misc_dev->uvdm_ready = max77705_sec_uvdm_ready; + ppdic_data->misc_dev->uvdm_close = max77705_sec_uvdm_close; + ppdic_data->misc_dev->pps_control = max77705_sec_pps_control; + usbc_data->ppdic_data = ppdic_data; + } +#endif + + hmd_list = kzalloc(MAX_NUM_HMD * sizeof(*hmd_list), + GFP_KERNEL); + if (ZERO_OR_NULL_PTR(hmd_list)) { + kfree(hmd_list); + usbc_data->hmd_list = NULL; + return -ENOMEM; + } + + /* add default AR/VR here */ + snprintf(hmd_list[0].hmd_name, NAME_LEN_HMD, "PicoVR"); + hmd_list[0].vid = 0x2d40; + hmd_list[0].pid = 0x0000; + + usbc_data->hmd_list = hmd_list; + + mutex_init(&usbc_data->hmd_power_lock); + +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + /* Register pdic handler to pdic notifier block list */ + ret = usb_external_notify_register(&usbc_data->usb_external_notifier_nb, + pdic_handle_usb_external_notifier_notification, EXTERNAL_NOTIFY_DEV_PDIC); + if (ret < 0) + schedule_delayed_work(&usbc_data->usb_external_notifier_register_work, msecs_to_jiffies(2000)); + else + pr_info("%s : external notifier register done!\n", __func__); +#endif + + max77705->cc_booting_complete = 1; + max77705_usbc_umask_irq(usbc_data); + init_waitqueue_head(&usbc_data->host_turn_on_wait_q); + init_waitqueue_head(&usbc_data->device_add_wait_q); +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + max77705_usbpd_set_host_on(usbc_data, 0); +#endif +#if IS_ENABLED(CONFIG_USB_DWC3_MSM) + usbc_data->host_turn_on_wait_time = 15; +#else + usbc_data->host_turn_on_wait_time = 3; +#endif + + usbc_data->cc_open_req = 1; + pdic_manual_ccopen_request(0); + + msg_maxim("probing Complete.."); + return 0; +} + +static int max77705_usbc_remove(struct platform_device *pdev) +{ + struct max77705_usbc_platform_data *usbc_data = + platform_get_drvdata(pdev); + struct max77705_dev *max77705 = usbc_data->max77705; + +#if defined(CONFIG_CCIC_MAX77705_DEBUG) + mxim_debug_exit(); +#endif + sysfs_remove_group(&max77705->dev->kobj, &max77705_attr_grp); +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0) + kzfree(usbc_data->hmd_list); +#else + kfree_sensitive(usbc_data->hmd_list); +#endif + usbc_data->hmd_list = NULL; + mutex_destroy(&usbc_data->hmd_power_lock); + mutex_destroy(&usbc_data->op_lock); + pdic_core_unregister_chip(); + + typec_unregister_port(usbc_data->port); + + pdic_register_switch_device(0); +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + if (usbc_data->ppdic_data && usbc_data->ppdic_data->misc_dev) + pdic_misc_exit(); +#endif +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + usb_external_notify_unregister(&usbc_data->usb_external_notifier_nb); +#endif + max77705_muic_remove(usbc_data); + + free_irq(usbc_data->irq_apcmd, usbc_data); + free_irq(usbc_data->irq_sysmsg, usbc_data); + free_irq(usbc_data->irq_vdm0, usbc_data); + free_irq(usbc_data->irq_vdm1, usbc_data); + free_irq(usbc_data->irq_vdm2, usbc_data); + free_irq(usbc_data->irq_vdm3, usbc_data); + free_irq(usbc_data->irq_vdm4, usbc_data); + free_irq(usbc_data->irq_vdm5, usbc_data); + free_irq(usbc_data->irq_vdm6, usbc_data); + free_irq(usbc_data->irq_vdm7, usbc_data); + free_irq(usbc_data->pd_data->irq_pdmsg, usbc_data); + free_irq(usbc_data->pd_data->irq_datarole, usbc_data); + free_irq(usbc_data->pd_data->irq_ssacc, usbc_data); + free_irq(usbc_data->pd_data->irq_fct_id, usbc_data); + free_irq(usbc_data->cc_data->irq_vconncop, usbc_data); + free_irq(usbc_data->cc_data->irq_vsafe0v, usbc_data); + free_irq(usbc_data->cc_data->irq_detabrt, usbc_data); + free_irq(usbc_data->cc_data->irq_vconnsc, usbc_data); + free_irq(usbc_data->cc_data->irq_ccpinstat, usbc_data); + free_irq(usbc_data->cc_data->irq_ccistat, usbc_data); + free_irq(usbc_data->cc_data->irq_ccvcnstat, usbc_data); + free_irq(usbc_data->cc_data->irq_ccstat, usbc_data); +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + kfree(usbc_data->man->usbpd_d); +#endif + kfree(usbc_data->cc_data); + kfree(usbc_data->pd_data); + kfree(usbc_data); + return 0; +} + +#if defined CONFIG_PM +static int max77705_usbc_suspend(struct device *dev) +{ + struct max77705_usbc_platform_data *usbc_data = + dev_get_drvdata(dev); + + max77705_muic_suspend(usbc_data); + + return 0; +} + +static int max77705_usbc_resume(struct device *dev) +{ + struct max77705_usbc_platform_data *usbc_data = + dev_get_drvdata(dev); + + max77705_muic_resume(usbc_data); + if (usbc_data->set_altmode_error) { + msg_maxim("set alternate mode"); + max77705_set_enable_alternate_mode + (usbc_data->set_altmode); + } + + return 0; +} +#else +#define max77705_usbc_suspend NULL +#define max77705_usbc_resume NULL +#endif + +static void max77705_usbc_disable_irq(struct max77705_usbc_platform_data *usbc_data) +{ + disable_irq(usbc_data->irq_apcmd); + disable_irq(usbc_data->irq_sysmsg); + disable_irq(usbc_data->irq_vdm0); + disable_irq(usbc_data->irq_vdm1); + disable_irq(usbc_data->irq_vdm2); + disable_irq(usbc_data->irq_vdm3); + disable_irq(usbc_data->irq_vdm4); + disable_irq(usbc_data->irq_vdm5); + disable_irq(usbc_data->irq_vdm6); + disable_irq(usbc_data->irq_vir0); + disable_irq(usbc_data->pd_data->irq_pdmsg); + disable_irq(usbc_data->pd_data->irq_psrdy); + disable_irq(usbc_data->pd_data->irq_datarole); + disable_irq(usbc_data->pd_data->irq_ssacc); + disable_irq(usbc_data->pd_data->irq_fct_id); + disable_irq(usbc_data->cc_data->irq_vconncop); + disable_irq(usbc_data->cc_data->irq_vsafe0v); + disable_irq(usbc_data->cc_data->irq_vconnsc); + disable_irq(usbc_data->cc_data->irq_ccpinstat); + disable_irq(usbc_data->cc_data->irq_ccistat); + disable_irq(usbc_data->cc_data->irq_ccvcnstat); + disable_irq(usbc_data->cc_data->irq_ccstat); +} + +static void max77705_usbc_shutdown(struct platform_device *pdev) +{ + struct max77705_usbc_platform_data *usbc_data = + platform_get_drvdata(pdev); + struct device_node *np; + int gpio_dp_sw_oe; + u8 status = 0, uic_int = 0, vbus = 0; + u8 uid = 0; + + msg_maxim("max77705 usbc driver shutdown++++"); + if (!usbc_data->muic) { + msg_maxim("no max77705 i2c client"); + return; + } + usbc_data->shut_down = 1; + max77705_muic_shutdown(usbc_data); + max77705_usbc_mask_irq(usbc_data); + /* unmask */ + max77705_write_reg(usbc_data->muic, REG_PD_INT_M, 0xFF); + max77705_write_reg(usbc_data->muic, REG_CC_INT_M, 0xFF); + max77705_write_reg(usbc_data->muic, REG_UIC_INT_M, 0xFF); + max77705_write_reg(usbc_data->muic, REG_VDM_INT_M, 0xFF); + + max77705_usbc_disable_irq(usbc_data); + + max77705_read_reg(usbc_data->muic, REG_USBC_STATUS1, &status); + uid = (status & BIT_UIDADC) >> FFS(BIT_UIDADC); + vbus = (status & BIT_VBADC) >> FFS(BIT_VBADC); + + /* send the reset command */ + if (usbc_data->current_connstat == WATER) { +#if defined(CONFIG_UPGRADED_WATER_DETECT) + msg_maxim("WATER STATE, %s, %s", vbus ? "VBUS" : "NO VBUS", + usbc_data->muic_data->is_hiccup_mode ? "HICCUP MODE" : "NOT HICCUP MODE"); + if (!vbus && !usbc_data->muic_data->is_hiccup_mode) { + msg_maxim("call reset function"); + max77705_reset_ic(usbc_data); + } else + msg_maxim("don't call the reset function"); +#else + msg_maxim("no call the reset function(WATER STATE)"); +#endif + } else if (usbc_data->cur_rid != RID_OPEN && usbc_data->cur_rid != RID_UNDEFINED) + msg_maxim("no call the reset function(RID)"); + else if (uid != UID_Open) + msg_maxim("no call the reset function(UID)"); + else { +#if defined(CONFIG_SUPPORT_SHIP_MODE) + if (usbc_data->ship_mode_en) + pr_info("%s: ship_mode_en(%d), skip reset_ic\n", + __func__, usbc_data->ship_mode_en); + else + max77705_reset_ic(usbc_data); +#else + max77705_reset_ic(usbc_data); +#endif + if (usbc_data->dp_is_connect) { + pr_info("aux_sw_oe pin set to high\n"); + np = of_find_node_by_name(NULL, "displayport"); + gpio_dp_sw_oe = of_get_named_gpio(np, "dp,aux_sw_oe", 0); + gpio_direction_output(gpio_dp_sw_oe, 1); + } + max77705_write_reg(usbc_data->muic, REG_PD_INT_M, 0xFF); + max77705_write_reg(usbc_data->muic, REG_CC_INT_M, 0xFF); + max77705_write_reg(usbc_data->muic, REG_UIC_INT_M, 0xFF); + max77705_write_reg(usbc_data->muic, REG_VDM_INT_M, 0xFF); + max77705_read_reg(usbc_data->muic, + MAX77705_USBC_REG_UIC_INT, &uic_int); + } + msg_maxim("max77705 usbc driver shutdown----"); +} + +static SIMPLE_DEV_PM_OPS(max77705_usbc_pm_ops, max77705_usbc_suspend, + max77705_usbc_resume); + +static struct platform_driver max77705_usbc_driver = { + .driver = { + .name = "max77705-usbc", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &max77705_usbc_pm_ops, +#endif + }, + .shutdown = max77705_usbc_shutdown, + .probe = max77705_usbc_probe, + .remove = max77705_usbc_remove, +}; + +static int __init max77705_usbc_init(void) +{ + msg_maxim("init"); + return platform_driver_register(&max77705_usbc_driver); +} +device_initcall(max77705_usbc_init); + +static void __exit max77705_usbc_exit(void) +{ + platform_driver_unregister(&max77705_usbc_driver); +} +module_exit(max77705_usbc_exit); + +MODULE_DESCRIPTION("max77705 USBPD driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/maxim/max77775-muic-afc.c b/drivers/usb/typec/maxim/max77775-muic-afc.c new file mode 100755 index 000000000000..9ac558ab213d --- /dev/null +++ b/drivers/usb/typec/maxim/max77775-muic-afc.c @@ -0,0 +1,781 @@ +/* + * max77775-muic.c - MUIC driver for the Maxim 77775 + * + * Copyright (C) 2015 Samsung Electronics + * Insun Choi + * + * 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, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* MUIC header file */ +#include +#include +#include + +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) +#include +#endif /* CONFIG_MUIC_NOTIFIER */ + +#if defined(CONFIG_USB_HW_PARAM) +#include +#endif + +#if IS_ENABLED(CONFIG_SEC_ABC) +#include +#endif +#include + +#define RETRY_COUNT 3 + +bool max77775_muic_check_is_enable_afc(struct max77775_muic_data *muic_data, muic_attached_dev_t new_dev) +{ + struct max77775_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + int ret = false; + + if (new_dev == ATTACHED_DEV_TA_MUIC || new_dev == ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC || + muic_data->is_usb_fail) { + if (!muic_data->is_charger_ready) { + md75_info_usb("%s Charger is not ready(%d), skip AFC\n", + __func__, muic_data->is_charger_ready); + } else if (muic_data->is_charger_mode) { + md75_info_usb("%s is_charger_mode(%d), skip AFC\n", + __func__, muic_data->is_charger_mode); +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + } else if (usbc_pdata->fac_water_enable) { + md75_info_usb("%s fac_water_enable(%d), skip AFC\n", __func__, + usbc_pdata->fac_water_enable); +#endif /* CONFIG_PDIC_NOTIFIER */ + } else if (muic_data->afc_water_disable) { + md75_info_usb("%s water detected(%d), skip AFC\n", __func__, + muic_data->afc_water_disable); + } else if (usbc_pdata->pd_support) { + md75_info_usb("%s PD TA detected(%d), skip AFC\n", __func__, + usbc_pdata->pd_support); + } else { + ret = true; + } + } + + return ret; +} + +static void max77775_muic_afc_reset(struct max77775_muic_data *muic_data) +{ + struct max77775_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + usbc_cmd_data write_data; + + md75_info_usb("%s:%s\n", MUIC_DEV_NAME, __func__); + muic_data->is_afc_reset = true; + + init_usbc_cmd_data(&write_data); + write_data.opcode = COMMAND_BC_CTRL2_WRITE; + write_data.write_length = 1; + write_data.write_data[0] = 0x13; /* DPDNMan enable, DP GND, DM Open */ + write_data.read_length = 0; + + max77775_usbc_opcode_write(usbc_pdata, &write_data); +} + +void max77775_muic_check_afc_disabled(struct max77775_muic_data *muic_data) +{ + struct muic_platform_data *pdata = muic_data->pdata; + muic_attached_dev_t new_attached_dev = (muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_DISABLED_MUIC || + muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_9V_MUIC) ? + ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC : ATTACHED_DEV_TA_MUIC; + md75_info_usb("%s:%s\n", MUIC_DEV_NAME, __func__); + + if ((!pdata->afc_disable && (muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_DISABLED_MUIC || + muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_5V_MUIC)) || + (pdata->afc_disable && (muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_9V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_9V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_5V_MUIC + ))) { + + md75_info_usb("%s:%s change charger (%d) -> (%d)\n", MUIC_DEV_NAME, __func__, + muic_data->attached_dev, new_attached_dev); + + muic_data->attached_dev = new_attached_dev; +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + muic_notifier_attach_attached_dev(new_attached_dev); +#endif + + cancel_delayed_work_sync(&(muic_data->afc_work)); + schedule_delayed_work(&(muic_data->afc_work), msecs_to_jiffies(500)); + } +} + +static void max77775_muic_afc_hv_tx_byte_set(struct max77775_muic_data *muic_data, u8 tx_byte) +{ + struct max77775_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + usbc_cmd_data write_data; + + md75_info_usb("%s:%s tx_byte(0x%02x)\n", MUIC_DEV_NAME, __func__, tx_byte); + + init_usbc_cmd_data(&write_data); + write_data.opcode = COMMAND_AFC_RESULT_READ; + write_data.write_length = 1; + write_data.write_data[0] = tx_byte; + write_data.read_length = 10; + + max77775_usbc_opcode_write(usbc_pdata, &write_data); +} + +void max77775_muic_clear_hv_control(struct max77775_muic_data *muic_data) +{ + struct max77775_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + usbc_cmd_data write_data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = COMMAND_HV_CONTROL_WRITE; + write_data.write_length = 1; + write_data.write_data[0] = 0; + + max77775_usbc_opcode_write(usbc_pdata, &write_data); +} + +int max77775_muic_afc_hv_set(struct max77775_muic_data *muic_data, int voltage) +{ + struct max77775_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + usbc_cmd_data write_data; + u8 tx_byte; + + switch (voltage) { + case 5: + tx_byte = 0x08; + break; + case 9: + tx_byte = 0x46; + break; + default: + md75_info_usb("%s:%s invalid value(%d), return\n", MUIC_DEV_NAME, + __func__, voltage); + return -EINVAL; + } + + md75_info_usb("%s:%s voltage(%d)\n", MUIC_DEV_NAME, __func__, voltage); + + init_usbc_cmd_data(&write_data); + write_data.opcode = COMMAND_AFC_RESULT_READ; + write_data.write_length = 1; + write_data.write_data[0] = tx_byte; + write_data.read_length = 10; + + return max77775_usbc_opcode_write(usbc_pdata, &write_data); +} + +int max77775_muic_qc_hv_set(struct max77775_muic_data *muic_data, int voltage) +{ + struct max77775_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + usbc_cmd_data write_data; + u8 dpdndrv; + + switch (voltage) { + case 5: + dpdndrv = 0x04; + break; + case 9: + dpdndrv = 0x09; + break; + default: + md75_info_usb("%s:%s invalid value(%d), return\n", MUIC_DEV_NAME, + __func__, voltage); + return -EINVAL; + } + + md75_info_usb("%s:%s voltage(%d)\n", MUIC_DEV_NAME, __func__, voltage); + + init_usbc_cmd_data(&write_data); + write_data.opcode = COMMAND_QC_2_0_SET; + write_data.write_length = 1; + write_data.write_data[0] = dpdndrv; + write_data.read_length = 2; + + return max77775_usbc_opcode_write(usbc_pdata, &write_data); +} + +#if !defined(CONFIG_MAX77775_MUIC_QC_DISABLE) +static void max77775_muic_handle_detect_dev_mpnack(struct max77775_muic_data *muic_data) +{ + struct max77775_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + usbc_cmd_data write_data; + u8 dpdndrv = 0x09; + + init_usbc_cmd_data(&write_data); + write_data.opcode = COMMAND_QC_2_0_SET; + write_data.write_length = 1; + write_data.write_data[0] = dpdndrv; + write_data.read_length = 2; + + max77775_usbc_opcode_write(usbc_pdata, &write_data); +} +#endif +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) +static void max77775_muic_handle_afc_retry(struct max77775_muic_data *muic_data, + muic_attached_dev_t current_attached_dev) +{ + md75_info_usb("%s:%s current_attached_dev: %d\n", MUIC_DEV_NAME, __func__, current_attached_dev); + + muic_data->attached_dev = current_attached_dev; + if (muic_data->attached_dev != ATTACHED_DEV_TIMEOUT_OPEN_MUIC) { + muic_data->attached_dev = ATTACHED_DEV_RETRY_TIMEOUT_OPEN_MUIC; +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + muic_notifier_detach_attached_dev(current_attached_dev); + muic_notifier_attach_attached_dev(muic_data->attached_dev); +#endif + } +} +#endif +void max77775_muic_handle_detect_dev_afc(struct max77775_muic_data *muic_data, unsigned char *data) +{ + int result = data[1]; + int vbadc = data[2]; + int vbadc2 = (muic_data->status1 & USBC_STATUS1_VBADC_MASK) >> USBC_STATUS1_VBADC_SHIFT; + muic_attached_dev_t new_afc_dev = muic_data->attached_dev; +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + bool noti = true; +#endif +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); + bool afc_err = false; +#endif + bool afc_nack = false; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + bool afc_retry_fail = false; + muic_attached_dev_t current_attached_dev = muic_data->attached_dev; +#endif + int i = 0; + int ret = 0; + + /* W/A: vbadc of opcode result is 0, but vbadc register value is not 0 */ + if (vbadc == 0 && vbadc2 > 0) + vbadc = data[2] = vbadc2; + + md75_info_usb("%s:%s result:0x%x vbadc:0x%x rxbyte:0x%x %x %x %x %x %x %x %x\n", MUIC_DEV_NAME, + __func__, data[1], data[2], data[3], data[4], data[5], + data[6], data[7], data[8], data[9], data[10]); + + switch (result) { + case 0: + md75_info_usb("%s:%s AFC Success, vbadc(%d)\n", MUIC_DEV_NAME, __func__, vbadc); + muic_data->afc_retry = 0; + + if (vbadc >= MAX77775_VBADC_4_5V_TO_5_5V && + vbadc <= MAX77775_VBADC_6_5V_TO_7_5V) { + if (muic_data->pdata->afc_disable) { + md75_info_usb("%s:%s AFC disabled, set cable type to AFC_CHARGER_DISABLED\n", MUIC_DEV_NAME, __func__); + new_afc_dev = ATTACHED_DEV_AFC_CHARGER_DISABLED_MUIC; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + if (muic_data->is_usb_fail) + new_afc_dev = ATTACHED_DEV_RETRY_AFC_CHARGER_5V_MUIC; +#endif + } else { + new_afc_dev = ATTACHED_DEV_AFC_CHARGER_5V_MUIC; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + if (muic_data->is_usb_fail) + new_afc_dev = ATTACHED_DEV_RETRY_AFC_CHARGER_5V_MUIC; +#endif + } + } else if (vbadc >= MAX77775_VBADC_7_5V_TO_8_5V && + vbadc <= MAX77775_VBADC_9_5V_TO_10_5V) { + new_afc_dev = ATTACHED_DEV_AFC_CHARGER_9V_MUIC; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + if (muic_data->is_usb_fail) + new_afc_dev = ATTACHED_DEV_RETRY_AFC_CHARGER_9V_MUIC; +#endif + } +#if defined(CONFIG_USB_HW_PARAM) + else + afc_err = true; +#endif + + if (new_afc_dev != muic_data->attached_dev) { +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + if (muic_data->is_usb_fail) + muic_notifier_detach_attached_dev(muic_data->attached_dev); +#endif + muic_notifier_attach_attached_dev(new_afc_dev); +#endif /* CONFIG_MUIC_NOTIFIER */ + muic_data->attached_dev = new_afc_dev; + } + break; + case 1: + md75_info_usb("%s:%s No CHGIN\n", MUIC_DEV_NAME, __func__); + if (muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_9V_MUIC || +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + muic_data->attached_dev == ATTACHED_DEV_RETRY_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_RETRY_AFC_CHARGER_9V_MUIC || +#endif + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_9V_MUIC) + afc_nack = true; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + else if (muic_data->is_usb_fail) + afc_retry_fail = true; +#endif + break; + case 2: + md75_info_usb("%s:%s Not High Voltage DCP\n", MUIC_DEV_NAME, __func__); + if (muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_9V_MUIC || +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + muic_data->attached_dev == ATTACHED_DEV_RETRY_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_RETRY_AFC_CHARGER_9V_MUIC || +#endif + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_9V_MUIC) + afc_nack = true; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + else if (muic_data->is_usb_fail) + afc_retry_fail = true; +#endif + break; + case 3: + md75_info_usb("%s:%s Not DCP\n", MUIC_DEV_NAME, __func__); + if (muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_9V_MUIC || +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + muic_data->attached_dev == ATTACHED_DEV_RETRY_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_RETRY_AFC_CHARGER_9V_MUIC || +#endif + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_9V_MUIC) + afc_nack = true; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + else if (muic_data->is_usb_fail) + afc_retry_fail = true; +#endif + break; + case 4: + md75_info_usb("%s:%s MPing NACK\n", MUIC_DEV_NAME, __func__); + if (muic_data->pdata->afc_disable) { + md75_info_usb("%s:%s skip checking QC TA by afc disable, just return!\n", MUIC_DEV_NAME, __func__); +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + if (muic_data->is_usb_fail) + afc_retry_fail = true; +#endif + } +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + else if (muic_data->is_usb_fail) { + afc_retry_fail = true; + md75_info_usb("%s:%s skip checking QC TA by usb fail, just return!\n", MUIC_DEV_NAME, __func__); + } +#endif +#if !defined(CONFIG_MAX77775_MUIC_QC_DISABLE) + else { + md75_info_usb("%s:%s checking QC TA!\n", MUIC_DEV_NAME, __func__); + max77775_muic_handle_detect_dev_mpnack(muic_data); + } +#endif + break; + case 5: + md75_info_usb("%s:%s Unsupported TX data\n", MUIC_DEV_NAME, __func__); + if (muic_data->afc_retry++ < RETRY_COUNT) { + md75_info_usb("%s:%s Retry(%d)\n", MUIC_DEV_NAME, __func__, muic_data->afc_retry); + for (i = 3; (i <= 10) && (data[i] != 0); i++) { + if ((muic_data->hv_voltage == 9) && ((data[i] & 0xF0) == 0x40)) { + /* 9V case */ + md75_info_usb("%s:%s seleted tx byte = 0x%02x", MUIC_DEV_NAME, + __func__, data[i]); + max77775_muic_afc_hv_tx_byte_set(muic_data, data[i]); + break; + } else if ((muic_data->hv_voltage == 5) && ((data[i] & 0xF0) == 0x0)) { + /* 5V case */ + md75_info_usb("%s:%s seleted tx byte = 0x%02x", MUIC_DEV_NAME, + __func__, data[i]); + max77775_muic_afc_hv_tx_byte_set(muic_data, data[i]); + break; + } + } + } + break; + case 6: + md75_info_usb("%s:%s Vbus is not changed with 3 continuous ping\n", + MUIC_DEV_NAME, __func__); + afc_nack = true; + break; + case 7: + md75_info_usb("%s:%s Vbus is not changed in 1sec\n", + MUIC_DEV_NAME, __func__); + afc_nack = true; + break; + case 8: + md75_info_usb("%s:%s CC-Vbus Short case\n", MUIC_DEV_NAME, __func__); + + muic_data->attached_dev = ATTACHED_DEV_TA_MUIC; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + if (muic_data->is_usb_fail) { + afc_retry_fail = true; + noti = false; + } +#endif +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + if (noti) + muic_notifier_attach_attached_dev(muic_data->attached_dev); +#endif /* CONFIG_MUIC_NOTIFIER */ +#if IS_ENABLED(CONFIG_SEC_ABC) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=muic@INFO=cable_short"); +#else + sec_abc_send_event("MODULE=muic@WARN=cable_short"); +#endif +#endif + break; + case 9: + md75_info_usb("%s:%s SBU-Gnd Short case\n", MUIC_DEV_NAME, __func__); + + muic_data->attached_dev = ATTACHED_DEV_TA_MUIC; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + if (muic_data->is_usb_fail) { + afc_retry_fail = true; + noti = false; + } +#endif +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + if (noti) + muic_notifier_attach_attached_dev(muic_data->attached_dev); +#endif /* CONFIG_MUIC_NOTIFIER */ +#if IS_ENABLED(CONFIG_SEC_ABC) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=muic@INFO=cable_short"); +#else + sec_abc_send_event("MODULE=muic@WARN=cable_short"); +#endif +#endif + break; + case 10: + md75_info_usb("%s:%s SBU-Vbus Short case\n", MUIC_DEV_NAME, __func__); + + muic_data->attached_dev = ATTACHED_DEV_TA_MUIC; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + if (muic_data->is_usb_fail) { + afc_retry_fail = true; + noti = false; + } +#endif +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + if (noti) + muic_notifier_attach_attached_dev(muic_data->attached_dev); +#endif /* CONFIG_MUIC_NOTIFIER */ +#if IS_ENABLED(CONFIG_SEC_ABC) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=muic@INFO=cable_short"); +#else + sec_abc_send_event("MODULE=muic@WARN=cable_short"); +#endif +#endif + break; + case 11: + md75_info_usb("%s:%s Not Rp 56K\n", MUIC_DEV_NAME, __func__); + if (muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_9V_MUIC || +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + muic_data->attached_dev == ATTACHED_DEV_RETRY_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_RETRY_AFC_CHARGER_9V_MUIC || +#endif + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_9V_MUIC) + afc_nack = true; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + else if (muic_data->is_usb_fail) + afc_retry_fail = true; +#endif + break; + case 16: + md75_info_usb("%s:%s A parity check failed during resceiving data\n", + MUIC_DEV_NAME, __func__); + afc_nack = true; + break; + case 17: + md75_info_usb("%s:%s The slave does not respond to the master ping\n", + MUIC_DEV_NAME, __func__); + afc_nack = true; + break; + case 18: + md75_info_usb("%s:%s RX buffer overflow is detected\n", + MUIC_DEV_NAME, __func__); + afc_nack = true; + break; + default: + md75_info_usb("%s:%s AFC error(%d)\n", MUIC_DEV_NAME, __func__, result); + afc_nack = true; + break; + } + + if (afc_nack) { + if (muic_data->afc_retry++ < RETRY_COUNT) { + md75_info_usb("%s:%s Retry(%d)\n", MUIC_DEV_NAME, __func__, muic_data->afc_retry); +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + /* Charging TG's request, send PREPARE noti */ + if (!muic_data->is_usb_fail) + muic_notifier_attach_attached_dev(ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC); +#endif /* CONFIG_MUIC_NOTIFIER */ + max77775_muic_afc_hv_set(muic_data, muic_data->hv_voltage); + } else { + md75_info_usb("%s:%s Retry Done, do not retry\n", MUIC_DEV_NAME, __func__); + if (vbadc >= MAX77775_VBADC_7_5V_TO_8_5V) { + max77775_muic_afc_reset(muic_data); + muic_data->afc_retry = 0; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + if (muic_data->is_usb_fail) + afc_retry_fail = true; +#endif + } else { +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + /* Send attached device noti to clear prepare noti */ + if (muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_9V_MUIC) + muic_notifier_attach_attached_dev(muic_data->attached_dev); +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + else if (muic_data->is_usb_fail) + afc_retry_fail = true; +#endif + else + muic_notifier_attach_attached_dev(ATTACHED_DEV_AFC_CHARGER_ERR_V_MUIC); +#endif /* CONFIG_MUIC_NOTIFIER */ + } + mutex_lock(&muic_data->afc_lock); + muic_data->pdata->afc_disabled_updated &= MAX77775_MUIC_AFC_DISABLE_CHANGE_DURING_WORK_END; + muic_data->pdata->afc_disabled_updated &= MAX77775_MUIC_AFC_SET_VOLTAGE_CHANGE_DURING_WORK_END; + muic_data->pdata->afc_disabled_updated &= MAX77775_MUIC_AFC_WORK_PROCESS_END; + mutex_unlock(&muic_data->afc_lock); +#if IS_ENABLED(CONFIG_SEC_ABC) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=muic@INFO=afc_hv_fail"); +#else + sec_abc_send_event("MODULE=muic@WARN=afc_hv_fail"); +#endif +#endif + } + } else { + mutex_lock(&muic_data->afc_lock); + if (muic_data->pdata->afc_disabled_updated & MAX77775_MUIC_AFC_DISABLE_CHANGE_DURING_WORK) { + max77775_muic_check_afc_disabled(muic_data); + muic_data->pdata->afc_disabled_updated &= MAX77775_MUIC_AFC_DISABLE_CHANGE_DURING_WORK_END; + } + + muic_data->pdata->afc_disabled_updated &= MAX77775_MUIC_AFC_WORK_PROCESS_END; + + if (muic_data->pdata->afc_disabled_updated & MAX77775_MUIC_AFC_SET_VOLTAGE_CHANGE_DURING_WORK) { + muic_data->pdata->afc_disabled_updated |= MAX77775_MUIC_AFC_WORK_PROCESS; + + ret = __max77775_muic_afc_set_voltage(muic_data, muic_data->reserve_hv_voltage); + if (ret < 0) + muic_data->pdata->afc_disabled_updated &= MAX77775_MUIC_AFC_WORK_PROCESS_END; + + muic_data->pdata->afc_disabled_updated &= MAX77775_MUIC_AFC_SET_VOLTAGE_CHANGE_DURING_WORK_END; + } + mutex_unlock(&muic_data->afc_lock); + } + +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) { + if (muic_data->is_skip_bigdata) +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + goto afc_out; +#else + return; +#endif + if (afc_err && !afc_nack) + inc_hw_param(o_notify, USB_MUIC_AFC_ERROR_COUNT); + if (afc_nack) { + inc_hw_param(o_notify, USB_MUIC_AFC_ERROR_COUNT); + inc_hw_param(o_notify, USB_MUIC_AFC_NACK_COUNT); + muic_data->is_skip_bigdata = true; + } + } +#endif +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) +#if defined(CONFIG_USB_HW_PARAM) +afc_out: +#endif + if (afc_retry_fail) + max77775_muic_handle_afc_retry(muic_data, current_attached_dev); +#endif /* CONFIG_MUIC_AFC_RETRY */ +} + +void max77775_muic_handle_detect_dev_qc(struct max77775_muic_data *muic_data, unsigned char *data) +{ + int result = data[1]; + int vbadc = data[2]; + int vbadc2 = (muic_data->status1 & USBC_STATUS1_VBADC_MASK) >> USBC_STATUS1_VBADC_SHIFT; + muic_attached_dev_t new_afc_dev = muic_data->attached_dev; +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); + bool afc_err = false; +#endif + bool afc_nack = false; + int ret = 0; + + /* W/A: vbadc of opcode result is 0, but vbadc register value is not 0 */ + if (vbadc == 0 && vbadc2 > 0) + vbadc = data[2] = vbadc2; + + md75_info_usb("%s:%s result:0x%x vbadc:0x%x\n", MUIC_DEV_NAME, + __func__, data[1], data[2]); + + switch (result) { + case 0: + md75_info_usb("%s:%s QC2.0 Success, vbadc(%d)\n", MUIC_DEV_NAME, __func__, vbadc); + muic_data->afc_retry = 0; + + if (vbadc >= MAX77775_VBADC_4_5V_TO_5_5V && + vbadc <= MAX77775_VBADC_6_5V_TO_7_5V) + new_afc_dev = ATTACHED_DEV_QC_CHARGER_5V_MUIC; + else if (vbadc >= MAX77775_VBADC_7_5V_TO_8_5V && + vbadc <= MAX77775_VBADC_9_5V_TO_10_5V) + new_afc_dev = ATTACHED_DEV_QC_CHARGER_9V_MUIC; +#if defined(CONFIG_USB_HW_PARAM) + else + afc_err = true; +#endif + + if (new_afc_dev != muic_data->attached_dev) { +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + muic_notifier_attach_attached_dev(new_afc_dev); +#endif /* CONFIG_MUIC_NOTIFIER */ + muic_data->attached_dev = new_afc_dev; + } + break; + case 1: + md75_info_usb("%s:%s No CHGIN\n", MUIC_DEV_NAME, __func__); + if (muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_9V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_9V_MUIC) + afc_nack = true; + break; + case 2: + md75_info_usb("%s:%s Not High Voltage DCP\n", + MUIC_DEV_NAME, __func__); + if (muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_9V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_9V_MUIC) + afc_nack = true; + break; + case 3: + md75_info_usb("%s:%s Not DCP\n", MUIC_DEV_NAME, __func__); + if (muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_AFC_CHARGER_9V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_9V_MUIC) + afc_nack = true; + break; + case 6: + md75_info_usb("%s:%s Vbus is not changed with 3 continuous ping\n", + MUIC_DEV_NAME, __func__); + afc_nack = true; + break; + case 7: + md75_info_usb("%s:%s Vbus is not changed in 1 sec\n", + MUIC_DEV_NAME, __func__); + afc_nack = true; + break; + default: + md75_info_usb("%s:%s QC2.0 error(%d)\n", MUIC_DEV_NAME, __func__, result); + afc_nack = true; + break; + } + + if (afc_nack) { + if (muic_data->afc_retry++ < RETRY_COUNT) { + md75_info_usb("%s:%s Retry(%d)\n", MUIC_DEV_NAME, __func__, muic_data->afc_retry); +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + /* Charging TG's request, send PREPARE noti */ + muic_notifier_attach_attached_dev(ATTACHED_DEV_QC_CHARGER_PREPARE_MUIC); +#endif /* CONFIG_MUIC_NOTIFIER */ + max77775_muic_qc_hv_set(muic_data, muic_data->hv_voltage); + } else { + md75_info_usb("%s:%s Retry Done, do not retry\n", MUIC_DEV_NAME, __func__); + if (vbadc >= MAX77775_VBADC_7_5V_TO_8_5V) { + max77775_muic_afc_reset(muic_data); + muic_data->afc_retry = 0; + } else { +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + /* Send attached device noti to clear prepare noti */ + if (muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_5V_MUIC || + muic_data->attached_dev == ATTACHED_DEV_QC_CHARGER_9V_MUIC) + muic_notifier_attach_attached_dev(muic_data->attached_dev); + else + muic_notifier_attach_attached_dev(ATTACHED_DEV_QC_CHARGER_ERR_V_MUIC); +#endif /* CONFIG_MUIC_NOTIFIER */ + } + mutex_lock(&muic_data->afc_lock); + muic_data->pdata->afc_disabled_updated &= MAX77775_MUIC_AFC_DISABLE_CHANGE_DURING_WORK_END; + muic_data->pdata->afc_disabled_updated &= MAX77775_MUIC_AFC_SET_VOLTAGE_CHANGE_DURING_WORK_END; + muic_data->pdata->afc_disabled_updated &= MAX77775_MUIC_AFC_WORK_PROCESS_END; + mutex_unlock(&muic_data->afc_lock); +#if IS_ENABLED(CONFIG_SEC_ABC) +#if IS_ENABLED(CONFIG_SEC_FACTORY) + sec_abc_send_event("MODULE=muic@INFO=qc_hv_fail"); +#else + sec_abc_send_event("MODULE=muic@WARN=qc_hv_fail"); +#endif +#endif + } + } else { + mutex_lock(&muic_data->afc_lock); + if (muic_data->pdata->afc_disabled_updated & MAX77775_MUIC_AFC_DISABLE_CHANGE_DURING_WORK) { + max77775_muic_check_afc_disabled(muic_data); + muic_data->pdata->afc_disabled_updated &= MAX77775_MUIC_AFC_DISABLE_CHANGE_DURING_WORK_END; + } + + muic_data->pdata->afc_disabled_updated &= MAX77775_MUIC_AFC_WORK_PROCESS_END; + + if (muic_data->pdata->afc_disabled_updated & MAX77775_MUIC_AFC_SET_VOLTAGE_CHANGE_DURING_WORK) { + muic_data->pdata->afc_disabled_updated |= MAX77775_MUIC_AFC_WORK_PROCESS; + + ret = __max77775_muic_afc_set_voltage(muic_data, muic_data->reserve_hv_voltage); + if (ret < 0) + muic_data->pdata->afc_disabled_updated &= MAX77775_MUIC_AFC_WORK_PROCESS_END; + + muic_data->pdata->afc_disabled_updated &= MAX77775_MUIC_AFC_SET_VOLTAGE_CHANGE_DURING_WORK_END; + } + mutex_unlock(&muic_data->afc_lock); + } + +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) { + if (muic_data->is_skip_bigdata) + goto qc_out; + + if (afc_err && !afc_nack) + inc_hw_param(o_notify, USB_MUIC_AFC_ERROR_COUNT); + if (afc_nack) { + inc_hw_param(o_notify, USB_MUIC_AFC_ERROR_COUNT); + inc_hw_param(o_notify, USB_MUIC_AFC_NACK_COUNT); + muic_data->is_skip_bigdata = true; + } + } +qc_out: + return; +#endif +} diff --git a/drivers/usb/typec/maxim/max77775-muic-ccic.c b/drivers/usb/typec/maxim/max77775-muic-ccic.c new file mode 100755 index 000000000000..21bee315862b --- /dev/null +++ b/drivers/usb/typec/maxim/max77775-muic-ccic.c @@ -0,0 +1,310 @@ +/* + * muic_ccic.c + * + * Copyright (C) 2014 Samsung Electronics + * Thomas Ryu + * + * 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, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_USB_HOST_NOTIFY +#include +#endif +#include + +#include + +#include +#include +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) +#include +#endif + +#if IS_ENABLED(CONFIG_MUIC_SUPPORT_PDIC) +#include +#endif +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) +#include +#endif + +static void max77775_muic_init_ccic_info_data(struct max77775_muic_data *muic_data) +{ + md75_info_usb("%s\n", __func__); + muic_data->ccic_info_data.ccic_evt_rid = RID_OPEN; + muic_data->ccic_info_data.ccic_evt_rprd = 0; + muic_data->ccic_info_data.ccic_evt_roleswap = 0; + muic_data->ccic_info_data.ccic_evt_dcdcnt = 0; + muic_data->ccic_info_data.ccic_evt_attached = MUIC_PDIC_NOTI_UNDEFINED; +} + +static void max77775_muic_handle_ccic_detach(struct max77775_muic_data *muic_data) +{ + md75_info_usb("%s\n", __func__); + muic_data->ccic_info_data.ccic_evt_rprd = 0; + muic_data->ccic_info_data.ccic_evt_roleswap = 0; + muic_data->ccic_info_data.ccic_evt_dcdcnt = 0; + muic_data->ccic_info_data.ccic_evt_attached = MUIC_PDIC_NOTI_DETACH; +} + +static int max77775_muic_handle_ccic_ATTACH(struct max77775_muic_data *muic_data, PD_NOTI_ATTACH_TYPEDEF *pnoti) +{ + bool need_to_run_work = false; + + md75_info_usb("%s: src:%d dest:%d id:%d attach:%d cable_type:%d rprd:%d\n", __func__, + pnoti->src, pnoti->dest, pnoti->id, pnoti->attach, pnoti->cable_type, pnoti->rprd); + + /* Attached */ + if (pnoti->attach) { + md75_info_usb("%s: Attach, cable type=%d\n", __func__, pnoti->cable_type); + + muic_data->ccic_info_data.ccic_evt_attached = MUIC_PDIC_NOTI_ATTACH; + + if (muic_data->ccic_info_data.ccic_evt_roleswap) { + md75_info_usb("%s: roleswap event, attach USB\n", __func__); + muic_data->ccic_info_data.ccic_evt_roleswap = 0; + need_to_run_work = true; + } + + if (pnoti->rprd) { + md75_info_usb("%s: RPRD\n", __func__); + muic_data->ccic_info_data.ccic_evt_rprd = 1; + need_to_run_work = true; + } + + /* CCIC ATTACH means NO WATER */ + if (muic_data->afc_water_disable) { + muic_data->afc_water_disable = false; + muic_data->ccic_evt_id = PDIC_NOTIFY_ID_WATER; + need_to_run_work = true; + } + } else { + if (pnoti->rprd) { + /* Role swap detach: attached=0, rprd=1 */ + md75_info_usb("%s: role swap event\n", __func__); + muic_data->ccic_info_data.ccic_evt_roleswap = 1; + } else { + /* Detached */ + if (muic_data->ccic_info_data.ccic_evt_rprd) + need_to_run_work = true; + max77775_muic_handle_ccic_detach(muic_data); + } + } + + /* run muic event handler */ + if (need_to_run_work) { + md75_info_usb("%s: do workqueue\n", __func__); + schedule_work(&(muic_data->ccic_info_data_work)); + } + + return 0; +} + +static int max77775_muic_handle_ccic_RID(struct max77775_muic_data *muic_data, PD_NOTI_RID_TYPEDEF *pnoti) +{ + int prev_rid = muic_data->ccic_info_data.ccic_evt_rid; + int rid = pnoti->rid; + + md75_info_usb("%s: src:%d dest:%d id:%d rid:%d sub2:%d sub3:%d\n", __func__, + pnoti->src, pnoti->dest, pnoti->id, pnoti->rid, pnoti->sub2, pnoti->sub3); + + if (rid > RID_OPEN || rid <= RID_UNDEFINED) { + md75_info_usb("%s: Out of range of RID(%d)\n", __func__, rid); + return 0; + } + + muic_data->ccic_info_data.ccic_evt_rid = rid; + + switch (rid) { + case RID_000K: + case RID_056K: + case RID_255K: + case RID_301K: + case RID_523K: + case RID_619K: + case RID_OPEN: + if (prev_rid != rid) { + md75_info_usb("%s: do workqueue\n", __func__); + schedule_work(&(muic_data->ccic_info_data_work)); + } + break; + default: + md75_err_usb("%s:Not determined now\n", __func__); + break; + } + + return 0; +} + +static int max77775_muic_handle_ccic_WATER(struct max77775_muic_data *muic_data, PD_NOTI_ATTACH_TYPEDEF *pnoti) +{ + md75_info_usb("%s: src:%d dest:%d id:%d attach:%d cable_type:%d rprd:%d\n", __func__, + pnoti->src, pnoti->dest, pnoti->id, pnoti->attach, pnoti->cable_type, pnoti->rprd); + + if (pnoti->attach == PDIC_NOTIFY_ATTACH) { + muic_data->afc_water_disable = true; + md75_info_usb("%s: Water detect, do workqueue\n", __func__); + schedule_work(&(muic_data->ccic_info_data_work)); + } else if (pnoti->attach == PDIC_NOTIFY_DETACH) { + muic_data->afc_water_disable = false; + muic_data->ccic_evt_id = PDIC_NOTIFY_ID_WATER; + schedule_work(&(muic_data->ccic_info_data_work)); + md75_info_usb("%s: Dry detect, do workqueue\n", __func__); + } + + return 0; +} + +static void max77775_muic_handle_ccic_usb(struct max77775_muic_data *muic_data, + PD_NOTI_USB_STATUS_TYPEDEF *pnoti) +{ + if (pnoti->attach == PDIC_NOTIFY_ATTACH) { + md75_info_usb("%s attach, return\n", __func__); + return; + } + + switch (muic_data->attached_dev) { + case ATTACHED_DEV_USB_MUIC: + case ATTACHED_DEV_CDP_MUIC: + case ATTACHED_DEV_TIMEOUT_OPEN_MUIC: + muic_data->is_usb_fail = true; +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77775_AFC) && IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + if (max77775_muic_check_is_enable_afc(muic_data, muic_data->attached_dev)) { + md75_info_usb("%s afc work 1200ms\n", __func__); + + __pm_wakeup_event(muic_data->afc_retry_ws, 1500); + cancel_delayed_work_sync(&(muic_data->afc_work)); + schedule_delayed_work(&(muic_data->afc_work), + msecs_to_jiffies(1200)); + } +#endif + break; + default: + break; + } +} + +#if defined(CONFIG_HICCUP_CHARGER) +static int max77775_muic_handle_ccic_hiccup(struct max77775_muic_data *muic_data, PD_NOTI_ATTACH_TYPEDEF *pnoti) +{ + md75_info_usb("%s: src:%d dest:%d id:%d attach:%d cable_type:%d rprd:%d\n", __func__, + pnoti->src, pnoti->dest, pnoti->id, pnoti->attach, pnoti->cable_type, pnoti->rprd); + + if (muic_data->pdata->muic_set_hiccup_mode_cb) { + if (pnoti->attach == PDIC_NOTIFY_ATTACH) + muic_data->pdata->muic_set_hiccup_mode_cb(MUIC_HICCUP_MODE_ON); + else + muic_data->pdata->muic_set_hiccup_mode_cb(MUIC_HICCUP_MODE_OFF); + } + + return 0; +} +#endif +static int max77775_muic_handle_ccic_notification(struct notifier_block *nb, + unsigned long action, void *data) +{ + PD_NOTI_TYPEDEF *pnoti = (PD_NOTI_TYPEDEF *)data; +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + struct max77775_muic_data *muic_data = container_of(nb, struct max77775_muic_data, manager_nb); +#else + struct max77775_muic_data *muic_data = container_of(nb, struct max77775_muic_data, ccic_nb); +#endif + + md75_info_usb("%s: Rcvd Noti=> action: %d src:%d dest:%d id:%d sub[%d %d %d]\n", __func__, + (int)action, pnoti->src, pnoti->dest, pnoti->id, pnoti->sub1, pnoti->sub2, pnoti->sub3); + +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + if (pnoti->dest != PDIC_NOTIFY_DEV_MUIC) { + md75_info_usb("%s destination id is invalid\n", __func__); + return 0; + } +#endif + muic_data->ccic_evt_id = pnoti->id; + + switch (pnoti->id) { + case PDIC_NOTIFY_ID_ATTACH: + md75_info_usb("%s: PDIC_NOTIFY_ID_ATTACH: %s\n", __func__, + pnoti->sub1 ? "Attached" : "Detached"); + max77775_muic_handle_ccic_ATTACH(muic_data, (PD_NOTI_ATTACH_TYPEDEF *)pnoti); + break; + case PDIC_NOTIFY_ID_RID: + md75_info_usb("%s: PDIC_NOTIFY_ID_RID\n", __func__); + max77775_muic_handle_ccic_RID(muic_data, (PD_NOTI_RID_TYPEDEF *)pnoti); + break; + case PDIC_NOTIFY_ID_WATER: + md75_info_usb("%s: PDIC_NOTIFY_ID_WATER\n", __func__); + max77775_muic_handle_ccic_WATER(muic_data, (PD_NOTI_ATTACH_TYPEDEF *)pnoti); + break; + case PDIC_NOTIFY_ID_WATER_CABLE: + md75_info_usb("%s: PDIC_NOTIFY_ID_WATER_CABLE\n", __func__); +#if defined(CONFIG_HICCUP_CHARGER) + max77775_muic_handle_ccic_hiccup(muic_data, (PD_NOTI_ATTACH_TYPEDEF *)pnoti); +#endif + break; + case PDIC_NOTIFY_ID_USB: + md75_info_usb("%s: PDIC_NOTIFY_ID_USB\n", __func__); + max77775_muic_handle_ccic_usb(muic_data, (PD_NOTI_USB_STATUS_TYPEDEF *)pnoti); + break; + default: + md75_info_usb("%s: Undefined Noti. ID\n", __func__); + return NOTIFY_DONE; + } + + return NOTIFY_DONE; +} + +void max77775_muic_register_ccic_notifier(struct max77775_muic_data *muic_data) +{ + int ret = 0; + + md75_info_usb("%s: Registering PDIC_NOTIFY_DEV_MUIC.\n", __func__); + + max77775_muic_init_ccic_info_data(muic_data); +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + ret = manager_notifier_register(&muic_data->manager_nb, + max77775_muic_handle_ccic_notification, MANAGER_NOTIFY_PDIC_MUIC); +#else + ret = ccic_notifier_register(&muic_data->ccic_nb, + max77775_muic_handle_ccic_notification, PDIC_NOTIFY_DEV_MUIC); +#endif + if (ret < 0) { + md75_info_usb("%s: PDIC Noti. is not ready\n", __func__); + return; + } + + md75_info_usb("%s: done.\n", __func__); +} + +void max77775_muic_unregister_ccic_notifier(struct max77775_muic_data *muic_data) +{ + int ret = 0; + +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + ret = manager_notifier_unregister(&muic_data->manager_nb); +#else + ret = ccic_notifier_unregister(&muic_data->ccic_nb); +#endif + if (ret < 0) { + md75_info_usb("%s: PDIC Noti. is not ready\n", __func__); + return; + } + + md75_info_usb("%s: done.\n", __func__); +} diff --git a/drivers/usb/typec/maxim/max77775-muic.c b/drivers/usb/typec/maxim/max77775-muic.c new file mode 100755 index 000000000000..649e3beb1b20 --- /dev/null +++ b/drivers/usb/typec/maxim/max77775-muic.c @@ -0,0 +1,2565 @@ +/* + * max77775-muic.c - MUIC driver for the Maxim 77775 + * + * Copyright (C) 2015 Samsung Electronics + * Insun Choi + * + * 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, see . + */ + + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_ANDROID_SWITCH) || IS_ENABLED(CONFIG_SWITCH) +#include +#endif +/* MUIC header file */ +#include +#include +#include +#include +#include +#include +#include + +#if IS_ENABLED(CONFIG_MUIC_SUPPORT_PDIC) +#include +#endif + +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) +#include +#endif /* CONFIG_MUIC_NOTIFIER */ + +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) +#include +#endif /* CONFIG_VBUS_NOTIFIER */ + +#if IS_ENABLED(CONFIG_USB_EXTERNAL_NOTIFY) +#include +#endif + +#include + +#include + +#include + +struct max77775_muic_data *g_muic_data; + +/* for the bringup, should be fixed */ +void __init_usbc_cmd_data(usbc_cmd_data *cmd_data) +{ + pr_warn("%s is not defined!\n", __func__); +} +int __max77775_usbc_opcode_write(struct max77775_usbc_platform_data *usbc_data, + usbc_cmd_data *write_op) +{ + pr_warn("%s is not defined!\n", __func__); + return 0; +} +void init_usbc_cmd_data(usbc_cmd_data *cmd_data) + __attribute__((weak, alias("__init_usbc_cmd_data"))); +int max77775_usbc_opcode_write(struct max77775_usbc_platform_data *usbc_data, + usbc_cmd_data *write_op) + __attribute__((weak, alias("__max77775_usbc_opcode_write"))); + +static bool debug_en_vps; +static void max77775_muic_detect_dev(struct max77775_muic_data *muic_data, int irq); + +struct max77775_muic_vps_data { + int adc; + int vbvolt; + int chgtyp; + int muic_switch; + const char *vps_name; + const muic_attached_dev_t attached_dev; +}; + +static int max77775_muic_read_reg + (struct i2c_client *i2c, u8 reg, u8 *value) +{ + int ret = max77775_read_reg(i2c, reg, value); + + return ret; +} + +#if 0 +static int max77775_muic_write_reg + (struct i2c_client *i2c, u8 reg, u8 value, bool debug_en) +{ + int ret = max77775_write_reg(i2c, reg, value); + + if (debug_en) + md75_info_usb("%s Reg[0x%02x]: 0x%02x\n", + __func__, reg, value); + + return ret; +} +#endif + +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) +static void max77775_muic_handle_vbus(struct max77775_muic_data *muic_data) +{ + int vbvolt = muic_data->status3 & BC_STATUS_VBUSDET_MASK; + vbus_status_t status = (vbvolt > 0) ? + STATUS_VBUS_HIGH : STATUS_VBUS_LOW; + + md75_info_usb("%s <%d>\n", __func__, status); + + vbus_notifier_handle(status); +} +#endif + +static const struct max77775_muic_vps_data muic_vps_table[] = { + { + .adc = MAX77775_UIADC_523K, + .vbvolt = VB_DONTCARE, + .chgtyp = CHGTYP_DONTCARE, + .muic_switch = COM_UART, + .vps_name = "JIG UART OFF", + .attached_dev = ATTACHED_DEV_JIG_UART_OFF_MUIC, + }, + { + .adc = MAX77775_UIADC_619K, + .vbvolt = VB_LOW, + .chgtyp = CHGTYP_NO_VOLTAGE, + .muic_switch = COM_UART, + .vps_name = "JIG UART ON", + .attached_dev = ATTACHED_DEV_JIG_UART_ON_MUIC, + }, + { + .adc = MAX77775_UIADC_619K, + .vbvolt = VB_HIGH, + .chgtyp = CHGTYP_DONTCARE, + .muic_switch = COM_UART, + .vps_name = "JIG UART ON/VB", + .attached_dev = ATTACHED_DEV_JIG_UART_ON_VB_MUIC, + }, + { + .adc = MAX77775_UIADC_255K, +#if IS_ENABLED(CONFIG_SEC_FACTORY) + .vbvolt = VB_DONTCARE, +#else + .vbvolt = VB_HIGH, +#endif + .chgtyp = CHGTYP_DONTCARE, + .muic_switch = COM_USB, + .vps_name = "JIG USB OFF", + .attached_dev = ATTACHED_DEV_JIG_USB_OFF_MUIC, + }, + { + .adc = MAX77775_UIADC_301K, + .vbvolt = VB_DONTCARE, + .chgtyp = CHGTYP_DONTCARE, + .muic_switch = COM_USB, + .vps_name = "JIG USB ON", + .attached_dev = ATTACHED_DEV_JIG_USB_ON_MUIC, + }, + { + .adc = MAX77775_UIADC_OPEN, + .vbvolt = VB_HIGH, + .chgtyp = CHGTYP_DEDICATED_CHARGER, + .muic_switch = COM_OPEN, + .vps_name = "TA", + .attached_dev = ATTACHED_DEV_TA_MUIC, + }, + { + .adc = MAX77775_UIADC_OPEN, + .vbvolt = VB_HIGH, + .chgtyp = CHGTYP_USB, + .muic_switch = COM_USB, + .vps_name = "USB", + .attached_dev = ATTACHED_DEV_USB_MUIC, + }, + { + .adc = MAX77775_UIADC_OPEN, + .vbvolt = VB_HIGH, + .chgtyp = CHGTYP_TIMEOUT_OPEN, + .muic_switch = COM_USB, + .vps_name = "DCD Timeout", + .attached_dev = ATTACHED_DEV_TIMEOUT_OPEN_MUIC, + }, + { + .adc = MAX77775_UIADC_OPEN, + .vbvolt = VB_HIGH, + .chgtyp = CHGTYP_CDP, + .muic_switch = COM_USB, + .vps_name = "CDP", + .attached_dev = ATTACHED_DEV_CDP_MUIC, + }, + { + .adc = MAX77775_UIADC_GND, + .vbvolt = VB_DONTCARE, + .chgtyp = CHGTYP_DONTCARE, + .muic_switch = COM_USB, + .vps_name = "OTG", + .attached_dev = ATTACHED_DEV_OTG_MUIC, + }, + { + .adc = MAX77775_UIADC_OPEN, + .vbvolt = VB_HIGH, + .chgtyp = CHGTYP_UNOFFICIAL_CHARGER, + .muic_switch = COM_OPEN, + .vps_name = "Unofficial TA", + .attached_dev = ATTACHED_DEV_UNOFFICIAL_TA_MUIC, + }, +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) + { + .adc = MAX77775_UIADC_OPEN, + .vbvolt = VB_HIGH, + .chgtyp = CHGTYP_HICCUP_MODE, + .muic_switch = COM_OPEN, + .vps_name = "Hiccup mode", + .attached_dev = ATTACHED_DEV_HICCUP_MUIC, + }, +#endif +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77775_AFC) + { + .adc = MAX77775_UIADC_OPEN, + .vbvolt = VB_HIGH, + .chgtyp = CHGTYP_DEDICATED_CHARGER, + .muic_switch = COM_OPEN, + .vps_name = "AFC Charger", + .attached_dev = ATTACHED_DEV_AFC_CHARGER_9V_MUIC, + }, + { + .adc = MAX77775_UIADC_OPEN, + .vbvolt = VB_HIGH, + .chgtyp = CHGTYP_DEDICATED_CHARGER, + .muic_switch = COM_OPEN, + .vps_name = "AFC Charger", + .attached_dev = ATTACHED_DEV_AFC_CHARGER_5V_MUIC, + }, + { + .adc = MAX77775_UIADC_OPEN, + .vbvolt = VB_HIGH, + .chgtyp = CHGTYP_DEDICATED_CHARGER, + .muic_switch = COM_OPEN, + .vps_name = "QC Charger", + .attached_dev = ATTACHED_DEV_QC_CHARGER_9V_MUIC, + }, + { + .adc = MAX77775_UIADC_OPEN, + .vbvolt = VB_HIGH, + .chgtyp = CHGTYP_DEDICATED_CHARGER, + .muic_switch = COM_OPEN, + .vps_name = "QC Charger", + .attached_dev = ATTACHED_DEV_QC_CHARGER_5V_MUIC, + }, +#endif +}; + +static int muic_lookup_vps_table(muic_attached_dev_t new_dev, + struct max77775_muic_data *muic_data) +{ + int i; + struct i2c_client *i2c = muic_data->i2c; + u8 reg_data; + const struct max77775_muic_vps_data *tmp_vps; + + max77775_muic_read_reg(i2c, MAX77775_USBC_REG_USBC_STATUS2, ®_data); + reg_data = reg_data & USBC_STATUS2_SYSMSG_MASK; + md75_info_usb("%s Last sysmsg = 0x%02x\n", __func__, reg_data); + + for (i = 0; i < (int)ARRAY_SIZE(muic_vps_table); i++) { + tmp_vps = &(muic_vps_table[i]); + + if (tmp_vps->attached_dev != new_dev) + continue; + + md75_info_usb("%s (%d) vps table match found at i(%d), %s\n", + __func__, new_dev, i, + tmp_vps->vps_name); + + return i; + } + + md75_info_usb("%s can't find (%d) on vps table\n", + __func__, new_dev); + + return -ENODEV; +} + +static void max77775_switch_path(struct max77775_muic_data *muic_data, + u8 reg_val) +{ + struct max77775_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + usbc_cmd_data write_data; + + md75_info_usb("%s value(0x%x)\n", __func__, reg_val); + +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + if (muic_data->usbc_pdata->fac_water_enable) { + md75_info_usb("%s fac_water_enable(%d), skip\n", __func__, + muic_data->usbc_pdata->fac_water_enable); + return; + } +#endif + + init_usbc_cmd_data(&write_data); + write_data.opcode = COMMAND_CONTROL1_WRITE; + write_data.write_length = 2; + write_data.write_data[0] = reg_val & NOBCCOMP_USBAUTODET_MASK; + write_data.write_data[1] = reg_val & COMSW_MASK; + write_data.read_length = 0; + + max77775_usbc_opcode_write(usbc_pdata, &write_data); +} + +static void com_to_open(struct max77775_muic_data *muic_data) +{ + u8 reg_val; + + md75_info_usb("%s\n", __func__); + + reg_val = COM_OPEN; + + /* write command - switch */ + max77775_switch_path(muic_data, reg_val); +} + +static int com_to_usb_ap(struct max77775_muic_data *muic_data) +{ + u8 reg_val; + int ret = 0; + + md75_info_usb("%s\n", __func__); + + reg_val = COM_USB; + + /* write command - switch */ + max77775_switch_path(muic_data, reg_val); + + return ret; +} + +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) +static void max77775_muic_hiccup_clear(struct max77775_muic_data *muic_data) +{ + struct max77775_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + usbc_cmd_data clear_data; + + clear_data.opcode = OPCODE_HICCUP_ENABLE; + clear_data.write_length = 1; + clear_data.write_data[0] = 0x0; /* Clear SW_CTRL1(open/open) and FC_CTRL */ + clear_data.read_length = 4; + + md75_info_usb("%s\n", __func__); + + max77775_usbc_opcode_write(usbc_pdata, &clear_data); +} + +static void max77775_muic_hiccup_on(struct max77775_muic_data *muic_data) +{ + struct max77775_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + usbc_cmd_data write_data; + + write_data.opcode = OPCODE_HICCUP_ENABLE; + write_data.write_length = 1; + write_data.write_data[0] = 0x1; /* Turn On SW_CTRL1 and FC_CTRL for hiccup */ + write_data.read_length = 4; + + md75_info_usb("%s: set DP/DN to GND\n", __func__); + max77775_usbc_opcode_write(usbc_pdata, &write_data); +} +#endif + +static void com_to_uart_ap(struct max77775_muic_data *muic_data) +{ + u8 reg_val; + if ((muic_data->pdata->opmode == OPMODE_MUIC) && muic_data->pdata->rustproof_on) + reg_val = COM_OPEN; + else + reg_val = COM_UART; + + md75_info_usb("%s(%d)\n", __func__, reg_val); + + /* write command - switch */ + max77775_switch_path(muic_data, reg_val); +} + +static void com_to_uart_cp(struct max77775_muic_data *muic_data) +{ + u8 reg_val; + + if ((muic_data->pdata->opmode == OPMODE_MUIC) && muic_data->pdata->rustproof_on) + reg_val = COM_OPEN; + else + reg_val = COM_UART_CP; + + md75_info_usb("%s(%d)\n", __func__, reg_val); + + /* write command - switch */ + max77775_switch_path(muic_data, reg_val); +} + +static int write_vps_regs(struct max77775_muic_data *muic_data, + muic_attached_dev_t new_dev) +{ + const struct max77775_muic_vps_data *tmp_vps; + int vps_index; + u8 prev_switch; + + vps_index = muic_lookup_vps_table(muic_data->attached_dev, muic_data); + if (vps_index < 0) { + md75_info_usb("%s: prev cable is none.\n", __func__); + prev_switch = COM_OPEN; + } else { + /* Prev cable information. */ + tmp_vps = &(muic_vps_table[vps_index]); + prev_switch = tmp_vps->muic_switch; + } + + if (prev_switch == muic_data->switch_val) + md75_info_usb("%s Duplicated(0x%02x), just ignore\n", + __func__, muic_data->switch_val); +#if 0 + else { + /* write command - switch */ + max77775_switch_path(muic_data, muic_data->switch_val); + } +#endif + + return 0; +} + +/* muic uart path control function */ +static int switch_to_ap_uart(struct max77775_muic_data *muic_data, + muic_attached_dev_t new_dev) +{ + int ret = 0; + + switch (new_dev) { + case ATTACHED_DEV_JIG_UART_OFF_MUIC: + case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC: + case ATTACHED_DEV_JIG_UART_ON_MUIC: + case ATTACHED_DEV_JIG_UART_ON_VB_MUIC: + com_to_uart_ap(muic_data); + break; + default: + pr_warn("%s current attached is (%d) not Jig UART Off\n", + __func__, muic_data->attached_dev); + break; + } + + return ret; +} + +static int switch_to_cp_uart(struct max77775_muic_data *muic_data, + muic_attached_dev_t new_dev) +{ + int ret = 0; + + switch (new_dev) { + case ATTACHED_DEV_JIG_UART_OFF_MUIC: + case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC: + case ATTACHED_DEV_JIG_UART_ON_MUIC: + case ATTACHED_DEV_JIG_UART_ON_VB_MUIC: + com_to_uart_cp(muic_data); + break; + default: + pr_warn("%s current attached is (%d) not Jig UART Off\n", + __func__, muic_data->attached_dev); + break; + } + + return ret; +} + +void max77775_muic_enable_detecting_short(struct max77775_muic_data *muic_data) +{ + + struct max77775_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + usbc_cmd_data write_data; + + md75_info_usb("%s\n", __func__); + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_SAMSUNG_CCSBU_SHORT; + write_data.write_length = 1; + /* + * bit 0: Enable detecting vbus-cc short + * bit 1: Enable detecting sbu-gnd short + * bit 2: Enable detecting vbus-sbu short + */ +#if !defined(CONFIG_SEC_FACTORY) + write_data.write_data[0] = 0x7; +#else + /* W/A, in factory mode, sbu-gnd short disable */ + write_data.write_data[0] = 0x5; +#endif + write_data.read_length = 1; + + max77775_usbc_opcode_write(usbc_pdata, &write_data); + +} + +static void max77775_muic_dp_reset(struct max77775_muic_data *muic_data) +{ + struct max77775_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + usbc_cmd_data update_data; + + md75_info_usb("%s\n", __func__); + + init_usbc_cmd_data(&update_data); + update_data.opcode = COMMAND_BC_CTRL2_READ; + update_data.mask = BC_CTRL2_DPDNMan_MASK | BC_CTRL2_DPDrv_MASK; + update_data.val = 0x10; + + max77775_usbc_opcode_update(usbc_pdata, &update_data); +} + +static void max77775_muic_enable_chgdet(struct max77775_muic_data *muic_data) +{ + struct max77775_usbc_platform_data *usbc_pdata = muic_data->usbc_pdata; + usbc_cmd_data update_data; + + md75_info_usb("%s\n", __func__); + + init_usbc_cmd_data(&update_data); + update_data.opcode = COMMAND_BC_CTRL1_READ; + update_data.mask = BC_CTRL1_CHGDetEn_MASK | BC_CTRL1_CHGDetMan_MASK; + update_data.val = 0xff; + + max77775_usbc_opcode_update(usbc_pdata, &update_data); +} + +static u8 max77775_muic_get_adc_value(struct max77775_muic_data *muic_data) +{ + u8 status; + u8 adc = MAX77775_UIADC_ERROR; + int ret; + + ret = max77775_muic_read_reg(muic_data->i2c, + MAX77775_USBC_REG_USBC_STATUS1, &status); + if (ret) + md75_err_usb("%s fail to read muic reg(%d)\n", + __func__, ret); + else + adc = status & USBC_STATUS1_UIADC_MASK; + + return adc; +} + +static u8 max77775_muic_get_vbadc_value(struct max77775_muic_data *muic_data) +{ + u8 status; + u8 vbadc = 0; + int ret; + + ret = max77775_muic_read_reg(muic_data->i2c, + MAX77775_USBC_REG_USBC_STATUS1, &status); + if (ret) + md75_err_usb("%s fail to read muic reg(%d)\n", + __func__, ret); + else + vbadc = (status & USBC_STATUS1_VBADC_MASK) >> USBC_STATUS1_VBADC_SHIFT; + + return vbadc; +} + +static ssize_t max77775_muic_show_uart_sel(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct max77775_muic_data *muic_data = dev_get_drvdata(dev); + struct muic_platform_data *pdata = muic_data->pdata; + const char *mode = "UNKNOWN\n"; + + switch (pdata->uart_path) { + case MUIC_PATH_UART_AP: + mode = "AP\n"; + break; + case MUIC_PATH_UART_CP: + mode = "CP\n"; + break; + default: + break; + } + + md75_info_usb("%s %s", __func__, mode); + return sprintf(buf, "%s", mode); +} + +static ssize_t max77775_muic_set_uart_sel(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct max77775_muic_data *muic_data = dev_get_drvdata(dev); + struct muic_platform_data *pdata = muic_data->pdata; + + mutex_lock(&muic_data->muic_mutex); + + if (!strncasecmp(buf, "AP", 2)) { + pdata->uart_path = MUIC_PATH_UART_AP; + switch_to_ap_uart(muic_data, muic_data->attached_dev); + } else if (!strncasecmp(buf, "CP", 2)) { + pdata->uart_path = MUIC_PATH_UART_CP; + switch_to_cp_uart(muic_data, muic_data->attached_dev); + } else { + pr_warn("%s invalid value\n", __func__); + } + + md75_info_usb("%s uart_path(%d)\n", __func__, + pdata->uart_path); + + mutex_unlock(&muic_data->muic_mutex); + + return count; +} + +static ssize_t max77775_muic_show_usb_sel(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct max77775_muic_data *muic_data = dev_get_drvdata(dev); + struct muic_platform_data *pdata = muic_data->pdata; + const char *mode = "UNKNOWN\n"; + + switch (pdata->usb_path) { + case MUIC_PATH_USB_AP: + mode = "PDA\n"; + break; + case MUIC_PATH_USB_CP: + mode = "MODEM\n"; + break; + default: + break; + } + + pr_debug("%s %s", __func__, mode); + return sprintf(buf, "%s", mode); +} + +static ssize_t max77775_muic_set_usb_sel(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct max77775_muic_data *muic_data = dev_get_drvdata(dev); + struct muic_platform_data *pdata = muic_data->pdata; + + if (!strncasecmp(buf, "PDA", 3)) + pdata->usb_path = MUIC_PATH_USB_AP; + else if (!strncasecmp(buf, "MODEM", 5)) + pdata->usb_path = MUIC_PATH_USB_CP; + else + pr_warn("%s invalid value\n", __func__); + + md75_info_usb("%s usb_path(%d)\n", __func__, + pdata->usb_path); + + return count; +} + +static ssize_t max77775_muic_show_uart_en(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct max77775_muic_data *muic_data = dev_get_drvdata(dev); + struct muic_platform_data *pdata = muic_data->pdata; + + if (!pdata->rustproof_on) { + md75_info_usb("%s UART ENABLE\n", __func__); + return sprintf(buf, "1\n"); + } + + md75_info_usb("%s UART DISABLE", __func__); + return sprintf(buf, "0\n"); +} + +static ssize_t max77775_muic_set_uart_en(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct max77775_muic_data *muic_data = dev_get_drvdata(dev); + struct muic_platform_data *pdata = muic_data->pdata; + + if (!strncasecmp(buf, "1", 1)) + pdata->rustproof_on = false; + else if (!strncasecmp(buf, "0", 1)) + pdata->rustproof_on = true; + else + pr_warn("%s invalid value\n", __func__); + + md75_info_usb("%s uart_en(%d)\n", __func__, + !pdata->rustproof_on); + + return count; +} + +static ssize_t max77775_muic_show_adc(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct max77775_muic_data *muic_data = dev_get_drvdata(dev); + u8 adc; + + adc = max77775_muic_get_adc_value(muic_data); + md75_info_usb("%s adc(0x%02x)\n", __func__, adc); + + if (adc == MAX77775_UIADC_ERROR) { + md75_err_usb("%s fail to read adc value\n", + __func__); + return sprintf(buf, "UNKNOWN\n"); + } + + switch (adc) { + case MAX77775_UIADC_GND: + adc = 0; + break; + case MAX77775_UIADC_255K: + adc = 0x18; + break; + case MAX77775_UIADC_301K: + adc = 0x19; + break; + case MAX77775_UIADC_523K: + adc = 0x1c; + break; + case MAX77775_UIADC_619K: + adc = 0x1d; + break; + case MAX77775_UIADC_OPEN: + adc = 0x1f; + break; + default: + adc = 0xff; + } + + return sprintf(buf, "adc: 0x%x\n", adc); +} + +static ssize_t max77775_muic_show_usb_state(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct max77775_muic_data *muic_data = dev_get_drvdata(dev); + + pr_debug("%s attached_dev(%d)\n", __func__, + muic_data->attached_dev); + + switch (muic_data->attached_dev) { + case ATTACHED_DEV_USB_MUIC: + return sprintf(buf, "USB_STATE_CONFIGURED\n"); + default: + break; + } + + return sprintf(buf, "USB_STATE_NOTCONFIGURED\n"); +} + +static ssize_t max77775_muic_show_attached_dev(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct max77775_muic_data *muic_data = dev_get_drvdata(dev); + const struct max77775_muic_vps_data *tmp_vps; + int vps_index; + + vps_index = muic_lookup_vps_table(muic_data->attached_dev, muic_data); + if (vps_index < 0) + return sprintf(buf, "No VPS\n"); + + tmp_vps = &(muic_vps_table[vps_index]); + + return sprintf(buf, "%s\n", tmp_vps->vps_name); +} + +static ssize_t max77775_muic_show_otg_test(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct max77775_muic_data *muic_data = dev_get_drvdata(dev); + int ret = -ENODEV; + + if (muic_data->is_otg_test == true) + ret = 0; + else + ret = 1; + md75_info_usb("%s ret:%d\n", __func__, ret); + + return sprintf(buf, "%d\n", ret); +} + +static ssize_t max77775_muic_set_otg_test(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct max77775_muic_data *muic_data = dev_get_drvdata(dev); + int ret = -ENODEV; + + mutex_lock(&muic_data->muic_mutex); + + md75_info_usb("%s buf:%s\n", __func__, buf); + if (!strncmp(buf, "0", 1)) { + muic_data->is_otg_test = true; + ret = 0; + } else if (!strncmp(buf, "1", 1)) { + muic_data->is_otg_test = false; + ret = 1; + } + + md75_info_usb("%s ret: %d\n", __func__, ret); + + mutex_unlock(&muic_data->muic_mutex); + return count; +} + +static ssize_t max77775_muic_show_apo_factory(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct max77775_muic_data *muic_data = dev_get_drvdata(dev); + const char *mode; + + /* true: Factory mode, false: not Factory mode */ + if (muic_data->is_factory_start) + mode = "FACTORY_MODE"; + else + mode = "NOT_FACTORY_MODE"; + + md75_info_usb("%s apo factory=%s\n", __func__, mode); + + return sprintf(buf, "%s\n", mode); +} + +static ssize_t max77775_muic_set_apo_factory(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ +#if IS_ENABLED(CONFIG_SEC_FACTORY) + struct max77775_muic_data *muic_data = dev_get_drvdata(dev); +#endif /* CONFIG_SEC_FACTORY */ + const char *mode; + + md75_info_usb("%s buf:%s\n", __func__, buf); + + /* "FACTORY_START": factory mode */ + if (!strncmp(buf, "FACTORY_START", 13)) { +#if IS_ENABLED(CONFIG_SEC_FACTORY) + muic_data->is_factory_start = true; +#endif /* CONFIG_SEC_FACTORY */ + mode = "FACTORY_MODE"; + } else { + pr_warn("%s Wrong command\n", __func__); + return count; + } + + md75_info_usb("%s apo factory=%s\n", __func__, mode); + + return count; +} + +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77775_AFC) || defined(CONFIG_SUPPORT_QC30) +static ssize_t max77775_muic_show_afc_disable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct max77775_muic_data *muic_data = dev_get_drvdata(dev); + struct muic_platform_data *pdata = muic_data->pdata; + + if (pdata->afc_disable) { + md75_info_usb("%s AFC DISABLE\n", __func__); + return sprintf(buf, "1\n"); + } + + md75_info_usb("%s AFC ENABLE", __func__); + return sprintf(buf, "0\n"); +} + +static ssize_t max77775_muic_set_afc_disable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct max77775_muic_data *muic_data = dev_get_drvdata(dev); + struct muic_platform_data *pdata = muic_data->pdata; + int param_val, ret = 0; + bool curr_val = pdata->afc_disable; + union power_supply_propval psy_val; + + if (!strncasecmp(buf, "1", 1)) { + /* Disable AFC */ + pdata->afc_disable = true; + muic_afc_request_cause_clear(); + } else if (!strncasecmp(buf, "0", 1)) { + /* Enable AFC */ + pdata->afc_disable = false; + } else { + pr_warn("%s invalid value\n", __func__); + } + + param_val = pdata->afc_disable ? '1' : '0'; + md75_info_usb("%s: param_val:%d\n", __func__, param_val); + + if (ret < 0) { + md75_info_usb("%s:set_param failed - %02x:%02x(%d)\n", __func__, + param_val, curr_val, ret); + + pdata->afc_disable = curr_val; + + return -EIO; + } else { + mutex_lock(&muic_data->afc_lock); + md75_info_usb("%s: afc_disable:%d (AFC %s)\n", __func__, + pdata->afc_disable, pdata->afc_disable ? "Disabled" : "Enabled"); + + if (pdata->afc_disabled_updated & MAX77775_MUIC_AFC_WORK_PROCESS) + pdata->afc_disabled_updated |= MAX77775_MUIC_AFC_DISABLE_CHANGE_DURING_WORK; + else + max77775_muic_check_afc_disabled(muic_data); + mutex_unlock(&muic_data->afc_lock); + } + + md75_info_usb("%s afc_disable(%d)\n", __func__, pdata->afc_disable); + + psy_val.intval = param_val; + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_HV_DISABLE, psy_val); + + return count; +} +#endif /* CONFIG_HV_MUIC_MAX77775_AFC */ + +static ssize_t max77775_muic_show_vbus_value(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct max77775_muic_data *muic_data = dev_get_drvdata(dev); + u8 vbadc; + + vbadc = max77775_muic_get_vbadc_value(muic_data); + md75_info_usb("%s vbadc(0x%02x)\n", __func__, vbadc); + + switch (vbadc) { + case MAX77775_VBADC_3_8V_UNDER: + vbadc = 0; + break; + case MAX77775_VBADC_3_8V_TO_4_5V ... MAX77775_VBADC_6_5V_TO_7_5V: + vbadc = 5; + break; + case MAX77775_VBADC_7_5V_TO_8_5V ... MAX77775_VBADC_8_5V_TO_9_5V: + vbadc = 9; + break; + default: + vbadc += 3; + } + + return sprintf(buf, "%d\n", vbadc); +} + +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) +#if defined(CONFIG_MAX77775_CCOPEN_AFTER_WATERCABLE) +static void max77775_muic_reset_hiccup_mode_for_watercable(struct max77775_muic_data *muic_data) +{ + /*Source Connection Status of Moisture Case, 0x0: unplug TA, 0x1:plug TA*/ + if (muic_data->usbc_pdata->ta_conn_status == 0x1) + max77775_ccic_event_work(muic_data->usbc_pdata, + PDIC_NOTIFY_DEV_MUIC, PDIC_NOTIFY_ID_WATER_CABLE, 1, 0, 0); +} +#endif +static ssize_t hiccup_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "ENABLE\n"); +} + +static ssize_t hiccup_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct max77775_muic_data *muic_data = dev_get_drvdata(dev); + + if (!strncasecmp(buf, "DISABLE", 7)) { + md75_info_usb("%s\n", __func__); + max77775_pdic_manual_ccopen_request(0); + max77775_muic_hiccup_clear(muic_data); + muic_data->is_hiccup_mode = MUIC_HICCUP_MODE_OFF; +#if defined(CONFIG_MAX77775_CCOPEN_AFTER_WATERCABLE) + max77775_muic_reset_hiccup_mode_for_watercable(muic_data); +#endif + } else + pr_warn("%s invalid com : %s\n", __func__, buf); + + return count; +} +#endif /* CONFIG_HICCUP_CHARGER */ + +static DEVICE_ATTR(uart_sel, 0664, max77775_muic_show_uart_sel, + max77775_muic_set_uart_sel); +static DEVICE_ATTR(usb_sel, 0664, max77775_muic_show_usb_sel, + max77775_muic_set_usb_sel); +static DEVICE_ATTR(uart_en, 0660, max77775_muic_show_uart_en, + max77775_muic_set_uart_en); +static DEVICE_ATTR(adc, 0444, max77775_muic_show_adc, NULL); +static DEVICE_ATTR(usb_state, 0444, max77775_muic_show_usb_state, NULL); +static DEVICE_ATTR(attached_dev, 0444, max77775_muic_show_attached_dev, NULL); +static DEVICE_ATTR(otg_test, 0664, + max77775_muic_show_otg_test, max77775_muic_set_otg_test); +static DEVICE_ATTR(apo_factory, 0664, + max77775_muic_show_apo_factory, max77775_muic_set_apo_factory); +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77775_AFC) +static DEVICE_ATTR(afc_disable, 0664, + max77775_muic_show_afc_disable, max77775_muic_set_afc_disable); +#endif /* CONFIG_HV_MUIC_MAX77775_AFC */ +static DEVICE_ATTR(vbus_value, 0444, max77775_muic_show_vbus_value, NULL); +static DEVICE_ATTR(vbus_value_pd, 0444, max77775_muic_show_vbus_value, NULL); +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) +static DEVICE_ATTR_RW(hiccup); +#endif /* CONFIG_HICCUP_CHARGER */ + +static struct attribute *max77775_muic_attributes[] = { + &dev_attr_uart_sel.attr, + &dev_attr_usb_sel.attr, + &dev_attr_uart_en.attr, + &dev_attr_adc.attr, + &dev_attr_usb_state.attr, + &dev_attr_attached_dev.attr, + &dev_attr_otg_test.attr, + &dev_attr_apo_factory.attr, +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77775_AFC) + &dev_attr_afc_disable.attr, +#endif /* CONFIG_HV_MUIC_MAX77775_AFC */ + &dev_attr_vbus_value.attr, + &dev_attr_vbus_value_pd.attr, +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) + &dev_attr_hiccup.attr, +#endif /* CONFIG_HICCUP_CHARGER */ + NULL +}; + +static const struct attribute_group max77775_muic_group = { + .attrs = max77775_muic_attributes, +}; + +void max77775_muic_read_register(struct i2c_client *i2c) +{ + const enum max77775_usbc_reg regfile[] = { + MAX77775_USBC_REG_PRODUCT_ID, + MAX77775_USBC_REG_USBC_STATUS1, + MAX77775_USBC_REG_USBC_STATUS2, + MAX77775_USBC_REG_BC_STATUS, + MAX77775_USBC_REG_UIC_INT_M, + }; + u8 val; + int i, ret; + + md75_info_usb("%s read register--------------\n", __func__); + for (i = 0; i < (int)ARRAY_SIZE(regfile); i++) { + ret = max77775_muic_read_reg(i2c, regfile[i], &val); + if (ret) { + md75_err_usb("%s fail to read muic reg(0x%02x), ret=%d\n", + __func__, regfile[i], ret); + continue; + } + + md75_info_usb("%s reg(0x%02x)=[0x%02x]\n", + __func__, regfile[i], val); + } + md75_info_usb("%s end register---------------\n", __func__); +} + +static int max77775_muic_attach_uart_path(struct max77775_muic_data *muic_data, + muic_attached_dev_t new_dev) +{ + struct muic_platform_data *pdata = muic_data->pdata; + int ret = 0; + + md75_info_usb("%s\n", __func__); + + if (pdata->uart_path == MUIC_PATH_UART_AP) + ret = switch_to_ap_uart(muic_data, new_dev); + else if (pdata->uart_path == MUIC_PATH_UART_CP) + ret = switch_to_cp_uart(muic_data, new_dev); + else + pr_warn("%s invalid uart_path\n", __func__); + + return ret; +} + +static int max77775_muic_handle_detach(struct max77775_muic_data *muic_data, int irq) +{ + int ret = 0; + muic_attached_dev_t attached_dev = muic_data->attached_dev; + +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77775_AFC) + /* Do workaround if Vbusdet goes to low status */ + if (muic_data->is_check_hv && irq == muic_data->irq_vbusdet && + (muic_data->status3 & BC_STATUS_VBUSDET_MASK) == 0) + muic_data->is_check_hv = false; + muic_data->hv_voltage = 0; + muic_data->afc_retry = 0; + muic_data->is_afc_reset = false; + muic_data->is_skip_bigdata = false; + muic_data->is_usb_fail = false; + cancel_delayed_work_sync(&(muic_data->afc_work)); + muic_data->pdata->afc_disabled_updated = MAX77775_MUIC_AFC_STATUS_CLEAR; + muic_afc_request_cause_clear(); +#endif + + if (attached_dev == ATTACHED_DEV_NONE_MUIC) { + md75_info_usb("%s Duplicated(%d), just ignore\n", + __func__, attached_dev); + goto out_without_noti; + } + + muic_data->dcdtmo_retry = 0; +#if 0 + /* Enable Charger Detection */ + max77775_muic_enable_chgdet(muic_data); +#endif + + muic_lookup_vps_table(attached_dev, muic_data); + + switch (attached_dev) { + case ATTACHED_DEV_TA_MUIC: + case ATTACHED_DEV_UNOFFICIAL_TA_MUIC: + case ATTACHED_DEV_AFC_CHARGER_5V_MUIC: + case ATTACHED_DEV_AFC_CHARGER_9V_MUIC: + case ATTACHED_DEV_QC_CHARGER_5V_MUIC: + case ATTACHED_DEV_QC_CHARGER_9V_MUIC: + if ((muic_data->status3 & BC_STATUS_VBUSDET_MASK) > 0) { + /* W/A for chgtype 0 irq when CC pin is only detached */ + md75_info_usb("%s Vbus is high, keep the current state(%d)\n", + __func__, attached_dev); + return 0; + } + break; + case ATTACHED_DEV_JIG_UART_OFF_MUIC: + case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC: + case ATTACHED_DEV_JIG_UART_OFF_VB_OTG_MUIC: + case ATTACHED_DEV_JIG_UART_OFF_VB_FG_MUIC: + case ATTACHED_DEV_JIG_UART_ON_MUIC: + case ATTACHED_DEV_JIG_UART_ON_VB_MUIC: + case ATTACHED_DEV_JIG_USB_OFF_MUIC: + case ATTACHED_DEV_JIG_USB_ON_MUIC: + case ATTACHED_DEV_NONE_MUIC: + com_to_open(muic_data); + break; + case ATTACHED_DEV_USB_MUIC: + case ATTACHED_DEV_CDP_MUIC: + case ATTACHED_DEV_OTG_MUIC: + case ATTACHED_DEV_TIMEOUT_OPEN_MUIC: + if (muic_data->ccic_info_data.ccic_evt_attached == MUIC_PDIC_NOTI_DETACH) + com_to_open(muic_data); + break; + case ATTACHED_DEV_UNOFFICIAL_ID_MUIC: + goto out_without_noti; + default: + break; + } + +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + muic_data->attached_dev = ATTACHED_DEV_NONE_MUIC; + muic_notifier_detach_attached_dev(attached_dev); +#endif /* CONFIG_MUIC_NOTIFIER */ + +out_without_noti: + muic_data->attached_dev = ATTACHED_DEV_NONE_MUIC; + return ret; +} + +static int max77775_muic_logically_detach(struct max77775_muic_data *muic_data, + muic_attached_dev_t new_dev) +{ + muic_attached_dev_t attached_dev = muic_data->attached_dev; + bool force_path_open = true; + int ret = 0; + + switch (attached_dev) { + case ATTACHED_DEV_OTG_MUIC: + break; + case ATTACHED_DEV_USB_MUIC: + case ATTACHED_DEV_CDP_MUIC: + case ATTACHED_DEV_TIMEOUT_OPEN_MUIC: + if (new_dev == ATTACHED_DEV_OTG_MUIC) { + md75_info_usb("%s: data role changed, not detach\n", __func__); + force_path_open = false; + goto out; + } + break; + case ATTACHED_DEV_UNDEFINED_CHARGING_MUIC: + case ATTACHED_DEV_JIG_USB_OFF_MUIC: + case ATTACHED_DEV_JIG_USB_ON_MUIC: + break; + case ATTACHED_DEV_JIG_UART_OFF_MUIC: + case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC: + case ATTACHED_DEV_JIG_UART_ON_MUIC: + case ATTACHED_DEV_JIG_UART_ON_VB_MUIC: + case ATTACHED_DEV_UNKNOWN_MUIC: + if (new_dev == ATTACHED_DEV_JIG_UART_OFF_MUIC || + new_dev == ATTACHED_DEV_JIG_UART_OFF_VB_MUIC || + new_dev == ATTACHED_DEV_JIG_UART_ON_MUIC || + new_dev == ATTACHED_DEV_JIG_UART_ON_VB_MUIC) + force_path_open = false; + break; + case ATTACHED_DEV_TA_MUIC: +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77775_AFC) + case ATTACHED_DEV_AFC_CHARGER_9V_MUIC: + case ATTACHED_DEV_AFC_CHARGER_5V_MUIC: + case ATTACHED_DEV_QC_CHARGER_9V_MUIC: + case ATTACHED_DEV_QC_CHARGER_5V_MUIC: +#endif +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) + if (new_dev == ATTACHED_DEV_HICCUP_MUIC) { + md75_info_usb("%s hiccup charger, do not logically detach\n", __func__); + force_path_open = false; + goto out; + } +#endif + break; + case ATTACHED_DEV_UNOFFICIAL_TA_MUIC: + break; +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) + case ATTACHED_DEV_HICCUP_MUIC: + break; +#endif /* CONFIG_HICCUP_CHARGER */ + case ATTACHED_DEV_NONE_MUIC: + force_path_open = false; + goto out; + default: + pr_warn("%s try to attach without logically detach\n", + __func__); + goto out; + } + + md75_info_usb("%s attached(%d)!=new(%d), assume detach\n", + __func__, attached_dev, new_dev); + + muic_data->attached_dev = ATTACHED_DEV_NONE_MUIC; +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + muic_notifier_detach_attached_dev(attached_dev); +#endif /* CONFIG_MUIC_NOTIFIER */ + +out: + if (force_path_open) + com_to_open(muic_data); + + return ret; +} + +static int max77775_muic_handle_attach(struct max77775_muic_data *muic_data, + muic_attached_dev_t new_dev, int irq) +{ + bool notify_skip = false; +#if IS_ENABLED(CONFIG_CCIC_MAX77775) + int fw_update_state = muic_data->usbc_pdata->max77775->fw_update_state; +#endif /* CONFIG_CCIC_MAX77775 */ +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif /* CONFIG_USB_HW_PARAM */ + int ret = 0; + + md75_info_usb("%s\n", __func__); + + if (new_dev == muic_data->attached_dev) { + switch (new_dev) { + case ATTACHED_DEV_OTG_MUIC: + case ATTACHED_DEV_JIG_USB_ON_MUIC: + /* W/A for setting usb path */ + md75_info_usb("%s:%s Duplicated(%d), Not ignore\n", + MUIC_DEV_NAME, __func__, muic_data->attached_dev); + notify_skip = true; + goto handle_attach; + default: + break; + } + + if (new_dev == ATTACHED_DEV_HICCUP_MUIC) + goto handle_attach; + + md75_info_usb("%s Duplicated(%d), just ignore\n", + __func__, muic_data->attached_dev); + return ret; + } + + ret = max77775_muic_logically_detach(muic_data, new_dev); + if (ret) + pr_warn("%s fail to logically detach(%d)\n", + __func__, ret); + +handle_attach: + switch (new_dev) { + case ATTACHED_DEV_OTG_MUIC: + ret = com_to_usb_ap(muic_data); + break; + case ATTACHED_DEV_JIG_UART_OFF_MUIC: + case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC: + ret = max77775_muic_attach_uart_path(muic_data, new_dev); + break; + case ATTACHED_DEV_JIG_UART_ON_MUIC: + case ATTACHED_DEV_JIG_UART_ON_VB_MUIC: + ret = max77775_muic_attach_uart_path(muic_data, new_dev); + break; + case ATTACHED_DEV_TA_MUIC: + case ATTACHED_DEV_UNDEFINED_CHARGING_MUIC: + case ATTACHED_DEV_UNOFFICIAL_TA_MUIC: + ret = write_vps_regs(muic_data, new_dev); + break; + case ATTACHED_DEV_JIG_USB_ON_MUIC: + case ATTACHED_DEV_JIG_USB_OFF_MUIC: + case ATTACHED_DEV_USB_MUIC: + case ATTACHED_DEV_CDP_MUIC: + ret = com_to_usb_ap(muic_data); + break; + case ATTACHED_DEV_TIMEOUT_OPEN_MUIC: + md75_info_usb("%s DCD_TIMEOUT system_state = 0x%x\n", __func__, system_state); +#if IS_ENABLED(CONFIG_CCIC_MAX77775) + if (fw_update_state == FW_UPDATE_END && system_state < SYSTEM_RUNNING) { + /* TA Reset, D+ gnd */ + max77775_muic_dp_reset(muic_data); + + max77775_muic_enable_chgdet(muic_data); + goto out; + } +#endif /* CONFIG_CCIC_MAX77775 */ + ret = com_to_usb_ap(muic_data); + break; +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) + case ATTACHED_DEV_HICCUP_MUIC: + max77775_muic_hiccup_on(muic_data); + if (!(muic_data->status3 & BC_STATUS_VBUSDET_MASK)) + notify_skip = true; + break; +#endif /* CONFIG_HICCUP_CHARGER */ + default: + pr_warn("%s unsupported dev(%d)\n", __func__, + new_dev); + ret = -ENODEV; + goto out; + } + + muic_data->attached_dev = new_dev; + if (notify_skip) { + md75_info_usb("%s: noti\n", __func__); + } else { +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + muic_notifier_attach_attached_dev(new_dev); +#endif /* CONFIG_MUIC_NOTIFIER */ + } + +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77775_AFC) + if (max77775_muic_check_is_enable_afc(muic_data, new_dev)) { + /* Maxim's request, wait 1200ms for checking HVDCP */ + md75_info_usb("%s afc work after 1200ms\n", __func__); + cancel_delayed_work_sync(&(muic_data->afc_work)); + schedule_delayed_work(&(muic_data->afc_work), + msecs_to_jiffies(1200)); + } +#endif /* CONFIG_HV_MUIC_MAX77775_AFC */ + +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify && + muic_data->bc1p2_retry_count >= 2 && + muic_data->dcdtmo_retry > 0 && + muic_data->dcdtmo_retry < muic_data->bc1p2_retry_count && + new_dev != ATTACHED_DEV_TIMEOUT_OPEN_MUIC) + inc_hw_param(o_notify, USB_MUIC_BC12_RETRY_SUCCESS_COUNT); +#endif /* CONFIG_USB_HW_PARAM */ + +out: + return ret; +} + +static bool muic_check_vps_adc + (const struct max77775_muic_vps_data *tmp_vps, u8 adc) +{ + bool ret = false; + + if (tmp_vps->adc == adc) { + ret = true; + goto out; + } + + if (tmp_vps->adc == MAX77775_UIADC_DONTCARE) + ret = true; + +out: + if (debug_en_vps) { + md75_info_usb("%s vps(%s) adc(0x%02x) ret(%c)\n", + __func__, tmp_vps->vps_name, + adc, ret ? 'T' : 'F'); + } + + return ret; +} + +static bool muic_check_vps_vbvolt(const struct max77775_muic_vps_data *tmp_vps, + u8 vbvolt) +{ + bool ret = false; + + if (tmp_vps->vbvolt == vbvolt) { + ret = true; + goto out; + } + + if (tmp_vps->vbvolt == VB_DONTCARE) + ret = true; + +out: + if (debug_en_vps) { + pr_debug("%s vps(%s) vbvolt(0x%02x) ret(%c)\n", + __func__, tmp_vps->vps_name, + vbvolt, ret ? 'T' : 'F'); + } + + return ret; +} + +static bool muic_check_vps_chgtyp(const struct max77775_muic_vps_data *tmp_vps, + u8 chgtyp) +{ + bool ret = false; + + if (tmp_vps->chgtyp == chgtyp) { + ret = true; + goto out; + } + + if (tmp_vps->chgtyp == CHGTYP_ANY) { + if (chgtyp > CHGTYP_NO_VOLTAGE) { + ret = true; + goto out; + } + } + + if (tmp_vps->chgtyp == CHGTYP_DONTCARE) + ret = true; + +out: + if (debug_en_vps) { + md75_info_usb("%s vps(%s) chgtyp(0x%02x) ret(%c)\n", + __func__, tmp_vps->vps_name, + chgtyp, ret ? 'T' : 'F'); + } + + return ret; +} + +static u8 max77775_muic_update_adc_with_rid(struct max77775_muic_data *muic_data, + u8 adc) +{ + u8 new_adc = adc; + + if (muic_data->pdata->opmode & OPMODE_PDIC) { + switch (muic_data->ccic_info_data.ccic_evt_rid) { + case RID_000K: + new_adc = MAX77775_UIADC_GND; + break; + case RID_255K: + new_adc = MAX77775_UIADC_255K; + break; + case RID_301K: + new_adc = MAX77775_UIADC_301K; + break; + case RID_523K: + new_adc = MAX77775_UIADC_523K; + break; + case RID_619K: + new_adc = MAX77775_UIADC_619K; + break; + default: + new_adc = MAX77775_UIADC_OPEN; + break; + } + + if (muic_data->ccic_info_data.ccic_evt_rprd) + new_adc = MAX77775_UIADC_GND; + + md75_info_usb("%s: adc(0x%x->0x%x) rid(%d) rprd(%d)\n", + __func__, adc, new_adc, + muic_data->ccic_info_data.ccic_evt_rid, + muic_data->ccic_info_data.ccic_evt_rprd); + } + + return new_adc; +} + +static u8 max77775_resolve_chgtyp(struct max77775_muic_data *muic_data, u8 chgtyp, + u8 spchgtyp, u8 dcdtmo, int irq) +{ + u8 ret = chgtyp; + u8 ccistat = 0; + + max77775_read_reg(muic_data->i2c, REG_CC_STATUS1, &ccistat); + ccistat = (ccistat & BIT_CCIStat) >> FFS(BIT_CCIStat); + +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) + /* Check hiccup mode */ + if (muic_data->is_hiccup_mode > MUIC_HICCUP_MODE_OFF) { + md75_info_usb("%s is_hiccup_mode(%d)\n", __func__, muic_data->is_hiccup_mode); + return CHGTYP_HICCUP_MODE; + } +#endif /* CONFIG_HICCUP_CHARGER */ + + /* Check DCD timeout */ + if (dcdtmo && chgtyp == CHGTYP_USB && + (irq == muic_data->irq_chgtyp || irq == MUIC_IRQ_INIT_DETECT)) { + if (irq == MUIC_IRQ_INIT_DETECT) { + ret = CHGTYP_TIMEOUT_OPEN; + + if (ccistat == CCI_500mA) { + ret = CHGTYP_NO_VOLTAGE; + muic_data->dcdtmo_retry = muic_data->bc1p2_retry_count; + md75_info_usb("%s: DCD_TIMEOUT retry in init\n", __func__); + max77775_muic_enable_chgdet(muic_data); + } + } else { + ret = (muic_data->dcdtmo_retry >= muic_data->bc1p2_retry_count) ? CHGTYP_TIMEOUT_OPEN : CHGTYP_NO_VOLTAGE; + } + goto out; + } + + /* Check Special chgtyp */ + switch (spchgtyp) { + case PRCHGTYP_SAMSUNG_2A: + case PRCHGTYP_APPLE_500MA: + case PRCHGTYP_APPLE_1A: + case PRCHGTYP_APPLE_2A: + case PRCHGTYP_APPLE_12W: + if (chgtyp == CHGTYP_USB || chgtyp == CHGTYP_CDP) { + ret = CHGTYP_UNOFFICIAL_CHARGER; + goto out; + } + break; + default: + break; + } + +out: + if (ret != chgtyp) + md75_info_usb("%s chgtyp(0x%x) spchgtyp(0x%x) dcdtmo(0x%x) -> chgtyp(0x%x)", + __func__, chgtyp, spchgtyp, dcdtmo, ret); + + return ret; +} + +muic_attached_dev_t max77775_muic_check_new_dev(struct max77775_muic_data *muic_data, + int *intr, int irq) +{ + const struct max77775_muic_vps_data *tmp_vps; + muic_attached_dev_t new_dev = ATTACHED_DEV_NONE_MUIC; + u8 adc = muic_data->status1 & USBC_STATUS1_UIADC_MASK; + u8 vbvolt = muic_data->status3 & BC_STATUS_VBUSDET_MASK; + u8 chgtyp = muic_data->status3 & BC_STATUS_CHGTYP_MASK; + u8 spchgtyp = (muic_data->status3 & BC_STATUS_PRCHGTYP_MASK) >> BC_STATUS_PRCHGTYP_SHIFT; + u8 dcdtmo = (muic_data->status3 & BC_STATUS_DCDTMO_MASK) >> BC_STATUS_DCDTMO_SHIFT; + unsigned long i; + + chgtyp = max77775_resolve_chgtyp(muic_data, chgtyp, spchgtyp, dcdtmo, irq); + + adc = max77775_muic_update_adc_with_rid(muic_data, adc); + + for (i = 0; i < (int)ARRAY_SIZE(muic_vps_table); i++) { + tmp_vps = &(muic_vps_table[i]); + + if (!(muic_check_vps_adc(tmp_vps, adc))) + continue; + + if (!(muic_check_vps_vbvolt(tmp_vps, vbvolt))) + continue; + + if (!(muic_check_vps_chgtyp(tmp_vps, chgtyp))) + continue; + + md75_info_usb("%s vps table match found at i(%lu), %s\n", + __func__, i, tmp_vps->vps_name); + + new_dev = tmp_vps->attached_dev; + muic_data->switch_val = tmp_vps->muic_switch; + + *intr = MUIC_INTR_ATTACH; + break; + } + + md75_info_usb("%s %d->%d switch_val[0x%02x]\n", __func__, + muic_data->attached_dev, new_dev, muic_data->switch_val); + + return new_dev; +} + +static void max77775_muic_detect_dev(struct max77775_muic_data *muic_data, + int irq) +{ + struct i2c_client *i2c = muic_data->i2c; + muic_attached_dev_t new_dev = ATTACHED_DEV_NONE_MUIC; + int intr = MUIC_INTR_DETACH; + u8 status[5]; + u8 adc, vbvolt, chgtyp, spchgtyp, sysmsg, vbadc, dcdtmo, ccstat; + static unsigned long killer_stamp; + int ret; +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + int event; +#endif +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif + + ret = max77775_bulk_read(i2c, + MAX77775_USBC_REG_USBC_STATUS1, 5, status); + if (ret) { + md75_err_usb("%s fail to read muic reg(%d)\n", + __func__, ret); + return; + } + + md75_info_usb("%s USBC1:0x%02x, USBC2:0x%02x, BC:0x%02x\n", + __func__, status[0], status[1], status[2]); + + /* attached status */ + muic_data->status1 = status[0]; + muic_data->status2 = status[1]; + muic_data->status3 = status[2]; + + adc = status[0] & USBC_STATUS1_UIADC_MASK; + sysmsg = status[1] & USBC_STATUS2_SYSMSG_MASK; + vbvolt = (status[2] & BC_STATUS_VBUSDET_MASK) >> BC_STATUS_VBUSDET_SHIFT; + chgtyp = status[2] & BC_STATUS_CHGTYP_MASK; + spchgtyp = (status[2] & BC_STATUS_PRCHGTYP_MASK) >> BC_STATUS_PRCHGTYP_SHIFT; + vbadc = (status[0] & USBC_STATUS1_VBADC_MASK) >> USBC_STATUS1_VBADC_SHIFT; + dcdtmo = (status[2] & BC_STATUS_DCDTMO_MASK) >> BC_STATUS_DCDTMO_SHIFT; + ccstat = (status[4] & BIT_CCStat) >> FFS(BIT_CCStat); + + md75_info_usb("%s adc:0x%x vbvolt:0x%x chgtyp:0x%x spchgtyp:0x%x sysmsg:0x%x vbadc:0x%x dcdtmo:0x%x\n", + __func__, adc, vbvolt, chgtyp, spchgtyp, sysmsg, vbadc, dcdtmo); + + /* Set the fake_vbus charger type */ + muic_data->fake_chgtyp = chgtyp; + + if (irq == muic_data->irq_vbadc) { + if (vbadc == MAX77775_VBADC_3_8V_TO_4_5V && + ccstat == cc_No_Connection) { + /* W/A of CC is detached but Vbus is valid(3.8~4.5V) */ + vbvolt = 0; + muic_data->status3 = muic_data->status3 & ~(BC_STATUS_VBUSDET_MASK); + md75_info_usb("%s vbadc(0x%x), ccstat(0x%x), set vbvolt to 0 => BC(0x%x)\n", + __func__, vbadc, ccstat, muic_data->status3); +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77775_AFC) + } else if (vbadc > MAX77775_VBADC_3_8V_TO_4_5V && + vbadc <= MAX77775_VBADC_6_5V_TO_7_5V && + muic_data->is_afc_reset) { + muic_data->is_afc_reset = false; + md75_info_usb("%s afc reset is done\n", __func__); + + switch (muic_data->attached_dev) { + case ATTACHED_DEV_AFC_CHARGER_5V_MUIC: + case ATTACHED_DEV_AFC_CHARGER_9V_MUIC: + muic_data->attached_dev = ATTACHED_DEV_AFC_CHARGER_5V_MUIC; +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + muic_notifier_attach_attached_dev(muic_data->attached_dev); +#endif /* CONFIG_MUIC_NOTIFIER */ + break; + case ATTACHED_DEV_QC_CHARGER_5V_MUIC: + case ATTACHED_DEV_QC_CHARGER_9V_MUIC: + muic_data->attached_dev = ATTACHED_DEV_QC_CHARGER_5V_MUIC; +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) + muic_notifier_attach_attached_dev(muic_data->attached_dev); +#endif /* CONFIG_MUIC_NOTIFIER */ + break; + default: + break; + } + return; +#endif /* CONFIG_HV_MUIC_MAX77775_AFC */ + } else { + md75_info_usb("%s vbadc irq(%d), return\n", + __func__, muic_data->irq_vbadc); + return; + } + } + + if (irq == muic_data->irq_dcdtmo && dcdtmo) { + muic_data->dcdtmo_retry++; + md75_info_usb("%s:%s DCD_TIMEOUT retry count: %d\n", MUIC_DEV_NAME, __func__, muic_data->dcdtmo_retry); + } + + if (!is_lpcharge_pdic_param() && !muic_data->is_factory_start) { +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) + if ((irq == MUIC_IRQ_PDIC_HANDLER) && + (muic_data->ccic_evt_id == PDIC_NOTIFY_ID_WATER)) { + /* Force hiccup clear once at water state */ + if (muic_data->afc_water_disable) + max77775_muic_hiccup_clear(muic_data); + } + + if (muic_data->afc_water_disable && !muic_data->is_hiccup_mode) { + if (vbvolt > 0) { + max77775_muic_hiccup_on(muic_data); + } else { + /* Clear muic deive type and hiccup at water state (booting with water) */ + if (muic_data->attached_dev != ATTACHED_DEV_NONE_MUIC) { + md75_info_usb("%s initialize hiccup state and device type(%d) at hiccup booting\n", + __func__, muic_data->attached_dev); + muic_notifier_detach_attached_dev(muic_data->attached_dev); + muic_data->attached_dev = ATTACHED_DEV_NONE_MUIC; + max77775_muic_hiccup_clear(muic_data); + } + } +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) + max77775_muic_handle_vbus(muic_data); +#endif + + return; + } +#endif /* CONFIG_HICCUP_CHARGER */ + } + + if (irq == muic_data->irq_chgtyp && vbvolt == 0 && chgtyp > 0) { + killer_stamp = jiffies; + } else if (irq == muic_data->irq_vbusdet && chgtyp > 0 && vbvolt > 0 && + time_before(jiffies, killer_stamp + msecs_to_jiffies(500))) { + md75_info_usb("%s: this is checking killer, retry bc12\n", __func__); + max77775_muic_enable_chgdet(muic_data); + goto out; + } else if (irq == muic_data->irq_vbusdet && vbvolt == 0) { + killer_stamp = 0; + } + + new_dev = max77775_muic_check_new_dev(muic_data, &intr, irq); + + if (intr == MUIC_INTR_ATTACH) { + md75_info_usb("%s ATTACHED\n", __func__); + + ret = max77775_muic_handle_attach(muic_data, new_dev, irq); + if (ret) + md75_err_usb("%s cannot handle attach(%d)\n", __func__, ret); + } else { + md75_info_usb("%s DETACHED\n", __func__); + + if (irq == muic_data->irq_chgtyp && vbvolt == 0 && chgtyp == CHGTYP_DEDICATED_CHARGER) { + md75_info_usb("[MUIC] %s USB Killer Detected!!!\n", __func__); +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_USBKILLER; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_USB_KILLER_COUNT); +#endif + } + + ret = max77775_muic_handle_detach(muic_data, irq); + if (ret) + md75_err_usb("%s cannot handle detach(%d)\n", __func__, ret); + } +out: +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) + max77775_muic_handle_vbus(muic_data); +#endif +} + +static irqreturn_t max77775_muic_irq(int irq, void *data) +{ + struct max77775_muic_data *muic_data = data; + struct irq_desc *desc = irq_to_desc(irq); + + if (!desc) { + md75_info_usb("%s desc is null\n", __func__); + goto out; + } + + if (!muic_data) { + md75_err_usb("%s muic_data is null(%s)\n", __func__, desc->action->name); + goto out; + } + + md75_info_usb("%s irq:%d (%s)\n", __func__, irq, desc->action->name); + + mutex_lock(&muic_data->muic_mutex); + if (muic_data->is_muic_ready == true) + max77775_muic_detect_dev(muic_data, irq); + else + md75_info_usb("%s MUIC is not ready, just return\n", __func__); + mutex_unlock(&muic_data->muic_mutex); + +out: + return IRQ_HANDLED; +} + +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77775_AFC) +static void max77775_muic_afc_work(struct work_struct *work) +{ + struct max77775_muic_data *muic_data = + container_of(work, struct max77775_muic_data, afc_work.work); + + md75_info_usb("%s\n", __func__); + + if (max77775_muic_check_is_enable_afc(muic_data, muic_data->attached_dev)) { + mutex_lock(&muic_data->afc_lock); + muic_data->pdata->afc_disabled_updated |= MAX77775_MUIC_AFC_WORK_PROCESS; + + if (!muic_data->pdata->afc_disable) { + muic_data->is_check_hv = true; + muic_data->hv_voltage = 9; + max77775_muic_afc_hv_set(muic_data, 9); + } else { + muic_data->is_check_hv = true; + muic_data->hv_voltage = 5; + max77775_muic_afc_hv_set(muic_data, 5); + } + mutex_unlock(&muic_data->afc_lock); + } +} + +static int max77775_muic_hv_charger_disable(bool en) +{ + struct max77775_muic_data *muic_data = g_muic_data; + + muic_data->is_charger_mode = en; + + mutex_lock(&muic_data->afc_lock); + if (muic_data->pdata->afc_disabled_updated & MAX77775_MUIC_AFC_WORK_PROCESS) + muic_data->pdata->afc_disabled_updated |= MAX77775_MUIC_AFC_DISABLE_CHANGE_DURING_WORK; + else + schedule_delayed_work(&(muic_data->afc_work), msecs_to_jiffies(0)); + mutex_unlock(&muic_data->afc_lock); + + return 0; +} + +int __max77775_muic_afc_set_voltage(struct max77775_muic_data *muic_data, int voltage) +{ + int now_voltage = 0, ret = 0; + + switch (voltage) { + case 5: + case 9: + break; + default: + md75_err_usb("%s: invalid value %d, return\n", __func__, voltage); + ret = -EINVAL; + goto err; + } + + switch (muic_data->attached_dev) { + case ATTACHED_DEV_AFC_CHARGER_5V_MUIC: + case ATTACHED_DEV_QC_CHARGER_5V_MUIC: + now_voltage = 5; + break; + case ATTACHED_DEV_AFC_CHARGER_9V_MUIC: + case ATTACHED_DEV_QC_CHARGER_9V_MUIC: + now_voltage = 9; + break; + default: + break; + } + + if (voltage == now_voltage) { + md75_err_usb("%s: same with current voltage, return\n", __func__); + ret = -EINVAL; + goto err; + } + + muic_data->hv_voltage = voltage; + + switch (muic_data->attached_dev) { + case ATTACHED_DEV_AFC_CHARGER_5V_MUIC: + case ATTACHED_DEV_AFC_CHARGER_9V_MUIC: + ret = max77775_muic_afc_hv_set(muic_data, voltage); + break; + case ATTACHED_DEV_QC_CHARGER_5V_MUIC: + case ATTACHED_DEV_QC_CHARGER_9V_MUIC: + ret = max77775_muic_qc_hv_set(muic_data, voltage); + break; + default: + md75_err_usb("%s: not a HV Charger %d, return\n", __func__, muic_data->attached_dev); + ret = -EINVAL; + goto err; + } +err: + return ret; +} + +static int max77775_muic_afc_set_voltage(int voltage) +{ + struct max77775_muic_data *muic_data = g_muic_data; + int ret = 0; + + mutex_lock(&muic_data->afc_lock); + + if (muic_data->pdata->afc_disabled_updated & MAX77775_MUIC_AFC_WORK_PROCESS) { + muic_data->pdata->afc_disabled_updated |= MAX77775_MUIC_AFC_SET_VOLTAGE_CHANGE_DURING_WORK; + muic_data->reserve_hv_voltage = voltage; + goto skip; + } + + muic_data->pdata->afc_disabled_updated |= MAX77775_MUIC_AFC_WORK_PROCESS; + muic_data->reserve_hv_voltage = 0; + + ret = __max77775_muic_afc_set_voltage(muic_data, voltage); + if (ret < 0) + muic_data->pdata->afc_disabled_updated &= MAX77775_MUIC_AFC_WORK_PROCESS_END; +skip: + mutex_unlock(&muic_data->afc_lock); + return ret; +} + +static int max77775_muic_hv_charger_init(void) +{ + struct max77775_muic_data *muic_data = g_muic_data; + + if (!muic_data || !muic_data->pdata || + !test_bit(MUIC_PROBE_DONE, &muic_data->pdata->driver_probe_flag)) { + md75_info_usb("[%s:%s] skip\n", MUIC_DEV_NAME, __func__); + return 0; + } + + if (muic_data->is_charger_ready) { + md75_info_usb("%s: charger is already ready(%d), return\n", + __func__, muic_data->is_charger_ready); + return -EINVAL; + } + + muic_data->is_charger_ready = true; + + if (max77775_muic_check_is_enable_afc(muic_data, muic_data->attached_dev)) { + md75_info_usb("%s afc work start\n", __func__); + cancel_delayed_work_sync(&(muic_data->afc_work)); + schedule_delayed_work(&(muic_data->afc_work), msecs_to_jiffies(0)); + } + + return 0; +} + +static void max77775_muic_detect_dev_hv_work(struct work_struct *work) +{ + struct max77775_muic_data *muic_data = container_of(work, + struct max77775_muic_data, afc_handle_work); + unsigned char opcode = muic_data->afc_op_dataout[0]; + + mutex_lock(&muic_data->muic_mutex); + if (!max77775_muic_check_is_enable_afc(muic_data, muic_data->attached_dev)) { + switch (muic_data->attached_dev) { + case ATTACHED_DEV_AFC_CHARGER_5V_MUIC: + case ATTACHED_DEV_AFC_CHARGER_9V_MUIC: + case ATTACHED_DEV_QC_CHARGER_5V_MUIC: + case ATTACHED_DEV_QC_CHARGER_9V_MUIC: + md75_info_usb("%s high voltage value is changed\n", __func__); + break; + default: + md75_info_usb("%s status is changed, return\n", __func__); + goto out; + } + } + + if (opcode == COMMAND_AFC_RESULT_READ) + max77775_muic_handle_detect_dev_afc(muic_data, muic_data->afc_op_dataout); + else if (opcode == COMMAND_QC_2_0_SET) + max77775_muic_handle_detect_dev_qc(muic_data, muic_data->afc_op_dataout); + else + md75_info_usb("%s undefined opcode(%d)\n", __func__, opcode); + +out: + mutex_unlock(&muic_data->muic_mutex); +} + +void max77775_muic_handle_detect_dev_hv(struct max77775_muic_data *muic_data, unsigned char *data) +{ + int i; + + for (i = 0; i < AFC_OP_OUT_LEN; i++) + muic_data->afc_op_dataout[i] = data[i]; + + schedule_work(&(muic_data->afc_handle_work)); +} +#endif /* CONFIG_HV_MUIC_MAX77775_AFC */ + +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) +static int max77775_muic_set_hiccup_mode(int on_off) +{ + struct max77775_muic_data *muic_data = g_muic_data; + + md75_info_usb("%s (%d)\n", __func__, on_off); + + switch (on_off) { + case MUIC_HICCUP_MODE_OFF: + case MUIC_HICCUP_MODE_ON: + if (muic_data->is_hiccup_mode != on_off) { + muic_data->is_hiccup_mode = on_off; +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77775_AFC) + if (muic_data->is_check_hv) + max77775_muic_clear_hv_control(muic_data); +#endif /* CONFIG_HV_MUIC_MAX77775_AFC */ + schedule_work(&(muic_data->ccic_info_data_work)); + } + break; + default: + md75_err_usb("%s undefined value(%d), return\n", __func__, on_off); + return -EINVAL; + } + + return 0; +} +#endif /* CONFIG_HICCUP_CHARGER */ + +static void max77775_muic_print_reg_log(struct work_struct *work) +{ + struct max77775_muic_data *muic_data = + container_of(work, struct max77775_muic_data, debug_work.work); + struct max77775_usbc_platform_data *usbc_data = muic_data->usbc_pdata; + struct i2c_client *i2c = muic_data->i2c; + struct i2c_client *pmic_i2c = muic_data->usbc_pdata->i2c; + u8 status[12] = {0, }; +#if defined(CONFIG_MAX77775_CCOPEN_AFTER_WATERCABLE) + u8 spare_status[2] = {0, }; +#endif + u8 fw_rev = 0, fw_rev2 = 0; + int delay_time = 60000; + static int prev_opcode_fail_count; + + max77775_muic_read_reg(i2c, MAX77775_USBC_REG_USBC_STATUS1, &status[0]); + max77775_muic_read_reg(i2c, MAX77775_USBC_REG_USBC_STATUS2, &status[1]); + max77775_muic_read_reg(i2c, MAX77775_USBC_REG_BC_STATUS, &status[2]); + max77775_muic_read_reg(i2c, MAX77775_USBC_REG_CC_STATUS1, &status[3]); + max77775_muic_read_reg(i2c, MAX77775_USBC_REG_CC_STATUS2, &status[4]); + max77775_muic_read_reg(i2c, MAX77775_USBC_REG_PD_STATUS1, &status[5]); + max77775_muic_read_reg(i2c, MAX77775_USBC_REG_PD_STATUS2, &status[6]); + max77775_muic_read_reg(i2c, MAX77775_USBC_REG_UIC_INT_M, &status[7]); + max77775_muic_read_reg(i2c, MAX77775_USBC_REG_CC_INT_M, &status[8]); + max77775_muic_read_reg(i2c, MAX77775_USBC_REG_PD_INT_M, &status[9]); + max77775_muic_read_reg(i2c, MAX77775_USBC_REG_VDM_INT_M, &status[10]); + max77775_muic_read_reg(pmic_i2c, MAX77775_PMIC_REG_INTSRC_MASK, &status[11]); + max77775_muic_read_reg(i2c, MAX77775_USBC_REG_UIC_FW_REV, &fw_rev); + max77775_muic_read_reg(i2c, MAX77775_USBC_REG_UIC_FW_REV2, &fw_rev2); + + md75_info_usb("%s USBC1:0x%02x, USBC2:0x%02x, BC:0x%02x, CC0:0x%x, CC1:0x%x, PD0:0x%x, PD1:0x%x FW:%02X.%02X attached_dev:%d\n", + __func__, status[0], status[1], status[2], status[3], status[4], status[5], status[6], + fw_rev, fw_rev2, muic_data->attached_dev); + md75_info_usb("%s UIC_INT_M:0x%x, CC_INT_M:0x%x, PD_INT_M:0x%x, VDM_INT_M:0x%x, PMIC_MASK:0x%x, WDT:%d, POR:%d\n", + __func__, status[7], status[8], status[9], status[10], status[11], + muic_data->usbc_pdata->watchdog_count, muic_data->usbc_pdata->por_count); + +#if defined(CONFIG_MAX77775_CCOPEN_AFTER_WATERCABLE) + max77775_muic_read_reg(i2c, MAX77775_USBC_REG_SPARE_STATUS1, &spare_status[0]); + max77775_muic_read_reg(i2c, MAX77775_USBC_REG_SPARE_INT_M, &spare_status[1]); + + md75_info_usb("%s TA:0x%x, SPARE_INT_M:0x%x\n", + __func__, spare_status[0], spare_status[1]); +#endif + + if (max77775_need_check_stuck(usbc_data)) { + msg_maxim("%s stuck suppose.", __func__); + max77775_send_check_stuck_opcode(usbc_data); + } + + if (usbc_data->opcode_fail_count) { + delay_time = 11000; + if (prev_opcode_fail_count != usbc_data->opcode_fail_count) + __pm_wakeup_event(muic_data->muic_ws, 11000 + 1000); + } + + prev_opcode_fail_count = usbc_data->opcode_fail_count; + + msg_maxim("%s usbc_data->opcode_fail_count(%d), delay_time(%d)", + __func__, usbc_data->opcode_fail_count, delay_time); + schedule_delayed_work(&(muic_data->debug_work), + msecs_to_jiffies(delay_time)); +} + +static void max77775_muic_handle_ccic_event(struct work_struct *work) +{ + struct max77775_muic_data *muic_data = container_of(work, + struct max77775_muic_data, ccic_info_data_work); + + md75_info_usb("%s\n", __func__); + + mutex_lock(&muic_data->muic_mutex); + if (muic_data->is_muic_ready == true) + max77775_muic_detect_dev(muic_data, MUIC_IRQ_PDIC_HANDLER); + else + md75_info_usb("%s MUIC is not ready, just return\n", __func__); + mutex_unlock(&muic_data->muic_mutex); +} + +#define REQUEST_IRQ(_irq, _dev_id, _name) \ +do { \ + ret = request_threaded_irq(_irq, NULL, max77775_muic_irq, \ + IRQF_NO_SUSPEND, _name, _dev_id); \ + if (ret < 0) { \ + md75_err_usb("%s Failed to request IRQ #%d: %d\n", \ + __func__, _irq, ret); \ + _irq = 0; \ + } \ +} while (0) + +static int max77775_muic_irq_init(struct max77775_muic_data *muic_data) +{ + int ret = 0; + + md75_info_usb("%s\n", __func__); + + if (muic_data->mfd_pdata && (muic_data->mfd_pdata->irq_base > 0)) { + int irq_base = muic_data->mfd_pdata->irq_base; + + /* request MUIC IRQ */ + muic_data->irq_uiadc = irq_base + MAX77775_USBC_IRQ_UIDADC_INT; + REQUEST_IRQ(muic_data->irq_uiadc, muic_data, "muic-uiadc"); + + muic_data->irq_chgtyp = irq_base + MAX77775_USBC_IRQ_CHGT_INT; + REQUEST_IRQ(muic_data->irq_chgtyp, muic_data, "muic-chgtyp"); + + muic_data->irq_dcdtmo = irq_base + MAX77775_USBC_IRQ_DCD_INT; + REQUEST_IRQ(muic_data->irq_dcdtmo, muic_data, "muic-dcdtmo"); + + muic_data->irq_vbadc = irq_base + MAX77775_USBC_IRQ_VBADC_INT; + REQUEST_IRQ(muic_data->irq_vbadc, muic_data, "muic-vbadc"); + + muic_data->irq_vbusdet = irq_base + MAX77775_USBC_IRQ_VBUS_INT; + REQUEST_IRQ(muic_data->irq_vbusdet, muic_data, "muic-vbusdet"); + } + + md75_info_usb("%s uiadc(%d), chgtyp(%d), dcdtmo(%d), vbadc(%d), vbusdet(%d)\n", + __func__, muic_data->irq_uiadc, + muic_data->irq_chgtyp, muic_data->irq_dcdtmo, + muic_data->irq_vbadc, muic_data->irq_vbusdet); + return ret; +} + +#define FREE_IRQ(_irq, _dev_id, _name) \ +do { \ + if (_irq) { \ + free_irq(_irq, _dev_id); \ + md75_info_usb("%s IRQ(%d):%s free done\n", \ + __func__, _irq, _name); \ + } \ +} while (0) + +static void max77775_muic_free_irqs(struct max77775_muic_data *muic_data) +{ + md75_info_usb("%s\n", __func__); + + disable_irq(muic_data->irq_uiadc); + disable_irq(muic_data->irq_chgtyp); + disable_irq(muic_data->irq_dcdtmo); + disable_irq(muic_data->irq_vbadc); + disable_irq(muic_data->irq_vbusdet); + + /* free MUIC IRQ */ + FREE_IRQ(muic_data->irq_uiadc, muic_data, "muic-uiadc"); + FREE_IRQ(muic_data->irq_chgtyp, muic_data, "muic-chgtyp"); + FREE_IRQ(muic_data->irq_dcdtmo, muic_data, "muic-dcdtmo"); + FREE_IRQ(muic_data->irq_vbadc, muic_data, "muic-vbadc"); + FREE_IRQ(muic_data->irq_vbusdet, muic_data, "muic-vbusdet"); +} + +static void max77775_muic_init_detect(struct max77775_muic_data *muic_data) +{ + md75_info_usb("%s\n", __func__); + + mutex_lock(&muic_data->muic_mutex); + muic_data->is_muic_ready = true; + + max77775_muic_detect_dev(muic_data, MUIC_IRQ_INIT_DETECT); + max77775_muic_enable_detecting_short(muic_data); + + mutex_unlock(&muic_data->muic_mutex); +} + +static void max77775_set_bc1p2_retry_count(struct max77775_muic_data *muic_data) +{ + struct device_node *np = NULL; + int count; + + np = of_find_compatible_node(NULL, NULL, "maxim,max77775"); + + if (np && !of_property_read_u32(np, "max77775,bc1p2_retry_count", &count)) + muic_data->bc1p2_retry_count = count; + else + muic_data->bc1p2_retry_count = 1; /* default retry count */ + + md75_info_usb("%s:%s BC1p2 Retry count: %d\n", MUIC_DEV_NAME, + __func__, muic_data->bc1p2_retry_count); +} + +static void max77775_muic_clear_interrupt(struct max77775_muic_data *muic_data) +{ + struct i2c_client *i2c = muic_data->i2c; + u8 interrupt; + int ret; + + md75_info_usb("%s\n", __func__); + + ret = max77775_muic_read_reg(i2c, + MAX77775_USBC_REG_UIC_INT, &interrupt); + if (ret) + md75_err_usb("%s fail to read muic INT1 reg(%d)\n", + __func__, ret); + + md75_info_usb("%s CLEAR!! UIC_INT:0x%02x\n", + __func__, interrupt); +} + +static int max77775_muic_init_regs(struct max77775_muic_data *muic_data) +{ + int ret; + + md75_info_usb("%s\n", __func__); + + max77775_muic_clear_interrupt(muic_data); + + ret = max77775_muic_irq_init(muic_data); + if (ret < 0) { + md75_err_usb("%s Failed to initialize MUIC irq:%d\n", + __func__, ret); + max77775_muic_free_irqs(muic_data); + } + + return ret; +} + +#if IS_ENABLED(CONFIG_USB_EXTERNAL_NOTIFY) +static int muic_handle_usb_notification(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct max77775_muic_data *pmuic = + container_of(nb, struct max77775_muic_data, usb_nb); + + switch (action) { + /* Abnormal device */ + case EXTERNAL_NOTIFY_3S_NODEVICE: + md75_info_usb("%s: 3S_NODEVICE(USB HOST Connection timeout)\n", + __func__); + if (pmuic->attached_dev == ATTACHED_DEV_HMT_MUIC) + muic_send_dock_intent(MUIC_DOCK_ABNORMAL); + break; + + /* Gamepad device connected */ + case EXTERNAL_NOTIFY_DEVICE_CONNECT: + md75_info_usb("%s: DEVICE_CONNECT(Gamepad)\n", __func__); + break; + + default: + break; + } + + return NOTIFY_DONE; +} + + +static void muic_register_usb_notifier(struct max77775_muic_data *pmuic) +{ + int ret = 0; + + md75_info_usb("%s\n", __func__); + + + ret = usb_external_notify_register(&pmuic->usb_nb, + muic_handle_usb_notification, EXTERNAL_NOTIFY_DEV_MUIC); + if (ret < 0) { + md75_info_usb("%s: USB Noti. is not ready.\n", __func__); + return; + } + + md75_info_usb("%s: done.\n", __func__); +} + +static void muic_unregister_usb_notifier(struct max77775_muic_data *pmuic) +{ + int ret = 0; + + md75_info_usb("%s\n", __func__); + + ret = usb_external_notify_unregister(&pmuic->usb_nb); + if (ret < 0) { + md75_info_usb("%s: USB Noti. unregister error.\n", __func__); + return; + } + + md75_info_usb("%s: done.\n", __func__); +} +#else +static void muic_register_usb_notifier(struct max77775_muic_data *pmuic) {} +static void muic_unregister_usb_notifier(struct max77775_muic_data *pmuic) {} +#endif + +int max77775_muic_probe(struct max77775_usbc_platform_data *usbc_data) +{ + struct max77775_platform_data *mfd_pdata = usbc_data->max77775_data; + struct max77775_muic_data *muic_data; + int ret = 0; +#if IS_ENABLED(CONFIG_ARCH_QCOM) && !defined(CONFIG_USB_ARCH_EXYNOS) + u8 pogo_adc = 0; +#endif + md75_info_usb("%s\n", __func__); + + muic_data = devm_kzalloc(usbc_data->dev, sizeof(struct max77775_muic_data), GFP_KERNEL); + if (!muic_data) { + ret = -ENOMEM; + goto err_return; + } + + if (!mfd_pdata) { + md75_err_usb("%s: failed to get mfd platform data\n", __func__); + ret = -ENOMEM; + goto err_return; + } + + mutex_init(&muic_data->muic_mutex); + mutex_init(&muic_data->afc_lock); + muic_data->muic_ws = wakeup_source_register(usbc_data->dev, "muic-irq"); +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + muic_data->afc_retry_ws = wakeup_source_register(usbc_data->dev, "muic-afc-retry"); +#endif + muic_data->i2c = usbc_data->muic; + muic_data->mfd_pdata = mfd_pdata; + muic_data->usbc_pdata = usbc_data; + muic_data->pdata = &muic_pdata; + muic_data->attached_dev = ATTACHED_DEV_NONE_MUIC; + muic_data->is_muic_ready = false; + muic_data->is_otg_test = false; + muic_data->is_factory_start = false; + muic_data->switch_val = COM_OPEN; + muic_data->is_charger_mode = false; + muic_data->dcdtmo_retry = 0; + + usbc_data->muic_data = muic_data; + g_muic_data = muic_data; + +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + muic_data->muic_d.ops = NULL; + muic_data->muic_d.data = (void *)muic_data; + muic_data->man = register_muic(&(muic_data->muic_d)); +#endif + if (muic_data->pdata->init_gpio_cb) { +#if IS_MODULE(CONFIG_MUIC_NOTIFIER) + ret = muic_data->pdata->init_gpio_cb(get_switch_sel()); +#else + ret = muic_data->pdata->init_gpio_cb(); +#endif + if (ret) { + md75_err_usb("%s: failed to init gpio(%d)\n", __func__, ret); + goto fail; + } + } + + mutex_lock(&muic_data->muic_mutex); + + /* create sysfs group */ + if (switch_device) { + ret = sysfs_create_group(&switch_device->kobj, &max77775_muic_group); + if (ret) { + md75_err_usb("%s: failed to create attribute group. error: %d\n", + __func__, ret); + goto fail_sysfs_create; + } + dev_set_drvdata(switch_device, muic_data); + } + + if (muic_data->pdata->init_switch_dev_cb) + muic_data->pdata->init_switch_dev_cb(); + + ret = max77775_muic_init_regs(muic_data); + if (ret < 0) { + md75_err_usb("%s Failed to initialize MUIC irq:%d\n", + __func__, ret); + goto fail_init_irq; + } + + mutex_unlock(&muic_data->muic_mutex); + +#if IS_ENABLED(CONFIG_USB_ARCH_EXYNOS) + muic_data->pdata->opmode = get_pdic_info() & 0x0f; +#else + ret = max77775_read_reg(muic_data->i2c, MAX77775_USBC_REG_USBC_STATUS1, &pogo_adc); + pogo_adc &= USBC_STATUS1_UIADC_MASK; + md75_info_usb("%s:%s POGO_ADC : 0x%02x\n", MUIC_DEV_NAME, __func__, pogo_adc); + + if (pogo_adc == 0x07) + muic_data->pdata->opmode = OPMODE_PDIC; + else + muic_data->pdata->opmode = OPMODE_MUIC; +#endif + if (muic_data->pdata->opmode & OPMODE_PDIC) { + max77775_muic_register_ccic_notifier(muic_data); + INIT_WORK(&(muic_data->ccic_info_data_work), + max77775_muic_handle_ccic_event); + } + + muic_data->afc_water_disable = false; + +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77775_AFC) + if (get_afc_mode() == CH_MODE_AFC_DISABLE_VAL) { + md75_info_usb("%s: afc_disable: AFC disabled\n", __func__); + muic_data->pdata->afc_disable = true; + } else { + md75_info_usb("%s: afc_disable: AFC enabled\n", __func__); + muic_data->pdata->afc_disable = false; + } + muic_data->pdata->afc_disabled_updated = 0; + + INIT_DELAYED_WORK(&(muic_data->afc_work), + max77775_muic_afc_work); + INIT_WORK(&(muic_data->afc_handle_work), + max77775_muic_detect_dev_hv_work); + + /* set MUIC afc voltage switching function */ + muic_data->pdata->muic_afc_set_voltage_cb = max77775_muic_afc_set_voltage; + muic_data->pdata->muic_hv_charger_disable_cb = max77775_muic_hv_charger_disable; + + /* set MUIC check charger init function */ + muic_data->pdata->muic_hv_charger_init_cb = max77775_muic_hv_charger_init; + muic_data->is_charger_ready = false; + muic_data->is_check_hv = false; + muic_data->hv_voltage = 0; + muic_data->afc_retry = 0; + muic_data->is_afc_reset = false; + muic_data->is_skip_bigdata = false; + muic_data->is_usb_fail = false; +#endif /* CONFIG_HV_MUIC_MAX77775_AFC */ + +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) + muic_data->is_hiccup_mode = 0; + muic_data->pdata->muic_set_hiccup_mode_cb = max77775_muic_set_hiccup_mode; +#endif /* CONFIG_HICCUP_CHARGER */ + + /* set bc1p2 retry count */ + max77775_set_bc1p2_retry_count(muic_data); + + /* initial cable detection */ + max77775_muic_init_detect(muic_data); + + /* register usb external notifier */ + muic_register_usb_notifier(muic_data); + + INIT_DELAYED_WORK(&(muic_data->debug_work), + max77775_muic_print_reg_log); + schedule_delayed_work(&(muic_data->debug_work), + msecs_to_jiffies(10000)); + + /* hv charger init */ + set_bit(MUIC_PROBE_DONE, &muic_data->pdata->driver_probe_flag); + if (test_bit(CHARGER_PROBE_DONE, &muic_data->pdata->driver_probe_flag)) + max77775_muic_hv_charger_init(); + + return 0; + +fail_init_irq: + if (muic_data->pdata->cleanup_switch_dev_cb) + muic_data->pdata->cleanup_switch_dev_cb(); + if (switch_device) + sysfs_remove_group(&switch_device->kobj, &max77775_muic_group); +fail_sysfs_create: + mutex_unlock(&muic_data->muic_mutex); +fail: + mutex_destroy(&muic_data->muic_mutex); +err_return: + return ret; +} + +int max77775_muic_remove(struct max77775_usbc_platform_data *usbc_data) +{ + struct max77775_muic_data *muic_data = usbc_data->muic_data; + + if (switch_device) + sysfs_remove_group(&switch_device->kobj, &max77775_muic_group); + + if (muic_data) { + md75_info_usb("%s\n", __func__); + + max77775_muic_free_irqs(muic_data); + + if (muic_data->pdata->opmode & OPMODE_PDIC) { + max77775_muic_unregister_ccic_notifier(muic_data); + cancel_work_sync(&(muic_data->ccic_info_data_work)); + } +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77775_AFC) + cancel_delayed_work_sync(&(muic_data->afc_work)); + cancel_work_sync(&(muic_data->afc_handle_work)); +#endif + muic_unregister_usb_notifier(muic_data); + cancel_delayed_work_sync(&(muic_data->debug_work)); + + if (muic_data->pdata->cleanup_switch_dev_cb) + muic_data->pdata->cleanup_switch_dev_cb(); + + mutex_destroy(&muic_data->muic_mutex); + wakeup_source_unregister(muic_data->muic_ws); +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + wakeup_source_unregister(muic_data->afc_retry_ws); +#endif + } + + return 0; +} + +void max77775_muic_shutdown(struct max77775_usbc_platform_data *usbc_data) +{ + struct max77775_muic_data *muic_data = usbc_data->muic_data; + + md75_info_usb("%s +\n", __func__); + + if (switch_device) + sysfs_remove_group(&switch_device->kobj, &max77775_muic_group); + + if (!muic_data) { + md75_err_usb("%s no drvdata\n", __func__); + goto out; + } + + com_to_open(muic_data); +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) + max77775_muic_hiccup_clear(muic_data); +#endif + + max77775_muic_free_irqs(muic_data); + + if ((muic_data->pdata) && (muic_data->pdata->opmode & OPMODE_PDIC)) { + max77775_muic_unregister_ccic_notifier(muic_data); + cancel_work_sync(&(muic_data->ccic_info_data_work)); + } +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77775_AFC) + if (muic_data->pdata) { + /* clear MUIC afc voltage switching function */ + muic_data->pdata->muic_afc_set_voltage_cb = NULL; + /* clear MUIC check charger init function */ + muic_data->pdata->muic_hv_charger_init_cb = NULL; + } + cancel_delayed_work_sync(&(muic_data->afc_work)); + cancel_work_sync(&(muic_data->afc_handle_work)); +#endif +#if IS_ENABLED(CONFIG_HICCUP_CHARGER) + if (muic_data->pdata) + muic_data->pdata->muic_set_hiccup_mode_cb = NULL; +#endif /* CONFIG_HICCUP_CHARGER */ + muic_unregister_usb_notifier(muic_data); + cancel_delayed_work_sync(&(muic_data->debug_work)); + +out: + md75_info_usb("%s -\n", __func__); +} + +int max77775_muic_suspend(struct max77775_usbc_platform_data *usbc_data) +{ + struct max77775_muic_data *muic_data = usbc_data->muic_data; + + md75_info_usb("%s\n", __func__); + cancel_delayed_work_sync(&(muic_data->debug_work)); + + return 0; +} + +int max77775_muic_resume(struct max77775_usbc_platform_data *usbc_data) +{ + struct max77775_muic_data *muic_data = usbc_data->muic_data; + + md75_info_usb("%s\n", __func__); + schedule_delayed_work(&(muic_data->debug_work), msecs_to_jiffies(1000)); + + return 0; +} diff --git a/drivers/usb/typec/maxim/max77775_alternate.c b/drivers/usb/typec/maxim/max77775_alternate.c new file mode 100755 index 000000000000..10fce3de2adc --- /dev/null +++ b/drivers/usb/typec/maxim/max77775_alternate.c @@ -0,0 +1,1984 @@ +/* + * Copyrights (C) 2017 Samsung Electronics, Inc. + * Copyrights (C) 2017 Maxim Integrated Products, 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) +#include +#endif +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) +#include +#include +#endif +#include +#include +#include + +#define UVDM_DEBUG (0) +#define SEC_UVDM_ALIGN (4) +#define MAX_DATA_FIRST_UVDMSET 12 +#define MAX_DATA_NORMAL_UVDMSET 16 +#define CHECKSUM_DATA_COUNT 20 +#define MAX_INPUT_DATA (255) + +extern struct max77775_usbc_platform_data *g_usbc_data; + +const struct DP_DP_DISCOVER_IDENTITY DP_DISCOVER_IDENTITY = { + { + .BITS.Num_Of_VDO = 1, + .BITS.Cmd_Type = ACK, + .BITS.Reserved = 0 + }, + + { + .BITS.VDM_command = Discover_Identity, + .BITS.Rsvd2_VDM_header = 0, + .BITS.VDM_command_type = REQ, + .BITS.Object_Position = 0, + .BITS.Rsvd_VDM_header = 0, + .BITS.Structured_VDM_Version = Version_1_0, + .BITS.VDM_Type = STRUCTURED_VDM, + .BITS.Standard_Vendor_ID = 0xFF00 + } + +}; +const struct DP_DP_DISCOVER_ENTER_MODE DP_DISCOVER_ENTER_MODE = { + { + .BITS.Num_Of_VDO = 1, + .BITS.Cmd_Type = ACK, + .BITS.Reserved = 0 + }, + { + .BITS.VDM_command = Enter_Mode, + .BITS.Rsvd2_VDM_header = 0, + .BITS.VDM_command_type = REQ, + .BITS.Object_Position = 1, + .BITS.Rsvd_VDM_header = 0, + .BITS.Structured_VDM_Version = Version_1_0, + .BITS.VDM_Type = STRUCTURED_VDM, + .BITS.Standard_Vendor_ID = 0xFF01 + } +}; + +struct DP_DP_CONFIGURE DP_CONFIGURE = { + { + .BITS.Num_Of_VDO = 2, + .BITS.Cmd_Type = ACK, + .BITS.Reserved = 0 + }, + { + .BITS.VDM_command = 17, /* SVID Specific Command */ + .BITS.Rsvd2_VDM_header = 0, + .BITS.VDM_command_type = REQ, + .BITS.Object_Position = 1, + .BITS.Rsvd_VDM_header = 0, + .BITS.Structured_VDM_Version = Version_1_0, + .BITS.VDM_Type = STRUCTURED_VDM, + .BITS.Standard_Vendor_ID = 0xFF01 + }, + { + .BITS.SEL_Configuration = num_Cfg_UFP_U_as_UFP_D, + .BITS.Select_DP_V1p3 = 1, + .BITS.Select_USB_Gen2 = 0, + .BITS.Select_Reserved_1 = 0, + .BITS.Select_Reserved_2 = 0, + .BITS.DFP_D_PIN_Assign_A = 0, + .BITS.DFP_D_PIN_Assign_B = 0, + .BITS.DFP_D_PIN_Assign_C = 0, + .BITS.DFP_D_PIN_Assign_D = 1, + .BITS.DFP_D_PIN_Assign_E = 0, + .BITS.DFP_D_PIN_Assign_F = 0, + .BITS.DFP_D_PIN_Reserved = 0, + .BITS.UFP_D_PIN_Assign_A = 0, + .BITS.UFP_D_PIN_Assign_B = 0, + .BITS.UFP_D_PIN_Assign_C = 0, + .BITS.UFP_D_PIN_Assign_D = 0, + .BITS.UFP_D_PIN_Assign_E = 0, + .BITS.UFP_D_PIN_Assign_F = 0, + .BITS.UFP_D_PIN_Reserved = 0, + .BITS.DP_MODE_Reserved = 0 + } +}; + +struct SS_DEX_DISCOVER_MODE SS_DEX_DISCOVER_MODE_MODE = { + { + .BITS.Num_Of_VDO = 1, + .BITS.Cmd_Type = ACK, + .BITS.Reserved = 0 + }, + { + .BITS.VDM_command = Discover_Modes, + .BITS.Rsvd2_VDM_header = 0, + .BITS.VDM_command_type = REQ, + .BITS.Object_Position = 0, + .BITS.Rsvd_VDM_header = 0, + .BITS.Structured_VDM_Version = Version_1_0, + .BITS.VDM_Type = STRUCTURED_VDM, + .BITS.Standard_Vendor_ID = SAMSUNG_VENDOR_ID + } +}; + +struct SS_DEX_ENTER_MODE SS_DEX_DISCOVER_ENTER_MODE = { + { + .BITS.Num_Of_VDO = 1, + .BITS.Cmd_Type = ACK, + .BITS.Reserved = 0 + }, + { + .BITS.VDM_command = Enter_Mode, + .BITS.Rsvd2_VDM_header = 0, + .BITS.VDM_command_type = REQ, + .BITS.Object_Position = 1, + .BITS.Rsvd_VDM_header = 0, + .BITS.Structured_VDM_Version = Version_1_0, + .BITS.VDM_Type = STRUCTURED_VDM, + .BITS.Standard_Vendor_ID = SAMSUNG_VENDOR_ID + } +}; + +struct SS_UNSTRUCTURED_VDM_MSG SS_DEX_UNSTRUCTURED_VDM; + +static char vdm_msg_irq_state_print[9][40] = { + {"bFLAG_Vdm_Reserve_b0"}, + {"bFLAG_Vdm_Discover_ID"}, + {"bFLAG_Vdm_Discover_SVIDs"}, + {"bFLAG_Vdm_Discover_MODEs"}, + {"bFLAG_Vdm_Enter_Mode"}, + {"bFLAG_Vdm_Exit_Mode"}, + {"bFLAG_Vdm_Attention"}, + {"bFlag_Vdm_DP_Status_Update"}, + {"bFlag_Vdm_DP_Configure"}, +}; +static char dp_pin_assignment_print[7][40] = { + {"DP_Pin_Assignment_None"}, + {"DP_Pin_Assignment_A"}, + {"DP_Pin_Assignment_B"}, + {"DP_Pin_Assignment_C"}, + {"DP_Pin_Assignment_D"}, + {"DP_Pin_Assignment_E"}, + {"DP_Pin_Assignment_F"}, +}; + +static uint8_t dp_pin_assignment_data[7] = { + DP_PIN_ASSIGNMENT_NODE, + DP_PIN_ASSIGNMENT_A, + DP_PIN_ASSIGNMENT_B, + DP_PIN_ASSIGNMENT_C, + DP_PIN_ASSIGNMENT_D, + DP_PIN_ASSIGNMENT_E, + DP_PIN_ASSIGNMENT_F, +}; + +bool max77775_check_hmd_dev(struct max77775_usbc_platform_data *usbpd_data) +{ + struct max77775_hmd_power_dev *hmd_list; + int i; + bool ret = false; + uint16_t vid = usbpd_data->Vendor_ID; + uint16_t pid = usbpd_data->Product_ID; + + if (!vid && !pid) + return ret; + hmd_list = usbpd_data->hmd_list; + if (!hmd_list) { + msg_maxim("hmd_list is null!"); + return ret; + } + for (i = 0; i < MAX_NUM_HMD; i++) { + if (strlen(hmd_list[i].hmd_name) > 0) + msg_maxim("%s,0x%04x,0x%04x", + hmd_list[i].hmd_name, + hmd_list[i].vid, + hmd_list[i].pid); + } + for (i = 0; i < MAX_NUM_HMD; i++) { + if (hmd_list[i].hmd_name[0]) { + if (vid == hmd_list[i].vid && pid == hmd_list[i].pid) { + msg_maxim("hmd found %s,0x%04x,0x%04x", + hmd_list[i].hmd_name, + hmd_list[i].vid, + hmd_list[i].pid); + ret = true; + break; + } + continue; + } + break; + } + + return ret; +} + +int max77775_process_check_accessory(void *data) +{ + struct max77775_usbc_platform_data *usbpd_data = data; +#if defined(CONFIG_USB_HW_PARAM) || IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + struct otg_notify *o_notify = get_otg_notify(); +#endif + uint16_t vid = usbpd_data->Vendor_ID; + uint16_t pid = usbpd_data->Product_ID; + uint16_t acc_type = PDIC_DOCK_DETACHED; + + if (usbpd_data->usb_mock.check_accessory) + return usbpd_data->usb_mock.check_accessory(data); + + /* detect Gear VR */ + if (usbpd_data->acc_type == PDIC_DOCK_DETACHED) { + if (vid == SAMSUNG_VENDOR_ID) { + switch (pid) { + /* GearVR: Reserved GearVR PID+6 */ + case GEARVR_PRODUCT_ID: + case GEARVR_PRODUCT_ID_1: + case GEARVR_PRODUCT_ID_2: + case GEARVR_PRODUCT_ID_3: + case GEARVR_PRODUCT_ID_4: + case GEARVR_PRODUCT_ID_5: + acc_type = PDIC_DOCK_HMT; + msg_maxim("Samsung Gear VR connected."); +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_VR_USE_COUNT); +#endif + break; + case DEXDOCK_PRODUCT_ID: + acc_type = PDIC_DOCK_DEX; + msg_maxim("Samsung DEX connected."); +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_DEX_USE_COUNT); +#endif + break; + case DEXPAD_PRODUCT_ID: + acc_type = PDIC_DOCK_DEXPAD; + msg_maxim("Samsung DEX PAD connected."); +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_DEX_USE_COUNT); +#endif + break; + case HDMI_PRODUCT_ID: + acc_type = PDIC_DOCK_HDMI; + msg_maxim("Samsung HDMI connected."); + break; + default: + msg_maxim("default device connected."); + acc_type = PDIC_DOCK_NEW; + break; + } + } else if (vid == SAMSUNG_MPA_VENDOR_ID) { + switch (pid) { + case MPA_PRODUCT_ID: + acc_type = PDIC_DOCK_MPA; + msg_maxim("Samsung MPA connected."); + break; + default: + msg_maxim("default device connected."); + acc_type = PDIC_DOCK_NEW; + break; + } + } else { + msg_maxim("unknown device connected."); + acc_type = PDIC_DOCK_NEW; + } + usbpd_data->acc_type = acc_type; + } else + acc_type = usbpd_data->acc_type; + + if (acc_type != PDIC_DOCK_NEW) + pdic_send_dock_intent(acc_type); + + pdic_send_dock_uevent(vid, pid, acc_type); + + mutex_lock(&usbpd_data->hmd_power_lock); + if (max77775_check_hmd_dev(usbpd_data)) { +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + if (o_notify) + send_otg_notify(o_notify, NOTIFY_EVENT_HMD_EXT_CURRENT, 1); +#endif + } + mutex_unlock(&usbpd_data->hmd_power_lock); + return 1; +} + +void max77775_vdm_process_printf(char *type, char *vdm_data, int len) +{ +#ifdef DEBUG_VDM_PRINT + int i = 0; + + for (i = 2; i < len; i++) + msg_maxim("[%s] , %d, [0x%x]", type, i, vdm_data[i]); +#endif +} + +struct vdm_info { + void *buf; + int len; + int vdo0_num; + uint8_t w_data; +}; + +void max77775_request_vdm(struct max77775_usbc_platform_data *usbpd_data, + struct vdm_info *vdm, bool in_lock) +{ + /* send the opcode */ + usbc_cmd_data write_data; + int len = vdm->len; + int vdm_header_num = sizeof(UND_DATA_MSG_VDM_HEADER_Type); + + if (!usbpd_data) { + msg_maxim("%s usbpd_data is null.", __func__); + return; + } + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_VDM_DISCOVER_SET_VDM_REQ; + memcpy(write_data.write_data, vdm->buf, len); + write_data.write_length = len; + if (vdm->w_data) + write_data.write_data[6] = vdm->w_data; + write_data.read_length = OPCODE_SIZE + OPCODE_HEADER_SIZE + + vdm_header_num + (vdm->vdo0_num * 4); + if (in_lock) + max77775_usbc_opcode_push(usbpd_data, &write_data); + else + max77775_usbc_opcode_write(usbpd_data, &write_data); +} + +void max77775_set_discover_identity(void *data) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + struct vdm_info vdm; + + vdm.buf = (void *)&DP_DISCOVER_IDENTITY; + vdm.len = sizeof(DP_DISCOVER_IDENTITY); + vdm.vdo0_num = DP_DISCOVER_IDENTITY.byte_data.BITS.Num_Of_VDO; + vdm.w_data = 0; + + max77775_request_vdm(usbpd_data, &vdm, false); +} +EXPORT_SYMBOL_KUNIT(max77775_set_discover_identity); + +void max77775_set_dp_enter_mode(void *data) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + struct vdm_info vdm; + + vdm.buf = (void *)&DP_DISCOVER_ENTER_MODE; + vdm.len = sizeof(DP_DISCOVER_ENTER_MODE); + vdm.vdo0_num = DP_DISCOVER_ENTER_MODE.byte_data.BITS.Num_Of_VDO; + vdm.w_data = 0; + + max77775_request_vdm(usbpd_data, &vdm, true); +} +EXPORT_SYMBOL_KUNIT(max77775_set_dp_enter_mode); + +void max77775_set_dp_configure(void *data, uint8_t w_data) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + struct vdm_info vdm; + + vdm.buf = (void *)&DP_CONFIGURE; + vdm.len = sizeof(DP_CONFIGURE); + vdm.vdo0_num = DP_CONFIGURE.byte_data.BITS.Num_Of_VDO; + vdm.w_data = w_data; + + max77775_request_vdm(usbpd_data, &vdm, true); +} +EXPORT_SYMBOL_KUNIT(max77775_set_dp_configure); + +void max77775_set_dex_disover_mode(void *data) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + struct vdm_info vdm; + + vdm.buf = (void *)&SS_DEX_DISCOVER_MODE_MODE; + vdm.len = sizeof(SS_DEX_DISCOVER_MODE_MODE); + vdm.vdo0_num = SS_DEX_DISCOVER_MODE_MODE.byte_data.BITS.Num_Of_VDO; + vdm.w_data = 0; + + max77775_request_vdm(usbpd_data, &vdm, true); +} +EXPORT_SYMBOL_KUNIT(max77775_set_dex_disover_mode); + +void max77775_set_dex_enter_mode(void *data) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + struct vdm_info vdm; + + vdm.buf = (void *)&SS_DEX_DISCOVER_ENTER_MODE; + vdm.len = sizeof(SS_DEX_DISCOVER_ENTER_MODE); + vdm.vdo0_num = SS_DEX_DISCOVER_ENTER_MODE.byte_data.BITS.Num_Of_VDO; + vdm.w_data = 0; + + max77775_request_vdm(usbpd_data, &vdm, true); +} +EXPORT_SYMBOL_KUNIT(max77775_set_dex_enter_mode); + +__visible_for_testing void max77775_vdm_process_discover_identity(void *data, char *vdm_data) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + UND_DATA_MSG_ID_HEADER_Type *DATA_MSG_ID = NULL; + UND_PRODUCT_VDO_Type *DATA_MSG_PRODUCT = NULL; + + if (!usbpd_data) { + msg_maxim("%s usbpd_data is null.", __func__); + return; + } + + /* Message Type Definition */ + DATA_MSG_ID = (UND_DATA_MSG_ID_HEADER_Type *)&vdm_data[8]; + DATA_MSG_PRODUCT = (UND_PRODUCT_VDO_Type *)&vdm_data[16]; + usbpd_data->is_sent_pin_configuration = 0; + usbpd_data->is_samsung_accessory_enter_mode = 0; + usbpd_data->Vendor_ID = DATA_MSG_ID->BITS.USB_Vendor_ID; + usbpd_data->Product_ID = DATA_MSG_PRODUCT->BITS.Product_ID; + usbpd_data->Device_Version = DATA_MSG_PRODUCT->BITS.Device_Version; + msg_maxim("Vendor_ID : 0x%X, Product_ID : 0x%X Device Version 0x%X", + usbpd_data->Vendor_ID, usbpd_data->Product_ID, usbpd_data->Device_Version); + max77775_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_ALL, PDIC_NOTIFY_ID_DEVICE_INFO, + usbpd_data->Vendor_ID, usbpd_data->Product_ID, usbpd_data->Device_Version); + if (max77775_process_check_accessory(usbpd_data)) + msg_maxim("Samsung Accessory Connected."); +} +EXPORT_SYMBOL_KUNIT(max77775_vdm_process_discover_identity); + +__visible_for_testing void max77775_vdm_process_discover_svids(void *data, char *vdm_data) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + int i = 0; + uint16_t svid = 0; + DIS_MODE_DP_CAPA_Type *pDP_DIS_MODE = (DIS_MODE_DP_CAPA_Type *)&vdm_data[0]; + /* Number_of_obj has msg_header & vdm_header, each vdo has 2 svids + * This logic can work until Max VDOs 12 */ + int num_of_vdos = (pDP_DIS_MODE->MSG_HEADER.BITS.Number_of_obj - 2) * 2; + UND_VDO1_Type *DATA_MSG_VDO1 = (UND_VDO1_Type *)&vdm_data[8]; +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + int timeleft = 0; + struct otg_notify *o_notify = get_otg_notify(); +#endif + + if (!usbpd_data) { + msg_maxim("%s usbpd_data is null.", __func__); + return; + } + + usbpd_data->SVID_DP = 0; + usbpd_data->SVID_0 = DATA_MSG_VDO1->BITS.SVID_0; + usbpd_data->SVID_1 = DATA_MSG_VDO1->BITS.SVID_1; + + for (i = 0; i < num_of_vdos; i++) { + memcpy(&svid, &vdm_data[8 + i * 2], 2); + if (svid == TypeC_DP_SUPPORT) { + msg_maxim("svid_%d : 0x%X", i, svid); + usbpd_data->SVID_DP = svid; + break; + } + } + + if (usbpd_data->SVID_DP == TypeC_DP_SUPPORT) { + usbpd_data->dp_is_connect = 1; + /* If you want to support USB SuperSpeed when you connect + * Display port dongle, You should change dp_hs_connect depend + * on Pin assignment.If DP use 4lane(Pin Assignment C,E,A), + * dp_hs_connect is 1. USB can support HS.If DP use 2lane(Pin Assigment B,D,F), dp_hs_connect is 0. USB + * can support SS + */ + usbpd_data->dp_hs_connect = 1; + + max77775_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_DP, PDIC_NOTIFY_ID_DP_CONNECT, + PDIC_NOTIFY_ATTACH, usbpd_data->Vendor_ID, usbpd_data->Product_ID); +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + if (o_notify) { +#if defined(CONFIG_USB_HW_PARAM) + inc_hw_param(o_notify, USB_CCIC_DP_USE_COUNT); +#endif + msg_maxim("%s wait_event %d", __func__, + (usbpd_data->host_turn_on_wait_time)*HZ); + + timeleft = wait_event_interruptible_timeout(usbpd_data->host_turn_on_wait_q, + usbpd_data->host_turn_on_event && !usbpd_data->detach_done_wait +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + && !usbpd_data->wait_entermode +#endif + , (usbpd_data->host_turn_on_wait_time)*HZ); + msg_maxim("%s host turn on wait = %d", __func__, timeleft); + } +#endif + max77775_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_USB_DP, PDIC_NOTIFY_ID_USB_DP, + usbpd_data->dp_is_connect /*attach*/, usbpd_data->dp_hs_connect, 0); + } + msg_maxim("SVID_0 : 0x%X, SVID_1 : 0x%X", + usbpd_data->SVID_0, usbpd_data->SVID_1); +} +EXPORT_SYMBOL_KUNIT(max77775_vdm_process_discover_svids); + +static u8 max77775_vdm_choose_pin_assignment(DIS_MODE_DP_CAPA_Type *pDP_DIS_MODE) +{ + u8 pin_assignment = DP_PIN_ASSIGNMENT_NODE; + u8 port_capability = pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.BITS.Port_Capability; + u8 receptacle_indication = pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.BITS.Receptacle_Indication; + + /* this setting is based on vesa spec (DP_Alt_Mode_on_USB_Type-C) + * A USB Type-C Receptacle that supports DFP_D functionality (e.g., the receptacle can behave + * as a DisplayPort Source device or as a DFP_D on a DisplayPort Branch device) shall support + * one or more DFP_D pin assignments. Likewise, a USB Type-C Receptacle that supports UFP_D + * (e.g., the receptacle can behave as a DisplayPort Sink device or as the UFP_D on a DisplayPort + * Branch device) shall support one or more UFP_D pin assignments. + * + * A USB Type-C Plug that is intended to plug directly into a receptacle-based DFP_D (e.g., the + * plug can behave as a DisplayPort Sink device or as the UFP_D on a DisplayPort Branch device) + * shall support one or more DFP_D pin assignments. Likewise, a USB Type-C Plug that is intended + * to plug directly into a receptacle-based UFP_D (e.g., the plug can behave as a DisplayPort Source + * device or as the DFP_D on a DisplayPort Branch device) shall support one or more UFP_D + * pin assignments. + */ + + if (port_capability == num_UFP_D_Capable || port_capability == num_DFP_D_and_UFP_D_Capable) { + if (receptacle_indication == num_USB_TYPE_C_Receptacle) { + pin_assignment = pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.BITS.UFP_D_Pin_Assignments; + msg_maxim("1. support UFP_D 0x%08x", pin_assignment); + } else { + pin_assignment = pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.BITS.DFP_D_Pin_Assignments; + msg_maxim("2. support DFP_D 0x%08x", pin_assignment); + } + } else { + pin_assignment = DP_PIN_ASSIGNMENT_NODE; + msg_maxim("do not support Port_Capability %x", port_capability); + } + + return pin_assignment; +} + +__visible_for_testing void max77775_vdm_process_discover_mode(void *data, char *vdm_data) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + DIS_MODE_DP_CAPA_Type *pDP_DIS_MODE = (DIS_MODE_DP_CAPA_Type *)&vdm_data[0]; + UND_DATA_MSG_VDM_HEADER_Type *DATA_MSG_VDM = (UND_DATA_MSG_VDM_HEADER_Type *)&vdm_data[4]; + + if (!usbpd_data) { + msg_maxim("%s usbpd_data is null.", __func__); + return; + } + + msg_maxim("vendor_id = 0x%04x , svid_1 = 0x%04x", DATA_MSG_VDM->BITS.Standard_Vendor_ID, usbpd_data->SVID_1); + if (DATA_MSG_VDM->BITS.Standard_Vendor_ID == TypeC_DP_SUPPORT && usbpd_data->SVID_DP == TypeC_DP_SUPPORT) { + /* pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.BITS. */ + msg_maxim("pDP_DIS_MODE->MSG_HEADER.DATA = 0x%08X", pDP_DIS_MODE->MSG_HEADER.DATA); + msg_maxim("pDP_DIS_MODE->DATA_MSG_VDM_HEADER.DATA = 0x%08X", pDP_DIS_MODE->DATA_MSG_VDM_HEADER.DATA); + msg_maxim("pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.DATA = 0x%08X", pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.DATA); + + if (pDP_DIS_MODE->MSG_HEADER.BITS.Number_of_obj > 1) + usbpd_data->pin_assignment = max77775_vdm_choose_pin_assignment(pDP_DIS_MODE); + } + + if (DATA_MSG_VDM->BITS.Standard_Vendor_ID == SAMSUNG_VENDOR_ID) { + msg_maxim("dex mode discover_mode ack status!"); + /* pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.BITS. */ + msg_maxim("pDP_DIS_MODE->MSG_HEADER.DATA = 0x%08X", pDP_DIS_MODE->MSG_HEADER.DATA); + msg_maxim("pDP_DIS_MODE->DATA_MSG_VDM_HEADER.DATA = 0x%08X", pDP_DIS_MODE->DATA_MSG_VDM_HEADER.DATA); + msg_maxim("pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.DATA = 0x%08X", pDP_DIS_MODE->DATA_MSG_MODE_VDO_DP.DATA); + /*Samsung Enter Mode */ + if (usbpd_data->send_enter_mode_req == 0) { + msg_maxim("dex: second enter mode request"); + usbpd_data->send_enter_mode_req = 1; + max77775_set_dex_enter_mode(usbpd_data); + } + } else { + max77775_set_dp_enter_mode(usbpd_data); + } +} +EXPORT_SYMBOL_KUNIT(max77775_vdm_process_discover_mode); + +__visible_for_testing void max77775_vdm_process_enter_mode(void *data, char *vdm_data) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + UND_DATA_MSG_VDM_HEADER_Type *DATA_MSG_VDM = (UND_DATA_MSG_VDM_HEADER_Type *)&vdm_data[4]; + + if (!usbpd_data) { + msg_maxim("%s usbpd_data is null.", __func__); + return; + } + + if (DATA_MSG_VDM->BITS.VDM_command_type == 1) { + msg_maxim("EnterMode ACK."); + if (DATA_MSG_VDM->BITS.Standard_Vendor_ID == SAMSUNG_VENDOR_ID) { + usbpd_data->is_samsung_accessory_enter_mode = 1; + msg_maxim("dex mode enter_mode ack status!"); + } + } else { + msg_maxim("EnterMode NAK."); + } +} +EXPORT_SYMBOL_KUNIT(max77775_vdm_process_enter_mode); + +__visible_for_testing int max77775_vdm_dp_select_pin(void *data, int multi_function_preference) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + int pin_sel = 0; + + if (!usbpd_data) { + msg_maxim("%s usbpd_data is null.", __func__); + goto err; + } + + if (multi_function_preference) { + if (usbpd_data->pin_assignment & DP_PIN_ASSIGNMENT_D) + pin_sel = PDIC_NOTIFY_DP_PIN_D; + else if (usbpd_data->pin_assignment & DP_PIN_ASSIGNMENT_B) + pin_sel = PDIC_NOTIFY_DP_PIN_B; + else if (usbpd_data->pin_assignment & DP_PIN_ASSIGNMENT_F) + pin_sel = PDIC_NOTIFY_DP_PIN_F; + else if (usbpd_data->pin_assignment & DP_PIN_ASSIGNMENT_C) + pin_sel = PDIC_NOTIFY_DP_PIN_C; + else if (usbpd_data->pin_assignment & DP_PIN_ASSIGNMENT_E) + pin_sel = PDIC_NOTIFY_DP_PIN_E; + else if (usbpd_data->pin_assignment & DP_PIN_ASSIGNMENT_A) + pin_sel = PDIC_NOTIFY_DP_PIN_A; + else + msg_maxim("wrong pin assignment value"); + } else { + if (usbpd_data->pin_assignment & DP_PIN_ASSIGNMENT_C) + pin_sel = PDIC_NOTIFY_DP_PIN_C; + else if (usbpd_data->pin_assignment & DP_PIN_ASSIGNMENT_E) + pin_sel = PDIC_NOTIFY_DP_PIN_E; + else if (usbpd_data->pin_assignment & DP_PIN_ASSIGNMENT_A) + pin_sel = PDIC_NOTIFY_DP_PIN_A; + else if (usbpd_data->pin_assignment & DP_PIN_ASSIGNMENT_D) + pin_sel = PDIC_NOTIFY_DP_PIN_D; + else if (usbpd_data->pin_assignment & DP_PIN_ASSIGNMENT_B) + pin_sel = PDIC_NOTIFY_DP_PIN_B; + else if (usbpd_data->pin_assignment & DP_PIN_ASSIGNMENT_F) + pin_sel = PDIC_NOTIFY_DP_PIN_F; + else + msg_maxim("wrong pin assignment value"); + } +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + if (pin_sel == PDIC_NOTIFY_DP_PIN_C || + pin_sel == PDIC_NOTIFY_DP_PIN_E || + pin_sel == PDIC_NOTIFY_DP_PIN_A) + usb_restart_host_mode(usbpd_data->man, 4); + else + usb_restart_host_mode(usbpd_data->man, 2); +#endif +err: + return pin_sel; +} +EXPORT_SYMBOL_KUNIT(max77775_vdm_dp_select_pin); + +static void max77775_vdm_prepare_dp_configure(struct max77775_usbc_platform_data *usbpd_data, + DP_STATUS_UPDATE_Type *DP_STATUS) +{ + uint8_t W_DATA = 0; + uint8_t multi_func = 0; + int pin_sel = 0; + + msg_maxim("DP_STATUS_UPDATE = 0x%08X", DP_STATUS->DATA_DP_STATUS_UPDATE.DATA); + + if (!usbpd_data) { + msg_maxim("%s usbpd_data is null.", __func__); + goto skip; + } + + if (DP_STATUS->DATA_DP_STATUS_UPDATE.BITS.Port_Connected == 0x00) { + msg_maxim("port disconnected!"); + goto skip; + } + + if (usbpd_data->is_sent_pin_configuration == 0) { + multi_func = DP_STATUS->DATA_DP_STATUS_UPDATE.BITS.Multi_Function_Preference; + pin_sel = max77775_vdm_dp_select_pin(usbpd_data, multi_func); + usbpd_data->dp_selected_pin = pin_sel; + W_DATA = dp_pin_assignment_data[pin_sel]; + + msg_maxim("multi_func_preference %d, %s, W_DATA : %d", + multi_func, dp_pin_assignment_print[pin_sel], W_DATA); + + max77775_set_dp_configure(usbpd_data, W_DATA); + + usbpd_data->is_sent_pin_configuration = 1; + } else { + msg_maxim("pin configuration is already sent as %s!", + dp_pin_assignment_print[usbpd_data->dp_selected_pin]); + } +skip: + return; +} + +__visible_for_testing void max77775_vdm_announce_hpd(struct max77775_usbc_platform_data *usbpd_data, + DP_STATUS_UPDATE_Type *DP_STATUS) +{ + int hpd = 0; + int hpdirq = 0; + + if (!usbpd_data) { + msg_maxim("%s usbpd_data is null.", __func__); + return; + } + + if (DP_STATUS->DATA_DP_STATUS_UPDATE.BITS.HPD_State == 1) + hpd = PDIC_NOTIFY_HIGH; + else if (DP_STATUS->DATA_DP_STATUS_UPDATE.BITS.HPD_State == 0) + hpd = PDIC_NOTIFY_LOW; + + if (DP_STATUS->DATA_DP_STATUS_UPDATE.BITS.HPD_Interrupt == 1) + hpdirq = PDIC_NOTIFY_IRQ; + + max77775_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_DP, PDIC_NOTIFY_ID_DP_HPD, + hpd, hpdirq, 0); +} +EXPORT_SYMBOL_KUNIT(max77775_vdm_announce_hpd); + +static void max77775_vdm_process_dp_status_update(void *data, char *vdm_data) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + VDO_MESSAGE_Type *VDO_MSG; + DP_STATUS_UPDATE_Type *DP_STATUS; + int i; + + if (!usbpd_data) { + msg_maxim("%s usbpd_data is null.", __func__); + return; + } + + if (usbpd_data->SVID_DP == TypeC_DP_SUPPORT) { + DP_STATUS = (DP_STATUS_UPDATE_Type *)&vdm_data[0]; + + max77775_vdm_prepare_dp_configure(usbpd_data, DP_STATUS); + + max77775_vdm_announce_hpd(usbpd_data, DP_STATUS); + } else { + /* need to check F/W code */ + VDO_MSG = (VDO_MESSAGE_Type *)&vdm_data[8]; + for (i = 0; i < 6; i++) + msg_maxim("VDO_%d : %d", i+1, VDO_MSG->VDO[i]); + } +} + +static void max77775_vdm_process_dp_attention(void *data, char *vdm_data) +{ + max77775_vdm_process_dp_status_update(data, vdm_data); +} + +__visible_for_testing void max77775_vdm_process_dp_configure(void *data, char *vdm_data) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + UND_DATA_MSG_VDM_HEADER_Type *DATA_MSG_VDM = (UND_DATA_MSG_VDM_HEADER_Type *)&vdm_data[4]; + + if (!usbpd_data) { + msg_maxim("%s usbpd_data is null.", __func__); + return; + } + + msg_maxim("vendor_id = 0x%04x , svid_1 = 0x%04x", DATA_MSG_VDM->BITS.Standard_Vendor_ID, usbpd_data->SVID_1); + if (usbpd_data->SVID_DP == TypeC_DP_SUPPORT) + schedule_work(&usbpd_data->dp_configure_work); + if (DATA_MSG_VDM->BITS.Standard_Vendor_ID == TypeC_DP_SUPPORT && usbpd_data->SVID_1 == TypeC_Dex_SUPPORT) { + /* Samsung Discover Modes packet */ + usbpd_data->send_enter_mode_req = 0; + max77775_set_dex_disover_mode(usbpd_data); + } +} +EXPORT_SYMBOL_KUNIT(max77775_vdm_process_dp_configure); + +void max77775_vdm_message_handler(struct max77775_usbc_platform_data *usbpd_data, + char *opcode_data, int len) +{ + unsigned char vdm_data[OPCODE_DATA_LENGTH] = {0,}; + UND_DATA_MSG_VDM_HEADER_Type vdm_header; + + if (!usbpd_data) { + msg_maxim("%s usbpd_data is null.", __func__); + return; + } + + memset(&vdm_header, 0, sizeof(UND_DATA_MSG_VDM_HEADER_Type)); + memcpy(vdm_data, opcode_data, len); + memcpy(&vdm_header, &vdm_data[4], sizeof(vdm_header)); + if ((vdm_header.BITS.VDM_command_type) == SEC_UVDM_RESPONDER_NAK) { + msg_maxim("IGNORE THE NAK RESPONSE !!![%d]", vdm_data[1]); + return; + } + + switch (vdm_data[1]) { + case OPCODE_ID_VDM_DISCOVER_IDENTITY: + max77775_vdm_process_printf("VDM_DISCOVER_IDENTITY", vdm_data, len); + max77775_vdm_process_discover_identity(usbpd_data, vdm_data); + break; + case OPCODE_ID_VDM_DISCOVER_SVIDS: + max77775_vdm_process_printf("VDM_DISCOVER_SVIDS", vdm_data, len); + max77775_vdm_process_discover_svids(usbpd_data, vdm_data); + break; + case OPCODE_ID_VDM_DISCOVER_MODES: + max77775_vdm_process_printf("VDM_DISCOVER_MODES", vdm_data, len); + vdm_data[0] = vdm_data[2]; + vdm_data[1] = vdm_data[3]; + max77775_vdm_process_discover_mode(usbpd_data, vdm_data); + break; + case OPCODE_ID_VDM_ENTER_MODE: + max77775_vdm_process_printf("VDM_ENTER_MODE", vdm_data, len); + max77775_vdm_process_enter_mode(usbpd_data, vdm_data); + break; + case OPCODE_ID_VDM_SVID_DP_STATUS: + max77775_vdm_process_printf("VDM_SVID_DP_STATUS", vdm_data, len); + vdm_data[0] = vdm_data[2]; + vdm_data[1] = vdm_data[3]; + max77775_vdm_process_dp_status_update(usbpd_data, vdm_data); + break; + case OPCODE_ID_VDM_SVID_DP_CONFIGURE: + max77775_vdm_process_printf("VDM_SVID_DP_CONFIGURE", vdm_data, len); + max77775_vdm_process_dp_configure(usbpd_data, vdm_data); + break; + case OPCODE_ID_VDM_ATTENTION: + max77775_vdm_process_printf("VDM_ATTENTION", vdm_data, len); + vdm_data[0] = vdm_data[2]; + vdm_data[1] = vdm_data[3]; + max77775_vdm_process_dp_attention(usbpd_data, vdm_data); + break; + case OPCODE_ID_VDM_EXIT_MODE: + break; + default: + break; + } +} + +struct vdm_response { + u8 data; + int write_length; + int read_length; +}; + +static int max77775_get_vdm_response(struct max77775_usbc_platform_data *usbpd_data, + struct vdm_response *vdm) +{ + /* send the opcode */ + usbc_cmd_data write_data; + int ret = 0; + + if (!usbpd_data) { + msg_maxim("%s usbpd_data is null.", __func__); + ret = -ENOENT; + goto skip; + } + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_VDM_DISCOVER_GET_VDM_RESP; + write_data.write_data[0] = vdm->data; + write_data.write_length = vdm->write_length; + write_data.read_length = vdm->read_length; + ret = max77775_usbc_opcode_write(usbpd_data, &write_data); + if (ret) + msg_maxim("%s error. ret=%d", __func__, ret); +skip: + return ret; +} + +__visible_for_testing int max77775_get_discover_identity(void *data) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + struct vdm_response vdm; + + if (!usbpd_data) { + msg_maxim("%s usbpd_data is null.", __func__); + return -ENOENT; + } + + vdm.data = OPCODE_ID_VDM_DISCOVER_IDENTITY; + vdm.write_length = 1; + vdm.read_length = DISCOVER_IDENTITY_RESPONSE_SIZE; + return max77775_get_vdm_response(usbpd_data, &vdm); +} +EXPORT_SYMBOL_KUNIT(max77775_get_discover_identity); + +__visible_for_testing int max77775_get_discover_svids(void *data) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + struct vdm_response vdm; + + vdm.data = OPCODE_ID_VDM_DISCOVER_SVIDS; + vdm.write_length = 1; + vdm.read_length = DISCOVER_SVIDS_RESPONSE_SIZE; + return max77775_get_vdm_response(usbpd_data, &vdm); +} +EXPORT_SYMBOL_KUNIT(max77775_get_discover_svids); + +__visible_for_testing int max77775_get_discover_modes(void *data) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + struct vdm_response vdm; + + vdm.data = OPCODE_ID_VDM_DISCOVER_MODES; + vdm.write_length = 1; + vdm.read_length = DISCOVER_MODES_RESPONSE_SIZE; + return max77775_get_vdm_response(usbpd_data, &vdm); +} +EXPORT_SYMBOL_KUNIT(max77775_get_discover_modes); + +__visible_for_testing int max77775_get_enter_mode(void *data) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + struct vdm_response vdm; + + vdm.data = OPCODE_ID_VDM_ENTER_MODE; + vdm.write_length = 1; + vdm.read_length = ENTER_MODE_RESPONSE_SIZE; + return max77775_get_vdm_response(usbpd_data, &vdm); +} +EXPORT_SYMBOL_KUNIT(max77775_get_enter_mode); + +__visible_for_testing int max77775_get_attention(void *data) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + struct vdm_response vdm; + + vdm.data = OPCODE_ID_VDM_ATTENTION; + vdm.write_length = 1; + vdm.read_length = ATTENTION_RESPONSE_SIZE; + return max77775_get_vdm_response(usbpd_data, &vdm); +} +EXPORT_SYMBOL_KUNIT(max77775_get_attention); + +__visible_for_testing int max77775_get_dp_status_update(void *data) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + struct vdm_response vdm; + + vdm.data = OPCODE_ID_VDM_SVID_DP_STATUS; + vdm.write_length = 1; + vdm.read_length = DP_STATUS_RESPONSE_SIZE; + return max77775_get_vdm_response(usbpd_data, &vdm); +} +EXPORT_SYMBOL_KUNIT(max77775_get_dp_status_update); + +__visible_for_testing int max77775_get_dp_configure(void *data) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + struct vdm_response vdm; + + vdm.data = OPCODE_ID_VDM_SVID_DP_CONFIGURE; + vdm.write_length = 1; + vdm.read_length = DP_CONFIGURE_RESPONSE_SIZE; + return max77775_get_vdm_response(usbpd_data, &vdm); +} +EXPORT_SYMBOL_KUNIT(max77775_get_dp_configure); + +static void max77775_process_alternate_mode(void *data) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + uint32_t mode; +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + struct otg_notify *o_notify = get_otg_notify(); +#endif + + if (!usbpd_data) { + msg_maxim("%s usbpd_data is null.", __func__); + return; + } + + mode = usbpd_data->alternate_state; + + if (mode) { + msg_maxim("mode : 0x%x", mode); +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + if (is_blocked(o_notify, NOTIFY_BLOCK_TYPE_HOST)) { + msg_maxim("host is blocked, skip all the alternate mode."); + goto process_error; + } + + if (usbpd_data->mdm_block) { + msg_maxim("is blocked by mdm, skip all the alternate mode."); + goto process_error; + } +#endif + + if (mode & VDM_DISCOVER_ID) + if (max77775_get_discover_identity(usbpd_data)) + goto process_error; + if (mode & VDM_DISCOVER_SVIDS) + if (max77775_get_discover_svids(usbpd_data)) + goto process_error; + if (mode & VDM_DISCOVER_MODES) + if (max77775_get_discover_modes(usbpd_data)) + goto process_error; + if (mode & VDM_ENTER_MODE) + if (max77775_get_enter_mode(usbpd_data)) + goto process_error; + if (mode & VDM_DP_STATUS_UPDATE) + if (max77775_get_dp_status_update(usbpd_data)) + goto process_error; + if (mode & VDM_DP_CONFIGURE) + if (max77775_get_dp_configure(usbpd_data)) + goto process_error; + if (mode & VDM_ATTENTION) + if (max77775_get_attention(usbpd_data)) + goto process_error; +process_error: + usbpd_data->alternate_state = 0; + } +} + +void max77775_receive_alternate_message(struct max77775_usbc_platform_data *data, MAX77775_VDM_MSG_IRQ_STATUS_Type *VDM_MSG_IRQ_State) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + + if (!usbpd_data) { + msg_maxim("%s usbpd_data is null.", __func__); + return; + } + + if (VDM_MSG_IRQ_State->BITS.Vdm_Flag_Discover_ID) { + msg_maxim(": %s", &vdm_msg_irq_state_print[1][0]); + usbpd_data->alternate_state |= VDM_DISCOVER_ID; + } + + if (VDM_MSG_IRQ_State->BITS.Vdm_Flag_Discover_SVIDs) { + msg_maxim(": %s", &vdm_msg_irq_state_print[2][0]); + usbpd_data->alternate_state |= VDM_DISCOVER_SVIDS; + } + + if (VDM_MSG_IRQ_State->BITS.Vdm_Flag_Discover_MODEs) { + msg_maxim(": %s", &vdm_msg_irq_state_print[3][0]); + usbpd_data->alternate_state |= VDM_DISCOVER_MODES; + } + + if (VDM_MSG_IRQ_State->BITS.Vdm_Flag_Enter_Mode) { + msg_maxim(": %s", &vdm_msg_irq_state_print[4][0]); + usbpd_data->alternate_state |= VDM_ENTER_MODE; + } + + if (VDM_MSG_IRQ_State->BITS.Vdm_Flag_Exit_Mode) { + msg_maxim(": %s", &vdm_msg_irq_state_print[5][0]); + usbpd_data->alternate_state |= VDM_EXIT_MODE; + } + + if (VDM_MSG_IRQ_State->BITS.Vdm_Flag_Attention) { + msg_maxim(": %s", &vdm_msg_irq_state_print[6][0]); + usbpd_data->alternate_state |= VDM_ATTENTION; + } + + if (VDM_MSG_IRQ_State->BITS.Vdm_Flag_DP_Status_Update) { + msg_maxim(": %s", &vdm_msg_irq_state_print[7][0]); + usbpd_data->alternate_state |= VDM_DP_STATUS_UPDATE; + } + + if (VDM_MSG_IRQ_State->BITS.Vdm_Flag_DP_Configure) { + msg_maxim(": %s", &vdm_msg_irq_state_print[8][0]); + usbpd_data->alternate_state |= VDM_DP_CONFIGURE; + } + + max77775_process_alternate_mode(usbpd_data); +} + +static void set_sec_uvdm_txdataheader(void *data, int first_set, int cur_uvdmset, + int total_data_size, int remained_data_size) +{ + U_SEC_TX_DATA_HEADER *SEC_TX_DATA_HEADER; + uint8_t *SendMSG = (uint8_t *)data; + + if (first_set) + SEC_TX_DATA_HEADER = (U_SEC_TX_DATA_HEADER *)&SendMSG[12]; + else + SEC_TX_DATA_HEADER = (U_SEC_TX_DATA_HEADER *)&SendMSG[8]; + + SEC_TX_DATA_HEADER->BITS.data_size_of_current_set = + get_data_size(first_set, remained_data_size); + SEC_TX_DATA_HEADER->BITS.total_data_size = total_data_size; + SEC_TX_DATA_HEADER->BITS.order_of_current_uvdm_set = cur_uvdmset; + SEC_TX_DATA_HEADER->BITS.reserved = 0; +} + +static void set_msgheader(void *data, int msg_type, int obj_num) +{ + /* Common : Fill the VDM OpCode MSGHeader */ + SEND_VDM_BYTE_DATA *MSG_HDR; + uint8_t *SendMSG = (uint8_t *)data; + + MSG_HDR = (SEND_VDM_BYTE_DATA *)&SendMSG[0]; + MSG_HDR->BITS.Num_Of_VDO = obj_num; + if (msg_type == NAK) + MSG_HDR->BITS.Reserved = 7; + MSG_HDR->BITS.Cmd_Type = msg_type; +} + +static void set_uvdmheader(void *data, int vendor_id, int vdm_type) +{ + /* Common : Fill the UVDMHeader */ + UND_UNSTRUCTURED_VDM_HEADER_Type *UVDM_HEADER; + U_DATA_MSG_VDM_HEADER_Type *VDM_HEADER; + uint8_t *SendMSG = (uint8_t *)data; + + UVDM_HEADER = (UND_UNSTRUCTURED_VDM_HEADER_Type *)&SendMSG[4]; + UVDM_HEADER->BITS.USB_Vendor_ID = vendor_id; + UVDM_HEADER->BITS.VDM_TYPE = vdm_type; + VDM_HEADER = (U_DATA_MSG_VDM_HEADER_Type *)&SendMSG[4]; + VDM_HEADER->BITS.VDM_command = 4; //from s2mm005 concept +} + +static void set_sec_uvdmheader(void *data, int pid, bool data_type, int cmd_type, + bool dir, int total_uvdmset_num, uint8_t received_data) +{ + U_SEC_UVDM_HEADER *SEC_VDM_HEADER; + uint8_t *SendMSG = (uint8_t *)data; + + SEC_VDM_HEADER = (U_SEC_UVDM_HEADER *)&SendMSG[8]; + SEC_VDM_HEADER->BITS.pid = pid; + SEC_VDM_HEADER->BITS.data_type = data_type; + SEC_VDM_HEADER->BITS.command_type = cmd_type; + SEC_VDM_HEADER->BITS.direction = dir; + if (dir == DIR_OUT) + SEC_VDM_HEADER->BITS.total_number_of_uvdm_set = total_uvdmset_num; + if (data_type == TYPE_SHORT) + SEC_VDM_HEADER->BITS.data = received_data; + else + SEC_VDM_HEADER->BITS.data = 0; +#if (UVDM_DEBUG) + msg_maxim("pid = 0x%x data_type=%d ,cmd_type =%d,direction= %d, total_num_of_uvdm_set = %d", + SEC_VDM_HEADER->BITS.pid, + SEC_VDM_HEADER->BITS.data_type, + SEC_VDM_HEADER->BITS.command_type, + SEC_VDM_HEADER->BITS.direction, + SEC_VDM_HEADER->BITS.total_number_of_uvdm_set); +#endif +} + +static void set_sec_uvdm_txdata_tailer(void *data) +{ + U_SEC_TX_DATA_TAILER *SEC_TX_DATA_TAILER; + uint8_t *SendMSG = (uint8_t *)data; + + SEC_TX_DATA_TAILER = (U_SEC_TX_DATA_TAILER *)&SendMSG[28]; + SEC_TX_DATA_TAILER->BITS.checksum = get_checksum(SendMSG, 8, SAMSUNGUVDM_CHECKSUM_DATA_COUNT); + SEC_TX_DATA_TAILER->BITS.reserved = 0; +} + +static void set_sec_uvdm_rxdata_header(void *data, int cur_uvdmset_num, int cur_uvdmset_data, int ack) +{ + U_SEC_RX_DATA_HEADER *SEC_UVDM_RX_HEADER; + uint8_t *SendMSG = (uint8_t *)data; + + SEC_UVDM_RX_HEADER = (U_SEC_RX_DATA_HEADER *)&SendMSG[8]; + SEC_UVDM_RX_HEADER->BITS.order_of_current_uvdm_set = cur_uvdmset_num; + SEC_UVDM_RX_HEADER->BITS.received_data_size_of_current_set = cur_uvdmset_data; + SEC_UVDM_RX_HEADER->BITS.result_value = ack; + SEC_UVDM_RX_HEADER->BITS.reserved = 0; +} + +static int max77775_request_uvdm(struct max77775_usbc_platform_data *usbpd_data, + void *data, int specific_read_len) +{ + struct SS_UNSTRUCTURED_VDM_MSG vdm_opcode_msg; + uint8_t *SendMSG = (uint8_t *)data; + usbc_cmd_data write_data; + int len = sizeof(struct SS_UNSTRUCTURED_VDM_MSG); + + if (!usbpd_data) + return -ENOENT; + + memset(&vdm_opcode_msg, 0, len); + /* Common : MSGHeader */ + memcpy(&vdm_opcode_msg.byte_data, &SendMSG[0], sizeof(SEND_VDM_BYTE_DATA)); + /* Copy the data from SendMSG buffer */ + memcpy(&vdm_opcode_msg.VDO_MSG.VDO[0], &SendMSG[SAMSUNGUVDM_UVDM_HEADER_OFFSET], + sizeof(VDO_MESSAGE_Type)); + /* Write SendMSG buffer via I2C */ + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_VDM_DISCOVER_SET_VDM_REQ; + write_data.is_uvdm = 1; + memcpy(write_data.write_data, &vdm_opcode_msg, sizeof(vdm_opcode_msg)); + write_data.write_length = len; + if (specific_read_len) + write_data.read_length = specific_read_len; + else + write_data.read_length = len; + max77775_usbc_opcode_write(usbpd_data, &write_data); + msg_maxim("opcode is sent"); + return 0; +} + +void max77775_send_dex_fan_unstructured_vdm_message(void *data, int cmd) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + uint8_t SendMSG[32] = {0,}; + uint32_t message = (uint32_t)cmd; + VDO_MESSAGE_Type *VDO_MSG = (VDO_MESSAGE_Type *)&SendMSG[4]; + + if (!usbpd_data) + return; + + set_msgheader(SendMSG, ACK, 2); + + VDO_MSG->VDO[0] = 0x04E80000; + VDO_MSG->VDO[1] = message; + + md75_info_usb("%s: cmd : 0x%x\n", __func__, cmd); + + max77775_request_uvdm(usbpd_data, SendMSG, SAMSUNGUVDM_DEXFAN_RESPONSE_SIZE); +} +EXPORT_SYMBOL_KUNIT(max77775_send_dex_fan_unstructured_vdm_message); + +void max77775_set_alternate_mode_opcode(void *data, int mode) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + usbc_cmd_data write_data; + + if (!usbpd_data) + return; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_SET_ALTERNATEMODE; + write_data.write_data[0] = mode; + write_data.write_length = 0x1; + write_data.read_length = 0x1; + max77775_usbc_opcode_write(usbpd_data, &write_data); +} + +static void max77775_print_usbc_status_register + (struct max77775_usbc_platform_data *usbpd_data) +{ + u8 status[7] = {0,}; + + if (!usbpd_data) + return; + + max77775_read_reg(usbpd_data->muic, MAX77775_USBC_REG_USBC_STATUS1, &status[0]); + max77775_read_reg(usbpd_data->muic, MAX77775_USBC_REG_USBC_STATUS2, &status[1]); + max77775_read_reg(usbpd_data->muic, MAX77775_USBC_REG_BC_STATUS, &status[2]); + max77775_read_reg(usbpd_data->muic, MAX77775_USBC_REG_CC_STATUS1, &status[3]); + max77775_read_reg(usbpd_data->muic, MAX77775_USBC_REG_CC_STATUS2, &status[4]); + max77775_read_reg(usbpd_data->muic, MAX77775_USBC_REG_PD_STATUS1, &status[5]); + max77775_read_reg(usbpd_data->muic, MAX77775_USBC_REG_PD_STATUS2, &status[6]); + + msg_maxim("USBC1:0x%02x, USBC2:0x%02x, BC:0x%02x", + status[0], status[1], status[2]); + msg_maxim("CC_STATUS0:0x%x, CC_STATUS1:0x%x, PD_STATUS0:0x%x, PD_STATUS1:0x%x", + status[3], status[4], status[5], status[6]); +} + +static void max77775_print_usbc_int_mask_register + (struct max77775_usbc_platform_data *usbpd_data) +{ + u8 status[4] = {0,}; + + if (!usbpd_data) + return; + + max77775_read_reg(usbpd_data->muic, MAX77775_USBC_REG_UIC_INT_M, &status[0]); + max77775_read_reg(usbpd_data->muic, MAX77775_USBC_REG_CC_INT_M, &status[1]); + max77775_read_reg(usbpd_data->muic, MAX77775_USBC_REG_PD_INT_M, &status[2]); + max77775_read_reg(usbpd_data->muic, MAX77775_USBC_REG_VDM_INT_M, &status[3]); + + msg_maxim("UIC_INT_M:0x%x, CC_INT_M:0x%x, PD_INT_M:0x%x, VDM_INT_M:0x%x", + status[0], status[1], status[2], status[3]); +} + +static void max77775_set_usbc_int_mask_register + (struct max77775_usbc_platform_data *usbpd_data) +{ + u8 status[2] = {0,}; + + if (!usbpd_data) + return; + + max77775_write_reg(usbpd_data->muic, MAX77775_USBC_REG_VDM_INT_M, 0xF0); + max77775_write_reg(usbpd_data->muic, MAX77775_USBC_REG_PD_INT_M, 0x0); + max77775_read_reg(usbpd_data->muic, MAX77775_USBC_REG_PD_INT_M, &status[0]); + max77775_read_reg(usbpd_data->muic, MAX77775_USBC_REG_VDM_INT_M, &status[1]); + + msg_maxim("PD_INT_M:0x%x, VDM_INT_M:0x%x", status[0], status[1]); +} + +#if defined(CONFIG_MAX77775_CCOPEN_AFTER_WATERCABLE) +static void max77775_print_usbc_spare_register + (struct max77775_usbc_platform_data *usbpd_data) +{ + u8 status[2] = {0,}; + + if (!usbpd_data) + return; + + max77775_read_reg(usbpd_data->muic, MAX77775_USBC_REG_SPARE_STATUS1, &status[0]); + max77775_read_reg(usbpd_data->muic, MAX77775_USBC_REG_SPARE_INT_M, &status[1]); + + msg_maxim("TA_STATUS:0x%x, SPARE_INT_M:0x%x", + status[0], status[1]); +} +#endif + +void max77775_set_enable_alternate_mode(int mode) +{ + struct max77775_usbc_platform_data *usbpd_data = NULL; + static int check_is_driver_loaded; + int is_first_booting = 0; + struct max77775_pd_data *pd_data = NULL; + + usbpd_data = g_usbc_data; + + if (!usbpd_data) + return; + + is_first_booting = usbpd_data->is_first_booting; + pd_data = usbpd_data->pd_data; + + msg_maxim("is_first_booting : %x mode %x", + usbpd_data->is_first_booting, mode); +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + store_usblog_notify(NOTIFY_ALTERNATEMODE, (void *)&mode, NULL); +#endif + usbpd_data->set_altmode = mode; + + if ((mode & ALTERNATE_MODE_NOT_READY) && + (mode & ALTERNATE_MODE_READY)) { + msg_maxim("mode is invalid!"); + return; + } + if ((mode & ALTERNATE_MODE_START) && (mode & ALTERNATE_MODE_STOP)) { + msg_maxim("mode is invalid!"); + return; + } + + if (mode & ALTERNATE_MODE_NOT_READY) { + check_is_driver_loaded = 0; + msg_maxim("alternate mode is not ready!"); + } else if (mode & ALTERNATE_MODE_READY) { + check_is_driver_loaded = 1; + msg_maxim("alternate mode is ready!"); + } else { + ; + } + + if (check_is_driver_loaded) { + switch (is_first_booting) { + case 0: /*this routine is calling after complete a booting.*/ + if (mode & ALTERNATE_MODE_START) { + max77775_set_alternate_mode_opcode(usbpd_data, + MAXIM_ENABLE_ALTERNATE_SRC_VDM); + msg_maxim("[NO BOOTING TIME] !!!alternate mode is started!"); + } else if (mode & ALTERNATE_MODE_STOP) { + max77775_set_alternate_mode_opcode(usbpd_data, + MAXIM_ENABLE_ALTERNATE_SRCCAP); + msg_maxim("[NO BOOTING TIME] alternate mode is stopped!"); + } + break; + case 1: + if (mode & ALTERNATE_MODE_START) { + msg_maxim("[ON BOOTING TIME] !!!alternate mode is started!"); + max77775_set_alternate_mode_opcode(usbpd_data, + MAXIM_ENABLE_ALTERNATE_SRC_VDM); + msg_maxim("!![ON BOOTING TIME] SEND THE PACKET REGARDING IN CASE OF VR/DP "); + /* FOR THE DEX FUNCTION. */ + max77775_print_usbc_status_register(usbpd_data); + + max77775_print_usbc_int_mask_register(usbpd_data); + + max77775_set_usbc_int_mask_register(usbpd_data); +#if defined(CONFIG_MAX77775_CCOPEN_AFTER_WATERCABLE) + max77775_print_usbc_spare_register(usbpd_data); +#endif + usbpd_data->is_first_booting = 0; + } else if (mode & ALTERNATE_MODE_STOP) { +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + max77775_set_alternate_mode_opcode(usbpd_data, + MAXIM_ENABLE_ALTERNATE_SRCCAP); +#endif + msg_maxim("[ON BOOTING TIME] alternate mode is stopped!"); + } + break; + default: + msg_maxim("[Never calling] is_first_booting [ %d]", is_first_booting); + break; + } + } +} + +static int check_is_wait_ack_accessroy(int vid, int pid, int svid) +{ + int should_wait = true; + + if (vid == SAMSUNG_VENDOR_ID && pid == DEXDOCK_PRODUCT_ID && svid == TypeC_DP_SUPPORT) { + msg_maxim("no need to wait ack response!"); + should_wait = false; + } + return should_wait; +} + +static int max77775_send_sec_unstructured_short_vdm_message(void *data, void *buf, size_t size) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + uint8_t SendMSG[32] = {0,}; + /* Message Type Definition */ + uint8_t received_data = 0; + int time_left; +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + int event; +#endif + + if ((buf == NULL) || size <= 0) { + msg_maxim("given data is not valid !"); + return -EINVAL; + } + + if (!usbpd_data->is_samsung_accessory_enter_mode) { + msg_maxim("samsung_accessory mode is not ready!"); + return -ENXIO; + } + /* 1. Calc the receivced data size and determin the uvdm set count and last data of uvdm set. */ + received_data = *(char *)buf; + /* 2. Common : Fill the MSGHeader */ + set_msgheader(SendMSG, ACK, 2); + /* 3. Common : Fill the UVDMHeader*/ + set_uvdmheader(SendMSG, SAMSUNG_VENDOR_ID, 0); + /* 4. Common : Fill the First SEC_VDMHeader*/ + set_sec_uvdmheader(SendMSG, usbpd_data->Product_ID, TYPE_SHORT, SEC_UVDM_ININIATOR, DIR_OUT, 1, received_data); + usbpd_data->is_in_first_sec_uvdm_req = true; + + usbpd_data->uvdm_error = 0; + max77775_request_uvdm(usbpd_data, SendMSG, 0); + + if (check_is_wait_ack_accessroy(usbpd_data->Vendor_ID, usbpd_data->Product_ID, usbpd_data->SVID_DP)) { + reinit_completion(&usbpd_data->uvdm_longpacket_out_wait); + /* Wait Response*/ + time_left = + wait_for_completion_interruptible_timeout(&usbpd_data->uvdm_longpacket_out_wait, + msecs_to_jiffies(SAMSUNGUVDM_WAIT_MS)); + if (time_left <= 0) { + usbpd_data->is_in_first_sec_uvdm_req = false; +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_UVDM_TIMEOUT; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + return -ETIME; + } + if (usbpd_data->uvdm_error) { + usbpd_data->is_in_first_sec_uvdm_req = false; + return usbpd_data->uvdm_error; + } + } + msg_maxim("exit : short data transfer complete!"); + usbpd_data->is_in_first_sec_uvdm_req = false; + return size; +} + +static int max77775_send_sec_unstructured_long_vdm_message(void *data, void *buf, size_t size) +{ + struct max77775_usbc_platform_data *usbpd_data; + uint8_t SendMSG[32] = {0,}; + uint8_t *SEC_DATA; + int need_uvdmset_count = 0; + int cur_uvdmset_data = 0; + int cur_uvdmset_num = 0; + int accumulated_data_size = 0; + int remained_data_size = 0; + uint8_t received_data[MAX_INPUT_DATA] = {0,}; + int time_left; + int i; + int received_data_index; +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + int event; +#endif + + usbpd_data = data; + if (!usbpd_data) + return -ENXIO; + + if (!buf) { + msg_maxim("given data is not valid !"); + return -EINVAL; + } + + /* 1. Calc the receivced data size and determin the uvdm set count and last data of uvdm set. */ + set_endian(buf, received_data, size); + need_uvdmset_count = set_uvdmset_count(size); + msg_maxim("need_uvdmset_count = %d", need_uvdmset_count); + usbpd_data->is_in_first_sec_uvdm_req = true; + usbpd_data->is_in_sec_uvdm_out = DIR_OUT; + cur_uvdmset_num = 1; + accumulated_data_size = 0; + remained_data_size = size; + received_data_index = 0; + /* 2. Common : Fill the MSGHeader */ + set_msgheader(SendMSG, ACK, 7); + /* 3. Common : Fill the UVDMHeader*/ + set_uvdmheader(SendMSG, SAMSUNG_VENDOR_ID, 0); + /* 4. Common : Fill the First SEC_VDMHeader*/ + if (usbpd_data->is_in_first_sec_uvdm_req) + set_sec_uvdmheader(SendMSG, usbpd_data->Product_ID, + TYPE_LONG, SEC_UVDM_ININIATOR, DIR_OUT, need_uvdmset_count, 0); + + while (cur_uvdmset_num <= need_uvdmset_count) { + cur_uvdmset_data = 0; + time_left = 0; + + set_sec_uvdm_txdataheader(SendMSG, usbpd_data->is_in_first_sec_uvdm_req, + cur_uvdmset_num, size, remained_data_size); + + cur_uvdmset_data = get_data_size(usbpd_data->is_in_first_sec_uvdm_req, remained_data_size); + msg_maxim("data_size_of_current_set = %d ,total_data_size = %ld, order_of_current_uvdm_set = %d", + cur_uvdmset_data, size, cur_uvdmset_num); + /* 6. Common : Fill the DATA */ + if (usbpd_data->is_in_first_sec_uvdm_req) { + SEC_DATA = (uint8_t *)&SendMSG[8+8]; + for (i = 0; i < SAMSUNGUVDM_MAXDATA_FIRST_UVDMSET; i++) + SEC_DATA[i] = received_data[received_data_index++]; + } else { + SEC_DATA = (uint8_t *)&SendMSG[8+4]; + for (i = 0; i < SAMSUNGUVDM_MAXDATA_NORMAL_UVDMSET; i++) + SEC_DATA[i] = received_data[received_data_index++]; + } + /* 7. Common : Fill the TX_DATA_Tailer */ + set_sec_uvdm_txdata_tailer(SendMSG); + /* 8. Send data to PDIC */ + usbpd_data->uvdm_error = 0; + max77775_request_uvdm(usbpd_data, SendMSG, 0); + /* 9. Wait Response*/ + reinit_completion(&usbpd_data->uvdm_longpacket_out_wait); + time_left = + wait_for_completion_interruptible_timeout(&usbpd_data->uvdm_longpacket_out_wait, + msecs_to_jiffies(SAMSUNGUVDM_WAIT_MS)); + if (time_left <= 0) { +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_UVDM_TIMEOUT; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + return -ETIME; + } + + if (usbpd_data->uvdm_error) + return usbpd_data->uvdm_error; + + accumulated_data_size += cur_uvdmset_data; + remained_data_size -= cur_uvdmset_data; + if (usbpd_data->is_in_first_sec_uvdm_req) + usbpd_data->is_in_first_sec_uvdm_req = false; + cur_uvdmset_num++; + } + return size; +} + +__visible_for_testing int max77775_check_sec_uvdm_responder(struct max77775_usbc_platform_data *usbpd_data, + int command_type, char *prefix) +{ + bool ret = false; + + if (!usbpd_data) + return ret; + + if (command_type == SEC_UVDM_RESPONDER_ACK) + return true; + else if (command_type == SEC_UVDM_RESPONDER_NAK) { + msg_maxim("%s SEC_UVDM_RESPONDER_NAK received", prefix); + usbpd_data->uvdm_error = -ENODATA; + } else if (command_type == SEC_UVDM_RESPONDER_BUSY) { + msg_maxim("%s SEC_UVDM_RESPONDER_BUSY received", prefix); + usbpd_data->uvdm_error = -EBUSY; + } else { + msg_maxim("%s Undefined RESPONDER value", prefix); + usbpd_data->uvdm_error = -EPROTO; + } + return ret; +} +EXPORT_SYMBOL_KUNIT(max77775_check_sec_uvdm_responder); + +__visible_for_testing int max77775_check_sec_uvdm_rx_header(struct max77775_usbc_platform_data *usbpd_data, + int result_value) +{ + bool ret = false; + + if (!usbpd_data) + return ret; + + if (result_value == RX_ACK) { + return true; + } else if (result_value == RX_NAK) { + msg_maxim("DIR_OUT RX_NAK received"); + usbpd_data->uvdm_error = -ENODATA; + } else if (result_value == RX_BUSY) { + msg_maxim("DIR_OUT RX_BUSY received"); + usbpd_data->uvdm_error = -EBUSY; + } else { + msg_maxim("DIR_OUT Undefined RX value"); + usbpd_data->uvdm_error = -EPROTO; + } + return ret; +} +EXPORT_SYMBOL_KUNIT(max77775_check_sec_uvdm_rx_header); + +static void print_sec_uvdm_response_header_data(uint32_t data) +{ +#if (UVDM_DEBUG) + msg_maxim("DIR_OUT SEC_UVDM_RESPONSE_HEADER : 0x%x", data); +#endif +} + +static void print_sec_uvdm_response_header(U_SEC_UVDM_RESPONSE_HEADER *header) +{ + if (!header) + return; + +#if (UVDM_DEBUG) + msg_maxim("DIR_IN data_type = %d , command_type = %d, direction=%d, total_number_of_uvdm_set=%d", + header->BITS.data_type, + header->BITS.command_type, + header->BITS.direction, + header->BITS.total_number_of_uvdm_set); +#endif +} + +static void print_sec_uvdm_rx_header_data(uint32_t data) +{ +#if (UVDM_DEBUG) + msg_maxim("DIR_OUT SEC_UVDM_RX_HEADER : 0x%x", data); +#endif +} + +static void print_sec_uvdm_tx_header(U_SEC_TX_DATA_HEADER *header) +{ + if (!header) + return; + +#if (UVDM_DEBUG) + msg_maxim("DIR_IN order_of_current_uvdm_set = %d , total_data_size = %d, data_size_of_current_set=%d", + header->BITS.order_of_current_uvdm_set, + header->BITS.total_data_size, + header->BITS.data_size_of_current_set); +#endif +} + +static void max77775_process_out_uvdm_response + (struct max77775_usbc_platform_data *usbpd_data, uint8_t read_msg[]) +{ + U_SEC_UVDM_RESPONSE_HEADER *SEC_UVDM_RESPONSE_HEADER; + U_SEC_RX_DATA_HEADER *SEC_UVDM_RX_HEADER; + + if (!usbpd_data) + return; + + if (usbpd_data->is_in_first_sec_uvdm_req) { + SEC_UVDM_RESPONSE_HEADER = (U_SEC_UVDM_RESPONSE_HEADER *)&read_msg[VDO1_OFFSET]; + + print_sec_uvdm_response_header_data(SEC_UVDM_RESPONSE_HEADER->data); + + if (SEC_UVDM_RESPONSE_HEADER->BITS.data_type == TYPE_LONG) { + if (max77775_check_sec_uvdm_responder(usbpd_data, + (int)SEC_UVDM_RESPONSE_HEADER->BITS.command_type, "DIR_OUT")) { + SEC_UVDM_RX_HEADER = (U_SEC_RX_DATA_HEADER *)&read_msg[VDO2_OFFSET]; + + print_sec_uvdm_rx_header_data(SEC_UVDM_RX_HEADER->data); + + max77775_check_sec_uvdm_rx_header + (usbpd_data, SEC_UVDM_RX_HEADER->BITS.result_value); + } + } else { /* TYPE_SHORT */ + max77775_check_sec_uvdm_responder(usbpd_data, + (int)SEC_UVDM_RESPONSE_HEADER->BITS.command_type, "DIR_OUT SHORT"); + } + } else { /* after 2nd packet for TYPE_LONG */ + SEC_UVDM_RX_HEADER = (U_SEC_RX_DATA_HEADER *)&read_msg[6]; + max77775_check_sec_uvdm_rx_header + (usbpd_data, SEC_UVDM_RX_HEADER->BITS.result_value); + } + complete(&usbpd_data->uvdm_longpacket_out_wait); + msg_maxim("DIR_OUT complete!"); +} + +static void max77775_process_in_uvdm_response + (struct max77775_usbc_platform_data *usbpd_data, uint8_t read_msg[]) +{ + U_SEC_UVDM_RESPONSE_HEADER *SEC_UVDM_RESPONSE_HEADER; + U_SEC_TX_DATA_HEADER *SEC_UVDM_TX_HEADER; + + if (!usbpd_data) + return; + + if (usbpd_data->is_in_first_sec_uvdm_req) { /* LONG and SHORT response is same */ + SEC_UVDM_RESPONSE_HEADER = (U_SEC_UVDM_RESPONSE_HEADER *)&read_msg[VDO1_OFFSET]; + SEC_UVDM_TX_HEADER = (U_SEC_TX_DATA_HEADER *)&read_msg[VDO2_OFFSET]; + + print_sec_uvdm_response_header(SEC_UVDM_RESPONSE_HEADER); + + if (max77775_check_sec_uvdm_responder(usbpd_data, + (int)SEC_UVDM_RESPONSE_HEADER->BITS.command_type, "DIR_IN")) { + + memcpy(usbpd_data->ReadMSG, read_msg, OPCODE_DATA_LENGTH); + + print_sec_uvdm_tx_header(SEC_UVDM_TX_HEADER); + + msg_maxim("DIR_IN 1st Response"); + } + } else { + /* don't have ack packet after 2nd sec_tx_data_header */ + memcpy(usbpd_data->ReadMSG, read_msg, OPCODE_DATA_LENGTH); + SEC_UVDM_TX_HEADER = (U_SEC_TX_DATA_HEADER *)&usbpd_data->ReadMSG[VDO1_OFFSET]; + + print_sec_uvdm_tx_header(SEC_UVDM_TX_HEADER); + + if (SEC_UVDM_TX_HEADER->data != 0) + msg_maxim("DIR_IN %dth Response", SEC_UVDM_TX_HEADER->BITS.order_of_current_uvdm_set); + else + msg_maxim("DIR_IN Last Response. It's zero"); + } + complete(&usbpd_data->uvdm_longpacket_in_wait); + msg_maxim("DIR_IN complete!"); +} + +void max77775_uvdm_opcode_response_handler(struct max77775_usbc_platform_data *usbpd_data, + char *opcode_data, int len) +{ + int unstructured_len = sizeof(struct SS_UNSTRUCTURED_VDM_MSG); + uint8_t read_msg[32] = {0,}; + + if (!usbpd_data) + return; + + if (len != unstructured_len + OPCODE_SIZE) { + msg_maxim("This isn't UVDM message!"); + return; + } + + memcpy(read_msg, opcode_data, OPCODE_DATA_LENGTH); + usbpd_data->uvdm_error = 0; + + switch (usbpd_data->is_in_sec_uvdm_out) { + case DIR_OUT: + max77775_process_out_uvdm_response(usbpd_data, read_msg); + break; + case DIR_IN: + max77775_process_in_uvdm_response(usbpd_data, read_msg); + break; + default: + msg_maxim("Never Call!!!"); + break; + } +} + +int max77775_sec_uvdm_out_request_message(void *data, int size) +{ + struct max77775_usbc_platform_data *usbpd_data; + struct i2c_client *i2c; + int ret; + + usbpd_data = g_usbc_data; + if (!usbpd_data) + return -ENXIO; + + i2c = usbpd_data->muic; + if (i2c == NULL) { + msg_maxim("usbpd_data->i2c is not valid!"); + return -EINVAL; + } + + if (data == NULL) { + msg_maxim("given data is not valid !"); + return -EINVAL; + } + + if (size >= SAMSUNGUVDM_MAX_LONGPACKET_SIZE) { + msg_maxim("size %d is too big to send data", size); + ret = -EFBIG; + } else if (size <= SAMSUNGUVDM_MAX_SHORTPACKET_SIZE) + ret = max77775_send_sec_unstructured_short_vdm_message(usbpd_data, data, size); + else + ret = max77775_send_sec_unstructured_long_vdm_message(usbpd_data, data, size); + + return ret; +} + +int max77775_sec_uvdm_in_request_message(void *data) +{ + struct max77775_usbc_platform_data *usbpd_data; + uint8_t SendMSG[32] = {0,}; + uint8_t ReadMSG[32] = {0,}; + uint8_t IN_DATA[MAX_INPUT_DATA] = {0,}; + + /* Send Request */ + /* 1 Message Type Definition */ + U_SEC_UVDM_RESPONSE_HEADER *SEC_RES_HEADER; + U_SEC_TX_DATA_HEADER *SEC_UVDM_TX_HEADER; + U_SEC_TX_DATA_TAILER *SEC_UVDM_TX_TAILER; + + int cur_uvdmset_data = 0; + int cur_uvdmset_num = 0; + int total_uvdmset_num = 0; + int received_data_size = 0; + int total_received_data_size = 0; + int ack = 0; + int size = 0; + int time_left = 0; + int cal_checksum = 0; + int i = 0; +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + int event; +#endif + + usbpd_data = g_usbc_data; + if (!usbpd_data) + return -ENXIO; + + if (data == NULL) { + msg_maxim("%s given data is not valid !", __func__); + return -EINVAL; + } + + usbpd_data->is_in_sec_uvdm_out = DIR_IN; + usbpd_data->is_in_first_sec_uvdm_req = true; + + /* 1. Common : Fill the MSGHeader */ + set_msgheader(SendMSG, ACK, 2); + /* 2. Common : Fill the UVDMHeader*/ + set_uvdmheader(SendMSG, SAMSUNG_VENDOR_ID, 0); + /* 3. Common : Fill the First SEC_VDMHeader*/ + if (usbpd_data->is_in_first_sec_uvdm_req) + set_sec_uvdmheader(SendMSG, usbpd_data->Product_ID, TYPE_LONG, SEC_UVDM_ININIATOR, DIR_IN, 0, 0); + /* 8. Send data to PDIC */ + usbpd_data->uvdm_error = 0; + max77775_request_uvdm(usbpd_data, SendMSG, 0); + + cur_uvdmset_num = 0; + total_uvdmset_num = 1; + + do { + reinit_completion(&usbpd_data->uvdm_longpacket_in_wait); + time_left = + wait_for_completion_interruptible_timeout(&usbpd_data->uvdm_longpacket_in_wait, + msecs_to_jiffies(SAMSUNGUVDM_WAIT_MS)); + + if (time_left <= 0) { + msg_maxim("timeout"); +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_UVDM_TIMEOUT; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + return -ETIME; + } + if (usbpd_data->uvdm_error) + return usbpd_data->uvdm_error; + /* read data */ + memset(ReadMSG, 0, 32); + memcpy(ReadMSG, usbpd_data->ReadMSG, OPCODE_DATA_LENGTH); + + if (usbpd_data->is_in_first_sec_uvdm_req) { + SEC_RES_HEADER = (U_SEC_UVDM_RESPONSE_HEADER *)&ReadMSG[6]; + SEC_UVDM_TX_HEADER = (U_SEC_TX_DATA_HEADER *)&ReadMSG[10]; +#if (UVDM_DEBUG) + msg_maxim("SEC_RES_HEADER : 0x%x, SEC_UVDM_TX_HEADER : 0x%x", SEC_RES_HEADER->data, SEC_UVDM_TX_HEADER->data); +#endif + if (SEC_RES_HEADER->BITS.data_type == TYPE_LONG) { + /* 1. check the data size received */ + size = SEC_UVDM_TX_HEADER->BITS.total_data_size; + cur_uvdmset_data = SEC_UVDM_TX_HEADER->BITS.data_size_of_current_set; + cur_uvdmset_num = SEC_UVDM_TX_HEADER->BITS.order_of_current_uvdm_set; + total_uvdmset_num = + SEC_RES_HEADER->BITS.total_number_of_uvdm_set; + + usbpd_data->is_in_first_sec_uvdm_req = false; + /* 2. copy data to buffer */ + for (i = 0; i < SAMSUNGUVDM_MAXDATA_FIRST_UVDMSET; i++) + IN_DATA[received_data_size++] = ReadMSG[14 + i]; + + total_received_data_size += cur_uvdmset_data; + usbpd_data->is_in_first_sec_uvdm_req = false; + } else { + IN_DATA[received_data_size++] = SEC_RES_HEADER->BITS.data; + return received_data_size; + } + } else { + SEC_UVDM_TX_HEADER = (U_SEC_TX_DATA_HEADER *)&ReadMSG[6]; + cur_uvdmset_data = SEC_UVDM_TX_HEADER->BITS.data_size_of_current_set; + cur_uvdmset_num = SEC_UVDM_TX_HEADER->BITS.order_of_current_uvdm_set; + /* 2. copy data to buffer */ + for (i = 0 ; i < SAMSUNGUVDM_MAXDATA_NORMAL_UVDMSET ; i++) + IN_DATA[received_data_size++] = ReadMSG[10 + i]; + total_received_data_size += cur_uvdmset_data; + } + /* 3. Check Checksum */ + SEC_UVDM_TX_TAILER = (U_SEC_TX_DATA_TAILER *)&ReadMSG[26]; + cal_checksum = get_checksum(ReadMSG, 6, SAMSUNGUVDM_CHECKSUM_DATA_COUNT); + ack = (cal_checksum == SEC_UVDM_TX_TAILER->BITS.checksum) ? + SEC_UVDM_RX_HEADER_ACK : SEC_UVDM_RX_HEADER_NAK; +#if (UVDM_DEBUG) + msg_maxim("SEC_UVDM_TX_TAILER : 0x%x, cal_checksum : 0x%x, size : %d", SEC_UVDM_TX_TAILER->data, cal_checksum, size); +#endif + /* 1. Common : Fill the MSGHeader */ + if (cur_uvdmset_num == total_uvdmset_num) + set_msgheader(SendMSG, NAK, 2); + else + set_msgheader(SendMSG, ACK, 2); + /* 2. Common : Fill the UVDMHeader*/ + set_uvdmheader(SendMSG, SAMSUNG_VENDOR_ID, 0); + /* 5-3. Common : Fill the SEC RXHeader */ + set_sec_uvdm_rxdata_header(SendMSG, cur_uvdmset_num, cur_uvdmset_data, ack); + + /* 8. Send data to PDIC */ + usbpd_data->uvdm_error = 0; + max77775_request_uvdm(usbpd_data, SendMSG, 0); + } while (cur_uvdmset_num < total_uvdmset_num); + set_endian(IN_DATA, data, size); + + reinit_completion(&usbpd_data->uvdm_longpacket_in_wait); + time_left = + wait_for_completion_interruptible_timeout(&usbpd_data->uvdm_longpacket_in_wait, + msecs_to_jiffies(SAMSUNGUVDM_WAIT_MS)); + if (time_left <= 0) { + msg_maxim("last in request timeout"); +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_UVDM_TIMEOUT; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + return -ETIME; + } + if (usbpd_data->uvdm_error) + return usbpd_data->uvdm_error; + + return size; +} + +int max77775_sec_uvdm_ready(void) +{ + int uvdm_ready = false; + struct max77775_usbc_platform_data *usbpd_data; + + usbpd_data = g_usbc_data; + + msg_maxim("power_nego is%s done, accessory_enter_mode is%s done", + usbpd_data->pn_flag ? "" : " not", + usbpd_data->is_samsung_accessory_enter_mode ? "" : " not"); + + if (usbpd_data->pn_flag && + usbpd_data->is_samsung_accessory_enter_mode) + uvdm_ready = true; + + msg_maxim("uvdm ready = %d", uvdm_ready); + return uvdm_ready; +} + +void max77775_sec_uvdm_close(void) +{ + struct max77775_usbc_platform_data *usbpd_data; + + usbpd_data = g_usbc_data; + complete(&usbpd_data->uvdm_longpacket_out_wait); + complete(&usbpd_data->uvdm_longpacket_in_wait); + msg_maxim("success"); +} diff --git a/drivers/usb/typec/maxim/max77775_cc.c b/drivers/usb/typec/maxim/max77775_cc.c new file mode 100755 index 000000000000..fb9c7f3424b0 --- /dev/null +++ b/drivers/usb/typec/maxim/max77775_cc.c @@ -0,0 +1,895 @@ +/* + * Copyrights (C) 2017 Samsung Electronics, Inc. + * Copyrights (C) 2017 Maxim Integrated Products, 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) +#include +#endif +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) +#include +#include +#endif +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_COMBO_REDRIVER_PTN36502) +#include +#endif + +extern struct max77775_usbc_platform_data *g_usbc_data; + +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) +static void max77775_ccic_event_notifier(struct work_struct *data) +{ + struct pdic_state_work *event_work = + container_of(data, struct pdic_state_work, pdic_work); + PD_NOTI_TYPEDEF ccic_noti; + + switch (event_work->dest) { + case PDIC_NOTIFY_DEV_USB: + msg_maxim("usb: dest=%s, id=%s, attach=%s, drp=%s", + pdic_event_dest_string(event_work->dest), + pdic_event_id_string(event_work->id), + event_work->attach ? "Attached" : "Detached", + pdic_usbstatus_string(event_work->event)); + break; + default: + msg_maxim("usb: dest=%s, id=%s, attach=%d, event=%d", + pdic_event_dest_string(event_work->dest), + pdic_event_id_string(event_work->id), + event_work->attach, + event_work->event); + break; + } + + ccic_noti.src = PDIC_NOTIFY_DEV_CCIC; + ccic_noti.dest = event_work->dest; + ccic_noti.id = event_work->id; + ccic_noti.sub1 = event_work->attach; + ccic_noti.sub2 = event_work->event; + ccic_noti.sub3 = event_work->sub; +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + ccic_noti.pd = &g_usbc_data->pd_data->pd_noti; +#endif + pdic_notifier_notify((PD_NOTI_TYPEDEF *) &ccic_noti, NULL, 0); + + kfree(event_work); +} + +void max77775_ccic_event_work(void *data, int dest, int id, int attach, int event, int sub) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + struct pdic_state_work *event_work; + struct typec_partner_desc desc; + enum typec_pwr_opmode mode = TYPEC_PWR_MODE_USB; + + if (usbpd_data->usb_mock.ccic_event_work) + return usbpd_data->usb_mock.ccic_event_work(data, dest, id, attach, event, sub); + + msg_maxim("usb: DIAES %d-%d-%d-%d-%d", dest, id, attach, event, sub); + event_work = kmalloc(sizeof(struct pdic_state_work), GFP_KERNEL); + if (!event_work) { + msg_maxim("failed to allocate event_work"); + return; + } + INIT_WORK(&event_work->pdic_work, max77775_ccic_event_notifier); + + event_work->dest = dest; + event_work->id = id; + event_work->attach = attach; + event_work->event = event; + event_work->sub = sub; + + if (id == PDIC_NOTIFY_ID_USB) { + if (usbpd_data->partner == NULL) { + msg_maxim("typec_register_partner, typec_power_role=%d typec_data_role=%d event=%d", + usbpd_data->typec_power_role, usbpd_data->typec_data_role, event); + if (event == USB_STATUS_NOTIFY_ATTACH_UFP) { + mode = max77775_get_pd_support(usbpd_data); + typec_set_pwr_opmode(usbpd_data->port, mode); + desc.usb_pd = mode == TYPEC_PWR_MODE_PD; + desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ + desc.identity = NULL; + usbpd_data->typec_data_role = TYPEC_DEVICE; + typec_set_pwr_role(usbpd_data->port, usbpd_data->typec_power_role); + typec_set_data_role(usbpd_data->port, usbpd_data->typec_data_role); + usbpd_data->partner = typec_register_partner(usbpd_data->port, &desc); + } else if (event == USB_STATUS_NOTIFY_ATTACH_DFP) { + mode = max77775_get_pd_support(usbpd_data); + typec_set_pwr_opmode(usbpd_data->port, mode); + desc.usb_pd = mode == TYPEC_PWR_MODE_PD; + desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ + desc.identity = NULL; + usbpd_data->typec_data_role = TYPEC_HOST; + typec_set_pwr_role(usbpd_data->port, usbpd_data->typec_power_role); + typec_set_data_role(usbpd_data->port, usbpd_data->typec_data_role); + usbpd_data->partner = typec_register_partner(usbpd_data->port, &desc); + } else + msg_maxim("detach case"); + } else { + msg_maxim("data_role changed, typec_power_role=%d typec_data_role=%d, event=%d", + usbpd_data->typec_power_role, usbpd_data->typec_data_role, event); + if (event == USB_STATUS_NOTIFY_ATTACH_UFP) { + usbpd_data->typec_data_role = TYPEC_DEVICE; + typec_set_data_role(usbpd_data->port, usbpd_data->typec_data_role); + } else if (event == USB_STATUS_NOTIFY_ATTACH_DFP) { + usbpd_data->typec_data_role = TYPEC_HOST; + typec_set_data_role(usbpd_data->port, usbpd_data->typec_data_role); + } else + msg_maxim("detach case"); + } + } + + queue_work(usbpd_data->ccic_wq, &event_work->pdic_work); +} +#endif + +void max77775_set_unmask_vbus(struct max77775_usbc_platform_data *usbc_data, u8 ccstat) +{ + u8 bc_status = 0, vbus = 0; + u64 time_gap = 0; + usbc_cmd_data write_data; + int latest_idx = 0, cmp_idx = 0; +#if defined(CONFIG_USB_HW_PARAM) && defined(CONFIG_MAX77775_GET_UNMASK_VBUS_HWPARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif + + if (usbc_data->rid_check == true) + return; + + if (ccstat == cc_SINK) { + max77775_read_reg(usbc_data->muic, MAX77775_USBC_REG_BC_STATUS, &bc_status); + vbus = (bc_status & BIT_VBUSDet) >> FFS(BIT_VBUSDet); + + if (!vbus) { + usbc_data->time_lapse[usbc_data->lapse_idx] = get_jiffies_64(); + + pr_info("%s: lapse_idx: %d, time_lapse: %llu\n", + __func__, usbc_data->lapse_idx, usbc_data->time_lapse[usbc_data->lapse_idx]); + + usbc_data->lapse_idx = (usbc_data->lapse_idx + 1) % MAX_NVCN_CNT; + } + } else if (ccstat == cc_No_Connection) { + if (usbc_data->time_lapse[0] == 0) + return; + + latest_idx = (usbc_data->lapse_idx + MAX_NVCN_CNT - 1) % MAX_NVCN_CNT; + cmp_idx = usbc_data->lapse_idx; + + time_gap = usbc_data->time_lapse[latest_idx] - usbc_data->time_lapse[cmp_idx]; + + if (time_gap <= (HZ * MAX_CHK_TIME)) { + pr_info("%s: time_gap: %llu, Opcode write 0x1f 0x30\n" + , __func__, time_gap); + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_CCCTRL5_W; + write_data.write_length = 1; + write_data.write_data[0] = 0x01; + write_data.read_length = 0; + + max77775_usbc_opcode_write(usbc_data, &write_data); +#if defined(CONFIG_USB_HW_PARAM) && defined(CONFIG_MAX77775_GET_UNMASK_VBUS_HWPARAM) + inc_hw_param(o_notify, USB_CCIC_UNMASK_VBUS_COUNT); +#endif + } + } +} + +void max77775_dp_detach(void *data) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + + pr_info("%s: dp_is_connect %d\n", __func__, usbpd_data->dp_is_connect); + + max77775_ccic_event_work(usbpd_data, PDIC_NOTIFY_DEV_USB_DP, + PDIC_NOTIFY_ID_USB_DP, 0/*attach*/, usbpd_data->dp_hs_connect/*drp*/, 0); + max77775_ccic_event_work(usbpd_data, PDIC_NOTIFY_DEV_DP, + PDIC_NOTIFY_ID_DP_CONNECT, 0/*attach*/, 0/*drp*/, 0); + + usbpd_data->dp_is_connect = 0; + usbpd_data->dp_hs_connect = 0; + usbpd_data->is_sent_pin_configuration = 0; +} + +void max77775_notify_dr_status(struct max77775_usbc_platform_data *usbpd_data, uint8_t attach) +{ + struct max77775_pd_data *pd_data = usbpd_data->pd_data; + + msg_maxim("Data Role: %s Power Role: %s State: %s", + pd_data->current_dr ? "DFP":"UFP", + usbpd_data->cc_data->current_pr ? "SRC":"SNK", + attach ? "ATTACHED":"DETACHED"); + + if (attach == PDIC_NOTIFY_ATTACH) { + if (usbpd_data->current_connstat == WATER) { + pr_info("%s: blocked by WATER\n", __func__); + return; + } + if (usbpd_data->shut_down) { + pr_info("%s: blocked by shutdown\n", __func__); + return; + } + if (pd_data->current_dr == UFP) { + if (usbpd_data->is_host == HOST_ON) { + msg_maxim("pd_state:%02d, turn off host", + usbpd_data->pd_state); + if (usbpd_data->dp_is_connect == 1) + max77775_dp_detach(usbpd_data); + max77775_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_USB, PDIC_NOTIFY_ID_USB, + 0/*attach*/, USB_STATUS_NOTIFY_DETACH/*drp*/, 0); + usbpd_data->is_host = HOST_OFF; + usbpd_data->send_enter_mode_req = 0; + } + if (usbpd_data->is_client == CLIENT_OFF) { + usbpd_data->is_client = CLIENT_ON; + /* muic */ + max77775_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_MUIC, PDIC_NOTIFY_ID_ATTACH, + 1/*attach*/, 0/*rprd*/, 0); + /* USB */ + max77775_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_USB, PDIC_NOTIFY_ID_USB, + 1/*attach*/, USB_STATUS_NOTIFY_ATTACH_UFP/*drp*/, 0); + } + + } else if (pd_data->current_dr == DFP) { + if (usbpd_data->is_client == CLIENT_ON) { + msg_maxim("pd_state:%02d, turn off client", + usbpd_data->pd_state); + max77775_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_USB, PDIC_NOTIFY_ID_USB, + 0/*attach*/, USB_STATUS_NOTIFY_DETACH/*drp*/, 0); + usbpd_data->is_client = CLIENT_OFF; +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) && IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + usb_set_vbus_current(usbpd_data->man, USB_CURRENT_CLEAR); +#endif + } + if (usbpd_data->is_host == HOST_OFF) { + usbpd_data->is_host = HOST_ON; + /* muic */ + max77775_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_MUIC, + PDIC_NOTIFY_ID_ATTACH, 1/*attach*/, 1/*rprd*/, 0); + /* USB */ + max77775_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_USB, PDIC_NOTIFY_ID_USB, + 1/*attach*/, USB_STATUS_NOTIFY_ATTACH_DFP/*drp*/, 0); + } + + } else { + msg_maxim("Unknown dr type (%d) no action", + pd_data->current_dr); + } + } else { /* PDIC_NOTIFY_DETACH */ + if (usbpd_data->dp_is_connect == 1) + max77775_dp_detach(usbpd_data); + if (usbpd_data->acc_type != PDIC_DOCK_DETACHED) { + pr_info("%s: schedule_delayed_work - pd_state : %d\n", + __func__, usbpd_data->pd_state); + if (usbpd_data->acc_type == PDIC_DOCK_HMT) + schedule_delayed_work(&usbpd_data->acc_detach_work, + msecs_to_jiffies(GEAR_VR_DETACH_WAIT_MS)); + else + schedule_delayed_work(&usbpd_data->acc_detach_work, + msecs_to_jiffies(0)); + } + usbpd_data->mdm_block = 0; + usbpd_data->is_host = HOST_OFF; + usbpd_data->is_client = CLIENT_OFF; + /* muic */ + max77775_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_MUIC, PDIC_NOTIFY_ID_ATTACH, + 0/*attach*/, 0/*rprd*/, 0); + /* USB */ + max77775_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_USB, PDIC_NOTIFY_ID_USB, + 0/*attach*/, USB_STATUS_NOTIFY_DETACH/*drp*/, 0); + } + +} + +static irqreturn_t max77775_vconnocp_irq(int irq, void *data) +{ + struct max77775_usbc_platform_data *usbc_data = data; + struct max77775_cc_data *cc_data = usbc_data->cc_data; + + max77775_read_reg(usbc_data->muic, REG_CC_STATUS2, &cc_data->cc_status2); + cc_data->vconnocp = (cc_data->cc_status2 & BIT_VCONNOCPI) + >> FFS(BIT_VCONNOCPI); + msg_maxim("New VCONNOCP Status Interrupt (%d)", + cc_data->vconnocp); + + return IRQ_HANDLED; +} + +static irqreturn_t max77775_vsafe0v_irq(int irq, void *data) +{ + struct max77775_usbc_platform_data *usbc_data = data; + struct max77775_cc_data *cc_data = usbc_data->cc_data; + u8 ccpinstat = 0; + u8 connstat = 0; + + pr_debug("%s: IRQ(%d)_IN\n", __func__, irq); + max77775_read_reg(usbc_data->muic, REG_BC_STATUS, &cc_data->bc_status); + max77775_read_reg(usbc_data->muic, REG_CC_STATUS1, &cc_data->cc_status1); + max77775_read_reg(usbc_data->muic, REG_CC_STATUS2, &cc_data->cc_status2); + ccpinstat = (cc_data->cc_status1 & BIT_CCPinStat) + >> FFS(BIT_CCPinStat); + cc_data->vsafe0v = (cc_data->cc_status2 & BIT_VSAFE0V) + >> FFS(BIT_VSAFE0V); + connstat = (cc_data->cc_status2 & BIT_ConnStat) + >> FFS(BIT_ConnStat); + + msg_maxim("New VSAFE0V Status Interrupt (%d)", + cc_data->vsafe0v); + pr_debug("%s: IRQ(%d)_OUT\n", __func__, irq); + + return IRQ_HANDLED; +} + + +static irqreturn_t max77775_vconnsc_irq(int irq, void *data) +{ + struct max77775_usbc_platform_data *usbc_data = data; + struct max77775_cc_data *cc_data = usbc_data->cc_data; + u8 connstat = 0; + + pr_debug("%s: IRQ(%d)_IN\n", __func__, irq); + max77775_read_reg(usbc_data->muic, REG_CC_STATUS2, &cc_data->cc_status2); + connstat = (cc_data->cc_status2 & BIT_ConnStat) + >> FFS(BIT_ConnStat); + + switch (connstat) { + case DRY: + msg_maxim("== WATER RUN-DRY DETECT =="); + if (usbc_data->current_connstat != DRY) { + usbc_data->prev_connstat = usbc_data->current_connstat; + usbc_data->current_connstat = DRY; + if (!usbc_data->max77775->blocking_waterevent) + max77775_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_WATER, + 0/*attach*/, + 0, + 0); + } + break; + + case WATER: + msg_maxim("== WATER DETECT =="); + + if (usbc_data->current_connstat != WATER) { + usbc_data->prev_connstat = usbc_data->current_connstat; + usbc_data->current_connstat = WATER; + if (!usbc_data->max77775->blocking_waterevent) + max77775_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_WATER, + 1/*attach*/, + 0, + 0); + } + break; + default: + break; + + } + + pr_debug("%s: IRQ(%d)_OUT\n", __func__, irq); + + return IRQ_HANDLED; +} + +static irqreturn_t max77775_ccpinstat_irq(int irq, void *data) +{ + struct max77775_usbc_platform_data *usbc_data = data; + struct max77775_cc_data *cc_data = usbc_data->cc_data; + u8 ccpinstat = 0; + + max77775_read_reg(usbc_data->muic, REG_CC_STATUS1, &cc_data->cc_status1); + + pr_debug("%s: IRQ(%d)_IN\n", __func__, irq); + ccpinstat = (cc_data->cc_status1 & BIT_CCPinStat) + >> FFS(BIT_CCPinStat); + + switch (ccpinstat) { + case NO_DETERMINATION: + msg_maxim("CCPINSTAT (NO_DETERMINATION)"); +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + if (usbc_data->ccrp_state) { + usbc_data->ccrp_state = 0; + max77775_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_BATT, PDIC_NOTIFY_ID_WATER_CABLE, + PDIC_NOTIFY_DETACH, 0/*rprd*/, 0); + } + max77775_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_ALL, PDIC_NOTIFY_ID_CLEAR_INFO, + PDIC_NOTIFY_ID_SVID_INFO, 0, 0); +#endif + break; + case CC1_ACTIVE: + msg_maxim("CCPINSTAT (CC1_ACTIVE)"); + break; + + case CC2_ACTVIE: + msg_maxim("CCPINSTAT (CC2_ACTIVE)"); + break; + + case AUDIO_ACCESSORY: + msg_maxim("CCPINSTAT (AUDIO_ACCESSORY)"); + break; + + default: + msg_maxim("CCPINSTAT [%d]", ccpinstat); + break; + + } + cc_data->ccpinstat = ccpinstat; + usbc_data->cc_pin_status = ccpinstat; +#if IS_ENABLED(CONFIG_SEC_FACTORY) + max77775_ccic_event_work(usbc_data, PDIC_NOTIFY_DEV_CCIC, + PDIC_NOTIFY_ID_CC_PIN_STATUS, ccpinstat, 0, 0); +#endif + pr_debug("%s: IRQ(%d)_OUT\n", __func__, irq); + + return IRQ_HANDLED; +} + +static irqreturn_t max77775_ccistat_irq(int irq, void *data) +{ + struct max77775_usbc_platform_data *usbc_data = data; + struct max77775_cc_data *cc_data = usbc_data->cc_data; + struct max77775_pd_data *pd_data = usbc_data->pd_data; + u8 ccistat = 0; + enum typec_pwr_opmode mode = TYPEC_PWR_MODE_USB; + usbc_cmd_data value; + u8 num; + + max77775_read_reg(usbc_data->muic, REG_CC_STATUS1, &cc_data->cc_status1); + pr_debug("%s: IRQ(%d)_IN\n", __func__, irq); + ccistat = (cc_data->cc_status1 & BIT_CCIStat) >> FFS(BIT_CCIStat); + switch (ccistat) { + case NOT_IN_UFP_MODE: + msg_maxim("Not in UFP"); + break; + + case CCI_500mA: + msg_maxim("Vbus Current is 500mA!"); + break; + + case CCI_1_5A: + msg_maxim("Vbus Current is 1.5A!"); + mode = TYPEC_PWR_MODE_1_5A; + break; + + case CCI_3_0A: + msg_maxim("Vbus Current is 3.0A!"); + mode = TYPEC_PWR_MODE_3_0A; + + if (usbc_data->srcccap_request_retry) { + usbc_data->pn_flag = false; + usbc_data->srcccap_request_retry = false; + num = pd_data->pd_noti.sink_status.selected_pdo_num; + + if (usbc_data->cc_data->current_pr == SNK && (pd_data->current_dr == DFP)) { + if (pd_data->pd_noti.sink_status.power_list[num].apdo) { + //skip + } else { + cancel_delayed_work(&usbc_data->pd_data->retry_work); + queue_delayed_work(usbc_data->pd_data->wqueue, &usbc_data->pd_data->retry_work, 1000); //1 sec + } + } else { + init_usbc_cmd_data(&value); + value.opcode = OPCODE_SRCCAP_REQUEST; + value.write_data[0] = pd_data->pd_noti.sink_status.selected_pdo_num; + value.write_length = 1; + value.read_length = 1; + max77775_usbc_opcode_write(usbc_data, &value); + pr_info("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d) NUM(%d)\n", + __func__, value.opcode, value.write_length, value.read_length, + pd_data->pd_noti.sink_status.selected_pdo_num); + } + } + break; + + default: + msg_maxim("CCINSTAT(Never Call this routine) !"); + break; + + } + cc_data->ccistat = ccistat; + pr_debug("%s: IRQ(%d)_OUT\n", __func__, irq); + + max77775_notify_rp_current_level(usbc_data); + + if (!usbc_data->pd_support) { + usbc_data->pwr_opmode = mode; + typec_set_pwr_opmode(usbc_data->port, mode); + } + + return IRQ_HANDLED; +} + + +static irqreturn_t max77775_ccvnstat_irq(int irq, void *data) +{ + struct max77775_usbc_platform_data *usbc_data = data; + struct max77775_cc_data *cc_data = usbc_data->cc_data; + u8 ccvcnstat = 0; + + max77775_read_reg(usbc_data->muic, REG_CC_STATUS1, &cc_data->cc_status1); + + pr_debug("%s: IRQ(%d)_IN\n", __func__, irq); + ccvcnstat = (cc_data->cc_status1 & BIT_CCVcnStat) >> FFS(BIT_CCVcnStat); + + switch (ccvcnstat) { + case 0: + msg_maxim("Vconn Disabled"); + if (cc_data->current_vcon != OFF) { + cc_data->previous_vcon = cc_data->current_vcon; + cc_data->current_vcon = OFF; + } + break; + + case 1: + msg_maxim("Vconn Enabled"); + if (cc_data->current_vcon != ON) { + cc_data->previous_vcon = cc_data->current_vcon; + cc_data->current_vcon = ON; + } + break; + + default: + msg_maxim("ccvnstat(Never Call this routine) !"); + break; + + } + cc_data->ccvcnstat = ccvcnstat; + pr_debug("%s: IRQ(%d)_OUT\n", __func__, irq); + + + return IRQ_HANDLED; +} + +static void max77775_ccstat_irq_handler(void *data, int irq) +{ + struct power_supply *psy_charger; + union power_supply_propval val; + struct max77775_usbc_platform_data *usbc_data = data; + struct max77775_cc_data *cc_data = usbc_data->cc_data; + u8 ccstat = 0; + + int prev_power_role = usbc_data->typec_power_role; + +#if IS_ENABLED(CONFIG_USB_HOST_NOTIFY) + struct otg_notify *o_notify = get_otg_notify(); +#endif +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + int event; +#endif + + max77775_read_reg(usbc_data->muic, REG_CC_STATUS1, &cc_data->cc_status1); + ccstat = (cc_data->cc_status1 & BIT_CCStat) >> FFS(BIT_CCStat); + if (irq == CCIC_IRQ_INIT_DETECT) { + if (ccstat == cc_SINK) + msg_maxim("initial time : SNK"); + else + return; + } + + max77775_set_unmask_vbus(usbc_data, ccstat); + +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + if (ccstat == cc_No_Connection) + usbc_data->pd_state = max77775_State_PE_Initial_detach; + else if (ccstat == cc_SOURCE) + usbc_data->pd_state = max77775_State_PE_SRC_Send_Capabilities; + else if (ccstat == cc_SINK) + usbc_data->pd_state = max77775_State_PE_SNK_Wait_for_Capabilities; +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + store_usblog_notify(NOTIFY_FUNCSTATE, (void *)&usbc_data->pd_state, NULL); +#endif +#endif + if (!ccstat) { + if (usbc_data->plug_attach_done) { + msg_maxim("PLUG_DETACHED ---"); + if (usbc_data->partner) { + msg_maxim("ccstat : typec_unregister_partner"); + if (!IS_ERR(usbc_data->partner)) + typec_unregister_partner(usbc_data->partner); + usbc_data->partner = NULL; + usbc_data->typec_power_role = TYPEC_SINK; + usbc_data->typec_data_role = TYPEC_DEVICE; + usbc_data->pwr_opmode = TYPEC_PWR_MODE_USB; + } + if (usbc_data->typec_try_state_change == TRY_ROLE_SWAP_PR || + usbc_data->typec_try_state_change == TRY_ROLE_SWAP_DR) { + /* Role change try and new mode detected */ + msg_maxim("typec_reverse_completion, detached while pd_swap"); + usbc_data->typec_try_state_change = TRY_ROLE_SWAP_NONE; + complete(&usbc_data->typec_reverse_completion); + } + max77775_notify_dr_status(usbc_data, 0); + usbc_data->plug_attach_done = 0; + usbc_data->cc_data->current_pr = 0xFF; + usbc_data->pd_data->current_dr = 0xFF; + usbc_data->cc_data->current_vcon = 0xFF; + usbc_data->detach_done_wait = 1; +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + send_otg_notify(o_notify, NOTIFY_EVENT_PD_CONTRACT, 0); + send_otg_notify(o_notify, NOTIFY_EVENT_REVERSE_BYPASS_DEVICE_ATTACH, 0); +#endif +#if IS_ENABLED(CONFIG_COMBO_REDRIVER_PTN36502) + ptn36502_config(SAFE_STATE, 0); +#endif + } + } else { + if (!usbc_data->plug_attach_done) { + msg_maxim("PLUG_ATTACHED +++"); + usbc_data->plug_attach_done = 1; + } + } + + switch (ccstat) { + case cc_No_Connection: + msg_maxim("ccstat : cc_No_Connection"); + usbc_data->pd_data->cc_status = CC_NO_CONN; + wake_up_interruptible(&usbc_data->device_add_wait_q); + usbc_data->is_samsung_accessory_enter_mode = 0; + usbc_data->pn_flag = false; + usbc_data->pd_support = false; + usbc_data->srcccap_request_retry = false; + + if (!usbc_data->typec_try_state_change) + max77775_usbc_clear_queue(usbc_data); + + usbc_data->typec_power_role = TYPEC_SINK; + +#if IS_ENABLED(CONFIG_USB_HOST_NOTIFY) + send_otg_notify(o_notify, NOTIFY_EVENT_POWER_SOURCE, 0); + send_otg_notify(o_notify, NOTIFY_EVENT_DR_SWAP, 0); +#endif + max77775_detach_pd(usbc_data); + usbc_data->pd_pr_swap = cc_No_Connection; + max77775_vbus_turn_on_ctrl(usbc_data, OFF, false); +#if IS_ENABLED(CONFIG_SEC_FACTORY) + factory_execute_monitor(FAC_ABNORMAL_REPEAT_STATE); +#endif + cancel_delayed_work(&usbc_data->vbus_hard_reset_work); + break; + case cc_SINK: + msg_maxim("ccstat : cc_SINK"); + /* keep awake during pd communication */ + pm_wakeup_ws_event(&cc_data->ccstat_ws, 1000, false); + usbc_data->pd_data->cc_status = CC_SNK; + usbc_data->pn_flag = false; + + usbc_data->typec_power_role = TYPEC_SINK; + typec_set_pwr_role(usbc_data->port, TYPEC_SINK); +#if IS_ENABLED(CONFIG_USB_HOST_NOTIFY) + send_otg_notify(o_notify, NOTIFY_EVENT_POWER_SOURCE, 0); +#endif + if (cc_data->current_pr != SNK) { + cc_data->previous_pr = cc_data->current_pr; + cc_data->current_pr = SNK; + if (prev_power_role == TYPEC_SOURCE) + max77775_vbus_turn_on_ctrl(usbc_data, OFF, true); + } + psy_charger = power_supply_get_by_name("max77775-charger"); + if (psy_charger) { + val.intval = 1; + psy_do_property("max77775-charger", set, POWER_SUPPLY_EXT_PROP_CHGINSEL, val); + } else { + pr_err("%s: Fail to get psy charger\n", __func__); + } + max77775_notify_rp_current_level(usbc_data); +#if IS_ENABLED(CONFIG_SEC_FACTORY) + factory_execute_monitor(FAC_ABNORMAL_REPEAT_STATE); +#endif + break; + case cc_SOURCE: + msg_maxim("ccstat : cc_SOURCE"); + usbc_data->pd_data->cc_status = CC_SRC; + usbc_data->pn_flag = false; + usbc_data->srcccap_request_retry = false; + + usbc_data->typec_power_role = TYPEC_SOURCE; + typec_set_pwr_role(usbc_data->port, TYPEC_SOURCE); +#if IS_ENABLED(CONFIG_USB_HOST_NOTIFY) + send_otg_notify(o_notify, NOTIFY_EVENT_POWER_SOURCE, 1); +#endif +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + send_otg_notify(o_notify, NOTIFY_EVENT_REVERSE_BYPASS_DEVICE_ATTACH, 1); +#endif + if (cc_data->current_pr != SRC) { + cc_data->previous_pr = cc_data->current_pr; + cc_data->current_pr = SRC; + + if (prev_power_role == TYPEC_SINK) + max77775_vbus_turn_on_ctrl(usbc_data, ON, false); +#if IS_ENABLED(CONFIG_DUAL_ROLE_USB_INTF) + else if (prev_power_role == DUAL_ROLE_PROP_PR_SNK) + max77775_vbus_turn_on_ctrl(usbc_data, ON, true); +#endif + } + break; + case cc_Audio_Accessory: + msg_maxim("ccstat : cc_Audio_Accessory"); + usbc_data->acc_type = PDIC_DOCK_UNSUPPORTED_AUDIO; +#ifdef CONFIG_MAX77775_CCIC_ALTERNATE_MODE + max77775_process_check_accessory(usbc_data); +#endif +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_USB_ANALOGAUDIO; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + break; + case cc_Debug_Accessory: + msg_maxim("ccstat : cc_Debug_Accessory"); + break; + case cc_Error: + msg_maxim("ccstat : cc_Error"); + break; + case cc_Disabled: + msg_maxim("ccstat : cc_Disabled"); + break; + case cc_Debug_Sink: + msg_maxim("ccstat : cc_Debug_Sink"); + break; + default: + break; + } +} + +static irqreturn_t max77775_ccstat_irq(int irq, void *data) +{ + pr_debug("%s: IRQ(%d)_IN\n", __func__, irq); + max77775_ccstat_irq_handler(data, irq); + pr_debug("%s: IRQ(%d)_OUT\n", __func__, irq); + return IRQ_HANDLED; +} + +int max77775_cc_init(struct max77775_usbc_platform_data *usbc_data) +{ + struct max77775_cc_data *cc_data = NULL; + int ret; + + msg_maxim("IN"); + + cc_data = usbc_data->cc_data; + cc_data->ccstat_ws.name = "max77775-ccstat"; + wakeup_source_add(&cc_data->ccstat_ws); + + cc_data->irq_vconncop = usbc_data->irq_base + MAX77775_CC_IRQ_VCONNCOP_INT; + if (cc_data->irq_vconncop) { + ret = request_threaded_irq(cc_data->irq_vconncop, + NULL, max77775_vconnocp_irq, + 0, + "cc-vconncop-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + goto err_irq; + } + } + + cc_data->irq_vsafe0v = usbc_data->irq_base + MAX77775_CC_IRQ_VSAFE0V_INT; + if (cc_data->irq_vsafe0v) { + ret = request_threaded_irq(cc_data->irq_vsafe0v, + NULL, max77775_vsafe0v_irq, + 0, + "cc-vsafe0v-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + goto err_irq; + } + } + + cc_data->irq_vconnsc = usbc_data->irq_base + MAX77775_CC_IRQ_VCONNSC_INT; + if (cc_data->irq_vconnsc) { + ret = request_threaded_irq(cc_data->irq_vconnsc, + NULL, max77775_vconnsc_irq, + 0, + "cc-vconnsc-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + goto err_irq; + } + } + cc_data->irq_ccpinstat = usbc_data->irq_base + MAX77775_CC_IRQ_CCPINSTAT_INT; + if (cc_data->irq_ccpinstat) { + ret = request_threaded_irq(cc_data->irq_ccpinstat, + NULL, max77775_ccpinstat_irq, + 0, + "cc-ccpinstat-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + goto err_irq; + } + } + cc_data->irq_ccistat = usbc_data->irq_base + MAX77775_CC_IRQ_CCISTAT_INT; + if (cc_data->irq_ccistat) { + ret = request_threaded_irq(cc_data->irq_ccistat, + NULL, max77775_ccistat_irq, + 0, + "cc-ccistat-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + goto err_irq; + } + } + cc_data->irq_ccvcnstat = usbc_data->irq_base + MAX77775_CC_IRQ_CCVCNSTAT_INT; + if (cc_data->irq_ccvcnstat) { + ret = request_threaded_irq(cc_data->irq_ccvcnstat, + NULL, max77775_ccvnstat_irq, + 0, + "cc-ccvcnstat-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + goto err_irq; + } + } + cc_data->irq_ccstat = usbc_data->irq_base + MAX77775_CC_IRQ_CCSTAT_INT; + if (cc_data->irq_ccstat) { + ret = request_threaded_irq(cc_data->irq_ccstat, + NULL, max77775_ccstat_irq, + 0, + "cc-ccstat-irq", usbc_data); + if (ret) { + pr_err("%s: Failed to Request IRQ (%d)\n", __func__, ret); + goto err_irq; + } + } + /* check CC Pin state for cable attach booting scenario */ + max77775_ccstat_irq_handler(usbc_data, CCIC_IRQ_INIT_DETECT); + max77775_read_reg(usbc_data->muic, REG_CC_STATUS2, &cc_data->cc_status2); + usbc_data->current_connstat = (cc_data->cc_status2 & BIT_ConnStat) + >> FFS(BIT_ConnStat); + pr_info("%s: water state : %s\n", __func__, usbc_data->current_connstat ? "WATER" : "DRY"); + + if (usbc_data->current_connstat) { + if (!usbc_data->max77775->blocking_waterevent) + max77775_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_WATER, + 1/*attach*/, + 0, + 0); + } + msg_maxim("OUT"); + + return 0; + +err_irq: + kfree(cc_data); + return ret; + +} diff --git a/drivers/usb/typec/maxim/max77775_pd.c b/drivers/usb/typec/maxim/max77775_pd.c new file mode 100755 index 000000000000..f6edcb5ec735 --- /dev/null +++ b/drivers/usb/typec/maxim/max77775_pd.c @@ -0,0 +1,1995 @@ +/* + * Copyrights (C) 2017 Samsung Electronics, Inc. + * Copyrights (C) 2017 Maxim Integrated Products, 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) +#include +#endif +#include +#include +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) +#include +#include +#endif +#include +#include +#include +#include +#include + +#if IS_ENABLED(CONFIG_SEC_MPARAM) || (IS_MODULE(CONFIG_SEC_PARAM) && defined(CONFIG_ARCH_EXYNOS)) +extern int factory_mode; +#else +static int __read_mostly factory_mode; +module_param(factory_mode, int, 0444); +#endif + +extern struct max77775_usbc_platform_data *g_usbc_data; +extern void max77775_set_CCForceError(struct max77775_usbc_platform_data *usbpd_data); +void max77775_set_enable_pps(bool bPPS_on, bool enable, int ppsVol, int ppsCur); + +static int max77775_get_facmode(void) { return factory_mode; } + +static void max77775_process_pd(struct max77775_usbc_platform_data *usbc_data) +{ + struct max77775_pd_data *pd_data = usbc_data->pd_data; + + if (pd_data->cc_sbu_short) { + pd_data->pd_noti.sink_status.available_pdo_num = 1; + pd_data->pd_noti.sink_status.power_list[1].max_current = + pd_data->pd_noti.sink_status.power_list[1].max_current > 1800 ? + 1800 : pd_data->pd_noti.sink_status.power_list[1].max_current; + pd_data->pd_noti.sink_status.has_apdo = false; + } + + md75_info_usb("%s : current_pdo_num(%d), available_pdo_num(%d), has_apdo(%d)\n", __func__, + pd_data->pd_noti.sink_status.current_pdo_num, pd_data->pd_noti.sink_status.available_pdo_num, pd_data->pd_noti.sink_status.has_apdo); + + max77775_ccic_event_work(usbc_data, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_POWER_STATUS, 1/*attach*/, 0, 0); +} +static void max77775_send_new_src_cap(struct max77775_usbc_platform_data *pusbpd, + int auth, int d2d_type); +void max77775_vbus_turn_on_ctrl(struct max77775_usbc_platform_data *usbc_data, bool enable, bool swaped); +void max77775_response_req_pdo(struct max77775_usbc_platform_data *usbc_data, + unsigned char *data) +{ + u8 sel_pdo = 0x00; + int auth_type = usbc_data->pd_data->auth_type; + int d2d_type = usbc_data->pd_data->d2d_type; + + if ((d2d_type == D2D_NONE) || (auth_type == AUTH_NONE)) + return; + + sel_pdo = ((data[1] >> 3) & 0x07); + + if (d2d_type == D2D_SRCSNK) { + if (sel_pdo == 1) { + /* 2.5w fpdo */ + usbc_data->pd_data->req_pdo_type = PDO_TYPE_FIXED; + } else if ((sel_pdo >= 2) && + (auth_type == AUTH_HIGH_PWR)) { + /* 15w vpdo */ + usbc_data->pd_data->req_pdo_type = PDO_TYPE_VARIABLE; + } + /* TEST */ + + //if (auth_type == AUTH_HIGH_PWR) + // usbc_data->pd_data->req_pdo_type = PDO_TYPE_VARIABLE; + //else + // usbc_data->pd_data->req_pdo_type = PDO_TYPE_FIXED; + + /* TEST */ + } else + usbc_data->pd_data->req_pdo_type = PDO_TYPE_FIXED; + + md75_info_usb("%s : snk pdo(%d, %d)\n", __func__, sel_pdo, usbc_data->pd_data->req_pdo_type); + max77775_vbus_turn_on_ctrl(usbc_data, ON, false); +} + +void max77775_check_req_pdo(struct max77775_usbc_platform_data *usbc_data) +{ + usbc_cmd_data value; + + init_usbc_cmd_data(&value); + value.opcode = OPCODE_SNK_SELECTED_PDO; + value.write_length = 0x0; + value.read_length = 31; + max77775_usbc_opcode_read(usbc_data, &value); + + md75_info_usb("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d)\n", + __func__, value.opcode, value.write_length, value.read_length); +} +void max77775_select_pdo(int num) +{ + struct max77775_pd_data *pd_data = g_usbc_data->pd_data; + usbc_cmd_data value; + u8 temp; + + if (pd_data->pd_noti.event == PDIC_NOTIFY_EVENT_DETACH) { + md75_info_usb("%s : PD TA already detached. Doesn't select pdo(%d)\n", __func__, num); + return; + } + + max77775_set_enable_pps(pd_data->bPPS_on, false, 0, 0); + + init_usbc_cmd_data(&value); + md75_info_usb("%s : NUM(%d)\n", __func__, num); + + temp = num; + + pd_data->pd_noti.sink_status.selected_pdo_num = num; + + if (pd_data->pd_noti.event != PDIC_NOTIFY_EVENT_PD_SINK) + pd_data->pd_noti.event = PDIC_NOTIFY_EVENT_PD_SINK; + + if (pd_data->pd_noti.sink_status.current_pdo_num == pd_data->pd_noti.sink_status.selected_pdo_num) { + max77775_process_pd(g_usbc_data); + } else { + g_usbc_data->pn_flag = false; + value.opcode = OPCODE_SRCCAP_REQUEST; + value.write_data[0] = temp; + value.write_length = 1; + value.read_length = 1; + max77775_usbc_opcode_write(g_usbc_data, &value); + } + + md75_info_usb("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d) NUM(%d)\n", + __func__, value.opcode, value.write_length, value.read_length, num); +} + +void max77775_response_pdo_request(struct max77775_usbc_platform_data *usbc_data, + unsigned char *data) +{ + u8 result = data[1]; + + md75_info_usb("%s: %s (0x%02X)\n", __func__, result ? "Error," : "Sent,", result); + + switch (result) { + case 0x00: + md75_info_usb("%s: Sent PDO Request Message to Port Partner(0x%02X)\n", __func__, result); + break; + case 0xFE: + md75_info_usb("%s: Error, SinkTxNg(0x%02X)\n", __func__, result); + break; + case 0xFF: + md75_info_usb("%s: Error, Not in SNK Ready State(0x%02X)\n", __func__, result); + break; + default: + break; + } + + /* retry if the state of sink is not stable yet */ + if (result == 0xFE || result == 0xFF) { + cancel_delayed_work(&usbc_data->pd_data->retry_work); + queue_delayed_work(usbc_data->pd_data->wqueue, &usbc_data->pd_data->retry_work, 0); + } +} + +void max77775_set_enable_pps(bool bPPS_on, bool enable, int ppsVol, int ppsCur) +{ + usbc_cmd_data value; + + if (bPPS_on == enable) + return; + + init_usbc_cmd_data(&value); + value.opcode = OPCODE_SET_PPS; + if (enable) { + value.write_data[0] = 0x1; //PPS_ON On + value.write_data[1] = (ppsVol / 20) & 0xFF; //Default Output Voltage (Low), 20mV + value.write_data[2] = ((ppsVol / 20) >> 8) & 0xFF; //Default Output Voltage (High), 20mV + value.write_data[3] = (ppsCur / 50) & 0x7F; //Default Operating Current, 50mA + value.write_length = 4; + value.read_length = 1; + md75_info_usb("%s : PPS_On (Vol:%dmV, Cur:%dmA)\n", __func__, ppsVol, ppsCur); + } else { + value.write_data[0] = 0x0; //PPS_ON Off + value.write_length = 1; + value.read_length = 1; + md75_info_usb("%s : PPS_Off\n", __func__); + } + max77775_usbc_opcode_write(g_usbc_data, &value); +} + +void max77775_response_set_pps(struct max77775_usbc_platform_data *usbc_data, + unsigned char *data) +{ + u8 result = data[1]; + + if (result & 0x01) + usbc_data->pd_data->bPPS_on = true; + else + usbc_data->pd_data->bPPS_on = false; + + md75_info_usb("%s : PPS_%s (0x%02X)\n", + __func__, usbc_data->pd_data->bPPS_on ? "On" : "Off", result); +} + +void max77775_response_apdo_request(struct max77775_usbc_platform_data *usbc_data, + unsigned char *data) +{ + u8 result = data[1]; + u8 status[5]; + u8 vbvolt; + + md75_info_usb("%s: %s (0x%02X)\n", __func__, result ? "Error," : "Sent,", result); + + switch (result) { + case 0x00: + md75_info_usb("%s: Sent APDO Request Message to Port Partner(0x%02X)\n", __func__, result); + break; + case 0x01: + md75_info_usb("%s: Error, Invalid APDO position(0x%02X)\n", __func__, result); + break; + case 0x02: + md75_info_usb("%s: Error, Invalid Output Voltage(0x%02X)\n", __func__, result); + break; + case 0x03: + md75_info_usb("%s: Error, Invalid Operating Current(0x%02X)\n", __func__, result); + break; + case 0x04: + md75_info_usb("%s: Error, PPS Function Off(0x%02X)\n", __func__, result); + break; + case 0x05: + md75_info_usb("%s: Error, Not in SNK Ready State(0x%02X)\n", __func__, result); + break; + case 0x06: + md75_info_usb("%s: Error, PD2.0 Contract(0x%02X)\n", __func__, result); + break; + case 0x07: + md75_info_usb("%s: Error, SinkTxNg(0x%02X)\n", __func__, result); + break; + default: + break; + } + + max77775_bulk_read(usbc_data->muic, MAX77775_USBC_REG_USBC_STATUS1, 5, status); + vbvolt = (status[2] & BC_STATUS_VBUSDET_MASK) >> BC_STATUS_VBUSDET_SHIFT; + if (vbvolt != 0x01) + md75_info_usb("%s: Error, VBUS isn't above 5V(0x%02X)\n", __func__, vbvolt); + + /* retry if the state of sink is not stable yet */ + if ((result == 0x05 || result == 0x07) && vbvolt == 0x1) { + cancel_delayed_work(&usbc_data->pd_data->retry_work); + queue_delayed_work(usbc_data->pd_data->wqueue, &usbc_data->pd_data->retry_work, 0); + } +} + +int max77775_select_pps(int num, int ppsVol, int ppsCur) +{ + struct max77775_pd_data *pd_data = g_usbc_data->pd_data; + usbc_cmd_data value; + + /* [dchg] TODO: check more below option */ + if (num > pd_data->pd_noti.sink_status.available_pdo_num) { + md75_info_usb("%s: request pdo num(%d) is higher that available pdo.\n", __func__, num); + return -EINVAL; + } + + if (!(pd_data->pd_noti.sink_status.power_list[num].pdo_type == APDO_TYPE)) { + md75_info_usb("%s: request pdo num(%d) is not apdo.\n", __func__, num); + return -EINVAL; + } else + pd_data->pd_noti.sink_status.selected_pdo_num = num; + + if (ppsVol > pd_data->pd_noti.sink_status.power_list[num].max_voltage) { + md75_info_usb("%s: ppsVol is over(%d, max:%d)\n", + __func__, ppsVol, pd_data->pd_noti.sink_status.power_list[num].max_voltage); + ppsVol = pd_data->pd_noti.sink_status.power_list[num].max_voltage; + } else if (ppsVol < pd_data->pd_noti.sink_status.power_list[num].min_voltage) { + md75_info_usb("%s: ppsVol is under(%d, min:%d)\n", + __func__, ppsVol, pd_data->pd_noti.sink_status.power_list[num].min_voltage); + ppsVol = pd_data->pd_noti.sink_status.power_list[num].min_voltage; + } + + if (ppsCur > pd_data->pd_noti.sink_status.power_list[num].max_current) { + md75_info_usb("%s: ppsCur is over(%d, max:%d)\n", + __func__, ppsCur, pd_data->pd_noti.sink_status.power_list[num].max_current); + ppsCur = pd_data->pd_noti.sink_status.power_list[num].max_current; + } else if (ppsCur < 0) { + md75_info_usb("%s: ppsCur is under(%d, 0)\n", + __func__, ppsCur); + ppsCur = 0; + } + + pd_data->pd_noti.sink_status.pps_voltage = ppsVol; + pd_data->pd_noti.sink_status.pps_current = ppsCur; + + md75_info_usb(" %s : PPS PDO(%d), voltage(%d), current(%d) is selected to change\n", + __func__, pd_data->pd_noti.sink_status.selected_pdo_num, ppsVol, ppsCur); + + max77775_set_enable_pps(pd_data->bPPS_on, true, 5000, 1000); /* request as default 5V when enable first */ + + init_usbc_cmd_data(&value); + + g_usbc_data->pn_flag = false; + value.opcode = OPCODE_APDO_SRCCAP_REQUEST; + value.write_data[0] = (num & 0xFF); /* APDO Position */ + value.write_data[1] = (ppsVol / 20) & 0xFF; /* Output Voltage(Low) */ + value.write_data[2] = ((ppsVol / 20) >> 8) & 0xFF; /* Output Voltage(High) */ + value.write_data[3] = (ppsCur / 50) & 0x7F; /* Operating Current */ + value.write_length = 4; + value.read_length = 1; /* Result */ + max77775_usbc_opcode_write(g_usbc_data, &value); + +/* [dchg] TODO: add return value */ + return 0; +} + +void max77775_pd_retry_work(struct work_struct *work) +{ + struct max77775_pd_data *pd_data = g_usbc_data->pd_data; + usbc_cmd_data value; + u8 num; + + if (pd_data->pd_noti.event == PDIC_NOTIFY_EVENT_DETACH) + return; + + init_usbc_cmd_data(&value); + num = pd_data->pd_noti.sink_status.selected_pdo_num; + md75_info_usb("%s : latest selected_pdo_num(%d)\n", __func__, num); + g_usbc_data->pn_flag = false; + + if (pd_data->pd_noti.sink_status.power_list[num].pdo_type == APDO_TYPE) { + value.opcode = OPCODE_APDO_SRCCAP_REQUEST; + value.write_data[0] = (num & 0xFF); /* APDO Position */ + value.write_data[1] = (pd_data->pd_noti.sink_status.pps_voltage / 20) & 0xFF; /* Output Voltage(Low) */ + value.write_data[2] = ((pd_data->pd_noti.sink_status.pps_voltage / 20) >> 8) & 0xFF; /* Output Voltage(High) */ + value.write_data[3] = (pd_data->pd_noti.sink_status.pps_current / 50) & 0x7F; /* Operating Current */ + value.write_length = 4; + value.read_length = 1; /* Result */ + max77775_usbc_opcode_write(g_usbc_data, &value); + } else { + value.opcode = OPCODE_SRCCAP_REQUEST; + value.write_data[0] = num; + value.write_length = 1; + value.read_length = 1; + max77775_usbc_opcode_write(g_usbc_data, &value); + } + + md75_info_usb("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d) NUM(%d)\n", + __func__, value.opcode, value.write_length, value.read_length, num); +} + +void max77775_usbc_icurr_autoibus_on(u8 curr) +{ + usbc_cmd_data value; + + init_usbc_cmd_data(&value); + value.opcode = OPCODE_ICURR_AUTOIBUS_ON; + value.write_data[0] = curr; + value.write_length = 1; + value.read_length = 1; + max77775_usbc_opcode_write(g_usbc_data, &value); + + md75_info_usb("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d) DATA(%d)\n", + __func__, value.opcode, value.write_length, value.read_length, 0); +} +EXPORT_SYMBOL(max77775_usbc_icurr_autoibus_on); + +void max77775_usbc_icurr(u8 curr) +{ + usbc_cmd_data value; + + init_usbc_cmd_data(&value); + value.opcode = OPCODE_CHGIN_ILIM_W; + value.write_data[0] = curr; + value.write_length = 1; + value.read_length = 0; + max77775_usbc_opcode_write(g_usbc_data, &value); + + md75_info_usb("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d) USBC_ILIM(0x%x)\n", + __func__, value.opcode, value.write_length, value.read_length, curr); + +} +EXPORT_SYMBOL(max77775_usbc_icurr); + +void max77775_set_fw_noautoibus(int enable) +{ + usbc_cmd_data value; + u8 op_data = 0x00; + + switch (enable) { + case MAX77775_AUTOIBUS_FW_AT_OFF: + op_data = 0x03; /* usbc fw off & auto off(manual on) */ + break; + case MAX77775_AUTOIBUS_FW_OFF: + op_data = 0x02; /* usbc fw off & auto on(manual off) */ + break; + case MAX77775_AUTOIBUS_AT_OFF: + op_data = 0x01; /* usbc fw on & auto off(manual on) */ + break; + case MAX77775_AUTOIBUS_ON: + default: + op_data = 0x00; /* usbc fw on & auto on(manual off) */ + break; + } + + if (max77775_get_facmode()) { + md75_info_usb("%s: Factory Mode set AUTOIBUS_FW_AT_OFF\n", __func__); + op_data = 0x03; /* usbc fw off & auto off(manual on) */ + } + + init_usbc_cmd_data(&value); + value.opcode = OPCODE_SAMSUNG_FW_AUTOIBUS; + value.write_data[0] = op_data; + value.write_length = 1; + value.read_length = 0; + max77775_usbc_opcode_write(g_usbc_data, &value); + + md75_info_usb("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d) AUTOIBUS(0x%x)\n", + __func__, value.opcode, value.write_length, value.read_length, op_data); +} +EXPORT_SYMBOL(max77775_set_fw_noautoibus); + +void max77775_set_shipmode_op(int enable, u8 data) +{ +#if defined(CONFIG_SUPPORT_SHIP_MODE) + g_usbc_data->ship_mode_en = (enable ? 1 : 0); + g_usbc_data->ship_mode_data = data; + + md75_info_usb("%s : enable(%d) data(0x%x)\n", + __func__, enable, data); +#else + md75_info_usb("%s Not supported\n", __func__); +#endif +} +EXPORT_SYMBOL(max77775_set_shipmode_op); + +void max77775_usb_id_set(u8 mode) +{ + usbc_cmd_data write_data; + struct max77775_muic_data *muic_data; + + if (g_usbc_data && g_usbc_data->muic_data) { + muic_data = g_usbc_data->muic_data; + disable_irq(muic_data->irq_vbadc); + } + + msg_maxim("mode=%d", mode); + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_USB_ID_SET; + write_data.write_data[0] = mode & 0x07; + write_data.write_length = 0x1; + write_data.read_length = 1; + max77775_usbc_opcode_write(g_usbc_data, &write_data); +} +EXPORT_SYMBOL(max77775_usb_id_set); + +void max77775_chgrcv_ramp(bool enable) +{ + usbc_cmd_data value; + u8 op_data = 0x00; + + op_data = enable ? 0x00 : 0x01; + + init_usbc_cmd_data(&value); + value.opcode = OPCODE_CHGRCV_RAMP; + value.write_data[0] = op_data; + value.write_length = 1; + value.read_length = 0; + max77775_usbc_opcode_write(g_usbc_data, &value); + + md75_info_usb("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d) CHGRCV_RAMP(0x%x)\n", + __func__, value.opcode, value.write_length, value.read_length, op_data); +} +EXPORT_SYMBOL(max77775_chgrcv_ramp); + +#if !defined(CONFIG_SEC_FACTORY) +void max77775_bypass_maintain(void) +{ + usbc_cmd_data value; + u8 op_data = 0x00; + + init_usbc_cmd_data(&value); + value.opcode = OPCODE_BYPASS_MTN; + value.write_data[0] = op_data; + value.write_length = 1; + value.read_length = 0; + max77775_usbc_opcode_write(g_usbc_data, &value); + + md75_info_usb("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d) BYPASS(0x%x)\n", + __func__, value.opcode, value.write_length, value.read_length, op_data); +} +EXPORT_SYMBOL(max77775_bypass_maintain); +#endif + +static void max77775_set_snkcap(struct max77775_usbc_platform_data *usbc_data) +{ + struct device_node *np = NULL; + u8 *snkcap_data; + int len = 0, ret = 0; + usbc_cmd_data value; + int i; + char *str = NULL; + + np = of_find_compatible_node(NULL, NULL, "maxim,max77775"); + if (!np) { + md75_info_usb("%s : np is NULL\n", __func__); + return; + } + + if (!of_get_property(np, "max77775,snkcap_data", &len)) { + md75_info_usb("%s : max77775,snkcap_data is Empty !!\n", __func__); + return; + } + + len = len / sizeof(u8); + snkcap_data = kzalloc(sizeof(*snkcap_data) * len, GFP_KERNEL); + if (!snkcap_data) { + md75_err_usb("%s : Failed to allocate memory (snkcap_data)\n", __func__); + return; + } + + ret = of_property_read_u8_array(np, "max77775,snkcap_data", + snkcap_data, len); + if (ret) { + md75_info_usb("%s : max77775,snkcap_data is Empty\n", __func__); + goto err_free_snkcap_data; + } + + init_usbc_cmd_data(&value); + + if (len) + memcpy(value.write_data, snkcap_data, len); + + str = kzalloc(sizeof(char) * 1024, GFP_KERNEL); + if (str) { + for (i = 0; i < len; i++) + sprintf(str + strlen(str), "0x%02x ", value.write_data[i]); + md75_info_usb("%s: SNK_CAP : %s\n", __func__, str); + } + + value.opcode = OPCODE_SET_SNKCAP; + value.write_length = len; + value.read_length = 0; + max77775_usbc_opcode_write(usbc_data, &value); + + kfree(str); +err_free_snkcap_data: + kfree(snkcap_data); +} + +bool max77775_check_boost_enable(int auth_t, int req_pdo, int d2d_t) +{ + if ((auth_t == AUTH_NONE) || (d2d_t != D2D_SRCSNK)) + return false; + + if (req_pdo == PDO_TYPE_VARIABLE) + return true; + + return false; +} + +bool max77775_check_boost_off(int auth_t, int req_pdo, int d2d_t) +{ + if ((auth_t == AUTH_NONE) || (d2d_t != D2D_SRCSNK)) + return false; + + if (req_pdo == PDO_TYPE_FIXED) + return true; + + return false; +} + +bool max77775_check_src_otg_type(bool enable, int auth_t, int req_pdo, int d2d_t) +{ + if ((auth_t == AUTH_NONE) || (d2d_t != D2D_SRCSNK)) + return enable; + + if (req_pdo == PDO_TYPE_VARIABLE) + return false; + + return enable; +} + +void max77775_vbus_turn_on_ctrl(struct max77775_usbc_platform_data *usbc_data, bool enable, bool swaped) +{ + struct power_supply *psy_otg; + union power_supply_propval val; + int on = !!enable; + int ret = 0; + int count = 5; + int auth_type = usbc_data->pd_data->auth_type; + int req_pdo_type = usbc_data->pd_data->req_pdo_type; + int d2d_type = usbc_data->pd_data->d2d_type; +#if defined(CONFIG_USB_HOST_NOTIFY) + struct otg_notify *o_notify = get_otg_notify(); + bool must_block_host = 0; + static int reserve_booster; + +#ifdef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + if (o_notify) + must_block_host = is_blocked(o_notify, NOTIFY_BLOCK_TYPE_HOST); +#endif + + md75_info_usb("%s : enable=%d, auto_vbus_en=%d, must_block_host=%d, swaped=%d\n", + __func__, enable, usbc_data->auto_vbus_en, must_block_host, swaped); + // turn on + if (enable) { + // auto-mode + if (usbc_data->auto_vbus_en) { + // mpsm + if (must_block_host) { + if (swaped) { + // turn off vbus because of swaped and blocked host + enable = false; + md75_info_usb("%s : turn off vbus because of blocked host\n", + __func__); + } else { + enable = false; + md75_info_usb("%s : turn off vbus because of blocked host\n", + __func__); + } + } else { + // don't turn on because of auto-mode + return; + } + // non auto-mode + } else { + if (must_block_host) { + if (swaped) { + enable = false; + md75_info_usb("%s : turn off vbus because of blocked host\n", + __func__); + } else { + enable = false; + md75_info_usb("%s : turn off vbus because of blocked host\n", + __func__); + } + } + } + // turn off + } else { + // don't turn off because of auto-mode or blocked (already off) + if (usbc_data->auto_vbus_en || must_block_host) + return; + } +#endif + + md75_info_usb("%s : enable=%d\n", __func__, enable); + +#if defined(CONFIG_USB_HOST_NOTIFY) + if (o_notify && o_notify->booting_delay_sec && enable) { + md75_info_usb("%s %d, is booting_delay_sec. skip to control booster\n", + __func__, __LINE__); + reserve_booster = 1; + send_otg_notify(o_notify, NOTIFY_EVENT_RESERVE_BOOSTER, 1); + return; + } + if (!enable) { + if (reserve_booster) { + reserve_booster = 0; + send_otg_notify(o_notify, NOTIFY_EVENT_RESERVE_BOOSTER, 0); + } + } +#endif + + while (count) { + psy_otg = power_supply_get_by_name("otg"); + if (psy_otg) { + if (max77775_check_boost_off(auth_type, req_pdo_type, d2d_type)) { + val.intval = 0; + /* disable dc reverse boost before otg on */ + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL, val); + } + + val.intval = max77775_check_src_otg_type(enable, auth_type, req_pdo_type, d2d_type); +#if defined(CONFIG_USE_SECOND_MUIC) + muic_hv_charger_disable(enable); +#endif + + ret = psy_otg->desc->set_property(psy_otg, POWER_SUPPLY_PROP_ONLINE, &val); + if (ret == -ENODEV) { + md75_err_usb("%s: fail to set power_suppy ONLINE property %d) retry (%d)\n", __func__, ret, count); + count--; + } else { + if (ret) { + md75_err_usb("%s: fail to set power_suppy ONLINE property(%d)\n", __func__, ret); + } else { + md75_info_usb("otg accessory power = %d\n", on); + } + if (max77775_check_boost_enable(auth_type, req_pdo_type, d2d_type)) { + val.intval = enable; /* set dc reverse boost after otg off */ + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL, val); + } + break; + } + } else { + md75_err_usb("%s: fail to get psy battery\n", __func__); + count--; + msleep(200); + } + } +} + +void max77775_pdo_list(struct max77775_usbc_platform_data *usbc_data, unsigned char *data) +{ + struct max77775_pd_data *pd_data = usbc_data->pd_data; + u8 temp = 0x00; + int i; + bool do_power_nego = false; + pd_data->pd_noti.event = PDIC_NOTIFY_EVENT_PD_SINK; + + temp = (data[1] >> 5); + + if (temp > MAX_PDO_NUM) { + md75_info_usb("%s : update available_pdo_num[%d -> %d]", + __func__, temp, MAX_PDO_NUM); + temp = MAX_PDO_NUM; + } + + pd_data->pd_noti.sink_status.available_pdo_num = temp; + md75_info_usb("%s : Temp[0x%02x] Data[0x%02x] available_pdo_num[%d]\n", + __func__, temp, data[1], pd_data->pd_noti.sink_status.available_pdo_num); + + for (i = 0; i < temp; i++) { + u32 pdo_temp; + int max_current = 0, max_voltage = 0; + + pdo_temp = (data[2 + (i * 4)] + | (data[3 + (i * 4)] << 8) + | (data[4 + (i * 4)] << 16) + | (data[5 + (i * 4)] << 24)); + + md75_info_usb("%s : PDO[%d] = 0x%x\n", __func__, i, pdo_temp); + + max_current = (0x3FF & pdo_temp); + max_voltage = (0x3FF & (pdo_temp >> 10)); + + if (!(do_power_nego) && + (pd_data->pd_noti.sink_status.power_list[i + 1].max_current != max_current * UNIT_FOR_CURRENT || + pd_data->pd_noti.sink_status.power_list[i + 1].max_voltage != max_voltage * UNIT_FOR_VOLTAGE)) + do_power_nego = true; + + pd_data->pd_noti.sink_status.power_list[i + 1].max_current = max_current * UNIT_FOR_CURRENT; + pd_data->pd_noti.sink_status.power_list[i + 1].max_voltage = max_voltage * UNIT_FOR_VOLTAGE; + + md75_info_usb("%s : PDO_Num[%d] MAX_CURR(%d) MAX_VOLT(%d), AVAILABLE_PDO_Num(%d)\n", __func__, + i, pd_data->pd_noti.sink_status.power_list[i + 1].max_current, + pd_data->pd_noti.sink_status.power_list[i + 1].max_voltage, + pd_data->pd_noti.sink_status.available_pdo_num); + } + + if (usbc_data->pd_data->pdo_list && do_power_nego) { + md75_info_usb("%s : PDO list is changed, so power negotiation is need\n", + __func__); + pd_data->pd_noti.sink_status.selected_pdo_num = 0; + pd_data->pd_noti.event = PDIC_NOTIFY_EVENT_PD_SINK_CAP; + } + + if (pd_data->pd_noti.sink_status.current_pdo_num != pd_data->pd_noti.sink_status.selected_pdo_num) { + if (pd_data->pd_noti.sink_status.selected_pdo_num == 0) + md75_info_usb("%s : PDO is not selected yet by default\n", __func__); + } + + usbc_data->pd_data->pdo_list = true; + max77775_process_pd(usbc_data); +} + +bool is_accept_pdo(POWER_LIST *pPower_list) +{ + int pdo_type = pPower_list->pdo_type; + int max_volt = pPower_list->max_voltage; + int min_volt = pPower_list->min_voltage; + + if (max_volt < min_volt) + return false; + + if ((pdo_type == FPDO_TYPE) || (pdo_type == VPDO_TYPE)) { + if ((max_volt < DEFAULT_VOLTAGE) || (max_volt > AVAILABLE_VOLTAGE)) + return false; + } + + return true; +} + +void max77775_abnormal_pdo_work(struct work_struct *work) +{ + struct max77775_usbc_platform_data *usbc_data = g_usbc_data; + + md75_info_usb("%s\n", __func__); + //executes the ErroryRecovery. + max77775_set_CCForceError(usbc_data); +} + +void max77775_current_pdo(struct max77775_usbc_platform_data *usbc_data, unsigned char *data) +{ + struct max77775_pd_data *pd_data = usbc_data->pd_data; + u8 sel_pdo_pos = 0x00, num_of_pdo = 0x00; + int i, available_pdo_num = 0; + bool do_power_nego = false, is_abnormal_pdo = true; + U_SEC_PDO_OBJECT pdo_obj; + POWER_LIST *pPower_list; + POWER_LIST prev_power_list; + + if (!pd_data->pd_noti.sink_status.available_pdo_num) + do_power_nego = true; + + sel_pdo_pos = ((data[1] >> 3) & 0x07); + pd_data->pd_noti.sink_status.current_pdo_num = sel_pdo_pos; + + num_of_pdo = (data[1] & 0x07); + if (num_of_pdo > MAX_PDO_NUM) { + md75_info_usb("%s : update available_pdo_num[%d -> %d]", + __func__, num_of_pdo, MAX_PDO_NUM); + num_of_pdo = MAX_PDO_NUM; + } + + pd_data->pd_noti.sink_status.has_apdo = false; + + for (i = 0; i < num_of_pdo; ++i) { + pPower_list = &pd_data->pd_noti.sink_status.power_list[available_pdo_num + 1]; + + pdo_obj.data = (data[2 + (i * 4)] + | (data[3 + (i * 4)] << 8) + | (data[4 + (i * 4)] << 16) + | (data[5 + (i * 4)] << 24)); + + if (!do_power_nego) + prev_power_list = pd_data->pd_noti.sink_status.power_list[available_pdo_num + 1]; + + switch (pdo_obj.BITS_supply.type) { + case PDO_TYPE_FIXED: + pPower_list->apdo = false; + pPower_list->pdo_type = FPDO_TYPE; + pPower_list->max_voltage = pdo_obj.BITS_pdo_fixed.voltage * UNIT_FOR_VOLTAGE; + pPower_list->min_voltage = 0; + pPower_list->max_current = pdo_obj.BITS_pdo_fixed.max_current * UNIT_FOR_CURRENT; + pPower_list->comm_capable = pdo_obj.BITS_pdo_fixed.usb_communications_capable; + pPower_list->suspend = pdo_obj.BITS_pdo_fixed.usb_suspend_supported; + available_pdo_num++; + break; + case PDO_TYPE_APDO: + pd_data->pd_noti.sink_status.has_apdo = true; + pPower_list->apdo = true; + pPower_list->pdo_type = APDO_TYPE; + pPower_list->max_voltage = pdo_obj.BITS_pdo_programmable.max_voltage * UNIT_FOR_APDO_VOLTAGE; + pPower_list->min_voltage = pdo_obj.BITS_pdo_programmable.min_voltage * UNIT_FOR_APDO_VOLTAGE; + pPower_list->max_current = pdo_obj.BITS_pdo_programmable.max_current * UNIT_FOR_APDO_CURRENT; + available_pdo_num++; + break; + case PDO_TYPE_VARIABLE: + pPower_list->apdo = false; + pPower_list->pdo_type = VPDO_TYPE; + pPower_list->max_voltage = pdo_obj.BITS_pdo_variable.max_voltage * UNIT_FOR_VOLTAGE; + pPower_list->min_voltage = pdo_obj.BITS_pdo_variable.min_voltage * UNIT_FOR_VOLTAGE; + pPower_list->max_current = pdo_obj.BITS_pdo_variable.max_current * UNIT_FOR_CURRENT; + available_pdo_num++; + break; + default: + break; + } + + if (!(do_power_nego) && + (pPower_list->max_current != prev_power_list.max_current || + pPower_list->max_voltage != prev_power_list.max_voltage || + pPower_list->min_voltage != prev_power_list.min_voltage)) + do_power_nego = true; + } + + + if (!do_power_nego && (pd_data->pd_noti.sink_status.available_pdo_num != available_pdo_num)) + do_power_nego = true; + + pd_data->pd_noti.sink_status.available_pdo_num = available_pdo_num; + md75_info_usb("%s : current_pdo_num(%d), available_pdo_num(%d/%d), comm(%d), suspend(%d)\n", __func__, + pd_data->pd_noti.sink_status.current_pdo_num, + pd_data->pd_noti.sink_status.available_pdo_num, num_of_pdo, + pd_data->pd_noti.sink_status.power_list[sel_pdo_pos].comm_capable, + pd_data->pd_noti.sink_status.power_list[sel_pdo_pos].suspend); + + pd_data->pd_noti.event = PDIC_NOTIFY_EVENT_PD_SINK; + + if (usbc_data->pd_data->pdo_list && do_power_nego) { + md75_info_usb("%s : PDO list is changed, so power negotiation is need\n", __func__); + pd_data->pd_noti.sink_status.selected_pdo_num = 0; + pd_data->pd_noti.event = PDIC_NOTIFY_EVENT_PD_SINK_CAP; + } + + if (pd_data->pd_noti.sink_status.current_pdo_num != pd_data->pd_noti.sink_status.selected_pdo_num) { + if (pd_data->pd_noti.sink_status.selected_pdo_num == 0) + md75_info_usb("%s : PDO is not selected yet by default\n", __func__); + } + + if (do_power_nego || pd_data->pd_noti.sink_status.selected_pdo_num == 0) { + for (i = 0; i < num_of_pdo; ++i) { + pdo_obj.data = (data[2 + (i * 4)] + | (data[3 + (i * 4)] << 8) + | (data[4 + (i * 4)] << 16) + | (data[5 + (i * 4)] << 24)); + md75_info_usb("%s : PDO[%d] = 0x%08X\n", __func__, i + 1, pdo_obj.data); + } + + for (i = 0; i < pd_data->pd_noti.sink_status.available_pdo_num; ++i) { + pPower_list = &pd_data->pd_noti.sink_status.power_list[i + 1]; + pPower_list->accept = is_accept_pdo(pPower_list); + + md75_info_usb("%s : PDO[%d,%s,%s] max_vol(%dmV),min_vol(%dmV),max_cur(%dmA)\n", + __func__, i + 1, + pPower_list->pdo_type ? ((pPower_list->pdo_type == APDO_TYPE) ? "APDO" : "VPDO") : "FIXED", + pPower_list->accept ? "O" : "X", + pPower_list->max_voltage, pPower_list->min_voltage, pPower_list->max_current); + + if (pPower_list->accept) + is_abnormal_pdo = false; + } + } else { + is_abnormal_pdo = false; + } + + usbc_data->pd_data->pdo_list = true; + if (is_abnormal_pdo) { + if (!delayed_work_pending(&usbc_data->pd_data->abnormal_pdo_work)) { + union power_supply_propval val = {0,}; + + for (i = 0; i < num_of_pdo; ++i) { + pdo_obj.data = (data[2 + (i * 4)] + | (data[3 + (i * 4)] << 8) + | (data[4 + (i * 4)] << 16) + | (data[5 + (i * 4)] << 24)); + val.intval = (int)pdo_obj.data; + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_ABNORMAL_SRCCAP, val); + } + queue_delayed_work(usbc_data->pd_data->wqueue, + &usbc_data->pd_data->abnormal_pdo_work, 0); + } + } else { + max77775_process_pd(usbc_data); + } +} + +void max77775_detach_pd(struct max77775_usbc_platform_data *usbc_data) +{ + struct max77775_pd_data *pd_data = usbc_data->pd_data; + + md75_info_usb("%s : Detach PD CHARGER\n", __func__); + + if (pd_data->pd_noti.event != PDIC_NOTIFY_EVENT_DETACH) { + cancel_delayed_work(&usbc_data->pd_data->retry_work); + cancel_delayed_work(&usbc_data->pd_data->abnormal_pdo_work); + if (pd_data->pd_noti.sink_status.available_pdo_num) + memset(pd_data->pd_noti.sink_status.power_list, 0, (sizeof(POWER_LIST) * (MAX_PDO_NUM + 1))); + pd_data->pd_noti.sink_status.rp_currentlvl = RP_CURRENT_LEVEL_NONE; + pd_data->pd_noti.sink_status.selected_pdo_num = 0; + pd_data->pd_noti.sink_status.available_pdo_num = 0; + pd_data->pd_noti.sink_status.current_pdo_num = 0; + pd_data->pd_noti.sink_status.pps_voltage = 0; + pd_data->pd_noti.sink_status.pps_current = 0; + pd_data->pd_noti.sink_status.has_apdo = false; + max77775_set_enable_pps(pd_data->bPPS_on, false, 0, 0); + pd_data->pd_noti.event = PDIC_NOTIFY_EVENT_DETACH; + usbc_data->pd_data->psrdy_received = false; + usbc_data->pd_data->pdo_list = false; + usbc_data->pd_data->cc_sbu_short = false; + pd_data->auth_type = AUTH_NONE; + max77775_ccic_event_work(usbc_data, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_POWER_STATUS, 0/*attach*/, 0, 0); + } +} + +static void max77775_notify_prswap(struct max77775_usbc_platform_data *usbc_data, u8 pd_msg) +{ + struct max77775_pd_data *pd_data = usbc_data->pd_data; + + md75_info_usb("%s : PR SWAP pd_msg [%x]\n", __func__, pd_msg); + + switch (pd_msg) { + case PRSWAP_SNKTOSWAP: + pd_data->pd_noti.event = PDIC_NOTIFY_EVENT_PD_PRSWAP_SNKTOSRC; + pd_data->pd_noti.sink_status.selected_pdo_num = 0; + pd_data->pd_noti.sink_status.available_pdo_num = 0; + pd_data->pd_noti.sink_status.current_pdo_num = 0; + usbc_data->pd_data->psrdy_received = false; + usbc_data->pd_data->pdo_list = false; + usbc_data->pd_data->cc_sbu_short = false; + max77775_ccic_event_work(usbc_data, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_POWER_STATUS, 0/*attach*/, 0, 0); + break; + case PRSWAP_SRCTOSWAP: + pd_data->pd_noti.event = PDIC_NOTIFY_EVENT_PD_PRSWAP_SRCTOSNK; + pd_data->pd_noti.sink_status.selected_pdo_num = 0; + pd_data->pd_noti.sink_status.available_pdo_num = 0; + pd_data->pd_noti.sink_status.current_pdo_num = 0; + usbc_data->pd_data->psrdy_received = false; + usbc_data->pd_data->pdo_list = false; + usbc_data->pd_data->cc_sbu_short = false; + max77775_ccic_event_work(usbc_data, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_POWER_STATUS, 0/*attach*/, 0, 0); + break; + default: + break; + } +} + +void max77775_check_pdo(struct max77775_usbc_platform_data *usbc_data) +{ + usbc_cmd_data value; + + init_usbc_cmd_data(&value); + value.opcode = OPCODE_CURRENT_SRCCAP; + value.write_length = 0x0; + value.read_length = 31; + max77775_usbc_opcode_read(usbc_data, &value); + + md75_info_usb("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d)\n", + __func__, value.opcode, value.write_length, value.read_length); +} + +void max77775_notify_rp_current_level(struct max77775_usbc_platform_data *usbc_data) +{ + struct max77775_pd_data *pd_data = usbc_data->pd_data; + unsigned int rp_currentlvl; + + switch (usbc_data->cc_data->ccistat) { + case CCI_500mA: + rp_currentlvl = RP_CURRENT_LEVEL_DEFAULT; + break; + case CCI_1_5A: + rp_currentlvl = RP_CURRENT_LEVEL2; + break; + case CCI_3_0A: + rp_currentlvl = RP_CURRENT_LEVEL3; + break; + case CCI_SHORT: + rp_currentlvl = RP_CURRENT_ABNORMAL; + break; + default: + rp_currentlvl = RP_CURRENT_LEVEL_NONE; + break; + } + + if (usbc_data->plug_attach_done && !usbc_data->pd_data->psrdy_received && + usbc_data->cc_data->current_pr == SNK && + usbc_data->pd_state == max77775_State_PE_SNK_Wait_for_Capabilities && + pd_data->pdsmg != SRC_CAP_RECEIVED && // fw changes for advertise Rp22k for CtoC + rp_currentlvl != pd_data->pd_noti.sink_status.rp_currentlvl && + rp_currentlvl >= RP_CURRENT_LEVEL_DEFAULT) { + pd_data->pd_noti.sink_status.rp_currentlvl = rp_currentlvl; + pd_data->pd_noti.event = PDIC_NOTIFY_EVENT_PDIC_ATTACH; + md75_info_usb("%s : rp_currentlvl(%d)\n", __func__, pd_data->pd_noti.sink_status.rp_currentlvl); + max77775_ccic_event_work(usbc_data, PDIC_NOTIFY_DEV_BATT, + PDIC_NOTIFY_ID_POWER_STATUS, 0/*attach*/, 0, 0); + } +} + +static int max77775_get_chg_info(struct max77775_usbc_platform_data *usbc_data) +{ + usbc_cmd_data value; + POWER_LIST *pPower_list; + + pPower_list = &usbc_data->pd_data->pd_noti.sink_status.power_list[1]; + + if ((usbc_data->pd_data->sent_chg_info) || + (pPower_list->max_current < 2000)) + return 0; + + if (usbc_data->pd_data->sent_chg_info) + return 0; + + init_usbc_cmd_data(&value); + value.opcode = OPCODE_SEND_GET_REQUEST; + value.write_data[0] = OPCODE_GET_SRC_CAP_EXT; + value.write_data[1] = 0; /* */ + value.write_data[2] = 0; /* */ + value.write_length = 3; + value.read_length = 1; /* Result */ + max77775_usbc_opcode_write(g_usbc_data, &value); + + md75_info_usb("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d)\n", + __func__, value.opcode, value.write_length, value.read_length); + + usbc_data->pd_data->sent_chg_info = true; + return 0; +} + +static void clear_chg_info(struct max77775_usbc_platform_data *usbc_data) +{ + SEC_PD_SINK_STATUS *snk_sts = &usbc_data->pd_data->pd_noti.sink_status; + + usbc_data->pd_data->sent_chg_info = false; + snk_sts->pid = 0; + snk_sts->vid = 0; + snk_sts->xid = 0; +} + +static void max77775_pd_check_pdmsg(struct max77775_usbc_platform_data *usbc_data, u8 pd_msg) +{ + struct power_supply *psy_charger; + union power_supply_propval val; + usbc_cmd_data value; + /*int dr_swap, pr_swap, vcon_swap = 0; u8 value[2], rc = 0;*/ +#ifdef CONFIG_MAX77775_CCIC_ALTERNATE_MODE + MAX77775_VDM_MSG_IRQ_STATUS_Type VDM_MSG_IRQ_State; +#endif +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + int event; +#endif +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + struct otg_notify *o_notify = get_otg_notify(); +#endif +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + struct power_supply *psy; +#endif + +#ifdef CONFIG_MAX77775_CCIC_ALTERNATE_MODE + VDM_MSG_IRQ_State.DATA = 0x0; +#endif + init_usbc_cmd_data(&value); + msg_maxim(" pd_msg [%x]", pd_msg); + + switch (pd_msg) { + case Nothing_happened: + usbc_data->pd_data->src_cap_done = CC_SNK; + usbc_data->pd_data->req_pdo_type = PDO_TYPE_FIXED; + usbc_data->pd_data->psrdy_sent = false; + clear_chg_info(usbc_data); + break; + case Sink_PD_PSRdy_received: + max77775_get_chg_info(usbc_data); + /* currently, do nothing + * calling max77775_check_pdo() has been moved to max77775_psrdy_irq() + * for specific PD charger issue + */ + break; + case Sink_PD_Error_Recovery: + break; + case Sink_PD_SenderResponseTimer_Timeout: + msg_maxim("Sink_PD_SenderResponseTimer_Timeout received."); + /* queue_work(usbc_data->op_send_queue, &usbc_data->op_send_work); */ + break; + case Source_PD_PSRdy_Sent: + if (usbc_data->mpsm_mode && (usbc_data->pd_pr_swap == cc_SOURCE)) { + max77775_usbc_disable_auto_vbus(usbc_data); + max77775_vbus_turn_on_ctrl(usbc_data, OFF, false); + } + if ((usbc_data->pd_data->src_cap_done == CC_SRC) && + (usbc_data->pd_data->d2d_type != D2D_NONE)) + max77775_check_req_pdo(usbc_data); + usbc_data->pd_data->psrdy_sent = true; + break; + case Source_PD_Error_Recovery: + break; + case Source_PD_SenderResponseTimer_Timeout: + max77775_vbus_turn_on_ctrl(usbc_data, OFF, false); + schedule_delayed_work(&usbc_data->vbus_hard_reset_work, msecs_to_jiffies(800)); + break; + case PD_DR_Swap_Request_Received: + msg_maxim("DR_SWAP received."); +#if IS_ENABLED(CONFIG_USB_HOST_NOTIFY) + send_otg_notify(o_notify, NOTIFY_EVENT_DR_SWAP, 1); +#endif + /* currently, do nothing + * calling max77775_check_pdo() has been moved to max77775_psrdy_irq() + * for specific PD charger issue + */ + break; + case PD_PR_Swap_Request_Received: + msg_maxim("PR_SWAP received."); + break; + case PD_VCONN_Swap_Request_Received: + msg_maxim("VCONN_SWAP received."); + break; + case Received_PD_Message_in_illegal_state: + break; + case Samsung_Accessory_is_attached: + break; + case VDM_Attention_message_Received: + break; + case Sink_PD_Disabled: +#if 0 + /* to do */ + /* AFC HV */ + value[0] = 0x20; + rc = max77775_ccpd_write_command(chip, value, 1); + if (rc > 0) + md75_err_usb("failed to send command\n"); +#endif + break; + case Source_PD_Disabled: + break; + case Prswap_Snktosrc_Sent: + usbc_data->pd_pr_swap = cc_SOURCE; + break; + case Prswap_Srctosnk_Sent: + usbc_data->pd_pr_swap = cc_SINK; + break; + case HARDRESET_RECEIVED: + max77775_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_ALL, PDIC_NOTIFY_ID_CLEAR_INFO, + PDIC_NOTIFY_ID_DEVICE_INFO, 0, 0); + max77775_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_ALL, PDIC_NOTIFY_ID_CLEAR_INFO, + PDIC_NOTIFY_ID_SVID_INFO, 0, 0); + usbc_data->send_enter_mode_req = 0; + /*turn off the vbus both Source and Sink*/ + if (usbc_data->cc_data->current_pr == SRC) { + max77775_vbus_turn_on_ctrl(usbc_data, OFF, false); + schedule_delayed_work(&usbc_data->vbus_hard_reset_work, msecs_to_jiffies(760)); + } else if (usbc_data->cc_data->current_pr == SNK) { + usbc_data->detach_done_wait = 1; +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + psy = power_supply_get_by_name("battery"); + if (psy) { + val.intval = 0; + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_HARDRESET_OCCUR, val); + } else { + md75_err_usb("%s: Fail to get psy battery\n", __func__); + } +#endif + } +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_HARDRESET_RECEIVED; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + break; + case HARDRESET_SENT: + max77775_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_ALL, PDIC_NOTIFY_ID_CLEAR_INFO, + PDIC_NOTIFY_ID_DEVICE_INFO, 0, 0); + max77775_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_ALL, PDIC_NOTIFY_ID_CLEAR_INFO, + PDIC_NOTIFY_ID_SVID_INFO, 0, 0); + usbc_data->send_enter_mode_req = 0; + /*turn off the vbus both Source and Sink*/ + if (usbc_data->cc_data->current_pr == SRC) { + max77775_vbus_turn_on_ctrl(usbc_data, OFF, false); + schedule_delayed_work(&usbc_data->vbus_hard_reset_work, msecs_to_jiffies(760)); + } else if (usbc_data->cc_data->current_pr == SNK) { + usbc_data->detach_done_wait = 1; +#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG) + psy = power_supply_get_by_name("battery"); + if (psy) { + val.intval = 1; + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_HARDRESET_OCCUR, val); + } else { + md75_err_usb("%s: Fail to get psy battery\n", __func__); + } +#endif + } +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_HARDRESET_SENT; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + break; + case Get_Vbus_turn_on: + break; + case Get_Vbus_turn_off: + max77775_vbus_turn_on_ctrl(usbc_data, OFF, false); + break; + case PRSWAP_SRCTOSWAP: + usbc_data->pd_data->req_pdo_type = PDO_TYPE_FIXED; + max77775_notify_prswap(usbc_data, PRSWAP_SRCTOSWAP); + max77775_vbus_turn_on_ctrl(usbc_data, OFF, false); + msg_maxim("PRSWAP_SRCTOSWAP : [%x]", pd_msg); + break; + case PRSWAP_SWAPTOSNK: + max77775_vbus_turn_on_ctrl(usbc_data, OFF, false); + msg_maxim("PRSWAP_SWAPTOSNK : [%x]", pd_msg); + break; + case PRSWAP_SNKTOSWAP: + msg_maxim("PRSWAP_SNKTOSWAP : [%x]", pd_msg); + max77775_notify_prswap(usbc_data, PRSWAP_SNKTOSWAP); + /* CHGINSEL disable */ + psy_charger = power_supply_get_by_name("max77775-charger"); + if (psy_charger) { + val.intval = 0; + psy_do_property("max77775-charger", set, POWER_SUPPLY_EXT_PROP_CHGINSEL, val); + psy_do_property("max77775-charger", set, POWER_SUPPLY_EXT_PROP_PRSWAP, val); + } else { + md75_err_usb("%s: Fail to get psy charger\n", __func__); + } + break; + case PRSWAP_SWAPTOSRC: + if (usbc_data->pd_data->d2d_type == D2D_SRCSNK) + max77775_send_new_src_cap(g_usbc_data, + usbc_data->pd_data->auth_type, usbc_data->pd_data->d2d_type); + max77775_vbus_turn_on_ctrl(usbc_data, ON, false); + msg_maxim("PRSWAP_SNKTOSRC : [%x]", pd_msg); + break; + case Current_Cable_Connected: + max77775_set_jig_on(usbc_data, 1); + usbc_data->manual_lpm_mode = 1; + msg_maxim("Current_Cable_Connected : [%x]", pd_msg); + break; + case SRC_CAP_RECEIVED: + break; + case Status_Received: + value.opcode = OPCODE_SAMSUNG_READ_MESSAGE; + value.write_data[0] = 0x02; + value.write_length = 1; + value.read_length = 32; + max77775_usbc_opcode_write(usbc_data, &value); + msg_maxim("@TA_ALERT: Status Receviced : [%x]", pd_msg); + break; + case Alert_Message: + value.opcode = OPCODE_SAMSUNG_READ_MESSAGE; + value.write_data[0] = 0x01; + value.write_length = 1; + value.read_length = 32; + max77775_usbc_opcode_write(usbc_data, &value); + msg_maxim("@TA_ALERT: Alert Message : [%x]", pd_msg); + break; + case PDMSG_DP_ENTER_MODE: + /* To check SVID of enter mode */ + value.opcode = OPCODE_SAMSUNG_READ_MESSAGE; + value.write_data[0] = 0x03; + value.write_length = 1; + value.read_length = 32; + max77775_usbc_opcode_write(usbc_data, &value); + msg_maxim("Enter mode Receviced : [%x]", pd_msg); + break; + case PDMSG_SRC_ACCEPT: + if ((usbc_data->pd_data->src_cap_done == CC_SRC) && + (usbc_data->pd_data->d2d_type != D2D_NONE)) + max77775_check_req_pdo(usbc_data); + msg_maxim("SRC ACCEPT : [%x]", pd_msg); + break; + default: + break; + } +} + +void max77775_pd_check_pdmsg_callback(void *data, u8 pdmsg) +{ + struct max77775_usbc_platform_data *usbc_data = data; + union power_supply_propval val; + + if (!usbc_data) { + msg_maxim("usbc_data is null"); + return; + } + + if (!usbc_data->pd_data->psrdy_received && + (pdmsg == Sink_PD_PSRdy_received || pdmsg == SRC_CAP_RECEIVED)) { + msg_maxim("pdmsg=%x", pdmsg); + val.intval = 1; + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_SRCCAP, val); + } else if (usbc_data->pd_data->psrdy_received && (pdmsg == SRC_CAP_RECEIVED)) { + msg_maxim("pdmsg=%x", pdmsg); + val.intval = 0; + psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_SRCCAP, val); + } +} + +static void max77775_pd_rid(struct max77775_usbc_platform_data *usbc_data, u8 fct_id) +{ + struct max77775_pd_data *pd_data = usbc_data->pd_data; + + u8 prev_rid = pd_data->device; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + static int rid = RID_OPEN; +#endif + + switch (fct_id) { + case FCT_GND: + msg_maxim(" RID_GND"); + pd_data->device = DEV_FCT_GND; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + rid = RID_000K; +#endif + break; + case FCT_56Kohm: + msg_maxim(" RID_056K"); + pd_data->device = DEV_FCT_56K; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + rid = RID_056K; +#endif + break; + case FCT_255Kohm: + msg_maxim(" RID_255K"); + pd_data->device = DEV_FCT_255K; + usbc_data->rid_check = true; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + rid = RID_255K; +#endif + break; + case FCT_301Kohm: + msg_maxim(" RID_301K"); + pd_data->device = DEV_FCT_301K; + usbc_data->rid_check = true; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + rid = RID_301K; +#endif + break; + case FCT_523Kohm: + msg_maxim(" RID_523K"); + pd_data->device = DEV_FCT_523K; + usbc_data->rid_check = true; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + rid = RID_523K; +#endif + break; + case FCT_619Kohm: + msg_maxim(" RID_619K"); + pd_data->device = DEV_FCT_619K; + usbc_data->rid_check = true; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + rid = RID_619K; +#endif + break; + case FCT_OPEN: + msg_maxim(" RID_OPEN"); + pd_data->device = DEV_FCT_OPEN; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + rid = RID_OPEN; +#endif + break; + default: + msg_maxim(" RID_UNDEFINED"); + pd_data->device = DEV_UNKNOWN; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + rid = RID_UNDEFINED; +#endif + break; + } + + if (prev_rid != pd_data->device) { +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + /* RID */ + max77775_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_MUIC, PDIC_NOTIFY_ID_RID, + rid, 0, 0); + usbc_data->cur_rid = rid; + /* turn off USB */ + if (pd_data->device == DEV_FCT_OPEN || pd_data->device == DEV_UNKNOWN + || pd_data->device == DEV_FCT_523K || pd_data->device == DEV_FCT_619K) { + + usbc_data->typec_power_role = TYPEC_SINK; + + /* usb or otg */ + max77775_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_USB, PDIC_NOTIFY_ID_USB, + 0/*attach*/, USB_STATUS_NOTIFY_DETACH/*drp*/, 0); + } +#endif + } +} + +static irqreturn_t max77775_pdmsg_irq(int irq, void *data) +{ + struct max77775_usbc_platform_data *usbc_data = data; + struct max77775_pd_data *pd_data = usbc_data->pd_data; + u8 pdmsg = 0; + + max77775_read_reg(usbc_data->muic, REG_PD_STATUS1, &pd_data->pd_status1); + pdmsg = pd_data->pd_status1; + msg_maxim("IRQ(%d)_IN pdmsg: %02x", irq, pdmsg); + max77775_pd_check_pdmsg(usbc_data, pdmsg); + pd_data->pdsmg = pdmsg; + msg_maxim("IRQ(%d)_OUT", irq); + + return IRQ_HANDLED; +} + +static irqreturn_t max77775_psrdy_irq(int irq, void *data) +{ + struct max77775_usbc_platform_data *usbc_data = data; + u8 psrdy_received = 0; + enum typec_pwr_opmode mode = TYPEC_PWR_MODE_USB; +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + struct otg_notify *o_notify = get_otg_notify(); +#endif + + msg_maxim("IN"); + max77775_read_reg(usbc_data->muic, REG_PD_STATUS2, &usbc_data->pd_status2); + psrdy_received = (usbc_data->pd_status2 & BIT_PD_PSRDY) + >> FFS(BIT_PD_PSRDY); + + if (psrdy_received && !usbc_data->pd_support + && usbc_data->pd_data->cc_status != CC_NO_CONN) + usbc_data->pd_support = true; + + if (usbc_data->typec_try_state_change == TRY_ROLE_SWAP_PR && + usbc_data->pd_support) { + msg_maxim("typec_reverse_completion"); + usbc_data->typec_try_state_change = TRY_ROLE_SWAP_NONE; + complete(&usbc_data->typec_reverse_completion); + } + msg_maxim("psrdy_received=%d, usbc_data->pd_support=%d, cc_status=%d, src_cp_dn=%d", + psrdy_received, usbc_data->pd_support, usbc_data->pd_data->cc_status, + usbc_data->pd_data->src_cap_done); + + mode = max77775_get_pd_support(usbc_data); + typec_set_pwr_opmode(usbc_data->port, mode); +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + if (mode == TYPEC_PWR_MODE_PD) + send_otg_notify(o_notify, NOTIFY_EVENT_PD_CONTRACT, 1); + else + send_otg_notify(o_notify, NOTIFY_EVENT_PD_CONTRACT, 0); +#endif + + if (usbc_data->pd_data->cc_status == CC_SNK && psrdy_received) { + max77775_check_pdo(usbc_data); + usbc_data->pd_data->psrdy_received = true; + usbc_data->pd_data->src_cap_done = CC_SNK; + } + + if (psrdy_received && usbc_data->pd_data->cc_status != CC_NO_CONN) { + if (usbc_data->pd_data->cc_status == CC_SRC) { + if (usbc_data->pd_data->src_cap_done != CC_SRC) { + cancel_delayed_work(&usbc_data->pd_data->d2d_work); + /* send the PD message after 1000ms. */ + queue_delayed_work(usbc_data->pd_data->wqueue, + &usbc_data->pd_data->d2d_work, msecs_to_jiffies(1000)); + } + } + usbc_data->pn_flag = true; + complete(&usbc_data->psrdy_wait); + } + + msg_maxim("OUT"); + return IRQ_HANDLED; +} + +bool max77775_sec_pps_control(int en) +{ + struct max77775_usbc_platform_data *pusbpd = g_usbc_data; + union power_supply_propval val = {0,}; + + msg_maxim(": %d", en); + + val.intval = en; /* 0: stop pps, 1: start pps */ + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_DIRECT_SEND_UVDM, val); + if (!en && !pusbpd->pn_flag) { + reinit_completion(&pusbpd->psrdy_wait); + if (!wait_for_completion_timeout(&pusbpd->psrdy_wait, msecs_to_jiffies(1000))) { + msg_maxim("PSRDY COMPLETION TIMEOUT"); + return false; + } + } + return true; +} + +static void max77775_datarole_irq_handler(void *data, int irq) +{ + struct max77775_usbc_platform_data *usbc_data = data; + struct max77775_pd_data *pd_data = usbc_data->pd_data; + u8 datarole = 0; + + max77775_read_reg(usbc_data->muic, REG_PD_STATUS2, &pd_data->pd_status2); + datarole = (pd_data->pd_status2 & BIT_PD_DataRole) + >> FFS(BIT_PD_DataRole); + /* abnormal data role without setting power role */ + if (usbc_data->cc_data->current_pr == 0xFF) { + msg_maxim("INVALID IRQ IRQ(%d)_OUT", irq); + return; + } + + if (irq == CCIC_IRQ_INIT_DETECT) { + if (usbc_data->pd_data->cc_status == CC_SNK) + msg_maxim("initial time : SNK"); + else + return; + } + + switch (datarole) { + case UFP: + if (pd_data->current_dr != UFP) { + pd_data->previous_dr = pd_data->current_dr; + pd_data->current_dr = UFP; + if (pd_data->previous_dr != 0xFF) + msg_maxim("%s detach previous usb connection\n", __func__); + max77775_notify_dr_status(usbc_data, 1); + if (usbc_data->typec_try_state_change == TRY_ROLE_SWAP_DR || + usbc_data->typec_try_state_change == TRY_ROLE_SWAP_TYPE) { + msg_maxim("typec_reverse_completion"); + usbc_data->typec_try_state_change = TRY_ROLE_SWAP_NONE; + complete(&usbc_data->typec_reverse_completion); + } + } + msg_maxim(" UFP"); + break; + + case DFP: + if (pd_data->current_dr != DFP) { + pd_data->previous_dr = pd_data->current_dr; + pd_data->current_dr = DFP; + if (pd_data->previous_dr != 0xFF) + msg_maxim("%s detach previous usb connection\n", __func__); + + max77775_notify_dr_status(usbc_data, 1); + if (usbc_data->typec_try_state_change == TRY_ROLE_SWAP_DR || + usbc_data->typec_try_state_change == TRY_ROLE_SWAP_TYPE) { + msg_maxim("typec_reverse_completion"); + usbc_data->typec_try_state_change = TRY_ROLE_SWAP_NONE; + complete(&usbc_data->typec_reverse_completion); + } + +#ifdef CONFIG_MAX77775_CCIC_ALTERNATE_MODE + if (usbc_data->cc_data->current_pr == SNK && !(usbc_data->is_first_booting)) + msg_maxim("SEND THE IDENTITY REQUEST FROM DFP HANDLER"); +#endif + } + msg_maxim(" DFP"); + break; + + default: + msg_maxim(" DATAROLE(Never Call this routine)"); + break; + } +} + +static irqreturn_t max77775_datarole_irq(int irq, void *data) +{ + pr_debug("%s: IRQ(%d)_IN\n", __func__, irq); + max77775_datarole_irq_handler(data, irq); + pr_debug("%s: IRQ(%d)_OUT\n", __func__, irq); + return IRQ_HANDLED; +} + +static irqreturn_t max77775_ssacc_irq(int irq, void *data) +{ + struct max77775_usbc_platform_data *usbc_data = data; + struct max77775_pd_data *pd_data = usbc_data->pd_data; + + pr_debug("%s: IRQ(%d)_IN\n", __func__, irq); + msg_maxim(" SSAcc command received"); + /* Read through Opcode command 0x50 */ + pd_data->ssacc = 1; + pr_debug("%s: IRQ(%d)_OUT\n", __func__, irq); + return IRQ_HANDLED; +} + +static void max77775_check_cc_sbu_short(void *data) +{ + u8 cc_status2 = 0; + + struct max77775_usbc_platform_data *usbc_data = data; + struct max77775_pd_data *pd_data = usbc_data->pd_data; + + max77775_read_reg(usbc_data->muic, REG_CC_STATUS2, &cc_status2); + /* 0b01: CC-5V, 0b10: SBU-5V, 0b11: SBU-GND Short */ + cc_status2 = (cc_status2 & BIT_CCSBUSHORT) >> FFS(BIT_CCSBUSHORT); + if (cc_status2) + pd_data->cc_sbu_short = true; + + msg_maxim("%s cc_status2 : %x, cc_sbu_short : %d", __func__, cc_status2, pd_data->cc_sbu_short); +} + +static u8 max77775_check_rid(void *data) +{ + u8 fct_id = 0; + struct max77775_usbc_platform_data *usbc_data = data; + struct max77775_pd_data *pd_data = usbc_data->pd_data; + + max77775_read_reg(usbc_data->muic, REG_PD_STATUS2, &pd_data->pd_status2); + fct_id = (pd_data->pd_status2 & BIT_FCT_ID) >> FFS(BIT_FCT_ID); +#if defined(CONFIG_SEC_FACTORY) + factory_execute_monitor(FAC_ABNORMAL_REPEAT_RID); +#endif + max77775_pd_rid(usbc_data, fct_id); + pd_data->fct_id = fct_id; + msg_maxim("%s rid : %d, fct_id : %d", __func__, usbc_data->cur_rid, fct_id); + return fct_id; +} + +static irqreturn_t max77775_fctid_irq(int irq, void *data) +{ + pr_debug("%s: IRQ(%d)_IN\n", __func__, irq); + max77775_check_rid(data); + pr_debug("%s: IRQ(%d)_OUT\n", __func__, irq); + return IRQ_HANDLED; +} + +void set_src_pdo_data(usbc_cmd_data *value, + int pdo_n, int p_type, int curr) +{ + value->opcode = OPCODE_SET_SRCCAP; + value->write_data[1 + (pdo_n * 4)] = (u8)(curr / 10); + + if (p_type == VPDO_TYPE) { + /* 7~9v vpdo */ + value->write_data[2 + (pdo_n * 4)] = 0x30; + value->write_data[3 + (pdo_n * 4)] = 0x42; + value->write_data[4 + (pdo_n * 4)] = 0x8B; + } else { + /* 5v fpdo */ + value->write_data[2 + (pdo_n * 4)] = 0x90; + value->write_data[3 + (pdo_n * 4)] = 0x01; + value->write_data[4 + (pdo_n * 4)] = 0x36; + } +} + +void set_varible_pdo_data(usbc_cmd_data *value, int auth_t, int d2d_t) +{ + value->opcode = OPCODE_SET_SRCCAP; + if ((d2d_t == D2D_SRCSNK) && + (auth_t == AUTH_HIGH_PWR)) { + value->write_data[0] = 0x2; + // 0x36019032, //5V, 500mA + // 0x36019064, //5V, 1 A + // 0x3602D0C8, //9V, 2 A + set_src_pdo_data(value, 0, FPDO_TYPE, 500); + // 0x8B4230AA, //Variable :7V~9V Max15W + set_src_pdo_data(value, 1, VPDO_TYPE, 1650); + value->write_length = 12; + value->read_length = 1; + } else if ((d2d_t == D2D_SNKONLY) && + (auth_t == AUTH_HIGH_PWR)) { + value->write_data[0] = 0x1; + // 0x36019096, //5V, 1.5A + set_src_pdo_data(value, 0, FPDO_TYPE, 1500); + value->write_length = 7; + value->read_length = 1; + } else { + value->write_data[0] = 0x1; + // 0x36019032, //5V, 500mA + set_src_pdo_data(value, 0, FPDO_TYPE, 500); + value->write_length = 7; + value->read_length = 1; + } +} + +void max77775_set_fpdo_srccap(usbc_cmd_data *value, int max_cur) +{ + value->write_data[0] = 0x1; + set_src_pdo_data(value, 0, FPDO_TYPE, max_cur); + value->write_length = 7; + value->read_length = 1; +} + +void max77775_forced_change_srccap(int max_cur) +{ + usbc_cmd_data value; + + init_usbc_cmd_data(&value); + + max77775_set_fpdo_srccap(&value, max_cur); + max77775_usbc_opcode_write(g_usbc_data, &value); + + pr_info("%s : write => OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d)\n", + __func__, value.opcode, value.write_length, value.read_length); +} + +static void max77775_send_new_src_cap(struct max77775_usbc_platform_data *pusbpd, + int auth, int d2d_type) +{ + usbc_cmd_data value; + init_usbc_cmd_data(&value); + set_varible_pdo_data(&value, auth, d2d_type); + max77775_usbc_opcode_write(pusbpd, &value); + + md75_info_usb("%s : write => OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d)\n", + __func__, value.opcode, value.write_length, value.read_length); +} + +void max77775_send_new_src_cap_push(struct max77775_usbc_platform_data *pusbpd, + int auth, int d2d_type) +{ + usbc_cmd_data value; + init_usbc_cmd_data(&value); + set_varible_pdo_data(&value, auth, d2d_type); + max77775_usbc_opcode_push(pusbpd, &value); + + md75_info_usb("%s : push => OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d)\n", + __func__, value.opcode, value.write_length, value.read_length); +} + +static void max77775_send_srcap_work(struct work_struct *work) +{ + struct max77775_pd_data *pd_data = g_usbc_data->pd_data; + int auth = pd_data->auth_type; + int d2d_type = pd_data->d2d_type; + + if ((pd_data->src_cap_done != CC_SRC) && + (auth == AUTH_HIGH_PWR && d2d_type != D2D_NONE)) { + max77775_send_new_src_cap(g_usbc_data, auth, d2d_type); + pd_data->src_cap_done = CC_SRC; + md75_info_usb("%s\n", __func__); + } else { + md75_info_usb("%s Donot Send the new SRC_CAP\n", __func__); + } +} + +void max77775_vpdo_auth(int auth, int d2d_type) +{ + struct max77775_pd_data *pd_data = g_usbc_data->pd_data; + + if (d2d_type == D2D_NONE) + return; + + if (pd_data->cc_status == CC_SRC) { + if (((pd_data->auth_type == AUTH_HIGH_PWR) && (auth == AUTH_LOW_PWR)) || + ((pd_data->auth_type == AUTH_LOW_PWR) && (auth == AUTH_HIGH_PWR))) { + max77775_send_new_src_cap(g_usbc_data, auth, d2d_type); + pd_data->src_cap_done = CC_SRC; + md75_info_usb("%s: change src %s -> %s\n", __func__, + (auth == AUTH_LOW_PWR) ? "HIGH PWR" : "LOW PWR", + (auth == AUTH_LOW_PWR) ? "LOW PWR" : "HIGH PWR"); + } + } else if ((pd_data->cc_status == CC_SNK) && + (auth == AUTH_HIGH_PWR)) { + md75_info_usb("%s: preset vpdo auth for prswap snk to src\n", __func__); + } + + /* set default src cap for detach or hard reset case */ + if (pd_data->cc_status != CC_SNK) { + if ((pd_data->auth_type == AUTH_HIGH_PWR) && (auth == AUTH_NONE)) { + max77775_send_new_src_cap(g_usbc_data, auth, d2d_type); + md75_info_usb("%s: set to default src cap\n", __func__); + } + } + + md75_info_usb("%s: vpdo auth set (%d, %d)\n", __func__, auth, d2d_type); + pd_data->auth_type = auth; + pd_data->d2d_type = d2d_type; +} + +static void max77775_check_enter_mode(void *data) +{ + u8 pd_status1 = 0, enter_mode = 0; + int ret; + usbc_cmd_data value; + struct max77775_usbc_platform_data *usbc_data = data; + + init_usbc_cmd_data(&value); + + ret = max77775_read_reg(usbc_data->muic, REG_PD_STATUS1, &pd_status1); + + if (ret) { + md75_err_usb("%s fail to read REG_PD_STATUS1 reg\n", __func__); + return; + } + + /* 0b01: Enter Mode : 0b00 : Not Enter mode */ + enter_mode = (pd_status1 & BIT_PD_ENTER_MODE) >> FFS(BIT_PD_ENTER_MODE); + if (enter_mode) { + value.opcode = OPCODE_SAMSUNG_READ_MESSAGE; + value.write_data[0] = 0x03; + value.write_length = 1; + value.read_length = 32; + max77775_usbc_opcode_write(usbc_data, &value); + } + msg_maxim("%s pd_status1 : %x, enter_mode : %d", __func__, pd_status1, enter_mode); +} + +int max77775_pd_init(struct max77775_usbc_platform_data *usbc_data) +{ + struct max77775_pd_data *pd_data = usbc_data->pd_data; + int ret = 0; + + msg_maxim(" IN(%d)", pd_data->pd_noti.sink_status.rp_currentlvl); + + /* skip below codes for detecting incomplete connection cable. */ + /* pd_data->pd_noti.sink_status.rp_currentlvl = RP_CURRENT_LEVEL_NONE; */ + pd_data->pd_noti.sink_status.available_pdo_num = 0; + pd_data->pd_noti.sink_status.selected_pdo_num = 0; + pd_data->pd_noti.sink_status.current_pdo_num = 0; + pd_data->pd_noti.sink_status.pps_voltage = 0; + pd_data->pd_noti.sink_status.pps_current = 0; + pd_data->pd_noti.sink_status.has_apdo = false; + pd_data->pd_noti.sink_status.fp_sec_pd_select_pdo = max77775_select_pdo; + pd_data->pd_noti.sink_status.fp_sec_pd_select_pps = max77775_select_pps; + pd_data->pd_noti.sink_status.fp_sec_pd_vpdo_auth = max77775_vpdo_auth; + pd_data->pd_noti.sink_status.fp_sec_pd_manual_ccopen_req = max77775_pdic_manual_ccopen_request; + pd_data->pd_noti.sink_status.fp_sec_pd_change_src = max77775_forced_change_srccap; + + /* skip below codes for detecting incomplete connection cable. */ + /* pd_data->pd_noti.event = PDIC_NOTIFY_EVENT_DETACH; */ + pd_data->pdo_list = false; + pd_data->psrdy_received = false; + pd_data->cc_sbu_short = false; + pd_data->device = DEV_FCT_OPEN; + + pd_data->wqueue = create_singlethread_workqueue("max77775_pd"); + if (!pd_data->wqueue) { + md75_err_usb("%s: Fail to Create Workqueue\n", __func__); + goto err_irq; + } + + INIT_DELAYED_WORK(&pd_data->d2d_work, max77775_send_srcap_work); + INIT_DELAYED_WORK(&pd_data->retry_work, max77775_pd_retry_work); + INIT_DELAYED_WORK(&pd_data->abnormal_pdo_work, max77775_abnormal_pdo_work); + + pd_data->irq_pdmsg = usbc_data->irq_base + MAX77775_PD_IRQ_PDMSG_INT; + if (pd_data->irq_pdmsg) { + ret = request_threaded_irq(pd_data->irq_pdmsg, + NULL, max77775_pdmsg_irq, + 0, + "pd-pdmsg-irq", usbc_data); + if (ret) { + md75_err_usb("%s: Failed to Request IRQ (%d)\n", __func__, ret); + goto err_irq; + } + } + + pd_data->irq_psrdy = usbc_data->irq_base + MAX77775_PD_IRQ_PS_RDY_INT; + if (pd_data->irq_psrdy) { + ret = request_threaded_irq(pd_data->irq_psrdy, + NULL, max77775_psrdy_irq, + 0, + "pd-psrdy-irq", usbc_data); + if (ret) { + md75_err_usb("%s: Failed to Request IRQ (%d)\n", __func__, ret); + goto err_irq; + } + } + + pd_data->irq_datarole = usbc_data->irq_base + MAX77775_PD_IRQ_DATAROLE_INT; + if (pd_data->irq_datarole) { + ret = request_threaded_irq(pd_data->irq_datarole, + NULL, max77775_datarole_irq, + 0, + "pd-datarole-irq", usbc_data); + if (ret) { + md75_err_usb("%s: Failed to Request IRQ (%d)\n", __func__, ret); + goto err_irq; + } + } + + pd_data->irq_ssacc = usbc_data->irq_base + MAX77775_PD_IRQ_SSACCI_INT; + if (pd_data->irq_ssacc) { + ret = request_threaded_irq(pd_data->irq_ssacc, + NULL, max77775_ssacc_irq, + 0, + "pd-ssacci-irq", usbc_data); + if (ret) { + md75_err_usb("%s: Failed to Request IRQ (%d)\n", __func__, ret); + goto err_irq; + } + } + pd_data->irq_fct_id = usbc_data->irq_base + MAX77775_PD_IRQ_FCTIDI_INT; + if (pd_data->irq_fct_id) { + ret = request_threaded_irq(pd_data->irq_fct_id, + NULL, max77775_fctid_irq, + 0, + "pd-fctid-irq", usbc_data); + if (ret) { + md75_err_usb("%s: Failed to Request IRQ (%d)\n", __func__, ret); + goto err_irq; + } + } + + max77775_set_fw_noautoibus(MAX77775_AUTOIBUS_AT_OFF); + max77775_set_snkcap(usbc_data); + /* check CC Pin state for cable attach booting scenario */ + max77775_datarole_irq_handler(usbc_data, CCIC_IRQ_INIT_DETECT); + max77775_check_cc_sbu_short(usbc_data); + + /* check RID value for booting time */ + max77775_check_rid(usbc_data); + + max77775_check_enter_mode(usbc_data); + max77775_register_pdmsg_func(usbc_data->max77775, + max77775_pd_check_pdmsg_callback, (void *)usbc_data); + + msg_maxim(" OUT(%d)", pd_data->pd_noti.sink_status.rp_currentlvl); + return 0; + +err_irq: + kfree(pd_data); + return ret; +} diff --git a/drivers/usb/typec/maxim/max77775_usbc.c b/drivers/usb/typec/maxim/max77775_usbc.c new file mode 100755 index 000000000000..3506ed2e1fc1 --- /dev/null +++ b/drivers/usb/typec/maxim/max77775_usbc.c @@ -0,0 +1,4296 @@ +/* + * Copyrights (C) 2017 Samsung Electronics, Inc. + * Copyrights (C) 2017 Maxim Integrated Products, 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. + */ + +#include +#include +#include +#ifdef CONFIG_OF +#include +#endif +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) +#include +#endif +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) +#include +#include +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#ifdef MAX77775_SYS_FW_UPDATE +#include +#if IS_ENABLED(CONFIG_SPU_VERIFY) +#include +#endif +#endif +#include +#if IS_ENABLED(CONFIG_SEC_ABC) +#include +#endif + +static enum pdic_sysfs_property max77775_sysfs_properties[] = { + PDIC_SYSFS_PROP_CHIP_NAME, + PDIC_SYSFS_PROP_CUR_VERSION, + PDIC_SYSFS_PROP_SRC_VERSION, + PDIC_SYSFS_PROP_LPM_MODE, + PDIC_SYSFS_PROP_STATE, + PDIC_SYSFS_PROP_RID, + PDIC_SYSFS_PROP_CTRL_OPTION, + PDIC_SYSFS_PROP_BOOTING_DRY, + PDIC_SYSFS_PROP_FW_UPDATE, + PDIC_SYSFS_PROP_FW_UPDATE_STATUS, + PDIC_SYSFS_PROP_FW_WATER, + PDIC_SYSFS_PROP_DEX_FAN_UVDM, + PDIC_SYSFS_PROP_ACC_DEVICE_VERSION, + PDIC_SYSFS_PROP_DEBUG_OPCODE, + PDIC_SYSFS_PROP_CONTROL_GPIO, + PDIC_SYSFS_PROP_USBPD_IDS, + PDIC_SYSFS_PROP_USBPD_TYPE, + PDIC_SYSFS_PROP_CC_PIN_STATUS, + PDIC_SYSFS_PROP_RAM_TEST, + PDIC_SYSFS_PROP_SBU_ADC, + PDIC_SYSFS_PROP_CC_ADC, + PDIC_SYSFS_PROP_VSAFE0V_STATUS, + PDIC_SYSFS_PROP_OVP_IC_SHUTDOWN, + PDIC_SYSFS_PROP_HMD_POWER, +#if defined(CONFIG_SEC_FACTORY) + PDIC_SYSFS_PROP_15MODE_WATERTEST_TYPE, +#endif + PDIC_SYSFS_PROP_MAX_COUNT, +}; +#define DRIVER_VER "1.2VER" + +#define MAX77775_IRQSRC_CHG (1 << 0) +#define MAX77775_IRQSRC_FG (1 << 2) +#define MAX77775_IRQSRC_MUIC (1 << 3) + +struct max77775_usbc_platform_data *g_usbc_data; + +#ifdef MAX77775_SYS_FW_UPDATE +#define MAXIM_DEFAULT_FW "secure_max77775.bin" +#define MAXIM_SPU_FW "/pdic/pdic_fw.bin" + +struct pdic_fw_update { + char id[10]; + char path[50]; + int need_verfy; + int enforce_do; +}; +#endif + +#define OPCODE_RESET_COUNT 5 + +static void max77775_usbc_mask_irq(struct max77775_usbc_platform_data *usbc_data); +static void max77775_usbc_umask_irq(struct max77775_usbc_platform_data *usbc_data); +static void max77775_get_version_info(struct max77775_usbc_platform_data *usbc_data); +static void max77775_reset_ic(struct max77775_usbc_platform_data *usbc_data); +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) +static void max77775_usbpd_sbu_switch_control(void *data, int on); +#endif + +int max77775_current_pr_state(struct max77775_usbc_platform_data *usbc_data) +{ + int current_pr = usbc_data->cc_data->current_pr; + return current_pr; + +} + +void max77775_blocking_auto_vbus_control(int enable) +{ + int current_pr = 0; + + msg_maxim("disable : %d", enable); + + if (enable) { + current_pr = max77775_current_pr_state(g_usbc_data); + switch (current_pr) { + case SRC: + /* turn off the vbus */ + max77775_vbus_turn_on_ctrl(g_usbc_data, OFF, false); + break; + default: + break; + } + g_usbc_data->mpsm_mode = MPSM_ON; + } else { + current_pr = max77775_current_pr_state(g_usbc_data); + switch (current_pr) { + case SRC: + max77775_vbus_turn_on_ctrl(g_usbc_data, ON, false); + break; + default: + break; + + } + g_usbc_data->mpsm_mode = MPSM_OFF; + } + msg_maxim("current_pr : %x disable : %x", current_pr, enable); +} +EXPORT_SYMBOL(max77775_blocking_auto_vbus_control); + +static void vbus_control_hard_reset(struct work_struct *work) +{ + struct max77775_usbc_platform_data *usbpd_data = g_usbc_data; + + msg_maxim("current_pr=%d", usbpd_data->cc_data->current_pr); + + if (usbpd_data->cc_data->current_pr == SRC) + max77775_vbus_turn_on_ctrl(usbpd_data, ON, false); +} + +void max77775_usbc_enable_auto_vbus(struct max77775_usbc_platform_data *usbc_data) +{ + usbc_cmd_data write_data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_SAMSUNG_FACTORY_TEST; + write_data.write_data[0] = 0x2; + write_data.write_length = 0x1; + write_data.read_length = 0x1; + max77775_usbc_opcode_write(usbc_data, &write_data); + msg_maxim("TURN ON THE AUTO VBUS"); + usbc_data->auto_vbus_en = true; +} + +void max77775_usbc_disable_auto_vbus(struct max77775_usbc_platform_data *usbc_data) +{ + usbc_cmd_data write_data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_SAMSUNG_FACTORY_TEST; + write_data.write_data[0] = 0x0; + write_data.write_length = 0x1; + write_data.read_length = 0x1; + max77775_usbc_opcode_write(usbc_data, &write_data); + msg_maxim("TURN OFF THE AUTO VBUS"); + usbc_data->auto_vbus_en = false; +} + +void max77775_usbc_enable_audio(struct max77775_usbc_platform_data *usbc_data) +{ + usbc_cmd_data write_data; + + /* we need new function for BIT_CCSrcDbgEn */ + usbc_data->op_ctrl1_w |= (BIT_CCVcnEn | BIT_CCTrySnkEn | BIT_CCSrcDbgEn | BIT_CCAudEn); + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_CCCTRL1_W; + write_data.write_data[0] = usbc_data->op_ctrl1_w; + write_data.write_length = 0x1; + write_data.read_length = 0x1; + max77775_usbc_opcode_write(usbc_data, &write_data); + msg_maxim("Enable Audio Detect(0x%02X)", usbc_data->op_ctrl1_w); +} + +static void max77775_send_role_swap_message(struct max77775_usbc_platform_data *usbpd_data, u8 mode) +{ + usbc_cmd_data write_data; + + max77775_usbc_clear_queue(usbpd_data); + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_SWAP_REQUEST; + /* 0x1 : DR_SWAP, 0x2 : PR_SWAP, 0x4: Manual Role Swap */ + write_data.write_data[0] = mode; + write_data.write_length = 0x1; + write_data.read_length = 0x1; + max77775_usbc_opcode_write(usbpd_data, &write_data); +} + +void max77775_rprd_mode_change(struct max77775_usbc_platform_data *usbpd_data, u8 mode) +{ + msg_maxim("mode = 0x%x", mode); + + switch (mode) { + case TYPE_C_ATTACH_DFP: + case TYPE_C_ATTACH_UFP: + max77775_send_role_swap_message(usbpd_data, MANUAL_ROLE_SWAP); + msleep(1000); + break; + default: + break; + }; +} + +void max77775_power_role_change(struct max77775_usbc_platform_data *usbpd_data, int power_role) +{ + msg_maxim("power_role = 0x%x", power_role); + + switch (power_role) { + case TYPE_C_ATTACH_SRC: + case TYPE_C_ATTACH_SNK: + max77775_send_role_swap_message(usbpd_data, POWER_ROLE_SWAP); + break; + }; +} + +void max77775_data_role_change(struct max77775_usbc_platform_data *usbpd_data, int data_role) +{ + msg_maxim("data_role = 0x%x", data_role); + + switch (data_role) { + case TYPE_C_ATTACH_DFP: + case TYPE_C_ATTACH_UFP: + max77775_send_role_swap_message(usbpd_data, DATA_ROLE_SWAP); + break; + }; +} + +static int max77775_dr_set(struct typec_port *port, enum typec_data_role role) +{ + struct max77775_usbc_platform_data *usbpd_data = typec_get_drvdata(port); +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif /* CONFIG_USB_HW_PARAM */ + + if (!usbpd_data) + return -EINVAL; + msg_maxim("typec_power_role=%d, typec_data_role=%d, role=%d", + usbpd_data->typec_power_role, usbpd_data->typec_data_role, role); + + if (usbpd_data->typec_data_role != TYPEC_DEVICE + && usbpd_data->typec_data_role != TYPEC_HOST) + return -EPERM; + else if (usbpd_data->typec_data_role == role) + return -EPERM; + + reinit_completion(&usbpd_data->typec_reverse_completion); + if (role == TYPEC_DEVICE) { + msg_maxim("try reversing, from DFP to UFP"); + usbpd_data->typec_try_state_change = TRY_ROLE_SWAP_DR; + max77775_data_role_change(usbpd_data, TYPE_C_ATTACH_UFP); + } else if (role == TYPEC_HOST) { + msg_maxim("try reversing, from UFP to DFP"); + usbpd_data->typec_try_state_change = TRY_ROLE_SWAP_DR; + max77775_data_role_change(usbpd_data, TYPE_C_ATTACH_DFP); + } else { + msg_maxim("invalid typec_role"); + return -EIO; + } + if (!wait_for_completion_timeout(&usbpd_data->typec_reverse_completion, + msecs_to_jiffies(TRY_ROLE_SWAP_WAIT_MS))) { + usbpd_data->typec_try_state_change = TRY_ROLE_SWAP_NONE; + return -ETIMEDOUT; + } +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_DR_SWAP_COUNT); +#endif /* CONFIG_USB_HW_PARAM */ + return 0; +} + +static int max77775_pr_set(struct typec_port *port, enum typec_role role) +{ + struct max77775_usbc_platform_data *usbpd_data = typec_get_drvdata(port); +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif /* CONFIG_USB_HW_PARAM */ + + if (!usbpd_data) + return -EINVAL; + + msg_maxim("typec_power_role=%d, typec_data_role=%d, role=%d", + usbpd_data->typec_power_role, usbpd_data->typec_data_role, role); + + if (usbpd_data->typec_power_role != TYPEC_SINK + && usbpd_data->typec_power_role != TYPEC_SOURCE) + return -EPERM; + else if (usbpd_data->typec_power_role == role) + return -EPERM; + + reinit_completion(&usbpd_data->typec_reverse_completion); + if (role == TYPEC_SINK) { + msg_maxim("try reversing, from Source to Sink"); + usbpd_data->typec_try_state_change = TRY_ROLE_SWAP_PR; + max77775_power_role_change(usbpd_data, TYPE_C_ATTACH_SNK); + } else if (role == TYPEC_SOURCE) { + msg_maxim("try reversing, from Sink to Source"); + usbpd_data->typec_try_state_change = TRY_ROLE_SWAP_PR; + max77775_power_role_change(usbpd_data, TYPE_C_ATTACH_SRC); + } else { + msg_maxim("invalid typec_role"); + return -EIO; + } + if (!wait_for_completion_timeout(&usbpd_data->typec_reverse_completion, + msecs_to_jiffies(TRY_ROLE_SWAP_WAIT_MS))) { + usbpd_data->typec_try_state_change = TRY_ROLE_SWAP_NONE; + if (usbpd_data->typec_power_role != role) + return -ETIMEDOUT; + } +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_PR_SWAP_COUNT); +#endif + return 0; +} + +static int max77775_port_type_set(struct typec_port *port, enum typec_port_type port_type) +{ + struct max77775_usbc_platform_data *usbpd_data = typec_get_drvdata(port); + + if (!usbpd_data) + return -EINVAL; + + msg_maxim("typec_power_role=%d, typec_data_role=%d, port_type=%d", + usbpd_data->typec_power_role, usbpd_data->typec_data_role, port_type); + + reinit_completion(&usbpd_data->typec_reverse_completion); + if (port_type == TYPEC_PORT_DFP) { + msg_maxim("try reversing, from UFP(Sink) to DFP(Source)"); + usbpd_data->typec_try_state_change = TRY_ROLE_SWAP_TYPE; + max77775_rprd_mode_change(usbpd_data, TYPE_C_ATTACH_DFP); + } else if (port_type == TYPEC_PORT_UFP) { + msg_maxim("try reversing, from DFP(Source) to UFP(Sink)"); +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + max77775_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_MUIC, PDIC_NOTIFY_ID_ATTACH, + 0/*attach*/, 0/*rprd*/, 0); +#endif + usbpd_data->typec_try_state_change = TRY_ROLE_SWAP_TYPE; + max77775_rprd_mode_change(usbpd_data, TYPE_C_ATTACH_UFP); + } else { + msg_maxim("invalid typec_role"); + return 0; + } + + if (!wait_for_completion_timeout(&usbpd_data->typec_reverse_completion, + msecs_to_jiffies(TRY_ROLE_SWAP_WAIT_MS))) { + usbpd_data->typec_try_state_change = TRY_ROLE_SWAP_NONE; + return -ETIMEDOUT; + } + return 0; +} + +static const struct typec_operations max77775_ops = { + .dr_set = max77775_dr_set, + .pr_set = max77775_pr_set, + .port_type_set = max77775_port_type_set +}; + +int max77775_get_pd_support(struct max77775_usbc_platform_data *usbc_data) +{ + bool support_pd_role_swap = false; + struct device_node *np = NULL; + + np = of_find_compatible_node(NULL, NULL, "maxim,max77775_pdic"); + + if (np) + support_pd_role_swap = of_property_read_bool(np, "support_pd_role_swap"); + else + msg_maxim("np is null"); + + msg_maxim("TYPEC_CLASS: support_pd_role_swap is %d, usbc_data->pd_support : %d", + support_pd_role_swap, usbc_data->pd_support); + + if (support_pd_role_swap && usbc_data->pd_support) + return TYPEC_PWR_MODE_PD; + + return usbc_data->pwr_opmode; +} + +#if defined(MAX77775_SYS_FW_UPDATE) +static int max77775_firmware_update_sys(struct max77775_usbc_platform_data *data, int fw_dir) +{ + struct max77775_usbc_platform_data *usbc_data = data; + max77775_fw_header *fw_header; + const struct firmware *fw_entry; + int fw_size, ret = 0; +#if IS_ENABLED(CONFIG_SPU_VERIFY) + long fw_verify_size; +#endif + struct pdic_fw_update fwup[FWUP_CMD_MAX] = { + {"BUILT_IN", "", 0, 1}, + {"UMS", MAXIM_DEFAULT_FW, 0, 1}, + {"SPU", MAXIM_SPU_FW, 1, 0}, + {"SPU_V", MAXIM_SPU_FW, 1, 0} + }; + + if (!usbc_data) { + msg_maxim("usbc_data is null!!"); + return -ENODEV; + } + + switch (fw_dir) { +#if IS_ENABLED(CONFIG_SPU_VERIFY) + case SPU: + case SPU_VERIFICATION: + break; +#endif + case BUILT_IN: + max77775_usbc_fw_setting(usbc_data->max77775, fwup[fw_dir].enforce_do); + return 0; + default: + return -EINVAL; + } + + ret = request_firmware(&fw_entry, fwup[fw_dir].path, usbc_data->dev); + if (ret) { + md75_info_usb("%s: firmware is not available %d\n", __func__, ret); + return ret; + } + + fw_size = (int)fw_entry->size; + fw_header = (max77775_fw_header *)fw_entry->data; + msg_maxim("Req fw %02X.%02X, length=%lu", fw_header->major, fw_header->minor, fw_entry->size); + +#if IS_ENABLED(CONFIG_SPU_VERIFY) + if (fwup[fw_dir].need_verfy) { + fw_size = (int)fw_entry->size - SPU_METADATA_SIZE(PDIC); + fw_verify_size = spu_firmware_signature_verify("PDIC", fw_entry->data, fw_entry->size); + if (fw_verify_size != fw_size) { + md75_info_usb("%s: signature verify failed, verify_ret:%ld, ori_size:%d\n", + __func__, fw_verify_size, fw_size); + ret = -EPERM; + goto out; + } + if (fw_dir == SPU_VERIFICATION) + goto out; + } +#endif + + switch (usbc_data->max77775->pmic_rev) { + case MAX77775_PASS1: + case MAX77775_PASS2: + case MAX77775_PASS3: + case MAX77775_PASS4: + ret = max77775_usbc_fw_update(usbc_data->max77775, fw_entry->data, + fw_size, fwup[fw_dir].enforce_do); + break; + default: + msg_maxim("FAILED PMIC_REVISION isn't valid (pmic_rev : 0x%x)\n", + usbc_data->max77775->pmic_rev); + break; + } +#if IS_ENABLED(CONFIG_SPU_VERIFY) +out: +#endif + release_firmware(fw_entry); + return ret; +} +#endif + +static int max77775_firmware_update_misc(struct max77775_usbc_platform_data *data, + void *fw_data, size_t fw_size) +{ + struct max77775_usbc_platform_data *usbc_data = data; + max77775_fw_header *fw_header; + int ret = 0; + const u8 *fw_bin; + size_t fw_bin_len; + u8 pmic_rev = 0;/* pmic Rev */ + u8 fw_enable = 0; + + if (!usbc_data) { + msg_maxim("usbc_data is null!!"); + ret = -ENOMEM; + goto out; + } + + pmic_rev = usbc_data->max77775->pmic_rev; + + if (fw_size > 0) { + msg_maxim("start, size %lu Bytes", fw_size); + + fw_bin_len = fw_size; + fw_bin = fw_data; + fw_header = (max77775_fw_header *)fw_bin; + usbc_data->FW_Revision = usbc_data->max77775->FW_Revision; + usbc_data->FW_Minor_Revision = usbc_data->max77775->FW_Minor_Revision; + msg_maxim("chip %02X.%02X, fw %02X.%02X", + usbc_data->FW_Revision, usbc_data->FW_Minor_Revision, + fw_header->major, fw_header->minor); + switch (pmic_rev) { + case MAX77775_PASS1: + case MAX77775_PASS2: + case MAX77775_PASS3: + case MAX77775_PASS4: + fw_enable = 1; + break; + default: + msg_maxim("FAILED F/W via SYS and PMIC_REVISION isn't valid"); + break; + }; + + if (fw_enable) + ret = max77775_usbc_fw_update(usbc_data->max77775, fw_bin, (int)fw_bin_len, 1); + else + msg_maxim("FAILED F/W MISMATCH pmic_rev : 0x%x, fw_header->major : 0x%x", + pmic_rev, fw_header->major); + } +out: + return ret; +} + +void max77775_get_jig_on(struct max77775_usbc_platform_data *usbpd_data, unsigned char *data) +{ + int jigon; + + jigon = data[1]; + msg_maxim("jigon=%s", jigon ? "High" : "Low"); + + complete(&usbpd_data->ccic_sysfs_completion); +} + +void max77775_set_jig_on(struct max77775_usbc_platform_data *usbpd_data, int jigon) +{ + usbc_cmd_data read_data; + usbc_cmd_data write_data; + u8 chgin_dtls_00; + bool chgin_valid = false; + + msg_maxim("jigon=%s", jigon ? "High" : "Low"); + + max77775_read_reg(usbpd_data->max77775->charger, MAX77775_CHG_REG_DETAILS_00, &chgin_dtls_00); + if ((chgin_dtls_00 & 0x60) == 0x60) + chgin_valid = true; + md75_info_usb("%s : chgin_valid = %d (%s)\n", __func__, chgin_dtls_00 & 0x60, + chgin_valid ? "Valid" : "Invalid"); + + if (!chgin_valid) { + md75_err_usb("%s: CHGIN is not valid, return\n", __func__); + return; + } + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_CONTROL_JIG_W; + write_data.write_length = 0x1; + write_data.read_length = 0x0; + if (jigon) + write_data.write_data[0] = 0x1; + else + write_data.write_data[0] = 0x0; + max77775_usbc_opcode_write(usbpd_data, &write_data); + + init_usbc_cmd_data(&read_data); + read_data.opcode = OPCODE_CONTROL_JIG_R; + read_data.write_length = 0x0; + read_data.read_length = 0x01; + max77775_usbc_opcode_read(usbpd_data, &read_data); +} + +#if defined(CONFIG_MAX77775_CCOPEN_AFTER_WATERCABLE) +void max77775_set_moisture_cc_open(struct max77775_usbc_platform_data *usbpd_data) +{ + usbc_cmd_data write_data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_MOISTURE_CC_OPEN; + write_data.write_length = 0x1; + write_data.read_length = 0x0; + write_data.write_data[0] = 0x1; + max77775_usbc_opcode_write(usbpd_data, &write_data); +} +#endif +void max77775_control_option_command(struct max77775_usbc_platform_data *usbpd_data, int cmd) +{ + struct max77775_cc_data *cc_data = usbpd_data->cc_data; + u8 ccstat = 0; + usbc_cmd_data write_data; + + /* for maxim request : they want to check ccstate here */ + max77775_read_reg(usbpd_data->muic, REG_CC_STATUS1, &cc_data->cc_status1); + ccstat = (cc_data->cc_status1 & BIT_CCStat) >> FFS(BIT_CCStat); + msg_maxim("usb: cmd=0x%x ccstat : %d", cmd, ccstat); + + init_usbc_cmd_data(&write_data); + /* 1 : Vconn control option command ON */ + /* 2 : Vconn control option command OFF */ + /* 3 : Water Detect option command ON */ + /* 4 : Water Detect option command OFF */ + if (cmd == 1) + usbpd_data->vconn_test = 0; + else if (cmd == 2) + usbpd_data->vconn_test = 0; /* do nothing */ + else if (cmd == 3) { /* if SBU pin is low, water interrupt is happened. */ + write_data.opcode = OPCODE_SAMSUNG_FACTORY_TEST; + write_data.write_data[0] = 0x3; + write_data.write_length = 0x1; + write_data.read_length = 0x0; + max77775_usbc_opcode_write(usbpd_data, &write_data); + } else if (cmd == 4) { + write_data.opcode = OPCODE_SAMSUNG_FACTORY_TEST; + write_data.write_data[0] = 0x2; + write_data.write_length = 0x1; + write_data.read_length = 0x0; + max77775_usbc_opcode_write(usbpd_data, &write_data); + } + + if ((cmd & 0xF) == 0x3) + usbpd_data->fac_water_enable = 1; + else if ((cmd & 0xF) == 0x4) + usbpd_data->fac_water_enable = 0; +} + +void max77775_response_sbu_read(struct max77775_usbc_platform_data *usbpd_data, unsigned char *data) +{ + usbpd_data->sbu[0] = data[2]; + usbpd_data->sbu[1] = data[3]; + + msg_maxim("opt(%d) SBU1 = 0x%x, SBU2 = 0x%x", + data[1], usbpd_data->sbu[0], usbpd_data->sbu[1]); + complete(&usbpd_data->ccic_sysfs_completion); +} + +void max77775_request_sbu_read(struct max77775_usbc_platform_data *usbpd_data) +{ + usbc_cmd_data write_data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_READ_SBU; + write_data.write_data[0] = 0x0; + write_data.write_length = 0x1; + write_data.read_length = 0x3; + max77775_usbc_opcode_write(usbpd_data, &write_data); +} + +void max77775_response_cc_read(struct max77775_usbc_platform_data *usbpd_data, unsigned char *data) +{ + u8 cc1 = 0, cc2 = 0; + + cc1 = data[1]; + cc2 = data[2]; + + msg_maxim("CC1 = 0x%x, CC2 = 0x%x", cc1, cc2); + + usbpd_data->cc[0] = cc1; + usbpd_data->cc[1] = cc2; + + complete(&usbpd_data->ccic_sysfs_completion); +} + +void max77775_request_cc_read(struct max77775_usbc_platform_data *usbpd_data) +{ + usbc_cmd_data write_data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_READ_CC; + write_data.write_length = 0x0; + write_data.read_length = 0x2; + max77775_usbc_opcode_write(usbpd_data, &write_data); +} + +void max77775_request_selftest_read(struct max77775_usbc_platform_data *usbpd_data) +{ + usbc_cmd_data write_data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_READ_SBU; + write_data.write_length = 0x1; + write_data.write_data[0] = 0x1; + write_data.read_length = 0x3; + max77775_usbc_opcode_write(usbpd_data, &write_data); +} + +void max77775_request_cccontrol4(struct max77775_usbc_platform_data *usbpd_data) +{ + usbc_cmd_data read_data; + + init_usbc_cmd_data(&read_data); + read_data.opcode = OPCODE_CCCTRL4_R; + read_data.write_length = 0x0; + read_data.read_length = 0x1; + max77775_usbc_opcode_read(g_usbc_data, &read_data); +} + +void max77775_set_CCForceError(struct max77775_usbc_platform_data *usbpd_data) +{ + usbc_cmd_data write_data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_CCCTRL3_W; + write_data.write_data[0] = 0x84; + write_data.write_length = 0x1; + write_data.read_length = 0x0; + max77775_usbc_opcode_write(usbpd_data, &write_data); +} + +void max77775_set_lockerroren(struct max77775_usbc_platform_data *usbpd_data, + unsigned char data, u8 en) +{ + usbc_cmd_data write_data; + u8 ccctrl4_reg = data; + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_CCCTRL4_W; + ccctrl4_reg &= ~(0x1 << 4); + write_data.write_data[0] = ccctrl4_reg | ((en & 0x1) << 4); + write_data.write_length = 0x1; + write_data.read_length = 0x0; + max77775_usbc_opcode_write(usbpd_data, &write_data); +} + +void max77775_ccctrl4_read_complete(struct max77775_usbc_platform_data *usbpd_data, + unsigned char *data) +{ + usbpd_data->ccctrl4_reg = data[1]; + complete(&usbpd_data->op_completion); +} + +void max77775_pdic_manual_ccopen_request(int is_on) +{ + struct max77775_usbc_platform_data *usbpd_data = g_usbc_data; + + msg_maxim("is_on %d > %d", usbpd_data->cc_open_req, is_on); + if (usbpd_data->cc_open_req != is_on) { + usbpd_data->cc_open_req = is_on; + schedule_work(&usbpd_data->cc_open_req_work); + } +} +EXPORT_SYMBOL(max77775_pdic_manual_ccopen_request); + +static void max77775_cc_open_work_func( + struct work_struct *work) +{ + struct max77775_usbc_platform_data *usbc_data; + u8 lock_err_en; +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + int event; +#endif + + usbc_data = container_of(work, struct max77775_usbc_platform_data, cc_open_req_work); + msg_maxim("%s", usbc_data->cc_open_req ? "set":"clear"); + + if (usbc_data->cc_open_req) { + reinit_completion(&usbc_data->op_completion); + max77775_request_cccontrol4(usbc_data); + if (!wait_for_completion_timeout(&usbc_data->op_completion, msecs_to_jiffies(1000))) { + msg_maxim("OPCMD COMPLETION TIMEOUT"); + return; + } + lock_err_en = GET_CCCTRL4_LOCK_ERROR_EN(usbc_data->ccctrl4_reg); + msg_maxim("data: 0x%x lock_err_en=%d", usbc_data->ccctrl4_reg, lock_err_en); + if (!lock_err_en) { +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_CCOPEN_REQ_SET; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + max77775_set_lockerroren(usbc_data, usbc_data->ccctrl4_reg, 1); + } + max77775_set_CCForceError(usbc_data); + } else { + reinit_completion(&usbc_data->op_completion); + max77775_request_cccontrol4(usbc_data); + if (!wait_for_completion_timeout(&usbc_data->op_completion, msecs_to_jiffies(1000))) + msg_maxim("OPCMD COMPLETION TIMEOUT"); + + lock_err_en = GET_CCCTRL4_LOCK_ERROR_EN(usbc_data->ccctrl4_reg); + msg_maxim("data: 0x%x lock_err_en=%d", usbc_data->ccctrl4_reg, lock_err_en); + if (lock_err_en) { +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_CCOPEN_REQ_CLEAR; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + max77775_set_lockerroren(usbc_data, usbc_data->ccctrl4_reg, 0); + } + } +} + +static void max77775_dp_configure_work_func( + struct work_struct *work) +{ + struct max77775_usbc_platform_data *usbpd_data; +#if !IS_ENABLED(CONFIG_ARCH_QCOM) || !defined(CONFIG_SEC_FACTORY) + int timeleft = 0; +#endif + + usbpd_data = container_of(work, struct max77775_usbc_platform_data, dp_configure_work); +#if !IS_ENABLED(CONFIG_ARCH_QCOM) || !defined(CONFIG_SEC_FACTORY) + timeleft = wait_event_interruptible_timeout(usbpd_data->device_add_wait_q, + usbpd_data->device_add || usbpd_data->pd_data->cc_status == CC_NO_CONN, HZ/2); + msg_maxim("%s timeleft = %d", __func__, timeleft); +#endif + if (usbpd_data->pd_data->cc_status != CC_NO_CONN) + max77775_ccic_event_work(usbpd_data, PDIC_NOTIFY_DEV_DP, + PDIC_NOTIFY_ID_DP_LINK_CONF, usbpd_data->dp_selected_pin, 0, 0); +} + +#if defined(MAX77775_SYS_FW_UPDATE) +static int max77775_firmware_update_sysfs(struct max77775_usbc_platform_data *usbpd_data, int fw_dir) +{ + int ret = 0; + usbpd_data->fw_update = 1; + max77775_usbc_mask_irq(usbpd_data); + max77775_write_reg(usbpd_data->muic, REG_PD_INT_M, 0xFF); + max77775_write_reg(usbpd_data->muic, REG_CC_INT_M, 0xFF); + max77775_write_reg(usbpd_data->muic, REG_UIC_INT_M, 0xFF); + max77775_write_reg(usbpd_data->muic, REG_VDM_INT_M, 0xFF); + max77775_write_reg(usbpd_data->muic, REG_SPARE_INT_M, 0xFF); + ret = max77775_firmware_update_sys(usbpd_data, fw_dir); + max77775_write_reg(usbpd_data->muic, REG_UIC_INT_M, REG_UIC_INT_M_INIT); + max77775_write_reg(usbpd_data->muic, REG_CC_INT_M, REG_CC_INT_M_INIT); + max77775_write_reg(usbpd_data->muic, REG_PD_INT_M, REG_PD_INT_M_INIT); + max77775_write_reg(usbpd_data->muic, REG_VDM_INT_M, REG_VDM_INT_M_INIT); + max77775_write_reg(usbpd_data->muic, REG_SPARE_INT_M, REG_SPARE_INT_M_INIT); + // max77775_usbc_enable_auto_vbus(usbpd_data); + max77775_set_enable_alternate_mode(ALTERNATE_MODE_START); + max77775_usbc_umask_irq(usbpd_data); + if (ret) + usbpd_data->fw_update = 2; + else + usbpd_data->fw_update = 0; + return ret; +} +#endif + +static int max77775_firmware_update_callback(void *data, + void *fw_data, size_t fw_size) +{ + struct max77775_usbc_platform_data *usbpd_data + = (struct max77775_usbc_platform_data *)data; + int ret = 0; + + usbpd_data->fw_update = 1; + max77775_usbc_mask_irq(usbpd_data); + max77775_write_reg(usbpd_data->muic, REG_PD_INT_M, 0xFF); + max77775_write_reg(usbpd_data->muic, REG_CC_INT_M, 0xFF); + max77775_write_reg(usbpd_data->muic, REG_UIC_INT_M, 0xFF); + max77775_write_reg(usbpd_data->muic, REG_VDM_INT_M, 0xFF); + max77775_write_reg(usbpd_data->muic, REG_SPARE_INT_M, 0xFF); + ret = max77775_firmware_update_misc(usbpd_data, fw_data, fw_size); + max77775_write_reg(usbpd_data->muic, REG_UIC_INT_M, REG_UIC_INT_M_INIT); + max77775_write_reg(usbpd_data->muic, REG_CC_INT_M, REG_CC_INT_M_INIT); + max77775_write_reg(usbpd_data->muic, REG_PD_INT_M, REG_PD_INT_M_INIT); + max77775_write_reg(usbpd_data->muic, REG_VDM_INT_M, REG_VDM_INT_M_INIT); + max77775_write_reg(usbpd_data->muic, REG_SPARE_INT_M, REG_SPARE_INT_M_INIT); + // max77775_usbc_enable_auto_vbus(usbpd_data); + max77775_set_enable_alternate_mode(ALTERNATE_MODE_START); + max77775_usbc_umask_irq(usbpd_data); + if (ret) + usbpd_data->fw_update = 2; + else + usbpd_data->fw_update = 0; + return ret; +} + +static size_t max77775_get_firmware_size(void *data) +{ + struct max77775_usbc_platform_data *usbpd_data + = (struct max77775_usbc_platform_data *)data; + unsigned long ret = 0; + + ret = usbpd_data->max77775->fw_size; + + return ret; +} + +#if defined(MAX77775_SYS_FW_UPDATE) +static void max77775_firmware_update_sysfs_work(struct work_struct *work) +{ + struct max77775_usbc_platform_data *usbpd_data = container_of(work, + struct max77775_usbc_platform_data, fw_update_work); + + max77775_firmware_update_sysfs(usbpd_data, BUILT_IN); +} +#endif + +int max77775_request_vsafe0v_read(struct max77775_usbc_platform_data *usbpd_data) +{ + u8 cc_status2 = 0; + int vsafe0v = 0; + + max77775_read_reg(usbpd_data->muic, REG_CC_STATUS2, &cc_status2); + + vsafe0v = (cc_status2 & BIT_VSAFE0V) >> FFS(BIT_VSAFE0V); + md75_info_usb("%s: ccstatus2: 0x%x %d\n", __func__, cc_status2, vsafe0v); + return vsafe0v; +} + +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) +static int max77775_sysfs_get_local_prop(struct _pdic_data_t *ppdic_data, + enum pdic_sysfs_property prop, + char *buf) +{ + int retval = -ENODEV, i = 0; + u8 cur_major = 0, cur_minor = 0, src_major = 0, src_minor = 0; + u8 cur_pid = 0, src_pid = 0; + struct max77775_usbc_platform_data *usbpd_data = + (struct max77775_usbc_platform_data *)ppdic_data->drv_data; + + if (!usbpd_data) { + msg_maxim("usbpd_data is null : request prop = %d", prop); + return -ENODEV; + } + + switch (prop) { + case PDIC_SYSFS_PROP_CUR_VERSION: + retval = max77775_read_reg(usbpd_data->muic, REG_UIC_FW_REV, &cur_major); + if (retval < 0) { + msg_maxim("Failed to read FW_REV"); + return retval; + } + retval = max77775_read_reg(usbpd_data->muic, REG_UIC_FW_REV2, &cur_minor); + if (retval < 0) { + msg_maxim("Failed to read FW_REV2"); + return retval; + } + retval = max77775_read_reg(usbpd_data->muic, REG_PRODUCT_ID, &cur_pid); + if (retval < 0) { + msg_maxim("Failed to read PRODUCT_ID"); + return retval; + } + retval = sprintf(buf, "%02X.%02X\n", cur_major, cur_minor); + msg_maxim("usb: PDIC_SYSFS_PROP_CUR_VERSION : %02X.%02X (PID%02X)", + cur_major, cur_minor, cur_pid); + break; + case PDIC_SYSFS_PROP_SRC_VERSION: + src_major = usbpd_data->max77775->FW_Revision_bin; + src_minor = usbpd_data->max77775->FW_Minor_Revision_bin; + src_pid = usbpd_data->max77775->FW_Product_ID_bin; + retval = sprintf(buf, "%02X.%02X\n", src_major, src_minor); + msg_maxim("usb: PDIC_SYSFS_PROP_SRC_VERSION : %02X.%02X (PID%02X)", + src_major, src_minor, src_pid); + break; + case PDIC_SYSFS_PROP_LPM_MODE: + retval = sprintf(buf, "%d\n", usbpd_data->manual_lpm_mode); + msg_maxim("usb: PDIC_SYSFS_PROP_LPM_MODE : %d", + usbpd_data->manual_lpm_mode); + break; + case PDIC_SYSFS_PROP_STATE: + retval = sprintf(buf, "%d\n", usbpd_data->pd_state); + msg_maxim("usb: PDIC_SYSFS_PROP_STATE : %d", + usbpd_data->pd_state); + break; + case PDIC_SYSFS_PROP_RID: + retval = sprintf(buf, "%d\n", usbpd_data->cur_rid); + msg_maxim("usb: PDIC_SYSFS_PROP_RID : %d", + usbpd_data->cur_rid); + break; + case PDIC_SYSFS_PROP_BOOTING_DRY: + usbpd_data->sbu[0] = 0; + usbpd_data->sbu[1] = 0; + reinit_completion(&usbpd_data->ccic_sysfs_completion); + max77775_request_selftest_read(usbpd_data); + i = wait_for_completion_timeout(&usbpd_data->ccic_sysfs_completion, msecs_to_jiffies(1000 * 5)); + if (i == 0) + msg_maxim("CCIC SYSFS COMPLETION TIMEOUT"); + msg_maxim("usb: PDIC_SYSFS_PROP_BOOTING_DRY timeout : %d", i); + if (usbpd_data->sbu[0] >= 7 && usbpd_data->sbu[1] >= 7) + retval = sprintf(buf, "%d\n", 1); + else + retval = sprintf(buf, "%d\n", 0); + break; + case PDIC_SYSFS_PROP_FW_UPDATE_STATUS: + + retval = sprintf(buf, "%s\n", usbpd_data->fw_update == 1 ? "UPDATE" : + usbpd_data->fw_update == 2 ? "NG" : "OK"); + msg_maxim("usb: PDIC_SYSFS_PROP_FW_UPDATE_STATUS : %s", + usbpd_data->fw_update == 1 ? "UPDATE" : + usbpd_data->fw_update == 2 ? "NG" : "OK"); + break; + case PDIC_SYSFS_PROP_FW_WATER: + retval = sprintf(buf, "%d\n", usbpd_data->current_connstat == WATER ? 1 : 0); + msg_maxim("usb: PDIC_SYSFS_PROP_FW_WATER : %d", + usbpd_data->current_connstat == WATER ? 1 : 0); + break; + case PDIC_SYSFS_PROP_ACC_DEVICE_VERSION: + retval = sprintf(buf, "%04x\n", usbpd_data->Device_Version); + msg_maxim("usb: PDIC_SYSFS_PROP_ACC_DEVICE_VERSION : %d", + usbpd_data->Device_Version); + break; + case PDIC_SYSFS_PROP_CONTROL_GPIO: + usbpd_data->sbu[0] = 0; + usbpd_data->sbu[1] = 0; + reinit_completion(&usbpd_data->ccic_sysfs_completion); + max77775_request_sbu_read(usbpd_data); + i = wait_for_completion_timeout(&usbpd_data->ccic_sysfs_completion, msecs_to_jiffies(200 * 5)); + if (i == 0) + msg_maxim("CCIC SYSFS COMPLETION TIMEOUT"); + /* compare SBU1, SBU2 values after interrupt */ + msg_maxim("usb: PDIC_SYSFS_PROP_CONTROL_GPIO SBU1 = 0x%x ,SBU2 = 0x%x timeout:%d", + usbpd_data->sbu[0], usbpd_data->sbu[1], i); + /* return value must be 1 or 0 */ + retval = sprintf(buf, "%d %d\n", !!usbpd_data->sbu[0], !!usbpd_data->sbu[1]); + break; + case PDIC_SYSFS_PROP_USBPD_IDS: + retval = sprintf(buf, "%04x:%04x\n", + le16_to_cpu(usbpd_data->Vendor_ID), + le16_to_cpu(usbpd_data->Product_ID)); + msg_maxim("usb: PDIC_SYSFS_USBPD_IDS : %04x:%04x", + le16_to_cpu(usbpd_data->Vendor_ID), le16_to_cpu(usbpd_data->Product_ID)); + break; + case PDIC_SYSFS_PROP_USBPD_TYPE: + retval = sprintf(buf, "%d\n", usbpd_data->acc_type); + msg_maxim("usb: PDIC_SYSFS_USBPD_TYPE : %d", + usbpd_data->acc_type); + break; + case PDIC_SYSFS_PROP_CC_PIN_STATUS: + retval = sprintf(buf, "%d\n", usbpd_data->cc_pin_status); + msg_maxim("usb: PDIC_SYSFS_PROP_PIN_STATUS : %d", + usbpd_data->cc_pin_status); + break; + case PDIC_SYSFS_PROP_SBU_ADC: + usbpd_data->sbu[0] = 0; + usbpd_data->sbu[1] = 0; + reinit_completion(&usbpd_data->ccic_sysfs_completion); + max77775_request_selftest_read(usbpd_data); + i = wait_for_completion_timeout(&usbpd_data->ccic_sysfs_completion, msecs_to_jiffies(1000 * 5)); + if (i == 0) + msg_maxim("CCIC SYSFS COMPLETION TIMEOUT"); + msg_maxim("usb: PDIC_SYSFS_PROP_SBU_ADC : %d %d timeout : %d", + usbpd_data->sbu[0], usbpd_data->sbu[1], i); + retval = sprintf(buf, "%d %d\n", usbpd_data->sbu[0], usbpd_data->sbu[1]); + break; + case PDIC_SYSFS_PROP_CC_ADC: + usbpd_data->cc[0] = 0;usbpd_data->cc[1] = 0; + reinit_completion(&usbpd_data->ccic_sysfs_completion); + max77775_request_cc_read(usbpd_data); + i = wait_for_completion_timeout(&usbpd_data->ccic_sysfs_completion, msecs_to_jiffies(1000 * 5)); + if (i == 0) + msg_maxim("CCIC SYSFS COMPLETION TIMEOUT"); + msg_maxim("usb: PDIC_SYSFS_PROP_CC_ADC : %d %d timeout : %d", + usbpd_data->cc[0], usbpd_data->cc[1], i); + retval = sprintf(buf, "%d %d\n", usbpd_data->cc[0], usbpd_data->cc[1]); + break; + case PDIC_SYSFS_PROP_VSAFE0V_STATUS: + usbpd_data->vsafe0v_status = max77775_request_vsafe0v_read(usbpd_data); + retval = sprintf(buf, "%d\n", usbpd_data->vsafe0v_status); + msg_maxim("usb: PDIC_SYSFS_PROP_VSAFE0V_STATUS : %d", + usbpd_data->vsafe0v_status); + break; +#if defined(CONFIG_SEC_FACTORY) + case PDIC_SYSFS_PROP_15MODE_WATERTEST_TYPE: +#if 0 //use unsupport water pid fw feature (ex CONFIG_MAX77775_FW_PID05_SUPPORT) + retval = sprintf(buf, "unsupport\n"); +#else + retval = sprintf(buf, "uevent\n"); +#endif + md75_info_usb("%s : PDIC_SYSFS_PROP_15MODE_WATERTEST_TYPE : uevent", __func__); + break; +#endif + default: + msg_maxim("prop read not supported prop (%d)", prop); + retval = -ENODATA; + break; + } + + return retval; +} + +/* + * assume that 1 HMD device has name(14),vid(4),pid(4) each, then + * max 32 HMD devices(name,vid,pid) need 806 bytes including TAG, NUM, comba + */ +#define MAX_HMD_POWER_STORE_LEN 1024 +enum { + HMD_POWER_MON = 0, /* monitor name field */ + HMD_POWER_VID, /* vid field */ + HMD_POWER_PID, /* pid field */ + HMD_POWER_FIELD_MAX, +}; + +/* convert VID/PID string to uint in hexadecimal */ +static int _max77775_strtoint(char *tok, uint *result) +{ + int ret = 0; + + if (!tok || !result) { + msg_maxim("invalid arg!"); + ret = -EINVAL; + goto end; + } + + if (strlen(tok) == 5 && tok[4] == 0xa/*LF*/) { + /* continue since it's ended with line feed */ + } else if (strlen(tok) != 4) { + msg_maxim("%s should have 4 len, but %lu!", tok, strlen(tok)); + ret = -EINVAL; + goto end; + } + + ret = kstrtouint(tok, 16, result); + if (ret) { + msg_maxim("fail to convert %s! ret:%d", tok, ret); + goto end; + } +end: + return ret; +} + +int max77775_store_hmd_dev(struct max77775_usbc_platform_data *usbc_data, char *str, size_t len, int num_hmd) +{ + struct max77775_hmd_power_dev *hmd_list; + char *tok; + int i, j, ret = 0, rmdr; + uint value; + + if (num_hmd <= 0 || num_hmd > MAX_NUM_HMD) { + msg_maxim("invalid num_hmd! %d", num_hmd); + ret = -EINVAL; + goto end; + } + + hmd_list = usbc_data->hmd_list; + if (!hmd_list) { + msg_maxim("hmd_list is null!"); + ret = -ENOMEM; + goto end; + } + + msg_maxim("+++ %s, %lu, %d", str, len, num_hmd); + + /* reset */ + for (i = 0; i < MAX_NUM_HMD; i++) { + memset(hmd_list[i].hmd_name, 0, NAME_LEN_HMD); + hmd_list[i].vid = 0; + hmd_list[i].pid = 0; + } + + tok = strsep(&str, ","); + i = 0, j = 0; + while (tok != NULL && *tok != 0xa/*LF*/) { + if (i >= num_hmd * HMD_POWER_FIELD_MAX) { + msg_maxim("num of tok cannot exceed <%dx%d>!", + num_hmd, HMD_POWER_FIELD_MAX); + break; + } + if (j >= MAX_NUM_HMD) { + msg_maxim("num of HMD cannot exceed %d!", + MAX_NUM_HMD); + break; + } + + rmdr = i % HMD_POWER_FIELD_MAX; + + switch (rmdr) { + case HMD_POWER_MON: + strlcpy(hmd_list[j].hmd_name, tok, NAME_LEN_HMD); + break; + + case HMD_POWER_VID: + case HMD_POWER_PID: + ret = _max77775_strtoint(tok, &value); + if (ret) + goto end; + + if (rmdr == HMD_POWER_VID) { + hmd_list[j].vid = value; + } else { + hmd_list[j].pid = value; + j++; /* move next */ + } + break; + } + + tok = strsep(&str, ","); + i++; + } + for (i = 0; i < MAX_NUM_HMD; i++) { + if (strlen(hmd_list[i].hmd_name) > 0) + msg_maxim("%s,0x%04x,0x%04x", + hmd_list[i].hmd_name, + hmd_list[i].vid, + hmd_list[i].pid); + } + +end: + return ret; +} + +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) +static void max77775_control_gpio_for_sbu(int onoff) +{ + struct otg_notify *o_notify = get_otg_notify(); + struct usb_notifier_platform_data *pdata = get_notify_data(o_notify); + + if (o_notify && o_notify->set_ldo_onoff) + o_notify->set_ldo_onoff(pdata, onoff); +} +#endif + +static ssize_t max77775_sysfs_set_prop(struct _pdic_data_t *ppdic_data, + enum pdic_sysfs_property prop, + const char *buf, size_t size) +{ + ssize_t retval = size; + int mode = 0; + int ret = 0; + struct max77775_usbc_platform_data *usbpd_data = + (struct max77775_usbc_platform_data *)ppdic_data->drv_data; + int rv, len; + char str[MAX_HMD_POWER_STORE_LEN] = {0,}, *p, *tok; +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + struct otg_notify *o_notify = get_otg_notify(); +#endif + + if (!usbpd_data) { + msg_maxim("usbpd_data is null : request prop = %d", prop); + return -ENODEV; + } + switch (prop) { + case PDIC_SYSFS_PROP_LPM_MODE: + rv = sscanf(buf, "%d", &mode); + msg_maxim("usb: PDIC_SYSFS_PROP_LPM_MODE mode=%d", mode); + switch (mode) { + case 0: + max77775_set_jig_on(usbpd_data, 0); + usbpd_data->manual_lpm_mode = 0; + break; + case 1: + case 2: + max77775_set_jig_on(usbpd_data, 1); + usbpd_data->manual_lpm_mode = 1; + break; + default: + max77775_set_jig_on(usbpd_data, 0); + usbpd_data->manual_lpm_mode = 0; + break; + } + break; + case PDIC_SYSFS_PROP_CTRL_OPTION: + rv = sscanf(buf, "%d", &mode); + msg_maxim("usb: PDIC_SYSFS_PROP_CTRL_OPTION mode=%d", mode); + max77775_control_option_command(usbpd_data, mode); + break; + case PDIC_SYSFS_PROP_FW_UPDATE: + rv = sscanf(buf, "%d", &mode); + msg_maxim("PDIC_SYSFS_PROP_FW_UPDATE mode=%d", mode); + +#ifdef MAX77775_SYS_FW_UPDATE + md75_info_usb("%s before : FW_REV %02X.%02X\n", __func__, + usbpd_data->max77775->FW_Revision, + usbpd_data->max77775->FW_Minor_Revision); + + /* Factory cmd for firmware update + * argument represent what is source of firmware like below. + * + * 0 : [BUILT_IN] Getting firmware from source. + * 1 : [UMS] Getting firmware from sd card. + * 2 : [SPU] Getting firmware from SPU APP. + */ + switch (mode) { + case BUILT_IN: + schedule_work(&usbpd_data->fw_update_work); + break; + case UMS: + case SPU: + case SPU_VERIFICATION: + ret = max77775_firmware_update_sysfs(usbpd_data, mode); + break; + default: + ret = -EINVAL; + msg_maxim("Not support command[%d]", mode); + break; + } + if (ret < 0) { + msg_maxim("Failed to update FW"); + return ret; + } + + max77775_get_version_info(usbpd_data); +#else + return -EINVAL; +#endif + break; + case PDIC_SYSFS_PROP_DEX_FAN_UVDM: + rv = sscanf(buf, "%d", &mode); + msg_maxim("PDIC_SYSFS_PROP_DEX_FAN_UVDM mode=%d", mode); + max77775_send_dex_fan_unstructured_vdm_message(usbpd_data, mode); + break; + case PDIC_SYSFS_PROP_DEBUG_OPCODE: + rv = sscanf(buf, "%d", &mode); + msg_maxim("PDIC_SYSFS_PROP_DEBUG_OPCODE mode=%d", mode); + break; + case PDIC_SYSFS_PROP_CONTROL_GPIO: + rv = sscanf(buf, "%d", &mode); + msg_maxim("PDIC_SYSFS_PROP_CONTROL_GPIO mode=%d. do nothing for control gpio.", mode); + /* orignal concept : mode 0 : SBU1/SBU2 set as open-drain status + * mode 1 : SBU1/SBU2 set as default status - Pull up + * But, max77775 is always open-drain status so we don't need to control it. + */ +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + max77775_control_gpio_for_sbu(!mode); +#endif + break; + case PDIC_SYSFS_PROP_OVP_IC_SHUTDOWN: + rv = sscanf(buf, "%d", &mode); + msg_maxim("PDIC_SYSFS_PROP_OVP_IC_SHUTDOWN mode=%d", mode); + if (ret) + return -ENODATA; + break; + case PDIC_SYSFS_PROP_HMD_POWER: + if (size >= MAX_HMD_POWER_STORE_LEN) { + msg_maxim("too long args! %lu", size); + return -EOVERFLOW; + } + mutex_lock(&usbpd_data->hmd_power_lock); + memcpy(str, buf, size); + p = str; + tok = strsep(&p, ","); + if (tok) { + len = strlen(tok); + msg_maxim("tok: %s, len: %d", tok, len); + } else { + len = 0; + msg_maxim("tok: len: 0"); + } + + if (!strncmp(TAG_HMD, tok, len)) { + /* called by HmtManager to inform list of supported HMD devices + * + * Format : + * HMD,NUM,NAME01,VID01,PID01,NAME02,VID02,PID02,... + * + * HMD : tag + * NUM : num of HMD dev ..... max 2 bytes to decimal (max 32) + * NAME : name of HMD ...... max 14 bytes, char string + * VID : vendor id ....... 4 bytes to hexadecimal + * PID : product id ....... 4 bytes to hexadecimal + * + * ex) HMD,2,PicoVR,2d40,0000,Nreal light,0486,573c + * + * call hmd store function with tag(HMD),NUM removed + */ + int num_hmd = 0, sz = 0; + + tok = strsep(&p, ","); + if (tok) { + sz = strlen(tok); + ret = kstrtouint(tok, 10, &num_hmd); + } + + msg_maxim("HMD num: %d, sz:%d", num_hmd, sz); + + max77775_store_hmd_dev(usbpd_data, str + (len + sz + 2), size - (len + sz + 2), + num_hmd); + + if (usbpd_data->acc_type == PDIC_DOCK_NEW && max77775_check_hmd_dev(usbpd_data)) { +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + if (o_notify) + send_otg_notify(o_notify, NOTIFY_EVENT_HMD_EXT_CURRENT, 1); +#endif + } + mutex_unlock(&usbpd_data->hmd_power_lock); + return size; + } + mutex_unlock(&usbpd_data->hmd_power_lock); + break; + default: + md75_info_usb("%s prop write not supported prop (%d)\n", __func__, prop); + retval = -ENODATA; + return retval; + } + return size; +} + +static int max77775_sysfs_is_writeable(struct _pdic_data_t *ppdic_data, + enum pdic_sysfs_property prop) +{ + switch (prop) { + case PDIC_SYSFS_PROP_LPM_MODE: + case PDIC_SYSFS_PROP_CTRL_OPTION: + case PDIC_SYSFS_PROP_DEBUG_OPCODE: + case PDIC_SYSFS_PROP_CONTROL_GPIO: + return 1; + default: + return 0; + } +} + +static int max77775_sysfs_is_writeonly(struct _pdic_data_t *ppdic_data, + enum pdic_sysfs_property prop) +{ + switch (prop) { + case PDIC_SYSFS_PROP_FW_UPDATE: + case PDIC_SYSFS_PROP_DEX_FAN_UVDM: + case PDIC_SYSFS_PROP_OVP_IC_SHUTDOWN: + case PDIC_SYSFS_PROP_HMD_POWER: + return 1; + default: + return 0; + } +} +#endif +static ssize_t max77775_fw_update(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + unsigned int start_fw_update = 0; + usbc_cmd_data read_data; + usbc_cmd_data write_data; + + init_usbc_cmd_data(&read_data); + init_usbc_cmd_data(&write_data); + read_data.opcode = OPCODE_CTRL1_R; + read_data.write_length = 0x0; + read_data.read_length = 0x1; + + write_data.opcode = OPCODE_CTRL1_W; + write_data.write_data[0] = 0x09; + write_data.write_length = 0x1; + write_data.read_length = 0x0; + + if (kstrtou32(buf, 0, &start_fw_update)) { + dev_err(dev, + "%s: Failed converting from str to u32.", __func__); + } + + msg_maxim("start_fw_update %d", start_fw_update); + + max77775_usbc_opcode_rw(g_usbc_data, &read_data, &write_data); + + switch (start_fw_update) { + case 1: + max77775_usbc_opcode_rw(g_usbc_data, &read_data, &write_data); + break; + case 2: + max77775_usbc_opcode_read(g_usbc_data, &read_data); + break; + case 3: + max77775_usbc_opcode_write(g_usbc_data, &write_data); + + break; + default: + break; + } + return size; +} +static DEVICE_ATTR(fw_update, S_IRUGO | S_IWUSR | S_IWGRP, + NULL, max77775_fw_update); + +static struct attribute *max77775_attr[] = { + &dev_attr_fw_update.attr, + NULL, +}; + +static struct attribute_group max77775_attr_grp = { + .attrs = max77775_attr, +}; + +static void max77775_get_version_info(struct max77775_usbc_platform_data *usbc_data) +{ + u8 hw_rev[4] = {0, }; + u8 sw_main[3] = {0, }; +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + u8 sw_boot = 0; +#endif + + hw_rev[0] = usbc_data->max77775->pmic_rev; + sw_main[0] = usbc_data->max77775->FW_Revision; + sw_main[1] = usbc_data->max77775->FW_Minor_Revision; + + usbc_data->HW_Revision = hw_rev[0]; + usbc_data->FW_Revision = sw_main[0]; + usbc_data->FW_Minor_Revision = sw_main[1]; + + /* H/W, Minor, Major, Boot */ + msg_maxim("HW rev is %02Xh, FW rev is %02X.%02X!", + usbc_data->HW_Revision, usbc_data->FW_Revision, usbc_data->FW_Minor_Revision); +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + store_ccic_version(&hw_rev[0], &sw_main[0], &sw_boot); +#endif +} + +static void max77775_init_opcode + (struct max77775_usbc_platform_data *usbc_data, int reset) +{ + struct max77775_platform_data *pdata = usbc_data->max77775_data; + + max77775_usbc_disable_auto_vbus(usbc_data); + if (pdata && pdata->support_audio) + max77775_usbc_enable_audio(usbc_data); + if (reset) { + max77775_set_enable_alternate_mode(ALTERNATE_MODE_START); + max77775_muic_enable_detecting_short(usbc_data->muic_data); + } +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION + else { + max77775_set_enable_alternate_mode(ALTERNATE_MODE_READY | ALTERNATE_MODE_STOP); + } +#endif +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) +#if defined(CONFIG_MAX77775_CCOPEN_AFTER_WATERCABLE) + if (!is_lpcharge_pdic_param()) + max77775_set_moisture_cc_open(usbc_data); +#endif +#endif +} + +static bool max77775_check_recover_opcode(u8 opcode) +{ + bool ret = false; + + switch (opcode) { + case OPCODE_CCCTRL1_W: + case OPCODE_SAMSUNG_FACTORY_TEST: + case OPCODE_SET_ALTERNATEMODE: + case OPCODE_SBU_CTRL1_W: + case OPCODE_SAMSUNG_CCSBU_SHORT: +#if defined(CONFIG_MAX77775_CCOPEN_AFTER_WATERCABLE) + case OPCODE_MOISTURE_CC_OPEN: +#endif + ret = true; + break; + default: + ret = false; + break; + } + return ret; +} + +static void max77775_recover_opcode + (struct max77775_usbc_platform_data *usbc_data, bool opcode_list[]) +{ + int i; + + for (i = 0; i < OPCODE_NONE; i++) { + if (opcode_list[i]) { + msg_maxim("opcode = 0x%02x", i); + switch (i) { + case OPCODE_CCCTRL1_W: + if (usbc_data->op_ctrl1_w & BIT_CCAudEn) + max77775_usbc_enable_audio(usbc_data); + break; + case OPCODE_SAMSUNG_FACTORY_TEST: + if (usbc_data->auto_vbus_en) + max77775_usbc_enable_auto_vbus(usbc_data); + else + max77775_usbc_disable_auto_vbus(usbc_data); + break; + case OPCODE_SET_ALTERNATEMODE: + max77775_set_enable_alternate_mode + (usbc_data->set_altmode); + break; +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + case OPCODE_SBU_CTRL1_W: + max77775_usbpd_sbu_switch_control(usbc_data, usbc_data->sbu_switch_status); + break; +#endif + case OPCODE_SAMSUNG_CCSBU_SHORT: + max77775_muic_enable_detecting_short + (usbc_data->muic_data); + break; +#if defined(CONFIG_MAX77775_CCOPEN_AFTER_WATERCABLE) + case OPCODE_MOISTURE_CC_OPEN: + max77775_set_moisture_cc_open(usbc_data); + break; +#endif + default: + break; + } + opcode_list[i] = false; + } + } +} + +void init_usbc_cmd_data(usbc_cmd_data *cmd_data) +{ + cmd_data->opcode = OPCODE_NONE; + cmd_data->prev_opcode = OPCODE_NONE; + cmd_data->response = OPCODE_NONE; + cmd_data->val = REG_NONE; + cmd_data->mask = REG_NONE; + cmd_data->reg = REG_NONE; + cmd_data->noti_cmd = OPCODE_NOTI_NONE; + cmd_data->write_length = 0; + cmd_data->read_length = 0; + cmd_data->seq = 0; + cmd_data->is_uvdm = 0; + memset(cmd_data->write_data, REG_NONE, OPCODE_DATA_LENGTH); + memset(cmd_data->read_data, REG_NONE, OPCODE_DATA_LENGTH); +} + +static void init_usbc_cmd_node(usbc_cmd_node *usbc_cmd_node) +{ + usbc_cmd_data *cmd_data = &(usbc_cmd_node->cmd_data); + + pr_debug("%s:%s\n", "MAX77775", __func__); + + usbc_cmd_node->next = NULL; + + init_usbc_cmd_data(cmd_data); +} + +static void copy_usbc_cmd_data(usbc_cmd_data *from, usbc_cmd_data *to) +{ + to->opcode = from->opcode; + to->response = from->response; + memcpy(to->read_data, from->read_data, OPCODE_DATA_LENGTH); + memcpy(to->write_data, from->write_data, OPCODE_DATA_LENGTH); + to->reg = from->reg; + to->mask = from->mask; + to->val = from->val; + to->seq = from->seq; + to->read_length = from->read_length; + to->write_length = from->write_length; + to->prev_opcode = from->prev_opcode; + to->is_uvdm = from->is_uvdm; +} + +bool is_empty_usbc_cmd_queue(usbc_cmd_queue_t *usbc_cmd_queue) +{ + bool ret = false; + + if (usbc_cmd_queue->front == NULL) + ret = true; + + if (ret) + msg_maxim("usbc_cmd_queue Empty(%c)", ret ? 'T' : 'F'); + + return ret; +} + +void enqueue_usbc_cmd(usbc_cmd_queue_t *usbc_cmd_queue, usbc_cmd_data *cmd_data) +{ + usbc_cmd_node *temp_node = kzalloc(sizeof(usbc_cmd_node), GFP_KERNEL); + + if (!temp_node) { + msg_maxim("failed to allocate usbc command queue"); + return; + } + + init_usbc_cmd_node(temp_node); + + copy_usbc_cmd_data(cmd_data, &(temp_node->cmd_data)); + + if (is_empty_usbc_cmd_queue(usbc_cmd_queue)) { + usbc_cmd_queue->front = temp_node; + usbc_cmd_queue->rear = temp_node; + } else { + usbc_cmd_queue->rear->next = temp_node; + usbc_cmd_queue->rear = temp_node; + } +} + +static void dequeue_usbc_cmd + (usbc_cmd_queue_t *usbc_cmd_queue, usbc_cmd_data *cmd_data) +{ + usbc_cmd_node *temp_node; + + if (is_empty_usbc_cmd_queue(usbc_cmd_queue)) { + msg_maxim("Queue, Empty!"); + return; + } + + temp_node = usbc_cmd_queue->front; + copy_usbc_cmd_data(&(temp_node->cmd_data), cmd_data); + + msg_maxim("Opcode(0x%02x) Response(0x%02x)", cmd_data->opcode, cmd_data->response); + + if (usbc_cmd_queue->front->next == NULL) { + msg_maxim("front->next = NULL"); + usbc_cmd_queue->front = NULL; + } else + usbc_cmd_queue->front = usbc_cmd_queue->front->next; + + if (is_empty_usbc_cmd_queue(usbc_cmd_queue)) + usbc_cmd_queue->rear = NULL; + + kfree(temp_node); +} + +static bool front_usbc_cmd + (usbc_cmd_queue_t *cmd_queue, usbc_cmd_data *cmd_data) +{ + if (is_empty_usbc_cmd_queue(cmd_queue)) { + msg_maxim("Queue, Empty!"); + return false; + } + + copy_usbc_cmd_data(&(cmd_queue->front->cmd_data), cmd_data); + msg_maxim("Opcode(0x%02x)", cmd_data->opcode); + return true; +} + +static bool is_usbc_notifier_opcode(u8 opcode) +{ + bool noti = false; + + return noti; +} + +/* + * max77775_i2c_opcode_write - SMBus "opcode write" protocol + * @chip: max77775 platform data + * @command: OPcode + * @values: Byte array into which data will be read; big enough to hold + * the data returned by the slave. + * + * This executes the SMBus "opcode read" protocol, returning negative errno + * else the number of data bytes in the slave's response. + */ +int max77775_i2c_opcode_write(struct max77775_usbc_platform_data *usbc_data, + u8 opcode, u8 length, u8 *values) +{ + u8 write_values[OPCODE_MAX_LENGTH] = { 0, }; + int ret = 0; + + if (length > OPCODE_DATA_LENGTH) + return -EMSGSIZE; + + write_values[0] = opcode; + if (length) + memcpy(&write_values[1], values, length); + +#if 0 + int i = 0; // To use this, move int i to the top to avoid build error + for (i = 0; i < length + OPCODE_SIZE; i++) + msg_maxim("[%d], 0x[%x]", i, write_values[i]); +#else + msg_maxim("opcode 0x%x, write_length %d", + opcode, length + OPCODE_SIZE); + print_hex_dump(KERN_INFO, "max77775: opcode_write: ", + DUMP_PREFIX_OFFSET, 16, 1, write_values, + length + OPCODE_SIZE, false); +#endif + + /* Write opcode and data */ + ret = max77775_bulk_write(usbc_data->muic, OPCODE_WRITE, + length + OPCODE_SIZE, write_values); + /* Write end of data by 0x00 */ + if (length < OPCODE_DATA_LENGTH) + max77775_write_reg(usbc_data->muic, OPCODE_WRITE_END, 0x00); + + if (opcode == OPCODE_SET_ALTERNATEMODE) + usbc_data->set_altmode_error = ret; + + if (ret == 0) + usbc_data->opcode_stamp = jiffies; + + return ret; +} + +/** + * max77775_i2c_opcode_read - SMBus "opcode read" protocol + * @chip: max77775 platform data + * @command: OPcode + * @values: Byte array into which data will be read; big enough to hold + * the data returned by the slave. + * + * This executes the SMBus "opcode read" protocol, returning negative errno + * else the number of data bytes in the slave's response. + */ +int max77775_i2c_opcode_read(struct max77775_usbc_platform_data *usbc_data, + u8 opcode, u8 length, u8 *values) +{ + int size = 0; + + if (length > OPCODE_DATA_LENGTH) + return -EMSGSIZE; + + /* + * We don't need to use opcode to get any feedback + */ + + /* Read opcode data */ + max77775_bulk_read(usbc_data->muic, OPCODE_READ, OPCODE_SIZE, values); + if (length > 0) + size = max77775_bulk_read(usbc_data->muic, OPCODE_READ + OPCODE_SIZE, + length, values + OPCODE_SIZE); + +#if 0 + int i = 0; // To use this, move int i to the top to avoid build error + for (i = 0; i < length + OPCODE_SIZE; i++) + msg_maxim("[%d], 0x[%x]", i, values[i]); +#else + msg_maxim("opcode 0x%x, read_length %d, ret_error %d", + opcode, length + OPCODE_SIZE, size); + print_hex_dump(KERN_INFO, "max77775: opcode_read: ", + DUMP_PREFIX_OFFSET, 16, 1, values, + length + OPCODE_SIZE, false); +#endif + return size; +} + +static void max77775_notify_execute(struct max77775_usbc_platform_data *usbc_data, + const usbc_cmd_data *cmd_data) +{ + /* to do */ +} + +static void max77775_handle_update_opcode(struct max77775_usbc_platform_data *usbc_data, + const usbc_cmd_data *cmd_data, unsigned char *data) +{ + usbc_cmd_data write_data; + u8 read_value = data[1]; + u8 write_value = (read_value & (~cmd_data->mask)) | (cmd_data->val & cmd_data->mask); + u8 opcode = cmd_data->response + 1; /* write opcode = read opocde + 1 */ + + md75_info_usb("%s: value update [0x%x]->[0x%x] at OPCODE(0x%x)\n", __func__, + read_value, write_value, opcode); + + init_usbc_cmd_data(&write_data); + write_data.opcode = opcode; + write_data.write_length = 1; + write_data.write_data[0] = write_value; + write_data.read_length = 0; + + max77775_usbc_opcode_push(usbc_data, &write_data); +} + +static void max77775_request_response(struct max77775_usbc_platform_data *usbc_data) +{ + usbc_cmd_data value; + + init_usbc_cmd_data(&value); + value.opcode = OPCODE_READ_RESPONSE_FOR_GET_REQUEST; + value.read_length = 28; + max77775_usbc_opcode_push(usbc_data, &value); + + md75_info_usb("%s : OPCODE(0x%02x) W_LENGTH(%d) R_LENGTH(%d)\n", + __func__, value.opcode, value.write_length, value.read_length); +} + +#define UNKNOWN_VID 0xFFFF +void max77775_send_get_request(struct max77775_usbc_platform_data *usbc_data, unsigned char *data) +{ + enum { + SENT_REQ_MSG = 0, + ERR_SNK_RDY = 5, + ERR_PD20, + ERR_SNKTXNG, + }; + SEC_PD_SINK_STATUS *snk_sts = &usbc_data->pd_data->pd_noti.sink_status; + + if (data[1] == SENT_REQ_MSG) { + max77775_request_response(usbc_data); + } else { /* ERROR case */ + /* Mark Error in xid */ + snk_sts->xid = (UNKNOWN_VID << 16) | (data[1] << 8); + msg_maxim("%s, Err : %d", __func__, data[1]); + } +} + +void max77775_extend_msg_process(struct max77775_usbc_platform_data *usbc_data, unsigned char *data, + unsigned char len) +{ + SEC_PD_SINK_STATUS *snk_sts = &usbc_data->pd_data->pd_noti.sink_status; + + snk_sts->vid = *(unsigned short *)(data + 2); + snk_sts->pid = *(unsigned short *)(data + 4); + snk_sts->xid = *(unsigned int *)(data + 6); + msg_maxim("%s, %04x, %04x, %08x", + __func__, snk_sts->vid, snk_sts->pid, snk_sts->xid); + if (snk_sts->fp_sec_pd_ext_cb) + snk_sts->fp_sec_pd_ext_cb(snk_sts->vid, snk_sts->pid); +} + +void max77775_read_response(struct max77775_usbc_platform_data *usbc_data, unsigned char *data) +{ + SEC_PD_SINK_STATUS *snk_sts = &usbc_data->pd_data->pd_noti.sink_status; + + switch (data[1] >> 5) { + case OPCODE_GET_SRC_CAP_EXT: + max77775_extend_msg_process(usbc_data, data+2, data[1] & 0x1F); + break; + default: + snk_sts->xid = (UNKNOWN_VID << 16) | data[1]; + msg_maxim("%s, Err : %d", __func__, data[1]); + break; + } +} + +static void max77775_irq_execute(struct max77775_usbc_platform_data *usbc_data, + const usbc_cmd_data *cmd_data) +{ + int len = cmd_data->read_length; + unsigned char data[OPCODE_DATA_LENGTH + OPCODE_SIZE] = {0,}; + u8 response = 0xff; + u8 vdm_opcode_header = 0x0; + UND_DATA_MSG_VDM_HEADER_Type vdm_header; + u8 vdm_command = 0x0; + u8 vdm_type = 0x0; + u8 vdm_response = 0x0; + u8 reqd_vdm_command = 0; + uint8_t W_DATA = 0x0; + + memset(&vdm_header, 0, sizeof(UND_DATA_MSG_VDM_HEADER_Type)); + max77775_i2c_opcode_read(usbc_data, cmd_data->response, + len, data); + + /* opcode identifying the messsage type. (0x51)*/ + response = data[0]; + + if (response != cmd_data->response) { + msg_maxim("Response [0x%02x] != [0x%02x]", + response, cmd_data->response); + if (cmd_data->response == OPCODE_FW_OPCODE_CLEAR) { + msg_maxim("Response after FW opcode cleared, just return"); + return; + } + } + + /* to do(read switch case) */ + switch (response) { + case OPCODE_BCCTRL1_R: + case OPCODE_BCCTRL2_R: + case OPCODE_CTRL1_R: + case OPCODE_CCCTRL1_R: + case OPCODE_CCCTRL2_R: + case OPCODE_CCCTRL3_R: + case OPCODE_VCONN_ILIM_R: + if (cmd_data->seq == OPCODE_UPDATE_SEQ) + max77775_handle_update_opcode(usbc_data, cmd_data, data); + break; +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77775_AFC) + case COMMAND_AFC_RESULT_READ: + case COMMAND_QC_2_0_SET: + max77775_muic_handle_detect_dev_hv(usbc_data->muic_data, data); + break; +#endif + case OPCODE_CURRENT_SRCCAP: + max77775_current_pdo(usbc_data, data); + break; + case OPCODE_GET_SRCCAP: + max77775_pdo_list(usbc_data, data); + break; + case OPCODE_SEND_GET_REQUEST: + max77775_send_get_request(usbc_data, data); + break; + case OPCODE_READ_RESPONSE_FOR_GET_REQUEST: + max77775_read_response(usbc_data, data); + break; + case OPCODE_SRCCAP_REQUEST: + /* + * If response of Source_Capablities message is SinkTxNg(0xFE) or Not in Ready State(0xFF) + * It means that the message can not be sent to Port Partner. + * After Attaching Rp 3.0A, send again the message. + */ + if (data[1] == 0xfe || data[1] == 0xff) { + usbc_data->srcccap_request_retry = true; + md75_info_usb("%s : srcccap_request_retry is set\n", __func__); + } + break; + case OPCODE_APDO_SRCCAP_REQUEST: + max77775_response_apdo_request(usbc_data, data); + break; + case OPCODE_SET_PPS: + max77775_response_set_pps(usbc_data, data); + break; + case OPCODE_SAMSUNG_READ_MESSAGE: + md75_info_usb("@TA_ALERT: %s : OPCODE[%x] Data[1] = 0x%x Data[7] = 0x%x Data[9] = 0x%x\n", + __func__, OPCODE_SAMSUNG_READ_MESSAGE, data[1], data[7], data[9]); +#if defined(CONFIG_DIRECT_CHARGING) + if ((data[0] == 0x5D) && + /* OCP would be set to Alert or Status message */ + ((data[1] == 0x01 && data[7] == 0x04) || (data[1] == 0x02 && (data[9] & 0x02)))) { + union power_supply_propval value = {0,}; + value.intval = true; + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_DIRECT_TA_ALERT, value); + } +#endif + break; + case OPCODE_VDM_DISCOVER_GET_VDM_RESP: + max77775_vdm_message_handler(usbc_data, data, len + OPCODE_SIZE); + break; + case OPCODE_READ_SBU: + max77775_response_sbu_read(usbc_data, data); + break; + case OPCODE_READ_CC: + max77775_response_cc_read(usbc_data, data); + break; + case OPCODE_VDM_DISCOVER_SET_VDM_REQ: + vdm_opcode_header = data[1]; + switch (vdm_opcode_header) { + case 0xFF: + msg_maxim("This isn't invalid response(OPCODE : 0x48, HEADER : 0xFF)"); + break; + default: + memcpy(&vdm_header, &data[2], sizeof(vdm_header)); + vdm_type = vdm_header.BITS.VDM_Type; + vdm_command = vdm_header.BITS.VDM_command; + vdm_response = vdm_header.BITS.VDM_command_type; + msg_maxim("vdm_type[%x], vdm_command[%x], vdm_response[%x]", + vdm_type, vdm_command, vdm_response); + switch (vdm_type) { + case STRUCTURED_VDM: + if (vdm_response == SEC_UVDM_RESPONDER_ACK) { + switch (vdm_command) { + case Discover_Identity: + msg_maxim("ignore Discover_Identity"); + break; + case Discover_SVIDs: + msg_maxim("ignore Discover_SVIDs"); + break; + case Discover_Modes: + /* work around. The discover mode irq is not happened */ + if (vdm_header.BITS.Standard_Vendor_ID + == SAMSUNG_VENDOR_ID) { + if (usbc_data->send_enter_mode_req == 0) { + /*Samsung Enter Mode */ + msg_maxim("dex: second enter mode request"); + usbc_data->send_enter_mode_req = 1; + max77775_set_dex_enter_mode(usbc_data); + } + } else + msg_maxim("ignore Discover_Modes"); + break; + case Enter_Mode: + /* work around. The enter mode irq is not happened */ + if (vdm_header.BITS.Standard_Vendor_ID + == SAMSUNG_VENDOR_ID) { + usbc_data->is_samsung_accessory_enter_mode = 1; + msg_maxim("dex mode enter_mode ack status!"); + } else + msg_maxim("ignore Enter_Mode"); + break; + case Exit_Mode: + msg_maxim("ignore Exit_Mode"); + break; + case Attention: + msg_maxim("ignore Attention"); + break; + case Configure: + break; + default: + msg_maxim("vdm_command isn't valid[%x]", vdm_command); + break; + }; + } else if (vdm_response == SEC_UVDM_ININIATOR) { + switch (vdm_command) { + case Attention: + /* Attention message is not able to be received via 0x48 OPCode */ + /* Check requested vdm command and responded vdm command */ + { + /* Read requested vdm command */ + max77775_read_reg(usbc_data->muic, 0x23, &reqd_vdm_command); + reqd_vdm_command &= 0x1F; /* Command bit, b4...0 */ + + if (reqd_vdm_command == Configure) { + W_DATA = 1 << (usbc_data->dp_selected_pin - 1); + /* Retry Configure message */ + msg_maxim("Retry Configure message, W_DATA = %x, dp_selected_pin = %d", + W_DATA, usbc_data->dp_selected_pin); + max77775_set_dp_configure(usbc_data, W_DATA); + } + } + break; + case Discover_Identity: + case Discover_SVIDs: + case Discover_Modes: + case Enter_Mode: + case Configure: + default: + /* Nothing */ + break; + }; + } else + msg_maxim("vdm_response is error value[%x]", vdm_response); + break; + case UNSTRUCTURED_VDM: + max77775_uvdm_opcode_response_handler(usbc_data, data, len + OPCODE_SIZE); + break; + default: + msg_maxim("vdm_type isn't valid error"); + break; + }; + break; + }; + break; + case OPCODE_SET_ALTERNATEMODE: + usbc_data->max77775->set_altmode_en = 1; + break; + case OPCODE_FW_OPCODE_CLEAR: + msg_maxim("Cleared FW OPCODE"); + break; + case OPCODE_CCCTRL4_R: + max77775_ccctrl4_read_complete(usbc_data, data); + break; + case OPCODE_CONTROL_JIG_R: + max77775_get_jig_on(usbc_data, data); + break; + default: + break; + } +} + +void max77775_usbc_dequeue_queue(struct max77775_usbc_platform_data *usbc_data) +{ + usbc_cmd_data cmd_data; + usbc_cmd_queue_t *cmd_queue = NULL; + + cmd_queue = &(usbc_data->usbc_cmd_queue); + + init_usbc_cmd_data(&cmd_data); + + if (is_empty_usbc_cmd_queue(cmd_queue)) { + msg_maxim("Queue, Empty"); + return; + } + + dequeue_usbc_cmd(cmd_queue, &cmd_data); + msg_maxim("!! Dequeue queue : opcode : %x, 1st data : %x. 2st data : %x", + cmd_data.write_data[0], + cmd_data.read_data[0], + cmd_data.val); +} + +static void max77775_usbc_clear_fw_queue(struct max77775_usbc_platform_data *usbc_data) +{ + usbc_cmd_data write_data; + + msg_maxim("called"); + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_FW_OPCODE_CLEAR; + max77775_usbc_opcode_write(usbc_data, &write_data); +} + +void max77775_usbc_clear_queue(struct max77775_usbc_platform_data *usbc_data) +{ + usbc_cmd_data cmd_data; + usbc_cmd_queue_t *cmd_queue = NULL; + + mutex_lock(&usbc_data->op_lock); + msg_maxim("IN"); + cmd_queue = &(usbc_data->usbc_cmd_queue); + + while (!is_empty_usbc_cmd_queue(cmd_queue)) { + init_usbc_cmd_data(&cmd_data); + dequeue_usbc_cmd(cmd_queue, &cmd_data); + if (max77775_check_recover_opcode(cmd_data.opcode)) { + usbc_data->need_recover = true; + usbc_data->recover_opcode_list[cmd_data.opcode] = usbc_data->need_recover; + } + } + usbc_data->opcode_stamp = 0; + msg_maxim("OUT"); + mutex_unlock(&usbc_data->op_lock); + /* also clear fw opcode queue to sync with driver */ + max77775_usbc_clear_fw_queue(usbc_data); +} + +bool max77775_is_noirq_opcode(uint8_t opcode) +{ + bool ret = false; + + switch (opcode) { + case OPCODE_ICURR_AUTOIBUS_ON: + ret = true; + break; + default: + break; + } + + return ret; +} + +bool max77775_is_stuck_suppose_opcode(uint8_t opcode) +{ + bool ret = false; + + switch (opcode) { + case OPCODE_APDO_SRCCAP_REQUEST: + case OPCODE_SET_PPS: + case OPCODE_BCCTRL1_R: + ret = true; + break; + default: + break; + } + + return ret; +} + +static int max77775_usbc_increase_opcode_fail(struct max77775_usbc_platform_data *usbc_data, + bool enforce) +{ + const int reset_count = OPCODE_RESET_COUNT; + + usbc_data->opcode_fail_count++; + md75_info_usb("%s: opcode_fail_count = %d enforce = %d\n", __func__, + usbc_data->opcode_fail_count, enforce); + + if (usbc_data->opcode_fail_count == reset_count || enforce) { +#if IS_ENABLED(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); + + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_STUCK_COUNT); +#endif +#if IS_ENABLED(CONFIG_SEC_ABC) + sec_abc_send_event("MODULE=pdic@INFO=ccic_stuck"); +#endif + } + + if (usbc_data->opcode_fail_count >= reset_count || enforce) { +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + union power_supply_propval value = {0, }; +#endif + + md75_info_usb("%s: STUCK. we will call uic reset.\n", __func__); +#if IS_ENABLED(CONFIG_USB_USING_ADVANCED_USBLOG) + printk_usb(NOTIFY_PRINTK_USB_SNAPSHOT, "ic stuck.\n"); +#endif + + max77775_reset_ic(usbc_data); + usbc_data->stuck_suppose = 1; + +#if IS_ENABLED(CONFIG_DIRECT_CHARGING) + value.intval = 1; + psy_do_property("sec-direct-charger", set, + POWER_SUPPLY_EXT_PROP_DIRECT_CLEAR_ERR, value); +#endif + return 1; + } + return 0; +} + +static int max77775_usbc_handle_noirq_opcode(struct max77775_usbc_platform_data *usbc_data, usbc_cmd_data cmd_data) +{ + int ret; + usbc_cmd_queue_t *cmd_queue = NULL; + usbc_cmd_data cmd_data_dummy; + unsigned char rbuf[OPCODE_DATA_LENGTH + OPCODE_SIZE] = {0,}; + int retry = 10; + + ret = max77775_i2c_opcode_write(usbc_data, cmd_data.opcode, + cmd_data.write_length, cmd_data.write_data); + if (ret < 0) { + msg_maxim("i2c write fail. dequeue opcode"); + max77775_usbc_dequeue_queue(usbc_data); + return 0; + } + + cmd_queue = &(usbc_data->usbc_cmd_queue); + if (is_empty_usbc_cmd_queue(cmd_queue)) { + msg_maxim("cmd_queue is empty"); + return 0; + } + + dequeue_usbc_cmd(cmd_queue, &cmd_data_dummy); + if (cmd_data_dummy.response != cmd_data.opcode) { + msg_maxim("response unmatch (response %x, opcode %x", + cmd_data_dummy.response, cmd_data.opcode); + if (cmd_data_dummy.opcode != OPCODE_NONE) { + msg_maxim("re-enqueue opcode %x", cmd_data_dummy.opcode); + enqueue_usbc_cmd(cmd_queue, &cmd_data_dummy); + return 0; + } + } + + do { + ret = max77775_i2c_opcode_read(usbc_data, cmd_data_dummy.response, + cmd_data_dummy.read_length, rbuf); + if (retry < 8) + msleep(100); + } while ((--retry > 0) && (rbuf[0] != cmd_data_dummy.response)); + + if (rbuf[0] != cmd_data_dummy.response) { + md75_err_usb("%s: NO RESPONSE for OPCODE (response==0x%02X)\n", + __func__, rbuf[0]); + return max77775_usbc_increase_opcode_fail(usbc_data, 0); + } else { + usbc_data->opcode_fail_count = 0; + usbc_data->stuck_suppose = 0; + } + + return 0; +} + +static void max77775_usbc_cmd_run(struct max77775_usbc_platform_data *usbc_data) +{ + usbc_cmd_queue_t *cmd_queue = NULL; + usbc_cmd_data cmd_data; + int ret = 0; + + cmd_queue = &(usbc_data->usbc_cmd_queue); + + init_usbc_cmd_data(&cmd_data); + + if (is_empty_usbc_cmd_queue(cmd_queue)) { + msg_maxim("Queue, Empty"); + return; + } + + dequeue_usbc_cmd(cmd_queue, &cmd_data); + + if (is_usbc_notifier_opcode(cmd_data.opcode)) { + max77775_notify_execute(usbc_data, &cmd_data); + max77775_usbc_cmd_run(usbc_data); + } else if (cmd_data.opcode == OPCODE_NONE) {/* Apcmdres isr */ + msg_maxim("Apcmdres ISR !!!"); + max77775_irq_execute(usbc_data, &cmd_data); + usbc_data->opcode_stamp = 0; + max77775_usbc_cmd_run(usbc_data); + } else if (max77775_is_noirq_opcode(cmd_data.opcode)) { + if (!max77775_usbc_handle_noirq_opcode(usbc_data, cmd_data)) + max77775_usbc_cmd_run(usbc_data); + } else { /* No ISR */ + msg_maxim("No ISR"); + copy_usbc_cmd_data(&cmd_data, &(usbc_data->last_opcode)); + ret = max77775_i2c_opcode_write(usbc_data, cmd_data.opcode, + cmd_data.write_length, cmd_data.write_data); + if (ret < 0) { + msg_maxim("i2c write fail. dequeue opcode"); + max77775_usbc_dequeue_queue(usbc_data); + } + } +} + +int max77775_usbc_opcode_write(struct max77775_usbc_platform_data *usbc_data, + usbc_cmd_data *write_op) +{ + struct max77775_dev *max77775 = usbc_data->max77775; + usbc_cmd_queue_t *cmd_queue = &(usbc_data->usbc_cmd_queue); + usbc_cmd_data execute_cmd_data; + usbc_cmd_data current_cmd; + bool enforce = false; + + if (usbc_data->usb_mock.opcode_write) + return usbc_data->usb_mock.opcode_write((void *)usbc_data, write_op); + + if (max77775->suspended == true) { + msg_maxim("max77775 in suspend state, just return"); + return -EACCES; + } + + mutex_lock(&usbc_data->op_lock); + init_usbc_cmd_data(¤t_cmd); + + /* the messages sent to USBC. */ + init_usbc_cmd_data(&execute_cmd_data); + execute_cmd_data.opcode = write_op->opcode; + execute_cmd_data.write_length = write_op->write_length; + execute_cmd_data.is_uvdm = write_op->is_uvdm; + memcpy(execute_cmd_data.write_data, write_op->write_data, OPCODE_DATA_LENGTH); + execute_cmd_data.seq = OPCODE_WRITE_SEQ; + enqueue_usbc_cmd(cmd_queue, &execute_cmd_data); + + /* the messages recevied From USBC. */ + init_usbc_cmd_data(&execute_cmd_data); + execute_cmd_data.response = write_op->opcode; + execute_cmd_data.read_length = write_op->read_length; + execute_cmd_data.is_uvdm = write_op->is_uvdm; + execute_cmd_data.seq = OPCODE_WRITE_SEQ; + enqueue_usbc_cmd(cmd_queue, &execute_cmd_data); + + msg_maxim("W->W opcode[0x%02x] write_length[%d] read_length[%d]", + write_op->opcode, write_op->write_length, write_op->read_length); + + front_usbc_cmd(cmd_queue, ¤t_cmd); + if (current_cmd.opcode == write_op->opcode) + max77775_usbc_cmd_run(usbc_data); + else { + msg_maxim("!!!current_cmd.opcode [0x%02x][0x%02x], read_op->opcode[0x%02x]", + current_cmd.opcode, current_cmd.response, write_op->opcode); + if (usbc_data->opcode_stamp != 0 && current_cmd.opcode == OPCODE_NONE) { + if (time_after(jiffies, + usbc_data->opcode_stamp + MAX77775_MAX_APDCMD_TIME)) { + usbc_data->opcode_stamp = 0; + msg_maxim("error. we will dequeue response data"); + max77775_usbc_dequeue_queue(usbc_data); + if (max77775_is_stuck_suppose_opcode(current_cmd.response)) + enforce = true; + if (max77775_usbc_increase_opcode_fail(usbc_data, enforce)) + goto out; + max77775_usbc_cmd_run(usbc_data); + } + } + } +out: + mutex_unlock(&usbc_data->op_lock); + + return 0; +} + +int max77775_usbc_opcode_read(struct max77775_usbc_platform_data *usbc_data, + usbc_cmd_data *read_op) +{ + struct max77775_dev *max77775 = usbc_data->max77775; + usbc_cmd_queue_t *cmd_queue = &(usbc_data->usbc_cmd_queue); + usbc_cmd_data execute_cmd_data; + usbc_cmd_data current_cmd; + bool enforce = false; + + if (usbc_data->usb_mock.opcode_read) + return usbc_data->usb_mock.opcode_read((void *)usbc_data, read_op); + + if (max77775->suspended == true) { + msg_maxim("max77775 in suspend state, just return"); + return -EACCES; + } + + mutex_lock(&usbc_data->op_lock); + init_usbc_cmd_data(¤t_cmd); + + /* the messages sent to USBC. */ + init_usbc_cmd_data(&execute_cmd_data); + execute_cmd_data.opcode = read_op->opcode; + execute_cmd_data.write_length = read_op->write_length; + execute_cmd_data.is_uvdm = read_op->is_uvdm; + memcpy(execute_cmd_data.write_data, read_op->write_data, read_op->write_length); + execute_cmd_data.seq = OPCODE_READ_SEQ; + enqueue_usbc_cmd(cmd_queue, &execute_cmd_data); + + /* the messages recevied From USBC. */ + init_usbc_cmd_data(&execute_cmd_data); + execute_cmd_data.response = read_op->opcode; + execute_cmd_data.read_length = read_op->read_length; + execute_cmd_data.is_uvdm = read_op->is_uvdm; + execute_cmd_data.seq = OPCODE_READ_SEQ; + enqueue_usbc_cmd(cmd_queue, &execute_cmd_data); + + msg_maxim("R->R opcode[0x%02x] write_length[%d] read_length[%d]", + read_op->opcode, read_op->write_length, read_op->read_length); + + front_usbc_cmd(cmd_queue, ¤t_cmd); + if (current_cmd.opcode == read_op->opcode) + max77775_usbc_cmd_run(usbc_data); + else { + msg_maxim("!!!current_cmd.opcode [0x%02x][0x%02x], read_op->opcode[0x%02x]", + current_cmd.opcode, current_cmd.response, read_op->opcode); + if (usbc_data->opcode_stamp != 0 && current_cmd.opcode == OPCODE_NONE) { + if (time_after(jiffies, + usbc_data->opcode_stamp + MAX77775_MAX_APDCMD_TIME)) { + usbc_data->opcode_stamp = 0; + msg_maxim("error. we will dequeue response data"); + max77775_usbc_dequeue_queue(usbc_data); + if (max77775_is_stuck_suppose_opcode(current_cmd.response)) + enforce = true; + if (max77775_usbc_increase_opcode_fail(usbc_data, enforce)) + goto out; + max77775_usbc_cmd_run(usbc_data); + } + } + } +out: + mutex_unlock(&usbc_data->op_lock); + + return 0; +} + +int max77775_usbc_opcode_update(struct max77775_usbc_platform_data *usbc_data, + usbc_cmd_data *update_op) +{ + struct max77775_dev *max77775 = usbc_data->max77775; + usbc_cmd_queue_t *cmd_queue = &(usbc_data->usbc_cmd_queue); + usbc_cmd_data execute_cmd_data; + usbc_cmd_data current_cmd; + bool enforce = false; + + if (usbc_data->usb_mock.opcode_update) + return usbc_data->usb_mock.opcode_update((void *)usbc_data, update_op); + + if (max77775->suspended == true) { + msg_maxim("max77775 in suspend state, just return"); + return -EACCES; + } + + switch (update_op->opcode) { + case OPCODE_BCCTRL1_R: + case OPCODE_BCCTRL2_R: + case OPCODE_CTRL1_R: + case OPCODE_CCCTRL1_R: + case OPCODE_CCCTRL2_R: + case OPCODE_CCCTRL3_R: + case OPCODE_CCCTRL4_R: + case OPCODE_VCONN_ILIM_R: + break; + default: + md75_err_usb("%s: invalid usage(0x%x), return\n", __func__, update_op->opcode); + return -EINVAL; + } + + mutex_lock(&usbc_data->op_lock); + init_usbc_cmd_data(¤t_cmd); + + /* the messages sent to USBC. */ + init_usbc_cmd_data(&execute_cmd_data); + execute_cmd_data.opcode = update_op->opcode; + execute_cmd_data.write_length = 0; + execute_cmd_data.is_uvdm = update_op->is_uvdm; + memcpy(execute_cmd_data.write_data, update_op->write_data, update_op->write_length); + execute_cmd_data.seq = OPCODE_UPDATE_SEQ; + enqueue_usbc_cmd(cmd_queue, &execute_cmd_data); + + /* the messages recevied From USBC. */ + init_usbc_cmd_data(&execute_cmd_data); + execute_cmd_data.response = update_op->opcode; + execute_cmd_data.read_length = 1; + execute_cmd_data.seq = OPCODE_UPDATE_SEQ; + execute_cmd_data.val = update_op->val; + execute_cmd_data.mask = update_op->mask; + execute_cmd_data.is_uvdm = update_op->is_uvdm; + enqueue_usbc_cmd(cmd_queue, &execute_cmd_data); + + msg_maxim("U->U opcode[0x%02x] write_length[%d] read_length[%d]", + update_op->opcode, update_op->write_length, update_op->read_length); + + front_usbc_cmd(cmd_queue, ¤t_cmd); + if (current_cmd.opcode == update_op->opcode) + max77775_usbc_cmd_run(usbc_data); + else { + msg_maxim("!!! current_cmd.opcode [0x%02x], update_op->opcode[0x%02x]", + current_cmd.opcode, update_op->opcode); + if (usbc_data->opcode_stamp != 0 && current_cmd.opcode == OPCODE_NONE) { + if (time_after(jiffies, + usbc_data->opcode_stamp + MAX77775_MAX_APDCMD_TIME)) { + usbc_data->opcode_stamp = 0; + msg_maxim("error. we will dequeue response data"); + max77775_usbc_dequeue_queue(usbc_data); + if (max77775_is_stuck_suppose_opcode(current_cmd.response)) + enforce = true; + if (max77775_usbc_increase_opcode_fail(usbc_data, enforce)) + goto out; + max77775_usbc_cmd_run(usbc_data); + } + } + } +out: + mutex_unlock(&usbc_data->op_lock); + + return 0; +} + +int max77775_usbc_opcode_push(struct max77775_usbc_platform_data *usbc_data, + usbc_cmd_data *read_op) +{ + struct max77775_dev *max77775 = usbc_data->max77775; + usbc_cmd_queue_t *cmd_queue = &(usbc_data->usbc_cmd_queue); + usbc_cmd_data execute_cmd_data; + usbc_cmd_data current_cmd; + + if (usbc_data->usb_mock.opcode_push) + return usbc_data->usb_mock.opcode_push((void *)usbc_data, read_op); + + if (max77775->suspended == true) { + msg_maxim("max77775 in suspend state, just return"); + return -EACCES; + } + + init_usbc_cmd_data(¤t_cmd); + + /* the messages sent to USBC. */ + init_usbc_cmd_data(&execute_cmd_data); + execute_cmd_data.opcode = read_op->opcode; + execute_cmd_data.write_length = read_op->write_length; + execute_cmd_data.is_uvdm = read_op->is_uvdm; + memcpy(execute_cmd_data.write_data, read_op->write_data, read_op->write_length); + execute_cmd_data.seq = OPCODE_PUSH_SEQ; + enqueue_usbc_cmd(cmd_queue, &execute_cmd_data); + + /* the messages recevied From USBC. */ + init_usbc_cmd_data(&execute_cmd_data); + execute_cmd_data.response = read_op->opcode; + execute_cmd_data.read_length = read_op->read_length; + execute_cmd_data.is_uvdm = read_op->is_uvdm; + execute_cmd_data.seq = OPCODE_PUSH_SEQ; + enqueue_usbc_cmd(cmd_queue, &execute_cmd_data); + + msg_maxim("P->P opcode[0x%02x] write_length[%d] read_length[%d]", + read_op->opcode, read_op->write_length, read_op->read_length); + + return 0; +} + +int max77775_usbc_opcode_rw(struct max77775_usbc_platform_data *usbc_data, + usbc_cmd_data *read_op, usbc_cmd_data *write_op) +{ + struct max77775_dev *max77775 = usbc_data->max77775; + usbc_cmd_queue_t *cmd_queue = &(usbc_data->usbc_cmd_queue); + usbc_cmd_data execute_cmd_data; + usbc_cmd_data current_cmd; + bool enforce = false; + + if (usbc_data->usb_mock.opcode_rw) + return usbc_data->usb_mock.opcode_rw((void *)usbc_data, read_op, write_op); + + if (max77775->suspended == true) { + msg_maxim("max77775 in suspend state, just return"); + return -EACCES; + } + + mutex_lock(&usbc_data->op_lock); + init_usbc_cmd_data(¤t_cmd); + + /* the messages sent to USBC. */ + init_usbc_cmd_data(&execute_cmd_data); + execute_cmd_data.opcode = read_op->opcode; + execute_cmd_data.write_length = read_op->write_length; + execute_cmd_data.is_uvdm = read_op->is_uvdm; + memcpy(execute_cmd_data.write_data, read_op->write_data, read_op->write_length); + execute_cmd_data.seq = OPCODE_RW_SEQ; + enqueue_usbc_cmd(cmd_queue, &execute_cmd_data); + + /* the messages recevied From USBC. */ + init_usbc_cmd_data(&execute_cmd_data); + execute_cmd_data.response = read_op->opcode; + execute_cmd_data.read_length = read_op->read_length; + execute_cmd_data.is_uvdm = read_op->is_uvdm; + execute_cmd_data.seq = OPCODE_RW_SEQ; + enqueue_usbc_cmd(cmd_queue, &execute_cmd_data); + + /* the messages sent to USBC. */ + init_usbc_cmd_data(&execute_cmd_data); + execute_cmd_data.opcode = write_op->opcode; + execute_cmd_data.write_length = write_op->write_length; + execute_cmd_data.is_uvdm = write_op->is_uvdm; + memcpy(execute_cmd_data.write_data, write_op->write_data, OPCODE_DATA_LENGTH); + execute_cmd_data.seq = OPCODE_RW_SEQ; + enqueue_usbc_cmd(cmd_queue, &execute_cmd_data); + + /* the messages recevied From USBC. */ + init_usbc_cmd_data(&execute_cmd_data); + execute_cmd_data.response = write_op->opcode; + execute_cmd_data.read_length = write_op->read_length; + execute_cmd_data.is_uvdm = write_op->is_uvdm; + execute_cmd_data.seq = OPCODE_RW_SEQ; + enqueue_usbc_cmd(cmd_queue, &execute_cmd_data); + + msg_maxim("RW->R opcode[0x%02x] write_length[%d] read_length[%d]", + read_op->opcode, read_op->write_length, read_op->read_length); + msg_maxim("RW->W opcode[0x%02x] write_length[%d] read_length[%d]", + write_op->opcode, write_op->write_length, write_op->read_length); + + front_usbc_cmd(cmd_queue, ¤t_cmd); + if (current_cmd.opcode == read_op->opcode) + max77775_usbc_cmd_run(usbc_data); + else { + msg_maxim("!!! current_cmd.opcode [0x%02x], read_op->opcode[0x%02x]", + current_cmd.opcode, read_op->opcode); + if (usbc_data->opcode_stamp != 0 && current_cmd.opcode == OPCODE_NONE) { + if (time_after(jiffies, + usbc_data->opcode_stamp + MAX77775_MAX_APDCMD_TIME)) { + usbc_data->opcode_stamp = 0; + msg_maxim("error. we will dequeue response data"); + max77775_usbc_dequeue_queue(usbc_data); + if (max77775_is_stuck_suppose_opcode(current_cmd.response)) + enforce = true; + if (max77775_usbc_increase_opcode_fail(usbc_data, enforce)) + goto out; + max77775_usbc_cmd_run(usbc_data); + } + } + } +out: + mutex_unlock(&usbc_data->op_lock); + + return 0; +} + +/* + * max77775_uic_op_send_work_func func - Send OPCode + * @work: work_struct of max77775_i2c + * + * Wait for OPCode response + */ +static void max77775_uic_op_send_work_func( + struct work_struct *work) +{ + struct max77775_usbc_platform_data *usbc_data; + struct max77775_opcode opcodes[] = { + { + .opcode = OPCODE_BCCTRL1_R, + .data = { 0, }, + .write_length = 0, + .read_length = 1, + }, + { + .opcode = OPCODE_BCCTRL1_W, + .data = { 0x20, }, + .write_length = 1, + .read_length = 0, + }, + { + .opcode = OPCODE_BCCTRL2_R, + .data = { 0, }, + .write_length = 0, + .read_length = 1, + }, + { + .opcode = OPCODE_BCCTRL2_W, + .data = { 0x10, }, + .write_length = 1, + .read_length = 0, + }, + { + .opcode = OPCODE_CURRENT_SRCCAP, + .data = { 0, }, + .write_length = 1, + .read_length = 4, + }, + { + .opcode = OPCODE_AFC_HV_W, + .data = { 0x46, }, + .write_length = 1, + .read_length = 2, + }, + { + .opcode = OPCODE_SRCCAP_REQUEST, + .data = { 0x00, }, + .write_length = 1, + .read_length = 32, + }, + }; + int op_loop; + + usbc_data = container_of(work, struct max77775_usbc_platform_data, op_send_work); + + msg_maxim("IN"); + for (op_loop = 0; op_loop < ARRAY_SIZE(opcodes); op_loop++) { + if (usbc_data->op_code == opcodes[op_loop].opcode) { + max77775_i2c_opcode_write(usbc_data, opcodes[op_loop].opcode, + opcodes[op_loop].write_length, opcodes[op_loop].data); + break; + } + } + msg_maxim("OUT"); +} + +static void max77775_reset_ic(struct max77775_usbc_platform_data *usbc_data) +{ + struct max77775_dev *max77775 = usbc_data->max77775; + + //gurantee to block i2c trasaction during ccic reset + msg_maxim("IN"); + mutex_lock(&max77775->i2c_lock); + max77775_write_reg_nolock(usbc_data->muic, 0x80, 0x0F); + msleep(150); /* need 150ms delay */ + mutex_unlock(&max77775->i2c_lock); + msg_maxim("OUT"); +} + +void max77775_usbc_check_sysmsg(struct max77775_usbc_platform_data *usbc_data, u8 sysmsg) +{ + usbc_cmd_queue_t *cmd_queue = &(usbc_data->usbc_cmd_queue); + bool is_empty_queue = is_empty_usbc_cmd_queue(cmd_queue); + usbc_cmd_data cmd_data; + usbc_cmd_data next_cmd_data; + u8 next_opcode = 0xFF; + u8 interrupt = 0; +#if defined(CONFIG_USB_HW_PARAM) + struct otg_notify *o_notify = get_otg_notify(); +#endif +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + int event; +#endif + int ret = 0; + + if (usbc_data->shut_down) { + msg_maxim("IGNORE SYSTEM_MSG IN SHUTDOWN MODE!!"); + return; + } + + if (usbc_data->stuck_suppose && + !(sysmsg == SYSERROR_BOOT_WDT || sysmsg == SYSMSG_BOOT_POR)) { + msg_maxim("Do usbc Reset operation, opcode_fail_count: %d", usbc_data->opcode_fail_count); + max77775_usbc_mask_irq(usbc_data); + max77775_reset_ic(usbc_data); + max77775_write_reg(usbc_data->muic, REG_UIC_INT_M, REG_UIC_INT_M_INIT); + max77775_write_reg(usbc_data->muic, REG_CC_INT_M, REG_CC_INT_M_INIT); + max77775_write_reg(usbc_data->muic, REG_PD_INT_M, REG_PD_INT_M_INIT); + max77775_write_reg(usbc_data->muic, REG_VDM_INT_M, REG_VDM_INT_M_INIT); + max77775_write_reg(usbc_data->muic, REG_SPARE_INT_M, REG_SPARE_INT_M_INIT); + /* clear UIC_INT to prevent infinite sysmsg irq*/ + g_usbc_data->max77775->enable_nested_irq = 1; + max77775_read_reg(usbc_data->muic, MAX77775_USBC_REG_UIC_INT, &interrupt); + g_usbc_data->max77775->usbc_irq = interrupt & 0xBF; //clear the USBC SYSTEM IRQ + max77775_usbc_clear_queue(usbc_data); + usbc_data->opcode_fail_count = 0; + usbc_data->stuck_suppose = 0; + usbc_data->is_first_booting = 1; + usbc_data->pd_data->bPPS_on = false; + max77775_init_opcode(usbc_data, 1); + max77775_usbc_umask_irq(usbc_data); + } + + switch (sysmsg) { + case SYSERROR_NONE: + break; + case SYSERROR_BOOT_WDT: + usbc_data->watchdog_count++; + msg_maxim("SYSERROR_BOOT_WDT: %d", usbc_data->watchdog_count); + max77775_usbc_mask_irq(usbc_data); + max77775_write_reg(usbc_data->muic, REG_UIC_INT_M, REG_UIC_INT_M_INIT); + max77775_write_reg(usbc_data->muic, REG_CC_INT_M, REG_CC_INT_M_INIT); + max77775_write_reg(usbc_data->muic, REG_PD_INT_M, REG_PD_INT_M_INIT); + max77775_write_reg(usbc_data->muic, REG_VDM_INT_M, REG_VDM_INT_M_INIT); + max77775_write_reg(usbc_data->muic, REG_SPARE_INT_M, REG_SPARE_INT_M_INIT); + /* clear UIC_INT to prevent infinite sysmsg irq*/ + g_usbc_data->max77775->enable_nested_irq = 1; + max77775_read_reg(usbc_data->muic, MAX77775_USBC_REG_UIC_INT, &interrupt); + g_usbc_data->max77775->usbc_irq = interrupt & 0xBF; //clear the USBC SYSTEM IRQ + max77775_usbc_clear_queue(usbc_data); + usbc_data->opcode_fail_count = 0; + usbc_data->stuck_suppose = 0; + usbc_data->is_first_booting = 1; + usbc_data->pd_data->bPPS_on = false; + max77775_init_opcode(usbc_data, 1); + max77775_usbc_umask_irq(usbc_data); +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_SYSERROR_BOOT_WDT; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + break; + case SYSERROR_BOOT_SWRSTREQ: + break; + case SYSMSG_BOOT_POR: + usbc_data->por_count++; + max77775_usbc_mask_irq(usbc_data); + max77775_reset_ic(usbc_data); + max77775_write_reg(usbc_data->muic, REG_UIC_INT_M, REG_UIC_INT_M_INIT); + max77775_write_reg(usbc_data->muic, REG_CC_INT_M, REG_CC_INT_M_INIT); + max77775_write_reg(usbc_data->muic, REG_PD_INT_M, REG_PD_INT_M_INIT); + max77775_write_reg(usbc_data->muic, REG_VDM_INT_M, REG_VDM_INT_M_INIT); + max77775_write_reg(usbc_data->muic, REG_SPARE_INT_M, REG_SPARE_INT_M_INIT); + /* clear UIC_INT to prevent infinite sysmsg irq*/ + g_usbc_data->max77775->enable_nested_irq = 1; + max77775_read_reg(usbc_data->muic, MAX77775_USBC_REG_UIC_INT, &interrupt); + g_usbc_data->max77775->usbc_irq = interrupt & 0xBF; //clear the USBC SYSTEM IRQ + msg_maxim("SYSERROR_BOOT_POR: %d, UIC_INT:0x%02x", usbc_data->por_count, interrupt); + max77775_usbc_clear_queue(usbc_data); + usbc_data->opcode_fail_count = 0; + usbc_data->stuck_suppose = 0; + usbc_data->is_first_booting = 1; + usbc_data->pd_data->bPPS_on = false; + max77775_init_opcode(usbc_data, 1); + max77775_usbc_umask_irq(usbc_data); +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_SYSMSG_BOOT_POR; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + break; + case SYSERROR_HV_NOVBUS: + break; + case SYSERROR_HV_FMETHOD_RXPERR: + break; + case SYSERROR_HV_FMETHOD_RXBUFOW: + break; + case SYSERROR_HV_FMETHOD_RXTFR: + break; + case SYSERROR_HV_FMETHOD_MPNACK: + break; + case SYSERROR_HV_FMETHOD_RESET_FAIL: + break; + case SYSMsg_AFC_Done: + break; + case SYSERROR_SYSPOS: + break; + case SYSERROR_APCMD_UNKNOWN: + break; + case SYSERROR_APCMD_INPROGRESS: + break; + case SYSERROR_APCMD_FAIL: + + init_usbc_cmd_data(&cmd_data); + init_usbc_cmd_data(&next_cmd_data); + + if (front_usbc_cmd(cmd_queue, &next_cmd_data)) + next_opcode = next_cmd_data.response; + + if (!is_empty_queue) { + copy_usbc_cmd_data(&(usbc_data->last_opcode), &cmd_data); + + if (next_opcode == OPCODE_VDM_DISCOVER_SET_VDM_REQ) { + usbc_data->opcode_stamp = 0; + max77775_usbc_dequeue_queue(usbc_data); + cmd_data.opcode = OPCODE_NONE; + } + + if ((cmd_data.opcode != OPCODE_NONE) && (cmd_data.opcode == next_opcode)) { + if (next_opcode != OPCODE_VDM_DISCOVER_SET_VDM_REQ) { + ret = max77775_i2c_opcode_write(usbc_data, + cmd_data.opcode, + cmd_data.write_length, + cmd_data.write_data); + if (ret) { + msg_maxim("i2c write fail. dequeue opcode"); + max77775_usbc_dequeue_queue(usbc_data); + } else + msg_maxim("RETRY SUCCESS : %x, %x", cmd_data.opcode, next_opcode); + } else + msg_maxim("IGNORE COMMAND : %x, %x", cmd_data.opcode, next_opcode); + } else { + msg_maxim("RETRY FAILED : %x, %x", cmd_data.opcode, next_opcode); + } + + } + + /* TO DO DEQEUE MSG. */ + break; + case SYSMSG_CCx_5V_SHORT: + case SYSMSG_CCx_5V_AFC_SHORT: + msg_maxim("CC-VBUS SHORT"); + usbc_data->pd_data->cc_sbu_short = true; +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_VBUS_CC_SHORT_COUNT); +#endif +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_SYSMSG_CC_SHORT; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + usbc_data->cc_data->ccistat = CCI_SHORT; + max77775_notify_rp_current_level(usbc_data); + break; + case SYSMSG_SBUx_GND_SHORT: + msg_maxim("SBU-GND SHORT"); +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_GND_SBU_SHORT_COUNT); +#endif +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_SYSMSG_SBU_GND_SHORT; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + break; + case SYSMSG_SBUx_5V_SHORT: + msg_maxim("SBU-VBUS SHORT"); + usbc_data->pd_data->cc_sbu_short = true; +#if defined(CONFIG_USB_HW_PARAM) + if (o_notify) + inc_hw_param(o_notify, USB_CCIC_VBUS_SBU_SHORT_COUNT); +#endif +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_SYSMSG_SBU_VBUS_SHORT; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + break; + case SYSERROR_POWER_NEGO: + if (!usbc_data->last_opcode.is_uvdm) { /* structured vdm */ + if (usbc_data->last_opcode.opcode == OPCODE_VDM_DISCOVER_SET_VDM_REQ) { + max77775_usbc_opcode_write(usbc_data, &usbc_data->last_opcode); + msg_maxim("SYSMSG PWR NEGO ERR : VDM request retry"); + } + } else { /* unstructured vdm */ + usbc_data->uvdm_error = -EACCES; + msg_maxim("SYSMSG PWR NEGO ERR : UVDM request error - dir : %d", + usbc_data->is_in_sec_uvdm_out); + if (usbc_data->is_in_sec_uvdm_out == DIR_OUT) + complete(&usbc_data->uvdm_longpacket_out_wait); + else if (usbc_data->is_in_sec_uvdm_out == DIR_IN) + complete(&usbc_data->uvdm_longpacket_in_wait); + else + ; + } + break; +#if defined(CONFIG_SEC_FACTORY) + case SYSERROR_FACTORY_RID0: + factory_execute_monitor(FAC_ABNORMAL_REPEAT_RID0); + break; +#endif + case SYSERROR_CCRP_HIGH: + msg_maxim("CCRP HIGH"); +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + if (usbc_data->ccrp_state != 1) { + usbc_data->ccrp_state = 1; + max77775_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_BATT, PDIC_NOTIFY_ID_WATER_CABLE, + PDIC_NOTIFY_ATTACH, 0/*rprd*/, 0); + } +#endif + break; + case SYSERROR_CCRP_LOW: + msg_maxim("CCRP LOW"); +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + if (usbc_data->ccrp_state != 0) { + usbc_data->ccrp_state = 0; + max77775_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_BATT, PDIC_NOTIFY_ID_WATER_CABLE, + PDIC_NOTIFY_DETACH, 0/*rprd*/, 0); + } +#endif + break; + case SYSMSG_ABNORMAL_TA: + { + union power_supply_propval value = {0,}; + + msg_maxim("ABNORMAL TA detected"); + value.intval = true; + psy_do_property("battery", set, + POWER_SUPPLY_EXT_PROP_ABNORMAL_TA, value); + } + break; + case SYSMSG_RELEASE_CC1_SHORT: + case SYSMSG_RELEASE_CC2_SHORT: + msg_maxim("CCX_RELEASE_SHORT"); + usbc_data->pd_data->cc_sbu_short = false; + usbc_data->cc_data->ccistat = NOT_IN_UFP_MODE; + break; + default: + break; + } +} + +bool max77775_need_check_stuck(struct max77775_usbc_platform_data *usbc_data) +{ + usbc_cmd_queue_t *cmd_queue = &(usbc_data->usbc_cmd_queue); + usbc_cmd_data current_cmd; + bool ret = false; + + mutex_lock(&usbc_data->op_lock); + + if (front_usbc_cmd(cmd_queue, ¤t_cmd)) { + if (usbc_data->opcode_stamp != 0 && current_cmd.opcode == OPCODE_NONE) { + if (time_after(jiffies, + usbc_data->opcode_stamp + MAX77775_MAX_APDCMD_TIME)) + ret = true; + } + } + + mutex_unlock(&usbc_data->op_lock); + + return ret; +} + +void max77775_send_check_stuck_opcode(struct max77775_usbc_platform_data *usbpd_data) +{ + usbc_cmd_data read_data; + + msg_maxim("%s", __func__); + + init_usbc_cmd_data(&read_data); + read_data.opcode = OPCODE_BCCTRL1_R; + max77775_usbc_opcode_read(usbpd_data, &read_data); +} + +static irqreturn_t max77775_apcmd_irq(int irq, void *data) +{ + struct max77775_usbc_platform_data *usbc_data = data; + u8 sysmsg = 0; + + usbc_data->opcode_fail_count = 0; + usbc_data->stuck_suppose = 0; + msg_maxim("IRQ(%d)_IN", irq); + max77775_read_reg(usbc_data->muic, REG_USBC_STATUS2, &usbc_data->usbc_status2); + sysmsg = usbc_data->usbc_status2; + msg_maxim(" [IN] sysmsg : %d", sysmsg); + + mutex_lock(&usbc_data->op_lock); + max77775_usbc_cmd_run(usbc_data); + mutex_unlock(&usbc_data->op_lock); + + if (usbc_data->need_recover) { + max77775_recover_opcode(usbc_data, + usbc_data->recover_opcode_list); + usbc_data->need_recover = false; + } + + msg_maxim("IRQ(%d)_OUT", irq); + + return IRQ_HANDLED; +} + +static irqreturn_t max77775_sysmsg_irq(int irq, void *data) +{ + struct max77775_usbc_platform_data *usbc_data = data; + u8 sysmsg = 0; + u8 i = 0; + u8 raw_data[3] = {0, }; + u8 usbc_status2 = 0; + u8 dump_reg[10] = {0, }; + + for (i = 0; i < 3; i++) { + usbc_status2 = 0; + max77775_read_reg(usbc_data->muic, REG_USBC_STATUS2, &usbc_status2); + raw_data[i] = usbc_status2; + } + if ((raw_data[0] == raw_data[1]) && (raw_data[0] == raw_data[2])) { + sysmsg = raw_data[0]; + } else { + max77775_bulk_read(usbc_data->muic, REG_USBC_STATUS1, 8, dump_reg); + msg_maxim("[ERROR ]sys_reg, %x, %x, %x", raw_data[0], raw_data[1], raw_data[2]); + msg_maxim("[ERROR ]dump_reg, %x, %x, %x, %x, %x, %x, %x, %x\n", dump_reg[0], dump_reg[1], + dump_reg[2], dump_reg[3], dump_reg[4], dump_reg[5], dump_reg[6], dump_reg[7]); + sysmsg = 0x6D; + } + msg_maxim("IRQ(%d)_IN sysmsg: %x", irq, sysmsg); + max77775_usbc_check_sysmsg(usbc_data, sysmsg); + usbc_data->sysmsg = sysmsg; + msg_maxim("IRQ(%d)_OUT sysmsg: %x", irq, sysmsg); + + return IRQ_HANDLED; +} + + +static irqreturn_t max77775_vdm_identity_irq(int irq, void *data) +{ + struct max77775_usbc_platform_data *usbc_data = data; + MAX77775_VDM_MSG_IRQ_STATUS_Type VDM_MSG_IRQ_State; + + memset(&VDM_MSG_IRQ_State, 0, sizeof(VDM_MSG_IRQ_State)); + msg_maxim("IRQ(%d)_IN", irq); + VDM_MSG_IRQ_State.BITS.Vdm_Flag_Discover_ID = 1; + max77775_receive_alternate_message(usbc_data, &VDM_MSG_IRQ_State); + msg_maxim("IRQ(%d)_OUT", irq); + + return IRQ_HANDLED; +} + +static irqreturn_t max77775_vdm_svids_irq(int irq, void *data) +{ + struct max77775_usbc_platform_data *usbc_data = data; + MAX77775_VDM_MSG_IRQ_STATUS_Type VDM_MSG_IRQ_State; + + memset(&VDM_MSG_IRQ_State, 0, sizeof(VDM_MSG_IRQ_State)); + msg_maxim("IRQ(%d)_IN", irq); + VDM_MSG_IRQ_State.BITS.Vdm_Flag_Discover_SVIDs = 1; + max77775_receive_alternate_message(usbc_data, &VDM_MSG_IRQ_State); + msg_maxim("IRQ(%d)_OUT", irq); + return IRQ_HANDLED; +} + +static irqreturn_t max77775_vdm_discover_mode_irq(int irq, void *data) +{ + struct max77775_usbc_platform_data *usbc_data = data; + MAX77775_VDM_MSG_IRQ_STATUS_Type VDM_MSG_IRQ_State; + + memset(&VDM_MSG_IRQ_State, 0, sizeof(VDM_MSG_IRQ_State)); + msg_maxim("IRQ(%d)_IN", irq); + VDM_MSG_IRQ_State.BITS.Vdm_Flag_Discover_MODEs = 1; + max77775_receive_alternate_message(usbc_data, &VDM_MSG_IRQ_State); + msg_maxim("IRQ(%d)_OUT", irq); + return IRQ_HANDLED; +} + +static irqreturn_t max77775_vdm_enter_mode_irq(int irq, void *data) +{ + struct max77775_usbc_platform_data *usbc_data = data; + MAX77775_VDM_MSG_IRQ_STATUS_Type VDM_MSG_IRQ_State; + + memset(&VDM_MSG_IRQ_State, 0, sizeof(VDM_MSG_IRQ_State)); + msg_maxim("IRQ(%d)_IN", irq); + VDM_MSG_IRQ_State.BITS.Vdm_Flag_Enter_Mode = 1; + max77775_receive_alternate_message(usbc_data, &VDM_MSG_IRQ_State); + msg_maxim("IRQ(%d)_OUT", irq); + return IRQ_HANDLED; +} + +static irqreturn_t max77775_vdm_dp_status_irq(int irq, void *data) +{ + struct max77775_usbc_platform_data *usbc_data = data; + MAX77775_VDM_MSG_IRQ_STATUS_Type VDM_MSG_IRQ_State; + + memset(&VDM_MSG_IRQ_State, 0, sizeof(VDM_MSG_IRQ_State)); + msg_maxim("IRQ(%d)_IN", irq); + VDM_MSG_IRQ_State.BITS.Vdm_Flag_DP_Status_Update = 1; + max77775_receive_alternate_message(usbc_data, &VDM_MSG_IRQ_State); + msg_maxim("IRQ(%d)_OUT", irq); + return IRQ_HANDLED; +} + +static irqreturn_t max77775_vdm_dp_configure_irq(int irq, void *data) +{ + struct max77775_usbc_platform_data *usbc_data = data; + MAX77775_VDM_MSG_IRQ_STATUS_Type VDM_MSG_IRQ_State; + + memset(&VDM_MSG_IRQ_State, 0, sizeof(VDM_MSG_IRQ_State)); + msg_maxim("IRQ(%d)_IN", irq); + VDM_MSG_IRQ_State.BITS.Vdm_Flag_DP_Configure = 1; + max77775_receive_alternate_message(usbc_data, &VDM_MSG_IRQ_State); + msg_maxim("IRQ(%d)_OUT", irq); + return IRQ_HANDLED; +} + +static irqreturn_t max77775_vdm_attention_irq(int irq, void *data) +{ + struct max77775_usbc_platform_data *usbc_data = data; + MAX77775_VDM_MSG_IRQ_STATUS_Type VDM_MSG_IRQ_State; + + memset(&VDM_MSG_IRQ_State, 0, sizeof(VDM_MSG_IRQ_State)); + msg_maxim("IRQ(%d)_IN", irq); + VDM_MSG_IRQ_State.BITS.Vdm_Flag_Attention = 1; + max77775_receive_alternate_message(usbc_data, &VDM_MSG_IRQ_State); + msg_maxim("IRQ(%d)_OUT", irq); + return IRQ_HANDLED; +} + +static irqreturn_t max77775_vir_altmode_irq(int irq, void *data) +{ + struct max77775_usbc_platform_data *usbc_data = data; + + msg_maxim("max77775_vir_altmode_irq"); + + if (usbc_data->shut_down) { + msg_maxim("%s doing shutdown. skip set alternate mode", __func__); + goto skip; + } + + max77775_set_enable_alternate_mode + (usbc_data->set_altmode); + +skip: + return IRQ_HANDLED; +} + +static irqreturn_t max77775_usbid_irq(int irq, void *data) +{ +#if 0 /* TODO */ + struct max77775_usbc_platform_data *usbc_data = data; +#endif + + msg_maxim("IRQ(%d)_IN", irq); + /* TODO */ + msg_maxim("IRQ(%d)_OUT", irq); + return IRQ_HANDLED; +} + +#if defined(CONFIG_MAX77775_CCOPEN_AFTER_WATERCABLE) +static irqreturn_t max77775_taconnect_irq(int irq, void *data) +{ + struct max77775_usbc_platform_data *usbc_data = data; + u8 spare_status1 = 0; + u8 cc_open_status = 0; + + msg_maxim("%s: IRQ(%d)_IN", __func__, irq); + if (usbc_data->current_connstat == WATER) { + max77775_read_reg(usbc_data->muic, MAX77775_USBC_REG_SPARE_STATUS1, &spare_status1); + usbc_data->ta_conn_status = (spare_status1 & BIT_SPARE_TA_CONNECT) >> FFS(BIT_SPARE_TA_CONNECT); + cc_open_status = (spare_status1 & BIT_SPARE_CC_OPEN) >> FFS(BIT_SPARE_CC_OPEN); + /*Source Connection Status of Moisture Case, 0x0: unplug TA, 0x1:plug TA*/ + if (usbc_data->ta_conn_status == 0x1) { + msg_maxim("Water Cable Attached (CC OPEN Status : %s)", cc_open_status ? "Enabled" : "Disabled"); + cancel_delayed_work(&usbc_data->set_ccopen_for_watercable_work); + schedule_delayed_work(&usbc_data->set_ccopen_for_watercable_work, msecs_to_jiffies(100)); + } else { + msg_maxim("Water Cable Detached (CC OPEN Status : %s)", cc_open_status ? "Enabled" : "Disabled"); + cancel_delayed_work(&usbc_data->set_ccopen_for_watercable_work); + } + } + msg_maxim("%s: IRQ(%d)_OUT", __func__, irq); + return IRQ_HANDLED; +} +#endif + +int max77775_init_irq_handler(struct max77775_usbc_platform_data *usbc_data) +{ + int ret = 0; + + usbc_data->irq_apcmd = usbc_data->irq_base + MAX77775_USBC_IRQ_APC_INT; + if (usbc_data->irq_apcmd) { + ret = request_threaded_irq(usbc_data->irq_apcmd, + NULL, max77775_apcmd_irq, + 0, + "usbc-apcmd-irq", usbc_data); + if (ret) { + md75_err_usb("%s: Failed to Request IRQ (%d)\n", __func__, ret); + return ret; + } + } + + usbc_data->irq_sysmsg = usbc_data->irq_base + MAX77775_USBC_IRQ_SYSM_INT; + if (usbc_data->irq_sysmsg) { + ret = request_threaded_irq(usbc_data->irq_sysmsg, + NULL, max77775_sysmsg_irq, + 0, + "usbc-sysmsg-irq", usbc_data); + if (ret) { + md75_err_usb("%s: Failed to Request IRQ (%d)\n", __func__, ret); + return ret; + } + } + + usbc_data->irq_vdm0 = usbc_data->irq_base + MAX77775_IRQ_VDM_DISCOVER_ID_INT; + if (usbc_data->irq_vdm0) { + ret = request_threaded_irq(usbc_data->irq_vdm0, + NULL, max77775_vdm_identity_irq, + 0, + "usbc-vdm0-irq", usbc_data); + if (ret) { + md75_err_usb("%s: Failed to Request IRQ (%d)\n", __func__, ret); + return ret; + } + } + + usbc_data->irq_vdm1 = usbc_data->irq_base + MAX77775_IRQ_VDM_DISCOVER_SVIDS_INT; + if (usbc_data->irq_vdm1) { + ret = request_threaded_irq(usbc_data->irq_vdm1, + NULL, max77775_vdm_svids_irq, + 0, + "usbc-vdm1-irq", usbc_data); + if (ret) { + md75_err_usb("%s: Failed to Request IRQ (%d)\n", __func__, ret); + return ret; + } + } + + usbc_data->irq_vdm2 = usbc_data->irq_base + MAX77775_IRQ_VDM_DISCOVER_MODES_INT; + if (usbc_data->irq_vdm2) { + ret = request_threaded_irq(usbc_data->irq_vdm2, + NULL, max77775_vdm_discover_mode_irq, + 0, + "usbc-vdm2-irq", usbc_data); + if (ret) { + md75_err_usb("%s: Failed to Request IRQ (%d)\n", __func__, ret); + return ret; + } + } + + usbc_data->irq_vdm3 = usbc_data->irq_base + MAX77775_IRQ_VDM_ENTER_MODE_INT; + if (usbc_data->irq_vdm3) { + ret = request_threaded_irq(usbc_data->irq_vdm3, + NULL, max77775_vdm_enter_mode_irq, + 0, + "usbc-vdm3-irq", usbc_data); + if (ret) { + md75_err_usb("%s: Failed to Request IRQ (%d)\n", __func__, ret); + return ret; + } + } + + usbc_data->irq_vdm4 = usbc_data->irq_base + MAX77775_IRQ_VDM_DP_STATUS_UPDATE_INT; + if (usbc_data->irq_vdm4) { + ret = request_threaded_irq(usbc_data->irq_vdm4, + NULL, max77775_vdm_dp_status_irq, + 0, + "usbc-vdm4-irq", usbc_data); + if (ret) { + md75_err_usb("%s: Failed to Request IRQ (%d)\n", __func__, ret); + return ret; + } + } + + usbc_data->irq_vdm5 = usbc_data->irq_base + MAX77775_IRQ_VDM_DP_CONFIGURE_INT; + if (usbc_data->irq_vdm5) { + ret = request_threaded_irq(usbc_data->irq_vdm5, + NULL, max77775_vdm_dp_configure_irq, + 0, + "usbc-vdm5-irq", usbc_data); + if (ret) { + md75_err_usb("%s: Failed to Request IRQ (%d)\n", __func__, ret); + return ret; + } + } + + usbc_data->irq_vdm6 = usbc_data->irq_base + MAX77775_IRQ_VDM_ATTENTION_INT; + if (usbc_data->irq_vdm6) { + ret = request_threaded_irq(usbc_data->irq_vdm6, + NULL, max77775_vdm_attention_irq, + 0, + "usbc-vdm6-irq", usbc_data); + if (ret) { + md75_err_usb("%s: Failed to Request IRQ (%d)\n", __func__, ret); + return ret; + } + } + + usbc_data->irq_vir0 = usbc_data->irq_base + MAX77775_VIR_IRQ_ALTERROR_INT; + if (usbc_data->irq_vir0) { + ret = request_threaded_irq(usbc_data->irq_vir0, + NULL, max77775_vir_altmode_irq, + 0, + "usbc-vir0-irq", usbc_data); + if (ret) { + md75_err_usb("%s: Failed to Request IRQ (%d)\n", __func__, ret); + return ret; + } + } + + usbc_data->irq_usbid = usbc_data->irq_base + MAX77775_IRQ_USBID_INT; + if (usbc_data->irq_usbid) { + ret = request_threaded_irq(usbc_data->irq_usbid, + NULL, max77775_usbid_irq, + 0, + "usbc-usbid-irq", usbc_data); + if (ret) { + md75_err_usb("%s: Failed to Request IRQ (%d)\n", __func__, ret); + return ret; + } + } + +#if defined(CONFIG_MAX77775_CCOPEN_AFTER_WATERCABLE) + usbc_data->irq_taconn = usbc_data->irq_base + MAX77775_IRQ_TACONN_INT; + if (usbc_data->irq_taconn) { + ret = request_threaded_irq(usbc_data->irq_taconn, + NULL, max77775_taconnect_irq, + 0, + "usbc-taconnect-irq", usbc_data); + if (ret) { + md75_err_usb("%s: Failed to Request IRQ (%d)\n", __func__, ret); + return ret; + } + } +#endif + + return ret; +} + +void max77775_usbc_free_irq(struct max77775_usbc_platform_data *usbc_data) +{ + free_irq(usbc_data->irq_usbid, usbc_data); +#if defined(CONFIG_MAX77775_CCOPEN_AFTER_WATERCABLE) + free_irq(usbc_data->irq_taconn, usbc_data); +#endif + free_irq(usbc_data->irq_apcmd, usbc_data); + free_irq(usbc_data->irq_sysmsg, usbc_data); + free_irq(usbc_data->irq_vdm0, usbc_data); + free_irq(usbc_data->irq_vdm1, usbc_data); + free_irq(usbc_data->irq_vdm2, usbc_data); + free_irq(usbc_data->irq_vdm3, usbc_data); + free_irq(usbc_data->irq_vdm4, usbc_data); + free_irq(usbc_data->irq_vdm5, usbc_data); + free_irq(usbc_data->irq_vdm6, usbc_data); + free_irq(usbc_data->irq_vdm7, usbc_data); + free_irq(usbc_data->pd_data->irq_pdmsg, usbc_data); + free_irq(usbc_data->pd_data->irq_datarole, usbc_data); + free_irq(usbc_data->pd_data->irq_ssacc, usbc_data); + free_irq(usbc_data->pd_data->irq_fct_id, usbc_data); + free_irq(usbc_data->cc_data->irq_vconncop, usbc_data); + free_irq(usbc_data->cc_data->irq_vsafe0v, usbc_data); + free_irq(usbc_data->cc_data->irq_detabrt, usbc_data); + free_irq(usbc_data->cc_data->irq_vconnsc, usbc_data); + free_irq(usbc_data->cc_data->irq_ccpinstat, usbc_data); + free_irq(usbc_data->cc_data->irq_ccistat, usbc_data); + free_irq(usbc_data->cc_data->irq_ccvcnstat, usbc_data); + free_irq(usbc_data->cc_data->irq_ccstat, usbc_data); +} + +static void max77775_usbc_umask_irq(struct max77775_usbc_platform_data *usbc_data) +{ + int ret = 0; + u8 i2c_data = 0; + /* Unmask max77775 interrupt */ + ret = max77775_read_reg(usbc_data->i2c, MAX77775_PMIC_REG_INTSRC_MASK, + &i2c_data); + if (ret) { + md75_err_usb("%s fail to read muic reg\n", __func__); + return; + } + + i2c_data &= ~(MAX77775_IRQSRC_USBC); /* Unmask muic interrupt */ + max77775_write_reg(usbc_data->i2c, MAX77775_PMIC_REG_INTSRC_MASK, + i2c_data); +} +static void max77775_usbc_mask_irq(struct max77775_usbc_platform_data *usbc_data) +{ + int ret = 0; + u8 i2c_data = 0; + /* Unmask max77775 interrupt */ + ret = max77775_read_reg(usbc_data->i2c, MAX77775_PMIC_REG_INTSRC_MASK, + &i2c_data); + if (ret) { + md75_err_usb("%s fail to read muic reg\n", __func__); + return; + } + + i2c_data |= MAX77775_IRQSRC_USBC; /* Mask muic interrupt */ + max77775_write_reg(usbc_data->i2c, MAX77775_PMIC_REG_INTSRC_MASK, + i2c_data); +} + +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) +static int pdic_handle_usb_external_notifier_notification(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct max77775_usbc_platform_data *usbpd_data = g_usbc_data; + int ret = 0; + int enable = *(int *)data; + + md75_info_usb("%s : action=%lu , enable=%d\n", __func__, action, enable); + switch (action) { + case EXTERNAL_NOTIFY_HOSTBLOCK_PRE: + if (enable) { + max77775_set_enable_alternate_mode(ALTERNATE_MODE_STOP); + if (usbpd_data->dp_is_connect) + max77775_dp_detach(usbpd_data); + } else { + if (usbpd_data->dp_is_connect) + max77775_dp_detach(usbpd_data); + } + break; + case EXTERNAL_NOTIFY_HOSTBLOCK_POST: + if (enable) { + } else { + max77775_set_enable_alternate_mode(ALTERNATE_MODE_START); + } + break; + case EXTERNAL_NOTIFY_DEVICEADD: + if (enable) { + usbpd_data->device_add = 1; + wake_up_interruptible(&usbpd_data->device_add_wait_q); + } + break; + case EXTERNAL_NOTIFY_MDMBLOCK_PRE: + if (enable && usbpd_data->dp_is_connect) { + usbpd_data->mdm_block = 1; + max77775_dp_detach(usbpd_data); + } + break; + default: + break; + } + + return ret; +} + +static void delayed_external_notifier_init(struct work_struct *work) +{ + int ret = 0; + static int retry_count = 1; + int max_retry_count = 5; + struct max77775_usbc_platform_data *usbpd_data = g_usbc_data; + + md75_info_usb("%s : %d = times!\n", __func__, retry_count); + + /* Register ccic handler to ccic notifier block list */ + ret = usb_external_notify_register(&usbpd_data->usb_external_notifier_nb, + pdic_handle_usb_external_notifier_notification, EXTERNAL_NOTIFY_DEV_PDIC); + if (ret < 0) { + md75_err_usb("Manager notifier init time is %d.\n", retry_count); + if (retry_count++ != max_retry_count) + schedule_delayed_work(&usbpd_data->usb_external_notifier_register_work, msecs_to_jiffies(2000)); + else + md75_err_usb("fail to init external notifier\n"); + } else + md75_info_usb("%s : external notifier register done!\n", __func__); +} +#endif + +#if defined(CONFIG_MAX77775_CCOPEN_AFTER_WATERCABLE) +static void max77775_set_ccopen_for_watercable(struct work_struct *work) +{ + struct max77775_usbc_platform_data *usbc_data = g_usbc_data; + + md75_info_usb("%s\n", __func__); +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + max77775_ccic_event_work(usbc_data, + PDIC_NOTIFY_DEV_MUIC, PDIC_NOTIFY_ID_WATER_CABLE, 1, 0, 0); +#endif +} +#endif + +#if defined(CONFIG_SEC_FACTORY) +static void factory_check_abnormal_state(struct work_struct *work) +{ + struct max77775_usbc_platform_data *usbpd_data = g_usbc_data; + int state_cnt = usbpd_data->factory_mode.FAC_Abnormal_Repeat_State; + + msg_maxim("IN "); + + if (state_cnt >= FAC_ABNORMAL_REPEAT_STATE) { + msg_maxim("Notify the abnormal state [STATE] [ %d]", state_cnt); + max77775_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_CCIC, PDIC_NOTIFY_ID_FAC, 1, 0, 0); + } else + msg_maxim("[STATE] cnt : [%d]", state_cnt); + usbpd_data->factory_mode.FAC_Abnormal_Repeat_State = 0; + msg_maxim("OUT "); +} + +static void factory_check_normal_rid(struct work_struct *work) +{ + struct max77775_usbc_platform_data *usbpd_data = g_usbc_data; + int rid_cnt = usbpd_data->factory_mode.FAC_Abnormal_Repeat_RID; + + msg_maxim("IN "); + + if (rid_cnt >= FAC_ABNORMAL_REPEAT_RID) { + msg_maxim("Notify the abnormal state [RID] [ %d]", rid_cnt); + max77775_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_CCIC, PDIC_NOTIFY_ID_FAC, 1 << 1, 0, 0); + } else + msg_maxim("[RID] cnt : [%d]", rid_cnt); + + usbpd_data->factory_mode.FAC_Abnormal_Repeat_RID = 0; + msg_maxim("OUT "); +} + +void factory_execute_monitor(int type) +{ + struct max77775_usbc_platform_data *usbpd_data = g_usbc_data; + uint32_t state_cnt = usbpd_data->factory_mode.FAC_Abnormal_Repeat_State; + uint32_t rid_cnt = usbpd_data->factory_mode.FAC_Abnormal_Repeat_RID; + uint32_t rid0_cnt = usbpd_data->factory_mode.FAC_Abnormal_RID0; + + switch (type) { + case FAC_ABNORMAL_REPEAT_RID0: + if (!rid0_cnt) { + msg_maxim("Notify the abnormal state [RID0] [%d]!!", rid0_cnt); + usbpd_data->factory_mode.FAC_Abnormal_RID0++; + max77775_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_CCIC, PDIC_NOTIFY_ID_FAC, 1 << 2, 0, 0); + } else { + usbpd_data->factory_mode.FAC_Abnormal_RID0 = 0; + } + break; + case FAC_ABNORMAL_REPEAT_RID: + if (!rid_cnt) { + schedule_delayed_work(&usbpd_data->factory_rid_work, msecs_to_jiffies(1000)); + msg_maxim("start the factory_rid_work"); + } + usbpd_data->factory_mode.FAC_Abnormal_Repeat_RID++; + break; + case FAC_ABNORMAL_REPEAT_STATE: + if (!state_cnt) { + schedule_delayed_work(&usbpd_data->factory_state_work, msecs_to_jiffies(1000)); + msg_maxim("start the factory_state_work"); + } + usbpd_data->factory_mode.FAC_Abnormal_Repeat_State++; + break; + default: + msg_maxim("Never Calling [%d]", type); + break; + } +} +#endif + +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) +static void max77775_usbpd_set_host_on(void *data, int mode) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + + if (!usbpd_data) + return; + + md75_info_usb("%s : current_set is %d!\n", __func__, mode); + if (mode) { + usbpd_data->device_add = 0; + usbpd_data->detach_done_wait = 0; + usbpd_data->host_turn_on_event = 1; + wake_up_interruptible(&usbpd_data->host_turn_on_wait_q); + } else { + usbpd_data->device_add = 0; + usbpd_data->detach_done_wait = 0; + usbpd_data->host_turn_on_event = 0; + } +} + +static void max77775_usbpd_wait_entermode(void *data, int on) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + + if (!usbpd_data) + return; + + md75_info_usb("%s : %d!\n", __func__, on); + if (on) { + usbpd_data->wait_entermode = 1; + } else { + usbpd_data->wait_entermode = 0; + wake_up_interruptible(&usbpd_data->host_turn_on_wait_q); + } +} + +static void max77775_usbpd_sbu_switch_control(void *data, int on) +{ + struct max77775_usbc_platform_data *usbpd_data = data; + usbc_cmd_data write_data; + int mode = UNDEFINED; + + md75_info_usb("%s : on: %d\n", __func__, on); + + if (!usbpd_data) + return; + + if (usbpd_data->sbu_switch_status == on) { + md75_info_usb("%s : requested value is already set\n", __func__); + return; + } + + usbpd_data->sbu_switch_status = on; + + switch (on) { + case OPEN_SBU: + mode = 0x00; + break; + case CLOSE_SBU_CC1_ACTIVE: + mode = 0x09; + break; + case CLOSE_SBU_CC2_ACTIVE: + mode = 0x12; + break; + default: + md75_info_usb("%s : unproper value\n", __func__); + return; + } + + md75_info_usb("%s : sbu_status: %d mode; 0x%x\n", __func__, usbpd_data->sbu_switch_status, mode); + + init_usbc_cmd_data(&write_data); + write_data.opcode = OPCODE_SBU_CTRL1_W; + write_data.write_data[0] = mode; + write_data.write_length = 0x1; + write_data.read_length = 0x0; + + max77775_usbc_opcode_write(usbpd_data, &write_data); +} + +void max77775_acc_detach_check(struct work_struct *wk) +{ + struct delayed_work *delay_work = + container_of(wk, struct delayed_work, work); + struct max77775_usbc_platform_data *usbpd_data = + container_of(delay_work, struct max77775_usbc_platform_data, acc_detach_work); +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + struct otg_notify *o_notify = get_otg_notify(); +#endif + + md75_info_usb("%s : pd_state : %d, acc_type : %d\n", __func__, + usbpd_data->pd_state, usbpd_data->acc_type); + if (usbpd_data->pd_state == max77775_State_PE_Initial_detach) { + if (usbpd_data->acc_type != PDIC_DOCK_DETACHED) { + if (usbpd_data->acc_type != PDIC_DOCK_NEW) + pdic_send_dock_intent(PDIC_DOCK_DETACHED); + pdic_send_dock_uevent(usbpd_data->Vendor_ID, + usbpd_data->Product_ID, + PDIC_DOCK_DETACHED); + usbpd_data->acc_type = PDIC_DOCK_DETACHED; + usbpd_data->Vendor_ID = 0; + usbpd_data->Product_ID = 0; + usbpd_data->send_enter_mode_req = 0; + usbpd_data->Device_Version = 0; + max77775_ccic_event_work(usbpd_data, + PDIC_NOTIFY_DEV_ALL, PDIC_NOTIFY_ID_CLEAR_INFO, + PDIC_NOTIFY_ID_DEVICE_INFO, 0, 0); +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + if (o_notify) + send_otg_notify(o_notify, NOTIFY_EVENT_HMD_EXT_CURRENT, 0); +#endif + } + } +} + +struct usbpd_ops ops_usbpd = { + .usbpd_set_host_on = max77775_usbpd_set_host_on, + .usbpd_wait_entermode = max77775_usbpd_wait_entermode, + .usbpd_sbu_switch_control = max77775_usbpd_sbu_switch_control, +}; +#endif + +static int max77775_usbc_probe(struct platform_device *pdev) +{ + struct max77775_dev *max77775 = dev_get_drvdata(pdev->dev.parent); + struct max77775_platform_data *pdata = dev_get_platdata(max77775->dev); + struct max77775_usbc_platform_data *usbc_data = NULL; + int ret = 0, i = 0; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + ppdic_data_t ppdic_data; + ppdic_sysfs_property_t ppdic_sysfs_prop; +#endif +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + struct otg_notify *o_notify = get_otg_notify(); +#endif +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + struct usbpd_dev *usbpd_d; +#endif + struct max77775_hmd_power_dev *hmd_list; + + msg_maxim("Probing : %d", max77775->irq); + usbc_data = devm_kzalloc(&pdev->dev, + sizeof(struct max77775_usbc_platform_data), GFP_KERNEL); + if (!usbc_data) { + err_maxim("usbc_data is null"); + ret = -ENOMEM; + goto err_usbc_data; + } + + g_usbc_data = usbc_data; + usbc_data->dev = &pdev->dev; + usbc_data->max77775 = max77775; + usbc_data->muic = max77775->muic; + usbc_data->charger = max77775->charger; + usbc_data->i2c = max77775->i2c; + usbc_data->max77775_data = pdata; + usbc_data->irq_base = pdata->irq_base; + + usbc_data->pd_data = devm_kzalloc(&pdev->dev, + sizeof(struct max77775_pd_data), GFP_KERNEL); + if (!usbc_data->pd_data) { + err_maxim("pd_data is null"); + ret = -ENOMEM; + goto err_cc_pd_data; + } + + usbc_data->cc_data = devm_kzalloc(&pdev->dev, + sizeof(struct max77775_cc_data), GFP_KERNEL); + if (!usbc_data->cc_data) { + err_maxim("cc_data is null"); + ret = -ENOMEM; + goto err_cc_pd_data; + } + + + platform_set_drvdata(pdev, usbc_data); + + ret = sysfs_create_group(&max77775->dev->kobj, &max77775_attr_grp); + msg_maxim("created sysfs. ret=%d", ret); + + usbc_data->HW_Revision = 0x0; + usbc_data->FW_Revision = 0x0; + usbc_data->plug_attach_done = 0x0; + usbc_data->cc_data->current_pr = 0xFF; + usbc_data->pd_data->current_dr = 0xFF; + usbc_data->cc_data->current_vcon = 0xFF; + usbc_data->op_code_done = 0x0; + usbc_data->current_connstat = 0xFF; + usbc_data->prev_connstat = 0xFF; + usbc_data->usbc_cmd_queue.front = NULL; + usbc_data->usbc_cmd_queue.rear = NULL; + usbc_data->opcode_stamp = 0; + mutex_init(&usbc_data->op_lock); +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + ppdic_data = devm_kzalloc(&pdev->dev, sizeof(pdic_data_t), GFP_KERNEL); + if (!ppdic_data) { + err_maxim("ppdic_data is null"); + ret = -ENOMEM; + goto err_ppdic; + } + + ppdic_sysfs_prop = devm_kzalloc(&pdev->dev, sizeof(pdic_sysfs_property_t), GFP_KERNEL); + if (!ppdic_sysfs_prop) { + err_maxim("ppdic_sysfs_prop is null"); + ret = -ENOMEM; + goto err_ppdic; + } + + ppdic_sysfs_prop->get_property = max77775_sysfs_get_local_prop; + ppdic_sysfs_prop->set_property = max77775_sysfs_set_prop; + ppdic_sysfs_prop->property_is_writeable = max77775_sysfs_is_writeable; + ppdic_sysfs_prop->property_is_writeonly = max77775_sysfs_is_writeonly; + ppdic_sysfs_prop->properties = max77775_sysfs_properties; + ppdic_sysfs_prop->num_properties = ARRAY_SIZE(max77775_sysfs_properties); + ppdic_data->pdic_sysfs_prop = ppdic_sysfs_prop; + ppdic_data->drv_data = usbc_data; + ppdic_data->name = "max77775"; + ppdic_data->set_enable_alternate_mode = max77775_set_enable_alternate_mode; + pdic_core_register_chip(ppdic_data); + usbc_data->vconn_en = 1; + usbc_data->cur_rid = RID_OPEN; + usbc_data->cc_pin_status = NO_DETERMINATION; +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + usbpd_d = devm_kzalloc(&pdev->dev, + sizeof(struct usbpd_dev), GFP_KERNEL); + if (!usbpd_d) { + err_maxim("failed to allocate usbpd data"); + ret = -ENOMEM; + goto err_usbpd_d; + } + + usbpd_d->ops = &ops_usbpd; + usbpd_d->data = (void *)usbc_data; + usbc_data->man = register_usbpd(usbpd_d); +#endif + usbc_data->typec_cap.revision = USB_TYPEC_REV_1_2; + usbc_data->typec_cap.pd_revision = 0x300; + usbc_data->typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE; + + usbc_data->typec_cap.driver_data = usbc_data; + usbc_data->typec_cap.ops = &max77775_ops; + + usbc_data->typec_cap.type = TYPEC_PORT_DRP; + usbc_data->typec_cap.data = TYPEC_PORT_DRD; + + usbc_data->typec_power_role = TYPEC_SINK; + usbc_data->typec_try_state_change = TRY_ROLE_SWAP_NONE; + + usbc_data->port = typec_register_port(usbc_data->dev, &usbc_data->typec_cap); + if (IS_ERR(usbc_data->port)) + md75_err_usb("unable to register typec_register_port\n"); + else + msg_maxim("success typec_register_port port=%pK", usbc_data->port); + init_completion(&usbc_data->typec_reverse_completion); + + usbc_data->auto_vbus_en = false; + usbc_data->is_first_booting = 1; + usbc_data->pd_support = false; + usbc_data->ccrp_state = 0; + usbc_data->set_altmode = 0; + usbc_data->set_altmode_error = 0; + usbc_data->need_recover = false; + usbc_data->op_ctrl1_w = (BIT_CCSrcSnk | BIT_CCSnkSrc); + usbc_data->srcccap_request_retry = false; +#if defined(CONFIG_SUPPORT_SHIP_MODE) + usbc_data->ship_mode_en = 0; + usbc_data->ship_mode_data = 0x00; +#endif + usbc_data->rid_check = false; + usbc_data->lapse_idx = 0; + for (i = 0; i < MAX_NVCN_CNT; i++) + usbc_data->time_lapse[i] = 0; + +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + send_otg_notify(o_notify, NOTIFY_EVENT_POWER_SOURCE, 0); +#endif +#endif + init_completion(&usbc_data->op_completion); + init_completion(&usbc_data->ccic_sysfs_completion); + init_completion(&usbc_data->psrdy_wait); + usbc_data->op_wait_queue = create_singlethread_workqueue("op_wait"); + if (usbc_data->op_wait_queue == NULL) + goto err_op_wait_queue; + usbc_data->op_send_queue = create_singlethread_workqueue("op_send"); + if (usbc_data->op_send_queue == NULL) + goto err_op_send_queue; + + INIT_WORK(&usbc_data->op_send_work, max77775_uic_op_send_work_func); + INIT_WORK(&usbc_data->cc_open_req_work, max77775_cc_open_work_func); + INIT_WORK(&usbc_data->dp_configure_work, max77775_dp_configure_work_func); +#if defined(MAX77775_SYS_FW_UPDATE) + INIT_WORK(&usbc_data->fw_update_work, + max77775_firmware_update_sysfs_work); +#endif + +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + /* Create a work queue for the ccic irq thread */ + usbc_data->ccic_wq + = create_singlethread_workqueue("ccic_irq_event"); + if (!usbc_data->ccic_wq) { + md75_err_usb("%s failed to create work queue\n", __func__); + goto err_ccic_wq; + } +#endif +#if defined(CONFIG_SEC_FACTORY) + INIT_DELAYED_WORK(&usbc_data->factory_state_work, + factory_check_abnormal_state); + INIT_DELAYED_WORK(&usbc_data->factory_rid_work, + factory_check_normal_rid); +#endif + INIT_DELAYED_WORK(&usbc_data->vbus_hard_reset_work, + vbus_control_hard_reset); + /* turn on the VBUS automatically. */ + // max77775_usbc_enable_auto_vbus(usbc_data); + INIT_DELAYED_WORK(&usbc_data->acc_detach_work, max77775_acc_detach_check); + + pdic_register_switch_device(1); +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + INIT_DELAYED_WORK(&usbc_data->usb_external_notifier_register_work, + delayed_external_notifier_init); +#endif +#if defined(CONFIG_MAX77775_CCOPEN_AFTER_WATERCABLE) + INIT_DELAYED_WORK(&usbc_data->set_ccopen_for_watercable_work, + max77775_set_ccopen_for_watercable); +#endif + init_completion(&usbc_data->uvdm_longpacket_out_wait); + init_completion(&usbc_data->uvdm_longpacket_in_wait); +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + ppdic_data->fw_data.data = (void *)usbc_data; + ppdic_data->fw_data.firmware_update = max77775_firmware_update_callback; + ppdic_data->fw_data.get_prev_fw_size = max77775_get_firmware_size; + ret = pdic_misc_init(ppdic_data); + if (ret) { + md75_err_usb("pdic_misc_init is failed, error %d\n", ret); + } else { + ppdic_data->misc_dev->uvdm_read = max77775_sec_uvdm_in_request_message; + ppdic_data->misc_dev->uvdm_write = max77775_sec_uvdm_out_request_message; + ppdic_data->misc_dev->uvdm_ready = max77775_sec_uvdm_ready; + ppdic_data->misc_dev->uvdm_close = max77775_sec_uvdm_close; + ppdic_data->misc_dev->pps_control = max77775_sec_pps_control; + usbc_data->ppdic_data = ppdic_data; + } +#endif + + hmd_list = devm_kzalloc(&pdev->dev, MAX_NUM_HMD * sizeof(*hmd_list), + GFP_KERNEL); + if (ZERO_OR_NULL_PTR(hmd_list)) { + usbc_data->hmd_list = NULL; + goto err_hmd_list; + } + + /* add default AR/VR here */ + snprintf(hmd_list[0].hmd_name, NAME_LEN_HMD, "PicoVR"); + hmd_list[0].vid = 0x2d40; + hmd_list[0].pid = 0x0000; + + usbc_data->hmd_list = hmd_list; + + mutex_init(&usbc_data->hmd_power_lock); + +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + /* Register pdic handler to pdic notifier block list */ + ret = usb_external_notify_register(&usbc_data->usb_external_notifier_nb, + pdic_handle_usb_external_notifier_notification, EXTERNAL_NOTIFY_DEV_PDIC); + if (ret < 0) + schedule_delayed_work(&usbc_data->usb_external_notifier_register_work, msecs_to_jiffies(2000)); + else + md75_info_usb("%s : external notifier register done!\n", __func__); +#endif + + init_waitqueue_head(&usbc_data->host_turn_on_wait_q); + init_waitqueue_head(&usbc_data->device_add_wait_q); +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + max77775_usbpd_set_host_on(usbc_data, 0); +#endif +#if IS_ENABLED(CONFIG_USB_DWC3_MSM) + usbc_data->host_turn_on_wait_time = 15; +#else + usbc_data->host_turn_on_wait_time = 3; +#endif + + max77775_get_version_info(usbc_data); + max77775_init_irq_handler(usbc_data); +#ifdef CONFIG_MUIC_MAX77775 + ret = max77775_muic_probe(usbc_data); + if (ret) { + msg_maxim("max77775_muic_probe error."); + goto err_muic_probe; + } +#endif + max77775_cc_init(usbc_data); + max77775_pd_init(usbc_data); + max77775_write_reg(usbc_data->muic, REG_PD_INT_M, 0x1C); + max77775_write_reg(usbc_data->muic, REG_VDM_INT_M, 0xFF); + max77775_init_opcode(usbc_data, 0); + max77775->cc_booting_complete = 1; + max77775_usbc_umask_irq(usbc_data); + + usbc_data->cc_open_req = 1; + max77775_pdic_manual_ccopen_request(0); + + msg_maxim("probing Complete.."); + return 0; + +err_muic_probe: + max77775_usbc_free_irq(usbc_data); +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + if (delayed_work_pending(&usbc_data->usb_external_notifier_register_work)) + cancel_delayed_work_sync(&usbc_data->usb_external_notifier_register_work); + usb_external_notify_unregister(&usbc_data->usb_external_notifier_nb); +#endif + mutex_destroy(&usbc_data->hmd_power_lock); + usbc_data->hmd_list = NULL; +err_hmd_list: +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + if (usbc_data->ppdic_data && usbc_data->ppdic_data->misc_dev) + pdic_misc_exit(); +#endif + pdic_register_switch_device(0); +err_ccic_wq: +err_op_send_queue: +err_op_wait_queue: + typec_unregister_port(usbc_data->port); +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + register_usbpd(NULL); +#endif +err_usbpd_d: + pdic_core_unregister_chip(); +err_ppdic: + mutex_destroy(&usbc_data->op_lock); + sysfs_remove_group(&max77775->dev->kobj, &max77775_attr_grp); + platform_set_drvdata(pdev, NULL); +err_cc_pd_data: + g_usbc_data = NULL; +err_usbc_data: + + return ret; +} + +static int max77775_usbc_remove(struct platform_device *pdev) +{ + struct max77775_usbc_platform_data *usbc_data = + platform_get_drvdata(pdev); + struct max77775_dev *max77775 = usbc_data->max77775; + + max77775_usbc_free_irq(usbc_data); +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) + if (delayed_work_pending(&usbc_data->usb_external_notifier_register_work)) + cancel_delayed_work_sync(&usbc_data->usb_external_notifier_register_work); + usb_external_notify_unregister(&usbc_data->usb_external_notifier_nb); +#endif + mutex_destroy(&usbc_data->hmd_power_lock); + usbc_data->hmd_list = NULL; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + if (usbc_data->ppdic_data && usbc_data->ppdic_data->misc_dev) + pdic_misc_exit(); +#endif + pdic_register_switch_device(0); + + typec_unregister_port(usbc_data->port); +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + register_usbpd(NULL); +#endif + + pdic_core_unregister_chip(); + mutex_destroy(&usbc_data->op_lock); + sysfs_remove_group(&max77775->dev->kobj, &max77775_attr_grp); + + mutex_destroy(&usbc_data->hmd_power_lock); + mutex_destroy(&usbc_data->op_lock); + pdic_core_unregister_chip(); + + typec_unregister_port(usbc_data->port); + + pdic_register_switch_device(0); +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + if (usbc_data->ppdic_data && usbc_data->ppdic_data->misc_dev) + pdic_misc_exit(); +#endif +#ifdef CONFIG_MUIC_MAX77775 + max77775_muic_remove(usbc_data); +#endif + platform_set_drvdata(pdev, NULL); + g_usbc_data = NULL; + + return 0; +} + +#if defined CONFIG_PM +static int max77775_usbc_suspend(struct device *dev) +{ + struct max77775_usbc_platform_data *usbc_data = + dev_get_drvdata(dev); + + max77775_muic_suspend(usbc_data); + + return 0; +} + +static int max77775_usbc_resume(struct device *dev) +{ + struct max77775_usbc_platform_data *usbc_data = + dev_get_drvdata(dev); + + max77775_muic_resume(usbc_data); + if (usbc_data->set_altmode_error) { + msg_maxim("set alternate mode"); + max77775_set_enable_alternate_mode + (usbc_data->set_altmode); + } + + return 0; +} +#else +#define max77775_usbc_suspend NULL +#define max77775_usbc_resume NULL +#endif + +static void max77775_usbc_disable_irq(struct max77775_usbc_platform_data *usbc_data) +{ + disable_irq(usbc_data->irq_usbid); +#if defined(CONFIG_MAX77775_CCOPEN_AFTER_WATERCABLE) + disable_irq(usbc_data->irq_taconn); +#endif + disable_irq(usbc_data->irq_apcmd); + disable_irq(usbc_data->irq_sysmsg); + disable_irq(usbc_data->irq_vdm0); + disable_irq(usbc_data->irq_vdm1); + disable_irq(usbc_data->irq_vdm2); + disable_irq(usbc_data->irq_vdm3); + disable_irq(usbc_data->irq_vdm4); + disable_irq(usbc_data->irq_vdm5); + disable_irq(usbc_data->irq_vdm6); + disable_irq(usbc_data->irq_vir0); + disable_irq(usbc_data->pd_data->irq_pdmsg); + disable_irq(usbc_data->pd_data->irq_psrdy); + disable_irq(usbc_data->pd_data->irq_datarole); + disable_irq(usbc_data->pd_data->irq_ssacc); + disable_irq(usbc_data->pd_data->irq_fct_id); + disable_irq(usbc_data->cc_data->irq_vconncop); + disable_irq(usbc_data->cc_data->irq_vsafe0v); + disable_irq(usbc_data->cc_data->irq_vconnsc); + disable_irq(usbc_data->cc_data->irq_ccpinstat); + disable_irq(usbc_data->cc_data->irq_ccistat); + disable_irq(usbc_data->cc_data->irq_ccvcnstat); + disable_irq(usbc_data->cc_data->irq_ccstat); +} + +static void max77775_usbc_shutdown(struct platform_device *pdev) +{ + struct max77775_usbc_platform_data *usbc_data = + platform_get_drvdata(pdev); + u8 status = 0, uic_int = 0, vbus = 0; + u8 uid = 0; +#if defined(CONFIG_SUPPORT_SHIP_MODE) + u8 op_data = 0; +#endif + + msg_maxim("max77775 usbc driver shutdown++++"); + if (!usbc_data->muic) { + msg_maxim("no max77775 i2c client"); + return; + } +#ifdef CONFIG_MUIC_MAX77775 + max77775_muic_shutdown(usbc_data); +#endif + max77775_usbc_mask_irq(usbc_data); + /* unmask */ + max77775_write_reg(usbc_data->muic, REG_PD_INT_M, 0xFF); + max77775_write_reg(usbc_data->muic, REG_CC_INT_M, 0xFF); + max77775_write_reg(usbc_data->muic, REG_UIC_INT_M, 0xFF); + max77775_write_reg(usbc_data->muic, REG_VDM_INT_M, 0xFF); + max77775_write_reg(usbc_data->muic, REG_SPARE_INT_M, 0xFF); + + max77775_usbc_disable_irq(usbc_data); + max77775_usbc_free_irq(usbc_data); + + usbc_data->shut_down = 1; + + max77775_read_reg(usbc_data->muic, REG_USBC_STATUS1, &status); + uid = (status & BIT_UIDADC) >> FFS(BIT_UIDADC); + vbus = (status & BIT_VBADC) >> FFS(BIT_VBADC); + + /* send the reset command */ + if (usbc_data->current_connstat == WATER) { +#if defined(CONFIG_MAX77775_UPGRADED_WATER_DETECT) + msg_maxim("WATER STATE, %s, %s", vbus ? "VBUS" : "NO VBUS", + usbc_data->muic_data->is_hiccup_mode ? "HICCUP MODE" : "NOT HICCUP MODE"); + if (!vbus && !usbc_data->muic_data->is_hiccup_mode) { + msg_maxim("call reset function"); + max77775_reset_ic(usbc_data); + } else + msg_maxim("don't call the reset function"); +#else + msg_maxim("no call the reset function(WATER STATE)"); +#endif + } else if (usbc_data->cur_rid != RID_OPEN && usbc_data->cur_rid != RID_UNDEFINED) + msg_maxim("no call the reset function(RID)"); + else if (uid != UID_Open) + msg_maxim("no call the reset function(UID)"); + else { + max77775_reset_ic(usbc_data); +#if defined(CONFIG_SUPPORT_SHIP_MODE) + if (usbc_data->ship_mode_en) { + md75_info_usb("%s: reset_ic before ship_mode_en(%d, 0x%x)\n", + __func__, usbc_data->ship_mode_en, usbc_data->ship_mode_data); + op_data = 0x08; + op_data |= (usbc_data->ship_mode_data << 6); + msleep(50); + max77775_write_reg(usbc_data->muic, OPCODE_WRITE, 0x97); //auto ship mode + max77775_write_reg(usbc_data->muic, OPCODE_DATAOUT1, op_data); + max77775_write_reg(usbc_data->muic, OPCODE_WRITE_END, 0x00); + msleep(50); + max77775_read_reg(usbc_data->muic, + MAX77775_USBC_REG_UIC_INT, &uic_int); + } +#endif + max77775_write_reg(usbc_data->muic, REG_PD_INT_M, 0xFF); + max77775_write_reg(usbc_data->muic, REG_CC_INT_M, 0xFF); + max77775_write_reg(usbc_data->muic, REG_UIC_INT_M, 0xFF); + max77775_write_reg(usbc_data->muic, REG_VDM_INT_M, 0xFF); + max77775_write_reg(usbc_data->muic, REG_SPARE_INT_M, 0xFF); + max77775_read_reg(usbc_data->muic, + MAX77775_USBC_REG_UIC_INT, &uic_int); + } + msg_maxim("max77775 usbc driver shutdown----"); +} + +static SIMPLE_DEV_PM_OPS(max77775_usbc_pm_ops, max77775_usbc_suspend, + max77775_usbc_resume); + +static struct platform_driver max77775_usbc_driver = { + .driver = { + .name = "max77775-usbc", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &max77775_usbc_pm_ops, +#endif + }, + .shutdown = max77775_usbc_shutdown, + .probe = max77775_usbc_probe, + .remove = max77775_usbc_remove, +}; + +static int __init max77775_usbc_init(void) +{ + msg_maxim("init"); + return platform_driver_register(&max77775_usbc_driver); +} +device_initcall(max77775_usbc_init); + +static void __exit max77775_usbc_exit(void) +{ + platform_driver_unregister(&max77775_usbc_driver); +} +module_exit(max77775_usbc_exit); + +MODULE_DESCRIPTION("max77775 USBPD driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/vendor_notify/Kconfig b/drivers/usb/vendor_notify/Kconfig new file mode 100644 index 000000000000..4dbcf67077fa --- /dev/null +++ b/drivers/usb/vendor_notify/Kconfig @@ -0,0 +1,19 @@ +config USB_VENDOR_NOTIFY + tristate "USB vendor notify" + default n + help + Say Y here to include support for USB vendor notify. + + To compile this driver as a module, choose M here: the module + will be called usb-vendor-notify. + + +config USB_VENDOR_RECEIVER + tristate "USB vendor receiver" + default n + depends on USB_NOTIFY_LAYER + help + Say Y here to include support for USB vendor receiver. + + To compile this driver as a module, choose M here: the module + will be called usb-vendor-receiver. diff --git a/drivers/usb/vendor_notify/Makefile b/drivers/usb/vendor_notify/Makefile new file mode 100644 index 000000000000..24fa5a6e687f --- /dev/null +++ b/drivers/usb/vendor_notify/Makefile @@ -0,0 +1,3 @@ +subdir-ccflags-y := -Wformat +obj-$(CONFIG_USB_VENDOR_NOTIFY) += usb_vendor_notify.o +obj-$(CONFIG_USB_VENDOR_RECEIVER) += usb_vendor_receiver.o diff --git a/drivers/usb/vendor_notify/usb_vendor_notify.c b/drivers/usb/vendor_notify/usb_vendor_notify.c new file mode 100644 index 000000000000..80a75a85373d --- /dev/null +++ b/drivers/usb/vendor_notify/usb_vendor_notify.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Samsung Electronics Co. Ltd. + * + * 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 + +static struct blocking_notifier_head *usb_vendor_notifier; + +int send_usb_vendor_notify_pcm_info(int direction, int enable) +{ + int ret = 0; + int action; + struct data_pcm_info data; + + if (!usb_vendor_notifier) { + pr_err("%s: not initialized\n", __func__); + return -ENODATA; + } + + action = USB_VENDOR_NOTIFY_PCM_INFO; + data.direction = direction; + data.enable = enable; + + ret = blocking_notifier_call_chain(usb_vendor_notifier, action, &data); + if (ret != NOTIFY_DONE && ret != NOTIFY_OK) + pr_err("%s: err(%d)\n", __func__, ret); + + return ret; +} +EXPORT_SYMBOL(send_usb_vendor_notify_pcm_info); + +int send_usb_vendor_notify_cardnum(int card_num, int bundle, int attach) +{ + int ret = 0; + int action; + struct data_cardnum data; + + if (!usb_vendor_notifier) { + pr_err("%s: not initialized\n", __func__); + return -ENODATA; + } + + action = USB_VENDOR_NOTIFY_CARDNUM; + data.card_num = card_num; + data.bundle = bundle; + data.attach = attach; + + ret = blocking_notifier_call_chain(usb_vendor_notifier, action, &data); + if (ret != NOTIFY_DONE && ret != NOTIFY_OK) + pr_err("%s: err(%d)\n", __func__, ret); + + return ret; +} +EXPORT_SYMBOL(send_usb_vendor_notify_cardnum); + +int send_usb_vendor_notify_audio_uevent(struct usb_device *dev, int card_num, + int attach) +{ + int ret = 0; + int action; + struct data_audio_uevent data; + + if (!usb_vendor_notifier) { + pr_err("%s: not initialized\n", __func__); + return -ENODATA; + } + + action = USB_VENDOR_NOTIFY_AUDIO_UEVENT; + data.dev = dev; + data.card_num = card_num; + data.attach = attach; + + ret = blocking_notifier_call_chain(usb_vendor_notifier, action, &data); + if (ret != NOTIFY_DONE && ret != NOTIFY_OK) + pr_err("%s: err(%d)\n", __func__, ret); + + return ret; +} +EXPORT_SYMBOL(send_usb_vendor_notify_audio_uevent); + +int send_usb_vendor_notify_new_device(struct usb_device *dev) +{ + int ret = 0; + int action; + struct data_new_device data; + + if (!usb_vendor_notifier) { + pr_err("%s: not initialized\n", __func__); + return -ENODATA; + } + + action = USB_VENDOR_NOTIFY_NEW_DEVICE; + data.dev = dev; + data.ret = 0; + + ret = blocking_notifier_call_chain(usb_vendor_notifier, action, &data); + if (ret != NOTIFY_DONE && ret != NOTIFY_OK) + pr_err("%s: err(%d)\n", __func__, ret); + + if (data.ret) + ret = data.ret; + + return ret; +} +EXPORT_SYMBOL(send_usb_vendor_notify_new_device); + +static int usb_vendor_notify_probe(struct platform_device *pdev) +{ + int ret = 0; + + usb_vendor_notifier = devm_kzalloc(&pdev->dev, + sizeof(struct blocking_notifier_head), GFP_KERNEL); + if (!usb_vendor_notifier) { + ret = -ENOMEM; + goto out; + } + BLOCKING_INIT_NOTIFIER_HEAD(usb_vendor_notifier); + platform_set_drvdata(pdev, usb_vendor_notifier); + pr_info("%s done\n", __func__); +out: + return ret; +} + +static int usb_vendor_notify_remove(struct platform_device *pdev) +{ + platform_set_drvdata(pdev, NULL); + usb_vendor_notifier = NULL; + return 0; +} + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id usb_vendor_notify_dt_ids[] = { + { .compatible = "samsung,usb_vendor_notify" }, + { } +}; +MODULE_DEVICE_TABLE(of, usb_vendor_notify_dt_ids); +#endif /* CONFIG_OF */ + +static struct platform_driver usb_vendor_notify_driver = { + .probe = usb_vendor_notify_probe, + .remove = usb_vendor_notify_remove, + .driver = { + .name = "usb_vendor_notify", + .owner = THIS_MODULE, +#if IS_ENABLED(CONFIG_OF) + .of_match_table = of_match_ptr(usb_vendor_notify_dt_ids), +#endif /* CONFIG_OF */ + }, +}; + +static int __init usb_vendor_notify_init(void) +{ + return platform_driver_register(&usb_vendor_notify_driver); +} + +static void __exit usb_vendor_notify_exit(void) +{ + platform_driver_unregister(&usb_vendor_notify_driver); +} + +module_init(usb_vendor_notify_init); +module_exit(usb_vendor_notify_exit); + +MODULE_DESCRIPTION("usb vendor notify"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/vendor_notify/usb_vendor_receiver.c b/drivers/usb/vendor_notify/usb_vendor_receiver.c new file mode 100644 index 000000000000..fd0df89d85af --- /dev/null +++ b/drivers/usb/vendor_notify/usb_vendor_receiver.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Samsung Electronics Co. Ltd. + * + * 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 + +struct blocking_notifier_head *nh; +struct notifier_block nb; + +static void usb_vendor_receiver_pcm_info(struct data_pcm_info *data) +{ + int direction = data->direction; + int enable = data->enable; +#if IS_ENABLED(CONFIG_USB_NOTIFY_PROC_LOG) + int type = 0; + + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + type = NOTIFY_PCM_PLAYBACK; + else + type = NOTIFY_PCM_CAPTURE; + store_usblog_notify(type, (void *)&enable, NULL); +#endif /* CONFIG_USB_NOTIFY_PROC_LOG */ + pr_info("%s: enable(%d) direction(%d)\n", __func__, enable, direction); +} + +static void usb_vendor_receiver_cardnum(struct data_cardnum *data) +{ + int card_num = data->card_num; + int bundle = data->bundle; + int attach = data->attach; + + set_usb_audio_cardnum(card_num, bundle, attach); +} + +static void usb_vendor_receiver_audio_uevent(struct data_audio_uevent *data) +{ + struct usb_device *dev = data->dev; + int card_num = data->card_num; + int attach = data->attach; + + send_usb_audio_uevent(dev, card_num, attach); +} + +static void usb_vendor_receiver_new_device(struct data_new_device *data) +{ + struct usb_device *dev = data->dev; + int ret = 0; + + ret = check_new_device_added(dev); + if (ret) + data->ret = ret; +} + +static int usb_vendor_receiver_callback(struct notifier_block *nb, + unsigned long action, void *data) +{ + switch (action) { + case USB_VENDOR_NOTIFY_PCM_INFO: + usb_vendor_receiver_pcm_info(data); + break; + case USB_VENDOR_NOTIFY_CARDNUM: + usb_vendor_receiver_cardnum(data); + break; + case USB_VENDOR_NOTIFY_AUDIO_UEVENT: + usb_vendor_receiver_audio_uevent(data); + break; + case USB_VENDOR_NOTIFY_NEW_DEVICE: + usb_vendor_receiver_new_device(data); + break; + default: + break; + } + + return NOTIFY_DONE; +} + +#if IS_ENABLED(CONFIG_OF) +static struct platform_device *usb_vendor_receiver_get_notify(struct device *dev) +{ + struct platform_device *notify_pdev; + struct device_node *notify_node; + int ret = 0; + + notify_node = of_parse_phandle(dev->of_node, "notify", 0); + if (!notify_node) { + pr_err("%s: failed to get notify_node\n", __func__); + ret = -ENODEV; + goto out; + } + + notify_pdev = of_find_device_by_node(notify_node); + if (!notify_pdev) { + pr_err("%s: failed to get notify_pdev\n", __func__); + ret = -ENODEV; + goto out; + } + + return notify_pdev; +out: + return ERR_PTR(ret); +} +#endif /* CONFIG_OF */ + +static int usb_vendor_receiver_probe(struct platform_device *pdev) +{ + struct platform_device *notify_pdev; + int ret = 0; + +#if IS_ENABLED(CONFIG_OF) + notify_pdev = usb_vendor_receiver_get_notify(&pdev->dev); + if (IS_ERR(notify_pdev)) { + ret = -ENODEV; + goto out; + } +#else + pr_err("%s: failed to get notify_pdev\n", __func__); + ret = -ENODEV; + goto out; +#endif /* CONFIG_OF */ + + nh = platform_get_drvdata(notify_pdev); + if (!nh) { + pr_err("%s: failed to get notifier head\n", __func__); + ret = -ENODEV; + goto out; + } + + nb.notifier_call = usb_vendor_receiver_callback; + ret = blocking_notifier_chain_register(nh, &nb); + if (ret) { + pr_err("%s: failed blocking_notifier_chain_register(%d)\n", + __func__, ret); + nh = NULL; + goto out; + } + pr_info("%s done\n", __func__); +out: + return ret; +} + +static int usb_vendor_receiver_remove(struct platform_device *pdev) +{ + if (nh) + blocking_notifier_chain_unregister(nh, &nb); + nh = NULL; + return 0; +} + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id usb_vendor_receiver_dt_ids[] = { + { .compatible = "samsung,usb_vendor_receiver" }, + { } +}; +MODULE_DEVICE_TABLE(of, usb_vendor_receiver_dt_ids); +#endif /* CONFIG_OF */ + +static struct platform_driver usb_vendor_receiver_driver = { + .probe = usb_vendor_receiver_probe, + .remove = usb_vendor_receiver_remove, + .driver = { + .name = "usb_vendor_receiver", + .owner = THIS_MODULE, +#if IS_ENABLED(CONFIG_OF) + .of_match_table = of_match_ptr(usb_vendor_receiver_dt_ids), +#endif /* CONFIG_OF */ + }, +}; + +static int __init usb_vendor_receiver_init(void) +{ + return platform_driver_register(&usb_vendor_receiver_driver); +} + +static void __exit usb_vendor_receiver_exit(void) +{ + platform_driver_unregister(&usb_vendor_receiver_driver); +} + +module_init(usb_vendor_receiver_init); +module_exit(usb_vendor_receiver_exit); + +MODULE_DESCRIPTION("usb vendor receiver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/uwb/Kconfig b/drivers/uwb/Kconfig new file mode 100755 index 000000000000..82a7528ef5ab --- /dev/null +++ b/drivers/uwb/Kconfig @@ -0,0 +1,34 @@ +# +# UWB device configuration +# +config SAMSUNG_UWB + tristate "Samsung UWB driver" + default n + help + Say Y here if you want to build support for UWB devices. + To compile this support as a module, choose M here: the module will + be called uwb. + +config UWB_SR200 + bool "Nxp SPI driver for SR200 devices" + default n + help + NXP SR200 UWB controller support. + +config SEC_UWB_LOGGER + bool "UWB logger" + default n + help + Enable UWB logger. + +config ACPM_INIT + bool "ACPM INIT" + default n + help + ACPM INIT must be called to use CLK4 in S5910. + +config UWB_QM35 + bool "Qorvo SPI driver for QM35 devices" + default n + help + Qorvo QM35 UWB controller support. diff --git a/drivers/uwb/Makefile b/drivers/uwb/Makefile new file mode 100755 index 000000000000..c39a14c322b9 --- /dev/null +++ b/drivers/uwb/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for UWB devices +# + +ifeq ($(CONFIG_SAMSUNG_UWB),m) +obj-$(CONFIG_SAMSUNG_UWB) += uwb.o +uwb-$(CONFIG_UWB_SR200) += sr200.o +uwb-$(CONFIG_SEC_UWB_LOGGER) += uwb_logger/uwb_logger.o +else +obj-$(CONFIG_UWB_SR200) += sr200.o +obj-$(CONFIG_SEC_UWB_LOGGER) += uwb_logger/uwb_logger.o +endif diff --git a/drivers/uwb/sr200.c b/drivers/uwb/sr200.c new file mode 100755 index 000000000000..f357cc68ae39 --- /dev/null +++ b/drivers/uwb/sr200.c @@ -0,0 +1,1009 @@ +/*====================================================================================*/ +/* */ +/* Copyright 2022 NXP */ +/* */ +/* 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 */ +/* */ +/*====================================================================================*/ + +/** + * \addtogroup spi_driver + * + * @{ */ +#include "sr200.h" +#include "../uwb/uwb_logger/uwb_logger.h" + +#define DEBUG_LOG +/* Cold reset Feature in case of Secure Element tx timeout */ +#define ESE_COLD_RESET 1 +#if ESE_COLD_RESET +#ifdef CONFIG_NFC_NXP_COMBINED +#include "../nfc/nxp_combined/common_ese.h" +#else +#include "../nfc/common_ese.h" +#endif +/*Invoke cold reset if no response from eSE*/ +extern int perform_ese_cold_reset(unsigned long source); +#endif +static bool read_abort_requested = false; +static bool is_fw_dwnld_enabled = false; +/* Variable to store current debug level request by ioctl */ +static unsigned char debug_level; +#define SR200_DBG_MSG(msg...) \ + switch (debug_level) { \ + case SR200_DEBUG_OFF: \ + break; \ + case SR200_FULL_DEBUG: \ + UWB_LOG_INFO(msg); \ + break; \ + default: \ + printk(KERN_ERR "[NXP-sr200] : wrong debug level %d", debug_level); \ + break; \ + } +#define SR200_ERR_MSG(msg...) UWB_LOG_ERR(msg); + +#if 1//MW WA +#define WRITE_GUARD_TIME 500 +static bool isNtfReceived = false; +#endif + +/****************************************************************************** + * Function : sr200_dev_open + * + * Description : Open sr200 device node and returns instance to the user space + * + * Parameters : inode : sr200 device node path + * filep : File pointer to structure of sr200 device + * + * Returns : Returns file descriptor for sr200 device + * otherwise indicate each error code + ****************************************************************************/ +static int sr200_dev_open(struct inode *inode, struct file *filp) { + struct sr200_dev *sr200_dev = + container_of(filp->private_data, struct sr200_dev, sr200_device); + filp->private_data = sr200_dev; + SR200_DBG_MSG("%s : major No: %d, minor No: %d\n", __func__, imajor(inode), + iminor(inode)); + if (device_can_wakeup(&sr200_dev->spi->dev)) { + device_wakeup_enable(&sr200_dev->spi->dev); + } + return 0; +} +/****************************************************************************** + * Function : sr200_disable_irq + * + * Description : To disable IR + * + * Parameters : sr200_dev : sr200 device structure pointer + * + * Returns : Returns void + ****************************************************************************/ +static void sr200_disable_irq(struct sr200_dev *sr200_dev) { + unsigned long flags; +#ifdef DEBUG_LOG + UWB_LOG_REC("d i\n"); +#endif + spin_lock_irqsave(&sr200_dev->irq_enabled_lock, flags); + if ((sr200_dev->irq_enabled)) { + disable_irq_nosync(sr200_dev->spi->irq); + sr200_dev->irq_received = true; + sr200_dev->irq_enabled = false; + } + spin_unlock_irqrestore(&sr200_dev->irq_enabled_lock, flags); +} +/****************************************************************************** + * Function : sr200_enable_irq + * + * Description : Set the irq flag status + * + * Parameters : sr200_dev : sr200 device structure pointer + * + * Returns : Returns void + ****************************************************************************/ +static void sr200_enable_irq(struct sr200_dev *sr200_dev) { + unsigned long flags; +#ifdef DEBUG_LOG + UWB_LOG_REC("e i\n"); +#endif + spin_lock_irqsave(&sr200_dev->irq_enabled_lock, flags); + if (!sr200_dev->irq_enabled) { + enable_irq(sr200_dev->spi->irq); + sr200_dev->irq_enabled = true; + sr200_dev->irq_received = false; + } + spin_unlock_irqrestore(&sr200_dev->irq_enabled_lock, flags); +} +/****************************************************************************** + * Function : sr200_dev_irq_handler + * + * Description : Will get called when interrupt line asserted from sr200 + * + * Parameters : irq : IRQ Number + * dev_id : sr200 device Id + * + * Returns : Returns IRQ Handler + ****************************************************************************/ +static irqreturn_t sr200_dev_irq_handler(int irq, void *dev_id) { + struct sr200_dev *sr200_dev = dev_id; +#ifdef DEBUG_LOG + UWB_LOG_REC("h\n"); +#endif + sr200_disable_irq(sr200_dev); + /* Wake up waiting readers */ + wake_up(&sr200_dev->read_wq); + return IRQ_HANDLED; +} +/****************************************************************************** + * Function : sr200_dev_iotcl + * + * Description : Input/OutPut control from user space to perform required + * operation on sr200 device. + * + * Parameters : cmd : Indicates what operation needs to be done sr200 + * arg : Value to be passed to sr200 to do the required + * opeation + * + * Returns : 0 on success and (-1) on error + ****************************************************************************/ +static long sr200_dev_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) { + int ret = 0; + struct sr200_dev *sr200_dev = NULL; + SR200_DBG_MSG("sr200 - %s\n", __FUNCTION__); + sr200_dev = filp->private_data; + if(sr200_dev == NULL) { + ret = -EINVAL; + SR200_ERR_MSG("sr200_dev is NULL"); + return ret; + } + switch (cmd) { + case SR200_SET_PWR: + if (arg == PWR_ENABLE) { + SR200_DBG_MSG(" enable power request...\n"); + gpio_set_value(sr200_dev->reset_gpio, 1); + msleep(10); + } else if (arg == PWR_DISABLE) { + SR200_DBG_MSG("disable power request...\n"); + gpio_set_value(sr200_dev->reset_gpio, 0); + sr200_disable_irq(sr200_dev); + msleep(10); + } else if (arg == ABORT_READ_PENDING) { + SR200_ERR_MSG("%s abort read pending\n", __func__); + read_abort_requested = true; + sr200_disable_irq(sr200_dev); + /* Wake up waiting readers */ + wake_up(&sr200_dev->read_wq); + } + break; + case SR200_SET_FWD: + if (arg == 1) { + is_fw_dwnld_enabled = true; + read_abort_requested = false; + SR200_DBG_MSG("%s fw download enabled.\n", __func__); + } else if (arg == 0) { + is_fw_dwnld_enabled = false; + SR200_DBG_MSG("%s fw download disabled.\n", __func__); + } + break; + case SR200_GET_THROUGHPUT: + if (arg == 0) { + SR200_DBG_MSG("%s measure throughput disabled.\n", __func__); + } + break; +#if ESE_COLD_RESET + case SR200_ESE_RESET: + SR200_DBG_MSG("%s SR200_ESE_RESET enter\n", __func__); + ret = perform_ese_cold_reset(ESE_CLD_RST_OTHER); + break; +#endif + + case SR200_GET_ANT_CONNECTION_STATUS: + SR200_DBG_MSG("SR200_GET_ANT_CONNECTION_STATUS Enter\n"); + if((int)sr200_dev->ant_connection_status_gpio > 0) { + ret = !gpio_get_value(sr200_dev->ant_connection_status_gpio); + } else { + ret = -1; + } + SR200_DBG_MSG("SR200_GET_ANT_CONNECTION_STATUS ret =%d \n", ret); + break; + default: + SR200_ERR_MSG(" error case\n"); + ret = -EINVAL; // ToDo: After adding proper switch cases we have to + // return with error statusi here + } + return ret; +} +/****************************************************************************** + * Function : sr200_dev_transceive + * + * Description : Used to Write/read data from sr200 + * + * Parameters : sr200_dev :sr200 device structure pointer + * op_mode :Indicates write/read mode + * count : Number of bytes to be write/read + * Returns : return status code as per spi_status_codes enum, + * need added each status code and indicate + * when each status code returns + ****************************************************************************/ +static int sr200_dev_transceive(struct sr200_dev *sr200_dev, int op_mode, + int count) { + int ret; + mutex_lock(&sr200_dev->sr200_access_lock); + sr200_dev->mode = op_mode; + sr200_dev->total_bytes_to_read = 0; + sr200_dev->is_ext_payload_len_bit_set = 0; + ret = -1; + switch (sr200_dev->mode) { + case SR200_WRITE_MODE: { +#if 1 //MW WA + if (!is_fw_dwnld_enabled && isNtfReceived) { + isNtfReceived = false; + usleep_range(WRITE_GUARD_TIME, WRITE_GUARD_TIME + 10); + } +#endif + sr200_dev->write_count = 0; + ret = spi_write(sr200_dev->spi, sr200_dev->tx_buffer, count); + if (ret < 0) { + ret = -EIO; + SR200_ERR_MSG("spi_write: failed.\n"); + goto transcive_end; + } + sr200_dev->write_count = count; + ret = spi_transcive_success; + } break; + case SR200_READ_MODE: { + int hdr_length = UCI_HEADER_LEN; + int payloadLen = 0; + struct spi_transfer hdr_transfer; + if (!gpio_get_value(sr200_dev->irq_gpio)) { + SR200_ERR_MSG("IRQ might have gone low due to write\n"); + ret = spi_irq_wait_request; + goto transcive_end; + } + sr200_dev->read_count = 0; + // set directional byte to 0XFF for read operation. + sr200_dev->rx_dir_byte_buff[0] = 0xFF; + if (is_fw_dwnld_enabled) { + hdr_length = HDLL_MODE_HEADER_LEN; + } + hdr_transfer.len = (hdr_length + DIRECTIONAL_BYTE_LEN); // Total header bytes to read + // including directional byte. + hdr_transfer.rx_buf = sr200_dev->rx_buffer; + hdr_transfer.tx_buf = sr200_dev->rx_dir_byte_buff; + ret = spi_sync_transfer(sr200_dev->spi, &hdr_transfer, 1); + if (ret < 0) { + SR200_ERR_MSG("spi_sync_transfer: spi transfer error.. %d\n ", ret); + goto transcive_end; + } + if (is_fw_dwnld_enabled) { + sr200_dev->total_bytes_to_read = + (((sr200_dev->rx_buffer[1] & 0x1F) << 8) | + sr200_dev->rx_buffer[2]); // reading 13 bits length from HDLL header + sr200_dev->total_bytes_to_read += CRC_BYTES_LEN; + payloadLen = + sr200_dev->total_bytes_to_read + CRC_BYTES_LEN + DIRECTIONAL_BYTE_LEN; + } else { + if ((sr200_dev->rx_buffer[1] & UCI_MT_MASK) == 0) { + sr200_dev->total_bytes_to_read = + sr200_dev->rx_buffer[NORMAL_MODE_LEN_OFFSET + DIRECTIONAL_BYTE_LEN]; + sr200_dev->total_bytes_to_read = + ((sr200_dev->total_bytes_to_read << 8) | + sr200_dev->rx_buffer[UCI_EXTENDED_LENGTH_OFFSET + DIRECTIONAL_BYTE_LEN]); + }else { +#if 1 //MW WA + if ((sr200_dev->rx_buffer[1] & UCI_MT_MASK) == 0x60) { + isNtfReceived = true; + } +#endif + sr200_dev->is_ext_payload_len_bit_set = + (sr200_dev->rx_buffer[UCI_EXTND_LEN_INDICATOR_OFFSET + + DIRECTIONAL_BYTE_LEN] & + UCI_EXTND_LEN_INDICATOR_OFFSET_MASK); + sr200_dev->total_bytes_to_read = + sr200_dev->rx_buffer[NORMAL_MODE_LEN_OFFSET + DIRECTIONAL_BYTE_LEN]; + if (sr200_dev->is_ext_payload_len_bit_set) { + sr200_dev->total_bytes_to_read = + ((sr200_dev->total_bytes_to_read << 8) | + sr200_dev->rx_buffer[UCI_EXTENDED_LENGTH_OFFSET + + DIRECTIONAL_BYTE_LEN]); + } + } + payloadLen = sr200_dev->total_bytes_to_read + DIRECTIONAL_BYTE_LEN; + } + if (sr200_dev->total_bytes_to_read > (MAX_UCI_PKT_SIZE - UCI_HEADER_LEN)) { + SR200_ERR_MSG("length %d exceeds the max limit %d....", + (int)sr200_dev->total_bytes_to_read, (int)MAX_UCI_PKT_SIZE); + ret = -1; + goto transcive_end; + } + if (sr200_dev->total_bytes_to_read > 0) { + struct spi_transfer payload_transfer = { + .len = payloadLen, // Total bytes to read including directional byte + .rx_buf = &sr200_dev->rx_buffer[hdr_length + DIRECTIONAL_BYTE_LEN], + .tx_buf = sr200_dev->rx_dir_byte_buff, + }; + ret = spi_sync_transfer(sr200_dev->spi, &payload_transfer, 1); + if (ret < 0) { + SR200_ERR_MSG("spi_sync_transfer: spi transfer error.. %d\n ", ret); + goto transcive_end; + } + sr200_dev->total_bytes_to_read += (2 * DIRECTIONAL_BYTE_LEN); + } + sr200_dev->read_count = + (unsigned int)(sr200_dev->total_bytes_to_read + hdr_length); + } break; + default: + SR200_ERR_MSG("invalid operation .....\n"); + break; + } +transcive_end: + mutex_unlock(&sr200_dev->sr200_access_lock); + return ret; +} +/****************************************************************************** + * Function : sr200_dev_write + * + * Description : Write Data to sr200 on SPI line + * + * Parameters : filp : Device Node File Pointer + * buf : Buffer which contains data to be sent to sr200 + * count : Number of bytes to be send + * offset : Pointer to a object that indicates file position + * user is accessing. + * Returns : Number of bytes writen if write is success else (-1) + * otherwise indicate each error code + ****************************************************************************/ +static ssize_t sr200_dev_write(struct file *filp, const char *buf, size_t count, + loff_t *offset) { + int ret = -1; + struct sr200_dev *sr200_dev; + sr200_dev = filp->private_data; +#ifdef DEBUG_LOG + UWB_LOG_REC("w\n"); +#endif + if (sr200_dev == NULL) { + SR200_ERR_MSG("%s : sr200_dev is null \n", __func__); + return ret; + } + if (sr200_dev->tx_buffer == NULL ) { + SR200_ERR_MSG("sr200_dev->tx_buffer is null %s \n", __func__); + return ret; + } + if (count >= SR200_TXBUF_SIZE) { + SR200_ERR_MSG("%s : write size exceeds\n", __func__); + ret = -ENOBUFS; + goto write_end; + } + memset(sr200_dev->tx_buffer, 0x00, count + DIRECTIONAL_BYTE_LEN); + if (copy_from_user(sr200_dev->tx_buffer + DIRECTIONAL_BYTE_LEN, buf, count)) { + SR200_ERR_MSG("%s : failed to copy from user space \n", __func__); + return -EFAULT; + } + count += DIRECTIONAL_BYTE_LEN; // Including Direction byte + ret = sr200_dev_transceive(sr200_dev, SR200_WRITE_MODE, count); + if (ret == spi_transcive_success) { + ret = sr200_dev->write_count; + } else { + SR200_ERR_MSG("write failed......\n"); + } +#ifdef DEBUG_LOG + UWB_LOG_REC("w %d\n", ret); +#endif +write_end: + return ret; +} +/****************************************************************************** + * Function : sr200_dev_read + * + * Description : Used to read data from sr200 + * + * Parameters : filp : Device Node File Pointer + * buf : Buffer which contains data to be read from sr200 + * count : Number of bytes to be read + * offset : Pointer to a object that indicates file position + * user is accessing. + * Returns : Number of bytes read if read is success else (-1) + * otherwise indicate each error code + ****************************************************************************/ +static ssize_t sr200_dev_read(struct file *filp, char *buf, size_t count, + loff_t *offset) { + struct sr200_dev *sr200_dev = filp->private_data; + int ret = -EIO; + int retry_count = 0; + int hdr_length = UCI_HEADER_LEN; +#ifdef DEBUG_LOG + UWB_LOG_REC("r\n"); +#endif + if(sr200_dev == NULL) { + SR200_ERR_MSG("sr200_dev is null %s \n", __func__); + return ret; + } + if (sr200_dev->rx_buffer == NULL || sr200_dev->rx_dir_byte_buff == NULL) { + SR200_ERR_MSG("sr200_dev->rx_buffer is null %s \n", __func__); + return ret; + } + + /*500ms timeout in jiffies*/ + sr200_dev->timeout_in_ms = ((500 * HZ) / 2000); + memset(sr200_dev->rx_buffer, 0x00, SR200_RXBUF_SIZE); + memset(sr200_dev->rx_dir_byte_buff, 0x00, SR200_TXBUF_SIZE); + if (!gpio_get_value(sr200_dev->irq_gpio)) { + if (filp->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + return ret; + } + } + /*FW download packet read*/ + if (is_fw_dwnld_enabled) { + hdr_length = HDLL_MODE_HEADER_LEN; + } +first_irq_wait: + sr200_enable_irq(sr200_dev); + if (!read_abort_requested) { + ret = wait_event_interruptible(sr200_dev->read_wq, sr200_dev->irq_received); + if (ret) { + if (ret != -ERESTARTSYS) { + SR200_ERR_MSG("read_wq wait_event_interruptible() :%d Failed.\n", ret); + } + return ret; + } + } + if (read_abort_requested) { + read_abort_requested = false; + SR200_ERR_MSG("abort Read pending......\n"); + return ret; + } + mutex_lock(&sr200_dev->sr200_read_count_lock); + ret = sr200_dev_transceive(sr200_dev, SR200_READ_MODE, count); + if (ret == spi_transcive_success) { + sr200_dev->read_count -= DIRECTIONAL_BYTE_LEN; + if (copy_to_user(buf, &sr200_dev->rx_buffer[1], hdr_length)) { + SR200_ERR_MSG("sr200_dev_read: copy to user failed for hdr_length\n"); + ret = -EFAULT; + goto read_end; + } + + if (sr200_dev->read_count > hdr_length) { + sr200_dev->read_count -= DIRECTIONAL_BYTE_LEN; + if (copy_to_user(&buf[hdr_length], &sr200_dev->rx_buffer[hdr_length+2*DIRECTIONAL_BYTE_LEN], (sr200_dev->read_count-hdr_length))) { + SR200_ERR_MSG("sr200_dev_read: copy to user failed for payload\n"); + ret = -EFAULT; + goto read_end; + } + } + ret = sr200_dev->read_count; + } else if (ret == spi_irq_wait_request) { + SR200_DBG_MSG(" irg is low due to write hence irq is requested again...\n"); + mutex_unlock(&sr200_dev->sr200_read_count_lock); + goto first_irq_wait; + } else if (ret == spi_irq_wait_timeout) { + SR200_DBG_MSG("second irq is not received..time out...\n"); + ret = -1; + } else { + SR200_ERR_MSG("spi read failed...%d", ret); + ret = -1; + } +#ifdef DEBUG_LOG + UWB_LOG_REC("r %d\n", ret); +#endif +read_end: + mutex_unlock(&sr200_dev->sr200_read_count_lock); + retry_count = 0; + return ret; +} +/****************************************************************************** + * Function : sr200_hw_setup + * + * Description : Used to read data from sr200 + * + * Parameters : platform_data : struct sr200_spi_platform_data * + * + * Returns : retval 0 if ok else -1 on error + ****************************************************************************/ +static int sr200_hw_setup(struct sr200_spi_platform_data *platform_data) { + int ret; + SR200_DBG_MSG("entry : %s\n", __FUNCTION__); + ret = gpio_request(platform_data->irq_gpio, "sr200 irq"); + if (ret < 0) { + SR200_ERR_MSG("gpio request failed gpio = 0x%x\n", platform_data->irq_gpio); + goto fail; + } + ret = gpio_direction_input(platform_data->irq_gpio); + if (ret < 0) { + SR200_ERR_MSG("gpio request failed gpio = 0x%x\n", platform_data->irq_gpio); + goto fail_irq; + } + ret = gpio_request(platform_data->reset_gpio, "sr200 reset"); + if (ret < 0) { + SR200_ERR_MSG("gpio request failed gpio = 0x%x\n", platform_data->reset_gpio); + goto fail; + } + ret = gpio_direction_output(platform_data->reset_gpio, 0); + if (ret < 0) { + SR200_ERR_MSG("sr200 - failed setting ce gpio - %d\n", platform_data->reset_gpio); + goto fail_gpio; + } + if((int)platform_data->ant_connection_status_gpio > 0) { + ret = gpio_request(platform_data->ant_connection_status_gpio, "sr200 ant connection status"); + if (ret < 0) { + SR200_ERR_MSG("sr200 - Failed requesting ant connection status gpio - %d\n", platform_data->ant_connection_status_gpio); + goto fail_gpio; + } + ret = gpio_direction_input(platform_data->ant_connection_status_gpio); + if (ret < 0) { + SR200_ERR_MSG("sr200 - Failed setting ant connection status gpio - %d\n", platform_data->ant_connection_status_gpio); + goto fail_gpio; + } + } + + ret = 0; + SR200_DBG_MSG("Exit : %s\n", __FUNCTION__); + return ret; + +fail_gpio: + if((int)platform_data->ant_connection_status_gpio > 0) { + gpio_free(platform_data->ant_connection_status_gpio); + } + gpio_free(platform_data->reset_gpio); +fail_irq: + gpio_free(platform_data->irq_gpio); +fail: + SR200_ERR_MSG("sr200_hw_setup failed\n"); + return ret; +} +/****************************************************************************** + * Function : sr200_set_data + * + * Description : Set the sr200 device specific context for future use + * + * Parameters : spi : struct spi_device * + * data: void* + * + * Returns : retval 0 if ok else -1 on error + ****************************************************************************/ +static inline void sr200_set_data(struct spi_device *spi, void *data) { + dev_set_drvdata(&spi->dev, data); +} +/****************************************************************************** + * Function : sr200_get_data + * + * Description : Get the sr200 device specific context + * + * Parameters : spi : struct spi_device * + * + * Returns : retval 0 if ok else -1 on error + ****************************************************************************/ +static inline void *sr200_get_data(const struct spi_device *spi) { + return dev_get_drvdata(&spi->dev); +} +/* possible fops on the sr200 device */ +static const struct file_operations sr200_dev_fops = { + .owner = THIS_MODULE, + .read = sr200_dev_read, + .write = sr200_dev_write, + .open = sr200_dev_open, + .unlocked_ioctl = sr200_dev_ioctl, +}; +/****************************************************************************** + * Function : sr200_parse_dt + * + * Description : Parse the dtsi configartion + * + * Parameters : dev : struct spi_device * + * pdata: Ponter to platform data + * + * Returns : retval 0 if ok else -1 on error + ****************************************************************************/ +static int sr200_parse_dt(struct device *dev, + struct sr200_spi_platform_data *pdata) { + struct device_node *np = dev->of_node; + SR200_DBG_MSG("sr200 - %s\n", __FUNCTION__); + pdata->irq_gpio = of_get_named_gpio(np, "nxp,sr200-irq", 0); + if (!gpio_is_valid(pdata->irq_gpio)) { + return -EINVAL; + } + pdata->reset_gpio = of_get_named_gpio(np, "nxp,sr200-reset", 0); + if (!gpio_is_valid(pdata->reset_gpio)) { + return -EINVAL; + } + pdata->ant_connection_status_gpio = of_get_named_gpio(np, "nxp,sr200-ant-connection-status", 0); + if (!gpio_is_valid(pdata->ant_connection_status_gpio)) { + SR200_ERR_MSG("nxp,sr100-ant-connection-status not found\n"); + // return -EINVAL; + } + if (of_property_read_string(np, "nxp,vdd-io", &pdata->uwb_vdd_io) < 0) { + SR200_ERR_MSG("uwb_vdd_io not found\n"); + pdata->uwb_vdd_io = NULL; + } else { + SR200_ERR_MSG("uwb_vdd_io :%s\n", pdata->uwb_vdd_io); + } + if (of_property_read_string(np, "nxp,vdd-sw", &pdata->uwb_vdd_sw) < 0) { + SR200_ERR_MSG("uwb_vdd_sw not found\n"); + pdata->uwb_vdd_sw = NULL; + } else { + SR200_ERR_MSG("uwb_vdd_sw :%s\n", pdata->uwb_vdd_sw); + } + + SR200_DBG_MSG("sr200 : irq_gpio = %d, reset_gpio = %d \n", pdata->irq_gpio, + pdata->reset_gpio); + SR200_DBG_MSG("sr200 : ant_connection_status_gpio = %u \n", pdata->ant_connection_status_gpio); + return 0; +} + +static int sr200_regulator_onoff(struct device *dev, struct sr200_dev* pdev, bool onoff) +{ + int rc = 0; + struct regulator *regulator_uwb_vdd_io = NULL, *regulator_uwb_vdd_sw = NULL; + + if(pdev->uwb_vdd_io != NULL) { + regulator_uwb_vdd_io = regulator_get(dev, pdev->uwb_vdd_io); + if (IS_ERR(regulator_uwb_vdd_io) || regulator_uwb_vdd_io == NULL) { + SR200_ERR_MSG("regulator_uwb_vdd_io regulator_get fail\n"); + return -ENODEV; + } + } else { + regulator_uwb_vdd_io = NULL; + SR200_ERR_MSG("regulator_uwb_vdd_io not support\n"); + } + + if(pdev->uwb_vdd_sw != NULL) { + regulator_uwb_vdd_sw = regulator_get(dev, pdev->uwb_vdd_sw); + if (IS_ERR(regulator_uwb_vdd_sw) || regulator_uwb_vdd_sw == NULL) { + SR200_ERR_MSG("regulator_uwb_vdd_sw regulator_get fail\n"); + return -ENODEV; + } + } else { + regulator_uwb_vdd_sw = NULL; + SR200_ERR_MSG("regulator_uwb_vdd_sw not support\n"); + } + + SR200_DBG_MSG("sr200_regulator_onoff = %d\n", onoff); + if (onoff == true) { + if(regulator_uwb_vdd_io != NULL) { + rc = regulator_set_load(regulator_uwb_vdd_io, 400000); + if (rc) { + SR200_ERR_MSG("regulator_uwb_vdd_io set_load failed, rc=%d\n", rc); + goto regulator_err; + } + regulator_set_voltage(regulator_uwb_vdd_io, 1800000, 1800000); + rc = regulator_enable(regulator_uwb_vdd_io); + if (rc) { + SR200_ERR_MSG("regulator_uwb_vdd_io enable failed, rc=%d\n", rc); + goto regulator_err; + } + } + if(regulator_uwb_vdd_sw != NULL) { + rc = regulator_enable(regulator_uwb_vdd_sw); + if (rc) { + SR200_ERR_MSG("regulator_uwb_vdd_sw enable failed, rc=%d\n", rc); + goto regulator_err; + } + } + } else { + if(regulator_uwb_vdd_io != NULL) { + rc = regulator_disable(regulator_uwb_vdd_io); + if (rc) { + SR200_ERR_MSG("regulator_uwb_vdd_io disable failed, rc=%d\n", rc); + goto regulator_err; + } + } + if(regulator_uwb_vdd_sw != NULL) { + rc = regulator_disable(regulator_uwb_vdd_sw); + if (rc) { + SR200_ERR_MSG("regulator_uwb_vdd_sw disable failed, rc=%d\n", rc); + goto regulator_err; + } + } +regulator_err: + if(regulator_uwb_vdd_io != NULL) regulator_put(regulator_uwb_vdd_io); + if(regulator_uwb_vdd_sw != NULL) regulator_put(regulator_uwb_vdd_sw); + } + + return rc; +} + +/****************************************************************************** + * Function : sr200_probe + * + * Description : To probe for sr200 SPI interface. If found initialize the SPI + * clock,bit rate & SPI mode. It will create the dev entry + * (sr200) for user space. + * Parameters : spi : struct spi_device * + * + * Returns : retval 0 if ok else -1 on error + ****************************************************************************/ +static int sr200_probe(struct spi_device *spi) { + int ret; + struct sr200_spi_platform_data *platform_data = NULL; + struct sr200_spi_platform_data platform_data1; + struct sr200_dev *sr200_dev = NULL; + unsigned int irq_flags; + SR200_DBG_MSG("%s chip select : %d , bus number = %d \n", __FUNCTION__, + spi->chip_select, spi->master->bus_num); + ret = sr200_parse_dt(&spi->dev, &platform_data1); + if (ret) { + SR200_ERR_MSG("%s - failed to parse DT\n", __func__); + goto err_exit; + } + platform_data = &platform_data1; + sr200_dev = kzalloc(sizeof(*sr200_dev), GFP_KERNEL); + if (sr200_dev == NULL) { + SR200_ERR_MSG("failed to allocate memory for module data\n"); + ret = -ENOMEM; + goto err_exit; + } + ret = sr200_hw_setup(platform_data); + if (ret < 0) { + SR200_ERR_MSG("failed to sr200_enable_SR200_IRQ_ENABLE\n"); + goto err_exit0; + } + spi->bits_per_word = 8; + spi->mode = SPI_MODE_0; + spi->max_speed_hz = SR200_SPI_CLOCK; + ret = spi_setup(spi); + if (ret < 0) { + SR200_ERR_MSG("failed to do spi_setup()\n"); + goto err_exit0; + } + sr200_dev->spi = spi; + sr200_dev->sr200_device.minor = MISC_DYNAMIC_MINOR; + sr200_dev->sr200_device.name = "sr200"; + sr200_dev->sr200_device.fops = &sr200_dev_fops; + sr200_dev->sr200_device.parent = &spi->dev; + sr200_dev->irq_gpio = platform_data->irq_gpio; + sr200_dev->reset_gpio = platform_data->reset_gpio; + sr200_dev->ant_connection_status_gpio = platform_data->ant_connection_status_gpio; + sr200_dev->uwb_vdd_io = platform_data->uwb_vdd_io; + sr200_dev->uwb_vdd_sw = platform_data->uwb_vdd_sw; + sr200_dev->tx_buffer = kzalloc(SR200_TXBUF_SIZE, GFP_KERNEL); + sr200_dev->rx_buffer = kzalloc(SR200_RXBUF_SIZE, GFP_KERNEL); + sr200_dev->rx_dir_byte_buff = kzalloc(SR200_RXBUF_SIZE, GFP_KERNEL); + if (sr200_dev->tx_buffer == NULL) { + ret = -ENOMEM; + goto exit_free_dev; + } + if (sr200_dev->rx_buffer == NULL) { + ret = -ENOMEM; + goto exit_free_dev; + } + if (sr200_dev->rx_dir_byte_buff == NULL) { + ret = -ENOMEM; + goto exit_free_dev; + } + dev_set_drvdata(&spi->dev, sr200_dev); + /* init mutex and queues */ + init_waitqueue_head(&sr200_dev->read_wq); + mutex_init(&sr200_dev->sr200_access_lock); + mutex_init(&sr200_dev->sr200_read_count_lock); + + spin_lock_init(&sr200_dev->irq_enabled_lock); + ret = misc_register(&sr200_dev->sr200_device); + if (ret < 0) { + SR200_ERR_MSG("misc_register failed! %d\n", ret); + goto err_exit0; + } + + ret = sr200_regulator_onoff(&spi->dev, sr200_dev, true); + if (ret < 0) { + SR200_ERR_MSG("regulator_on fail err:%d\n", ret); + } + usleep_range(1000, 1100); + + sr200_dev->spi->irq = gpio_to_irq(platform_data->irq_gpio); + if (sr200_dev->spi->irq < 0) { + SR200_ERR_MSG("gpio_to_irq request failed gpio = 0x%x\n", + platform_data->irq_gpio); + goto err_exit1; + } + /* request irq. the irq is set whenever the chip has data available + * for reading. it is cleared when all data has been read. + */ + // irq_flags = IRQF_TRIGGER_RISING; + irq_flags = IRQ_TYPE_LEVEL_HIGH; + sr200_dev->irq_enabled = true; + sr200_dev->irq_received = false; + ret = request_irq(sr200_dev->spi->irq, sr200_dev_irq_handler, irq_flags, + sr200_dev->sr200_device.name, sr200_dev); + if (ret) { + SR200_ERR_MSG("request_irq failed\n"); + goto err_exit1; + } else { + sr200_disable_irq(sr200_dev); + device_set_wakeup_capable(&sr200_dev->spi->dev, true); + device_wakeup_disable(&sr200_dev->spi->dev); + } + + SR200_DBG_MSG("exit : %s\n", __FUNCTION__); + return ret; +err_exit1: +exit_free_dev: + if (sr200_dev != NULL) { + if (sr200_dev->tx_buffer) { + kfree(sr200_dev->tx_buffer); + } + if (sr200_dev->rx_buffer) { + kfree(sr200_dev->rx_buffer); + } + if (sr200_dev->rx_dir_byte_buff) { + kfree(sr200_dev->rx_dir_byte_buff); + } + misc_deregister(&sr200_dev->sr200_device); + } +err_exit0: + if (sr200_dev != NULL) { + mutex_destroy(&sr200_dev->sr200_access_lock); + mutex_destroy(&sr200_dev->sr200_read_count_lock); + } +err_exit: + if (sr200_dev != NULL) + kfree(sr200_dev); + SR200_DBG_MSG("ERROR: exit : %s ret %d\n", __FUNCTION__, ret); + return ret; +} + +#if KERNEL_VERSION(5, 18, 0) <= LINUX_VERSION_CODE +static void sr200_remove(struct spi_device *spi) { + struct sr200_dev *sr200_dev = sr200_get_data(spi); + SR200_DBG_MSG("entry : %s\n", __FUNCTION__); + if (sr200_dev != NULL) { + device_wakeup_disable(&sr200_dev->spi->dev); + sr200_regulator_onoff(&spi->dev, sr200_dev, false); + gpio_free(sr200_dev->reset_gpio); + mutex_destroy(&sr200_dev->sr200_access_lock); + free_irq(sr200_dev->spi->irq, sr200_dev); + gpio_free(sr200_dev->irq_gpio); + if((int)sr200_dev->ant_connection_status_gpio > 0) { + gpio_free(sr200_dev->ant_connection_status_gpio); + } + misc_deregister(&sr200_dev->sr200_device); + + if (sr200_dev->tx_buffer != NULL) + kfree(sr200_dev->tx_buffer); + if (sr200_dev->rx_buffer != NULL) + kfree(sr200_dev->rx_buffer); + if(sr200_dev->rx_dir_byte_buff != NULL) + kfree(sr200_dev->rx_dir_byte_buff); + kfree(sr200_dev); + } + SR200_DBG_MSG("exit : %s\n", __FUNCTION__); + return; +} +#else +/****************************************************************************** + * Function : sr200_remove + * + * Description : Will get called when the device is removed to release the + * resources. + * + * Parameters : spi : struct spi_device * + * + * Returns : retval 0 if ok else -1 on error + ****************************************************************************/ +static int sr200_remove(struct spi_device *spi) { + struct sr200_dev *sr200_dev = sr200_get_data(spi); + SR200_DBG_MSG("entry : %s\n", __FUNCTION__); + if (sr200_dev != NULL) { + device_wakeup_disable(&sr200_dev->spi->dev); + sr200_regulator_onoff(&spi->dev, sr200_dev, false); + gpio_free(sr200_dev->reset_gpio); + mutex_destroy(&sr200_dev->sr200_access_lock); + mutex_destroy(&sr200_dev->sr200_read_count_lock); + free_irq(sr200_dev->spi->irq, sr200_dev); + gpio_free(sr200_dev->irq_gpio); + if((int)sr200_dev->ant_connection_status_gpio > 0) { + gpio_free(sr200_dev->ant_connection_status_gpio); + } + misc_deregister(&sr200_dev->sr200_device); + + if (sr200_dev->tx_buffer != NULL) + kfree(sr200_dev->tx_buffer); + if (sr200_dev->rx_buffer != NULL) + kfree(sr200_dev->rx_buffer); + if(sr200_dev->rx_dir_byte_buff != NULL) + kfree(sr200_dev->rx_dir_byte_buff); + kfree(sr200_dev); + } + SR200_DBG_MSG("exit : %s\n", __FUNCTION__); + return 0; +} +#endif + +/** + * sr200_dev_resume + * + * Executed after waking the system up from a sleep state + * + */ +int sr200_dev_resume(struct device *dev) +{ + struct sr200_dev *sr200_dev = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + disable_irq_wake(sr200_dev->spi->irq); + + return 0; +} + +/** + * sr200_dev_suspend + * + * Executed before putting the system into a sleep state + * + */ +int sr200_dev_suspend(struct device *dev) +{ + struct sr200_dev *sr200_dev = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + enable_irq_wake(sr200_dev->spi->irq); + + return 0; +} + +static struct of_device_id sr200_dt_match[] = {{ + .compatible = "nxp,sr200", + }, + {}}; + +static const struct dev_pm_ops sr200_dev_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS( + sr200_dev_suspend, sr200_dev_resume) }; + +static struct spi_driver sr200_driver = { + .driver = + { + .name = "sr200", + .pm = &sr200_dev_pm_ops, + .bus = &spi_bus_type, + .owner = THIS_MODULE, + .of_match_table = sr200_dt_match, + }, + .probe = sr200_probe, + .remove = (sr200_remove), +}; +/****************************************************************************** + * Function : sr200_dev_init + * + * Description : Module init interface + * + * Parameters :void + * + * Returns : returns handle + ****************************************************************************/ +static int __init sr200_dev_init(void) { + debug_level = SR200_FULL_DEBUG; + uwb_logger_init(); + SR200_DBG_MSG("entry : %s\n", __FUNCTION__); + return spi_register_driver(&sr200_driver); +} +module_init(sr200_dev_init); +/****************************************************************************** + * Function : sr200_dev_exit + * + * Description : Module Exit interface + * + * Parameters :void + * + * Returns : returns void + ****************************************************************************/ +static void __exit sr200_dev_exit(void) { + SR200_DBG_MSG("entry : %s\n", __FUNCTION__); + spi_unregister_driver(&sr200_driver); + SR200_DBG_MSG("exit : %s\n", __FUNCTION__); +} +module_exit(sr200_dev_exit); +MODULE_AUTHOR("Manjunatha Venkatesh"); +MODULE_DESCRIPTION("NXP SR200 SPI driver"); +MODULE_LICENSE("GPL"); +/** @} */ diff --git a/drivers/uwb/sr200.h b/drivers/uwb/sr200.h new file mode 100755 index 000000000000..f113ad6ebd00 --- /dev/null +++ b/drivers/uwb/sr200.h @@ -0,0 +1,122 @@ +/*====================================================================================*/ +/* */ +/* Copyright 2022 NXP */ +/* */ +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SR200_MAGIC 0xEA +#define SR200_SET_PWR _IOW(SR200_MAGIC, 0x01, long) +#define SR200_SET_DBG _IOW(SR200_MAGIC, 0x02, long) +#define SR200_SET_POLL _IOW(SR200_MAGIC, 0x03, long) +#define SR200_SET_FWD _IOW(SR200_MAGIC, 0x04, long) +#define SR200_GET_THROUGHPUT _IOW(SR200_MAGIC, 0x05, long) +#define SR200_ESE_RESET _IOW(SR200_MAGIC, 0x06, long) +#define SR200_GET_ANT_CONNECTION_STATUS _IOW(SR200_MAGIC, 0x61, long) +#define UCI_HEADER_LEN 4 +#define HDLL_MODE_HEADER_LEN 2 +#define NORMAL_MODE_LEN_OFFSET 3 +#define UCI_NORMAL_PKT_SIZE 0 +#define DIRECTIONAL_BYTE_LEN 1 +#define CRC_BYTES_LEN 2 +#define UCI_MT_MASK 0xE0 +#define UCI_EXTND_LEN_INDICATOR_OFFSET 1 +#define UCI_EXTND_LEN_INDICATOR_OFFSET_MASK 0x80 +#define UCI_EXTENDED_LENGTH_OFFSET 2 + +#define SR200_TXBUF_SIZE 4201 //4200 + one directional byte +#define SR200_RXBUF_SIZE 4202 //4200 + two directional bytes +#define SR200_MAX_TX_BUF_SIZE 4200 +#define MAX_READ_RETRY_COUNT 10 +/* Macro to define SPI clock frequency */ +#define SR200_SPI_CLOCK 20000000L; +/* Maximum UCI packet size supported from the driver */ +#define MAX_UCI_PKT_SIZE 4200 +/* Different driver debug lever */ +struct sr200_spi_platform_data { + unsigned int irq_gpio; + unsigned int reset_gpio; + unsigned int ant_connection_status_gpio; + const char *uwb_vdd_io; + const char *uwb_vdd_sw; +}; +enum { + PWR_DISABLE = 0, + PWR_ENABLE, + ABORT_READ_PENDING +}; +enum SR200_DEBUG_LEVEL { + SR200_DEBUG_OFF, + SR200_FULL_DEBUG +}; +enum spi_status_codes { + spi_transcive_success, + spi_transcive_fail, + spi_irq_wait_request, + spi_irq_wait_timeout +}; +enum spi_operation_modes { + SR200_WRITE_MODE, + SR200_READ_MODE +}; +/* Device specific macro and structure */ +struct sr200_dev { + wait_queue_head_t read_wq; /* wait queue for read interrupt */ + struct spi_device *spi; /* spi device structure */ + struct miscdevice sr200_device; /* char device as misc driver */ + unsigned int reset_gpio; /* SW Reset gpio */ + unsigned int irq_gpio; /* sr200 will interrupt DH for any ntf */ + unsigned int ant_connection_status_gpio; /* antenna connection status */ + bool irq_enabled; /* flag to indicate disable/enable irq sequence */ + bool irq_received; /* flag to indicate that irq is received */ + spinlock_t irq_enabled_lock; /* spin lock for read irq */ + unsigned char *tx_buffer; /* transmit buffer */ + unsigned char *rx_buffer; /* receive buffer buffer */ + unsigned char *rx_dir_byte_buff; /* rx directional byte buffer */ + unsigned int write_count; /* Holds nubers of byte writen*/ + unsigned int read_count; /* Hold nubers of byte read */ + struct mutex sr200_access_lock; /* Hold mutex lock to between read and write */ + size_t total_bytes_to_read; + size_t is_ext_payload_len_bit_set; + int mode; + long timeout_in_ms; + const char *uwb_vdd_io; + const char *uwb_vdd_sw; + struct mutex sr200_read_count_lock; +}; diff --git a/drivers/uwb/uwb_logger/Kconfig b/drivers/uwb/uwb_logger/Kconfig new file mode 100755 index 000000000000..20b4c844074b --- /dev/null +++ b/drivers/uwb/uwb_logger/Kconfig @@ -0,0 +1 @@ +# Dummy file diff --git a/drivers/uwb/uwb_logger/Makefile b/drivers/uwb/uwb_logger/Makefile new file mode 100755 index 000000000000..3f4bfa3c65d7 --- /dev/null +++ b/drivers/uwb/uwb_logger/Makefile @@ -0,0 +1,7 @@ +# +# Copyright (c) 2013 Samsung Electronics Co., Ltd. +# http://www.samsung.com +# +# Licensed under GPLv2 +# + diff --git a/drivers/uwb/uwb_logger/uwb_logger.c b/drivers/uwb/uwb_logger/uwb_logger.c new file mode 100755 index 000000000000..ab41f6cc6f31 --- /dev/null +++ b/drivers/uwb/uwb_logger/uwb_logger.c @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * UWB logger + * + * 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. + */ + +#include "uwb_logger.h" + +#define BUF_SIZE SZ_256K +#define MAX_STR_LEN 160 +#define PROC_FILE_NAME "uwblog" +#define LOG_PREFIX "sec-uwb" +#define PRINT_DATE_FREQ 20 + +static char log_buf[BUF_SIZE]; +static unsigned int g_curpos; +static int is_uwb_logger_init; +static int is_buf_full; +static int log_max_count = -1; + +/* set max log count, if count is -1, no limit */ +void uwb_logger_set_max_count(int count) +{ + log_max_count = count; +} + +void uwb_logger_print_date_time(void) +{ + char tmp[64] = {0x0, }; + struct timespec64 ts; + struct tm tm; + unsigned long sec; + + ktime_get_real_ts64(&ts); + sec = ts.tv_sec - (sys_tz.tz_minuteswest * 60); + time64_to_tm(sec, 0, &tm); + snprintf(tmp, sizeof(tmp), "@%02d-%02d %02d:%02d:%02d.%03lu", tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec / 1000000); + + uwb_logger_print("%s\n", tmp); +} + +void uwb_logger_print(const char *fmt, ...) +{ + int len; + va_list args; + char buf[MAX_STR_LEN] = {0, }; + u64 time; + unsigned long nsec; + volatile unsigned int curpos; + static unsigned int log_count = PRINT_DATE_FREQ; + + if (!is_uwb_logger_init) + return; + + if (log_max_count == 0) + return; + else if (log_max_count > 0) + log_max_count--; + + if (--log_count == 0) { + uwb_logger_print_date_time(); + log_count = PRINT_DATE_FREQ; + } + time = local_clock(); + nsec = do_div(time, 1000000000); + len = snprintf(buf, sizeof(buf), "[%5lu.%06ld] ", (unsigned long)time, nsec / 1000); + + va_start(args, fmt); + len += vsnprintf(buf + len, MAX_STR_LEN - len, fmt, args); + va_end(args); + + if (len > MAX_STR_LEN) + len = MAX_STR_LEN; + + curpos = g_curpos; + if (curpos + len >= BUF_SIZE) { + g_curpos = curpos = 0; + is_buf_full = 1; + } + memcpy(log_buf + curpos, buf, len); + g_curpos += len; +} + +void uwb_print_hex_dump(void *buf, void *pref, uint32_t size) +{ + uint8_t *ptr = buf; + uint32_t i; + char tmp[128] = {0x0, }; + char *ptmp = tmp; + int len; + + if (!is_uwb_logger_init) + return; + + if (log_max_count == 0) + return; + else if (log_max_count > 0) + log_max_count--; + + for (i = 0; i < size; i++) { + len = snprintf(ptmp, 4, "%02x ", *ptr++); + ptmp = ptmp + len; + if (((i+1)%16) == 0) { + uwb_logger_print("%s%s\n", pref, tmp); + ptmp = tmp; + } + } + + if (i % 16) { + len = ptmp - tmp; + tmp[len] = 0x0; + uwb_logger_print("%s%s\n", pref, tmp); + } +} + +static ssize_t uwb_logger_read(struct file *file, char __user *buf, size_t len, loff_t *offset) +{ + loff_t pos = *offset; + ssize_t count; + size_t size; + volatile unsigned int curpos = g_curpos; + + if (is_buf_full || BUF_SIZE <= curpos) + size = BUF_SIZE; + else + size = (size_t)curpos; + + if (pos >= size) + return 0; + + count = min(len, size); + + if ((pos + count) > size) + count = size - pos; + + if (copy_to_user(buf, log_buf + pos, count)) + return -EFAULT; + + *offset += count; + + return count; +} + +static sec_input_proc_ops(uwb_logger_ops, THIS_MODULE, uwb_logger_read); + +int uwb_logger_init(void) +{ + struct proc_dir_entry *entry; + + if (is_uwb_logger_init) + return 0; + + entry = proc_create(PROC_FILE_NAME, 0444, NULL, &uwb_logger_ops); + if (!entry) { + pr_err("%s: failed to create proc entry\n", __func__); + return 0; + } + + proc_set_size(entry, BUF_SIZE); + is_uwb_logger_init = 1; + uwb_logger_print("uwb logger init ok\n"); + + return 0; +} diff --git a/drivers/uwb/uwb_logger/uwb_logger.h b/drivers/uwb/uwb_logger/uwb_logger.h new file mode 100755 index 000000000000..4fd45d15ecee --- /dev/null +++ b/drivers/uwb/uwb_logger/uwb_logger.h @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 10, 0) +#include +#else +#include +#endif + + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)) +#define sec_input_proc_ops(ops_name, ops_owner, read_fn) \ + const struct proc_ops ops_name = { \ + .proc_read = read_fn, \ + } +#else +#define sec_input_proc_ops(ops_name, ops_owner, read_fn) \ + const struct file_operations ops_name = { \ + .owner = ops_owner, \ + .read = read_fn, \ + } +#endif + +#ifndef _UWB_LOGGER_H_ +#define _UWB_LOGGER_H_ + +#ifdef CONFIG_SEC_UWB_LOGGER + +#define UWB_LOG_ERR(fmt, ...) \ + do { \ + pr_err(fmt, ##__VA_ARGS__); \ + uwb_logger_print(fmt, ##__VA_ARGS__); \ + } while (0) +#define UWB_LOG_INFO(fmt, ...) \ + do { \ + pr_info(fmt, ##__VA_ARGS__); \ + uwb_logger_print(fmt, ##__VA_ARGS__); \ + } while (0) +#define UWB_LOG_DBG(fmt, ...) pr_debug(sec-uwb, ##__VA_ARGS__) +#define UWB_LOG_REC(fmt, ...) uwb_logger_print(fmt, ##__VA_ARGS__) + +void uwb_logger_set_max_count(int count); +void uwb_logger_print(const char *fmt, ...); +void uwb_print_hex_dump(void *buf, void *pref, uint32_t size); +int uwb_logger_init(void); + +#else /*CONFIG_SEC_UWB_LOGGER*/ + +#define UWB_LOG_ERR(fmt, ...) pr_err(fmt, ##__VA_ARGS__) +#define UWB_LOG_INFO(fmt, ...) pr_info(fmt, ##__VA_ARGS__) +#define UWB_LOG_DBG(fmt, ...) pr_debug(fmt, ##__VA_ARGS__) +#define UWB_LOG_REC(fmt, ...) do { } while (0) + +#define uwb_print_hex_dump(a, b, c) do { } while (0) +#define uwb_logger_init() do { } while (0) +#define uwb_logger_set_max_count(a) do { } while (0) +#endif + +#endif diff --git a/drivers/vibrator/common/inputff/Kconfig b/drivers/vibrator/common/inputff/Kconfig new file mode 100644 index 000000000000..a7757079ac74 --- /dev/null +++ b/drivers/vibrator/common/inputff/Kconfig @@ -0,0 +1,33 @@ +config SEC_VIBRATOR_INPUTFF + tristate "sec vibrator inputff" + default n + help + If you say yes here you will get support for the + sec vibrator inputff driver + +config SEC_VIBRATOR_INPUTFF_TEST + bool "KUnit test for sec_vibrator_inputff_test" + depends on SEC_KUNIT + help + This driver is for inputff test driver + sec vibrator kunit test + If you want to add some functions, + Please make the test case. + +config SEC_VIB_FOLD_MODEL + bool "get support for folder/flip status" + default n + help + If you enable this feature, + you will get event for folder/flip status + through event_cmd sysfs + +config VIB_STORE_LE_PARAM + bool "To store the le param into NV preferably on SEC PARAM" + default n + help + If you enable this feature, + the LRA Estimated data will be stored on to a non-volatile memory + preferably on SEC_PARAM. If sec param is used, make sure to enable + at BL as well to match the param size. + diff --git a/drivers/vibrator/common/inputff/Makefile b/drivers/vibrator/common/inputff/Makefile new file mode 100644 index 000000000000..4db69aee9d58 --- /dev/null +++ b/drivers/vibrator/common/inputff/Makefile @@ -0,0 +1,5 @@ +subdir-ccflags-y := -Wformat +obj-$(CONFIG_SEC_VIBRATOR_INPUTFF) += sec_vibrator_inputff_module.o +sec_vibrator_inputff_module-y := sec_vibrator_inputff.o sec_vibrator_inputff_sysfs.o +ifeq ($(CONFIG_SEC_KUNIT), y) +endif diff --git a/drivers/vibrator/common/inputff/sec_vibrator_inputff.c b/drivers/vibrator/common/inputff/sec_vibrator_inputff.c new file mode 100644 index 000000000000..d0b2a97c196f --- /dev/null +++ b/drivers/vibrator/common/inputff/sec_vibrator_inputff.c @@ -0,0 +1,874 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 Samsung Electronics Co. Ltd. + * + * 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. + */ + +/* sec vibrator inputff */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_SEC_KUNIT) +#include +#else +#define __visible_for_testing static +#endif + +static struct sec_vib_inputff_pdata sec_vib_inputff_pdata; + +static struct blocking_notifier_head sec_vib_inputff_nb_head = + BLOCKING_NOTIFIER_INIT(sec_vib_inputff_nb_head); + +int sec_vib_inputff_notifier_register(struct notifier_block *nb) +{ + int ret = 0; + + pr_info("%s\n", __func__); + + ret = blocking_notifier_chain_register(&sec_vib_inputff_nb_head, nb); + if (ret < 0) + pr_err("%s: failed(%d)\n", __func__, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(sec_vib_inputff_notifier_register); + +int sec_vib_inputff_notifier_unregister(struct notifier_block *nb) +{ + int ret = 0; + + pr_info("%s\n", __func__); + + ret = blocking_notifier_chain_unregister(&sec_vib_inputff_nb_head, nb); + if (ret < 0) + pr_err("%s: failed(%d)\n", __func__, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(sec_vib_inputff_notifier_unregister); + +int sec_vib_inputff_vib_notifier_notify(void) +{ + int ret = 0; + + ret = blocking_notifier_call_chain(&sec_vib_inputff_nb_head, + VIB_NOTIFIER_ON, NULL); + + switch (ret) { + case NOTIFY_DONE: + case NOTIFY_OK: + pr_info("%s done(0x%x)\n", __func__, ret); + break; + default: + pr_info("%s failed(0x%x)\n", __func__, ret); + break; + } + + return ret; +} +EXPORT_SYMBOL_GPL(sec_vib_inputff_vib_notifier_notify); + +__visible_for_testing bool sec_vib_inputff_getbit(struct sec_vib_inputff_drvdata *ddata, int val) +{ + return (ddata->ff_val >> (val - FF_EFFECT_MIN) & 1); +} + +int sec_vib_inputff_setbit(struct sec_vib_inputff_drvdata *ddata, int val) +{ + return ddata->ff_val |= (1 << (val - FF_EFFECT_MIN)); +} +EXPORT_SYMBOL_GPL(sec_vib_inputff_setbit); + +static void configure_constant_effect(struct sec_vib_inputff_drvdata *ddata, + struct common_inputff_effect *inputff_effect, int index) +{ + if (!ddata || !inputff_effect) + return; + + ddata->effects[index].gain = (ddata->effect_gain * inputff_effect->scale) / 100; + ddata->effects[index].compose_effects.type = inputff_effect->type; + ddata->effects[index].compose_effects.id = ddata->compose.compose_effect_id; + ddata->effects[index].compose_effects.replay.length = inputff_effect->duration; + ddata->effects[index].compose_effects.u.constant.level = inputff_effect->frequency; + pr_info("%s <%d> gain:%d type:%d duration:%d\n", __func__, index, + ddata->effects[index].gain, ddata->effects[index].compose_effects.type, + ddata->effects[index].compose_effects.replay.length); +} + +static void configure_periodic_effect(struct sec_vib_inputff_drvdata *ddata, + struct common_inputff_effect *inputff_effect, int index) +{ + if (!ddata || !inputff_effect) + return; + + ddata->effects[index].gain = (ddata->effect_gain * inputff_effect->scale) / 100; + ddata->effects[index].compose_effects.type = inputff_effect->type; + ddata->effects[index].compose_effects.id = ddata->compose.compose_effect_id; + ddata->effects[index].compose_effects.replay.length = inputff_effect->duration; + ddata->effects[index].compose_effects.u.periodic.offset = inputff_effect->effect_id; + ddata->effects[index].compose_effects.u.periodic.waveform = 0; + pr_info("%s <%d> gain:%d type:%d sep index:%d duration:%d\n", __func__, index, + ddata->effects[index].gain, ddata->effects[index].compose_effects.type, + ddata->effects[index].compose_effects.u.periodic.offset, + ddata->effects[index].compose_effects.replay.length); +} + +static void configure_vib_free_duration(struct sec_vib_inputff_drvdata *ddata, + struct common_inputff_effect *inputff_effect, int index) +{ + if (!ddata || !inputff_effect) + return; + + ddata->effects[index].compose_effects.type = 0; + ddata->effects[index].compose_effects.replay.length = inputff_effect->duration; + pr_info("%s <%d> type:%d duration:%d\n", __func__, index, + ddata->effects[index].compose_effects.type, + ddata->effects[index].compose_effects.replay.length); +} + +__visible_for_testing int parsing_compose_effects(struct sec_vib_inputff_drvdata *ddata, + struct common_inputff_effects *input_effects) +{ + struct common_inputff_effect *inputff_effect; + int i = 0, ret = 0; + + if (!ddata) { + ret = -ENOENT; + pr_err("%s ddata null\n", __func__); + goto err; + } + + if (!input_effects) { + ret = -ENOENT; + pr_err("%s input_effects null\n", __func__); + goto err; + } + + ddata->compose.num_of_compose_effects = input_effects->num_of_effects; + ddata->compose.compose_repeat = input_effects->repeat; + + pr_info("%s num_of_effects:%d repeat:%d\n", __func__, + ddata->compose.num_of_compose_effects, ddata->compose.compose_repeat); + + if (ddata->compose.num_of_compose_effects > MAX_COMPOSE_EFFECT) { + pr_err("%s error. num_of_compose_effects=%d\n", __func__, + ddata->compose.num_of_compose_effects); + ret = -EFBIG; + goto err; + } + + for (i = 0; i < ddata->compose.num_of_compose_effects; i++) { + inputff_effect = &input_effects->effects[i]; + + switch (inputff_effect->type) { + case FF_CONSTANT: + configure_constant_effect(ddata, inputff_effect, i); + break; + case FF_PERIODIC: + configure_periodic_effect(ddata, inputff_effect, i); + break; + case VIB_FREE_DURATION: + configure_vib_free_duration(ddata, inputff_effect, i); + break; + default: + pr_err("%s invalid effect type=%d\n", __func__, inputff_effect->type); + ret = -EINVAL; + goto err; + } + } +err: + return ret; +} + +__visible_for_testing void compose_effect_set_gain(struct input_dev *dev, u16 gain) +{ + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + + pr_info("%s\n", __func__); + + if (!ddata) + return; + + if (ddata && ddata->vib_ops->set_gain) + ddata->vib_ops->set_gain(dev, gain); +} + +__visible_for_testing int compose_effect_upload(struct input_dev *dev, struct ff_effect *effect) +{ + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + int ret = 0; + + pr_info("%s\n", __func__); + + if (!ddata || !effect) { + ret = -ENOENT; + goto err; + } + + if (ddata && ddata->vib_ops->upload) { + ret = ddata->vib_ops->upload(dev, effect, NULL); + if (ret) { + pr_err("%s error. upload ret=%d\n", __func__, ret); + goto err; + } + } + ddata->compose.upload_partial_effect = 1; +err: + return ret; +} + +__visible_for_testing int compose_effect_playback_on(struct input_dev *dev, + struct ff_effect *effect) +{ + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + int ret = 0; + + pr_info("%s\n", __func__); + + if (!ddata || !effect) { + ret = -ENOENT; + goto err; + } + + if (ddata && ddata->vib_ops->playback) { + ret = ddata->vib_ops->playback(dev, effect->id, 1); + if (ret) + pr_err("%s error. ret=%d\n", __func__, ret); + } +err: + return ret; +} + +__visible_for_testing int compose_effect_playback_off(struct input_dev *dev, + struct ff_effect *effect) +{ + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + int ret = 0; + + pr_info("%s\n", __func__); + + if (!ddata || !effect) { + ret = -ENOENT; + goto err; + } + + if (ddata && ddata->vib_ops->playback) { + ret = ddata->vib_ops->playback(dev, effect->id, 0); + if (ret) + pr_err("%s error. ret=%d\n", __func__, ret); + } +err: + return ret; +} + +__visible_for_testing int compose_effect_erase(struct input_dev *dev, int effect_id) +{ + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + int ret = 0; + + pr_info("%s\n", __func__); + + if (!ddata) { + ret = -ENOENT; + goto err; + } + + if (ddata->vib_ops->erase) { + ret = ddata->vib_ops->erase(dev, effect_id); + if (ret) { + pr_err("%s error. ret=%d\n", __func__, ret); + goto err; + } + } + + ddata->compose.upload_partial_effect = 0; +err: + return ret; +} + +static void compose_effects_play_work(struct kthread_work *work) +{ + struct sec_vib_inputff_compose *compose = + container_of(work, struct sec_vib_inputff_compose, kwork); + struct sec_vib_inputff_drvdata *ddata = + container_of(compose, struct sec_vib_inputff_drvdata, compose); + struct input_dev *dev = ddata->input; + struct ff_effect *effect = NULL; + u16 gain = 0; + int ret = 0, pattern_seq = 0, type = 0, duration = 0; + + pr_info("%s start\n", __func__); + ddata->compose.thread_state = COMPOSE_START; + + if (!dev) { + ret = -ENOENT; + pr_err("%s ddata->input null\n", __func__); + goto exit; + } + + if (ddata->compose.num_of_compose_effects < 1) { + pr_err("%s error. ddata->compose.num_of_compose_effects=%d\n", __func__, + ddata->compose.num_of_compose_effects); + goto exit; + } + + while (!ddata->compose.thread_exit) { + gain = ddata->effects[pattern_seq].gain; + effect = &ddata->effects[pattern_seq].compose_effects; + type = ddata->effects[pattern_seq].compose_effects.type; + duration = effect->replay.length; + + switch (type) { + case FF_CONSTANT: + case FF_PERIODIC: + pr_info("%s type %s duration %dms\n", __func__, + (type == FF_CONSTANT) ? "FF_CONSTANT" : "FF_PERIODIC", duration); + + compose_effect_set_gain(dev, gain); + + if (ddata->compose.thread_exit) + break; + + if (compose_effect_upload(dev, effect)) + goto exit; + + if (ddata->compose.thread_exit) + break; + + compose_effect_playback_on(dev, effect); + break; + case VIB_FREE_DURATION: + pr_info("%s delay duration %dms\n", __func__, duration); + break; + default: + pr_err("%s error. type=%d\n", __func__, type); + break; + } + + wait_event_interruptible_timeout(ddata->compose.delay_wait, + ddata->compose.thread_exit, msecs_to_jiffies(duration)); + + if (type == FF_CONSTANT || type == FF_PERIODIC) + compose_effect_playback_off(dev, effect); + + if (ddata->compose.thread_exit) + break; + + pattern_seq++; + + if (pattern_seq >= ddata->compose.num_of_compose_effects) { + if (ddata->compose.compose_repeat) + pattern_seq = 0; + else + break; + } + + if (type == FF_CONSTANT || type == FF_PERIODIC) + compose_effect_erase(dev, effect->id); + } + +exit: + ddata->compose.thread_state = COMPOSE_EXIT; + pr_info("%s exit\n", __func__); + ddata->compose.thread_exit = 1; +} + +static void stop_compose_effects_thread(struct sec_vib_inputff_drvdata *ddata) +{ + pr_info("%s\n", __func__); + + if (!ddata) + return; + + if (ddata->compose.compose_thread) { + if (ddata->compose.thread_state != COMPOSE_STOP) { + ddata->compose.thread_exit = 1; + wake_up_interruptible(&ddata->compose.delay_wait); + } + } +} + +__visible_for_testing bool check_common_inputff_compose_pattern(struct sec_vib_inputff_drvdata *ddata, + struct ff_effect *effect) +{ + if (!ddata || !effect) + return false; + + if (ddata->use_common_inputff) { + if (effect->u.periodic.custom_len > sizeof(int)) + return true; + } + return false; +} + +__visible_for_testing bool uploaded_common_inputff_compose_pattern(struct sec_vib_inputff_drvdata *ddata) +{ + if (!ddata) + return false; + + if (ddata->use_common_inputff) { + if (ddata->compose.upload_compose_effect) + return true; + } + return false; +} + +static int process_common_inputff_compose_upload_effect(struct sec_vib_inputff_drvdata *ddata, + struct ff_effect *effect) +{ + struct common_inputff_effects input_compose_effects; + int ret = 0; + + if (!ddata || !effect) { + ret = -ENOENT; + pr_err("%s ddata or effect null\n", __func__); + goto err; + } + + if (copy_from_user(&input_compose_effects, + effect->u.periodic.custom_data, + sizeof(struct common_inputff_effects))) { + ret = -ENODATA; + goto err; + } + ddata->compose.compose_effect_id = effect->id; + ret = parsing_compose_effects(ddata, &input_compose_effects); + if (ret) + goto err; + ddata->compose.upload_compose_effect = 1; +err: + return ret; +} + +static void process_common_inputff_compose_erase(struct sec_vib_inputff_drvdata *ddata) +{ + if (!ddata) + return; + + stop_compose_effects_thread(ddata); + kthread_flush_work(&ddata->compose.kwork); + ddata->compose.thread_state = COMPOSE_STOP; + ddata->compose.upload_compose_effect = 0; + ddata->compose.num_of_compose_effects = 0; + ddata->compose.compose_repeat = 0; + ddata->compose.compose_effect_id = -1; +} + +static void process_common_inputff_compose_playback(struct sec_vib_inputff_drvdata *ddata, + int val) +{ + if (!ddata) + return; + + if (val) { + ddata->compose.thread_exit = 0; + kthread_queue_work(&ddata->compose.kworker, &ddata->compose.kwork); + ddata->compose.thread_state = COMPOSE_RUN; + } else + stop_compose_effects_thread(ddata); +} + +static int sec_vib_inputff_upload_effect(struct input_dev *dev, + struct ff_effect *effect, + struct ff_effect *old) +{ + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + int ret = 0; + + if (!ddata) { + ret = -ENOENT; + pr_err("%s ddata null\n", __func__); + goto err; + } + + if (check_common_inputff_compose_pattern(ddata, effect)) { + ret = process_common_inputff_compose_upload_effect(ddata, effect); + } else { + if (ddata->vib_ops->upload) + ret = ddata->vib_ops->upload(dev, effect, old); + } +err: + return ret; +} + +static int sec_vib_inputff_erase(struct input_dev *dev, int effect_id) +{ + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + int ret = 0; + + if (!ddata) { + ret = -ENOENT; + pr_err("%s ddata null\n", __func__); + goto exit; + } + + if (uploaded_common_inputff_compose_pattern(ddata)) { + process_common_inputff_compose_erase(ddata); + if (ddata->compose.upload_partial_effect == 0) + goto exit; + } + + if (ddata->vib_ops->erase) + ret = ddata->vib_ops->erase(dev, effect_id); + +exit: + return ret; +} + +static int sec_vib_inputff_playback(struct input_dev *dev, int effect_id, + int val) +{ + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + int ret = 0; + + if (!ddata) { + ret = -ENOENT; + pr_err("%s ddata null\n", __func__); + goto err; + } + + if (uploaded_common_inputff_compose_pattern(ddata)) { + process_common_inputff_compose_playback(ddata, val); + } else { + if (ddata->vib_ops->playback) + ret = ddata->vib_ops->playback(dev, effect_id, val); + } +err: + return ret; +} + +static void sec_vib_inputff_set_gain(struct input_dev *dev, u16 gain) +{ + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + + if (!ddata) { + pr_err("%s ddata null\n", __func__); + goto err; + } + + ddata->effect_gain = gain; + if (ddata->vib_ops->set_gain) + ddata->vib_ops->set_gain(dev, gain); +err: + return; +} + +int sec_vib_inputff_register(struct sec_vib_inputff_drvdata *ddata) +{ + int i, ret = 0, ff = 0, ff_cnt = 0; + struct device *dev = NULL; + + if (!ddata) { + ret = -ENODEV; + pr_err("%s no ddata\n", __func__); + goto err_nodev; + } + dev = ddata->dev; + + pr_info("%s +++\n", __func__); + + ddata->input = devm_input_allocate_device(dev); + if (!ddata->input) { + ret = -ENOMEM; + dev_err(dev, "%s no memory\n", __func__); + goto err_nomem; + } + + ddata->input->name = "sec_vibrator_inputff"; + ddata->input->id.product = ddata->devid; + ddata->input->id.version = ddata->revid; + + input_set_drvdata(ddata->input, ddata); + + init_waitqueue_head(&ddata->compose.delay_wait); + kthread_init_worker(&ddata->compose.kworker); + kthread_init_work(&ddata->compose.kwork, compose_effects_play_work); + + ddata->compose.compose_thread = kthread_run(kthread_worker_fn, + &ddata->compose.kworker, "compose-effects"); + if (IS_ERR(ddata->compose.compose_thread)) { + pr_err("%s Unable to start compose effects thread\n", __func__); + ret = PTR_ERR(ddata->compose.compose_thread); + goto err_nomem; + } + + for (ff = FF_EFFECT_MIN; ff <= FF_MAX_EFFECTS; ff++) { + if (sec_vib_inputff_getbit(ddata, ff)) { + input_set_capability(ddata->input, EV_FF, ff); + ff_cnt++; + } + } + + ret = input_ff_create(ddata->input, ff_cnt); + if (ret) { + pr_err("Failed to create FF device: %d\n", ret); + goto err_input_ff; + } + + /* + * input_ff_create() automatically sets FF_RUMBLE capabilities; + * we want to restrtict this to only FF_PERIODIC + */ + __clear_bit(FF_RUMBLE, ddata->input->ffbit); + + ddata->input->ff->upload = sec_vib_inputff_upload_effect; + ddata->input->ff->erase = sec_vib_inputff_erase; + ddata->input->ff->playback = sec_vib_inputff_playback; + ddata->input->ff->set_gain = sec_vib_inputff_set_gain; + + ret = input_register_device(ddata->input); + if (ret) { + pr_err("Cannot register input device: %d\n", ret); + goto err_input_reg; + } + + for (i = 0; ddata->vendor_dev_attr_groups[i]; i++) { + ret = sysfs_create_group(&ddata->input->dev.kobj, + ddata->vendor_dev_attr_groups[i]); + if (ret) { + pr_err("Failed to create sysfs groups[%d] : ret - %d\n", i, ret); + goto err_sysfs; + } + } + ret = sec_vib_inputff_sysfs_init(ddata); + if (ret) { + pr_err("Failed to create inputff sysfs group: %d\n", ret); + goto err_sysfs; + } + + ddata->pdata = &sec_vib_inputff_pdata; + +#if defined(CONFIG_SEC_VIB_FOLD_MODEL) + sec_vib_inputff_event_cmd(ddata); +#endif + + ddata->vibe_init_success = true; + + pr_info("%s ---\n", __func__); + return ret; +err_sysfs: + if (ddata->input) + input_unregister_device(ddata->input); +err_input_reg: +err_input_ff: + kthread_stop(ddata->compose.compose_thread); +err_nomem: +err_nodev: + return ret; +} +EXPORT_SYMBOL_GPL(sec_vib_inputff_register); + +void sec_vib_inputff_unregister(struct sec_vib_inputff_drvdata *ddata) +{ + struct device *dev = NULL; + + if (!ddata) { + pr_err("%s no ddata\n", __func__); + goto fail; + } + dev = ddata->dev; + + pr_info("%s +++\n", __func__); + + if (ddata->vibe_init_success) { + int i; + sec_vib_inputff_sysfs_exit(ddata); + for (i = 0; ddata->vendor_dev_attr_groups[i]; i++) { + sysfs_remove_group(&ddata->input->dev.kobj, + ddata->vendor_dev_attr_groups[i]); + } + } + if (ddata->input) + input_unregister_device(ddata->input); + + if (ddata->compose.compose_thread) + kthread_stop(ddata->compose.compose_thread); + + ddata->vibe_init_success = false; + +fail: + return; +} +EXPORT_SYMBOL_GPL(sec_vib_inputff_unregister); + +/* + * sec_vib_inputff_recheck_gain() - facilitate drvier IC to finds + * the appropriate gain ratio by checking temperature + * & fold position. + * @ddata - driver data passed from the driver ic. + */ +int sec_vib_inputff_recheck_gain(struct sec_vib_inputff_drvdata *ddata) +{ + int temp = sec_vib_inputff_get_current_temp(ddata); + + if (temp >= ddata->pdata->high_temp_ref) + return ddata->pdata->high_temp_ratio; +#if defined(CONFIG_SEC_VIB_FOLD_MODEL) + else + return set_fold_model_ratio(ddata); +#endif + + return ddata->pdata->normal_ratio; +} + +/* + * sec_vib_inputff_tune_gain() - facilitate drvier IC to finds + * the appropriate gain ratio by checking temperature + * & fold position. + * @ddata - driver data passed from the driver ic. + * @gain - Currently selected gain value + * + * return : Tuned gain value based on temperature, fold state, etc., + */ +int sec_vib_inputff_tune_gain(struct sec_vib_inputff_drvdata *ddata, int gain) +{ + int ratio = sec_vib_inputff_recheck_gain(ddata); + int tuned_gain = (gain * ratio) / 100; + + pr_info("%s: gain: %d, ratio: %d, tunned gain: %d\n", + __func__, gain, ratio, tuned_gain); + + return tuned_gain; +} +EXPORT_SYMBOL_GPL(sec_vib_inputff_tune_gain); + +/* + * sec_vib_inputff_parse_dt - parses hw details from dts + */ +static int sec_vib_inputff_parse_dt(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + int ret = 0; + int temp; + + pr_info("%s +++\n", __func__); + + if (unlikely(!np)) { + pr_err("err: could not parse dt node\n"); + return -ENODEV; + } + + ret = of_property_read_u32(np, "haptic,normal_ratio", &temp); + if (ret) { + pr_err("%s: WARNING! normal_ratio not found\n", __func__); + sec_vib_inputff_pdata.normal_ratio = 100; // 100% + } else + sec_vib_inputff_pdata.normal_ratio = (int)temp; + + ret = of_property_read_u32(np, "haptic,overdrive_ratio", &temp); + if (ret) { + pr_info("%s: overdrive_ratio isn't used\n", __func__); + sec_vib_inputff_pdata.overdrive_ratio = sec_vib_inputff_pdata.normal_ratio; + } else + sec_vib_inputff_pdata.overdrive_ratio = (int)temp; + + ret = of_property_read_u32(np, "haptic,high_temp_ref", &temp); + if (ret) { + pr_info("%s: high temperature concept isn't used\n", __func__); + sec_vib_inputff_pdata.high_temp_ref = SEC_VIBRATOR_INPUTFF_DEFAULT_HIGH_TEMP_REF; + } else { + sec_vib_inputff_pdata.high_temp_ref = (int)temp; + + ret = of_property_read_u32(np, "haptic,high_temp_ratio", &temp); + if (ret) { + pr_info("%s: high_temp_ratio isn't used\n", __func__); + sec_vib_inputff_pdata.high_temp_ratio = SEC_VIBRATOR_INPUTFF_DEFAULT_HIGH_TEMP_RATIO; + } + else + sec_vib_inputff_pdata.high_temp_ratio = (int)temp; + } + +#if defined(CONFIG_SEC_VIB_FOLD_MODEL) + ret = of_property_read_string(np, "haptic,fold_string", + (const char **)&sec_vib_inputff_pdata.fold_cmd); + if (ret < 0) + pr_err("%s: WARNING! fold_string not found\n", __func__); + + ret = of_property_read_u32(np, + "haptic,tent_open_ratio", &temp); + if (ret) { + pr_err("%s: WARNING! tent_open_ratio not found\n", __func__); + sec_vib_inputff_pdata.tent_open_ratio = sec_vib_inputff_pdata.normal_ratio; + } else + sec_vib_inputff_pdata.tent_open_ratio = (int)temp; + + ret = of_property_read_u32(np, + "haptic,tent_close_ratio", &temp); + if (ret) { + pr_err("%s: WARNING! tent_close_ratio not found\n", __func__); + sec_vib_inputff_pdata.tent_close_ratio = sec_vib_inputff_pdata.normal_ratio; + } else + sec_vib_inputff_pdata.tent_close_ratio = (int)temp; + + ret = of_property_read_u32(np, "haptic,fold_open_ratio", &temp); + if (ret) { + pr_err("%s: WARNING! fold_open_ratio not found\n", __func__); + sec_vib_inputff_pdata.fold_open_ratio = sec_vib_inputff_pdata.normal_ratio; + } else + sec_vib_inputff_pdata.fold_open_ratio = (int)temp; + + ret = of_property_read_u32(np, + "haptic,fold_close_ratio", &temp); + if (ret) { + pr_err("%s: WARNING! fold_close_ratio not found\n", __func__); + sec_vib_inputff_pdata.fold_close_ratio = sec_vib_inputff_pdata.normal_ratio; + } else + sec_vib_inputff_pdata.fold_close_ratio = (int)temp; +#endif + + ret = of_property_read_string(np, "haptic,f0_cal_way", + (const char **)&sec_vib_inputff_pdata.f0_cal_way); + if (ret < 0) { + sec_vib_inputff_pdata.f0_cal_way = "NONE"; + pr_err("%s: WARNING! F0 Calibration Method not found\n", __func__); + } + + pr_info("%s : done! ---\n", __func__); + return 0; +} + +static int sec_vib_inputff_probe(struct platform_device *pdev) +{ + pr_info("%s +++\n", __func__); + + if (unlikely(sec_vib_inputff_parse_dt(pdev))) + pr_err("%s: WARNING!>..parse dt failed\n", __func__); + + sec_vib_inputff_pdata.probe_done = true; + pr_info("%s : done! ---\n", __func__); + + return 0; +} + + +static const struct of_device_id sec_vib_inputff_id[] = { + { .compatible = "sec_vib_inputff" }, + { } +}; + +static struct platform_driver sec_vib_inputff_driver = { + .probe = sec_vib_inputff_probe, + .driver = { + .name = "sec_vib_inputff", + .owner = THIS_MODULE, + .of_match_table = sec_vib_inputff_id, + }, +}; + +module_platform_driver(sec_vib_inputff_driver); + +MODULE_AUTHOR("Samsung electronics , Samsung vibrator team members"); +MODULE_DESCRIPTION("sec_vibrator_inputff"); +MODULE_LICENSE("GPL"); diff --git a/drivers/vibrator/common/inputff/sec_vibrator_inputff_sysfs.c b/drivers/vibrator/common/inputff/sec_vibrator_inputff_sysfs.c new file mode 100644 index 000000000000..5da708c4c273 --- /dev/null +++ b/drivers/vibrator/common/inputff/sec_vibrator_inputff_sysfs.c @@ -0,0 +1,995 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 Samsung Electronics Co. Ltd. + * + * 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 +#ifdef CONFIG_USB_NOTIFY_PROC_LOG +#include +#endif +#if defined(CONFIG_SEC_KUNIT) +#include +#else +#define __visible_for_testing static +#endif +#if IS_ENABLED(CONFIG_SEC_ABC) +#include +#endif +#if IS_ENABLED(CONFIG_SEC_PARAM) +#include +#endif + +/* Use __VIB_TEST_STORE_LE_PARAM only for testing purpose, + * else there will be GKI violation. + * #define __VIB_TEST_STORE_LE_PARAM + */ + +#if defined(CONFIG_VIB_STORE_LE_PARAM) +static uint32_t vib_le_est; +module_param(vib_le_est, uint, 0444); +MODULE_PARM_DESC(vib_le_est, "sec_vib_inputff_le_est value"); +#endif + +__visible_for_testing char *sec_vib_inputff_get_i2c_test( + struct sec_vib_inputff_drvdata *ddata) +{ + if (ddata->vib_ops->get_i2c_test) { + if (ddata->vib_ops->get_i2c_test(ddata->input)) + return "PASS"; + else + return "FAIL"; + } else + return "NONE"; +} + +static ssize_t i2c_test_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%s\n", sec_vib_inputff_get_i2c_test(ddata)); +} +static DEVICE_ATTR_RO(i2c_test); + +__visible_for_testing int sec_vib_inputff_get_i2s_test( + struct sec_vib_inputff_drvdata *ddata) +{ + int ret = 0; + + if (ddata->vib_ops->get_i2s_test) + ret = ddata->vib_ops->get_i2s_test(ddata->input); + dev_info(ddata->dev, "%s ret : %d\n", __func__, ret); + return ret; +} + +static ssize_t i2s_test_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", sec_vib_inputff_get_i2s_test(ddata)); +} +static DEVICE_ATTR_RO(i2s_test); + +__visible_for_testing int sec_vib_inputff_load_firmware( + struct sec_vib_inputff_drvdata *ddata, int fw_id) +{ + int ret = 0; + int retry = 0; +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + int event; +#endif + if (!ddata->vib_ops || !ddata->vib_ops->fw_load || !ddata->vib_ops->get_ap_chipset) { + dev_err(ddata->dev, "%s Fail to find pointer\n", __func__); + ret = -ENOENT; + goto err; + } + + dev_info(ddata->dev, "%s ret(%d), fw_id(%d) retry(%d) stat(%d)\n", + __func__, ret, ddata->fw.id, ddata->fw.retry, ddata->fw.stat); + mutex_lock(&ddata->fw.stat_lock); + + retry = ddata->fw.retry; + __pm_stay_awake(&ddata->fw.ws); + ret = ddata->vib_ops->fw_load(ddata->input, ddata->fw.id); + __pm_relax(&ddata->fw.ws); + if (!ret) { +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + event = NOTIFY_EXTRA_VIB_FW_LOAD_SUCCESS; + store_usblog_notify(NOTIFY_EXTRA, (void *)&event, NULL); +#endif + ddata->fw.ret[retry] = FW_LOAD_SUCCESS; + ddata->fw.stat = FW_LOAD_SUCCESS; + if (!ddata->fw_init_attempted) + ddata->fw_init_attempted = true; + if (ddata->f0_stored && ddata->fw.id == 0) + ddata->vib_ops->set_f0_stored(ddata->input, ddata->f0_stored); + } else { + if (retry < FWLOAD_TRY - 1) { + ddata->fw.ret[retry] = ret; + queue_delayed_work(ddata->fw.fw_workqueue, + &ddata->fw.retry_wk, msecs_to_jiffies(1000)); + ddata->fw.retry++; + } else { + dev_err(ddata->dev, "%s firmware load retry fail\n", __func__); + if (!ddata->fw_init_attempted) { + ddata->fw_init_attempted = true; + if ((strcmp(ddata->vib_ops->get_ap_chipset(ddata->input), "slsi") == 0)) + goto err; + } +#if IS_ENABLED(CONFIG_SEC_ABC) + sec_abc_send_event("MODULE=vib@WARN=fw_load_fail"); +#endif + } + } +err: + mutex_unlock(&ddata->fw.stat_lock); + dev_info(ddata->dev, "%s done\n", __func__); + return ret; +} + +static void sec_vib_inputff_load_firmware_work(struct work_struct *work) +{ + struct sec_vib_inputff_fwdata *fdata + = container_of(work, struct sec_vib_inputff_fwdata, wk); + struct sec_vib_inputff_drvdata *ddata + = container_of(fdata, struct sec_vib_inputff_drvdata, fw); + int ret = 0; + + dev_info(ddata->dev, "%s\n", __func__); + ret = sec_vib_inputff_load_firmware(ddata, fdata->id); + if (ret < 0) + dev_err(ddata->dev, "%s fail(%d)\n", __func__, ret); +} + +static void sec_vib_inputff_load_firmware_retry(struct work_struct *work) +{ + struct sec_vib_inputff_fwdata *fdata + = container_of(to_delayed_work(work), struct sec_vib_inputff_fwdata, retry_wk); + struct sec_vib_inputff_drvdata *ddata + = container_of(fdata, struct sec_vib_inputff_drvdata, fw); + int ret = 0; + + dev_info(ddata->dev, "%s\n", __func__); + ret = sec_vib_inputff_load_firmware(ddata, fdata->id); + if (ret < 0) + dev_err(ddata->dev, "%s fail(%d)\n", __func__, ret); +} + +static void firmware_load_store_work(struct work_struct *work) +{ + struct sec_vib_inputff_fwdata *fdata + = container_of(to_delayed_work(work), struct sec_vib_inputff_fwdata, store_wk); + struct sec_vib_inputff_drvdata *ddata + = container_of(fdata, struct sec_vib_inputff_drvdata, fw); + unsigned int i; + + dev_info(ddata->dev, "%s stat(%d) retry(%d)\n", __func__, + ddata->fw.stat, ddata->fw.retry); + + cancel_work_sync(&ddata->fw.wk); + cancel_delayed_work_sync(&ddata->fw.retry_wk); + dev_info(ddata->dev, "%s clear retry\n", __func__); + ddata->fw.retry = 0; + for (i = 0; i < FWLOAD_TRY; i++) + ddata->fw.ret[i] = FW_LOAD_STORE; + ddata->fw.stat = FW_LOAD_STORE; + + queue_work(ddata->fw.fw_workqueue, &ddata->fw.wk); +} + +static void sec_vib_inputff_start_cal_work(struct work_struct *work) +{ + struct sec_vib_inputff_drvdata *ddata + = container_of(work, struct sec_vib_inputff_drvdata, cal_work); + int ret; + + dev_info(ddata->dev, "%s start\n", __func__); + + ret = ddata->vib_ops->set_trigger_cal(ddata->input, ddata->trigger_calibration); + if (ret) { + dev_err(ddata->dev, "%s set_trigger_cal error : %d\n", __func__, ret); + ddata->trigger_calibration = 0; + } +} + +static ssize_t firmware_load_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + bool fw_load = false; + + if (ddata->fw.stat == FW_LOAD_SUCCESS) + fw_load = true; + + return sprintf(buf, "%d\n", fw_load); +} + +static ssize_t firmware_load_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + u32 fw_id = 0; + int ret = 0; + + if (!ddata->vib_ops->fw_load) { + dev_info(ddata->dev, "%s fw_upload not registered.\n", __func__); + + // Mark Success for upload check + ddata->fw.stat = FW_LOAD_SUCCESS; + + return count; + } + + ret = kstrtou32(buf, 0, &fw_id); + if (ret < 0) { + dev_err(ddata->dev, "%s kstrtou32 error : %d\n", __func__, ret); + goto err; + } + dev_info(ddata->dev, "%s id(%d) stat(%d) retry(%d)\n", __func__, + fw_id, ddata->fw.stat, ddata->fw.retry); + ddata->fw.id = fw_id; + schedule_delayed_work(&ddata->fw.store_wk, msecs_to_jiffies(0)); + dev_info(ddata->dev, "%s done\n", __func__); +err: + return ret ? ret : count; +} +static DEVICE_ATTR_RW(firmware_load); + +int sec_vib_inputff_get_current_temp(struct sec_vib_inputff_drvdata *ddata) +{ + dev_info(ddata->dev, "%s temperature : %d\n", __func__, ddata->temperature); + + return ddata->temperature; +} +EXPORT_SYMBOL_GPL(sec_vib_inputff_get_current_temp); + +static ssize_t current_temp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", ddata->temperature); +} + +static ssize_t current_temp_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + int temp, ret = 0; + + ret = kstrtos32(buf, 0, &temp); + if (ret < 0) { + dev_err(ddata->dev, "%s kstrtos32 error : %d\n", __func__, ret); + ddata->temperature = INT_MIN; + goto err; + } + + ddata->temperature = temp; + dev_info(ddata->dev, "%s temperature : %d\n", __func__, ddata->temperature); +err: + return ret ? ret : count; +} +static DEVICE_ATTR_RW(current_temp); + +int sec_vib_inputff_get_ach_percent(struct sec_vib_inputff_drvdata *ddata) +{ + dev_info(ddata->dev, "%s ach_percent : %d\n", __func__, ddata->ach_percent); + + return ddata->ach_percent; +} +EXPORT_SYMBOL_GPL(sec_vib_inputff_get_ach_percent); + +static ssize_t ach_percent_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", ddata->ach_percent); +} + +static ssize_t ach_percent_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + int percent, ret = 0; + + ret = kstrtos32(buf, 0, &percent); + if (ret < 0) { + dev_err(ddata->dev, "%s kstrtos32 error : %d\n", __func__, ret); + ddata->ach_percent = 100; + goto err; + } + + if (percent >= 0 && percent <= 100) + ddata->ach_percent = percent; + else + ddata->ach_percent = 100; + dev_info(ddata->dev, "%s ach_percent : %d\n", __func__, ddata->ach_percent); +err: + return ret ? ret : count; +} +static DEVICE_ATTR_RW(ach_percent); + +static ssize_t cal_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + + if (ddata->is_ls_calibration) + return snprintf(buf, PAGE_SIZE, "ls_cal\n"); + else if (ddata->is_f0_tracking) + return snprintf(buf, PAGE_SIZE, "f0_cal\n"); + else + return snprintf(buf, PAGE_SIZE, "NONE\n"); +} +static DEVICE_ATTR_RO(cal_type); + +static ssize_t ls_calib_temp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + + u32 param_temp; + int ret; + + if (!ddata->vib_ops->get_ls_temp || !ddata->is_ls_calibration) { + dev_err(ddata->dev, "%s get_ls_temp doesn't support\n", __func__); + return -EOPNOTSUPP; + } + + ret = ddata->vib_ops->get_ls_temp(ddata->input, ¶m_temp); + + return snprintf(buf, PAGE_SIZE, "0x%06X\n", param_temp); +} + +static ssize_t ls_calib_temp_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + u32 param_temp; + int ret; + + ret = kstrtos32(buf, 16, ¶m_temp); + if (ret < 0) { + dev_err(ddata->dev, "%s kstrtos32 error : %d\n", __func__, ret); + return ret; + } + + if (ddata->vib_ops->set_ls_temp && ddata->is_ls_calibration) { + ret = ddata->vib_ops->set_ls_temp(ddata->input, param_temp); + if (ret) { + dev_err(ddata->dev, "%s set_ls_temp error : %d\n", __func__, ret); + return ret; + } + dev_info(ddata->dev, "%s set_ls_temp value : %d\n", __func__, param_temp); + } else { + dev_err(ddata->dev, "%s set_ls_temp doesn't support\n", __func__); + return -EOPNOTSUPP; + } + return count; +} +static DEVICE_ATTR_RW(ls_calib_temp); + + +static ssize_t trigger_calibration_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", ddata->trigger_calibration); +} + +static ssize_t trigger_calibration_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + int ret; + + cancel_work_sync(&ddata->cal_work); + + ret = kstrtos32(buf, 16, &ddata->trigger_calibration); + if (ret < 0) { + dev_err(ddata->dev, "%s kstrtos32 error : %d\n", __func__, ret); + return ret; + } + if (ddata->vib_ops->set_trigger_cal && (ddata->is_f0_tracking || ddata->is_ls_calibration)) { + queue_work(ddata->cal_workqueue, &ddata->cal_work); + dev_info(ddata->dev, "%s trigger_calibration : %u\n", __func__, ddata->trigger_calibration); + } else { + dev_err(ddata->dev, "%s set_trigger_cal doesn't support\n", __func__); + return -EOPNOTSUPP; + } + return count; +} +static DEVICE_ATTR_RW(trigger_calibration); + +static ssize_t ls_calibration_results_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + int ret; + + if (!ddata->vib_ops->get_ls_calib_res_name || !ddata->is_ls_calibration) { + dev_err(ddata->dev, "%s get_ls_calib_res doesn't support\n", __func__); + return -EOPNOTSUPP; + } + + ret = ddata->vib_ops->get_ls_calib_res_name(ddata->input, buf); + + return ret; +} +static DEVICE_ATTR_RO(ls_calibration_results_name); + +static ssize_t ls_calibration_results_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + int ret; + + if (!ddata->vib_ops->get_ls_calib_res || !ddata->is_ls_calibration) { + dev_err(ddata->dev, "%s get_ls_calib_res doesn't support\n", __func__); + return -EOPNOTSUPP; + } + + ret = ddata->vib_ops->get_ls_calib_res(ddata->input, buf); + + return ret; +} + +static ssize_t ls_calibration_results_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + int ret; + + char *str_full; + + str_full = kstrdup(buf, GFP_KERNEL); + if (!str_full) { + dev_err(ddata->dev, "%s kstrdup error NULL\n", __func__); + return -ENOMEM; + } + + if (ddata->vib_ops->set_ls_calib_res && ddata->is_ls_calibration) { + ret = ddata->vib_ops->set_ls_calib_res(ddata->input, str_full); + if (ret) { + dev_err(ddata->dev, "%s set_ls_calib_res error : %d\n", __func__, ret); + kfree(str_full); + return ret; + } + dev_info(ddata->dev, "%s ls_calib_res ret : %d\n", __func__, ret); + } else { + dev_err(ddata->dev, "%s set_ls_calib_res doesn't support\n", __func__); + kfree(str_full); + return -EOPNOTSUPP; + } + kfree(str_full); + return count; +} +static DEVICE_ATTR_RW(ls_calibration_results); + + +static ssize_t f0_measured_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + + if (!ddata->vib_ops->get_f0_measured || !ddata->is_f0_tracking) { + dev_err(ddata->dev, "%s get_f0_measured doesn't support\n", __func__); + return -EOPNOTSUPP; + } + + return snprintf(buf, PAGE_SIZE, "%08X\n", ddata->vib_ops->get_f0_measured(ddata->input)); +} +static DEVICE_ATTR_RO(f0_measured); + +static ssize_t f0_offset_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + int ret; + u32 f0_offset; + + ret = kstrtos32(buf, 16, &f0_offset); + if (ret < 0) { + dev_err(ddata->dev, "%s kstrtos32 error : %d\n", __func__, ret); + return ret; + } + if (ddata->vib_ops->set_f0_offset && ddata->is_f0_tracking) { + ret = ddata->vib_ops->set_f0_offset(ddata->input, f0_offset); + if (ret) { + dev_err(ddata->dev, "%s set_f0_offset error : %d\n", __func__, ret); + return -EINVAL; + } + dev_info(ddata->dev, "%s f0_offset : %u, ret : %d\n", __func__, f0_offset, ret); + } else { + dev_err(ddata->dev, "%s set_f0_offset doesn't support\n", __func__); + return -EOPNOTSUPP; + } + return count; +} + +static ssize_t f0_offset_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + + if (!ddata->vib_ops->get_f0_offset || !ddata->is_f0_tracking) { + dev_err(ddata->dev, "%s get_f0_offset doesn't support\n", __func__); + return -EOPNOTSUPP; + } + + return snprintf(buf, PAGE_SIZE, "%08X\n", ddata->vib_ops->get_f0_offset(ddata->input)); +} +static DEVICE_ATTR_RW(f0_offset); + +static ssize_t f0_stored_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + + if (!ddata->vib_ops->set_f0_stored || !ddata->is_f0_tracking) { + dev_err(ddata->dev, "%s f0_stored read doesn't support\n", __func__); + return -EOPNOTSUPP; + } + + return sprintf(buf, "%08X\n", ddata->vib_ops->get_f0_stored ? + ddata->vib_ops->get_f0_stored(ddata->input) : ddata->f0_stored); +} + +/* + * f0_stored_store - loads the f0 calibrated data to the driver. + * Its stored in efs and the f0 calibration data to be loaded + * on every boot. + * sequence: (Pre-requisite) + * 1) Load Calib firmware. + * 2) Trigger Calibration + * 3) Measure f0 value + * 4) Store the f0 value to efs area. + * 5) Load Normal firmware + * Upon every boot, HAL reads from efs and loads to driver via this function. + * Future Check : If any new IC, will have EEPROM, that may needs to be + * preferred over efs area? + */ +static ssize_t f0_stored_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + int ret; + u32 f0_stored; + + ret = kstrtos32(buf, 16, &f0_stored); + if (ret < 0) { + dev_err(ddata->dev, "%s kstrtos32 error : %d\n", __func__, ret); + return ret; + } + + if (ddata->vib_ops->set_f0_stored && ddata->is_f0_tracking) { + if (ddata->fw.stat == FW_LOAD_SUCCESS && ddata->fw.id == 0) { + ret = ddata->vib_ops->set_f0_stored(ddata->input, f0_stored); + if (ret) { + dev_err(ddata->dev, "%s set_f0_stored error : %d\n", __func__, ret); + return -EINVAL; + } + } + ddata->f0_stored = f0_stored; + dev_info(ddata->dev, "%s f0_stored : %u, ret : %d\n", __func__, f0_stored, ret); + } else { + dev_err(ddata->dev, "%s set_f0_stored doesn't support\n", __func__); + return -EOPNOTSUPP; + } + return count; +} +static DEVICE_ATTR_RW(f0_stored); + +static ssize_t le_support_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", ddata->is_le_support); +} +static DEVICE_ATTR_RO(le_support); + +static ssize_t le_est_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + unsigned int le = 0; + int ret = 0; + + if (!ddata->vib_ops->get_le_est) { + dev_err(ddata->dev, "%s le_est doesn't support\n", __func__); + return -EOPNOTSUPP; + } + + ret = ddata->vib_ops->get_le_est(ddata->input, &le); + if (ret) + return ret; + + return snprintf(buf, PAGE_SIZE, "%u\n", le); +} +static DEVICE_ATTR_RO(le_est); + +static ssize_t le_stored_param_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + + if (!ddata->vib_ops->get_le_stored) { + dev_err(ddata->dev, "%s le_stored doesn't support\n", __func__); + return -EOPNOTSUPP; + } + + return snprintf(buf, PAGE_SIZE, "%u\n", ddata->vib_ops->get_le_stored(ddata->input)); +} + +/* + * le_stored_param_store - Stores the lra estimated data to SEC param. + * sequence: + * 1) Load Calib firmware. + * 2) LRA Estimate + * 3) Call this function to store the LRA. + * 4) Load Normal firmware (To get the value to be effective). + * Future Check : If any new IC, will have EEPROM, that may needs to be + * preferred over SEC_PARAM? + */ +static ssize_t le_stored_param_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + int ret; + u32 le_stored; + + ret = kstrtos32(buf, 10, &le_stored); + if (ret < 0) { + dev_err(ddata->dev, "%s kstrtos32 error : %d\n", __func__, ret); + return ret; + } + + pr_info("%s le_stored: %d\n", __func__, le_stored); + + if (ddata->vib_ops->set_le_stored) { + if (ddata->fw.stat == FW_LOAD_SUCCESS && ddata->fw.id == 1) { + ret = ddata->vib_ops->set_le_stored(ddata->input, le_stored); + if (ret) { + dev_err(ddata->dev, "%s le_stored error : %d\n", __func__, ret); + return -EINVAL; + } + } + ddata->le_stored = le_stored; + dev_info(ddata->dev, "%s le_stored : %u, Updated\n", __func__, le_stored); +#if defined(__VIB_TEST_STORE_LE_PARAM) + dev_err(ddata->dev, "%s Dont store sec_param from driver. Use only for testing\n", __func__); +#if IS_ENABLED(CONFIG_SEC_PARAM) +#if IS_ENABLED(CONFIG_ARCH_QCOM) && !defined(CONFIG_USB_ARCH_EXYNOS) && !defined(CONFIG_ARCH_EXYNOS) + ret = sec_set_param(param_vib_le_est, &le_stored); + if (ret == false) { + dev_info(ddata->dev, "%s:set_paramset_param failed - %x(%d)\n", __func__, le_stored, ret); + + return -EIO; + } + dev_info(ddata->dev, "%s Store in sec param - Done\n", __func__); +#endif +#else + /* Need to add for SLSI */ + dev_err(ddata->dev, "%s le_stored not stored in sec_param\n", __func__); +#endif +#endif // __VIB_TEST_STORE_LE_PARAM + } else { + dev_err(ddata->dev, "%s le_stored doesn't support\n", __func__); + return -EOPNOTSUPP; + } + return count; +} +static DEVICE_ATTR_RW(le_stored_param); + +static ssize_t use_sep_index_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + int ret = 0; + bool use_sep_index; + + ret = kstrtobool(buf, &use_sep_index); + if (ret < 0) { + dev_err(ddata->dev, "%s kstrtobool error : %d\n", __func__, ret); + goto err; + } + + dev_info(ddata->dev, "%s use_sep_index:%d\n", __func__, use_sep_index); + + if (ddata->vib_ops->set_use_sep_index) { + ret = ddata->vib_ops->set_use_sep_index(ddata->input, use_sep_index); + if (ret) { + dev_err(ddata->dev, "%s set_use_sep_index error : %d\n", __func__, ret); + goto err; + } + } else { + dev_info(ddata->dev, "%s this model doesn't need use_sep_index\n", __func__); + } +err: + return ret ? ret : count; +} +static DEVICE_ATTR_WO(use_sep_index); + +static ssize_t lra_resistance_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + + if (!ddata->vib_ops->get_lra_resistance) { + dev_err(ddata->dev, "%s lra_resistance isn't supported\n", __func__); + return -EOPNOTSUPP; + } + + return snprintf(buf, PAGE_SIZE, "%u\n", ddata->vib_ops->get_lra_resistance(ddata->input)); +} +static DEVICE_ATTR_RO(lra_resistance); + +static ssize_t f0_cal_way_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%s\n", ddata->pdata->f0_cal_way); +} +static DEVICE_ATTR_RO(f0_cal_way); + +#if defined(CONFIG_SEC_VIB_FOLD_MODEL) +static const char sec_vib_event_cmd[EVENT_CMD_MAX][MAX_STR_LEN_EVENT_CMD] = { + [EVENT_CMD_NONE] = "NONE", + [EVENT_CMD_FOLDER_CLOSE] = "FOLDER_CLOSE", + [EVENT_CMD_FOLDER_OPEN] = "FOLDER_OPEN", + [EVENT_CMD_ACCESSIBILITY_BOOST_ON] = "ACCESSIBILITY_BOOST_ON", + [EVENT_CMD_ACCESSIBILITY_BOOST_OFF] = "ACCESSIBILITY_BOOST_OFF", + [EVENT_CMD_TENT_CLOSE] = "FOLDER_TENT_CLOSE", + [EVENT_CMD_TENT_OPEN] = "FOLDER_TENT_OPEN", +}; + +/* get_event_index_by_command() - internal function to find event command + * index from event command string + */ +static int get_event_index_by_command(char *cur_cmd) +{ + int cmd_idx; + + for (cmd_idx = 0; cmd_idx < EVENT_CMD_MAX; cmd_idx++) { + if (!strcmp(cur_cmd, sec_vib_event_cmd[cmd_idx])) + return cmd_idx; + } + return EVENT_CMD_NONE; +} + +/* + * event_cmd_store() - sysfs interface to inform vibrator HAL + * about fold states supported (such as FOLD, FOLD|TENT, etc.,). + */ +static ssize_t event_cmd_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + + pr_info("%s event: %s\n", __func__, ddata->event_cmd); + return snprintf(buf, MAX_STR_LEN_EVENT_CMD, "%s\n", ddata->event_cmd); +} + +/* + * event_cmd_store() - sysfs interface to get the fold state from + * user space. + */ +static ssize_t event_cmd_store(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count) +{ + if (count > MAX_STR_LEN_EVENT_CMD) + pr_err("%s: size(%zu) is too long.\n", __func__, count); + else { + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + + if (sscanf(buf, "%s", ddata->event_cmd) == 1) { + ddata->event_idx = get_event_index_by_command((char *)ddata->event_cmd); + + pr_info("%s: size:%zu, event_cmd: %s, %s (idx=%d)\n", + __func__, count, buf, ddata->event_cmd, ddata->event_idx); + } else { + pr_err("%s invalid param : %s\n", __func__, buf); + return -EINVAL; + } + } + + return count; +} +static DEVICE_ATTR_RW(event_cmd); + +/* + * set_fold_model_ratio() - finds the gain ratio based on fold state + * @ddata - driver data passed from the driver ic. + */ +int set_fold_model_ratio(struct sec_vib_inputff_drvdata *ddata) +{ + switch (ddata->event_idx) { + case EVENT_CMD_FOLDER_OPEN: + return ddata->pdata->fold_open_ratio; + case EVENT_CMD_FOLDER_CLOSE: + return ddata->pdata->fold_close_ratio; + case EVENT_CMD_TENT_CLOSE: + return ddata->pdata->tent_close_ratio; + case EVENT_CMD_TENT_OPEN: + return ddata->pdata->tent_open_ratio; + case EVENT_CMD_ACCESSIBILITY_BOOST_ON: + case EVENT_CMD_ACCESSIBILITY_BOOST_OFF: + case EVENT_CMD_NONE: + default: + return ddata->pdata->normal_ratio; + } + + return ddata->pdata->normal_ratio; +} +/* + * sec_vib_inputff_event_cmd() - initialize event_cmd string for HAL init + * from dt + * @ddata - driver data passed from the driver ic. + */ +void sec_vib_inputff_event_cmd(struct sec_vib_inputff_drvdata *ddata) +{ + if (sscanf(ddata->pdata->fold_cmd, "%s", ddata->event_cmd) == 1) + pr_info("%s: fold_cmd: %s, vib_event_cmd: %s\n", __func__, + ddata->pdata->fold_cmd, ddata->event_cmd); + else + pr_err("%s : fold cmd error: Please check dt entry\n", __func__); +} +#endif //CONFIG_SEC_VIB_FOLD_MODEL + +static ssize_t owt_lib_compat_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sec_vib_inputff_drvdata *ddata = dev_get_drvdata(dev); + + if (!ddata->vib_ops->get_owt_lib_compat_version) { + dev_err(ddata->dev, "%s get_owt_lib_compat_version isn't supported\n", __func__); + return -EOPNOTSUPP; + } + return snprintf(buf, PAGE_SIZE, "%s\n", ddata->vib_ops->get_owt_lib_compat_version(ddata->input)); +} +static DEVICE_ATTR_RO(owt_lib_compat); + +static struct attribute *vib_inputff_sys_attr[] = { + &dev_attr_i2c_test.attr, + &dev_attr_i2s_test.attr, + &dev_attr_firmware_load.attr, + &dev_attr_current_temp.attr, + &dev_attr_ach_percent.attr, + &dev_attr_cal_type.attr, + &dev_attr_ls_calib_temp.attr, + &dev_attr_trigger_calibration.attr, + &dev_attr_ls_calibration_results_name.attr, + &dev_attr_ls_calibration_results.attr, + &dev_attr_f0_measured.attr, + &dev_attr_f0_offset.attr, + &dev_attr_f0_stored.attr, + &dev_attr_le_support.attr, + &dev_attr_le_est.attr, + &dev_attr_le_stored_param.attr, + &dev_attr_use_sep_index.attr, + &dev_attr_lra_resistance.attr, +#if defined(CONFIG_SEC_VIB_FOLD_MODEL) + &dev_attr_event_cmd.attr, +#endif + &dev_attr_owt_lib_compat.attr, + &dev_attr_f0_cal_way.attr, + NULL, +}; + +static struct attribute_group vib_inputff_sys_attr_group = { + .attrs = vib_inputff_sys_attr, +}; + + +static void sec_vib_inputff_vib_param_init(struct sec_vib_inputff_drvdata *ddata) +{ +#if defined(CONFIG_VIB_STORE_LE_PARAM) + ddata->le_stored = vib_le_est; + if (ddata->le_stored) + ddata->vib_ops->set_le_stored(ddata->input, ddata->le_stored); +#endif +} + +static void sec_vib_inputff_fw_init(struct sec_vib_inputff_drvdata *ddata) +{ + unsigned int i; + + mutex_init(&ddata->fw.stat_lock); + for (i = 0; i < FWLOAD_TRY; i++) + ddata->fw.ret[i] = FW_LOAD_INIT; + ddata->fw.stat = FW_LOAD_INIT; + ddata->fw.id = 0; + ddata->fw.ws.name = "sec_vib_inputff"; + wakeup_source_add(&ddata->fw.ws); + ddata->fw.fw_workqueue = alloc_ordered_workqueue("vib_workqueue", + WQ_FREEZABLE | WQ_MEM_RECLAIM); + INIT_WORK(&ddata->fw.wk, sec_vib_inputff_load_firmware_work); + INIT_DELAYED_WORK(&ddata->fw.retry_wk, sec_vib_inputff_load_firmware_retry); + INIT_DELAYED_WORK(&ddata->fw.store_wk, firmware_load_store_work); + ddata->fw_init_attempted = false; + queue_work(ddata->fw.fw_workqueue, &ddata->fw.wk); + dev_info(ddata->dev, "%s done\n", __func__); +} + +int sec_vib_inputff_sysfs_init(struct sec_vib_inputff_drvdata *ddata) +{ + int ret = 0; + + dev_info(ddata->dev, "%s\n", __func__); + + if (ddata->support_fw) { + sec_vib_inputff_vib_param_init(ddata); + sec_vib_inputff_fw_init(ddata); + } else { + ddata->fw.stat = FW_LOAD_SUCCESS; + } + + ddata->cal_workqueue = alloc_ordered_workqueue("calibration_workqueue", WQ_HIGHPRI); + INIT_WORK(&ddata->cal_work, sec_vib_inputff_start_cal_work); + + ddata->sec_vib_inputff_class = class_create(THIS_MODULE, "sec_vib_inputff"); + if (IS_ERR(ddata->sec_vib_inputff_class)) { + ret = PTR_ERR(ddata->sec_vib_inputff_class); + goto err1; + } + ddata->virtual_dev = device_create(ddata->sec_vib_inputff_class, + NULL, MKDEV(0, 0), ddata, "control"); + if (IS_ERR(ddata->virtual_dev)) { + ret = PTR_ERR(ddata->virtual_dev); + goto err2; + } + + ret = sysfs_create_group(&ddata->virtual_dev->kobj, + &vib_inputff_sys_attr_group); + if (ret) { + ret = -ENODEV; + dev_err(ddata->dev, "Failed to create sysfs %d\n", ret); + goto err3; + } + dev_info(ddata->dev, "%s done\n", __func__); + + return 0; +err3: + device_destroy(ddata->sec_vib_inputff_class, MKDEV(0, 0)); +err2: + class_destroy(ddata->sec_vib_inputff_class); +err1: + return ret; +} +EXPORT_SYMBOL_GPL(sec_vib_inputff_sysfs_init); + +void sec_vib_inputff_fw_exit(struct sec_vib_inputff_drvdata *ddata) +{ + cancel_work_sync(&ddata->fw.wk); + cancel_delayed_work_sync(&ddata->fw.retry_wk); + destroy_workqueue(ddata->fw.fw_workqueue); + cancel_delayed_work_sync(&ddata->fw.store_wk); + wakeup_source_remove(&ddata->fw.ws); +} + +void sec_vib_inputff_sysfs_exit(struct sec_vib_inputff_drvdata *ddata) +{ + if (ddata->support_fw) + sec_vib_inputff_fw_exit(ddata); + if (ddata->cal_workqueue) { + cancel_work_sync(&ddata->cal_work); + destroy_workqueue(ddata->cal_workqueue); + } + sysfs_remove_group(&ddata->virtual_dev->kobj, &vib_inputff_sys_attr_group); + device_destroy(ddata->sec_vib_inputff_class, MKDEV(0, 0)); + class_destroy(ddata->sec_vib_inputff_class); +} +EXPORT_SYMBOL_GPL(sec_vib_inputff_sysfs_exit); diff --git a/drivers/vibrator/common/vib_info/Kconfig b/drivers/vibrator/common/vib_info/Kconfig new file mode 100644 index 000000000000..2c9ff7002ad1 --- /dev/null +++ b/drivers/vibrator/common/vib_info/Kconfig @@ -0,0 +1,17 @@ +config VIBRATOR_VIB_INFO + tristate "The module for vibrator information" + default n + help + This module supports sysfs nodes for vibrator information. + supported functions are served for vibrator hal. + vibrator hal also can get intensities table by this module. + We recommend to use this module for all models. + +config VIBRATOR_VIB_INFO_TEST + bool "KUnit test for vibrator_vib_info_test" + depends on SEC_KUNIT + help + This driver is for vibrator_vib_info_test driver + vibrator_vib_info_test kunit test + If you want to add some functions, + Please make the test case. \ No newline at end of file diff --git a/drivers/vibrator/common/vib_info/Makefile b/drivers/vibrator/common/vib_info/Makefile new file mode 100644 index 000000000000..9acde0a70aef --- /dev/null +++ b/drivers/vibrator/common/vib_info/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for vib_info driver +# +subdir-ccflags-y := -Wformat +obj-$(CONFIG_VIBRATOR_VIB_INFO) += vibrator_vib_info.o +ifeq ($(CONFIG_SEC_KUNIT), y) +GCOV_PROFILE_vibrator_vib_info.o := $(CONFIG_SEC_KUNIT) +endif \ No newline at end of file diff --git a/drivers/vibrator/common/vib_info/vibrator_vib_info.c b/drivers/vibrator/common/vib_info/vibrator_vib_info.c new file mode 100644 index 000000000000..6992aa0aef36 --- /dev/null +++ b/drivers/vibrator/common/vib_info/vibrator_vib_info.c @@ -0,0 +1,394 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 Samsung Electronics Co. Ltd. + * + * 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. + */ +#define pr_fmt(fmt) "[VIB] " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(CONFIG_SEC_KUNIT) +#include "kunit_test/vibrator_vib_info_test.h" +#else +#define __visible_for_testing static +#endif +#define MAX_INTENSITY_VALUE 10000 +#define MAX_FUNCTION_NUM 64 + +struct vib_info_platform_data { + char **functions_s; + int functions_count; + u32 *intensities; + u32 *haptic_intensities; + int intensities_steps; + int haptic_intensities_steps; +}; + +struct vib_info_driver_data { + struct class *vib_info_class; + struct device *virtual_dev; + char **functions_s; + int functions_count; + u32 *intensities; + u32 *haptic_intensities; + int intensities_steps; + int haptic_intensities_steps; +}; + +static const char *str_delimiter = ","; +static const char *str_newline = "\n"; + +__visible_for_testing ssize_t array2str(char *buf, u32 *arr_intensity, int size) +{ + ssize_t ret = 0; + int i = 0; + int data_limit = 0; + + data_limit = PAGE_SIZE - sizeof(str_newline); + + if (!arr_intensity) + return -EINVAL; + + for (i = 0; i < size; i++) { + ret += snprintf(buf + ret, data_limit - ret, "%u", + arr_intensity[i]); + if (i < (size - 1)) + ret += snprintf(buf + ret, data_limit - ret, "%s", + str_delimiter); + } + + ret += snprintf(buf + ret, PAGE_SIZE - ret, "%s", str_newline); + + pr_info("%s %s\n", __func__, buf); + + return ret; +} + +__visible_for_testing ssize_t names2str(char *buf, char **names, int size) +{ + ssize_t ret = 0; + int i = 0; + int data_limit = 0; + + data_limit = PAGE_SIZE - sizeof(str_newline); + + if (!names) + return -EINVAL; + + for (i = 0; i < size; i++) { + ret += snprintf(buf + ret, data_limit - ret, "%s", + *(names + i)); + if (i < (size - 1)) + ret += snprintf(buf + ret, data_limit - ret, " "); + } + + ret += snprintf(buf + ret, PAGE_SIZE - ret, "%s", str_newline); + + pr_info("%s %s\n", __func__, buf); + + return ret; +} + +static ssize_t functions_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vib_info_driver_data *ddata = dev_get_drvdata(dev); + ssize_t ret = 0; + + pr_info("%s\n", __func__); + + ret = names2str(buf, ddata->functions_s, + ddata->functions_count); + + return ret; +} + +static ssize_t intensities_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vib_info_driver_data *ddata = dev_get_drvdata(dev); + ssize_t ret = 0; + + pr_info("%s\n", __func__); + + ret = array2str(buf, ddata->intensities, + ddata->intensities_steps); + + return ret; +} + +static ssize_t haptic_intensities_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vib_info_driver_data *ddata = dev_get_drvdata(dev); + ssize_t ret = 0; + + pr_info("%s\n", __func__); + + ret = array2str(buf, ddata->haptic_intensities, + ddata->haptic_intensities_steps); + + return ret; +} + +static DEVICE_ATTR_RO(functions); +static DEVICE_ATTR_RO(intensities); +static DEVICE_ATTR_RO(haptic_intensities); + +static struct attribute *vib_info_attributes[] = { + &dev_attr_functions.attr, + &dev_attr_intensities.attr, + &dev_attr_haptic_intensities.attr, + NULL, +}; + +static struct attribute_group vib_info_attr_group = { + .attrs = vib_info_attributes, +}; + +static struct vib_info_platform_data *vib_info_get_pdata(struct platform_device *pdev) +{ + struct device_node *dn = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct vib_info_platform_data *pdata; + int count = 0; + int ret = 0, i = 0; + + if (!IS_ENABLED(CONFIG_OF) || !pdev->dev.of_node) + return dev_get_platdata(&pdev->dev); + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + goto err; + + count = of_property_count_strings(dn, "functions"); + if (count < 0 || count > MAX_FUNCTION_NUM) + goto err; + + dev_info(dev, "%s functions count %d\n", __func__, count); + + pdata->functions_s = devm_kcalloc(&pdev->dev, count, + sizeof(*(pdata->functions_s)), GFP_KERNEL); + if (!pdata->functions_s) + goto err; + + count = of_property_read_string_array(dn, "functions", + (const char **)pdata->functions_s, count); + if (count < 0) { + dev_err(dev, "%s error.of_property_read_string_array\n", __func__); + goto err; + } + + pdata->functions_count = count; + + for (i = 0; i < count; i++) + dev_info(dev, "%s %s\n", __func__, *(pdata->functions_s+i)); + + pdata->intensities_steps = of_property_count_elems_of_size(dn, + "samsung,intensities", sizeof(u32)); + if (pdata->intensities_steps < 0) { + dev_err(dev, "%s error.intensities.of_property_count_elems_of_size\n", + __func__); + goto err; + } + + dev_info(dev, "%s intensity size %d\n", + __func__, pdata->intensities_steps); + + pdata->intensities = kmalloc_array(pdata->intensities_steps, + sizeof(u32), GFP_KERNEL); + if (!pdata->intensities) + goto err; + + ret = of_property_read_u32_array(dn, "samsung,intensities", + pdata->intensities, pdata->intensities_steps); + if (ret) { + dev_err(dev, "%s error.intensities.of_property_read_u32_array\n", + __func__); + goto err1; + } + + for (i = 0; i < pdata->intensities_steps; i++) { + if (pdata->intensities[i] > MAX_INTENSITY_VALUE) { + dev_err(dev, "%s error. i=%d pdata->intensities=%u\n", + __func__, i, pdata->intensities[i]); + goto err1; + } else { + dev_info(dev, "%s i=%d %u\n", __func__, + i, pdata->intensities[i]); + } + } + + pdata->haptic_intensities_steps = of_property_count_elems_of_size(dn, + "samsung,haptic_intensities", sizeof(u32)); + if (pdata->intensities_steps < 0) { + dev_err(dev, "%s error.haptic_intensities.of_property_count_elems_of_size\n", + __func__); + goto err1; + } + + dev_info(dev, "%s haptic_intensity size %d\n", + __func__, pdata->haptic_intensities_steps); + + pdata->haptic_intensities = kmalloc_array(pdata->haptic_intensities_steps, + sizeof(u32), GFP_KERNEL); + if (!pdata->haptic_intensities) + goto err1; + + ret = of_property_read_u32_array(dn, "samsung,haptic_intensities", + pdata->haptic_intensities, pdata->haptic_intensities_steps); + if (ret) { + dev_err(dev, "%s error.haptic_intensities.of_property_read_u32_array\n", + __func__); + goto err2; + } + + for (i = 0; i < pdata->haptic_intensities_steps; i++) { + if (pdata->haptic_intensities[i] > MAX_INTENSITY_VALUE) { + dev_err(dev, "%s error. i=%d pdata->haptic_intensities=%u\n", + __func__, i, pdata->haptic_intensities[i]); + goto err2; + } else { + dev_info(dev, "%s i=%d %u\n", __func__, + i, pdata->haptic_intensities[i]); + } + } + + return pdata; +err2: + kfree(pdata->haptic_intensities); +err1: + kfree(pdata->intensities); +err: + return NULL; +} + +static int vib_info_probe(struct platform_device *pdev) +{ + struct vib_info_platform_data *pdata; + struct vib_info_driver_data *ddata; + struct device *dev = &pdev->dev; + + int ret = 0; + + pdata = vib_info_get_pdata(pdev); + if (!pdata) { + dev_err(dev, "No platform data found\n"); + ret = -EINVAL; + goto err1; + } + + pdev->dev.platform_data = pdata; + + ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) { + ret = -ENOMEM; + goto err1; + } + + ddata->functions_count = pdata->functions_count; + ddata->functions_s = pdata->functions_s; + ddata->intensities_steps = pdata->intensities_steps; + ddata->intensities = pdata->intensities; + ddata->haptic_intensities_steps + = pdata->haptic_intensities_steps; + ddata->haptic_intensities = pdata->haptic_intensities; + + platform_set_drvdata(pdev, ddata); + dev_set_drvdata(dev, ddata); + + ddata->vib_info_class = class_create(THIS_MODULE, "vib_info_class"); + if (IS_ERR(ddata->vib_info_class)) { + ret = PTR_ERR(ddata->vib_info_class); + goto err1; + } + ddata->virtual_dev = device_create(ddata->vib_info_class, + NULL, MKDEV(0, 0), ddata, "vib_support_info"); + if (IS_ERR(ddata->virtual_dev)) { + ret = PTR_ERR(ddata->virtual_dev); + goto err2; + } + + ret = sysfs_create_group(&ddata->virtual_dev->kobj, + &vib_info_attr_group); + if (ret) { + ret = -ENODEV; + dev_err(dev, "Failed to create sysfs %d\n", ret); + goto err3; + } + + dev_info(dev, "%s\n", __func__); + return 0; +err3: + device_destroy(ddata->vib_info_class, MKDEV(0, 0)); +err2: + class_destroy(ddata->vib_info_class); +err1: + return ret; +} + +static int vib_info_remove(struct platform_device *pdev) +{ + struct vib_info_driver_data *ddata = dev_get_drvdata(&pdev->dev); + struct vib_info_platform_data *pdata; + + pdata = pdev->dev.platform_data; + + sysfs_remove_group(&pdev->dev.kobj, &vib_info_attr_group); + device_destroy(ddata->vib_info_class, MKDEV(0, 0)); + class_destroy(ddata->vib_info_class); + if (pdata) { + kfree(pdata->haptic_intensities); + kfree(pdata->intensities); + } + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id vib_info_dt_ids[] = { + { .compatible = "samsung,vib-info", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, vib_info_dt_ids); +#endif + +static struct platform_driver vib_info_driver = { + .probe = vib_info_probe, + .remove = vib_info_remove, + .driver = { + .name = "vib_info", + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = of_match_ptr(vib_info_dt_ids), +#endif + }, +}; + +static int __init vib_info_init(void) +{ + return platform_driver_register(&vib_info_driver); +} + +static void __exit vib_info_exit(void) +{ + platform_driver_unregister(&vib_info_driver); +} + +module_init(vib_info_init); +module_exit(vib_info_exit); + +MODULE_AUTHOR("Samsung USB Team"); +MODULE_DESCRIPTION("vib_info"); +MODULE_LICENSE("GPL"); diff --git a/drivers/vibrator/cs/cs40l26/Kconfig b/drivers/vibrator/cs/cs40l26/Kconfig new file mode 100644 index 000000000000..26f06d0ae44f --- /dev/null +++ b/drivers/vibrator/cs/cs40l26/Kconfig @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Input cs40l26 drivers configuration +# +config INPUT_CS40L26_I2C + tristate "Cirrus Logic CS40L26 Haptic Driver (I2C)" + depends on I2C + select REGMAP_I2C + help + Say Y here to enable support for CS40L26 boosted + haptic amplifier with I2C control port. + + To complie the driver as a module choose M here: the + module will be called input_cs40l26_i2c. + +config INPUT_CS40L26_SPI + tristate "Cirrus Logic CS40L26 Haptic Driver (SPI)" + depends on SPI_MASTER + select REGMAP_SPI + help + Say Y here to enable support for CS40L26 boosted + haptic amplifier with SPI control port. + + To compile the driver as a module choose M here: the + module will be called input_cs40l26_spi. + +config CS40L26_SAMSUNG_FEATURE + bool "Cirrus CS40L26 Haptic Driver for Samsung feature" + depends on INPUT_CS40L26_I2C + default n + help + Say Y to enable CS40L26_SAMSUNG_FEATURE + codes featured with this comment + aren't the original code from cirrus. + To use samsung concept, please enable this. + +config CS40L26_SAMSUNG_USE_DVL + bool "Cirrus CS40L26 Haptic Driver uses dvl bin" + default n + help + Say Y to enable CS40L26_SAMSUNG_USE_DVL. + is used for enabling dvl firmware load + +config CS40L26_SAMSUNG_USE_MAX_DATA_TX_SIZE + bool "Cirrus CS40L26 driver uses max data transfer size" + depends on CS40L26_SAMSUNG_FEATURE + default n + help + Say Y to enable CS40L26_SAMSUNG_USE_MAX_DATA_TX_SIZE. + Samsung feature uses only 32 byte transfer due to SLSI AP's + I3C Regmap limitation. To use maximum data transfer size + please enable it. + +config CS40L26_KUNIT_TEST + tristate "KUnit test for cs40l26_test" + depends on SEC_KUNIT + depends on UML + select REGMAP_IRQ + select MFD_CORE + help + TODO: Describe config fully. + This CONFIG is recommended to set to y. + diff --git a/drivers/vibrator/cs/cs40l26/Makefile b/drivers/vibrator/cs/cs40l26/Makefile new file mode 100644 index 000000000000..ec538ebfd53f --- /dev/null +++ b/drivers/vibrator/cs/cs40l26/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the cs40l26 drivers. +# + +# Each configuration option enables a list of files. +subdir-ccflags-y := -Wformat +input-cs40l26-i2c-objs := cs40l26.o cs40l26-tables.o cs40l26-sysfs.o cs40l26-i2c.o cs40l26-debugfs.o +input-cs40l26-spi-objs := cs40l26.o cs40l26-tables.o cs40l26-sysfs.o cs40l26-spi.o cs40l26-debugfs.o + +obj-$(CONFIG_INPUT_CS40L26_I2C) += input-cs40l26-i2c.o +obj-$(CONFIG_INPUT_CS40L26_SPI) += input-cs40l26-spi.o + +GCOV_PROFILE_cs40l26.o := $(CONFIG_SEC_KUNIT) diff --git a/drivers/vibrator/cs/cs40l26/cs40l26-debugfs.c b/drivers/vibrator/cs/cs40l26/cs40l26-debugfs.c new file mode 100644 index 000000000000..6057f5439da4 --- /dev/null +++ b/drivers/vibrator/cs/cs40l26/cs40l26-debugfs.c @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// cs40l26-debugfs.c -- CS40L26 Boosted Haptic Driver with Integrated DSP and +// Waveform Memory with Advanced Closed Loop Algorithms and LRA protection +// +// Copyright 2022 Cirrus Logic, Inc. +// +// Author: Fred Treven +// +// 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. + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE +#include +#else +#include +#endif + +#ifdef CONFIG_DEBUG_FS +static ssize_t cs40l26_fw_ctrl_name_read(struct file *file, char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct cs40l26_private *cs40l26 = file->private_data; + ssize_t error = 0; + + mutex_lock(&cs40l26->lock); + + if (cs40l26->dbg_fw_ctrl_name) + error = simple_read_from_buffer(user_buf, count, ppos, cs40l26->dbg_fw_ctrl_name, + strlen(cs40l26->dbg_fw_ctrl_name)); + + mutex_unlock(&cs40l26->lock); + + return error; +} + +static ssize_t cs40l26_fw_ctrl_name_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct cs40l26_private *cs40l26 = file->private_data; + ssize_t error = 0; + + mutex_lock(&cs40l26->lock); + + kfree(cs40l26->dbg_fw_ctrl_name); + cs40l26->dbg_fw_ctrl_name = NULL; + + cs40l26->dbg_fw_ctrl_name = kzalloc(count, GFP_KERNEL); + if (!cs40l26->dbg_fw_ctrl_name) { + error = -ENOMEM; + goto err_mutex; + } + + error = simple_write_to_buffer(cs40l26->dbg_fw_ctrl_name, count, ppos, user_buf, count); + +err_mutex: + mutex_unlock(&cs40l26->lock); + + return error ? error : count; +} + +static ssize_t cs40l26_fw_algo_id_read(struct file *file, char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct cs40l26_private *cs40l26 = file->private_data; + ssize_t error; + char *str; + + str = kzalloc(CS40L26_ALGO_ID_MAX_STR_LEN, GFP_KERNEL); + if (!str) + return -ENOMEM; + + mutex_lock(&cs40l26->lock); + + snprintf(str, count, "0x%06X\n", cs40l26->dbg_fw_algo_id); + + mutex_unlock(&cs40l26->lock); + + error = simple_read_from_buffer(user_buf, count, ppos, str, strlen(str)); + + kfree(str); + + return error; +} + +static ssize_t cs40l26_fw_algo_id_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct cs40l26_private *cs40l26 = file->private_data; + ssize_t error; + char *str; + u32 val; + + str = kzalloc(count, GFP_KERNEL); + if (!str) + return -ENOMEM; + + simple_write_to_buffer(str, count, ppos, user_buf, count); + + error = kstrtou32(str, 16, &val); + if (error) + goto exit_free; + + mutex_lock(&cs40l26->lock); + + cs40l26->dbg_fw_algo_id = val; + + mutex_unlock(&cs40l26->lock); + +exit_free: + + kfree(str); + + return error ? error : count; +} + +static ssize_t cs40l26_fw_ctrl_val_read(struct file *file, char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct cs40l26_private *cs40l26 = file->private_data; + u32 reg, val, mem_type; + char *result, *input; + ssize_t error; + + if (!cs40l26->dbg_fw_ctrl_name || !cs40l26->dbg_fw_algo_id) + return -ENODEV; + + if (strlen(cs40l26->dbg_fw_ctrl_name) == 0) + return -ENODATA; + + error = pm_runtime_get_sync(cs40l26->dev); + if (error < 0) { + cs40l26_resume_error_handle(cs40l26->dev, (int) error); + return error; + } + + mutex_lock(&cs40l26->lock); + + mem_type = cs40l26->dbg_fw_ym ? CL_DSP_YM_UNPACKED_TYPE : CL_DSP_XM_UNPACKED_TYPE; + + input = kzalloc(strlen(cs40l26->dbg_fw_ctrl_name), GFP_KERNEL); + if (!input) { + error = -ENOMEM; + goto err_mutex; + } + + snprintf(input, strlen(cs40l26->dbg_fw_ctrl_name), "%s", cs40l26->dbg_fw_ctrl_name); + + error = cl_dsp_get_reg(cs40l26->dsp, input, mem_type, cs40l26->dbg_fw_algo_id, ®); + kfree(input); + if (error) + goto err_mutex; + + error = regmap_read(cs40l26->regmap, reg, &val); + if (error) { + dev_err(cs40l26->dev, "Failed to read fw control\n"); + goto err_mutex; + } + + result = kzalloc(CS40L26_ALGO_ID_MAX_STR_LEN, GFP_KERNEL); + if (!result) { + error = -ENOMEM; + goto err_mutex; + } + + snprintf(result, CS40L26_ALGO_ID_MAX_STR_LEN, "0x%08X\n", val); + error = simple_read_from_buffer(user_buf, count, ppos, result, strlen(result)); + + kfree(result); + +err_mutex: + mutex_unlock(&cs40l26->lock); + + pm_runtime_mark_last_busy(cs40l26->dev); + pm_runtime_put_autosuspend(cs40l26->dev); + + return error; +} + +static const struct { + const char *name; + const struct file_operations fops; +} cs40l26_debugfs_fops[] = { + { + .name = "fw_ctrl_name", + .fops = { + .open = simple_open, + .read = cs40l26_fw_ctrl_name_read, + .write = cs40l26_fw_ctrl_name_write, + }, + }, + { + .name = "fw_algo_id", + .fops = { + .open = simple_open, + .read = cs40l26_fw_algo_id_read, + .write = cs40l26_fw_algo_id_write, + }, + }, + { + .name = "fw_ctrl_val", + .fops = { + .open = simple_open, + .read = cs40l26_fw_ctrl_val_read, + }, + }, +}; + +void cs40l26_debugfs_init(struct cs40l26_private *cs40l26) +{ + struct dentry *root = NULL; + int i; + + cs40l26_debugfs_cleanup(cs40l26); + + root = debugfs_create_dir("cs40l26", NULL); + if (!root) + return; + + debugfs_create_bool("fw_ym_space", CL_DSP_DEBUGFS_RW_FILE_MODE, root, &cs40l26->dbg_fw_ym); + + for (i = 0; i < CS40L26_NUM_DEBUGFS; i++) + debugfs_create_file(cs40l26_debugfs_fops[i].name, CL_DSP_DEBUGFS_RW_FILE_MODE, + root, cs40l26, &cs40l26_debugfs_fops[i].fops); + + cs40l26->dbg_fw_ym = false; + cs40l26->dbg_fw_algo_id = CS40L26_VIBEGEN_ALGO_ID; + cs40l26->debugfs_root = root; + + if (cs40l26->fw_id == CS40L26_FW_ID && + cl_dsp_algo_is_present(cs40l26->dsp, CS40L26_EVENT_LOGGER_ALGO_ID)) { + cs40l26->cl_dsp_db = cl_dsp_debugfs_create(cs40l26->dsp, cs40l26->debugfs_root, + (u32) CS40L26_EVENT_LOGGER_ALGO_ID); + + if (IS_ERR(cs40l26->cl_dsp_db) || !cs40l26->cl_dsp_db) + dev_err(cs40l26->dev, "Failed to create CL DSP Debugfs\n"); + } +} +EXPORT_SYMBOL_GPL(cs40l26_debugfs_init); + +void cs40l26_debugfs_cleanup(struct cs40l26_private *cs40l26) +{ + cl_dsp_debugfs_destroy(cs40l26->cl_dsp_db); + cs40l26->cl_dsp_db = NULL; + kfree(cs40l26->dbg_fw_ctrl_name); + cs40l26->dbg_fw_ctrl_name = NULL; + debugfs_remove_recursive(cs40l26->debugfs_root); +} +EXPORT_SYMBOL_GPL(cs40l26_debugfs_cleanup); + +#endif /* CONFIG_DEBUG_FS */ diff --git a/drivers/vibrator/cs/cs40l26/cs40l26-i2c.c b/drivers/vibrator/cs/cs40l26/cs40l26-i2c.c new file mode 100644 index 000000000000..94d60ce23e47 --- /dev/null +++ b/drivers/vibrator/cs/cs40l26/cs40l26-i2c.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// cs40l26-i2c.c -- CS40L26 I2C Driver +// +// Copyright 2022 Cirrus Logic, Inc. +// +// Author: Fred Treven +// +// 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. + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE +#include +#else +#include +#endif + +static const struct i2c_device_id cs40l26_id_i2c[] = { + {"cs40l26a", 0}, + {"cs40l26b", 1}, + {"cs40l27a", 2}, + {"cs40l27b", 3}, + {} +}; +MODULE_DEVICE_TABLE(i2c, cs40l26_id_i2c); + +static const struct of_device_id cs40l26_of_match[CS40L26_NUM_DEVS + 1] = { + { .compatible = "cirrus,cs40l26a" }, + { .compatible = "cirrus,cs40l26b" }, + { .compatible = "cirrus,cs40l27a" }, + { .compatible = "cirrus,cs40l27b" }, + {} +}; +MODULE_DEVICE_TABLE(of, cs40l26_of_match); + +static int cs40l26_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct cs40l26_private *cs40l26; + int error; + + cs40l26 = devm_kzalloc(&client->dev, sizeof(struct cs40l26_private), GFP_KERNEL); + if (!cs40l26) + return -ENOMEM; + + i2c_set_clientdata(client, cs40l26); + + cs40l26->regmap = devm_regmap_init_i2c(client, &cs40l26_regmap); + if (IS_ERR(cs40l26->regmap)) { + error = PTR_ERR(cs40l26->regmap); + dev_err(&client->dev, "Failed to allocate register map: %d\n", error); + return error; + } + + cs40l26->dev = &client->dev; + cs40l26->irq = client->irq; + + return cs40l26_probe(cs40l26); +} + +static void cs40l26_i2c_remove(struct i2c_client *client) +{ + struct cs40l26_private *cs40l26 = i2c_get_clientdata(client); + + cs40l26_remove(cs40l26); +} + +static struct i2c_driver cs40l26_i2c_driver = { + .driver = { + .name = "cs40l26", + .of_match_table = cs40l26_of_match, + .pm = &cs40l26_pm_ops, + }, + .id_table = cs40l26_id_i2c, + .probe = cs40l26_i2c_probe, + .remove = cs40l26_i2c_remove, +}; + +module_i2c_driver(cs40l26_i2c_driver); + +MODULE_DESCRIPTION("CS40L26 I2C Driver"); +MODULE_AUTHOR("Fred Treven, Cirrus Logic Inc. "); +MODULE_LICENSE("GPL"); diff --git a/drivers/vibrator/cs/cs40l26/cs40l26-spi.c b/drivers/vibrator/cs/cs40l26/cs40l26-spi.c new file mode 100644 index 000000000000..ed9accc26401 --- /dev/null +++ b/drivers/vibrator/cs/cs40l26/cs40l26-spi.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// cs40l26-spi.c -- CS40L26 SPI Driver +// +// Copyright 2022 Cirrus Logic, Inc. +// +// Author: Fred Treven +// +// 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. + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE +#include +#else +#include +#endif + +static const struct spi_device_id cs40l26_id_spi[] = { + {"cs40l26a", 0}, + {"cs40l26b", 1}, + {"cs40l27a", 2}, + {"cs40l27b", 3}, + {} +}; +MODULE_DEVICE_TABLE(spi, cs40l26_id_spi); + +static const struct of_device_id cs40l26_of_match[CS40L26_NUM_DEVS + 1] = { + { .compatible = "cirrus,cs40l26a" }, + { .compatible = "cirrus,cs40l26b" }, + { .compatible = "cirrus,cs40l27a" }, + { .compatible = "cirrus,cs40l27b" }, + {} +}; +MODULE_DEVICE_TABLE(of, cs40l26_of_match); + +static int cs40l26_spi_probe(struct spi_device *spi) +{ + struct cs40l26_private *cs40l26; + int error; + + cs40l26 = devm_kzalloc(&spi->dev, sizeof(struct cs40l26_private), GFP_KERNEL); + if (!cs40l26) + return -ENOMEM; + + spi_set_drvdata(spi, cs40l26); + + cs40l26->regmap = devm_regmap_init_spi(spi, &cs40l26_regmap); + if (IS_ERR(cs40l26->regmap)) { + error = PTR_ERR(cs40l26->regmap); + dev_err(&spi->dev, "Failed to allocate register map: %d\n", error); + return error; + } + + cs40l26->dev = &spi->dev; + cs40l26->irq = spi->irq; + + return cs40l26_probe(cs40l26); +} + +static void cs40l26_spi_remove(struct spi_device *spi) +{ + struct cs40l26_private *cs40l26 = spi_get_drvdata(spi); + + cs40l26_remove(cs40l26); +} + +static struct spi_driver cs40l26_spi_driver = { + .driver = { + .name = "cs40l26", + .of_match_table = cs40l26_of_match, + .pm = &cs40l26_pm_ops, + }, + + .id_table = cs40l26_id_spi, + .probe = cs40l26_spi_probe, + .remove = cs40l26_spi_remove, +}; + +module_spi_driver(cs40l26_spi_driver); + +MODULE_DESCRIPTION("CS40L26 SPI Driver"); +MODULE_AUTHOR("Fred Treven, Cirrus Logic Inc. "); +MODULE_LICENSE("GPL"); diff --git a/drivers/vibrator/cs/cs40l26/cs40l26-sysfs.c b/drivers/vibrator/cs/cs40l26/cs40l26-sysfs.c new file mode 100644 index 000000000000..d5d731fa4ce7 --- /dev/null +++ b/drivers/vibrator/cs/cs40l26/cs40l26-sysfs.c @@ -0,0 +1,2156 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// cs40l26-sysfs.c -- CS40L26 Boosted Haptic Driver with Integrated DSP and +// Waveform Memory with Advanced Closed Loop Algorithms and LRA protection +// +// Copyright 2022 Cirrus Logic, Inc. +// +// Author: Fred Treven +// +// 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. + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE +#include +#else +#include +#endif + +static ssize_t dsp_state_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u8 dsp_state; + int error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + error = cs40l26_dsp_state_get(cs40l26, &dsp_state); + + cs40l26_pm_exit(cs40l26->dev); + + if (error) + return error; + else + return snprintf(buf, PAGE_SIZE, "%u\n", (unsigned int) (dsp_state & 0xFF)); +} +static DEVICE_ATTR_RO(dsp_state); + +static ssize_t owt_lib_compat_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "1.0.0\n"); +} +static DEVICE_ATTR_RO(owt_lib_compat); + +static ssize_t overprotection_gain_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 reg, op_gain; + int error; + + if (!cl_dsp_algo_is_present(cs40l26->dsp, CS40L26_EP_ALGO_ID)) + return -EPERM; + + error = cl_dsp_get_reg(cs40l26->dsp, "PROTECTION_XM_OP_GAIN", + CL_DSP_XM_UNPACKED_TYPE, CS40L26_EP_ALGO_ID, ®); + if (error) + return error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + error = regmap_read(cs40l26->regmap, reg, &op_gain); + + cs40l26_pm_exit(cs40l26->dev); + + return error ? error : snprintf(buf, PAGE_SIZE, "%d\n", op_gain); +} + +static ssize_t overprotection_gain_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 reg, op_gain; + int error; + + if (!cl_dsp_algo_is_present(cs40l26->dsp, CS40L26_EP_ALGO_ID)) + return -EPERM; + + error = kstrtou32(buf, 10, &op_gain); + + if (error || op_gain < CS40L26_OVERPROTECTION_GAIN_MIN || + op_gain > CS40L26_UINT_24_BITS_MAX) + return -EINVAL; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + error = cl_dsp_get_reg(cs40l26->dsp, "PROTECTION_XM_OP_GAIN", + CL_DSP_XM_UNPACKED_TYPE, CS40L26_EP_ALGO_ID, ®); + if (error) + goto err_pm; + + error = regmap_write(cs40l26->regmap, reg, op_gain); + +err_pm: + cs40l26_pm_exit(cs40l26->dev); + + return error ? error : count; +} +static DEVICE_ATTR_RW(overprotection_gain); + +static ssize_t halo_heartbeat_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 reg, halo_heartbeat; + int error; + + error = cl_dsp_get_reg(cs40l26->dsp, "HALO_HEARTBEAT", CL_DSP_XM_UNPACKED_TYPE, + cs40l26->fw_id, ®); + if (error) + return error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + error = regmap_read(cs40l26->regmap, reg, &halo_heartbeat); + + cs40l26_pm_exit(cs40l26->dev); + + if (error) + return error; + + return snprintf(buf, PAGE_SIZE, "%d\n", halo_heartbeat); +} +static DEVICE_ATTR_RO(halo_heartbeat); + +static ssize_t pm_stdby_timeout_ms_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 timeout_ms; + int error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + error = cs40l26_pm_timeout_ms_get(cs40l26, CS40L26_DSP_STATE_STANDBY, &timeout_ms); + + cs40l26_pm_exit(cs40l26->dev); + + if (error) + return error; + + return snprintf(buf, PAGE_SIZE, "%u\n", timeout_ms); +} + +static ssize_t pm_stdby_timeout_ms_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 timeout_ms; + int error; + + error = kstrtou32(buf, 10, &timeout_ms); + if (error) + return -EINVAL; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + error = cs40l26_pm_timeout_ms_set(cs40l26, CS40L26_DSP_STATE_STANDBY, timeout_ms); + + cs40l26_pm_exit(cs40l26->dev); + + if (error) + return error; + + return count; +} +static DEVICE_ATTR_RW(pm_stdby_timeout_ms); + +static ssize_t pm_active_timeout_ms_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 timeout_ms; + int error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + error = cs40l26_pm_timeout_ms_get(cs40l26, CS40L26_DSP_STATE_ACTIVE, &timeout_ms); + + cs40l26_pm_exit(cs40l26->dev); + + if (error) + return error; + + return snprintf(buf, PAGE_SIZE, "%u\n", timeout_ms); +} + +static ssize_t pm_active_timeout_ms_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 timeout_ms; + int error; + + error = kstrtou32(buf, 10, &timeout_ms); + if (error) + return -EINVAL; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + error = cs40l26_pm_timeout_ms_set(cs40l26, CS40L26_DSP_STATE_ACTIVE, timeout_ms); + + cs40l26_pm_exit(cs40l26->dev); + + if (error) + return error; + + return count; +} +static DEVICE_ATTR_RW(pm_active_timeout_ms); + +static ssize_t vibe_state_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + unsigned int state; + + if (!cs40l26->vibe_state_reporting) { + dev_err(cs40l26->dev, "vibe_state not supported\n"); + return -EPERM; + } + + mutex_lock(&cs40l26->lock); + state = cs40l26->vibe_state; + mutex_unlock(&cs40l26->lock); + + return snprintf(buf, PAGE_SIZE, "%u\n", state); +} +static DEVICE_ATTR_RO(vibe_state); + +static ssize_t power_on_seq_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + struct cs40l26_pseq_op *op; + u32 addr, data, base; + int error; + + mutex_lock(&cs40l26->lock); + + base = cs40l26->pseq_base; + + if (list_empty(&cs40l26->pseq_op_head)) { + dev_err(cs40l26->dev, "Power on sequence is empty\n"); + error = -EINVAL; + goto err_mutex; + } + + list_for_each_entry_reverse(op, &cs40l26->pseq_op_head, list) { + switch (op->operation) { + case CS40L26_PSEQ_OP_WRITE_FULL: + addr = ((op->words[0] & 0xFFFF) << 16) | ((op->words[1] & 0x00FFFF00) >> 8); + data = ((op->words[1] & 0xFF) << 24) | (op->words[2] & 0xFFFFFF); + break; + case CS40L26_PSEQ_OP_WRITE_H16: + case CS40L26_PSEQ_OP_WRITE_L16: + addr = ((op->words[0] & 0xFFFF) << 8) | ((op->words[1] & 0xFF0000) >> 16); + data = (op->words[1] & 0xFFFF); + + if (op->operation == CS40L26_PSEQ_OP_WRITE_H16) + data <<= 16; + break; + case CS40L26_PSEQ_OP_WRITE_ADDR8: + addr = (op->words[0] & 0xFF00) >> 8; + data = ((op->words[0] & 0xFF) << 24) | (op->words[1] & 0xFFFFFF); + break; + case CS40L26_PSEQ_OP_END: + addr = CS40L26_PSEQ_OP_END_ADDR; + data = CS40L26_PSEQ_OP_END_DATA; + break; + default: + dev_err(cs40l26->dev, "Unrecognized Op Code: 0x%02X\n", op->operation); + error = -EINVAL; + goto err_mutex; + } + + dev_dbg(cs40l26->dev, "0x%08x: code = 0x%02X, Addr = 0x%08X, Data = 0x%08X\n", + base + op->offset, op->operation, addr, data); + } + + error = snprintf(buf, PAGE_SIZE, "%d\n", cs40l26->pseq_num_ops); + +err_mutex: + mutex_unlock(&cs40l26->lock); + return error; +} +static DEVICE_ATTR_RO(power_on_seq); + +static ssize_t owt_free_space_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 reg, words; + int error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + error = cl_dsp_get_reg(cs40l26->dsp, "OWT_SIZE_XM", + CL_DSP_XM_UNPACKED_TYPE, CS40L26_VIBEGEN_ALGO_ID, ®); + if (error) + goto err_pm; + + error = regmap_read(cs40l26->regmap, reg, &words); + if (error) { + dev_err(cs40l26->dev, "Failed to get remaining OWT space\n"); + goto err_pm; + } + + error = snprintf(buf, PAGE_SIZE, "%d\n", words * CL_DSP_BYTES_PER_WORD); + +err_pm: + cs40l26_pm_exit(cs40l26->dev); + + return error; +} +static DEVICE_ATTR_RO(owt_free_space); + +static ssize_t die_temp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + struct regmap *regmap = cs40l26->regmap; + u16 die_temp; + int error; + u32 val; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + error = regmap_read(regmap, CS40L26_GLOBAL_ENABLES, &val); + if (error) { + dev_err(cs40l26->dev, "Failed to read GLOBAL_EN status\n"); + goto err_pm; + } + + if (!(val & CS40L26_GLOBAL_EN_MASK)) { + dev_err(cs40l26->dev, "Global enable must be set to get die temp.\n"); + error = -EPERM; + goto err_pm; + } + + error = regmap_read(regmap, CS40L26_ENABLES_AND_CODES_DIG, &val); + if (error) { + dev_err(cs40l26->dev, "Failed to get die temperature\n"); + goto err_pm; + } + + die_temp = (val & CS40L26_TEMP_RESULT_FILT_MASK) >> CS40L26_TEMP_RESULT_FILT_SHIFT; + + error = snprintf(buf, PAGE_SIZE, "0x%03X\n", die_temp); + +err_pm: + cs40l26_pm_exit(cs40l26->dev); + + return error; +} +static DEVICE_ATTR_RO(die_temp); + +static ssize_t num_waves_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 nwaves; + int error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + error = cs40l26_get_num_waves(cs40l26, &nwaves); + if (error) + goto err_pm; + + error = snprintf(buf, PAGE_SIZE, "%u\n", nwaves); + +err_pm: + cs40l26_pm_exit(cs40l26->dev); + + return error; +} +static DEVICE_ATTR_RO(num_waves); + +/* boost_disable_delay is in units of 125us, e.g. 8 -> 1ms */ +static ssize_t boost_disable_delay_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 reg, boost_disable_delay; + int error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + error = cl_dsp_get_reg(cs40l26->dsp, "BOOST_DISABLE_DELAY", + CL_DSP_XM_UNPACKED_TYPE, CS40L26_EXT_ALGO_ID, ®); + if (error) + goto err_pm; + + error = regmap_read(cs40l26->regmap, reg, &boost_disable_delay); + if (error) + goto err_pm; + + error = snprintf(buf, PAGE_SIZE, "%d\n", boost_disable_delay); + +err_pm: + cs40l26_pm_exit(cs40l26->dev); + + return error; +} + +static ssize_t boost_disable_delay_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 reg, boost_disable_delay; + int error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s: %s", __func__, buf); +#else + dev_dbg(cs40l26->dev, "%s: %s", __func__, buf); +#endif + + error = kstrtou32(buf, 10, &boost_disable_delay); + + if (error || boost_disable_delay < CS40L26_BOOST_DISABLE_DELAY_MIN || + boost_disable_delay > CS40L26_BOOST_DISABLE_DELAY_MAX) + return -EINVAL; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + error = cl_dsp_get_reg(cs40l26->dsp, "BOOST_DISABLE_DELAY", + CL_DSP_XM_UNPACKED_TYPE, CS40L26_EXT_ALGO_ID, ®); + if (error) + goto err_pm; + + error = regmap_write(cs40l26->regmap, reg, boost_disable_delay); + +err_pm: + cs40l26_pm_exit(cs40l26->dev); + + if (error) + return error; + else + return count; +} +static DEVICE_ATTR_RW(boost_disable_delay); + +static ssize_t f0_offset_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + unsigned int reg, val; + int error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "F0_OFFSET", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_VIBEGEN_ALGO_ID, ®); + if (error) + goto err_mutex; + + + error = regmap_read(cs40l26->regmap, reg, &val); + if (error) + goto err_mutex; + + error = snprintf(buf, PAGE_SIZE, "%u\n", val); + +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + return error; +} + +static ssize_t f0_offset_store(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + unsigned int reg, val; + int error; + + error = kstrtou32(buf, 10, &val); + if (error) + return -EINVAL; + + if (val > CS40L26_F0_OFFSET_MAX && val < CS40L26_F0_OFFSET_MIN) + return -EINVAL; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "F0_OFFSET", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_VIBEGEN_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_write(cs40l26->regmap, reg, val); + if (error) + goto err_mutex; + + error = count; + +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + return error; +} +static DEVICE_ATTR_RW(f0_offset); + +static ssize_t delay_before_stop_playback_us_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + int error; + + mutex_lock(&cs40l26->lock); + + error = snprintf(buf, PAGE_SIZE, "%d\n", cs40l26->delay_before_stop_playback_us); + + mutex_unlock(&cs40l26->lock); + + return error; +} + +static ssize_t delay_before_stop_playback_us_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 val; + int error; + + error = kstrtou32(buf, 10, &val); + if (error) + return -EINVAL; + + mutex_lock(&cs40l26->lock); + + cs40l26->delay_before_stop_playback_us = val; + + mutex_unlock(&cs40l26->lock); + + return count; +} +static DEVICE_ATTR_RW(delay_before_stop_playback_us); + +static ssize_t f0_comp_enable_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + int error; + + mutex_lock(&cs40l26->lock); + + if (cs40l26->fw_id == CS40L26_FW_CALIB_ID) { + error = -EPERM; + goto err_mutex; + } + + if (cs40l26->comp_enable_pend) { + error = -EIO; + goto err_mutex; + } + + error = snprintf(buf, PAGE_SIZE, "%d\n", cs40l26->comp_enable_f0); + +err_mutex: + mutex_unlock(&cs40l26->lock); + + return error; +} + +static ssize_t f0_comp_enable_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + int error; + unsigned int val; + u32 reg, value; + + error = kstrtou32(buf, 10, &val); + if (error) + return -EINVAL; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + cs40l26->comp_enable_pend = true; + cs40l26->comp_enable_f0 = val > 0; + + value = (cs40l26->comp_enable_redc << CS40L26_COMP_EN_REDC_SHIFT) | + (cs40l26->comp_enable_f0 << CS40L26_COMP_EN_F0_SHIFT); + + if (cs40l26->fw_id == CS40L26_FW_CALIB_ID) { + error = -EPERM; + } else { + error = cl_dsp_get_reg(cs40l26->dsp, "COMPENSATION_ENABLE", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_VIBEGEN_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_write(cs40l26->regmap, reg, value); + } + + if (error) + goto err_mutex; + + error = count; + +err_mutex: + cs40l26->comp_enable_pend = false; + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + return error; +} +static DEVICE_ATTR_RW(f0_comp_enable); + +static ssize_t redc_comp_enable_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + int error; + + mutex_lock(&cs40l26->lock); + + if (cs40l26->fw_id == CS40L26_FW_CALIB_ID) { + error = -EPERM; + goto err_mutex; + } + + if (cs40l26->comp_enable_pend) { + error = -EIO; + goto err_mutex; + } + + error = snprintf(buf, PAGE_SIZE, "%d\n", cs40l26->comp_enable_redc); + +err_mutex: + mutex_unlock(&cs40l26->lock); + + return error; +} + +static ssize_t redc_comp_enable_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + int error; + unsigned int val; + u32 reg, value; + + error = kstrtou32(buf, 10, &val); + if (error) + return -EINVAL; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + cs40l26->comp_enable_pend = true; + cs40l26->comp_enable_redc = val > 0; + + value = (cs40l26->comp_enable_redc << CS40L26_COMP_EN_REDC_SHIFT) | + (cs40l26->comp_enable_f0 << CS40L26_COMP_EN_F0_SHIFT); + + if (cs40l26->fw_id == CS40L26_FW_CALIB_ID) { + error = -EPERM; + } else { + error = cl_dsp_get_reg(cs40l26->dsp, "COMPENSATION_ENABLE", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_VIBEGEN_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_write(cs40l26->regmap, reg, value); + } + + if (error) + goto err_mutex; + + error = count; + +err_mutex: + cs40l26->comp_enable_pend = false; + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + return error; +} +static DEVICE_ATTR_RW(redc_comp_enable); + +static ssize_t swap_firmware_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + int error; + + mutex_lock(&cs40l26->lock); + + if (cs40l26->fw_id == CS40L26_FW_ID) + error = snprintf(buf, PAGE_SIZE, "%d\n", 0); + else if (cs40l26->fw_id == CS40L26_FW_CALIB_ID) + error = snprintf(buf, PAGE_SIZE, "%d\n", 1); + else + error = -EINVAL; + + mutex_unlock(&cs40l26->lock); + + return error; +} + +static ssize_t swap_firmware_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + int error; + unsigned int variant; + + error = kstrtou32(buf, 10, &variant); + if (error) + return error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s %d\n", __func__, variant); +#endif + if (variant == 0) + error = cs40l26_fw_swap(cs40l26, CS40L26_FW_ID); + else if (variant == 1) + error = cs40l26_fw_swap(cs40l26, CS40L26_FW_CALIB_ID); + else + error = -EINVAL; + + return error ? error : count; +} +static DEVICE_ATTR_RW(swap_firmware); + +static struct attribute *cs40l26_dev_attrs[] = { + &dev_attr_num_waves.attr, + &dev_attr_die_temp.attr, + &dev_attr_owt_free_space.attr, + &dev_attr_power_on_seq.attr, + &dev_attr_dsp_state.attr, + &dev_attr_halo_heartbeat.attr, + &dev_attr_pm_stdby_timeout_ms.attr, + &dev_attr_pm_active_timeout_ms.attr, + &dev_attr_vibe_state.attr, + &dev_attr_boost_disable_delay.attr, + &dev_attr_f0_offset.attr, + &dev_attr_delay_before_stop_playback_us.attr, + &dev_attr_f0_comp_enable.attr, + &dev_attr_redc_comp_enable.attr, + &dev_attr_swap_firmware.attr, + &dev_attr_owt_lib_compat.attr, + &dev_attr_overprotection_gain.attr, + NULL, +}; + +struct attribute_group cs40l26_dev_attr_group = { + .name = "default", + .attrs = cs40l26_dev_attrs, +}; + +static ssize_t dbc_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 val, reg; + int error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "FLAGS", CL_DSP_XM_UNPACKED_TYPE, CS40L26_EXT_ALGO_ID, + ®); + if (error) + goto err_pm; + + error = regmap_read(cs40l26->regmap, reg, &val); + if (error) { + dev_err(cs40l26->dev, "Failed to get FLAGS\n"); + goto err_pm; + } + + + val &= CS40L26_DBC_ENABLE_MASK; + val >>= CS40L26_DBC_ENABLE_SHIFT; + + error = snprintf(buf, PAGE_SIZE, "%u\n", val); + +err_pm: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + return error; +} + +static ssize_t dbc_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + int error; + u32 val; + + error = kstrtou32(buf, 10, &val); + if (error) + return error; + + if (val > 1) + return -EINVAL; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cs40l26_dbc_enable(cs40l26, val); + + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + return error ? error : count; +} +static DEVICE_ATTR_RW(dbc_enable); + +static ssize_t dbc_env_rel_coef_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + unsigned int val; + int error; + + error = cs40l26_dbc_get(cs40l26, CS40L26_DBC_ENV_REL_COEF, &val); + + return error ? error : snprintf(buf, PAGE_SIZE, "%u\n", val); +} + +static ssize_t dbc_env_rel_coef_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + struct device *cdev = cs40l26->dev; + u32 val; + int error; + + error = kstrtou32(buf, 10, &val); + if (error) + return error; + + error = cs40l26_pm_enter(cdev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cs40l26_dbc_set(cs40l26, CS40L26_DBC_ENV_REL_COEF, val); + + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cdev); + + return error ? error : count; +} +static DEVICE_ATTR_RW(dbc_env_rel_coef); + +static ssize_t dbc_rise_headroom_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + unsigned int val; + int error; + + error = cs40l26_dbc_get(cs40l26, CS40L26_DBC_RISE_HEADROOM, &val); + + return error ? error : snprintf(buf, PAGE_SIZE, "%u\n", val); +} + +static ssize_t dbc_rise_headroom_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + struct device *cdev = cs40l26->dev; + u32 val; + int error; + + error = kstrtou32(buf, 10, &val); + if (error) + return error; + + error = cs40l26_pm_enter(cdev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cs40l26_dbc_set(cs40l26, CS40L26_DBC_RISE_HEADROOM, val); + + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cdev); + + return error ? error : count; +} +static DEVICE_ATTR_RW(dbc_rise_headroom); + +static ssize_t dbc_fall_headroom_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + unsigned int val; + int error; + + error = cs40l26_dbc_get(cs40l26, CS40L26_DBC_FALL_HEADROOM, &val); + + return error ? error : snprintf(buf, PAGE_SIZE, "%u\n", val); +} + +static ssize_t dbc_fall_headroom_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + struct device *cdev = cs40l26->dev; + u32 val; + int error; + + error = kstrtou32(buf, 10, &val); + if (error) + return error; + + error = cs40l26_pm_enter(cdev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cs40l26_dbc_set(cs40l26, CS40L26_DBC_FALL_HEADROOM, val); + + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cdev); + + return error ? error : count; +} +static DEVICE_ATTR_RW(dbc_fall_headroom); + +static ssize_t dbc_tx_lvl_thresh_fs_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + unsigned int val; + int error; + + error = cs40l26_dbc_get(cs40l26, CS40L26_DBC_TX_LVL_THRESH_FS, &val); + + return error ? error : snprintf(buf, PAGE_SIZE, "%u\n", val); +} + +static ssize_t dbc_tx_lvl_thresh_fs_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + struct device *cdev = cs40l26->dev; + u32 val; + int error; + + error = kstrtou32(buf, 10, &val); + if (error) + return error; + + error = cs40l26_pm_enter(cdev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cs40l26_dbc_set(cs40l26, CS40L26_DBC_TX_LVL_THRESH_FS, val); + + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cdev); + + return error ? error : count; +} +static DEVICE_ATTR_RW(dbc_tx_lvl_thresh_fs); + +static ssize_t dbc_tx_lvl_hold_off_ms_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + unsigned int val; + int error; + + error = cs40l26_dbc_get(cs40l26, CS40L26_DBC_TX_LVL_HOLD_OFF_MS, &val); + + return error ? error : snprintf(buf, PAGE_SIZE, "%u\n", val); +} + +static ssize_t dbc_tx_lvl_hold_off_ms_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + struct device *cdev = cs40l26->dev; + u32 val; + int error; + + error = kstrtou32(buf, 10, &val); + if (error) + return error; + + error = cs40l26_pm_enter(cdev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cs40l26_dbc_set(cs40l26, CS40L26_DBC_TX_LVL_HOLD_OFF_MS, val); + + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cdev); + + return error ? error : count; +} +static DEVICE_ATTR_RW(dbc_tx_lvl_hold_off_ms); + +static struct attribute *cs40l26_dev_attrs_dbc[] = { + &dev_attr_dbc_enable.attr, + &dev_attr_dbc_env_rel_coef.attr, + &dev_attr_dbc_rise_headroom.attr, + &dev_attr_dbc_fall_headroom.attr, + &dev_attr_dbc_tx_lvl_thresh_fs.attr, + &dev_attr_dbc_tx_lvl_hold_off_ms.attr, + NULL, +}; + +struct attribute_group cs40l26_dev_attr_dbc_group = { + .name = "dbc", + .attrs = cs40l26_dev_attrs_dbc, +}; + +static ssize_t trigger_calibration_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 mailbox_command, calibration_request_payload; + int error; + struct completion *completion; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s: %s", __func__, buf); +#else + dev_dbg(cs40l26->dev, "%s: %s", __func__, buf); +#endif + + if (!cs40l26->calib_fw) { + dev_err(cs40l26->dev, "Must use calibration firmware\n"); + return -EPERM; + } + + error = kstrtou32(buf, 16, &calibration_request_payload); + if (error) + return -EINVAL; + + switch (calibration_request_payload) { + case CS40L26_CALIBRATION_CONTROL_REQUEST_F0_AND_Q: + completion = &cs40l26->cal_f0_cont; + break; + case CS40L26_CALIBRATION_CONTROL_REQUEST_REDC: + completion = &cs40l26->cal_redc_cont; + break; + case CS40L26_CALIBRATION_CONTROL_REQUEST_DVL_PEQ: + completion = &cs40l26->cal_dvl_peq_cont; + break; + case CS40L26_CALIBRATION_CONTROL_REQUEST_LS_CALIBRATION: + completion = &cs40l26->cal_ls_cont; + break; + default: + return -EINVAL; + } + + mailbox_command = ((CS40L26_DSP_MBOX_CMD_INDEX_CALIBRATION_CONTROL << + CS40L26_DSP_MBOX_CMD_INDEX_SHIFT) & CS40L26_DSP_MBOX_CMD_INDEX_MASK) | + (calibration_request_payload & CS40L26_DSP_MBOX_CMD_PAYLOAD_MASK); + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + reinit_completion(completion); + + error = cs40l26_mailbox_write(cs40l26, mailbox_command); + + mutex_unlock(&cs40l26->lock); + + if (error) { + dev_err(cs40l26->dev, "Failed to request calibration\n"); + goto err_pm; + } + + if (!wait_for_completion_timeout( + completion, + msecs_to_jiffies(CS40L26_CALIBRATION_TIMEOUT_MS))) { + error = -ETIME; + dev_err(cs40l26->dev, "Failed to complete cal req, %d, err: %d", + calibration_request_payload, error); + goto err_pm; + } + + mutex_lock(&cs40l26->lock); + + if (calibration_request_payload == CS40L26_CALIBRATION_CONTROL_REQUEST_F0_AND_Q) + error = cs40l26_copy_f0_est_to_dvl(cs40l26); + + mutex_unlock(&cs40l26->lock); +err_pm: + cs40l26_pm_exit(cs40l26->dev); + return error ? error : count; +} +static DEVICE_ATTR_WO(trigger_calibration); + +static ssize_t f0_measured_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 reg, f0_measured; + int error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "F0_EST", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_F0_EST_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_read(cs40l26->regmap, reg, &f0_measured); + if (error) + goto err_mutex; + +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + if (error) + return error; + else + return snprintf(buf, PAGE_SIZE, "%08X\n", f0_measured); +} +static DEVICE_ATTR_RO(f0_measured); + +static ssize_t q_measured_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 reg, q_measured; + int error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "Q_EST", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_F0_EST_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_read(cs40l26->regmap, reg, &q_measured); + if (error) + goto err_mutex; + +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + if (error) + return error; + else + return snprintf(buf, PAGE_SIZE, "%08X\n", q_measured); +} +static DEVICE_ATTR_RO(q_measured); + +static ssize_t redc_measured_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 reg, redc_measured; + int error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "RE_EST_STATUS", CL_DSP_YM_UNPACKED_TYPE, + CS40L26_SVC_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_read(cs40l26->regmap, reg, &redc_measured); + if (error) + goto err_mutex; + +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + if (error) + return error; + else + return snprintf(buf, PAGE_SIZE, "%08X\n", redc_measured); +} +static DEVICE_ATTR_RO(redc_measured); + +static ssize_t redc_est_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 reg, redc_est; + int error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "REDC", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_F0_EST_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_read(cs40l26->regmap, reg, &redc_est); + if (error) + goto err_mutex; + +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + if (error) + return error; + else + return snprintf(buf, PAGE_SIZE, "%08X\n", redc_est); +} + +static ssize_t redc_est_store(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 reg, redc_est; + int error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s: %s", __func__, buf); +#else + dev_dbg(cs40l26->dev, "%s: %s", __func__, buf); +#endif + + error = kstrtou32(buf, 16, &redc_est); + if (error) + return error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "REDC", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_F0_EST_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_write(cs40l26->regmap, reg, redc_est); + if (error) + goto err_mutex; + +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + if (error) + return error; + else + return count; +} +static DEVICE_ATTR_RW(redc_est); + +static ssize_t f0_stored_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 reg, f0_stored; + int error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "F0_OTP_STORED", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_VIBEGEN_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_read(cs40l26->regmap, reg, &f0_stored); + if (error) + goto err_mutex; + +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + if (error) + return error; + else + return snprintf(buf, PAGE_SIZE, "%08X\n", f0_stored); +} + +static ssize_t f0_stored_store(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 reg, f0_stored; + int error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s: %s", __func__, buf); +#else + dev_dbg(cs40l26->dev, "%s: %s", __func__, buf); +#endif + + error = kstrtou32(buf, 16, &f0_stored); + + if (error || f0_stored < CS40L26_F0_EST_MIN || f0_stored > CS40L26_F0_EST_MAX) + return -EINVAL; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "F0_OTP_STORED", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_VIBEGEN_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_write(cs40l26->regmap, reg, f0_stored); + if (error) + goto err_mutex; + +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + if (error) + return error; + else + return count; +} +static DEVICE_ATTR_RW(f0_stored); + +static ssize_t q_stored_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 reg, q_stored; + int error; + + if (cs40l26->revid == CS40L26_REVID_B2) { + dev_err(cs40l26->dev, "q_stored not support for revision %02X\n", cs40l26->revid); + return -EPERM; + } + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "Q_STORED", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_VIBEGEN_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_read(cs40l26->regmap, reg, &q_stored); + if (error) + goto err_mutex; + +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + if (error) + return error; + else + return snprintf(buf, PAGE_SIZE, "%08X\n", q_stored); +} + +static ssize_t q_stored_store(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 reg, q_stored; + int error; + + if (cs40l26->revid == CS40L26_REVID_B2) { + dev_err(cs40l26->dev, "q_stored not support for revision %02X\n", cs40l26->revid); + return -EPERM; + } +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s: %s", __func__, buf); +#else + dev_dbg(cs40l26->dev, "%s: %s", __func__, buf); +#endif + + error = kstrtou32(buf, 16, &q_stored); + + if (error || q_stored < CS40L26_Q_EST_MIN || q_stored > CS40L26_Q_EST_MAX) + return -EINVAL; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "Q_STORED", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_VIBEGEN_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_write(cs40l26->regmap, reg, q_stored); + if (error) + goto err_mutex; + +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + if (error) + return error; + else + return count; +} +static DEVICE_ATTR_RW(q_stored); + +static ssize_t redc_stored_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 reg, redc_stored; + int error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "REDC_OTP_STORED", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_VIBEGEN_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_read(cs40l26->regmap, reg, &redc_stored); + if (error) + goto err_mutex; + +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + if (error) + return error; + else + return snprintf(buf, PAGE_SIZE, "%08X\n", redc_stored); +} + +static ssize_t redc_stored_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 reg, redc_stored; + int error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s: %s", __func__, buf); +#else + dev_dbg(cs40l26->dev, "%s: %s", __func__, buf); +#endif + + error = kstrtou32(buf, 16, &redc_stored); + if (error) + return error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "REDC_OTP_STORED", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_VIBEGEN_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_write(cs40l26->regmap, reg, redc_stored); + if (error) + goto err_mutex; + +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + if (error) + return error; + else + return count; +} +static DEVICE_ATTR_RW(redc_stored); + +static ssize_t freq_centre_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 freq_centre, reg; + int error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "FREQ_CENTRE", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_F0_EST_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_read(cs40l26->regmap, reg, &freq_centre); + +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + return error ? error : snprintf(buf, PAGE_SIZE, "%08X\n", freq_centre); +} + +static ssize_t freq_centre_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 freq_centre, reg; + int error; + + error = kstrtou32(buf, 16, &freq_centre); + if (error) + return error; + + if (freq_centre < CS40L26_F0_FREQ_CENTRE_MIN || + freq_centre > CS40L26_F0_FREQ_CENTRE_MAX) + return -EINVAL; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "FREQ_CENTRE", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_F0_EST_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_write(cs40l26->regmap, reg, freq_centre); + +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + return error ? error : count; +} +static DEVICE_ATTR_RW(freq_centre); + +static ssize_t freq_span_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + int error, freq_span; + u32 reg; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "FREQ_SPAN", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_F0_EST_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_read(cs40l26->regmap, reg, &freq_span); + +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + return error ? error : snprintf(buf, PAGE_SIZE, "%08X\n", freq_span); +} + +static ssize_t freq_span_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + int error, s_freq_span; + u32 freq_span, reg; + + error = kstrtou32(buf, 16, &freq_span); + if (error) + return error; + + freq_span &= GENMASK(23, 0); + s_freq_span = (freq_span & BIT(23)) ? (freq_span | GENMASK(31, 24)) : freq_span; + + if (abs(s_freq_span) < CS40L26_F0_FREQ_SPAN_MIN || + abs(s_freq_span) > CS40L26_F0_FREQ_SPAN_MAX) + return -EINVAL; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "FREQ_SPAN", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_F0_EST_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_write(cs40l26->regmap, reg, freq_span); + +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + return error ? error : count; +} +static DEVICE_ATTR_RW(freq_span); + +static ssize_t f0_and_q_cal_time_ms_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 reg, tone_dur_ms, freq_centre, freq_span; + int error, f0_and_q_cal_time_ms; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "TONE_DURATION_MS", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_F0_EST_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_read(cs40l26->regmap, reg, &tone_dur_ms); + if (error) { + dev_err(cs40l26->dev, "Failed to get tone duration\n"); + goto err_mutex; + } + + if (tone_dur_ms == 0) { /* Calculate value */ + error = cl_dsp_get_reg(cs40l26->dsp, "FREQ_SPAN", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_F0_EST_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_read(cs40l26->regmap, reg, &freq_span); + if (error) { + dev_err(cs40l26->dev, "Failed to get FREQ_SPAN\n"); + goto err_mutex; + } + + error = cl_dsp_get_reg(cs40l26->dsp, "FREQ_CENTRE", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_F0_EST_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_read(cs40l26->regmap, reg, &freq_centre); + if (error) { + dev_err(cs40l26->dev, "Failed to get FREQ_CENTRE\n"); + goto err_mutex; + } + + f0_and_q_cal_time_ms = ((CS40L26_F0_CHIRP_DURATION_FACTOR * + (int) (freq_span >> CS40L26_F0_EST_FREQ_FRAC_BITS)) / + (int) (freq_centre >> CS40L26_F0_EST_FREQ_FRAC_BITS)); + } else if (tone_dur_ms < CS40L26_F0_AND_Q_CALIBRATION_MIN_MS) { + f0_and_q_cal_time_ms = CS40L26_F0_AND_Q_CALIBRATION_MIN_MS; + } else if (tone_dur_ms > CS40L26_F0_AND_Q_CALIBRATION_MAX_MS) { + f0_and_q_cal_time_ms = CS40L26_F0_AND_Q_CALIBRATION_MAX_MS; + } else { + f0_and_q_cal_time_ms = tone_dur_ms; + } + +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + if (error) + return error; + else + return snprintf(buf, PAGE_SIZE, "%d\n", f0_and_q_cal_time_ms); +} +static DEVICE_ATTR_RO(f0_and_q_cal_time_ms); + +static ssize_t redc_cal_time_ms_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + /* FIRMWARE_STUMPY_CALIB_REDC_PLAYTIME_MS + SVC_INIT + buffer */ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 reg, redc_playtime_ms, redc_total_cal_time_ms; + int error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "REDC_PLAYTIME_MS", CL_DSP_XM_UNPACKED_TYPE, + cs40l26->fw_id, ®); + if (error) + goto err_mutex; + + error = regmap_read(cs40l26->regmap, reg, &redc_playtime_ms); + + redc_total_cal_time_ms = redc_playtime_ms + CS40L26_SVC_INITIALIZATION_PERIOD_MS + + CS40L26_REDC_CALIBRATION_BUFFER_MS; + +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + if (error) + return error; + else + return snprintf(buf, PAGE_SIZE, "%d\n", redc_total_cal_time_ms); +} +static DEVICE_ATTR_RO(redc_cal_time_ms); + +static ssize_t dvl_peq_coefficients_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + u32 reg, dvl_peq_coefficients[CS40L26_DVL_PEQ_COEFFICIENTS_NUM_REGS]; + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + int error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "PEQ_COEF1_X", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_DVL_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_bulk_read(cs40l26->regmap, reg, dvl_peq_coefficients, + CS40L26_DVL_PEQ_COEFFICIENTS_NUM_REGS); + if (error) + goto err_mutex; + +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + if (error) + return error; + + return snprintf(buf, PAGE_SIZE, "%08X %08X %08X %08X %08X %08X\n", + dvl_peq_coefficients[0], dvl_peq_coefficients[1], dvl_peq_coefficients[2], + dvl_peq_coefficients[3], dvl_peq_coefficients[4], dvl_peq_coefficients[5]); +} + +static ssize_t dvl_peq_coefficients_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + u32 reg, dvl_peq_coefficients[CS40L26_DVL_PEQ_COEFFICIENTS_NUM_REGS]; + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + char *coeffs_str, *coeffs_str_temp, *coeff_str; + int error, coeffs_found = 0; + + coeffs_str = kstrdup(buf, GFP_KERNEL); + if (!coeffs_str) + return -ENOMEM; + + coeffs_str_temp = coeffs_str; + while ((coeff_str = strsep(&coeffs_str_temp, " ")) != NULL) { + error = kstrtou32(coeff_str, 16, &dvl_peq_coefficients[coeffs_found++]); + if (error) + goto err_free; + } + + if (coeffs_found != CS40L26_DVL_PEQ_COEFFICIENTS_NUM_REGS) { + dev_err(cs40l26->dev, "Num DVL PEQ coeffs, %d, expecting %d\n", + coeffs_found, CS40L26_DVL_PEQ_COEFFICIENTS_NUM_REGS); + error = -EINVAL; + goto err_free; + } + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + goto err_free; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "PEQ_COEF1_X", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_DVL_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_bulk_write(cs40l26->regmap, reg, dvl_peq_coefficients, + CS40L26_DVL_PEQ_COEFFICIENTS_NUM_REGS); + if (error) + dev_err(cs40l26->dev, "Failed to write DVL PEQ coefficients,%d", error); + +err_mutex: + mutex_unlock(&cs40l26->lock); + cs40l26_pm_exit(cs40l26->dev); +err_free: + kfree(coeffs_str); + return error ? error : count; +} +static DEVICE_ATTR_RW(dvl_peq_coefficients); + +static ssize_t logging_en_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 reg, enable; + int error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "ENABLE", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_LOGGER_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_read(cs40l26->regmap, reg, &enable); + +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + return error ? error : snprintf(buf, PAGE_SIZE, "%u\n", enable); +} + +static ssize_t logging_en_store(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 enable, reg; + int error; + + error = kstrtou32(buf, 10, &enable); + if (error) + return error; + + enable &= CS40L26_LOGGER_EN_MASK; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "ENABLE", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_LOGGER_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_write(cs40l26->regmap, reg, enable); + +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + return error ? error : count; +} +static DEVICE_ATTR_RW(logging_en); + +static ssize_t logging_max_reset_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 rst; + int error; + + error = kstrtou32(buf, 10, &rst); + if (error) + return error; + + if (rst != 1) + return -EINVAL; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + cs40l26_mailbox_write(cs40l26, CS40L26_DSP_MBOX_CMD_LOGGER_MAX_RESET); + + cs40l26_pm_exit(cs40l26->dev); + + return count; +} +static DEVICE_ATTR_WO(logging_max_reset); + +static int cs40l26_logger_max_get(struct cs40l26_private *cs40l26, u32 src_id, u32 *max) +{ + int error, reg, src_num; + u32 offset; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + for (src_num = 0; src_num < cs40l26->num_log_srcs; src_num++) { + if (cs40l26->log_srcs[src_num].id == src_id) + break; + } + + if (src_num == cs40l26->num_log_srcs) { + error = -ENODEV; + goto err_mutex; + } + + error = cl_dsp_get_reg(cs40l26->dsp, "DATA", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_LOGGER_ALGO_ID, ®); + if (error) + goto err_mutex; + + offset = (src_num * CS40L26_LOGGER_DATA_MAX_STEP) + CS40L26_LOGGER_DATA_MAX_OFFSET; + + error = regmap_read(cs40l26->regmap, reg + offset, max); + +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + return error; +} + +static ssize_t max_bemf_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 max_bemf; + int error; + + error = cs40l26_logger_max_get(cs40l26, CS40L26_LOGGER_SRC_ID_BEMF, &max_bemf); + + return error ? error : snprintf(buf, PAGE_SIZE, "0x%06X\n", max_bemf); +} +static DEVICE_ATTR_RO(max_bemf); + +static ssize_t max_vbst_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 max_vbst; + int error; + + error = cs40l26_logger_max_get(cs40l26, CS40L26_LOGGER_SRC_ID_VBST, &max_vbst); + + return error ? error : snprintf(buf, PAGE_SIZE, "0x%06X\n", max_vbst); +} +static DEVICE_ATTR_RO(max_vbst); + +static ssize_t max_vmon_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 max_vmon; + int error; + + error = cs40l26_logger_max_get(cs40l26, CS40L26_LOGGER_SRC_ID_VMON, &max_vmon); + + return error ? error : snprintf(buf, PAGE_SIZE, "0x%06X\n", max_vmon); +} +static DEVICE_ATTR_RO(max_vmon); + +static ssize_t max_excursion_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 max_excursion; + int error; + + error = cs40l26_logger_max_get(cs40l26, CS40L26_LOGGER_SRC_ID_EP, &max_excursion); + + return error ? error : snprintf(buf, PAGE_SIZE, "0x%06X\n", max_excursion); +} +static DEVICE_ATTR_RO(max_excursion); + +static ssize_t svc_le_est_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + unsigned int le; + int error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cs40l26_svc_le_estimate(cs40l26, &le); + + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + if (error) + return error; + + return snprintf(buf, PAGE_SIZE, "%u\n", le); +} +static DEVICE_ATTR_RO(svc_le_est); + +static ssize_t svc_le_stored_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + int error; + + mutex_lock(&cs40l26->lock); + + error = snprintf(buf, PAGE_SIZE, "%d\n", cs40l26->svc_le_est_stored); + + mutex_unlock(&cs40l26->lock); + + return error; +} + +static ssize_t svc_le_stored_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + u32 svc_le_stored; + int error; + + error = kstrtou32(buf, 10, &svc_le_stored); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + cs40l26->svc_le_est_stored = svc_le_stored; + + mutex_unlock(&cs40l26->lock); + + return count; +} +static DEVICE_ATTR_RW(svc_le_stored); + +static struct attribute *cs40l26_dev_attrs_cal[] = { + &dev_attr_svc_le_est.attr, + &dev_attr_svc_le_stored.attr, + &dev_attr_max_vbst.attr, + &dev_attr_max_bemf.attr, + &dev_attr_max_vmon.attr, + &dev_attr_max_excursion.attr, + &dev_attr_logging_max_reset.attr, + &dev_attr_logging_en.attr, + &dev_attr_trigger_calibration.attr, + &dev_attr_f0_measured.attr, + &dev_attr_q_measured.attr, + &dev_attr_redc_measured.attr, + &dev_attr_dvl_peq_coefficients.attr, + &dev_attr_redc_est.attr, + &dev_attr_f0_stored.attr, + &dev_attr_q_stored.attr, + &dev_attr_redc_stored.attr, + &dev_attr_freq_centre.attr, + &dev_attr_freq_span.attr, + &dev_attr_f0_and_q_cal_time_ms.attr, + &dev_attr_redc_cal_time_ms.attr, + NULL, +}; + +struct attribute_group cs40l26_dev_attr_cal_group = { + .name = "calibration", + .attrs = cs40l26_dev_attrs_cal, +}; diff --git a/drivers/vibrator/cs/cs40l26/cs40l26-tables.c b/drivers/vibrator/cs/cs40l26/cs40l26-tables.c new file mode 100644 index 000000000000..273d95fbdaf7 --- /dev/null +++ b/drivers/vibrator/cs/cs40l26/cs40l26-tables.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// cs40l26-tables.c -- CS40L26 Boosted Haptic Driver with Integrated DSP and +// Waveform Memory with Advanced Closed Loop Algorithms and LRA protection +// +// Copyright 2022 Cirrus Logic, Inc. +// +// Author: Fred Treven +// +// 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. + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE +#include +#else +#include +#endif + +const struct regmap_config cs40l26_regmap = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_BIG, + .max_register = CS40L26_LASTREG, + .num_reg_defaults = 0, + .precious_reg = cs40l26_precious_reg, + .readable_reg = cs40l26_readable_reg, + .volatile_reg = cs40l26_volatile_reg, + .cache_type = REGCACHE_NONE, +}; +EXPORT_SYMBOL_GPL(cs40l26_regmap); + +const struct cs40l26_dbc cs40l26_dbc_params[CS40L26_DBC_NUM_CONTROLS] = { + { + .type = CS40L26_DBC_ENV_REL_COEF, + .name = CS40L26_DBC_ENV_REL_COEF_NAME, + .max = CS40L26_DBC_CONTROLS_MAX, + .min = CS40L26_DBC_ENV_REL_COEF_MIN, + }, + { + .type = CS40L26_DBC_RISE_HEADROOM, + .name = CS40L26_DBC_RISE_HEADROOM_NAME, + .max = CS40L26_DBC_CONTROLS_MAX, + .min = CS40L26_DBC_RISE_HEADROOM_MIN, + }, + { + .type = CS40L26_DBC_FALL_HEADROOM, + .name = CS40L26_DBC_FALL_HEADROOM_NAME, + .max = CS40L26_DBC_CONTROLS_MAX, + .min = CS40L26_DBC_FALL_HEADROOM_MIN, + }, + { + .type = CS40L26_DBC_TX_LVL_THRESH_FS, + .name = CS40L26_DBC_TX_LVL_THRESH_FS_NAME, + .max = CS40L26_DBC_CONTROLS_MAX, + .min = CS40L26_DBC_TX_LVL_THRESH_FS_MIN, + }, + { + .type = CS40L26_DBC_TX_LVL_HOLD_OFF_MS, + .name = CS40L26_DBC_TX_LVL_HOLD_OFF_MS_NAME, + .max = CS40L26_DBC_TX_LVL_HOLD_OFF_MS_MAX, + .min = CS40L26_DBC_TX_LVL_HOLD_OFF_MS_MIN, + }, +}; +EXPORT_SYMBOL_GPL(cs40l26_dbc_params); + +const struct reg_sequence cs40l26_a1_errata[CS40L26_ERRATA_A1_NUM_WRITES] = { + { CS40L26_PLL_REFCLK_DETECT_0, 0x00000000 }, + { CS40L26_TEST_KEY_CTRL, CS40L26_TEST_KEY_UNLOCK_CODE1 }, + { CS40L26_TEST_KEY_CTRL, CS40L26_TEST_KEY_UNLOCK_CODE2 }, + { CS40L26_TEST_LBST, CS40L26_DISABLE_EXPL_MODE }, + { CS40L26_TEST_KEY_CTRL, CS40L26_TEST_KEY_LOCK_CODE }, +}; + +const u8 cs40l26_pseq_op_sizes[CS40L26_PSEQ_NUM_OPS][2] = { + { CS40L26_PSEQ_OP_WRITE_FULL, CS40L26_PSEQ_OP_WRITE_FULL_WORDS }, + { CS40L26_PSEQ_OP_WRITE_FIELD, CS40L26_PSEQ_OP_WRITE_FIELD_WORDS }, + { CS40L26_PSEQ_OP_WRITE_ADDR8, CS40L26_PSEQ_OP_WRITE_ADDR8_WORDS }, + { CS40L26_PSEQ_OP_WRITE_INCR, CS40L26_PSEQ_OP_WRITE_INCR_WORDS }, + { CS40L26_PSEQ_OP_WRITE_L16, CS40L26_PSEQ_OP_WRITE_X16_WORDS }, + { CS40L26_PSEQ_OP_WRITE_H16, CS40L26_PSEQ_OP_WRITE_X16_WORDS }, + { CS40L26_PSEQ_OP_DELAY, CS40L26_PSEQ_OP_DELAY_WORDS }, + { CS40L26_PSEQ_OP_END, CS40L26_PSEQ_OP_END_WORDS }, +}; + +struct regulator_bulk_data cs40l26_supplies[CS40L26_NUM_SUPPLIES] = { + { .supply = CS40L26_VP_SUPPLY_NAME }, + { .supply = CS40L26_VA_SUPPLY_NAME }, +}; + +const struct mfd_cell cs40l26_devs[CS40L26_NUM_MFD_DEVS] = { + { .name = "cs40l26-codec" }, +}; + +const u32 cs40l26_attn_q21_2_vals[CS40L26_NUM_PCT_MAP_VALUES] = { + 400, /* MUTE */ + 160, /* 1% */ + 136, + 122, + 112, + 104, + 98, + 92, + 88, + 84, + 80, + 77, + 74, + 71, + 68, + 66, + 64, + 62, + 60, + 58, + 56, + 54, + 53, + 51, + 50, + 48, /* 25% */ + 47, + 45, + 44, + 43, + 42, + 41, + 40, + 39, + 37, + 36, + 35, + 35, + 34, + 33, + 32, + 31, + 30, + 29, + 29, + 28, + 27, + 26, + 26, + 25, + 24, /* 50 % */ + 23, + 23, + 22, + 21, + 21, + 20, + 20, + 19, + 18, + 18, + 17, + 17, + 16, + 16, + 15, + 14, + 14, + 13, + 13, + 12, + 12, + 11, + 11, + 10, + 10, /* 75% */ + 10, + 9, + 9, + 8, + 8, + 7, + 7, + 6, + 6, + 6, + 5, + 5, + 4, + 4, + 4, + 3, + 3, + 3, + 2, + 2, + 1, + 1, + 1, + 0, + 0, /* 100% */ +}; + +bool cs40l26_precious_reg(struct device *dev, unsigned int reg) +{ + return false; +} +EXPORT_SYMBOL_GPL(cs40l26_precious_reg); + +bool cs40l26_volatile_reg(struct device *dev, unsigned int reg) +{ + return false; +} +EXPORT_SYMBOL_GPL(cs40l26_volatile_reg); + +bool cs40l26_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS40L26_DEVID: + case CS40L26_REVID: + case CS40L26_TEST_KEY_CTRL: + case CS40L26_GLOBAL_ENABLES: + case CS40L26_BLOCK_ENABLES2: + case CS40L26_ERROR_RELEASE: + case CS40L26_PWRMGT_CTL: + case CS40L26_PWRMGT_STS: + case CS40L26_REFCLK_INPUT: + case CS40L26_GLOBAL_SAMPLE_RATE: + case CS40L26_PLL_REFCLK_DETECT_0: + case CS40L26_VBST_CTL_1: + case CS40L26_VBST_CTL_2: + case CS40L26_BST_IPK_CTL: + case CS40L26_BST_DCM_CTL: + case CS40L26_TEST_LBST: + case CS40L26_MONITOR_FILT: + case CS40L26_SPKMON_VMON_DEC_OUT_DATA: + case CS40L26_ENABLES_AND_CODES_DIG: + case CS40L26_ASP_ENABLES1: + case CS40L26_ASP_CONTROL2: + case CS40L26_ASP_FRAME_CONTROL5: + case CS40L26_ASP_DATA_CONTROL5: + case CS40L26_DACPCM1_INPUT: + case CS40L26_ASPTX1_INPUT: + case CS40L26_DSP1RX1_INPUT: + case CS40L26_DSP1RX5_INPUT: + case CS40L26_NGATE1_INPUT: + case CS40L26_VPBR_CONFIG: + case CS40L26_VBBR_CONFIG: + case CS40L26_VPBR_STATUS: + case CS40L26_VBBR_STATUS: + case CS40L26_DIGPWM_CONFIG2: + case CS40L26_TST_DAC_MSM_CONFIG: + case CS40L26_IRQ1_CFG: + case CS40L26_IRQ1_STATUS: + case CS40L26_IRQ1_EINT_1: + case CS40L26_IRQ1_EINT_2: + case CS40L26_IRQ1_STS_1: + case CS40L26_IRQ1_STS_2: + case CS40L26_IRQ1_MASK_1: + case CS40L26_IRQ1_MASK_2: + case CS40L26_MIXER_NGATE_CH1_CFG: + case CS40L26_DSP_MBOX_1 ... CS40L26_DSP_VIRTUAL1_MBOX_1: + case CS40L26_DSP1_XMEM_PACKED_0 ... CS40L26_DSP1_XMEM_PACKED_6143: + case CS40L26_DSP1_XROM_PACKED_0 ... CS40L26_DSP1_XROM_PACKED_4604: + case CS40L26_DSP1_XMEM_UNPACKED32_0 ... CS40L26_DSP1_XROM_UNPACKED32_3070: + case CS40L26_DSP1_XMEM_UNPACKED24_0 ... CS40L26_DSP1_XMEM_UNPACKED24_8191: + case CS40L26_DSP1_XROM_UNPACKED24_0 ... CS40L26_DSP1_XROM_UNPACKED24_6141: + case CS40L26_DSP1_CCM_CORE_CONTROL: + case CS40L26_DSP1_YMEM_PACKED_0 ... CS40L26_DSP1_YMEM_PACKED_1532: + case CS40L26_DSP1_YMEM_UNPACKED32_0 ... CS40L26_DSP1_YMEM_UNPACKED32_1022: + case CS40L26_DSP1_YMEM_UNPACKED24_0 ... CS40L26_DSP1_YMEM_UNPACKED24_2045: + case CS40L26_DSP1_PMEM_0 ... CS40L26_DSP1_PMEM_5114: + case CS40L26_DSP1_PROM_0 ... CS40L26_DSP1_PROM_30714: + return true; + default: + return false; + } +} +EXPORT_SYMBOL_GPL(cs40l26_readable_reg); diff --git a/drivers/vibrator/cs/cs40l26/cs40l26.c b/drivers/vibrator/cs/cs40l26/cs40l26.c new file mode 100644 index 000000000000..36d4a9c3aa26 --- /dev/null +++ b/drivers/vibrator/cs/cs40l26/cs40l26.c @@ -0,0 +1,6123 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// cs40l26.c -- CS40L26 Boosted Haptic Driver with Integrated DSP and +// Waveform Memory with Advanced Closed Loop Algorithms and LRA protection +// +// Copyright 2022 Cirrus Logic, Inc. +// +// Author: Fred Treven +// +// 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. + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE +#include +#if IS_ENABLED(CONFIG_SEC_ABC) +#include +#endif +#else +#include +#endif + +static const struct cs40l26_rom_regs cs40l26_rom_regs_a1_b0_b1 = { + .pm_cur_state = 0x02800370, + .pm_state_locks = 0x02800378, + .pm_timeout_ticks = 0x02800350, + .dsp_halo_state = 0x02800fa8, + .event_map_table_event_data_packed = 0x02806FC4, + .p_vibegen_rom = 0x02802154, + .rom_pseq_end_of_script = 0x028003E8, +}; + +static const struct cs40l26_rom_regs cs40l26_rom_regs_b2 = { /* RC2 8.1.2 */ + .pm_cur_state = 0x02801F98, + .pm_state_locks = 0x02801FA0, + .pm_timeout_ticks = 0x02801F78, + .dsp_halo_state = 0x02806AF8, + .event_map_table_event_data_packed = 0x02806FB0, + .p_vibegen_rom = 0x02802F50, + .rom_pseq_end_of_script = 0x02802040, +}; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE +static const char * const vibe_state_events[] = { + [CS40L26_VIBE_STATE_EVENT_MBOX_PLAYBACK] = "MBOX_PLAYBACK", + [CS40L26_VIBE_STATE_EVENT_MBOX_COMPLETE] = "MBOX_COMPLETE", + [CS40L26_VIBE_STATE_EVENT_GPIO_TRIGGER] = "GPIO_TRIGGER", + [CS40L26_VIBE_STATE_EVENT_GPIO_COMPLETE] = "GPIO_COMPLETE", + [CS40L26_VIBE_STATE_EVENT_ASP_START] = "ASP_START", + [CS40L26_VIBE_STATE_EVENT_ASP_STOP] = "ASP_STOP", +}; + +static const char * const vibe_state_strings[] = { + [CS40L26_VIBE_STATE_STOPPED] = "VIBE_STATE_STOPPED", + [CS40L26_VIBE_STATE_HAPTIC] = "VIBE_STATE_HAPTIC", + [CS40L26_VIBE_STATE_ASP] = "VIBE_STATE_ASP", +}; + +static const char * const pm_state_strings[] = { + [CS40L26_PM_STATE_HIBERNATE] = "HIBERNATE", + [CS40L26_PM_STATE_WAKEUP] = "WAKEUP", + [CS40L26_PM_STATE_PREVENT_HIBERNATE] = "PREVENT_HIBERNATE", + [CS40L26_PM_STATE_ALLOW_HIBERNATE] = "ALLOW_HIBERNATE", + [CS40L26_PM_STATE_SHUTDOWN] = "SHUTDOWN", +}; +#endif + +static inline bool section_complete(struct cs40l26_owt_section *s) +{ + return s->delay ? true : false; +} + +static u32 gpio_map_get(struct device *dev, enum cs40l26_gpio_map gpio) +{ + const char *name = (gpio == CS40L26_GPIO_MAP_A_PRESS) ? + "cirrus,press-index" : "cirrus,release-index"; + u32 bank_idx_pair[2]; + int error; + + error = device_property_read_u32_array(dev, name, bank_idx_pair, 2); + if (error) + return error; + + if (bank_idx_pair[0] == CS40L26_RAM_BANK_ID) + return (bank_idx_pair[1] & CS40L26_BTN_INDEX_MASK) | (1 << CS40L26_BTN_BANK_SHIFT); + else if (bank_idx_pair[0] == CS40L26_ROM_BANK_ID) + return (bank_idx_pair[1] & CS40L26_BTN_INDEX_MASK); + + return CS40L26_EVENT_MAP_GPI_DISABLE; +} + +static int cs40l26_dsp_read(struct cs40l26_private *cs40l26, u32 reg, u32 *val) +{ + struct regmap *regmap = cs40l26->regmap; + struct device *dev = cs40l26->dev; + u32 read_val; + int i; + + for (i = 0; i < CS40L26_DSP_TIMEOUT_COUNT; i++) { + if (regmap_read(regmap, reg, &read_val)) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "Failed to read 0x%X, attempt(s) = %d\n", reg, i + 1); +#else + dev_dbg(dev, "Failed to read 0x%X, attempt(s) = %d\n", reg, i + 1); +#endif + } else + break; + + usleep_range(CS40L26_DSP_TIMEOUT_US_MIN, CS40L26_DSP_TIMEOUT_US_MAX); + } + + if (i >= CS40L26_DSP_TIMEOUT_COUNT) { + dev_err(dev, "Timed out attempting to read 0x%X\n", reg); + return -ETIME; + } + + *val = read_val; + + return 0; +} + +static int cs40l26_dsp_write(struct cs40l26_private *cs40l26, u32 reg, u32 val) +{ + struct regmap *regmap = cs40l26->regmap; + struct device *dev = cs40l26->dev; + int i; + + for (i = 0; i < CS40L26_DSP_TIMEOUT_COUNT; i++) { + if (regmap_write(regmap, reg, val)) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "Failed to write to 0x%X, attempt(s) = %d\n", reg, i + 1); +#else + dev_dbg(dev, "Failed to write to 0x%X, attempt(s) = %d\n", reg, i + 1); +#endif + } else + break; + + usleep_range(CS40L26_DSP_TIMEOUT_US_MIN, CS40L26_DSP_TIMEOUT_US_MAX); + } + + if (i >= CS40L26_DSP_TIMEOUT_COUNT) { + dev_err(dev, "Timed out attempting to write to 0x%X\n", reg); + return -ETIME; + } + + return 0; +} + +int cs40l26_mailbox_write(struct cs40l26_private *cs40l26, u32 write_val) +{ + int i, error; + u32 val; + + error = cs40l26_dsp_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1, write_val); + if (error) + return error; + + for (i = 0; i < CS40L26_DSP_TIMEOUT_COUNT; i++) { + error = cs40l26_dsp_read(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1, &val); + if (error) + return error; + + if (val == 0x0) + break; + + usleep_range(CS40L26_DSP_TIMEOUT_US_MIN, CS40L26_DSP_TIMEOUT_US_MAX); + } + + if (i >= CS40L26_DSP_TIMEOUT_COUNT) { + dev_err(cs40l26->dev, "Mailbox not acknowledged (0x%08X != 0x0)\n", val); + return -ETIMEDOUT; + } + + return 0; +} +EXPORT_SYMBOL_GPL(cs40l26_mailbox_write); + +int cs40l26_dsp_state_get(struct cs40l26_private *cs40l26, u8 *state) +{ + u32 reg, dsp_state; + int error; + + if (cs40l26->fw_loaded) { + error = cl_dsp_get_reg(cs40l26->dsp, "PM_CUR_STATE", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_PM_ALGO_ID, ®); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(cs40l26->dev, "CANT READ PM_CUR_STATE %d\n", error); +#endif + return error; + } + } else { + reg = cs40l26->rom_regs->pm_cur_state; + } + + error = cs40l26_dsp_read(cs40l26, reg, &dsp_state); + if (error) + return error; + + switch (dsp_state) { + case CS40L26_DSP_STATE_HIBERNATE: + /* intentionally fall through */ + case CS40L26_DSP_STATE_SHUTDOWN: + /* intentionally fall through */ + case CS40L26_DSP_STATE_STANDBY: + /* intentionally fall through */ + case CS40L26_DSP_STATE_ACTIVE: + *state = CS40L26_DSP_STATE_MASK & dsp_state; + break; + default: + dev_err(cs40l26->dev, "DSP state %u is invalid\n", dsp_state); + error = -EINVAL; + } + + return error; +} +EXPORT_SYMBOL_GPL(cs40l26_dsp_state_get); + +int cs40l26_set_pll_loop(struct cs40l26_private *cs40l26, unsigned int pll_loop) +{ + int i; + + if (pll_loop != CS40L26_PLL_REFCLK_SET_OPEN_LOOP && + pll_loop != CS40L26_PLL_REFCLK_SET_CLOSED_LOOP) { + dev_err(cs40l26->dev, "Invalid PLL Loop setting: %u\n", pll_loop); + return -EINVAL; + } + + /* Retry in case DSP is hibernating */ + for (i = 0; i < CS40L26_PLL_REFCLK_SET_ATTEMPTS; i++) { + if (!regmap_update_bits(cs40l26->regmap, CS40L26_REFCLK_INPUT, + CS40L26_PLL_REFCLK_LOOP_MASK, pll_loop << + CS40L26_PLL_REFCLK_LOOP_SHIFT)) + break; + } + + if (i == CS40L26_PLL_REFCLK_SET_ATTEMPTS) { + dev_err(cs40l26->dev, "Failed to configure PLL\n"); + return -ETIMEDOUT; + } + + return 0; +} +EXPORT_SYMBOL_GPL(cs40l26_set_pll_loop); + +int cs40l26_dbc_get(struct cs40l26_private *cs40l26, enum cs40l26_dbc_type dbc, unsigned int *val) +{ + struct device *dev = cs40l26->dev; + unsigned int reg; + int error; + + error = cs40l26_pm_enter(dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, cs40l26_dbc_params[dbc].name, CL_DSP_XM_UNPACKED_TYPE, + CS40L26_EXT_ALGO_ID, ®); + if (error) + goto err_pm; + + error = regmap_read(cs40l26->regmap, reg, val); + if (error) + dev_err(dev, "Failed to read Dynamic Boost Control value\n"); + +err_pm: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(dev); + + return error; +} +EXPORT_SYMBOL_GPL(cs40l26_dbc_get); + +int cs40l26_dbc_set(struct cs40l26_private *cs40l26, enum cs40l26_dbc_type dbc, u32 val) +{ + struct device *dev = cs40l26->dev; + u32 reg, write_val; + int error; + + if (val > cs40l26_dbc_params[dbc].max) + write_val = cs40l26_dbc_params[dbc].max; + else if (val < cs40l26_dbc_params[dbc].min) + write_val = cs40l26_dbc_params[dbc].min; + else + write_val = val; + + error = cl_dsp_get_reg(cs40l26->dsp, cs40l26_dbc_params[dbc].name, + CL_DSP_XM_UNPACKED_TYPE, CS40L26_EXT_ALGO_ID, ®); + if (error) + return error; + + error = regmap_write(cs40l26->regmap, reg, write_val); + if (error) + dev_err(dev, "Failed to write Dynamic Boost Control value\n"); + + return error; +} +EXPORT_SYMBOL_GPL(cs40l26_dbc_set); + +int cs40l26_pm_timeout_ms_set(struct cs40l26_private *cs40l26, unsigned int dsp_state, + u32 timeout_ms) +{ + u32 reg, timeout_ticks; + unsigned int min; + int error; + + if (cs40l26->fw_loaded) { + error = cl_dsp_get_reg(cs40l26->dsp, "PM_TIMER_TIMEOUT_TICKS", + CL_DSP_XM_UNPACKED_TYPE, CS40L26_PM_ALGO_ID, ®); + if (error) + return error; + } else { + reg = cs40l26->rom_regs->pm_timeout_ticks; + } + + if (dsp_state == CS40L26_DSP_STATE_STANDBY) { + reg += CS40L26_PM_STDBY_TIMEOUT_OFFSET; + min = CS40L26_PM_STDBY_TIMEOUT_MS_MIN; + } else if (dsp_state == CS40L26_DSP_STATE_ACTIVE) { + reg += CS40L26_PM_ACTIVE_TIMEOUT_OFFSET; + min = CS40L26_PM_ACTIVE_TIMEOUT_MS_MIN; + } else { + dev_err(cs40l26->dev, "Invalid DSP state: %u\n", dsp_state); + return -EINVAL; + } + + if (timeout_ms > CS40L26_PM_TIMEOUT_MS_MAX) + timeout_ticks = CS40L26_PM_TIMEOUT_MS_MAX * CS40L26_PM_TICKS_PER_MS; + else if (timeout_ms < min) + timeout_ticks = min * CS40L26_PM_TICKS_PER_MS; + else + timeout_ticks = timeout_ms * CS40L26_PM_TICKS_PER_MS; + + error = regmap_write(cs40l26->regmap, reg, timeout_ticks); + if (error) + dev_err(cs40l26->dev, "Failed to set PM timeout: %d\n", error); + + return error; +} +EXPORT_SYMBOL_GPL(cs40l26_pm_timeout_ms_set); + +int cs40l26_pm_timeout_ms_get(struct cs40l26_private *cs40l26, unsigned int dsp_state, + u32 *timeout_ms) +{ + u32 reg, timeout_ticks; + int error; + + if (cs40l26->fw_loaded) { + error = cl_dsp_get_reg(cs40l26->dsp, "PM_TIMER_TIMEOUT_TICKS", + CL_DSP_XM_UNPACKED_TYPE, CS40L26_PM_ALGO_ID, ®); + if (error) + return error; + } else { + reg = cs40l26->rom_regs->pm_timeout_ticks; + } + + if (dsp_state == CS40L26_DSP_STATE_STANDBY) { + reg += CS40L26_PM_STDBY_TIMEOUT_OFFSET; + } else if (dsp_state == CS40L26_DSP_STATE_ACTIVE) { + reg += CS40L26_PM_ACTIVE_TIMEOUT_OFFSET; + } else { + dev_err(cs40l26->dev, "Invalid DSP state: %u\n", dsp_state); + return -EINVAL; + } + + error = regmap_read(cs40l26->regmap, reg, &timeout_ticks); + if (error) { + dev_err(cs40l26->dev, "Failed to get PM timeout: %d\n", error); + return error; + } + + *timeout_ms = timeout_ticks / CS40L26_PM_TICKS_PER_MS; + + return 0; +} +EXPORT_SYMBOL_GPL(cs40l26_pm_timeout_ms_get); + +static inline void cs40l26_pm_runtime_setup(struct cs40l26_private *cs40l26) +{ +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s+\n", __func__); +#endif + pm_runtime_mark_last_busy(cs40l26->dev); + pm_runtime_use_autosuspend(cs40l26->dev); + pm_runtime_set_autosuspend_delay(cs40l26->dev, CS40L26_AUTOSUSPEND_DELAY_MS); + pm_runtime_enable(cs40l26->dev); +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s-\n", __func__); +#endif +} + +static inline void cs40l26_pm_runtime_teardown(struct cs40l26_private *cs40l26) +{ +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s+ power.disable_depth=%d\n", + __func__, cs40l26->dev->power.disable_depth); + + if (cs40l26->dev->power.disable_depth > 0) + return; +#endif + pm_runtime_dont_use_autosuspend(cs40l26->dev); + pm_runtime_disable(cs40l26->dev); +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s-\n", __func__); +#endif +} + +static int cs40l26_check_pm_lock(struct cs40l26_private *cs40l26, bool *locked) +{ + unsigned int dsp_lock; + int error; + + error = regmap_read(cs40l26->regmap, cs40l26->rom_regs->pm_state_locks + + CS40L26_DSP_LOCK3_OFFSET, &dsp_lock); + if (error) + return error; + + if (dsp_lock & CS40L26_DSP_LOCK3_MASK) + *locked = true; + else + *locked = false; + + return 0; +} + +static void cs40l26_remove_asp_scaling(struct cs40l26_private *cs40l26) +{ + struct device *dev = cs40l26->dev; + u16 gain; + + if (cs40l26->asp_scale_pct >= CS40L26_GAIN_FULL_SCALE || !cs40l26->scaling_applied) + return; + + gain = cs40l26->gain_tmp; + + if (gain >= CS40L26_NUM_PCT_MAP_VALUES) { + dev_err(dev, "Gain %u%% out of bounds\n", gain); + return; + } + + cs40l26->gain_pct = gain; + cs40l26->scaling_applied = false; + + queue_work(cs40l26->vibe_workqueue, &cs40l26->set_gain_work); +} + +int cs40l26_pm_state_transition(struct cs40l26_private *cs40l26, enum cs40l26_pm_state state) +{ + struct device *dev = cs40l26->dev; + u32 cmd, he_time_cmd, he_time_cmd_payload; + u8 curr_state; + bool dsp_lock; + int error, i; + + cmd = (u32) CS40L26_DSP_MBOX_PM_CMD_BASE + state; + + switch (state) { + case CS40L26_PM_STATE_WAKEUP: + error = cs40l26_mailbox_write(cs40l26, cmd); + if (error) + return error; + + break; + case CS40L26_PM_STATE_PREVENT_HIBERNATE: + for (i = 0; i < CS40L26_DSP_STATE_ATTEMPTS; i++) { + error = cs40l26_mailbox_write(cs40l26, cmd); + if (error) + return error; + + error = cs40l26_dsp_state_get(cs40l26, &curr_state); + if (error) + return error; + + if (curr_state == CS40L26_DSP_STATE_ACTIVE) + break; + + if (curr_state == CS40L26_DSP_STATE_STANDBY) { + error = cs40l26_check_pm_lock(cs40l26, &dsp_lock); + if (error) + return error; + + if (dsp_lock) + break; + } + usleep_range(5000, 5100); + } + + if (i == CS40L26_DSP_STATE_ATTEMPTS) { + dev_err(cs40l26->dev, "DSP not starting\n"); + return -ETIMEDOUT; + } + + if (cs40l26->allow_hibernate_sent) { + /* + * send time elapsed since last ALLOW_HIBERNATE mailbox + * command to provide input to thermal model + */ + if (timer_pending(&cs40l26->hibernate_timer)) { + he_time_cmd_payload = ktime_to_ms(ktime_sub( + ktime_get_boottime(), + cs40l26->allow_hibernate_ts)); + if (he_time_cmd_payload > CS40L26_DSP_MBOX_HE_PAYLOAD_MAX_MS) + he_time_cmd_payload = CS40L26_DSP_MBOX_HE_PAYLOAD_OVERFLOW; + } else { + he_time_cmd_payload = + CS40L26_DSP_MBOX_HE_PAYLOAD_OVERFLOW; + } + + dev_dbg(dev, "HE_TIME payload, 0x%06X", + he_time_cmd_payload); + + he_time_cmd = CS40L26_DSP_MBOX_CMD_HE_TIME_BASE | + he_time_cmd_payload; + + error = cs40l26_dsp_write(cs40l26, + CS40L26_DSP_VIRTUAL1_MBOX_1, + he_time_cmd); + if (error) + return error; + } + + break; + case CS40L26_PM_STATE_ALLOW_HIBERNATE: + cs40l26->wksrc_sts = 0x00; + error = cs40l26_dsp_write(cs40l26, CS40L26_DSP_VIRTUAL1_MBOX_1, cmd); + if (error) + return error; + + cs40l26->allow_hibernate_sent = true; + + mod_timer(&cs40l26->hibernate_timer, jiffies + + msecs_to_jiffies(CS40L26_DSP_MBOX_HE_PAYLOAD_MAX_MS)); + + cs40l26->allow_hibernate_ts = ktime_get_boottime(); + + break; + case CS40L26_PM_STATE_SHUTDOWN: + cs40l26->wksrc_sts = 0x00; + error = cs40l26_mailbox_write(cs40l26, cmd); + if (error) + return error; + + break; + default: + dev_err(dev, "Invalid PM state: %u\n", state); + return -EINVAL; + } + + cs40l26->pm_state = state; +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + if (state <= CS40L26_PM_STATE_SHUTDOWN) + dev_info(dev, "%s PM state: %s\n", __func__, pm_state_strings[state]); +#endif + return 0; +} + +static int cs40l26_dsp_start(struct cs40l26_private *cs40l26) +{ + u8 dsp_state; + int error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s\n", __func__); +#endif + + error = regmap_write(cs40l26->regmap, CS40L26_DSP1_CCM_CORE_CONTROL, + CS40L26_DSP_CCM_CORE_RESET); + if (error) { + dev_err(cs40l26->dev, "Failed to reset DSP core\n"); + return error; + } + + error = cs40l26_dsp_state_get(cs40l26, &dsp_state); + if (error) + return error; + + if (dsp_state != CS40L26_DSP_STATE_ACTIVE && dsp_state != CS40L26_DSP_STATE_STANDBY) { + dev_err(cs40l26->dev, "Failed to wake DSP core\n"); + return -EINVAL; + } + + return 0; +} + +static int cs40l26_dsp_pre_config(struct cs40l26_private *cs40l26) +{ + u32 halo_state, timeout_ms; + u8 dsp_state; + int error, i; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s\n", __func__); +#endif + error = cs40l26_pm_state_transition(cs40l26, CS40L26_PM_STATE_PREVENT_HIBERNATE); + if (error) + return error; + + error = regmap_read(cs40l26->regmap, cs40l26->rom_regs->dsp_halo_state, &halo_state); + if (error) { + dev_err(cs40l26->dev, "Failed to get HALO state\n"); + return error; + } + + if (halo_state != CS40L26_DSP_HALO_STATE_RUN) { + dev_err(cs40l26->dev, "DSP not Ready: HALO_STATE: %08X\n", halo_state); + return -EINVAL; + } + + error = cs40l26_pm_timeout_ms_get(cs40l26, CS40L26_DSP_STATE_ACTIVE, &timeout_ms); + if (error) + return error; + + for (i = 0; i < CS40L26_DSP_SHUTDOWN_MAX_ATTEMPTS; i++) { + error = cs40l26_dsp_state_get(cs40l26, &dsp_state); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(cs40l26->dev, "DSP state get fail\n"); +#endif + return error; + } + + if (dsp_state != CS40L26_DSP_STATE_SHUTDOWN && + dsp_state != CS40L26_DSP_STATE_STANDBY) + dev_warn(cs40l26->dev, "DSP core not safe to kill\n"); + else + break; + + usleep_range(CS40L26_MS_TO_US(timeout_ms), CS40L26_MS_TO_US(timeout_ms) + 100); + } + + if (i == CS40L26_DSP_SHUTDOWN_MAX_ATTEMPTS) { + dev_err(cs40l26->dev, "DSP Core could not be shut down\n"); + return -EINVAL; + } + + error = regmap_write(cs40l26->regmap, CS40L26_DSP1_CCM_CORE_CONTROL, + CS40L26_DSP_CCM_CORE_KILL); + if (error) + dev_err(cs40l26->dev, "Failed to kill DSP core\n"); + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s done ret %d\n", __func__, error); +#endif + return error; +} + +static int cs40l26_mbox_buffer_read(struct cs40l26_private *cs40l26, u32 *val) +{ + struct regmap *regmap = cs40l26->regmap; + struct device *dev = cs40l26->dev; + u32 base, last, len, mbox_response, read_ptr, reg, status, write_ptr; + u32 buffer[CS40L26_DSP_MBOX_BUFFER_NUM_REGS]; + int error; + + error = cl_dsp_get_reg(cs40l26->dsp, "QUEUE_BASE", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_MAILBOX_ALGO_ID, ®); + if (error) + return error; + + error = regmap_bulk_read(regmap, reg, buffer, CS40L26_DSP_MBOX_BUFFER_NUM_REGS); + if (error) { + dev_err(dev, "Failed to read buffer contents\n"); + return error; + } + + base = buffer[0]; + len = buffer[1]; + write_ptr = buffer[2]; + read_ptr = buffer[3]; + last = base + ((len - 1) * CL_DSP_BYTES_PER_WORD); + + error = cl_dsp_get_reg(cs40l26->dsp, "STATUS", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_MAILBOX_ALGO_ID, ®); + if (error) + return error; + + error = regmap_read(regmap, reg, &status); + if (error) { + dev_err(dev, "Failed to read mailbox status\n"); + return error; + } + + if (status) { + dev_err(dev, "Mailbox status error: 0x%X\n", status); + return -ENOSPC; + } + + if (read_ptr == write_ptr) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "Reached end of queue\n"); +#else + dev_dbg(dev, "Reached end of queue\n"); +#endif + return 1; + } + + error = regmap_read(regmap, read_ptr, &mbox_response); + if (error) { + dev_err(dev, "Failed to read from mailbox buffer\n"); + return error; + } + + if (read_ptr == last) + read_ptr = base; + else + read_ptr += CL_DSP_BYTES_PER_WORD; + + error = cl_dsp_get_reg(cs40l26->dsp, "QUEUE_RD", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_MAILBOX_ALGO_ID, ®); + if (error) + return error; + + error = regmap_write(regmap, reg, read_ptr); + if (error) { + dev_err(dev, "Failed to update read pointer\n"); + return error; + } + + *val = mbox_response; + + return 0; +} + +static irqreturn_t cs40l26_handle_mbox_buffer(int irq, void *data) +{ + struct cs40l26_private *cs40l26 = data; + irqreturn_t irq_status = IRQ_HANDLED; + struct device *dev = cs40l26->dev; + u32 val = 0; + int error; + + mutex_lock(&cs40l26->lock); + + while (!cs40l26_mbox_buffer_read(cs40l26, &val)) { + if ((val & CS40L26_DSP_MBOX_CMD_INDEX_MASK) == CS40L26_DSP_MBOX_PANIC) { + dev_alert(dev, "DSP PANIC! Error condition: 0x%06X\n", + (u32) (val & CS40L26_DSP_MBOX_CMD_PAYLOAD_MASK)); + irq_status = IRQ_HANDLED; + goto err_mutex; + } + + if ((val & CS40L26_DSP_MBOX_CMD_INDEX_MASK) == CS40L26_DSP_MBOX_WATERMARK) { + dev_dbg(dev, "Mailbox: WATERMARK\n"); +#ifdef CONFIG_DEBUG_FS + error = cl_dsp_logger_update(cs40l26->cl_dsp_db); + if (error) { + irq_status = IRQ_NONE; + goto err_mutex; + } +#endif + continue; + } + + switch (val) { + case CS40L26_DSP_MBOX_COMPLETE_MBOX: +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "Mailbox: COMPLETE_MBOX\n"); +#else + dev_dbg(dev, "Mailbox: COMPLETE_MBOX\n"); +#endif + complete_all(&cs40l26->erase_cont); + cs40l26_vibe_state_update(cs40l26, CS40L26_VIBE_STATE_EVENT_MBOX_COMPLETE); + break; + case CS40L26_DSP_MBOX_COMPLETE_GPIO: +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "Mailbox: COMPLETE_GPIO\n"); +#else + dev_dbg(dev, "Mailbox: COMPLETE_GPIO\n"); +#endif + cs40l26_vibe_state_update(cs40l26, CS40L26_VIBE_STATE_EVENT_GPIO_COMPLETE); + break; + case CS40L26_DSP_MBOX_COMPLETE_I2S: +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "Mailbox: COMPLETE_I2S\n"); +#else + dev_dbg(dev, "Mailbox: COMPLETE_I2S\n"); +#endif + /* ASP is interrupted */ + if (cs40l26->asp_enable) + complete(&cs40l26->i2s_cont); + break; + case CS40L26_DSP_MBOX_TRIGGER_I2S: +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "Mailbox: TRIGGER_I2S\n"); +#else + dev_dbg(dev, "Mailbox: TRIGGER_I2S\n"); +#endif + complete(&cs40l26->i2s_cont); + break; + case CS40L26_DSP_MBOX_TRIGGER_CP: + if (!cs40l26->vibe_state_reporting) { + dev_err(dev, "vibe_state not supported\n"); + irq_status = IRQ_HANDLED; + goto err_mutex; + } + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "Mailbox: TRIGGER_CP\n"); +#else + dev_dbg(dev, "Mailbox: TRIGGER_CP\n"); +#endif + cs40l26_vibe_state_update(cs40l26, CS40L26_VIBE_STATE_EVENT_MBOX_PLAYBACK); + break; + case CS40L26_DSP_MBOX_TRIGGER_GPIO: +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "Mailbox: TRIGGER_GPIO\n"); +#else + dev_dbg(dev, "Mailbox: TRIGGER_GPIO\n"); +#endif + cs40l26_vibe_state_update(cs40l26, CS40L26_VIBE_STATE_EVENT_GPIO_TRIGGER); + break; + case CS40L26_DSP_MBOX_PM_AWAKE: + cs40l26->wksrc_sts |= CS40L26_WKSRC_STS_EN; +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "Mailbox: AWAKE\n"); +#else + dev_dbg(dev, "Mailbox: AWAKE\n"); +#endif + break; + case CS40L26_DSP_MBOX_F0_EST_START: +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "Mailbox: F0_EST_START\n"); +#else + dev_dbg(dev, "Mailbox: F0_EST_START\n"); +#endif + break; + case CS40L26_DSP_MBOX_F0_EST_DONE: +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "Mailbox: F0_EST_DONE\n"); + cs40l26->sec_vib_ddata.trigger_calibration = 0; +#else + dev_dbg(dev, "Mailbox: F0_EST_DONE\n"); +#endif + complete(&cs40l26->cal_f0_cont); + break; + case CS40L26_DSP_MBOX_REDC_EST_START: +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "Mailbox: REDC_EST_START\n"); +#else + dev_dbg(dev, "Mailbox: REDC_EST_START\n"); +#endif + break; + case CS40L26_DSP_MBOX_REDC_EST_DONE: +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "Mailbox: REDC_EST_DONE\n"); +#else + dev_dbg(dev, "Mailbox: REDC_EST_DONE\n"); +#endif + complete(&cs40l26->cal_redc_cont); + break; + case CS40L26_DSP_MBOX_LS_CALIBRATION_START: +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "Mailbox: LS_CALIBRATION_START\n"); +#else + dev_dbg(dev, "Mailbox: LS_CALIBRATION_START\n"); +#endif + break; + case CS40L26_DSP_MBOX_LS_CALIBRATION_DONE: +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "Mailbox: LS_CALIBRATION_DONE\n"); +#else + dev_dbg(dev, "Mailbox: LS_CALIBRATION_DONE\n"); +#endif + complete(&cs40l26->cal_ls_cont); + break; + case CS40L26_DSP_MBOX_LS_CALIBRATION_ERROR: + dev_warn(dev, "Mailbox: LS_CALIBRATION_ERROR\n"); + complete(&cs40l26->cal_ls_cont); + break; + case CS40L26_DSP_MBOX_LE_EST_START: +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "Mailbox: LE_EST_START\n"); +#else + dev_dbg(dev, "Mailbox: LE_EST_START\n"); +#endif + break; + case CS40L26_DSP_MBOX_LE_EST_DONE: +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "Mailbox: LE_EST_DONE\n"); +#else + dev_dbg(dev, "Mailbox: LE_EST_DONE\n"); +#endif + break; + case CS40L26_DSP_MBOX_PEQ_CALCULATION_START: +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "Mailbox: PEQ_CALCULATION_START\n"); +#else + dev_dbg(dev, "Mailbox: PEQ_CALCULATION_START\n"); +#endif + break; + case CS40L26_DSP_MBOX_PEQ_CALCULATION_DONE: +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "Mailbox: PEQ_CALCULATION_DONE\n"); +#else + dev_dbg(dev, "Mailbox: PEQ_CALCULATION_DONE\n"); +#endif + complete(&cs40l26->cal_dvl_peq_cont); + break; + case CS40L26_DSP_MBOX_SYS_ACK: + dev_err(dev, "Mailbox: ACK\n"); + irq_status = IRQ_HANDLED; + goto err_mutex; + default: + dev_err(dev, "MBOX buffer value (0x%X) is invalid\n", val); + irq_status = IRQ_HANDLED; + goto err_mutex; + } + } + +err_mutex: + mutex_unlock(&cs40l26->lock); + + return irq_status; +} + +int cs40l26_copy_f0_est_to_dvl(struct cs40l26_private *cs40l26) +{ + u32 reg, f0_measured_q9_14, global_sample_rate, normalized_f0_q1_23; + int error, sample_rate; + + /* Must be awake and under mutex lock */ + error = regmap_read(cs40l26->regmap, CS40L26_GLOBAL_SAMPLE_RATE, &global_sample_rate); + if (error) + return error; + + switch (global_sample_rate & CS40L26_GLOBAL_FS_MASK) { + case CS40L26_GLOBAL_FS_48K: + sample_rate = 48000; + break; + case CS40L26_GLOBAL_FS_96K: + sample_rate = 96000; + break; + default: + dev_warn(cs40l26->dev, "Invalid GLOBAL_FS, %08X", global_sample_rate); + return -EINVAL; + } + + error = cl_dsp_get_reg(cs40l26->dsp, "F0_EST", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_F0_EST_ALGO_ID, ®); + if (error) + return error; + + error = regmap_read(cs40l26->regmap, reg, &f0_measured_q9_14); + if (error) + return error; + + error = cl_dsp_get_reg(cs40l26->dsp, "LRA_NORM_F0", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_DVL_ALGO_ID, ®); + if (error) + return error; + + normalized_f0_q1_23 = (f0_measured_q9_14 << 9) / sample_rate; + + return regmap_write(cs40l26->regmap, reg, normalized_f0_q1_23); +} +EXPORT_SYMBOL_GPL(cs40l26_copy_f0_est_to_dvl); + +int cs40l26_asp_start(struct cs40l26_private *cs40l26) +{ + int error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + cs40l26->asp_scale_pct = sec_vib_inputff_get_ach_percent(&cs40l26->sec_vib_ddata); + dev_info(cs40l26->dev, "%s calls stop playback. asp_scale_pct : %d\n", + __func__, cs40l26->asp_scale_pct); +#endif + if (cs40l26->asp_scale_pct < CS40L26_GAIN_FULL_SCALE) + queue_work(cs40l26->vibe_workqueue, &cs40l26->set_gain_work); + + error = cs40l26_mailbox_write(cs40l26, CS40L26_STOP_PLAYBACK); + if (error) { + dev_err(cs40l26->dev, "Failed to stop playback before I2S start\n"); + return error; + } + + reinit_completion(&cs40l26->i2s_cont); + + return cs40l26_mailbox_write(cs40l26, CS40L26_DSP_MBOX_CMD_START_I2S); +} +EXPORT_SYMBOL_GPL(cs40l26_asp_start); + +void cs40l26_vibe_state_update(struct cs40l26_private *cs40l26, enum cs40l26_vibe_state_event event) +{ + if (!mutex_is_locked(&cs40l26->lock)) { + dev_err(cs40l26->dev, "%s must be called under mutex lock\n", __func__); + return; + } +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s: effects_in_flight = %d\n", __func__, cs40l26->effects_in_flight); +#else + dev_dbg(cs40l26->dev, "effects_in_flight = %d\n", cs40l26->effects_in_flight); +#endif + + switch (event) { + case CS40L26_VIBE_STATE_EVENT_MBOX_PLAYBACK: + case CS40L26_VIBE_STATE_EVENT_GPIO_TRIGGER: + cs40l26_remove_asp_scaling(cs40l26); + cs40l26->effects_in_flight = cs40l26->effects_in_flight <= 0 ? 1 : + cs40l26->effects_in_flight + 1; + break; + case CS40L26_VIBE_STATE_EVENT_MBOX_COMPLETE: + case CS40L26_VIBE_STATE_EVENT_GPIO_COMPLETE: + cs40l26->effects_in_flight = cs40l26->effects_in_flight <= 0 ? 0 : + cs40l26->effects_in_flight - 1; + if (cs40l26->effects_in_flight == 0 && cs40l26->asp_enable) + if (cs40l26_asp_start(cs40l26)) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(cs40l26->dev, "%s error -\n", __func__); +#endif + return; + } + break; + case CS40L26_VIBE_STATE_EVENT_ASP_START: + cs40l26->asp_enable = true; + break; + case CS40L26_VIBE_STATE_EVENT_ASP_STOP: + cs40l26_remove_asp_scaling(cs40l26); + cs40l26->asp_enable = false; + break; + default: + dev_err(cs40l26->dev, "Invalid vibe state event: %d\n", event); + break; + } + + if (cs40l26->effects_in_flight) + cs40l26->vibe_state = CS40L26_VIBE_STATE_HAPTIC; + else if (cs40l26->asp_enable) + cs40l26->vibe_state = CS40L26_VIBE_STATE_ASP; + else + cs40l26->vibe_state = CS40L26_VIBE_STATE_STOPPED; + + sysfs_notify(&cs40l26->dev->kobj, NULL, "vibe_state"); +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + if (event <= CS40L26_VIBE_STATE_EVENT_ASP_STOP) + dev_info(cs40l26->dev, "%s vib_state(%s)->(%s) effects_in_flight(%d) asp_enable(%d)\n", + __func__, vibe_state_events[event], vibe_state_strings[cs40l26->vibe_state], + cs40l26->effects_in_flight, cs40l26->asp_enable); +#endif + +} +EXPORT_SYMBOL_GPL(cs40l26_vibe_state_update); + +static int cs40l26_error_release(struct cs40l26_private *cs40l26, + unsigned int err_rls) +{ + struct regmap *regmap = cs40l26->regmap; + struct device *dev = cs40l26->dev; + u32 err_sts, err_cfg; + int error; + + error = regmap_read(regmap, CS40L26_ERROR_RELEASE, &err_sts); + if (error) { + dev_err(cs40l26->dev, "Failed to get error status\n"); + return error; + } + + err_cfg = err_sts & ~BIT(err_rls); + + error = regmap_write(cs40l26->regmap, CS40L26_ERROR_RELEASE, err_cfg); + if (error) { + dev_err(dev, "Actuator Safe Mode release sequence failed\n"); + return error; + } + + err_cfg |= BIT(err_rls); + + error = regmap_write(regmap, CS40L26_ERROR_RELEASE, err_cfg); + if (error) { + dev_err(dev, "Actuator Safe Mode release sequence failed\n"); + return error; + } + + err_cfg &= ~BIT(err_rls); + + error = regmap_write(cs40l26->regmap, CS40L26_ERROR_RELEASE, err_cfg); + if (error) + dev_err(dev, "Actuator Safe Mode release sequence failed\n"); + + return error; +} + +static int cs40l26_handle_pre_irq(void *irq_drv_data) +{ + struct cs40l26_private *cs40l26 = irq_drv_data; + unsigned int sts; + int error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + error = regmap_read(cs40l26->regmap, CS40L26_IRQ1_STATUS, &sts); + if (error) + goto err_pm; + + if (!(sts & CS40L26_IRQ_STATUS_MASK)) { + dev_err(cs40l26->dev, "IRQ1 asserted with no pending interrupts\n"); +#if IS_ENABLED(CONFIG_CS40L26_SAMSUNG_FEATURE) && IS_ENABLED(CONFIG_SEC_ABC) + /* If it occurs 25 times in 5 seconds, it is recognized as a problem */ + sec_abc_send_event("MODULE=vib@WARN=int_gnd_short"); +#endif + } + +err_pm: + cs40l26_pm_exit(cs40l26->dev); + + return error; +} + +static irqreturn_t cs40l26_gpio_rise(int irq, void *data) +{ + struct cs40l26_private *cs40l26 = data; + + mutex_lock(&cs40l26->lock); + + if (cs40l26->wksrc_sts & CS40L26_WKSRC_STS_EN) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "GPIO rising edge detected\n"); +#else + dev_dbg(cs40l26->dev, "GPIO rising edge detected\n"); +#endif + } + cs40l26->wksrc_sts |= CS40L26_WKSRC_STS_EN; + + mutex_unlock(&cs40l26->lock); + + return IRQ_HANDLED; +} + +static irqreturn_t cs40l26_gpio_fall(int irq, void *data) +{ + struct cs40l26_private *cs40l26 = data; + + mutex_lock(&cs40l26->lock); + + if (cs40l26->wksrc_sts & CS40L26_WKSRC_STS_EN) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "GPIO falling edge detected\n"); +#else + dev_dbg(cs40l26->dev, "GPIO falling edge detected\n"); +#endif + } + cs40l26->wksrc_sts |= CS40L26_WKSRC_STS_EN; + + mutex_unlock(&cs40l26->lock); + + return IRQ_HANDLED; +} + +static irqreturn_t cs40l26_wakesource_any(int irq, void *data) +{ + struct cs40l26_private *cs40l26 = data; + irqreturn_t irq_return = IRQ_HANDLED; + u32 reg, val; + int error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "Wakesource detected (ANY)\n"); +#else + dev_dbg(cs40l26->dev, "Wakesource detected (ANY)\n"); +#endif + + mutex_lock(&cs40l26->lock); + + error = regmap_read(cs40l26->regmap, CS40L26_PWRMGT_STS, &val); + if (error) { + dev_err(cs40l26->dev, "Failed to get Power Management Status\n"); + irq_return = IRQ_NONE; + goto mutex_exit; + } + + cs40l26->wksrc_sts = (u8) ((val & CS40L26_WKSRC_STS_MASK) >> + CS40L26_WKSRC_STS_SHIFT); + + error = cl_dsp_get_reg(cs40l26->dsp, "LAST_WAKESRC_CTL", + CL_DSP_XM_UNPACKED_TYPE, cs40l26->fw_id, ®); + if (error) { + irq_return = IRQ_NONE; + goto mutex_exit; + } + + error = regmap_read(cs40l26->regmap, reg, &val); + if (error) { + dev_err(cs40l26->dev, "Failed to read LAST_WAKESRC_CTL\n"); + irq_return = IRQ_NONE; + goto mutex_exit; + } + + cs40l26->last_wksrc_pol = (u8) (val & CS40L26_WKSRC_GPIO_POL_MASK); + +mutex_exit: + mutex_unlock(&cs40l26->lock); + + return irq_return; +} + +static irqreturn_t cs40l26_wakesource_gpio(int irq, void *data) +{ + struct cs40l26_private *cs40l26 = data; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "GPIO event woke device from hibernate\n"); +#else + dev_dbg(cs40l26->dev, "GPIO event woke device from hibernate\n"); +#endif + + mutex_lock(&cs40l26->lock); + + if (cs40l26->wksrc_sts & cs40l26->last_wksrc_pol) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "GPIO falling edge detected\n"); +#else + dev_dbg(cs40l26->dev, "GPIO falling edge detected\n"); +#endif + cs40l26->wksrc_sts |= CS40L26_WKSRC_STS_EN; + } else { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "GPIO rising edge detected\n"); +#else + dev_dbg(cs40l26->dev, "GPIO rising edge detected\n"); +#endif + } + + mutex_unlock(&cs40l26->lock); + + return IRQ_HANDLED; +} + +static irqreturn_t cs40l26_wakesource_iic(int irq, void *data) +{ + struct cs40l26_private *cs40l26 = data; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "I2C event woke device from hibernate\n"); +#else + dev_dbg(cs40l26->dev, "I2C event woke device from hibernate\n"); +#endif + + return IRQ_HANDLED; +} + +static irqreturn_t cs40l26_bst_ovp_err(int irq, void *data) +{ + struct cs40l26_private *cs40l26 = data; + + dev_err(cs40l26->dev, "BST overvolt. error\n"); + + return IRQ_RETVAL(!cs40l26_error_release(cs40l26, CS40L26_BST_OVP_ERR_RLS)); +} + +static irqreturn_t cs40l26_bst_uv_err(int irq, void *data) +{ + struct cs40l26_private *cs40l26 = data; + + dev_err(cs40l26->dev, "BST undervolt. error\n"); + + return IRQ_RETVAL(!cs40l26_error_release(cs40l26, CS40L26_BST_UVP_ERR_RLS)); +} + +static irqreturn_t cs40l26_bst_short(int irq, void *data) +{ + struct cs40l26_private *cs40l26 = data; + + dev_err(cs40l26->dev, "LBST short detected\n"); + + return IRQ_RETVAL(!cs40l26_error_release(cs40l26, CS40L26_BST_SHORT_ERR_RLS)); +} + +static irqreturn_t cs40l26_ipk_flag(int irq, void *data) +{ + struct cs40l26_private *cs40l26 = data; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "Current is being limited by LBST inductor\n"); +#else + dev_dbg(cs40l26->dev, "Current is being limited by LBST inductor\n"); +#endif + + return IRQ_HANDLED; +} + +static irqreturn_t cs40l26_temp_err(int irq, void *data) +{ + struct cs40l26_private *cs40l26 = data; + + dev_err(cs40l26->dev, "Die overtemperature error\n"); + + return IRQ_RETVAL(!cs40l26_error_release(cs40l26, CS40L26_TEMP_ERR_RLS)); +} + +static irqreturn_t cs40l26_amp_short(int irq, void *data) +{ + struct cs40l26_private *cs40l26 = data; + + dev_err(cs40l26->dev, "AMP short detected\n"); + + return IRQ_RETVAL(!cs40l26_error_release(cs40l26, CS40L26_AMP_SHORT_ERR_RLS)); +} + +static irqreturn_t cs40l26_vpbr_flag(int irq, void *data) +{ + struct cs40l26_private *cs40l26 = data; + + dev_err(cs40l26->dev, "VP voltage has dropped below brownout threshold\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t cs40l26_vpbr_att_clr(int irq, void *data) +{ + struct cs40l26_private *cs40l26 = data; + + dev_warn(cs40l26->dev, "Cleared attenuation applied by VP brownout event\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t cs40l26_vbbr_flag(int irq, void *data) +{ + struct cs40l26_private *cs40l26 = data; + + dev_err(cs40l26->dev, "VBST voltage has dropped below brownout threshold\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t cs40l26_vbst_att_clr(int irq, void *data) +{ + struct cs40l26_private *cs40l26 = data; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "Cleared attenuation caused by VBST brownout\n"); +#else + dev_dbg(cs40l26->dev, "Cleared attenuation caused by VBST brownout\n"); +#endif + + return IRQ_HANDLED; +} + +static const struct cs40l26_irq cs40l26_irqs[] = { + CS40L26_IRQ(GPIO1_RISE, "GPIO1 rise", cs40l26_gpio_rise), + CS40L26_IRQ(GPIO1_FALL, "GPIO1 fall", cs40l26_gpio_fall), + CS40L26_IRQ(GPIO2_RISE, "GPIO2 rise", cs40l26_gpio_rise), + CS40L26_IRQ(GPIO2_FALL, "GPIO2 fall", cs40l26_gpio_fall), + CS40L26_IRQ(GPIO3_RISE, "GPIO3 rise", cs40l26_gpio_rise), + CS40L26_IRQ(GPIO3_FALL, "GPIO3 fall", cs40l26_gpio_fall), + CS40L26_IRQ(GPIO4_RISE, "GPIO4 rise", cs40l26_gpio_rise), + CS40L26_IRQ(GPIO4_FALL, "GPIO4 fall", cs40l26_gpio_fall), + CS40L26_IRQ(WKSRC_STS_ANY, "Wakesource any", cs40l26_wakesource_any), + CS40L26_IRQ(WKSRC_STS_GPIO1, "Wakesource GPIO1", cs40l26_wakesource_gpio), + CS40L26_IRQ(WKSRC_STS_GPIO2, "Wakesource GPIO2", cs40l26_wakesource_gpio), + CS40L26_IRQ(WKSRC_STS_GPIO3, "Wakesource GPIO3", cs40l26_wakesource_gpio), + CS40L26_IRQ(WKSRC_STS_GPIO4, "Wakesource GPIO4", cs40l26_wakesource_gpio), + CS40L26_IRQ(WKSRC_STS_I2C, "Wakesource I2C", cs40l26_wakesource_iic), + CS40L26_IRQ(BST_OVP_ERR, "Boost overvoltage error", cs40l26_bst_ovp_err), + CS40L26_IRQ(BST_DCM_UVP_ERR, "Boost undervoltage error", cs40l26_bst_uv_err), + CS40L26_IRQ(BST_SHORT_ERR, "Boost short", cs40l26_bst_short), + CS40L26_IRQ(BST_IPK_FLAG, "Current limited", cs40l26_ipk_flag), + CS40L26_IRQ(TEMP_ERR, "Die overtemperature error", cs40l26_temp_err), + CS40L26_IRQ(AMP_ERR, "Amp short", cs40l26_amp_short), + CS40L26_IRQ(VIRTUAL2_MBOX_WR, "Mailbox interrupt", cs40l26_handle_mbox_buffer), + CS40L26_IRQ(VPBR_FLAG, "VP brownout", cs40l26_vpbr_flag), + CS40L26_IRQ(VPBR_ATT_CLR, "VPBR attenuation cleared", cs40l26_vpbr_att_clr), + CS40L26_IRQ(VBBR_FLAG, "VBST brownout", cs40l26_vbbr_flag), + CS40L26_IRQ(VBBR_ATT_CLR, "VBST attenuation cleared", cs40l26_vbst_att_clr), +}; + +static const struct regmap_irq cs40l26_reg_irqs[] = { + CS40L26_REG_IRQ(IRQ1_EINT_1, GPIO1_RISE), + CS40L26_REG_IRQ(IRQ1_EINT_1, GPIO1_FALL), + CS40L26_REG_IRQ(IRQ1_EINT_1, GPIO2_RISE), + CS40L26_REG_IRQ(IRQ1_EINT_1, GPIO2_FALL), + CS40L26_REG_IRQ(IRQ1_EINT_1, GPIO3_RISE), + CS40L26_REG_IRQ(IRQ1_EINT_1, GPIO3_FALL), + CS40L26_REG_IRQ(IRQ1_EINT_1, GPIO4_RISE), + CS40L26_REG_IRQ(IRQ1_EINT_1, GPIO4_FALL), + CS40L26_REG_IRQ(IRQ1_EINT_1, WKSRC_STS_ANY), + CS40L26_REG_IRQ(IRQ1_EINT_1, WKSRC_STS_GPIO1), + CS40L26_REG_IRQ(IRQ1_EINT_1, WKSRC_STS_GPIO2), + CS40L26_REG_IRQ(IRQ1_EINT_1, WKSRC_STS_GPIO3), + CS40L26_REG_IRQ(IRQ1_EINT_1, WKSRC_STS_GPIO4), + CS40L26_REG_IRQ(IRQ1_EINT_1, WKSRC_STS_I2C), + CS40L26_REG_IRQ(IRQ1_EINT_1, BST_OVP_ERR), + CS40L26_REG_IRQ(IRQ1_EINT_1, BST_DCM_UVP_ERR), + CS40L26_REG_IRQ(IRQ1_EINT_1, BST_SHORT_ERR), + CS40L26_REG_IRQ(IRQ1_EINT_1, BST_IPK_FLAG), + CS40L26_REG_IRQ(IRQ1_EINT_1, TEMP_ERR), + CS40L26_REG_IRQ(IRQ1_EINT_1, AMP_ERR), + CS40L26_REG_IRQ(IRQ1_EINT_1, VIRTUAL2_MBOX_WR), + CS40L26_REG_IRQ(IRQ1_EINT_2, VPBR_FLAG), + CS40L26_REG_IRQ(IRQ1_EINT_2, VPBR_ATT_CLR), + CS40L26_REG_IRQ(IRQ1_EINT_2, VBBR_FLAG), + CS40L26_REG_IRQ(IRQ1_EINT_2, VBBR_ATT_CLR), +}; + +static struct regmap_irq_chip cs40l26_regmap_irq_chip = { + .name = "cs40l26 IRQ1 Controller", + .status_base = CS40L26_IRQ1_EINT_1, + .mask_base = CS40L26_IRQ1_MASK_1, + .ack_base = CS40L26_IRQ1_EINT_1, + .num_regs = 2, + .irqs = cs40l26_reg_irqs, + .num_irqs = ARRAY_SIZE(cs40l26_reg_irqs), + .handle_pre_irq = cs40l26_handle_pre_irq, + .runtime_pm = true, +}; + +static struct cs40l26_pseq_op *cs40l26_pseq_op_format(struct cs40l26_private *cs40l26, + u32 addr, u32 data, u8 op_code) +{ + struct cs40l26_pseq_op *op; + + if (op_code != CS40L26_PSEQ_OP_WRITE_FULL) { + if (addr & CS40L26_PSEQ_INVALID_ADDR) { + dev_err(cs40l26->dev, "Invalid PSEQ address: 0x%08X\n", addr); + return ERR_PTR(-EINVAL); + } + } + + op = devm_kzalloc(cs40l26->dev, sizeof(struct cs40l26_pseq_op), GFP_KERNEL); + if (!op) + return ERR_PTR(-ENOMEM); + + op->operation = op_code; + op->words[0] = op_code << CS40L26_PSEQ_OP_SHIFT; + + switch (op_code) { + case CS40L26_PSEQ_OP_WRITE_FULL: + op->size = CS40L26_PSEQ_OP_WRITE_FULL_WORDS; + op->words[0] |= ((addr & CS40L26_PSEQ_WRITE_FULL_UPPER_ADDR_MASK) >> + CS40L26_PSEQ_WRITE_FULL_UPPER_ADDR_SHIFT); + op->words[1] = ((addr & CS40L26_PSEQ_WRITE_FULL_LOWER_ADDR_MASK) << + CS40L26_PSEQ_WRITE_FULL_LOWER_ADDR_SHIFT); + op->words[1] |= ((data & CS40L26_PSEQ_WRITE_FULL_UPPER_DATA_MASK) >> + CS40L26_PSEQ_WRITE_FULL_UPPER_DATA_SHIFT); + op->words[2] = data & CS40L26_PSEQ_WRITE_FULL_LOWER_DATA_MASK; + break; + case CS40L26_PSEQ_OP_WRITE_L16: + case CS40L26_PSEQ_OP_WRITE_H16: + op->size = CS40L26_PSEQ_OP_WRITE_X16_WORDS; + op->words[0] |= ((addr & CS40L26_PSEQ_WRITE_X16_UPPER_ADDR_MASK) >> + CS40L26_PSEQ_WRITE_X16_UPPER_ADDR_SHIFT); + op->words[1] = ((addr & CS40L26_PSEQ_WRITE_X16_LOWER_ADDR_MASK) << + CS40L26_PSEQ_WRITE_X16_LOWER_ADDR_SHIFT); + op->words[1] |= ((data & CS40L26_PSEQ_WRITE_X16_UPPER_DATA_MASK) >> + CS40L26_PSEQ_WRITE_X16_UPPER_DATA_SHIFT); + break; + case CS40L26_PSEQ_OP_WRITE_ADDR8: + op->size = CS40L26_PSEQ_OP_WRITE_ADDR8_WORDS; + op->words[0] |= ((addr & CS40L26_PSEQ_WRITE_ADDR8_ADDR_MASK) << + CS40L26_PSEQ_WRITE_ADDR8_ADDR_SHIFT); + op->words[0] |= ((data & CS40L26_PSEQ_WRITE_ADDR8_UPPER_DATA_MASK) >> + CS40L26_PSEQ_WRITE_ADDR8_UPPER_DATA_SHIFT); + op->words[1] = data & CS40L26_PSEQ_WRITE_ADDR8_LOWER_DATA_MASK; + break; + default: + dev_err(cs40l26->dev, "Invalid PSEQ Op. Code 0x%02X\n", op_code); + return ERR_PTR(-EINVAL); + } + + return op; +} + +static int cs40l26_pseq_find_end(struct cs40l26_private *cs40l26, struct cs40l26_pseq_op **op_end) +{ + u8 operation = 0; + struct cs40l26_pseq_op *op; + + list_for_each_entry(op, &cs40l26->pseq_op_head, list) { + operation = op->operation; + if (operation == CS40L26_PSEQ_OP_END) + break; + } + + if (operation != CS40L26_PSEQ_OP_END) { + dev_err(cs40l26->dev, "Failed to find PSEQ list terminator\n"); + return -ENOENT; + } + + *op_end = op; + + return 0; +} + +int cs40l26_pseq_write(struct cs40l26_private *cs40l26, u32 addr, + u32 data, bool update, u8 op_code) +{ + struct device *dev = cs40l26->dev; + bool is_new = true; + struct cs40l26_pseq_op *op, *op_new, *op_end; + int error; + + op_new = cs40l26_pseq_op_format(cs40l26, addr, data, op_code); + if (IS_ERR_OR_NULL(op_new)) + return op_new ? PTR_ERR(op_new) : -EINVAL; + + if (update) { + list_for_each_entry(op, &cs40l26->pseq_op_head, list) { + if (op->words[0] == op_new->words[0] && + (op->words[1] & CS40L26_PSEQ_OP_MASK) == + (op_new->words[1] & CS40L26_PSEQ_OP_MASK)) { + if (op->size != op_new->size) { + dev_err(dev, "Failed to replace PSEQ op.\n"); + error = -EINVAL; + goto op_new_free; + } + is_new = false; + break; + } + } + } + + error = cs40l26_pseq_find_end(cs40l26, &op_end); + if (error) + goto op_new_free; + + if (((CS40L26_PSEQ_MAX_WORDS * CL_DSP_BYTES_PER_WORD) - op_end->offset) + < (op_new->size * CL_DSP_BYTES_PER_WORD)) { + dev_err(dev, "Not enough space in pseq to add op\n"); + error = -ENOMEM; + goto op_new_free; + } + + if (is_new) { + op_new->offset = op_end->offset; + op_end->offset += (op_new->size * CL_DSP_BYTES_PER_WORD); + } else { + op_new->offset = op->offset; + } + + error = regmap_bulk_write(cs40l26->regmap, cs40l26->pseq_base + op_new->offset, + op_new->words, op_new->size); + if (error) { + dev_err(dev, "Failed to write PSEQ op.\n"); + goto op_new_free; + } + + if (is_new) { + error = regmap_bulk_write(cs40l26->regmap, cs40l26->pseq_base + op_end->offset, + op_end->words, op_end->size); + if (error) { + dev_err(dev, "Failed to write PSEQ terminator\n"); + goto op_new_free; + } + + list_add(&op_new->list, &cs40l26->pseq_op_head); + cs40l26->pseq_num_ops++; + } else { + list_replace(&op->list, &op_new->list); + } + + return 0; + +op_new_free: + devm_kfree(dev, op_new); + + return error; +} +EXPORT_SYMBOL_GPL(cs40l26_pseq_write); + +static int cs40l26_pseq_multi_write(struct cs40l26_private *cs40l26, + const struct reg_sequence *reg_seq, int num_regs, bool update, u8 op_code) +{ + int error, i; + + for (i = 0; i < num_regs; i++) { + error = cs40l26_pseq_write(cs40l26, reg_seq[i].reg, reg_seq[i].def, + update, op_code); + if (error) + return error; + } + + return 0; +} + +static int cs40l26_update_reg_defaults_via_pseq(struct cs40l26_private *cs40l26) +{ + struct device *dev = cs40l26->dev; + int error; + + error = cs40l26_pseq_write(cs40l26, CS40L26_NGATE1_INPUT, CS40L26_DATA_SRC_DSP1TX4, true, + CS40L26_PSEQ_OP_WRITE_L16); + if (error) + return error; + + /* set SPK_DEFAULT_HIZ to 1 */ + error = cs40l26_pseq_write(cs40l26, CS40L26_TST_DAC_MSM_CONFIG, + CS40L26_TST_DAC_MSM_CONFIG_DEFAULT_CHANGE_VALUE_H16, + true, CS40L26_PSEQ_OP_WRITE_H16); + if (error) + dev_err(dev, "Failed to sequence register default updates\n"); + + return error; +} + +static int cs40l26_pseq_init(struct cs40l26_private *cs40l26) +{ + struct cs40l26_pseq_op *pseq_op; + int i, num_words, error; + u8 operation; + u32 *words; + + INIT_LIST_HEAD(&cs40l26->pseq_op_head); + cs40l26->pseq_num_ops = 0; + + words = kcalloc(CS40L26_PSEQ_MAX_WORDS, CL_DSP_BYTES_PER_WORD, GFP_KERNEL); + if (IS_ERR_OR_NULL(words)) + return -ENOMEM; + + error = cl_dsp_get_reg(cs40l26->dsp, "POWER_ON_SEQUENCE", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_PM_ALGO_ID, &cs40l26->pseq_base); + if (error) + goto err_free; + + /* read pseq memory space */ + error = regmap_raw_read(cs40l26->regmap, cs40l26->pseq_base, words, + CS40L26_PSEQ_MAX_WORDS * CL_DSP_BYTES_PER_WORD); + if (error) + goto err_free; + + for (i = 0; i < CS40L26_PSEQ_MAX_WORDS; i++) + words[i] = be32_to_cpu(words[i]); + + for (i = 0; i < CS40L26_PSEQ_MAX_WORDS; i += num_words) { + operation = (words[i] & CS40L26_PSEQ_OP_MASK) >> CS40L26_PSEQ_OP_SHIFT; + + switch (operation) { + case CS40L26_PSEQ_OP_END: + num_words = CS40L26_PSEQ_OP_END_WORDS; + break; + case CS40L26_PSEQ_OP_WRITE_ADDR8: + num_words = CS40L26_PSEQ_OP_WRITE_ADDR8_WORDS; + break; + case CS40L26_PSEQ_OP_WRITE_H16: + case CS40L26_PSEQ_OP_WRITE_L16: + num_words = CS40L26_PSEQ_OP_WRITE_X16_WORDS; + break; + case CS40L26_PSEQ_OP_WRITE_FULL: + num_words = CS40L26_PSEQ_OP_WRITE_FULL_WORDS; + break; + default: + dev_err(cs40l26->dev, "Invalid OP code 0x%02X\n", operation); + error = -EINVAL; + goto err_free; + } + + pseq_op = devm_kzalloc(cs40l26->dev, sizeof(struct cs40l26_pseq_op), GFP_KERNEL); + if (IS_ERR_OR_NULL(pseq_op)) { + error = -ENOMEM; + goto err_free; + } + + memcpy(pseq_op->words, &words[i], num_words * CL_DSP_BYTES_PER_WORD); + pseq_op->size = num_words; + pseq_op->offset = i * CL_DSP_BYTES_PER_WORD; + pseq_op->operation = operation; + list_add(&pseq_op->list, &cs40l26->pseq_op_head); + + cs40l26->pseq_num_ops++; + + if (operation == CS40L26_PSEQ_OP_END) + break; + } + + if (operation != CS40L26_PSEQ_OP_END) { + dev_err(cs40l26->dev, "PSEQ_END_OF_SCRIPT not found\n"); + error = -ENOENT; + goto err_free; + } + + error = cs40l26_update_reg_defaults_via_pseq(cs40l26); + +err_free: + kfree(words); + + return error; +} + +static int cs40l26_irq_update_mask(struct cs40l26_private *cs40l26, u32 reg, u32 val, u32 bit_mask) +{ + u32 eint_reg, cur_mask, new_mask; + int error; + + if (reg == CS40L26_IRQ1_MASK_1) { + eint_reg = CS40L26_IRQ1_EINT_1; + } else if (reg == CS40L26_IRQ1_MASK_2) { + eint_reg = CS40L26_IRQ1_EINT_2; + } else { + dev_err(cs40l26->dev, "Invalid IRQ mask reg: 0x%08X\n", reg); + return -EINVAL; + } + + error = regmap_read(cs40l26->regmap, reg, &cur_mask); + if (error) { + dev_err(cs40l26->dev, "Failed to get IRQ mask\n"); + return error; + } + + new_mask = (cur_mask & ~bit_mask) | val; + + /* Clear interrupt prior to masking/unmasking */ + error = regmap_write(cs40l26->regmap, eint_reg, bit_mask); + if (error) { + dev_err(cs40l26->dev, "Failed to clear IRQ\n"); + return error; + } + + error = regmap_write(cs40l26->regmap, reg, new_mask); + if (error) { + dev_err(cs40l26->dev, "Failed to update IRQ mask\n"); + return error; + } + + if (bit_mask & GENMASK(31, 16)) { + error = cs40l26_pseq_write(cs40l26, reg, (new_mask & GENMASK(31, 16)) >> 16, + true, CS40L26_PSEQ_OP_WRITE_H16); + if (error) { + dev_err(cs40l26->dev, "Failed to update IRQ mask H16"); + return error; + } + } + + if (bit_mask & GENMASK(15, 0)) { + error = cs40l26_pseq_write(cs40l26, reg, (new_mask & GENMASK(15, 0)), + true, CS40L26_PSEQ_OP_WRITE_L16); + if (error) { + dev_err(cs40l26->dev, "Failed to update IRQ mask L16"); + return error; + } + } + + return error; +} + +static int cs40l26_map_gpi_to_haptic(struct cs40l26_private *cs40l26, struct ff_effect *effect, + struct cs40l26_uploaded_effect *ueffect) +{ + u8 gpio = (effect->trigger.button & CS40L26_BTN_NUM_MASK) >> CS40L26_BTN_NUM_SHIFT; + bool edge, ev_handler_bank_ram, owt, use_timeout; + unsigned int fw_rev; + u32 reg, write_val; + int error; + + edge = (effect->trigger.button & CS40L26_BTN_EDGE_MASK) >> CS40L26_BTN_EDGE_SHIFT; + + switch (ueffect->wvfrm_bank) { + case CS40L26_RAM_BANK_ID: + case CS40L26_BUZ_BANK_ID: + owt = false; + ev_handler_bank_ram = true; + break; + case CS40L26_ROM_BANK_ID: + owt = false; + ev_handler_bank_ram = false; + break; + case CS40L26_OWT_BANK_ID: + owt = true; + ev_handler_bank_ram = true; + break; + default: + dev_err(cs40l26->dev, "Effect bank %u not supported\n", ueffect->wvfrm_bank); + return -EINVAL; + } + + if (gpio != CS40L26_GPIO1) { + dev_err(cs40l26->dev, "GPIO%u not supported on 0x%02X\n", gpio, cs40l26->revid); + return -EINVAL; + } + + reg = cs40l26->event_map_base + (edge ? 0 : 4); + write_val = (ueffect->trigger_index & CS40L26_BTN_INDEX_MASK) | + (ev_handler_bank_ram << CS40L26_BTN_BANK_SHIFT) | + (owt << CS40L26_BTN_OWT_SHIFT); + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s\n", __func__); +#endif + + error = regmap_write(cs40l26->regmap, reg, write_val); + if (error) { + dev_err(cs40l26->dev, "Failed to update event map\n"); + return error; + } + + error = cl_dsp_fw_rev_get(cs40l26->dsp, &fw_rev); + if (error) + return error; + + use_timeout = (!cs40l26->calib_fw && fw_rev >= CS40L26_FW_GPI_TIMEOUT_MIN_REV) || + (cs40l26->calib_fw && fw_rev >= CS40L26_FW_GPI_TIMEOUT_CALIB_MIN_REV); + + if (use_timeout) { + error = cl_dsp_get_reg(cs40l26->dsp, "TIMEOUT_GPI_MS", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_VIBEGEN_ALGO_ID, ®); + if (error) + return error; + + error = regmap_write(cs40l26->regmap, reg, effect->replay.length); + if (error) + dev_warn(cs40l26->dev, "Failed to set GPI timeout, continuing...\n"); + } + + if (edge) + ueffect->mapping = CS40L26_GPIO_MAP_A_PRESS; + else + ueffect->mapping = CS40L26_GPIO_MAP_A_RELEASE; + + return error; +} + +static struct cs40l26_uploaded_effect *cs40l26_uploaded_effect_find(struct cs40l26_private *cs40l26, + int id) +{ + struct list_head *head = &cs40l26->effect_head; + int uid = -1; + struct cs40l26_uploaded_effect *ueffect; + + if (list_empty(head)) { + dev_dbg(cs40l26->dev, "Effect list is empty\n"); + return ERR_PTR(-ENODATA); + } + + list_for_each_entry(ueffect, head, list) { + uid = ueffect->id; + if (uid == id) + break; + } + + if (uid != id) { + dev_dbg(cs40l26->dev, "No such effect (ID = %d)\n", id); + return ERR_PTR(-EINVAL); + } + + return ueffect; +} + +static struct cs40l26_buzzgen_config cs40l26_buzzgen_configs[] = { + { + .duration_name = "BUZZ_EFFECTS2_BUZZ_DURATION", + .freq_name = "BUZZ_EFFECTS2_BUZZ_FREQ", + .level_name = "BUZZ_EFFECTS2_BUZZ_LEVEL", + .effect_id = -1 + }, + { + .duration_name = "BUZZ_EFFECTS3_BUZZ_DURATION", + .freq_name = "BUZZ_EFFECTS3_BUZZ_FREQ", + .level_name = "BUZZ_EFFECTS3_BUZZ_LEVEL", + .effect_id = -1 + }, + { + .duration_name = "BUZZ_EFFECTS4_BUZZ_DURATION", + .freq_name = "BUZZ_EFFECTS4_BUZZ_FREQ", + .level_name = "BUZZ_EFFECTS4_BUZZ_LEVEL", + .effect_id = -1 + }, + { + .duration_name = "BUZZ_EFFECTS5_BUZZ_DURATION", + .freq_name = "BUZZ_EFFECTS5_BUZZ_FREQ", + .level_name = "BUZZ_EFFECTS5_BUZZ_LEVEL", + .effect_id = -1 + }, + { + .duration_name = "BUZZ_EFFECTS6_BUZZ_DURATION", + .freq_name = "BUZZ_EFFECTS6_BUZZ_FREQ", + .level_name = "BUZZ_EFFECTS6_BUZZ_LEVEL", + .effect_id = -1 + }, +}; + +static int cs40l26_buzzgen_find_slot(struct cs40l26_private *cs40l26, int id) +{ + int i, slot = -1; + + for (i = CS40L26_BUZZGEN_NUM_CONFIGS - 1; i >= 0; i--) { + if (cs40l26_buzzgen_configs[i].effect_id == id) { + slot = i; + break; + } else if (cs40l26_buzzgen_configs[i].effect_id == -1) { + slot = i; + } + } + + return slot; +} + +static int cs40l26_erase_buzzgen(struct cs40l26_private *cs40l26, int id) +{ + int slot = cs40l26_buzzgen_find_slot(cs40l26, id); + + if (slot == -1) { + dev_err(cs40l26->dev, "Failed to erase BUZZGEN config for id %d\n", id); + return -EINVAL; + } + + cs40l26_buzzgen_configs[slot].effect_id = -1; + + return 0; +} + +static bool cs40l26_is_no_wait_ram_index(struct cs40l26_private *cs40l26, + u32 index) +{ + int i; + + for (i = 0; i < cs40l26->num_no_wait_ram_indices; i++) { + if (cs40l26->no_wait_ram_indices[i] == index) + return true; + } + + return false; +} + +static void cs40l26_set_gain_worker(struct work_struct *work) +{ + struct cs40l26_private *cs40l26 = container_of(work, struct cs40l26_private, set_gain_work); + int error; + u16 gain; + u32 reg; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + if (cs40l26->busy_state) { + pr_info("%s - f/w is busy\n", __func__); + return; + } +#endif + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return; + + mutex_lock(&cs40l26->lock); + + if (cs40l26->vibe_state == CS40L26_VIBE_STATE_ASP) { + gain = (cs40l26->asp_scale_pct * cs40l26->gain_pct) / CS40L26_GAIN_FULL_SCALE; + cs40l26->gain_tmp = cs40l26->gain_pct; + cs40l26->gain_pct = gain; + cs40l26->scaling_applied = true; + } else { + gain = cs40l26->gain_pct; + } +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s: gain = %u%%\n", __func__, gain); +#else + dev_dbg(cs40l26->dev, "%s: gain = %u%%\n", __func__, gain); +#endif + + /* Write Q21.2 value to SOURCE_ATTENUATION */ + error = cl_dsp_get_reg(cs40l26->dsp, "SOURCE_ATTENUATION", + CL_DSP_XM_UNPACKED_TYPE, CS40L26_EXT_ALGO_ID, ®); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(cs40l26->dev, "Failed to get Source Attenuation\n"); +#endif + goto err_mutex; + } + + error = regmap_write(cs40l26->regmap, reg, cs40l26_attn_q21_2_vals[gain]); + if (error) + dev_err(cs40l26->dev, "Failed to set attenuation\n"); + +err_mutex: + mutex_unlock(&cs40l26->lock); + cs40l26_pm_exit(cs40l26->dev); +} + +static void cs40l26_vibe_start_worker(struct work_struct *work) +{ + struct cs40l26_private *cs40l26 = container_of(work, struct cs40l26_private, + vibe_start_work); + struct device *dev = cs40l26->dev; + struct cs40l26_uploaded_effect *ueffect; + struct ff_effect *effect; + unsigned int reg; + u16 duration; + bool invert; + int error; + + dev_dbg(dev, "%s\n", __func__); + + error = cs40l26_pm_enter(dev); + if (error) + return; + + mutex_lock(&cs40l26->lock); + + effect = cs40l26->trigger_effect; + + ueffect = cs40l26_uploaded_effect_find(cs40l26, effect->id); + if (IS_ERR_OR_NULL(ueffect)) { + dev_err(dev, "No such effect to play back\n"); + goto err_mutex; + } + + duration = effect->replay.length; +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "%s CS40L26_START_PLAYBACK duration = %dms\n", + __func__, duration); +#endif + error = cl_dsp_get_reg(cs40l26->dsp, "TIMEOUT_MS", + CL_DSP_XM_UNPACKED_TYPE, CS40L26_VIBEGEN_ALGO_ID, ®); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(dev, "%s cs40l26 read fail(%d)\n", __func__, error); +#endif + goto err_mutex; + } + + error = regmap_write(cs40l26->regmap, reg, duration); + if (error) { + dev_err(dev, "Failed to set TIMEOUT_MS\n"); + goto err_mutex; + } + + error = cl_dsp_get_reg(cs40l26->dsp, "SOURCE_INVERT", + CL_DSP_XM_UNPACKED_TYPE, CS40L26_EXT_ALGO_ID, ®); + if (error) + goto err_mutex; + + switch (effect->direction) { + case 0x0000: + invert = false; + break; + case 0x8000: + invert = true; + break; + default: + dev_err(dev, "Invalid ff_effect direction: 0x%X\n", effect->direction); + goto err_mutex; + } + + error = regmap_write(cs40l26->regmap, reg, invert); + if (error) + goto err_mutex; + + switch (effect->u.periodic.waveform) { + case FF_CUSTOM: + case FF_SINE: + error = cs40l26_mailbox_write(cs40l26, ueffect->trigger_index); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(dev, "%s: cs40l26 write fail(%d)\n", __func__, error); +#endif + goto err_mutex; + } + + cs40l26->cur_index = ueffect->trigger_index; + break; + default: + dev_err(dev, "Invalid waveform type: 0x%X\n", effect->u.periodic.waveform); + goto err_mutex; + } + + if (!cs40l26->vibe_state_reporting) + cs40l26_vibe_state_update(cs40l26, CS40L26_VIBE_STATE_EVENT_MBOX_PLAYBACK); + + reinit_completion(&cs40l26->erase_cont); +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(dev); +} + +static void cs40l26_vibe_stop_worker(struct work_struct *work) +{ + struct cs40l26_private *cs40l26 = container_of(work, struct cs40l26_private, + vibe_stop_work); + bool skip_delay; + u32 delay_us; + int error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s vib_state(%s)\n", + __func__, vibe_state_strings[cs40l26->vibe_state]); +#else + dev_dbg(cs40l26->dev, "%s\n", __func__); +#endif + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return; + + mutex_lock(&cs40l26->lock); + + delay_us = cs40l26->delay_before_stop_playback_us; + skip_delay = cs40l26_is_no_wait_ram_index(cs40l26, cs40l26->cur_index); + + if (delay_us && !skip_delay) { + mutex_unlock(&cs40l26->lock); + + dev_info(cs40l26->dev, "Applying delay\n"); + + /* wait for SVC init phase to complete */ + usleep_range(delay_us, delay_us + 100); + + mutex_lock(&cs40l26->lock); + } else { + dev_info(cs40l26->dev, "Skipping delay\n"); + } + + if (skip_delay) { + dev_dbg(cs40l26->dev, "Stop command skipped\n"); + goto mutex_exit; + } + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s CS40L26_STOP_PLAYBACK\n", __func__); +#endif + error = cs40l26_mailbox_write(cs40l26, CS40L26_STOP_PLAYBACK); + if (error) + dev_err(cs40l26->dev, "Failed to stop playback\n"); + +mutex_exit: + mutex_unlock(&cs40l26->lock); + cs40l26_pm_exit(cs40l26->dev); +} + +/* This function made by cirrus, samsung moved location for samsung_hw_reset() */ +static int cs40l26_part_num_resolve(struct cs40l26_private *cs40l26) +{ + struct regmap *regmap = cs40l26->regmap; + struct device *dev = cs40l26->dev; + u32 devid, revid, fullid; + int error; + + error = regmap_read(regmap, CS40L26_DEVID, &devid); + if (error) { + dev_err(dev, "Failed to read device ID\n"); + return error; + } + + error = regmap_read(regmap, CS40L26_REVID, &revid); + if (error) { + dev_err(dev, "Failed to read revision ID\n"); + return error; + } + + devid &= CS40L26_DEVID_MASK; + revid &= CS40L26_REVID_MASK; + fullid = (devid << 8) | revid; + + switch (fullid) { + case CS40L26_ID_L26A_A1: + case CS40L26_ID_L26B_A1: + case CS40L26_ID_L27A_A1: + case CS40L26_ID_L27B_A1: + case CS40L26_ID_L26A_B0: + case CS40L26_ID_L26B_B0: + case CS40L26_ID_L27A_B0: + case CS40L26_ID_L27B_B0: + case CS40L26_ID_L27A_B1: + cs40l26->rom_regs = &cs40l26_rom_regs_a1_b0_b1; + break; + case CS40L26_ID_L27A_B2: + cs40l26->rom_regs = &cs40l26_rom_regs_b2; + break; + default: + dev_err(dev, "Invalid ID: 0x%06X 0x%02X\n", devid, revid); + return -EINVAL; + } + + cs40l26->devid = devid; + cs40l26->revid = revid; + + dev_info(dev, "Cirrus Logic %s ID: 0x%06X, Revision: 0x%02X\n", + CS40L26_DEV_NAME, cs40l26->devid, cs40l26->revid); + + return 0; +} + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE +__visible_for_testing bool samsung_is_valid_vmon(struct cs40l26_private *cs40l26, u32 vmon) +{ + u32 num; + int pass_val; + + pass_val = cs40l26->asp_scale_pct * VMON_100_MV / 100; + if (pass_val < VMON_20_MV) + pass_val = VMON_20_MV; + + /* num = VMON/(2^23-1)*12.3V*1000 is between 80mV to 120mV */ + if (vmon > pass_val - VMON_20_MV && vmon < pass_val + VMON_20_MV) { + num = vmon * 12300 / 8388607; + dev_info(cs40l26->dev, "%s, num : %umV\n", __func__, num); + return true; + } else + dev_info(cs40l26->dev, "vmon is out of range\n"); + return false; +} + +static int samsung_get_i2s_test(struct input_dev *dev) +{ + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + struct cs40l26_private *cs40l26 = ddata->private_data; + int error = 0; + u32 vmon; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + error = regmap_read(cs40l26->regmap, CS40L26_SPKMON_VMON_DEC_OUT_DATA, + &vmon); + if (error) { + dev_err(cs40l26->dev, "Failed to get VMON Data for I2S\n"); + goto pm_err; + } + + if (vmon & CS40L26_VMON_OVFL_FLAG_MASK) { + dev_err(cs40l26->dev, "I2S VMON overflow detected\n"); + error = -EOVERFLOW; + goto pm_err; + } + + vmon &= CS40L26_VMON_DEC_OUT_DATA_MASK; + + if (samsung_is_valid_vmon(cs40l26, vmon)) + error = 1; + dev_info(cs40l26->dev, "%s, vmon : %u, ret : %d done\n", __func__, vmon, error); +pm_err: + cs40l26_pm_exit(cs40l26->dev); + + return error; +} + +static int samsung_set_trigger_cal(struct input_dev *dev, u32 val) +{ + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + struct cs40l26_private *cs40l26 = ddata->private_data; + u32 mailbox_command; + int error; + struct completion *completion; + + if (val < 1 || val > 2) { + dev_err(cs40l26->dev, "%s: %u is out of range\n", __func__, val); + return -EINVAL; + } + + if (!cs40l26->calib_fw) { + dev_err(cs40l26->dev, "Must use calibration firmware\n"); + return -EPERM; + } + + switch (val) { + case CS40L26_CALIBRATION_CONTROL_REQUEST_F0_AND_Q: + completion = &cs40l26->cal_f0_cont; + break; + case CS40L26_CALIBRATION_CONTROL_REQUEST_REDC: + completion = &cs40l26->cal_redc_cont; + break; + case CS40L26_CALIBRATION_CONTROL_REQUEST_DVL_PEQ: + completion = &cs40l26->cal_dvl_peq_cont; + break; + case CS40L26_CALIBRATION_CONTROL_REQUEST_LS_CALIBRATION: + completion = &cs40l26->cal_ls_cont; + break; + default: + return -EINVAL; + } + + mailbox_command = ((CS40L26_DSP_MBOX_CMD_INDEX_CALIBRATION_CONTROL << + CS40L26_DSP_MBOX_CMD_INDEX_SHIFT) & CS40L26_DSP_MBOX_CMD_INDEX_MASK) | + (val & CS40L26_DSP_MBOX_CMD_PAYLOAD_MASK); + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + reinit_completion(completion); + + error = cs40l26_mailbox_write(cs40l26, mailbox_command); + + mutex_unlock(&cs40l26->lock); + + if (error) { + dev_err(cs40l26->dev, "Failed to request calibration\n"); + goto err_pm; + } + + if (!wait_for_completion_timeout( + completion, + msecs_to_jiffies(CS40L26_CALIBRATION_TIMEOUT_MS))) { + error = -ETIME; + dev_err(cs40l26->dev, "Failed to complete cal req, %d, err: %d", + val, error); + goto err_pm; + } + + mutex_lock(&cs40l26->lock); + + if (val == CS40L26_CALIBRATION_CONTROL_REQUEST_F0_AND_Q) + error = cs40l26_copy_f0_est_to_dvl(cs40l26); + + mutex_unlock(&cs40l26->lock); + +err_pm: + cs40l26_pm_exit(cs40l26->dev); + return error; +} + +static u32 samsung_get_f0_measured(struct input_dev *dev) +{ + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + struct cs40l26_private *cs40l26 = ddata->private_data; + u32 reg, f0_measured; + int error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "F0_EST", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_F0_EST_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_read(cs40l26->regmap, reg, &f0_measured); + if (error) + goto err_mutex; + + dev_info(cs40l26->dev, "%s: f0_measured : %u", __func__, f0_measured); +err_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cs40l26->dev); + + if (error) { + dev_err(cs40l26->dev, "%s is return error : %d\n", __func__, error); + return error; + } else + return f0_measured; +} + +static int samsung_get_f0_offset(struct input_dev *dev) +{ + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + struct cs40l26_private *cs40l26 = ddata->private_data; + + if (cs40l26->pdata.f0_offset) { + dev_info(cs40l26->dev, "%s: f0_offset : 0x%08X", __func__, cs40l26->pdata.f0_offset); + return cs40l26->pdata.f0_offset; + } + return 0; +} + +static u32 samsung_set_f0_stored(struct input_dev *dev, u32 val) +{ + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + struct cs40l26_private *cs40l26 = ddata->private_data; + u32 reg; + int error; + + if (val < CS40L26_SAMSUNG_F0_MIN || val > CS40L26_SAMSUNG_F0_MAX) { + dev_err(cs40l26->dev, "%s: %u is out of range\n", __func__, val); + return -EINVAL; + } + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cl_dsp_get_reg(cs40l26->dsp, "F0_OTP_STORED", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_VIBEGEN_ALGO_ID, ®); + if (error) + goto err_mutex; + + error = regmap_write(cs40l26->regmap, reg, val); + if (error) + goto err_mutex; + + dev_info(cs40l26->dev, "%s: f0 val : %u", __func__, val); +err_mutex: + mutex_unlock(&cs40l26->lock); + cs40l26_pm_exit(cs40l26->dev); + + if (error) + dev_err(cs40l26->dev, "%s is return error : %d\n", __func__, error); + return error; +} + +static int samsung_get_le_est(struct input_dev *dev, u32 *le) +{ + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + struct cs40l26_private *cs40l26 = ddata->private_data; + int error; + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + + error = cs40l26_svc_le_estimate(cs40l26, le); + if (error) + dev_err(cs40l26->dev, "%s is return error : %d\n", __func__, error); + + mutex_unlock(&cs40l26->lock); + cs40l26_pm_exit(cs40l26->dev); + + return error; +} + + +static unsigned int samsung_get_le_stored(struct input_dev *dev) +{ + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + struct cs40l26_private *cs40l26 = ddata->private_data; + unsigned int le; + + mutex_lock(&cs40l26->lock); + le = cs40l26->svc_le_est_stored; + mutex_unlock(&cs40l26->lock); + + return le; +} + +static int samsung_set_le_stored(struct input_dev *dev, u32 val) +{ + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + struct cs40l26_private *cs40l26 = ddata->private_data; + + mutex_lock(&cs40l26->lock); + cs40l26->svc_le_est_stored = val; + mutex_unlock(&cs40l26->lock); + + return 0; +} + +static const char *samsung_get_owt_lib_compat_version(struct input_dev *dev) +{ + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + struct cs40l26_private *cs40l26 = ddata->private_data; + + return cs40l26->pdata.owt_lib_compat_version; +} + +static const char *samsung_get_ap_chipset(struct input_dev *dev) +{ + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + struct cs40l26_private *cs40l26 = ddata->private_data; + + return cs40l26->pdata.ap_chipset; +} + +static int samsung_set_use_sep_index(struct input_dev *dev, bool use_sep_index) +{ + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + struct cs40l26_private *cs40l26 = ddata->private_data; + + dev_info(cs40l26->dev, "%s +\n", __func__); + + mutex_lock(&cs40l26->lock); + + cs40l26->use_sep_index = use_sep_index; + + mutex_unlock(&cs40l26->lock); + + dev_info(cs40l26->dev, "%s -\n", __func__); + + return 0; +} + +static int samsung_hw_reset(struct cs40l26_private *cs40l26) +{ + int error; + + dev_info(cs40l26->dev, "HW Reset\n"); + gpiod_set_value_cansleep(cs40l26->reset_gpio, 1); + msleep(500); + gpiod_set_value_cansleep(cs40l26->reset_gpio, 0); + usleep_range(CS40L26_CONTROL_PORT_READY_DELAY, + CS40L26_CONTROL_PORT_READY_DELAY + 100); + error = cs40l26_part_num_resolve(cs40l26); + if (error) { + dev_err(cs40l26->dev, "Failed to part num resolve(%d)\n", error); + return error; + } + /* Set LRA to high-z to avoid fault conditions */ + error = regmap_update_bits(cs40l26->regmap, CS40L26_TST_DAC_MSM_CONFIG, + CS40L26_SPK_DEFAULT_HIZ_MASK, 1 << + CS40L26_SPK_DEFAULT_HIZ_SHIFT); + if (error) { + dev_err(cs40l26->dev, "Failed to update reg defaults(%d)\n", error); + return error; + } + return 0; +} + +static int samsung_fw_load(struct input_dev *dev, unsigned int fw_id) +{ + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + struct cs40l26_private *cs40l26 = ddata->private_data; + int error = 0; + + cs40l26->busy_state = 1; + if (fw_id == 0) + error = cs40l26_fw_swap(cs40l26, CS40L26_FW_ID); + else if (fw_id == 1) + error = cs40l26_fw_swap(cs40l26, CS40L26_FW_CALIB_ID); + else + error = -EINVAL; + + if (error) { + dev_err(cs40l26->dev, "%s: retry(%d), fail(%d)", __func__, + cs40l26->sec_vib_ddata.fw.retry, error); + cs40l26->fw_id = 0; + if (samsung_hw_reset(cs40l26)) + dev_err(cs40l26->dev, "%s HW Reset Failed\n", __func__); + } + cs40l26->busy_state = 0; + + dev_info(cs40l26->dev, "%s fw_id : %d done\n", __func__, fw_id); + + return error; +} + +static void samsung_recovery(struct cs40l26_private *cs40l26) +{ + int error = 0, i = 0; + struct input_dev *dev = cs40l26->sec_vib_ddata.input; + + if (cs40l26->vibe_workqueue) { + cancel_work_sync(&cs40l26->vibe_start_work); + cancel_work_sync(&cs40l26->vibe_stop_work); + cancel_work_sync(&cs40l26->set_gain_work); + cancel_work_sync(&cs40l26->erase_work); + } + + disable_irq(cs40l26->irq); + + cs40l26->busy_state = 1; + + for (i = 0; i < 3; i++) { + pr_info("%s, try(%d)\n", __func__, i + 1); + + samsung_hw_reset(cs40l26); + msleep(100); + + cs40l26_pm_runtime_teardown(cs40l26); + cs40l26->fw_loaded = false; + cs40l26->fw_id = 0; + error = cs40l26_fw_swap(cs40l26, CS40L26_FW_ID); + if (!error) { + pr_info("%s, f/w load success!\n", __func__); + if (cs40l26->sec_vib_ddata.f0_stored) { + error = samsung_set_f0_stored(dev, cs40l26->sec_vib_ddata.f0_stored); + if (error) + pr_err("%s, samsung_set_f0_stored error : %d\n", __func__, error); + } + break; + } + msleep(100); + } + + cs40l26->busy_state = 0; + enable_irq(cs40l26->irq); + + dev_info(cs40l26->dev, "%s done\n", __func__); + +} + +#endif + +static void cs40l26_set_gain(struct input_dev *dev, u16 gain) +{ +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + struct cs40l26_private *cs40l26 = ddata->private_data; +#else + struct cs40l26_private *cs40l26 = input_get_drvdata(dev); +#endif + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + if (gain > 0 && gain < 100) + cs40l26->gain_pct = 1; + else + cs40l26->gain_pct = gain/100; + + if (cs40l26->gain_pct > CS40L26_NUM_PCT_MAP_VALUES) { + dev_err(cs40l26->dev, "%s: gain_pct(%d) is over, just return!\n", + __func__, cs40l26->gain_pct); + return; + } + + dev_info(cs40l26->dev, "Before %s:gain(%d), gain_pct(%d)\n", __func__, + gain, cs40l26->gain_pct); + + cs40l26->gain_pct = sec_vib_inputff_tune_gain(&cs40l26->sec_vib_ddata, cs40l26->gain_pct); + + dev_info(cs40l26->dev, "After: %s:gain(%d), gain_pct(%d)\n", __func__, + gain, cs40l26->gain_pct); +#else + if (gain >= CS40L26_NUM_PCT_MAP_VALUES) { + dev_err(cs40l26->dev, "Gain value %u %% out of bounds\n", gain); + return; + } + + cs40l26->gain_pct = gain; +#endif + queue_work(cs40l26->vibe_workqueue, &cs40l26->set_gain_work); +} + +static int cs40l26_playback_effect(struct input_dev *dev, + int effect_id, int val) +{ +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + struct cs40l26_private *cs40l26 = ddata->private_data; +#else + struct cs40l26_private *cs40l26 = input_get_drvdata(dev); +#endif + struct ff_effect *effect; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + if (cs40l26->busy_state) { + pr_info("%s - f/w is busy\n", __func__); + return -EINVAL; + } + + dev_info(cs40l26->dev, "%s: effect ID = %d, val = %d\n", __func__, effect_id, val); +#else + dev_dbg(cs40l26->dev, "%s: effect ID = %d, val = %d\n", __func__, effect_id, val); +#endif + + effect = &dev->ff->effects[effect_id]; + if (!effect) { + dev_err(cs40l26->dev, "No such effect to playback\n"); + return -EINVAL; + } + + cs40l26->trigger_effect = effect; + + if (val > 0) + queue_work(cs40l26->vibe_workqueue, &cs40l26->vibe_start_work); + else + queue_work(cs40l26->vibe_workqueue, &cs40l26->vibe_stop_work); + + return 0; +} + +int cs40l26_get_num_waves(struct cs40l26_private *cs40l26, u32 *num_waves) +{ + u32 reg, nwaves, nowt; + int error; + + error = cl_dsp_get_reg(cs40l26->dsp, "NUM_OF_WAVES", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_VIBEGEN_ALGO_ID, ®); + if (error) + return error; + + error = cs40l26_dsp_read(cs40l26, reg, &nwaves); + if (error) + return error; + + error = cl_dsp_get_reg(cs40l26->dsp, "OWT_NUM_OF_WAVES_XM", + CL_DSP_XM_UNPACKED_TYPE, CS40L26_VIBEGEN_ALGO_ID, ®); + if (error) + return error; + + error = cs40l26_dsp_read(cs40l26, reg, &nowt); + if (error) + return error; + + *num_waves = nwaves + nowt; + + return 0; +} +EXPORT_SYMBOL_GPL(cs40l26_get_num_waves); + +static struct cl_dsp_owt_header *cs40l26_owt_header(struct cs40l26_private *cs40l26, u8 index, + u16 bank) +{ + if (bank == CS40L26_RAM_BANK_ID && cs40l26->dsp->wt_desc && + index < cs40l26->dsp->wt_desc->owt.nwaves) + return &cs40l26->dsp->wt_desc->owt.waves[index]; + + return ERR_PTR(-EINVAL); +} + +static int cs40l26_owt_get_wlength(struct cs40l26_private *cs40l26, u8 index, u32 *wlen_whole, + u16 bank) +{ + struct device *dev = cs40l26->dev; + struct cl_dsp_owt_header *entry; + struct cl_dsp_memchunk ch; + + if (index == 0) { + *wlen_whole = 0; + return 0; + } + + entry = cs40l26_owt_header(cs40l26, index, bank); + if (IS_ERR(entry)) + return PTR_ERR(entry); + + switch (entry->type) { + case WT_TYPE_V6_PCM_F0_REDC: + case WT_TYPE_V6_PCM_F0_REDC_VAR: + case WT_TYPE_V6_PWLE: + break; + default: + dev_err(dev, "Cannot size waveform type %u\n", entry->type); + return -EINVAL; + } + + ch = cl_dsp_memchunk_create(entry->data, sizeof(u32)); + + /* First 24 bits of each waveform is the length in samples @ 8 kHz */ + return cl_dsp_memchunk_read(cs40l26->dsp, &ch, 24, wlen_whole); +} + +static void cs40l26_owt_set_section_info(struct cs40l26_private *cs40l26, + struct cl_dsp_memchunk *ch, struct cs40l26_owt_section *sections, u8 nsections) +{ + int i; + + for (i = 0; i < nsections; i++) { + cl_dsp_memchunk_write(ch, 8, sections[i].amplitude); + cl_dsp_memchunk_write(ch, 8, sections[i].index); + cl_dsp_memchunk_write(ch, 8, sections[i].repeat); + cl_dsp_memchunk_write(ch, 8, sections[i].flags); + cl_dsp_memchunk_write(ch, 16, sections[i].delay); + + if (sections[i].flags & CS40L26_WT_TYPE10_COMP_DURATION_FLAG) { + cl_dsp_memchunk_write(ch, 8, 0x00); /* Pad */ + cl_dsp_memchunk_write(ch, 16, sections[i].duration); + } + } +} + +static int cs40l26_owt_get_section_info(struct cs40l26_private *cs40l26, struct cl_dsp_memchunk *ch, + struct cs40l26_owt_section *sections, u8 nsections) +{ + int error = 0, i; + + for (i = 0; i < nsections; i++) { + error = cl_dsp_memchunk_read(cs40l26->dsp, ch, 8, §ions[i].amplitude); + if (error) + return error; + + error = cl_dsp_memchunk_read(cs40l26->dsp, ch, 8, §ions[i].index); + if (error) + return error; + + error = cl_dsp_memchunk_read(cs40l26->dsp, ch, 8, §ions[i].repeat); + if (error) + return error; + + error = cl_dsp_memchunk_read(cs40l26->dsp, ch, 8, §ions[i].flags); + if (error) + return error; + + error = cl_dsp_memchunk_read(cs40l26->dsp, ch, 16, §ions[i].delay); + if (error) + return error; + + if (sections[i].flags & CS40L26_WT_TYPE10_COMP_DURATION_FLAG) { + /* Skip padding */ + error = cl_dsp_memchunk_read(cs40l26->dsp, ch, 8, NULL); + if (error) + return error; + + error = cl_dsp_memchunk_read(cs40l26->dsp, ch, 16, §ions[i].duration); + if (error) + return error; + } + + if (sections[i].flags & CS40L26_WT_TYPE10_COMP_ROM_FLAG) + sections[i].wvfrm_bank = CS40L26_ROM_BANK_ID; + else + sections[i].wvfrm_bank = CS40L26_RAM_BANK_ID; + } + + return error; +} + +static int cs40l26_owt_calculate_wlength(struct cs40l26_private *cs40l26, u8 nsections, + u8 global_rep, u8 *data, u32 data_size_bytes, u32 *owt_wlen) +{ + u32 total_len = 0, section_len = 0, loop_len = 0, wlen_whole = 0; + bool in_loop = false; + struct cs40l26_owt_section *sections; + struct cl_dsp_memchunk ch; + u32 dlen, wlen; + int error, i; + + if (nsections < 1) { + dev_err(cs40l26->dev, "Not enough sections for composite\n"); + return -EINVAL; + } + + sections = kcalloc(nsections, sizeof(struct cs40l26_owt_section), GFP_KERNEL); + if (!sections) + return -ENOMEM; + + ch = cl_dsp_memchunk_create((void *) data, data_size_bytes); + error = cs40l26_owt_get_section_info(cs40l26, &ch, sections, nsections); + if (error) { + dev_err(cs40l26->dev, "Failed to get section info\n"); + goto err_free; + } + + for (i = 0; i < nsections; i++) { + error = cs40l26_owt_get_wlength(cs40l26, sections[i].index, &wlen_whole, + sections[i].wvfrm_bank); + if (error) { + dev_err(cs40l26->dev, "Failed to get wlength for index %u: %d\n", + sections[i].index, error); + goto err_free; + } + + if (wlen_whole & CS40L26_WT_TYPE10_WAVELEN_INDEF) { + if (!(sections[i].flags & CS40L26_WT_TYPE10_COMP_DURATION_FLAG)) { + dev_err(cs40l26->dev, "Indefinite entry needs duration\n"); + error = -EINVAL; + goto err_free; + } + + wlen = CS40L26_WT_TYPE10_WAVELEN_MAX; + } else { + /* Length is 22 LSBs, filter out flags */ + wlen = wlen_whole & CS40L26_WT_TYPE10_WAVELEN_MAX; + } + + dlen = 8 * sections[i].delay; + + if (sections[i].flags & CS40L26_WT_TYPE10_COMP_DURATION_FLAG) { + if (wlen > (2 * sections[i].duration)) + wlen = 2 * sections[i].duration; + } + + section_len = wlen + dlen; + loop_len += section_len; + + if (sections[i].repeat == 0xFF) { + in_loop = true; + } else if (sections[i].repeat) { + total_len += (loop_len * (sections[i].repeat + 1)); + + in_loop = false; + loop_len = 0; + } else if (!in_loop) { + total_len += section_len; + loop_len = 0; + } + } + + *owt_wlen = (total_len * (global_rep + 1)) | CS40L26_WT_TYPE10_WAVELEN_CALCULATED; + +err_free: + kfree(sections); + + return error; +} + +static int cs40l26_owt_upload(struct cs40l26_private *cs40l26, u8 *data, u32 data_size_bytes) +{ + struct device *dev = cs40l26->dev; + struct cl_dsp *dsp = cs40l26->dsp; + unsigned int write_reg, reg, wt_offset, wt_size_words, wt_base; + int error; +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + bool err = false; +#endif + + error = cs40l26_pm_enter(dev); + if (error) + return error; + + error = cl_dsp_get_reg(dsp, "OWT_NEXT_XM", CL_DSP_XM_UNPACKED_TYPE, CS40L26_VIBEGEN_ALGO_ID, + ®); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(dev, "Failed to get cl_dsp_get_reg(OWT_NEXT_XM)\n"); + err = true; +#endif + goto err_pm; + } + + error = regmap_read(cs40l26->regmap, reg, &wt_offset); + if (error) { + dev_err(dev, "Failed to get wavetable offset\n"); +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + err = true; +#endif + goto err_pm; + } + + error = cl_dsp_get_reg(dsp, "OWT_SIZE_XM", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_VIBEGEN_ALGO_ID, ®); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(dev, "Failed to get cl_dsp_get_reg(OWT_SIZE_XM)\n"); + err = true; +#endif + goto err_pm; + } + + error = regmap_read(cs40l26->regmap, reg, &wt_size_words); + if (error) { + dev_err(dev, "Failed to get available WT size\n"); +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + err = true; +#endif + goto err_pm; + } + + if ((wt_size_words * CL_DSP_BYTES_PER_WORD) < data_size_bytes) { + dev_err(dev, "No space for OWT waveform\n"); + error = -ENOSPC; +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + err = true; +#endif + goto err_pm; + } + + error = cl_dsp_get_reg(dsp, CS40L26_WT_NAME_XM, CL_DSP_XM_UNPACKED_TYPE, + CS40L26_VIBEGEN_ALGO_ID, &wt_base); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(dev, "Failed to get cl_dsp_get_reg(CS40L26_WT_NAME_XM)\n"); + err = true; +#endif + goto err_pm; + } + + write_reg = wt_base + (wt_offset * 4); + + error = cl_dsp_raw_write(cs40l26->dsp, write_reg, data, data_size_bytes, CL_DSP_MAX_WLEN); + if (error) { + dev_err(dev, "Failed to sync OWT\n"); +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + err = true; +#endif + goto err_pm; + } + + error = cs40l26_mailbox_write(cs40l26, CS40L26_DSP_MBOX_CMD_OWT_PUSH); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(dev, "Failed to set cs40l26_ack_write\n"); + err = true; +#endif + goto err_pm; + } + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "Successfully wrote waveform (%u bytes) to 0x%08X\n", + data_size_bytes, write_reg); +#else + dev_dbg(dev, "Successfully wrote waveform (%u bytes) to 0x%08X\n", data_size_bytes, + write_reg); +#endif + +err_pm: + cs40l26_pm_exit(dev); + + return error; +} + +static u8 *cs40l26_ncw_refactor_data(struct cs40l26_private *cs40l26, u8 amp, u8 nsections, + void *in_data, u32 data_bytes, u16 bank) +{ + struct cs40l26_owt_section *sections; + struct cl_dsp_memchunk in_ch, out_ch; + u16 amp_product; + u8 *out_data; + int i, error; + + if (nsections <= 0) { + dev_err(cs40l26->dev, "Too few sections for NCW\n"); + return ERR_PTR(-EINVAL); + } + + sections = kcalloc(nsections, sizeof(struct cs40l26_owt_section), GFP_KERNEL); + if (!sections) + return ERR_PTR(-ENOMEM); + + in_ch = cl_dsp_memchunk_create(in_data, data_bytes); + + error = cs40l26_owt_get_section_info(cs40l26, &in_ch, sections, nsections); + if (error) { + dev_err(cs40l26->dev, "Failed to get section info\n"); + goto sections_free; + } + + for (i = 0; i < nsections; i++) { + if (sections[i].index != 0) { + amp_product = sections[i].amplitude * amp; + sections[i].amplitude = (u8) DIV_ROUND_UP(amp_product, 100); + } + if (bank == CS40L26_ROM_BANK_ID) + sections[i].flags |= CS40L26_WT_TYPE10_COMP_ROM_FLAG; + } + + out_data = kzalloc(data_bytes, GFP_KERNEL); + if (!out_data) { + error = -ENOMEM; + goto sections_free; + } + + out_ch = cl_dsp_memchunk_create((void *) out_data, data_bytes); + cs40l26_owt_set_section_info(cs40l26, &out_ch, sections, nsections); + +sections_free: + kfree(sections); + + return error ? ERR_PTR(error) : out_data; +} + +static int cs40l26_owt_comp_data_size(struct cs40l26_private *cs40l26, + u8 nsections, struct cs40l26_owt_section *sections) +{ + int i, size = 0; + struct cl_dsp_owt_header *header; + + for (i = 0; i < nsections; i++) { + if (sections[i].index == 0) { + size += CS40L26_WT_TYPE10_SECTION_BYTES_MIN; + continue; + } + + header = cs40l26_owt_header(cs40l26, sections[i].index, sections[i].wvfrm_bank); + if (IS_ERR(header)) + return PTR_ERR(header); + + if (header->type == WT_TYPE_V6_COMPOSITE) { + size += (header->size - 2) * 4; + + if (section_complete(§ions[i])) + size += CS40L26_WT_TYPE10_SECTION_BYTES_MIN; + } else { + size += sections[i].duration ? + CS40L26_WT_TYPE10_SECTION_BYTES_MAX : + CS40L26_WT_TYPE10_SECTION_BYTES_MIN; + } + } + + return size; +} + +static int cs40l26_composite_upload(struct cs40l26_private *cs40l26, s16 *in_data, + u32 in_data_nibbles) +{ + int pos_byte = 0, in_pos_nib = 2, in_data_bytes = 2 * in_data_nibbles; + u8 nsections, global_rep, out_nsections = 0; + int out_data_bytes = 0, data_bytes = 0; + struct device *dev = cs40l26->dev; + u8 ncw_nsections, ncw_global_rep, *data, *ncw_data, *out_data; + u8 delay_section_data[CS40L26_WT_TYPE10_SECTION_BYTES_MIN]; + struct cs40l26_owt_section *sections; + struct cl_dsp_memchunk ch, out_ch; + struct cl_dsp_owt_header *header; + u16 section_size_bytes; + u32 ncw_bytes, wlen; + int i, error; + + ch = cl_dsp_memchunk_create((void *) in_data, in_data_bytes); + /* Skip padding */ + error = cl_dsp_memchunk_read(cs40l26->dsp, &ch, 8, NULL); + if (error) + return error; + + error = cl_dsp_memchunk_read(cs40l26->dsp, &ch, 8, &nsections); + if (error) + return error; + + error = cl_dsp_memchunk_read(cs40l26->dsp, &ch, 8, &global_rep); + if (error) + return error; + + sections = kcalloc(nsections, sizeof(struct cs40l26_owt_section), + GFP_KERNEL); + if (!sections) + return -ENOMEM; + + error = cs40l26_owt_get_section_info(cs40l26, &ch, sections, nsections); + if (error) { + dev_err(cs40l26->dev, "Failed to get section info\n"); + goto sections_err_free; + } + + data_bytes = cs40l26_owt_comp_data_size(cs40l26, nsections, sections); + if (data_bytes <= 0) { + dev_err(dev, "Failed to get OWT Composite Data Size\n"); + error = data_bytes; + goto sections_err_free; + } + + data = kcalloc(data_bytes, sizeof(u8), GFP_KERNEL); + if (!data) { + error = -ENOMEM; + goto sections_err_free; + } + + cl_dsp_memchunk_flush(&ch); + memset(&delay_section_data, 0, CS40L26_WT_TYPE10_SECTION_BYTES_MIN); + + for (i = 0; i < nsections; i++) { + section_size_bytes = sections[i].duration ? + CS40L26_WT_TYPE10_SECTION_BYTES_MAX : + CS40L26_WT_TYPE10_SECTION_BYTES_MIN; + + if (sections[i].index == 0) { + memcpy(data + pos_byte, in_data + in_pos_nib, section_size_bytes); + pos_byte += section_size_bytes; + in_pos_nib += section_size_bytes / 2; + out_nsections++; + continue; + } + + if (sections[i].repeat != 0) { + dev_err(dev, "Inner repeats not allowed for NCWs\n"); + error = -EPERM; + goto data_err_free; + } + + header = cs40l26_owt_header(cs40l26, sections[i].index, sections[i].wvfrm_bank); + if (IS_ERR(header)) { + error = PTR_ERR(header); + goto data_err_free; + } + + if (header->type == WT_TYPE_V6_COMPOSITE) { + ch = cl_dsp_memchunk_create(header->data, 8); + /* Skip Wlength */ + error = cl_dsp_memchunk_read(cs40l26->dsp, &ch, 24, NULL); + if (error) + goto data_err_free; + + /* Skip Padding */ + error = cl_dsp_memchunk_read(cs40l26->dsp, &ch, 8, NULL); + if (error) + goto data_err_free; + + error = cl_dsp_memchunk_read(cs40l26->dsp, &ch, 8, &ncw_nsections); + if (error) + goto data_err_free; + + error = cl_dsp_memchunk_read(cs40l26->dsp, &ch, 8, &ncw_global_rep); + if (error) + goto data_err_free; + + if (ncw_global_rep != 0) { + dev_err(dev, + "No NCW support for outer repeat\n"); + error = -EPERM; + goto data_err_free; + } + + cl_dsp_memchunk_flush(&ch); + + ncw_bytes = (header->size - 2) * 4; + ncw_data = cs40l26_ncw_refactor_data(cs40l26, sections[i].amplitude, + ncw_nsections, header->data + 8, + ncw_bytes, sections[i].wvfrm_bank); + if (IS_ERR(ncw_data)) { + error = PTR_ERR(ncw_data); + goto data_err_free; + } + + memcpy(data + pos_byte, ncw_data, ncw_bytes); + pos_byte += ncw_bytes; + out_nsections += ncw_nsections; + kfree(ncw_data); + + if (section_complete(§ions[i])) { + ch = cl_dsp_memchunk_create((void *) delay_section_data, + CS40L26_WT_TYPE10_SECTION_BYTES_MIN); + + cl_dsp_memchunk_write(&ch, 24, 0x000000); + cl_dsp_memchunk_write(&ch, 8, 0x00); + cl_dsp_memchunk_write(&ch, 16, sections[i].delay); + + memcpy(data + pos_byte, delay_section_data, + CS40L26_WT_TYPE10_SECTION_BYTES_MIN); + + cl_dsp_memchunk_flush(&ch); + + pos_byte += CS40L26_WT_TYPE10_SECTION_BYTES_MIN; + out_nsections++; + } + } else { + memcpy(data + pos_byte, in_data + in_pos_nib, section_size_bytes); + pos_byte += section_size_bytes; + out_nsections++; + } + in_pos_nib += section_size_bytes / 2; + } + + out_data_bytes = data_bytes + CS40L26_WT_HEADER_COMP_SIZE; + out_data = kcalloc(out_data_bytes, sizeof(u8), GFP_KERNEL); + if (!out_data) { + dev_err(dev, "Failed to allocate space for composite\n"); + error = -ENOMEM; + goto data_err_free; + } + + out_ch = cl_dsp_memchunk_create((void *) out_data, out_data_bytes); + cl_dsp_memchunk_write(&out_ch, 16, CS40L26_WT_HEADER_DEFAULT_FLAGS); + cl_dsp_memchunk_write(&out_ch, 8, WT_TYPE_V6_COMPOSITE); + cl_dsp_memchunk_write(&out_ch, 24, CS40L26_WT_HEADER_OFFSET); + cl_dsp_memchunk_write(&out_ch, 24, data_bytes / CL_DSP_BYTES_PER_WORD); + + error = cs40l26_owt_calculate_wlength(cs40l26, out_nsections, global_rep, data, data_bytes, + &wlen); + if (error) + goto out_data_err_free; + + cl_dsp_memchunk_write(&out_ch, 24, wlen); + cl_dsp_memchunk_write(&out_ch, 8, 0x00); /* Pad */ + cl_dsp_memchunk_write(&out_ch, 8, out_nsections); + cl_dsp_memchunk_write(&out_ch, 8, global_rep); + + memcpy(out_data + out_ch.bytes, data, data_bytes); + + error = cs40l26_owt_upload(cs40l26, out_data, out_data_bytes); + +out_data_err_free: + kfree(out_data); +data_err_free: + kfree(data); +sections_err_free: + kfree(sections); + + return error; +} + +static int cs40l26_sine_upload(struct cs40l26_private *cs40l26, struct ff_effect *effect, + struct cs40l26_uploaded_effect *ueffect) +{ + unsigned int duration, freq, level; + int error, slot; + u32 reg; + + slot = cs40l26_buzzgen_find_slot(cs40l26, effect->id); + if (slot == -1) { + dev_err(cs40l26->dev, "No free BUZZGEN slot available\n"); + return -ENOSPC; + } + + cs40l26_buzzgen_configs[slot].effect_id = effect->id; + + /* + * Divide duration by 4 to match firmware's expectation. + * Round up to avoid inadvertently setting a duration of 0. + */ + duration = (unsigned int) DIV_ROUND_UP(effect->replay.length, 4); + + if (effect->u.periodic.period < CS40L26_BUZZGEN_PER_MIN) + freq = 1000 / CS40L26_BUZZGEN_PER_MIN; + else if (effect->u.periodic.period > CS40L26_BUZZGEN_PER_MAX) + freq = 1000 / CS40L26_BUZZGEN_PER_MAX; + else + freq = 1000 / effect->u.periodic.period; + + if (effect->u.periodic.magnitude < CS40L26_BUZZGEN_LEVEL_MIN) + level = CS40L26_BUZZGEN_LEVEL_MIN; + else if (effect->u.periodic.magnitude > CS40L26_BUZZGEN_LEVEL_MAX) + level = CS40L26_BUZZGEN_LEVEL_MAX; + else + level = effect->u.periodic.magnitude; + + error = cl_dsp_get_reg(cs40l26->dsp, cs40l26_buzzgen_configs[slot].duration_name, + CL_DSP_XM_UNPACKED_TYPE, CS40L26_BUZZGEN_ALGO_ID, ®); + if (error) + return error; + + error = regmap_write(cs40l26->regmap, reg, duration); + if (error) + return error; + + error = cl_dsp_get_reg(cs40l26->dsp, cs40l26_buzzgen_configs[slot].freq_name, + CL_DSP_XM_UNPACKED_TYPE, CS40L26_BUZZGEN_ALGO_ID, ®); + if (error) + return error; + + error = regmap_write(cs40l26->regmap, reg, freq); + if (error) + return error; + + error = cl_dsp_get_reg(cs40l26->dsp, cs40l26_buzzgen_configs[slot].level_name, + CL_DSP_XM_UNPACKED_TYPE, CS40L26_BUZZGEN_ALGO_ID, ®); + if (error) + return error; + + error = regmap_write(cs40l26->regmap, reg, level); + if (error) + return error; + + ueffect->id = effect->id; + ueffect->wvfrm_bank = CS40L26_BUZ_BANK_ID; + + /* + * BUZZGEN 1 is reserved for OTP buzz; BUZZGEN 2 - BUZZGEN 6 are valid. + * Add an offset of 1 for this reason. + */ + ueffect->trigger_index = CS40L26_BUZZGEN_INDEX_START + slot + 1; + + return 0; +} + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE +static int cs40l26_index_mapping(int sep_index) +{ + int cirrus_index = 0; + + switch (sep_index) { + case 0: + case 100: + break; + case 119 ... 124: + cirrus_index = sep_index + 16; + break; + case 126 ... 127: + cirrus_index = sep_index + 15; + break; + default: + cirrus_index = sep_index + 9; + break; + } + + return cirrus_index; +} +#endif + +static int cs40l26_custom_upload(struct cs40l26_private *cs40l26, struct ff_effect *effect, + struct cs40l26_uploaded_effect *ueffect) +{ + struct device *dev = cs40l26->dev; + u8 *pwle_data = NULL; + int error, data_len, pwle_data_len, max_index_tmp; + u32 nwaves, min_index, max_index, trigger_index; + u16 index, bank; + + data_len = effect->u.periodic.custom_len; + + if (data_len > CS40L26_CUSTOM_DATA_SIZE) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s OWT effect\n", __func__); +#endif + if (cs40l26->raw_custom_data[1] == CS40L26_WT_TYPE12_IDENTIFIER) { + pwle_data_len = cs40l26->raw_custom_data_len * 2; + pwle_data = kcalloc(pwle_data_len, sizeof(u8), GFP_KERNEL); + if (!pwle_data) { + dev_err(dev, "Failed to allocate space for PWLE\n"); + return -ENOMEM; + } + + memcpy(pwle_data, cs40l26->raw_custom_data, pwle_data_len); + + error = cs40l26_owt_upload(cs40l26, pwle_data, pwle_data_len); + if (error) + return error; + } else { + error = cs40l26_composite_upload(cs40l26, cs40l26->raw_custom_data, + data_len); + if (error) { + dev_err(dev, "Failed to refactor OWT\n"); + return error; + } + } + + bank = (u16) CS40L26_OWT_BANK_ID; + index = (u16) cs40l26->num_owt_effects; + } else { + bank = (u16) cs40l26->raw_custom_data[0]; + index = (u16) (cs40l26->raw_custom_data[1] & CS40L26_MAX_INDEX_MASK); +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + if (cs40l26->use_sep_index) { + dev_info(cs40l26->dev, "%s SEP index(%d)\n", __func__, index); + index = cs40l26_index_mapping(index); + } + dev_info(cs40l26->dev, "%s Index(%d) effect\n", __func__, index); +#endif + } + + error = cs40l26_get_num_waves(cs40l26, &nwaves); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(cs40l26->dev, "%s cs40l26_get_num_waves in FF_CUSTOM error(%d)\n", + __func__, error); +#endif + return error; + } + + switch (bank) { + case CS40L26_RAM_BANK_ID: + if (nwaves - cs40l26->num_owt_effects == 0) { + dev_err(dev, "No waveforms in RAM bank\n"); + return -EINVAL; + } + + min_index = CS40L26_RAM_INDEX_START; + + max_index_tmp = min_index + nwaves - cs40l26->num_owt_effects - 1; + if (max_index_tmp < 0) { + dev_err(dev, "Invalid RAM index %d\n", max_index_tmp); + return -EINVAL; + } + + max_index = (u32) max_index_tmp; + break; + case CS40L26_ROM_BANK_ID: + min_index = CS40L26_ROM_INDEX_START; + max_index = CS40L26_ROM_INDEX_END; + break; + case CS40L26_OWT_BANK_ID: + min_index = CS40L26_OWT_INDEX_START; + max_index = CS40L26_OWT_INDEX_END; + break; + default: + dev_err(dev, "Bank ID (%u) invalid\n", bank); + return -EINVAL; + } + + trigger_index = index + min_index; + if (trigger_index < min_index || trigger_index > max_index) { + dev_err(dev, "Index 0x%X out of bounds (0x%X - 0x%X)\n", trigger_index, min_index, + max_index); + return -EINVAL; + } +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s: ID = %d, trigger index = 0x%08X, duration = %dms\n", + __func__, effect->id, trigger_index, effect->replay.length); +#else + dev_dbg(dev, "ID = %d, trigger index = 0x%08X\n", effect->id, trigger_index); +#endif + + if (bank == CS40L26_OWT_BANK_ID) + cs40l26->num_owt_effects++; + + ueffect->id = effect->id; + ueffect->wvfrm_bank = bank; + ueffect->trigger_index = trigger_index; + + return error; +} + +static int cs40l26_uploaded_effect_add(struct cs40l26_private *cs40l26, struct ff_effect *effect) +{ + struct device *dev = cs40l26->dev; + bool is_new = false; + struct cs40l26_uploaded_effect *ueffect; + int error; + + ueffect = cs40l26_uploaded_effect_find(cs40l26, effect->id); + if (IS_ERR_OR_NULL(ueffect)) { + is_new = true; + ueffect = devm_kzalloc(dev, sizeof(*ueffect), GFP_KERNEL); + if (!ueffect) + return -ENOMEM; + } + + if (effect->u.periodic.waveform == FF_CUSTOM) { + error = cs40l26_custom_upload(cs40l26, effect, ueffect); + } else if (effect->u.periodic.waveform == FF_SINE) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s FF_SINE\n", __func__); +#endif + error = cs40l26_sine_upload(cs40l26, effect, ueffect); + } else { + dev_err(dev, "Periodic waveform type 0x%X not supported\n", + effect->u.periodic.waveform); + error = -EINVAL; + } + + if (error) + goto err_free; + + if (effect->trigger.button) { + error = cs40l26_map_gpi_to_haptic(cs40l26, effect, ueffect); + if (error) + goto err_free; + } else { + ueffect->mapping = CS40L26_GPIO_MAP_INVALID; + } + + if (is_new) + list_add(&ueffect->list, &cs40l26->effect_head); + + return 0; +err_free: + if (is_new) + devm_kfree(dev, ueffect); + + return error; +} + +static void cs40l26_upload_worker(struct work_struct *work) +{ + struct cs40l26_private *cs40l26 = container_of(work, + struct cs40l26_private, upload_work); + struct device *cdev = cs40l26->dev; + struct ff_effect *effect; + u32 nwaves; + int error; +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + bool err = false; + + if (cs40l26->busy_state) { + dev_err(cs40l26->dev, "%s - f/w is busy\n", __func__); + return; + } +#endif + + error = cs40l26_pm_enter(cdev); + if (error) + return; + + mutex_lock(&cs40l26->lock); + + effect = &cs40l26->upload_effect; + + if (effect->type != FF_PERIODIC) { + dev_err(cdev, "Effect type 0x%X not supported\n", effect->type); + error = -EINVAL; +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + err = true; +#endif + goto out_mutex; + } + + error = cs40l26_uploaded_effect_add(cs40l26, effect); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + err = true; +#endif + goto out_mutex; + } + + error = cs40l26_get_num_waves(cs40l26, &nwaves); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(cdev, "%s final cs40l26_get_num_waves error(%d)\n", + __func__, error); + err = true; +#endif + goto out_mutex; + } + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cdev, "Total number of waveforms = %u\n", nwaves); +#else + dev_dbg(cdev, "Total number of waveforms = %u\n", nwaves); +#endif + +out_mutex: + mutex_unlock(&cs40l26->lock); + + cs40l26_pm_exit(cdev); + + cs40l26->upload_ret = error; +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + if (err) + samsung_recovery(cs40l26); +#endif +} + +static int cs40l26_upload_effect(struct input_dev *dev, + struct ff_effect *effect, struct ff_effect *old) +{ +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + struct cs40l26_private *cs40l26 = ddata->private_data; +#else + struct cs40l26_private *cs40l26 = input_get_drvdata(dev); +#endif + int len = effect->u.periodic.custom_len; + int error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s: effect ID = %d len=%d\n", __func__, effect->id, len); +#else + dev_dbg(cs40l26->dev, "%s: effect ID = %d\n", __func__, effect->id); +#endif + + memcpy(&cs40l26->upload_effect, effect, sizeof(struct ff_effect)); + + if (effect->u.periodic.waveform == FF_CUSTOM) { + cs40l26->raw_custom_data_len = len; + + cs40l26->raw_custom_data = kcalloc(len, sizeof(s16), + GFP_KERNEL); + if (!cs40l26->raw_custom_data) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(cs40l26->dev, "%s:raw_custom_data is null\n", __func__); +#endif + error = -ENOMEM; + goto out_free; + } + + if (copy_from_user(cs40l26->raw_custom_data, effect->u.periodic.custom_data, + sizeof(s16) * len)) { + dev_err(cs40l26->dev, "Failed to get user data\n"); + error = -EFAULT; + goto out_free; + } + } + + queue_work(cs40l26->vibe_workqueue, &cs40l26->upload_work); + + /* Wait for upload to finish */ + flush_work(&cs40l26->upload_work); + + error = cs40l26->upload_ret; + +out_free: + memset(&cs40l26->upload_effect, 0, sizeof(struct ff_effect)); + kfree(cs40l26->raw_custom_data); + cs40l26->raw_custom_data = NULL; + + return error; +} + +static int cs40l26_erase_gpi_mapping(struct cs40l26_private *cs40l26, enum cs40l26_gpio_map mapping) +{ + u32 reg, base, offset; + int error; + + if (mapping != CS40L26_GPIO_MAP_A_PRESS && mapping != CS40L26_GPIO_MAP_A_RELEASE) { + dev_err(cs40l26->dev, "Invalid GPI mapping %u\n", mapping); + return -EINVAL; + } + + base = cs40l26->rom_regs->event_map_table_event_data_packed; + offset = mapping * CL_DSP_BYTES_PER_WORD; + reg = base + offset; + + error = regmap_write(cs40l26->regmap, reg, CS40L26_EVENT_MAP_GPI_DISABLE); + if (error) { + dev_err(cs40l26->dev, "Failed to clear GPI mapping %u\n", + mapping); + return error; + } + + return 0; +} + +static int cs40l26_erase_owt(struct cs40l26_private *cs40l26, + struct cs40l26_uploaded_effect *ueffect) +{ + u32 cmd = CS40L26_DSP_MBOX_CMD_OWT_DELETE_BASE; + u32 index = ueffect->trigger_index; + struct cs40l26_uploaded_effect *ueffect_tmp; + int error; +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + u32 old_nwaves = 0, nwaves = 0; +#endif + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s\n", __func__); + + error = cs40l26_get_num_waves(cs40l26, &old_nwaves); + if (error) + dev_err(cs40l26->dev, "%s Failed to get old num waves: %d\n", + __func__, error); +#endif + + cmd |= (index & 0xFF); + + error = cs40l26_mailbox_write(cs40l26, cmd); + if (error) + return error; +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + error = cs40l26_get_num_waves(cs40l26, &nwaves); + if (error) + dev_err(cs40l26->dev, "%s Failed to get new num waves: %d\n", + __func__, error); + + if (old_nwaves-1 != nwaves) { + dev_err(cs40l26->dev, "%s unmatch nwaves old %d: new %d\n", + __func__, old_nwaves, nwaves); + + cs40l26_mailbox_write(cs40l26, cmd); + + error = cs40l26_get_num_waves(cs40l26, &nwaves); + if (error) + dev_err(cs40l26->dev, "%s Failed to get num waves: %d\n", + __func__, error); + dev_info(cs40l26->dev, "nwaves old %d: new %d\n", + old_nwaves, nwaves); + } +#endif + + /* Update indices for OWT waveforms uploaded after erased effect */ + list_for_each_entry(ueffect_tmp, &cs40l26->effect_head, list) { + if (ueffect_tmp->wvfrm_bank == CS40L26_OWT_BANK_ID && + ueffect_tmp->trigger_index > index) + ueffect_tmp->trigger_index--; + } + + cs40l26->num_owt_effects--; + + return 0; +} + +static void cs40l26_erase_worker(struct work_struct *work) +{ + struct cs40l26_private *cs40l26 = container_of(work, + struct cs40l26_private, erase_work); + struct cs40l26_uploaded_effect *ueffect; + int effect_id, error; + u16 duration; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + if (cs40l26->busy_state) { + pr_info("%s - f/w is busy\n", __func__); + return; + } +#endif + + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return; + + mutex_lock(&cs40l26->lock); + + effect_id = cs40l26->erase_effect->id; + ueffect = cs40l26_uploaded_effect_find(cs40l26, effect_id); + if (IS_ERR_OR_NULL(ueffect)) { + dev_err(cs40l26->dev, "No such effect to erase (%d)\n", + effect_id); + error = ueffect ? PTR_ERR(ueffect) : -EINVAL; + goto out_mutex; + } + + duration = (cs40l26->erase_effect->replay.length == 0) ? + CS40L26_MAX_WAIT_VIBE_COMPLETE_MS : + cs40l26->erase_effect->replay.length + CS40L26_ERASE_BUFFER_MS; + + /* Check for ongoing effect playback. */ + if (cs40l26->vibe_state == CS40L26_VIBE_STATE_HAPTIC) { + /* Wait for effect to complete. */ + mutex_unlock(&cs40l26->lock); + if (!wait_for_completion_timeout(&cs40l26->erase_cont, + msecs_to_jiffies(duration))) { + error = -ETIME; + dev_err(cs40l26->dev, "Failed to erase effect (%d)\n", + effect_id); + goto pm_err; + } + mutex_lock(&cs40l26->lock); + } + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s: effect ID = %d\n", __func__, effect_id); +#else + dev_dbg(cs40l26->dev, "%s: effect ID = %d\n", __func__, effect_id); +#endif + if (ueffect->wvfrm_bank == CS40L26_BUZ_BANK_ID) { + error = cs40l26_erase_buzzgen(cs40l26, ueffect->id); + if (error) + goto out_mutex; + } + + if (ueffect->mapping != CS40L26_GPIO_MAP_INVALID) { + error = cs40l26_erase_gpi_mapping(cs40l26, ueffect->mapping); + if (error) + goto out_mutex; + ueffect->mapping = CS40L26_GPIO_MAP_INVALID; + } + + if (ueffect->wvfrm_bank == CS40L26_OWT_BANK_ID) + error = cs40l26_erase_owt(cs40l26, ueffect); + + if (error) { + dev_err(cs40l26->dev, "Failed to erase effect: %d", error); + goto out_mutex; + } + + list_del(&ueffect->list); + devm_kfree(cs40l26->dev, ueffect); + +out_mutex: + mutex_unlock(&cs40l26->lock); +pm_err: + cs40l26_pm_exit(cs40l26->dev); + + cs40l26->erase_ret = error; +} + +static int cs40l26_erase_effect(struct input_dev *dev, int effect_id) +{ +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + struct sec_vib_inputff_drvdata *ddata = input_get_drvdata(dev); + struct cs40l26_private *cs40l26 = ddata->private_data; +#else + struct cs40l26_private *cs40l26 = input_get_drvdata(dev); +#endif + struct ff_effect *effect; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s: effect ID = %d\n", __func__, effect_id); +#else + dev_dbg(cs40l26->dev, "%s: effect ID = %d\n", __func__, effect_id); +#endif + + effect = &dev->ff->effects[effect_id]; + if (!effect) { + dev_err(cs40l26->dev, "No such effect to erase\n"); + return -EINVAL; + } + + cs40l26->erase_effect = effect; + + queue_work(cs40l26->vibe_workqueue, &cs40l26->erase_work); + + /* Wait for erase to finish */ + flush_work(&cs40l26->erase_work); + + return cs40l26->erase_ret; +} + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE +static const struct sec_vib_inputff_ops cs40l26_vib_ops = { + .upload = cs40l26_upload_effect, + .playback = cs40l26_playback_effect, + .set_gain = cs40l26_set_gain, + .erase = cs40l26_erase_effect, + .get_i2s_test = samsung_get_i2s_test, + .fw_load = samsung_fw_load, + .set_trigger_cal = samsung_set_trigger_cal, + .get_f0_measured = samsung_get_f0_measured, + .get_f0_offset = samsung_get_f0_offset, + .set_f0_stored = samsung_set_f0_stored, + .set_le_stored = samsung_set_le_stored, + .get_le_stored = samsung_get_le_stored, + .get_le_est = samsung_get_le_est, + .set_use_sep_index = samsung_set_use_sep_index, + .get_owt_lib_compat_version = samsung_get_owt_lib_compat_version, + .get_ap_chipset = samsung_get_ap_chipset, +}; + +static struct attribute_group *cs40l26_dev_attr_groups[] = { + NULL +}; + +static void samsung_input_data_init(struct cs40l26_private *cs40l26) +{ + cs40l26->sec_vib_ddata.dev = cs40l26->dev; + cs40l26->sec_vib_ddata.vib_ops = &cs40l26_vib_ops; + cs40l26->sec_vib_ddata.vendor_dev_attr_groups = cs40l26_dev_attr_groups; + cs40l26->sec_vib_ddata.private_data = (void *)cs40l26; + cs40l26->sec_vib_ddata.devid = cs40l26->devid; + cs40l26->sec_vib_ddata.revid = cs40l26->revid; + cs40l26->sec_vib_ddata.ff_val = 0; + cs40l26->sec_vib_ddata.support_fw = 1; + cs40l26->sec_vib_ddata.ach_percent = cs40l26->asp_scale_pct; + cs40l26->sec_vib_ddata.f0_stored = 0; + cs40l26->sec_vib_ddata.is_f0_tracking = cs40l26->pdata.is_f0_tracking; + cs40l26->sec_vib_ddata.is_le_support = cs40l26->pdata.is_mv_support; + cs40l26->sec_vib_ddata.trigger_calibration = 0; + sec_vib_inputff_setbit(&cs40l26->sec_vib_ddata, FF_PERIODIC); + sec_vib_inputff_setbit(&cs40l26->sec_vib_ddata, FF_CUSTOM); + sec_vib_inputff_setbit(&cs40l26->sec_vib_ddata, FF_SINE); + sec_vib_inputff_setbit(&cs40l26->sec_vib_ddata, FF_GAIN); +} +#else +static int cs40l26_input_init(struct cs40l26_private *cs40l26) +{ + struct device *dev = cs40l26->dev; + int error; + + cs40l26->input = devm_input_allocate_device(dev); + if (!cs40l26->input) + return -ENOMEM; + + cs40l26->input->name = "cs40l26_input"; + cs40l26->input->id.product = cs40l26->devid; + cs40l26->input->id.version = cs40l26->revid; + + input_set_drvdata(cs40l26->input, cs40l26); + input_set_capability(cs40l26->input, EV_FF, FF_PERIODIC); + input_set_capability(cs40l26->input, EV_FF, FF_CUSTOM); + input_set_capability(cs40l26->input, EV_FF, FF_SINE); + input_set_capability(cs40l26->input, EV_FF, FF_GAIN); + + error = input_ff_create(cs40l26->input, FF_MAX_EFFECTS); + if (error) { + dev_err(dev, "Failed to create FF device: %d\n", error); + return error; + } + + /* + * input_ff_create() automatically sets FF_RUMBLE capabilities; + * we want to restrtict this to only FF_PERIODIC + */ + clear_bit(FF_RUMBLE, cs40l26->input->ffbit); + + cs40l26->input->ff->upload = cs40l26_upload_effect; + cs40l26->input->ff->playback = cs40l26_playback_effect; + cs40l26->input->ff->set_gain = cs40l26_set_gain; + cs40l26->input->ff->erase = cs40l26_erase_effect; + + error = input_register_device(cs40l26->input); + if (error) { + dev_err(dev, "Cannot register input device: %d\n", error); + return error; + } + + error = sysfs_create_group(&cs40l26->input->dev.kobj, + &cs40l26_dev_attr_group); + if (error) { + dev_err(dev, "Failed to create sysfs group: %d\n", error); + return error; + } + + error = sysfs_create_group(&cs40l26->input->dev.kobj, + &cs40l26_dev_attr_cal_group); + if (error) { + dev_err(dev, "Failed to create cal sysfs group: %d\n", error); + return error; + } + + error = sysfs_create_group(&cs40l26->input->dev.kobj, + &cs40l26_dev_attr_dbc_group); + if (error) { + dev_err(dev, "Failed to create DBC sysfs group\n"); + return error; + } + + cs40l26->vibe_init_success = true; + + return error; +} +#endif + +static int cs40l26_wksrc_config(struct cs40l26_private *cs40l26) +{ + u8 mask_wksrc; + u32 val, mask; + + if (cs40l26->devid == CS40L26_DEVID_A || + cs40l26->devid == CS40L26_DEVID_L27_A) + mask_wksrc = 1; + else + mask_wksrc = 0; + + val = CS40L26_WKSRC_STS_SPI_MASK | + (mask_wksrc ? CS40L26_WKSRC_STS_GPIO2_MASK : 0) | + (mask_wksrc ? CS40L26_WKSRC_STS_GPIO3_MASK : 0) | + (mask_wksrc ? CS40L26_WKSRC_STS_GPIO4_MASK : 0); + + mask = CS40L26_WKSRC_STS_ANY_MASK | CS40L26_WKSRC_STS_GPIO1_MASK | + CS40L26_WKSRC_STS_I2C_MASK | CS40L26_WKSRC_STS_SPI_MASK | + CS40L26_WKSRC_STS_GPIO2_MASK | CS40L26_WKSRC_STS_GPIO3_MASK | + CS40L26_WKSRC_STS_GPIO4_MASK; + + return cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_1, val, mask); +} + +static int cs40l26_gpio_config(struct cs40l26_private *cs40l26) +{ + u32 val, reg; + u8 mask_gpio; + int error; + + if (cs40l26->devid == CS40L26_DEVID_A || + cs40l26->devid == CS40L26_DEVID_L27_A) + mask_gpio = 1; + else + mask_gpio = 0; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s\n", __func__); +#endif + error = cl_dsp_get_reg(cs40l26->dsp, "ENT_MAP_TABLE_EVENT_DATA_PACKED", + CL_DSP_XM_UNPACKED_TYPE, CS40L26_EVENT_HANDLER_ALGO_ID, + &cs40l26->event_map_base); + if (error) + return error; + + if (mask_gpio) + val = (u32) GENMASK(CS40L26_GPIO4_FALL_IRQ, + CS40L26_GPIO2_RISE_IRQ); + else + val = 0; + + reg = cs40l26->event_map_base + (CS40L26_GPIO_MAP_A_PRESS * CL_DSP_BYTES_PER_WORD); + + error = regmap_write(cs40l26->regmap, reg, cs40l26->press_idx); + if (error) { + dev_err(cs40l26->dev, "Failed to map press GPI event\n"); + return error; + } + + reg = cs40l26->event_map_base + (CS40L26_GPIO_MAP_A_RELEASE * CL_DSP_BYTES_PER_WORD); + + error = regmap_write(cs40l26->regmap, reg, cs40l26->release_idx); + if (error) { + dev_err(cs40l26->dev, "Failed to map release GPI event\n"); + return error; + } + + return cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_1, val, + GENMASK(CS40L26_GPIO4_FALL_IRQ, CS40L26_GPIO1_RISE_IRQ)); +} + +static const struct cs40l26_brwnout_limits cs40l26_brwnout_params[] = { + { + .max = CS40L26_VBBR_THLD_UV_MAX, + .min = CS40L26_VBBR_THLD_UV_MIN, + }, + { + .max = CS40L26_VPBR_THLD_UV_MAX, + .min = CS40L26_VPBR_THLD_UV_MIN, + }, + { + .max = CS40L26_VXBR_MAX_ATT_MAX, + .min = CS40L26_VXBR_MAX_ATT_MIN, + }, + { + .max = CS40L26_VXBR_ATK_STEP_MAX, + .min = CS40L26_VXBR_ATK_STEP_MIN, + }, + { + .max = CS40L26_VXBR_ATK_RATE_MAX, + .min = CS40L26_VXBR_ATK_RATE_MIN, + }, + { + .max = CS40L26_VXBR_WAIT_MAX, + .min = CS40L26_VXBR_WAIT_MIN, + }, + { + .max = CS40L26_VXBR_REL_RATE_MAX, + .min = CS40L26_VXBR_REL_RATE_MIN, + }, +}; + +static int cs40l26_brwnout_prevention_init(struct cs40l26_private *cs40l26) +{ + u32 enables, pseq_mask = 0, val, vbbr_config, vpbr_config; + struct device *dev = cs40l26->dev; + struct regmap *regmap = cs40l26->regmap; + int error; + + error = regmap_read(regmap, CS40L26_BLOCK_ENABLES2, &enables); + if (error) { + dev_err(dev, "Failed to read block enables 2\n"); + return error; + } + + enables |= ((cs40l26->vbbr.enable << CS40L26_VBBR_EN_SHIFT) | + (cs40l26->vpbr.enable << CS40L26_VPBR_EN_SHIFT)); + + error = regmap_write(regmap, CS40L26_BLOCK_ENABLES2, enables); + if (error) { + dev_err(dev, "Failed to enable brownout prevention\n"); + return error; + } + + error = cs40l26_pseq_write(cs40l26, CS40L26_BLOCK_ENABLES2, enables, true, + CS40L26_PSEQ_OP_WRITE_FULL); + if (error) { + dev_err(dev, "Failed to sequence brownout prevention\n"); + return error; + } + + if (cs40l26->vbbr.enable) { + pseq_mask = CS40L26_VBBR_ATT_CLR_MASK | CS40L26_VBBR_FLAG_MASK; + + vbbr_config = (cs40l26->vbbr.thld_uv / CS40L26_VBBR_THLD_UV_DIV) & + CS40L26_VBBR_THLD_MASK; + + vbbr_config |= ((cs40l26->vbbr.max_att_db << CS40L26_VXBR_MAX_ATT_SHIFT) & + CS40L26_VXBR_MAX_ATT_MASK); + + vbbr_config |= ((cs40l26->vbbr.atk_step << CS40L26_VXBR_ATK_STEP_SHIFT) & + CS40L26_VXBR_ATK_STEP_MASK); + + vbbr_config |= ((cs40l26->vbbr.atk_rate << CS40L26_VXBR_ATK_RATE_SHIFT) & + CS40L26_VXBR_ATK_RATE_MASK); + + vbbr_config |= ((cs40l26->vbbr.wait << CS40L26_VXBR_WAIT_SHIFT) & + CS40L26_VXBR_WAIT_MASK); + + vbbr_config |= ((cs40l26->vbbr.rel_rate << CS40L26_VXBR_REL_RATE_SHIFT) & + CS40L26_VXBR_REL_RATE_MASK); + + error = regmap_read(regmap, CS40L26_VBBR_CONFIG, &val); + if (error) { + dev_err(dev, "Failed to read VBBR_CONFIG\n"); + return error; + } + + vbbr_config |= (val & CS40L26_VXBR_DEFAULT_MASK); + + error = regmap_write(regmap, CS40L26_VBBR_CONFIG, vbbr_config); + if (error) { + dev_err(dev, "Failed to write VBBR_CONFIG\n"); + return error; + } + + error = cs40l26_pseq_write(cs40l26, CS40L26_VBBR_CONFIG, + (vbbr_config & GENMASK(31, 16)) >> 16, + true, CS40L26_PSEQ_OP_WRITE_H16); + if (error) + return error; + + error = cs40l26_pseq_write(cs40l26, CS40L26_VBBR_CONFIG, + (vbbr_config & GENMASK(15, 0)), + true, CS40L26_PSEQ_OP_WRITE_L16); + if (error) + return error; + } + + if (cs40l26->vpbr.enable) { + pseq_mask |= CS40L26_VPBR_ATT_CLR_MASK | CS40L26_VPBR_FLAG_MASK; + + vpbr_config = ((cs40l26->vpbr.thld_uv / CS40L26_VPBR_THLD_UV_DIV) - 51) & + CS40L26_VPBR_THLD_MASK; + + vpbr_config |= ((cs40l26->vpbr.max_att_db << CS40L26_VXBR_MAX_ATT_SHIFT) & + CS40L26_VXBR_MAX_ATT_MASK); + + vpbr_config |= ((cs40l26->vpbr.atk_step << CS40L26_VXBR_ATK_STEP_SHIFT) & + CS40L26_VXBR_ATK_STEP_MASK); + + vpbr_config |= ((cs40l26->vpbr.atk_rate << CS40L26_VXBR_ATK_RATE_SHIFT) & + CS40L26_VXBR_ATK_RATE_MASK); + + vpbr_config |= ((cs40l26->vpbr.wait << CS40L26_VXBR_WAIT_SHIFT) & + CS40L26_VXBR_WAIT_MASK); + + vpbr_config |= ((cs40l26->vpbr.rel_rate << CS40L26_VXBR_REL_RATE_SHIFT) & + CS40L26_VXBR_REL_RATE_MASK); + + error = regmap_read(regmap, CS40L26_VPBR_CONFIG, &val); + if (error) { + dev_err(dev, "Failed to read VPBR_CONFIG\n"); + return error; + } + + vpbr_config |= (val & CS40L26_VXBR_DEFAULT_MASK); + + error = regmap_write(regmap, CS40L26_VPBR_CONFIG, vpbr_config); + if (error) { + dev_err(dev, "Failed to write VPBR_CONFIG\n"); + return error; + } + + error = cs40l26_pseq_write(cs40l26, CS40L26_VPBR_CONFIG, + (vpbr_config & GENMASK(31, 16)) >> 16, + true, CS40L26_PSEQ_OP_WRITE_H16); + if (error) + return error; + + error = cs40l26_pseq_write(cs40l26, CS40L26_VPBR_CONFIG, + (vpbr_config & GENMASK(15, 0)), + true, CS40L26_PSEQ_OP_WRITE_L16); + if (error) + return error; + } + + return cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_2, 0, pseq_mask); +} + +static int cs40l26_asp_config(struct cs40l26_private *cs40l26) +{ + struct reg_sequence *dsp1rx_config = + kcalloc(2, sizeof(struct reg_sequence), GFP_KERNEL); + int error; + + if (!dsp1rx_config) { + dev_err(cs40l26->dev, "Failed to allocate reg. sequence\n"); + return -ENOMEM; + } + + dsp1rx_config[0].reg = CS40L26_DSP1RX1_INPUT; + dsp1rx_config[0].def = CS40L26_DATA_SRC_ASPRX1; + dsp1rx_config[1].reg = CS40L26_DSP1RX5_INPUT; + dsp1rx_config[1].def = CS40L26_DATA_SRC_ASPRX2; + + error = regmap_multi_reg_write(cs40l26->regmap, dsp1rx_config, 2); + if (error) { + dev_err(cs40l26->dev, "Failed to configure ASP\n"); + goto err_free; + } + + error = cs40l26_pseq_multi_write(cs40l26, dsp1rx_config, 2, true, + CS40L26_PSEQ_OP_WRITE_L16); + +err_free: + kfree(dsp1rx_config); + + return error; +} + +static int cs40l26_bst_dcm_config(struct cs40l26_private *cs40l26) +{ + int error = 0; + u32 val; + + if (cs40l26->bst_dcm_en != CS40L26_BST_DCM_EN_DEFAULT) { + error = regmap_read(cs40l26->regmap, CS40L26_BST_DCM_CTL, &val); + if (error) + return error; + + val &= ~CS40L26_BST_DCM_EN_MASK; + val |= cs40l26->bst_dcm_en << CS40L26_BST_DCM_EN_SHIFT; + + error = regmap_write(cs40l26->regmap, CS40L26_BST_DCM_CTL, val); + if (error) + return error; + + error = cs40l26_pseq_write(cs40l26, CS40L26_BST_DCM_CTL, + val, true, CS40L26_PSEQ_OP_WRITE_FULL); + } + + return error; +} + +static int cs40l26_zero_cross_config(struct cs40l26_private *cs40l26) +{ + int error = 0; + u32 reg; + + if (cs40l26->pwle_zero_cross) { + error = cl_dsp_get_reg(cs40l26->dsp, "PWLE_EXTEND_ZERO_CROSS", + CL_DSP_XM_UNPACKED_TYPE, CS40L26_VIBEGEN_ALGO_ID, ®); + if (error) + return error; + + error = regmap_write(cs40l26->regmap, reg, 1); + if (error) + dev_err(cs40l26->dev, "Failed to set PWLE_EXTEND_ZERO_CROSS\n"); + + } + + return error; +} + +static int cs40l26_calib_dt_config(struct cs40l26_private *cs40l26) +{ + int error = 0; + u32 reg; + + if (cs40l26->f0_default <= CS40L26_F0_EST_MAX && + cs40l26->f0_default >= CS40L26_F0_EST_MIN) { + error = cl_dsp_get_reg(cs40l26->dsp, "F0_OTP_STORED", + CL_DSP_XM_UNPACKED_TYPE, + CS40L26_VIBEGEN_ALGO_ID, ®); + if (error) + return error; + + error = regmap_write(cs40l26->regmap, reg, cs40l26->f0_default); + if (error) { + dev_err(cs40l26->dev, "Failed to write default f0\n"); + return error; + } + } + + if (cs40l26->redc_default && cs40l26->redc_default <= CS40L26_UINT_24_BITS_MAX) { + error = cl_dsp_get_reg(cs40l26->dsp, "REDC_OTP_STORED", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_VIBEGEN_ALGO_ID, ®); + if (error) + return error; + + error = regmap_write(cs40l26->regmap, reg, cs40l26->redc_default); + if (error) { + dev_err(cs40l26->dev, "Failed to write default ReDC\n"); + return error; + } + } + + if (cs40l26->revid < CS40L26_REVID_B2) { + if (cs40l26->q_default <= CS40L26_Q_EST_MAX) { + error = cl_dsp_get_reg(cs40l26->dsp, "Q_STORED", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_VIBEGEN_ALGO_ID, ®); + if (error) + return error; + + error = regmap_write(cs40l26->regmap, reg, cs40l26->q_default); + if (error) { + dev_err(cs40l26->dev, "Failed to write default Q\n"); + return error; + } + } + } + + return error; +} + +static int cs40l26_bst_ipk_config(struct cs40l26_private *cs40l26) +{ + u32 bst_ipk; + int error; + + if (cs40l26->bst_ipk < CS40L26_BST_IPK_UA_MIN || cs40l26->bst_ipk > CS40L26_BST_IPK_UA_MAX) + bst_ipk = CS40L26_BST_IPK_DEFAULT; + else + bst_ipk = (cs40l26->bst_ipk / CS40L26_BST_IPK_UA_STEP) - 16; + + error = regmap_write(cs40l26->regmap, CS40L26_BST_IPK_CTL, bst_ipk); + if (error) { + dev_err(cs40l26->dev, "Failed to update BST peak current\n"); + return error; + } + + error = cs40l26_pseq_write(cs40l26, CS40L26_BST_IPK_CTL, bst_ipk, true, + CS40L26_PSEQ_OP_WRITE_L16); + if (error) + return error; + + return cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_1, 0, + CS40L26_BST_IPK_FLAG_MASK); +} + +static int cs40l26_bst_ctl_config(struct cs40l26_private *cs40l26) +{ + u32 bst_ctl; + int error; + + if (cs40l26->bst_ctl < CS40L26_BST_UV_MIN || cs40l26->bst_ctl > CS40L26_BST_UV_MAX) + bst_ctl = CS40L26_BST_CTL_DEFAULT; + else + bst_ctl = (cs40l26->bst_ctl - CS40L26_BST_UV_MIN) / CS40L26_BST_UV_STEP; + + error = regmap_write(cs40l26->regmap, CS40L26_VBST_CTL_1, bst_ctl); + if (error) { + dev_err(cs40l26->dev, "Failed to write VBST limit\n"); + return error; + } + + return cs40l26_pseq_write(cs40l26, CS40L26_VBST_CTL_1, bst_ctl, true, + CS40L26_PSEQ_OP_WRITE_L16); +} + +static int cs40l26_noise_gate_config(struct cs40l26_private *cs40l26) +{ + u32 ng_config; + int error; + + if (cs40l26->ng_thld < CS40L26_NG_THRESHOLD_MIN || + cs40l26->ng_thld > CS40L26_NG_THRESHOLD_MAX) + cs40l26->ng_thld = CS40L26_NG_THRESHOLD_DEFAULT; + + if (cs40l26->ng_delay < CS40L26_NG_DELAY_MIN || cs40l26->ng_delay > CS40L26_NG_DELAY_MAX) + cs40l26->ng_delay = CS40L26_NG_DELAY_DEFAULT; + + ng_config = FIELD_PREP(CS40L26_NG_THRESHOLD_MASK, cs40l26->ng_thld) | + FIELD_PREP(CS40L26_NG_DELAY_MASK, cs40l26->ng_delay) | + FIELD_PREP(CS40L26_NG_ENABLE_MASK, cs40l26->ng_enable); + + error = regmap_write(cs40l26->regmap, CS40L26_NG_CONFIG, ng_config); + if (error) + return error; + + return cs40l26_pseq_write(cs40l26, CS40L26_NG_CONFIG, ng_config, true, + CS40L26_PSEQ_OP_WRITE_FULL); +} + +static int cs40l26_aux_noise_gate_config(struct cs40l26_private *cs40l26) +{ + u32 aux_ng_config; + int error; + + if (cs40l26->aux_ng_thld > CS40L26_AUX_NG_THLD_MAX) + cs40l26->aux_ng_thld = CS40L26_AUX_NG_THLD_DEFAULT; + + if (cs40l26->aux_ng_delay > CS40L26_AUX_NG_HOLD_MAX) + cs40l26->aux_ng_delay = CS40L26_AUX_NG_HOLD_DEFAULT; + + aux_ng_config = FIELD_PREP(CS40L26_AUX_NG_THLD_MASK, cs40l26->aux_ng_thld) | + FIELD_PREP(CS40L26_AUX_NG_HOLD_MASK, cs40l26->aux_ng_delay) | + FIELD_PREP(CS40L26_AUX_NG_EN_MASK, cs40l26->aux_ng_enable); + + error = regmap_write(cs40l26->regmap, CS40L26_MIXER_NGATE_CH1_CFG, aux_ng_config); + if (error) + return error; + + return cs40l26_pseq_write(cs40l26, CS40L26_MIXER_NGATE_CH1_CFG, aux_ng_config, true, + CS40L26_PSEQ_OP_WRITE_FULL); +} + +static int cs40l26_clip_lvl_config(struct cs40l26_private *cs40l26) +{ + u32 clip_lvl, digpwm_config; + int error; + + error = regmap_write(cs40l26->regmap, CS40L26_TEST_KEY_CTRL, CS40L26_TEST_KEY_UNLOCK_CODE1); + if (error) + return error; + + error = cs40l26_pseq_write(cs40l26, CS40L26_TEST_KEY_CTRL, CS40L26_TEST_KEY_UNLOCK_CODE1, + false, CS40L26_PSEQ_OP_WRITE_L16); + if (error) + return error; + + error = regmap_write(cs40l26->regmap, CS40L26_TEST_KEY_CTRL, CS40L26_TEST_KEY_UNLOCK_CODE2); + if (error) + return error; + + error = cs40l26_pseq_write(cs40l26, CS40L26_TEST_KEY_CTRL, CS40L26_TEST_KEY_UNLOCK_CODE2, + false, CS40L26_PSEQ_OP_WRITE_ADDR8); + if (error) + return error; + + if (cs40l26->clip_lvl < CS40L26_CLIP_LVL_UV_MIN || + cs40l26->clip_lvl > CS40L26_CLIP_LVL_UV_MAX) + clip_lvl = CS40L26_CLIP_LVL_DEFAULT; + else + clip_lvl = cs40l26->clip_lvl / CS40L26_CLIP_LVL_UV_STEP; + + error = regmap_read(cs40l26->regmap, CS40L26_DIGPWM_CONFIG2, &digpwm_config); + if (error) { + dev_err(cs40l26->dev, "Failed to get DIGPWM config\n"); + return error; + } + + digpwm_config &= ~CS40L26_CLIP_LVL_MASK; + digpwm_config |= ((clip_lvl << CS40L26_CLIP_LVL_SHIFT) & CS40L26_CLIP_LVL_MASK); + + error = regmap_write(cs40l26->regmap, CS40L26_DIGPWM_CONFIG2, digpwm_config); + if (error) { + dev_err(cs40l26->dev, "Failed to set DIGPWM config\n"); + return error; + } + + error = cs40l26_pseq_write(cs40l26, CS40L26_DIGPWM_CONFIG2, digpwm_config, true, + CS40L26_PSEQ_OP_WRITE_FULL); + if (error) + return error; + + error = regmap_write(cs40l26->regmap, CS40L26_TEST_KEY_CTRL, CS40L26_TEST_KEY_LOCK_CODE); + if (error) + return error; + + return cs40l26_pseq_write(cs40l26, CS40L26_TEST_KEY_CTRL, CS40L26_TEST_KEY_LOCK_CODE, + false, CS40L26_PSEQ_OP_WRITE_L16); +} + +static int cs40l26_lbst_short_test(struct cs40l26_private *cs40l26) +{ + struct regmap *regmap = cs40l26->regmap; + struct device *dev = cs40l26->dev; + unsigned int err, vbst_ctl_1, vbst_ctl_2; + int error; + + error = regmap_read(regmap, CS40L26_VBST_CTL_1, &vbst_ctl_1); + if (error) { + dev_err(dev, "Failed to read VBST_CTL_1\n"); + return error; + } + + error = regmap_read(regmap, CS40L26_VBST_CTL_2, &vbst_ctl_2); + if (error) { + dev_err(dev, "Failed to read VBST_CTL_2\n"); + return error; + } + + error = regmap_update_bits(regmap, CS40L26_VBST_CTL_1, + CS40L26_BST_CTL_MASK, CS40L26_BST_CTL_VP); + if (error) { + dev_err(dev, "Failed to set VBST_CTL_1\n"); + return error; + } + + error = regmap_update_bits(regmap, CS40L26_VBST_CTL_2, + CS40L26_BST_CTL_SEL_MASK, CS40L26_BST_CTL_SEL_FIXED); + if (error) { + dev_err(dev, "Failed to set VBST_CTL_2\n"); + return error; + } + + /* Set GLOBAL_EN; safe because DSP is guaranteed to be off here */ + error = regmap_update_bits(regmap, CS40L26_GLOBAL_ENABLES, + CS40L26_GLOBAL_EN_MASK, 1); + if (error) { + dev_err(dev, "Failed to set GLOBAL_EN\n"); + return error; + } + + /* Wait until boost converter is guranteed to be powered up */ + usleep_range(CS40L26_BST_TIME_MIN_US, CS40L26_BST_TIME_MAX_US); + + error = regmap_read(regmap, CS40L26_ERROR_RELEASE, &err); + if (error) { + dev_err(dev, "Failed to get ERROR_RELEASE contents\n"); + return error; + } + + if (err & BIT(CS40L26_BST_SHORT_ERR_RLS)) { + dev_alert(dev, "FATAL: Boost shorted at startup\n"); + return -ENOTRECOVERABLE; + } + + /* Clear GLOBAL_EN; safe because DSP is guaranteed to be off here */ + error = regmap_update_bits(regmap, CS40L26_GLOBAL_ENABLES, + CS40L26_GLOBAL_EN_MASK, 0); + if (error) { + dev_err(dev, "Failed to clear GLOBAL_EN\n"); + return error; + } + + error = regmap_write(regmap, CS40L26_VBST_CTL_1, vbst_ctl_1); + if (error) { + dev_err(dev, "Failed to set VBST_CTL_1\n"); + return error; + } + + error = regmap_write(regmap, CS40L26_VBST_CTL_2, vbst_ctl_2); + if (error) + dev_err(dev, "Failed to set VBST_CTL_2\n"); + + return error; +} + +static int cs40l26_handle_errata(struct cs40l26_private *cs40l26) +{ + int error, num_writes; + + if (!cs40l26->expl_mode_enabled) { + error = cs40l26_lbst_short_test(cs40l26); + if (error) + return error; + + num_writes = CS40L26_ERRATA_A1_NUM_WRITES; + } else { + num_writes = CS40L26_ERRATA_A1_EXPL_EN_NUM_WRITES; + } + + return cs40l26_pseq_multi_write(cs40l26, cs40l26_a1_errata, num_writes, + false, CS40L26_PSEQ_OP_WRITE_FULL); +} + +int cs40l26_dbc_enable(struct cs40l26_private *cs40l26, u32 enable) +{ + unsigned int reg; + int error; + + error = cl_dsp_get_reg(cs40l26->dsp, "FLAGS", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_EXT_ALGO_ID, ®); + if (error) + return error; + + error = regmap_update_bits(cs40l26->regmap, reg, CS40L26_DBC_ENABLE_MASK, + enable << CS40L26_DBC_ENABLE_SHIFT); + if (error) + dev_err(cs40l26->dev, "Failed to %s DBC\n", enable ? "enable" : "disable"); + + return error; +} +EXPORT_SYMBOL_GPL(cs40l26_dbc_enable); + +static int cs40l26_handle_dbc_defaults(struct cs40l26_private *cs40l26) +{ + unsigned int i; + int error; + u32 val; + + for (i = 0; i < CS40L26_DBC_NUM_CONTROLS; i++) { + val = cs40l26->dbc_defaults[i]; + + if (val != CS40L26_DBC_USE_DEFAULT) { + error = cs40l26_dbc_set(cs40l26, i, val); + if (error) + return error; + } + } + + if (cs40l26->dbc_enable_default) { + error = cs40l26_dbc_enable(cs40l26, 1); + if (error) + return error; + } + + return 0; +} + +static int cs40l26_logger_setup(struct cs40l26_private *cs40l26) +{ + u32 exc_offset, exc_reg, exc_src, reg, src; + int error, i; + + if (cs40l26->log_srcs != NULL) { + cs40l26->num_log_srcs = 0; + devm_kfree(cs40l26->dev, cs40l26->log_srcs); + } + + error = cl_dsp_get_reg(cs40l26->dsp, "COUNT", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_LOGGER_ALGO_ID, ®); + if (error) + return error; + + error = regmap_read(cs40l26->regmap, reg, &cs40l26->num_log_srcs); + if (error) + return error; + + if (cl_dsp_algo_is_present(cs40l26->dsp, CS40L26_EP_ALGO_ID)) { + /* Add excursion logger source */ + cs40l26->num_log_srcs++; + + error = regmap_write(cs40l26->regmap, reg, cs40l26->num_log_srcs); + if (error) + return error; + + error = cl_dsp_get_reg(cs40l26->dsp, "DBG_SRC_CFG", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_EP_ALGO_ID, ®); + if (error) + return error; + + error = regmap_write(cs40l26->regmap, reg, + CS40L26_LOGGER_SRC_PROTECTION_OUT << 8); + if (error) + return error; + + error = cl_dsp_get_reg(cs40l26->dsp, "DBG_ADDR", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_EP_ALGO_ID, &exc_reg); + if (error) + return error; + + exc_reg += CL_DSP_BYTES_PER_WORD; + exc_reg &= CS40L26_LOGGER_SRC_ADDR_MASK; + exc_reg /= CL_DSP_BYTES_PER_WORD; + + exc_src = exc_reg | FIELD_PREP(CS40L26_LOGGER_SRC_ID_MASK, + CS40L26_LOGGER_SRC_ID_EP) | FIELD_PREP(CS40L26_LOGGER_SRC_TYPE_MASK, + CS40L26_LOGGER_SRC_TYPE_XM_TO_XM) | CS40L26_LOGGER_SRC_SIGN_MASK; + + error = cl_dsp_get_reg(cs40l26->dsp, "SOURCE", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_LOGGER_ALGO_ID, ®); + if (error) + return error; + + exc_offset = (cs40l26->num_log_srcs - 1) * CL_DSP_BYTES_PER_WORD; + + error = regmap_write(cs40l26->regmap, reg + exc_offset, exc_src); + if (error) + return error; + } + + cs40l26->log_srcs = devm_kcalloc(cs40l26->dev, cs40l26->num_log_srcs, + sizeof(struct cs40l26_log_src), GFP_KERNEL); + if (IS_ERR_OR_NULL(cs40l26->log_srcs)) + return cs40l26->log_srcs ? PTR_ERR(cs40l26->log_srcs) : -ENOMEM; + + error = cl_dsp_get_reg(cs40l26->dsp, "SOURCE", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_LOGGER_ALGO_ID, ®); + if (error) + goto err_free; + + for (i = 0; i < cs40l26->num_log_srcs; i++) { + error = regmap_read(cs40l26->regmap, reg + (i * CL_DSP_BYTES_PER_WORD), &src); + if (error) + goto err_free; + + cs40l26->log_srcs[i].sign = FIELD_GET(CS40L26_LOGGER_SRC_SIGN_MASK, src); + cs40l26->log_srcs[i].size = FIELD_GET(CS40L26_LOGGER_SRC_SIZE_MASK, src); + cs40l26->log_srcs[i].type = FIELD_GET(CS40L26_LOGGER_SRC_TYPE_MASK, src); + cs40l26->log_srcs[i].id = FIELD_GET(CS40L26_LOGGER_SRC_ID_MASK, src); + cs40l26->log_srcs[i].addr = FIELD_GET(CS40L26_LOGGER_SRC_ADDR_MASK, src); + } + + return 0; + +err_free: + devm_kfree(cs40l26->dev, cs40l26->log_srcs); + return error; +} + +static int cs40l26_dsp_config(struct cs40l26_private *cs40l26) +{ + struct regmap *regmap = cs40l26->regmap; + struct device *dev = cs40l26->dev; + unsigned int val; + u32 reg, nwaves, value; + int error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "%s\n", __func__); +#endif + + if (!cs40l26->fw_rom_only) { + error = regmap_update_bits(regmap, CS40L26_PWRMGT_CTL, + CS40L26_MEM_RDY_MASK, 1 << CS40L26_MEM_RDY_SHIFT); + if (error) { + dev_err(dev, "Failed to set MEM_RDY to initialize RAM\n"); + return error; + } + + error = cl_dsp_get_reg(cs40l26->dsp, "CALL_RAM_INIT", CL_DSP_XM_UNPACKED_TYPE, + cs40l26->fw_id, ®); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(dev, "cl_dsp_get_reg CALL_RAM_INIT fail\n"); +#endif + return error; + } + + error = cs40l26_dsp_write(cs40l26, reg, 1); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(dev, "cl_dsp_write fail\n"); +#endif + return error; + } + } + + cs40l26->fw_loaded = true; + +#ifdef CONFIG_DEBUG_FS + cs40l26_debugfs_init(cs40l26); +#endif + + error = cs40l26_pseq_init(cs40l26); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(dev, "%s:Failed to pseq_init\n", __func__); +#endif + return error; + } + + error = cs40l26_handle_errata(cs40l26); + if (error) + return error; + + if (!cs40l26->fw_rom_only) { + error = cs40l26_dsp_start(cs40l26); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(dev, "%s:Failed to dsp_start\n", __func__); +#endif + return error; + } + } + + error = cs40l26_pm_state_transition(cs40l26, CS40L26_PM_STATE_PREVENT_HIBERNATE); + if (error) + return error; + + /* ensure firmware running */ + error = cl_dsp_get_reg(cs40l26->dsp, "HALO_STATE", CL_DSP_XM_UNPACKED_TYPE, cs40l26->fw_id, + ®); + if (error) + return error; + + error = regmap_read(regmap, reg, &val); + if (error) { + dev_err(dev, "Failed to read HALO_STATE\n"); + return error; + } + + if (val != CS40L26_DSP_HALO_STATE_RUN) { + dev_err(dev, "Firmware in unexpected state: 0x%X\n", val); + return -EINVAL; + } + + error = cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_1, 0, + CS40L26_AMP_ERR_MASK | CS40L26_TEMP_ERR_MASK | + CS40L26_BST_SHORT_ERR_MASK | CS40L26_BST_DCM_UVP_ERR_MASK | + CS40L26_BST_OVP_ERR_MASK | CS40L26_VIRTUAL2_MBOX_WR_MASK); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(dev, "%s:Failed to irq_update_mask\n", __func__); +#endif + return error; + } + + error = cs40l26_wksrc_config(cs40l26); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(dev, "%s:Failed to wksrc_config\n", __func__); +#endif + return error; + } + + error = cs40l26_gpio_config(cs40l26); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(dev, "%s:Failed to gpio_config\n", __func__); +#endif + return error; + } + + error = cs40l26_bst_dcm_config(cs40l26); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(dev, "%s:Failed to bst_dcm_config\n", __func__); +#endif + return error; + } + + error = cs40l26_bst_ipk_config(cs40l26); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(dev, "%s:Failed to bst_ipk_config\n", __func__); +#endif + return error; + } + + error = cs40l26_bst_ctl_config(cs40l26); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(dev, "%s:Failed to bst_ctl_config\n", __func__); +#endif + return error; + } + + error = cs40l26_clip_lvl_config(cs40l26); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(dev, "%s:Failed to clip_lvl_config\n", __func__); +#endif + return error; + } + + error = cs40l26_handle_dbc_defaults(cs40l26); + if (error) + return error; + + error = cs40l26_zero_cross_config(cs40l26); + if (error) + return error; + + error = cs40l26_noise_gate_config(cs40l26); + if (error) + return error; + + error = cs40l26_aux_noise_gate_config(cs40l26); + if (error) + return error; + + if (!cs40l26->vibe_init_success) { + error = cs40l26_calib_dt_config(cs40l26); + if (error) + return error; + } + + error = cs40l26_brwnout_prevention_init(cs40l26); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(dev, "%s:Failed to brownout_prevention_init\n", __func__); +#endif + return error; + } + + cs40l26_pm_runtime_setup(cs40l26); + + error = cs40l26_pm_state_transition(cs40l26, CS40L26_PM_STATE_ALLOW_HIBERNATE); + if (error) + return error; + + error = cs40l26_pm_enter(dev); + if (error) + return error; + + error = cl_dsp_get_reg(cs40l26->dsp, "TIMEOUT_MS", + CL_DSP_XM_UNPACKED_TYPE, CS40L26_VIBEGEN_ALGO_ID, ®); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(dev, "Failed to get TIMEOUT_MS\n"); +#endif + goto pm_err; + } + error = regmap_write(regmap, reg, 0); + if (error) { + dev_err(dev, "Failed to set TIMEOUT_MS\n"); + goto pm_err; + } + + error = cs40l26_logger_setup(cs40l26); + if (error) + goto pm_err; + + error = cs40l26_asp_config(cs40l26); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(dev, "Failed to set asp config\n"); +#endif + goto pm_err; + } + + error = cs40l26_get_num_waves(cs40l26, &nwaves); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_err(dev, "Failed to get num waves\n"); +#endif + goto pm_err; + } + + dev_info(dev, "%s loaded with %u RAM waveforms\n", CS40L26_DEV_NAME, nwaves); + + cs40l26->num_owt_effects = 0; + + value = (cs40l26->comp_enable_redc << CS40L26_COMP_EN_REDC_SHIFT) | + (cs40l26->comp_enable_f0 << CS40L26_COMP_EN_F0_SHIFT); + + if (cs40l26->fw_id != CS40L26_FW_CALIB_ID) { + error = cl_dsp_get_reg(cs40l26->dsp, "COMPENSATION_ENABLE", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_VIBEGEN_ALGO_ID, ®); + if (error) + goto pm_err; + + error = regmap_write(cs40l26->regmap, reg, value); + if (error) + dev_err(dev, "Failed to configure compensation\n"); + } + +pm_err: + cs40l26_pm_exit(dev); + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "%s done ret %d\n", __func__, error); +#endif + return error; +} + +static void cs40l26_gain_adjust(struct cs40l26_private *cs40l26, s32 adjust) +{ + u16 total, asp, change; + + if (abs(adjust) > 100) { + dev_warn(cs40l26->dev, "Gain adjust %d invalid, not applied\n", adjust); + return; + } + + asp = cs40l26->asp_scale_pct; + + if (adjust < 0) { + change = (u16) ((adjust * -1) & 0xFFFF); + if (asp < change) + total = 0; + else + total = asp - change; + } else { + change = (u16) (adjust & 0xFFFF); + total = asp + change; + if (total > CS40L26_GAIN_FULL_SCALE) + total = CS40L26_GAIN_FULL_SCALE; + } + + cs40l26->asp_scale_pct = total; +} + +int cs40l26_svc_le_estimate(struct cs40l26_private *cs40l26, unsigned int *le) +{ + struct device *dev = cs40l26->dev; + unsigned int reg, le_est = 0; + int error, i; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s\n", __func__); +#endif + + error = cs40l26_mailbox_write(cs40l26, CS40L26_DSP_MBOX_CMD_LE_EST); + if (error) + return error; + + error = cl_dsp_get_reg(cs40l26->dsp, "LE_EST_STATUS", CL_DSP_YM_UNPACKED_TYPE, + CS40L26_SVC_ALGO_ID, ®); + if (error) + return error; + + for (i = 0; i < CS40L26_SVC_LE_MAX_ATTEMPTS; i++) { + usleep_range(CS40L26_SVC_LE_EST_TIME_US, CS40L26_SVC_LE_EST_TIME_US + 100); + error = regmap_read(cs40l26->regmap, reg, &le_est); + if (error) { + dev_err(dev, "Failed to get LE_EST_STATUS\n"); + return error; + } + + dev_info(dev, "Measured Le Estimation = %u\n", le_est); + + if (le_est) + break; + } + + *le = le_est; + + return 0; +} +EXPORT_SYMBOL_GPL(cs40l26_svc_le_estimate); + +static void cs40l26_tuning_select_from_svc_le(struct cs40l26_private *cs40l26, + unsigned int le, u32 *tuning_num) +{ +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + int i = -1; +#else + int i; +#endif + + if (le) { + for (i = 0; i < cs40l26->num_svc_le_vals; i++) { + if (le >= cs40l26->svc_le_vals[i]->min && + le <= cs40l26->svc_le_vals[i]->max) { + *tuning_num = cs40l26->svc_le_vals[i]->n; + + cs40l26_gain_adjust(cs40l26, cs40l26->svc_le_vals[i]->gain_adjust); + break; + } + } + } + + if (!le || i == cs40l26->num_svc_le_vals) + dev_warn(cs40l26->dev, "Using default tunings\n"); + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s, le_est: %u, le_cnt:%d, svc_le:%u, i=%d\n", __func__, + le, cs40l26->num_svc_le_vals, *tuning_num, i); +#endif +} + +static char **cs40l26_get_tuning_names(struct cs40l26_private *cs40l26, int *actual_num_files, + u32 tuning) +{ + int i, file_count = 0; + char **coeff_files; + + coeff_files = kcalloc(CS40L26_MAX_TUNING_FILES, sizeof(char *), GFP_KERNEL); + if (!coeff_files) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < CS40L26_MAX_TUNING_FILES; i++) { + coeff_files[i] = kzalloc(CS40L26_TUNING_FILE_NAME_MAX_LEN, GFP_KERNEL); + if (!coeff_files[i]) + goto err_free; + } + + if (tuning) { + snprintf(coeff_files[file_count++], CS40L26_TUNING_FILE_NAME_MAX_LEN, "%s%d%s", + CS40L26_WT_FILE_PREFIX, tuning, CS40L26_TUNING_FILE_SUFFIX); + } else { + strscpy(coeff_files[file_count++], CS40L26_WT_FILE_NAME, + CS40L26_TUNING_FILE_NAME_MAX_LEN); + } + + if (tuning) { + snprintf(coeff_files[file_count++], CS40L26_TUNING_FILE_NAME_MAX_LEN, "%s%d%s", + CS40L26_SVC_TUNING_FILE_PREFIX, tuning, CS40L26_TUNING_FILE_SUFFIX); + } else { + strscpy(coeff_files[file_count++], CS40L26_SVC_TUNING_FILE_NAME, + CS40L26_TUNING_FILE_NAME_MAX_LEN); + } + if (cl_dsp_algo_is_present(cs40l26->dsp, CS40L26_LF0T_ALGO_ID)) + strscpy(coeff_files[file_count++], CS40L26_LF0T_FILE_NAME, + CS40L26_TUNING_FILE_NAME_MAX_LEN); + + if (cl_dsp_algo_is_present(cs40l26->dsp, CS40L26_DVL_ALGO_ID)) + strscpy(coeff_files[file_count++], CS40L26_DVL_FILE_NAME, + CS40L26_TUNING_FILE_NAME_MAX_LEN); + + if (cs40l26->fw_id == CS40L26_FW_ID) { + if (cl_dsp_algo_is_present(cs40l26->dsp, CS40L26_A2H_ALGO_ID)) + strscpy(coeff_files[file_count++], + CS40L26_A2H_TUNING_FILE_NAME, + CS40L26_TUNING_FILE_NAME_MAX_LEN); + + if (cl_dsp_algo_is_present(cs40l26->dsp, CS40L26_EP_ALGO_ID)) + strscpy(coeff_files[file_count++], + CS40L26_EP_TUNING_FILE_NAME, + CS40L26_TUNING_FILE_NAME_MAX_LEN); + } else { + strscpy(coeff_files[file_count++], CS40L26_CALIB_BIN_FILE_NAME, + CS40L26_TUNING_FILE_NAME_MAX_LEN); + } + + *actual_num_files = file_count; + return coeff_files; + +err_free: + for (; i >= 0; i--) + kfree(coeff_files[i]); + kfree(coeff_files); + *actual_num_files = 0; + return ERR_PTR(-ENOMEM); +} + +static int cs40l26_coeff_load(struct cs40l26_private *cs40l26, u32 tuning) +{ + struct device *dev = cs40l26->dev; + int i, error, num_files_to_load; + const struct firmware *coeff; + char **coeff_files; + + coeff_files = cs40l26_get_tuning_names(cs40l26, &num_files_to_load, tuning); + if (IS_ERR(coeff_files)) + return PTR_ERR(coeff_files); + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "%s\n", __func__); +#endif + for (i = 0; i < num_files_to_load; i++) { + error = request_firmware(&coeff, coeff_files[i], dev); + if (error) { + dev_warn(dev, "Continuing...\n"); + continue; + } + + error = cl_dsp_coeff_file_parse(cs40l26->dsp, coeff); + if (error) + dev_warn(dev, "Failed to load %s, %d. Continuing...\n", coeff_files[i], + error); + else + dev_info(dev, "%s Loaded Successfully\n", coeff_files[i]); + + release_firmware(coeff); + } + + kfree(coeff_files); + + return 0; +} + +static int cs40l26_change_fw_control_defaults(struct cs40l26_private *cs40l26) +{ + int error; + + error = cs40l26_pm_timeout_ms_set(cs40l26, CS40L26_DSP_STATE_STANDBY, + cs40l26->pm_stdby_timeout_ms); + if (error) + return error; + + return cs40l26_pm_timeout_ms_set(cs40l26, CS40L26_DSP_STATE_ACTIVE, + cs40l26->pm_active_timeout_ms); +} + +static int cs40l26_get_fw_params(struct cs40l26_private *cs40l26) +{ + u32 id, min_rev, rev, branch; + int error, maj, min, patch; + + error = cl_dsp_fw_rev_get(cs40l26->dsp, &rev); + if (error) + return error; + + branch = CL_DSP_GET_MAJOR(rev); + maj = (int) branch; + min = (int) CL_DSP_GET_MINOR(rev); + patch = (int) CL_DSP_GET_PATCH(rev); + + error = cl_dsp_fw_id_get(cs40l26->dsp, &id); + if (error) + return error; + + switch (id) { + case CS40L26_FW_ID: + switch (branch) { + case CS40L26_FW_BRANCH: + min_rev = CS40L26_FW_MIN_REV; + cs40l26->vibe_state_reporting = true; + break; + case CS40L26_FW_MAINT_BRANCH: + min_rev = CS40L26_FW_MAINT_MIN_REV; + cs40l26->vibe_state_reporting = false; + break; + case CS40L26_FW_B2_BRANCH: + min_rev = CS40L26_FW_B2_MIN_REV; + cs40l26->vibe_state_reporting = true; + break; + default: + error = -EINVAL; + break; + } + break; + case CS40L26_FW_CALIB_ID: + if (branch == CS40L26_FW_CALIB_BRANCH) { + min_rev = CS40L26_FW_CALIB_MIN_REV; + cs40l26->vibe_state_reporting = true; + } else if (branch == CS40L26_FW_MAINT_CALIB_BRANCH) { + min_rev = CS40L26_FW_MAINT_CALIB_MIN_REV; + cs40l26->vibe_state_reporting = false; + } else { + error = -EINVAL; + } + break; + default: + dev_err(cs40l26->dev, "Invalid FW ID: 0x%06X\n", id); + return -EINVAL; + } + + if (error) { + dev_err(cs40l26->dev, "Rev. Branch 0x%02X invalid\n", maj); + return error; + } + + if (rev < min_rev) { + dev_err(cs40l26->dev, "Invalid firmware revision: %d.%d.%d\n", + maj, min, patch); + return -EINVAL; + } + + cs40l26->fw_id = id; + + dev_info(cs40l26->dev, "Firmware revision %d.%d.%d\n", maj, min, patch); + + return 0; +} + +static int cs40l26_cl_dsp_reinit(struct cs40l26_private *cs40l26) +{ + int error; + + if (cs40l26->dsp) { + error = cl_dsp_destroy(cs40l26->dsp); + if (error) { + dev_err(cs40l26->dev, "Failed to destroy DSP struct\n"); + return error; + } + + cs40l26->dsp = NULL; + } + + cs40l26->dsp = cl_dsp_create(cs40l26->dev, cs40l26->regmap); + if (IS_ERR(cs40l26->dsp)) + return PTR_ERR(cs40l26->dsp); + + return cl_dsp_wavetable_create(cs40l26->dsp, CS40L26_VIBEGEN_ALGO_ID, + CS40L26_WT_NAME_XM, CS40L26_WT_NAME_YM, CS40L26_WT_FILE_NAME); +} + +static int cs40l26_fw_upload(struct cs40l26_private *cs40l26) +{ + bool svc_le_required = cs40l26->num_svc_le_vals && !cs40l26->calib_fw; + struct device *dev = cs40l26->dev; + u32 rev, branch, tuning_num = 0; + unsigned int le = 0; + const struct firmware *fw; + int error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s\n", __func__); +#endif + + cs40l26->fw_loaded = false; + + error = cs40l26_cl_dsp_reinit(cs40l26); + if (error) + return error; + + if (cs40l26->calib_fw) + error = request_firmware(&fw, CS40L26_FW_CALIB_NAME, dev); + else + error = request_firmware(&fw, CS40L26_FW_FILE_NAME, dev); + + if (error) { + release_firmware(fw); + return error; + } + + if (!cs40l26->fw_rom_only) { + error = cs40l26_dsp_pre_config(cs40l26); + if (error) { +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + release_firmware(fw); +#endif + return error; + } + } + + error = cl_dsp_firmware_parse(cs40l26->dsp, fw, !cs40l26->fw_rom_only); + release_firmware(fw); + if (error) + return error; + + error = cs40l26_change_fw_control_defaults(cs40l26); + if (error) + return error; + + error = cs40l26_get_fw_params(cs40l26); + if (error) + return error; + + if (svc_le_required) { + error = cl_dsp_fw_rev_get(cs40l26->dsp, &rev); + if (error) + return error; + + branch = CL_DSP_GET_MAJOR(rev); + + switch (branch) { + case CS40L26_FW_MAINT_BRANCH: + error = cs40l26_dsp_config(cs40l26); + if (error) + return error; + + error = cs40l26_pm_enter(dev); + if (error) + return error; + + error = cs40l26_svc_le_estimate(cs40l26, &le); + if (error) + dev_warn(dev, "svc_le_est failed: %d", error); + + cs40l26_pm_exit(dev); + + cs40l26_pm_runtime_teardown(cs40l26); + + error = cs40l26_dsp_pre_config(cs40l26); + if (error) + return error; + + break; + + case CS40L26_FW_BRANCH: + le = cs40l26->svc_le_est_stored; + break; + + default: + dev_err(dev, "Invalid firmware branch, %d", branch); + return -EINVAL; + } + + cs40l26_tuning_select_from_svc_le(cs40l26, le, &tuning_num); + } + + error = cs40l26_coeff_load(cs40l26, tuning_num); + if (error) + return error; + + return cs40l26_dsp_config(cs40l26); +} + +static int cs40l26_request_irq(struct cs40l26_private *cs40l26) +{ + int error, irq, i; + + cs40l26_regmap_irq_chip.irq_drv_data = cs40l26; + + error = devm_regmap_add_irq_chip(cs40l26->dev, cs40l26->regmap, + cs40l26->irq, IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_LOW, + -1, &cs40l26_regmap_irq_chip, &cs40l26->irq_data); + if (error < 0) { + dev_err(cs40l26->dev, "Failed to request threaded IRQ: %d\n", error); + return error; + } + + cs40l26_regmap_irq_chip.irq_drv_data = cs40l26; + + for (i = 0; i < ARRAY_SIZE(cs40l26_irqs); i++) { + irq = regmap_irq_get_virq(cs40l26->irq_data, cs40l26_irqs[i].irq); + if (irq < 0) { + dev_err(cs40l26->dev, "Failed to get %s\n", cs40l26_irqs[i].name); + return irq; + } + + error = devm_request_threaded_irq(cs40l26->dev, irq, NULL, cs40l26_irqs[i].handler, + IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_LOW, + cs40l26_irqs[i].name, cs40l26); + if (error) { + dev_err(cs40l26->dev, "Failed to request IRQ %s: %d\n", + cs40l26_irqs[i].name, error); + return error; + } + } + + return error; +} + +int cs40l26_fw_swap(struct cs40l26_private *cs40l26, const u32 id) +{ + struct device *dev = cs40l26->dev; + bool re_enable = false; + int error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "%s: fw_id:%d, id%d\n", __func__, cs40l26->fw_id, id); +#endif + + if (cs40l26->fw_loaded) { + disable_irq(cs40l26->irq); + cs40l26_pm_runtime_teardown(cs40l26); + re_enable = true; + } + + switch (cs40l26->revid) { + case CS40L26_REVID_A1: + case CS40L26_REVID_B0: + case CS40L26_REVID_B1: + break; + default: + dev_err(dev, "pseq unrecognized revid: %d\n", cs40l26->revid); + return -EINVAL; + } + + /* reset pseq END_OF_SCRIPT to location from ROM */ + error = cs40l26_dsp_write(cs40l26, cs40l26->rom_regs->rom_pseq_end_of_script, + CS40L26_PSEQ_OP_END << CS40L26_PSEQ_OP_SHIFT); + if (error) { + dev_err(dev, "Failed to reset pseq END_OF_SCRIPT %d\n", error); + return error; + } + + if (id == CS40L26_FW_CALIB_ID) + cs40l26->calib_fw = true; + else + cs40l26->calib_fw = false; + + error = cs40l26_fw_upload(cs40l26); + if (error) + return error; + + if (cs40l26->fw_defer && cs40l26->fw_loaded) { + error = cs40l26_request_irq(cs40l26); + if (error) + return error; + + cs40l26->fw_defer = false; + } + + if (re_enable) + enable_irq(cs40l26->irq); + + return error; +} +EXPORT_SYMBOL_GPL(cs40l26_fw_swap); + +static int cs40l26_handle_svc_le_nodes(struct cs40l26_private *cs40l26) +{ + int i, error, init_count, node_count = 0; + struct device *dev = cs40l26->dev; + unsigned int min, max, index; + struct fwnode_handle *child; + const char *node_name; + u32 gain_adjust_raw; + s32 gain_adjust; + + init_count = device_get_child_node_count(dev); + if (!init_count) + return 0; + + cs40l26->svc_le_vals = devm_kcalloc(dev, init_count, sizeof(struct cs40l26_svc_le *), + GFP_KERNEL); + + if (!cs40l26->svc_le_vals) + return -ENOMEM; + + device_for_each_child_node(dev, child) { + node_name = fwnode_get_name(child); + + if (strncmp(node_name, CS40L26_SVC_DT_PREFIX, 6)) + continue; + + if (fwnode_property_read_u32(child, "cirrus,min", &min)) { + dev_err(dev, "No minimum value for SVC LE node\n"); + continue; + } + + if (fwnode_property_read_u32(child, "cirrus,max", &max)) { + dev_err(dev, "No maximum value for SVC LE node\n"); + continue; + } + + if (max <= min) { + dev_err(dev, "Max <= Min, SVC LE node malformed\n"); + continue; + } + + if (fwnode_property_read_u32(child, "cirrus,gain-adjust", &gain_adjust_raw)) + gain_adjust = 0; + else + gain_adjust = (s32) gain_adjust_raw; + + if (fwnode_property_read_u32(child, "cirrus,index", &index)) { + dev_err(dev, "No index specified for SVC LE node\n"); + continue; + } + + for (i = 0; i < node_count; i++) { + if (index == cs40l26->svc_le_vals[i]->n) + break; + } + + if (i < node_count) { + dev_err(dev, "SVC LE nodes must have unique index\n"); + return -EINVAL; + } + + cs40l26->svc_le_vals[node_count] = devm_kzalloc(dev, sizeof(struct cs40l26_svc_le), + GFP_KERNEL); + + if (!cs40l26->svc_le_vals[node_count]) { + error = -ENOMEM; + goto err; + } + + cs40l26->svc_le_vals[node_count]->min = min; + cs40l26->svc_le_vals[node_count]->max = max; + cs40l26->svc_le_vals[node_count]->gain_adjust = gain_adjust; + cs40l26->svc_le_vals[node_count]->n = index; + node_count++; + } + + if (node_count != init_count) + dev_warn(dev, "%d platform nodes unused for SVC LE\n", init_count - node_count); + + return node_count; + +err: + devm_kfree(dev, cs40l26->svc_le_vals); + return error; +} + +static int cs40l26_no_wait_ram_indices_get(struct cs40l26_private *cs40l26) +{ + int i, num, error; + + num = device_property_count_u32(cs40l26->dev, "cirrus,no-wait-ram-indices"); + if (num <= 0) + return 0; + + cs40l26->no_wait_ram_indices = devm_kcalloc(cs40l26->dev, num, sizeof(u32), GFP_KERNEL); + if (!cs40l26->no_wait_ram_indices) + return -ENOMEM; + + error = device_property_read_u32_array(cs40l26->dev, "cirrus,no-wait-ram-indices", + cs40l26->no_wait_ram_indices, num); + if (error) + goto err_free; + + for (i = 0; i < num; i++) + cs40l26->no_wait_ram_indices[i] += CS40L26_RAM_INDEX_START; + + cs40l26->num_no_wait_ram_indices = num; + + return 0; + +err_free: + devm_kfree(cs40l26->dev, cs40l26->no_wait_ram_indices); + cs40l26->num_no_wait_ram_indices = 0; + return error; +} + +static void cs40l26_hibernate_timer_callback(struct timer_list *t) +{ + struct cs40l26_private *cs40l26 = from_timer(cs40l26, t, hibernate_timer); + + dev_dbg(cs40l26->dev, "Time since ALLOW_HIBERNATE exceeded HE_TIME max"); +} + +static inline bool cs40l26_brwnout_is_valid(enum cs40l26_brwnout_type type, u32 val) +{ + if (type >= CS40L26_NUM_BRWNOUT_TYPES) + return false; + + return (val <= cs40l26_brwnout_params[type].max) && + (val >= cs40l26_brwnout_params[type].min); +} + +static void cs40l26_parse_brwnout_properties(struct cs40l26_private *cs40l26) +{ + struct device *dev = cs40l26->dev; + int error; + + if (device_property_present(dev, "cirrus,vbbr-enable")) { + cs40l26->vbbr.enable = true; + + error = device_property_read_u32(dev, "cirrus,vbbr-thld-uv", + &cs40l26->vbbr.thld_uv); + if (error || !cs40l26_brwnout_is_valid(CS40L26_VBBR_THLD, cs40l26->vbbr.thld_uv)) + cs40l26->vbbr.thld_uv = CS40L26_VBBR_THLD_UV_DEFAULT; + + error = device_property_read_u32(dev, "cirrus,vbbr-max-att-db", + &cs40l26->vbbr.max_att_db); + if (error || !cs40l26_brwnout_is_valid(CS40L26_VXBR_MAX_ATT, + cs40l26->vbbr.max_att_db)) + cs40l26->vbbr.max_att_db = CS40L26_VXBR_MAX_ATT_DEFAULT; + + error = device_property_read_u32(dev, "cirrus,vbbr-atk-step", + &cs40l26->vbbr.atk_step); + if (error || !cs40l26_brwnout_is_valid(CS40L26_VXBR_ATK_STEP, + cs40l26->vbbr.atk_step)) + cs40l26->vbbr.atk_step = CS40L26_VXBR_ATK_STEP_DEFAULT; + + error = device_property_read_u32(dev, "cirrus,vbbr-atk-rate", + &cs40l26->vbbr.atk_rate); + if (error || !cs40l26_brwnout_is_valid(CS40L26_VXBR_ATK_RATE, + cs40l26->vbbr.atk_rate)) + cs40l26->vbbr.atk_rate = CS40L26_VXBR_ATK_RATE_DEFAULT; + + error = device_property_read_u32(dev, "cirrus,vbbr-wait", &cs40l26->vbbr.wait); + if (error || !cs40l26_brwnout_is_valid(CS40L26_VXBR_WAIT, cs40l26->vbbr.wait)) + cs40l26->vbbr.wait = CS40L26_VXBR_WAIT_DEFAULT; + + error = device_property_read_u32(dev, "cirrus,vbbr-rel-rate", + &cs40l26->vbbr.rel_rate); + if (error || !cs40l26_brwnout_is_valid(CS40L26_VXBR_REL_RATE, + cs40l26->vbbr.rel_rate)) + cs40l26->vbbr.rel_rate = CS40L26_VXBR_REL_RATE_DEFAULT; + } + + if (device_property_present(dev, "cirrus,vpbr-enable")) { + cs40l26->vpbr.enable = true; + + error = device_property_read_u32(dev, "cirrus,vpbr-thld-uv", + &cs40l26->vpbr.thld_uv); + if (error || !cs40l26_brwnout_is_valid(CS40L26_VPBR_THLD, cs40l26->vpbr.thld_uv)) + cs40l26->vpbr.thld_uv = CS40L26_VPBR_THLD_UV_DEFAULT; + + error = device_property_read_u32(dev, "cirrus,vpbr-max-att-db", + &cs40l26->vpbr.max_att_db); + if (error || !cs40l26_brwnout_is_valid(CS40L26_VXBR_MAX_ATT, + cs40l26->vpbr.max_att_db)) + cs40l26->vpbr.max_att_db = CS40L26_VXBR_MAX_ATT_DEFAULT; + + error = device_property_read_u32(dev, "cirrus,vpbr-atk-step", + &cs40l26->vpbr.atk_step); + if (error || !cs40l26_brwnout_is_valid(CS40L26_VXBR_ATK_STEP, + cs40l26->vpbr.atk_step)) + cs40l26->vpbr.atk_step = CS40L26_VXBR_ATK_STEP_DEFAULT; + + error = device_property_read_u32(dev, "cirrus,vpbr-atk-rate", + &cs40l26->vpbr.atk_rate); + if (error || !cs40l26_brwnout_is_valid(CS40L26_VXBR_ATK_RATE, + cs40l26->vpbr.atk_rate)) + cs40l26->vpbr.atk_rate = CS40L26_VXBR_ATK_RATE_DEFAULT; + + error = device_property_read_u32(dev, "cirrus,vpbr-wait", &cs40l26->vpbr.wait); + if (error || !cs40l26_brwnout_is_valid(CS40L26_VXBR_WAIT, cs40l26->vpbr.wait)) + cs40l26->vpbr.wait = CS40L26_VXBR_WAIT_DEFAULT; + + error = device_property_read_u32(dev, "cirrus,vpbr-rel-rate", + &cs40l26->vpbr.rel_rate); + if (error || !cs40l26_brwnout_is_valid(CS40L26_VXBR_REL_RATE, + cs40l26->vpbr.rel_rate)) + cs40l26->vpbr.rel_rate = CS40L26_VXBR_REL_RATE_DEFAULT; + } + +} + +static int cs40l26_parse_properties(struct cs40l26_private *cs40l26) +{ + struct device *dev = cs40l26->dev; +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + struct device_node *np = dev->of_node; +#endif + int error; + + cs40l26->fw_defer = device_property_present(dev, "cirrus,fw-defer"); + + cs40l26->fw_rom_only = device_property_present(dev, "cirrus,fw-rom-only"); + + cs40l26->calib_fw = device_property_present(dev, "cirrus,calib-fw"); + + cs40l26->expl_mode_enabled = !device_property_present(dev, "cirrus,bst-expl-mode-disable"); + + cs40l26_parse_brwnout_properties(cs40l26); + + cs40l26->bst_dcm_en = device_property_present(dev, "cirrus,bst-dcm-en"); + + cs40l26->ng_enable = device_property_present(dev, "cirrus,ng-enable"); + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + cs40l26->irq_gpio = of_get_named_gpio(np, "irq-gpio", 0); + pr_info("%s: irq-gpio: %u\n", __func__, cs40l26->irq_gpio); +#endif + + error = device_property_read_u32(dev, "cirrus,bst-ipk-microamp", &cs40l26->bst_ipk); + if (error) + cs40l26->bst_ipk = CS40L26_BST_IPK_UA_DEFAULT; + + error = device_property_read_u32(dev, "cirrus,bst-ctl-microvolt", &cs40l26->bst_ctl); + if (error) + cs40l26->bst_ctl = CS40L26_BST_UV_MAX; + + error = device_property_read_u32(dev, "cirrus,clip-lvl-microvolt", &cs40l26->clip_lvl); + if (error) + cs40l26->clip_lvl = CS40L26_CLIP_LVL_UV_MAX; + + error = device_property_read_u32(dev, "cirrus,pm-stdby-timeout-ms", + &cs40l26->pm_stdby_timeout_ms); + if (error) + cs40l26->pm_stdby_timeout_ms = CS40L26_PM_STDBY_TIMEOUT_MS_MIN; + + error = device_property_read_u32(dev, "cirrus,pm-active-timeout-ms", + &cs40l26->pm_active_timeout_ms); + if (error) + cs40l26->pm_active_timeout_ms = CS40L26_PM_ACTIVE_TIMEOUT_MS_DEFAULT; + + error = cs40l26_handle_svc_le_nodes(cs40l26); + if (error < 0) + cs40l26->num_svc_le_vals = 0; + else + cs40l26->num_svc_le_vals = error; + +#if IS_ENABLED(CONFIG_SEC_FACTORY) + dev_info(dev, "%s - asp_scale_pct is always 100 %%\n", __func__); + cs40l26->asp_scale_pct = CS40L26_GAIN_FULL_SCALE; +#else + error = device_property_read_u32(dev, "cirrus,asp-gain-scale-pct", &cs40l26->asp_scale_pct); + if (error) + cs40l26->asp_scale_pct = CS40L26_GAIN_FULL_SCALE; +#endif + cs40l26->gain_pct = CS40L26_GAIN_FULL_SCALE; + cs40l26->gain_tmp = CS40L26_GAIN_FULL_SCALE; + + error = device_property_read_u32(dev, "cirrus,ng-thld", &cs40l26->ng_thld); + if (error) + cs40l26->ng_thld = CS40L26_NG_THRESHOLD_DEFAULT; + + error = device_property_read_u32(dev, "cirrus,ng-delay", &cs40l26->ng_delay); + if (error) + cs40l26->ng_delay = CS40L26_NG_DELAY_DEFAULT; + + cs40l26->aux_ng_enable = device_property_present(dev, "cirrus,aux-ng-enable"); + + error = device_property_read_u32(dev, "cirrus,aux-ng-thld", &cs40l26->aux_ng_thld); + if (error) + cs40l26->aux_ng_thld = CS40L26_AUX_NG_THLD_DEFAULT; + + error = device_property_read_u32(dev, "cirrus,aux-ng-delay", &cs40l26->aux_ng_delay); + if (error) + cs40l26->aux_ng_delay = CS40L26_AUX_NG_HOLD_DEFAULT; + + error = device_property_read_u32(dev, "cirrus,f0-default", &cs40l26->f0_default); + if (error && error != -EINVAL) + return error; + + error = device_property_read_u32(dev, "cirrus,redc-default", &cs40l26->redc_default); + if (error && error != -EINVAL) + return error; + + error = device_property_read_u32(dev, "cirrus,q-default", &cs40l26->q_default); + if (error && error != -EINVAL) + return error; + + cs40l26->dbc_enable_default = device_property_present(dev, "cirrus,dbc-enable"); + + error = device_property_read_u32(dev, "cirrus,dbc-env-rel-coef", + &cs40l26->dbc_defaults[CS40L26_DBC_ENV_REL_COEF]); + if (error) + cs40l26->dbc_defaults[CS40L26_DBC_ENV_REL_COEF] = CS40L26_DBC_USE_DEFAULT; + + error = device_property_read_u32(dev, "cirrus,dbc-fall-headroom", + &cs40l26->dbc_defaults[CS40L26_DBC_FALL_HEADROOM]); + if (error) + cs40l26->dbc_defaults[CS40L26_DBC_FALL_HEADROOM] = CS40L26_DBC_USE_DEFAULT; + + error = device_property_read_u32(dev, "cirrus,dbc-rise-headroom", + &cs40l26->dbc_defaults[CS40L26_DBC_RISE_HEADROOM]); + if (error) + cs40l26->dbc_defaults[CS40L26_DBC_RISE_HEADROOM] = CS40L26_DBC_USE_DEFAULT; + + error = device_property_read_u32(dev, "cirrus,dbc-tx-lvl-hold-off-ms", + &cs40l26->dbc_defaults[CS40L26_DBC_TX_LVL_HOLD_OFF_MS]); + if (error) + cs40l26->dbc_defaults[CS40L26_DBC_TX_LVL_HOLD_OFF_MS] = CS40L26_DBC_USE_DEFAULT; + + error = device_property_read_u32(dev, "cirrus,dbc-tx-lvl-thresh-fs", + &cs40l26->dbc_defaults[CS40L26_DBC_TX_LVL_THRESH_FS]); + if (error) + cs40l26->dbc_defaults[CS40L26_DBC_TX_LVL_THRESH_FS] = CS40L26_DBC_USE_DEFAULT; + + cs40l26->pwle_zero_cross = device_property_present(dev, "cirrus,pwle-zero-cross-en"); + + cs40l26->press_idx = gpio_map_get(dev, CS40L26_GPIO_MAP_A_PRESS); + cs40l26->release_idx = gpio_map_get(dev, CS40L26_GPIO_MAP_A_RELEASE); + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + cs40l26->pdata.is_f0_tracking = device_property_present(dev, "samsung,f0-tracking"); + cs40l26->pdata.is_mv_support = device_property_present(dev, "samsung,mv_support"); + error = device_property_read_u32(dev, "samsung,f0-tracking-offset", &cs40l26->pdata.f0_offset); + if (error) + cs40l26->pdata.f0_offset = 0; + else + cs40l26->pdata.f0_offset *= CS40L26_SAMSUNG_F0_OFFSET; + + dev_info(dev, "%s - f0 tracking is %s, f0 offset is 0x%x\n", __func__, + cs40l26->pdata.is_f0_tracking ? "set" : "unset", cs40l26->pdata.f0_offset); + + error = device_property_read_string(dev, "samsung,owt-lib-compat-version", + (const char **) &cs40l26->pdata.owt_lib_compat_version); + if (error) + cs40l26->pdata.owt_lib_compat_version = "0.0.0"; + + dev_info(dev, "%s - owt lib compat version is %s\n", __func__, cs40l26->pdata.owt_lib_compat_version); + + error = device_property_read_string(dev, "samsung,ap_chipset", (const char **) &cs40l26->pdata.ap_chipset); + if (error) + cs40l26->pdata.ap_chipset = "qcom"; + + dev_info(dev, "%s - AP Chipset is %s\n", __func__, cs40l26->pdata.ap_chipset); +#endif + return cs40l26_no_wait_ram_indices_get(cs40l26); +} + +int cs40l26_probe(struct cs40l26_private *cs40l26) +{ + struct device *dev = cs40l26->dev; + int error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "%s - start\n", __func__); +#endif + + mutex_init(&cs40l26->lock); + + cs40l26->vibe_workqueue = alloc_ordered_workqueue("vibe_workqueue", WQ_HIGHPRI); + if (!cs40l26->vibe_workqueue) { + error = -ENOMEM; + goto err; + } + + INIT_WORK(&cs40l26->vibe_start_work, cs40l26_vibe_start_worker); + INIT_WORK(&cs40l26->vibe_stop_work, cs40l26_vibe_stop_worker); + INIT_WORK(&cs40l26->set_gain_work, cs40l26_set_gain_worker); + INIT_WORK(&cs40l26->upload_work, cs40l26_upload_worker); + INIT_WORK(&cs40l26->erase_work, cs40l26_erase_worker); + + timer_setup(&cs40l26->hibernate_timer, cs40l26_hibernate_timer_callback, 0); + + error = devm_regulator_bulk_get(dev, CS40L26_NUM_SUPPLIES, cs40l26_supplies); + if (error) { + dev_err(dev, "Failed to request core supplies: %d\n", error); + goto err; + } + + error = cs40l26_parse_properties(cs40l26); + if (error) + goto err; + + + error = regulator_bulk_enable(CS40L26_NUM_SUPPLIES, cs40l26_supplies); + if (error) { + dev_err(dev, "Failed to enable core supplies\n"); + goto err; + } + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + cs40l26->delay_before_stop_playback_us = DELAY_BEFORE_STOP_PLAYBACK_US; + pr_info("%s delay_before_stop_playback_us: %d\n", __func__, DELAY_BEFORE_STOP_PLAYBACK_US); +#endif + cs40l26->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(cs40l26->reset_gpio)) { + dev_err(dev, "Failed to get reset GPIO\n"); + + error = PTR_ERR(cs40l26->reset_gpio); + cs40l26->reset_gpio = NULL; + goto err; + } + + usleep_range(CS40L26_MIN_RESET_PULSE_WIDTH, CS40L26_MIN_RESET_PULSE_WIDTH + 100); + + gpiod_set_value_cansleep(cs40l26->reset_gpio, 0); + + usleep_range(CS40L26_CONTROL_PORT_READY_DELAY, CS40L26_CONTROL_PORT_READY_DELAY + 100); + + /* + * The DSP may lock up if a haptic effect is triggered via + * GPI event or control port and the PLL is set to closed-loop. + * + * Set PLL to open-loop and remove any default GPI mappings + * to prevent this while the driver is loading and configuring RAM + * firmware. + */ + + error = cs40l26_set_pll_loop(cs40l26, CS40L26_PLL_REFCLK_SET_OPEN_LOOP); + if (error) + goto err; + + error = cs40l26_part_num_resolve(cs40l26); + if (error) + goto err; + + error = cs40l26_erase_gpi_mapping(cs40l26, CS40L26_GPIO_MAP_A_PRESS); + if (error) + goto err; + + error = cs40l26_erase_gpi_mapping(cs40l26, CS40L26_GPIO_MAP_A_RELEASE); + if (error) + goto err; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + cs40l26->irq = gpio_to_irq(cs40l26->irq_gpio); + pr_info("%s irq number: %d\n", __func__, cs40l26->irq); +#endif + /* Set LRA to high-z to avoid fault conditions */ + error = regmap_update_bits(cs40l26->regmap, CS40L26_TST_DAC_MSM_CONFIG, + CS40L26_SPK_DEFAULT_HIZ_MASK, 1 << CS40L26_SPK_DEFAULT_HIZ_SHIFT); + if (error) { + dev_err(dev, "Failed to set LRA to HI-Z\n"); + goto err; + } + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + cs40l26->busy_state = 0; +#endif + + init_completion(&cs40l26->i2s_cont); + init_completion(&cs40l26->erase_cont); + init_completion(&cs40l26->cal_f0_cont); + init_completion(&cs40l26->cal_redc_cont); + init_completion(&cs40l26->cal_dvl_peq_cont); + init_completion(&cs40l26->cal_ls_cont); + + if (!cs40l26->fw_defer) { + error = cs40l26_fw_upload(cs40l26); + if (error) + goto err; + + error = cs40l26_request_irq(cs40l26); + if (error) + goto err; + } + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + samsung_input_data_init(cs40l26); + error = sec_vib_inputff_register(&cs40l26->sec_vib_ddata); +#else + error = cs40l26_input_init(cs40l26); +#endif + if (error) + goto err; + + INIT_LIST_HEAD(&cs40l26->effect_head); + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + cs40l26->input = cs40l26->sec_vib_ddata.input; + error = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, cs40l26_devs, + CS40L26_NUM_MFD_DEVS, NULL, 0, NULL); +#else + error = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, cs40l26_devs, + CS40L26_NUM_MFD_DEVS, NULL, 0, NULL); +#endif + if (error) { + dev_err(dev, "Failed to register codec component\n"); + goto err; + } + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "%s - end\n", __func__); +#endif + return 0; +err: + cs40l26_remove(cs40l26); + + return error; +} +EXPORT_SYMBOL_GPL(cs40l26_probe); + +int cs40l26_remove(struct cs40l26_private *cs40l26) +{ + struct regulator *vp_consumer = cs40l26_supplies[CS40L26_VP_SUPPLY].consumer; + struct regulator *va_consumer = cs40l26_supplies[CS40L26_VA_SUPPLY].consumer; + + disable_irq(cs40l26->irq); + mutex_destroy(&cs40l26->lock); + + cs40l26_pm_runtime_teardown(cs40l26); + + if (cs40l26->vibe_workqueue) { + cancel_work_sync(&cs40l26->vibe_start_work); + cancel_work_sync(&cs40l26->vibe_stop_work); + cancel_work_sync(&cs40l26->set_gain_work); + cancel_work_sync(&cs40l26->upload_work); + cancel_work_sync(&cs40l26->erase_work); + destroy_workqueue(cs40l26->vibe_workqueue); + } + + if (vp_consumer) + regulator_disable(vp_consumer); + + if (va_consumer) + regulator_disable(va_consumer); + + gpiod_set_value_cansleep(cs40l26->reset_gpio, 1); + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE +#ifdef CONFIG_DEBUG_FS + cs40l26_debugfs_cleanup(cs40l26); +#endif /* CONFIG_DEBUG_FS */ + sec_vib_inputff_unregister(&cs40l26->sec_vib_ddata); +#else + if (cs40l26->vibe_init_success) { + sysfs_remove_group(&cs40l26->input->dev.kobj, &cs40l26_dev_attr_group); + sysfs_remove_group(&cs40l26->input->dev.kobj, &cs40l26_dev_attr_cal_group); + sysfs_remove_group(&cs40l26->input->dev.kobj, &cs40l26_dev_attr_dbc_group); + } + +#ifdef CONFIG_DEBUG_FS + cs40l26_debugfs_cleanup(cs40l26); +#endif + + if (cs40l26->input) + input_unregister_device(cs40l26->input); +#endif + + return 0; +} +EXPORT_SYMBOL_GPL(cs40l26_remove); + +int cs40l26_pm_enter(struct device *dev) +{ + int error; + + error = pm_runtime_get_sync(dev); + if (error < 0) { + cs40l26_resume_error_handle(dev, error); + return error; + } + + return 0; +} +EXPORT_SYMBOL_GPL(cs40l26_pm_enter); + +void cs40l26_pm_exit(struct device *dev) +{ + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); +} +EXPORT_SYMBOL_GPL(cs40l26_pm_exit); + +int cs40l26_suspend(struct device *dev) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s: Enabling hibernation\n", __func__); +#else + dev_dbg(cs40l26->dev, "%s: Enabling hibernation\n", __func__); +#endif + return cs40l26_pm_state_transition(cs40l26, CS40L26_PM_STATE_ALLOW_HIBERNATE); +} +EXPORT_SYMBOL_GPL(cs40l26_suspend); + +int cs40l26_sys_suspend(struct device *dev) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + struct i2c_client *i2c_client = to_i2c_client(dev); + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "System suspend, disabling IRQ\n"); +#else + dev_dbg(cs40l26->dev, "System suspend, disabling IRQ\n"); +#endif + + disable_irq(i2c_client->irq); + + return 0; +} +EXPORT_SYMBOL_GPL(cs40l26_sys_suspend); + +int cs40l26_sys_suspend_noirq(struct device *dev) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + struct i2c_client *i2c_client = to_i2c_client(dev); + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "Late system suspend, re-enabling IRQ\n"); +#else + dev_dbg(cs40l26->dev, "Late system suspend, re-enabling IRQ\n"); +#endif + enable_irq(i2c_client->irq); + + return 0; +} +EXPORT_SYMBOL_GPL(cs40l26_sys_suspend_noirq); + +void cs40l26_resume_error_handle(struct device *dev, int error) +{ + dev_alert(dev, "PM Runtime Resume failed: %d\n", error); + + pm_runtime_set_active(dev); + + cs40l26_pm_exit(dev); +} +EXPORT_SYMBOL_GPL(cs40l26_resume_error_handle); + +int cs40l26_resume(struct device *dev) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s: Disabling hibernation\n", __func__); +#else + dev_dbg(cs40l26->dev, "%s: Disabling hibernation\n", __func__); +#endif + + return cs40l26_pm_state_transition(cs40l26, CS40L26_PM_STATE_PREVENT_HIBERNATE); +} +EXPORT_SYMBOL_GPL(cs40l26_resume); + +int cs40l26_sys_resume(struct device *dev) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + struct i2c_client *i2c_client = to_i2c_client(dev); + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "System resume, re-enabling IRQ\n"); +#else + dev_dbg(cs40l26->dev, "System resume, re-enabling IRQ\n"); +#endif + + enable_irq(i2c_client->irq); + + return 0; +} +EXPORT_SYMBOL_GPL(cs40l26_sys_resume); + +int cs40l26_sys_resume_noirq(struct device *dev) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(dev); + struct i2c_client *i2c_client = to_i2c_client(dev); + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "Early system resume, disabling IRQ\n"); +#else + dev_dbg(cs40l26->dev, "Early system resume, disabling IRQ\n"); +#endif + + disable_irq(i2c_client->irq); + + return 0; +} +EXPORT_SYMBOL_GPL(cs40l26_sys_resume_noirq); + +const struct dev_pm_ops cs40l26_pm_ops = { + SET_RUNTIME_PM_OPS(cs40l26_suspend, cs40l26_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(cs40l26_sys_suspend, cs40l26_sys_resume) + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(cs40l26_sys_suspend_noirq, cs40l26_sys_resume_noirq) +}; +EXPORT_SYMBOL_GPL(cs40l26_pm_ops); + +MODULE_DESCRIPTION("CS40L26 Boosted Mono Class D Amplifier for Haptics"); +MODULE_AUTHOR("Fred Treven, Cirrus Logic Inc. "); +MODULE_LICENSE("GPL"); diff --git a/include/dt-bindings/battery/sec-battery.h b/include/dt-bindings/battery/sec-battery.h new file mode 100644 index 000000000000..12fe38823750 --- /dev/null +++ b/include/dt-bindings/battery/sec-battery.h @@ -0,0 +1,378 @@ + +#ifndef _DT_BINDINGS_BATTERY_SEC_BATTERY_H +#define _DT_BINDINGS_BATTERY_SEC_BATTERY_H + +#define SEC_BATTERY_CABLE_UNKNOWN 0 +#define SEC_BATTERY_CABLE_NONE 1 +#define SEC_BATTERY_CABLE_PREPARE_TA 2 +#define SEC_BATTERY_CABLE_TA 3 +#define SEC_BATTERY_CABLE_USB 4 +#define SEC_BATTERY_CABLE_USB_CDP 5 +#define SEC_BATTERY_CABLE_9V_TA 6 +#define SEC_BATTERY_CABLE_9V_ERR 7 +#define SEC_BATTERY_CABLE_9V_UNKNOWN 8 +#define SEC_BATTERY_CABLE_12V_TA 9 +#define SEC_BATTERY_CABLE_WIRELESS 10 +#define SEC_BATTERY_CABLE_HV_WIRELESS 11 +#define SEC_BATTERY_CABLE_PMA_WIRELESS 12 +#define SEC_BATTERY_CABLE_WIRELESS_PACK 13 +#define SEC_BATTERY_CABLE_WIRELESS_HV_PACK 14 +#define SEC_BATTERY_CABLE_WIRELESS_STAND 15 +#define SEC_BATTERY_CABLE_WIRELESS_HV_STAND 16 +#define SEC_BATTERY_CABLE_QC20 17 +#define SEC_BATTERY_CABLE_QC30 18 +#define SEC_BATTERY_CABLE_PDIC 19 +#define SEC_BATTERY_CABLE_UARTOFF 20 +#define SEC_BATTERY_CABLE_OTG 21 +#define SEC_BATTERY_CABLE_LAN_HUB 22 +#define SEC_BATTERY_CABLE_POWER_SHARING 23 +#define SEC_BATTERY_CABLE_HMT_CONNECTED 24 +#define SEC_BATTERY_CABLE_HMT_CHARGE 25 +#define SEC_BATTERY_CABLE_HV_TA_CHG_LIMIT 26 +#define SEC_BATTERY_CABLE_WIRELESS_VEHICLE 27 +#define SEC_BATTERY_CABLE_WIRELESS_HV_VEHICLE 28 +#define SEC_BATTERY_CABLE_PREPARE_WIRELESS_HV 29 +#define SEC_BATTERY_CABLE_TIMEOUT 30 +#define SEC_BATTERY_CABLE_SMART_OTG 31 +#define SEC_BATTERY_CABLE_SMART_NOTG 32 +#define SEC_BATTERY_CABLE_WIRELESS_TX 33 +#define SEC_BATTERY_CABLE_HV_WIRELESS_20 34 +#define SEC_BATTERY_CABLE_HV_WIRELESS_20_LIMIT 35 +#define SEC_BATTERY_CABLE_WIRELESS_FAKE 36 +#define SEC_BATTERY_CABLE_PREPARE_WIRELESS_20 37 +#define SEC_BATTERY_CABLE_PDIC_APDO 38 +#define SEC_BATTERY_CABLE_POGO 39 +#define SEC_BATTERY_CABLE_POGO_9V 40 +#define SEC_BATTERY_CABLE_FPDO_DC 41 +#define SEC_BATTERY_CABLE_WIRELESS_EPP 42 +#define SEC_BATTERY_CABLE_LO_TA 43 +#define SEC_BATTERY_CABLE_WIRELESS_EPP_NV 44 +#define SEC_BATTERY_CABLE_WIRELESS_EPP_FAKE 45 +#define SEC_BATTERY_CABLE_MAX 46 + +/* d2d support type */ +#define SB_D2D_NONE 0 +#define SB_D2D_SNKONLY 1 +#define SB_D2D_SRCSNK 2 + +/* temperature check type */ +#define SEC_BATTERY_TEMP_CHECK_NONE 0 /* no temperature check */ +#define SEC_BATTERY_TEMP_CHECK_ADC 1 /* by ADC value */ +#define SEC_BATTERY_TEMP_CHECK_TEMP 2 /* by temperature */ +#define SEC_BATTERY_TEMP_CHECK_FAKE 3 /* by a fake temperature */ + +/* ADC type */ + /* NOT using this ADC channel */ +#define SEC_BATTERY_ADC_TYPE_NONE 0 + /* ADC in AP */ +#define SEC_BATTERY_ADC_TYPE_AP 1 + /* ADC by additional IC */ +#define SEC_BATTERY_ADC_TYPE_IC 2 +#define SEC_BATTERY_ADC_TYPE_NUM 3 + +/* ADC read type */ +#define SEC_BATTERY_ADC_PROCESSED 0 +#define SEC_BATTERY_ADC_RAW 1 + +/* thermal source */ +/* none */ +#define SEC_BATTERY_THERMAL_SOURCE_NONE 0 +/* by external source */ +#define SEC_BATTERY_THERMAL_SOURCE_CALLBACK 1 +/* by ADC */ +#define SEC_BATTERY_THERMAL_SOURCE_ADC 2 +/* by charger */ +#define SEC_BATTERY_THERMAL_SOURCE_CHG_ADC 3 +/* by fuel gauge */ +#define SEC_BATTERY_THERMAL_SOURCE_FG 4 +/* by fuel gauge adc */ +#define SEC_BATTERY_THERMAL_SOURCE_FG_ADC 5 + +#define SEC_BATTERY_CABLE_CHECK_NOUSBCHARGE 1 +/* SEC_BATTERY_CABLE_CHECK_NOINCOMPATIBLECHARGE + * for incompatible charger + * (Not compliant to USB specification, + * cable type is SEC_BATTERY_CABLE_UNKNOWN), + * do NOT charge and show message to user + * (only for VZW) + */ +#define SEC_BATTERY_CABLE_CHECK_NOINCOMPATIBLECHARGE 2 +/* SEC_BATTERY_CABLE_CHECK_PSY + * check cable by power supply set_property + */ +#define SEC_BATTERY_CABLE_CHECK_PSY 4 +/* SEC_BATTERY_CABLE_CHECK_INT + * check cable by interrupt + */ +#define SEC_BATTERY_CABLE_CHECK_INT 8 +/* SEC_BATTERY_CABLE_CHECK_CHGINT + * check cable by charger interrupt + */ +#define SEC_BATTERY_CABLE_CHECK_CHGINT 16 +/* SEC_BATTERY_CABLE_CHECK_POLLING + * check cable by GPIO polling + */ +#define SEC_BATTERY_CABLE_CHECK_POLLING 32 + + +/* SEC_BATTERY_CABLE_SOURCE_EXTERNAL + * already given by external argument + */ +#define SEC_BATTERY_CABLE_SOURCE_EXTERNAL 1 +/* SEC_BATTERY_CABLE_SOURCE_CALLBACK + * by callback (MUIC, USB switch) + */ +#define SEC_BATTERY_CABLE_SOURCE_CALLBACK 2 +/* SEC_BATTERY_CABLE_SOURCE_ADC + * by ADC + */ +#define SEC_BATTERY_CABLE_SOURCE_ADC 4 + + + /* polling work queue */ +#define SEC_BATTERY_MONITOR_WORKQUEUE 0 + /* alarm polling */ +#define SEC_BATTERY_MONITOR_ALARM 1 + /* timer polling (NOT USE) */ +#define SEC_BATTERY_MONITOR_TIMER 2 + +/* OVP, UVLO check : POWER_SUPPLY_PROP_HEALTH */ + + /* by callback function */ +#define SEC_BATTERY_OVP_UVLO_CALLBACK 0 + /* by PMIC polling */ +#define SEC_BATTERY_OVP_UVLO_PMICPOLLING 1 + /* by PMIC interrupt */ +#define SEC_BATTERY_OVP_UVLO_PMICINT 2 + /* by charger polling */ +#define SEC_BATTERY_OVP_UVLO_CHGPOLLING 3 + /* by charger interrupt */ +#define SEC_BATTERY_OVP_UVLO_CHGINT 4 + + +/* full charged check : POWER_SUPPLY_PROP_STATUS */ + +#define SEC_BATTERY_FULLCHARGED_NONE 0 + /* current check by ADC */ +#define SEC_BATTERY_FULLCHARGED_ADC 1 + /* fuel gauge current check */ +#define SEC_BATTERY_FULLCHARGED_FG_CURRENT 2 + /* time check */ +#define SEC_BATTERY_FULLCHARGED_TIME 3 + /* SOC check */ +#define SEC_BATTERY_FULLCHARGED_SOC 4 + /* charger GPIO, NO additional full condition */ +#define SEC_BATTERY_FULLCHARGED_CHGGPIO 5 + /* charger interrupt, NO additional full condition */ +#define SEC_BATTERY_FULLCHARGED_CHGINT 6 + /* charger power supply property, NO additional full condition */ +#define SEC_BATTERY_FULLCHARGED_CHGPSY 7 + /* Limiter power supply property, NO additional full condition */ +#define SEC_BATTERY_FULLCHARGED_LIMITER 8 + +#define TEMP_CONTROL_SOURCE_NONE 0 +#define TEMP_CONTROL_SOURCE_BAT_THM 1 +#define TEMP_CONTROL_SOURCE_CHG_THM 2 +#define TEMP_CONTROL_SOURCE_WPC_THM 3 +#define TEMP_CONTROL_SOURCE_USB_THM 4 + +/* SEC_BATTERY_FULL_CONDITION_NOTIMEFULL + * full-charged by absolute-timer only in high voltage + */ +#define SEC_BATTERY_FULL_CONDITION_NOTIMEFULL 1 +/* SEC_BATTERY_FULL_CONDITION_NOSLEEPINFULL + * do not set polling time as sleep polling time in full-charged + */ +#define SEC_BATTERY_FULL_CONDITION_NOSLEEPINFULL 2 +/* SEC_BATTERY_FULL_CONDITION_SOC + * use capacity for full-charged check + */ +#define SEC_BATTERY_FULL_CONDITION_SOC 4 +/* SEC_BATTERY_FULL_CONDITION_VCELL + * use VCELL for full-charged check + */ +#define SEC_BATTERY_FULL_CONDITION_VCELL 8 +/* SEC_BATTERY_FULL_CONDITION_AVGVCELL + * use average VCELL for full-charged check + */ +#define SEC_BATTERY_FULL_CONDITION_AVGVCELL 16 +/* SEC_BATTERY_FULL_CONDITION_OCV + * use OCV for full-charged check + */ +#define SEC_BATTERY_FULL_CONDITION_OCV 32 + +/* recharge check condition type (can be used overlapped) */ +#define sec_battery_recharge_condition_t unsigned int +/* SEC_BATTERY_RECHARGE_CONDITION_SOC + * use capacity for recharging check + */ +#define SEC_BATTERY_RECHARGE_CONDITION_SOC 1 +/* SEC_BATTERY_RECHARGE_CONDITION_AVGVCELL + * use average VCELL for recharging check + */ +#define SEC_BATTERY_RECHARGE_CONDITION_AVGVCELL 2 +/* SEC_BATTERY_RECHARGE_CONDITION_VCELL + * use VCELL for recharging check + */ +#define SEC_BATTERY_RECHARGE_CONDITION_VCELL 4 + +/* SEC_BATTERY_RECHARGE_CONDITION_LIMITER + * use VCELL of LIMITER for recharging check + */ +#define SEC_BATTERY_RECHARGE_CONDITION_LIMITER 8 + +#define SIOP_DEFAULT 0xFFFF +#define SIOP_SKIP 0xFFFE + +/* inbat ocv type */ +#define SEC_BATTERY_OCV_NONE 0 +#define SEC_BATTERY_OCV_FG_SRC_CHANGE 1 +#define SEC_BATTERY_OCV_FG_NOSRC_CHANGE 2 +#define SEC_BATTERY_OCV_ADC 3 +#define SEC_BATTERY_OCV_VOLT_FROM_PMIC 4 + + +/* enum sec_wireless_rx_power_list */ +#define SEC_WIRELESS_RX_POWER_3W 3000000 +#define SEC_WIRELESS_RX_POWER_5W 5000000 +#define SEC_WIRELESS_RX_POWER_6_5W 6500000 +#define SEC_WIRELESS_RX_POWER_7_5W 7500000 +#define SEC_WIRELESS_RX_POWER_10W 10000000 +#define SEC_WIRELESS_RX_POWER_12W 12000000 +#define SEC_WIRELESS_RX_POWER_15W 15000000 +#define SEC_WIRELESS_RX_POWER_17_5W 17500000 +#define SEC_WIRELESS_RX_POWER_20W 20000000 +#define SEC_WIRELESS_RX_POWER_MAX 20000000 + +/* enum sec_wireless_rx_power_class_list */ +#define SEC_WIRELESS_RX_POWER_CLASS_1 1 /* 4.5W ~ 7.5W */ +#define SEC_WIRELESS_RX_POWER_CLASS_2 2 /* 7.6W ~ 12W */ +#define SEC_WIRELESS_RX_POWER_CLASS_3 3 /* 12.1W ~ 20W */ +#define SEC_WIRELESS_RX_POWER_CLASS_4 4 /* reserved */ +#define SEC_WIRELESS_RX_POWER_CLASS_5 5 /* reserved */ + +#define SEC_WIRELESS_PHM_VOUT_CTRL_NO_DEV 0 +#define SEC_WIRELESS_PHM_VOUT_CTRL_OTHER_DEV 1 +#define SEC_WIRELESS_PHM_VOUT_CTRL_GEAR 2 +#define SEC_WIRELESS_PHM_VOUT_CTRL_PHONE 4 +#define SEC_WIRELESS_PHM_VOUT_CTRL_BUDS 8 + +/* enum sec_wireless_rx_control_mode */ +#define WIRELESS_PAD_FAN_OFF 0 +#define WIRELESS_PAD_FAN_ON 1 +#define WIRELESS_PAD_LED_OFF 2 +#define WIRELESS_PAD_LED_ON 3 +#define WIRELESS_PAD_LED_DIMMING 4 +#define WIRELESS_VRECT_ADJ_ON 5 +#define WIRELESS_VRECT_ADJ_OFF 6 +#define WIRELESS_VRECT_ADJ_ROOM_0 7 +#define WIRELESS_VRECT_ADJ_ROOM_1 8 +#define WIRELESS_VRECT_ADJ_ROOM_2 9 +#define WIRELESS_VRECT_ADJ_ROOM_3 10 +#define WIRELESS_VRECT_ADJ_ROOM_4 11 +#define WIRELESS_VRECT_ADJ_ROOM_5 12 +#define WIRELESS_CLAMP_ENABLE 13 +#define WIRELESS_SLEEP_MODE_ENABLE 14 +#define WIRELESS_SLEEP_MODE_DISABLE 15 + +/* enum sec_wireless_tx_vout */ +#define WC_TX_VOUT_OFF 0 +#define WC_TX_VOUT_5000MV 5000 +#define WC_TX_VOUT_5500MV 5500 +#define WC_TX_VOUT_6000MV 6000 +#define WC_TX_VOUT_6500MV 6500 +#define WC_TX_VOUT_7000MV 7000 +#define WC_TX_VOUT_7500MV 7500 +#define WC_TX_VOUT_8000MV 8000 +#define WC_TX_VOUT_8500MV 8500 +#define WC_TX_VOUT_9000MV 9000 +#define WC_TX_VOUT_MIN WC_TX_VOUT_5000MV +#define WC_TX_VOUT_MAX WC_TX_VOUT_9000MV +#define WC_TX_VOUT_STEP_AOV 500 + +/* enum sec_wireless_vout_control_mode */ +#define WIRELESS_VOUT_OFF 0 +#define WIRELESS_VOUT_NORMAL_VOLTAGE 1 /* 5V , reserved by factory */ +#define WIRELESS_VOUT_RESERVED 2 /* 6V */ +#define WIRELESS_VOUT_HIGH_VOLTAGE 3 /* 9V , reserved by factory */ +#define WIRELESS_VOUT_CC_CV_VOUT 4 +#define WIRELESS_VOUT_CALL 5 +#define WIRELESS_VOUT_5V 6 +#define WIRELESS_VOUT_9V 7 +#define WIRELESS_VOUT_10V 8 +#define WIRELESS_VOUT_11V 9 +#define WIRELESS_VOUT_12V 10 +#define WIRELESS_VOUT_12_5V 11 +#define WIRELESS_VOUT_4_5V_STEP 12 +#define WIRELESS_VOUT_5V_STEP 13 +#define WIRELESS_VOUT_5_5V_STEP 14 +#define WIRELESS_VOUT_9V_STEP 15 +#define WIRELESS_VOUT_10V_STEP 16 +#define WIRELESS_VOUT_OTG 17 +#define WIRELESS_VOUT_FORCE_9V 18 +#define WIRELESS_VOUT_5_5V 19 +#define WIRELESS_VOUT_4_5V 20 +#define WIRELESS_VOUT_FORCE_4_7V 21 +#define WIRELESS_VOUT_FORCE_4_8V 22 +#define WIRELESS_VOUT_FORCE_4_9V 23 +#define WIRELESS_VOUT_FORCE_5V 24 + +/* enum mfc_send_command */ +#define MFC_END_SIG_STRENGTH 0 +#define MFC_END_POWER_TRANSFER 1 +#define MFC_END_CTR_ERROR 2 +#define MFC_END_RECEIVED_POWER 3 +#define MFC_END_CHARGE_STATUS 4 +#define MFC_POWER_CTR_HOLD_OFF 5 +#define MFC_AFC_CONF_5V 6 +#define MFC_AFC_CONF_10V 7 +#define MFC_AFC_CONF_5V_TX 8 +#define MFC_AFC_CONF_10V_TX 9 +#define MFC_AFC_CONF_12V_TX 10 +#define MFC_AFC_CONF_12_5V_TX 11 +#define MFC_AFC_CONF_20V_TX 12 +#define MFC_CONFIGURATION 13 +#define MFC_IDENTIFICATION 14 +#define MFC_EXTENDED_IDENT 15 +#define MFC_LED_CONTROL_ON 16 +#define MFC_LED_CONTROL_OFF 17 +#define MFC_FAN_CONTROL_ON 18 +#define MFC_FAN_CONTROL_OFF 19 +#define MFC_REQUEST_AFC_TX 20 +#define MFC_REQUEST_TX_ID 21 +#define MFC_DISABLE_TX 22 +#define MFC_PHM_ON 23 +#define MFC_LED_CONTROL_DIMMING 24 +#define MFC_SET_OP_FREQ 25 +#define MFC_TX_UNO_OFF 26 +#define MFC_REQ_TX_PWR_BUDG 27 + +#define MFC_VOUT_4_5V 0 +#define MFC_VOUT_4_7V 1 +#define MFC_VOUT_4_8V 2 +#define MFC_VOUT_4_9V 3 +#define MFC_VOUT_5V 4 +#define MFC_VOUT_5_5V 5 +#define MFC_VOUT_6V 6 +#define MFC_VOUT_7V 7 +#define MFC_VOUT_8V 8 +#define MFC_VOUT_9V 9 +#define MFC_VOUT_10V 10 +#define MFC_VOUT_11V 11 +#define MFC_VOUT_12V 12 +#define MFC_VOUT_12_5V 13 +#define MFC_VOUT_OTG 14 + +/* fod macro */ +#define FOD_FLAG_NONE 0 +#define FOD_FLAG_ADD 1 +#define FOD_FLAG_USE_CC 2 +#define FOD_FLAG_USE_CV 3 +#define FOD_FLAG_USE_FULL 4 +#define FOD_FLAG_USE_DEF_PAD 5 +#define FOD_FLAG_USE_DEF_OP 6 +#define SET_FOD_CC(_flag) (FOD_FLAG_ ##_flag) +#define SET_FOD_CV(_flag) (FOD_FLAG_ ##_flag << 4) +#define SET_FOD_FULL(_flag) (FOD_FLAG_ ##_flag << 8) + +#endif /* _DT_BINDINGS_BATTERY_SEC_BATTERY_H */ diff --git a/include/dt-bindings/camera/msm-camera.h b/include/dt-bindings/camera/msm-camera.h new file mode 100644 index 000000000000..766a0dad3309 --- /dev/null +++ b/include/dt-bindings/camera/msm-camera.h @@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2018-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef __MSM_CAMERA_H +#define __MSM_CAMERA_H + +/* CPAS path data types */ +#define CAM_CPAS_PATH_DATA_IFE_START_OFFSET 0 +#define CAM_CPAS_PATH_DATA_IFE_LINEAR (CAM_CPAS_PATH_DATA_IFE_START_OFFSET + 0) +#define CAM_CPAS_PATH_DATA_IFE_VID (CAM_CPAS_PATH_DATA_IFE_START_OFFSET + 1) +#define CAM_CPAS_PATH_DATA_IFE_DISP (CAM_CPAS_PATH_DATA_IFE_START_OFFSET + 2) +#define CAM_CPAS_PATH_DATA_IFE_STATS (CAM_CPAS_PATH_DATA_IFE_START_OFFSET + 3) +#define CAM_CPAS_PATH_DATA_IFE_RDI0 (CAM_CPAS_PATH_DATA_IFE_START_OFFSET + 4) +#define CAM_CPAS_PATH_DATA_IFE_RDI1 (CAM_CPAS_PATH_DATA_IFE_START_OFFSET + 5) +#define CAM_CPAS_PATH_DATA_IFE_RDI2 (CAM_CPAS_PATH_DATA_IFE_START_OFFSET + 6) +#define CAM_CPAS_PATH_DATA_IFE_RDI3 (CAM_CPAS_PATH_DATA_IFE_START_OFFSET + 7) +#define CAM_CPAS_PATH_DATA_IFE_PDAF (CAM_CPAS_PATH_DATA_IFE_START_OFFSET + 8) +#define CAM_CPAS_PATH_DATA_IFE_PIXEL_RAW \ + (CAM_CPAS_PATH_DATA_IFE_START_OFFSET + 9) +#define CAM_CPAS_PATH_DATA_IFE_MAX_OFFSET \ + (CAM_CPAS_PATH_DATA_IFE_START_OFFSET + 31) + +#define CAM_CPAS_PATH_DATA_IPE_START_OFFSET 32 +#define CAM_CPAS_PATH_DATA_IPE_RD_IN (CAM_CPAS_PATH_DATA_IPE_START_OFFSET + 0) +#define CAM_CPAS_PATH_DATA_IPE_RD_REF (CAM_CPAS_PATH_DATA_IPE_START_OFFSET + 1) +#define CAM_CPAS_PATH_DATA_IPE_WR_VID (CAM_CPAS_PATH_DATA_IPE_START_OFFSET + 2) +#define CAM_CPAS_PATH_DATA_IPE_WR_DISP (CAM_CPAS_PATH_DATA_IPE_START_OFFSET + 3) +#define CAM_CPAS_PATH_DATA_IPE_WR_REF (CAM_CPAS_PATH_DATA_IPE_START_OFFSET + 4) +#define CAM_CPAS_PATH_DATA_IPE_WR_APP (CAM_CPAS_PATH_DATA_IPE_START_OFFSET + 5) +#define CAM_CPAS_PATH_DATA_IPE_MAX_OFFSET \ + (CAM_CPAS_PATH_DATA_IPE_START_OFFSET + 31) + +#define CAM_CPAS_PATH_DATA_OPE_START_OFFSET 64 +#define CAM_CPAS_PATH_DATA_OPE_RD_IN (CAM_CPAS_PATH_DATA_OPE_START_OFFSET + 0) +#define CAM_CPAS_PATH_DATA_OPE_RD_REF (CAM_CPAS_PATH_DATA_OPE_START_OFFSET + 1) +#define CAM_CPAS_PATH_DATA_OPE_WR_VID (CAM_CPAS_PATH_DATA_OPE_START_OFFSET + 2) +#define CAM_CPAS_PATH_DATA_OPE_WR_DISP (CAM_CPAS_PATH_DATA_OPE_START_OFFSET + 3) +#define CAM_CPAS_PATH_DATA_OPE_WR_REF (CAM_CPAS_PATH_DATA_OPE_START_OFFSET + 4) +#define CAM_CPAS_PATH_DATA_OPE_MAX_OFFSET \ + (CAM_CPAS_PATH_DATA_OPE_START_OFFSET + 31) + +#define CAM_CPAS_PATH_DATA_SFE_START_OFFSET 96 +#define CAM_CPAS_PATH_DATA_SFE_NRDI (CAM_CPAS_PATH_DATA_SFE_START_OFFSET + 0) +#define CAM_CPAS_PATH_DATA_SFE_RDI0 (CAM_CPAS_PATH_DATA_SFE_START_OFFSET + 1) +#define CAM_CPAS_PATH_DATA_SFE_RDI1 (CAM_CPAS_PATH_DATA_SFE_START_OFFSET + 2) +#define CAM_CPAS_PATH_DATA_SFE_RDI2 (CAM_CPAS_PATH_DATA_SFE_START_OFFSET + 3) +#define CAM_CPAS_PATH_DATA_SFE_RDI3 (CAM_CPAS_PATH_DATA_SFE_START_OFFSET + 4) +#define CAM_CPAS_PATH_DATA_SFE_RDI4 (CAM_CPAS_PATH_DATA_SFE_START_OFFSET + 5) +#define CAM_CPAS_PATH_DATA_SFE_STATS (CAM_CPAS_PATH_DATA_SFE_START_OFFSET + 6) +#define CAM_CPAS_PATH_DATA_SFE_MAX_OFFSET \ + (CAM_CPAS_PATH_DATA_SFE_START_OFFSET + 31) + +#define CAM_CPAS_PATH_DATA_CRE_START_OFFSET (CAM_CPAS_PATH_DATA_SFE_MAX_OFFSET + 1) +#define CAM_CPAS_PATH_DATA_CRE_RD_IN (CAM_CPAS_PATH_DATA_CRE_START_OFFSET + 0) +#define CAM_CPAS_PATH_DATA_CRE_WR_OUT (CAM_CPAS_PATH_DATA_CRE_START_OFFSET + 1) +#define CAM_CPAS_PATH_DATA_CRE_MAX_OFFSET \ + (CAM_CPAS_PATH_DATA_CRE_START_OFFSET + 31) + +#define CAM_CPAS_PATH_DATA_OFE_START_OFFSET (CAM_CPAS_PATH_DATA_CRE_MAX_OFFSET + 1) +#define CAM_CPAS_PATH_DATA_OFE_RD_EXT (CAM_CPAS_PATH_DATA_OFE_START_OFFSET + 0) +#define CAM_CPAS_PATH_DATA_OFE_RD_INT_PDI (CAM_CPAS_PATH_DATA_OFE_START_OFFSET + 1) +#define CAM_CPAS_PATH_DATA_OFE_RD_INT_HDR (CAM_CPAS_PATH_DATA_OFE_START_OFFSET + 2) +#define CAM_CPAS_PATH_DATA_OFE_WR_VID (CAM_CPAS_PATH_DATA_OFE_START_OFFSET + 3) +#define CAM_CPAS_PATH_DATA_OFE_WR_DISP (CAM_CPAS_PATH_DATA_OFE_START_OFFSET + 4) +#define CAM_CPAS_PATH_DATA_OFE_WR_IR (CAM_CPAS_PATH_DATA_OFE_START_OFFSET + 5) +#define CAM_CPAS_PATH_DATA_OFE_WR_HDR_LTM (CAM_CPAS_PATH_DATA_OFE_START_OFFSET + 6) +#define CAM_CPAS_PATH_DATA_OFE_WR_DC4 (CAM_CPAS_PATH_DATA_OFE_START_OFFSET + 7) +#define CAM_CPAS_PATH_DATA_OFE_WR_AI (CAM_CPAS_PATH_DATA_OFE_START_OFFSET + 8) +#define CAM_CPAS_PATH_DATA_OFE_WR_PDI (CAM_CPAS_PATH_DATA_OFE_START_OFFSET + 9) +#define CAM_CPAS_PATH_DATA_OFE_WR_IDEALRAW (CAM_CPAS_PATH_DATA_OFE_START_OFFSET + 10) +#define CAM_CPAS_PATH_DATA_OFE_WR_STATS (CAM_CPAS_PATH_DATA_OFE_START_OFFSET + 11) +#define CAM_CPAS_PATH_DATA_OFE_MAX_OFFSET \ + (CAM_CPAS_PATH_DATA_OFE_START_OFFSET + 31) + +#define CAM_CPAS_PATH_DATA_CONSO_OFFSET 256 +#define CAM_CPAS_PATH_DATA_ALL (CAM_CPAS_PATH_DATA_CONSO_OFFSET + 0) + +/* IFE consolidated paths */ +#define CAM_CPAS_PATH_DATA_IFE_LINEAR_PDAF (CAM_CPAS_PATH_DATA_CONSO_OFFSET + 1) +#define CAM_CPAS_PATH_DATA_IFE_UBWC_STATS (CAM_CPAS_PATH_DATA_CONSO_OFFSET + 2) +#define CAM_CPAS_PATH_DATA_IFE_PIXEL_ALL (CAM_CPAS_PATH_DATA_CONSO_OFFSET + 3) +#define CAM_CPAS_PATH_DATA_IFE_RDI_PIXEL_RAW (CAM_CPAS_PATH_DATA_CONSO_OFFSET + 4) +#define CAM_CPAS_PATH_DATA_IFE_RDI_ALL (CAM_CPAS_PATH_DATA_CONSO_OFFSET + 5) +#define CAM_CPAS_PATH_DATA_IFE_UBWC (CAM_CPAS_PATH_DATA_CONSO_OFFSET + 6) +#define CAM_CPAS_PATH_DATA_IFE_LINEAR_STATS (CAM_CPAS_PATH_DATA_CONSO_OFFSET + 7) +#define CAM_CPAS_PATH_DATA_IFE_UBWC_LINEAR (CAM_CPAS_PATH_DATA_CONSO_OFFSET + 8) +#define CAM_CPAS_PATH_DATA_IFE_PDAF_LINEAR (CAM_CPAS_PATH_DATA_CONSO_OFFSET + 9) + +/* IPE Consolidated paths */ +#define CAM_CPAS_PATH_DATA_IPE_WR_VID_DISP (CAM_CPAS_PATH_DATA_CONSO_OFFSET + 1) + +/* CPAS transaction types */ +#define CAM_CPAS_TRANSACTION_READ 0 +#define CAM_CPAS_TRANSACTION_WRITE 1 + +/* CPAS traffic merge types */ +#define CAM_CPAS_TRAFFIC_MERGE_SUM 0 +#define CAM_CPAS_TRAFFIC_MERGE_SUM_INTERLEAVE 1 + +/* Feature bit type */ +#define CAM_CPAS_FEATURE_TYPE_DISABLE 0 +#define CAM_CPAS_FEATURE_TYPE_ENABLE 1 +#define CAM_CPAS_FEATURE_TYPE_VALUE 2 + +/* Feature support bit positions in feature fuse register*/ +#define CAM_CPAS_QCFA_BINNING_ENABLE 0 +#define CAM_CPAS_SECURE_CAMERA_ENABLE 1 +#define CAM_CPAS_MF_HDR_ENABLE 2 +#define CAM_CPAS_MP_LIMIT_FUSE 3 +#define CAM_CPAS_ISP_FUSE 4 +#define CAM_CPAS_ISP_PIX_FUSE 5 +#define CAM_CPAS_ISP_LITE_FUSE 6 +#define CAM_CPAS_CSIPHY_FUSE 7 +#define CAM_CPAS_IPE_VID_OUT_8BPP_LIMIT_ENABLE 8 +#define CAM_CPAS_FUSE_FEATURE_MAX 9 + +#define CCI_MASTER_0 0 +#define CCI_MASTER_1 1 +#define CCI_MASTER_MAX 2 + +/* AON Camera IDs*/ +#define AON_CAM1 0 +#define AON_CAM2 1 +#define MAX_AON_CAM 2 +#define NOT_AON_CAM 255 + +/* Camera DRV enable masks */ +#define CAM_DDR_DRV 0x1 +#define CAM_CLK_DRV 0x2 + +/* Port index for BW voting */ +#define CAM_CPAS_PORT_HLOS_DRV 0 +#define CAM_CPAS_PORT_DRV_0 1 +#define CAM_CPAS_PORT_DRV_1 2 +#define CAM_CPAS_PORT_DRV_2 3 +#define CAM_CPAS_PORT_DRV_DYN 32 + +/* Domain ID types */ +#define CAM_CPAS_NON_SECURE_DOMAIN 0 +#define CAM_CPAS_SECURE_DOMAIN 1 + +/* Debug bypass driver */ +#define CAM_BYPASS_RGLTR 0x1 +#define CAM_BYPASS_RGLTR_MODE 0x2 +#define CAM_BYPASS_CLKS 0x4 +#define CAM_BYPASS_CESTA 0x8 +#define CAM_BYPASS_ICC 0x10 + +#endif diff --git a/include/dt-bindings/samsung/debug/qcom/sec_qc_irq_exit_log.h b/include/dt-bindings/samsung/debug/qcom/sec_qc_irq_exit_log.h new file mode 100644 index 000000000000..ba753cb41f9b --- /dev/null +++ b/include/dt-bindings/samsung/debug/qcom/sec_qc_irq_exit_log.h @@ -0,0 +1,7 @@ +#ifndef __DT_BINDINGS__SEC_QC_IRQ_EXIT_LOG_H__ +#define __DT_BINDINGS__SEC_QC_IRQ_EXIT_LOG_H__ + +/* crc32 <(echo -n "SEC_QC_IRQ_EXIT_LOG_MAGIC") | tr '[a-z]' '[A-Z]' */ +#define SEC_QC_IRQ_EXIT_LOG_MAGIC 0xCD0BD0E1 + +#endif /* __DT_BINDINGS__SEC_QC_IRQ_EXIT_LOG_H__ */ diff --git a/include/dt-bindings/samsung/debug/qcom/sec_qc_irq_log.h b/include/dt-bindings/samsung/debug/qcom/sec_qc_irq_log.h new file mode 100644 index 000000000000..9587027e402e --- /dev/null +++ b/include/dt-bindings/samsung/debug/qcom/sec_qc_irq_log.h @@ -0,0 +1,7 @@ +#ifndef __DT_BINDINGS__SEC_QC_IRQ_LOG_H__ +#define __DT_BINDINGS__SEC_QC_IRQ_LOG_H__ + +/* crc32 <(echo -n "SEC_QC_IRQ_LOG_MAGIC") | tr '[a-z]' '[A-Z]' */ +#define SEC_QC_IRQ_LOG_MAGIC 0x811E64D7 + +#endif /* __DT_BINDINGS__SEC_QC_IRQ_LOG_H__ */ diff --git a/include/dt-bindings/samsung/debug/qcom/sec_qc_msg_log.h b/include/dt-bindings/samsung/debug/qcom/sec_qc_msg_log.h new file mode 100644 index 000000000000..55ed211e2a63 --- /dev/null +++ b/include/dt-bindings/samsung/debug/qcom/sec_qc_msg_log.h @@ -0,0 +1,7 @@ +#ifndef __DT_BINDINGS__SEC_QC_MSG_LOG_H__ +#define __DT_BINDINGS__SEC_QC_MSG_LOG_H__ + +/* crc32 <(echo -n "SEC_QC_MSG_LOG_MAGIC") | tr '[a-z]' '[A-Z]' */ +#define SEC_QC_MSG_LOG_MAGIC 0x9C414CA6 + +#endif /* __DT_BINDINGS__SEC_QC_MSG_LOG_H__ */ diff --git a/include/dt-bindings/samsung/debug/qcom/sec_qc_sched_log.h b/include/dt-bindings/samsung/debug/qcom/sec_qc_sched_log.h new file mode 100644 index 000000000000..ac78596aad2e --- /dev/null +++ b/include/dt-bindings/samsung/debug/qcom/sec_qc_sched_log.h @@ -0,0 +1,7 @@ +#ifndef __DT_BINDINGS__SEC_QC_SCHED_LOG_H__ +#define __DT_BINDINGS__SEC_QC_SCHED_LOG_H__ + +/* crc32 <(echo -n "SEC_QC_SCHED_LOG_MAGIC") | tr '[a-z]' '[A-Z]' */ +#define SEC_QC_SCHED_LOG_MAGIC 0x705E7C9E + +#endif /* __DT_BINDINGS__SEC_QC_SCHED_LOG_H__ */ diff --git a/include/dt-bindings/samsung/debug/sec_arm64_vh_ipi_stop.h b/include/dt-bindings/samsung/debug/sec_arm64_vh_ipi_stop.h new file mode 100644 index 000000000000..d2f6301a7a24 --- /dev/null +++ b/include/dt-bindings/samsung/debug/sec_arm64_vh_ipi_stop.h @@ -0,0 +1,7 @@ +#ifndef __DT_BINDINGS__SEC_ARM64_VH_IPI_STOP_H__ +#define __DT_BINDINGS__SEC_ARM64_VH_IPI_STOP_H__ + +/* crc32 <(echo -n "SEC_ARM64_VH_IPI_STOP_MAGIC") | tr '[a-z]' '[A-Z]' */ +#define SEC_ARM64_VH_IPI_STOP_MAGIC 0x86655958 + +#endif /* __DT_BINDINGS__SEC_ARM64_VH_IPI_STOP_H__ */ diff --git a/include/dt-bindings/samsung/debug/sec_crashkey.h b/include/dt-bindings/samsung/debug/sec_crashkey.h new file mode 100644 index 000000000000..42dca651bd89 --- /dev/null +++ b/include/dt-bindings/samsung/debug/sec_crashkey.h @@ -0,0 +1,12 @@ +#ifndef __DT_BINDINGS__SEC_CRASHKEY_H__ +#define __DT_BINDINGS__SEC_CRASHKEY_H__ + +#define EVENT_KEY_PRESS(__keycode) \ + <__keycode 1> +#define EVENT_KEY_RELEASE(__keycode) \ + <__keycode 0> +#define EVENT_KEY_PRESS_AND_RELEASE(__keycode) \ + EVENT_KEY_PRESS(__keycode), \ + EVENT_KEY_RELEASE(__keycode) + +#endif /* __DT_BINDINGS__SEC_CRASHKEY_H__ */ diff --git a/include/dt-bindings/samsung/debug/sec_debug.h b/include/dt-bindings/samsung/debug/sec_debug.h new file mode 100644 index 000000000000..b6e926121949 --- /dev/null +++ b/include/dt-bindings/samsung/debug/sec_debug.h @@ -0,0 +1,8 @@ +#ifndef __DT_BINDINGS__SEC_DEBUG_H__ +#define __DT_BINDINGS__SEC_DEBUG_H__ + +#define SEC_DEBUG_LEVEL_LOW 0x4F4C +#define SEC_DEBUG_LEVEL_MID 0x494D +#define SEC_DEBUG_LEVEL_HIGH 0x4948 + +#endif /* __DT_BINDINGS__SEC_DEBUG_H__ */ diff --git a/include/dt-bindings/samsung/debug/sec_log_buf.h b/include/dt-bindings/samsung/debug/sec_log_buf.h new file mode 100644 index 000000000000..a81cdfabc716 --- /dev/null +++ b/include/dt-bindings/samsung/debug/sec_log_buf.h @@ -0,0 +1,10 @@ +#ifndef __DT_BINDINGS__SEC_LOG_BUF_H__ +#define __DT_BINDINGS__SEC_LOG_BUF_H__ + +#define SEC_LOG_BUF_STRATEGY_BUILTIN 0 +#define SEC_LOG_BUF_STRATEGY_KPROBE 1 +#define SEC_LOG_BUF_STRATEGY_CONSOLE 2 +#define SEC_LOG_BUF_STRATEGY_TP_CONSOLE 3 +#define SEC_LOG_BUF_NR_STRATEGIES (SEC_LOG_BUF_STRATEGY_TP_CONSOLE + 1) + +#endif /* __DT_BINDINGS__SEC_LOG_BUF_H__ */ diff --git a/include/dt-bindings/samsung/debug/sec_riscv64_vh_ipi_stop.h b/include/dt-bindings/samsung/debug/sec_riscv64_vh_ipi_stop.h new file mode 100644 index 000000000000..d1d719b48010 --- /dev/null +++ b/include/dt-bindings/samsung/debug/sec_riscv64_vh_ipi_stop.h @@ -0,0 +1,7 @@ +#ifndef __DT_BINDINGS__SEC_RISCV64_VH_IPI_STOP_H__ +#define __DT_BINDINGS__SEC_RISCV64_VH_IPI_STOP_H__ + +/* crc32 <(echo -n "SEC_RISCV64_VH_IPI_STOP_MAGIC") | tr '[a-z]' '[A-Z]' */ +#define SEC_RISCV64_VH_IPI_STOP_MAGIC 0x699BB6F4 + +#endif /* __DT_BINDINGS__SEC_RISCV64_VH_IPI_STOP_H__ */ diff --git a/include/linux/adsp/adsp_ft_common.h b/include/linux/adsp/adsp_ft_common.h new file mode 100755 index 000000000000..3d50fa9d6f97 --- /dev/null +++ b/include/linux/adsp/adsp_ft_common.h @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __ADSP_FT_COMMON_H__ +#define __ADSP_FT_COMMON_H__ + +#ifdef SS_SLPI_PROJECT// hal build +#ifndef IS_ENABLED +#define IS_ENABLED(x) 0 +#endif +#else// kernel build +#include +#endif + +#define PID 20000 +#define NETLINK_ADSP_FAC 23 +#define MAX_REG_NUM 128 + +/* max size of each sensor's msg_buf */ +#define MSG_TYPE_SIZE_ZERO 0 +#define MSG_ACCEL_MAX 128 +#define MSG_GYRO_MAX 20 +#define MSG_MAG_MAX 15 +#define MSG_LIGHT_MAX 16 +#define MSG_PROX_MAX 16 +#define MSG_GYRO_TEMP_MAX 3 +#define MSG_PRESSURE_TEMP_MAX 3 +#define MSG_PRESSURE_MAX 128 +#define MSG_FLIP_COVER_DETECTOR_MAX 3 +#define MSG_BACKTAP_MAX 1 +#define MSG_VOPTIC_MAX 2 +#define MSG_REG_SNS_MAX 24 /* 8 * 3 */ +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) || defined(CONFIG_SUPPORT_AK09973) +#define MSG_DIGITAL_HALL_MAX 15 +#define MSG_DIGITAL_HALL_ANGLE_MAX 58 +#elif IS_ENABLED(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) || defined(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) +#define MSG_REF_ANGLE_MAX 9 +#endif +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_DDI_COPR_FOR_LIGHT_SENSOR) || defined(CONFIG_SUPPORT_DUAL_DDI_COPR_FOR_LIGHT_SENSOR) +#define MSG_DDI_MAX 12 +#endif +#if IS_ENABLED(CONFIG_SUPPORT_FLICKER) || defined(CONFIG_SUPPORT_FLICKER) +#define MSG_FLICKER_MAX 12 +#endif +#define MSG_COMMON_INFO_MAX BD_SENSOR_MAX + +#define ACCEL_FACTORY_CAL_PATH "/efs/FactoryApp/accel_factory_cal" +#define SUB_ACCEL_FACTORY_CAL_PATH "/efs/FactoryApp/sub_accel_factory_cal" +#define SW_OFFSET_FILE_PATH "/efs/FactoryApp/baro_sw_offset" + +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) || defined(CONFIG_SUPPORT_AK09973) +#define AUTO_CAL_DATA_NUM 19 +#define AUTO_CAL_FILE_BUF_LEN 140 +#define DIGITAL_HALL_AUTO_CAL_X_PATH "/efs/FactoryApp/digital_hall_auto_cal_x" +#define DIGITAL_HALL_AUTO_CAL_Y_PATH "/efs/FactoryApp/digital_hall_auto_cal_y" +#define DIGITAL_HALL_AUTO_CAL_Z_PATH "/efs/FactoryApp/digital_hall_auto_cal_z" +#define ENABLE_LF_STREAM 0 +#endif + +enum { + MSG_ACCEL, + MSG_GYRO, + MSG_MAG, + MSG_PRESSURE, + MSG_LIGHT, + MSG_PROX, +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) || defined(CONFIG_SUPPORT_DUAL_OPTIC) + MSG_LIGHT_SUB, + MSG_PROX_SUB, +#endif +#if IS_ENABLED(CONFIG_FLICKER_FACTORY) || defined(CONFIG_FLICKER_FACTORY) + MSG_FLICKER, +#endif +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_6AXIS) || defined(CONFIG_SUPPORT_DUAL_6AXIS) + MSG_ACCEL_SUB, + MSG_GYRO_SUB, +#endif + PHYSICAL_SENSOR_SYSFS,//MSG_TYPE_SIZE_ZERO + MSG_GYRO_TEMP, +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_6AXIS) || defined(CONFIG_SUPPORT_DUAL_6AXIS) + MSG_GYRO_SUB_TEMP, +#endif + MSG_PRESSURE_TEMP, + MSG_MAG_CAL,//MSG_TYPE_SIZE_ZERO +#if IS_ENABLED(CONFIG_FLIP_COVER_DETECTOR_FACTORY) || defined(CONFIG_FLIP_COVER_DETECTOR_FACTORY) + MSG_FLIP_COVER_DETECTOR, +#endif +#if IS_ENABLED(CONFIG_SUPPORT_VIRTUAL_OPTIC) || defined(CONFIG_SUPPORT_VIRTUAL_OPTIC) + MSG_VIR_OPTIC, +#endif + MSG_REG_SNS,//MSG_TYPE_SIZE_ZERO +#if IS_ENABLED(CONFIG_SUPPORT_AK09973) || defined(CONFIG_SUPPORT_AK09973) + MSG_DIGITAL_HALL, + MSG_DIGITAL_HALL_ANGLE, +#if ENABLE_LF_STREAM + MSG_LF_STREAM, +#endif +#elif IS_ENABLED(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) || defined(CONFIG_SUPPORT_REF_ANGLE_WITHOUT_DIGITAL_HALL) + MSG_REF_ANGLE, +#endif +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_DDI_COPR_FOR_LIGHT_SENSOR) || defined(CONFIG_SUPPORT_DUAL_DDI_COPR_FOR_LIGHT_SENSOR) + MSG_DDI, +#endif +#if IS_ENABLED(CONFIG_SUPPORT_LIGHT_MAIN2_SENSOR) || defined(CONFIG_SUPPORT_LIGHT_MAIN2_SENSOR) + MSG_LIGHT_MAIN2, +#endif +#if IS_ENABLED(CONFIG_BACKTAP_FACTORY) || defined(CONFIG_BACKTAP_FACTORY) + MSG_BACKTAP, +#endif + MSG_FACTORY_INIT_CMD,//MSG_TYPE_SIZE_ZERO + MSG_SSC_CORE,//MSG_TYPE_SIZE_ZERO + MSG_SENSOR_MAX +}; + +/* Netlink ENUMS Message Protocols */ +enum { + MSG_TYPE_GET_RAW_DATA, + MSG_TYPE_ST_SHOW_DATA, + MSG_TYPE_SET_ACCEL_LPF, + MSG_TYPE_SET_ACCEL_MOTOR, + MSG_TYPE_GET_THRESHOLD, + MSG_TYPE_SET_THRESHOLD, + MSG_TYPE_SET_TEMPORARY_MSG, + MSG_TYPE_GET_REGISTER, + MSG_TYPE_SET_REGISTER, + MSG_TYPE_GET_DUMP_REGISTER, + MSG_TYPE_GET_CAL_DATA, + MSG_TYPE_SET_CAL_DATA, + MSG_TYPE_GET_DHR_INFO, + MSG_TYPE_FACTORY_ENABLE, + MSG_TYPE_FACTORY_DISABLE, + MSG_TYPE_OPTION_DEFINE, + MSG_TYPE_DUMPSTATE, + MSG_TYPE_MAX +}; + +/* Sensor types defined by android */ +/* (Keep in sync with hardware/sensors-base.h and Sensor.java.) */ +#define SENSOR_HANDLE_ACCELEROMETER (1) +#define SENSOR_HANDLE_GEOMAGNETIC_FIELD (2) +#define SENSOR_HANDLE_GYROSCOPE (4) +#define SENSOR_HANDLE_LIGHT (5) +#define SENSOR_HANDLE_PRESSURE (6) +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_6AXIS) +#define SENSOR_HANDLE_ACCELEROMETER_SUB (65687) +#endif +#define SENSOR_HANDLE_ALL (0) + +#define SENSOR_NAME_MAX 40 + +#if IS_ENABLED(CONFIG_SUPPORT_DUAL_OPTIC) || defined(CONFIG_SUPPORT_DUAL_OPTIC) +enum { + FSTATE_INACTIVE, + FSTATE_ACTIVE, + FSTATE_FAC_INACTIVE, + FSTATE_FAC_ACTIVE, + FSTATE_FAC_INACTIVE_2 +}; + +#define LIGHT_DUAL_CHECK_MODE 13 + +enum { + VOPTIC_OP_CMD_FAC_FLIP, + VOPTIC_OP_CMD_SSC_FLIP, + VOPTIC_OP_CMD_SSC_FLIP_UPDATE, + VOPTIC_OP_CMD_MAX +}; +#endif + +enum { + COMMON_DATA_SET_ABS_OFF, // 0x00 00 47 C1 + COMMON_DATA_SET_ABS_ON, // 0x01 00 47 C1 +#if IS_ENABLED(CONFIG_SUPPORT_VFOLD_FLEX) || defined(CONFIG_SUPPORT_VFOLD_FLEX) + COMMON_DATA_SET_MAIN_ON, // 0x02 00 47 C1 + COMMON_DATA_SET_SUB_ON, // 0x03 00 47 C1 +#endif + COMMON_DATA_SET_LCD_INTENT_ON = 0xf1, // 0xf1 00 47 C1 + COMMON_DATA_SET_LCD_INTENT_OFF, // 0xf2 00 47 C1 +}; + +// for ssc_core sensor type +enum { + OPTION_TYPE_SSC_CHARGING_STATE, // for pocket mode + OPTION_TYPE_SSC_ABS_LCD_TYPE, // for pocket mode + OPTION_TYPE_SSC_LCD_TYPE, // for pocket mode + auto roation + OPTION_TYPE_SSC_LCD_INTENT_TYPE, // for auto rotation + OPTION_TYPE_SSC_DUMP_TYPE, // for pocket mode + OPTION_TYPE_SSC_AOD_RECT, // for AOD + OPTION_TYPE_SSC_AOD_LIGHT_CIRCLE, // for AOD + OPTION_TYPE_SSC_LIGHT_SEAMLESS, // for light seamless + OPTION_TYPE_SSC_AUTO_ROTATION_MODE, // for auto rotation + OPTION_TYPE_SSC_SBM_INIT, // for sar backoff motion + OPTION_TYPE_SSC_WAKEUP_REASON, // for commoninfo + OPTION_TYPE_SSC_RECOVERY, // for commoninfo + OPTION_TYPE_SSC_POCKET_INJECT, // for pocket mode + OPTION_TYPE_SSC_SSR_DUMP, // for commoninfo + OPTION_TYPE_SSC_MAX +}; + +enum { + BD_SMD, //0 + BD_TILT, + BD_PICKUP, + BD_SBM, + BD_WAKEUP_MOTION, + BD_PROXIMITY, + BD_CALL_GESTURE, + BD_POCKET_MODE, + BD_LED_COVER, + BD_FLIP_COVER, + BD_DROP_CLASSIFIER, // 10 + BD_POCKET_PS_MODE, + BD_STEP_CNT_ALERT, + BD_FOLDING_STATE_LPM, + BD_HINGE_ANGLE, + BD_LID_ANGLE_FUSION, + BD_ANGLE_SENSOR_STATUS, + BD_SCONTEXT_START_IDX, + BD_SEM_MOVEMENT = BD_SCONTEXT_START_IDX, //17 + BD_SEM_AUTO_ROTATION, + BD_SEM_WIRELESS_CHARGING_DET, + BD_SEM_PUT_DOWN_MOTION, //20 + BD_SEM_SLOCATION, + BD_SEM_AMD, + BD_SEM_AOD, + BD_SEM_FLAT_MOTION, + BD_SEM_SNS_STATUS_CHECK, + BD_SEM_DEVICE_POSITION, + BD_SEM_LOC_CHANGE_TRIGGER, + BD_SEM_FREE_FALL_DET, + BD_SEM_START_ACTIVITIES, + BD_SEM_PEDOMETER = BD_SEM_START_ACTIVITIES, + BD_SEM_STEP_LEVEL_MONITOR, //30 + BD_SEM_AT_NORMAL, + BD_SEM_AT_INT, + BD_SEM_AT_BATCH, + BD_SEM_AT_EXT_INT, + BD_SEM_ACTIVITY_CAL, // Activity vehicle + BD_SENSOR_MAX +}; +#endif diff --git a/include/linux/adsp/slpi-loader.h b/include/linux/adsp/slpi-loader.h new file mode 100755 index 000000000000..de1c42f74166 --- /dev/null +++ b/include/linux/adsp/slpi-loader.h @@ -0,0 +1,18 @@ +/* Copyright (c) 2012-2013, 2015 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * 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 _SLPI_LOADER_H_ +#define _SLPI_LOADER_H_ +#ifdef CONFIG_DSP_SLEEP_RECOVERY +int slpi_ssr(void); +#endif +#endif diff --git a/include/linux/adsp/ssc_ssr_reason.h b/include/linux/adsp/ssc_ssr_reason.h new file mode 100755 index 000000000000..9b237c431538 --- /dev/null +++ b/include/linux/adsp/ssc_ssr_reason.h @@ -0,0 +1,5 @@ +#ifndef SSC_SSR_REASON_H +#define SSC_SSR_REASON_H + +void ssr_reason_call_back(char reason[], int len); +#endif /* SSP_MOTOR_H */ diff --git a/include/linux/battery/sb_def.h b/include/linux/battery/sb_def.h new file mode 100644 index 000000000000..7119c7baad12 --- /dev/null +++ b/include/linux/battery/sb_def.h @@ -0,0 +1,50 @@ +/* + * sb_def.h + * Samsung Mobile Battery DEF Header + * + * Copyright (C) 2012 Samsung Electronics, 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 __SB_DEF_H +#define __SB_DEF_H __FILE__ + +enum sb_dev_type { + SB_DEV_UNKNOWN = 0, + + SB_DEV_BATTERY, + SB_DEV_DIRECT_CHARGER, + SB_DEV_DUAL_BATTERY, + + SB_DEV_CHARGER, + SB_DEV_FUEL_GAUGE, + SB_DEV_WIRELESS_CHARGER, + SB_DEV_LIMITTER, + SB_DEV_DIVIDER, + + SB_DEV_MODULE, + SB_DEV_MAX, +}; + +typedef unsigned long long sb_data; + +#define cast_to_sb_data(data) ((sb_data)(data)) +#define cast_to_sb_pdata(data) ((sb_data *)(data)) + +#define cast_sb_data(type, data) ((type)(data)) +#define cast_sb_data_ptr(type, data) ((type *)(data)) +#define cast_sb_pdata(type, pdata) ((type)(*pdata)) +#define cast_sb_pdata_ptr(type, pdata) ((type *)(*pdata)) + +#endif /* __SB_DEF_H */ + diff --git a/include/linux/battery/sb_notify.h b/include/linux/battery/sb_notify.h new file mode 100644 index 000000000000..6168c9c3d2da --- /dev/null +++ b/include/linux/battery/sb_notify.h @@ -0,0 +1,70 @@ +/* + * sb_notify.h + * Samsung Mobile Battery Notify Header + * + * Copyright (C) 2012 Samsung Electronics, 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 __SB_NOTIFY_H +#define __SB_NOTIFY_H __FILE__ + +#include +#include + +enum sbn_type { + SB_NOTIFY_UNKNOWN = 0, + + SB_NOTIFY_DEV_PROBE, + SB_NOTIFY_DEV_SHUTDOWN, + SB_NOTIFY_DEV_LIST, + + SB_NOTIFY_EVENT_MISC, + SB_NOTIFY_EVENT_SIOP, + SB_NOTIFY_EVENT_SLATE_MODE, + + SB_NOTIFY_MAX, + +}; + +struct sbn_dev_list { + const char **list; + unsigned int count; +}; + +struct sbn_bit_event { + unsigned int value; + unsigned int mask; +}; + +#define SB_NOTIFY_DISABLE (-3661) +#if IS_ENABLED(CONFIG_SB_NOTIFY) +int sb_notify_call(enum sbn_type ntype, sb_data *ndata); +int sb_notify_register(struct notifier_block *nb, + notifier_fn_t notifier, const char *name, enum sb_dev_type type); + +int sb_notify_unregister(struct notifier_block *nb); +#else +static inline int sb_notify_call(enum sbn_type ntype, sb_data *ndata) +{ return SB_NOTIFY_DISABLE; } + +static inline int sb_notify_register(struct notifier_block *nb, + notifier_fn_t notifier, const char *name, enum sb_dev_type type) +{ return SB_NOTIFY_DISABLE; } + +static inline int sb_notify_unregister(struct notifier_block *nb) +{ return SB_NOTIFY_DISABLE; } +#endif + +#endif /* __SB_NOTIFY_H */ + diff --git a/include/linux/battery/sb_pqueue.h b/include/linux/battery/sb_pqueue.h new file mode 100644 index 000000000000..c33eda6fcdf4 --- /dev/null +++ b/include/linux/battery/sb_pqueue.h @@ -0,0 +1,81 @@ +/* + * sb_pqueue.h + * Samsung Mobile Priority Queue Header + * + * Copyright (C) 2021 Samsung Electronics, 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 __SB_PQUEUE_H +#define __SB_PQUEUE_H __FILE__ + +#include +#include + +#define PQF_REMOVE 0x1 +#define PQF_PRIORITY 0x2 + +struct sb_pqueue; + +/* priority condition : data1 > data2 = true, data1 < data2 = false */ +typedef bool (*cmp_func)(sb_data *data1, sb_data *data2); + +#define SB_PQUEUE_DISABLE (-3660) +#if IS_ENABLED(CONFIG_SB_PQUEUE) +struct sb_pqueue *sb_pq_create(unsigned int flag, unsigned int size, cmp_func cmp); +void sb_pq_destroy(struct sb_pqueue *pq); + +sb_data *sb_pq_pop(struct sb_pqueue *pq); +int sb_pq_push(struct sb_pqueue *pq, unsigned int idx, sb_data *data); + +int sb_pq_get_en(struct sb_pqueue *pq, unsigned int idx); + +int sb_pq_set_pri(struct sb_pqueue *pq, unsigned int idx, int pri); +int sb_pq_get_pri(struct sb_pqueue *pq, unsigned int idx); + +int sb_pq_set_data(struct sb_pqueue *pq, unsigned int idx, sb_data *data); +sb_data *sb_pq_get_data(struct sb_pqueue *pq, unsigned int idx); + +sb_data *sb_pq_top(struct sb_pqueue *pq); +int sb_pq_remove(struct sb_pqueue *pq, unsigned int idx); +#else +static inline struct sb_pqueue *sb_pq_create(unsigned int flag, unsigned int size, cmp_func cmp) +{ return ERR_PTR(SB_PQUEUE_DISABLE); } +static inline void sb_pq_destroy(struct sb_pqueue *pq) {} + +static inline sb_data *sb_pq_pop(struct sb_pqueue *pq) +{ return ERR_PTR(SB_PQUEUE_DISABLE); } +static inline int sb_pq_push(struct sb_pqueue *pq, unsigned int idx, sb_data *data) +{ return SB_PQUEUE_DISABLE; } + +static inline int sb_pq_get_en(struct sb_pqueue *pq, unsigned int idx) +{ return SB_PQUEUE_DISABLE; } + +static inline int sb_pq_set_pri(struct sb_pqueue *pq, unsigned int idx, int pri) +{ return SB_PQUEUE_DISABLE; } +static inline int sb_pq_get_pri(struct sb_pqueue *pq, unsigned int idx) +{ return SB_PQUEUE_DISABLE; } + +static inline int sb_pq_set_data(struct sb_pqueue *pq, unsigned int idx, sb_data *data) +{ return SB_PQUEUE_DISABLE; } +static inline sb_data *sb_pq_get_data(struct sb_pqueue *pq, unsigned int idx) +{ return ERR_PTR(SB_PQUEUE_DISABLE); } + +static inline sb_data *sb_pq_top(struct sb_pqueue *pq) +{ return ERR_PTR(SB_PQUEUE_DISABLE); } +static inline int sb_pq_remove(struct sb_pqueue *pq, unsigned int idx) +{ return SB_PQUEUE_DISABLE; } +#endif + +#endif /* __SB_PQUEUE_H */ + diff --git a/include/linux/battery/sb_sysfs.h b/include/linux/battery/sb_sysfs.h new file mode 100644 index 000000000000..ced8b4f2dedd --- /dev/null +++ b/include/linux/battery/sb_sysfs.h @@ -0,0 +1,43 @@ +/* + * sb_sysfs.h + * Samsung Mobile SysFS Header + * + * Copyright (C) 2021 Samsung Electronics, 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 __SB_SYSFS_H +#define __SB_SYSFS_H __FILE__ + +#include +#include + +#define SB_SYSFS_DISABLE (-3662) +#if IS_ENABLED(CONFIG_SB_SYSFS) +int sb_sysfs_add_attrs(const char *name, void *pdata, struct device_attribute *attr, unsigned long size); +int sb_sysfs_remove_attrs(const char *name); +void *sb_sysfs_get_pdata(const char *name); +int sb_sysfs_create_attrs(struct device *dev); +#else +static inline int sb_sysfs_add_attrs(const char *name, void *pdata, struct device_attribute *attr, unsigned long size) +{ return SB_SYSFS_DISABLE; } +static inline int sb_sysfs_remove_attrs(const char *name) +{ return SB_SYSFS_DISABLE; } +static inline void *sb_sysfs_get_pdata(const char *name) +{ return ERR_PTR(SB_SYSFS_DISABLE); } +static inline int sb_sysfs_create_attrs(struct device *dev) +{ return SB_SYSFS_DISABLE; } +#endif + +#endif /* __SB_SYSFS_H */ + diff --git a/include/linux/battery/sb_vote.h b/include/linux/battery/sb_vote.h new file mode 100644 index 000000000000..f62a5e7886e4 --- /dev/null +++ b/include/linux/battery/sb_vote.h @@ -0,0 +1,101 @@ +/* + * sb_vote.h + * Samsung Mobile Battery Vote Header + * + * Copyright (C) 2021 Samsung Electronics, 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 __SB_VOTE_H +#define __SB_VOTE_H __FILE__ + +#include +#include + +enum { + SB_VOTE_MIN, + SB_VOTE_MAX, + SB_VOTE_EN, + SB_VOTE_DATA, +}; + +enum { + VOTE_PRI_0 = 0, + VOTE_PRI_1, + VOTE_PRI_2, + VOTE_PRI_3, + VOTE_PRI_4, + VOTE_PRI_5, + VOTE_PRI_6, + VOTE_PRI_7, + VOTE_PRI_8, + VOTE_PRI_9, + VOTE_PRI_10, +}; +#define VOTE_PRI_MIN VOTE_PRI_0 +#define VOTE_PRI_MAX VOTE_PRI_10 + +struct sb_vote; + +typedef int (*cb_vote)(void *pdata, sb_data vdata); +typedef bool (*cmp_vote)(sb_data *vdata1, sb_data *vdata2); + +struct sb_vote_cfg { + const char *name; + int type; + + const char **voter_list; + int voter_num; + + cb_vote cb; + cmp_vote cmp; +}; + +#define SB_VOTE_DISABLE (-3663) +#define SB_VOTE_DISABLE_STR "voteoff" + +#if IS_ENABLED(CONFIG_SB_VOTE) +struct sb_vote *sb_vote_create(const struct sb_vote_cfg *vote_cfg, void *pdata, sb_data init_data); +void sb_vote_destroy(struct sb_vote *vote); + +struct sb_vote *sb_vote_find(const char *name); + +int sb_vote_get(struct sb_vote *vote, int event, sb_data *data); +int sb_vote_get_result(struct sb_vote *vote, sb_data *data); +int _sb_vote_set(struct sb_vote *vote, int event, bool en, sb_data data, const char *fname, int line); +int sb_vote_set_pri(struct sb_vote *vote, int event, int pri); +int sb_vote_refresh(struct sb_vote *vote); +#else +static inline struct sb_vote *sb_vote_create(const struct sb_vote_cfg *vote_cfg, void *pdata, sb_data init_data) +{ return ERR_PTR(SB_VOTE_DISABLE); } +static inline void sb_vote_destroy(struct sb_vote *vote) {} + +static inline struct sb_vote *sb_vote_find(const char *name) +{ return ERR_PTR(SB_VOTE_DISABLE); } + +static inline int sb_vote_get(struct sb_vote *vote, int event, sb_data *data) +{ return SB_VOTE_DISABLE; } +static inline int sb_vote_get_result(struct sb_vote *vote, sb_data *data) +{ return SB_VOTE_DISABLE; } +static inline int _sb_vote_set(struct sb_vote *vote, int event, bool en, sb_data data, const char *fname, int linee) +{ return SB_VOTE_DISABLE; } +static inline int sb_vote_set_pri(struct sb_vote *vote, int event, int pri) +{ return SB_VOTE_DISABLE; } +static inline int sb_vote_refresh(struct sb_vote *vote) +{ return SB_VOTE_DISABLE; } +#endif + +#define sb_vote_set(vote, event, en, value) _sb_vote_set(vote, event, en, value, __func__, __LINE__) + +#endif /* __SB_VOTE_H */ + diff --git a/include/linux/battery/sb_wireless.h b/include/linux/battery/sb_wireless.h new file mode 100644 index 000000000000..2159ab6a55e0 --- /dev/null +++ b/include/linux/battery/sb_wireless.h @@ -0,0 +1,55 @@ +/* + * include/linux/battery/sb_wireless.h + * + * header file supporting samsung battery wireless information + * + * Copyright (C) 2023 Samsung Electronics + * + * 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 __SB_WIRELESS_H__ +#define __SB_WIRELESS_H__ + +enum wpc_op_mode { + WPC_OP_MODE_NONE = 0, /* AC MISSING */ + WPC_OP_MODE_BPP, + WPC_OP_MODE_PPDE, + WPC_OP_MODE_EPP, + WPC_OP_MODE_MPP, + WPC_OP_MODE_MAX +}; + +enum wpc_auth_mode { + WPC_AUTH_MODE_NONE = 0, + WPC_AUTH_MODE_BPP, + WPC_AUTH_MODE_PPDE, + WPC_AUTH_MODE_EPP, + WPC_AUTH_MODE_MPP, + WPC_AUTH_MODE_MAX +}; + +struct sb_wireless_op { + int (*get_op_mode)(void *pdata); + int (*get_qi_ver)(void *pdata); + int (*get_auth_mode)(void *pdata); +}; + +const char *sb_wrl_op_mode_str(int op_mode); + +int sb_wireless_set_op(void *pdata, const struct sb_wireless_op *op); + +#endif /* __SB_WIRELESS_H__ */ diff --git a/include/linux/battery/sec_battery_common.h b/include/linux/battery/sec_battery_common.h new file mode 100644 index 000000000000..4f4b4b4ed7c2 --- /dev/null +++ b/include/linux/battery/sec_battery_common.h @@ -0,0 +1,363 @@ +/* + * sec_battery_common.h + * Samsung Mobile Charging Common Header + * + * Copyright (C) 2020 Samsung Electronics, 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 __SEC_BATTERY_COMMON_H +#define __SEC_BATTERY_COMMON_H __FILE__ + +#include +#include + +enum power_supply_ext_property { + POWER_SUPPLY_EXT_PROP_MIN = 1000, + POWER_SUPPLY_EXT_PROP_CHECK_SUB_CHG_I2C = POWER_SUPPLY_EXT_PROP_MIN, + POWER_SUPPLY_EXT_PROP_MULTI_CHARGER_MODE, + POWER_SUPPLY_EXT_PROP_WIRELESS_OP_FREQ, + POWER_SUPPLY_EXT_PROP_WIRELESS_OP_FREQ_STRENGTH, + POWER_SUPPLY_EXT_PROP_WIRELESS_TRX_CMD, + POWER_SUPPLY_EXT_PROP_WIRELESS_TRX_VAL, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ID, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ID_CNT, + POWER_SUPPLY_EXT_PROP_WIRELESS_ERR, + POWER_SUPPLY_EXT_PROP_WIRELESS_SWITCH, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ENABLE, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_VOUT, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_IOUT, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_UNO_VIN, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_UNO_IIN, + POWER_SUPPLY_EXT_PROP_WIRELESS_RX_CONNECTED, + POWER_SUPPLY_EXT_PROP_WIRELESS_RX_POWER, + POWER_SUPPLY_EXT_PROP_WIRELESS_WR_CONNECTED, + POWER_SUPPLY_EXT_PROP_WIRELESS_MAX_VOUT, + POWER_SUPPLY_EXT_PROP_WIRELESS_AUTH_ADT_STATUS, + POWER_SUPPLY_EXT_PROP_WIRELESS_AUTH_ADT_DATA, + POWER_SUPPLY_EXT_PROP_WIRELESS_AUTH_ADT_SIZE, + POWER_SUPPLY_EXT_PROP_WIRELESS_RX_TYPE, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ERR, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_RETRY_CASE, + POWER_SUPPLY_EXT_PROP_WIRELESS_MIN_DUTY, + POWER_SUPPLY_EXT_PROP_WIRELESS_SEND_FSK, + POWER_SUPPLY_EXT_PROP_WIRELESS_RX_VOUT, + POWER_SUPPLY_EXT_PROP_WIRELESS_INITIAL_WC_CHECK, + POWER_SUPPLY_EXT_PROP_WIRELESS_PARAM_INFO, + POWER_SUPPLY_EXT_PROP_WIRELESS_CHECK_FW_VER, + POWER_SUPPLY_EXT_PROP_WIRELESS_SGF, + POWER_SUPPLY_EXT_PROP_WIRELESS_MST_PWR_EN, + POWER_SUPPLY_EXT_PROP_WIRELESS_JIG_PAD, + POWER_SUPPLY_EXT_PROP_WIRELESS_OP_MODE, + POWER_SUPPLY_EXT_PROP_WIRELESS_WC_STATUS, + POWER_SUPPLY_EXT_PROP_AICL_CURRENT, + POWER_SUPPLY_EXT_PROP_CHIP_ID, + POWER_SUPPLY_EXT_PROP_ERROR_CAUSE, + POWER_SUPPLY_EXT_PROP_SYSOVLO, + POWER_SUPPLY_EXT_PROP_VBAT_OVP, + POWER_SUPPLY_EXT_PROP_FGSRC_SWITCHING, + POWER_SUPPLY_EXT_PROP_USB_CONFIGURE, + POWER_SUPPLY_EXT_PROP_WDT_STATUS, + POWER_SUPPLY_EXT_PROP_WATER_DETECT, + POWER_SUPPLY_EXT_PROP_SURGE, + POWER_SUPPLY_EXT_PROP_HV_DISABLE, + POWER_SUPPLY_EXT_PROP_FUELGAUGE_RESET, + POWER_SUPPLY_EXT_PROP_FACTORY_VOLTAGE_REGULATION, + POWER_SUPPLY_EXT_PROP_FUELGAUGE_FACTORY, + POWER_SUPPLY_EXT_PROP_DISABLE_FACTORY_MODE, + POWER_SUPPLY_EXT_PROP_OVERHEAT_NOTIFY, + POWER_SUPPLY_EXT_PROP_CHARGE_POWER, + POWER_SUPPLY_EXT_PROP_MEASURE_SYS, + POWER_SUPPLY_EXT_PROP_MEASURE_INPUT, + POWER_SUPPLY_EXT_PROP_WC_CONTROL, + POWER_SUPPLY_EXT_PROP_WC_EPT_UNKNOWN, + POWER_SUPPLY_EXT_PROP_CHGINSEL, + POWER_SUPPLY_EXT_PROP_JIG_GPIO, + POWER_SUPPLY_EXT_PROP_MONITOR_WORK, + POWER_SUPPLY_EXT_PROP_SHIPMODE_TEST, + POWER_SUPPLY_EXT_PROP_AUTO_SHIPMODE_CONTROL, + POWER_SUPPLY_EXT_PROP_WIRELESS_TIMER_ON, + POWER_SUPPLY_EXT_PROP_CALL_EVENT, + POWER_SUPPLY_EXT_PROP_GEAR_PHM_EVENT, + POWER_SUPPLY_EXT_PROP_RX_PHM, +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + POWER_SUPPLY_EXT_PROP_CHGIN_OK, + POWER_SUPPLY_EXT_PROP_SUPLLEMENT_MODE, + POWER_SUPPLY_EXT_PROP_RECHG_ON, + POWER_SUPPLY_EXT_PROP_EOC_ON, + POWER_SUPPLY_EXT_PROP_DISCHG_MODE, + POWER_SUPPLY_EXT_PROP_CHG_MODE, + POWER_SUPPLY_EXT_PROP_CHG_VOLTAGE, + POWER_SUPPLY_EXT_PROP_FASTCHG_LIMIT_CURRENT, + POWER_SUPPLY_EXT_PROP_TRICKLECHG_LIMIT_CURRENT, + POWER_SUPPLY_EXT_PROP_DISCHG_LIMIT_CURRENT, + POWER_SUPPLY_EXT_PROP_RECHG_VOLTAGE, + POWER_SUPPLY_EXT_PROP_EOC_VOLTAGE, + POWER_SUPPLY_EXT_PROP_EOC_CURRENT, + POWER_SUPPLY_EXT_PROP_POWERMETER_ENABLE, + POWER_SUPPLY_EXT_PROP_POWER_MODE2, + POWER_SUPPLY_EXT_PROP_DUAL_BAT_DET, + POWER_SUPPLY_EXT_PROP_FULL_CONDITION, + POWER_SUPPLY_EXT_PROP_LIMITER_SHIPMODE, +#endif + POWER_SUPPLY_EXT_PROP_REPSOC, + POWER_SUPPLY_EXT_PROP_REPCAP, + POWER_SUPPLY_EXT_PROP_FULLCAPREP, + POWER_SUPPLY_EXT_PROP_FULLCAPNOM, + POWER_SUPPLY_EXT_PROP_DESIGNCAP, + POWER_SUPPLY_EXT_PROP_CYCLES, + POWER_SUPPLY_EXT_PROP_CURRENT_EVENT, + POWER_SUPPLY_EXT_PROP_CURRENT_EVENT_CLEAR, + POWER_SUPPLY_EXT_PROP_PAD_VOLT_CTRL, + POWER_SUPPLY_EXT_PROP_WIRELESS_VOUT, + POWER_SUPPLY_EXT_PROP_WIRELESS_TX_AVG_CURR, + POWER_SUPPLY_EXT_PROP_WIRELESS_1ST_DONE, + POWER_SUPPLY_EXT_PROP_WIRELESS_2ND_DONE, + POWER_SUPPLY_EXT_PROP_CURRENT_MEASURE, + POWER_SUPPLY_EXT_PROP_DIRECT_CHARGER_MODE, + POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED_DC, + POWER_SUPPLY_EXT_PROP_DIRECT_DONE, + POWER_SUPPLY_EXT_PROP_DIRECT_FIXED_PDO, + POWER_SUPPLY_EXT_PROP_DIRECT_WDT_CONTROL, + POWER_SUPPLY_EXT_PROP_DIRECT_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_EXT_PROP_DIRECT_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_EXT_PROP_DIRECT_CURRENT_MAX, + POWER_SUPPLY_EXT_PROP_DIRECT_ADC_CTRL, + POWER_SUPPLY_EXT_PROP_DIRECT_HAS_APDO, + POWER_SUPPLY_EXT_PROP_DIRECT_TA_ALERT, + POWER_SUPPLY_EXT_PROP_DIRECT_CHARGER_CHG_STATUS, + POWER_SUPPLY_EXT_PROP_CHANGE_CHARGING_SOURCE, + POWER_SUPPLY_EXT_PROP_REFRESH_CHARGING_SOURCE, + POWER_SUPPLY_EXT_PROP_DIRECT_CLEAR_ERR, + POWER_SUPPLY_EXT_PROP_DIRECT_SEND_UVDM, + POWER_SUPPLY_EXT_PROP_UPDATE_BATTERY_DATA, + POWER_SUPPLY_EXT_PROP_SRCCAP, + POWER_SUPPLY_EXT_PROP_HV_PDO, + POWER_SUPPLY_EXT_PROP_CHARGE_BOOST, + POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED, + POWER_SUPPLY_EXT_PROP_INPUT_VOLTAGE_REGULATION, + POWER_SUPPLY_EXT_PROP_POWER_DESIGN, + POWER_SUPPLY_EXT_PROP_FILTER_CFG, + POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL, + POWER_SUPPLY_EXT_PROP_CHARGE_OTG_ICL_CONTROL, + POWER_SUPPLY_EXT_PROP_CHARGE_POWERED_OTG_CONTROL, + POWER_SUPPLY_EXT_PROP_CHARGE_UNO_CONTROL, + POWER_SUPPLY_EXT_PROP_CHARGE_COUNTER_SHADOW, + POWER_SUPPLY_EXT_PROP_WPC_EN, + POWER_SUPPLY_EXT_PROP_WPC_EN_MST, + POWER_SUPPLY_EXT_PROP_MST_MODE, + POWER_SUPPLY_EXT_PROP_MST_DELAY, + POWER_SUPPLY_EXT_PROP_WPC_FREQ_STRENGTH, + POWER_SUPPLY_EXT_PROP_HEALTH, + POWER_SUPPLY_EXT_PROP_SLEEP_MODE, + POWER_SUPPLY_EXT_PROP_MFC_FW_UPDATE, + POWER_SUPPLY_EXT_PROP_THERMAL_ZONE, + POWER_SUPPLY_EXT_PROP_TEMP_CHECK_TYPE, + POWER_SUPPLY_EXT_PROP_DC_INITIALIZE, + POWER_SUPPLY_EXT_PROP_BATTERY_ID, +#if IS_ENABLED(CONFIG_DUAL_BATTERY) + POWER_SUPPLY_EXT_PROP_DIRECT_VBAT_CHECK, + POWER_SUPPLY_EXT_PROP_VOLTAGE_PACK_MAIN, + POWER_SUPPLY_EXT_PROP_VOLTAGE_PACK_SUB, +#endif + POWER_SUPPLY_EXT_PROP_WIRELESS_RX_CONTROL, + POWER_SUPPLY_EXT_PROP_INPUT_CURRENT_LIMIT_WRL, + POWER_SUPPLY_EXT_PROP_CONSTANT_CHARGE_CURRENT_WRL, + POWER_SUPPLY_EXT_PROP_SUB_TEMP, + POWER_SUPPLY_EXT_PROP_MIX_LIMIT, + POWER_SUPPLY_EXT_PROP_ENABLE_HW_FACTORY_MODE, + POWER_SUPPLY_EXT_PROP_FACTORY_MODE, + POWER_SUPPLY_EXT_PROP_CHECK_INIT, + POWER_SUPPLY_EXT_PROP_IB_MODE, + POWER_SUPPLY_EXT_PROP_OB_MODE_CABLE_REMOVED, + POWER_SUPPLY_EXT_PROP_BATT_F_MODE, + POWER_SUPPLY_EXT_PROP_FACTORY_MODE_RELIEVE, + POWER_SUPPLY_EXT_PROP_FACTORY_MODE_BYPASS, + POWER_SUPPLY_EXT_PROP_LCD_FLICKER, + POWER_SUPPLY_EXT_PROP_PASS_THROUGH_MODE, + POWER_SUPPLY_EXT_PROP_PASS_THROUGH_MODE_TA_VOL, + POWER_SUPPLY_EXT_PROP_D2D_REVERSE_VOLTAGE, + POWER_SUPPLY_EXT_PROP_FUELGAUGE_LOG, + POWER_SUPPLY_EXT_PROP_CHARGER_IC_NAME, + POWER_SUPPLY_EXT_PROP_D2D_REVERSE_OCP, + POWER_SUPPLY_EXT_PROP_BATT_DUMP, + POWER_SUPPLY_EXT_PROP_FLASH_STATE, + POWER_SUPPLY_EXT_PROP_PMIC_BAT_VOLTAGE, + POWER_SUPPLY_EXT_PROP_USB_BOOTCOMPLETE, +#if IS_ENABLED(CONFIG_MTK_CHARGER) + POWER_SUPPLY_EXT_PROP_BATT_VSYS, + POWER_SUPPLY_EXT_PROP_RP_LEVEL, + POWER_SUPPLY_EXT_PROP_BUCK_STATE, + POWER_SUPPLY_EXT_PROP_AFC_INIT, + POWER_SUPPLY_EXT_PROP_MTK_FG_INIT, +#endif + POWER_SUPPLY_EXT_PROP_D2D_REVERSE_VBUS, + POWER_SUPPLY_EXT_PROP_ADC_MODE, + POWER_SUPPLY_EXT_PROP_DC_OP_MODE, + POWER_SUPPLY_EXT_PROP_LRP_CHG_SRC, + POWER_SUPPLY_EXT_PROP_MISC_EVENT, + POWER_SUPPLY_EXT_PROP_MISC_EVENT_CLEAR, + POWER_SUPPLY_EXT_PROP_MST_EN, +#if defined(CONFIG_SEC_FACTORY) + POWER_SUPPLY_EXT_PROP_AFC_TEST_FG_MODE, +#endif + POWER_SUPPLY_EXT_PROP_SPSN_TEST, + POWER_SUPPLY_EXT_PROP_ABNORMAL_SRCCAP, + POWER_SUPPLY_EXT_PROP_CHARGE_FULL_REPCAP, + POWER_SUPPLY_EXT_PROP_STATUS_FULL_REPCAP, + POWER_SUPPLY_EXT_PROP_DC_VIN_OVERCURRENT, + POWER_SUPPLY_EXT_PROP_DC_REVERSE_MODE, + POWER_SUPPLY_EXT_PROP_OTG_VBUS_CTRL, + POWER_SUPPLY_EXT_PROP_TX_PWR_BUDG, + POWER_SUPPLY_EXT_PROP_HARDRESET_OCCUR, + POWER_SUPPLY_EXT_PROP_FPDO_DC_THERMAL_CHECK, + POWER_SUPPLY_EXT_PROP_CHARGER_MODE_DIRECT, + POWER_SUPPLY_EXT_PROP_DCHG_READ_BATP_BATN, + POWER_SUPPLY_EXT_PROP_ABNORMAL_TA, + POWER_SUPPLY_EXT_PROP_WIRELESS_PING_DUTY, + POWER_SUPPLY_EXT_PROP_WARM_FOD, + POWER_SUPPLY_EXT_PROP_MPP_COVER, + POWER_SUPPLY_EXT_PROP_MPP_CLOAK, + POWER_SUPPLY_EXT_PROP_MPP_ICL_CTRL, + POWER_SUPPLY_EXT_PROP_MPP_INC_INT_CTRL, + POWER_SUPPLY_EXT_PROP_DC_RCP, +#if IS_ENABLED(CONFIG_SBP_FG) + POWER_SUPPLY_EXT_PROP_FAKE_SOC, +#endif + POWER_SUPPLY_EXT_PROP_ARI_CNT, + POWER_SUPPLY_EXT_PROP_PRSWAP, + POWER_SUPPLY_EXT_PROP_MAX, +}; + +enum { + POWER_SUPPLY_DC_REVERSE_STOP, /* stop reverse mode */ + POWER_SUPPLY_DC_REVERSE_BYP, /* 1:1 reverse bypass mode */ + POWER_SUPPLY_DC_REVERSE_1TO2, /* 1:2 reverse switching mode */ +}; + +enum { + TURN_OTG_OFF, + TURN_OTG_ON, + TURN_RB_OFF, /* REVERSE BYPASS for Earphone*/ + TURN_RB_ON, + TURN_OTG_OFF_RB_ON, +}; + +enum sec_battery_usb_conf { + USB_CURRENT_NONE = 0, + USB_CURRENT_SUSPENDED = 1, + USB_CURRENT_CLEAR = 2, + USB_CURRENT_UNCONFIGURED = 100, + USB_CURRENT_HIGH_SPEED = 475, + USB_CURRENT_SUPER_SPEED = 850, +}; + +enum sec_battery_wpc_en_ctrl { + WPC_EN_SYSFS = 0x1, + WPC_EN_CCIC = 0x2, + WPC_EN_CHARGING = 0x4, + WPC_EN_TX = 0x8, + WPC_EN_MST = 0x10, + WPC_EN_FW = 0x20, + WPC_EN_SLATE = 0x40, +}; + +static inline struct power_supply *get_power_supply_by_name(char *name) +{ + if (!name) + return (struct power_supply *)NULL; + else + return power_supply_get_by_name(name); +} + +#define psy_do_property(name, function, property, value) \ +({ \ + struct power_supply *psy; \ + int ret = 0; \ + psy = get_power_supply_by_name((name)); \ + if (!psy) { \ + pr_err("%s: Fail to "#function" psy (%s)\n", \ + __func__, (name)); \ + value.intval = 0; \ + ret = -ENOENT; \ + } else { \ + if (psy->desc->function##_property != NULL) { \ + ret = psy->desc->function##_property(psy, \ + (enum power_supply_property) (property), &(value)); \ + if (ret < 0) { \ + pr_err("%s: Fail to %s "#function" "#property" (%d)\n", \ + __func__, name, ret); \ + value.intval = 0; \ + } \ + } else { \ + ret = -ENOSYS; \ + } \ + power_supply_put(psy); \ + } \ + ret; \ +}) + +#if defined(CONFIG_OF) +#define sb_of_parse_u32(np, pdata, value, deft) \ +({ \ + int ret = 0; \ + ret = of_property_read_u32(np, #value, (unsigned int *)&pdata->value); \ + if (!ret) \ + pr_info("%s: %s - write "#value" to %d\n", __func__, np->name, pdata->value); \ + else \ + pdata->value = deft; \ + ret;\ +}) + +#define sb_of_parse_str(np, pdata, value) \ +({ \ + int ret = 0; \ + ret = of_property_read_string(np, #value, (const char **)&pdata->value); \ + if (!ret) \ + pr_info("%s: %s - write "#value" to %s\n", __func__, np->name, pdata->value); \ + ret;\ +}) + +#define sb_of_parse_bool(np, pdata, value) \ +({ \ + pdata->value = of_property_read_bool(np, #value); \ + pr_info("%s: %s - write "#value" to %d\n", __func__, np->name, pdata->value); \ +}) + +#define sb_of_parse_str_dt(np, dt_name, pdata, path) \ +({ \ + int ret = 0; \ + ret = of_property_read_string(np, dt_name, (const char **)&pdata->path); \ + if (!ret) \ + pr_info("%s: %s - write "dt_name" to %s\n", __func__, np->name, pdata->path); \ + ret;\ +}) + +#define sb_of_parse_u32_dt(np, dt_name, pdata, path, deft) \ +({ \ + int ret = 0; \ + ret = of_property_read_u32(np, dt_name, (unsigned int *)&pdata->path); \ + if (ret) \ + pdata->path = deft; \ + pr_info("%s: %s - write "dt_name" to %d\n", __func__, np->name, pdata->path); \ + ret;\ +}) + +#define sb_of_parse_bool_dt(np, dt_name, pdata, path) \ +({ \ + pdata->path = of_property_read_bool(np, dt_name); \ + pr_info("%s: %s - write "dt_name" to %d\n", __func__, np->name, pdata->path); \ +}) +#endif + +#endif /* __SEC_BATTERY_COMMON_H */ diff --git a/include/linux/battery/sec_pd.h b/include/linux/battery/sec_pd.h new file mode 100644 index 000000000000..c8e05d497d70 --- /dev/null +++ b/include/linux/battery/sec_pd.h @@ -0,0 +1,154 @@ +/* + * include/linux/battery/sec_pd.h + * + * header file supporting samsung pd information + * + * Copyright (C) 2020 Samsung Electronics + * + * 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 __SEC_PD_H__ +#define __SEC_PD_H__ +#define MAX_PDO_NUM 8 +#define AVAILABLE_VOLTAGE 9000 +#define DEFAULT_VOLTAGE 5000 +#define UNIT_FOR_VOLTAGE 50 +#define UNIT_FOR_CURRENT 10 +#define UNIT_FOR_APDO_VOLTAGE 100 +#define UNIT_FOR_APDO_CURRENT 50 + +/* d2d 15w authentication information */ +#define AUTH_VENDOR_ID 0x04e8 +#define AUTH_PRODUCT_ID 0x6860 + +typedef enum { + PDIC_NOTIFY_EVENT_DETACH = 0, + PDIC_NOTIFY_EVENT_PDIC_ATTACH, + PDIC_NOTIFY_EVENT_PD_SINK, + PDIC_NOTIFY_EVENT_PD_SOURCE, + PDIC_NOTIFY_EVENT_PD_SINK_CAP, + PDIC_NOTIFY_EVENT_PD_PRSWAP_SNKTOSRC, + PDIC_NOTIFY_EVENT_PD_PRSWAP_SRCTOSNK, +} pdic_notifier_event_t; + +typedef enum +{ + RP_CURRENT_LEVEL_NONE = 0, + RP_CURRENT_LEVEL_DEFAULT, + RP_CURRENT_LEVEL2, + RP_CURRENT_LEVEL3, + RP_CURRENT_ABNORMAL, +} RP_CURRENT_LEVEL; + +typedef enum +{ + FPDO_TYPE = 0, + APDO_TYPE, + VPDO_TYPE, +} PDO_TYPE_T; + +typedef enum { + AUTH_NONE = 0, + AUTH_LOW_PWR, + AUTH_HIGH_PWR, +} AUTH_TYPE_T; + +typedef struct _power_list { + int accept; + int max_voltage; + int min_voltage; + int max_current; + int pdo_type; + int apdo; + int comm_capable; + int suspend; + } POWER_LIST; + +typedef struct sec_pd_sink_status +{ + POWER_LIST power_list[MAX_PDO_NUM+1]; + int has_apdo; // pd source has apdo or not + int has_vpdo; + int available_pdo_num; // the number of available PDO + int selected_pdo_num; // selected number of PDO to change + int current_pdo_num; // current number of PDO + unsigned short vid; + unsigned short pid; + unsigned int xid; + + int pps_voltage; + int pps_current; + + unsigned int rp_currentlvl; // rp current level by ccic + + void (*fp_sec_pd_select_pdo)(int num); + int (*fp_sec_pd_select_pps)(int num, int ppsVol, int ppsCur); + void (*fp_sec_pd_vpdo_auth)(int auth, int d2d_type); + void (*fp_sec_pd_ext_cb)(unsigned short v_id, unsigned short p_id); + void (*fp_sec_pd_manual_ccopen_req)(int is_on); + void (*fp_sec_pd_manual_jig_ctrl)(bool mode); + void (*fp_sec_pd_detach_with_cc)(int state); + void (*fp_sec_pd_change_src)(int max_cur); +} SEC_PD_SINK_STATUS; + +struct pdic_notifier_struct { + pdic_notifier_event_t event; + SEC_PD_SINK_STATUS sink_status; + void *pusbpd; +}; + +#if IS_ENABLED(CONFIG_SEC_PD) +const char* sec_pd_pdo_type_str(int pdo_type); +int sec_pd_select_pdo(int num); +int sec_pd_select_pps(int num, int ppsVol, int ppsCur); +int sec_pd_vpdo_auth(int auth, int d2d_type); +int sec_pd_get_current_pdo(unsigned int *pdo); +int sec_pd_get_selected_pdo(unsigned int *pdo); +int sec_pd_is_apdo(unsigned int pdo); +int sec_pd_get_apdo_prog_volt(unsigned int pdo_type, unsigned int max_volt); +int sec_pd_get_max_power(unsigned int pdo_type, unsigned int min_volt, unsigned int max_volt, unsigned int max_curr); +int sec_pd_get_pdo_power(unsigned int *pdo, unsigned int *min_volt, unsigned int *max_volt, unsigned int *curr); +int sec_pd_get_apdo_max_power(unsigned int *pdo_pos, unsigned int *taMaxVol, unsigned int *taMaxCur, unsigned int *taMaxPwr); +void sec_pd_init_data(SEC_PD_SINK_STATUS* psink_status); +int sec_pd_register_chg_info_cb(void *cb); +int sec_pd_get_chg_info(void); +void sec_pd_get_vid_pid(unsigned short *vid, unsigned short *pid, unsigned int *xid); +void sec_pd_manual_ccopen_req(int is_on); +void sec_pd_manual_jig_ctrl(bool mode); +int sec_pd_detach_with_cc(int state); +int sec_pd_change_src(int max_cur); +#else +static inline char* sec_pd_pdo_type_str(int pdo_type) { return "\0"; } +static inline int sec_pd_select_pdo(int num) { return -ENODEV; } +static inline int sec_pd_select_pps(int num, int ppsVol, int ppsCur) { return -ENODEV; } +static inline int sec_pd_vpdo_auth(int auth, int d2d_type) { return -ENODEV; } +static inline int sec_pd_get_current_pdo(unsigned int *pdo) { return -ENODEV; } +static inline int sec_pd_get_selected_pdo(unsigned int *pdo) { return -ENODEV; } +static inline int sec_pd_is_apdo(unsigned int pdo) { return -ENODEV; } +static inline int sec_pd_get_apdo_prog_volt(unsigned int pdo_type, unsigned int max_volt) { return -ENODEV; } +static inline int sec_pd_get_max_power(unsigned int pdo_type, unsigned int min_volt, unsigned int max_volt, unsigned int max_curr) { return -ENODEV; } +static inline int sec_pd_get_pdo_power(unsigned int *pdo, unsigned int *min_volt, unsigned int *max_volt, unsigned int *curr) { return -ENODEV; } +static inline int sec_pd_get_apdo_max_power(unsigned int *pdo_pos, unsigned int *taMaxVol, unsigned int *taMaxCur, unsigned int *taMaxPwr) { return -ENODEV; } +static inline void sec_pd_init_data(SEC_PD_SINK_STATUS* psink_status) { } +static inline int sec_pd_register_chg_info_cb(void *cb) { return 0; } +static inline void sec_pd_get_vid_pid(unsigned short *vid, unsigned short *pid, unsigned int *xid) { } +static inline void sec_pd_manual_ccopen_req(int is_on) { } +static inline void sec_pd_manual_jig_ctrl(bool mode) { } +static inline int sec_pd_detach_with_cc(int state) { return 0; } +static inline int sec_pd_change_src(int max_cur) { return 0; } +#endif +#endif /* __SEC_PD_H__ */ diff --git a/include/linux/cpufreq_limit.h b/include/linux/cpufreq_limit.h new file mode 100644 index 000000000000..2fe1ac39d418 --- /dev/null +++ b/include/linux/cpufreq_limit.h @@ -0,0 +1,43 @@ +/* + * drivers/cpufreq/cpufreq_limit.c + * + * Remade according to cpufreq change + * (refer to commit df0eea4488081e0698b0b58ccd1e8c8823e22841 + * 18c49926c4bf4915e5194d1de3299c0537229f9f) + * + * 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. + */ + +#ifndef __LINUX_CPUFREQ_LIMIT_H__ +#define __LINUX_CPUFREQ_LIMIT_H__ + +/* sched boost type from kernel/sched/sched.h */ +extern int sched_set_boost(int enable); +#define NO_BOOST (CONSERVATIVE_BOOST * -1) +#define FULL_THROTTLE_BOOST 1 +#define CONSERVATIVE_BOOST 2 +#define RESTRAINED_BOOST 3 + +/* voltage based freq table */ +#define PRIME_CPU 0 +#define GOLD_CPU 1 +#define NUM_THM_CPUS 2 +#define NUM_MAX_FREQS 40 + +struct freq_voltage_base { + int count; + unsigned int table[NUM_THM_CPUS][NUM_MAX_FREQS]; +}; + +enum { + CFLM_USERSPACE = 0, /* user(/sys/power/cpufreq*limit) */ + CFLM_TOUCH = 1, /* touch */ + CFLM_FINGER = 2, /* fingerprint */ + CFLM_ARGOS = 3, /* argos */ + + CFLM_MAX_ITEM +}; + +#endif /* __LINUX_CPUFREQ_LIMIT_H__ */ diff --git a/include/linux/dev_ril_bridge.h b/include/linux/dev_ril_bridge.h new file mode 100644 index 000000000000..7e8e66bc089c --- /dev/null +++ b/include/linux/dev_ril_bridge.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017 Samsung Electronics. + * + * 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 __DEV_RIL_BRIDGE_H__ +#define __DEV_RIL_BRIDGE_H__ + +#define IPC_SYSTEM_CP_CHANNEL_INFO 0x01 +#define IPC_SYSTEM_CHARGING_DOCK_INFO 0x02 +#define IPC_SYSTEM_CP_ADAPTIVE_MIPI_INFO 0x05 + +struct __packed sipc_fmt_hdr { + u16 len; + u8 msg_seq; + u8 ack_seq; + u8 main_cmd; + u8 sub_cmd; + u8 cmd_type; +}; + +struct dev_ril_bridge_msg { + unsigned int dev_id; + unsigned int data_len; + void *data; +}; + +#if IS_ENABLED(CONFIG_DEV_RIL_BRIDGE) +extern int register_dev_ril_bridge_event_notifier(struct notifier_block *nb); +extern int unregister_dev_ril_bridge_event_notifier(struct notifier_block *nb); +extern int dev_ril_bridge_send_msg(int id, int size, void *buf); + +#else +static inline int register_dev_ril_bridge_event_notifier( + struct notifier_block *nb) {return 0;} +static inline int unregister_dev_ril_bridge_event_notifier( + struct notifier_block *nb) {return 0;} +static inline int dev_ril_bridge_send_msg(int id, int size, void *buf) {return 0;} +#endif + +#endif/*__DEV_RIL_BRIDGE_H__*/ diff --git a/include/linux/external_notify.h b/include/linux/external_notify.h new file mode 100644 index 000000000000..2e926c775cb3 --- /dev/null +++ b/include/linux/external_notify.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * + * header file supporting usb notify layer + * external notify call chain information + * + * Copyright (C) 2016-2023 Samsung Electronics + * + * 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 + * + */ + + /* usb notify layer v4.0 */ + +#ifndef __EXTERNAL_NOTIFY_H__ +#define __EXTERNAL_NOTIFY_H__ + +#include + +/* external notifier call chain command */ +enum external_notify_cmd { + EXTERNAL_NOTIFY_3S_NODEVICE = 1, + EXTERNAL_NOTIFY_DEVICE_CONNECT, + EXTERNAL_NOTIFY_HOSTBLOCK_PRE, + EXTERNAL_NOTIFY_HOSTBLOCK_POST, + EXTERNAL_NOTIFY_MDMBLOCK_PRE, + EXTERNAL_NOTIFY_MDMBLOCK_POST, + EXTERNAL_NOTIFY_POWERROLE, + EXTERNAL_NOTIFY_DEVICEADD, + EXTERNAL_NOTIFY_HOSTBLOCK_EARLY, + EXTERNAL_NOTIFY_VBUS_RESET, + EXTERNAL_NOTIFY_POSSIBLE_USB, +}; + +/* external notifier call sequence, + * largest priority number device will be called first. + */ +enum external_notify_device { + EXTERNAL_NOTIFY_DEV_MUIC, + EXTERNAL_NOTIFY_DEV_CHARGER, + EXTERNAL_NOTIFY_DEV_PDIC, + EXTERNAL_NOTIFY_DEV_MANAGER, +}; + +enum external_notify_condev { + EXTERNAL_NOTIFY_NONE = 0, + EXTERNAL_NOTIFY_GPAD, + EXTERNAL_NOTIFY_LANHUB, +}; + +#ifdef CONFIG_USB_EXTERNAL_NOTIFY +extern int send_external_notify(unsigned long cmd, int data); +extern int usb_external_notify_register(struct notifier_block *nb, + notifier_fn_t notifier, int listener); +extern int usb_external_notify_unregister(struct notifier_block *nb); +extern void external_notifier_init(void); +#else +static inline int send_external_notify(unsigned long cmd, + int data) {return 0; } +static inline int usb_external_notify_register(struct notifier_block *nb, + notifier_fn_t notifier, int listener) {return 0; } +static inline int usb_external_notify_unregister(struct notifier_block *nb) + {return 0; } +static inline void external_notifier_init(void) {} +#endif + +#endif /* __EXTERNAL_NOTIFY_H__ */ diff --git a/include/linux/firmware/cirrus/cl_dsp.h b/include/linux/firmware/cirrus/cl_dsp.h new file mode 100644 index 000000000000..6facbc5cf775 --- /dev/null +++ b/include/linux/firmware/cirrus/cl_dsp.h @@ -0,0 +1,395 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * cl_dsp.h -- DSP control for non-ALSA Cirrus Logic devices + * + * Copyright 2022 Cirrus Logic, Inc. + * + * Author: Fred Treven + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __CL_DSP_H__ +#define __CL_DSP_H__ + +#define CL_DSP_BYTES_PER_WORD 4 +#define CL_DSP_BITS_PER_BYTE 8 + +#define CL_DSP_BYTE_MASK GENMASK(7, 0) +#define CL_DSP_NIBBLE_MASK GENMASK(15, 0) + +#define CL_DSP_FW_FILE_HEADER_SIZE 40 +#define CL_DSP_COEFF_FILE_HEADER_SIZE 16 + +#define CL_DSP_MAGIC_ID_SIZE 4 + +#define CL_DSP_WMFW_MAGIC_ID "WMFW" +#define CL_DSP_WMDR_MAGIC_ID "WMDR" + +#define CL_DSP_DBLK_HEADER_SIZE 8 +#define CL_DSP_COEFF_DBLK_HEADER_SIZE 20 + +#define CL_DSP_ALIGN 0x00000003 + +#define CL_DSP_TARGET_CORE_ADSP1 0x01 +#define CL_DSP_TARGET_CORE_ADSP2 0x02 +#define CL_DSP_TARGET_CORE_HALO 0x04 +#define CL_DSP_TARGET_CORE_WARP2 0x12 +#define CL_DSP_TARGET_CORE_MCU 0x45 + +#define CL_DSP_MIN_FORMAT_VERSION 0x03 +#define CL_DSP_API_REVISION 0x0300 + +#define CL_DSP_ALGO_NAME_LEN_SIZE 1 +#define CL_DSP_ALGO_DESC_LEN_SIZE 2 +#define CL_DSP_ALGO_ID_SIZE 4 +#define CL_DSP_COEFF_COUNT_SIZE 4 +#define CL_DSP_COEFF_OFFSET_SIZE 2 +#define CL_DSP_COEFF_TYPE_SIZE 2 +#define CL_DSP_COEFF_NAME_LEN_SIZE 1 +#define CL_DSP_COEFF_FULLNAME_LEN_SIZE 1 +#define CL_DSP_COEFF_DESC_LEN_SIZE 2 +#define CL_DSP_COEFF_LEN_SIZE 4 +#define CL_DSP_COEFF_FLAGS_SIZE 4 +#define CL_DSP_COEFF_FLAGS_SHIFT 16 +#define CL_DSP_COEFF_NAME_LEN_MAX 32 +#define CL_DSP_COEFF_MIN_FORMAT_VERSION 0x01 +#define CL_DSP_COEFF_API_REV_HALO 0x030000 +#define CL_DSP_COEFF_API_REV_ADSP2 0x000500 + +#define CL_DSP_ALGO_LIST_TERM 0xBEDEAD + +#define CL_DSP_REV_OFFSET_SHIFT 8 + +#define CL_DSP_REV_MAJOR_MASK GENMASK(23, 16) +#define CL_DSP_REV_MAJOR_SHIFT 16 +#define CL_DSP_REV_MINOR_MASK GENMASK(15, 8) +#define CL_DSP_REV_MINOR_SHIFT 8 +#define CL_DSP_REV_PATCH_MASK GENMASK(7, 0) + +#define CL_DSP_NUM_ALGOS_MAX 32 + +#ifndef CONFIG_CS40L26_SAMSUNG_USE_MAX_DATA_TX_SIZE +#define CL_DSP_MAX_WLEN 32 +#else +#define CL_DSP_MAX_WLEN 4096 +#endif +#define CL_DSP_XM_UNPACKED_TYPE 0x0005 +#define CL_DSP_YM_UNPACKED_TYPE 0x0006 +#define CL_DSP_PM_PACKED_TYPE 0x0010 +#define CL_DSP_XM_PACKED_TYPE 0x0011 +#define CL_DSP_YM_PACKED_TYPE 0x0012 +#define CL_DSP_ALGO_INFO_TYPE 0x00F2 +#define CL_DSP_WMFW_INFO_TYPE 0x00FF + +#define CL_DSP_MEM_REG_TYPE_MASK GENMASK(27, 20) +#define CL_DSP_MEM_REG_TYPE_SHIFT 20 + +#define CL_DSP_PM_NUM_BYTES 5 +#define CL_DSP_PACKED_NUM_BYTES 3 +#define CL_DSP_UNPACKED_NUM_BYTES 4 + +#define CL_DSP_WMDR_DBLK_OFFSET_SIZE 2 +#define CL_DSP_WMDR_DBLK_TYPE_SIZE 2 +#define CL_DSP_WMDR_ALGO_ID_SIZE 4 +#define CL_DSP_WMDR_ALGO_REV_SIZE 4 +#define CL_DSP_WMDR_SAMPLE_RATE_SIZE 4 +#define CL_DSP_WMDR_DBLK_LEN_SIZE 4 +#define CL_DSP_WMDR_NAME_LEN 32 +#define CL_DSP_WMDR_DATE_LEN 16 +#define CL_DSP_WMDR_HEADER_LEN_SIZE 4 + +#define CL_DSP_WMDR_DATE_PREFIX "Date: " +#define CL_DSP_WMDR_DATE_PREFIX_LEN 6 + +#define CL_DSP_WMDR_FILE_NAME_MISSING "N/A" +#define CL_DSP_WMDR_FILE_DATE_MISSING "N/A" + +#define CL_DSP_WMDR_NAME_TYPE 0xFE00 +#define CL_DSP_WMDR_INFO_TYPE 0xFF00 + +//HALO core specific registers +#define CL_DSP_HALO_XMEM_PACKED_BASE 0x02000000 +#define CL_DSP_HALO_XROM_PACKED_BASE 0x02006000 +#define CL_DSP_HALO_XMEM_UNPACKED32_BASE 0x02400000 +#define CL_DSP_HALO_XMEM_UNPACKED24_BASE 0x02800000 +#define CL_DSP_HALO_XROM_UNPACKED24_BASE 0x02808000 +#define CL_DSP_HALO_YMEM_PACKED_BASE 0x02C00000 +#define CL_DSP_HALO_YMEM_UNPACKED32_BASE 0x03000000 +#define CL_DSP_HALO_YMEM_UNPACKED24_BASE 0x03400000 +#define CL_DSP_HALO_PMEM_BASE 0x03800000 +#define CL_DSP_HALO_PROM_BASE 0x03C60000 + +#define CL_DSP_HALO_XM_FW_ID_REG 0x0280000C +#define CL_DSP_HALO_NUM_ALGOS_REG 0x02800024 + +#define CL_DSP_HALO_ALGO_REV_OFFSET 4 +#define CL_DSP_HALO_ALGO_XM_BASE_OFFSET 8 +#define CL_DSP_HALO_ALGO_XM_SIZE_OFFSET 12 +#define CL_DSP_HALO_ALGO_YM_BASE_OFFSET 16 +#define CL_DSP_HALO_ALGO_YM_SIZE_OFFSET 20 +#define CL_DSP_ALGO_ENTRY_SIZE 24 + +/* open wavetable */ +#define CL_DSP_OWT_HEADER_MAX_LEN 254 +#define CL_DSP_OWT_HEADER_ENTRY_SIZE 12 + +/* macros */ +#define CL_DSP_WORD_ALIGN(n) (CL_DSP_BYTES_PER_WORD +\ + (((n) / CL_DSP_BYTES_PER_WORD) *\ + CL_DSP_BYTES_PER_WORD)) + +#define CL_DSP_GET_MAJOR(n) (((n) & CL_DSP_REV_MAJOR_MASK) >>\ + CL_DSP_REV_MAJOR_SHIFT) + +#define CL_DSP_GET_MINOR(n) (((n) & CL_DSP_REV_MINOR_MASK) >>\ + CL_DSP_REV_MINOR_SHIFT) + +#define CL_DSP_GET_PATCH(n) ((n) & CL_DSP_REV_PATCH_MASK) + +enum cl_dsp_wt_type { + WT_TYPE_V4_PCM = 0, + WT_TYPE_V4_PWLE = 1, + WT_TYPE_V4_PCM_F0_REDC = 2, + WT_TYPE_V4_PCM_F0_REDC_VAR = 3, + WT_TYPE_V4_COMPOSITE = 4, + WT_TYPE_V5_PCM_PCM_F0_REDC_Q = 5, + WT_TYPE_V5_PWLE_LONG = 6, + WT_TYPE_V5_PWLE_LINEAR = 7, + WT_TYPE_V6_PCM_F0_REDC = 8, + WT_TYPE_V6_PCM_F0_REDC_VAR = 9, + WT_TYPE_V6_COMPOSITE = 10, + WT_TYPE_V6_PCM_F0_REDC_Q = 11, + WT_TYPE_V6_PWLE = 12, + + WT_TYPE_TERMINATOR = 0xFF, +}; + +union cl_dsp_wmdr_header { + struct { + char magic[CL_DSP_BYTES_PER_WORD]; + u32 header_len; + u32 fw_revision : 24; + u8 file_format_version; + u32 api_revision : 24; + u8 target_core; + } __attribute__((__packed__)); + u8 data[CL_DSP_COEFF_FILE_HEADER_SIZE]; +}; + +union cl_dsp_wmfw_header { + struct { + char magic[CL_DSP_BYTES_PER_WORD]; + u32 header_len; + u16 api_revision; + u8 target_core; + u8 file_format_version; + u32 xm_size; + u32 ym_size; + u32 pm_size; + u32 zm_size; + u32 timestamp[2]; + u32 checksum; + } __attribute__((__packed__)); + u8 data[CL_DSP_FW_FILE_HEADER_SIZE]; +}; + +union cl_dsp_data_block_header { + struct { + u32 start_offset : 24; + u8 block_type; + u32 data_len; + } __attribute__((__packed__)); + u8 data[CL_DSP_DBLK_HEADER_SIZE]; +}; + +union cl_dsp_coeff_data_block_header { + struct { + u16 start_offset; + u16 block_type; + u32 algo_id; + u32 algo_rev; + u32 sample_rate; + u32 data_len; + } __attribute__((__packed__)); + u8 data[CL_DSP_COEFF_DBLK_HEADER_SIZE]; +}; + +struct cl_dsp_data_block { + union cl_dsp_data_block_header header; + u8 *payload; +}; + +struct cl_dsp_coeff_data_block { + union cl_dsp_coeff_data_block_header header; + u8 *payload; +}; + +struct cl_dsp_coeff_desc { + u32 parent_id; + char *parent_name; + u16 block_offset; + u16 block_type; + unsigned char name[CL_DSP_COEFF_NAME_LEN_MAX]; + unsigned int reg; + unsigned int flags; + unsigned int length; + struct list_head list; +}; + +struct cl_dsp_memchunk { + u8 *data; + u8 *max; + int bytes; + u32 cache; + int cachebits; +}; + +struct cl_dsp_owt_header { + enum cl_dsp_wt_type type; + u16 flags; + u32 offset; + u32 size; + void *data; +}; + +struct cl_dsp_owt_desc { + struct cl_dsp_owt_header waves[CL_DSP_OWT_HEADER_MAX_LEN]; + int nwaves; + int bytes; + u8 *raw_data; +}; + +struct cl_dsp_wt_desc { + unsigned int id; + char wt_name_xm[CL_DSP_WMDR_NAME_LEN]; + char wt_name_ym[CL_DSP_WMDR_NAME_LEN]; + unsigned int wt_limit_xm; + unsigned int wt_limit_ym; + char wt_file[CL_DSP_WMDR_NAME_LEN]; + char wt_date[CL_DSP_WMDR_DATE_LEN]; + struct cl_dsp_owt_desc owt; + bool is_xm; +}; + +struct cl_dsp_algo_info { + unsigned int id; + unsigned int rev; + unsigned int xm_base; + unsigned int xm_size; + unsigned int ym_base; + unsigned int ym_size; +}; + +struct cl_dsp { + struct device *dev; + struct regmap *regmap; + struct list_head coeff_desc_head; + unsigned int num_algos; + struct cl_dsp_algo_info algo_info[CL_DSP_NUM_ALGOS_MAX + 1]; + const struct cl_dsp_fw_desc *fw_desc; + const struct cl_dsp_mem_reg_desc *mem_reg_desc; + const struct cl_dsp_algo_params *algo_params; + struct cl_dsp_wt_desc *wt_desc; +}; + +#ifdef CONFIG_DEBUG_FS +/* Debug Logger */ +struct cl_dsp_host_buffer { + __be32 buf1_base; + __be32 buf1_size; + __be32 buf2_base; + __be32 buf1_buf2_size; + __be32 buf3_base; + __be32 buf_total_size; + __be32 high_water_mark; + __be32 irq_count; + __be32 irq_ack; + __be32 next_write_index; + __be32 next_read_index; + __be32 error; + __be32 oldest_block_index; + __be32 requested_rewind; + __be32 reserved_space; + __be32 min_free; + __be32 blocks_written[2]; + __be32 words_written[2]; +} __packed; + +struct cl_dsp_logger { + u32 *buf_data; + u32 buf_data_size; + u32 algo_id; + u32 host_buf_ptr; + u32 host_buf_base; + int host_buf_size_words; + u32 high_watermark; +}; + +struct cl_dsp_debugfs { + struct cl_dsp *core; + struct dentry *debugfs_root; + struct dentry *debugfs_node; + struct mutex lock; + struct cl_dsp_logger dl; +}; + +#define CL_DSP_DEBUGFS_NUM_CONTROLS 3 +#define CL_DSP_DEBUGFS_RW_FILE_MODE 0600 +#define CL_DSP_DEBUGFS_RO_FILE_MODE 0400 +#define CL_DSP_DEBUGFS_WO_FILE_MOADE 0200 +#define CL_DSP_DEBUGFS_TRACE_LOG_STRING_SIZE 3 +#define CL_DSP_DEBUGFS_TRACE_LOG_DISABLE 0 +#define CL_DSP_DEBUGFS_TRACE_LOG_ENABLE 1 + +#define CL_DSP_HOST_BUFFER_DATA_MASK 0x00FFFFFFu +#define CL_DSP_HOST_BUFFER_ERROR_OVERFLOW BIT(0) +#define CL_DSP_HOST_BUFFER_READ_INDEX_RESET 0x00FFFFFF +#define CL_DSP_HOST_BUFFER_IRQ_MASK BIT(0) +#define CL_DSP_HOST_BUFFER_DATA_SLOT_SIZE 10 + +#define HOST_BUFFER_FIELD(field) \ + (offsetof(struct cl_dsp_host_buffer, field) / sizeof(__be32)) + +int cl_dsp_logger_update(struct cl_dsp_debugfs *db); +struct cl_dsp_debugfs *cl_dsp_debugfs_create(struct cl_dsp *dsp, + struct dentry *parent_node, u32 event_log_algo_id); +void cl_dsp_debugfs_destroy(struct cl_dsp_debugfs *db); + +#endif /* CONFIG_DEBUG_FS */ + +/* Exported Functions */ +struct cl_dsp *cl_dsp_create(struct device *dev, struct regmap *regmap); +int cl_dsp_destroy(struct cl_dsp *dsp); +int cl_dsp_wavetable_create(struct cl_dsp *dsp, unsigned int id, + const char *wt_name_xm, const char *wt_name_ym, + const char *wt_file); +int cl_dsp_firmware_parse(struct cl_dsp *dsp, const struct firmware *fw, + bool write_fw); +int cl_dsp_coeff_file_parse(struct cl_dsp *dsp, const struct firmware *fw); +int cl_dsp_get_reg(struct cl_dsp *dsp, const char *coeff_name, + const unsigned int block_type, const unsigned int algo_id, + unsigned int *reg); +bool cl_dsp_algo_is_present(struct cl_dsp *dsp, const unsigned int algo_id); +struct cl_dsp_memchunk cl_dsp_memchunk_create(void *data, int size); +int cl_dsp_memchunk_write(struct cl_dsp_memchunk *ch, int nbits, u32 val); +int cl_dsp_memchunk_read(struct cl_dsp *dsp, struct cl_dsp_memchunk *ch, + int nbits, void *val); +int cl_dsp_memchunk_flush(struct cl_dsp_memchunk *ch); +int cl_dsp_raw_write(struct cl_dsp *dsp, unsigned int reg, + const void *val, size_t val_len, size_t limit); +int cl_dsp_fw_id_get(struct cl_dsp *dsp, unsigned int *id); +int cl_dsp_fw_rev_get(struct cl_dsp *dsp, unsigned int *rev); + +#endif /* __CL_DSP_H */ diff --git a/include/linux/firmware/cirrus/cs_dsp.h b/include/linux/firmware/cirrus/cs_dsp.h index cad828e21c72..6a738296173d 100644 --- a/include/linux/firmware/cirrus/cs_dsp.h +++ b/include/linux/firmware/cirrus/cs_dsp.h @@ -248,6 +248,8 @@ int cs_dsp_read_raw_data_block(struct cs_dsp *dsp, int mem_type, unsigned int me int cs_dsp_read_data_word(struct cs_dsp *dsp, int mem_type, unsigned int mem_addr, u32 *data); int cs_dsp_write_data_word(struct cs_dsp *dsp, int mem_type, unsigned int mem_addr, u32 data); void cs_dsp_remove_padding(u32 *buf, int nwords); +int cs_dsp_load_coeff(struct cs_dsp *dsp, const struct firmware *firmware, + const char *file); struct cs_dsp_alg_region *cs_dsp_find_alg_region(struct cs_dsp *dsp, int type, unsigned int id); diff --git a/include/linux/hall/hall_ic_notifier.h b/include/linux/hall/hall_ic_notifier.h new file mode 100644 index 000000000000..a3e9d596dab2 --- /dev/null +++ b/include/linux/hall/hall_ic_notifier.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2021 Samsung Electronics + */ + +#ifndef __HALL_NOTIFIER_H__ +#define __HALL_NOTIFIER_H__ + +#include + +struct hall_notifier_context { + const char *name; + int value; +}; + +extern int hall_notifier_register(struct notifier_block *n); +extern int hall_notifier_unregister(struct notifier_block *nb); +extern int hall_notifier_notify(const char *hall_name, int hall_value); +#endif /* __SEC_VIB_NOTIFIER_H__ */ diff --git a/include/linux/hall/sec_hall_dumpkey.h b/include/linux/hall/sec_hall_dumpkey.h new file mode 100644 index 000000000000..968b559e042c --- /dev/null +++ b/include/linux/hall/sec_hall_dumpkey.h @@ -0,0 +1,11 @@ +#ifndef __SEC_HALL_DUMPKEY_H__ +#define __SEC_HALL_DUMPKEY_H__ +struct sec_hall_dumpkey_param { + unsigned int keycode; + int down; +}; + +struct hall_dump_callbacks { + void (*inform_dump)(struct device *dev); +}; +#endif /* __SEC_HALL_DUMPKEY_H__ */ diff --git a/include/linux/hdm.h b/include/linux/hdm.h new file mode 100644 index 000000000000..a4c1f1548a5f --- /dev/null +++ b/include/linux/hdm.h @@ -0,0 +1,51 @@ +/* + * @file hdm.h + * @brief Header file for HDM driver + * Copyright (c) 2019, Samsung Electronics Corporation. 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 __HDM_H__ +#define __HDM_H__ +#include + + +#ifndef __ASSEMBLY__ + +#define HDM_CMD_LEN ((size_t)8) + +#define HDM_P_BITMASK 0xFFFF +#define HDM_C_BITMASK 0xF0000 +#define HDM_FLAG_SET 0x10000 +#define HDM_FLAG_UNSET 0x20000 +#define HDM_HYP_CALL 0x40000 +#define HDM_HYP_INIT 0x50000 +#define HDM_HYP_CLEAR 0x60000 +#define HDM_HYP_CALLP 0x80000 +#define HDM_CMD_MAX 0xFFFFF + +#define HDM_GET_SUPPORTED_SUBSYSTEM 6 + +#define HDM_WIFI_SUPPORT_BIT 0x08 +#define HDM_CP_SUPPORT_BIT 0x100 + +extern int hdm_is_wlan_enabled(void); +extern int hdm_is_cp_enabled(void); + +enum { + HDM_ALLOW = 0, + HDM_PROTECT, +}; + +extern const struct file_operations hdm_fops; + +#endif //__ASSEMBLY__ +#endif //__HDM_H__ diff --git a/include/linux/host_notify.h b/include/linux/host_notify.h new file mode 100644 index 000000000000..8cd1ddff1305 --- /dev/null +++ b/include/linux/host_notify.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Host notify class driver + * + * Copyright (C) 2011-2023 Samsung, Inc. + * Author: Dongrak Shin + * + */ + + /* usb notify layer v4.0 */ + +#ifndef __LINUX_HOST_NOTIFY_H__ +#define __LINUX_HOST_NOTIFY_H__ + +enum host_uevent_state { + NOTIFY_HOST_NONE, + NOTIFY_HOST_ADD, + NOTIFY_HOST_REMOVE, + NOTIFY_HOST_OVERCURRENT, + NOTIFY_HOST_LOWBATT, + NOTIFY_HOST_BLOCK, + NOTIFY_HOST_UNKNOWN, + NOTIFY_HOST_SOURCE, + NOTIFY_HOST_SINK, +}; + +enum host_uevent_type { + NOTIFY_UNKNOWN_STATE, + NOTIFY_HOST_STATE, + NOTIFY_POWER_STATE, +}; + +enum otg_hostnotify_mode { + NOTIFY_NONE_MODE, + NOTIFY_HOST_MODE, + NOTIFY_PERIPHERAL_MODE, + NOTIFY_TEST_MODE, +}; + +enum booster_power { + NOTIFY_POWER_OFF, + NOTIFY_POWER_ON, +}; + +enum set_command { + NOTIFY_SET_OFF, + NOTIFY_SET_ON, +}; + +struct host_notify_dev { + const char *name; + struct device *dev; + int index; + int host_state; + int host_change; + int power_state; + int power_change; + int mode; + int booster; + int (*set_mode)(bool on); + int (*set_booster)(bool on); +}; + +#ifdef CONFIG_USB_HOST_NOTIFY +extern int host_state_notify(struct host_notify_dev *ndev, int state); +extern int host_notify_dev_register(struct host_notify_dev *ndev); +extern void host_notify_dev_unregister(struct host_notify_dev *ndev); +#else +static inline int host_state_notify(struct host_notify_dev *ndev, int state) + {return 0; } +static inline int host_notify_dev_register(struct host_notify_dev *ndev) + {return 0; } +static inline void host_notify_dev_unregister(struct host_notify_dev *ndev) {} +#endif + +#endif /* __LINUX_HOST_NOTIFY_H__ */ diff --git a/include/linux/input/input_booster.h b/include/linux/input/input_booster.h new file mode 100644 index 000000000000..c933a0ede3a2 --- /dev/null +++ b/include/linux/input/input_booster.h @@ -0,0 +1,373 @@ +#ifndef _INPUT_BOOSTER_H_ +#define _INPUT_BOOSTER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_SOC_EXYNOS2100) || \ + IS_ENABLED(CONFIG_ARCH_LAHAINA) || \ + IS_ENABLED(CONFIG_ARCH_WAIPIO) +#include +#endif//CONFIG_SOC_EXYNOS2100 || CONFIG_ARCH_LAHAINA + +#if IS_ENABLED(CONFIG_MACH_MT6739) +#include +#endif//CONFIG_MACH_MT6739 + +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic ignored "-Wdeclaration-after-statement" +#define USE_HMP_BOOST (IS_ENABLED(CONFIG_SCHED_WALT)) + +#ifndef ITAG +#define ITAG " [Input Booster] " +#endif + +#define pr_booster(format, ...) { \ + if (debug_flag) \ + printk(ITAG format, ## __VA_ARGS__); \ +} + +#define IB_EVENT_TOUCH_BOOSTER 1 +#define MAX_MULTI_TOUCH_EVENTS 10 +#define MAX_IB_COUNT 100 +#define MAX_EVENT_COUNT 1024 +#define MAX_EVENTS (MAX_MULTI_TOUCH_EVENTS * 10) +#define INPUT_BOOSTER_NULL -1 +#define INIT_ZERO 0 +#define DEFAULT_LEVEL 0 +#define INPUT_LEVEL 2 + +//+++++++++++++++++++++++++++++++++++++++++++++++ STRUCT & VARIABLE FOR SYSFS +++++++++++++++++++++++++++++++++++++++++++++++// +#define SYSFS_CLASS(_ATTR_, _ARGU_, _COUNT_) \ + ssize_t input_booster_sysfs_class_show_##_ATTR_(struct class *dev, struct class_attribute *attr, char *buf) \ + { \ + ssize_t ret; \ + unsigned int enable_event; \ + unsigned int debug_level; \ + unsigned int sendevent; \ + unsigned int ib_mode_state; \ + enable_event = enable_event_booster; \ + debug_level = debug_flag; \ + sendevent = send_ev_enable; \ + if (IS_ENABLED(CONFIG_SEC_INPUT_BOOSTER_MODE)) \ + ib_mode_state = u_ib_mode; \ + ret = sprintf _ARGU_; \ + pr_booster("[Input Booster8] %s buf : %s\n", __func__, buf); \ + return ret; \ + } \ + ssize_t input_booster_sysfs_class_store_##_ATTR_(struct class *dev, struct class_attribute *attr, const char *buf, size_t count) \ + { \ + unsigned int enable_event[1] = {0}; \ + unsigned int debug_level[1] = {0}; \ + unsigned int sendevent[1] = {0}; \ + unsigned int ib_mode_state[1] = {0}; \ + enable_event[0] = enable_event_booster; \ + debug_level[0] = debug_flag; \ + sendevent[0] = send_ev_enable; \ + if (IS_ENABLED(CONFIG_SEC_INPUT_BOOSTER_MODE)) \ + ib_mode_state[0] = u_ib_mode; \ + sscanf _ARGU_; \ + send_ev_enable = sendevent[0]; \ + debug_flag = debug_level[0]; \ + if (IS_ENABLED(CONFIG_SEC_INPUT_BOOSTER_MODE)) \ + u_ib_mode = ib_mode_state[0] >= num_of_mode ? u_ib_mode : ib_mode_state[0]; \ + enable_event_booster = enable_event[0]; \ + pr_booster("[Input Booster8] %s buf : %s\n", __func__, buf); \ + if (sscanf _ARGU_ != _COUNT_) { \ + return count; \ + } \ + return count; \ + } \ + static struct class_attribute class_attr_##_ATTR_ = __ATTR(_ATTR_, S_IRUGO | S_IWUSR, input_booster_sysfs_class_show_##_ATTR_, input_booster_sysfs_class_store_##_ATTR_); + +#define HEAD_TAIL_SYSFS_DEVICE(_ATTR_) \ + ssize_t input_booster_sysfs_device_show_##_ATTR_(struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + int i = 0; \ + ssize_t ret = 0; \ + ssize_t return_value = 0; \ + struct t_ib_device_tree *ib_dt = dev_get_drvdata(dev); \ + if (ib_dt == NULL) { \ + return return_value; \ + } \ + ret = sprintf(buf, "%d", ib_dt->_ATTR_##_time); \ + return_value += ret; \ + buf = buf + ret; \ + if (allowed_resources == NULL) { \ + return return_value; \ + } \ + for (i = 0; i < allowed_res_count; ++i) { \ + pr_booster("[Input Booster8] show i : %d, %s\n", i, #_ATTR_); \ + ret = sprintf(buf, " %d", ib_dt->res[allowed_resources[i]]._ATTR_##_value); \ + buf = buf + ret; \ + return_value += ret; \ + } \ + ret = sprintf(buf, "\n"); \ + buf = buf + ret; \ + return_value += ret; \ + return return_value; \ + } \ + ssize_t input_booster_sysfs_device_store_##_ATTR_(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) \ + { \ + struct t_ib_device_tree *ib_dt = dev_get_drvdata(dev); \ + int time = 0; \ + int values[MAX_RES_COUNT + 1] = {0,}; \ + int i = 0; \ + int offset = 0; \ + int dataCnt = 0; \ + pr_booster("[Input Booster8] %s buf : %s\n", __func__, buf); \ + if (ib_dt == NULL) \ + return count; \ + if (!sscanf(buf, "%d%n", &time, &offset)) { \ + pr_booster("### Keep this format : [time cpu_freq hmp_boost ddr_freq lpm_bias] (Ex: 200 1171200 2 1017 5###\n"); \ + return count; \ + } \ + buf += offset; \ + if (allowed_resources == NULL) { \ + return count; \ + } \ + for (i = 0; i < allowed_res_count; ++i) { \ + if (!sscanf(buf, "%d%n", &values[allowed_resources[i]], &offset)) { \ + pr_booster("### Keep this format : [time cpu_freq hmp_boost ddr_freq lpm_bias] (Ex: 200 1171200 2 1017 5###\n"); \ + return count; \ + } \ + dataCnt++; \ + buf += offset; \ + } \ + if (sscanf(buf, "%d", &values[i])) { \ + pr_booster("### Keep this format : [time cpu_freq hmp_boost ddr_freq lpm_bias] (Ex: 200 1171200 2 1017 5###\n"); \ + return count; \ + } \ + ib_dt->_ATTR_##_time = time; \ + for (i = 0; i < allowed_res_count; ++i) { \ + ib_dt->res[allowed_resources[i]]._ATTR_##_value = values[allowed_resources[i]]; \ + } \ + return count; \ + } \ + DEVICE_ATTR(_ATTR_, S_IRUGO | S_IWUSR, input_booster_sysfs_device_show_##_ATTR_, input_booster_sysfs_device_store_##_ATTR_); + +#define LEVEL_SYSFS_DEVICE(_ATTR_) \ + ssize_t input_booster_sysfs_device_show_##_ATTR_(struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + ssize_t ret = 0; \ + ret += sprintf(buf, "%d\n", level_value); \ + return ret; \ + } \ + ssize_t input_booster_sysfs_device_store_##_ATTR_(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) \ + { \ + int level; \ + sscanf(buf, "%d", &level); \ + if (level < 0 || level > 2) { \ + pr_booster("### Keep this format : greater than 0, and less than 3\n"); \ + return count; \ + } \ + pr_booster("[Input Booster8] %s buf : %s\n", __func__, buf); \ + level_value = level; \ + return count; \ + } \ + DEVICE_ATTR(_ATTR_, S_IRUGO | S_IWUSR, input_booster_sysfs_device_show_##_ATTR_, input_booster_sysfs_device_store_##_ATTR_); + +#define INIT_SYSFS_CLASS(_CLASS_) { \ + ret = class_create_file(sysfs_class, &class_attr_##_CLASS_); \ + if (ret) { \ + pr_booster("[Input Booster] Failed to create class\n"); \ + class_destroy(sysfs_class); \ + return; \ + } \ + } + +//---------------------------------------------- STRUCT & VARIABLE FOR SYSFS ----------------------------------------------// + +#define TYPE_BITS 8 +#define CODE_BITS 12 + +enum ib_flag_on_off { + FLAG_OFF = 0, + FLAG_ON +}; + +enum booster_head_or_tail { + IB_HEAD = 0, + IB_TAIL, + IB_MAX +}; + +enum booster_mode_on_off { + BOOSTER_OFF = 0, + BOOSTER_ON, +}; + +enum { + NONE_TYPE_DEVICE = -1, + KEY = 0, + TOUCH_KEY, + TOUCH, + MULTI_TOUCH, + KEYBOARD, + MOUSE, + MOUSH_WHEEL, + HOVER, + SPEN, + MAX_DEVICE_TYPE_NUM +}; + +struct t_ib_trigger { + int key_id; + int event_type; + int dev_type; + + struct work_struct ib_trigger_work; +}; + +struct ib_event_data { + struct input_value *vals; + int evt_cnt; +}; + +struct ib_event_work { + struct input_value vals[MAX_EVENT_COUNT]; + int evt_cnt; + struct work_struct evdev_work; +}; + +struct t_ib_info { + int key_id; + int uniq_id; + int press_flag; + int rel_flag; + int isHeadFinished; + + struct t_ib_device_tree *ib_dt; + + struct list_head list; + + struct work_struct ib_state_work[IB_MAX]; + struct delayed_work ib_timeout_work[IB_MAX]; + + struct mutex lock; +}; + +struct t_ib_target { + int uniq_id; + long value; + struct list_head list; +}; + +struct t_ib_res_info { + int res_id; + const char *label; + int head_value; + int tail_value; +}; + +struct t_ib_device_tree { + const char *label; + int type; + int head_time; + int tail_time; + struct t_ib_res_info *res; +}; + +#if IS_ENABLED(CONFIG_SEC_INPUT_BOOSTER_MODE) +struct t_ib_boost_mode { + const char *label; + int type; + struct t_ib_device_tree *dt; + int dt_count; + unsigned int dt_mask; + int type_to_idx_table[MAX_DEVICE_TYPE_NUM]; +}; +#endif + +struct t_ddr_info { + long mHz; + long bps; +}; + +void trigger_input_booster(struct work_struct *work); +void press_state_func(struct work_struct *work); +void press_timeout_func(struct work_struct *work); +void release_state_func(struct work_struct *work); +void release_timeout_func(struct work_struct *work); +unsigned int create_uniq_id(int type, int code, int slot); + +void input_booster_init(void); +void input_booster_exit(void); + +#if IS_ENABLED(CONFIG_SEC_INPUT_BOOSTER_MODE) +void init_sysfs_device(struct class *sysfs_class, struct device* pdev, struct t_ib_device_tree *ib_dt); +#else +void init_sysfs_device(struct class *sysfs_class, struct t_ib_device_tree *ib_dt); +#endif + +#if IS_ENABLED(CONFIG_SEC_INPUT_BOOSTER_QC) || \ + IS_ENABLED(CONFIG_SEC_INPUT_BOOSTER_SLSI) || \ + IS_ENABLED(CONFIG_SEC_INPUT_BOOSTER_MTK) +void ib_release_booster(long *rel_flags); +void ib_set_booster(long *qos_values); +int input_booster_init_vendor(void); +void input_booster_exit_vendor(void); + +extern int sched_set_boost(int type); +extern int ib_notifier_register(struct notifier_block *nb); +extern int ib_notifier_unregister(struct notifier_block *nb); +#endif + +int set_freq_limit(unsigned long id, unsigned int freq); + +#if IS_ENABLED(CONFIG_SEC_INPUT_BOOSTER_QC) +extern void update_hyst_times_kernel(u64 ib_value); +enum booster_res_type { + CPUFREQ = 0, + DDRFREQ, + HMPBOOST, + LPMBIAS, + MAX_RES_COUNT +}; +#elif IS_ENABLED(CONFIG_SEC_INPUT_BOOSTER_SLSI) +enum booster_res_type { + CLUSTER2 = 0, + CLUSTER1, + CLUSTER0, + MIF, + INT, + HMPBOOST, + UCC, + MAX_RES_COUNT +}; +#elif IS_ENABLED(CONFIG_SEC_INPUT_BOOSTER_MTK) +enum booster_res_type { + CPUFREQ = 0, + DDRFREQ, + SCHEDBOOST, + MAX_RES_COUNT +}; +#endif + +extern int *cpu_cluster_policy; +extern int *allowed_resources; +extern int *release_val; +extern int allowed_res_count; +extern int max_resource_count; +extern int max_cluster_count; +extern int ib_init_succeed; +extern unsigned int debug_flag; +extern unsigned int enable_event_booster; +#if IS_ENABLED(CONFIG_SEC_INPUT_BOOSTER_MODE) +extern unsigned int u_ib_mode; +#endif + +extern int trigger_cnt; +// @ ib_trigger : input trigger starts input booster in evdev.c. +extern struct t_ib_trigger *ib_trigger; +// @evdev_mt_slot : save the number of inputed touch slot. +extern int evdev_mt_slot; +// @evdev_mt_event[] : save count of each boooter's events. +extern int evdev_mt_event[MAX_DEVICE_TYPE_NUM]; + +#endif // _INPUT_BOOSTER_H_ diff --git a/include/linux/ipa.h b/include/linux/ipa.h new file mode 100644 index 000000000000..280817ecfbca --- /dev/null +++ b/include/linux/ipa.h @@ -0,0 +1,2450 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2021-2023 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef _IPA_H_ +#define _IPA_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define IPA_APPS_MAX_BW_IN_MBPS 700 +#define IPA_BW_THRESHOLD_MAX 3 + +#define IPA_MAX_CH_STATS_SUPPORTED 5 +#define IPA_EP_ARR_SIZE 2 +#define IPA_EP_PER_REG 32 + +/* Notifiers for rmnet driver */ +#define BUFF_ABOVE_HIGH_THRESHOLD_FOR_DEFAULT_PIPE 1 +#define BUFF_ABOVE_HIGH_THRESHOLD_FOR_COAL_PIPE 2 +#define BUFF_BELOW_LOW_THRESHOLD_FOR_DEFAULT_PIPE 3 +#define BUFF_BELOW_LOW_THRESHOLD_FOR_COAL_PIPE 4 +#define BUFF_ABOVE_HIGH_THRESHOLD_FOR_LL_PIPE 5 +#define BUFF_BELOW_LOW_THRESHOLD_FOR_LL_PIPE 6 +#define FREE_PAGE_TASK_SCHEDULED 7 +#define FREE_PAGE_TASK_SCHEDULED_LL 8 + +/** + * the attributes of the socksv5 options + */ +#define IPA_SOCKSv5_ENTRY_VALID (1ul << 0) +#define IPA_SOCKSv5_IPV4 (1ul << 1) +#define IPA_SOCKSv5_IPV6 (1ul << 2) +#define IPA_SOCKSv5_OPT_TS (1ul << 3) +#define IPA_SOCKSv5_OPT_SACK (1ul << 4) +#define IPA_SOCKSv5_OPT_WS_STC (1ul << 5) +#define IPA_SOCKSv5_OPT_WS_DMC (1ul << 6) + +#define IPA_SOCKsv5_ADD_COM_ID 15 +#define IPA_SOCKsv5_ADD_V6_V4_COM_PM 1 +#define IPA_SOCKsv5_ADD_V4_V6_COM_PM 2 +#define IPA_SOCKsv5_ADD_V6_V6_COM_PM 3 + +/** + * enum ipa_transport_type + * transport type: either GSI or SPS + */ +enum ipa_transport_type { + IPA_TRANSPORT_TYPE_SPS, + IPA_TRANSPORT_TYPE_GSI +}; + +/** + * enum ipa_nat_en_type - NAT setting type in IPA end-point + */ +enum ipa_nat_en_type { + IPA_BYPASS_NAT, + IPA_SRC_NAT, + IPA_DST_NAT, +}; + +/** + * enum ipa_ipv6ct_en_type - IPv6CT setting type in IPA end-point + */ +enum ipa_ipv6ct_en_type { + IPA_BYPASS_IPV6CT, + IPA_ENABLE_IPV6CT, +}; + +/** + * enum ipa_mode_type - mode setting type in IPA end-point + * @BASIC: basic mode + * @ENABLE_FRAMING_HDLC: not currently supported + * @ENABLE_DEFRAMING_HDLC: not currently supported + * @DMA: all data arriving IPA will not go through IPA logic blocks, this + * allows IPA to work as DMA for specific pipes. + */ +enum ipa_mode_type { + IPA_BASIC, + IPA_ENABLE_FRAMING_HDLC, + IPA_ENABLE_DEFRAMING_HDLC, + IPA_DMA, +}; + +/** + * enum ipa_aggr_en_type - aggregation setting type in IPA + * end-point + */ +enum ipa_aggr_en_type { + IPA_BYPASS_AGGR, + IPA_ENABLE_AGGR, + IPA_ENABLE_DEAGGR, +}; + +/** + * enum ipa_aggr_type - type of aggregation in IPA end-point + */ +enum ipa_aggr_type { + IPA_MBIM_16 = 0, + IPA_HDLC = 1, + IPA_TLP = 2, + IPA_RNDIS = 3, + IPA_GENERIC = 4, + IPA_COALESCE = 5, + IPA_QCMAP = 6, +}; + +/** + * enum ipa_aggr_mode - global aggregation mode + */ +enum ipa_aggr_mode { + IPA_MBIM_AGGR, + IPA_QCNCM_AGGR, +}; + +/** + * enum ipa_dp_evt_type - type of event client callback is + * invoked for on data path + * @IPA_RECEIVE: data is struct sk_buff + * @IPA_WRITE_DONE: data is struct sk_buff + */ +enum ipa_dp_evt_type { + IPA_RECEIVE, + IPA_WRITE_DONE, +}; + +/** + * enum hdr_total_len_or_pad_type - type of value held by TOTAL_LEN_OR_PAD + * field in header configuration register. + * @IPA_HDR_PAD: field is used as padding length + * @IPA_HDR_TOTAL_LEN: field is used as total length + */ +enum hdr_total_len_or_pad_type { + IPA_HDR_PAD = 0, + IPA_HDR_TOTAL_LEN = 1, +}; + +/** + * struct ipa_ep_cfg_nat - NAT configuration in IPA end-point + * @nat_en: This defines the default NAT mode for the pipe: in case of + * filter miss - the default NAT mode defines the NATing operation + * on the packet. Valid for Input Pipes only (IPA consumer) + * @nat_exc_suppress: 1 - NAT exception is supressed and packet will be + * routed using configured routing tables. + * 0 - NAT exception is allowed and packets will be routed to exception + * pipe. Valid for input pipes only (IPA consumer) + */ +struct ipa_ep_cfg_nat { + enum ipa_nat_en_type nat_en; + bool nat_exc_suppress; +}; + +/** + * struct ipa_ep_cfg_conn_track - IPv6 Connection tracking configuration in + * IPA end-point + * @conn_track_en: Defines speculative conn_track action, means if specific + * pipe needs to have UL/DL IPv6 Connection Tracking or Bypass + * IPv6 Connection Tracking. 0: Bypass IPv6 Connection Tracking + * 1: IPv6 UL/DL Connection Tracking. + * Valid for Input Pipes only (IPA consumer) + */ +struct ipa_ep_cfg_conn_track { + enum ipa_ipv6ct_en_type conn_track_en; +}; + +/** + * struct ipa_ep_cfg_hdr - header configuration in IPA end-point + * + * @hdr_len:Header length in bytes to be added/removed. Assuming + * header len is constant per endpoint. Valid for + * both Input and Output Pipes + * @hdr_ofst_metadata_valid: 0: Metadata_Ofst value is invalid, i.e., no + * metadata within header. + * 1: Metadata_Ofst value is valid, i.e., metadata + * within header is in offset Metadata_Ofst Valid + * for Input Pipes only (IPA Consumer) (for output + * pipes, metadata already set within the header) + * @hdr_ofst_metadata: Offset within header in which metadata resides + * Size of metadata - 4bytes + * Example - Stream ID/SSID/mux ID. + * Valid for Input Pipes only (IPA Consumer) (for output + * pipes, metadata already set within the header) + * @hdr_additional_const_len: Defines the constant length that should be added + * to the payload length in order for IPA to update + * correctly the length field within the header + * (valid only in case Hdr_Ofst_Pkt_Size_Valid=1) + * Valid for Output Pipes (IPA Producer) + * Starting IPA4.5, this field in H/W requires more bits + * to support larger range, but no spare bits to use. + * So the MSB part is done thourgh the EXT register. + * When accessing this register, need to access the EXT + * register as well. + * @hdr_ofst_pkt_size_valid: 0: Hdr_Ofst_Pkt_Size value is invalid, i.e., no + * length field within the inserted header + * 1: Hdr_Ofst_Pkt_Size value is valid, i.e., a + * packet length field resides within the header + * Valid for Output Pipes (IPA Producer) + * @hdr_ofst_pkt_size: Offset within header in which packet size reside. Upon + * Header Insertion, IPA will update this field within the + * header with the packet length . Assumption is that + * header length field size is constant and is 2Bytes + * Valid for Output Pipes (IPA Producer) + * Starting IPA4.5, this field in H/W requires more bits + * to support larger range, but no spare bits to use. + * So the MSB part is done thourgh the EXT register. + * When accessing this register, need to access the EXT + * register as well. + * @hdr_a5_mux: Determines whether A5 Mux header should be added to the packet. + * This bit is valid only when Hdr_En=01(Header Insertion) + * SW should set this bit for IPA-to-A5 pipes. + * 0: Do not insert A5 Mux Header + * 1: Insert A5 Mux Header + * Valid for Output Pipes (IPA Producer) + * @hdr_remove_additional: bool switch, remove more of the header + * based on the aggregation configuration (register + * HDR_LEN_INC_DEAGG_HDR) + * @hdr_metadata_reg_valid: bool switch, metadata from + * register INIT_HDR_METADATA_n is valid. + * (relevant only for IPA Consumer pipes) + * Starting IPA4.5, this parameter is irrelevant and H/W + * assumes it is always valid. + */ +struct ipa_ep_cfg_hdr { + u32 hdr_len; + u32 hdr_ofst_metadata_valid; + u32 hdr_ofst_metadata; + u32 hdr_additional_const_len; + u32 hdr_ofst_pkt_size_valid; + u32 hdr_ofst_pkt_size; + u32 hdr_a5_mux; + u32 hdr_remove_additional; + u32 hdr_metadata_reg_valid; +}; + +/** + * struct ipa_ep_cfg_hdr_ext - extended header configuration in IPA end-point + * @hdr_pad_to_alignment: Pad packet to specified alignment + * (2^pad to alignment value), i.e. value of 3 means pad to 2^3 = 8 bytes + * alignment. Alignment is to 0,2 up to 32 bytes (IPAv2 does not support 64 + * byte alignment). Valid for Output Pipes only (IPA Producer). + * @hdr_total_len_or_pad_offset: Offset to length field containing either + * total length or pad length, per hdr_total_len_or_pad config + * @hdr_payload_len_inc_padding: 0-IPA_ENDP_INIT_HDR_n's + * HDR_OFST_PKT_SIZE does + * not includes padding bytes size, payload_len = packet length, + * 1-IPA_ENDP_INIT_HDR_n's HDR_OFST_PKT_SIZE includes + * padding bytes size, payload_len = packet length + padding + * @hdr_total_len_or_pad: field is used as PAD length ot as Total length + * (header + packet + padding) + * @hdr_total_len_or_pad_valid: 0-Ignore TOTAL_LEN_OR_PAD field, 1-Process + * TOTAL_LEN_OR_PAD field + * @hdr_little_endian: 0-Big Endian, 1-Little Endian + * @hdr: The header structure. Used starting IPA4.5 where part of the info + * at the header structure is implemented via the EXT register at the H/W + * @hdr_bytes_to_remove_valid: 0-Ignore hdr_bytes_to_remove field, 1-Process + * hdr_bytes_to_remove field + * @hdr_bytes_to_remove: desired bytes to remove from top of the packet for + * partial L2 header retention + */ +struct ipa_ep_cfg_hdr_ext { + u32 hdr_pad_to_alignment; + u32 hdr_total_len_or_pad_offset; + bool hdr_payload_len_inc_padding; + enum hdr_total_len_or_pad_type hdr_total_len_or_pad; + bool hdr_total_len_or_pad_valid; + bool hdr_little_endian; + struct ipa_ep_cfg_hdr *hdr; + bool hdr_bytes_to_remove_valid; + u32 hdr_bytes_to_remove; +}; + +/** + * struct ipa_ep_cfg_mode - mode configuration in IPA end-point + * @mode: Valid for Input Pipes only (IPA Consumer) + * @dst: This parameter specifies the output pipe to which the packets + * will be routed to. + * This parameter is valid for Mode=DMA and not valid for + * Mode=Basic + * Valid for Input Pipes only (IPA Consumer) + */ +struct ipa_ep_cfg_mode { + enum ipa_mode_type mode; + enum ipa_client_type dst; +}; + +/** + * struct ipa_ep_cfg_aggr - aggregation configuration in IPA end-point + * + * @aggr_en: Valid for both Input and Output Pipes + * @aggr: aggregation type (Valid for both Input and Output Pipes) + * @aggr_byte_limit: Limit of aggregated packet size in KB (<=32KB) When set + * to 0, there is no size limitation on the aggregation. + * When both, Aggr_Byte_Limit and Aggr_Time_Limit are set + * to 0, there is no aggregation, every packet is sent + * independently according to the aggregation structure + * Valid for Output Pipes only (IPA Producer ) + * @aggr_time_limit: Timer to close aggregated packet When set to 0, + * there is no time limitation on the aggregation. When + * both, Aggr_Byte_Limit and Aggr_Time_Limit are set to 0, + * there is no aggregation, every packet is sent + * independently according to the aggregation structure + * Valid for Output Pipes only (IPA Producer). + * Time unit is -->> usec <<-- + * @aggr_pkt_limit: Defines if EOF close aggregation or not. if set to false + * HW closes aggregation (sends EOT) only based on its + * aggregation config (byte/time limit, etc). if set to + * true EOF closes aggregation in addition to HW based + * aggregation closure. Valid for Output Pipes only (IPA + * Producer). EOF affects only Pipes configured for + * generic aggregation. + * @aggr_hard_byte_limit_en: If set to 1, byte-limit aggregation for this + * pipe will apply a hard-limit behavior which will not + * allow frames to be closed with more than byte-limit + * bytes. If set to 0, previous byte-limit behavior + * will apply - frames close once a packet causes the + * accumulated byte-count to cross the byte-limit + * threshold (closed frame will contain that packet). + * @aggr_sw_eof_active: 0: EOF does not close aggregation. HW closes aggregation + * (sends EOT) only based on its aggregation config + * (byte/time limit, etc). + * 1: EOF closes aggregation in addition to HW based + * aggregation closure. Valid for Output Pipes only (IPA + * Producer). EOF affects only Pipes configured for generic + * aggregation. + * @pulse_generator: Pulse generator number to be used. + * For internal use. + * Supported starting IPA4.5. + * @scaled_time: Time limit in accordance to the pulse generator + * granularity. + * For internal use + * Supported starting IPA4.5 + * @aggr_coal_l2: enable L2 coalescing on the specifid dest pipe, + * work only if AGGR_TYPE set to AGGR_TYPE_COALESCING. + * Supported starting IPA5.5 + */ +struct ipa_ep_cfg_aggr { + enum ipa_aggr_en_type aggr_en; + enum ipa_aggr_type aggr; + u32 aggr_byte_limit; + u32 aggr_time_limit; + u32 aggr_pkt_limit; + u32 aggr_hard_byte_limit_en; + bool aggr_sw_eof_active; + u8 pulse_generator; + u8 scaled_time; + bool aggr_coal_l2; +}; + +/** + * struct ipa_ep_cfg_route - route configuration in IPA end-point + * @rt_tbl_hdl: Defines the default routing table index to be used in case there + * is no filter rule matching, valid for Input Pipes only (IPA + * Consumer). Clients should set this to 0 which will cause default + * v4 and v6 routes setup internally by IPA driver to be used for + * this end-point + */ +struct ipa_ep_cfg_route { + u32 rt_tbl_hdl; +}; + +/** + * struct ipa_ep_cfg_holb - head of line blocking configuration in IPA end-point + * @en: enable(1 => ok to drop pkt)/disable(0 => never drop pkt) + * @tmr_val: duration in units of 128 IPA clk clock cyles [0,511], 1 clk=1.28us + * IPAv2.5 support 32 bit HOLB timeout value, previous versions + * supports 16 bit + * IPAv4.2: splitting timer value into 2 fields. Timer value is: + * BASE_VALUE * (2^SCALE) + * IPA4.5: tmr_val is in -->>msec<<--. Range is dynamic based + * on H/W configuration. (IPA4.5 absolute maximum is 0.65535*31 -> ~20sec). + * @base_val : IPA4.2 only field. base value of the timer. + * @scale : IPA4.2 only field. scale value for timer. + * @pulse_generator: Pulse generator number to be used. + * For internal use. + * Supported starting IPA4.5. + * @scaled_time: Time limit in accordance to the pulse generator granularity + * For internal use + * Supported starting IPA4.5 + */ +struct ipa_ep_cfg_holb { + u32 tmr_val; + u32 base_val; + u32 scale; + u16 en; + u8 pulse_generator; + u8 scaled_time; +}; + +/** + * struct ipa_ep_cfg_deaggr - deaggregation configuration in IPA end-point + * @deaggr_hdr_len: Deaggregation Header length in bytes. Valid only for Input + * Pipes, which are configured for 'Generic' deaggregation. + * @syspipe_err_detection - If set to 1, enables error detection for + * de-aggregration. Valid only for Input Pipes, which are configured + * for 'Generic' deaggregation. + * Note: if this bit is set, de-aggregated frames must be contiguous + * in memory. + * @packet_offset_valid: - 0: PACKET_OFFSET is not used, 1: PACKET_OFFSET is + * used. + * @packet_offset_location: Location of packet offset field, which specifies + * the offset to the packet from the start of the packet offset field. + * @ignore_min_pkt_err - Ignore packets smaller than header. This is intended + * for use in RNDIS de-aggregated pipes, to silently ignore a redundant + * 1-byte trailer in MSFT implementation. + * @max_packet_len: DEAGGR Max Packet Length in Bytes. A Packet with higher + * size wil be treated as an error. 0 - Packet Length is not Bound, + * IPA should not check for a Max Packet Length. + */ +struct ipa_ep_cfg_deaggr { + u32 deaggr_hdr_len; + bool syspipe_err_detection; + bool packet_offset_valid; + u32 packet_offset_location; + bool ignore_min_pkt_err; + u32 max_packet_len; +}; + +/** + * enum ipa_cs_offload - checksum offload setting + */ +enum ipa_cs_offload { + IPA_DISABLE_CS_OFFLOAD, + /* + * For enum value = 1, we check the csum required/valid bit which is the + * same bit used for both DL and UL but have different meanings. + * For UL pipe, HW checks if it needs to perform Csum caluclation. + * For DL pipe, HW checks if the csum is valid or invalid + */ + IPA_ENABLE_CS_OFFLOAD_UL, + IPA_ENABLE_CS_DL_QMAP = IPA_ENABLE_CS_OFFLOAD_UL, + IPA_ENABLE_CS_OFFLOAD_DL, + IPA_CS_RSVD +}; + +/** + * struct ipa_ep_cfg_cfg - IPA ENDP_INIT Configuration register + * @frag_offload_en: - 0 - IP packet fragment handling is disabled. IP packet + * fragments should be sent to SW. SW is responsible for + * configuring filter rules, and IP packet filter exception should be + * used to send all fragments to SW. 1 - IP packet fragment + * handling is enabled. IPA checks for fragments and uses frag + * rules table for processing fragments. Valid only for Input Pipes + * (IPA Consumer) + * @cs_offload_en: Checksum offload enable: 00: Disable checksum offload, 01: + * Enable checksum calculation offload (UL) - For output pipe + * (IPA producer) specifies that checksum trailer is to be added. + * For input pipe (IPA consumer) specifies presence of checksum + * header and IPA checksum calculation accordingly. 10: Enable + * checksum calculation offload (DL) - For output pipe (IPA + * producer) specifies that checksum trailer is to be added. For + * input pipe (IPA consumer) specifies IPA checksum calculation. + * 11: Reserved + * @cs_metadata_hdr_offset: Offset in Words (4 bytes) within header in which + * checksum metadata info header (4 bytes) starts (UL). Values are 0-15, which + * mean 0 - 60 byte checksum header offset. Valid for input + * pipes only (IPA consumer) + * @gen_qmb_master_sel: Select bit for ENDP GEN-QMB master. This is used to + * separate DDR & PCIe transactions in-order to limit them as + * a group (using MAX_WRITES/READS limiation). Valid for input and + * output pipes (IPA consumer+producer) + * @pipe_replicate_en: 1 - For consumer pipe - consumer DPL will be active. + * For producer pipe - producer DPL will be active. + * 0 - packet replication disabled for both consumer and producer pipe. + * Supported from IPA5.5 onwards. + */ +struct ipa_ep_cfg_cfg { + bool frag_offload_en; + enum ipa_cs_offload cs_offload_en; + u8 cs_metadata_hdr_offset; + u8 gen_qmb_master_sel; + u8 tx_instance; + bool pipe_replicate_en; +}; + +/** + * struct ipa_ep_cfg_prod_cfg - IPA ENDP_INIT Producer Configuration register + * @tx_instance: - 0 - select TX_0 instance. + * 1 - select TX_1 instance. + * @tsp_enable: boolean to indicate TSP-enablement per producer pipe. + * @max_output_size_drop_enable: enable policing by max output size for TSP + * feature. In case of TSP_ENABLE == 1 + valid egress_tc, max output size + * policing will be valid regardless to this bit. + * @tsp_idx: TSP producer-index. Controls pointer to producer-rate database. + * Valid only when TSP_ENABLE field is set. Value should be unique. + * @max_output_size: max output size allowed per producer. Value is in 64-byte + * resolution for TSP feature + * @egress_tc_lowest: Lowest egress traffic-class index assignes to this + * producer. + * @egress_tc_highest: Highest egress traffic-class index assignes to this + * producer. + */ +struct ipa_ep_cfg_prod_cfg { + u8 tx_instance; + bool tsp_enable; + bool max_output_size_drop_enable; + u8 tsp_idx; + u8 max_output_size; + u8 egress_tc_lowest; + u8 egress_tc_highest; +}; + +/** + * struct ipa_ep_cfg_metadata_mask - Endpoint initialization hdr metadata mask + * @metadata_mask: Mask specifying which metadata bits to write to + * IPA_ENDP_INIT_HDR_n.s HDR_OFST_METADATA. Only + * masked metadata bits (set to 1) will be written. Valid for Output + * Pipes only (IPA Producer) + */ +struct ipa_ep_cfg_metadata_mask { + u32 metadata_mask; +}; + +/** + * struct ipa_ep_cfg_metadata - Metadata configuration in IPA end-point + * @md: This defines the metadata from tx data descriptor + * @qmap_id: qmap id + */ +struct ipa_ep_cfg_metadata { + u32 qmap_id; +}; + +/** + * struct ipa_ep_cfg_seq - HPS/DPS sequencer type configuration in IPA end-point + * @set_dynamic: 0 - HPS/DPS seq type is configured statically, + * 1 - HPS/DPS seq type is set to seq_type + * @seq_type: HPS/DPS sequencer type configuration + */ +struct ipa_ep_cfg_seq { + bool set_dynamic; + int seq_type; +}; + +/** + * struct ipa_ep_cfg_ulso - ULSO configurations + * @ipid_min_max_idx: A value in the range [0, 2]. Determines the registers + * pair from which to read the minimum and maximum of IPv4 packets ID. It + * is set to 0 as this range is platform specific and there is no need for + * more than one pair values for this range. The minimum and maximum values + * are taken from the device tree in pre_init and are stored in dedicated + * registers. + * @is_ulso_pipe: Indicates whether the pipe is in ulso operation mode. + */ +struct ipa_ep_cfg_ulso { + int ipid_min_max_idx; + bool is_ulso_pipe; +}; + +/** + * struct ipa_ep_cfg - configuration of IPA end-point + * @nat: NAT parameters + * @conn_track: IPv6CT parameters + * @hdr: Header parameters + * @hdr_ext: Extended header parameters + * @mode: Mode parameters + * @aggr: Aggregation parameters + * @deaggr: Deaggregation params + * @route: Routing parameters + * @cfg: Configuration register data + * @metadata_mask: Hdr metadata mask + * @meta: Metadata + * @seq: HPS/DPS sequencers configuration + * @ulso: ULSO configuration + * @prod_cfg: Producer specific Configuration register data + */ +struct ipa_ep_cfg { + struct ipa_ep_cfg_nat nat; + struct ipa_ep_cfg_conn_track conn_track; + struct ipa_ep_cfg_hdr hdr; + struct ipa_ep_cfg_hdr_ext hdr_ext; + struct ipa_ep_cfg_mode mode; + struct ipa_ep_cfg_aggr aggr; + struct ipa_ep_cfg_deaggr deaggr; + struct ipa_ep_cfg_route route; + struct ipa_ep_cfg_cfg cfg; + struct ipa_ep_cfg_metadata_mask metadata_mask; + struct ipa_ep_cfg_metadata meta; + struct ipa_ep_cfg_seq seq; + struct ipa_ep_cfg_ulso ulso; + struct ipa_ep_cfg_prod_cfg prod_cfg; +}; + +/** + * struct ipa_ep_cfg_ctrl - Control configuration in IPA end-point + * @ipa_ep_suspend: 0 - ENDP is enabled, 1 - ENDP is suspended (disabled). + * Valid for PROD Endpoints + * @ipa_ep_delay: 0 - ENDP is free-running, 1 - ENDP is delayed. + * SW controls the data flow of an endpoint usind this bit. + * Valid for CONS Endpoints + */ +struct ipa_ep_cfg_ctrl { + bool ipa_ep_suspend; + bool ipa_ep_delay; +}; + +/** + * x should be in bytes + */ +#define IPA_NUM_OF_FIFO_DESC(x) (x/sizeof(struct sps_iovec)) +typedef void (*ipa_notify_cb)(void *priv, enum ipa_dp_evt_type evt, + unsigned long data); + +/** + * enum ipa_wdi_meter_evt_type - type of event client callback is + * for AP+STA mode metering + * @IPA_GET_WDI_SAP_STATS: get IPA_stats betwen SAP and STA - + * use ipa_get_wdi_sap_stats structure + * @IPA_SET_WIFI_QUOTA: set quota limit on STA - + * use ipa_set_wifi_quota structure + * @IPA_SET_WLAN_BW: set wlan BW - + * use ipa_set_wlan_bw structure + */ +enum ipa_wdi_meter_evt_type { + IPA_GET_WDI_SAP_STATS, + IPA_SET_WIFI_QUOTA, + IPA_INFORM_WLAN_BW, +}; + +struct ipa_get_wdi_sap_stats { + /* indicate to reset stats after query */ + uint8_t reset_stats; + /* indicate valid stats from wlan-fw */ + uint8_t stats_valid; + /* Tx: SAP->STA */ + uint64_t ipv4_tx_packets; + uint64_t ipv4_tx_bytes; + /* Rx: STA->SAP */ + uint64_t ipv4_rx_packets; + uint64_t ipv4_rx_bytes; + uint64_t ipv6_tx_packets; + uint64_t ipv6_tx_bytes; + uint64_t ipv6_rx_packets; + uint64_t ipv6_rx_bytes; +}; + +/** + * struct ipa_set_wifi_quota - structure used for + * IPA_SET_WIFI_QUOTA. + * + * @quota_bytes: Quota (in bytes) for the STA interface. + * @set_quota: Indicate whether to set the quota (use 1) or + * unset the quota. + * + */ +struct ipa_set_wifi_quota { + uint64_t quota_bytes; + uint8_t set_quota; + /* indicate valid quota set from wlan-fw */ + uint8_t set_valid; +}; + +/** + * struct ipa_inform_wlan_bw - structure used for + * IPA_INFORM_WLAN_BW. + * + * @index: Indicate which bw-index hit + * @throughput: throughput usage + * + */ +struct ipa_inform_wlan_bw { + uint8_t index; + uint64_t throughput; +}; + +typedef void (*ipa_wdi_meter_notifier_cb)(enum ipa_wdi_meter_evt_type evt, + void *data); + + +/** + * struct ipa_tx_intf - interface tx properties + * @num_props: number of tx properties + * @prop: the tx properties array + */ +struct ipa_tx_intf { + u32 num_props; + struct ipa_ioc_tx_intf_prop *prop; +}; + +/** + * struct ipa_rx_intf - interface rx properties + * @num_props: number of rx properties + * @prop: the rx properties array + */ +struct ipa_rx_intf { + u32 num_props; + struct ipa_ioc_rx_intf_prop *prop; +}; + +/** + * struct ipa_ext_intf - interface ext properties + * @excp_pipe_valid: is next field valid? + * @excp_pipe: exception packets should be routed to this pipe + * @num_props: number of ext properties + * @prop: the ext properties array + */ +struct ipa_ext_intf { + bool excp_pipe_valid; + enum ipa_client_type excp_pipe; + u32 num_props; + struct ipa_ioc_ext_intf_prop *prop; +}; + +/** + * struct ipa_sys_connect_params - information needed to setup an IPA end-point + * in system-BAM mode + * @ipa_ep_cfg: IPA EP configuration + * @client: the type of client who "owns" the EP + * @desc_fifo_sz: size of desc FIFO. This number is used to allocate the desc + * fifo for BAM. For GSI, this size is used by IPA driver as a + * baseline to calculate the GSI ring size in the following way: + * For PROD pipes, GSI ring is 4 * desc_fifo_sz. + For PROD pipes, GSI ring is 2 * desc_fifo_sz. + * @priv: callback cookie + * @notify: callback + * priv - callback cookie + * evt - type of event + * data - data relevant to event. May not be valid. See event_type + * enum for valid cases. + * @skip_ep_cfg: boolean field that determines if EP should be configured + * by IPA driver + * @keep_ipa_awake: when true, IPA will not be clock gated + * @napi_enabled: when true, IPA call client callback to start polling + * @bypass_agg: when true, IPA bypasses the aggregation + * @int_modt: GSI event ring interrupt moderation time + * cycles base interrupt moderation (32KHz clock) + * @int_modc: GSI event ring interrupt moderation packet counter + * @buff_size: Actual buff size of rx_pkt + * @ext_ioctl_v2: Flag to determine whether ioctl_v2 received + */ +struct ipa_sys_connect_params { + struct ipa_ep_cfg ipa_ep_cfg; + enum ipa_client_type client; + u32 desc_fifo_sz; + void *priv; + ipa_notify_cb notify; + bool skip_ep_cfg; + bool keep_ipa_awake; + struct napi_struct *napi_obj; + bool recycle_enabled; + bool bypass_agg; + u32 int_modt; + u32 int_modc; + u32 buff_size; + bool ext_ioctl_v2; +}; + +/** + * struct ipa_tx_meta - metadata for the TX packet + * @dma_address: dma mapped address of TX packet + * @dma_address_valid: is above field valid? + */ +struct ipa_tx_meta { + u8 pkt_init_dst_ep; + bool pkt_init_dst_ep_valid; + bool pkt_init_dst_ep_remote; + dma_addr_t dma_address; + bool dma_address_valid; +}; + +/** + * typedef ipa_msg_free_fn - callback function + * @param buff - [in] the message payload to free + * @param len - [in] size of message payload + * @param type - [in] the message type + * + * Message callback registered by kernel client with IPA driver to + * free message payload after IPA driver processing is complete + * + * No return value + */ +typedef void (*ipa_msg_free_fn)(void *buff, u32 len, u32 type); + +/** + * typedef ipa_msg_pull_fn - callback function + * @param buff - [in] where to copy message payload + * @param len - [in] size of buffer to copy payload into + * @param type - [in] the message type + * + * Message callback registered by kernel client with IPA driver for + * IPA driver to pull messages from the kernel client upon demand from + * user-space + * + * Returns how many bytes were copied into the buffer. + */ +typedef int (*ipa_msg_pull_fn)(void *buff, u32 len, u32 type); + +/** + * enum ipa_voltage_level - IPA Voltage levels + */ +enum ipa_voltage_level { + IPA_VOLTAGE_UNSPECIFIED, + IPA_VOLTAGE_SVS2 = IPA_VOLTAGE_UNSPECIFIED, + IPA_VOLTAGE_SVS, + IPA_VOLTAGE_NOMINAL, + IPA_VOLTAGE_TURBO, + IPA_VOLTAGE_MAX, +}; + +/** + * enum ipa_rm_event - IPA RM events + * + * Indicate the resource state change + */ +enum ipa_rm_event { + IPA_RM_RESOURCE_GRANTED, + IPA_RM_RESOURCE_RELEASED +}; + +typedef void (*ipa_rm_notify_cb)(void *user_data, + enum ipa_rm_event event, + unsigned long data); +/** + * struct ipa_rm_register_params - information needed to + * register IPA RM client with IPA RM + * + * @user_data: IPA RM client provided information + * to be passed to notify_cb callback below + * @notify_cb: callback which is called by resource + * to notify the IPA RM client about its state + * change IPA RM client is expected to perform non + * blocking operations only in notify_cb and + * release notification context as soon as + * possible. + */ +struct ipa_rm_register_params { + void *user_data; + ipa_rm_notify_cb notify_cb; +}; + +/** + * struct ipa_rm_create_params - information needed to initialize + * the resource + * @name: resource name + * @floor_voltage: floor voltage needed for client to operate in maximum + * bandwidth. + * @reg_params: register parameters, contains are ignored + * for consumer resource NULL should be provided + * for consumer resource + * @request_resource: function which should be called to request resource, + * NULL should be provided for producer resource + * @release_resource: function which should be called to release resource, + * NULL should be provided for producer resource + * + * IPA RM client is expected to perform non blocking operations only + * in request_resource and release_resource functions and + * release notification context as soon as possible. + */ +struct ipa_rm_create_params { + enum ipa_rm_resource_name name; + enum ipa_voltage_level floor_voltage; + struct ipa_rm_register_params reg_params; + int (*request_resource)(void); + int (*release_resource)(void); +}; + +/** + * struct ipa_rm_perf_profile - information regarding IPA RM client performance + * profile + * + * @max_bandwidth_mbps: maximum bandwidth need of the client in Mbps + */ +struct ipa_rm_perf_profile { + u32 max_supported_bandwidth_mbps; +}; + +#define A2_MUX_HDR_NAME_V4_PREF "dmux_hdr_v4_" +#define A2_MUX_HDR_NAME_V6_PREF "dmux_hdr_v6_" + +/** + * struct ipa_tx_data_desc - information needed + * to send data packet to HW link: link to data descriptors + * priv: client specific private data + * @pyld_buffer: pointer to the data buffer that holds frame + * @pyld_len: length of the data packet + */ +struct ipa_tx_data_desc { + struct list_head link; + void *priv; + void *pyld_buffer; + u16 pyld_len; +}; + +/** + * struct ipa_rx_data - information needed + * to send to wlan driver on receiving data from ipa hw + * @skb: skb + * @dma_addr: DMA address of this Rx packet + */ +struct ipa_rx_data { + struct sk_buff *skb; + dma_addr_t dma_addr; +}; + +/** + * enum ipa_irq_type - IPA Interrupt Type + * Used to register handlers for IPA interrupts + * + * Below enum is a logical mapping and not the actual interrupt bit in HW + */ +enum ipa_irq_type { + IPA_BAD_SNOC_ACCESS_IRQ, + IPA_UC_IRQ_0, + IPA_UC_IRQ_1, + IPA_UC_IRQ_2, + IPA_UC_IRQ_3, + IPA_UC_IN_Q_NOT_EMPTY_IRQ, + IPA_UC_RX_CMD_Q_NOT_FULL_IRQ, + IPA_PROC_TO_UC_ACK_Q_NOT_EMPTY_IRQ, + IPA_RX_ERR_IRQ, + IPA_DEAGGR_ERR_IRQ, + IPA_TX_ERR_IRQ, + IPA_STEP_MODE_IRQ, + IPA_PROC_ERR_IRQ, + IPA_TX_SUSPEND_IRQ, + IPA_TX_HOLB_DROP_IRQ, + IPA_BAM_GSI_IDLE_IRQ, + IPA_PIPE_YELLOW_MARKER_BELOW_IRQ, + IPA_PIPE_RED_MARKER_BELOW_IRQ, + IPA_PIPE_YELLOW_MARKER_ABOVE_IRQ, + IPA_PIPE_RED_MARKER_ABOVE_IRQ, + IPA_UCP_IRQ, + IPA_DCMP_IRQ, + IPA_GSI_EE_IRQ, + IPA_GSI_IPA_IF_TLV_RCVD_IRQ, + IPA_GSI_UC_IRQ, + IPA_TLV_LEN_MIN_DSM_IRQ, + IPA_DRBIP_PKT_EXCEED_MAX_SIZE_IRQ, + IPA_DRBIP_DATA_SCTR_CFG_ERROR_IRQ, + IPA_DRBIP_IMM_CMD_NO_FLSH_HZRD_IRQ, + IPA_IRQ_MAX +}; + +/** + * typedef ipa_irq_handler_t - irq handler/callback type + * @param ipa_irq_type - [in] interrupt type + * @param private_data - [in, out] the client private data + * @param interrupt_data - [out] interrupt information data + * + * callback registered by ipa_add_interrupt_handler function to + * handle a specific interrupt type + * + * No return value + */ +typedef void (*ipa_irq_handler_t)(enum ipa_irq_type interrupt, + void *private_data, + void *interrupt_data); + +/** + * struct IpaHwBamStats_t - Structure holding the BAM statistics + * + * @bamFifoFull : Number of times Bam Fifo got full - For In Ch: Good, + * For Out Ch: Bad + * @bamFifoEmpty : Number of times Bam Fifo got empty - For In Ch: Bad, + * For Out Ch: Good + * @bamFifoUsageHigh : Number of times Bam fifo usage went above 75% - + * For In Ch: Good, For Out Ch: Bad + * @bamFifoUsageLow : Number of times Bam fifo usage went below 25% - + * For In Ch: Bad, For Out Ch: Good + */ +struct IpaHwBamStats_t { + u32 bamFifoFull; + u32 bamFifoEmpty; + u32 bamFifoUsageHigh; + u32 bamFifoUsageLow; + u32 bamUtilCount; +} __packed; + +/** + * struct IpaHwRingStats_t - Structure holding the Ring statistics + * + * @ringFull : Number of times Transfer Ring got full - For In Ch: Good, + * For Out Ch: Bad + * @ringEmpty : Number of times Transfer Ring got empty - For In Ch: Bad, + * For Out Ch: Good + * @ringUsageHigh : Number of times Transfer Ring usage went above 75% - + * For In Ch: Good, For Out Ch: Bad + * @ringUsageLow : Number of times Transfer Ring usage went below 25% - + * For In Ch: Bad, For Out Ch: Good + */ +struct IpaHwRingStats_t { + u32 ringFull; + u32 ringEmpty; + u32 ringUsageHigh; + u32 ringUsageLow; + u32 RingUtilCount; +} __packed; + +/** + * struct ipa_uc_dbg_rtk_ring_stats - uC dbg stats info for RTK + * offloading protocol + * @commStats: common stats + * @trCount: transfer ring count + * @erCount: event ring count + * @totalAosCount: total AoS completion count + * @busyTime: total busy time + */ +struct ipa_uc_dbg_rtk_ring_stats { + struct IpaHwRingStats_t commStats; + u32 trCount; + u32 erCount; + u32 totalAosCount; + u64 busyTime; +} __packed; + +/** + * struct IpaHwStatsWDIRxInfoData_t - Structure holding the WDI Rx channel + * structures + * + * @max_outstanding_pkts : Number of outstanding packets in Rx Ring + * @num_pkts_processed : Number of packets processed - cumulative + * @rx_ring_rp_value : Read pointer last advertized to the WLAN FW + * @rx_ind_ring_stats : Ring info + * @bam_stats : BAM info + * @num_bam_int_handled : Number of Bam Interrupts handled by FW + * @num_db : Number of times the doorbell was rung + * @num_unexpected_db : Number of unexpected doorbells + * @num_pkts_in_dis_uninit_state : number of completions we + * received in disabled or uninitialized state + * @num_ic_inj_vdev_change : Number of times the Imm Cmd is + * injected due to vdev_id change + * @num_ic_inj_fw_desc_change : Number of times the Imm Cmd is + * injected due to fw_desc change + * @num_qmb_int_handled : Number of QMB interrupts handled + */ +struct IpaHwStatsWDIRxInfoData_t { + u32 max_outstanding_pkts; + u32 num_pkts_processed; + u32 rx_ring_rp_value; + struct IpaHwRingStats_t rx_ind_ring_stats; + struct IpaHwBamStats_t bam_stats; + u32 num_bam_int_handled; + u32 num_db; + u32 num_unexpected_db; + u32 num_pkts_in_dis_uninit_state; + u32 num_ic_inj_vdev_change; + u32 num_ic_inj_fw_desc_change; + u32 num_qmb_int_handled; + u32 reserved1; + u32 reserved2; +} __packed; + +/** + * struct IpaHwStatsWDITxInfoData_t - Structure holding the WDI Tx channel + * structures + * + * @num_pkts_processed : Number of packets processed - cumulative + * @copy_engine_doorbell_value : latest value of doorbell written to copy engine + * @num_db_fired : Number of DB from uC FW to Copy engine + * @tx_comp_ring_stats : ring info + * @bam_stats : BAM info + * @num_db : Number of times the doorbell was rung + * @num_unexpected_db : Number of unexpected doorbells + * @num_bam_int_handled : Number of Bam Interrupts handled by FW + * @num_bam_int_in_non_running_state : Number of Bam interrupts while not in + * Running state + * @num_qmb_int_handled : Number of QMB interrupts handled + */ +struct IpaHwStatsWDITxInfoData_t { + u32 num_pkts_processed; + u32 copy_engine_doorbell_value; + u32 num_db_fired; + struct IpaHwRingStats_t tx_comp_ring_stats; + struct IpaHwBamStats_t bam_stats; + u32 num_db; + u32 num_unexpected_db; + u32 num_bam_int_handled; + u32 num_bam_int_in_non_running_state; + u32 num_qmb_int_handled; + u32 num_bam_int_handled_while_wait_for_bam; +} __packed; + +/** + * struct IpaHwStatsWDIInfoData_t - Structure holding the WDI channel structures + * + * @rx_ch_stats : RX stats + * @tx_ch_stats : TX stats + */ +struct IpaHwStatsWDIInfoData_t { + struct IpaHwStatsWDIRxInfoData_t rx_ch_stats; + struct IpaHwStatsWDITxInfoData_t tx_ch_stats; +} __packed; + + +/** + * struct ipa_wdi_ul_params - WDI_RX configuration + * @rdy_ring_base_pa: physical address of the base of the Rx ring (containing + * Rx buffers) + * @rdy_ring_size: size of the Rx ring in bytes + * @rdy_ring_rp_pa: physical address of the location through which IPA uc is + * reading (WDI-1.0) + * @rdy_comp_ring_base_pa: physical address of the base of the Rx completion + * ring (WDI-2.0) + * @rdy_comp_ring_wp_pa: physical address of the location through which IPA + * uc is writing (WDI-2.0) + * @rdy_comp_ring_size: size of the Rx_completion ring in bytes + * expected to communicate about the Read pointer into the Rx Ring + */ +struct ipa_wdi_ul_params { + phys_addr_t rdy_ring_base_pa; + u32 rdy_ring_size; + phys_addr_t rdy_ring_rp_pa; + phys_addr_t rdy_comp_ring_base_pa; + phys_addr_t rdy_comp_ring_wp_pa; + u32 rdy_comp_ring_size; + u32 *rdy_ring_rp_va; + u32 *rdy_comp_ring_wp_va; + bool is_txr_rn_db_pcie_addr; + bool is_evt_rn_db_pcie_addr; +}; + +/** + * struct ipa_wdi_ul_params_smmu - WDI_RX configuration (with WLAN SMMU) + * @rdy_ring: SG table describing the Rx ring (containing Rx buffers) + * @rdy_ring_size: size of the Rx ring in bytes + * @rdy_ring_rp_pa: physical address of the location through which IPA uc is + * expected to communicate about the Read pointer into the Rx Ring + */ +struct ipa_wdi_ul_params_smmu { + struct sg_table rdy_ring; + u32 rdy_ring_size; + phys_addr_t rdy_ring_rp_pa; + struct sg_table rdy_comp_ring; + phys_addr_t rdy_comp_ring_wp_pa; + u32 rdy_comp_ring_size; + u32 *rdy_ring_rp_va; + u32 *rdy_comp_ring_wp_va; + bool is_txr_rn_db_pcie_addr; + bool is_evt_rn_db_pcie_addr; +}; + +/** + * struct ipa_wdi_dl_params - WDI_TX configuration + * @comp_ring_base_pa: physical address of the base of the Tx completion ring + * @comp_ring_size: size of the Tx completion ring in bytes + * @ce_ring_base_pa: physical address of the base of the Copy Engine Source + * Ring + * @ce_door_bell_pa: physical address of the doorbell that the IPA uC has to + * write into to trigger the copy engine + * @ce_ring_size: Copy Engine Ring size in bytes + * @num_tx_buffers: Number of pkt buffers allocated + */ +struct ipa_wdi_dl_params { + phys_addr_t comp_ring_base_pa; + u32 comp_ring_size; + phys_addr_t ce_ring_base_pa; + phys_addr_t ce_door_bell_pa; + u32 ce_ring_size; + u32 num_tx_buffers; + bool is_txr_rn_db_pcie_addr; + bool is_evt_rn_db_pcie_addr; +}; + +/** + * struct ipa_wdi_dl_params_smmu - WDI_TX configuration (with WLAN SMMU) + * @comp_ring: SG table describing the Tx completion ring + * @comp_ring_size: size of the Tx completion ring in bytes + * @ce_ring: SG table describing the Copy Engine Source Ring + * @ce_door_bell_pa: physical address of the doorbell that the IPA uC has to + * write into to trigger the copy engine + * @ce_ring_size: Copy Engine Ring size in bytes + * @num_tx_buffers: Number of pkt buffers allocated + */ +struct ipa_wdi_dl_params_smmu { + struct sg_table comp_ring; + u32 comp_ring_size; + struct sg_table ce_ring; + phys_addr_t ce_door_bell_pa; + u32 ce_ring_size; + u32 num_tx_buffers; + bool is_txr_rn_db_pcie_addr; + bool is_evt_rn_db_pcie_addr; +}; + +/** + * struct ipa_wdi_in_params - information provided by WDI client + * @sys: IPA EP configuration info + * @ul: WDI_RX configuration info + * @dl: WDI_TX configuration info + * @ul_smmu: WDI_RX configuration info when WLAN uses SMMU + * @dl_smmu: WDI_TX configuration info when WLAN uses SMMU + * @smmu_enabled: true if WLAN uses SMMU + * @ipa_wdi_meter_notifier_cb: Get WDI stats and quato info + */ +struct ipa_wdi_in_params { + struct ipa_sys_connect_params sys; + union { + struct ipa_wdi_ul_params ul; + struct ipa_wdi_dl_params dl; + struct ipa_wdi_ul_params_smmu ul_smmu; + struct ipa_wdi_dl_params_smmu dl_smmu; + } u; + bool smmu_enabled; +#ifdef IPA_WAN_MSG_IPv6_ADDR_GW_LEN + ipa_wdi_meter_notifier_cb wdi_notify; +#endif +}; + +enum ipa_upstream_type { + IPA_UPSTEAM_MODEM = 1, + IPA_UPSTEAM_WLAN, + IPA_UPSTEAM_MAX +}; + +/** + * struct ipa_wdi_out_params - information provided to WDI client + * @uc_door_bell_pa: physical address of IPA uc doorbell + * @clnt_hdl: opaque handle assigned to client + */ +struct ipa_wdi_out_params { + phys_addr_t uc_door_bell_pa; + u32 clnt_hdl; +}; + +/** + * struct ipa_wdi_db_params - information provided to retrieve + * physical address of uC doorbell + * @client: type of "client" (IPA_CLIENT_WLAN#_PROD/CONS) + * @uc_door_bell_pa: physical address of IPA uc doorbell + */ +struct ipa_wdi_db_params { + enum ipa_client_type client; + phys_addr_t uc_door_bell_pa; +}; + +/** + * struct ipa_wdi_uc_ready_params - uC ready CB parameters + * @is_uC_ready: uC loaded or not + * @priv : callback cookie + * @notify: callback + */ +typedef void (*ipa_uc_ready_cb)(void *priv); +struct ipa_wdi_uc_ready_params { + bool is_uC_ready; + void *priv; + ipa_uc_ready_cb notify; +}; + +/** + * struct ipa_wdi_buffer_info - address info of a WLAN allocated buffer + * @pa: physical address of the buffer + * @iova: IOVA of the buffer as embedded inside the WDI descriptors + * @size: size in bytes of the buffer + * @result: result of map or unmap operations (out param) + * + * IPA driver will create/release IOMMU mapping in IPA SMMU from iova->pa + */ +struct ipa_wdi_buffer_info { + phys_addr_t pa; + unsigned long iova; + size_t size; + int result; +}; + +/** + * struct ipa_wdi_bw_info - address info of a WLAN allocated buffer + * @threshold: throughput wants to be monitored + * @num: number of threshold entries + * @stop: true to stop monitoring + * + * IPA driver will create/release IOMMU mapping in IPA SMMU from iova->pa + */ +struct ipa_wdi_bw_info { + uint64_t threshold[IPA_BW_THRESHOLD_MAX]; + int num; + bool stop; +}; + +/** + * struct ipa_wdi_tx_info - sw tx info from WLAN + * @sta_tx: sw tx stats on sta interface + * @ap_tx: sw tx stats on ap interface + * + * IPA driver will create/release IOMMU mapping in IPA SMMU from iova->pa + */ +struct ipa_wdi_tx_info { + uint64_t sta_tx; + uint64_t ap_tx; +}; + +/** + * struct ipa_gsi_ep_config - IPA GSI endpoint configurations + * + * @ipa_ep_num: IPA EP pipe number + * @ipa_gsi_chan_num: GSI channel number + * @ipa_if_tlv: number of IPA_IF TLV + * @ipa_if_aos: number of IPA_IF AOS + * @ee: Execution environment + * @prefetch_mode: Prefetch mode to be used + * @prefetch_threshold: Prefetch empty level threshold. + * relevant for smart and free prefetch modes + */ +struct ipa_gsi_ep_config { + int ipa_ep_num; + int ipa_gsi_chan_num; + int ipa_if_tlv; + int ipa_if_aos; + int ee; + enum gsi_prefetch_mode prefetch_mode; + uint8_t prefetch_threshold; +}; + +/** + * struct ipa_smmu_in_params - information provided from client + * @ipa_smmu_client_type: clinet requesting for the smmu info. + */ + +enum ipa_smmu_client_type { + IPA_SMMU_WLAN_CLIENT, + IPA_SMMU_AP_CLIENT, + IPA_SMMU_WIGIG_CLIENT, + IPA_SMMU_WLAN1_CLIENT, + IPA_SMMU_ETH_CLIENT, + IPA_SMMU_ETH1_CLIENT, + IPA_SMMU_CLIENT_MAX +}; + +struct ipa_smmu_in_params { + enum ipa_smmu_client_type smmu_client; +}; + +/** + * struct ipa_smmu_out_params - information provided to IPA client + * @smmu_enable: IPA S1 SMMU enable/disable status + * @shared_cb: is client CB shared (mappings should be done by client only) + */ +struct ipa_smmu_out_params { + bool smmu_enable; + bool shared_cb; +}; + +struct iphdr_rsv { + struct iphdr ipv4_temp; /* 20 bytes */ + uint32_t rsv1; + uint32_t rsv2; + uint32_t rsv3; + uint32_t rsv4; + uint32_t rsv5; +} __packed; + +union ip_hdr_temp { + struct iphdr_rsv ipv4_rsv; /* 40 bytes */ + struct ipv6hdr ipv6_temp; /* 40 bytes */ +} __packed; + +struct ipa_socksv5_uc_tmpl { + uint16_t cmd_id; + uint16_t rsv; + uint32_t cmd_param; + uint16_t pkt_count; + uint16_t rsv2; + uint32_t byte_count; + union ip_hdr_temp ip_hdr; + /* 2B src/dst port */ + uint16_t src_port; + uint16_t dst_port; + + /* attribute mask */ + uint32_t ipa_sockv5_mask; + + /* reqquired update 4B/4B Seq/Ack/SACK */ + uint32_t out_irs; + uint32_t out_iss; + uint32_t in_irs; + uint32_t in_iss; + + /* option 10B: time-stamp */ + uint32_t out_ircv_tsval; + uint32_t in_ircv_tsecr; + uint32_t out_ircv_tsecr; + uint32_t in_ircv_tsval; + + /* option 2B: window-scaling/dynamic */ + uint16_t in_isnd_wscale:4; + uint16_t out_isnd_wscale:4; + uint16_t in_ircv_wscale:4; + uint16_t out_ircv_wscale:4; + uint16_t MAX_WINDOW_SIZE; + /* 11*4 + 40 bytes = 84 bytes */ + uint32_t rsv3; + uint32_t rsv4; + uint32_t rsv5; + uint32_t rsv6; + uint32_t rsv7; + uint32_t rsv8; + uint32_t rsv9; +} __packed; +/*reserve 16 bytes : 16 bytes+ 40 bytes + 44 bytes = 100 bytes (28 bytes left)*/ + +struct ipa_socksv5_info { + /* ipa-uc info */ + struct ipa_socksv5_uc_tmpl ul_out; + struct ipa_socksv5_uc_tmpl dl_out; + + /* ipacm info */ + struct ipacm_socksv5_info ul_in; + struct ipacm_socksv5_info dl_in; + + /* output: handle (index) */ + uint16_t handle; +}; + +struct ipa_ipv6_nat_uc_tmpl { + uint16_t cmd_id; + uint16_t rsv; + uint32_t cmd_param; + uint16_t pkt_count; + uint16_t rsv2; + uint32_t byte_count; + uint64_t private_address_lsb; + uint64_t private_address_msb; + uint64_t public_address_lsb; + uint64_t public_address_msb; + uint16_t private_port; + uint16_t public_port; + uint32_t rsv3; + uint64_t rsv4; + uint64_t rsv5; + uint64_t rsv6; + uint64_t rsv7; + uint64_t rsv8; + uint64_t rsv9; + uint64_t rsv10; + uint64_t rsv11; + uint64_t rsv12; +} __packed; + +#if IS_ENABLED(CONFIG_IPA3) +/* + * Configuration + */ + +/** + * ipa_cfg_ep_ctrl() - IPA end-point Control configuration + * @clnt_hdl: [in] opaque client handle assigned by IPA to client + * @ipa_ep_cfg_ctrl: [in] IPA end-point configuration params + * + * Returns: 0 on success, negative on failure + */ +int ipa_cfg_ep_ctrl(u32 clnt_hdl, const struct ipa_ep_cfg_ctrl *ep_ctrl); + +/* + * Routing + */ + +/** + * ipa_add_rt_rule() - Add the specified routing rules to SW and optionally + * commit to IPA HW + * @rules: [inout] set of routing rules to add + * + * Returns: 0 on success, negative on failure + * + * Note: Should not be called from atomic context + */ +int ipa_add_rt_rule(struct ipa_ioc_add_rt_rule *rules); + +/** + * ipa_put_rt_tbl() - Release the specified routing table handle + * @rt_tbl_hdl: [in] the routing table handle to release + * + * Returns: 0 on success, negative on failure + * + * Note: Should not be called from atomic context + */ +int ipa_put_rt_tbl(u32 rt_tbl_hdl); + +/* + * Interface + */ +int ipa_register_intf(const char *name, + const struct ipa_tx_intf *tx, + const struct ipa_rx_intf *rx); +int ipa_deregister_intf(const char *name); + +/* + * Aggregation + */ + +/** + * ipa_set_aggr_mode() - Set the aggregation mode which is a global setting + * @mode: [in] the desired aggregation mode for e.g. straight MBIM, QCNCM, + * etc + * + * Returns: 0 on success + */ + +int ipa_set_aggr_mode(enum ipa_aggr_mode mode); + +/** + * ipa_set_qcncm_ndp_sig() - Set the NDP signature used for QCNCM aggregation + * mode + * @sig: [in] the first 3 bytes of QCNCM NDP signature (expected to be + * "QND") + * + * Set the NDP signature used for QCNCM aggregation mode. The fourth byte + * (expected to be 'P') needs to be set using the header addition mechanism + * + * Returns: 0 on success, negative on failure + */ +int ipa_set_qcncm_ndp_sig(char sig[3]); + +/** + * ipa_set_single_ndp_per_mbim() - Enable/disable single NDP per MBIM frame + * configuration + * @enable: [in] true for single NDP/MBIM; false otherwise + * + * Returns: 0 on success + */ +int ipa_set_single_ndp_per_mbim(bool enable); + +/* + * interrupts + */ + +/** + * ipa_add_interrupt_handler() - Adds handler to an interrupt type + * @interrupt: Interrupt type + * @handler: The handler to be added + * @deferred_flag: whether the handler processing should be deferred in + * a workqueue + * @private_data: the client's private data + * + * Adds handler to an interrupt type and enable the specific bit + * in IRQ_EN register, associated interrupt in IRQ_STTS register will be enabled + */ + +int ipa_add_interrupt_handler(enum ipa_irq_type interrupt, + ipa_irq_handler_t handler, + bool deferred_flag, + void *private_data); + +/** + * ipa_restore_suspend_handler() - restores the original suspend IRQ handler + * as it was registered in the IPA init sequence. + * Return codes: + * 0: success + * -EPERM: failed to remove current handler or failed to add original handler + */ +int ipa_restore_suspend_handler(void); + +/* + * Messaging + */ + +/** + * ipa_send_msg() - Send "message" from kernel client to IPA driver + * @metadata: [in] message metadata + * @buff: [in] the payload for message + * @callback: [in] free callback + * + * Client supplies the message metadata and payload which IPA driver buffers + * till read by user-space. After read from user space IPA driver invokes the + * callback supplied to free the message payload. Client must not touch/free + * the message payload after calling this API. + * + * Returns: 0 on success, negative on failure + * + * Note: Should not be called from atomic context + */ +int ipa_send_msg(struct ipa_msg_meta *metadata, void *buff, + ipa_msg_free_fn callback); + +/* + * Data path + */ + +/** + * ipa_tx_dp() - Data-path tx handler + * @dst: [in] which IPA destination to route tx packets to + * @skb: [in] the packet to send + * @metadata: [in] TX packet metadata + * + * Data-path tx handler, this is used for both SW data-path which by-passes most + * IPA HW blocks AND the regular HW data-path for WLAN AMPDU traffic only. If + * dst is a "valid" CONS type, then SW data-path is used. If dst is the + * WLAN_AMPDU PROD type, then HW data-path for WLAN AMPDU is used. Anything else + * is an error. For errors, client needs to free the skb as needed. For success, + * IPA driver will later invoke client callback if one was supplied. That + * callback should free the skb. If no callback supplied, IPA driver will free + * the skb internally + * + * The function will use two descriptors for this send command + * (for A5_WLAN_AMPDU_PROD only one desciprtor will be sent), + * the first descriptor will be used to inform the IPA hardware that + * apps need to push data into the IPA (IP_PACKET_INIT immediate command). + * Once this send was done from SPS point-of-view the IPA driver will + * get notified by the supplied callback - ipa_sps_irq_tx_comp() + * + * ipa_sps_irq_tx_comp will call to the user supplied + * callback (from ipa_connect) + * + * Returns: 0 on success, negative on failure + */ +int ipa_tx_dp(enum ipa_client_type dst, struct sk_buff *skb, + struct ipa_tx_meta *metadata); + +/* + * ipa_rmnet_ctl_xmit - QMAP Flow control TX + * + * @skb - tx QMAP control packet + * + * Note: This need to be called after client receive rmnet_ctl_ + * ready_cb and want to send TX flow control message. + * + * This funciton will return 0 on success, -EAGAIN if pipe if full. + */ +int ipa_rmnet_ctl_xmit(struct sk_buff *skb); + +/* + * ipa_rmnet_ll_xmit - Low lat data Tx + * + * @skb - tx low lat data packet + * + * Note: This need to be called after client receive rmnet_ll_ + * ready_cb and want to send TX ll data message. + * + * This funciton will return 0 on success, -EAGAIN if pipe if full. + */ +int ipa_rmnet_ll_xmit(struct sk_buff *skb); + +/* + * ipa_register_notifier - Register for IPA atomic notifier + * + * @fn_ptr - Function pointer to get the notification + * + * This funciton will return 0 on success, -EAGAIN if reg fails. + */ +int ipa_register_notifier(void *fn_ptr); + +/* + * ipa_unregister_notifier - Unregister for IPA atomic notifier + * + * @fn_ptr - Same function pointer used to get the notification + * + * This funciton will return 0 on success, -EAGAIN if reg fails. + */ +int ipa_unregister_notifier(void *fn_ptr); + +void ipa_free_skb(struct ipa_rx_data *data); + +/* + * System pipes + */ + +/** + * ipa_setup_sys_pipe() - Setup an IPA end-point in system-BAM mode and perform + * IPA EP configuration + * @sys_in: [in] input needed to setup BAM pipe and configure EP + * @clnt_hdl: [out] client handle + * + * - configure the end-point registers with the supplied + * parameters from the user. + * - call SPS APIs to create a system-to-bam connection with IPA. + * - allocate descriptor FIFO + * - register callback function(ipa_sps_irq_rx_notify or + * ipa_sps_irq_tx_notify - depends on client type) in case the driver is + * not configured to pulling mode + * + * Returns: 0 on success, negative on failure + */ +int ipa_setup_sys_pipe(struct ipa_sys_connect_params *sys_in, u32 *clnt_hdl); + +/** + * ipa_teardown_sys_pipe() - Teardown the system-BAM pipe and cleanup IPA EP + * @clnt_hdl: [in] the handle obtained from ipa_setup_sys_pipe + * + * Returns: 0 on success, negative on failure + */ +int ipa_teardown_sys_pipe(u32 clnt_hdl); + +int ipa_connect_wdi_pipe(struct ipa_wdi_in_params *in, + struct ipa_wdi_out_params *out); +int ipa_disconnect_wdi_pipe(u32 clnt_hdl); +int ipa_enable_wdi_pipe(u32 clnt_hdl); +int ipa_disable_wdi_pipe(u32 clnt_hdl); +int ipa_resume_wdi_pipe(u32 clnt_hdl); +int ipa_suspend_wdi_pipe(u32 clnt_hdl); +int ipa_reg_uc_rdyCB(struct ipa_wdi_uc_ready_params *param); +int ipa_dereg_uc_rdyCB(void); +int ipa_add_hdr(struct ipa_ioc_add_hdr *hdrs); +int ipa_del_hdr(struct ipa_ioc_del_hdr *hdls); +int ipa_get_hdr(struct ipa_ioc_get_hdr *lookup); +/** + * ipa_get_wdi_stats() - Query WDI statistics from uc + * @stats: [inout] stats blob from client populated by driver + * + * Returns: 0 on success, negative on failure + * + * @note Cannot be called from atomic context + * + */ +int ipa_get_wdi_stats(struct IpaHwStatsWDIInfoData_t *stats); +int ipa_uc_bw_monitor(struct ipa_wdi_bw_info *info); + +/** + * ipa_broadcast_wdi_quota_reach_ind() - quota reach + * @uint32_t fid: [in] input netdev ID + * @uint64_t num_bytes: [in] used bytes + * + * Returns: 0 on success, negative on failure + */ +int ipa_broadcast_wdi_quota_reach_ind(uint32_t fid, + uint64_t num_bytes); + +/* + * To retrieve doorbell physical address of + * wlan pipes + */ +int ipa_uc_wdi_get_dbpa(struct ipa_wdi_db_params *out); + +/* + * IPADMA + */ + /** + * ipa_dma_init() -Initialize IPADMA. + * + * This function initialize all IPADMA internal data and connect in dma: + * MEMCPY_DMA_SYNC_PROD ->MEMCPY_DMA_SYNC_CONS + * MEMCPY_DMA_ASYNC_PROD->MEMCPY_DMA_SYNC_CONS + * + * Return codes: 0: success + * -EFAULT: IPADMA is already initialized + * -ENOMEM: allocating memory error + * -EPERM: pipe connection failed + */ +int ipa_dma_init(void); + +/** + * ipa_dma_enable() -Vote for IPA clocks. + * + *Return codes: 0: success + * -EINVAL: IPADMA is not initialized + * -EPERM: Operation not permitted as ipa_dma is already + * enabled + */ +int ipa_dma_enable(void); + +/** + * ipa_dma_disable()- Unvote for IPA clocks. + * + * enter to power save mode. + * + * Return codes: 0: success + * -EINVAL: IPADMA is not initialized + * -EPERM: Operation not permitted as ipa_dma is already + * disabled + * -EFAULT: can not disable ipa_dma as there are pending + * memcopy works + */ +int ipa_dma_disable(void); + +/** + * ipa_dma_sync_memcpy()- Perform synchronous memcpy using IPA. + * + * @dest: physical address to store the copied data. + * @src: physical address of the source data to copy. + * @len: number of bytes to copy. + * + * Return codes: 0: success + * -EINVAL: invalid params + * -EPERM: operation not permitted as ipa_dma isn't enable or + * initialized + * -SPS_ERROR: on sps faliures + * -EFAULT: other + */ +int ipa_dma_sync_memcpy(u64 dest, u64 src, int len); + +/** + * ipa_dma_async_memcpy()- Perform asynchronous memcpy using IPA. + * + * @dest: physical address to store the copied data. + * @src: physical address of the source data to copy. + * @len: number of bytes to copy. + * @user_cb: callback function to notify the client when the copy was done. + * @user_param: cookie for user_cb. + * + * Return codes: 0: success + * -EINVAL: invalid params + * -EPERM: operation not permitted as ipa_dma isn't enable or + * initialized + * -SPS_ERROR: on sps faliures + * -EFAULT: descr fifo is full. + */ +int ipa_dma_async_memcpy(u64 dest, u64 src, int len, + void (*user_cb)(void *user1), void *user_param); + + +/** + * ipa_dma_destroy() -teardown IPADMA pipes and release ipadma. + * + * this is a blocking function, returns just after destroying IPADMA. + */ +void ipa_dma_destroy(void); + +/* + * Miscellaneous + */ +void ipa_bam_reg_dump(void); + +int ipa_get_ep_mapping(enum ipa_client_type client); + +bool ipa_is_ready(void); + +void ipa_proxy_clk_vote(void); +void ipa_proxy_clk_unvote(void); + +enum ipa_hw_type ipa_get_hw_type(void); + +const struct ipa_gsi_ep_config *ipa_get_gsi_ep_info( + enum ipa_client_type client); + +int ipa_stop_gsi_channel(u32 clnt_hdl); + +typedef void (*ipa_ready_cb)(void *user_data); + +typedef void (*ipa_rmnet_ctl_ready_cb)(void *user_data); + +typedef void (*ipa_rmnet_ctl_stop_cb)(void *user_data); + +typedef void (*ipa_rmnet_ctl_rx_notify_cb)(void *user_data, void *rx_data); + +typedef void (*ipa_rmnet_ll_ready_cb)(void *user_data); + +typedef void (*ipa_rmnet_ll_stop_cb)(void *user_data); + +typedef void (*ipa_rmnet_ll_rx_notify_cb)(void *user_data, void *rx_data); + +int ipa_get_default_aggr_time_limit(enum ipa_client_type client, + u32 *default_aggr_time_limit); + +/** + * ipa_register_ipa_ready_cb() - register a callback to be invoked + * when IPA core driver initialization is complete. + * + * @ipa_ready_cb: CB to be triggered. + * @user_data: Data to be sent to the originator of the CB. + * + * Note: This function is expected to be utilized when ipa_is_ready + * function returns false. + * An IPA client may also use this function directly rather than + * calling ipa_is_ready beforehand, as if this API returns -EEXIST, + * this means IPA initialization is complete (and no callback will + * be triggered). + * When the callback is triggered, the client MUST perform his + * operations in a different context. + * + * The function will return 0 on success, -ENOMEM on memory issues and + * -EEXIST if IPA initialization is complete already. + */ +int ipa_register_ipa_ready_cb(void (*ipa_ready_cb)(void *user_data), + void *user_data); + +/** + * ipa_register_rmnet_ctl_cb() - register callbacks to be invoked + * to rmnet_ctl for qmap flow control pipes setup/teardown/rx_notify. + * + * @ipa_rmnet_ctl_ready_cb: CB to be called when pipes setup. + * @user_data1: user_data for ipa_rmnet_ctl_ready_cb. + * @ipa_rmnet_ctl_stop_cb: CB to be called when pipes teardown. + * @user_data2: user_data for ipa_rmnet_ctl_stop_cb. + * @ipa_rmnet_ctl_rx_notify_cb: CB to be called when receive rx pkts. + * @user_data3: user_data for ipa_rmnet_ctl_rx_notify_cb. + * @rx_data: RX data buffer. + * + * Note: This function is expected to be utilized for rmnet_ctl + * module when new qmap flow control is enabled. + * + * The function will return 0 on success, -EAGAIN if IPA not ready, + * -ENXIO is feature is not enabled, -EEXIST if already called. + */ +int ipa_register_rmnet_ctl_cb( + void (*ipa_rmnet_ctl_ready_cb)(void *user_data1), + void *user_data1, + void (*ipa_rmnet_ctl_stop_cb)(void *user_data2), + void *user_data2, + void (*ipa_rmnet_ctl_rx_notify_cb)(void *user_data3, void *rx_data), + void *user_data3); + +/** + * ipa_unregister_rmnet_ctl_cb() - unregister callbacks to be + * invoked to rmnet_ctl for qmap flow control pipes + * setup/teardown/rx_notify. + * + * Note: This function is expected to be utilized for rmnet_ctl + * module when new qmap flow control is enabled. + * + * The function will return 0 on success, -EAGAIN if IPA not ready, + * -ENXIO is feature is not enabled. + */ +int ipa_unregister_rmnet_ctl_cb(void); + +/** + * ipa_register_rmnet_ll_cb() - register callbacks to be invoked + * to rmnet_ll for low latency data pipes setup/teardown/rx_notify. + * + * @ipa_rmnet_ll_ready_cb: CB to be called when pipes setup. + * @user_data1: user_data for ipa_rmnet_ctl_ready_cb. + * @ipa_rmnet_ll_stop_cb: CB to be called when pipes teardown. + * @user_data2: user_data for ipa_rmnet_ctl_stop_cb. + * @ipa_rmnet_ll_rx_notify_cb: CB to be called when receive rx pkts. + * @user_data3: user_data for ipa_rmnet_ctl_rx_notify_cb. + * @rx_data: RX data buffer. + * + * Note: This function is expected to be utilized for rmnet_ll + * module. + * + * The function will return 0 on success, -EAGAIN if IPA not ready, + * -ENXIO is feature is not enabled, -EEXIST if already called. + */ +int ipa_register_rmnet_ll_cb( + void (*ipa_rmnet_ll_ready_cb)(void *user_data1), + void *user_data1, + void (*ipa_rmnet_ll_stop_cb)(void *user_data2), + void *user_data2, + void (*ipa_rmnet_ll_rx_notify_cb)(void *user_data3, void *rx_data), + void *user_data3); + +/** + * ipa_unregister_rmnet_ll_cb() - unregister callbacks to be + * invoked to rmnet_ll for low lat data pipes + * setup/teardown/rx_notify. + * + * Note: This function is expected to be utilized for rmnet_ll + * module. + * + * The function will return 0 on success, -EAGAIN if IPA not ready, + * -ENXIO is feature is not enabled. + */ +int ipa_unregister_rmnet_ll_cb(void); + +int ipa_get_smmu_params(struct ipa_smmu_in_params *in, + struct ipa_smmu_out_params *out); +/** + * ipa_is_vlan_mode - check if a LAN driver should load in VLAN mode + * @iface - type of vlan capable device + * @res - query result: true for vlan mode, false for non vlan mode + * + * API must be called after ipa_is_ready() returns true, otherwise it will fail + * + * Returns: 0 on success, negative on failure + */ +int ipa_is_vlan_mode(enum ipa_vlan_ifaces iface, bool *res); + +/** + * ipa_get_lan_rx_napi - returns true if NAPI is enabled in the LAN RX dp + */ +bool ipa_get_lan_rx_napi(void); +/* + * ipa_add_socksv5_conn - add socksv5 info to ipa driver + */ +int ipa_add_socksv5_conn(struct ipa_socksv5_info *info); + +/* + * ipa_del_socksv5_conn - del socksv5 info to ipa driver + */ +int ipa_del_socksv5_conn(uint32_t handle); + +int ipa_mhi_handle_ipa_config_req(struct ipa_config_req_msg_v01 *config_req); +int ipa_wigig_save_regs(void); + +#else /* IS_ENABLED(CONFIG_IPA3) */ + +/* + * Configuration + */ +static inline int ipa_cfg_ep_ctrl(u32 clnt_hdl, + const struct ipa_ep_cfg_ctrl *ep_ctrl) +{ + return -EPERM; +} + +/* + * Routing + */ +static inline int ipa_add_rt_rule(struct ipa_ioc_add_rt_rule *rules) +{ + return -EPERM; +} + +static inline int ipa_put_rt_tbl(u32 rt_tbl_hdl) +{ + return -EPERM; +} + +/* + * Interface + */ +static inline int ipa_register_intf(const char *name, + const struct ipa_tx_intf *tx, + const struct ipa_rx_intf *rx) +{ + return -EPERM; +} + +/* + * Aggregation + */ +static inline int ipa_set_aggr_mode(enum ipa_aggr_mode mode) +{ + return -EPERM; +} + +static inline int ipa_set_qcncm_ndp_sig(char sig[3]) +{ + return -EPERM; +} + +static inline int ipa_set_single_ndp_per_mbim(bool enable) +{ + return -EPERM; +} + +/* + * interrupts + */ +static inline int ipa_add_interrupt_handler(enum ipa_irq_type interrupt, + ipa_irq_handler_t handler, + bool deferred_flag, + void *private_data) +{ + return -EPERM; +} + +static inline int ipa_restore_suspend_handler(void) +{ + return -EPERM; +} + +/* + * Messaging + */ +static inline int ipa_send_msg(struct ipa_msg_meta *metadata, void *buff, + ipa_msg_free_fn callback) +{ + return -EPERM; +} + +/* + * Data path + */ +static inline int ipa_tx_dp(enum ipa_client_type dst, struct sk_buff *skb, + struct ipa_tx_meta *metadata) +{ + return -EPERM; +} + +/* + * QMAP Flow control TX + */ +static inline int ipa_rmnet_ctl_xmit(struct sk_buff *skb) +{ + return -EPERM; +} + +/* + * Low Latency data Tx + */ +static inline int ipa_rmnet_ll_xmit(struct sk_buff *skb) +{ + return -EPERM; +} + +/* + * Yellow water mark notifier register + */ +static inline int ipa_register_notifier(void *fn_ptr) +{ + return -EPERM; +} + +/* + * Yellow water mark notifier unregister + */ +static inline int ipa_unregister_notifier(void *fn_ptr) +{ + return -EPERM; +} + +static inline void ipa_free_skb(struct ipa_rx_data *rx_in) +{ +} + +/* + * System pipes + */ + +static inline int ipa_setup_sys_pipe(struct ipa_sys_connect_params *sys_in, + u32 *clnt_hdl) +{ + return -EPERM; +} + +static inline int ipa_teardown_sys_pipe(u32 clnt_hdl) +{ + return -EPERM; +} + +static inline int ipa_connect_wdi_pipe(struct ipa_wdi_in_params *in, + struct ipa_wdi_out_params *out) +{ + return -EPERM; +} + +static inline int ipa_disconnect_wdi_pipe(u32 clnt_hdl) +{ + return -EPERM; +} + +static inline int ipa_enable_wdi_pipe(u32 clnt_hdl) +{ + return -EPERM; +} + +static inline int ipa_disable_wdi_pipe(u32 clnt_hdl) +{ + return -EPERM; +} + +static inline int ipa_resume_wdi_pipe(u32 clnt_hdl) +{ + return -EPERM; +} + +static inline int ipa_suspend_wdi_pipe(u32 clnt_hdl) +{ + return -EPERM; +} + +static inline int ipa_broadcast_wdi_quota_reach_ind(uint32_t fid, + uint64_t num_bytes) +{ + return -EPERM; +} + +static inline int ipa_uc_wdi_get_dbpa( + struct ipa_wdi_db_params *out) +{ + return -EPERM; +} + +/* + * IPADMA + */ +static inline int ipa_dma_init(void) +{ + return -EPERM; +} + +static inline int ipa_dma_enable(void) +{ + return -EPERM; +} + +static inline int ipa_dma_disable(void) +{ + return -EPERM; +} + +static inline int ipa_dma_sync_memcpy(phys_addr_t dest, phys_addr_t src + , int len) +{ + return -EPERM; +} + +static inline int ipa_dma_async_memcpy(phys_addr_t dest, phys_addr_t src + , int len, void (*user_cb)(void *user1), + void *user_param) +{ + return -EPERM; +} + +static inline void ipa_dma_destroy(void) +{ +} + +/* + * Miscellaneous + */ + +static inline int ipa_get_wdi_stats(struct IpaHwStatsWDIInfoData_t *stats) +{ + return -EPERM; +} + +static inline int ipa_uc_bw_monitor(struct ipa_wdi_bw_info *info) +{ + return -EPERM; +} + +static inline int ipa_get_ep_mapping(enum ipa_client_type client) +{ + return -EPERM; +} + +static inline bool ipa_is_ready(void) +{ + return false; +} + +static inline enum ipa_hw_type ipa_get_hw_type(void) +{ + return IPA_HW_None; +} + +static inline int ipa_register_ipa_ready_cb( + void (*ipa_ready_cb)(void *user_data), + void *user_data) +{ + return -EPERM; +} + +static inline int ipa_is_vlan_mode(enum ipa_vlan_ifaces iface, bool *res) +{ + return -EPERM; +} + +static inline bool ipa_get_lan_rx_napi(void) +{ + return false; +} + +static inline int ipa_add_socksv5_conn(struct ipa_socksv5_info *info) +{ + return -EPERM; +} + +static inline int ipa_del_socksv5_conn(uint32_t handle) +{ + return -EPERM; +} + +static inline const struct ipa_gsi_ep_config *ipa_get_gsi_ep_info( + enum ipa_client_type client) +{ + return NULL; +} + +static inline int ipa_stop_gsi_channel(u32 clnt_hdl) +{ + return -EPERM; +} + +static inline int ipa_register_rmnet_ctl_cb( + void (*ipa_rmnet_ctl_ready_cb)(void *user_data1), + void *user_data1, + void (*ipa_rmnet_ctl_stop_cb)(void *user_data2), + void *user_data2, + void (*ipa_rmnet_ctl_rx_notify_cb)(void *user_data3, void *rx_data), + void *user_data3) +{ + return -EPERM; +} + +static inline int ipa_unregister_rmnet_ctl_cb(void) +{ + return -EPERM; +} + +static inline int ipa3_uc_reg_rdyCB( + struct ipa_wdi_uc_ready_params *inout) +{ + return -EPERM; +} + +static inline int ipa_register_rmnet_ll_cb( + void (*ipa_rmnet_ll_ready_cb)(void *user_data1), + void *user_data1, + void (*ipa_rmnet_ll_stop_cb)(void *user_data2), + void *user_data2, + void (*ipa_rmnet_ll_rx_notify_cb)(void *user_data3, void *rx_data), + void *user_data3) +{ + return -EPERM; +} + +static inline int ipa_get_default_aggr_time_limit(enum ipa_client_type client, + u32 *default_aggr_time_limit) +{ + return -EPERM; +} + +static inline int ipa_unregister_rmnet_ll_cb(void) +{ + return -EPERM; +} + +#endif /* IS_ENABLED(CONFIG_IPA3) */ + +/* stubs - to be removed once dependent drivers remove references */ +static inline int ipa_reset_endpoint(u32 clnt_hdl) +{ + return -EPERM; +} + +static inline int ipa_clear_endpoint_delay(u32 clnt_hdl) +{ + return -EPERM; +} + +static inline int ipa_commit_hdr(void) +{ + return -EPERM; +} + +static inline int ipa_put_hdr(u32 hdr_hdl) +{ + return -EPERM; +} + +static inline int ipa_deregister_pull_msg(struct ipa_msg_meta *metadata) +{ + return -EPERM; +} + +/* + * Miscellaneous + */ +static inline int ipa_rm_delete_resource( + enum ipa_rm_resource_name resource_name) +{ + return -EPERM; +} + +static inline int ipa_rm_deregister( + enum ipa_rm_resource_name resource_name, + struct ipa_rm_register_params *reg_params) +{ + return -EPERM; +} + +static inline int ipa_rm_set_perf_profile( + enum ipa_rm_resource_name resource_name, + struct ipa_rm_perf_profile *profile) +{ + return -EPERM; +} + +static inline int ipa_rm_add_dependency( + enum ipa_rm_resource_name resource_name, + enum ipa_rm_resource_name depends_on_name) +{ + return -EPERM; +} + +static inline int ipa_rm_add_dependency_sync( + enum ipa_rm_resource_name resource_name, + enum ipa_rm_resource_name depends_on_name) +{ + return -EPERM; +} + +static inline int ipa_rm_delete_dependency( + enum ipa_rm_resource_name resource_name, + enum ipa_rm_resource_name depends_on_name) +{ + return -EPERM; +} + +static inline int ipa_rm_request_resource( + enum ipa_rm_resource_name resource_name) +{ + return -EPERM; +} + +static inline int ipa_rm_inactivity_timer_init( + enum ipa_rm_resource_name resource_name, + unsigned long msecs) +{ + return -EPERM; +} + +static inline int ipa_rm_release_resource( + enum ipa_rm_resource_name resource_name) +{ + return -EPERM; +} + +static inline int ipa_rm_notify_completion(enum ipa_rm_event event, + enum ipa_rm_resource_name resource_name) +{ + return -EPERM; +} + +static inline int ipa_rm_inactivity_timer_destroy( + enum ipa_rm_resource_name resource_name) +{ + return -EPERM; +} + +static inline int ipa_rm_inactivity_timer_request_resource( + enum ipa_rm_resource_name resource_name) +{ + return -EPERM; +} + +static inline int ipa_rm_inactivity_timer_release_resource( + enum ipa_rm_resource_name resource_name) +{ + return -EPERM; +} + +static inline enum ipa_rm_resource_name ipa_get_rm_resource_from_ep( + int pipe_idx) +{ + return -EPERM; +} + +static inline bool ipa_is_client_handle_valid(u32 clnt_hdl) +{ + return false; +} + +static inline enum ipa_client_type ipa_get_client_mapping(int pipe_idx) +{ + return -EPERM; +} + +static inline bool ipa_get_modem_cfg_emb_pipe_flt(void) +{ + return false; +} + +static inline enum ipa_transport_type ipa_get_transport_type(void) +{ + return IPA_TRANSPORT_TYPE_GSI; +} + +static inline struct device *ipa_get_dma_dev(void) +{ + return NULL; +} + +static inline struct iommu_domain *ipa_get_smmu_domain(void) +{ + return NULL; +} + +static inline int ipa_disable_apps_wan_cons_deaggr( + uint32_t agg_size, uint32_t agg_count) +{ + return -EPERM; +} + +#endif /* _IPA_H_ */ diff --git a/include/linux/ipa_eth.h b/include/linux/ipa_eth.h new file mode 100644 index 000000000000..ad42975bb0df --- /dev/null +++ b/include/linux/ipa_eth.h @@ -0,0 +1,278 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2019-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef _IPA_ETH_H_ +#define _IPA_ETH_H_ + +#include "ipa.h" +#include +#include + +/* New architecture prototypes */ + +typedef void (*ipa_eth_ready_cb)(void *user_data); +typedef u32 ipa_eth_hdl_t; + +/** + * struct ipa_eth_ready_cb - eth readiness parameters + * + * @notify: ipa_eth client ready callback notifier + * @userdata: userdata for ipa_eth ready cb + * @is_eth_ready: true if ipa_eth client is already ready + */ +struct ipa_eth_ready { + ipa_eth_ready_cb notify; + void *userdata; + + /* out params */ + bool is_eth_ready; +}; + +/** + * enum ipa_eth_client_type - names for the various IPA + * eth "clients". + */ +enum ipa_eth_client_type { + IPA_ETH_CLIENT_AQC107, + IPA_ETH_CLIENT_AQC113, + IPA_ETH_CLIENT_RTK8111K, + IPA_ETH_CLIENT_RTK8125B, + IPA_ETH_CLIENT_NTN, + IPA_ETH_CLIENT_EMAC, + IPA_ETH_CLIENT_MAX, +}; + +/** + * enum ipa_eth_pipe_traffic_type - traffic type for the various IPA + * eth "pipes". + */ +enum ipa_eth_pipe_traffic_type { + IPA_ETH_PIPE_BEST_EFFORT, + IPA_ETH_PIPE_LOW_LATENCY, + IPA_ETH_PIPE_TRAFFIC_TYPE_MAX, +}; + +/** + * enum ipa_eth_pipe_direction - pipe direcitons for same + * ethernet client. + */ +enum ipa_eth_pipe_direction { + IPA_ETH_PIPE_DIR_TX, + IPA_ETH_PIPE_DIR_RX, + IPA_ETH_PIPE_DIR_MAX, +}; + +#define IPA_ETH_INST_ID_MAX (2) + +/** + * struct ipa_eth_ntn_setup_info - parameters for ntn ethernet + * offloading + * + * @bar_addr: bar PA to access NTN register + * @tail_ptr_offs: tail ptr offset + * @ioc_mod_threshold: Descriptors # per interrupt request from + * NTN3 HW via descriptor bit as part of the protocol. + */ +struct ipa_eth_ntn_setup_info { + phys_addr_t bar_addr; + phys_addr_t tail_ptr_offs; + uint16_t ioc_mod_threshold; +}; + +/** + * struct ipa_eth_aqc_setup_info - parameters for aqc ethernet + * offloading + * + * @bar_addr: bar PA to access AQC register + * @head_ptr_offs: head ptr offset + * @aqc_ch: AQC ch number + * @dest_tail_ptr_offs: tail ptr offset + */ +struct ipa_eth_aqc_setup_info { + phys_addr_t bar_addr; + phys_addr_t head_ptr_offs; + u8 aqc_ch; + phys_addr_t dest_tail_ptr_offs; +}; + + +/** + * struct ipa_eth_realtek_setup_info - parameters for realtek ethernet + * offloading + * + * @bar_addr: bar PA to access RTK register + * @bar_size: bar region size + * @queue_number: Which RTK queue to check the status on + * @dest_tail_ptr_offs: tail ptr offset + */ +struct ipa_eth_realtek_setup_info { + phys_addr_t bar_addr; + u32 bar_size; + u8 queue_number; + phys_addr_t dest_tail_ptr_offs; +}; + +/** + * struct ipa_eth_buff_smmu_map - IPA iova->pa SMMU mapping + * @iova: virtual address of the data buffer + * @pa: physical address of the data buffer + */ +struct ipa_eth_buff_smmu_map { + dma_addr_t iova; + phys_addr_t pa; +}; + +/** + * struct ipa_eth_pipe_setup_info - info needed for IPA setups + * @is_transfer_ring_valid: if transfer ring is needed + * @transfer_ring_base: the base of the transfer ring + * @transfer_ring_sgt: sgtable of transfer ring + * @transfer_ring_size: size of the transfer ring + * @is_buffer_pool_valid: if buffer pool is needed + * @buffer_pool_base_addr: base of buffer pool address + * @buffer_pool_base_sgt: sgtable of buffer pool + * @data_buff_list_size: number of buffers + * @data_buff_list: array of data buffer list + * @fix_buffer_size: buffer size + * @notify: callback for exception/embedded packets + * @priv: priv for exception callback + * @client_info: vendor specific pipe setup info + * @db_pa: doorbell physical address + * @db_val: doorbell value ethernet HW need to ring + */ +struct ipa_eth_pipe_setup_info { + /* transfer ring info */ + bool is_transfer_ring_valid; + dma_addr_t transfer_ring_base; + struct sg_table *transfer_ring_sgt; + u32 transfer_ring_size; + + /* buffer pool info */ + bool is_buffer_pool_valid; + dma_addr_t buffer_pool_base_addr; + struct sg_table *buffer_pool_base_sgt; + + /* buffer info */ + u32 data_buff_list_size; + struct ipa_eth_buff_smmu_map *data_buff_list; + u32 fix_buffer_size; + + /* client notify cb */ + ipa_notify_cb notify; + void *priv; + + /* vendor specific info */ + union { + struct ipa_eth_aqc_setup_info aqc; + struct ipa_eth_realtek_setup_info rtk; + struct ipa_eth_ntn_setup_info ntn; + } client_info; + + /* output params */ + phys_addr_t db_pa; + u32 db_val; +}; + +/** + * struct ipa_eth_client_pipe_info - ETH pipe/gsi related configuration + * @link: link of ep for different client function on same ethernet HW + * @dir: TX or RX direction + * @info: tx/rx pipe setup info + * @client_info: client the pipe belongs to + * @pipe_hdl: output params, pipe handle + */ +struct ipa_eth_client_pipe_info { + struct list_head link; + enum ipa_eth_pipe_direction dir; + struct ipa_eth_pipe_setup_info info; + struct ipa_eth_client *client_info; + + /* output params */ + ipa_eth_hdl_t pipe_hdl; +}; + +/** + * struct ipa_eth_client - client info per traffic type + * provided by offload client + * @client_type: ethernet client type + * @inst_id: instance id for dual NIC support + * @traffic_type: traffic type + * @pipe_list: list of pipes with same traffic type + * @priv: private data for client + * @test: is test client + */ +struct ipa_eth_client { + /* vendor driver */ + enum ipa_eth_client_type client_type; + u8 inst_id; + + /* traffic type */ + enum ipa_eth_pipe_traffic_type traffic_type; + struct list_head pipe_list; + + /* client specific priv data*/ + void *priv; + bool test; +}; + +/** + * struct ipa_eth_perf_profile - To set BandWidth profile + * + * @max_supported_bw_mbps: maximum bandwidth needed (in Mbps) + */ +struct ipa_eth_perf_profile { + u32 max_supported_bw_mbps; +}; + +/** + * struct ipa_eth_hdr_info - Header to install on IPA HW + * + * @hdr: header to install on IPA HW + * @hdr_len: length of header + * @dst_mac_addr_offset: destination mac address offset + * @hdr_type: layer two header type + */ +struct ipa_eth_hdr_info { + u8 *hdr; + u8 hdr_len; + u8 dst_mac_addr_offset; + enum ipa_hdr_l2_type hdr_type; +}; + +/** + * struct ipa_eth_intf_info - parameters for ipa offload + * interface registration + * + * @netdev_name: network interface name + * @hdr: hdr for ipv4/ipv6 + * @pipe_hdl_list_size: number of pipes prop needed for this interface + * @pipe_hdl_list: array of pipes used for this interface + */ +struct ipa_eth_intf_info { + const char *netdev_name; + struct ipa_eth_hdr_info hdr[IPA_IP_MAX]; + + /* tx/rx pipes for same netdev */ + int pipe_hdl_list_size; + ipa_eth_hdl_t *pipe_hdl_list; +}; + +int ipa_eth_register_ready_cb(struct ipa_eth_ready *ready_info); +int ipa_eth_unregister_ready_cb(struct ipa_eth_ready *ready_info); +int ipa_eth_client_conn_pipes(struct ipa_eth_client *client); +int ipa_eth_client_disconn_pipes(struct ipa_eth_client *client); +int ipa_eth_client_reg_intf(struct ipa_eth_intf_info *intf); +int ipa_eth_client_unreg_intf(struct ipa_eth_intf_info *intf); +int ipa_eth_client_set_perf_profile(struct ipa_eth_client *client, + struct ipa_eth_perf_profile *profile); +int ipa_eth_client_conn_evt(struct ipa_ecm_msg *msg); +int ipa_eth_client_disconn_evt(struct ipa_ecm_msg *msg); +enum ipa_client_type ipa_eth_get_ipa_client_type_from_eth_type( + enum ipa_eth_client_type eth_client_type, enum ipa_eth_pipe_direction dir); +bool ipa_eth_client_exist( + enum ipa_eth_client_type eth_client_type, int inst_id); + +#endif // _IPA_ETH_H_ diff --git a/include/linux/ipa_mhi.h b/include/linux/ipa_mhi.h new file mode 100644 index 000000000000..dbb1cace27f2 --- /dev/null +++ b/include/linux/ipa_mhi.h @@ -0,0 +1,170 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved. + */ + +#ifndef IPA_MHI_H_ +#define IPA_MHI_H_ + +#include +#include + +/** + * enum ipa_mhi_event_type - event type for mhi callback + * + * @IPA_MHI_EVENT_READY: IPA MHI is ready and IPA uC is loaded. After getting + * this event MHI client is expected to call to ipa_mhi_start() API + * @IPA_MHI_EVENT_DATA_AVAILABLE: downlink data available on MHI channel + */ +enum ipa_mhi_event_type { + IPA_MHI_EVENT_READY, + IPA_MHI_EVENT_DATA_AVAILABLE, + IPA_MHI_EVENT_MAX, +}; + +enum ipa_mhi_mstate { + IPA_MHI_STATE_M0, + IPA_MHI_STATE_M1, + IPA_MHI_STATE_M2, + IPA_MHI_STATE_M3, + IPA_MHI_STATE_M_MAX +}; + +typedef void (*mhi_client_cb)(void *priv, enum ipa_mhi_event_type event, + unsigned long data); + +/** + * struct ipa_mhi_msi_info - parameters for MSI (Message Signaled Interrupts) + * @addr_low: MSI lower base physical address + * @addr_hi: MSI higher base physical address + * @data: Data Pattern to use when generating the MSI + * @mask: Mask indicating number of messages assigned by the host to device + * + * msi value is written according to this formula: + * ((data & ~mask) | (mmio.msiVec & mask)) + */ +struct ipa_mhi_msi_info { + u32 addr_low; + u32 addr_hi; + u32 data; + u32 mask; +}; + +/** + * struct ipa_mhi_init_params - parameters for IPA MHI initialization API + * + * @msi: MSI (Message Signaled Interrupts) parameters + * @mmio_addr: MHI MMIO physical address + * @first_ch_idx: First channel ID for hardware accelerated channels. + * @first_er_idx: First event ring ID for hardware accelerated channels. + * @assert_bit40: should assert bit 40 in order to access host space. + * if PCIe iATU is configured then not need to assert bit40 + * @notify: client callback + * @priv: client private data to be provided in client callback + * @test_mode: flag to indicate if IPA MHI is in unit test mode + */ +struct ipa_mhi_init_params { + struct ipa_mhi_msi_info msi; + u32 mmio_addr; + u32 first_ch_idx; + u32 first_er_idx; + bool assert_bit40; + mhi_client_cb notify; + void *priv; + bool test_mode; +}; + +/** + * struct ipa_mhi_start_params - parameters for IPA MHI start API + * + * @host_ctrl_addr: Base address of MHI control data structures + * @host_data_addr: Base address of MHI data buffers + * @channel_context_addr: channel context array address in host address space + * @event_context_addr: event context array address in host address space + */ +struct ipa_mhi_start_params { + u32 host_ctrl_addr; + u32 host_data_addr; + u64 channel_context_array_addr; + u64 event_context_array_addr; +}; + +/** + * struct ipa_mhi_connect_params - parameters for IPA MHI channel connect API + * + * @sys: IPA EP configuration info + * @channel_id: MHI channel id + */ +struct ipa_mhi_connect_params { + struct ipa_sys_connect_params sys; + u8 channel_id; +}; + +/* bit #40 in address should be asserted for MHI transfers over pcie */ +#define IPA_MHI_HOST_ADDR(addr) ((addr) | BIT_ULL(40)) + +#if IS_ENABLED(CONFIG_IPA3) + +int ipa_mhi_init(struct ipa_mhi_init_params *params); + +int ipa_mhi_start(struct ipa_mhi_start_params *params); + +int ipa_mhi_connect_pipe(struct ipa_mhi_connect_params *in, u32 *clnt_hdl); + +int ipa_mhi_disconnect_pipe(u32 clnt_hdl); + +int ipa_mhi_suspend(bool force); + +int ipa_mhi_resume(void); + +void ipa_mhi_destroy(void); + +int ipa_mhi_update_mstate(enum ipa_mhi_mstate mstate_info); + +#else /* IS_ENABLED(CONFIG_IPA3) */ + +static inline int ipa_mhi_init(struct ipa_mhi_init_params *params) +{ + return -EPERM; +} + +static inline int ipa_mhi_start(struct ipa_mhi_start_params *params) +{ + return -EPERM; +} + +static inline int ipa_mhi_connect_pipe(struct ipa_mhi_connect_params *in, + u32 *clnt_hdl) +{ + return -EPERM; +} + +static inline int ipa_mhi_disconnect_pipe(u32 clnt_hdl) +{ + return -EPERM; +} + +static inline int ipa_mhi_suspend(bool force) +{ + return -EPERM; +} + +static inline int ipa_mhi_resume(void) +{ + return -EPERM; +} + +static inline void ipa_mhi_destroy(void) +{ + +} + +static inline int ipa_mhi_update_mstate + (enum ipa_mhi_mstate mstate_info) +{ + return -EPERM; +} + +#endif /* IS_ENABLED(CONFIG_IPA3) */ + +#endif /* IPA_MHI_H_ */ diff --git a/include/linux/ipa_odu_bridge.h b/include/linux/ipa_odu_bridge.h new file mode 100644 index 000000000000..08658ecee438 --- /dev/null +++ b/include/linux/ipa_odu_bridge.h @@ -0,0 +1,141 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef _IPA_ODO_BRIDGE_H_ +#define _IPA_ODO_BRIDGE_H_ + +#include "ipa.h" + +/** + * struct odu_bridge_params - parameters for odu bridge initialization API + * + * @netdev_name: network interface name + * @priv: private data that will be supplied to client's callback + * @tx_dp_notify: callback for handling SKB. the following event are supported: + * IPA_WRITE_DONE: will be called after client called to odu_bridge_tx_dp() + * Client is expected to free the skb. + * IPA_RECEIVE: will be called for delivering skb to APPS. + * Client is expected to deliver the skb to network stack. + * @send_dl_skb: callback for sending skb on downlink direction to adapter. + * Client is expected to free the skb. + * @device_ethaddr: device Ethernet address in network order. + * @ipa_desc_size: IPA Sys Pipe Desc Size + */ +struct odu_bridge_params { + const char *netdev_name; + void *priv; + ipa_notify_cb tx_dp_notify; + int (*send_dl_skb)(void *priv, struct sk_buff *skb); + u8 device_ethaddr[ETH_ALEN]; + u32 ipa_desc_size; +}; + +/** + * struct ipa_bridge_init_params - parameters for IPA bridge initialization API + * + * @info: structure contains initialization information + * @wakeup_request: callback to client to indicate there is downlink data + * available. Client is expected to call ipa_bridge_resume() to start + * receiving data + */ +struct ipa_bridge_init_params { + struct odu_bridge_params info; + void (*wakeup_request)(void *cl_priv); +}; + +#if IS_ENABLED(CONFIG_IPA3) + +int ipa_bridge_init(struct ipa_bridge_init_params *params, u32 *hdl); + +int ipa_bridge_connect(u32 hdl); + +int ipa_bridge_set_perf_profile(u32 hdl, u32 bandwidth); + +int ipa_bridge_disconnect(u32 hdl); + +int ipa_bridge_suspend(u32 hdl); + +int ipa_bridge_resume(u32 hdl); + +int ipa_bridge_tx_dp(u32 hdl, struct sk_buff *skb, + struct ipa_tx_meta *metadata); + +int ipa_bridge_cleanup(u32 hdl); + +#else /* IS_ENABLED(CONFIG_IPA3) */ + +static inline int ipa_bridge_init(struct odu_bridge_params *params, u32 *hdl) +{ + return -EPERM; +} + +static inline int ipa_bridge_connect(u32 hdl) +{ + return -EPERM; +} + +static inline int ipa_bridge_set_perf_profile(u32 hdl, u32 bandwidth) +{ + return -EPERM; +} + +static inline int ipa_bridge_disconnect(u32 hdl) +{ + return -EPERM; +} + +static inline int ipa_bridge_suspend(u32 hdl) +{ + return -EPERM; +} + +static inline int ipa_bridge_resume(u32 hdl) +{ + return -EPERM; +} + +static inline int ipa_bridge_tx_dp(u32 hdl, struct sk_buff *skb, +struct ipa_tx_meta *metadata) +{ + return -EPERM; +} + +static inline int ipa_bridge_cleanup(u32 hdl) +{ + return -EPERM; +} + +#endif /* IS_ENABLED(CONFIG_IPA3) */ + +/* Below API is deprecated. Please use the API above */ + +static inline int odu_bridge_init(struct odu_bridge_params *params) +{ + return -EPERM; +} + +static inline int odu_bridge_disconnect(void) +{ + return -EPERM; +} + +static inline int odu_bridge_connect(void) +{ + return -EPERM; +} + +static inline int odu_bridge_tx_dp(struct sk_buff *skb, + struct ipa_tx_meta *metadata) +{ + return -EPERM; +} + +static inline int odu_bridge_cleanup(void) +{ + return -EPERM; +} + +#endif /* _IPA_ODO_BRIDGE_H */ diff --git a/include/linux/ipa_qdss.h b/include/linux/ipa_qdss.h new file mode 100644 index 000000000000..8a5204d00a2c --- /dev/null +++ b/include/linux/ipa_qdss.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef _IPA_QDSS_H_ +#define _IPA_QDSS_H_ + +#include "ipa.h" + +/** + * enum ipa_qdss_notify - these are the only return items + * @IPA_QDSS_SUCCESS: will be returned as it is for both conn + * and disconn + * @IPA_QDSS_PIPE_CONN_FAILURE: will be returned as negative value + * @IPA_QDSS_PIPE_DISCONN_FAILURE: will be returned as negative value + */ +enum ipa_qdss_notify { + IPA_QDSS_SUCCESS, + IPA_QDSS_PIPE_CONN_FAILURE, + IPA_QDSS_PIPE_DISCONN_FAILURE, +}; + +/** + * struct ipa_qdss_conn_in_params - QDSS -> IPA TX configuration + * @data_fifo_base_addr: Base address of the data FIFO used by BAM + * @data_fifo_size: Size of the data FIFO + * @desc_fifo_base_addr: Base address of the descriptor FIFO by BAM + * @desc_fifo_size: Should be configured to 1 by QDSS + * @bam_p_evt_dest_addr: equivalent to event_ring_doorbell_pa + * physical address of the doorbell that IPA uC + * will update the headpointer of the event ring. + * QDSS should send BAM_P_EVNT_REG address in this var + * Configured with the GSI Doorbell Address. + * GSI sends Update RP by doing a write to this address + * @bam_p_evt_threshold: Threshold level of how many bytes consumed + * @override_eot: if override EOT==1, it doesn't check the EOT bit in + * the descriptor + */ +struct ipa_qdss_conn_in_params { + phys_addr_t data_fifo_base_addr; + u32 data_fifo_size; + phys_addr_t desc_fifo_base_addr; + u32 desc_fifo_size; + phys_addr_t bam_p_evt_dest_addr; + u32 bam_p_evt_threshold; + u32 override_eot; +}; + +/** + * struct ipa_qdss_conn_out_params - information provided + * to QDSS driver + * @rx_db_pa: physical address of IPA doorbell for RX (QDSS->IPA transactions) + * QDSS to take this address and assign it to BAM_P_EVENT_DEST_ADDR + */ +struct ipa_qdss_conn_out_params { + phys_addr_t ipa_rx_db_pa; +}; + +#if IS_ENABLED(CONFIG_IPA3) + +/** + * ipa_qdss_conn_pipes - Client should call this + * function to connect QDSS -> IPA pipe + * + * @in: [in] input parameters from client + * @out: [out] output params to client + * + * Note: Should not be called from atomic context + * + * @Return 0 on success, negative on failure + */ +int ipa_qdss_conn_pipes(struct ipa_qdss_conn_in_params *in, + struct ipa_qdss_conn_out_params *out); + +/** + * ipa_qdss_disconn_pipes() - Client should call this + * function to disconnect pipes + * + * Note: Should not be called from atomic context + * + * Returns: 0 on success, negative on failure + */ +int ipa_qdss_disconn_pipes(void); + +#else /* CONFIG_IPA3 */ + +static inline int ipa_qdss_conn_pipes(struct ipa_qdss_conn_in_params *in, + struct ipa_qdss_conn_out_params *out) +{ + return -IPA_QDSS_PIPE_CONN_FAILURE; +} + +static inline int ipa_qdss_disconn_pipes(void) +{ + return -IPA_QDSS_PIPE_DISCONN_FAILURE; +} + +#endif /* CONFIG_IPA3 */ +#endif /* _IPA_QDSS_H_ */ diff --git a/include/linux/ipa_uc_offload.h b/include/linux/ipa_uc_offload.h new file mode 100644 index 000000000000..e43b903ac1c8 --- /dev/null +++ b/include/linux/ipa_uc_offload.h @@ -0,0 +1,326 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef _IPA_UC_OFFLOAD_H_ +#define _IPA_UC_OFFLOAD_H_ + +#include "ipa.h" + +/** + * enum ipa_uc_offload_proto + * Protocol type: either WDI or Neutrino + * + * @IPA_UC_WDI: wdi Protocol + * @IPA_UC_NTN: Neutrino Protocol + */ +enum ipa_uc_offload_proto { + IPA_UC_INVALID = 0, + IPA_UC_WDI = 1, + IPA_UC_NTN = 2, + IPA_UC_NTN_V2X = 3, + IPA_UC_MAX_PROT_SIZE +}; + +/** + * struct ipa_hdr_info - Header to install on IPA HW + * + * @hdr: header to install on IPA HW + * @hdr_len: length of header + * @dst_mac_addr_offset: destination mac address offset + * @hdr_type: layer two header type + */ +struct ipa_hdr_info { + u8 *hdr; + u8 hdr_len; + u8 dst_mac_addr_offset; + enum ipa_hdr_l2_type hdr_type; +}; + +/** + * struct ipa_uc_offload_intf_params - parameters for uC offload + * interface registration + * + * @netdev_name: network interface name + * @notify: callback for exception/embedded packets + * @priv: callback cookie + * @hdr_info: header information + * @meta_data: metadata if any + * @meta_data_mask: metadata mask + * @proto: uC offload protocol type + * @alt_dst_pipe: alternate routing output pipe + */ +struct ipa_uc_offload_intf_params { + const char *netdev_name; + ipa_notify_cb notify; + void *priv; + struct ipa_hdr_info hdr_info[IPA_IP_MAX]; + u8 is_meta_data_valid; + u32 meta_data; + u32 meta_data_mask; + enum ipa_uc_offload_proto proto; + enum ipa_client_type alt_dst_pipe; +}; + +/** + * struct ntn_buff_smmu_map - IPA iova->pa SMMU mapping + * @iova: virtual address of the data buffer + * @pa: physical address of the data buffer + */ +struct ntn_buff_smmu_map { + dma_addr_t iova; + phys_addr_t pa; +}; + +/** + * struct ipa_ntn_setup_info - NTN TX/Rx configuration + * @client: type of "client" (IPA_CLIENT_ODU#_PROD/CONS) + * @smmu_enabled: SMMU is enabled for uC or not + * @ring_base_pa: physical address of the base of the Tx/Rx ring + * @ring_base_iova: virtual address of the base of the Tx/Rx ring + * @ring_base_sgt:Scatter table for ntn_rings,contains valid non NULL + * value when ENAC S1-SMMU enabed, else NULL. + * @ntn_ring_size: size of the Tx/Rx ring (in terms of elements) + * @buff_pool_base_pa: physical address of the base of the Tx/Rx buffer pool + * @buff_pool_base_iova: virtual address of the base of the Tx/Rx buffer pool + * @buff_pool_base_sgt: Scatter table for buffer pools,contains valid + * non NULL value. When NULL, do continuosly + * pa to iova mapping (SMMU disable, pa == iova). + * @num_buffers: Rx/Tx buffer pool size (in terms of elements) + * @data_buff_size: size of the each data buffer allocated in DDR + * @ntn_reg_base_ptr_pa: physical address of the Tx/Rx NTN Ring's + * @u8 db_mode: 0 means irq mode, 1 means db mode + * tail pointer + */ +struct ipa_ntn_setup_info { + enum ipa_client_type client; + bool smmu_enabled; + phys_addr_t ring_base_pa; + dma_addr_t ring_base_iova; + struct sg_table *ring_base_sgt; + + u32 ntn_ring_size; + + phys_addr_t buff_pool_base_pa; + dma_addr_t buff_pool_base_iova; + struct sg_table *buff_pool_base_sgt; + + struct ntn_buff_smmu_map *data_buff_list; + + u32 num_buffers; + + u32 data_buff_size; + + phys_addr_t ntn_reg_base_ptr_pa; + + u8 db_mode; +}; + +/** + * struct ipa_uc_offload_out_params - out parameters for uC offload + * + * @clnt_hndl: Handle that client need to pass during + * further operations + */ +struct ipa_uc_offload_out_params { + u32 clnt_hndl; +}; + +/** + * struct ipa_ntn_conn_in_params - NTN TX/Rx connect parameters + * @ul: parameters to connect UL pipe(from Neutrino to IPA) + * @dl: parameters to connect DL pipe(from IPA to Neutrino) + */ +struct ipa_ntn_conn_in_params { + struct ipa_ntn_setup_info ul; + struct ipa_ntn_setup_info dl; +}; + +/** + * struct ipa_ntn_conn_out_params - information provided + * to uC offload client + * @ul_uc_db_pa: physical address of IPA uc doorbell for UL + * @dl_uc_db_pa: physical address of IPA uc doorbell for DL + * @clnt_hdl: opaque handle assigned to offload client + * @ul_uc_db_iomem: iomem address of IPA uc doorbell for UL + * @dl_uc_db_iomem: iomem address of IPA uc doorbell for DL + */ +struct ipa_ntn_conn_out_params { + phys_addr_t ul_uc_db_pa; + phys_addr_t dl_uc_db_pa; + void __iomem *ul_uc_db_iomem; + void __iomem *dl_uc_db_iomem; +}; + +/** + * struct ipa_uc_offload_conn_in_params - information provided by + * uC offload client + * @clnt_hndl: Handle that return as part of reg interface + * @proto: Protocol to use for offload data path + * @ntn: uC RX/Tx configuration info + */ +struct ipa_uc_offload_conn_in_params { + u32 clnt_hndl; + union { + struct ipa_ntn_conn_in_params ntn; + } u; +}; + +/** + * struct ipa_uc_offload_conn_out_params - information provided + * to uC offload client + * @ul_uc_db_pa: physical address of IPA uc doorbell for UL + * @dl_uc_db_pa: physical address of IPA uc doorbell for DL + * @clnt_hdl: opaque handle assigned to offload client + */ +struct ipa_uc_offload_conn_out_params { + union { + struct ipa_ntn_conn_out_params ntn; + } u; +}; + +/** + * struct ipa_perf_profile - To set BandWidth profile + * + * @client: type of "client" (IPA_CLIENT_ODU#_PROD/CONS) + * @proto: uC offload protocol type + * @max_supported_bw_mbps: maximum bandwidth needed (in Mbps) + */ +struct ipa_perf_profile { + enum ipa_client_type client; + enum ipa_uc_offload_proto proto; + u32 max_supported_bw_mbps; +}; + +/** + * struct ipa_uc_ready_params - uC ready CB parameters + * @is_uC_ready: uC loaded or not + * @priv : callback cookie + * @notify: callback + * @proto: uC offload protocol type + */ +struct ipa_uc_ready_params { + bool is_uC_ready; + void *priv; + ipa_uc_ready_cb notify; + enum ipa_uc_offload_proto proto; +}; + +#if IS_ENABLED(CONFIG_IPA3) + +/** + * ipa_uc_offload_reg_intf - Client should call this function to + * init uC offload data path + * + * @init: [in] initialization parameters + * + * Note: Should not be called from atomic context and only + * after checking IPA readiness using ipa_register_ipa_ready_cb() + * + * @Return 0 on success, negative on failure + */ +int ipa_uc_offload_reg_intf( + struct ipa_uc_offload_intf_params *in, + struct ipa_uc_offload_out_params *out); + +/** + * ipa_uc_offload_cleanup - Client Driver should call this + * function before unload and after disconnect + * + * @Return 0 on success, negative on failure + */ +int ipa_uc_offload_cleanup(u32 clnt_hdl); + +/** + * ipa_uc_offload_conn_pipes - Client should call this + * function to connect uC pipe for offload data path + * + * @in: [in] input parameters from client + * @out: [out] output params to client + * + * Note: Should not be called from atomic context and only + * after checking IPA readiness using ipa_register_ipa_ready_cb() + * + * @Return 0 on success, negative on failure + */ +int ipa_uc_offload_conn_pipes(struct ipa_uc_offload_conn_in_params *in, + struct ipa_uc_offload_conn_out_params *out); + +/** + * ipa_uc_offload_disconn_pipes() - Client should call this + * function to disconnect uC pipe to disable offload data path + * @clnt_hdl: [in] opaque client handle assigned by IPA to client + * + * Note: Should not be called from atomic context + * + * Returns: 0 on success, negative on failure + */ +int ipa_uc_offload_disconn_pipes(u32 clnt_hdl); + +/** + * ipa_set_perf_profile() - Client should call this function to + * set IPA clock Band Width based on data rates + * @profile: [in] BandWidth profile to use + * + * Returns: 0 on success, negative on failure + */ +int ipa_set_perf_profile(struct ipa_perf_profile *profile); + + +/* + * To register uC ready callback if uC not ready + * and also check uC readiness + * if uC not ready only, register callback + */ +int ipa_uc_offload_reg_rdyCB(struct ipa_uc_ready_params *param); + +/* + * To de-register uC ready callback + */ +void ipa_uc_offload_dereg_rdyCB(enum ipa_uc_offload_proto proto); + +#else /* IS_ENABLED(CONFIG_IPA3) */ + +static inline int ipa_uc_offload_reg_intf( + struct ipa_uc_offload_intf_params *in, + struct ipa_uc_offload_out_params *out) +{ + return -EPERM; +} + +static inline int ipa_uC_offload_cleanup(u32 clnt_hdl) +{ + return -EPERM; +} + +static inline int ipa_uc_offload_conn_pipes( + struct ipa_uc_offload_conn_in_params *in, + struct ipa_uc_offload_conn_out_params *out) +{ + return -EPERM; +} + +static inline int ipa_uc_offload_disconn_pipes(u32 clnt_hdl) +{ + return -EPERM; +} + +static inline int ipa_set_perf_profile(struct ipa_perf_profile *profile) +{ + return -EPERM; +} + +static inline int ipa_uc_offload_reg_rdyCB(struct ipa_uc_ready_params *param) +{ + return -EPERM; +} + +static inline void ipa_uc_offload_dereg_rdyCB(enum ipa_uc_offload_proto proto) +{ +} + +#endif /* CONFIG_IPA3 */ + +#endif /* _IPA_UC_OFFLOAD_H_ */ diff --git a/include/linux/ipa_wdi3.h b/include/linux/ipa_wdi3.h new file mode 100644 index 000000000000..54b2c2421d68 --- /dev/null +++ b/include/linux/ipa_wdi3.h @@ -0,0 +1,1004 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2018 - 2021, The Linux Foundation. All rights reserved. + * + * Copyright (c) 2021-2024, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef _IPA_WDI3_H_ +#define _IPA_WDI3_H_ + +#include + +#define IPA_HW_WDI3_TCL_DATA_CMD_ER_DESC_SIZE 32 +#define IPA_HW_WDI3_IPA2FW_ER_DESC_SIZE 8 + +#define IPA_HW_WDI3_MAX_ER_DESC_SIZE \ + (((IPA_HW_WDI3_TCL_DATA_CMD_ER_DESC_SIZE) > \ + (IPA_HW_WDI3_IPA2FW_ER_DESC_SIZE)) ? \ + (IPA_HW_WDI3_TCL_DATA_CMD_ER_DESC_SIZE) : \ + (IPA_HW_WDI3_IPA2FW_ER_DESC_SIZE)) + +#define IPA_WDI_MAX_SUPPORTED_SYS_PIPE 3 +#define IPA_WDI_MAX_FILTER_INFO_COUNT 5 + +typedef u32 ipa_wdi_hdl_t; + +enum ipa_wdi_version { + IPA_WDI_1, + IPA_WDI_2, + IPA_WDI_3, + IPA_WDI_3_V2, + IPA_WDI_VER_MAX +}; + +#define IPA_WDI3_TX_DIR 1 +#define IPA_WDI3_TX1_DIR 2 +#define IPA_WDI3_RX_DIR 3 +#define IPA_WDI_INST_MAX (2) + +/** + * struct ipa_wdi_init_in_params - wdi init input parameters + * + * @wdi_version: wdi version + * @notify: uc ready callback + * @priv: uc ready callback cookie + */ +struct ipa_wdi_init_in_params { + enum ipa_wdi_version wdi_version; + ipa_uc_ready_cb notify; + void *priv; +#ifdef IPA_WAN_MSG_IPv6_ADDR_GW_LEN + ipa_wdi_meter_notifier_cb wdi_notify; +#endif + int inst_id; +}; + +/** + * struct ipa_wdi_init_out_params - wdi init output parameters + * + * @is_uC_ready: is uC ready. No API should be called until uC + is ready. + * @is_smmu_enable: is smmu enabled + * @is_over_gsi: is wdi over GSI or uC + * @opt_wdi_dpath: is optimized data path enabled. + */ +struct ipa_wdi_init_out_params { + bool is_uC_ready; + bool is_smmu_enabled; + bool is_over_gsi; + ipa_wdi_hdl_t hdl; + bool opt_wdi_dpath; +}; +/** + * struct filter_tuple_info - Properties of filters installed with WLAN + * + * @version: IP version, 0 - IPv4, 1 - IPv6 + * @ipv4_saddr: IPV4 source address + * @ipv4_daddr: IPV4 destination address + * @ipv6_saddr: IPV6 source address + * @ipv6_daddr: IPV6 destination address + * @ipv4_addr: IPV4 address + * @ipv6_addr: IPV6 address + * @protocol: trasport protocol being used + * @sport: source port + * @dport: destination port + * @out_hdl: handle given by WLAN for filter installation + */ + +struct filter_tuple_info { + u8 version; + union { + struct { + __be32 ipv4_saddr; + __be32 ipv4_daddr; + } ipv4_addr; + struct { + __be32 ipv6_saddr[4]; + __be32 ipv6_daddr[4]; + } ipv6_addr; + }; + u8 protocol; + __be16 sport; + __be16 dport; + u32 out_hdl; +}; + +/** + * struct ipa_wdi_opt_dpath_flt_add_cb_params - wdi filter add callback parameters + * + * @num_tuples: Number of filter tuples + * @ip_addr_port_tuple: IP info (source/destination IP, source/destination port) + */ +struct ipa_wdi_opt_dpath_flt_add_cb_params { + u8 num_tuples; + struct filter_tuple_info flt_info[IPA_WDI_MAX_FILTER_INFO_COUNT]; +}; + +/** + * struct ipa_wdi_opt_dpath_flt_rem_cb_params - wdi filter remove callback parameters + * + * @num_tuples: Number of filters to be removed + * @hdl_info: array of handles of filters to be removed + */ +struct ipa_wdi_opt_dpath_flt_rem_cb_params { + u8 num_tuples; + u32 hdl_info[IPA_WDI_MAX_FILTER_INFO_COUNT]; +}; + +/** + * struct ipa_wdi_opt_dpath_flt_rsrv_cb_params - wdi filter reserve callback parameters + * + * @num_filters: number of filters to be reserved + * @rsrv_timeout: reservation timeout in milliseconds + */ +struct ipa_wdi_opt_dpath_flt_rsrv_cb_params { + u8 num_filters; + u32 rsrv_timeout; +}; + +typedef int (*ipa_wdi_opt_dpath_flt_rsrv_cb) + (void *priv, struct ipa_wdi_opt_dpath_flt_rsrv_cb_params *in); + +typedef int (*ipa_wdi_opt_dpath_flt_rsrv_rel_cb) + (void *priv); + +typedef int (*ipa_wdi_opt_dpath_flt_add_cb) + (void *priv, struct ipa_wdi_opt_dpath_flt_add_cb_params *in_out); + +typedef int (*ipa_wdi_opt_dpath_flt_rem_cb) + (void *priv, struct ipa_wdi_opt_dpath_flt_rem_cb_params *in); + +/** + * struct ipa_wdi_hdr_info - Header to install on IPA HW + * + * @hdr: header to install on IPA HW + * @hdr_len: length of header + * @dst_mac_addr_offset: destination mac address offset + * @hdr_type: layer two header type + */ +struct ipa_wdi_hdr_info { + u8 *hdr; + u8 hdr_len; + u8 dst_mac_addr_offset; + enum ipa_hdr_l2_type hdr_type; +}; + +/** + * struct ipa_wdi_reg_intf_in_params - parameters for uC offload + * interface registration + * + * @netdev_name: network interface name + * @hdr_info: header information + * @is_meta_data_valid: if metadata is valid + * @meta_data: metadata if any + * @meta_data_mask: metadata mask + * @is_tx1_used: to indicate whether 2.4g or 5g iface + */ +struct ipa_wdi_reg_intf_in_params { + const char *netdev_name; + struct ipa_wdi_hdr_info hdr_info[IPA_IP_MAX]; + enum ipa_client_type alt_dst_pipe; + u8 is_meta_data_valid; + u32 meta_data; + u32 meta_data_mask; + u8 is_tx1_used; + ipa_wdi_hdl_t hdl; +}; + +/** + * struct ipa_wdi_pipe_setup_info - WDI TX/Rx configuration + * @ipa_ep_cfg: ipa endpoint configuration + * @client: type of "client" + * @transfer_ring_base_pa: physical address of the base of the transfer ring + * @transfer_ring_size: size of the transfer ring + * @transfer_ring_doorbell_pa: physical address of the doorbell that + IPA uC will update the tailpointer of the transfer ring + * @is_txr_rn_db_pcie_addr: Bool indicated txr ring DB is pcie or not + * @event_ring_base_pa: physical address of the base of the event ring + * @event_ring_size: event ring size + * @event_ring_doorbell_pa: physical address of the doorbell that IPA uC + will update the headpointer of the event ring + * @is_evt_rn_db_pcie_addr: Bool indicated evt ring DB is pcie or not + * @num_pkt_buffers: Number of pkt buffers allocated. The size of the event + ring and the transfer ring has to be at least ( num_pkt_buffers + 1) + * @pkt_offset: packet offset (wdi header length) + * @desc_format_template[IPA_HW_WDI3_MAX_ER_DESC_SIZE]: Holds a cached + template of the desc format + * @rx_bank_id: value used to perform TCL HW setting + + */ +struct ipa_wdi_pipe_setup_info { + struct ipa_ep_cfg ipa_ep_cfg; + enum ipa_client_type client; + phys_addr_t transfer_ring_base_pa; + u32 transfer_ring_size; + phys_addr_t transfer_ring_doorbell_pa; + bool is_txr_rn_db_pcie_addr; + + phys_addr_t event_ring_base_pa; + u32 event_ring_size; + phys_addr_t event_ring_doorbell_pa; + bool is_evt_rn_db_pcie_addr; + u16 num_pkt_buffers; + + u16 pkt_offset; + + u32 desc_format_template[IPA_HW_WDI3_MAX_ER_DESC_SIZE]; + u8 rx_bank_id; +}; + +/** + * struct ipa_wdi_pipe_setup_info_smmu - WDI TX/Rx configuration + * @ipa_ep_cfg: ipa endpoint configuration + * @client: type of "client" + * @transfer_ring_base_pa: physical address of the base of the transfer ring + * @transfer_ring_size: size of the transfer ring + * @transfer_ring_doorbell_pa: physical address of the doorbell that + IPA uC will update the tailpointer of the transfer ring + * @is_txr_rn_db_pcie_addr: Bool indicated txr ring DB is pcie or not + * @event_ring_base_pa: physical address of the base of the event ring + * @event_ring_size: event ring size + * @event_ring_doorbell_pa: physical address of the doorbell that IPA uC + will update the headpointer of the event ring + * @is_evt_rn_db_pcie_addr: Bool indicated evt ring DB is pcie or not + * @num_pkt_buffers: Number of pkt buffers allocated. The size of the event + ring and the transfer ring has to be at least ( num_pkt_buffers + 1) + * @pkt_offset: packet offset (wdi header length) + * @desc_format_template[IPA_HW_WDI3_MAX_ER_DESC_SIZE]: Holds a cached + template of the desc format + * @rx_bank_id: value used to perform TCL HW setting + + */ +struct ipa_wdi_pipe_setup_info_smmu { + struct ipa_ep_cfg ipa_ep_cfg; + enum ipa_client_type client; + struct sg_table transfer_ring_base; + u32 transfer_ring_size; + phys_addr_t transfer_ring_doorbell_pa; + bool is_txr_rn_db_pcie_addr; + + struct sg_table event_ring_base; + u32 event_ring_size; + phys_addr_t event_ring_doorbell_pa; + bool is_evt_rn_db_pcie_addr; + u16 num_pkt_buffers; + + u16 pkt_offset; + + u32 desc_format_template[IPA_HW_WDI3_MAX_ER_DESC_SIZE]; + u8 rx_bank_id; +}; + +/** + * struct ipa_wdi_conn_in_params - information provided by + * uC offload client + * @notify: client callback function + * @priv: client cookie + * @is_smmu_enabled: if smmu is enabled + * @num_sys_pipe_needed: number of sys pipe needed + * @sys_in: parameters to setup sys pipe in mcc mode + * @tx: parameters to connect TX pipe(from IPA to WLAN) + * @tx_smmu: smmu parameters to connect TX pipe(from IPA to WLAN) + * @rx: parameters to connect RX pipe(from WLAN to IPA) + * @rx_smmu: smmu parameters to connect RX pipe(from WLAN to IPA) + * @is_tx1_used: to notify extra pipe required/not + * @tx1: parameters to connect TX1 pipe(from IPA to WLAN second pipe) + * @tx1_smmu: smmu parameters to connect TX1 pipe(from IPA to WLAN second pipe) + */ +struct ipa_wdi_conn_in_params { + ipa_notify_cb notify; + void *priv; + bool is_smmu_enabled; + u8 num_sys_pipe_needed; + struct ipa_sys_connect_params sys_in[IPA_WDI_MAX_SUPPORTED_SYS_PIPE]; + union { + struct ipa_wdi_pipe_setup_info tx; + struct ipa_wdi_pipe_setup_info_smmu tx_smmu; + } u_tx; + union { + struct ipa_wdi_pipe_setup_info rx; + struct ipa_wdi_pipe_setup_info_smmu rx_smmu; + } u_rx; + bool is_tx1_used; + union { + struct ipa_wdi_pipe_setup_info tx; + struct ipa_wdi_pipe_setup_info_smmu tx_smmu; + } u_tx1; + ipa_wdi_hdl_t hdl; +}; + +/** + * struct ipa_wdi_conn_out_params - information provided + * to WLAN driver + * @tx_uc_db_pa: physical address of IPA uC doorbell for TX + * @rx_uc_db_pa: physical address of IPA uC doorbell for RX + * @tx1_uc_db_pa: physical address of IPA uC doorbell for TX1 + * @is_ddr_mapped: flag set to true if address is from DDR + */ +struct ipa_wdi_conn_out_params { + phys_addr_t tx_uc_db_pa; + phys_addr_t rx_uc_db_pa; + phys_addr_t tx1_uc_db_pa; + bool is_ddr_mapped; +}; + +/** + * struct ipa_wdi_perf_profile - To set BandWidth profile + * + * @client: type of client + * @max_supported_bw_mbps: maximum bandwidth needed (in Mbps) + */ +struct ipa_wdi_perf_profile { + enum ipa_client_type client; + u32 max_supported_bw_mbps; +}; + + +/** + * struct ipa_wdi_capabilities - wdi capability parameters + * + * @num_of_instances: Number of WLAN instances supported. + */ +struct ipa_wdi_capabilities_out_params { + u8 num_of_instances; +}; + +#if IS_ENABLED(CONFIG_IPA3) + +/** + * ipa_wdi_get_capabilities - Client should call this function to + * know the WDI capabilities + * + * Note: Should not be called from atomic context and only + * after checking IPA readiness using ipa_register_ipa_ready_cb() + * + * @Return 0 on success, negative on failure + */ +int ipa_wdi_get_capabilities( + struct ipa_wdi_capabilities_out_params *out); + +/** + * ipa_wdi_init - Client should call this function to + * init WDI IPA offload data path + * + * Note: Should not be called from atomic context and only + * after checking IPA readiness using ipa_register_ipa_ready_cb() + * + * @Return 0 on success, negative on failure + */ +int ipa_wdi_init(struct ipa_wdi_init_in_params *in, + struct ipa_wdi_init_out_params *out); + +/** + * ipa_wdi_opt_dpath_register_flt_cb_per_inst - Client should call this function to + * register filter reservation/release and filter addition/deletion callbacks + * + * + * @Return 0 on success, negative on failure + */ +int ipa_wdi_opt_dpath_register_flt_cb_per_inst( + ipa_wdi_hdl_t hdl, + ipa_wdi_opt_dpath_flt_rsrv_cb flt_rsrv_cb, + ipa_wdi_opt_dpath_flt_rsrv_rel_cb flt_rsrv_rel_cb, + ipa_wdi_opt_dpath_flt_add_cb flt_add_cb, + ipa_wdi_opt_dpath_flt_rem_cb flt_rem_cb); + +/** + * ipa_wdi_opt_dpath_notify_flt_rsvd_per_inst - Client should call this function to + * notify filter reservation event to IPA + * + * + * @Return 0 on success, negative on failure + */ +int ipa_wdi_opt_dpath_notify_flt_rsvd_per_inst(ipa_wdi_hdl_t hdl, + bool is_success); +/** + * ipa_wdi_opt_dpath_notify_flt_rlsd_per_inst - Client should call this function to + * notify filter deletion event to IPA + * + * + * @Return 0 on success, negative on failure + */ +int ipa_wdi_opt_dpath_notify_flt_rlsd_per_inst(ipa_wdi_hdl_t hdl, + bool is_success); + +/** + * ipa_wdi_opt_dpath_rsrv_filter_req - Client should call this function to + * send filter reservation request to wlan + * + * + * @Return 0 on success, negative on failure + */ +int ipa_wdi_opt_dpath_rsrv_filter_req( + struct ipa_wlan_opt_dp_rsrv_filter_req_msg_v01 *req, + struct ipa_wlan_opt_dp_rsrv_filter_resp_msg_v01 *resp); + +/** + * ipa_wdi_opt_dpath_add_filter_req - Client should call this function to + * send filter add request to wlan + * + * + * @Return 0 on success, negative on failure + */ +int ipa_wdi_opt_dpath_add_filter_req( + struct ipa_wlan_opt_dp_add_filter_req_msg_v01 *req, + struct ipa_wlan_opt_dp_add_filter_complt_ind_msg_v01 *ind); + +/** + * ipa_wdi_opt_dpath_remove_filter_req - Client should call this function to + * send filter remove request to wlan + * + * + * @Return 0 on success, negative on failure + */ +int ipa_wdi_opt_dpath_remove_filter_req( + struct ipa_wlan_opt_dp_remove_filter_req_msg_v01 *req, + struct ipa_wlan_opt_dp_remove_filter_complt_ind_msg_v01 *ind); + +/** + * ipa_wdi_opt_dpath_remove_filter_req - Client should call this function to + * send release reservation request to wlan + * + * + * @Return 0 on success, negative on failure + */ +int ipa_wdi_opt_dpath_remove_all_filter_req( + struct ipa_wlan_opt_dp_remove_all_filter_req_msg_v01 *req, + struct ipa_wlan_opt_dp_remove_all_filter_resp_msg_v01 *resp); + +/** + * ipa_xr_wdi_opt_dpath_rsrv_filter_req - Client should call this function to + * send filter reservation request to wlan + * + * + * @Return 0 on success, negative on failure + */ +int ipa_xr_wdi_opt_dpath_rsrv_filter_req(void); + +/** + * ipa_xr_wdi_opt_dpath_add_filter_req - Client should call this function to + * send filter add request to wlan + * + * + * @Return 0 on success, negative on failure + */ +int ipa_xr_wdi_opt_dpath_add_filter_req(struct ipa_wdi_opt_dpath_flt_add_cb_params *req, + u32 stream_id); + +/** + * ipa_xr_wdi_opt_dpath_remove_filter_req - Client should call this function to + * send filter remove request to wlan + * + * + * @Return 0 on success, negative on failure + */ +int ipa_xr_wdi_opt_dpath_remove_filter_req(u32 stream_id); + +/** + * ipa_xr_wdi_opt_dpath_remove_all_filter_req - Client should call this function to + * send release reservation request to wlan + * + * + * @Return 0 on success, negative on failure + */ +int ipa_xr_wdi_opt_dpath_remove_all_filter_req(void); + +/** ipa_get_wdi_version - return wdi version + * + * @Return void + */ +int ipa_get_wdi_version(void); + +/** ipa_wdi_is_tx1_used - return if DBS mode is active + * + * @Return bool + */ +bool ipa_wdi_is_tx1_used(void); + +/** + * ipa_wdi_init_per_inst - Client should call this function to + * init WDI IPA offload data path + * + * Note: Should not be called from atomic context and only + * after checking IPA readiness using ipa_register_ipa_ready_cb() + * + * @Return 0 on success, negative on failure + */ +int ipa_wdi_init_per_inst(struct ipa_wdi_init_in_params *in, + struct ipa_wdi_init_out_params *out); + +/** + * ipa_wdi_cleanup - Client should call this function to + * clean up WDI IPA offload data path + * + * @Return 0 on success, negative on failure + */ +int ipa_wdi_cleanup(void); + +/** + * ipa_wdi_cleanup_per_inst - Client should call this function to + * clean up WDI IPA offload data path + * + * @hdl: hdl to wdi client + * + * @Return 0 on success, negative on failure + */ +int ipa_wdi_cleanup_per_inst(ipa_wdi_hdl_t hdl); + + +/** + * ipa_wdi_reg_intf - Client should call this function to + * register interface + * + * Note: Should not be called from atomic context + * + * @Return 0 on success, negative on failure + */ +int ipa_wdi_reg_intf( + struct ipa_wdi_reg_intf_in_params *in); + +/** + * ipa_wdi_reg_intf_per_inst - Client should call this function to + * register interface + * + * Note: Should not be called from atomic context + * + * @Return 0 on success, negative on failure + */ +int ipa_wdi_reg_intf_per_inst( + struct ipa_wdi_reg_intf_in_params *in); + +/** + * ipa_wdi_dereg_intf - Client Driver should call this + * function to deregister before unload and after disconnect + * + * @Return 0 on success, negative on failure + */ +int ipa_wdi_dereg_intf(const char *netdev_name); + +/** + * ipa_wdi_dereg_intf_per_inst - Client Driver should call this + * function to deregister before unload and after disconnect + * + * @Return 0 on success, negative on failure + */ +int ipa_wdi_dereg_intf_per_inst(const char *netdev_name, ipa_wdi_hdl_t hdl); + +/** + * ipa_wdi_conn_pipes - Client should call this + * function to connect pipes + * + * @in: [in] input parameters from client + * @out: [out] output params to client + * + * Note: Should not be called from atomic context + * + * @Return 0 on success, negative on failure + */ +int ipa_wdi_conn_pipes(struct ipa_wdi_conn_in_params *in, + struct ipa_wdi_conn_out_params *out); + +/** + * ipa_wdi_conn_pipes_per_inst - Client should call this + * function to connect pipes + * + * @in: [in] input parameters from client + * @out: [out] output params to client + * + * Note: Should not be called from atomic context + * + * @Return 0 on success, negative on failure + */ +int ipa_wdi_conn_pipes_per_inst(struct ipa_wdi_conn_in_params *in, + struct ipa_wdi_conn_out_params *out); + +/** + * ipa_wdi_disconn_pipes() - Client should call this + * function to disconnect pipes + * + * Note: Should not be called from atomic context + * + * Returns: 0 on success, negative on failure + */ +int ipa_wdi_disconn_pipes(void); + +/** + * ipa_wdi_disconn_pipes_per_inst() - Client should call this + * function to disconnect pipes + * + * @hdl: hdl to wdi client + * Note: Should not be called from atomic context + * + * Returns: 0 on success, negative on failure + */ +int ipa_wdi_disconn_pipes_per_inst(ipa_wdi_hdl_t hdl); + +/** + * ipa_wdi_enable_pipes() - Client should call this + * function to enable IPA offload data path + * + * Note: Should not be called from atomic context + * + * Returns: 0 on success, negative on failure + */ +int ipa_wdi_enable_pipes(void); + +/** + * ipa_wdi_enable_pipes_per_inst() - Client should call this + * function to enable IPA offload data path + * + * @hdl: hdl to wdi client + * Note: Should not be called from atomic context + * + * Returns: 0 on success, negative on failure + */ +int ipa_wdi_enable_pipes_per_inst(ipa_wdi_hdl_t hdl); + +/** + * ipa_wdi_disable_pipes() - Client should call this + * function to disable IPA offload data path + * + * Note: Should not be called from atomic context + * + * Returns: 0 on success, negative on failure + */ +int ipa_wdi_disable_pipes(void); + +/** + * ipa_wdi_disable_pipes_per_inst() - Client should call this + * function to disable IPA offload data path + * + * @hdl: hdl to wdi client + * Note: Should not be called from atomic context + * + * Returns: 0 on success, negative on failure + */ +int ipa_wdi_disable_pipes_per_inst(ipa_wdi_hdl_t hdl); + +/** + * ipa_wdi_set_perf_profile() - Client should call this function to + * set IPA clock bandwidth based on data rates + * + * @profile: [in] BandWidth profile to use + * + * Returns: 0 on success, negative on failure + */ +int ipa_wdi_set_perf_profile(struct ipa_wdi_perf_profile *profile); + +/** + * ipa_wdi_set_perf_profile_per_inst() - Client should call this function to + * set IPA clock bandwidth based on data rates + * + * @hdl: hdl to wdi client + * @profile: [in] BandWidth profile to use + * + * Returns: 0 on success, negative on failure + */ +int ipa_wdi_set_perf_profile_per_inst(ipa_wdi_hdl_t hdl, + struct ipa_wdi_perf_profile *profile); + +/** + * ipa_wdi_create_smmu_mapping() - Create smmu mapping + * + * @num_buffers: number of buffers + * + * @info: wdi buffer info + */ +int ipa_wdi_create_smmu_mapping(u32 num_buffers, + struct ipa_wdi_buffer_info *info); + +/** + * ipa_wdi_create_smmu_mapping_per_inst() - Create smmu mapping + * + * @hdl: hdl to wdi client + * @num_buffers: number of buffers + * @info: wdi buffer info + */ +int ipa_wdi_create_smmu_mapping_per_inst(ipa_wdi_hdl_t hdl, + u32 num_buffers, + struct ipa_wdi_buffer_info *info); + +/** + * ipa_wdi_release_smmu_mapping() - Release smmu mapping + * + * @num_buffers: number of buffers + * + * @info: wdi buffer info + */ +int ipa_wdi_release_smmu_mapping(u32 num_buffers, + struct ipa_wdi_buffer_info *info); + +/** + * ipa_wdi_release_smmu_mapping_per_inst() - Release smmu mapping + * + * @hdl: hdl to wdi client + * @num_buffers: number of buffers + * + * @info: wdi buffer info + */ +int ipa_wdi_release_smmu_mapping_per_inst(ipa_wdi_hdl_t hdl, + u32 num_buffers, + struct ipa_wdi_buffer_info *info); + +/** + * ipa_wdi_get_stats() - Query WDI statistics + * @stats: [inout] stats blob from client populated by driver + * + * Returns: 0 on success, negative on failure + * + * @note Cannot be called from atomic context + * + */ +int ipa_wdi_get_stats(struct IpaHwStatsWDIInfoData_t *stats); + + +/** + * ipa_wdi_bw_monitor() - set wdi BW monitoring + * @info: [inout] info blob from client populated by driver + * + * Returns: 0 on success, negative on failure + * + * @note Cannot be called from atomic context + * + */ +int ipa_wdi_bw_monitor(struct ipa_wdi_bw_info *info); + +/** + * ipa_wdi_sw_stats() - set wdi BW monitoring + * @info: [inout] info blob from client populated by driver + * + * Returns: 0 on success, negative on failure + * + * @note Cannot be called from atomic context + * + */ +int ipa_wdi_sw_stats(struct ipa_wdi_tx_info *info); + +#else /* IS_ENABLED(CONFIG_IPA3) */ + +/** + * ipa_wdi_get_capabilities - Client should call this function to + * know the WDI capabilities + * + * Note: Should not be called from atomic context and only + * after checking IPA readiness using ipa_register_ipa_ready_cb() + * + * @Return 0 on success, negative on failure + */ +int ipa_wdi_get_capabilities( + struct ipa_wdi_capabilities_out_params *out) +{ + return -EPERM; +} + +static inline int ipa_wdi_init(struct ipa_wdi_init_in_params *in, + struct ipa_wdi_init_out_params *out) +{ + return -EPERM; +} + +static inline int ipa_wdi_init_per_inst( + struct ipa_wdi_init_in_params *in, + struct ipa_wdi_init_out_params *out) +{ + return -EPERM; +} + +static inline int ipa_get_wdi_version(void) +{ + return -EPERM; +} + +static inline int ipa_wdi_is_tx1_used(void) +{ + return -EPERM; +} + +static inline int ipa_wdi_cleanup(void) +{ + return -EPERM; +} + +static inline int ipa_wdi_cleanup_per_inst(ipa_wdi_hdl_t hdl) +{ + return -EPERM; +} + +static inline int ipa_wdi_reg_intf( + struct ipa_wdi_reg_intf_in_params *in) +{ + return -EPERM; +} + +static inline int ipa_wdi_reg_intf_per_inst( + struct ipa_wdi_reg_intf_in_params *in) +{ + return -EPERM; +} + +static inline int ipa_wdi_dereg_intf(const char *netdev_name) +{ + return -EPERM; +} + +static inline int ipa_wdi_dereg_intf_per_inst(const char *netdev_name, + ipa_wdi_hdl_t hdl) +{ + return -EPERM; +} + +static inline int ipa_wdi_conn_pipes(struct ipa_wdi_conn_in_params *in, + struct ipa_wdi_conn_out_params *out) +{ + return -EPERM; +} + +static inline int ipa_wdi_conn_pipes_per_inst( + struct ipa_wdi_conn_in_params *in, + struct ipa_wdi_conn_out_params *out) +{ + return -EPERM; +} + +static inline int ipa_wdi_disconn_pipes(void) +{ + return -EPERM; +} + +static inline int ipa_wdi_disconn_pipes_per_inst(ipa_wdi_hdl_t hdl) +{ + return -EPERM; +} + + +static inline int ipa_wdi_enable_pipes(void) +{ + return -EPERM; +} + +static inline int ipa_wdi_enable_pipes_per_inst(ipa_wdi_hdl_t hdl) +{ + return -EPERM; +} + +static inline int ipa_wdi_disable_pipes(void) +{ + return -EPERM; +} + +static inline int ipa_wdi_disable_pipes_per_inst(ipa_wdi_hdl_t hdl) +{ + return -EPERM; +} + +static inline int ipa_wdi_set_perf_profile( + struct ipa_wdi_perf_profile *profile) +{ + return -EPERM; +} + +static inline int ipa_wdi_set_perf_profile_per_inst( + ipa_wdi_hdl_t hdl, + struct ipa_wdi_perf_profile *profile) +{ + return -EPERM; +} + +static inline int ipa_wdi_create_smmu_mapping(u32 num_buffers, + struct ipa_wdi_buffer_info *info) +{ + return -EPERM; +} + +static inline int ipa_wdi_create_smmu_mapping_per_inst( + ipa_wdi_hdl_t hdl, + u32 num_buffers, + struct ipa_wdi_buffer_info *info) +{ + return -EPERM; +} + +static inline int ipa_wdi_release_smmu_mapping(u32 num_buffers, + struct ipa_wdi_buffer_info *info) +{ + return -EPERM; +} + +static inline int ipa_wdi_release_smmu_mapping_per_inst( + ipa_wdi_hdl_t hdl, + u32 num_buffers, + struct ipa_wdi_buffer_info *info) +{ + return -EPERM; +} + +static inline int ipa_wdi_get_stats(struct IpaHwStatsWDIInfoData_t *stats) +{ + return -EPERM; +} + +static inline int ipa_wdi_bw_monitor(struct ipa_wdi_bw_info *info) +{ + return -EPERM; +} + +static inline int ipa_wdi_sw_stats(struct ipa_wdi_tx_info *info) +{ + return -EPERM; +} + +static inline int ipa_wdi_opt_dpath_register_flt_cb_per_inst( + ipa_wdi_hdl_t hdl, + ipa_wdi_opt_dpath_flt_rsrv_cb flt_rsrv_cb, + ipa_wdi_opt_dpath_flt_rsrv_rel_cb flt_rsrv_rel_cb, + ipa_wdi_opt_dpath_flt_add_cb flt_add_cb, + ipa_wdi_opt_dpath_flt_rem_cb flt_rem_cb) +{ + return -EPERM; +} + +static inline int ipa_wdi_opt_dpath_notify_flt_rsvd_per_inst(ipa_wdi_hdl_t hdl, + bool is_success) +{ + return -EPERM; +} + +static inline int ipa_wdi_opt_dpath_notify_flt_rlsd_per_inst(ipa_wdi_hdl_t hdl, + bool is_success) +{ + return -EPERM; +} + +static int ipa_wdi_opt_dpath_rsrv_filter_req( + struct ipa_wlan_opt_dp_rsrv_filter_req_msg_v01 *req, + struct ipa_wlan_opt_dp_rsrv_filter_resp_msg_v01 *resp) +{ + return -EPERM; +} + +static int ipa_wdi_opt_dpath_add_filter_req( + struct ipa_wlan_opt_dp_add_filter_req_msg_v01 *req, + struct ipa_wlan_opt_dp_add_filter_complt_ind_msg_v01 *ind) +{ + return -EPERM; +} + +static int ipa_wdi_opt_dpath_remove_filter_req( + struct ipa_wlan_opt_dp_remove_filter_req_msg_v01 *req, + struct ipa_wlan_opt_dp_remove_filter_complt_ind_msg_v01 *ind) +{ + return -EPERM; +} + +static int ipa_wdi_opt_dpath_remove_all_filter_req( + struct ipa_wlan_opt_dp_remove_all_filter_req_msg_v01 *req, + struct ipa_wlan_opt_dp_remove_all_filter_resp_msg_v01 *resp) +{ + return -EPERM; +} + +static int ipa_xr_wdi_opt_dpath_rsrv_filter_req(void) +{ + return -EPERM; +} + +static int ipa_xr_wdi_opt_dpath_add_filter_req(struct ipa_wdi_opt_dpath_flt_add_cb_params *req, + u32 stream_id) +{ + return -EPERM; +} + +static int ipa_xr_wdi_opt_dpath_remove_filter_req(u32 stream_id) +{ + return -EPERM; +} + +static int ipa_xr_wdi_opt_dpath_remove_all_filter_req(void) +{ + return -EPERM; +} + +#endif /* IS_ENABLED(CONFIG_IPA3) */ + +#endif /* _IPA_WDI3_H_ */ diff --git a/include/linux/ipa_wigig.h b/include/linux/ipa_wigig.h new file mode 100644 index 000000000000..d0e57d4aa5ab --- /dev/null +++ b/include/linux/ipa_wigig.h @@ -0,0 +1,487 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef _IPA_WIGIG_H_ +#define _IPA_WIGIG_H_ + +#include +#include "ipa.h" + +typedef void (*ipa_wigig_misc_int_cb)(void *priv); + +/* + * struct ipa_wigig_init_in_params - wigig init input parameters + * + * @periph_baddr_pa: physical address of wigig HW base + * @pseudo_cause_pa: physical address of wigig HW pseudo_cause register + * @int_gen_tx_pa: physical address of wigig HW int_gen_tx register + * @int_gen_rx_pa: physical address of wigig HW int_gen_rx register + * @dma_ep_misc_pa: physical address of wigig HW dma_ep_misc register + * @notify: uc ready callback + * @int_notify: wigig misc interrupt callback + * @priv: uc ready callback cookie + */ +struct ipa_wigig_init_in_params { + phys_addr_t periph_baddr_pa; + phys_addr_t pseudo_cause_pa; + phys_addr_t int_gen_tx_pa; + phys_addr_t int_gen_rx_pa; + phys_addr_t dma_ep_misc_pa; + ipa_uc_ready_cb notify; + ipa_wigig_misc_int_cb int_notify; + void *priv; +}; + +/* + * struct ipa_wigig_init_out_params - wigig init output parameters + * + * @is_uC_ready: is uC ready. No API should be called until uC is ready. + * @uc_db_pa: physical address of IPA uC doorbell + * @lan_rx_napi_enable: if we use NAPI in the LAN rx + */ +struct ipa_wigig_init_out_params { + bool is_uc_ready; + phys_addr_t uc_db_pa; + bool lan_rx_napi_enable; +}; + +/* + * struct ipa_wigig_hdr_info - Header to install on IPA HW + * + * @hdr: header to install on IPA HW + * @hdr_len: length of header + * @dst_mac_addr_offset: destination mac address offset + * @hdr_type: layer two header type + */ +struct ipa_wigig_hdr_info { + u8 *hdr; + u8 hdr_len; + u8 dst_mac_addr_offset; + enum ipa_hdr_l2_type hdr_type; +}; + +/* + * struct ipa_wigig_reg_intf_in_params - parameters for offload interface + * registration + * + * @netdev_name: network interface name + * @netdev_mac: netdev mac address + * @hdr_info: header information + */ +struct ipa_wigig_reg_intf_in_params { + const char *netdev_name; + u8 netdev_mac[IPA_MAC_ADDR_SIZE]; + struct ipa_wigig_hdr_info hdr_info[IPA_IP_MAX]; +}; + +/* + * struct ipa_wigig_pipe_setup_info - WIGIG TX/Rx configuration + * @desc_ring_base_pa: physical address of the base of the descriptor ring + * @desc_ring_size: size of the descriptor ring in bytes + * @desc_ring_HWHEAD_pa: physical address of the wigig descriptor ring HWHEAD + * @desc_ring_HWTAIL_pa: physical address of the wigig descriptor ring HWTAIL + * @status_ring_base_pa: physical address of the base of the status ring + * @status_ring_size: status ring size in bytes + * @desc_ring_HWHEAD_pa: physical address of the wigig descriptor ring HWHEAD + * @desc_ring_HWTAIL_pa: physical address of the wigig descriptor ring HWTAIL + */ +struct ipa_wigig_pipe_setup_info { + phys_addr_t desc_ring_base_pa; + u16 desc_ring_size; + phys_addr_t desc_ring_HWHEAD_pa; + phys_addr_t desc_ring_HWTAIL_pa; + + phys_addr_t status_ring_base_pa; + u16 status_ring_size; + phys_addr_t status_ring_HWHEAD_pa; + phys_addr_t status_ring_HWTAIL_pa; +}; + +/* + * struct ipa_wigig_pipe_setup_info_smmu - WIGIG TX/Rx configuration smmu mode + * @desc_ring_base: sg_table of the base of the descriptor ring + * @desc_ring_base_iova: IO virtual address mapped to physical base address + * @desc_ring_size: size of the descriptor ring in bytes + * @desc_ring_HWHEAD_pa: physical address of the wigig descriptor ring HWHEAD + * @desc_ring_HWTAIL_pa: physical address of the wigig descriptor ring HWTAIL + * @status_ring_base: sg_table of the base of the status ring + * @status_ring_base_iova: IO virtual address mapped to physical base address + * @status_ring_size: status ring size in bytes + * @desc_ring_HWHEAD_pa: physical address of the wigig descriptor ring HWHEAD + * @desc_ring_HWTAIL_pa: physical address of the wigig descriptor ring HWTAIL + */ +struct ipa_wigig_pipe_setup_info_smmu { + struct sg_table desc_ring_base; + u64 desc_ring_base_iova; + u16 desc_ring_size; + phys_addr_t desc_ring_HWHEAD_pa; + phys_addr_t desc_ring_HWTAIL_pa; + + struct sg_table status_ring_base; + u64 status_ring_base_iova; + u16 status_ring_size; + phys_addr_t status_ring_HWHEAD_pa; + phys_addr_t status_ring_HWTAIL_pa; +}; + +/* + * struct ipa_wigig_rx_pipe_data_buffer_info - WIGIG Rx data buffer + * configuration + * @data_buffer_base_pa: physical address of the physically contiguous + * Rx data buffer + * @data_buffer_size: size of the data buffer + */ +struct ipa_wigig_rx_pipe_data_buffer_info { + phys_addr_t data_buffer_base_pa; + u32 data_buffer_size; +}; + +/* + * struct ipa_wigig_rx_pipe_data_buffer_info_smmu - WIGIG Rx data buffer + * configuration smmu mode + * @data_buffer_base: sg_table of the physically contiguous + * Rx data buffer + * @data_buffer_base_iova: IO virtual address mapped to physical base address + * @data_buffer_size: size of the data buffer + */ +struct ipa_wigig_rx_pipe_data_buffer_info_smmu { + struct sg_table data_buffer_base; + u64 data_buffer_base_iova; + u32 data_buffer_size; +}; + +/* + * struct ipa_wigig_conn_rx_in_params - information provided by + * WIGIG offload client for Rx pipe + * @notify: client callback function + * @priv: client cookie + * @pipe: parameters to connect Rx pipe (WIGIG to IPA) + * @dbuff: Rx data buffer info + */ +struct ipa_wigig_conn_rx_in_params { + ipa_notify_cb notify; + void *priv; + struct ipa_wigig_pipe_setup_info pipe; + struct ipa_wigig_rx_pipe_data_buffer_info dbuff; +}; + +/* + * struct ipa_wigig_conn_rx_in_params_smmu - information provided by + * WIGIG offload client for Rx pipe + * @notify: client callback function + * @priv: client cookie + * @pipe_smmu: parameters to connect Rx pipe (WIGIG to IPA) smmu mode + * @dbuff_smmu: Rx data buffer info smmu mode + */ +struct ipa_wigig_conn_rx_in_params_smmu { + ipa_notify_cb notify; + void *priv; + struct ipa_wigig_pipe_setup_info_smmu pipe_smmu; + struct ipa_wigig_rx_pipe_data_buffer_info_smmu dbuff_smmu; +}; + +/* + * struct ipa_wigig_conn_out_params - information provided + * to WIGIG driver + * @client: client type allocated by IPA driver + */ +struct ipa_wigig_conn_out_params { + enum ipa_client_type client; +}; + +/* + * struct ipa_wigig_tx_pipe_data_buffer_info - WIGIG Tx data buffer + * configuration + * @data_buffer_size: size of a single data buffer + */ +struct ipa_wigig_tx_pipe_data_buffer_info { + u32 data_buffer_size; +}; + +/* + * struct ipa_wigig_tx_pipe_data_buffer_info_smmu - WIGIG Tx data buffer + * configuration smmu mode + * @data_buffer_base_pa: sg_tables of the Tx data buffers + * @data_buffer_base_iova: IO virtual address mapped to physical base address + * @num_buffers: number of buffers + * @data_buffer_size: size of a single data buffer + */ +struct ipa_wigig_tx_pipe_data_buffer_info_smmu { + struct sg_table *data_buffer_base; + u64 *data_buffer_base_iova; + u32 num_buffers; + u32 data_buffer_size; +}; + +/* + * struct ipa_wigig_conn_tx_in_params - information provided by + * wigig offload client for Tx pipe + * @pipe: parameters to connect Tx pipe (IPA to WIGIG) + * @dbuff: Tx data buffer info + * @int_gen_tx_bit_num: bit in int_gen_tx register associated with this client + * @client_mac: MAC address of client to be connected + */ +struct ipa_wigig_conn_tx_in_params { + struct ipa_wigig_pipe_setup_info pipe; + struct ipa_wigig_tx_pipe_data_buffer_info dbuff; + u8 int_gen_tx_bit_num; + u8 client_mac[IPA_MAC_ADDR_SIZE]; +}; + +/* + * struct ipa_wigig_conn_tx_in_params_smmu - information provided by + * wigig offload client for Tx pipe + * @pipe_smmu: parameters to connect Tx pipe (IPA to WIGIG) smmu mode + * @dbuff_smmu: Tx data buffer info smmu mode + * @int_gen_tx_bit_num: bit in int_gen_tx register associated with this client + * @client_mac: MAC address of client to be connected + */ +struct ipa_wigig_conn_tx_in_params_smmu { + struct ipa_wigig_pipe_setup_info_smmu pipe_smmu; + struct ipa_wigig_tx_pipe_data_buffer_info_smmu dbuff_smmu; + u8 int_gen_tx_bit_num; + u8 client_mac[IPA_MAC_ADDR_SIZE]; +}; + +#if IS_ENABLED(CONFIG_IPA3) + +/* + * ipa_wigig_init - Client should call this function to + * init WIGIG IPA offload data path + * + * Note: Should not be called from atomic context + * + * @Return 0 on success, negative on failure + */ +int ipa_wigig_init(struct ipa_wigig_init_in_params *in, + struct ipa_wigig_init_out_params *out); + +/* + * ipa_wigig_cleanup - Client should call this function to + * clean up WIGIG IPA offload data path + * + * @Return 0 on success, negative on failure + */ +int ipa_wigig_cleanup(void); + +/* + * ipa_wigig_is_smmu_enabled - get smmu state + * + * @Return true if smmu is enabled, false if disabled + */ +bool ipa_wigig_is_smmu_enabled(void); + +/* + * ipa_wigig_reg_intf - Client should call this function to + * register interface + * + * Note: Should not be called from atomic context + * + * @Return 0 on success, negative on failure + */ +int ipa_wigig_reg_intf(struct ipa_wigig_reg_intf_in_params *in); + +/* + * ipa_wigig_dereg_intf - Client Driver should call this + * function to deregister before unload and after disconnect + * + * @Return 0 on success, negative on failure + */ +int ipa_wigig_dereg_intf(const char *netdev_name); + +/* + * ipa_wigig_conn_rx_pipe - Client should call this + * function to connect the rx (UL) pipe + * + * @in: [in] input parameters from client + * @out: [out] output params to client + * + * Note: Non SMMU mode only, Should not be called from atomic context + * + * @Return 0 on success, negative on failure + */ +int ipa_wigig_conn_rx_pipe(struct ipa_wigig_conn_rx_in_params *in, + struct ipa_wigig_conn_out_params *out); + +/* + * ipa_wigig_conn_rx_pipe_smmu - Client should call this + * function to connect the rx (UL) pipe + * + * @in: [in] input parameters from client + * @out: [out] output params to client + * + * Note: SMMU mode only, Should not be called from atomic context + * + * @Return 0 on success, negative on failure + */ +int ipa_wigig_conn_rx_pipe_smmu(struct ipa_wigig_conn_rx_in_params_smmu *in, + struct ipa_wigig_conn_out_params *out); + +/* + * ipa_wigig_conn_client - Client should call this + * function to connect one of the tx (DL) pipes when a WIGIG client connects + * + * @in: [in] input parameters from client + * @out: [out] output params to client + * + * Note: Non SMMU mode only, Should not be called from atomic context + * + * @Return 0 on success, negative on failure + */ +int ipa_wigig_conn_client(struct ipa_wigig_conn_tx_in_params *in, + struct ipa_wigig_conn_out_params *out); + +/* + * ipa_wigig_conn_client_smmu - Client should call this + * function to connect one of the tx (DL) pipes when a WIGIG client connects + * + * @in: [in] input parameters from client + * @out: [out] output params to client + * + * Note: SMMU mode only, Should not be called from atomic context + * + * @Return 0 on success, negative on failure + */ +int ipa_wigig_conn_client_smmu(struct ipa_wigig_conn_tx_in_params_smmu *in, + struct ipa_wigig_conn_out_params *out); + +/* + * ipa_wigig_disconn_pipe() - Client should call this + * function to disconnect a pipe + * + * @client: [in] pipe to be disconnected + * + * Note: Should not be called from atomic context + * + * Returns: 0 on success, negative on failure + */ +int ipa_wigig_disconn_pipe(enum ipa_client_type client); + +/* + * ipa_wigig_enable_pipe() - Client should call this + * function to enable IPA offload data path + * + * @client: [in] pipe to be enabled + * Note: Should not be called from atomic context + * + * Returns: 0 on success, negative on failure + */ + +int ipa_wigig_enable_pipe(enum ipa_client_type client); + +/* + * ipa_wigig_disable_pipe() - Client should call this + * function to disable IPA offload data path + * + * @client: [in] pipe to be disabled + * Note: Should not be called from atomic context + * + * Returns: 0 on success, negative on failure + */ +int ipa_wigig_disable_pipe(enum ipa_client_type client); + +/* + * ipa_wigig_tx_dp() - transmit tx packet through IPA to 11ad HW + * + * @dst: [in] destination ipa client pipe to be used + * @skb: [in] skb to be transmitted + * + * Returns: 0 on success, negative on failure + */ +int ipa_wigig_tx_dp(enum ipa_client_type dst, struct sk_buff *skb); + +/** + * ipa_wigig_set_perf_profile() - Client should call this function to + * set IPA clock bandwidth based on data rates + * + * @max_supported_bw_mbps: [in] maximum bandwidth needed (in Mbps) + * + * Returns: 0 on success, negative on failure + */ +int ipa_wigig_set_perf_profile(u32 max_supported_bw_mbps); + +#else /* IS_ENABLED(CONFIG_IPA3) */ +static inline int ipa_wigig_init(struct ipa_wigig_init_in_params *in, + struct ipa_wigig_init_out_params *out) +{ + return -EPERM; +} + +static inline int ipa_wigig_cleanup(void) +{ + return -EPERM; +} + +static inline bool ipa_wigig_is_smmu_enabled(void) +{ + return -EPERM; +} + +static inline int ipa_wigig_reg_intf(struct ipa_wigig_reg_intf_in_params *in) +{ + return -EPERM; +} + +static inline int ipa_wigig_dereg_intf(const char *netdev_name) +{ + return -EPERM; +} + +static inline int ipa_wigig_conn_rx_pipe( + struct ipa_wigig_conn_rx_in_params *in, + struct ipa_wigig_conn_out_params *out) +{ + return -EPERM; +} + +static inline int ipa_wigig_conn_rx_pipe_smmu( + struct ipa_wigig_conn_rx_in_params_smmu *in, + struct ipa_wigig_conn_out_params *out) +{ + return -EPERM; +} + +static inline int ipa_wigig_conn_client( + struct ipa_wigig_conn_tx_in_params *in, + struct ipa_wigig_conn_out_params *out) +{ + return -EPERM; +} + +static inline int ipa_wigig_conn_client_smmu( + struct ipa_wigig_conn_tx_in_params_smmu *in, + struct ipa_wigig_conn_out_params *out) +{ + return -EPERM; +} + +static inline int ipa_wigig_disconn_pipe(enum ipa_client_type client) +{ + return -EPERM; +} + +static inline int ipa_wigig_enable_pipe(enum ipa_client_type client) +{ + return -EPERM; +} + +static inline int ipa_wigig_disable_pipe(enum ipa_client_type client) +{ + return -EPERM; +} + +static inline int ipa_wigig_tx_dp(enum ipa_client_type dst, + struct sk_buff *skb) +{ + return -EPERM; +} + +static inline int ipa_wigig_set_perf_profile(u32 max_supported_bw_mbps) +{ + return -EPERM; +} +#endif /* IS_ENABLED(CONFIG_IPA3) */ +#endif /* _IPA_WIGIG_H_ */ diff --git a/include/linux/kdp.h b/include/linux/kdp.h new file mode 100644 index 000000000000..57325cea00ac --- /dev/null +++ b/include/linux/kdp.h @@ -0,0 +1,216 @@ +#ifndef __KDP_H__ +#define __KDP_H__ + +#ifndef __ASSEMBLY__ +#include +#include +#include + +#define __kdp_ro __section(".kdp_ro") +#define __lsm_ro_after_init_kdp __section(".kdp_ro") + +#define CRED_JAR_RO "cred_jar_ro" +#define TSEC_JAR "tsec_jar" +#define VFSMNT_JAR "vfsmnt_cache" + +struct kmem_cache; + +enum __KDP_CMD_ID { + KDP_INIT = 0x00, + //SET_VERIFIED = 0x01, // for BL. change to 0x00 + JARRO_TSEC_SIZE = 0x02, + SET_SLAB_RO = 0x03, + SET_FREEPTR = 0x04, + PREPARE_RO_CRED = 0x05, + SET_CRED_PGD = 0x06, + SELINUX_CRED_FREE = 0x07, + PGD_RWX = 0x08, + MARK_PPT = 0x09, + PROTECT_SELINUX_VAR = 0x0A, + SET_CRED_UCOUNTS = 0x0B, + NS_INIT = 0x10, + SET_NS_BP = 0x11, + SET_NS_DATA = 0x12, + SET_NS_ROOT_SB = 0x13, + SET_NS_SB_VFSMOUNT = 0x14, + SET_NS_FLAGS = 0x15, +#ifdef CONFIG_KDP_TEST + TEST_INIT = 0x16, + TEST_GET_PAR = 0x17, + TEST_EXIT = 0x18, +#endif + SET_NS_USERNS = 0x20, +}; + +//kernel/cred.c +enum __CRED_CMD_ID { + CMD_COPY_CREDS = 0, + CMD_COMMIT_CREDS, + CMD_OVRD_CREDS, +}; + +enum _KMEM_TYPE { + UNKNOWN_JAR_TYPE = 0, + CRED_JAR_TYPE, + TSEC_JAR_TYPE, + VFSMNT_JAR_TYPE +}; + +struct kdp_init { + u64 _srodata; + u64 _erodata; + u64 init_mm_pgd; + u32 credSize; + u32 sp_size; + u32 pgd_mm; + u32 uid_cred; + u32 euid_cred; + u32 gid_cred; + u32 egid_cred; + u32 bp_pgd_cred; + u32 bp_task_cred; + u32 type_cred; + u32 security_cred; + u32 usage_cred; + u32 cred_task; + u32 mm_task; + u32 pid_task; + u32 rp_task; + u32 comm_task; + u32 bp_cred_secptr; + u32 task_threadinfo; + u64 verifiedbootstate; + struct { + u64 selinux_enforcing_va; + u64 ss_initialized_va; + } selinux; +}; + +extern bool kdp_enable; +extern void __init kdp_init(void); +extern int get_kdp_kmem_cache_type(const char *name); +extern bool is_kdp_kmem_cache(struct kmem_cache *s); +extern bool is_kdp_kmem_cache_name(const char *name); + +#ifdef CONFIG_KDP_CRED +/***************** KDP_CRED *****************/ +struct ro_rcu_head { + /* RCU deletion */ + union { + int non_rcu; /* Can we skip RCU deletion? */ + struct rcu_head rcu; /* RCU deletion hook */ + }; + void *bp_cred; + void *reflected_cred; +}; + +struct cred_param { + struct cred_kdp *cred; + struct cred_kdp *cred_ro; + void *use_cnt_ptr; + void *sec_ptr; + unsigned long type; + union { + void *task_ptr; + u64 use_cnt; + }; +}; + +#define PROTECT_INIT 1 +#define PROTECT_KMEM 2 + +#define GET_ROCRED_RCU(cred) \ +( \ + ((u64)cred == (u64)&init_cred)? \ + (struct ro_rcu_head *)((atomic_t *)init_cred_kdp.use_cnt + 1): \ + (struct ro_rcu_head *)((atomic_t *)((struct cred_kdp *)cred)->use_cnt + 1) \ +) + +extern struct cred init_cred; +extern struct cred_kdp init_cred_kdp; +extern struct task_security_struct init_sec; +struct filename; + +extern void __init kdp_cred_init(void); +extern void __init kdp_do_early_param_setup(char *param, char *val); + +// match for kernel/cred.c function +extern inline void set_cred_subscribers(struct cred *cred, int n); + +extern void put_rocred_rcu(struct rcu_head *rcu); +extern void kdp_put_cred_rcu(struct cred *cred, void *put_cred_rcu); +extern unsigned int kdp_get_usecount(struct cred *cred); +extern void kdp_usecount_inc(struct cred *cred); +extern unsigned int kdp_usecount_inc_not_zero(struct cred *cred); +extern unsigned int kdp_usecount_dec_and_test(struct cred *cred); +extern void kdp_set_cred_non_rcu(struct cred *cred, int val); + +// linux/cred.h +extern int security_integrity_current(void); +extern struct cred *prepare_ro_creds(struct cred *old, int kdp_cmd, u64 p); + +extern void kdp_assign_pgd(struct task_struct *p); +extern inline int kdp_restrict_fork(struct filename *path); +extern void kdp_free_security(unsigned long tsec); + +extern int is_kdp_protect_addr(unsigned long addr); +extern void set_rocred_ucounts(struct cred *cred, struct ucounts *new_ucounts); +#endif /* CONFIG_KDP_CRED */ + +#ifdef CONFIG_KDP_NS +/***************** KDP_NS *****************/ +struct ns_param { + u32 ns_buff_size; + u32 ns_size; + u32 bp_offset; + u32 sb_offset; + u32 flag_offset; + u32 data_offset; + u32 userns_offset; +}; + +/* Populate all superblocks required for NS Protection */ +enum __KDP_SB { + KDP_SB_ROOTFS = 0, + KDP_SB_ODM, + KDP_SB_SYS, + KDP_SB_VENDOR, + KDP_SB_ART, + KDP_SB_CRYPT, + KDP_SB_DEX2OAT, + KDP_SB_ADBD, + KDP_SB_SYS_EXT, + KDP_SB_MAX +}; + +/* fs/pnode.h */ +#define IS_MNT_SHARED(m) (((struct kdp_mount *)(m))->mnt->mnt_flags & MNT_SHARED) +#define CLEAR_MNT_SHARED(m) kdp_clear_mnt_flags(((struct kdp_mount *)(m))->mnt,MNT_SHARED) +#define IS_MNT_UNBINDABLE(m) (((struct kdp_mount *)(m))->mnt->mnt_flags & MNT_UNBINDABLE) +#define IS_MNT_MARKED(m) (((struct kdp_mount *)(m))->mnt->mnt_flags & MNT_MARKED) +#define SET_MNT_MARK(m) kdp_set_mnt_flags(((struct kdp_mount *)(m))->mnt,MNT_MARKED) +#define CLEAR_MNT_MARK(m) kdp_clear_mnt_flags(((struct kdp_mount *)(m))->mnt,MNT_MARKED) +#define IS_MNT_LOCKED(m) (((struct kdp_mount *)(m))->mnt->mnt_flags & MNT_LOCKED) + +struct mount; +struct vfsmount; +struct super_block; +struct path; +extern void __init kdp_mnt_init(void); +extern void __init kdp_init_mount_tree(struct vfsmount *mnt); + +extern int kdp_mnt_alloc_vfsmount(struct mount *mnt); +extern void kdp_set_ns_data(struct vfsmount *mnt,void *data); +inline extern void kdp_set_mnt_root_sb(struct vfsmount *mnt, struct dentry *mnt_root, struct super_block *mnt_sb); +extern void kdp_set_mnt_flags(struct vfsmount *mnt, int flags); +inline extern void kdp_clear_mnt_flags(struct vfsmount *mnt,int flags); +inline extern void kdp_assign_mnt_flags(struct vfsmount *mnt, int flags); +extern void kdp_set_mnt_userns(struct vfsmount *mnt, struct user_namespace *userns); + +extern bool is_kdp_vfsmnt_cache(unsigned long addr); +extern void kdp_free_vfsmount(void *objp); + +#endif /* CONFIG_KDP_NS */ + +#endif //__ASSEMBLY__ +#endif //__KDP_H__ diff --git a/include/linux/leds-qti-flash.h b/include/linux/leds-qti-flash.h index 337ba0369f91..898387bb9f9f 100644 --- a/include/linux/leds-qti-flash.h +++ b/include/linux/leds-qti-flash.h @@ -27,6 +27,10 @@ int qti_flash_led_prepare(struct led_trigger *trig, int options, int *max_current); int qti_flash_led_set_param(struct led_trigger *trig, struct flash_led_param param); +#if IS_ENABLED(CONFIG_SENSORS_STK6D2X) || IS_ENABLED(CONFIG_SENSORS_TSL2511) +int qti_flash_led_set_strobe_sel(struct led_trigger *trig, + int strobe_sel); +#endif #else static inline int qti_flash_led_prepare(struct led_trigger *trig, int options, int *max_current) @@ -39,5 +43,13 @@ static inline int qti_flash_led_set_param(struct led_trigger *trig, { return -EINVAL; } + +#if IS_ENABLED(CONFIG_SENSORS_STK6D2X) +static inline int qti_flash_led_set_strobe_sel(struct led_trigger *trig, + int strobe_sel); +{ + return -EINVAL; +} +#endif #endif #endif diff --git a/include/linux/leds-s2mpb02.h b/include/linux/leds-s2mpb02.h new file mode 100644 index 000000000000..d20eb9ca4b4e --- /dev/null +++ b/include/linux/leds-s2mpb02.h @@ -0,0 +1,172 @@ +/* + * leds-S2MPB02.h - Flash-led driver for Samsung S2MPB02 + * + * Copyright (C) 2014 Samsung Electronics + * XXX + * + * 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 driver is based on leds-max77804.h + */ + +#ifndef __LEDS_S2MPB02_H__ +#define __LEDS_S2MPB02_H__ + +#define S2MPB02_FLED_CHANNEL_1 /* GPIOs connected to FLASH_EN1, TORCH_EN1 */ +//#define S2MPB02_FLED_CHANNEL_2 /* GPIOs connected to FLASH_EN2, TORCH_EN2 */ +//#define S2MPB02_FLED_CONTROLED_BY_GPIO 1 + +#define S2MPB02_FLASH_TORCH_CURRENT_MAX 0xF +#define S2MPB02_TIMEOUT_MAX 0xF + +/* S2MPB02_LV_SEL_VOUT */ +#define S2MPB02_LV_SEL_VOUT_MASK 0x07 +#define S2MPB02_LV_SEL_VOLT(mV) \ + ((mV) <= 2700 ? 0x00 : \ + ((mV) <= 3400 ? ((mV) - 2700) / 100 : 0x07)) + +#define S2MPB02_FLASH_MASK 0xF0 +#define S2MPB02_TORCH_MASK 0x0F + +#define S2MPB02_FLED_ENABLE 1 +#define S2MPB02_FLED_DISABLE 0 +#define S2MPB02_FLED_ENABLE_SHIFT 7 + +#define S2MPB02_FLED_FLASH_MODE 0 +#define S2MPB02_FLED_TORCH_MODE 1 +#define S2MPB02_FLED_MODE_SHIFT 6 + +/* S2MPB02_LV_EN */ +#define S2MPB02_FLED_CTRL1_LV_EN_MASK 0x08 +#define S2MPB02_FLED_CTRL1_LV_ENABLE 1 +#define S2MPB02_FLED_CTRL1_LV_DISABLE 0 + +#define S2MPB02_FLED_ENABLE_MODE_MASK 0xC0 +#define S2MPB02_FLED2_MAX_TIME_MASK 0x1F +#define S2MPB02_FLED2_MAX_TIME_CLEAR_MASK 0x04 +#define S2MPB02_FLED2_MAX_TIME_EN_MASK 0x01 +#define S2MPB02_FLED2_IRON2_MASK 0xC0 + +#define S2MPB02_FLASH_STEP_MA 100 +#define S2MPB02_TORCH_STEP_MA 20 + +enum s2mpb02_led_id { + S2MPB02_FLASH_LED_1, + S2MPB02_TORCH_LED_1, + S2MPB02_LED_MAX, +}; + +enum s2mpb02_flash_current { + S2MPB02_FLASH_OUT_I_100MA = 1, + S2MPB02_FLASH_OUT_I_200MA, + S2MPB02_FLASH_OUT_I_300MA, + S2MPB02_FLASH_OUT_I_400MA, + S2MPB02_FLASH_OUT_I_500MA, + S2MPB02_FLASH_OUT_I_600MA, + S2MPB02_FLASH_OUT_I_700MA, + S2MPB02_FLASH_OUT_I_800MA, + S2MPB02_FLASH_OUT_I_900MA, + S2MPB02_FLASH_OUT_I_1000MA, + S2MPB02_FLASH_OUT_I_1100MA, + S2MPB02_FLASH_OUT_I_1200MA, + S2MPB02_FLASH_OUT_I_1300MA, + S2MPB02_FLASH_OUT_I_1400MA, + S2MPB02_FLASH_OUT_I_1500MA, + S2MPB02_FLASH_OUT_I_MAX, +}; + +enum s2mpb02_torch_current { + S2MPB02_TORCH_OUT_I_20MA = 1, + S2MPB02_TORCH_OUT_I_40MA, + S2MPB02_TORCH_OUT_I_60MA, + S2MPB02_TORCH_OUT_I_80MA, + S2MPB02_TORCH_OUT_I_100MA, + S2MPB02_TORCH_OUT_I_120MA, + S2MPB02_TORCH_OUT_I_140MA, + S2MPB02_TORCH_OUT_I_160MA, + S2MPB02_TORCH_OUT_I_180MA, + S2MPB02_TORCH_OUT_I_200MA, + S2MPB02_TORCH_OUT_I_220MA, + S2MPB02_TORCH_OUT_I_240MA, + S2MPB02_TORCH_OUT_I_260MA, + S2MPB02_TORCH_OUT_I_280MA, + S2MPB02_TORCH_OUT_I_300MA, + S2MPB02_TORCH_OUT_I_MAX, +}; + +enum s2mpb02_flash_timeout { + S2MPB02_FLASH_TIMEOUT_62P5MS, + S2MPB02_FLASH_TIMEOUT_125MS, + S2MPB02_FLASH_TIMEOUT_187P5MS, + S2MPB02_FLASH_TIMEOUT_250MS, + S2MPB02_FLASH_TIMEOUT_312P5MS, + S2MPB02_FLASH_TIMEOUT_375MS, + S2MPB02_FLASH_TIMEOUT_437P5MS, + S2MPB02_FLASH_TIMEOUT_500MS, + S2MPB02_FLASH_TIMEOUT_562P5MS, + S2MPB02_FLASH_TIMEOUT_625MS, + S2MPB02_FLASH_TIMEOUT_687P5MS, + S2MPB02_FLASH_TIMEOUT_750MS, + S2MPB02_FLASH_TIMEOUT_812P5MS, + S2MPB02_FLASH_TIMEOUT_875MS, + S2MPB02_FLASH_TIMEOUT_937P5MS, + S2MPB02_FLASH_TIMEOUT_1000MS, + S2MPB02_FLASH_TIMEOUT_MAX, +}; + + +enum s2mpb02_torch_timeout { + S2MPB02_TORCH_TIMEOUT_1S, + S2MPB02_TORCH_TIMEOUT_2S, + S2MPB02_TORCH_TIMEOUT_3S, + S2MPB02_TORCH_TIMEOUT_4S, + S2MPB02_TORCH_TIMEOUT_5S, + S2MPB02_TORCH_TIMEOUT_6S, + S2MPB02_TORCH_TIMEOUT_7S, + S2MPB02_TORCH_TIMEOUT_8S, + S2MPB02_TORCH_TIMEOUT_9S, + S2MPB02_TORCH_TIMEOUT_10S, + S2MPB02_TORCH_TIMEOUT_11S, + S2MPB02_TORCH_TIMEOUT_12S, + S2MPB02_TORCH_TIMEOUT_13S, + S2MPB02_TORCH_TIMEOUT_14S, + S2MPB02_TORCH_TIMEOUT_15S, + S2MPB02_TORCH_TIMEOUT_16S, + S2MPB02_TORCH_TIMEOUT_MAX, +}; + +enum s2mpb02_led_turn_way { + S2MPB02_LED_TURN_WAY_I2C, + S2MPB02_LED_TURN_WAY_GPIO, + S2MPB02_LED_TURN_WAY_MAX, +}; + +struct s2mpb02_led { + const char *name; + int id; + int brightness; + int timeout; + const char *default_trigger; /* Trigger to use */ + uint32_t gpio; +}; + +struct s2mpb02_led_platform_data { + int num_leds; + struct s2mpb02_led leds[S2MPB02_LED_MAX]; +}; + +extern int s2mpb02_led_en(int mode, int onoff, enum s2mpb02_led_turn_way turn_way); +extern ssize_t s2mpb02_store(const char *buf); +extern ssize_t s2mpb02_show(char *buf); + +#if defined(CONFIG_SAMSUNG_SECURE_CAMERA) +extern int s2mpb02_ir_led_init(void); +extern int s2mpb02_ir_led_current(int32_t current_value); +extern int s2mpb02_ir_led_pulse_width(int32_t width); +extern int s2mpb02_ir_led_pulse_delay(int32_t delay); +extern int s2mpb02_ir_led_max_time(int32_t max_time); +#endif + +#endif diff --git a/include/linux/mfd/firmware/max77705C_pass2_specific.h b/include/linux/mfd/firmware/max77705C_pass2_specific.h new file mode 100644 index 000000000000..e35a939219d0 --- /dev/null +++ b/include/linux/mfd/firmware/max77705C_pass2_specific.h @@ -0,0 +1,6635 @@ +const uint8_t BOOT_FLASH_FW_PASS2[] = { +0xc1, 0x66, 0xf1, 0xce, 0x6d, 0x60, 0x15, 0x02, +0x01, 0x22, 0x21, 0x70, 0x01, 0x02, 0x03, 0x04, +0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, +0x0d, 0x0e, 0x0f, 0x00, 0x6d, 0x60, 0x15, 0x02, +0x03, 0x00, 0x00, 0x00, 0xbc, 0x69, 0x0e, 0x89, +0x99, 0xe5, 0x00, 0xf9, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0x20, 0xa9, 0xe8, +0x91, 0x2c, 0xdf, 0x69, 0xb7, 0x95, 0x9d, 0xa4, +0x69, 0x33, 0xed, 0x4a, 0x1d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x54, +0xe8, 0xa5, 0x65, 0x55, 0x54, 0x26, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0x37, +0x81, 0x8a, 0x5e, 0xf8, 0x01, 0x9e, 0xdf, 0x0f, +0x6b, 0x7a, 0x9d, 0x35, 0x5d, 0x47, 0xe1, 0x99, +0x4f, 0x83, 0xf6, 0x5c, 0xeb, 0x51, 0xeb, 0x42, +0x4d, 0xb3, 0x4d, 0x18, 0x23, 0x58, 0xdd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0x3d, 0xc9, 0xc9, 0x16, 0x6a, 0xad, 0xa7, +0xbf, 0x49, 0xc9, 0x64, 0x23, 0x20, 0xbc, 0x30, +0x33, 0xee, 0x20, 0xe0, 0xa4, 0x76, 0x16, 0x2d, +0xaf, 0x5f, 0x14, 0x9d, 0x67, 0xcb, 0xbe, 0xe2, +0x3f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0x79, 0x0a, 0x0e, 0x92, 0xd7, +0xed, 0x6b, 0xca, 0x7e, 0x69, 0x18, 0xdc, 0x4e, +0xe1, 0x31, 0xb9, 0xce, 0x9b, 0x56, 0x03, 0x68, +0x74, 0xf4, 0x10, 0x9b, 0x4d, 0x91, 0x48, 0xc2, +0x25, 0x19, 0x13, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0xd5, 0x1b, 0xcc, +0xf9, 0xd2, 0x96, 0xe1, 0x27, 0x38, 0x81, 0xe1, +0xe2, 0x19, 0xeb, 0x40, 0x37, 0xc3, 0x8e, 0x2a, +0x20, 0x3a, 0x7b, 0xc8, 0x2c, 0x7f, 0x71, 0x0c, +0xea, 0xf2, 0x94, 0x5b, 0x9d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0xe7, +0xf0, 0x0a, 0x75, 0x06, 0x95, 0xe9, 0xe9, 0x79, +0xe9, 0x43, 0xdc, 0x0d, 0xf7, 0x70, 0xb6, 0x45, +0xef, 0xdf, 0xd9, 0x08, 0x4f, 0xc2, 0x4f, 0x55, +0xdb, 0x05, 0x9e, 0xf8, 0xe7, 0x09, 0xdd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0x18, 0x36, 0x7e, 0x71, 0xce, 0x28, 0x65, +0x07, 0x71, 0xcd, 0x25, 0x70, 0xe0, 0x4d, 0x66, +0xaa, 0x1f, 0x84, 0x0e, 0x4f, 0x76, 0x40, 0xb6, +0xa1, 0x47, 0xed, 0x64, 0xca, 0x71, 0xb7, 0xfb, +0x55, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0xad, 0xe8, 0x87, 0xa7, 0xd4, +0xac, 0xa2, 0x10, 0xfa, 0x0b, 0x36, 0x97, 0xdd, +0x75, 0x29, 0xc4, 0x0e, 0x11, 0x4b, 0xde, 0x55, +0x65, 0x92, 0xdf, 0xcf, 0x97, 0x52, 0x76, 0xf3, +0xcb, 0x1d, 0x20, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x18, 0x10, 0x80, +0x00, 0xc3, 0xfd, 0x26, 0x0e, 0x9b, 0xdf, 0xb4, +0x19, 0x1f, 0x50, 0xb3, 0x77, 0x9f, 0xac, 0x6f, +0x59, 0x7c, 0x83, 0xd7, 0x23, 0x01, 0xe9, 0x4d, +0xcb, 0x12, 0xa3, 0x55, 0x09, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0xf7, +0x1d, 0xdf, 0x84, 0x5d, 0x89, 0x75, 0x0b, 0xa3, +0x8c, 0xb7, 0x19, 0x3d, 0xb0, 0x6b, 0xfb, 0x7c, +0xb1, 0x05, 0x31, 0x30, 0x65, 0x21, 0x0c, 0x28, +0xc5, 0x5a, 0x2a, 0xc7, 0x8e, 0x06, 0xd3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x93, 0x70, 0x6c, 0x03, 0x11, 0x9d, 0xf1, +0x97, 0xed, 0x63, 0xf3, 0x61, 0xc9, 0x76, 0x80, +0xf8, 0x86, 0xce, 0xfa, 0x05, 0x64, 0xf5, 0xd9, +0x36, 0xbc, 0xc7, 0x0f, 0xc0, 0xec, 0xf6, 0xd1, +0x20, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0xed, 0x32, 0x94, 0x3b, 0x48, +0x6d, 0xdd, 0x35, 0xad, 0x93, 0xf2, 0x86, 0xbe, +0xc0, 0x0e, 0xa9, 0xcc, 0x49, 0x98, 0x39, 0x7d, +0x2a, 0x90, 0x34, 0x70, 0xd8, 0xf1, 0x4b, 0x9c, +0x66, 0xc3, 0xf4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0x84, 0x2f, 0xaa, +0x52, 0x1a, 0xb9, 0xf7, 0xc4, 0x3e, 0xcc, 0x99, +0xd4, 0x80, 0x60, 0x66, 0x02, 0xa2, 0x72, 0x04, +0xc6, 0x92, 0x13, 0x42, 0x95, 0x5f, 0x03, 0x66, +0xac, 0x78, 0x4b, 0x89, 0x4d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x27, +0x17, 0x2e, 0x32, 0x26, 0xdd, 0x25, 0x15, 0x92, +0xe9, 0xb1, 0xb0, 0x86, 0x78, 0xdd, 0xba, 0xfa, +0xf6, 0xc7, 0x56, 0x97, 0x49, 0xd6, 0xd2, 0xff, +0x7d, 0x80, 0x4a, 0x14, 0x72, 0xfd, 0x9a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0x0e, 0x6f, 0xe4, 0x30, 0xf5, 0x9f, 0x82, +0xad, 0x86, 0x7d, 0xa0, 0x4b, 0x20, 0x23, 0x84, +0x97, 0x65, 0x5c, 0x73, 0x78, 0x2f, 0x95, 0xf8, +0xed, 0x9a, 0x42, 0x4c, 0x72, 0xc1, 0x55, 0x6c, +0x55, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0x07, 0x31, 0x34, 0x84, 0xe7, +0x9e, 0xfc, 0x79, 0x91, 0xd7, 0xaa, 0x5b, 0x60, +0xb1, 0x3e, 0x56, 0x3d, 0xd8, 0xb5, 0xf1, 0x45, +0x06, 0xd9, 0xbb, 0x40, 0xa9, 0x36, 0x32, 0x8d, +0x4d, 0x42, 0xa1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0x30, 0x4c, 0x71, +0x4d, 0x65, 0x8c, 0x38, 0x36, 0xd0, 0xca, 0xfc, +0x56, 0xbb, 0x1d, 0xa9, 0x95, 0xd8, 0x32, 0x6f, +0x31, 0xec, 0x94, 0x01, 0xdb, 0x68, 0x74, 0xfc, +0x4a, 0x83, 0xbd, 0xcf, 0xa4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x6d, +0x0c, 0xb2, 0xc4, 0xd8, 0xc0, 0xa8, 0xce, 0x8e, +0x03, 0x4d, 0x69, 0x15, 0xae, 0x8e, 0xe3, 0x4c, +0x37, 0xbc, 0xe2, 0x4f, 0x70, 0x7a, 0xd3, 0xa6, +0xdb, 0x51, 0xf5, 0x57, 0xf0, 0x23, 0x76, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0x89, 0xc4, 0xc7, 0x67, 0x3a, 0x4d, 0x96, +0x32, 0x43, 0xd9, 0xc0, 0x25, 0xdb, 0xa3, 0xb4, +0xfd, 0xc1, 0xe3, 0xb3, 0x61, 0xf6, 0x81, 0xa5, +0xef, 0x17, 0x0c, 0x3f, 0xe7, 0x1a, 0x99, 0x48, +0x9f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0xd6, 0x22, 0x7e, 0x95, 0x90, +0x04, 0x0a, 0x9d, 0x22, 0x14, 0x15, 0xd0, 0xa2, +0x70, 0x4e, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x32, 0xb3, 0x8d, 0x3f, +0x18, 0x37, 0x53, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0x60, 0xd9, 0xf7, +0xa2, 0x30, 0x62, 0x34, 0x05, 0xfc, 0xc5, 0x31, +0xf5, 0x5e, 0xcf, 0x3d, 0x85, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, +0x05, 0x4d, 0x8d, 0xba, 0xb0, 0x14, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0xc4, +0xb4, 0xe3, 0x49, 0xfc, 0x51, 0xae, 0xf2, 0xa5, +0xb1, 0x17, 0x12, 0xfe, 0x05, 0x9d, 0x77, 0x0b, +0x44, 0x07, 0x49, 0x80, 0x9b, 0x11, 0x19, 0x70, +0x5f, 0x50, 0xca, 0x66, 0x7a, 0xed, 0x2e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0xdb, 0x14, 0xad, 0x0d, 0xb4, 0x0c, 0x4d, +0x37, 0xaa, 0x03, 0x6b, 0x40, 0x13, 0xd0, 0xf9, +0x18, 0x1e, 0x33, 0xa9, 0x2c, 0x15, 0x7a, 0xd2, +0x42, 0x09, 0x7a, 0xbf, 0x59, 0xc9, 0x35, 0x15, +0xb3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0xd0, 0xff, 0x02, 0xf6, 0x38, +0xca, 0x5f, 0x7d, 0x32, 0x6e, 0x70, 0x33, 0xc5, +0x2c, 0x1a, 0x5a, 0x40, 0x4d, 0x82, 0x24, 0x36, +0xa6, 0x20, 0xd7, 0x7f, 0x75, 0x16, 0x0a, 0x72, +0x46, 0x5f, 0x69, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0xc2, 0x21, 0x14, +0xb4, 0x9a, 0x62, 0x2b, 0x90, 0x83, 0x82, 0x2d, +0x24, 0x67, 0x8d, 0x34, 0xdc, 0x7d, 0xb1, 0x2e, +0x7a, 0x8b, 0x7e, 0x8f, 0x57, 0xb2, 0xb7, 0x2b, +0x87, 0x56, 0x69, 0x8e, 0xfd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0xa8, +0xa4, 0x90, 0x2c, 0xae, 0xa0, 0xf6, 0xa5, 0x91, +0xa2, 0xa3, 0x52, 0xbe, 0x2d, 0xee, 0x1d, 0xcf, +0x31, 0x9d, 0x67, 0xcf, 0xa7, 0x4b, 0x43, 0x08, +0x19, 0x32, 0xe8, 0xb1, 0x8c, 0xd7, 0xaf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0x09, 0x1f, 0xa5, 0xdf, 0x8b, 0x72, 0x2d, +0x87, 0xb5, 0x18, 0xc3, 0xec, 0x7b, 0x9d, 0xc0, +0xc4, 0x5a, 0xc3, 0x13, 0xa2, 0xc3, 0x63, 0x08, +0x59, 0xbb, 0xe6, 0x3e, 0xdc, 0xce, 0xb5, 0xbf, +0x26, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0xa2, 0x88, 0x20, 0xef, 0xb7, +0x53, 0x8e, 0x27, 0x75, 0x37, 0x6a, 0x4c, 0x61, +0xf6, 0x32, 0xc9, 0x66, 0xf6, 0x2c, 0xbf, 0x81, +0x9d, 0x66, 0xff, 0xb9, 0x1e, 0x40, 0xd0, 0xbb, +0xb4, 0x7a, 0x90, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0xbb, 0x0f, 0x19, +0xcd, 0x6e, 0x0c, 0x96, 0x64, 0xbd, 0xe7, 0xbc, +0xb3, 0x7c, 0x9d, 0x20, 0xd5, 0xf8, 0x64, 0xfa, +0x13, 0x27, 0xb2, 0x57, 0xdc, 0xf1, 0x7d, 0xb7, +0x2a, 0x82, 0xef, 0xd9, 0xb3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0x5e, +0x94, 0xb2, 0xb9, 0x24, 0xca, 0x70, 0xa2, 0x28, +0xcf, 0x4e, 0x1d, 0x97, 0x5c, 0x7c, 0x09, 0x7b, +0x45, 0xbb, 0x2d, 0x9f, 0x74, 0x81, 0x4b, 0xeb, +0xad, 0x06, 0x20, 0x47, 0x68, 0x68, 0x98, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x43, 0xc2, 0x7d, 0xd4, 0xac, 0x1f, 0x6a, +0x82, 0x5f, 0xb3, 0x07, 0x3c, 0xa6, 0x28, 0xab, +0x40, 0xfd, 0x04, 0xe5, 0x54, 0x09, 0xc6, 0x13, +0x84, 0x87, 0x2a, 0x2a, 0xb1, 0xfa, 0x01, 0x99, +0x32, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x32, 0x31, 0x81, 0x42, 0xea, +0xa8, 0x27, 0xc8, 0xe8, 0x3d, 0x7c, 0x96, 0xa3, +0x10, 0x97, 0x00, 0xf8, 0xb6, 0xb2, 0x9e, 0x3f, +0x9f, 0x1c, 0x25, 0x52, 0x2d, 0xdf, 0xc7, 0x9b, +0xbb, 0xd1, 0x4d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0x53, 0xb0, 0xaa, +0xb2, 0x09, 0x88, 0x39, 0xe2, 0xfb, 0xde, 0xc7, +0x48, 0xb6, 0x44, 0xdd, 0x5c, 0xb6, 0x6b, 0x51, +0xdf, 0x03, 0xed, 0x81, 0xf2, 0x0a, 0xaa, 0x9c, +0x48, 0x80, 0x0a, 0x2f, 0xdf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0x99, +0xbe, 0x69, 0x1e, 0xd3, 0x75, 0x7b, 0x84, 0x3e, +0x08, 0xb9, 0x55, 0x01, 0xfd, 0x9c, 0xe3, 0x4f, +0x68, 0xce, 0x52, 0x06, 0xb2, 0x68, 0x79, 0x54, +0xac, 0x2d, 0xb3, 0xb3, 0x84, 0xab, 0x17, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0xc0, 0xa4, 0xee, 0xd2, 0x61, 0xfe, 0x5e, +0xb2, 0xef, 0x9b, 0x6f, 0x4e, 0x9d, 0x8e, 0x5b, +0xf0, 0x95, 0x3e, 0x6b, 0x79, 0x6f, 0x85, 0x14, +0x35, 0xac, 0x72, 0xb8, 0xff, 0x94, 0xf9, 0x82, +0x85, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0xb9, 0x85, 0xdf, 0x22, 0x31, +0xc2, 0x2f, 0x7f, 0x1d, 0x5b, 0x1f, 0x94, 0x00, +0x8b, 0xac, 0xd5, 0x98, 0xe1, 0x7a, 0x6f, 0x8d, +0x54, 0x23, 0x0e, 0xa1, 0x04, 0x8f, 0x75, 0xd3, +0xc0, 0xa0, 0xe4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0x0b, 0xf7, 0x50, +0x2c, 0x79, 0xae, 0xd4, 0x86, 0xb6, 0x92, 0xf2, +0xe2, 0x36, 0x59, 0x71, 0x55, 0x05, 0x7f, 0x34, +0x8f, 0x37, 0x04, 0x70, 0x5a, 0xbb, 0x71, 0x0c, +0x1f, 0xc7, 0x03, 0x7d, 0xf1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x4b, +0x51, 0xa1, 0xd4, 0xfc, 0x4f, 0xef, 0x57, 0x08, +0x95, 0x5c, 0x1f, 0x82, 0x93, 0x23, 0x95, 0x69, +0x5e, 0x7d, 0x62, 0x12, 0x0f, 0x7f, 0xad, 0x19, +0xc6, 0xdd, 0x3a, 0xea, 0xee, 0x58, 0xa3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0x48, 0x6b, 0x14, 0xce, 0xea, 0x30, 0x14, +0x6a, 0x84, 0x67, 0x95, 0x66, 0x31, 0x6d, 0xe2, +0x9d, 0x85, 0x08, 0xf1, 0xe6, 0x93, 0x4e, 0x46, +0x16, 0xd4, 0x3b, 0x89, 0x34, 0xad, 0x46, 0xf3, +0x2b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0xe3, 0x36, 0x3a, 0x0d, 0xf1, +0x3c, 0xbe, 0xc4, 0xb9, 0x62, 0x07, 0xa2, 0xec, +0x52, 0x3a, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x11, 0xa2, 0x9a, 0x0e, +0xcb, 0xc9, 0x20, 0x6a, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0x9b, 0xa5, 0x6f, +0x1e, 0xe4, 0xdd, 0x74, 0x54, 0xb2, 0x29, 0x8c, +0x32, 0x59, 0xae, 0x39, 0x05, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x6c, +0x5f, 0x92, 0xf5, 0x1b, 0x9e, 0x55, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0xc5, +0xd6, 0xb3, 0xf5, 0x80, 0x8d, 0x9f, 0xf8, 0x62, +0x6c, 0xea, 0xaa, 0x4d, 0xa1, 0xcf, 0xdf, 0xf0, +0xab, 0xb2, 0x4c, 0x96, 0x9b, 0x66, 0x89, 0x46, +0x61, 0xac, 0xf3, 0xe0, 0xf1, 0xa4, 0x38, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0x2a, 0xa5, 0x3c, 0x28, 0x70, 0x65, 0x0f, +0xaa, 0x0e, 0x0c, 0x75, 0xe9, 0x42, 0xca, 0xde, +0x3a, 0x54, 0x1b, 0xec, 0x41, 0x0f, 0xf0, 0x75, +0x41, 0x50, 0xa0, 0xfc, 0x6e, 0xb6, 0xb7, 0x37, +0x3e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0x29, 0x74, 0x73, 0x68, 0xc3, +0x40, 0xff, 0x7a, 0xd2, 0x69, 0x35, 0x41, 0x2b, +0xb2, 0xc7, 0xb4, 0x91, 0x85, 0x4b, 0x16, 0x51, +0x02, 0xcf, 0x64, 0xa7, 0x3b, 0x1a, 0xb3, 0x0e, +0x5e, 0xb0, 0xf0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0x07, 0x66, 0xd5, +0xc7, 0x2a, 0x76, 0x1c, 0xad, 0x25, 0x83, 0x46, +0xd6, 0xbe, 0xf9, 0x01, 0x99, 0x7d, 0xbd, 0x4c, +0x57, 0xd1, 0x0b, 0x2e, 0x5e, 0xca, 0xa3, 0xe0, +0xbf, 0xdf, 0x43, 0x74, 0xa8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0x04, +0x32, 0x5c, 0x98, 0x03, 0x1b, 0x63, 0x44, 0x6b, +0x1a, 0xe1, 0x7b, 0x71, 0xc7, 0xed, 0xb5, 0x03, +0x64, 0x05, 0x4c, 0x18, 0x6a, 0x3a, 0x97, 0x65, +0x12, 0xc7, 0x1a, 0x57, 0xd7, 0xc7, 0xd9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0x46, 0x01, 0x24, 0x8d, 0x85, 0xd9, 0xcc, +0xd0, 0x57, 0x0a, 0x3f, 0xdd, 0x10, 0x3d, 0x57, +0xc1, 0x75, 0xcc, 0xe1, 0x62, 0x13, 0x8a, 0x9d, +0x96, 0x36, 0x0a, 0xd0, 0x24, 0xf1, 0x39, 0xa9, +0xcd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0x3f, 0x20, 0x23, 0x90, 0x0c, +0x00, 0xc2, 0xa1, 0xf6, 0xda, 0x13, 0xba, 0x07, +0xe4, 0x71, 0x63, 0xb3, 0x5a, 0x4f, 0x27, 0x9b, +0xd4, 0x5a, 0x04, 0x72, 0xf6, 0xf7, 0x5d, 0xcd, +0xbd, 0x03, 0x49, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0x8b, 0xa5, 0x28, +0x91, 0x57, 0xe3, 0xbd, 0xd5, 0xbe, 0x17, 0x56, +0x59, 0x06, 0xf2, 0x03, 0xb3, 0xe1, 0x84, 0x82, +0x56, 0xbd, 0xb3, 0xf7, 0x08, 0xac, 0x7c, 0x50, +0xd6, 0x52, 0x8e, 0x46, 0xed, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0x00, +0x20, 0xde, 0x7c, 0xfd, 0x53, 0x2b, 0xa3, 0x1b, +0x9c, 0xfc, 0xf9, 0x52, 0x09, 0xcf, 0x0e, 0xdb, +0x81, 0x28, 0x33, 0x39, 0x5d, 0xc7, 0xee, 0xfb, +0xb9, 0xd5, 0xb5, 0x32, 0x54, 0xa6, 0xf3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0x52, 0xa8, 0x98, 0x29, 0x6e, 0x50, 0x65, +0x5d, 0xea, 0xb7, 0xa8, 0x96, 0x5c, 0x0c, 0xe2, +0x29, 0x52, 0x08, 0x3e, 0x40, 0x0a, 0xb3, 0xfc, +0x35, 0xf7, 0xf5, 0xbd, 0x14, 0x0d, 0xf4, 0x41, +0xa7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0xa2, 0xaf, 0xe0, 0x2f, 0x1e, +0x02, 0x35, 0xb9, 0x70, 0x34, 0xd7, 0x12, 0x1b, +0xc1, 0xd2, 0xab, 0x32, 0x7c, 0x6f, 0xa2, 0xa4, +0x67, 0x66, 0x55, 0x50, 0x26, 0x9d, 0xd3, 0x8d, +0x0b, 0xb8, 0x5d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0x93, 0x24, 0xfd, +0x6f, 0x19, 0xb7, 0x91, 0x01, 0xc0, 0xf8, 0x0e, +0xa4, 0x1a, 0x31, 0x4b, 0xed, 0x7d, 0x1e, 0xda, +0x75, 0x61, 0x55, 0x7c, 0x1c, 0x97, 0xd3, 0x8f, +0x32, 0x18, 0x76, 0x58, 0xd4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0xb0, +0xfc, 0xb1, 0x36, 0xcb, 0x36, 0xd6, 0xeb, 0x76, +0x4c, 0x85, 0x71, 0xfb, 0x50, 0xbf, 0x8e, 0x33, +0xa2, 0x54, 0x37, 0x83, 0xc2, 0xbc, 0xb6, 0x3e, +0x6c, 0xee, 0x8a, 0x23, 0xd7, 0xda, 0x76, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0xa8, 0x0f, 0x4a, 0x6d, 0xed, 0x6f, 0x6f, +0x84, 0xa1, 0x39, 0x07, 0x9e, 0x43, 0x38, 0xdf, +0x76, 0x90, 0xfd, 0xb2, 0x4e, 0x86, 0xa6, 0x9d, +0xb9, 0x6b, 0x0d, 0xb4, 0xe0, 0x3b, 0x89, 0xd2, +0x7a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0xe0, 0x80, 0xb6, 0x3a, 0x92, +0x94, 0x05, 0xaf, 0x06, 0xbf, 0x7d, 0xd0, 0x50, +0xa8, 0xf4, 0x39, 0xfa, 0x9e, 0x20, 0x54, 0x4d, +0x18, 0xef, 0x11, 0x54, 0x23, 0x0e, 0xf1, 0x48, +0x0f, 0x64, 0xfb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0x96, 0x75, 0x20, +0x0a, 0xda, 0xda, 0x46, 0x8a, 0x95, 0x48, 0xa6, +0x21, 0xee, 0xc1, 0xd0, 0x6d, 0x84, 0x0a, 0xcd, +0xa3, 0x17, 0xa8, 0xdc, 0x44, 0x6c, 0x18, 0xd3, +0xa7, 0xa7, 0x23, 0xaa, 0xcf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0x0d, +0x76, 0xdf, 0xaf, 0x20, 0x5a, 0x07, 0xaa, 0x90, +0xd7, 0x6a, 0x93, 0xc4, 0xbd, 0x44, 0x27, 0x9f, +0xf7, 0xa3, 0xee, 0x34, 0x0e, 0xfd, 0xcb, 0xa5, +0xa1, 0x3d, 0xd2, 0x29, 0x7a, 0x33, 0xd3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x7f, 0x00, 0xe0, 0xc4, 0xf9, 0x99, 0x13, +0x29, 0xa9, 0xc8, 0xdc, 0x5c, 0x5a, 0xcb, 0xbc, +0xc6, 0x1c, 0x89, 0xa0, 0xaf, 0x90, 0xdd, 0x14, +0xa4, 0xbb, 0xba, 0xd9, 0xfe, 0x47, 0x6f, 0x3d, +0xf5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x54, 0x55, 0xfe, 0x87, 0x16, +0x21, 0x3f, 0x13, 0xd3, 0x0d, 0x5a, 0xe6, 0x27, +0xaa, 0xa8, 0xec, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xc3, 0x88, 0x27, 0x2f, +0x4a, 0x85, 0x6e, 0xa3, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0x97, 0xa2, 0x16, +0x1b, 0xec, 0xf9, 0x7f, 0x8e, 0xf6, 0x0d, 0x22, +0x8f, 0xb8, 0x03, 0xa3, 0xcb, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x31, +0x1c, 0xbf, 0x35, 0x22, 0x19, 0x78, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0xed, +0x66, 0x9e, 0xac, 0x81, 0x9b, 0x5d, 0x76, 0xa9, +0xfa, 0x8b, 0xfe, 0x30, 0xd2, 0xd9, 0x42, 0x2c, +0x15, 0x05, 0x24, 0x35, 0x52, 0x5d, 0xa3, 0x3f, +0x28, 0x0b, 0x26, 0xcc, 0xf9, 0xdd, 0xfe, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0xfc, 0x00, 0x83, 0xbe, 0xff, 0xcd, 0x28, +0xc7, 0x5b, 0xe3, 0xf8, 0xa0, 0x8d, 0x70, 0x60, +0x97, 0x1d, 0xba, 0xed, 0x34, 0x5f, 0x28, 0x9c, +0x89, 0xa8, 0x73, 0x63, 0x90, 0x17, 0x04, 0xc6, +0x78, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0xe4, 0x2f, 0x0c, 0x5d, 0xca, +0xf5, 0xad, 0x68, 0x21, 0xd9, 0x7a, 0x12, 0x35, +0xef, 0x2f, 0xf3, 0x27, 0x9f, 0xaf, 0x02, 0x77, +0xb0, 0xa5, 0x84, 0xb6, 0x63, 0xc9, 0x44, 0xe7, +0x1f, 0x4f, 0x4e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0xd1, 0x24, 0x1f, +0xb5, 0x2f, 0x89, 0x23, 0xf5, 0x38, 0x37, 0x79, +0x76, 0xfb, 0x5f, 0x5c, 0xc0, 0x16, 0x78, 0xb6, +0x51, 0x7a, 0x6f, 0x79, 0x22, 0x05, 0x51, 0x08, +0xf0, 0x88, 0x65, 0xb6, 0xdd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x91, +0xd1, 0xce, 0xde, 0xb8, 0x65, 0xf5, 0xd3, 0x23, +0xf4, 0x78, 0x35, 0x85, 0x71, 0x89, 0x83, 0x3e, +0x0a, 0x4e, 0x2b, 0xb0, 0x7b, 0x39, 0xe0, 0x82, +0xd4, 0x1b, 0x98, 0x5b, 0x07, 0x28, 0x12, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0x4b, 0xc9, 0x1f, 0x52, 0xac, 0x53, 0xaf, +0x5b, 0x21, 0x21, 0xaf, 0xec, 0xd6, 0x02, 0x1d, +0xd9, 0x16, 0x24, 0xaa, 0xe2, 0x36, 0xa5, 0x16, +0x12, 0x56, 0xe7, 0xb4, 0xbf, 0x89, 0x6c, 0x83, +0x38, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0xe6, 0xaf, 0xe5, 0x6e, 0x76, +0x8f, 0xe7, 0xc4, 0x3c, 0xa1, 0x63, 0x03, 0xc1, +0xe0, 0x6e, 0x2c, 0x05, 0xa0, 0x26, 0xf3, 0x8c, +0x43, 0xe3, 0x02, 0x36, 0x71, 0xc2, 0xcf, 0x48, +0xf6, 0x18, 0x02, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0x71, 0x7c, 0xfb, +0x12, 0x36, 0xfe, 0x61, 0x6d, 0xcb, 0xb2, 0x2a, +0x61, 0x58, 0xed, 0xce, 0x75, 0xae, 0x54, 0x89, +0x93, 0xc7, 0xca, 0x59, 0x39, 0x83, 0x3f, 0xf1, +0x52, 0x82, 0xab, 0x9a, 0xb9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0x10, +0xec, 0xd8, 0xa4, 0x24, 0x00, 0x6e, 0x17, 0x80, +0x86, 0x02, 0xa2, 0x97, 0xe7, 0x25, 0x35, 0x47, +0x55, 0x03, 0xce, 0x9e, 0xdb, 0x56, 0x43, 0xdc, +0x59, 0x5b, 0xa2, 0xf0, 0xb2, 0xc5, 0x2a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0x88, 0x47, 0xb6, 0xcd, 0xdc, 0xd0, 0x33, +0xb5, 0xac, 0xd0, 0xb6, 0x73, 0x08, 0x77, 0x37, +0x98, 0xd2, 0xdf, 0x57, 0x61, 0x0d, 0x25, 0x32, +0xe3, 0xb7, 0xad, 0x4b, 0xab, 0xfa, 0x63, 0x7e, +0x9e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0x3a, 0xbb, 0x24, 0x9d, 0x6d, +0xcd, 0x06, 0x34, 0xbe, 0xa0, 0xf4, 0x3a, 0x99, +0xf4, 0x1c, 0x6d, 0x98, 0x1c, 0x2f, 0x61, 0x57, +0xac, 0x75, 0xd4, 0x7a, 0x08, 0x20, 0x1f, 0xbb, +0x85, 0x5c, 0x12, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0xe6, 0xaf, 0xce, +0x7f, 0x22, 0x70, 0xba, 0x1e, 0x2d, 0x60, 0x52, +0xe1, 0x08, 0x63, 0x4c, 0xf0, 0x63, 0xfd, 0x54, +0xc2, 0x48, 0x09, 0xe3, 0x3a, 0xa1, 0xe5, 0xf3, +0x74, 0x06, 0x1e, 0xa4, 0xb5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0xf3, +0x7a, 0xc6, 0xe1, 0x1b, 0x37, 0xe6, 0x95, 0x53, +0x84, 0x48, 0x9c, 0xfe, 0xf0, 0x2c, 0xe4, 0xcd, +0xee, 0xfa, 0xee, 0x0d, 0x4b, 0xeb, 0xc7, 0x3f, +0x87, 0x0b, 0xc6, 0xa8, 0xde, 0x68, 0x8d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0x2a, 0x9d, 0x7c, 0x35, 0x2c, 0xa0, 0x1b, +0x2b, 0x1d, 0x5b, 0xbc, 0x2d, 0x86, 0x07, 0x53, +0x7f, 0xe5, 0x10, 0xcd, 0x2e, 0xf2, 0x4e, 0x3d, +0x94, 0x44, 0x8f, 0x1f, 0xf2, 0xd0, 0xd7, 0x67, +0x04, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0xbf, 0x86, 0x3b, 0xc1, 0x1e, +0x4b, 0x37, 0x55, 0x88, 0x8b, 0x88, 0x90, 0xa1, +0x6a, 0xde, 0x75, 0xe3, 0x1f, 0x18, 0x60, 0x88, +0xe7, 0xc5, 0xf0, 0x94, 0x66, 0x1c, 0x99, 0x33, +0xc8, 0xe8, 0x19, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x71, 0x3c, 0x48, +0x66, 0x70, 0x1a, 0x9e, 0x48, 0x53, 0xb7, 0xcd, +0x52, 0xce, 0xe8, 0xc8, 0x59, 0xd3, 0x3c, 0xad, +0xca, 0xe9, 0x14, 0xaf, 0x78, 0x7f, 0xce, 0x25, +0x30, 0xf2, 0x2a, 0xdb, 0xdc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0xf4, +0xa3, 0xf8, 0x85, 0xe3, 0xbb, 0x89, 0xc4, 0xc2, +0x77, 0xfa, 0xfa, 0x31, 0x50, 0x3d, 0x82, 0x79, +0x8f, 0xc9, 0xf8, 0x42, 0x48, 0x03, 0xac, 0x5e, +0xe9, 0x37, 0xa6, 0x75, 0xb4, 0x74, 0x29, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0x11, 0x49, 0x04, 0xe6, 0x4d, 0x3c, 0xb7, +0x14, 0x84, 0x0c, 0x36, 0xff, 0x05, 0x7a, 0xdb, +0x7d, 0xb2, 0xdf, 0x19, 0xf1, 0xfd, 0x9a, 0xdc, +0x87, 0x21, 0xcf, 0x56, 0x7c, 0xe4, 0x7c, 0x06, +0x43, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0xd9, 0xa6, 0x00, 0xdb, 0x54, +0xd6, 0x37, 0xf4, 0x4e, 0x4d, 0xea, 0xeb, 0x31, +0x32, 0xca, 0xc5, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x37, 0xc1, 0x57, 0x3c, +0x1e, 0xbc, 0x45, 0xe9, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x02, 0x0a, 0x3c, +0x0a, 0xb6, 0x3a, 0x88, 0x30, 0x7d, 0x89, 0xd6, +0x0f, 0xfc, 0x0d, 0x21, 0x83, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc9, 0x3a, +0x6b, 0xde, 0x77, 0x48, 0x3c, 0xe1, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x7d, +0x55, 0x9c, 0xd0, 0x81, 0xb7, 0xef, 0x15, 0xfd, +0xab, 0xb9, 0x8e, 0x3b, 0x22, 0x49, 0x20, 0x3a, +0x50, 0x47, 0x11, 0x2c, 0xf5, 0x59, 0x51, 0x88, +0xb5, 0x43, 0x94, 0x67, 0x46, 0x3f, 0x09, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0xa2, 0xd5, 0xb0, 0xce, 0x21, 0x58, 0x38, +0xf9, 0xeb, 0xb3, 0x7a, 0xc2, 0x68, 0x28, 0xd6, +0xd3, 0x18, 0xad, 0x8a, 0x98, 0x7b, 0xc2, 0x66, +0x3c, 0x02, 0xc0, 0x43, 0x17, 0x66, 0x01, 0x3a, +0x67, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0x4d, 0xa8, 0x1c, 0x43, 0x05, +0xa4, 0x40, 0xb7, 0xf5, 0x54, 0x6d, 0xd9, 0x0f, +0xc4, 0x88, 0xbd, 0x07, 0xcc, 0x96, 0xc8, 0x9b, +0x22, 0x95, 0xff, 0x45, 0xc6, 0x35, 0xcd, 0x44, +0x70, 0x71, 0x70, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0x1b, 0x66, 0xa2, +0x0c, 0x7d, 0xef, 0x70, 0x2d, 0xe1, 0x87, 0x94, +0x87, 0xc1, 0x40, 0x7f, 0xf9, 0x5c, 0x05, 0x66, +0x1c, 0x24, 0x27, 0x79, 0xdd, 0x56, 0x90, 0xb2, +0x05, 0x52, 0x16, 0x61, 0xa7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x61, +0xe9, 0x4e, 0x66, 0x72, 0x1d, 0x17, 0x7c, 0xca, +0x04, 0xb1, 0xba, 0x9d, 0xfb, 0x76, 0xc4, 0x64, +0x80, 0xe2, 0x79, 0xa9, 0x7e, 0x6d, 0xa1, 0x75, +0x06, 0x99, 0xb1, 0x8d, 0xe4, 0x4e, 0xcd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0x39, 0x35, 0x69, 0x51, 0xdb, 0x58, 0x9b, +0x60, 0xda, 0x08, 0x53, 0x3d, 0xec, 0x24, 0x45, +0x40, 0x10, 0xba, 0x8c, 0xc4, 0x46, 0x44, 0x20, +0xff, 0x9a, 0xb6, 0xc9, 0x32, 0xf2, 0x56, 0x34, +0x8c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0xd8, 0x36, 0x62, 0x1f, 0x39, +0xef, 0x36, 0x35, 0xd5, 0xfb, 0xf4, 0xa1, 0x52, +0xc0, 0x22, 0xd6, 0xf2, 0x3e, 0xb4, 0xbb, 0xc5, +0x5b, 0xc0, 0xfb, 0xf1, 0x6d, 0x91, 0xf4, 0xa7, +0xea, 0x7a, 0x6a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0xeb, 0x70, 0x0f, +0x86, 0x13, 0x6a, 0x12, 0xa9, 0xac, 0xd3, 0xae, +0x0f, 0xb1, 0x07, 0x18, 0xf9, 0x8d, 0x7f, 0xfe, +0x36, 0x3a, 0x88, 0xc6, 0x05, 0x64, 0xfc, 0xfd, +0x41, 0x08, 0x7f, 0xfc, 0x33, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x06, +0x03, 0xd3, 0x2b, 0x6c, 0xb6, 0x7d, 0xd2, 0x24, +0x55, 0x3c, 0x60, 0xee, 0x19, 0x8c, 0x2c, 0x56, +0x20, 0xae, 0xef, 0x6d, 0xbb, 0x52, 0x35, 0x2e, +0x2e, 0x68, 0x87, 0xe7, 0x65, 0x8c, 0x3a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0xa1, 0x9f, 0x69, 0xc3, 0x03, 0xfc, 0xc3, +0x64, 0x66, 0x8e, 0x11, 0x9c, 0xbb, 0x30, 0x7f, +0x93, 0xa4, 0xe4, 0xfd, 0xb9, 0x2e, 0x31, 0x95, +0x8f, 0xb6, 0xe7, 0xca, 0x56, 0x75, 0x82, 0xf6, +0x3a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0xe7, 0x6c, 0x0d, 0x48, 0xcf, +0x5e, 0x35, 0x75, 0x39, 0x5e, 0xe2, 0x1e, 0x66, +0xf1, 0x54, 0x6e, 0xe4, 0x8c, 0xd3, 0xa8, 0xa1, +0x13, 0xbf, 0x8a, 0xe4, 0xeb, 0x77, 0x12, 0x4b, +0xd0, 0xed, 0x9c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0xf2, 0x70, 0x74, +0x9f, 0x88, 0x25, 0x0f, 0x91, 0x27, 0x65, 0x96, +0x8f, 0x29, 0x88, 0xd7, 0x9e, 0x2a, 0xff, 0x83, +0x57, 0xf8, 0x6d, 0xf7, 0x84, 0x43, 0x17, 0xcc, +0x59, 0xf4, 0xd3, 0x1a, 0x61, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0xf7, +0xf2, 0x45, 0x1f, 0x1a, 0xb7, 0x7a, 0xa6, 0xad, +0xb5, 0x8a, 0x2c, 0xee, 0x89, 0x84, 0x49, 0x15, +0x28, 0x4f, 0xba, 0x95, 0x1d, 0xab, 0x6c, 0xd7, +0xf2, 0x04, 0xe5, 0x94, 0x0b, 0xda, 0x96, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0x8b, 0x61, 0x7f, 0x46, 0x31, 0x92, 0xf4, +0xf3, 0x73, 0x07, 0x20, 0x41, 0x36, 0x92, 0xef, +0xa8, 0x22, 0xea, 0xe0, 0x8e, 0x8b, 0x36, 0x53, +0xda, 0xd4, 0xd2, 0x32, 0x71, 0x87, 0x17, 0x20, +0x56, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0xfa, 0x95, 0xb9, 0x5b, 0x80, +0x1b, 0x83, 0x6e, 0x36, 0x26, 0x70, 0x13, 0x5e, +0x8d, 0xd0, 0xdb, 0xf4, 0x48, 0xc9, 0x9b, 0xb5, +0x9a, 0x11, 0xab, 0x01, 0x9e, 0xa4, 0xb5, 0x4e, +0x98, 0xc9, 0x7d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x5e, 0x1b, 0x6e, +0x75, 0xe7, 0xb9, 0xeb, 0x94, 0x67, 0xef, 0x43, +0x6c, 0x2d, 0x04, 0x2c, 0xcb, 0x2b, 0xd4, 0x5e, +0x11, 0xb4, 0x11, 0x15, 0xb4, 0x49, 0x89, 0x14, +0x52, 0xef, 0x9f, 0xe6, 0x29, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0xe3, +0x7a, 0xf7, 0x70, 0x3f, 0xd3, 0xeb, 0xc2, 0x4e, +0xa9, 0xa1, 0x4c, 0x30, 0x68, 0x40, 0x64, 0xc0, +0x5e, 0xea, 0x82, 0x58, 0x39, 0xb2, 0xa5, 0x81, +0x91, 0xa0, 0xfe, 0x2d, 0x9d, 0x4e, 0x4c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0xbb, 0x21, 0x30, 0xf9, 0x69, 0x02, 0x2a, +0x40, 0xaf, 0x05, 0xc3, 0x62, 0xad, 0x26, 0xa6, +0x69, 0xd9, 0x1f, 0xea, 0xba, 0x6f, 0x0c, 0x1f, +0x6e, 0x42, 0xd7, 0xf6, 0xa4, 0xff, 0x6b, 0x77, +0x7b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0x07, 0xe5, 0xe0, 0x33, 0xd2, +0x36, 0xa8, 0x7b, 0xfa, 0x38, 0x26, 0x51, 0xde, +0xc6, 0xa3, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xe3, 0xf1, 0xdc, 0x2e, +0xf8, 0x39, 0x26, 0x91, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0x2d, 0x13, 0x28, +0x1d, 0x30, 0xbd, 0x67, 0x43, 0xbb, 0x19, 0x00, +0x8e, 0x29, 0x4d, 0x99, 0xed, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xd3, +0x37, 0x0a, 0x08, 0x6a, 0x29, 0x57, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0x11, +0x32, 0xc7, 0x83, 0xa3, 0xb0, 0x25, 0x36, 0x15, +0xc3, 0xdc, 0xaf, 0x29, 0x9c, 0x51, 0x2e, 0x61, +0x78, 0x19, 0x21, 0x4b, 0xcf, 0x3f, 0x5d, 0x39, +0x93, 0xe4, 0xb1, 0x23, 0x67, 0x43, 0x7c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0x3b, 0x8e, 0x24, 0x44, 0x02, 0x47, 0xdc, +0xe0, 0x14, 0x8b, 0xe5, 0x05, 0xdb, 0xfe, 0xf4, +0x97, 0x40, 0xa5, 0xcd, 0x57, 0x02, 0xf8, 0x9e, +0x4e, 0x99, 0xd6, 0x86, 0xf7, 0x46, 0x1f, 0x93, +0x45, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0xf8, 0xa0, 0xbd, 0x5b, 0xf2, +0xde, 0xcc, 0x73, 0x6d, 0x19, 0xab, 0xf7, 0xcf, +0xee, 0x42, 0x01, 0x7e, 0x96, 0x3b, 0xf5, 0xdb, +0x8c, 0xbf, 0x42, 0x92, 0x20, 0x9e, 0xbb, 0x58, +0xe0, 0x6e, 0xc7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0xc3, 0x7f, 0xf6, +0x1f, 0x05, 0x2b, 0x5b, 0x57, 0xdf, 0xdb, 0x7c, +0x24, 0x70, 0xf4, 0x60, 0x9a, 0x1f, 0xf9, 0x32, +0xfd, 0x64, 0x7b, 0x86, 0x47, 0x10, 0xc6, 0x00, +0x97, 0x85, 0x93, 0xd2, 0xfd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x0d, +0x14, 0xe5, 0x54, 0x5a, 0x66, 0x5d, 0xb2, 0x41, +0xf9, 0x7b, 0x09, 0x5b, 0xa9, 0xee, 0x45, 0x16, +0x12, 0x0f, 0xdb, 0xd3, 0xe3, 0xc1, 0x0b, 0x3a, +0x45, 0x7e, 0x70, 0x1e, 0x8e, 0x8a, 0xfe, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0x43, 0x5e, 0x95, 0xd0, 0x1f, 0x46, 0xd2, +0x26, 0x51, 0xb6, 0x98, 0x41, 0x53, 0x6d, 0xbf, +0x16, 0x32, 0x72, 0x06, 0xbe, 0x93, 0xda, 0x36, +0x94, 0xe6, 0x62, 0x8f, 0x95, 0xd7, 0x9b, 0x25, +0x5e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0x41, 0x6f, 0x92, 0x60, 0x93, +0xfd, 0x25, 0xa0, 0x2c, 0x3a, 0x06, 0xd4, 0x95, +0xbe, 0x91, 0x45, 0x19, 0x64, 0xa8, 0x48, 0xb5, +0x93, 0x57, 0xe9, 0xf3, 0xa5, 0x72, 0x50, 0x6b, +0x74, 0x65, 0x24, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0xdf, 0x21, 0x07, +0x15, 0xb4, 0x78, 0x05, 0x94, 0x7d, 0xad, 0xb4, +0xe5, 0x28, 0x7a, 0xa3, 0x33, 0x9b, 0x66, 0x18, +0x10, 0xef, 0x26, 0x1d, 0x1b, 0xce, 0xad, 0x10, +0xcf, 0xbd, 0x57, 0x04, 0xde, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0x01, +0x34, 0x8f, 0xcd, 0x05, 0x8c, 0x59, 0x61, 0xe6, +0x56, 0x1c, 0x86, 0x78, 0x69, 0x6e, 0xfc, 0x1e, +0x7a, 0xd5, 0x96, 0xcd, 0x66, 0x31, 0x11, 0x33, +0xd7, 0xfc, 0x10, 0x76, 0x87, 0xa5, 0x64, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0x87, 0x4b, 0x7c, 0x11, 0x01, 0x98, 0xde, +0xe5, 0x04, 0xf3, 0x18, 0x53, 0x76, 0x63, 0x80, +0xd3, 0xfe, 0x7b, 0x58, 0xc7, 0x20, 0xaf, 0x0f, +0x77, 0x9a, 0x0b, 0x16, 0x27, 0x22, 0xbf, 0x9e, +0x05, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x8c, 0x97, 0xf9, 0xbd, 0xc6, +0x78, 0x33, 0x8f, 0xda, 0xb1, 0xf1, 0x38, 0x77, +0xe8, 0x0c, 0x11, 0x71, 0xaf, 0x89, 0xef, 0xf1, +0xbc, 0x60, 0x92, 0xa4, 0x2c, 0xc5, 0xb4, 0x11, +0xb1, 0xf7, 0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0xa0, 0x21, 0xbd, +0x13, 0x51, 0xe2, 0xcd, 0x84, 0x31, 0xd1, 0xf2, +0x18, 0xef, 0xec, 0x1f, 0xb3, 0x8d, 0xd3, 0x4a, +0xec, 0xf9, 0xcd, 0x5d, 0xa7, 0xc9, 0x34, 0x4e, +0x9f, 0x6d, 0x6d, 0xfd, 0x1d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0xf0, +0xbb, 0x18, 0x6b, 0x08, 0x5e, 0x8f, 0x1d, 0x3c, +0xe4, 0xbd, 0xbd, 0x93, 0xa6, 0x5f, 0xb1, 0xc6, +0xbb, 0x21, 0x71, 0x96, 0xbc, 0x2a, 0x5e, 0xe7, +0xde, 0x12, 0x0c, 0x55, 0xb7, 0x59, 0xfb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0xc8, 0xac, 0xab, 0x29, 0x22, 0x7e, 0xa4, +0xb1, 0xa6, 0xb0, 0xb4, 0x73, 0xca, 0x6a, 0x6b, +0x10, 0xd9, 0x45, 0x85, 0x8e, 0xdb, 0x9d, 0xf4, +0xcb, 0xc1, 0x09, 0x50, 0x78, 0x9a, 0x8a, 0xfe, +0x7e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0xb5, 0x04, 0x0d, 0xf4, 0x50, +0xbe, 0xe3, 0xec, 0xb6, 0x2d, 0xbf, 0xfc, 0x1a, +0x74, 0x43, 0xcf, 0xaa, 0x51, 0x13, 0x71, 0x8f, +0x55, 0x37, 0x91, 0xbe, 0x5b, 0x70, 0x3b, 0xb7, +0xcb, 0x6d, 0xea, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0xf8, 0x98, 0xf7, +0x04, 0x58, 0x60, 0xa9, 0xcc, 0x1f, 0xb5, 0x45, +0xe9, 0x52, 0x6c, 0xd0, 0xeb, 0xbf, 0x70, 0x1a, +0xc0, 0xe7, 0xcd, 0x9e, 0x03, 0x75, 0xb3, 0xda, +0x16, 0xda, 0xa0, 0x36, 0xfb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0xc3, +0x77, 0x7f, 0xe9, 0xb2, 0x53, 0x28, 0xf5, 0x6d, +0x57, 0xdf, 0x79, 0x1c, 0xf7, 0x85, 0x1b, 0x7e, +0xd9, 0x82, 0x18, 0x9a, 0x98, 0x9b, 0xfe, 0x50, +0xb4, 0xb4, 0xdf, 0x18, 0x4e, 0x82, 0x66, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0x90, 0x36, 0x91, 0x17, 0x4d, 0x98, 0xa3, +0xd7, 0xa0, 0xce, 0xb1, 0xc5, 0x2b, 0x83, 0xb6, +0x3b, 0xe7, 0xb9, 0xa9, 0x9b, 0x80, 0x5d, 0x23, +0xac, 0xc5, 0x0f, 0x3d, 0xb9, 0xb5, 0xed, 0x4d, +0x21, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0x39, 0x74, 0x19, 0x0b, 0xd6, +0xe1, 0x2a, 0x5c, 0x62, 0x85, 0xd2, 0x1c, 0xf3, +0xa2, 0xbb, 0xa6, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xeb, 0x30, 0x8d, 0x8a, +0x98, 0x59, 0xbe, 0xa0, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0x7f, 0x6b, 0xfd, +0x74, 0xa9, 0xf1, 0x1b, 0x16, 0x9b, 0x0c, 0x75, +0x5e, 0xdb, 0x4a, 0x0b, 0x8b, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdd, 0x0e, +0x5d, 0xa6, 0x12, 0x62, 0x56, 0xaf, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0x91, +0x6e, 0x16, 0x70, 0x99, 0x2b, 0x11, 0xb2, 0xb5, +0xe5, 0x27, 0x32, 0x11, 0x92, 0x3f, 0x3e, 0xc4, +0x00, 0x69, 0x15, 0x5f, 0x47, 0xd0, 0xde, 0x45, +0xaf, 0x43, 0xc0, 0x60, 0x1a, 0x79, 0x19, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0x6e, 0x3a, 0x40, 0x17, 0xa5, 0x81, 0x21, +0x14, 0xe7, 0xf4, 0x9c, 0x83, 0x43, 0x94, 0xa8, +0xc6, 0xeb, 0x68, 0x04, 0x46, 0xa9, 0x5d, 0xab, +0x39, 0x87, 0x97, 0x4b, 0x85, 0xe5, 0x0c, 0x4e, +0x9b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0x07, 0xb5, 0x95, 0x22, 0x25, +0x47, 0x55, 0x0a, 0x22, 0x82, 0x8b, 0xef, 0x63, +0xbd, 0xc5, 0xa9, 0x18, 0xec, 0xe8, 0xf3, 0xf8, +0xb0, 0x2c, 0x78, 0x54, 0x32, 0x7f, 0x6d, 0xdf, +0xba, 0xe0, 0x53, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0x65, 0x02, 0x54, +0xad, 0x63, 0xfb, 0x74, 0x44, 0xdc, 0x6d, 0xc0, +0xdc, 0xaf, 0xec, 0xfd, 0xe9, 0x04, 0x98, 0x57, +0xbf, 0xd2, 0xdc, 0x29, 0xc6, 0x2b, 0x36, 0xf0, +0xfe, 0x9d, 0x7b, 0x9f, 0x04, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0x1b, +0xa5, 0xa7, 0xb4, 0xdc, 0x30, 0x73, 0x5c, 0x3a, +0x11, 0xe1, 0xb7, 0xc9, 0x8a, 0xc3, 0x23, 0xef, +0xc6, 0xee, 0x80, 0x73, 0x74, 0x5a, 0xf7, 0xbf, +0xe7, 0x73, 0x12, 0x2e, 0xdb, 0x9e, 0xc2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x0b, 0x40, 0x09, 0xac, 0x25, 0x22, 0xb7, +0x8c, 0x12, 0xd9, 0x43, 0xa2, 0x45, 0x09, 0x24, +0x7e, 0x5b, 0xe9, 0x13, 0xf6, 0x13, 0xee, 0xda, +0x04, 0x84, 0xd7, 0x4a, 0x5e, 0x43, 0xa3, 0x7d, +0x77, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0x2f, 0xf4, 0xb2, 0xfe, 0x2b, +0xe0, 0xeb, 0x83, 0xbf, 0x8a, 0xca, 0x1d, 0xc0, +0xd1, 0x8e, 0xe6, 0xc4, 0xc6, 0x7a, 0xa7, 0x7f, +0xa2, 0xf8, 0x2b, 0x8f, 0x2a, 0x43, 0x68, 0x4f, +0xcb, 0x78, 0x5d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x5d, 0x89, 0xdc, +0x49, 0x0c, 0xd2, 0x6c, 0x71, 0x58, 0x22, 0xe8, +0x6d, 0x34, 0xfb, 0xd4, 0xa5, 0x08, 0x29, 0x95, +0xcc, 0x2d, 0x9d, 0xab, 0x92, 0x89, 0x20, 0x6c, +0x5f, 0x68, 0x78, 0x03, 0xbe, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0xda, +0x48, 0x62, 0xfe, 0x90, 0x80, 0x21, 0xad, 0x54, +0xba, 0x53, 0xb7, 0x5a, 0xe1, 0xa5, 0xc6, 0x29, +0xee, 0x91, 0x4b, 0x89, 0x7e, 0x3c, 0x1e, 0x14, +0x9a, 0xbd, 0xc7, 0xab, 0x3d, 0x3e, 0x98, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0x65, 0x32, 0x8c, 0xe2, 0x01, 0x69, 0x67, +0xc0, 0x88, 0xf0, 0xb6, 0xdc, 0xf1, 0x2f, 0xec, +0x43, 0x93, 0x54, 0x95, 0x18, 0xf0, 0x2c, 0x3d, +0x02, 0xf0, 0x65, 0xd9, 0x9e, 0x41, 0x42, 0xc5, +0x79, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0x1c, 0xf3, 0x06, 0x92, 0xc2, +0x6d, 0xc7, 0x3a, 0xd6, 0x2c, 0x2a, 0x14, 0x46, +0x44, 0x12, 0xc6, 0x54, 0x59, 0xb6, 0x05, 0x14, +0x06, 0xa1, 0xdd, 0x6e, 0xcb, 0x20, 0x2f, 0xe9, +0x8d, 0x74, 0x42, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0xc9, 0xaa, 0x55, +0x84, 0x42, 0x6a, 0xf6, 0x84, 0xb0, 0x88, 0x71, +0xb4, 0xf3, 0x7c, 0xd8, 0x46, 0xb3, 0x72, 0x6e, +0xfd, 0x86, 0xec, 0x87, 0xbb, 0xd0, 0x7b, 0xeb, +0x6c, 0xd4, 0x55, 0x30, 0xab, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0xe1, +0x9f, 0x1a, 0x61, 0x90, 0x9a, 0xf5, 0xe6, 0x14, +0x01, 0x50, 0xac, 0x93, 0xe1, 0xce, 0x36, 0xf5, +0xa7, 0xcc, 0xec, 0x5c, 0x16, 0xea, 0x80, 0x89, +0xa0, 0x14, 0xcb, 0x4f, 0x4e, 0xe1, 0xc9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0xb7, 0x4a, 0x47, 0x3a, 0xb0, 0x37, 0xd2, +0x74, 0xf5, 0x7d, 0x0d, 0xf7, 0xbf, 0x1e, 0xd3, +0xa7, 0x02, 0xe3, 0x70, 0x44, 0x69, 0x18, 0x77, +0x3c, 0x85, 0x4d, 0xe8, 0xb4, 0x37, 0x33, 0xe6, +0xed, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0xa7, 0x98, 0x82, 0x3f, 0x3f, +0x0e, 0x2d, 0xd2, 0xe1, 0xe2, 0x46, 0xaf, 0xf6, +0xef, 0x90, 0x83, 0xd3, 0xc8, 0xf5, 0xc7, 0xd4, +0xf7, 0x2d, 0xe0, 0xa3, 0x53, 0xad, 0x2c, 0x9f, +0xca, 0x5a, 0x36, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0x13, 0x19, 0x22, +0xdf, 0x79, 0x01, 0x3a, 0x98, 0xa4, 0x56, 0x4f, +0xe5, 0x7c, 0xc1, 0xb5, 0x9f, 0x28, 0x56, 0x4b, +0x67, 0x91, 0x50, 0x7f, 0x84, 0x61, 0xbe, 0xc4, +0xc0, 0x5f, 0xb2, 0xe6, 0x30, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0xbb, +0xd9, 0x02, 0x67, 0x2a, 0xec, 0x67, 0xeb, 0xfa, +0xb9, 0x4a, 0xec, 0xea, 0xaf, 0xaa, 0x2d, 0xfc, +0x9f, 0xa7, 0xb3, 0x6f, 0xa3, 0xb6, 0x41, 0xb2, +0xc5, 0x6b, 0xcc, 0xa8, 0x8d, 0xb9, 0xb1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0x41, 0x1b, 0x4a, 0xd9, 0x1b, 0x39, 0x3d, +0x45, 0x58, 0x75, 0x91, 0x32, 0x97, 0xef, 0x6d, +0x9d, 0x82, 0x78, 0x62, 0xe1, 0xea, 0xc6, 0xb2, +0xae, 0x0c, 0xbd, 0x0e, 0x16, 0xf5, 0x36, 0xf0, +0xb5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0xae, 0x53, 0xde, 0xf1, 0x8f, +0x51, 0xc7, 0xc9, 0x9b, 0x8c, 0xc8, 0x45, 0x68, +0x32, 0x79, 0xe2, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x89, 0x6e, 0x88, 0x50, +0x9f, 0xa9, 0x0d, 0xd5, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x83, 0x10, 0x37, +0x70, 0xd9, 0x33, 0xee, 0xc4, 0x5b, 0x63, 0xf1, +0x9c, 0x2f, 0x2c, 0xa3, 0x3a, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x79, +0xcb, 0x91, 0x20, 0x91, 0x28, 0xb4, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x28, +0xa7, 0x21, 0xd3, 0xee, 0xf1, 0x83, 0x81, 0xd4, +0xfc, 0x30, 0x1c, 0x44, 0x56, 0x51, 0x61, 0x68, +0x3c, 0x0b, 0x5f, 0x72, 0x54, 0x90, 0x81, 0xc5, +0x3d, 0x89, 0x6a, 0x48, 0x64, 0x74, 0xec, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0x59, 0x74, 0x9b, 0x58, 0xc5, 0x4e, 0x83, +0xca, 0x0f, 0x18, 0x8a, 0x9f, 0x0b, 0x1e, 0xa3, +0x69, 0x4c, 0xbe, 0x2b, 0x11, 0xac, 0x40, 0x7d, +0xc8, 0xec, 0xfc, 0x89, 0xe5, 0xb2, 0xfc, 0xd4, +0x41, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0x0b, 0xba, 0xc0, 0x89, 0x16, +0x77, 0xaa, 0x49, 0xdf, 0x35, 0x1f, 0xa5, 0x6d, +0x16, 0x1f, 0xaf, 0x12, 0x14, 0x7f, 0x45, 0xa9, +0x7b, 0x05, 0xea, 0x98, 0xed, 0x76, 0x88, 0xbb, +0x78, 0x1f, 0xf0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0x04, 0xca, 0xbc, +0x6f, 0x70, 0x62, 0x90, 0x79, 0x18, 0x69, 0x5e, +0x31, 0x1d, 0xca, 0x31, 0xfd, 0x3f, 0x87, 0xbe, +0x43, 0xc2, 0x2a, 0x3e, 0xa3, 0xa1, 0xb8, 0x8f, +0xd5, 0x2b, 0x7a, 0xbb, 0x1d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0x4d, +0xce, 0x0f, 0xef, 0xde, 0x60, 0x7b, 0x7a, 0x76, +0x66, 0xab, 0x55, 0x06, 0x4d, 0x99, 0xd2, 0x05, +0xb0, 0x8c, 0xbf, 0x6d, 0x15, 0xe4, 0x34, 0x1b, +0xf7, 0xf4, 0x0e, 0x49, 0x18, 0x95, 0x6d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0xd0, 0x5c, 0x72, 0x48, 0xee, 0xdd, 0x99, +0xcd, 0x00, 0xc7, 0x10, 0xef, 0xc8, 0x2f, 0xab, +0x33, 0x21, 0x6d, 0x72, 0x6b, 0xdb, 0xad, 0x31, +0x2a, 0xa9, 0xc8, 0x84, 0x00, 0xf4, 0x12, 0xa1, +0x15, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0xd0, 0xcd, 0xba, 0xfb, 0xa1, +0x97, 0xc6, 0xd5, 0x7e, 0x06, 0xfb, 0x02, 0xf5, +0xaf, 0x7d, 0x39, 0x2f, 0x1d, 0x16, 0xe9, 0x8b, +0x2f, 0xfe, 0x97, 0x97, 0x93, 0x52, 0x5f, 0x7d, +0xed, 0x33, 0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0x7c, 0xca, 0x57, +0xdc, 0x57, 0x86, 0x9b, 0xbc, 0xd8, 0xf3, 0x80, +0x7b, 0xe2, 0xd1, 0xd6, 0x11, 0x1a, 0xe3, 0x9a, +0xf5, 0x16, 0x97, 0xb6, 0x93, 0xad, 0xa2, 0xf8, +0xc5, 0x1b, 0xaf, 0xde, 0x73, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0xc8, +0x26, 0xf7, 0x93, 0xc3, 0xd7, 0x53, 0x3e, 0xd3, +0xc9, 0x5b, 0xeb, 0x41, 0xda, 0x2a, 0x4f, 0xc9, +0x5a, 0x9e, 0x2d, 0x76, 0x93, 0xa4, 0xd1, 0x1f, +0x28, 0x5e, 0x72, 0xa0, 0x1a, 0x92, 0xf7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0x8a, 0x47, 0xda, 0xed, 0x20, 0x0b, 0x52, +0xf1, 0x63, 0x0d, 0x7d, 0x79, 0x2f, 0x8b, 0xf5, +0x2e, 0xe3, 0xf7, 0xb6, 0x70, 0x53, 0x63, 0xf5, +0x4f, 0xbe, 0x93, 0xf2, 0x7c, 0xe0, 0xf5, 0xa4, +0x99, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0x9b, 0x57, 0x45, 0xfb, 0x40, +0xd9, 0x81, 0x11, 0x68, 0xd0, 0x2b, 0x25, 0x93, +0x29, 0x15, 0xc5, 0x7e, 0xd6, 0xc0, 0x00, 0x82, +0x53, 0x22, 0xa7, 0x7a, 0xd6, 0x77, 0xca, 0x78, +0x98, 0x9f, 0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0x65, 0x5c, 0xb9, +0x6c, 0xa0, 0x70, 0xc2, 0x18, 0x6a, 0x62, 0x2b, +0x63, 0x95, 0xda, 0x09, 0xf3, 0x2f, 0x9e, 0xfe, +0xd9, 0xee, 0x76, 0x98, 0xcc, 0x6e, 0x4a, 0x69, +0xe9, 0x74, 0x77, 0xaa, 0x1c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0x90, +0x92, 0xb2, 0xb7, 0xfe, 0x18, 0xa8, 0xc3, 0xf6, +0x46, 0xa1, 0x74, 0x91, 0x46, 0xd9, 0x40, 0x7b, +0x56, 0x3e, 0x98, 0x4c, 0xc2, 0x4d, 0xf8, 0x7a, +0x1f, 0x96, 0x2a, 0x26, 0x5f, 0x67, 0x03, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x15, 0xc6, 0x9c, 0x76, 0x6e, 0x75, 0x24, +0x62, 0x37, 0x33, 0x26, 0x13, 0x49, 0xb5, 0x83, +0x10, 0xcd, 0x2a, 0xf6, 0x1d, 0x72, 0x6d, 0xfd, +0x3a, 0x45, 0xee, 0xcd, 0xf9, 0x6e, 0x52, 0xb8, +0x94, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0xd0, 0x02, 0x19, 0x7e, 0x0d, +0xea, 0xda, 0x8f, 0x44, 0x08, 0x18, 0x7d, 0x6c, +0x8f, 0x2b, 0x74, 0xd2, 0x8e, 0x8c, 0xac, 0x74, +0x98, 0xc1, 0xc6, 0x9b, 0x1d, 0x7c, 0x31, 0xcf, +0x2d, 0xc3, 0xb1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0x73, 0xdd, 0xeb, +0x4c, 0xdc, 0x5f, 0x1f, 0x64, 0x77, 0x5b, 0x5e, +0x7c, 0xd6, 0x16, 0xf8, 0x6a, 0x85, 0x12, 0x88, +0x08, 0xd1, 0x03, 0xe3, 0x62, 0x7b, 0x7a, 0xe1, +0x5c, 0x99, 0x20, 0xa9, 0xc0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0x1e, +0x9f, 0xa9, 0xf0, 0x5a, 0x5e, 0xe6, 0xcb, 0x6a, +0xff, 0xac, 0xc0, 0xdd, 0x48, 0xeb, 0xb1, 0x12, +0x14, 0x91, 0x58, 0x2c, 0xbe, 0x40, 0xa0, 0x96, +0x06, 0x43, 0x4e, 0xa0, 0xfe, 0xdd, 0xa6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0x57, 0xca, 0x42, 0xaa, 0x07, 0x1c, 0x0e, +0x3e, 0x7e, 0xb6, 0x9c, 0xd1, 0x18, 0xbd, 0x24, +0x05, 0xf7, 0xaf, 0x38, 0xd6, 0xe3, 0x4d, 0x15, +0x3e, 0x0e, 0x44, 0x0c, 0x67, 0x3b, 0x3d, 0x4c, +0x49, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0x07, 0x3c, 0xc9, 0x7b, 0xd9, +0xe8, 0xd7, 0xe0, 0x60, 0x34, 0x77, 0xb6, 0xc7, +0xe7, 0xcf, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x52, 0x2d, 0x82, 0x71, +0x5c, 0xf5, 0xca, 0x65, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0x48, 0x66, 0xd4, +0xb9, 0x4a, 0xbe, 0x13, 0x93, 0xa6, 0xf1, 0xee, +0xc3, 0x45, 0xef, 0x53, 0x75, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d, 0x6b, +0x08, 0xe3, 0x7f, 0x7e, 0xb8, 0xb6, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0x8c, +0xa7, 0x4b, 0x64, 0x8b, 0x45, 0x8e, 0xed, 0x2f, +0xe8, 0xdf, 0xc2, 0x46, 0xdb, 0x66, 0xaa, 0xad, +0xe9, 0x84, 0x23, 0xe0, 0xf9, 0x9a, 0xb8, 0xd9, +0xf7, 0xec, 0x47, 0x56, 0x39, 0x13, 0xc1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0xd1, 0xf6, 0xaf, 0x04, 0x89, 0x45, 0xc5, +0xfc, 0xbe, 0x06, 0x92, 0x89, 0x5d, 0xfe, 0x57, +0x40, 0x98, 0x77, 0x99, 0x5c, 0x4d, 0xc0, 0xef, +0xcf, 0x46, 0x12, 0xfc, 0x11, 0x53, 0x58, 0xf4, +0xbc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0x2b, 0x6f, 0x7f, 0x00, 0x65, +0x15, 0xb8, 0x24, 0xbc, 0x83, 0xe4, 0x24, 0xe4, +0xb3, 0x63, 0x3f, 0xb1, 0x84, 0xcf, 0xef, 0x42, +0xdf, 0xaa, 0x6b, 0x7a, 0x2c, 0xe4, 0x62, 0xe1, +0x41, 0x90, 0x67, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0xa2, 0x01, 0x35, +0xc7, 0x2b, 0xde, 0x85, 0x20, 0xe5, 0x6a, 0x10, +0x66, 0xdb, 0x0c, 0x0c, 0xa8, 0x9f, 0x6b, 0xf5, +0xf9, 0x8c, 0x90, 0x59, 0x9c, 0x24, 0x43, 0x50, +0x07, 0x85, 0x97, 0x53, 0xb6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0x71, +0x16, 0xea, 0x37, 0xf5, 0x82, 0xe2, 0x8a, 0x9a, +0xda, 0x61, 0x6e, 0x01, 0x6c, 0x95, 0xe7, 0xc4, +0x07, 0x4f, 0x8b, 0x29, 0x7c, 0x49, 0x06, 0x9c, +0x56, 0x58, 0x5d, 0x3b, 0x30, 0xba, 0xae, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0xe8, 0x60, 0xdc, 0xef, 0x5a, 0x20, 0xc7, +0x4f, 0x8f, 0x3f, 0x17, 0x32, 0xfd, 0xa8, 0x28, +0x74, 0x19, 0x70, 0x9f, 0xb4, 0xcf, 0xbe, 0x4e, +0x7c, 0x78, 0x16, 0x8c, 0x72, 0x64, 0x5c, 0xcc, +0x61, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0xcd, 0x8e, 0x23, 0xfe, 0x6c, +0x49, 0x10, 0xea, 0xa4, 0xa6, 0xbd, 0x9e, 0x36, +0x8d, 0x2d, 0xc3, 0xf6, 0xe0, 0xcf, 0x96, 0x9e, +0x46, 0xc1, 0x1a, 0x15, 0xb9, 0x12, 0xd5, 0xae, +0x6a, 0x05, 0xee, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0xba, 0xbe, 0x0c, +0xa2, 0x9c, 0x12, 0x5e, 0x66, 0xfe, 0x24, 0x76, +0xe3, 0xc1, 0x1e, 0x06, 0x71, 0xd3, 0xa1, 0x4c, +0x5f, 0x73, 0x69, 0x90, 0xd5, 0x85, 0x52, 0x26, +0x90, 0x97, 0xab, 0x3b, 0x73, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0x9e, +0xd9, 0xd8, 0x64, 0x84, 0x25, 0xf4, 0xc0, 0xbe, +0xf6, 0xef, 0x8b, 0x92, 0xfb, 0x1a, 0x7c, 0xd2, +0x07, 0x76, 0x9a, 0xad, 0x6d, 0xc3, 0x14, 0xb9, +0xab, 0x8b, 0xa8, 0xf6, 0x91, 0x6b, 0x13, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0x50, 0xec, 0x3f, 0x62, 0xb1, 0x24, 0x05, +0xee, 0xe0, 0xef, 0xb1, 0x0f, 0xd2, 0x9b, 0x8f, +0xc0, 0x4f, 0xac, 0xeb, 0x10, 0xe6, 0x00, 0x45, +0x7b, 0xe2, 0x1d, 0xaa, 0xe7, 0x1f, 0xdf, 0x30, +0x46, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x7c, 0xc5, 0x8d, 0x54, 0x75, +0xe4, 0xb1, 0xba, 0xbf, 0xc1, 0x1b, 0x4e, 0xf4, +0xb7, 0x15, 0xa2, 0x92, 0x9e, 0xc5, 0x66, 0x43, +0x44, 0x1b, 0x19, 0x9d, 0x70, 0x3a, 0xbf, 0xb6, +0x67, 0x86, 0xd3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0xc2, 0x77, 0x24, +0x86, 0x9d, 0x63, 0x70, 0x1b, 0x73, 0x02, 0x39, +0x14, 0xa3, 0x83, 0x78, 0xd1, 0xc2, 0x62, 0x1b, +0xcb, 0x7a, 0x59, 0x5a, 0x8c, 0xa1, 0xb2, 0x79, +0x47, 0x51, 0xdd, 0xe7, 0x78, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0x1e, +0xc3, 0x1c, 0xe2, 0x88, 0x38, 0xb4, 0xd9, 0x3d, +0xde, 0xdf, 0x6f, 0xe1, 0xff, 0xe0, 0x01, 0x9c, +0xaf, 0xc5, 0xb6, 0x78, 0x9f, 0x95, 0x0f, 0x69, +0x7c, 0x3e, 0x8e, 0xf8, 0x0b, 0xc4, 0x58, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0xdb, 0xaa, 0xca, 0x22, 0x5d, 0xc9, 0x61, +0x2d, 0xa4, 0x35, 0x8a, 0xc4, 0x43, 0xcd, 0xed, +0xe9, 0x4e, 0x29, 0xb9, 0xff, 0x7e, 0x90, 0x88, +0x01, 0x30, 0x9b, 0x7e, 0x21, 0x34, 0x5f, 0x7f, +0xc5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0x9d, 0xec, 0xe0, 0xe1, 0x0e, +0xbb, 0xa4, 0x5b, 0xae, 0x0f, 0xae, 0x0c, 0xe3, +0x17, 0x4f, 0xf3, 0x37, 0xb2, 0x9a, 0x57, 0x5e, +0x28, 0x76, 0x2a, 0x83, 0x46, 0x5e, 0xbe, 0x64, +0x79, 0x1d, 0xa4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0xc8, 0x33, 0x22, +0xa7, 0x53, 0xfc, 0xaf, 0x02, 0x55, 0xac, 0x1e, +0xf4, 0xdd, 0xba, 0x7c, 0xf6, 0xd8, 0x87, 0x56, +0xa9, 0xe0, 0x29, 0xf5, 0xd1, 0x23, 0x2d, 0xc5, +0xc7, 0x20, 0x55, 0x56, 0x15, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0x20, +0xfd, 0x88, 0x3e, 0x1e, 0x65, 0x6e, 0xb6, 0xb4, +0x72, 0xba, 0x79, 0xb5, 0x61, 0x04, 0x4c, 0xbf, +0x6f, 0x90, 0x4e, 0x66, 0xe7, 0x4e, 0x88, 0x81, +0x49, 0x55, 0x94, 0x4c, 0xc4, 0xc2, 0x85, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0xf1, 0x89, 0x9f, 0x4c, 0x37, 0xb6, 0xa3, +0x51, 0x35, 0x3e, 0x6a, 0x06, 0x96, 0x12, 0xe3, +0xec, 0xfc, 0x76, 0xb2, 0x02, 0x90, 0xa6, 0x5d, +0x33, 0x58, 0x80, 0x4c, 0x6d, 0xce, 0x2b, 0x3e, +0x20, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0x8a, 0x2c, 0xb8, 0xed, 0x3d, +0x20, 0xa8, 0x4c, 0x7d, 0x5c, 0x7c, 0xa7, 0xae, +0x79, 0xfb, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x14, 0xee, 0x0e, 0xc8, +0xe4, 0x1d, 0x74, 0x51, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0x2b, 0xd1, 0xc3, +0xa2, 0x18, 0xcf, 0x6d, 0x5c, 0xc7, 0x2b, 0x53, +0xba, 0xf7, 0x0f, 0x8b, 0x13, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa7, 0x03, +0xcf, 0x3e, 0x88, 0x57, 0x5c, 0x54, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0x63, +0xac, 0xc0, 0xe3, 0x42, 0xb4, 0x9a, 0x02, 0x49, +0x00, 0xc2, 0x75, 0x4f, 0xa3, 0x1e, 0x7e, 0x93, +0xe8, 0x37, 0x79, 0x6b, 0x9f, 0x96, 0x85, 0x5f, +0x43, 0xec, 0x08, 0x8d, 0xcb, 0x5d, 0x59, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0x2e, 0xe2, 0xb1, 0x78, 0xa2, 0xa5, 0x72, +0x50, 0x0f, 0x93, 0xa9, 0x19, 0xce, 0x60, 0x0c, +0x91, 0x18, 0x74, 0x8f, 0xaa, 0xcb, 0x01, 0x88, +0x19, 0xb5, 0xc6, 0x59, 0x1e, 0xce, 0x6d, 0x36, +0xaf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0xb9, 0x62, 0x4b, 0x29, 0x10, +0x10, 0xdd, 0x8d, 0xec, 0x3c, 0xe1, 0x01, 0x0c, +0x17, 0xe7, 0xaf, 0x36, 0xc9, 0xc8, 0xe3, 0x9b, +0xff, 0xf0, 0x2d, 0x59, 0x66, 0xaf, 0x4b, 0x5f, +0x64, 0xc6, 0x4c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0x4d, 0x0c, 0x80, +0xc8, 0x9b, 0x6b, 0xf2, 0xf1, 0x15, 0xfa, 0xaf, +0x02, 0x85, 0xfa, 0xaf, 0x8d, 0x1f, 0xaa, 0xd5, +0xb2, 0x3a, 0xef, 0xab, 0xa8, 0x0b, 0x42, 0x0b, +0x10, 0xdf, 0xe9, 0x3b, 0xd8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0xdd, +0xc6, 0x5c, 0x75, 0x7e, 0xa0, 0x07, 0x81, 0x16, +0xb1, 0xcf, 0xb3, 0x7e, 0xf4, 0xfa, 0x5a, 0x0d, +0xd0, 0x69, 0x97, 0x0d, 0xb0, 0x44, 0x79, 0xae, +0x43, 0x5f, 0xfd, 0x4a, 0x59, 0xba, 0x2b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0x5e, 0xe3, 0xbe, 0xa4, 0x9b, 0xf7, 0x39, +0x93, 0x98, 0x95, 0x9b, 0xb2, 0xe2, 0xde, 0xaa, +0xc4, 0x72, 0x28, 0xe3, 0xe9, 0xad, 0xbd, 0x55, +0xab, 0x2a, 0xeb, 0x6b, 0x24, 0xc5, 0x67, 0x4b, +0xbb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0x01, 0x6e, 0x7a, 0x94, 0x5c, +0x19, 0xc8, 0xe0, 0x7e, 0x65, 0x78, 0xa6, 0xef, +0xb9, 0xa5, 0x87, 0x75, 0x77, 0x49, 0xac, 0x16, +0xa4, 0x37, 0x51, 0xd6, 0x8a, 0x6f, 0x0c, 0x69, +0xce, 0x3b, 0xd6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0xdf, 0x16, 0x6a, +0x35, 0x2a, 0xe5, 0xdf, 0x39, 0x8d, 0x08, 0x36, +0xe0, 0x20, 0x04, 0xe4, 0x2d, 0xce, 0x6e, 0xb8, +0xa8, 0xd9, 0x82, 0x37, 0x94, 0x55, 0xbb, 0x4f, +0x31, 0x2f, 0x16, 0xa8, 0xf9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0xb3, +0x3b, 0xb8, 0x3d, 0x6c, 0x9c, 0x54, 0x1b, 0x8f, +0x4c, 0x1e, 0x5c, 0x17, 0xb6, 0xba, 0x54, 0xd0, +0x6e, 0xda, 0xe1, 0x96, 0xef, 0x4c, 0x3d, 0xf3, +0xc4, 0x66, 0xd6, 0x75, 0x4d, 0xa3, 0xc5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0x2b, 0x6e, 0x91, 0x89, 0x12, 0x85, 0x07, +0x78, 0x8f, 0x3e, 0x98, 0x46, 0x72, 0xe3, 0x72, +0x13, 0x6a, 0x90, 0xcf, 0x21, 0x68, 0xf6, 0x37, +0xe4, 0x52, 0x8f, 0x59, 0xbd, 0x78, 0xa1, 0x19, +0x4e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0xfe, 0x8e, 0x3e, 0xdc, 0x66, +0x73, 0x16, 0x5f, 0x6c, 0x6e, 0xa3, 0x6d, 0x0e, +0xcb, 0x9b, 0xb8, 0x79, 0xa5, 0x05, 0xcc, 0x98, +0xd0, 0x43, 0xa4, 0xbf, 0xb1, 0x38, 0x46, 0xa5, +0x60, 0x5e, 0x5c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0xd7, 0xe8, 0x60, +0x35, 0x84, 0x92, 0xf0, 0xd4, 0xa1, 0x31, 0x0f, +0x36, 0x88, 0xab, 0x56, 0x98, 0xe0, 0xec, 0x0b, +0x6c, 0xe6, 0xb7, 0x1f, 0xd8, 0x92, 0xf8, 0xd8, +0xf9, 0x29, 0xe9, 0x82, 0xf0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0xb0, +0x22, 0xb8, 0xb1, 0xe6, 0xb5, 0x76, 0xe9, 0x5e, +0x94, 0x4f, 0x6f, 0x2a, 0x5f, 0x43, 0xdf, 0x18, +0xaa, 0xe7, 0x12, 0x6f, 0x66, 0x16, 0x2e, 0x7c, +0x94, 0x15, 0x7e, 0x75, 0xdc, 0xc9, 0xce, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0x67, 0xee, 0x27, 0xf8, 0xd6, 0x2e, 0xc3, +0x55, 0xde, 0xb5, 0x20, 0x40, 0xbe, 0x56, 0x42, +0xc1, 0x66, 0x1a, 0x2a, 0x2d, 0x32, 0x9b, 0x65, +0x53, 0xa1, 0x0c, 0xd3, 0x65, 0xe7, 0x11, 0x31, +0x26, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0x79, 0x66, 0xab, 0xb2, 0x46, +0xa2, 0x08, 0x9b, 0x1f, 0x8f, 0x2e, 0xd2, 0xaf, +0x48, 0x9c, 0xa2, 0x02, 0xcd, 0xf6, 0x70, 0x5d, +0x38, 0x44, 0x16, 0xc4, 0xe6, 0x0f, 0xb7, 0x0c, +0xd6, 0x33, 0x5d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0xbd, 0x56, 0xa7, +0xbf, 0x73, 0x00, 0x2c, 0x0d, 0xfa, 0xa8, 0x6b, +0xc4, 0x25, 0xc0, 0x93, 0x50, 0xcc, 0x16, 0x25, +0x77, 0x0d, 0xc7, 0xcd, 0x3f, 0xd6, 0xaf, 0x43, +0xe1, 0x24, 0x7d, 0x5a, 0x66, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0xf3, +0xc2, 0x86, 0x7f, 0xa8, 0x4a, 0x22, 0xf3, 0xc6, +0x62, 0x07, 0x30, 0x32, 0x7f, 0x17, 0xcd, 0xe2, +0x0d, 0x98, 0x31, 0xfc, 0x7e, 0x54, 0x0f, 0x57, +0x98, 0xe1, 0xd2, 0x22, 0x92, 0x84, 0x50, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0x95, 0x48, 0xaa, 0x44, 0xeb, 0x12, 0x05, +0x9e, 0x7c, 0x3a, 0x89, 0x64, 0xd9, 0xcd, 0xc7, +0x57, 0x77, 0x9c, 0xdf, 0xaf, 0x45, 0xa9, 0x4e, +0x6f, 0xdb, 0xcd, 0x47, 0x56, 0x96, 0xc4, 0x59, +0xd0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0x18, 0x97, 0x31, 0xa7, 0x39, +0x37, 0xe4, 0x1f, 0x2f, 0xe6, 0x25, 0xf2, 0x93, +0xd4, 0x06, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x1f, 0xa5, 0x69, 0x13, +0xc1, 0x42, 0x0c, 0xcf, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0xe3, 0x6b, 0xef, +0x54, 0xc2, 0x62, 0x9f, 0x61, 0xbc, 0xba, 0x9b, +0x99, 0x07, 0x29, 0xb4, 0xbd, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4d, 0x5f, +0x0a, 0x68, 0xed, 0xe0, 0x38, 0x60, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0x69, +0x3c, 0x0b, 0x5d, 0xa6, 0xba, 0x8d, 0x5c, 0x26, +0xbb, 0xc4, 0xfb, 0x30, 0x7e, 0xe1, 0xa7, 0xa8, +0xdd, 0x3e, 0x33, 0x0d, 0xc7, 0x06, 0x30, 0xdd, +0x75, 0x5a, 0x39, 0x22, 0xcc, 0xc9, 0x25, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0x27, 0xc7, 0xf4, 0xff, 0xfa, 0x37, 0xd6, +0xda, 0x17, 0x9c, 0x41, 0xa0, 0x4f, 0x35, 0x3d, +0xec, 0x78, 0x78, 0x41, 0x00, 0x21, 0xe7, 0xc5, +0xd0, 0x64, 0xc2, 0x66, 0xc1, 0x35, 0x7b, 0x4f, +0x08, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0x79, 0xf6, 0x91, 0x46, 0xe7, +0x5f, 0xe4, 0xda, 0x8f, 0x13, 0xac, 0x72, 0x4c, +0xd7, 0x1d, 0xed, 0x63, 0x8d, 0xda, 0xdc, 0x14, +0xed, 0x1a, 0x1f, 0x70, 0x42, 0x1c, 0x05, 0xd9, +0x4d, 0x7b, 0x88, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x95, 0x40, 0xe1, +0xb7, 0x91, 0x20, 0x2a, 0x1b, 0x8b, 0x16, 0xa4, +0x28, 0xa7, 0x30, 0x2d, 0x26, 0x84, 0xeb, 0x9c, +0x97, 0x58, 0x54, 0x47, 0x5f, 0x3d, 0x63, 0xd0, +0x2e, 0xcb, 0xca, 0xa2, 0x3f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0x40, +0x40, 0x8b, 0xea, 0x61, 0xdf, 0x05, 0x22, 0xe3, +0xcd, 0x4e, 0x82, 0x27, 0xcd, 0x17, 0x86, 0x52, +0x74, 0x4f, 0x4a, 0xb7, 0x64, 0x9b, 0x81, 0xa3, +0xcc, 0x05, 0x40, 0xd3, 0x33, 0x28, 0xb1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0xda, 0xa6, 0x8c, 0x5a, 0x87, 0x8b, 0x14, +0x8a, 0x41, 0x98, 0x47, 0xce, 0x8a, 0x01, 0xc0, +0x60, 0xe6, 0x36, 0x0f, 0x37, 0x81, 0xdd, 0xb7, +0x7f, 0x86, 0x2a, 0x7f, 0x6a, 0x78, 0x5a, 0x01, +0x28, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0xa4, 0x2c, 0xb2, 0xa4, 0x4d, +0xbf, 0x1f, 0x9a, 0x70, 0x52, 0x2f, 0x9c, 0x9f, +0xcd, 0x08, 0x3a, 0xeb, 0x0e, 0x2a, 0x27, 0x24, +0x21, 0x66, 0x5a, 0x19, 0x68, 0x8c, 0xc2, 0xd8, +0x7a, 0x6a, 0x1d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0xb4, 0x80, 0x0f, +0x8e, 0xf9, 0x12, 0x66, 0xea, 0x4c, 0xb8, 0xe2, +0x64, 0x0c, 0x18, 0x9e, 0x43, 0x13, 0xfa, 0xf7, +0x86, 0xef, 0xec, 0xe6, 0x29, 0x2c, 0xff, 0xa7, +0xb2, 0x7f, 0xe9, 0x3e, 0xa6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0x97, +0x2e, 0x57, 0x00, 0xd2, 0xc2, 0xad, 0xac, 0xde, +0x2e, 0xc3, 0xe4, 0xca, 0x73, 0x68, 0x2a, 0x29, +0xcf, 0x93, 0x0d, 0x5a, 0x08, 0x08, 0x6c, 0x5d, +0x4a, 0xc0, 0xf6, 0x7d, 0x83, 0xee, 0xfb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0xb6, 0x9a, 0xb1, 0x2a, 0x06, 0x8b, 0x6c, +0xec, 0x58, 0x30, 0xdd, 0x4b, 0x7b, 0x07, 0x47, +0x1a, 0x53, 0x8f, 0x41, 0xa2, 0x13, 0x19, 0x28, +0x94, 0xd6, 0x32, 0xba, 0x7f, 0xe7, 0x1f, 0x4a, +0x9e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0x91, 0x24, 0xa8, 0x64, 0xf7, +0xcd, 0x6f, 0x07, 0xa2, 0x7b, 0x16, 0x4b, 0xb4, +0x27, 0x9b, 0xe9, 0xb0, 0x71, 0x48, 0x6a, 0xe0, +0x94, 0x36, 0x68, 0x12, 0xcf, 0xa7, 0xc8, 0xbe, +0xed, 0x10, 0x64, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0x0c, 0xb6, 0x83, +0xf8, 0x40, 0xc6, 0x86, 0x9b, 0x39, 0x8a, 0x04, +0xfc, 0xba, 0x46, 0x2a, 0x28, 0x04, 0x02, 0xf0, +0xad, 0x31, 0x65, 0x44, 0x91, 0x45, 0x16, 0x76, +0x87, 0x98, 0xc3, 0x16, 0x80, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x6e, +0xa2, 0x90, 0xc3, 0xf6, 0xa5, 0xe6, 0xea, 0x5c, +0x43, 0x49, 0x08, 0x09, 0x05, 0x72, 0x55, 0x2e, +0x7a, 0xdc, 0x39, 0x56, 0x48, 0x37, 0x1f, 0xba, +0xf9, 0x6c, 0x61, 0xa7, 0x00, 0xe1, 0x95, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0x04, 0x9a, 0x68, 0xc2, 0x27, 0x0f, 0x57, +0xad, 0xe1, 0xcb, 0xd8, 0x0e, 0x64, 0x54, 0x00, +0x1f, 0x96, 0xb7, 0x83, 0x65, 0x1e, 0x46, 0x12, +0xd6, 0x9b, 0x38, 0x78, 0xc1, 0xfb, 0x01, 0x91, +0x01, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0x52, 0x24, 0x08, 0x9f, 0xbe, +0x09, 0xc3, 0x91, 0x54, 0x12, 0x3b, 0x49, 0xb6, +0xe2, 0x05, 0xcc, 0xb2, 0x9a, 0xc5, 0xfe, 0xe8, +0x20, 0x3e, 0xa6, 0x3b, 0xe6, 0x98, 0x81, 0x1a, +0x03, 0x98, 0x24, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0x6a, 0x96, 0x09, +0x66, 0x5b, 0xe3, 0x3f, 0xa1, 0x06, 0x00, 0x53, +0xbc, 0x71, 0x24, 0x99, 0x20, 0x1f, 0xbe, 0xf1, +0x03, 0xee, 0x85, 0xda, 0xcf, 0x5d, 0x3c, 0xbc, +0x3c, 0x35, 0xdb, 0x43, 0x48, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0x87, +0x68, 0xfe, 0xfc, 0x83, 0x92, 0x6f, 0xe2, 0x04, +0x93, 0xf5, 0x64, 0xca, 0x88, 0x95, 0x42, 0xd6, +0xba, 0xf5, 0x16, 0x84, 0xac, 0x73, 0xa2, 0xd6, +0xf0, 0x78, 0x07, 0x50, 0xe0, 0x73, 0xc7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0x4e, 0x4a, 0xa2, 0xf9, 0x6a, 0x16, 0xfa, +0x68, 0x6f, 0xc7, 0x46, 0x87, 0x8f, 0x1d, 0xaa, +0x8e, 0x07, 0x21, 0x4c, 0x0a, 0x43, 0xdd, 0x7d, +0x11, 0x00, 0x4a, 0xf4, 0xf6, 0x3a, 0x8f, 0x33, +0x10, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0xb5, 0xa1, 0x62, 0x3b, 0x9f, +0x3f, 0xc5, 0x42, 0xa9, 0xc8, 0xc7, 0xf9, 0x37, +0x74, 0xdd, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x43, 0xee, 0xac, 0x12, +0x85, 0xcf, 0x97, 0x54, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0x78, 0x4e, 0xd8, +0x57, 0x2a, 0xbc, 0xf3, 0x60, 0xd3, 0xe9, 0x8c, +0x6d, 0x1a, 0xd1, 0xb7, 0x96, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0x93, +0x4b, 0x6b, 0x53, 0xc5, 0x3b, 0x10, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0xe2, +0x51, 0x45, 0xf0, 0x22, 0xe2, 0xb9, 0x63, 0xab, +0x43, 0x89, 0xfb, 0xcd, 0x19, 0x3c, 0x7d, 0xa4, +0xbd, 0xc0, 0xda, 0xa0, 0xcf, 0x53, 0xf7, 0x30, +0x5c, 0x79, 0xf2, 0x9c, 0x8e, 0x92, 0x0f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0xcb, 0xd7, 0x98, 0x32, 0xe8, 0x8d, 0xcc, +0x90, 0x0f, 0xe0, 0x62, 0xd0, 0x9a, 0xed, 0xac, +0xbc, 0xb6, 0x4e, 0x99, 0xb7, 0xe7, 0x06, 0xac, +0xd0, 0xf8, 0x28, 0xb4, 0x61, 0x49, 0x97, 0xd6, +0x98, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0xa1, 0xd7, 0x0b, 0x67, 0x7f, +0xdc, 0x52, 0xc0, 0xe5, 0xdd, 0xa0, 0x14, 0xe4, +0xa7, 0xe3, 0x42, 0x70, 0x4a, 0x79, 0x52, 0x81, +0xfa, 0x91, 0xa2, 0x98, 0x28, 0x07, 0xe2, 0x4c, +0x55, 0xe4, 0x46, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0xd1, 0xdd, 0xfb, +0x3f, 0xf7, 0xec, 0x64, 0x79, 0x0c, 0x22, 0x6f, +0x8c, 0xe3, 0xb6, 0x3b, 0x83, 0x29, 0x1e, 0x15, +0xa9, 0x8b, 0xb9, 0x48, 0xa7, 0xab, 0x2c, 0x0a, +0x9d, 0x2d, 0xb0, 0x4b, 0x99, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0xf9, +0x2e, 0x32, 0x50, 0x8b, 0xb5, 0x4a, 0x6c, 0xc2, +0x6d, 0x9f, 0x44, 0xb1, 0x35, 0x35, 0xf4, 0x2d, +0x5d, 0x78, 0x70, 0x62, 0x00, 0xad, 0xc7, 0x49, +0xb5, 0x75, 0xa8, 0xf9, 0xf7, 0x51, 0x38, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0x5b, 0x13, 0x27, 0x20, 0x24, 0x07, 0x76, +0x0e, 0xb1, 0x89, 0x68, 0xc6, 0x85, 0x2d, 0x08, +0xca, 0x7c, 0xba, 0x80, 0xe7, 0x1d, 0x1b, 0x79, +0xfa, 0x59, 0x4b, 0x40, 0x7b, 0xf0, 0xf4, 0xf2, +0xad, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0x6f, 0x29, 0xfb, 0x36, 0xb2, +0xb0, 0x38, 0xae, 0xe3, 0x5b, 0xc3, 0x8f, 0x19, +0x10, 0x04, 0xa2, 0xe6, 0xa7, 0xb3, 0x05, 0x7f, +0x90, 0x11, 0xd5, 0x56, 0x00, 0x6e, 0x0e, 0x6e, +0x74, 0xbc, 0x8d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x4e, 0xf4, 0x99, +0xad, 0xd2, 0x19, 0xe6, 0x88, 0xad, 0x09, 0x5e, +0xdc, 0x5b, 0x28, 0xe1, 0x4b, 0xc4, 0x8a, 0x04, +0x83, 0xa5, 0x1b, 0xb2, 0x9b, 0xeb, 0x45, 0xd0, +0xe0, 0x24, 0x64, 0xc2, 0xb1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x94, +0x8d, 0x36, 0xe1, 0x19, 0xb8, 0x37, 0xbe, 0xaf, +0xee, 0x0f, 0x8c, 0x35, 0x1f, 0x0a, 0x37, 0xe8, +0xf1, 0x3b, 0xbb, 0xd4, 0x7d, 0xc1, 0xda, 0xa8, +0x3f, 0x64, 0xbe, 0xb1, 0x4b, 0xf3, 0x31, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0xd6, 0x16, 0x12, 0x8c, 0x59, 0x49, 0x53, +0x3c, 0xb4, 0xa6, 0x5f, 0xee, 0xec, 0x4a, 0x05, +0x4d, 0xc0, 0x6f, 0x60, 0xbf, 0x05, 0xd8, 0x51, +0x77, 0xd9, 0x43, 0x88, 0x90, 0xd0, 0x75, 0x5e, +0x1c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0xf7, 0x44, 0xce, 0x49, 0xe2, +0xac, 0xd9, 0xae, 0xfa, 0xdd, 0x77, 0xf9, 0x9c, +0x95, 0x4d, 0x7a, 0x06, 0x4e, 0x53, 0x6a, 0x8b, +0x97, 0x76, 0x45, 0xdb, 0x4b, 0xf5, 0x8a, 0x65, +0x66, 0x26, 0xb6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0xcc, 0xf8, 0xe9, +0x99, 0xbd, 0xfe, 0x95, 0x7c, 0xb3, 0x62, 0x91, +0x76, 0x28, 0x75, 0x12, 0xeb, 0x8b, 0xc9, 0x7b, +0x5d, 0x8d, 0x48, 0x22, 0xcc, 0xeb, 0x75, 0x20, +0x37, 0x99, 0x4b, 0x32, 0x7d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0xf7, +0x48, 0x72, 0x83, 0x7e, 0x8c, 0x27, 0xf8, 0x8a, +0x2c, 0xcc, 0xad, 0x6e, 0x7a, 0xd1, 0xfe, 0x2d, +0x97, 0xc4, 0xf3, 0x2d, 0x04, 0xff, 0xed, 0x58, +0x84, 0x3b, 0x67, 0x87, 0xec, 0xb0, 0x79, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0xc1, 0xbd, 0xb5, 0xaf, 0xd2, 0xcc, 0x24, +0x52, 0x3e, 0xa0, 0x45, 0xba, 0xf7, 0x52, 0xd8, +0x57, 0x07, 0x21, 0xf8, 0x97, 0x4f, 0x00, 0xa7, +0xdc, 0x64, 0x05, 0x5f, 0xa5, 0xa7, 0x22, 0x8a, +0x5f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0x00, 0x7b, 0x50, 0x6e, 0x23, +0xcb, 0x6d, 0x4c, 0xce, 0x86, 0x02, 0x62, 0x23, +0x04, 0x77, 0x20, 0x4b, 0xfd, 0x1c, 0x2d, 0x2e, +0xd4, 0x18, 0x4f, 0xff, 0x7e, 0x1a, 0xc8, 0x7e, +0x93, 0x31, 0x77, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0x68, 0x52, 0x52, +0xbb, 0x66, 0xf3, 0xe3, 0xc9, 0xd8, 0xdb, 0xb2, +0xcd, 0xdf, 0xd6, 0xf4, 0xdd, 0x3a, 0xe4, 0x1f, +0x26, 0xdb, 0x0e, 0xf5, 0x81, 0x70, 0x56, 0x45, +0x9f, 0x5b, 0x19, 0xa6, 0x44, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0xd2, +0x33, 0x09, 0x8c, 0xb9, 0xc8, 0x85, 0x2b, 0xaf, +0x81, 0xd9, 0x99, 0x2c, 0x35, 0x40, 0xce, 0x24, +0x47, 0x0b, 0x89, 0x8e, 0x7b, 0xd6, 0x35, 0xa4, +0xf3, 0xeb, 0xb3, 0xac, 0x8d, 0xbe, 0x7a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0x54, 0x6a, 0x3e, 0x32, 0xc3, 0x33, 0x5b, +0x82, 0xcb, 0x8a, 0x63, 0x35, 0x27, 0x86, 0x86, +0xf2, 0x03, 0x31, 0xe5, 0xbb, 0x06, 0x46, 0xaf, +0xba, 0x2f, 0x97, 0x5e, 0xe1, 0xce, 0x37, 0x9c, +0xcb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0x28, 0xc5, 0x37, 0x4c, 0x02, +0x1b, 0xd7, 0x85, 0xe8, 0xef, 0x40, 0xa1, 0x98, +0xf0, 0x51, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x47, 0x4a, 0x08, 0xdd, +0xf8, 0x3d, 0xcb, 0x9f, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0x58, 0x53, 0xfc, +0x04, 0x04, 0xb5, 0x01, 0x58, 0x88, 0xd3, 0x4b, +0x7b, 0x84, 0x3c, 0xca, 0xc7, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x20, +0xa3, 0x40, 0x1f, 0xb1, 0xe3, 0x14, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0xdc, +0xb9, 0x34, 0xf2, 0x40, 0x47, 0xde, 0x0c, 0x53, +0xc0, 0x36, 0x3d, 0xe1, 0xa1, 0xaa, 0x40, 0xcb, +0x9a, 0x61, 0xd2, 0x90, 0x67, 0xa1, 0xb2, 0x7c, +0x28, 0x61, 0xda, 0x5f, 0x8b, 0xde, 0x77, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0x56, 0x25, 0xcc, 0xd9, 0xfa, 0x44, 0x14, +0x97, 0x6c, 0x12, 0xaf, 0x5b, 0x00, 0x04, 0x27, +0x7a, 0x0d, 0xbc, 0x19, 0xfc, 0xbe, 0xce, 0xc6, +0xce, 0x7b, 0x85, 0x22, 0xbe, 0xd5, 0xd5, 0xa3, +0x77, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0xc4, 0xc9, 0xf5, 0x30, 0xf8, +0xb3, 0x99, 0xd1, 0xfa, 0xeb, 0x26, 0xdd, 0xb5, +0x91, 0x88, 0xf7, 0x66, 0xff, 0x1e, 0x53, 0xf1, +0xb0, 0x0e, 0x9d, 0x83, 0xbe, 0x4f, 0xce, 0x3b, +0xb6, 0x9f, 0x1c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0xa5, 0xa2, 0x7a, +0x05, 0xf1, 0x7a, 0x2d, 0x90, 0x90, 0xb3, 0xe1, +0x9f, 0x2c, 0x4b, 0x7c, 0x66, 0x5a, 0x25, 0x8b, +0x2e, 0xe0, 0xbf, 0xc3, 0x4f, 0xb8, 0x80, 0x30, +0x60, 0xc5, 0xa4, 0xe1, 0xa6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0xf5, +0xd8, 0x41, 0x1c, 0xe2, 0x44, 0xed, 0xcf, 0xde, +0x06, 0x21, 0x4a, 0x1a, 0x4e, 0xa0, 0x07, 0x8b, +0xac, 0xf8, 0x4e, 0xfb, 0xc5, 0x11, 0xa7, 0x35, +0xf9, 0x24, 0xbb, 0x4d, 0x1c, 0xfc, 0xd5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0x79, 0xf2, 0x94, 0x36, 0x77, 0xf0, 0xa2, +0x27, 0x85, 0x45, 0x59, 0xec, 0x86, 0x0a, 0xfc, +0xf1, 0x73, 0xd3, 0xc0, 0xaa, 0xee, 0x74, 0x13, +0x52, 0x84, 0x90, 0x44, 0x17, 0x40, 0x2a, 0xbc, +0xa5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0x66, 0x47, 0xe2, 0x57, 0xd4, +0x64, 0x46, 0xd2, 0x3d, 0x92, 0xb8, 0x93, 0x42, +0x8c, 0x16, 0x9e, 0x48, 0x5d, 0x96, 0x42, 0x72, +0x4f, 0xf0, 0x5f, 0x28, 0x88, 0x2d, 0xb4, 0x46, +0xc8, 0xe1, 0x59, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0x1e, 0x4f, 0x8c, +0x0c, 0xf9, 0x58, 0x74, 0x36, 0xb4, 0x73, 0x43, +0xc5, 0x40, 0xe1, 0x50, 0x2f, 0xa4, 0xa1, 0xa6, +0x5a, 0xde, 0x06, 0x16, 0xf6, 0x43, 0xee, 0xec, +0xe1, 0x18, 0x72, 0x5f, 0x19, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0x6e, +0x77, 0x7e, 0x3e, 0xb6, 0x33, 0x49, 0xea, 0x33, +0xbf, 0xd0, 0x5f, 0x15, 0xe8, 0xf1, 0xa8, 0x50, +0xa0, 0x76, 0x4a, 0xb2, 0x73, 0x35, 0xe4, 0xa2, +0x2a, 0x04, 0xcc, 0x6b, 0xc5, 0xa4, 0x0b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0x9d, 0x8a, 0x7d, 0xe2, 0x3d, 0xbe, 0x61, +0x4d, 0x01, 0x05, 0x95, 0x32, 0x20, 0x36, 0x78, +0x31, 0x00, 0x0d, 0x4a, 0xd7, 0x73, 0x03, 0xfe, +0x5c, 0x12, 0x3f, 0x4e, 0xe2, 0x1e, 0x2e, 0x2a, +0x80, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0x99, 0x9e, 0x50, 0x2d, 0xda, +0xde, 0x47, 0xe6, 0x37, 0xb1, 0x89, 0x5f, 0x0e, +0x9e, 0x0b, 0x28, 0xd1, 0x67, 0xb5, 0xb1, 0xc7, +0xbf, 0xa6, 0x2c, 0xb7, 0xd0, 0xa0, 0x1c, 0x96, +0x2f, 0x36, 0x49, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0x70, 0xc5, 0x16, +0xbe, 0x43, 0x19, 0xc0, 0x08, 0x2c, 0xac, 0x69, +0x4e, 0xa2, 0xd9, 0xf9, 0x8b, 0x96, 0xc1, 0xee, +0x6c, 0xe1, 0xac, 0x9e, 0xfd, 0x97, 0xc2, 0x95, +0x1b, 0xa0, 0x80, 0xf8, 0xe5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0x24, +0xbb, 0xbf, 0x17, 0xd6, 0xc9, 0x3d, 0xf5, 0xa2, +0x24, 0xfe, 0x47, 0xfb, 0x97, 0xa6, 0x77, 0x8e, +0x0a, 0xdf, 0xfc, 0xeb, 0x7d, 0xe9, 0xb2, 0x00, +0x47, 0xe4, 0x99, 0x46, 0x63, 0xce, 0x97, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0xcf, 0xa1, 0xdd, 0x12, 0xb2, 0x11, 0x3e, +0x45, 0x05, 0x9e, 0x5d, 0x4b, 0x96, 0x7e, 0x75, +0xe3, 0xd3, 0xf5, 0xcc, 0x72, 0x6e, 0x96, 0xcc, +0x7a, 0xba, 0xea, 0x14, 0x21, 0x7e, 0x63, 0x61, +0x39, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0x24, 0x16, 0x1e, 0x65, 0x9f, +0x1c, 0x63, 0xc6, 0xab, 0x0b, 0x68, 0xda, 0x0d, +0xb6, 0x69, 0x00, 0x88, 0x36, 0xc5, 0xc4, 0x4e, +0x2c, 0xfa, 0x8f, 0x2f, 0x68, 0xe8, 0x3c, 0x7e, +0x42, 0x5a, 0x52, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0x3d, 0x3b, 0x5c, +0xa6, 0x4e, 0x1e, 0x76, 0xbd, 0x25, 0xd3, 0x82, +0xc3, 0xab, 0x9c, 0x1d, 0x16, 0x82, 0xa3, 0x81, +0xf6, 0x65, 0xc4, 0x9a, 0xe0, 0x88, 0x3b, 0x6b, +0x3c, 0x79, 0x73, 0x7e, 0xaa, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0x9d, +0x7b, 0x17, 0x6f, 0x56, 0xa2, 0x09, 0x5d, 0x31, +0xb0, 0xcb, 0x5b, 0xc9, 0x6c, 0xbe, 0x08, 0x3a, +0x32, 0xf1, 0xf3, 0xbf, 0x56, 0x59, 0x27, 0xdb, +0xf5, 0xd9, 0xab, 0xd4, 0x7a, 0xa7, 0x53, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0xbe, 0xc0, 0x73, 0xff, 0x3f, 0x94, 0xc7, +0xeb, 0xfd, 0x0f, 0x5f, 0xfa, 0x02, 0xe8, 0xe4, +0x84, 0xca, 0x59, 0xb4, 0x64, 0x98, 0xff, 0x3e, +0x05, 0xe5, 0xc5, 0x4e, 0x23, 0xe3, 0x56, 0xf9, +0x17, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0x51, 0x91, 0xd6, 0x3a, 0xf1, +0x10, 0xab, 0x4a, 0xd1, 0x50, 0x82, 0xb7, 0xd4, +0xa3, 0xae, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x6d, 0x5f, 0x61, 0xcc, +0x47, 0x1d, 0x18, 0xa8, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0xa4, 0x60, 0x64, +0xac, 0x29, 0x8e, 0xc6, 0xd6, 0x8d, 0x2c, 0x46, +0x77, 0xb8, 0xd0, 0x89, 0x59, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x78, +0x85, 0xff, 0xe9, 0x46, 0x81, 0xd7, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0xb1, +0x33, 0x39, 0xd5, 0xd3, 0xf2, 0x25, 0x48, 0x6c, +0xbb, 0xea, 0x08, 0x6e, 0x8e, 0xd5, 0xd4, 0xa8, +0x1c, 0x80, 0xf2, 0x44, 0x56, 0x1e, 0x8d, 0x6a, +0xac, 0x4d, 0x06, 0xd2, 0xc7, 0xb7, 0xc3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0x8d, 0xb1, 0xa6, 0x1a, 0xf8, 0x30, 0x5e, +0x92, 0xb3, 0x0b, 0x14, 0xf4, 0xd2, 0x64, 0xa0, +0xa7, 0x39, 0x04, 0x2c, 0x20, 0x9c, 0x13, 0x61, +0x4e, 0x70, 0x73, 0x5e, 0xeb, 0x8f, 0x1b, 0x63, +0xc5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0x45, 0xbc, 0xb4, 0x64, 0x3d, +0x84, 0xb3, 0xbe, 0xa8, 0xd4, 0x9c, 0xd5, 0x9e, +0x07, 0x18, 0x55, 0x3e, 0x4e, 0x5f, 0x2e, 0x0d, +0x6d, 0xad, 0x94, 0x3f, 0x1e, 0x85, 0xf3, 0x61, +0xba, 0xe2, 0x1b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0xd9, 0xee, 0xd9, +0x36, 0xa8, 0x06, 0x76, 0xbd, 0x75, 0xa9, 0x67, +0x03, 0x3b, 0x32, 0xfa, 0x81, 0x69, 0x43, 0x5f, +0x92, 0xdd, 0xa3, 0x54, 0xd5, 0xf3, 0xef, 0xcf, +0xd8, 0xc0, 0x46, 0xf7, 0xa7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0x65, +0x75, 0x29, 0x03, 0x90, 0xce, 0xbf, 0x72, 0xd8, +0x1f, 0x04, 0x0a, 0x54, 0x97, 0x7d, 0xa6, 0xed, +0xe7, 0xa3, 0x5a, 0x38, 0x9e, 0x28, 0xd1, 0x90, +0x5f, 0x32, 0x60, 0xe9, 0xfc, 0x06, 0xcf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0xd2, 0xeb, 0xc1, 0x9a, 0xd7, 0x27, 0xae, +0x27, 0x4f, 0xbe, 0x1c, 0xf4, 0x0a, 0xee, 0xc3, +0x77, 0x11, 0x5e, 0xae, 0x79, 0xf0, 0x97, 0x5e, +0x16, 0x80, 0xfc, 0x8f, 0x01, 0x82, 0xb8, 0x84, +0x05, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0xd2, 0x65, 0x34, 0x8d, 0x1e, +0x59, 0xa4, 0xef, 0x73, 0xd6, 0x3f, 0x47, 0xd1, +0x8b, 0x90, 0x4b, 0x46, 0xc9, 0x59, 0x95, 0x51, +0xfc, 0x58, 0xc7, 0x51, 0xed, 0x79, 0x1b, 0xd8, +0x5f, 0x86, 0x46, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0xbc, 0x8d, 0xb7, +0xba, 0x88, 0x84, 0xf4, 0x82, 0x63, 0xff, 0x1a, +0x72, 0x01, 0xae, 0x55, 0x5f, 0xc1, 0xdb, 0xd4, +0x21, 0x56, 0xbc, 0x0a, 0xf4, 0x56, 0x6f, 0x35, +0xb6, 0xa5, 0x4a, 0x74, 0xe2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x22, +0xaa, 0x7f, 0x39, 0x38, 0x52, 0x92, 0xa4, 0x74, +0xae, 0x6c, 0xfc, 0x4e, 0x06, 0x32, 0x70, 0x30, +0x33, 0x62, 0xd8, 0x23, 0x2a, 0xf7, 0x07, 0x84, +0x54, 0xd9, 0x12, 0x7e, 0x97, 0xb1, 0xe7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0xff, 0x0c, 0xa1, 0x04, 0x4f, 0xd2, 0x57, +0xa7, 0x49, 0x0d, 0x8f, 0x95, 0xaa, 0x95, 0x80, +0xe0, 0xc1, 0x3b, 0xb0, 0x50, 0xbf, 0x6d, 0x0c, +0xcb, 0x2d, 0xa1, 0xb7, 0x68, 0xaf, 0x64, 0x91, +0xa4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0xf6, 0x09, 0xb4, 0xc9, 0xb7, +0x34, 0x13, 0x7e, 0xe2, 0xb1, 0x78, 0x8e, 0xbf, +0xf4, 0xca, 0x77, 0x66, 0x13, 0x69, 0x44, 0x7a, +0xa6, 0xa7, 0xc6, 0x97, 0x58, 0x48, 0x00, 0x24, +0xcb, 0x34, 0x9f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0xf7, 0x9f, 0x30, +0x7c, 0xa6, 0xec, 0x3e, 0xde, 0x01, 0xc8, 0xb5, +0xa8, 0xff, 0xa2, 0xde, 0xe3, 0x70, 0xb1, 0xa5, +0x27, 0x61, 0x04, 0x6d, 0x44, 0xa5, 0xbf, 0x64, +0x93, 0xc9, 0x79, 0x6a, 0x36, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x87, +0xb2, 0xa6, 0xbc, 0xec, 0x1b, 0xd0, 0xd4, 0x39, +0x2a, 0x3b, 0xcf, 0x63, 0xa4, 0x3b, 0x74, 0x6c, +0xd0, 0x99, 0x84, 0x62, 0x64, 0x58, 0x71, 0x34, +0x1c, 0x92, 0x85, 0x64, 0x53, 0x73, 0x0f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0x93, 0x0e, 0xed, 0xff, 0xd9, 0x57, 0xbb, +0xf0, 0x0a, 0x7e, 0x0d, 0x96, 0xbe, 0xd7, 0x07, +0x90, 0x30, 0xd8, 0x0c, 0x95, 0x67, 0x84, 0xcc, +0x12, 0x8e, 0xa0, 0xb4, 0xb0, 0xdc, 0xa1, 0x52, +0x6d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0xfb, 0xe7, 0xa0, 0x72, 0x42, +0xc4, 0xc8, 0x58, 0xe0, 0xb7, 0x31, 0x43, 0x95, +0x29, 0xc2, 0xba, 0x8c, 0x01, 0xa5, 0xfe, 0x39, +0xec, 0x1c, 0xa2, 0xaf, 0x75, 0x69, 0xdd, 0x9d, +0xe3, 0xfa, 0x4b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0xd9, 0xef, 0x56, +0x03, 0xb4, 0x19, 0x7b, 0x3d, 0x08, 0x3e, 0x89, +0x8b, 0xb5, 0x4b, 0x5d, 0xc9, 0x35, 0x77, 0x58, +0x19, 0xdc, 0xfd, 0x3a, 0x36, 0xf2, 0x31, 0xfa, +0x17, 0xe9, 0x9a, 0x82, 0x7c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0xde, +0xe7, 0x70, 0xb8, 0x44, 0xa9, 0xd0, 0xc4, 0xbe, +0xeb, 0x9e, 0x62, 0x0f, 0xff, 0x55, 0x06, 0x57, +0xc6, 0x02, 0x1e, 0x2e, 0x61, 0x9a, 0x66, 0x29, +0x9b, 0xff, 0x8c, 0x35, 0x18, 0x7f, 0xac, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0x87, 0x90, 0xf0, 0xe4, 0x98, 0x79, 0x60, +0x29, 0xb7, 0x82, 0x3c, 0xcf, 0x96, 0xc3, 0xec, +0x15, 0x50, 0xd1, 0x6b, 0x16, 0x93, 0x29, 0xcc, +0x68, 0xfa, 0x5e, 0xd8, 0x8e, 0xa2, 0xf8, 0xf5, +0x09, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0x67, 0x67, 0x95, 0x55, 0xb6, +0x55, 0x76, 0xbc, 0xa9, 0x17, 0x30, 0xbf, 0xbe, +0xc9, 0x68, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x0d, 0xc8, 0x13, 0xea, +0x7f, 0x2d, 0x96, 0x18, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x1c, 0xe9, 0x59, +0xb2, 0xcd, 0xcd, 0xa1, 0x0d, 0x20, 0x6a, 0xb7, +0xcc, 0x40, 0x3a, 0xec, 0x8d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd2, 0x18, +0xba, 0xf5, 0x02, 0xcb, 0x9c, 0xee, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0x5f, +0x49, 0xb1, 0x0f, 0x03, 0x7a, 0x60, 0x8f, 0x6c, +0x27, 0xb6, 0x21, 0x02, 0x82, 0x3f, 0x87, 0x9d, +0x69, 0xe3, 0x4d, 0x23, 0x07, 0x67, 0x85, 0xec, +0x82, 0x6a, 0xdb, 0xbf, 0x72, 0x80, 0x0a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0x03, 0xaf, 0xf4, 0xce, 0x77, 0x01, 0x21, +0x45, 0x93, 0xae, 0xf6, 0xb4, 0x7f, 0x41, 0x64, +0x39, 0xc6, 0x48, 0x3c, 0x29, 0x2d, 0xc4, 0x95, +0x59, 0x3d, 0x08, 0x45, 0x1a, 0xc2, 0x15, 0xb6, +0x01, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0x73, 0x05, 0x90, 0xa3, 0xcb, +0x00, 0x71, 0x95, 0xdb, 0x6a, 0x94, 0xd1, 0xa8, +0x10, 0x88, 0xf0, 0x42, 0xce, 0x3c, 0xa6, 0xc8, +0xd3, 0x58, 0x47, 0x14, 0xdf, 0x8c, 0xcf, 0x75, +0x16, 0x98, 0xe7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0xd8, 0xfc, 0x29, +0x2f, 0x3a, 0x92, 0xbe, 0x98, 0x1d, 0xf5, 0x02, +0x21, 0xca, 0xeb, 0x4d, 0xfc, 0xdc, 0xd6, 0xce, +0x72, 0x1d, 0x51, 0xf1, 0x55, 0x83, 0xab, 0x44, +0xdd, 0x47, 0xcb, 0x9f, 0xfc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0xdb, +0x53, 0x87, 0x57, 0x48, 0x57, 0xdd, 0xc5, 0x13, +0x28, 0x78, 0xee, 0xaf, 0xbc, 0x35, 0xcc, 0x36, +0x5e, 0x3a, 0x03, 0xc1, 0xf4, 0xd2, 0xf3, 0x4a, +0x44, 0xe7, 0x59, 0x9e, 0x76, 0x63, 0xfc, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x3c, 0xe2, 0x0a, 0xc2, 0x84, 0xdd, 0xf7, +0x4e, 0xb5, 0x9a, 0x27, 0xd7, 0xc7, 0x92, 0x59, +0x75, 0x59, 0x35, 0x37, 0xdd, 0xf9, 0xf6, 0xd9, +0x6e, 0xe0, 0xb3, 0x01, 0xf5, 0xf5, 0x51, 0x85, +0x9f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x80, 0x0d, 0x9b, 0xb3, 0x29, +0xff, 0x74, 0x2c, 0xfc, 0x35, 0x3c, 0x91, 0x43, +0x44, 0x36, 0xa9, 0x95, 0x07, 0x60, 0x0e, 0x46, +0x8c, 0x63, 0xe5, 0x0b, 0x3a, 0x32, 0xc4, 0xef, +0xd7, 0xc1, 0x1b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0x93, 0xa9, 0xce, +0xaf, 0x3c, 0x34, 0x09, 0xc3, 0x2f, 0xab, 0xc8, +0x13, 0x5a, 0x3c, 0x95, 0x62, 0x8c, 0xe9, 0xad, +0x51, 0xd8, 0xd1, 0x64, 0xd6, 0x4c, 0x84, 0x31, +0xe5, 0xf5, 0x22, 0x49, 0xd4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0x51, +0x68, 0x40, 0xb9, 0xa3, 0x92, 0x15, 0x41, 0x44, +0x54, 0x03, 0xf8, 0x5c, 0xf9, 0xb3, 0x9e, 0x9a, +0x7f, 0x5d, 0x77, 0xe6, 0x26, 0x90, 0x62, 0x5d, +0xdb, 0x69, 0xcd, 0xb8, 0x46, 0x63, 0x7e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x1c, 0x26, 0x9b, 0x7b, 0x0a, 0xbc, 0xfa, +0xe0, 0x92, 0xb6, 0xf8, 0x1c, 0x68, 0xd1, 0x42, +0x1b, 0x1d, 0x07, 0xa7, 0x82, 0x3a, 0xf1, 0x6f, +0x4b, 0x25, 0x58, 0xd8, 0x7f, 0xa9, 0x0e, 0x92, +0x3a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0x8a, 0xac, 0xc1, 0x14, 0x5e, +0x4f, 0x78, 0xfc, 0xab, 0x60, 0x53, 0xe1, 0x1c, +0x83, 0x7f, 0x9d, 0xc1, 0xf3, 0xf8, 0x66, 0xd8, +0xf4, 0xe5, 0xe4, 0x39, 0xd0, 0x53, 0x83, 0x05, +0x1d, 0x2b, 0xfd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0x46, 0xf1, 0x9d, +0x64, 0x9c, 0x3a, 0xe4, 0xf5, 0x2b, 0x50, 0xb9, +0xb7, 0x0a, 0x73, 0x09, 0x60, 0x4c, 0xe1, 0x4e, +0xc6, 0x40, 0x2f, 0x10, 0xbd, 0xeb, 0x97, 0x1f, +0xdd, 0x54, 0xc8, 0xc7, 0x33, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0xc1, +0x43, 0x1d, 0x05, 0xc7, 0xe9, 0x06, 0xbf, 0x2b, +0x59, 0x26, 0x4a, 0x1b, 0x00, 0x23, 0xd8, 0xf6, +0x5a, 0xea, 0xde, 0x92, 0x56, 0x32, 0x0d, 0x9b, +0x4e, 0xf8, 0x9f, 0xcb, 0x87, 0x8d, 0xc6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0xc2, 0x1d, 0xe0, 0xc5, 0x68, 0x0f, 0x28, +0x8a, 0xaa, 0x40, 0xb3, 0xf2, 0x78, 0x3e, 0x44, +0xae, 0x0b, 0x77, 0xf8, 0x43, 0xe8, 0x7d, 0x26, +0xf6, 0xba, 0xcf, 0xe8, 0xf2, 0xa4, 0x6e, 0xc3, +0x1a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0xa0, 0x48, 0x4c, 0xe3, 0x4d, +0x3f, 0x75, 0x58, 0x85, 0xca, 0x22, 0x42, 0x7f, +0x68, 0xe3, 0x41, 0x60, 0x62, 0x38, 0xa8, 0x72, +0x0b, 0x12, 0x88, 0x6c, 0xda, 0x9e, 0x71, 0x5e, +0xfc, 0xa5, 0x7d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0x16, 0x61, 0xeb, +0xc5, 0xfe, 0x6a, 0x0e, 0x0a, 0xd0, 0xd3, 0x12, +0xcb, 0xaa, 0xe5, 0x92, 0x88, 0x08, 0x40, 0xf4, +0x3f, 0x52, 0xb4, 0xfd, 0xe8, 0x94, 0xe3, 0xd4, +0x28, 0x5c, 0x9d, 0x97, 0x5d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0xbb, +0x85, 0x28, 0x86, 0xfb, 0xb4, 0x5d, 0x93, 0x2c, +0x12, 0xdc, 0x65, 0xd6, 0xe5, 0x2a, 0xe9, 0x67, +0x6b, 0x9b, 0xb7, 0x63, 0x28, 0x0c, 0x21, 0xfa, +0x63, 0x30, 0xc8, 0xcf, 0x6a, 0xac, 0xf2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0x63, 0x88, 0xb6, 0xba, 0x03, 0x67, 0x0c, +0xe6, 0x88, 0x5e, 0x18, 0x0d, 0xa6, 0x37, 0x5d, +0xe8, 0xfa, 0xc1, 0x3c, 0x13, 0x87, 0xed, 0xf7, +0x33, 0x02, 0xe4, 0x82, 0xfb, 0xad, 0x9f, 0xff, +0x61, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0xc9, 0x86, 0x02, 0x20, 0x12, +0x5b, 0x8b, 0xdf, 0xfb, 0x49, 0xd5, 0x9b, 0x1d, +0x56, 0x31, 0xd2, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xcc, 0x34, 0xcf, 0x97, +0xe4, 0x6b, 0x5f, 0x4b, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0x75, 0x01, 0x2b, +0xb2, 0xb2, 0xa9, 0xaf, 0xe0, 0x61, 0x67, 0xea, +0x5b, 0x0c, 0xca, 0xe7, 0x55, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x9f, +0xb2, 0xce, 0x4a, 0x15, 0xff, 0x6a, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0xfd, +0x06, 0xb5, 0xc3, 0x6c, 0x07, 0xa8, 0x9f, 0x3f, +0x3c, 0x5a, 0x4f, 0xb4, 0x28, 0x4f, 0x3b, 0x70, +0x2e, 0xf9, 0x76, 0xd7, 0x59, 0xae, 0x66, 0x4c, +0xba, 0xea, 0x4e, 0x2e, 0x73, 0x6e, 0xe7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0x23, 0xef, 0x34, 0x99, 0xcc, 0x4f, 0x35, +0xdb, 0x1d, 0xd1, 0x6e, 0xe0, 0xd3, 0x56, 0x8b, +0x4b, 0x76, 0xa2, 0x47, 0x3b, 0x94, 0xef, 0x5f, +0x52, 0xa9, 0x2c, 0xe9, 0x97, 0x30, 0x0c, 0x41, +0x29, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0xbd, 0xcb, 0x9c, 0x5e, 0x91, +0xc2, 0x85, 0x66, 0x52, 0x8a, 0x47, 0x2e, 0xd8, +0xbc, 0x28, 0xe7, 0x9d, 0xc8, 0x63, 0x7f, 0x86, +0xa3, 0xaa, 0x69, 0x43, 0xe3, 0x69, 0xa4, 0xb6, +0xe1, 0x35, 0x9a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0x86, 0x59, 0xb3, +0xa8, 0x25, 0x3e, 0xc9, 0x83, 0x84, 0xd7, 0x78, +0x76, 0x58, 0x8d, 0xfb, 0xea, 0x64, 0xa2, 0xc5, +0x09, 0x85, 0x17, 0x60, 0x57, 0x5a, 0xcc, 0x92, +0x1d, 0x4c, 0x96, 0x92, 0x9d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0x8b, +0x1d, 0x1b, 0xe4, 0x27, 0x80, 0xce, 0x92, 0xdf, +0x86, 0x07, 0x50, 0xf8, 0xd4, 0x7f, 0x38, 0x3c, +0xc8, 0x1d, 0x04, 0x42, 0x8a, 0x25, 0x10, 0x65, +0xed, 0x70, 0x98, 0xfe, 0x81, 0xbf, 0x23, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0x0d, 0x97, 0x8a, 0xff, 0x72, 0x2c, 0x9f, +0x18, 0xb7, 0x41, 0x6e, 0x1c, 0xa6, 0x93, 0x89, +0x79, 0x5b, 0x16, 0x77, 0x0e, 0xfa, 0x39, 0x0d, +0x9b, 0xee, 0xd1, 0xe0, 0x34, 0xd5, 0xfa, 0xfa, +0x9a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0x02, 0x12, 0xad, 0xbb, 0x9d, +0x0a, 0xb6, 0xa0, 0x75, 0x4f, 0x3b, 0x25, 0xad, +0xe6, 0x0d, 0xe1, 0x1f, 0xff, 0x7a, 0x11, 0xa2, +0xbd, 0xd0, 0x07, 0xb1, 0x56, 0xb7, 0xee, 0x66, +0xb7, 0x19, 0xc9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0x27, 0xc9, 0xc3, +0xe5, 0xe7, 0xaa, 0xbf, 0x61, 0x99, 0x50, 0x6b, +0xf1, 0x2d, 0xd8, 0x60, 0x49, 0x2e, 0x47, 0x66, +0xc1, 0xeb, 0x19, 0xe5, 0x18, 0x04, 0xc9, 0xc9, +0xf8, 0xeb, 0x25, 0x2f, 0x86, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0xda, +0x50, 0x49, 0xb9, 0x41, 0x7a, 0x0e, 0x12, 0x13, +0xb9, 0xfd, 0x0d, 0xba, 0x4a, 0x68, 0x16, 0x5e, +0xa9, 0x40, 0x65, 0xff, 0x5a, 0xc3, 0x92, 0x61, +0x84, 0x41, 0xab, 0x9e, 0xec, 0x11, 0xf2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0x7e, 0x81, 0xd1, 0x2c, 0x37, 0x78, 0x4c, +0xc7, 0x8c, 0x09, 0x86, 0x98, 0x4a, 0x88, 0x7b, +0x35, 0x6d, 0x36, 0xe1, 0xad, 0xad, 0x06, 0x2f, +0x42, 0xf4, 0x5d, 0x9f, 0xad, 0x69, 0x41, 0x73, +0xda, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x13, 0x57, 0x22, 0xe5, 0xfe, +0x91, 0x7c, 0xad, 0x17, 0x6a, 0xd1, 0xcc, 0x3c, +0xab, 0x1f, 0x26, 0x91, 0x99, 0xb1, 0xd4, 0x14, +0xeb, 0xb9, 0x52, 0x41, 0xec, 0x70, 0x0f, 0x98, +0x73, 0x0d, 0xd2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0xa6, 0xfc, 0x22, +0xdb, 0x3e, 0x06, 0xd7, 0x0a, 0x7b, 0x0b, 0x36, +0x00, 0xc0, 0xd8, 0x9b, 0x0f, 0x02, 0x0b, 0xcd, +0x64, 0x43, 0xd4, 0x43, 0x62, 0xe7, 0x06, 0x57, +0x35, 0x53, 0xa1, 0x87, 0xac, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0x7a, +0x9c, 0x80, 0xb7, 0x44, 0x9c, 0xc2, 0xd1, 0x87, +0x39, 0x88, 0xe8, 0xc7, 0xf1, 0xfa, 0xeb, 0xd6, +0x67, 0x92, 0x58, 0x4f, 0x72, 0xa9, 0xc4, 0x55, +0xdb, 0xf9, 0xb7, 0x5e, 0x31, 0xf3, 0xdd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0xac, 0xe3, 0x8e, 0xcc, 0x88, 0x4b, 0x29, +0x97, 0xc3, 0x5e, 0xf2, 0x58, 0x4f, 0x92, 0xf1, +0x54, 0xbb, 0x8c, 0x87, 0xcb, 0x7f, 0x1f, 0x72, +0x74, 0x76, 0x96, 0x2d, 0x80, 0x02, 0x89, 0x37, +0x9a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x6c, 0x7d, 0x16, 0x42, 0x66, +0xc3, 0x40, 0xcc, 0xb7, 0x4b, 0x7a, 0x7f, 0x07, +0x31, 0x44, 0xaf, 0x48, 0xec, 0x62, 0x43, 0xdc, +0xdc, 0x92, 0x50, 0x4c, 0x4b, 0x64, 0x1a, 0x5e, +0xf5, 0x2a, 0xab, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0x91, 0xb2, 0xc9, +0x92, 0x1d, 0x4b, 0xc3, 0xd6, 0xd4, 0x9b, 0xc5, +0xca, 0x44, 0x6b, 0x8b, 0xc6, 0xc7, 0x0e, 0xa3, +0xee, 0xf2, 0xf4, 0xf4, 0x81, 0xc0, 0xee, 0xd5, +0x22, 0xce, 0x2b, 0xc0, 0xa6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0xb4, +0xe1, 0xcf, 0x5a, 0x15, 0xf0, 0x92, 0x38, 0x1d, +0x73, 0x31, 0xb0, 0xf9, 0xba, 0x73, 0x70, 0x9b, +0x90, 0x4d, 0x4c, 0xa0, 0x29, 0x49, 0xc3, 0x5f, +0x46, 0xa9, 0xf2, 0xa6, 0xde, 0x6e, 0xcf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0xf0, 0xd0, 0xae, 0x24, 0xbb, 0xdb, 0x72, +0xfe, 0xfe, 0xfe, 0x88, 0x61, 0x9c, 0xe7, 0x45, +0x77, 0x27, 0x60, 0x8f, 0xf6, 0x62, 0x17, 0xa0, +0xaf, 0x3c, 0xfb, 0x44, 0x31, 0x12, 0x73, 0x3a, +0x6f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0xb3, 0xdd, 0xf1, 0x47, 0xc1, +0x7e, 0xb5, 0xa4, 0x81, 0x88, 0x91, 0xe9, 0xe0, +0x8d, 0xaf, 0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x8d, 0x5b, 0x36, 0x84, +0xf5, 0x7b, 0x08, 0x10, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x1e, 0x76, 0x03, +0x1b, 0x0b, 0xfb, 0x07, 0x23, 0xee, 0x29, 0xb9, +0xca, 0xb9, 0x73, 0x51, 0x81, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0xf8, +0xc7, 0x1a, 0x99, 0x3a, 0x89, 0x2e, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x9f, +0x15, 0x78, 0x62, 0xa6, 0x56, 0xe0, 0xea, 0x8e, +0x79, 0xf3, 0x38, 0x38, 0x4b, 0x74, 0xc3, 0x1a, +0x2d, 0x1f, 0x45, 0x59, 0x53, 0xf7, 0xbe, 0xcf, +0x69, 0xb4, 0x30, 0x5f, 0x65, 0xed, 0xb6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0x1f, 0x6e, 0x8a, 0x24, 0x85, 0xed, 0x87, +0x14, 0xe6, 0xb8, 0xe3, 0xa9, 0x3c, 0xee, 0x86, +0xc2, 0x58, 0x49, 0xe4, 0xdf, 0x36, 0x08, 0x70, +0x97, 0x15, 0x47, 0xbc, 0x0f, 0x43, 0xc4, 0x34, +0x31, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0xf3, 0xa5, 0x6a, 0x11, 0x96, +0x7f, 0xdc, 0x54, 0x3b, 0xbe, 0x05, 0x92, 0xeb, +0x3b, 0x85, 0x19, 0xa5, 0x73, 0x8b, 0xe3, 0x16, +0x9b, 0x40, 0x9f, 0xb3, 0x2d, 0x61, 0x43, 0x51, +0xfa, 0x32, 0x43, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0x84, 0x7b, 0x6c, +0x72, 0x00, 0x7d, 0x20, 0x4d, 0xf0, 0x28, 0xfe, +0x93, 0xf1, 0xb3, 0xdc, 0x07, 0xb9, 0x55, 0x3a, +0x82, 0x6f, 0x60, 0x98, 0x71, 0xeb, 0xea, 0xb4, +0xea, 0xd1, 0x1d, 0x4a, 0x10, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0xd4, +0x65, 0x7f, 0xab, 0x68, 0x51, 0xb3, 0x8a, 0x22, +0x65, 0xfb, 0xc6, 0x0c, 0xec, 0x1c, 0x0a, 0xce, +0xef, 0x50, 0x47, 0x29, 0x4d, 0x32, 0x89, 0x2f, +0x64, 0xb4, 0x87, 0x3b, 0x1f, 0x66, 0x83, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0x46, 0x7e, 0x93, 0x23, 0x9c, 0x10, 0x84, +0x09, 0x81, 0xcd, 0x43, 0x4a, 0x82, 0x71, 0x0c, +0x7a, 0x65, 0x10, 0x79, 0xc3, 0x80, 0x25, 0xc9, +0x07, 0x14, 0x89, 0x85, 0x81, 0x59, 0xcc, 0x1e, +0xcc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0xe5, 0xc0, 0x5c, 0x8d, 0xe9, +0xb9, 0x5c, 0xda, 0xd9, 0x2c, 0x1c, 0x26, 0x10, +0x4b, 0x8c, 0xd8, 0xc1, 0x34, 0x4c, 0xe0, 0x2e, +0x25, 0x0d, 0xb9, 0x20, 0xad, 0xfe, 0xba, 0x60, +0xa7, 0xf0, 0xa3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0x2b, 0x67, 0x22, +0xe0, 0x7f, 0xbe, 0x72, 0xb0, 0xb6, 0xf6, 0x1f, +0x14, 0xd9, 0xf3, 0x9a, 0x7c, 0x6c, 0x2c, 0x12, +0xeb, 0x19, 0xad, 0x07, 0xc5, 0x79, 0x1d, 0x11, +0xa7, 0x89, 0xa1, 0xc7, 0x36, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0x6d, +0x9c, 0x73, 0xbd, 0x57, 0x35, 0x65, 0xda, 0x2c, +0xb3, 0x05, 0x34, 0xee, 0x12, 0x26, 0xd2, 0x0c, +0x5c, 0x37, 0x57, 0xa1, 0x09, 0x60, 0xc5, 0x60, +0x8d, 0x63, 0x42, 0xcf, 0xbe, 0x45, 0x4d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0xd7, 0x33, 0x90, 0x11, 0x23, 0x5b, 0x8d, +0x81, 0xe1, 0xaa, 0x1a, 0xc1, 0x23, 0x17, 0x71, +0x38, 0x5a, 0xcd, 0x6d, 0x0b, 0x6c, 0x68, 0x4f, +0xce, 0x01, 0x39, 0xe1, 0x68, 0x21, 0xa4, 0xea, +0x67, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0x84, 0x7c, 0x3e, 0x68, 0x86, +0xc1, 0x3b, 0xbf, 0xc2, 0xa9, 0x5e, 0x55, 0x2a, +0xc7, 0x94, 0x49, 0x76, 0xf2, 0x07, 0x45, 0x3a, +0xf2, 0x4e, 0x76, 0xba, 0x0d, 0x56, 0x74, 0x76, +0x75, 0x69, 0xe2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x5e, 0x4f, 0x3d, +0xad, 0x0f, 0x6f, 0x09, 0xcf, 0xd5, 0x52, 0x2c, +0x60, 0xbd, 0xc6, 0x9f, 0xe9, 0x6d, 0xb7, 0xda, +0x5e, 0x1b, 0xe0, 0x18, 0xc0, 0x0e, 0x05, 0x7b, +0x3d, 0xdb, 0xe1, 0x3f, 0x4c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0x07, +0xc9, 0xa9, 0x47, 0x4e, 0xfe, 0xf5, 0xa4, 0xf1, +0xce, 0xad, 0xaa, 0xa8, 0x2d, 0xd7, 0xdc, 0xc7, +0x85, 0x42, 0xad, 0x4f, 0xe4, 0x15, 0xf3, 0xa1, +0xeb, 0x9f, 0xa2, 0xc7, 0xd3, 0x21, 0xcb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0x2b, 0x3c, 0x95, 0xa2, 0x13, 0x60, 0x59, +0x81, 0xbe, 0x55, 0x72, 0x8e, 0xe5, 0x36, 0xfb, +0xdb, 0x39, 0x1d, 0xff, 0x5a, 0x06, 0x41, 0x5d, +0xda, 0xd0, 0xb2, 0xee, 0xa8, 0x85, 0x98, 0x74, +0x05, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0xae, 0x0b, 0x76, 0x92, 0xd9, +0x77, 0x67, 0xf5, 0x11, 0xee, 0xf7, 0xe3, 0x17, +0x82, 0x7c, 0xa4, 0xd9, 0x58, 0x2b, 0x07, 0xb3, +0xf6, 0xb7, 0x58, 0xb7, 0xf8, 0xa4, 0xb8, 0x1e, +0x06, 0x02, 0x5a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x4e, 0x15, 0x22, +0x62, 0xa1, 0x7f, 0x6e, 0xbd, 0x4f, 0x4c, 0x22, +0xf0, 0xfa, 0xb2, 0x20, 0xb5, 0x3b, 0x34, 0x5b, +0x74, 0xc5, 0x98, 0x44, 0x38, 0xa1, 0x64, 0xcd, +0x20, 0xb5, 0x84, 0x21, 0x71, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x9b, +0xfe, 0x9a, 0x8f, 0x3a, 0xcb, 0x97, 0xbb, 0x74, +0x02, 0xe0, 0xa8, 0x0a, 0x23, 0x5a, 0x84, 0x24, +0x6d, 0x58, 0xbd, 0x2e, 0x70, 0x63, 0xa6, 0xba, +0xbe, 0x47, 0x6a, 0x3d, 0x1a, 0xcf, 0x4a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0x02, 0x6b, 0x6e, 0xd8, 0x04, 0x5a, 0x25, +0x63, 0x49, 0x31, 0xd8, 0xdb, 0x68, 0x35, 0x6c, +0xdc, 0xf6, 0x18, 0x3e, 0x84, 0x15, 0x43, 0xdb, +0x4b, 0xff, 0x38, 0xea, 0xa1, 0x2d, 0x54, 0x2c, +0x65, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0xdc, 0xae, 0x2e, 0x0e, 0x5d, +0xc9, 0x6f, 0x8b, 0x2b, 0x03, 0x1a, 0x36, 0x40, +0xc6, 0xe4, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x90, 0x4b, 0x78, 0x6c, +0xcb, 0x36, 0x83, 0xf4, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0x06, 0x77, 0xbc, +0x0b, 0x55, 0xe7, 0x1a, 0x17, 0x28, 0x36, 0x8f, +0x91, 0xa6, 0xc9, 0x1a, 0xfd, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xd9, +0xac, 0xc6, 0x1c, 0x2b, 0x16, 0xb7, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x9b, +0x92, 0xf3, 0xbf, 0xc1, 0x0a, 0x07, 0x76, 0xc4, +0x65, 0x09, 0xe6, 0xb5, 0x85, 0x7e, 0x5a, 0xcc, +0xb8, 0x7a, 0xd3, 0x79, 0x0d, 0x7a, 0x4a, 0xc1, +0x88, 0x43, 0x76, 0xe0, 0xc0, 0x6a, 0x7e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0x3a, 0x7a, 0xf5, 0xa1, 0x18, 0x5a, 0x89, +0x82, 0x73, 0x89, 0xf8, 0x33, 0x2a, 0xe9, 0x27, +0x8d, 0x90, 0xdf, 0x6d, 0xb0, 0x89, 0x2a, 0x3f, +0xd0, 0xc7, 0xf0, 0x92, 0x33, 0x35, 0x57, 0xf5, +0x07, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0x0f, 0xc0, 0x1a, 0x50, 0xfa, +0x0c, 0xaa, 0xe8, 0x1d, 0x06, 0x14, 0x7e, 0x41, +0xbd, 0x83, 0x00, 0x12, 0xa4, 0xa5, 0xd2, 0xb5, +0x82, 0x07, 0xa1, 0x3a, 0x0c, 0x14, 0x72, 0x68, +0x07, 0xc1, 0x85, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0xad, 0xf4, 0x35, +0x31, 0xa7, 0xdd, 0xce, 0xdb, 0xdf, 0x69, 0x2d, +0x60, 0x57, 0x63, 0x4d, 0x95, 0x06, 0xde, 0x6b, +0x38, 0x6b, 0x24, 0x24, 0x3c, 0x36, 0xd2, 0xea, +0x4f, 0xea, 0x6b, 0x94, 0x5e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x0c, +0x30, 0x0d, 0x75, 0x5e, 0xcc, 0xa6, 0xed, 0x82, +0x64, 0xef, 0x1d, 0x34, 0x0c, 0x27, 0x75, 0x62, +0xd2, 0xe6, 0xbe, 0x5e, 0xb5, 0x9e, 0x92, 0x4e, +0x0b, 0x94, 0x06, 0xb8, 0xac, 0x05, 0xcb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0x6c, 0xf2, 0xa5, 0x73, 0xe0, 0xbe, 0x3e, +0xc7, 0x79, 0x04, 0xa5, 0x57, 0x17, 0x5c, 0x8a, +0x7c, 0xb6, 0x71, 0xb9, 0x10, 0xf7, 0x21, 0x9c, +0x45, 0x4e, 0xa2, 0x67, 0xb8, 0xda, 0xdc, 0xfe, +0x16, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0xa9, 0x2d, 0x64, 0x65, 0xad, +0xe8, 0xd0, 0x2a, 0xa1, 0xcc, 0xc0, 0x93, 0x49, +0x7f, 0xe4, 0x8f, 0xe9, 0x38, 0x5b, 0xbf, 0xfa, +0xc5, 0xac, 0x1b, 0x35, 0x7e, 0x89, 0x66, 0x5a, +0x8d, 0x71, 0x69, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0x07, 0x3f, 0x37, +0x47, 0x60, 0x9d, 0x8d, 0x60, 0xa6, 0x78, 0x1a, +0x21, 0x61, 0x75, 0x9b, 0x1b, 0x94, 0x1a, 0x48, +0xfe, 0xae, 0x67, 0xf2, 0x68, 0x43, 0x8d, 0x25, +0x83, 0x03, 0x84, 0x9f, 0x29, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0xcd, +0x38, 0x04, 0x3d, 0x45, 0x4e, 0x83, 0x2c, 0xcd, +0x43, 0x46, 0x20, 0x1e, 0xa1, 0x83, 0x9f, 0xa9, +0x1f, 0x55, 0x35, 0x6e, 0xa5, 0x4f, 0x72, 0x17, +0xab, 0x48, 0xe9, 0xf2, 0x69, 0x0c, 0x7b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0x3b, 0xb6, 0x82, 0x14, 0x27, 0xa4, 0x82, +0x48, 0xf7, 0xdb, 0x03, 0xf8, 0xa0, 0x33, 0x45, +0x6d, 0xa9, 0x2a, 0x09, 0xaf, 0x8a, 0xc3, 0xa4, +0x27, 0x76, 0xcf, 0xff, 0xd5, 0xae, 0xea, 0xa2, +0x4e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0xca, 0x88, 0xf3, 0x0a, 0x94, +0x7d, 0x4c, 0x62, 0x54, 0x4b, 0x96, 0x86, 0xb3, +0x97, 0x68, 0x33, 0x3e, 0xe0, 0x00, 0xb7, 0x8c, +0x39, 0x8f, 0xf3, 0x3a, 0x90, 0xf6, 0x17, 0x8e, +0xeb, 0xbb, 0x1e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x4d, 0x56, 0x79, +0x46, 0xdf, 0x14, 0xbe, 0x0a, 0x12, 0xeb, 0xe6, +0x24, 0x1c, 0x47, 0x36, 0x92, 0x8f, 0x33, 0x17, +0xaa, 0xaa, 0x8a, 0x6e, 0x44, 0x5d, 0x67, 0xdc, +0x93, 0xcf, 0xcd, 0xec, 0x96, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0xd1, +0x8b, 0x65, 0x71, 0xb9, 0x4e, 0x46, 0x24, 0xa6, +0x6f, 0x92, 0x1b, 0x69, 0x4a, 0x0d, 0x73, 0xc2, +0xd4, 0x39, 0xa5, 0x14, 0x29, 0xb4, 0x4e, 0x03, +0x0f, 0x47, 0x7c, 0x66, 0x4f, 0xb8, 0xa1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0xc0, 0xb5, 0x96, 0x48, 0x65, 0x97, 0x51, +0x66, 0xe1, 0x0e, 0xaa, 0x03, 0x6b, 0xcb, 0x05, +0xcc, 0x14, 0x52, 0x3d, 0x82, 0x7c, 0x13, 0xf6, +0xbf, 0xf8, 0x6d, 0x6d, 0x45, 0x94, 0xd6, 0x35, +0x89, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0xdb, 0xc1, 0x7f, 0xe0, 0x54, +0x6d, 0xce, 0x92, 0x38, 0x5a, 0x26, 0x6b, 0xca, +0x8e, 0xb8, 0xc3, 0x54, 0x6e, 0x21, 0x6c, 0xa9, +0x60, 0x88, 0xf1, 0xcf, 0xde, 0x86, 0xca, 0x83, +0x95, 0xd9, 0xd2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0xb7, 0xb1, 0xac, +0x55, 0x5c, 0xfd, 0xd0, 0xd7, 0x18, 0xef, 0xdf, +0x6b, 0x88, 0x0e, 0x5b, 0xcf, 0x82, 0x2a, 0x2f, +0x1f, 0x28, 0x5e, 0xac, 0x18, 0x05, 0xe9, 0x95, +0x99, 0x34, 0x25, 0x92, 0x0a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0xd3, +0x58, 0x04, 0x8a, 0x77, 0x43, 0xa8, 0xe1, 0xe8, +0x7b, 0x95, 0x52, 0xcc, 0x42, 0x9b, 0x31, 0x6f, +0x35, 0x7a, 0xcd, 0xb6, 0x74, 0xe4, 0xf1, 0xe2, +0xaf, 0x5f, 0xf8, 0xa6, 0x10, 0x0f, 0x75, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0xd4, 0x1a, 0x72, 0x64, 0x8f, 0xf5, 0x0d, +0x8c, 0x81, 0x9b, 0x65, 0xa3, 0xe2, 0xf2, 0xb3, +0x5c, 0x1f, 0x50, 0x20, 0xd7, 0x9b, 0x2a, 0x10, +0x18, 0x43, 0x2d, 0x9e, 0xc0, 0xe3, 0xd9, 0xc0, +0x6d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0x85, 0x1e, 0x6d, 0xd3, 0xef, +0x4c, 0xa3, 0x25, 0xa8, 0x42, 0x6d, 0xad, 0xa4, +0x9e, 0xf1, 0xfa, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x68, 0xe1, 0x2e, 0x27, +0xe0, 0xda, 0x23, 0x2d, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0xfe, 0x5d, 0xc7, +0x72, 0xc4, 0x6d, 0xd0, 0xe0, 0x83, 0xa2, 0x18, +0x6e, 0x4e, 0x2a, 0x25, 0xb1, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd6, 0xd4, +0xe3, 0x66, 0x12, 0xe9, 0x84, 0xcb, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x07, +0xc7, 0xc6, 0x12, 0x50, 0x43, 0x29, 0x18, 0xb8, +0x1f, 0x99, 0x16, 0xc2, 0xa8, 0x1c, 0x01, 0xba, +0x19, 0x02, 0x28, 0x8a, 0xb6, 0x18, 0x93, 0xde, +0x48, 0x4f, 0x76, 0x49, 0x61, 0x1a, 0x01, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0xf3, 0x18, 0x9d, 0xac, 0xcf, 0x59, 0x74, +0x38, 0x61, 0x0f, 0xa0, 0xc6, 0x34, 0xf8, 0xc5, +0x4e, 0x46, 0x8e, 0x57, 0x42, 0x13, 0x4b, 0x6b, +0xe0, 0xf4, 0xc2, 0x98, 0xbc, 0x1e, 0xaf, 0x27, +0xa6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0x0f, 0xa3, 0x81, 0xab, 0x2c, +0xf8, 0x5e, 0x9a, 0x17, 0xdd, 0xbb, 0x89, 0xbe, +0xa3, 0x4e, 0x34, 0x80, 0xde, 0xf6, 0x95, 0xef, +0x04, 0x96, 0x09, 0x08, 0x3c, 0xd2, 0xc6, 0x76, +0x36, 0x05, 0x7b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0xf8, 0xae, 0x1e, +0xb0, 0xa0, 0x6d, 0xcb, 0xc0, 0x80, 0x38, 0x71, +0x74, 0x94, 0x7f, 0x06, 0x9e, 0xc3, 0xc4, 0xc6, +0x79, 0xa6, 0xed, 0xbb, 0x20, 0xa2, 0x3e, 0x10, +0x2f, 0x94, 0x49, 0x23, 0x0f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0x0f, +0x8a, 0xa7, 0x6b, 0x8d, 0x17, 0x59, 0x49, 0x63, +0xdc, 0x29, 0x1f, 0x4e, 0xc9, 0x42, 0x99, 0x25, +0x95, 0x38, 0xe2, 0x33, 0x71, 0xa4, 0xd1, 0x70, +0x2c, 0x7f, 0x1a, 0x42, 0x6e, 0xf8, 0x78, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0x20, 0x9b, 0xd7, 0x98, 0x40, 0x4d, 0x44, +0x7a, 0xa5, 0xd5, 0xc9, 0x9d, 0x2b, 0x76, 0xd3, +0xd6, 0x9b, 0x3a, 0x66, 0xaf, 0x1c, 0xcc, 0xf0, +0x4b, 0xc6, 0x19, 0xcc, 0x92, 0x42, 0x3c, 0xa6, +0x54, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x4a, 0x7b, 0x25, 0x49, 0x82, +0x43, 0xef, 0x1e, 0x81, 0x23, 0x64, 0x41, 0x78, +0xbb, 0x71, 0x4f, 0xe3, 0x76, 0x19, 0xe2, 0x62, +0x94, 0x9e, 0x01, 0xd9, 0x14, 0x11, 0x25, 0xf6, +0xf4, 0xa3, 0xe8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0x5c, 0x64, 0xf5, +0x8f, 0xbc, 0x25, 0x06, 0x46, 0xc1, 0xd1, 0x05, +0x9a, 0x7b, 0xad, 0x97, 0x28, 0x84, 0xc9, 0xf0, +0x09, 0x1a, 0x31, 0xce, 0x47, 0x5d, 0x27, 0xcc, +0x2c, 0x3f, 0x46, 0x02, 0x49, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0x5d, +0x50, 0x0c, 0xbe, 0xcf, 0x44, 0xb0, 0x19, 0x56, +0xf7, 0xab, 0x03, 0x85, 0xcf, 0xbe, 0xd3, 0x9e, +0x07, 0xd7, 0x70, 0xfa, 0x46, 0x65, 0xe1, 0x36, +0x16, 0x66, 0xbb, 0x8d, 0xf4, 0x4f, 0xcf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0x27, 0xf4, 0x68, 0x23, 0xa4, 0xd5, 0xb4, +0xec, 0x0b, 0x14, 0x5d, 0xd5, 0x01, 0xa5, 0x69, +0xa9, 0x25, 0x82, 0x89, 0x79, 0x57, 0x4f, 0x1f, +0xbf, 0x6a, 0x06, 0x7d, 0x08, 0x66, 0x93, 0xa8, +0x39, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0x45, 0x24, 0xec, 0x44, 0x74, +0xb1, 0xa3, 0xdd, 0xa3, 0xcf, 0xc8, 0x70, 0xed, +0x15, 0x75, 0x47, 0x7a, 0x7a, 0xbf, 0xd6, 0xc2, +0x2c, 0xc9, 0x42, 0x2a, 0xf2, 0x64, 0x0d, 0x9d, +0xec, 0x34, 0x57, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0xc2, 0xac, 0x8f, +0x6f, 0x0e, 0xcb, 0xf2, 0xf2, 0x4e, 0x28, 0xcd, +0x39, 0xf9, 0xdc, 0x85, 0x03, 0x7a, 0x21, 0x88, +0x83, 0xe1, 0xe9, 0x48, 0x26, 0xdb, 0x25, 0x40, +0xae, 0x4a, 0x33, 0x42, 0xe0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0xfa, +0x52, 0xf3, 0x0e, 0x79, 0x47, 0xc6, 0x49, 0x65, +0x25, 0x6a, 0x0d, 0x83, 0xf7, 0x5b, 0xad, 0x29, +0x63, 0x7b, 0x06, 0x46, 0x6f, 0xd7, 0x8d, 0xcf, +0xe0, 0x6a, 0x70, 0xac, 0x8e, 0xf7, 0x96, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0xef, 0x4a, 0xbc, 0xe9, 0xd6, 0xa4, 0x4f, +0x27, 0x27, 0xbd, 0xfb, 0xcc, 0x58, 0x1c, 0x89, +0xb3, 0xd5, 0x56, 0xc6, 0xe6, 0x5f, 0x29, 0xb9, +0x66, 0xb6, 0xbc, 0x3a, 0x75, 0xf9, 0x8d, 0xe1, +0xc6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0x75, 0xdc, 0x46, 0x15, 0x36, +0x81, 0xe2, 0xdb, 0xd4, 0x2b, 0xaf, 0xc1, 0x5e, +0x14, 0xe3, 0x57, 0xad, 0x35, 0x11, 0x6c, 0x1f, +0x66, 0x4f, 0xd7, 0x50, 0x5a, 0xda, 0xb1, 0xfe, +0xd4, 0xdc, 0x2d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0x48, 0x3d, 0xe8, +0xe0, 0xe7, 0xb5, 0x6d, 0xe9, 0xa9, 0x6c, 0x2e, +0x35, 0xd1, 0xfd, 0x37, 0xf0, 0x0c, 0xab, 0xbc, +0x41, 0x94, 0xbe, 0xcc, 0x05, 0x3c, 0x35, 0x15, +0x4b, 0xff, 0x51, 0x22, 0x31, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0x43, +0x58, 0xee, 0x11, 0x09, 0xc7, 0xf8, 0x11, 0x54, +0xbf, 0xe4, 0x04, 0x96, 0x59, 0xd0, 0x28, 0x22, +0x4b, 0x88, 0xfc, 0x98, 0xbd, 0xa0, 0x61, 0x0a, +0x80, 0xba, 0x15, 0x91, 0xda, 0x08, 0x86, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0xa2, 0x3f, 0x18, 0x50, 0x31, 0x3b, 0xf6, +0x1f, 0x8e, 0x62, 0xe4, 0x0d, 0xba, 0x8d, 0x9f, +0xf7, 0x76, 0xa6, 0xcd, 0x64, 0x0b, 0x91, 0xb1, +0x09, 0xb6, 0xcf, 0x95, 0xa3, 0x45, 0x0c, 0x7d, +0x6c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0xf7, 0x83, 0x55, 0x14, 0x4f, +0x9c, 0x66, 0xd5, 0x6e, 0x96, 0x71, 0xab, 0xbd, +0x10, 0x05, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x22, 0x8b, 0x7f, 0x1f, +0x78, 0x27, 0xeb, 0xd4, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0xba, 0x73, 0xd0, +0xf6, 0x69, 0xa2, 0x4e, 0xe5, 0x7a, 0xb2, 0x37, +0x39, 0x00, 0x6b, 0xe4, 0xb0, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0xcd, +0xdd, 0x5c, 0x70, 0x73, 0xe3, 0x15, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0x9a, +0x28, 0x3c, 0xb3, 0xe5, 0x1d, 0x2b, 0x47, 0x7a, +0x1f, 0xdd, 0xf9, 0xef, 0xfd, 0xde, 0x4f, 0x08, +0xaf, 0xed, 0xb9, 0xf0, 0xcd, 0x6a, 0xbc, 0x31, +0x3f, 0x8a, 0x14, 0xa1, 0x4a, 0x05, 0xcc, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x3b, 0xe9, 0x89, 0xc4, 0x9b, 0x59, 0xf0, +0x9d, 0x95, 0x12, 0x4c, 0xe4, 0x13, 0xf8, 0x66, +0xfc, 0x75, 0x39, 0x4c, 0x51, 0x94, 0x56, 0x92, +0xc9, 0xe1, 0x03, 0x1a, 0xe4, 0x27, 0x94, 0xbf, +0xd0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0x16, 0x7f, 0x1e, 0xaf, 0x3d, +0xb3, 0xb0, 0xe9, 0x6b, 0xde, 0x48, 0x28, 0x06, +0x5f, 0x36, 0x09, 0x31, 0xbb, 0x44, 0xc3, 0x49, +0xea, 0xe7, 0x3d, 0xd2, 0x50, 0x52, 0x68, 0x0f, +0xf5, 0x4a, 0x0a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x05, 0x33, 0x99, +0x13, 0x88, 0x2a, 0x1c, 0x61, 0x25, 0x0c, 0x3b, +0x65, 0xfe, 0x21, 0x88, 0xdf, 0x04, 0x54, 0xc6, +0x97, 0x95, 0x3a, 0x7f, 0xd6, 0x3b, 0x9b, 0x8c, +0x7d, 0xf3, 0x09, 0xfc, 0xe2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0x99, +0xd2, 0x03, 0x81, 0xef, 0x9c, 0xe5, 0x45, 0x18, +0x59, 0xcb, 0x48, 0x3a, 0x15, 0xfe, 0xc4, 0xce, +0xfa, 0x5b, 0xbd, 0xb0, 0xb4, 0x9b, 0xfe, 0x54, +0x04, 0x73, 0xf9, 0x78, 0x8f, 0xcc, 0xe0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0x03, 0xfe, 0xb0, 0xaf, 0x65, 0xb5, 0x78, +0x6a, 0x53, 0x3d, 0x2e, 0x61, 0xb5, 0xe9, 0x8f, +0x51, 0x7a, 0x58, 0x6d, 0x11, 0xad, 0x8f, 0x58, +0x57, 0x90, 0xbe, 0x8f, 0x1d, 0x1e, 0x98, 0x0f, +0x1d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0xfb, 0x77, 0x32, 0x5d, 0x73, +0x20, 0xac, 0x9f, 0x7c, 0x7d, 0xd1, 0x00, 0xd2, +0x5d, 0xd5, 0x57, 0xc8, 0x4c, 0xea, 0x61, 0x1b, +0xb3, 0x54, 0x0b, 0xbd, 0xfb, 0x6f, 0xf5, 0xe4, +0xf8, 0xf3, 0x50, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x8e, 0x77, 0xe8, +0x8a, 0xa1, 0x92, 0x6d, 0x43, 0x68, 0x17, 0xa9, +0x0f, 0x5e, 0x69, 0x3d, 0x94, 0x95, 0x81, 0x47, +0xa0, 0xa1, 0x1f, 0xcf, 0xcd, 0x0e, 0xf4, 0x5e, +0x19, 0x9d, 0xb8, 0x3d, 0xe6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0x64, +0xbb, 0x8d, 0xb2, 0xab, 0xab, 0xc9, 0xbb, 0xf5, +0xde, 0xd9, 0xda, 0x5f, 0x19, 0x8b, 0xcd, 0x8c, +0xf4, 0xf5, 0x04, 0x15, 0x22, 0x49, 0x43, 0x5a, +0x62, 0xd0, 0x06, 0xb9, 0x49, 0xe6, 0x42, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0x0d, 0x3d, 0xe5, 0xee, 0x0b, 0x5a, 0x64, +0x32, 0x15, 0x22, 0xef, 0xbd, 0x7c, 0x05, 0x78, +0x5d, 0x55, 0x52, 0x39, 0xa6, 0x81, 0x1b, 0xeb, +0x23, 0xb2, 0x19, 0x92, 0xc2, 0x76, 0xeb, 0xab, +0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0x40, 0x3b, 0xba, 0x2a, 0x7e, +0x99, 0x47, 0x60, 0xba, 0x52, 0x79, 0x58, 0x39, +0xae, 0xf6, 0x1c, 0xba, 0x1e, 0x49, 0xf1, 0x81, +0xb6, 0xd6, 0x0c, 0x43, 0x5c, 0x17, 0x07, 0x9c, +0x8f, 0x23, 0x42, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0xd4, 0xa0, 0xe3, +0xe8, 0xd4, 0xa1, 0xfa, 0xa1, 0x09, 0xf0, 0xbc, +0xaf, 0x74, 0x73, 0xe1, 0x77, 0xe8, 0xf2, 0x5a, +0x18, 0xc2, 0x29, 0xe8, 0xd9, 0x7a, 0xe5, 0x79, +0xf3, 0x3b, 0xcd, 0xd7, 0x60, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0x12, +0xf2, 0xd0, 0x7a, 0x7a, 0x85, 0x22, 0x51, 0xba, +0x04, 0x5a, 0xca, 0x30, 0xdb, 0x24, 0xe5, 0x44, +0xcf, 0x4b, 0x1b, 0xb8, 0x63, 0xd8, 0x37, 0xde, +0x2a, 0x09, 0xe1, 0x9d, 0x12, 0x27, 0x75, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0xa8, 0x80, 0x2e, 0x20, 0x58, 0x3a, 0xa1, +0x78, 0x9b, 0x80, 0x57, 0x7e, 0x0e, 0xb4, 0x33, +0x03, 0x6d, 0xf9, 0x9d, 0xf5, 0x36, 0xf3, 0xc4, +0xf8, 0x06, 0xba, 0xbe, 0x16, 0x5c, 0x44, 0xd1, +0xa0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0x58, 0xd4, 0x3d, 0x87, 0xd3, +0xa1, 0xca, 0xed, 0x0d, 0xdd, 0x35, 0x1f, 0x20, +0x88, 0x89, 0x7f, 0x45, 0x4b, 0x28, 0x7f, 0xbf, +0x69, 0x42, 0x37, 0xb8, 0x36, 0x31, 0x8e, 0x2d, +0xd2, 0x9d, 0x9f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0xdc, 0xaa, 0xc4, +0x43, 0xfd, 0x00, 0xc0, 0x24, 0x1e, 0xfe, 0xe7, +0x42, 0x1b, 0x2b, 0x48, 0x9f, 0x52, 0x3e, 0xca, +0xa1, 0xaa, 0xa4, 0x9b, 0x2e, 0x00, 0x7d, 0x6b, +0xa0, 0xa9, 0x7e, 0x48, 0x47, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x8b, +0x4c, 0xe1, 0xb4, 0xb8, 0xed, 0xd7, 0xa2, 0x5e, +0x6f, 0x3f, 0xb5, 0x77, 0x81, 0x18, 0xdd, 0x25, +0x88, 0xd0, 0xae, 0x90, 0xb9, 0xa2, 0xd6, 0xcb, +0x72, 0x46, 0xcf, 0xae, 0x15, 0xa6, 0x22, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0xd6, 0xaa, 0xd7, 0x9e, 0xa1, 0x52, 0xfc, +0x89, 0x2d, 0xe6, 0x39, 0x17, 0xc2, 0x51, 0x3d, +0x71, 0x3d, 0x77, 0x2e, 0xee, 0x6e, 0x41, 0x77, +0xec, 0x8e, 0x30, 0xcc, 0x7e, 0x8c, 0x4a, 0xc4, +0xc3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0x07, 0xe8, 0x92, 0xde, 0x86, +0xdd, 0xd7, 0x09, 0x20, 0x1d, 0xac, 0xa0, 0xa8, +0x91, 0x57, 0xc9, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x01, 0xa2, 0x40, 0x19, +0x8e, 0x72, 0x6b, 0xa5, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0xa7, 0xc8, 0x42, +0x36, 0x2c, 0xc6, 0x32, 0xab, 0xb9, 0x2e, 0xf3, +0xc3, 0x81, 0x85, 0xa7, 0x94, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4d, 0x1d, +0x61, 0xb1, 0x5a, 0x6e, 0xcf, 0x8b, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0xa1, +0x9a, 0xb3, 0xb0, 0x61, 0x63, 0x49, 0x16, 0x9d, +0x2d, 0xf8, 0x24, 0x25, 0xa2, 0x1d, 0xe2, 0x5a, +0x3b, 0x48, 0xa0, 0x2d, 0xb7, 0x92, 0x6e, 0xb2, +0xfb, 0xd7, 0xac, 0xd0, 0xf1, 0x93, 0xf8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x63, 0x94, 0x3f, 0xe8, 0x81, 0x8d, 0x40, +0x5b, 0x9d, 0x92, 0x3e, 0xba, 0xff, 0xa8, 0x47, +0xe7, 0xc8, 0x50, 0xfa, 0x56, 0x62, 0x8e, 0x34, +0x66, 0x06, 0x51, 0xf4, 0xa0, 0x54, 0xe4, 0x5f, +0xa2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0x6a, 0x6f, 0x53, 0xa9, 0x0e, +0x6b, 0x92, 0x64, 0x5c, 0xf9, 0x05, 0xe2, 0x97, +0x61, 0xe6, 0x1d, 0xc0, 0x64, 0xc9, 0xdd, 0x0b, +0x6f, 0xb9, 0x1e, 0xd4, 0x80, 0xd7, 0x7d, 0x98, +0x41, 0x5d, 0x93, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0x3d, 0x7b, 0x1e, +0x4c, 0x0f, 0xa3, 0x54, 0x7f, 0xf9, 0x6a, 0x76, +0x4a, 0x3f, 0x30, 0x30, 0xf1, 0x56, 0xb7, 0x62, +0xcc, 0xd7, 0x2b, 0x32, 0x61, 0xe4, 0x9d, 0x56, +0x57, 0xab, 0x70, 0xfd, 0x5d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0xf2, +0x70, 0xf0, 0xc1, 0x81, 0x4a, 0xae, 0x6e, 0xb6, +0x66, 0x90, 0xcf, 0x3b, 0x0e, 0x94, 0x1a, 0x0f, +0xa1, 0xb5, 0x1f, 0xfa, 0xe3, 0xdf, 0x13, 0xba, +0x78, 0x1e, 0x61, 0x59, 0x8e, 0xd7, 0x9d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0x7f, 0x2c, 0xd8, 0xe4, 0xf0, 0x4d, 0xbf, +0xac, 0x97, 0x7d, 0xf9, 0xf7, 0xef, 0xfd, 0x6f, +0x03, 0xaa, 0xb8, 0x75, 0xc0, 0xe5, 0x14, 0x59, +0x1a, 0x12, 0xe4, 0xb4, 0xd4, 0x3c, 0x7b, 0x53, +0x12, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0x9d, 0x6a, 0xfa, 0x75, 0x9e, +0xa2, 0x5f, 0x33, 0xb4, 0xfc, 0x88, 0x06, 0x79, +0x8b, 0xf3, 0x18, 0x1e, 0x40, 0x55, 0x6a, 0xc7, +0x21, 0x99, 0x8c, 0xb5, 0xd3, 0x65, 0x10, 0xdf, +0x60, 0x25, 0xf1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0x05, 0x7c, 0x61, +0x7f, 0x2a, 0xd8, 0xe1, 0x5e, 0x58, 0x2a, 0x87, +0xd3, 0xdb, 0x7b, 0xe5, 0x12, 0xd0, 0xb0, 0xf6, +0xe9, 0xa0, 0x3c, 0xa3, 0x9b, 0x6f, 0x6e, 0x2f, +0x96, 0xba, 0x45, 0x4a, 0x49, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0xcd, +0x8b, 0x7b, 0x78, 0xa0, 0xe7, 0x95, 0xb3, 0xd4, +0x23, 0xad, 0xdc, 0xb4, 0xe6, 0x39, 0x5d, 0x54, +0x68, 0xa7, 0x8f, 0xb8, 0x4e, 0xd9, 0x00, 0x50, +0x89, 0xb6, 0x14, 0x80, 0x4a, 0x1a, 0xea, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0xa5, 0x8d, 0x0a, 0xb4, 0xb4, 0x85, 0x68, +0x93, 0xa0, 0xe2, 0x79, 0xa3, 0xec, 0x47, 0xd9, +0x22, 0x27, 0xd2, 0x5c, 0x09, 0xff, 0x06, 0x1b, +0x74, 0x1b, 0xac, 0x7f, 0x4c, 0xd8, 0xd8, 0x5c, +0x3e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0x35, 0xd1, 0x03, 0x93, 0x98, +0xd3, 0x6e, 0x76, 0xbc, 0x45, 0xb8, 0x13, 0x0f, +0x40, 0xd7, 0xc4, 0x39, 0xfe, 0x0c, 0xa6, 0x94, +0xa4, 0x97, 0x35, 0x37, 0xd8, 0x4f, 0x8b, 0xc3, +0x50, 0x2b, 0x5e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0x1e, 0xc7, 0xb6, +0x0c, 0xec, 0x48, 0x15, 0x20, 0xb1, 0x78, 0xb7, +0x84, 0xa7, 0x82, 0xb8, 0x82, 0xd0, 0xfb, 0xc6, +0x65, 0x5c, 0x21, 0x6c, 0x22, 0x6f, 0x17, 0x23, +0xc3, 0x8d, 0xb4, 0xcd, 0x56, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0x3e, +0x51, 0x67, 0x29, 0x14, 0xce, 0x0a, 0xfa, 0xf2, +0x0a, 0x43, 0x94, 0x4b, 0x6f, 0x02, 0x61, 0xfd, +0xec, 0x36, 0xc3, 0x1a, 0x4f, 0x22, 0xdd, 0x66, +0xe5, 0x9a, 0x9b, 0x6f, 0x79, 0xde, 0x96, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0x8e, 0xa5, 0xbf, 0x1b, 0x4b, 0xbf, 0x81, +0x5d, 0x16, 0xa1, 0x9e, 0x81, 0x3e, 0x20, 0x1b, +0x1f, 0x96, 0x22, 0x03, 0x7d, 0x54, 0xab, 0x75, +0x36, 0xf0, 0x25, 0x06, 0xb5, 0xba, 0xb2, 0x57, +0xb4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0x6e, 0xc5, 0xdd, 0xa2, 0x9a, +0x18, 0xf6, 0x9d, 0xe0, 0x42, 0x39, 0xa2, 0x7b, +0xaa, 0x69, 0xb6, 0x44, 0xdd, 0xf2, 0x63, 0x86, +0x2c, 0xce, 0x40, 0xb5, 0xe6, 0x33, 0xd5, 0xb3, +0x4a, 0xab, 0x42, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0xa3, 0x43, 0x8b, +0x58, 0xdf, 0x90, 0xc3, 0x3d, 0x49, 0xa3, 0x1d, +0x44, 0xb2, 0xe9, 0x68, 0x41, 0x74, 0xef, 0xa7, +0x53, 0x64, 0xdc, 0x5d, 0x96, 0x38, 0xfd, 0xf7, +0x22, 0x31, 0xb1, 0xfb, 0x14, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0xcd, +0xdd, 0x58, 0xcb, 0xdc, 0x19, 0x0e, 0x91, 0x76, +0x9b, 0xbe, 0xb9, 0x19, 0xeb, 0x3e, 0x97, 0x73, +0x82, 0xec, 0xb3, 0x2c, 0xc0, 0xb9, 0x77, 0xc8, +0xdf, 0x28, 0x9b, 0x8c, 0xf9, 0xa7, 0xaa, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0x7a, 0xc6, 0x0e, 0xee, 0x41, 0xd5, 0xa1, +0x50, 0x7e, 0x66, 0x99, 0x1a, 0xbf, 0xea, 0x74, +0xd4, 0xaa, 0xfc, 0x87, 0x00, 0x92, 0x9f, 0xa9, +0x7e, 0x64, 0xd8, 0x6f, 0xd2, 0xa2, 0x9d, 0x3a, +0x6e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0x1b, 0x16, 0x77, 0xa1, 0x11, +0xc9, 0x91, 0xcb, 0x37, 0x8f, 0xd7, 0x84, 0xf2, +0x24, 0x21, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xb7, 0x70, 0xac, 0x95, +0x73, 0xa7, 0x9e, 0xde, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0xdb, 0xb0, 0xc2, +0xee, 0xc6, 0x44, 0xb9, 0x30, 0xd8, 0xa7, 0x52, +0x64, 0x07, 0xd1, 0x15, 0x25, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x46, +0x68, 0xcb, 0xf7, 0x8a, 0x76, 0xc9, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0xab, +0x2d, 0x04, 0x29, 0x54, 0xe2, 0x35, 0xa8, 0x9e, +0x02, 0x7c, 0x30, 0xf4, 0x24, 0x8e, 0xae, 0x05, +0x5c, 0x5c, 0x13, 0x29, 0xf2, 0xbd, 0xb5, 0x73, +0xbc, 0x1d, 0x43, 0xa5, 0x88, 0xcd, 0x9a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0xa2, 0x26, 0xd5, 0x73, 0xb6, 0x8c, 0x98, +0xd9, 0x63, 0xac, 0x87, 0x1d, 0x48, 0x49, 0xb3, +0xe5, 0x7e, 0xb9, 0xd2, 0x65, 0x40, 0x05, 0x3b, +0x1a, 0x0d, 0xc4, 0x1a, 0x38, 0x16, 0x88, 0x9a, +0x44, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0xc4, 0x69, 0x33, 0x4c, 0x99, +0x65, 0x82, 0xc1, 0x7e, 0x50, 0xba, 0x26, 0x47, +0x05, 0xe0, 0xe6, 0xaf, 0x99, 0x1c, 0x05, 0xeb, +0x55, 0x95, 0x51, 0x1b, 0x4e, 0x07, 0x80, 0x15, +0x27, 0x56, 0x59, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0xd0, 0xe0, 0x68, +0x5b, 0xa8, 0x10, 0x17, 0xe0, 0xc5, 0xfc, 0x0f, +0x95, 0x79, 0x88, 0x7c, 0x5f, 0x6b, 0x26, 0x4d, +0x25, 0x73, 0xfd, 0xc9, 0xd2, 0x35, 0xb7, 0x41, +0xaa, 0x17, 0xfd, 0x7f, 0x08, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0xcd, +0xea, 0x79, 0x2a, 0x64, 0xd8, 0x2c, 0x87, 0x92, +0x9e, 0xc3, 0x2f, 0x8d, 0x6a, 0x84, 0xcc, 0xf8, +0xde, 0x6e, 0x1f, 0x15, 0x04, 0x74, 0x1e, 0x6e, +0xe5, 0xa4, 0x33, 0xe0, 0x92, 0x0d, 0xa1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0x83, 0x8b, 0x20, 0xb3, 0xa1, 0xbf, 0xc0, +0xde, 0x0d, 0x71, 0x83, 0xdb, 0xe4, 0xdc, 0xe0, +0xf8, 0xb8, 0x04, 0x2d, 0x61, 0x43, 0x37, 0xaf, +0x2b, 0x47, 0xfc, 0xad, 0x8e, 0xb0, 0xd7, 0xbf, +0x01, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x42, 0x66, 0x5e, 0xe9, 0xf9, +0x1b, 0xc5, 0x32, 0xad, 0x1f, 0xd3, 0x70, 0xf6, +0x25, 0xbd, 0x93, 0x98, 0x55, 0xbe, 0xad, 0x64, +0xa1, 0x0d, 0x00, 0x18, 0xf4, 0x58, 0x93, 0xec, +0xaf, 0x0c, 0x87, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0xad, 0xaa, 0xb9, +0x1c, 0xfa, 0x59, 0x0b, 0x55, 0x38, 0xd3, 0x04, +0xd2, 0x84, 0x5e, 0xa4, 0xad, 0x66, 0x60, 0x9a, +0x46, 0xab, 0xda, 0x8e, 0x48, 0xfa, 0x69, 0xbf, +0xb6, 0x6e, 0xa8, 0x1c, 0x5f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0x71, +0x27, 0xd4, 0x55, 0x4c, 0x4a, 0xe7, 0xb0, 0xfc, +0xdf, 0xb4, 0xd5, 0x7f, 0x5f, 0x76, 0xbd, 0x5a, +0xa8, 0x9e, 0x33, 0xa8, 0xe3, 0x16, 0xbf, 0xea, +0x36, 0xa3, 0xf4, 0x6e, 0x76, 0xfb, 0x4a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0xfb, 0xba, 0xc6, 0xfc, 0x64, 0xef, 0x4d, +0xb4, 0x8e, 0x2c, 0xef, 0x1a, 0x2d, 0x5c, 0x70, +0xec, 0x68, 0x93, 0x6e, 0xf0, 0x0f, 0x25, 0x96, +0xef, 0x23, 0xe7, 0x24, 0x44, 0xda, 0xb1, 0x8c, +0x10, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0x0a, 0x7a, 0x53, 0x32, 0xd5, +0xf8, 0x59, 0xe1, 0x00, 0xda, 0x98, 0xf1, 0x10, +0x70, 0x8b, 0x6b, 0x53, 0xb2, 0xb8, 0xb4, 0x2f, +0x26, 0xee, 0x4c, 0x2d, 0xa8, 0xc6, 0xeb, 0x7f, +0x5a, 0x1f, 0x90, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0x4b, 0xd9, 0x77, +0x54, 0x6c, 0x96, 0xa0, 0x86, 0xe7, 0x01, 0xdb, +0x0d, 0xea, 0x62, 0x3a, 0xf4, 0xb2, 0xf2, 0xdf, +0x5f, 0x23, 0x06, 0xc9, 0x0f, 0x2c, 0x8b, 0xb8, +0x2b, 0x0a, 0xf8, 0x48, 0x73, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0x23, +0x46, 0x8a, 0x95, 0xa0, 0xf3, 0x15, 0x53, 0xf4, +0x3a, 0x64, 0x01, 0x16, 0x0c, 0x9d, 0x55, 0x60, +0x5f, 0x47, 0x42, 0xc8, 0x92, 0x38, 0xc3, 0x10, +0xc5, 0x10, 0x62, 0xfd, 0x86, 0x71, 0x27, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x84, 0x82, 0xf2, 0x58, 0xd3, 0x94, 0xe8, +0x28, 0x5e, 0x51, 0xe8, 0xc7, 0xb9, 0x99, 0x94, +0x86, 0x11, 0x99, 0x84, 0x8b, 0xf4, 0x2e, 0x3d, +0xa1, 0xde, 0x72, 0x67, 0x71, 0x8b, 0x99, 0x42, +0xac, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0x0b, 0xec, 0xcf, 0x2d, 0x68, +0xb3, 0x30, 0x9d, 0x26, 0xfe, 0x8b, 0x4e, 0x84, +0x6b, 0x43, 0x5c, 0xab, 0x5b, 0x4e, 0x1d, 0xad, +0x3c, 0x9d, 0xd3, 0x97, 0x2b, 0xb2, 0x1d, 0xcf, +0xab, 0xa0, 0x0b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0x5a, 0x69, 0xc3, +0x3f, 0xc1, 0x25, 0x5e, 0x1e, 0xd2, 0xe6, 0xc1, +0x9d, 0x5f, 0xe9, 0xb1, 0x04, 0xc4, 0x82, 0x2c, +0x4a, 0x2f, 0xdb, 0xb7, 0xd3, 0x13, 0x6d, 0x48, +0xb4, 0x9c, 0xf9, 0x26, 0x24, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0x2c, +0x8a, 0xac, 0x1d, 0x4a, 0xdc, 0x89, 0xd6, 0x73, +0x71, 0x4d, 0x87, 0x7d, 0x1b, 0x2f, 0x48, 0x77, +0x65, 0xf9, 0xfa, 0x5a, 0x78, 0x38, 0xe0, 0x17, +0x32, 0xcf, 0x73, 0x65, 0x4d, 0x64, 0x8b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0x24, 0x61, 0x18, 0x37, 0x2e, 0x68, 0xbb, +0x5e, 0xb4, 0x4b, 0xdb, 0xcd, 0x7d, 0xb4, 0xa9, +0x90, 0x10, 0xb1, 0x50, 0xa6, 0xb8, 0xfd, 0x20, +0xda, 0xff, 0x95, 0x99, 0xb2, 0x16, 0x15, 0xf3, +0xb2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0xdb, 0x89, 0x9a, 0xe3, 0xe3, +0x10, 0xc1, 0x2e, 0x59, 0x95, 0x17, 0xfc, 0x0a, +0x15, 0x7b, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x40, 0x3c, 0x4c, 0x2a, +0x6f, 0x0b, 0x85, 0xe6, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0x92, 0x1b, 0xac, +0x25, 0x39, 0x2e, 0x1b, 0x7c, 0x62, 0x15, 0xd5, +0xba, 0xd3, 0x2f, 0xdb, 0x9b, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0xf4, +0xf7, 0x76, 0xad, 0xa1, 0x09, 0xdf, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0x1c, +0x24, 0x7d, 0xe4, 0x93, 0xb6, 0x57, 0x10, 0xc7, +0xa5, 0xf4, 0x91, 0x0c, 0x77, 0x17, 0x4c, 0x9d, +0x1b, 0x4d, 0x37, 0x2d, 0x5c, 0x84, 0x7f, 0xea, +0x9b, 0xcc, 0x27, 0xf6, 0xb9, 0x6e, 0xa5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0xaa, 0x10, 0x37, 0xe7, 0x14, 0xad, 0x78, +0xaf, 0x61, 0x67, 0x7b, 0x7b, 0xa8, 0x55, 0xe0, +0xd9, 0x12, 0x6b, 0xc2, 0x62, 0x94, 0x92, 0x91, +0xe7, 0xfb, 0x04, 0x6c, 0x11, 0x15, 0x8e, 0xf0, +0xea, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0xa7, 0x26, 0xce, 0x3c, 0x6c, +0xc7, 0xf8, 0xbe, 0x81, 0xee, 0x64, 0x74, 0x27, +0xa2, 0x15, 0x64, 0x12, 0x10, 0x8d, 0xdf, 0x5e, +0x89, 0xd3, 0x3d, 0x67, 0xf4, 0x86, 0x02, 0xcb, +0x30, 0x97, 0xa1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0x50, 0xf5, 0x09, +0x69, 0xe8, 0x10, 0x1e, 0x99, 0x83, 0xb7, 0x07, +0xdf, 0xa2, 0xa5, 0xda, 0x9d, 0x1d, 0xe1, 0x23, +0x67, 0xa8, 0xac, 0x17, 0x10, 0x0c, 0x0b, 0x62, +0x7b, 0xc3, 0x33, 0x4d, 0x2d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0x9b, +0x35, 0xdc, 0xdb, 0x41, 0x90, 0x24, 0x35, 0x9d, +0xc9, 0x62, 0xd9, 0x01, 0xc6, 0xef, 0x6a, 0x8d, +0x13, 0x5b, 0x42, 0x09, 0x51, 0x29, 0xc1, 0xc3, +0xee, 0x7a, 0x2a, 0xd4, 0x50, 0x36, 0xf2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0x52, 0x31, 0xab, 0xf2, 0x90, 0x04, 0x1f, +0xac, 0x89, 0xc0, 0x48, 0xd9, 0x69, 0xf7, 0x2d, +0xeb, 0x4d, 0xc6, 0xb4, 0xe2, 0xab, 0x39, 0xc3, +0x89, 0x5c, 0xb3, 0x73, 0x58, 0x3a, 0xeb, 0x97, +0x64, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0x95, 0x61, 0x9d, 0x1f, 0x42, +0x59, 0x96, 0x53, 0x56, 0x90, 0x3a, 0x51, 0x52, +0xac, 0x99, 0x87, 0x03, 0x54, 0x64, 0x8b, 0x02, +0x81, 0x85, 0x78, 0x4c, 0xdd, 0x54, 0x77, 0x0a, +0xb2, 0xee, 0x99, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0x45, 0x48, 0x63, +0x34, 0xd1, 0xf0, 0xeb, 0xc5, 0x7c, 0xf5, 0xe5, +0xa9, 0x93, 0x69, 0xe9, 0x3a, 0x42, 0xda, 0xda, +0x86, 0x01, 0x16, 0x19, 0x03, 0xd1, 0xd1, 0x17, +0xaa, 0xbf, 0xfa, 0x15, 0x00, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0x82, +0xae, 0x9c, 0xd6, 0xa2, 0x45, 0x96, 0xb8, 0x1f, +0xe1, 0x14, 0x40, 0x98, 0x52, 0xdc, 0x67, 0xc0, +0xb9, 0xed, 0x12, 0x3b, 0x3b, 0x7d, 0x08, 0x09, +0xa4, 0x31, 0xe2, 0x78, 0xad, 0x87, 0xa9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0xfd, 0x91, 0x67, 0x6c, 0xdb, 0xb7, 0xfe, +0xb3, 0x57, 0x0b, 0x72, 0x84, 0x7a, 0xc5, 0x90, +0x8b, 0x4b, 0x55, 0xc0, 0x4c, 0xe4, 0x9e, 0xd4, +0xc1, 0x2d, 0xbb, 0x6a, 0xa5, 0x3e, 0x80, 0xdb, +0xb9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0x29, 0x5d, 0x50, 0x7e, 0x31, +0x65, 0x5f, 0xd6, 0xf0, 0x40, 0x3a, 0x68, 0x9a, +0xef, 0x3b, 0xd3, 0x3e, 0x2e, 0xed, 0x9a, 0x7f, +0x95, 0x83, 0x73, 0xf3, 0x88, 0xcb, 0xf7, 0xab, +0x83, 0xec, 0xd7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0xd6, 0x19, 0x4c, +0x59, 0x1a, 0x13, 0x54, 0x88, 0xc6, 0xfb, 0x35, +0xd6, 0x7c, 0x4c, 0xe5, 0x7a, 0xc6, 0x93, 0x0d, +0x0a, 0x5a, 0x29, 0x4c, 0x24, 0x8b, 0x95, 0xb2, +0xfd, 0xd2, 0xfb, 0xd5, 0xb7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0xee, +0x5d, 0x8e, 0xe3, 0x81, 0x72, 0x5a, 0x49, 0x31, +0xd8, 0x50, 0x03, 0x61, 0x50, 0x37, 0xdd, 0xf6, +0x70, 0x24, 0x86, 0x78, 0x32, 0x5b, 0x3d, 0x02, +0x6a, 0x71, 0x76, 0xd7, 0xf0, 0xa2, 0x97, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0x13, 0xcf, 0x68, 0x10, 0xe3, 0x99, 0x92, +0xee, 0x3c, 0x11, 0xc1, 0x7e, 0xc6, 0xe6, 0x55, +0x8a, 0x4a, 0x1e, 0x78, 0xb8, 0x24, 0xb4, 0xa6, +0xc1, 0xda, 0x7c, 0xd7, 0xc2, 0x1e, 0x0d, 0xdc, +0xd2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0xe3, 0x8f, 0xb2, 0xa1, 0x03, +0xd8, 0x1f, 0xc4, 0xcb, 0xec, 0x9d, 0x12, 0x66, +0x09, 0x37, 0xc9, 0x55, 0xe3, 0xab, 0x82, 0xcb, +0x32, 0xff, 0xf3, 0x99, 0x22, 0x84, 0x19, 0x38, +0xcd, 0x78, 0x52, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0x02, 0x8f, 0xa7, +0x43, 0xbb, 0xa0, 0x64, 0x35, 0x74, 0x8d, 0x7b, +0x11, 0x7c, 0x1d, 0x31, 0x53, 0xa2, 0x87, 0x98, +0x10, 0x70, 0x0e, 0x49, 0x66, 0x34, 0xa5, 0xcf, +0x9d, 0x6d, 0xe5, 0xd9, 0x21, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0x85, +0xaf, 0x64, 0x9a, 0xd4, 0x59, 0xea, 0x54, 0xd9, +0xb9, 0xb0, 0xdb, 0xe3, 0x2f, 0x82, 0xa6, 0x9c, +0x72, 0x39, 0x49, 0x13, 0x40, 0xd1, 0xff, 0xd1, +0xdd, 0x26, 0xec, 0x5d, 0xaa, 0x19, 0xc1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0x34, 0x68, 0x2b, 0xd7, 0x12, 0x22, 0xdc, +0x23, 0x3b, 0xf4, 0x0e, 0xba, 0xe1, 0x6b, 0x0a, +0x04, 0x8d, 0xe4, 0x20, 0x96, 0x33, 0xa9, 0x8e, +0x0a, 0xc4, 0x44, 0x4d, 0x24, 0x0f, 0xb8, 0xb6, +0x4b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0xe0, 0x00, 0x97, 0x7b, 0x93, +0x75, 0x65, 0x1c, 0x6a, 0xdb, 0x61, 0x1a, 0xcb, +0x77, 0xa4, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x5d, 0x86, 0x8b, 0xac, +0xc7, 0xdd, 0x10, 0x9b, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x92, 0x80, 0x65, +0x4b, 0xdd, 0x02, 0x85, 0x15, 0x7e, 0xbe, 0x39, +0x93, 0xef, 0xf0, 0xb3, 0x9b, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x80, +0x02, 0x67, 0x4e, 0xd0, 0x3f, 0x01, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0xd3, +0xc4, 0x2f, 0x23, 0xc6, 0xb6, 0xc4, 0x3f, 0xbc, +0xae, 0x1b, 0x82, 0x57, 0x0f, 0x9f, 0x28, 0x52, +0x49, 0xd4, 0x21, 0xc1, 0x8a, 0xb6, 0x74, 0x86, +0xd7, 0x84, 0xdc, 0xd7, 0xe4, 0x86, 0x01, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0x0c, 0x77, 0x97, 0x30, 0x73, 0xae, 0x65, +0xfa, 0x8c, 0x11, 0x00, 0x07, 0xb5, 0xa9, 0x5c, +0x03, 0x0f, 0xd7, 0x1e, 0xb2, 0xc9, 0xe4, 0xa3, +0xba, 0x70, 0x7b, 0x19, 0xbd, 0xbb, 0x4e, 0x52, +0x39, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0x70, 0x41, 0xd2, 0x60, 0xde, +0xc8, 0x78, 0x51, 0x77, 0x30, 0x1f, 0x39, 0x2d, +0xf0, 0xf3, 0xd6, 0xb5, 0xbd, 0x14, 0xf9, 0x4c, +0x0b, 0xbd, 0x6a, 0x4f, 0x65, 0xff, 0x3d, 0xf0, +0x66, 0x86, 0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0xa9, 0x29, 0x79, +0x0d, 0x2a, 0xf6, 0x0e, 0x27, 0x43, 0x02, 0xf4, +0xf2, 0x27, 0x46, 0x54, 0x76, 0x0b, 0xcf, 0xbc, +0x29, 0xf2, 0xa6, 0xb7, 0xd8, 0xa7, 0x81, 0x56, +0x77, 0x02, 0x2c, 0x13, 0x22, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0xad, +0xee, 0x07, 0x48, 0xcd, 0x50, 0x65, 0xe3, 0xa0, +0x0f, 0x11, 0x7e, 0xf5, 0x07, 0x9b, 0x6e, 0xc9, +0xb4, 0xbf, 0xc7, 0x26, 0xb7, 0xc1, 0x34, 0x53, +0xe2, 0x9c, 0xfc, 0x04, 0xc8, 0xc8, 0xc1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0x60, 0xae, 0x05, 0x1d, 0xfe, 0x3f, 0x67, +0x29, 0x27, 0xaa, 0x9e, 0x11, 0xda, 0x41, 0x36, +0x0f, 0x41, 0x48, 0xef, 0xb4, 0x2e, 0xc3, 0x5d, +0x05, 0x0f, 0x2e, 0x55, 0xad, 0x3e, 0x1a, 0x55, +0x6c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0xb4, 0xc0, 0xde, 0x6f, 0x89, +0x58, 0x85, 0xb3, 0xc6, 0x7c, 0xdf, 0x58, 0xf1, +0x96, 0x00, 0x61, 0x52, 0x6b, 0x2d, 0x45, 0xa9, +0xc7, 0x6a, 0x16, 0x8f, 0xd1, 0xf0, 0x07, 0x16, +0x43, 0x89, 0xae, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0x08, 0x39, 0x5a, +0xb2, 0x57, 0x7b, 0x49, 0x96, 0x2a, 0x34, 0x7a, +0x90, 0x96, 0x8c, 0x4f, 0x26, 0x87, 0xd0, 0x37, +0xd9, 0x49, 0x53, 0x51, 0x60, 0x98, 0x4f, 0x57, +0x7a, 0xb9, 0x20, 0x33, 0xbe, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x4c, +0x34, 0xa7, 0x9e, 0x54, 0x80, 0x50, 0xe7, 0x46, +0x02, 0xbd, 0xc0, 0x15, 0x9b, 0x7f, 0x1d, 0x89, +0xa6, 0xcb, 0x61, 0x15, 0xb2, 0x13, 0x8a, 0x36, +0xb5, 0x85, 0xf8, 0x38, 0xf2, 0x04, 0xd5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0xe6, 0x38, 0x4c, 0x52, 0x2d, 0x11, 0x54, +0x77, 0x97, 0xe0, 0xb4, 0xf9, 0xf0, 0x44, 0xe5, +0xbd, 0xec, 0xe5, 0x41, 0x3c, 0xec, 0x98, 0x31, +0xcd, 0xdc, 0x9c, 0xd0, 0xb6, 0xaa, 0xef, 0xde, +0x03, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0x53, 0xc3, 0x1e, 0x1b, 0x9e, +0x3e, 0x43, 0xe0, 0x48, 0xc7, 0xc2, 0x0f, 0xd7, +0x24, 0x9d, 0x7b, 0x60, 0xf0, 0xcb, 0xc8, 0x94, +0xd6, 0x5f, 0x0f, 0x56, 0x36, 0x70, 0xc7, 0xce, +0x1d, 0xe3, 0xcb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0xf6, 0x0d, 0xa5, +0x56, 0x69, 0x34, 0x08, 0x16, 0xb7, 0xc4, 0xdb, +0x69, 0xb5, 0x38, 0x9a, 0x0e, 0xb6, 0xc4, 0x6f, +0x5c, 0x14, 0xe2, 0x71, 0xf8, 0xf2, 0x1c, 0x23, +0x2a, 0x0e, 0x51, 0x5c, 0x25, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0x02, +0x30, 0xc7, 0x15, 0x0b, 0xe6, 0xa6, 0x1b, 0x5a, +0x27, 0xec, 0x94, 0x45, 0x1a, 0x8a, 0x5e, 0x24, +0x1a, 0x18, 0x2f, 0x0e, 0xe9, 0xf3, 0x3d, 0x97, +0x73, 0xa6, 0xf3, 0x99, 0x69, 0xee, 0x93, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0xd3, 0x86, 0x46, 0x5d, 0x09, 0x5d, 0xd0, +0xe1, 0x7a, 0x8b, 0x4f, 0xe3, 0x3a, 0xe5, 0x66, +0x32, 0x69, 0xd4, 0xaa, 0xf2, 0xda, 0xb5, 0xb3, +0xa8, 0x1b, 0xbc, 0x40, 0x5e, 0xf6, 0x6e, 0x48, +0x8b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0xfc, 0x67, 0xb3, 0xc3, 0x8b, +0xef, 0xbd, 0x8b, 0xb2, 0x94, 0xc9, 0x71, 0x76, +0x1a, 0x39, 0xb3, 0xfc, 0xc4, 0x25, 0xed, 0xac, +0xd6, 0xd5, 0x1a, 0xb6, 0xe9, 0x0b, 0x51, 0x21, +0x76, 0x95, 0xfd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0x84, 0x1e, 0x61, +0x9c, 0xc0, 0x03, 0x95, 0x1f, 0xba, 0x1b, 0x5f, +0x2e, 0xab, 0x92, 0xd3, 0x4e, 0xb8, 0x2e, 0x09, +0xde, 0x60, 0x44, 0x36, 0xa0, 0x64, 0x11, 0x9a, +0x74, 0x4b, 0x66, 0xc6, 0x68, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0xa0, +0x6b, 0x2b, 0xd8, 0xa5, 0x44, 0x0b, 0x1f, 0xad, +0x04, 0x83, 0xc3, 0xce, 0x03, 0x45, 0x66, 0x37, +0xe1, 0x88, 0x10, 0xf1, 0x03, 0x4a, 0x29, 0x90, +0xde, 0xe5, 0x61, 0x59, 0x68, 0xeb, 0x1a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0xff, 0x76, 0x11, 0xb1, 0xac, 0x76, 0x9f, +0x1b, 0x7d, 0x99, 0x02, 0xf9, 0xb4, 0xc3, 0xba, +0xaf, 0x3c, 0x54, 0xa0, 0x62, 0xed, 0xa1, 0x5c, +0x3b, 0xa6, 0x72, 0xe4, 0x6a, 0xf6, 0x53, 0x75, +0x36, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0x70, 0x10, 0xb4, 0xe4, 0x81, +0x4e, 0xe9, 0xd8, 0x64, 0x86, 0x60, 0xb0, 0xdb, +0x35, 0x96, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x83, 0xa4, 0x09, 0x97, +0x81, 0xd2, 0x18, 0x6f, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0x41, 0x82, 0x6b, +0x8b, 0x49, 0x3a, 0xdd, 0x53, 0x0c, 0xeb, 0x80, +0x80, 0x56, 0x54, 0xc6, 0xb9, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0xe6, +0x80, 0x84, 0x27, 0x74, 0xe0, 0x92, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0xf0, +0x24, 0x87, 0x02, 0x6f, 0x5f, 0x92, 0xa8, 0x94, +0x02, 0xe0, 0x73, 0x53, 0x38, 0x68, 0xc1, 0x15, +0x2a, 0xd8, 0x84, 0x22, 0x84, 0x1b, 0x43, 0x78, +0x88, 0xd3, 0x9f, 0x68, 0xb4, 0xf9, 0x09, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0x5a, 0xab, 0x67, 0x6c, 0x52, 0x5d, 0x56, +0x17, 0xfc, 0x45, 0xfc, 0xc5, 0x7b, 0xd7, 0x76, +0x99, 0x8f, 0x1e, 0x5f, 0x37, 0x08, 0xa4, 0x8b, +0x6d, 0x15, 0xfa, 0x73, 0x33, 0xd1, 0xa2, 0x55, +0x2d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0x64, 0x20, 0x5f, 0x98, 0x89, +0xc0, 0xd7, 0xe6, 0x86, 0xc6, 0xdd, 0x27, 0x73, +0x80, 0xdc, 0xd4, 0x07, 0xc3, 0x70, 0x7c, 0xc9, +0x70, 0x26, 0x19, 0x1e, 0x78, 0x36, 0xc6, 0x43, +0x1e, 0x03, 0xb7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x2a, 0x88, 0x6b, +0xd6, 0x3b, 0x37, 0xac, 0x62, 0xd4, 0x9a, 0xe1, +0x35, 0xc5, 0x81, 0x21, 0x11, 0xe9, 0xc3, 0x1a, +0x2f, 0xbc, 0x93, 0x4c, 0x05, 0x6c, 0xd2, 0xed, +0x54, 0xc6, 0xf9, 0x1f, 0x70, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0xeb, +0xdd, 0x80, 0xb9, 0x6d, 0xcd, 0xd3, 0x0d, 0x00, +0x02, 0xf9, 0xa8, 0xd7, 0x5f, 0x07, 0x2f, 0xe1, +0x81, 0x67, 0x07, 0x5f, 0x40, 0xcb, 0xd3, 0xda, +0x91, 0x36, 0x9b, 0xcb, 0x0f, 0xd1, 0xcd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x48, 0xfb, 0x72, 0x8b, 0xbb, 0x1a, 0x44, +0x90, 0xa9, 0xa9, 0xe9, 0x80, 0xce, 0xa9, 0x39, +0x16, 0xd3, 0xe2, 0x03, 0x11, 0x70, 0x2c, 0x36, +0x66, 0xaf, 0xd0, 0x28, 0xfb, 0xd5, 0x44, 0xe2, +0x14, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0x4b, 0xbe, 0x4a, 0x85, 0xd6, +0xbd, 0x6f, 0x7c, 0xd9, 0xa9, 0xf4, 0xd1, 0x12, +0x1c, 0x2f, 0x04, 0x8f, 0x48, 0x8f, 0x49, 0xbd, +0x98, 0xfd, 0x31, 0x92, 0xba, 0x21, 0x73, 0x95, +0x2d, 0xca, 0x4f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0xc7, 0xef, 0xa1, +0x57, 0x63, 0xe2, 0x17, 0x44, 0x5e, 0x92, 0x22, +0x25, 0x7b, 0x9e, 0x3b, 0x75, 0x2b, 0xd5, 0x7b, +0xbf, 0xc9, 0x73, 0x77, 0xab, 0xd7, 0x51, 0x68, +0xf6, 0x74, 0x7c, 0xea, 0x33, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0xf1, +0x7d, 0x9f, 0x3a, 0xdc, 0x1a, 0x2d, 0xc5, 0xc9, +0xfa, 0xd0, 0x98, 0x96, 0x40, 0x76, 0x11, 0xe7, +0x63, 0xa3, 0xe8, 0xc9, 0xec, 0x1b, 0xfe, 0x65, +0x34, 0x1c, 0x87, 0x8c, 0x65, 0x7c, 0xd1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0xf5, 0x52, 0xe7, 0x40, 0x49, 0xdc, 0xdd, +0x8a, 0x04, 0x4d, 0xce, 0x05, 0xaf, 0x9b, 0x9b, +0x28, 0x15, 0x3e, 0xbd, 0x59, 0x65, 0x39, 0x27, +0xc0, 0x05, 0x1a, 0x14, 0x05, 0xd2, 0xa5, 0x54, +0xa6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0x4b, 0x90, 0x5b, 0xc3, 0xb2, +0x50, 0x32, 0x0f, 0x95, 0xcd, 0x84, 0x6d, 0x6a, +0xc9, 0x14, 0x65, 0x60, 0x50, 0x80, 0x5f, 0xc0, +0x82, 0xa2, 0x39, 0x44, 0x27, 0x57, 0x23, 0x45, +0x69, 0x62, 0x74, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0x04, 0xfe, 0x96, +0xec, 0x05, 0x61, 0x79, 0xfa, 0x18, 0x73, 0x86, +0x6e, 0x28, 0xae, 0x48, 0xcd, 0x63, 0x64, 0x8f, +0xbf, 0x59, 0xd5, 0xaf, 0x7a, 0xad, 0x72, 0x21, +0x32, 0x6e, 0x09, 0x19, 0x30, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0x15, +0xff, 0x1d, 0x21, 0x9f, 0x6c, 0x4e, 0x62, 0x61, +0x4c, 0x46, 0xc8, 0x31, 0xa7, 0xda, 0xc7, 0xeb, +0xb6, 0x0f, 0xed, 0xd8, 0x59, 0x54, 0x8e, 0x25, +0x00, 0xe7, 0x9b, 0x6c, 0xaa, 0x25, 0x5f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0xe8, 0xd7, 0xf3, 0xc8, 0x3e, 0x05, 0x71, +0xc4, 0x07, 0x8a, 0x20, 0x71, 0xad, 0x0a, 0xc4, +0xd8, 0xb3, 0x98, 0x47, 0xbc, 0xc2, 0x09, 0x71, +0xb8, 0x3d, 0xc9, 0xbb, 0xbf, 0x5f, 0xd6, 0xc4, +0xee, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0x7f, 0x13, 0xdb, 0x18, 0x8c, +0x52, 0x8b, 0xc1, 0xda, 0xaf, 0xc5, 0x50, 0xe3, +0x9f, 0x46, 0x98, 0xb6, 0x95, 0x5a, 0x5e, 0xad, +0x6e, 0xe3, 0xc9, 0xf2, 0x7a, 0x4b, 0x3a, 0xd9, +0x6d, 0x10, 0x50, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0x07, 0x2c, 0xcb, +0xf8, 0xe8, 0x59, 0x56, 0x53, 0x7a, 0xb4, 0x81, +0x93, 0xf1, 0xb5, 0x04, 0xb6, 0x9d, 0x2b, 0x43, +0xba, 0x3a, 0xec, 0x0b, 0x3c, 0x9a, 0x61, 0x6e, +0xde, 0x69, 0x48, 0x8e, 0x20, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0x28, +0x2c, 0xf9, 0xed, 0xe1, 0x92, 0x72, 0xe3, 0x7a, +0xe9, 0x7e, 0x67, 0x1e, 0x47, 0xc3, 0x4f, 0xf4, +0x7e, 0xce, 0x44, 0x77, 0xc2, 0x4a, 0xbe, 0xce, +0xb4, 0x3c, 0x79, 0x78, 0xc3, 0x6d, 0x4f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0x02, 0x8b, 0x09, 0xdb, 0xde, 0xdd, 0xa9, +0x7c, 0x4d, 0x92, 0x21, 0x36, 0xbd, 0x21, 0x09, +0x05, 0x75, 0xcc, 0x24, 0xdb, 0xca, 0x44, 0xcd, +0xf7, 0x0c, 0x4b, 0xd6, 0x02, 0x9a, 0xb0, 0x8e, +0x1b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0xcd, 0xcd, 0x6c, 0xc1, 0x84, +0x37, 0x49, 0x84, 0x27, 0x11, 0x8e, 0xdd, 0x73, +0x4a, 0x1c, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x3f, 0x61, 0xc5, 0xbd, +0x01, 0x5a, 0xde, 0x3a, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0xf3, 0x30, 0xdd, +0xd8, 0x84, 0x9a, 0x8a, 0x35, 0x36, 0xd0, 0xfc, +0x0e, 0x12, 0x4e, 0xf7, 0xee, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x5f, +0x5f, 0xee, 0xbb, 0x2b, 0xf4, 0xbd, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0x9a, +0x54, 0x40, 0x8e, 0x7c, 0x92, 0x0d, 0x0c, 0x81, +0x79, 0xc3, 0x33, 0xdc, 0x20, 0xd9, 0xde, 0xbe, +0xde, 0xff, 0x64, 0x5d, 0xf4, 0x33, 0xc8, 0x5d, +0xdb, 0xf7, 0x94, 0xe3, 0x45, 0xa5, 0x91, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0xc0, 0x09, 0x3c, 0xac, 0x05, 0x5b, 0x7b, +0xc5, 0x10, 0x93, 0x4d, 0x7d, 0xbb, 0x00, 0x1d, +0x80, 0x56, 0xf6, 0xed, 0x6f, 0xf8, 0x05, 0xe0, +0xa1, 0x5c, 0x96, 0xf4, 0x4c, 0x27, 0x5c, 0x8f, +0x99, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0x2f, 0xaa, 0x5c, 0x36, 0xa4, +0xd0, 0x50, 0x35, 0x3a, 0x1b, 0x5c, 0x08, 0x89, +0xe1, 0x24, 0xa8, 0x19, 0x44, 0x9d, 0x24, 0x59, +0xa4, 0x58, 0x84, 0x87, 0x89, 0xd2, 0x11, 0x53, +0xbb, 0x0e, 0xd4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0x11, 0x94, 0xae, +0xe5, 0xcf, 0x73, 0x7e, 0x6b, 0xfa, 0x00, 0x15, +0x0b, 0xf7, 0xc5, 0x00, 0x76, 0x0e, 0xa1, 0x52, +0x9b, 0x1a, 0x42, 0xde, 0x05, 0xdf, 0xed, 0xc1, +0xe2, 0xf8, 0xb6, 0x36, 0xc5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0xca, +0x2d, 0x03, 0xa5, 0xa8, 0x72, 0xac, 0x1f, 0x5e, +0xa2, 0x82, 0x51, 0xf7, 0x58, 0x61, 0x83, 0x89, +0xc2, 0xed, 0xdf, 0xab, 0xee, 0x39, 0xef, 0xaf, +0xd2, 0xd0, 0xf2, 0xb9, 0xba, 0x2c, 0xe6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0xc3, 0x05, 0xe1, 0x6c, 0xd0, 0xeb, 0x55, +0x5b, 0x71, 0x55, 0xdf, 0xd0, 0xd8, 0xbd, 0x15, +0x9e, 0xf1, 0x97, 0x63, 0xff, 0x27, 0x1f, 0x93, +0xa5, 0xdd, 0xdf, 0x94, 0x43, 0xfe, 0xe2, 0xed, +0xf6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0x04, 0xd3, 0xd6, 0x6c, 0x4e, +0x97, 0xb6, 0x87, 0xca, 0x33, 0x43, 0x79, 0x68, +0xaf, 0xa3, 0xa3, 0x73, 0x5f, 0xc1, 0xd3, 0x1b, +0x84, 0x12, 0x64, 0xbe, 0x71, 0xbb, 0x78, 0x04, +0x03, 0xf7, 0x35, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0x4c, 0x82, 0xe2, +0xad, 0x31, 0x4c, 0x02, 0x97, 0x8a, 0xdc, 0x88, +0x24, 0x47, 0xf2, 0x2c, 0x90, 0x1b, 0xee, 0x23, +0x0d, 0xf8, 0x8e, 0xeb, 0x57, 0x8a, 0xe7, 0x20, +0x8b, 0x73, 0xc5, 0x47, 0x8c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0xce, +0x4a, 0xd1, 0x48, 0xb3, 0x09, 0x9b, 0xc0, 0x57, +0x84, 0xff, 0x13, 0x89, 0x10, 0x15, 0xd8, 0xa1, +0x23, 0x24, 0x74, 0x47, 0xf8, 0xbc, 0xc4, 0x26, +0x41, 0x53, 0x41, 0x67, 0x6c, 0x94, 0xcd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0x34, 0x0e, 0x7e, 0x0c, 0x23, 0xd2, 0x57, +0x87, 0xe0, 0xce, 0x52, 0x47, 0xa0, 0xbe, 0x01, +0x0d, 0x69, 0x72, 0x4f, 0x33, 0x14, 0xe0, 0xb6, +0xce, 0xae, 0x46, 0x35, 0xdd, 0xb5, 0x1c, 0x74, +0xfe, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0x64, 0x15, 0xab, 0x8f, 0x00, +0x61, 0x98, 0xda, 0x0b, 0xac, 0x27, 0x39, 0xb2, +0x6b, 0x0e, 0xf2, 0x06, 0x72, 0x63, 0xcb, 0xd9, +0x06, 0x22, 0xbd, 0x3c, 0xc4, 0x23, 0xeb, 0x62, +0xc3, 0x1a, 0x45, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0xaf, 0xf0, 0xaf, +0x47, 0x55, 0x55, 0x7f, 0x93, 0x5b, 0x1a, 0x9e, +0x75, 0xf6, 0xe1, 0xf4, 0xd3, 0x43, 0x6b, 0xc7, +0x16, 0x03, 0x9c, 0x5e, 0xbe, 0xb7, 0xff, 0x57, +0xd9, 0xa2, 0x19, 0x67, 0xbe, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0x6f, +0x5f, 0xac, 0xb0, 0x10, 0x76, 0x75, 0x34, 0x02, +0x09, 0xc0, 0xbe, 0xbe, 0xad, 0x5d, 0xc1, 0x20, +0x26, 0x9b, 0xfa, 0x4f, 0x89, 0xf5, 0xf4, 0x50, +0x5a, 0x2c, 0x46, 0x8d, 0xe6, 0xfa, 0x25, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0x60, 0x3e, 0xf5, 0xc6, 0xb4, 0x9c, 0x5e, +0x70, 0x49, 0xfe, 0x36, 0xe9, 0xa4, 0x6c, 0xa3, +0x4d, 0x95, 0x14, 0x87, 0xfe, 0xd6, 0xbe, 0x71, +0x3e, 0x69, 0x62, 0x93, 0xd3, 0xe8, 0x06, 0xb4, +0x7f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0x50, 0xe5, 0x94, 0x7c, 0xa8, +0xd1, 0xa4, 0x5a, 0xa9, 0xe3, 0xab, 0x6c, 0xe7, +0x71, 0x1e, 0x5f, 0xe9, 0x23, 0xd8, 0xce, 0xb8, +0x74, 0x57, 0xdb, 0xaa, 0xde, 0xd9, 0xe5, 0x09, +0xdf, 0xea, 0x58, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0x1e, 0x57, 0x79, +0x57, 0x1d, 0x80, 0xa3, 0x9b, 0xe8, 0x53, 0x14, +0xef, 0x36, 0xb5, 0x7b, 0xf3, 0x4f, 0x51, 0xde, +0x88, 0x93, 0xd4, 0xef, 0x08, 0x75, 0xb7, 0x02, +0x69, 0xe9, 0x21, 0xc6, 0x29, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0x13, +0x01, 0xd1, 0x87, 0x7b, 0xc7, 0xca, 0x2f, 0xe8, +0x63, 0x0e, 0x9f, 0xd9, 0xa7, 0x49, 0x62, 0xcd, +0x6e, 0x01, 0xfe, 0xca, 0x4c, 0xaa, 0xd3, 0x50, +0x44, 0x68, 0xc4, 0x33, 0x15, 0xa0, 0x73, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0x8d, 0x33, 0xd8, 0x5d, 0x85, 0x7f, 0x14, +0xc6, 0xfb, 0x75, 0xb3, 0x94, 0xdb, 0xa4, 0xc8, +0xfe, 0x34, 0x17, 0x5e, 0xcf, 0x81, 0x3f, 0xcb, +0xbe, 0xf2, 0xfe, 0x2d, 0x76, 0x81, 0x10, 0x3b, +0x26, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0x46, 0xbf, 0xa9, 0xdf, 0x43, +0x26, 0x26, 0x23, 0x14, 0x5c, 0xb9, 0xee, 0x07, +0xba, 0xaa, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x22, 0xe8, 0xd8, 0x78, +0x7f, 0xf2, 0xf5, 0x69, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x58, 0xbf, 0x60, +0xd5, 0x88, 0x33, 0x81, 0xe2, 0xb3, 0xdf, 0x01, +0xf4, 0x0f, 0x57, 0x3d, 0xa8, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbb, 0xb0, +0x9f, 0x56, 0x6b, 0x6b, 0x05, 0x3a, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0x37, +0xa2, 0xce, 0x10, 0xc5, 0x16, 0x2f, 0x98, 0x69, +0x04, 0xc1, 0x26, 0x43, 0xf0, 0xb7, 0xa7, 0x10, +0x54, 0x90, 0x59, 0x74, 0x18, 0x51, 0x88, 0x99, +0xda, 0x8d, 0x57, 0x35, 0x7b, 0x05, 0xb6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x01, 0xd3, 0xc9, 0x02, 0x6d, 0x22, 0xf6, +0xad, 0x67, 0x0f, 0x69, 0xb1, 0x91, 0x9a, 0xfe, +0x88, 0x2c, 0x8a, 0xd0, 0xa2, 0xe9, 0xbc, 0xd3, +0x09, 0xd1, 0x4b, 0x13, 0xa6, 0x0b, 0xd9, 0x5a, +0x23, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0xe4, 0x89, 0x3a, 0x28, 0xb3, +0xfc, 0xc4, 0xfd, 0x8a, 0x50, 0x78, 0x25, 0xb6, +0xd3, 0x1e, 0x16, 0xac, 0xd3, 0xba, 0x1b, 0x61, +0x1f, 0x68, 0xc6, 0xfa, 0x02, 0x60, 0xe0, 0x3b, +0xc1, 0xf0, 0x1f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0x18, 0x9e, 0xea, +0x16, 0xa4, 0xd7, 0xbb, 0xe1, 0x32, 0x42, 0x56, +0x6e, 0xab, 0x57, 0x76, 0x0d, 0xc5, 0x95, 0x37, +0xfe, 0x9b, 0x95, 0xc9, 0x8b, 0x17, 0xbc, 0x52, +0xd5, 0x91, 0x84, 0x42, 0x5d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x9d, +0xa1, 0x71, 0x7d, 0x33, 0xbb, 0x2c, 0x75, 0x24, +0x3f, 0xb9, 0x0b, 0x93, 0x32, 0x14, 0x28, 0xc5, +0x0e, 0x50, 0x72, 0xac, 0xdc, 0xa4, 0x1f, 0xa3, +0xaa, 0x42, 0x69, 0x16, 0xf1, 0x15, 0xf3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0xe7, 0x4f, 0x2a, 0x6d, 0xdc, 0x0b, 0x3b, +0xd4, 0x1b, 0xcd, 0x67, 0xae, 0xa5, 0xfc, 0xb7, +0x2c, 0xc6, 0xd9, 0x2e, 0x7a, 0xa4, 0xad, 0x3e, +0x85, 0x62, 0xb5, 0x58, 0x85, 0xd9, 0xe2, 0x3c, +0xa6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0xd6, 0x0e, 0xe6, 0x07, 0xf6, +0xa4, 0xb3, 0x1b, 0x09, 0xfa, 0xa0, 0x32, 0x05, +0x27, 0x8a, 0xcd, 0x88, 0xa5, 0x1d, 0x1b, 0x86, +0x89, 0x16, 0x53, 0x00, 0x44, 0x68, 0x71, 0x03, +0x79, 0x57, 0xf6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0x58, 0x7f, 0x94, +0x78, 0x02, 0xb0, 0x48, 0x97, 0xda, 0xcc, 0xbf, +0xa2, 0xb9, 0x13, 0x66, 0x61, 0x4c, 0xe6, 0xe0, +0x5b, 0x4f, 0xf5, 0x66, 0xab, 0xd2, 0x98, 0x46, +0x83, 0xb6, 0x0c, 0x6c, 0x16, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x8a, +0x7c, 0x76, 0xd6, 0x2c, 0xe0, 0x40, 0xb6, 0x9c, +0x30, 0xe5, 0xec, 0x02, 0x9e, 0xe2, 0xb1, 0x85, +0xf3, 0xa7, 0x38, 0xbf, 0x19, 0xee, 0xf5, 0xbe, +0xc2, 0xee, 0x75, 0x05, 0x1e, 0xab, 0x8f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0x82, 0xfa, 0xfc, 0x1b, 0x48, 0x05, 0x04, +0x87, 0xef, 0x85, 0x89, 0x64, 0x58, 0xbb, 0x97, +0x05, 0x0d, 0x11, 0xf6, 0xff, 0xba, 0x64, 0x28, +0xf8, 0xf9, 0x45, 0xe8, 0xeb, 0xfa, 0xc2, 0xdf, +0x3f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0x94, 0xa2, 0x43, 0x87, 0xf6, +0xda, 0x24, 0x3b, 0xc4, 0xb8, 0x97, 0x75, 0x10, +0x3a, 0x3e, 0x20, 0x91, 0x24, 0x25, 0x82, 0xcd, +0x94, 0xe2, 0xc8, 0x6b, 0xbd, 0xb5, 0xe9, 0xce, +0x83, 0x68, 0x1d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0xc1, 0xa7, 0x43, +0x1c, 0x12, 0xe2, 0xfe, 0x98, 0x16, 0x1b, 0x13, +0xab, 0x55, 0x8d, 0x67, 0x9d, 0x6f, 0x16, 0x0c, +0xbb, 0xc3, 0x15, 0x51, 0x0c, 0xac, 0xa7, 0xc4, +0xf6, 0x3d, 0xf2, 0x1b, 0x98, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x90, +0x02, 0x29, 0x1f, 0xc8, 0xcc, 0x93, 0x41, 0x29, +0xff, 0x5d, 0x45, 0xee, 0xf1, 0x42, 0x7d, 0x4f, +0x07, 0xbc, 0x3f, 0x4e, 0xe8, 0x92, 0xf3, 0x6c, +0xca, 0x1d, 0xc9, 0xd0, 0x58, 0xa5, 0xb5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0xdb, 0x41, 0x2f, 0xe2, 0x22, 0x31, 0x71, +0x7d, 0x79, 0x31, 0x4f, 0x9c, 0xd2, 0xea, 0xe8, +0x4f, 0xee, 0x02, 0xe0, 0xeb, 0x47, 0xbb, 0x4d, +0x6a, 0xe8, 0x7b, 0x9b, 0x41, 0x64, 0x10, 0x15, +0x25, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0x85, 0x8f, 0x03, 0xd1, 0x65, +0x10, 0x43, 0xba, 0xb4, 0x55, 0x4e, 0x16, 0x65, +0x6c, 0xd5, 0x18, 0x14, 0x87, 0x59, 0x09, 0x71, +0x93, 0xfd, 0x91, 0xb9, 0x30, 0x4a, 0x2c, 0x68, +0x6a, 0xdb, 0xce, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0xa9, 0x57, 0x93, +0x20, 0x7a, 0xe4, 0x2b, 0x35, 0xb1, 0x4b, 0x2d, +0x76, 0xac, 0x57, 0x91, 0x36, 0x26, 0x32, 0x41, +0x03, 0xd7, 0x1f, 0xf2, 0xeb, 0xbc, 0x26, 0x96, +0x7d, 0x9b, 0x51, 0xb2, 0x96, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0x4f, +0x24, 0x2e, 0x58, 0x0c, 0x70, 0x7b, 0x10, 0x82, +0x8d, 0x34, 0x1d, 0x7d, 0x44, 0x03, 0x67, 0x84, +0xe8, 0x80, 0x02, 0xd3, 0x76, 0x9c, 0x4f, 0x7a, +0x44, 0x72, 0xde, 0xeb, 0xb0, 0x4d, 0xda, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0xf7, 0x5d, 0x19, 0xe7, 0x22, 0x58, 0x92, +0xfd, 0x1e, 0x9a, 0x9a, 0x3c, 0xe0, 0xc8, 0xc6, +0x9c, 0x6f, 0x96, 0x9a, 0x2c, 0x44, 0x12, 0x8f, +0x58, 0xe5, 0xdf, 0xc6, 0xd3, 0x04, 0x90, 0xa3, +0xe9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0x9d, 0x24, 0xee, 0xb5, 0x56, +0xf1, 0xb5, 0x80, 0xdf, 0x2b, 0x66, 0x76, 0x6d, +0xa8, 0xad, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xc7, 0x55, 0x7e, 0x10, +0xc1, 0xf4, 0x1a, 0x4b, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0xfa, 0xcd, 0xb4, +0xa9, 0x47, 0xe9, 0x89, 0x50, 0xe7, 0xaf, 0x68, +0x5f, 0x42, 0xfc, 0x93, 0xfb, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb6, 0xe8, +0x81, 0x80, 0xf3, 0x11, 0x44, 0xd6, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0xf1, +0xdd, 0xdc, 0x8d, 0xe3, 0x77, 0x51, 0xb4, 0xdc, +0xfb, 0x52, 0x5f, 0x3b, 0xe3, 0xb8, 0x9c, 0xf3, +0xf1, 0x39, 0x9e, 0x7d, 0x75, 0x59, 0xa8, 0xb9, +0x6f, 0x92, 0xd4, 0xe1, 0xbb, 0x97, 0x09, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0xae, 0x0b, 0x89, 0x81, 0x45, 0x49, 0xb7, +0xe8, 0x8d, 0x01, 0x8b, 0xeb, 0x80, 0x8a, 0xde, +0x1e, 0xa4, 0xe1, 0x3e, 0xc9, 0x44, 0x11, 0x90, +0x72, 0xc8, 0xa2, 0x73, 0x91, 0x04, 0x52, 0xf5, +0x9f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x3b, 0xea, 0x4b, 0x1d, 0x1d, +0xc6, 0x2c, 0x78, 0x7a, 0xb3, 0x0d, 0x26, 0x41, +0x48, 0x05, 0x49, 0x21, 0xae, 0x79, 0x3b, 0x95, +0x18, 0x74, 0x09, 0x39, 0xaf, 0x84, 0xee, 0xcf, +0xdd, 0x4b, 0xcc, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0x72, 0x78, 0x42, +0x65, 0xc3, 0xdc, 0x3e, 0xeb, 0xd7, 0xcb, 0xce, +0xdb, 0xeb, 0x21, 0xf6, 0x5d, 0x04, 0xbb, 0x06, +0x4d, 0x79, 0x11, 0xa4, 0x29, 0xe1, 0x69, 0x1d, +0xc2, 0x20, 0xeb, 0xfe, 0x2e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0xbf, +0xcb, 0xc1, 0x9d, 0xc7, 0xca, 0xf1, 0x5e, 0x2a, +0x6e, 0x4c, 0x23, 0x88, 0x4c, 0x47, 0x63, 0x0f, +0xaf, 0xa3, 0x4b, 0x98, 0xa7, 0x2a, 0x65, 0x81, +0x1a, 0xd3, 0xc3, 0xfa, 0xf8, 0x67, 0xa1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x6c, 0x0d, 0xb1, 0xc7, 0xb3, 0xf4, 0xad, +0xef, 0x7f, 0x64, 0xee, 0xb2, 0x04, 0x62, 0xe4, +0xa8, 0x37, 0x3a, 0xb9, 0x4f, 0x48, 0x36, 0x74, +0x5a, 0xc6, 0x39, 0x75, 0x18, 0x73, 0xfb, 0x73, +0xa2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0x86, 0x4f, 0x96, 0x25, 0x0d, +0xb2, 0x5d, 0xa9, 0x5f, 0x24, 0x52, 0x4d, 0xcb, +0x12, 0x09, 0xf8, 0xc5, 0x96, 0x4c, 0xf3, 0x3d, +0xfd, 0x03, 0x36, 0x30, 0x10, 0x30, 0xd9, 0x99, +0xb2, 0xb5, 0x90, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0x96, 0xf2, 0xe7, +0xd5, 0x04, 0x0a, 0xcb, 0x66, 0x6d, 0x1d, 0x15, +0xdb, 0x54, 0x29, 0x4a, 0xd4, 0xdc, 0xd5, 0x77, +0xff, 0x9c, 0x83, 0x3b, 0x75, 0x73, 0xe3, 0xed, +0xf7, 0x7e, 0xae, 0x12, 0xb1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x82, +0x48, 0xa4, 0x29, 0x87, 0x5d, 0x3a, 0xfb, 0x7a, +0x34, 0x4f, 0x60, 0x43, 0xec, 0x06, 0x49, 0xff, +0xb3, 0x67, 0xbd, 0xfd, 0xaa, 0x43, 0x2b, 0xee, +0xc9, 0x9d, 0x19, 0x1c, 0x74, 0x63, 0x25, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0x07, 0x3e, 0x36, 0xd2, 0x7b, 0x82, 0xef, +0xf1, 0x30, 0x0f, 0x95, 0x44, 0x67, 0x78, 0xa7, +0xec, 0x14, 0x07, 0x7d, 0x02, 0xe7, 0x51, 0x7d, +0x73, 0x23, 0x8d, 0x13, 0x43, 0xb4, 0xa8, 0xa4, +0x2d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0x73, 0xb2, 0xc6, 0x5b, 0x7b, +0x66, 0x91, 0x4a, 0xcc, 0x3b, 0xb9, 0xda, 0xe3, +0x74, 0xd1, 0xfd, 0x08, 0x94, 0x65, 0xf0, 0x3c, +0x7f, 0xba, 0xc4, 0x79, 0xff, 0x55, 0x88, 0xc8, +0xbd, 0x9b, 0xf1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0x12, 0x00, 0x9b, +0x68, 0x9e, 0x18, 0x6b, 0xbf, 0x77, 0x4b, 0x1d, +0xda, 0x3c, 0x04, 0x06, 0xac, 0x6d, 0xf4, 0xa1, +0xf9, 0x9f, 0xf4, 0x74, 0x36, 0xed, 0x52, 0xa7, +0x03, 0xda, 0x3b, 0x59, 0xce, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0x87, +0x2b, 0x8b, 0x1c, 0x1d, 0x2e, 0x79, 0x5d, 0xc4, +0x75, 0x77, 0xc7, 0x1d, 0x16, 0xcc, 0xb1, 0x69, +0x2e, 0x8f, 0xc5, 0x88, 0x1b, 0xc2, 0xce, 0xf1, +0x40, 0x95, 0x70, 0xc7, 0xba, 0x03, 0x9b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0x1a, 0x4f, 0x3b, 0x8a, 0xa4, 0x0c, 0xe2, +0x8d, 0x0b, 0x3a, 0x1f, 0x60, 0x36, 0x11, 0xfd, +0x44, 0x81, 0x8a, 0x7e, 0xb6, 0x8c, 0xd4, 0x47, +0x98, 0x08, 0x9b, 0x15, 0x69, 0xc8, 0x4b, 0x34, +0xaa, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0x00, 0x18, 0x49, 0x39, 0x3c, +0x5c, 0x57, 0x39, 0xc0, 0xf8, 0x5d, 0x23, 0x4d, +0x70, 0x6c, 0x0d, 0xf9, 0xfa, 0x6a, 0xd4, 0x3b, +0x62, 0xc4, 0x7a, 0x98, 0xc4, 0xeb, 0xc8, 0x09, +0xb8, 0x80, 0xa2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0x13, 0x4b, 0xc9, +0x27, 0x0b, 0x40, 0x32, 0x5c, 0x7a, 0xed, 0x78, +0x14, 0xe0, 0xb2, 0x92, 0xf3, 0x8a, 0x49, 0xcf, +0x98, 0xfc, 0xad, 0xa2, 0x94, 0xff, 0x28, 0x6b, +0x47, 0x01, 0x90, 0xe2, 0x37, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0x52, +0x9a, 0x95, 0x78, 0x13, 0x16, 0x5e, 0x72, 0x0e, +0xed, 0x85, 0xfe, 0xee, 0x4c, 0x23, 0x78, 0x06, +0x0e, 0x3e, 0xc1, 0xfa, 0xba, 0x90, 0x22, 0x9b, +0x21, 0xaf, 0xcb, 0x88, 0xce, 0x6a, 0x61, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0xe4, 0x0b, 0x8a, 0x79, 0x6b, 0x15, 0x20, +0x79, 0x39, 0x6f, 0xe2, 0x88, 0xee, 0xb4, 0xbb, +0x6d, 0x8e, 0xdf, 0x91, 0x9d, 0x0d, 0xbe, 0x68, +0x9b, 0xc8, 0x23, 0xba, 0x4f, 0xf1, 0x43, 0x3f, +0xf6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0x64, 0x7a, 0x15, 0xdf, 0x17, +0xfb, 0x76, 0x19, 0x13, 0x5f, 0x94, 0x06, 0x59, +0x8b, 0xaf, 0xd2, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x5c, 0xe4, 0x80, 0x31, +0x88, 0xee, 0x32, 0xbe, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0xab, 0xf8, 0x1a, +0x63, 0x4e, 0x69, 0xe9, 0xd9, 0xdd, 0x60, 0x91, +0x87, 0x78, 0x18, 0xbe, 0x4a, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x1e, +0x7a, 0xab, 0x02, 0x05, 0x38, 0xac, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0x1a, +0xc8, 0x6c, 0x55, 0xcf, 0x37, 0x70, 0xd1, 0xde, +0x30, 0x2d, 0x9e, 0x35, 0x44, 0x3c, 0xd9, 0xd2, +0xa6, 0x99, 0x8e, 0xaf, 0x78, 0xa9, 0x1c, 0x3b, +0x77, 0x70, 0x9e, 0x19, 0x91, 0xb7, 0x05, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0x03, 0x4b, 0xd5, 0xd0, 0x72, 0xc9, 0x67, +0xe5, 0x68, 0x93, 0x0f, 0x35, 0xbb, 0x9a, 0x76, +0x18, 0x2d, 0xb5, 0x3b, 0xc0, 0x18, 0x55, 0x54, +0xcf, 0xa2, 0x4a, 0xfa, 0x27, 0xf8, 0x7e, 0x7c, +0x68, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0x93, 0x51, 0x95, 0x73, 0x51, +0x7a, 0x57, 0xce, 0x52, 0xb8, 0xb9, 0xaa, 0xf7, +0xf3, 0x2c, 0xe9, 0xb5, 0xc0, 0xeb, 0x00, 0x2d, +0x31, 0xa3, 0xe4, 0xef, 0x58, 0xf8, 0x53, 0x94, +0x55, 0xa5, 0xa9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0xd3, 0x81, 0x05, +0xea, 0x25, 0xa8, 0xc2, 0xa8, 0x93, 0x0a, 0x00, +0xc6, 0x19, 0x6c, 0xfe, 0x9f, 0xed, 0x4b, 0xbe, +0xa7, 0xec, 0x57, 0x9a, 0xba, 0x7d, 0x2c, 0x94, +0xee, 0x0c, 0x2f, 0x61, 0x88, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0xa1, +0x94, 0x33, 0x93, 0xe2, 0x33, 0x1d, 0x3d, 0x7b, +0xba, 0x67, 0x03, 0xd4, 0x2f, 0x04, 0x7e, 0xe3, +0x33, 0x95, 0x53, 0xce, 0x2c, 0xcf, 0x60, 0xe8, +0x3d, 0xc7, 0x18, 0xb5, 0xd1, 0xf6, 0xf9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0xac, 0xf4, 0x75, 0xcc, 0x4e, 0x0b, 0xba, +0x3e, 0x3a, 0xf2, 0xcd, 0x29, 0x70, 0x12, 0x63, +0x44, 0xa1, 0xfe, 0x20, 0x8f, 0x4f, 0x7d, 0xe8, +0xdd, 0xe8, 0x0a, 0x59, 0xbc, 0x3d, 0xb3, 0x96, +0xe9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x17, 0x8b, 0xa2, 0xb5, 0x08, +0xac, 0x5f, 0xf4, 0xfc, 0xdf, 0xc3, 0x54, 0xdb, +0xdc, 0x8f, 0xc0, 0xc4, 0xcb, 0xbe, 0x85, 0x58, +0x18, 0xf5, 0xe6, 0x05, 0x35, 0x44, 0x09, 0x5e, +0xfa, 0x3e, 0xea, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0xd2, 0x0e, 0x7d, +0x95, 0xcd, 0xfa, 0xb1, 0x37, 0x14, 0x25, 0x6b, +0x1b, 0x51, 0x98, 0xfd, 0x04, 0xfe, 0x72, 0xb2, +0xc6, 0x65, 0xe1, 0x49, 0x56, 0x22, 0xef, 0x66, +0x62, 0x0d, 0xe5, 0xe8, 0x23, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0xf2, +0x37, 0x6b, 0x49, 0x6c, 0x1b, 0x2a, 0x81, 0x62, +0x15, 0xd6, 0xcc, 0x04, 0xaa, 0x12, 0x5d, 0x92, +0x07, 0xc5, 0x36, 0x95, 0x56, 0x38, 0x41, 0x08, +0x4f, 0x21, 0xdc, 0x93, 0xdf, 0x4c, 0xd8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x35, 0xfb, 0x44, 0x78, 0x99, 0x5d, 0xa4, +0xba, 0x13, 0x1c, 0x18, 0x9a, 0x15, 0x6c, 0x33, +0x62, 0xb0, 0xb4, 0xac, 0x55, 0x87, 0xe3, 0xa3, +0xde, 0xf8, 0xc0, 0xc4, 0x13, 0x7d, 0x2b, 0x18, +0x86, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x3f, 0x85, 0xc8, 0x99, 0x3e, +0x32, 0x65, 0x8c, 0xbb, 0x90, 0x51, 0xb9, 0x45, +0x9a, 0x2d, 0x25, 0x12, 0xb9, 0x10, 0x0c, 0x7e, +0xce, 0x2a, 0x43, 0x82, 0xbd, 0xae, 0x61, 0xb2, +0x3e, 0x79, 0x16, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0xeb, 0x18, 0x55, +0x01, 0x1a, 0x11, 0xf5, 0x53, 0xed, 0x13, 0x53, +0x46, 0x79, 0x89, 0x50, 0xdb, 0x84, 0xc1, 0x5b, +0x73, 0x74, 0x19, 0xbc, 0x60, 0xd4, 0x63, 0x39, +0x62, 0x2e, 0x4d, 0x4b, 0x52, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0x7f, +0x80, 0x98, 0x89, 0xdd, 0x59, 0x3c, 0xa1, 0x57, +0xb1, 0x88, 0xff, 0xec, 0xd6, 0x46, 0xe6, 0xa8, +0x98, 0x67, 0x81, 0xec, 0x25, 0x30, 0xcc, 0xcc, +0x98, 0x63, 0x60, 0x1e, 0x5a, 0x6e, 0x39, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0xa8, 0x40, 0x7d, 0x5e, 0x51, 0x9e, 0xa2, +0xb0, 0x1f, 0x7e, 0x0a, 0xd6, 0x9c, 0xf6, 0xaa, +0x80, 0x97, 0x0a, 0xf5, 0x24, 0xe6, 0x1d, 0x7d, +0x88, 0xa2, 0x52, 0xda, 0x59, 0x48, 0xa5, 0x7a, +0xbe, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0x6d, 0xd0, 0xff, 0xa9, 0xde, +0x9d, 0xa2, 0xcc, 0xea, 0xd7, 0xd4, 0x60, 0x5e, +0x88, 0x4d, 0x08, 0x7c, 0x08, 0x48, 0xfd, 0xfe, +0x93, 0xce, 0x4f, 0xe4, 0x08, 0x27, 0x04, 0xca, +0x54, 0x78, 0x6e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x11, 0x53, 0xa7, +0xfb, 0xfb, 0x0e, 0x24, 0xad, 0x8f, 0x01, 0x74, +0x7a, 0xc1, 0x98, 0x55, 0xcb, 0x73, 0x5a, 0x94, +0x4b, 0x29, 0xbd, 0xc0, 0x2b, 0x04, 0xb9, 0x83, +0x97, 0x15, 0xd8, 0xd4, 0x8f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x3c, +0xb4, 0xb4, 0x2d, 0x5b, 0x53, 0x5a, 0x58, 0xbe, +0xd6, 0x1f, 0xfb, 0x60, 0x02, 0x90, 0x73, 0xf0, +0xd2, 0x5a, 0x54, 0x5b, 0x23, 0xfb, 0x42, 0x86, +0x81, 0x1b, 0x14, 0xbc, 0xeb, 0x50, 0x02, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0x4a, 0x5c, 0x23, 0x10, 0x23, 0xbe, 0x6e, +0x7e, 0x4d, 0x2d, 0x55, 0x2d, 0xf2, 0xcd, 0x3d, +0x40, 0x91, 0x63, 0x0a, 0x8c, 0x84, 0x80, 0xd0, +0xae, 0x48, 0x6a, 0x39, 0x8a, 0xd4, 0x22, 0x9e, +0x85, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0x63, 0x8b, 0xab, 0x45, 0x4e, +0x96, 0xa3, 0xb4, 0x21, 0xa4, 0x79, 0xb6, 0x38, +0xa7, 0x07, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x68, 0x4b, 0x03, 0xf7, +0x62, 0xc2, 0x95, 0x82, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0x55, 0x6c, 0xc7, +0xca, 0x21, 0x46, 0xe6, 0x67, 0xa8, 0x8a, 0x17, +0xf1, 0x4d, 0x7f, 0xe0, 0x54, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, 0xc6, +0x64, 0x9d, 0xb1, 0x4a, 0x91, 0x5f, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0x2e, +0x39, 0x58, 0x0e, 0xdd, 0x0f, 0xc1, 0x62, 0x15, +0x96, 0xe0, 0x11, 0x1a, 0x29, 0x54, 0x4f, 0x38, +0x4c, 0xb2, 0xcc, 0x3b, 0x1a, 0x3b, 0x04, 0x9e, +0xe1, 0x07, 0x9e, 0xd0, 0x02, 0x69, 0xb4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0xab, 0x0a, 0xf1, 0xd0, 0x15, 0x75, 0xc6, +0x87, 0xc9, 0x5c, 0x7e, 0xde, 0xa4, 0xa6, 0x90, +0x34, 0x48, 0xe7, 0x25, 0x99, 0x75, 0xe5, 0x37, +0x37, 0xe7, 0xa1, 0xa4, 0xf7, 0x03, 0x45, 0x6b, +0x13, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0xec, 0x58, 0x65, 0x3e, 0xf9, +0xb0, 0x71, 0xe3, 0xfa, 0x57, 0xe2, 0x61, 0x9d, +0x4b, 0x18, 0x0d, 0x20, 0xc2, 0x0c, 0x2a, 0xe3, +0x94, 0xb6, 0xad, 0x32, 0x7b, 0xd2, 0x58, 0xc5, +0x47, 0x89, 0xfa, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0x56, 0x91, 0xf5, +0x7e, 0xa2, 0x04, 0x66, 0x2f, 0xbc, 0xe7, 0xf7, +0x76, 0x34, 0x74, 0x9f, 0x36, 0xec, 0x34, 0x8a, +0xca, 0x67, 0x87, 0x74, 0x64, 0xf9, 0x19, 0xf1, +0x90, 0x7c, 0xce, 0x11, 0x13, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0xd5, +0xcb, 0xf6, 0x96, 0x7f, 0x2e, 0x2a, 0x24, 0x22, +0x28, 0xb2, 0x9c, 0xd6, 0xeb, 0xb8, 0x6b, 0x5c, +0x24, 0x17, 0x02, 0x3c, 0xc4, 0x97, 0x71, 0x9a, +0x4c, 0xc0, 0xd9, 0xa7, 0x36, 0x29, 0x3b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0xd6, 0x0c, 0xc3, 0xc4, 0xc6, 0x50, 0x72, +0x68, 0xfa, 0x0c, 0xae, 0x9c, 0xbf, 0x09, 0x09, +0x59, 0x9a, 0xea, 0xa7, 0xfc, 0xfc, 0x54, 0x37, +0xd7, 0x3f, 0xe9, 0x18, 0xb6, 0x21, 0x18, 0x39, +0x33, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0xed, 0xbf, 0xf3, 0xa0, 0x75, +0x19, 0xe8, 0x0c, 0x15, 0xf9, 0x41, 0x49, 0x35, +0xf6, 0x13, 0x17, 0xea, 0x81, 0xb5, 0xfb, 0xfb, +0xd8, 0x23, 0x7b, 0xca, 0x51, 0xe0, 0x90, 0x2d, +0xd5, 0x95, 0x24, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x75, 0xd6, 0x22, +0xb8, 0xbe, 0xba, 0x4e, 0x48, 0xa6, 0x3e, 0x36, +0x5f, 0x39, 0x27, 0xdb, 0xdf, 0x99, 0xf6, 0x29, +0x77, 0x60, 0x67, 0xe1, 0x23, 0x11, 0x6e, 0x1f, +0xe6, 0x0f, 0x07, 0x56, 0x97, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0xad, +0x0d, 0x94, 0xc6, 0x73, 0x89, 0x10, 0xf3, 0x41, +0x1f, 0x43, 0x67, 0x4b, 0xbe, 0xfd, 0x49, 0x36, +0x75, 0x4a, 0x29, 0xcf, 0x0a, 0xf4, 0x02, 0xf9, +0x16, 0x73, 0x89, 0x06, 0x0f, 0xc0, 0x26, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0xd5, 0x33, 0x8b, 0xc2, 0x16, 0xef, 0x4f, +0x92, 0x13, 0x07, 0x1d, 0xc6, 0x20, 0xff, 0xa4, +0x2d, 0xfd, 0xf1, 0x5b, 0x42, 0x17, 0x90, 0x01, +0x52, 0x89, 0xfd, 0x8f, 0x21, 0xcf, 0x4b, 0x43, +0x6d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0xf7, 0x72, 0x1d, 0x0c, 0x7b, +0xb9, 0x5a, 0xd2, 0xcc, 0xe4, 0x3e, 0x25, 0x84, +0xe5, 0x57, 0xc0, 0x09, 0xd2, 0x65, 0xd2, 0x88, +0x0f, 0x56, 0x2d, 0xfc, 0x18, 0x05, 0x43, 0x6d, +0xa9, 0x5e, 0x58, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x10, 0xb0, 0x3e, +0x38, 0xb2, 0x3a, 0x14, 0x89, 0x24, 0x5f, 0x57, +0xff, 0x19, 0xac, 0x4d, 0xd8, 0xe7, 0x9f, 0x04, +0x18, 0x08, 0x21, 0xe7, 0xce, 0x6e, 0xd3, 0xe2, +0xd3, 0xeb, 0x98, 0x7e, 0x47, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x1e, +0x67, 0x27, 0xa1, 0xfb, 0xfd, 0xef, 0x1f, 0x70, +0x24, 0xdb, 0x4b, 0x5d, 0xa7, 0x09, 0x08, 0x57, +0x8b, 0xbb, 0x44, 0x88, 0x84, 0x34, 0x47, 0x9c, +0xab, 0x7c, 0xc9, 0x6d, 0xaf, 0x13, 0x22, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0x14, 0x95, 0x45, 0x28, 0x08, 0xad, 0x25, +0xc9, 0x14, 0x0d, 0xd1, 0x8a, 0xe6, 0x86, 0x44, +0x22, 0x52, 0x72, 0x38, 0xd1, 0x24, 0x41, 0x2c, +0x51, 0xdf, 0xec, 0x97, 0xa2, 0x21, 0x28, 0x51, +0x00, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0xa4, 0x41, 0xb9, 0x67, 0x62, +0x96, 0x80, 0x1a, 0x37, 0x30, 0x23, 0xbd, 0x02, +0x87, 0xb3, 0x37, 0x10, 0xff, 0xe6, 0xcc, 0xb9, +0xde, 0xcd, 0x06, 0x7d, 0xd3, 0x3a, 0x18, 0x1c, +0xca, 0xcf, 0xa4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0xe9, 0x75, 0xc3, +0xdc, 0x43, 0x6d, 0xff, 0x37, 0x3b, 0x49, 0x8e, +0x60, 0x1d, 0x00, 0x02, 0x3c, 0x4d, 0x8c, 0xd0, +0x37, 0xe0, 0xd4, 0xfd, 0xda, 0xce, 0x0f, 0xa6, +0xf0, 0x28, 0x0e, 0x51, 0xd6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x03, +0xc4, 0x11, 0x62, 0x82, 0x26, 0x57, 0xca, 0xf9, +0xfb, 0xe1, 0x8b, 0x16, 0xae, 0x60, 0xf7, 0x03, +0x69, 0xfc, 0xed, 0xe5, 0xfb, 0xab, 0xc3, 0x3d, +0xc7, 0x17, 0x9a, 0x6e, 0xaa, 0x30, 0xc5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0x99, 0x81, 0x7e, 0x69, 0x15, 0xef, 0x7d, +0xc2, 0xab, 0x9c, 0x3e, 0xad, 0x86, 0x33, 0xf8, +0x00, 0xd8, 0x34, 0x49, 0x2c, 0x46, 0x26, 0x3e, +0xfa, 0x92, 0x5b, 0xa6, 0xd2, 0x4e, 0x01, 0x2e, +0x6f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0x72, 0xc3, 0x6c, 0xaf, 0xd5, +0x91, 0x7c, 0x7f, 0x3c, 0xfc, 0x12, 0x5d, 0xdd, +0xb5, 0x7f, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xe2, 0x89, 0xfd, 0x0b, +0xe7, 0x10, 0xae, 0x8d, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0x63, 0x3f, 0xb5, +0xd9, 0x9a, 0x5f, 0x22, 0xbb, 0x33, 0x26, 0xc4, +0x20, 0x5e, 0x85, 0xc2, 0xc3, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0x2b, +0x03, 0x15, 0x8b, 0xe4, 0x49, 0xdb, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x44, +0xfd, 0xab, 0xf4, 0xfd, 0x7a, 0x8b, 0x4a, 0xbc, +0x34, 0x63, 0x70, 0xf8, 0xaf, 0x1d, 0x8f, 0xc3, +0x42, 0x4b, 0x30, 0x5a, 0xfe, 0xce, 0xf9, 0x10, +0xe4, 0x1b, 0xa0, 0xc7, 0x79, 0x7e, 0x9a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0xdc, 0x62, 0x22, 0x41, 0x17, 0x30, 0xec, +0xcd, 0x2a, 0x81, 0xa6, 0x5b, 0xba, 0x5c, 0xe4, +0x16, 0xa8, 0x17, 0xa2, 0x9b, 0x28, 0xa5, 0xf4, +0xc9, 0xe2, 0xdb, 0x4b, 0xa6, 0xa7, 0x9e, 0x08, +0x6e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0xf7, 0x70, 0x69, 0x80, 0xa2, +0xa4, 0xd4, 0xbf, 0x86, 0x24, 0xe2, 0x9f, 0xdd, +0x1e, 0xe2, 0xb1, 0x94, 0xc6, 0x9f, 0xd3, 0x01, +0xa4, 0xf9, 0x08, 0x6d, 0x28, 0xc2, 0x8c, 0x93, +0x8d, 0x37, 0x89, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0x42, 0x54, 0xa0, +0x98, 0xbe, 0xea, 0xb9, 0x55, 0xfe, 0xda, 0x98, +0xfb, 0x65, 0x94, 0x84, 0x1f, 0x23, 0x34, 0x0b, +0xdd, 0xd5, 0x69, 0x62, 0x9a, 0x23, 0x7e, 0x3b, +0x32, 0x6f, 0xf7, 0x95, 0x0c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0xe5, +0xf3, 0x1a, 0xb7, 0x97, 0x13, 0x54, 0xb1, 0xb0, +0xa9, 0x78, 0xe2, 0x29, 0x9b, 0xc2, 0x47, 0x50, +0x71, 0x50, 0x53, 0x2c, 0x4b, 0xf7, 0x3b, 0x67, +0x47, 0x38, 0x86, 0x08, 0x51, 0x2c, 0x55, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0x63, 0xac, 0x58, 0x31, 0x50, 0x36, 0x9d, +0xad, 0xcb, 0x0f, 0x33, 0x58, 0x0f, 0x90, 0x6c, +0x96, 0x64, 0x80, 0xac, 0x20, 0x7b, 0x5b, 0x95, +0x5b, 0xb8, 0xea, 0x3a, 0x33, 0xa1, 0x2e, 0x99, +0x56, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x90, 0x5e, 0x31, 0x5e, 0x8d, +0x30, 0xc1, 0xd1, 0x97, 0x72, 0x68, 0xfb, 0xfb, +0x19, 0xa5, 0x71, 0x47, 0x32, 0x94, 0x8c, 0xef, +0xda, 0x5a, 0x0c, 0xe2, 0x1a, 0xfa, 0xed, 0x52, +0x69, 0xba, 0x33, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x45, 0x9f, 0xfa, +0xbd, 0x61, 0x98, 0x0b, 0xb3, 0xff, 0xb3, 0xec, +0xb4, 0x49, 0x21, 0x7e, 0x65, 0x0c, 0x68, 0xf9, +0xb7, 0x9d, 0x3b, 0x48, 0x45, 0xc3, 0x5c, 0x2b, +0x99, 0x0c, 0x96, 0x4f, 0x4b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0xe5, +0x2b, 0x75, 0x91, 0x49, 0x94, 0xa2, 0xa0, 0x2a, +0x06, 0x95, 0x9e, 0xe9, 0x41, 0x67, 0x81, 0x20, +0x1a, 0x04, 0xb1, 0xe6, 0x5a, 0x51, 0xad, 0xde, +0x63, 0x8a, 0xa5, 0x9a, 0xa7, 0x18, 0xd1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0xf8, 0xa6, 0x28, 0xc0, 0x07, 0x4a, 0x4b, +0xcb, 0xe4, 0xde, 0x13, 0xa1, 0xab, 0x9f, 0x6a, +0xf7, 0x75, 0xdb, 0x32, 0x04, 0xf1, 0x8d, 0x6d, +0x3a, 0xa9, 0xe5, 0x06, 0x74, 0xe9, 0x03, 0x59, +0x0a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0x8a, 0x22, 0x88, 0x79, 0x97, +0xbe, 0x55, 0x65, 0x67, 0xee, 0x93, 0xd4, 0x66, +0xd2, 0xd1, 0xa3, 0x2e, 0x9d, 0xa8, 0xcf, 0xf4, +0x0c, 0xb3, 0x1e, 0xac, 0x00, 0x1d, 0x6e, 0xaf, +0xac, 0x7f, 0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0x3c, 0x8d, 0x34, +0x80, 0x52, 0x9a, 0x93, 0xf9, 0xd2, 0xa6, 0x4e, +0x45, 0xcc, 0xd8, 0x60, 0xd3, 0x32, 0x19, 0x3f, +0x80, 0xb0, 0x92, 0xb0, 0x09, 0x9f, 0xa2, 0x97, +0xe5, 0xcc, 0xa7, 0xb3, 0xdb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0xb2, +0x21, 0x6a, 0x88, 0xfe, 0xcf, 0x51, 0xe2, 0xe7, +0xe1, 0x05, 0x9d, 0x86, 0x3a, 0xee, 0x26, 0x6b, +0x3f, 0xf0, 0x4d, 0xde, 0x95, 0xea, 0x53, 0xd4, +0x3f, 0x45, 0xff, 0xfe, 0x81, 0xa8, 0xba, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0x50, 0xa2, 0x43, 0xc6, 0x77, 0xbc, 0xd5, +0xa1, 0x72, 0xf1, 0x46, 0x66, 0xfa, 0x8d, 0x18, +0x4a, 0xd0, 0x6e, 0x09, 0xf7, 0xd4, 0xeb, 0x20, +0xb2, 0x8f, 0x14, 0x62, 0x49, 0x9f, 0xb5, 0xde, +0xc3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0xce, 0x21, 0x86, 0x20, 0xa1, +0x76, 0x4e, 0x81, 0xa6, 0x5f, 0x8f, 0x97, 0xa8, +0x6c, 0x88, 0x71, 0x81, 0xad, 0xbb, 0x8c, 0x18, +0x68, 0x2d, 0x89, 0x88, 0xd8, 0x05, 0x5e, 0x8a, +0x1f, 0x4c, 0xd5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0x7a, 0x61, 0xb8, +0x8a, 0x67, 0x87, 0x45, 0x46, 0x2c, 0xf0, 0x00, +0x6b, 0x05, 0xd2, 0x9d, 0x10, 0xa9, 0x85, 0xdb, +0xaf, 0x53, 0x0f, 0xba, 0xd6, 0x59, 0x27, 0x63, +0x84, 0x62, 0xfe, 0xeb, 0x91, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0xbd, +0x64, 0x70, 0x2a, 0xda, 0x2f, 0x76, 0xfe, 0x48, +0x35, 0x1d, 0xbc, 0xce, 0x53, 0x5a, 0xa5, 0x0f, +0x8f, 0x84, 0x4c, 0xc8, 0x38, 0x92, 0x1c, 0xe3, +0x1c, 0xda, 0xb3, 0xef, 0xbf, 0xa4, 0xab, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0x7d, 0x87, 0xd5, 0x32, 0x04, 0xb5, 0xa1, +0x97, 0x30, 0xef, 0xb5, 0x97, 0xfc, 0x86, 0xba, +0xe3, 0x96, 0x40, 0xec, 0xa0, 0x7a, 0xf5, 0x92, +0x30, 0x28, 0x77, 0x70, 0x20, 0xe2, 0x95, 0x5c, +0xde, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0x6d, 0xa4, 0x27, 0x76, 0x95, +0xd5, 0x84, 0xb3, 0x04, 0xe2, 0x88, 0x76, 0x72, +0xcd, 0xf8, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xb1, 0x4c, 0xba, 0x04, +0x45, 0x5e, 0x4f, 0x8e, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0x7b, 0x6f, 0xb4, +0x0c, 0xd8, 0x59, 0x61, 0x37, 0xaa, 0x01, 0x35, +0x48, 0xe2, 0x55, 0xea, 0x10, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x15, +0x9d, 0x6b, 0xa1, 0x98, 0xe5, 0x66, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0xdc, +0xc9, 0x30, 0x9a, 0x5e, 0x73, 0x4e, 0x60, 0x7d, +0x7f, 0x2d, 0xf2, 0x77, 0x32, 0xf2, 0xde, 0x57, +0xee, 0x58, 0xa2, 0xec, 0x8a, 0xa4, 0xb9, 0xc8, +0xe4, 0xfc, 0x9f, 0x19, 0x08, 0xe3, 0x18, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0xd3, 0xf4, 0x0a, 0xf2, 0x92, 0x67, 0xd0, +0x9f, 0x49, 0xd0, 0xa7, 0x8f, 0x56, 0xc1, 0xf8, +0x6f, 0xd1, 0xdf, 0x82, 0x8e, 0xfb, 0x60, 0xc8, +0x82, 0x5a, 0xc1, 0x6c, 0x2c, 0x03, 0xd9, 0xba, +0x1c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0xd0, 0xdd, 0xf7, 0x73, 0x37, +0x70, 0x32, 0xb4, 0xb3, 0xfc, 0x21, 0x30, 0x9f, +0xd2, 0x7e, 0x0c, 0xf2, 0xe4, 0x1d, 0x34, 0x4d, +0x05, 0x5f, 0x47, 0xf0, 0x75, 0xb7, 0xb9, 0xd0, +0xde, 0x91, 0xda, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0x3c, 0x72, 0x27, +0x4e, 0xcc, 0x68, 0x0a, 0x2c, 0x3c, 0x27, 0xcd, +0x7e, 0x87, 0xb1, 0xca, 0xec, 0xfa, 0xd3, 0x55, +0xf8, 0xa4, 0xd8, 0xdf, 0xce, 0x21, 0x28, 0xe5, +0xd5, 0x80, 0xc5, 0x4f, 0xe6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0xbe, +0x3a, 0x20, 0xb7, 0xec, 0x1b, 0x6d, 0x43, 0xc7, +0x56, 0xf8, 0x49, 0x2c, 0xbf, 0x98, 0x1b, 0x53, +0x41, 0xbd, 0xa8, 0x24, 0xee, 0x6f, 0x89, 0xc1, +0x0f, 0x80, 0x9b, 0xff, 0x69, 0x06, 0x23, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0x36, 0x0b, 0xf0, 0xea, 0xdb, 0x96, 0x4a, +0x80, 0xc8, 0xe9, 0xfe, 0x0d, 0xe8, 0xc6, 0xcc, +0x2d, 0xc0, 0x33, 0x29, 0x37, 0xaf, 0x20, 0x99, +0x54, 0x31, 0xb9, 0x3e, 0xc0, 0x80, 0x6e, 0xf9, +0xda, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0x91, 0x7e, 0x9b, 0x91, 0x61, +0x1f, 0x1e, 0xcd, 0x51, 0x70, 0x73, 0x25, 0x13, +0xba, 0xb1, 0x36, 0xa5, 0x92, 0x16, 0xb6, 0x9d, +0x41, 0xf0, 0x4b, 0x4e, 0x7f, 0xbc, 0xbf, 0x61, +0x18, 0x5e, 0x28, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0xa8, 0x61, 0xd3, +0x4f, 0xb2, 0xcb, 0xd2, 0x93, 0x27, 0x5e, 0x4e, +0x88, 0xa3, 0x63, 0x43, 0xee, 0xf6, 0x16, 0xa3, +0x28, 0xfc, 0xa5, 0x2e, 0xea, 0x98, 0x37, 0x6b, +0xe2, 0xdb, 0x2c, 0x26, 0xc7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0x5a, +0xd6, 0x48, 0x58, 0x0d, 0x3d, 0x38, 0xd3, 0x1f, +0x06, 0xc7, 0xf8, 0x86, 0x6f, 0x8a, 0xdb, 0x46, +0xd6, 0x8c, 0x5c, 0x74, 0xde, 0x05, 0x0e, 0x97, +0x8e, 0xa7, 0x5e, 0x12, 0x34, 0x15, 0x64, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0x19, 0x26, 0x8e, 0x08, 0x7a, 0x14, 0x22, +0x12, 0x78, 0x4f, 0x8b, 0x8e, 0x92, 0x2e, 0xfb, +0xbd, 0xf7, 0xff, 0x6c, 0x84, 0x75, 0x9a, 0x48, +0x70, 0x57, 0x6b, 0x6e, 0x68, 0x73, 0x4c, 0xb2, +0x70, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0x48, 0x9a, 0x51, 0xdc, 0xb4, +0x45, 0x12, 0x3c, 0x62, 0x93, 0xb8, 0x6e, 0x40, +0x71, 0xf0, 0x35, 0x16, 0xfc, 0xe9, 0xf3, 0x8a, +0x19, 0x7d, 0x51, 0xc8, 0x98, 0xb0, 0xc7, 0xfc, +0x76, 0xea, 0x83, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0xaa, 0x75, 0xcc, +0x15, 0x66, 0xfb, 0x7f, 0x33, 0xb0, 0x34, 0xa3, +0x63, 0x8e, 0x7b, 0xa8, 0xb1, 0x45, 0xd7, 0x82, +0xd6, 0xab, 0xba, 0xff, 0x39, 0xcb, 0x41, 0xd3, +0xaf, 0xad, 0x75, 0x6a, 0x0e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0xb1, +0x12, 0xad, 0xb1, 0x69, 0x44, 0x58, 0xbe, 0xd4, +0x5c, 0x6b, 0x96, 0x57, 0x99, 0x5d, 0x17, 0xf4, +0xac, 0xc4, 0xb1, 0x9e, 0xeb, 0x7c, 0x30, 0x4d, +0x7c, 0x56, 0x19, 0xd2, 0x3e, 0x35, 0xb5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0x2f, 0x6d, 0x65, 0x38, 0xbc, 0xdc, 0xce, +0x2d, 0x8a, 0x1b, 0xf1, 0x20, 0x35, 0xc3, 0x0c, +0x57, 0xff, 0x11, 0xbe, 0x4a, 0x6e, 0xc4, 0x2c, +0x46, 0x26, 0xc2, 0x24, 0xbd, 0x39, 0xe2, 0xe7, +0xd4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0x22, 0xe5, 0xed, 0x7a, 0x72, +0xe6, 0xe8, 0x48, 0xa5, 0x29, 0x78, 0x7f, 0x07, +0x15, 0xb5, 0xcd, 0x24, 0xd6, 0xa7, 0x95, 0x34, +0xc3, 0x2f, 0x1e, 0xd8, 0x4b, 0x24, 0xda, 0xda, +0x07, 0xab, 0x39, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0x57, 0x94, 0x29, +0xff, 0x80, 0x53, 0x6d, 0x26, 0x60, 0x10, 0x90, +0x9c, 0x9e, 0xf6, 0x0e, 0xc3, 0x71, 0x4d, 0x06, +0x8e, 0xad, 0x4b, 0xdb, 0x54, 0xca, 0x8b, 0x63, +0x07, 0xd7, 0xcf, 0x15, 0x85, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0x44, +0x82, 0x60, 0xd5, 0xb1, 0x4d, 0xb5, 0xd4, 0xf3, +0x49, 0x29, 0x78, 0x93, 0x6e, 0xe4, 0xc2, 0x0d, +0x3d, 0x04, 0x66, 0x80, 0x2a, 0xd2, 0xf3, 0x55, +0x38, 0xb0, 0xfe, 0xbe, 0x42, 0x43, 0x27, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x37, 0xc2, 0x08, 0xac, 0x44, 0xef, 0x2c, +0x50, 0xec, 0xf6, 0x4c, 0xba, 0x61, 0x2a, 0xb2, +0xbb, 0x09, 0x90, 0x0a, 0x27, 0xa5, 0x9b, 0xf4, +0xe2, 0xa5, 0xb2, 0xc9, 0x55, 0x0b, 0x03, 0x02, +0x0e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0xee, 0x38, 0xd7, 0xf5, 0x97, +0x45, 0xe6, 0xb0, 0xeb, 0xa2, 0xf3, 0x0f, 0x79, +0x62, 0x23, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x1a, 0xf3, 0xc0, 0xc4, +0x65, 0xa2, 0xbf, 0x4f, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x09, 0x88, 0x27, +0xd3, 0x47, 0x4f, 0x66, 0xc8, 0x0f, 0xe7, 0x35, +0x12, 0x7c, 0x12, 0xc1, 0xbd, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x46, +0x63, 0x05, 0xb8, 0x56, 0xa2, 0x7f, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0x05, +0x91, 0xda, 0xdb, 0x8b, 0xbf, 0x77, 0xd8, 0x2f, +0x79, 0x1d, 0x94, 0x98, 0x98, 0xd4, 0x9d, 0xba, +0x0a, 0x8c, 0xf0, 0x0a, 0xf8, 0x1a, 0x5d, 0xf2, +0x66, 0x33, 0xb2, 0xe7, 0xeb, 0xf6, 0x82, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0x1a, 0xaa, 0x38, 0x36, 0xbc, 0x40, 0x96, +0x05, 0xf9, 0xd3, 0xc0, 0x57, 0xd7, 0x5e, 0xcc, +0x32, 0x41, 0x11, 0x1e, 0x07, 0x75, 0x9a, 0xaa, +0x62, 0xd4, 0x9f, 0x48, 0x62, 0x8d, 0xa9, 0xfa, +0x0f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0x09, 0x98, 0x80, 0x71, 0x43, +0x26, 0x3a, 0x51, 0x15, 0xfa, 0x73, 0x13, 0x03, +0x4c, 0xc9, 0x16, 0xb7, 0x50, 0xf8, 0xab, 0x2f, +0xae, 0x29, 0xe0, 0x17, 0xc9, 0x68, 0xe0, 0x13, +0x74, 0xf5, 0xd5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x85, 0x2a, 0x91, +0xca, 0xf2, 0xc4, 0xf4, 0x26, 0x75, 0x13, 0x68, +0xfd, 0xa2, 0xb6, 0x58, 0xfd, 0x9f, 0x34, 0xd1, +0xcf, 0x9f, 0xc1, 0xdd, 0x66, 0xd8, 0x55, 0x6a, +0xc5, 0xf2, 0x0f, 0xb2, 0x22, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0x8d, +0x19, 0x02, 0x44, 0x21, 0x65, 0x0a, 0xd0, 0x76, +0xaa, 0x33, 0x54, 0xb3, 0x01, 0x6a, 0xf4, 0x9d, +0xb6, 0x04, 0x3e, 0xc4, 0x72, 0x40, 0xc5, 0xda, +0x81, 0xbe, 0xab, 0xe4, 0xfe, 0x9a, 0xd2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0x79, 0x15, 0xf8, 0x41, 0xe1, 0x7c, 0xdd, +0x4f, 0x46, 0x94, 0x4e, 0xde, 0x89, 0x90, 0x20, +0xc6, 0x4d, 0xae, 0x35, 0x75, 0xb3, 0x28, 0x07, +0x79, 0xc5, 0x87, 0xa3, 0xba, 0xb0, 0x54, 0x50, +0xed, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0x8a, 0x4e, 0xb4, 0xcf, 0x63, +0xb1, 0x52, 0x78, 0x0c, 0x2e, 0xc1, 0x55, 0x3d, +0x0b, 0xbf, 0xc4, 0xa2, 0x43, 0xc1, 0x89, 0x01, +0xd0, 0x8e, 0x2e, 0xe8, 0x3d, 0x61, 0xf0, 0x8a, +0x59, 0x2c, 0x64, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0xc8, 0x8d, 0x2d, +0xef, 0x09, 0x23, 0x5a, 0x4d, 0x57, 0x15, 0xd9, +0xf6, 0xf8, 0x57, 0xf3, 0xcc, 0x50, 0x94, 0x54, +0x6c, 0x61, 0xa0, 0x40, 0xc5, 0x64, 0x5f, 0x4b, +0xc2, 0x0e, 0x1f, 0xca, 0x05, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0x13, +0x4d, 0xd4, 0x20, 0xe0, 0xba, 0x8d, 0xe2, 0x44, +0xfb, 0x8e, 0xa6, 0x4e, 0x5e, 0x9a, 0x7c, 0x6b, +0x63, 0x99, 0x5b, 0x47, 0x43, 0xbf, 0x96, 0x0e, +0x7c, 0x3c, 0xef, 0x75, 0xc5, 0x1b, 0x7a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0xa2, 0x17, 0xfd, 0xfe, 0x55, 0x54, 0xc4, +0x66, 0x70, 0x6a, 0x25, 0x32, 0x07, 0x0c, 0x02, +0x5d, 0x6a, 0x12, 0x44, 0x2d, 0xe4, 0xa5, 0x77, +0x65, 0x59, 0xbc, 0x3f, 0x59, 0x7e, 0x34, 0xd5, +0xcb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0xc5, 0x59, 0x81, 0xb3, 0x84, +0x79, 0xee, 0xd7, 0x63, 0x8c, 0x88, 0xf9, 0xb3, +0xb6, 0x72, 0xbb, 0xca, 0x21, 0xef, 0x5e, 0xca, +0xe7, 0xd8, 0x56, 0x37, 0x28, 0x87, 0xd8, 0xa5, +0x2e, 0x23, 0x6b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x37, 0x55, 0x8e, +0x5b, 0xec, 0xa2, 0x52, 0xcc, 0x23, 0xa1, 0x9e, +0x3f, 0xd9, 0x41, 0x83, 0xa6, 0xe9, 0x42, 0x90, +0xf5, 0x76, 0x24, 0xc2, 0x7b, 0x16, 0x57, 0xc3, +0x41, 0xab, 0x77, 0x0f, 0x1e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x2f, +0x5b, 0x7a, 0x92, 0x80, 0x3b, 0x71, 0x16, 0xef, +0x28, 0xf4, 0x1d, 0xa1, 0xcd, 0x9d, 0xd9, 0xae, +0xc2, 0xd0, 0x0f, 0x5f, 0xf7, 0xf9, 0xaf, 0x11, +0xe0, 0x2a, 0xb0, 0x47, 0x98, 0x03, 0x74, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0x5d, 0xb0, 0x5a, 0x8d, 0x0e, 0x2b, 0xaf, +0x00, 0xab, 0xb0, 0x32, 0xb3, 0x25, 0x0b, 0xe4, +0xf0, 0x9b, 0x25, 0x10, 0x4e, 0x75, 0xcc, 0x96, +0x56, 0x20, 0xbb, 0x2c, 0x8c, 0x17, 0xb5, 0xdd, +0xc1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0x27, 0x7c, 0x02, 0xde, 0x8e, +0xde, 0x33, 0xdd, 0x6c, 0x01, 0xf9, 0x92, 0x22, +0xb6, 0x50, 0x45, 0xea, 0x8c, 0xf8, 0x42, 0x1c, +0xb2, 0x0b, 0x50, 0x0e, 0xc6, 0xd3, 0x47, 0x1b, +0xf5, 0xc7, 0x7f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0x79, 0x97, 0x41, +0xcb, 0x7d, 0x69, 0xb8, 0x69, 0x23, 0x52, 0x1b, +0xab, 0x9e, 0x0f, 0x1b, 0xae, 0xf4, 0x34, 0xcc, +0x8c, 0x29, 0xdb, 0x61, 0x82, 0x42, 0x1f, 0xe5, +0x00, 0xcc, 0x93, 0x33, 0x86, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0x19, +0xad, 0x46, 0x9c, 0x4c, 0xec, 0x7e, 0xbb, 0x88, +0x24, 0xa3, 0x4a, 0x17, 0xf5, 0x0b, 0xaf, 0x0c, +0xe4, 0x8d, 0xb2, 0x2b, 0xf1, 0x94, 0x30, 0x4d, +0xc2, 0xaa, 0xf1, 0xf3, 0x96, 0xa3, 0x40, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x99, 0xad, 0xad, 0x71, 0xf8, 0x05, 0xeb, +0x00, 0x92, 0x65, 0x01, 0x65, 0xf3, 0x10, 0x15, +0xd9, 0x2b, 0x07, 0x25, 0x7e, 0x44, 0xa3, 0x45, +0x9d, 0x48, 0x22, 0x07, 0xb5, 0x17, 0x19, 0xcf, +0xdc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0x6c, 0xf9, 0xee, 0x14, 0x37, +0x17, 0x1d, 0x06, 0x9a, 0xb9, 0x61, 0xc0, 0x49, +0x94, 0xf3, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x31, 0xf7, 0xd2, 0xea, +0xf2, 0xc0, 0xc2, 0x31, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0xed, 0xf5, 0x17, +0xdb, 0xe7, 0xd3, 0x45, 0x0e, 0x8f, 0xf9, 0xfe, +0x09, 0x96, 0xea, 0xfd, 0xe0, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0xb1, +0x7c, 0x78, 0x28, 0xc3, 0xb1, 0x18, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0xaa, +0x62, 0xfe, 0x0e, 0x51, 0x5a, 0x4f, 0x80, 0xd8, +0x2b, 0xa9, 0x3c, 0x23, 0x17, 0x26, 0x37, 0x33, +0x73, 0xe0, 0xb3, 0x26, 0x2a, 0x99, 0x19, 0x63, +0x7c, 0xfb, 0x81, 0x56, 0x8b, 0x4a, 0x9d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0x18, 0xf0, 0x68, 0xc9, 0xcd, 0xea, 0xb0, +0x49, 0x2a, 0x01, 0x44, 0x65, 0x27, 0xbd, 0x11, +0xb3, 0xa6, 0xcc, 0x9c, 0x22, 0xf3, 0x5e, 0x8b, +0x52, 0xfd, 0x11, 0x63, 0xba, 0x25, 0x9a, 0xd1, +0xdf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0x0c, 0xdf, 0x11, 0xe6, 0x84, +0xd7, 0x1e, 0x3f, 0x88, 0xa8, 0xec, 0x70, 0xc4, +0x51, 0x31, 0xdd, 0x06, 0x5b, 0xbf, 0xb3, 0xad, +0x9e, 0x47, 0xc2, 0x4d, 0xd1, 0xa3, 0xfa, 0x6b, +0x53, 0xfe, 0x2d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0xf9, 0x92, 0x05, +0xc0, 0x17, 0x6b, 0x1d, 0x5a, 0x42, 0xc0, 0xcd, +0x58, 0x9f, 0x19, 0xa5, 0xea, 0x19, 0x37, 0x15, +0xbd, 0xdc, 0x37, 0x37, 0x71, 0x8e, 0x30, 0x9d, +0x9a, 0x0b, 0xc5, 0x70, 0x0b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0x45, +0x2b, 0x73, 0x40, 0x33, 0x1f, 0xfd, 0xe9, 0x25, +0xab, 0x62, 0x64, 0xd1, 0x54, 0x5b, 0x77, 0x1a, +0xd3, 0x19, 0x97, 0x09, 0x48, 0x79, 0xa4, 0x4a, +0xec, 0xcc, 0x53, 0xc3, 0xe6, 0x78, 0x66, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x48, 0x5f, 0xe5, 0x6e, 0x81, 0xb1, 0x8c, +0xee, 0x9b, 0x84, 0x00, 0x8d, 0x62, 0xd4, 0x99, +0x40, 0x4b, 0xac, 0xac, 0xaf, 0x68, 0x37, 0xfe, +0x75, 0xa3, 0x84, 0x30, 0x71, 0xc5, 0x43, 0xae, +0x54, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0xda, 0xd4, 0x94, 0x5b, 0x1d, +0x28, 0xb3, 0x9a, 0xa2, 0xa0, 0xc2, 0xe6, 0x7d, +0x09, 0x7f, 0xbd, 0x5c, 0xb9, 0x91, 0xa5, 0x7a, +0x4e, 0x28, 0xcf, 0xd5, 0xc1, 0x8d, 0x14, 0x88, +0x4a, 0x78, 0xce, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0x32, 0x1b, 0x1b, +0x09, 0x28, 0x95, 0xf1, 0x16, 0xc9, 0xc8, 0x5d, +0x78, 0xf8, 0xad, 0xcb, 0x6f, 0x97, 0x9d, 0x95, +0x56, 0xf7, 0x52, 0xb3, 0xb9, 0xf2, 0x59, 0xfb, +0x0d, 0xd4, 0x24, 0xdd, 0x2f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0x55, +0x23, 0x44, 0xd5, 0x4d, 0xc7, 0xa3, 0x09, 0xb4, +0x01, 0xf3, 0x5b, 0x29, 0x45, 0x19, 0x10, 0xc1, +0x72, 0xe4, 0x2a, 0x9d, 0xb5, 0x17, 0x61, 0x1a, +0xcf, 0xce, 0x10, 0x26, 0x47, 0x5e, 0xba, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0xd4, 0xd7, 0xcc, 0xeb, 0x84, 0x5e, 0x8c, +0x9e, 0xed, 0x99, 0x43, 0x74, 0xbf, 0x96, 0xd6, +0x97, 0x35, 0xdc, 0x10, 0x77, 0x0e, 0x2a, 0x1e, +0x11, 0xd8, 0x32, 0x35, 0xae, 0x08, 0x05, 0x99, +0x21, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0xa5, 0x69, 0xde, 0xd2, 0x10, +0x0b, 0xa8, 0x10, 0x76, 0x95, 0x06, 0x6d, 0xb7, +0x6b, 0x9f, 0x68, 0xee, 0x0d, 0x31, 0x4a, 0x8e, +0x33, 0x35, 0xe5, 0x8a, 0x73, 0x94, 0xe1, 0xa5, +0x24, 0xbf, 0xaf, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0x25, 0x69, 0xc9, +0xfd, 0x5f, 0x9c, 0xf0, 0x86, 0x29, 0xa9, 0x49, +0x35, 0xec, 0x99, 0x25, 0x49, 0x34, 0xf0, 0x7b, +0xbc, 0x13, 0x48, 0x9a, 0xd3, 0x0d, 0x93, 0xdf, +0xc6, 0x94, 0x78, 0x8b, 0xbd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0x41, +0x25, 0xab, 0xa1, 0x07, 0x6a, 0xa7, 0x1e, 0x99, +0xc4, 0xc8, 0x02, 0xc4, 0x98, 0x6b, 0x4d, 0x7a, +0x4c, 0xc5, 0xfb, 0x97, 0x96, 0x0d, 0xaf, 0xb1, +0x8a, 0xd2, 0xac, 0xed, 0x88, 0x71, 0xdc, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0xb5, 0xb3, 0x87, 0x24, 0xc0, 0xf4, 0xe9, +0xd7, 0x95, 0xc9, 0xc6, 0x3c, 0x77, 0xcb, 0x81, +0xfd, 0xe6, 0x75, 0xd7, 0xa7, 0x74, 0x49, 0xe6, +0x49, 0x13, 0x53, 0x26, 0xb5, 0x0f, 0x68, 0x86, +0x2d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0xef, 0x47, 0xef, 0xa9, 0x01, +0x7c, 0xbd, 0x5a, 0xb3, 0x7b, 0x0f, 0xec, 0x37, +0xb4, 0x07, 0xb8, 0x08, 0xb2, 0xfc, 0x98, 0x8a, +0xf8, 0xf3, 0x7b, 0xa7, 0x05, 0x61, 0x8c, 0xfb, +0x0c, 0x56, 0xa5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0xc9, 0x84, 0xd0, +0x02, 0x88, 0x82, 0xd4, 0xc8, 0xb4, 0x07, 0x68, +0xa4, 0x07, 0xba, 0x19, 0x3c, 0xd2, 0x25, 0x98, +0x40, 0xaf, 0x03, 0x38, 0xe5, 0x0b, 0x3c, 0x89, +0x0f, 0x9f, 0x04, 0xd7, 0x31, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0x3c, +0xb1, 0x52, 0xe0, 0x0f, 0xa0, 0xf0, 0xc3, 0x2c, +0x7b, 0x72, 0x36, 0x99, 0x6b, 0xf7, 0x90, 0x27, +0x3b, 0xa1, 0xf5, 0xd7, 0xdf, 0xc3, 0x32, 0xbb, +0xa8, 0x03, 0x74, 0x2f, 0xf9, 0xa0, 0x9d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0x2e, 0x33, 0xfd, 0x27, 0xb9, 0x5f, 0xeb, +0x58, 0x98, 0x2d, 0x56, 0x4e, 0x9e, 0x98, 0x9e, +0xcb, 0x4d, 0x70, 0x88, 0x07, 0xf1, 0x85, 0xc1, +0xa5, 0xf7, 0x5d, 0x9e, 0xec, 0xb4, 0x2e, 0x44, +0xa1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0xe6, 0xd2, 0xc8, 0x5e, 0x8d, +0x7d, 0x12, 0x27, 0xcd, 0x12, 0x04, 0x0f, 0x82, +0x37, 0x80, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x27, 0xf0, 0x07, 0x54, +0xc8, 0x88, 0x08, 0xe2, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0x1b, 0x4b, 0x86, +0x26, 0xfd, 0x80, 0x47, 0x14, 0x3e, 0xff, 0xc7, +0xc8, 0x92, 0x18, 0xad, 0x0f, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdf, 0x5b, +0x42, 0x49, 0x19, 0xde, 0x38, 0x1b, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0xdb, +0xa1, 0x7b, 0x33, 0x5a, 0x5d, 0xf4, 0x1f, 0xa8, +0xf8, 0x19, 0x4c, 0x51, 0x98, 0x11, 0x35, 0x72, +0x79, 0x84, 0xc6, 0xe4, 0x6e, 0xf0, 0x5c, 0x35, +0xba, 0xfc, 0xb2, 0x1b, 0x4f, 0x30, 0xec, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0x58, 0xc4, 0x22, 0x5f, 0xf5, 0x27, 0x6d, +0x9c, 0x87, 0x27, 0xb6, 0x12, 0x4d, 0xa2, 0x35, +0xc5, 0xd0, 0x64, 0x5c, 0xf4, 0xc0, 0x14, 0x6d, +0xc6, 0x04, 0xd1, 0x01, 0x2f, 0xfc, 0x9e, 0xb5, +0xf6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x34, 0xc7, 0xf4, 0x25, 0xaf, +0x01, 0x9b, 0x0e, 0x17, 0xad, 0xdb, 0x8d, 0x17, +0xd4, 0x90, 0x80, 0x78, 0xe0, 0xcb, 0x97, 0xaf, +0x72, 0x5c, 0xc1, 0xc1, 0x93, 0x85, 0x01, 0x0a, +0xba, 0x82, 0xcc, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0x39, 0x63, 0x6c, +0xf7, 0xfb, 0x0e, 0x71, 0x16, 0x5e, 0x00, 0xe3, +0x7f, 0xad, 0xb1, 0x4f, 0x24, 0x17, 0xfd, 0x3c, +0xd4, 0x6a, 0xb7, 0x37, 0xa4, 0x63, 0x04, 0x4f, +0x83, 0x35, 0x7f, 0xa0, 0x5e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0xae, +0x54, 0x0a, 0x83, 0x53, 0x8a, 0x6f, 0xcb, 0x81, +0xa4, 0xea, 0xc8, 0xae, 0x9f, 0x83, 0x54, 0x51, +0x13, 0xa7, 0x01, 0x1a, 0x55, 0x97, 0xac, 0x3a, +0x0b, 0xd6, 0xc6, 0x30, 0x57, 0x05, 0xfc, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0xee, 0xab, 0x25, 0xca, 0x94, 0xe4, 0xcb, +0x87, 0xe4, 0xc9, 0x25, 0x73, 0xcc, 0x16, 0x8a, +0x51, 0xfa, 0x97, 0xc1, 0x1b, 0x5f, 0xf5, 0x44, +0x31, 0x19, 0x68, 0xde, 0x72, 0x41, 0x5c, 0xda, +0x9f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0xaf, 0x40, 0x34, 0x5e, 0x6d, +0xb8, 0x7f, 0x6a, 0x6c, 0x8f, 0xbf, 0xf0, 0xd7, +0xeb, 0xe4, 0xa6, 0xa6, 0x88, 0x0b, 0x1c, 0x02, +0xf3, 0x91, 0x21, 0xa7, 0x30, 0xe8, 0xaf, 0x9d, +0xed, 0x61, 0xcf, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0x15, 0xa6, 0x5a, +0xf3, 0x80, 0xd3, 0xb4, 0xa1, 0x72, 0xa6, 0x3c, +0x1f, 0x1d, 0x83, 0xc7, 0x50, 0x9c, 0x96, 0x45, +0x5d, 0x54, 0x57, 0x13, 0x12, 0xfb, 0x0b, 0x93, +0x9c, 0x01, 0x33, 0xcf, 0xdf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0xee, +0x74, 0x6a, 0xaa, 0xcb, 0x8c, 0xe9, 0x7e, 0xe9, +0x19, 0x17, 0x24, 0x2f, 0x6a, 0x16, 0xe2, 0x8a, +0xb7, 0x09, 0x59, 0xa1, 0x4f, 0x99, 0x86, 0xde, +0xfa, 0x2b, 0x69, 0x1d, 0x19, 0x88, 0x7b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x8c, 0x20, 0x9a, 0xb3, 0xf1, 0x24, 0x77, +0x03, 0x13, 0xbc, 0x80, 0xee, 0x9a, 0x69, 0x6d, +0x2a, 0x89, 0xa6, 0x0c, 0x1f, 0x46, 0x9b, 0x1d, +0x70, 0xb4, 0xc2, 0xa3, 0xf9, 0x1a, 0x3b, 0x9a, +0xc0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0xc3, 0x7c, 0x9f, 0x9e, 0x21, +0x6d, 0xb2, 0x4f, 0x05, 0xf0, 0x95, 0xf7, 0xa6, +0xfe, 0xe2, 0xb5, 0xb5, 0x4e, 0x9d, 0x59, 0xbb, +0x6a, 0x88, 0x0a, 0xac, 0xa8, 0x35, 0xe2, 0x60, +0x7f, 0xda, 0xe4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0x48, 0xa2, 0x4e, +0xf3, 0xea, 0x5e, 0x61, 0x33, 0x9b, 0x61, 0x27, +0x22, 0x43, 0x95, 0xb3, 0xbe, 0xde, 0x3b, 0x2d, +0xab, 0xe6, 0xa4, 0x87, 0x99, 0x83, 0x84, 0xff, +0xe8, 0x71, 0x95, 0x34, 0x47, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0x06, +0x56, 0xdf, 0xd0, 0xe3, 0xae, 0x8e, 0x96, 0x81, +0xb7, 0x6d, 0x37, 0xef, 0x36, 0x6a, 0xd2, 0xed, +0x68, 0x7a, 0x2e, 0xd9, 0x42, 0x11, 0x54, 0x51, +0xd8, 0xdd, 0x5c, 0x10, 0xf8, 0xf2, 0x88, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0xd5, 0xf8, 0x16, 0xc6, 0x1f, 0xe4, 0x1c, +0x9d, 0x00, 0x02, 0x33, 0x58, 0x33, 0x25, 0x2f, +0x59, 0x9e, 0x00, 0x89, 0x42, 0xd7, 0x42, 0x44, +0x0c, 0x43, 0x3d, 0x44, 0xcf, 0x3d, 0xd8, 0x4a, +0x76, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0x6d, 0x21, 0x8e, 0x84, 0xfc, +0x63, 0x7d, 0xdb, 0xe1, 0xe5, 0x22, 0x70, 0xbb, +0x2c, 0x46, 0xe9, 0x9e, 0x98, 0x3f, 0x62, 0xd9, +0x46, 0xd5, 0x24, 0x0c, 0xb4, 0x57, 0xa9, 0x48, +0x9a, 0xaa, 0x61, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0xf3, 0x43, 0x0e, +0x03, 0x5d, 0xb3, 0x90, 0x2a, 0x31, 0xfd, 0x1d, +0x0e, 0xf0, 0xdc, 0x49, 0x7f, 0xf7, 0x20, 0x67, +0xb8, 0x6a, 0x05, 0x40, 0xb6, 0xb0, 0xa4, 0x47, +0xe2, 0xac, 0x19, 0xc7, 0xd3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0xfb, +0x74, 0x14, 0x67, 0x62, 0xeb, 0x16, 0x73, 0x33, +0x89, 0x0f, 0xda, 0x77, 0x52, 0x15, 0x19, 0x0d, +0xe9, 0x77, 0x50, 0x87, 0xdf, 0x06, 0xaa, 0xd0, +0x8a, 0x25, 0x59, 0x20, 0x64, 0xe5, 0x85, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0x0a, 0x26, 0xec, 0x6f, 0xbe, 0x36, 0x12, +0x08, 0x00, 0x89, 0x08, 0x7b, 0x5b, 0xed, 0xe6, +0x11, 0xe2, 0x8d, 0x9f, 0x2b, 0xc5, 0xb6, 0x14, +0x70, 0xab, 0x73, 0x77, 0x3a, 0xde, 0x7a, 0x90, +0x46, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0xf1, 0xbb, 0x0d, 0xfa, 0xca, +0xf8, 0x48, 0x8e, 0xab, 0x01, 0xa1, 0xc3, 0xb1, +0x49, 0xcc, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xa2, 0xbe, 0xd1, 0x27, +0xc2, 0x1d, 0xf5, 0x1f, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0x95, 0x0f, 0xb7, +0x36, 0x52, 0xc6, 0xce, 0xb9, 0xa4, 0xf9, 0x6f, +0x0f, 0x7a, 0x6c, 0x28, 0xee, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x7f, +0xea, 0xd0, 0x29, 0x75, 0xa3, 0x40, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0xa6, +0xd9, 0x92, 0x7a, 0xe1, 0x7e, 0x80, 0xa5, 0xc9, +0x19, 0x35, 0xfe, 0x6a, 0x1c, 0x7e, 0x97, 0x96, +0x4d, 0x5e, 0xdc, 0xdf, 0x14, 0xca, 0x3b, 0x9e, +0xaa, 0xe5, 0x19, 0xb3, 0x59, 0x39, 0xc9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0x4e, 0x79, 0xaf, 0xda, 0xe4, 0xf1, 0x48, +0x07, 0x59, 0xab, 0x89, 0xdc, 0xd7, 0xa6, 0xaa, +0x0f, 0x5a, 0xae, 0xea, 0x55, 0x42, 0xe0, 0xeb, +0x92, 0x02, 0xea, 0xbf, 0x58, 0xb0, 0x88, 0xc0, +0xf3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0x74, 0xda, 0x33, 0xa0, 0xaf, +0x36, 0xeb, 0x04, 0x4b, 0x1d, 0xf8, 0xaa, 0x42, +0x46, 0x51, 0x88, 0x59, 0x69, 0x87, 0xc7, 0x26, +0x5c, 0x55, 0xd6, 0xf6, 0xe5, 0x7c, 0x8e, 0x9e, +0xc8, 0x60, 0xfd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0xc7, 0xae, 0x15, +0xf1, 0x55, 0xfe, 0x9d, 0x4f, 0x19, 0xc4, 0x73, +0x7e, 0xe8, 0xcb, 0x9f, 0x8c, 0x98, 0x0a, 0x6f, +0xf0, 0x32, 0xdd, 0xd8, 0x5d, 0x3e, 0xb1, 0x26, +0x7f, 0x0b, 0x83, 0xf2, 0x4d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0x41, +0x48, 0x18, 0x42, 0x4d, 0x5f, 0xf9, 0x2f, 0x30, +0x35, 0x00, 0x65, 0x54, 0x41, 0x97, 0x5e, 0x4f, +0xed, 0x99, 0x49, 0x69, 0x6b, 0xea, 0x6d, 0x1c, +0xad, 0xdf, 0x7d, 0xf7, 0xf8, 0x1f, 0x5d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0x12, 0xda, 0x37, 0xc7, 0x2c, 0x5e, 0x85, +0x2b, 0x53, 0x8f, 0x3b, 0x44, 0x8b, 0xbb, 0x3c, +0x2d, 0x9e, 0xb5, 0x1c, 0x5a, 0x05, 0x63, 0xaf, +0x53, 0x7d, 0x31, 0xa6, 0x98, 0xaa, 0x8c, 0x5a, +0xdd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0xcf, 0x5f, 0xe8, 0x4c, 0x20, +0xb1, 0xef, 0xa2, 0x2b, 0x44, 0xee, 0xfb, 0x64, +0x2a, 0x85, 0xcb, 0x4e, 0x93, 0x5d, 0x5f, 0xbf, +0x16, 0xd9, 0x08, 0x8b, 0x42, 0x64, 0x4e, 0x83, +0x83, 0xa9, 0xf1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0xdd, 0x36, 0xf8, +0x7b, 0x12, 0xf7, 0xc2, 0xce, 0x7a, 0x50, 0x77, +0x55, 0xa0, 0xf1, 0x65, 0xe8, 0x90, 0xe3, 0xef, +0xe5, 0xb1, 0xe3, 0x91, 0x50, 0xe1, 0xba, 0xaf, +0xf4, 0x0a, 0xe3, 0x04, 0xd0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x99, +0xef, 0x03, 0x41, 0x9f, 0x3b, 0x18, 0x57, 0x23, +0xaf, 0x05, 0x20, 0x57, 0x32, 0xf4, 0x3c, 0xf5, +0xbf, 0xe4, 0xd2, 0x0f, 0x60, 0x24, 0xc0, 0x65, +0xdb, 0xd1, 0xff, 0x52, 0x01, 0xbb, 0x9f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0xc7, 0x5e, 0x1d, 0xfd, 0x1e, 0x28, 0xe7, +0x93, 0x48, 0x31, 0xca, 0xaa, 0xa6, 0xd5, 0x1e, +0x1b, 0x65, 0x82, 0xf4, 0x3b, 0x4e, 0x06, 0x85, +0xb7, 0xd3, 0x32, 0x64, 0x51, 0x9e, 0x22, 0xf9, +0xe6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0xa9, 0x3d, 0x19, 0xb0, 0x94, +0x88, 0xde, 0x51, 0xd8, 0xee, 0x7e, 0x18, 0xf8, +0xd3, 0x19, 0x8a, 0xf3, 0xa5, 0x27, 0xa1, 0x91, +0xe7, 0x52, 0x67, 0xcd, 0x20, 0x77, 0x4b, 0x45, +0x90, 0xdf, 0x59, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0xc9, 0xc2, 0x40, +0x7c, 0x36, 0xbd, 0x58, 0x86, 0xc7, 0x39, 0xf9, +0x50, 0x01, 0xc2, 0x35, 0xbc, 0xe3, 0xa6, 0x4e, +0x11, 0x43, 0x2b, 0xcb, 0x55, 0xbd, 0xb2, 0xb5, +0x9c, 0x37, 0x26, 0x10, 0x91, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0xc0, +0x80, 0xcb, 0x27, 0xed, 0xc2, 0x71, 0x7a, 0x2f, +0x36, 0xfa, 0x19, 0xff, 0x8d, 0x3f, 0x21, 0xd4, +0x94, 0x2f, 0xc5, 0xf7, 0xc6, 0x59, 0x5e, 0xf3, +0x19, 0xba, 0x2e, 0xad, 0x92, 0x10, 0xc3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0x9c, 0x32, 0x5b, 0x65, 0xea, 0x46, 0xaa, +0x61, 0x8b, 0x05, 0xb7, 0x3d, 0x52, 0x3a, 0x42, +0xcc, 0xc2, 0xd4, 0x70, 0xe5, 0xa7, 0x18, 0x28, +0x9b, 0x4b, 0x3b, 0x9a, 0x94, 0x11, 0xf7, 0x2b, +0x03, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0xa6, 0xb9, 0x02, 0x0d, 0x05, +0xf2, 0xb0, 0x5d, 0xcf, 0xaa, 0x3f, 0x6d, 0xc1, +0x64, 0xb2, 0x70, 0x47, 0x95, 0x9e, 0x16, 0x1d, +0x4b, 0x16, 0x08, 0x85, 0x65, 0xbc, 0x7a, 0x55, +0x89, 0x1c, 0x18, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x13, 0x6b, 0xf6, +0x18, 0x9c, 0xad, 0x1c, 0x34, 0x03, 0xf2, 0x90, +0xf6, 0x1d, 0x76, 0x22, 0x40, 0x5f, 0x90, 0x5e, +0x87, 0xdd, 0xe1, 0x3a, 0xa4, 0x38, 0xfb, 0xe2, +0x92, 0xc5, 0x5e, 0x8f, 0x88, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0x71, +0x76, 0x91, 0xff, 0x14, 0xd8, 0x7a, 0x7d, 0x0b, +0x9f, 0x47, 0xdf, 0x7b, 0xb8, 0xe4, 0xc5, 0x35, +0x91, 0xa6, 0x4a, 0xbb, 0x0d, 0xa4, 0x88, 0xad, +0x40, 0x0f, 0x6a, 0x32, 0x74, 0x56, 0x0b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0xc1, 0x50, 0x7c, 0xb6, 0xa6, 0x0f, 0xb5, +0xf0, 0xf7, 0xa2, 0x05, 0xbc, 0xd7, 0x22, 0xa8, +0x4f, 0xd6, 0x8d, 0xec, 0x6d, 0xe0, 0x07, 0x42, +0x77, 0x40, 0xe9, 0x09, 0xd5, 0xaa, 0xb9, 0x96, +0x9e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0x9e, 0x75, 0x89, 0x15, 0x8b, +0x51, 0x4c, 0xa8, 0x3d, 0xb2, 0x5f, 0x59, 0xbe, +0xe8, 0x15, 0x8d, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x71, 0x23, 0xdc, 0x78, +0x88, 0x4d, 0xef, 0x2f, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x4e, 0x16, 0x0c, +0x69, 0x1b, 0x47, 0xb5, 0x4f, 0x2a, 0x76, 0x4b, +0xcd, 0x51, 0xaa, 0xea, 0x93, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaf, 0xfc, +0xc2, 0x98, 0x49, 0x14, 0xfa, 0x3e, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0xda, +0xf1, 0x23, 0x23, 0x94, 0xbe, 0x3d, 0x11, 0xd6, +0x5a, 0x88, 0x24, 0xe9, 0x24, 0x4a, 0x4f, 0x79, +0x47, 0x59, 0x02, 0x4e, 0x5c, 0x9e, 0x74, 0xbe, +0x96, 0xa5, 0xa2, 0xd9, 0xd3, 0xb0, 0x61, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0x1d, 0x46, 0xe3, 0xb5, 0x0e, 0x94, 0x42, +0x21, 0x36, 0xbd, 0x8b, 0x5c, 0xb7, 0x77, 0x51, +0xd6, 0x95, 0x02, 0x29, 0x21, 0xaa, 0x10, 0x48, +0xf8, 0x17, 0x50, 0x3b, 0x1f, 0x10, 0xe5, 0xd8, +0x88, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0xa6, 0x70, 0xe8, 0x22, 0x95, +0x0e, 0x5c, 0xd2, 0xa5, 0xe1, 0xff, 0xc3, 0x08, +0x97, 0x24, 0xa0, 0x02, 0xd0, 0xdd, 0xf4, 0xff, +0xb3, 0xe3, 0x03, 0x38, 0x2b, 0x23, 0xfa, 0x31, +0x6b, 0xad, 0xdc, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0x6e, 0x66, 0x5f, +0x8c, 0xb3, 0xc3, 0xe8, 0x70, 0x1b, 0x9a, 0x0a, +0xde, 0x3d, 0x24, 0xac, 0x1a, 0xca, 0x65, 0x3e, +0x61, 0x2c, 0x81, 0x0f, 0xdc, 0x4a, 0x6a, 0x64, +0x5d, 0x8f, 0x53, 0x8b, 0xb5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x97, +0x38, 0x96, 0xc5, 0xe4, 0xd4, 0x6e, 0x94, 0x85, +0xcc, 0x07, 0xc1, 0x63, 0xb8, 0x26, 0x74, 0x92, +0x3f, 0x92, 0xb5, 0x2b, 0x72, 0x58, 0x0f, 0x16, +0x4b, 0x38, 0x9e, 0x2f, 0x80, 0x7e, 0xd3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0x6e, 0x62, 0x25, 0x3f, 0x08, 0x23, 0x6d, +0x9b, 0x52, 0x69, 0x15, 0x55, 0x7b, 0x66, 0x57, +0x82, 0xda, 0x47, 0xe3, 0x89, 0xe0, 0xb5, 0x9c, +0x5a, 0x91, 0xf3, 0xe3, 0x4d, 0x04, 0xb8, 0x4a, +0xf5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0x9a, 0x1d, 0x48, 0x82, 0xbb, +0xa9, 0x68, 0x6e, 0x0d, 0x52, 0x95, 0xa1, 0x08, +0x66, 0x8d, 0x56, 0xa4, 0xba, 0x2a, 0x85, 0x74, +0xa4, 0xc4, 0x2a, 0xba, 0x63, 0x39, 0xfb, 0x5a, +0x4d, 0x60, 0xb7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0xee, 0x6f, 0x04, +0xde, 0x51, 0x8b, 0x17, 0xf5, 0xdd, 0x6a, 0x64, +0x8d, 0x65, 0x08, 0x38, 0xaf, 0x8c, 0x0c, 0xf3, +0xcc, 0xbe, 0x53, 0x22, 0xdf, 0x45, 0x93, 0xf8, +0xb5, 0x5d, 0x7f, 0x6d, 0xb0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0x6e, +0x54, 0xab, 0xdd, 0x4b, 0xac, 0x81, 0xa0, 0x2b, +0x61, 0xfe, 0x3d, 0xb8, 0x7f, 0x34, 0x0d, 0x9e, +0xd9, 0x74, 0x10, 0x4f, 0x28, 0xb3, 0x3e, 0x13, +0x18, 0xa4, 0x70, 0x7c, 0x6f, 0x9d, 0xe3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0x89, 0xb9, 0xa5, 0x1f, 0x07, 0x2c, 0xbe, +0x52, 0x26, 0x4e, 0x7c, 0x55, 0xe5, 0x06, 0x21, +0x4c, 0xc8, 0xd5, 0xe5, 0xea, 0xd6, 0x4a, 0xb9, +0x7c, 0x9a, 0x40, 0xd3, 0x03, 0xf7, 0x6f, 0xb1, +0x62, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0x2e, 0x7f, 0x42, 0x72, 0x57, +0xb2, 0x36, 0xaa, 0x3a, 0x38, 0xf6, 0xa3, 0x41, +0x62, 0x70, 0x48, 0x6f, 0x61, 0x86, 0x57, 0x0e, +0x00, 0xc4, 0xec, 0x9c, 0xa9, 0x7e, 0x87, 0xa3, +0x9f, 0xac, 0x4f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0x18, 0xa1, 0xae, +0x01, 0x91, 0xd3, 0x4f, 0x4c, 0x72, 0x3f, 0x31, +0x21, 0x10, 0x30, 0x74, 0x9d, 0x4b, 0xd8, 0x80, +0x74, 0xcf, 0x5f, 0x82, 0xdc, 0xee, 0xee, 0x6c, +0x2a, 0x85, 0x40, 0x91, 0xd2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0x54, +0x99, 0x2e, 0xc9, 0x3e, 0x64, 0xe2, 0xa9, 0x3e, +0x69, 0xc8, 0x48, 0x77, 0x78, 0xc3, 0xee, 0x25, +0xf0, 0x97, 0x04, 0x8e, 0x60, 0x09, 0x09, 0x7e, +0x59, 0xc2, 0x58, 0xcf, 0x63, 0x5b, 0x7d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0xed, 0xa4, 0x7d, 0x28, 0x43, 0x76, 0x1b, +0x5c, 0xd4, 0xbc, 0x3d, 0xd0, 0x3e, 0x70, 0x3f, +0x34, 0x45, 0x06, 0xc6, 0x3b, 0xf3, 0xde, 0x33, +0x0f, 0x90, 0x3b, 0x04, 0x5a, 0x25, 0x13, 0xa6, +0xc1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0xfe, 0x9a, 0xc9, 0x9c, 0x39, +0xc3, 0x25, 0xeb, 0x8e, 0xa8, 0x93, 0x6f, 0x73, +0xfc, 0x7e, 0x41, 0x19, 0xe3, 0x1a, 0x63, 0xc1, +0x93, 0xd2, 0x17, 0x8e, 0x4c, 0xc4, 0x2a, 0x27, +0x49, 0x57, 0xde, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0x3b, 0x73, 0x73, +0xaf, 0x50, 0x64, 0xd3, 0x33, 0x45, 0xd2, 0xd2, +0x1d, 0x05, 0x61, 0x04, 0x90, 0x20, 0xbb, 0xa9, +0xf3, 0x58, 0xf0, 0x90, 0x85, 0x5b, 0x39, 0x8b, +0x68, 0xf7, 0x62, 0x95, 0xfc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0xa7, +0x74, 0xf3, 0xaf, 0x66, 0xd9, 0xb8, 0x9d, 0x7d, +0xa2, 0xf9, 0xfc, 0xe3, 0x0c, 0x45, 0xde, 0x1c, +0xea, 0xff, 0x27, 0x4b, 0xcd, 0x48, 0x4f, 0x9c, +0x30, 0x66, 0x9e, 0xbc, 0x9a, 0xc1, 0xfa, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0x48, 0xb4, 0xc3, 0xc3, 0x2d, 0x6d, 0xdb, +0xf9, 0x15, 0xee, 0x91, 0x90, 0xfc, 0x62, 0x10, +0x93, 0xde, 0x7a, 0x8d, 0xc7, 0xa3, 0xeb, 0x0d, +0xde, 0x6b, 0x1a, 0x6c, 0x45, 0xf7, 0x69, 0xc1, +0xf4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0x0c, 0xdb, 0x4e, 0xfb, 0x5a, +0x9b, 0xb8, 0x04, 0x63, 0x54, 0x8b, 0x31, 0x05, +0x5e, 0xd3, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x04, 0xde, 0xbb, 0x6e, +0x5c, 0xa3, 0xd3, 0x77, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0xb4, 0x4f, 0xc4, +0x1d, 0x54, 0x01, 0x5f, 0xc4, 0x4c, 0xff, 0x6f, +0x32, 0xaa, 0xfa, 0xbc, 0x50, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x91, 0xec, +0xb3, 0x52, 0xe4, 0xe7, 0xcd, 0x0d, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0xc4, +0x33, 0xe2, 0xe8, 0x19, 0x13, 0xec, 0xef, 0xfe, +0x8b, 0x74, 0x47, 0x66, 0xb3, 0x10, 0x10, 0x65, +0xe7, 0x09, 0xc7, 0xc9, 0x96, 0xe1, 0xa3, 0xee, +0x02, 0x36, 0x75, 0xa1, 0x96, 0x97, 0x4f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x19, 0x55, 0xa7, 0xa2, 0xf2, 0x07, 0x8a, +0xf7, 0x7d, 0xcd, 0xf2, 0xb2, 0xe1, 0xf9, 0x08, +0xb3, 0xe6, 0x58, 0xde, 0x81, 0x23, 0xfb, 0x8a, +0x94, 0x86, 0x6c, 0x68, 0xf6, 0xad, 0xac, 0x6f, +0xc6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0x15, 0x43, 0xbc, 0x84, 0x54, +0x86, 0xda, 0x37, 0xf1, 0xd3, 0x7a, 0x73, 0x05, +0x39, 0x5e, 0xca, 0xce, 0x77, 0x49, 0x4a, 0x42, +0x97, 0x52, 0x49, 0x0b, 0xbd, 0xb3, 0xa1, 0xdf, +0x44, 0x2c, 0xc0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0x72, 0x06, 0x97, +0x7a, 0x38, 0x1d, 0xf7, 0x35, 0x9a, 0x1d, 0x24, +0x94, 0xe7, 0x4e, 0xd5, 0xdd, 0xb5, 0xc6, 0x6b, +0x13, 0x28, 0xac, 0xd1, 0xcf, 0x06, 0x62, 0x3e, +0xde, 0x18, 0xbb, 0x6b, 0xd9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0xc9, +0x58, 0xea, 0x76, 0x75, 0xa8, 0xb9, 0x69, 0xc6, +0x11, 0x9d, 0x44, 0xb2, 0xc1, 0xfb, 0x0a, 0x0e, +0x83, 0x0d, 0xf7, 0x1a, 0xe4, 0xc0, 0x36, 0xe9, +0xa1, 0xcb, 0xf8, 0x59, 0x79, 0x80, 0x71, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0x95, 0xa1, 0x83, 0x6f, 0x2b, 0x43, 0xc3, +0x8b, 0xdd, 0x1c, 0x28, 0xb0, 0x05, 0x75, 0x35, +0x1b, 0xf0, 0xcd, 0x55, 0x14, 0x70, 0x95, 0x6d, +0x93, 0xfb, 0x9a, 0x71, 0x42, 0xd5, 0x57, 0xbf, +0x04, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0xe2, 0x91, 0x7f, 0xc9, 0xdc, +0x0d, 0xfb, 0x10, 0x02, 0x6e, 0xe5, 0x0e, 0x55, +0xc7, 0x6a, 0xd2, 0xc4, 0x75, 0x45, 0x7f, 0x5d, +0xc3, 0x2a, 0x3e, 0x98, 0x34, 0x83, 0xe7, 0x9c, +0x6a, 0xcc, 0x98, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0x54, 0xf1, 0xcf, +0x10, 0xae, 0x68, 0x10, 0x5c, 0x12, 0xc7, 0x89, +0x95, 0xe4, 0x4e, 0x67, 0xc2, 0xd9, 0xa6, 0x28, +0xec, 0x22, 0x51, 0xf5, 0xe9, 0x84, 0x73, 0x60, +0x48, 0xbd, 0x60, 0x0c, 0xf7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0x77, +0x2e, 0x58, 0xf8, 0xce, 0xd2, 0x06, 0xc7, 0x8d, +0x52, 0x4c, 0xa1, 0x82, 0xa1, 0xcc, 0xed, 0xe0, +0x39, 0x86, 0x52, 0xdd, 0x92, 0x8c, 0xc3, 0x54, +0x85, 0xa1, 0x5f, 0xde, 0x30, 0x0c, 0x72, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0xce, 0x39, 0xaa, 0x45, 0x76, 0x20, 0x9f, +0x29, 0x01, 0xe5, 0x28, 0x38, 0x3b, 0x8c, 0x82, +0x7b, 0x2f, 0x6c, 0xeb, 0x20, 0x46, 0x06, 0x4e, +0x9a, 0xed, 0xc5, 0x66, 0x84, 0x14, 0x9a, 0x1e, +0xb1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0x71, 0xb8, 0x30, 0xcc, 0xe5, +0x16, 0xb1, 0x43, 0x15, 0x79, 0xfc, 0x7d, 0xfd, +0x84, 0xaf, 0x29, 0x39, 0xe6, 0xf5, 0x46, 0x88, +0x19, 0x5b, 0x9c, 0x65, 0x9f, 0x06, 0x03, 0xc8, +0x3b, 0x3c, 0x26, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0xd5, 0x08, 0xba, +0x39, 0xff, 0x58, 0x9a, 0xc6, 0xf8, 0x7b, 0x93, +0x9a, 0x23, 0x09, 0xc4, 0xbe, 0x52, 0xa0, 0xba, +0xe9, 0x82, 0x92, 0x27, 0x43, 0x49, 0x09, 0x23, +0x31, 0xd5, 0xcc, 0x95, 0x7b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0x51, +0x11, 0xff, 0xa2, 0x90, 0x39, 0x9a, 0x1b, 0x61, +0x6f, 0x26, 0xcb, 0x26, 0xe0, 0xb9, 0x0f, 0xb3, +0x06, 0x56, 0xaa, 0x85, 0xe8, 0x77, 0x81, 0x17, +0xaf, 0x07, 0x33, 0xca, 0x5a, 0x13, 0x2d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0x76, 0x13, 0x0b, 0xaa, 0x19, 0xfe, 0x51, +0x87, 0x5e, 0x14, 0xf8, 0xe3, 0x0c, 0x64, 0xde, +0x36, 0x72, 0xa7, 0x38, 0x3e, 0xb6, 0x99, 0x79, +0x09, 0x1c, 0xa7, 0x11, 0x46, 0x33, 0x89, 0xd3, +0xf6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0xa7, 0x7d, 0xab, 0x23, 0xf2, +0x71, 0xe6, 0xd2, 0x25, 0xa3, 0x8b, 0x16, 0xbb, +0x83, 0xb1, 0x10, 0x53, 0x42, 0x8c, 0x41, 0x79, +0x93, 0x2b, 0x23, 0x33, 0xd8, 0xe1, 0xda, 0xe5, +0x09, 0x3d, 0xc7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0x59, 0x50, 0xc9, +0x59, 0x7c, 0xdc, 0x9a, 0xdd, 0x6b, 0x3b, 0x03, +0xe4, 0xf5, 0x6a, 0xe6, 0xb7, 0xff, 0x1e, 0x8c, +0x81, 0xd0, 0x66, 0xaa, 0x6f, 0xdd, 0x69, 0x66, +0x81, 0x24, 0xea, 0x1c, 0xf9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0xba, +0x25, 0x28, 0x56, 0x8d, 0xba, 0x64, 0x88, 0x37, +0x0c, 0x49, 0x24, 0x2f, 0x75, 0x98, 0x1b, 0xbc, +0xeb, 0xba, 0x0b, 0x73, 0xc3, 0x7d, 0x28, 0x60, +0x0c, 0x01, 0xb2, 0x98, 0x61, 0x93, 0x57, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0xa8, 0xca, 0xb1, 0xa8, 0xd4, 0xd2, 0x91, +0xbc, 0x7a, 0xff, 0x41, 0x49, 0xfa, 0x2f, 0xb7, +0x52, 0x79, 0xa8, 0x9e, 0x34, 0xfb, 0x44, 0x12, +0xb9, 0xf8, 0x35, 0x9a, 0x9a, 0xa3, 0x80, 0x3a, +0x07, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0xac, 0x3b, 0x3c, 0x8c, 0x81, +0xdd, 0x23, 0x64, 0xc9, 0xa0, 0x05, 0x97, 0x44, +0x33, 0xb1, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x15, 0xc3, 0xe0, 0x07, +0x03, 0x90, 0x1f, 0x37, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0x40, 0xfe, 0x44, +0x07, 0x3b, 0x0c, 0xe6, 0x8a, 0xf4, 0xbb, 0x33, +0x42, 0x3a, 0x06, 0x4a, 0xc5, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf7, 0x46, +0x0f, 0x61, 0xcd, 0xdb, 0x80, 0x03, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0x64, +0xaa, 0xa7, 0x84, 0x21, 0xa0, 0x9c, 0x7a, 0x4a, +0x86, 0x98, 0x3e, 0xd3, 0x0e, 0xed, 0x67, 0xb0, +0x6b, 0xae, 0xaf, 0xc1, 0xc2, 0xd9, 0x6b, 0xc7, +0xc9, 0x74, 0x89, 0xa1, 0x85, 0xfc, 0x22, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0x27, 0xfd, 0x50, 0xb9, 0xb9, 0xaf, 0x0a, +0x79, 0x53, 0x76, 0x10, 0x8c, 0xfc, 0xa1, 0x0a, +0x09, 0x35, 0x4c, 0xc2, 0x74, 0xc9, 0x68, 0x63, +0xb3, 0x25, 0xcc, 0x5b, 0xfe, 0x8b, 0x6d, 0x9d, +0xce, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0x03, 0xa2, 0xf2, 0x35, 0x09, +0x69, 0x57, 0xba, 0xab, 0x96, 0xb0, 0xa5, 0x53, +0xf2, 0xe6, 0x02, 0x05, 0xb2, 0xbf, 0x0d, 0x8f, +0xe0, 0x07, 0xb9, 0xf0, 0x46, 0xb2, 0x84, 0x56, +0x4a, 0xde, 0x1c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0xfd, 0x8a, 0x2b, +0x40, 0x96, 0x70, 0xdb, 0x72, 0x5f, 0xaf, 0x2a, +0x9f, 0x28, 0x51, 0x22, 0x82, 0x61, 0xec, 0x30, +0xe6, 0x9e, 0x65, 0xb6, 0x79, 0x2d, 0x5f, 0x11, +0x58, 0x6d, 0xc4, 0x07, 0xc6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0x64, +0xb6, 0xf6, 0x08, 0x9b, 0x67, 0xcc, 0x45, 0xa2, +0x00, 0x2f, 0x6a, 0x65, 0x3d, 0xfd, 0x66, 0xdc, +0x3f, 0x6c, 0x85, 0xd9, 0xaf, 0xa7, 0x55, 0x08, +0x21, 0xee, 0x7a, 0xdc, 0x8b, 0xda, 0x9a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0x39, 0x8c, 0x6a, 0x73, 0xa4, 0xd0, 0xf3, +0xf8, 0x55, 0xe2, 0xd9, 0xef, 0x12, 0x37, 0x8e, +0xfd, 0x13, 0x1d, 0x11, 0x23, 0x42, 0x97, 0xf2, +0x04, 0x31, 0x79, 0x83, 0x3c, 0x42, 0xf2, 0xe4, +0x1e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0x94, 0xfe, 0xdf, 0x65, 0xa1, +0xec, 0xa5, 0x5d, 0x4f, 0xb3, 0x94, 0xfd, 0x74, +0x9c, 0x31, 0xbf, 0x9a, 0x75, 0xc2, 0x48, 0x9c, +0xc6, 0x2a, 0x07, 0x2b, 0xbf, 0xdc, 0xa7, 0xc9, +0x0c, 0xd7, 0x94, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0x18, 0xdf, 0x7e, +0x94, 0xf1, 0x22, 0xe5, 0xe5, 0xbe, 0xee, 0xa7, +0xc5, 0x1c, 0xdd, 0x5f, 0xc9, 0x65, 0x2a, 0x5e, +0x4a, 0x6c, 0x86, 0xad, 0xab, 0xbc, 0x37, 0xfd, +0x81, 0x94, 0x51, 0x21, 0xd4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0x28, +0x9b, 0x6c, 0x77, 0x3a, 0x31, 0x4c, 0x55, 0xf4, +0x75, 0x8f, 0xbd, 0x3e, 0x6e, 0x74, 0xa7, 0x49, +0xde, 0x27, 0x71, 0xa7, 0xfe, 0xc5, 0x5c, 0x73, +0x77, 0xfe, 0x7e, 0x1a, 0xc6, 0xcf, 0xdb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0xc4, 0x8d, 0xa8, 0x6e, 0xf1, 0x77, 0xed, +0x02, 0xb1, 0xa0, 0x5c, 0xa0, 0x71, 0x53, 0x4d, +0xc3, 0x0a, 0x62, 0x42, 0x53, 0x8b, 0xa3, 0xae, +0x84, 0xf9, 0x42, 0xd4, 0x90, 0x67, 0xdc, 0x3d, +0x25, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0x60, 0x79, 0x7d, 0x6b, 0xbc, +0x72, 0xff, 0x9e, 0xcf, 0xd4, 0x13, 0x52, 0xb2, +0x5a, 0x39, 0x8f, 0x78, 0x44, 0x5a, 0xca, 0x98, +0xbc, 0xd9, 0x46, 0x90, 0xad, 0x76, 0xed, 0xbc, +0x96, 0x64, 0xe2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0x90, 0xff, 0x9f, +0x79, 0xe4, 0x3c, 0x32, 0x85, 0xe4, 0x0e, 0xf4, +0x88, 0x29, 0xed, 0x5f, 0x4b, 0xe1, 0x59, 0x4f, +0xd8, 0x79, 0x25, 0xd7, 0x62, 0xe7, 0xe8, 0xa8, +0x28, 0x49, 0x32, 0x39, 0x40, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0xc1, +0x02, 0xa6, 0x2b, 0xa0, 0x0d, 0x9b, 0xc3, 0x06, +0x6d, 0x1d, 0xc0, 0x3f, 0xf1, 0x78, 0x33, 0xf9, +0x79, 0xdb, 0x4b, 0x3a, 0x96, 0x3f, 0x00, 0x89, +0x0a, 0x8a, 0xd4, 0x23, 0x81, 0xd5, 0x23, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0x56, 0xb3, 0x73, 0xd8, 0x7c, 0xe0, 0xa4, +0x25, 0x19, 0x71, 0x84, 0x9a, 0x17, 0x0c, 0x64, +0xec, 0x8f, 0xcf, 0x16, 0x5e, 0xe1, 0x2d, 0x10, +0x27, 0x76, 0x2a, 0xca, 0xa5, 0xad, 0x02, 0x36, +0x5d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0x78, 0xa1, 0xc9, 0xde, 0x67, +0x43, 0xab, 0x99, 0xce, 0x13, 0x07, 0xc4, 0x83, +0xfc, 0x7d, 0x8b, 0xf8, 0xa7, 0x59, 0x7c, 0x37, +0x72, 0xa7, 0x82, 0x3f, 0xc9, 0xbe, 0x11, 0xb7, +0x74, 0x60, 0x66, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x86, 0xec, 0x67, +0x6b, 0x19, 0xaf, 0x63, 0xeb, 0x58, 0x63, 0x97, +0x72, 0x7c, 0xaa, 0x36, 0x2d, 0x33, 0x2e, 0xa1, +0xfb, 0xa4, 0xb4, 0x72, 0xcf, 0xce, 0x19, 0x79, +0xc9, 0x00, 0xdc, 0x02, 0xce, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0x04, +0xf5, 0x10, 0xb9, 0x4c, 0x56, 0xe2, 0x74, 0x63, +0x3b, 0x61, 0xd9, 0x97, 0x8e, 0x5b, 0xb4, 0x8e, +0xbb, 0xca, 0x28, 0x76, 0xef, 0x86, 0xfe, 0xd6, +0x56, 0x16, 0x98, 0x7e, 0x02, 0x58, 0xc5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x8f, 0x7e, 0x27, 0x9e, 0xa6, 0xde, 0x9d, +0xaf, 0x72, 0x87, 0xb0, 0x82, 0x95, 0x13, 0xa0, +0x24, 0x98, 0xe8, 0x4f, 0x8a, 0x85, 0xec, 0x36, +0x96, 0x07, 0x93, 0x62, 0x51, 0xb2, 0xc4, 0x55, +0xd0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0x3d, 0xfa, 0xa5, 0xc4, 0xbd, +0x58, 0x0a, 0xcf, 0x9f, 0xdd, 0x8e, 0xae, 0x87, +0x82, 0x80, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x5a, 0xe7, 0xde, 0xdc, +0xdc, 0x10, 0x68, 0x99, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0x84, 0x37, 0xc4, +0x7e, 0x8c, 0x47, 0x98, 0xeb, 0x4f, 0xc8, 0xd8, +0x38, 0x8f, 0xe2, 0xa6, 0x78, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0xfd, +0xd5, 0x93, 0x63, 0xb4, 0x76, 0xf9, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x01, +0xe0, 0x8d, 0x7b, 0xbf, 0x8f, 0x7e, 0xbd, 0x2e, +0x88, 0x24, 0xbc, 0x85, 0xf8, 0xfc, 0x23, 0x63, +0x6c, 0xbd, 0xb3, 0xe9, 0xb2, 0xd6, 0x44, 0xd7, +0xef, 0xb8, 0x11, 0xfb, 0x6b, 0xda, 0x44, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0x53, 0x86, 0x32, 0x34, 0xb6, 0x26, 0x4e, +0x47, 0x3d, 0x2b, 0xd5, 0x80, 0x8d, 0x6c, 0xcb, +0x2c, 0x46, 0x58, 0xe9, 0x08, 0xa6, 0x6a, 0xdf, +0x71, 0x61, 0x1c, 0x87, 0xf3, 0x87, 0x60, 0x17, +0x15, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0xe0, 0x87, 0x66, 0x58, 0x2d, +0x2b, 0x54, 0xe8, 0x83, 0xae, 0xb1, 0xb4, 0x02, +0xf8, 0x94, 0x94, 0x92, 0xf7, 0xd1, 0x82, 0xcc, +0x07, 0x80, 0xac, 0xcc, 0x79, 0x4d, 0xd0, 0xaf, +0xd2, 0xa9, 0x01, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0xb6, 0x2c, 0x67, +0x24, 0xc3, 0xab, 0x64, 0xed, 0xe5, 0x8c, 0xa8, +0x07, 0xe3, 0x17, 0x26, 0x66, 0xf4, 0xe1, 0x5f, +0xf5, 0x84, 0x6c, 0x38, 0x15, 0xdc, 0x05, 0x37, +0x4b, 0xff, 0xbd, 0x06, 0x54, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x91, +0xbf, 0x9d, 0x08, 0xe5, 0xba, 0x07, 0x0e, 0xd8, +0x94, 0xbd, 0xc8, 0xa3, 0x88, 0x9f, 0xcc, 0xb6, +0x91, 0x5d, 0xb7, 0x6d, 0xe4, 0x30, 0xe0, 0x35, +0xde, 0x53, 0xa1, 0x9c, 0x5b, 0x92, 0xb4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0x4c, 0x0e, 0xb6, 0x2f, 0xd0, 0x45, 0xe9, +0x4c, 0x98, 0x62, 0x98, 0xe1, 0x33, 0x24, 0x68, +0x78, 0xf0, 0xb6, 0x15, 0x49, 0xb8, 0xff, 0x23, +0x64, 0xd9, 0x66, 0x12, 0x93, 0x76, 0x70, 0x29, +0x98, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0xdb, 0x56, 0xdc, 0xcc, 0x0f, +0x59, 0x99, 0x26, 0xae, 0x45, 0x7b, 0xa7, 0x12, +0x70, 0xd4, 0x72, 0xc9, 0xa7, 0x4f, 0x59, 0xea, +0xf5, 0xb1, 0xf3, 0xc6, 0x2f, 0xf1, 0xa9, 0x64, +0x38, 0x16, 0xbf, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0xbf, 0x54, 0x0b, +0x66, 0x97, 0xd5, 0x27, 0x71, 0x67, 0xee, 0x91, +0x12, 0xca, 0xb1, 0x29, 0x91, 0xf4, 0x90, 0xd5, +0x60, 0x02, 0x62, 0x63, 0x09, 0x9c, 0xfb, 0xf8, +0xa5, 0x93, 0xde, 0x57, 0x2b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x7e, +0xc5, 0x0d, 0x41, 0xd6, 0xd7, 0x9f, 0x09, 0xaa, +0xce, 0xfe, 0x46, 0xc4, 0x84, 0x78, 0xe3, 0x07, +0x94, 0xe2, 0x04, 0x76, 0xd4, 0x53, 0x54, 0xac, +0x69, 0xb9, 0xb3, 0x01, 0x24, 0xfd, 0xa8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0x86, 0x94, 0x6c, 0x38, 0x6e, 0xa8, 0x67, +0x3f, 0xf8, 0x94, 0x2e, 0xb8, 0xcb, 0x17, 0x25, +0xa1, 0xca, 0x36, 0xc4, 0x7f, 0x2e, 0x4b, 0x4b, +0xf4, 0xfe, 0xf6, 0x15, 0x1a, 0x5c, 0x82, 0xe3, +0xbf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0x47, 0x85, 0x0b, 0x55, 0xe2, +0x1d, 0x12, 0xfd, 0x1c, 0xe9, 0xe5, 0x90, 0xb3, +0x91, 0x69, 0x83, 0xfd, 0x6d, 0xcc, 0xb7, 0x4b, +0x06, 0xf3, 0x95, 0x05, 0x03, 0x10, 0xfd, 0x6c, +0x4d, 0xf0, 0x13, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x0b, 0x90, 0x65, +0x13, 0x9b, 0x64, 0xda, 0x38, 0x72, 0x82, 0x16, +0x50, 0x91, 0x48, 0xf3, 0x03, 0x5d, 0xe9, 0x4b, +0x64, 0xc6, 0x7f, 0x10, 0xa6, 0xf4, 0x49, 0xbb, +0xf5, 0x11, 0xe7, 0x6a, 0x14, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0xe5, +0x5b, 0xdc, 0x20, 0xe5, 0x35, 0x50, 0x24, 0xbc, +0xf3, 0xb6, 0xae, 0x8d, 0xa8, 0xef, 0xee, 0x6c, +0x5c, 0xe2, 0x46, 0x0d, 0x70, 0x34, 0x7a, 0xfc, +0x15, 0x8b, 0x0e, 0x91, 0x08, 0xa3, 0x11, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0xe4, 0xad, 0x9d, 0xbd, 0x73, 0x57, 0x98, +0x36, 0x14, 0x6a, 0x90, 0xcc, 0x27, 0x59, 0x4f, +0xf3, 0xba, 0x32, 0xd3, 0x42, 0x8a, 0x59, 0x14, +0x9b, 0xc2, 0x5e, 0x89, 0xf9, 0xdc, 0x43, 0x29, +0x15, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0x08, 0x3e, 0x0f, 0x42, 0x60, +0x44, 0x13, 0xdc, 0x13, 0x58, 0x64, 0x8c, 0x4e, +0xe0, 0xfe, 0x8a, 0xfa, 0xb2, 0x60, 0x29, 0xcf, +0x6e, 0xbb, 0x08, 0xbc, 0x17, 0x49, 0xd8, 0xdb, +0x59, 0xe9, 0x6f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0x91, 0xff, 0x66, +0x73, 0x26, 0x5e, 0x66, 0xc9, 0x11, 0x2c, 0x2c, +0x34, 0x93, 0xd4, 0xc0, 0x3c, 0xd4, 0x24, 0x7d, +0xb3, 0xf3, 0x17, 0x76, 0xb6, 0xd7, 0xdb, 0xad, +0xd0, 0x10, 0x50, 0xcf, 0xfe, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0xfd, +0x39, 0x56, 0x41, 0x36, 0x84, 0xaa, 0xe2, 0x0c, +0xdb, 0x54, 0x8a, 0xaf, 0x64, 0x76, 0x56, 0xe2, +0xf3, 0x48, 0x50, 0xd3, 0xeb, 0x16, 0x6b, 0x85, +0x47, 0xe7, 0xc8, 0xd6, 0x1a, 0xe6, 0x64, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x81, 0xd0, 0x80, 0x28, 0x0a, 0x7f, 0x40, +0x77, 0x42, 0x75, 0x57, 0xc4, 0x75, 0xcd, 0x61, +0x5f, 0x2c, 0xc5, 0xfd, 0x94, 0xc2, 0xbe, 0xfd, +0xe5, 0xdd, 0x57, 0xc4, 0x4c, 0x92, 0x14, 0xb8, +0x6f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x63, 0xa3, 0x54, 0xf0, 0xca, +0xac, 0x6f, 0x80, 0x2f, 0x0c, 0x51, 0xca, 0x38, +0xd0, 0x50, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xff, 0x7f, 0x20, 0xf9, +0x9e, 0xc4, 0x74, 0xdc, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0x02, 0x0d, 0x20, +0xb5, 0x9e, 0x22, 0x60, 0x2a, 0x58, 0x2c, 0x59, +0x03, 0xdf, 0x28, 0x34, 0x6e, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x9c, +0x44, 0x4c, 0x5d, 0xcb, 0x20, 0x10, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0xf7, +0x85, 0xfc, 0xeb, 0xc9, 0xc5, 0xf7, 0x1e, 0x8b, +0x3c, 0xd8, 0x5a, 0x08, 0x21, 0xf0, 0xb5, 0xd7, +0x7a, 0xa2, 0x59, 0x58, 0xba, 0xcc, 0x82, 0x89, +0x27, 0xe9, 0x05, 0xe8, 0x6d, 0xe2, 0x91, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x7c, 0x54, 0xf4, 0x19, 0x8d, 0x4d, 0x75, +0xd4, 0xec, 0x96, 0x29, 0xae, 0x16, 0x8b, 0x6b, +0xf2, 0x4d, 0x1a, 0x40, 0xed, 0xce, 0xfe, 0x4f, +0x71, 0x25, 0xca, 0x07, 0x01, 0x52, 0xf1, 0xaf, +0xa9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0xe9, 0xd9, 0x27, 0x10, 0x77, +0x21, 0xcd, 0x5d, 0x5f, 0x82, 0x5b, 0x28, 0x7b, +0x84, 0x34, 0xe2, 0x5b, 0x3a, 0x9c, 0x08, 0x0c, +0x58, 0xc0, 0x60, 0xa4, 0xae, 0x70, 0x42, 0xd0, +0x07, 0xbe, 0xfd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0xd9, 0x8e, 0x76, +0xe5, 0x7e, 0x35, 0x2b, 0x40, 0xf8, 0x97, 0x4f, +0xa4, 0xa0, 0xe4, 0x09, 0x5d, 0xdd, 0xa1, 0xe3, +0xbe, 0xae, 0x5f, 0xa9, 0x7c, 0xff, 0x03, 0x26, +0x9b, 0x48, 0xc7, 0xd0, 0xa1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x83, +0xda, 0xdb, 0x0e, 0xcb, 0x10, 0x8f, 0xdb, 0x4a, +0x35, 0xbc, 0xda, 0xd1, 0x37, 0xbf, 0xbf, 0x33, +0xa7, 0x2c, 0x5c, 0xeb, 0xfe, 0x3a, 0x5f, 0xe1, +0xa1, 0xe9, 0x11, 0xa7, 0x52, 0xa9, 0xa2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0x83, 0x98, 0x29, 0x3d, 0x79, 0x96, 0x20, +0xde, 0xca, 0x15, 0x24, 0x61, 0x00, 0xd2, 0xab, +0x99, 0x64, 0xd8, 0xba, 0x53, 0xf9, 0x14, 0x00, +0x81, 0x76, 0x93, 0x41, 0xd4, 0x92, 0x0d, 0x72, +0x81, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0x3a, 0xa1, 0x23, 0xce, 0x05, +0x17, 0x35, 0x8e, 0xec, 0x84, 0x5a, 0x86, 0x5a, +0x5e, 0x16, 0x36, 0x8c, 0xf6, 0x2b, 0x79, 0x38, +0x49, 0x7f, 0x6f, 0xe2, 0xc4, 0xa2, 0x03, 0x8e, +0x7a, 0x8d, 0x42, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0x8f, 0x71, 0xdd, +0x37, 0x37, 0xc1, 0x1f, 0x89, 0x85, 0x20, 0xbb, +0xb0, 0x91, 0xd6, 0xb4, 0x4e, 0x85, 0x17, 0xdd, +0x6f, 0x2f, 0xb0, 0x72, 0xa0, 0x31, 0xa2, 0x27, +0x39, 0x71, 0xee, 0x5e, 0x8a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0x3b, +0xda, 0xa6, 0x2a, 0x14, 0xb3, 0x83, 0xfa, 0x0b, +0xab, 0x18, 0xea, 0xad, 0xe5, 0xb1, 0xea, 0x84, +0xa3, 0xe5, 0xb4, 0x19, 0x44, 0x40, 0x6e, 0x49, +0xf8, 0xce, 0x7e, 0x98, 0xe3, 0xdc, 0x16, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0x23, 0x8a, 0xf6, 0x1c, 0x1a, 0x42, 0x91, +0xf9, 0x46, 0xd3, 0x5f, 0x05, 0x7d, 0xc2, 0x07, +0x1f, 0x87, 0x0e, 0x74, 0x88, 0x69, 0x33, 0x01, +0xad, 0x07, 0x05, 0x02, 0x39, 0x50, 0x7f, 0xbe, +0x5d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0x7f, 0x75, 0x77, 0xa8, 0x02, +0x78, 0xad, 0xdf, 0x79, 0xe7, 0x6d, 0x09, 0x02, +0x6f, 0x59, 0x21, 0xb5, 0x64, 0x69, 0xae, 0xfd, +0x80, 0xaa, 0x34, 0xd5, 0x7c, 0xf8, 0x70, 0x2b, +0xee, 0x4d, 0x82, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0x45, 0x5c, 0x7b, +0xe0, 0xab, 0x70, 0xb0, 0x4d, 0x76, 0x6e, 0x29, +0x54, 0x4d, 0x5f, 0xbc, 0xd0, 0x7e, 0xd3, 0xa7, +0x93, 0xc9, 0x78, 0x08, 0x5e, 0x0f, 0xe3, 0x36, +0xf9, 0x7d, 0xd3, 0x2b, 0xbf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0xbb, +0xe0, 0xa6, 0xcf, 0xaf, 0xfa, 0x98, 0x77, 0x32, +0x1a, 0xdb, 0x0f, 0x7b, 0x1a, 0x29, 0x89, 0x23, +0x76, 0x98, 0xa1, 0x3b, 0x14, 0x60, 0x77, 0x48, +0x05, 0xaf, 0x97, 0x92, 0x8a, 0x79, 0x12, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0xa1, 0x64, 0x71, 0x06, 0x0b, 0xf2, 0xcb, +0x97, 0x41, 0xd7, 0x2a, 0x8c, 0xa7, 0xd5, 0x95, +0x26, 0xf0, 0x3b, 0xbb, 0x06, 0x2e, 0x81, 0x9d, +0x84, 0x07, 0x8e, 0xa6, 0x03, 0x10, 0x1d, 0xf6, +0x5b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0xbd, 0xed, 0x5d, 0x18, 0x42, +0x9f, 0x05, 0xd3, 0x83, 0x4c, 0xbb, 0xf8, 0xb1, +0x3a, 0xe3, 0xbe, 0x06, 0x43, 0x27, 0xb0, 0x5b, +0x3a, 0x10, 0x62, 0x16, 0x0c, 0x74, 0x2e, 0x8d, +0x5e, 0x4d, 0xcb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0xec, 0xb3, 0x69, +0x13, 0xf6, 0xd4, 0xbd, 0x81, 0x51, 0x33, 0xe8, +0xee, 0xc8, 0x95, 0x58, 0xba, 0x0f, 0xa4, 0x18, +0xa4, 0xf9, 0x47, 0xa2, 0xd8, 0xf2, 0x2b, 0x73, +0x79, 0xae, 0x97, 0x2a, 0x05, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0xc2, +0xd4, 0x8e, 0x50, 0x70, 0x64, 0x99, 0x70, 0x0c, +0xc2, 0x30, 0x67, 0xb6, 0x41, 0x72, 0xe1, 0x9e, +0x6c, 0xad, 0xf3, 0x03, 0x05, 0x83, 0x8a, 0xd9, +0x78, 0x6b, 0xd8, 0x76, 0x14, 0x41, 0xab, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0xb7, 0x4c, 0x94, 0x07, 0x58, 0x31, 0x12, +0xcb, 0x0d, 0xd6, 0xf0, 0xb0, 0x4e, 0xc0, 0xca, +0x5f, 0xfc, 0xd3, 0xcc, 0x09, 0x37, 0x57, 0x7a, +0xff, 0x99, 0x8b, 0x17, 0x4a, 0x05, 0xc4, 0x8f, +0x05, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0x55, 0x3f, 0x7c, 0x96, 0x79, +0xef, 0x4c, 0xe9, 0x19, 0xc9, 0x77, 0xb4, 0x2a, +0xe1, 0xb1, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x61, 0x52, 0x45, 0x80, +0x27, 0x60, 0xec, 0xbc, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0xf1, 0x5b, 0xa9, +0xd2, 0x0a, 0xbe, 0xdf, 0x8e, 0x61, 0x75, 0xa1, +0x19, 0xd8, 0x88, 0x23, 0x44, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x3e, +0x0a, 0xce, 0x58, 0x20, 0x0d, 0x2a, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0x5e, +0xfb, 0x4d, 0x78, 0x3c, 0x31, 0x5e, 0x89, 0xab, +0xcb, 0x08, 0xe6, 0xec, 0x97, 0xc4, 0x0a, 0x5a, +0x24, 0x56, 0x49, 0x06, 0xd4, 0x5e, 0x94, 0x5b, +0xa3, 0x60, 0xe4, 0xcf, 0x9e, 0x16, 0xb5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0x10, 0xb9, 0xaa, 0x63, 0x99, 0x74, 0x89, +0x99, 0xbe, 0x2e, 0x31, 0xd2, 0xe8, 0x55, 0x44, +0x26, 0xeb, 0x99, 0x61, 0xe2, 0xef, 0x49, 0xe0, +0x6f, 0x41, 0x6d, 0x04, 0xab, 0x5d, 0x3d, 0x09, +0xa3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x81, 0xe1, 0x90, 0x7b, 0x3d, +0x5f, 0x5f, 0x8b, 0x8c, 0x5a, 0x3f, 0xbb, 0xf0, +0x1c, 0x94, 0x50, 0x2c, 0x8f, 0x9f, 0x14, 0x26, +0x51, 0xb9, 0x40, 0x43, 0xb2, 0x1c, 0xaa, 0xc7, +0x4e, 0x3e, 0x81, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0x56, 0x36, 0x7b, +0x18, 0xfe, 0xb7, 0x97, 0x80, 0xf5, 0x5f, 0x58, +0x18, 0x3a, 0x9e, 0x9b, 0xbe, 0x54, 0x9b, 0xee, +0x15, 0x77, 0x2b, 0x53, 0x7d, 0x5a, 0x28, 0x19, +0x24, 0x4f, 0xc9, 0x49, 0x10, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0xc5, +0x65, 0x10, 0x69, 0x30, 0xc8, 0x1c, 0xca, 0x8f, +0x90, 0xcb, 0x6d, 0x15, 0x7f, 0x49, 0x55, 0xb5, +0xb6, 0x28, 0x41, 0x59, 0x28, 0xd7, 0x06, 0x31, +0xa5, 0x75, 0x51, 0xcb, 0x55, 0x52, 0xb5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x95, 0xfe, 0x0f, 0x67, 0x8a, 0xca, 0x1e, +0x8a, 0x30, 0x23, 0x30, 0x27, 0xd1, 0x5b, 0xb8, +0x11, 0x6d, 0x12, 0xd5, 0x94, 0x51, 0x0e, 0x97, +0xc0, 0x61, 0x68, 0x37, 0xd6, 0xa5, 0x8c, 0x66, +0xa2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x14, 0x69, 0xf6, 0x21, 0x6d, +0x39, 0x05, 0x06, 0xc3, 0x4c, 0x0f, 0x14, 0x10, +0x17, 0x6c, 0x59, 0x92, 0x5e, 0x88, 0x25, 0x3e, +0xa8, 0x88, 0xd6, 0x2b, 0xaa, 0x9a, 0x0d, 0xe8, +0x44, 0xc4, 0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0x3d, 0x5e, 0x04, +0x94, 0x45, 0x0d, 0x2c, 0x67, 0xd9, 0x18, 0x75, +0x61, 0x48, 0x5b, 0x88, 0x77, 0xcc, 0x74, 0x30, +0xcc, 0xd2, 0xf6, 0x84, 0x62, 0x92, 0x81, 0x73, +0xb2, 0xca, 0x29, 0x49, 0xa8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0xf6, +0x77, 0xc2, 0x29, 0x1f, 0xb2, 0x82, 0x56, 0x49, +0x0e, 0x9d, 0x18, 0x5f, 0xba, 0xfe, 0x3d, 0x37, +0x21, 0xb4, 0x88, 0xc1, 0x27, 0x76, 0xb9, 0x69, +0x43, 0xc9, 0x7a, 0x0c, 0xd3, 0x60, 0x70, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0x59, 0x30, 0xee, 0xa7, 0xb0, 0x49, 0xd6, +0xe6, 0xe4, 0x67, 0xe0, 0xd7, 0x4a, 0x6d, 0x70, +0xa7, 0x6d, 0x19, 0xff, 0x8f, 0xed, 0x0e, 0x0d, +0x6f, 0x58, 0x3d, 0xe8, 0x1e, 0x76, 0x0a, 0x62, +0xd0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0x01, 0xfa, 0x26, 0x1b, 0x79, +0x3b, 0x46, 0x3c, 0x29, 0x58, 0x7e, 0x94, 0x76, +0x8e, 0xb5, 0xbc, 0x37, 0x4a, 0x1f, 0x16, 0xcd, +0x84, 0x97, 0x9c, 0xbf, 0x7e, 0xa7, 0x46, 0x2e, +0xc5, 0xbc, 0x51, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x58, 0x1d, 0x22, +0x63, 0x8d, 0x32, 0x12, 0xcf, 0x06, 0xb7, 0x97, +0x03, 0x1d, 0x87, 0x41, 0x60, 0x22, 0xc2, 0x42, +0x04, 0x23, 0x54, 0x89, 0x10, 0xd5, 0x6a, 0x06, +0x1e, 0xe6, 0x14, 0xa4, 0x3c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0xe7, +0xf6, 0xa2, 0x24, 0x60, 0x4a, 0x26, 0xbf, 0xf6, +0x25, 0x20, 0xb5, 0x37, 0x8d, 0xb6, 0x9f, 0x55, +0x2f, 0x60, 0x0b, 0xfe, 0xb0, 0xf9, 0xf1, 0xce, +0x64, 0x79, 0xc4, 0x6c, 0x07, 0xf6, 0x51, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0xbe, 0xeb, 0x9f, 0x97, 0x16, 0xc6, 0xc3, +0x45, 0xac, 0xd0, 0x34, 0xb9, 0x2f, 0xa6, 0x37, +0x40, 0x3b, 0xe8, 0x3d, 0x6e, 0x16, 0xfa, 0x8e, +0xa6, 0xc8, 0x3f, 0x12, 0xed, 0xd0, 0x10, 0x4d, +0xd4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0xb3, 0x8d, 0x84, 0xac, 0x52, +0x40, 0x13, 0xad, 0xf8, 0x33, 0x21, 0x20, 0x33, +0x34, 0x05, 0xa9, 0xe5, 0xa7, 0x1c, 0x90, 0x62, +0x92, 0xfd, 0x48, 0xcd, 0x82, 0xa3, 0x8c, 0xca, +0x29, 0x35, 0x18, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0x75, 0x05, 0xbc, +0x0a, 0xb8, 0x13, 0xc6, 0x51, 0xaa, 0xc7, 0xa3, +0x96, 0x31, 0x50, 0x04, 0x94, 0x85, 0xb0, 0x3b, +0xb1, 0x47, 0xc8, 0x84, 0xae, 0xb1, 0xb8, 0xbf, +0xac, 0x13, 0x11, 0x0a, 0x80, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0xc8, +0xa1, 0x83, 0xc9, 0x64, 0xd1, 0x46, 0x07, 0xd1, +0xc8, 0xcf, 0x46, 0x8c, 0xe2, 0x1a, 0xfe, 0x7c, +0xd2, 0xd8, 0xd9, 0x2d, 0x04, 0x00, 0x34, 0x7d, +0x59, 0x4f, 0x3f, 0x6d, 0x2e, 0xbf, 0x3b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0xea, 0xfe, 0xbb, 0xc8, 0x05, 0xe7, 0x54, +0x62, 0x96, 0x00, 0xe3, 0xde, 0xf2, 0x5c, 0x3e, +0x80, 0x41, 0x9a, 0x6b, 0x6d, 0x67, 0x59, 0xbf, +0x33, 0x01, 0x26, 0x88, 0xd1, 0x4f, 0x8b, 0x11, +0xf9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0x7c, 0xf1, 0xcd, 0x31, 0xbc, +0xeb, 0xf2, 0x86, 0xac, 0x43, 0x70, 0xb9, 0x1a, +0x54, 0xc8, 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xf2, 0x2b, 0x15, 0x08, +0x46, 0x15, 0xb6, 0xf7, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0x3c, 0xae, 0x7d, +0xfc, 0x40, 0x9d, 0x70, 0xe7, 0x85, 0x29, 0x25, +0xef, 0xca, 0x5d, 0x38, 0xd5, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd5, 0x73, +0xe4, 0xbf, 0x13, 0x00, 0x53, 0x25, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0x43, +0xa9, 0x24, 0x54, 0x31, 0xa9, 0xbd, 0xca, 0x1b, +0xd0, 0xca, 0x35, 0x0a, 0x45, 0xef, 0x71, 0xf1, +0xc6, 0x02, 0x37, 0x8d, 0x26, 0xe5, 0x08, 0x0a, +0x04, 0x2f, 0xd7, 0xc7, 0x15, 0x04, 0x52, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0xee, 0x04, 0x21, 0xf7, 0x70, 0x36, 0x6b, +0x0b, 0x02, 0xcf, 0x4f, 0xfb, 0xf1, 0xe4, 0xc9, +0xf2, 0xee, 0x1f, 0xd9, 0x78, 0xb7, 0xfd, 0xed, +0x1f, 0x90, 0xa5, 0x17, 0xe9, 0x66, 0x70, 0x53, +0xd3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0x29, 0xb3, 0x90, 0x29, 0x8b, +0xab, 0x81, 0x16, 0x35, 0xd6, 0x74, 0x49, 0x05, +0xaa, 0xec, 0x21, 0x0c, 0xf2, 0xec, 0x03, 0x43, +0xec, 0xbb, 0xa2, 0xee, 0x3b, 0x7d, 0x98, 0xb2, +0x33, 0x22, 0x6e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x61, 0xab, 0xcd, +0x12, 0x0a, 0x6a, 0x45, 0x73, 0x0f, 0x9e, 0x30, +0xef, 0x1e, 0x3c, 0xc9, 0xd9, 0xef, 0x18, 0x10, +0x28, 0xe5, 0x48, 0x59, 0xdd, 0xf4, 0x11, 0x53, +0x30, 0xb1, 0x92, 0x17, 0x81, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0x4f, +0x36, 0x80, 0xda, 0x5d, 0x6a, 0x68, 0x94, 0xa7, +0x05, 0xb6, 0x81, 0xad, 0xa1, 0x6e, 0x2b, 0x88, +0xc6, 0x64, 0xa9, 0xf2, 0xa0, 0xc8, 0xcd, 0xdb, +0xfa, 0xd8, 0xac, 0xdf, 0x67, 0x34, 0x55, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0x78, 0x9f, 0xcb, 0xe5, 0x0f, 0x30, 0x68, +0x94, 0x48, 0x6f, 0x10, 0x2c, 0xd8, 0x86, 0x70, +0x10, 0xe3, 0x4c, 0xcb, 0xfa, 0x48, 0xea, 0xf7, +0xa7, 0x5b, 0x48, 0x6a, 0xc5, 0x6f, 0xfa, 0xc0, +0x9b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0x83, 0x55, 0x86, 0x42, 0x9a, +0x58, 0x38, 0xa2, 0xa5, 0xb4, 0xc1, 0xc3, 0x61, +0xc1, 0x7f, 0x82, 0x2a, 0xcb, 0x9b, 0x8c, 0xf5, +0xf4, 0xb8, 0x97, 0x11, 0xe6, 0x4e, 0x3a, 0x34, +0x96, 0x8d, 0x9a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x07, 0xa7, 0x9d, +0x00, 0xf4, 0x11, 0x93, 0x8d, 0x6b, 0xba, 0x39, +0x13, 0xf2, 0x6c, 0x3c, 0x1d, 0xfd, 0x2e, 0x08, +0x7b, 0x45, 0x01, 0x1a, 0x2c, 0x70, 0x8c, 0x65, +0xce, 0x4e, 0xac, 0xef, 0x54, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0xdf, +0x47, 0xad, 0x09, 0x3f, 0x4c, 0xe4, 0x9f, 0x33, +0x63, 0xe5, 0x14, 0x7e, 0xe7, 0xb4, 0x8a, 0xc9, +0x65, 0x96, 0x1e, 0xb3, 0x5b, 0xc7, 0xef, 0x18, +0xb9, 0xc2, 0x96, 0xc6, 0x92, 0xaf, 0x17, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0xfd, 0x1f, 0x3c, 0xb9, 0xd0, 0xe3, 0x6b, +0x84, 0xd0, 0x13, 0x70, 0x54, 0xa2, 0x72, 0xac, +0xe9, 0x5c, 0x94, 0x12, 0xd4, 0x2a, 0x9f, 0x23, +0xea, 0x54, 0x01, 0x35, 0x43, 0x91, 0x82, 0xef, +0x08, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0x3f, 0x9c, 0x4a, 0x11, 0x24, +0x5a, 0xf3, 0x68, 0x03, 0x4f, 0x4a, 0x46, 0xc4, +0x64, 0x76, 0xc9, 0x87, 0xae, 0x5a, 0xc6, 0x1f, +0x1e, 0x1a, 0x32, 0x68, 0x6a, 0x56, 0xd5, 0xe5, +0x24, 0xc6, 0xd6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0x6a, 0x39, 0xd4, +0x3e, 0x6a, 0x24, 0xbb, 0x6f, 0xe1, 0x87, 0x7b, +0x79, 0xe5, 0x0c, 0xad, 0xcf, 0x10, 0x40, 0x68, +0x87, 0x92, 0xde, 0xe4, 0x31, 0x2c, 0xa7, 0xc8, +0xe9, 0x93, 0x41, 0x79, 0xd2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x8a, +0xa0, 0xba, 0x71, 0x55, 0x77, 0xe3, 0x69, 0xfd, +0xda, 0xd5, 0x42, 0x86, 0x04, 0x29, 0x35, 0xeb, +0x20, 0xb7, 0xc1, 0x48, 0x03, 0x5e, 0xb4, 0xc8, +0xec, 0xeb, 0xa7, 0x8e, 0xfc, 0x33, 0x9f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0x93, 0x15, 0xd4, 0x2a, 0x8e, 0x8c, 0x90, +0x42, 0x54, 0x00, 0x9f, 0x38, 0xfe, 0x7a, 0x42, +0xea, 0xf1, 0x4e, 0x85, 0x0a, 0x2e, 0x1b, 0x04, +0xcd, 0xe1, 0x20, 0xdc, 0x15, 0xd0, 0x70, 0x31, +0x9b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0xbe, 0x5f, 0xde, 0x6d, 0xdf, +0xec, 0x8c, 0x87, 0xef, 0x74, 0x7f, 0x5e, 0x8d, +0x1e, 0x02, 0xc0, 0x79, 0x67, 0x04, 0x8a, 0x52, +0x6b, 0x8e, 0xd3, 0x0f, 0xf9, 0xc4, 0xe3, 0x4b, +0x80, 0x00, 0x35, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0x1c, 0x40, 0x01, +0x0b, 0xcb, 0x76, 0xfa, 0xbe, 0x11, 0x0c, 0x8b, +0x61, 0xc1, 0x92, 0x75, 0xa0, 0xa8, 0x98, 0x98, +0xa4, 0x51, 0x5e, 0xb3, 0x2d, 0x6e, 0xa7, 0x23, +0x8a, 0x68, 0x8a, 0xfb, 0xe5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0xaa, +0x89, 0x1a, 0x79, 0x21, 0x12, 0x53, 0x93, 0x91, +0xae, 0xbe, 0x70, 0x85, 0xf7, 0xa2, 0xb9, 0x81, +0x49, 0xad, 0xc4, 0xae, 0x58, 0xa0, 0x69, 0xc5, +0xfc, 0xcd, 0x3e, 0x84, 0x0b, 0xe5, 0x7b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0xc3, 0x1f, 0x63, 0x2c, 0x61, 0xac, 0x8b, +0x10, 0x06, 0x8f, 0xe8, 0xce, 0x5c, 0x6c, 0x4b, +0xf9, 0x54, 0x65, 0x0a, 0x2b, 0x0a, 0x39, 0x55, +0x19, 0x60, 0xdb, 0xc2, 0xa4, 0xc8, 0x39, 0xa7, +0x66, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0xa5, 0x98, 0x45, 0x92, 0xb0, +0xcb, 0xdf, 0xd6, 0x07, 0x53, 0x11, 0x00, 0xc6, +0x19, 0xf5, 0xac, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x99, 0xc9, 0x41, 0x25, +0x12, 0xab, 0xe9, 0xba, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0xb2, 0xdf, 0x3d, +0xb6, 0x90, 0x31, 0xc5, 0xc3, 0x46, 0xe9, 0xaa, +0xa0, 0x3e, 0x0d, 0xbc, 0xf5, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9d, 0xaa, +0xa8, 0xf3, 0x6b, 0x3a, 0x10, 0x11, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0x4d, +0x1f, 0xeb, 0x79, 0xa0, 0xf1, 0x14, 0x7d, 0x57, +0x88, 0xd0, 0x0d, 0xa2, 0x06, 0x27, 0x20, 0xde, +0xc1, 0x8f, 0x5d, 0x13, 0xb8, 0x2c, 0xad, 0x24, +0x19, 0x1d, 0x1e, 0x65, 0x67, 0xe3, 0xc9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0x00, 0x7d, 0xfd, 0xd3, 0xec, 0x47, 0x7e, +0x18, 0x72, 0x6e, 0x2e, 0xad, 0x06, 0x6a, 0x4a, +0x33, 0x87, 0x62, 0xf6, 0x66, 0x9e, 0x5f, 0x33, +0xe1, 0x96, 0xe7, 0xbc, 0xf3, 0x94, 0x40, 0xdf, +0x89, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0xd2, 0xa6, 0x78, 0x2e, 0x4d, +0x6d, 0x2a, 0xa0, 0x7d, 0x1a, 0x68, 0x68, 0x31, +0x8e, 0x0f, 0x41, 0x97, 0x68, 0x84, 0x71, 0x09, +0xe8, 0xbb, 0x13, 0x1a, 0x92, 0x7d, 0xfb, 0x2e, +0x88, 0x1a, 0x67, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x8c, 0x2d, 0x97, +0xc9, 0x57, 0x6b, 0xb9, 0xde, 0xf5, 0x8e, 0xa2, +0x13, 0xb6, 0x9e, 0x2c, 0x40, 0x9e, 0xa9, 0x70, +0xb6, 0xd5, 0xb9, 0x9c, 0x99, 0x91, 0x87, 0x96, +0x3b, 0xf5, 0xdc, 0xb5, 0xb6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0x3b, +0x54, 0x6e, 0x01, 0x83, 0x40, 0xf6, 0x28, 0x86, +0x4d, 0x34, 0xf3, 0x09, 0x4a, 0x17, 0x22, 0x02, +0xb1, 0xb2, 0x8c, 0xc4, 0xa1, 0xa7, 0x84, 0xd0, +0xf1, 0x87, 0x7b, 0xa0, 0x90, 0xa1, 0x4f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0x6c, 0xd8, 0x1f, 0x62, 0x19, 0x84, 0x54, +0xb4, 0xc0, 0x0d, 0x46, 0x01, 0xf1, 0xbe, 0x14, +0xcc, 0xd6, 0x63, 0x5a, 0x07, 0xde, 0x31, 0x9c, +0x42, 0x5c, 0x90, 0x25, 0x70, 0x27, 0x4b, 0x89, +0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0xc0, 0x69, 0xee, 0xc2, 0xdc, +0xa4, 0x90, 0x63, 0x8c, 0xf4, 0xa5, 0x77, 0x60, +0x26, 0xf3, 0x82, 0xa9, 0x17, 0x57, 0x2d, 0x9f, +0xd5, 0x4a, 0xe6, 0x04, 0x31, 0xd6, 0x9f, 0xa9, +0x71, 0x0f, 0x11, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0x75, 0x4f, 0xa7, +0x1b, 0x8b, 0x32, 0xba, 0x84, 0x93, 0x39, 0xfe, +0x31, 0xa0, 0x6e, 0x05, 0xb7, 0x36, 0x03, 0x38, +0x60, 0x0a, 0xe2, 0x39, 0x53, 0xcf, 0x50, 0x4b, +0xfc, 0xe8, 0x54, 0x96, 0x89, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0x7c, +0x4a, 0x87, 0x9f, 0xba, 0x95, 0x08, 0x4b, 0x94, +0xcf, 0x0b, 0x45, 0xaa, 0x49, 0x6b, 0x9c, 0xd1, +0x7a, 0x07, 0xb3, 0x1d, 0x65, 0xc1, 0x4b, 0x85, +0xf8, 0xd2, 0xa7, 0xa1, 0x65, 0x5f, 0x5b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0xa8, 0xa3, 0xa5, 0x44, 0x2b, 0x7e, 0x7a, +0xd9, 0xd3, 0x53, 0x30, 0x44, 0xcd, 0xdd, 0x22, +0x56, 0x69, 0x92, 0x5c, 0xf3, 0x85, 0x5d, 0xf4, +0x8d, 0x5a, 0xfe, 0xea, 0xf8, 0x40, 0x2a, 0x7d, +0xc0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0xe6, 0xfd, 0xa4, 0xb8, 0x24, +0xcd, 0x90, 0x85, 0x0c, 0x49, 0xd9, 0x44, 0x84, +0xe6, 0xa0, 0x86, 0x05, 0x91, 0x01, 0xb7, 0xad, +0x00, 0xd4, 0xa5, 0x26, 0x0d, 0x23, 0x21, 0x14, +0x23, 0xdd, 0x1c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0x74, 0x8e, 0x15, +0x40, 0x32, 0xf0, 0x0e, 0x36, 0x55, 0xf1, 0x69, +0xe8, 0x0d, 0x06, 0xd0, 0x3c, 0x0d, 0x89, 0x03, +0xec, 0xc8, 0x8a, 0x0d, 0xc7, 0xf9, 0x12, 0xe1, +0x52, 0x25, 0x64, 0x9c, 0xb3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x3a, +0xe8, 0xf1, 0x37, 0x22, 0x9d, 0xda, 0xcf, 0x62, +0x0e, 0x2d, 0xa5, 0x6c, 0x49, 0x3e, 0xaf, 0xaf, +0x15, 0x5a, 0xb2, 0xa7, 0x8f, 0x00, 0x1d, 0xe6, +0x08, 0xb3, 0xf4, 0xbe, 0x06, 0xc5, 0x8d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0xd7, 0x1e, 0xea, 0xe5, 0xec, 0x04, 0x81, +0x59, 0x2c, 0x41, 0x3a, 0x71, 0x54, 0x55, 0x65, +0x54, 0x6a, 0xdb, 0x29, 0xa4, 0xfd, 0x6b, 0x04, +0x83, 0xd0, 0xc0, 0x43, 0x05, 0xa1, 0xfe, 0x3c, +0x7c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0x8d, 0x12, 0xdc, 0xf8, 0x4d, +0x05, 0xb7, 0x16, 0xa6, 0x43, 0x3d, 0x1d, 0xf9, +0x9d, 0xa5, 0xe7, 0xf7, 0x2a, 0xe0, 0xc6, 0x1b, +0x04, 0x7f, 0x8d, 0x36, 0x53, 0xe6, 0x64, 0x7d, +0x9b, 0x46, 0xda, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0x25, 0xd5, 0x98, +0xa2, 0xe5, 0x27, 0xac, 0x45, 0x8d, 0x4d, 0x6f, +0xe0, 0xa8, 0x29, 0xbc, 0x69, 0xb4, 0x2e, 0xc8, +0xb0, 0x9c, 0x26, 0x43, 0x52, 0xbb, 0x73, 0x10, +0xfb, 0xcb, 0xc9, 0x5a, 0x9e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0x8b, +0x34, 0x20, 0x36, 0x77, 0x55, 0x04, 0x42, 0xe4, +0x03, 0x68, 0x1e, 0xf9, 0x4b, 0xcc, 0xdb, 0xbf, +0x57, 0x12, 0xc5, 0x1d, 0x4e, 0x64, 0x26, 0x92, +0xb5, 0xcd, 0x0e, 0xc8, 0xb8, 0x14, 0xf1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0x8f, 0x6d, 0xe9, 0x98, 0xb9, 0xda, 0x90, +0x57, 0x00, 0x21, 0xe9, 0x42, 0x4b, 0xdc, 0x57, +0x13, 0xbc, 0xad, 0xc5, 0x80, 0x60, 0x7c, 0xe2, +0xfd, 0x1a, 0xf4, 0xf7, 0x0e, 0x6c, 0x46, 0xb7, +0x69, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x22, 0xc0, 0xfa, 0x2d, 0xb7, +0xfb, 0x70, 0x98, 0x53, 0x56, 0xde, 0x4e, 0x5b, +0x84, 0x31, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x8c, 0x06, 0xaf, 0x8d, +0x49, 0xdd, 0x82, 0xe2, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0x4f, 0xf0, 0xd2, +0xca, 0xd0, 0x58, 0x00, 0x9a, 0x40, 0xaf, 0x41, +0xd7, 0x5e, 0x4f, 0x88, 0xce, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0xbf, +0x80, 0x46, 0x70, 0x9b, 0x67, 0x04, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0x52, +0x08, 0xf7, 0x8c, 0x25, 0xca, 0xb2, 0xf6, 0xfc, +0xf8, 0x7b, 0x71, 0xb3, 0x27, 0xce, 0x27, 0x94, +0x7a, 0xb0, 0x76, 0x78, 0xeb, 0xb7, 0xa6, 0x6c, +0x0c, 0x59, 0xb8, 0x5c, 0xb2, 0x9a, 0x42, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0x00, 0xe2, 0x64, 0xa8, 0x51, 0xb9, 0x31, +0x19, 0x10, 0x8f, 0x60, 0xcd, 0xce, 0x16, 0x62, +0xc0, 0xba, 0x41, 0xd4, 0x3f, 0xfa, 0x6b, 0xf8, +0xd4, 0x1a, 0x57, 0x50, 0xa9, 0x38, 0xb9, 0x00, +0x40, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0x9a, 0xb0, 0x8d, 0x7a, 0xd3, +0x4a, 0x84, 0xde, 0x38, 0x02, 0x2a, 0x29, 0xa0, +0xfd, 0x16, 0x2c, 0x2c, 0xca, 0xae, 0xc9, 0x03, +0x5a, 0x03, 0xad, 0x0e, 0x55, 0xe7, 0x84, 0x54, +0x62, 0xc8, 0xe4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0xd2, 0xf7, 0x76, +0xfa, 0x0a, 0xe6, 0xc5, 0x41, 0x58, 0x9c, 0xd0, +0x6e, 0x2a, 0x61, 0x29, 0x65, 0xa9, 0x88, 0x1d, +0x44, 0x90, 0x00, 0x63, 0x0c, 0x1e, 0xb2, 0x68, +0x85, 0x1b, 0xa0, 0x73, 0x22, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0x43, +0x9e, 0x5c, 0x5a, 0x8c, 0xed, 0x70, 0x67, 0xba, +0xc7, 0x1e, 0xfe, 0x87, 0x12, 0x27, 0x02, 0x88, +0xab, 0xb1, 0xc4, 0x03, 0xef, 0x4b, 0xae, 0x34, +0xae, 0x39, 0xf3, 0x9b, 0x98, 0x81, 0x32, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0x59, 0x82, 0x22, 0x62, 0xbe, 0xc2, 0x3b, +0xc5, 0x56, 0x7c, 0xf4, 0xaa, 0x70, 0x70, 0xba, +0x40, 0xb8, 0x9c, 0x85, 0xa2, 0xab, 0xa6, 0x2f, +0x1a, 0x88, 0x83, 0x27, 0x48, 0x62, 0x57, 0x5d, +0xa6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0xaa, 0x07, 0x6d, 0xa7, 0x94, +0xc2, 0xde, 0x13, 0x87, 0x2f, 0x75, 0x0d, 0xe5, +0xcb, 0x2d, 0xdc, 0xa9, 0x26, 0x20, 0x3a, 0x29, +0xec, 0x48, 0x48, 0xe5, 0xaa, 0x9d, 0xaa, 0x4a, +0x52, 0xd9, 0xa5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0xaa, 0x69, 0x57, +0xee, 0xb0, 0x2c, 0x72, 0xa9, 0xaf, 0x98, 0x8f, +0x56, 0x82, 0x2e, 0x61, 0xfc, 0xc1, 0x16, 0xdb, +0x54, 0x3e, 0x5a, 0xce, 0xb6, 0x36, 0xf2, 0x4c, +0x5c, 0xe6, 0x57, 0x7f, 0x78, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0x42, +0xdb, 0xda, 0x6c, 0x56, 0x18, 0x1b, 0x76, 0xb5, +0x92, 0x1e, 0xeb, 0x46, 0xc9, 0xbe, 0xb2, 0x85, +0x75, 0xe4, 0x43, 0x2d, 0x02, 0xf3, 0x62, 0x4b, +0x6e, 0xcd, 0x5b, 0x0a, 0x92, 0x3e, 0x96, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0x5d, 0xa2, 0x0a, 0x02, 0x55, 0xd4, 0xf0, +0xcd, 0xe6, 0x80, 0xfa, 0xef, 0xf4, 0x7c, 0xaa, +0xca, 0x57, 0x84, 0x27, 0xf9, 0x74, 0x83, 0x08, +0x89, 0x71, 0xd7, 0x83, 0x6a, 0xd9, 0x07, 0x8d, +0xc3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0x69, 0x48, 0x9b, 0x78, 0xc5, +0x6b, 0xf2, 0x18, 0x52, 0xa9, 0x0c, 0xd3, 0x87, +0x98, 0xd5, 0xfc, 0x11, 0x80, 0x2c, 0x02, 0xc5, +0x37, 0x70, 0x5c, 0x54, 0xdf, 0xaf, 0xf0, 0x1b, +0x60, 0x27, 0x78, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0x01, 0x4d, 0xdd, +0x8a, 0xa4, 0xdd, 0xec, 0x31, 0x54, 0xba, 0xc1, +0x43, 0x25, 0x81, 0xb2, 0x3f, 0x5e, 0x4c, 0xb6, +0xe4, 0x1e, 0x25, 0xaa, 0xad, 0x2a, 0x42, 0xeb, +0x89, 0xcd, 0x83, 0xc9, 0x1e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0xfc, +0x61, 0x19, 0xb2, 0x2e, 0xde, 0xb3, 0x0a, 0xd0, +0xf8, 0xb6, 0xe2, 0x0b, 0xf1, 0x9f, 0x75, 0xe7, +0xe5, 0xd0, 0xa7, 0x0b, 0xb9, 0x10, 0x88, 0x45, +0x50, 0xea, 0x3a, 0xca, 0x8e, 0xf5, 0x8a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x1b, 0xaf, 0x92, 0x36, 0x0f, 0x68, 0x5f, +0x8a, 0x7f, 0x1c, 0x24, 0xf8, 0xba, 0xf3, 0x4d, +0xcf, 0x90, 0xd7, 0xb3, 0xde, 0xc5, 0xc5, 0xa9, +0x2c, 0x55, 0x0f, 0x11, 0x13, 0x3f, 0x50, 0x07, +0x04, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0x73, 0xe8, 0x4e, 0xb7, 0x66, +0x7d, 0xfe, 0x73, 0x27, 0x1b, 0xd2, 0x17, 0xeb, +0xc2, 0xdf, 0x1e, 0xca, 0x9e, 0x6f, 0xae, 0x0c, +0x1a, 0xae, 0xad, 0xde, 0x2d, 0x1c, 0xf4, 0x3d, +0x4c, 0xf2, 0x02, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0xd1, 0xd4, 0x14, +0xa3, 0xdd, 0x64, 0xe1, 0x47, 0xe8, 0x94, 0x4c, +0x23, 0x7f, 0x3f, 0x6c, 0x37, 0x84, 0x39, 0x69, +0x7a, 0x9c, 0xc9, 0x4e, 0xf8, 0xad, 0x51, 0x59, +0x21, 0x06, 0x27, 0x42, 0x53, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0xa5, +0xd5, 0x18, 0x60, 0x71, 0x99, 0xd7, 0x3a, 0x30, +0x4c, 0x33, 0x49, 0x2e, 0x6c, 0x3d, 0xe7, 0x7d, +0x60, 0xab, 0x1b, 0x99, 0x51, 0xda, 0xed, 0xf6, +0x8b, 0x5f, 0xd4, 0x4f, 0x3d, 0x04, 0x68, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0xed, 0x4c, 0xe0, 0x53, 0x03, 0x49, 0x36, +0x19, 0x73, 0xe9, 0xd6, 0xec, 0x6f, 0xb3, 0xfc, +0xbf, 0x10, 0x1d, 0x54, 0x92, 0xbd, 0x02, 0xa6, +0xf2, 0x27, 0x66, 0x28, 0x1e, 0xf4, 0x10, 0xd8, +0x06, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0x5f, 0x5e, 0x90, 0x26, 0x47, +0x71, 0x2f, 0xc9, 0x57, 0x12, 0xa1, 0x48, 0x62, +0xd4, 0xb8, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x48, 0x35, 0xeb, 0x7d, +0xa1, 0xff, 0xfe, 0xba, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x86, 0x4f, 0xa6, +0xec, 0xdc, 0x90, 0x01, 0x75, 0x6e, 0xd8, 0xca, +0xf4, 0x5f, 0xae, 0x5d, 0xe2, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0xd7, +0xf4, 0x7d, 0x05, 0xf9, 0xb6, 0x2c, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0xb4, +0x1a, 0x78, 0x87, 0xf8, 0xdb, 0xf5, 0x0c, 0x1d, +0x0f, 0x7c, 0x26, 0xd5, 0x70, 0x72, 0x75, 0xbb, +0x83, 0x2a, 0x06, 0xc9, 0x2d, 0xf5, 0x84, 0xfb, +0x51, 0x8c, 0xcf, 0x53, 0xd5, 0xcf, 0xcd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0x2e, 0x17, 0xf1, 0x30, 0xea, 0xb3, 0x87, +0x1e, 0x18, 0x0b, 0xc4, 0x34, 0xce, 0x1f, 0x3f, +0xc1, 0xe7, 0xd1, 0x01, 0xbb, 0x39, 0x10, 0x12, +0x8a, 0xb8, 0x13, 0xe1, 0xfe, 0xa1, 0x00, 0xd7, +0x30, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0x0b, 0x59, 0x6e, 0xa0, 0xc2, +0x20, 0xfb, 0x6e, 0x80, 0x56, 0x8f, 0x9d, 0x83, +0xfb, 0x02, 0x9d, 0x94, 0x4b, 0x8b, 0xb0, 0x35, +0xcb, 0x3f, 0x74, 0x28, 0xe3, 0x8f, 0x27, 0x63, +0x40, 0xbb, 0xe0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0x6b, 0x81, 0xcc, +0xa0, 0xfd, 0x97, 0x9d, 0xbc, 0x76, 0x03, 0xe5, +0xab, 0x81, 0x55, 0xfa, 0x1c, 0x76, 0xbc, 0x9f, +0xc3, 0xe9, 0xcb, 0xf8, 0x49, 0xce, 0x43, 0x50, +0xdd, 0xa1, 0xc9, 0xae, 0x07, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0x3e, +0xa6, 0x7f, 0xd4, 0xa2, 0xf1, 0xbc, 0xc1, 0xc7, +0xfb, 0x06, 0xbc, 0x0b, 0x74, 0x4f, 0xa5, 0xde, +0x59, 0x58, 0x84, 0x89, 0x85, 0x3f, 0x9e, 0x30, +0x14, 0x12, 0xc7, 0x72, 0x4a, 0x4b, 0x62, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0xa2, 0x69, 0xc3, 0xe3, 0xf3, 0xe6, 0xb8, +0xfd, 0x23, 0x97, 0xf0, 0xf2, 0x55, 0x20, 0x32, +0xec, 0x43, 0x5c, 0xd6, 0xdf, 0x9e, 0x27, 0x59, +0x86, 0x55, 0xe7, 0x43, 0xf3, 0xe5, 0xbc, 0x94, +0x0d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0xa4, 0x6c, 0xe2, 0x98, 0x15, +0xaa, 0x52, 0x78, 0x09, 0xe9, 0x5a, 0x47, 0xfb, +0xf3, 0x60, 0x28, 0x75, 0x98, 0x50, 0x89, 0x24, +0x56, 0x98, 0xcb, 0x87, 0xac, 0xfc, 0x49, 0xbd, +0x35, 0xde, 0x2d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x9c, 0xdf, 0xd6, +0x77, 0x65, 0x51, 0xed, 0x92, 0x28, 0x65, 0xff, +0x79, 0x1b, 0x1b, 0xa4, 0x75, 0xd2, 0x6f, 0xbf, +0xd0, 0xe4, 0xe9, 0xcf, 0xe7, 0x9f, 0x13, 0x73, +0xa8, 0xef, 0x3c, 0x6c, 0x30, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x38, +0x6f, 0xb4, 0x29, 0xb7, 0xd8, 0xa3, 0xe1, 0x1b, +0x23, 0xa6, 0xe5, 0x4c, 0x81, 0x9e, 0xf7, 0x20, +0x63, 0xbc, 0x26, 0x30, 0xd9, 0xae, 0x7d, 0x91, +0xe8, 0x08, 0x5e, 0x30, 0x8f, 0xf5, 0xe4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0x00, 0x15, 0xb4, 0x33, 0x64, 0x28, 0xf2, +0xfb, 0xe7, 0x86, 0x98, 0xc4, 0x38, 0x0c, 0xba, +0x59, 0x12, 0x35, 0x1e, 0xe2, 0x54, 0xcf, 0x70, +0x7e, 0xfc, 0xb6, 0x64, 0xbe, 0x8c, 0xf4, 0xb1, +0xba, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0x8d, 0xe2, 0x30, 0x21, 0x5d, +0xef, 0x0d, 0x74, 0x00, 0x06, 0x78, 0x73, 0xf2, +0x2d, 0x5d, 0xf7, 0xb9, 0xc0, 0xa4, 0xe5, 0x70, +0x6d, 0xce, 0xc9, 0x11, 0x6b, 0x24, 0x4a, 0xb2, +0x20, 0x94, 0x7c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0x51, 0xa5, 0xb5, +0x8a, 0x85, 0x0b, 0xe3, 0x6b, 0x9e, 0xd0, 0xde, +0xe5, 0xad, 0xdf, 0x0e, 0x3f, 0xb4, 0x6f, 0x91, +0x18, 0xc3, 0x00, 0xde, 0x23, 0xf8, 0x82, 0xb3, +0xf2, 0xe9, 0x11, 0xdf, 0x06, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0xd8, +0x09, 0xd5, 0xb7, 0x52, 0x2b, 0xdc, 0x0b, 0x68, +0xfb, 0x92, 0x02, 0x9f, 0xdc, 0x22, 0x2a, 0x23, +0x0b, 0x66, 0x39, 0x51, 0x40, 0xe4, 0xd3, 0xf6, +0x0d, 0xbc, 0x87, 0x7e, 0xd8, 0x39, 0xa8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x64, 0xa8, 0x11, 0x73, 0x29, 0xd2, 0x7a, +0xff, 0x3f, 0xf5, 0xef, 0x32, 0x59, 0xb3, 0xda, +0x42, 0xd3, 0xfd, 0x47, 0xf7, 0xa9, 0xc8, 0x3b, +0x23, 0x02, 0xd8, 0x10, 0x06, 0xbe, 0x7c, 0x67, +0xa1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0xaa, 0x48, 0x04, 0x5c, 0xf2, +0x0f, 0xea, 0x07, 0x27, 0x0c, 0xe2, 0x28, 0xed, +0x63, 0xca, 0x98, 0x1d, 0x0f, 0xf4, 0xce, 0x45, +0xe9, 0x25, 0x27, 0xa0, 0x17, 0x26, 0x1e, 0xb2, +0x75, 0x78, 0x27, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0xb8, 0x59, 0x3e, +0x55, 0x11, 0x65, 0xb2, 0xcf, 0x6f, 0xbf, 0x8f, +0x77, 0xb5, 0xe5, 0x8f, 0x6e, 0x31, 0x48, 0x7e, +0xfa, 0x88, 0x05, 0x40, 0xe0, 0xec, 0x73, 0x48, +0x1d, 0x02, 0xe8, 0x2c, 0x1a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0xcd, +0x26, 0x02, 0xf8, 0xbf, 0x51, 0xca, 0x0c, 0xc2, +0xbe, 0x1d, 0x64, 0xce, 0xcc, 0xf4, 0x07, 0x7b, +0xac, 0x92, 0x57, 0x14, 0xaa, 0xf4, 0xbc, 0xc3, +0x63, 0x50, 0x59, 0x76, 0x30, 0x14, 0x73, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0x7c, 0xf5, 0xff, 0x55, 0x53, 0x9e, 0xbe, +0x8c, 0xc3, 0x63, 0x4c, 0x3c, 0x7d, 0x4d, 0xc7, +0x64, 0x20, 0x7f, 0x97, 0xd0, 0x92, 0x39, 0x68, +0xbe, 0x22, 0xa3, 0xae, 0x8b, 0x2c, 0x8a, 0x71, +0xd2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0xf4, 0x51, 0x19, 0x18, 0x85, +0x83, 0x64, 0x11, 0x71, 0x6b, 0x26, 0x11, 0xe4, +0xa4, 0x42, 0xb6, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x9f, 0x52, 0x1c, 0xa4, +0x50, 0x7b, 0xd1, 0xac, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0xd0, 0x39, 0x74, +0xb0, 0xe6, 0x92, 0x38, 0x74, 0x51, 0x2d, 0xb0, +0x9f, 0x4b, 0x0e, 0xa4, 0x38, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x28, +0x4b, 0x20, 0xcf, 0x0b, 0x24, 0xc5, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0xb1, +0x9a, 0x89, 0xd9, 0xac, 0xf7, 0xa5, 0x2e, 0x50, +0x63, 0x05, 0x1f, 0x69, 0x9d, 0x8f, 0xf9, 0x36, +0x32, 0xbd, 0xdd, 0x94, 0x02, 0xc3, 0x6a, 0xb3, +0x04, 0xf3, 0xc2, 0x74, 0x6c, 0x59, 0xd2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x6f, 0x62, 0x74, 0xde, 0xc1, 0x55, 0xb4, +0x07, 0x2b, 0x0c, 0xa9, 0x63, 0xb7, 0x37, 0xe7, +0x0c, 0x5b, 0xf0, 0xd9, 0x76, 0xb4, 0x71, 0x7b, +0xf0, 0x7d, 0x59, 0xea, 0x34, 0xb3, 0x23, 0x02, +0x7c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0x68, 0x71, 0xcd, 0x43, 0x92, +0x80, 0x9c, 0xee, 0x0c, 0x75, 0x9d, 0x0f, 0xad, +0x00, 0x1d, 0xf3, 0x38, 0x56, 0x7c, 0xb5, 0xa0, +0xa3, 0xbd, 0x9a, 0xe4, 0x1d, 0x2e, 0x74, 0x18, +0xbf, 0x8d, 0x55, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0x3e, 0xab, 0xd2, +0x6e, 0xc1, 0x80, 0x24, 0x59, 0x57, 0xa1, 0x23, +0x10, 0xfd, 0x37, 0x4a, 0x93, 0x01, 0xca, 0x7f, +0x74, 0x33, 0xa4, 0x16, 0xa2, 0x49, 0x0b, 0xbe, +0xda, 0x81, 0x92, 0x20, 0xaf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0xe0, +0x3f, 0x2d, 0x44, 0xcf, 0x8b, 0xaa, 0x32, 0xb7, +0x07, 0x55, 0x28, 0xcb, 0x79, 0xe2, 0x2a, 0xc6, +0x1a, 0x0d, 0x31, 0xe9, 0x2a, 0x61, 0x03, 0xb3, +0x4a, 0xa5, 0x95, 0xe4, 0x19, 0xde, 0x7a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0xd9, 0x0c, 0x4b, 0xb6, 0x17, 0xee, 0xd3, +0x57, 0xb4, 0xcc, 0x1e, 0xf5, 0xaa, 0x68, 0xb2, +0x48, 0xe8, 0xff, 0x1b, 0x4b, 0x09, 0x16, 0xe8, +0x3d, 0xa1, 0x6f, 0x64, 0xc8, 0xd1, 0x5b, 0xb8, +0x71, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0x79, 0x73, 0xbf, 0x30, 0x0f, +0xbd, 0x1e, 0xfd, 0x49, 0xae, 0x0a, 0x21, 0xc8, +0x8c, 0x50, 0x12, 0x3c, 0x7e, 0xbf, 0x29, 0x38, +0xd5, 0x34, 0x68, 0xa5, 0x61, 0xc6, 0x06, 0x9b, +0x94, 0x17, 0x7c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0x5e, 0xa2, 0x07, +0xfe, 0x13, 0x9f, 0x3f, 0xe9, 0xe0, 0xaa, 0x3b, +0xbf, 0x5b, 0x79, 0x2e, 0x3d, 0xcb, 0x6d, 0xe8, +0x46, 0x32, 0xb6, 0xa5, 0xe7, 0x3c, 0x92, 0x01, +0x5e, 0xe5, 0x22, 0x92, 0x00, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0x3a, +0xad, 0x76, 0x16, 0xe5, 0xde, 0xc0, 0x82, 0x05, +0x1e, 0x2d, 0xa5, 0x20, 0x05, 0xb1, 0x78, 0xb5, +0xec, 0x88, 0x0a, 0x82, 0x9b, 0x5f, 0x53, 0xc3, +0x6f, 0xa1, 0xac, 0xa1, 0xec, 0x42, 0xe4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0x40, 0x5f, 0xb7, 0x18, 0xc3, 0xdb, 0x59, +0xa2, 0x06, 0x9b, 0xd4, 0x5d, 0x58, 0x31, 0x26, +0xdc, 0x96, 0x51, 0xac, 0x4e, 0x5d, 0x2e, 0xfa, +0x4e, 0xf8, 0x03, 0x84, 0x36, 0x9c, 0xad, 0x50, +0x52, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0x56, 0xa5, 0xdb, 0xd5, 0x3c, +0xcd, 0xea, 0x30, 0x60, 0x9f, 0x61, 0x7d, 0x92, +0x54, 0x7d, 0x46, 0x84, 0x31, 0x9b, 0x42, 0xe4, +0x11, 0x25, 0x69, 0x66, 0x84, 0x8b, 0xbb, 0x69, +0x00, 0x35, 0x76, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x68, 0xfc, 0x5e, +0x63, 0x4a, 0x7a, 0xfb, 0x68, 0x02, 0xa8, 0x96, +0x14, 0xcb, 0xb7, 0xe8, 0x0e, 0x7f, 0xf7, 0x31, +0xa3, 0xc2, 0x11, 0x62, 0x75, 0x07, 0x39, 0x68, +0x74, 0xba, 0xe9, 0x31, 0x92, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0x95, +0x5c, 0x83, 0x36, 0xd4, 0xdf, 0x54, 0x1b, 0x54, +0x63, 0x00, 0x15, 0xd8, 0x43, 0xaa, 0x5f, 0x9d, +0x7f, 0x2c, 0xcd, 0xf5, 0x9b, 0x0c, 0x11, 0xc3, +0xe2, 0xc0, 0x84, 0x1c, 0x4d, 0x3a, 0x28, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0x9c, 0x04, 0x49, 0x8d, 0x74, 0xcf, 0x75, +0xb6, 0xc2, 0x36, 0x07, 0xfd, 0xf2, 0x81, 0x1a, +0x58, 0x4b, 0xe8, 0x6c, 0x8c, 0x23, 0x9f, 0xef, +0x2e, 0xd9, 0xdd, 0xe1, 0xc6, 0xce, 0x1c, 0x7a, +0xde, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0xc9, 0x03, 0xc6, 0xe5, 0xfb, +0xca, 0xf9, 0x47, 0x86, 0x8c, 0x8b, 0xd4, 0x2d, +0x13, 0x79, 0xda, 0x3d, 0x4f, 0xef, 0x26, 0x67, +0xac, 0x79, 0x37, 0x99, 0x6c, 0x7a, 0x8f, 0xa1, +0x1d, 0x8c, 0x3d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0xda, 0xae, 0x3c, +0x48, 0x16, 0x8b, 0x74, 0x65, 0x6a, 0x85, 0x62, +0xe4, 0xd1, 0x7c, 0x17, 0x2d, 0x77, 0x30, 0x88, +0x3e, 0xbf, 0x82, 0x0c, 0x44, 0xab, 0xff, 0x0b, +0x5b, 0x2f, 0x2c, 0xed, 0xe5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0xea, +0x40, 0x99, 0x1d, 0xb0, 0x18, 0x58, 0xd8, 0xb1, +0x62, 0xca, 0x99, 0x6e, 0x5a, 0x40, 0xe6, 0x65, +0x6a, 0x70, 0x6d, 0xc2, 0xaa, 0x66, 0x4f, 0x52, +0x7d, 0x3d, 0xa3, 0x4b, 0xf4, 0xa9, 0x50, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0x8b, 0xf6, 0xfd, 0xab, 0xd6, 0xa5, 0xce, +0x94, 0x82, 0x71, 0x51, 0xfd, 0x4f, 0x0b, 0x49, +0xd0, 0x16, 0x6b, 0x0b, 0x17, 0xaa, 0xf2, 0x25, +0x54, 0xb4, 0x87, 0x93, 0xd4, 0x7c, 0x5b, 0xb5, +0x15, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x58, 0x29, 0x68, 0x2e, 0xd6, +0x2a, 0x94, 0xa6, 0x7d, 0xda, 0x99, 0x3d, 0xe5, +0xe8, 0x3f, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x91, 0x40, 0x01, 0x2b, +0x51, 0x18, 0xf9, 0x43, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0x5c, 0xc8, 0x14, +0x20, 0x27, 0xcc, 0xdf, 0x19, 0x13, 0x5f, 0xd6, +0xc6, 0x75, 0xec, 0xa9, 0x73, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe4, 0x5d, +0x59, 0x41, 0xeb, 0x0f, 0xdc, 0xfe, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0x8e, +0x1c, 0x13, 0x0a, 0xb7, 0x08, 0x27, 0x7c, 0x88, +0x1f, 0xcb, 0x44, 0xfd, 0xb0, 0x2f, 0x67, 0x05, +0xf5, 0x28, 0xbd, 0x1f, 0xfc, 0x42, 0x5c, 0xac, +0x7d, 0x98, 0x81, 0x69, 0x05, 0xf3, 0xd8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0x85, 0x46, 0x81, 0x86, 0x40, 0x93, 0x94, +0x9d, 0xe9, 0x81, 0x36, 0x6f, 0xaf, 0x77, 0xde, +0x86, 0x25, 0x20, 0xb9, 0xa2, 0xa5, 0x26, 0xe2, +0x2e, 0xe1, 0x9c, 0xbb, 0x0c, 0x24, 0x28, 0x72, +0xc0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0xbe, 0x31, 0x16, 0x08, 0xf0, +0x80, 0x41, 0x6d, 0x41, 0xb5, 0xa3, 0x2c, 0x30, +0x51, 0xaf, 0x9d, 0xad, 0x5f, 0x0d, 0xa4, 0xdc, +0x3a, 0x01, 0x14, 0xce, 0xa0, 0x76, 0xfe, 0x96, +0xb8, 0x23, 0x30, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0x8c, 0x4c, 0x98, +0x3e, 0x83, 0x58, 0xad, 0x46, 0x3e, 0x42, 0xa2, +0x85, 0x2d, 0x95, 0x9e, 0x92, 0x51, 0xb3, 0xe7, +0xf1, 0x80, 0xe8, 0xe5, 0xd2, 0x4f, 0xde, 0x14, +0x32, 0x0e, 0x79, 0xf4, 0x67, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0xa8, +0x99, 0xb6, 0x82, 0x13, 0x30, 0x8f, 0x0a, 0xb4, +0x30, 0x26, 0xca, 0x45, 0x3b, 0x43, 0x9f, 0xf8, +0x04, 0x81, 0x38, 0xcb, 0x5c, 0x06, 0xcb, 0xa6, +0xa9, 0xc5, 0xa4, 0x51, 0x88, 0xc3, 0xe0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x92, 0x26, 0x18, 0x71, 0x81, 0x60, 0xf0, +0x20, 0xdc, 0x1d, 0xfe, 0xf7, 0x19, 0x6f, 0xf3, +0x16, 0xc6, 0xc7, 0x3a, 0xfe, 0x61, 0x59, 0x93, +0xbe, 0x3c, 0x00, 0x70, 0xa9, 0x6c, 0x7a, 0xb6, +0xba, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0x49, 0x85, 0x45, 0x82, 0x42, +0xb7, 0x16, 0x65, 0x5d, 0xda, 0x0d, 0xce, 0xa2, +0x00, 0x8f, 0x46, 0x35, 0x44, 0x7f, 0x2b, 0xc5, +0x70, 0x69, 0x1e, 0x22, 0x52, 0x4c, 0x70, 0xb4, +0x00, 0x31, 0x6a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0x1d, 0x50, 0x45, +0xd6, 0x71, 0x1a, 0xfc, 0x16, 0x8d, 0x35, 0xbb, +0x9d, 0x15, 0x76, 0xbc, 0x82, 0x6b, 0x84, 0x26, +0x25, 0x5d, 0xc0, 0xd3, 0xf5, 0x78, 0x68, 0xe8, +0xe2, 0x73, 0x98, 0x2f, 0xf1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0x21, +0xd2, 0xe7, 0x6f, 0x1a, 0x03, 0x4e, 0x1d, 0x61, +0x24, 0x70, 0xdd, 0xff, 0x43, 0xdb, 0xf8, 0xca, +0xc0, 0xaf, 0xa6, 0xeb, 0xd1, 0x42, 0x6d, 0x80, +0x2c, 0xc7, 0x73, 0x43, 0xe9, 0x27, 0xee, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0x53, 0x47, 0x4c, 0x97, 0x7b, 0x23, 0x58, +0x30, 0x57, 0x2a, 0xd9, 0xe7, 0xd1, 0xb1, 0xb1, +0x52, 0x0e, 0x88, 0x7b, 0xa0, 0xe3, 0xaa, 0x80, +0x2a, 0xde, 0xae, 0x24, 0x54, 0xbe, 0x2f, 0xed, +0x49, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0x8a, 0xd5, 0x2b, 0x1e, 0x54, +0xc3, 0x62, 0xa3, 0x26, 0x60, 0x5a, 0x9c, 0x26, +0x07, 0x44, 0x87, 0x3f, 0x7a, 0xd7, 0x8a, 0xe7, +0xa4, 0x94, 0x89, 0xcb, 0x1b, 0x36, 0xb0, 0xfe, +0x3c, 0x16, 0x84, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0xbb, 0xde, 0xb6, +0x89, 0xfa, 0x91, 0xa7, 0xbc, 0xe4, 0xcd, 0xb7, +0xc1, 0x1e, 0xb5, 0xc1, 0x9b, 0xd6, 0xdf, 0x69, +0xbb, 0xaf, 0xbd, 0xe1, 0x87, 0x05, 0x87, 0xba, +0x0e, 0xb5, 0xcf, 0x45, 0x06, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0xe0, +0xcf, 0x95, 0x88, 0xdc, 0x4a, 0xa9, 0x9a, 0xb5, +0xd2, 0x5e, 0x64, 0x55, 0xf2, 0x97, 0xd0, 0x79, +0x44, 0x78, 0x4b, 0xf8, 0xe3, 0xbf, 0xe0, 0x38, +0xd2, 0xbc, 0x7f, 0x7d, 0x17, 0x96, 0xdd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0xd4, 0xf3, 0x30, 0xd5, 0x4f, 0x0d, 0x9c, +0x17, 0xf6, 0xb0, 0x6a, 0x77, 0x78, 0x74, 0x13, +0xfa, 0x6c, 0xab, 0x85, 0x60, 0x68, 0xc3, 0x86, +0x4d, 0xa4, 0x51, 0x14, 0x4e, 0x76, 0x7b, 0x8e, +0xa3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0x2d, 0xc5, 0x9e, 0x6c, 0xc1, +0xd3, 0x75, 0x37, 0x68, 0x27, 0xd4, 0xec, 0xab, +0x7b, 0x2c, 0x6f, 0x89, 0x10, 0x02, 0x27, 0x85, +0x38, 0x22, 0x2e, 0xc6, 0x90, 0xf6, 0x31, 0x53, +0xce, 0x0d, 0x9b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0x94, 0x77, 0x4a, +0xe3, 0x11, 0x6a, 0xd6, 0x1c, 0x3f, 0x5f, 0x71, +0x21, 0xa5, 0xc8, 0xe7, 0xd1, 0x4b, 0x3f, 0xbe, +0x0f, 0xce, 0x8e, 0xdf, 0x6c, 0x47, 0x07, 0x82, +0x87, 0x17, 0x49, 0x5e, 0xce, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0x6b, +0x9b, 0xf1, 0x22, 0x50, 0xf9, 0x1b, 0xf5, 0x01, +0x6b, 0x38, 0xa8, 0xa5, 0xc2, 0x8f, 0x89, 0xe4, +0x81, 0x33, 0x75, 0xf2, 0x87, 0xdd, 0x2d, 0x38, +0xf2, 0x91, 0x2f, 0x1e, 0x55, 0x45, 0x47, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0x6d, 0x5f, 0x6b, 0x63, 0x07, 0x3f, 0x98, +0xf0, 0xcd, 0xac, 0x2e, 0x65, 0x2f, 0x28, 0x6e, +0x5f, 0x89, 0x34, 0x38, 0xe5, 0x15, 0x13, 0x5a, +0x84, 0xf7, 0x9f, 0x31, 0xbe, 0xb2, 0x5a, 0x46, +0x2b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0x38, 0xce, 0xe6, 0xce, 0x0f, +0xb5, 0x67, 0x9c, 0x1c, 0x51, 0x1a, 0x8e, 0x73, +0xba, 0xd9, 0x8d, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x2d, 0x6f, 0x7a, 0xa9, +0x29, 0x72, 0x69, 0xbe, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0x82, 0xa4, 0x05, +0x54, 0x2e, 0xdd, 0xf9, 0x7e, 0xc4, 0x66, 0x59, +0x18, 0x96, 0x80, 0x3b, 0x59, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x1a, +0x02, 0x68, 0x9a, 0x4a, 0x92, 0xc2, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0xba, +0xc0, 0xd9, 0x82, 0x5d, 0xc0, 0x3a, 0x26, 0x72, +0x26, 0x6b, 0x88, 0x48, 0xe9, 0x29, 0x53, 0x4f, +0x0a, 0xef, 0x4b, 0x05, 0x03, 0x90, 0x53, 0xe9, +0x3f, 0xe5, 0x2a, 0x4b, 0xb1, 0xe7, 0x50, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0x8d, 0x37, 0x7b, 0x85, 0x8b, 0xfe, 0xc4, +0xbf, 0xf8, 0xfc, 0x5c, 0xc3, 0xb2, 0x73, 0x5f, +0x9f, 0x30, 0x45, 0x59, 0x56, 0xed, 0xd8, 0x7a, +0x41, 0x98, 0xaa, 0xef, 0xaf, 0x8b, 0xc0, 0xb3, +0xc8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0x59, 0x66, 0x89, 0xe9, 0x69, +0x2c, 0x10, 0xed, 0x3c, 0xb5, 0xfc, 0xc0, 0xf5, +0xdf, 0x64, 0x48, 0x81, 0xb0, 0x79, 0x5b, 0x62, +0x63, 0x9e, 0x49, 0xc3, 0x69, 0x9d, 0x43, 0x67, +0xdc, 0x10, 0x56, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0xb5, 0x57, 0x64, +0xa2, 0xa1, 0xef, 0xa2, 0xa7, 0x7e, 0xb1, 0xa7, +0x03, 0x64, 0xbf, 0xd8, 0xb2, 0x95, 0x30, 0x3d, +0x62, 0xef, 0x3d, 0x38, 0x8d, 0x20, 0x3d, 0x28, +0x3d, 0x45, 0xda, 0x8a, 0xf2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x1e, +0x44, 0xc8, 0xf3, 0x05, 0x57, 0x1c, 0xc2, 0x32, +0x80, 0x8c, 0xbe, 0xed, 0x88, 0xe2, 0xd1, 0x6b, +0x03, 0xfa, 0xe3, 0x60, 0xea, 0x7a, 0xc7, 0x25, +0xa3, 0xa4, 0x18, 0x4e, 0xb0, 0x21, 0x08, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0x6d, 0xfe, 0x5a, 0xa8, 0x8f, 0xc8, 0x81, +0x35, 0x09, 0xaa, 0xd1, 0x54, 0xc0, 0xdb, 0xb7, +0x7a, 0xc1, 0xe7, 0x40, 0x5e, 0xe1, 0x2d, 0xc7, +0x89, 0x4c, 0xe6, 0x3c, 0x5d, 0xab, 0x5a, 0x24, +0xf4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0xd2, 0x9d, 0xcf, 0xe6, 0x00, +0x73, 0x12, 0xf8, 0xd2, 0x33, 0xe4, 0xbc, 0x8f, +0xed, 0x01, 0x22, 0xb6, 0xee, 0xf3, 0x3a, 0x00, +0xc8, 0xaf, 0x2d, 0xc9, 0x16, 0xbe, 0xbe, 0x9f, +0x4b, 0xdd, 0xcf, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0xd2, 0xa9, 0xb4, +0xcd, 0xd4, 0xe5, 0xd0, 0x73, 0x58, 0x88, 0xd2, +0x9e, 0x1d, 0x91, 0xc7, 0x86, 0xfb, 0x3b, 0xae, +0xaf, 0xcd, 0x6f, 0x36, 0x8f, 0xe8, 0xae, 0x0e, +0x3f, 0xd9, 0x34, 0xcc, 0x51, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0xf0, +0x31, 0xc7, 0xb6, 0xc9, 0x72, 0x4f, 0x7c, 0xd4, +0xd2, 0xeb, 0xf3, 0xc0, 0xb4, 0x41, 0x9e, 0xf0, +0xbd, 0x5e, 0xa5, 0x91, 0x5a, 0x39, 0xca, 0x61, +0x1c, 0x0c, 0xca, 0x8c, 0x91, 0xaf, 0x3a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0x2f, 0x55, 0x38, 0x4a, 0xa3, 0x8d, 0x70, +0x66, 0xdb, 0xaa, 0x38, 0x38, 0x29, 0x3e, 0x46, +0xc8, 0x19, 0xa3, 0x1f, 0x64, 0x90, 0x5b, 0x48, +0x0b, 0x81, 0x68, 0x99, 0xe2, 0xa8, 0x44, 0x11, +0x89, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0x93, 0x22, 0x9f, 0xb6, 0x83, +0xab, 0x19, 0xc4, 0x18, 0x53, 0xec, 0xae, 0x8f, +0x0d, 0x9c, 0x8a, 0x80, 0x5e, 0x43, 0x38, 0x65, +0xe5, 0x09, 0x88, 0x2d, 0xa5, 0x64, 0xf1, 0xf2, +0xf9, 0x85, 0x78, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x1f, 0x30, 0x67, +0xd5, 0x4e, 0x68, 0x1c, 0xd7, 0x91, 0x9d, 0xb5, +0x0e, 0x38, 0x8e, 0xc5, 0x51, 0x82, 0x1a, 0x91, +0xb5, 0x7a, 0x3d, 0x18, 0xe0, 0x0f, 0x26, 0xe2, +0xeb, 0x95, 0x10, 0x08, 0xf4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0x69, +0x7a, 0xc7, 0x16, 0xf2, 0x63, 0xd1, 0x34, 0xca, +0x5e, 0xf3, 0x51, 0x28, 0xc1, 0xcd, 0xa5, 0xe2, +0xff, 0x77, 0x05, 0x57, 0xd3, 0x98, 0x25, 0xd3, +0x49, 0x19, 0x43, 0xd4, 0x4e, 0x21, 0x26, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0x94, 0x9d, 0x7d, 0xa2, 0x54, 0x6f, 0x39, +0x86, 0x13, 0x82, 0xe0, 0x35, 0x96, 0xe6, 0xf2, +0x14, 0xc9, 0xc2, 0x25, 0x8a, 0xd1, 0x12, 0x33, +0x5c, 0x2f, 0xed, 0x68, 0x89, 0x99, 0xd0, 0x25, +0xfb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0xed, 0x6f, 0x6b, 0xca, 0xc6, +0xad, 0x85, 0xb9, 0x93, 0x6f, 0x3c, 0x75, 0x92, +0x08, 0xa4, 0xe1, 0xd3, 0xf7, 0x52, 0xe2, 0xed, +0xd7, 0xb6, 0xc3, 0x5d, 0xdd, 0xc6, 0xa0, 0xe6, +0x4e, 0x50, 0x60, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0xd8, 0xdf, 0xd8, +0x3f, 0x72, 0x48, 0x72, 0x76, 0xac, 0xfc, 0xf0, +0xfd, 0xd7, 0x2b, 0x9d, 0x71, 0xe2, 0xb6, 0x65, +0x23, 0x0d, 0x11, 0x79, 0x07, 0x99, 0x4b, 0x30, +0x53, 0x55, 0xda, 0x5f, 0xb3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0xc3, +0x7f, 0x3b, 0x3a, 0xb0, 0x2b, 0x82, 0x1a, 0x1a, +0x04, 0x0f, 0x35, 0x65, 0x53, 0xdd, 0x33, 0xce, +0x0f, 0xa2, 0x57, 0xb7, 0xd2, 0x6a, 0x0e, 0x3c, +0x9d, 0x67, 0xe3, 0x13, 0x0b, 0x23, 0xd6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0x6b, 0x25, 0x9c, 0xd4, 0x7b, 0x0d, 0xe2, +0xc5, 0x69, 0xd7, 0x95, 0x38, 0x6f, 0x06, 0xf5, +0x84, 0x0d, 0x47, 0x7b, 0x73, 0x3f, 0xca, 0xe9, +0xee, 0xd3, 0x4c, 0x57, 0x61, 0xb5, 0x31, 0x04, +0x4f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0xb4, 0xb3, 0xcb, 0x22, 0xb3, +0x61, 0xea, 0x05, 0x8e, 0x73, 0x96, 0x18, 0x73, +0xdc, 0xc2, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xf9, 0x1f, 0xb5, 0x2c, +0x15, 0x4a, 0xf1, 0xc2, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0xb4, 0xfb, 0xc7, +0x8b, 0x02, 0xfb, 0xc1, 0xa7, 0xfa, 0xa2, 0xe7, +0x51, 0x30, 0xe4, 0x1c, 0xd2, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0xc9, +0x32, 0x85, 0x8e, 0xbd, 0x1c, 0x30, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x99, +0x2d, 0xd4, 0x23, 0x05, 0x68, 0x94, 0x8b, 0xce, +0x6e, 0x54, 0x21, 0x2a, 0x85, 0x30, 0x19, 0x13, +0x1d, 0xe6, 0x3e, 0x45, 0xcf, 0x5e, 0x90, 0x39, +0xf1, 0xdd, 0x08, 0xad, 0x2f, 0x5c, 0xd9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0xda, 0x63, 0x11, 0xb4, 0x5a, 0x07, 0xb4, +0x06, 0x18, 0x2d, 0x5c, 0x43, 0xdc, 0xc1, 0xaa, +0x02, 0x3d, 0xcb, 0x9d, 0x94, 0x29, 0x22, 0xc2, +0xd9, 0xdd, 0x81, 0x75, 0x5c, 0x21, 0x1b, 0x95, +0x65, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0xbc, 0x75, 0xe0, 0xa6, 0x50, +0x8d, 0xb6, 0x53, 0x81, 0x44, 0x52, 0x03, 0x1c, +0x32, 0xb3, 0x5f, 0x28, 0x53, 0xba, 0xdd, 0x32, +0xe1, 0x97, 0x2d, 0xdb, 0xfd, 0x58, 0x4b, 0x91, +0xf3, 0x7c, 0x74, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0x5a, 0x62, 0x1c, +0xb0, 0x0a, 0x83, 0x80, 0xd7, 0xdf, 0x3d, 0x91, +0x7c, 0x95, 0xf5, 0x0f, 0xe9, 0xcc, 0x60, 0x07, +0x47, 0x80, 0x53, 0x4a, 0x9c, 0x7f, 0xf2, 0xfa, +0x83, 0x72, 0x48, 0xc2, 0x2b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0x49, +0x48, 0xf0, 0x13, 0xff, 0xb1, 0xc3, 0x8a, 0x54, +0x84, 0xb1, 0xbc, 0xff, 0x0f, 0x18, 0x07, 0x16, +0xd3, 0xf7, 0x55, 0x9f, 0x9d, 0x76, 0xe7, 0xcb, +0x12, 0x4f, 0x26, 0xfa, 0xca, 0x81, 0xb1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0x41, 0x75, 0xcd, 0xc6, 0x70, 0xa2, 0xcc, +0xe7, 0xb0, 0xeb, 0x50, 0x7c, 0xb2, 0x39, 0x68, +0x5b, 0xa0, 0x74, 0x03, 0xa2, 0x5d, 0xbb, 0xa4, +0x7e, 0x12, 0x77, 0x85, 0x6a, 0x97, 0x62, 0x20, +0xee, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0xe3, 0x17, 0xe5, 0xe3, 0xb9, +0xbf, 0x8a, 0x30, 0x38, 0xa4, 0xf6, 0x00, 0x8c, +0x51, 0xb4, 0x89, 0xb4, 0x8f, 0xd0, 0x89, 0xde, +0xb3, 0x55, 0xe3, 0x8d, 0xb4, 0x63, 0x1e, 0x07, +0xb6, 0x15, 0xd8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0x3d, 0x93, 0x62, +0xa2, 0xb7, 0xa6, 0x8d, 0x3c, 0x9b, 0xe7, 0xf3, +0xbd, 0x7e, 0xf7, 0x6b, 0x13, 0x42, 0x4b, 0xb0, +0x48, 0x5f, 0x01, 0x50, 0x8f, 0xb4, 0x80, 0x4e, +0x56, 0xde, 0x9e, 0xc2, 0xb3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0xc2, +0xe9, 0x2b, 0xcf, 0x15, 0x58, 0xb0, 0xbe, 0x83, +0x20, 0x6c, 0x48, 0xd1, 0x9c, 0xbf, 0xd4, 0x84, +0xb5, 0xf3, 0x08, 0x35, 0x8e, 0x57, 0x91, 0x1e, +0x5e, 0xe7, 0x06, 0xda, 0x97, 0x87, 0x80, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0x11, 0x11, 0xb1, 0x58, 0x64, 0x3b, 0x23, +0x8a, 0x01, 0xec, 0xe6, 0x26, 0x12, 0x82, 0x1a, +0x2c, 0xcd, 0x39, 0x96, 0xa9, 0x50, 0x18, 0x13, +0x91, 0xbf, 0xf8, 0x6a, 0xf1, 0x4f, 0x6e, 0x21, +0x07, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0xfb, 0x99, 0x92, 0x88, 0xa2, +0x13, 0x6d, 0xa5, 0x2d, 0xb6, 0x6b, 0xdb, 0x39, +0x64, 0x12, 0x74, 0x88, 0x20, 0xfb, 0x82, 0x5c, +0x39, 0x44, 0x89, 0x99, 0xb2, 0x59, 0x00, 0x15, +0xc7, 0xf0, 0xcd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0x65, 0xa2, 0x02, +0x19, 0x6b, 0xab, 0xaa, 0xaf, 0xa1, 0x23, 0x59, +0x1b, 0x66, 0x41, 0xf6, 0x25, 0x58, 0x37, 0x98, +0xa6, 0x4a, 0x42, 0x8d, 0x47, 0x41, 0x5d, 0xa9, +0x82, 0x29, 0x2c, 0xbf, 0x89, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0x75, +0x75, 0x18, 0x20, 0xa9, 0x18, 0x7c, 0xfe, 0x8d, +0xee, 0xd9, 0x89, 0x27, 0xd1, 0x8e, 0x04, 0x74, +0x62, 0x96, 0xeb, 0x34, 0x23, 0xa8, 0x43, 0xe5, +0x05, 0xae, 0x24, 0x7c, 0xf6, 0xeb, 0xe6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0x64, 0x1b, 0x1a, 0x04, 0x78, 0xf2, 0xd4, +0x77, 0x70, 0x98, 0xd5, 0xc8, 0x68, 0xe3, 0x6d, +0xa1, 0x5e, 0x07, 0xe5, 0xab, 0x21, 0xf4, 0xf1, +0x5e, 0x71, 0xad, 0x33, 0xc0, 0xe2, 0x89, 0x69, +0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0xd0, 0x29, 0x99, 0x63, 0x98, +0x40, 0xf7, 0xdb, 0x62, 0x68, 0xd7, 0x54, 0x79, +0x9f, 0x36, 0x29, 0xf9, 0xe6, 0x2d, 0xbc, 0x7d, +0x2f, 0x1d, 0x4f, 0x22, 0xdf, 0x33, 0xbd, 0xb6, +0xba, 0x97, 0xdc, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x0b, 0x90, 0xfc, +0xff, 0x79, 0x3a, 0x21, 0xe8, 0x64, 0x26, 0xee, +0xd8, 0xd1, 0xd3, 0x31, 0x0e, 0x89, 0xcd, 0xd7, +0xbe, 0x7c, 0x50, 0xb7, 0x47, 0x2c, 0xc8, 0x32, +0x65, 0x40, 0x46, 0x02, 0x7e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x3e, +0x02, 0xbf, 0xd8, 0x2f, 0x1b, 0x52, 0xde, 0xd4, +0xed, 0x98, 0x80, 0xfa, 0x5b, 0xb2, 0x99, 0xe5, +0x22, 0x8e, 0x2a, 0x7e, 0x06, 0xbe, 0x3b, 0xc4, +0x1f, 0x2a, 0xa2, 0x96, 0x77, 0x7e, 0x76, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x53, 0x80, 0x48, 0xda, 0x34, 0xd3, 0xc2, +0x34, 0xfe, 0x07, 0x0f, 0xcf, 0xad, 0x4b, 0x13, +0xe1, 0xaf, 0xd6, 0xd1, 0xf8, 0x38, 0x07, 0xe5, +0x17, 0xc2, 0xd6, 0x2d, 0x5d, 0x0c, 0xa9, 0x79, +0x92, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0x84, 0x33, 0xcc, 0x33, 0xb2, +0x82, 0xc9, 0x3b, 0x1d, 0x72, 0xe7, 0x5d, 0x7e, +0x89, 0x52, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x21, 0xd2, 0xe8, 0x3a, +0xb8, 0x1a, 0xdb, 0x43, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0xf2, 0x98, 0x1c, +0xc9, 0xc9, 0x9a, 0x0e, 0x44, 0xaf, 0xdc, 0x8d, +0xf1, 0x81, 0xd2, 0xec, 0x78, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x9d, +0x0c, 0xc0, 0xe3, 0x3b, 0xd2, 0xe1, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0xa8, +0xca, 0x74, 0xe7, 0x7a, 0x6e, 0x78, 0xe0, 0x13, +0xa9, 0x75, 0x7f, 0x3e, 0xbc, 0x8a, 0x2c, 0x1f, +0xe2, 0x42, 0x32, 0xb0, 0xac, 0x6d, 0x6e, 0x0c, +0x40, 0xdc, 0x06, 0xee, 0xc3, 0xb3, 0xfa, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0xee, 0xd8, 0x09, 0xd2, 0x6b, 0xf0, 0x82, +0xbb, 0xab, 0x48, 0xa9, 0x54, 0x4b, 0xaf, 0xb8, +0x61, 0xa8, 0x9a, 0xd5, 0x7b, 0xa6, 0x0e, 0xe7, +0x5e, 0x97, 0x0a, 0xad, 0x2d, 0x50, 0x39, 0xf3, +0x1c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0x08, 0x11, 0xb0, 0xe6, 0x18, +0x0f, 0xeb, 0xea, 0xda, 0x32, 0xa0, 0x77, 0x1b, +0xc2, 0xae, 0x2a, 0xd8, 0x52, 0x88, 0x42, 0x71, +0xef, 0x76, 0x85, 0xb0, 0x6a, 0x47, 0x3d, 0x5b, +0x4d, 0x1d, 0xf8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0xac, 0xb5, 0x9e, +0xf2, 0x13, 0x32, 0xe2, 0x74, 0xcc, 0x40, 0xa6, +0x3b, 0xec, 0xe3, 0xaa, 0xc9, 0x8d, 0x8b, 0xcd, +0xb1, 0x00, 0x2f, 0xe8, 0xda, 0x2f, 0x50, 0x07, +0xa7, 0xfa, 0x67, 0x71, 0xb5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0x4a, +0xe7, 0x23, 0x43, 0xf7, 0x40, 0xe2, 0xce, 0xc3, +0x2d, 0x7c, 0xd8, 0xc8, 0x9c, 0xc5, 0x65, 0x70, +0x68, 0x0f, 0x3f, 0x85, 0x10, 0xfb, 0x3b, 0x7d, +0x81, 0xe4, 0xf5, 0xca, 0xf5, 0x44, 0x9a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0x67, 0xa7, 0x07, 0x4d, 0xf8, 0x36, 0x2e, +0x86, 0x0b, 0xdf, 0xad, 0x6a, 0x51, 0xeb, 0x43, +0xaa, 0x71, 0x73, 0x89, 0x58, 0xa3, 0xc9, 0x3b, +0x97, 0x72, 0x0a, 0x2b, 0x8a, 0x2a, 0xc9, 0x42, +0x86, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0x88, 0x8a, 0xbe, 0x97, 0xd3, +0xb8, 0x42, 0x29, 0xbb, 0xf1, 0xa2, 0xca, 0xb9, +0x5b, 0x35, 0x55, 0x79, 0xfa, 0xb9, 0x76, 0x96, +0x1b, 0x5e, 0x38, 0x54, 0x3a, 0x3c, 0x3c, 0x2f, +0x92, 0x1e, 0xd4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0xe1, 0x01, 0xe8, +0x9f, 0x2d, 0x39, 0x98, 0x2b, 0x0e, 0x9b, 0xa8, +0x8d, 0x37, 0x73, 0xf8, 0xb2, 0xc8, 0xcb, 0x63, +0x9e, 0xa1, 0x53, 0x3d, 0x73, 0x6a, 0xfc, 0x5e, +0x53, 0x13, 0xcd, 0x1a, 0xb3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0x1c, +0xb6, 0x92, 0x6a, 0x04, 0x2d, 0x5c, 0x15, 0x8c, +0xd5, 0x74, 0x5f, 0xcb, 0xd1, 0xde, 0xdb, 0xa9, +0x4b, 0x49, 0xc9, 0x05, 0x29, 0x47, 0x21, 0xd2, +0xb7, 0x71, 0xc0, 0x3d, 0x8a, 0x0f, 0x70, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0xfc, 0x1a, 0x4b, 0xad, 0x95, 0x26, 0xf0, +0x61, 0x1f, 0x5d, 0x52, 0x94, 0x2a, 0x97, 0x84, +0xcb, 0x32, 0xfa, 0x43, 0xcd, 0x30, 0x4c, 0x59, +0xad, 0x6e, 0x9c, 0x3b, 0x36, 0xee, 0xfa, 0x0c, +0x1d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0x58, 0xfe, 0x44, 0x58, 0xc6, +0x23, 0x1c, 0x70, 0xf1, 0x33, 0x67, 0x6d, 0x30, +0x95, 0x8a, 0x1b, 0x65, 0xf4, 0xab, 0x0a, 0xea, +0x81, 0xe9, 0x9f, 0xbe, 0xe6, 0x17, 0x28, 0xde, +0x5c, 0x80, 0xb7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0x1c, 0x8d, 0xee, +0xaa, 0x83, 0x6a, 0x26, 0x59, 0x8e, 0x10, 0x33, +0x74, 0xbf, 0xff, 0x24, 0x3d, 0x6f, 0x7a, 0x62, +0x1d, 0x7c, 0x15, 0x84, 0x8d, 0x1f, 0x2a, 0x79, +0xcf, 0x98, 0x1d, 0x85, 0x00, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0x56, +0xf2, 0xc4, 0x21, 0xac, 0x9d, 0x46, 0x9d, 0x9c, +0xea, 0xe9, 0xdb, 0xd3, 0x9d, 0x23, 0xfd, 0x6a, +0x1c, 0xc7, 0x8d, 0xd9, 0x40, 0x26, 0xf1, 0xbc, +0x5a, 0x6d, 0x9a, 0x66, 0x65, 0x26, 0xed, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0x67, 0x49, 0xe0, 0x9a, 0x8a, 0x97, 0x4a, +0xfa, 0x42, 0x3b, 0xb7, 0x12, 0x66, 0xaf, 0x69, +0xcf, 0xa6, 0xc6, 0xfe, 0x61, 0xb1, 0x9d, 0xfc, +0x96, 0xf9, 0xd3, 0x97, 0x2b, 0x91, 0xd2, 0xae, +0x0b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0x5e, 0x46, 0x97, 0x64, 0xe6, +0x8a, 0xe1, 0x18, 0x07, 0xd8, 0x6f, 0x4d, 0xf4, +0xe6, 0x32, 0xba, 0xc2, 0x76, 0xd0, 0xec, 0x4b, +0xdc, 0x5c, 0xb8, 0x62, 0x4f, 0x57, 0x0d, 0x9a, +0x75, 0xb9, 0xdc, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0x63, 0xe9, 0x39, +0x40, 0x4b, 0x86, 0x3a, 0x7b, 0x5b, 0xb8, 0xcc, +0x79, 0x4a, 0x40, 0xf4, 0x56, 0xb6, 0x6f, 0x99, +0xc4, 0xc1, 0x5e, 0x5c, 0x54, 0x2f, 0xab, 0xda, +0x51, 0x04, 0x4e, 0x41, 0x71, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0x04, +0x53, 0x1c, 0x72, 0x02, 0xb6, 0x92, 0xd2, 0x8f, +0xda, 0x28, 0xf2, 0xa6, 0x9c, 0xd2, 0xb8, 0xda, +0xe0, 0x90, 0x83, 0xf0, 0x94, 0xdf, 0x25, 0xfb, +0x82, 0xd6, 0xf7, 0xd1, 0x2c, 0x98, 0x73, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0x04, 0x14, 0xa6, 0x55, 0xbe, 0xf0, 0x73, +0x70, 0x49, 0x4d, 0xf7, 0x53, 0x25, 0x74, 0x09, +0x23, 0x31, 0x2f, 0xb5, 0x4a, 0xd6, 0xcd, 0xf4, +0x8e, 0x1f, 0x2a, 0x08, 0x07, 0x84, 0x1f, 0x82, +0xb8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0x5f, 0xe6, 0x7e, 0xf3, 0x38, +0x06, 0x38, 0xa8, 0xf7, 0xd7, 0x5a, 0x0e, 0x3d, +0x53, 0xff, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xbd, 0xbb, 0xf7, 0xf4, +0xe3, 0x63, 0x8d, 0x5f, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0xa8, 0x60, 0x5d, +0x15, 0xd2, 0x67, 0xb1, 0x7f, 0x31, 0x07, 0x38, +0x5a, 0x3a, 0x79, 0x25, 0x01, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x1f, +0xcf, 0x91, 0xe7, 0xa1, 0xde, 0xb6, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0xb3, +0x86, 0xed, 0x97, 0xd2, 0x4e, 0xfd, 0x8e, 0xb8, +0xd8, 0xf8, 0xbe, 0xdb, 0x00, 0xdb, 0x6f, 0x62, +0xd0, 0xa7, 0x5b, 0xc4, 0xc6, 0x42, 0x99, 0x87, +0x60, 0xf3, 0x12, 0x3a, 0xe7, 0xeb, 0xf6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0x63, 0x95, 0x5a, 0xb8, 0x03, 0xf5, 0xc4, +0xf5, 0x11, 0x03, 0xa8, 0x9c, 0x93, 0xe2, 0x10, +0xf5, 0x6b, 0xfb, 0x8d, 0x28, 0xe7, 0x80, 0xcc, +0x14, 0x0e, 0xe4, 0x34, 0x1b, 0x7d, 0xe5, 0x49, +0x98, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0x44, 0xeb, 0xfb, 0xaa, 0x52, +0x90, 0x66, 0x15, 0x6f, 0x81, 0xfd, 0x51, 0x50, +0x17, 0xaa, 0x5b, 0x30, 0xc1, 0xf6, 0xde, 0x21, +0x7f, 0x39, 0x78, 0x83, 0x60, 0x8b, 0x9b, 0xa9, +0x98, 0xdc, 0x33, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0x70, 0xa0, 0x72, +0xf3, 0xd6, 0x9f, 0x5b, 0x30, 0x94, 0x6b, 0x0a, +0x16, 0xe5, 0x61, 0x67, 0x12, 0x49, 0x23, 0xaf, +0x73, 0xfb, 0xe9, 0x4d, 0x1e, 0x67, 0xc4, 0x66, +0x40, 0x21, 0x71, 0xc6, 0x6e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0x65, +0xdf, 0x0f, 0x7d, 0x36, 0x93, 0x71, 0x29, 0xcd, +0xca, 0xef, 0x29, 0x74, 0x07, 0x6c, 0xab, 0x51, +0x0c, 0xa5, 0x00, 0xea, 0xae, 0xdb, 0x48, 0x6c, +0xbe, 0xe2, 0x44, 0xf9, 0xff, 0xb3, 0xfb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0x55, 0xcd, 0xe8, 0x8a, 0x69, 0x8e, 0xdd, +0x29, 0xfe, 0x29, 0x84, 0x70, 0x79, 0x1a, 0xfb, +0x85, 0x52, 0x6d, 0xdd, 0x5c, 0x59, 0x2a, 0x57, +0x89, 0x31, 0xf5, 0x7d, 0xc7, 0x8e, 0xb7, 0x35, +0x95, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0xae, 0x45, 0xcc, 0xab, 0x31, +0x0a, 0x0f, 0x0a, 0x6b, 0x8d, 0x7d, 0x11, 0x71, +0xf3, 0x7a, 0x67, 0xd9, 0xc8, 0x33, 0x78, 0x2c, +0x45, 0x19, 0xfd, 0x72, 0xb1, 0x51, 0x71, 0xb3, +0x27, 0x67, 0x84, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0x3a, 0xf8, 0x8e, +0x3b, 0x20, 0xe0, 0xb6, 0x4d, 0x92, 0x28, 0xb2, +0xd7, 0x22, 0xa4, 0xc0, 0x58, 0xc8, 0x81, 0x15, +0x81, 0x55, 0x98, 0x0d, 0x1e, 0xd5, 0x4f, 0x2b, +0x8e, 0xb1, 0x76, 0xe2, 0xd5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0xd9, +0xc6, 0x93, 0xf2, 0x8e, 0xa0, 0x33, 0x24, 0xd0, +0x6b, 0xc2, 0x9a, 0xab, 0x57, 0xa5, 0xb9, 0x95, +0xad, 0x6c, 0xd8, 0x0d, 0xc6, 0x9b, 0x7d, 0x0f, +0xa4, 0x93, 0x58, 0x51, 0x21, 0xc8, 0x52, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0x38, 0x5f, 0x3d, 0xf1, 0x3b, 0x36, 0x78, +0xa3, 0x82, 0x5b, 0xed, 0x25, 0xa4, 0x3d, 0x36, +0x10, 0x20, 0x82, 0xce, 0x2f, 0x81, 0x21, 0x54, +0xfd, 0x24, 0x6a, 0x31, 0xc1, 0xe9, 0xa6, 0xd4, +0x31, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0x06, 0x9a, 0xb0, 0x5a, 0x4d, +0x07, 0xad, 0x72, 0xc2, 0xf4, 0x52, 0xce, 0x31, +0x36, 0x55, 0x0e, 0x41, 0x11, 0x52, 0xb3, 0x23, +0x43, 0xda, 0xa9, 0x8e, 0x3c, 0x5d, 0x3a, 0x25, +0xa3, 0xb2, 0xae, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0xe0, 0x6f, 0x8f, +0x6a, 0x1a, 0x85, 0x33, 0x9f, 0x9e, 0x5b, 0x2e, +0xc1, 0xdc, 0xe4, 0xfb, 0x31, 0x79, 0x67, 0x2c, +0x94, 0xdd, 0x66, 0xd3, 0x3a, 0x18, 0x22, 0x18, +0x5f, 0xec, 0x89, 0x3c, 0x6b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0xac, +0x33, 0xe4, 0xe0, 0x90, 0x32, 0xfa, 0xa9, 0xe7, +0x70, 0xa4, 0xea, 0x13, 0xd7, 0x9d, 0xae, 0xb7, +0x7f, 0xf8, 0x3a, 0xdf, 0xac, 0xda, 0x81, 0xcc, +0xf8, 0x8c, 0x48, 0x28, 0x64, 0x02, 0xb3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x69, 0x6a, 0x2c, 0x77, 0x69, 0x09, 0x5b, +0x3c, 0x97, 0x09, 0xa2, 0x2a, 0xfb, 0x48, 0x1e, +0x83, 0x44, 0xd4, 0xae, 0xdf, 0x6b, 0x9a, 0x5c, +0x0c, 0x9f, 0x5d, 0xb9, 0x41, 0x4c, 0x15, 0x80, +0x17, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0x43, 0xed, 0x02, 0x61, 0x10, +0xc0, 0xf4, 0x46, 0x85, 0x65, 0x9b, 0xc7, 0x3a, +0x53, 0x8c, 0x18, 0xcc, 0xf1, 0x24, 0x5e, 0xce, +0x80, 0xa4, 0x7a, 0xd4, 0xb8, 0xe0, 0x26, 0xd7, +0x3e, 0xb0, 0xc5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0xd7, 0x0b, 0xc3, +0x11, 0xb5, 0x0c, 0x40, 0x86, 0xd1, 0xc2, 0xbb, +0x19, 0xef, 0x87, 0xbc, 0x50, 0x96, 0x73, 0x0c, +0xed, 0x7b, 0x29, 0xc3, 0x07, 0xcf, 0xd8, 0xcf, +0xc8, 0xed, 0xd4, 0x08, 0x0c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x9a, +0xb3, 0xec, 0x97, 0x0b, 0x7f, 0x05, 0x84, 0xa5, +0xf7, 0xdf, 0xb3, 0x0f, 0xe9, 0xad, 0xd2, 0x8b, +0xcf, 0xb4, 0x82, 0xd1, 0xe4, 0x68, 0xe6, 0x0d, +0xfc, 0x22, 0xe0, 0xf7, 0xf5, 0x86, 0x25, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0xc4, 0xf2, 0x62, 0xbe, 0xdf, 0x23, 0x7f, +0x60, 0xb3, 0x1e, 0x00, 0xb4, 0xf9, 0xa0, 0x81, +0x8e, 0xf0, 0xda, 0xf0, 0x77, 0x7b, 0x1c, 0x7a, +0xc5, 0x92, 0xee, 0xb1, 0xe2, 0x1e, 0x26, 0xfc, +0x5b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0xcd, 0x91, 0xe3, 0x01, 0x3a, +0xc6, 0x71, 0x30, 0x05, 0x55, 0x93, 0xf7, 0xc0, +0x85, 0x4e, 0xe2, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x2b, 0x90, 0xad, 0x56, +0x68, 0xcd, 0x92, 0x18, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0x6b, 0x99, 0x12, +0x6a, 0xf9, 0xbc, 0x2e, 0xe8, 0xb6, 0x59, 0xec, +0xe8, 0x1c, 0xe7, 0x7d, 0x58, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9f, 0x7b, +0x72, 0x14, 0xce, 0x6b, 0x65, 0xd7, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x94, +0xb3, 0x0c, 0x91, 0x24, 0xf2, 0x8e, 0xf4, 0xb6, +0x2f, 0x63, 0x3c, 0x16, 0x77, 0x62, 0xa0, 0x5b, +0x0b, 0xeb, 0xef, 0xab, 0xe8, 0xee, 0x4b, 0x14, +0x32, 0x99, 0xc8, 0x7c, 0x5a, 0xc7, 0xbb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0x05, 0x19, 0x2d, 0x60, 0xb6, 0xd6, 0xb8, +0x08, 0x21, 0xa5, 0x16, 0x69, 0xb7, 0xcb, 0x35, +0x7d, 0x9c, 0x47, 0x48, 0x2f, 0x96, 0x1a, 0xd0, +0x7c, 0x1e, 0x19, 0x40, 0xd1, 0xf6, 0x98, 0xc6, +0xb9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0xbe, 0x1c, 0xd8, 0xdb, 0x6b, +0x86, 0x50, 0x69, 0xa8, 0xca, 0x36, 0x6f, 0x0a, +0xa2, 0x95, 0x17, 0x0c, 0x20, 0xa4, 0x7b, 0x03, +0xe3, 0x01, 0x11, 0x4f, 0xa8, 0x36, 0x82, 0x01, +0xd1, 0x6d, 0xa2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0xe3, 0x8f, 0x3f, +0xb6, 0x67, 0x36, 0x53, 0xa1, 0x55, 0x2b, 0xb5, +0x4e, 0xb5, 0xbb, 0x5d, 0xe2, 0xef, 0xab, 0x0c, +0xc5, 0x45, 0x80, 0xee, 0x42, 0x63, 0xba, 0x20, +0xef, 0xf1, 0xeb, 0xee, 0xe0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x21, +0xe8, 0xde, 0x72, 0x0a, 0xee, 0x04, 0xc0, 0xa9, +0x5a, 0xae, 0x26, 0xda, 0x3e, 0xd0, 0x20, 0x23, +0x43, 0x80, 0xac, 0xc2, 0x69, 0x3d, 0x3b, 0x5b, +0x82, 0x9d, 0x1c, 0xee, 0xa3, 0x93, 0xf3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0x2f, 0xc3, 0x32, 0x82, 0xfb, 0x78, 0xb2, +0x08, 0xfb, 0x23, 0xc4, 0x88, 0x9f, 0x5d, 0x44, +0x90, 0x2b, 0x2c, 0xcf, 0x26, 0xca, 0x07, 0xfa, +0x5d, 0x0c, 0x0d, 0x56, 0x16, 0x28, 0x02, 0x0f, +0x09, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0xef, 0x3a, 0x65, 0x2e, 0x41, +0x9c, 0xc0, 0x0e, 0xf2, 0xd5, 0x8d, 0x68, 0x9d, +0x10, 0x28, 0xb8, 0xcb, 0xc8, 0xe2, 0xa7, 0x36, +0x9a, 0x03, 0x01, 0xe7, 0x7a, 0xad, 0x96, 0xd9, +0x8a, 0x52, 0x78, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x37, 0x13, 0x20, +0x11, 0xd2, 0x2d, 0xc9, 0xc9, 0x59, 0x32, 0x7c, +0x7b, 0xfa, 0xd3, 0x3b, 0xfb, 0x40, 0x95, 0x07, +0x34, 0x04, 0xb9, 0x71, 0xb8, 0x8f, 0x02, 0x19, +0xda, 0xbd, 0x91, 0x29, 0x3d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0xdf, +0x1d, 0xc3, 0x47, 0xd7, 0x0d, 0x6c, 0x0c, 0x59, +0xc5, 0xaf, 0xe6, 0x60, 0xf2, 0x7d, 0x61, 0x5c, +0x57, 0xf5, 0x62, 0xed, 0x19, 0xa6, 0x28, 0x87, +0x10, 0x4d, 0x23, 0xc2, 0x53, 0x7b, 0xe8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0x62, 0x2a, 0x35, 0xb3, 0x0b, 0x30, 0x53, +0xd4, 0xc0, 0x07, 0xb7, 0x40, 0x8a, 0x67, 0x6b, +0x06, 0x01, 0x01, 0xbc, 0x0e, 0xc2, 0x96, 0x0c, +0x20, 0x33, 0xcf, 0xc6, 0x00, 0x90, 0x4b, 0x02, +0x94, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0x73, 0xd1, 0x7e, 0x27, 0x4b, +0x51, 0xf7, 0x9c, 0x2b, 0x35, 0x6d, 0x2f, 0x9c, +0x03, 0x40, 0x2c, 0x2a, 0x88, 0xf6, 0x6f, 0x5f, +0xe6, 0xed, 0xf7, 0xfe, 0xe2, 0xec, 0x2b, 0x2c, +0x1e, 0xb9, 0x98, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0x4a, 0x1b, 0xe0, +0x52, 0x3a, 0x18, 0x9c, 0xc4, 0x74, 0xd4, 0x59, +0x79, 0x95, 0xc3, 0x84, 0xed, 0x06, 0x5a, 0x9a, +0xa2, 0x9b, 0x5c, 0x86, 0xc3, 0x8c, 0xba, 0x6e, +0x4d, 0x66, 0x05, 0xd2, 0x5f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0x43, +0x17, 0x1c, 0x3c, 0x39, 0xa2, 0xbf, 0xa5, 0xd5, +0x45, 0x8b, 0x14, 0xdd, 0x67, 0xd7, 0x18, 0x6c, +0x59, 0x83, 0xd1, 0xd1, 0x48, 0xdd, 0xe8, 0x87, +0x37, 0xc3, 0x63, 0xd0, 0x83, 0xf9, 0xcb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0xd0, 0xd5, 0x0c, 0x7d, 0x34, 0x90, 0x36, +0x1a, 0xdf, 0x8a, 0x6e, 0x95, 0x2f, 0xbc, 0x67, +0x30, 0xda, 0x96, 0x4d, 0x8a, 0x91, 0x1a, 0x27, +0xf6, 0x07, 0xdb, 0x34, 0x61, 0x72, 0xb4, 0x1d, +0xb0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x1e, 0xff, 0x5d, 0x65, 0x47, +0xf8, 0x14, 0xf9, 0xe3, 0xfc, 0x0a, 0x57, 0x3d, +0xe3, 0x79, 0x53, 0xbc, 0xb0, 0x85, 0x1c, 0x90, +0x22, 0x64, 0x72, 0xfd, 0xac, 0x31, 0x5b, 0x73, +0x32, 0x88, 0xd5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0x3b, 0xb7, 0xe7, +0x44, 0xf4, 0xdc, 0xde, 0xdb, 0x33, 0x1c, 0x65, +0xf4, 0x55, 0x1a, 0x93, 0x34, 0x67, 0xf0, 0x6e, +0xdb, 0xaa, 0xb4, 0x87, 0x3c, 0xf6, 0x36, 0xff, +0x3b, 0xea, 0xd5, 0x69, 0xa7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0x10, +0xb1, 0xc8, 0x83, 0xa5, 0x17, 0x89, 0xf9, 0x9a, +0x99, 0xd3, 0x25, 0x86, 0xe3, 0x8d, 0x70, 0x9d, +0xaa, 0x4d, 0x3e, 0x8a, 0xd1, 0xdb, 0xb6, 0xe9, +0x48, 0x95, 0x7a, 0x7c, 0x3d, 0x22, 0x07, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0xeb, 0x90, 0xc5, 0x7f, 0x15, 0xcd, 0x30, +0x5d, 0xff, 0x47, 0x74, 0x33, 0xd4, 0x2a, 0xe1, +0x8f, 0x7d, 0x88, 0x0a, 0xa4, 0x8f, 0xef, 0xc8, +0x87, 0xce, 0xcb, 0xb1, 0xbf, 0x8b, 0x77, 0xcf, +0x32, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0x3c, 0x16, 0xad, 0x7e, 0xc4, +0x60, 0x5d, 0x64, 0xc8, 0x8d, 0x65, 0xf4, 0xce, +0x8f, 0x16, 0x85, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x96, 0xb4, 0xb5, 0x01, +0x32, 0xd2, 0xd1, 0xf0, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0xca, 0x52, 0x4b, +0xc1, 0xa6, 0xb9, 0xd7, 0xdd, 0xfd, 0x54, 0x96, +0xbb, 0x31, 0xe4, 0xd0, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x96, 0x48, +0x08, 0x8d, 0x71, 0x2e, 0xbe, 0x2d, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x47, +0x6a, 0x42, 0x9e, 0x87, 0x43, 0x36, 0xa8, 0x4b, +0x32, 0x57, 0x17, 0x3a, 0xc6, 0xb4, 0x80, 0xb8, +0xf9, 0x11, 0x85, 0xa2, 0x50, 0x27, 0x5f, 0x69, +0x9e, 0x48, 0x07, 0x14, 0x94, 0x07, 0x1c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0xed, 0x0a, 0x88, 0xf7, 0xb6, 0x4d, 0x79, +0x91, 0x6a, 0xb7, 0x17, 0x57, 0x77, 0x2f, 0x13, +0xdc, 0xa0, 0xae, 0x98, 0xb3, 0x41, 0x72, 0x9b, +0x77, 0x90, 0xe6, 0x90, 0xa8, 0xc9, 0x9e, 0x28, +0xf1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0x58, 0xee, 0x2e, 0x23, 0x23, +0x0f, 0xc4, 0x2f, 0x44, 0xb6, 0x98, 0xd5, 0xc3, +0xdd, 0xdf, 0x78, 0xda, 0x02, 0x2a, 0x18, 0x23, +0xf0, 0x15, 0xf3, 0x22, 0x92, 0x74, 0x3d, 0x2c, +0x44, 0x3a, 0x53, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0xb2, 0xbb, 0x04, +0x78, 0x5e, 0x07, 0x13, 0xd5, 0x38, 0xf3, 0x7e, +0x6d, 0xf0, 0xf6, 0x12, 0x06, 0x97, 0x32, 0x96, +0x1c, 0xef, 0x52, 0x39, 0x20, 0xdf, 0x69, 0x4d, +0x91, 0x6c, 0x83, 0x9b, 0x1e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0xd3, +0x85, 0xf6, 0xac, 0xa9, 0x21, 0x5b, 0xec, 0x7d, +0xa1, 0x65, 0x34, 0xe9, 0xfe, 0x9e, 0x38, 0x28, +0x6a, 0xe3, 0x7d, 0x78, 0x16, 0x14, 0x46, 0x80, +0x5e, 0x00, 0x9d, 0x27, 0x36, 0x10, 0xed, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0xb2, 0xd4, 0x18, 0x03, 0x80, 0xf1, 0x75, +0x7f, 0x1a, 0x00, 0x3b, 0x83, 0xeb, 0x59, 0x3d, +0x84, 0x37, 0x52, 0x3e, 0xb4, 0xd8, 0xf9, 0xdc, +0x4a, 0xfc, 0x3b, 0xa5, 0x75, 0x4e, 0x16, 0xbe, +0x91, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0x0f, 0xda, 0x5e, 0x74, 0x3d, +0x2a, 0x03, 0xf1, 0x02, 0x1b, 0xbe, 0xed, 0xb3, +0x35, 0x6f, 0xed, 0x8c, 0x45, 0xf7, 0xe8, 0x79, +0x24, 0xb1, 0x9a, 0x27, 0x52, 0xd0, 0x98, 0x95, +0xdc, 0x6f, 0x30, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0xd4, 0x5f, 0x6f, +0x2d, 0x83, 0xe3, 0xc1, 0x73, 0xdf, 0x22, 0xfc, +0xc2, 0x15, 0x08, 0x2a, 0xd7, 0x6d, 0xa7, 0xe0, +0x12, 0x7d, 0x33, 0xd9, 0xd4, 0x33, 0x93, 0x3a, +0xa3, 0xf2, 0x36, 0x71, 0xa2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0x5b, +0x1e, 0xd7, 0x38, 0xc9, 0x9f, 0x4e, 0x1f, 0xaa, +0xd8, 0x1d, 0x36, 0x37, 0x56, 0xa0, 0xfe, 0xc3, +0x2f, 0xfc, 0x81, 0x25, 0xde, 0xb4, 0xa2, 0x3c, +0x23, 0x82, 0xeb, 0x63, 0xb7, 0x4c, 0xe4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0x34, 0x0b, 0xe2, 0x73, 0x87, 0x3b, 0x13, +0xcd, 0x7c, 0xed, 0x06, 0xb0, 0x67, 0x21, 0x22, +0x4a, 0xb2, 0xfb, 0x78, 0x2b, 0xa4, 0x2d, 0x50, +0xc2, 0xfa, 0x56, 0x15, 0x23, 0x21, 0xee, 0x97, +0x66, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0xbb, 0xdf, 0x1e, 0xf1, 0x7e, +0x74, 0x22, 0x9d, 0x6a, 0xc7, 0xa8, 0x9f, 0x81, +0x57, 0x22, 0x0c, 0x0a, 0xd1, 0xe4, 0x9d, 0x67, +0x37, 0xc1, 0x39, 0xba, 0xa5, 0x56, 0x61, 0x65, +0xcc, 0x24, 0xcd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0xc4, 0x4b, 0x49, +0xbc, 0xb2, 0x5b, 0x98, 0x9b, 0xe1, 0x6a, 0x34, +0x20, 0xb6, 0x47, 0x78, 0xbf, 0x17, 0x83, 0x3b, +0x50, 0xd5, 0x3d, 0x9c, 0xd0, 0x46, 0x0b, 0xd4, +0x15, 0x37, 0xe2, 0xb3, 0xf4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0xca, +0x57, 0x71, 0x47, 0x0b, 0xc9, 0xee, 0x95, 0xbd, +0x5b, 0xb5, 0x5f, 0xae, 0xa5, 0xb0, 0xa0, 0x50, +0x6f, 0x26, 0x05, 0xbc, 0x5e, 0x6b, 0x40, 0x07, +0x1c, 0xfc, 0x50, 0x82, 0xbc, 0x0b, 0xe9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0x0c, 0x5b, 0x16, 0x12, 0x79, 0x0f, 0x28, +0x08, 0xad, 0x0f, 0x6f, 0xb4, 0xe8, 0x9c, 0x22, +0xe2, 0x2e, 0x8d, 0x5e, 0xeb, 0x61, 0xda, 0x78, +0x6d, 0xdf, 0xa2, 0x40, 0x3e, 0x43, 0x24, 0x7f, +0xb0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0x80, 0x16, 0x8f, 0xd0, 0x33, +0xda, 0x08, 0x82, 0x29, 0xb1, 0x0e, 0xa3, 0xf6, +0x46, 0xca, 0x97, 0x77, 0xa7, 0xa7, 0x11, 0xdc, +0x7c, 0x91, 0xda, 0xf6, 0xfd, 0x3d, 0x2a, 0x4d, +0x24, 0x08, 0x51, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0xaa, 0x60, 0x71, +0x7b, 0x68, 0x28, 0x17, 0x96, 0x1d, 0x83, 0x54, +0x7c, 0xc2, 0x61, 0x1b, 0xf6, 0x9f, 0x3d, 0x79, +0x1b, 0xd5, 0xb6, 0xc9, 0xf8, 0xbc, 0x86, 0x7a, +0x68, 0x1f, 0x7e, 0xdd, 0x47, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0xf2, +0xf6, 0x37, 0x02, 0x06, 0x3a, 0xfc, 0xb4, 0x2d, +0x29, 0xb5, 0x7b, 0x78, 0x51, 0x82, 0x86, 0x45, +0xe9, 0x88, 0x80, 0x54, 0x7d, 0x0a, 0xaa, 0x49, +0x0d, 0x7d, 0xf9, 0x89, 0x05, 0x2b, 0x0f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0x94, 0x78, 0x34, 0x74, 0x56, 0xde, 0xdc, +0x37, 0x82, 0xaa, 0x57, 0x55, 0x61, 0xe0, 0x74, +0xd1, 0x48, 0xed, 0x57, 0xc3, 0x05, 0xb0, 0xc7, +0x94, 0x5b, 0x75, 0x48, 0xd6, 0x62, 0xf1, 0x1c, +0xcb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x3b, 0xbc, 0x68, 0x20, 0x6f, +0x4d, 0x5a, 0xf2, 0x3f, 0x75, 0x1a, 0x6d, 0x3b, +0x7c, 0xee, 0xce, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x77, 0x53, 0xa4, 0xbe, +0x06, 0xd9, 0xb2, 0xa5, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0x29, 0xe9, 0xb6, +0xb3, 0xca, 0xef, 0x34, 0x29, 0xa8, 0x36, 0xd8, +0x9f, 0xad, 0x4f, 0xc6, 0x46, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0xcb, +0xcf, 0x41, 0x80, 0xa0, 0x06, 0x88, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0x04, +0x9e, 0x21, 0x8d, 0x5d, 0xa2, 0x20, 0x3d, 0x4b, +0x06, 0x85, 0xba, 0xe2, 0x74, 0x9f, 0xef, 0x24, +0xd7, 0x51, 0x11, 0x74, 0xbf, 0x84, 0x7b, 0xb9, +0x36, 0x3f, 0x29, 0x42, 0x2c, 0xcb, 0xb4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x8c, 0xa2, 0xc7, 0x3e, 0x57, 0x7d, 0x3e, +0x1e, 0xfd, 0x53, 0x44, 0xee, 0x3b, 0xae, 0x7a, +0xa3, 0x4b, 0xa7, 0x40, 0xbd, 0xec, 0x6e, 0xfc, +0x11, 0xa4, 0xf7, 0x46, 0x4e, 0x29, 0x97, 0x7c, +0x15, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x49, 0x11, 0x78, 0x57, 0xaf, +0x0e, 0x4b, 0xae, 0x4a, 0xca, 0xad, 0x7c, 0x81, +0xf6, 0x2d, 0xd9, 0x68, 0x7a, 0x47, 0x1b, 0x0b, +0xe0, 0x65, 0xaa, 0x66, 0x3e, 0x5d, 0xfd, 0xfd, +0x1d, 0xab, 0x20, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0x46, 0x5b, 0xdb, +0x43, 0x66, 0xe0, 0x7c, 0xf3, 0xc2, 0x6e, 0x10, +0x53, 0xb1, 0x2d, 0xc7, 0xec, 0x8b, 0xb2, 0x94, +0x16, 0x63, 0x29, 0xfc, 0x59, 0xbb, 0xc1, 0xd4, +0x9c, 0x81, 0xd3, 0x67, 0x88, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0x49, +0x6b, 0x41, 0xaf, 0xdb, 0xf0, 0xec, 0xfb, 0x2c, +0xe3, 0xbe, 0x4a, 0x9b, 0x5f, 0xdc, 0xbf, 0x6e, +0xb7, 0x3d, 0xc9, 0xa8, 0x52, 0x0b, 0x18, 0x7a, +0x61, 0x31, 0x14, 0xba, 0x99, 0x21, 0x81, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0x7d, 0x53, 0x28, 0x40, 0xee, 0xa4, 0x15, +0x92, 0xc3, 0xc1, 0xda, 0xcb, 0x82, 0xc0, 0xeb, +0x1d, 0x52, 0xcb, 0x75, 0xe9, 0x94, 0xad, 0xa4, +0x43, 0x15, 0x01, 0xa4, 0xbb, 0x66, 0x84, 0xd0, +0x50, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0xe5, 0x03, 0x2f, 0x4b, 0x02, +0x34, 0x4a, 0x31, 0x93, 0xa3, 0x7a, 0x98, 0x2a, +0xc5, 0xca, 0xf0, 0x11, 0x5f, 0xb6, 0x98, 0x7c, +0x13, 0x23, 0x86, 0x46, 0xe8, 0x77, 0x0f, 0x46, +0x15, 0x47, 0x4b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0xf1, 0x5b, 0x36, +0x9d, 0x10, 0xca, 0xb5, 0xf1, 0x67, 0xbe, 0x4c, +0x26, 0xea, 0x9d, 0x51, 0x11, 0x3b, 0x52, 0xf9, +0x66, 0x53, 0x3e, 0x1c, 0x51, 0x70, 0x95, 0x1f, +0x9b, 0xe7, 0x89, 0x5e, 0xee, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0xf3, +0x5a, 0xbf, 0xbb, 0x5f, 0xeb, 0x69, 0x1e, 0x67, +0x3d, 0x28, 0x17, 0x95, 0x79, 0xff, 0xf5, 0xed, +0x41, 0xdc, 0xf3, 0xf9, 0xcc, 0x56, 0xf7, 0x2c, +0x07, 0x20, 0xa8, 0x89, 0xb5, 0x89, 0x3b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0x97, 0x4d, 0x14, 0x18, 0x97, 0x49, 0x78, +0xda, 0xa6, 0xfe, 0x3c, 0x49, 0x39, 0x27, 0x6f, +0xdd, 0x67, 0x49, 0x8c, 0x5b, 0xf7, 0xb5, 0xb5, +0x4e, 0x77, 0xe6, 0xd2, 0xee, 0x4b, 0xa1, 0x68, +0xb5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0x17, 0x98, 0xf6, 0x6b, 0x15, +0x67, 0x7c, 0x80, 0x73, 0x3b, 0x68, 0x5a, 0x7e, +0x55, 0xbf, 0x8d, 0x5f, 0x71, 0x1f, 0x01, 0x6c, +0xe8, 0xd0, 0xb0, 0x2e, 0x67, 0x71, 0x45, 0xbb, +0xdb, 0xeb, 0x3d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0x15, 0x5d, 0xd6, +0x56, 0x0f, 0xb0, 0xbb, 0x73, 0xdb, 0xad, 0xcb, +0xc2, 0xd8, 0x7c, 0xb3, 0xd9, 0x37, 0xfc, 0xa5, +0x73, 0xb5, 0xa5, 0x3d, 0x11, 0x44, 0xb7, 0xba, +0x63, 0x8f, 0x12, 0x73, 0xbc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0x77, +0x41, 0xce, 0xb3, 0x61, 0xb6, 0x4c, 0xa3, 0x55, +0x96, 0x66, 0xbb, 0x53, 0x34, 0x11, 0xea, 0x6f, +0x40, 0x6c, 0xfe, 0x29, 0xda, 0x74, 0x7e, 0x65, +0x58, 0x76, 0xfb, 0x93, 0xfe, 0x8c, 0xba, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0x16, 0xc6, 0xd3, 0x11, 0xdf, 0xd0, 0xca, +0x45, 0x67, 0x89, 0xe7, 0x12, 0x3b, 0x59, 0xee, +0x55, 0x5d, 0xa5, 0xaf, 0xd2, 0x2c, 0x8c, 0x51, +0xda, 0x7a, 0xc7, 0x65, 0x13, 0xc2, 0x19, 0x05, +0xff, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0x53, 0x4b, 0x85, 0x4f, 0x5a, +0xd5, 0x6e, 0x2f, 0x03, 0x3c, 0xc6, 0x2e, 0x79, +0x17, 0x6f, 0x53, 0x42, 0x0e, 0xa2, 0x67, 0xe9, +0x90, 0xf3, 0x19, 0x1d, 0x89, 0x1b, 0x4a, 0xb4, +0xdc, 0x67, 0x2d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0x4e, 0x04, 0xa3, +0xda, 0xbf, 0x06, 0x44, 0xcc, 0x94, 0x9a, 0x55, +0x02, 0xe7, 0xca, 0x77, 0xdc, 0x24, 0x0e, 0x69, +0xb0, 0xb4, 0xdc, 0xa1, 0x61, 0xd1, 0x4a, 0xed, +0xb8, 0xbb, 0xfe, 0x23, 0x0b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0xae, +0xd8, 0xa1, 0xfa, 0xca, 0xc3, 0xdc, 0xd8, 0x34, +0x19, 0xfe, 0x73, 0x84, 0xec, 0xc4, 0x0b, 0x99, +0xa2, 0x19, 0xc5, 0xe3, 0x40, 0x97, 0xbd, 0xa7, +0xc0, 0x8f, 0x31, 0x8b, 0xc0, 0x57, 0xd6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0xf7, 0x63, 0xc7, 0x08, 0x3a, 0x6c, 0x38, +0x2f, 0x0f, 0x1a, 0xcb, 0x0a, 0x52, 0x12, 0x46, +0x9a, 0xf9, 0xc8, 0xa4, 0x7e, 0xd6, 0x78, 0x64, +0x0d, 0x35, 0xd3, 0xea, 0xc3, 0x1a, 0x72, 0x9a, +0x38, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0xe1, 0xc7, 0x58, 0x82, 0x73, +0x65, 0x67, 0xa9, 0x6b, 0x1e, 0x19, 0xad, 0x1a, +0x41, 0xbc, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xa2, 0xa5, 0xa7, 0x63, +0x12, 0xa2, 0x19, 0xa9, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x7c, 0x34, 0x9a, +0x21, 0x04, 0xe9, 0xd6, 0xf4, 0xee, 0x8d, 0x0b, +0xbf, 0x76, 0x65, 0x97, 0x63, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2e, 0xd6, +0x33, 0x5f, 0x2b, 0x3e, 0x11, 0x3d, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0x2e, +0x93, 0xb5, 0x7e, 0xb6, 0xcd, 0x16, 0x24, 0x20, +0xc0, 0x1e, 0x00, 0x49, 0xd2, 0x64, 0x62, 0xb1, +0xf3, 0x7b, 0xc8, 0x7a, 0x88, 0xf5, 0x18, 0x2a, +0x6e, 0xe6, 0x30, 0xfa, 0x9e, 0x64, 0xea, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0xa3, 0x20, 0xe2, 0x4a, 0xb3, 0x9f, 0xbe, +0x90, 0x0b, 0x98, 0xa6, 0x84, 0xf4, 0xa8, 0x90, +0xef, 0x30, 0xe0, 0x2f, 0xd0, 0x9c, 0xcc, 0xdb, +0xfd, 0x89, 0xbf, 0x8e, 0x92, 0x54, 0x5f, 0xbb, +0xfa, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0x01, 0x01, 0xeb, 0x4f, 0x74, +0xbc, 0x02, 0x98, 0xe4, 0x48, 0xd1, 0x16, 0xb8, +0xbc, 0xae, 0xe1, 0xbc, 0x5b, 0x3a, 0xe0, 0x12, +0x4a, 0x5e, 0x69, 0xa7, 0x61, 0x72, 0xde, 0x41, +0x80, 0x4f, 0xec, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0xdf, 0x73, 0x04, +0xcf, 0x28, 0x37, 0x4e, 0x00, 0x72, 0x08, 0x13, +0x3e, 0xac, 0x3b, 0x93, 0x15, 0x30, 0xc8, 0xb4, +0xc4, 0x2d, 0x8b, 0xa2, 0x16, 0x20, 0x9a, 0xcd, +0xa5, 0x87, 0xae, 0xe9, 0x39, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0xa9, +0xd5, 0xa7, 0x69, 0xc7, 0x40, 0x02, 0xdd, 0x98, +0x33, 0xdc, 0x6d, 0x80, 0x39, 0xd4, 0xb4, 0xd1, +0x59, 0xa5, 0xe9, 0xf2, 0x04, 0x03, 0x92, 0x23, +0xd8, 0x7d, 0x95, 0x12, 0xdd, 0x2e, 0x15, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0x11, 0x9a, 0x59, 0x58, 0xef, 0x70, 0xbc, +0x55, 0x43, 0xe9, 0x35, 0xd6, 0x5a, 0xad, 0xe7, +0x00, 0xd8, 0x7e, 0x78, 0x66, 0xab, 0xc0, 0x2b, +0x37, 0x78, 0x42, 0x6b, 0xee, 0x7c, 0x91, 0x35, +0xac, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0xb1, 0x3f, 0x4d, 0x53, 0x1c, +0x75, 0x11, 0x61, 0xe4, 0x72, 0xcb, 0x5a, 0xe0, +0x7c, 0x64, 0x4a, 0x9b, 0xae, 0x22, 0xee, 0xbc, +0x4c, 0x94, 0xc0, 0x62, 0x7a, 0x78, 0x5c, 0xd1, +0xad, 0xb7, 0xe3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0x71, 0xf7, 0x71, +0x43, 0xe7, 0x78, 0x29, 0x11, 0x16, 0x26, 0x25, +0x5c, 0x7e, 0xda, 0x1d, 0x09, 0xe5, 0xc6, 0x7f, +0x72, 0x67, 0xfe, 0xa9, 0xdd, 0x96, 0xcb, 0xc8, +0xe7, 0xa0, 0xbd, 0xf9, 0x57, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x61, +0x3b, 0x89, 0xae, 0x24, 0xaa, 0xe9, 0xaf, 0x78, +0x38, 0x14, 0xc0, 0xc1, 0x11, 0x62, 0xb1, 0xbe, +0xbd, 0x7f, 0x26, 0x62, 0x87, 0x12, 0x07, 0x65, +0x3b, 0xfc, 0xa7, 0x81, 0x08, 0x36, 0x15, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0xaa, 0x65, 0x54, 0x83, 0x2c, 0xae, 0xb4, +0xae, 0x25, 0x92, 0xb1, 0xd9, 0xa4, 0x05, 0x0e, +0xfc, 0x88, 0x29, 0x2b, 0xa7, 0xe2, 0x79, 0xdc, +0x64, 0x4a, 0xd8, 0x03, 0x54, 0xcc, 0xe7, 0xe7, +0x0d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0xac, 0x44, 0x63, 0x64, 0x3a, +0x70, 0x34, 0x64, 0x85, 0x30, 0x04, 0xef, 0xc6, +0x56, 0x24, 0x1f, 0x18, 0x5e, 0x90, 0x3f, 0xee, +0x8d, 0x52, 0x38, 0xde, 0xcc, 0xd2, 0x5d, 0x59, +0x93, 0xe3, 0x20, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0x77, 0x45, 0xcc, +0xa9, 0xb3, 0x81, 0x36, 0x8b, 0x90, 0xfa, 0x90, +0xca, 0xd8, 0x9b, 0x71, 0x70, 0xb9, 0xd6, 0x46, +0x60, 0x98, 0xb9, 0x26, 0x38, 0x04, 0x6a, 0xee, +0x82, 0x63, 0xb1, 0xa8, 0x20, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x3b, +0xaf, 0x9e, 0xbc, 0xb9, 0xd6, 0x63, 0x46, 0x98, +0x61, 0xb7, 0xa6, 0x51, 0x3e, 0x15, 0xa0, 0xcc, +0x0d, 0xcf, 0x7a, 0xab, 0xbf, 0xe7, 0x65, 0x0c, +0x82, 0x8e, 0xf2, 0xdd, 0x77, 0x83, 0xf6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0xb2, 0x1e, 0x1b, 0x5c, 0x72, 0xde, 0xff, +0x08, 0x41, 0xed, 0xcd, 0x3b, 0x61, 0x5d, 0x59, +0xd7, 0x67, 0xdf, 0x3b, 0x41, 0x01, 0xb3, 0xfc, +0xe2, 0x5c, 0xd1, 0x89, 0x43, 0x15, 0x53, 0x3b, +0x3c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0x64, 0xf8, 0x09, 0x85, 0xb3, +0x3f, 0x0a, 0xef, 0xb5, 0x66, 0x52, 0x92, 0xa4, +0xe3, 0x7c, 0xfd, 0xfe, 0x8f, 0x0e, 0xb5, 0x3d, +0x17, 0x1a, 0x70, 0xbe, 0x0c, 0xbb, 0xb2, 0x62, +0x09, 0x79, 0x6d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0xed, 0xd3, 0xd9, +0xe6, 0x46, 0x45, 0x62, 0xaa, 0xf9, 0xc7, 0x8c, +0xc4, 0xe5, 0x41, 0x99, 0x77, 0x0a, 0xaf, 0xac, +0x72, 0xaf, 0x19, 0x85, 0x25, 0x82, 0x64, 0xf1, +0x57, 0x58, 0x22, 0xcb, 0x76, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0x1e, +0x14, 0x15, 0xf1, 0xe4, 0xcb, 0x6e, 0x59, 0x42, +0xb5, 0x1a, 0x30, 0xc5, 0x73, 0x1a, 0xb3, 0x5c, +0x20, 0xad, 0xdf, 0x7c, 0x9c, 0xf8, 0x85, 0xfd, +0xdb, 0xe9, 0x50, 0x9e, 0x2d, 0x6c, 0x5c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0x41, 0x7c, 0x2a, 0x2b, 0xc0, 0x9e, 0x32, +0xc3, 0x3a, 0x94, 0xb5, 0x89, 0x41, 0xc7, 0xb6, +0xb2, 0x07, 0x41, 0x9d, 0x1e, 0xfd, 0x85, 0x6d, +0xca, 0x75, 0x13, 0x03, 0x27, 0x79, 0x80, 0x47, +0x25, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x3a, 0x74, 0x2d, 0x8c, 0x3b, +0x1a, 0xeb, 0x18, 0x4a, 0x0e, 0xe0, 0x4b, 0xa9, +0xa1, 0x35, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x98, 0xd5, 0x15, 0x66, +0x3e, 0xac, 0x10, 0x23, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x12, 0x15, 0x20, +0x10, 0x27, 0x69, 0x55, 0x76, 0x4a, 0x44, 0x2f, +0x53, 0x5e, 0xd9, 0xbc, 0x77, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xe0, +0xd4, 0x43, 0x3d, 0xec, 0x73, 0x47, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0x5c, +0x21, 0x69, 0x00, 0x6a, 0xed, 0x13, 0xc3, 0x60, +0x3a, 0xcf, 0x64, 0xdb, 0xb3, 0x83, 0x8a, 0x97, +0x36, 0x25, 0xf2, 0xc6, 0xa9, 0x34, 0x68, 0x66, +0xdc, 0x5b, 0xaf, 0xc5, 0xc2, 0xc3, 0xfd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0xa5, 0xb6, 0xb8, 0xe2, 0xf0, 0x7c, 0x1a, +0xbd, 0x70, 0x82, 0x5a, 0xd0, 0xf8, 0x86, 0x4f, +0xd0, 0xbe, 0x97, 0x99, 0xe1, 0x08, 0x64, 0x1f, +0xd1, 0xb9, 0xd9, 0xc5, 0xbc, 0x15, 0x66, 0xcb, +0xc1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0x3e, 0x64, 0xf5, 0x73, 0x27, +0xf0, 0x0f, 0xbf, 0xc5, 0x87, 0x14, 0x04, 0x5d, +0x67, 0x7f, 0xa2, 0x3e, 0xb2, 0xe0, 0x24, 0x36, +0x37, 0xae, 0x84, 0x08, 0x7b, 0xad, 0xae, 0xef, +0x34, 0x16, 0x24, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0x94, 0x7d, 0x06, +0x0f, 0xae, 0x15, 0x13, 0x91, 0x3b, 0x0e, 0xca, +0x37, 0x7d, 0xcc, 0x06, 0xae, 0x11, 0x72, 0x6c, +0xa0, 0x16, 0xc7, 0xf3, 0x32, 0x93, 0xf3, 0xf7, +0x6a, 0x95, 0x58, 0xef, 0x83, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0x29, +0x4a, 0x53, 0x09, 0x9d, 0x72, 0xc1, 0x12, 0x90, +0x36, 0x77, 0xa3, 0xc4, 0x40, 0x9b, 0x86, 0x24, +0x16, 0x80, 0x1c, 0x85, 0x56, 0x2d, 0xd9, 0xe2, +0x94, 0x25, 0xca, 0xbb, 0xa5, 0x2c, 0x82, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0xdd, 0xdd, 0x74, 0x51, 0x7d, 0x11, 0xaa, +0x1d, 0xbe, 0x7c, 0xe2, 0x2d, 0xe2, 0x7e, 0x92, +0x32, 0xef, 0x20, 0x7d, 0x5a, 0x01, 0x71, 0xb0, +0x7e, 0x96, 0x71, 0xb7, 0x04, 0x8e, 0x24, 0x61, +0xcc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0xce, 0xef, 0x2c, 0x54, 0x99, +0x85, 0x70, 0x84, 0xe5, 0xe7, 0x43, 0x46, 0xaa, +0x08, 0x51, 0x78, 0x77, 0xbc, 0x77, 0x8b, 0x2e, +0x2a, 0x1e, 0xea, 0x4d, 0xba, 0x48, 0x85, 0x28, +0x51, 0x1f, 0x02, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0x51, 0xd2, 0xf2, +0xac, 0x06, 0x45, 0x02, 0xcc, 0xd2, 0xab, 0x68, +0xcb, 0x87, 0x2e, 0xc5, 0x76, 0xf9, 0xa4, 0x1f, +0xae, 0xcb, 0x71, 0x3c, 0xa1, 0xde, 0xee, 0x81, +0x8b, 0xa4, 0xd7, 0x69, 0x75, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x28, +0xdf, 0x11, 0x6e, 0x71, 0xd2, 0xef, 0x5c, 0x4b, +0x32, 0x4c, 0xb1, 0x30, 0xf6, 0xb9, 0xfd, 0xcd, +0x8a, 0x63, 0x4c, 0xb3, 0xeb, 0x1e, 0x8a, 0xee, +0xcb, 0xc4, 0x4a, 0x6f, 0x2e, 0x37, 0x21, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0xb4, 0x47, 0xf4, 0x18, 0x6a, 0x49, 0x52, +0xe9, 0x75, 0x79, 0x05, 0x87, 0x66, 0x53, 0xf7, +0x37, 0x53, 0xbd, 0xa8, 0xc5, 0x1c, 0x5c, 0x76, +0x05, 0x58, 0x3d, 0x8c, 0x25, 0x61, 0xd5, 0x67, +0x2a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0x78, 0x07, 0xc5, 0x56, 0x6c, +0x53, 0xed, 0x7b, 0x6f, 0xd2, 0x80, 0xef, 0x3a, +0x12, 0x98, 0xaf, 0x26, 0x09, 0x69, 0xad, 0x31, +0x2d, 0x8f, 0x5f, 0x84, 0x1e, 0xfd, 0xb8, 0xc4, +0x39, 0xba, 0x65, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0xee, 0xdc, 0x1f, +0x30, 0x7a, 0x08, 0xfb, 0x36, 0x99, 0x64, 0x78, +0x31, 0x2e, 0x6d, 0xd5, 0x88, 0x3a, 0x8a, 0x03, +0x40, 0x87, 0x29, 0x4c, 0x63, 0xcf, 0xe5, 0x68, +0x81, 0x86, 0xc0, 0x20, 0x50, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0xf2, +0xa4, 0x55, 0x2a, 0x43, 0x6a, 0x60, 0xaa, 0xae, +0x3f, 0x96, 0xc4, 0xd8, 0x16, 0x89, 0xe7, 0x72, +0x47, 0x8d, 0x9e, 0x25, 0xe8, 0x12, 0xa4, 0xe4, +0x2a, 0xd6, 0xe6, 0x30, 0x1f, 0x9b, 0xf0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0xa0, 0x8c, 0xc8, 0x9b, 0x03, 0x63, 0x4a, +0xfd, 0x4a, 0x7c, 0x70, 0x3a, 0xb1, 0xb0, 0xb1, +0xe8, 0xc7, 0x55, 0x6a, 0x68, 0xcc, 0xdf, 0x29, +0x1d, 0x06, 0xc8, 0x41, 0x89, 0x1b, 0xfa, 0x91, +0x5e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x30, 0xe8, 0x77, 0xb3, 0xd5, +0xf5, 0x16, 0x4b, 0x88, 0x78, 0x8f, 0x60, 0x82, +0x1d, 0x57, 0x80, 0xf6, 0x80, 0x0b, 0x3a, 0x9f, +0xac, 0xd0, 0xc8, 0xae, 0xbc, 0xd2, 0xe4, 0xb5, +0x07, 0xd3, 0x9b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0x8a, 0x32, 0x1b, +0x20, 0x41, 0x71, 0x03, 0x87, 0xbb, 0xda, 0xdc, +0x53, 0x68, 0x03, 0x10, 0x5b, 0xb2, 0x3b, 0xf8, +0x3d, 0xa0, 0x2f, 0x47, 0x7b, 0xe2, 0x51, 0x9d, +0xa8, 0x0a, 0x5a, 0x7c, 0x39, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0x3f, +0xb0, 0xfc, 0x2b, 0x05, 0xcd, 0x7d, 0x8b, 0x7e, +0x71, 0x80, 0xb0, 0xe1, 0x94, 0x4b, 0x83, 0x85, +0x72, 0x98, 0xcc, 0xd3, 0xc0, 0x2d, 0x33, 0xfd, +0x3b, 0x0c, 0x5a, 0xb5, 0x6c, 0xf8, 0x55, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0xa5, 0x6b, 0x54, 0x02, 0x24, 0xbe, 0xda, +0xdc, 0x8f, 0x21, 0x78, 0x1f, 0x35, 0x61, 0x07, +0x19, 0xe9, 0x51, 0xcb, 0xee, 0x8a, 0xb4, 0x68, +0xd5, 0x10, 0xe4, 0xd7, 0xab, 0x16, 0x47, 0x69, +0x23, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0x82, 0xdc, 0x84, 0xc3, 0xdc, +0xbf, 0x49, 0xb1, 0xa2, 0xf2, 0xc0, 0x52, 0x9b, +0xa0, 0x4c, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x12, 0x2f, 0x12, 0x1e, +0xbb, 0x0b, 0xc9, 0x56, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0x67, 0x3f, 0xbf, +0x86, 0x6b, 0x08, 0x9a, 0xc5, 0xbb, 0x35, 0x34, +0xd7, 0xf1, 0x97, 0x91, 0x5b, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x23, +0x74, 0x14, 0x74, 0x46, 0xd8, 0xfe, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0xa9, +0xb4, 0x19, 0x70, 0xcc, 0x44, 0x0e, 0x46, 0xb1, +0x0e, 0xd9, 0xc0, 0x6e, 0x06, 0x35, 0x38, 0xc6, +0xa2, 0x81, 0x3f, 0xcd, 0x68, 0x7b, 0x6d, 0x3d, +0xde, 0x62, 0x9a, 0xcd, 0xe5, 0xc1, 0xc6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0x87, 0x0e, 0x16, 0x8a, 0xed, 0xeb, 0x53, +0x74, 0xd8, 0xb6, 0x7e, 0x38, 0x88, 0x84, 0x04, +0x71, 0x1d, 0xc5, 0x29, 0x81, 0x50, 0x07, 0x19, +0x45, 0x62, 0x82, 0x63, 0x99, 0x21, 0xb4, 0x7d, +0x74, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0xdd, 0xbb, 0xd5, 0x45, 0xa8, +0x55, 0xd1, 0x78, 0x82, 0xcf, 0xd2, 0x3e, 0x39, +0x05, 0xdc, 0xe7, 0x8e, 0xad, 0x36, 0x12, 0x2d, +0xac, 0xce, 0x86, 0x86, 0x32, 0x2b, 0x99, 0x9d, +0x53, 0xeb, 0xc6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0x9f, 0x41, 0x86, +0x2d, 0xb9, 0x52, 0xce, 0xf1, 0x3f, 0x44, 0x2b, +0x07, 0x0a, 0x8a, 0xa6, 0x69, 0x8e, 0xd6, 0xb9, +0xb6, 0x88, 0x55, 0x82, 0x10, 0x45, 0x15, 0x5a, +0x0d, 0x84, 0x0e, 0x81, 0x51, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0xb6, +0xcd, 0xf3, 0x0a, 0xd3, 0xde, 0x84, 0xcc, 0xf7, +0xad, 0xf4, 0xdd, 0xfe, 0x20, 0x7a, 0x7b, 0x1b, +0x5a, 0x93, 0x62, 0x53, 0x03, 0x53, 0xa9, 0x5c, +0x0e, 0x17, 0x97, 0x1f, 0x53, 0xb9, 0x9b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0x23, 0x9c, 0xce, 0xdc, 0x5c, 0xb0, 0x96, +0xef, 0xc6, 0xb3, 0x99, 0x25, 0xfa, 0xec, 0x14, +0x57, 0xd4, 0x4a, 0xeb, 0x25, 0xf9, 0x93, 0x5d, +0xdf, 0x50, 0xe1, 0xcf, 0x36, 0x67, 0xb4, 0x0f, +0xdc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0x8a, 0x07, 0x91, 0x96, 0xfd, +0x50, 0xe9, 0x21, 0x48, 0xb4, 0x3b, 0xad, 0x2b, +0x1e, 0x12, 0xe9, 0x95, 0xf7, 0xff, 0x62, 0xd9, +0xc4, 0x6f, 0x0a, 0x97, 0x03, 0xb7, 0x27, 0x55, +0x48, 0x8b, 0x77, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0xdd, 0xa1, 0xe8, +0x65, 0xb1, 0x31, 0xdc, 0x8d, 0xd1, 0xc5, 0xf1, +0x53, 0x91, 0x53, 0x0b, 0xb4, 0x78, 0xe1, 0x5e, +0x64, 0x2d, 0xb3, 0x0d, 0x5e, 0x26, 0x44, 0x54, +0xac, 0xcf, 0x48, 0xd9, 0xe4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0xe8, +0x16, 0x75, 0x0a, 0x79, 0xd1, 0x78, 0x02, 0xcc, +0x21, 0xc2, 0xe7, 0xc6, 0x84, 0x6d, 0x15, 0xf8, +0x79, 0xbf, 0xad, 0xff, 0xe8, 0x30, 0x65, 0x77, +0xfd, 0xff, 0xa4, 0xab, 0x46, 0x80, 0xf6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x14, 0xa2, 0x74, 0xa9, 0x82, 0xae, 0x54, +0x2c, 0xf4, 0xc6, 0xff, 0xfe, 0xb7, 0xe8, 0xd0, +0xaa, 0xb8, 0x4c, 0xcb, 0x3d, 0xc9, 0x6f, 0x36, +0x38, 0x07, 0x91, 0x68, 0x32, 0x33, 0xdf, 0x6c, +0xe9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0x09, 0x81, 0xca, 0x96, 0x2d, +0xa7, 0x74, 0x20, 0x98, 0xee, 0x61, 0x03, 0xb9, +0xd6, 0x01, 0xd5, 0x5e, 0xf6, 0x80, 0xa3, 0xcb, +0xfe, 0xed, 0x35, 0xb4, 0xd9, 0x80, 0x67, 0x9a, +0x48, 0x5c, 0x3e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x56, 0x24, 0xc6, +0x4a, 0xd4, 0x88, 0x34, 0xb0, 0x74, 0x4d, 0x7f, +0xec, 0x89, 0x92, 0xe2, 0x78, 0x85, 0x71, 0xad, +0x6c, 0x82, 0x47, 0x04, 0xc7, 0xf7, 0x56, 0xd1, +0x8a, 0x95, 0x43, 0x26, 0xce, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0x4e, +0xb1, 0xd6, 0x03, 0x87, 0x62, 0xc6, 0xf7, 0xc2, +0x46, 0x74, 0x1e, 0xc5, 0x32, 0xd1, 0x8a, 0x4b, +0x01, 0xd2, 0x5c, 0x46, 0x81, 0xa4, 0x08, 0x5c, +0x7d, 0xa0, 0x77, 0x2d, 0xef, 0x6f, 0xb1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0x2f, 0x10, 0xe4, 0x28, 0x78, 0x0b, 0x00, +0x1b, 0xea, 0x0d, 0x88, 0xf0, 0x6b, 0xba, 0x4d, +0x14, 0x87, 0x5c, 0xe8, 0xc2, 0x0b, 0x37, 0x38, +0x2e, 0xf8, 0x28, 0xe5, 0x09, 0x34, 0x7c, 0x59, +0xbf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0x9d, 0x6e, 0x30, 0x6c, 0x6f, +0x5d, 0x5b, 0x0b, 0x33, 0xa6, 0x60, 0x3e, 0x96, +0x78, 0x38, 0x08, 0x20, 0x16, 0x0b, 0x30, 0x86, +0x30, 0x34, 0xb1, 0xd6, 0x34, 0x09, 0xf7, 0xc0, +0xa9, 0x8a, 0x52, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x9c, 0xcc, 0x97, +0x83, 0x0e, 0x90, 0xad, 0x28, 0x34, 0xab, 0xd6, +0xb3, 0x50, 0xc8, 0xdc, 0x65, 0x03, 0x78, 0xd0, +0xcd, 0x08, 0xee, 0xd1, 0xea, 0x04, 0x32, 0xd0, +0xc2, 0x67, 0x9c, 0xb5, 0x69, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0x83, +0x1d, 0xe4, 0xab, 0xd9, 0xc4, 0xe2, 0xb4, 0x56, +0xdc, 0xf7, 0x73, 0xaa, 0xf3, 0xdf, 0xca, 0x3b, +0x09, 0xab, 0x06, 0xba, 0x52, 0xd8, 0x02, 0x42, +0xdd, 0xff, 0x0f, 0x4c, 0x59, 0xd8, 0xb4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0x65, 0xaf, 0xf8, 0x48, 0x41, 0x76, 0x10, +0x66, 0x1d, 0xe8, 0x79, 0x45, 0xc0, 0x88, 0xf0, +0x1b, 0xe9, 0x70, 0xcd, 0xf2, 0x80, 0x6c, 0x02, +0x7d, 0xa6, 0x52, 0x4b, 0x0e, 0xd7, 0x9a, 0x33, +0x8c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0xf9, 0xf4, 0x3c, 0x79, 0xc1, +0x18, 0xb0, 0x0e, 0x00, 0x43, 0x3e, 0xde, 0x17, +0xbe, 0x3d, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x8f, 0x80, 0x62, 0x53, +0x6b, 0xba, 0xdd, 0xb7, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0x3a, 0x1a, 0x14, +0xa9, 0x25, 0x11, 0x3f, 0x5a, 0xf4, 0x5a, 0x3b, +0x7b, 0xcb, 0x73, 0xad, 0x3c, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb5, 0x02, +0xf6, 0xef, 0x86, 0x59, 0xe6, 0xec, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0xc1, +0x1b, 0xf2, 0x79, 0xd4, 0xdf, 0x89, 0x3b, 0xe7, +0xc4, 0xfd, 0xeb, 0x97, 0xf8, 0xa5, 0xfd, 0xf2, +0x40, 0xfa, 0xd3, 0xa0, 0xff, 0x7a, 0xef, 0x44, +0xe5, 0x99, 0x7c, 0x93, 0x70, 0x12, 0xdd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0x64, 0xbb, 0x65, 0x37, 0x20, 0xd8, 0xc8, +0x44, 0x42, 0x4d, 0xa8, 0x9d, 0x41, 0xca, 0xf9, +0x6d, 0x62, 0x8d, 0x6e, 0x70, 0x06, 0x56, 0xec, +0xf8, 0x17, 0xab, 0xd0, 0xba, 0xcb, 0xed, 0x3d, +0x95, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0x88, 0x5f, 0xda, 0x0c, 0x15, +0xf3, 0x5c, 0xa3, 0xc3, 0x80, 0xb9, 0xd4, 0xb4, +0x6c, 0x38, 0x4b, 0x48, 0x9a, 0xb4, 0x7f, 0xc5, +0x0e, 0x60, 0x9e, 0x4e, 0xa7, 0x33, 0x09, 0xc9, +0x00, 0x2f, 0x06, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x7d, 0xee, 0xc9, +0x55, 0x86, 0x57, 0x53, 0xb9, 0xb0, 0x13, 0x86, +0x71, 0x96, 0x54, 0x51, 0x2f, 0xe2, 0xf5, 0xde, +0x50, 0x53, 0xae, 0x3d, 0xab, 0xdd, 0x89, 0x65, +0xde, 0x48, 0x21, 0x65, 0xdf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0xe8, +0xc1, 0xf2, 0xa3, 0x1c, 0x64, 0xda, 0x72, 0x1a, +0xb0, 0x4b, 0x32, 0x6f, 0x61, 0xca, 0x0c, 0xc9, +0xf5, 0xbf, 0xe2, 0xc2, 0x45, 0xd9, 0x95, 0x25, +0x9e, 0xb4, 0x86, 0xda, 0x4e, 0xe2, 0xe4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0x87, 0x59, 0x89, 0x6d, 0x14, 0xe9, 0xde, +0x7b, 0xc3, 0x34, 0x2b, 0x08, 0xf9, 0x23, 0x5c, +0xf0, 0x8a, 0xac, 0x61, 0x94, 0xdb, 0x64, 0xa4, +0x50, 0x43, 0x1e, 0x71, 0xb6, 0x14, 0xfc, 0xc6, +0xeb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0xad, 0xf7, 0x4a, 0x28, 0x22, +0x8f, 0xc7, 0xe4, 0x33, 0x44, 0xd5, 0xec, 0x64, +0xfc, 0x2f, 0x95, 0x7f, 0xec, 0x1f, 0x3a, 0x5a, +0x54, 0xe0, 0xb1, 0x8b, 0xbc, 0xe5, 0x4d, 0x18, +0xfb, 0x5a, 0x1a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0xc7, 0xd6, 0x63, +0x1f, 0xb8, 0x70, 0x31, 0x7a, 0x77, 0x3c, 0xc2, +0x08, 0xbd, 0x88, 0xa6, 0xa9, 0xbd, 0x78, 0xa5, +0x0a, 0x8a, 0x5f, 0x6f, 0xaf, 0x08, 0x14, 0x77, +0xef, 0x99, 0xc7, 0x6b, 0x60, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0x7b, +0xed, 0xb7, 0x63, 0xd8, 0xe5, 0xd2, 0x25, 0x02, +0xd5, 0x94, 0xa4, 0xa1, 0x56, 0x9c, 0x78, 0xf3, +0x25, 0x62, 0x1b, 0xdd, 0x90, 0x5d, 0x16, 0xac, +0x8e, 0xb4, 0x59, 0x0b, 0x0b, 0x45, 0x99, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x52, 0x36, 0xd3, 0x02, 0x35, 0x39, 0x4f, +0x46, 0xdc, 0xb8, 0x00, 0x14, 0xef, 0x3b, 0xf1, +0x6a, 0xd2, 0x36, 0x95, 0xcf, 0xd0, 0x23, 0xfe, +0xa0, 0x75, 0x64, 0x29, 0xdd, 0x5c, 0x1a, 0x0d, +0x2e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0xe9, 0x66, 0x1e, 0xd7, 0x9b, +0xc3, 0xb8, 0x03, 0x4c, 0x07, 0xd6, 0x6a, 0xcb, +0x6c, 0x93, 0xc7, 0xaa, 0x23, 0x7d, 0x69, 0xa2, +0xb2, 0x42, 0x37, 0xae, 0x17, 0xd2, 0x38, 0x87, +0x22, 0xa6, 0xb7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0xc5, 0x59, 0xf3, +0x4a, 0x7e, 0x63, 0x02, 0xe3, 0x54, 0xc9, 0xe4, +0x63, 0x17, 0x81, 0x0d, 0xa0, 0x20, 0xed, 0xc9, +0x08, 0x2a, 0x6f, 0x5f, 0x78, 0x3e, 0x86, 0x41, +0x73, 0x27, 0x96, 0x9c, 0xed, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0xd1, +0x5a, 0x07, 0x80, 0x9c, 0xcc, 0xf3, 0x2e, 0x48, +0x2d, 0x41, 0x73, 0x47, 0x7e, 0xa1, 0x58, 0xa0, +0xfc, 0x4d, 0x19, 0xd0, 0x77, 0x37, 0xb3, 0xa1, +0x9c, 0x1b, 0xf6, 0x74, 0xa0, 0xdb, 0x30, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0x0f, 0x75, 0x23, 0x82, 0xc1, 0x81, 0x40, +0xa7, 0x4e, 0xdd, 0x15, 0x94, 0xcd, 0xe8, 0x27, +0x55, 0x78, 0xef, 0xa0, 0xee, 0x8c, 0xf1, 0x83, +0x55, 0xa8, 0xa9, 0x6c, 0x72, 0x4a, 0x41, 0x24, +0x80, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0xa3, 0xd5, 0xba, 0x97, 0x0f, +0x49, 0x53, 0x3c, 0x5b, 0x02, 0x4d, 0x81, 0x44, +0xfb, 0x0c, 0xed, 0x00, 0x54, 0x4f, 0x9d, 0x5c, +0x18, 0x18, 0x7e, 0x06, 0x68, 0x0f, 0xb7, 0x89, +0x5e, 0x84, 0x7b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0x15, 0x63, 0xb0, +0x85, 0x4c, 0xa9, 0x58, 0x0b, 0xf8, 0x10, 0xbb, +0x54, 0x4b, 0xa3, 0x62, 0x47, 0x59, 0x5d, 0xc2, +0xba, 0x94, 0x2a, 0xdd, 0x42, 0xa9, 0xd4, 0xaf, +0x1a, 0xb8, 0xe8, 0xfc, 0x6f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0x53, +0x65, 0x40, 0xa0, 0x7b, 0xba, 0x6f, 0x03, 0x4b, +0x1b, 0xba, 0x35, 0x06, 0x66, 0x9b, 0xda, 0x4c, +0x49, 0xd3, 0x14, 0xbd, 0x06, 0x05, 0x27, 0x11, +0xa3, 0xa6, 0xb9, 0x21, 0xd3, 0x4a, 0x23, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x77, 0x02, 0xd6, 0x14, 0x0c, 0x9b, 0xc8, +0xf0, 0x9b, 0x2b, 0x40, 0x47, 0x1b, 0xe6, 0xb5, +0xbc, 0xcf, 0x8f, 0xf1, 0x5d, 0x38, 0xb9, 0xde, +0x19, 0x20, 0x0a, 0xa4, 0x6d, 0x0c, 0xc8, 0xf9, +0x58, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0x46, 0x3d, 0xf6, 0x8f, 0x27, +0xa7, 0x4c, 0x2d, 0x41, 0x82, 0x52, 0x3e, 0xc5, +0x3a, 0xa1, 0xce, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xf8, 0xa4, 0xe2, 0x53, +0x0a, 0x15, 0xe9, 0x86, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0x66, 0x7f, 0x67, +0x0e, 0x30, 0x82, 0x2f, 0xf5, 0xfe, 0x35, 0xf1, +0xbe, 0xf0, 0x6a, 0x9c, 0xe5, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x97, +0x30, 0x15, 0xb0, 0x0d, 0xb2, 0x31, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0x42, +0x2c, 0x2f, 0xf8, 0x9d, 0x65, 0x19, 0xa8, 0x4b, +0x7e, 0x36, 0xbb, 0xcd, 0x30, 0xb3, 0x02, 0x56, +0x0a, 0xf1, 0x40, 0xe2, 0xa7, 0xa2, 0x54, 0x0e, +0x57, 0x22, 0x59, 0xcf, 0x5a, 0xdf, 0x30, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0x44, 0xeb, 0x61, 0x82, 0xf9, 0xa1, 0xf4, +0xfa, 0xde, 0x6a, 0x04, 0xf2, 0xce, 0xca, 0xd0, +0x31, 0x98, 0x40, 0x36, 0xf9, 0xcb, 0x6a, 0x55, +0x44, 0x07, 0x71, 0xd6, 0x47, 0x30, 0x32, 0x9f, +0x21, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0xf3, 0xd7, 0xb7, 0xfd, 0x21, +0xd8, 0xfc, 0x62, 0x4b, 0xa2, 0x42, 0xd5, 0xd9, +0x2c, 0x77, 0x36, 0x58, 0xad, 0xc6, 0x9e, 0x11, +0xd4, 0x44, 0xe7, 0x0f, 0x58, 0x52, 0xa1, 0x50, +0x91, 0x2c, 0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0xb2, 0xde, 0x3a, +0xb5, 0x75, 0xa4, 0x32, 0x98, 0x90, 0xf7, 0x5f, +0x5d, 0xdf, 0x8a, 0x17, 0x31, 0x13, 0x35, 0xd2, +0x0f, 0xd6, 0xf6, 0x15, 0xc1, 0x89, 0xb9, 0xd4, +0xa5, 0x22, 0x62, 0x72, 0x65, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0x94, +0x3d, 0xd4, 0x3e, 0xa3, 0x44, 0x89, 0xdd, 0xc3, +0x83, 0xee, 0xb4, 0x7f, 0x6e, 0x84, 0xe2, 0xe5, +0x61, 0xa2, 0x6f, 0xaa, 0xb3, 0x2d, 0xae, 0xc1, +0x13, 0xa9, 0xca, 0xfc, 0x83, 0xad, 0x79, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0xdf, 0x5f, 0x76, 0xbf, 0x62, 0xab, 0x30, +0x14, 0xfa, 0x82, 0xbe, 0x70, 0xb6, 0x23, 0x85, +0xc8, 0xfc, 0x0c, 0x64, 0x42, 0x3a, 0x60, 0xb3, +0x80, 0x28, 0x24, 0x6e, 0xf7, 0x0a, 0x83, 0x12, +0x8e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0x5a, 0x52, 0x0e, 0x60, 0x44, +0x05, 0xc7, 0xf9, 0x9f, 0x8e, 0x69, 0xf1, 0x06, +0x71, 0x9e, 0x13, 0xe1, 0xc6, 0x17, 0xc4, 0x03, +0x41, 0x5b, 0x43, 0xc3, 0xd6, 0xdb, 0xce, 0x48, +0x07, 0x8c, 0x99, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x24, 0xaf, 0x40, +0xa7, 0xf6, 0x24, 0x13, 0xdb, 0xfd, 0xbf, 0x1a, +0xf0, 0x21, 0x87, 0x45, 0x03, 0x65, 0xf9, 0x6d, +0x29, 0xf1, 0xdd, 0x7f, 0xab, 0x2b, 0x32, 0x6d, +0xc9, 0x0d, 0x73, 0x74, 0x0b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0x1f, +0x4d, 0x25, 0x49, 0xff, 0xe5, 0xfb, 0xa7, 0xa7, +0x75, 0x21, 0x24, 0xf5, 0xf1, 0xdb, 0x8a, 0xc6, +0x7d, 0x30, 0xec, 0x6d, 0xc4, 0xea, 0xcf, 0xca, +0x32, 0x09, 0x6c, 0x15, 0x8d, 0x2c, 0x69, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0x03, 0x12, 0xae, 0xc3, 0xda, 0x3b, 0x30, +0x77, 0xb7, 0x6d, 0x1f, 0xca, 0x11, 0x39, 0xaa, +0x8e, 0xd2, 0x27, 0x4b, 0x9c, 0xba, 0xf7, 0xbc, +0x80, 0x19, 0x23, 0xeb, 0x0e, 0xc6, 0xc4, 0x98, +0x99, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0xef, 0xc3, 0x25, 0x5e, 0x65, +0x7b, 0xdb, 0xae, 0xb1, 0xf5, 0x3d, 0x72, 0x53, +0xce, 0x93, 0x35, 0x6a, 0xcb, 0xf4, 0x16, 0x97, +0x36, 0x9a, 0x95, 0xc0, 0xf6, 0xbc, 0x76, 0x77, +0x9b, 0xa3, 0x32, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0x9a, 0x77, 0x8c, +0x24, 0x35, 0x77, 0x93, 0x52, 0xa4, 0x73, 0xf1, +0x1c, 0x6a, 0xc4, 0xe1, 0xd2, 0xde, 0x17, 0x08, +0xd9, 0x70, 0xb3, 0x5e, 0x1c, 0x34, 0x73, 0x5f, +0xc3, 0x77, 0xda, 0xe9, 0x40, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0xd0, +0x65, 0xd6, 0x31, 0xe0, 0xed, 0x0a, 0x07, 0x61, +0x8c, 0x8c, 0x97, 0xb3, 0x45, 0x8f, 0x1f, 0x74, +0x7d, 0x6e, 0x73, 0x42, 0x22, 0x37, 0x56, 0xec, +0xdf, 0x9e, 0xe1, 0xb1, 0x1a, 0x09, 0x2e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0x37, 0xfe, 0x81, 0x52, 0xaa, 0xed, 0xa5, +0xde, 0x8b, 0x91, 0x5c, 0x31, 0x5b, 0x89, 0xdc, +0xc0, 0xd6, 0xeb, 0xe9, 0xba, 0xe9, 0xae, 0x95, +0x26, 0xd9, 0xd1, 0x99, 0x73, 0x27, 0xa0, 0x50, +0x81, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x8a, 0xb1, 0xf2, 0x42, 0xd9, +0x51, 0xf8, 0x62, 0x50, 0x9c, 0x4a, 0x6e, 0xc1, +0xfb, 0x1b, 0x8f, 0x6d, 0x4a, 0xcf, 0x8b, 0x9e, +0xe2, 0x2e, 0x89, 0x17, 0x9e, 0xe5, 0x57, 0xfd, +0xdd, 0x0f, 0x24, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0x32, 0xdd, 0x3e, +0x56, 0xd7, 0x18, 0xf8, 0x88, 0x47, 0xe3, 0xa2, +0xe2, 0x6c, 0xf5, 0x0a, 0x9c, 0x28, 0x14, 0xe1, +0x30, 0xaa, 0x56, 0xd5, 0x52, 0x48, 0x09, 0x9a, +0x72, 0x60, 0x71, 0xa7, 0xf7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0x43, +0x57, 0xff, 0xe5, 0x09, 0x09, 0xc0, 0x8b, 0xf6, +0x50, 0xe8, 0x61, 0x72, 0xd3, 0xc6, 0x1d, 0x16, +0xcd, 0x64, 0x12, 0x24, 0x13, 0xd2, 0x21, 0x34, +0x83, 0xa5, 0xa7, 0x50, 0x7a, 0x4a, 0xc7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0xe8, 0xc8, 0xbc, 0x59, 0x5b, 0xfc, 0x1c, +0xa4, 0x5e, 0xfc, 0x18, 0xe2, 0xd0, 0xd3, 0x72, +0xc7, 0x65, 0xf9, 0x71, 0x32, 0x45, 0x58, 0x31, +0x4c, 0x98, 0xc3, 0x1e, 0xd0, 0x39, 0x22, 0xeb, +0x55, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0x3b, 0x72, 0x3d, 0x97, 0xfd, +0xac, 0xde, 0xca, 0xce, 0x38, 0x06, 0x8b, 0x89, +0xb6, 0xc8, 0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x6e, 0xb5, 0xd1, 0x89, +0x10, 0x19, 0xaf, 0x97, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0xf1, 0xc7, 0xb6, +0xa7, 0x34, 0x5f, 0xc8, 0xf0, 0xb6, 0x92, 0xda, +0x72, 0xf9, 0x7b, 0x5a, 0xa0, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x2c, +0x04, 0x76, 0xf3, 0x67, 0x2a, 0xb7, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0x8a, +0xfa, 0xb0, 0x96, 0x25, 0xa0, 0xe0, 0x32, 0xe3, +0xa4, 0x51, 0xa9, 0xed, 0xc4, 0x46, 0x50, 0x6f, +0x3d, 0x4e, 0x4c, 0xc2, 0xcf, 0xfa, 0x8b, 0xb8, +0x4a, 0x68, 0xec, 0x63, 0x64, 0x7e, 0x5d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x04, 0x3b, 0x20, 0xb7, 0xea, 0xc6, 0xcd, +0x54, 0x8e, 0x0a, 0xfe, 0x80, 0x6f, 0xdf, 0x98, +0x51, 0x93, 0x74, 0x90, 0xeb, 0xe2, 0x2b, 0x32, +0xce, 0x90, 0x31, 0x17, 0x4c, 0xa9, 0x1e, 0xa7, +0x59, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0xe1, 0x6b, 0xcd, 0x23, 0xfa, +0x5e, 0xba, 0xe9, 0x8f, 0xc2, 0xdd, 0x1a, 0xd9, +0xb4, 0x07, 0xe9, 0x35, 0x49, 0x24, 0x36, 0x0a, +0x72, 0x09, 0xb8, 0xb7, 0xb3, 0x28, 0x78, 0x4b, +0x19, 0xa4, 0xf4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0x8b, 0x9a, 0x9c, +0x62, 0xa5, 0x2e, 0x54, 0x5d, 0x82, 0x31, 0x47, +0x4c, 0x3d, 0x57, 0xea, 0x9d, 0xc7, 0x93, 0xf0, +0x3d, 0x54, 0xee, 0xbf, 0x83, 0x11, 0xed, 0xa2, +0x07, 0xab, 0x8b, 0xb5, 0x7a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0x55, +0x74, 0x8c, 0x59, 0x8b, 0x97, 0x95, 0x81, 0x6c, +0x75, 0xcd, 0xdb, 0x9f, 0x5b, 0xd0, 0x1b, 0xe0, +0x67, 0x3f, 0xed, 0xd9, 0xab, 0xcb, 0x23, 0x0f, +0xb6, 0x6c, 0x9c, 0xa6, 0x8e, 0x34, 0x66, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0xb3, 0x4f, 0x49, 0xda, 0x72, 0x7d, 0xa4, +0xe7, 0x0e, 0xb9, 0xfb, 0x94, 0x93, 0xe1, 0x07, +0x09, 0xae, 0xf6, 0x23, 0xff, 0xc5, 0xfb, 0xca, +0x54, 0x1a, 0x0d, 0x81, 0x24, 0x2e, 0xc2, 0x00, +0xa7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0xa0, 0xaa, 0xa1, 0x1b, 0xae, +0x20, 0x0f, 0x50, 0xf2, 0xe0, 0x93, 0x91, 0x46, +0x78, 0x22, 0x68, 0x9d, 0x8b, 0x2c, 0x62, 0xc1, +0x93, 0xaf, 0x43, 0x0e, 0x45, 0x4b, 0x04, 0x84, +0xdf, 0x40, 0xa7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0x72, 0xf6, 0x07, +0xe2, 0x41, 0x39, 0xef, 0x20, 0xde, 0x5a, 0xda, +0x69, 0xd1, 0x47, 0x1e, 0x0c, 0xf5, 0x9d, 0xe1, +0x24, 0xbc, 0xa7, 0x91, 0x96, 0x7f, 0x7e, 0xd7, +0xa0, 0x3a, 0x41, 0xa0, 0x4c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0x00, +0xb9, 0xf8, 0x7e, 0x72, 0xc3, 0x07, 0x9f, 0x3f, +0x28, 0x7b, 0x12, 0x5d, 0x00, 0xff, 0x5f, 0x01, +0x41, 0x11, 0xc2, 0xd6, 0xba, 0xe7, 0xff, 0xc5, +0xb4, 0x9e, 0x00, 0xf8, 0x11, 0x11, 0x4c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0x20, 0xfe, 0x8a, 0x4f, 0x89, 0x02, 0xac, +0xc6, 0x1e, 0x64, 0x21, 0x57, 0x3c, 0x3b, 0xd6, +0x03, 0xf7, 0xd5, 0x17, 0xb1, 0x38, 0xb8, 0x4d, +0x3f, 0xa6, 0x1d, 0xb5, 0xcf, 0x72, 0xfd, 0x80, +0x96, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0x5a, 0x35, 0x4a, 0x65, 0x64, +0x36, 0x3d, 0xf5, 0x3a, 0x23, 0x92, 0x3c, 0x27, +0xce, 0xf2, 0x12, 0xac, 0x91, 0x76, 0x7c, 0x70, +0x6a, 0xe8, 0x19, 0x0a, 0xc0, 0x4c, 0x27, 0xb7, +0x98, 0x76, 0xe1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0xec, 0xce, 0x23, +0xcd, 0xd8, 0x78, 0x55, 0x16, 0x02, 0x98, 0x6d, +0xe0, 0x6b, 0xd1, 0xc8, 0x4d, 0x9f, 0x94, 0x10, +0x72, 0x67, 0x8d, 0xde, 0xaf, 0xef, 0x7c, 0x85, +0xb9, 0xbe, 0xf8, 0x9c, 0x3d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0x8b, +0x94, 0x9f, 0x6e, 0x59, 0xed, 0x19, 0x00, 0xdd, +0x22, 0x56, 0x5b, 0xb8, 0x71, 0xed, 0x8c, 0xce, +0x2c, 0x8b, 0x04, 0x62, 0x07, 0x4a, 0xad, 0x75, +0xfa, 0x7a, 0x76, 0x1a, 0xb5, 0xb6, 0x7a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0xbd, 0x2c, 0xaf, 0x5e, 0x16, 0xfe, 0x58, +0xb0, 0x0c, 0x7a, 0x1c, 0xb5, 0xb9, 0x0d, 0x8e, +0x95, 0x8e, 0x04, 0x7a, 0x8e, 0xa1, 0x65, 0xdb, +0xe2, 0xce, 0xcf, 0x7e, 0xd7, 0x73, 0xfb, 0xfb, +0x72, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0x61, 0xa5, 0x0a, 0xb7, 0x6b, +0xc9, 0xd7, 0xb8, 0x02, 0xb5, 0xce, 0xcc, 0xe8, +0x0d, 0x64, 0x8d, 0xf0, 0xfa, 0x1c, 0xd7, 0x77, +0x8a, 0x9b, 0x6a, 0x5e, 0x72, 0xe5, 0xc2, 0x75, +0x8e, 0xb4, 0xb0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0xee, 0xae, 0x29, +0x8c, 0x92, 0x9f, 0x8c, 0xcd, 0x2e, 0x97, 0xcd, +0x13, 0xd1, 0x50, 0x7d, 0x00, 0x5f, 0x71, 0x6c, +0x20, 0xa2, 0xc6, 0x4a, 0x09, 0xda, 0x01, 0x0e, +0x9d, 0x06, 0x95, 0xfa, 0xa1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0x74, +0x87, 0x1f, 0xed, 0x2e, 0x17, 0x38, 0x2c, 0x36, +0xdf, 0x7f, 0x04, 0xd7, 0x50, 0xc6, 0x4e, 0x71, +0x8c, 0xf4, 0xe8, 0x65, 0x6c, 0x53, 0xae, 0x86, +0x07, 0xe9, 0x23, 0x50, 0x73, 0xa0, 0x81, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0x1d, 0x6b, 0x7e, 0x46, 0x0e, 0x05, 0x98, +0xb9, 0x3b, 0xb6, 0xb5, 0x7e, 0x5e, 0x1f, 0x2c, +0x3e, 0x3e, 0x1a, 0xaa, 0x6b, 0xf2, 0x26, 0x0c, +0x75, 0x66, 0x7b, 0x15, 0xc1, 0x6b, 0x7e, 0x3f, +0xf3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0xfb, 0x95, 0x75, 0x50, 0xe4, +0x5a, 0x39, 0x13, 0x1f, 0x32, 0x44, 0xaf, 0xaa, +0x84, 0x1b, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x98, 0xcd, 0x02, 0xf5, +0x7e, 0x0a, 0x85, 0x91, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0x24, 0xa7, 0xe8, +0xc1, 0xc3, 0x7c, 0x1e, 0xe2, 0x53, 0xb5, 0xf1, +0x3e, 0xa2, 0x66, 0x83, 0x3a, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdd, 0x97, +0x60, 0xa4, 0x83, 0x67, 0x3e, 0x1e, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x3b, +0xdf, 0x0a, 0x09, 0x1e, 0x8c, 0xb2, 0xb7, 0xed, +0x1c, 0x21, 0x8e, 0x38, 0x3d, 0x34, 0x9a, 0x5d, +0x8e, 0x71, 0x83, 0x44, 0x7b, 0xbe, 0xd5, 0xbc, +0x34, 0x4f, 0xaf, 0x85, 0xf9, 0x5e, 0x1b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0xfb, 0x03, 0x65, 0x5d, 0xdf, 0x4b, 0x82, +0x0b, 0x6e, 0xcf, 0x96, 0xeb, 0x2a, 0x6d, 0x3a, +0x79, 0x7c, 0x5f, 0x07, 0xb9, 0x0c, 0xdf, 0xbc, +0x7d, 0xa1, 0x2b, 0x50, 0x93, 0x87, 0x7f, 0x67, +0x73, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0xb1, 0x60, 0x88, 0x14, 0x48, +0xf8, 0x16, 0x2b, 0xf1, 0x72, 0xa6, 0x34, 0xe3, +0xa3, 0x50, 0x4d, 0x63, 0xe6, 0x21, 0xbb, 0x7e, +0xaf, 0x7a, 0x59, 0x59, 0x75, 0x1a, 0x33, 0x66, +0x8d, 0x8d, 0xb6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0x40, 0xd0, 0x76, +0x58, 0x9d, 0xff, 0xa0, 0x86, 0x56, 0xa4, 0x1d, +0x06, 0x17, 0x92, 0x2e, 0x7a, 0x7b, 0xde, 0x2c, +0x72, 0xd0, 0x1c, 0xe7, 0x19, 0x29, 0xca, 0xb4, +0x30, 0xa5, 0xfe, 0xf5, 0xfd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0x4f, +0x1b, 0x87, 0xb6, 0x84, 0x3b, 0x2a, 0x36, 0x55, +0xb4, 0x12, 0xfa, 0x96, 0xe2, 0x61, 0x19, 0xf0, +0x27, 0x42, 0x68, 0x74, 0xc5, 0x49, 0x00, 0x85, +0x11, 0x35, 0x37, 0x72, 0x5c, 0x18, 0x7f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0xc0, 0x35, 0xb9, 0xe3, 0x63, 0xcf, 0xdb, +0x8d, 0x4a, 0x43, 0x96, 0xaf, 0x81, 0xe6, 0xdf, +0x12, 0xf1, 0xd1, 0xbd, 0xac, 0xb0, 0x09, 0xb0, +0xe0, 0xef, 0xc6, 0xd5, 0xd0, 0x73, 0xae, 0xd7, +0x31, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0x61, 0x5e, 0xa3, 0x04, 0xa6, +0x58, 0x09, 0x33, 0xda, 0x13, 0x16, 0xb8, 0x3a, +0x1d, 0xf8, 0x61, 0x7c, 0xf1, 0xd7, 0x13, 0xf5, +0x15, 0x0c, 0xa1, 0x65, 0xfe, 0xf3, 0x81, 0x3d, +0xec, 0x67, 0xeb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x7d, 0x30, 0xe9, +0xf1, 0x39, 0xde, 0xd1, 0xe9, 0xd1, 0x10, 0x1a, +0x82, 0x2a, 0x7a, 0xe8, 0xdb, 0x8c, 0x5d, 0x56, +0xc6, 0x0f, 0x78, 0xfd, 0xc8, 0x2e, 0xef, 0x7e, +0xec, 0x8e, 0x11, 0x4b, 0xd8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0xfc, +0xf9, 0x5a, 0xf7, 0xbc, 0x75, 0xb2, 0x30, 0xe0, +0x96, 0x76, 0x0a, 0xdb, 0x9c, 0xb1, 0xb7, 0x28, +0xee, 0x43, 0x91, 0x1c, 0xeb, 0x8c, 0xee, 0x06, +0x3b, 0xfe, 0x46, 0x5d, 0x4a, 0xa6, 0x35, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0x54, 0xca, 0xc2, 0xd9, 0xde, 0x8b, 0x2c, +0xd7, 0x1d, 0x3a, 0xbd, 0xde, 0x65, 0x0f, 0x3a, +0xba, 0x34, 0x3c, 0x18, 0x14, 0x11, 0x4f, 0x09, +0xfc, 0xe5, 0xe0, 0x73, 0xdc, 0xac, 0x6c, 0xfe, +0xd4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0x79, 0xe6, 0x41, 0x5d, 0xd7, +0x04, 0x6f, 0xba, 0xa2, 0xff, 0x3c, 0x98, 0x9c, +0x7d, 0x46, 0x9f, 0x5a, 0x22, 0xa6, 0x09, 0x77, +0x8c, 0x56, 0x47, 0x1a, 0x2c, 0xcb, 0xf6, 0x4d, +0xf0, 0x2b, 0xe1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x42, 0xc6, 0x77, +0xe1, 0x0c, 0xa2, 0x7e, 0x0e, 0xe8, 0xc9, 0x41, +0xf9, 0xd8, 0xa3, 0x28, 0xa0, 0x47, 0xb4, 0x56, +0x37, 0x25, 0xca, 0x62, 0xb5, 0x49, 0x10, 0x9b, +0xab, 0x5f, 0xf7, 0x8f, 0x87, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0x28, +0xf7, 0x3a, 0x07, 0xe7, 0x48, 0xc5, 0x37, 0xeb, +0x01, 0xd2, 0x79, 0xf8, 0xc7, 0x6a, 0xf7, 0xc6, +0xf5, 0x4d, 0xce, 0x92, 0xe1, 0x22, 0xb9, 0xdb, +0xd8, 0x23, 0x71, 0xcc, 0x4c, 0x5e, 0x48, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0xe8, 0x3b, 0xdd, 0x4c, 0xac, 0x7b, 0x82, +0x82, 0x68, 0xab, 0xe5, 0x88, 0xd9, 0x40, 0x1a, +0xfc, 0x1c, 0xb7, 0xeb, 0xc5, 0xaa, 0x3f, 0x24, +0x5e, 0x0c, 0x45, 0x42, 0x0b, 0x34, 0x81, 0xa3, +0xca, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0x9d, 0xa1, 0x18, 0xc2, 0x5d, +0x44, 0x70, 0xd4, 0xc8, 0x19, 0x66, 0xc1, 0x87, +0x5a, 0xec, 0x58, 0xe4, 0x97, 0x0f, 0xe7, 0x07, +0x8c, 0x30, 0x34, 0x57, 0x87, 0xc3, 0x87, 0xcd, +0x57, 0xb0, 0xe1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0xf8, 0x93, 0x6c, +0x02, 0x6c, 0xb3, 0xea, 0xbd, 0x71, 0x95, 0xb9, +0xb5, 0x39, 0xe9, 0x7c, 0xc0, 0x1e, 0x1f, 0x2f, +0x76, 0x8b, 0xf8, 0x9c, 0x6c, 0x1e, 0x76, 0xf4, +0xfe, 0xbf, 0x97, 0xa8, 0x76, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x5d, +0xc6, 0x7c, 0xbe, 0x3c, 0x33, 0x33, 0x32, 0x35, +0xce, 0xd9, 0x81, 0xcf, 0x42, 0x43, 0xb7, 0x08, +0x52, 0x5e, 0x7d, 0x20, 0xe7, 0x94, 0x96, 0x82, +0x66, 0xd0, 0x2b, 0x21, 0xfb, 0xbc, 0xe2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0x2e, 0xb9, 0x76, 0xda, 0x66, 0x18, 0x3f, +0x70, 0x78, 0x63, 0x29, 0xa2, 0x02, 0x68, 0x9d, +0x85, 0x50, 0x0a, 0x57, 0xb9, 0x9f, 0xa2, 0xa9, +0x07, 0x76, 0x6e, 0x21, 0x39, 0xf3, 0xd7, 0x92, +0x12, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0x44, 0x48, 0xdb, 0x84, 0x62, +0x76, 0xd3, 0xdb, 0x43, 0xa0, 0x01, 0x8e, 0x40, +0xc3, 0x02, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x30, 0x9d, 0x6f, 0x0d, +0x40, 0x3d, 0x98, 0xcb, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0xb0, 0xd4, 0xf4, +0xd8, 0xc1, 0x25, 0x50, 0x9a, 0xbd, 0xa9, 0xd7, +0x0e, 0xc0, 0xe9, 0x30, 0x10, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, +0xfb, 0x95, 0xcb, 0x7f, 0x6f, 0xc0, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0x5e, +0x3c, 0xfe, 0xa0, 0x54, 0x2c, 0x0f, 0xbc, 0x9a, +0x9b, 0x1a, 0xb7, 0x5f, 0xd6, 0x15, 0x55, 0x22, +0xec, 0xb8, 0x09, 0xb9, 0x4a, 0x1f, 0x0c, 0xbe, +0x1b, 0x15, 0xad, 0xf5, 0xfd, 0x00, 0xa1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0x0d, 0x58, 0x49, 0x0a, 0x36, 0x60, 0x6b, +0xe8, 0xb4, 0x3f, 0x11, 0xf1, 0x3d, 0x44, 0xee, +0xe4, 0xb0, 0x96, 0x66, 0x66, 0x3f, 0x2a, 0xf4, +0xe2, 0xba, 0x99, 0x0a, 0x7d, 0x29, 0x41, 0xe1, +0x28, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0x98, 0x41, 0x29, 0x3c, 0xf9, +0xcf, 0xf9, 0x36, 0xe0, 0xd6, 0x5f, 0x04, 0xe2, +0x6a, 0x91, 0xa8, 0x95, 0x5f, 0x21, 0x71, 0xae, +0x92, 0x52, 0xb0, 0xc2, 0x5c, 0xc5, 0x40, 0x4a, +0xd2, 0x80, 0x57, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0xc1, 0x67, 0x7f, +0x5b, 0x41, 0x3d, 0xdf, 0x1b, 0xba, 0xcb, 0xa8, +0x71, 0xd5, 0x26, 0xa5, 0xa5, 0x78, 0x1c, 0x41, +0x0b, 0x68, 0xa3, 0x03, 0xa8, 0x29, 0xde, 0xad, +0xbd, 0x16, 0xdf, 0xbe, 0xd4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0x7d, +0x8b, 0x5c, 0x36, 0x09, 0x7d, 0xc5, 0xaa, 0xef, +0x8a, 0x51, 0x0a, 0xad, 0x78, 0xda, 0xbf, 0x08, +0xd5, 0x1b, 0xd2, 0xe1, 0x97, 0xc6, 0x65, 0xfa, +0xd5, 0xcf, 0xe6, 0x94, 0x8e, 0x61, 0x66, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0x63, 0x49, 0xb4, 0x13, 0xab, 0xbe, 0x30, +0x2d, 0xd2, 0x08, 0x2e, 0x11, 0x2a, 0x0b, 0x70, +0x1b, 0xa5, 0xbc, 0x13, 0x63, 0x18, 0xcc, 0xca, +0x08, 0x37, 0x36, 0x2c, 0xba, 0xbe, 0xd3, 0xae, +0x83, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0xe6, 0xc2, 0x85, 0x4a, 0x08, +0xc5, 0x42, 0xa5, 0x73, 0xb0, 0x2d, 0x46, 0x21, +0x57, 0xed, 0x9f, 0xe3, 0x7a, 0x64, 0xea, 0xc4, +0x63, 0xec, 0x55, 0x78, 0xd6, 0x2a, 0x74, 0x27, +0x7c, 0xb6, 0x68, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0x45, 0xf8, 0xf8, +0x57, 0x21, 0xa7, 0x03, 0x48, 0xca, 0x9c, 0xb8, +0x4b, 0x1f, 0x5f, 0x28, 0x5b, 0x2a, 0x2f, 0xfb, +0x4a, 0xf4, 0xe2, 0x73, 0xf3, 0x05, 0xba, 0xd8, +0xbd, 0x47, 0xfc, 0x1e, 0x09, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0x83, +0x81, 0x5c, 0xe6, 0xb8, 0xac, 0x6e, 0x7a, 0x40, +0x10, 0x51, 0x97, 0x18, 0x95, 0xac, 0x9b, 0xf5, +0xa7, 0x68, 0x87, 0x2e, 0xe4, 0x99, 0xb4, 0x40, +0x37, 0xdf, 0xa9, 0xed, 0x41, 0xe2, 0x08, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0x45, 0x04, 0x9b, 0x92, 0x51, 0xb9, 0xcf, +0xd8, 0x8f, 0x85, 0xa2, 0xec, 0x73, 0xdf, 0x8b, +0x9f, 0x01, 0x04, 0x9c, 0xf6, 0xcf, 0x1a, 0x85, +0x86, 0x50, 0xf5, 0x1c, 0x5c, 0x60, 0x42, 0xba, +0x42, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0x26, 0x47, 0x90, 0x53, 0xd0, +0x10, 0x95, 0x8a, 0x50, 0x92, 0x76, 0x98, 0x94, +0x75, 0xa2, 0xbe, 0x81, 0xab, 0xcc, 0xfe, 0xcc, +0x7e, 0x26, 0xcf, 0xe9, 0x5e, 0x1c, 0xf1, 0x66, +0xaf, 0x94, 0xd3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x29, 0xcd, 0x83, +0x42, 0x74, 0x15, 0xc4, 0x30, 0x24, 0x59, 0x93, +0x3f, 0x4a, 0xd3, 0x23, 0xc6, 0x34, 0x60, 0xb5, +0xc7, 0x97, 0xfe, 0x6d, 0x7a, 0x7a, 0x91, 0xaf, +0x48, 0x55, 0xa3, 0x58, 0x83, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x12, +0xf4, 0xd7, 0x12, 0xd8, 0xe9, 0x09, 0x7a, 0xca, +0x55, 0x6d, 0x28, 0x1c, 0x01, 0xaa, 0xdf, 0x1d, +0xf0, 0x48, 0x49, 0x45, 0xb0, 0xd8, 0x96, 0xea, +0x85, 0x0d, 0x83, 0x1e, 0x56, 0xb7, 0x22, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x2f, 0xdb, 0x45, 0x13, 0xfd, 0xa6, 0x59, +0xeb, 0x1b, 0x5e, 0xbe, 0x61, 0x68, 0xd6, 0x72, +0x0c, 0xa7, 0xe6, 0x85, 0xba, 0x62, 0xcb, 0x64, +0xe4, 0x54, 0xb2, 0x97, 0x06, 0xae, 0x2c, 0xf3, +0xc0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0xea, 0x73, 0x80, 0xad, 0xa5, +0x0f, 0x45, 0xe9, 0x25, 0x82, 0x36, 0x1b, 0x35, +0x55, 0xef, 0x93, 0xa8, 0x0e, 0xd1, 0x80, 0x74, +0x11, 0xc4, 0xaa, 0xaf, 0x07, 0x3b, 0xa2, 0x79, +0xe7, 0xe7, 0xc9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0x56, 0xd9, 0x5f, +0x32, 0x6b, 0xf1, 0x2f, 0x45, 0x66, 0x67, 0x55, +0xba, 0x92, 0xeb, 0x12, 0xdc, 0xab, 0x4e, 0x9a, +0x47, 0x87, 0xe0, 0xee, 0xed, 0x47, 0x98, 0x47, +0xa0, 0x74, 0x85, 0x30, 0xfb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0xa5, +0xb7, 0x33, 0x4b, 0x8c, 0xe5, 0xd8, 0xf7, 0x2a, +0x68, 0x9d, 0xb0, 0xd2, 0x65, 0x59, 0x9b, 0x3d, +0x0a, 0x77, 0x02, 0xda, 0x1a, 0xac, 0x7e, 0xc2, +0x12, 0xa3, 0x11, 0x11, 0xf2, 0xbf, 0xbe, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0x2a, 0x87, 0x1d, 0x69, 0xb3, 0x0d, 0x7e, +0xc8, 0x47, 0xc9, 0xf4, 0x52, 0x98, 0x5c, 0xd3, +0x26, 0xaa, 0x84, 0x9b, 0xb5, 0x04, 0xe0, 0x3e, +0xc4, 0x75, 0x51, 0xed, 0xc7, 0x84, 0x8c, 0x26, +0xb2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0xfe, 0x38, 0xaf, 0x7d, 0xdc, +0xe0, 0x00, 0x82, 0x20, 0x6d, 0xc5, 0xec, 0x27, +0xfe, 0x26, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xb8, 0x0d, 0x0f, 0x22, +0x78, 0x6d, 0xb6, 0xec, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0xe3, 0x08, 0x1d, +0x7c, 0xfe, 0x97, 0xa8, 0xf8, 0x28, 0xfb, 0xed, +0xb3, 0x05, 0x5d, 0x6c, 0xa6, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x6c, +0x96, 0x4c, 0x14, 0x8e, 0xa0, 0x89, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0xa5, +0xe0, 0x7a, 0x67, 0xc1, 0xfa, 0xbe, 0xb1, 0x51, +0xd4, 0x72, 0x0c, 0xa8, 0x2b, 0x53, 0xc2, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0xe5, 0x8d, 0xc5, 0xc1, 0xe5, 0x0e, 0xec, 0x78, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x00 +}; +EXPORT_SYMBOL_GPL(BOOT_FLASH_FW_PASS2); diff --git a/include/linux/mfd/max77705-private.h b/include/linux/mfd/max77705-private.h new file mode 100644 index 000000000000..ab9f7a2ad0db --- /dev/null +++ b/include/linux/mfd/max77705-private.h @@ -0,0 +1,445 @@ +/* + * max77705-private.h - Voltage regulator driver for the Maxim 77705 + * + * Copyright (C) 2016 Samsung Electrnoics + * Insun Choi + * + * 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, see . + */ + +#ifndef __LINUX_MFD_MAX77705_PRIV_H +#define __LINUX_MFD_MAX77705_PRIV_H + +#include +#include +#include +#include +#include +#include +#include +#include +#define MAX77705_I2C_ADDR (0x92) +#define MAX77705_REG_INVALID (0xff) + +#define MAX77705_IRQSRC_CHG (1 << 0) +#define MAX77705_IRQSRC_TOP (1 << 1) +#define MAX77705_IRQSRC_FG (1 << 2) +#define MAX77705_IRQSRC_USBC (1 << 3) +#define MAX77705_ELRN (1 << 0) +#define MAX77705_FILT_EMPTY (1 << 2) + +enum max77705_hw_rev { + MAX77705_PASS1 = 0x1, + MAX77705_PASS2 = 0x2, + MAX77705_PASS3 = 0x3, + MAX77705_PASS4 = 0x4, + MAX77705_PASS5 = 0x5, +}; + +enum max77705_reg { + /* Slave addr = 0xCC */ + /* PMIC Top-Level Registers */ + MAX77705_PMIC_REG_PMICID1 = 0x00, + MAX77705_PMIC_REG_PMICREV = 0x01, + MAX77705_PMIC_REG_MAINCTRL1 = 0x02, + MAX77705_PMIC_REG_INTSRC = 0x22, + MAX77705_PMIC_REG_INTSRC_MASK = 0x23, + MAX77705_PMIC_REG_SYSTEM_INT = 0x24, + MAX77705_PMIC_REG_RESERVED_25 = 0x25, + MAX77705_PMIC_REG_SYSTEM_INT_MASK = 0x26, + MAX77705_PMIC_REG_RESERVED_27 = 0x27, + MAX77705_PMIC_REG_RESERVED_28 = 0x28, + MAX77705_PMIC_REG_RESERVED_29 = 0x29, + MAX77705_PMIC_REG_BOOSTCONTROL1 = 0x4C, + MAX77705_PMIC_REG_BSTOUT_MASK = 0x03, + MAX77705_PMIC_REG_BOOSTCONTROL2 = 0x4F, + MAX77705_PMIC_REG_FORCE_EN_MASK = 0x08, + MAX77705_PMIC_REG_SW_RESET = 0x50, + MAX77705_PMIC_REG_USBC_RESET = 0x51, + +#if 0 + MAX77705_PMIC_REG_LSCNFG = 0x2B, + MAX77705_PMIC_REG_RESERVED_2C = 0x2C, + MAX77705_PMIC_REG_RESERVED_2D = 0x2D, +#endif + + /* Haptic motor driver Registers */ + MAX77705_PMIC_REG_MCONFIG = 0x10, + MAX77705_PMIC_REG_MCONFIG2 = 0x11, + + MAX77705_CHG_REG_INT = 0xB0, + MAX77705_CHG_REG_INT_MASK = 0xB1, + MAX77705_CHG_REG_INT_OK = 0xB2, + MAX77705_CHG_REG_DETAILS_00 = 0xB3, + MAX77705_CHG_REG_DETAILS_01 = 0xB4, + MAX77705_CHG_REG_DETAILS_02 = 0xB5, + MAX77705_CHG_REG_DTLS_03 = 0xB6, + MAX77705_CHG_REG_CNFG_00 = 0xB7, + MAX77705_CHG_REG_CNFG_01 = 0xB8, + MAX77705_CHG_REG_CNFG_02 = 0xB9, + MAX77705_CHG_REG_CNFG_03 = 0xBA, + MAX77705_CHG_REG_CNFG_04 = 0xBB, + MAX77705_CHG_REG_CNFG_05 = 0xBC, + MAX77705_CHG_REG_CNFG_06 = 0xBD, + MAX77705_CHG_REG_CNFG_07 = 0xBE, + MAX77705_CHG_REG_CNFG_08 = 0xBF, + MAX77705_CHG_REG_CNFG_09 = 0xC0, + MAX77705_CHG_REG_CNFG_10 = 0xC1, + MAX77705_CHG_REG_CNFG_11 = 0xC2, + MAX77705_CHG_REG_CNFG_12 = 0xC3, + MAX77705_CHG_REG_CNFG_13 = 0xC4, + MAX77705_CHG_REG_CNFG_14 = 0xC5, + MAX77705_CHG_REG_SAFEOUT_CTRL = 0xC6, + + MAX77705_PMIC_REG_END, +}; + +/* Slave addr = 0x6C : Fuelgauge */ +enum max77705_fuelgauge_reg { + STATUS_REG = 0x00, + VALRT_THRESHOLD_REG = 0x01, + TALRT_THRESHOLD_REG = 0x02, + SALRT_THRESHOLD_REG = 0x03, + REMCAP_REP_REG = 0x05, + SOCREP_REG = 0x06, + TEMPERATURE_REG = 0x08, + VCELL_REG = 0x09, + TIME_TO_EMPTY_REG = 0x11, + FULLSOCTHR_REG = 0x13, + CURRENT_REG = 0x0A, + AVG_CURRENT_REG = 0x0B, + SOCMIX_REG = 0x0D, + SOCAV_REG = 0x0E, + REMCAP_MIX_REG = 0x0F, + FULLCAP_REG = 0x10, + QRTABLE00_REG = 0x12, + RFAST_REG = 0x15, + AVR_TEMPERATURE_REG = 0x16, + CYCLES_REG = 0x17, + DESIGNCAP_REG = 0x18, + AVR_VCELL_REG = 0x19, + TIME_TO_FULL_REG = 0x20, + CONFIG_REG = 0x1D, + ICHGTERM_REG = 0x1E, + REMCAP_AV_REG = 0x1F, + QRTABLE10_REG = 0x22, + FULLCAP_NOM_REG = 0x23, + LEARN_CFG_REG = 0x28, + FILTER_CFG_REG = 0x29, + MISCCFG_REG = 0x2B, + CGAIN_REG = 0x2E, + COFFSET_REG = 0x2F, + QRTABLE20_REG = 0x32, + FULLCAP_REP_REG = 0x35, + RCOMP_REG = 0x38, + TEMPCO_REG = 0x39, + VEMPTY_REG = 0x3A, + FSTAT_REG = 0x3D, + DISCHARGE_THRESHOLD_REG = 0x40, + QRTABLE30_REG = 0x42, + ISYS_REG = 0x43, + DQACC_REG = 0x45, + DPACC_REG = 0x46, + AVGISYS_REG = 0x4B, + QH_REG = 0x4D, + VSYS_REG = 0xB1, + TALRTTH2_REG = 0xB2, + /* "not used REG(0xB2)" is for checking fuelgague init result. */ + FG_INIT_RESULT_REG = TALRTTH2_REG, + VBYP_REG = 0xB3, + CONFIG2_REG = 0xBB, + IIN_REG = 0xD0, + OCV_REG = 0xEE, + VFOCV_REG = 0xFB, + VFSOC_REG = 0xFF, + + MAX77705_FG_END, +}; + +#define MAX77705_REG_MAINCTRL1_BIASEN (1 << 7) + +/* Slave addr = 0x4A: USBC */ +enum max77705_usbc_reg { + MAX77705_USBC_REG_UIC_HW_REV = 0x00, + MAX77705_USBC_REG_UIC_FW_REV = 0x01, + MAX77705_USBC_REG_UIC_INT = 0x02, + MAX77705_USBC_REG_CC_INT = 0x03, + MAX77705_USBC_REG_PD_INT = 0x04, + MAX77705_USBC_REG_VDM_INT = 0x05, + MAX77705_USBC_REG_USBC_STATUS1 = 0x06, + MAX77705_USBC_REG_USBC_STATUS2 = 0x07, + MAX77705_USBC_REG_BC_STATUS = 0x08, + MAX77705_USBC_REG_CC_STATUS0 = 0x0a, + MAX77705_USBC_REG_CC_STATUS1 = 0x0b, + MAX77705_USBC_REG_PD_STATUS0 = 0x0c, + MAX77705_USBC_REG_PD_STATUS1 = 0x0d, + MAX77705_USBC_REG_UIC_INT_M = 0x0e, + MAX77705_USBC_REG_CC_INT_M = 0x0f, + MAX77705_USBC_REG_PD_INT_M = 0x10, + MAX77705_USBC_REG_VDM_INT_M = 0x11, + + MAX77705_USBC_REG_AP_DATAOUT0 = 0x21, + MAX77705_USBC_REG_AP_DATAOUT1 = 0x22, + MAX77705_USBC_REG_AP_DATAOUT2 = 0x23, + MAX77705_USBC_REG_AP_DATAOUT3 = 0x24, + MAX77705_USBC_REG_AP_DATAOUT4 = 0x25, + MAX77705_USBC_REG_AP_DATAOUT5 = 0x26, + MAX77705_USBC_REG_AP_DATAOUT6 = 0x27, + MAX77705_USBC_REG_AP_DATAOUT7 = 0x28, + MAX77705_USBC_REG_AP_DATAOUT8 = 0x29, + MAX77705_USBC_REG_AP_DATAOUT9 = 0x2a, + MAX77705_USBC_REG_AP_DATAOUT10 = 0x2b, + MAX77705_USBC_REG_AP_DATAOUT11 = 0x2c, + MAX77705_USBC_REG_AP_DATAOUT12 = 0x2d, + MAX77705_USBC_REG_AP_DATAOUT13 = 0x2e, + MAX77705_USBC_REG_AP_DATAOUT14 = 0x2f, + MAX77705_USBC_REG_AP_DATAOUT15 = 0x30, + MAX77705_USBC_REG_AP_DATAOUT16 = 0x31, + MAX77705_USBC_REG_AP_DATAOUT17 = 0x32, + MAX77705_USBC_REG_AP_DATAOUT18 = 0x33, + MAX77705_USBC_REG_AP_DATAOUT19 = 0x34, + MAX77705_USBC_REG_AP_DATAOUT20 = 0x35, + MAX77705_USBC_REG_AP_DATAOUT21 = 0x36, + MAX77705_USBC_REG_AP_DATAOUT22 = 0x37, + MAX77705_USBC_REG_AP_DATAOUT23 = 0x38, + MAX77705_USBC_REG_AP_DATAOUT24 = 0x39, + MAX77705_USBC_REG_AP_DATAOUT25 = 0x3a, + MAX77705_USBC_REG_AP_DATAOUT26 = 0x3b, + MAX77705_USBC_REG_AP_DATAOUT27 = 0x3c, + MAX77705_USBC_REG_AP_DATAOUT28 = 0x3d, + MAX77705_USBC_REG_AP_DATAOUT29 = 0x3e, + MAX77705_USBC_REG_AP_DATAOUT30 = 0x3f, + MAX77705_USBC_REG_AP_DATAOUT31 = 0x40, + MAX77705_USBC_REG_AP_DATAOUT32 = 0x41, + + MAX77705_USBC_REG_AP_DATAIN0 = 0x51, + MAX77705_USBC_REG_AP_DATAIN1 = 0x52, + MAX77705_USBC_REG_AP_DATAIN2 = 0x53, + MAX77705_USBC_REG_AP_DATAIN3 = 0x54, + MAX77705_USBC_REG_AP_DATAIN4 = 0x55, + MAX77705_USBC_REG_AP_DATAIN5 = 0x56, + MAX77705_USBC_REG_AP_DATAIN6 = 0x57, + MAX77705_USBC_REG_AP_DATAIN7 = 0x58, + MAX77705_USBC_REG_AP_DATAIN8 = 0x59, + MAX77705_USBC_REG_AP_DATAIN9 = 0x5a, + MAX77705_USBC_REG_AP_DATAIN10 = 0x5b, + MAX77705_USBC_REG_AP_DATAIN11 = 0x5c, + MAX77705_USBC_REG_AP_DATAIN12 = 0x5d, + MAX77705_USBC_REG_AP_DATAIN13 = 0x5e, + MAX77705_USBC_REG_AP_DATAIN14 = 0x5f, + MAX77705_USBC_REG_AP_DATAIN15 = 0x60, + MAX77705_USBC_REG_AP_DATAIN16 = 0x61, + MAX77705_USBC_REG_AP_DATAIN17 = 0x62, + MAX77705_USBC_REG_AP_DATAIN18 = 0x63, + MAX77705_USBC_REG_AP_DATAIN19 = 0x64, + MAX77705_USBC_REG_AP_DATAIN20 = 0x65, + MAX77705_USBC_REG_AP_DATAIN21 = 0x66, + MAX77705_USBC_REG_AP_DATAIN22 = 0x67, + MAX77705_USBC_REG_AP_DATAIN23 = 0x68, + MAX77705_USBC_REG_AP_DATAIN24 = 0x69, + MAX77705_USBC_REG_AP_DATAIN25 = 0x6a, + MAX77705_USBC_REG_AP_DATAIN26 = 0x6b, + MAX77705_USBC_REG_AP_DATAIN27 = 0x6c, + MAX77705_USBC_REG_AP_DATAIN28 = 0x6d, + MAX77705_USBC_REG_AP_DATAIN29 = 0x6e, + MAX77705_USBC_REG_AP_DATAIN30 = 0x6f, + MAX77705_USBC_REG_AP_DATAIN31 = 0x70, + MAX77705_USBC_REG_AP_DATAIN32 = 0x71, + + MAX77705_USBC_REG_END, +}; + +/* Slave addr = 0x94: RGB LED */ +enum max77705_led_reg { + MAX77705_RGBLED_REG_LEDEN = 0x30, + MAX77705_RGBLED_REG_LED0BRT = 0x31, + MAX77705_RGBLED_REG_LED1BRT = 0x32, + MAX77705_RGBLED_REG_LED2BRT = 0x33, + MAX77705_RGBLED_REG_LED3BRT = 0x34, + MAX77705_RGBLED_REG_LEDRMP = 0x36, + MAX77705_RGBLED_REG_LEDBLNK = 0x38, + MAX77705_LED_REG_END, +}; + +enum max77705_irq_source { + SYS_INT = 0, + CHG_INT, + FUEL_INT, + USBC_INT, + CC_INT, + PD_INT, + VDM_INT, + VIR_INT, + MAX77705_IRQ_GROUP_NR, +}; + +enum max77705_irq { + /* PMIC; TOPSYS */ + MAX77705_SYSTEM_IRQ_BSTEN_INT, + MAX77705_SYSTEM_IRQ_SYSUVLO_INT, + MAX77705_SYSTEM_IRQ_SYSOVLO_INT, + MAX77705_SYSTEM_IRQ_TSHDN_INT, + MAX77705_SYSTEM_IRQ_TM_INT, + + /* PMIC; Charger */ + MAX77705_CHG_IRQ_BYP_I, + MAX77705_CHG_IRQ_BATP_I, + MAX77705_CHG_IRQ_BAT_I, + MAX77705_CHG_IRQ_CHG_I, + MAX77705_CHG_IRQ_WCIN_I, + MAX77705_CHG_IRQ_CHGIN_I, + MAX77705_CHG_IRQ_AICL_I, + + /* Fuelgauge */ + MAX77705_FG_IRQ_ALERT, + + /* USBC */ + MAX77705_USBC_IRQ_APC_INT, + + /* CC */ + MAX77705_CC_IRQ_VCONNCOP_INT, + MAX77705_CC_IRQ_VSAFE0V_INT, + MAX77705_CC_IRQ_DETABRT_INT, + MAX77705_CC_IRQ_VCONNSC_INT, + MAX77705_CC_IRQ_CCPINSTAT_INT, + MAX77705_CC_IRQ_CCISTAT_INT, + MAX77705_CC_IRQ_CCVCNSTAT_INT, + MAX77705_CC_IRQ_CCSTAT_INT, + + MAX77705_USBC_IRQ_VBUS_INT, + MAX77705_USBC_IRQ_VBADC_INT, + MAX77705_USBC_IRQ_DCD_INT, + MAX77705_USBC_IRQ_FAKVB_INT, + MAX77705_USBC_IRQ_CHGT_INT, + MAX77705_USBC_IRQ_UIDADC_INT, + + /* + * USBC: SYSMSG INT should be after CC INT + * because of 2 times of CC Sync INT at WDT reset + */ + MAX77705_USBC_IRQ_SYSM_INT, + + /* PD */ + MAX77705_PD_IRQ_PDMSG_INT, + MAX77705_PD_IRQ_PS_RDY_INT, + MAX77705_PD_IRQ_DATAROLE_INT, + MAX77705_PD_IRQ_SSACCI_INT, + MAX77705_PD_IRQ_FCTIDI_INT, + + + /* VDM */ + MAX77705_IRQ_VDM_DISCOVER_ID_INT, + MAX77705_IRQ_VDM_DISCOVER_SVIDS_INT, + MAX77705_IRQ_VDM_DISCOVER_MODES_INT, + MAX77705_IRQ_VDM_ENTER_MODE_INT, + MAX77705_IRQ_VDM_DP_STATUS_UPDATE_INT, + MAX77705_IRQ_VDM_DP_CONFIGURE_INT, + MAX77705_IRQ_VDM_ATTENTION_INT, + + /* VIRTUAL */ + MAX77705_VIR_IRQ_ALTERROR_INT, + + MAX77705_IRQ_NR, +}; + +struct max77705_dev { + struct device *dev; + struct i2c_client *i2c; /* 0xCC; Haptic, PMIC */ + struct i2c_client *charger; /* 0xD2; Charger */ + struct i2c_client *fuelgauge; /* 0x6C; Fuelgauge */ + struct i2c_client *muic; /* 0x4A; MUIC */ + struct i2c_client *debug; /* 0xC4; Debug */ + struct mutex i2c_lock; + + int type; + + int irq; + int irq_base; + int irq_gpio; + bool wakeup; + bool blocking_waterevent; + int device_product_id; + struct mutex irqlock; + int irq_masks_cur[MAX77705_IRQ_GROUP_NR]; + int irq_masks_cache[MAX77705_IRQ_GROUP_NR]; + +#if IS_ENABLED(CONFIG_CCIC_MAX77705) + u8 HW_Revision; + u8 FW_Revision; + u8 FW_Minor_Revision; + u8 FW_Product_ID; + struct work_struct fw_work; + struct workqueue_struct *fw_workqueue; + struct completion fw_completion; + int fw_update_state; + int fw_size; +#endif + + /* pmic VER/REV register */ + u8 pmic_rev; /* pmic Rev */ + u8 pmic_ver; /* pmic version */ + + u8 cc_booting_complete; + + int set_altmode_en; + int enable_nested_irq; + u8 usbc_irq; + + bool suspended; + wait_queue_head_t suspend_wait; + + /* QCOM_IFPMIC_SUSPEND */ + wait_queue_head_t queue_empty_wait_q; + int doing_irq; + int is_usbc_queue; + bool (*check_usbc_opcode_queue) (void); + + void (*check_pdmsg)(void *data, u8 pdmsg); + void *usbc_data; + + struct max77705_platform_data *pdata; +}; + +enum max77705_types { + TYPE_MAX77705, +}; + +extern int max77705_irq_init(struct max77705_dev *max77705); +extern void max77705_irq_exit(struct max77705_dev *max77705); + +/* MAX77705 shared i2c API function */ +extern int max77705_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest); +extern int max77705_bulk_read(struct i2c_client *i2c, u8 reg, int count, + u8 *buf); +extern int max77705_write_reg(struct i2c_client *i2c, u8 reg, u8 value); +extern int max77705_write_reg_nolock(struct i2c_client *i2c, u8 reg, u8 value); +extern int max77705_bulk_write(struct i2c_client *i2c, u8 reg, int count, + u8 *buf); +extern int max77705_write_word(struct i2c_client *i2c, u8 reg, u16 value); +extern int max77705_read_word(struct i2c_client *i2c, u8 reg); + +extern int max77705_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask); + +/* MAX77705 check muic path function */ +extern bool is_muic_usb_path_ap_usb(void); +extern bool is_muic_usb_path_cp_usb(void); + +/* for charger api */ +extern void max77705_hv_muic_charger_init(void); +extern int max77705_usbc_fw_update(struct max77705_dev *max77705, const u8 *fw_bin, int fw_bin_len, int enforce_do); +extern void max77705_usbc_fw_setting(struct max77705_dev *max77705, int enforce_do); +extern void max77705_register_pdmsg_func(struct max77705_dev *max77705, + void (*check_pdmsg)(void *, u8), void *data); +#endif /* __LINUX_MFD_MAX77705_PRIV_H */ + diff --git a/include/linux/mfd/max77705.h b/include/linux/mfd/max77705.h new file mode 100644 index 000000000000..f88ded6757e2 --- /dev/null +++ b/include/linux/mfd/max77705.h @@ -0,0 +1,93 @@ +/* + * max77705.h - Driver for the Maxim 77705 + * + * Copyright (C) 2016 Samsung Electrnoics + * Insun Choi + * + * 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, see . + * + * This driver is based on max8997.h + * + * MAX77705 has Flash LED, SVC LED, Haptic, MUIC devices. + * The devices share the same I2C bus and included in + * this mfd driver. + */ + +#ifndef __MAX77705_H__ +#define __MAX77705_H__ +#include +#include + +#define MFD_DEV_NAME "max77705" +#define M2SH(m) ((m) & 0x0F ? ((m) & 0x03 ? ((m) & 0x01 ? 0 : 1) : ((m) & 0x04 ? 2 : 3)) : \ + ((m) & 0x30 ? ((m) & 0x10 ? 4 : 5) : ((m) & 0x40 ? 6 : 7))) + +struct max77705_vibrator_pdata { + int gpio; + char *regulator_name; + struct pwm_device *pwm; + const char *motor_type; + + int freq; + /* for multi-frequency */ + int freq_nums; + u32 *freq_array; + u32 *ratio_array; /* not used now */ + int normal_ratio; + int overdrive_ratio; + int high_temp_ratio; + int high_temp_ref; + int fold_open_ratio; + int fold_close_ratio; +#if defined(CONFIG_SEC_VIBRATOR) + bool calibration; + int steps; + int *intensities; + int *haptic_intensities; +#endif +}; + +struct max77705_regulator_data { + int id; + struct regulator_init_data *initdata; + struct device_node *reg_node; +}; + +struct max77705_platform_data { + /* IRQ */ + int irq_base; + int irq_gpio; + bool wakeup; + bool blocking_waterevent; + bool extra_fw_enable; + bool siso_ovp; + int wpc_en; + int fw_product_id; + struct muic_platform_data *muic_pdata; + + int num_regulators; + struct max77705_regulator_data *regulators; + struct max77705_vibrator_pdata *vibrator_data; + struct mfd_cell *sub_devices; + int num_subdevs; + bool support_audio; + char *wireless_charger_name; +}; + +struct max77705 { + struct regmap *regmap; +}; + +#endif /* __MAX77705_H__ */ + diff --git a/include/linux/mfd/max77705C_bolt_pass2.h b/include/linux/mfd/max77705C_bolt_pass2.h new file mode 100644 index 000000000000..c5d8d49bd87e --- /dev/null +++ b/include/linux/mfd/max77705C_bolt_pass2.h @@ -0,0 +1,6634 @@ +const uint8_t BOOT_FLASH_FW_PASS2[] = { +0xc1, 0x66, 0xf1, 0xce, 0x04, 0x10, 0x15, 0x02, +0x01, 0x22, 0x21, 0x70, 0x01, 0x02, 0x03, 0x04, +0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, +0x0d, 0x0e, 0x0f, 0x00, 0x04, 0x10, 0x15, 0x02, +0x03, 0x00, 0x00, 0x00, 0xd2, 0x16, 0xaf, 0x2f, +0x63, 0x87, 0x8e, 0x5d, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0x20, 0xa9, 0xe8, +0x91, 0x2c, 0xdf, 0x69, 0xb7, 0x95, 0x9d, 0xa4, +0x69, 0x33, 0xed, 0x4a, 0x1d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0xef, +0x29, 0xab, 0xf0, 0x76, 0x16, 0x2d, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0xd8, +0xf4, 0xd3, 0xbc, 0x12, 0x84, 0x2f, 0x73, 0x3f, +0xb2, 0x19, 0x55, 0x00, 0xb5, 0x07, 0xf2, 0xbc, +0xd0, 0x2b, 0xb3, 0xcc, 0xc5, 0x59, 0x62, 0xba, +0x82, 0x1e, 0x50, 0xb7, 0x11, 0x89, 0xac, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0x47, 0xbe, 0x18, 0x4b, 0x38, 0x8a, 0x74, +0xf7, 0xa1, 0xcb, 0x50, 0x45, 0x3c, 0x36, 0xe0, +0x4e, 0x0a, 0x57, 0xab, 0x9b, 0x08, 0xa9, 0x45, +0x71, 0x92, 0x44, 0x5d, 0xc7, 0x80, 0xea, 0xb2, +0x3a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0xdb, 0x5a, 0xbf, 0x17, 0x6b, +0xbd, 0x70, 0x67, 0x7f, 0x7d, 0x1a, 0x6e, 0xff, +0x93, 0x16, 0x10, 0x3f, 0x34, 0xef, 0xb5, 0xfc, +0x76, 0xe3, 0x5f, 0x0e, 0xb3, 0x6a, 0xae, 0x3b, +0x00, 0x38, 0xb6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0x61, 0x9d, 0xf2, +0xe1, 0xfd, 0x41, 0x4b, 0xda, 0x0e, 0xda, 0x94, +0xca, 0x52, 0xbe, 0x49, 0xaf, 0xf0, 0x42, 0xb4, +0xf9, 0xb4, 0xf6, 0xb6, 0xd5, 0x3f, 0xfe, 0x4c, +0x40, 0x14, 0xa7, 0x96, 0xf6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0x3f, +0x8e, 0x8c, 0x92, 0x7e, 0xff, 0x7c, 0xa5, 0x9d, +0x3c, 0x28, 0x8c, 0x53, 0x8c, 0x27, 0xa2, 0x20, +0x83, 0xae, 0xbc, 0x92, 0x3b, 0x6d, 0xd7, 0x65, +0x62, 0xe1, 0xe7, 0xdf, 0x54, 0x0f, 0xbe, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0xc0, 0x6b, 0x02, 0xd0, 0xf7, 0x2a, 0xe2, +0xab, 0xa0, 0xbf, 0x4b, 0x85, 0x9d, 0xc3, 0xcf, +0x96, 0xde, 0x1f, 0x1a, 0x28, 0xa5, 0x89, 0x4e, +0x96, 0xd5, 0xa3, 0xda, 0x7c, 0xcc, 0x1c, 0x2b, +0x51, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0x3d, 0x88, 0xc5, 0xde, 0x36, +0x72, 0xbd, 0xd1, 0x28, 0xf0, 0x80, 0x54, 0xcd, +0x32, 0x76, 0x45, 0xa8, 0xea, 0x05, 0xd9, 0xf2, +0xde, 0x09, 0x78, 0x3e, 0x90, 0x8f, 0xad, 0x58, +0x15, 0xce, 0x0b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x14, 0x28, 0x50, +0xa0, 0x7f, 0xfa, 0xa6, 0x71, 0xb9, 0xf6, 0x2c, +0x63, 0xa9, 0x47, 0xaf, 0x82, 0x6d, 0x98, 0x3d, +0x56, 0xca, 0xd3, 0xde, 0xa7, 0xa9, 0x3c, 0x48, +0x25, 0x60, 0xf3, 0xdd, 0xe3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0x87, +0xb9, 0x8f, 0x13, 0xc7, 0x50, 0xd2, 0x90, 0xf8, +0x5e, 0xd8, 0xc2, 0x00, 0xdd, 0x9c, 0xcc, 0xcc, +0xd1, 0xd1, 0xd8, 0x22, 0x71, 0x19, 0xfe, 0x79, +0x8a, 0xfd, 0xa0, 0xc3, 0x23, 0xe6, 0xe9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x98, 0x20, 0x4c, 0x12, 0xbb, 0x73, 0x90, +0x93, 0x53, 0x35, 0xde, 0x4a, 0x13, 0x37, 0x87, +0x3e, 0xa3, 0xaa, 0x74, 0x69, 0x2f, 0xdf, 0xa3, +0x4b, 0x19, 0xaa, 0x09, 0x5b, 0x5f, 0x3f, 0xa4, +0xaa, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0xa4, 0xae, 0xdd, 0x10, 0x60, +0x2f, 0x32, 0x82, 0x0a, 0x1d, 0x2a, 0x07, 0xe8, +0x64, 0xb0, 0xe2, 0x24, 0x6f, 0x34, 0xf8, 0x6f, +0x12, 0x46, 0x63, 0x81, 0x95, 0xb4, 0xfc, 0xfe, +0x29, 0x74, 0x94, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0xf9, 0xa6, 0x2d, +0xa4, 0xbe, 0x6f, 0x06, 0xfe, 0x64, 0xb4, 0x09, +0xfe, 0x79, 0xd4, 0x48, 0x63, 0xb7, 0x4e, 0x86, +0x01, 0xc3, 0xe5, 0xd3, 0x3c, 0xe7, 0x53, 0x79, +0xb7, 0x98, 0x61, 0xd7, 0x2a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0xd3, +0x32, 0x2c, 0x79, 0x61, 0x20, 0x58, 0xa2, 0x31, +0x70, 0x33, 0x44, 0x61, 0x98, 0x5f, 0x1d, 0xf6, +0x25, 0x55, 0x7e, 0xdd, 0xd7, 0x96, 0x97, 0x8b, +0xc8, 0x50, 0x4d, 0xdc, 0xcc, 0x41, 0xbb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0x6c, 0xab, 0xf1, 0x35, 0xa1, 0x87, 0xf4, +0x5e, 0x1e, 0x22, 0x31, 0xfb, 0xe9, 0x1f, 0x3b, +0xd0, 0xc0, 0xc5, 0x5a, 0x13, 0x52, 0xf2, 0x5d, +0x69, 0xc9, 0x47, 0xf3, 0x74, 0x75, 0x3a, 0x82, +0x0a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0xec, 0xd1, 0xbe, 0x66, 0x13, +0x95, 0x6b, 0x54, 0xaa, 0x19, 0x3d, 0x68, 0x9c, +0x55, 0x85, 0xda, 0x45, 0xf9, 0x45, 0x6f, 0x87, +0x6f, 0x93, 0xc7, 0x52, 0xd7, 0x5a, 0xf0, 0x6d, +0x8b, 0x6e, 0xb2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0x80, 0xff, 0x2c, +0x07, 0xcd, 0x1f, 0xb5, 0xca, 0x78, 0x1c, 0xb6, +0xbb, 0x54, 0x80, 0x9f, 0x7e, 0x84, 0x30, 0xfa, +0x97, 0x89, 0x40, 0xb9, 0xf2, 0xd7, 0xdc, 0x96, +0xe0, 0x90, 0x00, 0x81, 0x9c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x0c, +0x85, 0x17, 0x15, 0x99, 0x08, 0xce, 0xb7, 0x9b, +0xdb, 0x51, 0x82, 0x19, 0xaf, 0x37, 0xa9, 0x2f, +0xc0, 0x54, 0x8a, 0xb1, 0xe7, 0x20, 0x2f, 0xa8, +0x97, 0xed, 0xb6, 0xfa, 0xe2, 0x1b, 0x39, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0x4a, 0x37, 0x5f, 0xfd, 0xba, 0x10, 0xdf, +0x3d, 0x51, 0x7f, 0xce, 0x60, 0x51, 0xd7, 0x3d, +0x68, 0x1b, 0x76, 0xb1, 0x0b, 0x70, 0x02, 0x62, +0x1f, 0x46, 0xd8, 0x8a, 0x07, 0x49, 0x4b, 0x65, +0xf7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0xe6, 0xb2, 0x36, 0xfd, 0x26, +0x99, 0xf9, 0x6a, 0x31, 0x50, 0xf5, 0x0d, 0xfa, +0x26, 0x69, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x3d, 0xe7, 0xc0, 0x09, +0xc7, 0x8c, 0xd3, 0xb5, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0xb9, 0xc6, 0xc2, +0xe4, 0xb4, 0xab, 0xf4, 0xb2, 0x49, 0xab, 0xc2, +0xae, 0x81, 0xd8, 0x3b, 0xb3, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x8f, +0x91, 0x82, 0xa8, 0x2d, 0xa7, 0x1f, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x3c, +0xc0, 0x89, 0x2e, 0x67, 0x82, 0x43, 0x46, 0xff, +0xed, 0x70, 0xb8, 0x34, 0x0e, 0xcb, 0x28, 0x56, +0x02, 0x71, 0xd6, 0x08, 0xb8, 0x67, 0x9d, 0x94, +0xbf, 0xe6, 0x5c, 0x52, 0xb1, 0x09, 0x98, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0xfd, 0x25, 0xfe, 0x0e, 0x59, 0xa0, 0x31, +0x45, 0xdb, 0xc7, 0x73, 0xfa, 0x1f, 0x92, 0xdb, +0xa9, 0x51, 0x99, 0x63, 0xbb, 0x1c, 0x68, 0xc1, +0xac, 0xf5, 0xcd, 0x38, 0xa6, 0x91, 0x1d, 0x6e, +0xfd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0x74, 0x6b, 0xd4, 0xf7, 0xa5, +0x38, 0x3e, 0xa6, 0x0d, 0xfb, 0xd0, 0x1f, 0xb4, +0xa1, 0xe9, 0xbb, 0x41, 0x8e, 0x97, 0x7e, 0x9f, +0x91, 0x42, 0xc6, 0x1c, 0xae, 0xb7, 0x4a, 0x17, +0x2c, 0x14, 0x16, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x0f, 0x17, 0x98, +0x0a, 0xc1, 0xce, 0xf8, 0x59, 0x5b, 0x6a, 0xcb, +0xd3, 0x8e, 0xee, 0x68, 0x7a, 0xf0, 0x1b, 0x52, +0x5a, 0x24, 0x30, 0x5e, 0xb9, 0x5d, 0xa1, 0x76, +0xdc, 0xe9, 0x2b, 0x32, 0xd3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0xac, +0x1c, 0xc3, 0x26, 0x41, 0x6d, 0x9e, 0xd6, 0x92, +0x56, 0x51, 0xbe, 0xf4, 0x68, 0x91, 0xa4, 0xe1, +0xe2, 0xc0, 0x90, 0x75, 0x59, 0xdd, 0xf4, 0x40, +0xa0, 0x53, 0xb1, 0x46, 0x93, 0x4e, 0xc3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0x29, 0x03, 0x5e, 0x6e, 0xbf, 0xb8, 0x14, +0x92, 0x72, 0x17, 0x06, 0x67, 0x94, 0x17, 0x32, +0x43, 0xb8, 0x92, 0x01, 0xac, 0x4f, 0xb8, 0xa5, +0x44, 0x26, 0xb0, 0x9e, 0xc5, 0x04, 0xcb, 0xd4, +0x21, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0xef, 0x5c, 0x04, 0xe9, 0x86, +0x4d, 0x3e, 0x72, 0x2b, 0xd8, 0x2c, 0xfd, 0xfc, +0xb0, 0x5c, 0xc3, 0x1e, 0x9a, 0x13, 0x15, 0x58, +0x7c, 0x0d, 0x89, 0x64, 0xdf, 0xaa, 0x6b, 0xfd, +0x97, 0x4d, 0x0e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0x1b, 0x2c, 0xc3, +0xb5, 0x59, 0x74, 0x1d, 0x33, 0x7a, 0x4f, 0x04, +0xf8, 0xd4, 0x87, 0xcc, 0x58, 0xfa, 0x34, 0x34, +0x5c, 0xa6, 0x2a, 0x8d, 0x0b, 0xa1, 0x83, 0xd9, +0xe9, 0x49, 0xe2, 0xe9, 0x88, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0x5c, +0xbb, 0x1e, 0xb8, 0x4a, 0x3c, 0x56, 0xf9, 0x27, +0xab, 0xb6, 0x34, 0xe4, 0xfc, 0x0c, 0xff, 0x81, +0x38, 0x3b, 0xbe, 0x4d, 0x4a, 0x60, 0xb2, 0x6b, +0x3f, 0x6c, 0xae, 0x1a, 0xb0, 0xb3, 0x80, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x3b, 0x61, 0xc1, 0x4e, 0x8b, 0xbb, 0x72, +0xf4, 0xa1, 0x02, 0xcd, 0x6b, 0x3c, 0xf9, 0x01, +0xb2, 0x3b, 0x0b, 0xc1, 0xd4, 0xc7, 0x50, 0x44, +0xe4, 0x98, 0xc8, 0x34, 0x9a, 0x97, 0x87, 0x0c, +0x9f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0xd2, 0x07, 0x2b, 0x6c, 0xe2, +0x48, 0x0a, 0x59, 0xf3, 0x77, 0x9c, 0x35, 0x9f, +0x96, 0xcd, 0xfe, 0xd3, 0x42, 0x52, 0x88, 0x31, +0x16, 0x5e, 0x7a, 0x75, 0x3c, 0xe1, 0x3b, 0xba, +0x15, 0xfd, 0x45, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0x47, 0x00, 0xd8, +0x98, 0x4e, 0x76, 0xa0, 0x1d, 0x99, 0xe0, 0x6f, +0x14, 0x6c, 0x40, 0x14, 0xf6, 0x51, 0x6e, 0x99, +0x76, 0x60, 0x8f, 0x5c, 0xa2, 0x90, 0x8f, 0x8b, +0x94, 0x44, 0xd3, 0xfa, 0x9d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0x10, +0xbf, 0x07, 0xf5, 0xad, 0x73, 0xb6, 0xf9, 0x8b, +0x30, 0x8c, 0xa2, 0xaa, 0x4d, 0x86, 0x4a, 0x5a, +0x87, 0xdd, 0x7a, 0xb0, 0x3c, 0xb4, 0x8f, 0xff, +0xd8, 0x52, 0x44, 0x7d, 0x77, 0x09, 0x20, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0xbc, 0x78, 0x68, 0x87, 0x12, 0x52, 0x52, +0x57, 0x12, 0x37, 0x72, 0xc1, 0x82, 0xce, 0xc1, +0xc3, 0x8c, 0x15, 0xab, 0x3d, 0xbd, 0x6e, 0xc4, +0x52, 0x87, 0xe4, 0x5d, 0x81, 0x80, 0xb8, 0xf2, +0x85, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0xdf, 0x26, 0xfb, 0xd3, 0x2f, +0x6a, 0xf1, 0x9e, 0x2d, 0xbf, 0x7b, 0xdb, 0x49, +0x33, 0x3b, 0xf9, 0x7f, 0xe9, 0x5b, 0x70, 0x7e, +0xea, 0xb5, 0xd3, 0x0b, 0x0b, 0xaa, 0xc4, 0x58, +0xbf, 0x06, 0x73, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0x57, 0xa6, 0xff, +0x6d, 0x68, 0x5b, 0x6c, 0xba, 0x37, 0xd3, 0x75, +0x10, 0x8d, 0x67, 0xa2, 0xaa, 0xea, 0x67, 0xe1, +0xce, 0xab, 0x56, 0xcf, 0xc0, 0x92, 0xb7, 0x33, +0x97, 0x64, 0x75, 0xa8, 0xfc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x20, +0xd3, 0x47, 0xc6, 0x69, 0xf5, 0x3d, 0x71, 0x29, +0xad, 0xed, 0x55, 0x2c, 0x55, 0x08, 0xe1, 0xf4, +0x8c, 0x5b, 0x67, 0xda, 0x9a, 0x9d, 0x49, 0xf0, +0xda, 0xde, 0x99, 0x26, 0x44, 0x8e, 0x53, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0x5b, 0xbf, 0x8c, 0x16, 0x13, 0xf4, 0x09, +0x61, 0xd9, 0x11, 0x99, 0x2e, 0x3f, 0xd2, 0xab, +0x1b, 0xde, 0x18, 0x69, 0x89, 0x6d, 0x35, 0x1f, +0x7d, 0xb0, 0x5d, 0xc2, 0xd2, 0x13, 0x76, 0x27, +0x2c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0xe7, 0x30, 0x6a, 0x89, 0x65, +0xee, 0x08, 0xf2, 0x40, 0x01, 0x13, 0xc4, 0xc8, +0xc3, 0xc6, 0xd2, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xad, 0x4a, 0x75, 0xa3, +0x51, 0x79, 0x23, 0x61, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0x3c, 0x56, 0x37, +0xda, 0x78, 0x1b, 0x68, 0x14, 0x35, 0x01, 0x07, +0x19, 0x6c, 0x41, 0x74, 0x26, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x06, +0x6e, 0x36, 0x39, 0x41, 0xcc, 0x4f, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0xbd, +0x80, 0xce, 0x86, 0xab, 0x84, 0x1f, 0xfe, 0xb4, +0xf8, 0x82, 0x88, 0x42, 0x34, 0x71, 0xd1, 0x44, +0xb7, 0x14, 0xaa, 0x88, 0xa2, 0x53, 0xd1, 0x0e, +0x3a, 0x66, 0x96, 0x83, 0xd0, 0x7e, 0xdf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0x7c, 0x86, 0x12, 0x1e, 0x8c, 0x5b, 0xf0, +0xa7, 0xde, 0x3f, 0x45, 0xac, 0x3a, 0x8f, 0xdb, +0xee, 0x96, 0xc1, 0xdd, 0xac, 0x19, 0xa9, 0x15, +0xc3, 0xec, 0x16, 0xba, 0xdc, 0xfc, 0x37, 0x70, +0x1d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0xd4, 0x11, 0xf3, 0xa7, 0x48, +0xb1, 0x7d, 0xaa, 0x4d, 0x02, 0xa8, 0x26, 0x8a, +0x19, 0x1f, 0x5c, 0xc7, 0x5a, 0x33, 0x86, 0x3b, +0x54, 0x8e, 0xb1, 0x88, 0x7d, 0x92, 0x17, 0x6c, +0xbc, 0x04, 0xe9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0x42, 0xf2, 0xd4, +0x7e, 0xad, 0x14, 0x31, 0x6e, 0x12, 0xaa, 0x0b, +0x3a, 0xf8, 0xaa, 0x02, 0xe9, 0xc8, 0x49, 0xd5, +0x28, 0x35, 0xe0, 0x03, 0xbf, 0x42, 0xb2, 0x60, +0x61, 0x70, 0xc2, 0x0a, 0x3e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0x2a, +0xf4, 0xc5, 0x2b, 0x9b, 0x6d, 0x7a, 0xa4, 0x36, +0x34, 0x88, 0x42, 0x08, 0xde, 0xd9, 0x95, 0xbd, +0xa3, 0x0b, 0x4c, 0xdd, 0x5d, 0x25, 0xe8, 0x1d, +0xb5, 0x3c, 0xce, 0x16, 0x5a, 0x90, 0x17, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0x8c, 0x24, 0xba, 0x61, 0x5e, 0xd1, 0x57, +0xb8, 0x8d, 0xd5, 0x4c, 0x28, 0x8b, 0xd2, 0xa6, +0x29, 0x06, 0x0f, 0xa8, 0x65, 0x18, 0xf1, 0xb9, +0xae, 0x68, 0xf0, 0xbf, 0x69, 0x53, 0x7d, 0xd8, +0x32, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0xfb, 0xaf, 0x2b, 0xea, 0xe4, +0x5c, 0xac, 0x0a, 0xee, 0x9e, 0x12, 0xff, 0xf1, +0x1c, 0x9e, 0xd4, 0x55, 0x55, 0x1c, 0xa9, 0x5b, +0xdc, 0x90, 0xa3, 0xb6, 0x26, 0x98, 0x81, 0x58, +0x92, 0x73, 0xd1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0x31, 0x2a, 0xa1, +0xb1, 0x1d, 0xb6, 0x55, 0x07, 0x02, 0xdc, 0xc0, +0x0c, 0x76, 0xc4, 0x04, 0x6c, 0x73, 0x48, 0xd7, +0xbb, 0xa0, 0xec, 0x9f, 0xd2, 0x51, 0x1e, 0x4a, +0x15, 0xed, 0xad, 0x05, 0x19, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0xde, +0xbf, 0xb0, 0xa6, 0x4c, 0xc5, 0xcd, 0x57, 0x57, +0xe6, 0x78, 0x21, 0x45, 0x01, 0x34, 0x16, 0x91, +0xb4, 0x66, 0x66, 0x31, 0x94, 0x72, 0xd5, 0xcf, +0x61, 0x10, 0x9b, 0x7c, 0xe8, 0xff, 0x03, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0x59, 0x93, 0xaa, 0xd2, 0x12, 0x65, 0x58, +0x4d, 0xf8, 0xca, 0x40, 0x21, 0x81, 0x72, 0xea, +0xb0, 0xcf, 0x23, 0xbc, 0xb0, 0x74, 0x8b, 0xb1, +0x2b, 0xb4, 0x9e, 0xfd, 0xf9, 0x7f, 0xbd, 0xf2, +0x4f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0x99, 0xd8, 0x32, 0xe7, 0xf5, +0x17, 0x86, 0xec, 0x1e, 0xae, 0x05, 0xd2, 0xc9, +0x0d, 0xb6, 0x5e, 0xb7, 0x07, 0xf5, 0x06, 0x3e, +0x81, 0x3c, 0x41, 0x7f, 0x93, 0x8d, 0x16, 0x1f, +0x8d, 0x4a, 0x18, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0xed, 0x2f, 0xd1, +0xcc, 0x99, 0xc5, 0xca, 0xed, 0xb9, 0xe8, 0x5e, +0xc8, 0x81, 0xdc, 0xbf, 0xf3, 0x18, 0xfa, 0xe1, +0x6f, 0x8a, 0x58, 0x9a, 0xdc, 0x46, 0xe0, 0x5e, +0xa6, 0xb8, 0xf1, 0x22, 0x1d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0x67, +0x08, 0x3e, 0x00, 0x99, 0x94, 0x55, 0x53, 0xf8, +0x17, 0xd1, 0x40, 0x9b, 0x91, 0xf7, 0x1b, 0xab, +0xcd, 0xf5, 0x61, 0xde, 0xcc, 0x59, 0x87, 0xab, +0xbc, 0x0a, 0xfc, 0x81, 0x15, 0xed, 0x5b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0x3d, 0x2c, 0x7d, 0x9d, 0xec, 0x70, 0x55, +0x1b, 0x18, 0x00, 0x13, 0x77, 0xb1, 0x13, 0x43, +0x3e, 0x91, 0xed, 0x2a, 0x31, 0x1f, 0x9a, 0x5b, +0xc9, 0x31, 0x21, 0x9e, 0x5d, 0x01, 0x39, 0x75, +0xed, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x8a, 0x5b, 0x46, 0xd7, 0x1e, +0xbd, 0x8b, 0x10, 0x87, 0x73, 0xdb, 0xff, 0x05, +0x57, 0x39, 0x9e, 0x0f, 0x5e, 0x78, 0x4c, 0xcd, +0xf6, 0xc2, 0x4b, 0x01, 0x28, 0x2c, 0xc9, 0xf4, +0x37, 0xfe, 0x90, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0x99, 0x66, 0xf3, +0xaa, 0xed, 0xd3, 0x4e, 0x08, 0x73, 0x0f, 0x61, +0x63, 0xe9, 0x8f, 0x38, 0xc6, 0xa3, 0xb2, 0x49, +0x74, 0xf3, 0xf3, 0x11, 0xd3, 0x92, 0xb7, 0x01, +0x5e, 0xe0, 0x75, 0x20, 0x50, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0x97, +0x4e, 0x35, 0xde, 0xd9, 0x31, 0xb7, 0xd7, 0x65, +0xcb, 0x61, 0xa5, 0xe2, 0xa1, 0x3f, 0xac, 0x50, +0xdc, 0x72, 0xed, 0x48, 0x9d, 0x6c, 0x78, 0xc9, +0x7c, 0x70, 0x12, 0x1a, 0x59, 0x66, 0xc6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x67, 0x7e, 0xca, 0x8c, 0x5e, 0x21, 0x2f, +0x06, 0x1d, 0xfa, 0xa0, 0x30, 0x17, 0x06, 0x9c, +0x8b, 0x1a, 0x0d, 0x8b, 0x53, 0xdc, 0xd1, 0xc1, +0xda, 0x93, 0x9b, 0x71, 0xd6, 0x42, 0x10, 0x21, +0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x99, 0xa8, 0x29, 0xf3, 0xf7, +0xe2, 0xf3, 0x57, 0xf7, 0x55, 0x97, 0x6b, 0xfb, +0x58, 0x0c, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xc4, 0x18, 0x6a, 0xbc, +0x29, 0x76, 0x1c, 0xac, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0x0f, 0xb8, 0xe2, +0x5d, 0xfb, 0x7d, 0xec, 0x98, 0x9d, 0xbe, 0xa8, +0x81, 0x86, 0x3d, 0x11, 0x38, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0xa7, +0x79, 0x6e, 0x8d, 0x19, 0x88, 0x9b, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0xc2, +0x0a, 0xf5, 0x2e, 0xca, 0xbc, 0x63, 0xdf, 0xd2, +0xc8, 0x67, 0xdb, 0xb6, 0x8c, 0xe5, 0xa1, 0x7d, +0xf6, 0xda, 0x31, 0x9d, 0xb2, 0x6f, 0xd0, 0xe1, +0x2e, 0x4b, 0x3e, 0x47, 0xbb, 0xeb, 0x9c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0xa0, 0x25, 0xc7, 0x7e, 0xf6, 0x15, 0x90, +0x5a, 0x1b, 0x5f, 0xd6, 0x85, 0x2f, 0xd3, 0xf7, +0xa5, 0x8e, 0x0f, 0xa8, 0xb0, 0xeb, 0x92, 0x41, +0x70, 0xdf, 0xf7, 0x28, 0x61, 0x67, 0xec, 0xe9, +0x12, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0x08, 0x81, 0x67, 0x9d, 0x6e, +0xb6, 0x5d, 0xd2, 0xf5, 0x9c, 0x04, 0x11, 0x34, +0xbd, 0xbb, 0x00, 0xcf, 0x86, 0x36, 0xc7, 0xa8, +0x1a, 0x27, 0x7f, 0x93, 0xf3, 0x39, 0x80, 0x84, +0x82, 0x2a, 0x05, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x20, 0xcd, 0x54, +0x5b, 0x98, 0x4a, 0xb4, 0xad, 0xc8, 0x3a, 0x6d, +0x14, 0x34, 0x81, 0x84, 0x0c, 0x5b, 0xe4, 0xad, +0x91, 0xbf, 0x65, 0x47, 0xee, 0xb5, 0x8e, 0x27, +0x82, 0xaf, 0xd8, 0x09, 0x34, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x68, +0x8d, 0xb0, 0x5e, 0x42, 0x68, 0x9d, 0xa9, 0x18, +0x6d, 0xb1, 0x64, 0xf5, 0x7d, 0x8c, 0x14, 0xa4, +0xaf, 0xf6, 0xda, 0xf3, 0x81, 0x7e, 0x37, 0xc3, +0xce, 0x5b, 0x12, 0xb1, 0x62, 0xb9, 0xf7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0xfe, 0xd0, 0x20, 0x7c, 0xf3, 0xd1, 0x1e, +0x5f, 0x24, 0xad, 0xc6, 0x38, 0xaf, 0x58, 0x42, +0x86, 0xa4, 0x17, 0x53, 0xd0, 0x46, 0x8c, 0x2e, +0x9b, 0x2e, 0x67, 0x03, 0xb1, 0x47, 0x67, 0xc6, +0x40, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0xf7, 0x14, 0x3f, 0x64, 0xbe, +0x45, 0xb3, 0x9d, 0x1c, 0xcb, 0x32, 0x65, 0xa7, +0x3e, 0x75, 0x5d, 0x4a, 0xcf, 0x05, 0x60, 0xbe, +0xfe, 0x6c, 0xd6, 0xd3, 0xd8, 0x52, 0xcf, 0xa6, +0x93, 0x02, 0xbc, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0xad, 0x16, 0x2f, +0x3d, 0x01, 0x50, 0x9f, 0x1d, 0x0b, 0x34, 0xeb, +0x70, 0x39, 0x6c, 0x84, 0xa4, 0x8d, 0x74, 0x18, +0x9f, 0x06, 0xab, 0x81, 0xc0, 0x67, 0x7c, 0x7f, +0xfd, 0xf2, 0x80, 0x16, 0x90, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0x8c, +0x87, 0x96, 0x5d, 0xcd, 0x6f, 0xc8, 0x3b, 0xb7, +0x87, 0xca, 0x0c, 0xeb, 0x24, 0x0d, 0x6b, 0x2c, +0xeb, 0xb6, 0x86, 0x68, 0x3d, 0x07, 0xdd, 0x52, +0x48, 0x1f, 0x45, 0x1d, 0x72, 0x9f, 0xdb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0xad, 0x53, 0x10, 0x9a, 0x78, 0x37, 0x08, +0xc3, 0x8e, 0x9b, 0x5d, 0xf5, 0xa9, 0x77, 0x59, +0x26, 0x61, 0xf6, 0x14, 0x90, 0x32, 0x63, 0x31, +0xe3, 0xe7, 0xb7, 0x4f, 0x11, 0x44, 0xa7, 0x67, +0xf1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0xc5, 0x2d, 0x7a, 0x13, 0x93, +0x03, 0x96, 0x6d, 0x39, 0x5f, 0x1f, 0xc5, 0xcf, +0xb0, 0x8e, 0x53, 0x1c, 0x88, 0x94, 0x3e, 0x31, +0x98, 0x50, 0x0f, 0x43, 0xeb, 0x5b, 0x45, 0x6c, +0xb1, 0xb2, 0x95, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0x9e, 0xa6, 0xfa, +0xbf, 0x1d, 0x38, 0xf5, 0xcb, 0x1d, 0xfa, 0xd4, +0xb4, 0x74, 0x39, 0x5e, 0x49, 0x02, 0x0b, 0x47, +0x12, 0xc9, 0x55, 0x06, 0xd3, 0x61, 0xba, 0x65, +0x04, 0x89, 0xc0, 0x90, 0xd0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0x54, +0x5a, 0xd5, 0x4b, 0x12, 0x43, 0xe9, 0xe4, 0x92, +0xf4, 0xc9, 0x39, 0xa9, 0xb1, 0x21, 0x39, 0x39, +0x52, 0x7b, 0x16, 0x1c, 0x83, 0x8a, 0xd3, 0xe2, +0x74, 0xa7, 0x46, 0xf6, 0x92, 0xb2, 0x91, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0x06, 0x00, 0xb0, 0xcd, 0x8a, 0xab, 0x99, +0x0d, 0xd3, 0x1d, 0x2a, 0x7d, 0xaa, 0x8e, 0x2f, +0x1b, 0x67, 0x36, 0xf6, 0x7d, 0x76, 0x26, 0x7d, +0x0c, 0x11, 0xf5, 0x5c, 0x70, 0x56, 0xff, 0x59, +0x2e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0x63, 0x8d, 0xf8, 0x13, 0x1b, +0x4b, 0x0e, 0x24, 0xbb, 0x40, 0xbd, 0x98, 0xee, +0xaf, 0xc8, 0x1b, 0x8c, 0x90, 0xfd, 0x1f, 0x6f, +0xe8, 0xa5, 0xf4, 0x6e, 0x77, 0x0e, 0x5e, 0x10, +0x3e, 0x14, 0xc8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x51, 0x7b, 0x27, +0x56, 0x09, 0xe3, 0x69, 0x77, 0x65, 0xaa, 0x68, +0x6b, 0xab, 0xc1, 0xfc, 0x87, 0x94, 0xd0, 0x15, +0x2c, 0x52, 0xda, 0xe8, 0xf3, 0xbe, 0x5f, 0x70, +0x79, 0xe9, 0xbb, 0x69, 0x2f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0xc9, +0xe9, 0x28, 0x32, 0x85, 0x0f, 0xc3, 0x06, 0x9f, +0xd3, 0x32, 0x15, 0x11, 0x5e, 0x7a, 0xdc, 0x45, +0x1a, 0xd0, 0x0b, 0xd0, 0xa3, 0x41, 0x6f, 0x85, +0x09, 0x5f, 0xbf, 0xb3, 0xa3, 0xc9, 0xd8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0xe3, 0x31, 0x6c, 0xc2, 0xa3, 0x16, 0x0b, +0x51, 0x8e, 0x27, 0x57, 0x44, 0x64, 0xdf, 0x74, +0x85, 0xcc, 0xfd, 0x5d, 0x99, 0x66, 0x6e, 0x2b, +0xc7, 0x2b, 0x35, 0x61, 0x40, 0x24, 0xa2, 0x12, +0x20, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0x4d, 0x8f, 0x45, 0x87, 0x78, +0x2a, 0x14, 0x8d, 0x56, 0x1e, 0xbc, 0xad, 0x5f, +0x26, 0xff, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xff, 0xa0, 0x3d, 0x1d, +0x34, 0x8a, 0x88, 0x30, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x09, 0xf3, 0x47, +0xa0, 0x1a, 0xc7, 0xb9, 0x86, 0x51, 0x86, 0x08, +0x85, 0xe4, 0x64, 0x21, 0x89, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x27, +0xe2, 0xbc, 0x08, 0x9f, 0x7e, 0xb5, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x1e, +0x90, 0x16, 0x76, 0x76, 0x35, 0xbe, 0x5c, 0x67, +0xd3, 0x96, 0x5f, 0x98, 0x6d, 0x1f, 0x1b, 0x1b, +0x27, 0xf6, 0xfb, 0x40, 0xec, 0x2c, 0x73, 0x49, +0xdb, 0x7e, 0x15, 0xaa, 0xe3, 0x5c, 0xdc, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0xb6, 0xef, 0x1a, 0xbe, 0xaf, 0x96, 0x1d, +0xc8, 0xb2, 0x0f, 0xf7, 0x68, 0xcc, 0x23, 0x5a, +0xb0, 0xe3, 0xe4, 0x2c, 0x82, 0x64, 0x9b, 0xb9, +0x9a, 0xa1, 0x88, 0xe7, 0x9b, 0x11, 0xec, 0x55, +0x83, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0x18, 0xed, 0xed, 0x30, 0x93, +0x3c, 0xa0, 0xe3, 0x9f, 0xbd, 0x3c, 0x75, 0xf3, +0x9f, 0x1c, 0xc6, 0x05, 0xab, 0xf7, 0x63, 0xe0, +0xe8, 0x2c, 0x78, 0xce, 0x9b, 0x67, 0x8c, 0x97, +0x60, 0x86, 0x90, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0xc8, 0x17, 0x18, +0x64, 0x74, 0x1a, 0x20, 0xf3, 0x66, 0x83, 0x8a, +0x50, 0xca, 0x0b, 0x0f, 0xee, 0xca, 0xed, 0xf2, +0x10, 0x8d, 0x64, 0xef, 0x29, 0xda, 0x65, 0x1f, +0xb5, 0xb6, 0x96, 0xe6, 0xce, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x0c, +0x27, 0x49, 0x4c, 0x8d, 0x19, 0xe2, 0xde, 0xbf, +0x77, 0xb2, 0x55, 0x3d, 0x27, 0xf3, 0x76, 0x2e, +0x00, 0x28, 0xcc, 0x6b, 0xc3, 0x2b, 0xde, 0xcc, +0xe3, 0xb7, 0x63, 0x88, 0xa2, 0x7d, 0x7d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0x50, 0xc1, 0xfa, 0xc9, 0x9b, 0x49, 0x06, +0x1d, 0x81, 0x44, 0x08, 0x5f, 0x82, 0x86, 0xa5, +0x1b, 0x05, 0x2d, 0x2d, 0x73, 0x2f, 0xd0, 0x15, +0x49, 0x26, 0x81, 0x8f, 0x1e, 0x63, 0x97, 0x2c, +0x95, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0x2e, 0x75, 0x12, 0x01, 0xf1, +0x9b, 0xb2, 0x38, 0xe5, 0x5e, 0xdb, 0x8e, 0x02, +0x24, 0x51, 0xfd, 0x61, 0x9a, 0x49, 0x2c, 0x54, +0x5c, 0x54, 0x4d, 0xd1, 0x5e, 0x17, 0xcb, 0x4e, +0x64, 0x8c, 0xfb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0x17, 0xfb, 0x3b, +0x8e, 0x88, 0xbf, 0xc1, 0x57, 0xf2, 0xcf, 0x01, +0x8d, 0x23, 0xe8, 0x0b, 0xc3, 0x02, 0xda, 0x46, +0x89, 0x15, 0x36, 0x5b, 0x57, 0x79, 0x38, 0xe7, +0x29, 0xa3, 0xe7, 0x50, 0xf4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0xe3, +0xd4, 0xf8, 0xe6, 0x5f, 0x49, 0x24, 0xdb, 0xf4, +0xfa, 0x27, 0x93, 0x4e, 0xb6, 0xc2, 0x95, 0x7b, +0x93, 0x78, 0x68, 0x74, 0x6a, 0x63, 0x07, 0xd6, +0x6b, 0x45, 0xd4, 0x69, 0x51, 0xd2, 0x1e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0xce, 0x40, 0xcf, 0x2d, 0x01, 0xf7, 0x37, +0xd5, 0x97, 0x40, 0x36, 0xb4, 0x22, 0xa4, 0xdd, +0x1e, 0xd4, 0x8b, 0xa6, 0x11, 0x5b, 0xac, 0x86, +0x4d, 0x66, 0x14, 0xbb, 0x21, 0x69, 0xa4, 0xdf, +0x53, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0x98, 0x85, 0xb2, 0xbe, 0x79, +0x7d, 0x3b, 0x8d, 0xf7, 0x41, 0x5f, 0x98, 0x87, +0x9f, 0x35, 0xd4, 0x02, 0x88, 0xf9, 0xe0, 0x69, +0x99, 0xc4, 0x61, 0x04, 0xd4, 0x84, 0xeb, 0x9e, +0xe1, 0x67, 0x34, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0x2b, 0x20, 0x57, +0xcd, 0xb6, 0xde, 0x64, 0x1c, 0x4d, 0x13, 0xb8, +0x0f, 0x4d, 0xc7, 0xa1, 0xd5, 0x85, 0x88, 0xcc, +0x90, 0x31, 0xc4, 0x6e, 0x03, 0xfe, 0x8f, 0x74, +0x80, 0xf6, 0xe5, 0xe1, 0xf6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0xda, +0x44, 0x38, 0xa0, 0xab, 0xd8, 0xee, 0xed, 0xc6, +0xfa, 0x00, 0x28, 0xbe, 0x60, 0xe6, 0x41, 0x98, +0x3f, 0x7a, 0xe8, 0xf5, 0x9d, 0xb3, 0x3c, 0xfe, +0x3b, 0x69, 0xae, 0x8d, 0x4e, 0xb8, 0x98, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0xa2, 0xcd, 0x9e, 0x66, 0xde, 0xf0, 0x48, +0xa5, 0xd1, 0xf3, 0xe9, 0x22, 0xe1, 0x20, 0xa2, +0xbf, 0xf0, 0xf4, 0xfe, 0x67, 0x34, 0x29, 0x50, +0xa5, 0x49, 0xd4, 0xee, 0xd9, 0x6b, 0x58, 0x29, +0xd9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x81, 0xc3, 0x89, 0xe8, 0x00, +0x8f, 0x85, 0xfd, 0xc4, 0x23, 0xa5, 0x08, 0x44, +0x4a, 0x73, 0xac, 0x4e, 0xde, 0x47, 0x60, 0x66, +0x3a, 0xd2, 0x68, 0x9e, 0x75, 0xf7, 0x7f, 0x06, +0x82, 0x29, 0x70, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x99, 0x51, 0x4c, +0x59, 0xb3, 0x32, 0xa6, 0x6d, 0xc5, 0xc0, 0xf7, +0xd2, 0x41, 0x43, 0xb6, 0x38, 0xd7, 0xa5, 0x72, +0x83, 0x47, 0x0c, 0xb3, 0x12, 0xb1, 0x62, 0x10, +0xab, 0xfe, 0xed, 0x96, 0x30, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0x64, +0x39, 0x41, 0xa1, 0xcf, 0x15, 0xa5, 0x4c, 0x92, +0x4b, 0x57, 0x96, 0xc9, 0x73, 0x35, 0xcb, 0x0a, +0xe3, 0x29, 0x93, 0x5c, 0x69, 0x7e, 0x7e, 0xe5, +0x12, 0xdb, 0xd3, 0x96, 0x30, 0x26, 0x23, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0x5e, 0x24, 0x28, 0xeb, 0xd0, 0xb1, 0xa8, +0xb6, 0x64, 0x55, 0x80, 0x86, 0x2e, 0x77, 0x48, +0xd4, 0xfa, 0xc5, 0xb7, 0xd2, 0x1a, 0x54, 0x62, +0x97, 0xf2, 0xf9, 0xdf, 0xf3, 0x6d, 0x50, 0x79, +0x3e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0x14, 0x19, 0x85, 0x0b, 0x04, +0x52, 0x52, 0xf6, 0x78, 0xf7, 0x0b, 0xfe, 0xf3, +0xe6, 0x40, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x90, 0x62, 0xff, 0x6f, +0x20, 0x74, 0x51, 0x1f, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0x31, 0x94, 0x6e, +0xdb, 0xe8, 0xc3, 0x11, 0x12, 0x3d, 0x75, 0x32, +0x5b, 0x1b, 0x4c, 0xaf, 0x67, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0xf1, +0xf4, 0xd7, 0x9c, 0x2a, 0x01, 0xff, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0x2c, +0x55, 0xf7, 0x4a, 0x69, 0xe5, 0xee, 0x4a, 0x25, +0x59, 0x2f, 0x4c, 0xf5, 0x28, 0xf8, 0x69, 0x57, +0xce, 0x11, 0xb6, 0x87, 0x31, 0x43, 0xcf, 0xa1, +0x38, 0x3e, 0xbd, 0x40, 0xf0, 0x1f, 0xd1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0x0d, 0x11, 0x96, 0xd7, 0xfb, 0xf0, 0x9b, +0x3f, 0xd3, 0x76, 0xc4, 0x6b, 0xe3, 0x5c, 0x45, +0x04, 0x8b, 0x58, 0x0f, 0x4d, 0xa1, 0x12, 0xbb, +0xe1, 0x15, 0xa5, 0x7e, 0x3c, 0x30, 0xfa, 0xef, +0xc8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0x0a, 0xbf, 0xcc, 0xd5, 0x55, +0x87, 0xaf, 0x2e, 0x04, 0x17, 0xfc, 0x33, 0xe1, +0x8d, 0x0c, 0x00, 0x5d, 0x37, 0xd2, 0xbf, 0x04, +0x68, 0x47, 0xc9, 0x7b, 0xde, 0x37, 0x24, 0xc3, +0x20, 0x6e, 0x9f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0x2e, 0x45, 0x5d, +0x5b, 0x16, 0x67, 0x94, 0xd1, 0xff, 0xd7, 0x06, +0xf3, 0x82, 0xc0, 0x42, 0x0c, 0xdf, 0x01, 0x90, +0x7b, 0xc1, 0xb8, 0xdd, 0xa5, 0x26, 0xc2, 0xea, +0x6b, 0xdb, 0x9f, 0x13, 0x76, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x82, +0x2e, 0xb6, 0x91, 0x80, 0x4d, 0xc0, 0x61, 0x92, +0x68, 0xfc, 0x1e, 0xab, 0xf3, 0x8a, 0x20, 0x71, +0x36, 0xce, 0xea, 0x74, 0xe9, 0xea, 0xf6, 0x05, +0x74, 0x0a, 0xc0, 0x47, 0xea, 0x7c, 0xa7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0x67, 0xbb, 0x60, 0x39, 0x40, 0x55, 0xea, +0x75, 0xc5, 0xee, 0x72, 0x3e, 0xc1, 0xfa, 0x25, +0x14, 0x6f, 0xa0, 0xc0, 0xe6, 0x5c, 0x5b, 0xf2, +0x1e, 0xa2, 0x9b, 0x1f, 0x02, 0xc7, 0x75, 0x4c, +0x46, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0xfd, 0x4f, 0x87, 0x68, 0x95, +0x43, 0x73, 0x8f, 0xa6, 0xc9, 0xbd, 0xc2, 0xed, +0x0f, 0xef, 0x4c, 0xf2, 0x4f, 0x33, 0x99, 0xf3, +0x1f, 0xa2, 0xd4, 0x1c, 0x02, 0x64, 0x8c, 0x22, +0x50, 0xfe, 0x22, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0xec, 0x12, 0x94, +0x81, 0x6d, 0x99, 0x8d, 0xbd, 0x2e, 0x1a, 0x84, +0x20, 0x76, 0xb2, 0x31, 0x88, 0xd4, 0x54, 0x5d, +0xf4, 0xaa, 0xef, 0xab, 0x81, 0xd5, 0x9e, 0x29, +0xc9, 0x12, 0x74, 0x87, 0x81, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0x4e, +0x00, 0x31, 0xcd, 0x82, 0x23, 0xaa, 0x9c, 0xa3, +0xe6, 0x7a, 0xd8, 0x34, 0xb4, 0xb5, 0x7b, 0x6a, +0x2d, 0x8d, 0x3a, 0x4c, 0x67, 0x95, 0xdd, 0xec, +0xd9, 0x8c, 0x28, 0xe7, 0x82, 0x3f, 0xe6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0x97, 0x18, 0x2d, 0xf6, 0x53, 0xee, 0x70, +0xf8, 0x6e, 0x96, 0xc6, 0x82, 0xdd, 0xf0, 0x86, +0x92, 0x38, 0x7f, 0x45, 0xcb, 0xb4, 0xef, 0xee, +0xe0, 0xeb, 0xe6, 0x04, 0xc0, 0xce, 0x99, 0xe4, +0xc5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x67, 0x2c, 0xb6, 0x3f, 0x1e, +0xfb, 0x31, 0xb0, 0x92, 0x5e, 0x16, 0x5e, 0x92, +0xe3, 0xa5, 0xff, 0x7e, 0xd9, 0xc5, 0x11, 0x1c, +0x1d, 0x89, 0x82, 0x06, 0xbe, 0x06, 0x63, 0x28, +0x1d, 0xb1, 0x2c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0x15, 0xe3, 0xb9, +0x04, 0x6b, 0x0f, 0x27, 0xf9, 0xc3, 0xba, 0x7c, +0x09, 0x99, 0x8f, 0xa6, 0x75, 0xda, 0x42, 0x9e, +0x68, 0x57, 0xc4, 0x86, 0xa3, 0xe7, 0x4c, 0x62, +0xd6, 0xaf, 0x92, 0x99, 0x5c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0x2e, +0x34, 0xa2, 0x7f, 0xa5, 0x12, 0x2d, 0x9d, 0x5d, +0xd4, 0xfa, 0xe5, 0x50, 0xe1, 0x08, 0x1d, 0x5d, +0x93, 0xd9, 0xeb, 0x14, 0x58, 0x30, 0x07, 0x68, +0x1c, 0x1a, 0xd8, 0x11, 0x10, 0x43, 0xd9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0x03, 0xa0, 0x6b, 0x2c, 0x1a, 0x80, 0xc0, +0x0c, 0x80, 0xc5, 0xfa, 0x1b, 0x23, 0xa0, 0x22, +0xdc, 0x24, 0x67, 0xcb, 0xec, 0xfa, 0x91, 0x2c, +0xd0, 0xbb, 0x7d, 0xef, 0x56, 0x16, 0x07, 0xf4, +0xd8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0x51, 0x5c, 0x02, 0x8e, 0x7e, +0xa6, 0x41, 0xd1, 0xf4, 0xd4, 0xfe, 0x04, 0x29, +0x93, 0xd4, 0x31, 0x43, 0x78, 0xa0, 0xa4, 0x30, +0xd0, 0xb8, 0x8b, 0x48, 0x04, 0x36, 0xe9, 0xc8, +0xc8, 0x0c, 0x54, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0x83, 0xee, 0xa8, +0xa8, 0x81, 0x19, 0xc0, 0xa2, 0x60, 0x9e, 0x2a, +0x33, 0xb7, 0x7e, 0x4b, 0x56, 0xf7, 0x4c, 0xba, +0xbf, 0x9e, 0x2e, 0xf8, 0x9f, 0xe6, 0x5a, 0x65, +0xb9, 0x11, 0xfc, 0xb2, 0xae, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0x75, +0xbc, 0x52, 0xc6, 0xf0, 0x92, 0x45, 0x1e, 0x75, +0x45, 0xe0, 0x79, 0x76, 0x84, 0x64, 0x92, 0x91, +0x5e, 0x55, 0x3f, 0x92, 0xe4, 0x17, 0xe0, 0x30, +0x75, 0x2b, 0x08, 0xac, 0x5e, 0xc1, 0x28, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0x62, 0x4e, 0x54, 0x0a, 0xab, 0xba, 0xcb, +0xc3, 0xa3, 0x54, 0xfd, 0x5c, 0xcf, 0x93, 0x6b, +0x97, 0xe0, 0x2a, 0x48, 0xf3, 0xf0, 0x47, 0xc4, +0x96, 0x31, 0xfd, 0x03, 0xab, 0x52, 0xe5, 0xbf, +0x79, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0xc1, 0x92, 0x33, 0x01, 0xe6, +0x06, 0x5c, 0x1f, 0x82, 0x9a, 0xbc, 0xf2, 0xfb, +0x03, 0x83, 0xb6, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xee, 0x53, 0x57, 0xe6, +0xc0, 0xad, 0xc3, 0xeb, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0x40, 0xd4, 0x6e, +0xc4, 0xed, 0xdf, 0xbf, 0xc8, 0x47, 0x01, 0x91, +0x8e, 0xcd, 0x87, 0xab, 0xd9, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x95, +0x58, 0x72, 0x06, 0x6d, 0x58, 0xf3, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0xf1, +0x14, 0x51, 0x8a, 0x4d, 0xd4, 0xea, 0xb1, 0xb6, +0xf7, 0xbf, 0xee, 0x90, 0xa4, 0x05, 0x7f, 0xd8, +0x6e, 0x0a, 0x63, 0xbe, 0x0d, 0x09, 0x78, 0x8d, +0xc6, 0x32, 0x86, 0xdd, 0x80, 0xfa, 0x0c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0x2d, 0x09, 0xf8, 0x58, 0xcf, 0x00, 0xee, +0x54, 0x07, 0x5e, 0xc9, 0xa9, 0xb6, 0x2a, 0x54, +0x7a, 0xc0, 0x36, 0x5e, 0xe6, 0x0b, 0x0c, 0x0b, +0x12, 0x92, 0x53, 0x63, 0xd3, 0xc9, 0xe8, 0xd4, +0xde, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0x7b, 0x82, 0x48, 0xa7, 0x53, +0xa9, 0x97, 0x25, 0xa9, 0xa7, 0x5d, 0xe7, 0x25, +0xc6, 0x2c, 0xb4, 0x45, 0xa9, 0xe3, 0x56, 0x08, +0x66, 0xda, 0xd6, 0x6d, 0x71, 0x0c, 0xdf, 0xca, +0x92, 0x06, 0x30, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0x17, 0x38, 0x3a, +0x93, 0x1f, 0x38, 0xe3, 0x77, 0x66, 0xb8, 0x2e, +0x9f, 0x53, 0xd9, 0x6b, 0x09, 0x7c, 0xd4, 0xf0, +0x3e, 0x8e, 0xfc, 0x97, 0x26, 0xf8, 0x0d, 0xed, +0xfc, 0xb4, 0x84, 0x36, 0xdc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0xf6, +0x7f, 0xfb, 0x38, 0x36, 0xb1, 0x8b, 0x4a, 0x67, +0x94, 0x5f, 0xe6, 0x06, 0x88, 0x99, 0x2e, 0xb1, +0xaa, 0x84, 0x16, 0x66, 0x37, 0xfe, 0x22, 0xfb, +0xdd, 0xe9, 0x5c, 0x09, 0xf7, 0xc2, 0xa7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0xa5, 0xc2, 0x83, 0x60, 0x93, 0xa9, 0x8b, +0xa9, 0x1a, 0x4d, 0x53, 0x3a, 0xda, 0x76, 0x53, +0xb2, 0xea, 0xfd, 0x6f, 0xb4, 0x67, 0x9e, 0x99, +0x39, 0x64, 0x89, 0x4d, 0x24, 0xcf, 0x72, 0x37, +0xc2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0x25, 0xd7, 0xc2, 0xcd, 0x48, +0x49, 0x17, 0x57, 0xcd, 0x51, 0xc8, 0xf8, 0xb4, +0xbe, 0x94, 0x58, 0xfe, 0x1e, 0x18, 0x3f, 0x71, +0x9e, 0x68, 0xba, 0x55, 0x3f, 0xe3, 0x8a, 0x10, +0x9f, 0x4f, 0xf1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x54, 0x3d, 0x9a, +0x1e, 0x38, 0x04, 0x66, 0x20, 0x6a, 0xa9, 0xcb, +0xa9, 0x18, 0x1f, 0x62, 0x34, 0x98, 0x8e, 0x09, +0xc0, 0x98, 0xfc, 0x03, 0x71, 0xa7, 0xa1, 0x1e, +0xa6, 0x12, 0x56, 0xa4, 0x2e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0x81, +0xd0, 0x04, 0x7c, 0x22, 0xad, 0x8b, 0xf5, 0x38, +0x2f, 0xea, 0xef, 0x0d, 0xc4, 0x2b, 0x53, 0x82, +0x4c, 0x7a, 0xff, 0xf1, 0x14, 0x1a, 0x0e, 0xce, +0x6d, 0xee, 0xa3, 0x96, 0x8c, 0x6b, 0x09, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0xb6, 0x38, 0x8f, 0xcf, 0x8f, 0xbd, 0x7f, +0x0f, 0x82, 0xb7, 0xed, 0x41, 0x4a, 0x5c, 0xe3, +0x48, 0xe6, 0x33, 0xe5, 0xc2, 0xba, 0x13, 0x6c, +0x10, 0xd1, 0xf2, 0x5c, 0x63, 0x45, 0x44, 0xa3, +0x34, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0x25, 0x49, 0xf0, 0x1e, 0x75, +0x0d, 0x24, 0x39, 0xc3, 0x3a, 0x3e, 0xcc, 0xe6, +0xf6, 0xb3, 0x55, 0xcc, 0x42, 0xe8, 0xfa, 0xed, +0x7b, 0x0f, 0x6e, 0xb4, 0xfd, 0x7d, 0xb2, 0xa2, +0x31, 0xee, 0xf9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x99, 0x95, 0x05, +0xb2, 0x70, 0x54, 0x1c, 0x27, 0x7e, 0x69, 0x55, +0xeb, 0xba, 0x5f, 0xf3, 0x3a, 0xee, 0x73, 0xa0, +0xa8, 0x43, 0xad, 0x96, 0x9d, 0xe2, 0x4b, 0xde, +0xe6, 0xc3, 0xf7, 0x30, 0x8b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0x70, +0xb1, 0xaf, 0xb0, 0x68, 0x0b, 0x09, 0x05, 0xda, +0xd1, 0xf3, 0x9f, 0x17, 0x7e, 0x55, 0x9c, 0xe6, +0xad, 0x8a, 0x11, 0xed, 0x10, 0x8c, 0x0f, 0x68, +0x75, 0x9e, 0x02, 0xf3, 0x56, 0xc5, 0xb2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0xec, 0x39, 0xfb, 0xeb, 0xe8, 0x72, 0x2c, +0xae, 0xfe, 0x57, 0x85, 0xb0, 0xaf, 0x41, 0x11, +0xe7, 0x08, 0x7a, 0xb5, 0x28, 0x41, 0x70, 0xbd, +0x1b, 0x2e, 0xc3, 0xbb, 0x69, 0x80, 0x86, 0x82, +0x00, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0xdf, 0xf7, 0xb0, 0xba, 0x9c, +0x76, 0x1c, 0x18, 0x08, 0x4a, 0xe8, 0x0d, 0x82, +0x1e, 0x6b, 0x9e, 0x9b, 0x24, 0x92, 0x33, 0x69, +0xa4, 0x77, 0xe4, 0x55, 0xeb, 0x98, 0xf9, 0xde, +0x78, 0xa3, 0xeb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0xa7, 0xc9, 0x44, +0xfd, 0x41, 0xa5, 0x38, 0xc5, 0x94, 0xe6, 0x5a, +0x21, 0x72, 0x45, 0x37, 0x49, 0xdf, 0xee, 0x63, +0x8c, 0x4d, 0x52, 0xf2, 0x26, 0x58, 0x63, 0xd3, +0xbe, 0xd9, 0x61, 0x5b, 0xb3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0x5a, +0xa5, 0xe3, 0x16, 0xca, 0x56, 0x4b, 0x15, 0x26, +0xd0, 0x99, 0xe4, 0xd2, 0xf5, 0x0f, 0x42, 0xcb, +0xbc, 0x83, 0x77, 0xff, 0x08, 0xbb, 0xf9, 0xf2, +0xf5, 0x78, 0x42, 0xa0, 0xdd, 0xd2, 0x2c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0x5c, 0x9a, 0x57, 0xe3, 0x2f, 0x52, 0x30, +0x21, 0x8b, 0x6f, 0x2a, 0x4b, 0xb6, 0x6e, 0x29, +0x20, 0x89, 0xe9, 0x5e, 0x15, 0x7c, 0x81, 0x0c, +0xf1, 0xab, 0x4e, 0xfc, 0x5e, 0x47, 0xc4, 0x8a, +0x59, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0x7c, 0x60, 0x82, 0x10, 0x1d, +0x69, 0x8b, 0xef, 0xea, 0x8f, 0xa2, 0x67, 0xad, +0x68, 0xc4, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x24, 0x7e, 0xfc, 0x25, +0xb5, 0x95, 0xe5, 0x4d, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0xc8, 0x25, 0xea, +0x8c, 0xc9, 0x11, 0xd2, 0x8f, 0xaa, 0x84, 0x55, +0x2f, 0x7b, 0xed, 0x7d, 0x07, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x50, +0x4a, 0x68, 0x17, 0x1e, 0xe0, 0x0b, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x83, +0xd6, 0xc3, 0x45, 0xd5, 0xe5, 0xa9, 0xe1, 0x5a, +0x9c, 0x9a, 0xac, 0xa9, 0xda, 0x88, 0x6c, 0xf9, +0xc0, 0xcc, 0xef, 0xa1, 0xc4, 0x56, 0x29, 0xbe, +0xde, 0x79, 0x04, 0xc1, 0xd4, 0xcc, 0x7d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0xb6, 0xab, 0xda, 0x90, 0x97, 0xdd, 0x80, +0xb7, 0x3e, 0xa8, 0x0d, 0xc2, 0x04, 0x6b, 0xa8, +0x21, 0xb0, 0x2c, 0x24, 0x63, 0x44, 0x27, 0x0a, +0xb1, 0xd3, 0x01, 0xb8, 0x6c, 0x4e, 0x63, 0x3b, +0xc0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0x29, 0x1b, 0x5a, 0x5a, 0xde, +0x72, 0x92, 0xd1, 0xd1, 0xc7, 0x28, 0x14, 0x74, +0xaf, 0x3b, 0xb9, 0xdd, 0x55, 0x8b, 0xbe, 0x5a, +0x36, 0xdf, 0x88, 0x64, 0x02, 0x9f, 0xee, 0x49, +0x80, 0xa5, 0x55, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0x07, 0x0b, 0x42, +0x17, 0x26, 0x7e, 0x0c, 0x26, 0x9d, 0xc8, 0x3e, +0x81, 0xfc, 0x47, 0x89, 0x0f, 0x0d, 0x59, 0xf2, +0x7f, 0x69, 0xfa, 0xc0, 0xb8, 0x61, 0xab, 0x17, +0x28, 0xb1, 0x0a, 0xdd, 0x85, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0x73, +0x9a, 0x51, 0x7a, 0x28, 0xc7, 0xf6, 0x50, 0x57, +0x95, 0x7e, 0x60, 0x7e, 0xd9, 0x9c, 0xc0, 0xdf, +0x80, 0x03, 0xcd, 0x70, 0xed, 0x3a, 0x3a, 0x51, +0xd0, 0xbe, 0x8e, 0xef, 0x35, 0x12, 0xc5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x04, 0xe8, 0x8c, 0x33, 0xb9, 0x49, 0xb3, +0x1e, 0x18, 0xf7, 0x7f, 0xb7, 0xe5, 0xa9, 0x0f, +0x8b, 0x1a, 0x5d, 0x52, 0x35, 0x80, 0xce, 0x6f, +0xf6, 0xb9, 0xc0, 0xdf, 0x95, 0x53, 0x54, 0x2c, +0x2a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0xec, 0x9c, 0x25, 0x9a, 0xe2, +0x5c, 0xac, 0x7b, 0xc1, 0x26, 0xb2, 0x43, 0xc0, +0x60, 0x88, 0x1e, 0xfd, 0x09, 0x54, 0x0d, 0xb9, +0x70, 0xfc, 0x3c, 0x87, 0x8c, 0x0a, 0x5b, 0x03, +0xf9, 0xee, 0x5f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0xc8, 0x9b, 0x1a, +0xfa, 0xae, 0x51, 0x80, 0xaa, 0x59, 0x7f, 0xd5, +0x71, 0x80, 0x20, 0xe1, 0x26, 0x34, 0x83, 0x85, +0x68, 0x3f, 0x84, 0x42, 0xd0, 0x1c, 0xe4, 0x80, +0xac, 0x4e, 0xdc, 0x8b, 0x7b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0xd0, +0x55, 0x64, 0xc3, 0xc6, 0x22, 0x84, 0xd1, 0x25, +0xd6, 0x1e, 0x76, 0x1e, 0xc8, 0x3a, 0x66, 0x49, +0xf1, 0xdf, 0xab, 0x6c, 0xb4, 0x34, 0xb9, 0x4e, +0x2f, 0xfa, 0x6b, 0x30, 0x63, 0xbf, 0xe5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0x76, 0x43, 0xad, 0x42, 0x00, 0x0e, 0xcc, +0xbe, 0x8f, 0xee, 0x29, 0x6a, 0x29, 0x68, 0xf7, +0x2d, 0x5b, 0x97, 0x83, 0x72, 0x10, 0xd4, 0xf9, +0xf7, 0x58, 0x0f, 0xc7, 0x08, 0xd8, 0xaa, 0x86, +0x10, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0xd9, 0xdb, 0xf1, 0xca, 0xb7, +0x0b, 0x8e, 0xef, 0xa0, 0xf6, 0x57, 0xf7, 0x8b, +0x83, 0x19, 0xb8, 0x62, 0xcb, 0x56, 0xd9, 0x71, +0xf0, 0x26, 0xee, 0x40, 0xec, 0x8a, 0x94, 0x1b, +0xc1, 0xce, 0x60, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0x18, 0x18, 0x69, +0xad, 0xb4, 0x69, 0xec, 0xc3, 0x7c, 0x7b, 0x6a, +0xbb, 0x83, 0xd4, 0x08, 0x6f, 0x57, 0xaf, 0x71, +0x2c, 0xcc, 0x7d, 0x8e, 0xfd, 0x62, 0xc7, 0x63, +0x72, 0xc4, 0xa5, 0xc0, 0x2f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0x5e, +0x38, 0xf6, 0x22, 0xf9, 0x07, 0xc9, 0x63, 0x5c, +0x60, 0x99, 0x3c, 0xa9, 0xcd, 0x29, 0x4c, 0x3c, +0x07, 0x7d, 0xd7, 0x40, 0xf0, 0x28, 0xd8, 0xc8, +0xdd, 0x21, 0x8c, 0xa1, 0x07, 0x7a, 0xac, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0xf9, 0x51, 0xc0, 0x35, 0x06, 0xd8, 0x98, +0x49, 0x31, 0xa7, 0x59, 0xc4, 0x6a, 0xc6, 0x20, +0xcd, 0x3d, 0xe2, 0x87, 0x23, 0xca, 0x1f, 0xa1, +0x9e, 0x1c, 0xbc, 0x37, 0x63, 0x93, 0xe7, 0x05, +0x57, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0x44, 0x56, 0x2b, 0x8a, 0x75, +0x1f, 0x21, 0x6b, 0xcd, 0x70, 0x8a, 0xa1, 0x03, +0xfb, 0x34, 0x37, 0x51, 0xb9, 0x3e, 0x70, 0x33, +0x44, 0xb7, 0xd1, 0xef, 0x39, 0x28, 0x41, 0x13, +0x37, 0x17, 0x8b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0xdb, 0x3b, 0x22, +0x98, 0x2c, 0x30, 0xc5, 0xd3, 0x8b, 0x4d, 0xd7, +0x33, 0xde, 0x42, 0xe3, 0x36, 0xfc, 0x44, 0x0f, +0xd6, 0x69, 0x59, 0x2d, 0x7b, 0xc9, 0xd7, 0x06, +0xb6, 0x66, 0x47, 0xd6, 0x25, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0xad, +0x57, 0x8d, 0xd7, 0xb9, 0x38, 0x83, 0xb1, 0xd5, +0x53, 0x56, 0x66, 0x77, 0xd5, 0x5b, 0xd3, 0x03, +0x1a, 0x32, 0x09, 0x6f, 0x8c, 0xb7, 0xfa, 0x7c, +0xa7, 0x3b, 0xd3, 0x18, 0x06, 0x4e, 0xd0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0xff, 0xfc, 0xe1, 0x07, 0xcf, 0xf3, 0x43, +0x63, 0xc4, 0x49, 0x14, 0x77, 0x53, 0xa4, 0x9f, +0xc0, 0xfc, 0xf6, 0x77, 0xaa, 0x59, 0xe9, 0x76, +0x92, 0x5f, 0xcc, 0xd2, 0xff, 0xcc, 0x8f, 0x1a, +0x51, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0x84, 0xaf, 0xad, 0x74, 0x95, +0x5b, 0xb6, 0x5e, 0x06, 0x63, 0xd6, 0x3e, 0x49, +0x5a, 0x3c, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x30, 0xbb, 0x6e, 0x6f, +0xcd, 0xcc, 0xb4, 0x75, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0xae, 0x0a, 0xaf, +0x8b, 0xec, 0xbc, 0xf5, 0x92, 0x99, 0x56, 0x0c, +0xe7, 0x4c, 0xe6, 0x1a, 0xa3, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xae, 0x9c, +0xe4, 0xca, 0xee, 0x51, 0x92, 0x2e, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0x2c, +0x52, 0x66, 0x1b, 0xc9, 0xc1, 0x11, 0x23, 0xf8, +0x64, 0x4d, 0x4a, 0x96, 0xab, 0x4c, 0x3a, 0xc5, +0x30, 0x03, 0x77, 0xab, 0xb7, 0xe7, 0xac, 0xe6, +0xd9, 0x8d, 0x80, 0x9f, 0xe5, 0x98, 0x60, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0x1f, 0x20, 0xfd, 0x54, 0xb6, 0x67, 0x72, +0x8f, 0x81, 0xe5, 0x31, 0x2c, 0xfe, 0xec, 0xb7, +0x67, 0x84, 0xf2, 0x25, 0xb0, 0x56, 0xe9, 0x58, +0x68, 0xae, 0x4d, 0x54, 0xb4, 0xb3, 0x76, 0x7c, +0xa6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0x50, 0x85, 0x23, 0xa8, 0xbe, +0x22, 0x06, 0x6c, 0xe0, 0xa0, 0xe6, 0x58, 0x66, +0x20, 0x8a, 0xdc, 0xcd, 0xd1, 0x90, 0x6f, 0xb0, +0x3b, 0x47, 0x74, 0x82, 0x58, 0x11, 0xec, 0x56, +0xe2, 0x87, 0xca, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0xa0, 0x70, 0xa4, +0x31, 0xec, 0x81, 0xe8, 0x50, 0xe5, 0xdd, 0xee, +0xa5, 0x3c, 0x3c, 0x5b, 0x0e, 0xff, 0x99, 0x7d, +0xc8, 0x8c, 0xfe, 0x4c, 0x05, 0x42, 0x6c, 0x6f, +0xcb, 0x36, 0x47, 0xfe, 0xe3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0xcf, +0xa6, 0xc0, 0x1d, 0x15, 0x93, 0xba, 0x96, 0x64, +0x23, 0x48, 0x81, 0xc2, 0x82, 0xaa, 0xad, 0x2d, +0x19, 0x9a, 0x31, 0x1b, 0x40, 0xd9, 0x30, 0x81, +0x3c, 0xba, 0x6a, 0x64, 0xd5, 0x46, 0x65, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0x49, 0x79, 0x18, 0xe8, 0x32, 0xf3, 0x38, +0x90, 0xfc, 0xcc, 0x2a, 0x15, 0xe3, 0x14, 0x03, +0x67, 0x0e, 0xc0, 0x62, 0x7e, 0xa7, 0xf7, 0x6a, +0x4e, 0xcd, 0xd1, 0xdd, 0x6e, 0xc3, 0x0d, 0xfd, +0xc5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0x4c, 0xa2, 0xeb, 0x57, 0xa4, +0xfd, 0x99, 0x12, 0xe8, 0x46, 0x12, 0x8a, 0x13, +0x6b, 0x57, 0x26, 0x52, 0xf8, 0xca, 0xff, 0x1a, +0xb3, 0x44, 0xfb, 0x46, 0x4e, 0x51, 0x37, 0x18, +0x28, 0xe5, 0x0b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0xbe, 0x89, 0xff, +0xc6, 0xb1, 0x23, 0xd4, 0x20, 0x9c, 0xef, 0xdb, +0x7e, 0x53, 0x47, 0x2d, 0x13, 0x53, 0x1d, 0xd0, +0x99, 0xcd, 0x7c, 0xd6, 0x6a, 0xf4, 0x35, 0x72, +0x46, 0x32, 0x06, 0x49, 0x26, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0xa8, +0xb1, 0xcc, 0x8a, 0x15, 0x81, 0x69, 0xdc, 0xaf, +0xcd, 0xa7, 0x30, 0x23, 0x75, 0x31, 0x05, 0x81, +0x14, 0xde, 0x24, 0x88, 0x0c, 0xc7, 0xb2, 0xb0, +0x15, 0x09, 0xa1, 0x19, 0xaf, 0xb4, 0x38, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0xac, 0x1a, 0x01, 0x48, 0x80, 0x4f, 0xcf, +0x02, 0xfa, 0xda, 0x48, 0xb6, 0x28, 0x78, 0x19, +0x24, 0xc4, 0x53, 0xc7, 0xae, 0x14, 0xac, 0xc1, +0xd6, 0xaa, 0x37, 0x18, 0xc9, 0x57, 0x8e, 0xc6, +0x1e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x70, 0xa3, 0x4a, 0x68, 0x85, +0xa3, 0x8d, 0xa4, 0x72, 0x45, 0x80, 0xd4, 0xf3, +0x5e, 0x4f, 0x44, 0xb4, 0x06, 0x9d, 0x5d, 0x49, +0xbb, 0x06, 0x5a, 0x37, 0x75, 0x35, 0x73, 0x64, +0xd3, 0xe4, 0x31, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0x11, 0xd4, 0x57, +0xd8, 0xcc, 0x7d, 0xd1, 0x91, 0xdb, 0x8f, 0xf4, +0x24, 0x40, 0x0c, 0x2f, 0x79, 0x21, 0x8b, 0xd9, +0x43, 0xb2, 0xa5, 0x37, 0xad, 0x53, 0xeb, 0xbc, +0xab, 0xa6, 0x4b, 0x6e, 0xd3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0x62, +0x8f, 0x90, 0xb1, 0x6d, 0x07, 0xc6, 0xbb, 0xea, +0xa8, 0x16, 0x20, 0xef, 0x68, 0xa1, 0xfb, 0xb1, +0xe9, 0x4c, 0x7d, 0xb1, 0xda, 0xe2, 0x7f, 0x62, +0xb0, 0x21, 0x0d, 0x4a, 0x66, 0x12, 0x74, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0x80, 0x49, 0xd0, 0xa2, 0x9c, 0x2f, 0x39, +0x00, 0x6b, 0x31, 0x6c, 0x82, 0xc2, 0xf5, 0x33, +0x5c, 0xfd, 0x19, 0x6c, 0xec, 0xfe, 0x06, 0x4d, +0x62, 0xcf, 0xbc, 0x39, 0x2d, 0xdb, 0xe5, 0xbb, +0x45, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0xc7, 0x1a, 0xeb, 0x8f, 0x87, +0x5c, 0x64, 0x50, 0x31, 0xff, 0x03, 0xa2, 0x09, +0x04, 0x58, 0xb1, 0xb0, 0xa3, 0xf9, 0x7f, 0x35, +0x09, 0xbf, 0xc5, 0x44, 0x7a, 0x51, 0xde, 0x8f, +0xd1, 0xed, 0xa7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0xe4, 0xda, 0xe6, +0xbb, 0x95, 0xcd, 0x04, 0x6d, 0xa3, 0xb3, 0x95, +0x32, 0x4d, 0xaa, 0x58, 0x68, 0xdb, 0x4e, 0x4d, +0x5d, 0x53, 0x28, 0x71, 0x8c, 0xb0, 0xbe, 0xa3, +0xdf, 0x8e, 0x4a, 0xf0, 0xce, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0xa5, +0x16, 0x09, 0xc8, 0xe5, 0xd9, 0x5f, 0x85, 0x04, +0x8b, 0x9b, 0x30, 0x87, 0x85, 0xdd, 0x46, 0x84, +0x65, 0xfe, 0x04, 0x98, 0x7f, 0xe0, 0xe9, 0x14, +0x8f, 0x47, 0xb2, 0x0e, 0x0e, 0x00, 0xa6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x04, 0xa6, 0xbd, 0xd7, 0xa2, 0xc9, 0x3e, +0xb4, 0x48, 0x2b, 0x86, 0x7a, 0xdb, 0x06, 0xcd, +0x12, 0x72, 0x26, 0x11, 0x81, 0x6d, 0xf0, 0x4d, +0x82, 0x3a, 0x9f, 0x15, 0x83, 0x2b, 0x6c, 0x74, +0x1b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0xc6, 0x54, 0x88, 0xda, 0x1f, +0xe4, 0xd8, 0x58, 0x9e, 0x23, 0x9b, 0x79, 0xa0, +0x99, 0x21, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x9f, 0xc8, 0x90, 0x8f, +0x70, 0x6b, 0x0b, 0xa4, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0xb3, 0x8d, 0x65, +0x4d, 0x95, 0xc1, 0xf9, 0xbd, 0xbf, 0x6f, 0xf1, +0x72, 0x55, 0x44, 0xca, 0x47, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0x7a, +0x6a, 0x5a, 0x0f, 0xe5, 0x0b, 0x09, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0x1b, +0x4e, 0x77, 0x67, 0xf8, 0xbd, 0x53, 0x9e, 0xc9, +0x4e, 0x1f, 0xe8, 0xb3, 0xe8, 0xeb, 0xc8, 0xca, +0xe2, 0xdb, 0x2e, 0x2a, 0x10, 0xec, 0x29, 0xb8, +0x63, 0x3e, 0x5b, 0x6b, 0xb6, 0xf6, 0x80, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0x81, 0x28, 0x82, 0x9e, 0x53, 0x10, 0x0a, +0xa4, 0x60, 0xc0, 0xc8, 0x89, 0x24, 0x26, 0xf3, +0x7f, 0x3d, 0xcc, 0x14, 0x54, 0xcb, 0x27, 0x99, +0x77, 0xb5, 0xb7, 0x49, 0x9c, 0x29, 0xb1, 0x06, +0xc2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0x19, 0x07, 0xb6, 0x8b, 0xa7, +0xc8, 0xa2, 0xc2, 0xa8, 0x5c, 0xb7, 0xe1, 0xab, +0x8b, 0x82, 0x41, 0xec, 0x2e, 0xb5, 0xe8, 0x00, +0x39, 0xaf, 0xb6, 0x77, 0xdd, 0xb0, 0x95, 0xa4, +0xbd, 0x37, 0xeb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0xf0, 0xc7, 0x1e, +0x05, 0x1b, 0xcb, 0xdc, 0x5a, 0x49, 0x95, 0x4b, +0x63, 0x03, 0x48, 0xdb, 0x0c, 0xb9, 0x1a, 0x3f, +0xf6, 0xcc, 0x30, 0xa8, 0x6f, 0x2c, 0x41, 0x56, +0x84, 0xe3, 0x76, 0x1e, 0xe0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0xe6, +0x6a, 0x46, 0x01, 0x30, 0x8e, 0x43, 0xe1, 0xe4, +0xe0, 0xa2, 0x32, 0x7b, 0x5e, 0x88, 0x92, 0x7a, +0xcf, 0x78, 0x34, 0xbe, 0x4b, 0xf1, 0xcb, 0xfc, +0xff, 0xfb, 0x61, 0x1b, 0x5c, 0xa4, 0x9f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0xf9, 0x0e, 0x52, 0x16, 0xba, 0xf4, 0x6a, +0xd7, 0xa0, 0x6f, 0x4e, 0x7e, 0xa4, 0xb8, 0xe1, +0xb3, 0xc9, 0x9b, 0x1e, 0xf9, 0x00, 0xf5, 0x32, +0x5c, 0xee, 0x1e, 0x20, 0x34, 0x82, 0x1f, 0xe7, +0x40, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0x23, 0x3b, 0xe1, 0x5a, 0xe6, +0x18, 0xe5, 0x36, 0x6b, 0xf5, 0x03, 0x74, 0xaf, +0x19, 0xaa, 0x34, 0x57, 0x5c, 0xaf, 0x5b, 0xdf, +0xc9, 0x5b, 0x6c, 0x21, 0xab, 0x42, 0x22, 0xb3, +0x77, 0x80, 0x8b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0x7a, 0xd7, 0x11, +0xd3, 0xff, 0xea, 0x68, 0x70, 0x25, 0xb2, 0x47, +0xea, 0x77, 0x2d, 0x79, 0x88, 0x32, 0x5c, 0x92, +0x48, 0x90, 0x41, 0x5d, 0x38, 0x86, 0x8d, 0x01, +0x65, 0x7c, 0x4a, 0xe0, 0xc2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0x54, +0x52, 0xd3, 0xc1, 0x09, 0x6a, 0x35, 0xc1, 0xca, +0x6e, 0xa5, 0x6d, 0x47, 0x88, 0x1e, 0x24, 0x61, +0x83, 0x43, 0xd7, 0x96, 0x04, 0xc0, 0xe6, 0x5d, +0xf6, 0xe4, 0x09, 0xf1, 0x0f, 0xca, 0x15, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0x6d, 0x74, 0xed, 0x72, 0xe2, 0x26, 0xf6, +0x2c, 0xf2, 0x31, 0x88, 0x33, 0x0e, 0xa1, 0x61, +0x7d, 0x8a, 0x90, 0xc7, 0x7c, 0x13, 0x37, 0xfa, +0x5d, 0xc2, 0xb3, 0x4d, 0x58, 0x3c, 0x33, 0xe9, +0x99, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0x45, 0x0e, 0xd7, 0x90, 0xde, +0xe6, 0x92, 0x7f, 0x97, 0xa2, 0xba, 0xf1, 0x96, +0x9a, 0xfc, 0x21, 0x35, 0xd2, 0xfc, 0xda, 0x8e, +0xe2, 0xfe, 0x95, 0x82, 0x78, 0xd0, 0x52, 0x74, +0x7f, 0x16, 0x55, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0x3e, 0x0b, 0xc8, +0x30, 0xd3, 0xf0, 0xa8, 0x03, 0xbb, 0x4f, 0x87, +0x64, 0xbf, 0xb8, 0x84, 0xef, 0x60, 0xbe, 0xf8, +0x76, 0x24, 0xee, 0xd1, 0x89, 0x3c, 0xaa, 0xd6, +0x65, 0x51, 0xaa, 0x5a, 0x68, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0xc2, +0x15, 0x7f, 0x33, 0xa7, 0x9f, 0x77, 0xb1, 0xc8, +0x05, 0x45, 0x10, 0xed, 0x50, 0xcf, 0x37, 0x6b, +0x3e, 0x34, 0xcd, 0x07, 0x2a, 0xf1, 0xdf, 0x17, +0xef, 0xf8, 0x4d, 0x1a, 0x58, 0x02, 0x47, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0x03, 0x5d, 0x41, 0x70, 0x08, 0x83, 0x58, +0x5b, 0x2d, 0x39, 0x3b, 0x59, 0xfa, 0xaf, 0xe9, +0x3a, 0xc3, 0xbd, 0x1b, 0x69, 0xfa, 0xfe, 0xa4, +0x35, 0x55, 0x87, 0x91, 0x0e, 0x0c, 0x8d, 0x1c, +0xba, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0x82, 0x38, 0x5f, 0xef, 0xa7, +0x88, 0x45, 0x33, 0x84, 0xde, 0xef, 0xeb, 0x65, +0x17, 0xdf, 0x30, 0xb3, 0x8e, 0x95, 0xdc, 0x1c, +0x0a, 0xf1, 0x57, 0xde, 0x83, 0xb8, 0xe7, 0x1b, +0xb2, 0xa5, 0x1a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0xbc, 0x74, 0x88, +0x40, 0xd5, 0x97, 0xe8, 0xcb, 0xff, 0x0b, 0xb0, +0x1c, 0x66, 0xb7, 0x4a, 0x11, 0xec, 0xa8, 0xa2, +0x79, 0x78, 0xe6, 0xfb, 0x07, 0x18, 0xc0, 0xe4, +0x49, 0xa9, 0xfa, 0xd9, 0xcd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x92, +0xc0, 0x00, 0xb1, 0xe3, 0x83, 0x9a, 0xd0, 0x50, +0x74, 0xf1, 0x10, 0x62, 0xb6, 0xc9, 0xe3, 0x95, +0x3a, 0x81, 0x4f, 0x1e, 0xe8, 0xb2, 0x78, 0x76, +0x07, 0x31, 0x91, 0xab, 0x79, 0xbc, 0xd1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0x2b, 0xdd, 0x91, 0x98, 0xd0, 0xdc, 0xd9, +0x82, 0x7d, 0x8a, 0x26, 0xae, 0x42, 0x15, 0xcc, +0x02, 0x1a, 0xe2, 0x7c, 0xec, 0x55, 0x41, 0x56, +0xf3, 0xf9, 0xe5, 0x86, 0x80, 0xd6, 0x14, 0xed, +0x46, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0xe9, 0x59, 0xee, 0xab, 0x94, +0x85, 0xe4, 0xcd, 0x5a, 0x12, 0xb0, 0xcc, 0x0f, +0x76, 0xbb, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0xec, 0x21, 0xb1, +0x25, 0x73, 0xbb, 0xee, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0x08, 0xfc, 0x5c, +0x50, 0xf6, 0x4c, 0x39, 0xec, 0x68, 0x51, 0x13, +0xff, 0x23, 0x45, 0x77, 0x69, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x25, +0x8d, 0xf9, 0x01, 0x64, 0x01, 0x77, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0x6c, +0x55, 0xaa, 0x42, 0x9e, 0x93, 0xe1, 0x46, 0x7a, +0x98, 0x29, 0x5a, 0x88, 0xe7, 0xda, 0xea, 0x01, +0xf0, 0xd4, 0xc3, 0x9c, 0xba, 0x04, 0x6e, 0x01, +0xc6, 0x8a, 0xff, 0x8b, 0x15, 0xe7, 0x00, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0x50, 0xf0, 0x17, 0x66, 0x10, 0xf5, 0xfe, +0xda, 0x3d, 0xbe, 0x48, 0x7c, 0xd6, 0x50, 0x0b, +0xfc, 0x30, 0xdc, 0x49, 0xc0, 0x61, 0xf2, 0x69, +0xdc, 0x55, 0x62, 0xea, 0x65, 0x1e, 0x9e, 0x06, +0xe1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0x27, 0xd1, 0x5f, 0x2d, 0x7a, +0x07, 0x8d, 0xa1, 0xe7, 0x1f, 0xe3, 0x7e, 0x60, +0xc9, 0x0d, 0xe0, 0xb8, 0xd4, 0xb2, 0x19, 0xd3, +0x15, 0xc4, 0x13, 0xf2, 0x2f, 0xf0, 0xe6, 0x88, +0x89, 0x20, 0xd6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x0e, 0x7e, 0x5e, +0xc4, 0x58, 0x3d, 0x1e, 0xf7, 0x66, 0xba, 0x9f, +0xa2, 0xf6, 0xef, 0x71, 0x2c, 0x7c, 0xd4, 0x0f, +0x75, 0x9f, 0x07, 0xc7, 0x17, 0xd9, 0x21, 0x75, +0x3b, 0xfd, 0xdc, 0x8d, 0x46, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0xd8, +0xfa, 0x24, 0x20, 0x60, 0x4c, 0xd5, 0x7d, 0x0a, +0x4d, 0x91, 0x4d, 0xd9, 0x7a, 0x7f, 0x96, 0x97, +0x6b, 0xb4, 0x10, 0x48, 0x7d, 0xfb, 0xc1, 0x6b, +0xa4, 0xc2, 0xe1, 0xa8, 0x4a, 0x10, 0x2d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0x08, 0x0a, 0xc6, 0x4d, 0xcd, 0x02, 0x70, +0x2f, 0xce, 0xd7, 0xfd, 0x60, 0x5c, 0x87, 0xa1, +0xbb, 0x65, 0xb9, 0xf6, 0x51, 0x11, 0xad, 0xf0, +0xd5, 0x3a, 0x71, 0x97, 0x64, 0xa1, 0xe4, 0xf4, +0xc2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0xcd, 0x70, 0x60, 0x77, 0x4f, +0x37, 0x53, 0xbb, 0xca, 0x81, 0xdc, 0x07, 0x08, +0xa2, 0xac, 0xc9, 0x08, 0xa2, 0x86, 0xcc, 0x3e, +0xab, 0x52, 0x58, 0x14, 0x83, 0x68, 0xee, 0x39, +0x26, 0xac, 0x01, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x9b, 0xe5, 0x36, +0x39, 0x37, 0xb8, 0x4d, 0x13, 0xd5, 0x10, 0x08, +0xae, 0x7c, 0x54, 0xf2, 0x53, 0x6d, 0x75, 0x8b, +0x2c, 0x2d, 0x34, 0xbe, 0xc7, 0xf5, 0x7d, 0x85, +0x8e, 0x74, 0xc8, 0xf8, 0xe8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0x55, +0x6e, 0x34, 0x06, 0x0b, 0x29, 0x26, 0x2f, 0xe2, +0xff, 0x4f, 0xb3, 0x7d, 0x97, 0xcb, 0x1f, 0xd0, +0x56, 0x06, 0x25, 0xfc, 0x70, 0x68, 0x25, 0xb9, +0x5d, 0x24, 0x22, 0x4b, 0x24, 0x03, 0x89, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0xb4, 0x77, 0x90, 0xb1, 0xbe, 0xcd, 0x9d, +0x7a, 0xe2, 0x08, 0xed, 0x8b, 0xda, 0xca, 0x57, +0x6e, 0x9d, 0x70, 0xee, 0x7f, 0x34, 0xac, 0x55, +0xde, 0x15, 0x29, 0x40, 0xe7, 0x94, 0xdf, 0xe6, +0xa5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0xf4, 0x34, 0xfb, 0x53, 0x18, +0x04, 0xae, 0x97, 0x66, 0x1d, 0x5b, 0x91, 0xfb, +0x97, 0xdc, 0x0f, 0x3d, 0x38, 0x59, 0xb5, 0x93, +0x29, 0x7c, 0xd7, 0x2e, 0xfa, 0x4b, 0x59, 0x05, +0x54, 0xc0, 0x28, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0xb9, 0x6d, 0x51, +0xb3, 0x8c, 0x2d, 0xae, 0x27, 0x3d, 0xc2, 0x09, +0x75, 0xe1, 0x4f, 0x42, 0xa2, 0xdb, 0xd7, 0x64, +0x4c, 0x87, 0x50, 0x6c, 0x8a, 0xd4, 0x6b, 0xda, +0x38, 0xeb, 0x3e, 0x69, 0xc5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x83, +0x4b, 0x8b, 0xb5, 0xa8, 0x2d, 0xc9, 0x43, 0x14, +0xe1, 0x1a, 0xd6, 0x8b, 0x7a, 0xa0, 0x4c, 0x21, +0x2f, 0x9a, 0x15, 0xfd, 0x8e, 0xf0, 0x66, 0x31, +0x78, 0x05, 0x82, 0xdd, 0xb0, 0xd7, 0x36, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0x84, 0xe4, 0xc2, 0x0d, 0x64, 0xd2, 0x12, +0x97, 0xde, 0xeb, 0x76, 0x9b, 0x0d, 0x1c, 0x4d, +0x08, 0xc9, 0x20, 0x2a, 0x64, 0xdb, 0xd7, 0x80, +0x15, 0xbf, 0xc5, 0x7d, 0xae, 0x45, 0xf9, 0x96, +0xbd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0xc0, 0x94, 0x2d, 0x23, 0x91, +0xe1, 0xf9, 0xa1, 0xa3, 0xdf, 0x60, 0xb0, 0xb1, +0x7d, 0x25, 0xad, 0x5b, 0x37, 0xbd, 0xe2, 0xc5, +0xb7, 0x43, 0x70, 0x1f, 0x0b, 0x51, 0xc7, 0x7e, +0xe5, 0x90, 0xd7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0x58, 0xfe, 0xd8, +0x13, 0xf1, 0x23, 0xbd, 0x0f, 0xf0, 0x50, 0xf7, +0x37, 0x22, 0x67, 0xa0, 0x23, 0xe8, 0x85, 0x9e, +0x98, 0x61, 0xd3, 0x9d, 0x3a, 0x41, 0x53, 0x95, +0xef, 0x4e, 0x34, 0xf0, 0x86, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0x73, +0x24, 0xd5, 0xef, 0xa2, 0xff, 0xde, 0x97, 0x21, +0xd4, 0xa3, 0x89, 0xe5, 0x61, 0x25, 0x50, 0xa1, +0xcc, 0xd4, 0x3b, 0x2a, 0x38, 0x84, 0xe7, 0x67, +0x39, 0x01, 0x50, 0xde, 0xc5, 0x4b, 0xd0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0xa0, 0x92, 0x42, 0xcc, 0x81, 0xf9, 0x79, +0x57, 0xaa, 0x25, 0xa3, 0xd3, 0xd9, 0x97, 0xa8, +0x64, 0xf3, 0x71, 0x6f, 0x3b, 0x59, 0x1d, 0xac, +0x7b, 0x1c, 0xa8, 0x65, 0x18, 0xd8, 0xbb, 0xec, +0x71, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0x82, 0xa4, 0x6a, 0x18, 0xc2, +0x60, 0xd3, 0xb1, 0x52, 0xaa, 0x44, 0x41, 0x58, +0x7a, 0x68, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x1f, 0x5a, 0xb6, 0xe7, +0xa9, 0x44, 0xd5, 0xc0, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0x6d, 0xa8, 0x4c, +0x79, 0xd9, 0xc5, 0x03, 0x98, 0xab, 0xe5, 0x38, +0x71, 0x28, 0x3d, 0x62, 0xd6, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x32, +0xc3, 0x5e, 0xbc, 0x0e, 0x46, 0xd3, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0xd2, +0xea, 0xf8, 0xb5, 0xcf, 0x53, 0x55, 0x33, 0xd9, +0xad, 0x82, 0x54, 0x52, 0x68, 0x2a, 0x29, 0xdc, +0x22, 0xa8, 0xa2, 0xd8, 0x8b, 0x27, 0xb5, 0xe6, +0x97, 0xdd, 0x61, 0xee, 0xf3, 0x61, 0x72, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0x34, 0x63, 0x93, 0x76, 0x3e, 0x20, 0x11, +0x0c, 0xe1, 0x52, 0xf9, 0x7f, 0x1e, 0x21, 0xe2, +0xdc, 0x23, 0xb8, 0x31, 0xce, 0x85, 0x18, 0x96, +0xd3, 0x6f, 0x62, 0xa1, 0x4c, 0x36, 0xc5, 0xce, +0x53, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0x4d, 0xfa, 0x5c, 0xa5, 0x04, +0x7d, 0xec, 0x8a, 0x84, 0x88, 0x3f, 0x13, 0xe3, +0x66, 0x4b, 0x27, 0x09, 0x1c, 0x4f, 0x51, 0xe1, +0xea, 0xd9, 0xc4, 0x24, 0x75, 0xdb, 0x54, 0xf5, +0x9b, 0x3a, 0xc4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0x83, 0x7f, 0xe9, +0xa5, 0xb8, 0xd7, 0x98, 0xb9, 0xcd, 0xc3, 0xe8, +0x41, 0x9a, 0xa2, 0x72, 0xa4, 0xe8, 0x42, 0x3f, +0xb0, 0xb4, 0x68, 0x91, 0x7c, 0x88, 0x69, 0xdc, +0x04, 0x84, 0x76, 0x19, 0x71, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0x40, +0x58, 0x23, 0x52, 0xdf, 0x5d, 0x61, 0xe1, 0x60, +0x65, 0x7d, 0xf5, 0x17, 0x50, 0xd3, 0xda, 0xbb, +0x67, 0x2b, 0xf5, 0xc8, 0xef, 0x4e, 0x27, 0xce, +0x56, 0x2c, 0xfe, 0x3f, 0x58, 0x19, 0xff, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0x3b, 0x11, 0x5b, 0xb0, 0x4f, 0xe8, 0xcf, +0x6f, 0x23, 0xc4, 0x8d, 0xfc, 0xe3, 0x20, 0x94, +0x14, 0xb0, 0x22, 0x23, 0x4c, 0x23, 0xe6, 0x0d, +0xe9, 0x26, 0xf2, 0x83, 0x0d, 0x46, 0x8b, 0x56, +0xf5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0xe8, 0xd7, 0x05, 0x43, 0x03, +0xe4, 0x2e, 0xe0, 0x96, 0x5c, 0xf7, 0x04, 0x16, +0x5f, 0xbd, 0xbd, 0x61, 0x3b, 0x4f, 0x29, 0x39, +0xa7, 0x39, 0x38, 0xe3, 0xca, 0xf6, 0xd8, 0xe4, +0x6e, 0xd9, 0x90, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x68, 0x24, 0x67, +0xc1, 0x95, 0x3d, 0xf0, 0x38, 0x0e, 0xbb, 0x7a, +0x8c, 0xf6, 0x3c, 0x44, 0x62, 0x5b, 0x7c, 0x61, +0x13, 0x7b, 0x96, 0x3e, 0xd4, 0x87, 0xd6, 0xc9, +0xbd, 0xc7, 0x41, 0x0c, 0x54, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0xa6, +0x06, 0x34, 0x50, 0x1a, 0xb8, 0xa2, 0x06, 0x04, +0x1b, 0xe3, 0x72, 0x2b, 0xab, 0x8d, 0x09, 0xac, +0xf0, 0x52, 0xf9, 0xbb, 0xd7, 0x6d, 0xf0, 0xee, +0xaf, 0x8b, 0x96, 0xf6, 0x05, 0x42, 0x8a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0xaa, 0xc0, 0x25, 0x5d, 0x95, 0x80, 0xc4, +0x83, 0x09, 0xb6, 0x2e, 0xfd, 0xf3, 0x5e, 0x23, +0xf8, 0xb8, 0x89, 0xc8, 0xa3, 0xc4, 0x35, 0xcd, +0x5c, 0x2f, 0x23, 0x10, 0xde, 0xe9, 0x37, 0x5b, +0x53, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0xa4, 0x6e, 0x0b, 0x57, 0xa4, +0xbe, 0xef, 0xa0, 0x2b, 0x17, 0x62, 0x01, 0x0f, +0xba, 0xba, 0xb5, 0xdf, 0xec, 0x00, 0xd9, 0xbf, +0x70, 0x07, 0x05, 0x03, 0x74, 0x56, 0xdb, 0x06, +0xcc, 0x0a, 0xc4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0xbf, 0x1a, 0xf4, +0x57, 0x35, 0xe3, 0xd7, 0xda, 0x0b, 0x05, 0xd6, +0xdb, 0xe9, 0x06, 0x10, 0x68, 0x21, 0x36, 0x6f, +0xeb, 0xc5, 0xe9, 0xc1, 0xbf, 0x23, 0x85, 0x1b, +0x6a, 0xeb, 0x88, 0x31, 0x95, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0xcd, +0x6b, 0xc3, 0x5b, 0x00, 0xa3, 0x64, 0x26, 0x6e, +0xf0, 0x61, 0x6b, 0x53, 0x70, 0x08, 0x82, 0xfb, +0x83, 0xa1, 0xf9, 0x04, 0xde, 0xd4, 0x1c, 0x41, +0xe8, 0x96, 0x63, 0x51, 0x5e, 0x8e, 0xd9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0x6d, 0xfe, 0x31, 0xe6, 0x7c, 0xa2, 0xde, +0x94, 0x8a, 0x5f, 0xc9, 0x6f, 0x3d, 0xc4, 0x7b, +0x19, 0x51, 0x1e, 0x26, 0x18, 0x08, 0x23, 0x74, +0x2b, 0x75, 0x96, 0x9b, 0x93, 0x09, 0xd2, 0xfb, +0x40, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0x43, 0x60, 0x53, 0xdc, 0x9f, +0x52, 0x2c, 0x07, 0x7c, 0xd5, 0xed, 0x29, 0x1b, +0x63, 0x96, 0xeb, 0x58, 0x58, 0xa2, 0x16, 0x29, +0x70, 0x7a, 0xb0, 0x8e, 0xec, 0x76, 0xc4, 0xda, +0x47, 0x83, 0x32, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0x28, 0x16, 0xce, +0x96, 0x00, 0xc3, 0xfa, 0x4e, 0x80, 0x2e, 0xe6, +0xe3, 0x0b, 0xc1, 0x8f, 0x9e, 0x06, 0xa8, 0x2d, +0x41, 0x8e, 0x78, 0x09, 0x24, 0x0b, 0x21, 0xfd, +0xa8, 0xdb, 0xf0, 0x44, 0xa2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0x41, +0xee, 0xf8, 0xdc, 0xff, 0xd3, 0xce, 0x45, 0x88, +0xe2, 0xde, 0x8e, 0xe6, 0x2b, 0x2f, 0x7b, 0x72, +0x57, 0x87, 0x6b, 0xd6, 0xdd, 0x22, 0x23, 0x9d, +0x6f, 0x21, 0x1c, 0x39, 0xca, 0x76, 0x8b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0xc5, 0xb8, 0x81, 0x9a, 0x1b, 0xee, 0x16, +0x10, 0x2f, 0x68, 0xaf, 0xb8, 0x16, 0xe5, 0x63, +0xaa, 0xa5, 0x18, 0x1f, 0xee, 0x11, 0x8f, 0xb7, +0x29, 0x84, 0xf4, 0x89, 0xa7, 0x48, 0x10, 0x8b, +0x83, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0xf7, 0x8e, 0xd1, 0xbf, 0xf7, +0xb8, 0xdf, 0x92, 0x0d, 0x72, 0xd9, 0x2b, 0xbd, +0x05, 0x20, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xfe, 0x76, 0x71, 0x3d, +0xaf, 0xb1, 0x34, 0xc0, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0xa9, 0xc7, 0xd2, +0xf3, 0xdb, 0xb3, 0x88, 0x94, 0x20, 0xb4, 0x4f, +0x9a, 0x98, 0x05, 0xee, 0x7d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xa8, +0xaa, 0x2d, 0x23, 0x1c, 0x41, 0x62, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0x39, +0x8c, 0x69, 0x0d, 0x18, 0x85, 0xad, 0x20, 0x13, +0xbc, 0x2c, 0x38, 0x50, 0xa0, 0xc0, 0x4d, 0x84, +0xc5, 0x24, 0x63, 0x69, 0x67, 0xe7, 0x1f, 0x18, +0xb5, 0x03, 0x07, 0x92, 0x75, 0x55, 0x70, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0xa7, 0x6b, 0x4f, 0x2a, 0x9e, 0xa7, 0xcd, +0x33, 0x11, 0x39, 0x19, 0x91, 0xa6, 0x5a, 0x82, +0xae, 0x11, 0xd5, 0xaf, 0x84, 0x4a, 0xde, 0x2c, +0x5e, 0x03, 0x41, 0xa0, 0x36, 0xee, 0xac, 0x75, +0x26, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0xe7, 0xa8, 0x3e, 0x45, 0xf0, +0x20, 0x0d, 0x2f, 0x7b, 0xa4, 0x80, 0xc1, 0x58, +0x61, 0x5b, 0xd5, 0xec, 0x6e, 0x58, 0x7c, 0xca, +0xcd, 0xb4, 0xa6, 0x46, 0x5f, 0xb9, 0xb5, 0x8b, +0x94, 0xe0, 0x38, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0x82, 0x88, 0x21, +0x44, 0x4e, 0x3a, 0x28, 0x9c, 0x95, 0xa1, 0x98, +0x70, 0xc2, 0x8e, 0xbb, 0xee, 0x9a, 0x34, 0x87, +0xe3, 0x1f, 0xf3, 0xf0, 0x88, 0xb3, 0x43, 0x56, +0x47, 0xdd, 0x98, 0xae, 0x61, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0x90, +0x63, 0x55, 0xf1, 0x27, 0x91, 0x2d, 0xfd, 0x8a, +0x96, 0xb5, 0x1d, 0xfd, 0x59, 0x86, 0xc9, 0x20, +0xae, 0x3d, 0x1a, 0x1a, 0xe7, 0xbf, 0x45, 0x00, +0xa1, 0x38, 0x1c, 0x86, 0x54, 0x38, 0x7d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0x7b, 0xc3, 0x00, 0xf1, 0x1a, 0x31, 0x98, +0x31, 0xee, 0xdb, 0x7c, 0x1e, 0x75, 0x7f, 0x6f, +0xec, 0x17, 0xe2, 0xcd, 0x2a, 0x65, 0x32, 0xc1, +0x10, 0x73, 0xa8, 0x26, 0x8e, 0xfd, 0xe5, 0x1d, +0x44, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0x44, 0x0b, 0x43, 0x15, 0x72, +0x12, 0x01, 0x83, 0xca, 0x61, 0x0a, 0x33, 0x09, +0x23, 0x0d, 0x34, 0x37, 0x93, 0x02, 0xd7, 0xd8, +0x71, 0xd1, 0xbe, 0x0d, 0xf3, 0xf8, 0x33, 0x6b, +0x4f, 0x14, 0x2d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0x07, 0xc9, 0xe8, +0x8b, 0x82, 0x83, 0x17, 0x84, 0x56, 0x69, 0x02, +0xa2, 0x37, 0xb8, 0x45, 0x11, 0x14, 0x57, 0xfd, +0xf2, 0xce, 0xbf, 0xc6, 0xc6, 0xe3, 0x9b, 0xd4, +0x2d, 0xfc, 0xd6, 0xfe, 0xdc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0x5c, +0xd2, 0x54, 0xd6, 0xfe, 0x1e, 0x1c, 0x2f, 0xfa, +0xc2, 0x1d, 0x3f, 0x26, 0x06, 0xe6, 0x3a, 0xf0, +0x43, 0x20, 0xcb, 0x75, 0xe8, 0xf3, 0xf0, 0xb5, +0x34, 0xf5, 0xeb, 0x72, 0x40, 0xfd, 0x83, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0x09, 0x59, 0x04, 0xd7, 0x4b, 0x1d, 0xae, +0x44, 0x06, 0xe7, 0xca, 0x7f, 0x5b, 0xba, 0x83, +0x3f, 0xc6, 0x85, 0x7a, 0xc7, 0xfb, 0xdd, 0xf4, +0xa7, 0xbf, 0xac, 0x72, 0xfe, 0x33, 0x8f, 0x90, +0x1b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0xd3, 0x1a, 0x87, 0xc5, 0xd4, +0x9a, 0xf0, 0x34, 0xd5, 0xb8, 0x2a, 0x74, 0xb9, +0x7f, 0x74, 0xdc, 0x78, 0x60, 0x6f, 0x39, 0x2b, +0x14, 0xfe, 0x53, 0xaa, 0xee, 0x79, 0x22, 0xeb, +0xb4, 0xfb, 0xa6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0x60, 0xbe, 0xdb, +0x43, 0x71, 0x18, 0x2f, 0x80, 0xce, 0xe3, 0x3f, +0xa1, 0x37, 0xb5, 0x1d, 0x07, 0x68, 0x37, 0xab, +0xb5, 0x7f, 0xbc, 0x7f, 0xf6, 0xca, 0x5e, 0xbe, +0x67, 0xca, 0xac, 0x5e, 0x7a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0x04, +0xc0, 0xff, 0x32, 0x4a, 0xfe, 0x87, 0x32, 0xc2, +0xb9, 0xf2, 0xe8, 0x8d, 0x04, 0xb8, 0x6d, 0x9b, +0xa9, 0xce, 0x4b, 0x03, 0x6f, 0x19, 0x99, 0xbb, +0x4e, 0x8a, 0xa8, 0xc4, 0xb1, 0x54, 0x1e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0x64, 0xc0, 0xfd, 0xbc, 0x4c, 0x4b, 0xdb, +0xb8, 0xab, 0xf1, 0xb7, 0x14, 0x37, 0xd2, 0x85, +0x7c, 0x2f, 0x36, 0xe9, 0x81, 0xdd, 0x8f, 0x6f, +0xca, 0x67, 0x7c, 0xed, 0x1d, 0x3e, 0xea, 0xa4, +0x7d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0x04, 0x25, 0x80, 0x57, 0x02, +0x08, 0x7b, 0xd3, 0x84, 0xc4, 0x9f, 0x2c, 0xfa, +0x08, 0xb7, 0x20, 0x21, 0x94, 0x19, 0x6b, 0x0d, +0x5a, 0x40, 0x5c, 0x23, 0xc7, 0x51, 0x7e, 0x6b, +0x97, 0x44, 0x8d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0x70, 0x84, 0x04, +0x3f, 0x2f, 0xa4, 0xb3, 0xd8, 0x2e, 0x72, 0xef, +0xf7, 0xee, 0x48, 0xee, 0x87, 0x44, 0xfc, 0xf0, +0x88, 0x67, 0x05, 0x75, 0x56, 0xcc, 0x35, 0x86, +0x9b, 0xc5, 0xfb, 0xc2, 0xd4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0x51, +0x7a, 0xe7, 0x8d, 0x15, 0xd8, 0x9b, 0xbd, 0x0d, +0x98, 0x98, 0x3d, 0x70, 0xb7, 0x49, 0x14, 0xc7, +0xe0, 0x4e, 0xca, 0x83, 0x87, 0xbf, 0xc9, 0x90, +0xbe, 0x27, 0xb9, 0x66, 0x78, 0x57, 0x27, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0x84, 0x73, 0xed, 0xcf, 0x90, 0xa6, 0x6f, +0x9a, 0x6e, 0x46, 0x15, 0xa4, 0x6a, 0x02, 0x23, +0xce, 0xd4, 0xd0, 0x48, 0x44, 0x7e, 0xbb, 0x45, +0x92, 0xc8, 0xf0, 0x11, 0x05, 0x45, 0xf1, 0x9a, +0xe2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0x19, 0x25, 0xf2, 0x00, 0x9e, +0xc4, 0xd1, 0x2f, 0x00, 0x3d, 0x13, 0x43, 0x54, +0xab, 0xbb, 0xf7, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xa6, 0xae, 0xa4, 0x4e, +0x9d, 0x60, 0xcd, 0x1d, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0x2d, 0x83, 0xdc, +0xda, 0x3a, 0xb5, 0xc8, 0xcd, 0x66, 0x3b, 0xc2, +0x4c, 0x73, 0xff, 0x0f, 0x18, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xee, 0xe9, +0xf9, 0x6d, 0xf3, 0xb2, 0x4d, 0x14, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0xe0, +0x45, 0x2e, 0x53, 0x27, 0xd3, 0xdd, 0xf6, 0xd6, +0xb4, 0x42, 0x5a, 0x9b, 0x27, 0xb4, 0xaa, 0x68, +0x8d, 0x41, 0xed, 0xee, 0x3b, 0xd4, 0xcb, 0x7e, +0xca, 0x0f, 0xa1, 0x71, 0x94, 0x71, 0x75, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0xde, 0x6d, 0xe1, 0xbd, 0x7a, 0x86, 0x31, +0xb5, 0x23, 0x5d, 0xec, 0x40, 0xf9, 0x56, 0xef, +0x63, 0xcb, 0x65, 0x77, 0x44, 0xae, 0x6b, 0x9a, +0x3a, 0xff, 0xbb, 0x96, 0x27, 0xb5, 0x2a, 0x8c, +0xfe, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0xfd, 0xbc, 0x79, 0x59, 0x95, +0x97, 0x91, 0x02, 0x81, 0x74, 0xb3, 0xcd, 0xe3, +0xf1, 0x17, 0xb2, 0x1d, 0xd1, 0xc7, 0xa0, 0x2f, +0xd7, 0x8c, 0xb9, 0xef, 0x8e, 0x28, 0xd7, 0x57, +0xeb, 0x60, 0xf5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x29, 0xd6, 0x21, +0x9d, 0x3c, 0xdc, 0x39, 0x6b, 0x60, 0xda, 0x85, +0x84, 0xbe, 0x74, 0xac, 0xe9, 0x32, 0x45, 0x1b, +0x9e, 0x65, 0xe4, 0x2c, 0x92, 0x10, 0x05, 0x76, +0x1b, 0x34, 0x57, 0x74, 0x07, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0x22, +0xdd, 0x94, 0x95, 0xa2, 0x81, 0x48, 0x7b, 0x03, +0xd5, 0xac, 0x93, 0xed, 0xac, 0x1e, 0xc1, 0xad, +0x2c, 0x52, 0x0f, 0xde, 0x98, 0xa7, 0x99, 0x69, +0x97, 0xe0, 0x07, 0x9b, 0xb1, 0x62, 0xfe, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0xd5, 0x32, 0xf5, 0x3b, 0xaf, 0x94, 0xb0, +0xdd, 0xf0, 0x5d, 0xc8, 0x17, 0xca, 0x2e, 0x53, +0x8a, 0x38, 0x17, 0x2c, 0xa8, 0xc0, 0xfb, 0x0e, +0xba, 0xa3, 0xe9, 0xad, 0x89, 0x0f, 0xc1, 0xe9, +0x9c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0xc2, 0x54, 0x31, 0x0a, 0x14, +0x40, 0x64, 0x06, 0x10, 0xb6, 0xc1, 0xb9, 0x8a, +0x48, 0xb9, 0x45, 0xd6, 0xa9, 0xd1, 0x15, 0xed, +0xa0, 0x98, 0x63, 0xf4, 0xfb, 0x6d, 0xb1, 0x08, +0xc9, 0x2c, 0x18, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0x79, 0x46, 0x68, +0x53, 0x71, 0x2c, 0x45, 0x28, 0xe0, 0x5b, 0xfd, +0x5d, 0xa6, 0xc8, 0xc5, 0x88, 0x3a, 0xcd, 0x8d, +0xf7, 0x80, 0x9d, 0x45, 0xfb, 0xf4, 0xcf, 0xf8, +0x86, 0x4d, 0x2e, 0x78, 0x7b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x06, +0x89, 0xcf, 0x17, 0x41, 0x65, 0xf0, 0x37, 0xfa, +0x55, 0xa6, 0x35, 0x1d, 0xf6, 0xcb, 0x28, 0xe9, +0xed, 0xdb, 0x97, 0x99, 0x78, 0x53, 0x2a, 0x21, +0x7e, 0x57, 0x6e, 0x7c, 0x00, 0x91, 0x08, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0x74, 0xf7, 0xdb, 0x99, 0x45, 0x89, 0x83, +0xa9, 0x31, 0x58, 0x31, 0x5b, 0xe0, 0x8f, 0x4e, +0x8a, 0x8b, 0xfc, 0x62, 0xcd, 0x00, 0x3f, 0xbe, +0x55, 0xf8, 0x52, 0xc1, 0x51, 0xa2, 0x0a, 0x2b, +0x8a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0xfb, 0xc3, 0x47, 0x46, 0x9a, +0xa9, 0x17, 0x68, 0xfa, 0xf3, 0x52, 0xfb, 0x07, +0x11, 0x73, 0xb5, 0x1c, 0x1f, 0xb5, 0x62, 0xc8, +0xc3, 0x2e, 0x8c, 0x8b, 0x6b, 0x16, 0x06, 0x2e, +0xd8, 0xb0, 0x10, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0xcc, 0x4e, 0xc4, +0xf0, 0xc7, 0xd6, 0x2e, 0xe6, 0xd2, 0x5a, 0x82, +0xec, 0x9f, 0x91, 0x5c, 0x23, 0xa5, 0xf5, 0x39, +0x2a, 0xde, 0xb7, 0x87, 0x75, 0xac, 0x8e, 0xff, +0x78, 0xcb, 0xf4, 0xf4, 0x23, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x2c, +0x26, 0x7d, 0x9b, 0xf9, 0xd3, 0xad, 0x2f, 0xe8, +0xac, 0xe3, 0xf9, 0xe3, 0xfc, 0x47, 0x95, 0x0c, +0xe9, 0x48, 0x6e, 0x7e, 0x04, 0x54, 0x17, 0x53, +0x42, 0x02, 0xdf, 0xf7, 0x50, 0x10, 0x8f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0x69, 0x71, 0x8a, 0x77, 0x0e, 0x85, 0x51, +0xcf, 0xff, 0x1d, 0x89, 0x44, 0x0e, 0x34, 0x07, +0x27, 0x0a, 0xa1, 0x2c, 0x8a, 0xe3, 0xf9, 0xf3, +0x4c, 0xaa, 0x93, 0xe7, 0x63, 0x85, 0x4c, 0x57, +0x23, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0xd3, 0x7c, 0x50, 0xac, 0xa7, +0x0a, 0x64, 0x04, 0x7c, 0x13, 0x5d, 0xee, 0x28, +0xa0, 0x66, 0x7a, 0x16, 0x1d, 0x67, 0x4d, 0x5e, +0x2b, 0xdc, 0x9b, 0xf9, 0x8a, 0x5c, 0x21, 0x23, +0x43, 0x95, 0x69, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0x62, 0xd3, 0x19, +0x4b, 0xd2, 0x2b, 0x41, 0xf3, 0xf0, 0x72, 0x71, +0xda, 0xe2, 0x5c, 0xd9, 0xb4, 0x4d, 0x5f, 0x29, +0xdd, 0x77, 0x2e, 0x0a, 0xcb, 0xf9, 0x90, 0x57, +0x35, 0x6c, 0x1a, 0xb9, 0xed, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x3d, +0x7d, 0x7e, 0x10, 0x1e, 0xfb, 0xe0, 0x9d, 0x32, +0x7d, 0xe6, 0x7e, 0x5a, 0xc3, 0xbe, 0xd2, 0x78, +0xad, 0xab, 0x87, 0xe7, 0x78, 0x77, 0x8f, 0xdc, +0xf3, 0xf6, 0xc3, 0x5a, 0x6d, 0xee, 0xc4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0x81, 0xa2, 0x39, 0x8c, 0xcf, 0xdc, 0x4f, +0x21, 0x51, 0x79, 0xef, 0x9b, 0xe7, 0x59, 0xe8, +0xc0, 0x23, 0x86, 0x1e, 0xe6, 0x02, 0x02, 0xda, +0x6c, 0x5c, 0xaf, 0xb2, 0x08, 0xdc, 0x83, 0x67, +0x49, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0x0c, 0xd0, 0x28, 0x15, 0xcd, +0x9f, 0x71, 0x47, 0x98, 0x1c, 0x3e, 0xe1, 0x76, +0xbc, 0xa5, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xda, 0x6b, 0xe6, 0x08, +0x9f, 0xd5, 0x84, 0x92, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0xc8, 0xe5, 0x64, +0x57, 0xf5, 0x7b, 0xf3, 0x44, 0x7e, 0xce, 0x32, +0xfa, 0xa8, 0xe9, 0x27, 0x3c, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0xb8, +0x88, 0xb5, 0x97, 0x89, 0xf3, 0x65, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0x35, +0xd0, 0x86, 0x71, 0x54, 0x1a, 0x88, 0x54, 0xbe, +0xf1, 0xfd, 0x46, 0x90, 0x97, 0x33, 0x37, 0xa8, +0x59, 0xde, 0x04, 0x71, 0xc6, 0x37, 0xbf, 0xb4, +0x3e, 0x42, 0x60, 0x16, 0x31, 0x87, 0xf8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0x28, 0x8e, 0x14, 0x39, 0x97, 0x6f, 0x18, +0x0e, 0x90, 0x19, 0x64, 0xca, 0xc8, 0x24, 0x5b, +0xe1, 0xf7, 0xe0, 0x23, 0xe0, 0xdf, 0x18, 0xe1, +0x02, 0x2f, 0x1f, 0x94, 0x2d, 0xfb, 0x44, 0xfc, +0xbd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0x19, 0xcf, 0x4f, 0x36, 0x45, +0xa1, 0xfb, 0x7c, 0x24, 0xa4, 0xd1, 0x22, 0xd3, +0x0b, 0x72, 0xf5, 0xd5, 0x52, 0xc1, 0x13, 0x35, +0x4a, 0x03, 0x5b, 0x2e, 0x1a, 0x98, 0x04, 0x75, +0x8d, 0x9f, 0x76, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0x99, 0xe3, 0x36, +0x37, 0x8c, 0x67, 0x5d, 0xa2, 0x31, 0x2c, 0x86, +0xea, 0xbd, 0xe4, 0x74, 0x48, 0xb3, 0xc7, 0x13, +0xcc, 0x6e, 0x1d, 0xd0, 0x57, 0x2b, 0x91, 0xb9, +0x2b, 0x43, 0xad, 0x2d, 0xce, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0xe3, +0x91, 0x3f, 0xfb, 0xc2, 0xe7, 0xf8, 0xe8, 0x50, +0x57, 0xac, 0x9a, 0x36, 0x93, 0xee, 0xe6, 0xc2, +0x11, 0xef, 0x76, 0xff, 0xf9, 0x0a, 0x47, 0xdb, +0x89, 0xcc, 0x42, 0x44, 0xd8, 0x0b, 0x73, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x4c, 0x11, 0x38, 0x35, 0x4e, 0xb9, 0x86, +0x7b, 0x67, 0x92, 0x2d, 0x93, 0x44, 0xba, 0xed, +0xef, 0xd0, 0xfd, 0x1b, 0x1a, 0x91, 0x6a, 0xdf, +0xa6, 0x2e, 0xab, 0x3b, 0xc3, 0x15, 0x7f, 0x85, +0xa1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x86, 0x17, 0x25, 0x6f, 0x87, +0x37, 0x89, 0x64, 0x5d, 0x70, 0xef, 0x0c, 0x98, +0xd3, 0x84, 0x4c, 0x13, 0x4a, 0x87, 0xb3, 0x5d, +0x87, 0xd4, 0xe7, 0xd1, 0x2a, 0xdb, 0xf8, 0x33, +0x8f, 0x68, 0xb7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0xcd, 0x7a, 0xad, +0xa0, 0x9a, 0xbb, 0xc8, 0x4e, 0xca, 0x40, 0x0f, +0x49, 0x16, 0x04, 0x4b, 0x1d, 0x48, 0x4c, 0x2e, +0xdc, 0x6a, 0xae, 0x82, 0xb0, 0xa0, 0x73, 0x7e, +0xa1, 0x3a, 0x0e, 0x34, 0x59, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0xf0, +0xf7, 0x78, 0xd0, 0xa5, 0x4e, 0x74, 0xd3, 0x6e, +0xd3, 0x18, 0x7b, 0x40, 0x4c, 0x03, 0x7d, 0x57, +0x28, 0xf0, 0xfa, 0x2b, 0x84, 0x88, 0x70, 0x4c, +0x25, 0x6e, 0xe6, 0xbf, 0x96, 0xe7, 0x4f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x9a, 0x83, 0xaa, 0xd0, 0x1a, 0x38, 0x8c, +0x3b, 0x71, 0xd3, 0x29, 0xa6, 0x5e, 0x2d, 0x5a, +0xc6, 0x72, 0x05, 0x17, 0xa3, 0x9c, 0x10, 0xc2, +0x39, 0x41, 0xdb, 0xcc, 0x42, 0x82, 0x8d, 0xeb, +0xe0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0x64, 0x8a, 0x7c, 0x52, 0x40, +0xdf, 0xc3, 0xde, 0xac, 0x36, 0x29, 0x40, 0x8f, +0x97, 0xf9, 0x51, 0xda, 0xf6, 0xb9, 0xb1, 0x35, +0x35, 0x7f, 0x4a, 0x21, 0x8a, 0x42, 0xaa, 0x0a, +0x2d, 0x07, 0x5d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0x44, 0xf8, 0x03, +0x4d, 0x53, 0x87, 0x89, 0x50, 0x65, 0xb0, 0xbd, +0x61, 0x15, 0xfb, 0xd3, 0x94, 0x00, 0xef, 0x25, +0x8c, 0x21, 0x62, 0xaa, 0x3d, 0xe0, 0x48, 0x7d, +0xfd, 0x0b, 0x67, 0x85, 0x44, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x95, +0x51, 0x77, 0xa0, 0xf2, 0x76, 0x94, 0x0d, 0x66, +0x58, 0x63, 0x46, 0x2b, 0x76, 0xf8, 0x99, 0x75, +0x74, 0xce, 0x3a, 0x09, 0x2b, 0x33, 0xac, 0x99, +0xf5, 0x03, 0x67, 0x90, 0x6c, 0xfa, 0x66, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0x70, 0x64, 0x2e, 0x9f, 0xbd, 0x73, 0xe0, +0x71, 0xd4, 0x39, 0xad, 0x41, 0x61, 0x0c, 0x7e, +0x19, 0xba, 0x76, 0x49, 0xeb, 0x88, 0xa4, 0x16, +0xb7, 0x51, 0x46, 0xeb, 0x4a, 0xe5, 0xc2, 0x1e, +0xce, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0x98, 0x76, 0x81, 0xfc, 0xf3, +0x97, 0x4e, 0xb1, 0x7c, 0xfc, 0x5e, 0xdb, 0x20, +0xf1, 0xc4, 0x5a, 0x27, 0xd6, 0x3c, 0x6d, 0x90, +0x7e, 0x6a, 0x3b, 0x83, 0xb3, 0xaa, 0x7c, 0x87, +0x74, 0x9f, 0x41, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0x67, 0xbf, 0xd9, +0xc6, 0x8a, 0xcd, 0xfe, 0xaf, 0xab, 0x67, 0x52, +0x2f, 0x29, 0xb1, 0xd9, 0x67, 0x6b, 0xd3, 0x13, +0x6a, 0xb8, 0x97, 0xef, 0x9e, 0x34, 0xfe, 0x27, +0x8d, 0x2b, 0x56, 0xa3, 0xae, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0x1f, +0x95, 0x83, 0x0d, 0x91, 0x9a, 0xf3, 0xff, 0x8d, +0xf3, 0x2a, 0x6c, 0xa2, 0x35, 0x6d, 0x20, 0x2e, +0x84, 0xb2, 0x6d, 0x9d, 0xf0, 0x6e, 0xf3, 0xba, +0x1b, 0xd1, 0xc2, 0x3b, 0x6a, 0x48, 0x69, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0x2c, 0xc9, 0xa0, 0x44, 0x64, 0xcc, 0xf0, +0x38, 0x7a, 0x25, 0x9c, 0xf9, 0x75, 0x7d, 0x4e, +0xb6, 0x34, 0xcc, 0x46, 0x8e, 0x5c, 0xf1, 0xca, +0x14, 0x08, 0x70, 0x67, 0x99, 0xdb, 0x21, 0x44, +0xdd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0xaa, 0xf9, 0x0c, 0x05, 0xbc, +0x3a, 0xc7, 0xe1, 0x1b, 0x79, 0xaf, 0x77, 0xae, +0x38, 0x20, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x9c, 0x57, 0x28, 0x22, +0xd7, 0x49, 0x44, 0xe9, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0xf2, 0x4b, 0x75, +0xfb, 0x08, 0x73, 0x88, 0xce, 0x40, 0x9b, 0xff, +0xe7, 0x00, 0x90, 0x88, 0x6a, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa1, 0x45, +0x0f, 0x1b, 0xde, 0x40, 0xd4, 0x70, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0xad, +0x13, 0x11, 0x5b, 0xca, 0x73, 0xa7, 0xf2, 0x29, +0xd8, 0x00, 0x2e, 0xab, 0x84, 0x17, 0x12, 0x46, +0xd7, 0x53, 0x7c, 0xc7, 0x1b, 0x81, 0x2e, 0x09, +0x40, 0xa2, 0xa4, 0xe0, 0x1a, 0xde, 0x98, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0xd0, 0xef, 0xe5, 0x92, 0x20, 0x1b, 0xaf, +0x9e, 0xbb, 0xa6, 0xcf, 0x48, 0x73, 0xb3, 0x85, +0xfa, 0xc5, 0x7e, 0x69, 0x7b, 0xd0, 0xc2, 0xcd, +0x34, 0x2a, 0x60, 0x79, 0x1a, 0xa4, 0x6f, 0x10, +0xcd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0xe5, 0x2c, 0x3f, 0x20, 0x2b, +0x2c, 0xe0, 0x40, 0x8b, 0xb0, 0xf6, 0x84, 0x47, +0xb4, 0xfd, 0xff, 0xb9, 0xf9, 0x8d, 0xd1, 0x51, +0x41, 0x9e, 0x37, 0x7e, 0x7e, 0x84, 0xa6, 0x2e, +0x29, 0xc7, 0x21, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0xe3, 0x5f, 0xb5, +0xc7, 0x58, 0xfb, 0xba, 0x6d, 0x98, 0xae, 0xb1, +0x48, 0x8e, 0x4e, 0xce, 0xeb, 0x2a, 0xe0, 0x4b, +0x33, 0x13, 0x2c, 0xe3, 0x98, 0x3f, 0x91, 0xa2, +0x7f, 0x18, 0x6f, 0x41, 0x1d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0xbe, +0x0a, 0xfe, 0x3e, 0xd1, 0xc0, 0xc4, 0x3b, 0xb3, +0xb6, 0x98, 0x6f, 0x1d, 0x79, 0xbe, 0x28, 0x2f, +0xf0, 0x1e, 0x21, 0x2c, 0xb7, 0xe8, 0x1e, 0xc6, +0x52, 0x9f, 0x69, 0x66, 0x99, 0xff, 0xcc, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0x5e, 0x45, 0x04, 0xbf, 0xc5, 0xa8, 0x0b, +0x89, 0x38, 0x4c, 0x40, 0xa0, 0xd9, 0xf1, 0xca, +0x1a, 0x4b, 0x39, 0x03, 0x7d, 0x21, 0x29, 0xc5, +0xcf, 0x41, 0xcb, 0x1b, 0x0d, 0x34, 0x7f, 0x75, +0xb7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0xf7, 0x6c, 0x36, 0x2b, 0x0c, +0xf8, 0x63, 0x0b, 0xbb, 0x92, 0x11, 0xb9, 0xe2, +0xca, 0x41, 0xab, 0xba, 0x99, 0x29, 0x63, 0x1d, +0x3a, 0xbe, 0x40, 0xb9, 0xc7, 0x4c, 0xf9, 0x16, +0x62, 0x98, 0x5a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0x78, 0x71, 0xfe, +0x16, 0xfe, 0xe7, 0xe6, 0x2a, 0x34, 0x8b, 0x58, +0xd4, 0x4a, 0x46, 0xa7, 0x45, 0x31, 0x05, 0x91, +0xda, 0x1c, 0xb3, 0x67, 0xbf, 0x4b, 0x65, 0xf3, +0x8e, 0x8b, 0x28, 0x61, 0x53, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0x70, +0x16, 0x3b, 0x91, 0x32, 0x9b, 0x12, 0x5e, 0xb1, +0x62, 0x28, 0x9c, 0x5f, 0x78, 0x8d, 0xcb, 0x4e, +0x0c, 0x35, 0x3e, 0x25, 0xa4, 0xb6, 0x6a, 0x9c, +0x30, 0xe3, 0x27, 0x76, 0x71, 0x62, 0xb0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0x47, 0x79, 0x1c, 0x32, 0xfa, 0xbe, 0x62, +0x1c, 0x7b, 0xc2, 0xfc, 0xd6, 0x65, 0x5f, 0xdf, +0x52, 0x60, 0x4c, 0xa3, 0x59, 0x4e, 0xa8, 0x98, +0x84, 0xf2, 0x0c, 0x20, 0x60, 0x20, 0x00, 0x22, +0x27, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x4b, 0xc1, 0x43, 0xa3, 0x94, +0x8a, 0x07, 0xa5, 0x35, 0x97, 0xc3, 0xae, 0x43, +0xd3, 0xfe, 0x4d, 0x5c, 0x15, 0x17, 0xd6, 0xf2, +0x81, 0x74, 0xd8, 0x29, 0x60, 0x90, 0x00, 0x4e, +0x4a, 0x08, 0xd8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0xc4, 0x22, 0xab, +0x1c, 0x82, 0x93, 0xbc, 0x73, 0x20, 0xe0, 0x59, +0xa4, 0xc2, 0x73, 0xc2, 0xc8, 0x55, 0xa0, 0xdb, +0x09, 0x8c, 0x7a, 0x42, 0xc6, 0xf6, 0x11, 0x9e, +0x60, 0x29, 0x8f, 0xdd, 0xdf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0xcb, +0xae, 0xb4, 0x66, 0x43, 0x44, 0x23, 0x22, 0xa4, +0xdb, 0xc4, 0xf8, 0xbe, 0x09, 0xa2, 0x0d, 0x2a, +0x72, 0xbe, 0xcb, 0x1f, 0xc7, 0xd8, 0x0a, 0xf1, +0x33, 0x45, 0xd2, 0x84, 0x49, 0x82, 0x15, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x93, 0xe5, 0x56, 0x55, 0x4d, 0xa9, 0x8d, +0x74, 0x6d, 0xc4, 0xb8, 0xe5, 0xda, 0x07, 0xf3, +0xdd, 0x09, 0x03, 0xeb, 0x8f, 0xf1, 0x5a, 0x74, +0x78, 0xbe, 0x09, 0xab, 0x14, 0xb9, 0x11, 0x5f, +0xfc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x8f, 0xb6, 0x2b, 0xa7, 0xa9, +0xd3, 0x1d, 0xc1, 0x49, 0x5d, 0x93, 0x5a, 0x4d, +0xce, 0x4d, 0x1b, 0x0a, 0xf3, 0xf0, 0x7b, 0x0c, +0xf3, 0x43, 0x91, 0x60, 0xff, 0x2d, 0x4e, 0xd1, +0xc8, 0x6b, 0x20, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0x1b, 0x05, 0x33, +0x47, 0x4e, 0x59, 0xb4, 0x41, 0x66, 0xfd, 0xa3, +0xf6, 0xaa, 0x9e, 0xbd, 0xba, 0x56, 0x94, 0xd8, +0xc0, 0xf1, 0x54, 0x6c, 0xe2, 0x7c, 0xc4, 0xc7, +0x4a, 0x99, 0x57, 0xfd, 0x7a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0x53, +0x66, 0x70, 0x5f, 0x7d, 0xe5, 0xd8, 0x41, 0xd4, +0x5b, 0x22, 0xef, 0xf7, 0x7d, 0xb3, 0x42, 0x01, +0xeb, 0x30, 0x84, 0x09, 0x85, 0xd7, 0xfe, 0x63, +0xff, 0x2c, 0xc3, 0x48, 0x79, 0x82, 0x87, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0xe4, 0x7c, 0x4e, 0x9d, 0x81, 0xcd, 0x03, +0xff, 0x52, 0x99, 0x1b, 0x84, 0xbe, 0xdb, 0xf1, +0x58, 0x42, 0x42, 0x09, 0xb0, 0x77, 0x4c, 0xbc, +0xf0, 0xbc, 0x9c, 0xd5, 0x1e, 0x1e, 0x77, 0x82, +0xa0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0x1c, 0xf1, 0x9f, 0x7a, 0x64, +0xf5, 0x46, 0x8e, 0x5c, 0xc5, 0xfb, 0x1b, 0x1e, +0xd8, 0x5e, 0xda, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x8a, 0x7a, 0xc8, 0xd8, +0x03, 0x02, 0x55, 0x17, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x66, 0x10, 0xc9, +0x5c, 0xbc, 0xee, 0x6a, 0x3a, 0x1b, 0x5c, 0x2b, +0x46, 0xba, 0xaa, 0x21, 0x3e, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9b, 0x3a, +0xbe, 0xe7, 0x1c, 0x80, 0x07, 0x09, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x48, +0x26, 0x8a, 0xa6, 0x57, 0xd5, 0x33, 0x4e, 0x4c, +0x27, 0x4c, 0x2c, 0x21, 0xa5, 0xd0, 0xc5, 0x00, +0x15, 0xd6, 0x7b, 0x88, 0xb8, 0x3c, 0x71, 0xcd, +0x3c, 0xe4, 0x8d, 0x15, 0xf1, 0xe3, 0x1b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0x09, 0x9a, 0xc1, 0xe7, 0x2a, 0x21, 0x07, +0x31, 0x14, 0xa2, 0xb0, 0xb9, 0x0b, 0x6c, 0xe9, +0xf0, 0x5e, 0x39, 0xbe, 0x4d, 0x51, 0xd6, 0xd8, +0xd2, 0x92, 0xcb, 0xdc, 0x6e, 0x79, 0x7d, 0x60, +0xa3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0x35, 0xbe, 0xdc, 0x39, 0xcd, +0xb0, 0xbb, 0x8c, 0x9e, 0x1b, 0xf6, 0x87, 0x52, +0x00, 0x57, 0xd4, 0x46, 0xce, 0x3b, 0xe8, 0xa8, +0x5b, 0x27, 0xb0, 0x20, 0x3c, 0x3c, 0xbd, 0x1e, +0x24, 0xbb, 0x92, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0x07, 0x4a, 0x91, +0xc7, 0x3b, 0x8b, 0x97, 0xba, 0xf5, 0x49, 0xbc, +0x45, 0xbc, 0x9f, 0xe8, 0x00, 0x48, 0x31, 0x61, +0xa4, 0x0d, 0xf2, 0x73, 0x41, 0x8e, 0xd0, 0xf3, +0xb9, 0x50, 0x81, 0x86, 0x58, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0x65, +0x17, 0x4c, 0xf7, 0xe7, 0x17, 0xb9, 0x4c, 0xbc, +0xf4, 0x5b, 0x6a, 0x51, 0xbb, 0x74, 0xce, 0x6e, +0x60, 0x0e, 0xe9, 0x77, 0x93, 0x68, 0xd4, 0x8b, +0xfc, 0xe4, 0x35, 0x14, 0xd1, 0x3c, 0x5c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0xac, 0x23, 0x62, 0xcd, 0x46, 0x5a, 0xf5, +0x25, 0x44, 0xe5, 0x8b, 0xe8, 0x2f, 0x4a, 0x63, +0xe9, 0xdc, 0x53, 0x53, 0x93, 0x92, 0xb8, 0x38, +0x25, 0xa7, 0xc2, 0x13, 0xbf, 0xc0, 0x83, 0x1f, +0xa4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0xc8, 0x01, 0x36, 0x04, 0xad, +0x86, 0x20, 0x3b, 0xf2, 0xa4, 0x9a, 0x5d, 0xf5, +0x8f, 0xa9, 0x94, 0x94, 0x3d, 0xf3, 0xc1, 0x56, +0x53, 0x7c, 0x99, 0x6a, 0xce, 0x81, 0xfd, 0xeb, +0x04, 0x69, 0xfe, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0xf6, 0x10, 0xd9, +0x38, 0xda, 0x24, 0x2b, 0x7b, 0x41, 0xf1, 0xa6, +0xc6, 0xe3, 0x2a, 0xd2, 0xe4, 0xef, 0x38, 0xb6, +0x54, 0x29, 0x76, 0xd8, 0x09, 0x98, 0x55, 0xf2, +0x33, 0x01, 0xca, 0x0a, 0x0c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0xd0, +0xbd, 0x68, 0xb4, 0x2b, 0xac, 0x06, 0xb1, 0x9d, +0x3c, 0x86, 0xed, 0xef, 0x8d, 0x54, 0x34, 0x8c, +0x8d, 0x4e, 0xad, 0xc1, 0x03, 0x26, 0xfa, 0x38, +0x6d, 0x4f, 0xb4, 0x1a, 0x69, 0xf3, 0x6f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0x34, 0x0c, 0x27, 0xd2, 0x12, 0xe7, 0x52, +0xaa, 0x07, 0xf8, 0x55, 0x78, 0x90, 0xe8, 0x78, +0xc2, 0x7d, 0xcd, 0xf2, 0x1c, 0x1e, 0xbd, 0x51, +0xce, 0xc3, 0xaf, 0xcb, 0x28, 0x07, 0x93, 0x13, +0x99, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0xe5, 0x56, 0x81, 0xb5, 0x90, +0x41, 0x17, 0x77, 0x46, 0x56, 0x84, 0x29, 0x8f, +0xc0, 0x0a, 0xa9, 0xf6, 0x47, 0xa5, 0xeb, 0x97, +0x4d, 0xa6, 0x8e, 0x2b, 0x0f, 0x09, 0x94, 0x45, +0x0f, 0x7d, 0x1b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0xa7, 0x0d, 0x28, +0xe9, 0x0e, 0x50, 0x7e, 0xac, 0xd5, 0x22, 0x64, +0x77, 0xf4, 0x48, 0x19, 0xf8, 0x7c, 0xd6, 0x9b, +0xe6, 0x68, 0x45, 0xf9, 0xa5, 0x46, 0xfb, 0x3d, +0xcb, 0x4a, 0xae, 0x6e, 0x1e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0x01, +0x1c, 0x40, 0xa0, 0x7b, 0xf2, 0x3e, 0xe2, 0x33, +0x3e, 0xad, 0x3d, 0xee, 0x1b, 0xbe, 0xa1, 0x76, +0x1f, 0x3f, 0x05, 0x52, 0x7d, 0x53, 0xbb, 0x6f, +0x38, 0x8a, 0xd0, 0x06, 0x3d, 0x61, 0xaf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0x4f, 0xc3, 0x69, 0x5c, 0x80, 0x3f, 0x12, +0x21, 0x79, 0x22, 0x82, 0x54, 0x93, 0x69, 0x1c, +0xc3, 0x67, 0x24, 0x1b, 0x3b, 0x7a, 0x88, 0x9a, +0x45, 0xa7, 0xf6, 0xf4, 0x2d, 0xbb, 0xef, 0xf7, +0x92, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0x00, 0xf7, 0x5f, 0x81, 0xbd, +0xe1, 0x20, 0xe9, 0x6d, 0xae, 0xc0, 0x18, 0xa4, +0xcf, 0x4b, 0xe2, 0xdc, 0xd3, 0xad, 0xfd, 0x14, +0x06, 0x74, 0x04, 0xe2, 0x06, 0xdc, 0x20, 0x62, +0xa4, 0x03, 0x24, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x79, 0xd5, 0x93, +0xdf, 0x9e, 0xbe, 0x33, 0xbe, 0x95, 0x9d, 0xa4, +0x3b, 0x6e, 0xd4, 0x2d, 0x06, 0x68, 0xaa, 0x88, +0x3a, 0x49, 0x28, 0x81, 0xde, 0x6e, 0xa4, 0xdf, +0x7e, 0xb5, 0x66, 0x05, 0x06, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0xf0, +0x00, 0xf5, 0x6b, 0xcd, 0xb9, 0x92, 0x13, 0x2f, +0x34, 0xac, 0x46, 0x15, 0xbd, 0x85, 0x32, 0x16, +0xbf, 0x14, 0x7e, 0x1b, 0xde, 0x18, 0xb9, 0x1a, +0xf7, 0x5b, 0xd9, 0xfe, 0x13, 0x93, 0x84, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0xa0, 0x8f, 0x8d, 0xad, 0xc8, 0x06, 0xe6, +0x6a, 0x3a, 0x40, 0x68, 0xfe, 0xb2, 0xc6, 0x54, +0xdc, 0xee, 0x6d, 0x6a, 0x72, 0x1b, 0xfa, 0xd4, +0x07, 0x40, 0x7b, 0x31, 0x31, 0x65, 0x1b, 0x76, +0x4a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0xb0, 0x14, 0x60, 0x12, 0x7c, +0x65, 0x47, 0xaf, 0x96, 0x0e, 0xe4, 0x32, 0x47, +0xba, 0x41, 0xd9, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xe4, 0x2d, 0x50, 0xb4, +0x03, 0xdd, 0xfa, 0x65, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0xb8, 0x7e, 0x8d, +0x2e, 0x5f, 0xb2, 0x54, 0x51, 0x38, 0x71, 0xf0, +0x48, 0xa5, 0x6f, 0xff, 0xb2, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbd, 0x46, +0x79, 0xc5, 0x29, 0xba, 0xc6, 0xb7, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x6f, +0x3d, 0x14, 0xcf, 0x34, 0xd7, 0xfe, 0xdb, 0x5f, +0xab, 0xc7, 0x2c, 0xe7, 0x36, 0xfd, 0x27, 0xc4, +0x12, 0x6a, 0x9b, 0xf5, 0x57, 0x12, 0x2c, 0xd3, +0x02, 0x2d, 0x0d, 0xef, 0x19, 0x48, 0x4c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0x8e, 0xfa, 0x70, 0xe6, 0x7b, 0xb0, 0x06, +0xe8, 0x57, 0x16, 0x35, 0x74, 0x17, 0x4f, 0xf7, +0x05, 0x9e, 0x5b, 0x45, 0x3e, 0x60, 0xcd, 0x3d, +0x6b, 0xa3, 0x4b, 0x29, 0x87, 0x55, 0x45, 0x52, +0x44, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0xdb, 0x9d, 0x23, 0x38, 0xd0, +0xef, 0xfc, 0x57, 0x4e, 0x1d, 0xc3, 0x82, 0x75, +0xe6, 0x33, 0x5c, 0xe2, 0x3d, 0x83, 0x93, 0x4e, +0xa0, 0x8d, 0x53, 0x32, 0x83, 0x74, 0x52, 0x1f, +0xb7, 0x01, 0x2c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0x37, 0x66, 0x8f, +0x9e, 0xac, 0x81, 0x2f, 0xa7, 0x33, 0x38, 0x5d, +0x1c, 0x5d, 0x37, 0x83, 0xb4, 0xa8, 0x93, 0xf0, +0x6f, 0xc8, 0xa2, 0xe0, 0x4a, 0x8a, 0x36, 0x8b, +0x7c, 0x7b, 0x09, 0x21, 0xbc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x02, +0xee, 0x9d, 0x6c, 0xa2, 0xe0, 0x9a, 0x2f, 0xaf, +0x89, 0x56, 0xee, 0xf7, 0xe3, 0x30, 0x05, 0x52, +0xa1, 0x41, 0x33, 0x12, 0x2e, 0x7d, 0x26, 0x50, +0x3d, 0x12, 0x55, 0x6b, 0x56, 0x28, 0xd9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0x8c, 0x0d, 0x31, 0xe0, 0xf2, 0x13, 0xdd, +0x8d, 0xef, 0xd1, 0x90, 0x39, 0x2c, 0xe4, 0xaa, +0x99, 0xa3, 0x71, 0xec, 0x26, 0x69, 0x9b, 0x3d, +0xa8, 0x42, 0xb7, 0xde, 0x8f, 0xd2, 0x62, 0x95, +0x5d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0xe6, 0xf3, 0x2e, 0x62, 0x15, +0xe3, 0xdd, 0x26, 0x05, 0x45, 0x24, 0x13, 0xa2, +0x14, 0xa1, 0x78, 0x2b, 0x77, 0xe5, 0x66, 0x90, +0x73, 0x37, 0xe9, 0x3a, 0x0b, 0x63, 0xa6, 0x8c, +0x76, 0x34, 0xf4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0xe6, 0x06, 0x2d, +0xc0, 0x06, 0x10, 0x0d, 0x32, 0xf8, 0xdb, 0x21, +0x7b, 0xe2, 0x45, 0x0f, 0x93, 0x6a, 0x59, 0x8e, +0x66, 0xf7, 0xff, 0xa3, 0x04, 0x54, 0xa7, 0xc6, +0x9b, 0xb0, 0xeb, 0x71, 0x49, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0x3d, +0xfc, 0xc5, 0x94, 0x5c, 0x12, 0xeb, 0x75, 0x2d, +0xeb, 0xfc, 0x64, 0xf0, 0x73, 0x23, 0x15, 0xaf, +0x7d, 0xad, 0x1d, 0xc7, 0xc6, 0x33, 0x0a, 0x95, +0x6d, 0x29, 0xef, 0x9e, 0xd4, 0xdc, 0xdb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0xd8, 0x41, 0xe2, 0x63, 0x9e, 0x78, 0x4a, +0xfb, 0x8a, 0x30, 0xd1, 0x74, 0xe1, 0x28, 0x08, +0xd6, 0xcd, 0x3a, 0x52, 0x2d, 0x4e, 0xa5, 0xc5, +0x01, 0x09, 0xf3, 0x0b, 0xa2, 0xe1, 0x69, 0x0c, +0x5d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0xe8, 0x8a, 0x7d, 0xfb, 0x87, +0x99, 0xe6, 0x76, 0xe3, 0xb3, 0x1a, 0xcf, 0xab, +0x4f, 0x0a, 0x48, 0xb0, 0xed, 0x0d, 0x9f, 0x50, +0x1c, 0xd3, 0xf9, 0x07, 0x04, 0x4c, 0xd4, 0xf6, +0xdd, 0x8d, 0x2b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x3c, 0xa2, 0x82, +0xa8, 0xca, 0x9f, 0x91, 0x63, 0xf0, 0xae, 0x25, +0xbd, 0xe2, 0x73, 0xa6, 0x1a, 0xec, 0x92, 0xe7, +0x18, 0xd5, 0x08, 0x85, 0xba, 0x57, 0xe5, 0x19, +0xae, 0xbb, 0xb2, 0x70, 0x16, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0x1b, +0xd9, 0x64, 0xf6, 0xde, 0x2e, 0x2c, 0x24, 0xa4, +0x35, 0x5a, 0x70, 0x4c, 0xe2, 0xb1, 0xdf, 0x2e, +0x9f, 0xbc, 0x63, 0xc7, 0x60, 0x7a, 0x8b, 0x5f, +0xa5, 0xab, 0xd0, 0x2a, 0x43, 0x0d, 0x5c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0x68, 0x74, 0xc0, 0x61, 0x7b, 0xcd, 0x17, +0xa5, 0x1f, 0x7d, 0xa3, 0xdb, 0x32, 0xa4, 0x9f, +0xa4, 0x62, 0xac, 0x04, 0x9a, 0x0c, 0xfb, 0xc3, +0x71, 0x67, 0xe9, 0x17, 0xab, 0xb2, 0x11, 0x34, +0xb6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0xcd, 0xab, 0x46, 0x08, 0x5e, +0xc7, 0x75, 0x98, 0x37, 0x1f, 0x18, 0xe5, 0x3f, +0xae, 0x27, 0xbe, 0x81, 0x21, 0xae, 0x90, 0x64, +0xe5, 0x8c, 0x1f, 0x2a, 0x6b, 0xf9, 0x43, 0x89, +0xdd, 0x77, 0x1c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0xb1, 0xee, 0xbf, +0xe0, 0x2c, 0x4a, 0x74, 0x3d, 0xd6, 0x06, 0x39, +0x41, 0x1d, 0xe7, 0x43, 0x55, 0xa7, 0x19, 0x28, +0x23, 0x15, 0x0c, 0xa8, 0x21, 0xa6, 0x87, 0x8e, +0x3d, 0x8b, 0xdf, 0x55, 0xd3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0xcf, +0xa4, 0x97, 0xd3, 0x46, 0xc0, 0x83, 0x89, 0x40, +0x1f, 0x3f, 0x93, 0x4f, 0x55, 0xb2, 0x81, 0x1f, +0x7f, 0x75, 0xde, 0xbf, 0x4d, 0x12, 0x6a, 0x57, +0xba, 0xbe, 0xac, 0x7f, 0xbc, 0x15, 0x38, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0x2c, 0xbc, 0x48, 0x1d, 0xf0, 0xf4, 0x9d, +0x97, 0xcb, 0xbf, 0xdf, 0x5d, 0x9b, 0x43, 0x49, +0x8f, 0xa3, 0x5b, 0xb5, 0x90, 0xb1, 0xed, 0xd8, +0xee, 0x23, 0x68, 0x3f, 0xf9, 0x50, 0x13, 0x13, +0x30, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0xaa, 0x99, 0xcd, 0xf2, 0xcc, +0xfc, 0x72, 0x7b, 0x70, 0x6c, 0x2e, 0x30, 0x9e, +0xe9, 0x27, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x4e, 0x39, 0x20, 0x24, +0xef, 0xb2, 0xe3, 0xf4, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0x62, 0x76, 0x4c, +0x0f, 0x61, 0xaf, 0xa5, 0x78, 0x9c, 0x62, 0x04, +0x2b, 0xc3, 0xc1, 0x70, 0xb3, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x93, +0xce, 0x25, 0x27, 0xc4, 0xb8, 0x40, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x1d, +0xc7, 0x16, 0xd4, 0xbf, 0x11, 0x8b, 0x54, 0xfa, +0x1e, 0x95, 0xe4, 0xf1, 0xd4, 0x32, 0x56, 0x9f, +0x97, 0x99, 0xe2, 0xa4, 0x24, 0x6a, 0x87, 0x6b, +0xe0, 0x82, 0xf5, 0x62, 0x24, 0xaf, 0xeb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0x2d, 0xd0, 0x0c, 0x7b, 0x3d, 0xa3, 0x6e, +0xae, 0x3c, 0x5d, 0x90, 0xcb, 0xbb, 0x51, 0xbb, +0x96, 0x49, 0x2d, 0xa1, 0x74, 0xc8, 0x67, 0xe2, +0xef, 0x9f, 0x8c, 0x31, 0xc6, 0x17, 0x97, 0xd4, +0x3b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0xb5, 0x20, 0x2b, 0x28, 0x40, +0x6d, 0x44, 0xc5, 0xbd, 0xd9, 0xf8, 0xe4, 0x4f, +0x0e, 0x21, 0xbf, 0x7e, 0x09, 0x52, 0xa0, 0x9b, +0x67, 0x47, 0x90, 0x03, 0x6d, 0x18, 0x7a, 0x7b, +0xa8, 0x44, 0x7b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0xf4, 0xb6, 0xcc, +0x2f, 0xd4, 0x22, 0xbc, 0x5b, 0x0b, 0x81, 0xe5, +0x3a, 0x59, 0xa2, 0x29, 0x9a, 0x4c, 0x4c, 0xb9, +0x2e, 0xce, 0x47, 0x13, 0x60, 0xa1, 0x0c, 0xbd, +0x35, 0xcd, 0x59, 0x80, 0x8c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0x44, +0x45, 0xd2, 0x04, 0x7a, 0xa4, 0xe1, 0xd2, 0x57, +0x0b, 0x04, 0x38, 0xab, 0x98, 0x80, 0xde, 0xaa, +0x6e, 0xca, 0x94, 0x14, 0xe9, 0x97, 0x13, 0x1e, +0x00, 0x7a, 0x1e, 0x30, 0xc1, 0x2c, 0x6d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0xd1, 0xe8, 0x94, 0x89, 0x58, 0x62, 0x6d, +0x26, 0x6e, 0x68, 0x62, 0xc0, 0xd9, 0x4e, 0xea, +0x98, 0x47, 0x08, 0x3d, 0xa0, 0xaf, 0x93, 0x12, +0x01, 0xcb, 0xa8, 0xef, 0xff, 0xe4, 0xd3, 0xae, +0x7b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x15, 0xb8, 0xd6, 0x98, 0x48, +0xff, 0x54, 0xb7, 0x9a, 0xcf, 0xd9, 0x0d, 0xc1, +0x27, 0xae, 0xbf, 0x1d, 0xad, 0xa5, 0x7a, 0xd3, +0xe6, 0x74, 0x3c, 0x05, 0xda, 0x70, 0x2f, 0x62, +0x1a, 0xe8, 0xd7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0x61, 0x3d, 0x06, +0x9e, 0xed, 0xaf, 0x77, 0xcf, 0x1f, 0x63, 0x4f, +0x54, 0x51, 0x16, 0xa8, 0x9e, 0xde, 0xd9, 0x4b, +0x26, 0xcc, 0x20, 0xdd, 0x64, 0xdc, 0x1f, 0x9a, +0xd1, 0xab, 0x8b, 0xe4, 0x3f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0xca, +0x23, 0x96, 0xb4, 0x4e, 0xa7, 0x5a, 0x76, 0xbf, +0xfd, 0x72, 0x1d, 0xa4, 0x39, 0x9b, 0xba, 0xba, +0x94, 0x02, 0x51, 0xc2, 0xc9, 0xec, 0x8d, 0xae, +0xea, 0x22, 0xa0, 0xe6, 0xea, 0x8c, 0xe4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0x4a, 0x3a, 0x50, 0x7e, 0xc2, 0x07, 0x85, +0x81, 0x81, 0xba, 0xba, 0xd1, 0xc7, 0xe0, 0x9d, +0x6a, 0x10, 0x44, 0x74, 0x91, 0x9a, 0xde, 0x84, +0x09, 0xa9, 0xa2, 0xfb, 0xd3, 0x10, 0xb8, 0x82, +0x28, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0x37, 0xcc, 0xb4, 0x47, 0x74, +0xf9, 0x67, 0xef, 0x90, 0xd6, 0x69, 0x85, 0x97, +0xe8, 0x93, 0x0c, 0x52, 0x1b, 0x75, 0xc8, 0x6d, +0x44, 0x4f, 0x9d, 0xa3, 0x3b, 0x8b, 0x18, 0x2e, +0x03, 0x1f, 0x2f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0xef, 0xcf, 0xe0, +0xb5, 0x8b, 0x7c, 0x52, 0x02, 0x9d, 0x4a, 0x3b, +0x98, 0x1b, 0x8a, 0x4d, 0x42, 0xbf, 0xc7, 0x67, +0x85, 0x57, 0xa1, 0x8b, 0xc4, 0xe2, 0x2f, 0x9b, +0x8e, 0x8e, 0xbf, 0x8b, 0xb5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0x7b, +0x06, 0x44, 0x2f, 0x19, 0x36, 0xcf, 0x08, 0x98, +0x91, 0xa0, 0xd2, 0x90, 0xf6, 0x87, 0xb1, 0x1c, +0xf5, 0xe8, 0x23, 0xad, 0x52, 0x5c, 0xc7, 0x94, +0xd4, 0x00, 0x44, 0x6f, 0x35, 0xe7, 0x74, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0xe2, 0x53, 0x17, 0xe6, 0xd0, 0xc2, 0xd5, +0xfb, 0x01, 0x9a, 0x93, 0x26, 0xfe, 0x04, 0x73, +0xb4, 0xd2, 0x90, 0xff, 0x8a, 0x4b, 0x3c, 0x2b, +0x67, 0x11, 0xde, 0x7f, 0xf9, 0x46, 0x3d, 0x26, +0x70, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0x41, 0xae, 0x1e, 0xe2, 0x4c, +0x40, 0xd3, 0x80, 0x0e, 0x7e, 0x11, 0xe3, 0x82, +0x93, 0xe2, 0x13, 0x27, 0xf9, 0xbc, 0x95, 0x9e, +0x43, 0x8c, 0x99, 0xf9, 0x93, 0x16, 0x77, 0xcc, +0x43, 0x26, 0x63, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0x88, 0x9a, 0xbb, +0xa2, 0x0d, 0x68, 0xd0, 0xe9, 0xa7, 0xb8, 0xb3, +0x38, 0xc5, 0x39, 0xd0, 0x45, 0x49, 0x93, 0xc0, +0xba, 0x91, 0x76, 0xe7, 0x91, 0xbb, 0xc7, 0x6d, +0xcb, 0xb1, 0x02, 0x5c, 0x3e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0xb2, +0xbf, 0x7d, 0xdc, 0xa1, 0xe0, 0x4f, 0xb0, 0xe9, +0xa3, 0x04, 0x91, 0xc5, 0xd9, 0x47, 0x64, 0xf5, +0xe0, 0xda, 0x06, 0xd0, 0x82, 0x9d, 0x8c, 0x18, +0x22, 0x04, 0xcf, 0xb4, 0x07, 0xad, 0xfd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0x79, 0x00, 0x02, 0x92, 0x6d, 0x31, 0x0b, +0x69, 0x0e, 0xb6, 0xe3, 0xc3, 0xc4, 0xb4, 0x90, +0x4e, 0x0e, 0xe2, 0xac, 0x7f, 0x3c, 0xe1, 0x10, +0x7b, 0xf2, 0x81, 0x2e, 0xb2, 0x89, 0x80, 0x60, +0xd4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0xae, 0xaa, 0x3c, 0xcf, 0xc1, +0x5d, 0xf1, 0x9c, 0xef, 0x6a, 0xa2, 0xc3, 0x32, +0xc2, 0x1c, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x01, 0xe9, 0xf7, 0xd0, +0xb3, 0xb7, 0x1f, 0x43, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0xf1, 0x70, 0x22, +0x4c, 0x59, 0x46, 0x8d, 0x44, 0x95, 0x8e, 0x04, +0x7e, 0xae, 0x95, 0x57, 0x14, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa7, 0xac, +0x7b, 0x3b, 0x04, 0xc1, 0x08, 0x27, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0x50, +0x9c, 0xd5, 0x56, 0xd2, 0x5d, 0x6d, 0x05, 0x9a, +0xcf, 0x24, 0xdd, 0xf3, 0x61, 0x05, 0x5a, 0x29, +0xcf, 0x3c, 0x51, 0x6f, 0x08, 0x59, 0x88, 0x14, +0x8f, 0x21, 0x21, 0x24, 0x1b, 0x8a, 0x15, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0xd1, 0x18, 0xdb, 0x1b, 0x38, 0xbd, 0xda, +0x2d, 0xee, 0x17, 0x28, 0x41, 0x4c, 0x20, 0x51, +0x3a, 0xa1, 0xfc, 0x31, 0xdb, 0x52, 0x14, 0x4b, +0xa3, 0xcf, 0x06, 0x43, 0xd2, 0xf4, 0x57, 0x20, +0x3d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0x27, 0xa4, 0xec, 0x9a, 0x83, +0x34, 0xba, 0xce, 0x85, 0x12, 0xfc, 0x9a, 0x7a, +0x09, 0xd6, 0x6e, 0xcf, 0x21, 0xef, 0xb1, 0xbd, +0xb2, 0xe4, 0xf1, 0x52, 0xb0, 0x8b, 0xdc, 0x12, +0xfd, 0x43, 0x10, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x07, 0x0a, 0x2e, +0x9b, 0xee, 0x5e, 0xeb, 0x3c, 0xc8, 0x3c, 0xce, +0x32, 0x4f, 0x54, 0xc7, 0x19, 0xc9, 0x95, 0xa5, +0x07, 0x03, 0x31, 0xcb, 0xa4, 0xbf, 0x69, 0x62, +0x8f, 0xc6, 0x95, 0x77, 0xb9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0x99, +0xaf, 0x2a, 0x18, 0x94, 0x7f, 0x67, 0x42, 0x64, +0x46, 0xab, 0x78, 0x78, 0x3d, 0x82, 0xd1, 0x10, +0xbb, 0x0a, 0x76, 0xb7, 0xe8, 0x1b, 0x6b, 0xc0, +0x6a, 0xb6, 0x59, 0x1f, 0x9b, 0xae, 0x66, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0x13, 0x9d, 0x73, 0x41, 0x7a, 0x8c, 0x64, +0xcd, 0xe3, 0xb6, 0x97, 0xda, 0x55, 0x9a, 0xa6, +0x52, 0x6e, 0x57, 0x1d, 0x28, 0x29, 0xd5, 0x97, +0xa1, 0x51, 0xe3, 0xdf, 0x13, 0xaf, 0x01, 0xd2, +0xf5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0x61, 0xd4, 0x9a, 0xd3, 0xdf, +0xae, 0x9b, 0xa8, 0xc4, 0x30, 0x03, 0x56, 0xc6, +0xfd, 0x04, 0xd6, 0x89, 0xa5, 0x25, 0x2c, 0xcb, +0x4d, 0x76, 0x02, 0xb3, 0x92, 0x36, 0x97, 0x84, +0x68, 0x16, 0xd7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x7f, 0x1d, 0xb1, +0x5c, 0xb9, 0xc9, 0xde, 0x09, 0xc6, 0xa2, 0x06, +0x27, 0xd8, 0xba, 0xf9, 0x9b, 0x3c, 0x76, 0xf9, +0x25, 0x70, 0x93, 0x06, 0xa7, 0x2b, 0x7b, 0x93, +0x93, 0x16, 0x2b, 0xf5, 0xc2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0x19, +0xe9, 0xed, 0x9d, 0xfc, 0x37, 0x8e, 0xfb, 0xb4, +0x63, 0xb4, 0x94, 0xdd, 0xf2, 0x48, 0xdb, 0xb3, +0xfc, 0xcf, 0xcd, 0xe7, 0x6b, 0x21, 0x31, 0x5c, +0x6f, 0x4d, 0xc1, 0x23, 0xe8, 0x85, 0x6b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0x6a, 0x84, 0x25, 0x09, 0x38, 0xc7, 0x47, +0xba, 0x47, 0x66, 0xc3, 0x77, 0x71, 0xd1, 0x20, +0x33, 0x3f, 0x92, 0xef, 0x3a, 0x9a, 0x35, 0x31, +0x87, 0x64, 0x15, 0x8b, 0x28, 0x79, 0x07, 0x2b, +0xf9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0x30, 0x61, 0x63, 0xf2, 0xc7, +0x75, 0xe0, 0xec, 0xac, 0xba, 0x5a, 0xc2, 0x35, +0x45, 0x4e, 0x0e, 0x4a, 0xd6, 0x66, 0xfe, 0x90, +0x38, 0x87, 0xf9, 0x10, 0x48, 0x6f, 0x16, 0xbf, +0x79, 0xc9, 0xa1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0x39, 0xa0, 0x99, +0xe2, 0xb5, 0x0e, 0x52, 0x54, 0xe2, 0xaf, 0xe5, +0x09, 0x45, 0x77, 0x40, 0xc2, 0x98, 0x84, 0x40, +0xb5, 0xb2, 0x77, 0x3d, 0x81, 0xa0, 0x83, 0x88, +0xdd, 0xc7, 0x42, 0x17, 0xcf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0xed, +0xfa, 0xd1, 0xa0, 0xa8, 0xdc, 0xea, 0x9f, 0x6d, +0x15, 0x5a, 0x44, 0x6b, 0x78, 0xf1, 0x5d, 0xf3, +0x3f, 0x77, 0xe0, 0x50, 0xf7, 0x98, 0x7d, 0xa3, +0xcd, 0xd0, 0x6b, 0xf3, 0x2b, 0x74, 0xb4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0x31, 0x1e, 0x6a, 0x0f, 0x3b, 0x26, 0xdf, +0x0b, 0x90, 0x05, 0xd5, 0x0c, 0xfc, 0xd9, 0x4b, +0xdf, 0x4e, 0x48, 0x48, 0xde, 0x6e, 0x5d, 0x2f, +0x51, 0x35, 0x9f, 0x36, 0xa6, 0xcf, 0xed, 0x5b, +0x8d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0x45, 0x2b, 0x59, 0xd9, 0x31, +0x4f, 0x7d, 0x29, 0x55, 0x45, 0x62, 0x07, 0x3e, +0xaa, 0x06, 0x76, 0x8b, 0x72, 0x8e, 0x33, 0x9f, +0x37, 0x93, 0x61, 0x14, 0xf1, 0x3d, 0x3f, 0xbf, +0xd0, 0x12, 0xbe, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0xe9, 0xa5, 0x21, +0x32, 0xf9, 0x32, 0x38, 0xe9, 0xa9, 0x78, 0xa8, +0x84, 0x10, 0xa2, 0x94, 0x68, 0xcc, 0x54, 0xfe, +0x83, 0xe3, 0xdc, 0xcc, 0xf5, 0xc9, 0x7f, 0x08, +0x6b, 0x53, 0x25, 0x4f, 0xc9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0xe0, +0xcc, 0xbf, 0xb3, 0x02, 0xeb, 0x44, 0xd7, 0xca, +0x80, 0x40, 0x38, 0xdf, 0x32, 0xe1, 0x91, 0x13, +0xa0, 0x3b, 0xf3, 0x5b, 0x83, 0x7a, 0xdb, 0x46, +0x0e, 0xfb, 0x91, 0x8a, 0x62, 0x44, 0x41, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0xcf, 0x0e, 0x6c, 0x6b, 0xd3, 0x6c, 0xa6, +0x9c, 0xcf, 0xdb, 0xb0, 0xde, 0x04, 0x9d, 0x7c, +0x13, 0x05, 0xf6, 0x06, 0xb2, 0x30, 0xa9, 0x1a, +0x7c, 0xe9, 0x29, 0x08, 0x6c, 0x48, 0x09, 0x8f, +0x86, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0xfa, 0xc5, 0x35, 0xed, 0xdd, +0xb4, 0x64, 0xa9, 0xe2, 0x43, 0xd9, 0x37, 0x0b, +0x76, 0x1f, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xff, 0xa7, 0x6f, 0x70, +0x93, 0xb6, 0x4f, 0xc9, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0x10, 0x6f, 0x0d, +0x5f, 0xf5, 0xf6, 0x25, 0x69, 0x12, 0xa1, 0x16, +0xc6, 0x88, 0xab, 0x89, 0x8b, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0xc8, +0x35, 0x7e, 0x10, 0x62, 0xc6, 0x78, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0x60, +0xfd, 0x3b, 0x2c, 0xb5, 0x21, 0xe4, 0x02, 0x85, +0x3b, 0xee, 0xc8, 0xe5, 0xd1, 0xfb, 0x67, 0x5c, +0xd8, 0xeb, 0x59, 0xf5, 0x49, 0xa4, 0x1a, 0x81, +0xed, 0x57, 0x43, 0xd1, 0x44, 0xab, 0x71, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0xe5, 0xc8, 0x58, 0xc6, 0x87, 0x1a, 0x7f, +0x79, 0x1e, 0x1b, 0xb4, 0x2d, 0xcc, 0xbc, 0x01, +0xc9, 0x75, 0x02, 0x3a, 0x66, 0x05, 0xac, 0xb6, +0x12, 0xf3, 0x25, 0x54, 0x57, 0xde, 0x5c, 0x40, +0x71, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0x8d, 0xde, 0xc9, 0x40, 0x1d, +0x8c, 0xa9, 0x5f, 0xb8, 0x25, 0xa5, 0x40, 0x19, +0xad, 0x00, 0x59, 0x11, 0xf8, 0x61, 0x9c, 0x0f, +0xa6, 0xd3, 0x77, 0x29, 0x22, 0x2e, 0x86, 0x5f, +0x2e, 0x36, 0xb5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0x0f, 0x18, 0xb2, +0xaa, 0x24, 0x2c, 0xbc, 0xa4, 0xa1, 0x7c, 0xe2, +0x3c, 0x73, 0xb5, 0xbc, 0x83, 0x03, 0x59, 0x40, +0x45, 0x2e, 0x4b, 0x2b, 0xe0, 0xc8, 0xf9, 0xa9, +0x62, 0x02, 0xe5, 0xbf, 0x62, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0xb2, +0x0f, 0x58, 0x5a, 0x21, 0xdc, 0x93, 0xb9, 0x14, +0x2d, 0x18, 0x85, 0x17, 0x8c, 0x41, 0x1b, 0x83, +0x40, 0xc6, 0x46, 0x45, 0xd9, 0x9f, 0xf4, 0xc7, +0x3b, 0x23, 0xf3, 0x0d, 0xa9, 0x62, 0xfa, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0xf1, 0x0a, 0x61, 0x15, 0xae, 0x9a, 0x9a, +0xd2, 0x2f, 0xb6, 0xd4, 0x9c, 0xe5, 0xee, 0xcf, +0xd6, 0xc9, 0x17, 0xb3, 0x8c, 0x5f, 0x0b, 0xb9, +0x86, 0xbb, 0xa5, 0xb1, 0x5c, 0xd6, 0x20, 0x13, +0x28, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0x03, 0x40, 0xa8, 0xb4, 0xa5, +0x40, 0x75, 0x83, 0xcb, 0xcd, 0x99, 0x0a, 0x7b, +0x2f, 0xeb, 0xa3, 0x6d, 0xe7, 0x6f, 0xb0, 0x4f, +0x06, 0x54, 0xcb, 0xdd, 0x08, 0x37, 0x33, 0xd1, +0xdc, 0xe0, 0x75, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0x9b, 0x23, 0x91, +0xed, 0x59, 0xf2, 0x45, 0x2f, 0xd1, 0xbb, 0xa4, +0xec, 0x92, 0xea, 0x51, 0x12, 0xb1, 0x43, 0xcd, +0xad, 0x67, 0x75, 0xe7, 0x8e, 0x25, 0x13, 0x3a, +0x46, 0x47, 0xb4, 0x8b, 0xc2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0x70, +0x7c, 0x67, 0xa7, 0x8e, 0x0b, 0xbd, 0x5b, 0xf7, +0x68, 0x22, 0x5f, 0x53, 0x38, 0xfa, 0x7f, 0xcf, +0xb7, 0xcb, 0x17, 0x66, 0x5e, 0xf2, 0xd4, 0xbe, +0xdd, 0x0f, 0xdd, 0x17, 0x05, 0xa7, 0x11, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x6c, 0xca, 0xe4, 0x0e, 0xc0, 0x69, 0xba, +0x5c, 0xe6, 0xbf, 0x58, 0x10, 0x9f, 0x0f, 0xed, +0xd4, 0x80, 0xe6, 0xbf, 0x6f, 0x65, 0xe3, 0xba, +0x61, 0x75, 0x41, 0x21, 0xf8, 0xe5, 0x82, 0x0f, +0x36, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0x5e, 0xf3, 0x89, 0x0e, 0x13, +0x5e, 0x00, 0x95, 0x2e, 0xc8, 0x7a, 0x7d, 0xd5, +0x79, 0x97, 0x07, 0x3c, 0x87, 0xd7, 0x52, 0x97, +0xad, 0xf4, 0x09, 0x7b, 0x4d, 0x5d, 0xae, 0xc1, +0x5f, 0x0a, 0xae, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0x94, 0x3d, 0xc9, +0xc8, 0xae, 0x61, 0x4c, 0x42, 0x78, 0xc7, 0xf8, +0x6d, 0xd2, 0x22, 0xcd, 0x7b, 0x5b, 0x5a, 0x4e, +0xc3, 0x75, 0x3b, 0x7f, 0xd5, 0x7f, 0xdd, 0x28, +0xc4, 0xb4, 0xab, 0x18, 0x23, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0x9d, +0xbc, 0x65, 0x2f, 0xea, 0x06, 0xda, 0x90, 0x53, +0xc9, 0x0f, 0xde, 0x2a, 0xfd, 0x24, 0xce, 0x04, +0x61, 0x69, 0x85, 0xd9, 0x95, 0x3a, 0xd0, 0xb7, +0xf2, 0x4c, 0x9c, 0x08, 0x1c, 0x1c, 0xaf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0xbf, 0xab, 0xbc, 0x54, 0x55, 0x46, 0x78, +0xf9, 0x60, 0x7e, 0x2c, 0x19, 0x86, 0xa5, 0xe8, +0x08, 0xd1, 0x38, 0x65, 0xec, 0x69, 0xee, 0x88, +0x86, 0xc8, 0x2b, 0xa4, 0x30, 0x76, 0x00, 0xa7, +0xac, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0x6c, 0x30, 0x78, 0xcd, 0xc5, +0x85, 0xf8, 0x95, 0xb1, 0x0b, 0x9a, 0xb6, 0x71, +0x58, 0x6e, 0xc2, 0x99, 0x34, 0xdd, 0x3b, 0x3d, +0x28, 0x80, 0xbe, 0xab, 0x57, 0xff, 0xa7, 0xeb, +0xe0, 0x97, 0x2f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0xa0, 0x30, 0xfe, +0x3b, 0xe4, 0x38, 0xc7, 0x50, 0x0f, 0x48, 0x98, +0x39, 0x88, 0x20, 0xdd, 0x41, 0xbe, 0xb3, 0x58, +0x20, 0xe3, 0xd2, 0xd7, 0xae, 0x61, 0x35, 0xef, +0xb7, 0xfa, 0x14, 0x42, 0x30, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0x90, +0xb1, 0xfe, 0x3b, 0x20, 0x42, 0x83, 0x5f, 0x99, +0x79, 0xa7, 0x46, 0xd8, 0x3f, 0xa6, 0x73, 0x46, +0x35, 0xbc, 0x37, 0x30, 0x9c, 0x05, 0x0b, 0x6f, +0x6f, 0xb7, 0x36, 0xb1, 0x8f, 0xef, 0x91, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0x3a, 0x65, 0xd1, 0x8e, 0x4c, 0x69, 0xf9, +0xab, 0xa0, 0x5c, 0x0f, 0xae, 0xb4, 0x7c, 0xb8, +0x1f, 0x77, 0x41, 0xab, 0x0f, 0x73, 0x02, 0xb7, +0x41, 0x68, 0xd0, 0xa2, 0x5f, 0xe0, 0x59, 0xa5, +0x8a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0xeb, 0x75, 0x9a, 0xd6, 0xdf, +0xc0, 0x9e, 0x5f, 0x12, 0x4e, 0xdc, 0x79, 0xf2, +0x36, 0xec, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xc9, 0x5c, 0x73, 0x3c, +0x79, 0xe1, 0x5e, 0x33, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0xdf, 0xee, 0x5f, +0xa5, 0xdd, 0x5e, 0xfb, 0xb1, 0xd3, 0x2d, 0xcc, +0xd7, 0xe3, 0x5b, 0x6e, 0x6a, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0xc3, +0xc9, 0xd7, 0x79, 0x0e, 0x5e, 0x80, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0x38, +0x6a, 0x00, 0x40, 0xff, 0x01, 0x37, 0x9b, 0x5e, +0xd8, 0x67, 0x8c, 0x63, 0x52, 0xcb, 0x79, 0x2b, +0xe8, 0xeb, 0xb6, 0x22, 0x7c, 0x8a, 0x41, 0x3b, +0xb0, 0xce, 0xde, 0xc9, 0x74, 0x10, 0x78, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0x9c, 0xda, 0x5a, 0xa7, 0x73, 0x5d, 0x47, +0x72, 0x24, 0xd6, 0x2a, 0x7b, 0x47, 0x08, 0x2d, +0xd7, 0x8b, 0x3e, 0x5c, 0x66, 0x41, 0x0b, 0xd3, +0x63, 0xae, 0x89, 0xaa, 0xb8, 0x5d, 0xb0, 0x90, +0x8c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0x25, 0x0f, 0x04, 0x7a, 0xea, +0x17, 0x38, 0x1b, 0xed, 0x0d, 0x21, 0x38, 0x60, +0x69, 0xfb, 0xdc, 0xe2, 0x8d, 0xba, 0xf9, 0x57, +0x68, 0x26, 0x88, 0xb6, 0x80, 0xc8, 0x69, 0xc9, +0x9c, 0x4f, 0x3b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0x1c, 0xd3, 0x5c, +0xc5, 0x32, 0x15, 0x99, 0x37, 0x88, 0x8d, 0x96, +0xb7, 0xa3, 0xac, 0x62, 0xd7, 0x9e, 0x97, 0xba, +0xd3, 0xae, 0x41, 0x58, 0xb9, 0x25, 0x11, 0x52, +0x91, 0x14, 0x91, 0x79, 0x9f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0xf3, +0x22, 0x6d, 0x7c, 0x5f, 0x03, 0xcc, 0x2c, 0xfa, +0xd4, 0x64, 0x3c, 0xf6, 0x2c, 0x1e, 0xa6, 0xf3, +0xcc, 0x50, 0xc4, 0xc7, 0x6b, 0x9d, 0xa9, 0x56, +0x88, 0xd5, 0x22, 0x17, 0xb4, 0x62, 0x6e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0x6b, 0x63, 0x7e, 0x5e, 0x39, 0x32, 0x7d, +0x5a, 0xb2, 0xfc, 0xd5, 0xbb, 0x7e, 0x56, 0x05, +0x07, 0x93, 0xad, 0x66, 0x97, 0xb6, 0xba, 0xf3, +0xec, 0x0f, 0xa4, 0x2b, 0x91, 0x5d, 0x43, 0x9c, +0x2f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x9a, 0x39, 0x1c, 0x3b, 0x5b, +0xe4, 0x2f, 0x22, 0x7e, 0x16, 0xb6, 0x1c, 0x2d, +0xe4, 0x32, 0x67, 0xff, 0xaa, 0x32, 0x8d, 0xe0, +0x21, 0xff, 0xfa, 0xa3, 0xab, 0x93, 0xcc, 0xf4, +0x34, 0x9e, 0x0a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0x02, 0x5d, 0x6d, +0x6d, 0x9c, 0xed, 0xf7, 0xe2, 0x84, 0xaf, 0x1b, +0x77, 0x2e, 0x4a, 0x84, 0x36, 0x3a, 0x3d, 0x39, +0x32, 0x8c, 0x70, 0x6d, 0xc7, 0x0d, 0x7a, 0x7c, +0x76, 0x4d, 0x76, 0x9c, 0x29, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0x63, +0x61, 0x91, 0xc2, 0x6e, 0xf3, 0xfb, 0x73, 0x1a, +0x0a, 0xce, 0xb7, 0xde, 0xa3, 0xb5, 0x40, 0xe3, +0x50, 0xcc, 0x53, 0xe0, 0xce, 0x63, 0x9e, 0x57, +0xfd, 0xab, 0x4a, 0x73, 0xb2, 0xdb, 0x2e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0x94, 0xcc, 0x06, 0xf2, 0x59, 0x57, 0x82, +0x20, 0xca, 0x5d, 0x2d, 0x0d, 0x03, 0x24, 0x31, +0x24, 0x7b, 0x12, 0x92, 0x1c, 0x70, 0xe6, 0x45, +0x49, 0x15, 0xdf, 0x6d, 0xaf, 0xa3, 0x9d, 0x28, +0x00, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0xce, 0xbf, 0xed, 0x01, 0x2a, +0xee, 0xf1, 0xa6, 0x7a, 0x99, 0xc9, 0x5f, 0xfa, +0x94, 0x78, 0x35, 0xbe, 0xb0, 0xa8, 0xf1, 0xfc, +0x54, 0x92, 0xe0, 0xe5, 0x84, 0xf3, 0xdb, 0x73, +0x97, 0xc6, 0x3c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0x9b, 0xc9, 0x70, +0x95, 0xfd, 0x88, 0xc4, 0x47, 0xc9, 0x28, 0x28, +0xca, 0xa6, 0xf7, 0xf8, 0x1f, 0xa1, 0xa9, 0x78, +0x51, 0x06, 0x7a, 0xb7, 0xd0, 0x15, 0x15, 0x05, +0x89, 0xbf, 0x5b, 0x0b, 0x59, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0x81, +0x94, 0x40, 0x60, 0x70, 0x58, 0x4d, 0xe3, 0x7f, +0x10, 0x9e, 0x0f, 0x26, 0x30, 0x1a, 0xe6, 0x8b, +0x54, 0xeb, 0x9e, 0xde, 0x81, 0x73, 0x22, 0x2e, +0x80, 0xd4, 0x7d, 0x21, 0x7a, 0x84, 0x5e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x08, 0x59, 0x4d, 0x5a, 0x46, 0xc6, 0x4a, +0xef, 0x1e, 0x17, 0x3c, 0x60, 0xea, 0x7c, 0xe4, +0x26, 0xee, 0x42, 0x63, 0x14, 0xaf, 0x6b, 0x1e, +0xdb, 0xd2, 0xa4, 0x4c, 0x24, 0xdc, 0x68, 0x5f, +0xee, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0x5b, 0x99, 0x37, 0xf1, 0x39, +0xb6, 0x3d, 0x32, 0x25, 0xcc, 0x16, 0x96, 0x88, +0x6d, 0xd0, 0xf8, 0x03, 0x66, 0xb1, 0x85, 0xf8, +0x3f, 0x34, 0x11, 0xd7, 0xe0, 0x32, 0x92, 0x2d, +0xbc, 0xfe, 0xe5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0x0f, 0xae, 0x81, +0xeb, 0xd6, 0x26, 0x6a, 0xbb, 0xf5, 0x4f, 0x6f, +0xac, 0x88, 0xa7, 0x01, 0xb3, 0xe1, 0x8c, 0xd9, +0xf9, 0x8d, 0xee, 0x85, 0x00, 0x7f, 0xc2, 0x9c, +0x5d, 0x84, 0x2b, 0x3d, 0x18, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0x5e, +0xeb, 0x7c, 0x6d, 0x4f, 0x2a, 0x35, 0xaa, 0x22, +0x64, 0xc8, 0xcc, 0x4b, 0x34, 0xe1, 0xc2, 0x63, +0x56, 0xa6, 0xd2, 0xdd, 0x5b, 0x96, 0x70, 0x38, +0x77, 0x36, 0x93, 0x83, 0xfa, 0xd1, 0xd0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0xa7, 0x37, 0x51, 0x23, 0x8d, 0x64, 0x7d, +0xa1, 0x9c, 0x37, 0xb1, 0x70, 0x3e, 0x19, 0x20, +0x20, 0x70, 0xf8, 0x15, 0x60, 0x81, 0xf7, 0x6f, +0xa1, 0x4f, 0x6e, 0xdc, 0x02, 0xcd, 0x55, 0x80, +0x52, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0x30, 0x06, 0x99, 0x67, 0x41, +0x5d, 0x38, 0x44, 0x09, 0xc2, 0xb6, 0x80, 0x39, +0x0f, 0xb1, 0x9d, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xfb, 0xeb, 0xa7, 0x24, +0x55, 0x1a, 0x9d, 0x36, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0x71, 0xa3, 0xe6, +0x11, 0x87, 0x23, 0xbd, 0x23, 0x78, 0x86, 0x60, +0xd8, 0xea, 0x39, 0x2e, 0x59, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0xb3, +0xa7, 0x8a, 0xf3, 0xd6, 0x44, 0xdf, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0xa8, +0x92, 0x85, 0x21, 0xcb, 0x0f, 0x23, 0x9b, 0x70, +0xa1, 0x6c, 0xc5, 0x21, 0x3b, 0xd5, 0xd1, 0xb2, +0x59, 0x56, 0x78, 0xb6, 0xea, 0xe6, 0x7d, 0xfa, +0x66, 0xc6, 0x04, 0xa4, 0x08, 0x89, 0x6e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0x13, 0x50, 0x0c, 0x04, 0x3c, 0x2c, 0x0e, +0x4e, 0xdf, 0x20, 0x48, 0x3f, 0xb1, 0x06, 0x03, +0xb6, 0xcb, 0x00, 0xd4, 0x33, 0x53, 0xde, 0x62, +0x57, 0x9c, 0xdc, 0xeb, 0xdc, 0xda, 0x67, 0xd0, +0x7c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0x47, 0x55, 0xe3, 0x45, 0xec, +0xe0, 0x43, 0xa9, 0xa5, 0xbe, 0x18, 0x50, 0xa7, +0xb1, 0xb2, 0xfe, 0x30, 0x99, 0x8b, 0xf6, 0xde, +0x30, 0xd4, 0xf9, 0xa9, 0x29, 0x9d, 0x5f, 0x42, +0xcf, 0x95, 0x25, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0x9c, 0x80, 0x25, +0x6a, 0xf2, 0x36, 0xd9, 0x98, 0x4e, 0xf7, 0x4f, +0xfc, 0xd0, 0xcf, 0xfc, 0xf8, 0x47, 0x3d, 0xab, +0x5f, 0x3e, 0x7e, 0xbb, 0x6e, 0xaa, 0x09, 0x5f, +0xb8, 0x3d, 0x7f, 0x28, 0xe0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0xc1, +0x0e, 0x58, 0xa6, 0x04, 0xb7, 0x17, 0x1e, 0x1a, +0x62, 0xfc, 0x01, 0x66, 0x3e, 0x84, 0xab, 0xdf, +0x81, 0x59, 0x76, 0x20, 0xd9, 0x14, 0x95, 0x55, +0xbf, 0x76, 0x13, 0xe6, 0xa2, 0x1c, 0x73, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0x15, 0xd8, 0xec, 0x3a, 0xea, 0x86, 0x4e, +0x26, 0x42, 0x99, 0x10, 0x30, 0x7c, 0xb9, 0xe2, +0x2d, 0xdf, 0x0b, 0x54, 0xf5, 0xcc, 0xcb, 0x3f, +0x16, 0xa5, 0xf6, 0x5a, 0xeb, 0xc5, 0x26, 0x18, +0x45, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0x5b, 0x6e, 0xf0, 0x79, 0x5a, +0x00, 0xbb, 0x89, 0xee, 0xe8, 0xec, 0x12, 0xa3, +0xe9, 0x0c, 0x74, 0x9b, 0x83, 0xa5, 0xb3, 0x23, +0x13, 0xe0, 0x1d, 0xdf, 0xd5, 0xcb, 0x7e, 0x23, +0x00, 0xd0, 0xd4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0x5a, 0xee, 0x37, +0xfe, 0xf6, 0x2d, 0x28, 0xc9, 0x93, 0x85, 0x6c, +0xf1, 0x19, 0x44, 0x9b, 0x61, 0x88, 0xf9, 0xf2, +0xb1, 0x42, 0xd1, 0x0d, 0xc2, 0x65, 0x0b, 0x99, +0x7e, 0x8f, 0xe5, 0x50, 0x2b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0xcf, +0xab, 0x1b, 0x9c, 0xfe, 0xd8, 0x6b, 0x7d, 0xb1, +0x0b, 0x1d, 0x7c, 0x13, 0xc1, 0x47, 0x64, 0x31, +0x1c, 0xd9, 0x7c, 0xec, 0xbf, 0xb5, 0x06, 0x8d, +0xc9, 0xc5, 0xa9, 0x05, 0x9b, 0x3b, 0x0c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0xfb, 0x32, 0xa6, 0xd3, 0x22, 0xb2, 0x32, +0x2d, 0x58, 0x0d, 0xd7, 0x36, 0x94, 0x65, 0x2b, +0xd4, 0x0f, 0x69, 0x52, 0x41, 0xd8, 0xbf, 0x0b, +0x66, 0xdd, 0x2b, 0x7e, 0xe4, 0x97, 0xe8, 0x66, +0x78, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0x8c, 0x19, 0x19, 0x8f, 0x83, +0xf9, 0xfc, 0xad, 0xac, 0xa0, 0xb0, 0x21, 0xb9, +0xc6, 0x92, 0x2f, 0x5b, 0x98, 0x1d, 0x30, 0x18, +0x83, 0x63, 0x90, 0xd6, 0x92, 0xbb, 0xda, 0xbc, +0xc8, 0xa3, 0x38, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0x4f, 0x92, 0xa7, +0xf4, 0xe6, 0x62, 0x4d, 0x98, 0x35, 0xbc, 0x67, +0x11, 0x54, 0xe2, 0x28, 0xa2, 0x80, 0xc6, 0xa2, +0xa8, 0xfb, 0x7c, 0xc4, 0x91, 0x4a, 0x4d, 0xdf, +0xf4, 0x46, 0xb4, 0xf6, 0x10, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x49, +0x08, 0xcf, 0xe7, 0xa8, 0x15, 0xf3, 0xaa, 0xb5, +0xd5, 0x23, 0xf2, 0x6f, 0x52, 0xa7, 0x10, 0x7d, +0xaf, 0xda, 0x0e, 0xb2, 0xee, 0xfc, 0x33, 0x35, +0x76, 0xcb, 0x4f, 0x7b, 0xfc, 0x87, 0x98, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0xaa, 0xae, 0x13, 0x06, 0x73, 0xe7, 0x47, +0xa1, 0xb1, 0xce, 0x56, 0xf3, 0x34, 0x74, 0xde, +0x61, 0x26, 0xcd, 0x34, 0x7c, 0x55, 0x05, 0x36, +0x0b, 0xaf, 0xf6, 0x55, 0x38, 0x39, 0x16, 0x68, +0x77, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0x39, 0xb9, 0x64, 0xe9, 0x8b, +0xf4, 0xf8, 0x99, 0x6e, 0x56, 0xe0, 0x0f, 0x34, +0xd8, 0x28, 0x28, 0xa8, 0x19, 0x86, 0xff, 0xd7, +0x47, 0x04, 0xf5, 0xa1, 0x19, 0x68, 0xec, 0x62, +0xfe, 0x39, 0x0d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0xe6, 0x50, 0x9a, +0xe3, 0xbc, 0xca, 0xae, 0x09, 0xb1, 0xd1, 0x4a, +0xe7, 0x5b, 0xdf, 0x5b, 0x1d, 0xfd, 0xa5, 0xb4, +0x85, 0x63, 0xb0, 0xbd, 0x3b, 0xe8, 0xb2, 0xcd, +0xee, 0x82, 0x29, 0x89, 0x7b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0x87, +0x52, 0xd2, 0xf9, 0x9a, 0xe6, 0xa5, 0xb8, 0x37, +0x7e, 0x99, 0xd0, 0xbc, 0x3c, 0xac, 0x47, 0xf2, +0xd7, 0x85, 0x5e, 0x61, 0x10, 0x09, 0xf9, 0x72, +0xc3, 0xb5, 0xac, 0x51, 0xe5, 0x74, 0xf5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0x2f, 0x4c, 0x62, 0x34, 0x4f, 0xf8, 0x09, +0x77, 0x1b, 0xad, 0x84, 0x4d, 0xf6, 0x56, 0xa1, +0x2e, 0x1a, 0x63, 0x05, 0x72, 0xeb, 0x06, 0x63, +0xf5, 0x62, 0xf5, 0x97, 0x4c, 0x46, 0x91, 0x12, +0x8d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0x9c, 0xba, 0x23, 0x66, 0x14, +0x92, 0x83, 0x0d, 0x26, 0xb3, 0xf0, 0x95, 0xbd, +0xc2, 0xd4, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x51, 0xf7, 0x87, 0x05, +0x8d, 0x70, 0x40, 0xdf, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x96, 0x2d, 0x49, +0x79, 0xc1, 0x23, 0xaf, 0x83, 0x7c, 0x14, 0xed, +0x99, 0xbc, 0xb7, 0x9d, 0x34, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x0f, +0x47, 0x47, 0x80, 0x58, 0x44, 0xb1, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0x5e, +0x79, 0xb8, 0xf5, 0xc8, 0xdc, 0xc9, 0xea, 0x8e, +0x7d, 0x7d, 0x4f, 0x84, 0x45, 0x67, 0x1b, 0xbc, +0x58, 0x75, 0x8f, 0x10, 0x37, 0x4f, 0xa7, 0xb0, +0x95, 0x0e, 0x88, 0x24, 0x3d, 0xd9, 0x0b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0xbe, 0xdb, 0xb6, 0xd1, 0x4d, 0x6c, 0xd3, +0xa7, 0x79, 0x74, 0xd9, 0x55, 0xa9, 0x23, 0x2c, +0x62, 0xf7, 0x70, 0x34, 0xd6, 0x63, 0xe4, 0xbc, +0x8c, 0x3f, 0xf3, 0x9e, 0x75, 0x89, 0xe6, 0x23, +0x34, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0x6e, 0x90, 0xdd, 0x91, 0xc9, +0xb0, 0xba, 0x7d, 0xcc, 0x2f, 0x19, 0xac, 0x4a, +0x24, 0x79, 0xbf, 0xe4, 0xa6, 0x22, 0xbe, 0x27, +0x8e, 0x92, 0x2d, 0xb7, 0x62, 0x9d, 0xf5, 0x16, +0x16, 0xc8, 0x2f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x62, 0x1f, 0xfd, +0xdd, 0xc4, 0xdb, 0xed, 0x8b, 0xa0, 0x4a, 0x09, +0xb2, 0x7f, 0xf2, 0xe3, 0x87, 0x2b, 0x24, 0x43, +0x63, 0x58, 0x9f, 0x16, 0x87, 0x71, 0x48, 0xe7, +0xf4, 0xc7, 0x87, 0xbf, 0x06, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0xf7, +0x08, 0x86, 0xf2, 0x2a, 0x65, 0xdd, 0x7a, 0xa7, +0x98, 0xe2, 0x50, 0x76, 0xfe, 0xdd, 0xd2, 0x6a, +0x60, 0x02, 0x6b, 0x5d, 0xf3, 0x07, 0x34, 0x8f, +0xa7, 0xb9, 0x6b, 0x20, 0xe3, 0x51, 0x04, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0x94, 0xdd, 0xff, 0x4c, 0x15, 0x8a, 0xe6, +0x51, 0xe7, 0x9c, 0x66, 0x39, 0xf4, 0x92, 0x8b, +0x37, 0x03, 0x23, 0x71, 0xca, 0x9b, 0xd4, 0xbb, +0x59, 0x64, 0x3d, 0x20, 0xeb, 0x42, 0xd7, 0x5a, +0xb7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0xe6, 0x5e, 0xa6, 0x64, 0x57, +0x4a, 0xbc, 0x3f, 0xe3, 0x9e, 0x03, 0x16, 0xbe, +0xf4, 0x2f, 0x27, 0x6f, 0x82, 0x00, 0x2b, 0xb5, +0x26, 0xcb, 0x73, 0x42, 0x5f, 0x52, 0x64, 0x92, +0xd3, 0xe9, 0x5d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0x2b, 0x4d, 0xd0, +0x9c, 0x37, 0xf7, 0x86, 0x49, 0xe5, 0x59, 0xcc, +0x52, 0x50, 0xc2, 0x41, 0xe4, 0xe5, 0xac, 0x69, +0x57, 0xee, 0x94, 0xde, 0xdf, 0x51, 0x24, 0x82, +0xc1, 0x18, 0xc1, 0x03, 0x68, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x58, +0xb3, 0x21, 0xe0, 0x63, 0xd6, 0x01, 0x08, 0x47, +0x21, 0x9b, 0x82, 0x90, 0x21, 0xd5, 0x0b, 0x3b, +0xd5, 0xb9, 0xba, 0xe7, 0xfd, 0xfd, 0x4e, 0x54, +0x6b, 0x0f, 0xe1, 0xc2, 0x60, 0xe3, 0xe9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0x96, 0xab, 0x43, 0x15, 0xbb, 0x5e, 0x05, +0x3e, 0xdf, 0xaf, 0xe1, 0xba, 0xcc, 0x1b, 0x3c, +0x23, 0x24, 0xfb, 0x80, 0xed, 0x30, 0x5b, 0x4c, +0x72, 0xab, 0xd2, 0x1f, 0x36, 0x1d, 0x9e, 0xf3, +0x19, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0x7f, 0xb6, 0x59, 0x8d, 0x74, +0xcf, 0x77, 0x6b, 0x05, 0x9e, 0xb7, 0xd9, 0xab, +0x92, 0x40, 0x42, 0x5e, 0x1c, 0x58, 0x98, 0xec, +0xad, 0x63, 0x56, 0x08, 0xc2, 0xe6, 0x4c, 0x00, +0x2c, 0xdd, 0x4b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0xe9, 0x4e, 0x3f, +0x37, 0x2f, 0x86, 0x91, 0x0d, 0xe7, 0x00, 0x51, +0x5c, 0x12, 0x97, 0x23, 0xed, 0xbc, 0xec, 0x0a, +0x3d, 0x81, 0x45, 0xea, 0x82, 0x62, 0xc4, 0x14, +0xf7, 0xa8, 0x48, 0xc0, 0x2e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0xd6, +0x62, 0x8c, 0x24, 0xf0, 0x70, 0xdd, 0xd6, 0xa8, +0x74, 0xbe, 0x62, 0x8f, 0x91, 0x76, 0x5b, 0x16, +0x36, 0x80, 0x6a, 0xc1, 0x61, 0x89, 0x46, 0xf9, +0x5a, 0xd2, 0x03, 0x0d, 0x4d, 0x53, 0x78, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0xa6, 0xd7, 0xad, 0x30, 0xb9, 0x06, 0x8b, +0xe3, 0x95, 0x8a, 0xdd, 0x4a, 0xf5, 0x6a, 0x37, +0x65, 0x01, 0x45, 0x20, 0xe4, 0x4b, 0x59, 0xfe, +0x01, 0x12, 0xdd, 0x2e, 0xee, 0x53, 0x2f, 0x83, +0xae, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0xff, 0x04, 0xb8, 0x80, 0xfc, +0xdc, 0x16, 0x9f, 0xf5, 0xfd, 0xa3, 0x08, 0x55, +0x0d, 0x3d, 0x2a, 0x48, 0x4e, 0x33, 0x08, 0xb0, +0x0e, 0xb7, 0xc7, 0xc6, 0xf9, 0x19, 0x74, 0x80, +0xe7, 0xe1, 0xca, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0x7b, 0x9d, 0x1f, +0x8d, 0x32, 0x0d, 0x3d, 0x09, 0x81, 0xc1, 0x80, +0xc4, 0x2b, 0x72, 0x7c, 0x5f, 0x9d, 0x85, 0xf0, +0xa8, 0xd8, 0x4f, 0x89, 0x07, 0x17, 0xe7, 0xaa, +0xe9, 0xeb, 0x65, 0x54, 0xd8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0x96, +0xd4, 0xa5, 0x1e, 0xab, 0x52, 0x49, 0x29, 0xf4, +0x37, 0x71, 0xee, 0x17, 0xf8, 0x4b, 0x0d, 0xe6, +0xfd, 0x55, 0x72, 0x87, 0x7b, 0x11, 0xfa, 0x6b, +0xda, 0xcb, 0x89, 0xaa, 0x42, 0xe7, 0x59, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0x7c, 0x50, 0x25, 0xc0, 0x94, 0x48, 0xd3, +0xbb, 0xee, 0x7b, 0x58, 0x55, 0x89, 0x86, 0x62, +0xae, 0x88, 0x76, 0x92, 0x1a, 0x7b, 0x48, 0xdc, +0x9a, 0x40, 0x7e, 0x75, 0x10, 0xaf, 0xf8, 0x07, +0x34, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0x28, 0xe7, 0x72, 0x52, 0x74, +0x69, 0x23, 0x7e, 0x68, 0x8b, 0xdf, 0xc1, 0x14, +0xea, 0x8a, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x8e, 0x37, 0xab, 0x52, +0xe3, 0xf5, 0xa5, 0xde, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0xc8, 0xc9, 0x6a, +0xeb, 0x2f, 0x26, 0x4d, 0x9c, 0x99, 0x07, 0xbf, +0x5a, 0x4e, 0x0f, 0xfc, 0x0f, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0x43, +0xdb, 0x49, 0x14, 0x8f, 0xc1, 0x19, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0x92, +0x41, 0x89, 0x4e, 0x67, 0xd7, 0x39, 0x53, 0xac, +0x67, 0xc8, 0x0a, 0xae, 0x9c, 0x3c, 0x6e, 0x2e, +0x8c, 0xb5, 0xca, 0x15, 0x41, 0xa8, 0xcf, 0xdd, +0xb4, 0xc3, 0xa5, 0x80, 0xfc, 0xc4, 0xbb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0x08, 0xc1, 0xfe, 0xa5, 0x79, 0x58, 0x83, +0x3c, 0x26, 0x4b, 0x8d, 0x31, 0x94, 0xd6, 0xe2, +0x4a, 0xa7, 0x5a, 0x6a, 0x1f, 0xe4, 0x5b, 0x12, +0x92, 0x40, 0x79, 0x1b, 0x57, 0x93, 0xd0, 0xd9, +0x86, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0xf4, 0x7f, 0x0b, 0x3b, 0x6d, +0x40, 0x6b, 0x9f, 0x67, 0x07, 0x85, 0x71, 0x06, +0x14, 0xe7, 0x9b, 0x41, 0x1b, 0x57, 0x85, 0x5f, +0x45, 0x35, 0x34, 0xc9, 0x18, 0x4b, 0x47, 0xc7, +0x16, 0xbf, 0xdd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x19, 0x67, 0xf9, +0xae, 0xb2, 0x7f, 0xdb, 0xec, 0xbc, 0xb5, 0x1c, +0x45, 0x6a, 0x20, 0x9b, 0xb6, 0x2f, 0x9a, 0x16, +0xac, 0x09, 0x32, 0x60, 0xb6, 0xef, 0x9c, 0xc0, +0x7b, 0x85, 0x55, 0x28, 0xf4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x24, +0x9e, 0x6e, 0x35, 0xdb, 0x46, 0x58, 0xbc, 0x9b, +0xa2, 0x80, 0x96, 0x9d, 0xeb, 0x7b, 0x65, 0xe4, +0x31, 0x38, 0x65, 0x36, 0x9e, 0xd3, 0x1d, 0x58, +0x27, 0x6e, 0x1b, 0x5d, 0x63, 0xe9, 0x90, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x82, 0x4e, 0x4a, 0x01, 0x3c, 0xe5, 0xfb, +0x23, 0xf0, 0x2b, 0x96, 0xfb, 0x76, 0x2e, 0x54, +0xc0, 0xcb, 0x60, 0xde, 0xec, 0xed, 0xde, 0x59, +0x63, 0xe4, 0xce, 0x6d, 0x1b, 0xbe, 0x63, 0x12, +0x27, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0x61, 0x7a, 0x09, 0x91, 0xcf, +0x4d, 0x5c, 0x76, 0xa2, 0x10, 0x75, 0xa1, 0x81, +0x2f, 0x24, 0xcc, 0x9c, 0x60, 0xfc, 0xc3, 0x80, +0x4e, 0x7f, 0x5b, 0x3b, 0x13, 0x43, 0x58, 0x14, +0x86, 0xa3, 0x10, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0xb5, 0x09, 0xfd, +0x43, 0x0c, 0xb7, 0x3b, 0xc6, 0xd0, 0x3f, 0xb0, +0x02, 0x0b, 0xc9, 0x40, 0xb5, 0xd5, 0x15, 0xad, +0xc1, 0xd4, 0x42, 0xab, 0x14, 0x2d, 0x90, 0x19, +0x35, 0x44, 0x37, 0xb3, 0x85, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0x8b, +0xb0, 0xee, 0xc5, 0x1c, 0xd6, 0x04, 0x84, 0xd6, +0x2e, 0x87, 0x54, 0x8f, 0x87, 0x35, 0xc2, 0x70, +0x52, 0x9e, 0xd2, 0x47, 0x9e, 0xed, 0x0f, 0x3c, +0xac, 0x2e, 0x9f, 0x18, 0xbc, 0x47, 0x99, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0x06, 0xcc, 0x25, 0x68, 0xf0, 0xf6, 0xfe, +0x4a, 0xde, 0x4a, 0xc0, 0x5b, 0x03, 0xec, 0x4b, +0xd4, 0x35, 0xdf, 0x9c, 0x97, 0x54, 0x2e, 0x04, +0x2a, 0x14, 0xd9, 0xb3, 0xfa, 0x7a, 0x20, 0xbb, +0x94, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0x8e, 0x21, 0x80, 0x2e, 0x37, +0x02, 0xf9, 0x2a, 0x3f, 0x04, 0xf0, 0xf7, 0x80, +0xc6, 0x52, 0x9a, 0x9e, 0x0d, 0xe5, 0x90, 0xc3, +0xee, 0xde, 0x98, 0xb3, 0x1c, 0x94, 0x20, 0x8f, +0xb4, 0xd3, 0x9f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0x97, 0x74, 0x14, +0x9b, 0x88, 0x5c, 0x7f, 0xd9, 0x28, 0x70, 0xe2, +0x1d, 0x21, 0xa7, 0x4c, 0x15, 0x44, 0x79, 0x3b, +0x66, 0xc1, 0x82, 0x82, 0xaa, 0xa5, 0xf0, 0xb7, +0x02, 0xa5, 0xec, 0xd1, 0x95, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0x7b, +0x13, 0xb1, 0x68, 0x28, 0x24, 0x5f, 0xe3, 0x94, +0x65, 0x55, 0xa6, 0x8a, 0xb9, 0xfd, 0x71, 0x3e, +0xac, 0xb8, 0x45, 0x70, 0x5d, 0x0a, 0xeb, 0xc5, +0x8a, 0x54, 0xe6, 0xf2, 0xf2, 0xc8, 0xbd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0xa8, 0x59, 0x9f, 0xa1, 0x6f, 0xcd, 0xd7, +0xa2, 0xc9, 0xbb, 0x63, 0x83, 0x56, 0x0c, 0x81, +0x91, 0x9c, 0xe0, 0x26, 0x7a, 0x4b, 0x88, 0x2f, +0xae, 0xaa, 0x81, 0xe5, 0xa9, 0x8e, 0x9a, 0x80, +0xbd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0xc3, 0x93, 0xbf, 0x07, 0xbb, +0xce, 0xcd, 0x4e, 0x43, 0xb9, 0x71, 0x27, 0xcb, +0x69, 0x8f, 0x09, 0xbd, 0x50, 0x2d, 0xb7, 0xe6, +0xe1, 0x26, 0xc9, 0x93, 0x6b, 0xae, 0x6c, 0x8c, +0x87, 0xc4, 0x35, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0xe0, 0xbb, 0x88, +0xb4, 0xac, 0x11, 0x99, 0xb9, 0x93, 0x20, 0x30, +0xb7, 0x39, 0xe5, 0x6b, 0x83, 0x5a, 0x7c, 0xa7, +0x50, 0xf0, 0xec, 0x34, 0xa8, 0xc3, 0x37, 0xb2, +0x05, 0xa7, 0xff, 0x56, 0x42, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0x51, +0x07, 0xc6, 0x16, 0x39, 0xea, 0x56, 0xfb, 0xe7, +0x9d, 0xda, 0x58, 0xb8, 0x2d, 0x5c, 0xcd, 0x94, +0xba, 0xb7, 0x1e, 0x50, 0x79, 0x26, 0x8f, 0xa2, +0xa7, 0x4a, 0x84, 0xf6, 0xe1, 0x9d, 0xc1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0x9b, 0xd4, 0x00, 0xc7, 0x1e, 0xfa, 0xd4, +0xd5, 0xc9, 0x2c, 0x23, 0x3d, 0x5d, 0x59, 0xbf, +0x9b, 0xe0, 0xce, 0xbf, 0x37, 0x6c, 0xe9, 0xc2, +0x57, 0x83, 0xe4, 0x90, 0xbb, 0x2b, 0x60, 0x5c, +0xba, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0xaa, 0xa3, 0x75, 0x1c, 0x5f, +0x7b, 0x9c, 0xfa, 0xd1, 0x51, 0x1c, 0x50, 0xee, +0xe3, 0x67, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xc5, 0xb7, 0x79, 0xb7, +0xc3, 0x03, 0x89, 0x4f, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0x11, 0xf1, 0xbf, +0x87, 0x55, 0x4c, 0x5d, 0x8c, 0x52, 0x6b, 0xc3, +0xbf, 0xbe, 0xf0, 0x0d, 0xcd, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x64, +0xe8, 0xe4, 0x97, 0x0a, 0xb0, 0x82, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0x44, +0x18, 0x4b, 0x16, 0x56, 0xbb, 0xe9, 0x51, 0xf0, +0x17, 0xd7, 0x12, 0x23, 0x25, 0x93, 0x20, 0xfb, +0xad, 0x6a, 0xe8, 0x4f, 0x10, 0xb1, 0x17, 0x65, +0xbb, 0x5c, 0x35, 0x9d, 0xff, 0x44, 0xe1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0xb6, 0xa2, 0xb7, 0x56, 0x6d, 0x89, 0x61, +0xa7, 0x62, 0x5a, 0x91, 0x4d, 0x34, 0x52, 0x12, +0xef, 0xc0, 0x38, 0x05, 0x0f, 0xfa, 0x09, 0x18, +0x00, 0xb2, 0x4f, 0xf3, 0x9b, 0x9b, 0x02, 0xf4, +0xef, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0x20, 0xbe, 0x36, 0x2f, 0xfa, +0x31, 0x70, 0xb2, 0x5f, 0x74, 0xea, 0xb3, 0xdf, +0x3c, 0x2f, 0x41, 0x89, 0xa6, 0xbb, 0xe3, 0x7b, +0x29, 0x3c, 0x62, 0x97, 0x80, 0x8a, 0xf0, 0xf5, +0x83, 0xec, 0x40, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0x20, 0x58, 0xf7, +0x68, 0x75, 0x36, 0xef, 0xe8, 0x5b, 0x8b, 0xc4, +0x90, 0xf6, 0x0a, 0x5a, 0xbe, 0x3c, 0x10, 0x02, +0xaa, 0x6a, 0xa1, 0xd2, 0x29, 0x57, 0x2b, 0xef, +0x2d, 0x88, 0x02, 0x82, 0x08, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0xa2, +0xc4, 0x0a, 0x5c, 0x9d, 0x7e, 0x5d, 0x0f, 0xde, +0xa3, 0x72, 0x2f, 0xa1, 0x32, 0x68, 0x61, 0x5a, +0x86, 0x7b, 0xc8, 0x4c, 0xb3, 0xbf, 0x87, 0x79, +0x56, 0x6d, 0xae, 0x94, 0x15, 0x97, 0x0d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0x6b, 0x52, 0xde, 0xd7, 0xb7, 0x9e, 0x96, +0x1a, 0x52, 0x50, 0x67, 0x80, 0x12, 0x62, 0xf3, +0x47, 0xed, 0x3a, 0x1e, 0xc9, 0xef, 0x2b, 0xcb, +0x5a, 0xec, 0x5a, 0xd7, 0x57, 0xf9, 0xca, 0xc4, +0x75, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0x02, 0x0f, 0x7c, 0x0f, 0x63, +0xe4, 0xd7, 0xb9, 0x89, 0xad, 0x38, 0xc2, 0x72, +0x25, 0x63, 0x7d, 0x1e, 0xd8, 0xab, 0x02, 0xbf, +0x1b, 0xc7, 0x97, 0x81, 0xd8, 0xbc, 0x8b, 0xaa, +0x87, 0x47, 0xc8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0xe6, 0x10, 0x16, +0x2e, 0x75, 0xef, 0xf7, 0x38, 0x44, 0x6c, 0xaf, +0x4d, 0xd3, 0xc2, 0x1a, 0xe3, 0x75, 0x2a, 0x84, +0x0f, 0x7b, 0x00, 0x37, 0x4c, 0x6c, 0x86, 0x63, +0x70, 0xd1, 0x45, 0x6b, 0xd3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0x7a, +0x14, 0x97, 0xfb, 0x97, 0x1c, 0xcf, 0x1b, 0x24, +0xd0, 0x36, 0xc4, 0x3a, 0x52, 0xac, 0xec, 0x80, +0x8b, 0x35, 0xdf, 0x19, 0x5a, 0xec, 0xb6, 0x81, +0xee, 0x56, 0x18, 0xcd, 0xf7, 0x9f, 0xb1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0x81, 0x29, 0x3b, 0x1d, 0xce, 0x40, 0xf2, +0x4c, 0x27, 0xf6, 0x16, 0xef, 0x0d, 0x60, 0x73, +0xa4, 0x94, 0x7d, 0x3e, 0x46, 0x7c, 0x87, 0x10, +0x00, 0x2c, 0x15, 0x59, 0x29, 0xf0, 0xcb, 0x27, +0x22, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0xa2, 0x30, 0xce, 0x0b, 0xc1, +0x3a, 0x2f, 0xff, 0x1e, 0x6f, 0x20, 0x77, 0x12, +0x16, 0x1c, 0xbe, 0x90, 0xe4, 0x77, 0xbc, 0x0b, +0x52, 0xc9, 0x69, 0xa4, 0xff, 0xfe, 0x64, 0x30, +0xa9, 0x93, 0x4e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0xa7, 0x2e, 0x5a, +0x10, 0x7d, 0x2c, 0x4f, 0x61, 0x06, 0x50, 0xea, +0x72, 0xa2, 0xd4, 0xeb, 0x4d, 0xee, 0xaf, 0x19, +0x4d, 0x0f, 0x89, 0x34, 0x68, 0x82, 0x92, 0x2d, +0x98, 0x99, 0x94, 0xe1, 0x8d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0x62, +0x35, 0x7b, 0xa1, 0xc8, 0xf2, 0xa3, 0x32, 0x98, +0x2f, 0x50, 0x40, 0x51, 0x2b, 0x6d, 0x6c, 0x13, +0x3b, 0xbd, 0x07, 0x25, 0xd1, 0x56, 0x3f, 0x83, +0xbe, 0x80, 0x9f, 0xbb, 0x5f, 0xae, 0xca, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0x97, 0x04, 0xab, 0x06, 0x61, 0x53, 0xfb, +0x80, 0x9a, 0xa8, 0xf8, 0xc9, 0x42, 0xac, 0x44, +0x28, 0x2b, 0xe7, 0x6f, 0x8c, 0x1e, 0x6a, 0xc0, +0xd4, 0xb4, 0xf7, 0xe8, 0x57, 0x21, 0x88, 0x84, +0x6f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0x92, 0xd4, 0x64, 0x47, 0x7a, +0xf3, 0xb8, 0x8a, 0x3e, 0xe3, 0xd1, 0x62, 0x71, +0xfa, 0xd5, 0xef, 0xc1, 0x3a, 0x6e, 0x5c, 0xf6, +0x24, 0xc1, 0x67, 0xfd, 0x75, 0x63, 0xb5, 0xd4, +0xd1, 0x65, 0x72, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0x74, 0x75, 0xcb, +0x0f, 0x0d, 0x96, 0xaf, 0xd6, 0xa4, 0x5c, 0x78, +0xfa, 0x19, 0xd2, 0xb5, 0xbb, 0xf9, 0x7c, 0xe5, +0x89, 0xfe, 0xa2, 0xfa, 0x92, 0x0f, 0xe2, 0x06, +0xa9, 0x9d, 0xce, 0x6e, 0x0a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0x21, +0xb2, 0x71, 0xa7, 0x60, 0xf1, 0x95, 0xc7, 0x96, +0xdb, 0x7f, 0xea, 0xea, 0xaa, 0xd7, 0xd2, 0xfb, +0x28, 0xda, 0x09, 0xad, 0x00, 0xd5, 0x5c, 0xad, +0x3a, 0x33, 0xb1, 0x9a, 0x48, 0xd1, 0x8c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0xf2, 0x27, 0x66, 0x24, 0x0d, 0x86, 0xf0, +0x2a, 0xbe, 0xc1, 0x47, 0x9c, 0xce, 0x19, 0x61, +0xca, 0x1d, 0x78, 0xe8, 0xe2, 0xc0, 0xbb, 0x5e, +0x3e, 0xfe, 0xf9, 0x39, 0x79, 0x18, 0xd2, 0x88, +0xfe, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0x00, 0xb2, 0x9a, 0x59, 0x92, +0x2d, 0x28, 0xaf, 0x3b, 0x80, 0x84, 0x2f, 0xab, +0x88, 0x47, 0xa7, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xd8, 0x70, 0x00, 0x86, +0x15, 0x73, 0xf5, 0x52, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0xfa, 0x4e, 0x41, +0xd9, 0x33, 0x77, 0xd8, 0x72, 0xe4, 0xda, 0x70, +0x96, 0x1a, 0xaf, 0x4d, 0x5b, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0xbc, +0xba, 0x05, 0xb9, 0x51, 0xed, 0x45, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0xcc, +0xb1, 0x1b, 0xad, 0x51, 0xf8, 0x90, 0xca, 0x7d, +0xc7, 0x93, 0xcc, 0xd8, 0x0c, 0x54, 0xc4, 0x0b, +0x11, 0x29, 0xe6, 0x3d, 0x0f, 0xf3, 0x85, 0x3c, +0x7f, 0xfb, 0xaf, 0x5b, 0x64, 0x25, 0xee, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0xcf, 0x36, 0x43, 0x64, 0x99, 0x79, 0x6b, +0x2d, 0x94, 0xf1, 0x25, 0x88, 0xa6, 0x32, 0xc7, +0xc6, 0x07, 0x97, 0xa1, 0x0b, 0xad, 0x0a, 0xfd, +0x95, 0x80, 0x28, 0xf4, 0x84, 0xda, 0xe9, 0x43, +0x5a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0x6b, 0x6c, 0x72, 0xf0, 0x1f, +0xb5, 0x3a, 0x78, 0x01, 0x89, 0xb3, 0x4c, 0x17, +0x9d, 0xdf, 0x30, 0xc6, 0x7e, 0xa7, 0xd8, 0x56, +0x8c, 0xb7, 0xc7, 0x8f, 0xda, 0xc9, 0x68, 0x75, +0xb7, 0xfb, 0x50, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0x99, 0xdc, 0xa8, +0x6d, 0xfe, 0xab, 0x5a, 0x56, 0x16, 0xa7, 0xc7, +0x56, 0x4e, 0x89, 0x8b, 0xd4, 0xba, 0x2e, 0xca, +0xf6, 0x89, 0x7d, 0x03, 0xd9, 0xeb, 0x3a, 0x21, +0x31, 0x30, 0x46, 0x7b, 0x4f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0xce, +0x25, 0x10, 0x65, 0xd1, 0xff, 0xb5, 0x61, 0x1e, +0x3a, 0x1f, 0xae, 0x58, 0xf6, 0xcd, 0x6c, 0xbd, +0x82, 0x49, 0x87, 0x69, 0xbd, 0x5c, 0xc7, 0xf4, +0x1e, 0xdb, 0x12, 0x3b, 0x9f, 0x4f, 0x9a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0xa8, 0x4c, 0x24, 0xa9, 0x96, 0x5e, 0x3f, +0xc2, 0xf5, 0x98, 0x70, 0x67, 0xe8, 0x73, 0xe9, +0xc7, 0x88, 0x98, 0x28, 0xbb, 0xfe, 0x0f, 0x92, +0xbd, 0xe5, 0x1a, 0xf4, 0xef, 0x33, 0xdd, 0xd9, +0x69, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0xc5, 0x95, 0xe5, 0x06, 0x5c, +0xf9, 0x40, 0x0d, 0x4d, 0xce, 0x7f, 0xda, 0x3b, +0xc8, 0xb4, 0xab, 0x0d, 0x1b, 0xcf, 0xc0, 0xa7, +0x9c, 0x9d, 0xf3, 0x14, 0x7e, 0x9c, 0x85, 0xfc, +0x7b, 0xf4, 0xb7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0xfc, 0xfb, 0x2a, +0x2a, 0x8b, 0x8d, 0xed, 0xbe, 0x3a, 0xf9, 0x6d, +0x71, 0x5e, 0x7c, 0x9d, 0x3f, 0x44, 0xf9, 0x85, +0x28, 0x4b, 0x13, 0xe5, 0xfd, 0xe9, 0xb1, 0x53, +0x32, 0x74, 0x13, 0x8e, 0x41, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x94, +0x02, 0xdf, 0x8b, 0x12, 0x87, 0xfc, 0x25, 0x04, +0xba, 0x02, 0x80, 0xfd, 0x2c, 0xb3, 0xee, 0x55, +0xb6, 0xb4, 0xb8, 0x78, 0x1b, 0xf9, 0x30, 0xc2, +0x23, 0x18, 0x61, 0x63, 0xd4, 0x76, 0x12, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0x1a, 0xcb, 0xb4, 0x52, 0x4e, 0x8e, 0x56, +0x09, 0xa1, 0x0e, 0x26, 0x0b, 0xea, 0xd8, 0x56, +0x50, 0x1d, 0x4a, 0x7f, 0xac, 0x38, 0x78, 0xcd, +0x1a, 0x4c, 0x69, 0xd9, 0x3c, 0x97, 0x36, 0x54, +0x6f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0x6f, 0x3e, 0x4c, 0x51, 0x4e, +0xa7, 0x2e, 0xc5, 0x1f, 0x63, 0x42, 0x5c, 0x9a, +0x7f, 0x61, 0xfa, 0x40, 0xf5, 0x87, 0x22, 0xb2, +0xbc, 0x3f, 0x42, 0x75, 0xdc, 0x84, 0x94, 0xa5, +0x78, 0x0d, 0x7f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0xea, 0xa4, 0x84, +0x14, 0x7d, 0xe9, 0x44, 0x49, 0x5f, 0x28, 0x01, +0x73, 0xc9, 0x71, 0x57, 0x29, 0xe1, 0x0d, 0x31, +0x2e, 0x51, 0xec, 0x1d, 0xe7, 0x56, 0x5b, 0xaf, +0x13, 0xe9, 0x16, 0x4d, 0x94, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0xcd, +0xa8, 0xd7, 0x2a, 0xee, 0x87, 0xc2, 0xf6, 0x1c, +0x21, 0xaf, 0x8d, 0x6b, 0xac, 0x22, 0x79, 0x72, +0xa5, 0xb3, 0xef, 0x7f, 0x15, 0x5d, 0x68, 0x5b, +0x4c, 0xfe, 0x47, 0x25, 0xda, 0x2c, 0x4c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0xd3, 0xe2, 0x7c, 0xe6, 0x8e, 0x18, 0xcf, +0x4f, 0xec, 0x60, 0x92, 0x80, 0xbd, 0x58, 0x34, +0x00, 0x8a, 0x24, 0xc8, 0x48, 0xe1, 0x3b, 0x49, +0x67, 0x03, 0x70, 0x86, 0x48, 0x75, 0xa5, 0xc4, +0x6b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0xe6, 0x4d, 0xe9, 0x76, 0x9a, +0x2a, 0xd6, 0x33, 0xa2, 0x4d, 0xa2, 0xd9, 0x18, +0xa1, 0x2b, 0x02, 0xc5, 0x03, 0x88, 0x37, 0x24, +0x26, 0xe9, 0x0a, 0xd5, 0x3d, 0x6c, 0x5b, 0xf1, +0xc7, 0xb5, 0xd4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0xc9, 0x4e, 0xeb, +0xb5, 0x0a, 0x55, 0x15, 0x3a, 0x31, 0x6b, 0x36, +0x61, 0x48, 0xa3, 0x8a, 0xfd, 0x34, 0xbe, 0x20, +0x8e, 0x34, 0xb3, 0x69, 0x65, 0x1e, 0xa6, 0xda, +0x3f, 0x22, 0x96, 0x28, 0x8c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0x44, +0xe1, 0x11, 0xc1, 0x3c, 0xcd, 0x01, 0x36, 0x92, +0x4f, 0x33, 0x4f, 0x57, 0x6d, 0x44, 0x5d, 0xd9, +0xb8, 0x2d, 0xaf, 0x92, 0xb5, 0x91, 0x6b, 0x89, +0x7b, 0xf6, 0xed, 0x1c, 0x4a, 0xbf, 0x09, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0xb4, 0xed, 0xd0, 0x0f, 0x2e, 0x2b, 0x48, +0x52, 0x92, 0xb0, 0xca, 0xd5, 0xc1, 0x2f, 0xf9, +0x66, 0xef, 0x10, 0x84, 0x22, 0xea, 0x37, 0x87, +0x22, 0xdf, 0x2e, 0xd5, 0x5e, 0x64, 0x6d, 0x3c, +0x54, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0xcd, 0xb6, 0x8d, 0xbf, 0x2c, +0x72, 0xe8, 0xea, 0x42, 0xf7, 0x69, 0x51, 0xfa, +0x55, 0x32, 0xd3, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x2f, 0x95, 0x6c, 0x83, +0x4c, 0x52, 0x94, 0x2a, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0x9c, 0xa7, 0x60, +0x07, 0xd0, 0xf5, 0xf6, 0x6c, 0x0d, 0x6c, 0x7b, +0x84, 0x71, 0x52, 0x15, 0x62, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x8f, +0xa5, 0x95, 0x2a, 0x78, 0x0e, 0x85, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0xfc, +0x01, 0x65, 0xdf, 0x78, 0x3e, 0x68, 0x86, 0xcc, +0x2e, 0x00, 0xf4, 0x35, 0xf5, 0x44, 0x39, 0x51, +0xa7, 0xa0, 0x39, 0x6a, 0x2b, 0xb9, 0x75, 0x9b, +0x38, 0xe5, 0x57, 0x46, 0xfa, 0x67, 0x00, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x84, 0xc2, 0xdd, 0x38, 0x55, 0x34, 0xc3, +0xad, 0x29, 0x6a, 0x5d, 0xcb, 0x54, 0x71, 0xb6, +0xdd, 0xee, 0xfd, 0x01, 0x6a, 0xe7, 0x06, 0x48, +0x34, 0x2c, 0x7f, 0x80, 0x51, 0x6a, 0xdc, 0xa5, +0x71, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x79, 0x7c, 0xef, 0x27, 0x90, +0x48, 0x58, 0x42, 0x67, 0x2c, 0xaa, 0x9a, 0x6b, +0x33, 0xac, 0xa1, 0x8d, 0x96, 0x51, 0x22, 0xb1, +0x82, 0xc9, 0x8b, 0x39, 0xe2, 0xbb, 0x86, 0x6b, +0x14, 0x47, 0x23, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0xeb, 0xbd, 0xf7, +0x0e, 0x96, 0x4a, 0x58, 0xef, 0x49, 0x64, 0xec, +0xec, 0x85, 0xf6, 0x05, 0x03, 0xf5, 0x9b, 0xcd, +0x34, 0xd2, 0xb2, 0x9e, 0x04, 0x65, 0x28, 0xff, +0xad, 0x96, 0x77, 0x07, 0xae, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0x4c, +0x8b, 0x35, 0x1e, 0xaa, 0x7e, 0xce, 0x98, 0xdd, +0xdc, 0xd6, 0x60, 0x5f, 0xb0, 0x88, 0x07, 0xb0, +0x74, 0x05, 0x21, 0xac, 0xe3, 0xe0, 0x6c, 0x8e, +0xb0, 0x33, 0xd2, 0x87, 0x94, 0xf3, 0x9c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x3f, 0x48, 0x22, 0x21, 0xce, 0x60, 0xa7, +0x73, 0x57, 0xdd, 0x77, 0x21, 0x34, 0x4d, 0x65, +0x5f, 0xdb, 0xf5, 0xc3, 0x92, 0x96, 0x9d, 0x4f, +0x05, 0x6a, 0x50, 0xa3, 0xec, 0x74, 0x78, 0x7c, +0x37, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0xac, 0xb5, 0xe2, 0x26, 0x77, +0xc2, 0xcc, 0x3c, 0x57, 0xca, 0x55, 0xf1, 0xd7, +0x73, 0x77, 0x5e, 0x7c, 0xe6, 0x1f, 0xcf, 0xe4, +0x7f, 0x3f, 0xaf, 0xc1, 0x8d, 0x30, 0x63, 0x4d, +0x19, 0x2a, 0x3c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0x3e, 0x05, 0x04, +0xa4, 0xed, 0x29, 0xdb, 0x1e, 0x00, 0x87, 0x02, +0x77, 0x55, 0x40, 0x8d, 0x0c, 0x12, 0x80, 0xa0, +0xba, 0x8a, 0xbc, 0xee, 0x05, 0x25, 0x33, 0xa3, +0xb8, 0x83, 0xc3, 0xca, 0x62, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x8a, +0x6c, 0xba, 0x15, 0x53, 0x76, 0x3e, 0xc0, 0x54, +0xfa, 0xd6, 0x12, 0x4e, 0xe1, 0xb1, 0x20, 0x17, +0xf2, 0xde, 0x08, 0x69, 0x1d, 0x14, 0x68, 0xc7, +0x8b, 0x44, 0x43, 0x56, 0xdf, 0xbd, 0xba, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0xff, 0x59, 0x64, 0xda, 0xbd, 0xce, 0xbf, +0x7f, 0x24, 0x71, 0x3f, 0x33, 0x20, 0x1f, 0x3e, +0x80, 0xbc, 0x4d, 0x29, 0xa8, 0x90, 0xcd, 0x74, +0x5d, 0x55, 0x49, 0xa3, 0x02, 0x31, 0xa7, 0xd0, +0xe4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0xde, 0xe5, 0x08, 0x2c, 0x36, +0xc6, 0xf9, 0x81, 0x51, 0x1c, 0xf8, 0x1c, 0x70, +0xd6, 0xc6, 0x82, 0xfe, 0x38, 0x5e, 0x75, 0x1f, +0x74, 0x99, 0xa6, 0x1a, 0x9a, 0x19, 0x26, 0x25, +0xe6, 0xe3, 0x7c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0xfe, 0xa3, 0x3e, +0x81, 0x04, 0x7c, 0x92, 0xe1, 0x04, 0xd5, 0x56, +0x9e, 0x27, 0x22, 0x8c, 0x75, 0xb8, 0xb7, 0xb4, +0xcf, 0x60, 0x53, 0xc2, 0x7f, 0x3f, 0x5a, 0x7a, +0xad, 0x36, 0x29, 0xf7, 0x77, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0x60, +0x14, 0x8e, 0xd2, 0x4b, 0x0b, 0x07, 0x07, 0xbf, +0x52, 0x79, 0x43, 0x5a, 0x61, 0xa2, 0xb7, 0x58, +0x3c, 0xff, 0x62, 0x45, 0x53, 0xb6, 0xc0, 0x81, +0x7c, 0xae, 0x3a, 0xd1, 0xa7, 0xda, 0xd2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0xcf, 0x41, 0x8f, 0xbc, 0x67, 0x57, 0x83, +0x66, 0x58, 0x5f, 0x86, 0xdd, 0x69, 0x0b, 0xb7, +0x17, 0x86, 0x86, 0x1c, 0x37, 0xdb, 0x0a, 0x86, +0xe6, 0x97, 0xd6, 0x25, 0xeb, 0x09, 0xd5, 0xe1, +0x4f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0x7c, 0x88, 0x8c, 0xc4, 0xce, +0x66, 0x14, 0xa8, 0xca, 0x44, 0x5a, 0x9c, 0x64, +0x85, 0x60, 0xcf, 0xe8, 0xd2, 0x72, 0xf6, 0x2c, +0xc0, 0x69, 0xed, 0xa9, 0x18, 0x5e, 0x6e, 0x8c, +0x18, 0x1e, 0x3b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0x91, 0x96, 0x53, +0x75, 0x50, 0xad, 0x05, 0xb1, 0x89, 0x6e, 0x7e, +0xfb, 0x9d, 0x1a, 0x65, 0xa3, 0x0e, 0xcd, 0xca, +0x4f, 0xfe, 0xc4, 0xd1, 0x66, 0x2f, 0x87, 0x5c, +0x98, 0x0f, 0xa2, 0x85, 0x62, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0x97, +0xfb, 0xe3, 0x26, 0x7d, 0x49, 0x43, 0x4f, 0x5a, +0x4b, 0xd4, 0x50, 0x12, 0x3d, 0xdb, 0x38, 0x01, +0x22, 0x84, 0x41, 0x4c, 0xa5, 0x15, 0xaa, 0xc6, +0x72, 0xaa, 0xcd, 0xf1, 0xf2, 0x7d, 0xca, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0xc0, 0x91, 0xf1, 0x7d, 0x89, 0xe2, 0x49, +0x79, 0x07, 0x0e, 0x27, 0x5a, 0xd2, 0xc4, 0x21, +0x27, 0x88, 0x2f, 0x58, 0xff, 0xed, 0x5d, 0x62, +0x67, 0x08, 0x15, 0x8b, 0x8b, 0xb7, 0x50, 0xf2, +0x72, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0x15, 0x14, 0x71, 0xc4, 0xf1, +0xb6, 0x8d, 0x90, 0x62, 0xac, 0xd8, 0x60, 0xd0, +0x5a, 0x62, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x87, 0x6b, 0x5b, 0xb5, +0xbe, 0x8d, 0x04, 0x06, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0xe2, 0x3a, 0xa0, +0x66, 0x7e, 0x6d, 0xd6, 0xf2, 0x82, 0x14, 0xbd, +0xdf, 0x32, 0xb3, 0x8e, 0x0e, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x7b, +0x29, 0x08, 0x41, 0x60, 0x18, 0x8a, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0x11, +0xd0, 0x23, 0xf4, 0xd2, 0x1d, 0xf9, 0x0f, 0x50, +0xa6, 0xce, 0x8a, 0x31, 0x37, 0x1b, 0xaf, 0x4d, +0x50, 0x1c, 0x5c, 0xcb, 0xf2, 0x14, 0x77, 0xf6, +0x77, 0x4b, 0x21, 0x3f, 0xe6, 0x36, 0xb7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0x8e, 0xfc, 0xed, 0x38, 0x90, 0xcf, 0xb3, +0x57, 0x95, 0xfa, 0x77, 0x7d, 0xb1, 0x66, 0x17, +0x12, 0xf5, 0x3d, 0x09, 0xa2, 0xb8, 0xb9, 0x9b, +0xfb, 0xf2, 0x1d, 0x97, 0xdd, 0x66, 0x68, 0x4b, +0xb2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0x50, 0x4a, 0xf3, 0x57, 0x75, +0x72, 0xee, 0x06, 0x5d, 0xc0, 0xf9, 0x94, 0x0a, +0x88, 0xf8, 0xf2, 0xc9, 0x8e, 0x4e, 0xe4, 0xa8, +0x0a, 0xc2, 0x9a, 0x2c, 0x43, 0xa4, 0x7b, 0x6f, +0x01, 0x74, 0x83, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0xb4, 0x53, 0xcc, +0x1c, 0x05, 0x6d, 0x85, 0x7b, 0x3a, 0x01, 0xd4, +0x05, 0x84, 0x3c, 0x4c, 0xdd, 0xc9, 0x64, 0x47, +0x3b, 0x66, 0x6a, 0xc2, 0xdb, 0x37, 0x5d, 0x57, +0x3b, 0xfc, 0x13, 0x9b, 0xe0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0x89, +0x3a, 0xb3, 0xe5, 0x9d, 0xa1, 0x95, 0x29, 0x31, +0x0a, 0x72, 0xf2, 0x5c, 0xad, 0x3f, 0x7c, 0x62, +0x6d, 0x4d, 0xe3, 0xa8, 0x58, 0x0a, 0xe4, 0x4b, +0x26, 0x81, 0x15, 0x22, 0x08, 0xa1, 0x86, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0x90, 0x22, 0xf1, 0x41, 0xa9, 0xef, 0xec, +0xc5, 0x97, 0x67, 0xb6, 0xc5, 0xc2, 0x95, 0xcf, +0x41, 0xf1, 0x18, 0xa6, 0x91, 0x8a, 0x54, 0xdc, +0xd6, 0x23, 0x76, 0x6e, 0x42, 0x92, 0xc8, 0x1a, +0x4f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x83, 0x9b, 0x46, 0x49, 0x72, +0xee, 0x71, 0x7e, 0x28, 0x32, 0xb3, 0x15, 0x36, +0xae, 0x59, 0x73, 0x87, 0xcc, 0x12, 0x48, 0xd3, +0xa8, 0x95, 0x91, 0x70, 0x33, 0xf8, 0x19, 0x72, +0x0b, 0xe3, 0x57, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0x2f, 0x20, 0x41, +0x96, 0xf3, 0x4b, 0x25, 0x62, 0xe5, 0xe8, 0xd1, +0x6b, 0x12, 0xb5, 0xcb, 0x17, 0xc0, 0x2b, 0x1b, +0x17, 0x1d, 0x7e, 0x99, 0x4f, 0xe4, 0x45, 0xdb, +0x30, 0x8e, 0xbc, 0x67, 0x1d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0xea, +0x68, 0x39, 0x8b, 0xaf, 0xe1, 0x3d, 0x01, 0x1c, +0x70, 0xf4, 0x09, 0xeb, 0x63, 0x96, 0xe4, 0x32, +0xbe, 0xf1, 0xd7, 0xb7, 0x9e, 0xd2, 0xba, 0xc1, +0xdc, 0x83, 0xce, 0xf1, 0x19, 0x90, 0xc8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x40, 0x8e, 0x5e, 0x6d, 0x1e, 0x4f, 0xa6, +0x3d, 0x74, 0xee, 0x1e, 0x62, 0x47, 0xb4, 0x73, +0x50, 0x30, 0x01, 0xb4, 0x04, 0xec, 0x39, 0x5b, +0x08, 0x29, 0x55, 0x69, 0x14, 0x8d, 0xfc, 0x8e, +0x09, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x72, 0x09, 0x2c, 0x0f, 0xb4, +0xe3, 0x13, 0x1d, 0x9e, 0xba, 0xfa, 0x64, 0x09, +0x88, 0xfd, 0x7b, 0x1b, 0x81, 0xa7, 0xf8, 0x54, +0x3e, 0xec, 0x21, 0x94, 0x3e, 0x3a, 0xce, 0xa4, +0x62, 0xe5, 0xd7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0xcf, 0x90, 0x27, +0x4c, 0x2d, 0x78, 0x48, 0x27, 0x64, 0xcb, 0x20, +0x44, 0x54, 0x46, 0xe8, 0xf4, 0x3b, 0xcd, 0x70, +0xf6, 0xe8, 0x49, 0x7e, 0xa5, 0x16, 0x39, 0x51, +0xe0, 0x63, 0x51, 0x4c, 0x5b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0x58, +0xbf, 0x8e, 0x3a, 0x87, 0x9f, 0x39, 0x64, 0x97, +0x1d, 0x04, 0x56, 0x5e, 0x1a, 0xb5, 0x40, 0x80, +0x4d, 0x93, 0x87, 0x49, 0x38, 0xf3, 0x39, 0xcc, +0x99, 0x16, 0x9c, 0xf4, 0xa1, 0xd5, 0x20, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0x00, 0x5e, 0x34, 0x54, 0x4b, 0x4e, 0xda, +0x99, 0xb8, 0x5f, 0x2a, 0x1f, 0xbc, 0x36, 0x64, +0x25, 0x9f, 0x16, 0x04, 0xb9, 0xad, 0x20, 0xa3, +0x70, 0xf9, 0x22, 0x73, 0x64, 0xce, 0x5c, 0x55, +0x5f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0xbf, 0x0e, 0x7b, 0x00, 0x32, +0x4a, 0x61, 0xcb, 0x8c, 0x9e, 0xbe, 0x40, 0x3f, +0xb0, 0xcc, 0x73, 0x33, 0x57, 0xc9, 0x1f, 0xfd, +0x9d, 0x20, 0x22, 0x24, 0xf2, 0x81, 0x40, 0xac, +0x76, 0x2c, 0x0e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x50, 0xce, 0xe8, +0x57, 0x5c, 0x0d, 0xfd, 0x93, 0x1d, 0x39, 0x32, +0x59, 0x76, 0x63, 0x77, 0xb0, 0x89, 0x6d, 0x7b, +0x91, 0xd2, 0x71, 0xf5, 0xc9, 0x31, 0x72, 0x26, +0x6f, 0x12, 0xea, 0x49, 0x61, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0xb1, +0x56, 0xc3, 0x94, 0x3b, 0x63, 0x34, 0x5b, 0xd0, +0xd6, 0x16, 0xd2, 0x4f, 0xc5, 0x5e, 0x14, 0x4b, +0xc7, 0x23, 0x4b, 0x91, 0xf1, 0xd0, 0x32, 0x40, +0xee, 0x18, 0x79, 0x8f, 0xef, 0x50, 0x51, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0xcd, 0x04, 0x87, 0xab, 0xff, 0xcc, 0xd2, +0x19, 0x40, 0x43, 0xe2, 0x75, 0xed, 0x86, 0xc2, +0x91, 0xfe, 0x1a, 0x88, 0xf8, 0x26, 0xd7, 0xcf, +0xbe, 0x14, 0xf2, 0x63, 0x7e, 0x6c, 0x83, 0xe3, +0xbd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0x82, 0x7e, 0x20, 0x3e, 0x85, +0xfc, 0xe9, 0xf2, 0xd0, 0xdb, 0x1a, 0xe6, 0x97, +0xc2, 0xa1, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xde, 0x47, 0xd9, 0x3d, +0xea, 0x6c, 0xfc, 0xc6, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0x92, 0xb8, 0xb8, +0xcd, 0x00, 0x4b, 0x23, 0xb8, 0x18, 0x94, 0xbb, +0xf9, 0xaf, 0xb4, 0x83, 0xea, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0xfb, +0xac, 0x39, 0xb2, 0xd4, 0x94, 0xa2, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0xab, +0xd0, 0x5d, 0x3a, 0xb3, 0x75, 0x08, 0x8a, 0x0f, +0xae, 0x1f, 0x59, 0xe6, 0x08, 0xeb, 0x3d, 0x41, +0x25, 0xea, 0xc9, 0x56, 0x9b, 0x40, 0x44, 0x1e, +0x41, 0x6f, 0xf3, 0x51, 0xac, 0x33, 0x24, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0x68, 0xb9, 0xc2, 0x80, 0xe6, 0x27, 0xf6, +0xd9, 0xf9, 0xcb, 0xfc, 0x02, 0xd8, 0xf7, 0xf3, +0x1d, 0xc6, 0xe3, 0x04, 0x16, 0x59, 0xa2, 0x26, +0x2b, 0x32, 0x8f, 0x48, 0xe6, 0x17, 0x3f, 0xf1, +0x87, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0x28, 0x7c, 0xee, 0xac, 0x13, +0x3b, 0x73, 0x95, 0x09, 0x68, 0xff, 0x86, 0xe7, +0x2c, 0x85, 0x17, 0x82, 0xc9, 0xbd, 0xeb, 0xde, +0x03, 0xe8, 0xc4, 0xe4, 0xbd, 0x3e, 0x1f, 0x7b, +0x0f, 0x5a, 0xbe, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0xf0, 0x54, 0xfa, +0x9e, 0x33, 0xfe, 0x6c, 0xdb, 0x3e, 0xe0, 0xca, +0x06, 0x22, 0x41, 0xa1, 0x21, 0x27, 0x33, 0xd8, +0x1b, 0xec, 0x52, 0x89, 0xe6, 0x36, 0x65, 0x9f, +0xeb, 0x99, 0x50, 0x71, 0xc1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0xb7, +0x8e, 0x54, 0x8b, 0xc1, 0x69, 0x91, 0x3b, 0xe8, +0xf6, 0xd3, 0xa9, 0x8c, 0x47, 0xee, 0xdf, 0x1a, +0x72, 0xdf, 0x5e, 0xc0, 0xdc, 0xa1, 0x88, 0xa3, +0xbc, 0x46, 0xa2, 0x6a, 0xe4, 0x82, 0x8e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0xf1, 0xd6, 0xdd, 0x61, 0x83, 0x4a, 0x28, +0xb9, 0x16, 0x5e, 0x71, 0x7b, 0xde, 0x69, 0x67, +0xbc, 0xd7, 0x6a, 0xff, 0xfa, 0x41, 0x54, 0x8e, +0x9e, 0xb8, 0x14, 0xb4, 0x8a, 0x11, 0x20, 0xa1, +0x1b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0x1b, 0x6d, 0x81, 0x6a, 0x0a, +0x79, 0x85, 0xb8, 0x66, 0xff, 0xd3, 0x93, 0xae, +0x10, 0x54, 0x2b, 0x41, 0x8d, 0x79, 0xa0, 0x1f, +0xae, 0x9b, 0x25, 0x3d, 0xa9, 0x13, 0xea, 0xff, +0x0f, 0xb7, 0xbd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x49, 0x3c, 0xdb, +0xa1, 0x7a, 0xc5, 0x0d, 0xfd, 0x33, 0x5e, 0x9d, +0x67, 0x88, 0x14, 0xff, 0x88, 0x6e, 0x3f, 0xaa, +0xc6, 0xd8, 0xc9, 0x1d, 0x78, 0xa7, 0x50, 0x21, +0xcd, 0x72, 0x96, 0x3f, 0x43, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0x2b, +0x30, 0xc5, 0x32, 0x1f, 0x71, 0xe6, 0x75, 0xb6, +0x5b, 0x50, 0x6e, 0x73, 0x42, 0x60, 0x25, 0xf9, +0x44, 0x98, 0x7a, 0x51, 0x06, 0x8a, 0xc2, 0xec, +0x91, 0x04, 0xe7, 0x10, 0x21, 0x5a, 0xea, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0xc4, 0xd5, 0x1a, 0xdf, 0xe1, 0xf9, 0x6e, +0x46, 0x88, 0x0d, 0x9c, 0xa6, 0xfc, 0x23, 0x0f, +0xde, 0x53, 0x0c, 0x66, 0x33, 0x33, 0xd0, 0xa6, +0xde, 0x62, 0x55, 0x49, 0xd9, 0x56, 0x02, 0x09, +0xa6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0xbe, 0xb2, 0xbb, 0x3b, 0x28, +0xc8, 0x82, 0x92, 0x89, 0x86, 0x43, 0x41, 0xee, +0xbb, 0x3c, 0x38, 0x94, 0x6e, 0xf8, 0x19, 0xd1, +0x21, 0x5e, 0x88, 0x91, 0xfd, 0xbf, 0x92, 0x24, +0x1b, 0x19, 0x63, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x35, 0xd3, 0x26, +0x7b, 0xfd, 0x8d, 0xd2, 0x56, 0xcc, 0xa4, 0xa0, +0xae, 0x21, 0x52, 0x15, 0x7b, 0x14, 0x26, 0xe4, +0xe6, 0xe6, 0xe6, 0xae, 0x8e, 0x17, 0x89, 0x37, +0xbc, 0x2d, 0x81, 0xd7, 0x9c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x85, +0x5d, 0x33, 0xcc, 0xca, 0xa4, 0xe3, 0xbc, 0xd4, +0x44, 0xe6, 0x3b, 0x0d, 0x24, 0x1a, 0x42, 0x54, +0x34, 0x75, 0x6f, 0x0f, 0x61, 0x24, 0xc9, 0xa0, +0x8f, 0x42, 0x2a, 0xaa, 0xd1, 0xbf, 0x6f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0x01, 0xa9, 0x62, 0x22, 0x90, 0xdb, 0xe1, +0xf5, 0x94, 0xca, 0xfa, 0x3e, 0xb1, 0x4a, 0xdb, +0x06, 0xbb, 0xbe, 0xab, 0x04, 0x53, 0x9a, 0x0b, +0x80, 0xdb, 0xba, 0xb8, 0xe3, 0x09, 0xd6, 0xbb, +0xe3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0x2a, 0x9d, 0x6e, 0x58, 0xeb, +0xa3, 0xee, 0x24, 0x42, 0x02, 0x76, 0x74, 0xb9, +0x3a, 0x06, 0x0f, 0x12, 0x89, 0x57, 0x69, 0x9f, +0xaf, 0x76, 0xba, 0x52, 0x65, 0x75, 0x94, 0x7e, +0x60, 0xf4, 0xec, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0x4e, 0x92, 0xac, +0xe6, 0x5c, 0xcc, 0xfb, 0xf8, 0x40, 0x08, 0x67, +0xab, 0x80, 0xc2, 0x0d, 0x5d, 0x61, 0xfa, 0x99, +0x57, 0x23, 0x0e, 0x96, 0xb4, 0x1d, 0xa6, 0x1a, +0x84, 0xc0, 0x38, 0xb7, 0x75, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x50, +0x5a, 0xf5, 0xf4, 0x48, 0xcf, 0xc6, 0x4d, 0x88, +0x30, 0xdb, 0x2e, 0x64, 0x75, 0xa4, 0x9a, 0xad, +0xf9, 0xdc, 0x74, 0xcb, 0xa0, 0xb6, 0x0a, 0xb6, +0xd4, 0x52, 0x3a, 0x52, 0x42, 0xee, 0xb7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0x4b, 0x84, 0x78, 0xf1, 0x27, 0xfd, 0x46, +0x5a, 0x22, 0x20, 0x1e, 0xe1, 0x67, 0x1e, 0x79, +0x14, 0x7e, 0xcb, 0xc4, 0xb8, 0x02, 0x02, 0x78, +0xe4, 0xca, 0xa4, 0x3d, 0x42, 0xe6, 0x65, 0xce, +0x08, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0x00, 0xd1, 0x49, 0xc2, 0x0b, +0xe2, 0x41, 0x35, 0xb6, 0xed, 0x44, 0x55, 0x52, +0xb4, 0x14, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x8d, 0xb7, 0xeb, 0x99, +0x8c, 0x76, 0x84, 0x33, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0xe1, 0xf2, 0x0c, +0x61, 0x8f, 0xa3, 0xde, 0x6f, 0x1f, 0x05, 0x73, +0xe0, 0xd0, 0x76, 0x14, 0x45, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xec, 0x9d, +0x06, 0xe8, 0xd6, 0x84, 0xba, 0xa1, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x6d, +0x09, 0xb5, 0x62, 0xf0, 0xbe, 0x3d, 0x4c, 0xcf, +0xdc, 0x0e, 0xe0, 0x10, 0xf3, 0x11, 0x66, 0xb0, +0x9e, 0x38, 0xcb, 0xe5, 0x9f, 0x61, 0xac, 0x7a, +0x96, 0x64, 0xfc, 0x1f, 0xfe, 0xd7, 0x35, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0x5f, 0xef, 0x9d, 0xdf, 0x73, 0xed, 0x22, +0xd8, 0x1b, 0x25, 0x0f, 0xd0, 0xa5, 0xc4, 0x4b, +0x40, 0x21, 0xda, 0x95, 0xd3, 0x41, 0xc8, 0x34, +0x07, 0x00, 0x72, 0x29, 0x4e, 0x75, 0xab, 0x8f, +0xd6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0x9c, 0x14, 0xae, 0xbe, 0x77, +0xe5, 0x30, 0xb5, 0x5c, 0xd1, 0x75, 0x35, 0x39, +0x1a, 0x8a, 0xd9, 0x33, 0x08, 0x42, 0xff, 0x1a, +0x21, 0x30, 0xda, 0x26, 0xb5, 0xd4, 0xfb, 0x41, +0x67, 0x55, 0x70, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0x6d, 0x6e, 0xd3, +0x04, 0x8f, 0x80, 0xff, 0x35, 0x83, 0xad, 0xa1, +0x10, 0xfb, 0xde, 0x3f, 0xb3, 0xe4, 0x5e, 0xe0, +0xb4, 0x30, 0x6e, 0x7d, 0x93, 0x78, 0x94, 0x9a, +0xfa, 0x11, 0x8a, 0xe4, 0x58, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0xf7, +0x9c, 0xe3, 0x19, 0x02, 0xd4, 0xe3, 0x28, 0x91, +0x2c, 0x2c, 0x9d, 0x29, 0x14, 0xea, 0xd3, 0xa3, +0xbf, 0xf8, 0xcc, 0x8e, 0xd6, 0x1c, 0x0d, 0x45, +0xa7, 0x86, 0x37, 0xfa, 0x2a, 0xd8, 0x25, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0x21, 0xb8, 0xe5, 0xa2, 0x0b, 0xb7, 0xcc, +0x64, 0x7b, 0xb5, 0x9b, 0x1a, 0x63, 0x8c, 0xc7, +0xd3, 0x17, 0x77, 0x5d, 0x32, 0xdc, 0x01, 0x8a, +0x0c, 0x97, 0x0f, 0xec, 0xb2, 0xb5, 0xe0, 0xfa, +0x27, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0xae, 0x3e, 0x54, 0x9c, 0xa5, +0x80, 0x59, 0xb7, 0xe7, 0x80, 0xf8, 0xbe, 0xc7, +0x02, 0xf6, 0xd9, 0xc1, 0x14, 0x1d, 0x88, 0x2e, +0xfe, 0xfb, 0x45, 0xc3, 0xe2, 0xf2, 0x69, 0x67, +0x19, 0xe8, 0x62, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0xaf, 0xd8, 0x2c, +0xcd, 0xf1, 0x42, 0x96, 0x48, 0x79, 0x08, 0x78, +0x81, 0x66, 0x62, 0x79, 0xcc, 0xdd, 0xed, 0x51, +0x10, 0xdd, 0x77, 0xa3, 0xa1, 0xb9, 0x61, 0x56, +0xa5, 0xcf, 0x8c, 0x95, 0xb7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0x51, +0x77, 0x3e, 0x9b, 0x16, 0xb6, 0xf4, 0x28, 0x9d, +0xec, 0x92, 0x3a, 0x4e, 0xf0, 0x44, 0x6e, 0x82, +0x9e, 0xb4, 0x4c, 0xdb, 0x9c, 0xf1, 0x0a, 0xee, +0xa4, 0x8f, 0xfc, 0xca, 0x56, 0x51, 0xd9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0xe4, 0xb6, 0xfe, 0x59, 0xf3, 0xe4, 0x71, +0xe8, 0x63, 0x44, 0x98, 0xac, 0x33, 0x01, 0x35, +0x54, 0xaa, 0x6f, 0xce, 0xde, 0xaa, 0x91, 0x81, +0x93, 0x62, 0xba, 0xc8, 0x6c, 0x5c, 0x1d, 0x78, +0x62, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0x6a, 0xc8, 0xb6, 0xec, 0x49, +0x02, 0x5d, 0x42, 0xe8, 0xaa, 0x0a, 0x67, 0x16, +0x3a, 0xd0, 0x52, 0xa1, 0x61, 0x0f, 0x6b, 0x82, +0xe8, 0x40, 0xad, 0x8f, 0xb1, 0x5d, 0x3b, 0x05, +0xcf, 0xff, 0x40, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0xd3, 0x65, 0x68, +0x14, 0x14, 0x4d, 0x8a, 0x1c, 0x62, 0x68, 0x6e, +0x11, 0xe6, 0xc4, 0x95, 0xde, 0x09, 0x08, 0xa4, +0x9d, 0x56, 0xe4, 0x58, 0x41, 0xc1, 0x85, 0xeb, +0x8e, 0xf6, 0xf4, 0xdb, 0x1b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0xfa, +0x69, 0xaa, 0x39, 0xb8, 0x8b, 0xe0, 0xa5, 0xcf, +0x01, 0x63, 0x96, 0x3a, 0x77, 0xbe, 0x0f, 0x7c, +0x3b, 0x49, 0xa6, 0xb2, 0x2d, 0xea, 0x03, 0x20, +0x6f, 0x56, 0x08, 0x20, 0x2e, 0x2b, 0xdb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0x0b, 0x5a, 0x0a, 0x62, 0x0b, 0xb3, 0x5a, +0xfe, 0xd0, 0xb7, 0x9a, 0x5b, 0xa3, 0xf8, 0x29, +0x6e, 0x18, 0x3a, 0xe4, 0xb6, 0xde, 0x31, 0x76, +0x76, 0xd8, 0xa4, 0x9c, 0x3c, 0x19, 0x7e, 0x5d, +0x17, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0xf7, 0xf8, 0xa4, 0x66, 0x26, +0xee, 0x10, 0x61, 0x94, 0xfb, 0x99, 0xdf, 0x51, +0x5f, 0xa5, 0x92, 0xe7, 0x71, 0xe7, 0xbf, 0x4c, +0x5d, 0x78, 0xf8, 0x0a, 0x08, 0x07, 0x8c, 0x55, +0x73, 0xbc, 0x3a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0xd1, 0xe4, 0xbe, +0x6f, 0x01, 0x07, 0x38, 0x24, 0x37, 0x93, 0x19, +0xaf, 0xc5, 0xbb, 0x86, 0xb0, 0xaf, 0xc2, 0x5c, +0xfb, 0xa1, 0x58, 0x3c, 0x6b, 0x2b, 0x3d, 0x52, +0xf1, 0x38, 0xb4, 0xe5, 0xbf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x2b, +0x60, 0xbe, 0xf6, 0x01, 0xe1, 0x66, 0xe7, 0x92, +0x76, 0xbc, 0xda, 0xa5, 0xf7, 0xc0, 0x0d, 0x6c, +0x52, 0x01, 0xe4, 0xc4, 0x3f, 0xaa, 0x79, 0xcb, +0x43, 0xf2, 0xb3, 0x4f, 0xbf, 0x83, 0xb4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0x10, 0x53, 0xa8, 0x0f, 0x23, 0xeb, 0x59, +0xcb, 0xb4, 0x63, 0x8a, 0x9f, 0x5d, 0xf8, 0x77, +0x87, 0xf6, 0xf5, 0xbe, 0xde, 0xfe, 0xe5, 0x7e, +0xe1, 0x07, 0xe7, 0xab, 0x60, 0xf4, 0x56, 0x7d, +0xc5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0x72, 0xc8, 0x30, 0xaf, 0xdf, +0xe2, 0xde, 0xa0, 0x08, 0x77, 0xba, 0xac, 0xd2, +0x3a, 0xe3, 0xd7, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xe6, 0xb6, 0xc3, 0x46, +0x88, 0x95, 0x5d, 0x1b, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0x6b, 0x6c, 0x12, +0xe3, 0xe5, 0xc1, 0xdf, 0x29, 0xde, 0x32, 0x0b, +0xc6, 0x61, 0xd7, 0xdc, 0xae, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9d, 0xd9, +0x48, 0xc6, 0x4b, 0x6d, 0xc9, 0xe6, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0xbb, +0xdc, 0x80, 0xde, 0x44, 0x00, 0x2e, 0x92, 0x90, +0x10, 0x60, 0x27, 0xc8, 0x53, 0x4a, 0xcf, 0xdb, +0x2b, 0xa5, 0x1c, 0xa8, 0xa8, 0x45, 0xed, 0xdf, +0x14, 0x6a, 0xe7, 0x8e, 0x54, 0x4b, 0x21, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0x74, 0x1f, 0xad, 0x9f, 0xd5, 0x9f, 0x2f, +0x98, 0xbd, 0x16, 0x17, 0xfc, 0xac, 0xef, 0xd2, +0x92, 0x4f, 0x0e, 0x36, 0xa6, 0x1c, 0x55, 0x10, +0x8d, 0x20, 0xcf, 0x44, 0x94, 0xcd, 0x53, 0xa9, +0x02, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x0a, 0x18, 0x80, 0x2d, 0x14, +0xc4, 0xde, 0xb7, 0x52, 0x4d, 0xff, 0x09, 0xf7, +0xe8, 0xab, 0x11, 0x48, 0x4d, 0x91, 0xd8, 0x9d, +0x9a, 0xc0, 0xe3, 0x53, 0x46, 0xe5, 0xbe, 0x37, +0x03, 0x3d, 0xfc, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0xf9, 0x2a, 0x49, +0x77, 0x67, 0x9d, 0xbb, 0x37, 0x61, 0x19, 0x8b, +0x49, 0x84, 0x8a, 0xb5, 0x51, 0x4f, 0x19, 0x07, +0x7d, 0x35, 0x1d, 0x3b, 0x43, 0xde, 0x1e, 0x6a, +0x16, 0xc4, 0xf7, 0x0a, 0x68, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0x5c, +0xfb, 0x9c, 0x3a, 0x89, 0xd7, 0xf5, 0x50, 0x60, +0x89, 0x46, 0x6f, 0x07, 0x9c, 0x7b, 0xb8, 0xb5, +0xd1, 0x3c, 0x74, 0x2c, 0xfc, 0x88, 0x42, 0xc6, +0xd7, 0x6a, 0xd6, 0xa1, 0x60, 0x47, 0x16, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0xe0, 0xd0, 0x8d, 0x89, 0xe6, 0x0f, 0x4a, +0x3e, 0x75, 0x42, 0x49, 0x35, 0x09, 0xc0, 0xe3, +0xe7, 0x1d, 0xc0, 0x54, 0xbe, 0x22, 0x90, 0x0e, +0xbf, 0x61, 0x07, 0xe5, 0xec, 0x52, 0x09, 0xad, +0x1d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0x92, 0x75, 0x94, 0x82, 0xa6, +0xdd, 0x35, 0xbc, 0x8e, 0x01, 0xbc, 0x83, 0x3c, +0xdf, 0xf2, 0xe5, 0xf3, 0x5a, 0x01, 0x6b, 0x66, +0xe6, 0x89, 0x98, 0x44, 0x3d, 0x89, 0x05, 0xbb, +0xc1, 0x98, 0x8c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0x10, 0xd4, 0x65, +0x5c, 0xcc, 0x81, 0x96, 0xca, 0xfa, 0x70, 0xe3, +0x5a, 0x4b, 0x74, 0x0f, 0xbc, 0x7f, 0xa2, 0xd0, +0x35, 0xc4, 0xe8, 0x3e, 0x8f, 0xc1, 0x02, 0xac, +0x5c, 0x2a, 0x30, 0xaf, 0x3d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0x1e, +0xa7, 0x82, 0x16, 0xae, 0x35, 0xcb, 0x4c, 0xda, +0x6a, 0xc4, 0x5f, 0xe5, 0x00, 0x73, 0x5e, 0x7f, +0xf2, 0xc2, 0x6d, 0x38, 0xf8, 0x30, 0x6b, 0xe0, +0x39, 0x8b, 0x89, 0xe2, 0x52, 0x7f, 0x42, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0x01, 0x24, 0xa1, 0x5f, 0x61, 0x07, 0xe2, +0x08, 0x68, 0x04, 0x04, 0x24, 0x2f, 0x41, 0x52, +0xad, 0xd0, 0x78, 0x8e, 0xf8, 0x3a, 0xb8, 0xd0, +0xb6, 0x54, 0x7c, 0x25, 0xe0, 0xde, 0x7b, 0x4e, +0xc0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0xf7, 0x0f, 0xcf, 0x46, 0xa7, +0x21, 0x8d, 0x11, 0xb5, 0xd4, 0x3f, 0x3b, 0x0c, +0xe3, 0x1a, 0x3d, 0xbe, 0x1e, 0x57, 0xe2, 0x1b, +0x68, 0xd7, 0x30, 0xac, 0xb8, 0xcc, 0x46, 0xf6, +0x19, 0x80, 0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0x84, 0x25, 0x97, +0x41, 0x91, 0xae, 0x94, 0x64, 0xd5, 0x3f, 0x5b, +0x10, 0x11, 0x09, 0x40, 0x91, 0xff, 0xbb, 0xfa, +0xc7, 0xbc, 0xfc, 0xe9, 0xbf, 0x29, 0x25, 0x1f, +0x62, 0xd1, 0x4f, 0x8a, 0xbf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0xa6, +0x39, 0x40, 0x6d, 0x59, 0xf0, 0xbc, 0xbb, 0xfd, +0xad, 0x20, 0x21, 0xfb, 0xd2, 0x89, 0x8d, 0x9b, +0x10, 0x5b, 0xd7, 0x94, 0xf6, 0x28, 0xb7, 0x0b, +0xe5, 0xd0, 0x5c, 0xb4, 0x4c, 0x72, 0x13, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0x7f, 0x93, 0xd4, 0x21, 0x36, 0xab, 0x51, +0x3e, 0x69, 0x58, 0xa9, 0x22, 0x64, 0x9a, 0xca, +0x6f, 0xd8, 0xf6, 0x31, 0x1f, 0x92, 0xcd, 0x1e, +0x15, 0xb4, 0x62, 0xc1, 0x34, 0x3c, 0x7a, 0xaf, +0x95, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0xed, 0x96, 0xb7, 0x4f, 0x75, +0x8b, 0xf6, 0x73, 0x5c, 0xcf, 0x30, 0xb3, 0x6f, +0x7f, 0xa6, 0x39, 0x45, 0xa7, 0x80, 0xcc, 0x25, +0x42, 0xf0, 0x2c, 0x7c, 0x48, 0x4d, 0xed, 0xd9, +0x0e, 0x68, 0x7c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0x4a, 0x46, 0xa3, +0x78, 0x7a, 0x51, 0x18, 0x5e, 0xb2, 0xfa, 0xa9, +0x13, 0xc6, 0x00, 0x6c, 0x84, 0x2d, 0x29, 0xce, +0x22, 0x15, 0xd8, 0x5e, 0x1d, 0x31, 0xe2, 0xf6, +0xcc, 0x28, 0x02, 0x76, 0xf7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0x7b, +0x77, 0x18, 0x18, 0xf3, 0x74, 0x38, 0xd0, 0x09, +0xfa, 0x9f, 0x64, 0xf7, 0xcf, 0xbd, 0xca, 0x5c, +0x75, 0xb6, 0xbc, 0x0c, 0x34, 0x7f, 0x59, 0x4d, +0x17, 0x8d, 0x22, 0x37, 0xd4, 0x03, 0x39, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0xf9, 0x98, 0x53, 0xf2, 0xb5, 0xa4, 0x57, +0x26, 0xec, 0xe8, 0x1e, 0xfc, 0xd1, 0x6e, 0x0c, +0xfa, 0x0d, 0x4e, 0xea, 0xdc, 0xaf, 0x4a, 0x17, +0x12, 0xff, 0x08, 0x7f, 0xd9, 0xfd, 0xb8, 0xaf, +0x50, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0xe7, 0x6d, 0x16, 0x43, 0xa4, +0x84, 0x34, 0xd3, 0xd8, 0x7b, 0xa4, 0xf5, 0xef, +0xa8, 0x41, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xd5, 0x8a, 0xfd, 0x6d, +0x2f, 0x4c, 0x0f, 0x0b, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x81, 0xb5, 0x57, +0x4f, 0x18, 0xa2, 0x5e, 0x42, 0x36, 0xb6, 0xa2, +0x98, 0x57, 0xbc, 0x03, 0xa3, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb6, 0x94, +0x3d, 0xf7, 0x0e, 0x26, 0x69, 0x15, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0xf6, +0xcb, 0x3f, 0xbb, 0x50, 0x3c, 0x9d, 0x04, 0x2e, +0xbe, 0x1a, 0xa5, 0x39, 0x12, 0x52, 0xea, 0xdc, +0xc4, 0x08, 0xe6, 0xc7, 0x86, 0x1e, 0xcb, 0xe1, +0x05, 0xfe, 0x74, 0x9d, 0x03, 0x24, 0x19, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0x13, 0x84, 0x0e, 0x9d, 0xad, 0x7d, 0x97, +0x16, 0x75, 0x7f, 0x8e, 0xaf, 0x70, 0xbe, 0xdf, +0x15, 0xc6, 0x67, 0xb4, 0x9a, 0x80, 0x3f, 0xaa, +0x58, 0x74, 0x5b, 0x0b, 0xed, 0xae, 0x9b, 0x52, +0x41, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0xfa, 0x86, 0xa1, 0xcf, 0x1d, +0xab, 0xb5, 0x7f, 0x40, 0x99, 0x73, 0xeb, 0x8f, +0x3f, 0xc5, 0x10, 0x13, 0x3f, 0x18, 0x10, 0x72, +0xf1, 0xfc, 0x65, 0x07, 0x8b, 0xeb, 0xaa, 0x56, +0xeb, 0x0a, 0x9e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0xe8, 0x79, 0xb1, +0x72, 0x30, 0xd3, 0xfe, 0xc5, 0xb3, 0xc5, 0x6d, +0xe4, 0xa0, 0x60, 0xa5, 0x28, 0xb5, 0x09, 0x70, +0xb3, 0xa8, 0x09, 0x04, 0xe0, 0x25, 0xb3, 0x52, +0xea, 0x1c, 0xff, 0x1a, 0x2b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0x0f, +0xb3, 0xa4, 0xf0, 0x8c, 0x68, 0x5a, 0x53, 0x45, +0x33, 0x45, 0xc1, 0xa5, 0x40, 0x24, 0xde, 0x52, +0x06, 0xc5, 0xb2, 0xce, 0x03, 0x8c, 0xe4, 0xfb, +0xd1, 0x90, 0x05, 0x93, 0x8a, 0xd5, 0x4b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0xf7, 0x71, 0xd5, 0x29, 0x44, 0x8f, 0xac, +0xb3, 0x36, 0x90, 0x33, 0x9c, 0xee, 0xcd, 0x17, +0x99, 0x3c, 0x8d, 0xa0, 0x4b, 0x68, 0x3b, 0x5a, +0x23, 0xc7, 0xae, 0x49, 0x09, 0xf6, 0xbf, 0xa3, +0xe5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0x0b, 0x13, 0x76, 0x69, 0x5c, +0xd7, 0x93, 0xe0, 0xaf, 0x8b, 0xd4, 0x9b, 0x18, +0xa4, 0xc7, 0x13, 0xe4, 0x9b, 0x2a, 0xed, 0xaf, +0x98, 0x14, 0x6b, 0x80, 0x51, 0x81, 0x6d, 0x6c, +0xb6, 0xa3, 0xa3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0xfe, 0x13, 0x3b, +0x12, 0xd0, 0x0a, 0xf9, 0xe4, 0x20, 0x4f, 0x6b, +0x2b, 0xca, 0x49, 0x1d, 0x3a, 0xd2, 0x71, 0xa4, +0x5a, 0x13, 0xfd, 0x26, 0x02, 0x19, 0xef, 0x9e, +0x22, 0x72, 0x4b, 0xa4, 0x8b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0x47, +0x62, 0xdc, 0x71, 0x10, 0xf1, 0x9e, 0x9c, 0x73, +0xbc, 0x1a, 0x32, 0xb4, 0x1e, 0x12, 0xac, 0x2a, +0x15, 0xee, 0xc1, 0xe4, 0x53, 0xa1, 0xd7, 0x44, +0x95, 0x8e, 0x13, 0x96, 0x2d, 0xcd, 0x73, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0xef, 0xec, 0xb4, 0xb7, 0x1a, 0xeb, 0xa6, +0x85, 0xa0, 0x87, 0x98, 0x89, 0x17, 0x6c, 0xb7, +0xfb, 0xda, 0xcb, 0xba, 0x06, 0x4d, 0xb0, 0x7e, +0xfb, 0x53, 0xab, 0xd0, 0xe5, 0x55, 0x30, 0x64, +0x71, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0xf1, 0x7f, 0xd2, 0x86, 0xe1, +0xa9, 0x01, 0x17, 0xa9, 0x5b, 0x49, 0xa4, 0x3c, +0x28, 0x69, 0x6e, 0xf2, 0x7a, 0x6c, 0xfb, 0x52, +0x8f, 0xf6, 0xdb, 0x4b, 0xcd, 0x4d, 0x55, 0x0e, +0xa5, 0xe5, 0x26, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x43, 0x61, 0xd3, +0x1e, 0xd0, 0x73, 0x14, 0x26, 0xc9, 0x89, 0xcd, +0xef, 0xa6, 0x30, 0x22, 0xb2, 0x0a, 0xd9, 0x58, +0x78, 0xe1, 0x52, 0x74, 0xa5, 0x42, 0xd5, 0x09, +0xdd, 0xb0, 0x33, 0x73, 0x36, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x1a, +0x47, 0xa0, 0x9c, 0xa7, 0x3f, 0xf8, 0x3e, 0xb5, +0x07, 0x94, 0x97, 0xb0, 0x4e, 0x36, 0x6b, 0x5b, +0x0d, 0x98, 0x59, 0xbc, 0x6b, 0x88, 0x0f, 0x47, +0x97, 0x4a, 0x2b, 0x20, 0x95, 0x6a, 0x13, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0xe5, 0x6a, 0xa9, 0x71, 0xbd, 0xf7, 0x12, +0x03, 0xd9, 0x78, 0x40, 0x97, 0x41, 0x45, 0x95, +0x3d, 0xb6, 0xa8, 0x94, 0x0d, 0xf0, 0xad, 0xd4, +0xbd, 0x3c, 0x98, 0xac, 0x14, 0x0a, 0x0a, 0xd9, +0xd8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0xe5, 0xd8, 0x72, 0xb3, 0xba, +0x64, 0x16, 0x0d, 0x50, 0xf8, 0xb2, 0xf8, 0x22, +0x28, 0x51, 0xec, 0x37, 0x64, 0x29, 0x8e, 0x6f, +0x5b, 0xcc, 0x3c, 0xb5, 0x9d, 0xc6, 0x75, 0x74, +0x1a, 0x51, 0x29, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0x30, 0x4e, 0x0d, +0xba, 0x33, 0x86, 0x59, 0x20, 0xda, 0x7b, 0x81, +0x18, 0x06, 0x54, 0x5c, 0x3c, 0x2a, 0x5a, 0xf2, +0xc0, 0x34, 0xcc, 0x07, 0xc5, 0x9b, 0x90, 0x26, +0xd0, 0x60, 0x67, 0xcb, 0x0f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0xcd, +0x0f, 0xcb, 0x82, 0x15, 0x1f, 0x37, 0x25, 0x67, +0x7e, 0x0a, 0xd6, 0xbc, 0xd4, 0x48, 0x02, 0xdc, +0x9b, 0xec, 0x21, 0x1c, 0xd8, 0xdb, 0xf8, 0x96, +0xef, 0xf4, 0x49, 0xd0, 0x24, 0xe1, 0x11, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x70, 0xf5, 0xcc, 0x54, 0xef, 0xde, 0x7c, +0x59, 0xdf, 0xd8, 0x72, 0x3f, 0x8e, 0x0d, 0xc6, +0xfb, 0x30, 0xa1, 0x64, 0xda, 0xce, 0x51, 0xd6, +0xef, 0x6b, 0xa5, 0x07, 0x93, 0xd0, 0x92, 0x45, +0x9f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0xe2, 0x77, 0xef, 0x2b, 0xa2, +0xed, 0x91, 0x18, 0x72, 0x63, 0x6a, 0x76, 0xa1, +0xe9, 0x9f, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xb2, 0x1d, 0x4c, 0x3f, +0x81, 0x9e, 0x95, 0x43, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0x1f, 0x0c, 0x29, +0xc9, 0xf2, 0xb6, 0xba, 0x8f, 0xab, 0x11, 0xfe, +0x80, 0xdd, 0xe5, 0x50, 0xca, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0x27, +0x79, 0x33, 0x32, 0xe4, 0xcb, 0xf6, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0x60, +0xe1, 0x6d, 0x73, 0x9c, 0xac, 0xe7, 0x34, 0xae, +0x74, 0x6d, 0x16, 0x22, 0xf8, 0x96, 0x86, 0x1f, +0x89, 0x4c, 0xef, 0xc3, 0x00, 0x63, 0x99, 0xad, +0xd8, 0x13, 0xe1, 0xd6, 0xb8, 0xe2, 0x9c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0xe3, 0x4f, 0x7c, 0x09, 0x06, 0xf1, 0xe1, +0xdc, 0x20, 0xfb, 0x56, 0xcf, 0xd7, 0xdf, 0xb1, +0x66, 0x8d, 0x04, 0x93, 0xe0, 0x7b, 0x41, 0xcb, +0xf0, 0x7d, 0x04, 0xd0, 0xa5, 0x20, 0x09, 0xc5, +0x93, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0xc3, 0xb8, 0xe8, 0x69, 0xf9, +0x83, 0x1a, 0x80, 0x2b, 0xe1, 0x55, 0xda, 0xf2, +0x4f, 0x67, 0x10, 0x2b, 0x1b, 0xf3, 0x59, 0x90, +0x25, 0x1e, 0x5b, 0x64, 0xdd, 0x72, 0xc3, 0x53, +0x6b, 0x54, 0x7a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0x79, 0xe9, 0x0f, +0xba, 0x58, 0xd0, 0x9b, 0x99, 0x46, 0xc8, 0xa1, +0x9d, 0x8a, 0x4c, 0x49, 0x76, 0xbc, 0x0a, 0x00, +0x68, 0x9d, 0xa9, 0x14, 0x29, 0x92, 0x8e, 0xc8, +0x8a, 0x8c, 0x61, 0x29, 0xcc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0x54, +0x2d, 0xd0, 0xf4, 0xe2, 0x6c, 0xa6, 0x78, 0xfa, +0xc6, 0x4b, 0x98, 0xef, 0x19, 0x5a, 0xce, 0x86, +0xa5, 0x61, 0xf7, 0x61, 0xfa, 0xf6, 0x5c, 0xcb, +0x0f, 0xa4, 0x3b, 0xf8, 0x97, 0xb8, 0xb4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x87, 0xa5, 0xf1, 0xd0, 0x51, 0x1b, 0x1a, +0xfc, 0xea, 0x65, 0xd9, 0x4c, 0xf0, 0xa9, 0x34, +0x01, 0xc9, 0x65, 0x02, 0xe7, 0xad, 0x0b, 0x4e, +0x0d, 0xd3, 0xe8, 0xc4, 0xd2, 0x56, 0xc7, 0x35, +0x0c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0x88, 0xf6, 0xa8, 0x69, 0xaf, +0x7d, 0x4c, 0xed, 0x9a, 0xc2, 0x79, 0x2c, 0x1d, +0xae, 0xc9, 0x30, 0x12, 0x24, 0x48, 0x43, 0x2d, +0xcb, 0xf1, 0xb6, 0x8c, 0xe0, 0x21, 0x12, 0x18, +0x90, 0x19, 0x4d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0x64, 0x1b, 0x92, +0x61, 0x3b, 0xe9, 0x0f, 0x66, 0x26, 0xe2, 0x4d, +0x58, 0x65, 0xe4, 0x8c, 0x2e, 0x07, 0x68, 0x1d, +0x8e, 0xb8, 0x2e, 0x2c, 0xd4, 0xa8, 0xcb, 0xc7, +0x47, 0x6a, 0x56, 0x20, 0xff, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0xa2, +0x3d, 0x46, 0xb7, 0x9a, 0x93, 0xe2, 0xe7, 0xef, +0xda, 0xf3, 0x81, 0x33, 0x30, 0xa2, 0xea, 0xf8, +0x1c, 0x76, 0xe2, 0x6f, 0xea, 0x8e, 0x83, 0x66, +0x38, 0x72, 0xa3, 0xfc, 0xc8, 0x70, 0xb9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0xd4, 0x33, 0x08, 0xfd, 0xab, 0xc8, 0xbd, +0x00, 0x7f, 0x47, 0x47, 0xf1, 0x7d, 0x76, 0x83, +0x1c, 0x01, 0xb0, 0x3b, 0x74, 0xb1, 0xb6, 0x8b, +0xa3, 0x23, 0x49, 0xc9, 0x09, 0x22, 0x3d, 0x4c, +0x8c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0x6d, 0x01, 0x5c, 0x6b, 0xe8, +0x4a, 0xf5, 0xb2, 0xb1, 0xb1, 0xcd, 0x5f, 0x71, +0x9a, 0x90, 0x4c, 0x45, 0xec, 0x18, 0x0d, 0x8e, +0xbe, 0xde, 0xec, 0x98, 0x0c, 0x2c, 0x69, 0x32, +0x66, 0x05, 0x58, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0x22, 0x04, 0x55, +0xa6, 0xd3, 0xa4, 0x0a, 0xae, 0xa5, 0x77, 0x90, +0xcd, 0xc0, 0x66, 0xc6, 0xb1, 0xf7, 0x62, 0x95, +0x87, 0x79, 0xa9, 0xdf, 0x15, 0x7c, 0x0a, 0x7d, +0x83, 0xa7, 0xcf, 0xa5, 0xe0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0xb2, +0x91, 0x9c, 0x10, 0xe4, 0x28, 0x08, 0x6a, 0x85, +0x92, 0x7e, 0xa3, 0x50, 0xec, 0xa1, 0x7b, 0xd7, +0x19, 0x6a, 0xe4, 0x8b, 0xa4, 0x05, 0xc8, 0x8f, +0xc3, 0xa9, 0x76, 0xfc, 0x3f, 0x08, 0xd4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0x40, 0x81, 0x27, 0x39, 0xf7, 0x91, 0x91, +0xd8, 0x92, 0xdc, 0xca, 0x26, 0x13, 0xbb, 0xf9, +0x2f, 0x02, 0x24, 0x1f, 0x1f, 0x25, 0x57, 0x93, +0xc5, 0x6f, 0xc7, 0x72, 0x8f, 0x02, 0x87, 0x7f, +0xec, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0x27, 0xba, 0xf2, 0xf9, 0xac, +0x03, 0x78, 0xc0, 0xbc, 0xf6, 0x21, 0xaa, 0xb5, +0x5c, 0x51, 0x6a, 0xf6, 0xda, 0x9a, 0x17, 0xcb, +0x94, 0x6f, 0x69, 0xb9, 0xf7, 0x53, 0x26, 0xfd, +0xfe, 0xb4, 0x73, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x81, 0xed, 0x1b, +0xf2, 0xcb, 0x75, 0xdf, 0x69, 0xe3, 0x31, 0xf2, +0xa7, 0x94, 0xc4, 0x36, 0x60, 0x05, 0xb1, 0x46, +0xaa, 0x1c, 0xdf, 0xbe, 0x1a, 0xa1, 0xca, 0x23, +0x67, 0x44, 0x45, 0x7a, 0x8d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0xcd, +0xea, 0x52, 0x76, 0x04, 0x43, 0xd1, 0x00, 0x12, +0x1a, 0x30, 0x46, 0x06, 0x42, 0x94, 0xfb, 0x6a, +0x47, 0x95, 0xe6, 0x7a, 0x89, 0x55, 0xa9, 0x67, +0x42, 0x10, 0x97, 0x05, 0xc3, 0x5c, 0x83, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0xb2, 0xe9, 0xee, 0x2d, 0xf6, 0x8b, 0x05, +0x29, 0x32, 0xfd, 0xe0, 0x84, 0xdb, 0xf9, 0x13, +0x71, 0x18, 0xf0, 0xd1, 0x57, 0x22, 0xe1, 0x62, +0xc9, 0x51, 0xe7, 0x4d, 0x49, 0xae, 0x39, 0x65, +0xb3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0x89, 0xec, 0x45, 0x6d, 0x9e, +0xed, 0x38, 0xea, 0x22, 0xb9, 0x94, 0xb4, 0xb0, +0x0f, 0x52, 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x23, 0xfd, 0xe4, 0xfd, +0xdd, 0x9c, 0x1c, 0x62, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0xa3, 0x1b, 0x20, +0x59, 0x9d, 0x44, 0xda, 0xf3, 0x4a, 0x96, 0x82, +0x5f, 0x12, 0x00, 0xc1, 0x32, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9d, 0xa1, +0x1b, 0xb7, 0x55, 0xd5, 0x23, 0xea, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0x2d, +0x5d, 0x0c, 0xb3, 0xdb, 0x91, 0x92, 0x9c, 0x8b, +0x52, 0xfc, 0xe0, 0x07, 0x95, 0x88, 0x95, 0x5e, +0x5d, 0x75, 0x17, 0xb6, 0x17, 0x08, 0x2d, 0x57, +0x54, 0x42, 0x66, 0x68, 0x36, 0xd7, 0x41, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0x79, 0x8d, 0x7e, 0xd7, 0xdb, 0xb8, 0x72, +0x60, 0xa1, 0xb4, 0xbb, 0xc3, 0xe2, 0x2b, 0xce, +0xbd, 0x88, 0x49, 0xd1, 0xe1, 0xfd, 0xab, 0x79, +0x50, 0x5a, 0xd5, 0x5c, 0x43, 0x39, 0x07, 0x7e, +0x51, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x48, 0x8f, 0x87, 0x2f, 0xe2, +0x0e, 0x25, 0xa5, 0x38, 0x66, 0x6a, 0x7d, 0x4d, +0x99, 0x37, 0xaa, 0x6f, 0x87, 0x34, 0xc8, 0x09, +0x0f, 0x26, 0xda, 0x27, 0xac, 0x70, 0x6d, 0x66, +0xc4, 0x87, 0x43, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0xc2, 0x60, 0xae, +0x52, 0x4c, 0x12, 0x71, 0x88, 0xf4, 0x27, 0xee, +0x8b, 0x7a, 0xaf, 0x22, 0xaf, 0x5a, 0x0f, 0x50, +0x4c, 0x7e, 0x49, 0x9a, 0xd6, 0x92, 0x2a, 0x88, +0x00, 0x48, 0x04, 0x07, 0x7c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0x1a, +0x60, 0xa4, 0xad, 0xe5, 0xe5, 0x47, 0xc8, 0x80, +0x32, 0xed, 0xda, 0x63, 0x90, 0xe9, 0xec, 0xd0, +0x54, 0xf7, 0x62, 0x84, 0x35, 0xdc, 0x31, 0xd1, +0xe7, 0x6d, 0x2b, 0x8d, 0xe1, 0x3d, 0x90, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0x08, 0x13, 0x8f, 0x13, 0xef, 0x4a, 0xee, +0x6b, 0xa2, 0x6b, 0x15, 0xc2, 0x48, 0xb6, 0x08, +0x9f, 0xfc, 0xfd, 0x5e, 0x3f, 0x56, 0x0c, 0xc2, +0xa5, 0x07, 0xba, 0x65, 0xa5, 0xc8, 0xdf, 0x06, +0x1b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0x83, 0xa9, 0x65, 0x87, 0x32, +0x59, 0x3a, 0x25, 0x1e, 0x63, 0x12, 0xa4, 0x1f, +0x7b, 0x42, 0x1d, 0xca, 0xad, 0x62, 0xac, 0x75, +0xca, 0xaf, 0x68, 0xc5, 0x41, 0x35, 0xcf, 0x2a, +0xe3, 0x20, 0xe7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0xc4, 0x27, 0xc7, +0x13, 0xdb, 0xac, 0x29, 0xa2, 0x9a, 0xbf, 0xe9, +0xd8, 0x06, 0x86, 0x33, 0x7b, 0xe4, 0xfb, 0x80, +0x5a, 0x07, 0x29, 0x26, 0xc4, 0x2f, 0xa3, 0xf7, +0x9c, 0x91, 0xd1, 0x5c, 0x47, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0x79, +0xa3, 0xfa, 0x62, 0x6a, 0x02, 0x00, 0x8a, 0x4e, +0x94, 0x1e, 0xca, 0x88, 0xeb, 0xc8, 0xb8, 0xc8, +0x4c, 0x8b, 0xba, 0x8a, 0x2b, 0xa8, 0xa2, 0xa9, +0xaa, 0xac, 0x9f, 0x61, 0x43, 0x9f, 0xd4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0xa3, 0xcf, 0xe4, 0x5b, 0x05, 0x65, 0xaa, +0xf6, 0x7e, 0x32, 0xf9, 0xe3, 0x5a, 0xb7, 0xc5, +0xf8, 0xe8, 0x9b, 0x8b, 0xc2, 0xdf, 0x4d, 0xbc, +0x4c, 0xa6, 0xbe, 0x3a, 0xaf, 0x24, 0x21, 0xad, +0x51, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0x83, 0x33, 0x9f, 0xa2, 0xc2, +0x56, 0x3f, 0x96, 0xc7, 0x0c, 0x56, 0x07, 0xd9, +0x12, 0x82, 0x76, 0xde, 0x1f, 0xfb, 0x05, 0x76, +0xed, 0xb2, 0x7f, 0x9c, 0xbd, 0xca, 0x2e, 0xea, +0xac, 0x8d, 0xc2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0x7e, 0x13, 0x66, +0x62, 0xbe, 0xc7, 0x4e, 0x5d, 0x6a, 0x74, 0x87, +0x89, 0x41, 0x79, 0x86, 0x44, 0x50, 0x34, 0x3d, +0x41, 0x96, 0xe7, 0x57, 0x6e, 0x9e, 0xc8, 0x03, +0x4c, 0x88, 0x09, 0xb0, 0x27, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0xf2, +0x8f, 0xd0, 0x3b, 0x8a, 0x91, 0x50, 0x0f, 0x42, +0x3f, 0x93, 0xeb, 0x42, 0x2c, 0x5d, 0x3f, 0x3c, +0x69, 0x9f, 0x4f, 0x47, 0x0d, 0x86, 0xbe, 0x4e, +0x91, 0xd5, 0xbe, 0xd6, 0x2b, 0x29, 0xa9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0xa8, 0xbf, 0xa6, 0x95, 0xf3, 0x7c, 0xff, +0x15, 0xec, 0xd1, 0xc8, 0x71, 0x04, 0xbc, 0x18, +0xaa, 0x29, 0xd8, 0x5a, 0x98, 0xdb, 0x38, 0x93, +0xe2, 0xb3, 0x0f, 0x1f, 0x52, 0x0e, 0xed, 0xde, +0x9b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0x01, 0xb3, 0x54, 0xc5, 0xb9, +0xd9, 0x8b, 0x60, 0x07, 0xad, 0xf9, 0xe8, 0xc9, +0x1b, 0xf0, 0x86, 0x87, 0x04, 0xf8, 0x5d, 0x86, +0x20, 0x4c, 0xbd, 0xc3, 0xa5, 0xad, 0x4e, 0xbd, +0xfa, 0xf2, 0x61, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0x1b, 0x5d, 0x45, +0xf7, 0xf5, 0xa3, 0xc3, 0xe6, 0xc3, 0x9d, 0xba, +0x87, 0x3d, 0x2a, 0x3e, 0x5a, 0x3d, 0xd8, 0x7b, +0x60, 0xe5, 0xc5, 0x3b, 0xb0, 0x86, 0x2a, 0x1d, +0xf1, 0x3f, 0x3b, 0x1f, 0x67, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0xd1, +0xcd, 0x1a, 0x2e, 0xf9, 0xd6, 0x55, 0x15, 0x59, +0xb2, 0xff, 0xac, 0x09, 0x4b, 0x07, 0xd8, 0x7f, +0x6c, 0x8a, 0x5f, 0xa5, 0xe0, 0x42, 0x8c, 0xae, +0xcd, 0x13, 0x9d, 0xbe, 0x3f, 0x33, 0x08, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0x50, 0x81, 0xc8, 0x79, 0x1f, 0xeb, 0xd2, +0x7a, 0x26, 0x28, 0x0f, 0x05, 0xd8, 0x17, 0x52, +0xdb, 0xdd, 0x71, 0x8e, 0xb1, 0x04, 0xa2, 0x38, +0x5f, 0xf3, 0x6e, 0x0f, 0x06, 0x6d, 0x6e, 0x59, +0x62, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0x26, 0x0a, 0x65, 0x60, 0x96, +0x83, 0x51, 0xe7, 0x4b, 0x1d, 0x43, 0xe1, 0xd4, +0x2c, 0x9a, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xd8, 0x9f, 0xff, 0xaa, +0xb8, 0x3f, 0xe2, 0x6c, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0xff, 0xd0, 0xcc, +0xdc, 0xc0, 0xd5, 0x71, 0x3e, 0xfa, 0xc6, 0xf2, +0x0d, 0x28, 0xa8, 0x27, 0x6d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x5a, +0x78, 0x90, 0xfd, 0x2b, 0x12, 0x6c, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0x39, +0xa2, 0xb4, 0x83, 0x23, 0xef, 0x0f, 0x90, 0x00, +0xb5, 0xb7, 0x8c, 0xe7, 0x96, 0xa6, 0xd5, 0xd1, +0x63, 0xf5, 0x4e, 0x94, 0x3f, 0xbc, 0xfd, 0xe4, +0xfa, 0x13, 0xba, 0xf3, 0x29, 0x9b, 0xa4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0x9a, 0xe1, 0x3e, 0x22, 0xfd, 0xf1, 0xc3, +0x99, 0x2f, 0x94, 0x16, 0x10, 0x57, 0xbc, 0x17, +0xf5, 0xe0, 0x1a, 0x64, 0x77, 0x27, 0x14, 0xcb, +0x69, 0x0e, 0x61, 0x2e, 0x7c, 0x88, 0x85, 0x28, +0x0e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0x95, 0xe4, 0x25, 0x71, 0x9c, +0x35, 0xea, 0xfa, 0xf8, 0x09, 0x80, 0xc1, 0x87, +0x8b, 0x74, 0x20, 0xc5, 0xed, 0x2b, 0x9d, 0xa6, +0x59, 0x03, 0x44, 0x0f, 0x2d, 0xc9, 0x78, 0x57, +0xc9, 0xba, 0x27, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0xb2, 0x3c, 0xd4, +0x1a, 0xef, 0x61, 0xa5, 0x40, 0x59, 0x17, 0x66, +0x99, 0xd7, 0x84, 0xf6, 0xd6, 0xab, 0x6b, 0x95, +0xe0, 0x8a, 0x07, 0x49, 0xf6, 0x13, 0xb9, 0x25, +0xd1, 0x57, 0x01, 0x89, 0x7f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0x34, +0xbd, 0x87, 0xc8, 0x55, 0x01, 0xdd, 0x96, 0xd9, +0xb0, 0x29, 0xf5, 0xb5, 0x5b, 0xb9, 0x85, 0xcb, +0x5c, 0x89, 0x72, 0x50, 0xad, 0x73, 0x5d, 0x6e, +0x2c, 0xbf, 0xb4, 0x30, 0xf7, 0x93, 0xa7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0xc9, 0x6f, 0x9f, 0x54, 0x21, 0xa6, 0x8e, +0xde, 0x9d, 0x0f, 0x3e, 0xdd, 0x91, 0x41, 0x91, +0x10, 0xeb, 0xfb, 0x1d, 0x6b, 0x57, 0xc0, 0x92, +0xc0, 0x0c, 0x85, 0xe8, 0xa1, 0xd5, 0xe6, 0xb5, +0x2f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0x88, 0xf8, 0xee, 0x33, 0x24, +0x1c, 0x5b, 0x16, 0xc2, 0x44, 0xaa, 0xb9, 0xff, +0x06, 0xfb, 0xd2, 0x22, 0x15, 0x49, 0xa5, 0x10, +0x1f, 0x29, 0x71, 0x1a, 0x01, 0x36, 0xae, 0xe9, +0x97, 0x86, 0x91, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0x5e, 0xa7, 0xee, +0x53, 0xa6, 0x59, 0x4b, 0xd0, 0xe9, 0xfe, 0x80, +0xf6, 0x9c, 0x2f, 0xd1, 0x73, 0x89, 0x99, 0x4c, +0xda, 0xeb, 0xd8, 0x57, 0x44, 0xdd, 0x88, 0x69, +0xba, 0x66, 0x85, 0x7e, 0x46, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x32, +0xf3, 0xea, 0x48, 0x05, 0xec, 0x11, 0x5a, 0xb9, +0x6b, 0x18, 0xff, 0xd0, 0xe3, 0x1f, 0x48, 0x00, +0xb7, 0x61, 0xcf, 0xad, 0x91, 0xa7, 0x7d, 0x73, +0xfc, 0x52, 0x85, 0x9d, 0x26, 0x34, 0x9d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0x3a, 0x48, 0x76, 0xae, 0x11, 0xdb, 0xa7, +0xa0, 0x85, 0x77, 0x07, 0x62, 0xe5, 0x8a, 0x08, +0xcf, 0x94, 0x7e, 0x5d, 0x93, 0x22, 0x42, 0x95, +0x05, 0x60, 0x40, 0x25, 0x4a, 0xb6, 0xba, 0x95, +0x06, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0x08, 0xc6, 0x86, 0x04, 0x45, +0xf6, 0x4a, 0xb8, 0x53, 0x27, 0xb6, 0x08, 0xbc, +0x17, 0x2a, 0xce, 0xbb, 0x02, 0x91, 0x63, 0x00, +0x41, 0x9a, 0xf1, 0x29, 0xb9, 0x13, 0x76, 0xa8, +0x6d, 0x11, 0x3d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0x5a, 0x75, 0x14, +0xb6, 0x78, 0xa2, 0x95, 0xb3, 0x74, 0xa2, 0xad, +0x3b, 0xc1, 0xe8, 0x8a, 0x88, 0x42, 0x43, 0x6a, +0xd5, 0x50, 0xef, 0xc4, 0xc1, 0x42, 0x14, 0x93, +0x5f, 0xa3, 0xc9, 0x7f, 0x53, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0xa2, +0x07, 0x55, 0xf3, 0x1e, 0x42, 0x9a, 0x75, 0x2e, +0x67, 0x67, 0x69, 0x7b, 0xe0, 0xb6, 0xfc, 0x87, +0xf5, 0x67, 0xbe, 0x99, 0xd1, 0xdc, 0xde, 0x64, +0xcd, 0x2d, 0x11, 0x1d, 0x24, 0x8a, 0xb1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0x69, 0x6f, 0xf6, 0x7d, 0x55, 0x4e, 0x99, +0x70, 0x62, 0x9e, 0x87, 0xc1, 0x2f, 0x1d, 0x73, +0x48, 0x9b, 0x22, 0x2b, 0x62, 0x8e, 0x11, 0x0b, +0xaf, 0x71, 0x3e, 0xcf, 0x41, 0x6c, 0x18, 0x3b, +0x76, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0x7c, 0xb0, 0x02, 0x09, 0x4c, +0x08, 0xce, 0xdc, 0x0e, 0x65, 0xb1, 0x0d, 0x13, +0x7f, 0x85, 0x11, 0xce, 0xec, 0x0e, 0xb0, 0x5e, +0x7e, 0x62, 0x2a, 0x22, 0xe5, 0xd6, 0x68, 0x4a, +0xf7, 0xab, 0xcb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x41, 0xdd, 0xbb, +0xfa, 0xd3, 0x98, 0x72, 0xdd, 0xf2, 0xc6, 0x18, +0x9f, 0x3f, 0x5f, 0xd8, 0x6a, 0x3f, 0x14, 0x88, +0xc9, 0x10, 0x58, 0x1b, 0xf6, 0xe6, 0x5b, 0xf5, +0x21, 0xc1, 0x48, 0x12, 0x24, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0x95, +0x2c, 0x05, 0x0c, 0x4e, 0x72, 0xb5, 0x30, 0x69, +0x75, 0x43, 0x09, 0xbc, 0x7a, 0xbc, 0x07, 0x79, +0x8d, 0x22, 0x5b, 0x99, 0x6b, 0x50, 0xa9, 0x9f, +0x1b, 0xe6, 0xac, 0xf6, 0xbd, 0x95, 0xe7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0x8a, 0x4e, 0x9e, 0x76, 0x25, 0x2c, 0xce, +0x67, 0xb5, 0x78, 0x2a, 0x53, 0x9b, 0x31, 0x6c, +0x27, 0x2b, 0xca, 0x56, 0xd8, 0xd7, 0x39, 0xbc, +0x5f, 0x79, 0x8a, 0x45, 0x6e, 0xaa, 0x8d, 0x45, +0xd3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0xe1, 0x6b, 0xb3, 0xcc, 0x1a, +0xf8, 0x25, 0x55, 0x74, 0x6e, 0x63, 0x25, 0xa6, +0x83, 0xfd, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x42, 0x70, 0x8b, 0xd8, +0x3d, 0x64, 0xbe, 0x73, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0xe4, 0xf7, 0x18, +0xfc, 0xbb, 0xbf, 0xfd, 0xe3, 0xb7, 0xb9, 0x1c, +0x81, 0xcf, 0xd7, 0x19, 0xaf, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0x38, +0xa9, 0x43, 0xac, 0x9e, 0x34, 0xbb, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0xf9, +0xe5, 0x6f, 0x9c, 0x52, 0xf1, 0xf4, 0x02, 0x5f, +0x42, 0x34, 0x7d, 0x33, 0x82, 0x75, 0x74, 0x30, +0x0d, 0x7f, 0x32, 0x31, 0x8f, 0xef, 0x7e, 0xe8, +0x48, 0xa5, 0x45, 0x16, 0xd6, 0xc3, 0xd0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0x73, 0x91, 0xc7, 0x79, 0x8e, 0x14, 0xe3, +0x19, 0x26, 0x39, 0xc4, 0x88, 0x05, 0xe6, 0x25, +0x32, 0x83, 0x37, 0x23, 0x7a, 0xa2, 0xe9, 0x24, +0xf7, 0xec, 0x93, 0x02, 0x0d, 0x91, 0xdc, 0xe6, +0x9b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0xdc, 0x42, 0xa4, 0xc1, 0x16, +0x4d, 0xa8, 0x1a, 0xa0, 0x30, 0xd9, 0x7f, 0xc2, +0xe9, 0xbe, 0x74, 0xb7, 0x70, 0x2f, 0x67, 0x7e, +0x93, 0xf1, 0x75, 0x10, 0x2d, 0x2f, 0x26, 0x21, +0xeb, 0x53, 0xbe, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0x6a, 0xdf, 0xe5, +0x50, 0x23, 0x17, 0xfd, 0xf0, 0xa3, 0x23, 0x4b, +0xc2, 0xb9, 0x27, 0x8c, 0x36, 0xff, 0x6e, 0xd4, +0x21, 0xfe, 0x88, 0x34, 0xa1, 0xe4, 0x46, 0xfe, +0xac, 0xce, 0x36, 0x3c, 0xaf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x0d, +0x74, 0xe4, 0xc9, 0x29, 0x73, 0x70, 0x24, 0xce, +0x26, 0xfb, 0xaf, 0x0a, 0x81, 0x94, 0x63, 0x33, +0xa0, 0x83, 0xb3, 0xbc, 0x93, 0xdb, 0xbf, 0x88, +0x48, 0x7f, 0x60, 0xcf, 0x02, 0xfb, 0xa5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0x0a, 0xae, 0x0d, 0x0d, 0x3f, 0xa0, 0xf4, +0x71, 0xf1, 0x6b, 0x36, 0xe8, 0xf1, 0x22, 0xf7, +0xe8, 0xe8, 0xc9, 0x60, 0xb3, 0xf4, 0x4e, 0x1d, +0xed, 0x71, 0x2f, 0x61, 0xb3, 0xaf, 0xfe, 0x7e, +0x29, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0xd2, 0x76, 0x34, 0x36, 0x41, +0xdb, 0x68, 0x0a, 0x90, 0x99, 0x96, 0x9d, 0x19, +0x5e, 0xb8, 0x78, 0x99, 0xf3, 0x2b, 0xb4, 0x87, +0x74, 0xc3, 0xdc, 0x54, 0x50, 0xda, 0x18, 0xf0, +0xe2, 0xbc, 0xde, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0xd3, 0xee, 0x8a, +0xb8, 0xee, 0x8e, 0x71, 0xad, 0xfb, 0xd5, 0x04, +0xf7, 0x0b, 0xc3, 0x84, 0xb3, 0x5f, 0xb5, 0x85, +0x0d, 0x3e, 0x7b, 0xe3, 0x9a, 0xa4, 0xa8, 0x04, +0x36, 0xc4, 0x50, 0xe4, 0x38, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0xef, +0x61, 0x8c, 0xc8, 0x3f, 0x88, 0xf5, 0x2d, 0x00, +0xa2, 0x33, 0x2b, 0x4f, 0x0d, 0x6a, 0x6f, 0xbc, +0xec, 0xc8, 0x21, 0xa5, 0x04, 0x97, 0xad, 0xe0, +0x6b, 0x0e, 0x17, 0x7a, 0xdd, 0xbb, 0x27, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0x3e, 0xb7, 0x43, 0x44, 0xee, 0xe4, 0x34, +0xd2, 0x4c, 0x68, 0x88, 0x24, 0x32, 0x56, 0x69, +0xb0, 0xc5, 0x16, 0x60, 0x86, 0x8f, 0x8e, 0x06, +0x4d, 0x0c, 0x57, 0x1d, 0x48, 0x69, 0xe3, 0x76, +0x8e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0x73, 0x09, 0xa3, 0x2d, 0x9d, +0x29, 0xb8, 0xf4, 0xc2, 0x51, 0x14, 0xc2, 0xf2, +0x0f, 0xe3, 0x96, 0x47, 0x35, 0x7c, 0xd6, 0x42, +0x45, 0x64, 0xc0, 0x26, 0x56, 0x20, 0x2b, 0x76, +0x22, 0x9d, 0xf6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0xb4, 0x9d, 0xc5, +0x16, 0xa8, 0xae, 0xc5, 0xf3, 0x87, 0x80, 0xf0, +0xa0, 0x92, 0x55, 0xe8, 0x08, 0x0e, 0xd6, 0xac, +0x08, 0xa6, 0x6f, 0x20, 0xb5, 0xa9, 0x73, 0x4a, +0xb0, 0xa9, 0x79, 0x01, 0xf1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0xc7, +0x01, 0x3d, 0x95, 0x25, 0x25, 0xde, 0x18, 0xf2, +0x04, 0x46, 0xe7, 0x3d, 0xcd, 0x66, 0xfe, 0x59, +0xc3, 0x49, 0x87, 0x14, 0x83, 0x7e, 0x10, 0x8a, +0x27, 0xfb, 0xd4, 0x39, 0xdb, 0xf5, 0xc3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0x0b, 0xa3, 0x1c, 0xff, 0x2e, 0xe3, 0x43, +0x57, 0x29, 0x63, 0xf1, 0x7d, 0x4b, 0x14, 0x49, +0xc4, 0x9b, 0xbe, 0x34, 0x9a, 0x93, 0x32, 0xda, +0x0f, 0x3c, 0xfb, 0xa5, 0x2c, 0x6b, 0xd4, 0x41, +0x4f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0x90, 0xd2, 0x0a, 0x7b, 0x9f, +0xe7, 0x52, 0x1a, 0xf0, 0xee, 0x52, 0xe9, 0x4a, +0x8a, 0xde, 0xdd, 0x15, 0x5e, 0xcf, 0x8b, 0x60, +0x47, 0x24, 0x9c, 0x63, 0x4b, 0x31, 0x72, 0xa1, +0x95, 0x95, 0x4a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0xf8, 0x04, 0x41, +0x38, 0x64, 0x80, 0x5f, 0x0c, 0x53, 0x44, 0x7f, +0xd4, 0xa7, 0x79, 0x4a, 0xd0, 0x1f, 0xde, 0x08, +0x3a, 0x02, 0xa1, 0xc1, 0x92, 0x85, 0x95, 0x8d, +0x62, 0x60, 0x9e, 0x84, 0x84, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0x5c, +0x0d, 0x56, 0x42, 0x2b, 0x4f, 0xad, 0xb3, 0x0b, +0x54, 0x47, 0xfe, 0xd4, 0xfe, 0xd5, 0x92, 0x60, +0x26, 0x84, 0x8c, 0x60, 0xbc, 0x9d, 0x81, 0x73, +0xcf, 0x71, 0xbd, 0x8d, 0x71, 0x01, 0x05, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0x9c, 0x4f, 0x40, 0x33, 0x28, 0xd7, 0x3d, +0xf0, 0x34, 0x66, 0xaf, 0xf2, 0x94, 0x74, 0xa9, +0x8e, 0x9a, 0x76, 0xcd, 0xf2, 0x91, 0x72, 0xb6, +0x4e, 0xc0, 0x50, 0x72, 0xb0, 0xe5, 0x7c, 0x2f, +0xd3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0x7e, 0xe9, 0xbc, 0x66, 0x69, +0xa6, 0x8e, 0xae, 0x73, 0xa5, 0xea, 0xf8, 0x14, +0x9d, 0xfb, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x92, 0x5f, 0x1a, 0xa7, +0x5a, 0x57, 0xf1, 0x7a, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x21, 0x00, 0x99, +0x65, 0x45, 0x31, 0xf9, 0x7f, 0x0b, 0xe2, 0xb6, +0x00, 0x77, 0xf7, 0xe2, 0xb7, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa6, 0x1b, +0x85, 0xeb, 0x15, 0x6c, 0x75, 0xa9, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x84, +0xb0, 0x03, 0x3e, 0xd3, 0x5e, 0x0b, 0xd9, 0x1c, +0x8a, 0x0e, 0x93, 0x11, 0xe7, 0xf4, 0xcd, 0xdb, +0x93, 0xcc, 0xa9, 0x32, 0x50, 0xfa, 0xb9, 0x45, +0x98, 0x0e, 0x8d, 0x9e, 0x29, 0xbd, 0x77, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x4b, 0xec, 0x72, 0x17, 0x3d, 0x3c, 0xe5, +0xfe, 0x4e, 0xd7, 0xc4, 0x33, 0x71, 0x16, 0x7e, +0x5b, 0xb5, 0xad, 0x5c, 0xdf, 0x62, 0x6f, 0x5c, +0x5d, 0xbc, 0x96, 0xc1, 0x28, 0x72, 0x61, 0x4c, +0x7b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0xe7, 0x0d, 0x72, 0xe0, 0xce, +0xf2, 0x38, 0x1d, 0xa0, 0xd2, 0xb1, 0xa3, 0xe6, +0x1e, 0x90, 0x59, 0xd5, 0xb6, 0x04, 0xb1, 0x62, +0x28, 0x78, 0xac, 0xf8, 0xfe, 0xf5, 0xf1, 0xfc, +0xe7, 0x02, 0x75, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0x14, 0xda, 0x52, +0xa2, 0x57, 0xbe, 0x25, 0x91, 0xf0, 0x36, 0x06, +0x9a, 0x18, 0xc6, 0x94, 0x4e, 0x45, 0x44, 0xeb, +0xf6, 0x13, 0x6c, 0x3e, 0xa6, 0x0e, 0xcf, 0x9d, +0xfa, 0x6e, 0x01, 0xf0, 0xce, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0x4c, +0x53, 0xaa, 0xd2, 0xc3, 0xca, 0x29, 0x25, 0xf7, +0x4a, 0x54, 0xf3, 0x80, 0xab, 0x85, 0x3e, 0x52, +0x1d, 0xa0, 0x25, 0x97, 0x46, 0x48, 0x09, 0xf6, +0x39, 0x50, 0xeb, 0xf5, 0x09, 0xa1, 0x70, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0x23, 0x2b, 0xca, 0x23, 0x5c, 0xd2, 0x8a, +0xb8, 0xb3, 0x47, 0x57, 0x8f, 0x4d, 0x6e, 0x99, +0xa9, 0xaa, 0x68, 0x03, 0xf8, 0x25, 0xeb, 0xc0, +0x30, 0x42, 0x83, 0x18, 0xbe, 0x3e, 0x0f, 0x6f, +0xa3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0x74, 0x32, 0xe2, 0xc2, 0x27, +0x81, 0x07, 0x4f, 0xc1, 0xf3, 0xf4, 0xbe, 0x47, +0x45, 0x11, 0xe7, 0x6b, 0x26, 0x41, 0x45, 0xea, +0x35, 0xa4, 0xbc, 0x03, 0xcf, 0x37, 0xd2, 0xcd, +0xe8, 0x05, 0xea, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0x17, 0x13, 0xb3, +0x4d, 0x07, 0xb8, 0x01, 0x1b, 0x56, 0xf5, 0x5c, +0x3c, 0x58, 0x41, 0xbe, 0x7d, 0xa9, 0x36, 0xa3, +0x5d, 0x15, 0xff, 0xfe, 0x8f, 0x87, 0x40, 0xdd, +0x8e, 0xc2, 0x3d, 0xef, 0xa0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0xbc, +0xf7, 0x24, 0xe2, 0x1c, 0x26, 0xee, 0x37, 0xb0, +0x9b, 0x03, 0xbe, 0xc7, 0x14, 0x54, 0x90, 0xb3, +0xea, 0xf8, 0x76, 0x74, 0xac, 0xca, 0x5b, 0x68, +0xea, 0x8d, 0x07, 0x52, 0x94, 0x83, 0x76, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0x22, 0x66, 0x0f, 0xa2, 0x9d, 0xd9, 0xc1, +0xec, 0x32, 0x0a, 0xc1, 0x63, 0xbd, 0x95, 0x8e, +0xe1, 0xcd, 0xd0, 0xe1, 0x29, 0x70, 0xd0, 0xac, +0x97, 0x98, 0xb1, 0xda, 0x9d, 0x27, 0xb5, 0xd0, +0x26, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0x62, 0x59, 0x88, 0xda, 0x50, +0x84, 0x1f, 0xa6, 0x22, 0xa7, 0xa2, 0xfb, 0xc6, +0x51, 0x14, 0xb3, 0xa9, 0x3d, 0x3d, 0xc1, 0x5e, +0xbb, 0xd0, 0xb7, 0x58, 0xf0, 0x60, 0x6d, 0x36, +0xe6, 0x5a, 0x9a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0x33, 0xd3, 0x04, +0xc9, 0xad, 0x46, 0x6d, 0x3b, 0xd0, 0xb8, 0xc2, +0x52, 0x87, 0xb5, 0x1c, 0x30, 0x10, 0xde, 0x38, +0x73, 0x9b, 0xb7, 0x9c, 0x9e, 0x5a, 0x0d, 0xbb, +0xa5, 0x2d, 0x03, 0x26, 0x31, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0xf7, +0x21, 0x3a, 0x67, 0xf8, 0x39, 0xbe, 0xd3, 0x74, +0x9d, 0x5d, 0x8f, 0xf7, 0x52, 0xac, 0x0e, 0x03, +0x41, 0xf7, 0xcc, 0x84, 0x6c, 0x5d, 0x0f, 0xed, +0xdf, 0x3e, 0xec, 0x09, 0x2c, 0x15, 0x85, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0xc7, 0xa8, 0x20, 0xaa, 0x35, 0xc0, 0xca, +0x83, 0x82, 0x61, 0x4f, 0x8b, 0x4e, 0xdc, 0x15, +0x59, 0xe0, 0xd6, 0x4f, 0xea, 0x04, 0xd4, 0x23, +0xa4, 0x6c, 0xf0, 0x38, 0x68, 0xc4, 0x83, 0xa1, +0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0x99, 0xd2, 0xe7, 0x3d, 0xb5, +0x00, 0x8c, 0xfd, 0x1d, 0x16, 0x2f, 0xa2, 0xca, +0x5e, 0xfd, 0x55, 0xfb, 0xf0, 0x05, 0x9b, 0xc7, +0xb0, 0x55, 0xae, 0x56, 0xeb, 0xfc, 0xfb, 0x91, +0xc6, 0x1f, 0x07, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0x83, 0x1f, 0x92, +0x5d, 0x47, 0x8f, 0xc0, 0x9f, 0xa3, 0x3c, 0xa6, +0x62, 0xfc, 0xca, 0x41, 0x27, 0x5e, 0x0c, 0x21, +0xfd, 0xad, 0x86, 0x8e, 0xc2, 0x1e, 0xcb, 0xf2, +0xb5, 0xe5, 0x5e, 0x52, 0x36, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0x13, +0x43, 0xf5, 0x80, 0x27, 0x49, 0x8a, 0xac, 0x3f, +0xdc, 0x5c, 0x90, 0x00, 0x0a, 0xe0, 0x27, 0x5f, +0x0c, 0x38, 0x16, 0x1a, 0x46, 0xfa, 0x4e, 0x66, +0x0f, 0x08, 0x31, 0xaf, 0xdd, 0xf1, 0xdc, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0x79, 0xfa, 0x0c, 0x86, 0x24, 0x08, 0x1c, +0x58, 0x48, 0xb7, 0x6d, 0x34, 0xad, 0xdf, 0xb2, +0x68, 0x68, 0x4b, 0x91, 0x40, 0x43, 0x9b, 0xac, +0x97, 0x41, 0x0b, 0xa5, 0x6e, 0x1c, 0xac, 0xec, +0xcd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0x34, 0x53, 0xcf, 0x4c, 0x8d, +0x43, 0x2e, 0xa1, 0xf5, 0xb1, 0xc1, 0x91, 0xd5, +0x7e, 0xbc, 0xfa, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x1e, 0xa2, 0x56, 0x1f, +0xac, 0xb4, 0x62, 0xc7, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0xa8, 0xdb, 0xbb, +0x01, 0x82, 0x43, 0xc3, 0xd5, 0x83, 0x0b, 0xe3, +0x08, 0xae, 0x22, 0xd3, 0x82, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xba, +0x14, 0xe7, 0xae, 0xfe, 0x07, 0x27, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0x55, +0x77, 0xcb, 0x7f, 0x62, 0x10, 0x54, 0x5e, 0x08, +0xa7, 0x82, 0x39, 0x07, 0xf5, 0x0f, 0xde, 0x0a, +0x76, 0xbb, 0xbd, 0xf8, 0xc8, 0x54, 0xa7, 0xa4, +0xe4, 0xb2, 0xbb, 0xa7, 0x1a, 0x0a, 0xdf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0x92, 0xc8, 0x3e, 0x80, 0x06, 0x94, 0x5d, +0x3b, 0x01, 0xea, 0x7a, 0xcd, 0x17, 0x57, 0xa1, +0x90, 0x10, 0x04, 0x63, 0xbb, 0xad, 0x77, 0xb7, +0x64, 0x65, 0x97, 0x94, 0x9d, 0x7e, 0x21, 0x17, +0x26, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0x9f, 0x8e, 0xb0, 0x90, 0xfc, +0xb9, 0x28, 0xed, 0x43, 0x00, 0xd6, 0xf8, 0x3e, +0x1e, 0xcd, 0xdd, 0x37, 0x27, 0xcf, 0xf9, 0x40, +0xab, 0x46, 0x04, 0x93, 0x02, 0x79, 0xbf, 0x43, +0xad, 0x50, 0x72, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0xbf, 0xbf, 0x70, +0xad, 0xce, 0x08, 0x9f, 0x85, 0x65, 0x8c, 0xc6, +0x6c, 0x88, 0xcc, 0xb0, 0x7e, 0x56, 0x13, 0x88, +0x14, 0x87, 0x3d, 0x2d, 0xae, 0x3c, 0x3e, 0x23, +0x94, 0x52, 0x30, 0x94, 0x93, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0x86, +0x97, 0xad, 0x08, 0xb7, 0x70, 0x70, 0xf6, 0x8f, +0x4b, 0xad, 0x9c, 0xbc, 0xcc, 0x98, 0x3b, 0x6e, +0x94, 0xc9, 0x25, 0x40, 0x0f, 0x21, 0x7e, 0xa9, +0xe1, 0x29, 0x69, 0x06, 0x9e, 0xb5, 0x35, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0xed, 0x51, 0xe9, 0x87, 0xac, 0xb8, 0x0f, +0x15, 0x50, 0x14, 0x9d, 0xb6, 0xab, 0xbf, 0x3f, +0x95, 0x61, 0x5c, 0x93, 0x70, 0xef, 0x4d, 0x33, +0x89, 0x5e, 0x0a, 0x66, 0x90, 0xc0, 0xa7, 0xf2, +0xb1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0x11, 0x2a, 0xc1, 0x5c, 0x0e, +0x6a, 0x12, 0xb2, 0xc5, 0xe8, 0x95, 0x1f, 0x41, +0x3f, 0x0b, 0x94, 0x1b, 0x7f, 0xd5, 0x88, 0x68, +0x01, 0x19, 0xc8, 0xc2, 0x4a, 0xef, 0xeb, 0x09, +0xf7, 0x39, 0x85, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0xcb, 0x96, 0xbc, +0xd4, 0xe3, 0x69, 0x8d, 0x74, 0xcb, 0x4e, 0xc1, +0x4a, 0x26, 0x60, 0xeb, 0x41, 0x72, 0x7d, 0x8a, +0x57, 0x88, 0x2e, 0x38, 0x6e, 0x56, 0x31, 0x27, +0xc1, 0x3d, 0xbc, 0x4e, 0xbc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0x2a, +0xf1, 0x3e, 0x6e, 0xdd, 0x38, 0x2d, 0x75, 0xf1, +0x38, 0x97, 0x08, 0x7a, 0x35, 0x09, 0x24, 0xcb, +0x7c, 0xf6, 0x4b, 0x11, 0x99, 0xbf, 0xe4, 0x51, +0xea, 0x1b, 0x03, 0x69, 0xf2, 0x96, 0x39, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0x22, 0xc3, 0x7d, 0x45, 0x63, 0x59, 0xf9, +0xe4, 0x06, 0x47, 0x52, 0x70, 0x74, 0x4f, 0x4d, +0x29, 0x2f, 0x1a, 0x76, 0x11, 0x88, 0xd6, 0xd5, +0x1d, 0x96, 0xf9, 0xfe, 0x6f, 0x98, 0xb5, 0xea, +0x7e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0xbe, 0x38, 0x63, 0x7d, 0x87, +0xa2, 0xa7, 0x24, 0x63, 0xcb, 0xfa, 0xd8, 0xbf, +0x9c, 0xb5, 0x51, 0x25, 0x72, 0x63, 0xa6, 0x21, +0xa8, 0x16, 0x65, 0xfc, 0xd2, 0x77, 0x6c, 0x65, +0x34, 0x47, 0x64, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0xf3, 0x93, 0x72, +0xaf, 0x4c, 0xa3, 0x4c, 0xb3, 0xbc, 0x7b, 0xd1, +0xc6, 0xd6, 0x41, 0xd9, 0x4a, 0xb4, 0x02, 0x73, +0x91, 0x80, 0xd9, 0x16, 0x8f, 0x67, 0x66, 0x37, +0xda, 0xea, 0x01, 0x39, 0xa9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0xf1, +0xb5, 0x51, 0x31, 0xcd, 0xcb, 0xab, 0x7d, 0xa4, +0x27, 0xe8, 0xc9, 0xa4, 0x5d, 0x4c, 0x51, 0xdb, +0x6f, 0xda, 0xb4, 0x46, 0xc8, 0xa2, 0xae, 0xa5, +0xa8, 0xc3, 0xbc, 0x02, 0xac, 0x0e, 0x37, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0x62, 0xd6, 0x8a, 0x9a, 0x29, 0x53, 0xd9, +0xf4, 0x45, 0x6c, 0x09, 0x9b, 0x06, 0xd1, 0xb3, +0xc9, 0xdd, 0x99, 0xcc, 0x28, 0x33, 0x68, 0x02, +0xcf, 0x8c, 0x01, 0x4e, 0x9e, 0x4f, 0x9f, 0x1d, +0x26, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0x0e, 0x50, 0xd3, 0xda, 0xe7, +0x8b, 0x62, 0xd7, 0x87, 0x6d, 0x1e, 0xa3, 0xa4, +0x6a, 0x3c, 0xd5, 0x41, 0x5e, 0x19, 0xb5, 0xb1, +0xa6, 0xd2, 0xfa, 0x48, 0xc2, 0xeb, 0x97, 0xda, +0x5a, 0x10, 0xfe, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x62, 0x0f, 0xd7, +0x88, 0xe0, 0x0a, 0xfc, 0x3c, 0x46, 0x32, 0x30, +0x93, 0x4f, 0xf7, 0xc3, 0x31, 0x71, 0x75, 0x6d, +0xcc, 0xee, 0xad, 0x1b, 0x43, 0x2e, 0x68, 0x1f, +0x92, 0xbe, 0x0b, 0x7b, 0x4a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0xc0, +0x92, 0xff, 0x24, 0x71, 0x09, 0xd0, 0x27, 0x8b, +0x44, 0x1f, 0xc3, 0x57, 0x33, 0x5b, 0xf3, 0x68, +0x62, 0x5e, 0xc0, 0x9f, 0x80, 0x05, 0xdd, 0xbd, +0x23, 0xe6, 0x7e, 0xec, 0x0e, 0x66, 0xcb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x89, 0xc5, 0x3b, 0x91, 0x29, 0xd5, 0x85, +0x6d, 0xc5, 0x74, 0x50, 0xe7, 0x10, 0xbc, 0x45, +0x04, 0x1d, 0xb5, 0x7a, 0xe6, 0xb5, 0x09, 0xaf, +0x5f, 0x90, 0x8f, 0x4a, 0x52, 0x48, 0x31, 0xa9, +0x6e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0x7a, 0xfb, 0xf6, 0x61, 0x2e, +0xaa, 0xf2, 0xdc, 0xae, 0x9c, 0x8a, 0x4a, 0xc0, +0xa8, 0x55, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xfd, 0x9c, 0x5a, 0x50, +0xfe, 0xf1, 0x07, 0x72, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0xea, 0x57, 0xb9, +0xb1, 0x6b, 0x1a, 0x93, 0xdf, 0x03, 0x73, 0xd3, +0xb4, 0xe0, 0xa0, 0x11, 0x54, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4b, 0x77, +0x6e, 0xd5, 0x07, 0x66, 0x97, 0x6c, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x95, +0xd4, 0x0b, 0x8a, 0xe2, 0xd2, 0xa1, 0xe7, 0x24, +0x9d, 0x3e, 0xb4, 0x35, 0x82, 0x88, 0x22, 0xe5, +0x3f, 0xee, 0xeb, 0x36, 0xef, 0x6c, 0x81, 0x1e, +0x75, 0x78, 0xeb, 0x1f, 0xb1, 0x9b, 0xf9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0x99, 0xf4, 0x21, 0x15, 0xa6, 0x09, 0xcb, +0x3e, 0x48, 0x67, 0xa6, 0x92, 0xc5, 0x6a, 0xec, +0x1e, 0xc2, 0x71, 0xb6, 0xee, 0xf8, 0x8e, 0xbe, +0x63, 0x09, 0x16, 0xea, 0x09, 0xb0, 0x23, 0x19, +0xc2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0xeb, 0x20, 0x8d, 0xe2, 0xce, +0x24, 0xd5, 0xe6, 0xb5, 0x5b, 0xdf, 0x02, 0xb5, +0x0a, 0x8d, 0xee, 0x7e, 0x04, 0xdb, 0xb6, 0x11, +0x99, 0x67, 0xc3, 0xcd, 0x99, 0xaa, 0xc7, 0x07, +0xef, 0xf8, 0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0x23, 0xb3, 0x73, +0x09, 0x8d, 0x93, 0xd7, 0x06, 0x19, 0x05, 0x78, +0xed, 0xfa, 0x98, 0x71, 0x7a, 0xfa, 0x2a, 0xa9, +0xb4, 0x4b, 0x9b, 0xb3, 0x0a, 0xc7, 0x24, 0x28, +0x7d, 0x62, 0xab, 0xcd, 0x54, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x0d, +0x1a, 0xb4, 0xbe, 0x8c, 0xaa, 0xb9, 0x5e, 0x9a, +0x3d, 0x1f, 0x58, 0x70, 0x71, 0xd0, 0x9c, 0xd4, +0x4c, 0xe8, 0xcf, 0x3e, 0x63, 0xb6, 0x86, 0x4e, +0xb4, 0x76, 0x96, 0xc2, 0x2c, 0x54, 0xae, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0xbf, 0xde, 0x73, 0x0b, 0x3c, 0xcb, 0x7c, +0x38, 0x79, 0x3b, 0xf2, 0xfe, 0x8d, 0xab, 0xba, +0x32, 0x5c, 0x47, 0x0c, 0x58, 0xbc, 0x47, 0xb6, +0x48, 0x6a, 0x02, 0x80, 0x27, 0xbd, 0x71, 0x61, +0x4f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0xbd, 0x0a, 0x56, 0xa7, 0x1f, +0x9f, 0x52, 0xdf, 0x6d, 0x85, 0x0c, 0x52, 0x7c, +0xe2, 0x6f, 0xae, 0xaf, 0x06, 0x59, 0xfe, 0x74, +0x1a, 0xe2, 0x58, 0x86, 0xff, 0xa7, 0xa3, 0xea, +0xee, 0x05, 0x4b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0x6f, 0x71, 0x9a, +0x9a, 0x75, 0xa2, 0x39, 0x9d, 0x9c, 0x73, 0x00, +0x6b, 0x3e, 0x50, 0x2a, 0x7e, 0xfe, 0x46, 0x90, +0xf4, 0x08, 0x23, 0x57, 0x66, 0x26, 0xe4, 0xa8, +0x03, 0xa1, 0xa3, 0x4b, 0xca, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x4d, +0x14, 0x03, 0x99, 0x1f, 0x8c, 0xd6, 0x75, 0x3d, +0x7a, 0xc3, 0x27, 0xff, 0xd3, 0xec, 0x9d, 0x8e, +0x68, 0x35, 0x36, 0x9b, 0x47, 0x54, 0x10, 0xa6, +0x28, 0x69, 0x7e, 0x01, 0x0a, 0x14, 0x60, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0x8a, 0xab, 0xdb, 0xc0, 0xf5, 0x2b, 0x08, +0x9a, 0x44, 0x02, 0xfc, 0xb7, 0x91, 0xa8, 0xba, +0xdc, 0x75, 0x50, 0x61, 0x75, 0xb0, 0x88, 0xda, +0x7a, 0x8b, 0x08, 0x8e, 0xf1, 0x2d, 0x93, 0x6e, +0x1d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0x50, 0x23, 0xca, 0xfd, 0xd7, +0xd5, 0xfe, 0x2a, 0x3f, 0x5c, 0xd6, 0x62, 0x94, +0xd1, 0xe9, 0xac, 0x64, 0xc3, 0x7e, 0xf2, 0x55, +0x9a, 0x53, 0xfc, 0xdf, 0xa3, 0xee, 0x0e, 0xe2, +0x47, 0x5e, 0x0e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x61, 0x1b, 0x24, +0xd4, 0x1c, 0xcc, 0x1e, 0xfe, 0x30, 0x4a, 0x85, +0xd5, 0xda, 0x8d, 0xe8, 0x60, 0xb7, 0x7a, 0xba, +0xb6, 0x8a, 0xae, 0x5d, 0x44, 0xaf, 0x95, 0x1b, +0x0c, 0x61, 0xdf, 0x14, 0x9e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0xfa, +0xbb, 0x95, 0x20, 0xfc, 0xac, 0x07, 0xef, 0x22, +0x24, 0x70, 0x59, 0x07, 0xde, 0xfd, 0x9b, 0x1f, +0xb8, 0x42, 0xa3, 0xf3, 0x09, 0x55, 0xcc, 0x9d, +0x28, 0x09, 0x50, 0x68, 0xea, 0xea, 0xf4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0x83, 0x56, 0xde, 0xb3, 0xb3, 0x9e, 0xdd, +0x3e, 0xf2, 0x46, 0xfc, 0x04, 0x49, 0xde, 0x0a, +0x1c, 0x01, 0x98, 0xa1, 0xe6, 0xc5, 0xb9, 0x73, +0x76, 0xcc, 0xf6, 0xce, 0x02, 0xd7, 0x81, 0x2d, +0x4b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0xfd, 0xc6, 0x2b, 0xfa, 0xbf, +0x3e, 0x1d, 0x52, 0x81, 0x2c, 0x3f, 0x16, 0x40, +0x97, 0x73, 0xf9, 0x8f, 0x5c, 0x07, 0x4e, 0xcd, +0x1a, 0xb5, 0xa4, 0x40, 0x5f, 0x0a, 0xb9, 0x08, +0x94, 0x09, 0x74, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0xa8, 0x54, 0xd7, +0x11, 0x0d, 0x1d, 0x11, 0xf6, 0xaa, 0xde, 0x37, +0x1c, 0x1f, 0x94, 0x2e, 0x9f, 0xd0, 0xdd, 0xa1, +0x8b, 0x62, 0xdb, 0x16, 0xc9, 0xa2, 0x4d, 0x6c, +0x32, 0x60, 0xb6, 0x2a, 0x3c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0x01, +0x06, 0xf5, 0x23, 0x16, 0x42, 0xbb, 0x44, 0x18, +0x53, 0x1a, 0x50, 0x54, 0x48, 0x40, 0xc0, 0x52, +0x3c, 0xef, 0xdb, 0x88, 0x8d, 0x6b, 0x0a, 0x70, +0x8e, 0x3e, 0x2d, 0x0b, 0x95, 0xf6, 0x69, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x9a, 0x1e, 0xc8, 0xf2, 0x60, 0x1b, 0xdc, +0xa8, 0xd5, 0x73, 0x0b, 0xd4, 0x6d, 0x6e, 0x8a, +0xe9, 0x1a, 0xca, 0x0c, 0xc2, 0xbf, 0xcc, 0xe7, +0xc9, 0x30, 0x95, 0x86, 0x5a, 0xbb, 0x01, 0xc7, +0xbe, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0xc9, 0xef, 0xc3, 0x87, 0x5c, +0xcc, 0xce, 0x6e, 0xbe, 0x67, 0xb1, 0xad, 0xdd, +0x9f, 0x87, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x91, 0x10, 0x38, 0xec, +0x50, 0xc9, 0xd7, 0x19, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0x0b, 0xf5, 0xd7, +0x4d, 0x21, 0x78, 0x22, 0x23, 0x1b, 0xe9, 0x1a, +0xd5, 0xcf, 0xd4, 0x60, 0x23, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0xf1, +0x6d, 0x9f, 0x5c, 0x74, 0x9b, 0x3d, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0xaa, +0x98, 0x3a, 0xa7, 0xfb, 0x50, 0x6b, 0x89, 0x37, +0x8f, 0x8e, 0xdc, 0x45, 0xb6, 0xba, 0x5c, 0xb9, +0x8e, 0x9b, 0x39, 0x8b, 0x28, 0x82, 0x75, 0xed, +0xde, 0x8a, 0xe2, 0xf9, 0xc3, 0x44, 0x0b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0xe8, 0x01, 0x03, 0xcb, 0xf9, 0x30, 0xf2, +0x99, 0xf5, 0x3e, 0xab, 0xb4, 0x2d, 0x08, 0xca, +0x17, 0x4d, 0xdb, 0x86, 0xd2, 0xc1, 0x5c, 0x74, +0xc4, 0x37, 0x19, 0x2f, 0xf5, 0x74, 0xa6, 0x6a, +0x1f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0x01, 0x4d, 0xbe, 0x2f, 0xfe, +0x24, 0x69, 0x24, 0x09, 0x81, 0xb3, 0xaf, 0x66, +0xdd, 0x6b, 0xfe, 0x8d, 0x89, 0x87, 0x38, 0x5b, +0x7b, 0x4d, 0x5e, 0x71, 0x41, 0xe4, 0x98, 0xce, +0xd5, 0x52, 0x9d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0xc2, 0xe4, 0xff, +0x26, 0x18, 0x43, 0x48, 0x8e, 0x6c, 0x91, 0x19, +0x53, 0xd0, 0x03, 0x51, 0xf7, 0x31, 0xff, 0x00, +0x55, 0xcb, 0x86, 0x60, 0xc8, 0x47, 0xc5, 0x43, +0x2b, 0x8b, 0xe1, 0xec, 0xb0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0xe7, +0x5c, 0xf3, 0x36, 0x83, 0x56, 0xd0, 0xd1, 0x87, +0xd9, 0x0f, 0x4f, 0x9a, 0x54, 0x04, 0x14, 0x78, +0x84, 0x0f, 0x22, 0x46, 0x00, 0xce, 0x46, 0x36, +0xd9, 0x65, 0x27, 0x0b, 0x0c, 0x19, 0x69, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0x54, 0xff, 0xe5, 0xc3, 0x6f, 0x44, 0xe1, +0xc0, 0xd5, 0xe6, 0xc0, 0xf9, 0x22, 0xec, 0xe3, +0xa0, 0x85, 0x76, 0x71, 0x43, 0x3f, 0x57, 0xee, +0xc7, 0x9f, 0x54, 0xb9, 0xf7, 0x7f, 0x08, 0xa3, +0xd5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0x45, 0xca, 0x0a, 0x69, 0x42, +0x4b, 0x6f, 0x8c, 0xed, 0x50, 0x42, 0x82, 0x21, +0x43, 0x3a, 0x95, 0x60, 0xf3, 0x15, 0x96, 0xe4, +0x3c, 0x36, 0x37, 0x46, 0xde, 0xec, 0x42, 0x46, +0xc3, 0xde, 0x12, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0x27, 0xc9, 0x52, +0xef, 0xdc, 0x34, 0x29, 0x80, 0xf0, 0xdd, 0x89, +0xed, 0x1f, 0x7a, 0xd9, 0xe3, 0x01, 0xb8, 0xe6, +0x2d, 0x12, 0xe1, 0xdc, 0xbd, 0x18, 0x15, 0xf8, +0x98, 0x56, 0xae, 0xfd, 0x86, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0x56, +0x49, 0xc6, 0x50, 0x3a, 0x3e, 0xd6, 0x67, 0x4f, +0xa6, 0x9d, 0xf8, 0xdf, 0xb2, 0x25, 0xc8, 0x97, +0xca, 0xe4, 0x1d, 0xfc, 0x69, 0xf3, 0xed, 0x53, +0x6b, 0x13, 0x29, 0xdf, 0xb2, 0xc0, 0x73, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0xb5, 0x17, 0xc5, 0x31, 0xaa, 0x23, 0xe0, +0xf3, 0x84, 0x46, 0xcf, 0x6a, 0x5a, 0x39, 0xfe, +0xfa, 0xb1, 0xf0, 0x2c, 0x87, 0x2a, 0x1d, 0x00, +0xd0, 0x81, 0x8e, 0x13, 0x88, 0x4d, 0x7c, 0x71, +0x11, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0x45, 0xae, 0xcc, 0xbd, 0x86, +0x99, 0x1e, 0xba, 0x03, 0xd6, 0xa1, 0xdc, 0xaf, +0xe1, 0x0c, 0x22, 0x76, 0x9e, 0x8f, 0xe0, 0xdb, +0x7b, 0x94, 0x19, 0x84, 0x5b, 0xa6, 0xaa, 0xdd, +0x2b, 0x65, 0xb0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0xc6, 0xe9, 0x49, +0x71, 0xf7, 0xd6, 0xd4, 0xd9, 0xbb, 0x30, 0xc7, +0xba, 0x7d, 0x3e, 0x6a, 0xa0, 0xc1, 0xb5, 0xd9, +0xb4, 0xb5, 0x8e, 0x31, 0x4e, 0x55, 0x14, 0xf4, +0xd0, 0x9a, 0xeb, 0x2a, 0x09, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0x47, +0x00, 0xf0, 0x7f, 0x31, 0x25, 0x9a, 0xdf, 0x00, +0x5e, 0x95, 0xd0, 0x49, 0x51, 0x62, 0x75, 0x79, +0x76, 0x33, 0xe0, 0x20, 0xec, 0x33, 0x12, 0xfc, +0x74, 0x18, 0x82, 0x19, 0xd8, 0x23, 0xb8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0x43, 0xc2, 0x98, 0x15, 0xf6, 0xb4, 0x12, +0x7f, 0x79, 0x42, 0x1e, 0x59, 0x4c, 0x1c, 0xf6, +0x02, 0x72, 0xf5, 0x9f, 0x94, 0x70, 0xdf, 0x44, +0x96, 0x06, 0xf2, 0x24, 0x44, 0x37, 0xb1, 0xb9, +0x20, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0xa7, 0xfb, 0x33, 0x06, 0x90, +0x17, 0x03, 0x0e, 0xba, 0xb5, 0xc4, 0x5c, 0xb8, +0xae, 0x8b, 0xa9, 0x5e, 0x07, 0xde, 0xeb, 0x2b, +0x51, 0xd7, 0xd4, 0x02, 0x37, 0xd8, 0xd6, 0x16, +0xbd, 0x33, 0x9c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0x69, 0x29, 0x05, +0xd6, 0x2a, 0x6e, 0x51, 0xd0, 0x2a, 0xe6, 0x57, +0xde, 0xbb, 0x72, 0xaf, 0xa1, 0x0d, 0x2a, 0x2a, +0xf3, 0x77, 0x2a, 0x98, 0xa1, 0x2e, 0x7a, 0xe0, +0x90, 0xa8, 0x6b, 0x33, 0x19, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0x03, +0xdf, 0x50, 0xf7, 0x8e, 0x6c, 0x15, 0x40, 0x66, +0x7a, 0x74, 0x8d, 0x19, 0x1e, 0x0f, 0x62, 0x0d, +0x7e, 0x67, 0x2f, 0x6b, 0xbf, 0xc8, 0x62, 0x83, +0xc7, 0xcf, 0x0c, 0x61, 0x63, 0x35, 0xa5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0x94, 0x62, 0x0c, 0x96, 0x08, 0xfa, 0xb4, +0x5e, 0x4f, 0x87, 0x87, 0x91, 0x29, 0x49, 0x34, +0x56, 0x4d, 0xfe, 0x58, 0xe1, 0xa2, 0xdc, 0xaa, +0x69, 0xb7, 0x6c, 0xdf, 0xc2, 0xe6, 0x42, 0x8e, +0x50, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0x1f, 0xca, 0x9b, 0xfb, 0x90, +0xbb, 0x5b, 0x6d, 0xa6, 0x74, 0x4f, 0x46, 0xfd, +0x4d, 0xf5, 0xd4, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x8d, 0xc8, 0xd0, 0x43, +0x57, 0x21, 0xbe, 0xf6, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0xbb, 0x1e, 0x8a, +0xda, 0xb2, 0xf7, 0x4b, 0x75, 0xb1, 0xe1, 0x74, +0x90, 0xf1, 0xaf, 0xce, 0x96, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0xd5, +0xdb, 0x11, 0x74, 0xe3, 0x86, 0xa9, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0xbe, +0x00, 0x47, 0x7c, 0xca, 0xa6, 0xad, 0xfb, 0x72, +0xd9, 0x47, 0x58, 0x1f, 0x83, 0xd8, 0x29, 0xe1, +0x21, 0x4f, 0x42, 0x8f, 0x7e, 0xb9, 0xa1, 0xd0, +0x8b, 0x04, 0x2c, 0x71, 0xd2, 0x2d, 0x92, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0xdf, 0x07, 0xf5, 0x6a, 0x27, 0xbe, 0xcf, +0x8c, 0xb4, 0x57, 0x20, 0xec, 0xfd, 0x82, 0x6f, +0x90, 0xe2, 0x3b, 0x18, 0x0e, 0x89, 0xbc, 0xab, +0xe3, 0x9c, 0xf3, 0xe0, 0x20, 0xab, 0x41, 0x34, +0x10, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x3c, 0x30, 0x42, 0xe0, 0xb1, +0x06, 0xbd, 0x5b, 0x99, 0x11, 0x8a, 0xf5, 0xe3, +0x38, 0xd0, 0xab, 0x73, 0x0e, 0x90, 0xe6, 0x8f, +0x1a, 0xa9, 0xac, 0xb1, 0x75, 0x9c, 0x51, 0xf0, +0x30, 0x55, 0xed, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0xe6, 0xe0, 0xe8, +0x9d, 0x48, 0x5b, 0x2b, 0x27, 0xdc, 0x53, 0xb6, +0x4f, 0x49, 0xfa, 0xd8, 0xe4, 0x23, 0xda, 0x30, +0xc4, 0xa2, 0xe3, 0x70, 0xc3, 0xcf, 0x29, 0xf0, +0xe6, 0xf8, 0xfe, 0xe2, 0x4d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0x0a, +0x2b, 0x6f, 0xbe, 0x9f, 0xbe, 0x5a, 0xcf, 0x37, +0x81, 0x3d, 0x72, 0xe2, 0x58, 0x69, 0xf8, 0x08, +0xcd, 0x2a, 0x4b, 0x5d, 0x0c, 0xba, 0xda, 0x1e, +0xf7, 0xa5, 0x19, 0xcb, 0x83, 0xbf, 0x92, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x5f, 0xac, 0x62, 0xec, 0x37, 0x9a, 0xd3, +0x0f, 0x68, 0xd8, 0x66, 0xe3, 0x5f, 0x29, 0x81, +0xe4, 0xea, 0x93, 0x9b, 0x2f, 0x34, 0x63, 0x8f, +0x4e, 0xda, 0x60, 0x62, 0xbe, 0xac, 0x56, 0xa2, +0xc6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0xc4, 0x4b, 0xee, 0x95, 0xc7, +0xbe, 0xeb, 0x86, 0x19, 0xa4, 0x2d, 0x5c, 0x06, +0x66, 0x41, 0x34, 0x40, 0x2e, 0xc8, 0xd0, 0xd1, +0x47, 0xdf, 0xe3, 0xbd, 0x38, 0x4d, 0x95, 0x6e, +0x4f, 0xf3, 0xc0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0x96, 0xf4, 0xc8, +0x69, 0xfa, 0xeb, 0xac, 0x0d, 0x5a, 0x80, 0x96, +0xea, 0xeb, 0xe2, 0x5c, 0xb1, 0x6c, 0xb6, 0x61, +0x88, 0x25, 0xcc, 0x7a, 0x8b, 0x20, 0x56, 0x6c, +0x79, 0xbc, 0x93, 0x63, 0x2e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0xc0, +0x98, 0x74, 0x74, 0x45, 0xc2, 0xdb, 0x54, 0x17, +0x1d, 0x2e, 0xd1, 0x8d, 0x99, 0x1d, 0xa3, 0xeb, +0x16, 0xd5, 0x02, 0x94, 0x4e, 0x34, 0xf3, 0xf0, +0x24, 0x68, 0x98, 0xa4, 0xd4, 0xc6, 0x62, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0x8b, 0x97, 0xf1, 0x06, 0xe5, 0xf8, 0x86, +0x32, 0x24, 0x84, 0xeb, 0x5f, 0x28, 0x11, 0xf3, +0x33, 0xee, 0x2b, 0x4d, 0x0c, 0x6a, 0xbc, 0xba, +0x52, 0xdf, 0x56, 0x5d, 0xe5, 0x57, 0xae, 0x04, +0x8a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0x1c, 0xff, 0xf4, 0x22, 0x4b, +0xb0, 0xf0, 0x58, 0x95, 0x75, 0x71, 0xd9, 0xb8, +0x0a, 0xd9, 0xd9, 0xce, 0xb7, 0x25, 0xee, 0xda, +0x4d, 0xeb, 0x33, 0x32, 0xb2, 0x2d, 0xfb, 0x11, +0x1b, 0x08, 0x3c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x7a, 0x39, 0xdc, +0xbd, 0x80, 0x20, 0xaf, 0xd6, 0xff, 0x3a, 0x53, +0x5c, 0x05, 0x2b, 0x6c, 0x3a, 0x2c, 0xc7, 0xa9, +0x20, 0x0a, 0x8a, 0x05, 0xb8, 0x60, 0xba, 0x14, +0x89, 0xa7, 0x2b, 0x1a, 0xdd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x61, +0xe9, 0xa9, 0xc9, 0x50, 0x1f, 0x42, 0x80, 0x44, +0x45, 0x49, 0x4d, 0xef, 0xbb, 0x94, 0xb3, 0xe3, +0x7d, 0xcc, 0x00, 0x65, 0x7e, 0x94, 0xa6, 0x6e, +0xb1, 0x6b, 0x4a, 0x49, 0x3a, 0x32, 0x62, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0xbd, 0x38, 0xf3, 0xcf, 0x09, 0xab, 0xeb, +0x84, 0x8b, 0xfe, 0xa2, 0xb5, 0x01, 0xb3, 0xb3, +0x95, 0x26, 0x48, 0x89, 0x08, 0xe8, 0x42, 0xf6, +0x7a, 0x27, 0x6e, 0x3a, 0x45, 0x30, 0xba, 0xdd, +0x40, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0xdd, 0x2e, 0xb0, 0x3c, 0x45, +0x4a, 0xb1, 0x91, 0x46, 0x66, 0xbb, 0x36, 0xf8, +0x0e, 0x1b, 0xc1, 0xa6, 0xcb, 0x27, 0x95, 0x3e, +0x37, 0x1a, 0xbf, 0x50, 0x70, 0x33, 0xe6, 0x6b, +0xb7, 0x4c, 0xe3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0x3d, 0xff, 0x0e, +0x59, 0x93, 0xaa, 0x79, 0xd4, 0x97, 0xb3, 0x4f, +0x8b, 0xa7, 0x02, 0x6c, 0x51, 0xa2, 0xa5, 0xf4, +0xd4, 0x61, 0x04, 0x05, 0x6a, 0xf7, 0xb4, 0x34, +0x3e, 0x47, 0x8b, 0x47, 0xcc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0xe1, +0xb9, 0x9d, 0x25, 0x34, 0xbe, 0xdc, 0x0b, 0x40, +0x96, 0x26, 0x23, 0xb3, 0x2b, 0x78, 0x1f, 0x3b, +0x56, 0xac, 0x14, 0x59, 0x91, 0x9f, 0xd3, 0x67, +0x82, 0xf8, 0xc5, 0x58, 0xf4, 0x25, 0x13, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0x68, 0xf5, 0xfb, 0x8e, 0xef, 0x3c, 0x5c, +0xb7, 0xe0, 0x5e, 0x19, 0xb2, 0xfb, 0x7f, 0x82, +0xbb, 0x5a, 0xe3, 0x9b, 0xa2, 0xa9, 0x5c, 0x6e, +0x2d, 0x05, 0x2f, 0x47, 0x2f, 0x19, 0x0d, 0x03, +0x95, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0x30, 0xb3, 0x11, 0x3d, 0x56, +0x63, 0x81, 0x81, 0x82, 0xf0, 0x5f, 0x88, 0xce, +0x04, 0x67, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x43, 0xec, 0x85, 0x0e, +0x03, 0x0a, 0x06, 0xf1, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0xc7, 0x75, 0x20, +0x49, 0x9d, 0xa6, 0xa4, 0x8c, 0x58, 0x0d, 0x84, +0x26, 0xee, 0x01, 0x55, 0xa9, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9f, 0x17, +0x0b, 0x6d, 0x72, 0x4e, 0xad, 0x7b, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0x10, +0x65, 0x84, 0x70, 0x01, 0x83, 0x2e, 0x39, 0x6e, +0x66, 0x14, 0xe0, 0x58, 0x24, 0x76, 0x3b, 0xf7, +0xc5, 0xb2, 0x05, 0x02, 0xfd, 0xd0, 0x49, 0x84, +0x5d, 0x56, 0x8b, 0x12, 0xb8, 0x98, 0xd6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0x1b, 0xbe, 0xc5, 0x5b, 0x10, 0x74, 0x65, +0xd9, 0x79, 0x22, 0x51, 0x88, 0x4c, 0xde, 0x5a, +0x2e, 0xd5, 0x38, 0x87, 0x5f, 0x66, 0x11, 0x9e, +0x5e, 0x30, 0x63, 0xa2, 0xaf, 0x64, 0x41, 0x8f, +0xd6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0x18, 0xfb, 0x08, 0x2e, 0x46, +0xb0, 0x19, 0xab, 0xa1, 0xb9, 0xd0, 0xb6, 0x1d, +0xc9, 0xf3, 0xbc, 0x77, 0xb0, 0xbb, 0x62, 0xae, +0xc6, 0x99, 0x4f, 0x8c, 0x4f, 0x18, 0x37, 0x59, +0xf7, 0x62, 0x56, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x9e, 0x24, 0xd2, +0x15, 0x20, 0x3a, 0x58, 0xa2, 0x85, 0x0d, 0x27, +0x28, 0x14, 0xf2, 0xac, 0xdd, 0x2f, 0x1b, 0x9c, +0x93, 0x40, 0x1e, 0x4e, 0x83, 0xf9, 0x70, 0xdf, +0x1d, 0x33, 0x31, 0xed, 0xdb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0x99, +0x7b, 0x56, 0x59, 0x0e, 0x6e, 0x7b, 0x19, 0x4b, +0xb3, 0x63, 0xef, 0xfa, 0x75, 0x5b, 0x41, 0x02, +0x92, 0xaf, 0x86, 0xa7, 0xc1, 0xb1, 0x72, 0xcd, +0x18, 0x3c, 0xde, 0x15, 0x87, 0xa3, 0x7f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0xc3, 0xa7, 0xb4, 0xc8, 0x1c, 0xba, 0xbb, +0x62, 0xaf, 0x68, 0x5e, 0xab, 0xfe, 0x4a, 0x3a, +0x3a, 0x07, 0x17, 0xe9, 0x15, 0x83, 0xd9, 0x0c, +0xf7, 0x04, 0x49, 0x34, 0xcd, 0xe4, 0xe5, 0x24, +0x90, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0xe5, 0xd4, 0x9a, 0x84, 0x17, +0xdf, 0x63, 0xd8, 0x9d, 0x61, 0xa9, 0xfc, 0x8d, +0xbb, 0x5a, 0xd9, 0xa8, 0xc1, 0xaa, 0x53, 0x2a, +0x39, 0x52, 0x17, 0x9c, 0x58, 0xde, 0x1f, 0x6f, +0x0a, 0x83, 0xa5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0xd0, 0xf3, 0x08, +0x9b, 0xfc, 0xbb, 0x8e, 0x74, 0xa0, 0x80, 0x8f, +0x90, 0x54, 0xdb, 0x38, 0xa7, 0x8e, 0x11, 0x1e, +0x80, 0x65, 0x44, 0xd7, 0x24, 0x86, 0xf9, 0x1a, +0xce, 0xc7, 0xd6, 0x14, 0xf0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x09, +0x21, 0xcf, 0x92, 0xa1, 0x7f, 0x33, 0x7a, 0xf3, +0x3b, 0xb9, 0x47, 0x42, 0x1f, 0xf6, 0xe9, 0x51, +0x22, 0x64, 0x00, 0x74, 0x8f, 0x35, 0x42, 0xa9, +0x21, 0xd0, 0xb3, 0x21, 0x24, 0x58, 0x2b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0x7c, 0xb9, 0x62, 0x47, 0x54, 0xdb, 0x32, +0x0a, 0x10, 0x2e, 0xd1, 0x91, 0x5f, 0x31, 0x4c, +0x9c, 0xb4, 0x45, 0x3f, 0x26, 0xd4, 0x7e, 0xb8, +0x5c, 0x0d, 0x3b, 0x72, 0xe8, 0x01, 0x1b, 0x4b, +0xe1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0x2f, 0xef, 0xbf, 0x1c, 0x26, +0xbc, 0xd8, 0xbf, 0x69, 0xc9, 0x67, 0xeb, 0xdc, +0xd2, 0xdf, 0x88, 0x9a, 0x0b, 0x76, 0x5b, 0xca, +0x27, 0x93, 0xad, 0xed, 0xc6, 0xb4, 0x3c, 0x92, +0xcd, 0x4e, 0x65, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0x0a, 0x21, 0x73, +0x2c, 0x58, 0x0a, 0x35, 0xa0, 0xc7, 0x9c, 0x93, +0x4f, 0x0c, 0x2d, 0x8e, 0x09, 0x51, 0x3c, 0x10, +0xc9, 0x47, 0xcf, 0x81, 0x1a, 0xdd, 0x96, 0x86, +0xf5, 0x52, 0x0a, 0x6c, 0xe0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x69, +0x10, 0x3a, 0xf4, 0x8f, 0x22, 0x49, 0xc0, 0xf6, +0xb1, 0x98, 0x1f, 0x1c, 0x33, 0x3d, 0xb6, 0xb2, +0x53, 0xa6, 0xbc, 0x19, 0xcb, 0xdf, 0x57, 0xbc, +0x6c, 0x36, 0xbf, 0xb0, 0x70, 0x51, 0x97, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0xbe, 0x65, 0xe2, 0x97, 0x98, 0x5a, 0x3d, +0x09, 0x3b, 0xf6, 0x51, 0x4d, 0x77, 0xa0, 0x3f, +0x5b, 0xb1, 0x39, 0x2c, 0xfd, 0x05, 0x35, 0x5a, +0xf3, 0xfe, 0xa7, 0xc2, 0x03, 0xe1, 0xc9, 0xbe, +0x3b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0xc6, 0xb0, 0xab, 0xff, 0x2c, +0x97, 0x06, 0xcc, 0x5a, 0x56, 0x27, 0x23, 0x10, +0xba, 0xe4, 0xdd, 0x5e, 0xd1, 0x14, 0x61, 0xaf, +0xd4, 0x05, 0x69, 0x8e, 0x4f, 0xe1, 0x16, 0xb2, +0xe6, 0xe6, 0x21, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0x32, 0xf7, 0x3a, +0x64, 0xf2, 0x0b, 0x95, 0x34, 0xa9, 0xed, 0x73, +0x94, 0xcd, 0x7b, 0xde, 0xbf, 0x7b, 0x3c, 0x13, +0xf8, 0xe6, 0x26, 0x91, 0x6a, 0x50, 0x16, 0x3d, +0xe6, 0xc2, 0xf5, 0x73, 0x7d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x9c, +0xe8, 0xcd, 0xf4, 0xc9, 0xc8, 0x11, 0x9e, 0x36, +0x82, 0x27, 0x37, 0x8a, 0x1b, 0x7c, 0xf2, 0x80, +0x6f, 0xf8, 0x61, 0xf4, 0x7f, 0xfb, 0x68, 0xcb, +0xbb, 0xb8, 0xe7, 0xc5, 0xbf, 0x81, 0x51, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0xe3, 0x33, 0x17, 0x14, 0x50, 0xd8, 0x6a, +0xd9, 0x3d, 0x5a, 0x15, 0x24, 0x42, 0xa6, 0xc2, +0x09, 0xdf, 0x3e, 0xf6, 0x99, 0x76, 0x81, 0x96, +0x49, 0x27, 0x73, 0xda, 0xcc, 0xfc, 0xd1, 0x0d, +0xe4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0x33, 0x5d, 0x6a, 0x3e, 0x67, +0xc6, 0xe5, 0x03, 0x42, 0x62, 0xc0, 0x50, 0xd8, +0x7f, 0xb7, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x17, 0x93, 0x05, 0xad, +0xdf, 0x2a, 0xd0, 0x3e, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0x8e, 0x6a, 0x84, +0xbc, 0x63, 0xe8, 0xa8, 0xa8, 0xfd, 0x89, 0x47, +0x8d, 0x36, 0x53, 0x9a, 0x0f, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xb8, +0x90, 0x8e, 0x5f, 0xcf, 0x03, 0x37, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0xc0, +0x51, 0x58, 0xb3, 0x0b, 0xc6, 0x1a, 0x36, 0x8f, +0x81, 0xe5, 0xb2, 0x4f, 0x99, 0xa7, 0xb5, 0x12, +0x7c, 0xda, 0x90, 0x1b, 0xe4, 0xfe, 0x5a, 0xb7, +0x6f, 0xa7, 0xbe, 0x0e, 0x7c, 0xe3, 0x1c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0x23, 0x93, 0xe6, 0x55, 0x68, 0x2a, 0xaa, +0xc9, 0x8e, 0x7f, 0xe1, 0x78, 0x60, 0xc9, 0xf1, +0x68, 0x7f, 0x0f, 0x4d, 0xe9, 0x7d, 0xb8, 0x02, +0xaa, 0x44, 0xfe, 0xd4, 0xc7, 0x4e, 0x28, 0x97, +0xfc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x03, 0xab, 0x75, 0x2c, 0x54, +0x63, 0x70, 0xcb, 0xfd, 0xd7, 0x21, 0x5a, 0x7e, +0x46, 0x91, 0xd9, 0xa4, 0x41, 0xa9, 0xbf, 0x88, +0x25, 0xb9, 0x0a, 0xa1, 0xd8, 0x0a, 0x43, 0x88, +0x0f, 0x66, 0xc3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x15, 0x89, 0x1d, +0xcb, 0xce, 0xa7, 0x7b, 0xc8, 0x0a, 0x51, 0x73, +0xc3, 0x31, 0xe8, 0x55, 0xa2, 0x46, 0x42, 0x7e, +0x82, 0xcd, 0x93, 0x30, 0xb1, 0xee, 0x9a, 0x6d, +0xad, 0x89, 0xbf, 0x1d, 0xb9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0x27, +0x6e, 0xb9, 0xb5, 0x2e, 0x58, 0x23, 0x1f, 0xb2, +0x93, 0x2c, 0x20, 0x40, 0xdf, 0x8f, 0xf2, 0xcd, +0xe0, 0xb1, 0xfa, 0xb7, 0x99, 0xd0, 0x37, 0x52, +0x9d, 0x28, 0x57, 0x48, 0x66, 0xd8, 0x3f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0x29, 0x14, 0x39, 0x73, 0xca, 0x44, 0xae, +0x9e, 0xf9, 0xa0, 0x2d, 0x87, 0xf5, 0x41, 0xc9, +0xee, 0xe5, 0x5d, 0x1a, 0x7f, 0xfd, 0xa4, 0x26, +0xe2, 0x11, 0x08, 0x80, 0xd6, 0xbc, 0x95, 0xa7, +0x39, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0x17, 0x6c, 0xa1, 0x65, 0x33, +0x15, 0x1b, 0xc0, 0x98, 0x0c, 0xcf, 0x4e, 0xe3, +0x6c, 0xed, 0x6e, 0x91, 0xd3, 0x2e, 0xdd, 0xe5, +0x2a, 0x1c, 0xe5, 0x43, 0xa4, 0x48, 0xac, 0x41, +0xa7, 0x79, 0x3a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0x2b, 0x51, 0xcb, +0x85, 0xcb, 0xe2, 0x23, 0xcf, 0x4d, 0xf8, 0x09, +0xe7, 0xdf, 0xc1, 0x78, 0x87, 0xc8, 0xa8, 0xb5, +0xc4, 0x15, 0x14, 0x69, 0xeb, 0x4a, 0xa4, 0x9b, +0xd6, 0x84, 0x18, 0xa3, 0xd5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0x76, +0xbd, 0x0b, 0x38, 0xb9, 0xc2, 0xfd, 0xb1, 0x73, +0xa7, 0x93, 0xb5, 0xcf, 0x86, 0xb5, 0x0a, 0xd8, +0x80, 0x0f, 0x3e, 0xd0, 0x13, 0x45, 0x3b, 0x32, +0x6f, 0xf4, 0xd5, 0xbd, 0xee, 0x96, 0x5d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0xe4, 0xe1, 0x24, 0x9d, 0xd3, 0xf3, 0x7c, +0xcc, 0x33, 0x5e, 0x2e, 0xeb, 0x9b, 0x42, 0x8d, +0x33, 0x78, 0xbc, 0x87, 0xc1, 0x02, 0x43, 0x98, +0xb9, 0x2e, 0x0e, 0x20, 0xd2, 0x9b, 0x4f, 0xf5, +0x09, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0xc9, 0x75, 0xac, 0x33, 0x05, +0x86, 0x35, 0x93, 0x19, 0xd4, 0xbc, 0x49, 0x97, +0xe0, 0xda, 0x8b, 0xed, 0x6e, 0xb1, 0x30, 0xcc, +0x9f, 0xf9, 0xa0, 0xb0, 0xad, 0x50, 0xca, 0xbc, +0xaf, 0x4c, 0x72, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0x74, 0x9b, 0xb0, +0x2f, 0x3d, 0xad, 0x12, 0xde, 0x03, 0xa0, 0x35, +0x83, 0x30, 0x40, 0x18, 0x77, 0x8d, 0xb2, 0xbb, +0x33, 0x4b, 0xe1, 0x11, 0xc9, 0x45, 0x24, 0x5c, +0x5c, 0xfe, 0xa2, 0x4d, 0xc5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x55, +0x4f, 0x67, 0xab, 0x7e, 0x77, 0x55, 0x0d, 0x90, +0xe2, 0x5a, 0x87, 0xab, 0x31, 0x79, 0xb8, 0xf7, +0xd8, 0x45, 0x23, 0x02, 0xde, 0xd0, 0x63, 0xfc, +0x9a, 0xa2, 0xc7, 0xc8, 0xe5, 0x6c, 0x86, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0xdd, 0x32, 0xf2, 0x46, 0xfd, 0xfc, 0xd3, +0xf2, 0x7e, 0x24, 0x3b, 0x5d, 0xdc, 0xc0, 0xb3, +0x05, 0x6b, 0x44, 0xcc, 0x33, 0x79, 0x25, 0x30, +0x60, 0x36, 0x6b, 0x2e, 0x0b, 0x00, 0xd8, 0x12, +0xb5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0xa3, 0x6a, 0xb1, 0x4d, 0xfe, +0xc7, 0x30, 0xea, 0x6f, 0x35, 0xd1, 0x91, 0x67, +0xf5, 0x9a, 0xad, 0xd9, 0x7b, 0x7e, 0x45, 0x79, +0x5f, 0x19, 0x12, 0x9b, 0x79, 0x78, 0x01, 0x99, +0x1a, 0x40, 0xe7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0xe2, 0xc4, 0x36, +0x93, 0x71, 0xfb, 0xef, 0x3a, 0x6c, 0xa4, 0xfa, +0xaf, 0xeb, 0x71, 0x2e, 0x8d, 0x4c, 0xbf, 0x15, +0x12, 0x82, 0xca, 0x53, 0xbf, 0xbc, 0x35, 0x28, +0x56, 0x85, 0x9a, 0x6e, 0x34, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0x75, +0xbc, 0x50, 0xbc, 0x35, 0x1f, 0xef, 0x4d, 0xac, +0x36, 0xc9, 0x07, 0x09, 0x58, 0xd9, 0x43, 0x83, +0x71, 0xc7, 0x32, 0x2f, 0xce, 0xf1, 0x38, 0x78, +0xa1, 0x5d, 0x02, 0x85, 0xf1, 0xc7, 0x79, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0xab, 0x24, 0x1b, 0x60, 0x47, 0x9a, 0x59, +0xdb, 0x10, 0xf1, 0x1e, 0x8b, 0xde, 0x63, 0xab, +0x6e, 0x63, 0xf6, 0x4e, 0x33, 0x83, 0x37, 0x61, +0x7d, 0xaf, 0x48, 0x74, 0x23, 0x25, 0xc8, 0x6e, +0x2f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x43, 0xc7, 0x58, 0xf0, 0x1e, +0xc8, 0x46, 0x73, 0xcf, 0x22, 0x7f, 0x85, 0xd5, +0xb4, 0xfa, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xe9, 0x6c, 0x39, 0xa6, +0xa1, 0x14, 0xa3, 0x1b, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0x27, 0x73, 0x60, +0xc9, 0x8f, 0x18, 0xb8, 0xbc, 0xaa, 0x27, 0xee, +0x0c, 0x07, 0xf3, 0x73, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0xd0, +0x83, 0x15, 0xa3, 0x6c, 0xde, 0xcc, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0xfe, +0x93, 0x28, 0xa1, 0xba, 0xae, 0x59, 0xba, 0x9f, +0x88, 0x38, 0xbf, 0x49, 0x25, 0xcb, 0x1f, 0x57, +0xdb, 0xdf, 0xa8, 0x50, 0x07, 0x00, 0xf9, 0x04, +0x07, 0xa8, 0xfc, 0x34, 0x26, 0xc5, 0x68, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0x97, 0x2c, 0xaf, 0xe5, 0x83, 0x94, 0x6d, +0xe7, 0x8c, 0xbd, 0xc1, 0x5a, 0x06, 0x77, 0xf1, +0x90, 0xf6, 0x4f, 0xf3, 0x98, 0xfa, 0xb7, 0x1c, +0x81, 0x3d, 0x2e, 0xc0, 0x3d, 0x40, 0x19, 0x6d, +0x8e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0xaf, 0x57, 0xf3, 0xda, 0xe1, +0x2f, 0x65, 0x89, 0x5e, 0x74, 0xf8, 0xd2, 0xcb, +0x50, 0x26, 0x0a, 0xa4, 0xb2, 0xed, 0x7d, 0x13, +0x04, 0x65, 0xf0, 0xf1, 0x8a, 0x85, 0x1b, 0x43, +0x4c, 0x08, 0x5d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0x6a, 0x6f, 0xee, +0xa7, 0xea, 0x42, 0x97, 0x33, 0xc1, 0x00, 0x1b, +0x88, 0x9d, 0xd2, 0xc1, 0x56, 0x87, 0x4c, 0xad, +0x8c, 0x22, 0x22, 0x50, 0x84, 0x82, 0xca, 0x47, +0xe8, 0xd2, 0xd6, 0xfd, 0x69, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0x01, +0xb7, 0xc9, 0xd2, 0xcf, 0x49, 0x6e, 0x14, 0xfd, +0x4b, 0x50, 0xf3, 0x7d, 0x2e, 0x77, 0xaf, 0xf5, +0xa2, 0x23, 0xaa, 0xc8, 0x62, 0xbb, 0x28, 0x68, +0xc1, 0x1f, 0x28, 0xb5, 0xd7, 0xb4, 0xc2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0x33, 0x45, 0xe9, 0xbc, 0x85, 0x11, 0x7c, +0x54, 0xbf, 0x7d, 0x86, 0x68, 0xcb, 0x00, 0x36, +0x12, 0xde, 0xf7, 0x16, 0x65, 0xb1, 0x36, 0x48, +0x21, 0x91, 0x15, 0x77, 0x68, 0xfe, 0xd2, 0x2f, +0x53, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0x34, 0x7f, 0x4c, 0xe8, 0x39, +0xac, 0xfa, 0x23, 0x28, 0xec, 0xf8, 0x41, 0x80, +0xf2, 0xaa, 0xf2, 0x42, 0x6a, 0x68, 0x23, 0xdd, +0x23, 0xdf, 0x55, 0xdc, 0xb2, 0x36, 0x66, 0x53, +0xe4, 0x6c, 0x36, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0xbc, 0x5e, 0xa5, +0xd6, 0x46, 0x78, 0xff, 0xa5, 0xfd, 0xe3, 0xde, +0x92, 0x29, 0x79, 0x91, 0x97, 0x61, 0xef, 0xc5, +0x41, 0x60, 0x04, 0xc3, 0xf1, 0xc0, 0x42, 0xbf, +0x88, 0xfc, 0xbf, 0xb4, 0xf3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0xa7, +0x72, 0xcd, 0x3f, 0xcc, 0x4f, 0xde, 0xa3, 0xbf, +0xeb, 0x08, 0x87, 0x09, 0xc4, 0xd9, 0xd8, 0x63, +0xdb, 0xd6, 0x98, 0xad, 0x4c, 0xad, 0x19, 0x56, +0xa4, 0x82, 0x10, 0x4f, 0xee, 0xa9, 0x36, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0xb2, 0x4b, 0x33, 0xb9, 0x98, 0xfc, 0xa6, +0xbf, 0xdd, 0x7c, 0x04, 0xaf, 0x92, 0x4f, 0x16, +0x90, 0x7c, 0x63, 0x09, 0x21, 0xa8, 0xbd, 0xba, +0x51, 0xf2, 0xd3, 0x23, 0xa1, 0x71, 0xab, 0xa4, +0x53, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0xb2, 0xae, 0x0a, 0x2d, 0x2b, +0xf0, 0xcb, 0xd2, 0x98, 0x0e, 0x89, 0x4e, 0xba, +0x63, 0xe5, 0x47, 0x44, 0xa5, 0x0a, 0x8a, 0xc1, +0x11, 0x12, 0xf7, 0x7f, 0x66, 0xd7, 0x2d, 0xc5, +0x3b, 0x65, 0x50, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0x2d, 0xcd, 0xe2, +0x74, 0x97, 0x6d, 0x9c, 0x7a, 0x01, 0xc9, 0x7c, +0x66, 0x4f, 0x51, 0xb5, 0xbe, 0xd3, 0x13, 0xf3, +0x57, 0x77, 0xf6, 0x89, 0x92, 0x1f, 0x21, 0xfe, +0x8d, 0xe4, 0x02, 0xba, 0xe7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0xc5, +0x5d, 0xee, 0x67, 0x23, 0xc3, 0x43, 0xae, 0x05, +0x21, 0xe0, 0x69, 0x89, 0x09, 0x66, 0x53, 0x52, +0x00, 0xd8, 0xca, 0x20, 0x9f, 0xa6, 0xb2, 0x7a, +0xa6, 0x02, 0xd9, 0x2d, 0x6d, 0x07, 0x65, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x04, 0x5b, 0xc5, 0x0e, 0x79, 0xd7, 0xa4, +0xc0, 0xb1, 0xef, 0x09, 0x74, 0x62, 0xf3, 0x73, +0x02, 0x7d, 0xb6, 0x8a, 0x86, 0x10, 0x05, 0x69, +0xf5, 0x5b, 0xef, 0xbd, 0x88, 0xf5, 0xb5, 0x28, +0x8a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0x61, 0x0e, 0x0d, 0x6e, 0xbf, +0xe5, 0xb9, 0xba, 0x5e, 0x38, 0x0c, 0xa3, 0x81, +0x35, 0x07, 0xbb, 0xee, 0x9c, 0x42, 0x63, 0xeb, +0xa5, 0x6d, 0x0a, 0xcc, 0xec, 0x71, 0xe0, 0x71, +0x27, 0x2e, 0x7a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0xee, 0x1b, 0x1e, +0xc1, 0x3f, 0xb6, 0x3c, 0x7a, 0x93, 0xfc, 0x92, +0x89, 0xf2, 0x5c, 0x59, 0x75, 0x63, 0x4e, 0xb7, +0xbf, 0x5b, 0x79, 0x78, 0xc8, 0x45, 0xe2, 0xe3, +0x44, 0x33, 0x62, 0x38, 0xec, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0xbe, +0xd8, 0x62, 0x5a, 0x62, 0x80, 0x6e, 0x57, 0x9f, +0x29, 0x11, 0xdf, 0x7b, 0x58, 0x46, 0x7e, 0x8e, +0xd1, 0x8a, 0x02, 0x53, 0x26, 0xfa, 0xcc, 0x97, +0x1a, 0xf3, 0xbb, 0xd5, 0xbe, 0xa6, 0x01, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0x37, 0x74, 0x64, 0x62, 0x42, 0x21, 0xe4, +0xd2, 0x85, 0xce, 0xaf, 0x51, 0xbb, 0xfe, 0x23, +0xc1, 0x7a, 0xf8, 0x84, 0x8b, 0x25, 0x08, 0x6f, +0x99, 0xaf, 0x4f, 0x19, 0x03, 0xbb, 0xc2, 0x2a, +0x29, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0x36, 0xe6, 0xa7, 0xdf, 0x7f, +0xcc, 0x6e, 0x31, 0x86, 0x96, 0x95, 0x6c, 0xbe, +0xba, 0x47, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xf2, 0x63, 0xa9, 0x9b, +0xb0, 0x7e, 0x19, 0xd1, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0xbe, 0xc8, 0xcd, +0x5e, 0x86, 0xd2, 0x31, 0x61, 0xd7, 0x85, 0x0e, +0xb1, 0x4a, 0x0c, 0x20, 0xfd, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x5e, +0x4d, 0x67, 0xb5, 0xb4, 0x7a, 0xca, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0xe3, +0x84, 0x26, 0x86, 0x58, 0xea, 0xf1, 0x2f, 0xca, +0x4e, 0x24, 0x78, 0x07, 0x8a, 0xdd, 0x2c, 0x4b, +0x1a, 0x69, 0x8f, 0x0d, 0x4f, 0xce, 0x69, 0xdb, +0xfa, 0x2e, 0xaa, 0xbc, 0xc8, 0xf2, 0xb4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0x8d, 0x49, 0x0d, 0x6c, 0x3c, 0x8f, 0xca, +0xe7, 0xd4, 0x94, 0xbf, 0x23, 0x73, 0x9f, 0xdf, +0xff, 0x84, 0xb4, 0xa6, 0x29, 0xf0, 0xb2, 0x4e, +0x2f, 0x7a, 0x4b, 0x8f, 0x6c, 0xa7, 0xae, 0x7a, +0x00, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0xd5, 0xb5, 0xb6, 0x38, 0xe2, +0x7e, 0x74, 0xda, 0x16, 0x7d, 0x83, 0xa1, 0x91, +0x5f, 0x1d, 0x57, 0xfe, 0x95, 0x9b, 0x94, 0x22, +0xb6, 0x77, 0xab, 0x3d, 0xe2, 0x2a, 0x37, 0x8f, +0x61, 0xb0, 0xaf, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0x35, 0xb4, 0x71, +0xaa, 0xd3, 0x1f, 0x1f, 0x44, 0x75, 0x62, 0xd4, +0xa2, 0x49, 0x25, 0x15, 0x15, 0x45, 0x5e, 0xaf, +0xc1, 0xb2, 0x38, 0x90, 0x7c, 0x3a, 0x13, 0xac, +0x27, 0x22, 0x79, 0x33, 0x6d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0x54, +0x2e, 0x8e, 0xcb, 0x1b, 0x31, 0x06, 0x10, 0x64, +0x3d, 0xaf, 0x4a, 0xad, 0x90, 0x72, 0xea, 0xf1, +0x78, 0x88, 0x59, 0xce, 0x8c, 0x84, 0x74, 0x93, +0x1e, 0x78, 0xe0, 0x38, 0x76, 0x3e, 0x53, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0x73, 0x1a, 0xe6, 0xed, 0xdf, 0xed, 0x88, +0x42, 0x09, 0xce, 0xfc, 0x73, 0x8b, 0x5e, 0x79, +0xde, 0xe2, 0x60, 0x8d, 0x78, 0xfd, 0xf5, 0x54, +0xfb, 0x17, 0x2d, 0xfd, 0x24, 0x36, 0xbb, 0xc5, +0x61, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0x95, 0xb7, 0x38, 0xb7, 0x59, +0xe1, 0xc7, 0x6b, 0x66, 0x9e, 0x69, 0x0b, 0xcd, +0x27, 0x95, 0x7d, 0x7b, 0xfc, 0x22, 0x04, 0x34, +0x4c, 0xc7, 0xff, 0x35, 0x26, 0xf0, 0x61, 0xa0, +0xee, 0x1e, 0xfc, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x61, 0x58, 0xf0, +0x6e, 0x2d, 0x1e, 0x25, 0xd2, 0x59, 0x6f, 0xc3, +0xb8, 0xda, 0x56, 0xec, 0xc1, 0x1b, 0x5e, 0x29, +0x98, 0x9b, 0xcd, 0x85, 0x08, 0xf6, 0xb6, 0xbb, +0x26, 0x5b, 0xb8, 0xb3, 0x65, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x09, +0xb0, 0x87, 0x38, 0xcf, 0xd0, 0x7a, 0x1b, 0xae, +0x5a, 0x6a, 0x75, 0x1c, 0xb3, 0x46, 0x4d, 0x3d, +0xbe, 0x47, 0xbc, 0xce, 0xcc, 0x00, 0x7e, 0x77, +0x21, 0x77, 0xdf, 0x2a, 0x20, 0xe1, 0x05, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0xdd, 0x58, 0x82, 0x51, 0x51, 0x75, 0x27, +0x63, 0x57, 0x5b, 0x38, 0xec, 0x77, 0x25, 0x64, +0x41, 0xd7, 0x27, 0x86, 0xf6, 0x79, 0x81, 0x80, +0x45, 0xaf, 0x94, 0x85, 0x55, 0x92, 0x2f, 0xb4, +0x1f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0x04, 0x98, 0x3d, 0x26, 0x83, +0xb3, 0x1e, 0xca, 0x73, 0xce, 0x56, 0x48, 0x8e, +0x62, 0xf2, 0x11, 0xe5, 0xe9, 0x11, 0xbe, 0x32, +0x94, 0x8b, 0x7a, 0x16, 0xdc, 0x24, 0x3a, 0x1b, +0xae, 0x53, 0x86, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0xa3, 0x02, 0xa8, +0xe7, 0xd0, 0xba, 0xfe, 0x13, 0x16, 0x43, 0x58, +0xcc, 0x28, 0x08, 0xbd, 0x23, 0x88, 0x36, 0xce, +0xca, 0x94, 0x2c, 0xc3, 0x92, 0x4d, 0xaf, 0x5a, +0x94, 0xa0, 0x33, 0xa6, 0x35, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0xf5, +0xee, 0x73, 0x30, 0xd1, 0x47, 0xeb, 0xa2, 0x60, +0x8d, 0x0a, 0xaa, 0x6c, 0x1d, 0xcd, 0x5b, 0x6a, +0xf0, 0xb5, 0x52, 0xf2, 0x59, 0x61, 0x53, 0x15, +0xd6, 0x43, 0x0a, 0xf1, 0x51, 0x02, 0x72, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x49, 0x49, 0xd8, 0x6b, 0x90, 0xc1, 0x3b, +0xfa, 0xdc, 0xf7, 0x34, 0x90, 0x70, 0x07, 0x8f, +0x36, 0x14, 0x4c, 0xb9, 0x8b, 0x8a, 0x18, 0xb6, +0xa0, 0x77, 0x50, 0x44, 0x1f, 0x31, 0x95, 0xdc, +0x84, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0x09, 0xcd, 0x5e, 0x95, 0x60, +0x85, 0x4c, 0xd0, 0x4d, 0x7c, 0x85, 0x72, 0x53, +0x3e, 0x60, 0xdd, 0x8d, 0xbf, 0xbd, 0x2c, 0x99, +0x23, 0xc4, 0x1b, 0x14, 0x30, 0x1c, 0x48, 0x28, +0x89, 0x94, 0x5f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0xf9, 0xfa, 0x01, +0xa1, 0xa3, 0xdb, 0xb6, 0x12, 0x70, 0x38, 0x6c, +0x8f, 0xe8, 0xad, 0x64, 0xc8, 0xd5, 0xa7, 0x80, +0xab, 0xd1, 0xcc, 0x24, 0x5e, 0xb4, 0x32, 0x9c, +0xdf, 0x24, 0xf2, 0x01, 0x07, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0x5b, +0xb8, 0x63, 0xdd, 0x1e, 0xf2, 0x57, 0x30, 0x9a, +0xae, 0xda, 0xaf, 0x9f, 0x68, 0xeb, 0xa6, 0x92, +0x42, 0x3f, 0x77, 0x60, 0x80, 0x26, 0x6b, 0xf0, +0x85, 0xc6, 0x4d, 0xb2, 0x62, 0xc3, 0x75, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0x87, 0x23, 0x23, 0xf4, 0xe7, 0x0c, 0xa4, +0xda, 0x64, 0x31, 0x1c, 0xd6, 0x0c, 0x3f, 0x8e, +0xcc, 0xe1, 0x23, 0x48, 0xe9, 0x01, 0xae, 0x5b, +0x89, 0x3f, 0x1f, 0x45, 0xff, 0x42, 0x5e, 0x0b, +0x9c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0x15, 0x26, 0xe6, 0x83, 0xdf, +0xb7, 0x63, 0x25, 0x54, 0x53, 0x15, 0x9f, 0xe6, +0xd2, 0xfb, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x61, 0xa7, 0xcc, 0xcc, +0x00, 0xff, 0x41, 0xe4, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0xe5, 0x07, 0x6e, +0xe0, 0xc8, 0xdf, 0xe1, 0xf9, 0x37, 0xe5, 0xec, +0x5b, 0x9d, 0x73, 0xb3, 0x85, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x49, +0xcd, 0x70, 0xe8, 0x7e, 0x88, 0x55, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0xa7, +0x6f, 0x11, 0xb8, 0xa2, 0x9c, 0xa0, 0x5d, 0x04, +0x1e, 0xd3, 0x91, 0x63, 0xac, 0xca, 0x72, 0xc3, +0x7c, 0xe7, 0xa7, 0xfb, 0x1e, 0x86, 0x2d, 0x57, +0xbe, 0x58, 0x10, 0xe4, 0xe3, 0x9a, 0x90, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x51, 0x61, 0x71, 0xfb, 0xcb, 0x01, 0x3a, +0x00, 0x61, 0xb9, 0x82, 0x40, 0x59, 0x4f, 0x0e, +0x87, 0x7b, 0x10, 0xf8, 0xfe, 0x1e, 0xd4, 0x4e, +0xb1, 0x57, 0x60, 0x76, 0xf0, 0xce, 0x7f, 0xce, +0xb0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0x5d, 0xcd, 0x05, 0x78, 0xf8, +0x43, 0xfb, 0xe4, 0x03, 0x69, 0x19, 0xa6, 0x75, +0x15, 0xb4, 0x0c, 0x23, 0x71, 0xc0, 0xd7, 0x44, +0x5b, 0xb6, 0x0b, 0xa1, 0x96, 0x61, 0xff, 0xfc, +0x3f, 0x3d, 0xe1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0xc7, 0x40, 0xc3, +0xb9, 0xa9, 0x3c, 0x2d, 0x0c, 0x99, 0xf4, 0x16, +0xe9, 0x85, 0xbd, 0xe8, 0xa4, 0x1d, 0x3a, 0x06, +0x64, 0x37, 0xae, 0x84, 0x49, 0xf0, 0x69, 0x6d, +0xc0, 0x02, 0x25, 0x04, 0xed, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0x7c, +0x25, 0x95, 0x23, 0x1b, 0xcb, 0xe1, 0x7d, 0x79, +0x7d, 0x10, 0x65, 0x67, 0xf0, 0x23, 0x70, 0xd1, +0x05, 0x83, 0xaa, 0xd2, 0xe7, 0x5e, 0x95, 0x41, +0x10, 0x2f, 0xfa, 0xbe, 0x95, 0x3d, 0x0b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0xe0, 0x44, 0xba, 0xc1, 0xe1, 0x0a, 0x3d, +0x5a, 0xab, 0x60, 0xd3, 0xf5, 0x4b, 0x82, 0xac, +0x9d, 0x6e, 0x0c, 0xe9, 0x3a, 0xa4, 0xb5, 0x41, +0xc8, 0x52, 0x5b, 0x89, 0x36, 0x98, 0x82, 0x45, +0x11, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0x85, 0xb8, 0x84, 0x0f, 0x7b, +0xf9, 0xd0, 0xa0, 0xbf, 0xdb, 0xd6, 0xec, 0x76, +0x17, 0x15, 0x9f, 0x4a, 0xae, 0x8a, 0x55, 0x58, +0x8d, 0xcb, 0x94, 0xdf, 0x43, 0xd8, 0xad, 0xca, +0xbe, 0x0b, 0xad, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0xd4, 0x5d, 0x98, +0x79, 0xc1, 0xfc, 0x14, 0xf8, 0x93, 0xd1, 0x97, +0xf2, 0x9b, 0xe7, 0x0a, 0x7b, 0x7e, 0x8c, 0x01, +0x7e, 0xbb, 0x19, 0xf9, 0x62, 0xc2, 0x98, 0xed, +0x06, 0xe4, 0xbf, 0x06, 0x29, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0x4e, +0x9c, 0x4f, 0x81, 0xdd, 0x11, 0x65, 0xb9, 0x27, +0xb3, 0x77, 0xbc, 0x8c, 0x1d, 0xdf, 0xf5, 0xc9, +0x28, 0x8c, 0xfc, 0xe7, 0xfe, 0x57, 0x5a, 0x7f, +0x3b, 0x7b, 0xb9, 0x41, 0x9f, 0x96, 0x2d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0x29, 0x1f, 0xf1, 0xa7, 0x79, 0x63, 0xb0, +0xb2, 0x05, 0x56, 0xdf, 0xf5, 0x2b, 0x74, 0x1c, +0x84, 0x80, 0x17, 0x25, 0x21, 0xb9, 0x03, 0xb3, +0xe0, 0x3e, 0xe6, 0x1c, 0xe6, 0x2c, 0x4a, 0x83, +0x27, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0xcd, 0x91, 0xaa, 0x9e, 0xf9, +0xed, 0xc3, 0xe1, 0xdd, 0x3e, 0xd6, 0x5d, 0xe3, +0xed, 0x60, 0x04, 0x50, 0x66, 0x85, 0x79, 0x97, +0x51, 0x1d, 0xeb, 0xfa, 0xe7, 0x4e, 0xcc, 0x73, +0x30, 0x07, 0x35, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x46, 0xce, 0x9f, +0x0e, 0x44, 0xd1, 0xa8, 0x02, 0xe9, 0x74, 0x43, +0x98, 0xbf, 0xbc, 0x6f, 0x27, 0x36, 0x8c, 0x94, +0x0b, 0x5a, 0x2c, 0x41, 0x43, 0x47, 0x81, 0x77, +0xc1, 0xbc, 0x28, 0xaa, 0xbd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0x06, +0xc2, 0x72, 0x08, 0x6b, 0x92, 0xe2, 0x62, 0x9f, +0xc3, 0x1d, 0x21, 0xb6, 0x55, 0x00, 0x3c, 0x38, +0x3b, 0x3f, 0xbe, 0xc4, 0x16, 0xa7, 0x0f, 0xda, +0x8a, 0x25, 0x59, 0xd7, 0xe2, 0x32, 0x2b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0xc0, 0xa1, 0xea, 0x74, 0xb3, 0x96, 0xe9, +0x17, 0x3f, 0xaf, 0xe5, 0x6d, 0x11, 0x87, 0xb1, +0x95, 0x14, 0xc5, 0x4d, 0x82, 0xeb, 0x7a, 0xa4, +0x3f, 0x5c, 0x05, 0xbf, 0x7c, 0xf6, 0x81, 0xaf, +0x9f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0x01, 0x85, 0x9f, 0xf8, 0x4b, +0xfb, 0x0e, 0x03, 0xd3, 0xcb, 0x38, 0x45, 0x2b, +0xae, 0x2f, 0x1e, 0xca, 0x1c, 0x1f, 0x5a, 0x35, +0x7c, 0x38, 0x4f, 0x9b, 0xe1, 0x01, 0xdd, 0x34, +0xe4, 0x71, 0x29, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0xa5, 0xf6, 0x28, +0xa5, 0xb1, 0x33, 0x45, 0xa8, 0x75, 0x23, 0x57, +0x17, 0x37, 0x80, 0xb6, 0x51, 0x60, 0xf3, 0xf9, +0x0b, 0x74, 0x85, 0xe3, 0x21, 0x7a, 0x07, 0x5d, +0x04, 0x1b, 0x0c, 0x45, 0xa7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0x59, +0x48, 0x9b, 0x94, 0x72, 0x8f, 0x43, 0x4d, 0x1c, +0xd8, 0xa5, 0xf6, 0xa9, 0x95, 0xf6, 0x18, 0x68, +0xc8, 0x84, 0xe7, 0x78, 0x12, 0x2f, 0xe5, 0x27, +0x47, 0x30, 0x0f, 0x39, 0x43, 0xda, 0x80, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0xa1, 0xa5, 0xc3, 0xd9, 0x25, 0x00, 0x0c, +0x26, 0x83, 0x4e, 0xfc, 0xc8, 0x8b, 0x09, 0xdb, +0x15, 0xb3, 0x66, 0xea, 0x95, 0x77, 0x20, 0xed, +0xb3, 0xd6, 0xee, 0x56, 0x37, 0x92, 0xa6, 0x5d, +0xa7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x5f, 0xcc, 0xae, 0x0f, 0x7c, +0x7f, 0xaa, 0x94, 0x85, 0xcb, 0x8b, 0x71, 0x05, +0xbb, 0x0b, 0xb6, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x20, 0x88, 0x9f, 0xc5, +0x8e, 0x34, 0x59, 0x11, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0xa5, 0x39, 0xe9, +0xd3, 0x6c, 0xd8, 0xc4, 0xf5, 0xdb, 0x1f, 0x79, +0xe1, 0x86, 0x07, 0x23, 0x66, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xb9, +0x56, 0xfc, 0x6d, 0x07, 0x0b, 0x81, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0x1b, +0x6d, 0xc6, 0x09, 0xc4, 0x25, 0x73, 0x49, 0x48, +0x10, 0x36, 0x79, 0x07, 0x0f, 0x0a, 0x91, 0x41, +0x23, 0xc1, 0xf2, 0x1a, 0x81, 0x37, 0x6b, 0xd0, +0x3e, 0x45, 0x22, 0x62, 0x02, 0xa0, 0x05, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0x34, 0xab, 0x88, 0x6a, 0xb3, 0xaf, 0xa5, +0xe9, 0xf4, 0x58, 0xfb, 0x66, 0x44, 0xf3, 0x6a, +0x8a, 0x45, 0xd2, 0x52, 0x99, 0x77, 0xdc, 0x8e, +0x9e, 0xe0, 0x54, 0xb5, 0xb3, 0x04, 0x93, 0x85, +0xed, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0x3e, 0x42, 0xeb, 0x49, 0xff, +0x7f, 0x65, 0xdc, 0xba, 0xd7, 0x33, 0x2d, 0xbe, +0x0a, 0xeb, 0xf0, 0x96, 0x1b, 0xbc, 0xf1, 0x45, +0xf2, 0x61, 0x87, 0x3a, 0x61, 0x2f, 0x24, 0xb4, +0x32, 0x98, 0xd3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0xc1, 0x73, 0xde, +0x1a, 0x97, 0x8d, 0xd0, 0x12, 0x27, 0x49, 0x00, +0xb8, 0x8a, 0x45, 0xfa, 0xdd, 0x04, 0x09, 0x9d, +0x98, 0x4e, 0x19, 0x1e, 0xec, 0x7d, 0x44, 0x65, +0x56, 0xc8, 0x1c, 0x4f, 0x49, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0x59, +0x29, 0x40, 0x49, 0x56, 0x1d, 0xbd, 0xef, 0xda, +0x72, 0xb6, 0x3a, 0x75, 0x4c, 0xc6, 0x2c, 0x9a, +0xb2, 0x71, 0xc1, 0x0a, 0x12, 0xbe, 0xf2, 0xe5, +0x76, 0x00, 0xa7, 0xc3, 0x6f, 0xbb, 0xa1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0xd3, 0xa4, 0xea, 0x19, 0x8d, 0x3b, 0xc1, +0xee, 0x71, 0x7b, 0xf0, 0x65, 0xee, 0xb3, 0x85, +0xe6, 0x35, 0x54, 0xbb, 0x9f, 0x57, 0x82, 0xb1, +0x4e, 0x9d, 0x7e, 0xec, 0xb8, 0x2f, 0xfa, 0x51, +0x30, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0x0d, 0x1b, 0xe5, 0x1c, 0xfd, +0xfe, 0x7a, 0x69, 0xb6, 0xb7, 0x96, 0x36, 0x67, +0xa0, 0x6b, 0x76, 0xd0, 0xb7, 0xcc, 0xe3, 0x78, +0xfb, 0x2e, 0xcb, 0xc6, 0x49, 0x98, 0xd6, 0x42, +0x56, 0x6d, 0x1c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0xa9, 0x0a, 0xe2, +0xbf, 0xa1, 0xde, 0x23, 0x36, 0xa2, 0xe2, 0xeb, +0x4e, 0x6f, 0x50, 0x9c, 0xda, 0x3e, 0x37, 0x56, +0xda, 0x51, 0xdd, 0x55, 0xea, 0x6c, 0xb8, 0xcb, +0xd4, 0xea, 0x1b, 0x61, 0xa9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0x7a, +0x42, 0xc4, 0x66, 0xb2, 0x6b, 0x00, 0xee, 0xd8, +0x15, 0xa9, 0xb0, 0x71, 0x21, 0xdd, 0x93, 0x91, +0xa2, 0x59, 0x37, 0xb9, 0x37, 0x4b, 0x83, 0x1a, +0x54, 0xc2, 0x95, 0x7e, 0xe1, 0x91, 0xe3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0x4c, 0xcc, 0x92, 0x09, 0x32, 0x10, 0x2d, +0x55, 0xa0, 0x4c, 0x6d, 0xa3, 0xaa, 0x77, 0x82, +0x6d, 0x54, 0x31, 0x2c, 0xd4, 0xbe, 0x46, 0x08, +0x0b, 0x36, 0xda, 0x3c, 0xc1, 0xfd, 0x4c, 0x06, +0x41, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0xc8, 0xa2, 0x46, 0x4e, 0x27, +0x4b, 0x47, 0x4d, 0x2f, 0x5c, 0xad, 0x63, 0xb9, +0x72, 0x1a, 0x74, 0xc5, 0x43, 0xbe, 0x58, 0xdf, +0xec, 0x08, 0x4a, 0x10, 0xbe, 0x0c, 0xc9, 0xf6, +0xdb, 0xc3, 0x2d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0xe4, 0xb1, 0x11, +0x3f, 0x15, 0x3b, 0x65, 0x20, 0x7a, 0x96, 0x88, +0x7b, 0x02, 0xce, 0x66, 0x26, 0xc5, 0x7c, 0x10, +0x7a, 0xec, 0xf5, 0x4e, 0x0c, 0x34, 0x43, 0x11, +0xea, 0xda, 0x58, 0x82, 0x51, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0x6c, +0xa2, 0x9f, 0x86, 0x97, 0x4e, 0xf0, 0x50, 0xd3, +0xe0, 0xf9, 0x66, 0xaa, 0x9b, 0xcf, 0x71, 0xbb, +0xd2, 0x6a, 0xbb, 0xeb, 0x7f, 0x4a, 0x06, 0x1d, +0xc3, 0x1b, 0xf1, 0x01, 0x30, 0x7d, 0xb3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0x5a, 0x37, 0x76, 0xc3, 0x15, 0x2d, 0x03, +0xd6, 0xaf, 0x64, 0xa7, 0xd6, 0x7e, 0x15, 0x64, +0x4b, 0xd1, 0x72, 0x88, 0xba, 0x20, 0x97, 0x6c, +0x3f, 0xf1, 0x76, 0xfe, 0x24, 0x39, 0x82, 0x26, +0xdc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0x91, 0xb1, 0x83, 0xae, 0x4c, +0x92, 0x36, 0xb4, 0x46, 0x56, 0x30, 0xd7, 0xa1, +0x0e, 0xbe, 0xe5, 0x7a, 0xac, 0x1e, 0xec, 0x94, +0x29, 0x05, 0x48, 0x77, 0x33, 0x32, 0x6f, 0x5d, +0xa3, 0x0b, 0xee, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0x49, 0xb6, 0x76, +0xca, 0x66, 0xbf, 0xdd, 0xfe, 0x19, 0xa9, 0x37, +0x59, 0xf4, 0x19, 0xc4, 0x8e, 0xc8, 0xad, 0x13, +0x9b, 0x1e, 0x4f, 0xd4, 0xf0, 0xb9, 0xb0, 0x4b, +0x63, 0xdd, 0x49, 0x77, 0x58, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0x09, +0xc3, 0x5f, 0x9f, 0x1b, 0x74, 0xb3, 0x54, 0x42, +0x3e, 0xa3, 0xc0, 0xf6, 0xb8, 0xcc, 0x50, 0x2a, +0x85, 0xf2, 0xa7, 0x15, 0x34, 0x10, 0xa5, 0x49, +0xe6, 0xbc, 0x99, 0xb6, 0x9f, 0x74, 0x3f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0x03, 0xf9, 0x72, 0xcb, 0xb2, 0xeb, 0x7a, +0x8c, 0xd7, 0x62, 0xbb, 0xf2, 0x83, 0x74, 0x0e, +0x80, 0xcd, 0xad, 0xab, 0x97, 0xb7, 0x33, 0x29, +0x97, 0x5a, 0xe6, 0xbf, 0x91, 0x69, 0x18, 0xa7, +0xf7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0xab, 0xa5, 0xf5, 0x11, 0x53, +0x44, 0xd7, 0x87, 0x26, 0x71, 0xe6, 0xd6, 0xae, +0xf3, 0xbc, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x19, 0xb9, 0x1c, 0x7e, +0xa8, 0xda, 0xb8, 0xe0, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0xec, 0x13, 0x5e, +0x26, 0xbf, 0xf6, 0x37, 0xa3, 0xd7, 0xfd, 0x48, +0xdb, 0x08, 0x20, 0xd4, 0xd9, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x11, +0x68, 0xa3, 0x9d, 0x3b, 0xab, 0x5b, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0xee, +0xdf, 0x8e, 0x4e, 0x71, 0x7b, 0x7c, 0xaf, 0x3d, +0x7e, 0x1e, 0xbf, 0x8e, 0xc7, 0x9e, 0x66, 0x3e, +0xbf, 0x51, 0x52, 0x01, 0x48, 0x24, 0xf0, 0xa3, +0x57, 0x0b, 0x16, 0x95, 0x5f, 0x6a, 0x43, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0xf1, 0xd5, 0x23, 0x38, 0x6f, 0xe8, 0x73, +0x9a, 0x4e, 0x7b, 0xc5, 0x88, 0x35, 0x9f, 0x42, +0x7d, 0xa3, 0x9d, 0xa9, 0xd2, 0x07, 0x95, 0x9d, +0xa2, 0x53, 0xdf, 0x99, 0x29, 0x3b, 0x1f, 0x60, +0x3a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0x50, 0x7b, 0x38, 0xca, 0x9a, +0xb8, 0x68, 0xdd, 0xad, 0x98, 0xcb, 0x2c, 0xa3, +0x20, 0x94, 0xa2, 0x43, 0x57, 0x58, 0x22, 0xf1, +0x63, 0xb0, 0x36, 0x34, 0x94, 0x93, 0x33, 0xf6, +0xa6, 0x07, 0xee, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0xc2, 0x21, 0x9b, +0xce, 0xc3, 0xa0, 0x87, 0x0d, 0x6d, 0xfe, 0xd4, +0x4c, 0x9f, 0x87, 0x94, 0xd1, 0xaf, 0x97, 0x3c, +0xae, 0x08, 0xfa, 0x8e, 0x12, 0x63, 0xa7, 0xea, +0xfd, 0x34, 0x90, 0x5a, 0xce, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0xd4, +0x28, 0x58, 0x88, 0x1a, 0x88, 0x12, 0xf3, 0x95, +0x1a, 0xe7, 0xd4, 0x1d, 0x33, 0xcd, 0xc2, 0x07, +0x8c, 0x98, 0xd5, 0x38, 0x7e, 0x6c, 0xaf, 0x0e, +0x63, 0xd1, 0xbc, 0x9e, 0x71, 0x5c, 0x2a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0xab, 0x92, 0xe8, 0x08, 0x87, 0x1d, 0xc4, +0x2d, 0xc5, 0x37, 0x89, 0xb3, 0xff, 0xb6, 0xd0, +0xfb, 0xb2, 0x83, 0xeb, 0xf7, 0x7b, 0x5c, 0x1c, +0xd4, 0x71, 0x53, 0x05, 0x6a, 0x63, 0x32, 0x93, +0x83, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0x1d, 0xbb, 0xb6, 0x2b, 0x6b, +0x4e, 0xca, 0xd1, 0xe3, 0x81, 0xa3, 0x42, 0x8c, +0x4c, 0x4c, 0x60, 0x0f, 0x83, 0xaf, 0xe3, 0x1b, +0x9d, 0x6b, 0x3a, 0x8e, 0x95, 0x0a, 0x95, 0xb7, +0x0c, 0xe7, 0x12, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0x9d, 0x8f, 0x41, +0x5a, 0x81, 0x4e, 0x1c, 0x8b, 0x6d, 0xb9, 0xab, +0xb4, 0x33, 0xc8, 0xfd, 0x54, 0x24, 0x5e, 0x5c, +0x51, 0x9d, 0x47, 0xc9, 0xef, 0xff, 0x5e, 0x00, +0x56, 0xfb, 0xd5, 0x46, 0x53, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0x13, +0x57, 0x9a, 0x94, 0x0b, 0x13, 0x86, 0xd4, 0x59, +0xa5, 0x66, 0x6f, 0xfe, 0x8a, 0x45, 0x1a, 0x1b, +0x8d, 0x6e, 0x88, 0x52, 0x6d, 0x2f, 0xf4, 0x34, +0xcc, 0x33, 0x9a, 0xcb, 0x31, 0xcd, 0x69, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0xe5, 0x99, 0x6f, 0xab, 0x10, 0x22, 0xd4, +0x3d, 0xa7, 0x22, 0x48, 0xfb, 0x7f, 0xc3, 0xaa, +0x97, 0xf4, 0x6a, 0x62, 0x10, 0x0f, 0x97, 0xde, +0x17, 0x77, 0x82, 0xdb, 0xbb, 0x0f, 0xc2, 0xc2, +0x1f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0x77, 0x54, 0xf2, 0x8f, 0x3f, +0x4f, 0xcd, 0x8a, 0xeb, 0x95, 0xee, 0xd2, 0x81, +0xd5, 0x37, 0x48, 0xe2, 0x22, 0x0e, 0xce, 0xb4, +0xe1, 0x05, 0x82, 0x84, 0xe3, 0xca, 0x33, 0xea, +0x24, 0x7a, 0x67, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x0b, 0x12, 0x6b, +0xfd, 0x49, 0x70, 0x93, 0x0f, 0xcf, 0xa4, 0x56, +0x5b, 0x78, 0xca, 0xdd, 0xf3, 0x24, 0x3d, 0xa9, +0x7b, 0xa8, 0x9d, 0x1f, 0x15, 0x9e, 0x7f, 0xc9, +0x62, 0x2e, 0x97, 0x1a, 0xf3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0x2d, +0x9b, 0xc8, 0x84, 0xe8, 0x5c, 0x73, 0x59, 0xc8, +0x9a, 0x20, 0x42, 0x8e, 0x5d, 0xfa, 0x7f, 0x96, +0x80, 0xf6, 0x08, 0x49, 0x19, 0x0f, 0x01, 0x96, +0x3d, 0x27, 0x20, 0xb5, 0x69, 0xf7, 0x0a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0xc3, 0xcd, 0x24, 0xdc, 0x9f, 0xc0, 0xe9, +0xd2, 0xda, 0x60, 0xb5, 0x3a, 0x0b, 0xde, 0x81, +0x2b, 0x2e, 0xe4, 0xa8, 0x1b, 0x07, 0x8b, 0xb5, +0x1a, 0x64, 0x1f, 0x91, 0xa7, 0x84, 0x3b, 0x65, +0xbd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0xfc, 0x18, 0xb3, 0xa2, 0xf2, +0xb1, 0xb2, 0xc4, 0xd8, 0xcc, 0x4e, 0x70, 0xc7, +0x5c, 0xd0, 0x87, 0x90, 0x2b, 0xbe, 0x75, 0x50, +0x27, 0xf0, 0xe2, 0x6b, 0x1a, 0x41, 0xda, 0xa0, +0xdf, 0xde, 0xb2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x72, 0xdb, 0xa7, +0xfa, 0x71, 0x7e, 0x44, 0x93, 0xb3, 0x4f, 0x26, +0x06, 0xa9, 0x67, 0x2c, 0xf9, 0x6b, 0xc0, 0xd1, +0x3d, 0xb4, 0x84, 0x34, 0x95, 0x6a, 0xe7, 0x6c, +0x0e, 0xa8, 0xa2, 0xa9, 0xa4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0x3e, +0x66, 0xa6, 0xee, 0x1b, 0x95, 0xc3, 0x66, 0x2e, +0x04, 0xea, 0xac, 0x92, 0x90, 0x0d, 0xe1, 0x08, +0xa3, 0x79, 0x1d, 0x17, 0x49, 0xe2, 0x19, 0xa5, +0x9e, 0xa6, 0xcd, 0xad, 0x13, 0x1c, 0xe2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0xe5, 0xa3, 0x27, 0xf2, 0x07, 0x06, 0xcf, +0xbd, 0x55, 0x1b, 0x99, 0x51, 0x3d, 0xd0, 0xb2, +0x01, 0x8c, 0x6d, 0xed, 0xdd, 0x6f, 0x4f, 0xe0, +0x00, 0x4f, 0x57, 0x58, 0xf2, 0xd8, 0x08, 0xdc, +0x44, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0x6b, 0x72, 0xa0, 0x7e, 0x96, +0xde, 0xa2, 0xfc, 0x1c, 0x01, 0x51, 0xb1, 0x40, +0x59, 0xef, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xf1, 0x9a, 0x78, 0x77, +0xd4, 0xe8, 0xc3, 0x5c, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0x2c, 0x1c, 0xaf, +0xaf, 0x94, 0x03, 0xc7, 0xb7, 0x85, 0x75, 0x13, +0x95, 0x77, 0x70, 0x9e, 0xe6, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x5c, +0xa5, 0x63, 0xea, 0x23, 0x70, 0x24, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x50, +0xcd, 0x73, 0xb7, 0x20, 0x8e, 0xf8, 0xe2, 0xd9, +0xff, 0x7e, 0x7b, 0xfd, 0xce, 0x06, 0xab, 0xef, +0x17, 0x19, 0x32, 0xcc, 0x87, 0x47, 0x03, 0x91, +0x64, 0x5f, 0x1d, 0xae, 0xa0, 0xf0, 0xb6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0x6a, 0x23, 0x21, 0xe4, 0xa6, 0x97, 0x86, +0xe0, 0x59, 0x42, 0x1a, 0xd6, 0x3f, 0xd6, 0xfb, +0x1c, 0xb5, 0xf1, 0x8e, 0xe2, 0xa2, 0xa4, 0x8b, +0xca, 0x22, 0x8a, 0xbb, 0x8d, 0xbf, 0x6e, 0x1e, +0xcd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0x34, 0xc9, 0x8d, 0x6b, 0xb4, +0xfe, 0x97, 0xcb, 0x80, 0xb4, 0x1d, 0xd4, 0x7c, +0xb0, 0x73, 0x97, 0xaf, 0x15, 0x81, 0xe0, 0xf5, +0x5d, 0x1c, 0x09, 0x38, 0xc7, 0x9f, 0x13, 0x16, +0xe1, 0x4e, 0x8e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0x13, 0xac, 0x0c, +0xb1, 0x3c, 0xbc, 0x54, 0x76, 0x08, 0xc4, 0xb7, +0x23, 0x8f, 0x13, 0x06, 0x6e, 0x23, 0xb9, 0x71, +0xd8, 0xea, 0xa8, 0x69, 0x3d, 0x1e, 0xa8, 0x26, +0x0f, 0xe3, 0x53, 0xf7, 0xc4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0x95, +0xc1, 0x2d, 0xe4, 0x93, 0xfb, 0xb2, 0x71, 0xcd, +0xca, 0x02, 0x8d, 0x4b, 0x8a, 0x32, 0xc7, 0xfb, +0x65, 0x4b, 0x5d, 0x03, 0x61, 0xec, 0x49, 0xad, +0xe6, 0x5e, 0x3e, 0xb3, 0x1c, 0xd2, 0xd0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0xe1, 0x7e, 0x1b, 0x90, 0x2e, 0x60, 0x75, +0x12, 0x44, 0x9a, 0x6d, 0xf9, 0x06, 0x57, 0xcf, +0x99, 0x8c, 0xdb, 0x28, 0x1d, 0x11, 0xdc, 0x9b, +0x01, 0xf5, 0x66, 0x4a, 0x6e, 0x50, 0xec, 0x49, +0xbc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0x12, 0x86, 0xd0, 0x49, 0x01, +0x15, 0x6d, 0x2a, 0x12, 0xf2, 0xdc, 0x76, 0x79, +0x9b, 0xf0, 0xf0, 0xa8, 0xf7, 0x29, 0x10, 0xa4, +0x0b, 0xc4, 0x46, 0x3d, 0xe9, 0x7a, 0xbf, 0x6b, +0xd1, 0xd9, 0xe4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0xc1, 0x56, 0x95, +0x91, 0xdc, 0xa5, 0xf9, 0xfb, 0x63, 0x2c, 0x38, +0xe1, 0x81, 0x0e, 0xdf, 0x9a, 0xb6, 0xed, 0x46, +0xc5, 0xfd, 0x59, 0xde, 0xb2, 0x62, 0x06, 0x97, +0x6c, 0x2c, 0x01, 0x49, 0x13, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0xc9, +0x9b, 0x34, 0x14, 0x09, 0xe4, 0x1f, 0x27, 0xd5, +0x35, 0x2e, 0x30, 0xb3, 0xd2, 0x7f, 0x0a, 0x69, +0x66, 0x46, 0x15, 0x4a, 0x79, 0x77, 0x1a, 0x6f, +0x90, 0xb9, 0xb1, 0x4d, 0x2b, 0x33, 0x25, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0x86, 0xff, 0x50, 0x79, 0x0a, 0xf8, 0x71, +0x4c, 0x11, 0x11, 0x78, 0x73, 0xfc, 0xe4, 0x63, +0x3f, 0xee, 0x00, 0x71, 0xb1, 0xf9, 0x03, 0xbd, +0x46, 0x9b, 0x26, 0xa1, 0xbc, 0x20, 0x67, 0x5e, +0xa5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0x5f, 0x67, 0xf7, 0x67, 0x22, +0x83, 0x62, 0x7b, 0x91, 0x9d, 0x61, 0x72, 0x4b, +0x61, 0x9e, 0x98, 0xbe, 0xc9, 0x2a, 0x75, 0x38, +0xf4, 0x34, 0xb8, 0xc2, 0x4c, 0x9f, 0x14, 0x6f, +0xb7, 0xcd, 0x72, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0xaf, 0x17, 0x18, +0xb9, 0xd4, 0x35, 0x14, 0x3b, 0xa9, 0x82, 0x3a, +0xf1, 0x9a, 0x49, 0x5a, 0x75, 0xab, 0xd7, 0x43, +0x5c, 0x35, 0xe5, 0x01, 0x46, 0x29, 0x9a, 0x7b, +0x3f, 0xc3, 0xb7, 0xbe, 0xda, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0x89, +0x9e, 0x80, 0x79, 0x74, 0x96, 0xc9, 0x89, 0x25, +0x2e, 0x94, 0xad, 0x25, 0x08, 0x00, 0x3a, 0x3e, +0xbc, 0x2f, 0xf8, 0x36, 0x7e, 0x74, 0xce, 0xa5, +0xda, 0xcf, 0xd2, 0x9a, 0x02, 0xc7, 0x68, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0xa1, 0x26, 0x49, 0x1f, 0x34, 0x65, 0x75, +0x04, 0x5c, 0xc1, 0x25, 0x24, 0x8b, 0xfd, 0x5e, +0xf3, 0x7f, 0xd7, 0xbe, 0x4a, 0x31, 0x5a, 0xa5, +0x95, 0x97, 0x52, 0x16, 0x9b, 0x4f, 0xf5, 0x89, +0x3b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0xeb, 0x54, 0x88, 0xea, 0xbe, +0x14, 0xf0, 0xbc, 0xd9, 0x2b, 0x17, 0x36, 0x4f, +0xbc, 0x48, 0x7c, 0xc7, 0x18, 0x2e, 0xbe, 0x68, +0x66, 0x66, 0xf7, 0x44, 0x4b, 0xcc, 0xd7, 0x64, +0x50, 0x4f, 0x62, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0xd4, 0x1a, 0x04, +0x1e, 0x5f, 0x8b, 0x10, 0xec, 0x66, 0xcc, 0x65, +0x16, 0xaa, 0x1e, 0xbf, 0x2a, 0xeb, 0x01, 0xfe, +0x22, 0xae, 0xa7, 0x8d, 0x2a, 0x28, 0x31, 0x47, +0xed, 0xc3, 0x19, 0x3c, 0xa1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x5a, +0x40, 0x19, 0x20, 0x4c, 0xa9, 0xcc, 0xee, 0xda, +0xd1, 0x44, 0xbd, 0x3b, 0xfe, 0x98, 0x0d, 0x63, +0xba, 0x90, 0x22, 0x3e, 0x7c, 0x15, 0x55, 0x05, +0x2c, 0xc1, 0x23, 0x47, 0x7c, 0xc9, 0x2a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x80, 0x57, 0x47, 0xe5, 0xf3, 0x7d, 0x1a, +0x50, 0x41, 0xad, 0x77, 0xe0, 0xd9, 0x37, 0xea, +0x29, 0x9a, 0x6a, 0x8a, 0xc6, 0xa1, 0xb9, 0xc2, +0x90, 0xd3, 0x4b, 0x96, 0x4b, 0x53, 0x42, 0x1a, +0x31, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0xe0, 0xc8, 0xba, 0xdd, 0x1c, +0xf2, 0xd3, 0xb1, 0xc0, 0x91, 0xf0, 0xe1, 0xcb, +0x98, 0xe2, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x88, 0xe0, 0xfd, 0x41, +0x83, 0xff, 0x07, 0xa6, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0x05, 0x37, 0xd0, +0x6e, 0x87, 0x96, 0xcf, 0x22, 0x81, 0xf1, 0x8d, +0x13, 0x06, 0x5f, 0x2e, 0x3b, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x94, +0x88, 0x12, 0x90, 0x53, 0x43, 0x3a, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0x05, +0xeb, 0xa0, 0xf1, 0x56, 0xf5, 0x15, 0x5d, 0xd4, +0x20, 0xc3, 0x2a, 0x84, 0x0b, 0x84, 0x3c, 0xd5, +0x10, 0x8e, 0x1c, 0x92, 0x2a, 0xf4, 0x68, 0xce, +0x69, 0x5a, 0xe3, 0x18, 0xfc, 0xa4, 0x1a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0xc7, 0x9d, 0xca, 0x42, 0xa6, 0x88, 0x46, +0x82, 0xb5, 0x20, 0x0c, 0xe4, 0x3b, 0x50, 0xf2, +0x40, 0x61, 0x51, 0xa7, 0x86, 0x49, 0xb0, 0x51, +0x3c, 0xe5, 0x42, 0x97, 0x27, 0x60, 0x88, 0xf2, +0x4f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0x7b, 0xf1, 0x43, 0xad, 0x15, +0xfa, 0x76, 0xfc, 0x02, 0x9e, 0x0d, 0x0a, 0x07, +0x8d, 0x29, 0x18, 0x27, 0xd2, 0xf5, 0xcc, 0xf8, +0x97, 0x51, 0x0b, 0xed, 0xb6, 0xd3, 0x9f, 0x0d, +0x58, 0x49, 0x37, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0x43, 0xce, 0x8c, +0xf2, 0x49, 0xee, 0x3b, 0x8e, 0xbe, 0xe2, 0x22, +0x60, 0x47, 0x79, 0x01, 0xc1, 0x91, 0x87, 0x33, +0x6e, 0x87, 0x82, 0x2f, 0xc4, 0xc7, 0x25, 0x83, +0x39, 0x00, 0x74, 0x6e, 0x39, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0x4a, +0x9a, 0x71, 0x11, 0xb5, 0xdd, 0xf5, 0x09, 0x39, +0xe4, 0x7c, 0xf7, 0x7e, 0xc8, 0xfa, 0x62, 0x6c, +0x12, 0xc4, 0xc5, 0x75, 0x07, 0x99, 0xf0, 0x12, +0x04, 0x51, 0x97, 0x4f, 0xed, 0x23, 0xd2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0x51, 0x37, 0xbc, 0x5f, 0x0d, 0xe3, 0x3a, +0xd7, 0xaa, 0xd3, 0x8e, 0x2a, 0x7c, 0x36, 0xe2, +0x70, 0xf2, 0x35, 0xde, 0x08, 0x2c, 0xbe, 0x38, +0xfa, 0x60, 0xa9, 0xd9, 0x3d, 0x83, 0x23, 0x11, +0x89, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0xff, 0x0d, 0xb8, 0x8a, 0x3e, +0xa5, 0xa7, 0xe3, 0xf9, 0x01, 0x11, 0x20, 0x4f, +0xe6, 0x7f, 0x66, 0x0d, 0x36, 0x22, 0x2b, 0x4e, +0x45, 0x2e, 0xc8, 0x09, 0x21, 0x2d, 0xad, 0x6e, +0x56, 0x9f, 0x47, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0x31, 0x49, 0x7d, +0xd2, 0x37, 0x2c, 0x29, 0x31, 0x23, 0x59, 0x6f, +0xa8, 0xbd, 0x61, 0xc1, 0xef, 0x47, 0xec, 0xbc, +0xff, 0x10, 0xda, 0x92, 0x59, 0xab, 0xb1, 0xdc, +0xe4, 0x95, 0x7a, 0x17, 0x12, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0x7d, +0xe1, 0x32, 0x03, 0xc7, 0x0d, 0x4a, 0x57, 0x78, +0x46, 0xac, 0x7a, 0x94, 0x4f, 0x50, 0xa6, 0x31, +0x31, 0x2a, 0xc7, 0x45, 0xba, 0xfd, 0x96, 0x11, +0xb3, 0xf6, 0x67, 0x52, 0xb0, 0xba, 0xcf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0x00, 0xd5, 0xb2, 0x15, 0x01, 0xec, 0x69, +0xf2, 0x01, 0x00, 0x46, 0x68, 0x59, 0x6a, 0x8b, +0x7b, 0x65, 0x94, 0x9b, 0x10, 0x56, 0x91, 0x75, +0x6d, 0x5e, 0x04, 0xd7, 0x4b, 0xe5, 0x0c, 0xe8, +0xdd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0x21, 0x7f, 0x8d, 0x1d, 0xed, +0x5a, 0x4f, 0x31, 0xcc, 0x9f, 0x45, 0x52, 0x7e, +0x36, 0x4a, 0xb8, 0x45, 0x9b, 0x4c, 0xf2, 0xa1, +0x2e, 0x51, 0x02, 0x73, 0x71, 0x17, 0x45, 0x5a, +0x2a, 0x39, 0x42, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0xfe, 0x47, 0xd1, +0xd2, 0x6b, 0x4b, 0x6f, 0x38, 0xe3, 0x16, 0x01, +0x4b, 0x69, 0x5f, 0xa3, 0x9b, 0x38, 0x27, 0xf9, +0xb5, 0xe9, 0x3d, 0x50, 0xc2, 0x20, 0xe5, 0x95, +0xb1, 0x14, 0xb9, 0xd6, 0xe2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0xf9, +0x44, 0x2c, 0x7c, 0x69, 0x43, 0xd9, 0x11, 0x4c, +0x22, 0xf4, 0x83, 0x69, 0x29, 0x70, 0x0c, 0xf5, +0x49, 0x75, 0x4e, 0xe7, 0x1a, 0x18, 0x2e, 0x74, +0xc7, 0xfd, 0x0f, 0xe8, 0x7f, 0xc3, 0x1b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0x2d, 0x68, 0x0d, 0x1e, 0x8b, 0x6b, 0x85, +0x40, 0x09, 0x8e, 0xa9, 0xf1, 0x3d, 0x4e, 0x60, +0x38, 0x36, 0xec, 0x46, 0x2a, 0x87, 0x5e, 0xad, +0xa8, 0x1b, 0xd0, 0xbd, 0x66, 0x85, 0x78, 0x65, +0x5b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0x4a, 0x60, 0x30, 0xe8, 0x03, +0x5b, 0xb4, 0x3c, 0x72, 0xe2, 0x77, 0x4f, 0x36, +0x26, 0xe0, 0x6c, 0xae, 0xec, 0x68, 0x4f, 0x07, +0x25, 0x61, 0x2e, 0xbd, 0x1b, 0xa1, 0x30, 0x07, +0xaa, 0x3e, 0x89, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0xa9, 0xbf, 0x2c, +0x01, 0x41, 0xf4, 0x45, 0x59, 0xf7, 0x8a, 0x9c, +0x64, 0xa1, 0x91, 0xd2, 0x6e, 0x89, 0x69, 0x10, +0xa9, 0x23, 0xf7, 0x9a, 0xe3, 0x52, 0xd4, 0x52, +0xf3, 0x6b, 0xd9, 0xc7, 0xf1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0xea, +0x11, 0x36, 0x4b, 0xa1, 0x32, 0x09, 0x99, 0xd6, +0x26, 0x9d, 0x4a, 0x28, 0xc6, 0xf6, 0xbe, 0xe0, +0x6a, 0x8f, 0x70, 0x2d, 0xff, 0x3d, 0xc9, 0x4b, +0xc1, 0x62, 0xdc, 0x29, 0xb7, 0x55, 0xe9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0x4e, 0xcd, 0x5d, 0x39, 0x1b, 0xb9, 0xaf, +0x5f, 0x02, 0x8e, 0x42, 0xc4, 0xca, 0x94, 0xcc, +0x7c, 0x74, 0x01, 0x63, 0x34, 0xc1, 0x25, 0xc6, +0x90, 0x54, 0x7f, 0xbf, 0x9f, 0x31, 0x7c, 0x24, +0x1c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0xa5, 0x23, 0xb3, 0x2f, 0xed, +0xe5, 0xac, 0xe3, 0xe9, 0x3b, 0xf7, 0xed, 0x80, +0x82, 0x1e, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xe2, 0xaf, 0xb7, 0x5a, +0x0a, 0xa8, 0x4e, 0x13, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0x3f, 0xdd, 0x6f, +0x0d, 0x4a, 0xab, 0x89, 0x67, 0xd5, 0x11, 0x34, +0x7b, 0x94, 0x3a, 0x5a, 0x18, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd4, 0x8a, +0xba, 0x35, 0xed, 0x6b, 0x06, 0xaf, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0x93, +0xee, 0x60, 0xd2, 0x3c, 0x2c, 0xca, 0xcd, 0xbe, +0xb5, 0xf6, 0x0e, 0xe0, 0x65, 0x35, 0x5c, 0xa9, +0x51, 0x2f, 0x6b, 0xf4, 0x2a, 0x00, 0x20, 0x46, +0xb5, 0x49, 0x67, 0xb3, 0x09, 0x96, 0x25, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0x98, 0xf3, 0xee, 0x80, 0x31, 0x32, 0xe5, +0xd4, 0xa4, 0x11, 0x56, 0xa2, 0xab, 0xa0, 0x05, +0xd3, 0x2d, 0x82, 0x3f, 0x16, 0xaa, 0x59, 0x04, +0x8a, 0xe2, 0x28, 0x12, 0xe1, 0xd9, 0x96, 0x41, +0x45, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0xbd, 0x8b, 0x9c, 0x6b, 0x95, +0xa2, 0xa1, 0xab, 0xfb, 0x70, 0x9f, 0x43, 0x5f, +0x3f, 0x98, 0xc9, 0x2b, 0x5b, 0xee, 0xbc, 0x31, +0xa2, 0x2f, 0x51, 0x3b, 0xda, 0x5d, 0x76, 0x45, +0xf2, 0x42, 0x05, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0xd6, 0xa3, 0xda, +0xeb, 0x98, 0x09, 0x61, 0xcd, 0xe5, 0x59, 0x2c, +0xc2, 0x8f, 0x9f, 0x54, 0xb1, 0x07, 0x25, 0xb0, +0x1e, 0x3e, 0xc3, 0x49, 0x15, 0x12, 0x1d, 0x9a, +0x70, 0x19, 0xf7, 0xab, 0x9a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0x1e, +0x7a, 0xdf, 0x05, 0xa1, 0xa6, 0x65, 0x1d, 0x7b, +0x26, 0x44, 0xd7, 0x08, 0x39, 0x1a, 0xdf, 0x6f, +0x7a, 0x84, 0x83, 0xaa, 0x98, 0x0e, 0x1b, 0x7a, +0x9a, 0xf0, 0x5c, 0x7e, 0x1a, 0x7f, 0x35, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0x0c, 0xdb, 0x0a, 0xe5, 0xbe, 0x97, 0x79, +0x78, 0xaf, 0x24, 0x70, 0x0d, 0x1e, 0x5b, 0xf6, +0xfc, 0x95, 0x5c, 0xe7, 0x8e, 0xc9, 0xd2, 0x0b, +0xef, 0x55, 0x7b, 0x55, 0xe9, 0x51, 0x8a, 0x72, +0xf9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0xd2, 0xeb, 0x0c, 0x3f, 0x81, +0xb8, 0x3e, 0x42, 0x16, 0xf6, 0x9e, 0xa9, 0xe8, +0xf7, 0xc5, 0xdb, 0x7b, 0xca, 0x26, 0xa1, 0xb7, +0xc9, 0xe4, 0x86, 0xda, 0xc0, 0x5a, 0x71, 0xb5, +0xe0, 0x0f, 0xd8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0xde, 0x63, 0x7c, +0x29, 0x73, 0xc0, 0xb4, 0x19, 0x15, 0x08, 0x46, +0x5a, 0x11, 0x19, 0x70, 0x7b, 0x61, 0xdb, 0x15, +0xec, 0x14, 0x5d, 0x11, 0x26, 0x6c, 0x75, 0x91, +0xd4, 0xda, 0x93, 0x36, 0x0e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0xcd, +0x0c, 0x84, 0x2a, 0x35, 0x7a, 0x1e, 0x2f, 0x3c, +0x60, 0x0f, 0x3a, 0x38, 0xc6, 0xf2, 0xdd, 0x5e, +0x4e, 0xa7, 0xad, 0xe0, 0x7a, 0x0c, 0x56, 0x87, +0xf5, 0x82, 0x75, 0xc8, 0x16, 0xf2, 0x4a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0xab, 0xb6, 0x8a, 0xc0, 0x9c, 0x12, 0x74, +0xaa, 0xbd, 0x5d, 0xed, 0x67, 0x75, 0xf2, 0x08, +0x32, 0xc9, 0xfd, 0x3e, 0x55, 0x98, 0x62, 0xdb, +0xc5, 0x2c, 0x57, 0xac, 0xe6, 0x14, 0x5a, 0xca, +0x63, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0xa9, 0xdb, 0x94, 0x1b, 0x82, +0xa9, 0x53, 0x55, 0x58, 0xc2, 0x5c, 0x5f, 0xe7, +0x92, 0xc8, 0x1b, 0x80, 0x05, 0x24, 0xfb, 0xa1, +0x72, 0xc3, 0x62, 0x59, 0x76, 0xa0, 0x55, 0x9f, +0x02, 0xae, 0x3b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0xfc, 0x99, 0xa8, +0x95, 0x10, 0xd8, 0xa0, 0xb1, 0x9f, 0x68, 0x23, +0x76, 0x87, 0xdd, 0x40, 0x21, 0x07, 0x0b, 0xc5, +0x22, 0x67, 0x04, 0xb3, 0x55, 0x5a, 0xfc, 0x6b, +0x6c, 0xa6, 0x19, 0x19, 0xae, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0xc5, +0x30, 0xd7, 0x55, 0x52, 0x0e, 0x2d, 0x08, 0x60, +0x49, 0x80, 0xba, 0x4e, 0xf7, 0xb7, 0x54, 0x2d, +0xbd, 0x6f, 0x91, 0xd3, 0x1c, 0x1f, 0x1c, 0xe2, +0x6d, 0x72, 0x0b, 0x8b, 0x87, 0xa5, 0x06, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0xbf, 0x59, 0x56, 0xc0, 0x24, 0xe7, 0xff, +0xf3, 0xdd, 0x4b, 0x1c, 0xb8, 0xae, 0x9f, 0x18, +0xb2, 0x44, 0x95, 0x08, 0xb3, 0x53, 0x69, 0xe7, +0xb9, 0x38, 0x72, 0xb3, 0xe1, 0xe6, 0x38, 0x9a, +0x3b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0xc9, 0xff, 0xb4, 0xe1, 0x9d, +0x5a, 0x9a, 0xe6, 0xfc, 0xce, 0x65, 0x31, 0xac, +0x4a, 0x8c, 0xab, 0xd1, 0x3f, 0xd8, 0x42, 0xc2, +0x15, 0xf4, 0x46, 0x9a, 0xd1, 0x6c, 0x87, 0x00, +0x99, 0x16, 0xd1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0x55, 0xbb, 0xe4, +0x1a, 0xa0, 0xb2, 0x59, 0x4f, 0x84, 0x92, 0xe8, +0x3c, 0xe5, 0xbe, 0x95, 0x08, 0x75, 0x3d, 0xa1, +0x90, 0x40, 0x8e, 0x5f, 0x49, 0x5f, 0x8f, 0x66, +0x55, 0x5b, 0x24, 0xed, 0x32, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x83, +0xc1, 0xbd, 0x0d, 0x8b, 0xc9, 0x97, 0xc7, 0x19, +0xba, 0x9d, 0x92, 0x24, 0xb5, 0xfb, 0xc1, 0x4c, +0xc4, 0xb9, 0x6b, 0x9f, 0xca, 0x62, 0x60, 0x2d, +0x96, 0xd9, 0xd2, 0x83, 0xd5, 0x96, 0x31, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0xef, 0xc9, 0xbb, 0xb8, 0x11, 0xff, 0x04, +0x6a, 0x9e, 0xf8, 0xef, 0x27, 0xb9, 0x37, 0x36, +0xfb, 0x22, 0x1c, 0x7d, 0xde, 0xac, 0xb3, 0x6c, +0x76, 0xdb, 0x11, 0xb1, 0xfb, 0x31, 0x3f, 0xce, +0x8e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0xfe, 0x7d, 0xb3, 0xd9, 0x8b, +0x07, 0xcb, 0x5f, 0x32, 0xfc, 0x60, 0x0f, 0x92, +0x35, 0x31, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xd3, 0x87, 0x53, 0xb6, +0xe8, 0x43, 0xf7, 0x2e, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0x31, 0x24, 0x10, +0x61, 0x15, 0xd8, 0x6c, 0xdd, 0x11, 0xb1, 0xa9, +0xed, 0x15, 0xe9, 0xd0, 0x43, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xf0, +0x46, 0xe9, 0x4f, 0x55, 0x90, 0xef, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x62, +0x25, 0x16, 0xa2, 0x32, 0x76, 0x08, 0xdb, 0x4f, +0xe3, 0xd1, 0x8f, 0x34, 0xf8, 0x9d, 0xa6, 0xbf, +0xa4, 0xd5, 0x51, 0x86, 0x26, 0x0c, 0x36, 0x3e, +0x89, 0x07, 0x97, 0x87, 0x76, 0x05, 0xbd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0x6a, 0x3a, 0x52, 0xe8, 0x73, 0xc3, 0x6a, +0x46, 0x6e, 0x9a, 0x28, 0xd0, 0xa3, 0x77, 0xc4, +0xfd, 0xdb, 0x66, 0x52, 0x3f, 0x7f, 0x79, 0xd8, +0x36, 0xd1, 0x06, 0x77, 0x64, 0xac, 0x17, 0xd9, +0x9a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0x9b, 0xf8, 0x1c, 0xbc, 0x98, +0xfd, 0x41, 0x62, 0x1f, 0x75, 0x8d, 0x1b, 0x74, +0xce, 0xd8, 0x46, 0xa4, 0x97, 0x6d, 0x30, 0x97, +0x9d, 0x70, 0x9f, 0x3f, 0x05, 0xc1, 0x4b, 0xcc, +0x31, 0xe4, 0x93, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0xbd, 0x4a, 0x85, +0xd1, 0x74, 0x8c, 0x87, 0xe7, 0x46, 0xd1, 0xd9, +0x6c, 0xb0, 0xe3, 0x56, 0xc8, 0xf4, 0x60, 0x67, +0x5a, 0x6f, 0x08, 0x40, 0x73, 0x36, 0x3b, 0xc3, +0xc6, 0x9c, 0xea, 0x83, 0xed, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x50, +0x81, 0x91, 0xfd, 0x6a, 0x5f, 0x27, 0x29, 0x50, +0x9c, 0x36, 0x01, 0xba, 0x78, 0x04, 0x8d, 0x3c, +0xe8, 0xb7, 0x9e, 0xcd, 0x59, 0x87, 0xc3, 0xed, +0xd4, 0x9c, 0xc9, 0x4f, 0x57, 0x6f, 0x41, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0xb7, 0x11, 0x9a, 0xb6, 0x17, 0x39, 0x57, +0xdf, 0x10, 0x3a, 0xcc, 0xc3, 0xb8, 0x5b, 0x1a, +0xa3, 0x18, 0xae, 0xe4, 0x67, 0x87, 0xce, 0xf4, +0xfe, 0x97, 0xff, 0x7f, 0x38, 0x85, 0x72, 0x2e, +0xe1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0x0a, 0x75, 0xe4, 0xdd, 0xfc, +0xfa, 0x6e, 0x9a, 0x47, 0x50, 0x8f, 0x75, 0x9b, +0xcf, 0x7e, 0x5e, 0x33, 0xcf, 0xc3, 0xba, 0x8f, +0xe0, 0x05, 0xaa, 0x9a, 0x0b, 0x8e, 0x0f, 0x01, +0x16, 0x6e, 0x30, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x5b, 0xa5, 0x39, +0xbb, 0x4d, 0x22, 0x6a, 0xe2, 0x47, 0x12, 0x97, +0x31, 0x32, 0x4b, 0xd9, 0x6f, 0xe3, 0xbb, 0xdf, +0x7f, 0xb5, 0x4a, 0x3b, 0xb8, 0xf9, 0xe1, 0x71, +0x2c, 0x59, 0xe3, 0x04, 0x38, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0xf4, +0x22, 0xea, 0xfc, 0x46, 0x8a, 0xdf, 0x4c, 0x23, +0x25, 0x8b, 0xb2, 0xe8, 0x4e, 0x71, 0xba, 0x09, +0x9d, 0xa8, 0x91, 0xbd, 0x03, 0xa3, 0x38, 0x1b, +0x5c, 0x5c, 0x69, 0x56, 0xf6, 0x9f, 0x2c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0xb3, 0x0a, 0x29, 0xfa, 0x53, 0x3c, 0x92, +0xc4, 0x9c, 0x0e, 0x4c, 0x48, 0x19, 0x36, 0xd8, +0x87, 0xe6, 0x78, 0x86, 0x6d, 0xbb, 0xda, 0x93, +0xbd, 0xee, 0xed, 0x21, 0x82, 0x5e, 0x4e, 0x9b, +0x3f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0x9e, 0x2b, 0xee, 0x01, 0x73, +0x06, 0xa8, 0xc2, 0x75, 0x59, 0x47, 0xa5, 0xf8, +0x19, 0x5a, 0x2b, 0xb8, 0xa0, 0xf5, 0xa4, 0x99, +0x6e, 0x82, 0xc6, 0xe1, 0xd9, 0x4c, 0x1a, 0xad, +0xb9, 0x73, 0x58, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0x32, 0x79, 0x00, +0x5c, 0xe0, 0xcc, 0xa6, 0x3e, 0xf5, 0xbd, 0x47, +0x72, 0x4a, 0x5c, 0xfa, 0x8f, 0xf7, 0x18, 0x34, +0xd2, 0x78, 0xa9, 0xa9, 0xc7, 0x42, 0xa5, 0x85, +0x1e, 0x49, 0x80, 0x52, 0x24, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0x68, +0x9a, 0x8f, 0xb2, 0xaf, 0x6a, 0x10, 0x76, 0xc0, +0x57, 0xda, 0x37, 0x6e, 0x74, 0x4e, 0xbb, 0xf3, +0xc5, 0x96, 0xc1, 0x40, 0xec, 0xfa, 0xc7, 0x3f, +0xc4, 0x0f, 0x08, 0x14, 0x92, 0x9b, 0x92, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x1f, 0x21, 0x7f, 0xfa, 0xd2, 0xf9, 0x7c, +0x9e, 0x2f, 0xfc, 0x9a, 0x3a, 0x34, 0x1a, 0x13, +0x82, 0xcd, 0xbf, 0xf9, 0x83, 0x9d, 0x83, 0xaa, +0xe4, 0x77, 0x59, 0xa4, 0x1b, 0x7c, 0x13, 0x7c, +0x1d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0xf0, 0x44, 0xc3, 0x47, 0x7c, +0x1c, 0xb7, 0x0c, 0xa0, 0xdf, 0x10, 0x46, 0x1f, +0x80, 0x7a, 0x07, 0x11, 0x0a, 0x62, 0x02, 0x37, +0x22, 0xea, 0x50, 0x4a, 0xd8, 0x31, 0x9b, 0xa3, +0xda, 0xf7, 0x02, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0xeb, 0xa8, 0x13, +0x3f, 0x8d, 0xcb, 0x82, 0x76, 0x3e, 0xed, 0xc7, +0x29, 0x6e, 0x02, 0x05, 0x66, 0xc9, 0x62, 0x22, +0x2d, 0x06, 0x75, 0x7c, 0x63, 0x0b, 0x77, 0x18, +0x5e, 0xd0, 0xcf, 0xe5, 0x6e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0x05, +0x84, 0xfb, 0xc7, 0x18, 0xec, 0xc7, 0xe0, 0xfa, +0xe6, 0x01, 0x4b, 0x2e, 0x01, 0x1c, 0x9f, 0x5e, +0xeb, 0x47, 0xdd, 0x73, 0x61, 0x79, 0xea, 0x8a, +0xc6, 0xbf, 0x5c, 0x2f, 0x7e, 0x25, 0x92, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x67, 0xad, 0x42, 0xe6, 0x46, 0xd1, 0x08, +0x03, 0xae, 0xd9, 0x27, 0xd3, 0xd1, 0xdd, 0x9c, +0xa9, 0xe4, 0xbd, 0x71, 0xec, 0x83, 0x1c, 0x5d, +0x2f, 0xd3, 0xfc, 0x1c, 0x69, 0xfd, 0x25, 0x89, +0xb9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0x53, 0xcd, 0x5c, 0x1b, 0x4d, +0x25, 0xfd, 0xcf, 0xe1, 0x56, 0xd9, 0xd9, 0x1f, +0x70, 0x9b, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x16, 0x23, 0xb4, 0xed, +0x21, 0x68, 0xb9, 0x16, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0xbc, 0x71, 0x5c, +0x67, 0xa1, 0xd0, 0x8c, 0x18, 0xf6, 0x94, 0x8a, +0xb4, 0x60, 0xda, 0x12, 0x84, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0xf2, +0xc5, 0x8f, 0x08, 0xef, 0xcc, 0xb1, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x51, +0x6d, 0xa4, 0x41, 0xbc, 0xa4, 0x8c, 0x49, 0xc0, +0x20, 0xed, 0x29, 0xc6, 0x14, 0x12, 0x98, 0x99, +0x52, 0xed, 0x83, 0xc2, 0x93, 0xd2, 0x24, 0xf1, +0x00, 0x35, 0x6c, 0xd2, 0x2c, 0x72, 0xca, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0x27, 0xa7, 0xb2, 0x23, 0x81, 0xb4, 0xc2, +0x15, 0x63, 0x58, 0x0e, 0x20, 0xd2, 0xaa, 0x51, +0x05, 0x53, 0x88, 0x91, 0xda, 0x5c, 0xf4, 0x38, +0x8f, 0x8c, 0x4c, 0x0c, 0xd8, 0x02, 0xbc, 0x3e, +0x28, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0x96, 0x57, 0x58, 0xd6, 0x34, +0x20, 0xaf, 0x35, 0x15, 0x14, 0xc9, 0xae, 0x4c, +0x39, 0x38, 0xef, 0xd0, 0x0e, 0x2d, 0x55, 0xe5, +0x69, 0x5e, 0x6b, 0xac, 0x12, 0x4b, 0x4c, 0x48, +0xcd, 0x03, 0xd0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0xed, 0x74, 0xa2, +0xe5, 0xd6, 0x4b, 0x79, 0x97, 0x4f, 0x53, 0x1e, +0x7f, 0x94, 0x4a, 0xf7, 0x2f, 0xf1, 0x74, 0x0c, +0x69, 0x3b, 0x90, 0x23, 0x64, 0x9f, 0x3d, 0x8e, +0xad, 0x69, 0x3c, 0x21, 0xb4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0x7a, +0x40, 0x76, 0x82, 0xac, 0xaa, 0x48, 0xbe, 0xee, +0x5c, 0x33, 0x36, 0x13, 0xe5, 0x95, 0x29, 0xbe, +0x7a, 0xfc, 0xb3, 0xf4, 0xe0, 0xf9, 0x5f, 0x48, +0x30, 0x20, 0x5b, 0x70, 0x50, 0x51, 0xe1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0x50, 0xd2, 0xd0, 0x06, 0x29, 0xde, 0x02, +0x2c, 0x4d, 0xaf, 0x99, 0x5b, 0x28, 0x23, 0xbe, +0x3c, 0xe2, 0x0a, 0xa6, 0x4c, 0xf5, 0x96, 0x28, +0xe1, 0x8b, 0x26, 0xb0, 0xfc, 0x01, 0x6c, 0xe9, +0x5e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0x21, 0xfd, 0x17, 0xc1, 0x6b, +0xb2, 0x82, 0x97, 0xb9, 0x3a, 0x00, 0x18, 0x9f, +0x7d, 0xd5, 0x09, 0x16, 0x45, 0x8b, 0x81, 0x57, +0x73, 0xcb, 0x7d, 0x6e, 0x25, 0xf6, 0x70, 0x8d, +0x2a, 0xd9, 0x5c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0x8b, 0xa1, 0xd7, +0xf1, 0x8c, 0x06, 0x7a, 0x8a, 0x2c, 0x16, 0x80, +0xf9, 0xe0, 0xe1, 0xf7, 0x9d, 0x13, 0x62, 0xac, +0x41, 0x0a, 0x81, 0x3d, 0xf6, 0xa1, 0xab, 0x44, +0xa4, 0xa4, 0x5c, 0x28, 0x82, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0x13, +0x28, 0x59, 0x75, 0xdb, 0xec, 0xc9, 0xe3, 0xff, +0xef, 0x9d, 0xdd, 0x02, 0x34, 0x98, 0x0b, 0xfc, +0x19, 0x99, 0xdc, 0xe2, 0x2d, 0xf6, 0xe7, 0x7f, +0x54, 0x73, 0xae, 0xe0, 0xdc, 0x05, 0x14, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0x9d, 0xd6, 0x24, 0xc4, 0xa1, 0x25, 0xbf, +0x9a, 0x58, 0x84, 0xe1, 0x76, 0x6a, 0x6f, 0x6a, +0x5c, 0x7e, 0x2b, 0x14, 0xca, 0x13, 0x74, 0xec, +0xc1, 0x72, 0xe4, 0xa5, 0x05, 0x37, 0x65, 0x26, +0x4a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0x9b, 0xac, 0xba, 0x84, 0x9c, +0xad, 0xb3, 0x86, 0xb6, 0x01, 0x3e, 0x63, 0x11, +0x63, 0xb2, 0x08, 0x02, 0x1c, 0x1a, 0x7d, 0x0a, +0xd0, 0xd3, 0x78, 0x27, 0xa0, 0x89, 0x25, 0x94, +0x4f, 0x24, 0xef, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0xf3, 0xd8, 0xa9, +0xea, 0xe1, 0xde, 0x2c, 0xab, 0xf5, 0x81, 0x3e, +0x3c, 0xf8, 0xdc, 0x42, 0x8b, 0x39, 0xb6, 0xcc, +0x06, 0x98, 0xbd, 0xd8, 0xae, 0x4e, 0xa4, 0x69, +0x16, 0x95, 0xf0, 0xe8, 0x35, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0x65, +0xfa, 0x1b, 0x19, 0x5e, 0x2b, 0x03, 0xaa, 0xc1, +0xe6, 0xc3, 0x6f, 0xa9, 0x22, 0x0b, 0x29, 0xcb, +0xad, 0x46, 0x20, 0x79, 0x08, 0xaf, 0xc9, 0x1d, +0x58, 0x29, 0xd1, 0xe7, 0x20, 0x63, 0x42, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0xec, 0x1d, 0x54, 0x19, 0xe2, 0xeb, 0xc3, +0xca, 0xd1, 0xe5, 0x10, 0xd5, 0x81, 0x53, 0x98, +0xfa, 0x49, 0x27, 0x6f, 0x44, 0xc7, 0xe0, 0xfa, +0xdc, 0xdb, 0xb0, 0x4e, 0xa8, 0x4b, 0x47, 0xa2, +0x28, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0x28, 0xfa, 0x48, 0x6a, 0xd0, +0xc0, 0xea, 0x5b, 0xaa, 0x8e, 0xc4, 0x1c, 0x5e, +0xd6, 0xf2, 0xce, 0xdf, 0x89, 0xec, 0xfd, 0xf7, +0xa8, 0x10, 0xc6, 0x34, 0x39, 0x82, 0xeb, 0x56, +0x34, 0x81, 0x5e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0x77, 0x5c, 0xa3, +0xe7, 0x49, 0xea, 0x28, 0xe9, 0xa5, 0xb5, 0xfe, +0x3c, 0x2f, 0x00, 0x4b, 0x48, 0x72, 0xf2, 0x11, +0xe0, 0xbd, 0xf9, 0x36, 0x93, 0xc5, 0xd1, 0x71, +0xe2, 0xce, 0x99, 0xe3, 0x84, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0xc9, +0xdc, 0x3f, 0xea, 0x58, 0x26, 0x6d, 0x22, 0x30, +0xd3, 0x90, 0xe8, 0x54, 0xdd, 0x7f, 0xca, 0x30, +0xcc, 0xf7, 0xc9, 0x95, 0x9b, 0x2f, 0xe3, 0xcd, +0xc9, 0x60, 0xd1, 0xc6, 0x34, 0xfe, 0x29, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0x93, 0x32, 0x15, 0x9c, 0x20, 0x32, 0xda, +0xf0, 0x8e, 0x65, 0x9a, 0x91, 0xfc, 0x56, 0xa9, +0x15, 0x5f, 0x49, 0x2c, 0xe5, 0x76, 0xfe, 0x5d, +0x24, 0x35, 0x1b, 0x5b, 0x68, 0xfe, 0xda, 0x0e, +0xea, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x4b, 0xb0, 0x9a, 0x82, 0xce, +0x44, 0xec, 0x5e, 0xd3, 0x6d, 0x7c, 0x4a, 0xd7, +0x2a, 0x2d, 0xe9, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x41, 0x58, 0x52, 0x29, +0x41, 0x0e, 0xf5, 0x61, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0x7c, 0x5b, 0xf1, +0x15, 0x09, 0x67, 0x6a, 0x96, 0x4e, 0x50, 0x5c, +0xbe, 0x2e, 0xad, 0x61, 0x19, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9c, 0xee, +0xfc, 0xf1, 0x68, 0x15, 0x8e, 0xf1, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0x34, +0xff, 0x9d, 0xc5, 0xd2, 0x2c, 0x47, 0xe6, 0x19, +0x10, 0x41, 0xaa, 0x25, 0x5b, 0xda, 0x74, 0x51, +0xd3, 0x0e, 0x87, 0xb1, 0x4c, 0x09, 0x2c, 0x57, +0x4f, 0x81, 0x44, 0xd3, 0xfb, 0xce, 0x77, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x72, 0x9b, 0x5a, 0x16, 0x35, 0x5a, 0xeb, +0xaf, 0x45, 0x14, 0x27, 0x0b, 0x90, 0xfa, 0x05, +0xfb, 0x78, 0x07, 0xd3, 0xd4, 0xa1, 0x1e, 0x12, +0x36, 0xdb, 0x9d, 0xd4, 0x47, 0xef, 0x10, 0x28, +0x39, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0xe4, 0xb5, 0x70, 0x7f, 0x3b, +0x9d, 0x88, 0xc6, 0x70, 0x89, 0xc3, 0xda, 0x4d, +0x3f, 0x4b, 0x6d, 0x64, 0x48, 0x67, 0x7b, 0x8e, +0xac, 0x59, 0x9a, 0x03, 0x06, 0xab, 0x72, 0x4c, +0x1e, 0xf1, 0x90, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0x31, 0x42, 0x5c, +0xf2, 0x4b, 0xde, 0x1f, 0x79, 0x13, 0x0c, 0xe9, +0xd4, 0xb6, 0x67, 0xbe, 0x92, 0x2b, 0xde, 0x62, +0x1c, 0x38, 0x0e, 0x4e, 0x9e, 0x17, 0x39, 0xbc, +0x37, 0xbd, 0xa7, 0x51, 0xec, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0xb1, +0x87, 0x0c, 0x76, 0x3c, 0xe3, 0x58, 0x63, 0x33, +0x22, 0x18, 0xa9, 0x5d, 0x67, 0xc7, 0x04, 0xc1, +0x99, 0x51, 0x93, 0xa7, 0x8c, 0xce, 0x73, 0x3a, +0x54, 0x97, 0xd1, 0xab, 0xcf, 0x1d, 0x25, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0xbd, 0x3a, 0x41, 0x88, 0x9b, 0xf5, 0xd7, +0x07, 0x3b, 0xb1, 0xe8, 0x67, 0xbf, 0x11, 0x80, +0x58, 0x5b, 0x82, 0x2c, 0x25, 0x76, 0xfd, 0x27, +0x79, 0x60, 0x71, 0xe0, 0x73, 0xec, 0x6e, 0xa4, +0x20, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0x59, 0xff, 0xc9, 0xd8, 0x04, +0xb7, 0x3c, 0xc5, 0x78, 0x86, 0x7f, 0x51, 0x39, +0xe9, 0x41, 0xa8, 0xb8, 0xdc, 0x5d, 0xde, 0x9d, +0x16, 0x7f, 0xef, 0x70, 0x3a, 0x54, 0xd0, 0x04, +0x3f, 0xd8, 0x20, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x4e, 0xe1, 0xb9, +0xb4, 0xfc, 0x0d, 0xc2, 0x17, 0x7b, 0x79, 0x19, +0x4f, 0x97, 0x8f, 0xaf, 0x73, 0x51, 0x10, 0x8b, +0x6b, 0xc5, 0x76, 0xfd, 0xed, 0xe3, 0xd4, 0x64, +0x98, 0xf3, 0x66, 0xd2, 0x29, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x91, +0x94, 0xc1, 0x64, 0x0a, 0x9d, 0x75, 0xd6, 0x4f, +0xe7, 0xc5, 0x48, 0x2a, 0x6d, 0xe7, 0x13, 0x2c, +0xba, 0x52, 0xd5, 0xe7, 0xaf, 0x1f, 0x3a, 0x8b, +0xf7, 0x5b, 0xe3, 0x85, 0x18, 0x32, 0xcb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0x45, 0xde, 0x82, 0x79, 0x11, 0xd9, 0x5f, +0x78, 0xb2, 0x95, 0x81, 0x7e, 0x5f, 0xab, 0x70, +0xbf, 0x8b, 0xbf, 0x44, 0x7b, 0xa0, 0x0b, 0x6a, +0xae, 0xc3, 0xf6, 0xcf, 0x23, 0x43, 0x30, 0xee, +0x85, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0x8e, 0xca, 0x33, 0xc8, 0x1b, +0x84, 0x11, 0xe1, 0x59, 0x4a, 0x94, 0xff, 0x51, +0x82, 0xde, 0x2f, 0x4f, 0x88, 0xf7, 0xcd, 0xcd, +0xef, 0x75, 0x8d, 0xe6, 0x98, 0x93, 0xa2, 0x61, +0x98, 0x60, 0xba, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0x12, 0x72, 0x94, +0x9c, 0x9e, 0x14, 0xcf, 0x88, 0x86, 0x74, 0xb9, +0xb8, 0x82, 0xf7, 0xbf, 0x85, 0x2d, 0x3a, 0x77, +0xb6, 0x8b, 0x23, 0x86, 0x02, 0x89, 0x08, 0x41, +0x47, 0x42, 0xe8, 0x07, 0x49, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0xae, +0xa1, 0xa1, 0x92, 0x65, 0x94, 0xf1, 0xab, 0x06, +0x3f, 0x70, 0x4f, 0x08, 0xdd, 0x5a, 0x7c, 0xf3, +0xd9, 0xb6, 0x6b, 0xf8, 0x76, 0xf9, 0xe5, 0x06, +0x0f, 0xde, 0x53, 0x2a, 0xbf, 0x6e, 0x29, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0xac, 0x32, 0x5c, 0xcc, 0x2b, 0xdf, 0xa0, +0xdc, 0x74, 0xb3, 0x4d, 0x2b, 0x8d, 0x64, 0x6f, +0x00, 0xab, 0x20, 0x32, 0xcf, 0x91, 0x62, 0x03, +0x0e, 0x13, 0xc2, 0xb4, 0x71, 0x55, 0x78, 0x1a, +0x98, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0xe1, 0x65, 0x10, 0x81, 0xba, +0xe6, 0xf7, 0xd9, 0x8c, 0xbe, 0x55, 0xd1, 0x21, +0x91, 0x62, 0x29, 0x91, 0x40, 0x8d, 0x74, 0x88, +0xef, 0x91, 0x30, 0x18, 0xcb, 0xf5, 0x67, 0x37, +0x9b, 0x2d, 0xa0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0xb8, 0x17, 0x74, +0x29, 0x7d, 0x05, 0x9d, 0xe1, 0x96, 0x68, 0x12, +0x14, 0xcf, 0x49, 0x3d, 0xfb, 0x63, 0x1c, 0x86, +0xdf, 0xc4, 0x00, 0x8d, 0x87, 0x68, 0x26, 0x6a, +0x0d, 0x32, 0xa6, 0x24, 0x04, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0xc1, +0x86, 0x91, 0xd7, 0x59, 0x56, 0x2d, 0xab, 0x51, +0xab, 0x9a, 0x0f, 0x65, 0x4a, 0x27, 0x38, 0xb4, +0x0a, 0x8e, 0xc3, 0x2d, 0x69, 0xcc, 0x53, 0xcd, +0x4c, 0x3c, 0x19, 0xb0, 0x76, 0x33, 0x34, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0xf2, 0x88, 0x76, 0x86, 0x88, 0x2f, 0xd6, +0x2b, 0x90, 0x27, 0xdc, 0x29, 0x4e, 0x1c, 0xb6, +0xf0, 0x51, 0x72, 0x41, 0x55, 0x62, 0x10, 0xba, +0xb3, 0xaf, 0x20, 0xa6, 0x50, 0x8a, 0xdc, 0x47, +0xbf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0xe3, 0xc7, 0xb2, 0x37, 0x3e, +0x74, 0xae, 0xcf, 0x95, 0xb8, 0x38, 0xd3, 0x86, +0x48, 0xd1, 0xb3, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0xc1, 0x6e, +0xcd, 0x12, 0x5c, 0x62, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0xdd, 0x9c, 0xa5, +0x0b, 0x63, 0x7c, 0x9b, 0x07, 0x65, 0x1f, 0x79, +0x89, 0x75, 0x2d, 0x7b, 0x9d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0xa6, +0x12, 0xee, 0x55, 0x0c, 0x1e, 0xf6, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0x8e, +0x51, 0xfa, 0xd9, 0x66, 0x15, 0x6e, 0x0e, 0x54, +0xc4, 0xd9, 0x44, 0x78, 0x77, 0xcd, 0x61, 0xd1, +0x7c, 0xc8, 0xc2, 0xf6, 0x7d, 0xd4, 0x89, 0x4e, +0x87, 0x57, 0xe1, 0xd2, 0x13, 0xbb, 0x69, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0x17, 0x78, 0x54, 0xde, 0xee, 0x77, 0xb4, +0x46, 0x16, 0xf6, 0x3f, 0x44, 0xba, 0xde, 0x21, +0xa4, 0x10, 0x10, 0x04, 0xae, 0xec, 0xad, 0x1e, +0xbe, 0x35, 0xa0, 0xc3, 0x84, 0xcd, 0xc1, 0x58, +0xac, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0x22, 0xee, 0x5b, 0xd6, 0x46, +0x24, 0x37, 0x47, 0x78, 0x65, 0x9c, 0x3e, 0xd7, +0x03, 0x4a, 0x5c, 0xf1, 0x9e, 0xb9, 0x40, 0xbe, +0x5b, 0x7e, 0x2a, 0x68, 0x06, 0x27, 0xaa, 0x10, +0xb8, 0xed, 0xcf, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x87, 0x94, 0xb8, +0x0b, 0x5a, 0xa1, 0x86, 0x6d, 0x5d, 0x4f, 0xf9, +0xb1, 0x62, 0x9e, 0x17, 0xf8, 0x32, 0xa2, 0x91, +0xa0, 0x56, 0xee, 0x83, 0x27, 0xa2, 0x76, 0xb0, +0x44, 0x18, 0x5a, 0x2a, 0x8d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x77, +0x8c, 0x1b, 0x63, 0xeb, 0x60, 0x4c, 0xb3, 0xa4, +0x13, 0x1b, 0x68, 0x79, 0x35, 0x55, 0x62, 0xc7, +0x39, 0x1e, 0x4a, 0x91, 0x67, 0x40, 0x90, 0x01, +0x90, 0x72, 0xff, 0x0e, 0xd8, 0xa7, 0xc1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0x65, 0x52, 0x69, 0xd4, 0x39, 0xa7, 0x46, +0x51, 0x17, 0xb0, 0x53, 0xba, 0xa6, 0x59, 0xa8, +0x50, 0xe4, 0x0e, 0x02, 0x59, 0x88, 0xc8, 0x2f, +0x6f, 0xb3, 0xc6, 0xeb, 0x0d, 0xc6, 0xd7, 0x72, +0x7d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0x2a, 0x00, 0x5d, 0x3d, 0x4e, +0xf4, 0x67, 0xf7, 0xea, 0xe1, 0x09, 0x3b, 0x8d, +0xf0, 0x73, 0x39, 0x9b, 0x19, 0x2b, 0xef, 0xf3, +0x62, 0x08, 0x66, 0x9a, 0xb6, 0x06, 0x5d, 0xa6, +0x75, 0x4c, 0x18, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0xc3, 0xf3, 0x87, +0xd4, 0x8e, 0xdc, 0xb2, 0x59, 0x32, 0x1c, 0xef, +0xd4, 0xd0, 0xf1, 0xdd, 0x62, 0xb9, 0x77, 0x55, +0x9c, 0x8a, 0x7e, 0x2c, 0xdc, 0xce, 0x9e, 0x14, +0x4d, 0x87, 0x61, 0xea, 0x77, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x1c, +0x0c, 0x35, 0x73, 0xb9, 0x68, 0x4a, 0xc3, 0xd8, +0x68, 0x9b, 0x5d, 0xba, 0x31, 0x02, 0xbf, 0x0a, +0x58, 0x5f, 0xf8, 0x1e, 0x4e, 0x9e, 0xeb, 0xc7, +0x87, 0x11, 0xd5, 0x89, 0x08, 0x3b, 0x7c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0xdf, 0xb6, 0x7a, 0xff, 0x26, 0xf8, 0x5b, +0x39, 0x06, 0x56, 0x8e, 0x6b, 0x89, 0x6f, 0x8a, +0x4c, 0x50, 0x2c, 0xef, 0x8c, 0x00, 0xd7, 0x3f, +0xfe, 0xc9, 0x7a, 0x7c, 0x5e, 0x56, 0xcc, 0x83, +0x8b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0x8e, 0x2a, 0x0a, 0x3a, 0x7b, +0x4d, 0x40, 0x38, 0x5d, 0x55, 0xa0, 0x6e, 0xf3, +0xa9, 0xa6, 0xa4, 0xd8, 0xd5, 0x96, 0xdd, 0x87, +0x46, 0x74, 0x64, 0x49, 0x59, 0xf3, 0x99, 0xd3, +0xaf, 0x1b, 0x62, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0x3e, 0x8e, 0x2b, +0xab, 0x21, 0xea, 0x88, 0x89, 0xcf, 0x01, 0x34, +0x0e, 0x4e, 0x62, 0xeb, 0x9b, 0x2e, 0xaf, 0xae, +0x5a, 0x3e, 0x0f, 0xf8, 0x1d, 0x41, 0xc8, 0x82, +0xa0, 0x17, 0x0c, 0x66, 0x64, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x49, +0xd3, 0x56, 0x87, 0x0c, 0x46, 0x40, 0x6a, 0xb3, +0x86, 0x9f, 0x5e, 0xd7, 0xe9, 0xd0, 0x83, 0x3f, +0x4f, 0xc0, 0xac, 0x6a, 0xf0, 0x5e, 0x05, 0xd5, +0x90, 0x32, 0x77, 0xd6, 0x0d, 0x02, 0x2c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0x80, 0xc7, 0x12, 0x55, 0x86, 0x71, 0xbb, +0x6e, 0x71, 0x14, 0x06, 0xa8, 0x40, 0xc4, 0x86, +0x11, 0x5b, 0x37, 0x44, 0x4b, 0xb7, 0x4d, 0xf7, +0x0d, 0x93, 0x5c, 0xb7, 0xf2, 0xc2, 0x5c, 0xd6, +0x03, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0x11, 0x86, 0x06, 0xef, 0x26, +0xda, 0x42, 0xaf, 0xe7, 0x28, 0x81, 0xcd, 0x80, +0x3f, 0xcb, 0xd3, 0x10, 0x94, 0xe0, 0x91, 0x38, +0xab, 0x98, 0x7b, 0x3c, 0x47, 0x70, 0x5b, 0xf5, +0xd6, 0x14, 0x32, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0x21, 0x38, 0xfd, +0x29, 0x1c, 0x11, 0x0c, 0x3e, 0x56, 0xf3, 0xf5, +0xc7, 0xdf, 0xe7, 0x45, 0xf6, 0x26, 0x2c, 0x9d, +0xc0, 0x2d, 0x2e, 0xb9, 0x6d, 0x6f, 0xfd, 0x4c, +0xe4, 0x8c, 0xcd, 0x23, 0xd3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0xad, +0x41, 0x66, 0xc6, 0x80, 0xa0, 0xd0, 0xe8, 0x1d, +0x04, 0xf2, 0x2a, 0xf5, 0x9f, 0x4b, 0xdb, 0xa4, +0x3a, 0x92, 0x5e, 0x3a, 0x21, 0xe1, 0x85, 0x64, +0xfd, 0x20, 0x43, 0xc2, 0xfb, 0x57, 0x20, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0x4e, 0x97, 0x16, 0x06, 0x44, 0x06, 0xc1, +0x7f, 0x28, 0x66, 0x02, 0xe3, 0xcd, 0x69, 0xc4, +0xea, 0x1b, 0x53, 0x1b, 0xe2, 0x46, 0x69, 0x46, +0xce, 0x2a, 0x5a, 0x82, 0x80, 0xc5, 0x8c, 0x44, +0xb7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x80, 0x39, 0x59, 0xa8, 0xed, +0x73, 0x94, 0xe2, 0xca, 0x04, 0x7e, 0x4c, 0x9f, +0x93, 0x3d, 0x5b, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x3a, 0xed, 0x6d, 0x09, +0xc0, 0x17, 0xe0, 0x9e, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x50, 0x61, 0x12, +0xac, 0xe6, 0x7c, 0x03, 0x90, 0xac, 0x37, 0x79, +0x7a, 0x62, 0x68, 0xf3, 0x86, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x1d, +0x2f, 0x46, 0xbb, 0x9d, 0x28, 0x20, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0xb6, +0xc2, 0x9b, 0x83, 0x7f, 0x70, 0x33, 0x43, 0xf5, +0xe7, 0x61, 0xb6, 0xea, 0x0b, 0x3e, 0x0e, 0x38, +0xaa, 0x43, 0x37, 0xf0, 0x61, 0x60, 0xef, 0x62, +0xaa, 0xc1, 0x9d, 0x1a, 0x23, 0x9a, 0xd1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0xce, 0xb2, 0x32, 0x49, 0xdf, 0xda, 0x76, +0x89, 0xa1, 0xb5, 0xea, 0xf6, 0x6b, 0xe9, 0x59, +0x6a, 0x02, 0x93, 0xa8, 0x9d, 0x97, 0xcd, 0x54, +0x1c, 0xbd, 0x7a, 0xd9, 0x98, 0x19, 0x3d, 0x5d, +0x0d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0x0e, 0x49, 0xdb, 0x6b, 0x9d, +0xad, 0x69, 0x70, 0x54, 0x56, 0x87, 0xb9, 0xf3, +0x46, 0x59, 0x3c, 0x88, 0x1a, 0x25, 0x30, 0xeb, +0xc7, 0x2a, 0x10, 0x0f, 0x40, 0xcc, 0x58, 0x07, +0x45, 0x70, 0x77, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0x01, 0x7e, 0xbd, +0x2b, 0x27, 0xa0, 0xbd, 0xd1, 0x67, 0x80, 0x12, +0x5e, 0xc7, 0xfe, 0x0b, 0x6e, 0x27, 0x73, 0xe3, +0xb5, 0x3d, 0x0a, 0xa1, 0xd0, 0x1d, 0x9f, 0xf6, +0xe2, 0x06, 0xe5, 0x48, 0xa4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0x25, +0x95, 0xb3, 0xaa, 0x51, 0xfc, 0xdb, 0xcb, 0x70, +0x31, 0x04, 0x76, 0x54, 0xc9, 0x33, 0x28, 0x8e, +0x6c, 0x40, 0xd6, 0xfd, 0x69, 0xb3, 0x32, 0xe3, +0x87, 0xac, 0xa5, 0xb4, 0xc8, 0x0f, 0xa2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0xf8, 0x40, 0x4c, 0xbc, 0x81, 0x1e, 0x4b, +0x54, 0x12, 0x59, 0xf6, 0x68, 0x33, 0x82, 0x0a, +0xdc, 0x70, 0xa9, 0xfb, 0xe7, 0x12, 0x7a, 0xcb, +0x64, 0x13, 0x9a, 0xa4, 0xdb, 0xc0, 0x7b, 0x6e, +0x4b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0xc1, 0x4c, 0x9b, 0x01, 0xea, +0x1e, 0x9d, 0x7b, 0xfa, 0x89, 0x79, 0x48, 0x32, +0x18, 0x70, 0xa5, 0xb0, 0x66, 0xaf, 0x57, 0x51, +0x01, 0x67, 0x17, 0x48, 0x66, 0x37, 0x90, 0xe9, +0xa9, 0xcc, 0x1f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0xe3, 0x2b, 0x8f, +0x3f, 0x91, 0xfe, 0xd2, 0x9d, 0x9a, 0x37, 0x3a, +0xe8, 0x4a, 0xd0, 0xd3, 0x9d, 0x36, 0x11, 0x93, +0x8b, 0x83, 0x6f, 0x84, 0x14, 0xe2, 0x8f, 0x39, +0x02, 0x69, 0x04, 0x8f, 0xef, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x57, +0xfa, 0x22, 0x46, 0x03, 0x34, 0xb5, 0x85, 0x6f, +0x61, 0x29, 0xa4, 0x9b, 0x2c, 0xa5, 0x98, 0xbf, +0xe5, 0x03, 0xef, 0xa5, 0xa1, 0xec, 0xc4, 0xa0, +0xfa, 0xb1, 0x36, 0xc5, 0xc2, 0x84, 0x1f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0xdf, 0x56, 0xe0, 0x58, 0xa8, 0x69, 0x75, +0xd1, 0xbe, 0xd8, 0xe2, 0x06, 0xc4, 0xb4, 0xaf, +0x43, 0x3d, 0xaa, 0x3c, 0xc6, 0x3b, 0xdb, 0x61, +0x3e, 0x5d, 0xa6, 0xf8, 0x6a, 0xf1, 0xb9, 0x19, +0xa6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0x89, 0xf9, 0x99, 0xa2, 0xcb, +0x7e, 0xdd, 0xd0, 0x51, 0xd1, 0xde, 0x13, 0x08, +0xb3, 0x53, 0xeb, 0xb3, 0xc2, 0x15, 0xeb, 0xc1, +0xb8, 0x67, 0xb9, 0x71, 0xe4, 0xe3, 0x5a, 0x75, +0x17, 0x5c, 0x81, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0xa1, 0xd8, 0x11, +0xe4, 0xc8, 0x00, 0x7c, 0xb9, 0xb4, 0xe2, 0x02, +0x50, 0x05, 0x29, 0x9b, 0x97, 0x2a, 0x60, 0x70, +0x3c, 0x7f, 0x9b, 0x07, 0x5e, 0xf6, 0xb6, 0x65, +0x27, 0x7b, 0xdd, 0x75, 0xb5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0x88, +0xa1, 0xf0, 0xd4, 0x41, 0x32, 0x16, 0xf1, 0x2c, +0x30, 0x0c, 0xa8, 0x93, 0x6b, 0x17, 0x2a, 0x9d, +0x00, 0x2a, 0x4d, 0x35, 0x2d, 0xcb, 0x88, 0xff, +0xa8, 0xde, 0xe3, 0x80, 0xb9, 0x89, 0xe2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0x5a, 0x9f, 0x80, 0x3a, 0xab, 0xbb, 0x08, +0x0a, 0xb7, 0x87, 0x59, 0xd2, 0xee, 0xde, 0x77, +0xa7, 0x3e, 0x3f, 0x6a, 0x7f, 0xa4, 0x49, 0xa6, +0x4e, 0xfc, 0xd5, 0x55, 0x95, 0x1e, 0x10, 0x3d, +0xbd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0xef, 0xac, 0xa7, 0xa8, 0x0a, +0x0a, 0x7e, 0xe7, 0xbd, 0x19, 0x3a, 0x25, 0xbb, +0xda, 0x70, 0xa2, 0x3f, 0x7b, 0x67, 0x55, 0xa1, +0x43, 0xcf, 0xf2, 0x75, 0xc9, 0x1c, 0xb6, 0x92, +0xe2, 0xc2, 0x6b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0x02, 0x49, 0x8a, +0x53, 0x45, 0x1f, 0x54, 0x85, 0x88, 0xf5, 0x1d, +0xe2, 0x19, 0x33, 0x46, 0xd8, 0x63, 0xec, 0xa2, +0xf0, 0x2f, 0x51, 0x7b, 0x73, 0x3d, 0x12, 0x5e, +0xbe, 0xc9, 0x13, 0x7e, 0x03, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0xcd, +0xf1, 0xbd, 0x4f, 0xcb, 0xda, 0xf0, 0x09, 0x85, +0x10, 0x5b, 0xb7, 0x04, 0xff, 0xa5, 0xe5, 0x20, +0x10, 0x20, 0x11, 0x64, 0xd1, 0x3c, 0x1f, 0x59, +0x0d, 0x32, 0x6c, 0xf8, 0x27, 0x89, 0xb5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0xad, 0x75, 0xef, 0x59, 0x0f, 0x70, 0xaf, +0x4c, 0x88, 0xf9, 0x0e, 0x3b, 0xba, 0x45, 0xc4, +0x79, 0x0e, 0xf2, 0x6a, 0x95, 0xee, 0xd5, 0x78, +0xd3, 0xfe, 0x92, 0xf0, 0x07, 0x6e, 0xa1, 0xfa, +0x20, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0x98, 0x9f, 0xb7, 0x22, 0x01, +0x1a, 0x56, 0x14, 0x63, 0x46, 0xd6, 0x52, 0x17, +0xcd, 0x41, 0xf7, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xd0, 0xdd, 0x85, 0xd1, +0x6d, 0x60, 0x89, 0x75, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0xb9, 0x15, 0xcc, +0x61, 0x96, 0x45, 0x92, 0x4d, 0xdc, 0x9c, 0x28, +0x8e, 0xa0, 0x92, 0x6b, 0x35, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xb6, +0x0e, 0x09, 0x34, 0xca, 0x7c, 0x06, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0x07, +0xe8, 0xbf, 0x88, 0x8b, 0xec, 0xdd, 0xb9, 0xf8, +0x1c, 0x7e, 0xf8, 0x89, 0x4c, 0xd3, 0xb7, 0xa9, +0x12, 0x91, 0x8c, 0x5d, 0x30, 0xf1, 0x51, 0x85, +0xff, 0x42, 0x8d, 0x21, 0x34, 0x41, 0x8e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0x3f, 0x56, 0xb3, 0x79, 0x75, 0xc9, 0xe8, +0x06, 0xcc, 0x46, 0xe1, 0x7c, 0x49, 0x54, 0x5b, +0x30, 0x50, 0x31, 0xd1, 0xf9, 0x88, 0x48, 0x90, +0xdc, 0xc4, 0x7b, 0xdb, 0xb4, 0x36, 0xa9, 0x1d, +0xae, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0x50, 0xcf, 0xc3, 0x8b, 0x40, +0xf7, 0xd0, 0x1e, 0xcd, 0x47, 0xbf, 0x12, 0xdf, +0x71, 0xeb, 0xe0, 0xb5, 0x1c, 0x70, 0x5e, 0x06, +0xc4, 0x83, 0x85, 0x4e, 0x87, 0x74, 0x81, 0xe5, +0x6d, 0x86, 0xaa, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0xec, 0x3d, 0x3f, +0xed, 0x18, 0x00, 0x1d, 0xf0, 0x7e, 0xcd, 0x70, +0x53, 0x75, 0x68, 0x3b, 0xed, 0xf0, 0x7d, 0x70, +0xf3, 0x41, 0x62, 0x9e, 0xc0, 0xfb, 0xb2, 0xc6, +0x80, 0x4f, 0xda, 0x7c, 0x26, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0xc7, +0x62, 0x4e, 0x1b, 0x66, 0x42, 0x90, 0x04, 0xdb, +0xcc, 0x42, 0x17, 0x11, 0x9f, 0xa1, 0x01, 0x88, +0x46, 0x9c, 0x3e, 0x67, 0x10, 0xc6, 0x33, 0xa3, +0xfe, 0x02, 0xf9, 0x6c, 0x91, 0xa5, 0x63, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0x9c, 0x40, 0xba, 0xc5, 0x4b, 0xdb, 0x3e, +0x2a, 0xb5, 0x96, 0x58, 0xdf, 0x2a, 0x06, 0x1a, +0xe6, 0x30, 0xb1, 0x3b, 0x7b, 0xb6, 0x1d, 0xb3, +0x52, 0x22, 0x1e, 0xd7, 0xa6, 0x94, 0x81, 0x91, +0x0b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0x1e, 0xd4, 0x17, 0xfa, 0xec, +0x95, 0xcf, 0x61, 0x8b, 0x04, 0xcd, 0xd7, 0xce, +0x55, 0x32, 0xa0, 0x53, 0x01, 0x42, 0x61, 0xfb, +0x2b, 0xfc, 0x2f, 0x38, 0xab, 0xce, 0xc7, 0x6c, +0x38, 0xad, 0x4e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0x6c, 0x38, 0x08, +0x73, 0x70, 0x50, 0xf3, 0xd2, 0x98, 0xd7, 0x52, +0x50, 0x65, 0xbe, 0x7a, 0xa9, 0x3a, 0x86, 0xf9, +0xe0, 0xe5, 0xc4, 0xa5, 0x1c, 0xcb, 0x41, 0x50, +0xcd, 0x3d, 0x31, 0x07, 0x8d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0xfb, +0x58, 0x42, 0x3f, 0x12, 0xce, 0xf3, 0xe2, 0x02, +0x78, 0xf2, 0x0a, 0x56, 0x16, 0x76, 0xb7, 0xa1, +0x24, 0x98, 0x20, 0x3a, 0x14, 0x1c, 0x81, 0xc2, +0x0d, 0xd8, 0x66, 0xfe, 0x59, 0x67, 0xb6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0xc9, 0x71, 0xa5, 0xdc, 0x64, 0x5d, 0xdf, +0x0d, 0x25, 0xe4, 0x2d, 0x34, 0x34, 0x94, 0x44, +0xa5, 0x82, 0x15, 0xc1, 0xe2, 0x83, 0xec, 0x12, +0x85, 0x6f, 0x72, 0xcc, 0x64, 0x43, 0x19, 0x28, +0x2c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0x01, 0x0f, 0xf3, 0x4a, 0x61, +0xed, 0x38, 0x06, 0x09, 0x0e, 0xcc, 0x9d, 0xbc, +0x35, 0xc7, 0x6c, 0xf4, 0x67, 0xd4, 0xac, 0x89, +0x4c, 0xf7, 0x44, 0xdf, 0xdc, 0xb1, 0xb1, 0xa7, +0x53, 0xec, 0xd9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x19, 0x63, 0x9d, +0x83, 0x16, 0xb5, 0xd2, 0xfb, 0xc0, 0x80, 0x7b, +0x91, 0x4d, 0x72, 0x0f, 0xa4, 0xa6, 0xa2, 0xa5, +0x08, 0xfb, 0x1c, 0x84, 0xd8, 0xa9, 0xd1, 0x1f, +0xc2, 0x6e, 0x93, 0xa8, 0xa2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0x14, +0xbe, 0x04, 0x6c, 0xde, 0x0c, 0x34, 0x5e, 0x96, +0xff, 0xea, 0xbb, 0x9f, 0x10, 0xde, 0x59, 0xef, +0x4a, 0x39, 0x78, 0x88, 0xba, 0x48, 0xb5, 0x1d, +0xb6, 0xd3, 0x51, 0x1c, 0x67, 0x66, 0x42, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0x2a, 0xbe, 0x1f, 0x64, 0x76, 0x4d, 0x9f, +0x00, 0xcc, 0x78, 0xbb, 0xeb, 0x40, 0x39, 0x3b, +0xa0, 0xa2, 0x05, 0x88, 0x28, 0x6d, 0x86, 0xe4, +0xb7, 0xf1, 0x6e, 0x8f, 0x35, 0xb7, 0x5e, 0x0a, +0x32, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0x6c, 0x13, 0xe3, 0x20, 0x3b, +0x9b, 0x35, 0x06, 0x31, 0x32, 0x20, 0x37, 0xa9, +0x34, 0x2d, 0x43, 0x3c, 0x53, 0x19, 0xc0, 0xce, +0xea, 0x57, 0x47, 0x7c, 0xa4, 0xc2, 0x1d, 0x7e, +0x4f, 0x8a, 0xa7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x81, 0xc1, 0x7d, +0x1b, 0x9c, 0x75, 0x3a, 0xce, 0x9d, 0xf9, 0x1b, +0x5a, 0x06, 0x6d, 0xab, 0x6e, 0x45, 0x97, 0xbb, +0xb7, 0x9d, 0x89, 0x14, 0x8e, 0x1d, 0xc1, 0x00, +0xa7, 0x47, 0x46, 0xb7, 0xd3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0xd7, +0x5e, 0xb5, 0xcf, 0x39, 0xf0, 0x5f, 0x3d, 0x98, +0x78, 0x09, 0xff, 0xdf, 0x2c, 0xe2, 0x35, 0x50, +0x56, 0xfe, 0x9b, 0x39, 0x3c, 0x3b, 0x44, 0x5f, +0xf5, 0x71, 0xb0, 0xbd, 0xca, 0x69, 0x4d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0x20, 0x38, 0x20, 0xa4, 0x1b, 0xcc, 0xa4, +0x77, 0xa1, 0x55, 0xdf, 0x4b, 0xb3, 0xd2, 0x33, +0x0c, 0x96, 0x49, 0x25, 0x3f, 0x73, 0x82, 0x6d, +0xe7, 0x81, 0xfc, 0xb2, 0xd6, 0x30, 0x9a, 0x31, +0x14, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0x0b, 0x27, 0xdb, 0x60, 0x1a, +0x4a, 0x2b, 0xcf, 0x33, 0x72, 0x77, 0x87, 0x49, +0x40, 0xbd, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x76, 0x4e, 0xd3, 0x5f, +0x1e, 0x5d, 0x54, 0x2e, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0xf0, 0xd6, 0xb9, +0x6f, 0x14, 0x07, 0x3a, 0xbb, 0x4b, 0x47, 0x3b, +0x89, 0x1c, 0xa3, 0x65, 0x73, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x61, +0x33, 0xe1, 0x2e, 0x06, 0xd4, 0xa6, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0x30, +0x0b, 0x49, 0x17, 0x83, 0x7f, 0x28, 0x64, 0xf7, +0xab, 0xb9, 0x71, 0xa5, 0x43, 0xdf, 0xac, 0xea, +0x67, 0xf3, 0xb3, 0xfb, 0x3e, 0xa5, 0x8e, 0xa2, +0x3b, 0xf3, 0xe8, 0xe1, 0x25, 0x44, 0x8a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0xc5, 0xac, 0x16, 0x41, 0xf8, 0xa2, 0xea, +0x81, 0x91, 0x67, 0x63, 0xdf, 0xf4, 0x98, 0x3a, +0x00, 0x3c, 0x94, 0x7a, 0x52, 0x59, 0xa3, 0x3a, +0xda, 0x7b, 0x7e, 0x02, 0xcf, 0xc1, 0x96, 0x07, +0x16, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0xfe, 0x30, 0x39, 0x40, 0x3f, +0x4b, 0x56, 0x73, 0x3c, 0xaf, 0x46, 0x71, 0x9a, +0x68, 0x12, 0xfc, 0x75, 0xb3, 0x1d, 0x19, 0x1d, +0xfd, 0xf9, 0x14, 0x17, 0xc4, 0x2d, 0x28, 0xf8, +0x42, 0x38, 0x13, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x83, 0x25, 0x26, +0x78, 0x52, 0x78, 0x87, 0xdd, 0x94, 0xa2, 0xb1, +0x3c, 0x10, 0x3a, 0x98, 0x29, 0x6d, 0xb3, 0x05, +0x9c, 0xe5, 0xd9, 0x6f, 0xdd, 0x0b, 0x77, 0xa9, +0xaa, 0x93, 0xf7, 0x08, 0x32, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x62, +0x9f, 0xd2, 0xa5, 0x4c, 0x34, 0xdb, 0x1b, 0x43, +0xd2, 0x38, 0xc9, 0xc2, 0x5d, 0x9f, 0x2e, 0xdc, +0xe0, 0x8b, 0x3e, 0xa2, 0x31, 0x48, 0xd0, 0x85, +0xd3, 0xe4, 0x79, 0x80, 0xd5, 0x3d, 0xe1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0xb5, 0x72, 0x80, 0xce, 0x9e, 0x33, 0x2d, +0xc2, 0xde, 0xf8, 0x6a, 0x71, 0xca, 0x2e, 0x25, +0xd6, 0x0e, 0xb0, 0xe5, 0xff, 0xca, 0x91, 0xeb, +0x26, 0xc3, 0xc1, 0x70, 0x93, 0x7a, 0xe8, 0x20, +0xd6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0x46, 0x20, 0xca, 0x58, 0x82, +0x59, 0x75, 0x1d, 0xd0, 0x77, 0x7d, 0xda, 0x9d, +0xe1, 0x78, 0xad, 0x90, 0x9c, 0x30, 0x80, 0x65, +0x95, 0xea, 0xdd, 0x5b, 0x97, 0x7d, 0xc3, 0x9c, +0xad, 0x8a, 0x05, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0x07, 0xae, 0x83, +0xf9, 0x71, 0xdd, 0x5e, 0xfd, 0xfe, 0xe3, 0xaa, +0x74, 0x38, 0x8c, 0xf2, 0xda, 0xe3, 0x82, 0xb6, +0xd4, 0x0a, 0x5b, 0x8b, 0x91, 0x86, 0x87, 0x1b, +0xe6, 0x51, 0x9e, 0x2c, 0x63, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0xa2, +0xc9, 0xd9, 0x29, 0xa4, 0x40, 0x8f, 0x08, 0xa0, +0x62, 0xa9, 0x3f, 0xfd, 0x17, 0x1a, 0xa7, 0x1e, +0xe1, 0x8b, 0xe2, 0xfb, 0xd5, 0x8c, 0xf9, 0x24, +0x41, 0xfe, 0x2c, 0x16, 0xcd, 0x63, 0x79, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x42, 0xd1, 0x13, 0xe9, 0x62, 0x03, 0x23, +0x9e, 0x1b, 0x43, 0x33, 0x73, 0x70, 0xa2, 0x99, +0x9a, 0x36, 0x57, 0x31, 0x54, 0xb3, 0xbf, 0xec, +0xb0, 0x09, 0xfe, 0xa2, 0xc8, 0x5e, 0x7c, 0xd9, +0x25, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0x0d, 0xeb, 0x7f, 0xc3, 0x1f, +0x24, 0x72, 0x14, 0x17, 0xde, 0xef, 0x8b, 0xdb, +0x51, 0x75, 0x71, 0x8c, 0x9b, 0xd4, 0x66, 0x84, +0x00, 0xba, 0x6b, 0x9a, 0x32, 0x63, 0x25, 0xa7, +0xcd, 0xd4, 0xc8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0x8e, 0x21, 0xfc, +0xdb, 0xe4, 0x5c, 0x5b, 0xd9, 0x12, 0x6a, 0x19, +0x33, 0xe2, 0x9a, 0xbf, 0x29, 0x9c, 0xe9, 0x71, +0x53, 0x78, 0xfd, 0x05, 0xaf, 0xca, 0x51, 0x70, +0x41, 0x23, 0x62, 0x5e, 0xa0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0x65, +0x59, 0x6e, 0x0f, 0xfd, 0xce, 0x24, 0xd2, 0xb2, +0x73, 0xdb, 0x19, 0x55, 0x0a, 0x96, 0x91, 0x30, +0xcb, 0x92, 0x93, 0x53, 0xae, 0xc5, 0xe5, 0x26, +0xa2, 0x71, 0xd9, 0x59, 0x45, 0x14, 0x1b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0x65, 0xbe, 0x80, 0x7f, 0x7f, 0xc3, 0xf8, +0xf0, 0x78, 0x36, 0x74, 0xf9, 0xd2, 0xc3, 0xd2, +0xef, 0xff, 0x4c, 0x29, 0x5d, 0x9d, 0x49, 0x1c, +0x70, 0xf3, 0x85, 0x6c, 0xd3, 0x1e, 0x52, 0x0f, +0x7e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0x5c, 0x7d, 0x13, 0x3b, 0x6d, +0x06, 0x29, 0xb5, 0xdc, 0x08, 0xfb, 0x93, 0xc2, +0x9a, 0x72, 0x69, 0xd8, 0x4e, 0x77, 0x7e, 0xeb, +0x05, 0x8e, 0xcb, 0x97, 0xd4, 0xb2, 0x70, 0x3d, +0xed, 0xb5, 0x37, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0xed, 0xd3, 0x6f, +0x84, 0xfb, 0x68, 0xe9, 0xf5, 0x9f, 0x2a, 0xb5, +0x64, 0x53, 0xcf, 0xff, 0x80, 0x98, 0xb7, 0x2a, +0xed, 0xfa, 0x15, 0xd0, 0x7a, 0x54, 0x28, 0xb0, +0xc9, 0x85, 0xd0, 0x63, 0x0e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0x85, +0x4f, 0xa2, 0x10, 0xb1, 0x0e, 0xc2, 0x13, 0x2b, +0x89, 0xad, 0x81, 0x0c, 0x5a, 0x0f, 0xf2, 0x46, +0xd6, 0x0a, 0x6d, 0x95, 0x9c, 0x2c, 0x41, 0xf6, +0xa1, 0x20, 0x18, 0xc8, 0x00, 0xc1, 0x11, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x11, 0xa1, 0xb2, 0x6f, 0x2b, 0x41, 0xe7, +0x54, 0xcc, 0x94, 0x11, 0xb1, 0x0a, 0xe5, 0xef, +0x1c, 0x4c, 0x70, 0xb0, 0xd2, 0x2e, 0x35, 0xf9, +0xa7, 0x36, 0xe9, 0x5f, 0xee, 0x9d, 0xe1, 0xb3, +0x18, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0xa4, 0x79, 0xfc, 0x70, 0x0e, +0x9c, 0xd4, 0xe1, 0xef, 0xdf, 0xff, 0xd6, 0x14, +0x4d, 0x36, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x56, 0x0f, 0xff, 0xb3, +0x4f, 0xc7, 0x5e, 0x5d, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0x39, 0x97, 0x0a, +0x55, 0x2e, 0xce, 0x40, 0x5b, 0x5c, 0x78, 0xbe, +0x0e, 0xdd, 0x03, 0x26, 0x5e, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdc, 0xe6, +0x13, 0xfe, 0xb3, 0x8b, 0x50, 0x41, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0x4a, +0x64, 0x14, 0x6f, 0x4f, 0x1f, 0x7d, 0x42, 0xc6, +0x03, 0xce, 0xe7, 0x66, 0xda, 0xda, 0x75, 0x2f, +0x0d, 0xd0, 0x75, 0xbd, 0xe8, 0x05, 0xc6, 0x6e, +0x51, 0x16, 0xd6, 0x78, 0xef, 0xc6, 0x5f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0x44, 0x49, 0xb1, 0xdf, 0xc5, 0x6c, 0xf3, +0x83, 0xf6, 0xc3, 0xee, 0x2e, 0xd6, 0x2a, 0xa6, +0x3a, 0x6f, 0xe1, 0x6c, 0xbb, 0x6e, 0x2d, 0x3b, +0xf0, 0xd9, 0x2b, 0x03, 0xff, 0x50, 0x64, 0xbd, +0x87, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0x21, 0x81, 0x95, 0xbc, 0x58, +0xbe, 0x9c, 0x3d, 0x60, 0x77, 0xc1, 0xe4, 0xe5, +0x7f, 0xd9, 0xc5, 0xea, 0x7a, 0x57, 0x86, 0xc3, +0x10, 0x35, 0xae, 0x8b, 0x02, 0x45, 0xe1, 0xe5, +0xc6, 0x72, 0x27, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0xed, 0x94, 0x6a, +0x56, 0xc4, 0xea, 0x21, 0xaa, 0xbb, 0xce, 0xb2, +0xf7, 0x6a, 0x3d, 0xc9, 0xb1, 0x00, 0x04, 0x8f, +0x60, 0xe1, 0x97, 0x8c, 0xec, 0xd4, 0xf2, 0x9f, +0xd3, 0x99, 0x75, 0xaf, 0xe1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0x56, +0xa4, 0x33, 0x3b, 0xc2, 0x79, 0xc8, 0x86, 0x93, +0x77, 0x41, 0xc2, 0xf5, 0xbb, 0xf5, 0xa4, 0xc2, +0xd9, 0x12, 0x1c, 0xd5, 0x76, 0xc6, 0xd3, 0x20, +0x54, 0x3d, 0x28, 0xd7, 0x54, 0xd2, 0xb5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0x02, 0xf5, 0xcb, 0xf1, 0x17, 0x41, 0xa7, +0x8f, 0x7b, 0xd6, 0x54, 0xf2, 0xbf, 0x22, 0xf9, +0xf6, 0x92, 0xda, 0x92, 0x87, 0x53, 0x40, 0xa3, +0x00, 0xa2, 0xd6, 0xdb, 0x10, 0x5a, 0x2b, 0x4a, +0x7a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0xf1, 0xac, 0x13, 0xa1, 0x72, +0xc9, 0x2d, 0x36, 0x93, 0xac, 0xf1, 0x88, 0xdc, +0x64, 0x0e, 0xd1, 0x99, 0xa8, 0x31, 0x73, 0x5f, +0xeb, 0x36, 0x7a, 0x1d, 0xad, 0x5a, 0x87, 0xf4, +0x68, 0xe1, 0xd1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x08, 0xe3, 0x20, +0xd5, 0xd4, 0x7a, 0x39, 0x23, 0x7f, 0x76, 0xea, +0xbb, 0x50, 0x21, 0xfd, 0x4b, 0xc3, 0x60, 0xb3, +0xb2, 0xc9, 0xbb, 0xaa, 0x3d, 0x77, 0x9c, 0xb9, +0x33, 0x37, 0x30, 0xc5, 0xbd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0x34, +0xee, 0x38, 0x8d, 0xca, 0x04, 0x75, 0xfa, 0x7f, +0x16, 0xaa, 0x45, 0xc7, 0xcb, 0x64, 0xac, 0x8b, +0xf5, 0x47, 0xcd, 0x98, 0xbe, 0x1e, 0xfc, 0xcc, +0x2b, 0x7b, 0x65, 0x25, 0xb7, 0xcd, 0x63, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0xc8, 0xe3, 0x0d, 0x23, 0xba, 0xc1, 0xf4, +0x88, 0xbb, 0x72, 0x10, 0x3f, 0x7a, 0x9f, 0xe2, +0x25, 0x2c, 0x49, 0x95, 0x10, 0x24, 0xdd, 0xbd, +0xce, 0x41, 0x7e, 0x4f, 0x3d, 0xa1, 0x05, 0x60, +0x3e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0x71, 0x65, 0x02, 0xeb, 0xb3, +0x1c, 0xf1, 0xfd, 0x26, 0x4c, 0x49, 0xda, 0x6a, +0x96, 0x3d, 0xda, 0xce, 0x59, 0x19, 0x34, 0xe5, +0x36, 0xeb, 0x7e, 0x45, 0xd5, 0x40, 0x75, 0x59, +0x4c, 0x62, 0x1f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0xe3, 0x7a, 0xdf, +0x21, 0xe4, 0x52, 0xad, 0xb6, 0x55, 0x70, 0xa6, +0x0a, 0x7a, 0x02, 0x54, 0x4f, 0x23, 0x78, 0x13, +0x3f, 0x68, 0xa5, 0xd2, 0xd2, 0x80, 0x86, 0x58, +0x9f, 0xf5, 0x17, 0x59, 0x1a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0xa8, +0x79, 0x87, 0x93, 0x00, 0x4e, 0x78, 0x1d, 0xa5, +0x0c, 0xb1, 0x0c, 0x9b, 0x66, 0xac, 0x68, 0xb3, +0x31, 0xd1, 0xbe, 0x84, 0x25, 0x8d, 0xfa, 0x70, +0xc9, 0xa8, 0x04, 0x52, 0x87, 0x9d, 0x9a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0xf4, 0xde, 0x12, 0x91, 0x0a, 0x6a, 0x09, +0x1f, 0x2b, 0xca, 0x58, 0x96, 0x0d, 0x67, 0xf5, +0x93, 0x7d, 0xa0, 0x4f, 0x5a, 0xca, 0x8e, 0x55, +0xa1, 0x98, 0xd0, 0xf8, 0xa8, 0x6d, 0xab, 0x60, +0x13, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x73, 0x87, 0xe0, 0xbe, 0x71, +0x0f, 0xf6, 0x43, 0xbd, 0xf4, 0x48, 0xc7, 0x19, +0x58, 0x95, 0x5f, 0xa3, 0xde, 0x34, 0xed, 0xf5, +0xdd, 0x23, 0x23, 0x7a, 0x67, 0x79, 0xf2, 0x7c, +0x19, 0xa6, 0xf8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0x9e, 0x3e, 0x7f, +0xdd, 0x45, 0xc9, 0xa7, 0xc7, 0x5f, 0x6d, 0x31, +0xcf, 0xd4, 0x11, 0xcd, 0x68, 0x81, 0xaf, 0x82, +0xa3, 0x7f, 0x41, 0xf4, 0x70, 0x96, 0x4d, 0xf9, +0x5d, 0xe8, 0x78, 0xe5, 0xb7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0x47, +0xdf, 0xef, 0xa8, 0xa1, 0x14, 0x75, 0x0f, 0xd0, +0xb9, 0xaf, 0x6e, 0xa4, 0xbc, 0x5d, 0x4c, 0xb6, +0x6b, 0x69, 0xfa, 0x2c, 0x28, 0x1c, 0x15, 0xc0, +0x22, 0xfd, 0x87, 0x08, 0x79, 0xde, 0xc5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0xee, 0x66, 0x40, 0x4b, 0x03, 0x7d, 0xff, +0x0c, 0x6f, 0xbb, 0x54, 0xaa, 0x41, 0xed, 0x99, +0x3a, 0x60, 0x72, 0x03, 0x8c, 0xbf, 0xb8, 0xe2, +0x51, 0x6d, 0xde, 0x41, 0x4a, 0xaf, 0xa6, 0xd5, +0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0xf7, 0x95, 0xb8, 0xec, 0x83, +0xdf, 0x91, 0x7a, 0x3f, 0x8c, 0xdf, 0xc2, 0xb1, +0xd3, 0xd9, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x7e, 0x9a, 0x87, 0x0f, +0xaf, 0x9f, 0x6a, 0x79, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0xe1, 0x57, 0x9f, +0xec, 0x33, 0xb5, 0xef, 0x8a, 0xe9, 0xc1, 0x8c, +0x2e, 0x8b, 0xa7, 0xc4, 0x8a, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, 0xf0, +0x79, 0x1f, 0x3a, 0x41, 0x92, 0x77, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0x09, +0x21, 0xc9, 0x31, 0x13, 0x3b, 0x7c, 0x0d, 0xc2, +0x93, 0xd2, 0x9b, 0xb0, 0x62, 0xfd, 0x87, 0xb0, +0x7f, 0xb6, 0x41, 0x09, 0xef, 0x37, 0xd2, 0x89, +0x7b, 0x19, 0xc5, 0xab, 0xfa, 0xdd, 0xe9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0xa7, 0x39, 0x21, 0x84, 0x0d, 0x7d, 0x7e, +0x57, 0x84, 0xa1, 0x48, 0xf0, 0xea, 0x1c, 0xeb, +0x75, 0x36, 0x2d, 0xd9, 0xd7, 0x0a, 0x08, 0x07, +0x0d, 0x42, 0xbf, 0xea, 0x98, 0xd5, 0x22, 0x44, +0x5f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0xce, 0x9e, 0x58, 0x38, 0x9a, +0xd6, 0xdb, 0x6c, 0x10, 0xe8, 0x87, 0xeb, 0xd0, +0x9b, 0x6e, 0xff, 0xea, 0x04, 0x0c, 0x89, 0x8e, +0x1c, 0x69, 0xc6, 0x4f, 0x12, 0x07, 0x06, 0x11, +0x36, 0xbc, 0xfa, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0x8c, 0xf4, 0xbd, +0xef, 0xe1, 0x17, 0xe7, 0x93, 0x85, 0xbe, 0xe3, +0x10, 0x05, 0x91, 0xea, 0xfa, 0x79, 0x1e, 0x89, +0x90, 0xb5, 0x96, 0xef, 0xa3, 0x5e, 0x02, 0x56, +0xd8, 0x2e, 0xfa, 0xf4, 0x91, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0xbe, +0x3c, 0x30, 0xb0, 0x06, 0xa3, 0xf5, 0x9c, 0x7b, +0xb2, 0xb2, 0x69, 0xf4, 0xdd, 0x42, 0x7f, 0x62, +0x61, 0xe9, 0x2c, 0x9c, 0x02, 0xc8, 0xe4, 0xc4, +0x0b, 0x76, 0xed, 0x5c, 0xe6, 0x19, 0x44, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0x1d, 0x9c, 0x4e, 0x20, 0x7c, 0x16, 0x26, +0xd3, 0x7b, 0x2b, 0x8e, 0x8b, 0x06, 0xf8, 0x9f, +0x4c, 0xf0, 0xcd, 0x88, 0x5f, 0xbe, 0xfa, 0x74, +0x55, 0x10, 0x1f, 0xb4, 0x8e, 0xf4, 0x7d, 0xa5, +0xb6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0xad, 0x9f, 0x98, 0x22, 0x65, +0x3f, 0x0e, 0x3c, 0x00, 0x2a, 0x74, 0x33, 0x72, +0x11, 0x48, 0x46, 0xec, 0x91, 0x6e, 0xb5, 0x0a, +0x10, 0x26, 0x40, 0x68, 0x6f, 0x7b, 0xc1, 0x20, +0x4e, 0x72, 0x2c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0x84, 0x90, 0xdd, +0x84, 0xfd, 0x8c, 0x7c, 0xe4, 0x00, 0x57, 0xaf, +0x15, 0x85, 0x11, 0x87, 0x4f, 0x5a, 0x02, 0x8b, +0x0c, 0xf0, 0xa4, 0x96, 0x3c, 0x34, 0x64, 0x9d, +0xaf, 0x3f, 0xe2, 0x3c, 0xf9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0x9c, +0xda, 0x24, 0xcc, 0x8d, 0xd3, 0x19, 0xa5, 0x4a, +0xaa, 0x74, 0xef, 0x79, 0xba, 0x93, 0xf0, 0x1a, +0x64, 0x35, 0x2d, 0xeb, 0x8c, 0x1e, 0x50, 0x95, +0xb6, 0x15, 0x24, 0xe4, 0xd5, 0x52, 0x39, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0xe4, 0xbc, 0xfe, 0xac, 0x99, 0xfa, 0xff, +0xb0, 0x73, 0xe8, 0xda, 0x7a, 0xe8, 0x66, 0x45, +0x91, 0xc2, 0xc8, 0xb5, 0xe9, 0x68, 0x03, 0x95, +0xf9, 0x1d, 0x4b, 0xb4, 0x1e, 0xd9, 0xb3, 0x43, +0x5c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0xed, 0x5f, 0x32, 0x7a, 0x1f, +0xb1, 0x82, 0xa9, 0x0a, 0xfc, 0xd0, 0xc5, 0xa4, +0x31, 0x1d, 0x6e, 0x30, 0x76, 0x90, 0xdc, 0x79, +0xcc, 0xaa, 0xd8, 0x3b, 0x89, 0x3c, 0x2b, 0x7e, +0x5a, 0x5a, 0x12, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0x86, 0x2d, 0xf5, +0xb8, 0x09, 0xea, 0x90, 0x20, 0xc1, 0x93, 0x20, +0x61, 0x6d, 0x56, 0xf6, 0x5a, 0x61, 0xd2, 0xd2, +0xb7, 0x06, 0x21, 0x6b, 0xa7, 0x4d, 0x81, 0xba, +0x32, 0x75, 0xc6, 0x45, 0x37, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0xb7, +0x49, 0x02, 0x04, 0x16, 0x5e, 0x18, 0x8d, 0x70, +0x97, 0xe0, 0x17, 0x78, 0x9f, 0x74, 0x17, 0x17, +0xb7, 0x3e, 0x8e, 0x5d, 0x39, 0x2c, 0xe1, 0x3a, +0xce, 0x66, 0x0f, 0x55, 0xdc, 0x60, 0x8e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0xaa, 0xc3, 0xb5, 0x6a, 0x8f, 0x0b, 0x08, +0x27, 0x49, 0x4d, 0x7b, 0x13, 0x76, 0xea, 0x55, +0x2e, 0x6d, 0x84, 0x07, 0xb0, 0xa2, 0xfe, 0x4a, +0x65, 0x1e, 0x86, 0x6b, 0xa9, 0xf0, 0x48, 0x6f, +0x12, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0x78, 0xc6, 0x89, 0x83, 0x7d, +0x08, 0xbb, 0x32, 0x82, 0xaa, 0xb2, 0x32, 0xca, +0x33, 0x29, 0xf9, 0xdb, 0x9a, 0x8f, 0x1c, 0x8c, +0x62, 0xe4, 0xd1, 0xd9, 0xdb, 0xdd, 0xa3, 0x64, +0x2e, 0xf4, 0xfb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0x13, 0x8e, 0xd8, +0xeb, 0x99, 0xc2, 0xe5, 0x49, 0xbe, 0x3a, 0x24, +0x93, 0xea, 0xe4, 0x5d, 0x9a, 0xca, 0x59, 0xb4, +0x39, 0x18, 0xdb, 0x6c, 0x9a, 0xcb, 0x2d, 0xd7, +0x1d, 0x7b, 0xef, 0xb7, 0xae, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0xc0, +0xf0, 0xa1, 0xd8, 0xdd, 0xaa, 0x83, 0x1e, 0x0e, +0x10, 0x23, 0x5d, 0x63, 0x17, 0xaa, 0xac, 0xa0, +0x54, 0x7a, 0xfa, 0xc0, 0x70, 0x55, 0x1d, 0x95, +0x27, 0x53, 0x33, 0xeb, 0x9f, 0x98, 0x4c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0x0a, 0xd4, 0xae, 0x26, 0x8f, 0xb0, 0xc8, +0x88, 0xd9, 0x03, 0xef, 0x4e, 0x8b, 0x9f, 0x2a, +0x19, 0xf9, 0x1c, 0x29, 0xaa, 0x8a, 0x09, 0xcd, +0xca, 0x81, 0xfe, 0x08, 0x90, 0xd0, 0xfd, 0x56, +0x9f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0x2c, 0xaa, 0x95, 0x14, 0x02, +0xb8, 0x1b, 0xc2, 0xbb, 0x2e, 0xb4, 0xe3, 0xdf, +0x78, 0x07, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x07, 0x03, 0x54, 0x2b, +0xd1, 0x19, 0x9b, 0xad, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0x1e, 0x32, 0xff, +0x38, 0x28, 0x08, 0x94, 0xa5, 0x51, 0x95, 0x75, +0x03, 0x93, 0xfd, 0xd9, 0x88, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0xc0, +0xfe, 0xcd, 0x6b, 0xaf, 0x9e, 0x0b, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x58, +0xef, 0xe3, 0x3c, 0x3f, 0xa1, 0xa0, 0xf5, 0x28, +0xc0, 0xda, 0x0c, 0x80, 0xdf, 0xe8, 0x69, 0x42, +0x87, 0x8a, 0xbd, 0x88, 0x70, 0x6c, 0x6e, 0xa7, +0x60, 0x4e, 0x59, 0x36, 0xa9, 0xec, 0x63, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0xc8, 0xb4, 0x15, 0xb0, 0xf5, 0x70, 0x1b, +0x9d, 0x9e, 0x95, 0xe7, 0x7f, 0xe5, 0x79, 0x24, +0xcb, 0x2b, 0xe9, 0x5c, 0xe1, 0x38, 0x0c, 0xb6, +0x96, 0x70, 0xd9, 0xe1, 0x0d, 0x32, 0x83, 0x0d, +0x88, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0x22, 0x6b, 0xb4, 0xac, 0x34, +0x6d, 0xef, 0x2e, 0x15, 0x49, 0x9a, 0x63, 0xe4, +0x87, 0x51, 0xfe, 0x1b, 0xf1, 0x3c, 0xd6, 0x46, +0x19, 0x28, 0xf4, 0x78, 0x6a, 0x5a, 0x2e, 0x0b, +0x86, 0x39, 0x7d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0x7b, 0xad, 0xa3, +0x6a, 0x46, 0x53, 0x60, 0x5c, 0x1b, 0x60, 0x40, +0x77, 0xca, 0x40, 0x4d, 0x0d, 0x7e, 0xec, 0x29, +0x1f, 0x31, 0x40, 0x4a, 0x12, 0xc9, 0x9c, 0x93, +0xc8, 0xa4, 0x03, 0xa0, 0x9f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0xee, +0xc1, 0x38, 0x25, 0x2e, 0x04, 0x9b, 0xf9, 0x98, +0x37, 0xb7, 0x82, 0x2d, 0x58, 0x5d, 0x06, 0x3a, +0x1b, 0x08, 0x13, 0xa7, 0x36, 0x74, 0x01, 0xa5, +0xb3, 0x5b, 0xe7, 0x49, 0xec, 0xa2, 0x59, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0xc8, 0xb8, 0xb0, 0xd3, 0x58, 0x87, 0x20, +0x78, 0x29, 0x1f, 0x61, 0xa1, 0xce, 0x18, 0xb6, +0x82, 0x74, 0x52, 0x58, 0x4e, 0x2f, 0xab, 0x23, +0x1b, 0xfc, 0xb1, 0xb4, 0x5c, 0x2c, 0x66, 0xb6, +0xff, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0xf2, 0xf7, 0xed, 0x8b, 0xe4, +0xc9, 0x67, 0xb6, 0xbd, 0x63, 0xfa, 0x44, 0xf5, +0x4a, 0x28, 0xa2, 0xcd, 0xfa, 0xac, 0x81, 0x1a, +0x57, 0x60, 0x3f, 0x55, 0x52, 0x44, 0xbc, 0x06, +0x01, 0xdd, 0x35, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x89, 0xdc, 0x63, +0x49, 0xcc, 0x2c, 0xd4, 0xf6, 0x22, 0x57, 0x61, +0x10, 0xc7, 0xa5, 0x5a, 0xef, 0x85, 0xfb, 0xe2, +0x58, 0xb2, 0x0d, 0x29, 0x6b, 0xe1, 0x40, 0x6c, +0x57, 0xa7, 0x99, 0x98, 0x37, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0xa2, +0xaa, 0xbd, 0x31, 0xad, 0x9d, 0xe4, 0x57, 0x91, +0xcd, 0x08, 0xf6, 0x31, 0x79, 0xcb, 0xd4, 0x7b, +0x94, 0x19, 0x9c, 0xdd, 0xac, 0x9c, 0xff, 0x84, +0x74, 0x59, 0x2d, 0xbd, 0x4c, 0xd3, 0xe8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0xff, 0x9c, 0xbc, 0x34, 0xb4, 0x32, 0x62, +0xbe, 0x62, 0x48, 0x6d, 0x07, 0xd3, 0xf3, 0xc0, +0xd6, 0xab, 0x5e, 0xf8, 0x51, 0xae, 0x90, 0xff, +0xe2, 0x86, 0x2f, 0xc4, 0x7d, 0xb2, 0x5d, 0x98, +0xbc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0xdb, 0x26, 0xb7, 0xb1, 0xaf, +0x42, 0x9f, 0x56, 0x8c, 0x83, 0x8f, 0x72, 0xc0, +0x62, 0x9c, 0x54, 0xf0, 0x5f, 0xc7, 0x83, 0x71, +0x35, 0xa6, 0x69, 0x60, 0x9e, 0x32, 0x6f, 0x45, +0x95, 0x06, 0xae, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0xbd, 0x9d, 0x9a, +0x81, 0x86, 0x3f, 0x01, 0x0a, 0x92, 0x06, 0xfa, +0xb5, 0xa3, 0x8b, 0xc0, 0x07, 0xe6, 0xe4, 0x45, +0x66, 0xe2, 0x89, 0xdb, 0xdd, 0x3b, 0x4d, 0x2e, +0x73, 0x3d, 0x70, 0x41, 0x59, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0xc5, +0x6a, 0x1d, 0x50, 0x1b, 0xf9, 0x82, 0x62, 0x7a, +0x4d, 0xed, 0x3a, 0x88, 0x20, 0xd9, 0x45, 0x5e, +0x76, 0xd5, 0x88, 0x80, 0x64, 0xf4, 0xa6, 0x46, +0x32, 0x8d, 0xce, 0xe1, 0xfe, 0xc5, 0x01, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0x7c, 0xd9, 0x69, 0xa1, 0x44, 0xe6, 0x63, +0xa3, 0xde, 0xe9, 0xf2, 0xb9, 0xee, 0x24, 0x25, +0xb4, 0x6a, 0x77, 0x00, 0xef, 0x13, 0x38, 0xe1, +0xa9, 0x8f, 0x29, 0x9a, 0x97, 0x2a, 0x44, 0xee, +0xca, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0x8a, 0xa9, 0x4c, 0x8b, 0x79, +0x22, 0x54, 0x7d, 0x86, 0xae, 0x89, 0xef, 0x03, +0xdc, 0x87, 0xe1, 0xfd, 0xbc, 0xc2, 0x5c, 0xc4, +0x1f, 0x5a, 0xd8, 0xbc, 0xf8, 0x6e, 0x30, 0x4c, +0x07, 0x75, 0xc7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0x50, 0xc0, 0x04, +0x13, 0xb2, 0xa5, 0x7a, 0xf6, 0x32, 0x9e, 0x9a, +0x85, 0xe1, 0xf8, 0xff, 0x04, 0x42, 0x4f, 0x73, +0x60, 0x74, 0xcd, 0xfd, 0xd0, 0xb3, 0x6a, 0x3c, +0x0f, 0xd8, 0x15, 0x1b, 0x40, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x40, +0x1a, 0x39, 0xba, 0xd5, 0x44, 0xd2, 0x00, 0x74, +0xc8, 0x8e, 0x01, 0x47, 0x82, 0xf1, 0x5a, 0x83, +0x24, 0xf7, 0xeb, 0x07, 0xd7, 0x81, 0xb3, 0xe6, +0x4b, 0xd8, 0xd9, 0x3e, 0xca, 0x3d, 0x36, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0xa4, 0x34, 0x14, 0x25, 0x5b, 0x42, 0x07, +0x77, 0x13, 0x39, 0xe4, 0x5e, 0x76, 0x79, 0xf4, +0xdd, 0x0e, 0xa8, 0xfa, 0xe5, 0x60, 0x4c, 0x2c, +0x18, 0xf1, 0x28, 0xdf, 0x73, 0x54, 0xa4, 0x39, +0xe2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0x1c, 0xf5, 0x1c, 0x52, 0x5a, +0x6d, 0x30, 0x84, 0x34, 0xbe, 0x4f, 0xc9, 0x41, +0xba, 0xfa, 0xce, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0xca, 0xd6, +0xca, 0x3e, 0xb5, 0x93, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0x28, 0x43, 0xac, +0x26, 0x7f, 0xd2, 0xa6, 0x8e, 0x13, 0xa1, 0xdf, +0x1b, 0xfc, 0x7c, 0xb4, 0xe9, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x91, 0x27, +0x6a, 0x82, 0xd2, 0xe7, 0x72, 0x0a, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0xf2, +0xd2, 0x3b, 0x01, 0x89, 0x75, 0x4b, 0xbe, 0x16, +0xce, 0x4e, 0x97, 0x95, 0x98, 0x15, 0x12, 0x98, +0x54, 0xad, 0xe6, 0x0c, 0x48, 0x25, 0xb2, 0x61, +0x50, 0x92, 0xbb, 0x48, 0x6d, 0xa0, 0x9a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0x16, 0x00, 0x9f, 0xb0, 0x5b, 0x19, 0x89, +0x22, 0x5e, 0x76, 0x39, 0x7d, 0x2c, 0x56, 0x7b, +0x17, 0x2f, 0x43, 0x9e, 0xc2, 0xad, 0x3f, 0x26, +0x97, 0x28, 0xb5, 0x05, 0x36, 0xac, 0x99, 0x30, +0xd3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0xf3, 0xd4, 0xd7, 0x5b, 0x50, +0x04, 0xee, 0x35, 0x81, 0x07, 0xd6, 0x85, 0x38, +0x23, 0x2f, 0xe0, 0x99, 0xfa, 0xa7, 0x1d, 0xfe, +0xe2, 0x7b, 0xc4, 0xa7, 0x43, 0x4c, 0x7c, 0x89, +0x1a, 0x68, 0x89, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0x48, 0x4a, 0xb7, +0x52, 0x08, 0xd7, 0x42, 0xd9, 0xc0, 0xf6, 0x7d, +0x25, 0xfd, 0x5e, 0x62, 0x86, 0x4f, 0x18, 0x7a, +0x76, 0x60, 0xd3, 0x4c, 0xf1, 0x96, 0x7e, 0xc0, +0xfc, 0x80, 0x60, 0xe2, 0x60, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0x49, +0x13, 0x58, 0x16, 0x11, 0xc6, 0xf8, 0xd8, 0xe3, +0x71, 0x19, 0x49, 0x19, 0xd7, 0x75, 0x04, 0xf2, +0xfb, 0x77, 0x26, 0x08, 0x32, 0xf7, 0x5b, 0x26, +0x90, 0x9b, 0xd9, 0xe4, 0x01, 0x54, 0xb7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0x44, 0x2d, 0xaf, 0xaa, 0xde, 0xbe, 0x36, +0x52, 0x30, 0x45, 0x01, 0x88, 0x88, 0xed, 0xd4, +0x56, 0x98, 0xce, 0x74, 0xaf, 0xb0, 0x29, 0x52, +0x22, 0x77, 0x09, 0x6f, 0x99, 0x7e, 0x62, 0xea, +0xfa, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0xcc, 0x0d, 0x64, 0x12, 0xb3, +0xd3, 0x5d, 0xc4, 0xbd, 0x23, 0x58, 0x5f, 0x1f, +0xf1, 0xfa, 0xa1, 0x65, 0x45, 0x92, 0x03, 0x6f, +0x2f, 0x09, 0xbd, 0xb3, 0xc9, 0x32, 0xda, 0x81, +0x66, 0xfb, 0x8d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0xf4, 0xdf, 0x53, +0x5a, 0xc8, 0xd1, 0x20, 0x28, 0x0c, 0xd8, 0x8f, +0xc3, 0xab, 0xf4, 0xc9, 0x6c, 0x89, 0x65, 0x27, +0x6d, 0x1f, 0xc7, 0x87, 0xce, 0x93, 0xa9, 0x51, +0xe9, 0x40, 0x9e, 0x4c, 0x01, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0x4b, +0x99, 0x42, 0x28, 0xc7, 0x80, 0xd4, 0x38, 0x89, +0xa5, 0xcb, 0xbe, 0x8b, 0x09, 0x7e, 0x9e, 0x3f, +0x0d, 0x97, 0x1d, 0x97, 0x4b, 0xa0, 0x31, 0x3f, +0xbf, 0x4c, 0xc2, 0xd5, 0xd2, 0x78, 0xe3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0xa5, 0x25, 0x42, 0xcf, 0x47, 0xd3, 0x15, +0x16, 0x06, 0x6e, 0xf2, 0xdb, 0xde, 0xdc, 0xd8, +0xa8, 0x91, 0x83, 0xdc, 0x57, 0xdd, 0xa5, 0xbb, +0xe3, 0x37, 0xad, 0xa4, 0x40, 0xd6, 0x25, 0x21, +0x0a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0x38, 0x54, 0xce, 0x47, 0x22, +0x5b, 0x2e, 0x19, 0x75, 0xf9, 0xaa, 0x9c, 0x04, +0xbb, 0x92, 0x16, 0x01, 0x8b, 0x04, 0x00, 0x71, +0xbb, 0x51, 0x47, 0xf9, 0x20, 0x6d, 0x68, 0xd3, +0x83, 0x59, 0x3b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x1c, 0xd3, 0xc3, +0xaa, 0x24, 0x6d, 0xf7, 0xd4, 0x36, 0x40, 0x1c, +0xc1, 0x02, 0x56, 0x42, 0x6e, 0xdc, 0xf8, 0x16, +0x7c, 0x4f, 0x65, 0x56, 0x9c, 0xb5, 0xff, 0xf2, +0x8c, 0xb9, 0xd2, 0x4b, 0x7d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x65, +0x03, 0xf3, 0x9f, 0xfb, 0x3e, 0xab, 0xac, 0x95, +0x2a, 0x3c, 0x0f, 0x88, 0x62, 0xd4, 0xf0, 0xe7, +0x2e, 0x85, 0xd5, 0xb2, 0xa9, 0x41, 0x02, 0xd1, +0xd9, 0x1b, 0x44, 0xd9, 0xc9, 0x4c, 0xb5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0xdb, 0x4a, 0x19, 0xde, 0x35, 0x4e, 0xad, +0xe1, 0x36, 0x05, 0xb7, 0x31, 0x4b, 0x71, 0x8c, +0x8a, 0xb7, 0x19, 0xeb, 0xd7, 0xcb, 0xcf, 0x6e, +0x0e, 0x7a, 0x14, 0x0d, 0x91, 0xd9, 0xb5, 0xe8, +0x42, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0xe1, 0xe0, 0xdb, 0x5b, 0xca, +0x63, 0x4a, 0xf5, 0x97, 0x32, 0x44, 0xef, 0xc7, +0xef, 0x26, 0x2f, 0xda, 0x7a, 0xe6, 0xd4, 0xb7, +0x47, 0x12, 0xee, 0x49, 0x8e, 0xb9, 0x7d, 0xfe, +0x81, 0x00, 0x7a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0x35, 0xc9, 0xc3, +0x82, 0x3c, 0x21, 0x21, 0x90, 0x9a, 0xb8, 0xbe, +0x26, 0xbe, 0x45, 0x20, 0xed, 0x24, 0xa5, 0x43, +0x0a, 0x93, 0x42, 0xa2, 0x6d, 0xc8, 0xfa, 0xd7, +0x80, 0x4a, 0xc0, 0x0b, 0x15, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0xbe, +0xda, 0x1f, 0x8d, 0x25, 0x55, 0x96, 0xb3, 0x4b, +0xe1, 0x62, 0x61, 0x98, 0xfb, 0xa9, 0xb4, 0x5d, +0x01, 0x6f, 0x7a, 0xf3, 0xe8, 0xad, 0xd5, 0x78, +0xeb, 0x45, 0x9b, 0xe4, 0xac, 0x45, 0x48, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0x2f, 0x2b, 0xd4, 0xb8, 0x82, 0xaf, 0x03, +0x09, 0xbf, 0xfa, 0xa6, 0x3f, 0x33, 0xd5, 0xb9, +0xe8, 0xef, 0xe4, 0x3e, 0x3f, 0x3d, 0x5d, 0x7c, +0x22, 0x48, 0x0a, 0xb8, 0x4c, 0xe4, 0x9f, 0x83, +0x09, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0xa3, 0xcd, 0x26, 0xe2, 0x8b, +0x37, 0xdf, 0x32, 0xd4, 0xb8, 0xcf, 0xff, 0xe2, +0x36, 0x61, 0xe2, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x41, 0x43, 0xa0, 0x89, +0xb8, 0x59, 0x61, 0x8d, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0xd8, 0x9e, 0x42, +0xdf, 0x03, 0x0b, 0x10, 0x29, 0xbf, 0xce, 0x5a, +0x8c, 0x07, 0x58, 0x55, 0x6d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdc, 0xa1, +0xd4, 0x69, 0x3b, 0x44, 0x87, 0xa2, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0x30, +0xd3, 0xce, 0xcc, 0x8c, 0xfd, 0x52, 0x54, 0x75, +0x76, 0x9e, 0x0c, 0x1e, 0x73, 0xa5, 0xcb, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0xf6, 0xbb, 0x7e, 0x9b, 0xfd, 0x09, 0xd4, 0x68, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x00 +}; diff --git a/include/linux/mfd/max77705C_pass2.h b/include/linux/mfd/max77705C_pass2.h new file mode 100644 index 000000000000..8e3a106c5669 --- /dev/null +++ b/include/linux/mfd/max77705C_pass2.h @@ -0,0 +1,6635 @@ +const uint8_t BOOT_FLASH_FW_PASS2[] = { +0xc1, 0x66, 0xf1, 0xce, 0x18, 0x10, 0x15, 0x02, +0x01, 0x22, 0x21, 0x70, 0x01, 0x02, 0x03, 0x04, +0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, +0x0d, 0x0e, 0x0f, 0x00, 0x18, 0x10, 0x15, 0x02, +0x03, 0x00, 0x00, 0x00, 0x02, 0xb3, 0x25, 0x3c, +0x2b, 0x84, 0x47, 0x53, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0x20, 0xa9, 0xe8, +0x91, 0x2c, 0xdf, 0x69, 0xb7, 0x95, 0x9d, 0xa4, +0x69, 0x33, 0xed, 0x4a, 0x1d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x5f, +0x39, 0xe5, 0x7b, 0xb9, 0xca, 0xfe, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0xa3, +0x77, 0x21, 0x0f, 0x6b, 0x5f, 0x61, 0xea, 0xa1, +0xb1, 0xa0, 0x15, 0x12, 0x7f, 0xe0, 0x11, 0xad, +0x8b, 0x4f, 0x66, 0x3e, 0xa5, 0x50, 0x0f, 0x86, +0x56, 0x93, 0xda, 0x1c, 0x3a, 0x9a, 0x29, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0x52, 0x00, 0x12, 0x65, 0xd6, 0x64, 0x4a, +0x2f, 0xff, 0x98, 0x92, 0x75, 0xc1, 0x26, 0xe8, +0xf4, 0x77, 0x63, 0x4c, 0xc1, 0x4d, 0x30, 0xe3, +0x70, 0xab, 0xbb, 0xa3, 0x45, 0x86, 0x39, 0x39, +0x15, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0xe2, 0xb3, 0x30, 0xfe, 0x9e, +0x6b, 0xbf, 0x92, 0xe2, 0x58, 0x37, 0x97, 0xc7, +0x59, 0x2b, 0x2b, 0xf4, 0xc5, 0x49, 0x09, 0x71, +0x8a, 0x74, 0xdd, 0xed, 0x4d, 0xc1, 0x1a, 0x35, +0x4f, 0xf4, 0x24, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0x6a, 0x08, 0xdd, +0x10, 0xa9, 0x95, 0x72, 0xe6, 0x59, 0xe8, 0x85, +0x1a, 0x6b, 0x44, 0x6b, 0x1a, 0x95, 0x8b, 0x08, +0x43, 0x2d, 0x88, 0x01, 0x67, 0x75, 0x17, 0xf4, +0xc6, 0x51, 0x58, 0xac, 0x0b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0x3d, +0x32, 0xa9, 0x9b, 0x20, 0x40, 0xdf, 0x48, 0xf2, +0xe6, 0x13, 0xf4, 0x9c, 0x89, 0xf3, 0xf3, 0xa2, +0x98, 0xc3, 0xd1, 0xae, 0x29, 0x97, 0x4e, 0x97, +0xf9, 0x1e, 0x5c, 0xf9, 0x1d, 0xf7, 0x88, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0x7f, 0x5d, 0xe8, 0x8a, 0xfe, 0xf0, 0xba, +0xac, 0x6d, 0x66, 0xa4, 0x4a, 0xf9, 0xdd, 0xb7, +0x52, 0x94, 0xa2, 0xd1, 0xf5, 0x61, 0xec, 0x6e, +0xb0, 0xdd, 0x70, 0x1e, 0x4b, 0xe3, 0xd8, 0xb5, +0x53, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0x0b, 0xe7, 0x33, 0xc4, 0xf9, +0x17, 0x9f, 0x18, 0x20, 0x61, 0x07, 0xff, 0x2a, +0xdb, 0xc2, 0xd6, 0x7d, 0x30, 0x61, 0xe6, 0x69, +0x7b, 0x10, 0x86, 0x36, 0x87, 0x5e, 0xef, 0x26, +0xaf, 0xe1, 0x76, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0xb8, 0x83, 0xb6, +0x88, 0xe9, 0x61, 0xef, 0xdb, 0xe3, 0xfc, 0x2d, +0x64, 0x8b, 0x6b, 0x6a, 0x66, 0x87, 0x01, 0xb2, +0x67, 0xe1, 0xe3, 0xe0, 0xd0, 0x50, 0xf4, 0x18, +0x3b, 0xd7, 0xe9, 0xbd, 0x4a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0xf7, +0xaf, 0x94, 0xe9, 0x33, 0x2c, 0xf4, 0x3c, 0x01, +0xd4, 0x9f, 0x3b, 0xf2, 0x22, 0x50, 0x0f, 0x59, +0x83, 0x07, 0xd0, 0x67, 0xce, 0x00, 0xcd, 0xe8, +0x1b, 0x94, 0xe6, 0xbc, 0xc1, 0x5d, 0x35, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x90, 0x31, 0x14, 0x0f, 0xaa, 0x9d, 0x4c, +0xab, 0x32, 0x47, 0x2c, 0xaa, 0xc2, 0x6a, 0x6d, +0x46, 0x81, 0x71, 0xfd, 0x42, 0xe7, 0xbd, 0x75, +0xe0, 0xfa, 0x4d, 0xcd, 0x23, 0xe1, 0x99, 0x91, +0xec, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0x17, 0x81, 0xc1, 0x1e, 0x24, +0x43, 0x1b, 0x8e, 0xca, 0x19, 0xf7, 0x4a, 0x58, +0xe7, 0x42, 0xfa, 0x10, 0xa9, 0x50, 0xae, 0x2d, +0xbe, 0x5f, 0xd7, 0x5a, 0xb6, 0x5e, 0x82, 0xd9, +0x8e, 0x9a, 0x59, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0x6f, 0x86, 0xdd, +0xb5, 0x65, 0xfe, 0xa9, 0x88, 0x19, 0xf7, 0x53, +0x7a, 0x8f, 0x5e, 0x1e, 0x86, 0x38, 0xb2, 0x37, +0xb0, 0x2c, 0x24, 0x7d, 0x46, 0x96, 0x85, 0x7b, +0x04, 0x0c, 0x0e, 0x0a, 0xce, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0xa1, +0x26, 0x02, 0x25, 0x52, 0x8e, 0x98, 0xdc, 0x7b, +0xdf, 0x5e, 0x42, 0x5a, 0x4a, 0x17, 0xe8, 0x18, +0x7e, 0x95, 0xdd, 0x74, 0xbd, 0x43, 0xe3, 0x43, +0xf3, 0x9e, 0xb0, 0x88, 0xdf, 0x97, 0x30, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0xab, 0x8e, 0x5e, 0x9e, 0x83, 0xb9, 0x79, +0x4b, 0xb2, 0x82, 0x92, 0xd3, 0x5d, 0x8c, 0x4b, +0xcd, 0x4a, 0xb8, 0xe4, 0xff, 0x91, 0x24, 0xf1, +0x15, 0xe7, 0x17, 0x7f, 0x9d, 0x08, 0x22, 0x86, +0x8f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0xa1, 0x76, 0x78, 0xf5, 0xd6, +0xf7, 0xc1, 0x6c, 0xf5, 0x04, 0x99, 0x64, 0x06, +0x59, 0x34, 0x77, 0xf4, 0xd7, 0x7b, 0x30, 0x4b, +0xef, 0x13, 0xbb, 0xe5, 0x1e, 0x53, 0x48, 0x7a, +0xb1, 0x62, 0x8a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0x8a, 0x9a, 0x26, +0x48, 0x62, 0xa3, 0x51, 0x84, 0xb8, 0x0d, 0xa5, +0x46, 0x92, 0x08, 0x24, 0xf0, 0xa7, 0xa5, 0xf9, +0xcf, 0x57, 0x83, 0x2e, 0xd9, 0xa4, 0xeb, 0xd1, +0xe2, 0xe3, 0x75, 0x23, 0xe3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x3d, +0x8a, 0xa7, 0x3c, 0x37, 0x7b, 0x08, 0xd7, 0xa5, +0x1b, 0x90, 0x17, 0x70, 0xc0, 0x66, 0xcf, 0x6c, +0x9e, 0x33, 0xd5, 0xed, 0xe9, 0x18, 0xcc, 0x82, +0xf7, 0x67, 0x58, 0x6f, 0x28, 0x47, 0xb6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0xc9, 0xa8, 0x82, 0xc1, 0x88, 0x4a, 0x6a, +0x74, 0x98, 0x3a, 0xbc, 0x7d, 0x9b, 0x41, 0x88, +0x1e, 0xb2, 0x48, 0xae, 0xe3, 0x20, 0x9f, 0x98, +0x64, 0x24, 0x04, 0xa0, 0x25, 0xc1, 0x50, 0x06, +0x8a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0x97, 0x84, 0x37, 0xce, 0x9b, +0xf0, 0xdb, 0x7c, 0xdf, 0x7e, 0x6e, 0x47, 0xf7, +0x86, 0xcb, 0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x6b, 0x29, 0x89, 0x8c, +0x48, 0x9c, 0x33, 0xde, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0x2b, 0x37, 0x57, +0xda, 0xbe, 0x61, 0xe4, 0x5f, 0x09, 0x4c, 0xb5, +0xad, 0x18, 0x1c, 0x03, 0xdd, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb5, 0x57, +0x92, 0x83, 0x14, 0x65, 0x13, 0xbf, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0xaf, +0xec, 0x56, 0x0e, 0x0b, 0xa8, 0xd3, 0xb2, 0xec, +0xe0, 0x46, 0x80, 0x75, 0xe3, 0x8b, 0x38, 0x78, +0x37, 0xca, 0xc0, 0xe5, 0xee, 0xb7, 0xb0, 0xaa, +0xd2, 0x95, 0xb0, 0x8b, 0x5b, 0x8a, 0x50, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0x9d, 0xed, 0x6b, 0x80, 0xf4, 0x81, 0x8a, +0xd0, 0xb3, 0x20, 0xa4, 0x61, 0x32, 0x2f, 0x0c, +0xc5, 0x58, 0x57, 0xf3, 0x13, 0xeb, 0x31, 0xe0, +0xed, 0x57, 0x60, 0x8b, 0xff, 0xcd, 0x2a, 0x08, +0x47, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0x2e, 0xe8, 0x4c, 0x17, 0x06, +0xb5, 0x94, 0x6c, 0x1b, 0x67, 0x23, 0x77, 0xac, +0x53, 0x6d, 0xe9, 0x7f, 0xb4, 0x25, 0xf9, 0x14, +0xda, 0xb6, 0xfe, 0x79, 0x52, 0x6c, 0xb9, 0x55, +0x8b, 0xf0, 0x61, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x6e, 0x44, 0xcb, +0x62, 0xc0, 0x9c, 0x67, 0xb7, 0x89, 0x92, 0x3f, +0x90, 0x2b, 0x55, 0x69, 0x98, 0x40, 0xa3, 0xa1, +0x90, 0xf3, 0xc5, 0x80, 0xc1, 0x01, 0x8d, 0x67, +0xf3, 0xd1, 0x22, 0xc0, 0x5d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0x8a, +0xe9, 0x22, 0x5d, 0x70, 0xc7, 0x32, 0xe5, 0xdc, +0xb4, 0xdc, 0x44, 0xcf, 0x20, 0x36, 0x68, 0x48, +0x99, 0x70, 0x39, 0x68, 0x0c, 0xb8, 0x77, 0x7b, +0x19, 0x48, 0x3c, 0x0b, 0x06, 0x00, 0x0e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0x50, 0xed, 0x62, 0xce, 0x8c, 0xe9, 0x29, +0x62, 0x23, 0x2a, 0xa5, 0xcc, 0x9c, 0x47, 0xd7, +0xd5, 0xeb, 0xad, 0x5c, 0xd1, 0x56, 0x1d, 0x9b, +0xdc, 0xad, 0x68, 0xfe, 0x2d, 0x9a, 0xfc, 0xea, +0x17, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0xfb, 0x73, 0x9a, 0xd5, 0xdf, +0x9a, 0x37, 0x18, 0x25, 0xe7, 0xb4, 0x7a, 0x16, +0x44, 0x04, 0xcc, 0x33, 0xc1, 0xb6, 0x56, 0xce, +0xd9, 0xea, 0x3a, 0x1c, 0x2d, 0xba, 0xa7, 0x5f, +0x8f, 0xba, 0x60, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0x79, 0x25, 0x1c, +0x3a, 0x96, 0xa7, 0x41, 0xcc, 0x74, 0x76, 0x82, +0x28, 0x5b, 0x5c, 0x37, 0x11, 0xf3, 0xb5, 0xb5, +0xde, 0x74, 0x32, 0xa9, 0x1c, 0x6a, 0x8c, 0x70, +0x49, 0x6d, 0x9c, 0x2d, 0x1e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0x3e, +0x97, 0x04, 0x6a, 0x76, 0x3d, 0x7a, 0x43, 0x03, +0xa2, 0x7f, 0x4c, 0x87, 0xa8, 0xca, 0xfc, 0x83, +0x21, 0xb1, 0xc4, 0xc6, 0xb7, 0x3c, 0x7d, 0x85, +0xc5, 0x53, 0xd8, 0xca, 0x06, 0x7d, 0x9a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0xd8, 0x4d, 0x9f, 0x4b, 0xbd, 0x7f, 0x10, +0xd3, 0x4b, 0x44, 0xa3, 0xa5, 0x62, 0x42, 0x6e, +0x18, 0xcc, 0x26, 0xb2, 0x8f, 0x07, 0xd3, 0x57, +0xdf, 0xac, 0x77, 0x6c, 0x3e, 0x69, 0xf8, 0xb3, +0x68, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0xbd, 0xde, 0x72, 0x00, 0x7e, +0xac, 0x20, 0x9d, 0x83, 0x88, 0x35, 0xa3, 0xa3, +0x4a, 0x40, 0x66, 0x27, 0xfd, 0x9d, 0xdb, 0x59, +0x73, 0xea, 0xde, 0x80, 0xf5, 0x36, 0x66, 0x53, +0xfd, 0xff, 0x49, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0x81, 0x62, 0x93, +0xb3, 0x76, 0x00, 0xc8, 0x21, 0x48, 0xe1, 0xd5, +0x16, 0x0a, 0xe6, 0x35, 0x46, 0x2a, 0x13, 0x81, +0x43, 0xe3, 0xdc, 0x49, 0xb0, 0xa4, 0x8a, 0x0e, +0x60, 0xe7, 0x27, 0x17, 0xf1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0x13, +0x1d, 0x6b, 0x7b, 0xc0, 0x2c, 0x0b, 0xd0, 0xd3, +0xd3, 0xf3, 0xd4, 0x97, 0x05, 0x5e, 0x00, 0xe0, +0xb2, 0x02, 0xaf, 0x1d, 0xcf, 0x31, 0x7f, 0x24, +0x00, 0xb9, 0x0f, 0xcf, 0x7e, 0xfb, 0x42, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x88, 0x51, 0x06, 0x75, 0x5a, 0xdd, 0x93, +0xf4, 0xe3, 0x62, 0x32, 0x4c, 0x84, 0x24, 0x97, +0xad, 0xef, 0x76, 0x0c, 0x4f, 0x0b, 0x07, 0xc3, +0xb6, 0xf7, 0x08, 0xd6, 0xfc, 0x58, 0xfd, 0xca, +0x72, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0x0d, 0x24, 0x68, 0x08, 0xe9, +0xdf, 0xe8, 0x15, 0x67, 0x32, 0xe8, 0xa9, 0x50, +0xe1, 0x8c, 0x70, 0x4f, 0x1a, 0xa4, 0xe0, 0x47, +0x70, 0x47, 0x94, 0x52, 0x73, 0x36, 0xc4, 0xce, +0x45, 0x55, 0xeb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0xfb, 0xc4, 0x88, +0x09, 0x6d, 0xec, 0x9b, 0x0b, 0x9a, 0x55, 0xe7, +0x19, 0x8a, 0x76, 0xaf, 0x53, 0x8a, 0x43, 0x4b, +0xe1, 0xb7, 0xbe, 0xe7, 0xa8, 0xce, 0x2b, 0x2b, +0x20, 0x8f, 0x57, 0x84, 0xbf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x8d, +0x19, 0xae, 0xe6, 0xd9, 0xfe, 0x63, 0xc5, 0x3e, +0xc3, 0xe9, 0x9c, 0xf1, 0x09, 0x86, 0x33, 0x78, +0x05, 0x8e, 0xf2, 0x8a, 0x5d, 0x8d, 0x0c, 0xc2, +0xf9, 0x96, 0xb4, 0x52, 0x7a, 0x1a, 0xe3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0x66, 0x63, 0x3d, 0xa6, 0x9d, 0x09, 0x73, +0x2b, 0x0e, 0x47, 0x6d, 0xb7, 0xd5, 0x79, 0x82, +0x46, 0x4c, 0x08, 0xb2, 0xeb, 0x6c, 0x3b, 0xb5, +0xc0, 0x2e, 0xff, 0x9d, 0x84, 0x32, 0x8f, 0xf4, +0x3b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0xc2, 0x89, 0x15, 0x81, 0x53, +0x17, 0x45, 0x19, 0x25, 0x0c, 0x01, 0x4a, 0xe0, +0x98, 0x30, 0xc7, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x99, 0xd5, 0xdc, 0x12, +0x63, 0x89, 0x76, 0x42, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0x28, 0x29, 0xfd, +0x2b, 0x64, 0xb9, 0x2a, 0x88, 0x59, 0x75, 0x52, +0x95, 0xbb, 0xf2, 0x1a, 0x81, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0xcd, +0x1f, 0x0c, 0xef, 0xc2, 0x34, 0x80, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0xa1, +0xd3, 0x61, 0xf8, 0x00, 0xb3, 0x37, 0x5f, 0x12, +0x22, 0x6e, 0xe3, 0x11, 0x9e, 0x94, 0x37, 0xef, +0x70, 0xe0, 0xf4, 0x42, 0x10, 0x17, 0x4e, 0x96, +0xfd, 0x42, 0x52, 0x0a, 0xad, 0xe6, 0xe8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0xce, 0x32, 0x1e, 0x5b, 0xc4, 0x99, 0xe9, +0xa1, 0xd2, 0x08, 0xf4, 0xea, 0x7c, 0x89, 0x82, +0xce, 0x5a, 0x6b, 0x50, 0x09, 0xb3, 0x1b, 0x49, +0x2b, 0x40, 0xea, 0xaf, 0x2a, 0xda, 0xa1, 0x35, +0xfc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0x8d, 0xcd, 0x9a, 0xb4, 0x26, +0xb3, 0xb7, 0xb0, 0xfa, 0x9b, 0x8e, 0x7b, 0x21, +0x44, 0xde, 0x96, 0xd7, 0x57, 0xc7, 0x32, 0x52, +0xae, 0x72, 0xd8, 0x2f, 0xc8, 0xff, 0x09, 0x9a, +0x9b, 0x14, 0xc6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0xcf, 0xf8, 0x9e, +0xab, 0x4a, 0xd1, 0xca, 0x19, 0xa3, 0x4b, 0xc1, +0xaa, 0xb5, 0x81, 0xab, 0x21, 0x23, 0xe3, 0xae, +0xac, 0xee, 0x23, 0x73, 0x8b, 0x09, 0xe9, 0xd5, +0xef, 0xdd, 0x49, 0x13, 0xa7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0xda, +0x97, 0xb0, 0x8a, 0xed, 0x68, 0x10, 0x3c, 0x2e, +0xf4, 0x56, 0x99, 0x7f, 0x8c, 0xaf, 0xfd, 0xd9, +0x90, 0xda, 0x82, 0x45, 0x75, 0xe5, 0x08, 0x40, +0xfa, 0xc7, 0x32, 0x37, 0x81, 0x45, 0xec, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0x49, 0x0d, 0xe7, 0xbb, 0x53, 0x1d, 0x81, +0x70, 0xe9, 0xff, 0xe6, 0xb7, 0xaf, 0x86, 0x00, +0xa9, 0xab, 0x26, 0xe8, 0x5a, 0xec, 0x50, 0x9d, +0x0f, 0x18, 0xd1, 0x81, 0xb8, 0xda, 0xc5, 0x1c, +0xf1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0x3f, 0xac, 0x24, 0xca, 0x2e, +0x03, 0x90, 0x12, 0x78, 0x8b, 0xb8, 0x55, 0x14, +0xe9, 0xbe, 0xfb, 0x9d, 0x6c, 0xb7, 0x90, 0x7e, +0x90, 0xc5, 0xa3, 0xb1, 0xdb, 0xa0, 0x3f, 0x4b, +0x76, 0xe3, 0xb2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0xc1, 0x54, 0x37, +0x06, 0x0c, 0xa7, 0x7c, 0x55, 0x01, 0xbb, 0x41, +0x0e, 0x53, 0x32, 0x93, 0x9e, 0x3f, 0x3e, 0x50, +0x45, 0x41, 0x4a, 0xa2, 0x24, 0x70, 0x7f, 0x71, +0x50, 0x11, 0x08, 0xa3, 0x14, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0xa5, +0xfd, 0x46, 0x75, 0x58, 0xe3, 0xbc, 0x3a, 0x25, +0x21, 0x5a, 0xe0, 0xef, 0x26, 0xbd, 0xf1, 0xe7, +0x8d, 0xae, 0x6f, 0x2f, 0xe9, 0xa8, 0xd3, 0x06, +0x9d, 0xf0, 0x5e, 0x99, 0xda, 0x3f, 0x1a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0x90, 0x68, 0x95, 0xf3, 0x9e, 0x87, 0xa6, +0xa3, 0xe3, 0xc8, 0xb3, 0x67, 0xcc, 0xb3, 0x7b, +0x01, 0x50, 0xfb, 0xec, 0x58, 0x0a, 0x61, 0x6c, +0xf1, 0x1b, 0x1f, 0xea, 0xc7, 0xcb, 0xf0, 0x57, +0x36, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0x53, 0x7d, 0x1a, 0x76, 0x36, +0x57, 0x34, 0x26, 0x81, 0xc1, 0x6d, 0x3c, 0xae, +0x75, 0xaa, 0x91, 0x48, 0xa7, 0xf9, 0x82, 0xb4, +0x84, 0xc4, 0x3b, 0x64, 0x0b, 0x1c, 0x61, 0x25, +0x27, 0x1e, 0x7d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0x0a, 0x34, 0x06, +0x2b, 0x8a, 0x98, 0x65, 0x80, 0x0b, 0xd1, 0xc1, +0x34, 0xb7, 0x4d, 0x22, 0x62, 0x8f, 0x09, 0xf6, +0x64, 0xbc, 0x90, 0x27, 0x1d, 0x51, 0x52, 0x0e, +0x65, 0x0e, 0xa9, 0xcb, 0x69, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0x22, +0x32, 0x98, 0xb1, 0x2d, 0x7a, 0x07, 0x0b, 0x7c, +0x28, 0xc7, 0x01, 0x19, 0xfe, 0x19, 0x6b, 0xa6, +0x4e, 0x03, 0x54, 0xaa, 0x49, 0x0b, 0x37, 0x91, +0x45, 0x86, 0x0d, 0xc5, 0xca, 0x76, 0xb5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0xb0, 0x50, 0x9a, 0xa9, 0x70, 0x69, 0x4a, +0x6a, 0x13, 0x6b, 0xf7, 0xbc, 0x0b, 0xd1, 0x52, +0x33, 0x0e, 0xfd, 0x65, 0xd4, 0xb8, 0x30, 0xda, +0xaa, 0x0a, 0x00, 0x31, 0x74, 0x51, 0x06, 0xea, +0xce, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0xae, 0x5e, 0x40, 0xac, 0x37, +0xf7, 0x9c, 0xc4, 0xad, 0x5f, 0x27, 0x7a, 0x31, +0xd2, 0x92, 0xd4, 0xd6, 0x4d, 0xf8, 0x08, 0xc9, +0x81, 0x15, 0x6d, 0x64, 0x51, 0x64, 0x1e, 0x36, +0x9e, 0xb7, 0xba, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0xf6, 0xc9, 0x52, +0x34, 0xe7, 0x91, 0x44, 0x91, 0x4f, 0xb6, 0xa0, +0x88, 0x8d, 0x28, 0x48, 0xe2, 0x3a, 0x41, 0xc1, +0xa8, 0x5b, 0x5d, 0xfe, 0x21, 0x76, 0xfb, 0x78, +0x43, 0xe9, 0x19, 0xc5, 0xed, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0x86, +0x34, 0x2d, 0x80, 0x44, 0x49, 0xd9, 0x73, 0x95, +0xf7, 0x44, 0x46, 0x11, 0x10, 0x1f, 0xcd, 0x42, +0xb4, 0x10, 0x2a, 0x4f, 0xa5, 0xfe, 0x0d, 0xfc, +0x88, 0x43, 0xbf, 0x36, 0x27, 0x3d, 0x39, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x7c, 0xc1, 0xcb, 0x05, 0x5b, 0x60, 0x95, +0xd7, 0x1a, 0x32, 0x62, 0x84, 0x46, 0x5f, 0x97, +0xdc, 0x38, 0x50, 0x35, 0x3e, 0x37, 0x49, 0xfd, +0x5c, 0x59, 0xea, 0x37, 0xd5, 0x5f, 0xa2, 0x29, +0x79, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x5e, 0xf7, 0x7b, 0x5b, 0xcd, +0x4e, 0xd4, 0xf6, 0x03, 0xa2, 0x9b, 0x7c, 0xae, +0xb2, 0xc6, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x82, 0xfe, 0xac, 0xab, +0x26, 0xe5, 0xd8, 0xe2, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0xd8, 0x61, 0xbc, +0xeb, 0xfd, 0x86, 0x2e, 0xb0, 0x1f, 0x4b, 0x19, +0xdd, 0x0a, 0x3b, 0xcf, 0xd5, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0x7a, +0xbf, 0x4b, 0xa2, 0x36, 0xb8, 0xe5, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0x6d, +0xe3, 0xf6, 0x35, 0xac, 0xbc, 0x93, 0xf1, 0x6e, +0x24, 0xe0, 0xb9, 0xe4, 0xd6, 0x2b, 0xc9, 0x99, +0x3c, 0xb6, 0xa0, 0xe5, 0x19, 0x66, 0xa7, 0x3b, +0xf1, 0x39, 0x06, 0x90, 0x3b, 0x16, 0x07, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0x41, 0x80, 0xe7, 0x0d, 0xe2, 0x74, 0x17, +0xc9, 0x8f, 0x8f, 0x3b, 0x1e, 0xea, 0x7b, 0x3c, +0xe4, 0xed, 0xdc, 0xff, 0x81, 0xe8, 0xc9, 0x40, +0xe3, 0x90, 0xca, 0x69, 0x9b, 0x3b, 0xf2, 0xec, +0x3b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0x49, 0x62, 0x7c, 0x37, 0x32, +0x52, 0x88, 0xde, 0xdd, 0x9b, 0xd4, 0xf2, 0x48, +0x73, 0xc6, 0x11, 0x40, 0x1b, 0xe3, 0xe6, 0xec, +0xdd, 0x09, 0xaf, 0xd6, 0x20, 0x4f, 0x3a, 0x28, +0x42, 0x35, 0xf1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x51, 0xdf, 0x10, +0x28, 0x80, 0xbe, 0x01, 0xe1, 0xbc, 0x50, 0xc7, +0x92, 0x71, 0x53, 0x99, 0x0d, 0x2a, 0x89, 0xd9, +0x9b, 0x10, 0x5e, 0x58, 0xdd, 0x9a, 0x7c, 0x4d, +0x0d, 0x37, 0x4f, 0x27, 0x6b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0xc0, +0xe8, 0x59, 0x01, 0x3f, 0x98, 0xe4, 0x02, 0xdd, +0x9d, 0xcb, 0x53, 0x3c, 0xb8, 0x5a, 0x11, 0x70, +0x98, 0x8d, 0x77, 0x98, 0x08, 0xf9, 0x08, 0x5e, +0x52, 0x60, 0xe2, 0x56, 0xb0, 0x53, 0x6a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0x11, 0x6d, 0xbc, 0xa1, 0x04, 0x4f, 0xef, +0x29, 0x6b, 0x5f, 0xe5, 0xc6, 0x1d, 0xf0, 0x0d, +0x9d, 0x50, 0xf7, 0xe4, 0xed, 0xe9, 0x6e, 0x89, +0xd1, 0x0b, 0xe8, 0xab, 0xa8, 0x6b, 0xfe, 0x10, +0x16, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0xdc, 0x9a, 0x35, 0xf1, 0xde, +0xb4, 0xf2, 0x78, 0x2e, 0x20, 0xab, 0x8a, 0x51, +0xee, 0xb1, 0xf7, 0x58, 0x7f, 0xac, 0x33, 0xdf, +0xf1, 0x94, 0x03, 0xc3, 0x17, 0xb8, 0x0c, 0x3f, +0x9e, 0x02, 0x48, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0x14, 0xee, 0x18, +0xec, 0x19, 0x87, 0x38, 0x71, 0x8e, 0x59, 0xed, +0xf8, 0xdf, 0xb7, 0xf6, 0xac, 0xb8, 0xdd, 0xa1, +0x25, 0xd8, 0xd5, 0x6f, 0xda, 0x92, 0xef, 0x47, +0x6f, 0x16, 0xee, 0x65, 0x72, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0x29, +0xad, 0x14, 0x9c, 0x29, 0x82, 0x15, 0xfb, 0xfe, +0x90, 0x10, 0xba, 0x4e, 0xac, 0x4b, 0xd7, 0xe8, +0xd9, 0x46, 0x30, 0xb2, 0x04, 0x55, 0x35, 0x94, +0x1b, 0x32, 0xe5, 0xe1, 0xd2, 0x63, 0x34, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0x49, 0x36, 0xd6, 0xf4, 0x81, 0xff, 0xa8, +0x8c, 0x9c, 0x1f, 0x81, 0x32, 0x75, 0xbd, 0x52, +0x4a, 0xba, 0xc9, 0xa5, 0x84, 0x58, 0x42, 0x07, +0xd3, 0xb6, 0x80, 0x1b, 0xe2, 0x6c, 0xc1, 0x10, +0xf9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0x02, 0xb4, 0x87, 0xaa, 0xfd, +0x1e, 0x3a, 0x22, 0x80, 0xec, 0x94, 0xa2, 0x76, +0x94, 0xd6, 0x51, 0xf6, 0xf3, 0xfd, 0x61, 0x16, +0x66, 0x9d, 0xe6, 0xd1, 0x97, 0x49, 0xae, 0x0e, +0x8d, 0xa6, 0xcc, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0x82, 0x30, 0xf5, +0x3a, 0xeb, 0x7a, 0x4e, 0x6a, 0x1b, 0x4f, 0xd3, +0x16, 0x9e, 0xcb, 0x22, 0x8f, 0xae, 0x61, 0xc1, +0x56, 0xf8, 0xbb, 0x19, 0xf9, 0xd4, 0xe8, 0xc3, +0x0b, 0xfc, 0x44, 0x34, 0x19, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0x2e, +0xa6, 0x6d, 0x73, 0x18, 0x1b, 0xa4, 0x39, 0x67, +0x45, 0xf4, 0x64, 0xbc, 0x13, 0x6e, 0x5e, 0x4d, +0x85, 0xfa, 0x11, 0xad, 0x73, 0xc8, 0xe2, 0x9d, +0x5a, 0xdf, 0x89, 0x87, 0x97, 0xd6, 0x2f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0xbc, 0x67, 0x6e, 0x42, 0xf6, 0x45, 0x2c, +0x66, 0x96, 0x12, 0x01, 0xc8, 0xe6, 0xc8, 0xba, +0xb3, 0xeb, 0x6d, 0x55, 0x75, 0x84, 0xcc, 0x60, +0x54, 0x8e, 0x17, 0x40, 0xed, 0x96, 0xe8, 0x20, +0x42, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0x0b, 0xa1, 0xf5, 0xa5, 0x35, +0x7a, 0xfb, 0x8f, 0xfb, 0xa3, 0x12, 0x8e, 0x4f, +0xc2, 0x40, 0x7d, 0x23, 0x8a, 0x53, 0xa2, 0xeb, +0x61, 0x50, 0x86, 0x90, 0xa1, 0xf6, 0xd6, 0x95, +0x97, 0xda, 0x4f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0xce, 0x6a, 0xe3, +0x3e, 0x2f, 0x2d, 0x5a, 0x5a, 0x54, 0x36, 0xd2, +0x54, 0xfb, 0x18, 0x63, 0xe1, 0xa7, 0x29, 0x67, +0x02, 0xca, 0x82, 0xa8, 0x6f, 0x76, 0xf1, 0xd0, +0x3f, 0x29, 0xac, 0x3a, 0xa5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0x00, +0x6c, 0xa7, 0x14, 0x16, 0xe2, 0x88, 0x75, 0xf4, +0x82, 0x2c, 0x42, 0x59, 0xda, 0xb0, 0x4a, 0xcb, +0x07, 0x65, 0xea, 0x6e, 0xc9, 0xc6, 0x7e, 0x62, +0x91, 0x50, 0xb4, 0x65, 0x54, 0x52, 0x39, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0x5e, 0x58, 0x41, 0x4e, 0x8c, 0x30, 0xa1, +0xc9, 0xb6, 0xf8, 0xcb, 0x82, 0x4e, 0xd2, 0x2a, +0xd6, 0x3a, 0x0c, 0x63, 0x6b, 0xe8, 0x5b, 0xc0, +0xc0, 0x28, 0x76, 0x13, 0x2e, 0x76, 0x5b, 0xb3, +0xf9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0xde, 0x9a, 0x9e, 0x9b, 0x7d, +0xe3, 0xfd, 0xa7, 0xe0, 0x4c, 0x47, 0xc4, 0x63, +0x03, 0xc2, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x67, 0xba, 0xa9, 0x6a, +0x13, 0x10, 0xfa, 0x78, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x4c, 0xcb, 0xad, +0x13, 0xf8, 0x62, 0x57, 0x88, 0x4b, 0x33, 0x22, +0x8f, 0xf8, 0xfc, 0xc2, 0x5d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x6d, +0x26, 0x3a, 0xcd, 0x26, 0x3c, 0x3c, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x1b, +0x5d, 0xea, 0x3f, 0x20, 0x1b, 0xc6, 0xfb, 0x9b, +0xd6, 0x78, 0x72, 0xf1, 0xb2, 0x92, 0xfd, 0x0c, +0x32, 0xbe, 0x3c, 0x10, 0xcb, 0x6d, 0xa7, 0x7c, +0x6a, 0x94, 0xc2, 0x8b, 0x3c, 0x55, 0x33, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0x4b, 0xe2, 0x09, 0xcd, 0x82, 0xd7, 0x30, +0x3b, 0x3a, 0x36, 0x7c, 0x93, 0x69, 0xf1, 0x66, +0xf2, 0x5b, 0x0a, 0xb3, 0xc8, 0x88, 0x4c, 0xab, +0x72, 0x0f, 0x82, 0x60, 0xd0, 0x54, 0x37, 0xdc, +0xf6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0xbb, 0xb3, 0x96, 0xa9, 0x21, +0x26, 0x15, 0x3e, 0x40, 0x3c, 0xca, 0xe7, 0xfd, +0x73, 0x15, 0x46, 0x49, 0xff, 0x2f, 0xa2, 0xa4, +0xeb, 0xb9, 0x82, 0x2e, 0xe1, 0x89, 0x87, 0xb9, +0x86, 0x63, 0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0xa4, 0x4e, 0x55, +0x9e, 0xa5, 0x2c, 0xcf, 0xc5, 0x3e, 0xcd, 0x6a, +0xc4, 0xb9, 0x1b, 0x4a, 0xc6, 0x09, 0x86, 0xbb, +0x99, 0x05, 0x7b, 0x7e, 0x8e, 0x76, 0x1d, 0x89, +0xf9, 0x6c, 0xb3, 0x53, 0x14, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0xeb, +0x99, 0x35, 0x15, 0x6c, 0x4b, 0x64, 0x2f, 0x76, +0x06, 0x2d, 0x6d, 0x2f, 0xf3, 0xee, 0xdf, 0xa0, +0x90, 0x12, 0x25, 0x61, 0xb6, 0x8e, 0x25, 0xd5, +0x1b, 0xfa, 0xcb, 0x27, 0x4b, 0x50, 0x65, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0xb4, 0x2b, 0x95, 0x36, 0xe0, 0xc5, 0x7d, +0xbd, 0x69, 0x68, 0xc7, 0x92, 0xb2, 0x0d, 0x06, +0x15, 0x60, 0xdd, 0xa4, 0x94, 0x34, 0xce, 0x81, +0x33, 0xa1, 0x15, 0x61, 0xb0, 0x1a, 0x12, 0x26, +0x96, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0xff, 0x21, 0x2e, 0x2b, 0xe2, +0x68, 0xbe, 0x3f, 0x03, 0x9c, 0x00, 0x02, 0x50, +0x19, 0x87, 0xb5, 0x18, 0x07, 0xc9, 0x51, 0xb3, +0xcf, 0xa4, 0x12, 0x44, 0x9d, 0x2d, 0x09, 0xb5, +0xae, 0x53, 0x63, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0x85, 0x70, 0xd8, +0x5b, 0xbd, 0xb5, 0xe8, 0x6a, 0xd6, 0xe7, 0xd9, +0x18, 0x8a, 0x81, 0xd7, 0x97, 0x54, 0x77, 0x40, +0xf0, 0x4d, 0xc5, 0x85, 0x6e, 0xef, 0x83, 0x23, +0x57, 0x20, 0xc3, 0x1b, 0x71, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0xe1, +0xce, 0xe3, 0x6a, 0xb5, 0x19, 0x36, 0x70, 0x73, +0xc7, 0xf3, 0x32, 0x97, 0xcf, 0x34, 0x12, 0x98, +0x42, 0xbd, 0xed, 0x68, 0xe4, 0xee, 0xfd, 0x3a, +0xed, 0x62, 0x3a, 0x17, 0xfa, 0x06, 0xaa, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0x67, 0xc5, 0xee, 0xc8, 0x3e, 0xee, 0x58, +0xc1, 0x6b, 0x1a, 0x8d, 0xf4, 0x97, 0x6e, 0xaf, +0x1b, 0x3f, 0x9a, 0x43, 0x8b, 0xb8, 0xc7, 0x6e, +0x60, 0x10, 0x61, 0x70, 0xb0, 0x33, 0x85, 0xa2, +0x8c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0x23, 0x2e, 0x5e, 0xd8, 0x78, +0xfb, 0x69, 0xc6, 0xd6, 0xb7, 0x98, 0x47, 0xc4, +0xb6, 0x16, 0x03, 0x3e, 0x4b, 0xa2, 0xe4, 0xa3, +0x1d, 0x5f, 0x0d, 0x65, 0x3f, 0xea, 0x5c, 0xbc, +0x70, 0xb2, 0x39, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0x5a, 0x4b, 0x1f, +0x6b, 0x25, 0x9c, 0xb6, 0x0d, 0xbd, 0xf4, 0x22, +0x15, 0x90, 0x0a, 0xbe, 0xc6, 0xd4, 0x2d, 0x07, +0x84, 0x30, 0x4b, 0xff, 0x09, 0x36, 0x2b, 0x63, +0x77, 0x59, 0x41, 0x3f, 0xcb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0xc4, +0x86, 0x41, 0x15, 0xa7, 0x27, 0x12, 0x5c, 0xe9, +0x22, 0x87, 0xf9, 0x09, 0x30, 0x8e, 0x00, 0x10, +0x88, 0x66, 0x99, 0x75, 0x2a, 0x87, 0xfd, 0x74, +0x92, 0xfc, 0x81, 0xeb, 0xd1, 0x41, 0x77, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0x66, 0x60, 0xc1, 0x08, 0xfd, 0x00, 0x5e, +0x9a, 0xf4, 0xcc, 0xc5, 0xa9, 0xdc, 0x52, 0xa9, +0xd9, 0xdf, 0xcd, 0xdd, 0xc3, 0x1d, 0xf5, 0xcd, +0xe8, 0x15, 0x91, 0x5e, 0xfe, 0x25, 0xdb, 0x24, +0xed, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0xa8, 0x51, 0xba, 0x84, 0x05, +0xea, 0xfa, 0xff, 0x92, 0xde, 0x4d, 0x42, 0xac, +0xff, 0xf3, 0xda, 0xca, 0x3e, 0x77, 0xfc, 0xe9, +0x84, 0x22, 0x04, 0x12, 0x78, 0x05, 0x81, 0x30, +0x2a, 0x9c, 0x38, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x5f, 0xf0, 0x26, +0xe5, 0x76, 0x9a, 0x1e, 0xd0, 0xdd, 0xde, 0x84, +0x91, 0x2f, 0xec, 0x7e, 0xf7, 0x7b, 0xeb, 0x57, +0x62, 0xe4, 0x36, 0x02, 0x18, 0xa6, 0xb2, 0x41, +0x9b, 0xd4, 0xd9, 0xc0, 0x1c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0xe6, +0x21, 0x5d, 0xce, 0xa6, 0x26, 0xf7, 0x7c, 0x08, +0x5b, 0x8d, 0x2d, 0x11, 0x24, 0x43, 0x58, 0x42, +0xa3, 0x6c, 0x21, 0xf6, 0xee, 0xb5, 0x54, 0x10, +0x88, 0x4c, 0x0b, 0x77, 0xf1, 0xee, 0x64, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0x7d, 0x0d, 0x36, 0x44, 0x68, 0xa7, 0xa6, +0xbc, 0x28, 0x2b, 0xce, 0xba, 0x80, 0x7b, 0x0d, +0xe5, 0x7f, 0x66, 0xac, 0x4d, 0x6b, 0xe7, 0xe4, +0x66, 0x62, 0x4d, 0x07, 0xc8, 0xd8, 0xeb, 0x09, +0x73, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0x2c, 0xa9, 0x93, 0x12, 0x8f, +0x51, 0x01, 0x13, 0x98, 0x8f, 0xac, 0xb5, 0x74, +0x95, 0xf9, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xf9, 0x0c, 0x2d, 0x4a, +0x66, 0xb8, 0xd1, 0xa3, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0xe4, 0xcc, 0x1d, +0x6a, 0xa8, 0x77, 0x80, 0x7d, 0xc3, 0xae, 0xe9, +0x88, 0x1a, 0xcf, 0xba, 0xeb, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8e, 0xb7, +0x96, 0xd0, 0x4a, 0xfc, 0x30, 0xe1, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0xe8, +0xc0, 0xde, 0x31, 0xfa, 0xbf, 0x63, 0x1f, 0xd1, +0xa0, 0xb8, 0xf1, 0x21, 0x31, 0xc3, 0x2f, 0x49, +0xb4, 0x1d, 0xe7, 0x2d, 0x9a, 0x52, 0x68, 0x0d, +0xf6, 0x4f, 0xbd, 0xb7, 0xcb, 0x54, 0xa6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0xf3, 0x7f, 0xc0, 0x23, 0xe8, 0x26, 0x48, +0xd2, 0xb9, 0x78, 0x97, 0xe3, 0xd3, 0x42, 0x19, +0x54, 0x04, 0xec, 0x84, 0xd9, 0xad, 0xd0, 0x07, +0x8a, 0x91, 0x53, 0x2a, 0x33, 0x96, 0x6a, 0x18, +0x65, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0x32, 0x6d, 0x7e, 0xea, 0xc9, +0xdc, 0x6c, 0xd1, 0xbf, 0x7c, 0x4e, 0x43, 0xf9, +0x2a, 0x8b, 0x2a, 0x6b, 0x14, 0x2b, 0xcb, 0x49, +0x2f, 0xd9, 0x7f, 0xa9, 0x4e, 0x64, 0xf6, 0xdf, +0x5f, 0x3b, 0xd3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0x78, 0x4b, 0x3e, +0x22, 0x53, 0xdf, 0xae, 0xc3, 0xdf, 0x83, 0xf6, +0x19, 0xed, 0x12, 0x41, 0x67, 0xa0, 0x2b, 0x92, +0x85, 0x80, 0x50, 0x71, 0xdf, 0x0d, 0xd0, 0x9d, +0xad, 0x35, 0x74, 0xbb, 0x4f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x62, +0xe1, 0xd1, 0x2d, 0x5d, 0x72, 0xca, 0xef, 0xf7, +0xec, 0x2b, 0x0f, 0x87, 0x7c, 0x04, 0xf0, 0xeb, +0x28, 0xca, 0x14, 0x57, 0x68, 0x3f, 0xbc, 0xcc, +0xab, 0xf7, 0xc7, 0x19, 0xe5, 0xae, 0xda, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0xfe, 0x3b, 0x6f, 0x30, 0x6a, 0x9b, 0x4d, +0x26, 0x0f, 0x0e, 0x02, 0xf2, 0xa9, 0xba, 0xd9, +0xa3, 0xfd, 0x49, 0xa8, 0xfe, 0x7d, 0x2f, 0xfd, +0x83, 0x59, 0xea, 0xf2, 0xa3, 0x7a, 0xc7, 0x83, +0x78, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0x73, 0x3a, 0x37, 0x58, 0x9e, +0x8d, 0xd2, 0x4b, 0x48, 0x25, 0x32, 0xb2, 0x5f, +0x08, 0x3a, 0xf7, 0xe0, 0xd4, 0x09, 0x6d, 0x58, +0x0e, 0xb0, 0x00, 0x19, 0x21, 0x82, 0x0b, 0x08, +0xce, 0xf7, 0xf4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0x89, 0x51, 0xd2, +0xd6, 0xb2, 0x55, 0xac, 0x93, 0x31, 0xf2, 0x01, +0xc7, 0xae, 0xd2, 0x22, 0x1e, 0xaa, 0x98, 0xa5, +0x9e, 0xd5, 0xf3, 0xed, 0x2d, 0x1a, 0x80, 0x64, +0xaf, 0x08, 0x45, 0x77, 0x4e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0xed, +0x8c, 0xee, 0x1e, 0x49, 0x36, 0x1b, 0x87, 0x3c, +0x79, 0x24, 0x58, 0xa1, 0xf4, 0x3c, 0xf4, 0x1c, +0x98, 0xb8, 0x07, 0xcc, 0x17, 0xf2, 0x72, 0x4a, +0x2d, 0x88, 0x7d, 0x91, 0x85, 0xad, 0xc5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0x8a, 0x58, 0x2f, 0xcd, 0xc8, 0x7e, 0xb0, +0x9f, 0x02, 0xa8, 0x89, 0x85, 0xb9, 0xf2, 0xb2, +0x42, 0x44, 0x0f, 0x5d, 0xba, 0xc9, 0x00, 0x2a, +0xa8, 0xf5, 0x6e, 0x56, 0x7e, 0x23, 0xc9, 0xe4, +0x49, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0xab, 0x4c, 0x2b, 0x4f, 0xd5, +0x10, 0xcf, 0x49, 0x3d, 0xe2, 0x3d, 0xca, 0xa5, +0xeb, 0xbf, 0xe8, 0x0e, 0xd5, 0xa3, 0x24, 0x87, +0x33, 0xa6, 0xee, 0xd3, 0xeb, 0xb8, 0x68, 0x7d, +0x9f, 0xb5, 0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0x25, 0xe2, 0xc2, +0xdb, 0x47, 0x9c, 0x49, 0xa7, 0x7b, 0x80, 0x56, +0x1f, 0x94, 0xc2, 0x0d, 0x72, 0xa6, 0x16, 0x14, +0x63, 0x47, 0xca, 0x6d, 0xfa, 0xa2, 0x96, 0x8a, +0x6b, 0xde, 0x1c, 0xa6, 0x71, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0x82, +0xb9, 0x67, 0x2b, 0x06, 0x30, 0x27, 0xd3, 0xfc, +0xec, 0x86, 0x3f, 0x36, 0x12, 0x32, 0x7c, 0xae, +0xcd, 0x85, 0x9b, 0xee, 0x69, 0xd3, 0x70, 0xe7, +0xa2, 0x24, 0x97, 0xef, 0x52, 0x3f, 0xfb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0xad, 0x73, 0x83, 0x63, 0xb6, 0x97, 0x6f, +0xd6, 0xd7, 0xa4, 0x4a, 0x59, 0x9a, 0x88, 0xa3, +0xaf, 0x48, 0xb5, 0x6f, 0x0f, 0x63, 0x47, 0x53, +0x9e, 0x93, 0x73, 0x9f, 0x05, 0x15, 0x50, 0xad, +0x49, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0xc2, 0x07, 0x15, 0xc3, 0x4b, +0xd5, 0xa4, 0xdf, 0xa9, 0x3b, 0x70, 0xcc, 0xa0, +0xf3, 0x3c, 0xe8, 0xf7, 0x99, 0xcf, 0xb6, 0x60, +0xad, 0x35, 0xe1, 0x85, 0x02, 0xd4, 0xb1, 0x70, +0x1a, 0xaf, 0x84, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0xb6, 0x8c, 0xf3, +0xbe, 0x1a, 0xd9, 0xbb, 0x92, 0xf6, 0xc7, 0x4c, +0xec, 0x79, 0xbb, 0x2b, 0x15, 0x3d, 0xe2, 0x8a, +0x9e, 0x77, 0x86, 0xc6, 0xa5, 0x55, 0x9a, 0x64, +0x16, 0xe2, 0x92, 0xd3, 0xfa, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0xa6, +0xa9, 0x57, 0xc6, 0xf3, 0x4b, 0xf8, 0x06, 0xca, +0x36, 0xcb, 0x17, 0x9d, 0xfb, 0x7f, 0x6f, 0xe3, +0xf1, 0xdd, 0x50, 0x00, 0x6b, 0xee, 0xd0, 0x98, +0x65, 0x93, 0x50, 0x95, 0x98, 0xb0, 0xb2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0xc3, 0x4a, 0xb1, 0x92, 0x64, 0xce, 0xff, +0xaa, 0xce, 0x52, 0x8b, 0xa7, 0x56, 0x9d, 0xb9, +0x7d, 0xab, 0x71, 0x29, 0xca, 0xa2, 0x8c, 0x17, +0x31, 0x44, 0x16, 0x6b, 0xd6, 0xd7, 0x51, 0xbc, +0x7e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0x73, 0x71, 0xff, 0x63, 0x75, +0xee, 0xfb, 0x0d, 0x7f, 0x40, 0x73, 0xaf, 0x84, +0x21, 0x6a, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x96, 0xb4, 0xee, 0x8b, +0x34, 0xb7, 0xd8, 0x1c, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0xe3, 0xdf, 0x4e, +0xa2, 0x36, 0x33, 0x52, 0x54, 0x7a, 0x6c, 0x1c, +0x18, 0x35, 0xee, 0x5e, 0xeb, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0xf1, +0x25, 0x38, 0x1f, 0xb7, 0xe4, 0xf7, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0x48, +0xe3, 0x5c, 0xc1, 0x98, 0x92, 0x6d, 0xbb, 0x8c, +0x59, 0x9c, 0xdb, 0xb3, 0x34, 0x98, 0xae, 0x97, +0x10, 0x57, 0x93, 0x45, 0x4c, 0xe3, 0x55, 0x4b, +0x05, 0xcc, 0x7f, 0xca, 0x97, 0xd3, 0x60, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0xdd, 0x5c, 0x09, 0x04, 0xb7, 0xa3, 0x83, +0x3f, 0x4a, 0xbe, 0xf0, 0xbd, 0xb8, 0xc2, 0x1f, +0x45, 0xc9, 0x52, 0x48, 0x9b, 0x0c, 0x67, 0x3c, +0xb7, 0xda, 0xeb, 0xb7, 0xd4, 0x8a, 0x9e, 0x5e, +0x64, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0x97, 0x1b, 0xa6, 0x89, 0x5e, +0xfe, 0x19, 0xb0, 0xb6, 0x38, 0x0d, 0x30, 0x1d, +0xc2, 0x69, 0x7b, 0x85, 0x1a, 0x0d, 0xf2, 0x93, +0xd6, 0xe1, 0xe9, 0x4d, 0x9c, 0x0a, 0xb4, 0x57, +0x62, 0x2e, 0x4a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0xaf, 0xc5, 0x31, +0xd1, 0x1e, 0x5d, 0x59, 0x10, 0x8e, 0xd5, 0xb3, +0xb6, 0x41, 0x53, 0x4c, 0x78, 0x8f, 0x4a, 0xfe, +0x99, 0xd0, 0x6a, 0x96, 0x58, 0x7a, 0xdf, 0x1f, +0x7b, 0x1a, 0x33, 0x51, 0x05, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0x6c, +0x2e, 0xb6, 0xcc, 0x6a, 0x67, 0xaf, 0xda, 0x86, +0xb5, 0xc9, 0xf5, 0xe4, 0x15, 0x73, 0x46, 0xe6, +0xc1, 0x08, 0xbe, 0xc7, 0x1b, 0x59, 0xb4, 0x5d, +0xdd, 0x3c, 0x81, 0xe0, 0x47, 0xd2, 0x2b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x8c, 0x94, 0x4a, 0x1d, 0x8f, 0xde, 0x07, +0xfd, 0x6e, 0xb7, 0xe7, 0x08, 0x59, 0xd3, 0xb0, +0x83, 0xba, 0x61, 0x43, 0x44, 0x6c, 0x2c, 0x73, +0xac, 0xcb, 0x48, 0xc6, 0xb6, 0x59, 0x5b, 0x7a, +0xfa, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0xe3, 0xf4, 0x30, 0x41, 0x7c, +0xd3, 0xcf, 0xd3, 0xf1, 0x7f, 0x85, 0xe6, 0x6a, +0x50, 0x4a, 0x61, 0x49, 0x63, 0x9d, 0x50, 0xba, +0x29, 0x16, 0x7b, 0x77, 0x02, 0xc5, 0x06, 0x41, +0x9e, 0xba, 0x22, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x8f, 0xd1, 0x21, +0x16, 0x3e, 0x2c, 0x3f, 0xd5, 0xa7, 0x87, 0x50, +0x42, 0xc1, 0xb3, 0x2d, 0x36, 0x51, 0x0c, 0x17, +0x73, 0xa7, 0xd7, 0xca, 0x27, 0x91, 0xdb, 0xcd, +0x8d, 0x09, 0x5d, 0xf2, 0xd2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0xa3, +0x0c, 0x8c, 0xb8, 0x9a, 0x25, 0x61, 0xd8, 0xf3, +0x94, 0x62, 0x32, 0x99, 0x3d, 0x5d, 0x3a, 0x3c, +0x80, 0x16, 0xcf, 0xc3, 0x2a, 0x68, 0xe4, 0x57, +0xfd, 0x1b, 0xf9, 0xcd, 0xa6, 0xa7, 0x95, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0xfc, 0x61, 0xfc, 0xc8, 0xee, 0xa5, 0x94, +0x82, 0xcf, 0x64, 0xf9, 0xc4, 0x30, 0x3a, 0xca, +0x65, 0x4d, 0x70, 0x97, 0x8e, 0x1a, 0x88, 0x68, +0x56, 0xe6, 0x47, 0xc0, 0x70, 0x7c, 0x53, 0xf5, +0xac, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0xe9, 0xab, 0xaa, 0xad, 0xcf, +0xb5, 0x07, 0x4b, 0xfa, 0xb4, 0xd1, 0xdb, 0xa5, +0xcf, 0xcf, 0xa3, 0xa0, 0x0e, 0x5c, 0x47, 0xdc, +0x2a, 0xe7, 0xff, 0xc9, 0x2a, 0xd1, 0x1a, 0x07, +0x28, 0x29, 0xd4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x7e, 0x39, 0x98, +0xce, 0xc5, 0xbe, 0x05, 0x06, 0xfc, 0x20, 0x0b, +0xec, 0xf9, 0xf6, 0x12, 0xf2, 0xb9, 0x5d, 0x6b, +0x70, 0xb6, 0x8d, 0x32, 0xe6, 0x6c, 0x9c, 0x69, +0x87, 0x92, 0x9e, 0xaf, 0xb5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0x2f, +0x4e, 0x32, 0x89, 0x08, 0x4d, 0x88, 0xdb, 0x0a, +0x6d, 0xd3, 0x1b, 0x79, 0xb9, 0xd0, 0x2f, 0x8c, +0x7a, 0xdc, 0x65, 0x04, 0x03, 0x0b, 0x13, 0xe1, +0x7d, 0x36, 0x6a, 0x4f, 0xf7, 0xfc, 0x63, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0x17, 0xda, 0x0e, 0xcc, 0x88, 0xb4, 0x28, +0xb7, 0xef, 0x12, 0x20, 0x4d, 0xec, 0xdd, 0xc8, +0x1f, 0x13, 0x44, 0x33, 0x94, 0x74, 0x32, 0x94, +0x06, 0xf5, 0x65, 0x51, 0x2d, 0xb6, 0x8b, 0x42, +0x02, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0x88, 0x48, 0x95, 0xe3, 0x77, +0xc7, 0x07, 0xe1, 0xeb, 0x7d, 0xcf, 0x52, 0x58, +0x88, 0xdb, 0xf8, 0x51, 0x63, 0xfa, 0xa7, 0x92, +0xf8, 0x28, 0x1d, 0x25, 0xed, 0x05, 0x3a, 0xcd, +0xf9, 0xec, 0xc0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0x95, 0x95, 0x8e, +0x7f, 0x34, 0x21, 0x44, 0xf2, 0xe7, 0x1c, 0x25, +0xcc, 0x57, 0x13, 0x10, 0x74, 0x40, 0x2f, 0xc4, +0x91, 0x09, 0xaf, 0x81, 0xa9, 0xe1, 0xc5, 0x4d, +0x5e, 0x10, 0x1b, 0x84, 0x88, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0xc6, +0x62, 0x16, 0xb8, 0xe2, 0xb7, 0x55, 0x1a, 0x59, +0xf4, 0xdc, 0x14, 0xe7, 0xd1, 0x25, 0x43, 0x72, +0x15, 0xb7, 0xcb, 0xbb, 0x48, 0x82, 0xb0, 0xba, +0x43, 0xbc, 0x10, 0x41, 0x0e, 0x19, 0x36, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0x60, 0x24, 0xbf, 0xe5, 0x5a, 0x88, 0x45, +0x7a, 0x4c, 0xb7, 0x3e, 0xe7, 0x30, 0x61, 0xa2, +0x27, 0x07, 0xa0, 0x48, 0x08, 0x00, 0xde, 0x65, +0x0c, 0x55, 0xcc, 0x5e, 0x08, 0x8c, 0x3a, 0xe7, +0xef, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0xc4, 0x79, 0x20, 0x4a, 0x65, +0xf6, 0x2a, 0x2c, 0x66, 0x78, 0xa8, 0x21, 0xbb, +0x47, 0xda, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xb8, 0x47, 0xfd, 0xaa, +0x47, 0x79, 0x00, 0x91, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x17, 0x3f, 0x0a, +0x0d, 0x52, 0xec, 0x13, 0x3e, 0x3d, 0xbd, 0x14, +0x0b, 0x2c, 0xcd, 0xdc, 0x3a, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xac, 0xac, +0x70, 0x1b, 0xec, 0x4a, 0xe7, 0x5d, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0xa2, +0x72, 0xf2, 0xf5, 0x94, 0x7e, 0x1f, 0xeb, 0x7d, +0x0f, 0x3f, 0x6c, 0xb1, 0x77, 0x7c, 0x52, 0x5f, +0x3a, 0xf7, 0x0f, 0xb7, 0xc6, 0x15, 0x57, 0x44, +0xd4, 0x5b, 0x50, 0x94, 0x81, 0x7d, 0xad, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0x06, 0x22, 0x0d, 0x08, 0xe4, 0xdf, 0x38, +0x6e, 0xaa, 0x68, 0x84, 0x48, 0x35, 0x86, 0xdb, +0x9f, 0x9b, 0xfa, 0x54, 0xfc, 0xf0, 0x26, 0x29, +0xa0, 0x60, 0x75, 0x7a, 0xeb, 0x11, 0xb8, 0xa3, +0x90, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0xca, 0xf6, 0x36, 0x3e, 0x8e, +0x12, 0x8b, 0xa3, 0xd4, 0xc1, 0x31, 0x04, 0xec, +0x43, 0x51, 0x30, 0x8f, 0x70, 0x00, 0x39, 0x54, +0x0b, 0x8e, 0x27, 0x12, 0x5a, 0x0f, 0x7a, 0x2a, +0xb7, 0x67, 0x5d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0x1f, 0xfa, 0xe2, +0x49, 0x12, 0xf1, 0xff, 0xe2, 0x75, 0x6b, 0x5a, +0xca, 0x63, 0xb5, 0xcb, 0x07, 0xaa, 0x5c, 0x0c, +0xe5, 0xbd, 0xee, 0x7c, 0xa9, 0xc6, 0xb0, 0xd7, +0x1a, 0xff, 0xe0, 0x0d, 0x68, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0xfd, +0x01, 0xf3, 0x94, 0x24, 0xd6, 0xf9, 0xb9, 0x7b, +0x42, 0x35, 0x25, 0x77, 0xfb, 0x48, 0x3d, 0x8a, +0x5b, 0xef, 0x2e, 0x1d, 0xb5, 0xde, 0x40, 0x2c, +0xad, 0x10, 0xba, 0x73, 0x3e, 0xeb, 0x5c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x29, 0x69, 0x6d, 0x96, 0xc2, 0xe1, 0x69, +0x35, 0x6d, 0x10, 0x8f, 0x8e, 0x75, 0x69, 0xe0, +0xc0, 0x5a, 0x23, 0x04, 0x0d, 0x54, 0x1c, 0xe6, +0x47, 0xc5, 0x08, 0x79, 0xe8, 0x77, 0x0f, 0x30, +0x49, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0x98, 0x2b, 0x43, 0xc0, 0xe2, +0xe8, 0x65, 0xb6, 0xeb, 0x8c, 0x33, 0xc8, 0xd1, +0xf6, 0xa0, 0x8d, 0xb2, 0x28, 0x9e, 0xb1, 0x5e, +0x5f, 0x0d, 0x15, 0x99, 0x4d, 0x3a, 0x64, 0x1a, +0x65, 0xcc, 0x4d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0x70, 0x4d, 0x6d, +0xdb, 0x4e, 0x1d, 0x65, 0x90, 0x82, 0x2f, 0x92, +0x51, 0x33, 0x05, 0xdc, 0xb9, 0x74, 0x26, 0x02, +0x11, 0xf7, 0x01, 0x3e, 0xe5, 0x7e, 0x59, 0xcc, +0xb6, 0x8d, 0x6d, 0x9e, 0x5f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0x7a, +0x2a, 0x27, 0x69, 0x46, 0x87, 0x01, 0x42, 0x87, +0xc2, 0x19, 0xbf, 0x06, 0xfd, 0x28, 0xf7, 0x74, +0x89, 0x4c, 0x52, 0x8a, 0x36, 0x0e, 0x29, 0xce, +0x28, 0xd3, 0xa9, 0x61, 0x98, 0x1a, 0x6b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0x40, 0x44, 0xa9, 0x0f, 0x83, 0xec, 0x67, +0x48, 0xf0, 0xd4, 0x95, 0x3e, 0xbe, 0x33, 0xb9, +0x15, 0x0f, 0xd9, 0xb2, 0xc5, 0x79, 0x7a, 0x26, +0x52, 0xd9, 0x41, 0x39, 0xca, 0xb2, 0x97, 0x59, +0x96, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0x8f, 0xe8, 0x5a, 0x63, 0xbd, +0x0a, 0xf2, 0xdb, 0x03, 0xe3, 0xee, 0x67, 0xe9, +0x1b, 0xd6, 0xee, 0x37, 0x94, 0xec, 0x7a, 0x17, +0x68, 0x0f, 0x2f, 0x3c, 0x14, 0x24, 0xe6, 0xce, +0xe1, 0x69, 0xf6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0x48, 0x61, 0x02, +0x46, 0x8d, 0x1e, 0x0b, 0x1c, 0x35, 0x29, 0x42, +0xe2, 0x68, 0x26, 0x9f, 0x80, 0xb4, 0xde, 0x21, +0xff, 0x42, 0xb7, 0xd0, 0xa5, 0x8e, 0x2a, 0x24, +0x82, 0x9b, 0x2a, 0x29, 0x3a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0xf1, +0xd9, 0xd0, 0x5e, 0x52, 0xef, 0x39, 0xeb, 0xb3, +0xfc, 0x77, 0x90, 0x35, 0x9c, 0x4d, 0x44, 0x2a, +0x29, 0xb5, 0x5a, 0xeb, 0x08, 0x54, 0x63, 0xf8, +0xa1, 0x66, 0xe7, 0xdb, 0x84, 0xef, 0xd9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x5c, 0x74, 0xfb, 0x95, 0x24, 0x7d, 0x4f, +0x88, 0x12, 0x01, 0xe5, 0x3f, 0xaa, 0x74, 0x78, +0x50, 0x84, 0xb9, 0x33, 0xcd, 0x49, 0x51, 0xce, +0xf9, 0xff, 0xf9, 0xba, 0x1a, 0x1b, 0x87, 0x13, +0x55, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0x27, 0x7e, 0x2b, 0xd5, 0xd6, +0x4e, 0x07, 0x85, 0xee, 0x95, 0x91, 0x28, 0x5c, +0x44, 0x38, 0x7d, 0x7a, 0x85, 0x1e, 0x63, 0x03, +0x66, 0xd8, 0x9b, 0x1d, 0x99, 0xeb, 0xc9, 0x11, +0xee, 0x05, 0x4a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0xb2, 0x09, 0x86, +0x17, 0x35, 0xa2, 0x0d, 0xec, 0x7c, 0x40, 0xd3, +0xa3, 0x64, 0x07, 0x85, 0xda, 0x3a, 0xf3, 0x45, +0x40, 0x68, 0x34, 0x66, 0x9b, 0x01, 0x01, 0x49, +0xdb, 0xc6, 0x89, 0x68, 0x3e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0x17, +0x0b, 0x04, 0x6b, 0x1e, 0x51, 0x87, 0x9a, 0x27, +0xf6, 0x8d, 0xd5, 0xe3, 0x16, 0x89, 0x0d, 0xbd, +0xff, 0x00, 0x18, 0x0c, 0xd5, 0x24, 0xb7, 0x38, +0xd3, 0x33, 0xeb, 0x2f, 0xd4, 0x13, 0x90, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0x69, 0x88, 0x20, 0xcc, 0x2c, 0xab, 0x1f, +0x3b, 0x32, 0x22, 0x41, 0x67, 0x0a, 0xc2, 0x2f, +0xf4, 0xb1, 0x95, 0xd9, 0xf4, 0xd9, 0x59, 0xb5, +0x1d, 0xa9, 0x7a, 0x8c, 0x60, 0xe5, 0xd8, 0x01, +0xce, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0x28, 0xd1, 0xcb, 0xeb, 0xe4, +0x3e, 0x2b, 0x6e, 0x51, 0xc2, 0x03, 0xe4, 0x0c, +0x5a, 0x23, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x5d, 0x84, 0x35, 0x99, +0xe2, 0x49, 0x8c, 0x37, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0x5f, 0x7f, 0x08, +0x60, 0x9b, 0xad, 0x1d, 0xad, 0x1e, 0x85, 0xc2, +0xb4, 0xea, 0x1f, 0x0a, 0x97, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe7, 0xee, +0x14, 0x3b, 0x4c, 0x5d, 0xa7, 0xf9, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0xcd, +0x93, 0x21, 0xfa, 0x8d, 0xeb, 0x55, 0x54, 0x6b, +0x34, 0x1b, 0xc0, 0x30, 0x7b, 0x8f, 0x19, 0xdf, +0xdc, 0x97, 0x55, 0x87, 0x45, 0x10, 0xa4, 0x4e, +0x3b, 0x86, 0x19, 0x10, 0xbb, 0xfc, 0x4d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0x72, 0x16, 0x25, 0xc4, 0xe4, 0xae, 0x31, +0x90, 0xe2, 0x02, 0xed, 0x92, 0x13, 0xf9, 0x76, +0x7e, 0xff, 0x10, 0x91, 0x67, 0x03, 0x22, 0x6b, +0xe0, 0xcf, 0xd1, 0x73, 0x8e, 0x28, 0x91, 0x14, +0xf5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0x4f, 0xd8, 0xd9, 0x8f, 0x79, +0xd3, 0xab, 0x61, 0xd2, 0xb0, 0xe1, 0xd6, 0x20, +0x12, 0xf0, 0x51, 0xff, 0x46, 0x26, 0x38, 0xda, +0x3d, 0xc0, 0x05, 0xea, 0xd2, 0x98, 0xc7, 0x7c, +0xea, 0x81, 0x10, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x9b, 0x23, 0x7e, +0x4f, 0x9f, 0x6f, 0x73, 0x3a, 0xf9, 0xb0, 0xd1, +0x62, 0xa1, 0xb7, 0xe0, 0x86, 0x53, 0x2e, 0xf2, +0x8a, 0x24, 0xf0, 0x6a, 0xf0, 0x5a, 0x0b, 0x4e, +0xcb, 0xda, 0x20, 0x35, 0x97, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0xa1, +0xf7, 0x73, 0x61, 0xf6, 0xba, 0x6b, 0x81, 0x62, +0xa2, 0x1c, 0x3d, 0x0d, 0x8f, 0xeb, 0x54, 0xf3, +0xae, 0xaf, 0x96, 0x91, 0x34, 0xe5, 0x00, 0x55, +0x68, 0x87, 0x14, 0xee, 0xd5, 0x62, 0x7e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0xc7, 0xad, 0x4d, 0x54, 0x7c, 0x8d, 0xed, +0xb7, 0x12, 0x1d, 0x0d, 0xf6, 0xb5, 0x83, 0x97, +0xe8, 0xb1, 0x5f, 0x69, 0xb6, 0xfc, 0xaa, 0x92, +0x05, 0x27, 0xb0, 0xb7, 0xa0, 0xcc, 0x7e, 0x1d, +0x7c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0x82, 0xe6, 0x32, 0xd1, 0x9d, +0xa6, 0xd3, 0x45, 0x92, 0x32, 0xb1, 0x17, 0xfb, +0x1e, 0xfa, 0x30, 0x99, 0x36, 0xd8, 0xb0, 0x9e, +0x58, 0x39, 0x8a, 0xb8, 0xb6, 0x11, 0x22, 0xee, +0x2d, 0xda, 0xeb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0xe1, 0x0f, 0xcb, +0xac, 0x0c, 0xbf, 0x67, 0x07, 0x37, 0x72, 0xda, +0xc2, 0xb5, 0xc4, 0xad, 0xc6, 0x24, 0x54, 0xb4, +0xfb, 0x81, 0x96, 0x8c, 0x10, 0x66, 0x18, 0x2d, +0x28, 0xa2, 0xfc, 0x6a, 0x11, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0xb0, +0x9b, 0x16, 0xcc, 0xe9, 0x06, 0xc5, 0xe1, 0x94, +0x3c, 0xfd, 0x8b, 0x74, 0x67, 0x8d, 0x87, 0x62, +0x6d, 0xae, 0xe5, 0xc4, 0x53, 0x57, 0x94, 0x07, +0xa1, 0xb4, 0x1b, 0x43, 0x09, 0x16, 0x23, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0x0b, 0x38, 0xee, 0x2d, 0x46, 0xd1, 0xe8, +0xde, 0x01, 0x7f, 0x81, 0xaf, 0xfd, 0xa9, 0x28, +0x44, 0xdf, 0x0a, 0x5f, 0x7a, 0xd3, 0x1a, 0x35, +0xe1, 0xd8, 0x80, 0x27, 0xb7, 0x41, 0xa4, 0x1b, +0x63, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0xb9, 0xdd, 0x9f, 0xbe, 0x87, +0x8e, 0x5b, 0xfd, 0xed, 0x5d, 0x21, 0xf4, 0x20, +0x17, 0xc5, 0xf8, 0x5e, 0x13, 0xbc, 0xf3, 0x8a, +0x75, 0x45, 0x65, 0xcf, 0xfe, 0xe6, 0x7d, 0x7c, +0x90, 0xd3, 0x36, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0x8c, 0xc2, 0x13, +0x22, 0x3b, 0x26, 0x80, 0x3e, 0x94, 0x93, 0x55, +0x35, 0xfe, 0xe1, 0x60, 0xe5, 0x7f, 0x1f, 0x6e, +0xe5, 0x13, 0xc7, 0x34, 0x92, 0x11, 0xd8, 0x4f, +0xab, 0x01, 0x60, 0xa0, 0xd2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0xf7, +0xc7, 0xfd, 0xdf, 0xb3, 0x9a, 0x00, 0xdd, 0x6e, +0x93, 0x9d, 0x97, 0xd9, 0xaa, 0xc0, 0x6a, 0x0a, +0xff, 0x6f, 0x26, 0x72, 0xe6, 0x08, 0xf8, 0xae, +0x82, 0xb7, 0xf6, 0x7d, 0xc1, 0x45, 0x4a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0xae, 0xd2, 0xa7, 0xea, 0x34, 0x03, 0xe2, +0xd1, 0x28, 0x71, 0x16, 0xe0, 0x8f, 0x90, 0xf0, +0x6d, 0xbb, 0x39, 0xe5, 0x6c, 0x56, 0x9a, 0x57, +0xd7, 0x7f, 0x4b, 0x60, 0xf3, 0x0f, 0x4b, 0x3c, +0xdf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0xd9, 0x7f, 0x44, 0x95, 0x02, +0x69, 0x1f, 0x0b, 0xc2, 0x65, 0xcc, 0x51, 0x0e, +0x5a, 0x88, 0xc9, 0x77, 0x02, 0x3b, 0x13, 0x6f, +0x50, 0x21, 0x25, 0x3f, 0x9c, 0xcf, 0xa7, 0x9b, +0x75, 0x58, 0xf7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0x04, 0x12, 0xf6, +0x1b, 0x9a, 0x66, 0x32, 0xa9, 0xc4, 0xe0, 0x39, +0x89, 0xbb, 0x67, 0xbe, 0x3f, 0x7b, 0x81, 0xd9, +0x45, 0xad, 0x37, 0x1d, 0x10, 0x0c, 0xaa, 0x7a, +0xd9, 0x2a, 0x69, 0x54, 0x38, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0x0a, +0xd4, 0x64, 0x4d, 0x58, 0x52, 0xbe, 0x0f, 0xad, +0xa9, 0x55, 0x4f, 0xf1, 0x0a, 0x7b, 0x02, 0x74, +0xba, 0x73, 0x91, 0xd1, 0x54, 0xc1, 0x6e, 0xe2, +0x6b, 0xa8, 0x78, 0x0a, 0xb6, 0xa5, 0x2e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x49, 0xbe, 0x7b, 0x6c, 0xe0, 0x26, 0xce, +0x26, 0x1a, 0xd3, 0xa6, 0xbd, 0x16, 0x83, 0x11, +0x82, 0xe1, 0x59, 0x20, 0xed, 0x95, 0xc3, 0x24, +0x2e, 0x30, 0x90, 0xf5, 0x04, 0x7e, 0x36, 0xd9, +0x5c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0x12, 0xad, 0x26, 0xb8, 0x4a, +0x46, 0x11, 0x76, 0x83, 0xe2, 0x03, 0xef, 0x3d, +0x61, 0xe8, 0xae, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x46, 0xfc, 0x0d, 0x8f, +0xb2, 0xcb, 0x88, 0x8e, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0x0f, 0x8a, 0x7f, +0x33, 0x6f, 0xfa, 0xd6, 0x3c, 0x89, 0xbd, 0xa6, +0x5e, 0xf8, 0xfa, 0xdd, 0x9b, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x31, +0x72, 0x44, 0x92, 0x13, 0x1f, 0xdf, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0xa0, +0xfd, 0x30, 0x1e, 0x72, 0x45, 0x06, 0x8e, 0x13, +0x85, 0xfa, 0xb5, 0xd6, 0x24, 0xa9, 0x0d, 0x33, +0x6d, 0xc0, 0xee, 0x53, 0xdb, 0xb5, 0xa7, 0xbc, +0x96, 0x52, 0xb2, 0x0d, 0xd9, 0x12, 0x55, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0xc9, 0x67, 0xb4, 0xaa, 0xa0, 0x65, 0xf2, +0x34, 0x46, 0x68, 0x14, 0x9a, 0xde, 0xcc, 0x06, +0x6b, 0x33, 0x3d, 0x6b, 0x08, 0x73, 0x57, 0x3f, +0x4e, 0x32, 0x60, 0x0b, 0xf0, 0x65, 0x4d, 0xd7, +0x08, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0x3e, 0xef, 0x29, 0x95, 0x7d, +0x05, 0x85, 0xe8, 0x45, 0xb7, 0xfe, 0xb2, 0x72, +0x0f, 0x9b, 0xc9, 0xdc, 0x88, 0xe8, 0xd1, 0x85, +0x30, 0xde, 0x1b, 0x95, 0x0c, 0x7d, 0xd8, 0xa7, +0xb3, 0xc9, 0xd3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0x34, 0x43, 0x2a, +0xeb, 0xef, 0xc4, 0x95, 0x51, 0xc1, 0xe9, 0x57, +0xd9, 0x40, 0xfa, 0xb4, 0xf3, 0x72, 0x18, 0x58, +0x66, 0x0a, 0x52, 0x23, 0xc5, 0x35, 0xd8, 0x7e, +0x8c, 0x4d, 0xf7, 0x9a, 0xe2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0xf8, +0x18, 0x70, 0xcb, 0xaa, 0x1b, 0x0c, 0x63, 0x5d, +0xf3, 0xc2, 0x37, 0xe6, 0x7e, 0x78, 0x5d, 0x96, +0x27, 0xb0, 0x60, 0xd2, 0x12, 0x2c, 0xc9, 0x32, +0xa5, 0x18, 0x9a, 0x83, 0xcf, 0x12, 0x03, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0x39, 0x76, 0x6f, 0xa3, 0x2f, 0x12, 0x62, +0x7f, 0xf3, 0xd3, 0xd3, 0xa9, 0x8d, 0xd5, 0xa4, +0x90, 0x2f, 0xcd, 0x0f, 0xd6, 0xcd, 0xd0, 0x87, +0x5a, 0xe8, 0x94, 0x6e, 0x44, 0x5d, 0x33, 0x15, +0x42, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0x16, 0x65, 0xa7, 0x80, 0x5b, +0x09, 0x9a, 0x32, 0x44, 0x02, 0xa6, 0x94, 0xe9, +0x46, 0x25, 0x74, 0x73, 0x04, 0x31, 0xe8, 0x71, +0x91, 0x16, 0x27, 0xd6, 0xa6, 0x7f, 0x5b, 0xa0, +0xdb, 0x56, 0x0e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0x89, 0x5a, 0x22, +0x63, 0xe8, 0x03, 0x64, 0xb5, 0x76, 0x99, 0x1f, +0x8a, 0xae, 0xf4, 0x06, 0xe5, 0x11, 0xaf, 0xea, +0xd8, 0x4d, 0x0e, 0xa0, 0x24, 0xd5, 0x14, 0x0f, +0x6f, 0x74, 0x43, 0xad, 0xac, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0xec, +0xde, 0x97, 0x7e, 0xa2, 0xd8, 0xdc, 0x53, 0xc2, +0xfa, 0x06, 0x3f, 0xdf, 0x30, 0x59, 0x84, 0x7b, +0x49, 0xde, 0xf9, 0xe8, 0xa9, 0x84, 0xf3, 0x37, +0xb1, 0x6f, 0x57, 0x39, 0x07, 0x15, 0xa2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0xd5, 0x60, 0xe3, 0x28, 0x0a, 0x93, 0x2d, +0x9f, 0x29, 0x1f, 0xd7, 0xb4, 0x40, 0xef, 0x8a, +0x45, 0xe0, 0x69, 0xe2, 0xe7, 0x89, 0x91, 0x2e, +0x46, 0x08, 0xab, 0x2e, 0xfe, 0xb7, 0x5a, 0x9f, +0x29, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0x0d, 0x0d, 0x69, 0x6a, 0x2c, +0x99, 0x89, 0x61, 0xfb, 0xca, 0x9c, 0x29, 0x5a, +0xb1, 0x94, 0x3f, 0x2b, 0xd3, 0xdc, 0xbd, 0x0f, +0x57, 0x92, 0xeb, 0x19, 0xc5, 0xdb, 0xb6, 0xe7, +0x3a, 0x20, 0x10, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0x39, 0x41, 0x50, +0x63, 0xad, 0x11, 0x07, 0x8a, 0xe7, 0xa6, 0x1f, +0x2a, 0xec, 0x78, 0x3c, 0x1e, 0x8c, 0x47, 0x9a, +0x75, 0x5c, 0x90, 0xf1, 0x83, 0x44, 0xbf, 0xf2, +0x06, 0x13, 0x10, 0x74, 0x72, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0xf4, +0xfd, 0x3b, 0x64, 0x6f, 0x4a, 0x0e, 0x57, 0x93, +0xfb, 0x54, 0x5f, 0xfa, 0x53, 0x30, 0x59, 0x96, +0x4a, 0xcb, 0x1d, 0xb4, 0xbc, 0x1f, 0x5a, 0xa8, +0xca, 0xea, 0xb3, 0x2c, 0x99, 0x8f, 0x58, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0x86, 0x9d, 0x1d, 0x6d, 0xb5, 0xfc, 0xfc, +0xe9, 0x4c, 0x1b, 0x5f, 0xb7, 0x4e, 0xa2, 0xfe, +0x96, 0x4f, 0x8d, 0x87, 0xa7, 0x57, 0x45, 0xe3, +0xb0, 0xd7, 0x5a, 0xa9, 0xa2, 0x62, 0x5a, 0x8f, +0x02, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0xad, 0xe3, 0xbd, 0x15, 0x6d, +0x0d, 0x1e, 0x63, 0x62, 0x71, 0xac, 0x69, 0x35, +0x0d, 0x5b, 0xe4, 0xb9, 0xd9, 0x6b, 0xf2, 0xf3, +0x33, 0x89, 0x7f, 0x65, 0xc8, 0x0f, 0xdf, 0x0d, +0x83, 0x08, 0xd4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0xa6, 0x2c, 0x46, +0xba, 0x68, 0xe5, 0x7f, 0x4a, 0x9f, 0x05, 0x6d, +0x27, 0x10, 0x0d, 0x34, 0xcc, 0x07, 0x80, 0xd8, +0xa8, 0xeb, 0x14, 0xf4, 0x78, 0x35, 0x4b, 0x1e, +0x1b, 0x82, 0x80, 0xbf, 0xd9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x7f, +0x52, 0xcf, 0x11, 0xb8, 0x41, 0xd2, 0x11, 0x20, +0x85, 0xd6, 0xb6, 0x75, 0xbe, 0x44, 0x36, 0xfa, +0xa8, 0xfa, 0x17, 0xff, 0xaa, 0xdd, 0xb8, 0x56, +0x01, 0x16, 0x73, 0xec, 0x78, 0x7c, 0x6c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0x37, 0x14, 0x27, 0x30, 0x56, 0x71, 0xcc, +0x51, 0x30, 0x3d, 0x98, 0x2a, 0x09, 0x93, 0x67, +0x48, 0xea, 0x14, 0xe2, 0xc8, 0xf7, 0x33, 0xdd, +0x0b, 0xdc, 0xd0, 0x25, 0x59, 0x6d, 0xae, 0x0e, +0xf9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0xfc, 0x87, 0x7f, 0x03, 0x29, +0x96, 0x91, 0xae, 0xc7, 0xe0, 0x0b, 0x85, 0xc7, +0x1c, 0x8c, 0xa2, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xc6, 0xc4, 0x9a, 0x61, +0xb7, 0xf5, 0xe4, 0x1b, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0x35, 0x27, 0x03, +0x3d, 0x2a, 0x36, 0x2f, 0x5d, 0x79, 0xad, 0xf4, +0x6d, 0x7b, 0x5a, 0x55, 0x1d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f, 0xcd, +0x94, 0xda, 0x38, 0x3d, 0xf2, 0x4f, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0xa9, +0x4a, 0xa3, 0x33, 0x49, 0x9a, 0x37, 0x61, 0xa8, +0x82, 0x90, 0xbd, 0x36, 0x47, 0x79, 0x94, 0x1b, +0xf6, 0xb5, 0xb0, 0xcc, 0x2f, 0x4c, 0xc3, 0x75, +0x9d, 0x20, 0xd2, 0xf1, 0xe1, 0x59, 0x13, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0xbe, 0x13, 0xa8, 0xbf, 0xd8, 0x87, 0xce, +0x5c, 0x7a, 0x11, 0x2b, 0x75, 0x39, 0xd0, 0xcd, +0xd1, 0xbb, 0xb8, 0x1c, 0xa4, 0xc9, 0x8e, 0x9d, +0xf7, 0x37, 0xbe, 0x39, 0xfb, 0x7b, 0x5f, 0xf1, +0x64, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0x50, 0x6d, 0x14, 0xf7, 0x81, +0xa0, 0xed, 0x15, 0xd3, 0x1f, 0x62, 0xa9, 0x51, +0xec, 0xdf, 0x9b, 0x5c, 0x58, 0x16, 0xb5, 0x7e, +0xde, 0xf3, 0x8f, 0x55, 0x0c, 0x5f, 0xd7, 0xbd, +0x74, 0xab, 0xd7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x9e, 0x6c, 0x84, +0x4d, 0x77, 0x40, 0x0c, 0x42, 0x68, 0x14, 0x3b, +0x80, 0xc0, 0x21, 0xb6, 0x40, 0xcd, 0x9c, 0x93, +0x8f, 0xf4, 0x8d, 0x48, 0xac, 0x73, 0xc4, 0x2c, +0xe3, 0xaf, 0x73, 0xcf, 0x90, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0xa5, +0x84, 0x05, 0x1b, 0x0f, 0x75, 0xcc, 0x7f, 0x02, +0xae, 0x5b, 0xc1, 0x72, 0xf6, 0x90, 0xe1, 0x8b, +0x97, 0xd0, 0xf1, 0xe1, 0x23, 0x91, 0x95, 0x7d, +0x91, 0x1f, 0xe8, 0xbf, 0xaa, 0x7c, 0x8d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0x60, 0xb2, 0x4c, 0x24, 0x71, 0x62, 0x43, +0x4b, 0xbf, 0xcc, 0x8d, 0xfc, 0xf6, 0xaa, 0x07, +0xde, 0x9c, 0xc0, 0x75, 0x9e, 0x94, 0xca, 0x94, +0xf2, 0x73, 0x21, 0x1a, 0x1e, 0xa4, 0x91, 0xb6, +0x5d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0x87, 0xcd, 0xc3, 0x22, 0x9d, +0x3b, 0x9e, 0xcc, 0x8c, 0xfa, 0xbf, 0x31, 0x64, +0x32, 0x87, 0x8b, 0xdf, 0xe8, 0x80, 0x07, 0x4a, +0x8e, 0x27, 0x18, 0x18, 0xa9, 0x9f, 0x83, 0x85, +0x4e, 0x6d, 0x1a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x93, 0x3f, 0x45, +0x79, 0x65, 0x4e, 0x9b, 0x00, 0xee, 0x2b, 0x52, +0xfa, 0x5c, 0x6a, 0x56, 0x69, 0xa1, 0x39, 0x7d, +0x86, 0x28, 0x15, 0xd6, 0x59, 0xd4, 0x14, 0xdf, +0xc1, 0x10, 0x9a, 0x0a, 0xb8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0x00, +0xb1, 0x9d, 0xdc, 0xbb, 0x73, 0xc9, 0x00, 0x3d, +0x0e, 0x0d, 0xef, 0x4b, 0x5e, 0x7b, 0x7b, 0xc9, +0xcb, 0xb2, 0x4b, 0xa4, 0x83, 0x94, 0x6a, 0xa3, +0xec, 0xf6, 0x38, 0x7b, 0x66, 0xe0, 0xbd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0xaa, 0x09, 0x1b, 0xe9, 0x27, 0xda, 0xbd, +0xc2, 0xd5, 0x79, 0x5a, 0x73, 0x23, 0xb3, 0x81, +0xda, 0x5a, 0x22, 0x8e, 0xd7, 0x0c, 0x33, 0x5b, +0xea, 0x47, 0x23, 0x93, 0xbf, 0xec, 0x15, 0x67, +0x34, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0x59, 0xd8, 0x3b, 0x6c, 0xdf, +0x6a, 0x62, 0x5c, 0x88, 0xe9, 0x49, 0xfb, 0x29, +0x03, 0xfd, 0x58, 0x93, 0x05, 0xc4, 0x55, 0xbc, +0xeb, 0x61, 0x2a, 0x6f, 0x29, 0x38, 0x87, 0x63, +0x9a, 0x9a, 0x1e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0x98, 0xcb, 0x81, +0xe6, 0xd5, 0x05, 0x11, 0xc3, 0x54, 0x33, 0xb1, +0x28, 0x9d, 0xf2, 0xe4, 0xfc, 0xc3, 0x36, 0x7c, +0xf3, 0x99, 0xfb, 0xa1, 0x5d, 0x42, 0x5f, 0x1b, +0xcc, 0x25, 0x9e, 0x7a, 0xf9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x66, +0x45, 0x5e, 0xa9, 0x54, 0x57, 0x51, 0x54, 0x81, +0x2e, 0xeb, 0xd5, 0x59, 0xe2, 0xa8, 0x61, 0x72, +0x45, 0x76, 0x31, 0xce, 0x0c, 0xb0, 0xc2, 0xbf, +0xf2, 0xd4, 0x22, 0x69, 0x93, 0xd0, 0x17, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0x18, 0x0e, 0x32, 0x1e, 0x86, 0x90, 0x63, +0x1a, 0x90, 0x8b, 0xd7, 0x26, 0x68, 0xaa, 0x73, +0xd9, 0x1d, 0xfa, 0x72, 0xca, 0x49, 0xe5, 0x67, +0xf4, 0x23, 0x0e, 0x97, 0xb6, 0x7a, 0x14, 0x6a, +0xa9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0xf8, 0x0d, 0xe6, 0x9f, 0x4f, +0xa9, 0xb6, 0x33, 0x83, 0x2e, 0x74, 0x37, 0x51, +0x99, 0xfe, 0xf5, 0x3e, 0xd4, 0xdd, 0xb6, 0xc2, +0x8a, 0x29, 0xdc, 0x1c, 0xe6, 0xae, 0x9f, 0xc6, +0x76, 0xd7, 0xd6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0x02, 0x63, 0x71, +0x1e, 0x47, 0x4f, 0xa5, 0x30, 0x12, 0x14, 0x8c, +0x6b, 0x92, 0x82, 0x8e, 0xc5, 0x7c, 0xd5, 0xed, +0xdc, 0x7b, 0x29, 0xd9, 0x60, 0xbc, 0x6b, 0x37, +0x75, 0x08, 0xa1, 0x1c, 0xc9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0x5e, +0x2d, 0xfd, 0x64, 0x14, 0xe6, 0x9e, 0x09, 0xe4, +0x28, 0x16, 0x51, 0x9e, 0xbe, 0x26, 0xa6, 0x68, +0xc9, 0xe5, 0xa1, 0x0d, 0xca, 0xee, 0xb8, 0x4f, +0xc5, 0x47, 0x44, 0x8f, 0x24, 0x62, 0xf3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0x66, 0x6f, 0xbb, 0xb2, 0x86, 0xd2, 0x85, +0xb8, 0x71, 0x68, 0xec, 0xcd, 0xfa, 0xf7, 0xe5, +0xaf, 0x28, 0x44, 0x2c, 0x62, 0x13, 0xae, 0xed, +0xc2, 0x1d, 0xfe, 0x5c, 0x3f, 0x2b, 0x4d, 0x96, +0x71, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0x59, 0xc5, 0x58, 0x11, 0x67, +0x18, 0x18, 0x21, 0x51, 0x33, 0xa3, 0x53, 0x9f, +0x38, 0x92, 0xcd, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x2a, 0x93, 0x05, 0xfa, +0x74, 0x34, 0x1e, 0x5f, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0xbf, 0xfc, 0xdb, +0xdc, 0xbb, 0x50, 0xe2, 0x9e, 0xae, 0x1e, 0x0f, +0x1b, 0xa3, 0x31, 0x34, 0x2c, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa5, 0xe4, +0xb8, 0x94, 0xa1, 0x8e, 0xa9, 0x0a, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0x95, +0x57, 0x11, 0x38, 0xab, 0xb5, 0x4c, 0xb3, 0xd1, +0x66, 0x94, 0x8c, 0xb7, 0xcc, 0x5f, 0x56, 0xd8, +0x27, 0xbe, 0x65, 0x6f, 0x3d, 0xa6, 0x2b, 0xb2, +0x7a, 0xda, 0x40, 0x22, 0xa3, 0xa5, 0xdb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0x49, 0x72, 0x28, 0x6d, 0x61, 0xa7, 0xe2, +0x41, 0x10, 0x6a, 0x9e, 0xf2, 0x06, 0x52, 0xfb, +0x22, 0x51, 0x42, 0x55, 0x88, 0xc1, 0x72, 0x45, +0x92, 0x85, 0xf7, 0xca, 0x35, 0x06, 0xdd, 0xab, +0xd4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0xa8, 0x54, 0xcb, 0xdb, 0xcd, +0x0b, 0x0d, 0x5b, 0x85, 0xad, 0x35, 0x90, 0x97, +0xc6, 0xd8, 0x9f, 0x02, 0xe5, 0xbe, 0x72, 0xbd, +0x7c, 0x14, 0x23, 0xfb, 0x96, 0xc4, 0x83, 0x54, +0x27, 0x75, 0x9a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0xc2, 0x42, 0xc9, +0x4a, 0xb3, 0x94, 0x28, 0x19, 0x92, 0xe6, 0xc6, +0x1d, 0x3e, 0x27, 0xfb, 0x46, 0x2c, 0xb2, 0x38, +0xca, 0x1d, 0xc9, 0xc3, 0xb5, 0xcf, 0x67, 0x30, +0xbd, 0xb9, 0x48, 0x35, 0x7b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0x6c, +0x07, 0xb1, 0x80, 0x58, 0x57, 0x44, 0x74, 0xfb, +0xd6, 0x1d, 0xb9, 0xd0, 0x4d, 0x63, 0xb1, 0x03, +0x0e, 0x0a, 0x34, 0xa5, 0xdf, 0x59, 0xfc, 0x6f, +0x6c, 0xf6, 0x6e, 0xa2, 0x3b, 0x57, 0xde, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0x73, 0x62, 0xe1, 0x60, 0xb3, 0x31, 0x38, +0x0b, 0x8e, 0xad, 0xfc, 0x07, 0xf5, 0x8a, 0xf3, +0x0b, 0x87, 0xeb, 0x56, 0x3c, 0xb1, 0xb0, 0x69, +0x6c, 0x89, 0xe2, 0x22, 0x5f, 0x4a, 0x55, 0xe1, +0x25, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0xcd, 0xd6, 0x5a, 0x04, 0xf2, +0x70, 0xbb, 0x72, 0xeb, 0x76, 0x59, 0x25, 0xf8, +0x9a, 0x9a, 0x01, 0x69, 0x79, 0x80, 0xac, 0x0b, +0x45, 0xb1, 0x03, 0x2a, 0xbe, 0x2c, 0xdb, 0x34, +0xe8, 0xe5, 0x9b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x39, 0x86, 0xd0, +0x9b, 0xe0, 0x1e, 0xb7, 0xd9, 0xe5, 0x11, 0x66, +0x14, 0x13, 0x2d, 0x09, 0xa0, 0x1c, 0x99, 0xa0, +0x47, 0x9a, 0x8b, 0x0e, 0xe0, 0x87, 0x9e, 0x7c, +0x05, 0x66, 0x5a, 0xca, 0x46, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x58, +0xaa, 0xea, 0xf2, 0xdb, 0x1c, 0x6a, 0xf8, 0x59, +0x83, 0xd0, 0x0b, 0x26, 0xaa, 0x45, 0x13, 0x35, +0x77, 0xe4, 0xf3, 0xed, 0xa2, 0x91, 0xdf, 0xaf, +0x82, 0xa3, 0xbb, 0x88, 0x55, 0x84, 0x42, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0xee, 0x76, 0x6c, 0xfe, 0xd6, 0xe8, 0xf6, +0x31, 0xdd, 0x59, 0x11, 0x29, 0x88, 0x91, 0x14, +0x25, 0x01, 0xdf, 0xe8, 0xe3, 0x02, 0x10, 0x31, +0x35, 0xfd, 0x4e, 0x04, 0xed, 0xdd, 0xa3, 0x66, +0xd1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0xbe, 0x91, 0xd0, 0x70, 0x5a, +0x5e, 0x20, 0x5a, 0xf2, 0x24, 0x8b, 0x0c, 0x13, +0x7d, 0xee, 0x28, 0x45, 0x04, 0xe1, 0x32, 0x4d, +0x8d, 0x0a, 0x5c, 0xae, 0xd8, 0x20, 0xb2, 0x2c, +0x13, 0xe7, 0xf5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0x04, 0xef, 0xda, +0x9c, 0x72, 0x3d, 0xe6, 0x00, 0x46, 0xee, 0xae, +0x52, 0x3a, 0xf3, 0xc9, 0x2f, 0x19, 0x47, 0x09, +0x59, 0xaa, 0x5b, 0x46, 0x16, 0x35, 0x4f, 0x72, +0x67, 0x8e, 0x91, 0xc5, 0x7a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0x19, +0xb3, 0xda, 0x8d, 0x71, 0x54, 0xcb, 0x19, 0xee, +0x28, 0xe2, 0xb5, 0xae, 0x70, 0x0b, 0xa9, 0xdf, +0x0c, 0xba, 0x32, 0x45, 0xf3, 0x87, 0xb7, 0xff, +0x9f, 0xa1, 0x6a, 0xa7, 0x2d, 0xd8, 0xaf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0x31, 0x6f, 0xe6, 0x54, 0x76, 0x65, 0x92, +0x92, 0x06, 0xc3, 0xdb, 0x51, 0x24, 0xf8, 0x96, +0x51, 0x01, 0xf7, 0x9d, 0xee, 0x83, 0x2d, 0x04, +0x45, 0xec, 0x7c, 0xa8, 0x85, 0x8d, 0xc6, 0xd6, +0x05, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0x47, 0x42, 0x6e, 0x35, 0x26, +0x92, 0x1b, 0x47, 0x3a, 0x87, 0x4a, 0xc7, 0xc2, +0x72, 0xae, 0xe3, 0x52, 0xf8, 0x7d, 0xb5, 0x20, +0xff, 0x4c, 0x9d, 0xb0, 0x33, 0x95, 0xca, 0x50, +0xa1, 0xfd, 0x46, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0x19, 0xcc, 0xd7, +0xe1, 0xd2, 0x54, 0xc0, 0xee, 0xde, 0x73, 0x99, +0x0f, 0x87, 0xef, 0x57, 0x83, 0xbb, 0x7e, 0x91, +0x1f, 0xca, 0x8f, 0xab, 0x68, 0x84, 0xe1, 0x57, +0x57, 0xfd, 0x91, 0x9c, 0xe0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0x3d, +0xcd, 0xd0, 0xf1, 0x5e, 0x73, 0x6b, 0xd6, 0xca, +0x64, 0x55, 0xa0, 0x70, 0xcd, 0x44, 0x7c, 0x25, +0x53, 0xbe, 0x0d, 0xfe, 0x5c, 0x67, 0x56, 0x90, +0x1e, 0x4b, 0x5b, 0x75, 0xb3, 0x9c, 0x84, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0x53, 0x07, 0xba, 0x36, 0x5d, 0xd8, 0x69, +0xe4, 0xda, 0x3c, 0xe6, 0xc9, 0x64, 0x2a, 0xf3, +0x81, 0x36, 0x9a, 0x99, 0xa5, 0x7c, 0xc6, 0x8b, +0xd6, 0x98, 0x64, 0x2d, 0x4a, 0x39, 0x94, 0xf9, +0x13, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0x82, 0x06, 0x8d, 0x2e, 0x32, +0x82, 0x3f, 0xa1, 0x33, 0xbc, 0xf8, 0x1e, 0x01, +0x15, 0x4c, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xde, 0xde, 0xad, 0x77, +0x76, 0xd5, 0x39, 0x45, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0x28, 0xa0, 0xdf, +0xbd, 0xa3, 0xba, 0xc7, 0x2b, 0xd5, 0xa9, 0x41, +0xb7, 0x52, 0x2e, 0x18, 0xdc, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xed, 0xf2, +0xba, 0xa4, 0x91, 0x10, 0xbc, 0x07, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0x77, +0x3d, 0x33, 0x85, 0xa9, 0xf8, 0xb5, 0x23, 0x06, +0xfe, 0x13, 0x28, 0x84, 0x65, 0xb9, 0x28, 0x0e, +0xdf, 0x4c, 0x1b, 0x40, 0x9d, 0x85, 0xf2, 0xa3, +0xdb, 0x6f, 0x09, 0xab, 0xbf, 0x74, 0x0c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0x48, 0x9f, 0x5a, 0xcd, 0x20, 0xee, 0x4f, +0x07, 0xd0, 0xed, 0xf4, 0xd5, 0x8a, 0x65, 0xc9, +0xf4, 0xb7, 0x02, 0x26, 0x85, 0x94, 0xa0, 0x62, +0x3b, 0x54, 0x59, 0xe4, 0x55, 0xfa, 0x8b, 0x66, +0x93, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0x5f, 0x4c, 0x47, 0x45, 0x41, +0x00, 0xaf, 0xef, 0x54, 0x9c, 0x44, 0x4b, 0x38, +0xc3, 0x58, 0x81, 0x87, 0xf1, 0x6a, 0x4c, 0xf7, +0x35, 0xe4, 0x4f, 0x62, 0xf1, 0x00, 0x2b, 0x1c, +0xe5, 0x8f, 0x1f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0x90, 0xbc, 0xef, +0xfe, 0x30, 0x24, 0x8b, 0xff, 0x04, 0xcf, 0x0d, +0x12, 0xc5, 0xd7, 0x77, 0x58, 0xce, 0x04, 0x70, +0xef, 0xac, 0xf2, 0x77, 0x6c, 0x5d, 0xea, 0xe5, +0xc5, 0x66, 0xa6, 0x0c, 0x59, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0x54, +0xbf, 0xb7, 0x46, 0xdf, 0x73, 0x79, 0x3c, 0x4b, +0xd4, 0x87, 0x22, 0x69, 0x41, 0xa7, 0x31, 0xa5, +0xde, 0xaa, 0x1c, 0x9c, 0x87, 0xbc, 0x2f, 0x21, +0x63, 0x89, 0x84, 0xd2, 0x01, 0xf7, 0x0f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0x7d, 0x12, 0x90, 0xdb, 0xd4, 0xd6, 0x32, +0xc9, 0x43, 0x4b, 0xf8, 0x4c, 0x86, 0xda, 0x6f, +0xb7, 0x5d, 0x9a, 0x50, 0x4f, 0x05, 0xc5, 0xdc, +0x04, 0xc2, 0x12, 0x07, 0x23, 0x44, 0x3e, 0xdc, +0xbe, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0x57, 0xd6, 0x94, 0xe1, 0xf1, +0xe4, 0xc6, 0x71, 0xe4, 0x87, 0x60, 0x09, 0x60, +0x26, 0xce, 0xd7, 0x9d, 0x9e, 0xb8, 0x98, 0xea, +0x12, 0x2d, 0xba, 0x98, 0xac, 0xf2, 0x93, 0x40, +0x5b, 0x5e, 0xfb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0x2c, 0x5e, 0x89, +0x3b, 0x2b, 0x92, 0xd7, 0x6b, 0x3e, 0xf1, 0x08, +0x26, 0x25, 0x29, 0x18, 0x45, 0xdf, 0xde, 0xe7, +0x86, 0x77, 0xef, 0x08, 0xb1, 0xda, 0xf0, 0xcf, +0x6f, 0x39, 0x87, 0xb8, 0x07, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0x3a, +0x0b, 0x40, 0xcc, 0xb8, 0x7d, 0x2e, 0xda, 0x97, +0x6c, 0x87, 0x96, 0x98, 0x84, 0x3a, 0xa0, 0x69, +0xd2, 0x01, 0x12, 0xc5, 0x0a, 0xeb, 0x75, 0x46, +0x98, 0x9c, 0xfa, 0xab, 0x3d, 0x11, 0x71, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0x23, 0x36, 0x02, 0x8b, 0x70, 0x74, 0x10, +0x20, 0x3b, 0xc5, 0xbc, 0xc4, 0xbf, 0xf9, 0xd9, +0x20, 0x47, 0x51, 0x26, 0x64, 0x2f, 0xf0, 0xd4, +0x66, 0xba, 0x7a, 0xb4, 0x4a, 0xb2, 0x85, 0x15, +0xc7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0x43, 0xef, 0xda, 0xcc, 0x6b, +0xa7, 0xe4, 0xab, 0x64, 0x67, 0x3b, 0x54, 0x9e, +0x38, 0x46, 0xaa, 0x70, 0x1a, 0xc5, 0xa3, 0xe7, +0x8b, 0x34, 0xb5, 0x3d, 0x91, 0x05, 0x0d, 0xd5, +0x78, 0xa5, 0xed, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0xe6, 0xfb, 0x37, +0x68, 0xf4, 0x7d, 0xed, 0xf4, 0xca, 0x02, 0x98, +0xc3, 0x7d, 0xe5, 0x9b, 0x7d, 0xd9, 0x01, 0x28, +0x25, 0xf5, 0x06, 0x1e, 0x74, 0xdc, 0x99, 0xd2, +0x5a, 0x45, 0x20, 0x7c, 0x62, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0xc9, +0x5c, 0x20, 0xb9, 0x11, 0x96, 0xc9, 0xad, 0xb7, +0xc2, 0x7a, 0x82, 0x2d, 0x3f, 0x2c, 0xbb, 0x70, +0x6d, 0xb8, 0x0f, 0x59, 0xd8, 0xf4, 0xcf, 0x08, +0xfa, 0x35, 0x24, 0x41, 0xb5, 0x7a, 0x0e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0x66, 0x7e, 0x72, 0xfa, 0x2c, 0x70, 0xc7, +0xfb, 0x3e, 0xdc, 0xc7, 0x1b, 0xb6, 0x72, 0x3e, +0x56, 0x83, 0xc7, 0x78, 0xf2, 0x6f, 0x54, 0x5b, +0xd0, 0xf9, 0xd8, 0x99, 0xcb, 0x5e, 0x66, 0x37, +0x80, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0x40, 0x57, 0x63, 0x31, 0x39, +0xa4, 0xc9, 0x3c, 0xb4, 0xd1, 0x43, 0xa5, 0x57, +0xac, 0x1a, 0x53, 0x6b, 0x8f, 0x52, 0xe5, 0x75, +0xfc, 0x3c, 0x91, 0x0c, 0xaa, 0x20, 0xb3, 0x52, +0xc8, 0x81, 0xb5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0xb5, 0xfd, 0xbb, +0xac, 0x8c, 0x4e, 0x5d, 0x19, 0x4b, 0xf0, 0x2d, +0xb3, 0xfb, 0x3c, 0x5f, 0xf1, 0x65, 0x94, 0x5b, +0x8a, 0xfc, 0x9a, 0xc8, 0xf7, 0x94, 0xc5, 0x69, +0x04, 0x25, 0xe2, 0xde, 0x47, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0x2f, +0x31, 0x9f, 0x6a, 0xb0, 0x52, 0xbc, 0xb9, 0x38, +0x35, 0xb9, 0x13, 0x18, 0x7a, 0xf7, 0x36, 0xb6, +0x26, 0x0f, 0xfc, 0x7e, 0x27, 0xef, 0x92, 0x22, +0x25, 0xac, 0xa3, 0x9c, 0x76, 0x9a, 0x1b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0xce, 0x9a, 0x81, 0xcd, 0x8f, 0xf4, 0x27, +0x40, 0x4f, 0x3b, 0x46, 0x85, 0xa3, 0x50, 0xf9, +0x13, 0x4f, 0x57, 0xbc, 0x2e, 0x04, 0x15, 0xe3, +0x7f, 0xd1, 0x18, 0xef, 0xa8, 0x2e, 0xcc, 0xc8, +0x31, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0x17, 0x2e, 0xaa, 0x69, 0xa1, +0xd9, 0x65, 0x8c, 0x12, 0xca, 0x7f, 0x11, 0x14, +0xea, 0x42, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xaa, 0x4f, 0xd4, 0x51, +0x55, 0x5e, 0x79, 0xbf, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0xaf, 0xb1, 0xb1, +0x99, 0x0f, 0x78, 0x79, 0x37, 0x1b, 0x78, 0x8f, +0x1f, 0x28, 0xdb, 0x62, 0x02, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0x08, +0x04, 0x06, 0x3b, 0x71, 0x7f, 0xf4, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0xe3, +0x29, 0x1c, 0xb9, 0xc5, 0xd6, 0x63, 0x8d, 0x64, +0xc6, 0x7c, 0x4b, 0x2f, 0x8f, 0x63, 0x4e, 0x05, +0x24, 0x66, 0x2c, 0x5a, 0xab, 0xdb, 0x06, 0x01, +0x3d, 0x26, 0xca, 0xf6, 0xf0, 0xbb, 0x24, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0x0e, 0x02, 0x04, 0xbb, 0x4a, 0x7a, 0xd8, +0x3a, 0x2e, 0xe0, 0x23, 0x78, 0x68, 0x8d, 0xb2, +0xe0, 0xa3, 0x97, 0xb9, 0x76, 0x28, 0x04, 0xc6, +0x06, 0xba, 0xcb, 0xba, 0x2d, 0x96, 0x37, 0xb1, +0x16, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0x04, 0x50, 0x84, 0xf4, 0xaf, +0x9b, 0x03, 0xcb, 0xa3, 0xdd, 0x4d, 0x57, 0xbb, +0x9b, 0xfa, 0x50, 0xc0, 0xbe, 0x48, 0x27, 0x2e, +0xf9, 0xff, 0xae, 0xa7, 0xbc, 0x79, 0x2a, 0x63, +0xf8, 0x39, 0xd9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x8a, 0x66, 0x06, +0x90, 0xba, 0xe7, 0x49, 0xc0, 0xff, 0x4b, 0xce, +0x19, 0x9c, 0x0d, 0xae, 0x3b, 0xf1, 0x4d, 0x21, +0x76, 0x36, 0x3f, 0x67, 0x20, 0x42, 0xb7, 0x79, +0x65, 0x61, 0x50, 0x0f, 0xe4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0x81, +0xc7, 0xb2, 0x25, 0x02, 0xb8, 0x80, 0x26, 0x5c, +0x9c, 0x10, 0x5a, 0x2d, 0xe1, 0xfe, 0x3c, 0x9f, +0x31, 0xf7, 0x1a, 0x81, 0xf7, 0xa6, 0x9b, 0xeb, +0xb6, 0xcb, 0x95, 0x3d, 0xa1, 0x34, 0xeb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0xe9, 0xae, 0xdd, 0x37, 0x90, 0xc8, 0xcd, +0x8f, 0x7e, 0x70, 0x89, 0x9a, 0xa3, 0x16, 0xfa, +0xf8, 0xa1, 0x47, 0xc0, 0xdb, 0xcc, 0x8e, 0xd8, +0xd1, 0x16, 0x48, 0x53, 0x97, 0xc6, 0x6b, 0x91, +0x62, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0x08, 0x9b, 0xe2, 0x80, 0x9a, +0x29, 0x5d, 0xa3, 0x11, 0x19, 0x5e, 0xcd, 0x26, +0xb5, 0x27, 0xc3, 0x67, 0xe7, 0xd7, 0xfe, 0x9e, +0x91, 0xfd, 0xf4, 0xa0, 0xfb, 0x65, 0xe8, 0xd9, +0xe5, 0x4c, 0x16, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0x69, 0x7a, 0xe7, +0x0d, 0x8b, 0x83, 0x0b, 0x93, 0xdf, 0x6d, 0xdd, +0x22, 0xbe, 0xed, 0x77, 0x3d, 0x2f, 0xdc, 0x19, +0x1a, 0x89, 0x70, 0x5c, 0xad, 0x33, 0x13, 0x48, +0x59, 0x1a, 0x2f, 0xe2, 0x2d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0xfb, +0x90, 0xc6, 0x86, 0x1d, 0xb9, 0x29, 0x56, 0x1b, +0x72, 0x74, 0x63, 0xbf, 0x14, 0x1f, 0x1a, 0xab, +0x60, 0x4c, 0xab, 0x0a, 0x3f, 0x62, 0x1c, 0xa5, +0x93, 0x6a, 0x61, 0x01, 0xcf, 0x7d, 0x0c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0xe1, 0x11, 0x78, 0x63, 0xc1, 0x0c, 0x2c, +0x9a, 0x41, 0x90, 0x3d, 0xc0, 0xa8, 0x91, 0x3a, +0x5c, 0x86, 0x4f, 0xfd, 0x6b, 0xa7, 0xd6, 0xff, +0xed, 0x52, 0x03, 0x19, 0xc0, 0x05, 0x09, 0xe2, +0x05, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0xed, 0xba, 0x23, 0x9a, 0xff, +0x66, 0x5d, 0x63, 0xd3, 0x1a, 0x1a, 0x12, 0x64, +0x64, 0x67, 0x8e, 0x71, 0xe7, 0x93, 0x08, 0x9b, +0x73, 0x45, 0x20, 0x82, 0xd6, 0x9e, 0xd7, 0xd5, +0xb3, 0x31, 0xa9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0x00, 0xa7, 0x0c, +0xc3, 0x4b, 0x30, 0x43, 0xcc, 0x28, 0x2d, 0x21, +0x06, 0x13, 0x02, 0xbb, 0x33, 0x98, 0x40, 0x3d, +0x3b, 0x87, 0xeb, 0xe8, 0x94, 0x36, 0x61, 0x0f, +0xe9, 0x9d, 0x57, 0x76, 0xe5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0xcf, +0x65, 0xcd, 0xa9, 0xf8, 0xfb, 0x0a, 0x51, 0x05, +0xc0, 0xef, 0xe4, 0xd4, 0x3c, 0x2f, 0xd7, 0xb5, +0x2c, 0x7f, 0x12, 0x45, 0x97, 0xd8, 0x6f, 0x20, +0x96, 0x78, 0x5d, 0x8d, 0x40, 0x12, 0x15, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0x32, 0xb0, 0xb3, 0xa6, 0x0a, 0xc2, 0xdb, +0x17, 0x73, 0x59, 0x74, 0x97, 0x79, 0xcd, 0x22, +0x24, 0xbc, 0x13, 0x22, 0x6f, 0x40, 0xdf, 0xcb, +0x03, 0x2d, 0x9f, 0x05, 0xff, 0xb4, 0xe3, 0x40, +0x15, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0x62, 0x92, 0x9b, 0xc5, 0xc2, +0xf8, 0x3f, 0x37, 0xb6, 0x4f, 0x65, 0x95, 0x29, +0xf1, 0x6e, 0x35, 0xba, 0x3a, 0xad, 0x75, 0xe8, +0xa8, 0xac, 0xd8, 0x32, 0x30, 0xf0, 0x73, 0xae, +0xe8, 0xa0, 0xca, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0x47, 0x1b, 0xfe, +0xb7, 0xfd, 0x4f, 0x1b, 0xb7, 0x29, 0x28, 0x5f, +0x2f, 0x7e, 0xd4, 0x4f, 0x03, 0x25, 0x39, 0xa8, +0x0c, 0x1f, 0xb4, 0x2d, 0x56, 0x9c, 0xb8, 0x79, +0x87, 0x26, 0x4b, 0x72, 0xd7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x72, +0x12, 0x51, 0xea, 0x63, 0x0d, 0x9c, 0x8d, 0x83, +0x68, 0x22, 0xe5, 0x72, 0x0c, 0x5e, 0x17, 0xf8, +0x61, 0x66, 0x78, 0x84, 0xf3, 0x87, 0x60, 0x98, +0xfa, 0xaa, 0x4e, 0x48, 0x51, 0x81, 0x5a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0xe1, 0x9c, 0xc5, 0x05, 0xfa, 0x6c, 0x63, +0xfa, 0xea, 0x01, 0xa8, 0xba, 0xf6, 0x3b, 0x6e, +0x44, 0x3f, 0x3a, 0xcd, 0x83, 0xb1, 0xd8, 0xca, +0x98, 0x11, 0x70, 0x18, 0x61, 0xcb, 0x05, 0x4e, +0xdf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0x84, 0x9e, 0xd1, 0x08, 0x9f, +0x80, 0xbb, 0x21, 0x04, 0xd7, 0x45, 0xa3, 0x64, +0x18, 0xdd, 0xc9, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xf3, 0x26, 0x9b, 0xb2, +0x22, 0xf2, 0x86, 0xeb, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x69, 0x97, 0x77, +0x15, 0x36, 0x98, 0x78, 0x3e, 0x46, 0xd9, 0xa7, +0x13, 0x65, 0x39, 0x8b, 0xa8, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x55, +0x4c, 0x9f, 0x16, 0x5f, 0x65, 0xd0, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0x9d, +0xcc, 0xc5, 0x49, 0xbe, 0xa2, 0x3c, 0x87, 0x76, +0xe3, 0xc4, 0xf2, 0xe2, 0x42, 0xc8, 0x76, 0xd2, +0x91, 0x15, 0x1b, 0x60, 0xfb, 0x5f, 0x00, 0x03, +0xab, 0x0b, 0x22, 0xa4, 0x3f, 0x35, 0xf8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0x51, 0xc7, 0xa4, 0x98, 0xdf, 0x91, 0x34, +0xa0, 0x2d, 0x4b, 0xae, 0x59, 0x19, 0x5f, 0xc4, +0x43, 0xe0, 0xc9, 0x7f, 0x71, 0x73, 0x08, 0x15, +0xb6, 0x77, 0x31, 0x1b, 0x0b, 0x91, 0x5b, 0xfe, +0x41, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0x9d, 0x40, 0xc5, 0x1f, 0xce, +0x3d, 0x8b, 0x88, 0x7f, 0x07, 0xcb, 0xdc, 0x99, +0x82, 0x15, 0x73, 0x1b, 0xb4, 0x48, 0xb8, 0xb5, +0xd6, 0xea, 0xdf, 0xfd, 0xee, 0x20, 0x2e, 0xc8, +0x30, 0xf2, 0xaa, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0x5f, 0xfb, 0xc0, +0xd6, 0x83, 0x60, 0xcc, 0xb0, 0x7c, 0x0c, 0x32, +0x17, 0xa8, 0x9d, 0x8c, 0x8c, 0xe0, 0x1c, 0xac, +0xc2, 0x14, 0x97, 0x47, 0x8a, 0x3a, 0x33, 0x65, +0x64, 0xed, 0x73, 0xf1, 0xb0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0xec, +0xb1, 0x54, 0x16, 0x81, 0xa7, 0xb1, 0x60, 0xcc, +0xe0, 0x8d, 0x94, 0x80, 0x8c, 0x86, 0xa5, 0x46, +0xb6, 0x7a, 0x83, 0x5e, 0xcf, 0x19, 0x72, 0xe1, +0x28, 0xa1, 0xa3, 0xb8, 0xcd, 0xb2, 0x8f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x9d, 0xa5, 0xb3, 0x01, 0x96, 0x8c, 0x9e, +0x43, 0x61, 0xc9, 0x0c, 0xf7, 0x36, 0xf0, 0xc5, +0x1a, 0xa8, 0x5b, 0x57, 0x14, 0x5d, 0xda, 0x74, +0xdc, 0x66, 0x94, 0x1c, 0x06, 0x82, 0x9d, 0x04, +0xb7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x13, 0x77, 0xc1, 0x33, 0xf6, +0xd9, 0x8b, 0x64, 0x0e, 0x6e, 0x7a, 0x57, 0x25, +0xac, 0xab, 0xd4, 0xe1, 0x60, 0x51, 0xc1, 0xc2, +0x97, 0xd8, 0x3c, 0x17, 0x6a, 0x7c, 0x3b, 0xea, +0x89, 0xe6, 0x01, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0x4a, 0xd6, 0xf9, +0x61, 0xd9, 0x5b, 0x82, 0xdf, 0x7c, 0x80, 0x93, +0xaf, 0x79, 0x16, 0x6e, 0xf5, 0x52, 0x38, 0x77, +0xb0, 0x2a, 0x40, 0x57, 0x2a, 0x1e, 0x51, 0x58, +0x9d, 0x38, 0x50, 0x1b, 0xae, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0x9c, +0x1f, 0x61, 0x08, 0xdf, 0x63, 0xed, 0x9a, 0xfe, +0x60, 0x9d, 0x08, 0x5b, 0x3d, 0x36, 0xf1, 0xd1, +0x88, 0x69, 0x88, 0xc9, 0x07, 0xd1, 0xd2, 0xc3, +0x7c, 0x08, 0x8e, 0xa0, 0x2b, 0xe4, 0x62, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x99, 0xe2, 0xfc, 0x7a, 0x64, 0x8f, 0xe9, +0x82, 0x78, 0x21, 0xe1, 0x3a, 0x63, 0x2f, 0x86, +0xa8, 0x7a, 0xca, 0xac, 0x7a, 0xcb, 0xee, 0xe6, +0x27, 0xc3, 0x30, 0x92, 0x45, 0x85, 0x00, 0xb4, +0xcf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0x9a, 0xfe, 0xed, 0xaa, 0x70, +0x29, 0xb3, 0xdb, 0xb2, 0xcc, 0x0a, 0xba, 0xe5, +0x3a, 0x5d, 0xb8, 0xb3, 0x68, 0xd4, 0x16, 0x8a, +0x2f, 0x93, 0xcc, 0x07, 0x8a, 0xe2, 0xa0, 0x26, +0x10, 0x73, 0xbd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0xb3, 0xf6, 0xee, +0x75, 0xb9, 0x50, 0x28, 0xbf, 0xfa, 0xa4, 0xed, +0xc3, 0x25, 0x3a, 0x26, 0x4a, 0x37, 0x1f, 0xe8, +0x3b, 0x30, 0xd9, 0x8d, 0x9c, 0x2f, 0x94, 0xeb, +0x96, 0xeb, 0xa1, 0xd0, 0xe4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0xfc, +0xa6, 0x09, 0x23, 0xd7, 0x5d, 0x14, 0xa7, 0x25, +0x25, 0x6b, 0x7f, 0xa5, 0xa5, 0xce, 0x7f, 0xac, +0x04, 0xfd, 0x84, 0xf6, 0xcc, 0x06, 0xf3, 0x76, +0x5b, 0x81, 0xc4, 0x37, 0x70, 0x63, 0x28, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0x22, 0x97, 0x4b, 0x5e, 0xf9, 0x97, 0x8f, +0x85, 0xd8, 0xe0, 0xc1, 0xf1, 0xee, 0x16, 0x45, +0xfe, 0x1a, 0x7e, 0x20, 0x19, 0x3e, 0x53, 0x66, +0xa2, 0x87, 0x6b, 0x44, 0x8e, 0x97, 0x66, 0xa9, +0x60, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0xa3, 0xcb, 0x47, 0x6c, 0xd1, +0x50, 0x8d, 0x8c, 0x7c, 0x6f, 0x6d, 0x3f, 0x5b, +0xe0, 0x7b, 0x8f, 0x2a, 0xd5, 0xc2, 0x71, 0xdc, +0x0a, 0x3e, 0x9f, 0x2e, 0x43, 0x8e, 0x56, 0xac, +0xe2, 0xe0, 0xd9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0xc6, 0x1d, 0x19, +0xb6, 0x89, 0x0c, 0xe0, 0xdd, 0x1b, 0x2f, 0xf6, +0x51, 0x43, 0x32, 0x35, 0x9a, 0xb9, 0x94, 0xc2, +0x74, 0x5d, 0xd2, 0x8c, 0x37, 0xce, 0x8a, 0x39, +0xec, 0x64, 0xb3, 0x00, 0x17, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0x19, +0xd0, 0x55, 0x1f, 0x1f, 0x2f, 0x44, 0xd5, 0xcc, +0x55, 0x70, 0x7b, 0x34, 0x0e, 0xb9, 0x9c, 0xcc, +0xfe, 0xf7, 0x58, 0xa0, 0x9e, 0x90, 0xdd, 0x3f, +0x07, 0x0d, 0x36, 0x2b, 0xe4, 0x33, 0x22, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0x97, 0x53, 0x89, 0xa5, 0xc4, 0x98, 0x2b, +0xe5, 0x51, 0x4e, 0xc6, 0xf6, 0xe9, 0x9b, 0x66, +0x92, 0x50, 0xc0, 0xaf, 0x30, 0x18, 0x34, 0xed, +0x15, 0x29, 0x3c, 0x91, 0xb0, 0x5b, 0x96, 0x8a, +0x31, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0xc0, 0x2c, 0x15, 0x74, 0x9d, +0xc2, 0x6c, 0x1c, 0xfb, 0x68, 0x1e, 0x1d, 0xc2, +0x65, 0x25, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x55, 0xa0, 0x60, 0xf6, +0xc5, 0x7a, 0x8d, 0xe2, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0x7c, 0xcd, 0x67, +0x0c, 0x13, 0xc1, 0x42, 0x7d, 0x52, 0x65, 0xee, +0xf9, 0x67, 0x0a, 0x35, 0x48, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x60, +0xc2, 0xc2, 0xf9, 0xd4, 0x8e, 0xb1, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0x1a, +0x8a, 0xac, 0xb3, 0x23, 0x49, 0xd5, 0xa4, 0x59, +0xb8, 0x24, 0x42, 0x7b, 0xda, 0x02, 0x80, 0xe9, +0xa5, 0x4c, 0x7e, 0x49, 0x6a, 0xf3, 0x03, 0x31, +0x01, 0xa3, 0x53, 0x7f, 0x37, 0x4b, 0xe9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0x96, 0x3c, 0x81, 0xd3, 0xbb, 0x8d, 0x2a, +0xce, 0x78, 0xf6, 0x4c, 0x1b, 0x7b, 0xba, 0x06, +0x53, 0x41, 0x54, 0xfc, 0xe1, 0x63, 0x13, 0x78, +0x0f, 0x95, 0x4c, 0x32, 0x70, 0x8d, 0xf3, 0x2d, +0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0x02, 0x7c, 0xaa, 0x7c, 0x8b, +0xd6, 0x48, 0x10, 0xa2, 0x05, 0xed, 0x17, 0x7b, +0x13, 0x06, 0x2d, 0x5c, 0x53, 0x1e, 0xa0, 0x04, +0xa0, 0x39, 0x66, 0xad, 0x4e, 0xd0, 0x4c, 0x6d, +0xcd, 0xfa, 0x5f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0x67, 0xa1, 0xed, +0xfd, 0x42, 0x8f, 0x0b, 0xa2, 0xe3, 0xd5, 0x33, +0x20, 0xa7, 0x75, 0x9e, 0xcb, 0x2c, 0x61, 0xbf, +0xd4, 0xd4, 0x2d, 0x02, 0x11, 0xa8, 0x97, 0x54, +0x7f, 0xb5, 0x00, 0x7c, 0x69, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0xe9, +0x18, 0x5b, 0xb9, 0x03, 0xb8, 0x64, 0x12, 0xbe, +0xe3, 0x84, 0x33, 0x05, 0x29, 0x29, 0x55, 0x4c, +0x99, 0x7a, 0xfc, 0xf1, 0x30, 0x25, 0x73, 0xae, +0x46, 0x27, 0x70, 0x65, 0x82, 0xb9, 0x33, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0xca, 0x50, 0xa3, 0x8f, 0xe8, 0x21, 0x49, +0xbd, 0x56, 0x3a, 0x86, 0xb0, 0x0a, 0x39, 0x6d, +0x78, 0xa6, 0x0c, 0xeb, 0xdf, 0x38, 0xba, 0xab, +0xed, 0xb8, 0xcd, 0xc5, 0x78, 0x8c, 0x93, 0xa7, +0x95, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0x18, 0x06, 0x15, 0x10, 0xc7, +0x8f, 0x93, 0x05, 0xcb, 0x81, 0x9b, 0x7e, 0x31, +0x02, 0x54, 0xc5, 0x6c, 0x7a, 0xd2, 0xa5, 0x72, +0xc7, 0x31, 0xf4, 0x72, 0xa8, 0x69, 0x5e, 0xb3, +0xef, 0x55, 0x67, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0x5b, 0xdd, 0xa4, +0x8b, 0xdc, 0x01, 0xba, 0xcb, 0xb3, 0xef, 0x19, +0x8f, 0x50, 0x5c, 0x70, 0x0d, 0x71, 0x49, 0xb1, +0xb0, 0x7a, 0x3e, 0x38, 0xc4, 0x5b, 0x37, 0x05, +0x3a, 0xa3, 0x53, 0xa5, 0xd1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0xe3, +0x69, 0x63, 0x92, 0x51, 0x1d, 0x7a, 0x99, 0x9b, +0xf4, 0xa8, 0x82, 0x85, 0x2d, 0xb5, 0x4a, 0x16, +0xe9, 0x65, 0x98, 0x79, 0xf4, 0x70, 0xc0, 0x99, +0x21, 0x77, 0xd3, 0xb4, 0xf2, 0x83, 0x76, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0x3b, 0xaf, 0x4d, 0x98, 0x4c, 0xa3, 0x41, +0x3b, 0x12, 0xdf, 0xf0, 0x5f, 0xca, 0x92, 0xc1, +0x27, 0x0d, 0x82, 0xd4, 0x53, 0x97, 0x3f, 0x76, +0x90, 0x27, 0x2e, 0xe8, 0xba, 0x40, 0x38, 0x7f, +0xe5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x58, 0x5e, 0x34, 0x65, 0xbc, +0x9c, 0x5f, 0x3f, 0x23, 0x6e, 0x67, 0x08, 0x7f, +0xe8, 0x17, 0xd8, 0x21, 0x71, 0x9e, 0x33, 0x09, +0x28, 0xeb, 0x90, 0x0c, 0x0f, 0xed, 0xf3, 0xc7, +0x81, 0x1c, 0x04, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0x3d, 0x47, 0xb7, +0x42, 0xb3, 0xcb, 0xf4, 0xb5, 0x51, 0x5f, 0x7e, +0xbd, 0xac, 0x97, 0x1e, 0xad, 0xa2, 0xc5, 0x86, +0x19, 0xd0, 0x56, 0x89, 0xbe, 0xaa, 0x66, 0xcf, +0x34, 0x3b, 0x8f, 0x38, 0xa5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0x49, +0x84, 0xc3, 0x22, 0x34, 0xb6, 0xc4, 0x2a, 0xa6, +0xc2, 0x91, 0xf2, 0x66, 0xa4, 0xa0, 0xcc, 0x31, +0x8d, 0x43, 0xed, 0x5a, 0xeb, 0x78, 0x20, 0xd0, +0xfe, 0x4c, 0x06, 0xda, 0x10, 0xf9, 0x90, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0xf5, 0xb9, 0x7a, 0x22, 0x19, 0x85, 0xaa, +0x66, 0xd5, 0x1b, 0x2a, 0x5f, 0x97, 0x44, 0xdd, +0x4d, 0x96, 0xd6, 0xa4, 0x20, 0xd0, 0x8d, 0x15, +0xa6, 0xbc, 0xf5, 0x96, 0x62, 0xbd, 0x73, 0x06, +0xe4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x07, 0x07, 0x52, 0xb1, 0x82, +0x20, 0x73, 0xcc, 0xdf, 0xb5, 0xd1, 0xef, 0x04, +0x80, 0x5a, 0x93, 0xa4, 0xc0, 0xf7, 0x0f, 0xef, +0xec, 0x62, 0xb5, 0xb9, 0x3a, 0x41, 0xa2, 0xa6, +0x3d, 0x68, 0xa3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0x67, 0x0b, 0xde, +0xbc, 0x13, 0x16, 0x6d, 0x80, 0xc7, 0xc5, 0xd7, +0x89, 0x1e, 0xa5, 0xc2, 0xe7, 0xb7, 0xa1, 0xbf, +0xf8, 0xf9, 0x1b, 0xc6, 0x81, 0x17, 0x63, 0x6c, +0x9f, 0xac, 0xa3, 0x34, 0x9a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0x1e, +0xd0, 0x6e, 0x9e, 0x67, 0x7f, 0x2f, 0x21, 0x28, +0x4b, 0x55, 0x90, 0x3d, 0xc5, 0x6f, 0x62, 0x18, +0x17, 0x18, 0xff, 0xdd, 0xdb, 0xae, 0xeb, 0xe8, +0x86, 0xf6, 0x0e, 0x4d, 0x4f, 0xa3, 0x9d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0x2a, 0xf3, 0x3f, 0xc5, 0x1d, 0x3e, 0x32, +0x61, 0xe2, 0x2a, 0x4a, 0x7c, 0x08, 0xe2, 0x44, +0x04, 0x30, 0xc2, 0x3e, 0x28, 0x17, 0xe8, 0xe6, +0xf5, 0x5b, 0x55, 0x1d, 0x1b, 0x76, 0x93, 0x8a, +0xc6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0x71, 0xbf, 0x6b, 0x70, 0x01, +0x6b, 0xb9, 0x2f, 0xe3, 0xb4, 0xd0, 0xa9, 0x80, +0x13, 0x5c, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x19, 0xa3, 0x89, 0xba, +0xe9, 0x5e, 0xc4, 0x11, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x28, 0x53, 0x9e, +0xf7, 0x5e, 0xe7, 0x1c, 0x15, 0x5f, 0x77, 0x48, +0x40, 0x58, 0x63, 0xdd, 0x09, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa9, 0x9f, +0xd6, 0xd3, 0x58, 0x75, 0x21, 0x43, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x81, +0xa6, 0x25, 0xd4, 0x7a, 0x56, 0x8e, 0x80, 0xeb, +0x7f, 0xb6, 0x85, 0xb7, 0x1c, 0x15, 0x39, 0x5b, +0x4b, 0x8c, 0x92, 0xb3, 0xd4, 0x64, 0x62, 0x61, +0x9a, 0x6b, 0x9d, 0xfb, 0x1d, 0xe7, 0x4e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0x9c, 0x3b, 0xcd, 0x55, 0x36, 0x15, 0x76, +0xa0, 0xc5, 0xc0, 0xde, 0x57, 0x89, 0xed, 0xc0, +0xf7, 0x4a, 0x1e, 0x61, 0x35, 0x83, 0xb1, 0x4e, +0x5e, 0x97, 0x5b, 0x39, 0x0c, 0x23, 0x74, 0x37, +0xd4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0xd0, 0x22, 0x8c, 0x31, 0x70, +0x0d, 0xcd, 0x20, 0x11, 0x83, 0xec, 0xa4, 0xbd, +0xd1, 0x36, 0x72, 0x53, 0x17, 0x35, 0xe6, 0x1a, +0x76, 0x82, 0x57, 0x16, 0xc2, 0x43, 0x6e, 0x7d, +0x08, 0xe6, 0x9c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0x96, 0x8d, 0x05, +0x73, 0x74, 0xdf, 0x5c, 0x65, 0xcb, 0xe6, 0x9b, +0xfe, 0x55, 0xe2, 0x93, 0xce, 0x27, 0x63, 0xcd, +0xcf, 0x04, 0xf5, 0x41, 0x70, 0x9d, 0x02, 0x58, +0xbe, 0x39, 0x87, 0x0e, 0x9c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0xf3, +0x5e, 0xfb, 0xc8, 0xd1, 0xcf, 0xd8, 0xab, 0xbc, +0x59, 0xa8, 0x07, 0x24, 0x1d, 0x60, 0x74, 0x6f, +0xdb, 0x71, 0x79, 0xeb, 0x3c, 0x7e, 0x10, 0x81, +0x57, 0x6c, 0xa2, 0x40, 0xc1, 0xd8, 0xe1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0x36, 0x4b, 0xca, 0x65, 0x61, 0x67, 0xfb, +0x21, 0xea, 0x2d, 0x68, 0xb2, 0x2f, 0xf1, 0xd8, +0x5c, 0xe9, 0x50, 0x09, 0x40, 0x4d, 0xee, 0x13, +0x4d, 0x52, 0xe8, 0x23, 0xcb, 0xce, 0xbc, 0x59, +0x29, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0xb6, 0x8c, 0xf8, 0xa4, 0xe8, +0xd8, 0xac, 0x61, 0xf4, 0xb2, 0x33, 0xf4, 0x15, +0xce, 0x03, 0x75, 0x14, 0xc7, 0xf8, 0xea, 0x89, +0x03, 0xbf, 0xed, 0x93, 0x53, 0x16, 0x95, 0x6b, +0x19, 0xc2, 0x48, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0xe8, 0x65, 0x0c, +0x18, 0x0b, 0x78, 0x6c, 0x1a, 0xdd, 0x18, 0x17, +0x18, 0x4a, 0x66, 0x29, 0xfc, 0x08, 0xc9, 0xfb, +0x0b, 0x26, 0x7b, 0xa1, 0xd9, 0xe5, 0x03, 0x85, +0xd8, 0x17, 0x9a, 0x1c, 0x8a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0x2d, +0x0f, 0x9e, 0xd2, 0x70, 0xc2, 0x1a, 0x0d, 0x1f, +0x6a, 0xea, 0x82, 0x64, 0x7c, 0x40, 0x07, 0x23, +0x9e, 0x83, 0x93, 0x59, 0x3f, 0x9a, 0x5f, 0x65, +0x05, 0xaf, 0xe0, 0xa3, 0x2a, 0x6f, 0x80, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0x46, 0x44, 0x2b, 0x8c, 0xe2, 0xf4, 0x77, +0x4a, 0x23, 0x95, 0x26, 0xe6, 0x81, 0x45, 0xa5, +0x9b, 0x77, 0x72, 0x2f, 0x03, 0xaa, 0x78, 0x84, +0xe8, 0x3e, 0x72, 0x9d, 0x44, 0xf0, 0xd0, 0xf6, +0x6b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0x01, 0x67, 0xb5, 0x53, 0x98, +0xf7, 0xa8, 0xbf, 0x84, 0x31, 0x9d, 0x1a, 0xa0, +0xa4, 0xdf, 0x12, 0x9e, 0x92, 0x60, 0x7d, 0x94, +0x64, 0x15, 0x10, 0x91, 0x07, 0x78, 0x27, 0x3e, +0x1d, 0xc5, 0xb6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x00, 0xb3, 0x50, +0xa8, 0x4e, 0x0b, 0x9d, 0xde, 0xe8, 0x0e, 0x2b, +0x5d, 0x70, 0x3e, 0x48, 0x2a, 0xe6, 0x0e, 0x4c, +0xb4, 0x19, 0xc5, 0xe3, 0x31, 0x2e, 0x9b, 0x33, +0x9b, 0x4c, 0xd3, 0xa4, 0xdf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0x97, +0xc3, 0xc3, 0xc7, 0xe7, 0x5a, 0xad, 0x11, 0xea, +0xf4, 0x7f, 0x20, 0x83, 0xfa, 0x57, 0xa2, 0x9a, +0xa0, 0x47, 0x5f, 0x68, 0x86, 0x68, 0x97, 0xca, +0xf0, 0xa6, 0xa1, 0x24, 0xfd, 0xed, 0xf5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0x98, 0x65, 0x82, 0x5e, 0xc7, 0x6c, 0x6f, +0xa2, 0x9f, 0xc6, 0x5b, 0xb1, 0x14, 0xb7, 0x6f, +0xca, 0x37, 0xb8, 0xca, 0x2f, 0xcd, 0x96, 0x8d, +0x48, 0x11, 0x21, 0x64, 0x36, 0x23, 0xf2, 0x61, +0x4c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0x55, 0x99, 0xc1, 0x17, 0x47, +0xe5, 0xae, 0x7a, 0xa6, 0x05, 0x56, 0x8d, 0x7f, +0xcd, 0xb1, 0x06, 0xf5, 0x52, 0xdf, 0x80, 0x2f, +0x7e, 0x5a, 0x1e, 0xd0, 0xf6, 0xca, 0x32, 0xf6, +0x36, 0x9e, 0xef, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x10, 0xe9, 0xe8, +0x70, 0xfc, 0x8c, 0x91, 0xc4, 0x8b, 0x2e, 0x41, +0x8e, 0x4a, 0x2d, 0x9c, 0x66, 0xf8, 0x6b, 0x67, +0x6c, 0x60, 0x1e, 0x1f, 0xe4, 0x58, 0x43, 0x26, +0xdd, 0xf9, 0xec, 0xaf, 0xaf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0xaf, +0xc1, 0xc9, 0xd4, 0x2a, 0xff, 0x06, 0x45, 0x3b, +0x55, 0xfe, 0xbf, 0x46, 0x83, 0xea, 0x68, 0x86, +0xcb, 0x3c, 0xa4, 0xba, 0x5f, 0xc8, 0x87, 0xe2, +0x83, 0x3a, 0xaf, 0x3b, 0x54, 0x9e, 0x01, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0xaf, 0xb8, 0x7c, 0x52, 0x06, 0xdd, 0x24, +0x64, 0x72, 0x79, 0x91, 0x2f, 0x22, 0x36, 0x9c, +0xe0, 0xe2, 0x97, 0x3d, 0xa9, 0x72, 0xc4, 0x31, +0x9f, 0xa5, 0xb9, 0x69, 0x1f, 0x55, 0x17, 0x89, +0x22, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0x53, 0xcd, 0xb6, 0xe6, 0x97, +0xa7, 0x39, 0xb4, 0xe7, 0x13, 0xf8, 0x3c, 0x55, +0xab, 0x51, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x72, 0xaf, 0x4d, 0xc5, +0xd7, 0xa0, 0x14, 0xa7, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0xc0, 0x95, 0x69, +0x33, 0x71, 0x4f, 0xde, 0x6e, 0xb5, 0xb2, 0xbe, +0xfd, 0x2a, 0xb2, 0x10, 0xcb, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xf6, +0x4f, 0xea, 0x77, 0xe1, 0x18, 0xe7, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x4d, +0x4c, 0x0a, 0x19, 0xd2, 0x79, 0x84, 0x47, 0x7e, +0x89, 0xd8, 0x83, 0xd1, 0x7d, 0x12, 0x7b, 0x02, +0xba, 0x31, 0xdf, 0xbc, 0x3b, 0x8b, 0xa8, 0x31, +0x92, 0xbd, 0xfc, 0x43, 0x1e, 0x0e, 0x8c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0xe0, 0xc5, 0x87, 0xe5, 0x8d, 0x10, 0x78, +0x0a, 0xd8, 0x50, 0x4b, 0xc7, 0x7e, 0x1c, 0x7e, +0x25, 0xf2, 0x5f, 0x69, 0xb1, 0x67, 0xc2, 0xfc, +0x9f, 0x3e, 0x59, 0xb6, 0xfb, 0x02, 0x02, 0xbc, +0x69, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0xb3, 0x2a, 0xdf, 0x4c, 0x0c, +0x00, 0xcb, 0xb6, 0xcb, 0x36, 0xfb, 0xd5, 0x9e, +0x10, 0xd8, 0xd2, 0xd3, 0x37, 0xf9, 0x2d, 0x3e, +0x0e, 0xc2, 0xb9, 0x62, 0xcf, 0x74, 0xb2, 0x0c, +0x94, 0xb8, 0x0b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0x5e, 0x89, 0x9a, +0x4e, 0x2f, 0x7e, 0x43, 0x7b, 0x5b, 0x05, 0x5b, +0x3f, 0x5e, 0xc9, 0x3f, 0x95, 0x41, 0x03, 0x4a, +0xde, 0x96, 0x3c, 0x4f, 0x89, 0x89, 0x76, 0x1a, +0x92, 0x22, 0xe4, 0x2d, 0x15, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x4e, +0x8a, 0xe1, 0xb9, 0xc0, 0x37, 0x94, 0x18, 0x26, +0x00, 0x0b, 0x11, 0x39, 0x02, 0x2a, 0x18, 0xfa, +0x85, 0x83, 0x8f, 0xfc, 0x36, 0xc6, 0x43, 0x0e, +0x2b, 0x73, 0x19, 0x56, 0x73, 0x13, 0x8e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0xfb, 0x2a, 0x9d, 0xe5, 0x2c, 0x19, 0xbb, +0x9a, 0x4a, 0x66, 0xb1, 0x4d, 0x35, 0x3b, 0x6a, +0x7a, 0x34, 0xa3, 0x0e, 0x36, 0x21, 0xaf, 0xec, +0xe1, 0x53, 0x99, 0xc4, 0xcf, 0xa2, 0x94, 0x01, +0x70, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0x17, 0xf0, 0xdb, 0x4a, 0xae, +0x5b, 0x85, 0x58, 0x99, 0x59, 0xae, 0xd2, 0x49, +0xe1, 0x24, 0x38, 0x55, 0xb2, 0x92, 0xc4, 0x97, +0xbe, 0x07, 0x02, 0xf7, 0xaf, 0x70, 0xaa, 0x30, +0xeb, 0x10, 0x50, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0x17, 0x36, 0xc2, +0xc6, 0xe1, 0x88, 0x1d, 0x88, 0x7c, 0xbf, 0x1c, +0xe1, 0x59, 0xe1, 0x1a, 0x97, 0xcd, 0xdc, 0xc2, +0x57, 0x73, 0xf5, 0xaa, 0x73, 0xc7, 0x91, 0x87, +0x4d, 0x12, 0x0c, 0x5d, 0x03, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0xf1, +0xfb, 0x54, 0x96, 0xbc, 0x96, 0x0f, 0x7e, 0xbf, +0xe8, 0x26, 0x36, 0xe4, 0xd0, 0x67, 0xe1, 0x93, +0x2b, 0xd0, 0xd8, 0xdd, 0x0b, 0x5e, 0xc8, 0x8a, +0x90, 0xf3, 0x58, 0x41, 0xdf, 0x3d, 0x91, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0x1a, 0x4a, 0x92, 0xd1, 0x57, 0xb8, 0x9d, +0x40, 0x9e, 0x79, 0x9f, 0xd4, 0x63, 0x24, 0x62, +0xb7, 0x2b, 0xb2, 0x17, 0xe3, 0x91, 0xb7, 0x6c, +0x41, 0xca, 0x41, 0xc4, 0xe3, 0x8e, 0xf0, 0xe5, +0x44, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x5e, 0x6e, 0xba, 0x2f, 0x83, +0x9e, 0x5c, 0x0b, 0x4f, 0xd0, 0x83, 0xa2, 0x8d, +0x3e, 0x79, 0xe7, 0xc7, 0x31, 0x13, 0x1f, 0x0d, +0xcf, 0x83, 0xec, 0xa2, 0x3f, 0x08, 0x45, 0x51, +0x8c, 0xd4, 0x7d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0xbf, 0x9d, 0x4a, +0x86, 0xe6, 0x16, 0xf7, 0x34, 0x64, 0xf0, 0x98, +0x02, 0x71, 0xeb, 0x10, 0x76, 0x67, 0xf1, 0xa3, +0x73, 0xaa, 0xce, 0x99, 0x38, 0x4b, 0xec, 0x61, +0x2a, 0x4f, 0x01, 0x25, 0x4a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0xfa, +0xff, 0xd4, 0x43, 0x7e, 0x06, 0x22, 0x95, 0x7c, +0xe1, 0x09, 0x69, 0xd3, 0x11, 0x07, 0x31, 0x18, +0xfd, 0x20, 0x12, 0xf5, 0x76, 0xc8, 0x41, 0xf4, +0x2b, 0xef, 0xbf, 0xa9, 0x7a, 0x67, 0xc9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0x84, 0xef, 0x47, 0x6e, 0x7b, 0x1d, 0x11, +0x10, 0x01, 0xe8, 0xf4, 0x3d, 0x39, 0xff, 0x71, +0xae, 0xa8, 0x0e, 0x88, 0xb9, 0x69, 0x43, 0xfb, +0x05, 0x3a, 0xe7, 0x54, 0xef, 0xd3, 0x0f, 0xf3, +0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0x3c, 0x76, 0xf0, 0x1f, 0xb9, +0x2c, 0x59, 0x99, 0x21, 0xb1, 0x8f, 0x15, 0x76, +0x3c, 0x95, 0xb5, 0x04, 0xd1, 0x0f, 0x4d, 0xd1, +0xa3, 0xa4, 0x29, 0xc1, 0xaf, 0xe2, 0xd8, 0xaa, +0xfd, 0x75, 0x52, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0x1d, 0xc0, 0x3e, +0x83, 0x96, 0x38, 0xae, 0x4b, 0x54, 0x3e, 0xde, +0xd6, 0x91, 0xd1, 0x59, 0xae, 0x2c, 0x0e, 0xa7, +0xf1, 0x75, 0xa7, 0xdb, 0x24, 0x38, 0x2e, 0xd2, +0x08, 0x01, 0x28, 0x8f, 0x28, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0xe9, +0xf5, 0x8b, 0x1d, 0x7d, 0x04, 0x9f, 0x4e, 0xd4, +0x2d, 0x59, 0xaf, 0xb9, 0x94, 0x1b, 0x37, 0x7e, +0xc5, 0xb6, 0x28, 0xb3, 0x96, 0x64, 0xfc, 0x5f, +0xff, 0x98, 0xe8, 0xa7, 0x5b, 0x87, 0x0e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0x98, 0xa4, 0x79, 0x8b, 0x3b, 0x29, 0xcb, +0xea, 0xc7, 0x6a, 0xab, 0x86, 0x50, 0x49, 0x29, +0x26, 0xfa, 0x5c, 0x23, 0xae, 0x3d, 0xfd, 0x39, +0xf6, 0x35, 0xe7, 0xd0, 0x6b, 0xd3, 0xc5, 0x95, +0x12, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0xdf, 0x05, 0x0d, 0xbf, 0xa1, +0x8c, 0xca, 0x00, 0x83, 0xa1, 0x3b, 0x8b, 0x2b, +0xb9, 0x05, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x3f, 0xc0, 0xff, 0x01, +0x3e, 0xdb, 0x14, 0x48, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0xae, 0x48, 0xcf, +0x39, 0xbb, 0xf5, 0x2c, 0x6a, 0x08, 0xb9, 0x5e, +0x44, 0x56, 0x63, 0x15, 0xe6, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xa5, +0x16, 0x39, 0x96, 0xfa, 0x2e, 0xb8, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x37, +0x9b, 0x65, 0x05, 0x3a, 0x15, 0x08, 0x73, 0xc6, +0x19, 0xaf, 0xcd, 0xfc, 0xb7, 0x5d, 0x93, 0x29, +0x0f, 0x35, 0x7b, 0x00, 0x26, 0xfd, 0x46, 0x99, +0x85, 0x8c, 0x12, 0x12, 0xbf, 0x5c, 0x8c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0x88, 0x4f, 0x64, 0xff, 0x30, 0xf8, 0xd4, +0xa6, 0xeb, 0xf9, 0xaf, 0x83, 0x10, 0xc8, 0x76, +0xff, 0xf4, 0x70, 0xc5, 0xce, 0x5b, 0x98, 0x87, +0x5f, 0xdb, 0xc9, 0x05, 0xdc, 0x02, 0x6b, 0xe2, +0xfc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0x8e, 0x2e, 0x27, 0x4e, 0xb0, +0x02, 0x1b, 0x2e, 0xef, 0xe3, 0x20, 0x6a, 0xcd, +0x1b, 0x45, 0x72, 0xa2, 0x34, 0x76, 0x18, 0xb5, +0x26, 0x9f, 0x87, 0x64, 0xf8, 0x80, 0xc6, 0x68, +0x70, 0xe1, 0x88, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0xbb, 0x83, 0xed, +0x78, 0xb7, 0x26, 0x48, 0xb6, 0x27, 0x2f, 0x5d, +0xe5, 0xbb, 0x2d, 0xb6, 0xf3, 0x6f, 0xd9, 0x22, +0xdf, 0xa5, 0x4a, 0x6c, 0x31, 0x40, 0x93, 0xe8, +0x46, 0x88, 0x90, 0x60, 0x62, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0xf0, +0xf8, 0x8f, 0xac, 0x83, 0x7b, 0x7c, 0xb1, 0xbf, +0x6d, 0xaa, 0x49, 0x64, 0x0a, 0xce, 0x07, 0xa8, +0x7d, 0xdc, 0xd2, 0x5f, 0x36, 0x9e, 0x9b, 0x49, +0x6a, 0x9a, 0x34, 0x29, 0x4b, 0x43, 0x55, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0xcd, 0x64, 0x62, 0xb6, 0x53, 0xf2, 0xee, +0x5c, 0x36, 0x90, 0xdc, 0xcb, 0x84, 0x19, 0x3a, +0x24, 0xf0, 0xea, 0xc2, 0xa3, 0x44, 0x1f, 0x0a, +0x65, 0x2d, 0xa1, 0x35, 0xed, 0xb0, 0x2f, 0x21, +0xaf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0xbd, 0x6e, 0x99, 0x40, 0xd8, +0x3d, 0x34, 0xb6, 0xc2, 0x83, 0xee, 0xf2, 0x18, +0x4c, 0x5a, 0x44, 0xb1, 0x02, 0x21, 0x49, 0xa3, +0x88, 0x68, 0xae, 0x44, 0xa1, 0xaa, 0x49, 0x69, +0x6c, 0x45, 0xea, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0x04, 0x53, 0x91, +0x17, 0xa6, 0x5e, 0x15, 0xd3, 0x97, 0x45, 0x85, +0xd7, 0x82, 0xaf, 0xa9, 0x28, 0x0d, 0x64, 0x15, +0x68, 0xaf, 0x59, 0xce, 0x14, 0xd2, 0xe6, 0x6c, +0x4e, 0x48, 0xa9, 0x6a, 0xf8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0x30, +0x9c, 0x26, 0x3b, 0x7c, 0x01, 0xfa, 0x2d, 0x53, +0xb4, 0x56, 0xdf, 0xfe, 0x6d, 0x45, 0x99, 0xe3, +0xdb, 0x91, 0x52, 0x77, 0xbf, 0xd1, 0x75, 0xee, +0x8f, 0x6d, 0x29, 0x4d, 0x35, 0x5d, 0x36, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0xd3, 0x06, 0xc1, 0xa9, 0x68, 0xd9, 0x1a, +0xe6, 0xa8, 0x9c, 0xf0, 0xab, 0xec, 0xcc, 0x71, +0x55, 0x5d, 0xe2, 0x47, 0x3b, 0x0a, 0x87, 0x07, +0x65, 0xcd, 0x18, 0x28, 0xed, 0x08, 0x7b, 0x63, +0xcc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0x89, 0xae, 0xf1, 0x8e, 0xea, +0xe8, 0x42, 0x8b, 0xaf, 0x31, 0xbe, 0x06, 0xfe, +0x49, 0xbd, 0xe3, 0x95, 0xd6, 0xf2, 0xc1, 0x4e, +0xfb, 0xab, 0xf6, 0xe4, 0x35, 0x22, 0xc7, 0x50, +0x9b, 0x3b, 0xb5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0x69, 0x94, 0xfd, +0x27, 0xc9, 0x81, 0x56, 0xb7, 0x89, 0x21, 0x61, +0x66, 0xf9, 0x60, 0x8c, 0xd8, 0x02, 0x9e, 0xa4, +0x01, 0x30, 0xd5, 0x82, 0xc5, 0x34, 0xa1, 0xd8, +0xf9, 0x79, 0xf0, 0xac, 0x5a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0x66, +0x04, 0x2d, 0xa4, 0xb8, 0xc3, 0x8f, 0x38, 0x1b, +0xe0, 0x6a, 0x63, 0xaa, 0x14, 0x81, 0x07, 0x48, +0x00, 0x8f, 0xef, 0xc6, 0x9f, 0x88, 0x00, 0x43, +0x0a, 0xd9, 0xc5, 0x68, 0x73, 0x78, 0xf6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0x0b, 0xf3, 0xb6, 0x2e, 0x7c, 0xa7, 0x26, +0x45, 0x3f, 0x63, 0x70, 0xba, 0xc3, 0x2d, 0x02, +0x4f, 0x9f, 0x83, 0x36, 0x74, 0x06, 0x9d, 0xf9, +0x42, 0xb3, 0x10, 0xac, 0xe4, 0x75, 0x63, 0x7d, +0xa5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0xc0, 0x32, 0x42, 0x98, 0x1f, +0x1b, 0x5b, 0x92, 0xca, 0x9a, 0x59, 0xfb, 0x75, +0x19, 0x5a, 0xbd, 0xa5, 0x3e, 0xec, 0x9e, 0x3a, +0x96, 0x6c, 0x70, 0x18, 0x06, 0xda, 0x6f, 0xcc, +0x31, 0xc4, 0x0d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0x38, 0xd8, 0x21, +0xb7, 0xa9, 0x5b, 0xc5, 0x1f, 0x8a, 0x3e, 0xb5, +0xa1, 0x9a, 0xfb, 0x45, 0x1a, 0x55, 0xcf, 0x9a, +0x98, 0x91, 0xed, 0x6a, 0xba, 0x7d, 0x6c, 0xec, +0x9f, 0x36, 0x73, 0x57, 0x50, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0x9f, +0xcd, 0xda, 0x89, 0x6f, 0x2b, 0xc5, 0x0c, 0x81, +0xfc, 0x07, 0x6a, 0xdc, 0xc4, 0x4e, 0xad, 0x67, +0x1e, 0xb9, 0x70, 0x22, 0x47, 0x18, 0x67, 0x22, +0xe1, 0x03, 0x18, 0x82, 0x2e, 0xda, 0x6e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0xe1, 0xb0, 0xde, 0xb6, 0x2c, 0xaa, 0x5f, +0xe2, 0x93, 0x6b, 0x0a, 0xaa, 0xfa, 0xd0, 0xfa, +0xb9, 0xd0, 0xf2, 0x6c, 0xaf, 0x90, 0x46, 0x90, +0x64, 0xcf, 0xda, 0xaf, 0xaf, 0x87, 0x53, 0xfd, +0x97, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0x3b, 0x18, 0xbf, 0x30, 0x55, +0x87, 0x38, 0x12, 0x65, 0x6e, 0xa4, 0xaf, 0xfa, +0xb5, 0xc4, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x17, 0x5e, 0x20, 0x7f, +0xd5, 0x08, 0xd2, 0x1d, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0xea, 0xd4, 0x1a, +0xa8, 0x49, 0x6c, 0xf2, 0x03, 0x0c, 0xc9, 0x90, +0x8c, 0x04, 0x9c, 0xe2, 0xa7, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0xb1, +0xf3, 0x56, 0xb5, 0x6a, 0xb8, 0x36, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0xc1, +0x49, 0x08, 0x97, 0xdc, 0x6b, 0xf7, 0x9f, 0x44, +0x6e, 0x5a, 0x51, 0xdc, 0x32, 0xb5, 0xe1, 0x8f, +0x67, 0x08, 0xdf, 0x50, 0x89, 0x4f, 0x95, 0x53, +0x62, 0x5b, 0xd2, 0x45, 0xbd, 0x6c, 0x03, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x8b, 0x07, 0x12, 0x30, 0xec, 0x25, 0xdc, +0x98, 0x44, 0xae, 0x9c, 0xaa, 0xa0, 0x78, 0x45, +0xa9, 0x9b, 0xcc, 0xdb, 0x69, 0x3a, 0x66, 0x85, +0xb1, 0xff, 0xf9, 0xdf, 0x08, 0xab, 0x61, 0x41, +0x88, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0x3b, 0x74, 0x00, 0x4c, 0x45, +0x07, 0xc8, 0xa4, 0x6b, 0x56, 0xf7, 0xd7, 0x4e, +0xbb, 0xd8, 0x93, 0xe6, 0xed, 0x0c, 0xd5, 0x03, +0xcb, 0xcd, 0x6d, 0x08, 0x03, 0xe9, 0x07, 0x3d, +0x92, 0x0e, 0x06, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x82, 0x8a, 0xa7, +0x5b, 0xd0, 0x68, 0x59, 0x77, 0xe3, 0x20, 0xce, +0xba, 0x3e, 0x27, 0xe8, 0xa7, 0x15, 0xf4, 0x1f, +0xb6, 0xda, 0x93, 0xdb, 0x7a, 0x65, 0x69, 0x32, +0xf1, 0x0a, 0xcb, 0x5a, 0xac, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0x54, +0xec, 0x88, 0x23, 0xfe, 0x5f, 0xd3, 0x75, 0xcb, +0x3f, 0x92, 0xd3, 0xae, 0xcd, 0xa2, 0x73, 0xf9, +0x11, 0x04, 0xaa, 0x30, 0x75, 0x19, 0x59, 0x29, +0xd2, 0x3f, 0x20, 0x50, 0xd3, 0x80, 0xfa, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0xf6, 0x66, 0x19, 0x00, 0x21, 0xa0, 0x4a, +0xf4, 0xf3, 0x16, 0x95, 0x43, 0xd7, 0xe3, 0x68, +0xa8, 0x8b, 0x93, 0x35, 0x11, 0xbb, 0xe1, 0xcc, +0x13, 0xb5, 0x57, 0xf7, 0xc6, 0x4b, 0x51, 0x9a, +0x46, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0xa7, 0xc4, 0x74, 0xa7, 0xa0, +0x23, 0x46, 0x97, 0x26, 0xe2, 0x17, 0xf4, 0x05, +0x2f, 0xa7, 0x57, 0x72, 0x4a, 0x06, 0x0d, 0xee, +0xe3, 0x15, 0xf8, 0x72, 0xc1, 0x62, 0x56, 0x33, +0x67, 0x1e, 0x70, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x8a, 0x35, 0x69, +0x6e, 0x11, 0x65, 0xf9, 0x7f, 0xba, 0xb2, 0x1d, +0xc1, 0x09, 0x62, 0xe9, 0xa1, 0x6a, 0xfa, 0x0d, +0x8c, 0x14, 0x6e, 0x4b, 0x60, 0x1c, 0x9b, 0x42, +0xa5, 0x4a, 0x5d, 0x9f, 0xb0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0x6c, +0x18, 0x4b, 0x5b, 0x15, 0x46, 0xdc, 0x7c, 0x72, +0x7d, 0x85, 0xc0, 0x82, 0x31, 0xca, 0x8c, 0x75, +0xc8, 0xec, 0x37, 0xe4, 0x20, 0x66, 0x3b, 0x45, +0xe7, 0x44, 0x54, 0x97, 0x48, 0x17, 0x53, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0x70, 0x1a, 0xdd, 0x81, 0x1d, 0x6d, 0xd6, +0xcc, 0x9d, 0xac, 0x5b, 0x56, 0xf3, 0xbb, 0xe9, +0x8d, 0xe5, 0xf9, 0xf1, 0x07, 0xdb, 0xf3, 0xf9, +0xf6, 0xfc, 0x2b, 0x63, 0xf3, 0x88, 0x44, 0xba, +0x73, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0x90, 0xcd, 0xdb, 0x95, 0x90, +0x1d, 0x17, 0xd9, 0x52, 0xfb, 0xf4, 0x98, 0x21, +0x7c, 0xf1, 0x13, 0x9a, 0x1f, 0xf9, 0xb2, 0xd3, +0x4c, 0x7c, 0x5c, 0x37, 0x0e, 0x88, 0x6d, 0xb4, +0x5c, 0xec, 0x3f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0x5d, 0x92, 0x79, +0x27, 0x60, 0xa8, 0x89, 0x54, 0x97, 0xbd, 0xef, +0x76, 0xf0, 0xb3, 0xfe, 0x15, 0xff, 0xc8, 0x59, +0x10, 0x78, 0x61, 0xa3, 0xd4, 0x39, 0x2e, 0x39, +0x62, 0x95, 0xf4, 0x87, 0x0f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0xce, +0xd9, 0x3e, 0xc8, 0x09, 0x86, 0x8e, 0x53, 0x24, +0x42, 0x19, 0xc0, 0x25, 0xa0, 0x91, 0x47, 0x2e, +0xe2, 0xcc, 0x98, 0xd7, 0x75, 0x59, 0x1a, 0x8b, +0x4a, 0x22, 0xe1, 0x86, 0xe5, 0xd4, 0x5a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0xb0, 0xbb, 0x28, 0xc0, 0x20, 0x3e, 0xc8, +0xbb, 0x38, 0xdf, 0x1b, 0x23, 0x34, 0x7c, 0x92, +0x96, 0x77, 0xaa, 0x01, 0x90, 0x60, 0x4e, 0xc6, +0x8f, 0x36, 0xe0, 0x31, 0x6e, 0xa8, 0x57, 0x5a, +0x34, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0xba, 0x7a, 0x47, 0x80, 0x8d, +0xaf, 0xf0, 0x9f, 0xc0, 0x79, 0x9a, 0xf1, 0x6d, +0x66, 0xcf, 0x45, 0x8c, 0x06, 0xe2, 0x77, 0xd8, +0x0f, 0xd8, 0x99, 0xc5, 0xac, 0x5b, 0x24, 0x9c, +0xa1, 0x1a, 0x6f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x49, 0xdf, 0x11, +0x08, 0x5e, 0xb9, 0xe8, 0x4d, 0xcc, 0x9c, 0xd9, +0xf8, 0xdb, 0x92, 0xea, 0x46, 0x2b, 0xa1, 0x73, +0x9c, 0x11, 0x50, 0x0b, 0x4f, 0x9e, 0xfe, 0x24, +0x86, 0x07, 0x89, 0x6f, 0xd2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x8a, +0x50, 0x0a, 0xd8, 0x51, 0x01, 0x7c, 0x5e, 0x81, +0xb3, 0x41, 0x1f, 0x3e, 0xa7, 0xfe, 0xda, 0x49, +0x9e, 0x37, 0x91, 0x30, 0xa9, 0xfb, 0x65, 0xca, +0x9f, 0xb0, 0xc5, 0x7b, 0xf1, 0x65, 0xae, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0xdf, 0xc0, 0x8b, 0x83, 0xf6, 0xa4, 0xd1, +0x74, 0xcf, 0xed, 0x16, 0x73, 0xa3, 0x0e, 0xe7, +0x82, 0x8a, 0x44, 0x61, 0x94, 0xe3, 0x97, 0xc2, +0xe5, 0xc1, 0x6d, 0x24, 0x2a, 0x15, 0x26, 0x2f, +0xae, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0x0a, 0x14, 0x55, 0x01, 0x28, +0x8f, 0x17, 0xa4, 0xa8, 0x7c, 0xc7, 0x8f, 0xd6, +0x29, 0xf8, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x98, 0xab, 0x98, 0x50, +0xc7, 0xbf, 0xac, 0xb5, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0x9c, 0xe1, 0x5c, +0x87, 0x46, 0xf1, 0xcb, 0x25, 0xb5, 0x00, 0x51, +0x45, 0x0f, 0xc6, 0x43, 0x5f, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa9, 0x8f, +0xc5, 0x0c, 0x22, 0xd0, 0x2d, 0xf1, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0x90, +0x59, 0x8b, 0x65, 0xb7, 0x1a, 0xa0, 0xa9, 0x83, +0xcf, 0x84, 0xda, 0x58, 0x83, 0x70, 0xbd, 0x4a, +0x87, 0x58, 0xd0, 0xe5, 0x48, 0xf1, 0x61, 0xd6, +0xce, 0xa0, 0x06, 0x9e, 0x21, 0x39, 0xd4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x58, 0xd9, 0x64, 0x60, 0x63, 0x6a, 0x57, +0xb8, 0x74, 0xc1, 0x9e, 0x68, 0x19, 0xf0, 0xa0, +0x41, 0x78, 0xf8, 0xbc, 0x5e, 0x96, 0xba, 0x3f, +0x63, 0xd1, 0x1d, 0x97, 0x3c, 0xdf, 0x07, 0x45, +0xc2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0x9e, 0xf3, 0x14, 0xbb, 0x4e, +0x66, 0x6c, 0x7d, 0x1d, 0x90, 0x69, 0xa1, 0xc9, +0xf9, 0x0b, 0x18, 0x08, 0x65, 0x92, 0xa0, 0xd0, +0xd1, 0x96, 0x4d, 0x48, 0x5d, 0x2e, 0xc8, 0xbf, +0x34, 0x13, 0x7e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0x53, 0xe8, 0xc0, +0x24, 0x1c, 0xc3, 0xfd, 0x4b, 0x28, 0x74, 0x3c, +0x5c, 0x03, 0x92, 0xab, 0xe0, 0x67, 0x3a, 0x61, +0xe8, 0x58, 0x18, 0x83, 0xb2, 0x66, 0x9a, 0x24, +0xdf, 0xe9, 0x5f, 0xe1, 0xb4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0xdd, +0x63, 0xeb, 0x90, 0xc4, 0x59, 0x86, 0xea, 0x6a, +0x2f, 0xcf, 0xc3, 0x8a, 0x4e, 0xe6, 0x34, 0xee, +0x03, 0x45, 0xd9, 0xef, 0xb5, 0x7d, 0x18, 0xc2, +0xd1, 0x82, 0x7e, 0x31, 0x43, 0x32, 0xd4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0xaf, 0xed, 0x19, 0x0e, 0x0e, 0xf0, 0x3b, +0x12, 0x9b, 0x7d, 0x17, 0xbf, 0x42, 0xce, 0x5e, +0xf5, 0x51, 0x50, 0xe7, 0x4b, 0x27, 0xa5, 0xd4, +0x45, 0xca, 0x30, 0xff, 0xb6, 0xbd, 0xaa, 0xb6, +0x5b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0x27, 0xd9, 0xc2, 0xb7, 0x69, +0x03, 0xe1, 0xdd, 0x8f, 0x67, 0x09, 0xa7, 0x39, +0x1a, 0xab, 0xe1, 0x41, 0xd9, 0x7e, 0x69, 0xd8, +0xd0, 0xc5, 0xe8, 0xaa, 0xe0, 0x61, 0x8e, 0xb7, +0xee, 0xb8, 0xec, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0xdd, 0x2a, 0x56, +0x40, 0xbd, 0xab, 0x8e, 0x55, 0xe0, 0x49, 0xd5, +0x0b, 0x3b, 0x63, 0x6e, 0x08, 0x0d, 0x6f, 0xd0, +0x55, 0xff, 0xa8, 0x61, 0xa2, 0xa1, 0xc8, 0xd2, +0x6f, 0x3f, 0xe2, 0x86, 0x9b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0x8c, +0x68, 0xb1, 0xf2, 0xdc, 0xe4, 0xd4, 0x6e, 0x59, +0x29, 0x2d, 0xec, 0x92, 0x60, 0xdc, 0x88, 0x6c, +0x9a, 0x00, 0x1f, 0x12, 0x5f, 0x8e, 0x5c, 0x81, +0xa9, 0xe4, 0xa6, 0xaf, 0x6e, 0x6f, 0x17, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x81, 0x5e, 0x15, 0x1b, 0xc5, 0x31, 0xd2, +0xdd, 0x28, 0xb2, 0x84, 0x3b, 0x05, 0xcf, 0xa5, +0x78, 0x69, 0x66, 0x56, 0xa3, 0xdc, 0x50, 0x87, +0x01, 0x22, 0x3f, 0xc5, 0xfe, 0x1c, 0xef, 0x64, +0x89, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0x92, 0xa6, 0x5d, 0x25, 0xde, +0x7b, 0x22, 0xdd, 0xbb, 0x07, 0xcb, 0x2d, 0x99, +0x71, 0xda, 0x7c, 0x7b, 0x11, 0x16, 0xeb, 0xba, +0xb8, 0x7d, 0x60, 0xd3, 0x9f, 0x16, 0x79, 0x35, +0xb9, 0xcc, 0x79, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0x69, 0x39, 0xcb, +0x32, 0x0d, 0x20, 0xcd, 0xfe, 0x88, 0x00, 0x59, +0x8c, 0x20, 0xdd, 0xa6, 0x15, 0x84, 0x39, 0xae, +0xad, 0xfc, 0xd3, 0x79, 0x93, 0x5e, 0x8a, 0x4b, +0xe3, 0x03, 0xba, 0x51, 0x92, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0x70, +0x08, 0x0e, 0x66, 0xc5, 0x15, 0x66, 0xe2, 0x29, +0x73, 0x8c, 0xfa, 0xae, 0xc5, 0xd3, 0x8c, 0x08, +0x09, 0x41, 0x50, 0x86, 0x60, 0xe7, 0x4b, 0x38, +0x85, 0xc1, 0xdf, 0x3e, 0x34, 0x91, 0xae, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0x87, 0x58, 0x24, 0xed, 0x42, 0xcc, 0x0b, +0x28, 0x56, 0x3a, 0xf5, 0xa5, 0x0e, 0x35, 0xc5, +0x3d, 0xe7, 0xa9, 0x94, 0x64, 0xa5, 0x5e, 0x64, +0xe6, 0xf4, 0x4a, 0x2d, 0x1f, 0xec, 0xef, 0x7d, +0xec, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0xf3, 0x76, 0xa8, 0x85, 0xfe, +0x75, 0x86, 0x9f, 0x64, 0xde, 0xc1, 0x19, 0x6f, +0x15, 0xb4, 0xb5, 0xac, 0x8c, 0x8a, 0x54, 0xc4, +0xdf, 0xfa, 0xb3, 0x46, 0x5c, 0x3b, 0x30, 0xd6, +0x12, 0xde, 0x99, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0x01, 0xba, 0x2b, +0xca, 0x71, 0x82, 0x7b, 0xa2, 0x7f, 0x11, 0xd1, +0x79, 0xf0, 0xe9, 0xff, 0xec, 0xd3, 0xa3, 0x72, +0xc0, 0x19, 0x8b, 0x47, 0x2e, 0x79, 0x34, 0xbd, +0x57, 0x35, 0x10, 0xdb, 0x4d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0xe1, +0xd3, 0x47, 0x30, 0x1d, 0xd9, 0x20, 0xf4, 0x81, +0x57, 0x6d, 0x54, 0x08, 0x42, 0x65, 0x09, 0x28, +0x06, 0x32, 0x3e, 0xee, 0x28, 0x9c, 0xa3, 0xd1, +0xde, 0xc7, 0x58, 0x23, 0x39, 0x33, 0x79, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0x7a, 0xf1, 0x6c, 0xa5, 0x83, 0x70, 0x91, +0xe3, 0xe8, 0xc8, 0xfa, 0x1e, 0x7f, 0x6c, 0x10, +0xbf, 0x08, 0x3f, 0xb0, 0x8b, 0x9f, 0xa7, 0xdb, +0x1e, 0xff, 0xa5, 0xed, 0x35, 0x31, 0xcb, 0x47, +0x2b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0x53, 0x81, 0x21, 0xd7, 0xa8, +0x47, 0xa6, 0x88, 0x48, 0x12, 0x89, 0x1c, 0xa0, +0xf5, 0xd7, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x83, 0x51, 0x56, 0xaa, +0x3b, 0xfe, 0xf6, 0xf4, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0xc6, 0x54, 0x07, +0xd9, 0x46, 0xaa, 0xbf, 0x82, 0x4e, 0xe8, 0xd9, +0x64, 0x42, 0x14, 0x02, 0xcc, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0xaf, +0x49, 0x01, 0x94, 0x08, 0xa8, 0x3d, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0xde, +0x94, 0xa2, 0x79, 0x89, 0x4c, 0x37, 0xb5, 0xc2, +0xbb, 0x1c, 0xe5, 0xc0, 0x31, 0x60, 0x6a, 0x95, +0x7b, 0x10, 0x7c, 0x86, 0x58, 0x96, 0x07, 0x39, +0x2b, 0xbb, 0x47, 0x34, 0xd0, 0x48, 0x7d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0xaa, 0x7c, 0xd7, 0x11, 0x26, 0x7b, 0xdb, +0x5c, 0x6b, 0x66, 0x29, 0x1e, 0x48, 0xa9, 0x5f, +0x70, 0x41, 0x43, 0x73, 0x3b, 0xf8, 0x2a, 0x52, +0x3d, 0x8d, 0xee, 0xcd, 0x5d, 0x2f, 0xce, 0x2b, +0xcd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0xd0, 0x16, 0xe5, 0xcb, 0x80, +0xfc, 0x82, 0x5e, 0xe6, 0x93, 0xeb, 0x65, 0xe9, +0xcf, 0xd0, 0xe1, 0x4b, 0x1e, 0x44, 0x61, 0xab, +0x01, 0xfc, 0x61, 0x1d, 0x8a, 0xf7, 0xae, 0x8f, +0x5e, 0xd3, 0xa7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0x18, 0x66, 0x61, +0xab, 0xfe, 0xa0, 0xb9, 0x31, 0x12, 0xb1, 0x02, +0xec, 0xf5, 0x64, 0x5e, 0x80, 0xd6, 0x3e, 0x33, +0x17, 0x9d, 0xfb, 0xcc, 0x8f, 0x8f, 0xc0, 0x8e, +0x82, 0xa4, 0x95, 0x64, 0xb6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0x21, +0x84, 0xae, 0x0c, 0x13, 0x94, 0x76, 0x6d, 0x3c, +0x2f, 0x59, 0xa2, 0xc4, 0x64, 0x0e, 0xb6, 0x84, +0x73, 0x73, 0xfc, 0x8b, 0x6e, 0x54, 0x52, 0x46, +0x65, 0x52, 0xad, 0x50, 0xd7, 0xd4, 0x00, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0x9f, 0x6b, 0x45, 0x5d, 0xb1, 0x04, 0x9f, +0xa4, 0x9d, 0x90, 0x8f, 0xa7, 0x14, 0xf5, 0xc5, +0x71, 0x59, 0xd5, 0xa4, 0x44, 0x86, 0xd5, 0xb6, +0x13, 0x28, 0x2a, 0xdb, 0xfb, 0x33, 0x25, 0x1c, +0x73, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0xa7, 0x70, 0x9b, 0x2e, 0x86, +0xca, 0xbf, 0xa2, 0x17, 0xfd, 0xea, 0x52, 0xf5, +0x03, 0x15, 0xc6, 0xed, 0x43, 0x10, 0x8a, 0xa7, +0x3b, 0x5f, 0x0a, 0xea, 0xee, 0xc3, 0xc9, 0x89, +0x13, 0x77, 0xa9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0x40, 0xb7, 0xb5, +0xc4, 0x0d, 0x4a, 0xb3, 0x29, 0x9c, 0xd3, 0x8d, +0xba, 0x1a, 0x2d, 0xf4, 0x9e, 0xbb, 0xfa, 0x5a, +0xc5, 0x5f, 0x33, 0x7a, 0x5e, 0x4e, 0xd4, 0xa9, +0xb0, 0x1e, 0x5e, 0x5d, 0xc0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0x4e, +0x76, 0xe3, 0x94, 0xf2, 0x2a, 0x8c, 0x4f, 0x12, +0x45, 0x10, 0xb9, 0x99, 0x9e, 0x6c, 0xbb, 0x1e, +0x0f, 0x12, 0x4a, 0x99, 0x9c, 0xaf, 0x66, 0x82, +0xf1, 0xa4, 0xa1, 0xd5, 0x1f, 0xe8, 0x0c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0x9e, 0x52, 0x66, 0xdb, 0x4b, 0x46, 0xc7, +0xfc, 0x67, 0xb7, 0x7d, 0x81, 0x50, 0x8b, 0xf5, +0x09, 0x8b, 0x23, 0x40, 0x70, 0x23, 0x2b, 0xe5, +0x3e, 0xdc, 0xb3, 0x8d, 0x9a, 0x1a, 0xce, 0x05, +0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0x42, 0x6e, 0xc6, 0x01, 0xa7, +0x70, 0x5a, 0x5b, 0xd4, 0x43, 0x4b, 0xa6, 0x74, +0x9f, 0x3b, 0x2e, 0x5f, 0xcb, 0x65, 0xb4, 0xa6, +0xa4, 0x58, 0x4c, 0xb4, 0x11, 0xad, 0x38, 0x62, +0x11, 0xaf, 0x91, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0xf4, 0xbc, 0x55, +0x7e, 0xe2, 0xa8, 0x32, 0xdd, 0x90, 0xec, 0xa9, +0xbd, 0xff, 0xdb, 0x1f, 0x11, 0x48, 0xc6, 0x40, +0x94, 0x4e, 0xba, 0x5d, 0xe0, 0xcc, 0xeb, 0xc4, +0x07, 0x8f, 0x6d, 0x36, 0x4d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0x70, +0x8e, 0x26, 0xb3, 0x0f, 0x77, 0x54, 0xfc, 0xe4, +0x2b, 0x4f, 0x0b, 0x3a, 0x79, 0xff, 0xcf, 0xaf, +0xa0, 0x18, 0x37, 0xfa, 0xa0, 0x15, 0x65, 0xcd, +0xf6, 0xdd, 0x16, 0x86, 0xa8, 0xbc, 0x28, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0xfa, 0xd3, 0x9c, 0x72, 0x9b, 0xb1, 0x45, +0xe8, 0x86, 0xdb, 0x0c, 0xc3, 0x19, 0x46, 0xdf, +0x13, 0xeb, 0xe1, 0x2e, 0xdb, 0x35, 0x5b, 0x59, +0x64, 0xa7, 0xcc, 0x88, 0x6d, 0x11, 0x49, 0x7e, +0xa5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0x76, 0x25, 0xff, 0x33, 0xc6, +0xdf, 0x1b, 0x03, 0xd6, 0x79, 0xaa, 0x79, 0xb8, +0x56, 0x8c, 0x7f, 0xc1, 0x8b, 0x51, 0x9c, 0xb3, +0x76, 0x9a, 0xbf, 0x45, 0xd4, 0x65, 0x02, 0xd9, +0x4f, 0x85, 0x98, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0xc2, 0x88, 0xa5, +0xdd, 0x65, 0x76, 0x04, 0xae, 0x7d, 0x78, 0x13, +0x5e, 0xf0, 0x2f, 0x3a, 0x5a, 0xd0, 0xc1, 0xe0, +0xce, 0xc5, 0x41, 0x41, 0xe1, 0x69, 0x03, 0x55, +0x38, 0xfc, 0x56, 0x11, 0xa9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0xcb, +0xa7, 0x4d, 0xc3, 0x2a, 0x25, 0x5b, 0xb0, 0x81, +0x7c, 0xbf, 0xda, 0xb9, 0xaa, 0x36, 0x2b, 0xed, +0x94, 0xc5, 0xdf, 0xe2, 0x99, 0x53, 0xc4, 0x17, +0x9d, 0x50, 0x03, 0xc6, 0x71, 0x8b, 0x9c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0xd5, 0x4e, 0x28, 0x81, 0x6f, 0x1c, 0x0b, +0xa7, 0xfd, 0x26, 0x9d, 0x86, 0xa7, 0xfd, 0x5e, +0xe5, 0x1e, 0xf5, 0x1d, 0x6c, 0xd1, 0x07, 0xd8, +0x69, 0x80, 0x52, 0x18, 0x5d, 0x5b, 0xe3, 0xef, +0x3b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0xb8, 0x77, 0x16, 0xe8, 0x88, +0xb6, 0x24, 0x33, 0x72, 0xb6, 0x59, 0xd4, 0x80, +0x50, 0x5d, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x17, 0x11, 0xb4, 0xf4, +0x3a, 0x24, 0x0e, 0x71, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0x2d, 0x01, 0x5f, +0x37, 0x6d, 0x19, 0x8c, 0x46, 0xca, 0xbe, 0xde, +0xc4, 0xa2, 0xbd, 0xc0, 0xb0, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x9d, +0xaf, 0xa1, 0xa5, 0xd2, 0xcc, 0x0d, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0x17, +0xc3, 0xf8, 0x5d, 0xcb, 0xde, 0x65, 0x6e, 0xfc, +0xbb, 0xa8, 0xab, 0x43, 0xf9, 0xee, 0x85, 0x1d, +0xa0, 0x08, 0xfa, 0xfa, 0x39, 0xad, 0xdd, 0xc6, +0x0b, 0x94, 0x02, 0xde, 0x7c, 0xf2, 0x92, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0x32, 0xf8, 0x14, 0xa8, 0x9e, 0xad, 0x16, +0xf3, 0xad, 0x3c, 0x09, 0x39, 0xf1, 0x15, 0x14, +0x59, 0xc7, 0x57, 0x05, 0x43, 0x6f, 0x20, 0x4c, +0xb2, 0xad, 0xbb, 0xe5, 0xab, 0x0b, 0x0f, 0xad, +0xda, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0x76, 0xfe, 0x3a, 0x6c, 0x25, +0x0b, 0x64, 0xf5, 0xe5, 0x1d, 0x14, 0xe8, 0x72, +0xbf, 0xfc, 0x1e, 0x65, 0x68, 0x0e, 0x55, 0x3b, +0x1b, 0x6a, 0x85, 0x94, 0xbf, 0xed, 0xae, 0x1c, +0x31, 0x8b, 0xd7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0xd7, 0xd9, 0x39, +0xa4, 0x04, 0xe3, 0x7e, 0x72, 0x79, 0x8f, 0x46, +0xfe, 0x99, 0xab, 0xe5, 0xad, 0xa0, 0x84, 0x7a, +0xcb, 0x2d, 0xe2, 0x36, 0x79, 0x97, 0x79, 0x0d, +0xdf, 0xff, 0x4e, 0x3a, 0x3b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0xe1, +0x06, 0x57, 0xbc, 0x8a, 0x69, 0xdf, 0x00, 0xa9, +0x3e, 0x6b, 0x99, 0x73, 0x91, 0x87, 0xfc, 0x91, +0x2e, 0x05, 0xee, 0x60, 0xaa, 0x66, 0x07, 0x28, +0xa3, 0xdb, 0x1c, 0x1a, 0x50, 0x7b, 0x04, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0x1c, 0xf2, 0xe1, 0x65, 0xef, 0xf4, 0x28, +0xab, 0x51, 0x0d, 0xd2, 0xb4, 0x02, 0xb3, 0x9f, +0x06, 0xa2, 0x1b, 0xf3, 0xcb, 0x13, 0xa6, 0x2e, +0x57, 0xd5, 0x75, 0xee, 0xb7, 0xfa, 0xd7, 0x66, +0x60, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0x9e, 0xbf, 0x25, 0xe2, 0xfc, +0x71, 0x56, 0x4e, 0x95, 0x72, 0x96, 0x90, 0xf3, +0x7c, 0xcf, 0x88, 0x56, 0x35, 0x09, 0xc8, 0xef, +0xe8, 0xc9, 0x69, 0x72, 0x5e, 0xb8, 0xb6, 0x88, +0x07, 0x3c, 0x60, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0xbd, 0xde, 0xa2, +0x1a, 0xd7, 0x5a, 0x8b, 0xef, 0xe9, 0x6d, 0x15, +0x2d, 0x25, 0xcc, 0x5e, 0xb6, 0x97, 0x9e, 0xa9, +0xa6, 0x93, 0xd5, 0xda, 0xfd, 0x2a, 0x08, 0x96, +0x8d, 0x31, 0x7c, 0xab, 0x60, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0x9b, +0x35, 0xfb, 0xaa, 0xa0, 0x51, 0x67, 0x54, 0xc7, +0xfd, 0xd0, 0xd1, 0x51, 0x8d, 0x54, 0x01, 0x19, +0xac, 0x6f, 0xfd, 0xb5, 0xd7, 0x6a, 0xb2, 0xef, +0xfa, 0xd6, 0x92, 0xab, 0xc0, 0x42, 0x3d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0x4d, 0xb5, 0x07, 0xa1, 0xf5, 0x7f, 0xb4, +0x21, 0x59, 0x02, 0xa0, 0xf1, 0x8c, 0x3e, 0x70, +0xe3, 0xc4, 0xc4, 0x60, 0xba, 0x01, 0xc3, 0x6f, +0x69, 0xa0, 0xcc, 0x96, 0x09, 0x68, 0x52, 0x25, +0x83, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0xb7, 0x94, 0x39, 0x40, 0x86, +0xcc, 0xff, 0xa0, 0xcc, 0xe3, 0x06, 0xad, 0x57, +0x8d, 0x7a, 0x5c, 0xc4, 0x86, 0xaf, 0xf9, 0xa8, +0xd3, 0x29, 0x7e, 0x27, 0xdb, 0x1d, 0x6a, 0x42, +0x7a, 0xfd, 0xc3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0xcf, 0x46, 0x15, +0x1e, 0xa6, 0xf8, 0x4c, 0x01, 0xad, 0x1b, 0xba, +0xbd, 0x74, 0xbb, 0x2b, 0x86, 0x5a, 0x3a, 0x81, +0xd4, 0x90, 0x32, 0x9d, 0x1f, 0x70, 0x17, 0x6c, +0x76, 0x16, 0x24, 0x87, 0x0b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0xaa, +0xb7, 0xc7, 0x7c, 0x1b, 0x92, 0xf4, 0x3e, 0x7f, +0x9f, 0xc2, 0xbe, 0x16, 0x6a, 0xd4, 0x54, 0x38, +0x7d, 0xef, 0x8f, 0x4f, 0x9b, 0x5b, 0xe7, 0x92, +0x2c, 0xc7, 0x51, 0x60, 0x7e, 0x17, 0x92, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0x64, 0x1c, 0x1d, 0x2d, 0x81, 0x52, 0x0e, +0x3c, 0xb3, 0xdb, 0x62, 0x90, 0xfa, 0xc2, 0x10, +0x01, 0x22, 0xf4, 0xab, 0x9c, 0x1a, 0x39, 0xeb, +0x70, 0xdf, 0x63, 0x16, 0x71, 0x2b, 0xf0, 0xa6, +0x7c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0x1d, 0x38, 0xaa, 0x70, 0xe5, +0x67, 0x8a, 0xbf, 0x52, 0x8f, 0x53, 0xa9, 0x6b, +0xaf, 0xd4, 0xc3, 0xf2, 0xba, 0xb8, 0x25, 0xa1, +0xd0, 0x7e, 0xd9, 0x69, 0x42, 0x80, 0x21, 0x85, +0xb8, 0xc9, 0x06, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0x60, 0xaf, 0x1d, +0x54, 0x3b, 0x43, 0x77, 0x65, 0x64, 0xbb, 0x09, +0xef, 0x18, 0x95, 0xd3, 0x8e, 0x99, 0xea, 0xea, +0x14, 0xa6, 0x4b, 0xb8, 0x6d, 0x54, 0xb3, 0x40, +0x24, 0xd2, 0x25, 0x76, 0x5f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0xb2, +0x5d, 0xd8, 0x47, 0x5a, 0x2d, 0x70, 0xf3, 0x6e, +0xf5, 0xac, 0x6c, 0xfe, 0x79, 0x2d, 0xd8, 0x30, +0xc4, 0xbe, 0x7f, 0xbe, 0x93, 0x61, 0xa0, 0xbb, +0xf5, 0xb2, 0xaa, 0x28, 0xd7, 0x02, 0x68, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0x7d, 0xeb, 0xfd, 0x02, 0x97, 0xd3, 0xe3, +0x06, 0x3d, 0xc2, 0xfe, 0xea, 0x7b, 0x92, 0xc8, +0xf3, 0x95, 0x6b, 0x2f, 0x27, 0xb4, 0xca, 0xd8, +0xdb, 0x2e, 0x9a, 0x60, 0x04, 0x35, 0x04, 0xd1, +0xa1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0x33, 0x7c, 0xec, 0x16, 0x60, +0x49, 0x06, 0xca, 0x9c, 0x41, 0x2e, 0xf7, 0xcc, +0xf2, 0xd2, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x3b, 0x3e, 0x3f, 0x0e, +0x41, 0x5b, 0xc7, 0x75, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0xa1, 0x82, 0x3d, +0x6c, 0x40, 0xa7, 0x83, 0x58, 0x5c, 0xb7, 0xfb, +0x70, 0x72, 0xad, 0x13, 0x2a, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd4, 0x39, +0xfb, 0x51, 0x2d, 0xce, 0x48, 0x01, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0xae, +0xcc, 0x8d, 0x3e, 0x2e, 0xe6, 0x01, 0x6d, 0xad, +0x52, 0x73, 0xd4, 0x15, 0x7d, 0x77, 0x4c, 0xd0, +0x58, 0x9e, 0xa4, 0x92, 0x83, 0xd2, 0x1b, 0xec, +0x5b, 0x9c, 0x51, 0x43, 0x10, 0x8b, 0xc6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0x70, 0x65, 0x88, 0x8c, 0xf6, 0x7b, 0xa6, +0x4f, 0x33, 0xff, 0xc9, 0x75, 0x86, 0xb4, 0x60, +0xd3, 0x85, 0x17, 0xc6, 0xd6, 0x6e, 0xc3, 0x1c, +0x36, 0x84, 0xfb, 0xa2, 0xfd, 0x04, 0x72, 0x54, +0xc7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0x73, 0x6a, 0x34, 0x8b, 0x8d, +0x64, 0x5f, 0x2b, 0xea, 0x21, 0x1e, 0xab, 0xca, +0xfc, 0x28, 0x18, 0xd8, 0x23, 0xcf, 0x4c, 0x64, +0x97, 0x1a, 0x4a, 0xdb, 0xa5, 0xdb, 0xcc, 0x90, +0xe6, 0xf1, 0x4a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x0a, 0x01, 0xd4, +0x55, 0x83, 0xc5, 0xa5, 0x19, 0x55, 0xfa, 0x91, +0xeb, 0x2c, 0x02, 0x43, 0x34, 0xd6, 0x41, 0x60, +0x11, 0xbb, 0x36, 0xdb, 0x5f, 0x98, 0x31, 0x94, +0x7f, 0x6b, 0x19, 0xc1, 0xef, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0x58, +0x57, 0x85, 0x7e, 0xb5, 0x97, 0xa2, 0x02, 0xb9, +0x67, 0xfd, 0xce, 0xbc, 0x01, 0x92, 0xb2, 0xe1, +0xa9, 0x85, 0xc9, 0x53, 0x8b, 0x54, 0x84, 0xf3, +0xef, 0x79, 0xf3, 0x8a, 0x01, 0x9a, 0xef, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0xea, 0xe6, 0x58, 0xc0, 0x14, 0xb5, 0xc2, +0xde, 0x0d, 0x7a, 0x61, 0xc0, 0x4a, 0x7e, 0xbe, +0x0c, 0x6b, 0xe9, 0xe4, 0x4a, 0x14, 0x6a, 0xa2, +0xf3, 0xc4, 0x5d, 0x53, 0x76, 0x8f, 0x4c, 0xaa, +0x67, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0x4e, 0xc3, 0x41, 0x85, 0x6e, +0x81, 0x7d, 0x28, 0xfd, 0x23, 0x52, 0x37, 0x80, +0x82, 0xd4, 0x25, 0x6d, 0xc8, 0x36, 0xd3, 0xf1, +0x3c, 0x2d, 0x47, 0x41, 0x21, 0xc8, 0xa7, 0xf7, +0xfa, 0xf7, 0x50, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0x2c, 0x04, 0xb4, +0xed, 0x33, 0xaf, 0x5f, 0x26, 0xc4, 0xb7, 0x17, +0x2c, 0xf3, 0x67, 0x4b, 0xb1, 0x0f, 0x85, 0xcf, +0xb0, 0x6d, 0xca, 0xca, 0x9a, 0x71, 0x5d, 0xd3, +0xca, 0x3e, 0x36, 0xfc, 0x17, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0xeb, +0xde, 0x7f, 0x62, 0x48, 0x63, 0x58, 0xee, 0xe7, +0x2c, 0xbe, 0xcd, 0x8f, 0x63, 0xf2, 0x01, 0xe7, +0x3b, 0x8f, 0x46, 0x04, 0xc2, 0xdd, 0x66, 0x0b, +0xb3, 0x23, 0x07, 0x6c, 0x68, 0xf5, 0x50, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0xcf, 0x25, 0xe3, 0xd4, 0xcc, 0x55, 0x65, +0x64, 0x11, 0x5b, 0xab, 0x57, 0x76, 0xdb, 0xd1, +0x76, 0x3b, 0x89, 0xe7, 0xbf, 0x2c, 0xd3, 0x04, +0xfc, 0x50, 0xa0, 0xb9, 0x77, 0xc0, 0x0e, 0x3e, +0x22, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0x42, 0xc1, 0x7e, 0x9c, 0x12, +0x22, 0x3c, 0x7d, 0x60, 0xf9, 0xf2, 0xb9, 0xca, +0xce, 0x92, 0xb3, 0x40, 0x52, 0x6a, 0x09, 0x49, +0x21, 0xb8, 0xc7, 0x03, 0x12, 0xf3, 0xb8, 0xba, +0x06, 0x22, 0x6c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0x1e, 0xe6, 0x19, +0x04, 0x0a, 0xde, 0xef, 0xe8, 0xde, 0x9c, 0xf1, +0xd6, 0x18, 0x2c, 0x41, 0xc0, 0xd0, 0x18, 0xc8, +0x28, 0xb8, 0x9e, 0x59, 0x01, 0xc2, 0x5d, 0x4c, +0xa4, 0x14, 0x84, 0xa2, 0x7a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0x2a, +0xc5, 0xa9, 0x26, 0x2d, 0xb9, 0xb9, 0x70, 0x32, +0xf3, 0x3b, 0x20, 0x51, 0x17, 0x9d, 0xeb, 0x4f, +0x74, 0x6b, 0x47, 0x7d, 0xa8, 0xdb, 0x13, 0x3b, +0x83, 0x92, 0xdf, 0x0a, 0x63, 0x9c, 0x45, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0x05, 0xcc, 0x7e, 0x2e, 0xcf, 0x5e, 0x12, +0x83, 0x09, 0xfa, 0x75, 0xf5, 0xa8, 0x98, 0x6f, +0x57, 0x65, 0xe9, 0x3e, 0xd2, 0x0e, 0xce, 0x9d, +0x0a, 0xd8, 0x4e, 0x83, 0xd2, 0xe3, 0xca, 0xf8, +0xa7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0x2c, 0xa3, 0x64, 0x3f, 0xef, +0xe2, 0xa2, 0x52, 0xa1, 0x23, 0xab, 0x6a, 0x85, +0x41, 0xff, 0xee, 0x4e, 0x93, 0xc5, 0x80, 0x2a, +0x24, 0x66, 0xac, 0xcf, 0xef, 0xfd, 0x19, 0xfa, +0x00, 0x9e, 0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0xb2, 0xc6, 0x00, +0xa6, 0xe8, 0x96, 0x21, 0x26, 0x2f, 0xdb, 0xd1, +0xc7, 0x77, 0x36, 0xcc, 0x0e, 0xce, 0xed, 0x5b, +0xbc, 0x48, 0xf6, 0x8e, 0x69, 0x11, 0xa5, 0x8f, +0x78, 0x39, 0xac, 0x31, 0x0d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0xd5, +0x4f, 0x56, 0x27, 0x76, 0x4f, 0x08, 0xa9, 0x6b, +0xe8, 0xca, 0x8e, 0x19, 0x5c, 0x96, 0xcb, 0x84, +0xd9, 0x10, 0xc1, 0xb2, 0x85, 0x93, 0x6a, 0x4d, +0x25, 0xf4, 0xb3, 0x58, 0x78, 0x37, 0x00, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0x54, 0x18, 0x45, 0x63, 0xac, 0x48, 0x77, +0xc6, 0xb4, 0x2e, 0xc6, 0xaf, 0x50, 0xd1, 0x85, +0xd3, 0x55, 0x82, 0x5a, 0xab, 0x45, 0x91, 0xbc, +0x55, 0x64, 0x69, 0x2e, 0x3c, 0xb8, 0x89, 0x0e, +0x07, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0xd4, 0x3d, 0x11, 0x69, 0x0e, +0x44, 0x9e, 0x0e, 0x1a, 0x17, 0x7c, 0x27, 0x60, +0x22, 0xd9, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x89, 0xe7, 0x4b, 0x2f, +0x28, 0x91, 0x5e, 0xd7, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0x31, 0x7a, 0x4e, +0xbc, 0xb1, 0x35, 0x5f, 0x08, 0x9b, 0x7d, 0x8f, +0x33, 0x38, 0x74, 0x21, 0xc1, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x2e, +0xa4, 0x39, 0x8a, 0x78, 0x78, 0x60, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0x59, +0x6a, 0x0c, 0xfb, 0x46, 0x28, 0x1e, 0xa8, 0xc2, +0x68, 0x6b, 0xed, 0x12, 0xa5, 0x8e, 0x7b, 0x5f, +0xa4, 0x51, 0xdf, 0x0e, 0xa9, 0x0a, 0x2c, 0xb9, +0x95, 0xa0, 0x89, 0xee, 0x6c, 0x35, 0x2c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0x4c, 0xbb, 0xa6, 0x91, 0x9f, 0x8a, 0x3d, +0xa2, 0x89, 0x10, 0x4c, 0xd0, 0xcf, 0x90, 0xd5, +0x23, 0xad, 0x98, 0x66, 0x47, 0x85, 0x72, 0x6f, +0x6f, 0x02, 0x10, 0x7a, 0xb1, 0x52, 0x88, 0x14, +0x3a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0x41, 0x69, 0xe2, 0x24, 0x35, +0xca, 0x99, 0x27, 0x67, 0x90, 0x68, 0xd9, 0x3f, +0x96, 0x94, 0x9e, 0x43, 0xc4, 0xa2, 0x5b, 0x58, +0x45, 0x0d, 0x98, 0xb9, 0x2d, 0x05, 0xc7, 0x59, +0xa9, 0x30, 0xc5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x38, 0x57, 0xe9, +0x52, 0x36, 0xdb, 0x2c, 0x1c, 0xca, 0x0a, 0x0b, +0xb2, 0xf7, 0x17, 0x84, 0x32, 0x1b, 0x57, 0x5b, +0xbd, 0x50, 0xb3, 0x42, 0xdf, 0x5b, 0xb9, 0x0a, +0x5d, 0x76, 0xe5, 0x3d, 0x22, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x6a, +0x80, 0xcc, 0xa9, 0x8c, 0x0f, 0x5e, 0xe1, 0x3c, +0x47, 0xf9, 0xb5, 0x70, 0x10, 0x28, 0x28, 0x2c, +0xd0, 0xe1, 0xa3, 0x9e, 0x7f, 0x02, 0x12, 0x8c, +0x4a, 0x76, 0x55, 0x29, 0xa5, 0xda, 0x79, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x98, 0x33, 0x9a, 0x64, 0x11, 0x20, 0x6b, +0xb5, 0xb1, 0x43, 0xb8, 0xfe, 0x23, 0x9d, 0x08, +0x4c, 0xd8, 0x87, 0x4e, 0x51, 0x58, 0xd3, 0x12, +0x56, 0x0f, 0x63, 0x2f, 0x93, 0xfb, 0x23, 0x77, +0x8f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0x62, 0x61, 0x27, 0x7b, 0xcb, +0x7b, 0xfc, 0xc5, 0x9e, 0x3d, 0x2e, 0xae, 0x75, +0xad, 0xb8, 0x8f, 0x64, 0x6e, 0x35, 0x02, 0x91, +0x4c, 0x4b, 0x88, 0x3f, 0x94, 0x9f, 0x81, 0x23, +0x9d, 0x38, 0xa2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0xc6, 0xb2, 0x1e, +0xe2, 0x4e, 0x30, 0x95, 0xb7, 0x56, 0x6f, 0x64, +0xed, 0xe3, 0xd4, 0x6b, 0x9f, 0x1a, 0xe5, 0xf2, +0x9b, 0x62, 0x75, 0x2a, 0x28, 0x85, 0xb1, 0x4e, +0xc8, 0x41, 0x5e, 0xa2, 0x40, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0xab, +0x51, 0x41, 0x58, 0x67, 0x61, 0xf2, 0x56, 0xc5, +0x51, 0xba, 0x79, 0x6b, 0x1a, 0xcc, 0x20, 0x63, +0x1f, 0x6d, 0x87, 0xb0, 0x8c, 0xb4, 0xed, 0xd1, +0xaf, 0x66, 0x4b, 0x7a, 0x20, 0x0e, 0x56, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0x53, 0x80, 0x80, 0x81, 0xe3, 0xd0, 0x7d, +0xe5, 0xf0, 0xaa, 0x7d, 0x48, 0x9a, 0x87, 0x66, +0x3a, 0xb0, 0xe0, 0x32, 0x28, 0x2e, 0x1b, 0x2b, +0x4d, 0x03, 0x71, 0xf5, 0x0a, 0x07, 0xa8, 0xb8, +0x21, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0x69, 0xc6, 0xa0, 0xb7, 0x1b, +0x30, 0x43, 0xca, 0xa0, 0x31, 0x9f, 0xb0, 0xb1, +0xf8, 0x44, 0x35, 0x2b, 0xc1, 0xb5, 0xab, 0x61, +0x47, 0xaf, 0xb8, 0xf8, 0xbc, 0xa3, 0x7b, 0xb8, +0x8f, 0x51, 0x08, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0x4e, 0x2a, 0x5b, +0x0b, 0x5b, 0xa5, 0x35, 0x6f, 0x24, 0xa1, 0x80, +0x88, 0x5f, 0x9a, 0x10, 0xb4, 0x54, 0x37, 0x3f, +0xd0, 0x74, 0x0f, 0xbb, 0xec, 0x10, 0x27, 0xe9, +0x66, 0x78, 0xa3, 0x34, 0x66, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0x90, +0xda, 0xea, 0x46, 0xa9, 0xec, 0xc4, 0x21, 0x31, +0x04, 0xc9, 0x0a, 0xa6, 0x76, 0x28, 0xd0, 0x78, +0x8d, 0x98, 0xe0, 0xa4, 0xd4, 0xd0, 0x48, 0x27, +0x2f, 0x8c, 0x44, 0x20, 0xdd, 0x21, 0x57, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0x96, 0x1c, 0x1b, 0x68, 0x11, 0x4f, 0x1e, +0x69, 0x98, 0xa3, 0x6c, 0x42, 0x58, 0xdc, 0xa8, +0x8c, 0xef, 0x26, 0xc3, 0x1b, 0x2f, 0xd5, 0x7d, +0x23, 0xaa, 0xb6, 0x07, 0x4e, 0x87, 0x94, 0x59, +0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0x9c, 0xc8, 0xdf, 0xd1, 0x64, +0xed, 0x85, 0x77, 0x53, 0xaa, 0x7b, 0xf0, 0x46, +0x1d, 0x03, 0x1d, 0xa5, 0x15, 0x25, 0xc5, 0x17, +0xc5, 0xe6, 0x6c, 0x30, 0x8e, 0xb2, 0x52, 0x83, +0xb5, 0xd0, 0x0b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0x49, 0x90, 0x0e, +0xf9, 0x7d, 0xce, 0x23, 0x10, 0xa9, 0x31, 0x33, +0x8d, 0x35, 0xa3, 0x75, 0x52, 0xfd, 0xb2, 0x56, +0x0c, 0x2c, 0x98, 0x25, 0xcb, 0x56, 0xac, 0x78, +0xfe, 0x35, 0x94, 0x32, 0x54, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0x4a, +0x2a, 0x53, 0x5f, 0xff, 0x7a, 0x05, 0xc9, 0xe6, +0x23, 0x24, 0x27, 0x17, 0x92, 0x7e, 0x7f, 0x12, +0x88, 0xe3, 0xe8, 0x8c, 0xb3, 0xd2, 0x9a, 0x38, +0x5f, 0x86, 0x2b, 0xa2, 0xb4, 0x2b, 0x71, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0x73, 0xc3, 0xa8, 0x8d, 0xe5, 0xa2, 0x59, +0x81, 0xa5, 0x5c, 0x1b, 0xbe, 0x22, 0x51, 0xcf, +0xc7, 0x3d, 0x42, 0x7a, 0xe9, 0x5e, 0x41, 0x41, +0xff, 0x79, 0xa1, 0xe0, 0xd6, 0x68, 0x9f, 0x7c, +0x3d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0x4e, 0x27, 0x0c, 0xdd, 0xe6, +0x15, 0xa2, 0x56, 0x78, 0x0f, 0xd9, 0xf1, 0x5a, +0x5f, 0xce, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xe9, 0x54, 0xb8, 0x71, +0xe6, 0x9d, 0xab, 0x53, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0x1b, 0x4c, 0x9f, +0x0b, 0x5b, 0x8f, 0xa6, 0x74, 0x59, 0xc4, 0x2f, +0xfd, 0xab, 0xa1, 0x31, 0x10, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0xb1, +0xf1, 0xb1, 0x41, 0xc3, 0xfe, 0x48, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0x03, +0x07, 0xe8, 0xf4, 0x35, 0xd8, 0x2c, 0x02, 0xd9, +0xc6, 0x09, 0xfb, 0x0c, 0xf2, 0xb1, 0xbc, 0xd8, +0xdf, 0x67, 0x3a, 0xad, 0x82, 0x4f, 0xfe, 0x36, +0x89, 0xb1, 0x74, 0x8a, 0x49, 0x75, 0x0f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0x4c, 0xce, 0x05, 0xbf, 0xbe, 0x1b, 0x21, +0xfc, 0x6b, 0x41, 0x09, 0x21, 0xb7, 0x34, 0xf8, +0x05, 0x57, 0x8b, 0x6e, 0x01, 0xef, 0xef, 0x18, +0x2d, 0xe6, 0x48, 0x36, 0xff, 0xe4, 0xd6, 0xd4, +0xea, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0xa4, 0x11, 0xf8, 0xb1, 0x93, +0xd2, 0x44, 0x60, 0xd3, 0x00, 0x76, 0xcb, 0xdf, +0x5c, 0xd8, 0x76, 0xb6, 0x71, 0x16, 0x9a, 0x85, +0xf2, 0xc6, 0x8f, 0x39, 0xc5, 0xae, 0x08, 0xdd, +0xed, 0x1f, 0x94, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0x9d, 0x71, 0x56, +0x52, 0xa6, 0x6b, 0x9e, 0x87, 0xb6, 0xf1, 0x85, +0x71, 0xfc, 0xea, 0xa0, 0x1b, 0xb5, 0xc3, 0xde, +0xa6, 0x85, 0xd6, 0x6d, 0x09, 0xa6, 0xf8, 0x72, +0x54, 0x1b, 0x8c, 0x1f, 0xad, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0x5d, +0x33, 0xe3, 0xb1, 0xfe, 0x23, 0x9f, 0xc4, 0x48, +0x3d, 0xcc, 0x90, 0x52, 0x26, 0x70, 0x93, 0x5d, +0xee, 0x59, 0x9b, 0x3f, 0x68, 0x4f, 0xbf, 0xc8, +0xc8, 0x51, 0x91, 0xfb, 0x74, 0x92, 0x24, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0x07, 0x50, 0x51, 0x20, 0x48, 0x14, 0xc2, +0x87, 0x83, 0x3f, 0x49, 0xf6, 0x4a, 0xee, 0xcb, +0x9f, 0xba, 0xc4, 0x84, 0xca, 0x0b, 0x9a, 0x5a, +0x28, 0x91, 0x97, 0x87, 0xdc, 0xca, 0x66, 0xe4, +0x8d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0xf1, 0x91, 0xbb, 0x77, 0x35, +0xbc, 0xef, 0xe0, 0xa3, 0x26, 0x42, 0xfb, 0x6e, +0xb0, 0xa1, 0xb8, 0x13, 0xdf, 0x97, 0x11, 0xcd, +0xa3, 0xc0, 0x38, 0x28, 0xb7, 0x5b, 0x44, 0x6e, +0x67, 0x22, 0xda, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0x61, 0x9c, 0xbd, +0xba, 0x25, 0x8c, 0x5c, 0x2b, 0xc3, 0x3e, 0x66, +0xc3, 0x85, 0xc3, 0x50, 0x68, 0x0b, 0x6d, 0x56, +0x20, 0xdd, 0xcd, 0x21, 0x44, 0xad, 0x2e, 0x3b, +0x8b, 0xbb, 0xd0, 0x73, 0x9e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0x05, +0x2a, 0x2d, 0xf9, 0x0a, 0xc3, 0xd5, 0x4b, 0x85, +0xf6, 0x44, 0xff, 0x54, 0x23, 0x4e, 0xfd, 0x0a, +0xf3, 0x13, 0xa0, 0xea, 0xf8, 0x54, 0x46, 0xfd, +0x99, 0x46, 0x24, 0xac, 0xf9, 0x02, 0x4b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0xc8, 0x37, 0xea, 0x7f, 0x2b, 0x03, 0x1f, +0x30, 0x0f, 0x22, 0x3e, 0x55, 0x94, 0x21, 0x6b, +0xf6, 0xb2, 0x59, 0x59, 0x71, 0x3b, 0x21, 0x63, +0x58, 0xd7, 0x3f, 0x91, 0xe2, 0x13, 0xf8, 0x4d, +0xa2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0x86, 0xaf, 0x86, 0x01, 0x89, +0x3b, 0x95, 0x60, 0x7e, 0xc3, 0x85, 0x9f, 0x55, +0x05, 0x8f, 0x36, 0xbe, 0xec, 0xd9, 0x49, 0x47, +0x2e, 0x43, 0xab, 0xe3, 0x5b, 0x05, 0x90, 0x89, +0x50, 0x7a, 0xc8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0x9f, 0x80, 0x26, +0x6c, 0x43, 0xeb, 0xc9, 0x05, 0x5a, 0x59, 0xf7, +0xa2, 0x3c, 0x95, 0xb6, 0x45, 0xe9, 0x8f, 0x10, +0x91, 0x43, 0x18, 0xd7, 0x74, 0x01, 0xd0, 0xd5, +0xf4, 0xbf, 0xac, 0x6e, 0xb7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0x95, +0x3b, 0x71, 0x92, 0x93, 0xb6, 0x4b, 0x40, 0xd9, +0xa1, 0x80, 0xb9, 0x9d, 0x47, 0x04, 0x73, 0x09, +0xde, 0x79, 0x3b, 0xe2, 0xaf, 0xe8, 0x9e, 0x3f, +0x48, 0x08, 0x2e, 0xc0, 0x21, 0x34, 0x3b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0x13, 0x3b, 0xed, 0x09, 0xd7, 0x12, 0xd0, +0x1d, 0x9b, 0xee, 0x5d, 0x42, 0x3a, 0xb8, 0xaa, +0xe0, 0xc7, 0x38, 0x56, 0x23, 0xff, 0x8b, 0x1c, +0x8b, 0x04, 0x67, 0xb5, 0xa5, 0x69, 0xef, 0xcb, +0x74, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0xc4, 0xfe, 0x30, 0xee, 0x59, +0x6e, 0x4e, 0x2b, 0xa2, 0x0d, 0x03, 0xf4, 0xe1, +0xa5, 0x22, 0xfe, 0x6a, 0x03, 0x87, 0x9f, 0x65, +0xeb, 0x48, 0xa1, 0xb9, 0xea, 0xa4, 0x35, 0xa4, +0x23, 0xee, 0x0f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0x25, 0xa5, 0x2e, +0xc0, 0x26, 0x87, 0xd3, 0x05, 0xc3, 0x3f, 0xc8, +0xe6, 0xb4, 0x2a, 0x20, 0x53, 0xe4, 0xb8, 0x3f, +0x6c, 0xa0, 0xbd, 0x22, 0x84, 0x44, 0xbd, 0xac, +0x5c, 0x4f, 0x20, 0xad, 0xec, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0xee, +0xcc, 0x8f, 0x73, 0x62, 0xdd, 0x63, 0xa9, 0x7f, +0x6a, 0xab, 0x5d, 0xc3, 0xa9, 0x53, 0x0d, 0x7c, +0x89, 0xa9, 0x75, 0x78, 0xa5, 0xb5, 0xd7, 0xc9, +0xe6, 0x84, 0x36, 0x2f, 0xfb, 0xf8, 0xf7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0x13, 0xdd, 0x82, 0xff, 0xfa, 0x94, 0x8e, +0x67, 0xbb, 0x80, 0x45, 0xcd, 0x8b, 0x55, 0x67, +0x9a, 0x53, 0xfb, 0x42, 0x0e, 0x12, 0x68, 0xd7, +0x55, 0x41, 0x31, 0x25, 0x39, 0xda, 0x84, 0x60, +0x40, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0xa4, 0x59, 0x76, 0x58, 0xa2, +0x67, 0xa5, 0x81, 0x7e, 0x7d, 0x45, 0x65, 0xcc, +0xd8, 0x04, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x85, 0xf9, 0x15, 0x48, +0xb6, 0x03, 0xcc, 0xba, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x4b, 0x84, 0xcd, +0x3a, 0xaf, 0x44, 0xdb, 0x63, 0xdb, 0xa2, 0x04, +0xfe, 0x51, 0x3c, 0x7d, 0xed, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, 0x91, +0xcd, 0x81, 0x33, 0xd1, 0x24, 0xaa, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0xea, +0x26, 0xf3, 0x19, 0x34, 0x78, 0x53, 0xab, 0xe3, +0x74, 0xfc, 0xbe, 0x22, 0x1d, 0x86, 0x15, 0xaf, +0xd9, 0xea, 0x3c, 0x30, 0xb0, 0x71, 0xa7, 0x6f, +0x19, 0xf8, 0x0c, 0xc5, 0xf6, 0xce, 0x5f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0xf1, 0x2c, 0x3a, 0x16, 0x8f, 0x15, 0x46, +0xba, 0x68, 0x65, 0xc1, 0xbf, 0xdd, 0x12, 0x8f, +0xf3, 0x62, 0x07, 0xd8, 0xfe, 0xe8, 0xf2, 0x87, +0xd3, 0x4a, 0x17, 0x43, 0x14, 0x77, 0xfd, 0x0e, +0x3d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0x18, 0xa8, 0x3a, 0xf9, 0x7b, +0x4d, 0x08, 0xca, 0x0a, 0xbb, 0x42, 0x0d, 0x90, +0xab, 0xbc, 0xe4, 0x28, 0x5f, 0xe0, 0x9c, 0xa6, +0x7d, 0x2e, 0x88, 0xb6, 0xee, 0xab, 0x65, 0x93, +0x12, 0x70, 0x54, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0x61, 0xc1, 0x13, +0x1c, 0x62, 0xab, 0x5b, 0xd1, 0xf4, 0x10, 0x9a, +0x04, 0x46, 0xed, 0x5e, 0xd2, 0x05, 0x87, 0x13, +0x53, 0x44, 0xda, 0x29, 0xc7, 0xe6, 0xef, 0x53, +0x78, 0xab, 0xaa, 0x6c, 0x91, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x70, +0x70, 0xa0, 0x27, 0xb8, 0x7f, 0x25, 0xab, 0x55, +0xf0, 0x0f, 0xc5, 0x69, 0x37, 0xd6, 0x46, 0xca, +0x47, 0x26, 0x89, 0xca, 0xa2, 0x5a, 0x8c, 0x8f, +0x9e, 0x9e, 0x65, 0x3a, 0x8b, 0x47, 0xf1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0xb7, 0xe7, 0x77, 0x5f, 0xea, 0x99, 0xdd, +0x6c, 0x53, 0x59, 0xe4, 0xa6, 0x17, 0x56, 0xca, +0x65, 0x06, 0x9d, 0x29, 0x72, 0xe3, 0x4b, 0x19, +0x37, 0x10, 0xb3, 0x10, 0x97, 0x35, 0x20, 0x49, +0x6e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0x65, 0x06, 0x7a, 0x65, 0x25, +0xd1, 0xd6, 0xed, 0xf4, 0x75, 0x9f, 0x8d, 0x07, +0x17, 0xad, 0x52, 0x8f, 0xb1, 0xd5, 0x2a, 0xcc, +0x01, 0x6c, 0xf5, 0xd1, 0xb4, 0x37, 0x8a, 0x81, +0xed, 0x03, 0xa6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0xcf, 0x21, 0x4e, +0xdc, 0x83, 0xe0, 0x56, 0xaa, 0x67, 0x04, 0x9e, +0x4d, 0x38, 0x2b, 0xfa, 0xec, 0x06, 0x8d, 0x5a, +0xe5, 0x8b, 0xa0, 0xd2, 0xf8, 0xf1, 0x34, 0x0f, +0xe1, 0xa2, 0x7a, 0xaa, 0x3c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x5e, +0xf4, 0x1f, 0x1b, 0x69, 0x93, 0x39, 0x04, 0x40, +0x07, 0x00, 0x69, 0x68, 0x57, 0xf7, 0x4c, 0x4f, +0xaa, 0x58, 0x38, 0x49, 0xbe, 0xb9, 0x70, 0x52, +0x0a, 0xa0, 0x85, 0x53, 0x74, 0x5d, 0xab, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0xa6, 0x19, 0x40, 0xd2, 0xf1, 0x62, 0xf3, +0x58, 0x1c, 0x74, 0x5e, 0xb6, 0xce, 0x7a, 0x99, +0xdd, 0x78, 0x52, 0x42, 0x2e, 0xf9, 0x49, 0x36, +0x90, 0xfa, 0xa0, 0x5f, 0x10, 0xab, 0x93, 0x36, +0x4a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0x5b, 0xc6, 0x01, 0x93, 0xb1, +0x90, 0x2a, 0x4c, 0x45, 0x2b, 0x4a, 0xee, 0x8f, +0xde, 0x1a, 0x5c, 0xd3, 0x9a, 0xeb, 0x5a, 0x88, +0x5d, 0x41, 0x65, 0xd4, 0x7f, 0x6a, 0xca, 0xe7, +0x29, 0x32, 0x5a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0x78, 0xf9, 0xa0, +0x70, 0x26, 0x9f, 0x1f, 0xd7, 0xe3, 0xc5, 0x7b, +0xae, 0x25, 0x77, 0xa7, 0xa6, 0x0e, 0xbd, 0x7b, +0x3a, 0x83, 0x3c, 0x24, 0x75, 0xe2, 0x6b, 0xfe, +0x11, 0x3b, 0x46, 0xa7, 0xc0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x42, +0xaf, 0x43, 0xb1, 0x1e, 0x85, 0x26, 0xb7, 0x19, +0xa4, 0x0e, 0x02, 0x4f, 0xe9, 0x09, 0xd9, 0xda, +0x95, 0xc6, 0x9e, 0x5f, 0x77, 0xc8, 0xca, 0x70, +0xd0, 0xf8, 0x3a, 0x51, 0xe3, 0x2d, 0x56, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0xa6, 0x67, 0x84, 0xd2, 0x19, 0xca, 0x65, +0x88, 0xfd, 0xf7, 0xe6, 0xb0, 0x0d, 0xbc, 0xa8, +0xcb, 0x23, 0x8f, 0xea, 0x4b, 0x6f, 0x96, 0x35, +0xa3, 0xa2, 0x10, 0x63, 0x36, 0xad, 0x3d, 0x58, +0x62, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0xeb, 0x73, 0x69, 0x45, 0xec, +0xa2, 0x99, 0x2c, 0xb2, 0x75, 0xe1, 0x65, 0x87, +0xf5, 0xdc, 0xdf, 0x55, 0x8e, 0xc1, 0xd4, 0x2d, +0xb9, 0x93, 0xaf, 0xae, 0x12, 0x73, 0x7f, 0xf0, +0xe5, 0x9d, 0xcf, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x3e, 0xf4, 0x36, +0x2a, 0x74, 0xae, 0x57, 0x07, 0xe3, 0x58, 0x48, +0x7c, 0x30, 0xee, 0x77, 0x99, 0x50, 0xad, 0x33, +0xa3, 0xd2, 0xd1, 0xbe, 0x4d, 0x07, 0xac, 0x28, +0xd7, 0xc5, 0xa6, 0x61, 0x8c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0x98, +0x34, 0xe8, 0xb4, 0xa8, 0x5d, 0x78, 0xc2, 0x9b, +0xbf, 0x67, 0x7c, 0xc0, 0x1c, 0x5d, 0x2b, 0x70, +0x35, 0xf3, 0xcb, 0x87, 0x93, 0x98, 0xda, 0x1a, +0x6b, 0xa8, 0xec, 0x24, 0x26, 0x10, 0x1b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0x15, 0xc4, 0x5d, 0x50, 0x32, 0xfb, 0xd1, +0x81, 0xe2, 0xf7, 0x32, 0xd6, 0x70, 0xf4, 0xfa, +0x2d, 0x51, 0xc6, 0xa3, 0x3b, 0xe1, 0xba, 0xb6, +0x20, 0xad, 0x8e, 0x66, 0x96, 0xce, 0x95, 0xbb, +0x4a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0x36, 0xf1, 0xc3, 0x7f, 0xe2, +0xac, 0xee, 0x5f, 0xbc, 0x00, 0x57, 0x71, 0x2e, +0x44, 0x07, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x40, 0x61, 0x30, 0xba, +0x06, 0xf8, 0x7c, 0xb8, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0x85, 0xdb, 0x17, +0x53, 0x28, 0x4e, 0x77, 0x90, 0xb5, 0x8c, 0x4b, +0x0c, 0x76, 0xae, 0xf8, 0xa7, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x48, +0xb6, 0x9e, 0x36, 0xbc, 0xeb, 0x84, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0x24, +0xda, 0x76, 0x06, 0xdb, 0x88, 0xf8, 0xfa, 0x08, +0xb7, 0xa4, 0x55, 0xa6, 0x03, 0xdb, 0x9a, 0x1c, +0xd9, 0xb3, 0x5a, 0xbe, 0x54, 0x02, 0x22, 0xbe, +0xfa, 0x7a, 0x8c, 0xf2, 0x1d, 0x59, 0x56, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x7a, 0x70, 0xd4, 0xc5, 0xb2, 0x54, 0x34, +0x5a, 0x93, 0xc9, 0xdf, 0x5e, 0x78, 0x19, 0x18, +0x05, 0xd1, 0xbe, 0x58, 0xcc, 0xe7, 0xf6, 0x7d, +0x6b, 0xf5, 0xe4, 0xf2, 0x3a, 0xcb, 0x22, 0x62, +0x8c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x48, 0xc6, 0x7b, 0xc8, 0x91, +0x09, 0xa4, 0x26, 0xc0, 0xd2, 0x55, 0xa8, 0xc5, +0xf5, 0x92, 0xe2, 0xce, 0x58, 0x13, 0x6f, 0x64, +0xcf, 0x26, 0x72, 0x5d, 0xd8, 0xe7, 0x1e, 0x0f, +0x61, 0x68, 0x13, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0xc1, 0x74, 0x20, +0x36, 0x97, 0x75, 0xbf, 0x91, 0xc0, 0xd1, 0x84, +0x13, 0x80, 0xef, 0xc3, 0xf3, 0xcc, 0xd3, 0x2e, +0x5f, 0x86, 0x11, 0x72, 0x5b, 0x19, 0x53, 0x96, +0x67, 0xfe, 0xee, 0x46, 0xf8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0x06, +0xf8, 0x86, 0xda, 0x26, 0xf5, 0x31, 0xe6, 0x9c, +0x32, 0x58, 0xd7, 0x94, 0x13, 0xe3, 0x5c, 0x11, +0x6a, 0x38, 0x09, 0x25, 0x9d, 0x0d, 0x1c, 0x00, +0xf0, 0x19, 0x91, 0xf9, 0x09, 0xb5, 0x8f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0xd0, 0x79, 0x69, 0xea, 0x4a, 0x16, 0xf6, +0x4f, 0x1b, 0xfc, 0xb3, 0x7a, 0x5b, 0xae, 0x69, +0xef, 0x23, 0x2a, 0x31, 0xc5, 0xb5, 0x44, 0x03, +0x99, 0xc1, 0x25, 0x73, 0xf4, 0xfa, 0xfd, 0xb0, +0x3b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0x3a, 0xe7, 0xc4, 0x43, 0x28, +0x19, 0x0a, 0x5b, 0x3d, 0x27, 0xb3, 0xde, 0x29, +0xad, 0xae, 0xa3, 0x77, 0x94, 0x51, 0x37, 0x5c, +0xbd, 0x44, 0xc8, 0x02, 0xfd, 0x30, 0x70, 0x86, +0x77, 0x88, 0x3d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0x83, 0xdb, 0x51, +0x39, 0x25, 0x6b, 0xaf, 0xd3, 0x8a, 0x7d, 0xca, +0x53, 0x08, 0x67, 0xca, 0xb2, 0xf8, 0x21, 0xe1, +0xaf, 0xb4, 0xf1, 0x1e, 0xf7, 0x5f, 0x19, 0x0d, +0x53, 0x51, 0xea, 0x6e, 0x11, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x17, +0xe2, 0xeb, 0xc5, 0x28, 0xc3, 0xe2, 0x49, 0x52, +0x6f, 0xc1, 0x59, 0x34, 0xfe, 0xe3, 0xb6, 0xd6, +0x37, 0x48, 0x2c, 0x84, 0xb7, 0xcf, 0x2c, 0x5c, +0xa1, 0xaa, 0x71, 0xe1, 0x80, 0xd8, 0x78, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0x21, 0x79, 0x3a, 0xb6, 0x56, 0x4d, 0x16, +0x47, 0xe2, 0xeb, 0x03, 0xdf, 0xa2, 0x98, 0xf2, +0x0c, 0xd1, 0x3d, 0x96, 0x49, 0x3f, 0xa8, 0x8f, +0x77, 0x60, 0x22, 0xf2, 0x8e, 0xe1, 0x59, 0xf4, +0x08, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0x90, 0xd8, 0x08, 0x6a, 0x7f, +0xba, 0xb0, 0x60, 0x3f, 0xe9, 0x0d, 0x99, 0x7a, +0xfc, 0xce, 0x18, 0x1b, 0xc3, 0x90, 0x38, 0x8d, +0xe9, 0xa9, 0x7f, 0x8e, 0x33, 0xbe, 0xe0, 0xd8, +0x8b, 0x18, 0x79, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0xf5, 0x4e, 0x61, +0x51, 0xad, 0x97, 0x0c, 0x26, 0xeb, 0x60, 0x57, +0xf1, 0xc0, 0xb8, 0x03, 0xb4, 0x7b, 0xe9, 0x7d, +0xe1, 0x91, 0xc7, 0x13, 0x4a, 0x04, 0xbb, 0x74, +0xc1, 0x46, 0xf9, 0x81, 0xa3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0x1d, +0x3f, 0x64, 0xf5, 0xc7, 0xb6, 0x75, 0xae, 0xb6, +0x8f, 0x34, 0xdc, 0xb1, 0x49, 0xfc, 0x3d, 0x5f, +0x9a, 0xfb, 0x89, 0x38, 0x74, 0x77, 0x64, 0x9c, +0xf9, 0xfb, 0xab, 0x19, 0x95, 0x25, 0xed, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0x4d, 0x91, 0x53, 0x29, 0xad, 0x03, 0x41, +0x65, 0x23, 0x67, 0x75, 0x3c, 0xbf, 0x87, 0x64, +0x55, 0x05, 0xf4, 0x1c, 0x2c, 0xc1, 0xbc, 0x52, +0x6c, 0x4b, 0xe6, 0x17, 0x23, 0x18, 0xaf, 0x61, +0x3c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0x3c, 0xd2, 0x1a, 0xcc, 0xb3, +0x96, 0xc4, 0x83, 0xce, 0x74, 0x6c, 0x19, 0x90, +0xef, 0xdf, 0x28, 0x08, 0xf7, 0x6c, 0xca, 0xd9, +0xf6, 0x9c, 0xf5, 0x90, 0x77, 0x25, 0xd2, 0x5d, +0xf4, 0xb0, 0xe6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0xdb, 0xca, 0xb1, +0xb5, 0x2f, 0x3f, 0x9b, 0x2e, 0x38, 0x9b, 0xf2, +0x6b, 0x9c, 0xed, 0xef, 0xaf, 0x7e, 0xc4, 0x38, +0x1f, 0x8e, 0x5f, 0x22, 0x62, 0xe0, 0x7c, 0x0d, +0x63, 0xfe, 0x98, 0x3d, 0x23, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0xe8, +0x27, 0xe5, 0x17, 0xea, 0x54, 0x5b, 0x4f, 0x4a, +0xd5, 0x3f, 0x84, 0x03, 0xcb, 0x43, 0xf8, 0xc8, +0x2c, 0x48, 0x4a, 0xd4, 0x11, 0xe0, 0x59, 0xec, +0x5f, 0xe6, 0x85, 0x29, 0xa5, 0x58, 0x88, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0x9d, 0x1b, 0x8f, 0x46, 0x1b, 0x0e, 0x3a, +0xc4, 0xea, 0xc5, 0x72, 0x24, 0x20, 0x6d, 0x8c, +0x4e, 0xa4, 0xaf, 0x60, 0x47, 0x26, 0xe3, 0x48, +0x72, 0x25, 0x3d, 0x7a, 0xb0, 0xfe, 0xed, 0x87, +0x1d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0xfb, 0xd7, 0x75, 0xad, 0xdb, +0x62, 0x8d, 0x32, 0x75, 0x16, 0x36, 0x99, 0x90, +0x91, 0x47, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xa2, 0x1d, 0x5c, 0x79, +0x05, 0x5e, 0xee, 0x44, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0x0c, 0xe3, 0xf2, +0x0c, 0x98, 0x8d, 0xc9, 0x2a, 0x9d, 0xeb, 0x23, +0xc9, 0x8c, 0x02, 0xcb, 0x34, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x5b, +0xcb, 0xc1, 0x6e, 0xf5, 0xd7, 0x6c, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0x5c, +0x60, 0xbe, 0xb8, 0xb7, 0xde, 0xef, 0x40, 0x55, +0x95, 0x90, 0xde, 0x30, 0x1d, 0x83, 0x26, 0xba, +0x49, 0x36, 0xe5, 0xaa, 0xdf, 0x8e, 0xbd, 0x10, +0xad, 0x2a, 0x69, 0xd1, 0x26, 0xe1, 0x39, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0xcd, 0x9e, 0x4b, 0xcf, 0x8d, 0xc9, 0x67, +0x7d, 0xb1, 0x76, 0x1d, 0xe4, 0xb6, 0x8e, 0x80, +0x29, 0xa3, 0x8d, 0xb5, 0x2a, 0x0e, 0x27, 0x3d, +0x2d, 0x82, 0xbf, 0xc9, 0xf7, 0xc5, 0xaa, 0xd6, +0xb6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0xb2, 0xac, 0x7c, 0x7e, 0x2f, +0xf8, 0xee, 0x09, 0xce, 0xf8, 0x43, 0x66, 0x22, +0x52, 0xd7, 0x98, 0x76, 0xc6, 0x3d, 0x40, 0xa0, +0xbb, 0xa9, 0x4b, 0xe5, 0xf3, 0x6e, 0x83, 0x94, +0x3d, 0x86, 0x38, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0x6e, 0xa6, 0x5d, +0x55, 0x83, 0x71, 0x06, 0xce, 0xaf, 0x37, 0x2f, +0xd1, 0x3f, 0x2e, 0x8c, 0x9b, 0x2d, 0x4a, 0x8e, +0x34, 0xf4, 0xa7, 0x90, 0x91, 0x4e, 0xe1, 0xe4, +0x5a, 0x88, 0x4b, 0xad, 0xcd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0x21, +0x8e, 0x8c, 0xc6, 0x4d, 0x3a, 0x00, 0xb0, 0x77, +0x5a, 0xc0, 0x3d, 0xc9, 0x0d, 0xb6, 0xc1, 0xae, +0x31, 0x5a, 0x16, 0x1b, 0xf8, 0xc8, 0xa9, 0xab, +0xf4, 0xc3, 0x96, 0x7d, 0x56, 0x4c, 0x4f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0xca, 0xe0, 0xd9, 0x9e, 0x8f, 0xee, 0x80, +0xac, 0x58, 0xca, 0x81, 0xf3, 0xf0, 0x90, 0x09, +0x81, 0x72, 0x37, 0xd4, 0x4a, 0x14, 0x25, 0xa7, +0x07, 0x6b, 0x21, 0x7c, 0xf6, 0x58, 0x81, 0xa1, +0x62, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x77, 0x50, 0xcb, 0x05, 0x01, +0x1f, 0x5f, 0xa2, 0xf0, 0x93, 0x35, 0xfb, 0xe7, +0x47, 0x63, 0x57, 0xf5, 0x35, 0xcf, 0x33, 0xe8, +0x46, 0xd8, 0x8c, 0x88, 0x0b, 0xfc, 0x8b, 0xba, +0xd4, 0x8a, 0xe1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0x94, 0x0f, 0x83, +0xf6, 0x9e, 0xf6, 0xda, 0x1c, 0x4a, 0x87, 0x09, +0x97, 0x90, 0x04, 0x29, 0x41, 0xd5, 0xbc, 0x59, +0x98, 0x86, 0x4e, 0x90, 0xc4, 0x31, 0x74, 0xd7, +0x5d, 0xa2, 0x79, 0x94, 0xdf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0x5b, +0x53, 0x10, 0x08, 0x35, 0x13, 0x7a, 0xeb, 0xc7, +0x3c, 0x08, 0x7b, 0xb4, 0x33, 0x50, 0x3b, 0x57, +0x9a, 0xa4, 0x1b, 0x63, 0x8a, 0xf2, 0xba, 0x15, +0xe1, 0xb5, 0x62, 0x32, 0xe6, 0x54, 0x79, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x7d, 0x87, 0x29, 0x4b, 0x22, 0xe5, 0x4d, +0x1f, 0x22, 0x7c, 0x9c, 0x31, 0x90, 0xfc, 0x4e, +0xb4, 0xb1, 0x1c, 0x56, 0xc5, 0xb2, 0x3f, 0x46, +0xd6, 0x9e, 0xd7, 0x1b, 0x41, 0xf0, 0xd8, 0xbb, +0x27, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0xdb, 0xea, 0xb1, 0x6e, 0x72, +0xf3, 0xa0, 0x60, 0x9e, 0x47, 0x58, 0xee, 0x6f, +0xc0, 0x38, 0x97, 0xf8, 0xf9, 0x76, 0x5e, 0x92, +0x00, 0x27, 0xea, 0x05, 0xfe, 0x7f, 0xe8, 0xd8, +0x59, 0x0d, 0x66, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0x66, 0x69, 0x3a, +0x1a, 0x57, 0xbc, 0xf3, 0x1f, 0x3d, 0xa9, 0x87, +0x07, 0xa2, 0xa9, 0x18, 0x12, 0xe9, 0x09, 0xd7, +0x8b, 0xd2, 0x53, 0x92, 0xa4, 0x1b, 0xdf, 0x42, +0xdf, 0x48, 0x9a, 0x88, 0x6c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0x40, +0xde, 0x2e, 0x2c, 0xc7, 0x83, 0x3c, 0x59, 0x87, +0x3d, 0xf7, 0x99, 0x01, 0xe8, 0x96, 0x97, 0xd2, +0xc5, 0xc8, 0xa6, 0x3d, 0x67, 0xda, 0x2d, 0xa4, +0x37, 0x80, 0xe6, 0xe2, 0xa2, 0xe1, 0x8f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0xff, 0x0d, 0xfe, 0x55, 0xc9, 0xf3, 0xc5, +0x08, 0xab, 0xe3, 0xbb, 0x0a, 0xdc, 0x41, 0x4b, +0x02, 0x47, 0x46, 0x40, 0x73, 0x1f, 0xa8, 0x23, +0x17, 0x71, 0xce, 0xb8, 0xe4, 0x92, 0xe4, 0x49, +0xca, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0x9a, 0x17, 0xca, 0x3e, 0xe7, +0x3a, 0x9f, 0xbd, 0xab, 0x09, 0x3c, 0xf1, 0x13, +0xac, 0x65, 0xcd, 0xff, 0xa4, 0x26, 0x97, 0x1f, +0xd8, 0x72, 0x77, 0xa6, 0x49, 0xd5, 0x74, 0x44, +0x41, 0xfc, 0x1a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x14, 0x28, 0x2b, +0x33, 0xb6, 0xf7, 0x7a, 0x02, 0x48, 0x00, 0x36, +0x53, 0x21, 0xac, 0xbf, 0x5c, 0x8e, 0xb8, 0xe3, +0x0e, 0xf9, 0xe3, 0xaa, 0x89, 0xd5, 0x6e, 0xc1, +0x00, 0x9a, 0xcc, 0x53, 0x5d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0xcc, +0x6d, 0x91, 0x92, 0x87, 0xb0, 0xbb, 0x3e, 0xc6, +0x96, 0x83, 0xa2, 0xbe, 0x9c, 0x77, 0x78, 0x58, +0xa7, 0x54, 0xe5, 0xa4, 0x61, 0xb3, 0xb2, 0x05, +0x6e, 0x76, 0xc9, 0xef, 0xee, 0x7b, 0xcc, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0xc7, 0xd3, 0x6b, 0xe7, 0x96, 0x4b, 0x75, +0xb2, 0xdf, 0x6b, 0xaf, 0xea, 0xec, 0xb1, 0x43, +0x53, 0x90, 0xfc, 0x35, 0xc5, 0xa1, 0xc9, 0x5a, +0xac, 0x31, 0x0c, 0x5b, 0xf9, 0x8a, 0xb9, 0x27, +0x60, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0x87, 0x83, 0x4b, 0x18, 0xe7, +0x12, 0x36, 0xa6, 0x4c, 0x91, 0xe1, 0x01, 0x21, +0x11, 0x9c, 0xb3, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x4d, 0xdc, 0x6d, 0x3c, +0xf5, 0x86, 0x39, 0x15, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0x40, 0xf0, 0x49, +0x5f, 0x48, 0x05, 0x51, 0x9f, 0x57, 0x73, 0xc3, +0xca, 0xd9, 0xb3, 0x77, 0x78, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x79, +0xe6, 0x56, 0xe9, 0x86, 0xf0, 0x44, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0xe3, +0xfd, 0x35, 0x3c, 0x8b, 0x47, 0xa0, 0x07, 0x2d, +0x37, 0xd5, 0x5f, 0x78, 0x36, 0xae, 0x55, 0x19, +0x91, 0x1b, 0x33, 0xa4, 0x1c, 0x7b, 0x1e, 0x82, +0x69, 0x13, 0xe3, 0xd4, 0xd7, 0x6f, 0x3e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0xb5, 0x7d, 0xb3, 0x32, 0x0c, 0xbc, 0x91, +0x83, 0xa4, 0xb2, 0xe9, 0x34, 0x8b, 0xba, 0x45, +0x93, 0xfd, 0xbb, 0xb7, 0x05, 0x36, 0x11, 0xc4, +0x9e, 0x3b, 0x7f, 0x19, 0x24, 0x88, 0x04, 0x16, +0x32, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0x04, 0x0e, 0x2f, 0xcf, 0xb7, +0x3b, 0x1a, 0x1f, 0xe5, 0xfa, 0x1c, 0x7d, 0xe6, +0xfc, 0xf6, 0x95, 0xd5, 0xa3, 0x49, 0xaf, 0xd2, +0x2c, 0xd7, 0xd9, 0x2c, 0x39, 0x63, 0xf3, 0x12, +0x82, 0xf9, 0x0b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0x6a, 0x44, 0xdf, +0x67, 0x1f, 0xf0, 0xba, 0x96, 0xdb, 0x5b, 0xfe, +0xec, 0x60, 0x8b, 0xe1, 0x50, 0x89, 0x1c, 0x8e, +0x29, 0xf2, 0x13, 0x73, 0xc4, 0xb1, 0xfd, 0xc4, +0x6a, 0xef, 0xa1, 0x59, 0x62, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0x8b, +0x63, 0x26, 0xd9, 0x1c, 0x5e, 0x15, 0xfc, 0x0a, +0x58, 0x98, 0xfc, 0xa9, 0xaf, 0xf9, 0xcf, 0x34, +0xf9, 0x2a, 0x50, 0x5e, 0x9a, 0x0b, 0x8c, 0x14, +0x93, 0x6e, 0x01, 0xb9, 0x1c, 0x3c, 0xa1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0x3b, 0xff, 0xd6, 0x46, 0xe4, 0x59, 0x55, +0xed, 0xc7, 0x58, 0x9e, 0x2a, 0xcc, 0x0c, 0xed, +0x02, 0x46, 0xda, 0xd0, 0x79, 0x93, 0xda, 0x16, +0x54, 0x58, 0x0f, 0x23, 0x8e, 0x8b, 0xc7, 0x50, +0x91, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0x8d, 0xcf, 0x21, 0xc8, 0x7e, +0xf5, 0x66, 0x8d, 0x4e, 0xb0, 0x9e, 0xf4, 0x72, +0x46, 0x82, 0xa3, 0x1a, 0xad, 0x00, 0xe3, 0x2e, +0xcf, 0x05, 0x94, 0x20, 0xe6, 0x1c, 0x4a, 0x68, +0xa9, 0x42, 0xdd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x5e, 0xb2, 0x8b, +0xd7, 0x54, 0x78, 0xa0, 0x85, 0x8c, 0x0b, 0xb2, +0x90, 0xa8, 0x10, 0xcd, 0x31, 0x03, 0x3b, 0x1f, +0x19, 0xdb, 0x79, 0x30, 0x7a, 0x18, 0x92, 0x7b, +0x18, 0xec, 0xc5, 0xc4, 0xdf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0xe4, +0x60, 0x1e, 0x98, 0x0b, 0x61, 0x26, 0x39, 0x9b, +0x23, 0xfe, 0x94, 0xdc, 0x97, 0xe0, 0x4c, 0x9c, +0x69, 0xb0, 0xdf, 0x80, 0x73, 0x48, 0xff, 0xcb, +0xe2, 0xb4, 0xc6, 0x8d, 0x09, 0x2b, 0xac, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0xa5, 0x35, 0xed, 0x9c, 0xc6, 0xc7, 0xf4, +0x29, 0xed, 0xf4, 0xa4, 0xd7, 0xab, 0x77, 0x15, +0xd3, 0x86, 0xf7, 0xe6, 0x30, 0x94, 0x87, 0x1d, +0xf9, 0x44, 0x80, 0x24, 0xba, 0x53, 0x72, 0xd2, +0x53, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0xfa, 0x4b, 0xe7, 0x01, 0x2d, +0x9e, 0xf1, 0x3a, 0x00, 0xdb, 0x0c, 0x86, 0xf2, +0x1e, 0x84, 0x80, 0x04, 0x32, 0xf2, 0xa9, 0xab, +0x33, 0x27, 0x44, 0x7f, 0xb4, 0xa4, 0x0a, 0x3c, +0x20, 0xe0, 0x31, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x34, 0xa6, 0xe2, +0x42, 0x19, 0xd1, 0x30, 0xc7, 0xea, 0xc3, 0xae, +0x1f, 0x03, 0xf5, 0xe8, 0x1a, 0xad, 0x47, 0x58, +0x14, 0x34, 0x89, 0xa2, 0xf9, 0x88, 0xde, 0xfb, +0xfb, 0x80, 0xef, 0xe7, 0x6f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x75, +0x91, 0x4c, 0x5b, 0x4c, 0x8a, 0xd3, 0xb0, 0xf9, +0xa8, 0xac, 0xb5, 0xcc, 0x83, 0xa9, 0x6c, 0x02, +0x52, 0x92, 0xdd, 0xdd, 0xbf, 0x4c, 0x6e, 0x78, +0x2a, 0x62, 0xa4, 0xb1, 0x25, 0xcb, 0x65, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0xc6, 0xca, 0xfc, 0x5f, 0x3e, 0x40, 0x92, +0x25, 0xe5, 0x97, 0xd7, 0x48, 0x12, 0x34, 0x9c, +0x00, 0xe1, 0x6b, 0x81, 0x6d, 0xc2, 0xe1, 0xd2, +0x12, 0x28, 0x52, 0xcc, 0x3d, 0x78, 0x8f, 0xaa, +0xe6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0xd1, 0x08, 0xe1, 0x55, 0xa2, +0xb1, 0xe9, 0x0a, 0x33, 0xab, 0xb1, 0x25, 0x00, +0x56, 0x1f, 0x2e, 0x7b, 0xa0, 0x3c, 0xa9, 0xcc, +0xbe, 0xff, 0xcc, 0xe1, 0xd2, 0xd9, 0xf3, 0x5b, +0x86, 0x76, 0xe5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0x4d, 0x4c, 0xbd, +0x37, 0xbf, 0x42, 0x68, 0x23, 0x47, 0xab, 0xca, +0x22, 0x14, 0x04, 0xe5, 0x9d, 0xc7, 0xb2, 0x58, +0x0a, 0x25, 0xcd, 0x53, 0x3d, 0x2a, 0x00, 0x01, +0x5f, 0x87, 0x03, 0xc5, 0xe0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0xd1, +0x5e, 0xbe, 0x9b, 0xb6, 0xc0, 0x93, 0x20, 0x26, +0xf9, 0x3d, 0x42, 0x73, 0xe2, 0xe3, 0xbc, 0x3c, +0x34, 0x7a, 0xa0, 0x50, 0xc1, 0xb7, 0xd2, 0x4a, +0x4e, 0x74, 0xd3, 0x82, 0x5e, 0x1b, 0x4c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0xff, 0x50, 0xc5, 0x99, 0x19, 0x6d, 0xfb, +0x92, 0x84, 0xbc, 0xad, 0xe8, 0xea, 0x52, 0xe5, +0xa4, 0x38, 0xd0, 0xcd, 0xe4, 0xbb, 0x9c, 0x7f, +0x2c, 0xbb, 0x29, 0x56, 0x18, 0x40, 0xe2, 0x7a, +0xe6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0x23, 0x56, 0xe4, 0x5c, 0x68, +0xf3, 0x09, 0x89, 0xda, 0xc3, 0xb2, 0xd6, 0xa6, +0x4e, 0x11, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xa1, 0x9a, 0xec, 0x7e, +0x35, 0x07, 0xd0, 0x6c, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0xf8, 0xd1, 0x49, +0xdc, 0xb8, 0xbd, 0x36, 0xdc, 0x83, 0xb3, 0x34, +0x6e, 0xf2, 0x99, 0x13, 0x73, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x47, +0xe5, 0x25, 0xc8, 0x0e, 0x1f, 0x6d, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x2b, +0xe2, 0x13, 0x96, 0x67, 0x19, 0xcb, 0x14, 0xc7, +0xaf, 0x97, 0x2c, 0x3e, 0x42, 0xbe, 0x4f, 0x58, +0x66, 0xcb, 0xee, 0x43, 0xbf, 0xaf, 0xcc, 0x84, +0x3f, 0xe7, 0xff, 0x8b, 0x62, 0xf0, 0xba, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0xed, 0x4c, 0x9b, 0x34, 0xc2, 0xf6, 0xac, +0x7a, 0xea, 0x79, 0x6f, 0x16, 0x16, 0x1a, 0xaa, +0xb6, 0x63, 0x3d, 0x6c, 0x68, 0x96, 0x38, 0x0d, +0xc2, 0xad, 0x24, 0x6c, 0x5f, 0x49, 0x0e, 0x87, +0x13, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0x64, 0x54, 0xd1, 0x0e, 0x8e, +0xa6, 0x75, 0xb9, 0x32, 0x6e, 0xec, 0x7e, 0xb5, +0x52, 0x44, 0x92, 0x82, 0x5a, 0xb0, 0x0f, 0x96, +0x68, 0xcd, 0x0e, 0x19, 0xc1, 0x40, 0x6c, 0xf3, +0x1f, 0x3f, 0x9a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0xe8, 0xbb, 0x91, +0xd3, 0x3b, 0x6d, 0xa7, 0x9a, 0x9a, 0x9d, 0x3b, +0xa6, 0x37, 0x32, 0xc5, 0x6a, 0x15, 0x82, 0xf3, +0xb9, 0x7a, 0xd4, 0xdd, 0x91, 0xbb, 0x7e, 0x34, +0xc0, 0x1a, 0x3b, 0x1a, 0x3a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0x61, +0x81, 0x8b, 0xc3, 0x06, 0x35, 0xb6, 0xed, 0xf6, +0x11, 0x20, 0x5e, 0x28, 0xa3, 0xb9, 0x56, 0x2e, +0x51, 0xad, 0x94, 0x60, 0x2c, 0x81, 0x35, 0x10, +0x8b, 0x99, 0xc1, 0x58, 0x7f, 0xfd, 0xce, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0x84, 0xdc, 0xa8, 0x7e, 0xb4, 0x07, 0xde, +0x1f, 0x44, 0x37, 0xe7, 0x1f, 0xbf, 0xff, 0xca, +0x94, 0x75, 0xb4, 0x89, 0x10, 0x01, 0x9a, 0x6d, +0xf3, 0x48, 0x14, 0x95, 0xf2, 0xea, 0xa2, 0xdb, +0x9b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x26, 0x27, 0x66, 0x6f, 0x3a, +0x63, 0xbe, 0x47, 0xa7, 0x63, 0xf9, 0x00, 0x32, +0x9b, 0xb2, 0xe9, 0x23, 0xd4, 0xea, 0x3f, 0x00, +0xf4, 0x15, 0xae, 0x90, 0xce, 0x59, 0x13, 0x7e, +0xde, 0x4d, 0xf7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0xe3, 0xb2, 0x95, +0x4f, 0xe4, 0x0a, 0xd6, 0xb5, 0xdc, 0x0e, 0x1b, +0x98, 0xdb, 0x9c, 0x97, 0x3c, 0x2f, 0x74, 0xba, +0x16, 0xcd, 0x08, 0xd3, 0xde, 0x2d, 0xf2, 0xac, +0x73, 0xb5, 0x3c, 0x9e, 0x96, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0x92, +0xa7, 0x78, 0x7a, 0xa6, 0x6a, 0xce, 0x72, 0x18, +0x4d, 0x88, 0x82, 0xa2, 0xd7, 0x0b, 0x0a, 0x07, +0x91, 0x82, 0x3a, 0x6b, 0x2f, 0xc9, 0x50, 0x75, +0x59, 0x53, 0x34, 0xb3, 0xa0, 0x29, 0x60, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0x5c, 0x62, 0x84, 0xde, 0xff, 0x1b, 0x76, +0xb1, 0x4e, 0x7e, 0x79, 0x06, 0x92, 0x5c, 0x56, +0xcb, 0xea, 0x82, 0x03, 0xa7, 0xf3, 0x19, 0xcf, +0x6a, 0x1c, 0xd1, 0x34, 0x18, 0xb6, 0x6c, 0x3b, +0xb0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0x96, 0x26, 0xa0, 0x1e, 0x73, +0x29, 0x80, 0x09, 0xef, 0x0e, 0xcd, 0x3e, 0x37, +0xc3, 0xca, 0x9a, 0xde, 0xcb, 0x30, 0xfc, 0xe6, +0xbf, 0xe6, 0x5e, 0x39, 0x51, 0xf6, 0x7f, 0xc8, +0x7e, 0x05, 0x4f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0xf9, 0xbf, 0xf5, +0x8c, 0xd1, 0x9b, 0xbd, 0xad, 0x9a, 0x5c, 0x04, +0xcb, 0x53, 0xce, 0xba, 0x55, 0x55, 0x40, 0xcc, +0xfa, 0xa9, 0xcb, 0x21, 0xba, 0xbc, 0x75, 0x3c, +0x09, 0x9f, 0xf1, 0x0d, 0xd6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0xc2, +0x6b, 0xc7, 0x1a, 0xc8, 0xc0, 0xd6, 0x2d, 0xf5, +0x07, 0xa0, 0x30, 0xe9, 0x58, 0x69, 0x1e, 0x55, +0x06, 0xb1, 0xc6, 0x88, 0xf7, 0xeb, 0xbf, 0xb5, +0x68, 0x4e, 0xd0, 0xa9, 0xfd, 0x1c, 0x9c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0x28, 0x80, 0x73, 0x9a, 0x5d, 0x28, 0x2e, +0xa9, 0xdf, 0xf3, 0x51, 0x9c, 0xf9, 0xd3, 0x2a, +0x25, 0xfe, 0x6b, 0x1e, 0x69, 0x6c, 0xd3, 0x20, +0xea, 0xbf, 0x40, 0xfb, 0xda, 0xd0, 0xcd, 0xfd, +0x65, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0xd6, 0x21, 0x88, 0x23, 0x1c, +0x89, 0x98, 0x71, 0x71, 0x7b, 0x21, 0x3d, 0x7f, +0xa7, 0xd4, 0x56, 0x90, 0xe9, 0xe3, 0xae, 0x79, +0x4c, 0x7f, 0x76, 0xd0, 0x4d, 0x5c, 0xaf, 0xe6, +0xfe, 0x28, 0xe5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0x72, 0x30, 0x8d, +0x60, 0x98, 0x47, 0x42, 0xab, 0xe3, 0xec, 0xa1, +0xd7, 0x85, 0xae, 0xb5, 0xbd, 0xdf, 0x5e, 0xf3, +0x35, 0x88, 0xb9, 0x01, 0xf2, 0xff, 0xe3, 0xe9, +0x5e, 0x91, 0x3a, 0xac, 0xfc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x97, +0x6e, 0x6c, 0xd0, 0x43, 0xa2, 0x7d, 0x77, 0x5c, +0x68, 0xf7, 0x90, 0x9a, 0x9c, 0x98, 0xfc, 0xf6, +0xf2, 0xe4, 0xc6, 0xe7, 0x10, 0x70, 0x56, 0x0d, +0x4f, 0x9c, 0x71, 0x99, 0x56, 0xca, 0x66, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0xd4, 0x4c, 0x91, 0x81, 0x91, 0x22, 0x84, +0x9c, 0x28, 0xa7, 0x8d, 0x85, 0x04, 0xbe, 0x38, +0xd4, 0xb6, 0x25, 0x5f, 0xea, 0x08, 0xc8, 0x54, +0x6c, 0xd5, 0x7a, 0xd0, 0x6d, 0xed, 0x0a, 0xd4, +0x51, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0x43, 0xdc, 0x47, 0x92, 0x50, +0x8d, 0xdf, 0xc6, 0x8c, 0x86, 0x2b, 0xf2, 0xb7, +0x9b, 0x0e, 0xb6, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xb5, 0x45, 0xef, 0xcc, +0xcc, 0x85, 0x74, 0x57, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0x6f, 0xc3, 0x28, +0xcb, 0x2d, 0x5e, 0x68, 0x80, 0x83, 0x9f, 0xb9, +0xc7, 0x0c, 0xb6, 0x41, 0x66, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbc, 0xe3, +0xbd, 0x41, 0xb2, 0xc3, 0x64, 0x4f, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0x55, +0xe8, 0xbc, 0xa6, 0xe0, 0xd5, 0xf8, 0xf2, 0xef, +0x85, 0xc9, 0x52, 0xf4, 0x94, 0xc8, 0x15, 0x95, +0x88, 0x87, 0xd6, 0x20, 0x15, 0x9d, 0x70, 0xeb, +0x5f, 0xea, 0xc1, 0x33, 0x86, 0xd4, 0xbe, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0xf3, 0xe3, 0x03, 0x59, 0x30, 0xe7, 0x59, +0x87, 0x48, 0x2a, 0xaf, 0x87, 0x84, 0xba, 0x7d, +0x33, 0x0d, 0xe2, 0x0c, 0x69, 0xf6, 0xb9, 0xd9, +0xc1, 0x32, 0xd0, 0x1b, 0x7b, 0x21, 0x6e, 0x45, +0x44, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x7b, 0xc2, 0xe1, 0x23, 0x35, +0x17, 0x73, 0x31, 0x8a, 0x98, 0x6b, 0x39, 0xf4, +0x21, 0x07, 0xd4, 0xe5, 0x30, 0x9c, 0x28, 0x37, +0x95, 0xd4, 0x10, 0xfd, 0xc3, 0x87, 0x47, 0x68, +0xbd, 0x0d, 0xd1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0xe0, 0x6b, 0xe3, +0xec, 0xfe, 0x3d, 0xe7, 0x79, 0x35, 0xaf, 0xb2, +0xdf, 0x70, 0x19, 0x5a, 0x5d, 0xf1, 0xd4, 0x1b, +0x8d, 0x35, 0x59, 0x46, 0xb8, 0xec, 0xc8, 0xcd, +0xc2, 0xcc, 0xe2, 0xd3, 0x40, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0x63, +0x68, 0x51, 0x83, 0xe5, 0x2a, 0xf1, 0x55, 0x5f, +0x34, 0xe2, 0x52, 0xd4, 0x3c, 0xb1, 0x69, 0x33, +0xe3, 0x80, 0x8c, 0x37, 0xf3, 0x9f, 0x7d, 0xa1, +0x29, 0x9a, 0xbf, 0x0c, 0x06, 0x0a, 0x54, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0x10, 0x6a, 0xdb, 0x08, 0xdc, 0xb0, 0xec, +0xce, 0xc1, 0x36, 0xcd, 0x61, 0xaa, 0x98, 0x79, +0x4c, 0x8e, 0xc8, 0x76, 0xe7, 0xa7, 0x7b, 0x1d, +0x47, 0xc5, 0x7b, 0x7d, 0x96, 0xcc, 0x98, 0x6d, +0x4b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0xbe, 0x50, 0x75, 0xf3, 0x8b, +0xf2, 0xa2, 0x00, 0x22, 0x4f, 0x56, 0x2c, 0xd1, +0x8c, 0x08, 0xe6, 0xe3, 0x93, 0x80, 0x87, 0x97, +0xe5, 0xfc, 0xb8, 0x25, 0x49, 0x33, 0x82, 0x40, +0x6c, 0x35, 0x2a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0x8d, 0x74, 0xb4, +0x41, 0x98, 0x0d, 0x76, 0xc6, 0x2d, 0x7e, 0x2a, +0xdb, 0x49, 0xe7, 0xa5, 0x57, 0xce, 0xd1, 0x06, +0x29, 0xf4, 0xd3, 0x63, 0x4a, 0x81, 0xf1, 0xc2, +0x42, 0xef, 0x55, 0x4c, 0x17, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0x29, +0x77, 0x10, 0x0e, 0x47, 0x06, 0x0e, 0x6e, 0xcc, +0x54, 0x35, 0x27, 0x21, 0x9c, 0xc7, 0xd6, 0x30, +0x74, 0xa5, 0x8b, 0xdf, 0xda, 0xef, 0xd5, 0x8b, +0x4b, 0x65, 0x44, 0xf0, 0xe9, 0xd7, 0x9a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0xe5, 0xcf, 0x41, 0x70, 0x08, 0x26, 0xf2, +0x19, 0xbb, 0x51, 0x49, 0x5d, 0xa7, 0x9a, 0x46, +0x3b, 0x9c, 0x0a, 0xb3, 0xbc, 0xf5, 0xdc, 0x4e, +0x2c, 0xe8, 0xa8, 0x32, 0xac, 0xb9, 0xe9, 0x6a, +0xc4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0xbe, 0x2f, 0x58, 0x69, 0x36, +0x56, 0xf1, 0xe2, 0x82, 0x20, 0xff, 0xfc, 0x55, +0x80, 0xc6, 0x06, 0x69, 0x54, 0x1b, 0x32, 0x13, +0x37, 0x9b, 0x08, 0x5a, 0xf9, 0x58, 0x70, 0x9f, +0xf7, 0xa3, 0x37, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0x0b, 0x16, 0xfb, +0xb7, 0x88, 0x39, 0x16, 0x2c, 0x48, 0xc8, 0xb6, +0xda, 0x2e, 0xca, 0x26, 0x22, 0x31, 0x19, 0x3c, +0x14, 0xf8, 0xb4, 0xc1, 0xcc, 0xe9, 0xf1, 0xbe, +0x36, 0x43, 0xe5, 0x95, 0x9a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0xc3, +0x93, 0xb9, 0x86, 0x77, 0x3a, 0x77, 0x9e, 0xff, +0xc0, 0xf6, 0x0c, 0x39, 0x62, 0x97, 0x4a, 0xc2, +0x35, 0x5f, 0xe3, 0xbc, 0x29, 0x42, 0x1b, 0x41, +0xae, 0xa7, 0x40, 0x9e, 0x3a, 0xa1, 0x86, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0x74, 0xd8, 0xfa, 0x8f, 0x6f, 0xb3, 0x41, +0xa7, 0xb0, 0x11, 0xbe, 0xe7, 0x7d, 0x1c, 0x56, +0xa3, 0xa2, 0x6f, 0x88, 0xb7, 0x60, 0xde, 0x23, +0xed, 0x39, 0x14, 0x01, 0xd6, 0x20, 0x2d, 0x89, +0xbe, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0x2d, 0x0e, 0xde, 0xbc, 0xa6, +0xf9, 0x86, 0xcd, 0xf6, 0xec, 0x40, 0x45, 0xcf, +0x8e, 0x08, 0xf0, 0x24, 0xa3, 0xe4, 0x06, 0xfd, +0xa0, 0x61, 0x19, 0x95, 0xdc, 0x2e, 0xea, 0x15, +0x84, 0xb3, 0xc8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0x13, 0xa1, 0x55, +0xf9, 0x25, 0x61, 0xad, 0x57, 0xa5, 0x2c, 0x19, +0xea, 0xa6, 0x4e, 0x57, 0x59, 0x36, 0xb3, 0x3e, +0xdf, 0x73, 0x97, 0x4d, 0x2d, 0x7e, 0x3a, 0x6e, +0x73, 0x20, 0x9e, 0x49, 0x56, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0x92, +0xb4, 0xd3, 0x79, 0xfa, 0x9c, 0xfa, 0xe9, 0x6b, +0x84, 0x7c, 0xc5, 0x06, 0x9e, 0x25, 0x8e, 0xe3, +0x87, 0xfc, 0xe1, 0xe3, 0x0e, 0x9a, 0x93, 0x63, +0xd9, 0x4e, 0x70, 0x92, 0xc4, 0x26, 0xe3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x4d, 0x85, 0x69, 0x64, 0x95, 0x14, 0x0b, +0xa0, 0xdf, 0x06, 0xa0, 0xad, 0x24, 0x05, 0x16, +0x04, 0x2b, 0x38, 0xdb, 0xa5, 0x38, 0x84, 0xab, +0xbf, 0xda, 0xcd, 0x08, 0x8f, 0x8e, 0xcf, 0x6f, +0xdc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0x8a, 0x70, 0x5e, 0xd1, 0x43, +0xdb, 0xac, 0x44, 0x3d, 0x1d, 0x91, 0x73, 0xd4, +0xb5, 0x53, 0xf7, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x02, 0x28, 0x40, 0x0c, +0x5a, 0x3f, 0x2b, 0x67, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x2b, 0x6d, 0x06, +0x42, 0xe0, 0xe3, 0x88, 0x72, 0x31, 0x3f, 0xe0, +0x14, 0x7a, 0x34, 0x59, 0xe5, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7d, 0x80, +0x01, 0xb1, 0xdb, 0xc1, 0xbb, 0xef, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0x47, +0xfc, 0x95, 0xb3, 0x7c, 0xe1, 0x08, 0x8b, 0x4c, +0x6a, 0x7a, 0x3a, 0xb0, 0x93, 0x55, 0xeb, 0x9b, +0xdd, 0x84, 0xa9, 0x16, 0x4b, 0x53, 0x1b, 0x80, +0xda, 0x99, 0x19, 0xce, 0xf5, 0x8e, 0x83, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0x03, 0x1c, 0x57, 0xbf, 0xdf, 0x8b, 0x84, +0x17, 0xb0, 0x61, 0xb3, 0x17, 0x25, 0x20, 0xe8, +0x05, 0x8f, 0xad, 0xbb, 0xeb, 0x43, 0x10, 0x9b, +0x17, 0x3f, 0xfe, 0x44, 0xb6, 0x49, 0xcf, 0x8f, +0x4b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0x82, 0x90, 0x08, 0xfc, 0x93, +0x92, 0x03, 0x9b, 0x37, 0x22, 0x8e, 0x9d, 0x40, +0x4f, 0x23, 0xfb, 0x30, 0x72, 0xf6, 0x01, 0x46, +0xf3, 0xdb, 0xc5, 0x03, 0x2b, 0x4a, 0x7c, 0x70, +0xb6, 0x4d, 0xde, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x9d, 0xcc, 0x98, +0xbd, 0x5d, 0xd4, 0x64, 0x79, 0x45, 0xe7, 0x17, +0xa0, 0xdf, 0x9a, 0x5a, 0x5e, 0xfe, 0x9e, 0x68, +0xfa, 0xfe, 0x73, 0x84, 0xc0, 0xf6, 0x75, 0x5f, +0x33, 0x07, 0xbf, 0x80, 0x40, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0x59, +0x37, 0x67, 0x1a, 0xcd, 0x31, 0x52, 0xdf, 0x9e, +0x85, 0xd6, 0xe2, 0x1f, 0xfe, 0x0e, 0x86, 0xc8, +0xd2, 0xdb, 0xc5, 0xf1, 0x81, 0x35, 0x04, 0x0d, +0xfb, 0xe0, 0x28, 0x1c, 0x86, 0x51, 0x2c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0x65, 0x7a, 0x69, 0x56, 0xec, 0xe4, 0xf9, +0x41, 0x41, 0xa1, 0x38, 0x55, 0xe7, 0x4f, 0xae, +0x6b, 0xd9, 0x6e, 0xaa, 0xe6, 0xda, 0x81, 0xcb, +0x37, 0xe0, 0xd2, 0xf5, 0x54, 0xed, 0x65, 0x1e, +0x49, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0xdd, 0xb7, 0x55, 0xb5, 0xe4, +0xee, 0x1e, 0x50, 0x9a, 0xb6, 0x40, 0xcf, 0x20, +0xc7, 0xe2, 0x9f, 0x89, 0xbf, 0x2d, 0xf3, 0x15, +0x2f, 0x0c, 0x2d, 0xa1, 0xbd, 0xf6, 0x4d, 0x25, +0x18, 0x67, 0x5a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0xe5, 0x50, 0x52, +0x2b, 0xe1, 0x0b, 0x7c, 0xf1, 0x35, 0xdd, 0x1b, +0xa1, 0x76, 0x52, 0x5f, 0x2a, 0x83, 0x30, 0xb9, +0xfe, 0x39, 0xf1, 0x94, 0x8d, 0x1f, 0x3e, 0x83, +0x7e, 0x6d, 0x67, 0xc1, 0xba, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0x91, +0x2d, 0x85, 0x06, 0xb6, 0xba, 0x61, 0xa2, 0xea, +0x6e, 0xb3, 0x75, 0x7c, 0xb7, 0x8f, 0x33, 0xf7, +0xc9, 0x2a, 0xf9, 0x19, 0x7b, 0xc5, 0x26, 0xc8, +0x83, 0x9e, 0x54, 0x36, 0x39, 0x83, 0x3e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0xb6, 0xe7, 0xeb, 0x04, 0x13, 0x39, 0x8f, +0x21, 0x5a, 0x08, 0x69, 0x82, 0xd7, 0x52, 0xea, +0xd2, 0x1d, 0x93, 0x48, 0xd5, 0xb3, 0xed, 0x16, +0x99, 0x83, 0xd0, 0x18, 0xaa, 0x61, 0xd0, 0x99, +0xca, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0xcf, 0x5c, 0x9f, 0xf0, 0x96, +0x57, 0xed, 0xc1, 0x5c, 0x6c, 0x1a, 0xfd, 0x62, +0x7f, 0x6e, 0x83, 0x20, 0x5d, 0xa8, 0x8d, 0x08, +0xfe, 0xb6, 0x9b, 0x52, 0xbe, 0xaa, 0x66, 0x45, +0x07, 0x52, 0x9c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x12, 0xa9, 0x5a, +0x2e, 0x75, 0x69, 0x2b, 0x5b, 0x48, 0x98, 0xe7, +0xb8, 0x31, 0x8e, 0xb5, 0x50, 0xc3, 0x21, 0x14, +0x2f, 0x66, 0x3e, 0xf5, 0x7f, 0xce, 0x13, 0x0f, +0x94, 0x9c, 0x93, 0x2f, 0xae, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x90, +0x7f, 0xce, 0xef, 0xb5, 0x8c, 0xef, 0x17, 0x53, +0xd6, 0x1c, 0xe3, 0xc3, 0xcb, 0xec, 0x55, 0xb5, +0xea, 0xbf, 0xcd, 0xa7, 0x56, 0x31, 0xec, 0x4e, +0x39, 0x1d, 0xea, 0xbf, 0x0e, 0xb7, 0x1a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0x84, 0x6f, 0xf3, 0x61, 0x3d, 0x0f, 0xc3, +0xc3, 0x3f, 0x1e, 0x6a, 0x56, 0x83, 0x68, 0x70, +0xca, 0xd2, 0xd6, 0x6e, 0x8c, 0xfd, 0x3d, 0xed, +0x7a, 0xe0, 0x31, 0x56, 0x3f, 0x3d, 0xa5, 0x0a, +0xe5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0xc0, 0x70, 0x47, 0xb9, 0x56, +0x70, 0x25, 0x4f, 0x91, 0x26, 0xb5, 0x8b, 0x05, +0xa5, 0x48, 0xb9, 0x33, 0x69, 0x81, 0xbc, 0x2b, +0x16, 0xaa, 0x76, 0xd0, 0x3b, 0xf8, 0x0a, 0x0a, +0x9c, 0xa5, 0x59, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0x94, 0x37, 0xbd, +0xf3, 0x55, 0x55, 0x50, 0xe1, 0xd6, 0x39, 0x3a, +0xc1, 0xd2, 0xdf, 0x4d, 0x49, 0x4f, 0x34, 0xd7, +0x2a, 0x27, 0xa1, 0x36, 0x13, 0xf9, 0xd3, 0xa9, +0xc6, 0xcb, 0x2d, 0x38, 0x25, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0xd2, +0x57, 0x3d, 0xc5, 0x95, 0x61, 0xc6, 0x90, 0xe3, +0xad, 0xd8, 0x0e, 0xbd, 0x68, 0xd9, 0xa9, 0xc1, +0x3e, 0x9f, 0xe0, 0x0a, 0x0b, 0x45, 0xa6, 0xca, +0x21, 0x9e, 0xa7, 0x5d, 0xb8, 0x2c, 0xed, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0xe4, 0xc0, 0xa8, 0xc5, 0xfd, 0x2a, 0x6b, +0x45, 0x4c, 0x29, 0xeb, 0x62, 0xf4, 0xb8, 0x21, +0x73, 0x04, 0xea, 0x45, 0xe2, 0x5a, 0x28, 0x4e, +0xc5, 0x43, 0x48, 0x32, 0x76, 0xd9, 0x16, 0x3e, +0x53, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0xb6, 0xba, 0x93, 0x16, 0xb4, +0x5c, 0xd5, 0xcb, 0x1c, 0xf2, 0x92, 0xee, 0x9f, +0x97, 0xdf, 0x97, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x35, 0xdd, 0xf1, 0x3e, +0x55, 0x91, 0x20, 0x33, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0x3f, 0x7a, 0xe0, +0xf8, 0x79, 0xbb, 0x3b, 0xf9, 0x14, 0xcd, 0xf2, +0x5c, 0xd7, 0xd7, 0x9c, 0x8d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0x78, +0xcf, 0x03, 0x18, 0xce, 0xc1, 0x7b, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0x71, +0x2e, 0xd3, 0x42, 0x0c, 0x84, 0x6e, 0xad, 0xc6, +0xbf, 0x8c, 0x31, 0xb8, 0x3b, 0xb6, 0x11, 0x5a, +0x0a, 0x9a, 0x2d, 0xd9, 0x7c, 0x27, 0x6d, 0x94, +0x08, 0xb5, 0x8a, 0x49, 0x8c, 0xcf, 0x36, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0xd6, 0x33, 0x23, 0xb5, 0xd3, 0x87, 0x02, +0x2e, 0xdc, 0x2d, 0xe3, 0x4c, 0x60, 0xae, 0xb4, +0x3b, 0x96, 0x22, 0x46, 0x48, 0x21, 0x6d, 0xd8, +0x60, 0xe3, 0xb2, 0xca, 0x44, 0xe6, 0x5c, 0xf6, +0xac, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0x2c, 0xb3, 0x56, 0x93, 0x3a, +0x69, 0x80, 0x36, 0xbf, 0xf7, 0x84, 0x95, 0x66, +0xd0, 0x25, 0x9a, 0x71, 0x2d, 0xe5, 0xb4, 0x6a, +0x70, 0xae, 0xfd, 0x20, 0x3f, 0x64, 0x1e, 0xd9, +0x2e, 0x9b, 0x5f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0xcf, 0xdb, 0x7b, +0xf6, 0x85, 0xca, 0xd8, 0x30, 0xa4, 0xa1, 0x64, +0xa0, 0x5b, 0xaf, 0x76, 0xa3, 0xd8, 0x3a, 0x87, +0xac, 0x49, 0x56, 0x4f, 0xba, 0xb2, 0x90, 0x8a, +0xd4, 0xf1, 0xdd, 0x77, 0x8f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0xa4, +0x42, 0x23, 0xf7, 0x76, 0x37, 0xff, 0x5b, 0x39, +0x88, 0xaa, 0x4b, 0xeb, 0x1d, 0x70, 0x10, 0x3e, +0xb2, 0x5b, 0x2c, 0x7e, 0x3d, 0xf5, 0xa5, 0x7c, +0xf9, 0x9d, 0xf5, 0x6f, 0xc1, 0xd5, 0x86, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x6d, 0x2f, 0x62, 0xf4, 0x66, 0x06, 0x11, +0x06, 0x1a, 0x2e, 0xdb, 0x5c, 0x17, 0x42, 0xad, +0x90, 0x9d, 0xbe, 0xf2, 0x7c, 0x94, 0x04, 0xe4, +0xaf, 0xed, 0xfb, 0x98, 0xbe, 0x78, 0xde, 0xb6, +0xcb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0x65, 0x45, 0xd4, 0x47, 0xa6, +0x8f, 0x55, 0xed, 0x4b, 0xf1, 0xaa, 0x50, 0x14, +0x5f, 0x39, 0x6a, 0x4e, 0x4c, 0x12, 0x3f, 0x0b, +0x6f, 0x49, 0x4e, 0x85, 0x84, 0x3f, 0xee, 0xe5, +0x14, 0x8d, 0x0b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0xbf, 0x21, 0x5f, +0x8f, 0x56, 0xcf, 0x01, 0xbe, 0x24, 0xc3, 0x38, +0xb7, 0xd4, 0xbb, 0x61, 0xe6, 0x20, 0x0c, 0x8e, +0x47, 0xac, 0xb2, 0xbf, 0x55, 0x85, 0x17, 0x62, +0x31, 0x25, 0xb5, 0x59, 0xef, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0x86, +0x83, 0xb9, 0x6d, 0xc3, 0x6b, 0xaa, 0x4b, 0x91, +0x77, 0x9d, 0x0e, 0x69, 0xe0, 0xe3, 0x3c, 0x5e, +0x66, 0x3e, 0x9f, 0x07, 0xc0, 0x1a, 0x08, 0xfb, +0xca, 0xb5, 0xa5, 0x5f, 0xfd, 0x2e, 0x40, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0x7c, 0x11, 0xa3, 0xbe, 0xc5, 0x7f, 0xdb, +0xf4, 0x53, 0x48, 0x7c, 0x51, 0x4d, 0xfe, 0x5d, +0x22, 0xe3, 0x14, 0x1a, 0xf4, 0x1d, 0x9d, 0xc8, +0xa8, 0x58, 0x56, 0xb1, 0x3b, 0x09, 0x45, 0x12, +0x5b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0xa5, 0xdb, 0xd2, 0xb6, 0xd6, +0x87, 0xf3, 0x6d, 0x7f, 0xee, 0xa5, 0x1a, 0x86, +0xc9, 0xf3, 0x1d, 0x43, 0xd6, 0xcb, 0xe0, 0xc6, +0xf6, 0x2d, 0x41, 0xc6, 0x04, 0x27, 0xd1, 0x21, +0x51, 0xd8, 0x53, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0xf0, 0x2f, 0xbc, +0x41, 0x51, 0x65, 0x1f, 0x22, 0x36, 0x49, 0xce, +0xc3, 0x9e, 0x4f, 0x89, 0xae, 0xc1, 0xa3, 0x70, +0xc9, 0x05, 0x3e, 0xa1, 0x23, 0xf4, 0xcc, 0xe7, +0x80, 0x18, 0x15, 0x95, 0x88, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0x0f, +0x76, 0xc7, 0x40, 0x51, 0xdd, 0x05, 0x0c, 0xf3, +0x84, 0x85, 0x24, 0x71, 0xbe, 0xb4, 0x20, 0x13, +0xc3, 0xfc, 0xa0, 0xd8, 0xb8, 0x87, 0xa0, 0xd4, +0x71, 0x86, 0x67, 0xeb, 0x95, 0xda, 0x16, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0x20, 0x89, 0xca, 0x5e, 0x5f, 0xee, 0x5f, +0x40, 0x5d, 0xe5, 0x5b, 0x06, 0xb8, 0x55, 0x04, +0x4b, 0x69, 0x52, 0x10, 0x46, 0x91, 0x75, 0xbe, +0x8d, 0xc6, 0x27, 0xac, 0x68, 0x19, 0x7a, 0x56, +0xc4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0x4c, 0x6a, 0xdc, 0x1e, 0xea, +0xd2, 0x13, 0x42, 0xb6, 0x5b, 0xe8, 0x4e, 0xdb, +0x3a, 0xa9, 0x6e, 0xc2, 0x18, 0x27, 0xaa, 0xad, +0x05, 0x73, 0x28, 0xf5, 0x37, 0x91, 0x48, 0x80, +0x90, 0xea, 0x46, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x53, 0x22, 0x36, +0x4b, 0x0b, 0x2e, 0x1a, 0xc7, 0x03, 0xc1, 0x8c, +0x5f, 0x29, 0xd1, 0x72, 0xe2, 0xe2, 0xf8, 0x47, +0x9c, 0xb0, 0x02, 0x34, 0xa7, 0x28, 0xb5, 0x21, +0xbd, 0x08, 0xa4, 0x41, 0xd2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0xe6, +0x00, 0x87, 0x8c, 0x2c, 0xfa, 0xb0, 0x56, 0xd7, +0x5c, 0x5d, 0xdc, 0xa4, 0x6c, 0x61, 0x98, 0x51, +0x20, 0x26, 0x1c, 0x32, 0xd9, 0xef, 0xdb, 0x66, +0x38, 0x12, 0xab, 0x2b, 0x7e, 0x5f, 0xb8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0x84, 0x9c, 0x0c, 0xa7, 0x4d, 0x8d, 0xd6, +0xcf, 0x81, 0xb3, 0x0a, 0x6a, 0x62, 0x2b, 0x64, +0xe4, 0x31, 0xb1, 0xba, 0x41, 0x13, 0x5a, 0x12, +0x13, 0xbd, 0x9c, 0x9d, 0x28, 0x02, 0x1c, 0xb7, +0xa0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0x0b, 0x0e, 0x22, 0xbb, 0xb0, +0xe9, 0xe6, 0x6e, 0x39, 0x21, 0xb5, 0x9a, 0xbe, +0x61, 0x4c, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x49, 0x68, 0xf3, 0xdf, +0xb1, 0xe7, 0x5d, 0x2e, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0x2a, 0x1e, 0xcf, +0x7c, 0x23, 0xa6, 0x12, 0xef, 0x39, 0x0e, 0xd0, +0x05, 0x41, 0x31, 0xc2, 0x40, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x4c, +0x77, 0xd0, 0x21, 0x36, 0x9e, 0x16, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0x4f, +0x1f, 0xbc, 0x4c, 0xbc, 0x98, 0xd6, 0x9c, 0x0b, +0x2d, 0xcc, 0x7c, 0xdd, 0xb9, 0x06, 0xfd, 0xad, +0xee, 0xeb, 0x04, 0x17, 0x33, 0xd3, 0x9a, 0x4a, +0x79, 0x22, 0x4b, 0x41, 0xa8, 0xa5, 0x87, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0xce, 0xdc, 0x26, 0x13, 0x6a, 0x3f, 0xc1, +0xb4, 0x70, 0x6d, 0x0d, 0xf9, 0xf6, 0x00, 0xa2, +0x26, 0x8a, 0x7f, 0xd7, 0x54, 0x32, 0x7a, 0x00, +0x68, 0x6c, 0xbb, 0x3d, 0xbf, 0xe4, 0xbe, 0x76, +0x21, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0xdd, 0x44, 0xef, 0x34, 0xdb, +0xcb, 0xc9, 0x92, 0xaa, 0x29, 0xde, 0xa7, 0xce, +0xf6, 0xe0, 0xe8, 0x50, 0xe9, 0xbf, 0xee, 0xc2, +0x05, 0xef, 0x12, 0x1b, 0x71, 0x51, 0xaf, 0x0c, +0x05, 0x56, 0x4f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0xf1, 0x2f, 0x11, +0xb8, 0x70, 0x36, 0x42, 0x6e, 0x2a, 0xb8, 0xaa, +0xcd, 0x0c, 0x50, 0x0d, 0x1f, 0xfc, 0xdf, 0x56, +0x7e, 0x59, 0xec, 0x8c, 0xa4, 0x7a, 0x3e, 0x71, +0xa5, 0xcb, 0xbd, 0xa5, 0xf9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0xcb, +0x2c, 0xe8, 0x90, 0x9d, 0x2f, 0x2d, 0x8e, 0x83, +0xf6, 0x8b, 0xff, 0x53, 0xe7, 0xe8, 0x2b, 0x69, +0xbe, 0x11, 0x02, 0x03, 0x1a, 0xe9, 0xc1, 0xbd, +0xc9, 0x2f, 0xdb, 0x84, 0xd8, 0xef, 0x9f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0x75, 0x90, 0xb4, 0x9d, 0xf3, 0xe7, 0xfb, +0x22, 0x10, 0x4d, 0x17, 0xab, 0x76, 0x0d, 0xc7, +0x6e, 0x40, 0x41, 0x17, 0x51, 0x9d, 0xf6, 0x5f, +0xa4, 0xce, 0x46, 0x3f, 0x0f, 0x18, 0x40, 0xaa, +0xaa, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0xe6, 0x10, 0x17, 0xfa, 0xff, +0x7c, 0x64, 0xdd, 0x1c, 0x5f, 0x41, 0x9d, 0x23, +0x1b, 0xd9, 0x62, 0x08, 0xc7, 0x15, 0x7d, 0x4e, +0x15, 0x89, 0x26, 0x33, 0x28, 0x90, 0x35, 0xd4, +0x00, 0x73, 0x15, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0x19, 0xed, 0x49, +0x36, 0x67, 0x29, 0x3c, 0x5c, 0xda, 0x55, 0x5a, +0xd4, 0xe8, 0xdc, 0x52, 0x98, 0xd3, 0xef, 0xaf, +0x5c, 0x36, 0xb2, 0xec, 0x87, 0x51, 0x41, 0x54, +0xa6, 0x9a, 0xe7, 0x56, 0x06, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0x7e, +0xfb, 0x49, 0xea, 0xd1, 0x35, 0xc2, 0x14, 0xb7, +0x25, 0x4c, 0x7c, 0xc4, 0x91, 0xff, 0xe8, 0x86, +0x0c, 0xe9, 0xad, 0x69, 0xbc, 0xa7, 0xfb, 0x36, +0x12, 0xcd, 0xc9, 0xd6, 0xcd, 0xd5, 0xbe, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x95, 0x22, 0x19, 0x09, 0xd7, 0x94, 0x96, +0xd5, 0xe8, 0x25, 0xbb, 0xdf, 0x14, 0x0a, 0x01, +0x8a, 0xac, 0x37, 0x55, 0x8c, 0xf6, 0x07, 0xe4, +0xa6, 0xc3, 0x0b, 0x61, 0x76, 0xf5, 0x69, 0x7a, +0x8b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0x0b, 0xb2, 0xeb, 0x02, 0x75, +0x62, 0xb4, 0xd1, 0x09, 0x5f, 0xdf, 0xc5, 0x4b, +0x9a, 0x58, 0x7e, 0xa6, 0xc5, 0x52, 0xc6, 0xb9, +0x40, 0xdb, 0x96, 0xa8, 0xae, 0x29, 0x06, 0x89, +0x49, 0x98, 0x20, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0x75, 0x22, 0xef, +0x6a, 0xdb, 0xb9, 0x77, 0x89, 0x16, 0xee, 0x2a, +0x50, 0xd2, 0x09, 0xff, 0xa7, 0x31, 0x50, 0x1a, +0x09, 0x1f, 0xe7, 0xf2, 0x85, 0x36, 0x7a, 0xf0, +0x47, 0x72, 0xa0, 0xd7, 0x4c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0xe5, +0x3b, 0x7d, 0x88, 0x7a, 0x75, 0x8b, 0xdf, 0x54, +0xab, 0x04, 0x91, 0x99, 0x79, 0x36, 0x11, 0x0e, +0xaf, 0xa9, 0x47, 0x2c, 0x50, 0xdd, 0xd7, 0x26, +0xd8, 0xe9, 0x99, 0x42, 0x8a, 0xb0, 0x06, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0xa3, 0x6e, 0xb6, 0x90, 0xfe, 0x09, 0x9e, +0xde, 0x8d, 0xae, 0x51, 0x74, 0xd0, 0xec, 0x07, +0xfd, 0xf1, 0xa4, 0x0c, 0xea, 0xd0, 0x3f, 0x28, +0xc8, 0x95, 0x31, 0xdc, 0x26, 0xb4, 0x16, 0x7c, +0xc1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0xe9, 0x66, 0x59, 0x18, 0xb0, +0x82, 0x53, 0xb4, 0xc4, 0x41, 0xa8, 0xfa, 0x93, +0x4b, 0x8f, 0xa1, 0xa6, 0xb6, 0x99, 0x2f, 0x02, +0x59, 0x76, 0x8e, 0x4a, 0x25, 0xad, 0xfc, 0x17, +0x2f, 0x05, 0x2b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0x8b, 0x8d, 0x52, +0xa3, 0xd1, 0xb1, 0x54, 0x8d, 0x15, 0xb2, 0x43, +0x50, 0x13, 0x74, 0x28, 0x80, 0x06, 0xaa, 0x7f, +0xb6, 0x43, 0x36, 0x5b, 0x14, 0xfe, 0x30, 0x82, +0x4c, 0x64, 0x3c, 0x5a, 0xc5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0x95, +0x07, 0xe5, 0x42, 0xb5, 0x44, 0x3d, 0x0b, 0x0b, +0x70, 0x45, 0x3e, 0x16, 0x41, 0xd9, 0x27, 0x32, +0x74, 0x09, 0x9a, 0x88, 0x0c, 0xd0, 0x12, 0xa5, +0x4a, 0x0a, 0x62, 0x15, 0x9c, 0x78, 0xda, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0x11, 0x83, 0x1b, 0x1f, 0x5d, 0x23, 0x51, +0x72, 0x48, 0x58, 0x0d, 0xa2, 0xb0, 0x3d, 0xc4, +0xa8, 0xd2, 0x9c, 0x32, 0xd2, 0xbc, 0x25, 0x32, +0x39, 0x12, 0x71, 0x65, 0xe6, 0x98, 0x5d, 0x0c, +0xac, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0x31, 0x5b, 0x41, 0x7d, 0x66, +0xdb, 0xcb, 0xe0, 0x91, 0xdc, 0xc3, 0x0c, 0xc9, +0x30, 0x99, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xad, 0x76, 0x57, 0xab, +0x58, 0xd9, 0x1b, 0x1c, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0x3f, 0x9c, 0x1a, +0xce, 0x5c, 0x83, 0x80, 0x8a, 0xdf, 0x7d, 0x25, +0xbf, 0x27, 0x87, 0x2e, 0x04, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x49, +0xca, 0x6b, 0x03, 0x61, 0x23, 0x17, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0x6f, +0x77, 0x02, 0x46, 0x57, 0xd3, 0xa0, 0xdc, 0x14, +0xe4, 0x1b, 0x9b, 0x04, 0xbf, 0xf6, 0xe0, 0x37, +0x14, 0x50, 0x93, 0xc3, 0xa1, 0x8b, 0x68, 0x59, +0x45, 0x8a, 0xe7, 0xaa, 0xb7, 0xc2, 0x31, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0xce, 0xa7, 0xd4, 0xb7, 0x73, 0x93, 0x2b, +0x0b, 0x01, 0x5e, 0x65, 0x59, 0x0b, 0xfc, 0x73, +0x25, 0x66, 0xc9, 0xf8, 0x38, 0x49, 0x88, 0xf0, +0x3c, 0xf8, 0x8d, 0x00, 0x29, 0x60, 0xf6, 0xf8, +0x95, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0xa5, 0x64, 0x98, 0xfc, 0x37, +0xba, 0x37, 0x4f, 0x03, 0x80, 0x88, 0xc1, 0x1e, +0xe6, 0xd6, 0x59, 0x41, 0xc3, 0x6f, 0xc6, 0x8b, +0x30, 0xfd, 0xf8, 0xf5, 0x88, 0x7c, 0x6b, 0x33, +0x6e, 0x4c, 0x29, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0x34, 0x92, 0x88, +0xd7, 0xd7, 0x3a, 0x94, 0xb5, 0xce, 0xe5, 0xb9, +0x4b, 0x21, 0xc9, 0x12, 0xdf, 0x1c, 0xa6, 0x00, +0xdd, 0x06, 0x9b, 0x43, 0x1f, 0xc0, 0xf1, 0x95, +0x6a, 0x48, 0x8e, 0xea, 0x8a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0x0c, +0x2d, 0xa5, 0x4c, 0xbd, 0x72, 0x7d, 0xc4, 0x9f, +0x9f, 0x35, 0x71, 0x30, 0x09, 0xd0, 0x98, 0xf3, +0x21, 0xe3, 0xa1, 0x4d, 0xe8, 0xfb, 0x5b, 0xf9, +0xff, 0x81, 0x35, 0x8e, 0x68, 0x07, 0x1d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0xf9, 0x0d, 0x80, 0x03, 0xe5, 0xc9, 0x00, +0xe6, 0xd6, 0x84, 0xca, 0x79, 0xb2, 0xe5, 0x52, +0x8f, 0xdc, 0xd7, 0x29, 0x84, 0xe3, 0x23, 0x9e, +0x41, 0x29, 0x4e, 0x8e, 0x8d, 0x4f, 0x01, 0xd3, +0xe1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0xf9, 0x6a, 0xb4, 0x91, 0x2c, +0xd7, 0x93, 0xed, 0xd1, 0xf6, 0xbb, 0x7d, 0x65, +0xb6, 0x5b, 0x56, 0xf5, 0x08, 0x5d, 0x83, 0xec, +0xa1, 0x31, 0x86, 0x36, 0x1c, 0xcd, 0x56, 0x54, +0x62, 0x49, 0x72, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0x2b, 0x30, 0xea, +0x72, 0x51, 0x30, 0xf0, 0x23, 0x7d, 0x22, 0x00, +0xe3, 0x68, 0x17, 0x96, 0x2d, 0x38, 0x69, 0x4c, +0xc3, 0x71, 0x50, 0xb1, 0x1e, 0xd2, 0x8a, 0xba, +0x4f, 0x31, 0x46, 0x5d, 0xd0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x1e, +0x62, 0x2a, 0x41, 0x4a, 0xcf, 0x68, 0x49, 0xa2, +0x3b, 0x9b, 0x1f, 0x53, 0xdb, 0x91, 0xe1, 0x08, +0x4c, 0xe6, 0x57, 0x3f, 0x6f, 0x45, 0x98, 0x7b, +0x4e, 0x95, 0x8a, 0xfb, 0x79, 0xc6, 0x34, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0x45, 0x6e, 0xb6, 0x5f, 0x0e, 0xa2, 0x5a, +0xd7, 0xc4, 0xf0, 0x29, 0x73, 0x31, 0x6c, 0xf1, +0xf3, 0xc8, 0xe7, 0x31, 0xcd, 0x74, 0x26, 0xf6, +0xe8, 0xd2, 0x6d, 0xc2, 0x6a, 0x29, 0x3c, 0x10, +0x05, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0x6c, 0x99, 0x47, 0xb7, 0xb8, +0xb2, 0x7a, 0xea, 0x5a, 0x89, 0x3b, 0xbf, 0x5f, +0xb3, 0x75, 0x6b, 0x14, 0xa1, 0x39, 0x3f, 0xcb, +0xe3, 0x23, 0x18, 0x91, 0xde, 0x8d, 0x6a, 0x61, +0x26, 0x88, 0x7a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0xb5, 0x97, 0x13, +0xb3, 0x5c, 0xa0, 0xf3, 0xf5, 0x30, 0xb2, 0x87, +0x66, 0x87, 0x39, 0x1e, 0x18, 0x19, 0xa5, 0xb4, +0x95, 0x51, 0x6f, 0xd0, 0x52, 0xa1, 0x93, 0xe5, +0x3d, 0x24, 0x76, 0x55, 0xf8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0xfc, +0xb9, 0xc2, 0x6c, 0x60, 0x2d, 0xd4, 0x47, 0x8a, +0x9b, 0x6d, 0xc6, 0xf7, 0xbd, 0x5d, 0xd7, 0x73, +0x49, 0x81, 0x02, 0x4a, 0x4d, 0x19, 0xc6, 0x26, +0xc3, 0x08, 0x4b, 0xaf, 0x84, 0x16, 0x00, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0x04, 0x48, 0x2b, 0x67, 0xea, 0x18, 0x56, +0xdf, 0x1b, 0xca, 0xf8, 0x54, 0xe2, 0x5a, 0x75, +0x4b, 0x4b, 0x5f, 0x05, 0xdf, 0x06, 0x55, 0x6e, +0x48, 0x3d, 0xed, 0x4f, 0xdd, 0xa0, 0x52, 0x47, +0xa0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0x5b, 0xd1, 0xd6, 0x94, 0xa8, +0xdd, 0x6e, 0x16, 0xf7, 0xac, 0x19, 0x1e, 0x5a, +0x2b, 0xdc, 0x5d, 0x06, 0x6f, 0x81, 0x02, 0xe3, +0xe7, 0x95, 0xa9, 0x43, 0x4a, 0xc9, 0xf0, 0x17, +0xcd, 0x21, 0x75, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0xc5, 0x81, 0xa9, +0x09, 0xfa, 0x9a, 0xe2, 0x02, 0x05, 0x26, 0x2b, +0xae, 0xad, 0x7e, 0x0d, 0x52, 0x91, 0x56, 0x80, +0x98, 0x18, 0xac, 0xc5, 0x1b, 0xb9, 0x68, 0x74, +0x91, 0xfc, 0x1d, 0x60, 0x61, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0xdb, +0x11, 0x9c, 0x38, 0xfa, 0x86, 0x3f, 0x7e, 0xc7, +0x36, 0xac, 0xde, 0x11, 0xdc, 0xfd, 0x19, 0xd7, +0x26, 0x13, 0x80, 0xfc, 0xc6, 0x57, 0xe0, 0xcd, +0xf0, 0x14, 0xd8, 0x8e, 0x35, 0xa1, 0xbf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0x53, 0x0e, 0x56, 0x14, 0x54, 0xcf, 0x03, +0x6a, 0x03, 0xd4, 0x25, 0xdb, 0x27, 0x4b, 0xfb, +0xd1, 0x49, 0x40, 0x7b, 0xf3, 0x9d, 0x76, 0x92, +0xe5, 0xb4, 0xbf, 0x20, 0x5e, 0x66, 0x09, 0x73, +0x9c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0xfe, 0x33, 0xbe, 0x81, 0x29, +0xbb, 0x5e, 0x56, 0xf8, 0xe0, 0x21, 0xa1, 0x00, +0x96, 0x92, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xd6, 0x12, 0xe7, 0xab, +0xb2, 0xdc, 0xd0, 0x28, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x3d, 0xf1, 0x33, +0xa6, 0xfe, 0x3d, 0x5c, 0x01, 0xa4, 0x45, 0x70, +0x44, 0x5b, 0xc2, 0x58, 0x6b, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0x39, +0xbb, 0xed, 0x9c, 0x48, 0x06, 0x6a, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0xca, +0xe3, 0x02, 0x57, 0x75, 0x6b, 0x01, 0xd0, 0xa2, +0x04, 0x58, 0x90, 0x49, 0xa4, 0x60, 0x94, 0x3a, +0xfc, 0xe9, 0xaf, 0x20, 0x3b, 0xb4, 0xaa, 0xa6, +0x92, 0xbd, 0x58, 0xf6, 0x00, 0xa1, 0x16, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0xea, 0x91, 0x63, 0x61, 0x7d, 0x5a, 0xad, +0x29, 0x7b, 0x0a, 0x37, 0xae, 0x64, 0x9e, 0xbf, +0x58, 0x5b, 0xb8, 0x92, 0x83, 0x90, 0xde, 0xce, +0x61, 0xbf, 0x45, 0xd3, 0x07, 0xeb, 0xa0, 0x32, +0x20, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0x37, 0xf9, 0x29, 0x7e, 0x4a, +0xb8, 0x1e, 0x50, 0x66, 0x9e, 0xf9, 0x33, 0x2a, +0x4d, 0x94, 0xe3, 0xe0, 0xb2, 0x71, 0x98, 0xc6, +0xd4, 0xc4, 0x0f, 0x2c, 0xdd, 0xba, 0xab, 0xa5, +0x96, 0xc0, 0x5d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0x37, 0xcf, 0x84, +0xc5, 0x3b, 0xa8, 0x4e, 0x6c, 0x94, 0x67, 0xe8, +0x6f, 0x59, 0x11, 0x36, 0x90, 0xbe, 0xf7, 0xcd, +0xfe, 0x59, 0xfd, 0x41, 0x02, 0x95, 0xc6, 0x86, +0x91, 0xe9, 0xf8, 0x96, 0x5f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x7b, +0x25, 0x0e, 0xa2, 0xd5, 0x1e, 0x3a, 0x53, 0x61, +0xc9, 0xbd, 0x56, 0xf6, 0x48, 0xb7, 0x4a, 0x83, +0xdc, 0x1e, 0x34, 0x2a, 0x83, 0x38, 0xaa, 0x65, +0x4d, 0x89, 0x07, 0x18, 0xc7, 0x99, 0xf6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0x78, 0x8a, 0xe1, 0x26, 0x19, 0x7a, 0x4a, +0x13, 0x69, 0x53, 0x66, 0x18, 0xfd, 0xf1, 0x04, +0xdf, 0x6e, 0x08, 0x1d, 0x5a, 0x57, 0x26, 0x4c, +0x0c, 0xde, 0x56, 0xf0, 0xe5, 0x95, 0xdf, 0xbb, +0x4c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0x83, 0xf8, 0xa2, 0xda, 0x94, +0x90, 0x8c, 0x26, 0x25, 0x9f, 0x85, 0x96, 0x37, +0xc4, 0x2c, 0x32, 0x35, 0xac, 0xfa, 0x87, 0xb1, +0xd0, 0x81, 0xe6, 0xdc, 0x8f, 0x5f, 0x8f, 0xae, +0x37, 0xea, 0x6a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0x23, 0xd6, 0xf2, +0x86, 0xaf, 0x59, 0xf3, 0x52, 0xbd, 0xa9, 0x31, +0xa8, 0x7e, 0xfb, 0xa9, 0xa9, 0x03, 0x68, 0x4f, +0x22, 0xa8, 0x8f, 0x85, 0xb8, 0x35, 0xb3, 0xd6, +0x1c, 0xb9, 0xcb, 0x5c, 0x86, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0xf0, +0x8f, 0xec, 0xde, 0xa7, 0xa2, 0xad, 0x45, 0x24, +0xfc, 0x2f, 0x35, 0x58, 0xf2, 0xd4, 0xde, 0x0c, +0x82, 0x66, 0xb6, 0xcb, 0x7b, 0x10, 0xdb, 0x63, +0xd6, 0xfe, 0xb9, 0xa1, 0x69, 0x45, 0x16, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0xeb, 0xcd, 0xd0, 0x57, 0xd7, 0xc9, 0x1f, +0xf9, 0xf3, 0xca, 0x77, 0x20, 0x71, 0xfb, 0x7e, +0x96, 0xb7, 0xd3, 0xea, 0xb1, 0x7f, 0x50, 0xa4, +0xe2, 0x42, 0x10, 0xdb, 0xc9, 0x59, 0x8c, 0x88, +0x32, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0x71, 0x22, 0xba, 0xdd, 0x5c, +0x0e, 0x46, 0x71, 0x4e, 0xeb, 0x3e, 0x51, 0x68, +0x57, 0x3d, 0x2d, 0x3f, 0xe5, 0xd7, 0xfb, 0x00, +0xeb, 0xaf, 0xcd, 0x58, 0x8d, 0x26, 0x34, 0x09, +0xe7, 0xc4, 0xb5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0x9d, 0xbd, 0xcb, +0x64, 0x6d, 0x8f, 0xf2, 0x8c, 0x8e, 0xf6, 0x28, +0x65, 0xf1, 0x25, 0x16, 0x77, 0x27, 0xa3, 0x6c, +0x8c, 0xf6, 0xd5, 0x40, 0xef, 0x0e, 0x4c, 0x8f, +0x46, 0xce, 0x3c, 0x36, 0x36, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0xa8, +0x9c, 0xdb, 0x40, 0xa2, 0x0c, 0x46, 0xf8, 0x77, +0xa2, 0x21, 0x44, 0x60, 0xcd, 0x73, 0x4f, 0x63, +0xbf, 0x8c, 0x5b, 0x12, 0x75, 0xff, 0x52, 0xbf, +0x95, 0x6e, 0xd6, 0x44, 0xe5, 0x43, 0xa3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0x1e, 0xfd, 0x76, 0x9a, 0xf1, 0xc7, 0xc3, +0x46, 0x08, 0x6b, 0x1c, 0x9f, 0x1c, 0xb1, 0x33, +0x30, 0x7d, 0xcf, 0x50, 0x99, 0x3b, 0x7f, 0x54, +0xff, 0xf1, 0x43, 0xbb, 0x1e, 0xd0, 0xc2, 0xa4, +0x64, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0x38, 0x05, 0x7b, 0x5d, 0x26, +0x31, 0x15, 0x8a, 0xcd, 0xd0, 0x25, 0xd6, 0x82, +0xac, 0x62, 0x2c, 0x2e, 0x19, 0x9f, 0x99, 0x0e, +0x20, 0x94, 0x04, 0xf3, 0x1e, 0xdd, 0xf5, 0xcc, +0x86, 0x4e, 0x59, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0xb4, 0x94, 0x17, +0x7b, 0xd3, 0x27, 0xa1, 0x4f, 0x09, 0x43, 0xdb, +0x7d, 0x77, 0xc1, 0x8a, 0x4a, 0xbe, 0x31, 0xad, +0xd1, 0x77, 0x78, 0x3b, 0x05, 0x86, 0x26, 0x0b, +0x27, 0xa1, 0x98, 0x44, 0x61, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0xce, +0x64, 0xa2, 0x45, 0x5c, 0x70, 0xde, 0x65, 0x61, +0x35, 0xe0, 0x2d, 0xf5, 0x0f, 0x0d, 0xbe, 0xaa, +0x44, 0x54, 0x71, 0x2e, 0x0c, 0x38, 0x3c, 0x2f, +0x89, 0x0d, 0xf3, 0xf2, 0xbf, 0x29, 0x14, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0x31, 0x5e, 0x31, 0x02, 0xff, 0x67, 0x7d, +0x13, 0xf5, 0xc1, 0x64, 0x78, 0x37, 0x99, 0x43, +0xaf, 0x1b, 0xa7, 0x16, 0xa1, 0xfd, 0x79, 0xc2, +0xa1, 0xd2, 0x83, 0x9b, 0x2a, 0x79, 0xf8, 0x5d, +0xbc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0x03, 0x62, 0x61, 0x2d, 0xe4, +0xde, 0x2f, 0x50, 0x96, 0xf2, 0x57, 0x4b, 0x15, +0x1c, 0xd3, 0xed, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x42, 0xcd, 0x20, 0xb4, +0x75, 0xce, 0xe2, 0xf6, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x3d, 0xb4, 0x1c, +0x77, 0xd4, 0xbb, 0x32, 0x58, 0x96, 0xe3, 0xd3, +0x44, 0x9d, 0xd0, 0x5d, 0x3b, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0xc0, +0x24, 0x48, 0x6b, 0x34, 0x2b, 0x80, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0xd0, +0x95, 0xc2, 0xf4, 0x74, 0x8d, 0x67, 0xe6, 0x07, +0x9b, 0x8d, 0xc7, 0xad, 0x56, 0xfd, 0x13, 0x3f, +0xd6, 0x81, 0xe5, 0x9f, 0x29, 0xfc, 0x83, 0x4f, +0x85, 0x44, 0x0d, 0x13, 0x3c, 0xb6, 0xce, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x96, 0x93, 0xd7, 0x7c, 0xf9, 0xe4, 0xb7, +0xcd, 0xc7, 0x23, 0x26, 0xec, 0x8b, 0x1e, 0xb8, +0x89, 0xe1, 0x3e, 0xf6, 0x7d, 0xe1, 0x0d, 0x25, +0x9d, 0x10, 0xcd, 0xc3, 0x69, 0x2b, 0x87, 0x75, +0xcc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0x5b, 0xa9, 0x64, 0x2f, 0x3f, +0xf4, 0xbe, 0x67, 0xcb, 0xa6, 0xf7, 0xd1, 0x98, +0xcd, 0xf4, 0x27, 0x5c, 0x93, 0x49, 0x28, 0xc2, +0x71, 0xc7, 0x29, 0x0f, 0xc5, 0xd6, 0x85, 0xf3, +0xd8, 0x27, 0x4e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0xf2, 0x9a, 0x60, +0xff, 0xb4, 0x95, 0xf7, 0xc0, 0x4e, 0xa8, 0x16, +0x6b, 0x81, 0x03, 0xd7, 0xd9, 0x50, 0xc2, 0xbc, +0x26, 0x39, 0xf8, 0xbd, 0xbe, 0xe0, 0x31, 0x5e, +0x05, 0x81, 0xf0, 0xac, 0xf1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0x32, +0x66, 0xc1, 0x3c, 0xa2, 0x83, 0xf3, 0xa3, 0x0a, +0xbe, 0x91, 0x39, 0xd0, 0xe1, 0x16, 0xd6, 0xda, +0x51, 0x84, 0xf9, 0xe0, 0x45, 0x9b, 0x3a, 0x4e, +0x23, 0x3b, 0x2b, 0x60, 0xf1, 0x04, 0xe6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0x65, 0x95, 0x04, 0x71, 0x2b, 0x50, 0xef, +0xc7, 0x97, 0x59, 0x7c, 0x99, 0x32, 0xa8, 0x1d, +0xd0, 0x3a, 0x50, 0x8b, 0x52, 0x4e, 0xe5, 0xcd, +0xc9, 0x99, 0x9e, 0xde, 0xb5, 0x86, 0x38, 0xea, +0x2a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0x05, 0x01, 0xe0, 0xb3, 0xf0, +0x1f, 0xaf, 0x04, 0x04, 0xbd, 0x72, 0x5e, 0x64, +0x51, 0xa4, 0x37, 0x10, 0x52, 0xe5, 0xed, 0xed, +0x8d, 0xbc, 0xb4, 0x15, 0xea, 0x2e, 0x25, 0x24, +0xe5, 0x95, 0xba, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0x88, 0x61, 0xe5, +0xa2, 0xa4, 0xaa, 0xa8, 0xaa, 0x6c, 0xc0, 0xd2, +0x1c, 0x93, 0x74, 0xf0, 0x92, 0x52, 0xea, 0x07, +0x85, 0xb3, 0x18, 0xf9, 0xf1, 0x56, 0x14, 0x17, +0x0d, 0x0a, 0xfc, 0x69, 0xe5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0xc0, +0x26, 0xde, 0x29, 0x54, 0x76, 0x8f, 0x28, 0x9c, +0x3a, 0x89, 0xe8, 0x69, 0xd7, 0x42, 0xd1, 0x45, +0x69, 0x3f, 0x28, 0xbd, 0x52, 0x1a, 0x87, 0xd8, +0x76, 0x08, 0x18, 0x13, 0x12, 0x14, 0x16, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0x98, 0x2b, 0xc4, 0xa3, 0x2d, 0x11, 0xd2, +0x42, 0x14, 0x30, 0x5c, 0x3b, 0x7e, 0xed, 0x5f, +0x6c, 0xa1, 0xd4, 0xf5, 0xda, 0x1f, 0x6d, 0xd6, +0xdf, 0x40, 0xab, 0xfd, 0xe7, 0x51, 0x19, 0x62, +0x3e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0xd8, 0xb5, 0x74, 0x1c, 0x9f, +0x08, 0x11, 0x0e, 0x62, 0xc7, 0x91, 0x77, 0xe8, +0x74, 0x31, 0x2f, 0x37, 0xe4, 0x74, 0x71, 0xab, +0x63, 0x09, 0x1d, 0xad, 0xd6, 0xf1, 0x2d, 0x11, +0x9e, 0xf6, 0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0x52, 0xce, 0x27, +0x0d, 0xe0, 0xf5, 0xde, 0x2c, 0x62, 0x65, 0xa8, +0x1d, 0x89, 0xd0, 0xe2, 0x54, 0xc2, 0x70, 0x55, +0x5c, 0xef, 0x6f, 0xfb, 0x54, 0x8f, 0xcd, 0x0a, +0xd5, 0x30, 0xd3, 0x0b, 0x1c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0x74, +0x01, 0x4e, 0x9f, 0xea, 0x34, 0x87, 0xd1, 0x3a, +0xd7, 0x5d, 0xd2, 0x26, 0xc7, 0x93, 0x8e, 0xc5, +0x11, 0x05, 0xf1, 0x4e, 0x3f, 0x77, 0xa2, 0x47, +0xd3, 0xd8, 0xd2, 0x68, 0x6c, 0xf2, 0x22, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0x24, 0x7e, 0xa1, 0x85, 0x7a, 0x9d, 0x69, +0xe7, 0x65, 0x42, 0x49, 0x27, 0x01, 0x0b, 0xa9, +0x02, 0xf4, 0x55, 0x02, 0x7d, 0x40, 0x1b, 0xc0, +0xa7, 0xc3, 0x11, 0x13, 0x71, 0x49, 0x70, 0x3a, +0x4a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0x58, 0x15, 0x90, 0xca, 0xc8, +0x0a, 0x64, 0xa5, 0xed, 0xb6, 0x84, 0x87, 0xb2, +0x5a, 0x55, 0x0f, 0x7c, 0x35, 0xfd, 0xc8, 0xcb, +0x17, 0x1d, 0xa5, 0x58, 0x24, 0x6d, 0xe5, 0xdd, +0x42, 0x55, 0x5d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0xbc, 0xde, 0xee, +0x6c, 0x8c, 0x12, 0x3a, 0x57, 0x1c, 0xad, 0x3d, +0x70, 0x69, 0xbf, 0xe7, 0x8a, 0x32, 0xe6, 0xa3, +0xa5, 0x2c, 0xdd, 0xda, 0xaa, 0x7d, 0x94, 0x77, +0x37, 0xca, 0x1f, 0xaf, 0xce, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0x9c, +0x1d, 0x80, 0x80, 0x3a, 0x96, 0x9a, 0x87, 0xf1, +0x7f, 0xa7, 0xed, 0x2b, 0x05, 0xa7, 0x32, 0xb5, +0xe8, 0x93, 0x63, 0x3b, 0xd1, 0x8e, 0xb7, 0x36, +0x92, 0x83, 0x35, 0x9e, 0x46, 0x87, 0x7f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0xc0, 0x6f, 0xf3, 0x65, 0xae, 0xac, 0x0e, +0x8c, 0xe7, 0xfe, 0xb0, 0x87, 0xbd, 0x29, 0x12, +0x15, 0x76, 0xd7, 0x01, 0xe4, 0xeb, 0xb9, 0x91, +0xe7, 0xa5, 0xd1, 0xb7, 0x09, 0xf7, 0x07, 0xcb, +0xc6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0x00, 0x2a, 0xd0, 0xb8, 0xc3, +0x9b, 0xe4, 0xae, 0xb4, 0x4f, 0x1b, 0x21, 0x85, +0x83, 0x97, 0xb3, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x2b, 0x82, 0x35, 0x04, +0x81, 0xe0, 0x62, 0x8b, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0xac, 0x38, 0x54, +0x28, 0xee, 0x8d, 0x15, 0xa9, 0x5f, 0x6f, 0xcd, +0x9e, 0x16, 0x90, 0xc3, 0xf7, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf9, 0xbe, +0x6e, 0x2a, 0x41, 0x9b, 0xf4, 0xe3, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0x85, +0xfd, 0xdb, 0x11, 0x71, 0x99, 0xf4, 0x38, 0xa0, +0x0a, 0xf4, 0xe2, 0xb8, 0xb7, 0x41, 0x79, 0xa4, +0x79, 0x84, 0x56, 0x29, 0x36, 0x9d, 0x44, 0xa1, +0xde, 0x40, 0xf6, 0x85, 0x23, 0x4a, 0x9b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0x2f, 0xb2, 0x6b, 0x37, 0x14, 0xf1, 0x40, +0x65, 0x86, 0x44, 0x8e, 0x23, 0x01, 0xc8, 0x6e, +0x12, 0x28, 0xd6, 0xb4, 0xc1, 0xbd, 0x7d, 0x38, +0xb9, 0xa0, 0x15, 0xff, 0xd7, 0xe4, 0xf7, 0x0e, +0xba, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0x78, 0x29, 0xf8, 0x33, 0x7c, +0xcc, 0x34, 0xf4, 0x2e, 0xf9, 0xbc, 0xe1, 0xc7, +0xb9, 0x0b, 0x9b, 0xe4, 0x43, 0x56, 0x5d, 0x65, +0xd7, 0xb5, 0x2c, 0x45, 0xec, 0x25, 0xe2, 0x1c, +0xd0, 0x96, 0x4a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0x88, 0xac, 0xf5, +0x21, 0xaf, 0x1d, 0xde, 0xbf, 0xef, 0x3a, 0x7d, +0xac, 0x28, 0x80, 0x2e, 0xe0, 0x0d, 0x49, 0x56, +0x38, 0x8d, 0x65, 0xd4, 0xc5, 0xe2, 0x92, 0xbf, +0x27, 0x06, 0x38, 0x5c, 0x8e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0x3f, +0x86, 0x2d, 0xc7, 0xc6, 0xe7, 0x57, 0x29, 0xe1, +0xed, 0xf0, 0x1d, 0x87, 0x87, 0xb7, 0x26, 0xcb, +0xfb, 0x0c, 0x1a, 0x9d, 0x47, 0x5c, 0xa3, 0x3a, +0x57, 0x81, 0xc5, 0x1c, 0x5a, 0x19, 0xf3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0x25, 0x74, 0x18, 0x27, 0xed, 0x20, 0x7a, +0x47, 0xf0, 0xeb, 0x47, 0x0c, 0xd2, 0xc5, 0x49, +0x9a, 0xd1, 0x3e, 0x36, 0xfe, 0x46, 0x08, 0xf4, +0x01, 0x0e, 0xe1, 0x6c, 0x2c, 0x1d, 0x39, 0xb6, +0xeb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0x5d, 0x2d, 0x47, 0x69, 0x34, +0x62, 0xb5, 0xa2, 0x6d, 0x59, 0x9e, 0xdb, 0x16, +0x6b, 0x65, 0xed, 0xa1, 0x3c, 0xee, 0xf3, 0xd3, +0x6a, 0x1c, 0x63, 0xf5, 0x00, 0x65, 0x19, 0x30, +0x0e, 0x56, 0x6b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0x0f, 0xe0, 0x79, +0x1e, 0x94, 0xbf, 0x04, 0x1b, 0xd5, 0x2e, 0x28, +0xa6, 0x06, 0x8d, 0xb2, 0xa1, 0xa4, 0xbf, 0x1e, +0xc1, 0x63, 0xf9, 0x1d, 0x2f, 0xbf, 0x23, 0x94, +0xda, 0x7b, 0x2c, 0xe6, 0x76, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0x40, +0x9c, 0xae, 0x87, 0xfa, 0x4a, 0x66, 0xc9, 0x0b, +0xdd, 0xb7, 0xc7, 0x92, 0x9c, 0xc7, 0xfb, 0x20, +0x60, 0x4b, 0xb3, 0x8e, 0x63, 0xa6, 0x8b, 0x0f, +0x91, 0x64, 0xe6, 0x49, 0xa7, 0x56, 0x75, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0x43, 0x98, 0x5e, 0xe2, 0xb8, 0x8d, 0xdd, +0x37, 0xd8, 0x82, 0x6b, 0xfd, 0x18, 0x28, 0x66, +0x2d, 0x4b, 0x26, 0xb5, 0xdd, 0xaf, 0x06, 0x0c, +0x81, 0x94, 0x2a, 0x7c, 0x10, 0x7d, 0xc0, 0xfa, +0xad, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0x67, 0x26, 0x39, 0xe4, 0xc3, +0xa6, 0xbb, 0x9c, 0xa4, 0x54, 0x71, 0xf8, 0xf3, +0x8e, 0x08, 0x8f, 0xe6, 0xe0, 0x6c, 0x5e, 0xb1, +0xb2, 0x76, 0xbd, 0xfb, 0x28, 0x81, 0x7b, 0x52, +0xc1, 0x01, 0x1a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0x6c, 0x97, 0x0f, +0xe5, 0x1f, 0x0c, 0x43, 0xd0, 0x79, 0xb9, 0xe7, +0x3c, 0xd1, 0xe8, 0xb3, 0x59, 0xc7, 0x17, 0x19, +0x9b, 0x15, 0x39, 0x8c, 0x4e, 0x05, 0xc8, 0x44, +0x73, 0xa2, 0x38, 0xb8, 0x03, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0x01, +0x34, 0xbb, 0x9f, 0x4c, 0xf0, 0x66, 0x77, 0x46, +0xad, 0x4c, 0x6f, 0xda, 0x5b, 0x66, 0xab, 0x14, +0xe7, 0x48, 0x1f, 0x73, 0x9d, 0x5c, 0xe9, 0xb5, +0xc2, 0xc5, 0x7b, 0x89, 0xa0, 0xeb, 0xf5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0xf9, 0xad, 0xd2, 0x84, 0x80, 0x66, 0x65, +0x7a, 0xfa, 0xa9, 0x43, 0x88, 0x90, 0x1e, 0xfd, +0x2e, 0xe9, 0x39, 0xda, 0xc7, 0x86, 0x69, 0x7d, +0xcb, 0x33, 0x2f, 0x18, 0xaf, 0xf6, 0xa4, 0x63, +0xec, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0x9e, 0xca, 0x44, 0xac, 0xfa, +0x48, 0xbf, 0xc0, 0xcb, 0x9e, 0xc1, 0x52, 0x62, +0x71, 0xdc, 0xb8, 0x61, 0xfa, 0x5a, 0x9e, 0x63, +0xbb, 0x4d, 0x87, 0xdb, 0xaf, 0x0d, 0xc4, 0x43, +0x56, 0x7b, 0x60, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x33, 0xd0, 0xdf, +0x15, 0x3b, 0xb8, 0x23, 0x27, 0x29, 0x06, 0xd6, +0x20, 0xb1, 0xbf, 0x17, 0x39, 0xb1, 0x8a, 0x5b, +0x6a, 0x6b, 0xcb, 0x6c, 0xbf, 0x8d, 0x62, 0xfe, +0x38, 0x34, 0xda, 0x7b, 0x9b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0xb1, +0x7c, 0xb9, 0x01, 0xf7, 0x70, 0x85, 0xa1, 0xdd, +0x76, 0x4a, 0x0a, 0x55, 0x98, 0xaf, 0x6f, 0x8c, +0xe7, 0x33, 0x19, 0x0f, 0x53, 0x0a, 0xad, 0x6e, +0xf3, 0xb3, 0x2e, 0xc7, 0x72, 0xe1, 0xf9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x82, 0xaa, 0x13, 0x1e, 0x1b, 0xea, 0x69, +0xdd, 0x51, 0x6a, 0x94, 0x79, 0x4a, 0x9a, 0x53, +0x93, 0xa9, 0x4e, 0x62, 0x46, 0x9b, 0x87, 0xe8, +0x2f, 0x13, 0xc7, 0x1d, 0x3f, 0xf1, 0x00, 0xf5, +0xa7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0x76, 0xa0, 0x30, 0x26, 0x48, +0xdd, 0x4f, 0x33, 0x8f, 0xcb, 0xe9, 0xb9, 0x51, +0x2c, 0x6a, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x78, 0xee, 0x5a, 0x74, +0xa1, 0x6d, 0x04, 0x1d, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0xc8, 0x68, 0xbd, +0xeb, 0x50, 0xcb, 0x11, 0xdf, 0x12, 0xf6, 0x3b, +0xfc, 0x77, 0x64, 0xeb, 0xc7, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xda, +0x3d, 0x78, 0xe1, 0xfb, 0xa2, 0x5b, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x11, +0x71, 0xd0, 0xd6, 0x92, 0x80, 0x24, 0x48, 0xe4, +0x37, 0x8b, 0xba, 0x6f, 0xf1, 0x7f, 0x23, 0xd0, +0x79, 0x17, 0xa0, 0xce, 0x4a, 0xf1, 0xfa, 0x2f, +0x16, 0xc9, 0xdb, 0xb1, 0x69, 0xed, 0x44, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0x1a, 0x41, 0x07, 0x3c, 0x0e, 0x20, 0x4a, +0xad, 0xad, 0x97, 0xba, 0xa1, 0x8e, 0x8f, 0x7c, +0x72, 0xfc, 0x9f, 0xe0, 0xad, 0x3f, 0xaf, 0x88, +0x26, 0x69, 0xb3, 0x3c, 0xed, 0xca, 0x43, 0x9b, +0xca, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0x99, 0x13, 0x29, 0x94, 0x1c, +0x17, 0x70, 0xed, 0x2b, 0xaa, 0x35, 0x46, 0x3d, +0x84, 0xc9, 0xff, 0x71, 0xa8, 0x94, 0xe6, 0x36, +0x37, 0xcb, 0xff, 0x1f, 0xe3, 0x59, 0x19, 0xd2, +0xa4, 0x3a, 0xbe, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0xf9, 0x97, 0xc2, +0x7e, 0xd1, 0x49, 0xdc, 0x7c, 0xde, 0xac, 0x0b, +0xb1, 0x08, 0x04, 0xfd, 0xeb, 0xfe, 0xbb, 0xfb, +0x67, 0xfa, 0xb2, 0xa4, 0xb4, 0x18, 0x10, 0xb9, +0x2d, 0x67, 0x18, 0xdc, 0x33, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0xf1, +0xea, 0xb8, 0x2a, 0xd4, 0x4d, 0x64, 0x33, 0xd2, +0xb3, 0xc3, 0xf5, 0x38, 0x7e, 0x0d, 0x4a, 0x76, +0x43, 0xdf, 0x23, 0x5b, 0x89, 0x22, 0xd9, 0x44, +0xf5, 0xf4, 0xe3, 0xcd, 0x09, 0xa1, 0x5d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0x19, 0x63, 0x07, 0xc7, 0x4f, 0x41, 0xfc, +0x29, 0x75, 0x55, 0xb3, 0xa5, 0x4e, 0xa9, 0x87, +0x2c, 0x82, 0x75, 0xd5, 0xa0, 0x3d, 0x2c, 0xc3, +0x36, 0xb4, 0xd0, 0x58, 0x33, 0x01, 0xe5, 0x16, +0x6d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0x57, 0x9d, 0xb8, 0xbc, 0xf2, +0xf1, 0x64, 0x05, 0xcf, 0x40, 0xae, 0xc2, 0xb1, +0xd3, 0x93, 0x53, 0x46, 0xe0, 0xac, 0x1f, 0x09, +0xf8, 0xda, 0xae, 0xa2, 0xaf, 0x63, 0x8c, 0x37, +0x9d, 0x0e, 0x27, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0xb9, 0x76, 0xce, +0x70, 0x38, 0x1a, 0x49, 0xcf, 0x7f, 0xb9, 0x59, +0x1c, 0x9f, 0x2d, 0x08, 0x18, 0x04, 0x65, 0x96, +0xea, 0x8e, 0x7c, 0x98, 0x38, 0xe4, 0xd4, 0xdd, +0x78, 0xd7, 0x44, 0x6d, 0x2d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x9a, +0x61, 0x1f, 0x65, 0x1d, 0x3d, 0xd5, 0x91, 0xed, +0x63, 0xdc, 0xac, 0x43, 0xc9, 0x13, 0xaf, 0xa1, +0xa2, 0xb7, 0xb7, 0xd8, 0xc1, 0x90, 0xc6, 0x56, +0x96, 0x08, 0x3c, 0x3f, 0x37, 0x7a, 0x4b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0x38, 0xd6, 0xde, 0x82, 0xae, 0xa1, 0xab, +0x1a, 0x8f, 0xc8, 0xde, 0x91, 0xa1, 0x99, 0x7e, +0xa0, 0xf9, 0x05, 0x3e, 0xb5, 0xf9, 0x42, 0x8b, +0x4a, 0xde, 0xb6, 0xeb, 0x11, 0x7a, 0x31, 0x5e, +0x26, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0xd7, 0x49, 0xcc, 0xd4, 0x0e, +0xae, 0x2c, 0x44, 0x98, 0x27, 0xfb, 0xad, 0x5b, +0x98, 0xdc, 0x25, 0x93, 0x62, 0x19, 0x39, 0x02, +0x4a, 0xd0, 0x53, 0xeb, 0x3d, 0x72, 0x84, 0xde, +0x34, 0x21, 0x95, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x14, 0x1e, 0xf0, +0x07, 0x59, 0xc7, 0x54, 0x0d, 0xdb, 0xbb, 0x6c, +0x59, 0x6b, 0x86, 0xcc, 0x94, 0x28, 0xfb, 0xf4, +0x65, 0x59, 0x43, 0x82, 0xd8, 0xea, 0xaf, 0x91, +0x51, 0xc5, 0x8d, 0x9a, 0x16, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0xe4, +0x9e, 0x30, 0xf9, 0x66, 0x96, 0x33, 0x00, 0x33, +0xd0, 0x31, 0x05, 0xb1, 0x5c, 0xa2, 0xd2, 0xfa, +0xb7, 0xbd, 0x84, 0x77, 0xad, 0xd4, 0x1f, 0xf5, +0xc0, 0xdc, 0xce, 0xe2, 0xad, 0x65, 0x93, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0x3a, 0x08, 0xd9, 0xe0, 0x99, 0x40, 0xd1, +0x95, 0x6a, 0x22, 0x38, 0x6d, 0x88, 0x17, 0x50, +0xd5, 0xa2, 0x17, 0x0a, 0x10, 0x8b, 0xe5, 0x20, +0xb3, 0xf5, 0x39, 0x9b, 0x45, 0x95, 0x24, 0xc7, +0x8e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0xee, 0xa4, 0x73, 0x89, 0xe1, +0x20, 0x48, 0x72, 0xa9, 0x3d, 0xef, 0x69, 0xa9, +0x1b, 0x5d, 0xff, 0xbd, 0x22, 0xd3, 0xfe, 0xf8, +0x8f, 0xda, 0x57, 0x74, 0x85, 0xa6, 0xd3, 0x62, +0x21, 0xa8, 0x15, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0x24, 0x83, 0xf9, +0x27, 0x3a, 0x2d, 0xd0, 0x73, 0xb1, 0xd7, 0xbe, +0x41, 0x23, 0x64, 0x32, 0xce, 0x65, 0xef, 0xda, +0x3e, 0x50, 0x6d, 0x5f, 0x77, 0x08, 0x27, 0x8e, +0x70, 0x14, 0x61, 0xa1, 0xe8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0x52, +0x00, 0x1f, 0x54, 0x75, 0xdd, 0x42, 0xf7, 0x31, +0xc4, 0xe7, 0x50, 0x16, 0x61, 0x1b, 0x48, 0x11, +0x58, 0x1f, 0x9b, 0x49, 0xad, 0x09, 0x31, 0x5d, +0x8c, 0xb9, 0x94, 0xa1, 0xcf, 0x81, 0x69, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x88, 0x7c, 0x0b, 0xe7, 0x35, 0xa9, 0xb1, +0x26, 0xbf, 0x06, 0xd3, 0x10, 0xd3, 0x55, 0x8b, +0x25, 0x1e, 0xd1, 0xb6, 0xc4, 0xf5, 0xeb, 0xca, +0xae, 0x1a, 0xbc, 0xa0, 0xce, 0x57, 0xac, 0x74, +0xf7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x3c, 0xdf, 0xd6, 0x80, 0x99, +0x55, 0xe7, 0xd4, 0xcb, 0x89, 0x18, 0xd1, 0x9f, +0x4a, 0x79, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x64, 0xc3, 0xee, 0x8d, +0xff, 0x0f, 0x59, 0x3f, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0x42, 0x6c, 0x44, +0x56, 0xdc, 0x3b, 0x4c, 0x5d, 0xc1, 0xa3, 0x20, +0xe6, 0x3e, 0x82, 0xef, 0xba, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9a, 0x76, +0x54, 0x45, 0xfe, 0x6d, 0x7f, 0x14, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0xa4, +0x79, 0xd6, 0xad, 0x19, 0x84, 0x12, 0x4b, 0xfa, +0xae, 0xff, 0x8a, 0xaa, 0xa6, 0x55, 0x58, 0x40, +0xb4, 0xf7, 0x78, 0xfd, 0x95, 0xe7, 0x0c, 0x93, +0x5b, 0x48, 0x56, 0x8d, 0xeb, 0x70, 0x0a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0xeb, 0x10, 0x1f, 0x54, 0x66, 0x47, 0x3c, +0xfb, 0x9d, 0xee, 0x39, 0x4d, 0xfa, 0xa9, 0x9d, +0x55, 0x87, 0x4b, 0x63, 0x35, 0x80, 0xc5, 0xac, +0x30, 0xf5, 0xac, 0xea, 0x3f, 0xcc, 0x28, 0xc8, +0x7f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0x17, 0x11, 0xd9, 0xeb, 0x65, +0x84, 0x79, 0x16, 0xac, 0xe5, 0xb4, 0x3a, 0xa4, +0x15, 0x3f, 0x7e, 0xb1, 0x67, 0xd5, 0x8a, 0x66, +0x27, 0x9b, 0x37, 0x66, 0xdd, 0x49, 0x46, 0x71, +0x18, 0x2f, 0xb0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0x90, 0xf3, 0xb2, +0xda, 0x88, 0x46, 0x03, 0x8c, 0x9d, 0x9c, 0x4a, +0x90, 0x24, 0xc1, 0xfc, 0xe7, 0x2c, 0xfb, 0xf2, +0x7d, 0xa8, 0x09, 0x9a, 0xf5, 0x17, 0x96, 0x23, +0x91, 0x22, 0x68, 0xd9, 0x16, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x2c, +0x47, 0x53, 0x00, 0x78, 0xd0, 0xd8, 0xb8, 0x81, +0xed, 0xd1, 0x07, 0x06, 0x6e, 0x23, 0xb3, 0x50, +0xdc, 0xf7, 0x21, 0x97, 0x3e, 0x03, 0xc1, 0x57, +0x01, 0xf0, 0x3e, 0x1b, 0xe7, 0x94, 0x97, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0x4b, 0xef, 0xcd, 0x46, 0xb1, 0xe4, 0x25, +0xfa, 0x04, 0xab, 0xed, 0x33, 0xd2, 0xbc, 0x84, +0xdc, 0x13, 0xda, 0xdb, 0x79, 0x7e, 0x05, 0xa7, +0x17, 0xbb, 0xcb, 0x96, 0x4a, 0x92, 0xeb, 0x97, +0x9a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0x22, 0x57, 0x57, 0x03, 0x39, +0x5d, 0x56, 0xa0, 0xe7, 0x14, 0x34, 0x50, 0x6d, +0x8f, 0x4c, 0xc4, 0x96, 0x56, 0xbd, 0x88, 0x0e, +0x6a, 0xd7, 0xdd, 0x67, 0x0c, 0x3b, 0x76, 0xba, +0x63, 0xa2, 0x02, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0xc9, 0x81, 0x24, +0xe6, 0xee, 0x3b, 0xba, 0x25, 0x10, 0xce, 0xb4, +0xd0, 0x3a, 0xf0, 0x37, 0x3d, 0xa7, 0x1f, 0xc8, +0x32, 0xb0, 0x4a, 0x16, 0x78, 0x12, 0xf9, 0x40, +0x5b, 0x34, 0xfe, 0xe9, 0xfe, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0x99, +0xc0, 0xbf, 0xe2, 0x92, 0x9c, 0x93, 0xf3, 0x49, +0xaf, 0x74, 0x7a, 0xcf, 0xb7, 0x1d, 0xcb, 0xf5, +0x8c, 0x98, 0x25, 0xc1, 0x40, 0x82, 0xca, 0x17, +0x23, 0x04, 0xb8, 0x26, 0x34, 0xeb, 0x8c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0xe6, 0x7f, 0x2e, 0x7f, 0x2c, 0x43, 0x75, +0x50, 0x98, 0x2c, 0xa3, 0x0a, 0xc7, 0xe6, 0x2e, +0xc5, 0x7c, 0x3f, 0x7a, 0x93, 0x41, 0xc9, 0x86, +0x69, 0x7d, 0x29, 0x7b, 0xc0, 0xea, 0xd5, 0xa4, +0xe7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0xf1, 0x80, 0xeb, 0x8f, 0x1f, +0x62, 0x6d, 0x70, 0x99, 0xf5, 0x32, 0x50, 0xa4, +0x07, 0xf3, 0xfc, 0x00, 0xb2, 0xde, 0xc3, 0x28, +0xa5, 0x38, 0xd4, 0xb9, 0x62, 0xa8, 0x8d, 0x58, +0x0c, 0x48, 0xe9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0x0c, 0x59, 0x62, +0x4e, 0x34, 0xd9, 0x65, 0x9e, 0x30, 0x50, 0x61, +0x26, 0xcb, 0x7b, 0x05, 0x62, 0x85, 0x9b, 0x08, +0x91, 0x5d, 0x8d, 0x16, 0xa2, 0xce, 0x2f, 0x8b, +0x49, 0x94, 0x5d, 0x11, 0xf3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0x75, +0x99, 0x72, 0xc7, 0xc5, 0x05, 0xab, 0x8c, 0x06, +0x76, 0x4e, 0x16, 0xfc, 0x81, 0x54, 0xb4, 0xa9, +0xce, 0xf8, 0xf8, 0x97, 0xc3, 0x64, 0x61, 0x70, +0xaa, 0x41, 0x0a, 0x14, 0x22, 0xa0, 0xd7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0x61, 0x47, 0xf7, 0x22, 0x9a, 0x85, 0x45, +0x3e, 0xd1, 0xe5, 0x73, 0x9c, 0x30, 0x64, 0x4c, +0xa0, 0x62, 0x40, 0xdd, 0x42, 0xa1, 0xbc, 0x24, +0x94, 0x65, 0x78, 0xbe, 0x60, 0x63, 0x4f, 0x34, +0x40, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0x83, 0xbb, 0x06, 0x15, 0x6c, +0x77, 0xe8, 0x22, 0xee, 0x9d, 0xb4, 0x0b, 0x74, +0xfb, 0x06, 0x1d, 0x0b, 0x97, 0x6b, 0x66, 0x9f, +0xdc, 0x6e, 0xe5, 0xa6, 0x72, 0x87, 0xaf, 0xe2, +0x8d, 0xf8, 0xad, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0xff, 0x74, 0x51, +0x8a, 0x81, 0xa4, 0x42, 0xd8, 0x73, 0xca, 0x59, +0xab, 0xa7, 0x69, 0x66, 0x02, 0x9f, 0x34, 0xeb, +0x72, 0x25, 0xc5, 0x17, 0x72, 0x8e, 0x6a, 0xf2, +0xb1, 0x77, 0xd6, 0xe5, 0x66, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0x50, +0x62, 0xc6, 0x06, 0xc6, 0xd6, 0x59, 0xd5, 0x11, +0x73, 0x79, 0x82, 0xe4, 0x29, 0x93, 0x59, 0x1d, +0x88, 0x71, 0xb7, 0xad, 0xd5, 0x68, 0xdf, 0x90, +0x14, 0xa4, 0x43, 0xd4, 0x38, 0x0f, 0x28, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0x5b, 0xb3, 0x2c, 0x81, 0x68, 0x53, 0x81, +0x06, 0x7a, 0x09, 0xa7, 0x38, 0x3f, 0x0f, 0xe9, +0x3f, 0x16, 0x5e, 0x69, 0xdb, 0x4b, 0x78, 0x7e, +0xb9, 0x1b, 0xa9, 0x75, 0xd1, 0x54, 0xbe, 0xb6, +0xef, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0x49, 0x10, 0xd0, 0xcf, 0x2e, +0x5a, 0x22, 0x32, 0x62, 0xde, 0xf2, 0x5a, 0xaa, +0x38, 0x08, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x5e, 0xa3, 0x22, 0xc0, +0x4a, 0x85, 0xbf, 0xf2, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0xba, 0x89, 0x34, +0xef, 0x4c, 0x4c, 0x12, 0x22, 0x51, 0x4d, 0x8e, +0x45, 0xc1, 0xc0, 0x62, 0x81, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x63, +0x52, 0x6f, 0x31, 0xd2, 0xff, 0x16, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0x24, +0x94, 0xe5, 0xb1, 0xff, 0xf0, 0xa4, 0xc4, 0x14, +0xd0, 0x8a, 0xbe, 0x76, 0xc8, 0x17, 0x72, 0xee, +0x01, 0x27, 0x54, 0x84, 0x8b, 0x34, 0xcc, 0xf3, +0xe3, 0x9f, 0xda, 0x62, 0x32, 0xe0, 0xf3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0xbd, 0xba, 0xc3, 0x23, 0x2b, 0xf3, 0x57, +0x13, 0x6f, 0xfd, 0xf4, 0x4e, 0x4b, 0xa4, 0x6c, +0xa9, 0xbd, 0x8c, 0xce, 0x2e, 0x05, 0x7e, 0x70, +0x38, 0x55, 0x0b, 0x5c, 0x15, 0x0b, 0x6e, 0x62, +0x46, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0xa3, 0x93, 0x53, 0x05, 0x81, +0x8e, 0x2f, 0x01, 0xbb, 0x31, 0x3e, 0x60, 0xde, +0x37, 0x78, 0xd9, 0xb3, 0x88, 0x46, 0x81, 0xf2, +0x9b, 0x0c, 0x4f, 0xc7, 0xc7, 0xfa, 0x26, 0xda, +0xcf, 0x52, 0xda, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0x37, 0xe5, 0xe3, +0x22, 0x12, 0xdb, 0x2a, 0xf0, 0x42, 0x43, 0x82, +0x51, 0xbd, 0x32, 0xfc, 0x61, 0xb1, 0x2a, 0x65, +0xb4, 0x54, 0x19, 0xd2, 0x37, 0xb4, 0xb4, 0xcd, +0x42, 0x01, 0x28, 0x44, 0xd1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0x3d, +0x3a, 0xe9, 0x82, 0x84, 0x08, 0x70, 0xf2, 0x65, +0x85, 0xcd, 0x87, 0x0f, 0x16, 0x27, 0x9c, 0x9f, +0x74, 0xa0, 0x17, 0x4d, 0xc1, 0x6f, 0xd8, 0xb6, +0x52, 0x65, 0x99, 0x08, 0x4f, 0x3d, 0x91, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x87, 0xb9, 0x99, 0x14, 0x64, 0x2b, 0xe2, +0x47, 0x70, 0xe3, 0xb5, 0xa2, 0xe5, 0xe8, 0xd6, +0x4a, 0x25, 0xee, 0xda, 0xdd, 0x98, 0x0c, 0xa6, +0x1b, 0xb2, 0x51, 0x10, 0x28, 0xaf, 0x5d, 0xb7, +0x35, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x55, 0xc0, 0xd7, 0x2d, 0x96, +0x18, 0x6e, 0x8f, 0x1c, 0xe9, 0xfd, 0xde, 0x9b, +0x18, 0xc1, 0x63, 0x80, 0xe4, 0xc2, 0x16, 0x1b, +0x74, 0xfb, 0x01, 0x4f, 0x0a, 0xe5, 0xe1, 0x38, +0x8c, 0x0e, 0xf3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0x91, 0x7f, 0x41, +0x55, 0xcb, 0x91, 0x52, 0x9a, 0xa9, 0x22, 0x09, +0x8e, 0xf0, 0x6e, 0x3b, 0x01, 0x50, 0x2e, 0x6a, +0x98, 0x7e, 0x9e, 0xcf, 0x6e, 0xb4, 0x69, 0xdd, +0xa4, 0x5f, 0x2d, 0x6c, 0x7d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0x21, +0x13, 0xd5, 0xfa, 0xc0, 0x45, 0x6f, 0x4c, 0xde, +0x77, 0xd2, 0x1a, 0xd6, 0xbb, 0x59, 0x66, 0xc8, +0xa4, 0x39, 0x96, 0xdd, 0xf0, 0x2b, 0xfa, 0x62, +0xf2, 0x9a, 0x4e, 0x7c, 0x67, 0xc5, 0x88, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0x45, 0xc5, 0x87, 0x82, 0x87, 0x31, 0x13, +0xd5, 0x8b, 0xb9, 0x2c, 0x61, 0x33, 0xa7, 0x0f, +0xb3, 0x98, 0x46, 0x69, 0xae, 0x0e, 0xfa, 0x4d, +0x47, 0x7a, 0x0d, 0x11, 0x66, 0x9a, 0x31, 0x1c, +0xe1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0x17, 0xbd, 0x2e, 0x0b, 0x8b, +0x50, 0x8c, 0x45, 0xc3, 0xb5, 0x93, 0xdb, 0xd8, +0x7d, 0x63, 0x08, 0x39, 0x40, 0x0e, 0x5f, 0x72, +0x63, 0xcb, 0xfb, 0xa8, 0x47, 0x0c, 0x79, 0x55, +0x55, 0x88, 0xad, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0xea, 0x89, 0x57, +0x36, 0x21, 0x0a, 0x0a, 0xcb, 0x80, 0xc8, 0x8c, +0xed, 0xd3, 0x91, 0xf1, 0x2c, 0x7c, 0xe0, 0xf6, +0xa2, 0xee, 0x23, 0xed, 0xa1, 0x9d, 0x2e, 0x09, +0xf0, 0x84, 0x4c, 0xe2, 0x3b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x5f, +0x17, 0x4b, 0x02, 0x45, 0xe5, 0x6e, 0x65, 0x5e, +0xf4, 0x2a, 0x01, 0x24, 0x42, 0x65, 0x69, 0xd8, +0xa3, 0x39, 0x47, 0x92, 0xea, 0xf4, 0xa4, 0xdc, +0x6e, 0x68, 0x32, 0x76, 0x9c, 0x33, 0xff, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0x23, 0xa3, 0x91, 0xdd, 0x85, 0xa8, 0x92, +0x3d, 0x90, 0x04, 0x02, 0x2e, 0x48, 0xd1, 0x7d, +0x23, 0xe5, 0x76, 0x54, 0x2c, 0xd3, 0x07, 0xb3, +0x00, 0x82, 0xa9, 0xa1, 0xa8, 0xe2, 0x3d, 0x4f, +0x41, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0x19, 0x11, 0x7d, 0xdd, 0x98, +0x50, 0x92, 0xd7, 0x90, 0x47, 0x09, 0x48, 0xce, +0xf4, 0xd3, 0x93, 0x33, 0x64, 0x8e, 0x04, 0xe9, +0xd4, 0x44, 0x73, 0x4f, 0x7f, 0xf3, 0x74, 0xd6, +0x07, 0x00, 0x8c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0x06, 0x9d, 0xcb, +0x87, 0x0f, 0x26, 0x64, 0x29, 0xa0, 0xb7, 0x7c, +0xb6, 0x44, 0x59, 0x48, 0x9a, 0x42, 0x9b, 0xca, +0xe2, 0x2c, 0x54, 0x89, 0x87, 0x5d, 0xe0, 0x25, +0xc3, 0xd0, 0xbb, 0x64, 0x2c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0x6a, +0x64, 0x80, 0x97, 0x45, 0xc5, 0xfe, 0x4a, 0x29, +0xf1, 0x3b, 0xe5, 0xc4, 0x86, 0xa0, 0x28, 0x78, +0xc5, 0xa2, 0x0d, 0x9f, 0xca, 0xf8, 0xbf, 0xbd, +0xba, 0xa9, 0x04, 0xab, 0x1d, 0xbd, 0x1b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0xb1, 0x72, 0xe2, 0xe2, 0xf6, 0xac, 0xf7, +0x54, 0xb0, 0x49, 0x61, 0x06, 0x44, 0x8e, 0xc3, +0x82, 0x6d, 0x0e, 0x7a, 0x07, 0xe1, 0x90, 0xe1, +0xc6, 0x14, 0x1a, 0xcb, 0x98, 0x3d, 0x9d, 0x89, +0xea, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0x45, 0xbc, 0xf1, 0xa3, 0x35, +0xfe, 0x14, 0x3a, 0x7b, 0xe0, 0xaa, 0x52, 0xb4, +0xd1, 0xa2, 0x85, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x89, 0x9f, 0xf0, 0x7d, +0xd9, 0x86, 0x9f, 0x45, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0x92, 0xe4, 0xa2, +0x5f, 0x66, 0x1f, 0x68, 0x8b, 0x22, 0x22, 0x43, +0x79, 0x1e, 0x0b, 0x7d, 0x32, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd7, 0x93, +0x07, 0xa8, 0x35, 0x7a, 0xf9, 0x4d, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0xf8, +0xa5, 0x72, 0xe8, 0xf9, 0x22, 0xea, 0x2d, 0x8b, +0x50, 0x01, 0x8e, 0x7c, 0xa0, 0xd1, 0xcb, 0x8c, +0xa7, 0x39, 0x6c, 0xf6, 0x64, 0x73, 0x44, 0x69, +0x99, 0x2e, 0x19, 0x43, 0xbb, 0x64, 0xd7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0x04, 0x11, 0xf0, 0x69, 0xa4, 0x73, 0x78, +0x9d, 0x8e, 0x3c, 0xfb, 0x28, 0xfe, 0x93, 0x48, +0x13, 0x24, 0x91, 0x7f, 0xed, 0x47, 0x08, 0xb2, +0xf0, 0x87, 0xbc, 0x0e, 0x20, 0x14, 0x9e, 0x1f, +0x1d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0xf0, 0xde, 0x1f, 0x0f, 0x51, +0xc8, 0x5f, 0xde, 0xce, 0x10, 0x2f, 0x62, 0xf1, +0xb3, 0x2a, 0x89, 0xc2, 0xf2, 0xcd, 0xe6, 0xb1, +0x42, 0x9b, 0xef, 0x0d, 0x5a, 0xc5, 0x85, 0x43, +0xdc, 0xd2, 0x22, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x6b, 0xa5, 0x8a, +0x3b, 0x08, 0xa0, 0xac, 0xae, 0x97, 0x13, 0x00, +0x1d, 0xc0, 0xfd, 0xe3, 0x97, 0xfd, 0xa7, 0x18, +0xae, 0xd8, 0xbc, 0xc0, 0x18, 0xf6, 0xd6, 0x58, +0x82, 0x43, 0x6c, 0x6d, 0x0c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0xd3, +0x08, 0x0f, 0xf6, 0x78, 0x30, 0x11, 0x66, 0x3f, +0x92, 0x86, 0xa4, 0x0e, 0xcf, 0xb1, 0x62, 0x66, +0x32, 0x97, 0xb0, 0x1d, 0xe3, 0x4d, 0x16, 0xde, +0x3f, 0xbe, 0xe1, 0x40, 0x0e, 0x23, 0xd5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0xc0, 0xf5, 0xa1, 0xe1, 0x2d, 0xed, 0xfc, +0xc6, 0x2c, 0xfd, 0xca, 0x2c, 0x15, 0x42, 0x79, +0xb6, 0xf8, 0x39, 0xa1, 0x2e, 0xe1, 0xff, 0x62, +0x41, 0xf0, 0x9b, 0x09, 0x4e, 0x14, 0xc2, 0xe0, +0xe4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0xeb, 0xeb, 0x7e, 0x6a, 0xae, +0xa0, 0x43, 0x1c, 0x13, 0x2b, 0xfa, 0x1d, 0x8a, +0x2b, 0x83, 0xea, 0x25, 0x8a, 0x77, 0x8e, 0x8f, +0x98, 0xff, 0xab, 0x9c, 0xcb, 0xc2, 0xf3, 0x8f, +0xc5, 0x67, 0x81, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0xee, 0xd1, 0x08, +0x6f, 0x2b, 0x64, 0x63, 0xc9, 0xff, 0xc7, 0x52, +0xca, 0xfd, 0xf2, 0xd5, 0x44, 0x35, 0x25, 0xd5, +0x55, 0xb4, 0x4c, 0x99, 0x6d, 0x76, 0x3e, 0x53, +0x50, 0x68, 0xb8, 0xbe, 0x67, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x14, +0xc5, 0x7f, 0x33, 0xb2, 0xfa, 0xac, 0xca, 0x0f, +0x46, 0x55, 0x10, 0xc6, 0x10, 0x4a, 0x76, 0x67, +0xee, 0xed, 0xe4, 0x17, 0x6e, 0xa6, 0xc1, 0x53, +0xad, 0x8b, 0xd5, 0x2e, 0xb1, 0x54, 0x27, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0xae, 0x98, 0x28, 0xa8, 0x6d, 0xed, 0xd7, +0xa0, 0x6c, 0x17, 0xc3, 0x98, 0x38, 0x8e, 0x2e, +0xeb, 0xa9, 0x40, 0x1c, 0x2d, 0x32, 0xa8, 0x8b, +0x9e, 0xe1, 0x57, 0xde, 0x09, 0x98, 0xbb, 0x54, +0x9b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0x72, 0x47, 0x2d, 0xc6, 0xb9, +0x30, 0x84, 0xb3, 0x77, 0x41, 0xcc, 0x48, 0x26, +0x90, 0xba, 0xa0, 0x78, 0x4b, 0x3f, 0x65, 0xe4, +0x33, 0xec, 0x63, 0x4b, 0x11, 0xf8, 0xc6, 0x4e, +0xa4, 0x46, 0x48, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0x32, 0xe9, 0x2c, +0x92, 0x48, 0xb8, 0xaf, 0xa7, 0x6f, 0x75, 0x4f, +0x4e, 0x8d, 0x39, 0x76, 0x5c, 0xb6, 0x55, 0x34, +0x19, 0xbe, 0xac, 0x9a, 0xef, 0xd5, 0xeb, 0x6f, +0xa6, 0x0e, 0xbc, 0xc5, 0xb1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x1f, +0x94, 0x00, 0xc4, 0x08, 0x97, 0xaf, 0xd2, 0x71, +0xce, 0x35, 0xb1, 0xbc, 0x60, 0x48, 0x0e, 0x53, +0x46, 0xaf, 0xf2, 0x54, 0x98, 0x4c, 0x74, 0x83, +0x88, 0x44, 0xf8, 0xd8, 0x52, 0x36, 0xeb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0x84, 0x02, 0x1d, 0xf6, 0xbf, 0x45, 0x7b, +0xd6, 0x18, 0xcc, 0x2b, 0xf0, 0xa9, 0x08, 0x8d, +0xaa, 0xcd, 0x9b, 0x40, 0x27, 0x5e, 0xed, 0xb4, +0xfe, 0xab, 0x37, 0xc7, 0x19, 0x82, 0xea, 0x64, +0xc9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0xd1, 0xf8, 0x52, 0x56, 0xf1, +0xf5, 0x45, 0x0e, 0x9b, 0x05, 0x43, 0x66, 0x71, +0x8e, 0x50, 0x78, 0xb9, 0x72, 0x3a, 0xa3, 0x8e, +0x3d, 0xac, 0x8f, 0xc5, 0x80, 0x80, 0x24, 0x85, +0x28, 0x79, 0xc9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0xbe, 0xfe, 0x6a, +0x0d, 0xfb, 0xe9, 0xa5, 0x33, 0x58, 0x12, 0x85, +0xa9, 0xe4, 0x4e, 0x41, 0xbd, 0x57, 0xd6, 0x56, +0x2a, 0x5e, 0xa0, 0x19, 0x49, 0x73, 0x74, 0x24, +0xd3, 0xfd, 0xa7, 0x53, 0xf4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x4b, +0xe6, 0x6c, 0x54, 0x1f, 0xa0, 0xc0, 0xf3, 0x15, +0x9d, 0x34, 0xec, 0xe1, 0xf9, 0x79, 0xfd, 0x7e, +0x00, 0x41, 0xdd, 0x21, 0x3f, 0xc4, 0xdc, 0x7d, +0x4f, 0x8b, 0x9b, 0xab, 0xf9, 0x6f, 0xb1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0x6d, 0xe7, 0x9f, 0x52, 0x1c, 0xd3, 0x50, +0xc1, 0xd2, 0xf3, 0x53, 0x29, 0x4f, 0x4d, 0x8b, +0x3e, 0xe2, 0xa3, 0xbf, 0x59, 0xc8, 0x00, 0xc5, +0x70, 0x63, 0x4e, 0x10, 0xe9, 0xf3, 0x77, 0xc0, +0xe0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0x12, 0x1b, 0x9d, 0xd4, 0x4e, +0x52, 0x47, 0x4c, 0xc4, 0x4b, 0x8f, 0xcb, 0x48, +0x99, 0xe6, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x9c, 0x74, 0x7d, 0x0f, +0x76, 0x85, 0xf2, 0x84, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0x2e, 0xf5, 0x2f, +0xf0, 0x8e, 0xee, 0xf6, 0x03, 0x55, 0xdd, 0x57, +0xdd, 0xc8, 0x06, 0x34, 0x6b, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xba, 0xed, +0xb8, 0x63, 0x76, 0x60, 0xb6, 0x9c, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0x2c, +0x07, 0x2b, 0x89, 0x2f, 0xb0, 0xeb, 0xee, 0x6f, +0xd3, 0x07, 0xc8, 0xb6, 0xf8, 0xf0, 0x7d, 0x7e, +0xc3, 0x59, 0xbe, 0x31, 0xe8, 0x2e, 0x5d, 0x11, +0xeb, 0xfb, 0xf0, 0x2d, 0x54, 0x1e, 0x31, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0x8e, 0x4c, 0xd8, 0x43, 0xfb, 0x2e, 0xe8, +0xea, 0x85, 0x76, 0x0d, 0x89, 0x7d, 0x7b, 0xf0, +0x08, 0xe5, 0x92, 0xd8, 0x50, 0x09, 0xbf, 0x46, +0x01, 0xc6, 0xb3, 0x87, 0xb0, 0xe0, 0x27, 0xbc, +0xc5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0xa5, 0xd6, 0xc6, 0x90, 0xf6, +0xc7, 0x04, 0xd9, 0x20, 0xb1, 0x45, 0x75, 0x04, +0xc0, 0xa2, 0xf5, 0x25, 0xbf, 0x51, 0x4d, 0x06, +0x0d, 0x6f, 0x21, 0xab, 0x74, 0xfa, 0xa2, 0x05, +0xd6, 0xad, 0x15, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0xe4, 0xc9, 0x6f, +0x30, 0x95, 0x17, 0x09, 0xb6, 0x8d, 0x33, 0x09, +0x9b, 0x4e, 0x4c, 0x09, 0x2e, 0x02, 0x8f, 0x02, +0x54, 0x4c, 0x80, 0xf2, 0x79, 0xe7, 0xdd, 0xfd, +0xe3, 0x6e, 0x62, 0x5e, 0x7d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0x27, +0xc0, 0x57, 0xa5, 0x86, 0xcb, 0x70, 0x8a, 0x89, +0xed, 0x1f, 0x0e, 0x0f, 0x3e, 0x63, 0xce, 0x96, +0x58, 0x3a, 0x8c, 0xa3, 0xdc, 0xc5, 0xb4, 0x77, +0xe3, 0x4b, 0xd2, 0x91, 0x55, 0x8d, 0x18, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0x50, 0xfd, 0x90, 0x3b, 0xc7, 0xe5, 0xf1, +0x5d, 0x98, 0xfa, 0x16, 0x0e, 0xcd, 0x7f, 0xde, +0xf7, 0x41, 0xf8, 0xd8, 0x90, 0x09, 0xca, 0xcd, +0x12, 0xff, 0x81, 0xce, 0x05, 0x56, 0x68, 0x6c, +0x38, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0xde, 0x4e, 0xad, 0xcd, 0x3a, +0x89, 0x99, 0x26, 0x88, 0xc8, 0xc7, 0x48, 0x89, +0x20, 0x6a, 0x41, 0x9d, 0x3d, 0xaa, 0x0d, 0xe6, +0x10, 0xeb, 0x80, 0xff, 0x29, 0x11, 0xb4, 0xea, +0x5e, 0x13, 0x93, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0x67, 0x20, 0xda, +0xfa, 0xe2, 0x7f, 0xe8, 0x5d, 0xa4, 0xac, 0xf8, +0xdb, 0xe7, 0x45, 0x62, 0x27, 0x19, 0xef, 0xd1, +0x64, 0xff, 0x42, 0x11, 0xd1, 0xca, 0x5a, 0x33, +0xa1, 0x71, 0x9e, 0xa7, 0xa5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0x3a, +0x72, 0x71, 0xf7, 0xb0, 0xb8, 0xee, 0xfc, 0x59, +0xc5, 0xa3, 0x76, 0x28, 0x4d, 0x2e, 0x0d, 0x30, +0xc9, 0x3c, 0x2b, 0xc0, 0x58, 0x00, 0xd5, 0x63, +0x2d, 0xa5, 0x6e, 0x50, 0xff, 0x41, 0xe3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0x67, 0x57, 0x93, 0x0e, 0x62, 0x9b, 0x14, +0x8a, 0xc6, 0x75, 0x99, 0x1e, 0x59, 0xc1, 0xa0, +0xb8, 0xd1, 0xcc, 0xd8, 0x66, 0x42, 0x32, 0x30, +0x1b, 0xa0, 0x52, 0x7d, 0x13, 0x49, 0x1f, 0x9e, +0x1d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0x4f, 0x8e, 0x59, 0x19, 0x21, +0xe7, 0x7d, 0xbc, 0x28, 0x37, 0x0e, 0x05, 0x11, +0xe1, 0xab, 0x8a, 0xc6, 0x82, 0xe3, 0xeb, 0x8f, +0x8d, 0x3b, 0xfe, 0x83, 0x32, 0xaf, 0xc5, 0x3b, +0xf3, 0x27, 0xed, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0x5d, 0x8b, 0x23, +0x46, 0x0f, 0x99, 0x24, 0x53, 0xca, 0xab, 0x6e, +0x7f, 0x95, 0x92, 0x85, 0x4d, 0x9f, 0xe7, 0x89, +0x6b, 0x49, 0xa4, 0x2a, 0xcf, 0x32, 0x0f, 0x26, +0x5a, 0x8a, 0xa8, 0xa8, 0x32, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0xa7, +0xf0, 0x1a, 0x4c, 0x5e, 0x92, 0x85, 0xda, 0xbf, +0x17, 0x7c, 0x45, 0x78, 0x2d, 0x54, 0xa9, 0xb6, +0xa5, 0xa3, 0x08, 0xa7, 0x05, 0xf7, 0xa0, 0x53, +0x13, 0x16, 0xf0, 0x19, 0x49, 0xa3, 0xb0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0xb7, 0xca, 0x53, 0x3e, 0x75, 0xb4, 0x25, +0x9a, 0xf0, 0xd2, 0x2b, 0x6b, 0x77, 0x95, 0x29, +0x78, 0x7d, 0xdb, 0xc1, 0xe7, 0xf0, 0x75, 0xc0, +0xd1, 0x04, 0xa6, 0x14, 0x56, 0x40, 0x80, 0x21, +0x06, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0x34, 0x17, 0x14, 0xc1, 0xd0, +0x34, 0xe3, 0xbc, 0x8f, 0x2b, 0x84, 0x79, 0x38, +0x8b, 0x71, 0x15, 0xca, 0x58, 0xfb, 0xd0, 0x35, +0xcc, 0xc8, 0x7f, 0xb4, 0x5e, 0x85, 0xd2, 0x19, +0x75, 0x86, 0x62, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0xc8, 0x1e, 0x2f, +0xb5, 0xb6, 0x93, 0x6b, 0x33, 0x01, 0x9d, 0x9a, +0x75, 0xd8, 0xc6, 0x54, 0x07, 0xd4, 0x7e, 0x2d, +0x4f, 0x98, 0xdd, 0x33, 0xcf, 0x3d, 0xff, 0x3a, +0x4d, 0x80, 0x95, 0x10, 0xed, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0x22, +0x1b, 0x83, 0x55, 0xc3, 0x1d, 0x59, 0x37, 0x1d, +0xbd, 0x2a, 0x95, 0xf1, 0x86, 0x6a, 0x97, 0xfd, +0x59, 0x39, 0xbe, 0xd0, 0xcc, 0x54, 0xe6, 0x3a, +0x9c, 0x09, 0xde, 0x84, 0xd7, 0x1a, 0x34, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0x16, 0x5e, 0xa3, 0xeb, 0xfd, 0x25, 0x60, +0xc3, 0x78, 0xb7, 0xff, 0x80, 0xec, 0x88, 0xd5, +0xb5, 0x2d, 0xa5, 0x2e, 0x28, 0xd5, 0x6f, 0xa9, +0xe9, 0x1f, 0x96, 0x50, 0xf7, 0x2a, 0x65, 0x58, +0x63, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x1a, 0x31, 0x60, 0x9d, 0x88, +0x38, 0x6c, 0xc6, 0xbb, 0x14, 0xab, 0x48, 0xf5, +0x38, 0x87, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xe9, 0x0e, 0xe4, 0xd9, +0x1d, 0x76, 0x63, 0xdd, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0xe9, 0x3f, 0x13, +0xec, 0x6c, 0x73, 0xb5, 0x68, 0xa7, 0x72, 0x05, +0x8e, 0x23, 0x62, 0xdb, 0xf9, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa7, 0x7b, +0x8c, 0x26, 0x31, 0x35, 0x8a, 0x57, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0x23, +0x53, 0xa3, 0x8f, 0x15, 0x6a, 0xa8, 0x29, 0x49, +0xb0, 0xc5, 0xa7, 0x22, 0x10, 0xef, 0x21, 0xe8, +0x2f, 0x16, 0x5d, 0xb3, 0x64, 0xaf, 0xe3, 0x3e, +0xbd, 0xb9, 0xcf, 0x30, 0x6e, 0xe7, 0xf0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0xd7, 0x75, 0xaf, 0xd3, 0x0f, 0xc0, 0x5e, +0x27, 0x1d, 0x68, 0x91, 0x26, 0x1d, 0x8f, 0xe7, +0xae, 0x71, 0x35, 0xf4, 0x2b, 0xa8, 0xcb, 0xe4, +0x2b, 0xe6, 0xe6, 0xf7, 0xbd, 0x38, 0x7c, 0x98, +0xf9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0xca, 0x63, 0x35, 0x9a, 0x4f, +0x8a, 0xda, 0x31, 0x70, 0x7b, 0x0c, 0x66, 0x04, +0x57, 0xb1, 0xff, 0xa0, 0x74, 0x94, 0x43, 0x07, +0x78, 0x23, 0x50, 0x5a, 0xb4, 0x49, 0xab, 0x9d, +0x0d, 0x00, 0xd5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0x37, 0xff, 0x98, +0xf7, 0xf4, 0x06, 0x90, 0xcf, 0x5d, 0xb0, 0x3c, +0x59, 0x51, 0x4c, 0x79, 0x68, 0x8e, 0x84, 0x5c, +0x48, 0xeb, 0x36, 0x91, 0xa4, 0x9b, 0xa4, 0x40, +0x6e, 0x02, 0xf2, 0xf7, 0x35, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0x41, +0xda, 0xdc, 0x1b, 0x97, 0x2e, 0x53, 0xb8, 0xe6, +0xfa, 0x3a, 0x09, 0x19, 0x43, 0x9e, 0xf9, 0x9d, +0x25, 0x21, 0x2d, 0x8f, 0x48, 0x3f, 0xb8, 0x4a, +0x0d, 0x82, 0x4b, 0xb1, 0x03, 0x97, 0xca, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0x68, 0x57, 0x23, 0x11, 0x06, 0x74, 0x61, +0x55, 0x4b, 0x1d, 0xe6, 0xb0, 0x25, 0x69, 0x0d, +0xd4, 0xa9, 0xbb, 0x85, 0xb0, 0x6e, 0x08, 0x30, +0x1d, 0xd2, 0x7e, 0x48, 0x0b, 0x04, 0xd7, 0x54, +0x48, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0xb4, 0xfa, 0xdc, 0x35, 0x94, +0xa7, 0xd0, 0xec, 0x73, 0xde, 0x4d, 0xde, 0x48, +0x82, 0x6f, 0xa0, 0x3b, 0xef, 0x0e, 0x31, 0x57, +0xb3, 0x22, 0x69, 0xc8, 0x59, 0x5f, 0xd9, 0xe7, +0x04, 0x85, 0x54, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0x6e, 0xa0, 0xba, +0x7e, 0xc7, 0x1e, 0x8e, 0xd6, 0xb3, 0x83, 0x41, +0x3a, 0xb5, 0x9b, 0xa8, 0x5d, 0xe1, 0x4e, 0x02, +0xa3, 0xd4, 0x28, 0x95, 0xab, 0x3e, 0x85, 0xd1, +0xb7, 0xe0, 0xfe, 0xf3, 0xcf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0x66, +0xd1, 0xc4, 0x86, 0xac, 0x04, 0xf8, 0x14, 0xc8, +0x62, 0x55, 0x59, 0x9b, 0xfb, 0xcd, 0xf2, 0x4d, +0x06, 0x7b, 0xc1, 0xa2, 0xc5, 0x6d, 0x6c, 0x0b, +0x63, 0xae, 0xca, 0xea, 0x08, 0xac, 0x9a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0xd3, 0x76, 0x4f, 0x03, 0x0d, 0x8e, 0x53, +0xaf, 0x38, 0x84, 0xc7, 0x95, 0x17, 0x6d, 0x2f, +0x27, 0x1f, 0xdf, 0xb1, 0x7b, 0x84, 0x41, 0x93, +0x7f, 0x97, 0xce, 0x33, 0x25, 0x5c, 0x8e, 0xc2, +0x59, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0x87, 0x2d, 0xfd, 0x46, 0x8a, +0xb9, 0xb3, 0x50, 0x7f, 0x61, 0xe6, 0x67, 0xfc, +0xcc, 0x0b, 0x44, 0x26, 0x63, 0x8f, 0xcf, 0x3d, +0xba, 0x71, 0xfe, 0x44, 0x4d, 0x94, 0x65, 0x7d, +0x9c, 0xc8, 0x81, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0xa6, 0x7f, 0x4a, +0xb1, 0x42, 0x65, 0x71, 0xd2, 0x83, 0xc5, 0xc6, +0xfd, 0x87, 0x09, 0xda, 0xd4, 0x84, 0xa3, 0xea, +0x33, 0x60, 0xb9, 0x37, 0x39, 0x6e, 0xca, 0x85, +0x5c, 0xc2, 0x6e, 0x00, 0xf6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0x48, +0xa2, 0xff, 0x61, 0x30, 0xc3, 0x7b, 0xac, 0x2b, +0x4b, 0x16, 0x14, 0xb9, 0xde, 0x02, 0x73, 0xb5, +0x70, 0x4b, 0xc7, 0xe8, 0xac, 0x4e, 0x19, 0x45, +0x42, 0x73, 0x50, 0x47, 0x9b, 0x12, 0x10, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x2d, 0x05, 0x44, 0x79, 0x1a, 0xe7, 0x8d, +0xd1, 0xec, 0x2d, 0x6e, 0xef, 0x15, 0xef, 0xfd, +0x47, 0x07, 0xd0, 0x7f, 0xcf, 0xc2, 0xc4, 0x40, +0x33, 0x3e, 0x3e, 0x8e, 0xa6, 0x23, 0x2b, 0x97, +0x45, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0x9e, 0xec, 0x90, 0x6c, 0x3b, +0x9c, 0x30, 0x7e, 0xfb, 0x0b, 0xd9, 0x0c, 0xc0, +0x2b, 0x1d, 0x92, 0xcb, 0x27, 0x45, 0x03, 0x13, +0xa5, 0x2c, 0x40, 0xb5, 0x90, 0x86, 0xfa, 0x5b, +0x56, 0x45, 0x79, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x94, 0x3b, 0xeb, +0x36, 0xa4, 0x90, 0xbf, 0xdc, 0x3e, 0x8d, 0x81, +0xa0, 0x01, 0x91, 0x85, 0x95, 0x34, 0x8d, 0x6a, +0x9e, 0xfd, 0x48, 0x7f, 0x80, 0x30, 0xcf, 0xcc, +0x61, 0xa9, 0x13, 0x4a, 0x30, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0x94, +0xb2, 0x95, 0x34, 0xce, 0x90, 0xfe, 0x6a, 0xa0, +0xb2, 0xe2, 0x0a, 0x00, 0x2f, 0x46, 0x08, 0x27, +0xa6, 0x87, 0xf8, 0xc8, 0x08, 0x14, 0x7c, 0x9c, +0xcb, 0xb4, 0xe5, 0x11, 0xa5, 0xa9, 0xbe, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0x67, 0x39, 0x55, 0xb5, 0x45, 0x3e, 0x94, +0x36, 0x2d, 0x46, 0xb3, 0x94, 0xf2, 0x10, 0x96, +0xd1, 0x88, 0x42, 0x18, 0x61, 0x69, 0x55, 0xe5, +0xd6, 0xf5, 0x5d, 0xa8, 0x87, 0x8a, 0x10, 0x6c, +0x3c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0xb4, 0x38, 0x6a, 0x78, 0x95, +0x7d, 0x37, 0x9e, 0x5e, 0xb3, 0xf7, 0x4c, 0xd0, +0x9c, 0xf5, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x83, 0xba, 0x3d, 0xaa, +0xbd, 0x5d, 0x7b, 0xf1, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x62, 0x1f, 0xb5, +0xd9, 0x05, 0x70, 0x8e, 0xf8, 0x0a, 0x0c, 0x21, +0x28, 0x36, 0x25, 0x9b, 0xfd, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x91, +0xd0, 0x28, 0x11, 0x63, 0x1c, 0xe0, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0xfe, +0xb9, 0x74, 0x27, 0xaf, 0x26, 0x61, 0x31, 0xcd, +0x47, 0xaf, 0x2d, 0x53, 0xbc, 0xc6, 0xa3, 0x28, +0x47, 0x24, 0x74, 0x07, 0x22, 0x51, 0x25, 0x3e, +0xcb, 0xef, 0x38, 0xa9, 0xb0, 0x33, 0x1f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0xf8, 0xa8, 0xbf, 0x4c, 0x8f, 0xbb, 0x3a, +0x54, 0xb3, 0x70, 0x64, 0xb5, 0xd4, 0x25, 0xf1, +0xc5, 0x41, 0x2f, 0x1d, 0x62, 0x35, 0xde, 0x25, +0x21, 0x9b, 0x65, 0x05, 0x73, 0x1b, 0xc7, 0x01, +0x2a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0x35, 0x2c, 0xcc, 0xf6, 0x9b, +0x5f, 0xd0, 0x86, 0xd4, 0xa9, 0x19, 0x80, 0x0a, +0x16, 0x74, 0xa7, 0x78, 0x60, 0x23, 0x7e, 0xc5, +0x01, 0xd9, 0x16, 0x48, 0x66, 0xdf, 0x8e, 0x7e, +0xb4, 0x73, 0xf4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0x59, 0x91, 0x93, +0xd8, 0x9e, 0x94, 0x45, 0xcd, 0xc2, 0xfc, 0xb4, +0x11, 0xc5, 0x00, 0x2b, 0x01, 0x9a, 0x0c, 0x70, +0x29, 0x11, 0xec, 0xe8, 0xb6, 0x65, 0xc1, 0x73, +0xc3, 0xd6, 0x36, 0x5d, 0xfe, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0x86, +0xb6, 0xe1, 0x0e, 0xf6, 0x7d, 0x42, 0xc2, 0xc5, +0xd4, 0xe9, 0x01, 0x33, 0x63, 0xaa, 0xcc, 0xe6, +0x1d, 0x5d, 0xa1, 0x41, 0xbb, 0xe7, 0x73, 0x16, +0xad, 0x85, 0xbd, 0xda, 0x29, 0x73, 0x59, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0x1c, 0xf9, 0x77, 0x7f, 0x64, 0x64, 0xb8, +0x6a, 0xce, 0xf8, 0x03, 0x7e, 0x22, 0xfd, 0x0e, +0xfd, 0xb2, 0x81, 0xb3, 0x67, 0x59, 0x9c, 0xf8, +0x7e, 0x46, 0xbd, 0x74, 0xb5, 0xa4, 0x9d, 0x63, +0x8f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0x71, 0x85, 0x74, 0xbe, 0xde, +0x61, 0x7f, 0x82, 0x34, 0x4c, 0x84, 0xc2, 0x9d, +0xe6, 0x91, 0x10, 0xf4, 0x15, 0x93, 0xbc, 0x0d, +0xfc, 0xf4, 0xea, 0x12, 0x49, 0x54, 0x5e, 0xee, +0x78, 0x9d, 0x85, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x2c, 0x8c, 0x2d, +0x59, 0xf5, 0x28, 0xec, 0x1b, 0x40, 0xa9, 0x2a, +0xc2, 0x5b, 0x48, 0xee, 0x6d, 0x41, 0xcc, 0x2d, +0x13, 0xa0, 0x90, 0x73, 0xfa, 0x98, 0x93, 0xd9, +0x10, 0x82, 0xe8, 0x8d, 0xea, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x05, +0xee, 0x59, 0x3d, 0x4e, 0xbb, 0x05, 0x74, 0x6a, +0xaf, 0x02, 0xf4, 0x18, 0x66, 0x76, 0x6e, 0xe1, +0x3a, 0x3f, 0xe6, 0x2f, 0xa2, 0xe0, 0xb4, 0xe9, +0xc2, 0x0b, 0x0e, 0x0f, 0x21, 0x1a, 0xee, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0xd8, 0xb2, 0x68, 0xec, 0xbf, 0x2e, 0x81, +0xb3, 0x19, 0x80, 0x7b, 0x38, 0xdf, 0xd4, 0x24, +0x2a, 0x11, 0x0a, 0xdd, 0x5c, 0x02, 0xb2, 0x3e, +0x9f, 0xac, 0x9c, 0x0d, 0xf4, 0x4f, 0x86, 0x8e, +0x0b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0xa2, 0xdd, 0x9e, 0x3c, 0xaf, +0x23, 0x97, 0x19, 0x3c, 0x51, 0x00, 0xa0, 0xfa, +0xe5, 0x01, 0x7d, 0xef, 0x36, 0x56, 0xe9, 0x95, +0x48, 0xa6, 0x19, 0x8b, 0x42, 0x3a, 0x23, 0x13, +0x90, 0x19, 0x89, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0x2c, 0x7a, 0x59, +0x89, 0x76, 0x3e, 0x74, 0xbc, 0x8f, 0x3f, 0xad, +0x57, 0x1e, 0x9e, 0xef, 0xb4, 0x9a, 0x8c, 0x04, +0xb9, 0x38, 0x3d, 0x72, 0x81, 0xc6, 0x9d, 0x9e, +0xc8, 0xae, 0x9e, 0x33, 0x8e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0x9d, +0xf3, 0xbd, 0xfe, 0xb6, 0x30, 0xdb, 0x9d, 0xb5, +0x52, 0xbf, 0xff, 0x85, 0x14, 0x24, 0x43, 0xf9, +0xc4, 0x5b, 0x4d, 0xb9, 0x0c, 0xfd, 0xcd, 0xb1, +0x46, 0x8d, 0x36, 0xdc, 0xc3, 0xb1, 0x44, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x6d, 0x28, 0x79, 0x53, 0x52, 0xb5, 0x65, +0xb8, 0x9e, 0x07, 0xbd, 0x6c, 0xc1, 0xd5, 0x0e, +0xee, 0xb8, 0x3f, 0x17, 0xb2, 0x71, 0xfb, 0x34, +0xf4, 0xc7, 0xa3, 0x90, 0x43, 0x52, 0x79, 0x04, +0xd4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0x39, 0xe7, 0x56, 0x92, 0x2e, +0xf9, 0x29, 0x7c, 0x3f, 0x8f, 0x37, 0xdc, 0xfa, +0xef, 0x22, 0xa5, 0x01, 0x8d, 0x78, 0x2f, 0x9b, +0x54, 0x9d, 0x1e, 0xff, 0x64, 0x96, 0x51, 0x35, +0x44, 0xac, 0x23, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0xd6, 0x6c, 0x81, +0x85, 0xfa, 0x58, 0xfb, 0xb9, 0x51, 0x2e, 0x61, +0x32, 0x79, 0x7d, 0xf9, 0x25, 0xb3, 0x05, 0xc4, +0x86, 0x5e, 0xc4, 0x02, 0x0a, 0x7e, 0x51, 0x6d, +0x85, 0x65, 0x76, 0x08, 0x70, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0xc1, +0xe8, 0x1f, 0xf0, 0xd4, 0x50, 0x9d, 0xc0, 0x55, +0x30, 0x1b, 0x55, 0xc7, 0x55, 0x4a, 0x04, 0xda, +0x24, 0x52, 0x7f, 0x35, 0x32, 0x9c, 0x09, 0x3a, +0x0e, 0x01, 0x6e, 0x90, 0x2f, 0x2c, 0xa3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0xf0, 0xb4, 0xc3, 0x8f, 0x94, 0x45, 0x6f, +0xb3, 0x9e, 0x26, 0xc0, 0xf3, 0x0e, 0xf3, 0x20, +0x54, 0xe2, 0x07, 0xad, 0xfe, 0x52, 0xad, 0x19, +0xfa, 0x92, 0xa3, 0x0d, 0x1d, 0x3a, 0x7b, 0xb9, +0x55, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0xb1, 0x5d, 0x5a, 0x09, 0xc5, +0x58, 0x80, 0xb0, 0xe8, 0xe9, 0x74, 0x38, 0xa7, +0xa3, 0xdd, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x26, 0x0b, 0x41, 0x8e, +0xc2, 0x17, 0x2d, 0xf1, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0x23, 0x70, 0x4d, +0xf8, 0x0d, 0xc4, 0x18, 0xe9, 0xb8, 0x5e, 0xad, +0xc1, 0x0f, 0xc2, 0x0d, 0x41, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x6d, +0x0e, 0x6f, 0x41, 0x6c, 0x5b, 0x1b, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0x59, +0x1b, 0x32, 0x4b, 0x3e, 0x89, 0x42, 0x57, 0x87, +0x98, 0x64, 0x37, 0x6c, 0xf2, 0x04, 0x23, 0x90, +0xef, 0x24, 0x14, 0x93, 0x34, 0x73, 0x17, 0xc1, +0xc1, 0x3d, 0xfa, 0xa2, 0x46, 0x95, 0xfe, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x8f, 0xf2, 0x96, 0x7a, 0x09, 0xbb, 0xb9, +0xcc, 0x26, 0x09, 0x3e, 0xcf, 0xf5, 0xc8, 0x79, +0x57, 0x3c, 0xf7, 0x43, 0xac, 0xb7, 0xca, 0x1e, +0x50, 0xb8, 0x46, 0xd1, 0x2d, 0xb7, 0x3b, 0xea, +0x55, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0x09, 0xeb, 0xbe, 0xe3, 0x6a, +0x45, 0xd7, 0x02, 0xb4, 0x0c, 0x18, 0x9d, 0x17, +0xa3, 0xaf, 0x4a, 0xd9, 0xf6, 0x86, 0xc3, 0xbb, +0xef, 0xd8, 0x93, 0xec, 0x5b, 0x51, 0x8c, 0x28, +0x2e, 0x85, 0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0xfa, 0x16, 0x2b, +0x21, 0x6b, 0x0e, 0x02, 0x98, 0x27, 0x83, 0x22, +0x36, 0x72, 0xc9, 0x4c, 0xf8, 0x87, 0xa4, 0x6a, +0x13, 0xc6, 0x75, 0x52, 0xde, 0xe0, 0x9a, 0x96, +0x0b, 0x36, 0x0f, 0x84, 0xe0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0x49, +0xcd, 0xec, 0xc3, 0x9a, 0x15, 0x74, 0x3c, 0xcf, +0x14, 0x19, 0x8b, 0xe1, 0xcd, 0x18, 0x97, 0x05, +0x7d, 0xdb, 0xbd, 0xd2, 0xc7, 0x41, 0xa9, 0xa7, +0x2d, 0x1d, 0xe0, 0xa5, 0xf6, 0x0e, 0xd3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0x02, 0xcf, 0x31, 0x54, 0x5b, 0xac, 0xbd, +0xa0, 0x50, 0xa5, 0x78, 0x81, 0xa6, 0xb7, 0x87, +0xa4, 0xb9, 0x4c, 0x33, 0xfc, 0x55, 0x90, 0x0e, +0x5d, 0x27, 0x2d, 0x20, 0xdc, 0xe6, 0x9c, 0xce, +0x75, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0xdc, 0x72, 0x64, 0xc0, 0x8c, +0xa0, 0xf9, 0xec, 0x86, 0x4c, 0x5b, 0x3f, 0x71, +0x69, 0x86, 0x99, 0x1f, 0x1a, 0xd4, 0x48, 0x9b, +0x66, 0x62, 0xe2, 0xdb, 0x0e, 0x9f, 0xa0, 0xdb, +0x39, 0x34, 0xe4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0xd0, 0xe6, 0x74, +0x66, 0xef, 0xd3, 0xbb, 0xca, 0x54, 0x41, 0xce, +0x95, 0x73, 0x66, 0x0d, 0x23, 0xa3, 0x4f, 0x2a, +0xd1, 0xbd, 0x01, 0x72, 0x63, 0x62, 0xd7, 0x44, +0xc9, 0x8e, 0x27, 0x19, 0x7d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0x96, +0xc1, 0x1d, 0xb3, 0xc4, 0x59, 0xb3, 0x3c, 0xc1, +0x93, 0xcd, 0x6c, 0x30, 0x38, 0x87, 0xd5, 0xe5, +0x62, 0x9b, 0xf8, 0x52, 0x5f, 0x66, 0x21, 0xda, +0xfa, 0x61, 0x10, 0x03, 0xc3, 0x2d, 0xfb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0x83, 0x17, 0x90, 0xba, 0xb8, 0xfc, 0xea, +0x00, 0xd6, 0x1d, 0xc6, 0xf6, 0x60, 0x8c, 0x67, +0x73, 0x60, 0x17, 0x0e, 0x66, 0x9e, 0x79, 0x38, +0x86, 0xe9, 0x06, 0xcf, 0x1a, 0x2a, 0xc8, 0x8d, +0xc0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0xa7, 0x51, 0xf2, 0x41, 0xf8, +0x74, 0xf6, 0xd6, 0xdc, 0x3f, 0x19, 0x12, 0x26, +0x88, 0xb2, 0xde, 0xe4, 0x01, 0xfb, 0xe2, 0xa5, +0x7f, 0xb1, 0x3a, 0x0c, 0x56, 0xcd, 0x18, 0x66, +0xd5, 0x9c, 0x3d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x7c, 0x5f, 0xf5, +0x4a, 0xbc, 0xb3, 0xcb, 0x38, 0xff, 0x44, 0x25, +0x72, 0x1a, 0x77, 0xbe, 0x6b, 0xd7, 0x2a, 0xc8, +0x30, 0x8f, 0x59, 0x2f, 0x48, 0xad, 0xfc, 0x58, +0x2e, 0x68, 0xae, 0x4d, 0x93, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0x76, +0x49, 0x7d, 0x45, 0x9f, 0x86, 0x6f, 0x47, 0xa5, +0x16, 0x89, 0x73, 0x00, 0x50, 0x19, 0x88, 0x4a, +0x0e, 0x62, 0xbe, 0xf5, 0xac, 0x36, 0x73, 0x1a, +0xa3, 0x61, 0xe1, 0x7d, 0x41, 0x83, 0x02, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0x1e, 0x1a, 0xfe, 0xa6, 0x8c, 0x61, 0x4a, +0x3b, 0x58, 0x44, 0xd2, 0x89, 0xde, 0x31, 0xff, +0x72, 0x7f, 0x9a, 0xd0, 0x78, 0x04, 0x60, 0x52, +0x4e, 0x27, 0x4c, 0xef, 0xcc, 0xdf, 0x65, 0x16, +0x0f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0x42, 0x44, 0x73, 0x16, 0xb4, +0xa4, 0x9c, 0x10, 0x24, 0x7b, 0x78, 0x3b, 0xf1, +0x18, 0xc6, 0x78, 0x83, 0x55, 0x19, 0x46, 0xa7, +0x51, 0x0d, 0x41, 0xa0, 0x69, 0x5b, 0x04, 0xa2, +0x34, 0x46, 0x52, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0x2f, 0x97, 0x8d, +0x25, 0xdb, 0x97, 0xd6, 0x83, 0x83, 0xd7, 0x88, +0x9f, 0x8f, 0xb7, 0x95, 0x06, 0xcc, 0x69, 0xa7, +0x07, 0x0f, 0x47, 0x29, 0x02, 0x4c, 0x49, 0xd3, +0x59, 0x76, 0xb6, 0xc3, 0x81, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0x8d, +0x34, 0x85, 0x1d, 0x64, 0x12, 0xd3, 0xb2, 0xc9, +0xf1, 0xcb, 0x15, 0x87, 0x84, 0x07, 0x09, 0x2b, +0x2a, 0x7a, 0xf1, 0xa9, 0xb8, 0x3b, 0x36, 0x2d, +0x91, 0xec, 0x4a, 0xe4, 0xbe, 0x44, 0x04, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0x05, 0xcc, 0x65, 0x7e, 0x1f, 0x10, 0x0d, +0xc9, 0xf7, 0x4a, 0x30, 0x97, 0x33, 0xf7, 0x76, +0x71, 0x3f, 0xe1, 0x27, 0xce, 0xcf, 0xb5, 0xaf, +0xf0, 0xa4, 0xd8, 0x6f, 0xe6, 0xb1, 0xd0, 0x5c, +0xc2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x05, 0x73, 0x0d, 0xfc, 0xd9, +0x48, 0x7e, 0x96, 0xea, 0xd9, 0xcc, 0x24, 0x09, +0x7e, 0xdd, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x32, 0x82, 0x7f, 0x3a, +0x90, 0x89, 0x49, 0xea, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0xa4, 0x25, 0xb2, +0xe2, 0x7a, 0xf7, 0x10, 0x2e, 0x7b, 0xb0, 0xf9, +0x8d, 0x5e, 0xb7, 0xea, 0x64, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7d, 0x16, +0xb9, 0xd6, 0x3d, 0x9d, 0xaf, 0x59, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0x2a, +0x32, 0x0f, 0xd7, 0xd7, 0x6b, 0x17, 0x25, 0x4d, +0xc0, 0x0c, 0x03, 0x90, 0x7a, 0x66, 0xba, 0xc1, +0xa0, 0xed, 0x2a, 0x75, 0x46, 0xfc, 0xb1, 0xef, +0x31, 0x6b, 0xa1, 0x0f, 0x87, 0x8f, 0x60, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0x8a, 0x47, 0x34, 0x54, 0xe8, 0x54, 0x3f, +0x1b, 0xf4, 0xb3, 0xb6, 0xf9, 0xa4, 0xc4, 0xc1, +0x84, 0x2e, 0x3f, 0x01, 0xb5, 0xf2, 0x13, 0xb9, +0xc2, 0x00, 0xc1, 0x86, 0x28, 0x2f, 0x96, 0x3e, +0x8c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0x96, 0x59, 0xb1, 0xe5, 0xb6, +0xd9, 0x0b, 0x59, 0xb3, 0x7b, 0x8f, 0x66, 0x13, +0xb1, 0x26, 0x3d, 0xa1, 0x6a, 0x0c, 0xaf, 0x25, +0x09, 0xf5, 0xc0, 0xb8, 0xe2, 0x40, 0xfc, 0xe2, +0x94, 0x54, 0xa2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0xe0, 0x8f, 0xc8, +0x32, 0xc3, 0xc8, 0x4c, 0xc4, 0x1e, 0x1b, 0x1a, +0x35, 0x18, 0x58, 0xfa, 0xd5, 0x24, 0xb4, 0x3d, +0xe9, 0xb7, 0x15, 0x8b, 0xf5, 0xbc, 0x08, 0xba, +0xc2, 0xd6, 0xf3, 0xd5, 0xb5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0x3f, +0x29, 0x5b, 0x4b, 0xd4, 0xbc, 0xe5, 0x7a, 0xbf, +0x45, 0x14, 0xbe, 0x4b, 0x1a, 0x59, 0xf0, 0x76, +0x42, 0x52, 0xea, 0x1a, 0x38, 0x0c, 0x61, 0x73, +0xbb, 0x8b, 0x70, 0xf3, 0x0d, 0x5d, 0x1f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x6e, 0xa1, 0xca, 0xea, 0xc5, 0x65, 0xe0, +0x1e, 0x44, 0x73, 0x41, 0x30, 0xaf, 0xeb, 0xa8, +0x86, 0x60, 0xff, 0x77, 0x35, 0x5d, 0x86, 0x0f, +0x98, 0xb1, 0x51, 0x7c, 0x8d, 0xc7, 0xb3, 0xaa, +0xe6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0x6a, 0xb6, 0x9d, 0xbb, 0x16, +0xfb, 0x75, 0x27, 0xed, 0xed, 0x34, 0xcb, 0x3b, +0x32, 0x07, 0x8f, 0xa6, 0x0d, 0xea, 0xba, 0x5e, +0xdb, 0xfa, 0x4b, 0x28, 0x4c, 0x2a, 0x56, 0x91, +0x66, 0x6a, 0xa7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0xb2, 0x42, 0xe5, +0x1a, 0xba, 0xd1, 0x37, 0xfc, 0x55, 0xd4, 0x68, +0xca, 0xd2, 0x31, 0x59, 0x2e, 0x4d, 0x40, 0xa5, +0x24, 0x66, 0x09, 0x8a, 0xb6, 0x8d, 0x2e, 0x43, +0x17, 0x05, 0x93, 0x16, 0x6b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0x21, +0x81, 0x40, 0x8f, 0xc6, 0x0b, 0x34, 0x65, 0x86, +0x7e, 0x9a, 0x6a, 0xc7, 0x12, 0x79, 0x11, 0xb4, +0x32, 0x15, 0xe0, 0xab, 0x8a, 0x1a, 0xba, 0xd1, +0xf8, 0xfc, 0xd7, 0x86, 0x35, 0x0a, 0xf6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0x2a, 0x95, 0x86, 0x4e, 0xf0, 0x47, 0xfa, +0xa4, 0xe2, 0xeb, 0xc9, 0x73, 0x85, 0x18, 0x46, +0xab, 0x95, 0x6d, 0x0d, 0x5e, 0x38, 0x34, 0x89, +0x86, 0xe9, 0x8b, 0x27, 0x86, 0x89, 0x4a, 0x1e, +0x73, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0x47, 0xdb, 0x58, 0xf3, 0x34, +0x6f, 0xdd, 0xb8, 0x8a, 0x3a, 0x72, 0x4e, 0x3c, +0x72, 0xa2, 0x4d, 0x86, 0xd7, 0x3f, 0xd3, 0xaa, +0x61, 0x3d, 0x78, 0x6e, 0x8c, 0x19, 0x8c, 0xcf, +0x82, 0x99, 0x61, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0x6b, 0x55, 0xe1, +0x7b, 0xa0, 0x14, 0x96, 0xc0, 0x18, 0xd0, 0xfa, +0x42, 0x36, 0x61, 0x57, 0xef, 0xc3, 0x5b, 0xb5, +0xe4, 0x51, 0x25, 0xfe, 0x04, 0x2d, 0xe3, 0xfc, +0x01, 0xe2, 0xdb, 0x5a, 0x10, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0x7c, +0xe2, 0x56, 0xfc, 0xdb, 0x8d, 0xcb, 0x7d, 0xa7, +0x28, 0x21, 0x28, 0x8b, 0xda, 0x56, 0xc6, 0xac, +0xa2, 0x00, 0x07, 0x61, 0x81, 0x41, 0xa3, 0xad, +0x06, 0x9f, 0x66, 0xc3, 0x0d, 0x43, 0x6b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0xa3, 0xcb, 0xc7, 0x1e, 0x7b, 0x94, 0xbb, +0x66, 0x85, 0xad, 0x8f, 0xe3, 0x9f, 0xdf, 0x71, +0xa8, 0x1e, 0xac, 0x29, 0x8b, 0xe7, 0x35, 0xa1, +0x15, 0x11, 0xb6, 0x49, 0x0d, 0x58, 0x32, 0x75, +0xf8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0xa6, 0x60, 0xd3, 0x99, 0x90, +0xbe, 0xdd, 0x9f, 0xa7, 0xe8, 0xbd, 0x32, 0x5b, +0xe5, 0xfb, 0x50, 0xb7, 0x5c, 0xb6, 0xce, 0xaa, +0x94, 0x98, 0x20, 0x7e, 0x15, 0x5a, 0x6a, 0x45, +0x0e, 0xc7, 0x3c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0xb5, 0x48, 0x56, +0xa8, 0xd9, 0xea, 0xa1, 0x19, 0xb2, 0xf4, 0xc5, +0x56, 0x8b, 0xe3, 0xe2, 0x27, 0xa5, 0x46, 0xe7, +0x3b, 0x6c, 0xfb, 0xfe, 0x0d, 0xca, 0x15, 0x73, +0xc4, 0xa8, 0x5d, 0x66, 0x4a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0xff, +0x75, 0x33, 0x2d, 0xe4, 0x62, 0x6a, 0x52, 0x33, +0xf7, 0x6b, 0xce, 0x13, 0x40, 0x59, 0x74, 0xed, +0x42, 0xf8, 0xe0, 0x93, 0x89, 0xe9, 0x7f, 0x08, +0x8d, 0x89, 0xc8, 0x16, 0x42, 0xd0, 0x05, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0x15, 0x7c, 0x0d, 0xa6, 0x62, 0x56, 0xff, +0x59, 0xb8, 0xde, 0x8c, 0x3f, 0x67, 0xa4, 0x00, +0x40, 0x0d, 0x6a, 0x3c, 0x5b, 0xf5, 0xa3, 0x68, +0x13, 0xaf, 0x97, 0xb5, 0x45, 0x8b, 0x7e, 0xab, +0x3c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0x70, 0xa8, 0xdc, 0x50, 0x7f, +0xd0, 0x6b, 0xe5, 0xa3, 0xbd, 0x9c, 0xd6, 0x41, +0x08, 0x4b, 0xb1, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xf9, 0x05, 0xc6, 0x48, +0xc7, 0xbd, 0xdf, 0x43, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0x0a, 0x7b, 0x9f, +0x68, 0xd3, 0x96, 0x21, 0xe3, 0x67, 0xa6, 0xa1, +0xc2, 0x7b, 0x96, 0xf2, 0x77, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x8f, +0x2d, 0x6a, 0xf3, 0x60, 0x4a, 0xc5, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0xeb, +0x6d, 0x91, 0xd2, 0xa5, 0xba, 0xa1, 0xba, 0x81, +0xe7, 0xdf, 0xc0, 0xda, 0x6a, 0x82, 0xb0, 0x23, +0xf9, 0x67, 0xf0, 0x7b, 0xd6, 0x19, 0x6e, 0x7d, +0x4b, 0x0f, 0x5e, 0xb3, 0xc7, 0x37, 0xf4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0x9e, 0x5d, 0x96, 0xf8, 0xd1, 0x2c, 0x13, +0xc9, 0x75, 0x88, 0x19, 0xfa, 0x2b, 0xfe, 0x7e, +0x4c, 0xe9, 0x0b, 0x47, 0x3a, 0xc2, 0xc8, 0x6d, +0x70, 0x02, 0x40, 0x34, 0x44, 0x21, 0xa3, 0xda, +0x6e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0x58, 0x58, 0xfa, 0x9c, 0xdb, +0x3a, 0x80, 0x03, 0x5b, 0x95, 0x04, 0x4a, 0x32, +0x7b, 0xb6, 0x80, 0x85, 0x3e, 0xd3, 0x00, 0x84, +0x41, 0xa1, 0xb4, 0x9a, 0xe9, 0x9a, 0xda, 0x1b, +0x9f, 0xb0, 0xf7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0x94, 0x24, 0xee, +0x87, 0x56, 0x78, 0xec, 0x1b, 0xa0, 0x40, 0xbb, +0x4f, 0xbf, 0x94, 0xf8, 0xef, 0x4a, 0x49, 0xea, +0xc7, 0x64, 0x3e, 0xa1, 0x25, 0x45, 0xb5, 0xc8, +0x2f, 0xff, 0x34, 0xdf, 0x30, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x82, +0xd8, 0x3a, 0xe4, 0xc3, 0x76, 0xf4, 0xfe, 0xde, +0xdb, 0x1c, 0xcb, 0x0e, 0xa9, 0xf2, 0x19, 0x8a, +0x44, 0x60, 0x10, 0x03, 0x0c, 0x6c, 0x70, 0xcf, +0xc2, 0x23, 0x06, 0xf0, 0x57, 0x3e, 0x3f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0xb7, 0xc4, 0x40, 0x94, 0x99, 0xc8, 0x2f, +0x77, 0x97, 0xfa, 0x80, 0x74, 0xae, 0x3c, 0x48, +0x9a, 0xcd, 0x43, 0x12, 0x72, 0x0e, 0x02, 0xf6, +0x3a, 0x11, 0x82, 0x18, 0x96, 0x8c, 0x7b, 0xe9, +0xa3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0xa0, 0x54, 0xaf, 0xa5, 0xa8, +0x21, 0xfc, 0x42, 0xae, 0xaa, 0x31, 0xfb, 0xd3, +0xed, 0xfe, 0xd1, 0xb9, 0xcd, 0xaa, 0x30, 0x14, +0xcd, 0x44, 0x46, 0xb1, 0x7f, 0xda, 0xcb, 0x17, +0x80, 0x84, 0x52, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0xa2, 0xde, 0x13, +0x23, 0xb9, 0x0a, 0x45, 0xaf, 0x4c, 0x0f, 0x8c, +0x7f, 0x63, 0xc9, 0xa3, 0x8f, 0xb4, 0xc2, 0xa4, +0xde, 0xc6, 0x91, 0x26, 0xf8, 0xae, 0xf0, 0x57, +0xc6, 0x53, 0x01, 0xdb, 0xf0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0x73, +0x9f, 0x39, 0x26, 0x18, 0xad, 0xa2, 0x9b, 0xa7, +0xde, 0xdd, 0x94, 0xe8, 0xce, 0xce, 0x83, 0x15, +0xa8, 0xe1, 0xae, 0x7c, 0x40, 0x8c, 0xe3, 0x47, +0x1c, 0x45, 0x0e, 0xd0, 0x36, 0x6e, 0x74, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0x23, 0xf2, 0xb0, 0xc6, 0x7b, 0x45, 0xb8, +0x96, 0xbb, 0xf2, 0x24, 0x2a, 0x88, 0x44, 0x49, +0x6b, 0x08, 0x35, 0xa7, 0xf3, 0x78, 0x36, 0x5b, +0x22, 0x11, 0xca, 0x16, 0xd4, 0xe9, 0xdb, 0x05, +0x98, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0xe6, 0x5a, 0xd0, 0x84, 0x37, +0x64, 0x58, 0xe7, 0x43, 0x38, 0xaa, 0x3e, 0x2e, +0x99, 0x9d, 0x52, 0x8d, 0x66, 0xb8, 0x15, 0x43, +0x37, 0xdb, 0xcd, 0x12, 0x1d, 0x5d, 0x7f, 0x0a, +0xb6, 0x1b, 0x53, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x63, 0x4b, 0xeb, +0xb4, 0x1d, 0x3b, 0x6a, 0xd8, 0x38, 0x7e, 0x48, +0x6a, 0x72, 0xbb, 0x01, 0x60, 0xe3, 0xe8, 0x3d, +0xa4, 0x86, 0x8c, 0xa9, 0xfb, 0xe6, 0xae, 0xfb, +0xda, 0xb5, 0xe2, 0xae, 0xfb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0xce, +0x26, 0x7e, 0x0b, 0x30, 0x50, 0x9e, 0x76, 0x8e, +0x94, 0x54, 0xc2, 0x26, 0x08, 0x8b, 0x35, 0x17, +0x4d, 0x66, 0x0c, 0xf9, 0x1a, 0x4b, 0x5b, 0x91, +0xd9, 0x49, 0xa9, 0xd8, 0x0d, 0xdc, 0x2d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0x9f, 0xd9, 0xca, 0xea, 0x22, 0xee, 0xbb, +0xfc, 0x73, 0x7c, 0x0c, 0xc0, 0x64, 0x7c, 0x2b, +0xa6, 0xaf, 0xdf, 0xc8, 0xa5, 0x8e, 0xe6, 0xe2, +0x5f, 0x05, 0x7f, 0xca, 0x77, 0xbf, 0x41, 0x0d, +0x42, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0x5c, 0x5e, 0x2e, 0x2d, 0xbf, +0x42, 0xf4, 0x5f, 0x12, 0xfa, 0xb3, 0x1e, 0xb2, +0xa9, 0x69, 0x28, 0x40, 0xf9, 0x47, 0x48, 0x6a, +0x5e, 0xb1, 0xbe, 0x59, 0x13, 0xae, 0xf9, 0xed, +0x64, 0x27, 0xe8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x45, 0x52, 0x04, +0x2d, 0x87, 0x2e, 0xa9, 0x19, 0xcf, 0x40, 0xc2, +0x57, 0x72, 0xfc, 0xcb, 0x29, 0x53, 0x64, 0x98, +0xc7, 0xf1, 0x8c, 0x07, 0xd8, 0xdf, 0x95, 0x3f, +0x35, 0x11, 0x50, 0x5d, 0xf5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0xe6, +0x60, 0xd4, 0xea, 0x9a, 0x64, 0xf9, 0xc2, 0x13, +0xbc, 0x56, 0xa4, 0xf5, 0x95, 0xa2, 0xa1, 0x07, +0x1c, 0xaf, 0x34, 0x86, 0x9f, 0x3d, 0x75, 0xc2, +0xcf, 0xfe, 0x53, 0x04, 0x9a, 0xfb, 0x11, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0x39, 0x55, 0xf1, 0x29, 0xa4, 0xfd, 0x99, +0xf3, 0xdd, 0xaa, 0x92, 0xb3, 0xa1, 0xb1, 0xd9, +0x21, 0x62, 0x83, 0x03, 0xbf, 0x00, 0x75, 0x59, +0x2b, 0xea, 0xfc, 0x9b, 0x22, 0x14, 0xab, 0xbb, +0x1a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0xd8, 0x2f, 0x1e, 0xa9, 0x64, +0x78, 0xde, 0xec, 0xfe, 0x9f, 0xfe, 0xb1, 0x72, +0x19, 0x23, 0xec, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xc9, 0x18, 0x8f, 0x8c, +0x88, 0x38, 0x7d, 0x94, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0x3b, 0xab, 0x21, +0x91, 0x32, 0x5f, 0x39, 0x08, 0xec, 0x08, 0x38, +0x00, 0xc3, 0x81, 0xfa, 0x39, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x7f, +0x13, 0xf2, 0xe8, 0x5d, 0x48, 0x64, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x98, +0xb7, 0x66, 0x0e, 0xf9, 0xfc, 0x7f, 0x5c, 0xda, +0xba, 0xc8, 0x2b, 0x01, 0x4f, 0xc5, 0xc3, 0x7e, +0x51, 0x4f, 0x43, 0xaf, 0xe8, 0xe0, 0xc5, 0x23, +0x64, 0xee, 0xc3, 0xad, 0xa2, 0xac, 0x80, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0x0e, 0x2e, 0xdb, 0xfe, 0x1c, 0xdd, 0xb7, +0x10, 0x76, 0x07, 0x93, 0x5a, 0xe1, 0xdc, 0x37, +0x88, 0x81, 0x7e, 0x06, 0x8a, 0x42, 0x96, 0x5a, +0x68, 0x6b, 0x36, 0xf3, 0xb1, 0x3d, 0x05, 0x07, +0x90, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0xd6, 0x5a, 0xcd, 0x8e, 0xaf, +0xca, 0x1e, 0x1e, 0x78, 0xf2, 0xf7, 0x75, 0x9c, +0x17, 0xac, 0xcd, 0xca, 0x87, 0xc1, 0x78, 0x3a, +0xf0, 0x46, 0x42, 0x7f, 0x89, 0x3b, 0x08, 0x56, +0x31, 0x5a, 0x7d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0x8c, 0xa8, 0x90, +0xd1, 0xb8, 0xe2, 0xd3, 0x2b, 0xec, 0xa7, 0x50, +0x32, 0xf4, 0xcb, 0x76, 0xd5, 0xe2, 0x4e, 0xc2, +0xb6, 0x23, 0xf4, 0x58, 0xda, 0x96, 0xaa, 0x02, +0x9c, 0x91, 0x09, 0x7f, 0x05, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0x46, +0xa9, 0xfb, 0x0e, 0xe5, 0x89, 0x35, 0x84, 0x25, +0x53, 0xd1, 0x04, 0xc5, 0x39, 0xd3, 0x38, 0x4c, +0x91, 0xc6, 0x7a, 0x7a, 0x5b, 0xe9, 0x89, 0x6b, +0x3e, 0xbf, 0x86, 0x0e, 0xc0, 0x16, 0xdd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0x55, 0x55, 0x67, 0x13, 0xe3, 0xc0, 0x5b, +0x14, 0xbc, 0xd3, 0x92, 0x03, 0xb8, 0x88, 0x22, +0xb2, 0x42, 0x15, 0x87, 0x73, 0x4f, 0x7c, 0x53, +0x3a, 0x0c, 0x41, 0x28, 0x0e, 0xcd, 0x5c, 0xa4, +0x05, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0x0e, 0xc9, 0x60, 0x25, 0xd9, +0xad, 0x62, 0x5f, 0x9e, 0x4a, 0x74, 0x71, 0x95, +0x31, 0x2f, 0x11, 0x06, 0x56, 0x99, 0xd6, 0xc6, +0xc5, 0x83, 0x1b, 0x34, 0xfd, 0x41, 0x53, 0xed, +0x86, 0x03, 0xfc, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0x12, 0xd5, 0x4c, +0x05, 0xa0, 0x09, 0x71, 0x70, 0x52, 0xc2, 0xfc, +0xf0, 0xc9, 0xcf, 0xd5, 0x9a, 0x5a, 0xc2, 0xcf, +0xf9, 0x21, 0xf4, 0x6f, 0xfc, 0x66, 0x80, 0xc6, +0x5b, 0xf8, 0x1b, 0x22, 0x09, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0x9e, +0x6f, 0x0b, 0xa1, 0xca, 0x46, 0x8b, 0xbe, 0x06, +0x76, 0xf8, 0xe0, 0x57, 0x60, 0x0d, 0x55, 0xe9, +0xda, 0x5e, 0x4b, 0xca, 0x3b, 0x8a, 0x03, 0xdd, +0x13, 0xd3, 0x4a, 0xac, 0x13, 0x59, 0x03, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0xba, 0x1a, 0x3b, 0x3d, 0x63, 0x64, 0x03, +0xef, 0xad, 0x65, 0xa2, 0x4c, 0xb2, 0x4c, 0x31, +0x17, 0xcf, 0xff, 0xf4, 0x65, 0xf3, 0x08, 0x6b, +0xed, 0x70, 0x4d, 0x4a, 0x6e, 0x40, 0x26, 0x6d, +0x19, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0x8d, 0x09, 0xbf, 0x2b, 0x9b, +0xd9, 0x07, 0xc0, 0x0d, 0xa2, 0x88, 0xba, 0x2b, +0xf5, 0x69, 0x8c, 0x30, 0x6a, 0x64, 0x4d, 0xde, +0xa2, 0xfd, 0x48, 0x67, 0xd4, 0xdf, 0x40, 0x57, +0xb3, 0x4d, 0xed, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0x5c, 0x7e, 0xe0, +0x6a, 0x90, 0x62, 0x55, 0xba, 0x37, 0x56, 0x6c, +0x0a, 0x29, 0x65, 0x08, 0x21, 0x0c, 0x43, 0x54, +0x1a, 0xfe, 0xc0, 0x36, 0x0f, 0xfc, 0x90, 0xbb, +0x99, 0x84, 0xa1, 0xa4, 0x8e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0x58, +0x0c, 0x05, 0xf3, 0x6a, 0xc6, 0xef, 0x50, 0x03, +0xa6, 0xed, 0x01, 0x47, 0x94, 0xdd, 0x7d, 0xac, +0x1f, 0x17, 0xa4, 0x13, 0x82, 0xbf, 0xf9, 0x4d, +0x38, 0xf5, 0xb3, 0x1e, 0x87, 0x9a, 0xeb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0xaf, 0x2d, 0x00, 0x56, 0x05, 0xf6, 0x0b, +0x93, 0x1f, 0x41, 0x45, 0xf7, 0x77, 0x01, 0xf2, +0xca, 0x14, 0xc9, 0x0b, 0x4a, 0x38, 0xd8, 0x6d, +0xd6, 0xf8, 0x40, 0x9f, 0x02, 0x55, 0x30, 0xe9, +0xe7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0xc6, 0x76, 0xd1, 0x29, 0x8b, +0x34, 0x51, 0x0d, 0x67, 0xdd, 0x86, 0x8f, 0x9d, +0x1f, 0xd5, 0xe8, 0xc3, 0xed, 0xf3, 0x3e, 0x51, +0xd1, 0x37, 0xda, 0xf4, 0x9c, 0xfa, 0x21, 0xec, +0x9d, 0x16, 0xb8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0xd5, 0x61, 0x25, +0x5a, 0x11, 0x4d, 0xf8, 0x8d, 0xdd, 0xc8, 0x2e, +0x86, 0xf2, 0x69, 0x2d, 0x81, 0xab, 0xfd, 0x7b, +0x69, 0x4a, 0x91, 0x33, 0x75, 0xae, 0xbb, 0xb0, +0x67, 0x86, 0x05, 0xcd, 0x2e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x09, +0x08, 0xb2, 0x25, 0xcf, 0x85, 0xdb, 0xcb, 0x08, +0x6c, 0xbc, 0xef, 0x6f, 0x4c, 0x63, 0x1d, 0x4a, +0xe9, 0x2d, 0xd8, 0x27, 0xf1, 0x59, 0x64, 0xd3, +0x8f, 0x79, 0x1f, 0xd7, 0x7b, 0x1b, 0x52, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0xc8, 0x80, 0x57, 0x94, 0x16, 0x62, 0x7d, +0x7a, 0xcc, 0x86, 0x36, 0x34, 0xa8, 0x97, 0x96, +0x38, 0x9e, 0x61, 0x1f, 0x58, 0x9b, 0xe3, 0x47, +0x0f, 0x66, 0x08, 0x17, 0xc6, 0x47, 0x03, 0x45, +0x48, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0xce, 0xb3, 0x00, 0xab, 0xf6, +0xac, 0x73, 0x3c, 0x46, 0xf7, 0x71, 0xb5, 0x76, +0x0f, 0x83, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x09, 0x42, 0x80, 0xa8, +0x52, 0x96, 0xe3, 0x2f, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0xbf, 0x20, 0x96, +0x73, 0x35, 0x75, 0x77, 0xb7, 0x5f, 0xf3, 0xdd, +0x35, 0x7c, 0x40, 0x89, 0x96, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x67, +0x37, 0x94, 0x15, 0x63, 0x75, 0xc6, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0x0c, +0x57, 0xe3, 0x94, 0x17, 0x9a, 0x82, 0x76, 0x25, +0x17, 0x11, 0x0f, 0x6e, 0x22, 0xa4, 0x07, 0xc4, +0x27, 0x9e, 0x40, 0x9e, 0x6c, 0x85, 0xd7, 0xe9, +0x80, 0x81, 0xae, 0xd8, 0xef, 0x37, 0x3e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0x51, 0xd5, 0x68, 0xfa, 0x25, 0x00, 0x6f, +0xc4, 0x5c, 0x31, 0x25, 0x89, 0xf4, 0x88, 0x88, +0xd5, 0x1d, 0xa3, 0x2e, 0xeb, 0xdb, 0x74, 0xff, +0xb3, 0x9d, 0xfa, 0x1f, 0x0b, 0xd2, 0x91, 0xc4, +0x58, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0x9b, 0xe3, 0x54, 0x04, 0xdd, +0xd4, 0xd1, 0xce, 0x80, 0x23, 0xdc, 0x2b, 0xd1, +0x22, 0xd1, 0x63, 0x77, 0xfc, 0xff, 0x5f, 0xd9, +0x7e, 0x9e, 0xef, 0xf0, 0xa9, 0x30, 0xae, 0xb4, +0x16, 0xdd, 0x02, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0x96, 0x61, 0xc1, +0x12, 0x43, 0x75, 0x2d, 0x51, 0x2b, 0x54, 0x6f, +0x4e, 0x20, 0x08, 0xf8, 0x0d, 0x6e, 0x49, 0x71, +0x5c, 0x71, 0x23, 0xaf, 0xde, 0x9c, 0xa7, 0x2a, +0x2d, 0x31, 0xa0, 0x03, 0x7d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0x1a, +0xb0, 0xd0, 0x6f, 0xc3, 0x10, 0x22, 0xb6, 0xcf, +0x30, 0x16, 0xae, 0x05, 0x99, 0x7f, 0x9a, 0x59, +0xd1, 0x36, 0xe3, 0xad, 0xcd, 0xc0, 0x58, 0x2a, +0xf4, 0x08, 0xb6, 0x19, 0x3e, 0x84, 0xb8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0x64, 0x21, 0x29, 0xc2, 0xe9, 0x0b, 0x54, +0xae, 0xc2, 0x86, 0x80, 0xf9, 0x82, 0x54, 0xa5, +0xd1, 0xce, 0x0f, 0x4c, 0x7e, 0xdc, 0xdd, 0x4e, +0x06, 0xc4, 0x84, 0xc8, 0x56, 0x9b, 0x3a, 0x8b, +0x2b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0x3a, 0xca, 0x73, 0x96, 0x36, +0x36, 0xa4, 0xb7, 0xb9, 0x52, 0x01, 0x3f, 0xf4, +0x79, 0x13, 0x36, 0xff, 0xae, 0x76, 0xf7, 0xae, +0xf0, 0x3b, 0x95, 0xaf, 0x66, 0xfd, 0x82, 0x4a, +0xe7, 0x7c, 0xce, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0x9e, 0x4e, 0x2d, +0xe5, 0xe5, 0xa1, 0xa7, 0x93, 0x8d, 0x6c, 0x42, +0x39, 0x4b, 0xce, 0x7c, 0x7a, 0xe6, 0x8f, 0x8d, +0xb3, 0x29, 0x5e, 0xb6, 0xce, 0x3c, 0xf7, 0xf9, +0x16, 0xa7, 0x34, 0x8e, 0x88, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0x73, +0x38, 0x13, 0x13, 0x77, 0xeb, 0x43, 0xff, 0x6b, +0xca, 0x71, 0x66, 0xc3, 0xe5, 0xbe, 0xce, 0xce, +0xcb, 0x27, 0xaf, 0x90, 0x65, 0x65, 0x76, 0xf2, +0x28, 0xff, 0x9f, 0xe7, 0x27, 0x1d, 0x81, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0x11, 0x65, 0x67, 0x74, 0x6e, 0xc6, 0xee, +0x7f, 0xab, 0xf5, 0x8e, 0xc8, 0x03, 0x7d, 0xa2, +0x35, 0xd4, 0x49, 0xec, 0x2e, 0xa1, 0x38, 0x4b, +0x65, 0x8e, 0x82, 0xd4, 0x66, 0xbb, 0xca, 0x4a, +0x25, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0x36, 0x41, 0xd6, 0xa7, 0xc8, +0x65, 0x0f, 0x5a, 0xe9, 0xaf, 0xa6, 0x14, 0x6f, +0x29, 0x9a, 0xc0, 0x3f, 0xac, 0x8b, 0xa0, 0x58, +0x52, 0xe9, 0xba, 0xa3, 0xa3, 0x94, 0x88, 0xc4, +0x98, 0x0b, 0x11, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0x59, 0x92, 0x50, +0xfe, 0xd9, 0xfc, 0xf9, 0x84, 0x66, 0xbb, 0x1e, +0x1e, 0x28, 0xb1, 0x05, 0xd3, 0x5a, 0x22, 0x18, +0x61, 0xe6, 0x59, 0x65, 0xdf, 0xa1, 0x15, 0x84, +0x9a, 0xd4, 0x45, 0x3e, 0x94, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0x29, +0xd7, 0xd1, 0xd0, 0xce, 0x0c, 0x1a, 0x7a, 0x8f, +0xd7, 0xda, 0xc7, 0x30, 0x90, 0x58, 0xb6, 0x8e, +0xbb, 0x59, 0xc5, 0x95, 0x98, 0x0e, 0xbc, 0x85, +0xde, 0x9a, 0x57, 0xa2, 0xd1, 0x74, 0x73, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0xdc, 0xc8, 0xd0, 0xf8, 0x50, 0x4e, 0x5d, +0x65, 0x24, 0xc7, 0xfd, 0xfd, 0x86, 0xbb, 0xa6, +0xd3, 0xf0, 0x13, 0xaf, 0xeb, 0xa0, 0x31, 0xe7, +0xc5, 0xb2, 0x33, 0xf7, 0xf5, 0x8a, 0xc7, 0xa2, +0x8e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0x78, 0x5c, 0xf7, 0x5e, 0x1a, +0x77, 0x91, 0xbb, 0x40, 0xdb, 0x01, 0x5a, 0xc2, +0x9b, 0x63, 0x3c, 0xda, 0xe4, 0x0e, 0x79, 0xb1, +0x4d, 0x40, 0x61, 0x99, 0x67, 0xc1, 0x78, 0x7c, +0x79, 0xc3, 0x04, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0x63, 0xb4, 0x07, +0x37, 0x68, 0x4b, 0xbc, 0x9b, 0x0a, 0xa2, 0x88, +0x8c, 0x9f, 0xd2, 0xb7, 0xb4, 0x09, 0xdf, 0xe0, +0x07, 0x15, 0xde, 0xb9, 0x7e, 0xc3, 0xa5, 0xc9, +0x5f, 0x90, 0x63, 0x7d, 0xca, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0x64, +0x23, 0x10, 0xcd, 0x2b, 0x9a, 0x1a, 0x96, 0x34, +0xc9, 0x59, 0xe0, 0xc4, 0x9b, 0xfa, 0x34, 0xcc, +0xf4, 0x4b, 0xf6, 0x0c, 0x26, 0x57, 0xfb, 0x40, +0x77, 0x1b, 0x67, 0xe1, 0xf5, 0xaf, 0x06, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0x50, 0xde, 0xad, 0x3c, 0x98, 0xc5, 0x77, +0xe0, 0x3b, 0x45, 0xa9, 0x9a, 0xff, 0x11, 0x32, +0x3a, 0x9f, 0x48, 0xbb, 0x13, 0xff, 0xf2, 0x5a, +0x7b, 0x99, 0xd6, 0xb6, 0x35, 0xc5, 0xb0, 0xeb, +0x90, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0x81, 0xd8, 0x40, 0x47, 0xec, +0x12, 0x39, 0x82, 0x09, 0xe3, 0xb0, 0xe0, 0xb3, +0xcc, 0xab, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x54, 0x89, 0xf9, 0xe6, +0xa7, 0x4c, 0xe0, 0xbc, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0x37, 0x35, 0xac, +0x8e, 0x56, 0xdf, 0xed, 0x81, 0x85, 0x78, 0xd7, +0x5f, 0x1e, 0x94, 0x7f, 0xe0, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0x86, +0x62, 0x1d, 0x5b, 0x02, 0x1c, 0x57, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0x60, +0xf8, 0x12, 0x4b, 0xb6, 0x01, 0x1e, 0x68, 0x6c, +0xa8, 0x14, 0x4b, 0xcd, 0x50, 0x43, 0x8a, 0xa1, +0x79, 0x7b, 0x4d, 0x07, 0x81, 0x66, 0xff, 0x36, +0x70, 0x54, 0xaf, 0xd6, 0x5c, 0xdc, 0xd5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0x45, 0x96, 0x6e, 0x14, 0xd2, 0x5a, 0x5e, +0x6b, 0xd5, 0xe1, 0xa2, 0xc3, 0xe6, 0x3e, 0x5c, +0x4e, 0xbd, 0xed, 0x28, 0xd0, 0x3c, 0x94, 0x84, +0xe4, 0x96, 0xbf, 0xa8, 0x69, 0xfb, 0x27, 0x4b, +0x09, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0x66, 0x39, 0x5c, 0x0c, 0x2a, +0xac, 0x92, 0x26, 0xe5, 0x5a, 0x9e, 0xce, 0x20, +0x5c, 0xef, 0x6a, 0xcb, 0x31, 0xb8, 0x81, 0x16, +0xeb, 0x8b, 0xaa, 0x4a, 0x81, 0x68, 0x43, 0x78, +0x26, 0xe4, 0xcd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0xea, 0x5c, 0xa6, +0x4a, 0x12, 0xef, 0x65, 0x2a, 0xe2, 0xbe, 0x8f, +0x4a, 0x15, 0xd7, 0xb9, 0xb2, 0xd1, 0x83, 0x8b, +0x82, 0x34, 0x3c, 0x0a, 0x91, 0xb3, 0x9f, 0x7c, +0x56, 0xa5, 0x4e, 0x9a, 0x07, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0xe9, +0x3f, 0xb2, 0x6e, 0xff, 0xd6, 0xf3, 0x80, 0xd1, +0x38, 0xe7, 0xb6, 0x40, 0x6d, 0x88, 0xa3, 0x6c, +0x7c, 0x46, 0xf0, 0x07, 0x9a, 0x33, 0x75, 0xef, +0x20, 0x88, 0x3a, 0x0a, 0x10, 0x6d, 0xb4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0x4a, 0x32, 0xb3, 0x13, 0xb9, 0xa8, 0xb2, +0x66, 0x18, 0x1c, 0xd0, 0x82, 0xc6, 0x78, 0x4d, +0xfd, 0x03, 0xb3, 0xba, 0xf6, 0xcb, 0x12, 0x70, +0x34, 0x8d, 0xc2, 0xf6, 0x98, 0x7f, 0xa5, 0x44, +0xf9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0xaf, 0x81, 0x29, 0x5c, 0x36, +0x76, 0x9c, 0x48, 0x19, 0x65, 0x5b, 0xc9, 0xa7, +0x15, 0x7e, 0xdb, 0x6e, 0x79, 0x80, 0x9e, 0x7a, +0x00, 0xf1, 0xed, 0xf9, 0xe3, 0xb7, 0xb3, 0x24, +0x63, 0xb0, 0xe6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0x8b, 0x07, 0x89, +0xc9, 0x15, 0x13, 0x69, 0xcf, 0x36, 0xdb, 0x85, +0x36, 0xb4, 0xea, 0x5f, 0x73, 0xbf, 0x0a, 0xb9, +0x24, 0x20, 0x46, 0xaa, 0x0e, 0x80, 0xca, 0x80, +0x98, 0x27, 0xe5, 0xb9, 0x64, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0x85, +0xd2, 0xae, 0xa4, 0x3f, 0x96, 0x0c, 0x8e, 0x1c, +0x59, 0x87, 0xec, 0xd9, 0x62, 0x0a, 0x4c, 0x3b, +0x0d, 0xad, 0x71, 0x02, 0x85, 0x50, 0xf2, 0x51, +0x5a, 0xe7, 0x77, 0x87, 0x4e, 0xb7, 0xda, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0x55, 0x37, 0xfc, 0xe2, 0xc0, 0x9a, 0xfa, +0x15, 0x47, 0x07, 0x79, 0xd7, 0x19, 0x70, 0x2f, +0xe4, 0x99, 0x05, 0x0a, 0xe6, 0x67, 0xce, 0x79, +0xaa, 0x5a, 0x90, 0xa4, 0x13, 0xf6, 0xcc, 0xbb, +0x87, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0xb2, 0x53, 0x1c, 0x86, 0x8b, +0xaa, 0x2e, 0x6d, 0x84, 0x32, 0x15, 0x60, 0xb9, +0x5f, 0x8f, 0x89, 0x13, 0xba, 0x56, 0x1d, 0xc0, +0x6a, 0x0f, 0xd8, 0xe6, 0x63, 0xbe, 0xf0, 0x3d, +0xd9, 0xb7, 0xa5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x04, 0xd6, 0x94, +0x86, 0x77, 0x87, 0x16, 0xf2, 0x24, 0x66, 0x5f, +0x2b, 0xdb, 0x1d, 0xcd, 0x17, 0x36, 0x71, 0x14, +0x00, 0x65, 0xff, 0x05, 0xd0, 0xfb, 0x23, 0x99, +0x2d, 0x4b, 0x7a, 0x64, 0x3f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0xad, +0xcf, 0xcf, 0xa3, 0x6e, 0x3c, 0x3f, 0xcf, 0x88, +0x8a, 0x8e, 0x99, 0x94, 0x73, 0x38, 0x2e, 0x28, +0x8b, 0x68, 0x0c, 0xa3, 0x59, 0x6d, 0x88, 0xe3, +0xc4, 0xf4, 0x17, 0x52, 0xa4, 0x5c, 0x2b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x0e, 0x34, 0x92, 0x59, 0x86, 0x38, 0xf4, +0xfb, 0x7d, 0xd3, 0xc6, 0x94, 0x29, 0x0c, 0xc8, +0xe8, 0x8d, 0x4d, 0xe3, 0xac, 0x22, 0xc9, 0x9f, +0x77, 0x48, 0x48, 0x5d, 0x05, 0x09, 0xce, 0xd2, +0x38, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0x32, 0x8f, 0x8d, 0xfc, 0x71, +0x1d, 0xd8, 0xfd, 0x2c, 0x58, 0x21, 0x27, 0x66, +0x0d, 0x2e, 0xfd, 0xba, 0x93, 0x88, 0xac, 0x64, +0xe8, 0x65, 0xf6, 0xeb, 0xe6, 0xf0, 0x76, 0xe2, +0xcf, 0xb0, 0x14, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0x23, 0x74, 0x49, +0x7c, 0x28, 0xeb, 0x73, 0xc7, 0x6b, 0x0d, 0xd2, +0x45, 0x4f, 0x9a, 0x10, 0x7b, 0xe9, 0xfc, 0xd8, +0xcc, 0xc0, 0xb0, 0x1b, 0x47, 0xdd, 0x46, 0x1d, +0x62, 0xae, 0x93, 0xf6, 0x22, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x6d, +0x84, 0x13, 0xf6, 0x94, 0xc4, 0x47, 0xb7, 0xaa, +0x33, 0xb4, 0xe7, 0x7c, 0xdf, 0x57, 0xf3, 0xfc, +0x4c, 0x9d, 0x83, 0x36, 0x04, 0xec, 0xc4, 0x7d, +0xf7, 0xe4, 0xbf, 0x2f, 0x0a, 0xe5, 0x0f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0xb6, 0xca, 0x6f, 0x59, 0x88, 0x52, 0x79, +0x13, 0x66, 0xcd, 0x48, 0xb1, 0x59, 0x70, 0xc8, +0xfd, 0xc2, 0x7d, 0x0a, 0xa7, 0xb6, 0x52, 0xd2, +0xc0, 0x50, 0x1c, 0xfa, 0x1c, 0x35, 0x72, 0xbb, +0x14, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0xe7, 0xbc, 0x77, 0xee, 0x9d, +0xc2, 0x8a, 0x6f, 0xe6, 0x7e, 0xf3, 0xc2, 0x06, +0xcd, 0x56, 0xd5, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x9c, 0xcd, 0xed, 0xe2, +0x51, 0x33, 0xcf, 0xcb, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0xd3, 0x86, 0xee, +0x4e, 0xd8, 0x8d, 0x7d, 0x34, 0x2b, 0xe9, 0x2c, +0xaf, 0xaf, 0x72, 0xb6, 0x20, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x3e, +0xe7, 0x1c, 0xc9, 0xde, 0x9c, 0x3b, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x1f, +0x69, 0xc0, 0x78, 0x55, 0x37, 0x79, 0x34, 0x5d, +0x1d, 0x19, 0x61, 0xa5, 0x15, 0xfe, 0xc7, 0xee, +0x14, 0xdf, 0xb6, 0x62, 0x73, 0xd4, 0xf6, 0x95, +0x32, 0xc3, 0x53, 0xae, 0x91, 0xce, 0xd6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0xb5, 0x4a, 0x57, 0x68, 0x29, 0x6f, 0xe9, +0x48, 0xa5, 0x69, 0xe0, 0x91, 0x85, 0x07, 0xc6, +0xb9, 0xab, 0x74, 0x27, 0x7f, 0xc2, 0x75, 0x02, +0x2e, 0x4d, 0x52, 0x0a, 0x5b, 0x48, 0xac, 0x39, +0x4d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0xe1, 0x4d, 0x8b, 0xb8, 0x95, +0x9d, 0x3e, 0xfd, 0xda, 0x6a, 0x32, 0xa2, 0x09, +0x5b, 0xd4, 0x34, 0x95, 0xc9, 0x4f, 0x0b, 0x6c, +0x1a, 0xf5, 0x9a, 0x43, 0x7a, 0x17, 0xab, 0x92, +0x00, 0x41, 0x87, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0xee, 0x8d, 0xda, +0x27, 0x59, 0x66, 0xa8, 0xbd, 0x51, 0xb2, 0xc8, +0x82, 0xe7, 0x09, 0x5f, 0xaa, 0x61, 0xab, 0x22, +0x9c, 0x65, 0x78, 0xf8, 0x6d, 0x53, 0xfc, 0x4f, +0x22, 0xab, 0xf3, 0x04, 0xd6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x56, +0x00, 0x81, 0xdc, 0xe1, 0xe6, 0xf4, 0x79, 0x14, +0x47, 0x57, 0x81, 0x68, 0x03, 0xcd, 0xae, 0x53, +0x76, 0x62, 0x27, 0x5e, 0x01, 0x1e, 0xec, 0x17, +0x6f, 0x08, 0xb9, 0xf4, 0xbc, 0xd8, 0x8b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0xbe, 0x88, 0x1f, 0x24, 0xc0, 0x76, 0x59, +0x05, 0x29, 0xa6, 0x89, 0xa7, 0x62, 0xe7, 0x19, +0xe1, 0xd2, 0x0b, 0xe8, 0xfc, 0xf4, 0x2b, 0x88, +0x70, 0x0c, 0x39, 0x31, 0x24, 0xb8, 0xe5, 0xc8, +0x23, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0x90, 0x4f, 0x35, 0x5c, 0xd3, +0x6f, 0x8c, 0xf7, 0x7a, 0x5b, 0xc6, 0x82, 0xec, +0xd9, 0x25, 0x0e, 0x51, 0xbe, 0x75, 0x23, 0xd2, +0x3b, 0xff, 0x02, 0xe0, 0x16, 0x42, 0xed, 0x32, +0xd2, 0x21, 0x2e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0xff, 0xf1, 0xfa, +0x9a, 0xef, 0x34, 0xaf, 0x96, 0xd6, 0x07, 0xff, +0x50, 0x65, 0x57, 0x25, 0xcb, 0x41, 0xe1, 0x2c, +0xc2, 0x84, 0xd5, 0xae, 0xf6, 0x4b, 0x70, 0xd7, +0x86, 0xe9, 0xd9, 0xf6, 0x1a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0xe6, +0x3b, 0xb4, 0x41, 0x98, 0x39, 0x83, 0xa5, 0x06, +0x1d, 0x82, 0xcd, 0x74, 0xc5, 0xf4, 0x6b, 0x10, +0xaf, 0xff, 0x4a, 0xfb, 0x7d, 0x5d, 0x40, 0x1c, +0xfa, 0x90, 0x5b, 0xbe, 0x06, 0x2e, 0xe9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0x4a, 0x40, 0x7a, 0xca, 0xe9, 0x07, 0xdb, +0xec, 0x76, 0x5e, 0x88, 0x8b, 0x4f, 0xa9, 0xe6, +0xa8, 0x8b, 0xeb, 0x7d, 0xaf, 0xea, 0x10, 0x35, +0x58, 0xcf, 0xb4, 0xfe, 0x19, 0x85, 0x95, 0x33, +0xc8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0xf5, 0xfe, 0x79, 0xe6, 0x23, +0x43, 0x14, 0x0e, 0x09, 0x03, 0xb4, 0x37, 0x59, +0x73, 0x68, 0x3d, 0xe9, 0xee, 0x17, 0xcb, 0xfa, +0x38, 0x6d, 0x4c, 0xe0, 0x0c, 0x82, 0x1b, 0xb7, +0xf5, 0x90, 0xd1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0x77, 0x5d, 0xd5, +0xf7, 0x19, 0x20, 0x9d, 0x9c, 0xe5, 0x78, 0xb9, +0x02, 0x7c, 0xd5, 0x62, 0x88, 0x73, 0x26, 0x15, +0x6a, 0x02, 0xc5, 0x69, 0xcc, 0x5c, 0x75, 0xff, +0x11, 0xfb, 0x18, 0xb3, 0x62, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0xd6, +0xf4, 0x0a, 0xc1, 0x54, 0x45, 0xd6, 0xc2, 0x63, +0x32, 0x8c, 0x1e, 0x8e, 0xfd, 0x09, 0x73, 0xb5, +0x54, 0x34, 0x59, 0xbb, 0x05, 0x0f, 0xe1, 0x73, +0x79, 0xf4, 0x39, 0x59, 0x67, 0xda, 0x2c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0xa6, 0xe7, 0xf1, 0xc6, 0xfb, 0x2f, 0x16, +0xa8, 0x81, 0xb1, 0x4e, 0xe8, 0xf3, 0x79, 0x41, +0xf7, 0xee, 0x03, 0xc0, 0xd6, 0x75, 0xe6, 0x5a, +0x32, 0x7c, 0x9f, 0x8d, 0x2b, 0xcc, 0xdf, 0x42, +0xb8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x1e, 0xa0, 0x3b, 0xf9, 0x4d, +0x1e, 0xe0, 0x33, 0x97, 0x08, 0x30, 0x16, 0x4e, +0x7b, 0x64, 0xd5, 0xfa, 0x25, 0x3a, 0xb0, 0x89, +0x1e, 0xde, 0x22, 0xed, 0xb1, 0x1b, 0xf3, 0x53, +0x7a, 0x2c, 0x78, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0x51, 0xfb, 0x02, +0x97, 0x96, 0x6c, 0xf5, 0x1a, 0xee, 0x70, 0x44, +0x2e, 0x0f, 0x24, 0xaf, 0x1b, 0x0f, 0x2c, 0x98, +0x7a, 0x19, 0xfb, 0xa9, 0xee, 0xd6, 0x72, 0x7d, +0x73, 0x96, 0x5f, 0xca, 0x38, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0xf6, +0xe8, 0x25, 0x8e, 0x40, 0xc9, 0xf0, 0xd0, 0x58, +0xfa, 0xed, 0xf4, 0xaf, 0xb4, 0x46, 0x25, 0x1e, +0x2f, 0x37, 0xcc, 0x93, 0x68, 0xa5, 0xf6, 0x51, +0xe8, 0xc4, 0xfd, 0x42, 0x98, 0x6f, 0x73, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x91, 0xbe, 0x40, 0x0a, 0x57, 0xaf, 0xec, +0x97, 0x7b, 0xfd, 0x6d, 0x9f, 0xbb, 0xd6, 0x81, +0x00, 0x71, 0x96, 0xeb, 0xab, 0x21, 0xd0, 0x7b, +0xd6, 0x20, 0xcf, 0x53, 0xf4, 0x0b, 0x42, 0xb7, +0x91, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0x92, 0xc8, 0xc7, 0x67, 0xde, +0xd1, 0xf0, 0x0a, 0xcd, 0x62, 0x1a, 0xac, 0x9a, +0xed, 0xd2, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x6a, 0xc4, 0xfc, 0xb2, +0x15, 0x7d, 0xb7, 0x26, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0xd2, 0x8c, 0x6d, +0x36, 0x8c, 0x4e, 0xfc, 0xd9, 0x9e, 0x12, 0xed, +0xcc, 0x26, 0xa6, 0x63, 0xbd, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x0a, +0x84, 0x94, 0x0d, 0x37, 0xfb, 0x50, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x4c, +0xd1, 0xf9, 0x9b, 0x4d, 0xcd, 0x55, 0x7b, 0xb6, +0xcc, 0x13, 0xd0, 0x85, 0x70, 0x71, 0xf0, 0x9c, +0x00, 0xf7, 0xb4, 0x52, 0xde, 0x12, 0xee, 0x67, +0x9f, 0xb2, 0x5d, 0x8c, 0xa2, 0x66, 0x5f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0x03, 0x9f, 0x47, 0x26, 0x93, 0x82, 0x30, +0xb1, 0x0e, 0x1f, 0x6d, 0xfc, 0x69, 0x7f, 0xcc, +0xdd, 0xbe, 0xb8, 0x32, 0x9b, 0x2f, 0x3d, 0x64, +0xe9, 0x80, 0xb7, 0x4c, 0xd3, 0xcb, 0xb5, 0xe7, +0x65, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0x45, 0x9d, 0x88, 0xea, 0x0e, +0xf4, 0x85, 0x50, 0x95, 0x07, 0x68, 0xa3, 0xeb, +0xee, 0x87, 0x79, 0xc1, 0x29, 0x0f, 0x16, 0x6d, +0x1b, 0xef, 0xb9, 0x32, 0x31, 0xbe, 0x09, 0x31, +0x03, 0x90, 0x76, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0x83, 0x4d, 0xb3, +0xb6, 0x6e, 0x17, 0x68, 0x3c, 0x58, 0xc5, 0xb9, +0xe6, 0x7b, 0xa8, 0x08, 0xa3, 0xbf, 0x18, 0x2b, +0x97, 0x5e, 0xc4, 0xce, 0x3e, 0x53, 0xfc, 0x12, +0x3b, 0x5f, 0xa6, 0x9b, 0xaf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0x50, +0x73, 0x9d, 0xf2, 0x1c, 0x77, 0x44, 0xaa, 0x98, +0xdf, 0x0f, 0xd0, 0x31, 0x56, 0x62, 0x9b, 0x7b, +0x37, 0xed, 0x94, 0x0a, 0x18, 0xd8, 0x8b, 0xd4, +0xb3, 0x82, 0x65, 0x83, 0x34, 0x41, 0x7c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0x55, 0x7c, 0xae, 0xf7, 0x9e, 0x5d, 0x95, +0x1a, 0x5b, 0x16, 0xe7, 0xda, 0x72, 0x9f, 0x10, +0xf9, 0xfd, 0xc9, 0x81, 0x7b, 0x4d, 0x7c, 0x99, +0x71, 0xd0, 0x8b, 0x4a, 0xed, 0x57, 0xec, 0xa7, +0xdc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0x34, 0xb6, 0xb1, 0xf3, 0x19, +0x65, 0x56, 0x34, 0xb4, 0xa0, 0x56, 0xf0, 0xe4, +0xeb, 0xb9, 0x7b, 0x55, 0xe2, 0xc4, 0x64, 0x21, +0x44, 0x1f, 0x30, 0xe3, 0x9e, 0x47, 0x85, 0x44, +0x51, 0x84, 0x66, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0x6c, 0x67, 0x86, +0x43, 0x44, 0x6a, 0x8c, 0x07, 0x9b, 0xcf, 0x02, +0x8b, 0xf7, 0x6a, 0xeb, 0x68, 0xcb, 0x01, 0xff, +0xee, 0x53, 0x83, 0x63, 0x2d, 0x97, 0x04, 0x54, +0xe1, 0x1b, 0xcb, 0x38, 0xa6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0xe4, +0x52, 0x34, 0x6b, 0x24, 0x6c, 0x6c, 0x42, 0x75, +0x62, 0x65, 0xca, 0x3f, 0xc4, 0x2a, 0x80, 0xf5, +0x84, 0xa7, 0xa9, 0xd4, 0x56, 0xbc, 0x9e, 0x9f, +0x71, 0xd7, 0x33, 0xba, 0x39, 0xe0, 0x21, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0x59, 0x4b, 0x0f, 0xa1, 0xd2, 0xb9, 0x12, +0xb3, 0xdb, 0x76, 0x4d, 0x86, 0x1f, 0x6c, 0x14, +0xf5, 0xf4, 0x55, 0xa6, 0x5d, 0x40, 0xf7, 0x3a, +0x03, 0xce, 0xf9, 0xd5, 0x19, 0x9b, 0x9f, 0x5a, +0x62, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0xb0, 0x96, 0x9a, 0x09, 0xfc, +0x95, 0x79, 0x4d, 0x54, 0xe8, 0x9b, 0xdf, 0x1b, +0x6d, 0x11, 0x87, 0xd5, 0x0b, 0x65, 0x06, 0x35, +0x7e, 0xd2, 0x18, 0x89, 0xa5, 0xe7, 0x76, 0x23, +0xeb, 0xc2, 0x28, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0x33, 0x18, 0xd0, +0x5a, 0x67, 0x43, 0xdd, 0x4d, 0x8b, 0xf1, 0x46, +0x28, 0x71, 0x46, 0x33, 0x7e, 0x05, 0x8d, 0xfb, +0x4b, 0xa4, 0x9c, 0x93, 0xdf, 0x43, 0xbb, 0x68, +0x18, 0x09, 0xcb, 0xf7, 0x48, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0x1d, +0xd8, 0xe1, 0xc2, 0x15, 0x25, 0x4a, 0xca, 0x2a, +0x4a, 0x4f, 0xdf, 0x7b, 0x6b, 0x55, 0x1a, 0x6c, +0x31, 0xdd, 0x10, 0x90, 0x51, 0x9c, 0x89, 0xe4, +0x32, 0x60, 0xd8, 0xce, 0x18, 0x95, 0xd3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0x1a, 0x43, 0x93, 0xd5, 0x9a, 0x99, 0x8d, +0xb7, 0xf1, 0xba, 0xca, 0xfa, 0x60, 0x26, 0x71, +0xff, 0x4b, 0xe0, 0x9a, 0x6b, 0xd6, 0x7f, 0x9e, +0xdc, 0x21, 0x16, 0x06, 0xd8, 0x74, 0xfa, 0x79, +0xd4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0x52, 0xa1, 0xe5, 0xbf, 0xdb, +0x3c, 0x96, 0xca, 0xac, 0x31, 0x05, 0x47, 0x7b, +0xc6, 0x9f, 0xb9, 0x9a, 0xd0, 0x07, 0xd2, 0x36, +0xfe, 0x10, 0xc8, 0x64, 0x92, 0x9c, 0xf7, 0x6f, +0x22, 0x4a, 0xed, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0xe2, 0x05, 0xe1, +0xa5, 0x74, 0xe4, 0xba, 0x47, 0x72, 0x2d, 0x5a, +0x4e, 0xd6, 0x06, 0x78, 0xb2, 0xe1, 0x7d, 0xe3, +0x3f, 0xce, 0xd2, 0xd1, 0xed, 0x7c, 0xaf, 0xb5, +0x36, 0x69, 0x8a, 0xe8, 0xa8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0x55, +0xaa, 0x46, 0xd4, 0x7b, 0x99, 0xd8, 0x28, 0x99, +0xb7, 0xd7, 0xd9, 0x3f, 0x1f, 0x74, 0xf9, 0x90, +0x14, 0x5b, 0xee, 0x09, 0x29, 0x55, 0x4c, 0xd7, +0x58, 0x01, 0xb7, 0x00, 0xcf, 0x53, 0xb9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0xd5, 0x19, 0x3b, 0xb8, 0xa3, 0x60, 0x35, +0xee, 0xb4, 0x5f, 0xe8, 0x3b, 0x55, 0x43, 0xba, +0xae, 0xc1, 0xa4, 0xe6, 0x0d, 0x62, 0x2a, 0xfa, +0x5a, 0xde, 0x45, 0xb7, 0x4e, 0xb6, 0x94, 0x85, +0xc8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x80, 0x31, 0x8c, 0xbc, 0xd5, +0x7a, 0x5a, 0x2e, 0x0f, 0xe2, 0x3d, 0x01, 0x89, +0x6f, 0xd7, 0xec, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x96, 0xbd, 0x7e, 0x44, +0x2b, 0x7c, 0x8c, 0x10, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0x64, 0x8b, 0xe8, +0x9d, 0x4b, 0xd2, 0x0e, 0xa6, 0xb3, 0x68, 0x5f, +0x4f, 0x68, 0x31, 0xa3, 0x82, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x2a, +0xf6, 0x22, 0x21, 0xa2, 0xdd, 0x56, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0x93, +0xdd, 0x8d, 0x18, 0x8c, 0x22, 0x3d, 0xde, 0x0c, +0x0b, 0x4f, 0x9f, 0x7e, 0x4b, 0xe0, 0x1a, 0x47, +0x5b, 0x5b, 0x0f, 0x4a, 0x19, 0xaf, 0xd5, 0x97, +0x1e, 0x29, 0x1a, 0xac, 0xc0, 0xbe, 0xd0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x61, 0xd4, 0x0b, 0xf9, 0xc3, 0xc9, 0x3b, +0x61, 0x07, 0x68, 0x50, 0x37, 0x9d, 0xb4, 0xb7, +0x9d, 0x6d, 0x10, 0x60, 0x4d, 0xc0, 0x1d, 0x59, +0x9e, 0xd4, 0x7d, 0x71, 0x67, 0xf3, 0x4e, 0xa1, +0x4c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x84, 0x04, 0x4d, 0x3c, 0x5b, +0x04, 0xb8, 0xed, 0x6b, 0x3c, 0x47, 0x0e, 0x94, +0x6d, 0x85, 0xba, 0xe4, 0x9e, 0x14, 0xb1, 0x6f, +0x7a, 0xb9, 0xe1, 0x20, 0xb7, 0xd3, 0xef, 0xd3, +0x2d, 0xa3, 0x53, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0xb0, 0xb0, 0x5f, +0xc5, 0xd3, 0xca, 0xe8, 0x35, 0xd3, 0x0c, 0x72, +0xbf, 0x27, 0xac, 0x6e, 0xee, 0x92, 0x48, 0xa7, +0x0d, 0x64, 0x99, 0xbb, 0x95, 0x0e, 0xd7, 0xfd, +0x7b, 0x62, 0x08, 0x8a, 0x11, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0x09, +0xa4, 0xc0, 0xeb, 0xd1, 0xfd, 0x28, 0x7f, 0x7b, +0x02, 0x5a, 0xf4, 0x84, 0x28, 0x60, 0x2e, 0x03, +0x94, 0x19, 0x2d, 0x15, 0x62, 0x41, 0x9d, 0x0e, +0x2c, 0x84, 0x69, 0xd7, 0xfe, 0x42, 0xdb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0x2a, 0xa4, 0xf5, 0xc7, 0x25, 0xab, 0x3f, +0x26, 0xb4, 0x37, 0xd1, 0x08, 0xf4, 0x5d, 0x15, +0x66, 0xbc, 0xcc, 0x5c, 0x1a, 0x9d, 0x4a, 0xb2, +0x94, 0x36, 0x52, 0xe9, 0x02, 0xd7, 0x9a, 0x07, +0x21, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0x68, 0x56, 0x5a, 0xb7, 0x59, +0xd8, 0x5e, 0x17, 0x9c, 0xf6, 0xd2, 0xff, 0x5c, +0x1e, 0xdd, 0x22, 0x46, 0xcd, 0x15, 0x5a, 0x1b, +0xa2, 0x5d, 0xad, 0x17, 0xb8, 0xf3, 0x59, 0x7c, +0x22, 0xcb, 0xa5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x89, 0x50, 0x90, +0xb2, 0x62, 0xd5, 0x0f, 0xfc, 0xec, 0x6b, 0x8e, +0x3a, 0x7d, 0xf2, 0x8b, 0x2f, 0x87, 0xc2, 0x79, +0xe6, 0xb5, 0x54, 0xb1, 0xb4, 0x8c, 0x05, 0x22, +0x93, 0xe0, 0xdf, 0xba, 0xa9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x60, +0x22, 0xad, 0xd4, 0x43, 0x44, 0x12, 0x77, 0x97, +0xd6, 0x99, 0x47, 0x07, 0xd0, 0xa2, 0xaa, 0x70, +0x43, 0xf4, 0xa8, 0xbd, 0x0e, 0x25, 0x5c, 0x89, +0x87, 0xf8, 0xff, 0x7a, 0x51, 0x2f, 0xb0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0x60, 0xa6, 0xf7, 0x62, 0x3a, 0x63, 0x95, +0x69, 0x78, 0x33, 0xf0, 0xe3, 0xfb, 0x37, 0xb4, +0x62, 0xc7, 0xf8, 0xfc, 0x90, 0x5e, 0xcd, 0x19, +0x1d, 0x7a, 0x3e, 0xd4, 0x3b, 0x4b, 0x25, 0x80, +0x1e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0x98, 0xf3, 0xbc, 0xfc, 0x20, +0xfe, 0x06, 0x28, 0xee, 0x90, 0x7f, 0x41, 0xdd, +0x16, 0x90, 0x37, 0xa9, 0x00, 0x4d, 0x79, 0x97, +0xaf, 0xcd, 0x71, 0x02, 0xc4, 0xb4, 0x5b, 0x49, +0x19, 0x0d, 0x53, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0xb9, 0xdd, 0xe1, +0x0b, 0xfb, 0x80, 0xfc, 0x5e, 0xb2, 0x50, 0xd5, +0x67, 0xd3, 0x0c, 0x6a, 0x9a, 0x4e, 0xe1, 0x7f, +0xc2, 0xf3, 0xca, 0x38, 0xaf, 0x57, 0xea, 0x5b, +0x70, 0x61, 0x45, 0x9a, 0x02, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0xce, +0x2d, 0x39, 0x35, 0xa7, 0x48, 0xf1, 0x38, 0x80, +0xdb, 0x7d, 0xfc, 0x54, 0x78, 0xd9, 0x12, 0x89, +0x5a, 0x74, 0xed, 0x8c, 0xd4, 0x4e, 0xcd, 0xb9, +0x5c, 0x53, 0x1b, 0x30, 0xac, 0x50, 0x46, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0xbc, 0x11, 0x71, 0xa1, 0x94, 0xb1, 0xaa, +0x89, 0x99, 0xe4, 0xe7, 0x8d, 0x04, 0x91, 0x84, +0xb2, 0x82, 0x58, 0xf9, 0x79, 0xdd, 0xb6, 0xe6, +0x00, 0x87, 0x62, 0x93, 0xd3, 0x20, 0xd9, 0x23, +0xa4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0x0a, 0x73, 0xa1, 0xe3, 0xa4, +0xec, 0x5a, 0xfe, 0x63, 0xe9, 0xdf, 0xe1, 0x1d, +0xc4, 0x61, 0x56, 0x9f, 0xad, 0xac, 0x11, 0x4d, +0x4a, 0xb5, 0xfb, 0xaf, 0x06, 0x31, 0x9d, 0xd5, +0x36, 0x44, 0x50, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0xd6, 0xdf, 0xc4, +0x2f, 0x44, 0x3b, 0xa1, 0xbc, 0x9a, 0x72, 0x3c, +0x75, 0x1d, 0xcc, 0x97, 0x9b, 0x1a, 0x88, 0xf3, +0x86, 0x6c, 0xf3, 0x70, 0x72, 0x78, 0x7c, 0x07, +0x96, 0xd8, 0xcb, 0xa7, 0x95, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0xd3, +0x40, 0xab, 0xd6, 0x6b, 0xf8, 0x49, 0xde, 0xe6, +0xcb, 0x96, 0x06, 0x05, 0x06, 0xaa, 0xd3, 0x07, +0x50, 0x11, 0xc4, 0x0d, 0x06, 0x25, 0x89, 0x16, +0x2c, 0xb7, 0xb4, 0xa7, 0x43, 0xcb, 0x47, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0x70, 0x9b, 0xaa, 0x0d, 0x14, 0x44, 0xd5, +0xfd, 0xbd, 0x26, 0xa4, 0x7c, 0x61, 0x28, 0xad, +0xc3, 0x5a, 0x95, 0x44, 0x81, 0x22, 0x21, 0xdf, +0xba, 0x87, 0xc1, 0xa6, 0x76, 0xc4, 0x63, 0x55, +0xcd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0xfb, 0xeb, 0x75, 0x45, 0x75, +0xcd, 0xbd, 0x1b, 0xae, 0x8c, 0xd5, 0x58, 0xb6, +0xb0, 0xa9, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xcb, 0x10, 0xe2, 0xe4, +0x27, 0x9c, 0x92, 0x5c, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0xfa, 0xc2, 0x77, +0xa4, 0xd9, 0x50, 0xeb, 0x3e, 0xe4, 0x8f, 0x07, +0x63, 0x4a, 0xf2, 0x0c, 0x96, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2, 0x83, +0x94, 0xc2, 0xc0, 0x60, 0xdc, 0xeb, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0xd2, +0xc6, 0xac, 0xd5, 0xfe, 0x30, 0x78, 0x4a, 0x0c, +0x2b, 0xee, 0x67, 0xa9, 0xcb, 0x17, 0x41, 0x36, +0xba, 0x18, 0x1e, 0x84, 0x88, 0x4d, 0x0f, 0x81, +0xdc, 0x8c, 0x27, 0xf7, 0x8f, 0x0b, 0xe6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0xd1, 0x45, 0x64, 0x0f, 0x21, 0x53, 0xb7, +0xbb, 0x44, 0xc1, 0xaf, 0x85, 0x96, 0x2e, 0x0f, +0xf2, 0x4a, 0x1f, 0x3b, 0x90, 0xaa, 0x54, 0x90, +0x6a, 0x97, 0x3d, 0x6b, 0x85, 0x07, 0xfd, 0x4a, +0x22, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0x17, 0x93, 0xfe, 0x00, 0x6d, +0x85, 0x52, 0x9f, 0xb1, 0x04, 0x2e, 0xa4, 0xb6, +0xc6, 0x3c, 0x13, 0x26, 0x88, 0x70, 0xcc, 0x6b, +0xb5, 0x78, 0x7c, 0xd8, 0xc3, 0x03, 0xc8, 0x93, +0x70, 0x47, 0x33, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x03, 0x7a, 0x44, +0xd4, 0x71, 0xdf, 0x85, 0x9d, 0xa1, 0x31, 0x13, +0xc1, 0x8f, 0x27, 0xf5, 0xcf, 0x81, 0x75, 0x52, +0x41, 0xf7, 0x77, 0xab, 0xa4, 0x87, 0x37, 0xf8, +0x0a, 0x37, 0x4e, 0x24, 0xe2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0xf9, +0x52, 0x8b, 0x31, 0x7b, 0xb0, 0x9c, 0xe1, 0x7a, +0xfa, 0x1b, 0xf2, 0x6f, 0x22, 0xcf, 0x41, 0x71, +0x0b, 0x2a, 0xc4, 0x51, 0xfb, 0xa9, 0x96, 0x0c, +0x6c, 0x06, 0xc7, 0x0b, 0xb3, 0x19, 0x53, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0xd1, 0xb5, 0xbd, 0xca, 0xd0, 0x2a, 0x35, +0xfa, 0x62, 0xd5, 0xa8, 0xed, 0x02, 0x5d, 0xd8, +0x82, 0xf7, 0x7a, 0x4e, 0xa5, 0xc6, 0xac, 0x68, +0x34, 0x3d, 0xce, 0x3a, 0x4b, 0x56, 0x8d, 0x5d, +0xd3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0x38, 0x6b, 0x8c, 0xf3, 0xb8, +0x83, 0x02, 0x38, 0x26, 0xbd, 0x48, 0xfc, 0xa5, +0x49, 0xcf, 0xef, 0x0b, 0x05, 0xab, 0xd8, 0x2e, +0xfe, 0xe4, 0xf2, 0x66, 0xa2, 0xe1, 0xb9, 0x3d, +0x5b, 0x93, 0xf7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0x47, 0x2d, 0xd0, +0xa5, 0x9b, 0x48, 0xb5, 0x22, 0xb8, 0xd2, 0xc8, +0x13, 0xc9, 0x59, 0xfb, 0x01, 0xe9, 0xd5, 0x2a, +0x74, 0x21, 0x55, 0x7b, 0xa3, 0x9d, 0x46, 0xc3, +0xbe, 0x20, 0xc5, 0xd8, 0xed, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x76, +0x3b, 0x6f, 0xf1, 0x3d, 0x4c, 0xe0, 0xb9, 0x92, +0x00, 0x5e, 0x44, 0x2d, 0x80, 0x14, 0xc1, 0x1b, +0xfd, 0x94, 0x79, 0xdb, 0x17, 0x5e, 0x00, 0x5e, +0xf4, 0xa1, 0x52, 0xf3, 0x52, 0xb8, 0xba, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0x37, 0x99, 0xd4, 0x95, 0xd9, 0x10, 0x9d, +0xf1, 0x81, 0x73, 0x66, 0x47, 0x98, 0xcc, 0x85, +0x2d, 0x11, 0xbb, 0x80, 0x38, 0xec, 0xb8, 0x7a, +0x54, 0xf1, 0xff, 0x0b, 0x17, 0x7e, 0xc7, 0x32, +0xb3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0xd3, 0x0f, 0xc0, 0xc2, 0x30, +0xb3, 0x1d, 0x19, 0x6f, 0xcc, 0x33, 0x17, 0xaa, +0x1a, 0xb1, 0x29, 0x3a, 0x6a, 0x7b, 0x7b, 0x18, +0xee, 0x9c, 0xc0, 0x0c, 0x94, 0xce, 0x5b, 0x0b, +0xcd, 0x48, 0x53, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0x06, 0x57, 0x01, +0xb2, 0x69, 0xec, 0x7f, 0xc6, 0x62, 0xc0, 0x0f, +0x14, 0xc5, 0xcb, 0x9e, 0x2c, 0xd8, 0xcd, 0x89, +0x03, 0x27, 0x5d, 0x7e, 0xe2, 0x46, 0xdc, 0xdb, +0xba, 0x42, 0xc1, 0x8a, 0x68, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x90, +0xb0, 0x43, 0x99, 0x63, 0xce, 0xcd, 0x7c, 0x5e, +0x88, 0xd1, 0x7b, 0xb0, 0x41, 0xc6, 0x32, 0x25, +0x63, 0x26, 0x6d, 0xdb, 0x2d, 0x1d, 0x0a, 0x90, +0x40, 0x40, 0x67, 0x0a, 0xe8, 0x16, 0x0b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0xcf, 0xf6, 0x24, 0x30, 0x3f, 0xbe, 0xc4, +0xca, 0x2e, 0xdf, 0xb8, 0xf2, 0x0c, 0x18, 0xc9, +0xa1, 0x10, 0xc4, 0x7f, 0xce, 0xcb, 0x4c, 0x44, +0x26, 0x79, 0x94, 0x5b, 0x41, 0xf7, 0x47, 0x93, +0xee, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0x4f, 0x60, 0xad, 0xa8, 0xd0, +0x53, 0xf4, 0x7e, 0xec, 0x67, 0x5f, 0xa9, 0xe6, +0x54, 0x96, 0x15, 0x34, 0x2a, 0xbd, 0x88, 0x0d, +0xa7, 0x23, 0x17, 0x2c, 0x25, 0xe0, 0xfb, 0x2f, +0x3d, 0xb4, 0x7e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0xc7, 0x10, 0xa4, +0x4f, 0x67, 0xa8, 0xe1, 0x2b, 0x29, 0x9d, 0x41, +0x7a, 0x50, 0x90, 0x10, 0x0c, 0x04, 0x91, 0x34, +0x6b, 0x01, 0xfe, 0x3a, 0xb9, 0xa4, 0xc1, 0x76, +0x8a, 0x17, 0xab, 0xd0, 0x33, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0xd0, +0x40, 0xf3, 0x12, 0xc5, 0x94, 0x2c, 0x21, 0xf8, +0xa4, 0x16, 0x86, 0x0e, 0xc5, 0xf5, 0x19, 0x15, +0x69, 0x96, 0xa5, 0xea, 0x29, 0x87, 0x16, 0x40, +0x59, 0x08, 0x4b, 0x3d, 0x41, 0xc4, 0x2b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0x53, 0x75, 0xd0, 0xe9, 0xac, 0xa3, 0x9e, +0x5c, 0x1e, 0x51, 0x38, 0xc4, 0xc0, 0xbc, 0xd2, +0x53, 0xe5, 0xe6, 0x92, 0x56, 0x8f, 0x8c, 0x97, +0x66, 0x63, 0x1e, 0x14, 0x38, 0x0f, 0xc4, 0x04, +0xa2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0xec, 0x4b, 0xae, 0xca, 0x1e, +0x9b, 0x4c, 0x2e, 0x31, 0xb9, 0x7f, 0x9a, 0x49, +0x08, 0x15, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x72, 0x6f, 0x5e, 0xd1, +0x07, 0xd5, 0xf6, 0x15, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x85, 0x17, 0xa4, +0xee, 0x89, 0xf3, 0x28, 0x7f, 0x85, 0x7f, 0x64, +0x9a, 0x04, 0xc4, 0x1c, 0x4f, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x40, +0x6c, 0xb8, 0xc3, 0x87, 0xc0, 0x0e, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0x14, +0x51, 0x98, 0x05, 0x2f, 0xde, 0x67, 0x69, 0xa6, +0x3c, 0x58, 0xa9, 0xd0, 0x85, 0x98, 0x39, 0x08, +0x02, 0x17, 0x3e, 0x1f, 0xc4, 0x9a, 0xfc, 0x6e, +0xb7, 0x18, 0xba, 0x6c, 0x38, 0x13, 0xb2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0xe6, 0xb2, 0x4b, 0xca, 0x32, 0x83, 0x9d, +0x83, 0xc8, 0x4d, 0xe7, 0xc6, 0x70, 0xff, 0xcf, +0xbc, 0x88, 0x93, 0x53, 0x5e, 0x70, 0x88, 0x1d, +0x0b, 0x9a, 0x7b, 0x4c, 0x88, 0xa8, 0xf9, 0x78, +0x00, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0x4b, 0x9e, 0x10, 0x4f, 0x9f, +0x84, 0xa8, 0xa4, 0xca, 0x71, 0x68, 0x41, 0xb6, +0x9a, 0xf6, 0x6f, 0xba, 0x9c, 0x05, 0x5d, 0xcb, +0x9c, 0xd0, 0xd4, 0xea, 0x72, 0x82, 0x6a, 0x4a, +0x9b, 0x44, 0xc1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0x18, 0x2c, 0xfb, +0xe5, 0x3d, 0x78, 0xd0, 0xda, 0x0c, 0x39, 0xca, +0xb3, 0xfa, 0xd3, 0x02, 0x70, 0xa8, 0x01, 0x90, +0x0f, 0xf6, 0xd1, 0xe5, 0x6e, 0xd9, 0x55, 0xd0, +0xc9, 0x64, 0xc4, 0x28, 0x2c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0x65, +0xac, 0x85, 0xd7, 0xd0, 0xd3, 0xa4, 0xf5, 0xa5, +0xfa, 0xd7, 0x2b, 0x6a, 0xc2, 0x2a, 0x25, 0x16, +0x23, 0xec, 0x95, 0xb5, 0xab, 0x6c, 0x26, 0x18, +0xf2, 0xec, 0x0f, 0xb3, 0xac, 0x8b, 0xbd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0x70, 0x55, 0x52, 0x65, 0xab, 0x5c, 0x14, +0x44, 0x4c, 0x6f, 0x62, 0xc7, 0x9e, 0x40, 0x91, +0xaf, 0xd1, 0x63, 0x94, 0xd2, 0x9a, 0x78, 0xbd, +0x3a, 0x05, 0x15, 0x7e, 0x39, 0x65, 0x2a, 0x70, +0xc5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0x55, 0x46, 0x85, 0xc3, 0xd6, +0xee, 0x37, 0x2c, 0x97, 0x87, 0xb7, 0x8d, 0xd8, +0x00, 0x17, 0xea, 0x30, 0x27, 0x21, 0xdc, 0x6c, +0x5c, 0xd1, 0xb6, 0xea, 0x67, 0xf6, 0x86, 0x1a, +0x65, 0x7e, 0x87, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0x32, 0x38, 0x61, +0x6c, 0x79, 0x90, 0xfe, 0x9c, 0xe6, 0xc8, 0xc9, +0xcf, 0x76, 0x14, 0x79, 0xa2, 0x9e, 0xfa, 0xc1, +0x0f, 0x07, 0x21, 0x08, 0xc3, 0x40, 0x2f, 0xeb, +0x8e, 0xd9, 0x41, 0x8d, 0x48, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x08, +0xa9, 0xe6, 0xb0, 0x6d, 0xb9, 0x93, 0x1a, 0x43, +0x2e, 0x5a, 0x9e, 0x80, 0x7e, 0xeb, 0x71, 0xef, +0x4e, 0x2e, 0x66, 0xca, 0x97, 0x4a, 0xba, 0xf7, +0xf8, 0xeb, 0x65, 0x1e, 0xe3, 0xe2, 0x81, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0x41, 0x8d, 0xaa, 0x17, 0x27, 0x74, 0xd8, +0xd5, 0xc1, 0xda, 0x72, 0x0a, 0x63, 0x26, 0xcc, +0x47, 0xc7, 0xfb, 0x17, 0xec, 0x9d, 0x09, 0x91, +0xe9, 0x20, 0xfb, 0x58, 0x17, 0xd5, 0x44, 0x17, +0x2f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0x12, 0xf6, 0x11, 0x9a, 0xc3, +0x22, 0x49, 0x09, 0x87, 0x8c, 0x41, 0x43, 0x8f, +0xd3, 0x3b, 0x14, 0x9b, 0xf1, 0x5f, 0x63, 0x6f, +0xa4, 0x4c, 0xdc, 0xbc, 0x6a, 0xf1, 0x34, 0x86, +0xde, 0xff, 0x99, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0x4e, 0xae, 0x77, +0x1b, 0xc0, 0xc8, 0x94, 0xb1, 0x56, 0xb9, 0xfc, +0xbf, 0xd0, 0x58, 0x1a, 0xd8, 0x8d, 0x16, 0x97, +0x22, 0xbb, 0x40, 0xeb, 0x98, 0xc1, 0x1d, 0x19, +0xbf, 0x3f, 0xe6, 0xef, 0x4d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0x75, +0xa5, 0x88, 0x9a, 0x7c, 0x2b, 0x76, 0x2d, 0x0e, +0x2b, 0x9a, 0x37, 0xf2, 0xdc, 0xc7, 0x0e, 0x5c, +0x8b, 0x43, 0x9a, 0x17, 0x4d, 0x2a, 0x10, 0x5f, +0xd9, 0x6d, 0x4f, 0x05, 0x79, 0xd0, 0xfd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0xc9, 0x6c, 0x38, 0xca, 0x26, 0x11, 0x1e, +0x9d, 0xd4, 0xe4, 0x01, 0x0f, 0xd4, 0x21, 0x60, +0xd6, 0xc9, 0x18, 0x7e, 0xc8, 0xa5, 0x83, 0x3c, +0x22, 0x13, 0x5e, 0x7b, 0x98, 0xd8, 0xf1, 0xf1, +0x39, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x22, 0x33, 0x76, 0x7e, 0x74, +0xb7, 0xf5, 0xf1, 0xc1, 0x80, 0xde, 0x4a, 0x8e, +0x7a, 0x2c, 0xd3, 0x90, 0x8c, 0x45, 0xb3, 0x73, +0xeb, 0xf3, 0xab, 0x54, 0x5a, 0x6e, 0xa0, 0xd9, +0x61, 0x93, 0x20, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0x3d, 0x14, 0xee, +0x95, 0x43, 0x40, 0xf9, 0xea, 0x48, 0xa7, 0xfb, +0xbf, 0xab, 0xc5, 0x47, 0x80, 0x38, 0x39, 0x6b, +0x14, 0xa4, 0xe6, 0xfa, 0x88, 0x50, 0x32, 0xce, +0x42, 0xb2, 0xdb, 0x3f, 0x43, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0x83, +0x65, 0xb5, 0xe4, 0x60, 0x6c, 0x66, 0x49, 0x55, +0x27, 0x09, 0x23, 0xa9, 0x24, 0xf5, 0x41, 0x06, +0xaf, 0xb6, 0x0c, 0x7e, 0x0b, 0x96, 0xaa, 0x0d, +0xd6, 0xb9, 0xc2, 0x55, 0x8c, 0xdf, 0xa0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0x32, 0x8b, 0x0a, 0x94, 0xaf, 0x65, 0x0d, +0x04, 0x4c, 0x9e, 0x78, 0x9e, 0x47, 0x4e, 0x13, +0x34, 0xce, 0x31, 0x9e, 0xa2, 0x4f, 0x58, 0x48, +0x01, 0x1e, 0x43, 0xb3, 0x50, 0xc5, 0x98, 0xca, +0x41, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0x6e, 0xc4, 0x3b, 0x0b, 0xc7, +0xc2, 0xcc, 0xdd, 0x16, 0xe0, 0x54, 0xa1, 0x1d, +0x2a, 0xd6, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x9e, 0xf2, 0x30, 0xa7, +0x6a, 0xcc, 0x92, 0xa7, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0x6a, 0xf3, 0x2c, +0x24, 0x91, 0x21, 0x76, 0x83, 0x94, 0x54, 0xfa, +0x55, 0x42, 0x0b, 0x5e, 0xc3, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdb, 0xba, +0x3b, 0x20, 0xec, 0xed, 0x2a, 0x89, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0x70, +0xd9, 0x7b, 0x12, 0x8b, 0x51, 0x4f, 0x90, 0x42, +0xf6, 0xd5, 0x5a, 0x8f, 0xfe, 0xf2, 0xee, 0xb0, +0x51, 0xd9, 0xd8, 0xf9, 0xe0, 0xee, 0xef, 0xd7, +0xb7, 0xf8, 0xf6, 0x6c, 0x4d, 0xa9, 0xcf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0xdc, 0xec, 0xd8, 0x6a, 0xfa, 0xe9, 0x3f, +0x67, 0x13, 0xf1, 0x2d, 0xe4, 0xfe, 0xdc, 0x59, +0xfd, 0xf6, 0xb1, 0x79, 0x4e, 0xd7, 0x2f, 0x43, +0xe9, 0x57, 0x0b, 0xd3, 0x05, 0x46, 0x10, 0x11, +0xb1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0x95, 0xe7, 0x4b, 0x50, 0x7b, +0x83, 0x02, 0x25, 0x8f, 0x21, 0x38, 0x7f, 0x07, +0x6d, 0x3f, 0xa2, 0xf8, 0x6a, 0x43, 0xdd, 0xe0, +0x21, 0x5d, 0x12, 0x6b, 0xa3, 0x76, 0xa7, 0x02, +0x1f, 0x33, 0xa1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0xb5, 0x2e, 0xcc, +0x1f, 0x10, 0x99, 0x6d, 0x8a, 0x62, 0x0a, 0xff, +0x87, 0x3d, 0x1a, 0x4d, 0xf1, 0x80, 0x0c, 0xf4, +0x61, 0x56, 0x4a, 0x6c, 0xee, 0x8b, 0xb7, 0xf1, +0xcb, 0x20, 0xc6, 0x01, 0x83, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0x44, +0xd9, 0x3c, 0x10, 0x70, 0x0a, 0x56, 0x07, 0x6a, +0xf7, 0x6a, 0x16, 0xa8, 0x23, 0x01, 0xbd, 0x4d, +0x74, 0x60, 0xb9, 0x05, 0xd5, 0xb7, 0x68, 0x9d, +0x50, 0xd7, 0xf1, 0x39, 0x11, 0x5d, 0x59, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0x67, 0x88, 0x02, 0x32, 0x05, 0x61, 0xe9, +0x79, 0x60, 0x4f, 0x1b, 0x5d, 0xda, 0x95, 0xb8, +0x1c, 0x1a, 0xf7, 0xe8, 0xb6, 0x3c, 0xd9, 0x10, +0x41, 0x67, 0x58, 0xce, 0x1b, 0x93, 0x47, 0xf7, +0x31, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0xa1, 0x68, 0x23, 0x33, 0xdb, +0x58, 0xa0, 0x26, 0x61, 0xb7, 0x99, 0x02, 0x10, +0x66, 0x5c, 0xa1, 0xc1, 0x3c, 0xb7, 0x39, 0x8a, +0x63, 0x8e, 0xfc, 0xb1, 0xab, 0xb3, 0x93, 0x44, +0x15, 0x78, 0x7f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0xd4, 0xce, 0x02, +0xdc, 0x5e, 0x53, 0xac, 0xf8, 0x65, 0xe6, 0x49, +0x55, 0xf7, 0x93, 0x90, 0xd5, 0x4e, 0x72, 0xff, +0x88, 0xa1, 0xaf, 0x18, 0x0a, 0x8e, 0x53, 0x76, +0xf0, 0xf2, 0xb7, 0xc2, 0xdb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0xd7, +0x93, 0xdf, 0x18, 0x6f, 0xc9, 0xd6, 0xb3, 0x50, +0x7f, 0xf4, 0x12, 0xd1, 0x73, 0x6e, 0xe5, 0xef, +0x85, 0xf0, 0xc5, 0x4e, 0xe2, 0xdc, 0x49, 0x69, +0x69, 0x49, 0xd9, 0x38, 0x84, 0x1e, 0xcd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x01, 0x2c, 0x84, 0xed, 0xa9, 0xa4, 0x91, +0x8f, 0x22, 0x22, 0x14, 0x2b, 0x02, 0x69, 0xae, +0x05, 0x7b, 0x3b, 0x70, 0xb0, 0x2d, 0x6c, 0x5e, +0x05, 0x7a, 0x7a, 0xe9, 0x33, 0xb0, 0x37, 0x69, +0x21, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0x8f, 0x81, 0xb9, 0x5d, 0x69, +0x29, 0x92, 0xd7, 0x7c, 0xf2, 0xab, 0x23, 0x46, +0x3d, 0x8d, 0xa1, 0xbd, 0x29, 0x02, 0xb4, 0xfe, +0xb8, 0xa6, 0x11, 0xc1, 0x21, 0xea, 0x39, 0x1b, +0xf8, 0x55, 0xcc, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x65, 0xcb, 0xbc, +0x82, 0x00, 0x18, 0x16, 0x09, 0x87, 0x68, 0x43, +0x9c, 0x64, 0x0d, 0xee, 0x2d, 0x87, 0x9f, 0x8d, +0xc9, 0x1e, 0xf3, 0xa3, 0x5c, 0xaa, 0x77, 0xed, +0xb8, 0xfd, 0xbf, 0x68, 0x66, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0x8e, +0x55, 0xd4, 0xc0, 0xcc, 0x25, 0x7b, 0x5e, 0xcf, +0x14, 0xde, 0x69, 0x0d, 0x26, 0x87, 0x1a, 0xc7, +0x27, 0x88, 0xa6, 0xef, 0x6a, 0xaf, 0xa9, 0x55, +0x5b, 0x6a, 0xac, 0x50, 0x02, 0x3d, 0x9a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0xf3, 0xab, 0x66, 0xc5, 0xf5, 0x9f, 0x7c, +0x8a, 0xa0, 0x00, 0x66, 0x3d, 0x3b, 0x04, 0x6b, +0xa0, 0xc9, 0xdc, 0xb2, 0xe7, 0x25, 0x84, 0x10, +0x1b, 0xca, 0x70, 0x2b, 0x27, 0x8b, 0x32, 0x6f, +0xfd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0xba, 0x09, 0x3e, 0x15, 0x8a, +0x98, 0x3e, 0xd8, 0x05, 0x42, 0xa3, 0xbc, 0x6d, +0x58, 0x27, 0x0c, 0x0b, 0xbd, 0x3b, 0x58, 0xc9, +0x4a, 0x23, 0xb3, 0xb8, 0x0b, 0xb2, 0x89, 0x68, +0x60, 0x32, 0xee, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x90, 0x54, 0x16, +0xb4, 0x77, 0xb1, 0xab, 0x57, 0x10, 0xdb, 0xf6, +0x38, 0xe9, 0x05, 0xeb, 0x33, 0x40, 0x1e, 0x57, +0x08, 0xbd, 0x7a, 0xe5, 0xa8, 0x3f, 0x7d, 0x5f, +0xff, 0xd3, 0x6b, 0xa6, 0x35, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0x20, +0xa8, 0x0e, 0xba, 0x38, 0x10, 0xf5, 0x7d, 0x9f, +0xa5, 0x66, 0xb2, 0x8d, 0xe6, 0x00, 0xed, 0x20, +0x6f, 0xb0, 0x36, 0x27, 0x46, 0xe4, 0x18, 0xa2, +0x5e, 0xfc, 0x8f, 0xe7, 0xc9, 0x8c, 0x86, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0xa6, 0x77, 0xe1, 0x1a, 0x82, 0x78, 0x1a, +0x08, 0x58, 0xa3, 0x74, 0x6c, 0xb1, 0x22, 0x5e, +0x12, 0xb9, 0x82, 0x19, 0x1d, 0x75, 0x31, 0x0d, +0xc0, 0x4c, 0xf4, 0xdc, 0x98, 0xed, 0x18, 0x32, +0x42, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0xbd, 0x4a, 0x90, 0x2e, 0x48, +0x51, 0xa7, 0x45, 0x45, 0xe9, 0x7f, 0xe9, 0xb0, +0xab, 0xcd, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x5b, 0x51, 0x05, 0x87, +0xac, 0xe8, 0xbe, 0x04, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0x15, 0x96, 0x3c, +0xe6, 0x55, 0x9e, 0xfa, 0xa2, 0x11, 0x0d, 0xe9, +0x4a, 0xe8, 0x5c, 0xc0, 0xca, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdf, 0x00, +0x58, 0x82, 0xfc, 0xe5, 0x1c, 0xad, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0x79, +0xdf, 0x43, 0x9c, 0x0a, 0x1a, 0x2b, 0x46, 0x1e, +0x68, 0x99, 0xbf, 0xc5, 0x17, 0x8b, 0x1a, 0x5a, +0x45, 0x48, 0x17, 0x64, 0x8a, 0x16, 0xcc, 0xf7, +0x85, 0x93, 0xe4, 0xb3, 0x2a, 0xd4, 0x3d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0x66, 0xdc, 0xd5, 0x9e, 0xc3, 0x21, 0xa6, +0x43, 0xdd, 0x1c, 0x80, 0xac, 0x2b, 0x71, 0x1d, +0xb2, 0xd5, 0xd9, 0x7f, 0x1d, 0x5a, 0xa4, 0xb1, +0xf4, 0x5f, 0x1a, 0xba, 0x3a, 0x6c, 0x50, 0xe1, +0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0x57, 0xe9, 0x5c, 0x13, 0xd3, +0xe8, 0x12, 0x8f, 0xa0, 0xf4, 0x36, 0xfe, 0x5c, +0xd0, 0x97, 0x76, 0x97, 0x3c, 0x1d, 0x46, 0xb6, +0x34, 0x42, 0x27, 0xba, 0x33, 0xe0, 0xd6, 0xff, +0x87, 0x7d, 0x2e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x3b, 0x55, 0xe4, +0x57, 0x65, 0xaa, 0x29, 0x9f, 0x6a, 0x90, 0x8f, +0x96, 0x0f, 0xdf, 0x7c, 0x28, 0x40, 0xe7, 0x2e, +0x19, 0x1a, 0x1c, 0x6c, 0xef, 0x71, 0x2c, 0x06, +0x71, 0x62, 0x63, 0x3f, 0x7e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x7b, +0xdf, 0xd6, 0x21, 0xff, 0x2e, 0xcd, 0x1f, 0x49, +0x42, 0x77, 0xc2, 0xab, 0x2d, 0x17, 0x7e, 0x0d, +0x52, 0x9d, 0x2c, 0x75, 0x4c, 0x66, 0x7a, 0xcb, +0x12, 0x44, 0x61, 0xf5, 0xb4, 0x62, 0x95, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0x41, 0x2c, 0x11, 0x9f, 0x07, 0x00, 0xf6, +0x74, 0xc2, 0x14, 0x50, 0xe6, 0xae, 0x11, 0x57, +0xc2, 0x40, 0x35, 0xc4, 0xc3, 0x42, 0x11, 0xf2, +0xc7, 0xd3, 0x28, 0x22, 0xbe, 0x85, 0x3a, 0xcd, +0x85, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0x2f, 0xe7, 0x18, 0x02, 0xa9, +0x60, 0x99, 0xa9, 0xfe, 0x60, 0xf0, 0xaf, 0xb2, +0xf4, 0x2e, 0x39, 0x40, 0x2c, 0xba, 0x4c, 0x2c, +0x8b, 0x86, 0x5e, 0x33, 0x19, 0xbd, 0xe2, 0x88, +0xce, 0x22, 0x0e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0x5b, 0x9e, 0xc4, +0x1f, 0x4c, 0x86, 0xd0, 0x0e, 0x08, 0xfa, 0xaa, +0x4c, 0x0b, 0x98, 0x53, 0xce, 0x06, 0x8d, 0x8e, +0x25, 0x94, 0x94, 0xe8, 0xbf, 0xc0, 0x85, 0xc9, +0x02, 0x59, 0x1f, 0xd1, 0x29, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0xc2, +0xb8, 0x44, 0x0d, 0xb5, 0xc8, 0x5a, 0x72, 0x9b, +0xc8, 0x52, 0x40, 0xe6, 0x61, 0x39, 0x18, 0x97, +0xa9, 0x52, 0x92, 0x89, 0x56, 0x3f, 0x3b, 0x4c, +0xcb, 0xef, 0xf4, 0x5e, 0xb2, 0x79, 0xb3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0xaf, 0xa6, 0x10, 0x55, 0x8c, 0x66, 0x33, +0x96, 0x00, 0x3b, 0x6f, 0xb5, 0x7a, 0xe9, 0x73, +0xd6, 0xa0, 0x0d, 0x07, 0x59, 0x1d, 0x0a, 0x63, +0xd9, 0x7b, 0xbe, 0x58, 0x5f, 0xc7, 0xbd, 0xa4, +0xcf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0x3f, 0x6e, 0xef, 0x0b, 0x09, +0x0d, 0x33, 0x8c, 0xe7, 0xd8, 0xd9, 0xf8, 0xae, +0x90, 0xfd, 0x39, 0xe7, 0x9d, 0x7e, 0x86, 0x84, +0x98, 0x32, 0x3c, 0xa6, 0x45, 0x83, 0x28, 0x39, +0x8f, 0x3b, 0x6a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0x52, 0x80, 0x00, +0xb2, 0x05, 0x25, 0x94, 0xef, 0xf4, 0xd5, 0x84, +0x52, 0x2d, 0x81, 0x82, 0x7d, 0x37, 0x18, 0x80, +0xaf, 0x55, 0xc2, 0x75, 0x9a, 0xc7, 0x3b, 0xeb, +0xd1, 0x3a, 0xc6, 0x81, 0xe5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0xf5, +0x50, 0xf3, 0xe2, 0x64, 0x86, 0x4d, 0xb2, 0x19, +0xa6, 0xb4, 0x3e, 0x89, 0xcd, 0x78, 0xaf, 0x60, +0x8f, 0x04, 0xb5, 0xdc, 0x9a, 0x28, 0xfc, 0xbf, +0xaa, 0xcb, 0x33, 0x95, 0x16, 0x27, 0xe6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0x27, 0x1b, 0x2d, 0x96, 0xcc, 0x2f, 0x57, +0x9f, 0x31, 0x32, 0x43, 0x95, 0xda, 0xd4, 0xe1, +0x37, 0x15, 0xb9, 0xfc, 0x5c, 0x91, 0xd3, 0x4f, +0x1c, 0xc7, 0x60, 0x81, 0xef, 0xc6, 0x27, 0x5a, +0xb1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0xb0, 0xc2, 0x41, 0xcb, 0x45, +0xa7, 0x05, 0xf1, 0x73, 0x2d, 0xff, 0xd4, 0x47, +0x7b, 0xc7, 0x6a, 0x9e, 0x50, 0x4f, 0x0c, 0xb5, +0x2b, 0x65, 0x54, 0xb6, 0xf4, 0xa6, 0x67, 0x65, +0x89, 0x93, 0x91, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0xcf, 0x06, 0xa7, +0x84, 0xef, 0x9e, 0x05, 0x32, 0x96, 0xe1, 0x25, +0x19, 0x3d, 0x1b, 0x36, 0x6d, 0x17, 0x72, 0xf2, +0xa4, 0xec, 0xf3, 0x45, 0xa9, 0xe2, 0xfa, 0x1a, +0xc7, 0xa6, 0xc1, 0x96, 0xc8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0xa9, +0x9b, 0xa4, 0xa6, 0x3b, 0x88, 0x69, 0xfc, 0x0c, +0xe0, 0x14, 0x3f, 0xc7, 0x1e, 0xb8, 0xb3, 0x9a, +0x8b, 0x40, 0xa0, 0x25, 0xc5, 0x71, 0x31, 0xb6, +0x19, 0x33, 0x2f, 0xea, 0x1a, 0x56, 0xfa, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x37, 0xe8, 0x23, 0x36, 0x4a, 0x54, 0x6f, +0xf4, 0x96, 0xf4, 0xb7, 0xc9, 0x95, 0x1d, 0x85, +0x62, 0x3d, 0x3d, 0x22, 0x50, 0x47, 0x41, 0xd1, +0xf7, 0xd6, 0xbe, 0x4a, 0xe5, 0xbc, 0x61, 0x42, +0x5c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0xc0, 0xcb, 0x2a, 0x29, 0xad, +0x5a, 0x7a, 0x91, 0xcd, 0xa5, 0xc4, 0xe1, 0xd1, +0x8d, 0xad, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xee, 0x58, 0x6e, 0x2a, +0x57, 0x86, 0xb0, 0x5c, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0x6f, 0x1e, 0x8b, +0xa4, 0xb1, 0x44, 0xa0, 0x38, 0xaa, 0xe5, 0x0a, +0x3c, 0xab, 0xff, 0xf9, 0x82, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xdf, +0x03, 0x56, 0xf5, 0xf5, 0xe3, 0x96, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0xd1, +0x63, 0xad, 0x6c, 0x92, 0x0d, 0x44, 0xe5, 0xb8, +0xc1, 0xde, 0x20, 0x7b, 0x12, 0xe7, 0x93, 0x6b, +0x38, 0x21, 0xbf, 0xcb, 0x03, 0x48, 0x8e, 0x26, +0x77, 0x6c, 0xe4, 0xd8, 0xef, 0x21, 0x18, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0x6f, 0x3d, 0x5f, 0x7d, 0xa4, 0x58, 0x25, +0x5b, 0xb4, 0xf2, 0x4a, 0x0a, 0xa9, 0x7e, 0xa8, +0x1f, 0xb7, 0x86, 0x9a, 0xd6, 0x5b, 0xa1, 0x13, +0x3a, 0x27, 0x23, 0x2a, 0x5f, 0x48, 0xfc, 0xa8, +0x23, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0xa4, 0xb5, 0x21, 0xfb, 0xb4, +0x1d, 0x4a, 0x66, 0x22, 0xb5, 0xf2, 0x50, 0xc4, +0x40, 0x9d, 0xfd, 0xc2, 0xcb, 0x82, 0xe6, 0xf5, +0xb3, 0x37, 0xd4, 0xc3, 0x5c, 0x93, 0xbd, 0xf2, +0xcd, 0xff, 0xeb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0x12, 0xc9, 0xb2, +0xe5, 0xa7, 0x24, 0xba, 0xb8, 0x2e, 0xb5, 0x8d, +0x6a, 0x99, 0xfd, 0xe1, 0x50, 0x77, 0xe2, 0x46, +0x7d, 0xa0, 0x7f, 0xf5, 0x86, 0xa6, 0x04, 0x3a, +0x1a, 0x85, 0x42, 0xe0, 0x0e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0x4b, +0x8b, 0x0c, 0xcf, 0x56, 0x28, 0x23, 0x2f, 0x88, +0x60, 0x6c, 0xd9, 0x57, 0x5a, 0x2b, 0x63, 0xb0, +0x6d, 0x94, 0x95, 0xf5, 0x40, 0xd3, 0xa3, 0xdd, +0x5e, 0x09, 0xb2, 0xdb, 0x26, 0x45, 0x26, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0xfa, 0x46, 0x39, 0x28, 0xbb, 0x06, 0x00, +0x72, 0x77, 0x4e, 0x27, 0xe2, 0x89, 0xe8, 0x87, +0x6a, 0x54, 0xf2, 0xd5, 0x9e, 0xb0, 0xc3, 0x58, +0xa3, 0x8b, 0x14, 0x2b, 0x3d, 0x98, 0xef, 0x40, +0x0f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0xf4, 0xed, 0x5c, 0x14, 0x1f, +0x63, 0x7b, 0x9d, 0x0f, 0x4a, 0xff, 0x7c, 0xf8, +0x25, 0xa7, 0x33, 0x34, 0x83, 0x69, 0x57, 0xa3, +0xfc, 0x1c, 0x14, 0xbe, 0x80, 0x72, 0x9f, 0x13, +0x92, 0x69, 0x64, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x10, 0x95, 0xda, +0x2e, 0x84, 0x06, 0x5e, 0x6d, 0x29, 0x6a, 0x29, +0x7b, 0xe5, 0x4c, 0x94, 0xdf, 0xef, 0x00, 0x6f, +0x17, 0xec, 0x58, 0x0f, 0x9f, 0xf4, 0xfc, 0x93, +0x69, 0xea, 0x7b, 0x87, 0x8f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0x53, +0x0f, 0xf5, 0xac, 0x12, 0x63, 0xc8, 0xda, 0x20, +0xda, 0x95, 0xfa, 0xe7, 0x40, 0xcc, 0xd6, 0xa0, +0xf5, 0x3c, 0xf2, 0x62, 0x94, 0x51, 0x35, 0xd2, +0x70, 0xf0, 0x78, 0x35, 0xb7, 0xa7, 0x4b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0xc1, 0x22, 0x0a, 0xfa, 0x23, 0x76, 0xc7, +0x98, 0x09, 0x96, 0xf6, 0xa1, 0x09, 0x43, 0x4f, +0x7e, 0xa1, 0xd5, 0x3d, 0x4e, 0x75, 0xb8, 0x5d, +0x5e, 0x9b, 0x47, 0x65, 0xb5, 0xdb, 0x2f, 0x38, +0x05, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0xd2, 0x27, 0x65, 0x04, 0x73, +0x63, 0x9f, 0xe6, 0x78, 0x78, 0xd2, 0x78, 0x9c, +0x97, 0xff, 0xb9, 0x52, 0x62, 0xe2, 0x55, 0x98, +0x0c, 0xb2, 0x11, 0x96, 0x17, 0x53, 0xcb, 0xc5, +0x07, 0xc7, 0x9c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0x6e, 0xd6, 0x4e, +0x6b, 0x5a, 0xf7, 0x4b, 0x79, 0xe0, 0x49, 0x12, +0x94, 0x37, 0x1e, 0xf0, 0x2d, 0xb1, 0x89, 0x11, +0x02, 0x7f, 0xcf, 0x0b, 0xd6, 0x93, 0xca, 0x1b, +0x28, 0xae, 0x76, 0xb9, 0x3b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0x67, +0x9e, 0x87, 0xaa, 0xb7, 0x38, 0x89, 0x08, 0x66, +0x95, 0xb3, 0x32, 0x57, 0x8c, 0xb2, 0xb8, 0x56, +0x72, 0xf4, 0x3d, 0xad, 0xdc, 0x4e, 0xb6, 0x65, +0x3d, 0x9c, 0x34, 0xd6, 0xa3, 0x9b, 0xa2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0xd4, 0x02, 0x34, 0xab, 0x02, 0x63, 0x6f, +0x45, 0xee, 0xd7, 0x4b, 0x93, 0x2a, 0x0b, 0xd5, +0xfb, 0x3d, 0xa5, 0x31, 0xf8, 0x09, 0x44, 0x16, +0xcd, 0x29, 0xdd, 0xdb, 0xf6, 0x85, 0x52, 0x20, +0x64, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x93, 0x1e, 0x74, 0xb0, 0x84, +0xae, 0x34, 0x08, 0x4c, 0xc3, 0xf1, 0x82, 0xed, +0x42, 0xf9, 0x1e, 0x80, 0xeb, 0x7d, 0x5e, 0x18, +0xec, 0x67, 0x81, 0x10, 0xf3, 0x80, 0x02, 0xb0, +0x52, 0xe3, 0xac, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0x21, 0xfa, 0x54, +0x9a, 0x48, 0xd4, 0x66, 0xc8, 0xd8, 0x91, 0x87, +0x18, 0x1f, 0x83, 0x4e, 0x8a, 0xf9, 0xb3, 0xe8, +0xf2, 0xf2, 0xc4, 0xb4, 0xc7, 0x66, 0x96, 0xb6, +0x53, 0xac, 0xe8, 0x89, 0x29, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0x33, +0x7c, 0x88, 0xc3, 0x29, 0xd1, 0x13, 0x36, 0xa6, +0x82, 0xf0, 0x54, 0x43, 0xf6, 0x48, 0x11, 0xe2, +0x79, 0xfb, 0xa6, 0xbe, 0x88, 0xe4, 0xc3, 0x61, +0x69, 0x23, 0xe1, 0x6c, 0x41, 0x2f, 0x57, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0x94, 0x3b, 0xa6, 0x91, 0x9a, 0xfb, 0xb0, +0x5c, 0xaf, 0x55, 0x66, 0x56, 0x4b, 0x21, 0xe3, +0x1f, 0x55, 0xc0, 0x8d, 0xf2, 0x14, 0xea, 0xf6, +0x1d, 0x9a, 0x3f, 0x52, 0xcd, 0xc2, 0x39, 0xa4, +0x46, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0x89, 0x39, 0xb9, 0x29, 0x71, +0x66, 0xd8, 0x06, 0xfb, 0x7d, 0x08, 0x05, 0x21, +0x98, 0x02, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x9c, 0x91, 0x02, 0x1d, +0xf2, 0x23, 0x4d, 0xac, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0x9c, 0x32, 0x51, +0x39, 0xcf, 0x1d, 0x9a, 0x5b, 0xd4, 0x31, 0x7a, +0x34, 0x96, 0x49, 0x24, 0xd5, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xad, +0xdf, 0xc0, 0x6f, 0x71, 0x7a, 0x59, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0xeb, +0xee, 0xbd, 0x96, 0x7f, 0xed, 0x24, 0xc1, 0x76, +0x50, 0x94, 0xbb, 0x09, 0xfd, 0x60, 0x1e, 0x0f, +0x48, 0xaf, 0x05, 0x03, 0x22, 0xfe, 0xf6, 0x6e, +0x77, 0x15, 0x63, 0xe8, 0xd4, 0xd6, 0x5a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x0b, 0x2f, 0xba, 0x7b, 0x44, 0x4d, 0x7c, +0x82, 0x3c, 0x07, 0x40, 0x4e, 0x8c, 0x55, 0x71, +0x0f, 0xde, 0xc8, 0x0f, 0x9a, 0x6a, 0x21, 0x31, +0x65, 0xd8, 0x3c, 0x43, 0xe6, 0x12, 0x55, 0x94, +0x01, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0x29, 0x13, 0xbd, 0x0e, 0x48, +0x75, 0x59, 0x10, 0x1e, 0x2e, 0x89, 0x39, 0xa4, +0xce, 0xe9, 0x1f, 0xbc, 0x7d, 0x00, 0x5f, 0x66, +0x1b, 0x87, 0x4e, 0xd0, 0xd7, 0x5e, 0x7f, 0xaa, +0x56, 0x66, 0xc9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0xcf, 0x92, 0x3a, +0xd2, 0xa6, 0xfb, 0x66, 0x53, 0xf8, 0xfc, 0xdc, +0x47, 0xda, 0xc6, 0xa0, 0x1f, 0x91, 0x84, 0xe0, +0x01, 0xe9, 0x5b, 0x22, 0x80, 0xde, 0x93, 0x4a, +0x64, 0xb4, 0xe6, 0x5e, 0x89, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0x78, +0x07, 0xbe, 0xa3, 0x72, 0xb2, 0x40, 0xde, 0x7f, +0x4e, 0xc8, 0x5e, 0x72, 0xa7, 0xa0, 0x19, 0x40, +0x01, 0x92, 0xab, 0x4d, 0x49, 0xf8, 0x9f, 0xec, +0x14, 0x14, 0xdb, 0x3a, 0x34, 0x14, 0xca, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0xf2, 0x06, 0x50, 0x86, 0x46, 0x89, 0x90, +0x1d, 0x43, 0xad, 0x69, 0x44, 0xf3, 0x03, 0x2f, +0x3c, 0x90, 0x4b, 0x56, 0x23, 0x90, 0x6e, 0x5a, +0x27, 0x20, 0x71, 0x15, 0xf8, 0xc4, 0x19, 0xb3, +0x72, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0xfc, 0x53, 0x52, 0x5c, 0xd7, +0xbf, 0xf0, 0x5a, 0xba, 0xe8, 0xe4, 0x41, 0xd6, +0xc1, 0x04, 0xe2, 0x12, 0xc8, 0x02, 0x93, 0xbd, +0x84, 0x4b, 0xfe, 0x22, 0x67, 0xf8, 0xde, 0xe8, +0x47, 0x44, 0xe6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0x24, 0xe0, 0x69, +0xbc, 0xa9, 0x69, 0xd1, 0xc7, 0x54, 0xae, 0x91, +0xc4, 0x69, 0xe7, 0x85, 0x41, 0xca, 0xd2, 0xc5, +0xee, 0xb2, 0x75, 0xb2, 0xb5, 0xa1, 0x65, 0xf8, +0x29, 0x02, 0xf8, 0x52, 0x13, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0x56, +0x74, 0xcb, 0x51, 0x55, 0x44, 0xbd, 0x28, 0x5a, +0xfa, 0x1d, 0x68, 0xd1, 0x55, 0x21, 0xe4, 0x8f, +0x3d, 0xfe, 0x65, 0x91, 0x3a, 0xe1, 0xb9, 0x1c, +0x29, 0x4a, 0xd3, 0xa7, 0xee, 0x04, 0x1f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0xf9, 0x3f, 0xe1, 0xa0, 0x3c, 0x08, 0x09, +0x06, 0x05, 0x58, 0xea, 0x35, 0xd7, 0xc5, 0x29, +0x08, 0x97, 0xa3, 0xe5, 0x07, 0xf6, 0xf1, 0x0b, +0xdb, 0x28, 0x09, 0x13, 0x85, 0xb9, 0x10, 0x03, +0x66, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0x79, 0x43, 0x1f, 0x7f, 0xd1, +0x1e, 0xe5, 0x72, 0x9e, 0x1a, 0x9a, 0xbe, 0xac, +0x71, 0x7f, 0xdc, 0x82, 0x88, 0xfb, 0xc5, 0x50, +0x3a, 0xf6, 0xcd, 0x41, 0x78, 0x42, 0xd9, 0xd7, +0x1b, 0x5e, 0xd2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0xd8, 0x1e, 0x15, +0x30, 0x08, 0x60, 0xa2, 0x7d, 0x0c, 0xd8, 0x3f, +0x59, 0x6c, 0x75, 0xe7, 0x32, 0xda, 0xbb, 0x77, +0x36, 0x4a, 0x42, 0x57, 0x15, 0x64, 0xd7, 0x59, +0x37, 0xf9, 0x66, 0xb5, 0x79, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0x79, +0x81, 0xd5, 0xa8, 0x46, 0x8f, 0x0e, 0xdb, 0x7e, +0x7a, 0x38, 0xd2, 0x74, 0xc8, 0x1f, 0x7d, 0xcc, +0xf8, 0xc0, 0xd1, 0xdf, 0xe9, 0xae, 0xa0, 0x67, +0x75, 0x97, 0xbf, 0xea, 0x08, 0x86, 0x5a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0x31, 0x98, 0x9b, 0x9c, 0x04, 0x8a, 0x37, +0x82, 0x6f, 0xa5, 0xe5, 0x2f, 0xca, 0x40, 0xab, +0x6e, 0xe4, 0x95, 0x1e, 0x26, 0x32, 0x16, 0x8f, +0xe0, 0x5d, 0x03, 0x5b, 0xcf, 0x8b, 0x4b, 0x31, +0x16, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0xda, 0xee, 0x31, 0xfe, 0x62, +0x6f, 0x08, 0x17, 0x9c, 0xc3, 0x4f, 0xcf, 0xb1, +0x65, 0xb2, 0x5b, 0xb3, 0xe7, 0xe4, 0x49, 0xe3, +0x4d, 0xbb, 0x21, 0x84, 0x59, 0x52, 0xff, 0x52, +0x91, 0xff, 0x0f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0x89, 0x7d, 0x62, +0x1b, 0xca, 0xb6, 0x6a, 0x2b, 0x88, 0x41, 0x4e, +0x0c, 0xef, 0x95, 0xc3, 0x24, 0xb7, 0x5a, 0x09, +0x69, 0x37, 0x2c, 0xef, 0x56, 0x11, 0x3e, 0xb1, +0xbb, 0x87, 0xc2, 0x57, 0xf5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0x7f, +0x27, 0x2b, 0x2a, 0x63, 0xca, 0xe9, 0xb7, 0x0d, +0x0d, 0xd6, 0x12, 0x3b, 0xd2, 0xea, 0xb8, 0x1c, +0xcb, 0x69, 0x99, 0xe1, 0x77, 0xba, 0x43, 0x18, +0xf7, 0x96, 0xde, 0xfd, 0x98, 0x57, 0x83, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0xbb, 0x08, 0xa7, 0x55, 0xe2, 0xe2, 0x32, +0x00, 0xaf, 0xa4, 0xa9, 0xef, 0x98, 0xf2, 0x13, +0x42, 0x35, 0x40, 0x51, 0xc2, 0xb6, 0x29, 0xdc, +0xfc, 0x73, 0x93, 0x1d, 0x88, 0x86, 0x3e, 0xb5, +0x33, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0x94, 0x41, 0xbe, 0xf8, 0xd7, +0x58, 0x7a, 0xcb, 0x03, 0xca, 0xdb, 0x03, 0x2b, +0x5d, 0x8b, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x75, 0xd4, 0x2f, 0xc0, +0xba, 0x2e, 0xe8, 0x52, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0x24, 0xee, 0x56, +0x92, 0xbd, 0x43, 0x8c, 0xd2, 0x16, 0xde, 0xea, +0x18, 0x08, 0xd8, 0x53, 0xb2, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x06, +0xaf, 0x60, 0x11, 0xeb, 0x3c, 0x63, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x32, +0x76, 0xd7, 0xd3, 0x16, 0xe2, 0x0d, 0x63, 0xe6, +0xec, 0x0b, 0xb2, 0xbf, 0x46, 0x97, 0x46, 0x57, +0x43, 0x14, 0x90, 0xfd, 0x11, 0x33, 0x57, 0xcf, +0x24, 0xce, 0x43, 0xd5, 0x95, 0x61, 0x20, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0x06, 0x87, 0x0c, 0x03, 0xe6, 0x02, 0xfd, +0x31, 0x59, 0xc9, 0x70, 0xad, 0x97, 0x72, 0x20, +0x65, 0x6e, 0x92, 0xb0, 0xf1, 0xca, 0xd8, 0x12, +0x2d, 0xe0, 0xeb, 0xfb, 0xb9, 0x0f, 0x9c, 0xcd, +0xb6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0xe8, 0x37, 0xac, 0x72, 0x89, +0x70, 0xf9, 0x24, 0x8f, 0x29, 0x80, 0x04, 0x0a, +0x37, 0xf4, 0x3d, 0xb3, 0x43, 0x87, 0x8d, 0xd5, +0xef, 0xae, 0xd3, 0xac, 0x0b, 0x36, 0x3b, 0x2e, +0x28, 0x5b, 0x3f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0x71, 0xd7, 0xa6, +0x9b, 0x7e, 0x6b, 0x52, 0xe4, 0x1a, 0xc8, 0xc5, +0x57, 0xd2, 0x69, 0x99, 0xff, 0xb4, 0xcc, 0xc4, +0xeb, 0x91, 0xfe, 0x42, 0xa2, 0x69, 0x42, 0x08, +0xef, 0x93, 0xe3, 0x64, 0x8f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0x26, +0x76, 0xb0, 0x89, 0xd6, 0xc0, 0xcf, 0x6a, 0xad, +0x06, 0x53, 0xaf, 0x41, 0x60, 0xf7, 0x6c, 0xad, +0x32, 0xfd, 0x13, 0xd5, 0x3d, 0xe3, 0x75, 0x85, +0x4e, 0xbc, 0x3b, 0xdf, 0x82, 0x54, 0xab, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0x5f, 0x14, 0xdd, 0x08, 0xa8, 0xc6, 0xc9, +0x0e, 0xbc, 0x64, 0xf0, 0x2f, 0x55, 0xd8, 0xa9, +0x53, 0xa8, 0x59, 0x2a, 0xad, 0xd7, 0x34, 0xc5, +0x7d, 0xdf, 0x88, 0xc0, 0xb3, 0xfd, 0x1d, 0x0c, +0x87, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0xfc, 0x56, 0xed, 0x84, 0xb8, +0xc5, 0xf2, 0xf9, 0x38, 0xbe, 0x79, 0x41, 0x15, +0x05, 0xbd, 0xa1, 0x39, 0xdc, 0x99, 0x7d, 0x03, +0x7a, 0xda, 0x61, 0x15, 0x0e, 0xb9, 0x64, 0xb8, +0x19, 0x90, 0x62, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x24, 0xac, 0x97, +0x0c, 0x9b, 0x6d, 0x42, 0x2d, 0x0f, 0x64, 0x44, +0x47, 0x76, 0xe6, 0x42, 0x27, 0x6f, 0x26, 0x48, +0xad, 0xdd, 0xc8, 0x3c, 0x36, 0x42, 0x0c, 0x42, +0xe0, 0x59, 0xb8, 0xda, 0x0f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0x59, +0x9a, 0xcc, 0x0b, 0x50, 0x06, 0xac, 0x86, 0x8a, +0x4b, 0xba, 0xcf, 0xbe, 0x07, 0x0f, 0xbf, 0x5f, +0x70, 0x29, 0x81, 0x8e, 0x33, 0x32, 0x8d, 0x11, +0xc6, 0xbc, 0xea, 0xd4, 0xba, 0x23, 0xc3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0xe7, 0x32, 0x59, 0xc8, 0x74, 0x61, 0x5e, +0xd0, 0xbb, 0xdf, 0x16, 0xae, 0x34, 0xb5, 0x0a, +0x0e, 0x91, 0x0e, 0xaf, 0x4d, 0xce, 0x58, 0xc0, +0x76, 0x45, 0x96, 0x7d, 0x07, 0x4c, 0x8f, 0x20, +0x4b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0x11, 0x1e, 0xd6, 0x4e, 0x02, +0x6c, 0x23, 0x60, 0xe3, 0xc4, 0xe8, 0xf7, 0x04, +0xd2, 0x3b, 0xab, 0x62, 0xbf, 0xa3, 0xfc, 0x69, +0xeb, 0x0e, 0x35, 0x39, 0x9a, 0xaf, 0xf1, 0x55, +0xf4, 0x46, 0x50, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0xf7, 0x4e, 0x74, +0xff, 0xd4, 0x06, 0x3c, 0x87, 0x36, 0xa8, 0xf8, +0xf8, 0xff, 0xd9, 0x97, 0x87, 0x65, 0x84, 0xa6, +0xd6, 0x79, 0x4d, 0xfe, 0x5e, 0x78, 0xb1, 0xab, +0xbe, 0x14, 0xd4, 0x07, 0xa0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0x12, +0xfb, 0x9d, 0x00, 0x6a, 0x7b, 0xa9, 0x15, 0x51, +0x88, 0x69, 0x34, 0xd1, 0xe4, 0x01, 0x16, 0xee, +0x22, 0x99, 0x6e, 0xfb, 0x09, 0x00, 0x65, 0x45, +0x67, 0xa3, 0x1e, 0x72, 0xc1, 0xf9, 0x1f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0x1a, 0xc0, 0xfa, 0x7c, 0x47, 0xff, 0xf8, +0x99, 0x35, 0xae, 0xc7, 0x80, 0xbe, 0x29, 0x72, +0x79, 0x19, 0x28, 0x0c, 0xbd, 0x3b, 0x14, 0x1b, +0x00, 0x79, 0xef, 0xbc, 0xf4, 0x6c, 0x3f, 0xa5, +0xee, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0xad, 0x10, 0x05, 0x57, 0x7a, +0xb2, 0xa0, 0xeb, 0xdb, 0x8e, 0xbe, 0xd0, 0x2a, +0x87, 0xab, 0xb2, 0x3c, 0x41, 0xa1, 0x82, 0x2e, +0xa4, 0xfd, 0xc2, 0xfc, 0xb8, 0xc9, 0xc5, 0x55, +0x5e, 0xcd, 0x10, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0xe4, 0x21, 0x80, +0xd1, 0x38, 0xd9, 0x6a, 0xa4, 0x60, 0x16, 0xbf, +0xe7, 0x21, 0x77, 0x5f, 0x8b, 0x6c, 0x29, 0xf6, +0x44, 0x94, 0xf2, 0xc4, 0xbf, 0xc1, 0xf4, 0x99, +0xd1, 0x38, 0x2f, 0x11, 0xa0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x5e, +0xea, 0xe3, 0x2e, 0xf4, 0x50, 0xc4, 0x5b, 0x3e, +0x12, 0xc7, 0x1f, 0x66, 0x95, 0xbd, 0x72, 0xdf, +0x7d, 0x23, 0x4b, 0xb1, 0xd3, 0x2c, 0x3a, 0x9c, +0x1b, 0xda, 0x8b, 0x3d, 0xd6, 0x04, 0x46, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0x15, 0xbe, 0x71, 0x80, 0xa3, 0x65, 0xe2, +0x05, 0x74, 0x69, 0x13, 0x03, 0xd3, 0xed, 0x92, +0x4a, 0xf5, 0x51, 0xd0, 0x75, 0x0e, 0x4c, 0xa6, +0x37, 0x72, 0x8f, 0x60, 0xea, 0x81, 0x87, 0xdc, +0x15, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0x3f, 0x3c, 0x65, 0xe3, 0xb8, +0x41, 0x3b, 0x2f, 0x9d, 0x62, 0xcd, 0xaa, 0x8a, +0x7b, 0x8f, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x59, 0x50, 0x7d, 0x15, +0xfb, 0xdc, 0x7d, 0xbd, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0x41, 0x66, 0x30, +0xbb, 0xee, 0x6f, 0x2c, 0x3c, 0xd2, 0x89, 0x96, +0x71, 0x15, 0x0e, 0x2b, 0x3e, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x76, +0x31, 0x5f, 0xac, 0xf2, 0x33, 0xbd, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0xec, +0xb6, 0x80, 0x0f, 0xdb, 0x77, 0x2f, 0x25, 0xb1, +0x2b, 0x9e, 0xdb, 0x3d, 0x17, 0x85, 0x0b, 0xbc, +0x32, 0xf1, 0xe0, 0xc3, 0xf9, 0xc0, 0xad, 0x4f, +0x49, 0x89, 0xbc, 0xaa, 0x54, 0xd7, 0x6c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0xf1, 0xde, 0x61, 0xcb, 0x18, 0xb0, 0x8e, +0x80, 0x73, 0x84, 0xa8, 0xb6, 0x2a, 0x23, 0x63, +0x39, 0xd3, 0x30, 0x66, 0x84, 0x07, 0x08, 0x4e, +0xe3, 0xa0, 0x19, 0xa7, 0x51, 0x0c, 0x0f, 0x38, +0xeb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0xc2, 0x34, 0xa0, 0x19, 0xb6, +0xdd, 0x81, 0x6c, 0xb3, 0x52, 0x5f, 0x4d, 0x5c, +0x96, 0x00, 0x66, 0x73, 0xb0, 0x83, 0x7e, 0x32, +0xd3, 0xf7, 0x3c, 0xe5, 0x38, 0x44, 0xe9, 0x6b, +0x84, 0x34, 0x23, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0x22, 0xed, 0x0a, +0x31, 0xba, 0xff, 0xbd, 0x4b, 0xcb, 0xbf, 0x40, +0xd3, 0x98, 0x12, 0xb1, 0x9d, 0xdc, 0x62, 0x4a, +0x2a, 0xc0, 0xde, 0xed, 0x01, 0x23, 0x58, 0xdf, +0x42, 0x23, 0xa7, 0x27, 0x46, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0x14, +0x25, 0x97, 0x6d, 0x18, 0x98, 0x68, 0x7d, 0x93, +0x4e, 0xec, 0xbe, 0xd2, 0xcd, 0xcc, 0x9d, 0xe2, +0x46, 0xf1, 0xa8, 0x85, 0x43, 0xd7, 0x8e, 0x38, +0xf2, 0x69, 0xcf, 0xf4, 0x6e, 0x26, 0x3b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0xff, 0x47, 0x7b, 0x31, 0xe2, 0xb8, 0xdc, +0xed, 0xb6, 0xb4, 0xd4, 0xf2, 0xb5, 0x36, 0x89, +0xdd, 0x90, 0x54, 0xf7, 0xaa, 0xbe, 0x0c, 0x17, +0x2b, 0xbc, 0xbd, 0x3e, 0x74, 0x37, 0xdc, 0xd4, +0x04, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0x0d, 0x7e, 0xe2, 0x3f, 0x8a, +0xac, 0xbe, 0x95, 0x4d, 0x72, 0x29, 0x2c, 0xe7, +0x4b, 0x5a, 0xf3, 0x09, 0x99, 0x20, 0xbf, 0x4c, +0x0d, 0xf7, 0x8d, 0x92, 0x75, 0x7c, 0xcb, 0x05, +0x7f, 0x63, 0x2e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0x34, 0x89, 0x66, +0xa9, 0xc6, 0xdb, 0x94, 0x99, 0x44, 0xa1, 0xb4, +0x5d, 0x0c, 0xd7, 0x44, 0x0b, 0x0e, 0x2f, 0xaa, +0x7b, 0xe0, 0xc9, 0x52, 0x70, 0x36, 0x62, 0xec, +0xbf, 0x7c, 0x12, 0x29, 0x16, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0x0d, +0x73, 0x00, 0x4b, 0xc1, 0x4e, 0xbe, 0x42, 0xc1, +0xae, 0x8d, 0xa9, 0x19, 0xb7, 0xbe, 0x9d, 0xd2, +0xb2, 0xea, 0x89, 0x37, 0x30, 0xa5, 0xe9, 0xbb, +0xf5, 0xbe, 0x2f, 0x1e, 0xed, 0x5d, 0x1a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0x40, 0x8d, 0x9f, 0xd2, 0xaf, 0x70, 0x3c, +0x5d, 0xb3, 0xc9, 0x97, 0xfd, 0x8e, 0xed, 0x54, +0xd6, 0x4c, 0xc3, 0x26, 0x1f, 0x7e, 0x19, 0xb4, +0xf7, 0xf9, 0xbc, 0xef, 0x61, 0xd0, 0x15, 0xd9, +0x7a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0x0a, 0x5b, 0x27, 0xfa, 0x6c, +0x45, 0x57, 0xec, 0x7c, 0x8c, 0x1f, 0x08, 0x80, +0x8c, 0xef, 0x09, 0x5e, 0x66, 0x16, 0x64, 0xdf, +0x19, 0xf3, 0xe3, 0xba, 0x8a, 0x72, 0x76, 0xef, +0x64, 0xe5, 0xd9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x3b, 0x86, 0x2f, +0x69, 0x2b, 0xdf, 0x7a, 0x51, 0x37, 0x3d, 0x23, +0x8d, 0x15, 0x49, 0xa8, 0x45, 0x3c, 0x59, 0xbd, +0x3f, 0x1c, 0xe4, 0x5c, 0x24, 0x78, 0xa5, 0x4f, +0x28, 0xf0, 0x7f, 0x6c, 0xd3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x6b, +0xf5, 0x5f, 0x5e, 0x08, 0x66, 0xda, 0x23, 0x48, +0xbe, 0x28, 0x6e, 0x43, 0x16, 0xc0, 0x60, 0x96, +0x89, 0x0f, 0xc5, 0xec, 0xd9, 0x90, 0xb5, 0xfa, +0xa0, 0x4f, 0xdf, 0x22, 0x3c, 0x14, 0xa7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0xa6, 0x21, 0x76, 0xf3, 0x9b, 0x2b, 0x26, +0x3c, 0x44, 0x41, 0x80, 0x23, 0xda, 0x61, 0x40, +0x51, 0x15, 0x77, 0xca, 0xf0, 0xd7, 0xc5, 0xea, +0xe0, 0x56, 0x82, 0x35, 0x50, 0xf5, 0xb4, 0x71, +0x7c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0x1f, 0x68, 0x74, 0x43, 0xc8, +0xc3, 0x7b, 0x3e, 0x17, 0x52, 0xef, 0xfc, 0xcc, +0x97, 0x84, 0x3c, 0x89, 0x1e, 0xe6, 0xdd, 0x89, +0x13, 0x2a, 0x3f, 0xca, 0x1a, 0x9b, 0x11, 0x7e, +0x2a, 0x0e, 0x06, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0x09, 0x43, 0xb6, +0xc3, 0x37, 0xed, 0xf0, 0xe8, 0x53, 0x39, 0xe3, +0xbe, 0xd7, 0x94, 0x14, 0x39, 0x86, 0x1b, 0x0f, +0x53, 0x2d, 0x20, 0xdc, 0x0e, 0x57, 0xb8, 0xf2, +0xf1, 0x36, 0x4f, 0x02, 0x4a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0xda, +0xd7, 0x4d, 0x5f, 0x3f, 0x54, 0x7a, 0xd4, 0xfb, +0xd8, 0xd9, 0x49, 0x16, 0x12, 0xf3, 0x4d, 0x66, +0xb4, 0xfa, 0xd9, 0xd2, 0xeb, 0x65, 0xe5, 0xef, +0x24, 0xd7, 0x1e, 0x30, 0xf7, 0x23, 0xbd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0xea, 0x0d, 0xab, 0x79, 0x89, 0xaf, 0xc8, +0xe6, 0x24, 0x38, 0x78, 0xe9, 0x86, 0xf1, 0xbc, +0x06, 0x10, 0x50, 0x7d, 0xc0, 0x12, 0x89, 0xd2, +0x39, 0x6c, 0x61, 0xb4, 0x77, 0xd7, 0xbf, 0x02, +0x0b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0xc4, 0x7c, 0x66, 0xfb, 0xdc, +0x40, 0xff, 0xbc, 0xb9, 0x49, 0xb8, 0x5d, 0x49, +0xbf, 0x11, 0xe5, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x5b, 0x18, 0xfb, 0x19, +0x38, 0x94, 0xd5, 0xcb, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0x3a, 0x3f, 0xeb, +0x07, 0x9a, 0xf6, 0x4e, 0x07, 0x27, 0x9a, 0xe2, +0xeb, 0xff, 0xcf, 0x5c, 0x8d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0x56, +0x81, 0x80, 0x5f, 0xfe, 0x30, 0x49, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0xd1, +0x96, 0x52, 0x08, 0xca, 0x24, 0x27, 0xf0, 0x82, +0xf0, 0xcf, 0x0d, 0x6c, 0xc3, 0x16, 0x92, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x8f, 0x52, 0xc6, 0x61, 0x7e, 0xb4, 0x5d, 0xe7, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x00 +}; +EXPORT_SYMBOL(BOOT_FLASH_FW_PASS2); diff --git a/include/linux/mfd/max77705C_pass2_PID03.h b/include/linux/mfd/max77705C_pass2_PID03.h new file mode 100644 index 000000000000..6ef4e2df4a17 --- /dev/null +++ b/include/linux/mfd/max77705C_pass2_PID03.h @@ -0,0 +1,6635 @@ +const uint8_t BOOT_FLASH_FW_PASS2[] = { +0xc1, 0x66, 0xf1, 0xce, 0x5e, 0x18, 0x15, 0x02, +0x01, 0x22, 0x21, 0x70, 0x01, 0x02, 0x03, 0x04, +0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, +0x0d, 0x0e, 0x0f, 0x00, 0x5e, 0x18, 0x15, 0x02, +0x03, 0x00, 0x00, 0x00, 0xa6, 0xf6, 0x48, 0xbb, +0x77, 0xe9, 0x63, 0x21, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0x20, 0xa9, 0xe8, +0x91, 0x2c, 0xdf, 0x69, 0xb7, 0x95, 0x9d, 0xa4, +0x69, 0x33, 0xed, 0x4a, 0x1d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdf, 0xf2, +0x08, 0xc4, 0x6d, 0x97, 0x42, 0xce, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0xe6, +0xf4, 0x3b, 0x41, 0x5b, 0x6b, 0x0c, 0x50, 0xf3, +0x41, 0xda, 0x9b, 0xbf, 0x1e, 0xf2, 0x62, 0xad, +0x33, 0xbb, 0xae, 0x99, 0x6c, 0x49, 0xe7, 0x15, +0x7c, 0xdd, 0x5e, 0x38, 0x68, 0x14, 0x7a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0x6b, 0x74, 0x4b, 0x1f, 0x4b, 0x82, 0xdb, +0x7e, 0xf2, 0x41, 0x9f, 0x33, 0x3d, 0x3c, 0x90, +0x48, 0x7d, 0xa8, 0xfc, 0x0d, 0x76, 0xb5, 0x11, +0xda, 0xc7, 0x1c, 0x55, 0x35, 0x87, 0x7f, 0xa5, +0x67, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0x6f, 0xaf, 0x87, 0x1a, 0x88, +0x9f, 0xa9, 0xb9, 0x25, 0x1f, 0x2f, 0x69, 0xa7, +0xf8, 0xce, 0xcf, 0x8b, 0x5e, 0xaf, 0xa1, 0x3e, +0x73, 0xc9, 0x91, 0x81, 0x71, 0x2d, 0x8b, 0xa0, +0x7a, 0xe6, 0x60, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0x79, 0xfc, 0x74, +0x02, 0x5c, 0x0f, 0x59, 0x99, 0x59, 0x56, 0x6b, +0x73, 0x98, 0xa9, 0x54, 0x23, 0xb9, 0xc2, 0xd9, +0xa6, 0xd9, 0x18, 0x09, 0xa4, 0xfd, 0x59, 0xf5, +0x65, 0x50, 0x53, 0xd8, 0x3c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0x6e, +0xa7, 0xc2, 0x93, 0xaa, 0x48, 0x2f, 0xb6, 0x92, +0xf5, 0x83, 0x2b, 0xd7, 0xd3, 0xd0, 0x9f, 0xb9, +0xaf, 0x0f, 0x0b, 0xa9, 0x5f, 0x5c, 0xab, 0x6c, +0x2f, 0xb4, 0x92, 0x7e, 0x0d, 0xb8, 0xd5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0x2d, 0xd8, 0x4b, 0x61, 0xe0, 0xc8, 0x57, +0x90, 0x4d, 0x4b, 0x2b, 0xcb, 0x93, 0xb1, 0x20, +0x2a, 0x8c, 0x77, 0xfa, 0x0a, 0x20, 0x3f, 0x6f, +0x98, 0xfd, 0x06, 0xa2, 0x78, 0x76, 0x54, 0xad, +0x19, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0x00, 0x1c, 0x98, 0xf4, 0x67, +0xd5, 0xed, 0x85, 0x2b, 0x53, 0x38, 0x8b, 0xcb, +0xf1, 0x3e, 0x2b, 0x21, 0xc7, 0xfa, 0x5d, 0x4d, +0x43, 0x05, 0x61, 0x36, 0xa9, 0x2f, 0x42, 0xa6, +0x6c, 0x9e, 0xa0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x00, 0x39, 0x82, +0xa4, 0xf4, 0xd5, 0x51, 0x6e, 0x75, 0xdc, 0x2d, +0x92, 0x15, 0x73, 0x67, 0xf7, 0xc6, 0x20, 0x40, +0x67, 0xf4, 0x5f, 0x68, 0xe3, 0x43, 0x9b, 0x9a, +0xcf, 0x0e, 0x0a, 0x51, 0x7c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0xb9, +0xe7, 0xee, 0x6e, 0xbd, 0x04, 0xe6, 0x4a, 0x25, +0x96, 0x18, 0x5b, 0xc8, 0x1b, 0xd2, 0xea, 0xbc, +0x74, 0xe6, 0x51, 0x64, 0xad, 0xcc, 0x7b, 0xef, +0x87, 0xf3, 0xe0, 0xa3, 0xb5, 0x9f, 0x0e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x1c, 0x50, 0xa4, 0xa1, 0x9e, 0x59, 0xf0, +0x32, 0xfa, 0x4e, 0xf8, 0x33, 0xab, 0xd4, 0x39, +0x9a, 0x6d, 0xf5, 0x15, 0x0b, 0x98, 0x7a, 0xac, +0x41, 0x8a, 0x57, 0xab, 0x21, 0x95, 0xc0, 0xe7, +0x30, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0xa1, 0x0a, 0x1b, 0x07, 0x8a, +0x0d, 0xf6, 0xf0, 0x87, 0x49, 0xd0, 0x24, 0xed, +0x82, 0xe3, 0x78, 0xbd, 0x56, 0xc3, 0x78, 0x74, +0xe4, 0x71, 0x67, 0x0a, 0x62, 0x3e, 0x06, 0xaf, +0xed, 0xe5, 0xed, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0xeb, 0xbc, 0x94, +0xb4, 0x59, 0xc8, 0x83, 0x69, 0x9a, 0xb9, 0xc5, +0x74, 0x4d, 0x36, 0x65, 0x5d, 0xff, 0x05, 0xe2, +0x6d, 0x89, 0xb1, 0x85, 0x11, 0x37, 0x08, 0xa3, +0xbc, 0x9d, 0x4c, 0xd1, 0xc3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0xd8, +0x83, 0x75, 0x26, 0x28, 0x0e, 0x8a, 0xb7, 0x08, +0x45, 0x1a, 0x4a, 0xb4, 0xdd, 0xba, 0x84, 0xdf, +0x46, 0xb4, 0xbb, 0x96, 0x68, 0x2e, 0x35, 0xb8, +0xcc, 0x23, 0xaf, 0x1f, 0x25, 0x6d, 0xc9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0xa4, 0x16, 0x0f, 0xa7, 0x7a, 0x5b, 0x97, +0x4e, 0xa6, 0x57, 0x97, 0xb2, 0x96, 0xea, 0x85, +0x92, 0x46, 0x4a, 0x35, 0x00, 0x48, 0xa4, 0x20, +0x90, 0x2a, 0x48, 0x97, 0xb9, 0x76, 0xc6, 0xf9, +0x5d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0x24, 0x32, 0xa4, 0x21, 0x44, +0x96, 0x71, 0x5d, 0x5e, 0x3f, 0x8c, 0x9a, 0x44, +0x82, 0xb0, 0xd1, 0x9b, 0x25, 0xf4, 0x4c, 0xf5, +0xf1, 0x77, 0x2d, 0x47, 0x96, 0x1f, 0xb9, 0x2a, +0x8c, 0x72, 0x7c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0x62, 0x9c, 0xb9, +0xaf, 0xdf, 0xbe, 0xab, 0xd4, 0xc3, 0x74, 0x2c, +0xc7, 0x00, 0x5f, 0xcf, 0xe1, 0x18, 0xd4, 0x36, +0x71, 0xec, 0xa0, 0xda, 0x1b, 0xe3, 0x60, 0xcd, +0x99, 0x4a, 0x2c, 0x6b, 0x9f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x8b, +0xc0, 0xb4, 0x10, 0x81, 0xcf, 0x48, 0x3f, 0xff, +0x54, 0xe7, 0xe7, 0xf4, 0x9a, 0xad, 0xdb, 0x13, +0x46, 0xe1, 0x9f, 0x8c, 0xb0, 0x4f, 0xb9, 0xc5, +0x41, 0x22, 0xad, 0x98, 0x12, 0x4e, 0x4c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0x29, 0xd8, 0xac, 0x7e, 0xaf, 0x49, 0x53, +0x82, 0x59, 0x6a, 0xfc, 0xf2, 0x88, 0x67, 0x6b, +0x62, 0x52, 0x26, 0x12, 0x1c, 0x55, 0x34, 0x1e, +0x73, 0x3d, 0x8c, 0xc1, 0x64, 0xe1, 0x77, 0xb3, +0x8b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0xe6, 0xaa, 0x87, 0x1c, 0x66, +0x21, 0x17, 0x85, 0x07, 0x37, 0x22, 0xec, 0xad, +0xa9, 0x28, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xb0, 0xc7, 0xcd, 0xbd, +0x82, 0x0b, 0x40, 0xc5, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0xcc, 0xc1, 0x09, +0x6c, 0x15, 0x08, 0x2b, 0x7c, 0xd0, 0x83, 0x72, +0x6f, 0x3b, 0xe7, 0xec, 0x73, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x6b, +0x75, 0xbb, 0x06, 0x5c, 0x9c, 0x5c, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0xdb, +0xad, 0x7a, 0x50, 0x38, 0xc5, 0xdb, 0xa3, 0x23, +0x06, 0x78, 0xc8, 0xe2, 0xdc, 0x9d, 0x65, 0xa7, +0x8b, 0x60, 0x90, 0x3b, 0x4e, 0x77, 0x30, 0xdf, +0x2c, 0x70, 0xa9, 0xf8, 0xe5, 0xd9, 0xd4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0xe1, 0xa6, 0xa4, 0x17, 0x53, 0x1c, 0x9e, +0x1e, 0xc1, 0x5e, 0x6b, 0x9c, 0x88, 0x8a, 0x97, +0x67, 0xc0, 0x4f, 0x3c, 0x23, 0xdd, 0x14, 0xa7, +0x0c, 0x6a, 0xd8, 0xac, 0x01, 0xf3, 0x08, 0x27, +0x9e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0x0c, 0xe7, 0xef, 0x2d, 0x00, +0x7a, 0x2e, 0xdd, 0x3f, 0x3b, 0x4f, 0x90, 0x97, +0xc9, 0x18, 0x4d, 0x69, 0x65, 0x27, 0x8c, 0x38, +0xfe, 0xa1, 0x4e, 0xa6, 0xb2, 0x21, 0x3e, 0x0a, +0x27, 0x82, 0xdd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x43, 0x47, 0xb6, +0x4d, 0x6a, 0xaa, 0xce, 0x85, 0x6c, 0x9e, 0xaa, +0xbb, 0xcb, 0x6e, 0x0b, 0x8c, 0xc6, 0x1c, 0xf9, +0x20, 0xcf, 0xce, 0x41, 0x38, 0x2f, 0xe1, 0xb0, +0x6c, 0x9c, 0x83, 0xef, 0x20, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0xd1, +0xaf, 0x0a, 0xa9, 0x7e, 0xcc, 0xe4, 0x35, 0x8a, +0x4e, 0x8e, 0xd2, 0x22, 0xb1, 0xb3, 0x93, 0xfc, +0x0f, 0xa9, 0x76, 0x5d, 0xf8, 0x6b, 0x6a, 0xb1, +0xd4, 0x3d, 0x05, 0xb8, 0x2d, 0x84, 0x8f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0x25, 0x0e, 0x8a, 0x6f, 0x6a, 0x8f, 0x5d, +0x20, 0xc7, 0xdb, 0xfa, 0xd4, 0xc5, 0x26, 0xf1, +0x0a, 0xaa, 0x7e, 0x8a, 0x91, 0x01, 0x8a, 0x3c, +0xe1, 0xf6, 0x7f, 0x36, 0x75, 0x19, 0xb8, 0x0e, +0x72, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0xe2, 0x71, 0xde, 0xdf, 0xc8, +0xfb, 0xf3, 0xd7, 0x86, 0x32, 0x70, 0x27, 0xe9, +0xaf, 0x5f, 0x5b, 0xe1, 0xbb, 0x91, 0x27, 0x45, +0x29, 0x2b, 0x2b, 0xeb, 0x2b, 0xa6, 0x96, 0x64, +0x2f, 0x34, 0x52, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0x53, 0xb2, 0xeb, +0xb5, 0x4c, 0xe5, 0xb9, 0x3b, 0x95, 0x40, 0x68, +0xd7, 0x4c, 0xbd, 0xb9, 0x15, 0xa1, 0x98, 0x75, +0x7c, 0xf8, 0x45, 0xd8, 0x03, 0x67, 0x03, 0x39, +0x32, 0x6e, 0x0e, 0xf4, 0x2d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0x36, +0xdc, 0x40, 0x68, 0xc9, 0x9d, 0x6d, 0x6b, 0xaf, +0xfd, 0x66, 0x4b, 0x4e, 0x34, 0xb0, 0x5e, 0x3a, +0xcc, 0x62, 0xea, 0xf4, 0x3a, 0x6e, 0x7e, 0x1c, +0x08, 0xa4, 0x1e, 0x62, 0x76, 0x93, 0x24, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x37, 0xe0, 0xca, 0x87, 0x5a, 0x5a, 0x46, +0x95, 0xc5, 0x41, 0xdf, 0x8e, 0x21, 0x63, 0x57, +0xce, 0x7c, 0x6e, 0xf3, 0xa3, 0x33, 0x1f, 0x73, +0xca, 0x7b, 0x04, 0x8a, 0xa5, 0x95, 0xbd, 0x46, +0x1d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0xd9, 0x4a, 0x8f, 0x13, 0xd7, +0x5e, 0xd4, 0xad, 0xc0, 0x3d, 0x72, 0xf1, 0xfd, +0x63, 0xf2, 0x7c, 0xef, 0x27, 0x10, 0xd3, 0x50, +0xd9, 0x0d, 0x1e, 0x24, 0xd8, 0xcd, 0xe6, 0x7e, +0xe3, 0x03, 0xe0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0x52, 0x79, 0xac, +0xa5, 0x13, 0x3e, 0xd1, 0x1e, 0x22, 0x14, 0x7f, +0x63, 0x0e, 0x2f, 0xa7, 0x93, 0x15, 0x23, 0xb4, +0xfb, 0x44, 0xab, 0x1a, 0xda, 0x8b, 0xd1, 0x52, +0xaa, 0xb5, 0x74, 0x1e, 0xa2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0xf4, +0x84, 0xd4, 0x21, 0xd8, 0x13, 0x57, 0x68, 0x54, +0x1b, 0x4f, 0x92, 0x14, 0x81, 0x5b, 0x43, 0x38, +0x62, 0xd3, 0x01, 0x7b, 0xad, 0x66, 0x27, 0xe1, +0x05, 0x5e, 0x75, 0xeb, 0x22, 0xd3, 0x26, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x4c, 0xd3, 0xf4, 0x97, 0xe7, 0x4d, 0xa4, +0x88, 0xe4, 0xf4, 0x5e, 0x79, 0xf9, 0x4b, 0xe0, +0x02, 0x2c, 0xfd, 0xf7, 0x36, 0xc2, 0xc0, 0xd2, +0x0a, 0x8a, 0xe1, 0xed, 0x74, 0xd6, 0xc8, 0x06, +0xa6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0x7e, 0x88, 0x42, 0x58, 0x78, +0x2d, 0x3a, 0x66, 0x99, 0x66, 0x8b, 0x07, 0x63, +0x14, 0x4f, 0x1b, 0x8a, 0x9d, 0x96, 0x93, 0xd7, +0x81, 0x8d, 0xca, 0x11, 0xdf, 0xb8, 0xda, 0xf3, +0xe8, 0xd9, 0xc1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0x7c, 0x0c, 0x58, +0xe1, 0x1b, 0xd5, 0xcf, 0x1f, 0xc7, 0x6b, 0x40, +0x6b, 0xad, 0x13, 0xc4, 0xcd, 0xbc, 0xcc, 0xb9, +0xb0, 0x74, 0x83, 0xcc, 0x93, 0x82, 0x60, 0xda, +0x94, 0x5b, 0x84, 0x17, 0x8e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0xbd, +0xce, 0x8f, 0x84, 0xd8, 0xd4, 0x1a, 0x8a, 0x25, +0x95, 0x94, 0x82, 0x42, 0x01, 0xa2, 0xa8, 0x7e, +0xf5, 0x96, 0x65, 0xe4, 0xdb, 0x14, 0x7f, 0x65, +0x21, 0x90, 0xde, 0x5e, 0x9b, 0xa7, 0x91, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0xb5, 0xc5, 0x4e, 0x8f, 0xe2, 0x5e, 0x1b, +0xc7, 0x5c, 0xed, 0x3c, 0xbe, 0xec, 0x85, 0x34, +0x3a, 0x37, 0x66, 0x1a, 0xa0, 0xd9, 0x8c, 0xb7, +0x1b, 0xf2, 0x8c, 0x41, 0xcc, 0x36, 0x7b, 0x0c, +0x3c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0xa5, 0x7f, 0xcf, 0x41, 0xf0, +0xd1, 0x0c, 0x6b, 0x49, 0x94, 0x75, 0xd7, 0xc8, +0x94, 0x65, 0xb6, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x22, 0xe5, 0x6c, 0x7e, +0x9b, 0x08, 0xa1, 0xcf, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0xa1, 0x3c, 0x9c, +0xdc, 0x11, 0x88, 0x9d, 0xec, 0xb8, 0x03, 0xc2, +0x0c, 0x2a, 0x9a, 0x64, 0xc7, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd5, 0xfb, +0x4d, 0xb9, 0x3e, 0x46, 0xc9, 0x06, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0xac, +0x6c, 0xf0, 0x5c, 0x2f, 0xf1, 0xe2, 0x94, 0x94, +0x2e, 0xee, 0x1f, 0x95, 0xdd, 0xa4, 0xc3, 0x12, +0x24, 0xc4, 0x44, 0x94, 0x45, 0x91, 0x5f, 0x67, +0xa1, 0x4c, 0x0d, 0x72, 0x8e, 0x39, 0x8e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0xb4, 0x38, 0x15, 0x4b, 0xe9, 0xcf, 0x1e, +0x07, 0xc9, 0x37, 0x2e, 0x60, 0x77, 0xa9, 0x40, +0x5d, 0xdb, 0xa1, 0x16, 0xfa, 0x4f, 0x62, 0x79, +0xa7, 0x2e, 0x56, 0x01, 0x46, 0xa0, 0x24, 0x96, +0xad, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0x27, 0xaf, 0x30, 0x88, 0x87, +0x51, 0xcc, 0x4b, 0x22, 0x33, 0x00, 0xde, 0x02, +0xd3, 0xc6, 0xac, 0x42, 0xcd, 0xcc, 0x2a, 0xcf, +0xa6, 0x85, 0x08, 0x6b, 0xf7, 0x04, 0xd8, 0xea, +0x93, 0x4f, 0x84, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0xfd, 0xb3, 0xe0, +0x9b, 0xcc, 0x9e, 0x05, 0xdf, 0xc8, 0x67, 0xb8, +0x71, 0x9c, 0xa5, 0xef, 0xa8, 0x4c, 0xc2, 0x23, +0x09, 0x05, 0x13, 0x75, 0xad, 0xfa, 0x83, 0xbd, +0xb0, 0x97, 0x6d, 0x8e, 0x7f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0xd2, +0xc5, 0xca, 0x53, 0xde, 0x32, 0x95, 0x3b, 0xb0, +0x24, 0xb1, 0x56, 0x4c, 0x5c, 0x37, 0xdd, 0x1f, +0x8a, 0xbe, 0x7a, 0x8f, 0xd4, 0xda, 0x5c, 0x49, +0x9a, 0xed, 0x9e, 0xe1, 0xf5, 0xbb, 0xdd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0xac, 0x7a, 0x4c, 0x96, 0xa1, 0x57, 0x85, +0x18, 0x18, 0x18, 0x6a, 0x0d, 0x89, 0x8b, 0xad, +0x2b, 0x98, 0x29, 0xf2, 0x08, 0x03, 0x61, 0x1e, +0x72, 0x9d, 0x91, 0x44, 0xc3, 0x3b, 0xbd, 0xc2, +0x5c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0xc8, 0x31, 0xeb, 0x2d, 0x4e, +0x1f, 0x91, 0xa9, 0xb8, 0x2f, 0x76, 0x5d, 0x4e, +0xcc, 0xcf, 0xac, 0xe1, 0xd6, 0xe6, 0x5d, 0x7a, +0x7e, 0x44, 0x31, 0xaa, 0xa9, 0x7d, 0xe2, 0xa3, +0x22, 0x75, 0xce, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0x4d, 0xea, 0x74, +0x27, 0xe9, 0xd7, 0xa1, 0x3e, 0x32, 0x07, 0xb2, +0xe7, 0x12, 0xe9, 0x14, 0x04, 0x11, 0xca, 0x09, +0x39, 0x49, 0xb1, 0xbc, 0x7d, 0xa2, 0xec, 0x83, +0x5f, 0x8e, 0xfb, 0xcb, 0xbf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0xd2, +0xe6, 0x16, 0x62, 0x95, 0x6c, 0x6e, 0x8b, 0x4b, +0x4e, 0xa0, 0xdf, 0x4b, 0xd3, 0xef, 0xd7, 0x91, +0x17, 0x73, 0x3d, 0x71, 0xc2, 0x1b, 0x84, 0x74, +0xaf, 0xfe, 0xf4, 0x86, 0xe7, 0x84, 0xac, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0x46, 0xda, 0x6f, 0xc5, 0xea, 0xe3, 0xdb, +0xf3, 0xeb, 0x68, 0xb0, 0xcd, 0x02, 0x45, 0x92, +0x96, 0x78, 0x4d, 0xf5, 0x22, 0x3e, 0x8e, 0x92, +0x61, 0xf8, 0x52, 0x3a, 0x13, 0xd2, 0x1d, 0xd1, +0x4e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0x3d, 0x3d, 0xe5, 0xb2, 0x96, +0xfe, 0x13, 0xe8, 0x85, 0x81, 0x76, 0x17, 0xab, +0x8d, 0xe4, 0x8d, 0x57, 0xe2, 0x3d, 0x54, 0xa8, +0x05, 0x0c, 0xdb, 0x37, 0x24, 0x50, 0x60, 0xee, +0x75, 0x2a, 0x4f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0x19, 0xa5, 0x80, +0x4f, 0x8c, 0xa1, 0xb8, 0xa0, 0x03, 0x74, 0xe7, +0xda, 0xee, 0xf9, 0xc3, 0x68, 0x12, 0x08, 0x68, +0xdb, 0x37, 0x3c, 0x96, 0x8d, 0xd1, 0xbc, 0x57, +0xd4, 0xef, 0x8f, 0x68, 0x4e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0x3f, +0xf3, 0xc1, 0x1d, 0xf6, 0xfc, 0x28, 0x29, 0x95, +0x91, 0xef, 0xc9, 0x46, 0x4e, 0xd7, 0xfb, 0x4b, +0x45, 0x91, 0xb0, 0x38, 0x0e, 0x00, 0x7a, 0x35, +0xd3, 0x80, 0x2c, 0xe9, 0xaa, 0xf3, 0xfc, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0xb3, 0x97, 0x32, 0x19, 0x81, 0x19, 0x1d, +0x9b, 0x0f, 0x28, 0x55, 0x68, 0xec, 0xb1, 0xf1, +0x80, 0x8d, 0x48, 0xb2, 0x9b, 0x4a, 0x7a, 0xd6, +0xc3, 0xe1, 0xf1, 0x0e, 0x61, 0x79, 0x4b, 0xc2, +0xec, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x49, 0x72, 0x36, 0xf4, 0x88, +0x84, 0x56, 0x17, 0x88, 0x87, 0x63, 0x1a, 0x05, +0xb3, 0x94, 0x13, 0xa6, 0x6d, 0x11, 0xfa, 0xe4, +0x89, 0x13, 0xe6, 0x69, 0x41, 0x3e, 0xc3, 0x76, +0xdd, 0x84, 0x08, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0x71, 0xc7, 0xca, +0x8c, 0xb2, 0x98, 0x1f, 0x07, 0x2d, 0xe8, 0xc5, +0x00, 0xc0, 0x9e, 0x54, 0x67, 0x3e, 0xd0, 0xfd, +0xc9, 0x75, 0x7d, 0x85, 0xc1, 0xfa, 0x0c, 0xda, +0xc4, 0xc0, 0x74, 0x44, 0xd0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0xcd, +0x4b, 0x05, 0x31, 0xeb, 0xa3, 0x21, 0x9f, 0x3b, +0x0d, 0xfc, 0x87, 0xb8, 0xc9, 0xef, 0xe1, 0xcc, +0x09, 0x6b, 0x19, 0x49, 0x3c, 0xff, 0x72, 0x9b, +0x96, 0x9e, 0x7e, 0x2c, 0x93, 0x9a, 0xcc, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x86, 0x96, 0x5b, 0xdf, 0x3e, 0xcf, 0xf3, +0x8c, 0x10, 0xee, 0x31, 0x04, 0xe6, 0xda, 0x0e, +0xfb, 0x57, 0x70, 0x2d, 0x30, 0xba, 0xc4, 0x17, +0x5c, 0xfa, 0x2b, 0x59, 0xe4, 0x3f, 0x17, 0x28, +0x73, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x4e, 0xc8, 0xf8, 0x54, 0x1b, +0x0f, 0x28, 0x0b, 0x00, 0x08, 0xf1, 0xa2, 0xa9, +0x5e, 0xf5, 0xca, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xbb, 0x9c, 0xed, 0x16, +0xd5, 0x04, 0x86, 0x0e, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0x65, 0x4f, 0x65, +0xd2, 0x7c, 0x0b, 0x4c, 0x3f, 0xe5, 0xc2, 0xba, +0xa7, 0xc8, 0x42, 0xf9, 0xab, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6b, 0xeb, +0x4c, 0xc7, 0x07, 0xa8, 0x41, 0x9e, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0x51, +0x74, 0x02, 0x20, 0xa8, 0x9e, 0x65, 0xf7, 0xbe, +0x9b, 0xf6, 0xef, 0x5d, 0x0a, 0x8d, 0xdf, 0xb4, +0x82, 0x79, 0x75, 0xde, 0xa7, 0xcb, 0x78, 0x5a, +0x75, 0x0e, 0xfd, 0x21, 0xd7, 0x7b, 0x48, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0xfe, 0x90, 0x06, 0x32, 0x0c, 0x6c, 0xf8, +0x38, 0x1e, 0x11, 0x95, 0xc1, 0x2a, 0x58, 0x73, +0xe0, 0x5f, 0x83, 0xfe, 0x9d, 0x62, 0xaf, 0x1f, +0x44, 0xc5, 0xc6, 0x39, 0x84, 0xb4, 0x19, 0xa5, +0xc9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0x32, 0xc7, 0x06, 0xa4, 0xa3, +0xad, 0x05, 0x46, 0x1c, 0x19, 0x39, 0x1c, 0x8f, +0xa2, 0xc1, 0x6b, 0x09, 0xc7, 0xfb, 0x91, 0x38, +0x7e, 0x8c, 0x86, 0xb0, 0x7d, 0x61, 0xb1, 0x30, +0x4e, 0xc7, 0x92, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x9f, 0xcb, 0x7e, +0xfa, 0x48, 0xe2, 0x67, 0x0a, 0x23, 0xa5, 0x37, +0xc1, 0x4c, 0x59, 0x5d, 0x4a, 0x2a, 0x1a, 0xf5, +0x2c, 0x54, 0x65, 0x90, 0x8a, 0x51, 0x4d, 0xab, +0xaf, 0x37, 0x9d, 0x1a, 0x32, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x67, +0x28, 0x6c, 0xf1, 0xec, 0x85, 0x82, 0x43, 0x28, +0x7e, 0x1d, 0xc5, 0xfc, 0x7b, 0x81, 0x7a, 0x38, +0x89, 0xb4, 0xa7, 0x61, 0x17, 0x82, 0x88, 0xa1, +0x94, 0xfe, 0xfb, 0x23, 0x47, 0x49, 0xa6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0x6b, 0x02, 0x4d, 0x1b, 0x32, 0xb7, 0x5b, +0x4f, 0x4c, 0xda, 0xf6, 0xaa, 0x63, 0xde, 0x4a, +0xaf, 0x10, 0xd3, 0xc9, 0xd6, 0xb0, 0x35, 0xde, +0x3a, 0x24, 0x75, 0x1e, 0x61, 0x45, 0x27, 0x0d, +0x06, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0x41, 0xf3, 0x2c, 0x4a, 0x96, +0x5d, 0x1e, 0x97, 0x68, 0x61, 0xc4, 0x0d, 0x26, +0x48, 0xdd, 0x36, 0x33, 0xec, 0x91, 0xee, 0x60, +0x81, 0x1f, 0xf2, 0x5c, 0x06, 0x29, 0xce, 0xaf, +0xad, 0x7b, 0x2a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0xb1, 0x53, 0xe8, +0x0a, 0xcb, 0x76, 0x63, 0xdf, 0x96, 0x7f, 0x67, +0x0b, 0x12, 0x1f, 0x61, 0x6a, 0x7f, 0xb6, 0x98, +0x9d, 0xf1, 0xee, 0x23, 0x38, 0xd7, 0x29, 0xbd, +0xb4, 0xb9, 0xca, 0x16, 0x2f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0x60, +0x37, 0xc4, 0x48, 0x12, 0x24, 0x0d, 0xd0, 0x87, +0x67, 0x6f, 0xf5, 0xd3, 0xf4, 0xf9, 0x41, 0xef, +0xa7, 0x06, 0x80, 0x90, 0x94, 0x96, 0xa1, 0x53, +0xc2, 0x0a, 0x1b, 0x68, 0x26, 0x0c, 0x73, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0x03, 0xc5, 0xe1, 0x52, 0x55, 0x01, 0x61, +0xc0, 0xe6, 0x6b, 0xf8, 0x72, 0x51, 0x9c, 0xb2, +0x92, 0x63, 0x35, 0x68, 0x85, 0x60, 0xf0, 0xb5, +0x2e, 0x5c, 0xb7, 0x37, 0x51, 0xf4, 0x57, 0xc0, +0x55, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0xfb, 0x2c, 0x41, 0x73, 0x3e, +0x52, 0x45, 0xe5, 0xe8, 0xf7, 0x66, 0xe1, 0x5a, +0xe0, 0x0d, 0x25, 0x99, 0x02, 0xb5, 0x77, 0xd3, +0xce, 0x36, 0x78, 0x83, 0xb5, 0xe6, 0x1a, 0xd2, +0xe9, 0x17, 0x17, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0xab, 0x88, 0x94, +0x3c, 0x33, 0x86, 0x48, 0x65, 0x5e, 0xbb, 0x53, +0xba, 0x8d, 0x22, 0x59, 0xb8, 0x66, 0xb2, 0x6c, +0x9d, 0x67, 0xab, 0x70, 0xef, 0xd3, 0x0e, 0xe2, +0x6a, 0x2f, 0x68, 0x28, 0x5e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0x29, +0x44, 0x17, 0x63, 0x49, 0xf9, 0x39, 0xbc, 0x2d, +0x47, 0x0b, 0xac, 0x87, 0x1d, 0x2c, 0x06, 0x7d, +0x39, 0x41, 0x45, 0x15, 0xce, 0x01, 0xdb, 0xf0, +0x1b, 0x6e, 0xc4, 0x0b, 0xde, 0x1e, 0xd7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0xbb, 0xcf, 0x60, 0xdf, 0x18, 0xe2, 0xf8, +0x56, 0x16, 0xb9, 0x4c, 0x6a, 0x61, 0x71, 0xe5, +0xd7, 0xb7, 0x84, 0xc6, 0x9a, 0x44, 0xb1, 0x4b, +0x1f, 0xfe, 0x18, 0x8a, 0xc8, 0xdc, 0x47, 0xcc, +0xb0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0x4c, 0xc7, 0x9e, 0x88, 0x05, +0x6b, 0xc6, 0xf6, 0x49, 0x00, 0x78, 0xc5, 0x5a, +0x27, 0x78, 0xc2, 0x5b, 0x71, 0x6d, 0x3d, 0xa7, +0xd4, 0x8c, 0x27, 0xfa, 0x3e, 0x94, 0xc5, 0x84, +0x78, 0x2f, 0x31, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x46, 0x9d, 0xf6, +0x3d, 0xd5, 0x17, 0x0d, 0x17, 0x20, 0x4b, 0x34, +0x11, 0x70, 0x74, 0xe1, 0xf6, 0xc2, 0xc4, 0xe7, +0x91, 0x00, 0x32, 0xfb, 0x53, 0x50, 0xbb, 0x26, +0x3c, 0x3a, 0x44, 0xdb, 0x3f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0x12, +0x0e, 0x02, 0x21, 0xf1, 0xb6, 0x80, 0x05, 0xd8, +0x2e, 0xae, 0xf3, 0x1c, 0x0b, 0xba, 0xea, 0xbf, +0x0d, 0x7e, 0x8e, 0xa7, 0x9b, 0x58, 0x57, 0x36, +0x5f, 0x05, 0x91, 0x5c, 0x15, 0xcd, 0xe4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0xfe, 0xc3, 0x35, 0x99, 0x51, 0xda, 0x4d, +0x8c, 0x19, 0x4f, 0x2a, 0xde, 0x56, 0x66, 0x85, +0x30, 0x26, 0x4f, 0xcb, 0x39, 0x5b, 0x5e, 0x61, +0x4d, 0x4f, 0x00, 0xed, 0x1e, 0xd2, 0x51, 0xa7, +0x86, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0x2b, 0xef, 0x6c, 0x39, 0x7d, +0x49, 0x9b, 0xed, 0xba, 0x3c, 0x66, 0x3e, 0xb4, +0x62, 0x2b, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x85, 0xc1, 0xf1, 0xf5, +0x3d, 0x94, 0xb6, 0xcb, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x10, 0x0b, 0x6d, +0xb8, 0x50, 0xba, 0x9c, 0x70, 0x12, 0xe3, 0x26, +0x2e, 0x58, 0x67, 0x30, 0x3c, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x43, +0x2a, 0x18, 0x4d, 0x79, 0xbd, 0x17, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x10, +0x27, 0x50, 0xae, 0x64, 0xc0, 0x94, 0x64, 0x46, +0x17, 0xff, 0xbe, 0xea, 0x2d, 0xd3, 0x79, 0x03, +0xed, 0x64, 0xde, 0x47, 0x2a, 0x16, 0xde, 0xea, +0x15, 0x36, 0xff, 0xc3, 0xf7, 0xaa, 0x2f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0xce, 0x1b, 0xb0, 0x2c, 0x3c, 0xde, 0xfe, +0x84, 0x3c, 0xfd, 0x93, 0xb2, 0xa1, 0xde, 0xd6, +0x4e, 0x87, 0x1c, 0xaf, 0xa0, 0xd9, 0x6d, 0xd7, +0xd9, 0x03, 0xb0, 0x5e, 0x3d, 0xcd, 0xab, 0x69, +0xac, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0x11, 0x49, 0xba, 0xec, 0x04, +0x85, 0x4c, 0x17, 0xd9, 0x01, 0x16, 0xea, 0x1b, +0x8e, 0xdb, 0x9f, 0xc8, 0x45, 0x23, 0x7e, 0x2f, +0x41, 0x72, 0x78, 0x95, 0x38, 0x2f, 0x7f, 0xb0, +0xce, 0xf4, 0x40, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0xca, 0xb5, 0xb1, +0x8e, 0xf2, 0x61, 0x1f, 0x6c, 0xca, 0x9b, 0x5c, +0x37, 0x5e, 0xe1, 0x30, 0x83, 0x97, 0xac, 0xb3, +0x22, 0x9d, 0x0c, 0x6e, 0x62, 0x99, 0x50, 0xa7, +0x65, 0x57, 0x5d, 0x01, 0xb1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x68, +0xec, 0xe2, 0xa9, 0x14, 0x93, 0x11, 0x74, 0x0c, +0x52, 0x56, 0x72, 0x99, 0xba, 0xa9, 0x6f, 0x4b, +0xa5, 0x40, 0x25, 0x52, 0x27, 0xbd, 0x1c, 0xae, +0xda, 0x62, 0x97, 0x07, 0x38, 0x35, 0x53, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0x60, 0x75, 0x5a, 0x13, 0xf0, 0x3d, 0x4d, +0x39, 0x94, 0xe5, 0x13, 0x09, 0xe2, 0x2e, 0x41, +0xa6, 0x46, 0x98, 0x95, 0x36, 0x5e, 0x32, 0x6c, +0x3b, 0xee, 0x91, 0x11, 0x12, 0x5c, 0x4f, 0x33, +0x86, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0x4a, 0xdb, 0x34, 0x70, 0x03, +0x36, 0xf5, 0xb5, 0xf1, 0xec, 0xc0, 0x49, 0x8d, +0x75, 0x02, 0xc3, 0x19, 0x77, 0xe5, 0xbf, 0xc3, +0x23, 0xec, 0x64, 0x1f, 0x83, 0xa4, 0xe5, 0x0a, +0xb5, 0x86, 0x54, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0x3e, 0x08, 0xbe, +0x87, 0x52, 0x32, 0x3f, 0x10, 0xb8, 0xf2, 0x23, +0x28, 0x74, 0xcf, 0x96, 0x89, 0x60, 0x03, 0x75, +0x1a, 0x16, 0x7a, 0x28, 0xb0, 0xbf, 0x6a, 0x79, +0xc9, 0xcf, 0x61, 0x1e, 0xe9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x86, +0xe3, 0xcf, 0xae, 0x9c, 0x1b, 0x31, 0x8a, 0xdf, +0x31, 0x0b, 0x8e, 0x98, 0x40, 0x68, 0x33, 0x1c, +0x47, 0x61, 0xea, 0xaa, 0x36, 0xb6, 0x0f, 0xaa, +0x6b, 0x8e, 0x2a, 0xf6, 0xd4, 0xe4, 0xe7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0x3b, 0x85, 0x9c, 0xd7, 0xa5, 0xb3, 0x12, +0x03, 0x52, 0x27, 0x74, 0x53, 0x51, 0xf8, 0xc0, +0x69, 0x49, 0x73, 0x3d, 0x4f, 0x89, 0xf8, 0xbd, +0xa7, 0xe3, 0x42, 0xdb, 0xec, 0x1e, 0x74, 0x1a, +0x60, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0x5d, 0x68, 0x25, 0xb8, 0x50, +0xc2, 0x27, 0x74, 0xbb, 0x60, 0xa2, 0x52, 0xb4, +0x02, 0x7e, 0xe0, 0x07, 0x2c, 0x2b, 0xdb, 0x3b, +0x29, 0x94, 0xc8, 0x79, 0x1a, 0x7d, 0x9d, 0xf9, +0xef, 0xb7, 0x37, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0xd3, 0x23, 0x00, +0xb1, 0xbc, 0xa5, 0x33, 0x32, 0x10, 0x41, 0x6e, +0x20, 0xed, 0xc9, 0xd4, 0xd6, 0xc0, 0x4e, 0xbc, +0x21, 0x63, 0xc4, 0x9e, 0x66, 0x9f, 0x26, 0xc3, +0x11, 0x1a, 0x28, 0x6f, 0x7b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0xe5, +0x47, 0x6b, 0xd0, 0x1a, 0xe7, 0xf4, 0xe9, 0xb4, +0x57, 0xf5, 0xb8, 0x7e, 0x1d, 0x19, 0x47, 0xd6, +0xf9, 0xfa, 0xc2, 0x26, 0x95, 0x54, 0x40, 0x98, +0x0c, 0xae, 0x4f, 0x21, 0xb7, 0x84, 0x45, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0x18, 0x46, 0xd3, 0x58, 0x57, 0xa1, 0x41, +0xfe, 0x69, 0xd9, 0x6b, 0x81, 0xe2, 0xe5, 0xda, +0xe2, 0x39, 0x74, 0x1b, 0xc4, 0x10, 0x1d, 0xea, +0x94, 0x7c, 0xa2, 0x07, 0x39, 0x85, 0x12, 0x52, +0xc6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x01, 0xc3, 0x97, 0x7a, 0xf6, +0x2e, 0x88, 0xbb, 0xfe, 0x13, 0xfc, 0x2e, 0xce, +0x6e, 0xcd, 0xca, 0x30, 0xf7, 0xee, 0xa2, 0xfe, +0x6f, 0x0b, 0x53, 0x5e, 0xe8, 0xb4, 0xee, 0x83, +0x22, 0x26, 0x0a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x48, 0xc5, 0xcf, +0xd3, 0xbb, 0x07, 0x2a, 0x21, 0x6d, 0x8d, 0xf4, +0xdf, 0x72, 0xc0, 0xf2, 0x9e, 0x89, 0xc1, 0xd7, +0xa1, 0x25, 0x42, 0x33, 0x58, 0xd9, 0xed, 0x65, +0x05, 0xf6, 0x5f, 0x69, 0x99, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0xbe, +0xb8, 0x4e, 0x72, 0x76, 0x29, 0xe7, 0xb5, 0x4c, +0xc1, 0x58, 0xf7, 0x72, 0x6d, 0xd3, 0xd3, 0xea, +0xd1, 0x88, 0x60, 0xa2, 0x7f, 0xf2, 0x26, 0x8e, +0x3c, 0x81, 0x5c, 0x1d, 0x00, 0x53, 0x8c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0xad, 0xf3, 0x2e, 0x5c, 0xad, 0x16, 0xe6, +0xe4, 0x49, 0xe6, 0x77, 0xdc, 0xcf, 0x52, 0x07, +0x01, 0x77, 0x5d, 0xdf, 0x96, 0xdd, 0xd7, 0x72, +0x34, 0x2a, 0x60, 0xa3, 0x97, 0x2b, 0x11, 0x7b, +0x04, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0xa2, 0x6d, 0xc4, 0x1b, 0x0f, +0x75, 0x53, 0xc0, 0x57, 0xe6, 0xc9, 0xf5, 0xfb, +0xfb, 0x61, 0xc9, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x8d, 0x7d, 0xab, 0xb4, +0xcb, 0x1e, 0x6b, 0x14, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0xbe, 0xda, 0xe6, +0x3f, 0x87, 0xe1, 0x44, 0x50, 0xc1, 0xd2, 0x71, +0xf9, 0x8b, 0xfa, 0x7a, 0xb2, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x9a, +0xba, 0x38, 0x40, 0xeb, 0x58, 0xe4, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0x2b, +0x6a, 0x9d, 0x6a, 0xbf, 0x5c, 0xa9, 0xc3, 0xc7, +0xae, 0x82, 0xb3, 0x79, 0x28, 0x2b, 0xa4, 0xf0, +0x82, 0xf8, 0x5f, 0x83, 0x03, 0x90, 0xdd, 0x06, +0xda, 0xe4, 0x7a, 0xe2, 0x03, 0xc7, 0xab, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0x05, 0xaf, 0xa8, 0xdf, 0xda, 0x69, 0x9e, +0xb7, 0x02, 0xb4, 0x2e, 0xb8, 0xfa, 0x7a, 0xd0, +0xb7, 0xe7, 0x5f, 0xa1, 0xab, 0x16, 0x8f, 0x44, +0x0d, 0xa3, 0x68, 0xfb, 0x06, 0x7a, 0xb9, 0x9c, +0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0x8d, 0x9b, 0x48, 0x34, 0x4c, +0x4a, 0xef, 0xa3, 0x82, 0xe7, 0x9b, 0xbe, 0xd0, +0xf9, 0xfb, 0xe3, 0x5d, 0x4a, 0x69, 0x57, 0x54, +0x80, 0x9d, 0x6a, 0xbf, 0x51, 0xc7, 0xb7, 0xd4, +0x42, 0xfd, 0x8b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0xc4, 0xcb, 0xcc, +0xb6, 0x5c, 0x5f, 0xfe, 0x35, 0x6e, 0xba, 0x56, +0xe6, 0xf7, 0xb0, 0x6c, 0xe7, 0xb1, 0x1d, 0xdc, +0xc9, 0xc1, 0x2b, 0x5f, 0xeb, 0x2d, 0x7c, 0x23, +0xc0, 0x94, 0x3b, 0x86, 0xae, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0xb9, +0x5a, 0x60, 0x3f, 0x01, 0xe5, 0xb9, 0xd0, 0x5e, +0x1f, 0xb2, 0x88, 0xb5, 0x62, 0x7e, 0xd0, 0x54, +0x6a, 0x86, 0x2d, 0xbe, 0xd7, 0xbf, 0xf3, 0x71, +0xe9, 0x51, 0x10, 0xc1, 0x13, 0x4e, 0x7f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0x8a, 0xd0, 0x90, 0x81, 0x80, 0x4d, 0x9c, +0x56, 0xd6, 0xb4, 0xd3, 0xfe, 0x6c, 0x10, 0xb4, +0x71, 0x34, 0xd8, 0xf8, 0x7b, 0x07, 0x35, 0x4a, +0x8d, 0x61, 0x3b, 0xc4, 0x3d, 0xdd, 0x5d, 0x0c, +0x95, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0x26, 0x1b, 0x93, 0xec, 0x95, +0x8b, 0x0c, 0x50, 0x1c, 0x16, 0xd5, 0x3d, 0xf5, +0x6e, 0xc8, 0x8b, 0xb6, 0x8c, 0x82, 0xd5, 0x61, +0xd3, 0xd9, 0xfd, 0x10, 0x92, 0xc4, 0xf0, 0x28, +0xd4, 0x4f, 0x12, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0xa0, 0x88, 0x22, +0x8d, 0xda, 0x9a, 0x60, 0xdf, 0x5a, 0x3a, 0xc6, +0x7f, 0xef, 0x61, 0x24, 0x2f, 0xb7, 0x88, 0xef, +0xbc, 0x88, 0x2d, 0xae, 0x34, 0xac, 0x9c, 0x41, +0x53, 0xd7, 0x30, 0xb5, 0xb0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0x15, +0xc1, 0xfd, 0x1c, 0xe9, 0x74, 0x6b, 0xd9, 0xca, +0x64, 0xe5, 0x05, 0xc4, 0xa0, 0xe2, 0x33, 0x82, +0xf6, 0x89, 0x2b, 0xf2, 0x14, 0xbb, 0x5c, 0xb3, +0x1d, 0xa7, 0x33, 0xd4, 0xa0, 0xf4, 0x0b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0x1d, 0x6f, 0x22, 0x73, 0xd8, 0x54, 0x1e, +0xcf, 0x32, 0x79, 0xf8, 0x99, 0x4b, 0x60, 0x39, +0xff, 0x50, 0x09, 0xbc, 0x40, 0x60, 0xb3, 0x5c, +0xd2, 0x15, 0x4f, 0x95, 0x77, 0xf7, 0xf3, 0x83, +0x01, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x5f, 0x4c, 0xa6, 0x63, 0x0c, +0xc2, 0xef, 0x08, 0xf5, 0x86, 0xd3, 0x9b, 0x7e, +0x81, 0xae, 0x07, 0xa6, 0x56, 0x38, 0xce, 0x38, +0x4f, 0x94, 0xf4, 0xa7, 0xe2, 0x95, 0xe9, 0x33, +0x4b, 0x9e, 0xd3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0xd3, 0x2c, 0x9f, +0x98, 0xa7, 0xfb, 0x9c, 0xce, 0xc9, 0x4d, 0x8a, +0xb4, 0x78, 0x65, 0xf0, 0x0c, 0xda, 0xe7, 0x95, +0x89, 0xf0, 0x98, 0x2a, 0x7c, 0x9b, 0xf1, 0x07, +0xac, 0x26, 0x1a, 0x06, 0xd3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0xbb, +0xa0, 0x28, 0x2c, 0x64, 0xf2, 0x7a, 0xf8, 0x85, +0xd9, 0xff, 0x0b, 0xfa, 0x6a, 0xc4, 0xb9, 0xee, +0xbb, 0x4a, 0xef, 0xb5, 0x59, 0xa3, 0x20, 0x54, +0xf6, 0xc4, 0x78, 0xbd, 0x80, 0xb0, 0x44, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0xc3, 0x4a, 0x14, 0xe1, 0xbc, 0x64, 0xdd, +0x1e, 0x3e, 0xed, 0xec, 0x19, 0x8a, 0x6f, 0x30, +0xe7, 0x73, 0xbe, 0x75, 0x63, 0x1e, 0xb0, 0xb9, +0x5f, 0x7f, 0xf5, 0x95, 0x75, 0x86, 0x82, 0x59, +0xe0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0xa6, 0x93, 0x20, 0x49, 0xb6, +0xfe, 0x20, 0x3c, 0xaa, 0x4d, 0xd7, 0xc9, 0x1e, +0x07, 0x7b, 0x7c, 0xd1, 0x7d, 0x15, 0xbc, 0x18, +0xe2, 0xf5, 0xe1, 0x25, 0xe4, 0x34, 0x26, 0x9c, +0x4e, 0x66, 0x9c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0xd1, 0xaf, 0x2d, +0x85, 0x4d, 0x74, 0x0a, 0x80, 0xf4, 0x5d, 0x94, +0x5f, 0x78, 0x7a, 0x6d, 0x66, 0xcd, 0x2f, 0x37, +0x1c, 0xe5, 0x1e, 0x23, 0x3f, 0x43, 0x45, 0x2f, +0x3f, 0x8c, 0xb8, 0x14, 0x2d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0xfe, +0x4e, 0x07, 0x00, 0x24, 0xb2, 0x86, 0x3b, 0xf5, +0x06, 0x57, 0x51, 0x4c, 0x41, 0x36, 0x2b, 0xe0, +0x41, 0x31, 0x2d, 0x32, 0x98, 0x83, 0x3d, 0xcf, +0x14, 0x28, 0xac, 0x68, 0x80, 0x03, 0x10, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0x92, 0xdf, 0x11, 0x77, 0xa5, 0xc6, 0x66, +0xb8, 0x76, 0x3d, 0x10, 0x1f, 0x07, 0x6b, 0xcf, +0x89, 0x18, 0x08, 0x4a, 0x1d, 0xb3, 0xab, 0xb3, +0xb7, 0x57, 0xea, 0x55, 0xae, 0x70, 0x4a, 0xb0, +0x8a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0x01, 0xf5, 0x9b, 0xd5, 0x35, +0xd7, 0xd2, 0x83, 0xb7, 0x44, 0x3d, 0xb8, 0xc9, +0xbe, 0xb2, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x8c, 0x07, 0x01, 0x8b, +0xf0, 0x86, 0x89, 0x93, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0x8d, 0xf2, 0x1b, +0x23, 0xef, 0x29, 0xed, 0x5d, 0x73, 0x9c, 0x8e, +0x3c, 0xb6, 0x88, 0x1a, 0xbb, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x92, 0x53, +0x9c, 0xb5, 0x1e, 0x5b, 0xdb, 0x69, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0xe7, +0x4a, 0xde, 0xed, 0x8a, 0xc3, 0x2d, 0x0c, 0xbd, +0x5c, 0x1e, 0xd8, 0xb1, 0x46, 0xb6, 0x42, 0x2b, +0xc0, 0x53, 0xc8, 0x5e, 0x70, 0x22, 0x4d, 0x82, +0x96, 0x8a, 0xee, 0x34, 0xb7, 0xb8, 0x63, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0xdc, 0x2f, 0x0b, 0xf9, 0x45, 0x21, 0x7e, +0x31, 0xfe, 0x07, 0x75, 0x30, 0x83, 0x03, 0xef, +0x7c, 0xd9, 0x9e, 0x48, 0x4b, 0x33, 0xd8, 0x97, +0x71, 0x16, 0x02, 0x4a, 0x20, 0xc8, 0x04, 0x9f, +0xa5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0xcb, 0x12, 0x22, 0xe6, 0x9a, +0x8d, 0xf3, 0x10, 0xbe, 0xe1, 0x25, 0x69, 0x67, +0xb7, 0x91, 0x00, 0x68, 0x4b, 0xbb, 0xa0, 0x75, +0x35, 0x0a, 0x3d, 0x50, 0x42, 0xfa, 0xc2, 0xf1, +0xd9, 0xe4, 0xc5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0x2c, 0xc6, 0xb5, +0x09, 0xec, 0x91, 0xbf, 0xb5, 0x97, 0x4f, 0x09, +0x7d, 0x85, 0xdf, 0x28, 0xe7, 0x0b, 0x07, 0x7e, +0x95, 0x96, 0x7a, 0x31, 0x62, 0xef, 0xfc, 0xba, +0x46, 0x72, 0xf1, 0xfb, 0x25, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0xdb, +0x87, 0x57, 0xdd, 0xcc, 0x14, 0xd4, 0x88, 0x82, +0xb0, 0xe3, 0x02, 0x75, 0x6b, 0xb8, 0xa3, 0xea, +0x4e, 0xbc, 0x93, 0xce, 0x70, 0xa2, 0x84, 0x97, +0xb2, 0xfa, 0x41, 0xe0, 0x23, 0xc3, 0xdf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0xe2, 0x8d, 0x09, 0xb2, 0xbb, 0x7e, 0x93, +0x09, 0xb7, 0xcf, 0xf8, 0x3b, 0xee, 0xd8, 0x95, +0xf1, 0x1c, 0x0a, 0xcc, 0xdd, 0x74, 0x34, 0x6d, +0x01, 0x14, 0xc7, 0x8e, 0xb3, 0x04, 0x59, 0x5d, +0x06, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0xf0, 0xee, 0xba, 0xdc, 0x27, +0x4a, 0xd4, 0x82, 0xd5, 0x03, 0x80, 0xd3, 0x2e, +0x14, 0x81, 0xcb, 0x65, 0x7d, 0xcd, 0xed, 0xe3, +0x25, 0x93, 0x8b, 0x78, 0x02, 0x62, 0x8a, 0xce, +0x81, 0x80, 0x5e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x59, 0x6e, 0xa9, +0x8c, 0x0a, 0x10, 0x5d, 0x0e, 0xb6, 0xb7, 0xc9, +0xf5, 0x4a, 0x32, 0x25, 0x15, 0xd5, 0xfa, 0x64, +0x81, 0x99, 0x75, 0x4c, 0xda, 0x94, 0x9f, 0xa4, +0x71, 0xd4, 0xd1, 0x41, 0x35, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0xe3, +0x8a, 0x7b, 0x22, 0x5b, 0xa3, 0xdf, 0xda, 0xa7, +0xe8, 0x01, 0x23, 0xbf, 0x9b, 0x11, 0x23, 0x9b, +0x74, 0x69, 0x8d, 0x02, 0x9e, 0xfb, 0x3a, 0x5b, +0xaa, 0x69, 0xa7, 0x8b, 0xb5, 0xad, 0x53, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0x51, 0x48, 0xc1, 0xaa, 0xfb, 0xea, 0xef, +0x27, 0xd8, 0xd8, 0xf4, 0xed, 0xd2, 0x11, 0x9f, +0xc1, 0x0c, 0x35, 0xdf, 0x67, 0xbf, 0xf0, 0x12, +0x0a, 0xc9, 0xb9, 0xdd, 0xf8, 0x16, 0x49, 0x72, +0x1e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0x8b, 0x90, 0xf1, 0x7b, 0x5f, +0xf3, 0x5c, 0xb8, 0x2f, 0x30, 0x1e, 0x92, 0x89, +0x8b, 0x4f, 0xa1, 0x20, 0xa8, 0x62, 0x78, 0x69, +0xc3, 0x7f, 0xd5, 0xce, 0xd1, 0x9d, 0x98, 0x9d, +0xef, 0xc3, 0x27, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x8b, 0x2d, 0x53, +0x0e, 0x64, 0x16, 0xaf, 0x51, 0x5e, 0x78, 0xd1, +0x48, 0x05, 0xdd, 0x52, 0xbd, 0x24, 0x3d, 0x07, +0xf3, 0x9b, 0xc0, 0xe0, 0xdc, 0x3d, 0xbb, 0x89, +0x9b, 0x92, 0x6f, 0xdf, 0x60, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0x04, +0x7b, 0x9a, 0xdd, 0xe9, 0x1c, 0x36, 0x36, 0x03, +0x7a, 0x69, 0x6b, 0x92, 0xe6, 0x2b, 0x5f, 0x38, +0xf6, 0x90, 0x6b, 0x62, 0x69, 0x2a, 0x66, 0x34, +0x09, 0x85, 0xe0, 0xda, 0x76, 0x17, 0x88, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0x5f, 0x7a, 0xf3, 0xa2, 0xb3, 0xaa, 0x19, +0x05, 0x8b, 0x35, 0x2a, 0xb4, 0xda, 0x51, 0x80, +0x56, 0xc3, 0x12, 0xdc, 0x72, 0x78, 0xa4, 0xe0, +0x01, 0x1e, 0x06, 0xee, 0x6b, 0xe0, 0xd3, 0x25, +0x84, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0xdb, 0x01, 0x3b, 0x36, 0x69, +0x4e, 0xce, 0x76, 0x7c, 0xf6, 0xce, 0x21, 0xcb, +0xe8, 0x6d, 0x22, 0xc8, 0xad, 0xd1, 0x55, 0x12, +0x56, 0xb5, 0xc1, 0x9a, 0x93, 0xc5, 0x72, 0x67, +0x0a, 0xe3, 0x49, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0xa6, 0xcf, 0x2f, +0x29, 0xd4, 0xb6, 0x98, 0x67, 0x39, 0xdf, 0xc8, +0x55, 0x0e, 0x04, 0x65, 0xf1, 0xe7, 0x08, 0x77, +0x56, 0x11, 0x33, 0xcd, 0x80, 0xf8, 0xdc, 0x94, +0xbc, 0xcf, 0x27, 0xad, 0xa9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0x17, +0x50, 0xfb, 0x8e, 0x3b, 0x70, 0x45, 0x28, 0x5e, +0x41, 0x8a, 0x23, 0x61, 0xd9, 0x08, 0x40, 0x06, +0x52, 0x41, 0xf1, 0xd1, 0xdf, 0x33, 0xcd, 0x8c, +0x5a, 0xc2, 0x04, 0xcf, 0x2f, 0x32, 0x06, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0x54, 0x0d, 0xa6, 0x62, 0x3e, 0x34, 0x54, +0x38, 0x0c, 0xfe, 0x41, 0xcc, 0xe0, 0xca, 0x03, +0xbf, 0xbe, 0x53, 0x43, 0x9e, 0xcd, 0xf3, 0x48, +0x22, 0xbc, 0x4d, 0x00, 0x55, 0x09, 0x23, 0x8d, +0x08, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0x91, 0x5a, 0x19, 0xa1, 0x54, +0x4a, 0x04, 0x8b, 0x8c, 0x9d, 0xf7, 0x0e, 0x58, +0x94, 0xdb, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xe8, 0xad, 0x14, 0x75, +0xae, 0x45, 0xf2, 0x10, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x5f, 0x3c, 0xfc, +0x31, 0x77, 0x69, 0x4c, 0x6c, 0xce, 0xa4, 0x64, +0xb5, 0x7f, 0x56, 0xc4, 0xd9, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6b, 0x36, +0x36, 0x85, 0x18, 0xe0, 0xfa, 0x32, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0xb8, +0x37, 0x6a, 0x60, 0xd2, 0x12, 0xaa, 0xa7, 0x0a, +0x74, 0x5e, 0x7f, 0x47, 0xb9, 0x66, 0xcb, 0x8f, +0x1b, 0x39, 0x00, 0x48, 0x61, 0x2e, 0x26, 0x4e, +0xee, 0x3f, 0x69, 0x5f, 0x88, 0xc8, 0x0b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0x34, 0x83, 0x06, 0x56, 0x59, 0x84, 0x3c, +0xcf, 0xc1, 0x25, 0x16, 0xba, 0x81, 0x32, 0x4b, +0x6d, 0x45, 0x8a, 0x0f, 0xb2, 0x5b, 0xdf, 0xa5, +0x4b, 0x22, 0x18, 0x0f, 0x75, 0x57, 0x41, 0xcc, +0x43, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0x0a, 0x26, 0x65, 0x2e, 0xaa, +0x1d, 0x12, 0x1f, 0x57, 0xc3, 0xe1, 0x30, 0x8a, +0x52, 0x36, 0xee, 0x1e, 0x0e, 0x42, 0x06, 0xe5, +0x4f, 0x4a, 0x34, 0x8d, 0x64, 0xb6, 0xce, 0xcb, +0x86, 0x88, 0xc5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0xba, 0xe4, 0x3a, +0x02, 0xf2, 0xae, 0x9d, 0x91, 0x9b, 0xd4, 0x29, +0xf3, 0xc8, 0x9d, 0xe3, 0x13, 0xd9, 0x78, 0x09, +0x0b, 0xb6, 0xc3, 0xb2, 0xab, 0x17, 0x58, 0x34, +0x04, 0x2e, 0xe3, 0xfe, 0x10, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0x44, +0xb7, 0x76, 0x78, 0xea, 0x05, 0x6b, 0x65, 0x37, +0xaa, 0x6f, 0xb9, 0xbd, 0x35, 0x13, 0x75, 0x0a, +0x58, 0xb3, 0xad, 0xd1, 0x7f, 0x4d, 0x09, 0xe7, +0x72, 0x43, 0xbf, 0x39, 0x97, 0x86, 0xa6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0xe2, 0x4d, 0x98, 0xb2, 0xa3, 0x1d, 0x8d, +0xe9, 0x55, 0x7b, 0xd3, 0xcb, 0xb9, 0x69, 0x22, +0x16, 0x1b, 0xeb, 0xb1, 0x42, 0xce, 0x7c, 0x1a, +0x71, 0x37, 0xd7, 0x24, 0x86, 0x52, 0x89, 0x50, +0xf5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0xf2, 0x07, 0xd5, 0x28, 0xe9, +0xde, 0x3a, 0x9f, 0x63, 0x97, 0x08, 0x94, 0x57, +0x3d, 0x01, 0x72, 0x68, 0xa2, 0xce, 0x20, 0x5a, +0x2e, 0x38, 0x0b, 0xa4, 0x36, 0xbc, 0x17, 0xaf, +0xb4, 0x4a, 0xb6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0x09, 0x61, 0x23, +0x90, 0xb2, 0x79, 0xb0, 0xe9, 0x2d, 0xfe, 0xd3, +0x23, 0x73, 0x8f, 0x87, 0x18, 0xe3, 0xa3, 0xec, +0xba, 0x91, 0x72, 0x50, 0x31, 0x4d, 0xc2, 0x01, +0xc2, 0xe3, 0x0a, 0x0e, 0xf4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0x91, +0xfe, 0x33, 0x2a, 0x03, 0x99, 0xce, 0x93, 0xa5, +0xb8, 0x28, 0x8c, 0x79, 0x66, 0x4e, 0x1e, 0x86, +0x6a, 0xe2, 0xfa, 0x4a, 0x66, 0xd6, 0x8c, 0xa5, +0xc4, 0xf2, 0x4d, 0xf8, 0x8f, 0xfa, 0xae, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0xee, 0xef, 0x0c, 0xa7, 0xe6, 0x90, 0xeb, +0x9e, 0x80, 0x0f, 0x04, 0xef, 0xc0, 0xe3, 0xbf, +0xa0, 0x87, 0x88, 0xce, 0x39, 0x07, 0x42, 0x76, +0xf1, 0xf9, 0x17, 0x69, 0x95, 0x8f, 0x2c, 0x70, +0xfa, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0x07, 0x67, 0xa9, 0x8c, 0xff, +0x98, 0x07, 0xa8, 0xbf, 0xeb, 0x64, 0xd7, 0x69, +0x4f, 0x3e, 0xbd, 0xa3, 0x80, 0x8e, 0x51, 0x09, +0x07, 0x8b, 0x9f, 0x7f, 0xa8, 0x58, 0x98, 0x26, +0x6b, 0x12, 0x49, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0x92, 0x16, 0x7f, +0x58, 0xb5, 0x02, 0xd4, 0x2a, 0x6f, 0x58, 0xba, +0xe9, 0xe9, 0x9f, 0x6a, 0xd3, 0xd6, 0xed, 0xc9, +0x9a, 0x93, 0x75, 0x7d, 0x5e, 0x6e, 0x0b, 0x37, +0x3f, 0xbc, 0xb0, 0x40, 0x63, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0xc7, +0x26, 0x0f, 0xdd, 0x93, 0xb1, 0xe6, 0x1f, 0x17, +0xe5, 0xae, 0x85, 0xce, 0xfe, 0x6e, 0x43, 0xb2, +0x89, 0x43, 0x16, 0x34, 0x13, 0xa8, 0xaf, 0x60, +0x48, 0x71, 0x27, 0x6e, 0x97, 0x8e, 0xcb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x82, 0x32, 0x5e, 0x0f, 0x24, 0x05, 0xcf, +0x2c, 0x4d, 0x15, 0x65, 0xbe, 0xca, 0x20, 0x95, +0xb2, 0xa5, 0xb6, 0x4c, 0xee, 0xb7, 0xcc, 0x75, +0x80, 0xa6, 0x96, 0x00, 0xad, 0x88, 0xa9, 0x24, +0x84, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0x73, 0x95, 0xe8, 0xc3, 0x2d, +0x87, 0x17, 0x72, 0xbb, 0x9b, 0xbc, 0xf0, 0xdc, +0xaf, 0x7b, 0x00, 0x56, 0x39, 0xd3, 0xdc, 0xf8, +0x18, 0xda, 0x5d, 0xca, 0x2b, 0x00, 0x78, 0x26, +0x26, 0x29, 0x59, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0xc6, 0x75, 0xb9, +0x23, 0x28, 0x1b, 0x25, 0xa9, 0xad, 0x5e, 0x48, +0x2c, 0x6b, 0x3f, 0x8a, 0x50, 0x9a, 0xf8, 0x62, +0xef, 0x2b, 0x64, 0x7b, 0x94, 0x37, 0x7b, 0x49, +0xfe, 0xc6, 0x36, 0xd4, 0x8d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0x80, +0x1f, 0x8f, 0x2c, 0xef, 0xea, 0x5b, 0xd7, 0xae, +0x1f, 0x8f, 0x6b, 0xd3, 0xaa, 0x52, 0xc4, 0x54, +0xf6, 0xec, 0x9f, 0xd5, 0xd1, 0xf1, 0xad, 0xc8, +0x9d, 0x55, 0xb6, 0x71, 0xd5, 0x8c, 0x2c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0xad, 0x3c, 0x2b, 0xfe, 0x27, 0x52, 0x61, +0xc6, 0x6c, 0x3d, 0x1f, 0x10, 0x18, 0x20, 0x01, +0xe6, 0xf1, 0x31, 0x84, 0x37, 0x7d, 0x44, 0x1c, +0x7a, 0x05, 0xb9, 0x0e, 0x02, 0x10, 0x01, 0xbc, +0x98, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0x4c, 0xf4, 0xee, 0x44, 0x57, +0x0f, 0x50, 0xe3, 0x91, 0xf5, 0xff, 0x65, 0x34, +0x2f, 0xb0, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x35, 0x1f, 0x9a, 0x13, +0xe5, 0x36, 0xe4, 0xf9, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0xdf, 0x64, 0xab, +0x6f, 0x89, 0x21, 0x06, 0x9a, 0xbb, 0xfe, 0x86, +0xa1, 0x62, 0x82, 0x9e, 0x23, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x9f, +0x6f, 0xc6, 0xf9, 0x7f, 0xb2, 0x2f, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0xf1, +0x01, 0xbc, 0xd1, 0xf3, 0x20, 0x08, 0x92, 0xc2, +0x05, 0x85, 0xe1, 0xf9, 0x0f, 0x0b, 0xf6, 0x05, +0x74, 0x63, 0x4c, 0x20, 0x0f, 0xe9, 0xad, 0x71, +0x30, 0x87, 0x70, 0x7b, 0xce, 0x77, 0xd7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0x70, 0x92, 0x05, 0x0e, 0xdf, 0xff, 0xd6, +0xcc, 0x29, 0x43, 0xbf, 0x5d, 0x32, 0x30, 0x93, +0xec, 0xbe, 0x1e, 0xc5, 0x9a, 0x36, 0x85, 0x38, +0x79, 0xa0, 0x43, 0x7b, 0xc3, 0x39, 0x45, 0x0f, +0xdc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0x7e, 0xbf, 0xf6, 0x32, 0xa8, +0x0a, 0x08, 0x67, 0xa0, 0xef, 0x6f, 0x3c, 0xa0, +0x26, 0x6a, 0x0a, 0xb2, 0xa1, 0xdc, 0x02, 0x5f, +0xb9, 0x52, 0xb4, 0x0b, 0xe2, 0x1f, 0xf6, 0x89, +0x03, 0xb7, 0xd9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0xe6, 0x51, 0xcb, +0x82, 0xbd, 0x19, 0xe7, 0xb4, 0x46, 0xc7, 0xd3, +0xe3, 0xb4, 0x71, 0x71, 0xcb, 0x5f, 0x57, 0x7e, +0x5c, 0xa6, 0x9b, 0x54, 0xa4, 0x3d, 0xff, 0x1e, +0xff, 0x7e, 0xb5, 0xea, 0x1d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0x23, +0x76, 0x5c, 0x71, 0xcc, 0x9d, 0x81, 0x52, 0x68, +0x99, 0x65, 0x9c, 0x89, 0xae, 0x37, 0xa3, 0x11, +0x19, 0x0b, 0x83, 0xc5, 0xa0, 0x1e, 0xa9, 0xa8, +0xc8, 0xae, 0x4c, 0xa5, 0x76, 0x68, 0xb0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0xb5, 0x1f, 0x1f, 0x9e, 0x75, 0x21, 0x63, +0xff, 0xec, 0xe4, 0x61, 0xa7, 0x08, 0x08, 0x4e, +0x33, 0xdc, 0x55, 0x91, 0x48, 0xe0, 0x4d, 0x18, +0x7a, 0x73, 0xbe, 0x49, 0x16, 0x1b, 0xd0, 0x56, +0xfe, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0x64, 0xae, 0x0a, 0x65, 0xd8, +0x47, 0x10, 0x70, 0x7d, 0xb7, 0xb2, 0xc7, 0x55, +0x7b, 0xbe, 0x0f, 0x01, 0xd5, 0x4b, 0x85, 0x96, +0xaa, 0x5e, 0x70, 0x02, 0x03, 0x94, 0x9e, 0x80, +0x32, 0xc6, 0x40, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0xc6, 0x68, 0x83, +0x88, 0x9f, 0x83, 0x22, 0x90, 0x0a, 0xbe, 0x38, +0x4c, 0x3c, 0x55, 0x44, 0x73, 0x05, 0x9e, 0x5c, +0x9c, 0x3c, 0x04, 0x95, 0xfd, 0xee, 0x9e, 0x59, +0x9b, 0x18, 0xa1, 0xc9, 0xbf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0x9e, +0x65, 0x21, 0x96, 0x0b, 0x91, 0x01, 0x58, 0xe4, +0x44, 0x2c, 0x02, 0x51, 0x04, 0xf9, 0x08, 0x4e, +0x37, 0x75, 0xfa, 0xac, 0x04, 0x7e, 0x4c, 0x0f, +0xaa, 0x4e, 0x12, 0x56, 0x1d, 0xdd, 0x70, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0xff, 0xe7, 0xdb, 0x49, 0xc1, 0x07, 0xac, +0xfe, 0xd4, 0xbc, 0x1e, 0x69, 0x2a, 0x57, 0x91, +0x61, 0x44, 0x47, 0x4a, 0xe8, 0xc6, 0x51, 0x43, +0xda, 0x71, 0xdf, 0x79, 0x34, 0x8c, 0x8a, 0x98, +0x61, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x2f, 0x0d, 0x79, 0x35, 0x0d, +0xb9, 0xe9, 0xcf, 0xc8, 0x7d, 0xd3, 0x4d, 0x13, +0x7d, 0x38, 0x02, 0xf9, 0x04, 0xd4, 0xf1, 0xdc, +0x86, 0x38, 0x47, 0x2d, 0xdd, 0x65, 0x73, 0x28, +0x8e, 0xdb, 0x18, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0xcf, 0xc4, 0x1d, +0xa7, 0xc9, 0x80, 0x6e, 0x03, 0xf1, 0x09, 0x9f, +0x69, 0xd0, 0x0a, 0x7d, 0xab, 0xa3, 0xc6, 0x26, +0x41, 0x57, 0x45, 0x11, 0x28, 0x5b, 0xe1, 0xdb, +0xef, 0x24, 0xb7, 0x1a, 0x37, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0x4d, +0x56, 0x12, 0x43, 0xfa, 0x66, 0x6f, 0x61, 0xd8, +0x47, 0xd6, 0x1d, 0x01, 0x54, 0xa3, 0xcd, 0x4f, +0xd9, 0xf3, 0x2b, 0xc7, 0x29, 0xdf, 0xc6, 0x59, +0x71, 0xee, 0x3f, 0x00, 0x5e, 0x7b, 0xa0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0x5d, 0xbb, 0x49, 0x5c, 0x17, 0x75, 0xc1, +0xe9, 0x29, 0x75, 0x59, 0x16, 0x5b, 0x89, 0x8b, +0x35, 0x0a, 0x64, 0x63, 0xac, 0x1b, 0x31, 0xa9, +0xa2, 0xc0, 0xa3, 0xe5, 0xc4, 0xc8, 0x2d, 0xeb, +0x9c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0x12, 0xa4, 0xac, 0x07, 0x07, +0x3d, 0xd4, 0xb4, 0x71, 0x29, 0xc1, 0x72, 0x6b, +0xbb, 0x23, 0x03, 0x3f, 0xf8, 0xd4, 0x11, 0x90, +0x63, 0x1f, 0xf0, 0x61, 0xc2, 0x3c, 0x8c, 0xf4, +0x1a, 0x2f, 0x6f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0x6d, 0xd4, 0xf3, +0x43, 0xe8, 0x2c, 0xc1, 0xc6, 0x39, 0xdf, 0x7e, +0x31, 0xcf, 0xd2, 0xbd, 0xa5, 0x94, 0x6f, 0xd3, +0xfd, 0x5b, 0x9f, 0x2a, 0x1d, 0xde, 0xe6, 0xca, +0x05, 0xf2, 0x37, 0xfc, 0x79, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0x99, +0xd9, 0x00, 0x74, 0x51, 0x2b, 0x75, 0xb7, 0x16, +0xd2, 0xfd, 0x37, 0x28, 0xbf, 0x51, 0x46, 0xbb, +0xb3, 0x03, 0x13, 0x49, 0x7e, 0x5a, 0x52, 0xd9, +0xb3, 0x40, 0x3e, 0x42, 0x1d, 0x86, 0xbe, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x4f, 0x5c, 0x93, 0x95, 0xc6, 0xe2, 0x7e, +0x3f, 0xa9, 0xba, 0x11, 0xa5, 0x55, 0xd5, 0xfd, +0x33, 0x0e, 0x34, 0x1e, 0x5b, 0x0b, 0x17, 0x23, +0x84, 0x96, 0xe0, 0xa9, 0x01, 0xde, 0x9e, 0x6b, +0x7a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0x3c, 0x4e, 0x50, 0xe1, 0x70, +0x97, 0x5c, 0x60, 0xac, 0xdf, 0x1f, 0x6d, 0xd1, +0x70, 0xe3, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x0d, 0x2b, 0xca, 0xb7, +0xac, 0x79, 0x6b, 0xd0, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0x00, 0x78, 0xa5, +0xf1, 0x53, 0xd4, 0xd5, 0x74, 0x1c, 0x80, 0x6a, +0x65, 0x08, 0x30, 0xac, 0x45, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x32, +0x37, 0x22, 0xdd, 0x8e, 0xb0, 0x60, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0x21, +0xce, 0xf5, 0x7f, 0xea, 0x1b, 0x7e, 0x1c, 0x2e, +0x39, 0x55, 0x15, 0x84, 0x66, 0x6e, 0xbf, 0x74, +0xac, 0x38, 0xe3, 0x86, 0x7a, 0x4e, 0x97, 0xd5, +0x16, 0x55, 0x10, 0x05, 0x5c, 0xf0, 0xed, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0xe3, 0xd9, 0x50, 0x0e, 0x49, 0x4a, 0x93, +0x51, 0xaf, 0x7c, 0x5b, 0x73, 0xf4, 0xf1, 0x0a, +0x68, 0xa2, 0x65, 0x3d, 0x38, 0x44, 0x9b, 0x96, +0x2e, 0x72, 0x18, 0xc2, 0x3b, 0x05, 0x43, 0x31, +0x40, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0x53, 0x68, 0xb1, 0x97, 0x6f, +0x71, 0x23, 0xb0, 0x25, 0xe2, 0x68, 0x40, 0xfc, +0x34, 0xfb, 0x00, 0x4f, 0x29, 0xad, 0x2b, 0x42, +0xc4, 0x03, 0x84, 0x52, 0xdb, 0x00, 0x6e, 0x30, +0x4d, 0xb6, 0x42, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0x52, 0x55, 0xf2, +0x8a, 0x07, 0x38, 0x64, 0x58, 0x67, 0xd7, 0x49, +0x1b, 0xb0, 0x69, 0xd4, 0x16, 0xbf, 0xdb, 0xb1, +0xcf, 0xb0, 0x21, 0x62, 0xbb, 0xf6, 0x05, 0x65, +0xd1, 0x98, 0x98, 0xaf, 0xe9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0x42, +0xdf, 0x28, 0xf1, 0x4c, 0xa5, 0x24, 0x9a, 0x24, +0xec, 0xa4, 0xba, 0xed, 0x3d, 0x81, 0xc6, 0x22, +0x02, 0xc8, 0xbe, 0x46, 0x66, 0x5b, 0x26, 0x71, +0x57, 0x71, 0x03, 0xb2, 0x95, 0x5c, 0x92, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0x09, 0xcf, 0xb1, 0x25, 0xb8, 0x2f, 0xe5, +0x50, 0xe1, 0xb3, 0x42, 0x99, 0xc6, 0xfc, 0xc0, +0xfa, 0x99, 0xb8, 0x1f, 0xe5, 0xfb, 0x0c, 0xd4, +0x4b, 0x2d, 0x66, 0x03, 0xa1, 0x19, 0x28, 0xd3, +0x86, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0x0f, 0x40, 0xd1, 0x35, 0x57, +0xc5, 0x51, 0x19, 0x76, 0x94, 0x9e, 0x6a, 0xa6, +0x45, 0x43, 0x50, 0xd2, 0xda, 0x7d, 0xca, 0x3d, +0xe4, 0x1b, 0x92, 0x84, 0x3f, 0x7c, 0x8a, 0x57, +0x5f, 0x79, 0x5c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0x20, 0xde, 0xf1, +0xb0, 0x96, 0x6a, 0xcf, 0x72, 0x7f, 0xd3, 0x42, +0xa4, 0x67, 0x82, 0xc4, 0xee, 0x2a, 0x00, 0xa1, +0xcb, 0x0a, 0xa2, 0xe9, 0xdf, 0x8f, 0x34, 0x61, +0xd9, 0xfa, 0xd5, 0x0f, 0xf3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0x58, +0xe0, 0x5b, 0x81, 0x25, 0xe3, 0xd8, 0x87, 0xd3, +0x6a, 0x27, 0x2c, 0xa9, 0xb6, 0x73, 0x71, 0x1a, +0x35, 0x35, 0x49, 0xf1, 0x92, 0x22, 0x57, 0xc2, +0x2f, 0xf4, 0x1f, 0x9f, 0x07, 0xc1, 0x46, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0xa1, 0xc2, 0x54, 0x1b, 0x84, 0xd4, 0x36, +0xf0, 0x9d, 0xe7, 0x03, 0x1b, 0x71, 0xdb, 0xd8, +0xe6, 0xf0, 0x77, 0xbd, 0x84, 0x63, 0x2c, 0x65, +0x92, 0x6d, 0x86, 0x89, 0x12, 0xe7, 0x4a, 0x50, +0x73, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0xaf, 0xd7, 0xbb, 0x17, 0x0a, +0xd4, 0xf7, 0xa4, 0x0f, 0xd1, 0x9a, 0x16, 0x8d, +0xb2, 0x18, 0x84, 0x85, 0xd6, 0x98, 0xac, 0xba, +0xe3, 0xf6, 0x57, 0xea, 0x08, 0x22, 0x9f, 0x7d, +0xd3, 0xee, 0xf1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0x97, 0x33, 0xe1, +0x75, 0x64, 0xdf, 0xd9, 0x24, 0x43, 0x3b, 0x4c, +0x2f, 0x17, 0xa5, 0xe9, 0x4d, 0xec, 0x49, 0x37, +0xca, 0xbe, 0x2b, 0x97, 0x8c, 0x28, 0xa7, 0x80, +0xb3, 0x94, 0x38, 0x1b, 0x8a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0x7e, +0xbc, 0x3a, 0xc5, 0x3e, 0x16, 0x61, 0x8f, 0xaf, +0xd1, 0xe6, 0x5d, 0xe8, 0x25, 0x1e, 0xb6, 0x54, +0x72, 0x40, 0x11, 0x2f, 0xe1, 0x3a, 0x06, 0x18, +0x8c, 0x6d, 0x77, 0x7b, 0x84, 0x60, 0xe9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0xfc, 0xa1, 0x98, 0xb0, 0x09, 0x09, 0xe8, +0xe9, 0xe5, 0x0b, 0x4b, 0x25, 0x27, 0xbe, 0x2c, +0x03, 0x8f, 0xc0, 0x00, 0xc6, 0xb1, 0xf8, 0x1b, +0xe0, 0x8a, 0xb0, 0x05, 0xbd, 0xbe, 0x4c, 0xdb, +0xa2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0x17, 0x11, 0x14, 0x92, 0xec, +0xb5, 0x23, 0xde, 0x98, 0x69, 0xc0, 0x41, 0x9a, +0xaf, 0x79, 0xc8, 0x13, 0xec, 0x4b, 0x88, 0x57, +0xcf, 0xb1, 0xb7, 0xbd, 0x95, 0xa1, 0xd5, 0x52, +0xa4, 0x86, 0x20, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0x31, 0xe4, 0xe1, +0x9d, 0xb5, 0x24, 0x7c, 0x25, 0x11, 0xa4, 0x8c, +0x89, 0xfa, 0xa1, 0xaa, 0x55, 0xd3, 0x15, 0xc3, +0xcb, 0xc0, 0xbb, 0x9f, 0xdd, 0x91, 0x3a, 0x6c, +0xcc, 0x04, 0xd9, 0xf7, 0x84, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0xbd, +0x7a, 0x19, 0xd1, 0x4b, 0x9a, 0xff, 0x8a, 0x68, +0x52, 0x30, 0x98, 0x5a, 0x5b, 0x3c, 0x30, 0x1b, +0x6f, 0x55, 0xa6, 0x14, 0x64, 0x4c, 0xba, 0x40, +0x16, 0x48, 0xaa, 0x57, 0x6e, 0x97, 0x67, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0x56, 0x08, 0x0e, 0x71, 0xf6, 0x2a, 0x83, +0x34, 0x6e, 0x04, 0x05, 0x00, 0x97, 0x42, 0x31, +0x00, 0xca, 0xfd, 0x28, 0x65, 0x7d, 0x72, 0x20, +0x41, 0x08, 0x43, 0x59, 0x1a, 0x41, 0x74, 0x4f, +0x00, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0x61, 0x68, 0x8f, 0x3d, 0x6f, +0x77, 0x86, 0xb5, 0x8a, 0x2e, 0xb1, 0x92, 0xc5, +0x6e, 0x9a, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xad, 0x1b, 0xb9, 0xaa, +0x67, 0xb0, 0xc5, 0x04, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0x2d, 0x47, 0x2b, +0xe9, 0xb9, 0x6c, 0x5a, 0x0d, 0xad, 0xd0, 0xe3, +0x8c, 0xc0, 0x78, 0x70, 0x8e, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x9e, +0xdd, 0xaf, 0x3d, 0x29, 0x0d, 0x44, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0x76, +0x77, 0x2f, 0xf4, 0xde, 0x6e, 0xef, 0x95, 0xd6, +0x8b, 0x06, 0x44, 0xec, 0x36, 0xf7, 0x58, 0x38, +0x92, 0xfa, 0x49, 0xd0, 0xc7, 0xa5, 0x72, 0x28, +0xfe, 0x58, 0x27, 0x6a, 0xb8, 0x43, 0xfe, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0x88, 0xbf, 0xb8, 0x41, 0xcf, 0x03, 0x56, +0xee, 0x84, 0x6c, 0xe6, 0xe1, 0x2a, 0x24, 0xbe, +0x58, 0xcd, 0xe4, 0x1b, 0x09, 0x25, 0xb0, 0x73, +0x02, 0x8c, 0x10, 0xa1, 0x8a, 0x48, 0xd2, 0xb3, +0xfc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0xf3, 0xbf, 0x6a, 0xb2, 0xe6, +0x6e, 0x93, 0x30, 0x68, 0x95, 0x43, 0x0d, 0xd0, +0x9f, 0x9c, 0xc7, 0xc6, 0x13, 0x4d, 0xef, 0xc5, +0x02, 0xc8, 0x36, 0x73, 0x72, 0x5d, 0x78, 0xc3, +0x80, 0xc4, 0x09, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0xab, 0xb6, 0x03, +0xef, 0xc8, 0x58, 0x5c, 0x67, 0xa0, 0xc2, 0x2c, +0xed, 0xf6, 0x24, 0xa9, 0xfd, 0xa0, 0x4d, 0x16, +0xd1, 0x80, 0x41, 0x50, 0xcf, 0xe4, 0x84, 0xa9, +0x4c, 0x2b, 0x00, 0xb6, 0x7d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0x38, +0x27, 0x0c, 0x72, 0xf4, 0x18, 0x6f, 0xc8, 0x85, +0xb5, 0x31, 0x8e, 0xfc, 0x64, 0xff, 0x26, 0x12, +0x89, 0x10, 0x34, 0x69, 0x37, 0xc6, 0x4f, 0x7f, +0xf4, 0x35, 0x2e, 0x85, 0x82, 0x27, 0x62, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0x30, 0x71, 0x34, 0x7f, 0x2f, 0x01, 0xb5, +0x5c, 0x91, 0x3e, 0x82, 0x83, 0xb7, 0x1f, 0x56, +0xbd, 0x58, 0xac, 0xcf, 0x94, 0x94, 0xcb, 0x61, +0x81, 0x2f, 0xe9, 0xde, 0x19, 0x64, 0xdf, 0xe6, +0x9d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0x1d, 0x3f, 0x4e, 0x29, 0xc2, +0x0b, 0xe3, 0x7d, 0xa3, 0xc8, 0xa1, 0xf2, 0xe3, +0x2d, 0x8c, 0x7f, 0xb1, 0xab, 0xca, 0x15, 0xa3, +0xa4, 0xcb, 0xec, 0xd3, 0x3f, 0x5e, 0x07, 0x24, +0x79, 0xf0, 0xc8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x9e, 0x66, 0xf9, +0x9c, 0x2a, 0x9d, 0x95, 0x8a, 0x95, 0x78, 0x31, +0x20, 0x49, 0x09, 0x60, 0xe0, 0x85, 0xa0, 0x78, +0xfa, 0x48, 0x34, 0x95, 0x5b, 0xe0, 0x0f, 0x3c, +0x03, 0x18, 0x96, 0xb5, 0xc7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0x1c, +0x57, 0x5a, 0x1e, 0x78, 0xbd, 0x6f, 0x0d, 0x09, +0x76, 0x55, 0x17, 0x9d, 0x71, 0x88, 0xbd, 0x42, +0x03, 0x2d, 0x2b, 0x3d, 0x99, 0xd7, 0x78, 0x59, +0xa2, 0x54, 0x94, 0x5a, 0x50, 0x94, 0xc5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0x02, 0x01, 0x26, 0x14, 0xb5, 0xce, 0x07, +0xb9, 0xe0, 0x02, 0x2f, 0xee, 0x3f, 0xdc, 0x26, +0xf5, 0xd9, 0x70, 0x1e, 0x2d, 0x49, 0x79, 0x12, +0xd6, 0xe9, 0x71, 0x9d, 0xec, 0xce, 0xa3, 0x75, +0xd0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0xaa, 0xfa, 0x5c, 0x8d, 0x21, +0x5d, 0xec, 0x6c, 0x19, 0x98, 0xe8, 0x47, 0x12, +0x82, 0x1b, 0x89, 0xc9, 0xc8, 0xac, 0xef, 0x21, +0x4f, 0xf2, 0x72, 0x04, 0x5c, 0x23, 0xac, 0x73, +0xef, 0x1d, 0x78, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0x36, 0xa7, 0xca, +0x3f, 0xfc, 0x2f, 0x2b, 0xf1, 0x84, 0x4b, 0x2c, +0x98, 0x1d, 0x88, 0xd3, 0xa5, 0xee, 0xe4, 0x01, +0x14, 0x15, 0x6a, 0xb9, 0xb4, 0x50, 0x9b, 0xc5, +0x0d, 0x1c, 0x56, 0x85, 0xe1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x8f, +0x47, 0x57, 0xa1, 0x71, 0x54, 0x19, 0xb5, 0xd8, +0xbd, 0xe1, 0x74, 0x46, 0xec, 0xe8, 0x4a, 0x9b, +0xae, 0x1a, 0x8d, 0xe3, 0x3d, 0x2d, 0x41, 0x76, +0x12, 0x7f, 0x9a, 0x54, 0xc0, 0x35, 0x3b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0x8d, 0xf3, 0x68, 0xe4, 0xae, 0xb6, 0x5a, +0x13, 0x76, 0x94, 0x39, 0x34, 0xdc, 0x0b, 0x7a, +0x30, 0x92, 0x92, 0x1f, 0xa7, 0x71, 0xb5, 0xcd, +0xc5, 0xb7, 0xfa, 0xcd, 0x9f, 0x9b, 0x09, 0xde, +0x7f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0xb3, 0x80, 0x38, 0xdd, 0x2c, +0x31, 0x64, 0xa4, 0x2a, 0xa8, 0x89, 0xc5, 0x7f, +0x18, 0xe8, 0x1a, 0x2f, 0x52, 0x61, 0xaa, 0x51, +0xcb, 0x01, 0x20, 0x2e, 0x94, 0x45, 0x7c, 0x88, +0x1f, 0xfb, 0x7b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0x18, 0x61, 0x46, +0x1b, 0xe2, 0x8a, 0xed, 0xb3, 0x84, 0x86, 0x00, +0x75, 0xe7, 0x70, 0x42, 0xaf, 0xab, 0x5e, 0x2e, +0x56, 0x5b, 0xfe, 0xa9, 0x06, 0x9b, 0xb1, 0x30, +0x91, 0xb9, 0x8f, 0xdb, 0x21, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0xc7, +0x1e, 0x82, 0xb3, 0xc8, 0x0c, 0x52, 0x8d, 0x84, +0x04, 0xe6, 0x5f, 0x91, 0xc2, 0x47, 0x49, 0x74, +0x23, 0xc3, 0x54, 0xa4, 0x62, 0x7a, 0xe9, 0x80, +0x49, 0x79, 0x68, 0xaa, 0xac, 0x3d, 0xb1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0x86, 0xc8, 0x9f, 0x42, 0xb5, 0xa0, 0xb4, +0x2c, 0xc7, 0x41, 0x73, 0x52, 0xcd, 0xb0, 0xa5, +0x2c, 0x7a, 0xec, 0x94, 0xd1, 0xef, 0x60, 0x87, +0xf8, 0x9e, 0x7b, 0x04, 0xb3, 0x4c, 0xc6, 0xf6, +0xcc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0x36, 0x1f, 0x00, 0x3f, 0xbb, +0xff, 0x8a, 0x0e, 0xa2, 0x69, 0x56, 0xc9, 0x10, +0xf1, 0x18, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x18, 0x24, 0x99, 0x5a, +0x01, 0x4a, 0x3d, 0xff, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0xf5, 0xcc, 0xe3, +0xe6, 0x22, 0x08, 0xae, 0xe9, 0x2d, 0xa8, 0xf7, +0x29, 0x64, 0xff, 0xf6, 0xfc, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x63, +0x9c, 0x44, 0x25, 0xa8, 0x3d, 0x4f, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0xb8, +0xe5, 0x15, 0xc2, 0xab, 0xc4, 0x41, 0x58, 0x8a, +0x20, 0xd5, 0xe1, 0x33, 0x6b, 0x6e, 0x26, 0x41, +0x86, 0xeb, 0x15, 0x8e, 0xfa, 0x1d, 0x1c, 0x43, +0x1e, 0x51, 0x35, 0x96, 0x44, 0xdc, 0xde, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0xff, 0xe5, 0x73, 0x49, 0xfd, 0x61, 0x58, +0x9d, 0xca, 0x81, 0x6c, 0xe8, 0x9d, 0x2d, 0xee, +0x53, 0x29, 0x70, 0x7c, 0xc5, 0x4c, 0x4e, 0xa4, +0xf6, 0x29, 0xfc, 0xc2, 0x4a, 0xa4, 0xae, 0xa5, +0x48, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0xd9, 0x47, 0x03, 0xfe, 0x3b, +0xb0, 0x2d, 0xda, 0x87, 0x88, 0x05, 0xef, 0x51, +0x49, 0x13, 0xbb, 0xdb, 0xc7, 0xa9, 0x10, 0x54, +0x76, 0x0a, 0x9d, 0xb0, 0x2b, 0x38, 0xec, 0xda, +0x67, 0xb3, 0x80, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0xb4, 0xb1, 0xe4, +0x34, 0x68, 0xdc, 0xff, 0xe8, 0xb0, 0xb9, 0x4c, +0x26, 0xb1, 0xe5, 0xf0, 0xec, 0x1b, 0xe2, 0x02, +0x29, 0x1b, 0x44, 0x9d, 0xd9, 0x78, 0xc0, 0x63, +0x0c, 0xc3, 0x16, 0x8a, 0xae, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0x94, +0xfa, 0x56, 0xf3, 0x5e, 0x17, 0xe0, 0xe0, 0xd3, +0x63, 0x36, 0x72, 0xf9, 0x2c, 0x42, 0xb8, 0x3c, +0x89, 0x16, 0x83, 0xbc, 0x00, 0xe8, 0x95, 0xae, +0x9a, 0x0a, 0xd1, 0xb6, 0x6c, 0x0b, 0xc9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0xfc, 0xa7, 0x2c, 0x79, 0x13, 0x3f, 0x17, +0x17, 0xe5, 0x7a, 0x7a, 0x33, 0x32, 0xb3, 0x9a, +0xb0, 0x53, 0xb7, 0x51, 0x80, 0xb5, 0x8d, 0xc9, +0x83, 0x0c, 0xfb, 0x10, 0xdb, 0x1f, 0x34, 0xe9, +0x9f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0x2a, 0x3d, 0x1b, 0x27, 0x95, +0xdf, 0xc0, 0x41, 0xb8, 0xf3, 0x5d, 0x69, 0xae, +0x84, 0x8f, 0xec, 0xbb, 0xcf, 0xa8, 0x9a, 0xe1, +0x76, 0xaa, 0xfb, 0x0d, 0x12, 0xe0, 0xc2, 0xd4, +0xe1, 0x5f, 0x45, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x1e, 0xf0, 0x67, +0xad, 0xef, 0xa8, 0xdf, 0xe5, 0x89, 0x38, 0x37, +0x64, 0x84, 0x17, 0x78, 0xff, 0xa0, 0x83, 0xa2, +0xe7, 0x7e, 0x59, 0xbf, 0x54, 0x5c, 0x27, 0x1d, +0x2e, 0xa1, 0xca, 0x23, 0x85, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0xe2, +0x50, 0x81, 0x4a, 0x70, 0x91, 0x2d, 0xa4, 0xda, +0xb4, 0x61, 0x93, 0x62, 0xe3, 0xe1, 0xe8, 0x43, +0xa0, 0x0d, 0x22, 0x49, 0xb0, 0xb3, 0xc7, 0x06, +0xd5, 0xd7, 0x68, 0xa1, 0x01, 0x48, 0x99, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x3a, 0x5e, 0x5e, 0xf8, 0xbe, 0x81, 0xd8, +0x90, 0x36, 0xcb, 0xdc, 0x89, 0x15, 0x6b, 0x38, +0xc2, 0xfe, 0x0b, 0x2b, 0x38, 0x50, 0x36, 0x1c, +0xc4, 0x12, 0xb9, 0xeb, 0x60, 0x51, 0x34, 0xcf, +0xdd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0x1d, 0x10, 0xab, 0xc5, 0x3b, +0x5b, 0x59, 0x32, 0x38, 0x8c, 0xd5, 0xa7, 0x65, +0xdf, 0x5f, 0x84, 0xbe, 0x3d, 0x4c, 0x05, 0xc9, +0x95, 0x89, 0x2b, 0xcf, 0xf5, 0x16, 0xe3, 0x1c, +0xf1, 0xa7, 0x7f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0x77, 0x85, 0x7e, +0x64, 0x06, 0x6f, 0xa7, 0x66, 0x4e, 0xf6, 0xa6, +0x6d, 0xd6, 0x88, 0x6f, 0x20, 0xf9, 0x75, 0x10, +0x75, 0x1d, 0xbb, 0x53, 0x69, 0x31, 0x21, 0xd7, +0x43, 0xd9, 0xd6, 0x8d, 0x5c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0x77, +0xaf, 0x38, 0x07, 0xa9, 0x84, 0xd6, 0x4d, 0xda, +0x38, 0x76, 0xe9, 0x1f, 0xa1, 0x30, 0x45, 0x76, +0xb3, 0x22, 0xdd, 0xcc, 0xf7, 0x78, 0xfd, 0xf8, +0x21, 0xa6, 0x21, 0xdd, 0x77, 0xb4, 0x15, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0x6b, 0xab, 0xf8, 0xaf, 0xc5, 0xd6, 0x65, +0x96, 0x87, 0x0c, 0xb3, 0xa8, 0xb7, 0x6b, 0x30, +0xe9, 0xf4, 0xf3, 0x36, 0xa6, 0x11, 0x3d, 0xac, +0xb6, 0xcd, 0xa0, 0xcd, 0x62, 0xe9, 0xe1, 0x98, +0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0x1e, 0xad, 0x9e, 0x41, 0xfe, +0x40, 0x16, 0x33, 0x1d, 0x32, 0x5d, 0xf6, 0x51, +0xaa, 0x4f, 0x58, 0xbe, 0xfc, 0x52, 0xf5, 0x91, +0x13, 0xab, 0x04, 0x0e, 0x2f, 0xab, 0x97, 0x25, +0xba, 0x41, 0x21, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0x81, 0x8b, 0x32, +0x5a, 0xc9, 0x09, 0xe1, 0x0a, 0x96, 0x4e, 0x85, +0xc0, 0xbd, 0x6b, 0x02, 0xc8, 0xa1, 0x3d, 0x94, +0xbb, 0x10, 0x78, 0x92, 0xd7, 0xde, 0xfe, 0x4e, +0x7d, 0x1a, 0x8c, 0xdf, 0xf6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0x47, +0xb2, 0xa5, 0x4f, 0x69, 0x8f, 0x18, 0x12, 0xcd, +0x14, 0x70, 0x21, 0x67, 0x62, 0xc6, 0x09, 0x64, +0x39, 0xcd, 0xed, 0xe9, 0x35, 0x7d, 0x59, 0x58, +0xc3, 0xa4, 0x68, 0x48, 0xe2, 0xf7, 0x92, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0xc5, 0xa3, 0x8f, 0xa9, 0xb7, 0x96, 0xa3, +0x31, 0x7f, 0x6a, 0x29, 0x41, 0xd8, 0xf5, 0x88, +0x25, 0x88, 0x5a, 0xe9, 0x82, 0x02, 0x10, 0xf0, +0x7a, 0x4c, 0x97, 0xe5, 0xcf, 0x50, 0x06, 0xd4, +0xe5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0x32, 0x21, 0xb2, 0x08, 0xd5, +0xcc, 0x9a, 0xf3, 0xca, 0x13, 0x81, 0x19, 0x80, +0xa9, 0xcc, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x9d, 0x20, 0x17, 0x54, +0x7f, 0xd8, 0x6a, 0x13, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0x3c, 0xa4, 0x59, +0xa5, 0xe4, 0x5c, 0xcd, 0x2f, 0x68, 0x76, 0xfa, +0x17, 0x93, 0x9c, 0xa3, 0x4e, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb, 0xa1, +0x8c, 0x5f, 0xcb, 0xd3, 0x82, 0x35, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0x86, +0x73, 0x7e, 0xb3, 0x7d, 0x50, 0x84, 0xc9, 0xb6, +0xef, 0xaa, 0xbc, 0xea, 0xe0, 0xaa, 0xfb, 0x93, +0xcb, 0xba, 0x25, 0xfe, 0xb1, 0x25, 0xc8, 0x07, +0x2e, 0x18, 0xf9, 0xd7, 0xc8, 0x0d, 0x62, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0xc0, 0x53, 0xed, 0x07, 0x98, 0x91, 0xeb, +0x72, 0x92, 0xa9, 0xf8, 0xf4, 0xb3, 0xac, 0x68, +0x0d, 0xa4, 0x50, 0x80, 0x52, 0xc5, 0x1a, 0x84, +0xa9, 0x49, 0x43, 0xa3, 0x56, 0x87, 0x91, 0x17, +0x16, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0xf0, 0x79, 0xfd, 0x4e, 0x87, +0xd9, 0x7d, 0x61, 0xad, 0xf2, 0x04, 0x90, 0x09, +0x06, 0xd0, 0xe3, 0x39, 0xcf, 0xe9, 0xce, 0x80, +0xa4, 0x5d, 0xd9, 0xcd, 0xb6, 0x5f, 0x68, 0xb9, +0xc0, 0x77, 0xd1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0xde, 0x37, 0x18, +0x12, 0x34, 0x82, 0x21, 0x89, 0xa7, 0xbb, 0x38, +0x8b, 0x53, 0xf7, 0x65, 0x10, 0x07, 0xeb, 0x1b, +0x00, 0x07, 0x8f, 0x05, 0x01, 0xf0, 0xce, 0x40, +0x76, 0x9f, 0x6a, 0x0d, 0x6f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0xaa, +0x43, 0x8f, 0x08, 0x78, 0x48, 0x8e, 0xbe, 0xc2, +0x05, 0x1f, 0xb6, 0x30, 0xae, 0x0b, 0x2c, 0x05, +0x1f, 0xc2, 0xb1, 0x79, 0x4f, 0x1f, 0x4c, 0x9d, +0x52, 0xe4, 0xea, 0x5d, 0x0c, 0xb4, 0xfe, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0x02, 0x40, 0x02, 0xfd, 0x72, 0x0c, 0x04, +0x5e, 0x9e, 0xf2, 0x73, 0x41, 0xaa, 0x91, 0x83, +0x58, 0x90, 0x7f, 0xbc, 0x28, 0x81, 0x37, 0x35, +0x90, 0xa7, 0x3a, 0x84, 0x8d, 0xf4, 0x0f, 0x40, +0x77, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0x9c, 0x46, 0xdb, 0x22, 0xc7, +0x8c, 0x5e, 0xbc, 0xc1, 0xae, 0x16, 0xbf, 0xb6, +0x2b, 0xa4, 0x6d, 0xae, 0x73, 0x30, 0xdd, 0xdc, +0x79, 0xbe, 0x80, 0x38, 0x5a, 0xbe, 0x15, 0x5e, +0xa7, 0x9b, 0x17, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0x8e, 0xdc, 0x80, +0x6e, 0xa9, 0xcf, 0x1c, 0x38, 0x39, 0x9e, 0x81, +0x9a, 0x0d, 0xd7, 0x9a, 0x8a, 0xe9, 0x50, 0xd0, +0xd4, 0xd1, 0xc8, 0xca, 0x10, 0xea, 0x54, 0x08, +0x02, 0xeb, 0x50, 0x95, 0x5c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0x9d, +0x19, 0x9c, 0x32, 0xd8, 0x71, 0xcc, 0xb9, 0xd6, +0xe2, 0x01, 0x0e, 0x7e, 0x6d, 0xc4, 0x9e, 0x2f, +0x8b, 0x92, 0xae, 0xeb, 0xcc, 0xe0, 0x3e, 0xa5, +0x1f, 0x6e, 0x5c, 0xe0, 0xa7, 0xf6, 0x01, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0x41, 0x88, 0xf3, 0x96, 0xa2, 0xad, 0x4b, +0xa3, 0x3b, 0xc5, 0x07, 0x64, 0xfd, 0x57, 0xbb, +0xef, 0xbe, 0x82, 0x88, 0x9e, 0x67, 0xe3, 0xe7, +0x67, 0x0b, 0xa5, 0xce, 0xba, 0x69, 0xe0, 0xca, +0x27, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0x96, 0x51, 0xcd, 0x03, 0xa5, +0xf8, 0xc6, 0x13, 0xe8, 0xe5, 0x1d, 0xf9, 0x33, +0x74, 0xed, 0x1c, 0x46, 0xfa, 0x2a, 0x71, 0x4c, +0x09, 0x52, 0x9c, 0x14, 0xe4, 0x90, 0x33, 0x30, +0xe1, 0x69, 0x85, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0x2a, 0x44, 0x82, +0xca, 0x07, 0x43, 0x98, 0x57, 0x62, 0xdc, 0x7d, +0x16, 0xbf, 0xf7, 0xef, 0x42, 0xb0, 0x88, 0xd7, +0xe6, 0x79, 0xed, 0xf8, 0x1d, 0x3a, 0x3f, 0x7b, +0x6b, 0x5f, 0x22, 0xfb, 0x45, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0x5c, +0x50, 0x98, 0xac, 0x68, 0x0e, 0x43, 0x51, 0xa8, +0xa5, 0xe2, 0xd4, 0xed, 0x61, 0x05, 0x89, 0x22, +0xa8, 0x4c, 0x62, 0xb2, 0xfa, 0xf4, 0x03, 0xd1, +0x4d, 0xe4, 0xcc, 0x72, 0x30, 0x4c, 0x15, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0x84, 0x2b, 0x0e, 0x77, 0x32, 0x48, 0xc9, +0x3e, 0xef, 0x24, 0x17, 0xed, 0x9b, 0x1c, 0x45, +0xd2, 0x89, 0xef, 0x8b, 0x74, 0xc0, 0x1d, 0xbf, +0xae, 0x5d, 0xc2, 0x53, 0xbd, 0x2e, 0xa6, 0xeb, +0xbb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0x06, 0x3e, 0x69, 0x59, 0x11, +0xa4, 0x7a, 0x8f, 0xb8, 0xc2, 0x94, 0x29, 0x6e, +0x31, 0xa7, 0xfa, 0xcf, 0x6d, 0x53, 0x06, 0x6d, +0x30, 0x3c, 0xdc, 0x80, 0xc3, 0x98, 0xc5, 0x17, +0x88, 0x1e, 0x4e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0x07, 0x76, 0xe9, +0xba, 0x90, 0x64, 0x4f, 0x26, 0x54, 0xdb, 0xd0, +0xae, 0x06, 0xf5, 0xa7, 0x03, 0xa3, 0x29, 0x0b, +0x05, 0x58, 0x79, 0x60, 0x6a, 0xd0, 0x37, 0x02, +0x6c, 0x5b, 0x5d, 0x85, 0xb8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0x2c, +0x54, 0x27, 0x29, 0xed, 0x3b, 0x5f, 0x9c, 0xb5, +0x3c, 0x76, 0x2f, 0x2c, 0x32, 0x14, 0xd7, 0xca, +0xed, 0xb4, 0xae, 0x0d, 0x43, 0x13, 0xd9, 0x6e, +0x21, 0x11, 0xec, 0xf7, 0x81, 0x80, 0x49, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0x76, 0x55, 0x2e, 0x43, 0xf6, 0xfc, 0xc4, +0xb5, 0x00, 0x37, 0x1f, 0xcf, 0x15, 0x8e, 0xc4, +0x67, 0x21, 0xe9, 0xe5, 0x14, 0xe8, 0x40, 0x1b, +0x8b, 0x95, 0x1a, 0x22, 0x50, 0x18, 0xa1, 0xb1, +0xaf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0x24, 0x79, 0xef, 0xf3, 0xe0, +0x1d, 0xa4, 0x64, 0x34, 0x38, 0xc6, 0x5a, 0x8d, +0x22, 0xfb, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xdb, 0x46, 0x1c, 0xb2, +0x3c, 0x97, 0xbb, 0x5d, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0xec, 0xfd, 0xbb, +0x19, 0x62, 0xbb, 0x09, 0x2b, 0xad, 0x7e, 0x0f, +0x52, 0xb1, 0x6c, 0x80, 0x31, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0xae, +0x3a, 0x8e, 0xa5, 0xca, 0x1a, 0x2d, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0x45, +0x3b, 0x66, 0x66, 0x23, 0x9b, 0x44, 0xc5, 0x4a, +0x0b, 0x8b, 0xa1, 0x7f, 0xd8, 0xb9, 0x89, 0x19, +0x65, 0x5b, 0xdb, 0xad, 0x01, 0x4a, 0x0e, 0xe9, +0xaa, 0x99, 0x99, 0xad, 0xff, 0xb6, 0x80, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0xfd, 0x0c, 0x8a, 0x3d, 0xa7, 0xb8, 0x1a, +0x29, 0x3c, 0xbf, 0x32, 0xb6, 0x90, 0x1b, 0x76, +0xaf, 0x96, 0xd4, 0xd4, 0xc0, 0xc5, 0xf0, 0x2a, +0xb4, 0x71, 0x1d, 0xbb, 0x68, 0xe4, 0xc1, 0xe8, +0xfc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0x5e, 0xa5, 0xc2, 0xc8, 0x8c, +0x35, 0x80, 0xf4, 0xfc, 0x67, 0xf2, 0x43, 0xae, +0x05, 0x71, 0xe8, 0x44, 0xe8, 0xe4, 0xb3, 0x80, +0x17, 0x9d, 0x96, 0x23, 0x83, 0x71, 0x66, 0x7e, +0xdc, 0x83, 0x78, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x59, 0xaa, 0x40, +0x6b, 0xd1, 0x3a, 0x8e, 0xfb, 0xe6, 0x44, 0x09, +0x19, 0x39, 0xe6, 0x20, 0x35, 0x85, 0x96, 0x68, +0xed, 0xa8, 0x56, 0x52, 0xec, 0x02, 0x73, 0x3b, +0x75, 0x31, 0x18, 0x08, 0x3f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0xe2, +0x9e, 0x01, 0x5e, 0x18, 0x64, 0xe1, 0x84, 0x43, +0x31, 0xce, 0xfb, 0x84, 0x3f, 0xd1, 0x0c, 0x35, +0x15, 0x7e, 0x77, 0x78, 0x67, 0x46, 0xaf, 0x56, +0x1d, 0x12, 0x05, 0x1c, 0xda, 0x1d, 0xdd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x40, 0xc7, 0x9d, 0xaa, 0xd5, 0xc4, 0x56, +0x35, 0x61, 0xc6, 0xd3, 0x83, 0x87, 0xe0, 0xea, +0xb8, 0x5f, 0xc0, 0x0f, 0x57, 0x2f, 0x35, 0x32, +0xe2, 0xa5, 0xba, 0x6b, 0x95, 0x04, 0x72, 0x97, +0x7c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0x4d, 0x3a, 0x0c, 0x4a, 0x1a, +0x97, 0x70, 0x99, 0xf5, 0x2b, 0x90, 0xe4, 0x80, +0xca, 0x7f, 0x82, 0xc5, 0x69, 0xe5, 0x59, 0xa1, +0x9c, 0x61, 0xa5, 0xdc, 0xc5, 0xe6, 0x04, 0x11, +0xfb, 0x18, 0x67, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0x99, 0x8b, 0xd8, +0x6c, 0x0d, 0x53, 0xeb, 0x4e, 0x0e, 0x4a, 0x82, +0xd5, 0x71, 0x8c, 0x8b, 0x38, 0x76, 0x61, 0x51, +0xab, 0x61, 0xdf, 0x5f, 0x4b, 0xe8, 0x21, 0x9f, +0xeb, 0x9f, 0x04, 0x39, 0xd5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x7a, +0xd4, 0xe6, 0x72, 0xb9, 0xfa, 0x2b, 0xc8, 0x6c, +0x69, 0x94, 0xfe, 0x2c, 0x1c, 0x81, 0x1b, 0xc9, +0xf1, 0xbe, 0x87, 0x37, 0x9f, 0xce, 0xb0, 0x84, +0xdf, 0xfd, 0xfa, 0x26, 0xc6, 0x6c, 0x66, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0x75, 0x87, 0x2c, 0xe8, 0x2c, 0x86, 0x8d, +0xe9, 0x9f, 0xcc, 0x90, 0x9f, 0x01, 0x8e, 0x5f, +0xa9, 0x16, 0x30, 0x30, 0x1b, 0xfd, 0x76, 0x72, +0x86, 0xf8, 0x29, 0xeb, 0x82, 0x8f, 0x5c, 0xe4, +0xd2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0x4e, 0xbf, 0xea, 0xd9, 0x72, +0xd0, 0xf1, 0xa9, 0x4e, 0x6f, 0x27, 0xfe, 0x11, +0x52, 0x4e, 0xda, 0x20, 0x31, 0x6d, 0x1f, 0xc8, +0xd1, 0xa3, 0xd5, 0x7d, 0xb3, 0xab, 0xf7, 0x96, +0x47, 0xa2, 0x98, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0x6c, 0x02, 0x05, +0x9b, 0x67, 0x9c, 0x6e, 0x85, 0x23, 0xe4, 0xe2, +0x37, 0x16, 0x47, 0x00, 0x68, 0xd0, 0x02, 0xdd, +0xe4, 0x73, 0x66, 0xaa, 0x1c, 0x31, 0x63, 0xee, +0x24, 0x29, 0x1a, 0x14, 0xa4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x12, +0xd0, 0x4f, 0x41, 0xe3, 0x4c, 0xdb, 0x4b, 0x1b, +0xad, 0x12, 0xb1, 0x6d, 0xaf, 0xe3, 0x83, 0x06, +0x64, 0x7d, 0x3f, 0xcd, 0x60, 0xd5, 0xaa, 0xf7, +0x95, 0xba, 0xc0, 0xa3, 0xb8, 0xde, 0x45, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0xe1, 0x7e, 0x93, 0x9c, 0xd3, 0x28, 0xac, +0x8b, 0x40, 0x91, 0x1b, 0x32, 0x7f, 0xbd, 0xae, +0x60, 0xb9, 0xf4, 0x56, 0x22, 0x46, 0x1b, 0xb9, +0x92, 0x0c, 0x80, 0xc1, 0x6a, 0xef, 0x3b, 0xfb, +0xd4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0xac, 0x69, 0xdf, 0xad, 0xa0, +0x86, 0x0f, 0xf3, 0x69, 0xdd, 0x2a, 0x85, 0x7d, +0x07, 0x69, 0x28, 0x06, 0x89, 0xd9, 0x6d, 0xf9, +0x96, 0x96, 0x09, 0xb4, 0xaa, 0x75, 0xd2, 0xe2, +0xbf, 0x00, 0x85, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0x43, 0x39, 0x70, +0x32, 0xdd, 0x6c, 0xdd, 0x97, 0x4d, 0xac, 0x5c, +0xb3, 0x3c, 0x50, 0x59, 0x30, 0xe3, 0x72, 0x90, +0x18, 0x4e, 0x65, 0x59, 0xf7, 0xae, 0x18, 0xf4, +0xca, 0x50, 0xe3, 0x8f, 0xdf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x97, +0xb9, 0x5d, 0xd8, 0x9a, 0x08, 0x74, 0x5d, 0x4e, +0xe8, 0x90, 0x30, 0x5e, 0x3d, 0x6e, 0x98, 0x83, +0x26, 0x54, 0xb1, 0x5a, 0xe5, 0x04, 0xbf, 0x8c, +0xa3, 0x32, 0x8d, 0x6a, 0x3e, 0x6b, 0xd3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0x40, 0x83, 0x0c, 0xc3, 0xc9, 0x9a, 0xc1, +0x28, 0x1f, 0xd8, 0x99, 0xb0, 0x83, 0x18, 0xb9, +0x66, 0xe3, 0xb7, 0xee, 0x99, 0x47, 0x92, 0x3f, +0x39, 0x33, 0x88, 0xdd, 0x9e, 0x65, 0x39, 0xc6, +0x3c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0xcf, 0x52, 0xcc, 0x64, 0x65, +0xb7, 0x8c, 0xd6, 0x51, 0xfd, 0x89, 0x1f, 0xe8, +0x28, 0x8a, 0xe9, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xed, 0x97, 0x75, 0xa4, +0x28, 0xc3, 0x00, 0xf1, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0xaf, 0x2d, 0xf2, +0x75, 0x1b, 0xf7, 0x56, 0x8f, 0x61, 0xc7, 0x18, +0xa5, 0xa3, 0x77, 0x3a, 0xdc, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9c, 0x9d, +0x17, 0xbe, 0xd3, 0xda, 0x1d, 0xe8, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0xad, +0xd4, 0x49, 0x27, 0x26, 0xc4, 0x2d, 0x6a, 0xc2, +0x7b, 0xbc, 0x3d, 0x0a, 0x21, 0x63, 0xc5, 0x65, +0xe2, 0x81, 0xaa, 0xdf, 0x66, 0x38, 0x3a, 0xc2, +0x49, 0xc3, 0xa3, 0x76, 0xa0, 0x8d, 0x45, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0x1c, 0xa0, 0xaf, 0xdb, 0xa3, 0xd1, 0xd3, +0x7c, 0x33, 0xbf, 0xfb, 0x96, 0xb9, 0x29, 0x75, +0x57, 0xdd, 0x2d, 0xd3, 0xef, 0xb3, 0x3c, 0x27, +0x02, 0xf9, 0x01, 0xd7, 0xd2, 0xca, 0xd2, 0xc2, +0x81, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0xe9, 0x77, 0x7f, 0x71, 0xf6, +0xd2, 0xc4, 0x58, 0x00, 0x2d, 0x62, 0x54, 0xe8, +0xb6, 0xce, 0xdc, 0x8b, 0xb8, 0x55, 0x9d, 0x16, +0x03, 0x33, 0x77, 0xd0, 0x8e, 0xf2, 0xbc, 0x07, +0xc7, 0x9c, 0x0c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0x18, 0x12, 0x66, +0x7e, 0x98, 0x1e, 0x45, 0x5d, 0xbf, 0x99, 0x54, +0x93, 0x98, 0x63, 0x86, 0xd2, 0x85, 0xc9, 0x88, +0xab, 0x95, 0x4e, 0x81, 0x09, 0x21, 0x2b, 0x7f, +0xb8, 0x0a, 0x0a, 0xdc, 0x54, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0x65, +0x9b, 0xa6, 0x29, 0x8f, 0x5a, 0xb1, 0xd0, 0xc5, +0xb4, 0xd9, 0x28, 0x26, 0x77, 0xfa, 0xa6, 0x26, +0x87, 0x5a, 0x9e, 0x49, 0xb2, 0x0e, 0xe5, 0x10, +0x86, 0xc2, 0x3b, 0x66, 0x37, 0x22, 0x99, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x6c, 0x90, 0x28, 0xbd, 0x28, 0x7b, 0x30, +0x5d, 0x96, 0x2a, 0xe0, 0x52, 0x47, 0x3c, 0xe1, +0x42, 0xb7, 0x64, 0x95, 0x2e, 0xdc, 0xe3, 0xfc, +0x67, 0xba, 0x07, 0x79, 0x12, 0x29, 0x4d, 0xb2, +0xd1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0xa3, 0x2d, 0xd6, 0x47, 0x3e, +0x44, 0x52, 0x8e, 0x7f, 0x1d, 0x58, 0x2e, 0xa8, +0xf4, 0x90, 0x8e, 0x5d, 0x17, 0x75, 0x03, 0x0e, +0xa8, 0xbb, 0x50, 0x4b, 0x44, 0x59, 0xef, 0xe4, +0x45, 0x73, 0x5f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0x8b, 0xc9, 0xa3, +0xf4, 0xce, 0x98, 0x57, 0x7c, 0x2c, 0x6c, 0x7d, +0x50, 0x9c, 0x7c, 0x62, 0x0a, 0x41, 0xca, 0xd7, +0x48, 0xfd, 0x46, 0xf9, 0x8e, 0x1c, 0x42, 0xa9, +0x5f, 0x6e, 0x24, 0xe7, 0x45, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0x02, +0x85, 0x74, 0x90, 0xff, 0x4c, 0x3d, 0xcb, 0x5e, +0xb1, 0x06, 0xb9, 0x47, 0xb7, 0x95, 0x47, 0x42, +0xbf, 0xed, 0x3b, 0xe2, 0x3d, 0x09, 0xa8, 0xc9, +0xab, 0xd6, 0x3a, 0xfe, 0x21, 0xb8, 0x88, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x64, 0x42, 0x74, 0xb3, 0xc9, 0x06, 0x1c, +0x1c, 0x41, 0x4e, 0x5c, 0x0c, 0x38, 0x09, 0x63, +0x7a, 0x7e, 0x21, 0x60, 0x87, 0xa5, 0x7d, 0x1c, +0x3d, 0xf3, 0x70, 0x00, 0xa1, 0x0e, 0x0b, 0x5f, +0x4f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0x3b, 0x0c, 0xdc, 0x15, 0xb1, +0x11, 0x57, 0xad, 0xed, 0xbe, 0x54, 0x26, 0x9c, +0xf6, 0xc5, 0x81, 0xf7, 0x12, 0xe8, 0xe0, 0x33, +0x3b, 0x51, 0x00, 0xf1, 0xe6, 0xd5, 0x9b, 0xe7, +0xa3, 0x09, 0x98, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0xfc, 0x78, 0x92, +0x38, 0xd4, 0x82, 0x8c, 0x5c, 0x8b, 0xb4, 0x9e, +0x84, 0x70, 0xb0, 0xa0, 0x75, 0x90, 0x7f, 0xd3, +0x80, 0x50, 0xb1, 0x48, 0x06, 0x0d, 0x76, 0x4c, +0x8e, 0xa1, 0x7e, 0xb3, 0xd6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x85, +0xc2, 0x4c, 0x94, 0xc9, 0xa2, 0x01, 0xa3, 0xdf, +0x7d, 0xba, 0x09, 0x2d, 0x23, 0xde, 0x04, 0xfc, +0x16, 0xd6, 0xb4, 0xe1, 0xcc, 0x0f, 0x91, 0x68, +0x3c, 0xac, 0x94, 0xdf, 0xf2, 0x7c, 0x16, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0xdb, 0x43, 0xc4, 0xcd, 0xe0, 0x98, 0x64, +0x11, 0x06, 0x80, 0xce, 0x42, 0xfe, 0xef, 0x46, +0x66, 0x25, 0x65, 0xe0, 0x69, 0x53, 0x3a, 0x04, +0x91, 0x58, 0x49, 0x5c, 0x60, 0x8c, 0xc2, 0x3c, +0x2e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0x2e, 0x07, 0xe0, 0x5f, 0xa8, +0x44, 0x24, 0x8a, 0xc8, 0x9d, 0x09, 0x91, 0xd2, +0xbc, 0xba, 0x8c, 0xda, 0xea, 0xb0, 0x79, 0xd0, +0xc3, 0xfe, 0x1a, 0xfb, 0x95, 0x95, 0xd7, 0x8d, +0x0d, 0x46, 0x8b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0x5f, 0xb2, 0x62, +0xb1, 0xe4, 0xaa, 0xbc, 0x04, 0xb9, 0xf2, 0xb9, +0x15, 0xd6, 0x31, 0xd7, 0x1d, 0x3b, 0xc8, 0xdf, +0x80, 0xdc, 0x3c, 0xf7, 0xb4, 0xa8, 0xa3, 0xfe, +0x8d, 0x73, 0x56, 0x41, 0x52, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0x11, +0x26, 0x21, 0xad, 0xc3, 0xf7, 0x27, 0x23, 0xa8, +0x77, 0xb1, 0x45, 0xee, 0x41, 0x41, 0x21, 0x93, +0xec, 0x60, 0x71, 0x2d, 0x9f, 0xb5, 0x5e, 0x9a, +0xf6, 0xec, 0xb8, 0xfa, 0x6e, 0xd0, 0x8a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0xe2, 0xb2, 0x61, 0xe6, 0x05, 0xf8, 0x8c, +0x63, 0x6a, 0x8b, 0xe1, 0x45, 0x38, 0x98, 0xdf, +0xbb, 0xc7, 0x45, 0x11, 0x17, 0xcb, 0xf3, 0x7c, +0x3e, 0x8d, 0xa6, 0x7b, 0x3f, 0xea, 0xd8, 0xe6, +0x9d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0x55, 0x26, 0x85, 0x80, 0x18, +0xf4, 0x0d, 0x0a, 0x17, 0x74, 0x87, 0xeb, 0x94, +0x61, 0xa6, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x2b, 0x9a, 0x06, 0x7c, +0x55, 0xc7, 0x93, 0xeb, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0x5a, 0xd7, 0x69, +0x47, 0xd5, 0xb0, 0x4e, 0xb6, 0x69, 0x73, 0x7e, +0x6b, 0x3f, 0xa8, 0x9e, 0xad, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x72, +0xcf, 0x6a, 0xba, 0x54, 0x76, 0xce, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0xbb, +0xef, 0x16, 0x2b, 0x70, 0x4a, 0xad, 0xcd, 0x1b, +0x23, 0x1d, 0x38, 0x95, 0x63, 0x07, 0x68, 0xde, +0xde, 0xe1, 0xaa, 0x3a, 0xed, 0x6c, 0x45, 0xce, +0x07, 0x9b, 0xe3, 0x02, 0xde, 0x05, 0x2f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0xbe, 0x7b, 0x6e, 0xd7, 0xbe, 0xf3, 0x30, +0x8a, 0x55, 0xb5, 0xc0, 0xf8, 0xd0, 0x5c, 0x06, +0x97, 0x13, 0xc0, 0xc9, 0xed, 0x86, 0x40, 0xc0, +0x88, 0x4e, 0x79, 0xf9, 0x9b, 0xb3, 0xff, 0xb7, +0x02, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0x7a, 0x65, 0x68, 0x92, 0x4e, +0x25, 0xd1, 0x16, 0xd4, 0xe8, 0xa8, 0xa3, 0xaf, +0x8f, 0x83, 0x01, 0x23, 0x21, 0x4b, 0xdc, 0xfd, +0xff, 0x67, 0x6c, 0x76, 0xd7, 0x5e, 0x78, 0xb1, +0x1c, 0xdc, 0x5f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0x55, 0x41, 0xaa, +0xaa, 0x10, 0xa6, 0x69, 0x60, 0x2d, 0x39, 0x5a, +0x8b, 0xc7, 0xea, 0xd3, 0x26, 0x3d, 0x15, 0xca, +0xae, 0xb6, 0x54, 0x02, 0x19, 0xdb, 0xf8, 0x61, +0xd3, 0xba, 0x1f, 0x26, 0xea, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0xec, +0x15, 0xe4, 0x2d, 0x9a, 0x06, 0xe5, 0x33, 0x43, +0x40, 0x70, 0x8a, 0x6f, 0xbf, 0xa6, 0x76, 0x6a, +0x21, 0x80, 0xde, 0xf2, 0xef, 0x2d, 0x45, 0xdf, +0xb6, 0x74, 0xd2, 0xe4, 0x7a, 0x29, 0xd5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0x6d, 0x86, 0xbf, 0xee, 0x49, 0x42, 0xcc, +0x7f, 0x6c, 0xb2, 0x4f, 0xb5, 0x21, 0xc6, 0x8c, +0x84, 0x7b, 0xb4, 0xec, 0x69, 0x2d, 0xb2, 0x79, +0x72, 0x54, 0x0a, 0xa9, 0x3f, 0x50, 0xf3, 0x5e, +0xb1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0xe0, 0x42, 0xd3, 0x9d, 0xff, +0x8d, 0xc7, 0x72, 0x50, 0xc9, 0xf3, 0x73, 0x78, +0x22, 0xa3, 0xb3, 0xa4, 0x5a, 0xd8, 0xaf, 0x3a, +0x5b, 0x37, 0xec, 0xe7, 0x97, 0xa4, 0xd6, 0x56, +0xf9, 0x2e, 0xb7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0x6f, 0x0f, 0x72, +0xbb, 0xf8, 0x76, 0xc4, 0x9e, 0xce, 0xa2, 0x6e, +0x34, 0x7a, 0x4a, 0x43, 0x8e, 0x8b, 0xc7, 0x49, +0x3c, 0x96, 0x86, 0x8d, 0x15, 0x36, 0x9d, 0x5b, +0xe5, 0x73, 0xe7, 0xd1, 0xba, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0x4e, +0x65, 0xdd, 0x3b, 0x6d, 0x78, 0x87, 0x77, 0x01, +0x34, 0xbe, 0xe3, 0xd3, 0x44, 0xf4, 0xfb, 0x63, +0x9b, 0x4a, 0x5d, 0x2b, 0x9e, 0x4c, 0xd8, 0x39, +0x2a, 0x72, 0x23, 0xbc, 0xd4, 0x96, 0xc9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0x82, 0xf1, 0xdc, 0x39, 0x68, 0x15, 0xd0, +0xdf, 0xd9, 0x86, 0x08, 0xb7, 0x78, 0xef, 0x33, +0x86, 0x8d, 0xba, 0xa5, 0x81, 0xd7, 0xbd, 0x27, +0x9c, 0x63, 0xa8, 0xda, 0x4d, 0x7a, 0xe2, 0x94, +0xdd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x89, 0x28, 0x94, 0x60, 0x9f, +0x49, 0xed, 0x9d, 0xe6, 0x6b, 0x29, 0xf6, 0xd7, +0xb4, 0xf5, 0x3c, 0x58, 0x03, 0x7d, 0x77, 0xa6, +0x6b, 0xdd, 0x97, 0x69, 0x35, 0x32, 0x95, 0x59, +0xaf, 0x4d, 0xc3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0x48, 0xc0, 0x4c, +0x9b, 0xc0, 0x1f, 0x68, 0x89, 0xd5, 0x8a, 0xab, +0x86, 0x8b, 0xb7, 0xb3, 0xb2, 0x3a, 0x15, 0x93, +0x8e, 0xe5, 0xc5, 0x68, 0x27, 0xbf, 0xd9, 0x04, +0x34, 0x21, 0x1d, 0x87, 0x99, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0x17, +0xb6, 0x71, 0x7a, 0xc0, 0x9f, 0xa5, 0x31, 0xe0, +0x8f, 0x30, 0xea, 0x6a, 0x4f, 0x8a, 0xf0, 0x23, +0xbd, 0x57, 0xcd, 0xd1, 0x8e, 0xbb, 0xd4, 0x33, +0x1c, 0xcc, 0x6a, 0x40, 0x03, 0x39, 0x5b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0xc7, 0x30, 0x7f, 0x2d, 0x26, 0xb7, 0x5a, +0x8d, 0xad, 0xfb, 0x0e, 0xdd, 0x6e, 0x8c, 0xe9, +0xef, 0x65, 0x0c, 0xb5, 0xcc, 0xbf, 0xe6, 0x26, +0xf1, 0x83, 0x12, 0x1d, 0x48, 0x39, 0x5d, 0x46, +0x8b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0xd4, 0xe3, 0xe5, 0x47, 0x73, +0x36, 0xa0, 0xcf, 0xda, 0x82, 0xc4, 0x1c, 0xdb, +0xdc, 0xb6, 0x63, 0x88, 0xe4, 0x54, 0x66, 0x38, +0x5b, 0x8f, 0xf8, 0xff, 0x9e, 0xe5, 0xf1, 0x57, +0x7d, 0xd3, 0x7a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0x7a, 0x9b, 0xd8, +0xad, 0x96, 0xb8, 0xf0, 0xd0, 0x25, 0xa7, 0x47, +0x94, 0x68, 0x29, 0x50, 0x9c, 0x92, 0xec, 0xbf, +0x66, 0x0c, 0xb8, 0x11, 0x42, 0x8c, 0xb3, 0xf3, +0x36, 0x61, 0xe9, 0x38, 0x9a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0xc4, +0x0f, 0x3b, 0xea, 0xbd, 0x3c, 0xff, 0x9b, 0x9b, +0xda, 0xed, 0x98, 0xd5, 0xae, 0xf8, 0xd7, 0xca, +0xf3, 0x37, 0xd8, 0x59, 0x2e, 0x42, 0x96, 0x7e, +0xb7, 0x32, 0xf8, 0x59, 0x0c, 0x11, 0x2f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0x35, 0xa0, 0x7f, 0x00, 0x07, 0x37, 0xb0, +0x1b, 0x7e, 0x60, 0x37, 0xa1, 0x1d, 0xe4, 0x0b, +0xbf, 0x39, 0x90, 0x65, 0x2c, 0x4e, 0xd0, 0x0f, +0xb9, 0xb3, 0x62, 0x86, 0xdb, 0x12, 0x03, 0xc0, +0x6d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0x9a, 0xc4, 0xd4, 0x57, 0x45, +0x1d, 0xb5, 0x94, 0xfc, 0x7b, 0x40, 0xfa, 0x7a, +0x70, 0x86, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x87, 0x71, 0x5c, 0x4d, +0x5c, 0x82, 0xe0, 0x75, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x85, 0x54, 0xc3, +0x52, 0xf3, 0xbc, 0x90, 0x23, 0x83, 0x65, 0x55, +0xd0, 0xc6, 0x93, 0xe3, 0xc0, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xc6, +0x3f, 0xa2, 0x4d, 0x3b, 0x10, 0x2f, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0xc9, +0x45, 0x34, 0xa2, 0xf4, 0x5e, 0xfb, 0x09, 0xba, +0x82, 0x58, 0xc1, 0x9b, 0x18, 0xd9, 0x51, 0xd5, +0x2e, 0xa8, 0x70, 0xa7, 0x9f, 0xc2, 0x77, 0x1b, +0xc0, 0x61, 0x8d, 0x34, 0x3e, 0x58, 0x5a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0xb7, 0x2f, 0xaa, 0xc7, 0x71, 0xfe, 0x6e, +0x8e, 0xbc, 0x37, 0x37, 0x5e, 0x0d, 0x8d, 0xa2, +0xbf, 0x91, 0xfc, 0x9f, 0x20, 0x3a, 0x0d, 0xe3, +0x79, 0x3e, 0x50, 0x21, 0xe2, 0xe6, 0xf7, 0xca, +0xf9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0xd9, 0xd3, 0xc6, 0x20, 0x83, +0xc0, 0x93, 0x25, 0x1f, 0xab, 0x13, 0x26, 0x38, +0xea, 0x3a, 0x9d, 0x56, 0x01, 0xfc, 0xe3, 0x0a, +0xec, 0x22, 0x66, 0xd5, 0xde, 0xeb, 0x5d, 0x95, +0x09, 0x77, 0x38, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0xaf, 0xa5, 0xf5, +0x25, 0xba, 0x1d, 0x9e, 0x02, 0xc2, 0x35, 0xd1, +0x5d, 0x8a, 0x63, 0xed, 0xa6, 0xbd, 0x1d, 0xb7, +0x0e, 0xa8, 0x96, 0xd2, 0x50, 0xea, 0xa1, 0x59, +0x22, 0x3d, 0x88, 0xdc, 0x9b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0xb0, +0x69, 0x31, 0x7e, 0xa8, 0x9e, 0x42, 0xca, 0xe9, +0x2d, 0x96, 0x1b, 0x22, 0xac, 0x42, 0x35, 0x9e, +0x47, 0x70, 0xd7, 0x8a, 0xbd, 0xda, 0xb4, 0xbe, +0x41, 0x73, 0x1f, 0x6f, 0x46, 0x07, 0x24, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0x44, 0x6e, 0x68, 0x03, 0x2c, 0x30, 0x6b, +0xed, 0x18, 0x7c, 0x4c, 0x30, 0xa7, 0x47, 0x1d, +0x12, 0x09, 0xb3, 0xe6, 0x2a, 0xdb, 0x83, 0x05, +0x57, 0xde, 0x6a, 0x43, 0x71, 0x1e, 0xc2, 0xfa, +0xca, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0x2f, 0x5b, 0xa4, 0x17, 0xa3, +0xd0, 0xa5, 0xb5, 0x25, 0x1d, 0x0e, 0x4e, 0xf1, +0x06, 0x45, 0xd7, 0x30, 0x7f, 0x25, 0xcd, 0x61, +0x8e, 0x09, 0x82, 0xbe, 0x8f, 0x72, 0xdd, 0x57, +0xf6, 0xe6, 0x90, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0x2f, 0x83, 0xd9, +0x12, 0x7d, 0xf0, 0xfd, 0x55, 0xda, 0x25, 0x1e, +0xf0, 0xd9, 0x50, 0xe6, 0x0e, 0x61, 0x49, 0x62, +0xd7, 0xb4, 0x0a, 0xa3, 0x47, 0xb1, 0x8e, 0xba, +0x2f, 0xd2, 0xf1, 0x23, 0xcb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0x36, +0x9a, 0x32, 0xbb, 0x7a, 0x41, 0x3f, 0x36, 0x27, +0x3a, 0x63, 0xe9, 0xa9, 0xfd, 0xd4, 0xc1, 0xba, +0x4b, 0xf6, 0x3d, 0xc8, 0xd4, 0x28, 0xd0, 0xb1, +0xbf, 0x9b, 0x75, 0x42, 0xb0, 0xfc, 0xe3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0xd3, 0x63, 0x25, 0xbd, 0x63, 0x46, 0x1c, +0x31, 0xeb, 0x53, 0xe6, 0x9a, 0x80, 0x2e, 0x68, +0x59, 0x66, 0xf8, 0xb7, 0xec, 0x08, 0x68, 0x41, +0xa1, 0xd6, 0xfb, 0x31, 0xa6, 0x76, 0x4a, 0x34, +0x60, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0x66, 0xd6, 0xa5, 0xdf, 0x47, +0x8c, 0x67, 0x61, 0x03, 0x38, 0x0c, 0x5c, 0xb6, +0x0a, 0x59, 0xe4, 0x99, 0xb1, 0x4e, 0x2a, 0x9d, +0xe6, 0x50, 0xdd, 0xb9, 0x7f, 0xb7, 0xf1, 0xc4, +0x11, 0x65, 0x6a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x28, 0x82, 0xd4, +0x49, 0xb7, 0xaa, 0x7b, 0xbe, 0xf8, 0x27, 0xce, +0x9a, 0x1e, 0x78, 0xa1, 0xc6, 0x07, 0x61, 0xd4, +0xef, 0x0a, 0x16, 0x31, 0xf8, 0xc3, 0xf7, 0x0f, +0x9a, 0x45, 0xfd, 0x42, 0x5a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0x19, +0x62, 0x86, 0xc0, 0x21, 0x8c, 0x97, 0xe0, 0x76, +0x2d, 0xb6, 0xb2, 0x23, 0x95, 0x9b, 0x3e, 0x81, +0xde, 0xe6, 0x2b, 0x58, 0xc9, 0x9d, 0x8a, 0x9d, +0xd8, 0x9b, 0x8a, 0xce, 0x47, 0x43, 0x92, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0xd0, 0xb7, 0x48, 0x72, 0xec, 0xfe, 0x7e, +0x99, 0xa2, 0xda, 0x7a, 0x0c, 0xa9, 0xdb, 0xfb, +0xf5, 0xf8, 0x8a, 0x05, 0xbc, 0x85, 0x7d, 0x21, +0xf7, 0x51, 0x6e, 0x7a, 0x3d, 0x18, 0x97, 0x62, +0x4b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0x95, 0x7c, 0xb4, 0x35, 0x57, +0x32, 0xcd, 0xe6, 0x19, 0x02, 0xea, 0xfc, 0x10, +0x39, 0xfb, 0x08, 0x69, 0xa3, 0x03, 0x89, 0x67, +0xff, 0x3f, 0xf0, 0x07, 0x8a, 0xb7, 0x7d, 0x15, +0xdc, 0xd4, 0x4d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x6f, 0x3b, 0xd1, +0x88, 0x0f, 0xe0, 0x37, 0x51, 0x91, 0xb4, 0xcf, +0x9c, 0xab, 0xd2, 0x68, 0xb7, 0x93, 0xa2, 0xca, +0xaf, 0x29, 0x55, 0x7f, 0x48, 0x0e, 0x9a, 0x43, +0xad, 0x5c, 0x32, 0x31, 0xcf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x54, +0x49, 0x18, 0xee, 0xa0, 0xfa, 0xac, 0x4c, 0x8f, +0x02, 0x15, 0xe8, 0xfb, 0x3d, 0x01, 0x99, 0x4a, +0xec, 0x20, 0xfd, 0xf3, 0x49, 0xbf, 0xc0, 0xd6, +0xd0, 0x11, 0xf4, 0x29, 0xb6, 0x1e, 0x70, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0xde, 0x13, 0x44, 0x8d, 0xb3, 0x0c, 0xfe, +0xd2, 0x34, 0x67, 0x6c, 0x38, 0x34, 0xaa, 0xc4, +0x98, 0x04, 0x94, 0x0b, 0x00, 0xb9, 0x3a, 0xe0, +0xcc, 0x1d, 0xc4, 0x48, 0xcc, 0x16, 0xcf, 0xf2, +0x90, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0xcd, 0xb3, 0x70, 0x5d, 0x9e, +0x0d, 0x93, 0x55, 0xb1, 0x0c, 0xa0, 0x2e, 0x60, +0xe5, 0xd2, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x42, 0x25, 0x8d, 0x59, +0x79, 0x59, 0xa3, 0xcf, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0x7c, 0x67, 0x8f, +0x30, 0x24, 0xd6, 0xd4, 0x1a, 0x56, 0xa8, 0x41, +0xbe, 0x01, 0x4d, 0xb4, 0x26, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x21, +0xf5, 0xfb, 0xbc, 0x33, 0xb1, 0x2e, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x8c, +0x60, 0x45, 0x35, 0xf9, 0x77, 0x21, 0xd0, 0xdd, +0x47, 0xf2, 0xda, 0x49, 0xb5, 0x72, 0xc5, 0x36, +0xa2, 0xa6, 0x12, 0xd3, 0xae, 0xd4, 0x36, 0x86, +0xc8, 0x80, 0xa3, 0x75, 0x80, 0xf8, 0xe9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0x50, 0xac, 0x31, 0x63, 0x78, 0xb5, 0xb4, +0x70, 0x09, 0xfb, 0x02, 0xb9, 0xd8, 0x08, 0x82, +0x07, 0x44, 0xe4, 0xfb, 0x65, 0x89, 0x18, 0x3f, +0x77, 0x70, 0x01, 0xc9, 0x0a, 0xc2, 0xb1, 0x45, +0xe4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0x09, 0xc4, 0x73, 0x20, 0x9e, +0x95, 0xe9, 0xfd, 0xd4, 0xe1, 0x01, 0xb5, 0xe9, +0xca, 0x0d, 0x4c, 0xd9, 0x83, 0x68, 0xc0, 0xe8, +0x75, 0xfd, 0xd8, 0x63, 0xd9, 0xfe, 0xad, 0x83, +0x22, 0x73, 0xd9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0x14, 0x37, 0x14, +0xed, 0x62, 0x54, 0xb9, 0x80, 0x1d, 0x76, 0x83, +0x0f, 0xc7, 0x11, 0x62, 0xac, 0x70, 0x06, 0xee, +0xbd, 0xf8, 0x8b, 0xe4, 0x42, 0x20, 0x3a, 0xbb, +0xe0, 0x78, 0x7c, 0x3f, 0xbe, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0xb6, +0x2b, 0xde, 0x8d, 0x87, 0x58, 0x60, 0x0a, 0x51, +0x85, 0x75, 0xb0, 0xeb, 0x10, 0x18, 0x0b, 0x42, +0x4d, 0x85, 0xed, 0x67, 0x47, 0x1f, 0x0b, 0x43, +0xf3, 0xb3, 0x11, 0xec, 0x31, 0xbf, 0xc8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0x75, 0x1c, 0xb2, 0xcf, 0x0d, 0x13, 0x3a, +0x2b, 0x80, 0xc4, 0x1c, 0x27, 0x74, 0xcf, 0xd7, +0x33, 0x61, 0xba, 0xf5, 0xf2, 0x3e, 0xa4, 0x18, +0x85, 0xfe, 0xbd, 0x6a, 0x71, 0x08, 0x93, 0xe5, +0x70, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0x27, 0x6e, 0x0a, 0x36, 0x08, +0xfa, 0xbe, 0xa6, 0xbb, 0xa0, 0x60, 0x93, 0xb7, +0x63, 0xe4, 0xfc, 0xb6, 0x27, 0x3a, 0x8b, 0x57, +0x36, 0x5a, 0x71, 0x8f, 0xcd, 0x8f, 0x43, 0x67, +0xf9, 0xbb, 0x04, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0x2f, 0x28, 0xc5, +0x2a, 0x86, 0xde, 0x20, 0xb0, 0x32, 0x5a, 0x34, +0x76, 0x5e, 0xcf, 0x29, 0x76, 0x50, 0x9b, 0x01, +0x6f, 0xc3, 0xa9, 0xff, 0x23, 0xb7, 0x52, 0xf6, +0x6b, 0xd7, 0x88, 0x6d, 0x15, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0x31, +0xd9, 0x79, 0xd8, 0xe6, 0x40, 0x1e, 0x2e, 0x2a, +0xab, 0x34, 0xfb, 0x8d, 0x99, 0x3f, 0x73, 0xbf, +0x5a, 0xa4, 0xd5, 0xcc, 0x5e, 0x1a, 0x1a, 0x79, +0x28, 0x73, 0x75, 0x9f, 0x59, 0xa0, 0x71, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0xdc, 0x93, 0x4b, 0x19, 0x0a, 0x0d, 0x34, +0x6e, 0xcd, 0xa0, 0xb2, 0xcf, 0xdd, 0xd6, 0x13, +0x27, 0x07, 0x73, 0xaa, 0x59, 0x74, 0xbb, 0x1c, +0x88, 0x4a, 0x50, 0x56, 0x01, 0x56, 0x6e, 0x09, +0x3e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x61, 0xfe, 0xbf, 0xd2, 0x7a, +0x5c, 0xf6, 0xaf, 0x2d, 0x2a, 0xcd, 0xfa, 0xa9, +0x75, 0x93, 0xcc, 0x35, 0x51, 0x49, 0xd3, 0x34, +0x4e, 0xda, 0xac, 0x3d, 0xff, 0x8e, 0x87, 0x64, +0x15, 0x93, 0xac, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x7d, 0xa1, 0x42, +0xf4, 0x51, 0x65, 0x9a, 0xea, 0x39, 0x14, 0x84, +0x2f, 0x50, 0x3d, 0xeb, 0xf1, 0xf7, 0xa8, 0x1b, +0xc2, 0xce, 0x37, 0xfd, 0x47, 0x8c, 0xdd, 0x65, +0xa5, 0xbb, 0xbf, 0xfc, 0xac, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0x30, +0x6a, 0x81, 0x2f, 0x81, 0x68, 0x9c, 0xfb, 0xa2, +0xa6, 0xc5, 0x05, 0x9d, 0xeb, 0xe2, 0x4d, 0xeb, +0xa2, 0xa1, 0xe8, 0xa2, 0xc5, 0xeb, 0x6b, 0x91, +0x88, 0xcb, 0x3e, 0x4a, 0xce, 0x6c, 0x67, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0x02, 0xc6, 0xa8, 0xcf, 0x36, 0x07, 0x86, +0x08, 0x3f, 0x7e, 0x4a, 0xb2, 0xc5, 0x3c, 0x87, +0xc0, 0x0f, 0xd0, 0xb3, 0xd8, 0x03, 0x30, 0xbe, +0xb0, 0x41, 0x56, 0x1b, 0x07, 0x1a, 0xd8, 0xe1, +0xb8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0x03, 0xf7, 0xe1, 0x21, 0x37, +0x13, 0x4a, 0x3c, 0x86, 0xb9, 0xb5, 0x3d, 0x55, +0xdd, 0x9b, 0x2e, 0x51, 0x2d, 0xca, 0xf2, 0x2a, +0xf9, 0x0a, 0x81, 0x7d, 0xe9, 0x3c, 0x90, 0xfb, +0x52, 0xa7, 0x62, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0xcd, 0x3d, 0xbc, +0x66, 0x4a, 0xf1, 0x29, 0x13, 0xa2, 0xb9, 0x5c, +0x8a, 0x37, 0x79, 0xf3, 0x74, 0xf4, 0x0e, 0x40, +0x25, 0xb5, 0x5b, 0x01, 0x0e, 0x73, 0x5f, 0x98, +0x9e, 0x35, 0xed, 0x5d, 0x48, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0x0a, +0xd3, 0x9e, 0x17, 0xc8, 0x51, 0x15, 0xdd, 0xbb, +0x14, 0x53, 0x91, 0x4d, 0x39, 0xc9, 0x52, 0x18, +0x07, 0xcb, 0x03, 0x5e, 0x87, 0xd9, 0x24, 0x5d, +0xd7, 0x5c, 0xd4, 0x62, 0x75, 0x41, 0xbf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0xc5, 0x65, 0xe6, 0xb1, 0x51, 0x1d, 0xd8, +0x35, 0xf2, 0xd4, 0x4f, 0x77, 0x9e, 0x80, 0x85, +0xdd, 0x80, 0xc6, 0xf8, 0xf9, 0xa7, 0xa7, 0x0a, +0x27, 0x32, 0x01, 0x11, 0x44, 0x07, 0x02, 0x27, +0x46, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0xb4, 0x5b, 0xc0, 0x2e, 0x89, +0xf2, 0x38, 0x9d, 0x06, 0x89, 0x83, 0x12, 0xc9, +0xf3, 0xfb, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x7c, 0x71, 0xc1, 0xfa, +0xb7, 0xa0, 0xf7, 0x58, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0xbf, 0x82, 0xe9, +0x90, 0xf6, 0x9e, 0xb0, 0x5e, 0x81, 0x02, 0x5c, +0x0a, 0xf3, 0xaf, 0x24, 0xb1, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0xb8, +0x93, 0x8a, 0xca, 0x4e, 0xd1, 0x8e, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x9e, +0x2b, 0x34, 0xc0, 0x93, 0xdb, 0xb7, 0xc6, 0x11, +0x2a, 0xa9, 0x84, 0x34, 0x83, 0x71, 0xe9, 0x11, +0x3b, 0xf6, 0xc6, 0xaa, 0x31, 0xac, 0xc4, 0xe6, +0xf1, 0xfc, 0xb0, 0x0b, 0x3e, 0x6d, 0xaf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0x1a, 0xb8, 0xeb, 0x89, 0x16, 0x91, 0xbd, +0xfa, 0x24, 0xc1, 0x5d, 0x66, 0xd2, 0x50, 0x64, +0xb9, 0x2a, 0x51, 0x74, 0xb2, 0xa6, 0x6f, 0x02, +0x45, 0x9d, 0x6a, 0x93, 0xa5, 0xe9, 0x74, 0xbe, +0x25, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0xf0, 0x45, 0xaf, 0x76, 0x54, +0xcb, 0xe6, 0x15, 0xbc, 0x33, 0x14, 0xfc, 0xda, +0xd4, 0x22, 0xfa, 0xa2, 0x10, 0x85, 0xfe, 0x32, +0xf6, 0x53, 0x8e, 0x58, 0x5c, 0x14, 0x1b, 0x95, +0x70, 0xe0, 0xbf, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0x0e, 0xa4, 0x19, +0x65, 0x4a, 0xf4, 0xec, 0x38, 0x4a, 0x6f, 0x66, +0x83, 0xe0, 0xae, 0x4b, 0xef, 0x48, 0x36, 0x99, +0x30, 0x9b, 0x4e, 0x0f, 0xc3, 0x84, 0xc2, 0x18, +0xb7, 0x21, 0x6b, 0xb8, 0xcb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0x49, +0x15, 0x72, 0x2d, 0xeb, 0x71, 0x9b, 0xf1, 0x4c, +0xfb, 0x1d, 0x18, 0xaf, 0x29, 0x71, 0x94, 0xd0, +0x01, 0x97, 0xb9, 0x2e, 0x74, 0x0b, 0x16, 0xff, +0xc9, 0x20, 0xf8, 0xe5, 0x7e, 0x09, 0xf2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0xb3, 0xe5, 0x08, 0x5d, 0x21, 0x8c, 0x90, +0xda, 0x82, 0xe0, 0xfb, 0xd5, 0x54, 0x2b, 0xa2, +0xa5, 0x52, 0x45, 0x7a, 0xd3, 0xbd, 0xb2, 0x7b, +0xd2, 0x76, 0xba, 0x25, 0x08, 0x01, 0xe1, 0x56, +0xed, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x00, 0x69, 0x00, 0xa4, 0x3e, +0xc5, 0x16, 0xc6, 0x16, 0xc8, 0xdc, 0xd4, 0xb6, +0xd2, 0xe2, 0xd8, 0x54, 0x7f, 0xe4, 0x27, 0xa0, +0x3a, 0x8d, 0x9e, 0xd1, 0xac, 0x78, 0x78, 0x5e, +0xa3, 0x2c, 0x03, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0x66, 0x5a, 0x22, +0xea, 0x39, 0x48, 0xc9, 0xa4, 0x8b, 0x85, 0x82, +0x4e, 0xff, 0xa4, 0xeb, 0x64, 0xd0, 0x3f, 0x3f, +0xcc, 0x30, 0x83, 0x4b, 0x26, 0x17, 0xd6, 0x65, +0xa7, 0x11, 0xed, 0x21, 0x40, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0xb5, +0xe4, 0xec, 0x92, 0x27, 0xba, 0xbe, 0xf3, 0x61, +0xb0, 0x7c, 0x1a, 0xe2, 0xae, 0x65, 0x0e, 0x89, +0xf7, 0x1d, 0x2e, 0xe2, 0xcc, 0x31, 0x9d, 0xc9, +0xb7, 0xd2, 0xf6, 0xd0, 0x71, 0x36, 0x97, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0x96, 0x59, 0x46, 0xf7, 0x15, 0x02, 0x8f, +0x1c, 0xd8, 0x52, 0x50, 0xbc, 0xde, 0xc2, 0x03, +0xe1, 0xed, 0xf4, 0xb4, 0x8d, 0x29, 0xdd, 0xaf, +0x40, 0x25, 0xa8, 0x06, 0x9c, 0x4d, 0xbc, 0x67, +0x6a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0x55, 0xf8, 0x2b, 0xb4, 0xdb, +0x4f, 0xd6, 0xe3, 0x3b, 0xc7, 0x84, 0x04, 0xcf, +0xa1, 0x93, 0x92, 0x52, 0xb5, 0xd7, 0x81, 0xcf, +0xee, 0x0c, 0x89, 0xe1, 0x26, 0x66, 0x3c, 0x17, +0x43, 0x03, 0xce, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0xda, 0xb4, 0x50, +0xff, 0xc2, 0xc4, 0xd5, 0x0e, 0xda, 0xfa, 0x89, +0xa1, 0xe7, 0x04, 0x96, 0x47, 0xd2, 0xdf, 0x1e, +0x57, 0x34, 0xae, 0x81, 0x6b, 0x93, 0xf3, 0x7f, +0xf9, 0x81, 0x70, 0x5c, 0x20, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0xfc, +0x18, 0x04, 0x89, 0x45, 0x1e, 0x5e, 0x24, 0xb7, +0xaa, 0xff, 0x8c, 0x8e, 0xdf, 0x31, 0x13, 0xcc, +0xb1, 0x23, 0x11, 0x33, 0x2f, 0xb6, 0xaf, 0x2b, +0xdc, 0x47, 0xda, 0x10, 0x12, 0x28, 0xba, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0x61, 0x4a, 0x90, 0x77, 0x4f, 0xf9, 0x62, +0xa0, 0x9e, 0x25, 0x45, 0x4c, 0x0a, 0x08, 0x84, +0x14, 0x22, 0x91, 0x7c, 0x2e, 0xe8, 0x0e, 0x6f, +0x60, 0x7f, 0x76, 0x2e, 0x97, 0xcd, 0xac, 0x2b, +0xb7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0xb1, 0xbb, 0x5b, 0x71, 0xa5, +0x34, 0xc6, 0x61, 0x43, 0x9a, 0x27, 0x7d, 0xa1, +0x4b, 0x67, 0xca, 0x72, 0xfd, 0x69, 0x85, 0x4a, +0x5c, 0xb5, 0xf5, 0x5c, 0x88, 0x9c, 0xd3, 0xa9, +0xc0, 0xcb, 0xa5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0x43, 0x65, 0xfb, +0xe7, 0x7e, 0x19, 0xe4, 0x23, 0x48, 0x7f, 0x77, +0x24, 0x93, 0xbd, 0x35, 0x4a, 0x2b, 0xda, 0x4e, +0xd8, 0x55, 0x99, 0x58, 0x95, 0x71, 0x01, 0xd2, +0xe8, 0x10, 0xaf, 0x2c, 0x8f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0x54, +0x9a, 0x61, 0xba, 0x18, 0x27, 0x90, 0x6e, 0x6b, +0x9b, 0xf4, 0x02, 0x78, 0x3a, 0x77, 0xc1, 0x9e, +0xa1, 0x73, 0x1c, 0x2d, 0x58, 0x29, 0x5d, 0x32, +0x03, 0x80, 0x29, 0x69, 0x60, 0x16, 0x9a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0x74, 0xf7, 0xbe, 0x26, 0x3a, 0x84, 0xe3, +0xca, 0xc4, 0x19, 0xc3, 0x3d, 0x6c, 0xcc, 0x0a, +0x2b, 0x57, 0x39, 0x67, 0x30, 0xe7, 0x4e, 0x69, +0x33, 0x83, 0x24, 0x61, 0xf6, 0x10, 0xf8, 0xd1, +0x45, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0x6d, 0x61, 0x66, 0x0a, 0x4d, +0x01, 0x9b, 0x7c, 0x57, 0x51, 0xb5, 0x04, 0x91, +0x04, 0x9d, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xc1, 0xf6, 0x86, 0x82, +0xd8, 0xee, 0x18, 0xd0, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0xc2, 0x48, 0x30, +0xd6, 0xa7, 0x9f, 0x4f, 0xe6, 0xf3, 0x73, 0xce, +0xef, 0x53, 0x17, 0xa4, 0xcc, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0xc6, +0xf1, 0x3b, 0x3b, 0xbb, 0xc4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0x90, +0xf8, 0x00, 0xb9, 0xf5, 0x93, 0xad, 0xe6, 0xe5, +0xfd, 0x61, 0xfa, 0x0a, 0x15, 0xb2, 0x02, 0x3c, +0x94, 0x5e, 0x6a, 0x4f, 0xe0, 0x94, 0x57, 0x1a, +0x9b, 0xb3, 0xa2, 0x1b, 0x3d, 0x6b, 0x24, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x55, 0x84, 0x51, 0xc3, 0x80, 0xa7, 0x55, +0xba, 0xa2, 0x06, 0x03, 0xc2, 0x93, 0xb5, 0x5e, +0xdd, 0x95, 0xea, 0x96, 0x93, 0xba, 0x68, 0x1d, +0x06, 0xda, 0xf4, 0xaa, 0xd8, 0xde, 0xa0, 0x34, +0xe7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0x03, 0xea, 0x6e, 0xb7, 0x14, +0xab, 0xb1, 0xa8, 0xe9, 0x59, 0x06, 0xfe, 0xf8, +0xde, 0x28, 0xe5, 0x65, 0xe5, 0x74, 0x70, 0xd2, +0xf2, 0x82, 0x5a, 0x96, 0x53, 0xb5, 0xd6, 0xe6, +0x2a, 0xbd, 0xcd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0xd6, 0x24, 0xf2, +0x00, 0x91, 0xcc, 0x7a, 0x3f, 0x41, 0x9e, 0x5a, +0xe6, 0x24, 0x29, 0xb4, 0x54, 0x3d, 0x18, 0xcc, +0xb4, 0x31, 0x18, 0x13, 0xd8, 0x99, 0x79, 0x9d, +0x41, 0xc2, 0x74, 0x38, 0x1a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0xd1, +0xce, 0x34, 0xdb, 0x10, 0x0e, 0x40, 0x48, 0xad, +0xe0, 0xfe, 0x17, 0x88, 0xa5, 0xc7, 0x44, 0x32, +0x5a, 0xef, 0xaa, 0xdc, 0x52, 0x6e, 0x0f, 0x9c, +0x83, 0xd4, 0x31, 0x76, 0x70, 0x48, 0xf5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0x5d, 0x6c, 0xef, 0x52, 0x1b, 0xee, 0xcf, +0x0d, 0x49, 0xb8, 0xf3, 0xc9, 0x9a, 0xac, 0xe5, +0xbd, 0x65, 0x2d, 0xdf, 0x25, 0x2d, 0xc3, 0x9f, +0x57, 0xf0, 0x7c, 0xdf, 0xe3, 0x10, 0xac, 0xe8, +0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0xaa, 0x78, 0x38, 0x1e, 0x17, +0xb3, 0xe7, 0xba, 0x46, 0xab, 0x0e, 0x7a, 0x9a, +0xc4, 0x55, 0xaa, 0x17, 0x44, 0x1e, 0x84, 0x58, +0x86, 0x64, 0xce, 0x9a, 0xc7, 0x15, 0xd3, 0x3f, +0xd7, 0x70, 0x4c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x78, 0xb3, 0xee, +0x16, 0xfe, 0x69, 0x61, 0xb3, 0x78, 0xe4, 0xb7, +0x2e, 0x6a, 0xe7, 0x67, 0x4e, 0x35, 0x4d, 0x63, +0x06, 0xe4, 0xed, 0x8c, 0x54, 0xee, 0x15, 0x78, +0x93, 0xcc, 0xc3, 0x02, 0x21, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0x28, +0xaf, 0x0f, 0x38, 0xb6, 0x77, 0xb2, 0x4b, 0x99, +0x30, 0xa9, 0x1a, 0xe0, 0xc8, 0xbd, 0xa8, 0xbb, +0x24, 0xe3, 0x82, 0xe8, 0x8f, 0x94, 0x39, 0x51, +0xb6, 0x96, 0x7b, 0x53, 0x7c, 0x8d, 0x1d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0xb7, 0xa0, 0xe5, 0x4a, 0x96, 0x3f, 0xd8, +0x04, 0x81, 0x21, 0x01, 0x73, 0x5a, 0x7b, 0xcf, +0x2f, 0xa6, 0x09, 0xf6, 0xa1, 0x7a, 0x93, 0x36, +0x70, 0x1f, 0x43, 0x13, 0xfd, 0x46, 0xcc, 0x06, +0x27, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0xdc, 0x50, 0x7e, 0x67, 0x7a, +0x1b, 0xa7, 0x59, 0xdc, 0x52, 0x80, 0x6e, 0xcb, +0xc1, 0xfc, 0xd8, 0x11, 0xea, 0xa7, 0x8c, 0x68, +0x31, 0x7a, 0xa3, 0x92, 0xf6, 0xef, 0x71, 0x56, +0xb9, 0xe1, 0x4d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0xd1, 0x7e, 0x1f, +0xcc, 0x15, 0x60, 0x5e, 0x17, 0xe8, 0xad, 0xf7, +0xef, 0x58, 0x43, 0xce, 0xf7, 0x7b, 0x6d, 0x5a, +0xc7, 0x49, 0x3b, 0x23, 0x48, 0x9b, 0xf1, 0xf3, +0x64, 0x42, 0x0a, 0xa0, 0x3d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0xfc, +0x1a, 0xf7, 0x73, 0x6f, 0xd2, 0xe0, 0xd0, 0x7d, +0x84, 0xab, 0x79, 0x51, 0x8a, 0x27, 0xc0, 0x80, +0xd6, 0xa5, 0x04, 0xd4, 0x1e, 0xaa, 0x07, 0x39, +0x66, 0x32, 0xca, 0x9c, 0x9b, 0xa3, 0x3c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0x83, 0x1a, 0xc9, 0x96, 0xdc, 0x14, 0x2a, +0x9c, 0x68, 0x61, 0x3f, 0x46, 0xfa, 0x1d, 0x22, +0x6c, 0x3b, 0xd0, 0x91, 0x4d, 0x22, 0x0e, 0x1b, +0x31, 0x63, 0x63, 0xb5, 0x71, 0x1e, 0x4d, 0xb7, +0x28, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0x44, 0x6e, 0x9e, 0x1b, 0x9a, +0xb5, 0x62, 0xf1, 0xa4, 0x21, 0x05, 0xeb, 0x07, +0xb4, 0xb4, 0xb5, 0x99, 0x44, 0x2e, 0x5b, 0xe3, +0x60, 0xd4, 0x5b, 0x50, 0xc8, 0x31, 0xa3, 0xde, +0x07, 0x64, 0xda, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0xd9, 0xeb, 0x9e, +0x73, 0x12, 0x0c, 0x13, 0xf2, 0xb8, 0xba, 0x04, +0x16, 0xbe, 0x69, 0xaf, 0x84, 0xfe, 0x2c, 0x95, +0xfd, 0x04, 0xdd, 0x9a, 0xe5, 0xf9, 0xaf, 0xcb, +0x0c, 0x31, 0x3d, 0x8d, 0xae, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0xeb, +0x1d, 0xc6, 0xcc, 0x18, 0xa6, 0x62, 0x5d, 0x2c, +0xdb, 0x99, 0xc8, 0x9e, 0x4f, 0xfb, 0x89, 0x8d, +0xd3, 0x6e, 0xaf, 0x63, 0xc1, 0x0c, 0xf9, 0x17, +0x71, 0xe7, 0x0c, 0x1f, 0x69, 0x96, 0x0c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0xe6, 0xeb, 0xd4, 0x34, 0xac, 0x3b, 0x8e, +0xd5, 0x49, 0x37, 0x1a, 0x68, 0x4a, 0x0d, 0x23, +0xad, 0x34, 0x76, 0x68, 0xf8, 0x38, 0xbc, 0xe4, +0x15, 0xc3, 0xbc, 0xcf, 0x6f, 0x7f, 0x16, 0x4e, +0xcc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0xbd, 0xa6, 0x62, 0x87, 0x87, +0xb1, 0xaf, 0x37, 0xcb, 0x88, 0x2b, 0xd1, 0x2c, +0x50, 0xe4, 0xb6, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xe5, 0xcb, 0x98, 0x27, +0xbc, 0x3d, 0xf3, 0xaa, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0x89, 0x49, 0x3b, +0xac, 0x60, 0x54, 0x65, 0x5d, 0xb2, 0xa0, 0xec, +0x14, 0xc8, 0x37, 0x33, 0xdd, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x8d, +0x06, 0x21, 0x3c, 0xdb, 0xce, 0x64, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0x74, +0xa0, 0x75, 0xe1, 0xc0, 0x28, 0xae, 0xef, 0xaf, +0x28, 0x77, 0x10, 0x22, 0xc9, 0x60, 0x1e, 0x02, +0xbc, 0x62, 0x6e, 0x3e, 0x22, 0x83, 0x7f, 0xdf, +0xb4, 0x26, 0xa4, 0xdd, 0x1d, 0xe8, 0x16, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0xb2, 0x2d, 0x22, 0x2d, 0x0b, 0xa7, 0x31, +0x6d, 0xfa, 0xb1, 0x11, 0xd1, 0xec, 0xa7, 0x2e, +0xa7, 0x72, 0xf3, 0xb4, 0x8d, 0x9c, 0x56, 0x8e, +0xf4, 0xa3, 0xc4, 0xdd, 0xf9, 0xd7, 0xa3, 0x5c, +0x8d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0xcb, 0x17, 0xde, 0x4c, 0x3f, +0x80, 0x5c, 0xe1, 0x5e, 0x4c, 0xe4, 0xfb, 0xad, +0x70, 0x0d, 0xd6, 0xf3, 0x23, 0xbe, 0x7c, 0xae, +0xa1, 0xbb, 0x6b, 0xe2, 0x84, 0xb8, 0xe4, 0x6a, +0x15, 0xdc, 0x75, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0xa5, 0xc7, 0xce, +0x75, 0x52, 0x94, 0x56, 0x79, 0x0c, 0x9a, 0x48, +0x9d, 0xbe, 0xf4, 0x1c, 0x7f, 0xa9, 0xd7, 0x31, +0x87, 0x57, 0x5d, 0x7f, 0x27, 0xcf, 0x58, 0x21, +0xb3, 0xfd, 0xf8, 0x8e, 0xfc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0x16, +0xd2, 0x15, 0x33, 0x44, 0x07, 0xfe, 0x61, 0x6c, +0x82, 0x02, 0x9c, 0x03, 0x35, 0x6a, 0x3f, 0x3f, +0xd2, 0x6d, 0x35, 0xa2, 0xa2, 0xf9, 0x7f, 0x05, +0x95, 0xa4, 0x35, 0xa6, 0x15, 0x6f, 0xf7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0x03, 0x2e, 0xc7, 0x26, 0xc4, 0x1d, 0xb0, +0xf3, 0xd0, 0x7d, 0x76, 0x29, 0x30, 0x0d, 0x23, +0x28, 0x02, 0x13, 0xa5, 0x6a, 0x39, 0x86, 0xcc, +0xff, 0x5f, 0x2f, 0x76, 0xaa, 0x32, 0x99, 0x07, +0x93, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0x07, 0x01, 0x16, 0xfa, 0x0a, +0x28, 0x0d, 0x25, 0x1a, 0xc5, 0xec, 0x89, 0xf4, +0xdf, 0xc0, 0xca, 0xab, 0x6a, 0x36, 0x61, 0xc7, +0xfb, 0x23, 0xf5, 0x15, 0x3b, 0x71, 0xcc, 0xb2, +0x41, 0xe5, 0xf6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0x1a, 0x68, 0xee, +0xcf, 0xf3, 0x7a, 0xdd, 0x1e, 0xe9, 0x92, 0x28, +0xb9, 0x61, 0x3a, 0xf6, 0x9e, 0xe6, 0x19, 0x6c, +0xf9, 0x9f, 0x5d, 0x93, 0xe8, 0xab, 0xdc, 0xca, +0xbd, 0xfa, 0x8b, 0xe6, 0x9e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0x57, +0x9c, 0xfb, 0x0e, 0x45, 0x47, 0xdd, 0x74, 0x97, +0xb0, 0x07, 0xa5, 0x8d, 0x34, 0xc6, 0x9f, 0x24, +0x5c, 0x53, 0xbd, 0xbb, 0xcc, 0xa6, 0x50, 0xad, +0xcf, 0x26, 0x65, 0x38, 0x08, 0xd5, 0x9d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0xb0, 0x1e, 0x68, 0x16, 0xdc, 0x36, 0x80, +0xd1, 0x93, 0xcb, 0x31, 0x87, 0x9c, 0x9b, 0x6a, +0xf6, 0x6f, 0xa1, 0x81, 0x6a, 0x62, 0xdc, 0xc1, +0x02, 0xe7, 0x18, 0x6c, 0x26, 0x4e, 0x5d, 0xce, +0xe0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0x26, 0xf3, 0x1f, 0xac, 0x68, +0x37, 0x6a, 0x07, 0x3a, 0x07, 0x77, 0x0a, 0x14, +0xda, 0x9e, 0x7a, 0xdd, 0x65, 0xfd, 0x39, 0x3c, +0x98, 0xc3, 0xf1, 0x47, 0x3a, 0x06, 0x79, 0x29, +0x0f, 0xe7, 0x53, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0xf6, 0xdd, 0xab, +0x86, 0xfe, 0x64, 0x03, 0x5e, 0xd6, 0xea, 0x7c, +0xea, 0xe7, 0xa1, 0xa9, 0x10, 0x34, 0x53, 0xc5, +0x5c, 0xf4, 0xd5, 0x0f, 0x5c, 0x02, 0xa8, 0x2a, +0x1d, 0xcd, 0x4e, 0xf1, 0xbe, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0x1f, +0xd0, 0x86, 0x30, 0x6c, 0x4c, 0x40, 0xba, 0xfc, +0x90, 0x8a, 0xc9, 0x74, 0xd0, 0xff, 0xc3, 0xff, +0x09, 0x15, 0xeb, 0x45, 0x54, 0x58, 0xa8, 0xfe, +0x0c, 0xa4, 0x2d, 0xc6, 0xcd, 0x22, 0xc9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0xb4, 0xee, 0x12, 0x7e, 0xb4, 0x67, 0xf5, +0xcb, 0xb2, 0xd3, 0xec, 0xfb, 0xef, 0x0a, 0x60, +0x01, 0x09, 0x79, 0xde, 0xa6, 0x4b, 0xe4, 0x36, +0x95, 0x8d, 0x3a, 0xe7, 0x41, 0x9f, 0x89, 0x6c, +0x59, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0xa4, 0xf3, 0x8f, 0x53, 0xe8, +0x70, 0x66, 0x78, 0xaf, 0x4e, 0xd3, 0xdd, 0x40, +0xb2, 0x90, 0x2b, 0xfe, 0xb2, 0x1f, 0x2d, 0xdf, +0x3d, 0x4b, 0x07, 0xe7, 0xc7, 0x1f, 0x41, 0x0b, +0x6e, 0xcb, 0x00, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0x6f, 0xe2, 0xab, +0x21, 0xba, 0xbb, 0x07, 0x6e, 0xf6, 0x94, 0xf5, +0x78, 0x00, 0x5b, 0x7f, 0x8a, 0x25, 0x98, 0x30, +0xa1, 0xc3, 0x5f, 0xa6, 0x32, 0x4e, 0xbf, 0x09, +0xc5, 0xc2, 0x3c, 0x71, 0x87, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0x0b, +0x7b, 0x47, 0x41, 0xf6, 0x6f, 0xbc, 0xf9, 0xc0, +0xa9, 0x21, 0x4f, 0x01, 0xf1, 0x2e, 0x68, 0xd5, +0x62, 0x07, 0x4d, 0xc5, 0x90, 0xc2, 0x9a, 0x80, +0x00, 0x87, 0x58, 0x1a, 0xdb, 0x91, 0x6a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0x05, 0xad, 0x26, 0xdf, 0xb4, 0xea, 0x6a, +0xb7, 0xdd, 0x51, 0xd7, 0xa8, 0x46, 0xbd, 0xe2, +0xb7, 0x07, 0xf2, 0x28, 0x40, 0x96, 0x23, 0xbc, +0x5f, 0x24, 0x99, 0xaa, 0x92, 0x87, 0x27, 0x48, +0xbd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0x02, 0x25, 0x82, 0x80, 0x57, +0xfe, 0xf8, 0xf1, 0x55, 0x0c, 0x4c, 0xed, 0xa0, +0x37, 0xa7, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x4c, 0xbe, 0x95, 0xc5, +0xd9, 0x44, 0xbd, 0x4e, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x3d, 0x2b, 0x57, +0xc9, 0x07, 0x44, 0x33, 0x89, 0x86, 0x87, 0xeb, +0x3b, 0xbf, 0xf3, 0x5d, 0x06, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x5b, +0x7e, 0xbd, 0xf5, 0xed, 0xfe, 0x67, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0xc8, +0x5d, 0x11, 0x2a, 0x82, 0xa6, 0xf6, 0x6e, 0x26, +0xec, 0x9c, 0x7b, 0x80, 0x83, 0xae, 0x24, 0xc8, +0xb9, 0xec, 0x4d, 0x5b, 0x43, 0x9a, 0xe3, 0x9a, +0xe5, 0xcd, 0xe9, 0xca, 0x35, 0x10, 0xd8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0xa2, 0x17, 0xe6, 0x62, 0x18, 0x11, 0x53, +0xc0, 0x57, 0x90, 0x44, 0x43, 0xb7, 0x75, 0x19, +0x69, 0xd0, 0x5f, 0x8f, 0xec, 0x70, 0x08, 0x9a, +0x75, 0xe5, 0x1e, 0xc6, 0xe7, 0xf5, 0x09, 0x05, +0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0xf5, 0x29, 0xa5, 0xf3, 0x81, +0xaa, 0x43, 0x93, 0xc2, 0x8a, 0x6d, 0xf6, 0xb2, +0xfc, 0xb5, 0x86, 0xc6, 0xbc, 0x66, 0x00, 0x3c, +0x61, 0xb1, 0x69, 0x8f, 0x49, 0xcc, 0xd7, 0x4b, +0xd1, 0x82, 0x65, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0x3b, 0x9e, 0x75, +0x25, 0x19, 0x8f, 0x0c, 0x20, 0xb7, 0x8f, 0xea, +0xe1, 0x63, 0x0c, 0x03, 0x66, 0xd6, 0x63, 0xe9, +0x3d, 0x2c, 0x20, 0xeb, 0xc6, 0xfc, 0x35, 0x88, +0xd4, 0x12, 0x32, 0xdb, 0x76, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0xfc, +0x7a, 0x75, 0xb1, 0x9c, 0x2d, 0x5c, 0x8a, 0xac, +0x57, 0x83, 0x9d, 0xcb, 0xcc, 0x29, 0xde, 0xaf, +0x0c, 0x15, 0x28, 0xc6, 0xbb, 0xb2, 0x6b, 0x11, +0xe3, 0xf2, 0xe0, 0x70, 0x8c, 0x0d, 0x89, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0xfa, 0x18, 0x02, 0x24, 0xf6, 0x1c, 0x9b, +0x90, 0xe1, 0x88, 0xc5, 0x80, 0xf8, 0xa6, 0x25, +0x92, 0xb7, 0x45, 0x85, 0x97, 0x53, 0x5e, 0xee, +0x61, 0x84, 0x6b, 0xaf, 0xcd, 0x39, 0x4c, 0x3f, +0x22, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x98, 0xd3, 0x08, 0x9d, 0xf5, +0x82, 0xa2, 0x40, 0x4d, 0x99, 0x54, 0x26, 0x8d, +0xc7, 0x1d, 0xab, 0x41, 0x93, 0x83, 0x5b, 0x6b, +0xab, 0x43, 0xd3, 0x01, 0x16, 0x01, 0xa8, 0xb7, +0xc5, 0x98, 0x61, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0xf9, 0x25, 0xa5, +0xc8, 0xcc, 0x27, 0x7d, 0x36, 0xeb, 0x55, 0x1f, +0xb7, 0x94, 0x79, 0x53, 0xe5, 0xc8, 0x87, 0xa4, +0xa0, 0x12, 0xa1, 0x21, 0xb5, 0xb5, 0xf6, 0x53, +0x11, 0xc3, 0x02, 0x61, 0x07, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0xcd, +0x5c, 0x3a, 0xed, 0xc9, 0x3a, 0x5a, 0xe6, 0x08, +0x6b, 0x5c, 0x85, 0xa8, 0x95, 0x2a, 0x86, 0x66, +0xa0, 0x8b, 0x29, 0xb8, 0x6a, 0x4a, 0x01, 0x62, +0x4e, 0xf5, 0x87, 0x66, 0x58, 0x85, 0xb2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0xf7, 0x15, 0xff, 0xcd, 0x47, 0x92, 0xd4, +0xf9, 0xf3, 0x03, 0xa2, 0x47, 0x0f, 0x40, 0x5b, +0x38, 0x16, 0x69, 0xc9, 0x2d, 0x42, 0x27, 0x85, +0x50, 0x00, 0x98, 0xdc, 0xd0, 0x23, 0x10, 0x88, +0xe2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0x84, 0x07, 0x5b, 0x55, 0xb6, +0xfb, 0xac, 0x5f, 0x1a, 0x93, 0xa6, 0x1e, 0xeb, +0xa0, 0x1d, 0xce, 0x84, 0x49, 0x2a, 0xa1, 0x5b, +0x8c, 0xa3, 0x7b, 0xd7, 0xdb, 0x0f, 0xaa, 0x02, +0xa2, 0xb6, 0xfd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0xb6, 0x8e, 0x91, +0x39, 0x82, 0x22, 0xfc, 0x09, 0x41, 0x02, 0xb8, +0x94, 0x66, 0x6c, 0xcf, 0x88, 0x8c, 0x27, 0x20, +0x4a, 0xaf, 0x68, 0x13, 0xb5, 0x19, 0x37, 0x73, +0xe7, 0x6b, 0x3c, 0xaa, 0xb7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0xa9, +0x91, 0xc1, 0x91, 0x22, 0xcb, 0xc7, 0x24, 0x55, +0x4c, 0x8c, 0xca, 0x7c, 0x9e, 0x60, 0x1f, 0x96, +0x44, 0xb2, 0xf8, 0xd8, 0xe6, 0xef, 0x37, 0x66, +0xa9, 0xcd, 0x92, 0x36, 0x28, 0x8c, 0x1d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x37, 0x1e, 0x7a, 0x0b, 0xbc, 0xc7, 0xe3, +0xc7, 0x94, 0x67, 0x0f, 0x30, 0x10, 0xff, 0x93, +0xf5, 0xcc, 0xf6, 0xaa, 0xcf, 0x1e, 0x5f, 0x13, +0xd4, 0x53, 0xbd, 0x74, 0xd2, 0xd6, 0xad, 0xfe, +0xfb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0xad, 0x0c, 0x47, 0xa4, 0x60, +0x85, 0xfe, 0x2d, 0x37, 0x7c, 0x74, 0x2a, 0x50, +0x6f, 0x8b, 0x04, 0x74, 0x51, 0x01, 0xf5, 0xd5, +0x85, 0xc5, 0xb5, 0x3d, 0x9a, 0xcb, 0x72, 0x3b, +0x64, 0x88, 0x80, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0x3b, 0x08, 0xda, +0xbe, 0xc7, 0x1a, 0x11, 0x1b, 0x20, 0xfb, 0x46, +0x03, 0xc9, 0x2b, 0x18, 0x23, 0xc3, 0x3a, 0xba, +0x23, 0x6b, 0xed, 0xe9, 0x69, 0x16, 0xe3, 0x28, +0xb9, 0x48, 0xa8, 0xe3, 0xa0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0x18, +0xdc, 0x58, 0xcf, 0xec, 0xcb, 0x34, 0xb3, 0x4b, +0xd4, 0x69, 0xdd, 0xe0, 0x49, 0x30, 0x01, 0xad, +0xa9, 0xb2, 0xe0, 0x30, 0xdd, 0x72, 0x53, 0x48, +0x66, 0xb3, 0x25, 0x11, 0x5c, 0xf6, 0xf7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0x40, 0xab, 0x77, 0xbd, 0x00, 0xfb, 0x7d, +0x02, 0xa3, 0xd3, 0xd1, 0x1d, 0x2f, 0x5e, 0x58, +0x99, 0x89, 0x2b, 0x71, 0x36, 0x3d, 0xfc, 0x31, +0xaf, 0x3d, 0xd3, 0x70, 0x42, 0x25, 0xf2, 0x8c, +0x70, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0x2a, 0x1c, 0x7c, 0xcc, 0x34, +0xf0, 0x64, 0x92, 0xe6, 0x0f, 0xb9, 0x04, 0xd8, +0x7a, 0x6d, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x41, 0x1f, 0xa6, 0xbe, +0x75, 0x62, 0x45, 0x69, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0x2e, 0x8e, 0x4c, +0x44, 0xce, 0xe8, 0x36, 0xc2, 0x08, 0x74, 0xf6, +0xa9, 0x40, 0x4c, 0xf3, 0xb0, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0xad, +0x94, 0x70, 0xdd, 0x70, 0xce, 0xbb, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0x3f, +0x69, 0x2a, 0x13, 0xbb, 0xe3, 0xaa, 0x3d, 0x45, +0x2e, 0x7d, 0x03, 0x67, 0x02, 0x3a, 0x95, 0x9c, +0x12, 0x58, 0x28, 0x50, 0xba, 0x5c, 0x9d, 0xe7, +0x3e, 0x91, 0x80, 0x03, 0x49, 0xa6, 0x1b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0x78, 0x00, 0x13, 0xe5, 0x95, 0x69, 0xbc, +0xef, 0x05, 0x66, 0xa6, 0x18, 0xdb, 0x50, 0x31, +0x62, 0x62, 0x95, 0xf8, 0xa3, 0x28, 0xcf, 0xd4, +0xec, 0xb0, 0xa5, 0x98, 0x1c, 0xb1, 0x55, 0x0e, +0x08, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0xe7, 0x22, 0xb4, 0x79, 0xc6, +0x8d, 0x9b, 0xa4, 0x92, 0x0f, 0x23, 0x4d, 0xaf, +0xe0, 0x57, 0x76, 0x11, 0xa3, 0x9a, 0x21, 0x9e, +0x54, 0x25, 0xb8, 0xc2, 0x6f, 0x7f, 0x8e, 0x8a, +0x19, 0x0d, 0x06, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0x06, 0x5f, 0x58, +0x96, 0xaf, 0xd1, 0x80, 0x5a, 0x73, 0x42, 0x44, +0xf5, 0xd1, 0x2d, 0x02, 0x3a, 0xe9, 0x24, 0x95, +0xcf, 0xdc, 0x04, 0xee, 0x4a, 0x0c, 0x05, 0x96, +0xb5, 0x66, 0xc4, 0x0b, 0xe9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0x73, +0xd9, 0xbd, 0x5a, 0xd7, 0x38, 0x49, 0x9d, 0x38, +0xba, 0x50, 0xa0, 0x89, 0x4e, 0xe3, 0x69, 0x94, +0x60, 0xa7, 0x19, 0x8c, 0xc7, 0x9e, 0xe4, 0xc6, +0xc4, 0x06, 0x26, 0x94, 0xd1, 0x00, 0xa2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0xf8, 0x31, 0x99, 0xec, 0x50, 0xad, 0x44, +0x54, 0x24, 0x7a, 0x6a, 0xf7, 0xe1, 0x5f, 0xf7, +0xe1, 0x02, 0x6d, 0x3b, 0xfe, 0x16, 0x76, 0xf2, +0x28, 0x0b, 0x8a, 0xe5, 0x31, 0x15, 0x46, 0xfa, +0xe6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0xdc, 0x5e, 0x8e, 0x23, 0x50, +0xac, 0x88, 0x23, 0x2f, 0xb7, 0xd8, 0xca, 0xb9, +0x05, 0x89, 0x7c, 0x67, 0xc4, 0x4f, 0x15, 0x17, +0xc6, 0x9a, 0x5b, 0x6f, 0x6c, 0x73, 0x0d, 0xf0, +0xe8, 0xb7, 0x94, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0x8d, 0x4d, 0x29, +0x41, 0xcb, 0x2a, 0xe0, 0x18, 0x9d, 0x86, 0x6e, +0xb7, 0xe8, 0xeb, 0x7d, 0x76, 0xac, 0x8b, 0xa6, +0x39, 0x56, 0x8a, 0x0b, 0xad, 0x23, 0x53, 0x9d, +0xc4, 0x3e, 0xaf, 0x48, 0x10, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0xf7, +0xd9, 0x30, 0x9f, 0x18, 0x0c, 0x6a, 0xad, 0x8b, +0xc2, 0x04, 0x4c, 0x49, 0x11, 0x58, 0xc1, 0x59, +0x90, 0x1c, 0x8f, 0x14, 0x34, 0xda, 0xae, 0x16, +0x26, 0xa0, 0x6b, 0xe7, 0x92, 0xe2, 0xd3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0x61, 0x57, 0xd0, 0x3d, 0x8c, 0x53, 0xcb, +0x22, 0xbe, 0x73, 0x6c, 0x21, 0x6a, 0xc3, 0x4b, +0xc2, 0xc6, 0x80, 0xbf, 0x48, 0x1c, 0x3c, 0xc4, +0x3f, 0x88, 0xe0, 0x6a, 0x2b, 0x43, 0x9f, 0x61, +0x8b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0x8e, 0xac, 0x4c, 0xb0, 0x22, +0x43, 0x2b, 0x95, 0x1a, 0x3e, 0x24, 0x61, 0xab, +0xf7, 0xe7, 0x30, 0x63, 0xd9, 0x5b, 0x36, 0xf1, +0xf7, 0x15, 0x39, 0xd0, 0xa0, 0x78, 0x82, 0x11, +0xad, 0x6e, 0x56, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0xde, 0xc4, 0xb1, +0x19, 0x98, 0x85, 0x4b, 0x33, 0xe3, 0x55, 0xef, +0x33, 0x85, 0x57, 0x69, 0x98, 0xbb, 0x96, 0x0a, +0xbe, 0x11, 0xe5, 0xeb, 0x82, 0x0f, 0x3f, 0x86, +0xfc, 0x09, 0x7f, 0xc1, 0x6d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x94, +0x71, 0xdb, 0x83, 0x18, 0xb5, 0x0d, 0x05, 0xd8, +0x14, 0x93, 0xea, 0x56, 0x4c, 0xd1, 0x46, 0x1d, +0xf2, 0x8a, 0x5b, 0xea, 0x5a, 0x92, 0xba, 0x73, +0xd8, 0xe5, 0x45, 0x69, 0xc5, 0x0a, 0xd4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0x43, 0x87, 0x09, 0x4f, 0x07, 0x04, 0xbe, +0x8f, 0x75, 0xe3, 0xc9, 0xd3, 0xf4, 0x99, 0x09, +0x1d, 0x0b, 0x97, 0x16, 0xac, 0x2f, 0x4f, 0x4b, +0x43, 0x2d, 0xa3, 0x7e, 0x04, 0x4a, 0xe7, 0x34, +0xec, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0x34, 0x8a, 0x28, 0xa6, 0xa1, +0x62, 0xee, 0xa6, 0x4f, 0xbc, 0x5d, 0xc4, 0x72, +0xf7, 0xe8, 0xa5, 0x0c, 0x8b, 0x6d, 0x64, 0xaf, +0x79, 0xb4, 0x28, 0x1b, 0x94, 0xf4, 0xb7, 0x6c, +0xf6, 0x59, 0x5b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0x36, 0x1c, 0x90, +0x84, 0x0f, 0xe3, 0x8f, 0xe1, 0x58, 0xd3, 0xdc, +0x97, 0x6a, 0xd4, 0x14, 0x5c, 0x6b, 0x93, 0xd9, +0x09, 0x99, 0xb3, 0x05, 0x9a, 0xac, 0xb0, 0x5c, +0xf7, 0xc7, 0xd4, 0xfa, 0x6e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0x3d, +0xa2, 0xb3, 0x37, 0x10, 0xf0, 0xd0, 0x8e, 0x2c, +0x92, 0x67, 0x37, 0x77, 0x16, 0x50, 0x40, 0x1f, +0xf2, 0x5e, 0x48, 0xa8, 0x80, 0x42, 0xcc, 0x1e, +0xdb, 0x5b, 0x2c, 0x28, 0x8c, 0x5c, 0x71, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0xf9, 0xf3, 0x59, 0xe2, 0xa3, 0xb5, 0xe9, +0xcd, 0xd0, 0xcf, 0x10, 0x7c, 0x9e, 0x3b, 0xf3, +0xed, 0x48, 0x33, 0x76, 0xa9, 0xf2, 0xe3, 0x75, +0xe8, 0x83, 0xb1, 0x8c, 0x8c, 0x2c, 0x6f, 0x59, +0x75, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0x8e, 0x85, 0x49, 0x44, 0x3a, +0xce, 0x2a, 0x24, 0xe4, 0x7c, 0x6f, 0x55, 0xef, +0x13, 0x87, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x2c, 0xc9, 0x35, 0xfb, +0xed, 0xb0, 0x38, 0xa0, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x5c, 0x76, 0x2c, +0x1b, 0x02, 0x8a, 0x88, 0x7e, 0xd5, 0x74, 0xe8, +0xba, 0x96, 0x5e, 0x10, 0xf3, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x7e, +0x6c, 0x3a, 0x02, 0xf2, 0x77, 0x14, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0x0e, +0xe8, 0xd6, 0x96, 0xc1, 0xb9, 0x60, 0x21, 0x1e, +0x6d, 0xfd, 0x54, 0xb3, 0x3b, 0x87, 0x5a, 0xe7, +0x52, 0x62, 0x89, 0x78, 0xd4, 0x3c, 0x9f, 0x8d, +0x70, 0x57, 0xe6, 0x1c, 0x61, 0x5d, 0xf2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0x9e, 0x1f, 0x29, 0xaa, 0x20, 0xef, 0x8b, +0x74, 0xe4, 0x59, 0xd3, 0xbd, 0x7c, 0x88, 0x54, +0x85, 0xa7, 0x25, 0xdb, 0x19, 0x8a, 0x47, 0xe6, +0xd8, 0x4d, 0x99, 0xe8, 0xe3, 0x18, 0xd1, 0x74, +0x5e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0x26, 0xb6, 0x94, 0x74, 0x17, +0x15, 0x0c, 0x59, 0x4c, 0xda, 0x8e, 0xd5, 0x8b, +0xbb, 0xfe, 0x12, 0x7c, 0xa5, 0xfc, 0xd6, 0x8a, +0x1b, 0xfb, 0xf2, 0x9f, 0xd6, 0xaf, 0xea, 0xa2, +0x73, 0x04, 0x8e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x63, 0x35, 0x48, +0x89, 0xbd, 0x2d, 0xea, 0x13, 0xdc, 0x83, 0xaa, +0x5c, 0x6f, 0x3e, 0xf0, 0x4e, 0xa0, 0x77, 0x63, +0xda, 0xe0, 0xad, 0x4a, 0x9b, 0x7b, 0x33, 0x34, +0xee, 0x5a, 0x25, 0x06, 0xc8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0x13, +0x68, 0xb7, 0xb7, 0xe8, 0x76, 0x83, 0x7c, 0xa7, +0xb2, 0x7c, 0xfe, 0x0d, 0x19, 0xb9, 0x8d, 0x66, +0x59, 0xe5, 0x5d, 0x9d, 0x4e, 0x4d, 0x93, 0xbb, +0x30, 0x82, 0x22, 0x23, 0x63, 0x00, 0x47, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0x12, 0xf3, 0x1c, 0xc9, 0x58, 0x2a, 0x2f, +0x60, 0x5c, 0xdd, 0x4d, 0xd1, 0x5c, 0x4c, 0x9c, +0xab, 0x85, 0x9c, 0xff, 0x64, 0x2b, 0xd1, 0x4c, +0x65, 0x32, 0x74, 0x07, 0x1f, 0x13, 0xfb, 0xce, +0xc2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0x37, 0x70, 0xda, 0xa5, 0xcd, +0xf9, 0xc8, 0x62, 0x3e, 0xd3, 0x18, 0x43, 0xd9, +0xd4, 0x14, 0xf8, 0x4e, 0x6d, 0x80, 0x6a, 0x34, +0x24, 0xf9, 0x23, 0x7b, 0xe8, 0xf0, 0x4b, 0x6e, +0x03, 0xc0, 0xf9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0xb2, 0x68, 0x6b, +0x9d, 0xe3, 0x97, 0xb3, 0xa0, 0x84, 0xdc, 0x52, +0xeb, 0x09, 0xec, 0x24, 0xc3, 0x16, 0xdd, 0x89, +0x57, 0xf5, 0x0a, 0x51, 0xc8, 0x36, 0xa9, 0x12, +0x6e, 0x7e, 0x4b, 0x94, 0x95, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x3c, +0xfa, 0x6e, 0x5f, 0xe0, 0xb8, 0xd5, 0x9e, 0x4b, +0x7d, 0xf1, 0x8b, 0xe9, 0xed, 0x9f, 0x96, 0xed, +0xea, 0x5c, 0x21, 0xcc, 0xbd, 0xf7, 0x76, 0xbc, +0xce, 0x2e, 0x2c, 0xe3, 0xfa, 0xa3, 0x65, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0xf4, 0x3c, 0x7c, 0x5d, 0xb1, 0x83, 0x6d, +0xdc, 0x4a, 0x77, 0x85, 0x4e, 0x09, 0xa0, 0xe6, +0x2d, 0x48, 0xce, 0xa9, 0x2d, 0xf2, 0x7b, 0xf6, +0x2e, 0x0c, 0x9d, 0x6c, 0xe2, 0x97, 0x66, 0x5d, +0x50, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0x47, 0x42, 0x12, 0x49, 0xa1, +0x73, 0x5a, 0x47, 0x2a, 0x21, 0x36, 0x5e, 0xb1, +0xa9, 0x2c, 0x4c, 0xc4, 0x00, 0x4e, 0xa0, 0xa6, +0x49, 0xda, 0xad, 0xad, 0x7a, 0xe5, 0x11, 0xf2, +0xd7, 0x74, 0x42, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0x35, 0x6e, 0x42, +0x62, 0x9d, 0x4c, 0x13, 0xe8, 0xf5, 0x35, 0xe7, +0x5f, 0x62, 0xf5, 0x94, 0x1f, 0x21, 0x15, 0xc9, +0x64, 0x25, 0xf7, 0x48, 0xfe, 0x9d, 0x70, 0x7c, +0xa8, 0xee, 0xed, 0x7e, 0x00, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0xa9, +0x3d, 0x25, 0x14, 0x2b, 0x7e, 0x8c, 0x18, 0x6e, +0x9d, 0xf1, 0xd7, 0x87, 0x16, 0xfa, 0xef, 0xbc, +0x7e, 0x36, 0xa8, 0x54, 0xea, 0x00, 0x9d, 0x89, +0xf8, 0x19, 0x11, 0x52, 0xea, 0x1d, 0xfb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0x87, 0xad, 0x34, 0x2e, 0x8f, 0x47, 0xf2, +0xcb, 0x80, 0x00, 0xb7, 0x9e, 0x91, 0xc9, 0x60, +0x32, 0x0d, 0x89, 0x38, 0x1c, 0xf4, 0xd5, 0x0f, +0xa1, 0xcc, 0xa7, 0x1f, 0xc5, 0x69, 0x7d, 0xd3, +0x1f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0x9f, 0x35, 0x73, 0xf7, 0x7b, +0x88, 0x2a, 0xe2, 0xa2, 0x95, 0x36, 0x95, 0x46, +0xd7, 0x5b, 0xc6, 0x94, 0xd5, 0x2c, 0xbf, 0xad, +0x8b, 0x60, 0xaa, 0x18, 0x39, 0xf0, 0x48, 0xaf, +0xf7, 0x35, 0x17, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0x5f, 0xff, 0xc4, +0xcb, 0x5b, 0xf7, 0xc1, 0x81, 0xea, 0x3f, 0x6c, +0xc6, 0xe3, 0x1a, 0xa1, 0xe9, 0x86, 0x93, 0x55, +0xe9, 0xcd, 0x65, 0x8f, 0x9e, 0xe3, 0x55, 0x8c, +0x2c, 0x3e, 0xc7, 0xdd, 0xa6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0xf3, +0x6e, 0x44, 0x82, 0x8f, 0x1e, 0x6b, 0xa8, 0x6b, +0x1c, 0xa1, 0x6e, 0x13, 0x4c, 0xc2, 0x78, 0x02, +0xde, 0xd1, 0x27, 0xfa, 0x85, 0xbe, 0x20, 0xc8, +0x90, 0xd9, 0x64, 0xfe, 0x98, 0xb1, 0xad, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0x20, 0x6c, 0x7e, 0xe6, 0xf0, 0x90, 0xd5, +0x2c, 0x61, 0x87, 0xea, 0xe9, 0xe5, 0x3c, 0xa1, +0x4e, 0x4c, 0xa1, 0xa7, 0xc5, 0x19, 0x7f, 0x0d, +0x35, 0xfd, 0xf0, 0xca, 0xcb, 0x68, 0x4d, 0x6c, +0xda, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0xaa, 0x02, 0x74, 0x06, 0xc5, +0xcd, 0x8f, 0x35, 0x5b, 0xd6, 0xd8, 0xc1, 0x49, +0x22, 0xe2, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x66, 0xe9, 0xf5, 0xb1, +0x5c, 0x0d, 0x03, 0x04, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0x9d, 0x70, 0xde, +0x35, 0x7c, 0x9b, 0x1f, 0xc7, 0x21, 0x97, 0xe8, +0x70, 0x51, 0x56, 0xa0, 0x28, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d, 0x79, +0xb1, 0x96, 0xf9, 0x42, 0x43, 0x2b, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0xbf, +0x23, 0x7f, 0x5e, 0xd9, 0x44, 0xd4, 0x87, 0x71, +0x00, 0x21, 0x27, 0xce, 0x07, 0xd4, 0x37, 0xbb, +0xd4, 0x29, 0x70, 0x63, 0x78, 0x16, 0xf3, 0xe6, +0x59, 0xdf, 0x49, 0x82, 0x73, 0x26, 0x96, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0x9c, 0xf5, 0x58, 0x32, 0x69, 0x9a, 0x45, +0x91, 0x8b, 0x2b, 0x42, 0xa4, 0xa3, 0x80, 0x02, +0x33, 0x4b, 0xde, 0xdd, 0xec, 0x69, 0x93, 0xa2, +0x5f, 0x84, 0x2d, 0x1b, 0x6b, 0xf2, 0xaf, 0xbc, +0xcc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0xb6, 0xfd, 0x66, 0x50, 0x30, +0xd8, 0xef, 0xdc, 0xf2, 0x6a, 0xeb, 0x3c, 0x35, +0xbf, 0xed, 0xd4, 0xad, 0x7d, 0xea, 0x70, 0xc3, +0xc9, 0x53, 0x68, 0xca, 0x96, 0xfa, 0x5b, 0xac, +0xf1, 0x45, 0x66, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x90, 0x2b, 0x83, +0xdf, 0x83, 0x33, 0xb9, 0x2d, 0x3c, 0x98, 0xa3, +0xd2, 0x2a, 0xd9, 0x76, 0x61, 0xd2, 0xfc, 0x26, +0xae, 0x71, 0x82, 0xf3, 0x2d, 0xff, 0xc0, 0x27, +0x5c, 0x5b, 0x7b, 0xde, 0xc7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x89, +0xce, 0x88, 0x25, 0x5d, 0x71, 0x3f, 0x26, 0x36, +0x40, 0xf3, 0xd5, 0x00, 0xcc, 0x0d, 0x0f, 0xe8, +0x76, 0x35, 0xfd, 0xd5, 0x35, 0x82, 0xe0, 0xc2, +0x76, 0x90, 0x34, 0xb0, 0x8f, 0xe7, 0x09, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x9a, 0xeb, 0x51, 0xa1, 0x4a, 0xae, 0x58, +0xbf, 0x45, 0x4d, 0x62, 0x53, 0xe0, 0x7a, 0xa7, +0xce, 0x1f, 0x9a, 0xc8, 0x5e, 0x91, 0xa8, 0xa4, +0x51, 0x4a, 0x3d, 0xc5, 0x26, 0xbd, 0x14, 0x6b, +0xcf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0xfb, 0x14, 0xbe, 0xfd, 0xdd, +0xe5, 0x0c, 0xc7, 0xac, 0xf7, 0xaf, 0x9f, 0x1e, +0x56, 0x78, 0xa3, 0x45, 0x85, 0xbd, 0xa0, 0x07, +0x9e, 0x52, 0xde, 0xf0, 0xec, 0x79, 0x4e, 0x89, +0xf3, 0x9b, 0x65, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0x48, 0xbf, 0x13, +0x03, 0x5a, 0x85, 0xdb, 0x2a, 0x93, 0xc3, 0xd0, +0xf7, 0xa1, 0xf1, 0x55, 0x50, 0xc5, 0x59, 0x1c, +0x1c, 0x65, 0x81, 0x1b, 0xc9, 0x36, 0xf5, 0x6c, +0xb0, 0xfe, 0xe2, 0xd4, 0x76, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0x20, +0xf2, 0x4b, 0x56, 0xa2, 0x57, 0x3d, 0xe4, 0x0a, +0xfe, 0x74, 0x0b, 0x5f, 0x48, 0x46, 0x8b, 0xf7, +0xc0, 0x32, 0x47, 0xa7, 0x7a, 0x05, 0xac, 0x3b, +0xf1, 0x78, 0x4a, 0xff, 0x3a, 0xeb, 0xad, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0xa1, 0x33, 0xee, 0x06, 0x7b, 0xd3, 0xaf, +0xeb, 0x30, 0x3b, 0x01, 0x11, 0x75, 0x48, 0xaa, +0x05, 0xff, 0xb7, 0x8d, 0xa8, 0x22, 0xf8, 0x8c, +0x63, 0xc0, 0x91, 0xb0, 0xea, 0xaf, 0x0b, 0x4d, +0x84, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0x22, 0x8c, 0x83, 0x5e, 0xe1, +0x62, 0xb4, 0x57, 0x53, 0x29, 0x11, 0x42, 0x1f, +0x62, 0xde, 0x22, 0xd1, 0xde, 0xdb, 0x3a, 0xd5, +0x83, 0x97, 0x8e, 0xd9, 0xb1, 0x13, 0xbe, 0xc6, +0x4c, 0x52, 0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0x60, 0x81, 0xd7, +0x98, 0x0c, 0x61, 0xe6, 0x7f, 0x99, 0xa7, 0x5e, +0xe2, 0xd1, 0xd2, 0x24, 0x5e, 0xb0, 0x85, 0xda, +0x02, 0x7d, 0xbc, 0x4a, 0x1b, 0xef, 0x50, 0x07, +0xba, 0xb2, 0x3c, 0x28, 0xa7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0xb1, +0x55, 0x4e, 0x74, 0x31, 0x15, 0xfd, 0x89, 0xfd, +0x93, 0xb3, 0x36, 0x7e, 0x4d, 0x8e, 0xf1, 0x7c, +0x20, 0xb0, 0x07, 0xa5, 0x32, 0x8a, 0x29, 0xcf, +0xbe, 0xd2, 0x27, 0x46, 0x19, 0x1f, 0xc2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0xeb, 0x6a, 0x20, 0x9d, 0x4f, 0x62, 0x2f, +0xaa, 0x91, 0x1b, 0x7f, 0x23, 0x29, 0xe5, 0x4e, +0xd1, 0x00, 0x20, 0xd3, 0x52, 0x06, 0x85, 0xb4, +0x29, 0x15, 0x76, 0x02, 0x79, 0x9d, 0xd3, 0xd8, +0xb3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0xb9, 0xff, 0xd0, 0xc0, 0x43, +0x44, 0x4b, 0x8f, 0xe2, 0x21, 0xfd, 0x6d, 0x9f, +0x42, 0xc5, 0xee, 0x3c, 0x55, 0xaa, 0x77, 0xe4, +0x9b, 0x13, 0xd6, 0xc6, 0xaa, 0x61, 0x79, 0xcd, +0xab, 0x73, 0xab, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0xeb, 0x0a, 0x1d, +0x8a, 0x54, 0x38, 0x5e, 0x77, 0x2c, 0xb2, 0xc0, +0xd3, 0xef, 0x17, 0xde, 0x77, 0xb4, 0xe6, 0xe6, +0x4e, 0xbb, 0x69, 0x08, 0x5f, 0x63, 0xa3, 0x09, +0x7d, 0x9f, 0xb6, 0x78, 0xd3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0x01, +0x94, 0xa9, 0xed, 0xe3, 0xe2, 0xaf, 0x73, 0xde, +0x88, 0xd4, 0x6a, 0x64, 0xe8, 0x3d, 0xf6, 0x2b, +0x03, 0xaf, 0x36, 0xbd, 0xbd, 0xd6, 0xea, 0x4f, +0xa9, 0x0b, 0x07, 0x71, 0xd3, 0x74, 0x5c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0xbd, 0xc5, 0xe7, 0x45, 0x0e, 0xd7, 0x48, +0xee, 0xc8, 0x77, 0x13, 0x4c, 0x72, 0x26, 0x1c, +0x6b, 0xcd, 0xcf, 0xdb, 0x58, 0x6b, 0xeb, 0x0f, +0xdb, 0x36, 0x43, 0x66, 0xa9, 0x4e, 0x1f, 0x87, +0x6f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0x1f, 0x4c, 0x53, 0xec, 0xa9, +0x06, 0x44, 0x09, 0x54, 0xb9, 0x31, 0xa1, 0xd0, +0xbd, 0xb4, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x4e, 0x7b, 0x2a, 0x39, +0xf9, 0xe3, 0xae, 0xd4, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0xa4, 0x41, 0x93, +0x74, 0x6a, 0xf0, 0xbb, 0x08, 0x0d, 0x29, 0xef, +0x62, 0x5d, 0xf8, 0xb4, 0xda, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0xcc, +0xc4, 0x5d, 0x04, 0x3b, 0xc9, 0x0a, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0x04, +0xde, 0xdf, 0x67, 0xb2, 0x1b, 0x7f, 0xfe, 0x34, +0x5e, 0x4d, 0xbc, 0xe6, 0x18, 0x46, 0x00, 0xd2, +0x24, 0x0a, 0x25, 0x37, 0x72, 0x28, 0x06, 0x05, +0x5a, 0x24, 0xf9, 0x63, 0x7e, 0x5c, 0x69, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0x00, 0x00, 0xd0, 0xf3, 0x75, 0x62, 0xc8, +0x11, 0x5a, 0xd3, 0xbc, 0x75, 0x94, 0xd8, 0x46, +0x8a, 0x30, 0x5c, 0xbb, 0x29, 0x95, 0x89, 0x6e, +0xa1, 0x54, 0x26, 0x6d, 0x9a, 0x1a, 0x41, 0xc7, +0x27, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0x44, 0xb7, 0xc2, 0x7b, 0xd1, +0xa3, 0xad, 0xcb, 0xf9, 0x16, 0x07, 0x27, 0x47, +0x9b, 0xc7, 0xf2, 0xb2, 0xc7, 0x08, 0x44, 0x69, +0xcd, 0xf7, 0xb2, 0x5e, 0x02, 0x78, 0xa2, 0xdc, +0x4c, 0x12, 0xd7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0x3e, 0x70, 0xc1, +0x65, 0x3c, 0xf6, 0x11, 0x68, 0x86, 0xb6, 0xd1, +0xdc, 0xc1, 0xaf, 0x1e, 0x1b, 0xeb, 0xcc, 0xc5, +0xaf, 0x79, 0x6b, 0x74, 0x40, 0x35, 0xba, 0xa2, +0x9d, 0xe3, 0xdc, 0x61, 0x6c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0x07, +0xeb, 0xfe, 0xbb, 0xa1, 0x4d, 0xd8, 0x7f, 0x7f, +0x80, 0x31, 0x70, 0x3c, 0x0f, 0x51, 0x8d, 0xa1, +0x77, 0xff, 0x3d, 0x97, 0xd7, 0x51, 0x54, 0x08, +0xb5, 0x3a, 0x99, 0x67, 0x3e, 0xec, 0x6b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0x70, 0x62, 0xd7, 0xa5, 0xb5, 0x1d, 0xab, +0x15, 0x91, 0x25, 0x63, 0x79, 0x1c, 0x43, 0x67, +0x85, 0x90, 0xb7, 0x62, 0xce, 0x7c, 0xaf, 0x4e, +0x7a, 0x17, 0x8f, 0x96, 0xa2, 0x52, 0xb4, 0x29, +0xdc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0x42, 0x0f, 0x6a, 0x8e, 0xb9, +0x2b, 0x6e, 0xd8, 0x23, 0xc5, 0x37, 0xc0, 0x2a, +0xa7, 0x7b, 0xf3, 0x39, 0x88, 0xca, 0xaf, 0xc3, +0x94, 0xf8, 0x9c, 0x0f, 0x6c, 0x71, 0xf0, 0xf6, +0x9c, 0x7b, 0x24, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0x0a, 0xe0, 0x64, +0x98, 0xe4, 0x57, 0xe9, 0xb4, 0x95, 0x80, 0xff, +0x37, 0xce, 0xe7, 0xab, 0xc1, 0x60, 0x12, 0x44, +0xa0, 0x34, 0x99, 0x42, 0x97, 0xce, 0x54, 0x39, +0xdc, 0xa9, 0x57, 0x2a, 0xf2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0x43, +0xb9, 0x2e, 0x43, 0xe9, 0x35, 0xe2, 0xbf, 0x07, +0x01, 0x82, 0x89, 0x3b, 0x3c, 0xab, 0xb5, 0xb4, +0x9c, 0x9a, 0x9f, 0xb9, 0x64, 0xaf, 0xf8, 0xb9, +0xb7, 0xb9, 0x3e, 0xa0, 0x44, 0xda, 0xe0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0xc3, 0x17, 0x26, 0x44, 0x73, 0x88, 0xa6, +0x7c, 0x41, 0xfd, 0xe6, 0x51, 0xf9, 0x8d, 0x22, +0x9c, 0xc6, 0x45, 0x73, 0x5e, 0x64, 0x13, 0xd4, +0xe7, 0x16, 0x99, 0x6b, 0x6d, 0x03, 0x32, 0x44, +0xdc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0x71, 0x2d, 0xac, 0xac, 0xdc, +0x14, 0x3a, 0x86, 0xe7, 0xd6, 0x94, 0x41, 0xfc, +0xd9, 0x2c, 0xef, 0x58, 0x98, 0x26, 0x40, 0x9a, +0x72, 0xfd, 0xf1, 0xf7, 0x35, 0x03, 0x9c, 0x88, +0x64, 0x6c, 0x12, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0xc6, 0x98, 0x09, +0x69, 0x02, 0x38, 0x25, 0x61, 0x88, 0xb3, 0x49, +0xcb, 0x4c, 0xb7, 0xa3, 0x55, 0x1c, 0xa4, 0x56, +0x64, 0x5f, 0x7f, 0x90, 0x32, 0xd7, 0x1f, 0xcc, +0xd7, 0xe1, 0x83, 0x54, 0x2d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0xf7, +0x1f, 0xae, 0xa7, 0x17, 0x53, 0xe7, 0x25, 0x6f, +0xc1, 0xa9, 0xc5, 0xdc, 0xc4, 0x66, 0x19, 0x63, +0x1d, 0xb8, 0x71, 0x05, 0x1c, 0xb5, 0x74, 0x6c, +0x94, 0x1b, 0x77, 0xab, 0xc4, 0x07, 0x1d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0xee, 0x6b, 0x30, 0xb9, 0x2f, 0x5e, 0x14, +0x28, 0x3b, 0xb8, 0x67, 0x53, 0xeb, 0x4b, 0xf2, +0xe7, 0x7e, 0x10, 0x08, 0xec, 0xb1, 0x7e, 0x5b, +0x5d, 0x91, 0xa6, 0xf0, 0x12, 0x6d, 0x77, 0x9e, +0x72, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0xb2, 0xeb, 0xb5, 0xf0, 0x01, +0xea, 0xe8, 0x21, 0x5e, 0xb1, 0x66, 0xef, 0x02, +0xa2, 0xb5, 0x8a, 0x4f, 0xea, 0x56, 0x9c, 0x1e, +0x08, 0xcb, 0x42, 0x5a, 0x94, 0x10, 0x4e, 0x97, +0x9d, 0xe6, 0xdd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0x52, 0x3a, 0x17, +0x24, 0x69, 0xcb, 0x24, 0x22, 0xef, 0xbd, 0xcf, +0xba, 0x27, 0x50, 0xd0, 0x79, 0xf8, 0x2a, 0xc0, +0xcb, 0x40, 0xec, 0x1c, 0x33, 0xbf, 0x2d, 0x98, +0x35, 0x8e, 0xd4, 0xb8, 0x77, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0xc6, +0x18, 0x51, 0xfb, 0x87, 0x82, 0x5a, 0xf1, 0x58, +0x9e, 0x3c, 0xc3, 0x60, 0x43, 0xf6, 0x4c, 0x4b, +0xcd, 0x0a, 0x0f, 0x4a, 0x10, 0x23, 0x7f, 0x2b, +0xa7, 0x7f, 0xce, 0xf9, 0x66, 0xf6, 0x04, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0x7d, 0x52, 0xa7, 0xc0, 0x96, 0x6c, 0xf7, +0x79, 0xdd, 0x8e, 0xdf, 0x78, 0xac, 0x04, 0x5c, +0x5a, 0x6d, 0x0e, 0xb0, 0xb9, 0x33, 0x21, 0xab, +0x84, 0x40, 0x90, 0x1f, 0x19, 0xc0, 0x25, 0xc4, +0xe4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0x4f, 0x7b, 0x67, 0x50, 0xb2, +0x43, 0xa2, 0x72, 0x03, 0x3c, 0xae, 0x18, 0x0f, +0x6a, 0x82, 0xce, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x3c, 0x24, 0xe9, 0xaf, +0x82, 0x20, 0x5d, 0x9a, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x91, 0x38, 0x42, +0x2f, 0x48, 0x20, 0xe2, 0x4f, 0xdf, 0x66, 0x98, +0xb2, 0x46, 0x5e, 0xb4, 0xf2, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa4, 0xaa, +0x4b, 0xab, 0x54, 0x58, 0xf4, 0xae, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0x56, +0x45, 0x72, 0x3b, 0xf0, 0x90, 0xee, 0x97, 0x2b, +0x8e, 0x3a, 0xea, 0x90, 0x34, 0x2d, 0x64, 0xcc, +0xa1, 0x9c, 0x64, 0x79, 0xe7, 0xd4, 0x1c, 0x0a, +0xaf, 0x04, 0x7f, 0x9e, 0xb2, 0xca, 0x7c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x96, 0x7c, 0x4a, 0xbf, 0x5c, 0x24, 0x97, +0x99, 0x22, 0xae, 0xe3, 0xb9, 0x35, 0xc3, 0x57, +0x60, 0x3d, 0xde, 0xe9, 0xa6, 0xd6, 0xa7, 0x20, +0x95, 0x3c, 0xf3, 0x97, 0xda, 0xca, 0x5d, 0x4c, +0xbb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0x6b, 0x28, 0x4e, 0x06, 0x77, +0xd2, 0x52, 0x12, 0x73, 0xe2, 0x80, 0xf9, 0xa9, +0xef, 0xf0, 0x58, 0xa0, 0xa2, 0xae, 0xad, 0x02, +0x56, 0x3f, 0x81, 0x61, 0xf0, 0x4d, 0xb3, 0xa2, +0x70, 0x47, 0x20, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0xae, 0x84, 0x8d, +0x4d, 0x36, 0xc1, 0x3f, 0xbc, 0xef, 0x64, 0x84, +0x1a, 0x35, 0x4a, 0xf3, 0x38, 0x2d, 0xb1, 0x67, +0xa1, 0x1d, 0xbe, 0x77, 0x03, 0xef, 0x75, 0xeb, +0x47, 0x7a, 0xe8, 0x45, 0xde, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x33, +0xe2, 0x4f, 0xb0, 0xbd, 0xad, 0x4a, 0xf4, 0xfc, +0xf2, 0xd1, 0xeb, 0xe0, 0xc0, 0x92, 0x89, 0x48, +0x43, 0xfc, 0x6d, 0xd2, 0x67, 0xa3, 0xb3, 0xfb, +0xa5, 0x69, 0x4a, 0xfa, 0xac, 0xb1, 0x78, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0x6c, 0x34, 0x22, 0x0b, 0x58, 0x79, 0xe2, +0x7c, 0xbc, 0xa5, 0x16, 0x53, 0x0a, 0xa8, 0xfc, +0x50, 0xee, 0xc6, 0x70, 0x9b, 0x90, 0xb3, 0x68, +0x15, 0xaa, 0x0e, 0xb4, 0x5b, 0xec, 0x2c, 0x7d, +0x42, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0x00, 0x79, 0xef, 0xe8, 0xe2, +0xda, 0xf6, 0x5d, 0xc2, 0x7a, 0x0e, 0xe5, 0xce, +0x73, 0x45, 0x10, 0xcf, 0x20, 0xf8, 0xfb, 0xae, +0xb8, 0xc8, 0x45, 0x12, 0x16, 0xe5, 0x50, 0x19, +0x6c, 0x4d, 0x75, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0x00, 0x1b, 0xf0, +0x8b, 0x57, 0xba, 0x4b, 0xd3, 0xd6, 0x2b, 0x29, +0x39, 0x20, 0x92, 0x67, 0x38, 0x72, 0xab, 0x53, +0x45, 0xa9, 0xc1, 0x65, 0xe8, 0x7a, 0xbb, 0x41, +0xca, 0xde, 0xa4, 0x5d, 0x92, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x50, +0x6b, 0x2c, 0x5a, 0x2a, 0x3a, 0x7d, 0x96, 0xf9, +0x3b, 0xc1, 0x00, 0xf8, 0x9a, 0xdd, 0x3d, 0x3f, +0xe5, 0xb8, 0xd8, 0x67, 0x6a, 0xb4, 0x69, 0x54, +0xe0, 0xee, 0xec, 0x4f, 0x6a, 0xb3, 0x26, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0x50, 0xf9, 0x4b, 0xe4, 0x30, 0x6b, 0x4c, +0x5e, 0xf3, 0xef, 0x30, 0x93, 0x06, 0xd8, 0x8c, +0x30, 0xe6, 0x35, 0x8b, 0x88, 0x46, 0xc8, 0x98, +0x23, 0x62, 0xfb, 0xca, 0x0a, 0x0c, 0x0d, 0xfb, +0x65, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0x33, 0xde, 0xc6, 0xc8, 0xce, +0xe2, 0x44, 0x69, 0x28, 0x28, 0x5e, 0xcb, 0x49, +0x05, 0x75, 0xb5, 0x02, 0xba, 0xa3, 0xdc, 0xa2, +0x99, 0x34, 0xf2, 0xe3, 0xbc, 0x40, 0xf0, 0x7a, +0x23, 0x4e, 0x48, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0x01, 0x93, 0x27, +0x82, 0x46, 0x9d, 0x4d, 0x8b, 0x33, 0x51, 0xeb, +0xad, 0xd7, 0x67, 0x14, 0x8d, 0x37, 0x55, 0x43, +0x9c, 0xc7, 0x35, 0xb2, 0x35, 0xfc, 0x5a, 0x08, +0xb6, 0x9f, 0x22, 0x69, 0x53, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x90, +0x15, 0x68, 0x12, 0xec, 0x9e, 0x7c, 0x28, 0x05, +0xf4, 0xc9, 0xa1, 0x64, 0x17, 0xcd, 0x3e, 0xb5, +0xdb, 0x23, 0x23, 0x26, 0x47, 0x83, 0xc9, 0xd8, +0x6d, 0x45, 0x2a, 0x18, 0x38, 0xd2, 0xee, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0x02, 0x16, 0xc2, 0x32, 0x88, 0xff, 0x49, +0xe6, 0x65, 0xb7, 0x8a, 0x7c, 0x9f, 0x4e, 0xfb, +0xe5, 0xbd, 0xc3, 0xa6, 0xb5, 0xb6, 0xbf, 0x19, +0x87, 0x3e, 0x25, 0x5f, 0x37, 0x91, 0x1d, 0x63, +0xac, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0xd9, 0x17, 0x15, 0x74, 0x2d, +0x18, 0x0f, 0xed, 0x8a, 0x98, 0xf9, 0x9d, 0xfd, +0xe7, 0xe2, 0x7a, 0x9d, 0x04, 0x61, 0xd9, 0xb9, +0xcb, 0x7d, 0x9f, 0x7f, 0x8a, 0x49, 0x4f, 0xb0, +0x54, 0x49, 0x40, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0xd3, 0xa3, 0x62, +0xb7, 0x9f, 0xf9, 0x7b, 0x91, 0x12, 0x19, 0x96, +0x8b, 0xdd, 0x12, 0x4c, 0xe2, 0x9b, 0xc1, 0x3c, +0xf4, 0xaa, 0x2f, 0x9a, 0xc8, 0xc0, 0xc4, 0x9b, +0xfb, 0x9f, 0x32, 0xc3, 0x6a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0x6d, +0x50, 0x2c, 0xa5, 0x49, 0x06, 0x13, 0x9d, 0xef, +0x2b, 0x55, 0xd1, 0x3b, 0x6d, 0xf1, 0xb7, 0xec, +0xbe, 0x4d, 0x5a, 0xbb, 0x24, 0xba, 0x7e, 0x52, +0x29, 0xc0, 0xf2, 0x3e, 0xb7, 0x4b, 0x1e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0xf7, 0xd2, 0xd0, 0xba, 0x62, 0x56, 0x30, +0x95, 0x45, 0xc0, 0x6a, 0xdf, 0x39, 0x6e, 0x16, +0xe3, 0x48, 0xaf, 0x15, 0x30, 0x4f, 0xa2, 0xeb, +0xff, 0xaf, 0x58, 0x72, 0xdc, 0x7c, 0x06, 0x16, +0x89, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0x77, 0x6a, 0xad, 0xca, 0x4e, +0xb9, 0x9c, 0x1b, 0x54, 0xa2, 0x38, 0x12, 0x7b, +0x9d, 0xeb, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x8b, 0x95, 0x5c, 0x87, +0x07, 0xa4, 0xa2, 0x39, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0x30, 0x5a, 0x17, +0x0b, 0xe7, 0x0b, 0xaa, 0x63, 0x22, 0x6b, 0x0f, +0x84, 0x5a, 0xbc, 0x79, 0xa0, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9f, 0x0d, +0x67, 0xe4, 0x8d, 0xec, 0x7c, 0x96, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0x6f, +0x7e, 0x9c, 0xdb, 0x9e, 0xcb, 0x57, 0xae, 0x28, +0xf0, 0xa2, 0xe8, 0x17, 0x39, 0xa2, 0xd4, 0x17, +0x47, 0xa5, 0xc5, 0x32, 0x0e, 0x28, 0x09, 0x84, +0x74, 0x7a, 0x7d, 0x66, 0x01, 0x9f, 0x98, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x0a, 0xdb, 0x36, 0xd8, 0x90, 0xd6, 0xbb, +0x06, 0xb1, 0xf9, 0xe7, 0x58, 0x2e, 0x78, 0x12, +0xd3, 0xf5, 0x1d, 0x6b, 0x3d, 0x3e, 0x22, 0x2b, +0x5b, 0x65, 0x14, 0x0f, 0xca, 0x10, 0x2c, 0xbb, +0x22, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x04, 0x36, 0x6a, 0xdb, 0xf9, +0xf7, 0xf7, 0xc9, 0xab, 0xed, 0xd6, 0xbb, 0x0c, +0xe1, 0x42, 0x47, 0xac, 0xe5, 0x4d, 0xe6, 0x5c, +0xc0, 0xc9, 0xc0, 0x05, 0xf5, 0x0f, 0x47, 0x88, +0x86, 0x9b, 0x9b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0x75, 0x9b, 0x05, +0xf6, 0x5a, 0xe0, 0xa7, 0xe8, 0xee, 0x06, 0x6f, +0x1d, 0x4b, 0x8d, 0x3b, 0xfd, 0x02, 0xe8, 0x49, +0xa1, 0xf6, 0xc8, 0xe7, 0x47, 0x61, 0x3d, 0x8a, +0x19, 0x08, 0xc7, 0xe0, 0x15, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0x11, +0x56, 0x49, 0x1c, 0xbd, 0x6c, 0x87, 0xa7, 0x05, +0x44, 0x8d, 0x92, 0xb7, 0xa9, 0x7d, 0xeb, 0xe4, +0x65, 0x97, 0x5d, 0xa5, 0x1e, 0xf8, 0xe6, 0x8b, +0xa9, 0x24, 0xbc, 0x84, 0x1f, 0x3a, 0x98, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0xb2, 0x57, 0xd5, 0x0f, 0x69, 0x4e, 0x67, +0xf8, 0x1f, 0x3e, 0x49, 0xd3, 0x06, 0xdc, 0xc4, +0x8c, 0x88, 0x78, 0xfd, 0x5b, 0x19, 0xc6, 0xeb, +0x4c, 0xc2, 0xa1, 0x7b, 0x74, 0x9d, 0xc4, 0xd9, +0x32, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0x6c, 0x27, 0x6d, 0x20, 0xc9, +0xfb, 0x11, 0xdb, 0x77, 0x80, 0xc5, 0x8a, 0x74, +0x15, 0xe9, 0x31, 0x75, 0x20, 0x87, 0x20, 0x36, +0x42, 0x8a, 0xe4, 0x07, 0x1b, 0xb8, 0x1f, 0xac, +0x52, 0x57, 0xd8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0x0a, 0x75, 0x5c, +0x1f, 0x8d, 0x43, 0xe7, 0xde, 0x2c, 0xf4, 0xfd, +0xaf, 0x48, 0xb8, 0xcb, 0x53, 0x69, 0xc4, 0xf7, +0x50, 0xd2, 0xf5, 0x37, 0xb8, 0xe4, 0x6b, 0x00, +0x68, 0x7b, 0x57, 0x86, 0x2d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0xac, +0x13, 0xfb, 0x50, 0x02, 0xe2, 0x71, 0x32, 0x77, +0x11, 0x5e, 0x15, 0xfe, 0x58, 0xf9, 0x0a, 0x3f, +0x33, 0x9f, 0xc6, 0x24, 0xdc, 0x9b, 0xe5, 0xc7, +0xef, 0xce, 0x02, 0xc0, 0x22, 0x02, 0xee, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0x3c, 0xde, 0xff, 0x85, 0xc7, 0xcf, 0xaa, +0x8f, 0xdc, 0x73, 0x14, 0x63, 0xa8, 0xac, 0x24, +0xc7, 0x6a, 0x64, 0xb9, 0x27, 0x40, 0xb1, 0xc4, +0x8d, 0x20, 0x1d, 0xcf, 0x45, 0x6f, 0xdb, 0xf4, +0x9e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0x9e, 0x68, 0x3f, 0xc8, 0xf2, +0xc1, 0xbc, 0x23, 0x23, 0xe7, 0xa8, 0x6a, 0xaa, +0x53, 0xeb, 0x89, 0x93, 0xa4, 0xf0, 0x89, 0x05, +0x13, 0x43, 0xb6, 0xbb, 0x74, 0x29, 0xa6, 0xe7, +0x81, 0xc1, 0x54, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0xab, 0xff, 0xd9, +0x44, 0x50, 0x03, 0x79, 0x5a, 0x63, 0x37, 0x0d, +0x72, 0xc8, 0xc7, 0xb2, 0x78, 0x3a, 0xed, 0xb9, +0xa5, 0xee, 0x86, 0x6c, 0x6f, 0x19, 0xa8, 0xc1, +0xf6, 0xcd, 0xf3, 0x54, 0xd0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0x07, +0x72, 0x92, 0x1f, 0x3d, 0xf3, 0x73, 0x27, 0x1a, +0xef, 0xfe, 0x06, 0xd5, 0x2b, 0xf1, 0x7e, 0xf4, +0x60, 0xc5, 0x68, 0x61, 0xb4, 0x04, 0x1f, 0xaa, +0x48, 0x71, 0xa7, 0xc8, 0x50, 0x47, 0x6e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0xc3, 0x16, 0xe4, 0x57, 0xdd, 0x15, 0x1f, +0x4a, 0x7a, 0x39, 0x65, 0xa1, 0x99, 0x26, 0xfa, +0xc1, 0x59, 0x21, 0x2a, 0x25, 0xb3, 0x93, 0x2e, +0x1e, 0x66, 0x55, 0x31, 0xf0, 0x85, 0x3a, 0x6d, +0x77, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0x2f, 0xa0, 0x9b, 0xd5, 0xa7, +0xe7, 0xba, 0xad, 0x30, 0xc3, 0xa2, 0x66, 0xd3, +0xc2, 0x2a, 0x02, 0x37, 0x26, 0x4a, 0x92, 0x13, +0x4a, 0xfe, 0x05, 0x80, 0x68, 0xa1, 0x91, 0x8e, +0x63, 0xd4, 0x44, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0x66, 0xd3, 0x68, +0xac, 0x58, 0xb3, 0x85, 0xf8, 0x92, 0x25, 0x2b, +0x40, 0x9e, 0xa9, 0xc1, 0x19, 0x67, 0xca, 0x20, +0xd6, 0xe5, 0x96, 0x4a, 0xfc, 0x5a, 0x07, 0x3a, +0xc1, 0xf9, 0xc3, 0x7c, 0x12, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0x04, +0x21, 0x5c, 0x16, 0xbc, 0xa0, 0xde, 0x15, 0x8f, +0xef, 0xa7, 0x60, 0xf5, 0xe9, 0x2c, 0xbc, 0xdd, +0xa5, 0x50, 0xe7, 0xcf, 0x6d, 0x8c, 0x9b, 0x1b, +0x4d, 0x9a, 0x93, 0x15, 0x5a, 0x22, 0xf9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0xe6, 0x5a, 0x03, 0x7d, 0x40, 0xbd, 0x63, +0x9e, 0x4f, 0xdc, 0xe4, 0xc1, 0x82, 0x96, 0x8c, +0xf9, 0xb4, 0x03, 0x9e, 0xb1, 0x5c, 0x89, 0x94, +0xad, 0x2e, 0x9d, 0x08, 0xfe, 0xb1, 0x24, 0x05, +0x5d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0xb4, 0xec, 0x4c, 0xe5, 0xba, +0x43, 0xb6, 0x28, 0x9f, 0x61, 0x03, 0x23, 0xcb, +0xd1, 0x11, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x75, 0x1c, 0xa1, 0x4c, +0x47, 0x7e, 0x85, 0x6f, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0x68, 0x6a, 0x00, +0x2d, 0x19, 0xee, 0xf3, 0x0c, 0x9b, 0x29, 0x67, +0x29, 0x14, 0x68, 0x39, 0x42, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0xa2, +0x36, 0x3c, 0x9a, 0x51, 0xe6, 0xe5, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0x13, +0xf1, 0x00, 0x8d, 0xdf, 0x09, 0x8d, 0x69, 0x5f, +0x4d, 0x21, 0x8b, 0x7d, 0xef, 0xb7, 0x03, 0x92, +0x93, 0xbb, 0xf5, 0x3b, 0xd6, 0x52, 0x52, 0x18, +0x50, 0x1c, 0x0f, 0x58, 0x7f, 0x14, 0x79, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0xee, 0xae, 0xcb, 0x77, 0xb5, 0xa6, 0x69, +0xd5, 0x09, 0xc1, 0x74, 0x21, 0x8c, 0x49, 0x1c, +0xd0, 0x88, 0x40, 0x92, 0xc0, 0xe4, 0xa0, 0x93, +0xf7, 0x78, 0x77, 0xf1, 0xca, 0xd8, 0xd8, 0x56, +0xb5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0xcf, 0x6a, 0xfa, 0x1c, 0xad, +0x95, 0xd9, 0x9f, 0x2d, 0x9c, 0xff, 0x36, 0x1d, +0x11, 0x56, 0xe8, 0x2d, 0xe5, 0xac, 0x74, 0xa0, +0xb0, 0x55, 0x7d, 0xc8, 0xf5, 0xec, 0x80, 0x0a, +0xdd, 0x36, 0xe0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0x11, 0x81, 0xf6, +0x7f, 0x5b, 0x72, 0x31, 0xf4, 0x07, 0x96, 0x3a, +0xef, 0x53, 0x00, 0x6b, 0x07, 0x27, 0x0a, 0xf3, +0x68, 0x48, 0xe2, 0x2f, 0x93, 0x04, 0xb8, 0x66, +0x2f, 0xd3, 0x85, 0x01, 0x2e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0x9d, +0x6b, 0x15, 0xdb, 0xbd, 0xba, 0x88, 0x02, 0x9e, +0xbf, 0x3c, 0x50, 0xc8, 0x0f, 0x4d, 0x5b, 0x92, +0x0c, 0x0f, 0xc6, 0xc8, 0x13, 0x3b, 0x3b, 0x0c, +0x37, 0xcd, 0xcf, 0x44, 0xea, 0xcc, 0x5c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0xed, 0x91, 0x2b, 0x6e, 0x1d, 0x29, 0x62, +0x82, 0x72, 0xe4, 0xa9, 0x76, 0xf9, 0xcb, 0x45, +0x0a, 0x13, 0x13, 0x92, 0x89, 0x03, 0xce, 0x56, +0xe3, 0x69, 0x28, 0x1c, 0x41, 0x39, 0xfc, 0x9c, +0x11, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x26, 0x61, 0xee, 0xb6, 0xde, +0x5e, 0x53, 0x5b, 0xf4, 0x87, 0xfe, 0x40, 0x37, +0x38, 0x94, 0x06, 0x2b, 0x6f, 0xeb, 0xf9, 0x25, +0xe7, 0x73, 0x47, 0x7c, 0x75, 0x9f, 0xe6, 0xae, +0xb2, 0xb2, 0xb4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0xa4, 0xdd, 0x8d, +0x0d, 0x85, 0x4a, 0xc7, 0x66, 0xed, 0x1b, 0x07, +0x1e, 0x56, 0xc6, 0x04, 0xf0, 0xb8, 0x57, 0xe6, +0x07, 0x34, 0x0d, 0x73, 0x79, 0xaf, 0x94, 0x5e, +0x9d, 0x25, 0x0b, 0x67, 0xc0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0xff, +0x33, 0x81, 0x1b, 0x37, 0x6b, 0x27, 0xfd, 0xcc, +0xe9, 0x77, 0x33, 0xc5, 0xec, 0x63, 0x95, 0x11, +0xfd, 0xd5, 0xaf, 0x6f, 0x37, 0x26, 0x78, 0xeb, +0x0e, 0x27, 0xa6, 0xcd, 0x24, 0x35, 0x86, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x58, 0xc3, 0x03, 0x65, 0x9c, 0x44, 0x65, +0x5a, 0x77, 0xae, 0x59, 0xd4, 0xc2, 0x37, 0x10, +0xa5, 0x69, 0x2b, 0x5b, 0xb9, 0x6f, 0x7f, 0x8e, +0x52, 0xd1, 0xdf, 0x85, 0x03, 0xa8, 0x6f, 0x76, +0x92, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x0f, 0xc8, 0x17, 0xb8, 0x7f, +0xa5, 0x1e, 0x32, 0xfd, 0x82, 0x9e, 0xf0, 0x42, +0x81, 0xf8, 0xb2, 0xa0, 0x9c, 0x24, 0x45, 0x9a, +0x57, 0x5a, 0xe4, 0x6f, 0x72, 0xa9, 0x87, 0x94, +0xfa, 0xc9, 0x55, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0xb9, 0xb3, 0x9a, +0x82, 0x41, 0x9c, 0xce, 0xf4, 0x4a, 0x09, 0x41, +0x0f, 0x5a, 0xd4, 0x9d, 0x3c, 0x36, 0xb2, 0x0e, +0x14, 0xc8, 0x85, 0x34, 0x35, 0x62, 0x74, 0x7f, +0xdb, 0x93, 0xcd, 0x10, 0x18, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0x14, +0xea, 0x0c, 0x49, 0x1c, 0xaf, 0x3a, 0xe7, 0x9b, +0x59, 0x52, 0x2c, 0x38, 0xa8, 0xc2, 0x22, 0x0b, +0x7a, 0x84, 0x3c, 0x32, 0xdc, 0x65, 0xd3, 0x17, +0xbc, 0x2a, 0x87, 0x7c, 0xe7, 0xad, 0xe9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0x1b, 0xc3, 0xc5, 0xb0, 0xea, 0x80, 0x06, +0xcf, 0xb1, 0x3d, 0xaf, 0x2b, 0x29, 0x11, 0x9f, +0x31, 0xf3, 0xb1, 0x8d, 0x63, 0x45, 0x98, 0x25, +0x9e, 0x91, 0xe1, 0xda, 0x65, 0x26, 0xc7, 0x68, +0xb8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0xe9, 0x78, 0x55, 0x29, 0x16, +0xfe, 0xc4, 0x34, 0xac, 0x2c, 0x24, 0xfd, 0x16, +0x2d, 0xfa, 0xa8, 0x65, 0x38, 0xcf, 0x01, 0x8d, +0x5f, 0xe6, 0x7c, 0xee, 0x26, 0x87, 0xe0, 0xd4, +0xa7, 0x7c, 0xc0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0xdd, 0x39, 0xf7, +0x5b, 0x6d, 0x0c, 0xc2, 0xe6, 0xfc, 0x80, 0x25, +0x18, 0xc0, 0x62, 0x1d, 0x4a, 0xac, 0x45, 0x3b, +0x6e, 0x15, 0x15, 0x85, 0x83, 0x5e, 0x05, 0xb3, +0x87, 0x7d, 0xaf, 0x91, 0x9c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0xcf, +0xdf, 0x4f, 0x7d, 0x06, 0x41, 0x4d, 0x04, 0x62, +0xa3, 0x17, 0xac, 0x23, 0xf1, 0xea, 0xd8, 0xe0, +0xfe, 0x4a, 0x90, 0x07, 0xfb, 0xbc, 0xbf, 0x09, +0xc8, 0x1c, 0xfe, 0x11, 0x0d, 0x02, 0x71, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0x5b, 0x21, 0x6d, 0x24, 0xcf, 0x2f, 0x47, +0x33, 0x81, 0x25, 0x20, 0x8b, 0x49, 0x01, 0xd0, +0x32, 0x25, 0xc0, 0xf4, 0x79, 0xa2, 0x98, 0xfb, +0xbd, 0x7a, 0x5d, 0x34, 0x60, 0xf4, 0x2b, 0x75, +0x09, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0x9a, 0x5d, 0xe1, 0xd8, 0xaa, +0xfa, 0x33, 0xb1, 0x13, 0x7e, 0x1d, 0x7c, 0xb7, +0x8a, 0x37, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xe3, 0xe4, 0x65, 0x61, +0x4d, 0x41, 0xb6, 0xb0, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0xf8, 0xe6, 0x24, +0x7a, 0x13, 0x18, 0xd6, 0x32, 0x0a, 0xda, 0x3b, +0xf5, 0x4c, 0xbc, 0xca, 0xc0, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb2, 0xc3, +0x8e, 0x3b, 0x23, 0x8a, 0x22, 0x90, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0x98, +0xa8, 0x01, 0x75, 0x41, 0x3d, 0xd7, 0xd1, 0xc4, +0xa2, 0x42, 0xf4, 0xb8, 0x32, 0xe6, 0x26, 0x23, +0xb9, 0xcf, 0xb9, 0x28, 0x26, 0x93, 0x08, 0x00, +0x3b, 0x9d, 0xfc, 0x6b, 0xe1, 0x93, 0xed, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0x08, 0x02, 0xa9, 0x9e, 0xbd, 0x76, 0xa0, +0x0c, 0x44, 0x92, 0xf2, 0x0c, 0x93, 0x32, 0xf5, +0xcb, 0x0e, 0x60, 0x47, 0xb0, 0x41, 0x73, 0xf9, +0x9b, 0x3e, 0xff, 0x16, 0xa3, 0x4a, 0xdb, 0xdd, +0xa9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0x97, 0x00, 0x9b, 0xeb, 0xc3, +0xe3, 0x6b, 0x5c, 0x6b, 0xd6, 0x35, 0xa3, 0x92, +0xa9, 0x56, 0x8a, 0x11, 0xaa, 0xcf, 0x34, 0x6c, +0xef, 0x31, 0xa4, 0x7a, 0x2b, 0x06, 0x67, 0x5d, +0xeb, 0x70, 0xb9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0x5c, 0x0e, 0x28, +0x10, 0xe9, 0x98, 0x94, 0x1c, 0xfd, 0xc1, 0xb6, +0x1d, 0x46, 0xe2, 0x7e, 0x55, 0xc6, 0xb3, 0xc0, +0x55, 0x66, 0x8e, 0x08, 0x32, 0x56, 0x35, 0x91, +0x67, 0x22, 0xcf, 0x94, 0xbc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0x01, +0xcc, 0x12, 0x9b, 0x8a, 0xe2, 0x2b, 0x7b, 0x8c, +0x62, 0xaf, 0xb1, 0xbe, 0x48, 0x06, 0x5d, 0x95, +0xdc, 0x93, 0x34, 0x19, 0x0d, 0x4e, 0x3a, 0x65, +0x32, 0x59, 0x0d, 0x23, 0x28, 0x3f, 0xa0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0x41, 0x7f, 0x18, 0x69, 0x84, 0x12, 0xfb, +0x71, 0x4d, 0x24, 0xcf, 0x61, 0xde, 0x93, 0x6a, +0x86, 0xa2, 0xda, 0x85, 0xc3, 0xcd, 0x90, 0x8a, +0x47, 0x87, 0x81, 0xf9, 0xe6, 0x49, 0xe0, 0x93, +0x76, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0xf9, 0xfa, 0xa2, 0xd7, 0x7f, +0x84, 0x03, 0xbf, 0x1e, 0x06, 0x41, 0x18, 0xd8, +0x24, 0x59, 0x66, 0x83, 0x78, 0xa0, 0x95, 0x63, +0x0d, 0xab, 0x55, 0x85, 0xf6, 0x87, 0x7d, 0xd6, +0x2d, 0xe1, 0xe9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x38, 0xdd, 0x72, +0x7a, 0xe7, 0x32, 0x2c, 0x03, 0xbb, 0x85, 0x6b, +0xd0, 0x93, 0x75, 0x45, 0x96, 0xf5, 0xec, 0x7d, +0xa8, 0x18, 0x4e, 0xdf, 0x8f, 0x59, 0xbc, 0x87, +0x37, 0x74, 0x1b, 0xe5, 0x96, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0x3f, +0x6e, 0xbd, 0x50, 0xcf, 0xdc, 0xec, 0xfc, 0x23, +0x35, 0x9d, 0x41, 0x6c, 0xd2, 0x6a, 0x4f, 0xb2, +0x81, 0xa9, 0xaa, 0x79, 0x02, 0x35, 0x53, 0x6e, +0xed, 0x2f, 0x0e, 0xea, 0x76, 0xa7, 0xf0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0x33, 0x27, 0x7f, 0x90, 0x50, 0x00, 0xd7, +0x7f, 0xe4, 0x96, 0x79, 0x61, 0x2c, 0xe3, 0x4c, +0x0e, 0xaf, 0xa2, 0xfe, 0xca, 0x4f, 0x18, 0x86, +0x44, 0xe0, 0x4a, 0x37, 0x08, 0x79, 0x67, 0xbb, +0xf7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0x07, 0x14, 0x3c, 0x22, 0x5b, +0x16, 0x21, 0x89, 0xe5, 0x04, 0x5d, 0xe4, 0x92, +0x8e, 0x97, 0x1b, 0x06, 0xcf, 0x91, 0x19, 0x31, +0x68, 0x51, 0xf5, 0x6e, 0x69, 0x19, 0xfa, 0xcf, +0x6a, 0x5a, 0x6d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x60, 0x44, 0xed, +0xd5, 0x7c, 0x67, 0x75, 0x33, 0xf5, 0xe2, 0xaa, +0xae, 0xf6, 0x92, 0x67, 0x68, 0x9a, 0xc2, 0xcb, +0xdb, 0x96, 0xab, 0xf4, 0x6f, 0x0b, 0x54, 0xbb, +0xfb, 0xbe, 0xb0, 0xb6, 0xdb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x0a, +0x86, 0x4b, 0xbb, 0x51, 0xc3, 0xe0, 0x91, 0x23, +0x48, 0x1c, 0x2b, 0x0a, 0x0b, 0x97, 0x3e, 0x4d, +0xfb, 0xb3, 0x27, 0xba, 0x09, 0x6a, 0xa7, 0x26, +0xb1, 0xf7, 0xc9, 0x0d, 0x5b, 0x76, 0xb3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0x69, 0x46, 0x9b, 0x17, 0x93, 0xb9, 0x24, +0x2d, 0x47, 0x0b, 0xea, 0x58, 0xc7, 0x11, 0x17, +0x1c, 0xf2, 0x42, 0x6f, 0x2e, 0x89, 0x6f, 0x03, +0x57, 0xae, 0x07, 0xea, 0xb7, 0xd2, 0x9d, 0x8c, +0x54, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0x80, 0xcd, 0xa8, 0x5b, 0x63, +0x78, 0xf8, 0xb4, 0xf4, 0x3c, 0xe1, 0x76, 0xa5, +0x73, 0xba, 0x83, 0x29, 0x6e, 0x3a, 0xdb, 0x23, +0xb8, 0x38, 0xbc, 0x02, 0xdf, 0xda, 0x1d, 0x96, +0x39, 0x9b, 0xb0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0x88, 0xc4, 0x13, +0x11, 0x7e, 0x0a, 0x5e, 0x33, 0x73, 0x93, 0x41, +0xee, 0x5b, 0x30, 0xbb, 0xfd, 0x07, 0xe3, 0x23, +0xcb, 0xd2, 0x8a, 0xc0, 0x10, 0x3c, 0x46, 0xcb, +0x71, 0x80, 0x0d, 0xeb, 0x34, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x54, +0x75, 0x96, 0x9f, 0xb8, 0xc4, 0xb2, 0x09, 0x69, +0x65, 0xed, 0x50, 0xb1, 0x24, 0x08, 0x10, 0x89, +0x6f, 0xbd, 0x65, 0xb4, 0x8e, 0x31, 0x8f, 0xe9, +0x7b, 0xa5, 0x2b, 0xe5, 0x3e, 0xc0, 0xb0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0x14, 0xa5, 0x7f, 0x10, 0x2f, 0xc0, 0xa2, +0x11, 0xe1, 0xcd, 0x53, 0x4b, 0xd0, 0xf5, 0x42, +0xed, 0x7a, 0x61, 0x9d, 0x7c, 0x0f, 0xe0, 0xf9, +0x3b, 0x2a, 0xd4, 0xe1, 0x39, 0x09, 0x94, 0xc8, +0x82, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0xd4, 0x7d, 0xf3, 0x6b, 0xd7, +0xd7, 0xaf, 0x80, 0x5c, 0x59, 0xee, 0xe8, 0x06, +0xbe, 0x13, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x3c, 0x04, 0xc8, 0x04, +0xb5, 0x1c, 0xd6, 0xf1, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0x83, 0x8e, 0xaa, +0x0d, 0x04, 0x6f, 0x92, 0xea, 0xf7, 0xf8, 0xb8, +0x50, 0xbf, 0x8a, 0xe9, 0x94, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xc0, +0xf8, 0x85, 0x9f, 0x36, 0xd0, 0x42, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0xc6, +0xc1, 0x19, 0x5b, 0x0f, 0x53, 0x0e, 0x8b, 0xe6, +0x43, 0x19, 0xa4, 0xc8, 0x83, 0xec, 0x4d, 0x15, +0x01, 0x16, 0x88, 0x3d, 0xa8, 0x83, 0x8b, 0xce, +0xef, 0x0d, 0xdf, 0x9b, 0x9c, 0xcd, 0x2a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0xab, 0x65, 0x41, 0x2a, 0x9b, 0x42, 0xea, +0x74, 0xc2, 0xb8, 0x8a, 0xc0, 0xe3, 0x6f, 0x73, +0x4d, 0x14, 0x62, 0x0d, 0xe2, 0xc0, 0x78, 0x32, +0x96, 0x30, 0xf3, 0xc9, 0x38, 0x96, 0xa8, 0x56, +0x7a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0x0c, 0x44, 0x4f, 0x49, 0x30, +0x02, 0x64, 0x3c, 0x01, 0xd4, 0x3b, 0x3d, 0xf7, +0x10, 0x27, 0xe1, 0xec, 0xb4, 0x69, 0x8c, 0x2b, +0x3c, 0xba, 0x82, 0xb6, 0x68, 0x54, 0x85, 0xd4, +0x3b, 0x09, 0x97, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0xfb, 0x2d, 0x04, +0xe8, 0x44, 0xac, 0x1d, 0xfc, 0xa5, 0xf5, 0xab, +0x4d, 0x85, 0x61, 0x78, 0xc2, 0x29, 0x32, 0xe6, +0xd7, 0x98, 0xf5, 0xdb, 0xcb, 0x63, 0x4b, 0xd1, +0x15, 0x01, 0x4d, 0xc4, 0x6b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0xf5, +0x2d, 0x2e, 0x38, 0x4e, 0x5e, 0x99, 0x11, 0x33, +0xb4, 0x11, 0xc7, 0x92, 0x8c, 0xbf, 0xc3, 0x93, +0xc6, 0x98, 0x75, 0xaf, 0xa9, 0xa3, 0x80, 0x00, +0x1a, 0x2d, 0x06, 0x43, 0x70, 0x86, 0xce, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0xd6, 0xc7, 0xf5, 0x37, 0x41, 0x0d, 0xfa, +0xdd, 0x0c, 0x7c, 0x0d, 0x7b, 0x7e, 0x82, 0x2a, +0xa8, 0xd5, 0x01, 0x88, 0xa9, 0xc0, 0xad, 0x18, +0xc8, 0xeb, 0x5d, 0x3f, 0x29, 0xea, 0x70, 0x3e, +0xe0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0xd7, 0x80, 0xbf, 0x09, 0x12, +0xb9, 0xc4, 0x82, 0x33, 0x54, 0xf3, 0x92, 0x43, +0x28, 0x2e, 0xca, 0xe5, 0x25, 0xf8, 0xdc, 0x00, +0x37, 0x0f, 0x2a, 0x22, 0xa5, 0xbe, 0xea, 0x6d, +0xcc, 0x6d, 0x8d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x55, 0x3b, 0x01, +0xab, 0x6b, 0xc9, 0x61, 0x42, 0xca, 0x3e, 0x19, +0x73, 0xeb, 0xf1, 0x2b, 0xaa, 0xb1, 0x7a, 0x80, +0x96, 0xaa, 0x6d, 0x76, 0x0a, 0x9f, 0x45, 0xa7, +0xf2, 0xa1, 0x79, 0x71, 0x68, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0xaf, +0x19, 0xec, 0x50, 0xb7, 0xf8, 0x01, 0x4f, 0x28, +0xd3, 0x22, 0xec, 0xd1, 0x91, 0xd6, 0x72, 0xc7, +0xee, 0x73, 0x24, 0x59, 0x34, 0xfa, 0xe8, 0x3f, +0x65, 0x17, 0xdc, 0x86, 0x34, 0x88, 0x28, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0x18, 0xcf, 0xa8, 0x08, 0x51, 0x0b, 0x98, +0xe8, 0x23, 0xef, 0x98, 0xf9, 0x9f, 0x7c, 0x6d, +0xd1, 0xe1, 0xe8, 0xff, 0x67, 0xca, 0x02, 0x97, +0xb0, 0xc4, 0x98, 0x69, 0x46, 0x59, 0x42, 0x95, +0x10, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0xa8, 0xec, 0x6a, 0xf5, 0x4e, +0x71, 0x7a, 0xee, 0x96, 0xed, 0x44, 0xb6, 0x8f, +0x59, 0x49, 0x2a, 0x65, 0x34, 0x33, 0x97, 0x37, +0xc5, 0xb7, 0xf4, 0x78, 0x43, 0x33, 0x7c, 0x18, +0xa1, 0x10, 0x57, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0x60, 0x6d, 0x66, +0xe3, 0xa2, 0xc3, 0x16, 0xb2, 0x49, 0x75, 0x0e, +0x30, 0x56, 0x28, 0xd9, 0x0a, 0x08, 0xad, 0x88, +0xdb, 0xf2, 0x95, 0xcc, 0x0f, 0xe1, 0x51, 0xe3, +0xcd, 0x47, 0xeb, 0x79, 0xab, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0x9d, +0x28, 0xa5, 0x6d, 0x1c, 0x03, 0x5f, 0x97, 0xa5, +0x6a, 0x92, 0xa3, 0x98, 0x3d, 0x02, 0x2b, 0x79, +0xa3, 0x8c, 0x7e, 0x7b, 0x1b, 0x4f, 0xec, 0x27, +0x52, 0x00, 0x27, 0x0b, 0x2f, 0x67, 0x41, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0xd5, 0x45, 0x07, 0xdc, 0x2e, 0x87, 0x53, +0x08, 0x0e, 0x9f, 0x16, 0x70, 0xa2, 0x53, 0xe8, +0xbf, 0xe5, 0x9b, 0xbf, 0x16, 0x00, 0x8a, 0x28, +0x54, 0xfc, 0x3a, 0x14, 0xb1, 0xda, 0x41, 0xdd, +0x46, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0x10, 0xda, 0x13, 0x52, 0x47, +0x2e, 0xdf, 0x4b, 0x2f, 0x5a, 0x70, 0x79, 0x7a, +0xaf, 0xea, 0x94, 0x62, 0xb7, 0x89, 0xb0, 0xcd, +0x70, 0xad, 0x07, 0xe5, 0xf5, 0xe6, 0x3f, 0xec, +0xc6, 0xf5, 0xa5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0x4e, 0x22, 0x4c, +0xed, 0x5f, 0xd7, 0xcd, 0x4e, 0xfb, 0x49, 0x51, +0x94, 0xb4, 0x1b, 0x3b, 0x2c, 0xca, 0x09, 0xcf, +0x9d, 0x6d, 0xac, 0xec, 0x5f, 0xd0, 0x71, 0x2c, +0x1a, 0x1e, 0xee, 0xab, 0x22, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x50, +0xe1, 0x4b, 0xc8, 0x38, 0x1b, 0xed, 0xd8, 0x57, +0xbe, 0x4a, 0xba, 0x13, 0xbf, 0xac, 0xe0, 0x88, +0xfd, 0x9c, 0x5a, 0x28, 0x2c, 0xdb, 0x93, 0x3a, +0x75, 0xa9, 0xa3, 0x6d, 0x91, 0x19, 0xed, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0xc5, 0x93, 0x28, 0xac, 0x72, 0x1e, 0x10, +0xdc, 0x6b, 0x5f, 0xcd, 0x2a, 0x9f, 0x68, 0xd2, +0xad, 0xcb, 0xd9, 0x56, 0x73, 0xcb, 0xa0, 0x2f, +0x0f, 0x05, 0x7a, 0xca, 0xcf, 0xac, 0xf6, 0x52, +0x0c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0xb8, 0xda, 0xdc, 0xed, 0x03, +0xaa, 0x27, 0x9d, 0x2a, 0x60, 0x79, 0xbb, 0xba, +0x36, 0x45, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x46, 0x0f, 0x70, 0xf3, +0xd7, 0x62, 0x68, 0x50, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0xf6, 0x8e, 0x5d, +0x03, 0xe1, 0xf0, 0x64, 0x71, 0x6b, 0xe2, 0x41, +0x21, 0xdc, 0x39, 0x32, 0xfe, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0xb2, +0x69, 0xea, 0xa8, 0x0f, 0xe0, 0x13, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0x3a, +0xa9, 0x4a, 0x3c, 0x11, 0xb8, 0xc4, 0x50, 0xf0, +0xd7, 0xb2, 0x5e, 0x41, 0xc0, 0x9f, 0x0d, 0x27, +0x0a, 0x31, 0xcb, 0x47, 0xa4, 0x25, 0xf5, 0x39, +0x76, 0xf2, 0x82, 0xea, 0x9b, 0x62, 0x27, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0xfb, 0x0c, 0xd3, 0x34, 0xd5, 0x61, 0x6e, +0x42, 0xf8, 0x89, 0xed, 0x7b, 0x63, 0xf9, 0x83, +0x00, 0x85, 0x5d, 0x4f, 0x35, 0x92, 0xa4, 0xb9, +0x65, 0x25, 0x02, 0x65, 0x25, 0x68, 0x0a, 0x7d, +0x65, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x40, 0xc2, 0xa0, 0x9c, 0xbf, +0xe4, 0x51, 0xf3, 0xdc, 0x4d, 0x8b, 0x92, 0xc3, +0x30, 0x54, 0x28, 0x57, 0x85, 0x39, 0x29, 0x73, +0xf5, 0x55, 0x26, 0x34, 0x1c, 0x5e, 0x48, 0xe8, +0xc2, 0xd4, 0x60, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0x3c, 0xaa, 0xf5, +0xe6, 0x5e, 0x40, 0x9f, 0x3b, 0x9b, 0xd0, 0xcb, +0x63, 0x20, 0xff, 0x04, 0xae, 0x55, 0x6f, 0x2e, +0x47, 0x2d, 0xbf, 0xfd, 0x78, 0xb8, 0xb9, 0x62, +0xae, 0xe6, 0x28, 0x93, 0xef, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0x81, +0x40, 0x5a, 0x7e, 0xe3, 0x73, 0x5b, 0x0b, 0xa4, +0x9d, 0x17, 0xfe, 0x52, 0x3c, 0x00, 0x3f, 0xb2, +0xdc, 0x87, 0xb7, 0x26, 0x59, 0x70, 0x2c, 0x13, +0xc1, 0x4b, 0xf5, 0xe9, 0x8f, 0x25, 0x3c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0x18, 0x72, 0x79, 0x92, 0x98, 0xe0, 0xe8, +0xe4, 0x35, 0xa3, 0x93, 0x01, 0x1e, 0x6c, 0x54, +0x42, 0xf8, 0x07, 0x11, 0xaa, 0x10, 0xa4, 0xa0, +0xdb, 0xe4, 0x91, 0xdc, 0x12, 0xbf, 0x35, 0xca, +0xc8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0x42, 0xb0, 0xa9, 0x80, 0x75, +0x17, 0x6f, 0x1a, 0x3b, 0x26, 0x7b, 0xff, 0x3d, +0x83, 0x69, 0xb3, 0xad, 0x83, 0x30, 0x5a, 0x2f, +0x44, 0xe8, 0x27, 0xbf, 0x55, 0xb7, 0x5b, 0x15, +0xf6, 0x8d, 0x83, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0x70, 0xd3, 0x97, +0x1b, 0x38, 0x78, 0x85, 0x8e, 0xff, 0xad, 0xd7, +0x9f, 0xbb, 0x53, 0xcf, 0x61, 0xc6, 0x2d, 0x1c, +0x46, 0xab, 0xc6, 0x71, 0x3a, 0x1c, 0xb8, 0x99, +0x4a, 0x0e, 0xea, 0x98, 0xfd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0x68, +0xec, 0x11, 0x25, 0x1d, 0x22, 0x3d, 0x3d, 0xe2, +0xf5, 0xe6, 0x18, 0xe8, 0xbd, 0xa1, 0xc3, 0xcd, +0x2b, 0x74, 0xfc, 0x41, 0xc0, 0x34, 0x2f, 0xf4, +0xbc, 0xcc, 0x14, 0x5f, 0x65, 0xc2, 0xcf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0x6e, 0xbf, 0x9a, 0x34, 0x38, 0xe2, 0x92, +0xd5, 0x62, 0x3f, 0x02, 0xc2, 0x54, 0x39, 0x89, +0x77, 0xfb, 0x53, 0xfb, 0x8b, 0xe1, 0xd9, 0xd5, +0x4a, 0x63, 0xb7, 0x27, 0x1a, 0x93, 0x06, 0xf9, +0x7b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0x0b, 0xd0, 0xd5, 0xb7, 0x97, +0x02, 0xb5, 0x06, 0x0a, 0x30, 0x41, 0xde, 0x9c, +0x67, 0x0b, 0x35, 0x86, 0x53, 0xc3, 0x8c, 0xa2, +0xf1, 0x33, 0x16, 0xe4, 0xdd, 0x83, 0x28, 0x1a, +0x7d, 0xef, 0x59, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0xf4, 0xb6, 0x24, +0xff, 0xa6, 0x6e, 0x82, 0x54, 0x27, 0x1c, 0xeb, +0x55, 0x21, 0x66, 0x26, 0x0c, 0xaf, 0xdd, 0x74, +0xba, 0xc5, 0x7d, 0x5f, 0x5d, 0x6b, 0xef, 0xcc, +0xed, 0xa1, 0x8a, 0x54, 0xe7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0xf5, +0xdf, 0x05, 0x3b, 0xe3, 0x7b, 0xb1, 0xa0, 0xf5, +0xf5, 0x8e, 0x15, 0xf1, 0xb9, 0x89, 0xc5, 0x64, +0x90, 0x90, 0xaa, 0xe0, 0x3a, 0xa1, 0x54, 0x34, +0x37, 0x8e, 0xf7, 0x39, 0x3c, 0x37, 0xb2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0x2c, 0x52, 0x67, 0xa1, 0x8d, 0xef, 0x5a, +0xd1, 0xad, 0x4c, 0x3f, 0x81, 0x33, 0xdf, 0x9e, +0x02, 0xad, 0x52, 0xfa, 0x0d, 0xf1, 0x57, 0xc4, +0xd5, 0x7a, 0xf1, 0xf2, 0x23, 0x7f, 0xae, 0x18, +0x00, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0x37, 0xa0, 0xdd, 0x30, 0x49, +0xc2, 0x75, 0xe5, 0x41, 0xcd, 0x1c, 0x72, 0xcd, +0xff, 0x3c, 0xca, 0xe7, 0x37, 0xba, 0xb8, 0xb0, +0x42, 0x41, 0x22, 0xd3, 0xcc, 0xd3, 0x04, 0xfc, +0x7e, 0xbc, 0x07, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0x3b, 0x05, 0xb6, +0x01, 0xb2, 0xdd, 0xee, 0xeb, 0x82, 0xaf, 0x49, +0x21, 0xbf, 0x80, 0x97, 0x43, 0x24, 0xa1, 0xcf, +0x2d, 0xda, 0xdc, 0x49, 0xcf, 0xd9, 0x14, 0xaf, +0x85, 0x01, 0x67, 0xd4, 0xe9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0x05, +0xdf, 0x97, 0xd8, 0x21, 0xd9, 0xd8, 0x27, 0x56, +0xe2, 0x2f, 0x1f, 0xe6, 0xcb, 0xda, 0x14, 0x97, +0x63, 0x94, 0x2f, 0xff, 0x3b, 0xea, 0xa1, 0xe0, +0x9d, 0xd4, 0xb9, 0x67, 0x59, 0x0c, 0xa8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0xc7, 0x0e, 0xdf, 0xbb, 0x48, 0xf2, 0xfc, +0x04, 0x8f, 0x9b, 0x11, 0xfd, 0x28, 0xa9, 0x35, +0x39, 0xe9, 0xd0, 0x32, 0x3f, 0x72, 0xde, 0xc8, +0xdb, 0x7c, 0x9b, 0x62, 0x12, 0xf7, 0x1e, 0x66, +0x0d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0xd2, 0xeb, 0x93, 0x3d, 0xf3, +0x9a, 0x7d, 0xe5, 0x25, 0x30, 0x45, 0xf7, 0xbd, +0xf2, 0x86, 0x7b, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x29, 0xea, 0x77, 0xec, +0x4a, 0xf6, 0x69, 0x72, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0xef, 0x69, 0x09, +0xf0, 0xed, 0x49, 0x8d, 0x1f, 0xaf, 0xde, 0x49, +0xd8, 0x74, 0x93, 0x61, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xad, 0xdc, +0x58, 0x56, 0x19, 0x11, 0x66, 0x4c, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0xf5, +0xa3, 0x62, 0x28, 0xae, 0x37, 0x0a, 0x90, 0xf4, +0x62, 0xeb, 0x2c, 0xef, 0x67, 0x27, 0x2b, 0x4b, +0x9a, 0x22, 0xfc, 0xd7, 0x12, 0xa2, 0xd1, 0xcb, +0x26, 0xa2, 0x18, 0x35, 0x6d, 0x4d, 0x2f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0x5d, 0xc8, 0x0b, 0xf7, 0x6f, 0x52, 0xec, +0xe5, 0x0c, 0x81, 0x0e, 0x70, 0xef, 0x07, 0x87, +0xf9, 0x49, 0xa5, 0x9e, 0xfc, 0xcf, 0x9c, 0x95, +0x56, 0xc9, 0x96, 0x7b, 0xda, 0xeb, 0xfa, 0xd4, +0x66, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0x76, 0x87, 0xcb, 0x14, 0xfc, +0x41, 0x08, 0xd9, 0x5b, 0xfd, 0xab, 0x64, 0xfa, +0xa2, 0xaf, 0xa3, 0x38, 0x5b, 0x52, 0x36, 0x3b, +0xc0, 0xf5, 0x34, 0x9e, 0xab, 0xbd, 0xc6, 0x0c, +0x25, 0xae, 0x8f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0xbe, 0x8c, 0x88, +0x42, 0xe8, 0xeb, 0x32, 0x56, 0x47, 0x5c, 0xa5, +0x00, 0x7c, 0x31, 0xea, 0xb5, 0xbb, 0x7f, 0xa1, +0x2c, 0x74, 0x8c, 0xeb, 0xaf, 0x17, 0x04, 0x5d, +0x3f, 0x48, 0xff, 0x43, 0xf9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0xbd, +0x67, 0x8f, 0xa8, 0xca, 0xbd, 0xe6, 0x19, 0xd0, +0x52, 0xb1, 0x43, 0xd8, 0x34, 0x8b, 0x67, 0xf9, +0x2f, 0xea, 0xc4, 0x33, 0xf5, 0x37, 0x99, 0x93, +0x35, 0x2d, 0x45, 0x88, 0x9b, 0x96, 0x83, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0x80, 0x19, 0x99, 0x15, 0xdb, 0xe5, 0x65, +0x25, 0xe0, 0x60, 0x2b, 0x5a, 0x6f, 0x6a, 0x69, +0xb3, 0xc0, 0xdb, 0xc8, 0x81, 0xfa, 0xec, 0xb6, +0x18, 0xb7, 0xb8, 0x5c, 0x18, 0xf1, 0xda, 0x45, +0x50, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0x97, 0xf9, 0x63, 0x3d, 0x52, +0xf8, 0x65, 0xe7, 0x54, 0xbb, 0xbd, 0x2b, 0x44, +0x17, 0x85, 0x25, 0xe4, 0xb5, 0x16, 0xa6, 0xfb, +0xea, 0x4b, 0xfb, 0xd9, 0x63, 0x6c, 0xa6, 0xdc, +0xd1, 0xe9, 0x9a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0x57, 0x3c, 0x7f, +0x49, 0xc3, 0x65, 0x08, 0x36, 0x72, 0x2e, 0x2c, +0x0b, 0xe7, 0xa3, 0xbc, 0x6e, 0xb4, 0x84, 0x03, +0x2b, 0x09, 0xeb, 0xa4, 0xae, 0x35, 0x39, 0xc5, +0x3f, 0xaa, 0xf9, 0xa8, 0xfc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0xd1, +0xf8, 0xa9, 0x86, 0xb6, 0xd7, 0x3b, 0x2e, 0x84, +0xae, 0x4a, 0xa6, 0x35, 0x07, 0x0f, 0x97, 0x0e, +0x1f, 0xc9, 0xfd, 0x66, 0xf9, 0x79, 0xe7, 0xea, +0xe5, 0x26, 0x46, 0x1a, 0xf2, 0xdb, 0x60, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0x97, 0xf1, 0xd6, 0x3f, 0xf6, 0xb2, 0x58, +0xaa, 0x15, 0x1a, 0x06, 0x1e, 0x02, 0xa7, 0x78, +0xf0, 0x29, 0x5c, 0x96, 0x7d, 0xd0, 0xb6, 0x33, +0x02, 0x8e, 0x6d, 0x1b, 0x2d, 0x06, 0x3f, 0x3f, +0x78, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0x25, 0xd4, 0xa2, 0x1d, 0xd1, +0x41, 0x16, 0x5d, 0x76, 0xe5, 0x47, 0x92, 0xcb, +0x06, 0x68, 0xfa, 0xec, 0x7e, 0xb7, 0x92, 0x08, +0x4e, 0xef, 0x7f, 0x69, 0xd5, 0xc0, 0x7a, 0x58, +0xb4, 0x2f, 0x63, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x10, 0x5e, 0x84, +0x2e, 0x65, 0x61, 0xcf, 0xb8, 0xee, 0x02, 0xbf, +0x90, 0x7e, 0x2f, 0x37, 0x28, 0x99, 0x62, 0x3f, +0xb2, 0x44, 0xe6, 0xd5, 0x4b, 0x18, 0x3f, 0xbc, +0x1b, 0x4f, 0x73, 0x68, 0xf4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0xb3, +0xca, 0xe7, 0x37, 0x1c, 0x4b, 0x82, 0x87, 0x2b, +0x96, 0xe6, 0x92, 0xae, 0xb4, 0x37, 0x2c, 0x55, +0x27, 0xeb, 0x8d, 0xc0, 0x8e, 0xb3, 0x28, 0xc8, +0x70, 0x38, 0xed, 0xa7, 0xb7, 0xf7, 0x52, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0xc3, 0x2b, 0xf1, 0x48, 0x47, 0x5d, 0x72, +0x2a, 0xfe, 0x89, 0xc5, 0xe6, 0x24, 0x1c, 0xb1, +0x08, 0xc0, 0x27, 0x81, 0xd9, 0xb3, 0xb1, 0x6b, +0x68, 0x8d, 0x50, 0xae, 0x75, 0x63, 0x67, 0x7d, +0x46, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0x66, 0xd1, 0x1e, 0xa5, 0x49, +0xcf, 0x21, 0xfb, 0x61, 0xe2, 0xa5, 0xa1, 0x01, +0x3b, 0x8b, 0xef, 0xfb, 0x6a, 0x9b, 0x0c, 0x04, +0xdf, 0xae, 0x89, 0x8f, 0x22, 0xaa, 0x4d, 0x25, +0xfa, 0xef, 0xcd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0xab, 0xd1, 0x53, +0xb1, 0x13, 0x20, 0x6e, 0x33, 0xf7, 0x29, 0x97, +0xb7, 0xbc, 0x8f, 0xa8, 0x5e, 0x2c, 0x5d, 0xea, +0xfd, 0xe1, 0x5d, 0xa6, 0xaa, 0x91, 0xd3, 0x4f, +0x10, 0x99, 0xe6, 0xc2, 0xc1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0xc9, +0xc8, 0xe1, 0xea, 0x06, 0x1b, 0x55, 0x29, 0xbe, +0x76, 0x67, 0x78, 0xb6, 0x33, 0xc9, 0xcb, 0x1f, +0x68, 0x07, 0x82, 0x4d, 0xf7, 0xcf, 0xc7, 0x20, +0x5e, 0x26, 0xc0, 0xee, 0x37, 0xbb, 0x2d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x23, 0xc6, 0x89, 0x5d, 0x73, 0x1a, 0x77, +0x66, 0x19, 0xb4, 0x39, 0xee, 0x96, 0xb4, 0x9b, +0x3e, 0x72, 0x16, 0x1c, 0xda, 0x25, 0xa5, 0xa1, +0x44, 0x24, 0x3e, 0xd6, 0x5f, 0x3d, 0xf7, 0xb9, +0x80, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0xcb, 0xf0, 0xc8, 0xb2, 0xb8, +0x2f, 0x8d, 0x57, 0x40, 0x23, 0xff, 0x89, 0x12, +0xf0, 0xb7, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xd1, 0xcf, 0x59, 0x65, +0x1c, 0xe8, 0x13, 0x99, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0x8c, 0xc1, 0xd2, +0x8a, 0x41, 0x48, 0xd4, 0x40, 0x3d, 0x5b, 0x86, +0x17, 0x10, 0x5b, 0x2d, 0x53, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc2, 0x87, +0xc8, 0x9b, 0xa6, 0xe5, 0xed, 0x0e, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0xae, +0xa5, 0x59, 0x6c, 0x2d, 0x1b, 0x2f, 0x6a, 0x23, +0xe1, 0xc0, 0x14, 0xc2, 0x9d, 0x58, 0x33, 0x40, +0x93, 0x4c, 0x78, 0x44, 0x67, 0x09, 0x15, 0xef, +0x1f, 0x6a, 0x51, 0xe0, 0xc0, 0xff, 0x21, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0xd0, 0x85, 0x3a, 0xb5, 0x3d, 0xfa, 0x96, +0x6c, 0x71, 0x8b, 0x21, 0x27, 0x24, 0x17, 0x77, +0x7a, 0x9e, 0x60, 0x53, 0xe9, 0x23, 0x17, 0x15, +0x7e, 0xde, 0xdb, 0xee, 0xb1, 0xd9, 0x7f, 0xde, +0x08, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0x90, 0x81, 0xe9, 0x81, 0x78, +0x7c, 0x8a, 0x6e, 0x50, 0x56, 0x1d, 0xeb, 0xec, +0x8e, 0x08, 0xef, 0x85, 0xd2, 0x82, 0x60, 0xa8, +0x2e, 0x65, 0x52, 0x17, 0x80, 0x23, 0xec, 0xd9, +0x55, 0x23, 0x7c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0x13, 0xde, 0x32, +0x1d, 0x1e, 0x6e, 0x5b, 0x44, 0xa2, 0x26, 0x0c, +0xc9, 0xab, 0x50, 0xa7, 0x20, 0xab, 0xd8, 0x72, +0x38, 0x6e, 0x80, 0x51, 0x9c, 0x27, 0x44, 0xae, +0xad, 0x0c, 0x01, 0xa5, 0x39, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0x12, +0xd3, 0xc9, 0x40, 0x62, 0xbf, 0xae, 0xec, 0x99, +0x51, 0xbe, 0x23, 0x47, 0xd4, 0x6c, 0x20, 0x50, +0x56, 0xad, 0x34, 0x66, 0x06, 0xba, 0x02, 0x67, +0x11, 0xe1, 0x8c, 0xf1, 0x1f, 0xf3, 0x68, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x11, 0x0d, 0xdc, 0x1c, 0x9f, 0xeb, 0xac, +0xc9, 0xd7, 0xa0, 0x67, 0x78, 0xcf, 0x4e, 0x62, +0x87, 0xf7, 0x20, 0xa7, 0xc6, 0xca, 0x20, 0x89, +0xe8, 0x1d, 0xc6, 0xa8, 0x88, 0xa3, 0x68, 0x19, +0xa5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0x59, 0xa3, 0x02, 0x18, 0x4b, +0x5c, 0xd1, 0x92, 0x29, 0xa7, 0xb3, 0x98, 0x5d, +0xd9, 0x8e, 0xcf, 0x43, 0xc4, 0xca, 0x16, 0xd3, +0x87, 0x5f, 0x9f, 0x29, 0x22, 0x87, 0x27, 0x6e, +0xcb, 0xd7, 0x2b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0xa2, 0xf1, 0x5a, +0xec, 0x71, 0x0c, 0x45, 0x78, 0xd6, 0x03, 0xd9, +0x26, 0xce, 0x07, 0x3a, 0x29, 0xc8, 0xc3, 0xc7, +0xed, 0x23, 0xe7, 0xd1, 0x43, 0xfd, 0xa6, 0xf2, +0x35, 0x09, 0x79, 0xb6, 0x21, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0x00, +0x59, 0x29, 0xa6, 0x91, 0x78, 0x75, 0x93, 0x1c, +0xf4, 0xdb, 0x8b, 0x7e, 0xec, 0x64, 0x48, 0xb5, +0x84, 0xc4, 0x70, 0xe4, 0xf0, 0xfb, 0xb3, 0x45, +0xf5, 0x8d, 0xa2, 0x6b, 0x96, 0xd8, 0x37, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0x48, 0x09, 0x07, 0x2f, 0xb7, 0xb9, 0x8e, +0xb1, 0xa3, 0x7a, 0x31, 0xf6, 0xa1, 0xe3, 0x30, +0xe5, 0x81, 0x22, 0x51, 0xfd, 0x7f, 0x21, 0x5a, +0xc7, 0x6f, 0x45, 0xe0, 0xf4, 0x8e, 0x48, 0x02, +0x9c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0x9d, 0x63, 0xf1, 0xf1, 0x5f, +0xac, 0x36, 0xe9, 0xb7, 0xdb, 0xb1, 0x76, 0x7b, +0xec, 0x3b, 0x43, 0x67, 0x13, 0x67, 0x68, 0x12, +0xd4, 0x9c, 0x56, 0x25, 0x8e, 0xeb, 0xad, 0x98, +0x37, 0x37, 0x69, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0xdd, 0x8c, 0xc7, +0x57, 0x8a, 0xac, 0xd1, 0xe3, 0xfe, 0x6e, 0x78, +0xd0, 0xf7, 0xf1, 0xc6, 0x05, 0x5e, 0x42, 0x59, +0x4c, 0x78, 0xf9, 0x3a, 0x5b, 0x19, 0x03, 0x74, +0x19, 0x1d, 0x54, 0x62, 0xbb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0xb4, +0xb7, 0x8f, 0x7a, 0x75, 0x34, 0xd7, 0x26, 0x04, +0xd3, 0x47, 0x03, 0x63, 0x70, 0x2e, 0x80, 0xd6, +0xae, 0x45, 0x8b, 0x41, 0x99, 0x4a, 0x34, 0x95, +0xd7, 0x11, 0x28, 0x15, 0x1e, 0x26, 0x91, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0xfc, 0xda, 0xad, 0xd9, 0xd4, 0x14, 0x71, +0x63, 0x7a, 0xa7, 0x8b, 0xed, 0x7f, 0x5f, 0xa8, +0x4c, 0x9e, 0xcc, 0xbf, 0x48, 0xfd, 0xe8, 0x57, +0x27, 0x29, 0x96, 0x10, 0x60, 0x07, 0x62, 0x5f, +0x8d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0x22, 0xbe, 0x90, 0x4c, 0xbd, +0x60, 0xba, 0x6f, 0x0e, 0xde, 0xbf, 0x17, 0xf9, +0x9f, 0x2f, 0xff, 0x25, 0x74, 0x45, 0x3e, 0x50, +0xc8, 0x47, 0xc1, 0xf8, 0x21, 0xe7, 0x19, 0x24, +0xa9, 0xd8, 0x29, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x4e, 0x71, 0x04, +0xb9, 0xff, 0xac, 0xfb, 0x9a, 0x48, 0xbc, 0x4b, +0xfc, 0xe8, 0x41, 0x3f, 0xdd, 0xdd, 0x60, 0x72, +0x99, 0x09, 0xa8, 0xc9, 0xea, 0x89, 0x31, 0x64, +0x2c, 0xc7, 0xf9, 0x4c, 0xae, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0xc1, +0x8c, 0x68, 0xe7, 0x8c, 0x9f, 0xb5, 0x15, 0xb8, +0x9e, 0x99, 0x8c, 0x64, 0xbe, 0xbc, 0x09, 0x19, +0xe2, 0x45, 0x16, 0x92, 0x18, 0xfa, 0x8b, 0x72, +0x88, 0x1c, 0x5c, 0x1c, 0xfc, 0xa7, 0x89, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0x7b, 0x73, 0x04, 0x14, 0xbc, 0xaa, 0xf8, +0x59, 0x18, 0xdc, 0x81, 0x53, 0x22, 0x34, 0x75, +0x11, 0x99, 0xa6, 0x87, 0x97, 0xff, 0xa0, 0x62, +0xf1, 0x1a, 0x84, 0xf0, 0x37, 0xc4, 0x56, 0x21, +0x94, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0xa0, 0xf7, 0x71, 0x7a, 0xf4, +0x95, 0xbe, 0xea, 0xb9, 0xd1, 0x4f, 0xc8, 0xfb, +0x36, 0x8f, 0xb1, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x46, 0x88, 0x9d, 0x6b, +0x5d, 0xa8, 0xa3, 0xcb, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0x84, 0x7c, 0x9b, +0xd2, 0x9d, 0xe9, 0x02, 0x18, 0xad, 0xd0, 0x0e, +0x9b, 0xd8, 0x53, 0xee, 0x27, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x1d, +0x21, 0x56, 0x10, 0xb6, 0x4c, 0xd1, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0xf9, +0x6c, 0x0a, 0xe3, 0x66, 0x20, 0x40, 0xf3, 0xe7, +0x41, 0x52, 0x8f, 0xf2, 0x27, 0x28, 0xf4, 0x59, +0x66, 0x6a, 0x75, 0x44, 0x6e, 0xfe, 0x91, 0x0b, +0x01, 0xf5, 0xe5, 0x40, 0x83, 0x1f, 0x00, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0x06, 0xf1, 0xb1, 0x0f, 0x74, 0x37, 0xc9, +0x25, 0xdc, 0x90, 0x28, 0x86, 0x2b, 0xc2, 0xa0, +0x00, 0xce, 0x9d, 0x17, 0x15, 0x93, 0x9e, 0x23, +0x1c, 0xbc, 0x4f, 0x8a, 0x85, 0x4b, 0x30, 0x6e, +0x5f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0xd1, 0x39, 0x89, 0x50, 0xdb, +0xa5, 0x83, 0x51, 0xf5, 0xc3, 0xbb, 0x55, 0x78, +0xe4, 0xea, 0x63, 0x44, 0x50, 0x6d, 0x33, 0x91, +0xf7, 0x4e, 0x22, 0xef, 0x16, 0xda, 0x70, 0x21, +0xf7, 0x65, 0xe6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0xb8, 0x42, 0x1e, +0x4a, 0x8d, 0x34, 0x4e, 0x25, 0xb5, 0x9d, 0x75, +0xd4, 0x39, 0x08, 0x9e, 0x9c, 0xf8, 0x6c, 0xfe, +0x2c, 0x02, 0x23, 0x2f, 0xca, 0x2c, 0xfb, 0x63, +0xd9, 0x0c, 0xed, 0x2c, 0x48, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0xa7, +0x70, 0x03, 0x63, 0x07, 0x7d, 0xb0, 0xa7, 0x72, +0x12, 0x6e, 0x93, 0xa9, 0x8f, 0xfd, 0xd5, 0x1e, +0x65, 0x97, 0x65, 0x8f, 0x3d, 0xcb, 0xdc, 0x47, +0xff, 0x91, 0x5a, 0xa3, 0xb9, 0xa6, 0xf1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0x4b, 0x63, 0xac, 0xb6, 0x03, 0x9d, 0xd0, +0x19, 0xa0, 0xed, 0xa6, 0xe8, 0x32, 0x53, 0xe3, +0x5e, 0x4c, 0x0e, 0x1f, 0xb4, 0x55, 0x41, 0xa9, +0x69, 0x49, 0x39, 0xdf, 0x87, 0xb6, 0x67, 0x60, +0x14, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0x76, 0x42, 0x0c, 0xe3, 0x25, +0x6a, 0x0c, 0x3d, 0xad, 0xa3, 0x4e, 0x8a, 0x1d, +0xa0, 0xc0, 0xa4, 0xe9, 0x39, 0x7a, 0xb4, 0xd3, +0xfb, 0x85, 0xad, 0xfa, 0x9d, 0xfd, 0x05, 0x42, +0xb7, 0xbd, 0xe5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0xe9, 0xb2, 0x41, +0x9f, 0x06, 0x6e, 0x70, 0x61, 0xa3, 0xde, 0x69, +0xa0, 0x84, 0xa9, 0x67, 0xc1, 0x59, 0xc7, 0x1d, +0xe4, 0xb3, 0xd6, 0xd4, 0xf2, 0x80, 0xcb, 0xb7, +0x50, 0xc3, 0x75, 0x6d, 0x03, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0x42, +0x50, 0x49, 0xd9, 0xf6, 0xc8, 0xa8, 0x0f, 0x0c, +0x80, 0x5a, 0xba, 0xc3, 0x0f, 0x56, 0xdb, 0x64, +0x68, 0x8a, 0x5a, 0xcd, 0xa5, 0xef, 0x15, 0x3a, +0xd8, 0xb9, 0xfa, 0xd4, 0xe9, 0xf2, 0x67, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0xb9, 0x0b, 0x1e, 0x6f, 0x67, 0x8a, 0x64, +0xd8, 0xcc, 0xfb, 0x64, 0x3a, 0xd1, 0x3c, 0xf8, +0x7c, 0xbf, 0xc2, 0x3a, 0xa5, 0x71, 0x9c, 0x7a, +0xf5, 0x1c, 0x8a, 0x0b, 0xab, 0x6a, 0x21, 0x6f, +0x61, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0xa1, 0x5b, 0x7b, 0xfb, 0x07, +0xa3, 0x69, 0x99, 0x42, 0xc7, 0x2c, 0x8f, 0xab, +0xf7, 0x76, 0xc9, 0x3c, 0x56, 0x3c, 0x27, 0x55, +0xf8, 0x9a, 0x00, 0xbb, 0x51, 0x38, 0x81, 0x5a, +0x78, 0xd5, 0xd0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0x4a, 0x14, 0xfa, +0x88, 0xc2, 0xd7, 0x9d, 0xad, 0x66, 0xb4, 0x24, +0xf8, 0xf2, 0x87, 0xc4, 0xa4, 0x7b, 0x84, 0x41, +0x15, 0xce, 0x29, 0xd5, 0x4b, 0x4f, 0x95, 0x98, +0xeb, 0x5a, 0x2e, 0x49, 0x76, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0x3b, +0xba, 0xbe, 0x4d, 0x65, 0x49, 0xdf, 0x14, 0xf6, +0xcd, 0xd6, 0x1a, 0xfc, 0x96, 0xcc, 0x96, 0x29, +0xfb, 0xd9, 0xc8, 0xa4, 0xe4, 0x92, 0x86, 0x43, +0xfb, 0xf6, 0x1a, 0x4d, 0xbc, 0x30, 0x09, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0xda, 0xae, 0xa8, 0x74, 0x2c, 0xa2, 0x2e, +0x24, 0x50, 0x92, 0xa2, 0xb5, 0x2d, 0x14, 0x53, +0x00, 0x74, 0x90, 0x4d, 0xed, 0xdc, 0xd3, 0x1e, +0xa5, 0xe0, 0xbc, 0x8b, 0xe0, 0xef, 0x26, 0xc1, +0xca, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0x07, 0x0f, 0x7f, 0x58, 0xe0, +0x42, 0xcb, 0xe2, 0xf0, 0xd2, 0x33, 0x0e, 0x75, +0x4f, 0xb4, 0x0f, 0x96, 0x11, 0xd6, 0xc7, 0x03, +0x7e, 0xb2, 0x00, 0x78, 0x1b, 0x6f, 0x69, 0x21, +0xe9, 0xf4, 0xf1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0xab, 0x68, 0x74, +0x1b, 0x28, 0x66, 0x99, 0x3d, 0xdd, 0x77, 0x0a, +0x7a, 0xbf, 0x36, 0x3e, 0xcf, 0xa1, 0xa8, 0x36, +0x5d, 0x4c, 0x57, 0x8a, 0x36, 0xe7, 0xc6, 0xa7, +0xc3, 0x74, 0xc2, 0x74, 0x99, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0x24, +0x55, 0xfd, 0x29, 0xe9, 0x07, 0xef, 0x21, 0xe7, +0x5d, 0xae, 0x41, 0x35, 0x1e, 0x06, 0x6a, 0x80, +0x5f, 0x63, 0xd1, 0x75, 0x59, 0xf3, 0x44, 0xfe, +0xb1, 0xa5, 0x18, 0x72, 0x42, 0xaa, 0x55, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0x7d, 0x20, 0xf1, 0xf7, 0xf5, 0x2b, 0x8a, +0xbb, 0x6b, 0x2f, 0x42, 0xb7, 0x26, 0x28, 0x6b, +0xb8, 0x31, 0x87, 0xac, 0xad, 0xd6, 0xf9, 0x22, +0x81, 0xf6, 0x62, 0xfb, 0xba, 0x7f, 0x1e, 0x9d, +0xf4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0xa6, 0xcf, 0x4f, 0xa6, 0x20, +0xb9, 0xa2, 0x1c, 0x52, 0x92, 0x56, 0xb4, 0xd8, +0x12, 0x70, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x64, 0x69, 0xf8, 0xdb, +0xcc, 0xd8, 0x9d, 0x62, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0x26, 0xe4, 0xfb, +0x7b, 0xc0, 0x19, 0x71, 0xc7, 0x89, 0xa6, 0x4f, +0x7f, 0x1d, 0xe8, 0xd7, 0xac, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0xe3, +0x9d, 0x1e, 0xdd, 0x9c, 0xac, 0x35, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0x7f, +0xd7, 0xcb, 0x16, 0xe6, 0xb0, 0xc6, 0xf9, 0x6b, +0x56, 0x2f, 0xfb, 0x73, 0x19, 0x27, 0xa4, 0x75, +0x03, 0xb9, 0xb0, 0xcd, 0x39, 0x44, 0x89, 0xa4, +0x3d, 0xc8, 0xea, 0x52, 0xb9, 0xb3, 0xf1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0x29, 0x0b, 0xab, 0xf3, 0x20, 0x88, 0xb1, +0xea, 0xfc, 0xb4, 0xee, 0x01, 0x14, 0x99, 0x94, +0xc3, 0x67, 0x2c, 0x2c, 0x96, 0x69, 0x51, 0xa3, +0xae, 0x29, 0xb3, 0xae, 0x29, 0x83, 0x19, 0x52, +0x24, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0x10, 0x07, 0x6f, 0x60, 0xf5, +0x64, 0x45, 0x93, 0x23, 0x4f, 0x9b, 0xb1, 0xe7, +0x2a, 0x3b, 0x7e, 0x7c, 0xfa, 0xc4, 0x51, 0x7d, +0xaf, 0x41, 0xf5, 0x1b, 0x7b, 0x55, 0x88, 0x4a, +0x4a, 0xd8, 0x79, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0x23, 0x65, 0xf5, +0xdf, 0xea, 0x85, 0xaa, 0x57, 0xd3, 0x64, 0x27, +0x6c, 0x08, 0x96, 0xa2, 0xa4, 0xd3, 0x5b, 0x72, +0xb1, 0xc0, 0x9e, 0xc6, 0x75, 0xfb, 0x5b, 0x01, +0x92, 0xde, 0x8f, 0x43, 0x97, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0xc7, +0x67, 0x4b, 0x22, 0x66, 0xd3, 0xfb, 0xce, 0x40, +0xdd, 0xe5, 0x16, 0x46, 0xff, 0xf1, 0xec, 0x44, +0xc1, 0x75, 0x60, 0x8e, 0x0b, 0xe2, 0x5c, 0xf6, +0x30, 0x32, 0x1a, 0x16, 0xa3, 0x66, 0xd9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0xa0, 0x85, 0x6f, 0x05, 0x1d, 0xd9, 0xb9, +0x7a, 0x71, 0xb1, 0x62, 0x52, 0x56, 0x06, 0xfd, +0x22, 0x43, 0xe5, 0x0a, 0xe2, 0xf2, 0xc3, 0x62, +0xe8, 0x5e, 0x19, 0x49, 0x58, 0x41, 0x46, 0x35, +0x2b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0x12, 0xfd, 0xe9, 0x39, 0x11, +0xa8, 0xa4, 0x7d, 0x46, 0x5a, 0xd2, 0x73, 0x10, +0x41, 0x4a, 0xe2, 0xac, 0x4c, 0x1f, 0xe7, 0xcd, +0xe2, 0xd6, 0x7d, 0x86, 0x01, 0x4f, 0x79, 0xf7, +0x46, 0x27, 0x24, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0xd6, 0x29, 0xd1, +0x5d, 0xfd, 0x1f, 0xec, 0xa7, 0x96, 0x08, 0x05, +0x67, 0xa2, 0xf4, 0x6b, 0x1e, 0x94, 0x45, 0xfe, +0xe4, 0xc5, 0xcf, 0x10, 0x50, 0xf7, 0x41, 0xfd, +0xc0, 0x9a, 0x12, 0xfd, 0xad, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x62, +0x9d, 0x11, 0x67, 0xb4, 0xe0, 0x8e, 0xe1, 0x06, +0x3b, 0x56, 0x17, 0x7c, 0xf6, 0xc0, 0xd9, 0xf9, +0xc3, 0x6c, 0x22, 0xb3, 0x57, 0xed, 0x63, 0xdd, +0x7e, 0x03, 0x4b, 0x90, 0x35, 0x90, 0x1f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0x5c, 0x1d, 0xfe, 0x52, 0xe6, 0x6e, 0x39, +0x53, 0xfe, 0x7a, 0x1f, 0xfd, 0x24, 0xd8, 0xca, +0x2e, 0x15, 0x33, 0xaa, 0xde, 0xca, 0x69, 0xd9, +0x84, 0xb1, 0xcb, 0x00, 0x5e, 0x7a, 0xe5, 0x96, +0xa0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0x56, 0xaf, 0xba, 0x19, 0xfa, +0x8b, 0xfd, 0x8f, 0x7a, 0xf0, 0x90, 0xd6, 0xfd, +0x5c, 0x8a, 0x75, 0xa5, 0x6e, 0x50, 0xd9, 0xf9, +0x31, 0x22, 0xe7, 0xff, 0xda, 0x4b, 0x7b, 0xdd, +0xcd, 0xa5, 0xa5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0xb9, 0x0e, 0xf7, +0x71, 0x49, 0xf5, 0x60, 0x94, 0x02, 0xe7, 0xb6, +0x7d, 0xdb, 0x4d, 0x41, 0x56, 0xc5, 0x22, 0xfc, +0x70, 0xd4, 0xf8, 0x7d, 0x3a, 0x92, 0x60, 0x0f, +0x73, 0x6e, 0xc0, 0xc7, 0xdb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0x37, +0xa8, 0x18, 0x82, 0x4f, 0x5a, 0x31, 0xbf, 0xa4, +0x30, 0x7c, 0x8c, 0x37, 0x44, 0xe1, 0xc6, 0x50, +0x97, 0x6b, 0x4d, 0x47, 0x66, 0x6a, 0x4a, 0x76, +0x29, 0x9e, 0xae, 0x8d, 0xb4, 0xb2, 0x8e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0x49, 0x83, 0xe3, 0x6d, 0x44, 0x6c, 0x1c, +0x56, 0x25, 0x41, 0x3e, 0xe8, 0x80, 0x28, 0x24, +0x83, 0xc6, 0x0d, 0x0f, 0xb1, 0xe9, 0x36, 0x72, +0xc9, 0x69, 0x26, 0x39, 0x03, 0x7f, 0x40, 0xd7, +0xe8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0x6a, 0x84, 0xb5, 0x6b, 0x61, +0x71, 0xf4, 0x5e, 0x86, 0x7a, 0x68, 0x8e, 0x8b, +0x4f, 0xb8, 0x30, 0xa5, 0xf9, 0x74, 0x7d, 0xae, +0xbd, 0x27, 0xe3, 0xa9, 0x56, 0x8a, 0x9b, 0x00, +0xaa, 0x34, 0xdc, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x93, 0xbe, 0x52, +0xfc, 0x06, 0x6b, 0xd1, 0xf4, 0xa4, 0x63, 0x6f, +0x73, 0xb4, 0xa3, 0x3a, 0x81, 0x5d, 0x11, 0xe8, +0x78, 0x76, 0x05, 0x6f, 0x3e, 0x44, 0x65, 0xda, +0xa7, 0x0f, 0xd3, 0x37, 0x2f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0xd0, +0x11, 0xa3, 0xb9, 0xa1, 0xc2, 0x04, 0x4d, 0xff, +0x5a, 0x3f, 0x1b, 0xf9, 0x36, 0x61, 0xf6, 0x06, +0xb0, 0x27, 0x1c, 0x69, 0xdb, 0xda, 0x6f, 0x93, +0xd4, 0x69, 0x76, 0x11, 0x76, 0x08, 0x71, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0xb0, 0x48, 0xf7, 0x3c, 0xa7, 0xba, 0x71, +0x99, 0xf6, 0x85, 0xe6, 0x41, 0x2c, 0x53, 0x04, +0x2e, 0x4d, 0xf9, 0xb5, 0xf7, 0x80, 0x8c, 0x7a, +0xb4, 0xaa, 0x56, 0x4c, 0x3c, 0x79, 0xe0, 0xce, +0x06, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0xbc, 0x43, 0x68, 0xa8, 0x44, +0x32, 0x4f, 0x1b, 0x1a, 0x1d, 0x97, 0xc8, 0x2b, +0x5b, 0xa2, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x61, 0xb1, 0x56, 0xed, +0x0c, 0xb8, 0x95, 0x1d, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x1b, 0xa4, 0x72, +0xc1, 0x8f, 0x6c, 0x97, 0xc9, 0x5c, 0x26, 0xd4, +0x45, 0xd9, 0x5c, 0x03, 0x34, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0xc6, +0xcc, 0x43, 0x5d, 0x49, 0x4b, 0xcb, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0x90, +0x95, 0x80, 0x97, 0xc0, 0xc7, 0x1c, 0x8c, 0xf2, +0xbc, 0xc7, 0x58, 0x98, 0xb7, 0xc9, 0x11, 0x2b, +0x6c, 0xb4, 0xfb, 0x45, 0x87, 0xc9, 0x95, 0x25, +0xdc, 0xdb, 0x7c, 0x59, 0xd8, 0x3f, 0x12, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0x14, 0x97, 0xae, 0x01, 0x83, 0x9a, 0x9e, +0x7e, 0x49, 0x9e, 0xf5, 0x32, 0xa6, 0xe9, 0x78, +0xba, 0x6f, 0xc3, 0xe7, 0x09, 0x97, 0x36, 0xe2, +0x0c, 0xca, 0xfb, 0xc0, 0x42, 0x47, 0xcb, 0xb6, +0x8d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0x62, 0x2b, 0x28, 0xe3, 0x21, +0xc5, 0xf3, 0x7b, 0xee, 0xe2, 0x90, 0x28, 0x7c, +0x71, 0x19, 0xb6, 0x8c, 0x88, 0x81, 0xda, 0x56, +0xbc, 0x3f, 0x0b, 0x45, 0xfd, 0x8f, 0x34, 0xda, +0x08, 0x50, 0x8d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0x0e, 0xae, 0x4e, +0x92, 0x9e, 0xa0, 0x8a, 0x23, 0x63, 0xc7, 0xf8, +0x86, 0xc3, 0x8e, 0xc1, 0x96, 0x9c, 0x61, 0xd7, +0x84, 0x5e, 0xf0, 0x0a, 0xdf, 0x04, 0x67, 0x10, +0x1d, 0xdb, 0x33, 0x72, 0xd3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0xc5, +0xb7, 0x59, 0x03, 0xe7, 0x71, 0x8d, 0xcd, 0x9c, +0x17, 0xc4, 0x79, 0x59, 0xc9, 0x00, 0x2f, 0xc5, +0x1c, 0x8f, 0xe6, 0x35, 0x26, 0x05, 0x82, 0xfc, +0xd5, 0x00, 0x21, 0x50, 0xbf, 0x02, 0x93, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0x62, 0x5e, 0x89, 0xcc, 0xcf, 0xc5, 0xb2, +0x31, 0x12, 0x73, 0xea, 0x47, 0xa8, 0x5c, 0xc3, +0x01, 0x5f, 0xcb, 0x74, 0x78, 0x14, 0xca, 0x86, +0x83, 0xe6, 0x1f, 0x7d, 0x75, 0xce, 0xdd, 0xf2, +0x0e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0x93, 0x7d, 0x87, 0x67, 0x02, +0x31, 0x4b, 0xee, 0x60, 0x4f, 0xca, 0x3d, 0x82, +0xdb, 0xae, 0x08, 0x1e, 0xb4, 0xea, 0x72, 0xf0, +0xc8, 0x38, 0x4b, 0xc9, 0x93, 0xf1, 0xf5, 0x7f, +0x3c, 0x8a, 0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0x8c, 0xdc, 0x88, +0x32, 0xf5, 0x46, 0x7f, 0x12, 0x0b, 0xc1, 0x16, +0xad, 0x83, 0xbe, 0xd9, 0xd6, 0xfb, 0xf6, 0xf6, +0xac, 0x5a, 0x94, 0x99, 0xe2, 0x95, 0x4b, 0x3b, +0x3e, 0x47, 0x82, 0x4d, 0xcf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0x2c, +0xde, 0x50, 0xd6, 0x07, 0xb5, 0xa0, 0xa9, 0x0e, +0x7c, 0x06, 0x57, 0x07, 0x0a, 0x6f, 0x7c, 0x6b, +0x74, 0xfa, 0xf5, 0xb9, 0xf8, 0xa9, 0xce, 0xb6, +0x96, 0x72, 0x70, 0x13, 0x39, 0xdb, 0xd2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0x95, 0xe5, 0x94, 0x1a, 0x9c, 0x90, 0xd1, +0x3a, 0x34, 0x5c, 0xcb, 0xf9, 0x5a, 0x42, 0x99, +0x13, 0x8b, 0x73, 0x92, 0x9a, 0x94, 0x60, 0xda, +0x38, 0x03, 0x5b, 0x44, 0x00, 0xee, 0x49, 0x92, +0x1b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0x96, 0x62, 0xc0, 0xd3, 0x02, +0x9f, 0x98, 0x07, 0x1b, 0x23, 0x3e, 0x46, 0x66, +0xe7, 0xc5, 0xae, 0x9d, 0x3d, 0x7a, 0xd7, 0x7e, +0x32, 0x15, 0x8c, 0xbc, 0xe0, 0xb4, 0x15, 0xff, +0x8c, 0xa0, 0x88, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0x96, 0x2e, 0x36, +0x03, 0x07, 0xec, 0xe7, 0x47, 0x36, 0x6b, 0x6f, +0x5f, 0xd6, 0x33, 0x82, 0xac, 0xfd, 0x96, 0x9a, +0x5e, 0x74, 0x03, 0xc3, 0xc6, 0x69, 0x5b, 0xe5, +0x01, 0xf5, 0xd6, 0xe3, 0xda, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0x86, +0xa7, 0xfb, 0x0b, 0x54, 0x2d, 0x8d, 0x1f, 0x13, +0xa5, 0x71, 0xb3, 0xf1, 0xef, 0x4f, 0x30, 0x03, +0xfd, 0xcd, 0x7d, 0xa3, 0x82, 0x47, 0x32, 0x13, +0x4c, 0xee, 0x44, 0xb4, 0x0c, 0x00, 0x4d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0x84, 0x36, 0x6d, 0x1e, 0x81, 0xb0, 0x41, +0x51, 0x32, 0xd5, 0x85, 0x6b, 0x7d, 0x19, 0x66, +0x87, 0x58, 0xd7, 0xe7, 0x11, 0xb8, 0x94, 0xd2, +0x96, 0x24, 0x5e, 0x72, 0xa1, 0xd1, 0x40, 0xd6, +0x16, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0x9f, 0xa2, 0xa6, 0x0d, 0xf2, +0x30, 0x41, 0x58, 0x3d, 0x3e, 0xa9, 0x89, 0x98, +0x9e, 0x9b, 0x3d, 0x15, 0x1e, 0x0f, 0xba, 0x7e, +0xd4, 0xe1, 0x70, 0x32, 0xef, 0x29, 0x15, 0x03, +0xa4, 0x14, 0xd9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0x8e, 0x7f, 0x57, +0xcf, 0x62, 0x8e, 0x1c, 0xe0, 0x04, 0x8a, 0xd3, +0xc3, 0xea, 0x99, 0xb4, 0x52, 0x8e, 0xb5, 0x80, +0x47, 0x87, 0x56, 0xaf, 0x26, 0xcd, 0x42, 0xd4, +0x89, 0x92, 0xc7, 0xa2, 0xc9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0x58, +0x92, 0xb2, 0xf2, 0xef, 0xb2, 0x91, 0x3e, 0x46, +0xda, 0xac, 0x94, 0x76, 0xb6, 0xf1, 0x55, 0x7e, +0xf2, 0xa6, 0x28, 0x55, 0xaf, 0x8d, 0x27, 0x20, +0xa7, 0x6f, 0xd7, 0x67, 0xb5, 0x46, 0xfd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0x2c, 0x80, 0xa8, 0xb5, 0x3e, 0xff, 0x76, +0x8c, 0x98, 0xe5, 0xf6, 0x5f, 0x3d, 0x0b, 0xbe, +0xf9, 0x80, 0xc4, 0x25, 0xe6, 0x5d, 0x08, 0xa7, +0xfd, 0x7f, 0xbd, 0x80, 0x29, 0x79, 0x63, 0x5a, +0xc6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0xcf, 0x7a, 0x5e, 0x2f, 0x38, +0xc0, 0x42, 0x08, 0xd3, 0x4f, 0xa0, 0x1d, 0xdb, +0x80, 0x39, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xc6, 0x82, 0xfb, 0x56, +0x4c, 0xe0, 0x09, 0xbf, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x89, 0xc7, 0xa9, +0x28, 0x5e, 0xb7, 0xef, 0x07, 0x1f, 0xe9, 0xf3, +0x67, 0x64, 0xdd, 0xf1, 0x2a, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5b, 0x97, +0x9f, 0xf2, 0x5a, 0x55, 0x8f, 0x5f, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x9b, +0xa4, 0xc2, 0xeb, 0x99, 0x45, 0x3a, 0xfa, 0x21, +0x05, 0x38, 0x2e, 0xfc, 0x67, 0xd4, 0x0a, 0xeb, +0x06, 0xef, 0x38, 0x98, 0x2e, 0x48, 0x9e, 0xd1, +0x0e, 0x36, 0xc7, 0xa7, 0x0f, 0xd9, 0x66, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x5e, 0x48, 0x75, 0x26, 0x05, 0xdb, 0x05, +0x08, 0x09, 0xa9, 0xe0, 0xae, 0x88, 0x1a, 0x40, +0x69, 0xc3, 0x32, 0x97, 0xd3, 0x62, 0xc9, 0x27, +0xf3, 0xcd, 0xb7, 0xff, 0x9d, 0x54, 0x70, 0x79, +0x0e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0x1c, 0xd3, 0xff, 0xb5, 0x6b, +0x12, 0xbd, 0xf5, 0x89, 0xc6, 0x1c, 0xdd, 0xfb, +0x50, 0xf0, 0x54, 0x09, 0x4a, 0x8d, 0x61, 0x86, +0x02, 0x24, 0x89, 0x57, 0xba, 0x6f, 0xd8, 0xa0, +0x23, 0x3b, 0x37, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0xc2, 0x9c, 0xba, +0x96, 0x25, 0xdc, 0xf9, 0x57, 0xdf, 0xe8, 0x2a, +0x62, 0x4e, 0x62, 0x0a, 0xf1, 0x3c, 0xe3, 0xd5, +0xfd, 0x3b, 0x57, 0xc5, 0x88, 0x27, 0x56, 0xc5, +0xc7, 0xa3, 0x0b, 0xbe, 0x00, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0x09, +0xe8, 0xe7, 0x4f, 0xf9, 0x3f, 0xb5, 0x1c, 0x7a, +0x79, 0x35, 0x5e, 0x76, 0x17, 0x5f, 0xa9, 0xc3, +0x39, 0x26, 0x63, 0xcb, 0x68, 0x28, 0xcd, 0x10, +0xb4, 0x68, 0x64, 0x8b, 0xb0, 0xe3, 0x4a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0x72, 0x88, 0xec, 0x93, 0xae, 0x43, 0xf8, +0xf9, 0x89, 0x27, 0xad, 0x88, 0x87, 0xe8, 0xd2, +0x22, 0x7b, 0x28, 0x19, 0xcd, 0xfb, 0x6c, 0xab, +0x9c, 0x2b, 0x84, 0x08, 0x0f, 0x57, 0x25, 0x05, +0x5f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0x7d, 0xbb, 0xd2, 0x0c, 0x2e, +0xf8, 0x5d, 0xb1, 0xcc, 0xe0, 0x76, 0x25, 0x8f, +0xd0, 0xaa, 0x3b, 0xea, 0x9b, 0x9f, 0xa5, 0xd7, +0xfd, 0x78, 0xe9, 0xee, 0x66, 0x92, 0xf7, 0xc2, +0x1e, 0xc6, 0x13, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0x22, 0xde, 0x05, +0x18, 0xc0, 0xa5, 0xf5, 0xb8, 0x22, 0xd3, 0xa6, +0xc7, 0xa0, 0xa5, 0xfd, 0x6c, 0x1c, 0x8a, 0xd1, +0x3f, 0x54, 0xcf, 0x47, 0xfe, 0x30, 0xba, 0xa3, +0x26, 0x88, 0x5e, 0x64, 0x4d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0x3d, +0x41, 0x61, 0x0a, 0xad, 0x74, 0x01, 0x27, 0xf1, +0x6c, 0x15, 0x2d, 0xba, 0xc5, 0x48, 0xe5, 0x93, +0x5e, 0x4d, 0x07, 0xc4, 0x9f, 0x28, 0x9a, 0x81, +0xbf, 0x05, 0xd0, 0x89, 0x6b, 0x0a, 0x75, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0xae, 0x50, 0x17, 0xa5, 0xd2, 0xfa, 0xba, +0x3e, 0xb4, 0x66, 0x55, 0xd1, 0xbd, 0xf5, 0xa2, +0x43, 0x24, 0x76, 0x71, 0x9d, 0x24, 0xe9, 0xd6, +0x45, 0x66, 0xf9, 0xaf, 0xcb, 0x4e, 0x11, 0xef, +0x5b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0xf2, 0xa4, 0x10, 0x4e, 0xe2, +0x0b, 0x21, 0xdf, 0xad, 0x28, 0x24, 0x47, 0xe1, +0xc9, 0xb3, 0x24, 0xf2, 0xd8, 0xc6, 0x41, 0x86, +0xfa, 0x49, 0xf6, 0x81, 0x59, 0xd1, 0x2c, 0x68, +0x07, 0x69, 0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0x6e, 0xc4, 0x10, +0xe3, 0x6d, 0x37, 0x3f, 0xce, 0x31, 0xbd, 0x12, +0x22, 0x28, 0x8c, 0x47, 0x34, 0x99, 0x29, 0xde, +0x91, 0x4f, 0x6b, 0x1b, 0x0f, 0x50, 0x4a, 0x0b, +0xc5, 0xd3, 0x0c, 0x1e, 0x65, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0xf9, +0xc6, 0x95, 0xcd, 0xb0, 0x86, 0x9c, 0x01, 0xd3, +0x09, 0x13, 0x87, 0x04, 0x69, 0xb9, 0x93, 0xf9, +0xcd, 0x1f, 0x47, 0x66, 0xc3, 0xf4, 0x11, 0xa6, +0x6e, 0x39, 0xd1, 0x77, 0x8f, 0x31, 0x6d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0xac, 0x60, 0x39, 0x15, 0x87, 0xfb, 0xeb, +0xe9, 0x2f, 0xec, 0x92, 0x2c, 0xef, 0xaf, 0x99, +0xe1, 0xa2, 0x07, 0xbd, 0x90, 0xd7, 0xbb, 0xee, +0x6a, 0x0a, 0xbf, 0x63, 0x17, 0x56, 0xdb, 0x80, +0x29, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0x82, 0x8b, 0x3d, 0xac, 0x89, +0xb4, 0x06, 0xc6, 0x9e, 0xe8, 0xd2, 0xb7, 0x45, +0x3d, 0x39, 0x0d, 0xa7, 0x55, 0x7e, 0x0b, 0x06, +0x92, 0xa5, 0x70, 0x1f, 0x34, 0x0f, 0x0a, 0xbf, +0x15, 0xae, 0x0b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0xe2, 0x94, 0x53, +0x1b, 0x5b, 0xfc, 0x14, 0x26, 0xa6, 0x6d, 0xae, +0x4f, 0x90, 0x37, 0xea, 0xd7, 0x15, 0x1b, 0xee, +0xf2, 0x91, 0x3c, 0xcb, 0xae, 0x3b, 0x69, 0x07, +0x15, 0x08, 0x4a, 0x2a, 0x9a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0x29, +0x6c, 0xec, 0x95, 0x40, 0xaa, 0x50, 0x91, 0x12, +0x24, 0xaa, 0xe1, 0x82, 0xbc, 0x30, 0x35, 0xe1, +0x87, 0x6d, 0xfd, 0xdb, 0xca, 0x63, 0xa3, 0xe3, +0x3b, 0xfb, 0x2c, 0xb8, 0xca, 0xb2, 0xe8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0xd0, 0xf8, 0x77, 0x5a, 0x30, 0x32, 0x03, +0x1c, 0xe5, 0xb9, 0x4c, 0x1e, 0x70, 0x19, 0x95, +0x94, 0x2e, 0x18, 0x5b, 0xe8, 0x6c, 0x0a, 0x2c, +0xcb, 0x1e, 0x5b, 0xa3, 0x9f, 0x65, 0x5a, 0x6c, +0x75, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0xdf, 0xe8, 0x11, 0x33, 0x98, +0x35, 0x42, 0x2b, 0x1b, 0xb7, 0x04, 0x01, 0xc6, +0x39, 0x34, 0xcb, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x51, 0xa6, 0x98, 0x86, +0x8c, 0xc4, 0xee, 0x78, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0x44, 0xfa, 0xdb, +0x8c, 0x61, 0xb7, 0x79, 0xac, 0xbf, 0x65, 0x57, +0x95, 0x96, 0xd7, 0x5d, 0x6b, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x70, +0xae, 0x7d, 0x34, 0xc0, 0xcc, 0xaf, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0xf0, +0x5a, 0x3a, 0x45, 0x5b, 0x73, 0x72, 0x43, 0x87, +0x8a, 0x99, 0x8e, 0xab, 0x27, 0xdc, 0x2f, 0x8d, +0x78, 0xc6, 0x4b, 0xab, 0xc6, 0x9d, 0x9b, 0x97, +0x0c, 0xd1, 0x34, 0x92, 0x24, 0x30, 0x6e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0x48, 0x05, 0xc5, 0xc8, 0xee, 0xb0, 0x85, +0x6e, 0x84, 0x32, 0xce, 0x25, 0xf7, 0x22, 0x13, +0xd2, 0xff, 0xfa, 0x12, 0x05, 0xfd, 0x3a, 0x20, +0x9c, 0xda, 0x70, 0x7d, 0x3d, 0x43, 0x3e, 0xd4, +0xd1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0x5c, 0x36, 0x60, 0xe9, 0x03, +0x2b, 0xba, 0xb2, 0x93, 0xc4, 0xa9, 0x0c, 0xc7, +0x7f, 0x08, 0x97, 0xff, 0xf3, 0xdc, 0x3b, 0xd6, +0x81, 0x02, 0xd5, 0x89, 0x5c, 0x80, 0x0e, 0x3a, +0x9d, 0x5e, 0x20, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0x51, 0xdc, 0x62, +0x33, 0x6e, 0x30, 0x53, 0x30, 0x77, 0x14, 0xe0, +0x4a, 0xf5, 0x26, 0x82, 0x56, 0xb2, 0x2f, 0x72, +0xa3, 0xe5, 0xfa, 0x0d, 0xe8, 0x97, 0x19, 0x3d, +0x84, 0xbc, 0x12, 0xa0, 0x3d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0xf9, +0x94, 0x11, 0x69, 0x10, 0xad, 0xcb, 0xda, 0xc8, +0x79, 0xf4, 0x56, 0x0f, 0x0a, 0xd9, 0x51, 0x87, +0x42, 0x90, 0xd8, 0x29, 0xfb, 0x49, 0xbd, 0x3b, +0xb0, 0xe4, 0x06, 0xb1, 0xc4, 0xcc, 0x5e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0x4e, 0x74, 0x78, 0x81, 0x19, 0xc3, 0xa8, +0xd6, 0x9b, 0x58, 0x30, 0x86, 0x25, 0x60, 0xd8, +0x72, 0x90, 0x06, 0xb4, 0x91, 0xbd, 0xf3, 0x7a, +0x66, 0xfa, 0x1b, 0x42, 0x61, 0xa8, 0x4e, 0x13, +0xbc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0x6b, 0x3c, 0x9d, 0x9a, 0x57, +0xf9, 0xe4, 0x28, 0x33, 0x24, 0x11, 0x67, 0x51, +0xb9, 0x60, 0x73, 0x05, 0x7f, 0xd4, 0x9f, 0xd9, +0xdd, 0xc7, 0x4a, 0xd0, 0x00, 0x81, 0x0a, 0xbc, +0xc3, 0xa2, 0x63, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0x20, 0xe4, 0xc8, +0x11, 0x20, 0x03, 0x38, 0xde, 0xa9, 0xa8, 0x52, +0x11, 0x9e, 0x74, 0xeb, 0x8f, 0x07, 0xe8, 0xfd, +0xe5, 0xfc, 0x50, 0x2e, 0x91, 0x7f, 0x96, 0x61, +0xf6, 0x5e, 0x1b, 0x0b, 0x14, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0x77, +0x04, 0x9d, 0xaf, 0x03, 0x19, 0x51, 0xb4, 0x2d, +0xe2, 0xd9, 0xba, 0x0c, 0x47, 0x83, 0xf4, 0x8a, +0x81, 0xa2, 0x8a, 0xb5, 0x46, 0x17, 0xc1, 0xd8, +0x89, 0x4b, 0x5e, 0xb6, 0x07, 0x74, 0xaf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0xec, 0x84, 0x7d, 0xd8, 0x4b, 0x11, 0xa4, +0x14, 0xab, 0xf2, 0xdd, 0x37, 0x49, 0xae, 0xda, +0x80, 0x4b, 0x7d, 0x11, 0xd1, 0x21, 0xf7, 0x51, +0xe8, 0x06, 0xd3, 0xef, 0xeb, 0xb4, 0xe4, 0x20, +0x26, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0x12, 0x23, 0x2e, 0xda, 0xb4, +0xc0, 0x29, 0xd1, 0xf4, 0xb5, 0x8a, 0xa5, 0x29, +0x95, 0xfb, 0xfa, 0xbd, 0x9a, 0x18, 0x34, 0x3d, +0x4a, 0xe9, 0x3f, 0xd4, 0x31, 0x58, 0x19, 0x6f, +0xeb, 0xb9, 0x90, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0x08, 0x28, 0xf9, +0x94, 0x4b, 0xa7, 0xae, 0xee, 0x60, 0xf2, 0xcd, +0x3a, 0x65, 0x54, 0xa5, 0x88, 0xaf, 0xc6, 0xb3, +0x21, 0xc8, 0xbe, 0x9d, 0xa9, 0x91, 0xa2, 0xfc, +0xd2, 0x79, 0x87, 0x5c, 0xca, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0x1a, +0x51, 0x12, 0x57, 0xd7, 0xeb, 0x7b, 0x57, 0x42, +0x6e, 0x73, 0xbd, 0x4e, 0xe7, 0xbd, 0x82, 0x33, +0x93, 0x2e, 0x98, 0xe0, 0xd2, 0x01, 0xdc, 0x0d, +0xbe, 0xbb, 0x3c, 0xba, 0x12, 0x91, 0x82, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0x41, 0x91, 0xd0, 0xe1, 0x25, 0x8a, 0x33, +0x14, 0x09, 0x85, 0xe1, 0xf5, 0x00, 0xbc, 0xd7, +0x37, 0xf0, 0x9b, 0x43, 0xa1, 0xe1, 0x78, 0x16, +0x33, 0xf7, 0x09, 0x33, 0xed, 0x2d, 0xf5, 0x3c, +0x12, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0xcc, 0x1a, 0x37, 0xfb, 0xad, +0x70, 0xd0, 0xa3, 0x2b, 0x29, 0x11, 0xff, 0x21, +0xdd, 0x73, 0x0f, 0xef, 0xa0, 0x2c, 0x44, 0xee, +0xf9, 0x34, 0x26, 0x4e, 0x39, 0x18, 0x84, 0x01, +0xdf, 0xac, 0xcd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x98, 0xb1, 0xa0, +0xdf, 0xe4, 0x3c, 0x18, 0x3e, 0x50, 0xfe, 0xc4, +0x4a, 0xa9, 0x89, 0x66, 0x61, 0x34, 0x49, 0x67, +0xb8, 0x9a, 0xcf, 0x66, 0x84, 0xb1, 0x13, 0xc8, +0xcf, 0x5c, 0x85, 0x89, 0x7e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0xac, +0x21, 0x74, 0xe8, 0xf3, 0xdd, 0xc8, 0x8c, 0xb6, +0xb1, 0x2f, 0x5c, 0x94, 0xf6, 0xdd, 0x61, 0xa9, +0x8a, 0x97, 0xe4, 0x4d, 0x71, 0x8d, 0x82, 0x6c, +0xc3, 0xa1, 0xba, 0x54, 0x32, 0xc1, 0x60, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x3e, 0x3e, 0x6c, 0xcc, 0x5b, 0x33, 0x3c, +0xcd, 0xde, 0xbd, 0x8f, 0xf4, 0xf3, 0xc6, 0x09, +0xc3, 0xeb, 0xd2, 0xdb, 0xd4, 0xa2, 0x56, 0x49, +0x55, 0xcd, 0xed, 0x66, 0x28, 0x0b, 0x70, 0x71, +0xda, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0xad, 0x31, 0xc6, 0xdc, 0x9c, +0x36, 0x22, 0x12, 0x13, 0xc8, 0x31, 0x10, 0xeb, +0x9b, 0x51, 0xa9, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xea, 0x61, 0x4b, 0x9c, +0xc5, 0xb6, 0x17, 0x04, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0x04, 0x37, 0x11, +0xfd, 0x79, 0x15, 0x6d, 0x18, 0x1e, 0xc4, 0xb7, +0x03, 0x9d, 0x43, 0x67, 0xc1, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa4, 0xc0, +0xa2, 0x1a, 0x2b, 0x82, 0x32, 0xa6, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x11, +0x5e, 0x66, 0x91, 0xf9, 0x59, 0x9e, 0xc1, 0x0f, +0x97, 0x0d, 0x1b, 0x4b, 0x4d, 0x55, 0x77, 0x05, +0x1b, 0x9f, 0x86, 0x84, 0x33, 0x95, 0x88, 0x6d, +0xd9, 0x44, 0x17, 0x74, 0xeb, 0x0c, 0x5d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0xf4, 0x7f, 0xb1, 0x06, 0x98, 0x54, 0xdc, +0x50, 0xbc, 0xe6, 0xaa, 0x96, 0x3b, 0xe0, 0xae, +0xb2, 0x78, 0x2a, 0xbf, 0xae, 0xbd, 0x50, 0xf8, +0x12, 0xb7, 0x1d, 0xd5, 0x1f, 0x3a, 0xea, 0xcb, +0xe2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0x4c, 0x63, 0x89, 0x62, 0xbe, +0x29, 0x90, 0x74, 0x0b, 0xc7, 0x57, 0xab, 0x6f, +0xe1, 0x01, 0xf9, 0x3c, 0x1d, 0x01, 0x1b, 0x04, +0xfb, 0x10, 0x7c, 0x19, 0xd2, 0xb8, 0xae, 0xfa, +0x5c, 0x13, 0xc1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0xb8, 0x76, 0x49, +0x06, 0x82, 0x43, 0x05, 0xd7, 0x7b, 0x79, 0x61, +0xcd, 0x79, 0xd0, 0x7e, 0x1a, 0x01, 0xb7, 0xf5, +0x42, 0x40, 0x7b, 0xbd, 0x01, 0xa7, 0x38, 0x5f, +0x35, 0x5c, 0xaa, 0xcb, 0x19, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x0c, +0xc6, 0x39, 0x36, 0x4e, 0x59, 0xe8, 0x98, 0x47, +0x0f, 0xfe, 0x84, 0xfa, 0x9c, 0x01, 0x15, 0x24, +0x72, 0x49, 0x4a, 0x46, 0x72, 0xe0, 0x85, 0xe3, +0xcc, 0x81, 0x3a, 0x16, 0x94, 0xe7, 0xc0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0x8b, 0xdd, 0x78, 0xd9, 0xf6, 0x81, 0x81, +0x38, 0x7d, 0x78, 0x16, 0x66, 0xcc, 0x19, 0xfe, +0xd9, 0xfc, 0x59, 0x35, 0x5e, 0x96, 0x0d, 0x9a, +0xa3, 0x75, 0x67, 0x6c, 0x22, 0x65, 0xf4, 0xf7, +0xde, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0xb7, 0x8c, 0xbe, 0xab, 0x29, +0x22, 0x7c, 0xad, 0x53, 0xd1, 0x06, 0x82, 0xd0, +0x26, 0x32, 0x7e, 0x0c, 0x75, 0x0b, 0x7c, 0x37, +0x8f, 0x53, 0x87, 0x8c, 0x6c, 0x2c, 0x5e, 0x1e, +0xea, 0x18, 0x45, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0x76, 0x86, 0xa8, +0x1e, 0xca, 0x2c, 0x24, 0x9c, 0x1c, 0xa0, 0x16, +0x18, 0x9b, 0xdf, 0x1b, 0x01, 0x87, 0x86, 0x73, +0xab, 0x9b, 0xe0, 0x3f, 0xd2, 0xf3, 0x62, 0xfe, +0x0d, 0xc3, 0x46, 0x2a, 0xf3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x41, +0x1b, 0x2c, 0x19, 0x1c, 0xd0, 0xd8, 0xdf, 0xed, +0x71, 0x28, 0x8f, 0xc7, 0x6a, 0x8e, 0xb3, 0xb6, +0xb9, 0x1c, 0x97, 0xbe, 0x48, 0xbf, 0xff, 0x86, +0x42, 0xe4, 0x34, 0xf0, 0x2c, 0x1d, 0x5c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0xa6, 0x01, 0x02, 0x3d, 0x67, 0xc6, 0x71, +0x91, 0x02, 0x47, 0x1b, 0xdd, 0x4c, 0x2b, 0x4c, +0xa8, 0xe4, 0x5d, 0x32, 0xcc, 0xc6, 0x44, 0x38, +0x22, 0x79, 0x74, 0xc3, 0xc4, 0x91, 0xe1, 0xe5, +0xbc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0xc8, 0x0a, 0x5b, 0xe4, 0x9c, +0x68, 0xb7, 0x22, 0xa3, 0x64, 0xd6, 0xb0, 0x8c, +0xd6, 0xfa, 0x2c, 0x74, 0x44, 0xfc, 0x0a, 0xb1, +0x18, 0xd2, 0x49, 0x8f, 0xc5, 0x04, 0xf0, 0x47, +0x88, 0x28, 0x63, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x09, 0x03, 0xac, +0xbb, 0x1f, 0xfa, 0xbe, 0x24, 0xa5, 0xf1, 0x1a, +0x8c, 0x20, 0xca, 0x5e, 0xd1, 0xe2, 0x80, 0xcb, +0xd5, 0x48, 0x94, 0xd5, 0x56, 0xaa, 0x26, 0x74, +0xfa, 0xdf, 0x37, 0x98, 0xc9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0x79, +0x2d, 0xbb, 0x43, 0xf6, 0x39, 0x12, 0x6e, 0x77, +0xb2, 0xdb, 0x51, 0x82, 0xf3, 0x4c, 0x67, 0x7b, +0xce, 0x76, 0x06, 0xba, 0x8f, 0xbd, 0x06, 0xd9, +0x7c, 0x4c, 0xb7, 0xd5, 0xec, 0xcd, 0x66, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0xc0, 0x47, 0xe7, 0x96, 0x5d, 0x53, 0xf4, +0x60, 0xd4, 0x56, 0x50, 0x9c, 0x24, 0xe0, 0xd0, +0x4f, 0xf1, 0xef, 0xcf, 0x21, 0xd4, 0x1c, 0x5e, +0xce, 0xcc, 0x2d, 0x91, 0x4f, 0xbe, 0x7f, 0x78, +0x6f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0x58, 0x92, 0xb6, 0xa5, 0x8c, +0x99, 0xef, 0x89, 0xf1, 0xde, 0x86, 0x2f, 0x22, +0x52, 0x9a, 0x8d, 0x07, 0x37, 0x20, 0x75, 0x1d, +0x5b, 0xd2, 0x96, 0xf5, 0x93, 0x7a, 0xf7, 0x0b, +0x69, 0xfd, 0x37, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0x04, 0x84, 0xbd, +0x4d, 0xeb, 0xf8, 0x52, 0x25, 0xeb, 0x41, 0xdb, +0xfa, 0xc5, 0x8e, 0x7e, 0x45, 0x0f, 0x88, 0xe1, +0xc9, 0xe8, 0x76, 0x31, 0xc8, 0x1b, 0x77, 0xa3, +0x2b, 0x28, 0xb1, 0x70, 0x6a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0x6b, +0x25, 0x2f, 0x62, 0xd6, 0xcb, 0x10, 0x5d, 0xfa, +0xb1, 0x9b, 0xbd, 0x93, 0x1a, 0xe9, 0x65, 0xb4, +0x7a, 0xd8, 0xdc, 0x22, 0x56, 0x3c, 0x9f, 0xae, +0x21, 0xec, 0x77, 0x1b, 0xb1, 0xab, 0x18, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0xc7, 0x2c, 0xb8, 0x92, 0xd9, 0x91, 0x6c, +0xcd, 0xd6, 0x9d, 0xdd, 0xc0, 0x36, 0xc2, 0xe3, +0xe7, 0xc1, 0x8e, 0xba, 0x6d, 0x3a, 0x85, 0xb0, +0xdc, 0x05, 0x68, 0x07, 0x5a, 0x42, 0x7d, 0x2e, +0x9f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0xce, 0x52, 0x52, 0x8f, 0xeb, +0x8b, 0x5b, 0x62, 0x92, 0x3b, 0x45, 0xa8, 0xd4, +0x2c, 0xf0, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x45, 0x7d, 0x44, 0x67, +0x11, 0x87, 0xc4, 0xbd, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0xfb, 0x5b, 0x6d, +0x7a, 0x4a, 0x2a, 0x9e, 0x0d, 0xab, 0x8b, 0xa4, +0x56, 0x16, 0x0d, 0xaf, 0xfa, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9b, 0x1c, +0x9e, 0x49, 0xfc, 0xd4, 0xd8, 0x6e, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0xf6, +0xb9, 0xaa, 0xf8, 0x38, 0x6a, 0xcc, 0x63, 0x17, +0x03, 0x8e, 0x78, 0x1b, 0xf9, 0x24, 0xb1, 0x42, +0x6a, 0xd2, 0x3e, 0x89, 0xe6, 0xa8, 0x1b, 0x47, +0xbb, 0x67, 0xd4, 0x89, 0x38, 0x73, 0xca, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x29, 0x65, 0x25, 0xa3, 0x1c, 0x52, 0x1f, +0x03, 0x53, 0xe7, 0xc6, 0xcc, 0x96, 0x3c, 0xa2, +0xb0, 0xe2, 0x5f, 0x6b, 0xbc, 0xdc, 0x77, 0x30, +0xbb, 0x89, 0x75, 0xd8, 0x63, 0x4a, 0xcf, 0xfb, +0xed, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0x7d, 0xa5, 0x45, 0xd8, 0x35, +0x58, 0xb9, 0x33, 0x1f, 0x7a, 0x43, 0x78, 0xa1, +0x11, 0x87, 0xa7, 0x3e, 0xd2, 0x9f, 0xc2, 0xc6, +0x05, 0x90, 0x59, 0x66, 0x77, 0xf2, 0x7a, 0xd5, +0x78, 0x03, 0xfb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0xab, 0xe1, 0x67, +0xb3, 0xb0, 0xae, 0x41, 0xdb, 0xc2, 0x87, 0x03, +0x4e, 0xed, 0x9f, 0x32, 0xe5, 0xae, 0x83, 0x8f, +0x84, 0xab, 0xbf, 0xf1, 0xe2, 0x27, 0x62, 0xc4, +0x34, 0x19, 0x3f, 0xb3, 0xbe, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x37, +0xba, 0x86, 0x5d, 0x1d, 0x70, 0xe4, 0x6a, 0xa6, +0x81, 0xc1, 0xe6, 0x63, 0xdf, 0x02, 0x2e, 0x53, +0xe2, 0x40, 0x81, 0x07, 0x78, 0x77, 0x11, 0xfc, +0x90, 0xcb, 0x69, 0xf9, 0x87, 0xeb, 0x48, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0xef, 0x59, 0x7e, 0x42, 0x12, 0xe8, 0x43, +0xa5, 0x49, 0x92, 0x8d, 0x85, 0x7d, 0x10, 0xf4, +0xf7, 0x7b, 0x1e, 0xf0, 0x6d, 0x26, 0x4b, 0x98, +0xa2, 0x3e, 0x2d, 0x7e, 0x32, 0x68, 0xcf, 0x2c, +0x39, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0x73, 0xeb, 0x40, 0xb0, 0x8d, +0xc9, 0xeb, 0x16, 0x62, 0x15, 0xf0, 0x03, 0x4e, +0xea, 0xa1, 0x91, 0x1f, 0x00, 0xf5, 0xf3, 0xed, +0xf9, 0xe4, 0x45, 0xd3, 0x7e, 0x2e, 0x75, 0xa7, +0x54, 0x8b, 0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0x04, 0xb2, 0x7b, +0x17, 0x17, 0x90, 0x37, 0xf0, 0x21, 0x79, 0x93, +0x8a, 0xa0, 0xc2, 0x96, 0x91, 0x5e, 0x2f, 0x93, +0x7c, 0xa4, 0xef, 0x5d, 0x6c, 0x3f, 0x07, 0xf2, +0xe1, 0xc1, 0x3e, 0x06, 0x07, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0xdd, +0xca, 0xf0, 0x26, 0x48, 0x1d, 0xfc, 0x75, 0xfd, +0x48, 0x33, 0x37, 0xc1, 0x95, 0x88, 0x0a, 0xe3, +0x8b, 0xff, 0x83, 0xd9, 0x87, 0x73, 0xd9, 0xe4, +0x3c, 0x4c, 0x85, 0x02, 0x1c, 0x56, 0xcb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0x3c, 0x1f, 0xec, 0x15, 0xe3, 0xe8, 0xc4, +0xa2, 0x3b, 0xc9, 0xd8, 0xca, 0x78, 0xce, 0x86, +0xde, 0x70, 0xd3, 0xea, 0x2a, 0xf6, 0xeb, 0x4f, +0xe5, 0xdc, 0x72, 0xd2, 0x71, 0x37, 0x56, 0x85, +0x70, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0xb2, 0x96, 0x81, 0x0b, 0xd2, +0xaf, 0x74, 0xe7, 0xbc, 0x8e, 0xd9, 0xc2, 0x2f, +0xf3, 0x21, 0x7d, 0xd8, 0x95, 0x9c, 0xd0, 0xef, +0x39, 0x05, 0x73, 0xe2, 0x58, 0x79, 0xf7, 0xcf, +0x54, 0x81, 0x22, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0x8c, 0xec, 0x37, +0x4a, 0x61, 0x76, 0xfc, 0x09, 0x25, 0x9c, 0x5b, +0xa5, 0x4c, 0x56, 0xf1, 0xcc, 0x64, 0x2c, 0x73, +0x7a, 0x8e, 0x40, 0x5f, 0xe1, 0x90, 0xf0, 0xb2, +0xaf, 0x51, 0xc7, 0x94, 0x0b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0x4a, +0xe9, 0x36, 0xfa, 0xdf, 0xc7, 0x9b, 0xdf, 0x6b, +0x67, 0x82, 0xb4, 0x43, 0x46, 0xe2, 0x42, 0x00, +0x3f, 0xd2, 0x6f, 0x7f, 0x1b, 0xf2, 0x9b, 0x5d, +0x9f, 0x41, 0xdd, 0xa1, 0x96, 0xbd, 0xc8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0x52, 0x77, 0xd9, 0xdd, 0x8d, 0xf6, 0xfb, +0xdd, 0x9e, 0xd3, 0x8a, 0xd3, 0x8f, 0x25, 0x35, +0x71, 0x57, 0x6f, 0x9c, 0x95, 0xf6, 0xd7, 0x28, +0xc4, 0x58, 0x3f, 0x7e, 0x1a, 0x29, 0xcd, 0xd3, +0x4c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0xa3, 0x04, 0x3e, 0x34, 0xb4, +0x28, 0xf7, 0x22, 0x1e, 0xc8, 0x7b, 0x0c, 0xa1, +0x2c, 0x16, 0x39, 0xe5, 0x04, 0xef, 0xe5, 0xa2, +0x30, 0x72, 0x93, 0xed, 0x85, 0xf5, 0x7b, 0xa5, +0x5c, 0x33, 0x09, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0xdd, 0x13, 0xe6, +0xa0, 0xe1, 0x8e, 0x31, 0xa6, 0x29, 0xe1, 0x48, +0x1c, 0x19, 0x78, 0xe0, 0xf3, 0x37, 0x13, 0xb8, +0x1d, 0x8c, 0x05, 0xdf, 0x16, 0x5a, 0x7a, 0x60, +0x9f, 0x7d, 0x32, 0xaa, 0xa6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0x6e, +0x79, 0x35, 0x54, 0xfb, 0xcd, 0x1c, 0x48, 0xce, +0xf3, 0x79, 0x38, 0xda, 0x86, 0x42, 0x53, 0x6c, +0x2c, 0xca, 0xdd, 0x1e, 0x8d, 0x67, 0x62, 0xce, +0xf0, 0xaa, 0x7a, 0x49, 0xa7, 0xcd, 0x96, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0x55, 0xcd, 0xbf, 0x02, 0x32, 0x29, 0xbb, +0x91, 0xf7, 0xc6, 0xf0, 0xda, 0x60, 0x68, 0x0e, +0x3f, 0x31, 0x1f, 0xaa, 0xc9, 0x58, 0xc5, 0xd5, +0xb0, 0xc4, 0xda, 0xc9, 0x5c, 0xb7, 0xd3, 0x71, +0x7c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0xd8, 0x86, 0xb8, 0xda, 0x71, +0x82, 0x27, 0x22, 0x40, 0x17, 0x00, 0xe7, 0xfb, +0x54, 0x4a, 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xe4, 0x7d, 0x8b, 0xda, +0x30, 0xed, 0xef, 0x83, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0x40, 0xb6, 0xc0, +0xd3, 0x76, 0x6f, 0xc9, 0x5a, 0xc3, 0x06, 0x5d, +0xc8, 0xd5, 0xa4, 0x4d, 0xc6, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x3d, +0x95, 0x02, 0xb0, 0xfd, 0x54, 0x61, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0xb6, +0xe3, 0xd0, 0xd5, 0x37, 0xd1, 0xce, 0x90, 0x74, +0x3a, 0x78, 0xa4, 0xd2, 0x25, 0xc6, 0x4a, 0x75, +0x89, 0x0c, 0x17, 0x99, 0x4c, 0x7f, 0xf8, 0xc8, +0x0f, 0xae, 0x24, 0x98, 0xa0, 0x2a, 0x46, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0xdf, 0x25, 0x41, 0xea, 0x8f, 0x4e, 0x19, +0x9c, 0xce, 0xc4, 0x62, 0x5a, 0xfd, 0xc4, 0x5d, +0xef, 0x6d, 0x38, 0x33, 0x67, 0x29, 0x7c, 0xb0, +0x39, 0xcf, 0x59, 0x78, 0xb6, 0x1e, 0x1a, 0x79, +0xa4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x5c, 0xcc, 0xf6, 0x18, 0x26, +0x86, 0x39, 0x41, 0xf3, 0x0f, 0xf3, 0xc0, 0xc0, +0xae, 0x2b, 0xe0, 0x3f, 0x91, 0x3a, 0xb7, 0x84, +0xdc, 0xf8, 0xf6, 0x1e, 0x87, 0xee, 0xe7, 0xc2, +0x92, 0xb8, 0x36, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0x1d, 0x63, 0x68, +0x6a, 0x6f, 0x51, 0xa9, 0x7b, 0x32, 0x75, 0xe5, +0x77, 0xaa, 0xfb, 0x18, 0xd6, 0xe4, 0x0e, 0x15, +0x53, 0xa9, 0x27, 0x06, 0xca, 0x6a, 0x98, 0x2d, +0xc4, 0xd5, 0x0c, 0x78, 0xfc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0x56, +0x7d, 0xc9, 0x9b, 0xc4, 0x61, 0x75, 0x55, 0x64, +0x22, 0x37, 0xf5, 0x0f, 0x3b, 0xcb, 0x0e, 0x9a, +0xe4, 0x86, 0x06, 0xef, 0xbd, 0x11, 0xcd, 0x0d, +0xfa, 0xa3, 0x16, 0x74, 0xa8, 0x18, 0xf5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x81, 0x18, 0xdf, 0xa5, 0xd2, 0x6f, 0xf3, +0xa9, 0x6d, 0x43, 0x26, 0x72, 0xac, 0x46, 0xc9, +0x02, 0x79, 0x1b, 0x06, 0xf1, 0x05, 0xac, 0x7b, +0xc7, 0x32, 0xac, 0x86, 0x9d, 0x30, 0x11, 0x17, +0xea, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x28, 0x79, 0xf2, 0xfd, 0x11, +0xc3, 0x60, 0xe7, 0x31, 0x9e, 0xa7, 0x18, 0x1f, +0x8c, 0x00, 0x77, 0xaa, 0x8a, 0x96, 0x42, 0xd5, +0xc5, 0x81, 0xf9, 0xa3, 0x60, 0x8b, 0x57, 0x79, +0x73, 0xd5, 0x66, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0xda, 0x23, 0x90, +0x54, 0x1f, 0xb0, 0xd2, 0x6f, 0x33, 0xa5, 0xaf, +0xb3, 0x13, 0x56, 0xf3, 0xf7, 0x32, 0x1d, 0x38, +0x45, 0xdd, 0x76, 0xf8, 0x0b, 0x7b, 0x08, 0x7d, +0x55, 0xe5, 0xb2, 0x86, 0x10, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0xdf, +0xe0, 0x0f, 0xbd, 0x26, 0x49, 0x11, 0x92, 0xd5, +0x2a, 0x48, 0xfe, 0x66, 0x3f, 0x5d, 0x28, 0xa7, +0x42, 0x5d, 0xb2, 0x2e, 0x8f, 0xa2, 0x71, 0xb1, +0x05, 0x4c, 0x6f, 0x15, 0x08, 0x29, 0x6a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0x86, 0xd5, 0xfd, 0x4e, 0xbe, 0xd2, 0x68, +0x47, 0x34, 0xb6, 0xcf, 0x9e, 0x49, 0x85, 0x26, +0xac, 0xda, 0x4f, 0xf7, 0xe7, 0xd3, 0xa7, 0x83, +0x47, 0x10, 0x23, 0x10, 0x13, 0xf2, 0x2e, 0xd4, +0x22, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0x79, 0xcd, 0xde, 0x04, 0x42, +0x50, 0x9a, 0xdd, 0x60, 0x60, 0x6a, 0xf6, 0x06, +0x7d, 0xb4, 0x3e, 0x5f, 0x6a, 0xfa, 0x57, 0x25, +0x04, 0x0c, 0x45, 0x7c, 0xa0, 0x1a, 0xbe, 0xc9, +0x90, 0x74, 0x7d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x5b, 0x4a, 0xc2, +0xd3, 0x5a, 0xb2, 0x79, 0xf3, 0xa2, 0xe0, 0x72, +0xe6, 0xb4, 0x23, 0x75, 0xfa, 0x15, 0xa3, 0xc0, +0x2b, 0x91, 0x5d, 0xb5, 0xfa, 0x4b, 0x38, 0xc4, +0xef, 0xc8, 0xf9, 0x76, 0xfc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x66, +0x7b, 0x05, 0xd5, 0x97, 0x1e, 0x80, 0xe4, 0x54, +0x79, 0x76, 0xfe, 0x3a, 0x8b, 0xee, 0x46, 0xa3, +0xf3, 0x4e, 0xc4, 0x67, 0xc1, 0x4c, 0xa8, 0xa4, +0x77, 0x7b, 0x05, 0x47, 0x2f, 0xa8, 0x4e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0xb2, 0x93, 0x95, 0x92, 0xef, 0x13, 0x95, +0xa4, 0x54, 0x01, 0xa1, 0x65, 0x7a, 0x4a, 0x89, +0x22, 0x12, 0xe9, 0x76, 0xb3, 0x39, 0x76, 0x0b, +0xe4, 0xc4, 0x88, 0xe3, 0xec, 0x07, 0x79, 0xa6, +0x17, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0x05, 0x01, 0x8f, 0xe2, 0x86, +0x9d, 0xda, 0x1e, 0x50, 0x49, 0x1d, 0x27, 0x84, +0x3b, 0x7a, 0xbf, 0x14, 0x4b, 0x1d, 0xb1, 0x07, +0xd3, 0x3a, 0x76, 0x97, 0x00, 0x53, 0x89, 0x5a, +0xd3, 0x32, 0xa3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0x9e, 0x0d, 0xd5, +0x7c, 0xcc, 0x32, 0xe3, 0xdd, 0x75, 0xd9, 0x66, +0x78, 0xbb, 0x00, 0x2e, 0xaa, 0x92, 0x57, 0x06, +0x94, 0x45, 0x4f, 0x21, 0xae, 0xf7, 0x27, 0x89, +0xc2, 0xaa, 0xe3, 0xad, 0x1c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0x2b, +0x05, 0xc6, 0x87, 0xfb, 0x5a, 0x4f, 0x7e, 0x97, +0x29, 0xdd, 0xde, 0xf5, 0xd2, 0xd1, 0x03, 0x0b, +0x6b, 0x0d, 0xb1, 0x92, 0x8b, 0x89, 0xcf, 0x10, +0xd2, 0x26, 0xf5, 0xf4, 0xbc, 0x05, 0xeb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0xc6, 0xaf, 0x87, 0x6c, 0x5f, 0x98, 0x65, +0x47, 0xb1, 0x05, 0xa2, 0x5d, 0x8d, 0xe2, 0x8f, +0xa2, 0x5e, 0xb0, 0x55, 0x91, 0x13, 0x85, 0xc0, +0xe9, 0xc6, 0xc2, 0x43, 0x18, 0x18, 0x0f, 0x05, +0xa1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0x83, 0x8a, 0x94, 0x44, 0x39, +0x7c, 0x2d, 0xf5, 0x5f, 0x8f, 0xc8, 0x84, 0xbc, +0x94, 0xa5, 0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xf0, 0xb1, 0x57, 0x58, +0x61, 0xb6, 0xc5, 0x02, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0xf8, 0x93, 0xba, +0xe8, 0x48, 0xff, 0xfc, 0xd4, 0xa4, 0x4c, 0xf0, +0xe5, 0xbf, 0xf6, 0xc6, 0x50, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xb4, +0x34, 0x6a, 0xce, 0xb8, 0x46, 0xc0, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0xb3, +0x0c, 0x18, 0xfa, 0xde, 0x3c, 0xc8, 0x26, 0x39, +0xbd, 0xf3, 0x65, 0xb8, 0xd0, 0xda, 0x5f, 0x41, +0x61, 0xa3, 0xda, 0x02, 0xd9, 0xa9, 0x81, 0x9f, +0x35, 0xa7, 0xec, 0x43, 0xff, 0xce, 0x34, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0xe6, 0x71, 0xbe, 0x2d, 0x9f, 0x55, 0x1b, +0x2b, 0x66, 0x76, 0xd6, 0x4b, 0xe3, 0x74, 0xcb, +0x20, 0x8c, 0x59, 0x01, 0x11, 0x78, 0xa9, 0xd4, +0xa1, 0x80, 0x82, 0x67, 0x00, 0xb5, 0x1c, 0x51, +0x87, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0xc8, 0xa2, 0x41, 0x9d, 0x2e, +0x96, 0x71, 0x74, 0x5e, 0x2d, 0x85, 0x54, 0x29, +0x62, 0xd1, 0x5d, 0xcc, 0x2e, 0x5d, 0x5d, 0x47, +0x96, 0xea, 0x3b, 0x4c, 0xcf, 0xea, 0x5a, 0xb7, +0xe7, 0x82, 0xc4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0xbe, 0xd5, 0x02, +0x63, 0x0f, 0x3e, 0x81, 0x2e, 0x36, 0xa8, 0x8e, +0xfc, 0xcf, 0xa0, 0xb4, 0x64, 0x9d, 0x4b, 0xea, +0xb3, 0x0c, 0x8e, 0xa1, 0x88, 0xa7, 0xd3, 0x55, +0xce, 0xf9, 0xfd, 0x70, 0xb8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0x51, +0x9b, 0x8c, 0x4a, 0xee, 0xfa, 0xc0, 0x7b, 0x1a, +0x2f, 0xd6, 0x4f, 0x8f, 0x00, 0x65, 0xf9, 0x3b, +0x70, 0x4e, 0x76, 0xcb, 0x1d, 0x19, 0xa4, 0xa7, +0x36, 0x26, 0xde, 0x2d, 0xaa, 0x28, 0x08, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0x7b, 0xd6, 0xad, 0xa7, 0x86, 0xa7, 0xaf, +0x0e, 0x38, 0x83, 0xb4, 0x3f, 0x4c, 0x02, 0xff, +0x11, 0x60, 0x9b, 0xe3, 0x9f, 0x9f, 0x1c, 0xc8, +0x09, 0xf6, 0xd8, 0x9f, 0x20, 0x94, 0xc6, 0x0e, +0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0x4f, 0xba, 0x1f, 0x0a, 0x86, +0x83, 0x8d, 0xbb, 0x4b, 0xe7, 0x06, 0xd3, 0x86, +0x53, 0xb6, 0x5d, 0xf8, 0x15, 0x67, 0xe6, 0xd3, +0xc7, 0xec, 0x26, 0x8d, 0xb2, 0x98, 0x11, 0x6a, +0xeb, 0xdb, 0x60, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0xf7, 0x18, 0x95, +0xfd, 0x7a, 0x48, 0x64, 0x5f, 0x95, 0x75, 0xe1, +0xaa, 0x05, 0xe9, 0xa8, 0xa7, 0x04, 0x0c, 0xa7, +0x2a, 0xc3, 0xc0, 0xc7, 0x8f, 0x46, 0x22, 0xbf, +0x2b, 0x66, 0x92, 0xc5, 0x14, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x16, +0xd7, 0xed, 0x4a, 0xd0, 0xa2, 0x23, 0x96, 0x3d, +0x79, 0x4f, 0xe2, 0xbb, 0x4c, 0x08, 0xeb, 0x86, +0x69, 0xd5, 0x99, 0x88, 0x36, 0x59, 0xab, 0x77, +0x78, 0xfd, 0x36, 0xb6, 0x7b, 0xd9, 0x9d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0x52, 0x83, 0xe0, 0x0b, 0x7e, 0x3a, 0x11, +0xa5, 0x70, 0x41, 0xf4, 0x09, 0x94, 0xd6, 0x98, +0xd4, 0x9c, 0xfd, 0x1f, 0x3b, 0xa6, 0x9d, 0x8c, +0x71, 0x1d, 0x5e, 0xa1, 0x20, 0xdf, 0x87, 0xc3, +0x70, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0x98, 0xf9, 0x78, 0x89, 0x35, +0xe9, 0xf6, 0x2d, 0x98, 0xe3, 0xf2, 0x28, 0xd0, +0x3f, 0xb2, 0x1d, 0x9f, 0xdd, 0x61, 0x92, 0xe2, +0x12, 0xdd, 0xb2, 0x53, 0xb3, 0x3d, 0x58, 0xe6, +0xfa, 0xeb, 0x6b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0x16, 0x48, 0xeb, +0xdd, 0x96, 0x55, 0x08, 0x95, 0x42, 0x51, 0xf9, +0xce, 0x53, 0x1b, 0x43, 0xa3, 0x58, 0xec, 0xf4, +0x7c, 0x9d, 0x90, 0xa8, 0xc2, 0x5d, 0x47, 0x71, +0x04, 0xe8, 0xc8, 0x01, 0xcc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x40, +0x2f, 0xc4, 0x23, 0x6f, 0xc6, 0xcd, 0xef, 0x19, +0x48, 0x42, 0x4e, 0x3b, 0x6e, 0x4a, 0x0a, 0x54, +0x4e, 0xb7, 0x89, 0x3f, 0xf4, 0xe5, 0x33, 0x3b, +0x76, 0x1d, 0x7a, 0x18, 0xea, 0x41, 0xd0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0x53, 0xef, 0xe4, 0x12, 0xc6, 0x74, 0x44, +0x5c, 0x9b, 0x6a, 0x2d, 0x5e, 0x8f, 0x85, 0x77, +0x0b, 0x30, 0x78, 0xe3, 0xd7, 0x7e, 0x32, 0x15, +0xea, 0x54, 0x8f, 0x20, 0xbc, 0x1d, 0x73, 0xd8, +0x21, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0x7f, 0x47, 0xb6, 0xf8, 0x05, +0x33, 0x7d, 0xf4, 0x95, 0x60, 0x5b, 0x9a, 0x53, +0xc2, 0x53, 0x54, 0xde, 0x8b, 0xb6, 0x06, 0x5f, +0xe1, 0xd1, 0x4f, 0xa7, 0x8c, 0x79, 0xe0, 0x85, +0xd2, 0xa0, 0xf0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0x04, 0x9e, 0xa3, +0xf2, 0x24, 0x0c, 0x9c, 0xf3, 0x5a, 0x44, 0x56, +0x78, 0x48, 0xe9, 0x97, 0x1e, 0x7e, 0xf2, 0x50, +0xef, 0x6f, 0xf3, 0x2b, 0xba, 0x6e, 0xfe, 0x90, +0x1b, 0x00, 0x97, 0x65, 0x06, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x7e, +0xe5, 0xf7, 0x14, 0xee, 0x15, 0xb9, 0x98, 0x4b, +0x01, 0x54, 0x9f, 0x14, 0x3a, 0xf0, 0x21, 0xd5, +0xa2, 0x3f, 0x2d, 0x93, 0x47, 0xd6, 0x96, 0x08, +0x97, 0xb8, 0xcc, 0x55, 0x83, 0xcc, 0x50, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0x0e, 0xf7, 0xb1, 0xe3, 0xbd, 0x7d, 0x6c, +0xe9, 0x17, 0x0b, 0x4e, 0xc8, 0x50, 0x1c, 0xe4, +0xdd, 0xc2, 0xe2, 0xd4, 0xac, 0x50, 0x02, 0xbe, +0x9a, 0xe5, 0xd8, 0xa5, 0x24, 0x8c, 0x62, 0x82, +0x72, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0x6d, 0xff, 0xa5, 0x27, 0x32, +0xac, 0x18, 0x40, 0x44, 0x6a, 0x1c, 0xbb, 0xf4, +0x2a, 0xb9, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xdb, 0x79, 0x63, 0x89, +0x02, 0xec, 0x28, 0xc9, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0x7b, 0x3a, 0x21, +0xf1, 0x56, 0x89, 0x6a, 0x21, 0x7d, 0x2b, 0xc5, +0xd6, 0x74, 0xee, 0x19, 0xfb, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d, 0xc3, +0x44, 0xd4, 0x2f, 0xc9, 0xe1, 0xbc, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0x48, +0xa8, 0x38, 0x13, 0xc6, 0x70, 0x57, 0x89, 0xd8, +0xa8, 0x82, 0x99, 0x3e, 0x4f, 0xc1, 0x2b, 0x01, +0x0a, 0xcb, 0x72, 0x83, 0x72, 0xba, 0xf0, 0x63, +0xdb, 0xf7, 0x9c, 0xe4, 0x1d, 0x5c, 0x0a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0xac, 0x4c, 0x6c, 0x90, 0x7c, 0xb5, 0x28, +0x35, 0x85, 0xfd, 0x0a, 0x6a, 0xb2, 0xf7, 0xff, +0xf2, 0x40, 0xf1, 0xcd, 0xc2, 0x46, 0xf3, 0x20, +0x4a, 0xb4, 0x80, 0xa8, 0x55, 0x47, 0x37, 0xd0, +0xbe, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x7b, 0xe9, 0xd4, 0xb4, 0x82, +0x80, 0x13, 0xcb, 0x5a, 0x81, 0x5c, 0xc9, 0xa9, +0xd9, 0x7d, 0xca, 0x29, 0xfc, 0x1b, 0xe5, 0xd3, +0x32, 0x58, 0x88, 0xbb, 0x68, 0xe1, 0x47, 0xee, +0x13, 0xf5, 0x9f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x19, 0xc5, 0x5c, +0x60, 0x03, 0xf1, 0xb5, 0x09, 0x97, 0x87, 0x05, +0x80, 0x38, 0x6b, 0x7a, 0xce, 0x4a, 0x9a, 0x7e, +0x1b, 0x41, 0x96, 0x6b, 0xa6, 0x86, 0x9b, 0x56, +0xb2, 0xdc, 0xa9, 0xf6, 0xf3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0xfd, +0x5a, 0x43, 0x78, 0xc5, 0xe7, 0x67, 0x02, 0x06, +0xbf, 0xd2, 0xe2, 0x1d, 0x9b, 0x09, 0x55, 0x17, +0xb8, 0xe6, 0x27, 0xd9, 0x8b, 0x38, 0x6f, 0xf3, +0x7f, 0x0b, 0x04, 0xaa, 0x53, 0x74, 0x77, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0x7a, 0x3f, 0x2a, 0x0c, 0x3a, 0x0d, 0x2e, +0xaa, 0x9a, 0xa1, 0xc2, 0x05, 0x0e, 0xfc, 0x83, +0x70, 0x0f, 0xe7, 0x60, 0xd3, 0x04, 0x74, 0x9b, +0x77, 0x43, 0x64, 0xb6, 0xcf, 0xbf, 0x42, 0xce, +0x41, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0x98, 0x04, 0x99, 0xd4, 0x32, +0x19, 0x64, 0x15, 0xc3, 0xb5, 0x08, 0x38, 0xf9, +0x6b, 0xcd, 0xee, 0x97, 0x00, 0x71, 0xc3, 0xeb, +0x7b, 0x73, 0x5c, 0x32, 0x1d, 0x06, 0xb8, 0xa4, +0xba, 0x57, 0x65, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0xeb, 0x49, 0x89, +0x74, 0x6f, 0x6b, 0x86, 0xc6, 0x1b, 0x73, 0x1f, +0xba, 0xbb, 0x10, 0x9c, 0x2d, 0xd8, 0xd9, 0x97, +0xb2, 0xbf, 0xff, 0xfe, 0x8d, 0xf2, 0xea, 0x30, +0xa9, 0x3b, 0x04, 0x43, 0x8d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0x93, +0x40, 0x57, 0x28, 0x8b, 0x14, 0x5f, 0x72, 0x7b, +0x2b, 0x51, 0x40, 0xb4, 0x28, 0x7d, 0xb3, 0x5a, +0x5a, 0x5a, 0x9e, 0x1d, 0x24, 0xff, 0xa4, 0x7d, +0x25, 0xbd, 0x4e, 0x0a, 0x9d, 0xb5, 0xec, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0x73, 0x76, 0xf3, 0x06, 0x1f, 0xea, 0x03, +0x21, 0x6e, 0xc8, 0xa5, 0x49, 0x97, 0x2f, 0xf9, +0xfa, 0x9d, 0xfb, 0xcf, 0x63, 0x99, 0xb3, 0x07, +0xbb, 0xd8, 0xbd, 0xed, 0x77, 0xbe, 0xaf, 0x37, +0x32, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0x74, 0x28, 0x6e, 0xf1, 0x46, +0x42, 0xc5, 0xb6, 0x2f, 0xef, 0x98, 0xfa, 0x9d, +0x92, 0x90, 0xbd, 0x5b, 0x9c, 0xa3, 0x3e, 0x17, +0xc9, 0xd0, 0x24, 0x2e, 0x77, 0xdd, 0x8c, 0xc5, +0x85, 0x72, 0x8c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0xe5, 0x72, 0x89, +0x25, 0x91, 0x85, 0xea, 0x70, 0x75, 0xf1, 0x45, +0xb3, 0x48, 0xcf, 0xdb, 0xe7, 0x1d, 0x18, 0x3d, +0x7d, 0x5d, 0x59, 0x30, 0x35, 0xf3, 0x00, 0x11, +0x9a, 0xb6, 0x5c, 0x46, 0xcf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0xee, +0x41, 0x56, 0x49, 0x3d, 0x46, 0x40, 0x47, 0x20, +0xd9, 0x96, 0xa3, 0xac, 0xa4, 0x44, 0x5b, 0xb1, +0x1d, 0x0c, 0x42, 0x42, 0x6d, 0x41, 0xa8, 0xd4, +0x69, 0xf5, 0xd7, 0x99, 0x53, 0xd4, 0xbb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0x6a, 0x27, 0x92, 0x4c, 0xa5, 0xb6, 0x7a, +0xba, 0x21, 0x75, 0x0c, 0x20, 0xf2, 0x64, 0xc6, +0x6e, 0xcf, 0xa1, 0xe4, 0x52, 0x50, 0x42, 0x37, +0xee, 0x74, 0x64, 0x3c, 0x36, 0xdf, 0x2d, 0x4b, +0x79, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0x8f, 0xec, 0x82, 0xad, 0x9f, +0x01, 0x18, 0x58, 0x74, 0xf1, 0x37, 0x67, 0xa1, +0x27, 0x07, 0xb7, 0x74, 0xf3, 0xab, 0x46, 0x0c, +0x55, 0x88, 0xfd, 0x59, 0x87, 0x6c, 0x4f, 0x3d, +0xfb, 0x4e, 0xb6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0x8b, 0xaa, 0xdf, +0xc0, 0xe1, 0x32, 0xa2, 0xa7, 0x71, 0xe6, 0xcc, +0xd7, 0xc9, 0x0e, 0x39, 0xb8, 0xe2, 0xb6, 0xff, +0xa2, 0x46, 0x15, 0x10, 0x7f, 0x31, 0xed, 0x43, +0x56, 0xaf, 0x29, 0xe0, 0x30, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0x1d, +0x78, 0xdf, 0x62, 0x05, 0x62, 0xd6, 0x3f, 0x7f, +0x73, 0x25, 0x7a, 0xc4, 0x11, 0x02, 0xc1, 0x5b, +0x11, 0xf7, 0xcc, 0x09, 0xea, 0x25, 0x9d, 0x7e, +0x1a, 0xa6, 0xc4, 0x96, 0x6e, 0xaf, 0x98, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0xc3, 0x4c, 0x7e, 0x9a, 0xbf, 0x15, 0xf5, +0xc2, 0x81, 0xfb, 0x90, 0x4d, 0xa1, 0x6b, 0x63, +0x73, 0x6b, 0x56, 0xc5, 0x8c, 0x34, 0x35, 0x58, +0x93, 0x7e, 0x50, 0x9a, 0x83, 0x89, 0x7c, 0xb8, +0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x56, 0x5a, 0xda, 0xc9, 0xa7, +0x27, 0x25, 0x5b, 0xbc, 0x88, 0x60, 0xcd, 0x65, +0x86, 0x7f, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x5d, 0x91, 0x37, 0xce, +0x73, 0xe8, 0x9b, 0x8b, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0xde, 0x77, 0x79, +0xbc, 0x4f, 0xd7, 0x9f, 0xb7, 0x3f, 0x91, 0x3b, +0x9c, 0x37, 0x72, 0x11, 0xbd, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xfa, +0x17, 0x51, 0x82, 0x25, 0xf0, 0x06, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0x0e, +0x97, 0x0d, 0x7b, 0x9d, 0x1f, 0x34, 0xe3, 0xfe, +0x39, 0x32, 0x65, 0xf4, 0xec, 0xb6, 0x5c, 0x58, +0x21, 0x0c, 0x6e, 0x4c, 0x8e, 0xae, 0xd0, 0x9c, +0x30, 0xef, 0x11, 0x88, 0x8e, 0xd1, 0x26, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0xa5, 0xf8, 0xd9, 0xb8, 0x82, 0xde, 0x5e, +0x18, 0xd3, 0x2f, 0x85, 0xf5, 0x9d, 0x8f, 0x2a, +0x67, 0x23, 0x55, 0x7b, 0x58, 0x11, 0x28, 0x33, +0xb1, 0xfc, 0x3d, 0xfb, 0x75, 0x8c, 0x39, 0xed, +0xc8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0xe7, 0xe1, 0xe7, 0x75, 0x0e, +0xf2, 0x3d, 0x9b, 0x64, 0x2b, 0xc7, 0x90, 0x73, +0x8e, 0xf0, 0x0c, 0xef, 0x74, 0xdf, 0x4d, 0x1c, +0x3c, 0xfe, 0x0d, 0x9f, 0xbc, 0xc9, 0xe9, 0x67, +0x71, 0x7d, 0x11, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0xd0, 0xc1, 0x7e, +0x93, 0x62, 0x23, 0x58, 0x23, 0xc8, 0xf4, 0x51, +0xad, 0xb0, 0x2d, 0x60, 0x69, 0x0d, 0x33, 0xa1, +0x6b, 0xd9, 0x97, 0x65, 0x44, 0x43, 0x9e, 0xe0, +0x2a, 0x0c, 0x10, 0x9a, 0xf0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0x74, +0xcf, 0x08, 0xb5, 0x4c, 0x4b, 0xdc, 0x99, 0x87, +0x16, 0xe7, 0x88, 0xd3, 0x3d, 0x8f, 0xa8, 0xf9, +0x08, 0x70, 0xb6, 0x08, 0xf9, 0xf0, 0x20, 0x55, +0x91, 0xd9, 0x34, 0x9c, 0xa8, 0x17, 0x60, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0x4d, 0x17, 0xda, 0x48, 0x46, 0xe0, 0x5b, +0x1c, 0x37, 0x12, 0x22, 0xd9, 0x0f, 0x2b, 0x5d, +0xd6, 0x00, 0xa2, 0x3c, 0xf2, 0x2b, 0xd6, 0xd7, +0xee, 0xd7, 0xef, 0x74, 0x74, 0xf7, 0x1c, 0xbf, +0x3b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0xef, 0x18, 0x60, 0xcd, 0x2f, +0x72, 0x50, 0x8c, 0xdc, 0xa3, 0xd4, 0x18, 0x24, +0x0b, 0xa2, 0x19, 0x5e, 0xd6, 0x02, 0xcf, 0x0d, +0xf4, 0xae, 0xb2, 0xec, 0xfd, 0x5c, 0xef, 0x6e, +0xed, 0x17, 0xaf, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0xa7, 0x18, 0xbe, +0xb6, 0x41, 0x1d, 0x5c, 0xd5, 0x8b, 0x87, 0x86, +0xfa, 0x93, 0xd6, 0xaf, 0x97, 0x6b, 0x79, 0x27, +0x7f, 0x10, 0xe3, 0x6a, 0x7e, 0x41, 0xf9, 0x9f, +0xe3, 0x84, 0x85, 0xb6, 0x5f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0xe2, +0x7b, 0x39, 0xba, 0xb6, 0xc6, 0x5f, 0xaa, 0xbe, +0xc0, 0x4a, 0x8c, 0xba, 0xae, 0x39, 0x72, 0x2a, +0x9e, 0x8c, 0x12, 0x51, 0x41, 0x87, 0x50, 0x1f, +0x9f, 0xcc, 0xf9, 0xd7, 0x96, 0x0c, 0x49, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0xf4, 0xef, 0xeb, 0x35, 0x23, 0x5b, 0xdd, +0xd5, 0x02, 0x36, 0xbd, 0x47, 0x2b, 0x4c, 0x46, +0x45, 0xf9, 0x36, 0x8a, 0x1d, 0x72, 0xb3, 0x42, +0x0b, 0x87, 0xe9, 0xa7, 0x4e, 0x98, 0xfb, 0x26, +0xc3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0x9e, 0x11, 0x22, 0x40, 0xb5, +0x55, 0x6c, 0xea, 0xa2, 0xe1, 0xf2, 0x32, 0xfe, +0xb7, 0x08, 0x4f, 0xcf, 0x6e, 0xce, 0x7d, 0x28, +0x43, 0x1a, 0x55, 0xa8, 0x57, 0x61, 0x13, 0xda, +0x62, 0x09, 0x7c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0x24, 0x05, 0xca, +0xf4, 0xe0, 0x3c, 0xe4, 0x09, 0x19, 0x57, 0xfd, +0x90, 0x81, 0x63, 0x28, 0xa3, 0xd7, 0x4b, 0x98, +0x78, 0x3c, 0xeb, 0x3d, 0x02, 0x41, 0xda, 0x14, +0xcb, 0xc0, 0x76, 0xb4, 0x45, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0x10, +0x39, 0xcb, 0x82, 0xd7, 0xf4, 0xc5, 0x0b, 0x46, +0xe9, 0x35, 0x3f, 0x8a, 0x8f, 0x02, 0xd0, 0xfd, +0x34, 0xc3, 0x16, 0x50, 0x3c, 0x35, 0x9f, 0xa9, +0xef, 0xd0, 0xfb, 0x2a, 0xa5, 0x1d, 0x79, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x26, 0x70, 0x5d, 0x85, 0x7e, 0x39, 0x17, +0xe6, 0xcd, 0x9d, 0xb4, 0x02, 0x6e, 0x0b, 0x4d, +0xa6, 0xee, 0x14, 0xda, 0x46, 0xef, 0xaf, 0x79, +0x44, 0xcd, 0x94, 0x69, 0xe3, 0xba, 0xce, 0x5f, +0xdc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0x3a, 0x69, 0x40, 0x05, 0x11, +0x34, 0xe3, 0x1b, 0x57, 0x0d, 0xff, 0xca, 0x14, +0x67, 0x32, 0x3b, 0xdc, 0x2f, 0x74, 0xcc, 0x5b, +0x38, 0x03, 0x12, 0x26, 0x9f, 0x7f, 0xbd, 0x2e, +0x4a, 0x64, 0x57, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x91, 0xa4, 0x06, +0xb4, 0x53, 0x86, 0x46, 0x2c, 0xdd, 0xdb, 0x65, +0x75, 0x87, 0x27, 0x96, 0x05, 0x76, 0x5f, 0x42, +0x13, 0x5d, 0x20, 0x11, 0x15, 0x37, 0xff, 0x30, +0x7f, 0x2d, 0xfb, 0xc4, 0xf5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0xf1, +0xf5, 0x0b, 0x10, 0x0c, 0xce, 0x79, 0x71, 0x71, +0x27, 0xe9, 0x59, 0x73, 0x2d, 0x22, 0xe6, 0x2c, +0x1c, 0x1f, 0x17, 0xd9, 0x6f, 0x44, 0x01, 0x5a, +0x6d, 0xa0, 0xab, 0x86, 0x0a, 0x75, 0x95, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0xde, 0x1d, 0x37, 0x9a, 0x19, 0x8a, 0xdf, +0x48, 0x0d, 0xfd, 0xc5, 0x12, 0x49, 0xd2, 0xd3, +0xe5, 0xaf, 0x6c, 0x18, 0xc3, 0x85, 0x9c, 0xb0, +0x38, 0xe5, 0x7c, 0x3b, 0x86, 0x12, 0x11, 0x61, +0x92, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0x06, 0xed, 0xd1, 0xc4, 0xfb, +0xfd, 0xd4, 0xa8, 0x4b, 0xa2, 0x54, 0x24, 0xe8, +0xc4, 0xd3, 0xa4, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xbe, 0x31, 0x4d, 0xe9, +0x70, 0xe4, 0x8f, 0x79, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x4a, 0x82, 0xb2, +0x08, 0x41, 0xe1, 0x64, 0x89, 0x41, 0xb4, 0x4e, +0xb7, 0x40, 0xc5, 0xe8, 0xd6, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x79, +0x37, 0x3c, 0x12, 0xc9, 0xa8, 0xb4, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0x00, +0x07, 0xf8, 0xcc, 0x80, 0xf0, 0x66, 0x55, 0xc0, +0xb1, 0x33, 0xaf, 0x02, 0x16, 0x03, 0xfb, 0x9e, +0xf2, 0x0c, 0x35, 0xdf, 0xda, 0x86, 0xa3, 0xd1, +0x7e, 0x44, 0x7e, 0xc3, 0x48, 0x26, 0x6b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0x0e, 0x92, 0x1f, 0x1a, 0x43, 0xe3, 0x4f, +0xac, 0x23, 0x6d, 0xc3, 0xe5, 0x96, 0x76, 0x58, +0xce, 0x61, 0x95, 0x4a, 0x82, 0x98, 0xc8, 0x8c, +0xc3, 0x58, 0x7e, 0x17, 0xe8, 0x9a, 0x97, 0x54, +0xba, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0xe7, 0x3d, 0x7b, 0x42, 0xd1, +0x2d, 0xc6, 0x57, 0x35, 0x82, 0xfa, 0xd8, 0x59, +0xcc, 0x27, 0x91, 0xe6, 0x8b, 0x3e, 0xe4, 0xe5, +0x6a, 0x9d, 0x01, 0xb9, 0x41, 0xd0, 0xdc, 0x10, +0xf2, 0x79, 0x75, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0x15, 0x89, 0x5e, +0xcc, 0x2c, 0x81, 0x86, 0xbf, 0x7c, 0x73, 0xa9, +0xa9, 0x7a, 0x44, 0xf1, 0x30, 0xd8, 0xd2, 0xb6, +0x8c, 0x75, 0x91, 0x9a, 0x9b, 0x04, 0x73, 0xa6, +0xdc, 0x50, 0xd7, 0x08, 0x7c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0x1d, +0x19, 0x62, 0x2a, 0x38, 0xb9, 0x40, 0x8b, 0x2f, +0x37, 0xc1, 0x14, 0x9a, 0xec, 0xde, 0x7b, 0xf8, +0x79, 0x48, 0xc4, 0x29, 0xb6, 0xd2, 0x0e, 0x0a, +0xcf, 0x4d, 0x7c, 0x20, 0x45, 0xaa, 0x8e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0x39, 0x53, 0x18, 0xb7, 0xd0, 0xdc, 0x7d, +0x8b, 0xe0, 0x4c, 0x0a, 0xa8, 0x7e, 0x10, 0x47, +0x9b, 0xf3, 0x2c, 0xd3, 0x83, 0xef, 0xcf, 0xc8, +0xfa, 0xd2, 0x66, 0xc0, 0x37, 0xf8, 0xcb, 0x8a, +0xa9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0x70, 0x4e, 0x92, 0xf5, 0x6e, +0xd5, 0xa9, 0x1e, 0xe7, 0x1e, 0x74, 0x2f, 0xf1, +0x94, 0xde, 0xea, 0xe1, 0x3b, 0x94, 0x2d, 0x34, +0x51, 0xae, 0xb3, 0xc4, 0x29, 0xa1, 0x1a, 0xd1, +0x6d, 0x17, 0xf9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x29, 0xbc, 0xb2, +0x90, 0xf2, 0x31, 0xf4, 0x30, 0x4a, 0x1e, 0xd9, +0x7f, 0x6e, 0x20, 0x35, 0xc0, 0x14, 0x40, 0x82, +0x60, 0x01, 0xcc, 0xd4, 0xba, 0xf5, 0x0a, 0x5f, +0xea, 0xaa, 0x17, 0xf8, 0x67, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x2b, +0x00, 0xbe, 0x7d, 0x45, 0x94, 0x8a, 0x9a, 0xaa, +0x69, 0x2d, 0x44, 0x5b, 0x23, 0xf9, 0xd4, 0x6a, +0x04, 0xb8, 0x4f, 0xe5, 0x68, 0x34, 0x65, 0xc5, +0xf8, 0xd7, 0xbb, 0x81, 0xa8, 0x76, 0x84, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0xf9, 0x8b, 0xa7, 0x40, 0x17, 0x35, 0x0d, +0xeb, 0xd1, 0x59, 0x34, 0xbe, 0x5b, 0xd7, 0x7c, +0xe2, 0x9a, 0x92, 0xf3, 0xac, 0x56, 0xcc, 0x19, +0xce, 0xd0, 0x2a, 0x29, 0x89, 0xdc, 0x1b, 0xdb, +0x02, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0xfc, 0xfe, 0x50, 0x23, 0xca, +0x9a, 0x54, 0xfe, 0xfd, 0x6d, 0xe0, 0x80, 0xa4, +0x3a, 0xf1, 0xde, 0x6d, 0xd9, 0x7c, 0x8d, 0x11, +0x75, 0x83, 0xee, 0xa6, 0xe1, 0xaf, 0x51, 0x4f, +0xe7, 0x5e, 0xde, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0xcf, 0x8d, 0xa2, +0x4b, 0x95, 0x16, 0x7c, 0x5b, 0x53, 0x1a, 0x6f, +0x20, 0xdf, 0x3c, 0x25, 0xfb, 0x73, 0x8a, 0x28, +0xcc, 0x96, 0xc2, 0x5a, 0xe8, 0x24, 0x2b, 0x41, +0x2e, 0x2c, 0x53, 0xee, 0x00, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0x54, +0x37, 0xae, 0x9f, 0x95, 0x3f, 0x4f, 0xfa, 0x8f, +0x63, 0xc3, 0x9b, 0xd6, 0xf2, 0xf0, 0x51, 0x01, +0x6b, 0x19, 0x77, 0x27, 0x02, 0x8c, 0x21, 0xb7, +0xee, 0x11, 0xc2, 0xd1, 0xa8, 0xea, 0xd0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0xde, 0xea, 0x90, 0xa3, 0x55, 0x73, 0x9b, +0x91, 0x97, 0xa3, 0x86, 0xcc, 0x82, 0x26, 0xeb, +0x4e, 0x93, 0x42, 0x48, 0x9e, 0x34, 0x80, 0x57, +0xd5, 0xad, 0x4c, 0x4a, 0xa5, 0x21, 0x8c, 0x2a, +0xa4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0x04, 0x52, 0x2b, 0xd3, 0xc5, +0x16, 0x9d, 0x8c, 0x00, 0xc4, 0xbc, 0x71, 0x65, +0x75, 0x16, 0x14, 0xbd, 0x14, 0x12, 0x26, 0x2d, +0x77, 0x77, 0xd6, 0x5d, 0xf9, 0x47, 0x9d, 0xc0, +0xa3, 0xff, 0x39, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0x92, 0x85, 0x00, +0x86, 0xe8, 0x32, 0xf3, 0xce, 0xe4, 0xe7, 0xc4, +0x34, 0x25, 0x3a, 0x4a, 0x18, 0xe5, 0x1e, 0x44, +0x62, 0x33, 0x0e, 0xba, 0x8c, 0xf9, 0x83, 0xc0, +0xe1, 0xdf, 0x93, 0x38, 0x30, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0xfa, +0xc4, 0x54, 0x9a, 0xef, 0x8c, 0xfd, 0xde, 0x3b, +0x09, 0x6b, 0x35, 0xe7, 0x57, 0x5a, 0xe0, 0x7c, +0xd0, 0xcf, 0xc6, 0x3d, 0xda, 0xe0, 0x91, 0xc9, +0x7f, 0xf5, 0xe9, 0x04, 0xd7, 0xc8, 0xfb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0xe4, 0x19, 0xed, 0xd7, 0x67, 0xdc, 0x2d, +0x5e, 0x1f, 0x4d, 0x28, 0x58, 0x04, 0xae, 0xee, +0x41, 0x67, 0x39, 0x5f, 0x31, 0x5c, 0x11, 0x7f, +0xe9, 0x76, 0x6b, 0x6e, 0x92, 0xc5, 0x22, 0xfc, +0x61, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0xf2, 0x57, 0xbd, 0xd5, 0xed, +0xa5, 0xbc, 0xcd, 0x89, 0x65, 0x8d, 0x47, 0x4f, +0x56, 0x23, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xe7, 0x38, 0x53, 0x82, +0xc4, 0xeb, 0xc2, 0x4f, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0x4f, 0xba, 0xae, +0x0f, 0xdb, 0x76, 0x8d, 0x13, 0x60, 0xfe, 0xac, +0x58, 0x07, 0xe2, 0x46, 0xb0, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0xb3, +0x70, 0x56, 0xf9, 0x18, 0x83, 0x9c, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0x20, +0xa2, 0x50, 0x87, 0xf8, 0xc5, 0x3c, 0xed, 0xae, +0xac, 0x89, 0x2a, 0x79, 0xaf, 0x24, 0x24, 0x6e, +0x05, 0x5c, 0xdb, 0x6b, 0x75, 0x3e, 0x93, 0xff, +0xa0, 0x38, 0xed, 0xb0, 0xe1, 0xb4, 0xff, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x19, 0x54, 0xda, 0xc2, 0x4d, 0xde, 0x5d, +0xf0, 0x50, 0xf9, 0x22, 0x38, 0x14, 0x82, 0xbc, +0x1a, 0x8d, 0x4f, 0xed, 0xea, 0x04, 0x20, 0xfb, +0x86, 0x2a, 0x85, 0x38, 0xd0, 0xb6, 0x31, 0xab, +0x3e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0xdf, 0x51, 0x03, 0xc2, 0xc1, +0xd2, 0x88, 0xd1, 0xf4, 0x61, 0xda, 0xfb, 0x3b, +0x8f, 0xda, 0x07, 0xb3, 0x4a, 0x10, 0x3b, 0xa9, +0x27, 0xd3, 0xaf, 0xe0, 0xc2, 0xf9, 0x4e, 0x4c, +0x48, 0x33, 0x95, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0xa3, 0x2b, 0x18, +0x88, 0x12, 0x2f, 0x80, 0x3f, 0x24, 0xf4, 0xf5, +0xb6, 0x64, 0x1a, 0x34, 0xde, 0x04, 0x6c, 0x6a, +0xb0, 0xb6, 0x28, 0x28, 0x2e, 0x01, 0x0c, 0xa9, +0xc7, 0x93, 0x18, 0x24, 0xbb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0xd5, +0x30, 0x0d, 0xa3, 0x04, 0xb8, 0x1c, 0xd8, 0x0a, +0x70, 0x25, 0xc2, 0xeb, 0x45, 0x85, 0x30, 0xe4, +0x87, 0x47, 0x25, 0x38, 0xc0, 0x0d, 0x98, 0x5f, +0x49, 0x03, 0x3f, 0x4c, 0xff, 0xde, 0x5a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0xbb, 0x25, 0xc2, 0xe7, 0xc3, 0x22, 0xf5, +0xb5, 0xef, 0x85, 0x2c, 0x27, 0x29, 0x90, 0xbb, +0xb4, 0x12, 0xfa, 0xa7, 0xd9, 0xb0, 0x78, 0x1f, +0x1f, 0xa2, 0x93, 0xa3, 0x60, 0x39, 0xb3, 0xc9, +0xbd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0x1c, 0x68, 0x2d, 0xdc, 0x8c, +0x99, 0xd9, 0x75, 0x6f, 0x64, 0x93, 0xd3, 0x8b, +0xf7, 0x4e, 0x2a, 0x18, 0xf0, 0x9d, 0x3c, 0x84, +0x05, 0x9c, 0x12, 0xba, 0x49, 0x8d, 0x87, 0x94, +0x09, 0xa9, 0x8b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0x96, 0xea, 0xd9, +0x29, 0xac, 0x6e, 0xf1, 0x6d, 0x05, 0x1c, 0xe0, +0xfc, 0xfd, 0xcc, 0x93, 0x29, 0xee, 0x21, 0xf4, +0xf6, 0x0e, 0x87, 0x96, 0x5e, 0xb6, 0xd9, 0xcc, +0xd0, 0x92, 0x73, 0xb2, 0xd3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0x72, +0xc4, 0x5e, 0x8e, 0xcd, 0xf8, 0x97, 0x28, 0x3e, +0xba, 0x21, 0x08, 0x82, 0x2e, 0xac, 0x30, 0xa1, +0x01, 0xf1, 0xcf, 0xbc, 0x27, 0x5a, 0xd4, 0xe4, +0x65, 0x3b, 0x96, 0x17, 0xe1, 0x9d, 0xf5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0x52, 0xdf, 0x16, 0x90, 0xe7, 0x22, 0x49, +0xa0, 0x09, 0x59, 0x3e, 0x4b, 0x95, 0x20, 0x09, +0x08, 0xaf, 0x81, 0x12, 0x8b, 0x00, 0x38, 0xa2, +0xc0, 0x13, 0x79, 0x65, 0x3a, 0x15, 0x9f, 0xbf, +0x69, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0x63, 0x9c, 0x77, 0xa0, 0x61, +0x00, 0x85, 0xf8, 0x52, 0x69, 0xed, 0xcd, 0xec, +0x31, 0x9d, 0x77, 0xb8, 0x2e, 0x5c, 0xc1, 0xd0, +0xe2, 0xdf, 0x30, 0xa9, 0x50, 0x13, 0xf5, 0x37, +0x47, 0x5f, 0x80, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0xa7, 0xa6, 0xa6, +0xda, 0xa9, 0x3b, 0x94, 0xed, 0xee, 0xae, 0x99, +0x76, 0x83, 0xeb, 0xa3, 0x0e, 0x22, 0x8b, 0xcf, +0x33, 0xc3, 0x49, 0xaa, 0xcb, 0x00, 0x3d, 0xf1, +0x0c, 0xce, 0x67, 0xae, 0xe1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0x9a, +0x8b, 0x78, 0x87, 0x39, 0xb1, 0x02, 0xaa, 0x74, +0xbf, 0xb1, 0xe1, 0x31, 0x29, 0x0d, 0xc5, 0xc8, +0x34, 0xff, 0x25, 0xcf, 0x19, 0x3f, 0xfb, 0x0c, +0xda, 0xef, 0x6d, 0x0c, 0x49, 0xcc, 0x39, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0xb2, 0xa4, 0xa1, 0xc7, 0x99, 0xc0, 0x9b, +0x49, 0xbb, 0x14, 0xd0, 0xc1, 0xe7, 0x9f, 0xb2, +0x03, 0x18, 0x77, 0x4b, 0x2c, 0xca, 0x55, 0x3f, +0xe2, 0xc2, 0x2f, 0xa1, 0x63, 0x37, 0x79, 0x35, +0x57, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0x0d, 0x8d, 0x41, 0x7b, 0x34, +0xb2, 0x07, 0x2e, 0x45, 0x94, 0xea, 0x9f, 0xdf, +0xb4, 0x23, 0x96, 0x71, 0x71, 0xa9, 0x49, 0xdc, +0x73, 0x25, 0xb0, 0xa3, 0x32, 0x0d, 0x05, 0xb2, +0xeb, 0x91, 0xe2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0x25, 0x63, 0x25, +0xc5, 0x2b, 0xd2, 0xfd, 0x3e, 0xf5, 0xc6, 0x56, +0xac, 0x24, 0x41, 0xdf, 0xb1, 0xab, 0x6c, 0x37, +0x98, 0xd8, 0x4f, 0x22, 0xe4, 0x5c, 0xb0, 0xed, +0x8a, 0xe5, 0x30, 0x98, 0x90, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0x57, +0xd0, 0x17, 0x47, 0xab, 0xbe, 0x0a, 0x31, 0x25, +0x4e, 0x71, 0x3e, 0x78, 0x81, 0x31, 0x63, 0x59, +0xe8, 0xaf, 0x6f, 0x9a, 0x43, 0x99, 0x86, 0x42, +0xb9, 0xd5, 0x27, 0xd4, 0xc0, 0xfb, 0x62, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0x62, 0x76, 0x27, 0x50, 0x50, 0xcf, 0x9e, +0x84, 0x95, 0xce, 0xad, 0x84, 0x27, 0xa2, 0x98, +0xe6, 0x9d, 0xe4, 0x8b, 0x60, 0xc3, 0x82, 0xd3, +0x7a, 0x2e, 0xa1, 0x78, 0xd8, 0x86, 0xb7, 0x42, +0x1e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0xbb, 0x18, 0x05, 0x27, 0xbd, +0x42, 0xa4, 0x78, 0x66, 0x69, 0x36, 0x0b, 0x4d, +0x37, 0x8a, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x81, 0xab, 0x56, 0x46, +0xec, 0xc0, 0x42, 0x4e, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0xed, 0xf2, 0x37, +0xd5, 0x28, 0x1f, 0xdb, 0x0a, 0x6f, 0xa3, 0xc1, +0x38, 0xca, 0xc0, 0x98, 0x91, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5a, 0xb2, +0x1d, 0x77, 0x71, 0x9a, 0x93, 0xe5, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0xfb, +0xf1, 0x56, 0xb5, 0x1b, 0x23, 0x60, 0xe6, 0xfb, +0xdc, 0xe1, 0x9e, 0x8f, 0x9d, 0x16, 0x3a, 0xdb, +0x63, 0x30, 0xb8, 0x05, 0x63, 0xe7, 0x63, 0x23, +0x95, 0x68, 0x86, 0x42, 0x70, 0x90, 0x2d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0xa1, 0xda, 0x0f, 0x00, 0x45, 0xc6, 0x50, +0xe6, 0x63, 0x45, 0x4c, 0xf4, 0xb6, 0x26, 0xf6, +0x30, 0x5d, 0x8d, 0x4c, 0x41, 0x5d, 0x95, 0x05, +0xc2, 0x91, 0x9d, 0x50, 0x88, 0xd4, 0x56, 0xdc, +0x6d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0x3e, 0xf3, 0xf2, 0xba, 0xa9, +0x01, 0xae, 0x98, 0x95, 0x71, 0xcc, 0xaf, 0x85, +0xcd, 0x75, 0xc1, 0xfc, 0x04, 0x7e, 0xad, 0x7f, +0x24, 0x99, 0xd8, 0xd6, 0x29, 0xdc, 0x5c, 0x3c, +0xaf, 0x80, 0x44, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0x71, 0x92, 0xbd, +0x9c, 0x29, 0xae, 0xce, 0x77, 0x7e, 0x6f, 0x6a, +0xeb, 0x57, 0x56, 0xc2, 0x4f, 0x47, 0xe6, 0xea, +0xe2, 0x2f, 0x54, 0x98, 0x31, 0x9f, 0x7e, 0xe1, +0x5c, 0x03, 0xf9, 0x66, 0x9e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0x33, +0x0e, 0x93, 0xc7, 0xb6, 0xa4, 0x45, 0xb1, 0xb4, +0xd0, 0xe4, 0x4f, 0x85, 0xab, 0xe0, 0xa4, 0x89, +0xca, 0x19, 0x6c, 0x47, 0x3e, 0xef, 0xbc, 0x3a, +0xda, 0x59, 0x1f, 0xfe, 0x6b, 0x1a, 0xda, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x52, 0xff, 0x1d, 0x6d, 0x7c, 0x2d, 0x11, +0xfb, 0x89, 0x54, 0xf8, 0x6f, 0x0a, 0x06, 0xb9, +0x69, 0x3c, 0xdd, 0x84, 0xaf, 0x69, 0xcf, 0x9f, +0x7b, 0x7b, 0xfc, 0x68, 0xb5, 0x54, 0x4b, 0x70, +0x9d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0xdd, 0xb2, 0xff, 0x94, 0x75, +0xda, 0x92, 0xeb, 0xf5, 0xbe, 0xc8, 0xc6, 0x2a, +0x87, 0xb2, 0x91, 0x0a, 0x8e, 0xba, 0x82, 0x46, +0x55, 0xf4, 0x0b, 0x94, 0xfc, 0xdc, 0xec, 0xdf, +0xf1, 0x0d, 0x5e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0x3f, 0x13, 0x11, +0x27, 0xde, 0xb0, 0xa7, 0xd8, 0xbc, 0x12, 0xeb, +0xf5, 0x2f, 0x8e, 0x5d, 0xad, 0x26, 0x20, 0xf2, +0xaf, 0x83, 0xcc, 0x21, 0x1a, 0xd0, 0xcd, 0xf1, +0x2f, 0x44, 0xc4, 0x29, 0x7f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0x79, +0x29, 0xa6, 0xb3, 0x77, 0x69, 0x40, 0xb6, 0x8a, +0x7e, 0xc5, 0x50, 0x0f, 0x8f, 0x96, 0x78, 0x5c, +0x4a, 0x4d, 0x53, 0x37, 0xc2, 0x07, 0x6d, 0x22, +0x59, 0x50, 0x75, 0x7b, 0x69, 0x53, 0xa1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0xb3, 0x97, 0x17, 0xb8, 0xef, 0x87, 0xc7, +0xa2, 0xa8, 0x31, 0x80, 0xbc, 0x7d, 0x85, 0xe4, +0x25, 0xfe, 0x5f, 0xd9, 0xa1, 0x4c, 0x77, 0xf9, +0x88, 0x84, 0x8d, 0x3e, 0xd0, 0x13, 0x33, 0xc5, +0xf8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0x35, 0x30, 0x20, 0xf5, 0xfe, +0x50, 0x35, 0x63, 0xa6, 0x90, 0x2a, 0x7d, 0x2f, +0x09, 0xca, 0x7a, 0xba, 0xa0, 0xc4, 0x71, 0x70, +0x2f, 0x43, 0x1a, 0xe3, 0x30, 0xe2, 0x9f, 0xb5, +0xf1, 0xb3, 0x88, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0x77, 0xf5, 0x08, +0xaa, 0x2e, 0x3d, 0x03, 0x1f, 0x84, 0x12, 0x4a, +0x79, 0xf6, 0x29, 0x98, 0xc9, 0x2f, 0x4f, 0x30, +0x02, 0x6e, 0xf8, 0xa5, 0xc1, 0x63, 0x1d, 0xe0, +0x86, 0x37, 0xe6, 0xb6, 0x2c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0x50, +0x1d, 0x7c, 0x85, 0xa3, 0xc1, 0x96, 0xed, 0xf2, +0x2f, 0xee, 0x72, 0x2c, 0xfe, 0xde, 0xf1, 0x25, +0xb6, 0x7b, 0x6c, 0xdb, 0x57, 0x96, 0x5b, 0x2a, +0xca, 0xef, 0xcf, 0x72, 0x20, 0x6f, 0x3b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0xd4, 0x74, 0x8a, 0x2d, 0x74, 0x01, 0x2f, +0xdb, 0x38, 0xcc, 0xc2, 0x7b, 0x77, 0xf6, 0x91, +0x86, 0xf7, 0x87, 0xdb, 0xf9, 0x4c, 0x64, 0x63, +0x74, 0x1c, 0x8e, 0x1f, 0x05, 0xea, 0x0a, 0xe0, +0xa2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0x33, 0xee, 0xd4, 0x5f, 0x14, +0xd1, 0x19, 0x80, 0xeb, 0xa0, 0x58, 0x5c, 0x0a, +0x2c, 0xe3, 0x50, 0xff, 0xb8, 0xde, 0x64, 0xdf, +0xf7, 0x34, 0x21, 0x19, 0xea, 0x02, 0xdb, 0xb6, +0xd3, 0x3d, 0xbd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0xd8, 0x0b, 0xd4, +0x2e, 0x48, 0x6e, 0xc8, 0x83, 0x14, 0x0c, 0x7f, +0x3d, 0x94, 0x31, 0x9f, 0x84, 0x49, 0x5a, 0x4b, +0x02, 0xc4, 0x06, 0x95, 0x41, 0x29, 0x1b, 0xbd, +0xc3, 0x56, 0x35, 0xbd, 0x84, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0x50, +0xb9, 0x31, 0xc7, 0x4f, 0x46, 0x05, 0xca, 0x2a, +0x53, 0xfe, 0xe9, 0x3e, 0xa6, 0x1d, 0x1a, 0x1b, +0x2b, 0x14, 0x0f, 0x94, 0x41, 0x7e, 0xc5, 0xef, +0x64, 0x92, 0x55, 0x27, 0x01, 0xcb, 0xc3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0x73, 0x6f, 0x16, 0x6e, 0x50, 0x19, 0xe7, +0x88, 0x63, 0xc5, 0xdd, 0x3e, 0x6e, 0x94, 0xdf, +0x24, 0x95, 0x05, 0x6e, 0x1b, 0x0b, 0x2a, 0xf6, +0xbb, 0xec, 0x5e, 0x81, 0x11, 0x5a, 0xd7, 0xd1, +0x07, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0xb9, 0xfe, 0x18, 0xc4, 0x9e, +0x6a, 0xc7, 0xbf, 0x46, 0xf4, 0x9b, 0xee, 0x92, +0xe0, 0xe9, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x03, 0x8d, 0x05, 0x84, +0x53, 0x3e, 0x7d, 0x45, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0xef, 0x03, 0xce, +0xc6, 0xe0, 0x89, 0xcc, 0x88, 0xfb, 0x53, 0x5b, +0x2c, 0x63, 0x2b, 0xe5, 0xc6, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf3, 0x5a, +0x64, 0xda, 0x29, 0x83, 0xe7, 0x8c, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0x9c, +0xb9, 0xdd, 0xe9, 0xb3, 0x5f, 0x91, 0xd0, 0xd3, +0x21, 0xc8, 0x24, 0x36, 0x1a, 0x59, 0xd6, 0x14, +0x85, 0x92, 0x15, 0x1e, 0xe7, 0x8e, 0xf4, 0xd3, +0x29, 0xbc, 0xb4, 0x3f, 0xef, 0x51, 0xce, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0x6b, 0x68, 0xd7, 0x4a, 0x0a, 0xea, 0xdf, +0xd5, 0xd0, 0xee, 0x9b, 0x75, 0x4f, 0xc1, 0xb0, +0xa8, 0x9f, 0x1a, 0x73, 0x41, 0x16, 0xe4, 0xf2, +0xdd, 0xbb, 0xf4, 0x42, 0xcd, 0x5b, 0x4d, 0x10, +0xc6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0xfc, 0x4f, 0xda, 0x37, 0xd2, +0xeb, 0x14, 0xd2, 0xa2, 0x4a, 0x59, 0x70, 0x6e, +0xf0, 0xa6, 0x11, 0x5c, 0x29, 0x1a, 0x3a, 0x65, +0x29, 0x1b, 0x30, 0xf8, 0x00, 0x1e, 0xc3, 0xef, +0xa5, 0x81, 0xba, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0xec, 0x82, 0xc4, +0xe6, 0xc7, 0xfb, 0xf9, 0x40, 0x89, 0x14, 0x78, +0xc6, 0x1d, 0x1e, 0x79, 0x4a, 0x52, 0x94, 0xeb, +0xee, 0x37, 0xb8, 0xae, 0x25, 0x36, 0xcc, 0x4e, +0x01, 0x62, 0xc8, 0x1d, 0x4d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x3d, +0x79, 0xb9, 0xe3, 0x23, 0x76, 0xd7, 0x0c, 0xc3, +0x4f, 0x84, 0x21, 0x2b, 0xd8, 0x87, 0xf7, 0x50, +0x43, 0xb1, 0xab, 0x42, 0x7c, 0xd1, 0xba, 0xc6, +0x26, 0x00, 0xb2, 0x05, 0x1c, 0xee, 0x21, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0xd2, 0xc1, 0x99, 0xcd, 0x4a, 0xb6, 0x44, +0x8a, 0x3d, 0x99, 0xf1, 0x93, 0xad, 0x39, 0x5e, +0x57, 0x79, 0x1a, 0x0c, 0xfb, 0x3b, 0xa6, 0xea, +0xe7, 0x77, 0xf8, 0xfe, 0x0f, 0x5f, 0x08, 0xa3, +0x0e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0xeb, 0xc7, 0x7a, 0x07, 0x4e, +0xc9, 0x45, 0x1e, 0x1f, 0x81, 0x9b, 0x1a, 0x31, +0xff, 0x8a, 0xd5, 0xc4, 0x13, 0xb1, 0xd3, 0x02, +0x59, 0xb4, 0xbb, 0xde, 0x74, 0x89, 0x43, 0xd4, +0x0f, 0x05, 0x9b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0x6c, 0x8b, 0x33, +0x2b, 0xc9, 0x5a, 0x93, 0xd4, 0xbc, 0x74, 0x99, +0xe3, 0x53, 0xc0, 0x2d, 0xbf, 0xf4, 0x5a, 0xbe, +0x49, 0xfb, 0x41, 0x22, 0x82, 0x0a, 0x9d, 0x19, +0x0c, 0x80, 0xf8, 0x17, 0xdc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0xfd, +0xa5, 0xc4, 0x55, 0x79, 0xa2, 0xa5, 0x7d, 0xdd, +0xbf, 0x0d, 0x2c, 0x6a, 0xd1, 0x4a, 0x8b, 0x62, +0x18, 0x3e, 0x76, 0xfd, 0x4d, 0x51, 0x01, 0xbb, +0x2e, 0x7e, 0x8d, 0xb3, 0x51, 0x19, 0x00, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0x95, 0x49, 0xdf, 0x77, 0x72, 0xc9, 0x43, +0x46, 0x32, 0x6b, 0x39, 0x93, 0x07, 0xe4, 0x58, +0x58, 0x13, 0xd3, 0x8d, 0xa3, 0x8b, 0xff, 0x73, +0x9b, 0x5a, 0xee, 0xcf, 0x83, 0x87, 0x76, 0xef, +0x7e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0x4e, 0xc3, 0xc6, 0x6a, 0x13, +0xf6, 0x4b, 0x0a, 0x57, 0xaa, 0x29, 0xd1, 0x5a, +0x86, 0x56, 0x82, 0x6b, 0x5f, 0x87, 0xb3, 0xe7, +0x53, 0xc9, 0x16, 0xa7, 0x06, 0x27, 0x16, 0x68, +0x90, 0xc1, 0x28, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x94, 0x68, 0xf9, +0x32, 0xa4, 0xf7, 0x82, 0x2a, 0xd2, 0xef, 0xd1, +0x7b, 0x7b, 0x72, 0x91, 0x1a, 0x20, 0xb6, 0x86, +0xaf, 0xfb, 0x98, 0x69, 0xab, 0xd1, 0x09, 0xc2, +0xd4, 0x46, 0x6f, 0x06, 0xed, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0xab, +0xd6, 0xc6, 0x42, 0x8e, 0xbc, 0x3f, 0x1a, 0x76, +0x10, 0x94, 0xfd, 0x21, 0xee, 0xf0, 0x48, 0x7b, +0xf5, 0xe2, 0x1f, 0x42, 0x86, 0x58, 0x06, 0x12, +0xd6, 0xcd, 0xa3, 0xa5, 0x85, 0xca, 0xa4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0x96, 0x4e, 0xc4, 0xee, 0x80, 0x3e, 0x1d, +0x0d, 0xab, 0x1e, 0x90, 0x74, 0xc4, 0xf4, 0x5c, +0x77, 0x47, 0xc4, 0x6f, 0x02, 0x9e, 0xfd, 0x0e, +0x03, 0x9e, 0xcd, 0xdd, 0xf2, 0x77, 0x12, 0xd2, +0x2b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0xdd, 0xe7, 0x08, 0x64, 0x00, +0x65, 0xd8, 0x95, 0xd5, 0xeb, 0x52, 0xe7, 0xfd, +0x46, 0x9e, 0x72, 0x47, 0xeb, 0x0d, 0x52, 0x27, +0xf8, 0x18, 0x37, 0xc0, 0x80, 0x20, 0x5b, 0xec, +0x5a, 0x6b, 0x2a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x11, 0x83, 0xcc, +0x77, 0xf9, 0x70, 0x7a, 0x54, 0xec, 0x5f, 0xe9, +0x2c, 0xb2, 0x12, 0x9d, 0xfe, 0x78, 0x39, 0x9d, +0x16, 0xe4, 0x70, 0xfb, 0x34, 0x69, 0x39, 0x3f, +0x09, 0x8d, 0xbb, 0xfd, 0x43, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0x91, +0x04, 0x9a, 0x02, 0xf3, 0xde, 0x97, 0x5c, 0x09, +0x5c, 0x2d, 0xf3, 0x3a, 0xdf, 0x53, 0x59, 0x8e, +0xa1, 0x3b, 0xb9, 0xd3, 0x9c, 0xa7, 0x86, 0x23, +0xbf, 0x34, 0xd2, 0x31, 0x68, 0xb0, 0x6d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0xc1, 0xcb, 0xb8, 0x9b, 0x1f, 0xba, 0x9d, +0xf7, 0x34, 0xef, 0x16, 0xd6, 0xbf, 0x47, 0x22, +0xc2, 0xdf, 0xb0, 0xb9, 0x9b, 0xd1, 0x42, 0x50, +0x35, 0xc1, 0x12, 0x07, 0xfd, 0x37, 0x11, 0x00, +0x84, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0x5a, 0xb9, 0x0b, 0x19, 0x68, +0x75, 0x09, 0x97, 0xe1, 0x5f, 0xf2, 0xc6, 0xfd, +0xa7, 0x51, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xd9, 0x87, 0xe6, 0x49, +0x2a, 0xcb, 0x11, 0x69, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0xfa, 0x51, 0x66, +0x60, 0xb8, 0x8f, 0xb8, 0x2c, 0x24, 0x7a, 0xf5, +0xd6, 0x76, 0x08, 0x8b, 0xa7, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0xd0, +0x7b, 0x9a, 0x69, 0x77, 0x06, 0x82, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0xab, +0xe6, 0xf8, 0x3f, 0xf4, 0x9b, 0x83, 0x37, 0x30, +0x4e, 0x9b, 0x73, 0xd9, 0xce, 0x90, 0x50, 0xb1, +0x85, 0x6f, 0x9f, 0x29, 0x4d, 0x37, 0x2c, 0xe3, +0x16, 0x15, 0x5b, 0x66, 0x79, 0x08, 0xf4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0x16, 0xe2, 0x56, 0xbb, 0x03, 0x77, 0x9a, +0x94, 0x35, 0x8c, 0x23, 0x55, 0xdd, 0x39, 0x9d, +0x1f, 0xb3, 0x50, 0x7d, 0xef, 0x8e, 0x77, 0xeb, +0x0d, 0x80, 0xac, 0x35, 0x32, 0x26, 0x9a, 0x37, +0x96, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0xf4, 0x22, 0x59, 0xbc, 0x37, +0x14, 0x01, 0x7e, 0xbe, 0xae, 0xdd, 0x2b, 0x91, +0x35, 0xd6, 0x5a, 0xae, 0x04, 0x50, 0x5c, 0x58, +0x0c, 0x1c, 0x43, 0x89, 0x4a, 0xac, 0x50, 0x3c, +0xf6, 0x82, 0x4a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0x0b, 0xc8, 0x7e, +0xbb, 0xa0, 0x8d, 0x7f, 0x19, 0xac, 0x50, 0x03, +0xfb, 0x4e, 0x31, 0x81, 0x20, 0xe7, 0x73, 0xef, +0xe8, 0x15, 0xd0, 0x2d, 0xfe, 0x39, 0x31, 0x3f, +0x1c, 0xc9, 0xde, 0xeb, 0x5b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0xec, +0xb4, 0xad, 0xf4, 0xed, 0xfc, 0xfa, 0x34, 0x83, +0xb6, 0x43, 0xbc, 0xb5, 0x0c, 0xed, 0x4b, 0xf6, +0xba, 0x39, 0x4c, 0xe8, 0xb6, 0x3e, 0x6c, 0x4d, +0xa7, 0xe6, 0x75, 0x9c, 0x0b, 0xb3, 0x20, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0x8c, 0xa5, 0x2a, 0x37, 0xaa, 0x68, 0x26, +0xbd, 0x6d, 0x7f, 0x46, 0x2b, 0x1a, 0x4d, 0x37, +0x64, 0x37, 0x57, 0xf1, 0x70, 0xbc, 0xe7, 0x82, +0x4f, 0xb6, 0x88, 0x6b, 0xc9, 0x49, 0xa8, 0x3b, +0x64, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0x0f, 0xe9, 0x8f, 0x97, 0xff, +0x02, 0xfc, 0x93, 0xc5, 0x52, 0x97, 0xb7, 0xfd, +0xe6, 0xd8, 0x7c, 0x43, 0x27, 0x78, 0xc9, 0x8e, +0x87, 0x7e, 0x75, 0x2c, 0xd7, 0xf3, 0x76, 0xdb, +0xfb, 0xfb, 0x39, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0x83, 0x8a, 0x7e, +0xf2, 0x66, 0xd8, 0x3b, 0x6e, 0xc0, 0x52, 0x30, +0x9a, 0x22, 0x5e, 0x2f, 0x52, 0xae, 0x0b, 0x45, +0x0f, 0x0e, 0x85, 0xdd, 0x75, 0xf3, 0x93, 0x21, +0x82, 0x0e, 0x1f, 0x8f, 0x5d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0xde, +0x68, 0x08, 0x2b, 0x36, 0xf3, 0x9b, 0x42, 0xbb, +0x45, 0x2b, 0xb1, 0x7c, 0x72, 0x36, 0x83, 0x1c, +0xa8, 0x83, 0x4d, 0xd4, 0xbb, 0x26, 0xcb, 0x46, +0x02, 0x7f, 0x9c, 0x25, 0xb2, 0x43, 0xe3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0xe7, 0x1f, 0x7f, 0x33, 0x79, 0x2d, 0x16, +0xc6, 0x15, 0xad, 0x2e, 0x07, 0x53, 0xa2, 0xfe, +0x17, 0x09, 0x45, 0x19, 0x36, 0xc2, 0xfc, 0xdd, +0xc4, 0xb6, 0xfd, 0x0e, 0x81, 0x56, 0xa4, 0x0b, +0x0e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0x70, 0x71, 0x14, 0x0f, 0xd0, +0x8e, 0xaf, 0x65, 0x79, 0x72, 0xd1, 0x6f, 0x10, +0xe5, 0x6e, 0x47, 0xd8, 0x7e, 0x48, 0x2f, 0x54, +0x35, 0x59, 0x19, 0x30, 0x7c, 0xbe, 0xc3, 0x6d, +0x37, 0x38, 0x17, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0x0f, 0x94, 0x2f, +0xf6, 0x2c, 0x25, 0x8d, 0x8b, 0x79, 0x9f, 0xc1, +0x72, 0xe8, 0x67, 0x62, 0x74, 0x45, 0x5d, 0x02, +0x8e, 0x8b, 0xd4, 0x3c, 0x10, 0xf5, 0x68, 0x7e, +0x23, 0xd8, 0xb5, 0x55, 0x33, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0x88, +0x46, 0x95, 0xdc, 0xc4, 0x0c, 0xcc, 0x43, 0xbf, +0x19, 0x1d, 0x05, 0x05, 0x79, 0x02, 0x64, 0xcb, +0x44, 0x02, 0xd6, 0xa0, 0x6d, 0x80, 0x9d, 0xc6, +0x14, 0xa6, 0xc7, 0x61, 0x0b, 0x44, 0x01, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0xdd, 0x48, 0x70, 0xef, 0xcf, 0x00, 0x63, +0x00, 0xe1, 0xe6, 0x76, 0x9f, 0xd9, 0xcd, 0xe3, +0x60, 0xda, 0x6f, 0x85, 0xd2, 0x98, 0x89, 0x1e, +0x38, 0x7e, 0xc6, 0x0d, 0xdb, 0x63, 0xbd, 0x29, +0x82, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0x32, 0x1b, 0xdd, 0x7f, 0xcd, +0x18, 0x26, 0xdc, 0x2c, 0xf0, 0x50, 0x3b, 0x81, +0x30, 0x4a, 0x65, 0x62, 0xcc, 0x25, 0x5a, 0xb2, +0xd7, 0xd4, 0x24, 0xe9, 0x9e, 0xf7, 0x87, 0x85, +0xd6, 0xf0, 0x12, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x5c, 0x5b, 0x13, +0x38, 0x33, 0x0a, 0x29, 0x1f, 0x12, 0x46, 0xc1, +0xdb, 0xa0, 0xf6, 0x75, 0x29, 0x37, 0xa0, 0xba, +0x5f, 0xbd, 0xa6, 0x43, 0xe2, 0xd4, 0x34, 0x94, +0x69, 0x06, 0xf6, 0x84, 0xbf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x0e, +0x07, 0x83, 0x3c, 0x71, 0xe0, 0x91, 0xb2, 0x79, +0xbc, 0x67, 0x39, 0xe1, 0xc1, 0xf9, 0x2f, 0xd2, +0x67, 0x98, 0xd4, 0xf2, 0xb3, 0x72, 0x41, 0x2d, +0x8e, 0x86, 0xdf, 0x91, 0x96, 0x0b, 0xed, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x42, 0x0f, 0xf5, 0x15, 0xf6, 0x4a, 0x74, +0x9c, 0xe1, 0x02, 0x25, 0x7d, 0xb0, 0x18, 0x4a, +0x46, 0x1e, 0x61, 0x20, 0x8b, 0x5c, 0x0e, 0x0c, +0xfa, 0x01, 0xa8, 0x41, 0x59, 0x55, 0x70, 0xb0, +0xc0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0xad, 0xc0, 0x98, 0x12, 0x25, +0x8f, 0x66, 0x61, 0x77, 0x3a, 0x13, 0x5f, 0xc3, +0x46, 0xd1, 0x5b, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x8f, 0x68, 0x80, 0xef, +0xd4, 0x2d, 0xad, 0x9a, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0x95, 0xe3, 0xc0, +0x90, 0x4b, 0x08, 0x8c, 0xc1, 0xd0, 0x54, 0xba, +0xdb, 0x6a, 0x5f, 0x57, 0x56, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa6, 0xc6, +0x14, 0xe2, 0x7f, 0x7e, 0x71, 0x75, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0x20, +0x5e, 0xe9, 0xf8, 0x3e, 0x3a, 0x1c, 0x9f, 0x9f, +0x50, 0x40, 0x2b, 0x36, 0xa3, 0xfe, 0xb2, 0xf6, +0x9c, 0xb6, 0xb1, 0x2a, 0xc3, 0xe4, 0x2f, 0x5e, +0x44, 0x36, 0x16, 0x7b, 0xb9, 0xbd, 0x89, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0x09, 0x78, 0xb4, 0x8e, 0xac, 0x3b, 0xdd, +0xb1, 0x09, 0x16, 0x5f, 0x3e, 0x12, 0xd2, 0x5f, +0x51, 0x24, 0x94, 0x66, 0xc1, 0x3e, 0xfe, 0x9b, +0xe4, 0x3b, 0x32, 0xe9, 0x1f, 0xcc, 0xb9, 0x16, +0x94, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0x12, 0x1d, 0x9c, 0x1f, 0xed, +0x8e, 0x17, 0x54, 0x6d, 0x5f, 0xa6, 0x31, 0xf1, +0x92, 0x2a, 0x84, 0x87, 0xa1, 0x47, 0x9c, 0x1a, +0xeb, 0x32, 0xca, 0x0e, 0x96, 0x2c, 0x12, 0xda, +0x59, 0x54, 0x4a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0xb4, 0x4c, 0x54, +0x51, 0xa7, 0x90, 0x31, 0xda, 0x00, 0x38, 0x7d, +0xdd, 0x73, 0xdd, 0x0a, 0x74, 0x80, 0x7d, 0x0e, +0x70, 0xb0, 0x79, 0xb7, 0x2f, 0x54, 0x57, 0xae, +0x0a, 0x10, 0xed, 0x5e, 0x84, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0x9d, +0xf4, 0xd3, 0xfe, 0xec, 0xa3, 0xbb, 0xa2, 0x5b, +0x0a, 0xc7, 0x40, 0x5b, 0x16, 0xe7, 0x58, 0x68, +0xca, 0x03, 0x33, 0x50, 0xb2, 0x37, 0xc7, 0xa1, +0x80, 0xae, 0x9d, 0xe7, 0x66, 0x6e, 0xe0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0x76, 0x7a, 0x15, 0x52, 0x7f, 0x8d, 0x39, +0xd6, 0xe2, 0x7a, 0xf5, 0xed, 0xac, 0x5f, 0xd2, +0x5d, 0x95, 0x23, 0x47, 0xd8, 0xcf, 0x7a, 0x10, +0x22, 0xae, 0x54, 0x5c, 0x87, 0xda, 0xac, 0xbd, +0x11, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0xbd, 0x84, 0x17, 0xe9, 0x57, +0x3c, 0x42, 0x72, 0xa4, 0x21, 0x1a, 0xe1, 0xd9, +0xe9, 0x71, 0x04, 0xa8, 0x4f, 0xcc, 0xe1, 0xe3, +0x95, 0x1f, 0x42, 0xf9, 0x0f, 0x11, 0x83, 0x4b, +0xce, 0x52, 0xec, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0x9f, 0x0f, 0x22, +0x0b, 0x42, 0x86, 0x2e, 0x1c, 0xb4, 0x74, 0x93, +0x2e, 0x6f, 0xa7, 0x8b, 0x7c, 0xb3, 0x15, 0x29, +0x06, 0x2e, 0x55, 0x84, 0x96, 0x79, 0x76, 0xd9, +0xcb, 0xea, 0x60, 0x42, 0x97, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0x6f, +0xae, 0x6a, 0x3c, 0x64, 0xfb, 0xc4, 0x90, 0xd3, +0xcb, 0x96, 0xec, 0x82, 0xd0, 0x27, 0x75, 0x51, +0x11, 0xb6, 0xf4, 0x2a, 0xcd, 0xee, 0x34, 0x44, +0x7c, 0xd4, 0x00, 0x2f, 0x9e, 0x8e, 0xfa, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0xd7, 0xc3, 0xea, 0xb0, 0x76, 0xf9, 0xf4, +0xd3, 0x62, 0xaa, 0xeb, 0xf5, 0xd1, 0x17, 0x14, +0x05, 0xf8, 0x9a, 0xc6, 0x9f, 0x19, 0xa8, 0xea, +0x98, 0x3e, 0x68, 0xa0, 0x00, 0xaa, 0xc7, 0x71, +0xac, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0x32, 0xdf, 0x70, 0xd7, 0x8b, +0xf6, 0x3b, 0xbb, 0xd8, 0x52, 0x64, 0xf9, 0x76, +0x44, 0xe2, 0x34, 0x82, 0x92, 0x41, 0xe5, 0x83, +0x54, 0x1b, 0xdd, 0x4c, 0x8a, 0xc7, 0x9e, 0x2d, +0x52, 0x22, 0x72, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0xdc, 0x7b, 0xc6, +0x51, 0xc4, 0x3b, 0x29, 0x4d, 0xcd, 0xbc, 0xa3, +0x99, 0x6b, 0xe8, 0x8c, 0xdc, 0xfd, 0xaa, 0xd2, +0x35, 0x3e, 0xc9, 0xe7, 0xca, 0xc1, 0x06, 0x31, +0x9f, 0xc4, 0x8f, 0x9f, 0x4f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0xea, +0x08, 0xb5, 0x31, 0x61, 0xc8, 0x63, 0x28, 0xd3, +0xae, 0xde, 0xfb, 0x5e, 0xca, 0x71, 0xef, 0x73, +0xed, 0x03, 0x3d, 0x61, 0x7b, 0x99, 0x59, 0x3b, +0x66, 0x40, 0xbe, 0xf8, 0xa5, 0xb0, 0xeb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0x43, 0x8b, 0x3d, 0xcd, 0xbb, 0x1b, 0xd8, +0xbf, 0xe8, 0x1b, 0x41, 0x30, 0x90, 0x1f, 0x6d, +0x23, 0x32, 0xf6, 0xe3, 0x4d, 0x10, 0xe0, 0x02, +0x49, 0x35, 0x8b, 0xfd, 0xc6, 0xa0, 0x04, 0x15, +0xe9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0xcc, 0x92, 0xf8, 0x05, 0x3f, +0x24, 0x7e, 0x11, 0x02, 0x8f, 0x26, 0xeb, 0x10, +0x96, 0xe7, 0x85, 0x55, 0x62, 0x82, 0x44, 0x0e, +0xbb, 0x60, 0xfa, 0xa2, 0x08, 0x2a, 0xa2, 0x48, +0x73, 0x9e, 0x85, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0xe5, 0x00, 0x07, +0x29, 0x2d, 0x47, 0x39, 0xbe, 0xa4, 0x74, 0x76, +0xf5, 0xda, 0x22, 0x88, 0xea, 0xb7, 0x39, 0x19, +0xf0, 0xf4, 0xbd, 0xfc, 0xa4, 0x8f, 0x16, 0xdd, +0x8f, 0x39, 0x89, 0xc7, 0x76, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0x90, +0x8e, 0x6e, 0xb4, 0x86, 0x3e, 0x88, 0xe8, 0x64, +0xce, 0x79, 0x28, 0x1e, 0x30, 0x48, 0xc7, 0x81, +0x20, 0x58, 0x9c, 0x55, 0xfa, 0x9f, 0x2f, 0x6a, +0xe7, 0x69, 0x08, 0x40, 0xb4, 0x72, 0x3a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0x6e, 0x6a, 0x4e, 0x2f, 0x16, 0xa1, 0x17, +0x8a, 0x2f, 0x82, 0x6f, 0xe2, 0xac, 0x46, 0x31, +0x17, 0x98, 0x2f, 0xcd, 0x56, 0xce, 0xa3, 0xa0, +0xff, 0x35, 0x3b, 0x3b, 0x07, 0xb4, 0x08, 0xdf, +0xfb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0x24, 0x1a, 0xcc, 0xb8, 0xda, +0xce, 0xe7, 0xa7, 0x56, 0x20, 0xa0, 0x4a, 0x31, +0xfc, 0xfc, 0xcf, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x81, 0x6b, 0x88, 0x00, +0x2c, 0xdf, 0x58, 0xaf, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0xe6, 0xb7, 0x78, +0x07, 0xbb, 0xf5, 0x9e, 0x2b, 0x57, 0xfa, 0x4b, +0x9b, 0x85, 0x69, 0xf5, 0xba, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe9, 0x59, +0xa9, 0xdc, 0xc0, 0x80, 0x0f, 0x9b, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0x0f, +0x87, 0x0d, 0xa1, 0x30, 0xe1, 0x45, 0x5a, 0xcf, +0x4c, 0x8d, 0xb8, 0x23, 0xe5, 0xe0, 0x9d, 0xa4, +0x57, 0xeb, 0x23, 0x7d, 0x87, 0x67, 0x78, 0xcf, +0xd0, 0x4a, 0x54, 0xa1, 0xd7, 0x89, 0xba, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0x59, 0x8f, 0x2b, 0x8d, 0x96, 0x72, 0x83, +0x0e, 0x99, 0xfc, 0x07, 0xba, 0x04, 0x4c, 0xb4, +0xfe, 0xed, 0xc1, 0x06, 0x2d, 0x52, 0xae, 0xb0, +0x78, 0x89, 0xee, 0xa0, 0x9c, 0xbc, 0x28, 0x9b, +0x36, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0xb5, 0x7e, 0x99, 0xc9, 0x89, +0x3b, 0x0e, 0x41, 0xcb, 0xa4, 0x5d, 0x35, 0x78, +0x68, 0xe8, 0xf6, 0xd7, 0xee, 0x8d, 0x3a, 0x10, +0x8c, 0x63, 0xcf, 0xd4, 0xcd, 0x7b, 0xd4, 0xad, +0xfc, 0xbe, 0x59, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0x42, 0xdc, 0x1d, +0xd3, 0x6e, 0xd9, 0x88, 0xe8, 0x3d, 0x95, 0xef, +0x13, 0x96, 0xd3, 0xf2, 0x2f, 0x26, 0x31, 0xd6, +0xae, 0xb2, 0xa0, 0xad, 0xc8, 0x63, 0x0e, 0x79, +0x0e, 0x65, 0x5d, 0x70, 0xe1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0x8e, +0x5b, 0xa7, 0xab, 0xf9, 0xaf, 0xbd, 0x6a, 0xee, +0xf3, 0xf3, 0x52, 0x51, 0xaa, 0x62, 0x20, 0xdd, +0xd2, 0x63, 0x3c, 0x60, 0xcb, 0x47, 0x23, 0x1d, +0xe4, 0x48, 0xec, 0x25, 0x23, 0x41, 0x7c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0xea, 0x8d, 0xea, 0x1b, 0xc9, 0xc7, 0xb5, +0x5e, 0x92, 0x7b, 0xdd, 0x00, 0x83, 0xb6, 0x48, +0xbe, 0x90, 0x27, 0x5a, 0xfa, 0x0c, 0xf1, 0x43, +0x06, 0x7e, 0xe5, 0x34, 0x70, 0x74, 0xb8, 0xf9, +0x24, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0x9d, 0x98, 0x26, 0xa2, 0x18, +0xa8, 0x18, 0x71, 0x0e, 0x41, 0xdc, 0x2e, 0x80, +0xba, 0x95, 0x0e, 0x6f, 0x28, 0x61, 0x9d, 0xe8, +0xd3, 0x22, 0x3f, 0x7f, 0x66, 0x18, 0x4c, 0x0e, +0x0d, 0x02, 0xb5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0xdd, 0xba, 0xc4, +0xa4, 0x91, 0x12, 0xec, 0x46, 0xb1, 0x23, 0x6e, +0xaf, 0x24, 0x2c, 0xfb, 0x3a, 0x0e, 0x8b, 0x18, +0x6b, 0x89, 0xd7, 0x33, 0xd6, 0xc5, 0xa3, 0x5a, +0xcf, 0xb7, 0xbf, 0x07, 0xa5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0x37, +0x90, 0x28, 0x8b, 0x32, 0x30, 0xf1, 0x50, 0xd6, +0x02, 0xf9, 0x50, 0x74, 0x68, 0x21, 0x26, 0xa4, +0xba, 0x90, 0x69, 0x1e, 0x91, 0x98, 0xf5, 0x97, +0x4d, 0xad, 0x45, 0x47, 0x14, 0x1b, 0xaf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0xfe, 0x1a, 0x2b, 0x12, 0xd8, 0x50, 0xb6, +0x06, 0xc2, 0x57, 0x27, 0x53, 0x80, 0xb6, 0x23, +0x72, 0x8b, 0xff, 0x53, 0x4d, 0x68, 0x97, 0x57, +0x61, 0xd9, 0x10, 0x0c, 0x9c, 0x0b, 0x6c, 0x96, +0xa2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0x94, 0x4d, 0x40, 0xc4, 0x29, +0x32, 0xca, 0x3e, 0xd6, 0x16, 0xdc, 0x70, 0xd6, +0xd3, 0x60, 0xe0, 0xd6, 0x3e, 0x14, 0x51, 0x38, +0xfc, 0xef, 0xd3, 0x51, 0xee, 0x64, 0x2a, 0x91, +0xbc, 0x4f, 0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x3d, 0x0a, 0xe2, +0x4f, 0x12, 0x5e, 0x01, 0x27, 0x25, 0xf9, 0x38, +0xe9, 0x2d, 0x23, 0x6e, 0x11, 0x80, 0x09, 0xc6, +0xc4, 0x5b, 0x07, 0x7f, 0xf4, 0x75, 0x60, 0xcd, +0xd9, 0xa1, 0xfa, 0x36, 0xb3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0x79, +0xab, 0xb1, 0x35, 0x7a, 0x4e, 0xa6, 0xae, 0xc9, +0xf3, 0x86, 0x4a, 0x4a, 0xfe, 0xac, 0x7b, 0x1c, +0x47, 0x91, 0x2c, 0x2f, 0x7a, 0x3b, 0x10, 0x0d, +0x9b, 0x11, 0x1d, 0xc3, 0x50, 0xbf, 0x9c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x25, 0x71, 0x0e, 0x49, 0x03, 0xa5, 0x38, +0xb1, 0x38, 0x41, 0x98, 0xb2, 0x27, 0x11, 0xe7, +0x95, 0x87, 0x38, 0x2f, 0x98, 0x75, 0x65, 0xc9, +0xb3, 0x2c, 0x84, 0x88, 0x60, 0x17, 0xa0, 0x04, +0xc7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0x0e, 0x3e, 0x3c, 0x4e, 0xf3, +0xdc, 0xea, 0xd0, 0x32, 0xe8, 0xf1, 0x77, 0x9a, +0x91, 0x5b, 0x62, 0xa0, 0x65, 0x4f, 0xe4, 0xfc, +0x34, 0xa0, 0xbb, 0xf4, 0x67, 0x56, 0x44, 0x9b, +0x6e, 0xb6, 0x9e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0x02, 0x0a, 0x5c, +0xcb, 0x68, 0x2f, 0xe2, 0xfb, 0x29, 0x50, 0x7c, +0x9d, 0xc3, 0x38, 0xe0, 0xc9, 0x02, 0x2b, 0x1f, +0x9f, 0xfb, 0x30, 0x14, 0x9f, 0x24, 0xb2, 0xb8, +0xed, 0x37, 0x48, 0x00, 0xa6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x01, +0xd3, 0x7f, 0x2f, 0x76, 0xb4, 0xcb, 0x22, 0x72, +0x93, 0xd4, 0xec, 0x23, 0x91, 0xc0, 0xf0, 0x81, +0xbc, 0x30, 0xb1, 0x9f, 0x05, 0x9c, 0x52, 0x46, +0x4f, 0xe9, 0x59, 0x0c, 0x7e, 0xa4, 0x7b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0x74, 0x45, 0x09, 0x7c, 0x8b, 0x58, 0x81, +0xbb, 0x6d, 0xe3, 0x80, 0xa3, 0x0d, 0x2a, 0x04, +0xb2, 0x61, 0x4d, 0x37, 0x5f, 0x5c, 0x20, 0xde, +0x04, 0xba, 0xeb, 0x37, 0x34, 0xea, 0xbf, 0xf2, +0x9f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0xc7, 0x7d, 0x74, 0xbd, 0xd6, +0x21, 0x57, 0x04, 0x1e, 0xc1, 0x1c, 0x0b, 0xe3, +0x0c, 0x42, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xc3, 0x74, 0x67, 0xba, +0xcd, 0xb8, 0x05, 0x41, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0xb4, 0x0c, 0x26, +0x45, 0xa0, 0xd9, 0x87, 0xab, 0x89, 0x88, 0xfd, +0x6f, 0x05, 0xeb, 0x20, 0xba, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d, 0x7e, +0x5b, 0x12, 0x4f, 0x3b, 0x58, 0xee, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0xf6, +0x8b, 0xfd, 0xd5, 0x4c, 0xba, 0xe5, 0x99, 0xca, +0xcf, 0x96, 0xb1, 0x76, 0x4a, 0xbc, 0x6c, 0x7d, +0x24, 0x29, 0xe2, 0x33, 0xaa, 0x40, 0xc6, 0x8e, +0xfc, 0x52, 0x87, 0xf2, 0x72, 0x73, 0x5e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0xa7, 0xfe, 0x31, 0xe1, 0x72, 0x62, 0xe9, +0xd5, 0x6a, 0xd1, 0xd2, 0x02, 0x7f, 0x9f, 0xa3, +0xd6, 0x2c, 0xad, 0x1f, 0x3c, 0x90, 0x5e, 0x8c, +0x57, 0x5e, 0x3a, 0xbb, 0xed, 0xc7, 0xbb, 0x76, +0x96, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0xc1, 0xd4, 0x76, 0x3b, 0xfd, +0x83, 0xc8, 0xe0, 0x91, 0x8d, 0x4f, 0xbc, 0xe2, +0xcf, 0xf0, 0xb7, 0xb4, 0xbd, 0xf8, 0xec, 0x52, +0xa9, 0xa0, 0xcb, 0xfd, 0x9f, 0xa0, 0xd9, 0x6b, +0x1c, 0xdc, 0xa7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0x26, 0x72, 0x49, +0x8a, 0xa7, 0xcf, 0xdb, 0xe4, 0xf8, 0x22, 0x2b, +0xcd, 0x26, 0x22, 0x77, 0x6b, 0x00, 0xa7, 0x33, +0x1d, 0xf5, 0x5d, 0xb8, 0xca, 0x8b, 0xb9, 0xa9, +0xe2, 0xc2, 0x3f, 0x83, 0xdd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0xdb, +0xd0, 0x47, 0xa8, 0xad, 0xb7, 0x17, 0xa6, 0x79, +0xa1, 0x61, 0x33, 0x2c, 0xa4, 0xd8, 0xda, 0xf2, +0x09, 0x48, 0x81, 0xa7, 0x23, 0x2d, 0xf3, 0xe7, +0xbe, 0xa7, 0x7b, 0x8b, 0x32, 0x8b, 0x5f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0x63, 0x86, 0x9a, 0xb7, 0x8e, 0xff, 0x8f, +0x16, 0x6c, 0xeb, 0xc1, 0x41, 0x30, 0x58, 0x57, +0x99, 0x88, 0xd7, 0x69, 0x77, 0x8e, 0x9d, 0xd7, +0x37, 0x13, 0xc6, 0x4a, 0x13, 0x74, 0xcf, 0x90, +0xe1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0x68, 0xa6, 0xa8, 0x5b, 0x3a, +0x20, 0x91, 0xd2, 0x8e, 0xe0, 0x95, 0xa5, 0xa0, +0x40, 0x39, 0xf4, 0x1f, 0x44, 0x1f, 0x3e, 0x65, +0x75, 0x18, 0xc9, 0x67, 0xad, 0x01, 0x16, 0xac, +0x0e, 0xd0, 0xf1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x5d, 0xd0, 0xc2, +0xf5, 0x02, 0x8c, 0xb2, 0xcc, 0x51, 0x6c, 0x9b, +0xca, 0x32, 0x0d, 0x22, 0x76, 0xfb, 0x49, 0xc3, +0x17, 0x02, 0x47, 0xfd, 0x19, 0x79, 0xf8, 0x77, +0xe0, 0xa8, 0x40, 0xb4, 0xd3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0x13, +0x0c, 0x94, 0x89, 0x1b, 0x70, 0xdb, 0xca, 0x88, +0x9c, 0x8c, 0xb1, 0x81, 0xb9, 0x8c, 0x58, 0x25, +0x9d, 0x6d, 0xb8, 0x0e, 0xc0, 0x89, 0x7c, 0x0a, +0x40, 0x8a, 0x04, 0x54, 0xf3, 0xe3, 0xfa, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0x26, 0xb7, 0x50, 0xdb, 0x01, 0x92, 0xb1, +0xb1, 0xb8, 0x46, 0xe9, 0x24, 0x3d, 0xb2, 0xff, +0xcd, 0x20, 0xe2, 0x5d, 0xd8, 0x5e, 0xc5, 0xb6, +0x06, 0x42, 0xfa, 0xc3, 0x5c, 0x60, 0x2f, 0x8e, +0x5b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0x2f, 0xb4, 0x90, 0xb9, 0x56, +0x31, 0x53, 0xd9, 0xb4, 0x50, 0xee, 0xa8, 0x48, +0x8c, 0x38, 0x06, 0x77, 0x51, 0x53, 0x16, 0x69, +0x93, 0x16, 0xac, 0xe7, 0x0f, 0xb7, 0x5a, 0x45, +0x04, 0x21, 0x17, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0xae, 0x9f, 0x32, +0xef, 0x67, 0x61, 0xcf, 0xcb, 0xd8, 0x5a, 0x1a, +0xc6, 0x49, 0x84, 0x9a, 0xbc, 0x05, 0x41, 0x30, +0x45, 0x67, 0xf8, 0x40, 0x2f, 0xfc, 0xd2, 0x5a, +0x68, 0xc2, 0xb2, 0xda, 0xcc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0x79, +0x4d, 0x7a, 0x6b, 0x7a, 0x3a, 0xca, 0x42, 0x8d, +0x60, 0xa3, 0xb6, 0x3d, 0xa9, 0xb9, 0x72, 0xe7, +0x22, 0x9a, 0xc7, 0xb5, 0xa9, 0xb6, 0x08, 0xa3, +0xa8, 0xa3, 0xfa, 0x59, 0xb0, 0x18, 0x44, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x6d, 0xa6, 0xa0, 0xe8, 0x56, 0x21, 0x03, +0x42, 0xe4, 0x04, 0xd6, 0xb8, 0xc7, 0xc0, 0x85, +0x10, 0x29, 0x70, 0x7f, 0xf4, 0xa0, 0x8b, 0x37, +0xaf, 0xcb, 0x31, 0x51, 0x24, 0x3d, 0x8a, 0x47, +0x76, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0xed, 0xfb, 0x5f, 0x1b, 0x7f, +0xc4, 0x75, 0x2b, 0xa1, 0x85, 0xad, 0xc7, 0xa7, +0xd1, 0x8c, 0xdb, 0x81, 0x67, 0xae, 0x8a, 0x15, +0xd7, 0x58, 0xc1, 0xba, 0x3a, 0xbd, 0x26, 0xc8, +0x63, 0x86, 0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0xe3, 0x7d, 0x08, +0xf3, 0xbf, 0xb5, 0x92, 0xce, 0x38, 0x21, 0xf6, +0xa7, 0xd1, 0xa8, 0x7a, 0x58, 0x48, 0x2f, 0x03, +0x9a, 0x80, 0x32, 0x20, 0x3d, 0x13, 0xd3, 0xd9, +0x3b, 0x65, 0x31, 0xa3, 0x5f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0xd0, +0x71, 0xa8, 0x6e, 0x48, 0xb4, 0x8e, 0x18, 0x89, +0xcd, 0x0a, 0x9a, 0xbe, 0xd8, 0xd5, 0xe8, 0xe5, +0x53, 0xce, 0x59, 0xea, 0xd2, 0x4c, 0xc3, 0x36, +0x46, 0x39, 0xe5, 0x59, 0xb9, 0x6b, 0xa0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x23, 0x0a, 0x3a, 0x70, 0x65, 0x33, 0xdb, +0x72, 0xc0, 0x1b, 0x9d, 0xf0, 0xe0, 0xcd, 0xf9, +0xd0, 0x6f, 0xe6, 0x63, 0x04, 0xe3, 0x56, 0xc4, +0x82, 0xcd, 0xaa, 0x50, 0x83, 0x30, 0xa6, 0x5e, +0x02, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0xc1, 0x40, 0xde, 0x9f, 0xd0, +0x99, 0x17, 0x9e, 0x0f, 0xde, 0x3f, 0xf1, 0x5f, +0x71, 0x2b, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x0d, 0x2a, 0x24, 0xa8, +0x0a, 0x07, 0x85, 0x36, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0x09, 0x1c, 0xc4, +0x4e, 0x73, 0x00, 0x24, 0x03, 0xad, 0xb3, 0x2f, +0x95, 0x0e, 0x41, 0x6d, 0x12, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb9, 0xef, +0x06, 0x40, 0xfd, 0x28, 0x1e, 0x49, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0xad, +0xeb, 0x16, 0x0f, 0xb3, 0xba, 0x43, 0x8f, 0x13, +0xe2, 0x47, 0x73, 0x5b, 0x4a, 0x26, 0x28, 0x22, +0xce, 0xe6, 0xdd, 0x46, 0x15, 0xaa, 0x4c, 0xc9, +0x28, 0x28, 0xbb, 0x05, 0x03, 0x83, 0xb7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0x66, 0xa5, 0xa4, 0x61, 0x1e, 0x57, 0x4c, +0xfa, 0xe0, 0xff, 0x28, 0x41, 0x86, 0x70, 0xa7, +0x8a, 0x83, 0x0d, 0xda, 0x03, 0x59, 0x51, 0xf9, +0xcf, 0x05, 0xb0, 0x7f, 0x42, 0x5b, 0xb2, 0x9b, +0xf8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0xad, 0x3e, 0x5b, 0xbc, 0xc0, +0x5d, 0x91, 0xbd, 0xed, 0x9e, 0xa8, 0xe0, 0x37, +0x32, 0x36, 0xda, 0x5f, 0xb3, 0xe9, 0x02, 0xfa, +0xea, 0x76, 0x88, 0x51, 0x2e, 0xb2, 0xb7, 0x9f, +0xb8, 0x87, 0x0e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0x7b, 0x00, 0xe7, +0x7e, 0x16, 0xb3, 0x87, 0x83, 0x75, 0x34, 0xa9, +0xad, 0x30, 0xe2, 0x47, 0x57, 0xf0, 0x01, 0x67, +0x36, 0xa6, 0xa5, 0x18, 0xe4, 0x27, 0x32, 0xd8, +0x1c, 0xe3, 0x9d, 0x76, 0x99, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0x82, +0xc2, 0xb9, 0x57, 0xb5, 0x9d, 0xd1, 0x5b, 0x6d, +0x20, 0x7c, 0x28, 0xd5, 0xf5, 0x13, 0x74, 0x75, +0x48, 0x4a, 0x4d, 0x3e, 0x99, 0x6f, 0x20, 0x39, +0xeb, 0x54, 0xf9, 0x1e, 0xa9, 0x79, 0x54, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0x5e, 0x16, 0xf0, 0x1a, 0xe6, 0x0c, 0x9f, +0x15, 0x34, 0xe3, 0x19, 0xc8, 0x3a, 0x6d, 0xb4, +0x16, 0x9e, 0x99, 0xee, 0x0e, 0x0e, 0xc5, 0x35, +0x0a, 0xba, 0x91, 0x6a, 0x26, 0x09, 0x80, 0x77, +0x5f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0x40, 0x1e, 0x86, 0x09, 0x10, +0xf8, 0x7d, 0xfe, 0xfe, 0x41, 0xff, 0x23, 0x9c, +0x3d, 0x3c, 0x88, 0x71, 0x2e, 0xb7, 0x62, 0x95, +0x75, 0x56, 0x7f, 0x9b, 0x20, 0x5c, 0x5b, 0x57, +0x79, 0x73, 0x1e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0xd3, 0x58, 0x73, +0xe4, 0x0e, 0x0c, 0x7e, 0xe7, 0x81, 0xcc, 0x45, +0x9e, 0x76, 0x8b, 0x29, 0x24, 0x93, 0xfd, 0x00, +0x96, 0x6e, 0xcb, 0x36, 0xa3, 0x01, 0x79, 0x37, +0xe9, 0x39, 0xf2, 0x49, 0xe7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0x7d, +0xa8, 0x0b, 0x91, 0xec, 0xf1, 0x10, 0x36, 0x43, +0x1c, 0xe7, 0xbe, 0xc1, 0xac, 0x86, 0x92, 0xe2, +0x6d, 0x2c, 0xc6, 0x41, 0xa0, 0x74, 0xbd, 0x6c, +0xa8, 0x6f, 0xc5, 0xe1, 0xe6, 0x17, 0xea, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0xfb, 0x07, 0xb5, 0x75, 0x6c, 0x11, 0x5f, +0xfb, 0x89, 0x07, 0xed, 0xae, 0x4b, 0x60, 0x27, +0x2a, 0xc6, 0xfb, 0x14, 0x45, 0x8b, 0x63, 0xfd, +0xad, 0x75, 0xc0, 0x2c, 0xa2, 0xfd, 0x1f, 0xce, +0x24, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0x8f, 0xb7, 0x29, 0x27, 0x7a, +0x52, 0xf6, 0x99, 0xcd, 0x14, 0xd4, 0xc8, 0x9f, +0x54, 0xe6, 0x5d, 0xce, 0x9d, 0xa3, 0xf5, 0x4c, +0x53, 0x31, 0xa2, 0x11, 0x91, 0xea, 0x61, 0xbb, +0x7d, 0x89, 0x01, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0x36, 0xcf, 0x78, +0x9a, 0xde, 0xd1, 0x98, 0x7a, 0xad, 0x4c, 0xe7, +0x4e, 0x96, 0x08, 0x30, 0xd5, 0xf1, 0x29, 0xa6, +0xb5, 0x0a, 0x9c, 0xef, 0x1e, 0x75, 0x43, 0x5c, +0x1a, 0x44, 0x44, 0x4a, 0x6e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0x03, +0x61, 0x84, 0x2c, 0x82, 0x19, 0xe0, 0x5b, 0xd0, +0x6b, 0x0c, 0x09, 0xf2, 0xa3, 0x66, 0xf0, 0xa8, +0xe1, 0x09, 0xb8, 0x31, 0x50, 0xa5, 0xc2, 0xc1, +0xc8, 0x5e, 0xe2, 0x59, 0xdf, 0x2e, 0x2f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0x0b, 0x40, 0xf4, 0x0b, 0xce, 0x07, 0x08, +0xd4, 0xc4, 0xc9, 0x7c, 0xab, 0xc3, 0x24, 0xfc, +0xbd, 0xdc, 0xc2, 0x6b, 0xa1, 0x93, 0x58, 0x43, +0xfe, 0x1e, 0x9b, 0x1e, 0x16, 0x3c, 0x5f, 0x0c, +0x82, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0xf9, 0x01, 0x6c, 0x80, 0x75, +0x4c, 0x4f, 0x4c, 0x74, 0x4c, 0xc0, 0x5c, 0xab, +0xa2, 0x30, 0x31, 0x50, 0xb7, 0x13, 0x9f, 0x78, +0xc1, 0xf0, 0xb4, 0x38, 0x2f, 0xa4, 0x2a, 0xfc, +0x5d, 0xd9, 0xe1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0x7b, 0x5d, 0xef, +0x2f, 0x8e, 0x15, 0xe1, 0x60, 0x17, 0xed, 0x35, +0x04, 0x51, 0xdc, 0x9c, 0x79, 0x7d, 0xe9, 0xae, +0x26, 0x55, 0x98, 0x2e, 0xab, 0x49, 0xa9, 0xc8, +0x46, 0xb7, 0xce, 0xe0, 0x74, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0x6c, +0x57, 0x53, 0x46, 0x7d, 0xa2, 0x06, 0xbe, 0xd0, +0x6e, 0xc9, 0xba, 0x44, 0xc1, 0xf4, 0xab, 0x4e, +0x50, 0x8b, 0x50, 0x85, 0x3d, 0x47, 0xc8, 0x01, +0xe1, 0x29, 0xc3, 0x1f, 0x0b, 0xf2, 0x9d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0x03, 0xad, 0xa3, 0xd9, 0xcf, 0xf9, 0x9b, +0x68, 0xd3, 0x98, 0x76, 0xa1, 0x3e, 0xef, 0x6f, +0x51, 0x59, 0x75, 0x9f, 0x70, 0x80, 0xa9, 0x16, +0x30, 0x3c, 0x25, 0x2f, 0x04, 0x04, 0x21, 0x10, +0x39, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x4b, 0x43, 0x3d, 0xd5, 0x41, +0x40, 0x91, 0x75, 0xe7, 0x57, 0x02, 0x35, 0xab, +0x0f, 0xac, 0xca, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xde, 0x94, 0xe3, 0x34, +0x2c, 0x55, 0x37, 0x04, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0x60, 0x75, 0x44, +0x02, 0x7e, 0x53, 0xf4, 0x92, 0xb5, 0x8a, 0xce, +0x56, 0x2a, 0xfe, 0xfd, 0x42, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0xe5, +0x8b, 0x0c, 0x0d, 0x5b, 0x51, 0x9c, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0xe4, +0x89, 0x53, 0xff, 0x62, 0xe2, 0x42, 0xa6, 0x0e, +0xb4, 0x9e, 0x7c, 0xb3, 0xbe, 0xf8, 0x33, 0x3f, +0x4d, 0x4d, 0x90, 0x35, 0x97, 0x42, 0x57, 0x90, +0x07, 0x98, 0xdc, 0x42, 0x20, 0xcd, 0x75, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x95, 0x41, 0xef, 0x5d, 0xf5, 0xcd, 0x31, +0x14, 0xac, 0xb2, 0x41, 0xec, 0xca, 0x60, 0x27, +0x02, 0x9b, 0xd2, 0xbe, 0x8a, 0x8d, 0xba, 0x35, +0xca, 0xa5, 0x8e, 0x13, 0xc9, 0xa7, 0x14, 0xb7, +0x94, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x04, 0x01, 0xca, 0xf2, 0x75, +0x21, 0x87, 0x00, 0x77, 0x28, 0x6f, 0xa4, 0xba, +0xac, 0x35, 0x2d, 0x63, 0x25, 0xac, 0x03, 0x24, +0x43, 0x73, 0xc8, 0x36, 0xea, 0xf7, 0xc4, 0xbb, +0x05, 0xe3, 0x29, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0xd2, 0x12, 0x3c, +0x5c, 0x89, 0xca, 0xb0, 0x09, 0x6c, 0x8e, 0xf2, +0x0f, 0x4c, 0xf8, 0x38, 0xac, 0x77, 0x82, 0x9f, +0xd2, 0x17, 0x06, 0xc6, 0x9c, 0x2f, 0x16, 0x8b, +0x72, 0x7b, 0x2b, 0x17, 0x0b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0x64, +0xb1, 0xcd, 0x22, 0x5b, 0x67, 0x44, 0x70, 0xd1, +0xaf, 0x93, 0xfc, 0x12, 0xac, 0xc2, 0x46, 0xb4, +0x9f, 0xb8, 0x10, 0xa5, 0xb8, 0x56, 0x43, 0xc5, +0xf3, 0x67, 0x3c, 0x08, 0xa5, 0x21, 0xf8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0x16, 0x45, 0x07, 0xfa, 0xd8, 0xdb, 0x22, +0xe3, 0xb3, 0x0a, 0xa3, 0x6c, 0x23, 0xa8, 0xad, +0x28, 0x98, 0x03, 0xcd, 0xc2, 0x92, 0x09, 0xa9, +0xf2, 0x3e, 0xf6, 0xcf, 0x37, 0x6c, 0x90, 0x22, +0xf1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0x08, 0xb4, 0xbd, 0xb5, 0xf3, +0x63, 0x1a, 0x2c, 0x31, 0x2a, 0xe7, 0xee, 0x1b, +0xd0, 0x51, 0xca, 0x1d, 0x96, 0x15, 0xd7, 0x4f, +0x0a, 0x5c, 0x02, 0x04, 0xbf, 0x9d, 0x62, 0x08, +0xa6, 0xa1, 0xe5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x9b, 0xe8, 0xed, +0xb6, 0x81, 0x00, 0x35, 0x58, 0xd2, 0x84, 0xbc, +0x0b, 0x60, 0xbd, 0x0a, 0x89, 0x77, 0x2c, 0xf6, +0x6b, 0xc1, 0x72, 0xb6, 0x72, 0xfe, 0x34, 0xc1, +0xc8, 0xa0, 0x66, 0xa9, 0xe8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0xc2, +0xed, 0x43, 0xb0, 0x4f, 0x66, 0xb5, 0x44, 0xb6, +0xb0, 0xca, 0x2c, 0x83, 0x07, 0xa1, 0xdc, 0x97, +0x2f, 0xab, 0x32, 0xbf, 0x9f, 0xc7, 0xf4, 0x2e, +0xc1, 0xcf, 0x0a, 0xca, 0xca, 0x77, 0xb9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0xf7, 0x4f, 0xef, 0x1c, 0x5e, 0x11, 0x44, +0x93, 0x2e, 0x1a, 0xe8, 0x82, 0x84, 0xc7, 0xd4, +0x70, 0x54, 0xe4, 0xd2, 0xfc, 0x24, 0xa5, 0xc5, +0x9c, 0x0c, 0x40, 0x9e, 0xcc, 0xf9, 0xf5, 0xd3, +0x69, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0x5e, 0x22, 0x5c, 0x0d, 0x03, +0xf0, 0xd8, 0x97, 0xba, 0xc3, 0x38, 0x20, 0x86, +0x9e, 0x5f, 0xe8, 0x17, 0x1d, 0x91, 0x03, 0x5b, +0x67, 0xda, 0x28, 0xb1, 0x41, 0xaa, 0x79, 0xc2, +0x09, 0xcb, 0x70, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0xc8, 0x0c, 0x2d, +0x83, 0x95, 0x64, 0xeb, 0xee, 0xd8, 0x2c, 0xc8, +0x29, 0x8a, 0xf1, 0x7a, 0xe8, 0x97, 0x8d, 0x90, +0xbc, 0xdb, 0x3e, 0xa7, 0x83, 0x1e, 0x20, 0x73, +0xab, 0x41, 0xbc, 0x64, 0x8b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0x18, +0xc6, 0xfd, 0x70, 0x54, 0x1f, 0xea, 0x71, 0x49, +0x68, 0xa8, 0x1a, 0x78, 0x66, 0x38, 0x1d, 0x4d, +0xbf, 0x62, 0x72, 0x3a, 0x1d, 0x48, 0x1f, 0x53, +0xb5, 0xc7, 0xf5, 0xf9, 0xf9, 0x0f, 0xdb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0xc1, 0x35, 0xfe, 0x1f, 0x9e, 0x8b, 0x29, +0xd5, 0x47, 0x9d, 0x44, 0xff, 0x30, 0x45, 0x66, +0x36, 0x29, 0xfe, 0x18, 0x1f, 0x06, 0xa2, 0xa1, +0xa6, 0x30, 0xc8, 0x17, 0xf8, 0x68, 0x36, 0xad, +0x5e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0x1c, 0xbb, 0x47, 0x76, 0x35, +0x48, 0xad, 0x45, 0xe2, 0x81, 0x22, 0xdc, 0xdf, +0xae, 0x42, 0x98, 0x0b, 0xc8, 0x1c, 0x82, 0x49, +0xe3, 0x27, 0xf0, 0xa9, 0xff, 0x08, 0xab, 0xeb, +0x12, 0xad, 0xe1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0xf7, 0xd5, 0x60, +0x68, 0x07, 0xdd, 0x80, 0x16, 0x32, 0xcb, 0x0e, +0x81, 0x78, 0xb8, 0x97, 0xb4, 0x3a, 0x5c, 0xfc, +0xdb, 0x1b, 0x83, 0xa4, 0xde, 0x69, 0xa1, 0x5b, +0xf0, 0x5b, 0x66, 0x9e, 0xdf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0xcc, +0xbc, 0x85, 0x99, 0x78, 0x0b, 0xe9, 0x05, 0x85, +0x54, 0xe6, 0xb4, 0x8f, 0xdb, 0x1e, 0x6a, 0xa6, +0xae, 0x66, 0x02, 0xfc, 0xed, 0x17, 0xcb, 0x69, +0x8d, 0xef, 0xc0, 0xee, 0xdf, 0xc1, 0x23, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0x7e, 0xf2, 0x25, 0x33, 0x53, 0xea, 0x97, +0x2b, 0x89, 0x10, 0xbb, 0xc2, 0x42, 0x63, 0xec, +0x6b, 0xa4, 0xa7, 0x18, 0xa9, 0x90, 0x74, 0x12, +0xd8, 0x5d, 0x65, 0x33, 0x4e, 0x59, 0x10, 0xf9, +0xdd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0xee, 0xa9, 0xa7, 0x92, 0x7a, +0x21, 0xe1, 0x2f, 0x77, 0x23, 0xd8, 0x9e, 0x18, +0x35, 0x5f, 0xb9, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xad, 0x0f, 0xa1, 0x73, +0xda, 0x5f, 0x4a, 0x57, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x0d, 0x8d, 0xe4, +0x72, 0xe2, 0x9c, 0x8c, 0x3d, 0xfc, 0x1e, 0x43, +0x7e, 0xbe, 0x18, 0x8c, 0xff, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x15, +0xc4, 0x29, 0x20, 0x41, 0x7a, 0x4c, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0x2b, +0x0b, 0x4d, 0x97, 0x67, 0x52, 0xf7, 0x9f, 0xf5, +0x37, 0xe6, 0x2d, 0xbf, 0x89, 0xed, 0xa4, 0x28, +0xfa, 0x6a, 0x47, 0x14, 0xfb, 0x6a, 0x43, 0xf4, +0xc0, 0xa1, 0xe6, 0xf8, 0x2b, 0xc8, 0x43, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0x2e, 0xc9, 0x9f, 0xd2, 0xbe, 0xd3, 0xee, +0x2a, 0x30, 0x59, 0xe8, 0x57, 0x6e, 0x55, 0xb9, +0x6c, 0xf8, 0x03, 0xa5, 0x59, 0x45, 0x09, 0x35, +0x48, 0x37, 0xd2, 0xe8, 0x2e, 0xda, 0xf6, 0x9a, +0x13, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0x77, 0x85, 0xa1, 0xf0, 0x9f, +0x3d, 0x1e, 0x48, 0xd9, 0x6f, 0x17, 0x4c, 0x5f, +0x6e, 0xfa, 0x81, 0xe5, 0xe8, 0xfd, 0xe4, 0x39, +0x4e, 0x77, 0x82, 0x35, 0x96, 0xbf, 0x59, 0x1c, +0xa4, 0x87, 0x08, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x65, 0x5e, 0x0b, +0x27, 0x3e, 0x50, 0x1f, 0x04, 0x15, 0xcb, 0xd1, +0x7c, 0x1a, 0x16, 0x5b, 0xd5, 0x43, 0xb0, 0xa2, +0x44, 0x50, 0x56, 0x3f, 0xe3, 0x2f, 0x64, 0x9f, +0x38, 0x7c, 0x33, 0xba, 0xfd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x2f, +0xe2, 0xfe, 0x54, 0xc4, 0x69, 0x80, 0x15, 0xeb, +0x3e, 0x53, 0xb2, 0xb1, 0xc5, 0xb2, 0xc9, 0x24, +0x2a, 0x90, 0xd8, 0x94, 0x63, 0xce, 0x10, 0x93, +0x14, 0x93, 0xe8, 0xc3, 0xbd, 0x2f, 0x69, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0x16, 0x37, 0x5f, 0x02, 0x9c, 0x59, 0xed, +0x7e, 0xef, 0xd8, 0xef, 0xf1, 0x9c, 0x39, 0xcb, +0x0e, 0x20, 0x7e, 0xab, 0xf3, 0xb0, 0xf1, 0xf6, +0x2d, 0x2e, 0xbc, 0x57, 0xaa, 0xbf, 0x53, 0x30, +0x2d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0x93, 0xe4, 0x89, 0x34, 0x2c, +0x63, 0xe4, 0x89, 0x45, 0xcc, 0x74, 0x93, 0xf2, +0xa8, 0x4e, 0x27, 0x54, 0x0f, 0xb4, 0x0a, 0xee, +0xbf, 0x3f, 0x2b, 0x45, 0x17, 0xe4, 0x50, 0x3f, +0x15, 0xb0, 0x47, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0xb4, 0xde, 0x8c, +0xa4, 0x17, 0x5d, 0x8e, 0x45, 0x72, 0xa4, 0xe1, +0xd4, 0x7c, 0x86, 0x98, 0x0c, 0x97, 0x02, 0xac, +0x4f, 0x13, 0x32, 0x62, 0x18, 0x74, 0x8f, 0x17, +0x5b, 0x92, 0x27, 0x2e, 0x4b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x30, +0xa1, 0x91, 0x86, 0x6e, 0x38, 0x0b, 0x48, 0x41, +0x03, 0x5e, 0x7d, 0x37, 0x8f, 0xef, 0x97, 0xe3, +0xe7, 0xa4, 0x88, 0x20, 0x88, 0xd6, 0x46, 0xa5, +0xf9, 0x37, 0x76, 0xc3, 0xf0, 0x62, 0x21, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0xab, 0x96, 0x8d, 0x0b, 0xa4, 0xb9, 0xe0, +0x50, 0xe0, 0xe9, 0x6d, 0x45, 0x49, 0x2f, 0xe1, +0x2a, 0xa5, 0xa3, 0xad, 0x97, 0x08, 0xd4, 0xf6, +0x56, 0x0c, 0xcb, 0xaf, 0xcd, 0x5b, 0x12, 0x87, +0x3b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0x87, 0x98, 0xe1, 0x73, 0x8b, +0xf7, 0x78, 0x05, 0xc8, 0x95, 0x76, 0xc6, 0xb2, +0xe6, 0x35, 0xec, 0x36, 0x07, 0xdc, 0x78, 0x34, +0x04, 0xf3, 0xa7, 0x27, 0x1e, 0x3b, 0x0a, 0xce, +0x5c, 0xa5, 0x13, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0xc1, 0x94, 0x05, +0xf5, 0xea, 0xee, 0xfa, 0x15, 0x9b, 0xda, 0x97, +0x4a, 0xdf, 0x80, 0xbe, 0x79, 0x9a, 0xe2, 0x81, +0x67, 0x77, 0x23, 0x98, 0xc8, 0xa4, 0xbd, 0x01, +0xbf, 0xc0, 0xe1, 0xf2, 0x34, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x48, +0x4b, 0xe1, 0x1b, 0xdf, 0xa2, 0xe6, 0x05, 0x46, +0x03, 0x6e, 0x14, 0x28, 0x04, 0x4a, 0x9c, 0x94, +0x51, 0x4b, 0xd0, 0xc6, 0x9a, 0xca, 0xf9, 0x0a, +0xd5, 0xfe, 0xa5, 0x6c, 0x61, 0x6d, 0x7a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0x67, 0x26, 0xb5, 0x4f, 0x5b, 0xb6, 0xc0, +0xa9, 0x0c, 0xd6, 0x19, 0xf5, 0x1d, 0xbf, 0x68, +0xd8, 0xe9, 0xd2, 0x8c, 0xfc, 0xbc, 0xf6, 0x5c, +0xe9, 0xa2, 0x12, 0xa0, 0x22, 0x96, 0x24, 0x79, +0x6d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0x0d, 0x42, 0xf8, 0x4a, 0x25, +0xc7, 0xc8, 0x45, 0x14, 0x4d, 0x8c, 0x67, 0x4f, +0x3d, 0x87, 0xda, 0xe9, 0x67, 0x8c, 0x47, 0x13, +0xdb, 0x5e, 0x4f, 0x10, 0xa3, 0x1d, 0x31, 0xb9, +0xfd, 0x89, 0x8c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0x04, 0x87, 0x65, +0x4e, 0xc8, 0xbb, 0x38, 0x23, 0x81, 0xc1, 0x47, +0x52, 0x00, 0xb5, 0x03, 0xff, 0xfc, 0x3e, 0xde, +0x58, 0x4e, 0x94, 0x64, 0x09, 0x19, 0x34, 0xc0, +0x77, 0x8a, 0xd3, 0xfd, 0xf7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0xd1, +0x06, 0x5b, 0xec, 0x8c, 0x18, 0x3a, 0x7c, 0xf4, +0xf9, 0x71, 0xf8, 0xe6, 0xa6, 0xb5, 0x66, 0xac, +0xdc, 0xbd, 0x71, 0xe7, 0xbb, 0x58, 0xbb, 0x97, +0x1f, 0x73, 0x72, 0x50, 0xe1, 0xeb, 0x8c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0xd2, 0x47, 0x39, 0x52, 0x1c, 0x9c, 0x1f, +0x9a, 0xe0, 0x01, 0x20, 0x87, 0xe6, 0x7b, 0xc0, +0x27, 0x71, 0xc9, 0x81, 0xb3, 0xf1, 0x3e, 0x8f, +0xe5, 0x7b, 0xd7, 0x7b, 0x8f, 0xbc, 0x1e, 0x80, +0xac, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x71, 0x6e, 0xc8, 0xd3, 0x09, +0x20, 0x5d, 0x8e, 0x86, 0xfb, 0x60, 0xa3, 0xce, +0xbc, 0x7b, 0xd9, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x9a, 0x37, 0x48, 0x22, +0x3b, 0xfa, 0x84, 0xc6, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x14, 0x1d, 0xad, +0xbb, 0xbf, 0x3c, 0x3b, 0xa6, 0x21, 0x13, 0x89, +0xdd, 0x9b, 0xc2, 0x18, 0x0b, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5a, 0xb7, +0x57, 0xc7, 0x0c, 0x71, 0xc2, 0xaa, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0x52, +0xe6, 0x7d, 0xd6, 0xdf, 0x56, 0x43, 0x2c, 0x2f, +0xf2, 0x89, 0x65, 0xad, 0x79, 0x15, 0x9f, 0xdb, +0x5b, 0xd6, 0xfd, 0x5f, 0x31, 0x49, 0x8a, 0x3d, +0xbb, 0x8e, 0x8b, 0x10, 0x19, 0x99, 0x6c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0x6e, 0x83, 0x49, 0xff, 0xed, 0x3d, 0x40, +0xa7, 0x87, 0xbc, 0xc0, 0x7a, 0xd9, 0x61, 0xa2, +0x60, 0x63, 0x9d, 0x5b, 0x8e, 0x8e, 0x52, 0x0d, +0x7b, 0xbf, 0x87, 0xc5, 0xb9, 0x2a, 0xc1, 0xe4, +0xb4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0x8d, 0x15, 0x89, 0xcd, 0x66, +0x2e, 0x79, 0xce, 0x7f, 0x6d, 0x63, 0xb0, 0xc8, +0x43, 0x1c, 0xa6, 0xa7, 0xa4, 0x84, 0x6d, 0x85, +0x30, 0xd0, 0xe8, 0xdc, 0x28, 0x85, 0x77, 0xdc, +0xd3, 0x6f, 0xaf, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0xbf, 0x4a, 0x0b, +0x88, 0x4e, 0x32, 0xfe, 0x69, 0x6f, 0x52, 0x31, +0xd2, 0xc4, 0xf5, 0xdc, 0x07, 0x1f, 0x80, 0x56, +0x78, 0x4a, 0x32, 0x31, 0xfb, 0x41, 0xdf, 0x4b, +0xec, 0xb2, 0x2b, 0x9c, 0xcc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0x20, +0x95, 0x3a, 0xdf, 0x9c, 0xe5, 0xa0, 0x90, 0xd3, +0xdb, 0x73, 0xe4, 0x15, 0xa3, 0x3d, 0xe5, 0xbc, +0x09, 0x5b, 0x27, 0xac, 0x99, 0xb1, 0x13, 0xea, +0x52, 0xf8, 0xb2, 0x64, 0x6c, 0xe3, 0x99, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0x50, 0xc2, 0x91, 0xa7, 0x02, 0xa0, 0xc3, +0x64, 0x7d, 0xe0, 0xad, 0x85, 0x19, 0xbf, 0x8b, +0xb6, 0x02, 0xb3, 0xe1, 0x20, 0x6c, 0x79, 0x6e, +0x87, 0x73, 0xa0, 0xde, 0xf6, 0xbb, 0xde, 0xdd, +0x92, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0x77, 0xce, 0x56, 0xc7, 0xfc, +0x72, 0xea, 0x6b, 0x89, 0x73, 0xf5, 0xa1, 0x86, +0x56, 0xfc, 0x54, 0x4d, 0x75, 0x06, 0xba, 0x7d, +0x23, 0x79, 0xa8, 0xce, 0x9d, 0x34, 0xbe, 0xfc, +0x57, 0x3b, 0x06, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0x52, 0xfa, 0x99, +0x4d, 0x34, 0xca, 0x25, 0xa3, 0x50, 0x30, 0x26, +0x8c, 0xdd, 0xcf, 0x89, 0xaa, 0x2d, 0xcf, 0xe9, +0xbc, 0x02, 0xf0, 0x9e, 0x13, 0xc1, 0x3a, 0x61, +0x52, 0xf8, 0xf6, 0x4b, 0xcd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x7f, +0x56, 0x90, 0xfa, 0x89, 0x65, 0xd9, 0x9a, 0x18, +0x67, 0x48, 0x94, 0xac, 0x6e, 0xf4, 0x90, 0x12, +0xf2, 0x64, 0x12, 0xe8, 0xa0, 0x15, 0x0e, 0x60, +0x07, 0xb3, 0xc2, 0xd0, 0x2a, 0x75, 0x05, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0x14, 0xe5, 0x1a, 0x68, 0x5e, 0xc7, 0x30, +0xd8, 0x38, 0x75, 0x1f, 0xcf, 0x99, 0xcd, 0x69, +0xd3, 0x08, 0x8d, 0x8e, 0xdb, 0x2c, 0xe7, 0xa1, +0xba, 0x69, 0xd7, 0x8c, 0x48, 0x6d, 0x65, 0x29, +0x90, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0xe8, 0x0f, 0xaa, 0xff, 0x5d, +0x5b, 0xbf, 0xef, 0xbf, 0xb0, 0xc1, 0x32, 0x60, +0x7d, 0x06, 0xa3, 0x6a, 0xf5, 0xec, 0x26, 0x61, +0x33, 0x69, 0xb1, 0x5e, 0x67, 0x66, 0xd1, 0x65, +0x06, 0xdb, 0x5a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0x31, 0x8c, 0x62, +0x53, 0x84, 0x27, 0x2b, 0x39, 0x01, 0xf4, 0x57, +0x50, 0xdc, 0x22, 0x8c, 0xc5, 0x20, 0xff, 0xb3, +0x96, 0x08, 0x28, 0xa7, 0x8f, 0x5f, 0xe6, 0xff, +0xcc, 0xba, 0xdc, 0xf3, 0x5e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0x35, +0x73, 0x6c, 0xe4, 0x0e, 0xc5, 0x33, 0x5c, 0x85, +0xa8, 0x7f, 0x94, 0x97, 0xc3, 0x37, 0x22, 0xf8, +0x3d, 0xad, 0x2d, 0x7c, 0x25, 0x57, 0x0d, 0x4a, +0x18, 0x4c, 0xdd, 0x68, 0x2c, 0x0c, 0x35, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0x60, 0x28, 0x28, 0x4c, 0xcf, 0x72, 0xb8, +0x84, 0xe5, 0xbc, 0xec, 0x26, 0x96, 0xa1, 0x94, +0x15, 0x66, 0x92, 0xb8, 0xe9, 0xbe, 0x67, 0x54, +0x74, 0x6f, 0xc9, 0x0d, 0xd9, 0x70, 0x91, 0x57, +0xec, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x64, 0xea, 0x80, 0x0a, 0x88, +0xc6, 0xe8, 0x91, 0x41, 0x2d, 0x01, 0x26, 0xa5, +0x29, 0x5b, 0x08, 0xec, 0x47, 0xc1, 0x6a, 0x1c, +0x9e, 0xc0, 0x4a, 0x53, 0x7d, 0x63, 0x4d, 0xf1, +0xc4, 0xf3, 0x7b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0x3f, 0x42, 0x9d, +0xcd, 0xf2, 0x79, 0x92, 0x28, 0x51, 0x36, 0xc5, +0x1f, 0x6e, 0xa0, 0x46, 0x93, 0xbc, 0xe4, 0x3b, +0xf6, 0xbd, 0xe9, 0x71, 0x9d, 0xe9, 0x21, 0xf6, +0xa1, 0x52, 0xbc, 0xc2, 0x45, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0xb8, +0x05, 0xdd, 0x99, 0x3a, 0xa0, 0xe1, 0x8d, 0xe9, +0xf7, 0x63, 0xf1, 0x29, 0x1a, 0x88, 0x4e, 0x1d, +0xf2, 0xe0, 0x53, 0xa0, 0x82, 0xbc, 0xcc, 0x04, +0xc7, 0x12, 0xe2, 0xb9, 0x45, 0xa4, 0x90, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0x79, 0xae, 0x8a, 0x23, 0xfe, 0x7d, 0xff, +0x47, 0xd9, 0x39, 0x84, 0xff, 0x54, 0x79, 0x7c, +0x9a, 0x8f, 0xe1, 0x89, 0x37, 0x0d, 0x4f, 0x21, +0x3f, 0x15, 0x04, 0x5e, 0xeb, 0xd7, 0xc4, 0x64, +0x84, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0x0d, 0xdc, 0x98, 0xff, 0xe1, +0x59, 0xf1, 0xc6, 0x2b, 0x8d, 0xed, 0x3e, 0xe2, +0x5a, 0x51, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xc8, 0xb1, 0x6a, 0xb8, +0xf9, 0xe1, 0x1c, 0x26, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0xb7, 0x07, 0x58, +0xc8, 0x8c, 0xb2, 0xe0, 0x91, 0xf8, 0x6c, 0x33, +0x8d, 0x22, 0xcc, 0xdf, 0x69, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x92, 0x96, +0x58, 0x32, 0xf4, 0x0c, 0x65, 0x92, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0x46, +0xa3, 0xfd, 0x64, 0xbe, 0x33, 0xfd, 0xc0, 0x78, +0x9d, 0xc9, 0x5e, 0x90, 0x39, 0x98, 0xf7, 0x7e, +0x16, 0xc5, 0x03, 0x15, 0x47, 0x0e, 0xee, 0xa8, +0x15, 0x83, 0x55, 0xe9, 0x5b, 0xa9, 0xa7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0xe1, 0xd1, 0x0d, 0xaf, 0x24, 0xa0, 0x71, +0xf9, 0x60, 0xd0, 0x24, 0x8f, 0xf9, 0x08, 0x4f, +0x69, 0xb9, 0x80, 0x03, 0xcd, 0xfa, 0x4c, 0xce, +0x04, 0xc5, 0xc8, 0xc8, 0xbe, 0xe7, 0x7d, 0xbb, +0x39, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0x6c, 0x5b, 0xdc, 0xe7, 0x06, +0x86, 0x41, 0x83, 0x79, 0x9b, 0xe5, 0x30, 0x3d, +0x11, 0xf8, 0xab, 0x0f, 0x25, 0x18, 0x5c, 0x24, +0x41, 0x09, 0xb4, 0xba, 0x9e, 0xd7, 0x66, 0xf8, +0x21, 0x03, 0x8b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0x7a, 0x59, 0xb1, +0xb6, 0xbb, 0x92, 0x40, 0xe0, 0x2d, 0x88, 0x69, +0x06, 0x34, 0x36, 0xe4, 0x6d, 0x06, 0x50, 0x60, +0xe1, 0xea, 0x38, 0x82, 0x33, 0x44, 0xd2, 0xe7, +0x88, 0x83, 0x21, 0x94, 0x5d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0x93, +0xf5, 0xb8, 0x12, 0x26, 0x43, 0x3f, 0xf2, 0xe2, +0x0e, 0xa0, 0xb5, 0xd4, 0xc9, 0x8e, 0x85, 0x7f, +0x67, 0x86, 0x5a, 0x8b, 0x69, 0xc7, 0x0f, 0xf0, +0xb4, 0x1a, 0x29, 0x65, 0x10, 0x5e, 0xc9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0x52, 0x85, 0x83, 0xae, 0x45, 0x12, 0x26, +0xc2, 0x1c, 0xfb, 0xb9, 0xad, 0x56, 0x06, 0x31, +0x7a, 0x6e, 0xcf, 0xad, 0xa3, 0xc2, 0x2a, 0x8b, +0xa7, 0xff, 0x4a, 0x8d, 0xa6, 0xf4, 0x09, 0xbc, +0xe9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0x7f, 0xb2, 0xd2, 0x37, 0x9b, +0x42, 0x5e, 0x64, 0xb3, 0x25, 0x0e, 0x20, 0x4b, +0x80, 0xe6, 0x6a, 0x8d, 0xfb, 0x94, 0x0f, 0xb6, +0x46, 0x5f, 0x53, 0xb6, 0xc6, 0x51, 0x3a, 0xf6, +0x78, 0x20, 0x6e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0x0d, 0x0d, 0xac, +0x3d, 0x7b, 0x01, 0xd2, 0x8f, 0x91, 0x79, 0x23, +0xa3, 0xeb, 0xe1, 0xfa, 0x82, 0x50, 0x46, 0xda, +0x07, 0x95, 0x36, 0x80, 0x48, 0xe4, 0xbc, 0xbb, +0xe9, 0xb3, 0xde, 0x83, 0xf6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0xfe, +0x5d, 0xac, 0x3a, 0x18, 0x1d, 0xf9, 0x61, 0x10, +0x07, 0x2c, 0xfa, 0x5a, 0x76, 0xc0, 0xcd, 0x67, +0x73, 0x52, 0xc5, 0x6d, 0xe7, 0xa4, 0x5c, 0x59, +0x3c, 0x1d, 0x6e, 0x63, 0x57, 0xa5, 0xf6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x99, 0x71, 0xbe, 0x1a, 0x01, 0x51, 0x33, +0x35, 0x18, 0xa8, 0x9c, 0xf4, 0xc9, 0x9d, 0x28, +0x48, 0x20, 0xd3, 0xc8, 0x86, 0x4b, 0xe2, 0xc1, +0x80, 0xa9, 0x05, 0x3d, 0x44, 0x5b, 0x24, 0xf7, +0xf5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0xa4, 0x41, 0x02, 0xe0, 0x7e, +0x30, 0xb8, 0xe6, 0x6f, 0xf9, 0x59, 0x4c, 0xd9, +0x6d, 0x5c, 0xad, 0xb3, 0xd9, 0xaf, 0xf7, 0xcb, +0x17, 0x7c, 0xde, 0x72, 0x33, 0xf6, 0x83, 0x9c, +0x54, 0x67, 0x2f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x60, 0xd8, 0xc6, +0xa9, 0xc8, 0xe7, 0x29, 0xf6, 0xf2, 0xc4, 0x5e, +0x88, 0x2a, 0x35, 0x58, 0x64, 0xd5, 0x9c, 0x72, +0x04, 0x7c, 0x39, 0xe5, 0x91, 0x09, 0x8d, 0xc9, +0xfa, 0x51, 0xa4, 0xbb, 0xf0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0x2c, +0x80, 0x51, 0x7d, 0x1a, 0x02, 0xa0, 0x94, 0x8c, +0xda, 0xa1, 0x5e, 0x1e, 0xff, 0xd8, 0x21, 0x4b, +0xcc, 0x63, 0x11, 0x5b, 0x08, 0xf8, 0x9f, 0xfb, +0x36, 0xde, 0x6b, 0x16, 0x2e, 0x8d, 0x7b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0xd8, 0xb1, 0x52, 0xdf, 0xf4, 0x69, 0x6f, +0x31, 0x31, 0xf2, 0xd3, 0xfe, 0x71, 0xf4, 0x84, +0x38, 0x8f, 0x24, 0x58, 0x86, 0x67, 0x64, 0x2f, +0xb0, 0x60, 0x75, 0x8a, 0x56, 0xc0, 0x3c, 0x95, +0x17, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0xf7, 0x02, 0xa8, 0x81, 0x9b, +0xcc, 0xd8, 0x0f, 0xab, 0x61, 0xe8, 0xdb, 0x1b, +0x41, 0x42, 0xd5, 0xa3, 0x8c, 0xa0, 0x6a, 0x10, +0xd6, 0x95, 0x41, 0x86, 0x3d, 0xf8, 0x22, 0x60, +0xcb, 0x32, 0x01, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0xc2, 0xde, 0x63, +0x3f, 0x2a, 0xa4, 0x59, 0x29, 0xce, 0xe4, 0xbc, +0xaa, 0x92, 0x29, 0x48, 0xe8, 0x21, 0x90, 0xa7, +0x64, 0x39, 0xd7, 0xe2, 0x05, 0x6e, 0x6b, 0x15, +0xf3, 0x0d, 0xb7, 0xc9, 0x14, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0xd5, +0xa4, 0x4a, 0x7e, 0xee, 0xd8, 0xd7, 0x4a, 0xdf, +0xee, 0xf1, 0x9d, 0xe1, 0xe2, 0x7b, 0xc7, 0x45, +0xf7, 0xd2, 0xd9, 0xe0, 0xf6, 0xfe, 0xbf, 0x4d, +0x63, 0xe2, 0x40, 0x5f, 0x6c, 0xab, 0x30, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0x6b, 0xc1, 0xd8, 0x17, 0x92, 0x78, 0xbf, +0xc1, 0xc9, 0xc6, 0xdf, 0xd7, 0x81, 0x7f, 0xf7, +0x83, 0x7a, 0x1b, 0x6d, 0x1a, 0x50, 0x2e, 0x17, +0x35, 0xaa, 0x2f, 0x9e, 0x8f, 0xbb, 0x3f, 0x87, +0x6b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0x48, 0x10, 0xe1, 0xef, 0x7b, +0xe5, 0x43, 0x77, 0x6f, 0x68, 0xda, 0xa1, 0x8a, +0xfb, 0x59, 0xe2, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xf2, 0x23, 0x18, 0x58, +0x07, 0x7f, 0xd9, 0x48, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0x15, 0xbb, 0x5a, +0xf7, 0xe2, 0x5d, 0x8f, 0x95, 0xe6, 0xf4, 0x56, +0x9c, 0xb8, 0x4a, 0xd7, 0xd5, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xba, 0x31, +0xba, 0x37, 0x35, 0x67, 0xbd, 0x6a, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0x0c, +0xba, 0x04, 0x73, 0x64, 0xff, 0x19, 0x94, 0x41, +0x3d, 0x1a, 0x0e, 0xaf, 0x30, 0xc9, 0x48, 0x6d, +0x54, 0x7d, 0x75, 0x73, 0xe4, 0xd5, 0x54, 0x0e, +0x22, 0x28, 0xde, 0xf6, 0x42, 0x04, 0x21, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0xb9, 0x88, 0x01, 0x9f, 0xdf, 0x00, 0x81, +0x63, 0x26, 0x6a, 0xf2, 0x67, 0xce, 0xb4, 0xfd, +0xd3, 0x03, 0x15, 0xef, 0x77, 0x8b, 0xd0, 0x1b, +0x74, 0x97, 0x01, 0x2b, 0x18, 0x78, 0xd2, 0xfc, +0x87, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0x4d, 0x07, 0xab, 0xed, 0x28, +0xa9, 0xc2, 0x06, 0x90, 0x3c, 0xf8, 0x5f, 0xa5, +0x09, 0x4a, 0xcf, 0x5b, 0x4f, 0x5a, 0x29, 0x8b, +0x8c, 0x9d, 0x25, 0x6e, 0xac, 0x9f, 0xba, 0xf7, +0x76, 0x13, 0x6e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x7d, 0xa6, 0xeb, +0x05, 0x00, 0xdb, 0x22, 0x9f, 0x79, 0xad, 0x6c, +0x5a, 0x98, 0x62, 0xd6, 0xe9, 0x10, 0xd7, 0xbc, +0xd5, 0xff, 0x86, 0xb5, 0x2e, 0xc3, 0x26, 0xd3, +0xdd, 0x5a, 0xf9, 0x35, 0x77, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x6c, +0xcc, 0xae, 0xbb, 0xfe, 0x5c, 0x9f, 0xb6, 0xd6, +0x9c, 0xe7, 0x63, 0x98, 0xa8, 0xce, 0x6f, 0xf3, +0x4a, 0xa6, 0x9f, 0x2a, 0xec, 0x3a, 0x07, 0x70, +0x7a, 0xe7, 0xd5, 0xa5, 0xfa, 0x5e, 0xa5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0x50, 0x25, 0xa4, 0xd4, 0xff, 0x61, 0x01, +0x13, 0x25, 0x27, 0xc7, 0xea, 0x5d, 0xa9, 0xaf, +0x7d, 0xfb, 0x6d, 0xa7, 0xd0, 0x50, 0xae, 0x02, +0xd9, 0x28, 0x1c, 0x20, 0xe9, 0xe2, 0xc0, 0x15, +0x55, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0xcb, 0x28, 0x23, 0x1c, 0x75, +0x14, 0xa1, 0xf9, 0x05, 0x9e, 0xa4, 0x8f, 0xae, +0xc7, 0x72, 0x6d, 0x55, 0xe5, 0x62, 0x7d, 0x4d, +0x7d, 0xc8, 0xfe, 0x15, 0x26, 0x77, 0x07, 0x74, +0x72, 0xed, 0xe9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0x51, 0xb5, 0xd7, +0x0a, 0xf6, 0xf3, 0x8e, 0xb1, 0x97, 0x9f, 0xa5, +0x43, 0x1f, 0xe6, 0xeb, 0xae, 0xbc, 0x4b, 0x62, +0x05, 0x2c, 0x10, 0x0f, 0x01, 0x3c, 0x7c, 0xf1, +0xac, 0x54, 0xa6, 0xae, 0x29, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0x9f, +0xc6, 0xa5, 0x0f, 0x49, 0xab, 0x00, 0x44, 0xfb, +0xa3, 0x91, 0xd7, 0x5c, 0x48, 0x8b, 0x39, 0x0d, +0x76, 0x02, 0x86, 0x7c, 0x35, 0x7f, 0x12, 0x7a, +0x5e, 0xcc, 0xea, 0xe3, 0xd6, 0x1e, 0x28, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x95, 0xaa, 0x99, 0x1d, 0xe8, 0x80, 0x56, +0x70, 0x7d, 0x32, 0xb2, 0x00, 0x44, 0x97, 0xca, +0x76, 0x15, 0x16, 0x66, 0x03, 0x82, 0xb8, 0x0e, +0xbb, 0xa8, 0x95, 0x16, 0x46, 0xd0, 0xdc, 0x20, +0x3d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0x39, 0x11, 0xf6, 0xb1, 0x76, +0x2e, 0x3e, 0x20, 0x4c, 0x1b, 0x5c, 0xa5, 0x94, +0x6e, 0x0d, 0x88, 0xde, 0xbb, 0xd2, 0x2d, 0x84, +0x8c, 0xef, 0xdb, 0xb7, 0xf0, 0x24, 0x88, 0x4c, +0x10, 0xc5, 0xff, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0xcc, 0x00, 0xd7, +0x07, 0x1c, 0x3f, 0x22, 0x33, 0x57, 0x84, 0x6b, +0x59, 0x34, 0xf3, 0xb3, 0x4c, 0xaf, 0xef, 0x23, +0x17, 0x72, 0x50, 0xb1, 0x39, 0x95, 0x51, 0x01, +0x28, 0x08, 0xb9, 0x7f, 0xc8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0x22, +0x8e, 0x86, 0xe3, 0x4b, 0x02, 0x1e, 0x83, 0x0b, +0xac, 0x2f, 0xbd, 0xc3, 0xfe, 0x6c, 0x2c, 0x70, +0xfe, 0x9a, 0x11, 0xf7, 0x38, 0x58, 0xce, 0xa4, +0x2b, 0x4e, 0xec, 0xc5, 0x33, 0xd7, 0x28, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0x23, 0xdb, 0x36, 0x17, 0x1d, 0x13, 0xaa, +0x6f, 0x89, 0xbe, 0x2e, 0xfb, 0x35, 0xaf, 0xc5, +0x67, 0x88, 0x4c, 0xbb, 0x1e, 0x9d, 0x6c, 0x44, +0x22, 0x06, 0xe3, 0x78, 0x2f, 0x56, 0xde, 0xfb, +0xc3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0xb3, 0x89, 0xdc, 0xe2, 0x06, +0x31, 0xf4, 0x8d, 0x8d, 0xef, 0xef, 0x60, 0xc0, +0x3e, 0x30, 0xcd, 0xdf, 0x0f, 0x41, 0x0e, 0xd5, +0x48, 0x12, 0x36, 0xd0, 0x2c, 0xa3, 0x23, 0xbd, +0x77, 0x5a, 0x2b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0xaa, 0x5b, 0x1f, +0x0f, 0x25, 0x0d, 0x70, 0xfd, 0x37, 0x5f, 0xa9, +0x1c, 0x4a, 0x22, 0xa0, 0x7d, 0xb7, 0xbf, 0x18, +0x6e, 0x14, 0x35, 0x9b, 0x62, 0x50, 0x0f, 0x60, +0x09, 0xf1, 0x1b, 0x64, 0xc1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0xa4, +0xa8, 0xc2, 0xf3, 0xe5, 0x93, 0x11, 0xd9, 0xed, +0x54, 0x4b, 0x1d, 0xea, 0x51, 0x28, 0x70, 0xf5, +0xf3, 0xfb, 0x83, 0xd9, 0x56, 0x1b, 0x05, 0xeb, +0xfa, 0x0d, 0xce, 0x02, 0xbf, 0x5a, 0x52, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0xc8, 0xfc, 0xae, 0xa6, 0x34, 0xbf, 0xce, +0x3a, 0x3a, 0x05, 0xbc, 0x17, 0xb8, 0xfc, 0x35, +0xbb, 0x47, 0x11, 0xf9, 0x64, 0x59, 0x85, 0x1a, +0x61, 0xd8, 0x1f, 0xa4, 0xb3, 0xbc, 0x3a, 0xb2, +0x52, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0x0c, 0x42, 0x30, 0x8c, 0x00, +0xaf, 0xda, 0x82, 0x0c, 0x14, 0x81, 0x6e, 0xfa, +0x7a, 0xc1, 0x5d, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x66, 0x47, 0x03, 0x20, +0x58, 0x2d, 0x7d, 0xe2, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0xb2, 0xda, 0x54, +0x32, 0x7e, 0x61, 0x6d, 0x61, 0xf9, 0x2c, 0x61, +0x39, 0x0b, 0x1b, 0xa7, 0xff, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x3c, +0xa3, 0x2c, 0xeb, 0x45, 0x98, 0xd4, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0xdb, +0xee, 0x65, 0x39, 0x3b, 0xf3, 0x56, 0x8e, 0x18, +0xa6, 0x56, 0x9e, 0xbb, 0xde, 0x85, 0x45, 0xb1, +0x9b, 0xd6, 0x41, 0xf7, 0x3d, 0xe9, 0x9b, 0x0e, +0x80, 0x93, 0x15, 0x94, 0x28, 0xfe, 0xc0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0x4f, 0xe9, 0x5f, 0x41, 0x8b, 0xc4, 0x79, +0x13, 0x4d, 0xa5, 0x31, 0xbd, 0x5b, 0xbd, 0xcc, +0xee, 0xd5, 0xb1, 0xad, 0xfb, 0xae, 0x44, 0xcb, +0x20, 0x5f, 0xbc, 0x05, 0x80, 0xf2, 0xb5, 0x35, +0xf9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0x67, 0xde, 0xde, 0x3b, 0x29, +0x42, 0x21, 0x21, 0x05, 0x86, 0xf2, 0x37, 0x25, +0xf6, 0xa0, 0x4b, 0xc4, 0xa5, 0x2f, 0x41, 0xa4, +0x16, 0xea, 0xfe, 0x7d, 0x58, 0xa4, 0x93, 0x12, +0xeb, 0x0c, 0x5c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0x9a, 0x51, 0x58, +0x41, 0x24, 0xeb, 0xb0, 0x1a, 0xbe, 0x81, 0xa5, +0xf0, 0x9c, 0x69, 0xcd, 0x2b, 0x5e, 0xb8, 0x15, +0x8c, 0xc3, 0xdc, 0x85, 0xca, 0x1d, 0xf9, 0x74, +0xdf, 0x82, 0x57, 0xfb, 0x57, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0xba, +0xcd, 0x0f, 0x58, 0x42, 0xc5, 0x13, 0x21, 0x8b, +0x70, 0x8a, 0x7e, 0xbb, 0x9a, 0xe1, 0xfc, 0x75, +0x75, 0x91, 0xb5, 0x99, 0x38, 0x6e, 0x54, 0xc8, +0x02, 0x4e, 0xf8, 0x53, 0xe7, 0x3b, 0x26, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0xa3, 0xc5, 0xda, 0x75, 0x9f, 0x4a, 0x81, +0x23, 0xb1, 0x4b, 0xe6, 0x94, 0xd5, 0x00, 0xc1, +0xd3, 0xb9, 0x17, 0x6a, 0xc6, 0xd8, 0x89, 0xca, +0xe5, 0xec, 0x20, 0xd4, 0xea, 0xe7, 0x33, 0x46, +0x56, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0x29, 0x7e, 0x73, 0x3a, 0x2f, +0x07, 0xc0, 0x9c, 0xa5, 0xc0, 0x92, 0x4f, 0x25, +0xee, 0xee, 0x85, 0xd2, 0x5f, 0x64, 0x70, 0xa8, +0xd4, 0x31, 0x7f, 0x31, 0x8c, 0x7f, 0x6b, 0x05, +0x6b, 0x32, 0xf1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x32, 0x98, 0x6b, +0x51, 0xb7, 0xd5, 0xe9, 0x81, 0x91, 0xb4, 0xf8, +0x4f, 0x3c, 0x6e, 0x36, 0x05, 0x1f, 0x46, 0xba, +0xa5, 0x51, 0x53, 0x87, 0xa4, 0xca, 0x6e, 0x4a, +0x2c, 0x44, 0xb0, 0xb5, 0x57, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0xd1, +0x2a, 0xaf, 0x51, 0x6d, 0xb9, 0xa5, 0xdb, 0xb7, +0x2b, 0x36, 0x1a, 0x15, 0xb5, 0x41, 0x4d, 0x7d, +0xec, 0x97, 0x82, 0xa3, 0x6f, 0xcc, 0x1e, 0x5d, +0x7d, 0x27, 0x4c, 0x75, 0x9b, 0xf9, 0x73, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0x71, 0x41, 0xc9, 0x4a, 0x0f, 0x51, 0xd4, +0x40, 0xa5, 0xb9, 0xfb, 0xcb, 0x1c, 0x5f, 0x26, +0xe1, 0x7b, 0x25, 0x8a, 0xcf, 0xab, 0xc3, 0x09, +0xc1, 0x1d, 0x8a, 0xde, 0xf4, 0x4e, 0x31, 0x75, +0xb8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0xed, 0x2a, 0x50, 0x76, 0xd9, +0x74, 0x8b, 0xd3, 0x57, 0x10, 0x06, 0x66, 0xad, +0xb5, 0xfd, 0x3e, 0x83, 0x6f, 0x03, 0x54, 0xb3, +0x5f, 0xad, 0x8e, 0xef, 0x6a, 0x0a, 0xfc, 0xc1, +0xbf, 0x48, 0xa7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0x39, 0x29, 0xeb, +0xe0, 0x66, 0x01, 0xfb, 0x95, 0xaf, 0xe2, 0x32, +0xdf, 0xb2, 0x40, 0x7c, 0x7b, 0x61, 0xa8, 0xc3, +0x20, 0xb7, 0x9c, 0x9d, 0x7a, 0xb3, 0x10, 0xf7, +0x2a, 0x4d, 0x39, 0x45, 0x32, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0x30, +0x7b, 0x6d, 0x5b, 0x06, 0xda, 0xd4, 0x4a, 0xc7, +0x60, 0xd1, 0x47, 0xba, 0xeb, 0xfb, 0x79, 0xce, +0x11, 0x40, 0xa7, 0x20, 0xc7, 0x05, 0x00, 0xb3, +0x64, 0xa7, 0x27, 0xa0, 0xa1, 0xae, 0x47, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0x80, 0x1f, 0x28, 0x8f, 0x4a, 0xb2, 0x8e, +0xba, 0xa1, 0x39, 0xde, 0x57, 0x37, 0xbd, 0x92, +0xb9, 0x37, 0x5b, 0x3c, 0x4f, 0x08, 0x14, 0xa2, +0x04, 0x56, 0x6b, 0x3b, 0x70, 0xf8, 0xfa, 0xf6, +0x57, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x82, 0x95, 0xf4, 0x0e, 0x31, +0xbe, 0x9b, 0x31, 0xfa, 0xb0, 0x02, 0x70, 0x4f, +0x1e, 0xdd, 0x76, 0x9c, 0xf9, 0x2a, 0x48, 0xe4, +0x5b, 0x46, 0x7e, 0xb0, 0x35, 0xcd, 0x4d, 0x9d, +0xad, 0xca, 0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0x04, 0x27, 0xc8, +0x50, 0x56, 0x36, 0x86, 0x3c, 0x4e, 0xe8, 0xc4, +0xf1, 0x7c, 0x1e, 0xdf, 0xe1, 0x48, 0x0d, 0x71, +0x76, 0x27, 0xd2, 0x16, 0x50, 0xd1, 0x7a, 0xd7, +0xb4, 0x24, 0xf1, 0x72, 0xf2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0xf6, +0x92, 0x48, 0xaf, 0x11, 0xd9, 0xd0, 0x80, 0xf5, +0xcd, 0x06, 0x1d, 0x38, 0x31, 0xbf, 0xcd, 0xcf, +0xcd, 0x24, 0x38, 0x5a, 0x7e, 0x2a, 0x85, 0x72, +0x51, 0xbd, 0xe7, 0xc7, 0x44, 0x66, 0x48, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0xb8, 0x3a, 0x3c, 0x9c, 0xef, 0x61, 0xaf, +0x51, 0x55, 0xee, 0x72, 0xc5, 0x4e, 0x4e, 0x34, +0x57, 0x3f, 0x76, 0x47, 0x04, 0xd2, 0x32, 0x08, +0xa9, 0x13, 0xbb, 0x8a, 0xd7, 0x5c, 0x2e, 0x99, +0xc0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0xde, 0xe5, 0x0c, 0xcd, 0xc7, +0xea, 0xc5, 0x3d, 0xde, 0xed, 0x7d, 0x52, 0x69, +0x7d, 0xbe, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x19, 0xa0, 0xd6, 0x84, +0x41, 0x27, 0x1e, 0xe8, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0x76, 0x89, 0x9a, +0xe5, 0xdc, 0xd6, 0x29, 0x1d, 0x71, 0x6e, 0xfc, +0xe2, 0xf5, 0xb9, 0x88, 0xc1, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0xe0, +0x36, 0x5f, 0x7c, 0xcf, 0x5c, 0xd0, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0x6d, +0x7e, 0x9a, 0x66, 0xef, 0x9b, 0xf0, 0xea, 0x87, +0x32, 0xa2, 0xf3, 0x4b, 0xa1, 0x4c, 0x3b, 0x27, +0x8f, 0xf6, 0x37, 0x95, 0xca, 0xc9, 0xb0, 0xd9, +0xe8, 0x9d, 0xc0, 0x82, 0x7a, 0x61, 0x74, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x7a, 0xa4, 0xac, 0x85, 0xa5, 0xb5, 0xba, +0x5e, 0x21, 0x67, 0x9c, 0x24, 0x15, 0xa6, 0xf9, +0x72, 0xa6, 0x17, 0x30, 0xec, 0xcf, 0x72, 0xd4, +0x39, 0xb7, 0x7c, 0x45, 0x53, 0xb7, 0x9e, 0xd2, +0x2f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0x44, 0x37, 0x24, 0x49, 0x36, +0x2a, 0x0f, 0x2d, 0xa8, 0x42, 0xa7, 0xf9, 0x52, +0x95, 0x34, 0xa0, 0xac, 0x9f, 0xd8, 0x6f, 0x9b, +0x93, 0x68, 0x56, 0xff, 0x3a, 0x6c, 0xa9, 0x61, +0x9e, 0xdf, 0x36, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0x8f, 0x0f, 0xa9, +0x72, 0xbc, 0xb5, 0xd3, 0x16, 0x60, 0x11, 0x95, +0xd1, 0x3c, 0xf2, 0x15, 0x06, 0xb3, 0xcb, 0xdc, +0xe0, 0x20, 0x46, 0x93, 0x38, 0x08, 0xd8, 0x61, +0x2f, 0xe4, 0x7a, 0x30, 0xa6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0x64, +0x81, 0xde, 0x0f, 0x47, 0x6a, 0xed, 0xf3, 0x37, +0x4f, 0xae, 0xb0, 0x56, 0x3b, 0xb6, 0xd0, 0xcf, +0x45, 0xb3, 0x96, 0xbf, 0x86, 0x9c, 0xb2, 0xfc, +0x57, 0x26, 0x97, 0x74, 0x2e, 0xaf, 0x69, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0x01, 0x29, 0xf8, 0xd3, 0xcb, 0x66, 0x38, +0x44, 0x40, 0x33, 0x6d, 0x04, 0x7f, 0x3e, 0x8b, +0x4a, 0x6f, 0x4e, 0xaf, 0xee, 0xfd, 0x7f, 0x18, +0x8c, 0xba, 0xa7, 0xd0, 0x7f, 0xf0, 0xeb, 0x61, +0x69, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0xab, 0xab, 0xc7, 0xb7, 0x8f, +0x4e, 0x0d, 0x2f, 0xdb, 0x11, 0x3b, 0x1e, 0x3b, +0xad, 0x56, 0xf1, 0x10, 0xc8, 0x83, 0x13, 0x0a, +0x03, 0x5d, 0x2f, 0x97, 0x66, 0xc9, 0x0f, 0x9f, +0x3f, 0x44, 0x56, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0x52, 0xd1, 0xf5, +0x1d, 0xb2, 0xd7, 0xe2, 0xe0, 0xf4, 0x12, 0x76, +0xdc, 0x6c, 0x59, 0x18, 0x9a, 0xdb, 0x44, 0x26, +0x69, 0xd8, 0x9a, 0xcd, 0xfc, 0xb7, 0xd5, 0x72, +0x4d, 0xd4, 0xbf, 0xbf, 0x0c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0x15, +0x77, 0x75, 0x6c, 0x10, 0x8a, 0x20, 0xad, 0xa7, +0x2e, 0x56, 0xed, 0x3f, 0x07, 0x16, 0x64, 0xd1, +0x07, 0xde, 0x35, 0x73, 0x7f, 0x79, 0x94, 0x05, +0xc3, 0x95, 0x56, 0xd9, 0xb8, 0x55, 0x3c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0xe0, 0x63, 0x6b, 0x36, 0x48, 0x77, 0x26, +0xf1, 0x7f, 0xde, 0xe7, 0x50, 0x3e, 0x68, 0x6b, +0x05, 0xda, 0xbe, 0x1f, 0x44, 0x8b, 0xd5, 0x00, +0xac, 0x90, 0x44, 0x43, 0x9f, 0x11, 0x45, 0x12, +0x05, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0x11, 0xf1, 0xb0, 0xe3, 0xd1, +0x9d, 0xc7, 0x58, 0x9d, 0x7f, 0x8e, 0x18, 0x07, +0x2a, 0xdf, 0x23, 0x7a, 0x15, 0xf6, 0x91, 0x27, +0x85, 0x93, 0x11, 0x07, 0x95, 0xd4, 0xf1, 0xc7, +0xcb, 0x3c, 0xbe, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0x07, 0x92, 0xbd, +0x4f, 0xdb, 0xf3, 0xc0, 0x61, 0x2d, 0xac, 0x0b, +0xa7, 0x80, 0xde, 0xb1, 0x5a, 0x35, 0xcb, 0xd6, +0x50, 0x2f, 0xc3, 0xac, 0xec, 0xc2, 0x90, 0xd4, +0xdd, 0x35, 0xb1, 0xe0, 0x6c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0x71, +0x12, 0x18, 0x87, 0x9b, 0x49, 0xae, 0x28, 0xc3, +0x26, 0x0d, 0x33, 0xec, 0xf5, 0x13, 0x6f, 0xb0, +0xdb, 0x0c, 0x74, 0x21, 0x03, 0xe0, 0x84, 0x25, +0x78, 0xea, 0x75, 0x68, 0x08, 0xb6, 0xd7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0x4f, 0x05, 0x2c, 0xd0, 0xc6, 0xe7, 0xfd, +0x33, 0xce, 0x63, 0x75, 0xa2, 0x1f, 0x21, 0xbd, +0x0e, 0x26, 0x47, 0xed, 0x7f, 0xe9, 0xdd, 0x45, +0x4f, 0xbb, 0xd4, 0xbf, 0xc0, 0x45, 0xeb, 0xf4, +0xa5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0x20, 0x7f, 0x4a, 0x86, 0xb8, +0x6e, 0x3e, 0xc3, 0x79, 0xeb, 0x92, 0x0f, 0x8d, +0x6e, 0x83, 0x96, 0xd6, 0x4c, 0x12, 0x71, 0x78, +0x5b, 0x6d, 0x7e, 0xdd, 0x1e, 0xc9, 0x7b, 0x1d, +0x58, 0xb2, 0x4b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0xff, 0xe6, 0x4f, +0x03, 0x47, 0x68, 0x87, 0xf4, 0x57, 0x4c, 0xa5, +0xf2, 0x24, 0xa3, 0x01, 0xe5, 0x85, 0x35, 0x0c, +0x5b, 0x59, 0x11, 0x64, 0xd3, 0x8e, 0x0c, 0x23, +0x0d, 0xf1, 0x4d, 0x08, 0x5e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0xb0, +0x91, 0x01, 0x83, 0x62, 0xc5, 0xc3, 0x4e, 0xe8, +0xa4, 0x3a, 0x4b, 0x9a, 0x5f, 0xdc, 0xb9, 0xdc, +0x2a, 0x20, 0xa9, 0x36, 0xb1, 0x55, 0x28, 0x92, +0x19, 0xd9, 0xc9, 0x29, 0x40, 0x64, 0x80, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0x5c, 0xaf, 0xf3, 0x1a, 0xa1, 0xf3, 0xb3, +0xc1, 0x19, 0xaf, 0x70, 0x64, 0x98, 0xda, 0x05, +0xc1, 0x7a, 0x83, 0xaf, 0xb4, 0x30, 0xbc, 0xca, +0x2c, 0xf3, 0x9e, 0xa8, 0xfc, 0x62, 0x5a, 0xa4, +0xca, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0x9e, 0xb1, 0x5e, 0x2d, 0x53, +0x9d, 0xb0, 0x1f, 0x09, 0xdc, 0x60, 0x4c, 0x7d, +0xca, 0xf6, 0xfb, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x0c, 0x1f, 0x3c, 0xfa, +0xcb, 0x24, 0xb2, 0x26, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0x2d, 0xff, 0xe8, +0x77, 0xbd, 0xfe, 0xac, 0xe1, 0x9c, 0xb9, 0x23, +0x5a, 0x3c, 0x2e, 0x5e, 0x14, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x1f, +0xe8, 0xdd, 0xe0, 0x2c, 0x55, 0x2f, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x4b, +0xec, 0x49, 0x09, 0x78, 0xa1, 0x2a, 0xac, 0x71, +0x50, 0xf9, 0x2d, 0x78, 0xdd, 0x34, 0xd0, 0x47, +0xb5, 0x11, 0x73, 0x56, 0x32, 0x4e, 0xb7, 0x3a, +0x95, 0x27, 0xa2, 0xd8, 0xaa, 0x73, 0x6f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0xe6, 0x9c, 0x0f, 0x6c, 0x2b, 0x3a, 0x3b, +0x95, 0x10, 0xca, 0x51, 0x3b, 0x30, 0xdd, 0x39, +0x23, 0xf8, 0x2b, 0x7f, 0x75, 0x29, 0x77, 0x2f, +0x3e, 0x83, 0x34, 0x81, 0xb7, 0xdf, 0x55, 0xef, +0x22, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0xae, 0x94, 0x7a, 0xb4, 0x6d, +0x39, 0x7c, 0xd3, 0xcb, 0x5a, 0xf7, 0xb7, 0x15, +0xf4, 0x02, 0xb7, 0x6e, 0xb0, 0xc3, 0x02, 0xe1, +0xf1, 0xce, 0x30, 0xc5, 0x71, 0x66, 0xdd, 0xac, +0xed, 0x06, 0x69, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0xcc, 0xfc, 0xfc, +0xcf, 0x4a, 0xcb, 0x2f, 0x4d, 0xb9, 0x12, 0xe4, +0xfd, 0x49, 0x0f, 0xec, 0xe3, 0xb4, 0xaa, 0xde, +0x6d, 0xe6, 0xe5, 0xab, 0xdf, 0x4d, 0xb9, 0xe0, +0x6f, 0xf4, 0x5a, 0x62, 0x2e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0x6a, +0x59, 0x76, 0xcf, 0x7c, 0xbd, 0xb3, 0x9c, 0xa4, +0x7e, 0x3d, 0x10, 0xa6, 0x48, 0x91, 0x7a, 0x68, +0xef, 0x93, 0xb3, 0x87, 0xb6, 0xc6, 0xa4, 0xa3, +0xbf, 0x3d, 0xc1, 0x60, 0x82, 0xf0, 0xe8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0xc4, 0x65, 0x6a, 0x4b, 0x56, 0x68, 0x4b, +0xb5, 0x78, 0x19, 0x43, 0x52, 0x02, 0x2f, 0x8b, +0x7d, 0x3e, 0xa3, 0x55, 0x2c, 0x6d, 0x80, 0x1c, +0x76, 0xa0, 0x36, 0xed, 0x0e, 0x9f, 0x5e, 0x98, +0xdd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0xe0, 0xcf, 0x97, 0x70, 0xca, +0x7f, 0x49, 0x05, 0x70, 0x0d, 0x40, 0xc0, 0x8f, +0x68, 0xbc, 0xcb, 0xac, 0xcf, 0x7a, 0x99, 0x67, +0xc3, 0x2b, 0x03, 0xcf, 0xf1, 0x07, 0x7a, 0x4a, +0xe1, 0x48, 0x98, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0xc7, 0x63, 0xb3, +0x2d, 0x47, 0xbb, 0xbd, 0xac, 0x05, 0xd6, 0xbb, +0xaa, 0x01, 0xab, 0xde, 0x4c, 0x9a, 0x0f, 0x79, +0xbd, 0x8d, 0x7e, 0xf3, 0xb5, 0x2c, 0x55, 0x56, +0x4c, 0x3f, 0xa3, 0xe2, 0xb4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0x00, +0x26, 0xda, 0x39, 0x4a, 0xe7, 0xdf, 0xc4, 0x7c, +0x85, 0x48, 0xc7, 0xd9, 0x0b, 0x15, 0x6b, 0xeb, +0x83, 0x68, 0x62, 0xad, 0x7c, 0x8b, 0x4d, 0xf5, +0xe7, 0xac, 0xc9, 0x35, 0xd8, 0xdb, 0x5a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0xaf, 0xa7, 0xe5, 0xb1, 0x8e, 0x81, 0xbd, +0x9c, 0x45, 0x52, 0xcc, 0x76, 0x9c, 0x32, 0x40, +0xe9, 0xa1, 0x73, 0x69, 0xa4, 0xfc, 0x56, 0xba, +0x1b, 0x7d, 0xba, 0x10, 0xa7, 0x3f, 0x2e, 0xce, +0xe9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0x67, 0xce, 0x7a, 0xc7, 0x6d, +0x9c, 0x33, 0xb2, 0x8a, 0x27, 0xe7, 0x12, 0x68, +0x7e, 0x18, 0x1e, 0x1d, 0x1e, 0x66, 0xb2, 0xea, +0x60, 0xe0, 0x87, 0x08, 0x6e, 0x0d, 0x56, 0x9f, +0x86, 0x3f, 0x9c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x00, 0x72, 0x8c, +0x3b, 0xae, 0x7b, 0xa3, 0xdc, 0x05, 0x5f, 0x4a, +0xac, 0xc9, 0x67, 0x59, 0x07, 0x41, 0x39, 0xcd, +0xc7, 0x4a, 0x73, 0x11, 0x70, 0xe2, 0x85, 0xe4, +0x09, 0x54, 0xe1, 0xc2, 0x38, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0x6d, +0xee, 0x16, 0x6c, 0x8e, 0x80, 0x39, 0xea, 0x51, +0x82, 0x27, 0xda, 0x45, 0x88, 0xe8, 0x48, 0xb8, +0xe4, 0xd8, 0x53, 0xdd, 0xcb, 0xd9, 0x70, 0xc0, +0xac, 0xb0, 0xc2, 0xbb, 0x2d, 0x5c, 0x12, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0x8c, 0x45, 0x62, 0x21, 0x55, 0x75, 0x72, +0xe3, 0x1e, 0x92, 0xa7, 0x14, 0x7e, 0xce, 0x11, +0x83, 0xec, 0xe7, 0xca, 0xb7, 0xd2, 0x57, 0x70, +0xc2, 0xba, 0x67, 0x33, 0xf5, 0xe4, 0xa5, 0x6e, +0x0f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0xb6, 0xe3, 0x53, 0x12, 0xc3, +0x2e, 0x32, 0x96, 0x22, 0x37, 0xf3, 0x97, 0xf8, +0x98, 0xb4, 0x91, 0x01, 0x72, 0x13, 0x7b, 0x95, +0x19, 0x92, 0x85, 0xb7, 0xd1, 0x67, 0x35, 0x54, +0x3a, 0xe7, 0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0x7b, 0x35, 0xe2, +0x7a, 0x98, 0x4f, 0x64, 0x4f, 0xcd, 0x53, 0xd8, +0x6e, 0xf5, 0xf1, 0x88, 0xdc, 0x96, 0xdf, 0x03, +0x57, 0x3c, 0x3a, 0xf1, 0xd4, 0x1b, 0x25, 0xca, +0x4e, 0x34, 0xfe, 0x77, 0x06, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x60, +0xb2, 0x10, 0xba, 0x79, 0x6c, 0x0b, 0xcf, 0xee, +0xff, 0xb8, 0xf6, 0x6d, 0x0b, 0xaf, 0x38, 0xcf, +0x81, 0x02, 0x2b, 0x06, 0x39, 0xab, 0x9e, 0xe4, +0x03, 0xb7, 0x56, 0x9a, 0xd1, 0x4e, 0x8c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0xa0, 0x89, 0xf7, 0x76, 0x38, 0x6d, 0x39, +0x82, 0xfb, 0xd1, 0x5d, 0x89, 0xfc, 0x0f, 0xc5, +0xe4, 0xb9, 0xda, 0xad, 0x56, 0x89, 0x55, 0x51, +0x4d, 0xf4, 0x0b, 0x0b, 0xc1, 0x40, 0x7a, 0x23, +0xd1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0xfb, 0x70, 0xa3, 0xed, 0x0e, +0xa1, 0x77, 0xbc, 0x3a, 0xb1, 0xc5, 0x7d, 0x4f, +0x16, 0x73, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x2c, 0xee, 0xa9, 0xba, +0x39, 0xc8, 0x55, 0xc1, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0xe8, 0xf9, 0xe7, +0x8b, 0x07, 0x43, 0x13, 0x4a, 0x27, 0x54, 0xab, +0x6f, 0xfa, 0x8b, 0x9a, 0x1b, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf9, 0x47, +0x13, 0xe9, 0x80, 0xa4, 0x0f, 0xc5, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0x2d, +0xac, 0x53, 0xab, 0x5b, 0x3a, 0x90, 0x8c, 0x23, +0x86, 0xc8, 0xeb, 0x3c, 0xb2, 0x69, 0xd1, 0x64, +0x3b, 0x0f, 0x02, 0xdc, 0x0e, 0x5c, 0xc3, 0xa8, +0x26, 0x52, 0xde, 0x99, 0x5c, 0x29, 0x87, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0xfb, 0xf5, 0x34, 0xfd, 0x78, 0xf5, 0x97, +0x3f, 0xff, 0x9e, 0x88, 0x7c, 0xb6, 0x96, 0xee, +0x30, 0xc0, 0x59, 0xbe, 0x5e, 0x8c, 0xd7, 0x67, +0xa9, 0x89, 0xac, 0x68, 0xd8, 0xd7, 0xe8, 0x52, +0xd5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0xc3, 0xd6, 0x2c, 0xf0, 0xaa, +0xb0, 0xd1, 0x5b, 0xe5, 0x17, 0x4e, 0xb7, 0x9c, +0xa8, 0x3f, 0xb4, 0xe7, 0x6a, 0x0b, 0x4c, 0xb5, +0xe7, 0xa6, 0x72, 0x12, 0x9f, 0x45, 0x39, 0x3b, +0x0e, 0x07, 0xa6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0x57, 0x78, 0x9c, +0x03, 0x2e, 0xbd, 0x97, 0xcb, 0x13, 0xfc, 0x4d, +0x79, 0xef, 0x4d, 0x42, 0xde, 0xa4, 0xb5, 0x71, +0xaa, 0x1f, 0xa5, 0x29, 0x6f, 0x93, 0xeb, 0x2f, +0x82, 0xff, 0x22, 0x39, 0x26, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0x39, +0xb3, 0x48, 0x1b, 0xaf, 0xf6, 0x4c, 0x00, 0x83, +0x4d, 0x4e, 0x71, 0x0e, 0x78, 0x5f, 0xe7, 0x8c, +0x34, 0x22, 0xfd, 0xc4, 0x73, 0x0b, 0x87, 0x1a, +0x69, 0x57, 0xe9, 0xb5, 0xe5, 0x37, 0xa4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0xbe, 0x61, 0x39, 0xdb, 0xd7, 0x01, 0xcc, +0xe9, 0x83, 0xf4, 0x4d, 0x8e, 0xfd, 0x56, 0xda, +0x07, 0x71, 0x7c, 0xd1, 0x2d, 0xc1, 0xab, 0xed, +0x3a, 0xb8, 0x19, 0xf5, 0xdb, 0x31, 0xaf, 0xba, +0x14, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0xce, 0x67, 0x1f, 0x72, 0x62, +0xa5, 0x14, 0xc8, 0x93, 0xc3, 0x8d, 0x5b, 0xe9, +0xbb, 0xd4, 0xd3, 0xf7, 0xc2, 0x84, 0x12, 0x87, +0xc8, 0x79, 0x16, 0xd6, 0x3a, 0x70, 0x97, 0x3b, +0x6e, 0x54, 0x6b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0x8c, 0x5f, 0x4d, +0x32, 0x39, 0xdc, 0xd3, 0x82, 0xe7, 0x7f, 0x01, +0x13, 0x2e, 0x09, 0x7f, 0x97, 0xfa, 0x68, 0x0f, +0x1b, 0x9e, 0x8e, 0xb1, 0x06, 0x5a, 0x22, 0x4e, +0xa2, 0x89, 0x89, 0x6a, 0x64, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0x43, +0xba, 0x73, 0x03, 0x41, 0x56, 0x12, 0xa6, 0xf1, +0x8c, 0x43, 0x44, 0xff, 0xed, 0x8c, 0x05, 0x6d, +0x04, 0x60, 0x70, 0xbc, 0x9b, 0x57, 0xe5, 0x34, +0x0e, 0x50, 0x5a, 0x59, 0x60, 0xd9, 0x67, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0xc2, 0xb1, 0x12, 0xfb, 0xfd, 0x20, 0x99, +0x4d, 0xd2, 0x68, 0x74, 0xea, 0x11, 0xd2, 0x89, +0x0b, 0x3e, 0x04, 0xfa, 0xe4, 0x34, 0x41, 0x8a, +0xec, 0x3c, 0x5b, 0x04, 0x7f, 0x8e, 0x4b, 0x75, +0x0a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0x06, 0x8b, 0xd3, 0x22, 0x46, +0x7a, 0x20, 0x9b, 0x3d, 0xe1, 0xec, 0x90, 0xf6, +0x90, 0xc3, 0xb4, 0xac, 0x8c, 0x8a, 0x7a, 0xe2, +0x22, 0x88, 0xac, 0xdb, 0x25, 0x7e, 0x4f, 0xd4, +0x01, 0xa3, 0xce, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0xbd, 0xb9, 0x66, +0xb1, 0x84, 0x09, 0x05, 0xd8, 0xb8, 0x4a, 0x03, +0xcb, 0x09, 0x85, 0xfc, 0x50, 0xd4, 0xd0, 0xf6, +0xec, 0x34, 0x09, 0xe7, 0xbc, 0x04, 0xe8, 0x6a, +0x7d, 0x18, 0x2c, 0xc2, 0x97, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0xd8, +0x14, 0x9c, 0xcc, 0xf5, 0x5e, 0xfb, 0x98, 0x11, +0x1f, 0x89, 0x7d, 0xd2, 0x1d, 0x00, 0xf5, 0x85, +0xe4, 0xd9, 0x65, 0x4e, 0xf5, 0xef, 0xfc, 0xa5, +0x37, 0x29, 0xcf, 0x79, 0xd5, 0x82, 0x6d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x43, 0x8c, 0x90, 0x62, 0x2d, 0x4b, 0xf3, +0x83, 0x29, 0x60, 0xe4, 0x44, 0x57, 0x3e, 0x0b, +0x35, 0x4b, 0x88, 0xed, 0xb7, 0xac, 0xd7, 0x66, +0xc5, 0xb7, 0x9b, 0xfa, 0x59, 0xc9, 0x9f, 0xef, +0x23, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0x7b, 0xe4, 0x54, 0xde, 0xef, +0x23, 0xcc, 0xfa, 0xd1, 0x97, 0x2b, 0x36, 0x5d, +0x31, 0xc2, 0xa7, 0xd3, 0x32, 0xdf, 0xd3, 0xf0, +0x56, 0x07, 0xb6, 0x18, 0x4a, 0xca, 0xf6, 0x6a, +0xc4, 0xae, 0x41, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0xb0, 0x69, 0x4d, +0xb0, 0x4b, 0x09, 0x48, 0xc1, 0x95, 0xee, 0x65, +0x51, 0x94, 0xb1, 0x7b, 0x1f, 0x49, 0x13, 0x91, +0xa2, 0xac, 0xe7, 0x0d, 0x09, 0x1c, 0x14, 0x0d, +0x50, 0x3c, 0x03, 0x24, 0x6f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0x13, +0xe1, 0x4d, 0x84, 0xb3, 0x62, 0x05, 0xf3, 0x8e, +0xb1, 0x51, 0xc7, 0x66, 0xa6, 0x45, 0xf3, 0x52, +0xe8, 0x2f, 0x99, 0x2a, 0xa6, 0x52, 0xa3, 0xa2, +0x7f, 0xe6, 0x12, 0xdc, 0xc7, 0x6e, 0xe1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0xe0, 0x2c, 0xca, 0x99, 0xc6, 0x9c, 0x42, +0xc1, 0xe8, 0xeb, 0x00, 0xcd, 0xd5, 0x7f, 0x65, +0x25, 0x2f, 0x7a, 0xd1, 0x94, 0x5c, 0x4d, 0xd3, +0x8d, 0xee, 0xb0, 0x17, 0x6e, 0x91, 0xf5, 0x1e, +0xfe, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0xc3, 0xea, 0x1a, 0x30, 0xc1, +0x75, 0x67, 0x82, 0x10, 0xe2, 0xcc, 0xe8, 0x3d, +0x31, 0xba, 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x44, 0x02, 0x50, 0x47, +0xf6, 0xc0, 0x48, 0xab, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0x3d, 0x6c, 0x72, +0x39, 0xb1, 0xc1, 0xed, 0x5c, 0xa5, 0x27, 0x7b, +0x04, 0xc2, 0x1a, 0x67, 0x71, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f, 0x84, +0x6e, 0xbe, 0x2b, 0x04, 0x71, 0xc4, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0x20, +0x78, 0x62, 0x36, 0xbf, 0xc8, 0x0f, 0x41, 0x74, +0xc2, 0x1f, 0x24, 0xb9, 0x99, 0xd2, 0xe7, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x22, 0x0c, 0x51, 0x17, 0xfa, 0x89, 0xd2, 0x62, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x00 +}; +EXPORT_SYMBOL_GPL(BOOT_FLASH_FW_PASS2); diff --git a/include/linux/mfd/max77705_extra.h b/include/linux/mfd/max77705_extra.h new file mode 100644 index 000000000000..3572e72cc2a4 --- /dev/null +++ b/include/linux/mfd/max77705_extra.h @@ -0,0 +1,6635 @@ +const uint8_t BOOT_FLASH_FW_EXTRA[] = { +0xc1, 0x66, 0xf1, 0xce, 0x2d, 0x18, 0x15, 0x02, +0x01, 0x22, 0x21, 0x70, 0x01, 0x02, 0x03, 0x04, +0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, +0x0d, 0x0e, 0x0f, 0x00, 0x2d, 0x18, 0x15, 0x02, +0x03, 0x00, 0x00, 0x00, 0x95, 0x11, 0x25, 0xdf, +0xe1, 0x1e, 0x27, 0xc7, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0x20, 0xa9, 0xe8, +0x91, 0x2c, 0xdf, 0x69, 0xb7, 0x95, 0x9d, 0xa4, +0x69, 0x33, 0xed, 0x4a, 0x1d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0xff, +0x31, 0x2e, 0x20, 0x71, 0x73, 0xd4, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0x39, +0xf5, 0xe2, 0x60, 0x2a, 0x54, 0x89, 0x7b, 0x1e, +0x52, 0x2b, 0xe8, 0x50, 0x74, 0xaf, 0x33, 0xe8, +0xe3, 0x6f, 0x45, 0x8f, 0x2f, 0x3e, 0x2f, 0x0f, +0x57, 0x71, 0x15, 0xa7, 0xfd, 0x32, 0xe6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0x40, 0x65, 0xc4, 0xa6, 0x79, 0xfb, 0xfe, +0x66, 0x74, 0xbd, 0x09, 0x75, 0xc2, 0x88, 0xbf, +0x10, 0xc6, 0x72, 0xcc, 0x09, 0x0c, 0xb6, 0x8f, +0x0b, 0x31, 0x16, 0x26, 0xdf, 0xf0, 0x83, 0xf2, +0xb5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0x4f, 0x79, 0xab, 0x67, 0x13, +0x03, 0x6a, 0x29, 0x1f, 0x0f, 0xd8, 0xee, 0x86, +0x68, 0x1d, 0x6a, 0xba, 0x19, 0x32, 0x25, 0xe4, +0x78, 0x3d, 0x53, 0x89, 0x4a, 0x9e, 0x0f, 0xf9, +0xac, 0xbb, 0xc0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0xd9, 0xa1, 0x45, +0x05, 0x4b, 0x9d, 0xe9, 0x45, 0xb6, 0xba, 0xff, +0xf0, 0x6b, 0x8a, 0xec, 0x11, 0x82, 0x09, 0xe3, +0xb9, 0x7e, 0x2a, 0x37, 0x75, 0xdf, 0x78, 0x0a, +0x89, 0x35, 0x27, 0xd1, 0x8c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0xa1, +0x26, 0xed, 0xee, 0x76, 0x90, 0x74, 0xc2, 0x55, +0x14, 0x18, 0x81, 0x95, 0x98, 0x2b, 0xee, 0x6a, +0x09, 0xba, 0xad, 0x33, 0x54, 0xab, 0xa4, 0x4a, +0xdd, 0x0d, 0x01, 0xe3, 0xd0, 0xb2, 0x1f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0x4c, 0xf0, 0xf1, 0x99, 0xe9, 0x1a, 0x94, +0x9f, 0xc8, 0xab, 0x4b, 0xeb, 0xfd, 0x68, 0x62, +0x6b, 0x28, 0xfd, 0xc2, 0x36, 0xf3, 0x8a, 0x90, +0xf1, 0xf7, 0x8d, 0xa8, 0xfe, 0xf3, 0x5a, 0xdb, +0x10, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0xde, 0xad, 0x13, 0xa3, 0x9c, +0xb9, 0x02, 0x2b, 0x56, 0x19, 0xce, 0x15, 0xc2, +0xb8, 0xeb, 0x3c, 0x24, 0x91, 0xad, 0xee, 0x54, +0x68, 0x32, 0xcf, 0x44, 0x6e, 0xb4, 0xe5, 0xca, +0xc4, 0xa8, 0x09, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0xab, 0xd4, 0xb0, +0x67, 0x99, 0xb7, 0xc3, 0xeb, 0x25, 0x31, 0xeb, +0x54, 0x09, 0x78, 0x2a, 0x9d, 0x60, 0x11, 0xf2, +0x9a, 0x99, 0x28, 0x69, 0xac, 0xed, 0xd9, 0xf8, +0x9f, 0x6b, 0xfe, 0x3e, 0x6c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0x33, +0x21, 0xa6, 0x36, 0x3d, 0xf5, 0x0c, 0x06, 0x3c, +0xc1, 0xcd, 0x9a, 0x70, 0xee, 0x90, 0x5e, 0xc4, +0x20, 0xcc, 0x48, 0x78, 0x9c, 0x8d, 0x79, 0xfd, +0x64, 0xc0, 0xa7, 0xed, 0xf6, 0xb3, 0xae, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x47, 0x3a, 0xbf, 0x33, 0x2c, 0x68, 0x95, +0xa1, 0x69, 0x19, 0x4e, 0x96, 0x60, 0x55, 0x73, +0x53, 0x34, 0x97, 0xe4, 0x49, 0x48, 0x39, 0x34, +0x3a, 0x50, 0x8e, 0xb8, 0x52, 0xde, 0xad, 0x2a, +0xfa, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0xe0, 0x04, 0x8e, 0x7b, 0x52, +0x24, 0xb6, 0xc5, 0xf1, 0x20, 0x53, 0x55, 0xb4, +0x89, 0xdb, 0xe3, 0x4e, 0xd8, 0x7d, 0x9e, 0xe7, +0xcb, 0xb7, 0x4e, 0xf3, 0x11, 0xbc, 0x92, 0x71, +0x17, 0xfb, 0x8d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0x2e, 0x5e, 0x88, +0xf8, 0x19, 0x16, 0x31, 0xc9, 0x04, 0x0a, 0x45, +0x15, 0xd5, 0x7a, 0xbc, 0xe7, 0x5f, 0xf6, 0xcf, +0x62, 0x59, 0xc0, 0x08, 0xf8, 0x1a, 0xca, 0xc1, +0xd7, 0xe7, 0xb9, 0xc7, 0x18, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0xcb, +0x46, 0x90, 0x52, 0x72, 0x42, 0x6a, 0x39, 0x61, +0xb4, 0xdf, 0xb4, 0x87, 0xcf, 0x86, 0x02, 0x98, +0x57, 0x4f, 0xe3, 0xb6, 0xe3, 0x33, 0x14, 0x70, +0x64, 0x3a, 0x3b, 0xcd, 0x8d, 0x32, 0xb5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0xbe, 0x50, 0x86, 0x73, 0x45, 0xf5, 0xba, +0x9f, 0x81, 0x04, 0xc3, 0xc4, 0x14, 0x6e, 0x10, +0xbe, 0x77, 0x9b, 0x46, 0x56, 0xc6, 0x03, 0x4b, +0x45, 0xec, 0x95, 0xef, 0xb1, 0xda, 0x9d, 0x53, +0xa7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0x73, 0xef, 0x88, 0x99, 0xa2, +0x27, 0xce, 0x62, 0xb2, 0xdc, 0x63, 0x49, 0x33, +0xff, 0xf6, 0xb0, 0xb0, 0x96, 0x67, 0x76, 0x01, +0xe5, 0x07, 0x1d, 0x12, 0x34, 0x97, 0xfe, 0x4a, +0x2a, 0x1e, 0xaf, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0x10, 0x90, 0x5e, +0x74, 0x7f, 0x38, 0x02, 0x69, 0x79, 0x3e, 0x68, +0xca, 0x3f, 0xe1, 0x83, 0x41, 0x0a, 0xa9, 0x5a, +0xab, 0xfc, 0xe4, 0x52, 0xd0, 0x7e, 0xfb, 0xef, +0x7d, 0x43, 0x61, 0x29, 0xd3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x4b, +0xba, 0xb7, 0xb8, 0xec, 0x96, 0x1d, 0x0b, 0xb3, +0x0d, 0xaa, 0x9e, 0xac, 0x7b, 0xb5, 0x87, 0x63, +0x57, 0x83, 0xcd, 0x5e, 0xf2, 0x13, 0xd7, 0xdc, +0x91, 0xa4, 0x61, 0x4a, 0xcf, 0xc8, 0x6c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0x28, 0x79, 0x8c, 0xaf, 0x9f, 0x05, 0x40, +0x27, 0x75, 0x70, 0xaf, 0x6c, 0x25, 0x73, 0xdd, +0x3d, 0x0e, 0x26, 0x51, 0x8d, 0x90, 0x5f, 0xa1, +0x79, 0xf0, 0xc1, 0xe1, 0xf6, 0xc0, 0x0c, 0xd1, +0xde, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0xeb, 0x3b, 0xa9, 0x8e, 0x9b, +0xf1, 0x9f, 0xf1, 0x2e, 0xb7, 0x5c, 0x4a, 0x6d, +0x84, 0x1b, 0xba, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x2f, 0x73, 0xeb, 0x22, +0x22, 0x5a, 0xfa, 0x6d, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0x51, 0x28, 0xa6, +0x78, 0xe6, 0x3a, 0x72, 0x2f, 0xca, 0xab, 0x6a, +0x7c, 0x58, 0xac, 0xe9, 0x6b, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xec, 0xdb, +0x73, 0x76, 0x31, 0xbc, 0xf1, 0x50, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x1e, +0x8d, 0xea, 0x02, 0x8d, 0xdb, 0xf1, 0x2f, 0x8d, +0x6b, 0x7e, 0xc3, 0x63, 0xfe, 0x10, 0xd4, 0x67, +0x63, 0x9f, 0x60, 0x17, 0xe2, 0xa5, 0x22, 0x65, +0xcf, 0x79, 0xf3, 0xf6, 0x87, 0x51, 0x35, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0x13, 0x1b, 0xd2, 0x77, 0xc6, 0x6b, 0xff, +0x8d, 0xbc, 0x44, 0xcd, 0x81, 0x47, 0x70, 0xf0, +0xb3, 0x18, 0x1b, 0x96, 0xc7, 0x3b, 0xff, 0x26, +0x0d, 0x65, 0x9c, 0xd6, 0x97, 0x1f, 0x3f, 0x22, +0xd8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0xe6, 0x73, 0xb9, 0x27, 0x9a, +0xe8, 0x0c, 0x99, 0x53, 0x18, 0x31, 0xb5, 0x36, +0x8e, 0x4c, 0xd7, 0xe2, 0x1b, 0xd1, 0x08, 0xc4, +0xab, 0x6e, 0xa4, 0x13, 0xa4, 0xed, 0x83, 0x4e, +0x2e, 0xaa, 0x97, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x59, 0x69, 0x12, +0xf0, 0x4d, 0x3e, 0xd9, 0x1f, 0x6d, 0xf7, 0xe0, +0xdb, 0xa1, 0x5c, 0xe0, 0xfd, 0xcd, 0x41, 0xe6, +0x6f, 0xe9, 0x3b, 0x1d, 0xfb, 0xbb, 0xe3, 0x4e, +0xf3, 0x88, 0xfe, 0xea, 0xc3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0xa1, +0x3f, 0xad, 0x20, 0xd3, 0xba, 0x27, 0xed, 0xcc, +0x63, 0xeb, 0xa7, 0x2e, 0xd5, 0xf6, 0xdf, 0x23, +0xbc, 0x04, 0x73, 0xd3, 0x61, 0x1f, 0x44, 0xbb, +0x5a, 0x37, 0x8a, 0x7f, 0x49, 0xc2, 0x86, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0x04, 0xcb, 0x82, 0xd9, 0x92, 0x31, 0x1e, +0x75, 0x1c, 0x64, 0x06, 0xd3, 0x26, 0x20, 0x11, +0x65, 0x2c, 0x75, 0x51, 0xc0, 0x69, 0x00, 0xe3, +0x39, 0x8b, 0x77, 0x2d, 0x4a, 0x28, 0x26, 0xda, +0xaf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0x62, 0xe7, 0xa7, 0x4f, 0xda, +0x70, 0x64, 0xb3, 0xe8, 0x42, 0x36, 0x26, 0x85, +0x82, 0x8c, 0xb9, 0xbe, 0xbe, 0x60, 0xba, 0x4c, +0xbd, 0x3f, 0xe9, 0x0d, 0x0f, 0x7f, 0xb3, 0x50, +0x4e, 0x6f, 0xb9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0x80, 0x22, 0x44, +0xd2, 0xeb, 0xd5, 0x69, 0x9f, 0x4e, 0xf1, 0xf6, +0xfc, 0x3e, 0x7c, 0xc9, 0x9a, 0xb5, 0x68, 0x2e, +0xdf, 0xd7, 0x56, 0xe9, 0x0f, 0xab, 0xf3, 0x67, +0x7a, 0xb9, 0x17, 0x00, 0xdd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0xe1, +0x92, 0x51, 0x4f, 0x61, 0xb5, 0x16, 0xb1, 0x55, +0x42, 0x75, 0x6a, 0xd3, 0x8a, 0xc3, 0x33, 0x32, +0xd6, 0x92, 0x2e, 0x86, 0xc2, 0x88, 0xde, 0xc8, +0x95, 0x27, 0xde, 0x2d, 0x8a, 0x91, 0x3e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x52, 0xc9, 0xa9, 0x43, 0xf6, 0x6d, 0x95, +0x36, 0xc2, 0x2a, 0x3c, 0x18, 0xea, 0xcb, 0x32, +0xe1, 0x17, 0xf9, 0x52, 0xd9, 0xd0, 0x73, 0xbc, +0x1e, 0x05, 0xc5, 0x00, 0xa5, 0x64, 0x80, 0x36, +0x30, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x0d, 0xb4, 0xf8, 0x60, 0x2d, +0x21, 0x13, 0x60, 0xe1, 0x10, 0x48, 0x7b, 0xd8, +0xa4, 0x54, 0xe8, 0xdb, 0xf2, 0x40, 0xdb, 0x6f, +0x7a, 0xde, 0x23, 0x27, 0xfb, 0xeb, 0x4c, 0x05, +0x48, 0x76, 0x19, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0xa2, 0x85, 0x74, +0x49, 0xcb, 0xcb, 0x92, 0xff, 0x8d, 0xc7, 0xb9, +0xc4, 0xd1, 0xb7, 0x58, 0xc4, 0x7d, 0xa4, 0x6f, +0x55, 0xc1, 0x91, 0x4f, 0xbe, 0x42, 0xa4, 0x5e, +0x59, 0x83, 0x6d, 0x50, 0xe7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0x74, +0xd0, 0x5a, 0x99, 0x01, 0x28, 0xe7, 0x19, 0x31, +0xcc, 0xf6, 0x37, 0x15, 0xe9, 0xf6, 0xa0, 0x14, +0xd9, 0x6d, 0xd2, 0x38, 0x81, 0x03, 0x11, 0x45, +0x6e, 0xfe, 0x10, 0x56, 0x11, 0x89, 0xb6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x96, 0xc0, 0x24, 0x1a, 0x15, 0x94, 0x95, +0xae, 0x87, 0xea, 0x8f, 0xdb, 0x64, 0xc5, 0xa8, +0x40, 0x3a, 0x5c, 0x26, 0xc1, 0xfe, 0x4b, 0xe4, +0x2f, 0x9a, 0x2a, 0xc2, 0x89, 0xf1, 0x48, 0x63, +0x53, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0x29, 0xa2, 0x31, 0xe0, 0x7f, +0xa1, 0xb3, 0x2a, 0x54, 0xa0, 0x86, 0x19, 0x7f, +0x35, 0xf8, 0x4e, 0xe4, 0x80, 0xe6, 0xb5, 0xdc, +0xaf, 0x57, 0x7e, 0x01, 0xfa, 0x02, 0x7d, 0x20, +0x10, 0xe1, 0xa9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0x74, 0x24, 0x6d, +0x37, 0xe0, 0x77, 0xc5, 0xb6, 0xef, 0x34, 0x12, +0x12, 0x95, 0x92, 0x83, 0xd3, 0x93, 0x68, 0x30, +0xa8, 0xbb, 0xfb, 0x85, 0xa5, 0xee, 0xf1, 0xf6, +0x02, 0xf1, 0x69, 0xfa, 0x34, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x5a, +0x96, 0x45, 0xca, 0x6a, 0x15, 0xe2, 0xfa, 0xeb, +0x76, 0x40, 0x47, 0x7c, 0xef, 0x35, 0xd8, 0x98, +0x23, 0xbd, 0x8a, 0xd8, 0xc4, 0x63, 0x57, 0x8c, +0x93, 0x50, 0x5e, 0x72, 0xce, 0x81, 0x87, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0xa0, 0x2d, 0x9e, 0x4c, 0x5f, 0xa2, 0x3a, +0xce, 0xc7, 0x18, 0xba, 0xd9, 0x07, 0x61, 0x60, +0xe1, 0x43, 0xb3, 0x48, 0x2f, 0x87, 0x89, 0x71, +0x1e, 0x8f, 0x30, 0xfa, 0xd1, 0xd7, 0x4d, 0x69, +0xd6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0x0b, 0xd6, 0x8d, 0xd6, 0x51, +0x6d, 0xc2, 0x8f, 0x2d, 0xe0, 0xc0, 0x1f, 0x9a, +0x60, 0xe5, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x11, 0x4b, 0x26, 0xe5, +0xe9, 0xb5, 0x70, 0xa0, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0x10, 0x6e, 0xca, +0x4d, 0xc0, 0x3f, 0xea, 0x14, 0x80, 0xca, 0xd4, +0x68, 0xa8, 0xd2, 0xfd, 0x80, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x1f, +0x95, 0x11, 0x22, 0x6f, 0x2c, 0x45, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0x67, +0x59, 0x40, 0x7d, 0x3e, 0x96, 0x5a, 0x03, 0x83, +0x21, 0xd2, 0x20, 0x4b, 0x78, 0xca, 0x39, 0xbe, +0xb8, 0x2a, 0xd0, 0x60, 0xe0, 0x75, 0xe4, 0xc0, +0x36, 0x4a, 0xe9, 0x09, 0xb8, 0x10, 0xfa, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0x43, 0x80, 0x9b, 0x4c, 0x92, 0x2d, 0x2f, +0x62, 0x63, 0xca, 0x7c, 0xe3, 0x41, 0xfc, 0x3f, +0x3e, 0xd2, 0xe6, 0x2a, 0x26, 0xbd, 0xd8, 0xa0, +0xb8, 0xa5, 0x8f, 0x26, 0x0a, 0xb2, 0xac, 0x59, +0x1a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0xc7, 0x66, 0x46, 0xab, 0x3e, +0xa8, 0x28, 0x77, 0x9b, 0x11, 0x7d, 0x14, 0xfa, +0x2e, 0x73, 0x92, 0xee, 0x52, 0x7d, 0xad, 0xb4, +0x7d, 0xf9, 0xc3, 0x27, 0x3f, 0x8c, 0xd1, 0x68, +0xe9, 0x14, 0xb2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0x9b, 0xc8, 0xe6, +0x14, 0x91, 0x9e, 0xad, 0x67, 0x8f, 0x7c, 0x88, +0xec, 0x89, 0x15, 0x9c, 0xfc, 0xf2, 0xea, 0x08, +0xf8, 0xa1, 0x09, 0x4b, 0x17, 0x5d, 0x67, 0x5d, +0x99, 0xfc, 0x33, 0xb3, 0xa8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0x0c, +0x4c, 0x8b, 0xac, 0x1d, 0xfd, 0x18, 0x5c, 0x54, +0x03, 0x38, 0x20, 0x37, 0xcf, 0xf4, 0xa2, 0x61, +0x55, 0x7d, 0x55, 0xc4, 0x8a, 0x51, 0x1c, 0x0c, +0xf0, 0x51, 0x2c, 0xbe, 0xc5, 0x5d, 0xd9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0xae, 0x32, 0x0a, 0x91, 0xf4, 0xcb, 0x0d, +0xd7, 0xab, 0x30, 0x61, 0x82, 0x78, 0x03, 0x62, +0x36, 0xf6, 0x66, 0x80, 0xa9, 0x45, 0x10, 0x22, +0xe8, 0x93, 0xb6, 0xbb, 0xfd, 0x0e, 0x58, 0xc4, +0x7b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0xfd, 0xfd, 0xaf, 0x1b, 0x1b, +0x75, 0x7b, 0x42, 0x05, 0x6f, 0x7f, 0x77, 0x1b, +0x90, 0x3d, 0xcd, 0x1c, 0xaf, 0x9d, 0xe3, 0x45, +0x92, 0x61, 0x9b, 0xae, 0xca, 0xfb, 0x85, 0xb2, +0xbd, 0xa9, 0x1b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0xce, 0x50, 0x1d, +0xc5, 0x67, 0x78, 0x4d, 0xf7, 0x27, 0x33, 0x85, +0x48, 0xc8, 0x89, 0x3f, 0xc7, 0x4b, 0x79, 0x42, +0x02, 0xe2, 0xf7, 0x0e, 0x87, 0x41, 0x70, 0xe6, +0x40, 0xcb, 0x71, 0xa7, 0x41, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0x1e, +0x8c, 0x53, 0x01, 0x61, 0x80, 0xbe, 0xa6, 0x4b, +0xc3, 0x79, 0x3a, 0xc4, 0xbf, 0xe6, 0x84, 0x91, +0x92, 0x5a, 0x38, 0xa5, 0x11, 0x1d, 0x01, 0xeb, +0x70, 0xb2, 0xd8, 0xf1, 0x82, 0xed, 0xc9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0xc4, 0x46, 0x2a, 0x8e, 0x14, 0xac, 0x1d, +0xd6, 0xa6, 0x92, 0xac, 0x16, 0x09, 0x11, 0xc6, +0x9d, 0x5c, 0xe5, 0xa3, 0xa6, 0x90, 0x1a, 0xd1, +0x02, 0x7f, 0xcc, 0xd3, 0xfc, 0x98, 0x4c, 0x89, +0x93, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0xb4, 0x06, 0x4d, 0xdd, 0x01, +0x0e, 0xd8, 0xfc, 0x3a, 0xee, 0x33, 0x2d, 0xe9, +0xdc, 0x38, 0x58, 0xbd, 0x49, 0xdf, 0x7f, 0xcd, +0xf5, 0x52, 0xf2, 0xde, 0x88, 0xbe, 0x57, 0x29, +0x82, 0x5c, 0x29, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0xdf, 0x52, 0x7e, +0x66, 0x62, 0xf9, 0x50, 0x88, 0x60, 0x45, 0xba, +0xe7, 0xb5, 0x61, 0x55, 0x4e, 0x30, 0xa6, 0x55, +0x41, 0x4c, 0x57, 0x2f, 0x6e, 0xd7, 0xe7, 0x2a, +0x6f, 0x29, 0xcc, 0xf3, 0xf8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0xbc, +0xb6, 0x18, 0x74, 0x4c, 0xc9, 0xb8, 0x81, 0x5d, +0x87, 0x65, 0xdb, 0x41, 0x6d, 0x38, 0x65, 0x48, +0x1b, 0x5e, 0x6c, 0x67, 0xcb, 0xd3, 0x48, 0x19, +0x36, 0x39, 0x77, 0x2e, 0xfa, 0x11, 0x59, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0xb7, 0xc7, 0xda, 0x83, 0x9c, 0xa8, 0x0b, +0xda, 0x16, 0xab, 0x0f, 0xe4, 0x6b, 0xa9, 0xb2, +0xd8, 0x01, 0xa9, 0x68, 0x66, 0x0f, 0x5d, 0xe2, +0x55, 0xde, 0x0a, 0x49, 0x86, 0x36, 0x00, 0xc0, +0x00, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x43, 0x33, 0x57, 0xc8, 0xb1, +0x89, 0x62, 0x57, 0xdc, 0xcd, 0x8f, 0x96, 0x87, +0xa5, 0x16, 0xa4, 0xcd, 0x11, 0x08, 0x79, 0x52, +0xb9, 0xd1, 0xf2, 0x29, 0x36, 0xdc, 0x13, 0x3b, +0x1e, 0x82, 0x4f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0x15, 0xa9, 0xfc, +0x08, 0x8c, 0xc8, 0x40, 0xef, 0x0f, 0x74, 0xaa, +0x9a, 0x06, 0xa6, 0x38, 0xbe, 0xe6, 0xd5, 0x99, +0x7b, 0x7d, 0xf7, 0x76, 0x0e, 0xa3, 0x96, 0x46, +0x61, 0x95, 0x54, 0x2a, 0x2c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0x29, +0x4d, 0xd7, 0xa5, 0xe7, 0x3f, 0x16, 0x6e, 0x40, +0x23, 0x51, 0x5b, 0xb6, 0x2f, 0xcf, 0x27, 0xd8, +0x7c, 0xe2, 0x7e, 0xeb, 0xd9, 0x56, 0x2d, 0x68, +0xe7, 0x92, 0x6f, 0xdc, 0x24, 0x06, 0xfd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x32, 0x15, 0x14, 0xe5, 0xc5, 0x4b, 0x92, +0x43, 0x8b, 0x13, 0x85, 0x25, 0x5d, 0x47, 0xbd, +0x7d, 0xb9, 0x66, 0xc4, 0x51, 0xc2, 0x1b, 0x96, +0xe2, 0xf8, 0x2b, 0x37, 0x9d, 0x04, 0x59, 0x01, +0xc2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x31, 0x08, 0xcd, 0x1e, 0xeb, +0x5d, 0xeb, 0x5c, 0x9a, 0xa2, 0x58, 0x09, 0xe8, +0x5a, 0x4b, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x12, 0xd1, 0xa8, 0xc3, +0x05, 0x81, 0x4b, 0x05, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0xe0, 0x03, 0x05, +0xab, 0x21, 0x16, 0xc8, 0xd2, 0x2f, 0xd9, 0x03, +0x09, 0xe9, 0xc2, 0x22, 0x97, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0xab, +0xe1, 0xd9, 0xe5, 0x0a, 0x47, 0x17, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0xd9, +0x89, 0xc0, 0x1e, 0xd8, 0x1c, 0xbe, 0xcd, 0xd9, +0x84, 0x93, 0x45, 0xf1, 0xa0, 0xb2, 0xb9, 0x43, +0x78, 0x70, 0x2f, 0xfa, 0x25, 0x0c, 0x67, 0xdd, +0xfd, 0xa4, 0xb6, 0x75, 0xde, 0xdd, 0x13, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0xc2, 0x03, 0xff, 0xf9, 0xbc, 0xc9, 0x83, +0x27, 0xe5, 0x0f, 0x42, 0xd8, 0x9c, 0x0e, 0x48, +0x08, 0x39, 0x13, 0xac, 0xd5, 0x94, 0x76, 0x5a, +0x5d, 0xd1, 0x1a, 0x94, 0x1f, 0xfa, 0x34, 0x60, +0x90, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0x87, 0xd7, 0x7f, 0xa5, 0xe6, +0xf3, 0x55, 0x6d, 0x3f, 0x30, 0x03, 0xac, 0xf1, +0xcb, 0x9e, 0x47, 0xbe, 0x5a, 0xa0, 0x7c, 0x4d, +0x01, 0x2e, 0x68, 0x90, 0x87, 0xd7, 0x95, 0x45, +0x9d, 0x8f, 0xf4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0xbb, 0x4e, 0x20, +0xfe, 0x8b, 0x6e, 0x26, 0xcb, 0x8e, 0x21, 0xf3, +0x56, 0x0b, 0xa5, 0x77, 0x7f, 0x79, 0xb4, 0x30, +0x14, 0x22, 0xa0, 0x90, 0xaf, 0xd9, 0xbd, 0x64, +0xc2, 0xd1, 0xc0, 0x4e, 0xbe, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0xfb, +0x81, 0x28, 0xac, 0xb1, 0x9b, 0xc2, 0x5f, 0x75, +0x80, 0x6a, 0xc3, 0x25, 0x62, 0x7e, 0x6c, 0x2e, +0xe4, 0x88, 0x83, 0x9e, 0x34, 0x58, 0x89, 0x0c, +0xa6, 0x5b, 0x1c, 0xe2, 0x1b, 0x96, 0x40, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0xae, 0xcf, 0xc5, 0xd2, 0x83, 0x39, 0x87, +0xd8, 0x10, 0xad, 0x0b, 0x11, 0x27, 0x91, 0x56, +0xc1, 0xdb, 0x34, 0xf8, 0x50, 0x1d, 0x84, 0xb9, +0x9b, 0x0a, 0x31, 0x12, 0xa5, 0x3c, 0xf1, 0xad, +0x97, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0x8c, 0xce, 0x40, 0xa8, 0x51, +0x54, 0x6c, 0xad, 0xc3, 0x00, 0x28, 0xd4, 0x4e, +0x42, 0xb5, 0x96, 0x70, 0x4f, 0x7c, 0x40, 0xfb, +0x08, 0xe9, 0x3c, 0x7a, 0xcb, 0x34, 0x15, 0xa1, +0x97, 0x5b, 0x7b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0x9f, 0xa7, 0xca, +0xd6, 0x04, 0x5a, 0x81, 0x7f, 0xba, 0xd3, 0x0f, +0x73, 0x35, 0xdd, 0xda, 0x93, 0xf8, 0xa0, 0xe2, +0xff, 0x98, 0x28, 0xfb, 0xf9, 0xbb, 0x7c, 0xba, +0x40, 0xea, 0x0b, 0x4f, 0x4a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0x62, +0x4e, 0x1b, 0xe8, 0x94, 0x59, 0xbf, 0x3a, 0xc7, +0xf8, 0xc5, 0xc7, 0x2a, 0x03, 0xc9, 0x9d, 0xa3, +0x0f, 0xa6, 0x1e, 0xc8, 0xd3, 0x6c, 0xf5, 0xd9, +0xee, 0x5d, 0xc3, 0x11, 0x28, 0xa2, 0x82, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0xd3, 0x2b, 0x25, 0x1f, 0x3d, 0xc3, 0x7f, +0x00, 0xac, 0xe5, 0x96, 0xf9, 0x58, 0x57, 0x35, +0x9f, 0x5e, 0xf3, 0x4c, 0x6d, 0x6c, 0x28, 0x3e, +0x55, 0x99, 0x55, 0xd8, 0x7a, 0x25, 0x18, 0xfd, +0x01, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0x33, 0xae, 0xd4, 0x3b, 0x85, +0x68, 0xa6, 0xef, 0x6d, 0xab, 0x4e, 0xe9, 0xc6, +0xc2, 0x0b, 0x83, 0x64, 0xa6, 0x8c, 0x08, 0x22, +0xc3, 0x2f, 0xb3, 0x4f, 0x27, 0xa6, 0x8a, 0x21, +0x59, 0x86, 0xbb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0x4d, 0x0e, 0xca, +0x77, 0x37, 0x8b, 0x22, 0xa5, 0x1b, 0xfe, 0x09, +0xa7, 0x1f, 0x01, 0xea, 0x75, 0xca, 0x9f, 0x27, +0xc8, 0xf8, 0x5b, 0x36, 0xf4, 0x7d, 0x60, 0x47, +0xba, 0xe1, 0x33, 0x4c, 0x1c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0x5a, +0xe7, 0x20, 0xfc, 0xfa, 0x45, 0xfe, 0x98, 0xba, +0x27, 0x4a, 0xc8, 0x34, 0xf8, 0xb8, 0xcd, 0x4a, +0xf9, 0x62, 0xf3, 0x90, 0x5c, 0x18, 0x64, 0x7f, +0x57, 0xa4, 0xba, 0x1d, 0x66, 0xc0, 0x19, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0x29, 0x51, 0xe1, 0x34, 0x6d, 0x60, 0x27, +0x6d, 0x2a, 0x0a, 0x6b, 0x07, 0xcc, 0x68, 0x83, +0x2b, 0xa8, 0xaa, 0x96, 0xb8, 0xf1, 0x42, 0xca, +0xf1, 0x52, 0xb3, 0xff, 0x33, 0x9d, 0x29, 0xeb, +0x98, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0x69, 0xf5, 0x21, 0xd2, 0x57, +0x12, 0x53, 0xb9, 0x31, 0x31, 0x53, 0x60, 0x59, +0xe0, 0x88, 0xf1, 0x34, 0xd8, 0x29, 0x29, 0x8d, +0x61, 0x72, 0x38, 0xa0, 0x66, 0x7b, 0x5f, 0x94, +0xe3, 0x94, 0x7c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x6a, 0x62, 0x0f, +0x07, 0x59, 0x89, 0x6c, 0x41, 0xba, 0xb2, 0xdd, +0xae, 0x68, 0x37, 0x54, 0xb0, 0x43, 0x03, 0x4b, +0x41, 0x90, 0x17, 0x10, 0x61, 0x9f, 0xa1, 0xd0, +0xaa, 0xc6, 0x16, 0x05, 0x47, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0xb1, +0x26, 0x2e, 0x14, 0x92, 0x87, 0xad, 0xf5, 0xdd, +0x3d, 0xa6, 0xcf, 0xaa, 0x8a, 0x1f, 0x3d, 0xd4, +0xbf, 0x1e, 0xe3, 0x8f, 0x96, 0x8e, 0x20, 0x7f, +0xd4, 0x1c, 0x08, 0xc6, 0x20, 0xac, 0x36, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0x48, 0x70, 0x7e, 0xb9, 0x49, 0x36, 0x29, +0x09, 0xd7, 0xf7, 0x81, 0x03, 0xb2, 0xc9, 0x86, +0xb6, 0x96, 0xd7, 0x45, 0x57, 0x76, 0xdf, 0xde, +0x90, 0x7c, 0xb8, 0xce, 0x02, 0xef, 0x80, 0xdd, +0xbd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0x65, 0x5e, 0x1b, 0x64, 0x6b, +0xd9, 0x75, 0x52, 0x53, 0x5a, 0x62, 0x42, 0xb6, +0x8d, 0x51, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xd3, 0xf0, 0xc6, 0xd0, +0x02, 0x81, 0xa6, 0x8f, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x34, 0x9b, 0xfe, +0xea, 0xa4, 0x77, 0xf9, 0xb9, 0xc4, 0x50, 0xb5, +0x8b, 0x27, 0xd9, 0x6a, 0x6a, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xae, +0xcb, 0x9e, 0xfe, 0x81, 0x1e, 0xe5, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x50, +0xc7, 0xea, 0x0f, 0xe4, 0x67, 0x4c, 0xbf, 0x59, +0x1f, 0xe2, 0x22, 0x4c, 0x4c, 0xe1, 0x37, 0x42, +0xbd, 0x17, 0xe4, 0xf1, 0x5e, 0x22, 0xe2, 0x98, +0x37, 0xe3, 0x49, 0x69, 0x9e, 0x15, 0x6e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0xb4, 0x99, 0xd8, 0x11, 0xc3, 0x8e, 0xd4, +0xf5, 0x69, 0x39, 0xfa, 0x73, 0x79, 0xf0, 0x36, +0xa8, 0xd8, 0x93, 0xe2, 0x49, 0x0f, 0x29, 0xd5, +0xf5, 0x15, 0xb2, 0xad, 0x71, 0xa6, 0x13, 0x3d, +0x50, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0xf9, 0x40, 0x3b, 0x8f, 0xa0, +0x61, 0xb8, 0x94, 0x75, 0xc0, 0x36, 0xe4, 0x2c, +0x6b, 0xdf, 0x31, 0xc3, 0xca, 0x9b, 0xe4, 0xe1, +0x90, 0x3e, 0xe5, 0x0f, 0xcf, 0x79, 0x3d, 0x4e, +0x67, 0xf2, 0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0x97, 0xdf, 0x5a, +0x93, 0x29, 0x35, 0x5e, 0xc6, 0x66, 0x1a, 0x5b, +0xea, 0x79, 0xb6, 0xe5, 0xc4, 0xfb, 0xfa, 0xe8, +0x85, 0x0f, 0x6b, 0xf0, 0x40, 0x64, 0x27, 0x72, +0xc6, 0x43, 0x70, 0x65, 0xb5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0xee, +0x32, 0xa3, 0x65, 0x58, 0xea, 0x96, 0x15, 0xbc, +0x87, 0x68, 0x00, 0x45, 0xcc, 0x1a, 0xf0, 0x87, +0x18, 0xb9, 0x3f, 0xe4, 0x26, 0xe2, 0x9c, 0x94, +0xac, 0xce, 0xc2, 0x02, 0xb5, 0x9c, 0x81, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0x7a, 0xd6, 0x2c, 0x56, 0x63, 0x94, 0xdd, +0x22, 0x37, 0x25, 0x52, 0x3a, 0x5d, 0x47, 0xfd, +0xcd, 0x98, 0xec, 0x64, 0x9d, 0xaa, 0x5a, 0x3a, +0x52, 0xb9, 0xe0, 0x09, 0x26, 0xc4, 0x60, 0x7c, +0x68, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0xcd, 0x9f, 0xd4, 0x6c, 0xb7, +0x47, 0x3b, 0xa0, 0x7a, 0x7e, 0xbd, 0x7b, 0x5e, +0x86, 0x4f, 0x1b, 0xef, 0x8a, 0x23, 0xaa, 0x9e, +0x9a, 0xc5, 0xc6, 0x8e, 0x37, 0xef, 0xce, 0x48, +0x23, 0xd2, 0x10, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0xe4, 0xf9, 0x10, +0x59, 0xdb, 0x9c, 0xb9, 0x6d, 0xb7, 0x92, 0x44, +0x85, 0xb5, 0x3e, 0x23, 0x97, 0xaa, 0x8d, 0xd1, +0xac, 0x65, 0xf3, 0x7a, 0xc1, 0x08, 0xa6, 0xb3, +0xc2, 0x85, 0x45, 0x5c, 0x00, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0xee, +0xab, 0x99, 0xc8, 0x62, 0x10, 0xcd, 0x7c, 0xf4, +0x5a, 0x59, 0x24, 0x09, 0x87, 0xbf, 0xfd, 0x8b, +0xd3, 0x85, 0xbc, 0x5d, 0x8a, 0x24, 0x02, 0xf3, +0xab, 0x2b, 0xa3, 0x9f, 0xfd, 0xf0, 0x72, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0xb5, 0x13, 0x15, 0xa9, 0xbc, 0x73, 0x31, +0x98, 0x13, 0xc3, 0x67, 0xae, 0x6e, 0x63, 0x5d, +0xb9, 0xc7, 0x6f, 0x5c, 0xfe, 0x5d, 0xb1, 0x4f, +0x1f, 0x34, 0x60, 0xbe, 0x9c, 0x4d, 0x3e, 0x85, +0x0b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0x9b, 0x4b, 0x6f, 0x24, 0xa6, +0xa8, 0x52, 0x34, 0x84, 0x0c, 0xec, 0x50, 0xfc, +0x1d, 0x05, 0x20, 0xeb, 0x4d, 0xc9, 0x4f, 0x31, +0x9d, 0xf1, 0xe7, 0xa1, 0x3c, 0x6c, 0xf3, 0xf7, +0xcb, 0xa5, 0x50, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0x40, 0x62, 0x95, +0x09, 0x31, 0x15, 0xab, 0xae, 0xa5, 0x85, 0xcc, +0x32, 0x3d, 0x1f, 0x81, 0xbf, 0xe5, 0xb3, 0xaa, +0xeb, 0xe0, 0xa9, 0xc8, 0xc3, 0x06, 0xb3, 0x04, +0x94, 0xf1, 0xe5, 0xd0, 0xbc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0xae, +0x3b, 0xc3, 0x92, 0x97, 0x5c, 0xb0, 0x40, 0x64, +0x2a, 0x0a, 0x72, 0xf1, 0x09, 0x30, 0x46, 0xd8, +0xbb, 0xcd, 0x49, 0x9a, 0xd3, 0xd4, 0x11, 0x62, +0xca, 0x61, 0xf9, 0x56, 0x27, 0x44, 0x56, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0xaf, 0x43, 0x4d, 0xf2, 0x6a, 0x31, 0x17, +0x22, 0xdb, 0x6f, 0x10, 0xa8, 0x76, 0x25, 0x37, +0xe7, 0x78, 0xaa, 0x57, 0xe2, 0x79, 0x8b, 0xc5, +0xc9, 0x36, 0x50, 0x8f, 0x80, 0x7b, 0xc6, 0x43, +0x2f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0xe2, 0xac, 0x53, 0xdd, 0x67, +0xca, 0x9d, 0xaa, 0x70, 0x35, 0xd3, 0x74, 0x9c, +0x6c, 0xb3, 0x4a, 0xba, 0xdd, 0x37, 0x0d, 0xb4, +0x28, 0xcf, 0x81, 0xa5, 0xcc, 0x21, 0xda, 0x8a, +0x96, 0x01, 0x45, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x61, 0x54, 0x16, +0x45, 0xcd, 0xff, 0xe7, 0x2a, 0x35, 0xc4, 0xff, +0x91, 0x22, 0xb0, 0xd2, 0xf9, 0x1e, 0xad, 0xd2, +0x61, 0x90, 0xf2, 0x42, 0x19, 0x93, 0x5e, 0xf5, +0xdd, 0xbd, 0x76, 0x1e, 0xcb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0x46, +0x56, 0x41, 0x79, 0xd4, 0x34, 0x48, 0x5e, 0xa0, +0xb6, 0xb0, 0x9d, 0xe9, 0xe4, 0x7f, 0x34, 0x3a, +0x22, 0x40, 0xd7, 0xe9, 0xcf, 0x34, 0x90, 0x6d, +0xe2, 0x68, 0x49, 0x2c, 0xc4, 0xe0, 0xfc, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0x63, 0x09, 0xb7, 0x9d, 0x4a, 0x9e, 0xe4, +0xf7, 0xf9, 0x0b, 0x6b, 0x74, 0xaa, 0x82, 0xa2, +0x3b, 0x0e, 0xea, 0x51, 0xe5, 0x27, 0xee, 0x88, +0xe7, 0x8f, 0x34, 0x00, 0x54, 0x56, 0xca, 0x19, +0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0xb3, 0x64, 0xbb, 0xff, 0x22, +0xeb, 0x17, 0x34, 0x42, 0xde, 0xc5, 0x66, 0x21, +0xba, 0x6f, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x9d, 0xd2, 0x08, 0xc2, +0x63, 0xe5, 0xb1, 0x7d, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0xdf, 0x8c, 0x55, +0x96, 0x0f, 0x26, 0x44, 0x38, 0x46, 0x71, 0x4d, +0x2c, 0x73, 0x29, 0xdf, 0x8e, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0x95, +0x16, 0x0b, 0xf4, 0x60, 0xef, 0xdc, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0x0a, +0xc9, 0x9f, 0x48, 0x0d, 0x75, 0x84, 0xcb, 0x95, +0xe0, 0x24, 0x96, 0x02, 0x6e, 0xe0, 0x37, 0x0f, +0x6a, 0x09, 0x42, 0x10, 0xf8, 0xf3, 0xbd, 0xbd, +0x0b, 0xaa, 0x03, 0xa3, 0x45, 0x68, 0x9e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0x75, 0x9d, 0x10, 0x4b, 0x68, 0x76, 0x3c, +0xd6, 0xac, 0x0c, 0x18, 0xc5, 0xfa, 0xd0, 0xcd, +0x08, 0x1b, 0x68, 0x74, 0x89, 0xfd, 0x83, 0x10, +0x35, 0x88, 0x48, 0xe7, 0xd6, 0x52, 0xfc, 0xf8, +0xaa, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0x56, 0xe8, 0x35, 0xa6, 0xd4, +0x42, 0xb0, 0x6a, 0x25, 0xdc, 0xa7, 0x8d, 0x6e, +0x9b, 0x99, 0xbe, 0xdb, 0x3e, 0xac, 0xe6, 0xd5, +0x68, 0x05, 0x78, 0xe4, 0xc8, 0xe6, 0x9e, 0x0b, +0x08, 0x9c, 0x38, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0xe2, 0xbd, 0xb0, +0x43, 0xbe, 0x6a, 0xcf, 0x98, 0x16, 0x09, 0x2f, +0x38, 0xb0, 0x13, 0xdc, 0x06, 0x37, 0xed, 0x4d, +0xbb, 0xa2, 0x6e, 0x1c, 0xcb, 0x38, 0x68, 0x96, +0x36, 0xb8, 0x68, 0xc8, 0x8d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x3d, +0x8c, 0x4a, 0xac, 0xfb, 0x33, 0x76, 0xa6, 0x2b, +0x5a, 0x92, 0xc0, 0x75, 0x8d, 0x29, 0x2d, 0x77, +0xdc, 0x25, 0x20, 0x29, 0x28, 0x32, 0x51, 0xe8, +0xf6, 0x1f, 0xce, 0xe7, 0xdf, 0xc5, 0x6c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0xb2, 0xf8, 0x25, 0x06, 0x22, 0xf6, 0x6a, +0x1c, 0x0d, 0x66, 0x9d, 0xda, 0x62, 0x6c, 0xf1, +0xc9, 0x51, 0x5f, 0x27, 0x80, 0x20, 0xae, 0x91, +0x47, 0xe7, 0x0b, 0xc9, 0xf6, 0x35, 0x56, 0x6f, +0x53, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0x7d, 0x06, 0xda, 0xa5, 0x70, +0xe1, 0x4f, 0xad, 0x7d, 0xbb, 0x5e, 0xc6, 0x47, +0x3a, 0xbf, 0x6d, 0x97, 0x1f, 0x3f, 0x98, 0xed, +0x58, 0x5a, 0x93, 0x5a, 0xec, 0x52, 0x2d, 0xa4, +0xe5, 0x9e, 0xfb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0x15, 0xe5, 0x16, +0xd5, 0x7f, 0x77, 0xb4, 0x28, 0xda, 0x1b, 0xa9, +0x6e, 0xd9, 0x6a, 0xbb, 0x65, 0xa7, 0xe5, 0xf4, +0x8c, 0xba, 0xfe, 0xfd, 0x47, 0x45, 0x57, 0x56, +0xd5, 0xce, 0xd7, 0x5f, 0x24, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0x74, +0xc4, 0x5d, 0xba, 0x75, 0xf7, 0x4a, 0x5b, 0x58, +0xe8, 0x04, 0x78, 0x2b, 0x30, 0x17, 0xa5, 0x53, +0xb3, 0x17, 0x8e, 0x36, 0x29, 0xfb, 0x2d, 0x77, +0x91, 0x75, 0x6b, 0xbb, 0xea, 0x34, 0x9a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0xfd, 0x1c, 0x2f, 0x63, 0xf4, 0x81, 0x0e, +0x20, 0xf6, 0xee, 0xdd, 0x76, 0x42, 0xd6, 0x98, +0x7d, 0x54, 0xb3, 0xec, 0xde, 0x53, 0xe2, 0x62, +0x35, 0xd6, 0xfa, 0x8e, 0x4c, 0x63, 0xd7, 0x36, +0x5e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x28, 0x83, 0xba, 0x20, 0x3d, +0xea, 0x31, 0x9b, 0x81, 0xa7, 0x8a, 0x7b, 0x3d, +0xfa, 0x88, 0x04, 0xc2, 0x9c, 0xdc, 0x59, 0x94, +0x31, 0x97, 0xb7, 0xe1, 0xd7, 0x0a, 0x77, 0xfc, +0xbe, 0x60, 0x68, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0x26, 0x6c, 0xa4, +0x32, 0xc5, 0x35, 0x71, 0xc4, 0x42, 0x0b, 0x8f, +0xd3, 0x23, 0xb4, 0xec, 0xad, 0xc0, 0xad, 0xf5, +0x2e, 0x3a, 0x68, 0x1b, 0x6b, 0xe0, 0xd9, 0xce, +0xc4, 0x68, 0x6f, 0x8b, 0x87, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0x41, +0x57, 0x0a, 0xda, 0xdc, 0xf0, 0x2e, 0xfd, 0x30, +0x76, 0x35, 0x23, 0x08, 0x04, 0x05, 0x6e, 0x99, +0x6d, 0x3b, 0xc2, 0xdb, 0x00, 0x27, 0xc1, 0x68, +0x1b, 0xd6, 0xf2, 0x0a, 0x9e, 0xfa, 0xf1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0xdc, 0xb8, 0x15, 0x60, 0xcf, 0x78, 0xfe, +0x98, 0x35, 0xae, 0xf3, 0xda, 0xd7, 0x70, 0x67, +0x96, 0xdf, 0xc3, 0xad, 0xdb, 0x5f, 0x81, 0xbd, +0xd2, 0x0a, 0x5e, 0xbe, 0xf6, 0x1c, 0x1c, 0xf0, +0x1b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0xf8, 0xa1, 0x4d, 0xaa, 0x70, +0x3d, 0x66, 0x46, 0xa0, 0x51, 0x70, 0xd7, 0xd9, +0xc9, 0x70, 0x07, 0xeb, 0xb1, 0x65, 0x8a, 0x56, +0xe2, 0xd3, 0x34, 0xb5, 0xf9, 0x71, 0xd1, 0x72, +0x4b, 0x00, 0x46, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0xd7, 0xfe, 0x9b, +0x7a, 0x5e, 0x7e, 0x29, 0x49, 0x80, 0x4b, 0xc0, +0xb9, 0x4b, 0xb8, 0x89, 0xcb, 0x04, 0x89, 0xd5, +0x38, 0x04, 0x3c, 0xf0, 0xd9, 0xed, 0x13, 0x20, +0xe7, 0x27, 0x9e, 0xb4, 0x74, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0xad, +0x25, 0xf0, 0x1e, 0x4d, 0x2d, 0x87, 0x5a, 0x84, +0xa2, 0x4b, 0x23, 0xe9, 0x1f, 0x58, 0x00, 0x55, +0xee, 0x8e, 0xc2, 0x42, 0x00, 0xb7, 0xda, 0x22, +0x7b, 0xa5, 0x14, 0x6e, 0x21, 0xcc, 0xc7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0xac, 0x04, 0x01, 0xfd, 0xf4, 0xf1, 0xbc, +0xf8, 0x5c, 0xcd, 0x18, 0x9e, 0xc5, 0x6e, 0x84, +0x34, 0xc3, 0x7f, 0x59, 0xd1, 0xd1, 0x04, 0xdf, +0xe9, 0x2f, 0x7b, 0x97, 0xf4, 0x5d, 0x05, 0x35, +0x9d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0xc7, 0x3e, 0x7b, 0x66, 0xa2, +0x64, 0xdc, 0xdf, 0x34, 0x90, 0x3a, 0x2e, 0x75, +0x0f, 0x31, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x1f, 0x87, 0xa6, 0x3f, +0xbb, 0x01, 0xd1, 0xbb, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0xd3, 0xe3, 0xc8, +0x4c, 0xd7, 0xdc, 0x30, 0x05, 0x90, 0x81, 0xcc, +0x84, 0x0f, 0x24, 0x7f, 0x25, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0xd0, +0xd0, 0xfc, 0x49, 0xae, 0x51, 0x9d, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0x0e, +0xc7, 0x25, 0xbb, 0x7a, 0xf7, 0x7f, 0x8c, 0x87, +0xfe, 0xe4, 0x6b, 0x8b, 0x85, 0xae, 0xa8, 0xbd, +0xec, 0xd8, 0xde, 0x7c, 0x16, 0x79, 0x3e, 0x52, +0xb3, 0x3a, 0x0a, 0xc7, 0x5f, 0xc3, 0xc1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0x92, 0xd7, 0xe6, 0x7f, 0x31, 0xde, 0xac, +0xd2, 0x6e, 0xfc, 0x69, 0x5a, 0xbf, 0x82, 0x94, +0x9e, 0x4c, 0xcb, 0xb1, 0xf7, 0x63, 0xcc, 0x44, +0xcc, 0xfa, 0xf4, 0x2f, 0x67, 0xff, 0x25, 0xde, +0x02, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0x09, 0xa3, 0x0e, 0x55, 0x87, +0xe8, 0x68, 0xee, 0xa5, 0xe6, 0x77, 0x8d, 0x6e, +0x99, 0x9a, 0x0f, 0xdc, 0x04, 0xec, 0xc2, 0xca, +0x4a, 0xa0, 0x1a, 0xbb, 0xe7, 0x09, 0x0a, 0x61, +0x42, 0x38, 0xab, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0x29, 0xea, 0x0a, +0x38, 0xee, 0xa0, 0xbb, 0x8f, 0xb9, 0x9a, 0x78, +0xff, 0x20, 0xe0, 0x74, 0xe8, 0x2d, 0x50, 0x52, +0x4e, 0xa0, 0xb5, 0x2b, 0x15, 0x03, 0x20, 0x3a, +0x7e, 0x49, 0xfe, 0xe1, 0x29, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0xf9, +0x02, 0x26, 0x03, 0xff, 0xf6, 0xae, 0x68, 0xe5, +0x55, 0x5a, 0x57, 0xa8, 0xd2, 0xff, 0x53, 0xc9, +0xa0, 0x83, 0x81, 0xe7, 0x8f, 0x9d, 0x5e, 0x1e, +0xb7, 0x8b, 0x41, 0x9f, 0x39, 0xeb, 0xc3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x84, 0xb4, 0x1e, 0x7e, 0x93, 0xa9, 0x38, +0x9a, 0x19, 0xa0, 0xfe, 0xea, 0xf3, 0xf6, 0x4d, +0xb5, 0xbf, 0xc7, 0xf9, 0xa9, 0x62, 0x31, 0x59, +0x0c, 0x16, 0x97, 0x39, 0xcb, 0x84, 0x3f, 0xc8, +0xaf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0x36, 0x00, 0x14, 0x2e, 0x41, +0x94, 0x94, 0x79, 0xa9, 0x1b, 0xa5, 0x87, 0xae, +0xb7, 0x1a, 0xf6, 0xc1, 0xf4, 0x59, 0x4c, 0x7e, +0xf5, 0xb8, 0x79, 0xa2, 0xb7, 0xb8, 0x5b, 0xed, +0x9b, 0x75, 0x15, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0xf8, 0x87, 0xf6, +0xc8, 0x3b, 0xa6, 0xef, 0x53, 0xf4, 0x80, 0x31, +0x15, 0x7c, 0x82, 0x54, 0xe2, 0x09, 0x2d, 0xdd, +0x92, 0xba, 0x81, 0x8e, 0xc8, 0xc7, 0x40, 0x39, +0x5c, 0xf8, 0x1b, 0xe3, 0x18, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0x43, +0x07, 0xe6, 0x7d, 0x08, 0x6c, 0xe7, 0x21, 0x98, +0x29, 0x8f, 0x24, 0xc3, 0x9a, 0x66, 0xd3, 0x11, +0x9c, 0x0a, 0xed, 0x9a, 0xf9, 0x97, 0x0f, 0xfc, +0x93, 0x67, 0x6b, 0x3e, 0x98, 0xe3, 0x77, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0x3c, 0x8e, 0x1a, 0xf9, 0xb9, 0x2e, 0x4b, +0xda, 0x08, 0x87, 0x7b, 0xb2, 0xe1, 0xb9, 0xb0, +0x09, 0x92, 0xe5, 0x74, 0xcb, 0x91, 0x1a, 0xa9, +0x79, 0x22, 0x07, 0x10, 0x9e, 0xe0, 0x3d, 0xb5, +0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0xc6, 0x7f, 0xd9, 0x75, 0x76, +0xa5, 0x54, 0x16, 0x14, 0xcb, 0x8b, 0x91, 0xc9, +0xfd, 0x19, 0x8b, 0x9a, 0xd9, 0x26, 0x3c, 0x4e, +0xf2, 0x6c, 0x8c, 0x01, 0xac, 0xc8, 0xc0, 0x72, +0xe1, 0xa0, 0x36, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0xd2, 0xe1, 0x45, +0x90, 0x93, 0xe1, 0x89, 0xab, 0x35, 0x6c, 0x69, +0x6e, 0xd0, 0xb8, 0x90, 0x3a, 0xd1, 0xe0, 0x24, +0xf9, 0x6c, 0xf3, 0x3c, 0xb3, 0x0f, 0x36, 0x05, +0xd5, 0xb0, 0xbb, 0xc7, 0xb6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0x27, +0x9a, 0xb6, 0x24, 0x41, 0x79, 0x0c, 0x80, 0x7f, +0x5e, 0xa6, 0x3b, 0x41, 0x09, 0xe5, 0x3d, 0x65, +0xa2, 0x48, 0xbd, 0x8b, 0x83, 0xdd, 0xad, 0x73, +0xfc, 0x4b, 0xf3, 0x9f, 0xcd, 0x02, 0x7a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0xa8, 0xda, 0x5e, 0x2a, 0xf6, 0xd0, 0xaf, +0xe3, 0x12, 0x70, 0xbf, 0x53, 0x1a, 0xc5, 0x34, +0x87, 0xc6, 0x09, 0x1f, 0x79, 0x5a, 0xc6, 0xf2, +0x8b, 0x73, 0x9d, 0x1a, 0x7b, 0xe8, 0xde, 0xbf, +0x18, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0x8b, 0x49, 0x3c, 0x9e, 0x4c, +0x62, 0x01, 0x81, 0xde, 0x9f, 0x4a, 0x15, 0x0a, +0x24, 0xb5, 0xbc, 0x90, 0x70, 0xf1, 0xe4, 0x41, +0xd2, 0xba, 0x10, 0xde, 0xcf, 0xb1, 0xcc, 0xb4, +0x67, 0x28, 0x22, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0x7d, 0x8e, 0x4a, +0x2e, 0x86, 0x9c, 0x5d, 0xfd, 0x99, 0xce, 0xff, +0x03, 0x08, 0xfb, 0x73, 0xfe, 0xef, 0xb3, 0x8e, +0x17, 0x66, 0x8b, 0x8a, 0x7b, 0x72, 0x18, 0x6e, +0x34, 0x77, 0x53, 0x00, 0x2a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0xb2, +0xee, 0x4b, 0xbc, 0xa8, 0x0e, 0xba, 0x58, 0xc6, +0x67, 0x72, 0x6a, 0x72, 0x33, 0x0d, 0x13, 0x5a, +0xdb, 0x2e, 0xd1, 0xa4, 0x3b, 0xd4, 0x8b, 0x0f, +0x2a, 0xfc, 0x50, 0x9b, 0x22, 0x2c, 0x25, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0x2c, 0x1e, 0x67, 0xbb, 0x1e, 0xff, 0xd4, +0x16, 0x71, 0xa1, 0x55, 0x44, 0xc5, 0xed, 0x64, +0x2c, 0x08, 0x19, 0xd2, 0x53, 0x94, 0x29, 0xa8, +0xcc, 0xe2, 0x77, 0x3f, 0x8e, 0xbe, 0x46, 0x1d, +0x0c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0x76, 0x75, 0x5f, 0xa8, 0xa3, +0xba, 0x42, 0x6e, 0x6f, 0xce, 0xa5, 0x9c, 0x2e, +0x8d, 0x44, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xd7, 0x39, 0x4a, 0x49, +0x8d, 0x7d, 0x18, 0xa5, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x0e, 0xbf, 0xa6, +0x3a, 0x10, 0x1c, 0xb1, 0x7d, 0x56, 0xf8, 0x47, +0x8a, 0x9a, 0xa1, 0x05, 0x82, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x99, +0x7b, 0x7f, 0x7d, 0x2f, 0x24, 0xe0, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x60, +0xeb, 0x9b, 0x50, 0xf7, 0x12, 0x59, 0x26, 0x68, +0x84, 0xa8, 0xe6, 0xaa, 0xd9, 0xd6, 0x76, 0xa2, +0xbe, 0x50, 0x7a, 0x7b, 0x4f, 0x64, 0x03, 0xdd, +0xea, 0x10, 0x28, 0x11, 0xfb, 0xac, 0x1f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0xe3, 0x01, 0x2e, 0xe4, 0xb2, 0xe7, 0x1e, +0x7a, 0x02, 0x03, 0x25, 0x70, 0x0e, 0x5c, 0x21, +0x32, 0x39, 0x51, 0xf0, 0x58, 0x96, 0xf3, 0xe0, +0xcd, 0xd3, 0xcf, 0xfc, 0x26, 0x62, 0xaf, 0x1f, +0x0a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0xf8, 0x9c, 0xbc, 0x77, 0x9f, +0x2d, 0x6d, 0xec, 0xfe, 0xe2, 0x80, 0x22, 0xf5, +0xf7, 0x9e, 0x67, 0x4a, 0x1e, 0x81, 0x1b, 0xee, +0xf5, 0xc2, 0x72, 0x14, 0xc9, 0x42, 0x47, 0x41, +0x79, 0x8d, 0x46, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0x75, 0x9a, 0xea, +0x2a, 0xae, 0x20, 0xfc, 0x48, 0x44, 0xb4, 0x90, +0xe9, 0x1f, 0x1d, 0x50, 0xcf, 0x83, 0x35, 0xef, +0x88, 0x94, 0xbf, 0xd4, 0x11, 0xc5, 0x60, 0x40, +0xf3, 0xca, 0xc7, 0xf2, 0xb9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0xb7, +0x8e, 0x7c, 0xde, 0xd3, 0x94, 0x5c, 0x05, 0xf2, +0xb8, 0x6e, 0x60, 0xc9, 0x20, 0x17, 0x5f, 0xd1, +0x54, 0x73, 0xd4, 0x35, 0x1a, 0x84, 0x4d, 0xd6, +0xdc, 0xb9, 0xb8, 0x29, 0xb7, 0x3c, 0x23, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0xda, 0x8c, 0xb8, 0x4f, 0x8c, 0xe8, 0x7f, +0xe2, 0x51, 0x14, 0x5a, 0x96, 0xba, 0xa6, 0xf5, +0x2a, 0xc1, 0x60, 0xd4, 0x80, 0x41, 0x9a, 0xcb, +0xd6, 0xf2, 0x11, 0x4d, 0x85, 0x0b, 0x83, 0x7c, +0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0xa5, 0x22, 0x6c, 0x5c, 0x78, +0x0a, 0x52, 0x0f, 0x86, 0x98, 0x4d, 0xf4, 0xd6, +0xef, 0xfd, 0x8e, 0x50, 0xa8, 0x22, 0x78, 0x8c, +0x7a, 0x94, 0x1d, 0x91, 0x9c, 0xd9, 0x26, 0x7e, +0xf9, 0x65, 0x5a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0x82, 0x2b, 0x17, +0x4d, 0x26, 0xda, 0x6d, 0x64, 0x66, 0x36, 0xe4, +0x7f, 0xf3, 0x09, 0x11, 0x36, 0x9c, 0x0d, 0xfa, +0x60, 0x23, 0xd8, 0x6f, 0x7b, 0x98, 0xb3, 0x1c, +0xd8, 0x3d, 0x4f, 0x2e, 0x1c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0x3d, +0xdc, 0xe7, 0x2b, 0x95, 0x40, 0xd2, 0xb9, 0xa9, +0x13, 0x66, 0x66, 0xb8, 0x6f, 0x38, 0x64, 0xf8, +0x7f, 0x07, 0x71, 0x25, 0xf3, 0xa4, 0xc3, 0x4d, +0x85, 0x06, 0x3f, 0x68, 0x44, 0x8b, 0x98, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0x20, 0xec, 0xac, 0x28, 0x17, 0xfc, 0x05, +0xf2, 0x2b, 0x94, 0x91, 0x94, 0xc3, 0xc6, 0x22, +0x89, 0x82, 0x08, 0xe5, 0xd0, 0x89, 0x52, 0xd5, +0xb8, 0x2f, 0x8d, 0x10, 0x3d, 0x4a, 0x32, 0xf7, +0xcd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0x6d, 0x96, 0x84, 0x44, 0xd9, +0x67, 0x2f, 0x24, 0x0b, 0x26, 0x2c, 0xee, 0x46, +0x30, 0xd7, 0x5c, 0xda, 0x51, 0x70, 0xc6, 0xb5, +0x9b, 0x20, 0x76, 0xe1, 0xf6, 0x80, 0x1d, 0x56, +0x1c, 0xeb, 0x53, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0x67, 0xd0, 0xb1, +0xa0, 0x15, 0x3c, 0x4d, 0xda, 0xd7, 0x75, 0xbe, +0x40, 0x2a, 0x4c, 0x25, 0xa6, 0x90, 0x3e, 0xa6, +0x39, 0xee, 0x4a, 0x8f, 0x6e, 0x78, 0x8d, 0x38, +0x48, 0xa6, 0x5e, 0xe0, 0xe7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0x61, +0x42, 0x48, 0x56, 0xc5, 0x97, 0x63, 0x15, 0x44, +0x1a, 0xf9, 0x57, 0xdb, 0xf9, 0x80, 0x7a, 0x7c, +0xa6, 0xeb, 0x1c, 0x22, 0xaf, 0x6c, 0xc1, 0x47, +0x3d, 0x05, 0x60, 0x0b, 0x3b, 0xe6, 0xcb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x5d, 0x9b, 0x98, 0x8f, 0x8b, 0x8a, 0xa4, +0xa1, 0x4c, 0xd4, 0x9a, 0x13, 0xbf, 0x46, 0xe6, +0xed, 0x55, 0x59, 0x84, 0x38, 0x47, 0x51, 0xbb, +0xb2, 0xa9, 0xd9, 0x06, 0xaa, 0xa9, 0x4e, 0x02, +0xb5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0xb0, 0xec, 0x04, 0x67, 0x15, +0x0d, 0x8e, 0xbe, 0xab, 0x30, 0x20, 0x3b, 0x5e, +0xc7, 0x62, 0xff, 0xe4, 0x8b, 0x7d, 0xc0, 0xc2, +0x9d, 0x59, 0x36, 0xf6, 0xf2, 0x09, 0xbd, 0x1f, +0xd2, 0xc5, 0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0x25, 0x54, 0x35, +0x24, 0x87, 0x15, 0x90, 0x74, 0x36, 0xbf, 0xcb, +0xeb, 0x7f, 0x59, 0x3d, 0x9c, 0x2c, 0xa6, 0xf2, +0xc6, 0xb0, 0xbc, 0x9d, 0x12, 0xcf, 0x1a, 0x4e, +0x5d, 0x1d, 0x33, 0xeb, 0x6d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0x0b, +0xdd, 0xb2, 0x8b, 0xfd, 0x37, 0x53, 0xf3, 0x76, +0x7c, 0x8d, 0x15, 0xeb, 0xb0, 0x34, 0xab, 0xe8, +0x40, 0x71, 0x45, 0xd5, 0xd1, 0xc3, 0x6c, 0xc2, +0x50, 0xd9, 0xec, 0x3e, 0xd2, 0xc6, 0xe6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0x2e, 0x75, 0x9d, 0x60, 0x8e, 0xae, 0x01, +0xeb, 0xc2, 0xb1, 0xca, 0xa2, 0x83, 0xa5, 0xc1, +0x8e, 0xe5, 0x60, 0x7b, 0x14, 0x37, 0x70, 0xf2, +0xa8, 0x33, 0xed, 0xb4, 0x1e, 0x3c, 0x88, 0x04, +0x70, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0x6b, 0x55, 0xe0, 0xa0, 0x66, +0x2a, 0x9d, 0x65, 0x60, 0x96, 0x0d, 0x7e, 0x21, +0x04, 0xa4, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x04, 0xd9, 0x7d, 0x4a, +0x54, 0x8c, 0x59, 0x80, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0xba, 0xb8, 0xff, +0xdf, 0xc4, 0xd1, 0x7e, 0xe2, 0xd2, 0x2b, 0xbc, +0x12, 0x94, 0x99, 0xb5, 0xc5, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x61, +0x4c, 0x17, 0x14, 0x52, 0xcd, 0xfa, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0xee, +0xf4, 0xc4, 0x48, 0xd2, 0xf8, 0xca, 0x2d, 0x66, +0x7a, 0x0b, 0xdf, 0x61, 0x8a, 0x25, 0x5d, 0x9f, +0x9c, 0x60, 0x90, 0x99, 0x87, 0x63, 0xcf, 0x34, +0x84, 0x4a, 0x03, 0xa8, 0x23, 0x0a, 0x20, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0x28, 0x00, 0xf7, 0x80, 0xf5, 0x98, 0xc4, +0x6d, 0x2e, 0x38, 0x74, 0x37, 0x58, 0xe3, 0x5b, +0xd5, 0x48, 0x66, 0x11, 0x80, 0x9c, 0x69, 0x6a, +0xb4, 0x9f, 0x34, 0x6c, 0xf4, 0x34, 0x9a, 0x35, +0x68, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0x0a, 0x9f, 0x0f, 0x78, 0x33, +0xd3, 0x38, 0x66, 0x48, 0xe5, 0xe9, 0xbb, 0xda, +0xb5, 0xba, 0x79, 0x14, 0xa3, 0x12, 0x3b, 0x16, +0x91, 0xda, 0xd7, 0x78, 0xda, 0xd2, 0xb0, 0xdf, +0xf8, 0xce, 0xad, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x7d, 0xf6, 0xb2, +0x98, 0x4d, 0x86, 0x76, 0xf6, 0x2a, 0x7f, 0xeb, +0xab, 0x34, 0x00, 0x50, 0x2c, 0x0f, 0xa6, 0x62, +0x66, 0xd0, 0xe7, 0xce, 0x92, 0x68, 0x22, 0xaa, +0x27, 0xe9, 0xb3, 0xa6, 0x84, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0xa7, +0x62, 0xce, 0x3b, 0x86, 0xa7, 0x5b, 0xc1, 0x15, +0x00, 0x45, 0x24, 0xd4, 0xcd, 0x41, 0x29, 0x97, +0xfb, 0x19, 0x7f, 0x49, 0xf8, 0x69, 0x59, 0x05, +0x6f, 0x25, 0xba, 0x58, 0x53, 0xac, 0xec, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0x12, 0x54, 0x65, 0xe5, 0x94, 0xa6, 0xf8, +0xd7, 0xc6, 0x6a, 0x4e, 0x38, 0x07, 0x72, 0xce, +0xd0, 0x38, 0xe5, 0xd4, 0xd6, 0x28, 0x34, 0x9b, +0xc9, 0x90, 0xf3, 0x49, 0x5b, 0x71, 0x72, 0xca, +0xcb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0x71, 0x1b, 0x56, 0x7a, 0x3e, +0x0f, 0x24, 0x51, 0x77, 0x2c, 0x3b, 0x88, 0x11, +0x17, 0x9b, 0x75, 0xcf, 0xe1, 0x75, 0x78, 0x9e, +0x4e, 0x15, 0xbc, 0xaa, 0x22, 0x62, 0x9f, 0x1e, +0x77, 0xe1, 0xee, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0x00, 0xb5, 0x0a, +0x3e, 0x3d, 0x1c, 0xff, 0x03, 0x47, 0x25, 0xec, +0xff, 0x86, 0xc6, 0x8c, 0xe6, 0x52, 0xa1, 0xe8, +0x40, 0x33, 0xe1, 0x9d, 0xf6, 0xd0, 0x74, 0x98, +0x9d, 0xb4, 0xc2, 0x11, 0xea, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0xba, +0x89, 0x06, 0xc3, 0xea, 0x9d, 0x56, 0xc9, 0xba, +0x74, 0x50, 0x66, 0xd2, 0x0a, 0x22, 0xad, 0x28, +0x33, 0x47, 0x81, 0x48, 0x03, 0xb6, 0x24, 0x2a, +0xcc, 0x71, 0x1a, 0xd2, 0x9d, 0xe5, 0x8a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0xfc, 0x52, 0x04, 0xd9, 0xdb, 0x3b, 0xa7, +0xc4, 0x42, 0x84, 0x36, 0xc3, 0x67, 0x1c, 0xf9, +0x5a, 0x41, 0x6c, 0xf6, 0xd8, 0x1b, 0xbd, 0xcc, +0x91, 0x24, 0xd5, 0xc5, 0x96, 0xce, 0x89, 0x99, +0x02, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x41, 0x29, 0xbf, 0x51, 0x82, +0x4f, 0x40, 0x03, 0x31, 0x82, 0x1e, 0x07, 0x16, +0x1e, 0x07, 0x48, 0x07, 0x44, 0x48, 0xf6, 0x9d, +0x3a, 0x58, 0x06, 0x9d, 0x7a, 0x5b, 0xfa, 0xc8, +0xd6, 0x39, 0xf9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0x0b, 0xac, 0x71, +0xaa, 0x03, 0x4d, 0x8b, 0x0a, 0x0a, 0x43, 0xac, +0xae, 0xfe, 0x92, 0x2f, 0x08, 0x1f, 0xec, 0xf2, +0xb2, 0xe5, 0xad, 0x1b, 0x80, 0xa2, 0x8a, 0xa9, +0x09, 0x20, 0xae, 0x9a, 0xfd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0xb2, +0x29, 0x96, 0xb8, 0xee, 0x27, 0x68, 0x6e, 0xef, +0x95, 0x29, 0x60, 0x40, 0xb6, 0x24, 0x93, 0x67, +0x07, 0x37, 0x21, 0xe3, 0x4a, 0x75, 0xe1, 0xcb, +0x7d, 0x65, 0xae, 0xb6, 0x25, 0xb1, 0x60, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0x7b, 0x01, 0x50, 0x7d, 0x1f, 0xbe, 0x4a, +0xd5, 0x3b, 0xe6, 0x1a, 0x7a, 0x4c, 0xb4, 0x25, +0xc6, 0xc1, 0x65, 0xe1, 0x1f, 0x36, 0xcc, 0xc4, +0x1c, 0xba, 0x56, 0xc0, 0xda, 0xa8, 0xc8, 0x6e, +0xeb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0x0e, 0x81, 0x90, 0xdb, 0x8f, +0x77, 0xef, 0x67, 0x64, 0xc5, 0x7f, 0xea, 0xd2, +0x5a, 0x8f, 0x86, 0x86, 0xec, 0x4e, 0xd8, 0xc1, +0x4f, 0x07, 0x95, 0x85, 0xc8, 0x4c, 0x5e, 0x4f, +0xbc, 0x45, 0x60, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0x19, 0x9c, 0xd1, +0x2e, 0x8e, 0x89, 0xda, 0x23, 0xc7, 0x3d, 0x7d, +0x64, 0x72, 0xff, 0xb2, 0x55, 0x5a, 0x69, 0xee, +0x68, 0x83, 0xdf, 0x5a, 0xa5, 0x8b, 0x43, 0xda, +0x6e, 0xec, 0xda, 0xb2, 0xfd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0xc6, +0x01, 0x93, 0xdf, 0x19, 0xdf, 0x89, 0x88, 0xf0, +0xd9, 0xa3, 0x04, 0x42, 0x8f, 0x44, 0x42, 0xaa, +0xdb, 0x05, 0x7e, 0x61, 0x1a, 0x96, 0x59, 0x99, +0x66, 0x6c, 0xd7, 0xec, 0x19, 0x59, 0x98, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x68, 0x50, 0x05, 0x00, 0x57, 0xdf, 0x65, +0x1a, 0xff, 0x60, 0xe8, 0x44, 0x96, 0xe4, 0xc5, +0x42, 0x51, 0xc6, 0x6e, 0xba, 0xf2, 0xd7, 0x66, +0xb6, 0xab, 0xf4, 0xd4, 0x49, 0x7e, 0x33, 0x6c, +0x06, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0x78, 0xeb, 0x8e, 0x02, 0x74, +0x30, 0x5b, 0xfc, 0xc3, 0x5b, 0x42, 0xfb, 0x3c, +0xc1, 0xfc, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x2c, 0x67, 0x5f, 0x50, +0x47, 0x6b, 0x3a, 0xd6, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0x4c, 0x9e, 0x54, +0x25, 0x7b, 0xb9, 0x30, 0xc4, 0x63, 0x30, 0xe4, +0x3c, 0xf9, 0x56, 0x31, 0xfc, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa4, 0xd7, +0xfc, 0x9f, 0xe7, 0x20, 0x98, 0xf2, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0x80, +0xab, 0xc2, 0x94, 0x27, 0x9d, 0x85, 0x54, 0xfe, +0xf3, 0x8e, 0x2d, 0x94, 0xca, 0x6e, 0x3d, 0xbf, +0x15, 0xa5, 0xc1, 0x68, 0x23, 0x30, 0xa9, 0x2c, +0x21, 0x33, 0x55, 0xab, 0xad, 0xf3, 0x88, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0x9a, 0x9b, 0xc1, 0x76, 0x86, 0x3d, 0x88, +0x31, 0x88, 0x73, 0x16, 0xc1, 0xcb, 0xca, 0x7d, +0x21, 0xd6, 0x61, 0xec, 0xed, 0xd9, 0xf8, 0x5e, +0x59, 0x80, 0x66, 0xc5, 0x3d, 0x66, 0x82, 0x75, +0x5a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0x8b, 0x39, 0x90, 0x1b, 0xb5, +0x40, 0x93, 0x9c, 0xd2, 0xa7, 0xd1, 0x31, 0x35, +0x83, 0x92, 0x0b, 0x61, 0x35, 0x11, 0xb0, 0xbe, +0xbe, 0x86, 0xd2, 0x36, 0x40, 0xa3, 0x35, 0x2d, +0x97, 0xb1, 0xdd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0xa7, 0x24, 0x6b, +0xa0, 0xa1, 0xb7, 0xb4, 0x09, 0x78, 0xc3, 0x77, +0xf8, 0x68, 0x7b, 0x32, 0x3c, 0xe8, 0xc9, 0x3a, +0x6f, 0xd5, 0xa7, 0x80, 0x85, 0x83, 0x0a, 0xa3, +0xee, 0x29, 0x79, 0xbd, 0x3f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0xae, +0xe0, 0x92, 0xa4, 0x9e, 0x5e, 0x76, 0x50, 0xec, +0xb0, 0xe2, 0x97, 0xcd, 0xaf, 0x10, 0x09, 0xcf, +0xf1, 0x99, 0x37, 0x07, 0x3c, 0x52, 0x76, 0x67, +0x22, 0x12, 0x9d, 0x5a, 0x99, 0x5d, 0xff, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0xf4, 0xc7, 0xfa, 0xb9, 0x44, 0xad, 0x18, +0x84, 0xba, 0x1a, 0x5e, 0x7c, 0xc6, 0xa8, 0x1f, +0x59, 0xe0, 0xe5, 0x46, 0x21, 0x47, 0x22, 0x42, +0x31, 0x31, 0xb0, 0x1d, 0xb9, 0x21, 0x5a, 0x07, +0xbe, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0xae, 0x3e, 0x11, 0x26, 0x32, +0xb1, 0x28, 0x98, 0x1f, 0x2b, 0x25, 0xd8, 0xa8, +0xb9, 0x80, 0xbd, 0x5b, 0x53, 0xa5, 0xed, 0xf6, +0x3c, 0x5a, 0x0f, 0x77, 0x9a, 0x7e, 0x3b, 0x5c, +0x3e, 0xb5, 0xb6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0x78, 0xe3, 0xc6, +0x62, 0xed, 0xaf, 0x72, 0xb0, 0x58, 0x7d, 0xe6, +0x64, 0x82, 0x7d, 0xa6, 0x42, 0x74, 0x80, 0xd7, +0x3c, 0x61, 0xd3, 0x7a, 0xaa, 0x22, 0x32, 0xa7, +0x70, 0x4b, 0x13, 0x0c, 0x6e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0xa1, +0x15, 0xa4, 0xee, 0x6b, 0x98, 0x42, 0xe1, 0xad, +0xb3, 0x8e, 0xa9, 0xa4, 0xc4, 0x05, 0x82, 0xa5, +0xcb, 0x51, 0x77, 0xa3, 0xb2, 0xad, 0x7c, 0xec, +0xf1, 0xe0, 0x5e, 0x6e, 0x43, 0xeb, 0xc7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0xd7, 0x55, 0x6f, 0xcf, 0x76, 0x05, 0xf3, +0xc9, 0xd0, 0x41, 0xfb, 0x40, 0xec, 0x7b, 0xb9, +0x8d, 0x90, 0x70, 0xa5, 0x9c, 0x91, 0xc7, 0xb9, +0x5a, 0x85, 0x41, 0xde, 0xc3, 0xe6, 0x5e, 0x16, +0xb3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0x78, 0x7b, 0xd0, 0x66, 0xe3, +0xb4, 0x32, 0x12, 0x94, 0xef, 0x15, 0x50, 0x3a, +0xda, 0x62, 0x90, 0x46, 0x14, 0xea, 0x65, 0x3b, +0x92, 0xcf, 0xaf, 0x1c, 0xcb, 0x69, 0xed, 0x39, +0x87, 0x38, 0xa6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0xda, 0xed, 0xa1, +0x24, 0x1f, 0xce, 0x67, 0xbd, 0xb2, 0xf4, 0x8d, +0xd0, 0x0b, 0x18, 0xfc, 0x34, 0x3f, 0xdc, 0xb5, +0xf1, 0x35, 0xde, 0x84, 0x49, 0xb1, 0x63, 0x6a, +0x96, 0x78, 0xb1, 0xd8, 0xb2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0x44, +0x94, 0x12, 0x07, 0xcb, 0xcd, 0x08, 0x6b, 0xee, +0xc0, 0xc0, 0xa6, 0x5c, 0x52, 0x0b, 0x20, 0x91, +0x63, 0xf4, 0x7e, 0x02, 0xf5, 0xdb, 0x29, 0x3f, +0xbb, 0x1d, 0x95, 0x28, 0x47, 0x0c, 0xd8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0xea, 0x64, 0xd0, 0xa7, 0xee, 0xb9, 0x08, +0xdb, 0xac, 0x7b, 0x73, 0x34, 0x67, 0x84, 0xca, +0x23, 0x3d, 0x39, 0xaf, 0x20, 0x8e, 0x71, 0x54, +0x9b, 0x25, 0x75, 0x17, 0x0e, 0x7a, 0xa5, 0x90, +0x8c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0x60, 0x55, 0x73, 0xf1, 0xe0, +0x57, 0x9f, 0x4a, 0x2c, 0x9e, 0x2f, 0x79, 0xa9, +0x08, 0xd7, 0xae, 0xc0, 0x97, 0xce, 0x3c, 0xb5, +0x81, 0x7f, 0x39, 0xee, 0x67, 0xab, 0xb8, 0xd9, +0xad, 0xe7, 0xac, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0x20, 0x92, 0xd6, +0x23, 0xe0, 0x86, 0xef, 0xad, 0xcd, 0x81, 0xe7, +0x66, 0x05, 0x66, 0x5c, 0xd0, 0x45, 0x3a, 0xb8, +0x5b, 0x33, 0xd9, 0x30, 0x16, 0xa5, 0x82, 0xf1, +0x10, 0xb9, 0x1d, 0x82, 0x15, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0xd9, +0xc7, 0x77, 0x34, 0xca, 0xe5, 0x2c, 0x78, 0x27, +0xe5, 0x08, 0x2c, 0xe2, 0x93, 0xf7, 0xff, 0x45, +0x4e, 0x9e, 0x34, 0x0b, 0x91, 0x5f, 0x74, 0xfb, +0x0f, 0xab, 0x8c, 0x87, 0xab, 0xc7, 0x36, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0xbc, 0xd1, 0x0e, 0x5a, 0xda, 0x3f, 0x5a, +0x94, 0xa7, 0xd8, 0xbd, 0xb1, 0xda, 0xb1, 0xd9, +0xe9, 0x46, 0x9c, 0x2d, 0xe8, 0x29, 0x6c, 0xd6, +0xcd, 0x8c, 0x41, 0x0e, 0x57, 0x70, 0xf5, 0xe8, +0xd4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0x7e, 0x12, 0x3e, 0xe2, 0xdb, +0xcd, 0x5a, 0x00, 0x7c, 0xdf, 0x4a, 0xa9, 0xe7, +0x30, 0x76, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xe9, 0x9f, 0x5f, 0x45, +0xae, 0x51, 0x26, 0xef, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0x18, 0x6d, 0x76, +0x2d, 0x2f, 0xcf, 0x12, 0x7f, 0x97, 0x72, 0x89, +0x5a, 0x6d, 0x2a, 0x07, 0xd2, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x5e, +0x2d, 0xda, 0xdc, 0x9e, 0xc9, 0x71, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0x10, +0x6b, 0x04, 0x03, 0xce, 0xf4, 0xcf, 0x19, 0xea, +0xd4, 0x65, 0x64, 0x6a, 0x86, 0xe4, 0x0a, 0x7e, +0xbd, 0x31, 0x74, 0x4d, 0x54, 0xda, 0x27, 0x47, +0x48, 0x62, 0xb2, 0xf7, 0x18, 0x6c, 0x22, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0x03, 0x2e, 0x1b, 0x50, 0xb9, 0x58, 0x3e, +0x01, 0x9b, 0xac, 0xdb, 0x74, 0x8c, 0x9d, 0x50, +0x1c, 0x85, 0x19, 0x0c, 0xab, 0xe7, 0x14, 0xb2, +0x49, 0xcb, 0x45, 0x74, 0x4f, 0x61, 0x5c, 0xcc, +0x9c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0xef, 0xe0, 0x98, 0xc9, 0x5f, +0x0e, 0x87, 0x7f, 0xa4, 0x12, 0xb8, 0x48, 0xeb, +0x98, 0x17, 0xb0, 0x90, 0x2a, 0xa3, 0x01, 0x92, +0x75, 0xed, 0x41, 0xb6, 0x6b, 0x94, 0x97, 0x86, +0xf6, 0x50, 0x2f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0xb0, 0xa4, 0x61, +0xcd, 0xe6, 0x42, 0xe6, 0x55, 0x6e, 0x8d, 0x88, +0x09, 0x3a, 0x39, 0x96, 0xf3, 0x16, 0xec, 0xa0, +0x31, 0x85, 0x9f, 0x6f, 0x18, 0x12, 0x6b, 0xd5, +0xb3, 0x65, 0xba, 0xf0, 0x0f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0xb8, +0x5e, 0xba, 0xfb, 0x08, 0x21, 0xa5, 0x88, 0x70, +0x6e, 0xb1, 0x90, 0xab, 0x6d, 0x54, 0x68, 0x12, +0xd0, 0xdb, 0x38, 0x31, 0x16, 0x1b, 0xc1, 0xf4, +0x16, 0xc2, 0x31, 0x8b, 0x4b, 0x29, 0xd1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0x1c, 0x70, 0x17, 0x49, 0x2e, 0x7b, 0xa5, +0x11, 0x1b, 0x51, 0xa4, 0x15, 0x75, 0xd0, 0x3e, +0xc2, 0x51, 0x59, 0xcc, 0x93, 0x5e, 0x70, 0x5c, +0x10, 0x90, 0xf0, 0x58, 0x75, 0xab, 0xd2, 0xfa, +0x91, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0x16, 0xae, 0x9f, 0xa1, 0xa1, +0x3d, 0x33, 0x98, 0x49, 0x7d, 0x19, 0xd4, 0x4c, +0x32, 0xf8, 0xf4, 0xf2, 0x55, 0xb5, 0xda, 0x6a, +0x86, 0xe5, 0x66, 0xa9, 0x23, 0x8a, 0x86, 0x94, +0x68, 0x0d, 0xd0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0xd8, 0x93, 0xb5, +0xd6, 0xe4, 0x4a, 0xc5, 0x75, 0x8e, 0xb8, 0x79, +0xcb, 0x75, 0x11, 0x7c, 0x1e, 0x63, 0xcd, 0xdc, +0x31, 0x89, 0x0f, 0x8e, 0x20, 0x61, 0xb8, 0xdf, +0xb0, 0xf4, 0x11, 0x77, 0x73, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0x75, +0xf1, 0xf7, 0x1a, 0xc9, 0xf2, 0x07, 0x22, 0x73, +0x83, 0x5b, 0x7b, 0x66, 0x22, 0xf5, 0xd2, 0x91, +0x85, 0x32, 0xff, 0xbe, 0x90, 0xc4, 0x3e, 0x2e, +0x85, 0x79, 0x2d, 0xfe, 0xae, 0xe6, 0x8e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0xf5, 0x59, 0x24, 0x28, 0x36, 0x94, 0x27, +0x12, 0xa6, 0xee, 0x5d, 0x59, 0xe2, 0xa1, 0x08, +0x0c, 0xbd, 0x65, 0x53, 0x88, 0x78, 0xe7, 0x30, +0x9b, 0x4d, 0xb4, 0x68, 0x09, 0x13, 0xe0, 0x1b, +0x5e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0x97, 0xfa, 0x76, 0x34, 0xac, +0x06, 0x25, 0xac, 0xc0, 0x6e, 0xb5, 0xec, 0xac, +0x69, 0x43, 0x4d, 0x39, 0x81, 0x2f, 0x33, 0x3b, +0x9e, 0xfd, 0xef, 0x8e, 0x7f, 0xdd, 0x90, 0xed, +0xb8, 0x82, 0x3d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0x9a, 0x16, 0xee, +0xfa, 0x93, 0x07, 0x74, 0x04, 0x9a, 0x62, 0x08, +0x6b, 0xd4, 0x2f, 0x35, 0xf4, 0xa7, 0xe7, 0xf8, +0xcf, 0x61, 0x85, 0xea, 0xac, 0x85, 0xdf, 0x0f, +0x22, 0x83, 0x9e, 0x99, 0x34, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0xc3, +0xbb, 0x77, 0x40, 0x0d, 0x0f, 0x66, 0x11, 0x2f, +0x23, 0xf9, 0x67, 0xc8, 0x54, 0x9b, 0x69, 0x6a, +0x63, 0x15, 0x7f, 0x2d, 0x89, 0xd8, 0x0c, 0xc5, +0x5a, 0xbd, 0x19, 0x89, 0x1b, 0x79, 0x78, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0x4e, 0x28, 0x5e, 0xac, 0xe9, 0x7a, 0x6b, +0xde, 0x6b, 0xa4, 0x53, 0xc7, 0x3a, 0x6a, 0xac, +0x3a, 0x72, 0xb0, 0x03, 0xeb, 0x70, 0x01, 0xd9, +0xba, 0xf1, 0xbe, 0xcd, 0x73, 0x80, 0x04, 0x57, +0x1a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0xa8, 0x01, 0xe2, 0xc1, 0x0c, +0x03, 0xfb, 0xaf, 0x01, 0x33, 0xb5, 0xd5, 0x40, +0x36, 0xc9, 0xf8, 0x0d, 0x8c, 0x65, 0x0b, 0x5e, +0x0d, 0xe7, 0x69, 0xe2, 0x06, 0x11, 0x6f, 0xa7, +0xd2, 0x96, 0x6f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0x6e, 0x80, 0xa5, +0x70, 0x70, 0xe5, 0x05, 0xf8, 0x9a, 0x83, 0xeb, +0xc1, 0x29, 0xcb, 0xa3, 0x07, 0x7f, 0xbe, 0x9f, +0x49, 0x08, 0x06, 0x98, 0x00, 0x54, 0x51, 0x2d, +0xe7, 0x89, 0x27, 0xe5, 0x84, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0x46, +0xbb, 0x9c, 0x2b, 0xa7, 0xe0, 0x24, 0xae, 0x03, +0xac, 0xa1, 0xd1, 0xd6, 0xb7, 0x9a, 0xf8, 0x1e, +0x8f, 0x52, 0x06, 0x63, 0xcc, 0x0c, 0xfe, 0x04, +0x25, 0x2e, 0xb9, 0xa1, 0x19, 0x71, 0xdc, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0x40, 0x88, 0xa3, 0x7d, 0x7e, 0xbe, 0xfc, +0xb6, 0x4f, 0x36, 0x72, 0x30, 0x2c, 0x65, 0x18, +0x4f, 0xd9, 0x7a, 0xc3, 0x1a, 0x77, 0x80, 0x68, +0x4d, 0x80, 0x2d, 0xcb, 0xe9, 0x39, 0x46, 0xc1, +0x09, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0xa5, 0x10, 0x07, 0xe5, 0x9a, +0x4b, 0x7e, 0xa7, 0x58, 0xb0, 0xe0, 0x6d, 0xd0, +0xa2, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x97, 0x0d, 0xef, 0x9c, +0x6b, 0x02, 0x2a, 0x3f, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0x1a, 0x10, 0xf6, +0x6a, 0x8e, 0xfd, 0xe1, 0x49, 0xe9, 0x78, 0x3e, +0x5a, 0xf3, 0xbd, 0x15, 0x54, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, +0x2d, 0x7b, 0x28, 0x17, 0xb1, 0x2c, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0xb6, +0xda, 0x6b, 0x4f, 0xd0, 0x77, 0x65, 0xf7, 0x39, +0x35, 0x5d, 0x78, 0xea, 0x5a, 0x90, 0x36, 0x99, +0xae, 0x2c, 0xa0, 0xc4, 0x09, 0x0f, 0x44, 0x54, +0x6d, 0x50, 0x85, 0xae, 0x02, 0xf9, 0x3f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0x3c, 0x9a, 0xae, 0xd3, 0x1d, 0x1a, 0x0d, +0x9b, 0xf2, 0x76, 0xf4, 0xb6, 0xce, 0xf6, 0x76, +0xc3, 0x81, 0x21, 0x7b, 0x09, 0x31, 0xbc, 0x74, +0xd3, 0xde, 0xa0, 0xf3, 0xc4, 0x66, 0xca, 0xf2, +0x01, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0x8b, 0x63, 0x79, 0xb6, 0xe4, +0x64, 0xa3, 0x1a, 0x0c, 0xca, 0x49, 0x53, 0x45, +0xf1, 0xe5, 0x01, 0xba, 0x43, 0x0b, 0x2b, 0x1e, +0xe6, 0xad, 0x7f, 0x99, 0xbf, 0x5b, 0x21, 0x3e, +0xba, 0x85, 0x62, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0x76, 0x19, 0x20, +0x39, 0x6c, 0xad, 0xc4, 0x45, 0xf1, 0x6c, 0x82, +0xa0, 0x0f, 0xf5, 0xc5, 0x38, 0x14, 0xd4, 0xb2, +0x8e, 0xf7, 0x93, 0xed, 0x57, 0x6c, 0x7c, 0x35, +0xfb, 0xdd, 0x98, 0x1b, 0x76, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0xb1, +0x35, 0x43, 0xf5, 0xc4, 0x0a, 0x52, 0xf5, 0xfd, +0xbc, 0x0e, 0xa8, 0x9d, 0xc0, 0x11, 0x71, 0xa5, +0x67, 0xa4, 0x5f, 0xa7, 0xf2, 0x7a, 0x58, 0x1c, +0x44, 0xf4, 0x69, 0xa0, 0xe3, 0x4c, 0x74, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0xc9, 0x46, 0x87, 0x0d, 0x34, 0x67, 0x8b, +0x8a, 0x76, 0x54, 0x43, 0x6f, 0xf6, 0x27, 0x5f, +0x37, 0x92, 0x7e, 0x16, 0x9d, 0x56, 0x50, 0x4a, +0x87, 0x4b, 0x6d, 0xf9, 0x22, 0x7d, 0x94, 0xbf, +0x1e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0x4c, 0xbf, 0xe5, 0xc3, 0x24, +0xcc, 0xc6, 0x34, 0xe9, 0x64, 0x8d, 0x01, 0xc0, +0x36, 0x0d, 0x0f, 0x2d, 0x94, 0x56, 0x94, 0x48, +0xbd, 0x49, 0xbd, 0xa1, 0xb6, 0x03, 0x7e, 0xce, +0x0d, 0x9f, 0xff, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x93, 0xea, 0x11, +0xff, 0x30, 0x8f, 0x02, 0x7f, 0xb3, 0xc6, 0x33, +0xaa, 0x92, 0x22, 0x38, 0x99, 0x5d, 0xbd, 0xe1, +0xcf, 0xce, 0x68, 0x0e, 0x78, 0x67, 0x8e, 0xab, +0xc4, 0x9b, 0xb0, 0xa2, 0x72, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0xc8, +0x8b, 0x3c, 0x31, 0xb8, 0xb9, 0x95, 0x15, 0x08, +0xf8, 0xf2, 0xe8, 0xb9, 0x5f, 0x10, 0xb2, 0xc9, +0x98, 0xde, 0x26, 0x87, 0x2d, 0xdc, 0x8c, 0x3c, +0x54, 0x42, 0xc7, 0x44, 0xaf, 0xfa, 0x7f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x22, 0x33, 0xa5, 0xc7, 0x0d, 0x2c, 0x66, +0xe5, 0x99, 0xd2, 0x6e, 0x90, 0xd5, 0xcf, 0xf8, +0x9b, 0x5f, 0xf7, 0x59, 0x87, 0x9b, 0xb2, 0x7c, +0x4e, 0x9b, 0x29, 0x8f, 0xcf, 0x65, 0xd8, 0xf2, +0x3c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0x2a, 0xe4, 0xf7, 0x27, 0x21, +0xf9, 0xbd, 0x29, 0xbf, 0x48, 0x1d, 0x39, 0x35, +0x8c, 0x84, 0x8e, 0x0a, 0xaf, 0x13, 0xdb, 0xbd, +0x44, 0xdf, 0x3b, 0x7b, 0xe0, 0x7c, 0x07, 0x8b, +0x85, 0xbc, 0x00, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0xe4, 0x03, 0x1a, +0x21, 0x25, 0x2b, 0xaf, 0xf1, 0xd6, 0x64, 0xc3, +0x86, 0xba, 0x34, 0x1c, 0x4a, 0x93, 0xb7, 0x42, +0x08, 0xe6, 0x85, 0xba, 0x70, 0xa4, 0x09, 0x7e, +0x92, 0x52, 0xd4, 0x55, 0x42, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0x23, +0xed, 0xa5, 0xfa, 0xce, 0x73, 0x19, 0xbd, 0x30, +0x0a, 0x3b, 0x07, 0x39, 0x9c, 0x1a, 0x1c, 0x17, +0xbc, 0x46, 0x65, 0x7f, 0x03, 0x6e, 0x10, 0x9e, +0x88, 0xa3, 0x29, 0x2a, 0x13, 0x62, 0x35, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0x37, 0x1a, 0xdf, 0x0e, 0x49, 0xb2, 0xaa, +0xca, 0x4a, 0xf7, 0x86, 0xa3, 0xfc, 0x2a, 0xfe, +0x15, 0x5c, 0xc7, 0x7e, 0xce, 0xd5, 0x5c, 0xb1, +0xd5, 0xee, 0x9d, 0xd7, 0x4c, 0xe9, 0x0a, 0x2e, +0x14, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0xf7, 0xbf, 0x95, 0x05, 0xc3, +0x7c, 0x68, 0xbf, 0x22, 0x0f, 0x0c, 0xfe, 0x00, +0x0d, 0x49, 0x02, 0x07, 0xb6, 0x78, 0x7b, 0x7f, +0x19, 0xf2, 0x4c, 0x27, 0x13, 0xec, 0x10, 0x9b, +0xab, 0x61, 0xd5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0x35, 0x63, 0x09, +0x7d, 0x9b, 0x3d, 0x1e, 0x30, 0x8b, 0xb8, 0x0e, +0x92, 0x83, 0x78, 0x2e, 0xfc, 0x2f, 0xf0, 0xe3, +0x84, 0xdf, 0xb4, 0xdd, 0x65, 0x0c, 0x6c, 0xb7, +0xd9, 0x54, 0xae, 0xf6, 0x98, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0xbc, +0x6e, 0xfd, 0xf0, 0xe2, 0xbc, 0xaf, 0x14, 0x26, +0xe8, 0xa8, 0xaf, 0x8a, 0x31, 0x36, 0xf2, 0x67, +0x14, 0x49, 0x4f, 0x43, 0x8b, 0x49, 0xa6, 0xd7, +0x55, 0x1f, 0xec, 0x7a, 0xe0, 0xfb, 0x03, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0x61, 0x1f, 0xe2, 0x66, 0x62, 0x24, 0x25, +0x88, 0x86, 0x32, 0xfd, 0xf3, 0x37, 0x17, 0xe2, +0xfb, 0xb3, 0x1b, 0xdc, 0xbb, 0xa6, 0xfc, 0xad, +0x91, 0x67, 0x96, 0xef, 0xe2, 0x93, 0x0b, 0xbf, +0xa9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0x69, 0xdd, 0xce, 0x19, 0x41, +0xe3, 0x8b, 0x03, 0xe6, 0x69, 0x0c, 0xfe, 0xb9, +0xa0, 0xa6, 0xda, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xe0, 0xee, 0x8d, 0x7b, +0x9a, 0xb4, 0xb7, 0x63, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0x93, 0x87, 0x5c, +0x1a, 0x8e, 0xe1, 0x7e, 0x7b, 0x96, 0x8d, 0xa3, +0xda, 0xb8, 0xbe, 0x96, 0x82, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0xe4, +0xd4, 0xb8, 0xc6, 0x40, 0x84, 0xae, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0x09, +0xf0, 0x4e, 0x5d, 0xff, 0xd7, 0xcc, 0x23, 0x05, +0x04, 0xf3, 0xda, 0xb3, 0x06, 0xcd, 0x4d, 0x2a, +0xf6, 0x6a, 0xa3, 0x07, 0x97, 0x17, 0xf0, 0xc1, +0x2a, 0xaa, 0x0d, 0xe7, 0xdc, 0x90, 0xaa, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0x8b, 0x1d, 0x30, 0x31, 0x03, 0x8f, 0x79, +0xb3, 0x4b, 0xa9, 0x69, 0x0e, 0x8e, 0x37, 0x48, +0x02, 0xd9, 0x70, 0x24, 0x77, 0xea, 0x53, 0xa8, +0x94, 0x68, 0x81, 0xef, 0xe4, 0x92, 0x95, 0x11, +0xec, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0x16, 0x5d, 0x70, 0x3c, 0x20, +0x36, 0xc2, 0x5c, 0xdd, 0x95, 0x07, 0x0c, 0x15, +0x4e, 0x22, 0xd9, 0x27, 0xec, 0x38, 0xd6, 0x6b, +0x8c, 0x49, 0x11, 0x6d, 0x45, 0x30, 0xcc, 0x1d, +0x4a, 0xae, 0xd2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0x1a, 0x57, 0x23, +0x5c, 0xff, 0x38, 0xd5, 0x3e, 0x0b, 0x3d, 0x08, +0x1d, 0x32, 0xd2, 0x18, 0x9f, 0x59, 0x32, 0x7b, +0xa0, 0x52, 0x3e, 0x56, 0x96, 0x67, 0x58, 0xed, +0xcf, 0x25, 0xc9, 0xe3, 0x06, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0x71, +0xaf, 0x7b, 0x8d, 0xcc, 0x91, 0xa3, 0x3b, 0xef, +0x15, 0x4f, 0x9c, 0x02, 0xd1, 0xd3, 0x56, 0x55, +0x37, 0x94, 0xdb, 0xa4, 0x4a, 0x6a, 0xa1, 0x87, +0xe7, 0xf0, 0xbc, 0x38, 0x62, 0x24, 0xc7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0x63, 0x3b, 0x60, 0xe0, 0x66, 0x83, 0x43, +0x80, 0x51, 0x1e, 0xf6, 0xfe, 0x25, 0x34, 0xa6, +0xf3, 0x42, 0xd5, 0x98, 0x07, 0xa3, 0x71, 0xc4, +0x66, 0x5d, 0x5e, 0xaf, 0xbc, 0x2c, 0x54, 0x5c, +0x1c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0x3c, 0xbd, 0x92, 0xc2, 0x04, +0x58, 0x71, 0x9e, 0xaa, 0x26, 0x77, 0xdc, 0x55, +0x68, 0x28, 0xa7, 0xfe, 0x1b, 0x3c, 0x9b, 0x19, +0x1f, 0xb7, 0xcb, 0x88, 0xaa, 0xca, 0x4b, 0xad, +0x93, 0x84, 0x76, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0xd8, 0xce, 0x2d, +0x0e, 0x2e, 0x39, 0x9c, 0xf3, 0xc2, 0xce, 0x4c, +0x6a, 0xe4, 0x7d, 0x0a, 0x46, 0x91, 0x95, 0x3e, +0xcb, 0x1f, 0x8f, 0x73, 0xda, 0xca, 0xca, 0xa1, +0xfa, 0xc2, 0x0a, 0x7c, 0x54, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0x0a, +0xdb, 0xf3, 0xc0, 0x17, 0x30, 0xbc, 0x1e, 0x30, +0x22, 0x22, 0x1c, 0x88, 0xbf, 0xc1, 0xe7, 0x62, +0x50, 0xb6, 0xec, 0x01, 0xe5, 0x53, 0xc6, 0x84, +0xed, 0xf0, 0xbd, 0x2e, 0xc5, 0x65, 0xcb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0x65, 0xf9, 0x1e, 0x35, 0x3f, 0xcb, 0xb4, +0xcf, 0x8c, 0x67, 0x75, 0x71, 0x95, 0x05, 0x3b, +0xa5, 0x05, 0x05, 0xce, 0x71, 0x85, 0xe1, 0x55, +0x00, 0xa0, 0x99, 0x10, 0xe1, 0xee, 0x29, 0x02, +0x34, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0x1c, 0x57, 0x19, 0xb0, 0xa7, +0xbd, 0x16, 0x79, 0x4b, 0x90, 0x9e, 0x24, 0x5f, +0x2f, 0xf7, 0x77, 0x48, 0x66, 0xdb, 0x04, 0x2a, +0x3d, 0xa0, 0xd7, 0xda, 0xaf, 0xfe, 0x64, 0x56, +0xe0, 0x09, 0xf3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0xbe, 0xfa, 0x53, +0x39, 0x01, 0x98, 0x2c, 0x5e, 0x80, 0x4d, 0x34, +0x05, 0xbc, 0x81, 0xa1, 0xb9, 0x4f, 0x0e, 0x0e, +0xb2, 0x83, 0xdb, 0x32, 0x71, 0xc9, 0x6b, 0xeb, +0xcf, 0x9b, 0x88, 0x79, 0xce, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0x98, +0x32, 0x37, 0x6f, 0x00, 0x5d, 0x5f, 0x85, 0x22, +0xd2, 0x38, 0x54, 0x35, 0x34, 0x2a, 0xc8, 0x19, +0x23, 0x64, 0xf3, 0x53, 0x65, 0xf3, 0xee, 0xd1, +0x6e, 0x91, 0xcf, 0xed, 0x92, 0x0c, 0xd4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0x38, 0x11, 0x7e, 0xd6, 0x35, 0xbf, 0x8e, +0x6e, 0x00, 0x82, 0x31, 0xfa, 0xbc, 0xa4, 0x22, +0xa9, 0x35, 0x29, 0xba, 0x20, 0x6d, 0xaf, 0x02, +0x48, 0xae, 0xdc, 0x95, 0x3b, 0x93, 0xa5, 0xf4, +0xf3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0xac, 0x5f, 0xd7, 0x0f, 0xd3, +0x39, 0x1d, 0x64, 0x75, 0x50, 0xed, 0xad, 0xc1, +0x24, 0x8e, 0x5b, 0xfc, 0xc1, 0x97, 0x93, 0x8d, +0x09, 0x7b, 0x50, 0x5f, 0xfe, 0x0b, 0x33, 0x6e, +0xfa, 0x2a, 0x1b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0x7a, 0x1a, 0xf6, +0x3e, 0xb0, 0x82, 0xc3, 0x50, 0xdb, 0x66, 0x98, +0xbe, 0x52, 0x4b, 0x1a, 0xd3, 0xa1, 0x78, 0x71, +0xe5, 0x1c, 0x50, 0xde, 0x56, 0xb5, 0x05, 0xca, +0xf6, 0xc1, 0xc3, 0x37, 0x5c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0xc4, +0xc9, 0x38, 0xd5, 0xaa, 0xf9, 0xa8, 0xb3, 0xa2, +0x22, 0xaf, 0x47, 0x77, 0x95, 0x1c, 0xec, 0x07, +0xcb, 0x02, 0xef, 0x46, 0x6d, 0x6e, 0xc0, 0x9b, +0x83, 0x6d, 0xc2, 0x41, 0xf5, 0x50, 0x4a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0x69, 0xee, 0x7a, 0x72, 0x5d, 0x9a, 0xb2, +0x1b, 0x1c, 0xd7, 0x59, 0xeb, 0x8c, 0x32, 0xb4, +0x9d, 0xfd, 0x0f, 0x9b, 0x88, 0x8a, 0x8c, 0x0b, +0xb8, 0x2e, 0x83, 0xd0, 0xae, 0xa5, 0xe2, 0x22, +0x8b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0x84, 0x6a, 0x1f, 0x1d, 0x92, +0x48, 0xf6, 0x47, 0xeb, 0x1d, 0x0f, 0xa6, 0xbe, +0x8e, 0x56, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x07, 0xbf, 0x55, 0x5e, +0x87, 0xb7, 0x64, 0x5a, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0x7d, 0x28, 0xf7, +0x5c, 0x85, 0x31, 0x20, 0xe3, 0x3e, 0xdc, 0xb7, +0x8c, 0x3e, 0x29, 0x3e, 0xcb, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x92, 0xb9, +0x27, 0xe3, 0xe1, 0xb4, 0x4a, 0xbd, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0x57, +0xdd, 0x0a, 0xfc, 0x54, 0xd5, 0x16, 0x4d, 0xc8, +0xd7, 0x26, 0xc7, 0x91, 0xa1, 0xf7, 0x3f, 0x0a, +0xeb, 0x15, 0xe3, 0xd4, 0xdc, 0x2c, 0xe9, 0x41, +0x10, 0xe7, 0x44, 0x8a, 0x57, 0x64, 0x01, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0x5d, 0xcc, 0x1a, 0x85, 0xe5, 0xc0, 0x94, +0x55, 0x6f, 0xfd, 0x73, 0x47, 0xa3, 0x91, 0x5a, +0x1e, 0xc3, 0xb2, 0x3a, 0x33, 0xd9, 0x9d, 0x48, +0x57, 0x95, 0xdc, 0x0e, 0xa4, 0xc2, 0xc9, 0xd6, +0xea, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0x1d, 0x2a, 0x7e, 0xbe, 0xe8, +0xd9, 0xf6, 0x9c, 0xcc, 0x02, 0x3d, 0x6a, 0xca, +0x23, 0x7f, 0x42, 0x6c, 0x7d, 0x68, 0x3b, 0xce, +0xcd, 0xb3, 0xaf, 0x4c, 0x7f, 0xf0, 0xa4, 0xd3, +0xc2, 0xdc, 0xad, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x1b, 0x32, 0x85, +0x75, 0x8b, 0x1f, 0x6a, 0x91, 0x9d, 0x43, 0x2d, +0x12, 0x5e, 0x96, 0x35, 0xb2, 0xcb, 0xc4, 0x3f, +0x66, 0x39, 0xd2, 0xe3, 0x0a, 0x6d, 0x2b, 0xa4, +0x96, 0x29, 0xf8, 0x74, 0x57, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0xeb, +0x33, 0x8e, 0xf4, 0x4c, 0x55, 0x57, 0x20, 0x27, +0x44, 0xf0, 0xa2, 0x35, 0x42, 0x19, 0x10, 0xc0, +0x76, 0x1e, 0x91, 0xff, 0x48, 0x19, 0xbf, 0x56, +0x45, 0x36, 0x37, 0x06, 0x59, 0x57, 0x07, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x0a, 0x3b, 0x34, 0x7f, 0x73, 0xf8, 0x3a, +0x42, 0x8e, 0xa5, 0x01, 0x84, 0xc2, 0x2b, 0xec, +0xcf, 0xeb, 0x18, 0x25, 0x48, 0xcc, 0x3b, 0x86, +0x54, 0x55, 0xad, 0x63, 0xba, 0x65, 0xa6, 0x0f, +0x4c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0xcf, 0xb3, 0x6a, 0x4b, 0x34, +0x56, 0x17, 0x18, 0xc8, 0x6e, 0x36, 0x51, 0x3b, +0xd3, 0x4d, 0x27, 0x16, 0x02, 0x70, 0x44, 0x06, +0xd7, 0xff, 0xe2, 0x75, 0xf4, 0xaf, 0x0f, 0x33, +0x33, 0x15, 0x32, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0xa8, 0x86, 0xf8, +0xf7, 0x4a, 0x63, 0x7d, 0x6c, 0x6e, 0x7a, 0x35, +0xb6, 0x85, 0xd8, 0xf4, 0x63, 0x5f, 0x3a, 0x2f, +0x94, 0x83, 0xd2, 0xc9, 0xf0, 0x04, 0x03, 0x83, +0x2d, 0x6e, 0x41, 0x61, 0xd4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0xc4, +0x86, 0xfd, 0x08, 0x33, 0x97, 0x21, 0xb0, 0xae, +0x22, 0xbd, 0x2e, 0x40, 0x2a, 0xca, 0x0a, 0x65, +0xcd, 0x2d, 0x0c, 0x60, 0x0c, 0x41, 0xb7, 0xf4, +0xe6, 0x2a, 0x39, 0xe3, 0xb0, 0x44, 0x03, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0x95, 0x8f, 0xa8, 0xe4, 0xd1, 0xe2, 0xe1, +0xd8, 0x09, 0xe9, 0x06, 0xea, 0xfc, 0x96, 0x13, +0x9c, 0x2d, 0xf7, 0xc6, 0x81, 0x1e, 0x34, 0x4d, +0xe3, 0x67, 0x0e, 0x94, 0xc2, 0xcc, 0x01, 0x4f, +0xb3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0x88, 0x0c, 0x0f, 0x63, 0xfd, +0xca, 0xc1, 0xcd, 0xf7, 0x6c, 0x8f, 0x6f, 0xf5, +0xb3, 0xe9, 0x7f, 0x84, 0x2a, 0x82, 0x67, 0x52, +0x26, 0x81, 0xf8, 0xc3, 0x5c, 0x37, 0xde, 0xae, +0x1c, 0x2f, 0x0d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0x73, 0x80, 0xd5, +0xd8, 0xc8, 0x21, 0xba, 0xd4, 0x48, 0x79, 0x58, +0xce, 0x33, 0xe8, 0x86, 0x1f, 0x9b, 0xae, 0x1f, +0xb8, 0x75, 0x32, 0x3c, 0xbd, 0xde, 0x55, 0x97, +0xa5, 0xcf, 0x8e, 0xda, 0xdd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x7c, +0x4a, 0x5b, 0x8a, 0xf0, 0xfe, 0x63, 0x33, 0x8b, +0x48, 0x2b, 0x7f, 0xe7, 0x72, 0x1c, 0x58, 0xe2, +0xc0, 0xc3, 0x97, 0x3b, 0x97, 0xda, 0xc0, 0x1f, +0x54, 0x34, 0xa7, 0x74, 0x31, 0xf0, 0x3c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0x02, 0x89, 0x5c, 0x7c, 0xbd, 0x14, 0xcf, +0x7c, 0xc5, 0xb7, 0x39, 0x54, 0xc3, 0xb8, 0x97, +0x30, 0xf9, 0xc7, 0xba, 0x00, 0xce, 0x84, 0x94, +0x3f, 0x04, 0x7e, 0xa3, 0xa5, 0x6a, 0xcd, 0x39, +0xf0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0xfe, 0xd6, 0xb6, 0x1e, 0x8c, +0x1f, 0xa5, 0xfa, 0xd5, 0x12, 0x76, 0x69, 0x2d, +0x81, 0x1d, 0xd7, 0x26, 0x42, 0x35, 0x27, 0xab, +0x75, 0x02, 0xb8, 0x33, 0x5b, 0x8f, 0x48, 0x7d, +0x14, 0x77, 0xaf, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0xec, 0x6a, 0xc3, +0xee, 0xf7, 0x0e, 0xde, 0x6a, 0x3a, 0x32, 0xeb, +0x82, 0x72, 0x6e, 0x25, 0xa5, 0x3b, 0xdc, 0xb1, +0xa8, 0x90, 0xd3, 0x14, 0x5d, 0x12, 0x2a, 0xef, +0x55, 0xe4, 0x4b, 0x07, 0xfe, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0xe1, +0x91, 0x73, 0xf6, 0x37, 0x49, 0xc3, 0x03, 0x2f, +0x46, 0x42, 0xa8, 0x2a, 0x25, 0x83, 0xb4, 0x6f, +0x15, 0x7a, 0x14, 0x90, 0xc0, 0x35, 0x58, 0x86, +0xe2, 0x5d, 0x7b, 0xde, 0x39, 0x07, 0x54, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0xa7, 0xd6, 0xb3, 0xdf, 0xc1, 0x4b, 0x19, +0x05, 0x39, 0x6b, 0x83, 0xc8, 0x51, 0x43, 0xae, +0x07, 0x1b, 0x19, 0x5b, 0xce, 0xa7, 0x4c, 0x2b, +0x44, 0xae, 0x74, 0xa2, 0xe0, 0xd9, 0xcb, 0xd3, +0xd7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0x35, 0x0f, 0xfc, 0x10, 0x6c, +0x29, 0xeb, 0x7e, 0x29, 0x34, 0x09, 0x53, 0x37, +0xd4, 0x25, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xcc, 0x13, 0x28, 0xa1, +0x78, 0xd4, 0x6b, 0x0a, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x8b, 0xa6, 0xdd, +0x59, 0x28, 0x2a, 0x20, 0x2d, 0x63, 0x34, 0x85, +0x5d, 0x88, 0x8d, 0xc5, 0x64, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa7, 0x92, +0x3f, 0x9b, 0x59, 0x61, 0xca, 0xad, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0xbb, +0x89, 0xb0, 0x80, 0xc0, 0x95, 0x64, 0xdd, 0xb8, +0xf6, 0xb9, 0xf4, 0xf4, 0x74, 0xf2, 0x71, 0xd5, +0x91, 0x6e, 0x0d, 0xf6, 0x31, 0x88, 0xc0, 0x5a, +0x98, 0x80, 0xe8, 0xda, 0xc1, 0xde, 0x17, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0x95, 0x76, 0x07, 0x5e, 0x58, 0xf5, 0x10, +0x38, 0x50, 0x19, 0xf3, 0x10, 0x4a, 0xdb, 0xa9, +0x7b, 0x58, 0x46, 0x92, 0xbd, 0x3a, 0x52, 0x4b, +0x0a, 0x56, 0xce, 0x57, 0x03, 0x24, 0x3f, 0x24, +0xa0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0x06, 0xd2, 0x70, 0xa0, 0x16, +0x87, 0xc9, 0xe5, 0x29, 0xc7, 0x04, 0x4e, 0x50, +0xd9, 0x0e, 0x81, 0xa5, 0x0c, 0x63, 0x59, 0x0b, +0x52, 0xe2, 0xd0, 0xe0, 0x1a, 0x8f, 0x04, 0x89, +0xf1, 0x05, 0x92, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0xc9, 0xed, 0x9c, +0xa8, 0x71, 0xbe, 0xe4, 0x49, 0x2b, 0xa3, 0x2e, +0x56, 0x27, 0x4a, 0xcc, 0xa1, 0x6e, 0x47, 0x5e, +0x87, 0x1d, 0xc2, 0x8e, 0x21, 0x49, 0x30, 0x70, +0x56, 0xbc, 0xcc, 0xd0, 0xea, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0xe5, +0x8c, 0x5a, 0x7a, 0x18, 0xfa, 0x06, 0xfc, 0x1a, +0x46, 0x17, 0xb3, 0x73, 0x0f, 0xde, 0x4e, 0x24, +0x1f, 0xf2, 0x8d, 0x6b, 0xe3, 0xef, 0xe3, 0x7e, +0xbf, 0x54, 0x0a, 0x01, 0xb1, 0x00, 0x99, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x0e, 0x70, 0x49, 0x16, 0x4c, 0x5b, 0xb6, +0xce, 0x84, 0x1a, 0xe9, 0x62, 0xa9, 0x2b, 0x2d, +0x94, 0x84, 0x2b, 0xf1, 0x8a, 0x50, 0x6a, 0xb4, +0xfa, 0x6f, 0x6e, 0x06, 0x1a, 0x48, 0xb3, 0x3f, +0x13, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x96, 0xa6, 0x01, 0x23, 0x29, +0xd0, 0x04, 0x18, 0x17, 0xe9, 0x8e, 0x9b, 0xbf, +0x80, 0x0a, 0xc2, 0x65, 0xf6, 0xb7, 0xee, 0xac, +0xf1, 0x77, 0x66, 0xd4, 0xd6, 0xef, 0xa5, 0x83, +0x6d, 0x1a, 0xff, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0x4b, 0x08, 0x2f, +0x84, 0x2e, 0x98, 0x50, 0xb7, 0x55, 0xd5, 0x47, +0x05, 0xeb, 0xde, 0x65, 0xb7, 0xa5, 0x39, 0xdd, +0xe1, 0xb2, 0xba, 0xcc, 0x62, 0x80, 0xce, 0x10, +0x9c, 0xa4, 0x5d, 0x86, 0xcf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0x2c, +0x7f, 0x32, 0x07, 0x79, 0x82, 0x88, 0x7e, 0x65, +0xd8, 0x84, 0xe3, 0xba, 0x44, 0xb6, 0xd7, 0x70, +0x92, 0x91, 0xf8, 0xff, 0xcb, 0xa8, 0x69, 0x5b, +0x46, 0xe2, 0x9d, 0xc7, 0x9a, 0x0e, 0xcd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x4f, 0x75, 0xa8, 0xdd, 0x05, 0x8b, 0x3b, +0x93, 0xb2, 0x39, 0xc1, 0xb9, 0x9e, 0x77, 0x08, +0xa2, 0x6f, 0x7a, 0x05, 0x3d, 0xb0, 0x0a, 0x1d, +0x6a, 0x91, 0x11, 0x94, 0x75, 0x00, 0x82, 0xbd, +0x13, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0xcb, 0xa2, 0x4e, 0xe6, 0xf9, +0x47, 0xa3, 0xe2, 0xc9, 0x62, 0x6e, 0xa2, 0xe3, +0xed, 0x5b, 0x77, 0xea, 0x8c, 0x6b, 0x5e, 0xbe, +0x49, 0x3d, 0x04, 0x85, 0xe0, 0x56, 0xcd, 0xf9, +0xf0, 0x69, 0x1e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0x23, 0xe5, 0xcd, +0xed, 0x70, 0xa1, 0x20, 0x90, 0x9b, 0x1a, 0x79, +0x0f, 0x4b, 0x20, 0xcc, 0xd9, 0x13, 0xa5, 0xb6, +0x35, 0x68, 0x81, 0xcb, 0x6b, 0xf9, 0x17, 0x6d, +0xa3, 0x9f, 0x6f, 0x86, 0xa4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x36, +0x92, 0x46, 0x14, 0x21, 0xab, 0xf2, 0xd5, 0xc8, +0x68, 0x56, 0x56, 0x27, 0x56, 0x4f, 0x0e, 0x97, +0x56, 0xf2, 0xdc, 0xd8, 0xe5, 0x39, 0xe9, 0xf4, +0x4c, 0x58, 0x76, 0xda, 0x33, 0x84, 0x08, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0xb6, 0xd6, 0x3f, 0xdd, 0x5b, 0x88, 0xd7, +0x4b, 0x00, 0xb2, 0x0b, 0xba, 0xe2, 0xa0, 0x5f, +0x50, 0x63, 0x9e, 0x64, 0xe2, 0xcb, 0x28, 0x68, +0x11, 0x96, 0xd4, 0x06, 0xcd, 0x32, 0x16, 0xed, +0x46, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0x00, 0xfc, 0x29, 0x4b, 0x9d, +0xdd, 0x87, 0xcd, 0xa3, 0x3f, 0x5a, 0x3c, 0xaa, +0x22, 0x96, 0xcb, 0x51, 0xa5, 0x89, 0xc1, 0xfc, +0x85, 0xde, 0xd7, 0xfc, 0xae, 0x6d, 0xb7, 0xff, +0x8c, 0x26, 0x59, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0x4c, 0x44, 0xaa, +0xc4, 0xa8, 0x1f, 0xce, 0x26, 0xa1, 0x1b, 0x1e, +0xc6, 0xa7, 0x64, 0xa0, 0x7c, 0x10, 0x72, 0xae, +0x5a, 0x05, 0x63, 0xa3, 0xbe, 0xcd, 0x17, 0x04, +0xd7, 0x58, 0x6c, 0xec, 0x42, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0xc1, +0xfc, 0x9e, 0x1f, 0x63, 0x3a, 0x02, 0x3f, 0x1e, +0xba, 0x0e, 0x86, 0xc0, 0x3c, 0x3b, 0x6e, 0x2b, +0x8f, 0xe2, 0xe3, 0x7b, 0x72, 0xd9, 0x3e, 0xff, +0x6e, 0x6a, 0xf7, 0x84, 0x2a, 0x81, 0x5e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0xeb, 0x53, 0xd2, 0x01, 0xba, 0xbc, 0x99, +0x6d, 0x9b, 0xdc, 0x6a, 0xf4, 0xa4, 0xb6, 0x45, +0x88, 0xbb, 0xd5, 0x7f, 0xa0, 0xfe, 0x47, 0x18, +0xd8, 0xbc, 0x62, 0x7f, 0xd2, 0x60, 0xc0, 0x1f, +0x20, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0xd5, 0xad, 0x20, 0x18, 0x1b, +0x64, 0xbf, 0xe9, 0x6a, 0x38, 0xd0, 0x0f, 0xa2, +0x87, 0x5f, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xc7, 0xb0, 0x2f, 0xa7, +0x48, 0xed, 0x01, 0xcd, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0xd0, 0x75, 0x2b, +0x2c, 0x8c, 0xad, 0xb3, 0xcc, 0x93, 0x58, 0x95, +0x04, 0x55, 0x19, 0xd2, 0x1a, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x5b, +0x3d, 0x8f, 0xee, 0xa1, 0x86, 0x05, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0x9a, +0xc2, 0x3e, 0x1a, 0x7f, 0xa8, 0xb8, 0xd8, 0x65, +0x58, 0xb5, 0x47, 0x4b, 0x74, 0x4a, 0x1e, 0x0e, +0x38, 0x09, 0xf6, 0x8a, 0xad, 0xa8, 0xba, 0xb1, +0x11, 0x12, 0x30, 0xaa, 0x9a, 0x6a, 0xb6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0x08, 0x69, 0xce, 0xcb, 0x69, 0x33, 0x58, +0x6d, 0xbf, 0xf9, 0x8d, 0xbc, 0xee, 0xe7, 0x02, +0x41, 0xa8, 0x80, 0xd9, 0x8d, 0x9b, 0x46, 0x3d, +0x4b, 0x6a, 0xdf, 0x2c, 0xd0, 0x0d, 0xbd, 0x6f, +0x1a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0xe5, 0xee, 0x02, 0xa0, 0x94, +0xea, 0x45, 0x97, 0x29, 0x3a, 0xe7, 0x7b, 0x0c, +0xe3, 0x7f, 0x0d, 0xf7, 0xd8, 0x03, 0xb4, 0x9e, +0x26, 0xb0, 0xad, 0xed, 0x79, 0x7c, 0x06, 0x63, +0x8c, 0xbe, 0x26, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0x03, 0x30, 0xb4, +0xc5, 0x49, 0xf0, 0x0f, 0x25, 0x42, 0x7f, 0x6f, +0x37, 0xf4, 0x89, 0x6f, 0x34, 0x3a, 0xc3, 0x85, +0x5d, 0xcb, 0x18, 0xb0, 0x87, 0xf2, 0x5c, 0xd3, +0xd4, 0x76, 0xe1, 0x70, 0xf5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0xba, +0x88, 0x0a, 0x98, 0x24, 0x37, 0x66, 0x2a, 0xd1, +0xbc, 0x75, 0x7a, 0x77, 0xac, 0xa5, 0x63, 0xe6, +0x26, 0x68, 0x60, 0xba, 0x80, 0xb1, 0x65, 0xe0, +0xf8, 0xdf, 0xfc, 0x51, 0x66, 0x7b, 0x3c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0x1d, 0xe2, 0x73, 0x32, 0x33, 0xba, 0x88, +0x27, 0x9d, 0xe3, 0xa6, 0x7e, 0x2b, 0x9a, 0x53, +0x47, 0x35, 0x58, 0xc8, 0x28, 0xbf, 0x19, 0x1d, +0x87, 0x89, 0x68, 0x14, 0x8a, 0x0e, 0x33, 0xe0, +0xe5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0xd0, 0xd5, 0xe6, 0xc1, 0xf8, +0x22, 0x6a, 0x98, 0x4b, 0xf5, 0xd2, 0xf6, 0x4a, +0x69, 0x59, 0x4b, 0x22, 0x6a, 0xec, 0xd9, 0x03, +0xd0, 0x6f, 0x41, 0xef, 0x93, 0x0e, 0x99, 0x1e, +0x4c, 0x1b, 0x12, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0x02, 0x3a, 0x43, +0x09, 0x40, 0x5f, 0xfb, 0xb6, 0xf5, 0xe0, 0xfe, +0xba, 0x4d, 0x99, 0xb5, 0x90, 0x53, 0x26, 0x6c, +0xd2, 0x21, 0x7a, 0xfa, 0x86, 0x91, 0xc4, 0x11, +0xce, 0x3c, 0xac, 0xa9, 0x01, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0x23, +0x1a, 0x8c, 0xea, 0xab, 0x20, 0x0d, 0x84, 0x61, +0x50, 0x66, 0xc3, 0x69, 0xa1, 0x0a, 0x80, 0x9b, +0xb4, 0xbe, 0x4a, 0xfa, 0x4f, 0x54, 0x48, 0x1a, +0xcb, 0xf6, 0x2d, 0x3d, 0x7b, 0x9c, 0x54, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0xcd, 0x7a, 0xf0, 0x16, 0x88, 0x17, 0x36, +0xca, 0x38, 0x1b, 0x4c, 0x36, 0x4f, 0x14, 0x9d, +0xaa, 0x41, 0x26, 0x1d, 0x45, 0xe9, 0x7c, 0x18, +0x9c, 0xde, 0xf9, 0xec, 0x67, 0x59, 0x96, 0x66, +0xc4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x84, 0x21, 0xbc, 0x03, 0x57, +0xdf, 0x44, 0x5e, 0xa1, 0xdf, 0xeb, 0x1e, 0x34, +0xbe, 0x73, 0xbe, 0x65, 0x20, 0xeb, 0x14, 0x3b, +0x3c, 0xfa, 0x1c, 0xb1, 0xba, 0x06, 0xaa, 0xdc, +0xc5, 0x73, 0x14, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0x48, 0xfe, 0xf3, +0x9b, 0x74, 0xef, 0xf2, 0x9f, 0xde, 0xdc, 0x31, +0x6b, 0xb6, 0x70, 0xe1, 0x83, 0xbb, 0xd6, 0x49, +0x3c, 0x01, 0x17, 0xc4, 0x23, 0x37, 0xca, 0x18, +0x98, 0x68, 0x9c, 0xba, 0x21, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0xdc, +0x37, 0x0a, 0xec, 0x61, 0x83, 0x40, 0xc6, 0xfd, +0x4b, 0x2e, 0xc6, 0x87, 0x89, 0x7c, 0x0c, 0x2e, +0xb0, 0xc9, 0xc9, 0x94, 0x51, 0x59, 0x7f, 0x9d, +0x87, 0x21, 0x03, 0x0b, 0x04, 0x49, 0xa2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0xc5, 0x99, 0x60, 0x2a, 0xa2, 0x19, 0x7e, +0x64, 0xc8, 0x60, 0xf7, 0x20, 0xb3, 0x3e, 0xf8, +0x80, 0x09, 0x7b, 0x2a, 0x85, 0x6d, 0x16, 0xa6, +0x76, 0x50, 0x17, 0x75, 0x59, 0x8c, 0xbf, 0xe2, +0xbc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x76, 0xf7, 0xde, 0x87, 0x88, +0x5d, 0x78, 0x99, 0xfc, 0xac, 0x61, 0xbd, 0xc6, +0xcf, 0xa9, 0x3e, 0xc1, 0xc6, 0x0c, 0x61, 0x44, +0x6d, 0xdc, 0x88, 0x14, 0xe4, 0xb5, 0x4e, 0xf1, +0x74, 0xdf, 0x22, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0xec, 0x0c, 0x56, +0x10, 0x57, 0x7a, 0x1f, 0x6f, 0xa1, 0x74, 0x10, +0xc5, 0xf3, 0xb7, 0x24, 0x35, 0x4a, 0x6d, 0x55, +0x3d, 0x56, 0x0c, 0x29, 0x01, 0x00, 0xe3, 0x82, +0x33, 0x2e, 0x47, 0x64, 0x11, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0x2f, +0x75, 0x3b, 0x67, 0x12, 0x09, 0xde, 0x19, 0x15, +0x37, 0xfd, 0x24, 0xf9, 0xcc, 0x33, 0x9d, 0x75, +0xec, 0xdc, 0xfe, 0x9b, 0xae, 0x9b, 0x68, 0xa0, +0x4b, 0x76, 0x24, 0x18, 0x48, 0xc1, 0x03, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0x79, 0x01, 0x14, 0x8f, 0xbf, 0x03, 0x95, +0xa7, 0x29, 0xd2, 0x3b, 0x5c, 0xe1, 0xe1, 0x22, +0x7f, 0x2d, 0x02, 0xb7, 0x1a, 0x32, 0xda, 0x68, +0x88, 0x76, 0xb7, 0x17, 0x0a, 0xfb, 0x3f, 0x85, +0x8a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0xe8, 0xa5, 0xac, 0xd3, 0x70, +0x4e, 0x0a, 0x04, 0x9a, 0xca, 0xb6, 0xc1, 0x5d, +0x16, 0x3b, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x8a, 0xbf, 0x18, 0xb8, +0xf7, 0x73, 0xb6, 0xcf, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x9e, 0x41, 0xe5, +0xe8, 0x19, 0x17, 0xc2, 0x8f, 0xb4, 0xf9, 0x1a, +0x03, 0xbc, 0x9c, 0x55, 0x81, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x94, 0x12, +0x42, 0xbf, 0x74, 0x53, 0x63, 0xa2, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x59, +0xed, 0x1c, 0xcc, 0xe2, 0xa1, 0x5f, 0x6e, 0x8a, +0x7e, 0xc2, 0xcf, 0x9f, 0xcf, 0xb0, 0x4d, 0xcb, +0x26, 0x57, 0x16, 0xd8, 0xd3, 0xe4, 0xca, 0x1f, +0xde, 0x1d, 0x82, 0x3e, 0x6b, 0x07, 0xf8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0x44, 0xd4, 0x03, 0xe2, 0xce, 0x9e, 0x15, +0xf1, 0x96, 0xff, 0x16, 0x5f, 0x03, 0xc6, 0x0f, +0x14, 0x3b, 0x36, 0x6a, 0x92, 0xc2, 0x2c, 0x87, +0xaa, 0xb8, 0x1e, 0x7b, 0xbd, 0xa4, 0xcc, 0x17, +0x4a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0x50, 0xf1, 0x80, 0x20, 0x24, +0x7e, 0x33, 0x64, 0xe4, 0x91, 0xfd, 0x5f, 0xa3, +0x22, 0x15, 0x0c, 0x22, 0x7d, 0x70, 0x33, 0xc3, +0x7c, 0x49, 0x6b, 0x6d, 0x15, 0xb3, 0xe8, 0x31, +0x95, 0xcc, 0xc8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0xef, 0x67, 0xac, +0x33, 0x86, 0x7a, 0xdc, 0xeb, 0x05, 0x87, 0xb6, +0x1a, 0x16, 0xd0, 0x95, 0xb4, 0xfa, 0x69, 0x34, +0xd4, 0x96, 0xef, 0x78, 0x7c, 0xf8, 0x2d, 0xf2, +0x90, 0xff, 0x86, 0x2a, 0x5e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0xfe, +0x6f, 0xf6, 0x6b, 0x52, 0xa9, 0xcd, 0xf8, 0x69, +0xeb, 0xf7, 0x71, 0x75, 0x42, 0x73, 0x6c, 0x84, +0x39, 0xc6, 0xb5, 0x79, 0x19, 0x56, 0x3a, 0x84, +0x4e, 0x72, 0x42, 0x63, 0xa2, 0x80, 0xa0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0x5e, 0xc8, 0x86, 0xc6, 0x98, 0x6d, 0xac, +0xfa, 0x0d, 0xef, 0x50, 0xb7, 0xfc, 0x59, 0xa6, +0xb3, 0xf2, 0x7d, 0xa7, 0xe6, 0x34, 0x94, 0xfd, +0xd5, 0x0c, 0x60, 0x61, 0xbe, 0x72, 0x5b, 0x00, +0x26, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0x01, 0x37, 0x34, 0x6e, 0x1e, +0xc2, 0xfc, 0x07, 0x7e, 0x8a, 0x99, 0x0f, 0xe0, +0x99, 0x30, 0x6b, 0x5e, 0xfe, 0x88, 0x70, 0x3a, +0x94, 0x24, 0x0e, 0x30, 0x89, 0x85, 0xa6, 0x7d, +0x54, 0xb2, 0xfa, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0x7b, 0x46, 0xa8, +0x35, 0x27, 0x19, 0xd3, 0x9a, 0xdf, 0xe6, 0x72, +0x31, 0xf7, 0xe6, 0x29, 0x4c, 0xab, 0x5b, 0x8d, +0x6b, 0x4f, 0xc2, 0x1f, 0xff, 0xbc, 0x61, 0xd6, +0xe1, 0x9d, 0x87, 0x64, 0x04, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0xcd, +0xa0, 0x30, 0x4c, 0xf7, 0x56, 0x55, 0xc5, 0x94, +0x61, 0x9c, 0x7a, 0xf2, 0xdf, 0x5f, 0xe0, 0xbc, +0xc5, 0x3f, 0xea, 0x34, 0xae, 0x9d, 0xee, 0xbf, +0x47, 0x94, 0x5f, 0x24, 0x65, 0xa5, 0x8e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0x1f, 0x4e, 0xca, 0x98, 0x95, 0x15, 0x7d, +0x19, 0x22, 0xa6, 0xd9, 0xb6, 0x5e, 0x4e, 0xd1, +0x03, 0x6b, 0xdf, 0x67, 0x74, 0x50, 0x21, 0x40, +0x61, 0xe9, 0x58, 0x48, 0x1b, 0x1b, 0xc7, 0xa8, +0x17, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0xd1, 0xbc, 0x69, 0x88, 0x8e, +0x8f, 0xe7, 0xc0, 0x0c, 0xe1, 0x36, 0x5e, 0x83, +0xa3, 0x6a, 0xb4, 0x62, 0xbd, 0x11, 0x03, 0xa6, +0x98, 0x74, 0x22, 0x26, 0x03, 0x85, 0x8a, 0x3b, +0x9a, 0x6c, 0x92, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x92, 0x73, 0x10, +0x96, 0x2b, 0xd3, 0x59, 0x3e, 0xcc, 0xd2, 0x47, +0xed, 0x0e, 0x52, 0xa9, 0x51, 0x70, 0x3d, 0x41, +0xb8, 0x72, 0x55, 0x9f, 0x02, 0x49, 0x08, 0x61, +0x44, 0xc6, 0x9a, 0xf8, 0x7d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0x37, +0xde, 0x2c, 0x3e, 0x06, 0xb2, 0xdd, 0x5d, 0x23, +0x4b, 0x90, 0x46, 0x87, 0x45, 0x54, 0x2f, 0x92, +0xd7, 0x5b, 0xa5, 0x9e, 0x56, 0xfa, 0xa5, 0xc4, +0x72, 0x25, 0x31, 0x25, 0x59, 0x98, 0x85, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0xe0, 0x98, 0x80, 0xb4, 0x11, 0x60, 0x43, +0xab, 0xda, 0x42, 0xf7, 0x36, 0xa7, 0xd7, 0x60, +0x9d, 0x97, 0x63, 0x74, 0x34, 0x37, 0x39, 0xb1, +0x9f, 0xba, 0xcf, 0x34, 0xd4, 0xc0, 0x36, 0x9c, +0x14, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0x6f, 0x94, 0xee, 0x70, 0x11, +0x70, 0xc9, 0x98, 0x13, 0x09, 0x3c, 0xfa, 0xee, +0x97, 0xc3, 0x7e, 0xfe, 0xe0, 0x70, 0xff, 0x6f, +0xae, 0xdc, 0xfb, 0x66, 0x93, 0xa3, 0x62, 0xf7, +0x47, 0x76, 0x69, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0xc8, 0x6b, 0x2a, +0xb9, 0x83, 0x6e, 0x3a, 0x87, 0xa7, 0x6e, 0x68, +0x6c, 0x6e, 0xa9, 0x7a, 0xf0, 0x27, 0xd0, 0x12, +0x48, 0xf1, 0x9b, 0x53, 0xa5, 0xae, 0x03, 0x66, +0x3e, 0xbf, 0x5b, 0x5d, 0x89, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x19, +0x69, 0xf8, 0x47, 0x11, 0x5c, 0x88, 0x5d, 0x98, +0xe4, 0x93, 0x8a, 0xd2, 0xbf, 0xa1, 0x45, 0xe7, +0xfc, 0xdf, 0x48, 0x0e, 0x05, 0x8b, 0x88, 0xc4, +0x74, 0x68, 0x72, 0x6b, 0xd7, 0x9c, 0x81, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0x75, 0xa4, 0x0c, 0xaf, 0x94, 0x5b, 0x96, +0x74, 0xfa, 0x51, 0x30, 0x95, 0x12, 0xc2, 0x74, +0x07, 0x1d, 0xc9, 0xae, 0x8b, 0x4c, 0x1b, 0x52, +0x48, 0x89, 0xfd, 0x03, 0x27, 0x9f, 0x29, 0x1c, +0xa9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0x9a, 0x82, 0x73, 0xb8, 0xb7, +0x13, 0xa6, 0x55, 0x15, 0x8a, 0x6c, 0x34, 0x7a, +0x32, 0x37, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xc9, 0xbe, 0x94, 0x79, +0xf3, 0xe9, 0xec, 0x9b, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0x6b, 0x86, 0x6a, +0xf0, 0xb8, 0xa0, 0x54, 0x90, 0x70, 0xa8, 0x0d, +0xb5, 0xd1, 0x67, 0x0f, 0x9a, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd4, 0x9c, +0xb3, 0x8d, 0xca, 0x6c, 0x0e, 0x53, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x89, +0x75, 0xde, 0x3d, 0xec, 0xd9, 0x35, 0x39, 0xea, +0xb3, 0x88, 0x62, 0x39, 0x7c, 0x9f, 0x47, 0x46, +0xb0, 0xd9, 0xa1, 0x8b, 0xd2, 0x56, 0xdc, 0x50, +0xde, 0x3e, 0xc6, 0x76, 0xb4, 0xb5, 0x42, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0x90, 0x3b, 0xb0, 0xcc, 0x2c, 0xee, 0xd1, +0x2d, 0xa9, 0xd2, 0x5a, 0xf7, 0xc6, 0xb4, 0xe6, +0x05, 0xe6, 0x89, 0x20, 0x7d, 0x37, 0x68, 0x99, +0x51, 0xd6, 0x7a, 0xd2, 0x24, 0x8e, 0x97, 0x23, +0x4f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0x0b, 0x2a, 0x48, 0xfa, 0x2c, +0xd2, 0xe1, 0x62, 0x80, 0x2e, 0x8b, 0x84, 0x41, +0x56, 0x16, 0xa2, 0xc9, 0x35, 0xc7, 0xa1, 0xae, +0x82, 0xe9, 0xfd, 0xe0, 0xdb, 0xe4, 0xb8, 0xf6, +0xf7, 0x9d, 0x7a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0x49, 0xb0, 0x6c, +0x49, 0x82, 0x25, 0x7e, 0xa5, 0x7b, 0x26, 0x6a, +0x16, 0x99, 0x32, 0x84, 0x4d, 0x17, 0x7d, 0x29, +0xaa, 0x19, 0x8f, 0x2b, 0xbe, 0xf6, 0xaf, 0x9a, +0x04, 0xea, 0x2b, 0x75, 0x4d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x16, +0xca, 0x9a, 0x5f, 0x25, 0x1b, 0x6e, 0x1f, 0xf9, +0xe2, 0x96, 0x4d, 0x9c, 0xae, 0xe9, 0xd3, 0x8a, +0xd5, 0xf0, 0x2f, 0xe2, 0xf6, 0xdf, 0x23, 0xeb, +0x1a, 0xe3, 0xfb, 0x1a, 0x80, 0x33, 0xb5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0x27, 0xcd, 0x47, 0x60, 0xce, 0xf5, 0x3c, +0x04, 0x3a, 0x1e, 0x2a, 0x83, 0xf3, 0x98, 0xe9, +0xf8, 0x35, 0x0a, 0xf5, 0xea, 0xb1, 0x47, 0x24, +0x0d, 0x12, 0x32, 0x5e, 0x92, 0x7b, 0x06, 0xdb, +0x82, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0x3d, 0x23, 0xda, 0x1c, 0x7a, +0x13, 0xa7, 0xd8, 0x97, 0x10, 0x8a, 0x74, 0x70, +0xa7, 0xd2, 0xd5, 0x1e, 0xa7, 0xa2, 0x50, 0xd6, +0x63, 0xde, 0xa4, 0x2e, 0xfa, 0x57, 0x28, 0x4b, +0x77, 0xc3, 0x1b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0x18, 0x96, 0xcc, +0xe0, 0x64, 0x50, 0x9a, 0xfb, 0xa4, 0x38, 0x73, +0x56, 0x0f, 0x4f, 0x51, 0xec, 0x82, 0xe3, 0xd5, +0x5d, 0x45, 0x00, 0x10, 0x6a, 0x76, 0xc7, 0x0a, +0xda, 0x4e, 0x06, 0xaa, 0x3f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0x10, +0xe9, 0x56, 0xb9, 0xe0, 0xfe, 0x96, 0x20, 0xf9, +0x9c, 0x56, 0x5b, 0x35, 0xb7, 0x43, 0x74, 0x8f, +0xac, 0xd8, 0xec, 0xea, 0x24, 0x0f, 0x11, 0xf1, +0xf0, 0x29, 0xe9, 0x8b, 0x00, 0x66, 0xcc, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0x99, 0xfa, 0x1d, 0xfd, 0xb3, 0xbf, 0xe7, +0x03, 0x9a, 0xe7, 0x38, 0x34, 0xc0, 0x26, 0x96, +0x22, 0x23, 0x87, 0x28, 0xc6, 0x46, 0xe8, 0x7b, +0x5e, 0x67, 0xa3, 0xf9, 0x06, 0xb2, 0x7d, 0x50, +0x8a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x5b, 0xbf, 0x72, 0x66, 0xf1, +0x21, 0xd6, 0x1d, 0xcf, 0x39, 0xef, 0x5d, 0x6f, +0x1f, 0x50, 0x8c, 0x69, 0xa0, 0xac, 0x15, 0xac, +0x28, 0x2c, 0xf1, 0xf5, 0x0d, 0x27, 0xd4, 0xf1, +0x64, 0x00, 0xf9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x69, 0xfa, 0x2a, +0x20, 0xeb, 0xdd, 0x4f, 0x4e, 0x39, 0xfc, 0xc5, +0x7f, 0x73, 0x69, 0x59, 0x8a, 0xed, 0xf2, 0x1e, +0xa4, 0xce, 0xa6, 0x7d, 0xbb, 0x6e, 0x4f, 0x10, +0x14, 0x95, 0x9d, 0x0a, 0xe3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0x7c, +0x5e, 0xd1, 0xf3, 0x4b, 0x45, 0x8c, 0x9b, 0x17, +0xff, 0xf1, 0xd2, 0x54, 0x21, 0x88, 0xec, 0xff, +0x32, 0x29, 0x10, 0xc1, 0x3d, 0x58, 0xf8, 0x68, +0x41, 0x89, 0xfb, 0xab, 0xfd, 0x1d, 0xa7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0x16, 0x55, 0x22, 0x93, 0xc1, 0x4b, 0x54, +0x42, 0x21, 0xe5, 0x29, 0x6d, 0xc9, 0xc7, 0x04, +0x77, 0x06, 0x54, 0xd1, 0xe2, 0xf3, 0xef, 0x55, +0xa2, 0x24, 0x3b, 0xc3, 0x3d, 0x54, 0x56, 0x4e, +0xf9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0xb3, 0x72, 0x52, 0xd4, 0xe3, +0x35, 0x45, 0xf3, 0x23, 0xcc, 0x94, 0x5f, 0xb0, +0x3d, 0x96, 0xe1, 0x0d, 0xcc, 0x3a, 0xab, 0x76, +0x01, 0x30, 0x20, 0x75, 0x6e, 0x11, 0x53, 0x73, +0x62, 0xdf, 0xc9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0xe3, 0xa6, 0x70, +0x4e, 0xde, 0xb0, 0x8a, 0x7c, 0x36, 0x00, 0xe0, +0xf1, 0x20, 0x0c, 0xbe, 0x15, 0xb8, 0x37, 0x99, +0x92, 0xea, 0xfc, 0x25, 0x51, 0xf7, 0x5e, 0x85, +0x10, 0x65, 0x66, 0x8c, 0x57, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0x09, +0xc0, 0xcb, 0x1e, 0xb6, 0xc4, 0xda, 0x4d, 0x96, +0xdc, 0xa5, 0xc8, 0x69, 0xd5, 0xe2, 0xda, 0x66, +0x50, 0x49, 0xbc, 0xd9, 0x4d, 0x59, 0x84, 0x34, +0xec, 0x55, 0xaa, 0x1b, 0x26, 0x12, 0x8f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0x51, 0x9f, 0xac, 0x51, 0x6c, 0x51, 0xbc, +0x73, 0xd0, 0xd0, 0xc8, 0x2b, 0x98, 0xca, 0x47, +0x9b, 0x1d, 0x50, 0x58, 0xc1, 0x6d, 0xe4, 0xdb, +0xfd, 0xd5, 0xed, 0x52, 0xc6, 0xe1, 0xd2, 0x66, +0xe4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0xae, 0x4d, 0x94, 0x7b, 0x62, +0xaa, 0x21, 0xe1, 0x88, 0x84, 0x79, 0x40, 0x81, +0xd4, 0x50, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x87, 0x42, 0xe6, 0x54, +0xb8, 0xf4, 0x56, 0x04, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0x22, 0x16, 0x8b, +0xbe, 0xcf, 0x2b, 0x0f, 0x53, 0x70, 0x59, 0x42, +0xf1, 0xed, 0xa6, 0x27, 0x68, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfd, +0x98, 0x83, 0x69, 0x67, 0x57, 0xb8, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x01, +0xbb, 0x6c, 0x94, 0x86, 0x2d, 0xe0, 0x72, 0x9c, +0xda, 0x93, 0xc4, 0x26, 0x67, 0x1f, 0xeb, 0xb4, +0x5b, 0x18, 0xe1, 0x21, 0xee, 0x0b, 0x96, 0xaf, +0x8e, 0xab, 0x6a, 0x4a, 0x69, 0x7c, 0x4a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0xb0, 0xcf, 0x82, 0x6b, 0xb7, 0xcd, 0x1b, +0x99, 0x27, 0xba, 0xd9, 0x8f, 0x09, 0x8b, 0x4f, +0x7c, 0xa2, 0x57, 0x5a, 0xb3, 0xb2, 0x2b, 0xa2, +0xd1, 0x2b, 0x23, 0xd6, 0x96, 0x98, 0x7b, 0x63, +0xd5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0xe3, 0x8f, 0x34, 0x2f, 0xd4, +0x03, 0xc2, 0x86, 0xd0, 0x9b, 0x81, 0x28, 0x88, +0x7c, 0xee, 0x3f, 0x4d, 0x01, 0x8a, 0x14, 0x4f, +0x28, 0x5e, 0xf6, 0x18, 0x34, 0xd2, 0xb0, 0xb3, +0x1a, 0x6d, 0x3b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0xcf, 0xdd, 0x16, +0xb2, 0x5b, 0xec, 0x22, 0xe3, 0x70, 0x15, 0x4c, +0x97, 0xae, 0x42, 0xa1, 0xf1, 0xd2, 0xa5, 0x6d, +0xcb, 0xbd, 0x73, 0x2d, 0x6c, 0x36, 0x8f, 0xf8, +0xc1, 0x47, 0x5e, 0x7f, 0xab, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0xb0, +0x02, 0xaa, 0x4e, 0x2e, 0xb7, 0x2b, 0x4e, 0x9d, +0xa5, 0xdc, 0x00, 0x0b, 0xec, 0x92, 0xfd, 0xe1, +0xba, 0x1e, 0xe1, 0x44, 0x22, 0x12, 0x2e, 0x78, +0x36, 0xeb, 0xac, 0xf4, 0x13, 0x06, 0xcc, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0xec, 0xff, 0x85, 0x5a, 0xa4, 0x3c, 0x37, +0xe1, 0x66, 0x67, 0xf1, 0xc6, 0x0c, 0xaf, 0x7b, +0xfe, 0xdd, 0xd4, 0xa3, 0xbb, 0x1c, 0x2d, 0x62, +0xfb, 0x40, 0x94, 0xc3, 0x0f, 0xc7, 0x04, 0x9d, +0x0b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0xb5, 0xaf, 0xdc, 0xce, 0xa5, +0x09, 0x62, 0x3e, 0x54, 0xd6, 0x6a, 0x03, 0x13, +0xa5, 0x04, 0x41, 0x27, 0xc1, 0x6e, 0x82, 0xe2, +0x26, 0xfc, 0x14, 0x3c, 0x0a, 0xf3, 0xc1, 0xc4, +0x34, 0x7f, 0x6f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0x28, 0x46, 0x20, +0x21, 0x90, 0xb1, 0x50, 0xbd, 0x64, 0xba, 0xe1, +0xba, 0x3c, 0x16, 0x3f, 0x4f, 0xef, 0xfe, 0x51, +0x7d, 0xab, 0x11, 0x57, 0xff, 0x28, 0x14, 0x77, +0xbd, 0x05, 0x44, 0x19, 0x89, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0x38, +0x29, 0x78, 0x96, 0xda, 0x50, 0xfb, 0x88, 0x37, +0xde, 0xf8, 0x57, 0x99, 0xd1, 0xab, 0xcf, 0x17, +0xee, 0x5d, 0x60, 0xea, 0x32, 0xcc, 0x3e, 0x39, +0xe6, 0xfc, 0xa5, 0x35, 0x95, 0xf5, 0x42, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0xe3, 0x59, 0x0b, 0xe5, 0x47, 0x26, 0x88, +0x04, 0xe6, 0xb2, 0x06, 0x46, 0x60, 0x86, 0x90, +0x83, 0xc3, 0x82, 0x86, 0x34, 0x5f, 0x4a, 0xfe, +0x8b, 0xf4, 0x85, 0xa4, 0x95, 0x75, 0xdf, 0x2d, +0x7a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0x37, 0xc4, 0x6c, 0x5a, 0xed, +0x44, 0x8d, 0xa9, 0xb4, 0xbd, 0xc4, 0x29, 0xbd, +0x15, 0x5e, 0xcc, 0x0e, 0x1f, 0x3f, 0x84, 0x1b, +0x58, 0xff, 0x31, 0x88, 0xb8, 0xcc, 0x5b, 0x97, +0x01, 0x8e, 0x88, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0xdc, 0xee, 0x19, +0x27, 0x11, 0x3b, 0x89, 0x1c, 0xfe, 0x2b, 0x94, +0xfa, 0x45, 0x98, 0x5a, 0x86, 0x34, 0x01, 0xee, +0xde, 0x73, 0x60, 0xd8, 0x79, 0x00, 0x2b, 0xdc, +0x1b, 0xaa, 0xf8, 0x36, 0x47, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0x9d, +0x4b, 0x69, 0x9b, 0x88, 0x7c, 0x3f, 0x1c, 0x5c, +0x88, 0xb2, 0x15, 0xbd, 0xc6, 0x3e, 0xfe, 0x07, +0xba, 0xce, 0xb3, 0x21, 0x02, 0x1f, 0xbf, 0xdc, +0x3b, 0xd0, 0xca, 0x01, 0x69, 0x4d, 0xfd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0x62, 0xd7, 0x24, 0xee, 0xf6, 0x46, 0xf0, +0xc5, 0x7d, 0xbf, 0xf3, 0x5d, 0x4d, 0x60, 0xea, +0xde, 0x96, 0xf0, 0x29, 0xc7, 0xde, 0xf9, 0x9e, +0x1a, 0x6e, 0x6a, 0x7c, 0x66, 0x28, 0x97, 0x9f, +0xc7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0x01, 0x76, 0x85, 0x19, 0x90, +0xe5, 0xdc, 0xdc, 0x79, 0x72, 0x94, 0xc3, 0xdd, +0x71, 0x51, 0xd0, 0x60, 0x7f, 0xbc, 0xe7, 0xd1, +0x02, 0x32, 0x1b, 0xc1, 0x5c, 0x7c, 0xf2, 0x92, +0x3d, 0x3c, 0x57, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0x4e, 0x7f, 0x82, +0xef, 0xc8, 0xb2, 0x2d, 0x6d, 0x2d, 0xf8, 0x0e, +0xa9, 0xb8, 0x28, 0xd6, 0xd9, 0xdd, 0xe9, 0x24, +0xa2, 0xcb, 0x32, 0x05, 0xb3, 0x4c, 0xff, 0x80, +0x40, 0x7f, 0xb2, 0xa6, 0x35, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0xef, +0x35, 0xc0, 0x7d, 0xd8, 0x06, 0x0a, 0x55, 0x7d, +0x14, 0x92, 0xab, 0x97, 0x98, 0x46, 0x90, 0xa7, +0x6b, 0x37, 0xb3, 0x01, 0xe9, 0xa7, 0x4d, 0x7a, +0x00, 0x48, 0x81, 0xf2, 0x29, 0xc9, 0xd3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0x1f, 0x89, 0x8b, 0x18, 0x99, 0x45, 0x8d, +0x7e, 0xc9, 0xa7, 0x4b, 0x1f, 0x4d, 0x1a, 0x46, +0x41, 0x55, 0x57, 0x40, 0x4d, 0x6d, 0x69, 0xa1, +0x55, 0x29, 0x6b, 0xbd, 0x7e, 0xa1, 0xaa, 0x1a, +0xc2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0xb6, 0x9c, 0xad, 0x40, 0xc7, +0xf5, 0xa1, 0x17, 0xcf, 0x53, 0xba, 0x87, 0xd0, +0x34, 0x01, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x7c, 0x2f, 0x13, 0x77, +0x10, 0x8d, 0x02, 0xc4, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0x9c, 0x00, 0x2d, +0x21, 0x44, 0x66, 0xdd, 0x8b, 0xd4, 0x7e, 0x87, +0x20, 0x6c, 0xa5, 0xa5, 0xed, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x83, +0x62, 0x0d, 0xe4, 0x1d, 0x5b, 0x72, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0xff, +0x07, 0x26, 0x36, 0xea, 0xf4, 0x18, 0x67, 0x1a, +0x0b, 0xdf, 0xc3, 0xbc, 0x2c, 0xc5, 0xbf, 0x29, +0xe7, 0x32, 0x76, 0x97, 0x36, 0x52, 0xe2, 0x8f, +0xcc, 0x8b, 0xa8, 0x0e, 0xf5, 0x62, 0x80, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x74, 0x49, 0x89, 0xb5, 0x84, 0x32, 0x71, +0x31, 0x2e, 0xe3, 0xce, 0xe1, 0xb3, 0x9c, 0x03, +0xeb, 0xbf, 0x1a, 0xf3, 0xb0, 0x08, 0xe2, 0x15, +0x7e, 0x2e, 0x28, 0xdf, 0xc2, 0x14, 0x1f, 0x67, +0xd0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0xac, 0x4f, 0xb8, 0x31, 0x16, +0xf1, 0x5b, 0xf1, 0x71, 0xea, 0x3e, 0x69, 0xaf, +0x73, 0xf4, 0x96, 0xc2, 0xef, 0x25, 0xa3, 0x02, +0xad, 0x21, 0x6b, 0x6c, 0x88, 0x68, 0x3c, 0x77, +0x44, 0xc3, 0x26, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0xd1, 0x37, 0xa0, +0xd8, 0xe3, 0x2c, 0x97, 0x57, 0xf9, 0xdf, 0x79, +0x8a, 0x4c, 0x9f, 0xcc, 0x7a, 0x64, 0xf0, 0xec, +0x11, 0x37, 0xba, 0xe9, 0x21, 0x49, 0x53, 0x20, +0x0a, 0x2c, 0x45, 0x20, 0x12, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0x63, +0xb3, 0x83, 0x1f, 0x12, 0x4a, 0x8f, 0x6a, 0xae, +0x53, 0xb5, 0x3f, 0x59, 0xb8, 0xe9, 0x30, 0x89, +0x5b, 0x6b, 0xf5, 0xc4, 0x9f, 0xf3, 0x93, 0x28, +0xca, 0xae, 0x1e, 0xef, 0x56, 0xd6, 0x55, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0x91, 0xfc, 0x55, 0x7c, 0x3d, 0xc4, 0x70, +0x35, 0x7b, 0x1e, 0x8e, 0xb3, 0x9f, 0x37, 0x88, +0x5e, 0x5f, 0xfc, 0xc9, 0x60, 0xf8, 0x94, 0x0a, +0xbe, 0xac, 0xed, 0x2a, 0x0c, 0x44, 0xf3, 0x30, +0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0x16, 0xcc, 0x15, 0x0f, 0x14, +0xd0, 0x72, 0x7a, 0xeb, 0xfb, 0xb7, 0xc6, 0xc2, +0x34, 0xf6, 0x07, 0x5a, 0xb2, 0x37, 0x72, 0x64, +0x2e, 0xa1, 0xf6, 0xd1, 0xd3, 0x39, 0x8a, 0xc2, +0x7d, 0xb7, 0xf4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0xf4, 0xb1, 0xd8, +0x8d, 0x87, 0x54, 0xe5, 0xdf, 0xe4, 0x1e, 0x4b, +0x97, 0xb2, 0x49, 0xaf, 0x00, 0x9b, 0x8d, 0x30, +0x3e, 0xbe, 0xe9, 0xa2, 0x23, 0x5f, 0x96, 0xea, +0x94, 0x40, 0xfa, 0x12, 0x91, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0x78, +0xb2, 0xb0, 0xee, 0xc5, 0x7e, 0x43, 0x5f, 0xd6, +0x76, 0x96, 0x14, 0xa3, 0x4d, 0x76, 0x33, 0x98, +0x4b, 0xd3, 0x98, 0x5a, 0x3d, 0xa3, 0x12, 0x82, +0xa4, 0xfd, 0x8b, 0x21, 0x50, 0xa2, 0x81, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0xcb, 0xdf, 0x09, 0xb7, 0x65, 0xd5, 0x8d, +0x39, 0x38, 0x96, 0x78, 0xfd, 0xe3, 0x62, 0xad, +0xb0, 0x28, 0x1b, 0xcb, 0x09, 0x38, 0xa2, 0x06, +0xb3, 0xc0, 0x7b, 0xc2, 0xd0, 0x8d, 0xd6, 0x0f, +0xc2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0x86, 0x77, 0x6f, 0xa9, 0x4b, +0xdc, 0x89, 0xcb, 0xa4, 0xd7, 0xbd, 0x01, 0xe3, +0x92, 0x7d, 0x5f, 0xc5, 0x8e, 0xeb, 0xcb, 0x66, +0xb7, 0xd0, 0x3e, 0xc2, 0xed, 0x6a, 0xa2, 0xa0, +0x19, 0x25, 0xfd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0x5f, 0x8d, 0x62, +0x66, 0xca, 0xe9, 0x43, 0x42, 0xcf, 0xb4, 0x1e, +0xf4, 0x9d, 0x5d, 0xd7, 0xf7, 0x1a, 0xf9, 0x28, +0x35, 0xbb, 0xcb, 0x7c, 0xeb, 0x43, 0xc9, 0x61, +0x1e, 0x92, 0x6b, 0x37, 0x02, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0x2a, +0x23, 0x7c, 0x8f, 0x98, 0x69, 0x09, 0xdd, 0x14, +0x34, 0x75, 0x90, 0x16, 0x9f, 0xfb, 0x02, 0x02, +0x98, 0x5b, 0xd1, 0x70, 0x00, 0x1d, 0xc1, 0x47, +0x88, 0x3d, 0x8f, 0x59, 0xd0, 0xfa, 0xf6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0xf2, 0xdd, 0x5f, 0xd7, 0x9e, 0x04, 0x05, +0x2c, 0x1a, 0xd0, 0x1a, 0xfc, 0x69, 0x43, 0x5f, +0x6f, 0xdf, 0x12, 0x71, 0xd8, 0x54, 0x8c, 0xf0, +0xc4, 0x45, 0x75, 0x05, 0xe0, 0xdf, 0x88, 0x48, +0xe8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0xbe, 0xb3, 0x75, 0x65, 0x56, +0xfd, 0x08, 0xf4, 0x69, 0x6d, 0xb7, 0x6a, 0xd8, +0x5d, 0xa4, 0x97, 0x84, 0xd4, 0xd5, 0x15, 0x03, +0x19, 0x27, 0x7e, 0x39, 0xcd, 0x3d, 0xf3, 0x04, +0x0d, 0xf0, 0x58, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x5c, 0x2f, 0x76, +0xc2, 0x6b, 0x82, 0x0e, 0x18, 0x78, 0xef, 0xaa, +0xd5, 0x71, 0xbf, 0x89, 0xb9, 0x9a, 0x17, 0x58, +0x8f, 0xdd, 0xab, 0x21, 0x12, 0xf8, 0xd3, 0x99, +0xfa, 0x13, 0x80, 0x5f, 0xe4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0xa5, +0x8f, 0xa0, 0xf2, 0xbf, 0x8b, 0x91, 0xb8, 0xcd, +0xdc, 0x75, 0xaa, 0x08, 0x58, 0x6f, 0x5f, 0xaf, +0x49, 0x97, 0x12, 0xac, 0x93, 0xf1, 0xce, 0xc2, +0x3b, 0x0f, 0x18, 0xb0, 0xfe, 0x79, 0xd7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0x1a, 0xe4, 0xe8, 0x3c, 0x96, 0xf8, 0xe0, +0x5a, 0x74, 0x0c, 0x91, 0x3d, 0xf9, 0x3e, 0x4c, +0x4c, 0x64, 0x45, 0x86, 0x1f, 0x5b, 0xc0, 0xb2, +0x24, 0x55, 0xf7, 0xe7, 0x52, 0xc0, 0xbc, 0xc1, +0x4b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0x2e, 0x49, 0x9e, 0x1a, 0x63, +0x36, 0x2f, 0x8c, 0x96, 0x5b, 0x36, 0x28, 0x16, +0x1c, 0x15, 0xb6, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x3c, 0x84, 0x13, 0x69, +0xe6, 0xe9, 0xff, 0x50, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0x58, 0x64, 0x65, +0x92, 0x62, 0xf0, 0x08, 0x80, 0x42, 0x52, 0x2c, +0xfb, 0x78, 0xbd, 0x3b, 0x24, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x94, +0xd8, 0x09, 0x96, 0x8f, 0x6f, 0x85, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0xa2, +0xe9, 0x15, 0xc1, 0x1a, 0x3c, 0x1e, 0x96, 0x8e, +0x26, 0x18, 0xba, 0xe8, 0x70, 0x3f, 0x24, 0x07, +0xee, 0x36, 0x71, 0x04, 0xa4, 0x8a, 0x6e, 0x37, +0xb8, 0xa6, 0x26, 0xcc, 0x01, 0x47, 0x39, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x5e, 0x54, 0x50, 0xd6, 0x0a, 0x4e, 0x3d, +0x7b, 0xf6, 0x8a, 0xc0, 0x91, 0xcb, 0x19, 0x91, +0xd2, 0x25, 0x85, 0x70, 0xc4, 0x8a, 0xf5, 0xb9, +0x42, 0xdc, 0xa6, 0x38, 0xb1, 0xe2, 0x96, 0xa6, +0x84, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0x24, 0xce, 0x33, 0x3c, 0x6a, +0x7c, 0xd5, 0x55, 0xc7, 0xbd, 0xe0, 0x10, 0x39, +0xad, 0x67, 0x9e, 0xb2, 0x0c, 0x82, 0x5e, 0x56, +0xff, 0x40, 0x0b, 0x63, 0x39, 0xe9, 0xc3, 0x3e, +0xf9, 0x5a, 0xed, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0x0b, 0x47, 0x50, +0x74, 0x3f, 0xed, 0xc4, 0x92, 0x79, 0xa7, 0xe8, +0xa3, 0x85, 0xfb, 0x84, 0xbf, 0x4d, 0x05, 0xb7, +0x36, 0x0b, 0x0a, 0xa4, 0x06, 0x24, 0x24, 0xd7, +0xae, 0xab, 0xb9, 0x7a, 0x67, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0x21, +0xb0, 0x22, 0x20, 0x0b, 0xdd, 0x77, 0x47, 0xbd, +0xa8, 0xf7, 0x06, 0xe2, 0x94, 0xe4, 0xbf, 0xc9, +0x6c, 0xe9, 0x46, 0x2d, 0x1b, 0x6a, 0xd8, 0xf5, +0x5f, 0xee, 0x3d, 0x1e, 0x27, 0xd1, 0x73, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0x2e, 0xff, 0x20, 0x80, 0xb2, 0x16, 0x23, +0xfa, 0xa4, 0x76, 0x78, 0x5c, 0x36, 0x36, 0x8f, +0x56, 0x40, 0xd1, 0x63, 0x43, 0xf8, 0x10, 0x17, +0x59, 0xc0, 0xf7, 0xe6, 0xed, 0xd8, 0x26, 0x5e, +0xd7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0x58, 0x36, 0xf8, 0xf6, 0x31, +0x4d, 0x3a, 0x09, 0x33, 0xbd, 0x02, 0xb4, 0x53, +0x7c, 0x3a, 0xee, 0xcd, 0x12, 0x0d, 0xc4, 0x51, +0xe3, 0x3f, 0x55, 0x34, 0xbc, 0x96, 0x9f, 0xea, +0x56, 0xeb, 0xe8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0x51, 0x27, 0x9d, +0x64, 0x55, 0xf4, 0xfa, 0x68, 0x49, 0x11, 0x2f, +0xa1, 0xab, 0xf5, 0xc6, 0x6f, 0x4d, 0xe3, 0x37, +0x49, 0xc5, 0xf4, 0xd4, 0x59, 0x27, 0xc2, 0x77, +0x67, 0x5e, 0x9a, 0x9c, 0x2f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0xcd, +0x95, 0x22, 0xcf, 0x49, 0xfc, 0x95, 0xef, 0xf4, +0xe2, 0x46, 0xff, 0x3c, 0xa8, 0x52, 0xb9, 0x1c, +0x9b, 0x68, 0xb9, 0x83, 0x49, 0x9e, 0x2e, 0x3f, +0x9a, 0x55, 0xf6, 0x2e, 0xad, 0x23, 0xcd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0xcd, 0x21, 0xe8, 0x03, 0x18, 0xdc, 0x4c, +0xec, 0x48, 0xf3, 0x75, 0x53, 0x9b, 0xd5, 0xff, +0x55, 0x0d, 0x04, 0xa6, 0x56, 0xbf, 0xd1, 0x1c, +0x2b, 0xad, 0x21, 0x78, 0x9e, 0xbb, 0x3c, 0x2a, +0xb5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0xbe, 0x99, 0xa9, 0x1a, 0xba, +0x54, 0x12, 0xc5, 0xac, 0x7c, 0x1a, 0x8b, 0x40, +0x4d, 0x95, 0xd1, 0xa6, 0xd0, 0x63, 0x9f, 0x7e, +0x68, 0xed, 0xc3, 0x74, 0xe3, 0xa2, 0x89, 0xda, +0xe2, 0xc8, 0xe7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0x3a, 0x42, 0x3f, +0x0b, 0x42, 0xc2, 0x24, 0xaf, 0x7f, 0x58, 0x8f, +0x6d, 0x54, 0xd3, 0xc7, 0x16, 0x61, 0xbc, 0x00, +0x3b, 0x8e, 0xd9, 0x4b, 0x48, 0x59, 0xc2, 0x76, +0x9b, 0x53, 0x0f, 0xd4, 0xcc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0x87, +0x08, 0x80, 0x5a, 0xbb, 0x16, 0x5c, 0xe8, 0x20, +0x97, 0xcb, 0x67, 0x38, 0x11, 0xe7, 0xf7, 0x9a, +0x84, 0x8f, 0x7a, 0x86, 0x41, 0x98, 0x43, 0x20, +0x7a, 0xf4, 0xcf, 0x1d, 0x29, 0x56, 0x4c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0x99, 0x65, 0x52, 0xef, 0x47, 0x5c, 0x55, +0x1b, 0x9d, 0xae, 0x29, 0xc5, 0xe5, 0xad, 0x74, +0x2f, 0x63, 0xd0, 0x7a, 0x44, 0x6e, 0x7e, 0x19, +0xb5, 0x0a, 0xa6, 0x24, 0x97, 0x98, 0x3d, 0x07, +0x0a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0x11, 0x85, 0xf7, 0xb6, 0x8f, +0xfc, 0xa9, 0x11, 0x52, 0x98, 0xa3, 0x21, 0xb3, +0xad, 0x26, 0xb9, 0x63, 0x26, 0x46, 0xc9, 0x7c, +0xaf, 0x67, 0x66, 0x5e, 0x7d, 0x3a, 0x25, 0x48, +0xfb, 0x5e, 0xf9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0x4a, 0xd6, 0x62, +0x51, 0x5f, 0xe4, 0xf7, 0x5f, 0x2b, 0xf2, 0xc9, +0xf2, 0xf9, 0x49, 0xe2, 0x50, 0x9b, 0xb0, 0x17, +0x96, 0x2d, 0xb2, 0xa4, 0xec, 0x29, 0x07, 0xd5, +0xe4, 0xf1, 0x2a, 0x1a, 0x85, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0xe1, +0x13, 0x07, 0x4f, 0x9e, 0xbe, 0xb2, 0x42, 0x06, +0x25, 0xbb, 0x0a, 0x49, 0x11, 0x75, 0xbd, 0x41, +0x50, 0x55, 0x4a, 0x11, 0x36, 0x5b, 0x0c, 0x9b, +0xae, 0x1c, 0xbb, 0x81, 0x74, 0xff, 0x35, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0xe7, 0x92, 0x4b, 0x3e, 0xa6, 0x72, 0x3a, +0xab, 0x63, 0x55, 0x2d, 0x38, 0x6d, 0x9e, 0xc6, +0x0c, 0x49, 0x83, 0x97, 0xb1, 0xe2, 0x3a, 0xf6, +0xaf, 0xc7, 0x86, 0x35, 0xd2, 0x7c, 0x21, 0x91, +0xf1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0xd7, 0xae, 0x3c, 0x37, 0x2d, +0xbc, 0x14, 0x97, 0x43, 0xc5, 0x21, 0xb1, 0x16, +0xfe, 0xbc, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x5b, 0xa9, 0x2f, 0x88, +0xad, 0x51, 0x3c, 0x02, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x34, 0x71, 0x9e, +0x47, 0x6c, 0xb2, 0xeb, 0xdc, 0x4b, 0x88, 0x1e, +0x51, 0xb5, 0x68, 0x9d, 0x62, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xf0, +0x72, 0xc5, 0x3f, 0x7a, 0x47, 0x1d, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0x9c, +0xaf, 0x33, 0xdc, 0x92, 0x1c, 0x6c, 0xc4, 0x75, +0xab, 0xdb, 0xd4, 0x64, 0xf8, 0x66, 0x45, 0xd6, +0xf7, 0x73, 0xd5, 0x4f, 0x9f, 0xaa, 0xbf, 0x65, +0xd5, 0x97, 0x97, 0xf7, 0x0c, 0x09, 0x4d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0x6b, 0x7b, 0x6b, 0xfd, 0xa0, 0xd1, 0xc6, +0x22, 0x82, 0xc5, 0xe5, 0xfd, 0xe5, 0xb7, 0xc9, +0x46, 0x3a, 0x49, 0xf8, 0xeb, 0x71, 0xa5, 0x23, +0xbe, 0xd7, 0x10, 0x01, 0xd2, 0x75, 0xfd, 0x7c, +0xc7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0xdd, 0x47, 0x9e, 0x4f, 0xdd, +0xf3, 0x9c, 0xa7, 0xd1, 0xe4, 0x24, 0x50, 0xcb, +0xde, 0x01, 0xe1, 0x35, 0xa7, 0x77, 0xfa, 0x9c, +0xce, 0x82, 0x09, 0x73, 0x56, 0xd3, 0x5b, 0xee, +0xbb, 0x47, 0x82, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0xae, 0x9d, 0x77, +0x67, 0xc5, 0xce, 0x57, 0xef, 0x81, 0xac, 0x19, +0x9f, 0x45, 0x8c, 0x46, 0xd7, 0x3f, 0xc2, 0xfa, +0xcb, 0xa7, 0xcc, 0xc6, 0x44, 0x4f, 0xfc, 0x74, +0x5c, 0x40, 0xcd, 0x89, 0x16, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0xda, +0xd7, 0x6a, 0xef, 0x5a, 0x0b, 0x44, 0x88, 0xad, +0xd9, 0x5c, 0x4a, 0x8a, 0x50, 0x73, 0x3b, 0x0f, +0x05, 0x1f, 0x25, 0x73, 0xf3, 0xb3, 0x6a, 0x83, +0x82, 0x54, 0xbf, 0x11, 0xbd, 0x3f, 0x56, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0xec, 0xf6, 0x9b, 0xed, 0x63, 0x6b, 0x62, +0xd5, 0x23, 0xca, 0xc3, 0x13, 0xd9, 0x4c, 0xda, +0x86, 0xd0, 0x0e, 0x37, 0x48, 0x7d, 0xfc, 0x94, +0xf3, 0xb9, 0xba, 0x2c, 0xca, 0x1b, 0x48, 0xed, +0x0d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x3a, 0x5a, 0x1e, 0xdc, 0x52, +0xdb, 0x90, 0x3a, 0x7e, 0x14, 0x7d, 0x4c, 0x12, +0x1a, 0xe8, 0xaf, 0xfc, 0xd9, 0x5c, 0xdb, 0xe7, +0x29, 0xec, 0xe1, 0xe3, 0x3c, 0x3b, 0x42, 0xa0, +0x18, 0xbd, 0x22, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0x9a, 0x71, 0x8d, +0xcb, 0x12, 0x8b, 0x0f, 0x8f, 0x36, 0x36, 0xeb, +0x26, 0xd4, 0xd0, 0x0a, 0x81, 0x97, 0x1e, 0x77, +0x94, 0x72, 0xd0, 0xb4, 0x06, 0xff, 0xff, 0xa7, +0x9b, 0xf4, 0x45, 0xa2, 0x4b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0x85, +0x79, 0x74, 0xcd, 0xf3, 0xdc, 0x95, 0x22, 0x1c, +0xde, 0xca, 0xed, 0x02, 0x5e, 0xe4, 0xe7, 0xb0, +0x3b, 0xb5, 0x58, 0x34, 0x6f, 0x95, 0xf9, 0xe9, +0xa3, 0x64, 0x19, 0x7d, 0xc6, 0x7a, 0x80, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0x20, 0x73, 0xc8, 0xd1, 0xa6, 0x80, 0xf0, +0xc7, 0xd4, 0x33, 0xb5, 0xdc, 0x0c, 0xc6, 0x26, +0x3a, 0x50, 0xd5, 0x0c, 0xc7, 0xe4, 0x21, 0x9d, +0xae, 0xdf, 0x5f, 0x59, 0x4d, 0x78, 0xac, 0x1e, +0xa9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0x86, 0x76, 0xe4, 0xc7, 0x8f, +0x2c, 0x4d, 0x27, 0x43, 0x18, 0xa2, 0xae, 0xb4, +0xb9, 0xea, 0x7b, 0x4e, 0x7d, 0x0a, 0xe2, 0x9a, +0x4b, 0x7e, 0x93, 0x1b, 0x94, 0x1a, 0x05, 0xfc, +0xca, 0x93, 0xec, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0x81, 0x1e, 0x38, +0x59, 0x33, 0xab, 0x20, 0xd0, 0xfc, 0xcd, 0x87, +0x1c, 0x11, 0x0d, 0xea, 0xeb, 0x71, 0x4a, 0x52, +0x0c, 0x92, 0xa6, 0xf0, 0xd4, 0x19, 0xda, 0xe6, +0x2d, 0x85, 0x0d, 0x58, 0x83, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0x35, +0x6e, 0xb5, 0x13, 0x6c, 0xb7, 0x75, 0x80, 0x38, +0x08, 0x9f, 0x0b, 0xce, 0x53, 0xf8, 0x55, 0xe8, +0x80, 0xd9, 0x9e, 0x26, 0x38, 0x51, 0x97, 0xde, +0x3f, 0x3c, 0x43, 0x9b, 0x82, 0x86, 0xf2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x95, 0x5a, 0x3a, 0x1d, 0x1a, 0x35, 0x34, +0x55, 0x91, 0x4c, 0x9d, 0x5e, 0x17, 0x4a, 0x32, +0xbb, 0x81, 0x23, 0x77, 0x14, 0x9d, 0x73, 0xcd, +0x0a, 0x6d, 0x8d, 0xf2, 0xd2, 0x9b, 0x20, 0x42, +0x22, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0x2b, 0xb4, 0xf9, 0x98, 0xbf, +0x01, 0x1d, 0xac, 0x57, 0x3e, 0x54, 0xff, 0x46, +0xcf, 0x7b, 0xdc, 0xa4, 0x3d, 0x5c, 0x9f, 0x03, +0x48, 0x04, 0x32, 0x9a, 0x68, 0xae, 0x29, 0xbc, +0xd6, 0x51, 0xf9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0xfd, 0x8b, 0x48, +0xdb, 0x30, 0x9b, 0x1b, 0x8c, 0x7e, 0x5b, 0x27, +0x61, 0x01, 0xa3, 0x90, 0xa8, 0xfe, 0x86, 0x6e, +0x72, 0x74, 0xb7, 0x71, 0x1b, 0xa5, 0xd1, 0xf2, +0xe2, 0x32, 0x02, 0xc9, 0x7c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0xee, +0x97, 0x0d, 0xcf, 0x08, 0xd5, 0x93, 0xd2, 0x5a, +0x5d, 0x9a, 0x0d, 0x82, 0xa3, 0xdd, 0xe5, 0x00, +0xec, 0xb0, 0xb7, 0x98, 0xc7, 0x9b, 0xbb, 0x48, +0xda, 0x5d, 0x8d, 0x11, 0xfa, 0x16, 0x70, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0xcb, 0xf7, 0xbd, 0x05, 0xad, 0xbf, 0x26, +0x38, 0x02, 0xdf, 0xec, 0xb6, 0xfd, 0x87, 0x53, +0x11, 0x65, 0x26, 0x50, 0xda, 0xca, 0xa9, 0x92, +0x46, 0x4c, 0x42, 0xf8, 0x98, 0x08, 0x0d, 0xe5, +0x10, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0x32, 0x02, 0x73, 0x9e, 0xda, +0xf7, 0xe9, 0x1e, 0x81, 0x27, 0xee, 0x26, 0xef, +0x40, 0x60, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x84, 0x47, 0xc9, 0x1f, +0x58, 0x1a, 0x15, 0xba, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0xd7, 0x20, 0xc5, +0x53, 0x1b, 0x86, 0x25, 0x25, 0x9f, 0x98, 0xf4, +0xde, 0xc8, 0xf4, 0x1f, 0x74, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf9, 0xbe, +0x27, 0x86, 0xa0, 0xde, 0x2c, 0xb0, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0x7c, +0x1b, 0x78, 0x05, 0x4a, 0x68, 0xcc, 0xe1, 0xe8, +0x96, 0x67, 0xa2, 0xec, 0x2f, 0xf1, 0xc9, 0xb9, +0x83, 0x7f, 0x47, 0x99, 0x23, 0x66, 0xce, 0xd4, +0x1e, 0xa8, 0x9b, 0x3f, 0x27, 0x19, 0x6d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0x4f, 0x43, 0xf3, 0xf0, 0x95, 0x97, 0x5d, +0x2e, 0x60, 0xeb, 0xb9, 0x56, 0x57, 0x50, 0x0b, +0xba, 0xe2, 0x9f, 0xd1, 0x77, 0xb8, 0x9f, 0xe6, +0xc9, 0xaa, 0xaf, 0xe8, 0x53, 0xd1, 0x34, 0xec, +0x46, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0x6a, 0x18, 0xd7, 0xdd, 0x68, +0x7d, 0xf5, 0x92, 0xf8, 0x7e, 0xce, 0x68, 0x19, +0xaf, 0x29, 0x7c, 0x5e, 0xd9, 0x9a, 0x06, 0x05, +0x81, 0xf5, 0x05, 0x46, 0x99, 0xdc, 0x01, 0x6e, +0xb8, 0x80, 0x53, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0xf5, 0x89, 0x97, +0xf4, 0x5d, 0xa6, 0xb6, 0x7d, 0x8f, 0x4c, 0x31, +0x30, 0x09, 0x6b, 0x31, 0xfe, 0x8d, 0x1b, 0x80, +0xad, 0xac, 0x6a, 0x37, 0x69, 0xd4, 0xfb, 0x49, +0x17, 0x7d, 0xa8, 0x78, 0xa5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0xf8, +0x3a, 0xe6, 0x95, 0x6f, 0x47, 0x0a, 0xd2, 0xe2, +0xb9, 0x3b, 0xff, 0xe7, 0x0c, 0x1f, 0xa7, 0xbf, +0x86, 0xc0, 0x72, 0x17, 0xdb, 0x47, 0x47, 0x72, +0x78, 0x14, 0x8c, 0x69, 0xe5, 0xb7, 0x17, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0x85, 0xbe, 0xa1, 0x92, 0x9d, 0xb9, 0x27, +0xff, 0x04, 0xad, 0x6b, 0x5b, 0x73, 0xbc, 0x7f, +0xaa, 0x2c, 0x1a, 0x94, 0x02, 0xd8, 0x47, 0x37, +0xfb, 0x8b, 0xf0, 0xb7, 0x39, 0x3e, 0x2c, 0x64, +0x1c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0xed, 0x42, 0xbf, 0xe0, 0xdb, +0x28, 0x5a, 0xa3, 0x69, 0xe3, 0x0a, 0x1d, 0xc0, +0xdc, 0xc4, 0x05, 0xba, 0xbb, 0xca, 0xe3, 0xfa, +0x3e, 0x84, 0x95, 0x3f, 0x3a, 0xb9, 0xf4, 0x7b, +0x10, 0xfc, 0xa1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0xf5, 0x80, 0xd2, +0x7b, 0x61, 0x65, 0x94, 0x72, 0x60, 0x20, 0xb6, +0xc8, 0xb5, 0xcb, 0xd5, 0xe5, 0x59, 0x72, 0x18, +0xbf, 0x9f, 0xa4, 0x1e, 0xc8, 0x8e, 0xcb, 0x86, +0xd1, 0xa2, 0x77, 0x99, 0x81, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0x4a, +0xc4, 0x58, 0xe9, 0xd7, 0x44, 0x6b, 0xc1, 0x83, +0x7a, 0xcc, 0x26, 0xed, 0x24, 0x09, 0xaa, 0xdf, +0x41, 0xf9, 0x05, 0x2d, 0xc3, 0x67, 0x9e, 0xdf, +0xe9, 0x3b, 0x8a, 0x0f, 0xda, 0xa0, 0x61, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0x56, 0xa9, 0xc8, 0x53, 0x05, 0xb8, 0x82, +0xf1, 0xb6, 0x1d, 0x47, 0x13, 0x41, 0xaa, 0xb4, +0x78, 0xd5, 0x07, 0x6f, 0xdf, 0x27, 0x1d, 0xc9, +0x68, 0x3b, 0xd0, 0x32, 0x4e, 0x83, 0x19, 0x61, +0x15, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0x5f, 0x8f, 0x0c, 0xde, 0xb1, +0x8b, 0x80, 0xce, 0x8a, 0x13, 0xea, 0x61, 0x22, +0x04, 0x6a, 0xba, 0xfe, 0x05, 0x79, 0x32, 0x85, +0xff, 0x0f, 0x39, 0x7e, 0xa2, 0xed, 0x36, 0x06, +0x14, 0xc4, 0x3b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0x4f, 0x36, 0x13, +0xe5, 0xea, 0xc5, 0xad, 0xc2, 0x4b, 0x51, 0x84, +0x60, 0x58, 0x3e, 0xa7, 0x77, 0xe7, 0xed, 0x75, +0xe7, 0xe1, 0x7d, 0x47, 0xf5, 0xab, 0xbd, 0x06, +0xdb, 0x29, 0x6a, 0x44, 0xc9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x2c, +0x90, 0x97, 0x6f, 0xc3, 0x30, 0x9f, 0xfa, 0x36, +0xde, 0x09, 0x46, 0x32, 0x6f, 0x10, 0x99, 0xc2, +0x84, 0x25, 0xbd, 0x90, 0xb5, 0x23, 0xae, 0x0d, +0x94, 0xdb, 0x5a, 0x77, 0xa3, 0xc7, 0x6e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0xed, 0xd9, 0x64, 0xfa, 0x3a, 0x3d, 0xb2, +0xeb, 0xfa, 0x5a, 0xf5, 0x54, 0x82, 0x7b, 0x5c, +0x32, 0x36, 0xd1, 0xf4, 0x7d, 0x07, 0x26, 0xdd, +0x88, 0xf3, 0x0f, 0x37, 0xbd, 0x88, 0x86, 0xdb, +0x02, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0x2c, 0x03, 0x39, 0x8f, 0xf8, +0x72, 0xcd, 0x4d, 0x52, 0x6e, 0x8f, 0xaf, 0x0c, +0xff, 0x90, 0x87, 0x77, 0xe6, 0xee, 0xb3, 0x31, +0x58, 0xe6, 0x13, 0x3a, 0x88, 0x5c, 0x65, 0xce, +0x8b, 0xab, 0x8f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0xd0, 0x43, 0xa5, +0x1e, 0x99, 0x62, 0x3e, 0xa9, 0x29, 0xa5, 0xd2, +0x35, 0xc8, 0xc4, 0x1c, 0x01, 0x38, 0x76, 0x60, +0xfb, 0xa6, 0x82, 0xec, 0xa1, 0x57, 0x89, 0x11, +0x28, 0x1c, 0x91, 0x25, 0x9f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0x9b, +0x4b, 0x26, 0x45, 0x1d, 0x1e, 0x18, 0xc3, 0x6d, +0x21, 0xe7, 0xc9, 0x2e, 0xb0, 0xd6, 0xfa, 0xd8, +0x4a, 0xa1, 0x0c, 0x0d, 0xaf, 0xc7, 0xd8, 0x1c, +0x49, 0xbe, 0xe8, 0x04, 0x16, 0xa4, 0xfc, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0xc0, 0x9d, 0xeb, 0xea, 0x58, 0x29, 0xc6, +0xcb, 0x2d, 0x2e, 0x99, 0xbf, 0x02, 0x31, 0xee, +0x8d, 0x62, 0x54, 0xd3, 0x34, 0xa4, 0xcf, 0xa4, +0xdd, 0x64, 0x93, 0x3b, 0x5f, 0x8b, 0x0f, 0x55, +0x49, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0xe2, 0x5f, 0x4f, 0x75, 0xcf, +0xd2, 0x8f, 0x3b, 0x6e, 0xee, 0x12, 0x0e, 0xec, +0xa2, 0xb9, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xc2, 0xe3, 0xb4, 0xd7, +0x3b, 0xeb, 0x90, 0x27, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0xaa, 0x39, 0x7f, +0x83, 0x77, 0x8c, 0x4b, 0xfc, 0x8f, 0xc2, 0x62, +0xba, 0x36, 0xb9, 0x5a, 0xa0, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x18, +0xfd, 0xc4, 0xbb, 0xfe, 0xbb, 0xdd, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0x1a, +0x05, 0xe1, 0xe6, 0x47, 0xc6, 0x5e, 0xdb, 0xea, +0x60, 0x34, 0x07, 0x20, 0x31, 0xc0, 0x84, 0x10, +0xb9, 0xfa, 0xc9, 0x7b, 0xfa, 0x3d, 0x49, 0x90, +0xe8, 0xf2, 0x58, 0x83, 0x19, 0xca, 0xef, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0x32, 0x25, 0x99, 0xf6, 0xb2, 0x55, 0x73, +0x4c, 0xee, 0xad, 0xda, 0x73, 0x85, 0x2f, 0x08, +0xc6, 0x65, 0x08, 0x7a, 0xcd, 0x13, 0x32, 0xf7, +0xa5, 0x62, 0xfa, 0x12, 0x2a, 0x0c, 0x6e, 0x86, +0xfc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0x98, 0xbb, 0xeb, 0x4f, 0x95, +0xde, 0x72, 0xe0, 0x4c, 0x77, 0xfd, 0x9e, 0xb8, +0x2e, 0xf5, 0xcb, 0x49, 0x51, 0xf7, 0xbf, 0xb6, +0xed, 0x76, 0xb1, 0x06, 0xa5, 0xe8, 0xf2, 0x8c, +0x35, 0xd2, 0x86, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x25, 0x47, 0xef, +0xd9, 0x45, 0xae, 0x16, 0xde, 0x3a, 0x20, 0xeb, +0xed, 0x05, 0x45, 0xd0, 0x55, 0x5b, 0x04, 0x24, +0xa0, 0x16, 0xc4, 0xa6, 0xa3, 0x26, 0xb3, 0x37, +0x83, 0x15, 0x66, 0x59, 0x37, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0x1c, +0xcb, 0x79, 0x79, 0x97, 0xe4, 0x58, 0xcc, 0xc2, +0xfd, 0xe9, 0x8e, 0x03, 0xbd, 0xc0, 0x54, 0xab, +0x39, 0x55, 0x82, 0x45, 0x0f, 0x6b, 0xe2, 0x4f, +0x17, 0x93, 0xb7, 0x0e, 0x5b, 0x5e, 0xef, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0x54, 0xc4, 0x8d, 0x9a, 0xc3, 0x1f, 0xbd, +0x03, 0x6d, 0x27, 0x1a, 0x19, 0xa5, 0xad, 0x82, +0xba, 0xd9, 0x11, 0x79, 0x88, 0x28, 0xa1, 0xd8, +0xe3, 0xf0, 0x14, 0x0f, 0x65, 0x31, 0xa3, 0x2f, +0x49, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0x03, 0x98, 0xe1, 0xfd, 0x9a, +0xe2, 0x19, 0x88, 0x8e, 0xa5, 0x86, 0x60, 0x6a, +0xcd, 0x58, 0xa0, 0x63, 0x77, 0x05, 0x32, 0x41, +0x28, 0xf6, 0xae, 0x70, 0x4b, 0x35, 0xb8, 0x3d, +0x8f, 0x76, 0xe8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0x31, 0x9f, 0x1c, +0xe7, 0x25, 0x31, 0x53, 0x40, 0xce, 0x00, 0x95, +0x5a, 0xfd, 0x45, 0x70, 0x31, 0xc9, 0x5c, 0x23, +0x99, 0x31, 0xa6, 0xa4, 0x45, 0x25, 0x21, 0x63, +0xde, 0x57, 0xeb, 0x55, 0xf7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0xd6, +0xd8, 0x68, 0xce, 0x3f, 0xd9, 0x9d, 0xff, 0x80, +0xd9, 0x61, 0x16, 0x2e, 0xf1, 0xbe, 0x7f, 0xa0, +0x3f, 0xd2, 0xda, 0xac, 0xfa, 0xfa, 0xf7, 0x52, +0x05, 0x43, 0x0f, 0x47, 0x91, 0x1c, 0x44, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0xba, 0x20, 0xbe, 0x37, 0x3a, 0x65, 0x79, +0xac, 0xa4, 0x26, 0x6f, 0x3e, 0x72, 0xc5, 0xda, +0x51, 0xd8, 0x19, 0x51, 0x21, 0xb3, 0xb1, 0x3c, +0xa3, 0xe6, 0x6f, 0x93, 0x37, 0x54, 0xc2, 0x0e, +0x08, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0xab, 0x1e, 0x4f, 0x0b, 0xd5, +0xb1, 0xf6, 0x70, 0xc2, 0xf3, 0x5b, 0x58, 0x8b, +0x36, 0x43, 0x9d, 0x60, 0xbe, 0xfb, 0x96, 0x22, +0x7c, 0x34, 0x67, 0x72, 0x79, 0xb5, 0xd2, 0x09, +0x14, 0xa3, 0xd8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0x21, 0x26, 0xfb, +0x64, 0x69, 0x42, 0x39, 0xa4, 0x42, 0xcc, 0x47, +0x05, 0x56, 0x0e, 0xb3, 0x46, 0x67, 0x19, 0xbc, +0x2a, 0xa5, 0x7f, 0xc5, 0x2a, 0x31, 0xc7, 0x2c, +0x9a, 0xcf, 0x5a, 0xdd, 0x39, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0xf9, +0x41, 0x8c, 0xbc, 0x94, 0x89, 0xd9, 0xeb, 0x54, +0xa5, 0x93, 0xb0, 0x6b, 0xe6, 0x83, 0x9a, 0x1b, +0x8f, 0x84, 0x0c, 0xf9, 0x72, 0x0f, 0xaf, 0xfc, +0x89, 0xe4, 0x0e, 0xcc, 0xdc, 0x71, 0x40, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0x5a, 0x73, 0x8a, 0xa4, 0x8d, 0x91, 0xe3, +0xcb, 0xbb, 0xe1, 0xc4, 0x8e, 0xd2, 0xc7, 0x0f, +0x18, 0x76, 0x4c, 0xc5, 0xe2, 0x35, 0x88, 0x81, +0x47, 0x75, 0xf8, 0xce, 0x06, 0x3d, 0x70, 0x97, +0x89, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0xc6, 0xc3, 0x87, 0x1c, 0x36, +0xde, 0x71, 0x88, 0xc6, 0x35, 0x9f, 0x93, 0xd0, +0x06, 0xc6, 0xe1, 0x11, 0x40, 0x89, 0x9c, 0x5c, +0xaf, 0xbb, 0x11, 0x8c, 0x71, 0xff, 0xcc, 0xf3, +0x79, 0xcf, 0xfa, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0xb0, 0x24, 0xb3, +0x60, 0x20, 0x86, 0x56, 0x99, 0xfe, 0x31, 0xb5, +0x57, 0xd2, 0x77, 0xa9, 0xdf, 0x5c, 0xef, 0xd9, +0x53, 0x3f, 0x3b, 0xac, 0x7a, 0xcb, 0x01, 0xc5, +0xf4, 0xa9, 0x4e, 0x1e, 0x00, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0xc1, +0x8b, 0x8f, 0x37, 0xb7, 0x99, 0xac, 0x96, 0xeb, +0xd2, 0x8b, 0x4e, 0xf2, 0x49, 0xd2, 0x17, 0xe1, +0xca, 0x9a, 0x08, 0x5e, 0xd6, 0xf4, 0xa2, 0x9c, +0xe1, 0x10, 0xdd, 0x60, 0x6b, 0xeb, 0xae, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0xe5, 0x2b, 0xad, 0x0c, 0x3b, 0x4b, 0x5b, +0x96, 0x0d, 0x3f, 0x86, 0x56, 0x49, 0x49, 0x3d, +0xc1, 0x43, 0x6d, 0xea, 0x61, 0xed, 0x19, 0x61, +0x15, 0x13, 0x1c, 0xd3, 0x49, 0xd9, 0xf5, 0x28, +0x11, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0x04, 0x67, 0xfc, 0x15, 0x23, +0xf7, 0xfa, 0xda, 0x7c, 0x69, 0x56, 0x76, 0x2c, +0x53, 0xe6, 0xeb, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x7f, 0x10, 0x3c, 0xcf, +0x38, 0x05, 0x46, 0x4b, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0x87, 0x04, 0x4d, +0x89, 0x69, 0xc3, 0x15, 0x70, 0xb1, 0xeb, 0x58, +0xf8, 0xd3, 0xc8, 0x2c, 0xa7, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x98, +0xe9, 0x16, 0x37, 0x88, 0xad, 0xda, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0x30, +0x93, 0x86, 0xb7, 0xb1, 0xb6, 0x2f, 0xb9, 0xc7, +0x4d, 0xd5, 0x07, 0x38, 0x7f, 0x6d, 0x03, 0xd4, +0xaa, 0x2e, 0xc7, 0x55, 0x83, 0x95, 0xda, 0xc9, +0xd1, 0x45, 0xb0, 0x6b, 0x97, 0x31, 0xfa, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0x5d, 0x44, 0x3a, 0x98, 0x8a, 0xa5, 0x80, +0x2d, 0x22, 0x35, 0x47, 0xa0, 0x98, 0x00, 0xef, +0xe9, 0x47, 0xf3, 0xff, 0x93, 0x03, 0x1e, 0x6c, +0x9c, 0x12, 0x41, 0x25, 0xc9, 0x33, 0x53, 0xc8, +0xc5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0xa7, 0x1a, 0x18, 0x02, 0x1a, +0x3e, 0xfe, 0x6e, 0x51, 0xdc, 0x6f, 0x17, 0x31, +0x53, 0x1b, 0xf6, 0xaa, 0xfc, 0x79, 0x51, 0x8a, +0xcb, 0x74, 0x8e, 0xd0, 0x18, 0xbe, 0x1e, 0x3b, +0xe0, 0x99, 0xb8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x8e, 0x0b, 0x85, +0x38, 0xc8, 0x08, 0x5c, 0xa5, 0x27, 0x38, 0x4b, +0x7b, 0xfd, 0xb1, 0xa2, 0x69, 0xcf, 0x5c, 0x80, +0x28, 0xc9, 0x63, 0x76, 0xa3, 0x4b, 0x8d, 0x1d, +0x1d, 0xea, 0x8a, 0x6c, 0xf3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x4d, +0x2f, 0xb1, 0x05, 0xcb, 0xdf, 0xf9, 0xec, 0x23, +0x62, 0xf5, 0x89, 0x11, 0x68, 0xe7, 0x58, 0x5a, +0x67, 0x63, 0x28, 0x76, 0x5e, 0xd0, 0xc4, 0x8f, +0xdb, 0x31, 0x75, 0xfa, 0xff, 0x37, 0x25, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x05, 0xff, 0x84, 0x01, 0x5d, 0x41, 0xbe, +0xb1, 0xb4, 0xeb, 0x99, 0x2f, 0x06, 0x0a, 0x00, +0x7e, 0x81, 0x8b, 0x51, 0x72, 0x01, 0xd0, 0x78, +0xb9, 0x91, 0x71, 0x1e, 0xd0, 0x85, 0x03, 0xb7, +0xf7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0xef, 0xde, 0xd5, 0x2e, 0xeb, +0x46, 0xba, 0x3d, 0x32, 0x8c, 0xaf, 0x28, 0xf8, +0x76, 0x7a, 0xea, 0x64, 0x5c, 0x96, 0x93, 0x36, +0xcb, 0xa9, 0x13, 0xfc, 0xe1, 0x1b, 0x11, 0x98, +0xf3, 0xdf, 0x1e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0xf3, 0x9c, 0x1e, +0x40, 0x6c, 0xf3, 0x17, 0x09, 0x1e, 0x6e, 0xff, +0xe6, 0x15, 0xa5, 0x63, 0x2b, 0x21, 0x50, 0xa0, +0xae, 0xd5, 0x70, 0x1a, 0x71, 0x22, 0xa0, 0x62, +0x3e, 0x32, 0x49, 0xd5, 0x63, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0xa2, +0x0f, 0x93, 0x9d, 0x7a, 0x48, 0xa9, 0xde, 0x30, +0xfc, 0x9b, 0x14, 0xe6, 0xb2, 0x9d, 0x97, 0x46, +0x9e, 0x1c, 0xf4, 0x03, 0xa0, 0xe7, 0xec, 0x9c, +0x3d, 0x91, 0x2c, 0x3c, 0x07, 0xc5, 0xd6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0x3d, 0x10, 0x14, 0x76, 0xef, 0xe9, 0x20, +0x74, 0x4a, 0x26, 0xfe, 0xbd, 0xe1, 0x4f, 0x42, +0x6a, 0x97, 0xee, 0x50, 0x62, 0x25, 0x71, 0xac, +0x17, 0xeb, 0x94, 0x71, 0x2c, 0x78, 0xb7, 0x0c, +0xc6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0x66, 0x77, 0x35, 0xdb, 0xa6, +0xf3, 0x33, 0xfe, 0x0e, 0x79, 0xf5, 0xff, 0x88, +0x7c, 0x8a, 0xa0, 0x34, 0xff, 0x9d, 0x0e, 0xf3, +0x35, 0x17, 0xa4, 0x4b, 0xda, 0x4b, 0xe0, 0xa6, +0xdd, 0xad, 0xd1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0x15, 0xe2, 0x61, +0x73, 0xa3, 0x9e, 0xa0, 0x7d, 0xe0, 0x29, 0xbd, +0x61, 0x41, 0x9d, 0x53, 0xc1, 0x65, 0x22, 0x4e, +0x2c, 0x71, 0x52, 0x8a, 0x67, 0xb7, 0x5b, 0x56, +0xde, 0x26, 0x19, 0xce, 0x0d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0x62, +0x31, 0x45, 0x78, 0xf3, 0x52, 0x0e, 0x6d, 0xd3, +0x3c, 0xf1, 0xe2, 0x73, 0x91, 0x7a, 0x87, 0x13, +0x70, 0x8a, 0xbf, 0x28, 0x13, 0xba, 0x05, 0xa2, +0x34, 0x3d, 0x3a, 0xf0, 0x13, 0x1a, 0xd3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0xb9, 0x0f, 0x95, 0x0c, 0x25, 0xe0, 0x96, +0x9b, 0x64, 0x75, 0x1a, 0x27, 0x09, 0xc1, 0xa7, +0xc2, 0x3b, 0x17, 0x98, 0x2e, 0x49, 0x29, 0xe1, +0x26, 0x18, 0x6c, 0xe2, 0xe2, 0xa1, 0x80, 0x26, +0xe9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0x4f, 0x87, 0xce, 0x6c, 0x0c, +0x39, 0x83, 0x57, 0xbd, 0x7e, 0xdd, 0x78, 0xf7, +0x0d, 0x05, 0x3e, 0xc0, 0xc9, 0x4a, 0x50, 0x0b, +0xbf, 0xef, 0x63, 0xeb, 0x1b, 0xe0, 0x3b, 0xe9, +0x94, 0x0f, 0x5d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0xbd, 0x14, 0xfb, +0xd3, 0x02, 0x70, 0xae, 0x93, 0xe3, 0xb5, 0xa8, +0x19, 0x66, 0x18, 0xa0, 0x3d, 0x3b, 0xc2, 0x36, +0xd7, 0xa1, 0x23, 0x0c, 0xbb, 0xdb, 0x52, 0xad, +0xbd, 0x8d, 0x64, 0x51, 0x5f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0x22, +0x7e, 0x11, 0xd3, 0x28, 0x0b, 0x46, 0xa9, 0xf8, +0xa7, 0x63, 0xdb, 0x47, 0xcb, 0x3c, 0xa8, 0x82, +0x00, 0x19, 0xba, 0xa8, 0x79, 0x15, 0x39, 0x37, +0x52, 0x98, 0xe4, 0x68, 0xec, 0x95, 0x24, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0xeb, 0x8b, 0xc7, 0x2a, 0x02, 0x2d, 0x36, +0x0d, 0x68, 0x9c, 0x2c, 0x35, 0x9a, 0xfe, 0x75, +0x6d, 0xa9, 0x1f, 0x06, 0x73, 0xcc, 0x45, 0x70, +0x1b, 0x7d, 0xc5, 0xe5, 0x59, 0x10, 0xd0, 0xa1, +0x15, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0x11, 0x4b, 0x47, 0xd1, 0x86, +0x35, 0xea, 0x01, 0x06, 0x6d, 0x86, 0x1a, 0x61, +0x33, 0x28, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xb4, 0x21, 0x83, 0xd6, +0x17, 0x43, 0x75, 0xbd, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0x89, 0x15, 0xc0, +0x9a, 0x22, 0xc3, 0xf4, 0x50, 0xab, 0xb4, 0x24, +0x29, 0xd5, 0x68, 0x7d, 0xd1, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xd6, +0x3f, 0x2d, 0x13, 0xbc, 0xd3, 0xe9, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0xb4, +0x63, 0x16, 0x34, 0xed, 0x89, 0x2e, 0x1f, 0x96, +0x30, 0x97, 0x51, 0x92, 0x7b, 0x5d, 0x22, 0x9c, +0xf7, 0xaa, 0x69, 0x08, 0x95, 0x5f, 0x7d, 0x49, +0x60, 0xc9, 0x26, 0x89, 0xf6, 0xc2, 0x98, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0x83, 0xb8, 0x2a, 0x8f, 0xbb, 0x80, 0x46, +0x1e, 0x98, 0x8f, 0x1f, 0x21, 0x50, 0x6d, 0x77, +0x64, 0x79, 0x35, 0x68, 0x24, 0xe6, 0x9d, 0xc3, +0xbd, 0xd0, 0xe0, 0x27, 0x6b, 0xc2, 0x6f, 0xb9, +0x55, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0x38, 0x41, 0xa7, 0xc5, 0xb5, +0x2a, 0xaa, 0x09, 0x74, 0xa0, 0xff, 0x59, 0x2b, +0x0c, 0x42, 0x81, 0x0c, 0x7d, 0xe1, 0x31, 0xd7, +0x0a, 0xda, 0xda, 0x67, 0x3c, 0x24, 0x47, 0xb2, +0x78, 0x44, 0xc1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0xc3, 0xcd, 0xa4, +0x7b, 0x7b, 0x4c, 0xcf, 0xf4, 0x3f, 0x76, 0x3b, +0x3c, 0x22, 0x33, 0x9d, 0x27, 0xcb, 0x52, 0x44, +0x1d, 0x36, 0x33, 0xf3, 0x58, 0x02, 0x03, 0x96, +0xf0, 0x23, 0x86, 0x81, 0xc8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0x09, +0x16, 0x59, 0x51, 0x5b, 0x3a, 0xc1, 0x6b, 0xd7, +0xde, 0xc9, 0xfa, 0xaf, 0xc3, 0xb5, 0xb3, 0x3a, +0xbd, 0x00, 0xda, 0xd3, 0xc9, 0xfb, 0x38, 0xf8, +0x37, 0xd3, 0x20, 0x62, 0xf8, 0xd2, 0x26, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0x36, 0xba, 0xad, 0xdd, 0xc2, 0x37, 0x59, +0x43, 0xc3, 0x22, 0x75, 0x21, 0x7d, 0x5a, 0x60, +0x70, 0x6e, 0x2d, 0xff, 0xd4, 0x9d, 0x0a, 0x7b, +0x41, 0xe7, 0xf7, 0x7d, 0xf1, 0xc3, 0x16, 0x14, +0x59, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0x62, 0xb8, 0xc7, 0x05, 0x10, +0x50, 0x6b, 0xa7, 0xbf, 0x55, 0x47, 0x1c, 0x10, +0x10, 0x6c, 0xb7, 0xbd, 0x45, 0xcc, 0xef, 0x56, +0x55, 0x41, 0xa6, 0xaf, 0x0b, 0x1a, 0x1c, 0xf4, +0x89, 0x17, 0x5e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0x00, 0xf8, 0x7e, +0xc3, 0x1c, 0x42, 0x0a, 0xee, 0xd2, 0x33, 0xbc, +0xd7, 0x2a, 0xdf, 0x0a, 0xe1, 0x87, 0x8b, 0xb9, +0x99, 0x70, 0x9a, 0xc0, 0x3b, 0xac, 0x52, 0x72, +0xb3, 0x98, 0x32, 0x38, 0xbe, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0xb9, +0x08, 0xd7, 0x3d, 0xf6, 0x83, 0x61, 0x44, 0xd0, +0x98, 0x18, 0x98, 0xcf, 0x26, 0xfb, 0xeb, 0x4f, +0x46, 0xe8, 0xf8, 0x24, 0x90, 0xf4, 0xb8, 0x81, +0xfb, 0x7e, 0xc5, 0x9f, 0xbe, 0x9f, 0x6b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0xd3, 0x75, 0x2d, 0xe6, 0x4a, 0x16, 0xe0, +0x8b, 0x67, 0x52, 0x9a, 0xd8, 0x78, 0xe0, 0x51, +0xca, 0xc5, 0xb8, 0xc9, 0x07, 0xa7, 0xaf, 0x45, +0x9e, 0x2b, 0x77, 0x39, 0x7d, 0x52, 0xa7, 0x22, +0x2c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0x54, 0x42, 0xd6, 0x72, 0x30, +0x5e, 0x01, 0x45, 0x9c, 0xb6, 0x97, 0xf0, 0x8d, +0x0c, 0x7b, 0xa3, 0x36, 0x68, 0xac, 0xc5, 0x88, +0x47, 0x45, 0x15, 0xe6, 0x54, 0xcd, 0x54, 0x89, +0xba, 0xac, 0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0xd5, 0x62, 0x95, +0x5f, 0x67, 0x6b, 0x17, 0x4f, 0xfc, 0x37, 0xc1, +0x2e, 0x8e, 0x51, 0xab, 0x7a, 0xd3, 0x30, 0x17, +0x09, 0x03, 0x2d, 0xe2, 0xab, 0xa8, 0x98, 0x47, +0x68, 0x41, 0xb2, 0xc9, 0x1f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0xb5, +0x0b, 0x30, 0x92, 0xfb, 0x05, 0xf2, 0x4a, 0xfd, +0xcb, 0xe4, 0x22, 0x41, 0x59, 0xa3, 0xdb, 0xee, +0xd2, 0xb0, 0x03, 0x4b, 0x42, 0x3c, 0x7e, 0x5f, +0xed, 0x5c, 0x22, 0xe9, 0xb4, 0x36, 0x0f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0xa1, 0x60, 0x90, 0x69, 0xd8, 0xdd, 0x27, +0x4c, 0x7d, 0xe7, 0x9a, 0x7a, 0x74, 0x9c, 0x1d, +0xd1, 0xa9, 0x16, 0xd4, 0xb5, 0xce, 0xdc, 0x13, +0xd6, 0x94, 0x9c, 0xd5, 0xc1, 0x8b, 0x51, 0xc9, +0xee, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0xea, 0xbb, 0x85, 0xe2, 0x89, +0x2c, 0x3b, 0x70, 0xfa, 0x8c, 0xe7, 0xf9, 0xd0, +0xfc, 0x4b, 0x8a, 0x83, 0x78, 0xb1, 0x19, 0xea, +0x08, 0x66, 0xa6, 0x8f, 0xb6, 0x4c, 0x35, 0x80, +0x9b, 0x0f, 0x4f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0x02, 0x78, 0xf4, +0x23, 0x92, 0xdd, 0x7f, 0xda, 0x76, 0x67, 0x4d, +0x8f, 0x0a, 0x4c, 0xed, 0x07, 0x71, 0xb8, 0xeb, +0xa9, 0x01, 0x2d, 0x20, 0xac, 0x66, 0x6d, 0x61, +0xdd, 0xdf, 0xf4, 0x6b, 0x6e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0x0c, +0xa5, 0xaa, 0x80, 0x52, 0x02, 0xb6, 0xeb, 0x92, +0x23, 0x0c, 0x0b, 0x37, 0xc1, 0x79, 0xcc, 0x03, +0xb3, 0x5d, 0xa3, 0xf4, 0xb8, 0x1d, 0x23, 0xcd, +0x59, 0xe5, 0xbf, 0x2b, 0x44, 0x83, 0x18, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0x1d, 0x41, 0x67, 0xc6, 0x71, 0xfe, 0x89, +0x14, 0x11, 0x4e, 0xc5, 0x19, 0xdf, 0x19, 0x40, +0x75, 0x5d, 0x50, 0x8d, 0xb9, 0x5d, 0x1e, 0x87, +0xa9, 0x69, 0x64, 0xe9, 0x2c, 0x5d, 0x59, 0xdf, +0x89, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0x1e, 0x67, 0xdb, 0xfa, 0x8c, +0xc6, 0xdc, 0x52, 0xd7, 0xe1, 0xbd, 0x83, 0x7b, +0x22, 0xb8, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xab, 0xb1, 0x47, 0x85, +0x44, 0xfb, 0xfb, 0xac, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x7f, 0x47, 0xdc, +0x21, 0x31, 0x1c, 0x16, 0x81, 0x4f, 0x11, 0x14, +0xa6, 0xc6, 0x17, 0x30, 0xb5, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x62, +0x80, 0x36, 0x3d, 0x8e, 0x1a, 0x85, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0x32, +0xd8, 0xa4, 0x31, 0x60, 0x4a, 0xf3, 0x02, 0x2f, +0x4d, 0x06, 0x70, 0xa8, 0x25, 0x1b, 0xc6, 0x11, +0x8c, 0x4d, 0xa8, 0x4d, 0xe2, 0xb6, 0xc0, 0x80, +0x64, 0xee, 0x54, 0x52, 0x77, 0xd9, 0xad, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x5c, 0x2f, 0xae, 0x4a, 0x4d, 0x53, 0x34, +0xd2, 0x25, 0x18, 0xaa, 0xa8, 0x68, 0x4e, 0x8a, +0x40, 0x4f, 0x5e, 0x98, 0xbc, 0x46, 0x3d, 0x2f, +0xbf, 0x2f, 0x53, 0x97, 0x37, 0x6e, 0xc4, 0xb9, +0x60, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0xd5, 0xeb, 0x15, 0xad, 0x79, +0x7a, 0xe1, 0xad, 0x0d, 0x70, 0x83, 0x11, 0x8b, +0xce, 0xf0, 0x61, 0x92, 0xda, 0x4b, 0xde, 0x64, +0x89, 0x66, 0x66, 0x92, 0xe9, 0xf9, 0x7c, 0xe6, +0x04, 0xd3, 0xf0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0x37, 0x75, 0xff, +0xf7, 0xdb, 0x2c, 0x6c, 0xd2, 0x56, 0x9f, 0xe5, +0x7a, 0x19, 0xdf, 0xd2, 0x94, 0x0d, 0xf7, 0xad, +0xa5, 0xf3, 0x89, 0xc8, 0x4b, 0x0c, 0x22, 0xca, +0xa7, 0x4a, 0x7b, 0x22, 0xe6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x18, +0x8f, 0x94, 0x04, 0x8b, 0x04, 0x26, 0xca, 0x42, +0x57, 0xb9, 0x05, 0xd2, 0x90, 0x78, 0x65, 0x4b, +0xed, 0xd0, 0xfc, 0x91, 0x5d, 0x45, 0xd6, 0x61, +0xc7, 0xf1, 0xe6, 0x6d, 0xd9, 0x1c, 0xb1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0xc5, 0x46, 0x28, 0x6a, 0x9a, 0x0d, 0xa8, +0xa5, 0x57, 0xe2, 0x4f, 0xb6, 0xb4, 0xe6, 0x8c, +0x8d, 0x9e, 0x83, 0xe3, 0x0a, 0xca, 0xdb, 0xdd, +0x80, 0x3a, 0x15, 0x4a, 0xd5, 0x38, 0x9e, 0xf1, +0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0xc8, 0xbd, 0xb5, 0x50, 0xa0, +0xd4, 0xad, 0x77, 0xec, 0x90, 0x32, 0xf4, 0x4a, +0x35, 0x0f, 0xac, 0x7e, 0xa7, 0x15, 0x2b, 0x00, +0x89, 0xaa, 0xf3, 0xe6, 0xf1, 0x46, 0x9a, 0x93, +0x66, 0x35, 0x76, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0xbb, 0xb8, 0x3d, +0xd4, 0xdd, 0x33, 0xb3, 0x6b, 0xec, 0x9d, 0x62, +0xaf, 0xf6, 0x3c, 0xe9, 0x72, 0x6f, 0x8a, 0x22, +0xb2, 0x4e, 0xca, 0x91, 0x0e, 0x67, 0xe3, 0x2a, +0x6b, 0x85, 0xdf, 0x48, 0x9f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0xf7, +0x34, 0x05, 0xb2, 0x3f, 0x13, 0xc3, 0xa6, 0x49, +0x92, 0x1b, 0x66, 0x4a, 0x69, 0x4c, 0xa3, 0xba, +0x2e, 0x51, 0xd7, 0x40, 0x6c, 0x4a, 0x43, 0x6d, +0xbf, 0x98, 0xc1, 0x3a, 0xef, 0x8b, 0x60, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0xca, 0x05, 0xe8, 0xe4, 0x91, 0xa3, 0xdd, +0x10, 0xeb, 0xc5, 0x98, 0x31, 0xa1, 0x41, 0x69, +0xd6, 0x85, 0x23, 0xbb, 0xa8, 0xa3, 0xec, 0xef, +0x34, 0x32, 0xeb, 0xf2, 0xa2, 0x4f, 0x5f, 0x92, +0x58, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0xdc, 0xa9, 0xef, 0xc9, 0xf5, +0xc7, 0xd9, 0xa4, 0x02, 0x63, 0xe8, 0x7d, 0xce, +0x2d, 0x2a, 0x5b, 0x82, 0x48, 0xa5, 0xfd, 0xb6, +0x40, 0x4a, 0x46, 0x74, 0x19, 0x6f, 0x65, 0x2a, +0x48, 0x91, 0x1a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0x29, 0x8e, 0x8e, +0x80, 0x8e, 0x57, 0xdf, 0x6b, 0x60, 0xfd, 0x3e, +0x2a, 0x51, 0xbc, 0xf9, 0xd4, 0x04, 0x59, 0x8f, +0xd5, 0xda, 0xf0, 0xa6, 0xf0, 0xea, 0xf5, 0x68, +0x0a, 0x93, 0x24, 0x41, 0x2c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x2f, +0xb4, 0x68, 0xa2, 0x31, 0xaf, 0x74, 0x90, 0x90, +0x58, 0x3d, 0x21, 0xe8, 0xdf, 0x76, 0xa9, 0xd7, +0x84, 0xb1, 0x02, 0x1b, 0x90, 0x47, 0xb6, 0xfd, +0xa9, 0x50, 0xb7, 0xaa, 0x9c, 0x9d, 0x76, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0xa1, 0x4d, 0xda, 0x88, 0x8c, 0xde, 0xe3, +0x8b, 0xcd, 0x3b, 0x42, 0xf6, 0x3b, 0x7d, 0xf3, +0xaf, 0xa7, 0xfe, 0x35, 0x4e, 0x4d, 0xb6, 0x26, +0x9e, 0x6b, 0x71, 0x25, 0x32, 0xcb, 0xc3, 0x10, +0xd7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0xb7, 0xd3, 0x80, 0xf6, 0x04, +0xd3, 0x55, 0x1d, 0x14, 0x4e, 0x0d, 0x16, 0x65, +0xa2, 0x2e, 0xe7, 0xf1, 0x4f, 0x48, 0xb7, 0x90, +0xb2, 0x6f, 0xcb, 0xda, 0xee, 0x30, 0xd2, 0x50, +0xdb, 0x07, 0x36, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0xe5, 0x80, 0xe6, +0x5a, 0x3c, 0xb8, 0x62, 0x85, 0xd2, 0xa4, 0x5c, +0xad, 0x47, 0x8a, 0x7f, 0xda, 0x72, 0x52, 0xae, +0x2a, 0xe9, 0x4f, 0xe7, 0x0e, 0x01, 0xd8, 0x92, +0x44, 0x33, 0x17, 0x10, 0xf5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0x96, +0x16, 0x2a, 0xe2, 0x42, 0xf8, 0x20, 0x43, 0xbb, +0x25, 0xc3, 0x75, 0x81, 0x6c, 0x53, 0xce, 0xd1, +0x20, 0x14, 0xb5, 0xa5, 0xf7, 0xb8, 0x83, 0x54, +0x11, 0x8d, 0xbb, 0xdd, 0x9a, 0x98, 0x14, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0xb4, 0xac, 0x31, 0xd7, 0x68, 0x8f, 0x14, +0x70, 0x13, 0xe1, 0x55, 0xa1, 0x1a, 0xb4, 0x8c, +0x76, 0x83, 0xd9, 0x26, 0x91, 0x2a, 0x59, 0x5a, +0xc0, 0xa9, 0x3a, 0x3a, 0x3d, 0xe7, 0xdd, 0x42, +0x0a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0x4a, 0x13, 0xb5, 0xa9, 0x1a, +0x9a, 0x1d, 0x15, 0xef, 0x2a, 0x2b, 0x97, 0x25, +0xfd, 0xda, 0xd7, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x88, 0x4c, 0x79, 0xd1, +0xb7, 0x6f, 0x8e, 0x16, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0xb4, 0x06, 0xfc, +0x35, 0xbf, 0xbe, 0x6e, 0x78, 0xfd, 0x64, 0xd7, +0x64, 0x0e, 0x44, 0xa1, 0xe5, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe9, 0xf0, +0x51, 0xae, 0x8c, 0xc5, 0x8d, 0xb8, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0x06, +0x89, 0x0f, 0x36, 0x7d, 0xec, 0x48, 0xbb, 0xa9, +0x5a, 0xe5, 0xf8, 0xac, 0x75, 0x99, 0x80, 0x91, +0xff, 0x8e, 0x1c, 0xb4, 0xb4, 0x1e, 0xc0, 0x61, +0x43, 0x41, 0xfe, 0x44, 0x02, 0xf3, 0xe5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x14, 0x9a, 0x9d, 0x89, 0x4d, 0xbe, 0xf6, +0x83, 0x73, 0xe0, 0x1d, 0x3a, 0xb6, 0xd0, 0x59, +0x77, 0xbd, 0x01, 0x91, 0x37, 0xc8, 0x15, 0xcc, +0x5f, 0xe6, 0xdb, 0x93, 0xcb, 0x75, 0xe9, 0x00, +0xd1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x42, 0xa4, 0x26, 0x9e, 0x2d, +0x00, 0xfe, 0x11, 0xb1, 0x1a, 0x9d, 0xc7, 0xd0, +0xbf, 0x68, 0x30, 0x8b, 0x23, 0xa9, 0x1d, 0x4e, +0xc0, 0x7a, 0x69, 0x3a, 0xd8, 0x83, 0xd5, 0xe2, +0x89, 0x84, 0x35, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0x9f, 0xc6, 0xa0, +0x46, 0xfd, 0x1e, 0xce, 0xd6, 0x48, 0x83, 0xa1, +0xfa, 0x3d, 0x83, 0xab, 0x1a, 0x47, 0xe8, 0x20, +0x08, 0xd5, 0xce, 0xcf, 0x4e, 0xf6, 0x76, 0x66, +0xa8, 0x71, 0x07, 0x9b, 0x05, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0x72, +0xf4, 0xc3, 0xdb, 0x73, 0xac, 0xae, 0x97, 0x60, +0x4e, 0x74, 0x71, 0x04, 0x00, 0xf5, 0xef, 0xaf, +0x49, 0xf8, 0x13, 0x31, 0xda, 0x97, 0x44, 0x55, +0x42, 0x9e, 0x08, 0xdd, 0x31, 0xb9, 0x4a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x88, 0x07, 0xca, 0x71, 0x52, 0x37, 0xd8, +0xc5, 0xa2, 0xb7, 0xe8, 0xb1, 0x9f, 0x72, 0xe8, +0x14, 0x7c, 0xdc, 0xb4, 0xbd, 0x2b, 0xef, 0x53, +0xf9, 0x35, 0xf1, 0xe1, 0x3f, 0x5e, 0x8b, 0xbf, +0xa6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0x80, 0x89, 0x01, 0x59, 0xfe, +0x1e, 0xe9, 0x47, 0x7b, 0xc7, 0xc1, 0xd5, 0x8b, +0x45, 0xad, 0x5e, 0xdb, 0xb6, 0x35, 0xf5, 0x23, +0x60, 0x42, 0x68, 0xbe, 0xc0, 0x53, 0xb9, 0x06, +0x6c, 0xc5, 0xfb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0x35, 0x1a, 0xf2, +0x60, 0x5e, 0x1d, 0x97, 0xc0, 0x09, 0x0d, 0x46, +0x34, 0x64, 0x52, 0x8c, 0x2e, 0x5e, 0x82, 0x8b, +0x95, 0x44, 0x56, 0xc1, 0x1c, 0xce, 0xf8, 0x61, +0xeb, 0x7b, 0x52, 0x80, 0x2c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x08, +0x82, 0x32, 0x92, 0x66, 0x2e, 0x5a, 0x26, 0xdb, +0xdb, 0x8e, 0x77, 0x56, 0x19, 0x3e, 0x91, 0x28, +0xdf, 0x50, 0x2b, 0x30, 0xd7, 0xce, 0xd9, 0xdd, +0x46, 0x6b, 0xd7, 0xf7, 0xf6, 0xb0, 0x90, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0xd7, 0x9f, 0x64, 0x6b, 0x80, 0x63, 0x3a, +0x80, 0x09, 0xb1, 0xc6, 0xfb, 0x43, 0xd7, 0x96, +0x67, 0x79, 0x64, 0x58, 0x33, 0x84, 0xa0, 0x4f, +0x57, 0x1b, 0x12, 0xfd, 0x50, 0x30, 0xab, 0x16, +0x9f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0x0a, 0x6d, 0x3e, 0xa5, 0x3b, +0x87, 0x67, 0x95, 0x80, 0xd7, 0xc1, 0x63, 0x5e, +0xd4, 0x06, 0x02, 0xa8, 0x2e, 0xf9, 0xa9, 0x2c, +0xba, 0x83, 0x71, 0x46, 0xae, 0x6d, 0xf0, 0x7c, +0x97, 0x3f, 0xc1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0xe0, 0x9c, 0x8f, +0xff, 0x55, 0x79, 0xcc, 0xf8, 0x00, 0x3d, 0xc5, +0x4d, 0x4e, 0x22, 0x25, 0xd7, 0x57, 0xa4, 0xf1, +0xc0, 0x7f, 0xf7, 0x65, 0x8e, 0xcb, 0x6c, 0xbb, +0x7c, 0x84, 0xf5, 0x0d, 0x8d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0xa8, +0xa4, 0x7a, 0xeb, 0x0e, 0xf9, 0x0a, 0x0b, 0x80, +0x29, 0x6b, 0xb8, 0x8c, 0xec, 0xf4, 0x12, 0xf6, +0x13, 0x56, 0x53, 0xd8, 0xe4, 0x78, 0xc2, 0x55, +0xc4, 0x98, 0x83, 0xef, 0xa5, 0x99, 0xca, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0x56, 0xa8, 0x3a, 0x23, 0xa5, 0xb6, 0xc8, +0x4f, 0x5a, 0x4e, 0xa1, 0x00, 0xb0, 0x65, 0xd9, +0x29, 0x0a, 0xb6, 0x3f, 0x3c, 0xad, 0x62, 0x9c, +0xf1, 0xa2, 0x7d, 0x92, 0x98, 0xf6, 0x22, 0xcf, +0x30, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0x89, 0x26, 0xf2, 0xa6, 0x94, +0x97, 0x2c, 0xb8, 0x19, 0xba, 0x80, 0x60, 0x13, +0xb4, 0x03, 0x08, 0x93, 0x75, 0xac, 0xc6, 0xb0, +0xde, 0x8f, 0x77, 0xed, 0x7f, 0x98, 0x16, 0x34, +0x58, 0x31, 0x3e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0x76, 0x96, 0xf0, +0xc7, 0xe9, 0x25, 0xa0, 0x1b, 0x8b, 0x80, 0x17, +0xc6, 0x13, 0x35, 0xe4, 0x9b, 0xcd, 0x31, 0x09, +0x4b, 0x49, 0xee, 0x0a, 0x03, 0xfe, 0x1c, 0xb9, +0x17, 0xbf, 0xfe, 0x60, 0x7f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0xba, +0x82, 0x96, 0x7a, 0x54, 0xe4, 0xe4, 0x91, 0xc6, +0x0f, 0xfb, 0xde, 0x60, 0x31, 0xed, 0x32, 0x62, +0x49, 0x48, 0xf1, 0x79, 0x7c, 0x43, 0x13, 0xed, +0x2b, 0xee, 0xcc, 0x91, 0xd1, 0xee, 0x84, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0x22, 0x6d, 0xf4, 0x37, 0x8c, 0x8a, 0xdc, +0x45, 0xa9, 0xeb, 0xb4, 0xea, 0xb9, 0x52, 0x86, +0x5e, 0xab, 0x01, 0xbc, 0x5e, 0x3b, 0xba, 0x4e, +0x4a, 0x22, 0xd2, 0x95, 0xa3, 0xf3, 0x15, 0xbe, +0xec, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0xb9, 0x77, 0x71, 0x0f, 0x80, +0xa8, 0x9a, 0x91, 0x1e, 0x8e, 0x48, 0x6f, 0xcb, +0x73, 0x19, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xec, 0x3e, 0x63, 0x5f, +0xb2, 0x38, 0x8f, 0x0e, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0xef, 0xd6, 0x77, +0x4a, 0x8a, 0x92, 0xdf, 0xe4, 0x5d, 0xaa, 0x5b, +0x33, 0x7b, 0x03, 0xc6, 0x9d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0xb7, +0x55, 0x27, 0x4c, 0xb8, 0xde, 0x9b, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0x81, +0x8f, 0x95, 0x10, 0x78, 0x4e, 0x0d, 0x7b, 0xb0, +0x14, 0x63, 0xeb, 0xf4, 0xe4, 0x9e, 0x5c, 0x42, +0x5a, 0x5b, 0xc3, 0x28, 0x72, 0x37, 0xe8, 0x0f, +0xae, 0x2b, 0xe8, 0x19, 0x19, 0xf5, 0xea, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0xaf, 0x07, 0x3a, 0x91, 0xc7, 0x6b, 0x4e, +0x74, 0x6c, 0xd9, 0x6b, 0x36, 0xff, 0x90, 0x59, +0x1c, 0xd9, 0x45, 0xbe, 0x23, 0xee, 0x41, 0xba, +0x59, 0x86, 0xdc, 0xb5, 0x4b, 0x44, 0xe4, 0x2e, +0x0f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0x79, 0x64, 0x7c, 0x3a, 0x5f, +0x4f, 0xe3, 0x5e, 0x25, 0x0e, 0x0b, 0x82, 0xde, +0xfc, 0xef, 0x43, 0x9f, 0x51, 0x78, 0xba, 0xa1, +0x4c, 0x6e, 0x03, 0x05, 0x24, 0x8c, 0xe0, 0xb5, +0x2a, 0x05, 0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0x46, 0x88, 0x10, +0xc5, 0x72, 0x53, 0xf4, 0x57, 0x6a, 0x2d, 0x88, +0xd9, 0xd3, 0xeb, 0xf7, 0x4f, 0xfc, 0x70, 0x23, +0x2e, 0xe1, 0x52, 0x99, 0xba, 0xb4, 0x23, 0x20, +0x49, 0x5d, 0x66, 0x48, 0xd3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0x60, +0x2b, 0x0f, 0xe8, 0xfc, 0xf0, 0x5d, 0x02, 0xd0, +0x60, 0x46, 0x10, 0xca, 0xa8, 0xb9, 0x9c, 0x7d, +0x76, 0x09, 0x82, 0xe4, 0x36, 0xfb, 0x49, 0x95, +0x6b, 0x04, 0xce, 0xc5, 0xac, 0x6e, 0x0d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0xfa, 0x11, 0x31, 0x88, 0x00, 0x76, 0xd5, +0x58, 0x9a, 0x02, 0xad, 0x02, 0x1b, 0xcd, 0x02, +0x1c, 0x61, 0x8e, 0x77, 0xd5, 0x63, 0x30, 0x47, +0x7d, 0x92, 0xc6, 0xb7, 0x23, 0x0c, 0x57, 0xc4, +0x4d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x15, 0x6e, 0x36, 0xec, 0x20, +0xa8, 0x6e, 0x19, 0x4d, 0xc9, 0x40, 0x01, 0x0b, +0xca, 0xd8, 0xff, 0x34, 0x36, 0xcb, 0x88, 0x86, +0x56, 0x54, 0xe7, 0x4e, 0x3c, 0xe1, 0x90, 0xb5, +0x8b, 0x3c, 0xaa, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0x03, 0x21, 0xe2, +0x1c, 0x77, 0xfe, 0xf0, 0xb1, 0x5b, 0xa7, 0xcb, +0xba, 0x18, 0xd1, 0x05, 0x87, 0x2f, 0x00, 0x7d, +0xbd, 0x86, 0x54, 0xcc, 0xdf, 0x9a, 0x3e, 0x58, +0xc7, 0x72, 0x1c, 0x1f, 0xc0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0x85, +0xa0, 0x07, 0xbb, 0xe9, 0xe9, 0x63, 0x9d, 0xdf, +0x6c, 0x0d, 0xe0, 0xc4, 0x6d, 0xa7, 0x6a, 0xa8, +0xa8, 0xbf, 0xa3, 0x56, 0x50, 0x1b, 0xe2, 0xca, +0xc4, 0x55, 0xc3, 0x05, 0xd2, 0x6f, 0xc9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x10, 0x99, 0xb4, 0x73, 0xfd, 0x2a, 0xc1, +0x08, 0x50, 0x7c, 0x7c, 0x1d, 0xb0, 0x0c, 0xa3, +0xfc, 0xf5, 0xd6, 0xef, 0x5f, 0xf2, 0x63, 0x72, +0x32, 0xd1, 0xe0, 0x8c, 0xb5, 0x55, 0x8d, 0xde, +0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0xae, 0x39, 0x79, 0x36, 0x7c, +0x88, 0x01, 0x03, 0x3c, 0xed, 0xa7, 0xd1, 0x6c, +0x62, 0xb2, 0x8a, 0xbc, 0xf8, 0x03, 0xb7, 0xe0, +0x5a, 0xc0, 0x3f, 0x40, 0x98, 0xbb, 0xf0, 0xec, +0x5e, 0x97, 0x09, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0x7c, 0xa7, 0x62, +0x00, 0x6b, 0x61, 0x9b, 0x92, 0xf8, 0xab, 0x07, +0xdd, 0xf9, 0x0b, 0x10, 0x72, 0x6b, 0x66, 0x3c, +0x5c, 0x1b, 0xd2, 0xc6, 0x59, 0xee, 0xe6, 0xfa, +0x54, 0xdf, 0x43, 0x9b, 0xcd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0x64, +0xa2, 0xce, 0x39, 0x50, 0xcf, 0xf2, 0x44, 0x47, +0xe4, 0x74, 0x8b, 0x46, 0x2a, 0x25, 0x80, 0xb7, +0x29, 0x66, 0x6a, 0xa7, 0xa9, 0xde, 0x97, 0x25, +0x90, 0x76, 0x47, 0x6c, 0x8e, 0xdb, 0x9c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0xfc, 0x89, 0x65, 0x3b, 0x44, 0x76, 0xfb, +0xaa, 0x92, 0x1b, 0x4d, 0x3f, 0x0e, 0x2a, 0xfb, +0x2b, 0xd9, 0x8b, 0xcf, 0x87, 0xae, 0x05, 0x53, +0x40, 0xb5, 0x9c, 0x98, 0x01, 0x24, 0x78, 0x8f, +0x68, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0x1b, 0x56, 0xf2, 0x5b, 0x1a, +0x15, 0x7c, 0x94, 0x9d, 0xaa, 0x64, 0xec, 0x1b, +0x5d, 0xbd, 0xee, 0x07, 0x2d, 0x1b, 0x64, 0x58, +0xd2, 0xb5, 0x3c, 0x3f, 0xe5, 0x71, 0xb2, 0x7a, +0xc7, 0x58, 0x36, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x6d, 0x68, 0x37, +0x6f, 0x1d, 0xc7, 0xa4, 0x91, 0x15, 0x54, 0x7c, +0x66, 0x73, 0xcb, 0x1d, 0xb0, 0x40, 0xcc, 0x4f, +0xe0, 0xfa, 0xd2, 0x84, 0x39, 0x8a, 0x0d, 0x43, +0x9f, 0x22, 0x00, 0xc0, 0x20, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x94, +0xd8, 0x1a, 0xe6, 0xbd, 0x3f, 0xe8, 0x61, 0xe7, +0x5f, 0x3d, 0x93, 0x35, 0x6f, 0x9a, 0x86, 0x1a, +0xae, 0xaa, 0x02, 0x36, 0x3a, 0x90, 0xae, 0xc1, +0xa0, 0xfb, 0x40, 0xc2, 0x03, 0x25, 0x56, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0xcf, 0x56, 0xba, 0xa6, 0x73, 0x0f, 0x80, +0xbc, 0x5d, 0xfd, 0x76, 0xf5, 0x53, 0x31, 0xbd, +0x53, 0x47, 0xec, 0x87, 0xa3, 0x21, 0x7c, 0x7d, +0x6b, 0x17, 0xfd, 0xa5, 0x0a, 0xc1, 0xc6, 0x4c, +0x13, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0xe9, 0x4a, 0x2b, 0x7d, 0x41, +0x2d, 0xc5, 0x92, 0x17, 0x7b, 0x9e, 0x9d, 0xe1, +0x54, 0x65, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x5a, 0xb1, 0x5d, 0x74, +0x76, 0x91, 0xeb, 0xcc, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0x3a, 0x3d, 0x92, +0x9e, 0xcf, 0x0f, 0xd8, 0xa0, 0x65, 0xf8, 0x6d, +0xf3, 0x53, 0xe1, 0xf3, 0xc4, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x78, +0x0c, 0x43, 0xd6, 0x0a, 0x4d, 0xd9, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0x87, +0xc9, 0xab, 0x79, 0xc0, 0x14, 0xec, 0x71, 0xf6, +0x9b, 0xcf, 0x2f, 0x38, 0x3b, 0x6d, 0x11, 0xf3, +0x16, 0x19, 0x25, 0xd8, 0x13, 0x8b, 0x50, 0x28, +0x00, 0xfe, 0x6c, 0x7e, 0x0c, 0xfc, 0xf9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0xa4, 0x7f, 0x28, 0x3b, 0x92, 0xcf, 0x44, +0xe9, 0x71, 0xae, 0x18, 0x57, 0x4f, 0x75, 0x24, +0xbf, 0xa7, 0x88, 0x56, 0x72, 0x80, 0x88, 0xfc, +0xbb, 0x64, 0x8c, 0xfc, 0x76, 0xac, 0xb6, 0xeb, +0xde, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0x91, 0x9f, 0x87, 0x00, 0x1f, +0xf5, 0xae, 0x79, 0x7e, 0x67, 0x5f, 0x17, 0x2c, +0x67, 0x99, 0xa1, 0x78, 0x7c, 0x9a, 0x3a, 0x55, +0xf9, 0x40, 0x3e, 0xcb, 0x8a, 0xb1, 0xcd, 0xaa, +0x30, 0x91, 0xf3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0x65, 0xe5, 0xbe, +0x9b, 0x2d, 0x8c, 0x50, 0x90, 0x14, 0xf0, 0x15, +0xc9, 0x23, 0xe4, 0xc7, 0x56, 0x99, 0xf2, 0x52, +0x86, 0x7b, 0x28, 0x17, 0x54, 0xc1, 0x73, 0xe0, +0x29, 0xa0, 0xc2, 0xcb, 0xa6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0xa8, +0x8c, 0xc3, 0xe2, 0x56, 0xa5, 0x6f, 0x57, 0xb0, +0xfd, 0x5e, 0x19, 0x62, 0x7c, 0xc6, 0xf8, 0x24, +0xec, 0x8c, 0x7e, 0xeb, 0xd5, 0x32, 0x8b, 0x78, +0x22, 0xcf, 0xb5, 0x83, 0x5f, 0x06, 0xcb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0x77, 0xe0, 0x6e, 0x29, 0x69, 0xd9, 0x0c, +0x27, 0x33, 0x52, 0x22, 0x55, 0x8d, 0x1f, 0xa1, +0x5c, 0xa3, 0x33, 0xd6, 0xcb, 0xab, 0x66, 0x1e, +0x9b, 0x66, 0x97, 0x8a, 0x69, 0x88, 0x42, 0x45, +0x9c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0xb3, 0xba, 0xdb, 0x3c, 0xc3, +0xbf, 0x50, 0x68, 0x90, 0x81, 0xe7, 0xe8, 0xbe, +0xce, 0xb7, 0xb4, 0x74, 0x8e, 0x72, 0xcf, 0x7e, +0x5e, 0x84, 0x1b, 0xa4, 0x9c, 0x54, 0xcc, 0xcc, +0xe4, 0xa8, 0x3c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x84, 0x2c, 0xf7, +0x4b, 0xa8, 0xd8, 0x5a, 0xb4, 0x36, 0x0e, 0x58, +0xe3, 0xd2, 0x49, 0xf9, 0x02, 0x00, 0xad, 0x62, +0x1f, 0x36, 0xa2, 0xed, 0x0a, 0xe2, 0x0b, 0x6d, +0x8a, 0x9b, 0x9c, 0x29, 0x0e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0xc0, +0x7b, 0x22, 0x65, 0xfd, 0x0a, 0xb8, 0x97, 0xde, +0x83, 0x8d, 0x40, 0x23, 0x4b, 0xce, 0x17, 0xa4, +0xea, 0x5a, 0xef, 0x9d, 0x9e, 0xe0, 0xe5, 0xc8, +0xd9, 0xb7, 0x1e, 0x8e, 0xa1, 0x62, 0x4d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0xe8, 0x70, 0xbf, 0xd5, 0xe4, 0x71, 0xcf, +0x34, 0xae, 0x61, 0x67, 0x49, 0x07, 0xae, 0x25, +0xde, 0xb8, 0x84, 0xab, 0x72, 0x37, 0x5f, 0xa6, +0xce, 0xf3, 0x18, 0x15, 0x29, 0x69, 0x33, 0x50, +0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0x20, 0x0c, 0xc4, 0xbd, 0x3f, +0x77, 0xbf, 0xf3, 0xae, 0x71, 0x35, 0xb2, 0x22, +0x0a, 0x44, 0x9b, 0x25, 0x29, 0x19, 0x69, 0x01, +0x9f, 0x47, 0x09, 0xeb, 0x0f, 0x9a, 0xd7, 0x41, +0x7d, 0xb9, 0x0b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x90, 0xa0, 0x32, +0x03, 0x58, 0x45, 0x5b, 0x6f, 0xda, 0xae, 0x4d, +0xee, 0x08, 0xbd, 0x78, 0x97, 0x49, 0xa7, 0xb6, +0x07, 0x69, 0x6e, 0xea, 0x3e, 0x73, 0x49, 0xbb, +0xff, 0x10, 0x02, 0x2a, 0xff, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x04, +0x57, 0x3e, 0xe8, 0x95, 0xba, 0x74, 0x06, 0xad, +0x97, 0xa6, 0xea, 0xe9, 0x37, 0x90, 0xd4, 0x7e, +0x32, 0xcc, 0xea, 0xdf, 0x8f, 0x05, 0x6f, 0xbe, +0x70, 0x3c, 0x0b, 0x45, 0x3e, 0x78, 0x0a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0x65, 0x67, 0xc2, 0x02, 0x63, 0xa4, 0xaf, +0xd8, 0x00, 0x6c, 0xfe, 0x2e, 0xef, 0x25, 0x72, +0x0e, 0xa3, 0xe4, 0x0f, 0xca, 0xd2, 0x97, 0xd7, +0xde, 0x09, 0xd1, 0x38, 0x50, 0x48, 0xfb, 0x75, +0x37, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0xa1, 0xd7, 0x30, 0x47, 0x15, +0xc2, 0xc1, 0x21, 0x10, 0xd4, 0xb9, 0x3e, 0x21, +0xa2, 0xd8, 0x65, 0x3f, 0xc9, 0x5e, 0x66, 0x83, +0x78, 0x71, 0x9a, 0x16, 0x74, 0x2f, 0xda, 0x59, +0x00, 0x32, 0x79, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0xe8, 0x8c, 0x50, +0xaa, 0xe7, 0xd3, 0x41, 0x33, 0xf7, 0x89, 0x34, +0x9c, 0xdd, 0x9c, 0xf2, 0x92, 0x14, 0x16, 0x39, +0x6e, 0xd3, 0x76, 0x3a, 0xe8, 0x37, 0xfd, 0x55, +0x06, 0x21, 0x49, 0x61, 0xe4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0xd8, +0x1b, 0xf9, 0xba, 0x29, 0x93, 0xfd, 0xda, 0xe8, +0x76, 0x1a, 0xf2, 0x2c, 0x18, 0xff, 0xd3, 0xaa, +0xb0, 0x52, 0x15, 0x60, 0xab, 0xc6, 0x38, 0xf6, +0x3a, 0x8e, 0x1f, 0x77, 0x37, 0x6b, 0x95, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0x51, 0x06, 0x28, 0x77, 0x06, 0xe7, 0x4a, +0x59, 0x9a, 0x8f, 0x03, 0x29, 0x0f, 0x9c, 0x95, +0x88, 0xae, 0x33, 0x3a, 0xcf, 0xda, 0x10, 0x07, +0x38, 0x8c, 0xf5, 0xa3, 0x79, 0x3d, 0xa0, 0x74, +0x39, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0x0e, 0x7e, 0xaa, 0x68, 0xe0, +0xb6, 0xee, 0xb1, 0xd7, 0x70, 0xc6, 0xbf, 0x62, +0xc3, 0x98, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x67, 0x30, 0x0a, 0x8b, +0xc2, 0x5d, 0x1b, 0x97, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0x93, 0x92, 0x0a, +0x3d, 0xfa, 0x2c, 0x3b, 0xce, 0x3b, 0x61, 0xd2, +0x68, 0x8e, 0x91, 0x9f, 0xf2, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x22, +0xcd, 0x5e, 0x13, 0x78, 0xc3, 0x19, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0xf9, +0x42, 0xa8, 0xcb, 0x4d, 0x89, 0x18, 0x0d, 0x7f, +0x7e, 0x45, 0x24, 0xa2, 0x2a, 0xca, 0xfb, 0x16, +0x7d, 0x4f, 0x7e, 0x6c, 0x2d, 0xe6, 0x93, 0x47, +0xbe, 0x13, 0x17, 0xa3, 0x02, 0x68, 0xff, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0x54, 0x27, 0x04, 0x1f, 0x24, 0xaf, 0x83, +0xea, 0x08, 0x51, 0x7b, 0x2d, 0x4b, 0xc9, 0x23, +0x48, 0xf4, 0x3f, 0x03, 0x0b, 0x28, 0xa4, 0x93, +0x1c, 0xf1, 0xb7, 0xed, 0xf5, 0x3e, 0x55, 0xf5, +0x38, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0xc0, 0x0d, 0xa8, 0xef, 0x9c, +0x8b, 0x08, 0x43, 0x70, 0x2e, 0xb4, 0x62, 0x54, +0x9b, 0xb0, 0xbc, 0x37, 0x8b, 0xcb, 0x48, 0xf4, +0x2f, 0x13, 0xf7, 0xd3, 0xc5, 0xaf, 0x0c, 0xdd, +0x43, 0x6d, 0x0b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0xfe, 0x9f, 0x48, +0x55, 0x2c, 0x5e, 0xca, 0xfd, 0x78, 0x21, 0x76, +0x43, 0xb4, 0x6d, 0x49, 0xc9, 0xec, 0x87, 0xce, +0x8d, 0xa2, 0x58, 0xc9, 0xbb, 0x80, 0x45, 0x8d, +0xe0, 0xa1, 0x6b, 0x4d, 0x3e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0xef, +0xd4, 0x18, 0x59, 0xa3, 0x99, 0xd9, 0x9a, 0x92, +0x0e, 0x64, 0x77, 0x39, 0x8a, 0x8f, 0x3e, 0x43, +0x74, 0xad, 0x59, 0x4f, 0x0a, 0xe0, 0x21, 0x81, +0x7d, 0xa2, 0xff, 0xd7, 0x54, 0xcc, 0x5a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0x14, 0x6a, 0x33, 0xd4, 0x1a, 0xba, 0x4a, +0xf7, 0xf1, 0x09, 0x57, 0xfe, 0x2c, 0xd4, 0xb0, +0xba, 0x49, 0x28, 0xb0, 0xca, 0xe6, 0xcf, 0x4e, +0x7f, 0x42, 0xe8, 0xf7, 0xfc, 0x0e, 0xf4, 0x99, +0xd0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x2c, 0xd1, 0x3d, 0x4b, 0x31, +0x08, 0xe4, 0x2f, 0x8f, 0x72, 0xab, 0x80, 0xcd, +0xc5, 0x99, 0x63, 0x8b, 0x65, 0x88, 0x35, 0xb2, +0x2e, 0xb0, 0x41, 0xbb, 0x85, 0xa7, 0x94, 0x96, +0x22, 0xec, 0x4b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x67, 0xe7, 0x40, +0x0f, 0xb1, 0xa1, 0xd9, 0x63, 0xee, 0xd8, 0xa6, +0xcf, 0x91, 0xa6, 0x75, 0xbf, 0xb2, 0x39, 0x97, +0x25, 0xee, 0x59, 0x05, 0xa3, 0xe4, 0xe2, 0xf7, +0x48, 0x7c, 0x56, 0x96, 0xb5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0xca, +0x27, 0x0c, 0xab, 0xb3, 0x62, 0x9c, 0x51, 0x76, +0xee, 0x02, 0xc4, 0x91, 0xee, 0x88, 0x60, 0x6e, +0xb4, 0x51, 0xe5, 0x7c, 0x14, 0x70, 0xd3, 0xaf, +0xe5, 0x8d, 0x45, 0xce, 0xda, 0x3a, 0xeb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0xdd, 0x60, 0x53, 0xac, 0x56, 0x27, 0xbd, +0x12, 0x2d, 0x33, 0xee, 0x1f, 0x82, 0x9f, 0x58, +0x7b, 0xe9, 0x7c, 0x9c, 0x1d, 0x26, 0x2f, 0x0b, +0x1a, 0xae, 0xa2, 0x29, 0xbe, 0xef, 0x32, 0x40, +0x89, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0x07, 0x69, 0x25, 0x2a, 0x75, +0xb5, 0xbf, 0x18, 0x15, 0x24, 0x71, 0x90, 0x32, +0xf5, 0x81, 0x63, 0x3c, 0xec, 0xa5, 0xdd, 0x68, +0xa4, 0x3b, 0x68, 0x11, 0x69, 0x01, 0xb3, 0xcc, +0x09, 0xc6, 0xfc, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0x65, 0x65, 0x82, +0x15, 0x22, 0x85, 0xfc, 0x4a, 0xe3, 0xeb, 0xbb, +0xca, 0x20, 0x83, 0xcb, 0xcb, 0x64, 0xf2, 0xf7, +0x16, 0x43, 0x58, 0xca, 0xda, 0xf5, 0x62, 0x10, +0xcc, 0xa6, 0x7a, 0x75, 0x17, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0x91, +0x83, 0x7e, 0x9d, 0x17, 0xe1, 0x9f, 0xf4, 0x88, +0xe4, 0xef, 0x0e, 0x3e, 0xec, 0xc5, 0x0e, 0x2c, +0xf2, 0x1c, 0x14, 0x88, 0x46, 0x23, 0x6a, 0x88, +0xaf, 0xbe, 0x5d, 0x98, 0x0a, 0x68, 0x9c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0xbb, 0xd0, 0x49, 0x13, 0xe3, 0x13, 0x5f, +0x95, 0xd6, 0x8b, 0x15, 0x72, 0xc3, 0xf9, 0x66, +0x23, 0xcd, 0x84, 0x7b, 0xe3, 0x44, 0x40, 0x86, +0x8d, 0xbd, 0x50, 0x9f, 0x9f, 0xbc, 0x61, 0x1b, +0x99, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0xd6, 0x95, 0x1c, 0x03, 0xa1, +0xd8, 0x72, 0xeb, 0x45, 0xbf, 0xcf, 0xe7, 0x53, +0xb7, 0x11, 0x3e, 0x4b, 0x64, 0x2b, 0x31, 0x19, +0xe7, 0xc5, 0x4d, 0x71, 0x79, 0x2c, 0xcd, 0xb6, +0x82, 0x8e, 0x61, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0xdc, 0xf3, 0xf0, +0xf9, 0xab, 0x52, 0x4a, 0x9e, 0x38, 0x54, 0x05, +0x7b, 0xa8, 0xdb, 0x8f, 0x06, 0xc2, 0xf5, 0x4b, +0x5d, 0x80, 0x56, 0x27, 0x5e, 0x86, 0x30, 0x7a, +0x82, 0x09, 0xad, 0xe9, 0x84, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x01, +0x14, 0xfe, 0x0f, 0x3d, 0xfd, 0x90, 0x7f, 0xe8, +0xf1, 0xef, 0x39, 0x11, 0x42, 0x77, 0x75, 0x66, +0xc9, 0x96, 0x11, 0x95, 0x01, 0x2b, 0xe7, 0x82, +0xe9, 0x9a, 0x04, 0x60, 0x62, 0xce, 0xe3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0xb7, 0x1f, 0xf8, 0xdf, 0x8b, 0x23, 0xc2, +0xa3, 0x34, 0x77, 0x31, 0x5c, 0x59, 0xcb, 0x8f, +0xdc, 0xc1, 0x3c, 0xa2, 0x36, 0x97, 0xcc, 0x62, +0xd1, 0x9d, 0x3c, 0xaa, 0x0a, 0xb3, 0x1a, 0xd7, +0x15, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0x63, 0xb1, 0x2c, 0x2d, 0xd7, +0x12, 0x68, 0x60, 0x49, 0x33, 0x61, 0xb2, 0x2b, +0xcb, 0x8a, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x1c, 0x68, 0xfd, 0x3c, +0x3f, 0xaf, 0x82, 0xc3, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0x93, 0x85, 0x9a, +0x44, 0x1c, 0x4c, 0x42, 0xad, 0xa9, 0x6e, 0x52, +0x50, 0xb1, 0xaf, 0x8a, 0xd6, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc3, 0xb2, +0xa1, 0x45, 0xa3, 0xc7, 0xa0, 0xcc, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0x61, +0x8e, 0x94, 0xbd, 0x29, 0xc9, 0xe1, 0x3b, 0x6e, +0x5e, 0x2c, 0x1f, 0xa2, 0xc0, 0x83, 0xff, 0x7d, +0xd4, 0x78, 0xff, 0x8c, 0x8f, 0xb7, 0xf4, 0x06, +0x2e, 0x9c, 0x83, 0xde, 0x17, 0x5f, 0xf4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0x9b, 0x05, 0x09, 0x7f, 0xb3, 0x9b, 0x7a, +0x23, 0x5e, 0xe8, 0x0d, 0x3f, 0xfb, 0x67, 0xfa, +0x72, 0x59, 0xa1, 0x1b, 0x70, 0x81, 0x6c, 0x9a, +0x9d, 0x52, 0x9b, 0x03, 0x0b, 0x2f, 0x20, 0x83, +0x13, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x4c, 0xca, 0xf2, 0x98, 0x07, +0xe0, 0xae, 0x0c, 0x76, 0x13, 0x3e, 0x06, 0x71, +0xb2, 0x3d, 0x8b, 0x8a, 0x01, 0xaf, 0x7e, 0x9a, +0xca, 0x33, 0x13, 0xf5, 0xfe, 0xf8, 0x24, 0x3e, +0x68, 0xe9, 0x3b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0x14, 0x45, 0x6c, +0xec, 0x68, 0xe6, 0xd2, 0x92, 0x17, 0xb5, 0xf1, +0xad, 0xc2, 0xce, 0x3a, 0x8c, 0xdc, 0xea, 0x42, +0xb7, 0xa3, 0xed, 0xaa, 0xcc, 0x00, 0xaa, 0xe2, +0xb6, 0x44, 0xd4, 0xa4, 0x68, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0x1b, +0x38, 0x16, 0x57, 0x2a, 0xa3, 0x19, 0x0c, 0x8a, +0xb1, 0x26, 0xab, 0x91, 0xa1, 0xb5, 0x7a, 0xf1, +0x7d, 0x71, 0x43, 0x8a, 0x86, 0x34, 0x3f, 0x2a, +0xe3, 0x1b, 0x8f, 0xc0, 0x97, 0x84, 0x25, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0xdd, 0x4a, 0x8b, 0xaf, 0x26, 0x21, 0x69, +0x1b, 0x1b, 0x23, 0x3c, 0x37, 0xba, 0xd9, 0xb2, +0xff, 0xfd, 0x80, 0xac, 0x7b, 0x8f, 0xd1, 0xdd, +0x17, 0x8f, 0x77, 0xfd, 0xee, 0xf9, 0x18, 0x83, +0x4d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0xe8, 0x43, 0x5e, 0x2e, 0xe1, +0x6b, 0xfb, 0xbf, 0x88, 0xbe, 0xb3, 0xa2, 0xc2, +0xde, 0xb4, 0x4b, 0xa5, 0x25, 0x8a, 0xf1, 0x2b, +0x6f, 0x71, 0x38, 0xd8, 0xd4, 0xa6, 0x07, 0xb4, +0xb7, 0xff, 0xe8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0x81, 0xcd, 0xbe, +0xe4, 0xec, 0x00, 0x86, 0x59, 0x3a, 0x4b, 0xaa, +0x46, 0x68, 0x1d, 0xf1, 0x33, 0xbc, 0xa6, 0x79, +0x99, 0x66, 0x9f, 0x11, 0xea, 0x2d, 0x1e, 0x84, +0xc3, 0xf2, 0x39, 0xf1, 0x22, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0xee, +0xb4, 0xa4, 0x1e, 0x6c, 0x32, 0x30, 0xd0, 0xb7, +0x96, 0x16, 0x21, 0x20, 0x30, 0xf5, 0x05, 0x2a, +0x97, 0x26, 0x06, 0x44, 0xb7, 0x33, 0x83, 0xc3, +0x7e, 0xde, 0x15, 0xd9, 0x42, 0x60, 0x20, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0x32, 0x82, 0x7b, 0xcc, 0x72, 0x08, 0x40, +0x41, 0xe8, 0x95, 0x63, 0x52, 0x62, 0xf4, 0xe9, +0xfd, 0xed, 0x9b, 0x79, 0x39, 0xc2, 0x2c, 0xe7, +0x2c, 0xf6, 0x13, 0x5b, 0x91, 0x5e, 0x9e, 0x67, +0x7c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0x1e, 0x5e, 0x23, 0x1a, 0x27, +0xa3, 0x15, 0x98, 0xd4, 0x43, 0xad, 0xb9, 0x46, +0xe5, 0xb9, 0x5d, 0x1e, 0xfc, 0x27, 0x65, 0x50, +0xb7, 0xc4, 0x7d, 0x5f, 0x88, 0xcc, 0xe7, 0x2a, +0x12, 0x22, 0xdd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0x47, 0x45, 0xab, +0xff, 0x6d, 0x2d, 0xe4, 0x3b, 0xda, 0xc9, 0x51, +0x7f, 0xea, 0xbc, 0x64, 0x90, 0x52, 0xa5, 0x5f, +0x1d, 0xe4, 0xa9, 0x17, 0xa8, 0xf4, 0x7b, 0xe2, +0x2a, 0x1c, 0xe6, 0x9d, 0x3c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0x54, +0x26, 0xa4, 0x32, 0x86, 0x6d, 0x50, 0x4d, 0x8d, +0x74, 0x9c, 0x4a, 0xbd, 0x8f, 0x3b, 0x87, 0x47, +0x27, 0xbd, 0x75, 0x77, 0xbb, 0x02, 0x3e, 0xef, +0x38, 0x2b, 0x71, 0x14, 0xaf, 0x11, 0xde, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0x17, 0xc7, 0x42, 0xcc, 0x4c, 0x5c, 0xd9, +0xed, 0x7c, 0x30, 0xd3, 0x5e, 0xcd, 0x03, 0x23, +0xbf, 0x45, 0x47, 0xfd, 0x56, 0xc6, 0x00, 0xc7, +0x64, 0xfa, 0xc8, 0x0d, 0x3a, 0x27, 0x06, 0x93, +0x24, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0xee, 0xa7, 0x8c, 0x80, 0x6e, +0x29, 0x50, 0x16, 0xc8, 0x5a, 0x31, 0x1a, 0x1c, +0x7b, 0x34, 0x18, 0x4b, 0x5c, 0x77, 0x3c, 0xc8, +0x98, 0x6f, 0xcd, 0x5e, 0xd8, 0x45, 0x5b, 0xe2, +0xf0, 0xaf, 0x29, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0x02, 0x73, 0x66, +0xf4, 0xae, 0xf4, 0xa5, 0x36, 0x3b, 0xfd, 0xf0, +0x98, 0xb3, 0xf9, 0x1c, 0x8a, 0x38, 0x4e, 0xe8, +0xe2, 0xe8, 0xab, 0x19, 0x09, 0xb3, 0xe3, 0x3e, +0x0a, 0x01, 0x2b, 0x73, 0x28, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0x5d, +0xa6, 0xfc, 0xad, 0x46, 0x49, 0xc8, 0x3d, 0xf2, +0x62, 0xa9, 0xcd, 0x5a, 0xe4, 0x34, 0xf1, 0x86, +0x5b, 0x81, 0xa6, 0x22, 0x20, 0x3d, 0x72, 0xc2, +0xff, 0x2a, 0xc9, 0x54, 0xeb, 0xb9, 0x2a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x0f, 0x93, 0xe7, 0xd1, 0xc7, 0x71, 0xb5, +0x3f, 0xcc, 0x00, 0x1f, 0xaf, 0xa5, 0xbd, 0x2e, +0x2a, 0xbd, 0x61, 0x34, 0x94, 0x1f, 0x87, 0x03, +0x73, 0xe1, 0x1e, 0xe2, 0x24, 0x43, 0x1f, 0x87, +0x07, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0xac, 0x78, 0x62, 0x30, 0xc0, +0xbc, 0x38, 0x1e, 0x14, 0x64, 0x42, 0xb7, 0x84, +0xf1, 0xf7, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x6f, 0x4c, 0x8e, 0xdc, +0xcd, 0xcb, 0x81, 0x8b, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x08, 0x3b, 0xe6, +0xad, 0xa1, 0x3b, 0xe8, 0xe1, 0xf4, 0x95, 0x2d, +0x7b, 0x6f, 0xe7, 0x3b, 0xfd, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x16, +0xb9, 0xff, 0xe5, 0x85, 0xb5, 0xd8, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0x3d, +0x3d, 0xca, 0x2c, 0x97, 0x61, 0xb4, 0x09, 0xec, +0x64, 0x7d, 0x82, 0x2f, 0xdb, 0x00, 0x3b, 0x24, +0x00, 0x61, 0x33, 0xcc, 0xc1, 0x95, 0x5a, 0xa8, +0x35, 0xd1, 0xa4, 0x59, 0x9d, 0xcc, 0xff, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0x08, 0xcf, 0xc4, 0xac, 0xca, 0xc2, 0x19, +0x73, 0x1a, 0xb5, 0x8e, 0xb2, 0x78, 0x31, 0xeb, +0x2e, 0x72, 0x00, 0x5b, 0x22, 0xfc, 0x62, 0x1d, +0xd7, 0x55, 0x0e, 0x6a, 0xc1, 0xd6, 0x51, 0x25, +0x84, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0xeb, 0x7d, 0xa7, 0x61, 0x6a, +0x7c, 0xf7, 0x48, 0x3c, 0xb0, 0x2b, 0x63, 0xdf, +0x19, 0x5f, 0x9f, 0x63, 0x6c, 0x11, 0x7f, 0x10, +0x30, 0x00, 0x65, 0x13, 0x3a, 0xf4, 0x9c, 0x0e, +0xf8, 0xd7, 0x04, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x94, 0x21, 0x5d, +0x13, 0x68, 0x06, 0xe6, 0x7a, 0xea, 0xa4, 0x9c, +0x79, 0x6e, 0xf3, 0xb5, 0xe4, 0x87, 0x2f, 0xb3, +0xd8, 0x7f, 0x37, 0x4d, 0x71, 0x3b, 0xc7, 0x58, +0xa8, 0x65, 0xb3, 0x13, 0x0c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0xf0, +0x75, 0x27, 0x3c, 0x24, 0xd0, 0x8b, 0x54, 0x4c, +0x2d, 0x3c, 0xb5, 0x23, 0x27, 0x2e, 0x4f, 0x9d, +0x03, 0xf2, 0x46, 0xcc, 0x89, 0xe4, 0x07, 0xe7, +0x4e, 0xf5, 0x59, 0xce, 0x6c, 0xd5, 0xc6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0x9a, 0xdb, 0xa0, 0xe3, 0x58, 0xfd, 0xcd, +0x21, 0x10, 0x66, 0xf9, 0xdf, 0xf2, 0x42, 0xf3, +0xa3, 0xb4, 0x04, 0x86, 0x2f, 0x4f, 0xe9, 0xed, +0xe9, 0x85, 0xa3, 0x81, 0x7f, 0x7b, 0xcc, 0xd6, +0xce, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0xfe, 0x2a, 0x08, 0x17, 0xbc, +0x04, 0x1b, 0x13, 0xeb, 0x3b, 0x3f, 0x09, 0x62, +0x45, 0x36, 0xe2, 0x23, 0x93, 0x56, 0xe2, 0x77, +0x08, 0x4f, 0xb2, 0xe5, 0xdc, 0x14, 0x02, 0x54, +0xaf, 0x58, 0xc6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0xc6, 0x90, 0xf0, +0x44, 0x34, 0x87, 0x57, 0xb1, 0x39, 0x37, 0x2c, +0x71, 0x97, 0x3e, 0x02, 0x8a, 0x0e, 0xf6, 0xbe, +0x1a, 0x2f, 0xfc, 0x10, 0x1a, 0x60, 0xab, 0xd6, +0x2a, 0x41, 0xb3, 0x14, 0xc6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0x6f, +0x71, 0xa4, 0x85, 0x87, 0xd7, 0xd5, 0xbb, 0xcb, +0xca, 0x4a, 0xbc, 0x5a, 0x8b, 0x40, 0xe4, 0x55, +0x79, 0xb6, 0xa9, 0x6e, 0xb2, 0x7c, 0x72, 0xe2, +0xa4, 0x6b, 0xc5, 0x57, 0x8d, 0xfc, 0x27, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0xe9, 0xc6, 0x27, 0x6d, 0x6a, 0x4d, 0x7c, +0x0a, 0x16, 0x8e, 0x68, 0xf9, 0xd5, 0x60, 0x78, +0xaf, 0x7d, 0x5e, 0x05, 0x4f, 0x19, 0xc8, 0x07, +0x4d, 0x26, 0x4a, 0x16, 0xc3, 0x42, 0xd4, 0x2d, +0x5c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0xdf, 0xb4, 0x97, 0xf1, 0x96, +0xd9, 0x7d, 0xa2, 0x96, 0xcf, 0x3d, 0xe8, 0xdd, +0x03, 0x16, 0xcb, 0x74, 0x95, 0xcd, 0xe5, 0xd5, +0x28, 0x4f, 0xb2, 0xa9, 0xc6, 0x6f, 0x55, 0xce, +0xb4, 0xca, 0xc5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x00, 0xd6, 0x2a, +0xa4, 0x80, 0x06, 0x2c, 0x85, 0x52, 0xe7, 0x5c, +0x38, 0xe8, 0xb8, 0x3e, 0x03, 0x1a, 0xbb, 0x8e, +0xfa, 0xfe, 0x53, 0x71, 0xec, 0xb6, 0x2f, 0x9a, +0xdb, 0xd8, 0xad, 0xe4, 0x7f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x92, +0xa4, 0xc8, 0x67, 0x21, 0x87, 0x55, 0xf6, 0x90, +0x44, 0x92, 0xa7, 0xeb, 0x6e, 0x3f, 0x9f, 0x66, +0xe1, 0x62, 0xbd, 0xb7, 0x63, 0xce, 0x63, 0x8a, +0xa8, 0xea, 0xae, 0x22, 0x0e, 0x8a, 0xf5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0xbe, 0xef, 0xd6, 0xe9, 0x7f, 0xa4, 0xa6, +0x86, 0x2d, 0x92, 0x46, 0x8a, 0x4d, 0xa6, 0x29, +0xb3, 0xb8, 0x44, 0xa0, 0x34, 0x06, 0x02, 0x62, +0xcc, 0x50, 0x68, 0x46, 0xd0, 0x5a, 0x2f, 0x12, +0xfc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0x01, 0xb1, 0x2b, 0xdc, 0x14, +0x21, 0xb7, 0xfd, 0x75, 0x6a, 0x63, 0xed, 0xf4, +0xb6, 0x6d, 0x21, 0xf1, 0xb4, 0xa9, 0xb6, 0xbb, +0x74, 0x69, 0x0d, 0x77, 0x0c, 0x1b, 0xfa, 0x66, +0x0a, 0xf3, 0xd7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0x4e, 0x72, 0x66, +0x4d, 0x25, 0x66, 0x02, 0xae, 0xc9, 0x59, 0xb5, +0xa1, 0x7b, 0x54, 0x0a, 0xf1, 0xe8, 0xf2, 0x60, +0xb5, 0x54, 0x3e, 0xd1, 0x02, 0x0a, 0x43, 0xaa, +0x19, 0xb5, 0x61, 0x29, 0x39, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0x5e, +0xe9, 0x77, 0x56, 0xd6, 0x7f, 0x56, 0x7a, 0x0d, +0xb6, 0xa3, 0x27, 0x8e, 0x94, 0x89, 0x09, 0x7c, +0xb1, 0xec, 0x4c, 0x69, 0x1f, 0xa2, 0x56, 0x37, +0xb3, 0x6f, 0x2b, 0xdf, 0x66, 0xb8, 0xcc, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x97, 0x01, 0xb2, 0xfa, 0x5d, 0x16, 0x2f, +0x0e, 0xc6, 0x09, 0x21, 0xa9, 0x21, 0xe1, 0x51, +0x89, 0x2a, 0x6e, 0xc9, 0x20, 0x3e, 0xe6, 0xe9, +0x2c, 0x23, 0x39, 0x23, 0x84, 0x8d, 0x22, 0x19, +0xf8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0x5c, 0xe2, 0xab, 0x57, 0xa3, +0x64, 0xdf, 0x2c, 0x31, 0xc8, 0x3a, 0x15, 0x8b, +0xc8, 0x50, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x64, 0x64, 0x14, 0x9b, +0xa7, 0x88, 0xa7, 0xfb, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0xc0, 0x36, 0xe2, +0x3a, 0xeb, 0xd3, 0x4e, 0x88, 0x9c, 0x20, 0x5f, +0xd1, 0xba, 0x08, 0x3b, 0x92, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6b, 0xd2, +0x19, 0x90, 0xc9, 0xff, 0x8b, 0xb0, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0xf3, +0x9f, 0x22, 0x2e, 0x44, 0x75, 0xc4, 0xc7, 0xa3, +0x02, 0x89, 0xef, 0xad, 0xcb, 0xfe, 0xee, 0x28, +0x51, 0x8e, 0x7a, 0x67, 0x64, 0xe5, 0xf6, 0xe1, +0xa7, 0x79, 0x6a, 0x0f, 0x47, 0x50, 0xb9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0x16, 0x96, 0xa3, 0x99, 0x8e, 0x98, 0x1c, +0x35, 0xdd, 0x7a, 0x8b, 0x2f, 0x95, 0xaf, 0x89, +0xdf, 0x7f, 0x7b, 0xcd, 0xc6, 0x75, 0x76, 0x7f, +0x6b, 0xd3, 0x17, 0x07, 0x97, 0xe5, 0xee, 0xdd, +0xf8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0xbb, 0xc7, 0xfd, 0x4d, 0x5e, +0xe2, 0x96, 0x06, 0x24, 0x69, 0xd1, 0x1b, 0x89, +0xca, 0x11, 0xc6, 0x00, 0x34, 0xa0, 0xb7, 0x6a, +0x50, 0xc8, 0xe5, 0xfb, 0x21, 0x2b, 0x64, 0xbc, +0xc8, 0x90, 0x0c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0x92, 0x2c, 0x4a, +0x67, 0xaa, 0x9a, 0x4f, 0x0b, 0x0c, 0x7e, 0x39, +0x4a, 0x5e, 0xdd, 0x98, 0x7a, 0x27, 0x15, 0x56, +0xb4, 0x68, 0xa4, 0x24, 0x6d, 0x01, 0xa1, 0x5d, +0xf0, 0x71, 0xfd, 0xc6, 0x93, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0x9a, +0x2b, 0x7f, 0xaa, 0x49, 0x18, 0xec, 0x54, 0xaf, +0x5b, 0x9f, 0xe2, 0x0c, 0xd3, 0xbc, 0x2f, 0x51, +0x05, 0xbc, 0x28, 0xe9, 0xe9, 0xb8, 0x36, 0x6f, +0x42, 0x19, 0x1d, 0x43, 0x11, 0x2c, 0x45, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x7a, 0x1e, 0xb4, 0xe2, 0x25, 0xef, 0x6f, +0x45, 0xbc, 0xab, 0xc0, 0x67, 0xda, 0x3c, 0xff, +0x04, 0x35, 0xac, 0xc2, 0x94, 0x08, 0x21, 0x62, +0xf5, 0x05, 0x77, 0xd2, 0xcf, 0x4c, 0x51, 0x60, +0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0xe7, 0xc4, 0x19, 0x63, 0x6f, +0xa1, 0xe9, 0xc8, 0x63, 0x0a, 0x75, 0x5e, 0x25, +0xa2, 0xeb, 0x64, 0x7a, 0xb4, 0xaf, 0xd9, 0xd0, +0xce, 0x89, 0x96, 0x23, 0x33, 0x2e, 0x2f, 0x31, +0xc9, 0x27, 0x17, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0x9f, 0x5e, 0x36, +0x05, 0x1a, 0xe0, 0x1d, 0x61, 0xc9, 0xda, 0x2e, +0x6b, 0x35, 0x5c, 0x9b, 0x56, 0xc4, 0xd8, 0x3d, +0x68, 0x9f, 0x3c, 0x12, 0x79, 0x6e, 0xa6, 0x66, +0x8a, 0xf8, 0xd3, 0xac, 0x60, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0x54, +0x42, 0x32, 0x1a, 0xe7, 0xa1, 0xb8, 0x41, 0xc2, +0xad, 0x32, 0x55, 0xa4, 0x18, 0x41, 0xd2, 0xa5, +0x1a, 0x9e, 0x21, 0x06, 0x0a, 0xf4, 0x2b, 0xb1, +0x54, 0x50, 0x0e, 0x17, 0x8b, 0xee, 0x88, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0x11, 0x26, 0xed, 0x21, 0xa7, 0x0d, 0x6c, +0x17, 0xfc, 0xc9, 0xc6, 0x44, 0xaa, 0xfa, 0x16, +0x12, 0xc5, 0x44, 0xc3, 0xa4, 0x1a, 0xb8, 0x5e, +0xb3, 0xb3, 0xc2, 0x40, 0xee, 0xf2, 0x9c, 0x1e, +0xb3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0x5b, 0x77, 0x93, 0xf4, 0x2a, +0xff, 0x16, 0x8b, 0xde, 0xd2, 0x50, 0x1f, 0x98, +0x93, 0x91, 0x56, 0xc4, 0xa4, 0x7f, 0x56, 0xec, +0xf5, 0x49, 0xf5, 0xdf, 0x1e, 0x59, 0xf4, 0x3a, +0x76, 0x12, 0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0x2a, 0x12, 0xf6, +0x67, 0x75, 0xec, 0xd1, 0x41, 0x32, 0x2c, 0x23, +0x55, 0x17, 0x02, 0x2b, 0x93, 0x03, 0xc2, 0x82, +0x8c, 0x5a, 0x1a, 0x51, 0xac, 0x2e, 0x53, 0xa6, +0x14, 0x2b, 0xf7, 0x86, 0xaf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0xa8, +0x38, 0x75, 0x15, 0x8f, 0x8f, 0x6e, 0x69, 0x62, +0x99, 0x7b, 0x43, 0x2b, 0xb8, 0x2e, 0x90, 0xb4, +0x16, 0xaa, 0x45, 0x0b, 0x99, 0xb0, 0xd3, 0xdb, +0x1a, 0xb5, 0x47, 0x03, 0x30, 0xda, 0x6c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0xcb, 0x78, 0x24, 0xf1, 0xcd, 0x35, 0x71, +0x09, 0x9d, 0xbd, 0x52, 0x40, 0x67, 0x99, 0x42, +0xcc, 0x9b, 0xfd, 0x63, 0x3c, 0xb6, 0xc4, 0x66, +0x4b, 0xb9, 0xf8, 0xa6, 0xe9, 0xd7, 0xe3, 0xf7, +0xd3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0x95, 0x36, 0xc3, 0xed, 0xa0, +0xb3, 0xb6, 0x14, 0x38, 0x1d, 0x88, 0xbf, 0x41, +0x83, 0x3d, 0x5f, 0x70, 0x81, 0x2b, 0xe1, 0x0b, +0xc1, 0xa1, 0x36, 0xda, 0x39, 0xde, 0x47, 0xbc, +0x7a, 0xc8, 0x4d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x77, 0xff, 0x91, +0x5e, 0x45, 0x1a, 0x93, 0x7c, 0xe1, 0x78, 0x3f, +0xda, 0x1b, 0x93, 0x92, 0xb3, 0xf3, 0x8b, 0x03, +0xd4, 0x5c, 0x77, 0xd7, 0xf9, 0x40, 0x2f, 0x5d, +0x92, 0x2b, 0x66, 0x6c, 0x62, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0x5d, +0xd8, 0xab, 0xd2, 0xc3, 0x94, 0x42, 0xf3, 0x22, +0x6f, 0xf5, 0x73, 0x12, 0xa9, 0xcb, 0x14, 0xe9, +0x31, 0x11, 0xdc, 0x3d, 0xea, 0xb7, 0xdf, 0x5e, +0x04, 0x18, 0x8b, 0xcd, 0x44, 0x48, 0x31, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0xaf, 0xe5, 0x36, 0x12, 0x01, 0x9a, 0xd7, +0x4e, 0x1d, 0x6d, 0xe9, 0x6b, 0x2d, 0x99, 0x3f, +0x32, 0x29, 0xe6, 0x79, 0xee, 0xb6, 0x6d, 0xcd, +0xc6, 0x55, 0xa6, 0x8c, 0xcb, 0x5a, 0x0a, 0x1b, +0x40, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0xbc, 0x3d, 0x79, 0x50, 0xfe, +0xf4, 0xcd, 0xbb, 0xf1, 0x36, 0xd5, 0x0b, 0xa0, +0x8f, 0x9e, 0x89, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xdf, 0x95, 0xfe, 0x36, +0x47, 0x94, 0x98, 0x71, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0xe7, 0xaa, 0xc3, +0x12, 0x13, 0xb9, 0xd8, 0x52, 0x3c, 0x3c, 0x44, +0x47, 0xf9, 0xdd, 0x0b, 0x9d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdf, 0x6f, +0x6c, 0xa7, 0x53, 0x2d, 0xd8, 0x28, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0x5f, +0x36, 0x26, 0x22, 0x02, 0x36, 0x46, 0xea, 0x84, +0x36, 0xb0, 0x89, 0xba, 0x94, 0x88, 0x18, 0x8f, +0x88, 0x06, 0xa8, 0xae, 0xc8, 0xd1, 0xfc, 0x75, +0xf7, 0xba, 0x54, 0xfc, 0x49, 0x29, 0xa9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0x3c, 0xbe, 0x57, 0xf9, 0x40, 0xa6, 0x93, +0xe0, 0x8a, 0x9b, 0xe7, 0x32, 0x67, 0xdb, 0x82, +0xfd, 0xf1, 0x52, 0x83, 0xac, 0x25, 0x4f, 0x74, +0x4d, 0xd2, 0x49, 0x27, 0x10, 0xc7, 0x9c, 0xe1, +0xe4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0xe0, 0xec, 0x87, 0x05, 0xb7, +0x40, 0x6c, 0x3e, 0xb6, 0x54, 0x73, 0x9f, 0x31, +0xe1, 0x05, 0x6f, 0xf4, 0x72, 0x30, 0x61, 0x7d, +0x6c, 0x5e, 0x92, 0xb9, 0x7e, 0x17, 0xc4, 0x7d, +0x2c, 0x1b, 0x7a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0xaa, 0x5c, 0x4e, +0xda, 0x3c, 0x49, 0xaa, 0x3a, 0xfe, 0x2c, 0xf3, +0x73, 0x9e, 0x79, 0xe1, 0x4f, 0x51, 0xfc, 0xef, +0x5a, 0x33, 0xbc, 0xa3, 0x12, 0xd7, 0x19, 0x6c, +0x07, 0xcd, 0xed, 0xb2, 0x76, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0x0a, +0x3d, 0xac, 0x3a, 0x32, 0x8b, 0xbc, 0x10, 0x22, +0x72, 0x3d, 0x41, 0xe8, 0x51, 0xbc, 0xe7, 0x0a, +0xb0, 0x94, 0xf5, 0xd5, 0x1e, 0xff, 0x24, 0x99, +0xde, 0xaa, 0x63, 0x9a, 0x7b, 0x61, 0x17, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0xc9, 0x02, 0xc1, 0xba, 0x5e, 0xb7, 0x09, +0x6c, 0x9e, 0xbc, 0xd5, 0x14, 0x64, 0xc4, 0xcb, +0xb4, 0x05, 0x06, 0x50, 0x34, 0xa6, 0x39, 0xe6, +0xc8, 0x0f, 0x32, 0x72, 0x8e, 0x1b, 0xd4, 0xf1, +0xfc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0xd1, 0xab, 0xfd, 0xa3, 0x74, +0x37, 0xe5, 0x75, 0x69, 0xc5, 0xf2, 0xb9, 0xe0, +0x40, 0xf1, 0xf9, 0xdc, 0x36, 0x99, 0xe5, 0xda, +0x56, 0xb9, 0x4e, 0xd9, 0xdc, 0x99, 0x38, 0x6b, +0xb1, 0xf6, 0x44, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0xe7, 0xe0, 0x57, +0xa2, 0x36, 0x0e, 0xb3, 0x6c, 0x8e, 0x7e, 0xb7, +0x95, 0xfe, 0x72, 0xd6, 0xae, 0x5a, 0x98, 0x05, +0xe2, 0xdf, 0xcf, 0x0e, 0xf6, 0x83, 0x6f, 0x27, +0xd2, 0xe5, 0x26, 0x82, 0x8a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0xc7, +0x72, 0x63, 0xe9, 0x55, 0x5d, 0x40, 0x19, 0x1b, +0x2f, 0x72, 0x99, 0xa2, 0x45, 0x56, 0x2b, 0xa9, +0x16, 0x16, 0xff, 0x08, 0xd8, 0xba, 0x6f, 0x48, +0xaf, 0x05, 0x6b, 0xc4, 0xc6, 0xeb, 0x05, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0xf0, 0x5a, 0x16, 0x0b, 0xdb, 0xc0, 0x07, +0x47, 0xf2, 0x00, 0xfc, 0x83, 0x11, 0x26, 0x70, +0x29, 0x14, 0x5f, 0xe8, 0x3a, 0x25, 0x62, 0xb0, +0xca, 0x5c, 0x4e, 0x84, 0x94, 0xa4, 0xb6, 0x83, +0x0c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0xcd, 0x92, 0xbf, 0x22, 0x35, +0xb0, 0x50, 0x9b, 0xea, 0x6c, 0x20, 0x14, 0xc7, +0x2c, 0xa5, 0x09, 0x9a, 0x1f, 0x8d, 0x97, 0xe3, +0xe9, 0xe1, 0xce, 0x55, 0x55, 0x69, 0x6a, 0xf5, +0x52, 0xdf, 0x48, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0xb1, 0x6a, 0x18, +0x31, 0x12, 0xa9, 0x53, 0x48, 0xda, 0xf4, 0xfe, +0x9c, 0x5b, 0xdc, 0xd2, 0x92, 0x23, 0x9f, 0x4f, +0xa3, 0x56, 0x18, 0x96, 0x23, 0xe3, 0xce, 0x8d, +0xb9, 0x4b, 0x53, 0x24, 0x6b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0x85, +0xb0, 0xfe, 0x3b, 0xc9, 0xee, 0xfa, 0x9c, 0x14, +0xad, 0x99, 0x30, 0x0c, 0x89, 0xc6, 0xf1, 0x54, +0x50, 0x58, 0x62, 0x90, 0x27, 0xd4, 0xdd, 0xca, +0x8e, 0xa3, 0xca, 0x63, 0xbc, 0x93, 0x96, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0x2b, 0x05, 0x9a, 0x54, 0x49, 0xd8, 0xbf, +0x1d, 0x49, 0x4e, 0x7a, 0x6b, 0x46, 0xbc, 0x15, +0x10, 0x2e, 0x4f, 0x94, 0x33, 0xea, 0x6f, 0xd0, +0x90, 0x79, 0x97, 0x75, 0xcc, 0xf6, 0x41, 0x24, +0xfd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0x65, 0x92, 0x20, 0xd6, 0x5c, +0x7a, 0xf0, 0x93, 0x3b, 0xca, 0xac, 0x21, 0xe3, +0xed, 0x21, 0xcc, 0xff, 0x83, 0x5a, 0x54, 0x5e, +0xb0, 0x14, 0x33, 0x1b, 0x92, 0x88, 0x03, 0x27, +0x7b, 0x62, 0x08, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0x2c, 0x75, 0x45, +0x8c, 0x5c, 0x7a, 0xb8, 0x57, 0x8a, 0xd9, 0x05, +0xfd, 0x07, 0x43, 0x55, 0x5f, 0x4d, 0x28, 0xe1, +0xce, 0x3c, 0xfc, 0xd1, 0x31, 0xa4, 0x53, 0x85, +0x9b, 0xfb, 0xd7, 0x17, 0xc6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0x77, +0xa0, 0x85, 0x63, 0x55, 0xe2, 0x35, 0xd6, 0x21, +0x2d, 0xe8, 0x1d, 0x0d, 0x93, 0x66, 0x9e, 0xb4, +0xfb, 0x9e, 0xa5, 0x5f, 0x3e, 0x0c, 0x81, 0x97, +0xc4, 0x75, 0x8c, 0x80, 0xe9, 0x65, 0x32, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0x35, 0x7e, 0xd4, 0x25, 0x50, 0x29, 0x39, +0x7c, 0xd2, 0x14, 0x2a, 0xfd, 0x33, 0xb0, 0xcb, +0xd5, 0x67, 0x69, 0x4e, 0xca, 0x40, 0x52, 0x05, +0xa2, 0x38, 0x58, 0x1f, 0xe9, 0xe5, 0xd5, 0x9e, +0x80, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0x5c, 0xda, 0x94, 0xee, 0x55, +0x10, 0x4e, 0x07, 0xbc, 0x1a, 0x7b, 0xee, 0xda, +0x16, 0xb2, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x81, 0x87, 0x90, 0x73, +0x88, 0xfe, 0x2f, 0xdd, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0x36, 0xf1, 0x46, +0x83, 0xd1, 0xc2, 0xc1, 0x38, 0x14, 0x10, 0x01, +0xd4, 0xa5, 0x62, 0x82, 0x87, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf5, 0x13, +0xcd, 0xb6, 0x35, 0xe3, 0x76, 0xe4, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0x3f, +0xf2, 0x34, 0x8b, 0x37, 0x1a, 0x97, 0xf7, 0x69, +0x2c, 0x19, 0xd1, 0xb5, 0xae, 0xaa, 0x32, 0xd5, +0x5b, 0x15, 0x0f, 0x62, 0x10, 0x51, 0x21, 0xf5, +0xd9, 0x24, 0x54, 0x81, 0x6d, 0x23, 0x63, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0xf8, 0x09, 0xdd, 0x37, 0x3c, 0x33, 0xc6, +0xec, 0xe1, 0x2b, 0xdc, 0x42, 0x4c, 0xa7, 0x57, +0x13, 0x41, 0xfc, 0x30, 0xb8, 0x71, 0xc0, 0x81, +0x3b, 0x23, 0x87, 0x5a, 0xa3, 0xfd, 0x99, 0xb5, +0x14, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0xa3, 0x83, 0x84, 0x64, 0x43, +0x1b, 0xce, 0x25, 0x9a, 0x24, 0x0b, 0xc4, 0xb5, +0xc2, 0x46, 0x29, 0x6e, 0xed, 0x73, 0x4b, 0x63, +0xda, 0xc3, 0x5a, 0xdd, 0xe4, 0x54, 0xb3, 0xdf, +0x6c, 0xd2, 0x9b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0x58, 0xc9, 0x7a, +0x5c, 0x7f, 0x1b, 0x2b, 0xa2, 0x16, 0x60, 0x2c, +0x8d, 0xbd, 0x4f, 0xf4, 0x85, 0x7a, 0x5b, 0xbe, +0x3b, 0x67, 0xb3, 0x6a, 0xad, 0x9c, 0xd5, 0xa6, +0xa3, 0x01, 0x4d, 0xfe, 0x25, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0x03, +0x10, 0x20, 0x41, 0x35, 0x60, 0x2f, 0x3d, 0x88, +0xf8, 0x24, 0xbb, 0x9e, 0xd3, 0xe9, 0x79, 0xc6, +0x97, 0x99, 0xeb, 0xca, 0x3d, 0xb8, 0xc1, 0x48, +0xc3, 0xd2, 0x88, 0xe7, 0xc3, 0x34, 0x8e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0x61, 0x64, 0x88, 0x40, 0x6f, 0x59, 0x88, +0x86, 0x21, 0xe5, 0x09, 0xfc, 0x21, 0xd0, 0x26, +0xf1, 0x8e, 0xc9, 0xd3, 0x26, 0x87, 0x73, 0x83, +0x83, 0xd7, 0x00, 0x14, 0xcf, 0x77, 0x56, 0xea, +0xe8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0xb5, 0x02, 0xf8, 0x7f, 0x98, +0xbc, 0xf5, 0x80, 0xdb, 0xc6, 0x1b, 0xc2, 0x48, +0xd3, 0x31, 0x70, 0x8a, 0x61, 0x96, 0x30, 0x49, +0x14, 0x50, 0x74, 0x88, 0x47, 0x5a, 0x5a, 0x5d, +0x32, 0xea, 0x9b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0x98, 0x04, 0x65, +0xcf, 0xc0, 0xba, 0xd6, 0xf1, 0x69, 0xf7, 0x7f, +0xc4, 0xea, 0x09, 0x55, 0x3a, 0xd5, 0xa3, 0x27, +0x31, 0xc8, 0xed, 0x75, 0x5e, 0xbf, 0x42, 0x0e, +0x55, 0x00, 0x2a, 0x61, 0xd2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x40, +0x73, 0x9f, 0x90, 0x1a, 0x79, 0x91, 0x99, 0x04, +0xe8, 0x2f, 0x96, 0x36, 0x08, 0x5a, 0x77, 0x4d, +0x33, 0x45, 0x9d, 0x9b, 0xf9, 0x1b, 0x89, 0x68, +0x5b, 0x6b, 0xc6, 0xe2, 0xdb, 0xe8, 0x4d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0x93, 0xbe, 0x91, 0xb9, 0x14, 0xe9, 0x68, +0x24, 0x02, 0x8c, 0x39, 0xc6, 0xe6, 0x03, 0x8c, +0x1c, 0x5c, 0xee, 0xe2, 0x39, 0x12, 0x84, 0xe9, +0x50, 0x27, 0x87, 0xa4, 0xc1, 0x1c, 0x54, 0xc4, +0xad, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0x0c, 0xcd, 0x77, 0x01, 0x64, +0x7b, 0x8a, 0x04, 0x45, 0x70, 0x1f, 0x91, 0xa4, +0x1c, 0xc2, 0x89, 0xfa, 0x9d, 0xd1, 0x06, 0xfe, +0x49, 0xdb, 0xb6, 0xec, 0x69, 0xbb, 0xb7, 0x26, +0x47, 0x83, 0xc9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0x55, 0xf9, 0x39, +0x51, 0x66, 0x9d, 0x0b, 0xd5, 0x21, 0xb0, 0x51, +0x1b, 0xe2, 0x34, 0x0f, 0xa6, 0x2c, 0x61, 0xdd, +0xa9, 0x63, 0xd2, 0xb7, 0x51, 0x0a, 0xe4, 0x72, +0x9e, 0xd3, 0xab, 0x71, 0xa2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0xe3, +0x87, 0x49, 0x1d, 0xfa, 0x15, 0xcc, 0x9e, 0x8a, +0x1c, 0xfe, 0x14, 0xdc, 0xed, 0xff, 0xb5, 0xdd, +0xd9, 0x40, 0xeb, 0x31, 0xd3, 0xb9, 0x5b, 0xfb, +0x7e, 0x4f, 0x83, 0x91, 0xd8, 0xa8, 0x50, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0x68, 0x04, 0xd8, 0x84, 0xee, 0x10, 0xe4, +0x03, 0xb4, 0x1b, 0xcd, 0x33, 0x42, 0x6f, 0x39, +0x74, 0x0c, 0x89, 0x12, 0x2e, 0x6e, 0x63, 0x5e, +0x3d, 0xf6, 0xc1, 0x4d, 0x80, 0x9e, 0x61, 0xde, +0x39, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0x3f, 0xa2, 0x7e, 0x45, 0xf6, +0x90, 0xa4, 0xec, 0x87, 0x36, 0x01, 0x23, 0xe7, +0xe1, 0x72, 0xda, 0xf9, 0xf5, 0xc3, 0x2b, 0x7e, +0x16, 0x90, 0xae, 0x10, 0x71, 0x61, 0x10, 0x32, +0x3b, 0xd8, 0x86, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x21, 0x0a, 0x48, +0xab, 0x6d, 0xb1, 0x6d, 0x86, 0x1d, 0xe0, 0xa6, +0x50, 0xdf, 0xf3, 0x97, 0x4b, 0xcf, 0x96, 0xc3, +0x00, 0x94, 0x33, 0x7b, 0xb3, 0xcf, 0x21, 0x55, +0x6c, 0x84, 0xc6, 0x34, 0x5b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0x42, +0x23, 0x58, 0x7c, 0x9b, 0xea, 0x8c, 0xc7, 0x09, +0xa7, 0x21, 0x67, 0x99, 0x6a, 0xac, 0x1e, 0xb2, +0x2e, 0xef, 0x0a, 0xd9, 0xe8, 0x13, 0xbf, 0xb9, +0x60, 0x67, 0xcc, 0x03, 0xb0, 0x9b, 0xaf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0x68, 0xe0, 0x0a, 0x4c, 0xc9, 0x0f, 0xb6, +0xf1, 0xd5, 0xfc, 0x05, 0xaf, 0xa4, 0x06, 0x22, +0x52, 0x7e, 0xab, 0xd7, 0xee, 0xdb, 0xf4, 0x44, +0xcf, 0xe7, 0xb1, 0xa9, 0xe8, 0xee, 0xd4, 0xf6, +0x48, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0x0f, 0x72, 0xc7, 0x20, 0x1d, +0x7a, 0x88, 0x9b, 0x96, 0xd8, 0xa2, 0xa7, 0x76, +0x9c, 0x52, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x06, 0x18, 0x86, 0xe9, +0x5c, 0x56, 0x8b, 0x8f, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x96, 0x7f, 0x42, +0x6e, 0xd6, 0x84, 0x72, 0x1f, 0x73, 0x08, 0xbe, +0xc8, 0x5c, 0xa1, 0x9a, 0x1b, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0xd8, +0x23, 0xaf, 0xa0, 0x19, 0x58, 0x0b, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0x6c, +0x55, 0xd4, 0x86, 0xd9, 0x44, 0x11, 0x6b, 0xfb, +0x1a, 0xf6, 0xb0, 0x6b, 0x94, 0xee, 0xdf, 0x6f, +0x1a, 0xfb, 0xed, 0x1b, 0x37, 0xca, 0x18, 0x86, +0x0c, 0x9b, 0x7a, 0xa9, 0x89, 0xcc, 0x62, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0xbb, 0xe3, 0xbb, 0x75, 0x22, 0xc4, 0x0c, +0x04, 0xf8, 0x44, 0x06, 0x7d, 0xc8, 0x10, 0xab, +0xaa, 0x5c, 0x09, 0xf9, 0x72, 0xfa, 0x8a, 0xfb, +0x61, 0x8b, 0x7b, 0xec, 0x70, 0xc9, 0xfc, 0x0b, +0xa4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0xa6, 0x61, 0x96, 0xae, 0x9e, +0xfd, 0xe6, 0x16, 0x9c, 0x2e, 0xa5, 0x39, 0x82, +0xcd, 0x1d, 0xa2, 0xb1, 0x5d, 0x5b, 0x5c, 0x2e, +0x93, 0x7d, 0x2a, 0xec, 0x8d, 0x02, 0xde, 0x9e, +0x8b, 0x84, 0xc5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0xb9, 0x92, 0xbe, +0x8e, 0x05, 0x58, 0x3c, 0xdb, 0x68, 0xbc, 0x58, +0xbd, 0x2d, 0x82, 0x1f, 0x7e, 0xe2, 0x2f, 0x0a, +0x6f, 0x6b, 0xbd, 0xc5, 0x60, 0x76, 0x20, 0xf7, +0xa2, 0x12, 0x42, 0x1e, 0x1b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x9b, +0xe3, 0xcb, 0x35, 0x96, 0x31, 0xb4, 0x07, 0xba, +0xba, 0xd6, 0xe4, 0x0d, 0x24, 0x3a, 0x9b, 0xb0, +0x35, 0x3c, 0xdc, 0xaf, 0xb0, 0x97, 0x54, 0x6a, +0x66, 0xed, 0x21, 0x28, 0x39, 0xd8, 0x62, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0x3a, 0x52, 0x6e, 0x5e, 0x21, 0x65, 0xeb, +0x9e, 0xdd, 0x2c, 0x0e, 0x68, 0x6a, 0x47, 0x61, +0x1f, 0x0d, 0x17, 0xc7, 0xd9, 0xe4, 0xf8, 0x8b, +0xed, 0x4d, 0xd3, 0x7b, 0x83, 0x4c, 0xcc, 0x48, +0x19, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0x5f, 0xb9, 0x55, 0x31, 0x78, +0xab, 0xb8, 0x23, 0xa9, 0x5c, 0x3f, 0xd5, 0x72, +0x46, 0x12, 0x09, 0x67, 0x72, 0x10, 0xa8, 0xe6, +0x2e, 0x41, 0x23, 0xba, 0x22, 0xed, 0x07, 0x4e, +0x3c, 0x9c, 0x15, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0xc1, 0xb9, 0xae, +0x80, 0xa0, 0xd4, 0xfa, 0x6c, 0x74, 0x85, 0x34, +0xfd, 0x7a, 0xef, 0x5c, 0x62, 0x72, 0xe8, 0xe8, +0xb7, 0x8a, 0xa4, 0x55, 0x44, 0x7f, 0x65, 0xa3, +0xd9, 0x58, 0x65, 0x15, 0x63, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0x7a, +0x67, 0xca, 0x10, 0xf0, 0x38, 0x44, 0xc3, 0xc7, +0x85, 0xed, 0x1c, 0x7b, 0x6e, 0xea, 0x28, 0x92, +0xc7, 0xea, 0xeb, 0x76, 0x07, 0xaf, 0x8f, 0x6a, +0xde, 0x5c, 0xe9, 0x7e, 0xe8, 0xd6, 0xa3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0xfe, 0x2b, 0x59, 0x3d, 0x72, 0x4c, 0x8f, +0xe3, 0x06, 0x9a, 0x88, 0x02, 0x51, 0x8d, 0x71, +0xb0, 0x2a, 0x44, 0x0b, 0x3c, 0x81, 0xda, 0x19, +0x39, 0x95, 0x2c, 0xb4, 0x21, 0xf8, 0x93, 0x1f, +0x6a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0xec, 0xfe, 0xd7, 0x19, 0x8e, +0xe9, 0xe7, 0xa4, 0x12, 0xc4, 0x1d, 0x20, 0xb6, +0xc5, 0x38, 0x76, 0x67, 0xab, 0xe1, 0xd9, 0xdf, +0x1f, 0xe0, 0x71, 0xd2, 0x2a, 0x2d, 0xdd, 0xf6, +0xcf, 0x75, 0x5b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0x37, 0x79, 0xf6, +0x28, 0xa1, 0xd7, 0xc7, 0xdd, 0xa5, 0x49, 0xc1, +0xc7, 0x81, 0xd5, 0xa8, 0xf2, 0x92, 0xb9, 0x0c, +0xa5, 0x86, 0xf2, 0x4c, 0x7a, 0x16, 0x58, 0xc4, +0xb8, 0x7c, 0x84, 0xdf, 0x32, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0x2d, +0x5e, 0xca, 0x97, 0x01, 0x1e, 0x12, 0xc8, 0xf0, +0x6a, 0x3f, 0xf5, 0x82, 0x26, 0x39, 0xd3, 0xd0, +0x25, 0x47, 0x2c, 0x73, 0xba, 0xaa, 0xf5, 0xd2, +0x72, 0x53, 0x19, 0x5a, 0x59, 0x56, 0x18, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0xa9, 0x1b, 0xac, 0xb2, 0x9b, 0x91, 0x3e, +0xba, 0xbc, 0x89, 0x0a, 0xc5, 0x17, 0x2c, 0xe6, +0xa7, 0xf3, 0x95, 0x16, 0xd9, 0x01, 0xf4, 0x31, +0x02, 0x81, 0xa2, 0xa5, 0x98, 0xb8, 0x12, 0xb6, +0x2c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0x0d, 0x51, 0xa9, 0x4b, 0x49, +0x64, 0xd9, 0x9b, 0xfc, 0x96, 0x8e, 0x10, 0x76, +0xd3, 0x9f, 0x0c, 0xee, 0xfc, 0xa2, 0xf8, 0x6d, +0xa9, 0x6d, 0xc2, 0x05, 0x6a, 0xcb, 0xde, 0x63, +0x63, 0xe9, 0xc5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0x71, 0x55, 0xed, +0x8b, 0x1a, 0x22, 0x2a, 0x8d, 0xcb, 0x3e, 0x8f, +0xe5, 0xb4, 0xc4, 0x4f, 0xf7, 0xe5, 0x30, 0x5b, +0xca, 0x3e, 0x1b, 0x51, 0xaf, 0xc3, 0xb2, 0x9f, +0x18, 0x19, 0x54, 0xf1, 0x2d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0xc9, +0xf2, 0x67, 0x8c, 0x8e, 0xa8, 0x59, 0x47, 0x93, +0x32, 0xc6, 0xcf, 0xe4, 0x09, 0xbd, 0xcb, 0xa8, +0xa2, 0x0c, 0x30, 0xcd, 0xc2, 0xd9, 0x33, 0x69, +0x8c, 0xf1, 0x58, 0xc0, 0xa4, 0xd5, 0x13, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0x9c, 0x97, 0xec, 0x46, 0xe4, 0xe8, 0xfa, +0x61, 0xb8, 0x32, 0x6a, 0x76, 0x89, 0x10, 0x0b, +0xf6, 0xbb, 0x21, 0x52, 0xa3, 0x6c, 0xa4, 0x4e, +0x3f, 0xeb, 0x2b, 0x59, 0x36, 0xaa, 0x4a, 0xad, +0xd9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0x3e, 0xb4, 0x88, 0x0c, 0x7e, +0xf7, 0xb6, 0xed, 0x97, 0xb6, 0x92, 0x3a, 0x65, +0x7b, 0x28, 0xde, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x3c, 0xad, 0xa8, 0x67, +0x6b, 0xb6, 0xf4, 0x59, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0xcf, 0x7f, 0x98, +0xb4, 0x02, 0x07, 0x1c, 0x07, 0xbb, 0xf0, 0xb1, +0xb0, 0x5d, 0x5d, 0xd0, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xa2, +0x7e, 0xce, 0x84, 0x3f, 0x25, 0x65, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x01, +0x37, 0x64, 0xa0, 0xe3, 0x9e, 0x36, 0x81, 0xb4, +0x23, 0x70, 0x1a, 0x40, 0x61, 0x6b, 0xed, 0xf5, +0xc1, 0xa8, 0x61, 0x74, 0x66, 0x84, 0xc5, 0x6f, +0x65, 0x0a, 0x84, 0xe2, 0x0a, 0x29, 0x5e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x02, 0x12, 0xc0, 0xb6, 0xef, 0xf8, 0xa8, +0x7d, 0x8c, 0x44, 0x24, 0x9d, 0xec, 0xcd, 0x4f, +0x18, 0x01, 0x27, 0x44, 0xbd, 0x55, 0xd7, 0xac, +0x49, 0xf6, 0xdd, 0x7e, 0x8a, 0x9f, 0xf7, 0x16, +0x96, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0xab, 0x99, 0xc4, 0xa3, 0xc9, +0x6b, 0x66, 0x0a, 0x9a, 0xf4, 0xd1, 0xe0, 0xdf, +0x78, 0x31, 0x73, 0xe5, 0xbf, 0x20, 0x10, 0x6c, +0x7b, 0xa7, 0x61, 0x64, 0xae, 0x0c, 0xe7, 0xf5, +0x91, 0x48, 0xbf, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0x67, 0x2d, 0x4c, +0xfa, 0xcb, 0x1f, 0x67, 0x7e, 0x17, 0x41, 0x84, +0xc9, 0x4b, 0xfc, 0x3b, 0x70, 0xe3, 0xbe, 0xe0, +0xa2, 0x88, 0x67, 0xf7, 0x48, 0x2d, 0xde, 0x82, +0x8d, 0x8b, 0xef, 0x12, 0x2a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0x2e, +0xaf, 0xb1, 0x9f, 0xf8, 0xe3, 0x98, 0xd9, 0x1b, +0x2f, 0xb7, 0x18, 0x10, 0x84, 0x14, 0x4b, 0xa4, +0x1a, 0x28, 0x66, 0xeb, 0x1c, 0x05, 0x19, 0xf3, +0x05, 0xc9, 0xae, 0xb9, 0x67, 0x43, 0x7f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0xc0, 0x9b, 0x18, 0x73, 0x46, 0xff, 0x3a, +0x6d, 0xe2, 0xec, 0x74, 0x49, 0xb8, 0xe3, 0xfc, +0xec, 0xbd, 0xee, 0xa1, 0xa1, 0x47, 0x85, 0xe7, +0x79, 0x0a, 0x3c, 0xa9, 0x9c, 0x53, 0xfa, 0x9d, +0x07, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0xf4, 0x64, 0x9b, 0x7d, 0x62, +0xa8, 0xda, 0xb8, 0xdf, 0x2f, 0x93, 0x49, 0x70, +0xbc, 0xbb, 0xae, 0x05, 0xdd, 0x73, 0x24, 0x89, +0x55, 0xd7, 0x46, 0xce, 0x74, 0xaa, 0xf0, 0xc2, +0x82, 0xe6, 0xae, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0x2f, 0x3d, 0x19, +0x76, 0x11, 0xeb, 0x83, 0x5f, 0x57, 0xd2, 0x26, +0x57, 0x7e, 0xd2, 0xa4, 0x0d, 0x4b, 0x79, 0xa6, +0x1a, 0x4e, 0xf8, 0x00, 0x68, 0x73, 0x51, 0x42, +0x46, 0x7e, 0xba, 0x99, 0x72, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0x27, +0x99, 0x30, 0x68, 0x73, 0x6f, 0xf6, 0x1d, 0x17, +0xe9, 0x52, 0x95, 0x48, 0xd7, 0x61, 0xa2, 0x16, +0x6e, 0x0b, 0xce, 0xe2, 0x44, 0x01, 0x69, 0x14, +0xe6, 0x21, 0x69, 0x73, 0x5c, 0xc2, 0x16, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0x62, 0x57, 0xf5, 0xdd, 0x3d, 0x8e, 0x03, +0x8b, 0x5f, 0xf3, 0xe8, 0x13, 0xc8, 0x15, 0x22, +0x6b, 0xbb, 0xf2, 0x24, 0x10, 0x13, 0xb2, 0x4b, +0xaa, 0x5b, 0x54, 0x0c, 0x3a, 0x4e, 0xb4, 0xe7, +0x7a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0x07, 0x45, 0x93, 0x11, 0x4c, +0xe0, 0x50, 0x2f, 0xf7, 0xc1, 0xe7, 0xc9, 0x07, +0xc5, 0x29, 0x22, 0x0a, 0x97, 0xc5, 0x0b, 0x17, +0xe5, 0x2b, 0x65, 0xb8, 0x58, 0xb3, 0x01, 0xff, +0x71, 0xf9, 0xf3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0xb3, 0x49, 0xa8, +0x3a, 0xf0, 0x28, 0x55, 0x72, 0xa3, 0x33, 0xe6, +0xd5, 0xd3, 0x4a, 0x43, 0x32, 0xf0, 0x8d, 0x6f, +0x3c, 0xa5, 0xc8, 0x5b, 0x29, 0x13, 0x1f, 0xe0, +0x29, 0xfd, 0xd0, 0xe8, 0xef, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0xf6, +0xcb, 0x98, 0x20, 0x2d, 0x78, 0xb5, 0x4a, 0xc4, +0x1e, 0xe1, 0xb4, 0x4a, 0x65, 0xea, 0xfd, 0x4f, +0x5f, 0x3b, 0x29, 0x5a, 0x69, 0x4d, 0x8e, 0x11, +0xe3, 0x6e, 0xfd, 0x14, 0xf4, 0x89, 0xac, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0x9f, 0x58, 0x76, 0x04, 0x3d, 0x3f, 0x11, +0x25, 0x84, 0x25, 0x9b, 0x23, 0x78, 0x25, 0xd0, +0xb4, 0x34, 0x9f, 0xe2, 0x44, 0xee, 0x95, 0x32, +0x18, 0x51, 0xbb, 0x0f, 0x56, 0x84, 0x0f, 0x96, +0xad, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0xd7, 0xff, 0xce, 0x8d, 0x0d, +0x1d, 0xe6, 0x17, 0x5e, 0x5f, 0x6f, 0x2e, 0x4b, +0x30, 0xd2, 0xcb, 0xfb, 0xb3, 0x9c, 0xc0, 0xf5, +0xf6, 0xaf, 0xf2, 0x8a, 0x57, 0x3f, 0xa2, 0xa4, +0x6b, 0x0e, 0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0x1f, 0xb4, 0x28, +0xfa, 0x1d, 0xe5, 0x68, 0x39, 0x5d, 0xe6, 0x61, +0xec, 0x42, 0x87, 0xab, 0xf3, 0x62, 0x26, 0xe8, +0xa6, 0x0b, 0x6f, 0x3b, 0x39, 0x80, 0xdb, 0xf1, +0xeb, 0x3a, 0xa5, 0x7d, 0xa3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0xcd, +0x4d, 0x72, 0x4e, 0x3e, 0x58, 0x71, 0x51, 0x71, +0x15, 0x2c, 0x7e, 0xf0, 0x4d, 0x74, 0x17, 0xc7, +0x7f, 0xb1, 0x32, 0x49, 0xa0, 0x40, 0x61, 0x12, +0x78, 0x1e, 0x39, 0x38, 0x95, 0x6c, 0xb9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0xea, 0xa9, 0xb5, 0x54, 0x0c, 0x63, 0x91, +0x17, 0x62, 0x5d, 0xd3, 0x82, 0x69, 0x05, 0x1d, +0x51, 0xf7, 0x6b, 0x6e, 0xfb, 0x96, 0x8d, 0xf1, +0x53, 0x7a, 0xe5, 0x47, 0x4c, 0xaa, 0x9f, 0x2e, +0x3d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0xab, 0x38, 0xbc, 0xf6, 0x88, +0x9d, 0xd2, 0xd1, 0x54, 0x6d, 0x9c, 0xc8, 0x78, +0xb1, 0x24, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x4a, 0xff, 0xcb, 0x60, +0x40, 0x9d, 0x91, 0xb8, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0x16, 0x85, 0xec, +0x16, 0x2a, 0xfa, 0xb6, 0xeb, 0xdd, 0x93, 0x68, +0xab, 0x9b, 0x9b, 0xd4, 0x0d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0xca, +0xb7, 0xe8, 0x79, 0x31, 0xde, 0x6a, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0xa8, +0x0b, 0x6f, 0x18, 0x63, 0x5a, 0x05, 0x0d, 0xce, +0x2a, 0x4e, 0x35, 0x9d, 0xc9, 0xfa, 0x21, 0x35, +0x59, 0xc6, 0xa2, 0x88, 0xdd, 0x02, 0x8d, 0xeb, +0x6b, 0xf8, 0xd4, 0x98, 0xf5, 0xd6, 0xd5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0x50, 0x8c, 0xbb, 0x53, 0xac, 0xb5, 0xdc, +0x36, 0xb9, 0xeb, 0xba, 0x92, 0xe4, 0x21, 0xaf, +0x78, 0xf3, 0x7d, 0x42, 0xf5, 0x0c, 0x17, 0x90, +0xfe, 0x6a, 0xd6, 0x9f, 0x81, 0x49, 0xb4, 0xd6, +0x5e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0x15, 0x7d, 0x61, 0x8e, 0xd9, +0x2c, 0xf9, 0xfd, 0xa6, 0x1f, 0x18, 0xeb, 0xe9, +0x2d, 0x18, 0x4e, 0x43, 0x4c, 0x3c, 0xdf, 0x2e, +0xbd, 0xd3, 0x35, 0xa6, 0x8f, 0x27, 0xe6, 0xd0, +0xd9, 0xcf, 0x21, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0x72, 0x0d, 0x29, +0x89, 0x26, 0xcc, 0x44, 0x2b, 0x77, 0xdd, 0x94, +0xae, 0x5f, 0x37, 0x0e, 0x6b, 0x7b, 0x91, 0x7a, +0xb4, 0xd3, 0xaf, 0x6d, 0xc8, 0x0a, 0x4e, 0xfb, +0xf0, 0x3f, 0x0e, 0x7b, 0x80, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0xe2, +0xe0, 0x26, 0x32, 0x1c, 0x51, 0xa7, 0xa0, 0xad, +0x7a, 0xce, 0xa2, 0x6b, 0xd3, 0xf1, 0x5c, 0xb8, +0xc5, 0xfc, 0xe2, 0xca, 0x7c, 0x25, 0x30, 0x0b, +0xff, 0x26, 0xc0, 0xe1, 0x30, 0x79, 0x44, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0xcc, 0x95, 0xb3, 0xcc, 0x23, 0x8d, 0x49, +0x94, 0xeb, 0xaf, 0xf5, 0xdf, 0x78, 0x4c, 0x07, +0x8e, 0x47, 0x00, 0x69, 0x2d, 0x35, 0xbd, 0x18, +0x0d, 0xa3, 0xb6, 0x9d, 0x5f, 0xbe, 0xbd, 0xf2, +0x85, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0x05, 0x6d, 0xcd, 0x26, 0x92, +0x0f, 0xbe, 0x23, 0x28, 0xd3, 0x7f, 0x14, 0x08, +0x4c, 0x7f, 0xc9, 0x0f, 0xf4, 0xb1, 0x76, 0x34, +0x3f, 0x95, 0xfc, 0x81, 0x6d, 0x74, 0x24, 0x9e, +0x0c, 0x35, 0x86, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0xd4, 0xe1, 0x61, +0x9f, 0x71, 0xc7, 0x08, 0x9c, 0x54, 0xd7, 0xd9, +0x7a, 0x44, 0x02, 0x19, 0xff, 0xcb, 0x69, 0x04, +0x73, 0x76, 0x4c, 0xd1, 0xd6, 0x90, 0xd3, 0x5a, +0xa7, 0xf4, 0x9f, 0x3f, 0x40, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0xea, +0xf6, 0xe0, 0x58, 0xf0, 0xe8, 0x9d, 0x8e, 0x22, +0x85, 0xd1, 0x0b, 0xd5, 0x80, 0x21, 0xcd, 0xf3, +0xe8, 0x5d, 0x71, 0x4f, 0x80, 0x4f, 0x6a, 0xe4, +0x0d, 0xa3, 0xe6, 0xe3, 0x77, 0x38, 0xb4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0x31, 0x6a, 0xf0, 0x3f, 0xca, 0xf9, 0x1b, +0x45, 0xc9, 0x9e, 0x12, 0xd4, 0x2c, 0x9b, 0x4a, +0xaf, 0x46, 0x37, 0x08, 0xb5, 0x40, 0xc6, 0x95, +0xa7, 0xdd, 0x55, 0xdb, 0x4e, 0xc4, 0x09, 0xb3, +0x01, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0x35, 0x37, 0x84, 0xe2, 0xd3, +0x3f, 0x7f, 0x79, 0x50, 0xfa, 0xbb, 0x7f, 0x09, +0x27, 0xa3, 0xd5, 0xaf, 0xee, 0x84, 0xb2, 0xb1, +0x92, 0x10, 0xa9, 0x26, 0x42, 0x01, 0x9d, 0x1e, +0xd0, 0x1a, 0xaf, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0xc3, 0x79, 0x29, +0xbb, 0x13, 0x61, 0x28, 0xda, 0xa1, 0x8a, 0x18, +0x93, 0x17, 0xde, 0x5a, 0x94, 0xd5, 0x08, 0xf8, +0xfe, 0x2b, 0x5c, 0x8f, 0xcf, 0xe0, 0x50, 0x1b, +0xf2, 0x58, 0x91, 0x70, 0x69, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0xed, +0x0f, 0x12, 0x30, 0x30, 0xa5, 0xb4, 0x0a, 0xaf, +0x6b, 0xf5, 0xa8, 0x41, 0x82, 0xdb, 0xf0, 0x94, +0x06, 0xa0, 0x38, 0x8b, 0xcf, 0x33, 0xd3, 0x06, +0x2a, 0x5a, 0xc8, 0x37, 0x96, 0x13, 0x17, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0xec, 0x1b, 0x1c, 0xc2, 0x18, 0x56, 0x1f, +0x9e, 0x88, 0x32, 0xe3, 0xbd, 0x50, 0xcb, 0x17, +0x1d, 0x81, 0x99, 0xdf, 0x9f, 0xf3, 0x16, 0xb5, +0x53, 0x12, 0xf3, 0x2a, 0x9a, 0xf9, 0xaf, 0x0a, +0xff, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0x98, 0x19, 0x07, 0x5c, 0xa2, +0x8a, 0x32, 0xd9, 0x1b, 0xf1, 0x51, 0x96, 0xa5, +0x00, 0x07, 0x66, 0x42, 0xd3, 0x92, 0x34, 0x09, +0xa0, 0xe8, 0x9b, 0x61, 0x83, 0x74, 0x9b, 0xd7, +0xc4, 0x23, 0x62, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x11, 0xb8, 0x83, +0x7e, 0x3f, 0xf4, 0x70, 0xc5, 0x42, 0x0c, 0x05, +0x56, 0x9b, 0xe3, 0xf4, 0x29, 0x74, 0x71, 0x0c, +0xbd, 0x48, 0xc4, 0xfe, 0x28, 0x16, 0x69, 0x26, +0x4c, 0x04, 0xa9, 0xf4, 0x0b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0x7c, +0x53, 0xc0, 0x61, 0x89, 0xb8, 0x50, 0x6c, 0x67, +0x28, 0xd3, 0x98, 0x7b, 0x07, 0x07, 0xbb, 0x72, +0xd7, 0xb4, 0x3d, 0x5a, 0xa6, 0x23, 0xb3, 0x81, +0x57, 0xe8, 0xd6, 0xc8, 0xf6, 0xf9, 0xaf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0xf7, 0x9f, 0x22, 0xc3, 0x2f, 0x21, 0x6f, +0x05, 0x2a, 0xfd, 0x69, 0x6e, 0x1f, 0x9a, 0x93, +0xd4, 0x90, 0x75, 0x55, 0x42, 0xbb, 0x65, 0x29, +0x20, 0xcc, 0x0b, 0xa4, 0x2a, 0xc3, 0x14, 0xbe, +0x8a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0x5f, 0x15, 0xfa, 0x7c, 0x95, +0x6e, 0xbe, 0x1d, 0xf5, 0x56, 0xeb, 0x97, 0x71, +0x77, 0x6e, 0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x0a, 0x9d, 0xfc, 0xc3, +0x57, 0xad, 0xe0, 0xd2, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0xbe, 0x8a, 0x84, +0xa5, 0xa4, 0x6a, 0x62, 0xbc, 0x1c, 0x9e, 0xbb, +0x22, 0x6d, 0x3c, 0x77, 0x18, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x14, +0x60, 0x08, 0x7b, 0x3c, 0xf3, 0x4a, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x84, +0x45, 0xf0, 0xcf, 0x00, 0x20, 0x2f, 0xa7, 0xd2, +0xfc, 0xa1, 0xdb, 0xb8, 0x58, 0x34, 0x1f, 0x8f, +0x31, 0xa1, 0x85, 0x34, 0x76, 0x49, 0x3a, 0x85, +0x8e, 0x21, 0x4c, 0xd9, 0x62, 0xd2, 0x0f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0x6d, 0x0b, 0xd3, 0xaf, 0xc3, 0x0a, 0xdc, +0x9d, 0x5f, 0x7e, 0x59, 0x64, 0x8b, 0x06, 0x18, +0xa6, 0x8c, 0xc2, 0xa9, 0xda, 0x32, 0xa3, 0x7b, +0x54, 0x40, 0xc3, 0x18, 0xd2, 0xad, 0x85, 0xe3, +0x43, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0xf8, 0x21, 0xef, 0x14, 0x1c, +0x30, 0xe0, 0x0d, 0x9f, 0x96, 0x1b, 0x0e, 0x4b, +0x94, 0xf5, 0x38, 0x58, 0x25, 0xd1, 0xd6, 0x83, +0xb2, 0x40, 0xd6, 0xbd, 0xf4, 0x95, 0x1f, 0xe8, +0xe0, 0xa6, 0xcc, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0xf3, 0x8b, 0x5a, +0x74, 0x16, 0x09, 0xc1, 0x9a, 0x99, 0xeb, 0xc6, +0x78, 0xe0, 0x45, 0xba, 0x75, 0x96, 0x31, 0x41, +0xc9, 0x3c, 0xcc, 0x86, 0x23, 0xa1, 0x33, 0x62, +0x30, 0xcd, 0x98, 0xac, 0x44, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x9f, +0x26, 0xd0, 0x4f, 0x2e, 0x99, 0x00, 0x9f, 0x85, +0xef, 0xbc, 0x2d, 0xaf, 0x4e, 0xce, 0x68, 0x5a, +0xa2, 0x29, 0x26, 0xc2, 0x23, 0x30, 0x05, 0xe3, +0x0e, 0x43, 0x96, 0x39, 0x38, 0x35, 0x18, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0x80, 0xc5, 0x9a, 0x2e, 0x6d, 0xc7, 0xa2, +0x14, 0x15, 0x39, 0x45, 0xdb, 0x22, 0xac, 0x5d, +0x84, 0xf2, 0x80, 0x09, 0xa1, 0x6b, 0xc4, 0xb1, +0x10, 0x8e, 0xf1, 0x40, 0x84, 0xea, 0x5b, 0xde, +0x45, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0x03, 0x7c, 0xd6, 0x6a, 0xbf, +0x7b, 0x58, 0x37, 0x8c, 0x69, 0xfa, 0xdf, 0xeb, +0x9c, 0xbd, 0x2d, 0xf9, 0xf6, 0x28, 0x12, 0xa0, +0xe0, 0x37, 0xe3, 0xb8, 0xac, 0x86, 0x90, 0x82, +0x85, 0xe4, 0x0b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0xf8, 0x60, 0xb0, +0x1e, 0x64, 0x43, 0x31, 0xb0, 0x57, 0x81, 0x81, +0x26, 0x2d, 0x29, 0x81, 0xd9, 0x4c, 0x50, 0xb1, +0x6d, 0xe2, 0x16, 0x36, 0x57, 0xf0, 0xc4, 0x6a, +0xc8, 0x96, 0x6d, 0xf0, 0xb6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x6b, +0x6d, 0xed, 0x93, 0xac, 0x12, 0xde, 0x54, 0x2c, +0x9c, 0xeb, 0x85, 0xdd, 0x66, 0xa7, 0xb5, 0xae, +0x5c, 0x5a, 0xe2, 0xf2, 0xc5, 0x5d, 0xd4, 0xa1, +0x77, 0xe5, 0xb9, 0xec, 0x66, 0xa9, 0x5e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0xaa, 0xb0, 0x51, 0xa6, 0x6f, 0x44, 0x70, +0xb3, 0x47, 0xb8, 0xf1, 0xcb, 0x50, 0x3e, 0x9a, +0x7b, 0xd3, 0xe5, 0x99, 0x2a, 0xa8, 0x4f, 0x89, +0xf9, 0x52, 0xb8, 0x54, 0x3d, 0x19, 0xbc, 0x86, +0x55, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0x4d, 0x18, 0xf0, 0xae, 0xa5, +0x21, 0xdc, 0x06, 0x63, 0x74, 0x33, 0xbc, 0xc7, +0xbc, 0x2a, 0x66, 0x47, 0xc7, 0x71, 0xeb, 0xc8, +0xe9, 0xf8, 0x50, 0x2f, 0x8c, 0x73, 0x2a, 0x99, +0x37, 0x1e, 0xd9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x10, 0xcb, 0x04, +0xa6, 0x94, 0xc3, 0x93, 0x95, 0x45, 0x28, 0xf0, +0x92, 0x0e, 0x99, 0x2f, 0xa0, 0x77, 0x92, 0x09, +0xaa, 0x55, 0xc5, 0x92, 0xed, 0xe8, 0x24, 0x9b, +0x5f, 0x7b, 0x55, 0x82, 0xc5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0x48, +0xa4, 0x92, 0x83, 0x68, 0x2a, 0x25, 0xb6, 0x0f, +0x06, 0x7e, 0x8f, 0xbe, 0x37, 0xfc, 0x0f, 0xef, +0xec, 0x05, 0xcc, 0x2a, 0x5c, 0x98, 0xf5, 0x13, +0x1e, 0x66, 0xfa, 0x20, 0x74, 0x35, 0xc9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0xad, 0x9e, 0xc5, 0xe0, 0x47, 0x9b, 0x34, +0x11, 0xc9, 0xc3, 0x22, 0xaa, 0xe1, 0x54, 0x4d, +0x02, 0x22, 0xed, 0x0e, 0x92, 0x86, 0xc5, 0x85, +0x4c, 0xc1, 0xf0, 0x78, 0xe2, 0x2b, 0x93, 0x21, +0xd5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0xdc, 0xfa, 0xa0, 0x0c, 0x78, +0xe4, 0x8f, 0x84, 0x77, 0x0d, 0x38, 0xcc, 0xee, +0xe0, 0x05, 0x51, 0x5a, 0xfd, 0x8e, 0x11, 0xf7, +0x71, 0x8b, 0x7a, 0x1e, 0x2f, 0x9d, 0xb4, 0x9f, +0x97, 0x45, 0x70, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0x05, 0x4e, 0xe1, +0xc8, 0x1a, 0xec, 0x89, 0x65, 0xfc, 0x9f, 0xec, +0xa8, 0x23, 0x03, 0xbb, 0x9e, 0x03, 0xed, 0x9f, +0xb8, 0x6e, 0x14, 0x06, 0xca, 0xd4, 0xf6, 0x59, +0xdd, 0x94, 0x0d, 0x9a, 0x0c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0x51, +0xf1, 0x63, 0xe5, 0xea, 0xb8, 0x96, 0x75, 0x38, +0x53, 0xbf, 0xfb, 0xd2, 0xd7, 0x63, 0x7a, 0x82, +0x50, 0xdc, 0xbf, 0xbb, 0xb8, 0x1f, 0x6c, 0xa9, +0x59, 0x20, 0x7f, 0x79, 0x96, 0x1b, 0xda, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x84, 0xb9, 0xc7, 0xf3, 0xac, 0x28, 0x75, +0xf4, 0x2b, 0xbb, 0xf0, 0x86, 0x8f, 0x16, 0xb2, +0x1d, 0xcd, 0x50, 0x64, 0x9c, 0x7a, 0x4c, 0x33, +0x9b, 0x36, 0xe3, 0x2a, 0xb7, 0x91, 0x44, 0xdc, +0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x01, 0x56, 0x01, 0xf9, 0xbb, +0x96, 0xd1, 0x2e, 0x6b, 0xcf, 0xf6, 0x4d, 0xa8, +0x14, 0x1c, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xa4, 0x45, 0xb2, 0xc5, +0x40, 0xc9, 0x48, 0xcf, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0xde, 0xa5, 0x50, +0x62, 0x1d, 0xf3, 0x69, 0x9d, 0x12, 0xcc, 0x34, +0x52, 0x32, 0x41, 0xf1, 0xff, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0xf9, +0x20, 0x7d, 0x21, 0x78, 0x68, 0xdf, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0x2f, +0x9a, 0xbe, 0x36, 0x45, 0xd6, 0x6a, 0x62, 0x10, +0x0c, 0x79, 0x16, 0xea, 0x37, 0xa1, 0xd7, 0x18, +0xe1, 0x7e, 0x65, 0x06, 0xe9, 0x92, 0xc2, 0xe7, +0xf4, 0x55, 0x72, 0x1d, 0x15, 0x36, 0xa0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x57, 0xe2, 0x27, 0x79, 0x7c, 0xb0, 0x50, +0x6f, 0x46, 0xcc, 0x32, 0x1c, 0xa5, 0x75, 0x06, +0x3c, 0xdb, 0xee, 0xce, 0x61, 0x36, 0x80, 0x8a, +0x1f, 0x4c, 0xed, 0x0b, 0x1c, 0xf3, 0x9d, 0x0a, +0x98, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0x74, 0x4d, 0xf0, 0x28, 0xae, +0xf9, 0x19, 0x43, 0xb8, 0x8f, 0xe4, 0x69, 0x39, +0xc3, 0xe2, 0x18, 0x4f, 0x6f, 0x02, 0xdd, 0x44, +0x23, 0xc4, 0x85, 0xf9, 0x05, 0xd5, 0xfa, 0x29, +0x12, 0xfb, 0xbb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0x51, 0x93, 0x4e, +0x59, 0xec, 0x61, 0xc7, 0x9e, 0x08, 0x39, 0x22, +0x55, 0x94, 0x7c, 0xd4, 0x5e, 0x33, 0xf9, 0xa1, +0x2e, 0x5d, 0xfc, 0x94, 0xca, 0xc5, 0x31, 0x63, +0x4a, 0x6c, 0x03, 0x54, 0x04, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x1e, +0x6e, 0x08, 0xb6, 0x7c, 0x7d, 0x85, 0xa1, 0xa4, +0x40, 0xde, 0xfa, 0x77, 0x55, 0x6f, 0xbe, 0xc3, +0x9b, 0xcf, 0xca, 0x40, 0x90, 0xf2, 0xc9, 0x06, +0x0b, 0xa3, 0xaa, 0xd0, 0x85, 0xbc, 0xdd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0x40, 0x60, 0x1b, 0x0a, 0x30, 0x87, 0xea, +0xbb, 0x9b, 0xae, 0x40, 0xd4, 0x86, 0x74, 0x65, +0x4e, 0x03, 0x77, 0xb4, 0x10, 0xcf, 0xfa, 0x07, +0xb2, 0xaa, 0xf1, 0xeb, 0x46, 0x54, 0x2f, 0xc3, +0xef, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0x88, 0x81, 0x61, 0xd5, 0xae, +0xbf, 0x24, 0x08, 0x4c, 0x69, 0x26, 0x92, 0xcb, +0xe7, 0x35, 0xfb, 0x80, 0x88, 0xf3, 0x74, 0xd5, +0xdb, 0x40, 0xb7, 0xfc, 0x6e, 0x34, 0x73, 0x7a, +0x3e, 0xcb, 0x51, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0x60, 0xd1, 0xc5, +0x4b, 0x99, 0x3e, 0x72, 0x3f, 0x36, 0xdf, 0xb5, +0xa1, 0x0a, 0x94, 0x60, 0xf9, 0x49, 0x62, 0xe9, +0x5b, 0x61, 0xc1, 0xa5, 0x72, 0x5f, 0x4b, 0x3a, +0xc1, 0x9b, 0xdc, 0xb0, 0x74, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0x5b, +0x72, 0x6d, 0x0f, 0x05, 0x7e, 0x80, 0xbb, 0xb8, +0xcd, 0x95, 0xa0, 0x36, 0x04, 0xc9, 0x03, 0xbc, +0x2f, 0x50, 0xc2, 0x6e, 0xca, 0xc4, 0x94, 0x7f, +0x6a, 0xc1, 0x74, 0x1a, 0x8d, 0x87, 0x77, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0xa1, 0xee, 0x9a, 0x3f, 0x06, 0xa8, 0x78, +0x26, 0x32, 0x39, 0x9d, 0x30, 0x12, 0xa8, 0x86, +0xa0, 0x66, 0xc8, 0x45, 0x9f, 0xe4, 0x89, 0xc9, +0xa9, 0x8e, 0x8f, 0xd8, 0x4f, 0x65, 0xb6, 0xc8, +0x6b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0x81, 0xa3, 0x24, 0x44, 0xe7, +0xc2, 0xb5, 0x89, 0x01, 0xdd, 0xd4, 0x27, 0xda, +0x2d, 0x6f, 0x07, 0xac, 0x9e, 0xbd, 0xaf, 0x56, +0xff, 0x64, 0x06, 0x19, 0xac, 0x47, 0x5a, 0x00, +0x82, 0x1d, 0x8d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0x3c, 0x73, 0x6b, +0xfc, 0xab, 0x3b, 0xe7, 0x54, 0x7f, 0xcf, 0x75, +0xec, 0xc3, 0x5c, 0xd0, 0x39, 0x8c, 0xf9, 0x67, +0x55, 0x14, 0xa7, 0xb5, 0xcf, 0xf5, 0x6c, 0x82, +0x5d, 0x3f, 0x27, 0x2c, 0xeb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0x99, +0xfd, 0x3c, 0x31, 0x41, 0x66, 0xda, 0x8d, 0x05, +0xae, 0x3c, 0xac, 0x8d, 0x78, 0x44, 0x1b, 0x20, +0x38, 0x30, 0x64, 0x7d, 0x3d, 0xcd, 0x04, 0x33, +0x32, 0x28, 0x41, 0x7f, 0x2b, 0x09, 0x9f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0x0b, 0x70, 0x84, 0x96, 0x7d, 0xb4, 0xa7, +0x2d, 0x4f, 0xe7, 0xd6, 0xa4, 0x6b, 0xa6, 0xb1, +0xfb, 0x19, 0x38, 0x2e, 0x0c, 0x2e, 0xdb, 0x05, +0x61, 0x65, 0xaf, 0x25, 0x93, 0x55, 0xdd, 0x8d, +0x01, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0xb8, 0x47, 0xb1, 0x5e, 0x79, +0xa1, 0x29, 0x05, 0x07, 0xae, 0xee, 0xf2, 0x5f, +0x10, 0x5a, 0x30, 0xdb, 0x4a, 0xe2, 0xe7, 0x84, +0xf3, 0x2c, 0xfb, 0x62, 0x62, 0xc5, 0x16, 0x93, +0x87, 0x5f, 0x9c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0x82, 0x2f, 0x27, +0x41, 0xde, 0x27, 0xab, 0x34, 0x03, 0x83, 0xe0, +0x95, 0xc9, 0xe3, 0x27, 0x43, 0xd7, 0xd5, 0x55, +0x66, 0x18, 0x29, 0xe0, 0xc1, 0x74, 0x66, 0xd6, +0x70, 0x77, 0x18, 0x0c, 0x75, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0xc6, +0x72, 0x3e, 0x7e, 0xe3, 0x28, 0x06, 0xca, 0xd4, +0xce, 0xc8, 0x6a, 0x08, 0x96, 0x38, 0x36, 0x5a, +0x7e, 0x0c, 0xc2, 0xd6, 0x4a, 0x2e, 0x42, 0xc9, +0x69, 0x8d, 0x30, 0x28, 0xa9, 0x96, 0xd2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0xe6, 0x9b, 0xbc, 0x92, 0x58, 0x7b, 0x7e, +0xd2, 0x29, 0xf5, 0x67, 0xbf, 0xa9, 0x7e, 0x25, +0xe6, 0x10, 0x22, 0x41, 0x73, 0xe6, 0x66, 0x37, +0xfe, 0xed, 0xd6, 0x41, 0xae, 0xf7, 0xb8, 0xd6, +0xf0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0xed, 0xaf, 0x44, 0xa7, 0x67, +0x6a, 0x22, 0x85, 0x37, 0x46, 0x1c, 0x36, 0xed, +0x8f, 0x56, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xef, 0xcf, 0xfd, 0x70, +0xbe, 0xc4, 0xc3, 0x91, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0x1f, 0x99, 0xc7, +0x30, 0xba, 0x32, 0x63, 0xf5, 0xbf, 0xcf, 0x9c, +0x7e, 0x39, 0x09, 0x34, 0x36, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa9, 0xad, +0xaa, 0x6b, 0x02, 0x94, 0xa7, 0x73, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0xd1, +0x71, 0xcc, 0x62, 0x40, 0xfa, 0xb8, 0x66, 0xd1, +0x82, 0x45, 0xf7, 0x4b, 0x88, 0x99, 0x20, 0xbd, +0xb0, 0xbb, 0x78, 0xc1, 0xde, 0xba, 0xac, 0x62, +0xfb, 0xf7, 0xae, 0x1c, 0x86, 0x5f, 0xbe, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0x38, 0xbf, 0x78, 0x81, 0x9c, 0x1b, 0x09, +0x29, 0x69, 0x48, 0x1c, 0xd3, 0x64, 0x57, 0x59, +0x18, 0xd5, 0x0a, 0xc0, 0x31, 0x91, 0x3d, 0x56, +0x32, 0xdd, 0x3f, 0xeb, 0x77, 0xeb, 0x9f, 0x2e, +0xbf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0xb5, 0xff, 0xfd, 0x73, 0xdd, +0x99, 0x09, 0x69, 0x16, 0xad, 0xe1, 0x42, 0xd9, +0x71, 0x34, 0x1d, 0x20, 0x09, 0xff, 0x2d, 0xa0, +0xdc, 0xf6, 0x92, 0x34, 0x87, 0x1b, 0xe1, 0x2f, +0x26, 0xeb, 0x3d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0xf2, 0x80, 0x06, +0xa0, 0x0e, 0x84, 0x2c, 0x36, 0x9f, 0xc4, 0x26, +0x5b, 0x52, 0xf2, 0x94, 0x3e, 0x7f, 0x74, 0x6f, +0xff, 0x42, 0x6d, 0x0d, 0x9a, 0x6e, 0x0a, 0x84, +0x4d, 0x0e, 0xa6, 0xdd, 0x41, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0xd2, +0xc8, 0xf5, 0x15, 0xbe, 0x57, 0x3f, 0x96, 0x0e, +0x50, 0xe7, 0x1e, 0xd2, 0x32, 0x83, 0xbc, 0xa7, +0xdc, 0x48, 0xe1, 0xe3, 0x23, 0xf1, 0x35, 0x8f, +0xbc, 0xcf, 0x5b, 0xb9, 0xc1, 0x6e, 0x33, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x72, 0x01, 0x91, 0x08, 0x9c, 0x1d, 0x9f, +0x10, 0x06, 0x53, 0x92, 0xe8, 0x65, 0xd7, 0x03, +0x9b, 0x15, 0xbd, 0x47, 0x19, 0xfb, 0x5b, 0x66, +0x81, 0x7f, 0x03, 0x9b, 0x0e, 0xf4, 0xb9, 0x96, +0x4e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x3b, 0x05, 0x43, 0x07, 0x18, +0xb7, 0xe1, 0xc9, 0x8b, 0x60, 0x66, 0x38, 0xbe, +0x9f, 0x34, 0xfd, 0x85, 0xfb, 0xa0, 0x40, 0x7e, +0x3b, 0x62, 0x4a, 0x63, 0x61, 0xb9, 0xec, 0x57, +0x59, 0x52, 0xb4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0x35, 0x6b, 0x8d, +0x40, 0x6e, 0xbd, 0xf4, 0x82, 0xcd, 0x13, 0x2a, +0x32, 0xef, 0x9c, 0x37, 0x9f, 0x88, 0x48, 0xc9, +0xad, 0x99, 0xb4, 0xee, 0x02, 0x79, 0x8c, 0xe9, +0xc1, 0xe6, 0xa3, 0xa2, 0x1e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0x35, +0x0e, 0x65, 0x07, 0x7a, 0x16, 0x4d, 0x59, 0xd7, +0x00, 0xb7, 0x9e, 0xb4, 0x93, 0x34, 0xf6, 0xe1, +0x0e, 0xb6, 0xd5, 0x6f, 0x16, 0x55, 0x29, 0x18, +0x22, 0xfa, 0x9c, 0x3a, 0x7b, 0xb3, 0x6d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0x96, 0x66, 0xfc, 0xc7, 0xa9, 0x87, 0x8d, +0xd4, 0x67, 0x46, 0x03, 0xb2, 0x55, 0x93, 0xbf, +0x52, 0x90, 0x66, 0x8b, 0x7a, 0x0a, 0x75, 0x55, +0x46, 0x31, 0xb6, 0x99, 0xee, 0x55, 0x9f, 0xf5, +0x6e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0xba, 0x8c, 0x91, 0x31, 0xbf, +0xbe, 0x01, 0x2a, 0x85, 0x45, 0x3b, 0xad, 0x73, +0xca, 0x6f, 0x13, 0x9b, 0xb3, 0x0f, 0xf3, 0xd9, +0x0d, 0xff, 0x90, 0xda, 0x8a, 0x53, 0x81, 0x2e, +0x9f, 0x91, 0x24, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x43, 0x71, 0x10, +0x49, 0xc9, 0x68, 0x53, 0xb3, 0x4d, 0x23, 0x72, +0x0f, 0x62, 0x84, 0x6f, 0x1a, 0xf9, 0xc9, 0xd0, +0x47, 0x41, 0x7d, 0xc2, 0x4a, 0x96, 0x19, 0xd5, +0xc1, 0x7f, 0x4c, 0x39, 0xc4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x86, +0x1c, 0x31, 0x21, 0x30, 0xf0, 0x87, 0xf9, 0x1a, +0xef, 0x61, 0xfa, 0xad, 0x2b, 0xf1, 0x52, 0x80, +0x3b, 0xb3, 0x6d, 0x93, 0x4b, 0x70, 0xe7, 0x9d, +0x07, 0x72, 0xd7, 0x29, 0xb4, 0x98, 0xeb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0x80, 0xea, 0x55, 0xe2, 0x97, 0x79, 0x86, +0x0d, 0xd9, 0xdb, 0x27, 0x07, 0xc8, 0xc9, 0x1a, +0xb5, 0xd5, 0xff, 0x14, 0x31, 0x9c, 0x1b, 0x69, +0x35, 0x65, 0x17, 0x31, 0x59, 0x81, 0xd9, 0x4e, +0x3f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0xca, 0x6e, 0x7e, 0x5b, 0x0e, +0x29, 0xb3, 0x41, 0xe7, 0x24, 0xb7, 0xfa, 0x32, +0xd1, 0x4d, 0x6f, 0x58, 0x84, 0x97, 0x46, 0x27, +0x33, 0x6d, 0x5f, 0xd7, 0x38, 0x43, 0x9a, 0xbd, +0xb5, 0x1e, 0x5f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0x8e, 0x68, 0xa9, +0x13, 0x4a, 0x2e, 0xa7, 0xa2, 0xf8, 0x3b, 0x77, +0xe3, 0x80, 0xf6, 0xdb, 0x2a, 0x7f, 0x7e, 0xb8, +0x9d, 0x71, 0x42, 0xfd, 0x4a, 0xbd, 0xa4, 0x0e, +0xb2, 0x80, 0xdc, 0xe3, 0xea, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0x13, +0xf0, 0xe0, 0xb4, 0xcd, 0x1f, 0xa5, 0x14, 0xe7, +0xeb, 0x62, 0x0c, 0x36, 0xb5, 0x72, 0x86, 0xf0, +0xcc, 0x48, 0xc6, 0x24, 0xae, 0x0d, 0xf4, 0xbc, +0x9b, 0x2b, 0x46, 0x00, 0x8d, 0xf5, 0x80, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0xd3, 0x48, 0xd2, 0x7b, 0x2a, 0x28, 0xa5, +0x28, 0x9f, 0x5a, 0xd3, 0x50, 0x0b, 0x2e, 0xe5, +0xf4, 0x84, 0x44, 0x38, 0xbf, 0xb8, 0xdb, 0x46, +0x4d, 0xf4, 0xad, 0x87, 0x41, 0x01, 0x34, 0xcb, +0x63, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0x59, 0xbc, 0xf6, 0x58, 0xa0, +0x58, 0x68, 0xa5, 0x03, 0x99, 0xe7, 0x1b, 0x1d, +0xf2, 0xb3, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x59, 0xab, 0xc1, 0x56, +0x9c, 0x61, 0xe8, 0x8f, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0xe5, 0xda, 0xe2, +0x53, 0x38, 0x06, 0x0c, 0xfc, 0x68, 0x17, 0x8e, +0xff, 0xab, 0x6f, 0x86, 0xaa, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7d, 0x5d, +0x11, 0x9a, 0x00, 0xd0, 0x9b, 0x20, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0xd4, +0x13, 0xd6, 0xb1, 0x94, 0xba, 0xbc, 0x57, 0xba, +0x68, 0xba, 0xb5, 0x4c, 0x56, 0x62, 0x7b, 0x67, +0x6d, 0xa7, 0x5b, 0x28, 0x78, 0x25, 0xbe, 0xe3, +0x64, 0x19, 0xe3, 0xbe, 0xbe, 0x27, 0xbb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0xac, 0x82, 0x1e, 0xec, 0x1f, 0xd7, 0x2c, +0x76, 0xca, 0x15, 0xdc, 0xd7, 0xfa, 0xf2, 0x1f, +0xc4, 0x6d, 0x1f, 0x93, 0x44, 0xbf, 0xdb, 0x13, +0x7e, 0x07, 0x77, 0x14, 0x46, 0xef, 0x96, 0xfa, +0x6f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0x5e, 0xca, 0x1b, 0x0c, 0xe1, +0xf7, 0x6f, 0xda, 0x3c, 0xc2, 0x84, 0x90, 0x73, +0x40, 0x81, 0x96, 0xd5, 0x1a, 0x13, 0x42, 0x74, +0xa4, 0xa8, 0xb0, 0xd3, 0xe9, 0xe5, 0x7e, 0x2b, +0x73, 0xbb, 0x52, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0xd3, 0x0f, 0x15, +0x4f, 0xa7, 0x40, 0x2a, 0x60, 0xf9, 0xfa, 0xbb, +0x55, 0xff, 0x14, 0x1b, 0x05, 0xa5, 0x06, 0x20, +0x4f, 0x60, 0x4e, 0xe3, 0xa3, 0x49, 0xea, 0x04, +0xbb, 0xc4, 0x56, 0xce, 0x2c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0x04, +0x07, 0xe6, 0x9c, 0x44, 0x64, 0x72, 0x50, 0x2e, +0x9f, 0xdf, 0x7f, 0xe9, 0x3b, 0xea, 0xcc, 0x1a, +0x8b, 0x9c, 0xf0, 0xff, 0x1a, 0x2b, 0x8e, 0xe9, +0x4c, 0xb6, 0x62, 0x4d, 0x83, 0x5b, 0x09, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0x1b, 0x76, 0x33, 0xbe, 0xcd, 0x32, 0x5e, +0x70, 0x33, 0xe8, 0xca, 0x56, 0x4e, 0xa7, 0x8b, +0x7c, 0x5a, 0x3d, 0x7b, 0xc6, 0x82, 0x2a, 0x82, +0x47, 0xdf, 0xbf, 0xdc, 0x1a, 0x15, 0xec, 0xb3, +0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0x84, 0xf0, 0x1d, 0x42, 0x97, +0x15, 0x1e, 0xad, 0x4e, 0x40, 0x11, 0x31, 0xef, +0xd2, 0x78, 0x63, 0x98, 0x43, 0x11, 0x6b, 0x7c, +0xb5, 0xbb, 0x3e, 0x7e, 0x16, 0x35, 0x40, 0x87, +0x07, 0x4d, 0xe3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0xc7, 0x25, 0x8c, +0xb5, 0x08, 0x10, 0x7a, 0xfd, 0x4b, 0xbc, 0x8f, +0x83, 0x60, 0x39, 0x7b, 0x08, 0x17, 0x88, 0x0f, +0x2e, 0x11, 0x0d, 0x54, 0x6d, 0x4e, 0x58, 0x8b, +0xc8, 0x0c, 0x9a, 0x6f, 0xf9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x65, +0x68, 0x88, 0x5b, 0x96, 0xfe, 0x6f, 0x5e, 0xf6, +0xdd, 0x1e, 0x62, 0xf2, 0xc5, 0xf1, 0x4e, 0x1f, +0x3d, 0x58, 0x60, 0xfd, 0x4c, 0x66, 0x39, 0xd2, +0x77, 0x29, 0x5b, 0xf3, 0xb8, 0x59, 0x79, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0x75, 0xfa, 0x9f, 0x65, 0x35, 0xa2, 0x7e, +0xc1, 0x98, 0x12, 0x48, 0x72, 0xbb, 0x56, 0xa1, +0xb9, 0xaf, 0xb6, 0x44, 0x10, 0xa9, 0x67, 0x67, +0xbf, 0x98, 0x6f, 0xc1, 0x95, 0x66, 0xab, 0xad, +0xb5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0xe9, 0x05, 0xb6, 0x50, 0xab, +0x3d, 0x8b, 0xdc, 0xd2, 0xb1, 0x36, 0x91, 0xb7, +0x73, 0x9b, 0x49, 0x3b, 0x6d, 0x43, 0x8f, 0x98, +0x16, 0x3f, 0xe2, 0x27, 0x4e, 0x86, 0x99, 0x5a, +0xcd, 0xd7, 0x9b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0xfd, 0x2e, 0x4f, +0x16, 0x00, 0x98, 0xef, 0xbe, 0x01, 0x50, 0xdf, +0xcd, 0x1f, 0x36, 0x6b, 0x5b, 0x8a, 0x5a, 0x6a, +0x87, 0xa0, 0x31, 0x33, 0x1a, 0xec, 0x8d, 0x24, +0x28, 0x71, 0x67, 0x2d, 0x29, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x53, +0xc3, 0xc2, 0xf2, 0x50, 0x33, 0x1f, 0x01, 0x5b, +0x94, 0xfc, 0xe7, 0x0b, 0x2d, 0xb5, 0x36, 0x84, +0x33, 0x85, 0x2e, 0xc8, 0xf3, 0x5a, 0x16, 0x6d, +0x09, 0x06, 0x8f, 0xb4, 0x74, 0xba, 0xcd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0x50, 0x0f, 0xf5, 0x64, 0xb1, 0xfd, 0xeb, +0x14, 0x51, 0x03, 0x1b, 0x65, 0x6a, 0x7e, 0x30, +0x96, 0x84, 0x98, 0xb0, 0x5e, 0xeb, 0x59, 0xed, +0xc1, 0xbd, 0x51, 0xa8, 0xaf, 0x5e, 0xd3, 0xfc, +0x53, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0x11, 0xee, 0xa2, 0x79, 0xbe, +0xaf, 0xe7, 0x3c, 0xa7, 0x2e, 0xf2, 0x3d, 0x59, +0x99, 0xfd, 0xb0, 0xd9, 0x62, 0xf5, 0xa4, 0x38, +0xca, 0xfd, 0x04, 0xfa, 0xb0, 0x36, 0x90, 0xd8, +0xbd, 0x63, 0x85, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0xa3, 0x6c, 0x25, +0x4e, 0x1a, 0x02, 0x24, 0x00, 0xa7, 0x26, 0xa7, +0x23, 0x76, 0x3e, 0xe4, 0x51, 0xb0, 0x9c, 0x91, +0x58, 0x82, 0x9b, 0xec, 0x09, 0x26, 0xd2, 0xd5, +0x74, 0x5a, 0x6b, 0xe6, 0x4d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x65, +0x48, 0x52, 0x5d, 0x2e, 0x55, 0x58, 0x8f, 0x82, +0x3e, 0x30, 0x0c, 0xd1, 0x81, 0x09, 0xdd, 0x0b, +0xe6, 0x18, 0xa8, 0x83, 0x0c, 0x1a, 0xe6, 0xa4, +0x4a, 0x25, 0x8f, 0x01, 0x44, 0xda, 0x95, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0xaa, 0x50, 0x77, 0xa2, 0xfc, 0xeb, 0x50, +0xa9, 0xb0, 0x8a, 0xbc, 0xcc, 0x33, 0xdd, 0x74, +0xb9, 0xe4, 0x07, 0xe4, 0xd5, 0xe5, 0x79, 0xbb, +0x05, 0x46, 0xc0, 0xc9, 0x30, 0xb2, 0xcf, 0x33, +0x7a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0xbb, 0xc5, 0x87, 0x33, 0x96, +0xa9, 0x82, 0xb4, 0x3a, 0x99, 0x23, 0x31, 0x73, +0x0c, 0xa9, 0xae, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x32, 0x11, 0x0b, 0x06, +0x72, 0xb1, 0xb6, 0x0b, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0x75, 0xd8, 0x20, +0xda, 0x70, 0xc0, 0x46, 0x48, 0x85, 0xa4, 0x66, +0x45, 0x7f, 0x88, 0x01, 0xf2, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0xfb, +0x8b, 0x83, 0xc0, 0xb3, 0xfc, 0x6b, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0x97, +0xd7, 0x20, 0x15, 0xac, 0x83, 0xed, 0x31, 0x14, +0xe4, 0xc3, 0xa9, 0xc1, 0x58, 0xa2, 0x40, 0x43, +0x69, 0x77, 0x65, 0xdc, 0x28, 0x6a, 0xbb, 0x83, +0x6b, 0x60, 0x8b, 0x98, 0xf5, 0x96, 0x09, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0x2c, 0x7d, 0x72, 0xd8, 0x4a, 0x27, 0xda, +0x5f, 0x97, 0x86, 0xef, 0xff, 0x41, 0x0a, 0x43, +0x96, 0x55, 0x66, 0x2b, 0x40, 0x26, 0x9c, 0x13, +0x3b, 0x0c, 0x64, 0x4a, 0xb4, 0x7a, 0xff, 0xc1, +0xa6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x52, 0x16, 0x14, 0xe3, 0x3e, +0x08, 0xe4, 0x17, 0x7e, 0x1d, 0x47, 0x36, 0x2d, +0xe5, 0x05, 0xfe, 0xe7, 0xf8, 0xc8, 0x9a, 0x0c, +0x2d, 0x02, 0x6d, 0x9c, 0x17, 0xb8, 0xee, 0xbe, +0x92, 0xe5, 0x81, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0xd4, 0x0e, 0xad, +0xa0, 0x7f, 0x33, 0xbc, 0x9b, 0x53, 0x7c, 0x21, +0x5e, 0xcc, 0xa4, 0x63, 0xb5, 0x37, 0xd6, 0x9d, +0x84, 0x0d, 0x1f, 0x3d, 0xb0, 0x22, 0xc4, 0xe4, +0x38, 0xd5, 0x81, 0xe3, 0xc6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0xdc, +0x81, 0x84, 0x4a, 0x8b, 0x3a, 0xf9, 0x5b, 0x9a, +0xc0, 0x4e, 0xdb, 0x90, 0xf6, 0x5b, 0x42, 0x7c, +0xb4, 0x0e, 0xa5, 0x02, 0x09, 0x19, 0x69, 0x13, +0xb5, 0xa3, 0xec, 0xc7, 0x18, 0xd9, 0xc4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0x13, 0x7a, 0x67, 0x64, 0x4d, 0x0a, 0x8e, +0xca, 0x7d, 0x68, 0xbe, 0x2e, 0xb3, 0xf9, 0x3f, +0x5c, 0x2b, 0xe6, 0x2f, 0x44, 0xfb, 0x58, 0xea, +0x8b, 0x10, 0xc3, 0xe7, 0xb5, 0xd2, 0x46, 0xfe, +0xf6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0x88, 0x3b, 0x1b, 0x96, 0x67, +0xa5, 0xd7, 0x4f, 0xf0, 0x67, 0xb2, 0x93, 0x8a, +0x5a, 0x4f, 0x57, 0x36, 0xe3, 0x46, 0x27, 0x4d, +0x13, 0x93, 0x08, 0x2c, 0x4d, 0x74, 0xd7, 0x5c, +0x4c, 0x14, 0xb8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0xbf, 0x5e, 0x61, +0x01, 0xb4, 0x17, 0x2f, 0x5a, 0x4e, 0x65, 0x30, +0x94, 0xb5, 0xcd, 0xe2, 0xb7, 0x4a, 0x55, 0x8d, +0xfd, 0xc6, 0x38, 0x37, 0x4c, 0x67, 0x14, 0x62, +0x2b, 0xf3, 0x35, 0x22, 0xfd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0xeb, +0x9c, 0x72, 0x6d, 0x5a, 0x3b, 0x4f, 0xb9, 0x57, +0x0c, 0xb1, 0x7c, 0xd9, 0x18, 0x58, 0x8b, 0xfc, +0x29, 0xd4, 0x4b, 0x3f, 0x0a, 0x11, 0x7f, 0xc7, +0x81, 0x52, 0xc4, 0x1c, 0x37, 0xd4, 0xee, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0xf9, 0x29, 0x57, 0xff, 0xc1, 0x21, 0x9e, +0xc0, 0x30, 0x59, 0xe9, 0x23, 0x87, 0xeb, 0x50, +0xa1, 0xa4, 0xc8, 0xb8, 0xa7, 0x6b, 0xc4, 0x20, +0x86, 0x4e, 0x16, 0x17, 0x66, 0xd0, 0x78, 0x8d, +0x11, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0xcd, 0x33, 0x36, 0x02, 0x57, +0x4e, 0x51, 0x63, 0xea, 0xdf, 0x42, 0x19, 0x9d, +0xcb, 0xcd, 0x6a, 0xb1, 0x40, 0xc3, 0x66, 0xad, +0x9d, 0xb0, 0xea, 0xc9, 0x66, 0xea, 0x84, 0x89, +0x8d, 0x45, 0x16, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0x84, 0x4b, 0xe2, +0xb3, 0x6e, 0xc8, 0xa1, 0x13, 0x4c, 0x56, 0xdd, +0xf9, 0x6e, 0xd9, 0xe6, 0x60, 0x01, 0xc7, 0x3a, +0x9c, 0xb7, 0xc5, 0xde, 0x1e, 0xb6, 0x96, 0x55, +0xeb, 0x9f, 0x63, 0x75, 0x39, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x52, +0x6b, 0x5b, 0x82, 0x96, 0x52, 0x95, 0x4c, 0x43, +0x1d, 0x09, 0xf6, 0x80, 0x56, 0x19, 0x7a, 0x0a, +0x5e, 0x2f, 0x4b, 0xcb, 0xc8, 0x4c, 0x46, 0x4f, +0x49, 0xd4, 0x6c, 0xc4, 0x70, 0x88, 0x22, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0xd8, 0x0a, 0x02, 0x7b, 0x74, 0xa2, 0x1c, +0x19, 0x1b, 0x86, 0x07, 0xcf, 0x49, 0x84, 0x67, +0x18, 0xff, 0x64, 0xfe, 0xb4, 0x93, 0xec, 0x52, +0xcf, 0x5e, 0xcc, 0xf1, 0xc9, 0x6c, 0x33, 0x1a, +0x87, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0xfb, 0xf6, 0x4b, 0x9a, 0x59, +0xfd, 0xfc, 0x9f, 0xe9, 0x33, 0xb4, 0x0d, 0x68, +0x81, 0xd4, 0x5f, 0x3c, 0x13, 0x04, 0x1c, 0x28, +0xf4, 0x03, 0xc5, 0xcf, 0x60, 0x60, 0xbd, 0x43, +0x07, 0xbf, 0x68, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0xdd, 0x43, 0xea, +0x8d, 0x48, 0xfd, 0x13, 0x56, 0xd8, 0x66, 0x6e, +0xe2, 0x4d, 0x5e, 0x98, 0xc4, 0xa5, 0xf3, 0x78, +0xfc, 0x2f, 0xf9, 0x44, 0x8a, 0x59, 0x4b, 0xf5, +0xfb, 0x82, 0x9e, 0x70, 0x80, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0xff, +0x14, 0x4d, 0xe8, 0xdb, 0xc7, 0xae, 0x96, 0x0c, +0x78, 0x49, 0xab, 0x53, 0x44, 0x66, 0xe2, 0xc7, +0x57, 0x09, 0x56, 0x27, 0x1b, 0x88, 0xe5, 0x1c, +0x64, 0x67, 0x3c, 0x89, 0x3e, 0x69, 0x70, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0x73, 0xe3, 0x34, 0xbe, 0x5f, 0xf7, 0xd3, +0x6b, 0xb5, 0xe9, 0x5e, 0x53, 0xdb, 0x0b, 0x93, +0xf1, 0x81, 0x3f, 0x12, 0xbd, 0x77, 0x05, 0x09, +0x78, 0xf0, 0x0b, 0xdc, 0xcd, 0xa9, 0x2a, 0x94, +0x90, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x84, 0x30, 0x39, 0xfe, 0xa9, +0x75, 0x61, 0xa1, 0x3a, 0x72, 0xdd, 0x8d, 0x70, +0x08, 0x81, 0xf3, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x5b, 0xd2, 0x62, 0x79, +0xbc, 0x10, 0xf7, 0xc6, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0x36, 0x91, 0x58, +0x4f, 0xff, 0x96, 0x9b, 0x76, 0x74, 0x28, 0xd7, +0x63, 0x11, 0x02, 0xc8, 0x5a, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xad, 0x48, +0x53, 0x3a, 0x34, 0x41, 0x75, 0x43, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0xcb, +0xfe, 0xa4, 0x37, 0xa8, 0x60, 0x21, 0x3a, 0x41, +0xc6, 0xf9, 0x0a, 0xe9, 0x88, 0x4d, 0x39, 0xd5, +0x29, 0xa7, 0xc9, 0xa6, 0x79, 0x5f, 0x3f, 0x9c, +0xae, 0x38, 0x8b, 0xf8, 0xa5, 0x06, 0xfc, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0xba, 0xdb, 0xc3, 0xfb, 0x1d, 0xde, 0x23, +0x89, 0x06, 0xdd, 0xb3, 0x99, 0x45, 0xab, 0x8a, +0x00, 0xbf, 0x72, 0x57, 0xd1, 0x18, 0x54, 0x7f, +0xa7, 0xab, 0x78, 0x33, 0x67, 0xdd, 0xc5, 0x4d, +0x7f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0xc8, 0xa3, 0xbf, 0x1d, 0x8a, +0x8f, 0xb3, 0xc9, 0xe4, 0x81, 0x51, 0x81, 0x9d, +0xc8, 0x8a, 0x21, 0xfd, 0x4e, 0x9e, 0x71, 0x83, +0x36, 0x37, 0xcc, 0xd8, 0x03, 0x2a, 0xb8, 0x08, +0x1d, 0x1f, 0x38, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0x56, 0x49, 0xb6, +0x9f, 0xb4, 0x9b, 0xde, 0xb3, 0x9a, 0x13, 0x68, +0xc3, 0x20, 0x3e, 0xd7, 0xc7, 0x9b, 0xf3, 0x2f, +0xb1, 0xf6, 0x56, 0x50, 0x0d, 0xac, 0x74, 0x1c, +0x44, 0x0c, 0xd5, 0x4f, 0xc6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0x8d, +0xcc, 0x34, 0x3f, 0xe3, 0x7d, 0x08, 0xc4, 0x34, +0x3e, 0xa4, 0x2d, 0x7b, 0xea, 0x01, 0xf2, 0x96, +0xa3, 0xdc, 0x4f, 0x55, 0xca, 0xb3, 0xa4, 0x33, +0x7f, 0xe9, 0x5e, 0x69, 0x22, 0xd7, 0x28, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0x7f, 0x00, 0xd4, 0x94, 0xac, 0x24, 0xd5, +0xa2, 0xa5, 0x10, 0xcf, 0xc4, 0xfd, 0xef, 0x70, +0x11, 0x48, 0x8a, 0xd5, 0x50, 0x52, 0x66, 0x97, +0xb4, 0xb7, 0x26, 0xeb, 0x56, 0x6a, 0x75, 0xed, +0xac, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0xf9, 0x7a, 0xdf, 0x0f, 0xd0, +0xe1, 0xdf, 0x9b, 0x20, 0x27, 0x3b, 0x4a, 0x80, +0xbc, 0x6b, 0xb0, 0x86, 0x3a, 0x16, 0xf0, 0x18, +0x9e, 0x40, 0xcb, 0xe7, 0x42, 0x19, 0x4f, 0x71, +0x94, 0x80, 0x20, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0x2b, 0x12, 0x2a, +0xa3, 0x23, 0x62, 0xa6, 0x30, 0xad, 0x33, 0x8b, +0xea, 0xb4, 0x0d, 0x70, 0xc3, 0x95, 0x7d, 0xe1, +0x83, 0xf1, 0x5d, 0xd4, 0x5e, 0x02, 0x6e, 0x8d, +0x51, 0xba, 0x35, 0x97, 0xfd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0xbf, +0x2d, 0x58, 0x7b, 0x64, 0xd6, 0x50, 0x5e, 0xe7, +0xd1, 0x12, 0x83, 0xff, 0x4f, 0xbe, 0xba, 0x2e, +0x5a, 0x7e, 0xd0, 0x1d, 0xf0, 0x69, 0xa6, 0x65, +0x47, 0x35, 0xf0, 0x5f, 0x90, 0x6e, 0x5a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0xcc, 0x07, 0x99, 0x35, 0x77, 0xf2, 0x19, +0x20, 0xf0, 0xd4, 0x32, 0x24, 0x45, 0xa1, 0x26, +0x51, 0xa4, 0x1c, 0x22, 0xf9, 0xbf, 0xb2, 0x47, +0x1e, 0x4a, 0x95, 0x52, 0x1b, 0xe9, 0xfa, 0x58, +0xff, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0x26, 0x92, 0x6c, 0xea, 0xb8, +0x47, 0xcc, 0x8a, 0xbd, 0xba, 0x71, 0x02, 0x6c, +0x65, 0x99, 0x21, 0x12, 0x07, 0x7b, 0xb0, 0xf9, +0xf8, 0x3e, 0x5b, 0xa1, 0xcb, 0x14, 0x9f, 0xe3, +0x93, 0x57, 0x8a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0x7c, 0x46, 0x4b, +0x88, 0xfa, 0x50, 0x47, 0x8a, 0x24, 0x90, 0x85, +0x73, 0xc5, 0xe5, 0x2e, 0x85, 0xa4, 0x95, 0x3b, +0xee, 0x61, 0x07, 0x5e, 0x3f, 0x85, 0xa3, 0x42, +0xd9, 0xbe, 0xc3, 0x3e, 0x20, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0x0e, +0x2b, 0x25, 0x5d, 0x58, 0xd7, 0x95, 0x36, 0x09, +0x1e, 0x78, 0x65, 0xad, 0x70, 0xb7, 0x4f, 0xcf, +0x94, 0xf6, 0x63, 0xaf, 0xc0, 0x0a, 0xa0, 0x3e, +0xdb, 0x1e, 0x49, 0x29, 0xec, 0xca, 0x31, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x3f, 0xee, 0x85, 0x13, 0xc5, 0xd6, 0xdf, +0xa2, 0x0b, 0x37, 0x73, 0xc7, 0x1c, 0x43, 0xfb, +0xf2, 0xa5, 0x85, 0x26, 0xba, 0x4c, 0x18, 0x2c, +0xeb, 0x37, 0x30, 0x8c, 0x37, 0x88, 0xad, 0x6b, +0xd0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0x14, 0x9a, 0xfb, 0xcc, 0x98, +0xd0, 0xca, 0x3f, 0xca, 0xd4, 0x7c, 0xd4, 0xfd, +0xf5, 0x77, 0x0c, 0x42, 0xc7, 0x3b, 0x9b, 0x2a, +0x38, 0xb7, 0x6c, 0x3c, 0x8f, 0x10, 0x17, 0x33, +0x7f, 0x49, 0xbf, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x48, 0x7e, 0x3e, +0x0e, 0x3d, 0x93, 0x8a, 0x95, 0xa8, 0xd1, 0x31, +0x8c, 0x73, 0xc9, 0x72, 0x8b, 0xe8, 0x8e, 0x2f, +0x1d, 0x04, 0x41, 0x57, 0x15, 0x30, 0x8e, 0x54, +0xdf, 0xf0, 0x02, 0xe9, 0xe6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0x57, +0xeb, 0x45, 0x88, 0xf1, 0xa5, 0x71, 0xed, 0x11, +0x95, 0xd5, 0xc6, 0xd2, 0x9b, 0x63, 0x6d, 0x76, +0x1c, 0x85, 0x48, 0x4a, 0x71, 0x02, 0x37, 0x14, +0x3d, 0x1f, 0x7c, 0xed, 0xd1, 0x0f, 0xdf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0xa2, 0xa9, 0xb9, 0x75, 0x03, 0xce, 0xe6, +0xb5, 0x6e, 0xf3, 0x48, 0x84, 0x56, 0x04, 0xa5, +0x26, 0xea, 0xa2, 0xc1, 0xbf, 0xed, 0xb0, 0x42, +0x39, 0x4c, 0x7a, 0x72, 0x58, 0x72, 0x83, 0xea, +0x64, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0x81, 0x2e, 0x6d, 0xdd, 0xd1, +0x8f, 0xad, 0x7f, 0x0f, 0x92, 0x6a, 0x98, 0x11, +0x8c, 0x8c, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x68, 0xfa, 0xa8, 0x35, +0x6b, 0x38, 0xd1, 0x34, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x08, 0x58, 0x5c, +0x80, 0xfb, 0x45, 0x61, 0xe3, 0xf3, 0xe7, 0x59, +0x6c, 0xf1, 0xe8, 0x3f, 0x77, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6b, 0xce, +0x08, 0x32, 0xaf, 0x21, 0xd2, 0xc8, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0x8f, +0x09, 0xd1, 0x91, 0x26, 0x72, 0x56, 0x75, 0xb9, +0x2d, 0x7f, 0x27, 0x63, 0xe5, 0x35, 0xad, 0x4d, +0xcc, 0x44, 0x76, 0xdc, 0xb6, 0x17, 0x08, 0x03, +0xcd, 0x1c, 0x09, 0xe6, 0x7b, 0xce, 0xc1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0xf3, 0x35, 0x02, 0x49, 0xb8, 0xd4, 0x9e, +0x5e, 0x61, 0x45, 0x14, 0x86, 0x89, 0x90, 0x37, +0xe1, 0x02, 0xe7, 0xe5, 0x29, 0x0b, 0xce, 0xaa, +0x49, 0xe9, 0x61, 0x94, 0x29, 0xee, 0xf4, 0x75, +0x2b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0x84, 0xeb, 0x1a, 0xb7, 0xeb, +0x84, 0x17, 0x5b, 0xbe, 0x98, 0x0f, 0xcf, 0xb2, +0xcf, 0xf9, 0xc1, 0x4d, 0x42, 0xa5, 0x84, 0xe6, +0x2c, 0xd7, 0xac, 0xa0, 0x09, 0x63, 0x6c, 0x65, +0xab, 0x81, 0x01, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0xbf, 0x57, 0xc8, +0xe7, 0x4a, 0x56, 0x29, 0x7f, 0x04, 0xea, 0x01, +0x5e, 0x86, 0x3b, 0x0f, 0xa3, 0x59, 0xbb, 0x9a, +0xc4, 0x19, 0x2e, 0xe8, 0x2b, 0x8d, 0xe2, 0xb5, +0x0c, 0x1c, 0xc4, 0x56, 0x6f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0x38, +0xa6, 0xe7, 0x38, 0x42, 0xed, 0x5e, 0x64, 0x8a, +0x6f, 0x75, 0x62, 0xe6, 0x68, 0x35, 0x86, 0x57, +0x14, 0x85, 0xfb, 0xac, 0x47, 0xd4, 0x8a, 0xa8, +0x3a, 0x07, 0x3b, 0xb6, 0x4c, 0x76, 0xfe, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0x2b, 0xc8, 0x55, 0xa0, 0xd6, 0x4d, 0x96, +0xaa, 0x09, 0x8e, 0xc5, 0xae, 0x28, 0xc6, 0x5e, +0x70, 0xc5, 0x56, 0xc6, 0x2e, 0x3f, 0xcb, 0xa0, +0xbe, 0x28, 0xcf, 0x72, 0xc1, 0xeb, 0x76, 0x51, +0xef, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0xb9, 0xa4, 0xfd, 0x1c, 0x18, +0xa0, 0x9d, 0x34, 0x7c, 0x4d, 0x95, 0x98, 0x4a, +0xdf, 0x53, 0x67, 0xe7, 0xab, 0x5c, 0x3d, 0xea, +0xe5, 0xc3, 0x1c, 0xde, 0x28, 0xe3, 0xa9, 0xbf, +0xbe, 0x71, 0x75, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x21, 0xc6, 0x95, +0x57, 0xb1, 0xd5, 0x88, 0x14, 0xc1, 0x04, 0xc4, +0x2c, 0x3a, 0xe3, 0x16, 0x82, 0xfc, 0x96, 0x3b, +0x43, 0x38, 0x78, 0x3b, 0x05, 0x4c, 0xed, 0xd5, +0xb3, 0xba, 0xf2, 0x29, 0x36, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0xd3, +0x4f, 0x4c, 0xec, 0xf9, 0x31, 0x2a, 0x69, 0x9f, +0x85, 0x88, 0x81, 0x97, 0xb7, 0x6d, 0xda, 0x55, +0xa1, 0xde, 0xbd, 0xe2, 0x4c, 0x60, 0x71, 0x28, +0xf8, 0xbe, 0xd8, 0xd4, 0x77, 0x5d, 0x6c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0xe6, 0xb2, 0x96, 0x23, 0xd9, 0x69, 0x6b, +0x58, 0xad, 0xbb, 0xd1, 0x20, 0xac, 0x57, 0x3b, +0xb1, 0xf2, 0xc8, 0x0e, 0x8e, 0xbd, 0x0c, 0x96, +0x2c, 0x7e, 0xde, 0x27, 0xbd, 0x38, 0x4d, 0x28, +0x1b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0xf7, 0x88, 0x27, 0xe1, 0x20, +0xa9, 0x41, 0x88, 0xab, 0x97, 0xe9, 0x29, 0x45, +0xc7, 0x0a, 0xcf, 0xdd, 0x08, 0x9e, 0x36, 0x2d, +0x89, 0x04, 0x82, 0xb7, 0xaa, 0x16, 0x4e, 0x05, +0x2b, 0x58, 0xc9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0xfa, 0x59, 0xa5, +0xaa, 0xc2, 0x64, 0xc7, 0x33, 0x0a, 0x66, 0xda, +0x31, 0xe8, 0x98, 0xfc, 0x2a, 0x85, 0x3d, 0x95, +0x35, 0x26, 0x83, 0x3e, 0x0b, 0x69, 0xff, 0x28, +0x54, 0x23, 0xd5, 0x78, 0xbf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0x52, +0xb2, 0xd4, 0x4e, 0xdc, 0x1a, 0x9a, 0x1b, 0xb4, +0xcb, 0x91, 0x1c, 0x80, 0xf3, 0x7d, 0x61, 0x2e, +0xc3, 0x90, 0x9c, 0x1e, 0x10, 0x9b, 0x91, 0x36, +0x3c, 0x49, 0x7a, 0xee, 0x3a, 0xff, 0x39, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x6e, 0x85, 0xdb, 0xc9, 0xb4, 0x20, 0xbc, +0xd9, 0x06, 0x1e, 0xd7, 0x5d, 0x3d, 0x9a, 0x78, +0x2b, 0xe9, 0xaf, 0x49, 0xfe, 0x42, 0x7a, 0x8d, +0xa0, 0xd7, 0xb6, 0xd3, 0xb8, 0x8a, 0xf5, 0x33, +0xaf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0xec, 0xa3, 0x7b, 0x98, 0x80, +0x52, 0xcc, 0x84, 0x66, 0xca, 0x35, 0x0a, 0x6c, +0x84, 0x9e, 0xe8, 0x2a, 0x03, 0x09, 0x30, 0x9e, +0xe2, 0x55, 0x28, 0x37, 0x6a, 0xc6, 0x7c, 0xa0, +0x9f, 0x51, 0x22, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0x7c, 0x14, 0x34, +0x33, 0xfd, 0xcf, 0xc9, 0xfa, 0x4a, 0x54, 0x89, +0x81, 0x03, 0xfe, 0xab, 0xa9, 0x5a, 0x70, 0x3b, +0x83, 0xe9, 0x24, 0x29, 0x0d, 0x27, 0x13, 0x52, +0xce, 0x32, 0xdf, 0x1a, 0xa0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0x60, +0x30, 0x34, 0xcc, 0x07, 0xc1, 0x30, 0x33, 0x98, +0xe2, 0x16, 0xfb, 0xc0, 0xa0, 0x66, 0xdb, 0xcc, +0x58, 0x15, 0x61, 0xe1, 0x26, 0x22, 0xab, 0xb1, +0xdf, 0xc2, 0xe2, 0x32, 0x52, 0x35, 0x38, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0xd0, 0xb4, 0xc0, 0x82, 0x73, 0x6b, 0xa4, +0xe4, 0x25, 0xe5, 0xb1, 0x23, 0x76, 0x07, 0x4f, +0xfa, 0xf4, 0x27, 0xbf, 0x26, 0x28, 0xb0, 0xd3, +0x82, 0xb7, 0x5d, 0x33, 0x3e, 0x13, 0x6f, 0x66, +0x51, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0x8d, 0x77, 0x2b, 0x5d, 0xca, +0xde, 0xe0, 0x94, 0xf0, 0x99, 0xe1, 0x40, 0x81, +0x43, 0x32, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x5c, 0xdc, 0xd5, 0x55, +0x71, 0xe9, 0xfd, 0x53, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0x00, 0x6b, 0x10, +0xcb, 0x96, 0xc0, 0xc6, 0x2e, 0x27, 0xfb, 0xd1, +0x19, 0x85, 0xd7, 0xb0, 0x18, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x94, 0x65, +0xb8, 0xc9, 0x78, 0xff, 0x69, 0xde, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0xb8, +0x60, 0x08, 0x6f, 0x6a, 0xf6, 0x82, 0xef, 0x4a, +0xba, 0x18, 0xcf, 0x6a, 0x17, 0x9d, 0x30, 0x0b, +0x2e, 0x62, 0x17, 0x39, 0x70, 0xee, 0xd2, 0xc5, +0xe7, 0x23, 0xfb, 0x28, 0x8e, 0x0e, 0x04, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x59, 0xe8, 0xf4, 0x7c, 0xb7, 0x21, 0xe4, +0x8e, 0xaf, 0xcd, 0xee, 0xb3, 0x1e, 0xb3, 0x86, +0x6d, 0xbf, 0xf1, 0xa2, 0x2e, 0x6a, 0xa1, 0xf3, +0x7c, 0xea, 0xbf, 0x65, 0x96, 0x40, 0x2f, 0xbf, +0x94, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0xa4, 0x6d, 0xf4, 0x08, 0xf3, +0xdc, 0x3a, 0x55, 0x27, 0x21, 0xbb, 0x18, 0x61, +0x23, 0xc6, 0x09, 0xde, 0xf6, 0xf3, 0x04, 0xf0, +0x31, 0x9c, 0xba, 0x90, 0xd4, 0xcf, 0x78, 0x15, +0x66, 0x0f, 0x98, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0x2b, 0x15, 0x8b, +0x85, 0x4a, 0x5c, 0x60, 0x12, 0x84, 0xde, 0x07, +0x8c, 0x4e, 0x5d, 0x0a, 0x53, 0xbb, 0x9c, 0x5c, +0xc9, 0xa1, 0x13, 0xe7, 0x6d, 0x79, 0x1d, 0xc1, +0x52, 0xd3, 0x77, 0x0f, 0xd7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0x2a, +0xef, 0xf2, 0xaa, 0xe6, 0x89, 0x92, 0x26, 0x79, +0x72, 0x59, 0x75, 0xeb, 0x02, 0x20, 0xe5, 0x84, +0x9f, 0x6f, 0x6e, 0x60, 0x88, 0xd6, 0xb6, 0x96, +0xc9, 0xc4, 0xec, 0x9a, 0xb5, 0x44, 0xd9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0x67, 0x58, 0x8f, 0xf3, 0x41, 0x1c, 0x64, +0xfd, 0x87, 0xf2, 0xad, 0x18, 0x54, 0xf0, 0x65, +0xaf, 0x4d, 0xcc, 0xf0, 0x3d, 0xa0, 0x6b, 0x65, +0x3a, 0xf7, 0x70, 0x3d, 0xf2, 0xc5, 0x0e, 0x48, +0x8f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0x01, 0xad, 0x50, 0xad, 0x3d, +0x85, 0xb9, 0x04, 0xc1, 0xa7, 0xcd, 0xf4, 0x77, +0xcf, 0xd1, 0xee, 0x4f, 0x7e, 0x7c, 0x47, 0xbb, +0x0f, 0xc8, 0x51, 0xa8, 0x16, 0x4e, 0xb9, 0x7d, +0xfa, 0x90, 0xd8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0xcf, 0x4b, 0x1b, +0x15, 0x42, 0x84, 0x63, 0xf6, 0xf0, 0x5a, 0xb5, +0x46, 0x7b, 0xa4, 0xd3, 0x0d, 0xd8, 0xc7, 0x10, +0xf2, 0x86, 0x11, 0x01, 0x25, 0x5d, 0x49, 0x99, +0xe0, 0xa9, 0x5f, 0x2f, 0x8f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0xef, +0x7a, 0x92, 0x15, 0xc1, 0x5f, 0x2e, 0xef, 0x92, +0x3c, 0x53, 0x77, 0x38, 0x1d, 0xd0, 0xcd, 0x26, +0x6b, 0x5e, 0x05, 0xd9, 0x39, 0x03, 0x79, 0xc6, +0xd7, 0x81, 0xd9, 0x74, 0x2c, 0x3f, 0x06, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0xaf, 0xd4, 0x26, 0xe1, 0xb7, 0xfb, 0xc8, +0xcf, 0xac, 0xd7, 0x3b, 0xf6, 0x67, 0x5a, 0xae, +0xc7, 0x95, 0xd3, 0x4d, 0xa0, 0x09, 0xa0, 0x5a, +0xd6, 0x49, 0x6a, 0xa4, 0xf4, 0x28, 0x2c, 0xb3, +0xc9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0xac, 0xb8, 0x62, 0x02, 0x7a, +0x08, 0x23, 0x59, 0xf8, 0xdb, 0x89, 0x3a, 0xab, +0x0d, 0xe4, 0xf3, 0x15, 0x3a, 0xf1, 0x36, 0x6b, +0xb8, 0x43, 0xcf, 0xdb, 0x1b, 0x43, 0x8a, 0xab, +0xa8, 0x96, 0xcd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x68, 0x5f, 0xf7, +0x3e, 0x4c, 0x08, 0x1b, 0x73, 0x58, 0xa9, 0xe2, +0x17, 0x42, 0x09, 0xd3, 0xf1, 0x19, 0x1d, 0x12, +0xd8, 0x4d, 0xad, 0xf5, 0x72, 0xc0, 0x23, 0xc3, +0xe4, 0xfa, 0x0e, 0x15, 0xd0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0x22, +0xc9, 0x7c, 0x2b, 0x30, 0xec, 0x6b, 0x26, 0x88, +0xa6, 0xae, 0x1f, 0x16, 0xb8, 0x88, 0x46, 0x4d, +0x49, 0x2e, 0x36, 0x9a, 0xb8, 0x41, 0x69, 0x12, +0x05, 0xa0, 0xec, 0x4c, 0x1f, 0x8f, 0x65, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0x7b, 0xa1, 0x29, 0x26, 0xda, 0xef, 0x87, +0xda, 0x09, 0x66, 0xac, 0xfa, 0x98, 0x0d, 0x7e, +0x76, 0x47, 0x20, 0x8c, 0x7d, 0x81, 0xe0, 0x00, +0xc9, 0xbe, 0x4c, 0xae, 0xd9, 0x47, 0xb6, 0x24, +0x4a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0xbb, 0xcc, 0x68, 0x27, 0x89, +0x22, 0x74, 0x05, 0xc6, 0x46, 0xc4, 0x65, 0x35, +0x4d, 0xfe, 0x80, 0x50, 0x4a, 0x2a, 0x8c, 0x38, +0x72, 0x74, 0xba, 0x84, 0x40, 0xe6, 0x75, 0x42, +0x73, 0xa5, 0x08, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0x66, 0xb9, 0xc9, +0x4e, 0x83, 0x8c, 0x90, 0xe1, 0xc8, 0x73, 0x59, +0x42, 0x1b, 0x99, 0xc9, 0xf1, 0x36, 0x63, 0x95, +0xf4, 0x97, 0x0e, 0x4f, 0x19, 0x4e, 0x56, 0x89, +0x81, 0x7c, 0x42, 0x66, 0xbd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0x38, +0x83, 0x2f, 0x79, 0x26, 0xff, 0x21, 0x8f, 0x3d, +0x67, 0x78, 0x29, 0xad, 0x89, 0x4f, 0x6b, 0x82, +0x82, 0x10, 0xd3, 0xbc, 0x15, 0x60, 0xa9, 0xeb, +0xb2, 0xd8, 0x1e, 0xa8, 0x83, 0x28, 0xc1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0x18, 0x46, 0xb4, 0x0a, 0x0f, 0x89, 0x0f, +0xc2, 0xf1, 0xb3, 0x08, 0x92, 0xd4, 0x5e, 0x14, +0x06, 0xc4, 0x2c, 0x99, 0x7b, 0xdf, 0xc1, 0xdf, +0x23, 0x1d, 0xb1, 0x4c, 0xfb, 0x05, 0x89, 0xd1, +0x39, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x87, 0xd8, 0x9c, 0x60, 0x8c, +0x90, 0x58, 0x71, 0x89, 0xf1, 0x11, 0x6a, 0x44, +0x9c, 0x37, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x6b, 0x5f, 0x30, 0xae, +0xee, 0x45, 0x0f, 0x7e, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0x8c, 0x60, 0xfc, +0xdb, 0xf9, 0x53, 0xc6, 0x08, 0xd0, 0x8d, 0x43, +0x16, 0x4f, 0x12, 0x57, 0xd3, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc9, 0xec, +0xd8, 0x2f, 0x43, 0x4e, 0x44, 0xcc, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0x46, +0xa4, 0xc1, 0xbc, 0xc4, 0x3f, 0x40, 0xfb, 0x64, +0xac, 0xf3, 0x8c, 0x8a, 0x7e, 0xe5, 0xa0, 0x10, +0xba, 0xdf, 0xcc, 0x35, 0x16, 0x56, 0xc8, 0x73, +0x8c, 0xaf, 0x8e, 0xd7, 0xfd, 0x3d, 0x30, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0xbd, 0x0e, 0x86, 0x87, 0xda, 0x42, 0x7c, +0xdb, 0x35, 0xf4, 0x5d, 0x04, 0xec, 0x99, 0x34, +0xa0, 0x9a, 0x4d, 0xcc, 0x9a, 0x7a, 0x39, 0x7c, +0x61, 0x17, 0x6c, 0x27, 0x7b, 0x01, 0x6e, 0x88, +0xb7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0x47, 0x7d, 0x2e, 0x11, 0x41, +0x7a, 0x86, 0x6c, 0x57, 0x14, 0xed, 0x28, 0xab, +0xda, 0xe1, 0xfe, 0x67, 0x16, 0x47, 0xa8, 0xd7, +0xee, 0x4d, 0xad, 0x4e, 0x70, 0xf9, 0xf1, 0x98, +0xc4, 0xf4, 0x85, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0xde, 0x46, 0x7a, +0xb1, 0x70, 0xb3, 0x95, 0xeb, 0xa2, 0x5d, 0x77, +0xb7, 0x2f, 0xd0, 0x6e, 0xb7, 0x28, 0xfb, 0x99, +0x86, 0x9b, 0x2d, 0x82, 0x96, 0xa8, 0xf1, 0x4a, +0x44, 0x8a, 0x3c, 0x18, 0xe7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0xde, +0xb5, 0x74, 0xbd, 0x84, 0x9f, 0x3b, 0x7f, 0x8e, +0xcd, 0xdb, 0x81, 0x20, 0xc3, 0x92, 0x2d, 0x7b, +0xba, 0x84, 0x9f, 0xbe, 0xf2, 0x11, 0xc3, 0x48, +0x0d, 0xb8, 0x00, 0x41, 0xa5, 0xdc, 0x9a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x1b, 0x76, 0x3c, 0xab, 0x4d, 0xdb, 0x3a, +0x71, 0xa3, 0xe2, 0x2d, 0xf2, 0x8d, 0xbc, 0x26, +0x96, 0xca, 0xc3, 0x78, 0xf0, 0x76, 0xc0, 0x16, +0xa9, 0xeb, 0x77, 0x61, 0xed, 0x90, 0x18, 0xde, +0x87, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0xdf, 0xc1, 0xda, 0x73, 0x1e, +0xea, 0xf1, 0x08, 0xdb, 0xe6, 0x46, 0x79, 0xfe, +0xba, 0x8b, 0x34, 0x90, 0xe5, 0xdb, 0x47, 0x35, +0xf7, 0xc1, 0x4a, 0x5d, 0xde, 0x68, 0xdd, 0x19, +0x62, 0x29, 0x1f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0x8d, 0x7c, 0x7c, +0x9a, 0x58, 0x5f, 0xaa, 0xcb, 0x79, 0x94, 0xf3, +0x16, 0x24, 0x18, 0xee, 0x10, 0xe4, 0x0f, 0x20, +0xa7, 0xc8, 0x01, 0x97, 0x51, 0x7e, 0x67, 0xf6, +0x9b, 0x31, 0x40, 0xb3, 0x72, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0x7f, +0x61, 0x60, 0x56, 0x7f, 0xae, 0x57, 0xa7, 0x6a, +0x02, 0xc4, 0x1a, 0x92, 0x4c, 0xa0, 0xef, 0x3e, +0xea, 0x15, 0xf1, 0x09, 0x32, 0x88, 0x7c, 0x16, +0xe8, 0xfe, 0x50, 0xfc, 0x26, 0x88, 0x4c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0xcb, 0xc7, 0xe8, 0x9a, 0x19, 0xf5, 0xc4, +0x9f, 0xbf, 0xfe, 0x1e, 0xbd, 0xe2, 0x0c, 0x88, +0x7f, 0x0f, 0x37, 0x23, 0x0c, 0x9f, 0x50, 0x5b, +0x26, 0x22, 0x19, 0x27, 0x03, 0xa3, 0x92, 0xc8, +0x9e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0x9b, 0x62, 0x8d, 0x42, 0xba, +0x0f, 0xc6, 0xa1, 0x07, 0x49, 0x51, 0xab, 0x65, +0x75, 0xde, 0x72, 0x2a, 0xea, 0x84, 0x0f, 0x10, +0xb3, 0xf4, 0xa5, 0x04, 0x38, 0x67, 0x95, 0xcd, +0x5a, 0x21, 0x9d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0xeb, 0x4e, 0x89, +0xdd, 0x5a, 0x85, 0xd9, 0xd0, 0x07, 0x53, 0x5b, +0x00, 0xc9, 0xd7, 0xe3, 0xfd, 0xa3, 0xbe, 0xb1, +0x17, 0x27, 0xe5, 0xcf, 0x93, 0xec, 0x02, 0x96, +0xb8, 0x44, 0xea, 0x1c, 0x6b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0x50, +0x87, 0x91, 0x8b, 0x1a, 0x2c, 0x03, 0xba, 0x8f, +0x04, 0xb3, 0x03, 0xbf, 0x54, 0xe3, 0x2e, 0xf8, +0xf0, 0x34, 0xe5, 0xe2, 0x7a, 0x18, 0xb3, 0x0d, +0xc4, 0x3f, 0x79, 0x70, 0x6c, 0x90, 0x16, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0xe9, 0xfd, 0x1c, 0x6e, 0xb6, 0xe1, 0x51, +0x5a, 0x0b, 0x2b, 0xc7, 0x1b, 0x4c, 0xbb, 0x55, +0x2b, 0x24, 0x62, 0x98, 0x74, 0x6a, 0x39, 0xad, +0x50, 0xa8, 0x3d, 0x41, 0x81, 0x3d, 0x43, 0x08, +0x12, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0xb8, 0x18, 0x92, 0x82, 0x96, +0xef, 0xea, 0x25, 0xfe, 0x01, 0xc8, 0x0e, 0x04, +0x6e, 0x4f, 0xc2, 0xcf, 0x0c, 0xee, 0x4a, 0x11, +0x70, 0x09, 0x13, 0x1c, 0x29, 0xb2, 0x0b, 0x66, +0xf9, 0xe5, 0xd0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0x90, 0xee, 0xa3, +0xa0, 0xa2, 0x7a, 0xb2, 0xf8, 0x7a, 0x28, 0x82, +0xb4, 0x8c, 0x2f, 0xf4, 0x52, 0x37, 0x31, 0x3a, +0x65, 0xe0, 0xff, 0x7c, 0x47, 0x8f, 0x74, 0x3d, +0x31, 0x81, 0x27, 0x17, 0xa6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0x21, +0x3a, 0xbc, 0xba, 0x50, 0xf4, 0x30, 0xad, 0x15, +0x2c, 0xb0, 0xeb, 0x32, 0x58, 0x9b, 0xee, 0x7f, +0x3f, 0xb5, 0x0a, 0x40, 0x61, 0x1b, 0xf1, 0x47, +0x32, 0x40, 0x51, 0xa1, 0x29, 0xc7, 0x2b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0x62, 0xfa, 0x0f, 0xfe, 0xdc, 0xb5, 0x0b, +0xa9, 0x4a, 0x6b, 0x48, 0x9a, 0x8f, 0xf8, 0xdb, +0x9a, 0x89, 0x80, 0x74, 0xbc, 0x62, 0x88, 0x5b, +0x14, 0xc1, 0x06, 0x15, 0x83, 0x72, 0xc2, 0x78, +0x07, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0x55, 0x87, 0x4e, 0xce, 0x9d, +0x92, 0x98, 0xf9, 0xc3, 0xb4, 0xf0, 0x09, 0x27, +0x08, 0x3c, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x6b, 0x30, 0xf8, 0xaa, +0xeb, 0xbf, 0x86, 0x24, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0xfd, 0xaa, 0xbd, +0xce, 0xc8, 0x62, 0xaf, 0x54, 0x66, 0x78, 0xb7, +0x78, 0x5d, 0xff, 0x43, 0x0d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, 0x54, +0x0e, 0x8c, 0x67, 0x07, 0x9d, 0x1f, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0x2e, +0x1d, 0x25, 0x4d, 0x8e, 0x44, 0x55, 0x4e, 0x50, +0x54, 0xa9, 0x87, 0x04, 0x6c, 0x92, 0x03, 0x53, +0x3c, 0xdb, 0x6a, 0x5f, 0x25, 0x53, 0x68, 0x82, +0x64, 0xc0, 0x98, 0x10, 0x99, 0x15, 0xd2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0xbf, 0x5b, 0xb2, 0x2b, 0x8d, 0xd8, 0x6e, +0x24, 0xd5, 0xbe, 0xb7, 0x26, 0x98, 0xe4, 0x97, +0x45, 0xdd, 0x9c, 0x9f, 0xda, 0xd0, 0xd4, 0x85, +0x99, 0xc4, 0xa2, 0x23, 0xf0, 0xe8, 0x2d, 0x3e, +0x6f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0xdc, 0x76, 0x0c, 0x86, 0x77, +0x1e, 0x9f, 0x20, 0x1e, 0x1a, 0x91, 0xae, 0x74, +0x5d, 0x7f, 0x6b, 0x01, 0x8a, 0x6b, 0x25, 0xf5, +0xf1, 0xbb, 0x7d, 0x48, 0xe8, 0x22, 0x4f, 0x2e, +0x33, 0x3e, 0xe7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0xf7, 0x38, 0xec, +0xd6, 0x0b, 0xef, 0xc7, 0x30, 0x79, 0x1b, 0xc2, +0x02, 0x17, 0xac, 0xcb, 0xf4, 0x83, 0x55, 0x53, +0xc4, 0x03, 0xf6, 0x70, 0x94, 0x50, 0xbc, 0xbd, +0x24, 0xc4, 0x32, 0xaf, 0x41, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x23, +0x3b, 0x19, 0xe9, 0x62, 0xe2, 0x8d, 0x4c, 0x29, +0xd5, 0xd1, 0x00, 0x68, 0xb7, 0x87, 0xd6, 0x37, +0x85, 0x4c, 0x04, 0xce, 0xf5, 0xdf, 0x7e, 0xbb, +0xf3, 0xed, 0xe5, 0x2d, 0xca, 0x69, 0x24, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0xa9, 0xb3, 0x83, 0x1f, 0x53, 0x1f, 0xe0, +0xe4, 0x47, 0x38, 0xb9, 0xf1, 0xf5, 0x46, 0x6f, +0x1f, 0x64, 0x3e, 0x1e, 0x5b, 0x58, 0xfe, 0xa3, +0xeb, 0xac, 0x6a, 0x33, 0x12, 0x7d, 0x76, 0x10, +0xc1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0x88, 0x74, 0x40, 0x7f, 0x20, +0x9b, 0x3e, 0x51, 0x0b, 0x66, 0xe6, 0xe5, 0xa4, +0x7e, 0x59, 0xdd, 0xd6, 0xd0, 0xc3, 0x5a, 0xcd, +0xeb, 0x2b, 0xbd, 0xb1, 0xf6, 0x90, 0x6b, 0x49, +0xa3, 0xe1, 0xbb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0xec, 0xbe, 0xc2, +0x03, 0xa5, 0xb3, 0x6c, 0x79, 0xa3, 0x35, 0x6b, +0xbd, 0x55, 0x53, 0x31, 0x80, 0xac, 0x39, 0xde, +0x24, 0xa6, 0xa1, 0xef, 0xa8, 0x26, 0x11, 0xa0, +0xdf, 0x76, 0x9f, 0xf0, 0x79, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0xcf, +0xb1, 0x44, 0x7e, 0xd1, 0xae, 0xf3, 0x09, 0x8d, +0xaf, 0xb9, 0xf5, 0x4f, 0x0a, 0xb9, 0x5d, 0x19, +0xb7, 0xd4, 0x30, 0x2e, 0xac, 0xf7, 0xe2, 0x10, +0xec, 0xfb, 0xbd, 0x74, 0xbd, 0xf2, 0x6a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0x77, 0xfc, 0x94, 0x06, 0x9d, 0xa4, 0xaf, +0x4b, 0x76, 0x19, 0x4a, 0x10, 0xf8, 0xc3, 0xe0, +0x5f, 0xc1, 0x57, 0x9c, 0xa2, 0x4b, 0xe6, 0xf5, +0x6f, 0x43, 0x84, 0x41, 0x39, 0x4e, 0xca, 0x03, +0x6a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0xfd, 0xb0, 0x79, 0x6d, 0xd8, +0x34, 0x06, 0x7a, 0x2b, 0xea, 0x2f, 0xfb, 0x53, +0x63, 0x83, 0xa9, 0x47, 0xfc, 0x96, 0xf1, 0x51, +0x23, 0x5c, 0x75, 0xc1, 0xc4, 0x30, 0x3e, 0x28, +0xad, 0x0d, 0x8e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x27, 0xd1, 0xa3, +0x25, 0x17, 0xad, 0xbd, 0x94, 0x33, 0xb5, 0x81, +0x72, 0x6c, 0xe0, 0x35, 0xad, 0x96, 0xea, 0x20, +0xeb, 0x21, 0x74, 0x62, 0x3e, 0xc3, 0xad, 0x81, +0xbd, 0xa3, 0x43, 0xae, 0xc9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0xcd, +0x62, 0x26, 0x30, 0x2e, 0x0b, 0xee, 0x88, 0xdd, +0xc3, 0x93, 0xab, 0x03, 0x68, 0x37, 0x07, 0xa5, +0x0f, 0x53, 0xc4, 0xf1, 0x82, 0x43, 0x65, 0x66, +0xda, 0x3a, 0x8d, 0xe6, 0x59, 0x63, 0x78, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0x11, 0x03, 0x60, 0xb7, 0xd9, 0x06, 0xee, +0x1f, 0x09, 0xbc, 0x7d, 0x76, 0x7a, 0x5f, 0x83, +0xe9, 0x0e, 0x44, 0xa5, 0x7c, 0xee, 0x7f, 0xad, +0xa5, 0xfb, 0x9c, 0xf5, 0x7b, 0xe7, 0x29, 0xd4, +0xa0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0xd7, 0x97, 0x49, 0xc4, 0xe2, +0xaf, 0x74, 0x3b, 0xbd, 0xde, 0x07, 0x2e, 0x08, +0x60, 0x5a, 0xa5, 0x62, 0xd9, 0x22, 0x0c, 0x42, +0xaa, 0x44, 0x2e, 0xda, 0x8a, 0x09, 0x4e, 0x72, +0x18, 0x82, 0xa7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x2f, 0xc6, 0xff, +0xdf, 0x3d, 0x3e, 0x28, 0xdc, 0xd2, 0xb4, 0x24, +0x39, 0x78, 0x91, 0x07, 0x6d, 0x89, 0x19, 0xdb, +0x58, 0x77, 0x79, 0xd3, 0xbc, 0x0a, 0x5b, 0x95, +0x87, 0xfb, 0x0e, 0x93, 0x8b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0x3a, +0xbd, 0x1c, 0x58, 0x60, 0x0a, 0x9f, 0x7f, 0x60, +0xb6, 0xe3, 0xb0, 0xa6, 0xe0, 0x9c, 0x8c, 0x2b, +0x10, 0x04, 0x23, 0xd9, 0x95, 0xdc, 0x8f, 0x77, +0x32, 0x53, 0x46, 0x60, 0xd7, 0x51, 0x68, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0x42, 0x80, 0xd1, 0xb0, 0x14, 0xbe, 0xc6, +0x5d, 0xdf, 0x37, 0x3c, 0x08, 0x57, 0xa6, 0xbc, +0x0f, 0x3f, 0x36, 0xfb, 0xf0, 0x2e, 0xd8, 0x8a, +0xd1, 0x83, 0x0a, 0xf2, 0x75, 0xb9, 0x12, 0xf7, +0x3d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0x77, 0x7c, 0x4f, 0x7f, 0xa1, +0xe6, 0x06, 0x64, 0xab, 0xc0, 0xd3, 0x5c, 0x27, +0xa9, 0x03, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xd3, 0x32, 0xbe, 0x84, +0x35, 0x3b, 0x9d, 0xcf, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0xd5, 0x96, 0xc0, +0x52, 0x2f, 0x58, 0xa7, 0xb9, 0x45, 0x7a, 0x04, +0x13, 0xea, 0xc4, 0x25, 0xbf, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x30, +0xd5, 0x58, 0x9e, 0x22, 0x99, 0x6e, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x5c, +0xad, 0x77, 0x83, 0xed, 0xec, 0x5f, 0x1b, 0xa2, +0x9d, 0xc5, 0xd3, 0x34, 0x23, 0x2d, 0x59, 0x63, +0x4c, 0xdc, 0xfc, 0x75, 0x27, 0x18, 0xa0, 0x54, +0xc6, 0xb0, 0x58, 0x35, 0x44, 0x41, 0x61, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0x33, 0xa8, 0x10, 0xb5, 0xa4, 0x41, 0xd3, +0xec, 0x0a, 0x5a, 0x62, 0xee, 0x94, 0xe3, 0x52, +0x95, 0x42, 0x0c, 0x60, 0xd6, 0xe8, 0x8e, 0x4b, +0x48, 0xb1, 0x07, 0xf8, 0x62, 0xdc, 0xd1, 0xf9, +0x28, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0x3d, 0x87, 0x64, 0xb8, 0xa8, +0x54, 0x07, 0x3a, 0x2a, 0x9e, 0x2a, 0x9d, 0x53, +0xea, 0xaf, 0xea, 0x9e, 0x07, 0x0b, 0x1e, 0x76, +0xac, 0xd3, 0x39, 0xea, 0xb5, 0xb8, 0x3d, 0xc8, +0xba, 0x32, 0x50, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0xba, 0x07, 0xef, +0xd6, 0xaf, 0x5e, 0x46, 0x2f, 0xfd, 0xf5, 0xfa, +0xaf, 0x3f, 0x56, 0xf8, 0xc7, 0x8d, 0xe1, 0x5c, +0x49, 0xdd, 0x27, 0x7d, 0x81, 0x8e, 0x23, 0x61, +0x41, 0xd3, 0xed, 0xe9, 0x0c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0x55, +0x9d, 0xb0, 0x3e, 0x0c, 0x3f, 0x8b, 0x0b, 0x4f, +0x1a, 0xc8, 0xb1, 0x6d, 0x93, 0x17, 0xb5, 0x14, +0xa7, 0xcf, 0x09, 0xc5, 0xef, 0x87, 0x21, 0x5a, +0xd6, 0x79, 0xd9, 0xad, 0x93, 0xd1, 0x1a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0x9f, 0x64, 0xc6, 0xc7, 0x3a, 0xfd, 0x0f, +0xbf, 0xad, 0xc2, 0xc6, 0x96, 0x0f, 0x5d, 0x68, +0x98, 0x01, 0x6b, 0x86, 0x57, 0x57, 0xeb, 0xb0, +0x11, 0x06, 0xce, 0x34, 0xaf, 0x5b, 0xb4, 0x89, +0x7d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0xfb, 0x24, 0x15, 0x61, 0x70, +0x45, 0xc1, 0xf3, 0x02, 0x9b, 0xc1, 0xab, 0x2c, +0x69, 0xc4, 0xbb, 0xab, 0xfe, 0xb8, 0x54, 0x79, +0x3d, 0xa5, 0xaf, 0x91, 0xbe, 0x04, 0xe5, 0x1a, +0x7b, 0x8b, 0x1f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0x0e, 0xe9, 0x9d, +0x60, 0x89, 0xce, 0x51, 0x81, 0x95, 0xf5, 0xef, +0x7d, 0x4c, 0x8a, 0xd0, 0x11, 0xb6, 0xf9, 0xe3, +0xa3, 0x8f, 0x99, 0x59, 0x99, 0x96, 0x0c, 0x31, +0xdf, 0x2e, 0x26, 0x2f, 0x99, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0x5e, +0xd2, 0xb0, 0xca, 0xc3, 0xf6, 0xab, 0x1b, 0x5c, +0x82, 0xcc, 0xa5, 0x2e, 0xa5, 0xfe, 0xb0, 0x31, +0xc8, 0x59, 0x2a, 0xc9, 0xa5, 0x75, 0x33, 0x34, +0xa2, 0xac, 0xbb, 0xcc, 0x4e, 0xfd, 0x0a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0x0b, 0x19, 0x22, 0x48, 0xc9, 0x31, 0x70, +0xc9, 0xe9, 0x77, 0x50, 0xcf, 0x15, 0x1d, 0x88, +0x0a, 0x49, 0x47, 0x24, 0xcd, 0x1d, 0x3d, 0x23, +0x24, 0x0c, 0x68, 0xd8, 0xf0, 0xa7, 0x54, 0xb7, +0xc3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0x6a, 0x6b, 0x33, 0x75, 0xb6, +0x14, 0x44, 0x13, 0xab, 0x8b, 0x4c, 0xb5, 0x7c, +0x8e, 0x59, 0x27, 0x7c, 0x2a, 0x27, 0x9c, 0x56, +0xb4, 0x64, 0xeb, 0x6a, 0xcc, 0x5d, 0x1f, 0xb8, +0x31, 0x9e, 0x1f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0xf4, 0x4c, 0xca, +0x06, 0x95, 0x1c, 0x3c, 0x39, 0x09, 0x4a, 0x19, +0xdb, 0x8e, 0x24, 0x2a, 0x7a, 0x01, 0xb3, 0xf8, +0xf7, 0x85, 0x68, 0x83, 0xba, 0xdb, 0x3a, 0x4b, +0xff, 0xf2, 0x37, 0x99, 0x24, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0xef, +0xa5, 0xad, 0x63, 0xa9, 0x59, 0xf6, 0xc3, 0x8c, +0x3c, 0xa8, 0x96, 0xc0, 0x95, 0xf0, 0x78, 0xc6, +0xe6, 0x9e, 0x0b, 0x67, 0xca, 0x30, 0xc9, 0x1a, +0x64, 0xcb, 0x38, 0x56, 0x59, 0xf3, 0x65, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0x56, 0x8d, 0x81, 0x7f, 0xf4, 0x6f, 0xd4, +0xcf, 0x37, 0x07, 0xb6, 0x63, 0x9a, 0x9a, 0x5f, +0x13, 0xd9, 0xec, 0xa6, 0x7a, 0x6e, 0x7f, 0xdb, +0xa2, 0xc7, 0x5f, 0x3f, 0xa1, 0x5a, 0xb7, 0x13, +0x38, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0x5a, 0x31, 0x44, 0x8a, 0x95, +0x4f, 0x23, 0xab, 0x16, 0xe8, 0x52, 0xb0, 0x3a, +0x87, 0x7d, 0x80, 0xf9, 0xae, 0xba, 0x37, 0xb9, +0x44, 0xa1, 0x46, 0x2c, 0x4f, 0xf7, 0xce, 0x6f, +0x42, 0x3b, 0xc3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0xe7, 0x5d, 0x7b, +0x65, 0xf3, 0xfb, 0x83, 0xff, 0x61, 0x16, 0x88, +0x84, 0xa3, 0xfa, 0xcf, 0xd9, 0xf2, 0x02, 0xfe, +0x95, 0xdb, 0xb0, 0x99, 0xaa, 0xb0, 0x2a, 0x46, +0x20, 0x3e, 0x22, 0xf6, 0x63, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x07, +0xdd, 0x6d, 0x2c, 0x25, 0xc5, 0xf3, 0x55, 0x00, +0x6e, 0x0b, 0xdc, 0x0a, 0x00, 0xf9, 0x7c, 0x29, +0x62, 0x26, 0xb0, 0xd0, 0x6a, 0xb8, 0x94, 0xbb, +0x58, 0x43, 0x60, 0x21, 0xcd, 0xa8, 0x22, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x5e, 0x27, 0x73, 0x93, 0x79, 0x56, 0x50, +0x53, 0xee, 0x78, 0x94, 0xb1, 0xa1, 0xd8, 0x1c, +0x5d, 0x18, 0x70, 0x09, 0xda, 0xa3, 0x6b, 0xcf, +0x0e, 0xee, 0xb2, 0xe7, 0x5f, 0x15, 0x9d, 0xf8, +0x33, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0xa5, 0x5c, 0xb5, 0x89, 0xf3, +0xde, 0x3c, 0x8a, 0x1d, 0xf1, 0x43, 0x91, 0xbf, +0x5f, 0x42, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xb9, 0xc5, 0x3c, 0x29, +0x97, 0xbb, 0x7c, 0x84, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0x98, 0x9a, 0x36, +0x6b, 0x56, 0xb1, 0x6d, 0x11, 0xc4, 0x10, 0xdd, +0xf1, 0xd9, 0x47, 0x47, 0x4f, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xac, +0xe0, 0x91, 0xdc, 0xa9, 0x3f, 0x50, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0x83, +0xb7, 0x83, 0xeb, 0xc7, 0x71, 0x91, 0x71, 0x71, +0x74, 0x4a, 0x27, 0x81, 0x46, 0x6e, 0x50, 0x3d, +0x5f, 0x3e, 0x0a, 0x86, 0x2a, 0x54, 0x04, 0xcd, +0xcb, 0x50, 0x93, 0xab, 0x3f, 0x7b, 0x6f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0x0c, 0xb3, 0x7d, 0x7b, 0x33, 0x7c, 0x56, +0x88, 0x5c, 0x07, 0x31, 0x1e, 0x3d, 0x07, 0x61, +0x1d, 0x6f, 0x35, 0xfa, 0x95, 0x77, 0x43, 0x65, +0x13, 0x17, 0x6e, 0x00, 0x3b, 0x61, 0x80, 0x5a, +0x31, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0x77, 0xd0, 0x09, 0xd0, 0x97, +0x52, 0x12, 0x1b, 0x45, 0x2f, 0x3f, 0x5f, 0x2a, +0x39, 0x56, 0x28, 0x7d, 0x9e, 0x9b, 0xd7, 0xd9, +0x96, 0xc2, 0xd5, 0xad, 0x9f, 0xe6, 0xba, 0xef, +0xd1, 0xcf, 0xff, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0x08, 0xab, 0xec, +0xdc, 0x22, 0xc5, 0xa1, 0x72, 0x9f, 0x83, 0xec, +0xa6, 0xe1, 0xb5, 0x19, 0xa5, 0xcb, 0x50, 0x9b, +0x9a, 0x28, 0x95, 0x5c, 0x20, 0xf9, 0xf5, 0xf7, +0x29, 0x2e, 0x8e, 0x3c, 0x36, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0x36, +0x7d, 0x9e, 0x1c, 0x72, 0x9a, 0x6e, 0xc8, 0x84, +0x94, 0x62, 0xb4, 0x85, 0x41, 0x7e, 0xf3, 0x25, +0x59, 0x8e, 0x0c, 0x8b, 0x3a, 0x9b, 0x85, 0xae, +0xd3, 0xef, 0xd9, 0x53, 0x29, 0x71, 0xfa, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0x90, 0xec, 0xc4, 0x22, 0x4e, 0x96, 0x42, +0x61, 0x40, 0x4d, 0xa5, 0xd9, 0xd9, 0x40, 0x96, +0xa8, 0x87, 0x46, 0x3d, 0x0c, 0xad, 0x15, 0x98, +0x71, 0x88, 0x11, 0x95, 0xeb, 0x16, 0x0d, 0x23, +0x45, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0x4e, 0x7b, 0x65, 0x54, 0xb0, +0x97, 0x3d, 0x88, 0x2a, 0xf3, 0x47, 0x43, 0x1d, +0xb3, 0xa8, 0x1f, 0x56, 0x25, 0x0d, 0x23, 0x45, +0x3b, 0x98, 0x1b, 0x44, 0x90, 0x1e, 0x25, 0x52, +0x90, 0xba, 0x7e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0xed, 0xce, 0x1c, +0x55, 0x34, 0xbe, 0x3a, 0xb4, 0xb8, 0xe6, 0x39, +0xb5, 0xd5, 0xf2, 0x10, 0x42, 0x29, 0xc5, 0xde, +0xb0, 0xc2, 0xb7, 0x27, 0xee, 0xe9, 0xd0, 0x07, +0x02, 0x76, 0xb9, 0xab, 0x84, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0x69, +0xdd, 0x21, 0x5b, 0xe6, 0x8a, 0x18, 0x19, 0x79, +0x02, 0xa6, 0x70, 0x5a, 0x22, 0x08, 0x5b, 0x10, +0xf4, 0xec, 0xd3, 0x92, 0x83, 0x0b, 0x0c, 0xde, +0x19, 0x0e, 0x5f, 0x62, 0xc1, 0x28, 0xc5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0xaa, 0x9d, 0x55, 0xcd, 0x02, 0x2a, 0x00, +0xea, 0xaa, 0x68, 0x70, 0xd0, 0x57, 0x90, 0x66, +0x1a, 0xbb, 0x4d, 0xf1, 0x35, 0x74, 0xe5, 0x9c, +0xaa, 0xab, 0xc1, 0xb8, 0xae, 0x45, 0xe1, 0xb0, +0x64, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0x69, 0x87, 0x65, 0x1e, 0xe6, +0x1a, 0x41, 0xe3, 0xb5, 0xc9, 0x57, 0xb7, 0xd0, +0x45, 0xb1, 0xda, 0x79, 0xb8, 0xfb, 0xe6, 0xc5, +0x98, 0x12, 0xc9, 0xcb, 0x14, 0x9f, 0xc8, 0x3e, +0x39, 0x97, 0x75, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0x0a, 0xf3, 0xa9, +0xf7, 0x61, 0xbf, 0xa3, 0xbd, 0xcb, 0x8d, 0xdc, +0x55, 0x53, 0x74, 0xbd, 0x08, 0x3d, 0x02, 0x51, +0x60, 0x17, 0xe1, 0x21, 0xa3, 0x77, 0x1c, 0x65, +0x33, 0xc7, 0xb0, 0x84, 0x6a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0x6d, +0x75, 0x90, 0xa9, 0x9e, 0x4e, 0x7e, 0xaa, 0x72, +0xb9, 0x31, 0x2f, 0x99, 0x85, 0x46, 0x5d, 0xa4, +0x9b, 0x1a, 0x11, 0x7d, 0x2b, 0x79, 0xbb, 0xca, +0x5c, 0xc8, 0x12, 0x23, 0x2e, 0x08, 0x61, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0xaa, 0x02, 0x8d, 0x88, 0xea, 0x0d, 0x42, +0xbe, 0xa1, 0x4e, 0xfe, 0x83, 0x65, 0x51, 0xf1, +0x32, 0x90, 0x4c, 0x98, 0x07, 0x26, 0x80, 0x01, +0x08, 0xbb, 0x43, 0x5e, 0xd0, 0x6e, 0x86, 0xc6, +0xef, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0xd3, 0x8e, 0xbf, 0x33, 0x8d, +0x28, 0x96, 0xaa, 0x50, 0x9c, 0x6b, 0x0c, 0x64, +0x9d, 0x96, 0x03, 0x9d, 0x5d, 0xf9, 0xbe, 0x27, +0x34, 0x9a, 0x89, 0xcd, 0x11, 0x99, 0xb8, 0x61, +0x52, 0xfd, 0x8c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0xda, 0xa4, 0x26, +0xfc, 0xa6, 0x51, 0x94, 0x3a, 0x15, 0xa7, 0xc2, +0xac, 0x8e, 0x64, 0x2d, 0x15, 0x0e, 0x13, 0x10, +0x57, 0xed, 0xc4, 0xf2, 0x97, 0x95, 0xa1, 0x33, +0xf8, 0x38, 0x39, 0x41, 0xe0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0xb5, +0x3c, 0x94, 0x10, 0x97, 0x32, 0xc3, 0xc9, 0xcc, +0xd1, 0xf3, 0x52, 0xb2, 0x63, 0x62, 0x94, 0x49, +0xa8, 0xbb, 0x56, 0x57, 0x1e, 0x94, 0xb8, 0x8a, +0x9b, 0x73, 0x1c, 0x21, 0x50, 0xcf, 0xb6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0xcb, 0x6a, 0x08, 0x05, 0xa8, 0xaf, 0x7b, +0x76, 0x55, 0x59, 0x59, 0x15, 0xc7, 0xfb, 0x0f, +0xd5, 0x20, 0xec, 0xd3, 0xc5, 0xbd, 0xa8, 0xdb, +0x96, 0x61, 0x59, 0x5d, 0x66, 0x74, 0xd6, 0xa9, +0x01, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0xc5, 0x5d, 0x83, 0x3a, 0xa9, +0x51, 0x8e, 0x4c, 0xd0, 0xd9, 0x21, 0x1e, 0x47, +0x42, 0x2f, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x5c, 0x55, 0xa4, 0xf9, +0x96, 0x3e, 0x3a, 0x99, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0x38, 0x3f, 0xfd, +0xf6, 0x8b, 0xe5, 0x28, 0x03, 0xb4, 0xc9, 0x33, +0xbc, 0x22, 0x61, 0xce, 0x11, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x77, +0xf4, 0xc9, 0x0e, 0xd0, 0xc8, 0xa0, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0x17, +0x93, 0x5a, 0xc1, 0x52, 0x1b, 0x88, 0x75, 0xef, +0xa0, 0xb9, 0x76, 0x78, 0xc8, 0xb8, 0x01, 0x80, +0x1b, 0xe4, 0x92, 0x4d, 0x46, 0xea, 0x36, 0xb5, +0x40, 0x1c, 0xd5, 0x56, 0xfe, 0x58, 0xed, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0x88, 0x5c, 0xb3, 0x72, 0x81, 0xb1, 0x79, +0x9b, 0x13, 0x07, 0xb3, 0x33, 0xde, 0x42, 0xf5, +0x62, 0xf3, 0xea, 0x1b, 0x14, 0xe1, 0x40, 0x6a, +0xc0, 0xbc, 0x05, 0x21, 0xa5, 0xdd, 0x8d, 0xdf, +0xb0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0xa8, 0x13, 0x21, 0x48, 0x3e, +0x52, 0x45, 0xdb, 0xf9, 0x34, 0xec, 0xba, 0xb0, +0x83, 0xa1, 0xe8, 0x33, 0x6c, 0xd0, 0x2d, 0x91, +0x34, 0x83, 0x61, 0x2f, 0x0d, 0x8c, 0xfa, 0xd6, +0x0d, 0x25, 0xab, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0x87, 0x43, 0x6e, +0x13, 0x1f, 0x4d, 0x38, 0x20, 0xff, 0xbf, 0x85, +0x9c, 0xa7, 0x9d, 0x2b, 0xed, 0xd2, 0x9f, 0x54, +0x71, 0x88, 0x1c, 0xf1, 0x79, 0xe8, 0xd9, 0xb6, +0x13, 0xaa, 0xb0, 0x10, 0xde, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0x59, +0xf4, 0xf1, 0x4d, 0xe1, 0xde, 0x9a, 0x93, 0xd5, +0xab, 0x43, 0xd8, 0x2f, 0xa0, 0x5f, 0xac, 0x4e, +0xb4, 0xa9, 0x81, 0xcf, 0xee, 0x91, 0xa9, 0x65, +0x25, 0x59, 0x4f, 0xfb, 0x0f, 0x39, 0x4e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0xa8, 0x97, 0x0e, 0xb9, 0xc9, 0x81, 0x93, +0x61, 0x51, 0xd2, 0x91, 0xf4, 0x89, 0xde, 0x14, +0x68, 0xe4, 0xd6, 0xe1, 0xcf, 0x87, 0xc6, 0x6f, +0x34, 0x98, 0x83, 0x6c, 0xfc, 0x7f, 0x69, 0x15, +0x70, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0x08, 0x54, 0x92, 0x1c, 0x2e, +0x8b, 0x38, 0x86, 0xb0, 0xcd, 0xf8, 0x1a, 0x2f, +0x1a, 0xe1, 0x6a, 0x17, 0x31, 0x18, 0xf0, 0xaf, +0x69, 0xba, 0xbe, 0xf0, 0xcd, 0x14, 0x38, 0xa3, +0xa9, 0x92, 0x68, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0xde, 0xb7, 0x4b, +0xbb, 0x6c, 0xae, 0x40, 0xdd, 0xd3, 0x3b, 0x03, +0x96, 0x74, 0xff, 0x56, 0x5a, 0xec, 0x7d, 0x92, +0x2a, 0x16, 0xcf, 0x02, 0xc6, 0x12, 0xfd, 0xf3, +0xdf, 0xc0, 0x41, 0xc9, 0xe2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0x70, +0x32, 0x65, 0xbc, 0x59, 0x92, 0xee, 0x97, 0x06, +0x4e, 0xa6, 0xd1, 0x7f, 0xb4, 0x1c, 0x3e, 0xa7, +0xe5, 0x17, 0xf9, 0x0d, 0xf5, 0xa1, 0xa4, 0x22, +0x1f, 0x67, 0x36, 0xc2, 0x49, 0x50, 0x2f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0xde, 0x3f, 0xce, 0x63, 0xcd, 0x2e, 0xe2, +0xd4, 0xc3, 0x98, 0x9c, 0x84, 0x0a, 0x40, 0xca, +0xd9, 0xc3, 0x96, 0xa4, 0x69, 0x88, 0xb7, 0x1e, +0x1b, 0x69, 0xf7, 0x51, 0xf0, 0x2f, 0xfc, 0x52, +0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0x91, 0x0e, 0xee, 0x94, 0x39, +0xf1, 0x9a, 0xd0, 0x77, 0x7d, 0xf4, 0xe2, 0x7c, +0xde, 0x68, 0xa8, 0x3b, 0x70, 0xff, 0x56, 0x3a, +0x1d, 0x5c, 0x0f, 0x9e, 0x9e, 0xc5, 0x73, 0x67, +0xd3, 0x3a, 0x69, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0xa6, 0x9e, 0x3d, +0x10, 0x5d, 0xac, 0x5c, 0x35, 0xa4, 0xdc, 0xaa, +0x17, 0x5e, 0xdf, 0xa2, 0xff, 0xd6, 0xb1, 0x56, +0xba, 0x2d, 0x3f, 0x9b, 0x8b, 0x03, 0xe1, 0x16, +0xb0, 0x80, 0x15, 0xb3, 0x52, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0xb3, +0x6b, 0xa5, 0x86, 0xc8, 0x6e, 0x28, 0x0b, 0x2f, +0xf9, 0xe2, 0x5e, 0xff, 0x96, 0x97, 0x32, 0xca, +0xcc, 0x72, 0xb8, 0x07, 0xfd, 0x0f, 0x25, 0x2b, +0xe3, 0xda, 0x0b, 0xc3, 0x60, 0x08, 0x79, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0xa9, 0xe2, 0x1c, 0x9b, 0xd7, 0x79, 0xe1, +0x01, 0xb2, 0xa3, 0x6a, 0xf7, 0x59, 0xa0, 0x7f, +0x9d, 0x14, 0xc3, 0x4c, 0x5c, 0xf7, 0xf7, 0x0d, +0x3e, 0xad, 0xa5, 0x49, 0x05, 0x91, 0xfa, 0xd0, +0xc9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0xb9, 0x8a, 0x91, 0x1b, 0x23, +0xa7, 0x55, 0x3a, 0x2e, 0x2f, 0xb0, 0x9a, 0x6d, +0x40, 0xf5, 0xa6, 0x7b, 0x98, 0x4c, 0xaa, 0x7a, +0xba, 0x06, 0x28, 0x83, 0xa5, 0x04, 0x55, 0x28, +0x67, 0x7e, 0x32, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0x5e, 0x58, 0x53, +0xb2, 0x87, 0x1d, 0x57, 0xfe, 0x0f, 0x96, 0x1d, +0xf7, 0x66, 0x92, 0x56, 0xe5, 0x46, 0x89, 0xf9, +0xf3, 0xad, 0x1b, 0x19, 0xcd, 0x7d, 0x1b, 0x6b, +0x0b, 0xb9, 0x79, 0x28, 0x57, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x38, +0xc9, 0xc5, 0x66, 0x5a, 0x7d, 0xed, 0xfa, 0x1c, +0x0d, 0xe0, 0x5a, 0x21, 0xd1, 0x95, 0xb7, 0xe7, +0xfc, 0x85, 0xcf, 0x2f, 0x18, 0x16, 0x9d, 0x31, +0x66, 0xb5, 0x73, 0x47, 0x78, 0x3c, 0xbb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0xed, 0x5b, 0xaa, 0xd2, 0x81, 0x21, 0xe6, +0x8c, 0x6c, 0xc1, 0x93, 0x46, 0x65, 0xe8, 0x75, +0xf2, 0x92, 0xc2, 0x73, 0xce, 0xfc, 0x44, 0xdc, +0x4f, 0xda, 0xca, 0xeb, 0xc7, 0xda, 0xd2, 0x59, +0x2f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0xee, 0xa2, 0x52, 0x8b, 0x6b, +0x37, 0xa8, 0x05, 0x9b, 0x25, 0x02, 0x5b, 0x2c, +0x76, 0x52, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xdb, 0x96, 0x6e, 0x2c, +0x90, 0xb1, 0x9a, 0x8c, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0x41, 0x8d, 0x97, +0x4c, 0xa1, 0x1e, 0x2c, 0xe4, 0xfe, 0x80, 0xd4, +0x85, 0x0e, 0x70, 0x47, 0x1c, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x0b, +0x89, 0x91, 0xdc, 0xc0, 0x62, 0x4e, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x23, +0x81, 0x42, 0xcf, 0xce, 0xa8, 0x28, 0x97, 0x86, +0x85, 0xc0, 0x83, 0xf1, 0xc0, 0xcc, 0xd0, 0x60, +0x73, 0x96, 0x53, 0x0e, 0x52, 0xae, 0x0e, 0x61, +0x0b, 0xdc, 0xa4, 0xff, 0x3a, 0x3a, 0xe0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0xa7, 0x22, 0x53, 0x5d, 0x98, 0x36, 0x31, +0x6c, 0x55, 0xcb, 0xba, 0xba, 0x6c, 0x7d, 0x33, +0x42, 0xde, 0x54, 0x0f, 0x41, 0xf9, 0xba, 0x19, +0x0e, 0x16, 0x62, 0x5f, 0xf2, 0x69, 0xda, 0xe2, +0x8b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0x52, 0x7e, 0xf8, 0xcd, 0xd2, +0xd5, 0x78, 0x66, 0x16, 0x4e, 0xe0, 0xc1, 0x5d, +0x06, 0xd0, 0xf1, 0xc5, 0x92, 0x79, 0x20, 0x06, +0x2f, 0x92, 0xaf, 0x77, 0x58, 0xa1, 0x29, 0x10, +0x89, 0x6e, 0x71, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0x76, 0x42, 0xae, +0x85, 0x20, 0x78, 0x7e, 0xdb, 0x05, 0xbb, 0xbd, +0x90, 0xde, 0x37, 0x16, 0x90, 0x04, 0xc1, 0x7b, +0xf4, 0xad, 0x22, 0xe0, 0x94, 0x00, 0x5c, 0xd2, +0x9e, 0xa4, 0xa2, 0x1b, 0x99, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x54, +0xb7, 0x6a, 0x9c, 0x33, 0xbf, 0x3c, 0xfe, 0xb9, +0xb6, 0x4c, 0x17, 0x5e, 0x4e, 0x8b, 0x6e, 0x62, +0xf7, 0xc0, 0xc7, 0x96, 0xbc, 0x26, 0x63, 0xa7, +0xa8, 0x00, 0x2c, 0x64, 0x7e, 0x6f, 0x0c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0x28, 0x3d, 0x8d, 0xe7, 0xf2, 0x88, 0xce, +0xfa, 0x85, 0x62, 0x08, 0x39, 0x14, 0x24, 0x51, +0x13, 0xdf, 0xee, 0xce, 0x26, 0xb5, 0x12, 0x5b, +0x4d, 0x03, 0xde, 0x45, 0x60, 0x89, 0x12, 0xd3, +0x29, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0x03, 0xcd, 0x10, 0x9a, 0x85, +0x9c, 0x8e, 0x0a, 0x09, 0xa3, 0xa0, 0x82, 0x7e, +0x97, 0xc9, 0x04, 0xc2, 0x6d, 0x59, 0x5e, 0x24, +0xd0, 0x7c, 0x20, 0x7b, 0x50, 0xb4, 0x76, 0xbd, +0xac, 0x07, 0x62, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x0b, 0xc3, 0xb6, +0xfd, 0x0b, 0xd2, 0xec, 0xbe, 0x90, 0xc1, 0xaa, +0x14, 0x8f, 0x45, 0x0e, 0x42, 0x39, 0xd4, 0xcf, +0x88, 0xb5, 0x08, 0x94, 0x37, 0x35, 0x86, 0x28, +0x64, 0x3e, 0x9a, 0x1e, 0x0a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0x5e, +0xab, 0x8a, 0x06, 0x6a, 0xbc, 0x4c, 0x02, 0xea, +0x78, 0xda, 0x8f, 0xfd, 0x5a, 0xfb, 0xfd, 0x8f, +0x44, 0x16, 0x7d, 0x71, 0xe1, 0x4d, 0xc4, 0x8e, +0x4f, 0xa7, 0xfe, 0x4f, 0x79, 0xf0, 0xd8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0xfc, 0x0a, 0xd5, 0x50, 0xf7, 0x65, 0x6f, +0x7f, 0x36, 0xd6, 0x41, 0xed, 0x8b, 0xc3, 0x41, +0x79, 0x91, 0xdc, 0xdd, 0x05, 0xa1, 0x42, 0x68, +0x85, 0x74, 0x0a, 0x80, 0x24, 0x11, 0x37, 0xd4, +0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0x54, 0x95, 0x92, 0x19, 0xba, +0x7c, 0x64, 0x18, 0x96, 0xd5, 0x55, 0x84, 0x4f, +0x31, 0xfc, 0xd1, 0x05, 0x9b, 0x96, 0xf3, 0x28, +0xe3, 0x06, 0x56, 0xd2, 0x0b, 0xf3, 0x05, 0x7c, +0xf3, 0xe0, 0x5e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0xe0, 0x12, 0x54, +0xad, 0x2d, 0xb6, 0x6d, 0xc5, 0x04, 0x24, 0x4c, +0xcd, 0x6c, 0xc1, 0x64, 0xe5, 0x35, 0x7f, 0xef, +0xc9, 0x52, 0x00, 0x4d, 0xb7, 0xfa, 0x2e, 0x08, +0x99, 0x37, 0x49, 0xb8, 0x47, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0x16, +0xf1, 0xa4, 0x77, 0xf3, 0xde, 0xde, 0xf3, 0x8e, +0x94, 0xfb, 0x10, 0x14, 0xde, 0xf0, 0x7e, 0x52, +0xa2, 0xb4, 0xfa, 0x5d, 0x04, 0x92, 0x41, 0x47, +0xaa, 0xcd, 0x9f, 0x1b, 0x8d, 0xb4, 0x85, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x0f, 0x2e, 0x0d, 0x29, 0x4c, 0x14, 0x63, +0x45, 0xc4, 0x7b, 0xca, 0xbf, 0x69, 0xed, 0x1e, +0xf1, 0xc9, 0xce, 0xb8, 0xb1, 0xeb, 0x98, 0xd9, +0x18, 0x05, 0xb4, 0xb4, 0xb1, 0x29, 0xac, 0xa7, +0xaa, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x1f, 0xa1, 0x99, 0x40, 0x4f, +0xbb, 0x42, 0x43, 0x6e, 0x11, 0xe1, 0xfa, 0x5b, +0xa2, 0x0e, 0x2a, 0x64, 0x1d, 0x0b, 0xd8, 0xcf, +0x7c, 0x68, 0x8c, 0x0c, 0x19, 0x97, 0xcf, 0x6a, +0xc8, 0xae, 0x41, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0xd9, 0xd3, 0x86, +0xa2, 0x88, 0xf9, 0x89, 0xe2, 0xdc, 0x6b, 0xc8, +0x66, 0x61, 0xec, 0x14, 0xa2, 0x75, 0xec, 0x5c, +0x14, 0xbb, 0x7e, 0x05, 0xaa, 0xa6, 0x92, 0x74, +0x86, 0xbb, 0x1e, 0x5a, 0x5a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0x01, +0x85, 0x14, 0xe0, 0xf1, 0x7f, 0x3c, 0xa7, 0x49, +0x4d, 0xd7, 0x4a, 0x2f, 0xc8, 0xbf, 0x15, 0xdb, +0xea, 0x29, 0x7c, 0x32, 0x1d, 0xf0, 0x97, 0x33, +0xf2, 0xe4, 0x57, 0xba, 0x49, 0x92, 0xc5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0xa9, 0x57, 0x4d, 0x64, 0x4c, 0x3e, 0xf5, +0xfb, 0xaa, 0xfd, 0x25, 0x11, 0x45, 0x44, 0x7c, +0x20, 0xdb, 0x1a, 0xbc, 0x24, 0x4e, 0xaf, 0x54, +0x32, 0x14, 0x31, 0x1c, 0x26, 0x6d, 0xdd, 0x0b, +0x9d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0xe3, 0xb3, 0xe8, 0x04, 0x08, +0xf9, 0xaa, 0xcc, 0x7f, 0xc5, 0xb0, 0x89, 0xf2, +0x29, 0x40, 0x5d, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x8e, 0x29, 0xfd, 0xe7, +0x54, 0xe2, 0xba, 0x70, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0xa5, 0xb8, 0x00, +0xc6, 0xf9, 0x53, 0xcf, 0xa9, 0x22, 0x0b, 0xb0, +0x9b, 0x70, 0x8a, 0x7c, 0xee, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x19, +0x14, 0x58, 0xd2, 0x1e, 0x56, 0xb6, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x5e, +0x1a, 0xe1, 0xbb, 0x0f, 0x7d, 0x95, 0x82, 0x26, +0x80, 0x2a, 0xa7, 0x10, 0xf6, 0x9f, 0x09, 0x81, +0x24, 0xc9, 0xf7, 0xdd, 0xd9, 0x61, 0x20, 0xce, +0xbe, 0x2c, 0x16, 0xd7, 0xc1, 0x4f, 0xa6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0xcd, 0x5a, 0xfa, 0x59, 0x83, 0x46, 0x4c, +0x51, 0x69, 0x2e, 0x48, 0xb4, 0xac, 0xd3, 0x09, +0x99, 0xd7, 0x3a, 0x3c, 0x71, 0x01, 0x0a, 0x02, +0x5c, 0x44, 0x04, 0xa8, 0x19, 0x99, 0x3b, 0xf9, +0x55, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0x38, 0xc8, 0x6a, 0xc6, 0x44, +0xb1, 0xea, 0x0a, 0xd3, 0xfc, 0x43, 0xc0, 0xb2, +0xe2, 0xe5, 0x3d, 0xb5, 0xb0, 0x54, 0x12, 0x82, +0x0e, 0xb0, 0x32, 0x41, 0x82, 0xb8, 0x65, 0xf3, +0x54, 0x5b, 0x46, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0x85, 0xa2, 0x41, +0x5c, 0x6b, 0xa9, 0x7e, 0x7b, 0x9a, 0xda, 0xb8, +0x7e, 0x8a, 0x4c, 0xea, 0x96, 0xe9, 0xb5, 0x5e, +0x4e, 0xb7, 0xe8, 0x64, 0x85, 0xbe, 0x0f, 0xe7, +0x6d, 0x48, 0x59, 0xa3, 0x9d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0xb8, +0x4f, 0x3d, 0x90, 0xb0, 0xe8, 0xb5, 0xb9, 0x1d, +0xf7, 0x74, 0x23, 0x64, 0x3c, 0x43, 0x8f, 0x02, +0xc6, 0xf8, 0xcd, 0x74, 0xc6, 0x74, 0x11, 0x8f, +0x60, 0x06, 0x23, 0x24, 0x8f, 0x6f, 0xcb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0xc1, 0xe0, 0x0c, 0x45, 0x2d, 0x09, 0x6b, +0xf4, 0x71, 0xd6, 0xec, 0xe4, 0xd8, 0x9d, 0xad, +0x21, 0x6f, 0xac, 0x56, 0xf6, 0xd8, 0xb1, 0x1c, +0x3c, 0x7f, 0x55, 0xd4, 0x56, 0x3c, 0x94, 0xce, +0xb4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0xac, 0x25, 0xdc, 0xcc, 0x83, +0x4a, 0x8a, 0xdc, 0x54, 0xd2, 0x16, 0xd7, 0x88, +0x0a, 0x51, 0x68, 0xb3, 0xf1, 0x40, 0x26, 0xc9, +0xab, 0xcf, 0x26, 0xc2, 0x95, 0x3f, 0x3d, 0x7d, +0xa3, 0x35, 0xdd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0x51, 0x9d, 0x06, +0xee, 0x98, 0x8b, 0xdf, 0xf4, 0xb3, 0x88, 0xc6, +0x79, 0x99, 0xd6, 0x13, 0x7a, 0x58, 0xbe, 0x5a, +0xb9, 0x42, 0x22, 0x2b, 0x0d, 0xf7, 0x73, 0x11, +0xb0, 0x7d, 0xa4, 0xc2, 0x6b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0x1b, +0x52, 0x35, 0x6b, 0xbc, 0xe1, 0x5a, 0xe1, 0x0f, +0x78, 0xa8, 0x06, 0xa6, 0x94, 0xa7, 0x24, 0x89, +0x81, 0x71, 0x21, 0x6e, 0x2d, 0x41, 0xc3, 0x3e, +0x05, 0xe1, 0x95, 0x96, 0x7b, 0xf9, 0x9c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0xe2, 0x01, 0x07, 0x36, 0x69, 0x86, 0xb6, +0x0b, 0x9b, 0x40, 0xe4, 0x67, 0xbe, 0x9a, 0xcf, +0xc3, 0xf5, 0xba, 0xc5, 0xa1, 0x58, 0x9c, 0x1a, +0x36, 0x73, 0x23, 0x2d, 0x1f, 0xff, 0xec, 0x28, +0xf4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0x72, 0x7d, 0x81, 0xde, 0x75, +0x2b, 0xfe, 0xed, 0xf1, 0xfa, 0xfb, 0xff, 0x64, +0x62, 0xa7, 0xf1, 0x96, 0x17, 0x10, 0xc2, 0x9b, +0x63, 0x48, 0xe3, 0x48, 0x31, 0x7e, 0xe7, 0x87, +0x3a, 0x99, 0xfa, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0xd6, 0x30, 0x81, +0x45, 0xb1, 0xe9, 0x69, 0xa1, 0xa6, 0x59, 0x80, +0x5e, 0x00, 0x2a, 0x17, 0x6b, 0xee, 0x27, 0xa0, +0xef, 0x14, 0x7c, 0xab, 0xb4, 0x93, 0xed, 0xdf, +0x04, 0xdd, 0xa2, 0x7a, 0x5c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0xe4, +0x72, 0x8e, 0x8d, 0x67, 0x0c, 0x66, 0xf4, 0x4c, +0xe2, 0x74, 0x3c, 0xd9, 0x71, 0x8b, 0x56, 0x85, +0x0c, 0xe9, 0xcc, 0x81, 0x13, 0x97, 0x53, 0xc6, +0xd8, 0x25, 0xd9, 0x9d, 0x67, 0x04, 0x97, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0xd1, 0x28, 0x94, 0xb0, 0x49, 0xc7, 0x18, +0x13, 0x85, 0x13, 0x22, 0xe7, 0xa6, 0xd9, 0x13, +0x4c, 0xc8, 0xed, 0x46, 0xf3, 0xfa, 0x4e, 0xda, +0xdc, 0x56, 0x53, 0x5a, 0x0b, 0x21, 0xd2, 0x19, +0x49, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0xd6, 0x1a, 0xc3, 0x1c, 0x92, +0x01, 0x31, 0x20, 0xce, 0x4c, 0xfa, 0xe8, 0xc1, +0x35, 0x65, 0x6f, 0xb3, 0x3b, 0xa3, 0x96, 0x9d, +0x8b, 0x98, 0x67, 0x58, 0x73, 0x54, 0x91, 0x41, +0x1e, 0x87, 0xf7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0x72, 0x59, 0x0d, +0xdf, 0x19, 0x2b, 0xe0, 0x96, 0x35, 0x95, 0x17, +0x9d, 0x41, 0x0b, 0x2b, 0x46, 0xb9, 0xbb, 0xcd, +0x31, 0x59, 0x15, 0x01, 0x4f, 0xa8, 0xe8, 0x50, +0xdb, 0x61, 0x93, 0x2b, 0x44, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0xc5, +0xdd, 0xa0, 0x72, 0xda, 0xda, 0x0d, 0x2d, 0x7d, +0x76, 0x78, 0x92, 0x7e, 0x4b, 0x4f, 0xda, 0x7e, +0x99, 0xcb, 0x27, 0xfa, 0x18, 0xa8, 0xe1, 0xff, +0xb8, 0x6b, 0x08, 0x61, 0x75, 0x65, 0x0b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0x6e, 0xde, 0x18, 0xc6, 0xd7, 0xcd, 0x55, +0x20, 0x1a, 0x6f, 0xa5, 0x56, 0xa7, 0x85, 0x86, +0x33, 0x45, 0x10, 0xd8, 0x29, 0x7b, 0xc2, 0x46, +0xf3, 0x11, 0x17, 0xd1, 0x92, 0xf6, 0x22, 0xb2, +0xd9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0xe8, 0xd4, 0x34, 0xf4, 0x34, +0xc3, 0xf0, 0xc7, 0x90, 0x54, 0xbb, 0x0d, 0x0b, +0xf8, 0x0a, 0x97, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x63, 0x0d, 0x68, 0xc0, +0x4e, 0x71, 0x03, 0xdb, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0x49, 0x19, 0x64, +0x04, 0x66, 0x93, 0xcf, 0x49, 0x96, 0x31, 0x8d, +0xa9, 0xcf, 0xe5, 0xb5, 0x27, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x40, +0x14, 0xd2, 0xb6, 0x4c, 0xf2, 0x12, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0x33, +0x46, 0x07, 0x9d, 0x78, 0xb5, 0x5e, 0x5b, 0x94, +0x30, 0x43, 0xe5, 0x1b, 0x9d, 0x8d, 0xef, 0x2a, +0xb1, 0xd7, 0x00, 0xc0, 0xcd, 0x6e, 0x25, 0xad, +0xf5, 0xc0, 0xbb, 0x8e, 0x9e, 0x75, 0xb5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x92, 0x5e, 0x51, 0x6c, 0xbe, 0x9b, 0xea, +0x8b, 0x1c, 0xf1, 0xf4, 0x4b, 0x97, 0x5c, 0x31, +0xfc, 0x30, 0x8c, 0x43, 0xa9, 0x6e, 0x39, 0x9b, +0xc4, 0x76, 0x4a, 0x11, 0x0d, 0x79, 0x79, 0x70, +0x15, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0xf4, 0x59, 0x3d, 0x16, 0x5f, +0xf3, 0xe8, 0x37, 0xb4, 0xae, 0xae, 0xce, 0x42, +0xd2, 0x28, 0x86, 0x66, 0x35, 0xff, 0xdf, 0xa3, +0x9d, 0x6d, 0xda, 0x2e, 0x66, 0xef, 0xbd, 0x51, +0xd7, 0xe0, 0x08, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0x42, 0x86, 0x40, +0xb8, 0x11, 0x96, 0x3a, 0x2f, 0x11, 0x35, 0xf5, +0xd2, 0x7a, 0xe8, 0x0a, 0xdf, 0x23, 0x32, 0x25, +0x43, 0xee, 0x92, 0xce, 0x49, 0x83, 0x02, 0x3c, +0x7d, 0xd1, 0xfd, 0x86, 0xf8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0x4f, +0x45, 0x21, 0x48, 0xe2, 0x84, 0x0c, 0x59, 0xde, +0xbe, 0xc3, 0x73, 0x5e, 0xae, 0xb0, 0xcb, 0x9c, +0xe0, 0x4e, 0xc3, 0x49, 0x70, 0x78, 0x2d, 0xac, +0x1e, 0x1b, 0x7e, 0x6f, 0xca, 0xac, 0x1b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0xbf, 0x7b, 0x77, 0xf6, 0x64, 0x8c, 0xdb, +0xe8, 0xb1, 0x6b, 0x61, 0xe0, 0x32, 0x20, 0xd7, +0x13, 0xaf, 0x19, 0x70, 0xaf, 0xef, 0x98, 0x2e, +0x77, 0xcc, 0x78, 0x9e, 0xfb, 0x70, 0x76, 0x10, +0x88, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0x0a, 0x3e, 0x4a, 0xb2, 0x53, +0xd4, 0x26, 0x14, 0x48, 0x01, 0x00, 0x38, 0xe4, +0x8b, 0x17, 0xed, 0x38, 0x49, 0xa6, 0xd9, 0xac, +0x65, 0xb3, 0x82, 0x18, 0x5c, 0x38, 0x45, 0x58, +0xf3, 0x48, 0x42, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x35, 0xb4, 0xa3, +0x17, 0x04, 0x19, 0x11, 0x01, 0xd7, 0x74, 0x54, +0x95, 0x67, 0xcb, 0x5a, 0x46, 0xfd, 0x15, 0x65, +0xc7, 0xa9, 0x65, 0xa2, 0x70, 0x3f, 0x66, 0xfd, +0xc2, 0xb0, 0x44, 0x1f, 0xa2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x13, +0xcc, 0xe8, 0xe0, 0x18, 0x47, 0xf2, 0x6b, 0x20, +0x09, 0x8f, 0xfe, 0x14, 0x15, 0xb7, 0xaf, 0x6a, +0x90, 0x7a, 0x33, 0x8a, 0xd6, 0x77, 0xc4, 0x12, +0xec, 0xbc, 0xde, 0xc1, 0x67, 0xb4, 0xfe, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0x6c, 0x81, 0x7c, 0xc9, 0x8d, 0x7d, 0xc8, +0x0a, 0x47, 0x57, 0xd2, 0xaf, 0x69, 0xe3, 0x3b, +0x9d, 0x63, 0x74, 0x4e, 0x21, 0x0f, 0xde, 0x5a, +0xc7, 0x03, 0xd6, 0xc2, 0x7f, 0xc6, 0xba, 0x9f, +0x0e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0xd3, 0x22, 0x29, 0x07, 0x37, +0xeb, 0x2d, 0x72, 0x5c, 0x30, 0xc1, 0x8a, 0x6f, +0x27, 0xb3, 0xdb, 0xe5, 0xf3, 0xb2, 0x7d, 0x82, +0x60, 0x5c, 0x2b, 0xb9, 0x3f, 0xa6, 0xfe, 0x59, +0x25, 0xfd, 0x0e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0x95, 0x4d, 0xd1, +0xa9, 0x5b, 0x1e, 0x15, 0x4d, 0xed, 0xbd, 0xf1, +0x02, 0x3d, 0xe9, 0xe0, 0xf9, 0xf5, 0x0a, 0xf6, +0x9b, 0x1e, 0xb4, 0x0c, 0xa2, 0xe1, 0x0b, 0xa4, +0x29, 0x1e, 0x95, 0x46, 0x31, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0x04, +0xe9, 0x6b, 0x03, 0x54, 0xd4, 0xa9, 0x7b, 0x71, +0xa1, 0x49, 0xe6, 0x78, 0x67, 0x12, 0x17, 0x4d, +0xa2, 0x0e, 0x3f, 0x58, 0x2d, 0xa7, 0x36, 0x08, +0x53, 0xe7, 0x53, 0x2b, 0xa7, 0x56, 0x34, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0x7d, 0x93, 0x12, 0xa5, 0x0f, 0x02, 0x70, +0x54, 0x90, 0xe6, 0xe0, 0x50, 0x86, 0xf4, 0x59, +0x4a, 0xa6, 0x75, 0x26, 0x69, 0x5b, 0x61, 0x6d, +0x17, 0x43, 0x0f, 0xde, 0xf4, 0x43, 0x6e, 0x49, +0x6e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0x30, 0xa8, 0x33, 0x53, 0x1e, +0x00, 0xd8, 0x0f, 0xd6, 0x48, 0x72, 0x55, 0x16, +0x5d, 0x26, 0xc2, 0xe7, 0x7c, 0xba, 0x28, 0x0e, +0xe1, 0x03, 0xb1, 0x19, 0xd6, 0x6f, 0x58, 0x1d, +0xb2, 0x2a, 0x86, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0x02, 0x75, 0x26, +0xc8, 0xa5, 0xd4, 0x01, 0x08, 0xc7, 0x97, 0xd8, +0xde, 0x58, 0x29, 0xee, 0xac, 0x4a, 0x18, 0xe9, +0x0d, 0xfc, 0xa1, 0xe2, 0xc3, 0xe7, 0x1f, 0x62, +0xa9, 0x09, 0x35, 0x14, 0x22, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0x2d, +0xdf, 0x76, 0xc1, 0x8d, 0x96, 0x79, 0x40, 0x3f, +0x56, 0xc4, 0x5b, 0x31, 0x32, 0x08, 0x30, 0xc9, +0xb1, 0xcc, 0x1c, 0x2a, 0xb4, 0xaa, 0xd0, 0x4e, +0xae, 0xa1, 0xf2, 0x1a, 0x79, 0xbd, 0x17, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0x28, 0x1f, 0xae, 0x4f, 0x8b, 0x1a, 0xaa, +0x24, 0x92, 0xa0, 0x99, 0xd7, 0xce, 0x17, 0x07, +0x53, 0x4e, 0x18, 0x54, 0x3e, 0xc6, 0x0c, 0xb1, +0x7a, 0x58, 0x1f, 0xd6, 0xf5, 0x07, 0x3d, 0x7b, +0xf9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0xdd, 0xff, 0x94, 0xd3, 0x61, +0x96, 0x69, 0x4f, 0x80, 0xa5, 0x20, 0xd3, 0x39, +0x49, 0xe3, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x7e, 0x88, 0x0d, 0xda, +0xa2, 0x65, 0xbe, 0x87, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x3c, 0xa6, 0x1c, +0xd5, 0xfa, 0xad, 0x0f, 0x69, 0x74, 0x2e, 0x99, +0x01, 0x86, 0xe1, 0x91, 0x75, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86, 0xdb, +0x9c, 0x07, 0xe1, 0xad, 0x89, 0xa4, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0x9c, +0x0f, 0xf7, 0xe4, 0xab, 0x52, 0x1c, 0xbd, 0x34, +0xe6, 0x12, 0xc4, 0x08, 0x48, 0x65, 0x3f, 0xf9, +0x0e, 0x17, 0x2c, 0x48, 0xde, 0x04, 0xfa, 0x78, +0x38, 0x74, 0xf1, 0x68, 0x5f, 0xa8, 0x1b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0x0c, 0xeb, 0x33, 0xa2, 0x90, 0x25, 0x8b, +0xc1, 0xf6, 0xb3, 0x72, 0xd9, 0xe1, 0x66, 0x66, +0xaa, 0xe3, 0xc7, 0x66, 0xd3, 0x87, 0x57, 0xec, +0xcd, 0x2e, 0xfe, 0xb8, 0x91, 0x6f, 0x89, 0xe0, +0x52, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0x63, 0x40, 0xc4, 0x9c, 0xc2, +0x2d, 0xe6, 0x51, 0x7e, 0x30, 0x80, 0xc9, 0x8b, +0xfb, 0xec, 0x81, 0xfb, 0x86, 0x2d, 0x17, 0x64, +0x97, 0xc2, 0x8d, 0x8f, 0x0f, 0x27, 0x6a, 0x2e, +0x57, 0xf4, 0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x1c, 0x52, 0x83, +0xbc, 0x0f, 0xf5, 0xa3, 0x72, 0x23, 0x3d, 0x03, +0xd2, 0x85, 0x15, 0x61, 0xc1, 0xe8, 0x02, 0x59, +0x31, 0x66, 0x23, 0x75, 0x67, 0x0a, 0xc9, 0x39, +0xc1, 0x11, 0x62, 0x5e, 0x8d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x7a, +0x30, 0x29, 0x9a, 0xfc, 0x06, 0x42, 0x41, 0x75, +0xb8, 0x33, 0x7c, 0xff, 0xbc, 0x8b, 0x86, 0xcf, +0x97, 0x71, 0x71, 0x47, 0xe9, 0x43, 0xfd, 0x48, +0x4d, 0xa7, 0xd7, 0x83, 0xe4, 0x00, 0xfd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0xd7, 0x45, 0x0b, 0x61, 0xf8, 0xdc, 0x39, +0x57, 0x70, 0x66, 0x50, 0x15, 0x1b, 0xa9, 0x39, +0x37, 0xba, 0x52, 0xe6, 0xca, 0x4e, 0x2e, 0x57, +0x8d, 0x8a, 0x1f, 0x58, 0x5f, 0xef, 0x63, 0x75, +0x48, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0x20, 0x49, 0xb5, 0xed, 0x2b, +0x0a, 0x2e, 0x8a, 0x02, 0x5d, 0xd1, 0x67, 0x2e, +0x05, 0x66, 0x6a, 0xd4, 0x2b, 0x10, 0xba, 0x7d, +0x20, 0xaa, 0x57, 0x2f, 0x48, 0x30, 0xe5, 0xc0, +0x8f, 0x2d, 0x41, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0x38, 0x0f, 0x99, +0x86, 0x86, 0x20, 0x46, 0xa6, 0x0c, 0x4c, 0x1d, +0x59, 0xa6, 0xfc, 0x47, 0xa0, 0xe1, 0x75, 0x16, +0x39, 0x94, 0x4e, 0x37, 0xad, 0x62, 0xaa, 0xc1, +0x3e, 0x46, 0xa4, 0xac, 0x58, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x22, +0x06, 0xec, 0xe1, 0xde, 0x78, 0xc4, 0x41, 0x9b, +0xb3, 0x1f, 0xc9, 0x17, 0x41, 0xf8, 0x25, 0xb4, +0xfb, 0xdb, 0x40, 0xc3, 0x1f, 0xa7, 0xda, 0x31, +0x00, 0xe7, 0xfe, 0x6e, 0x62, 0xaa, 0x95, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0x69, 0xa3, 0xc6, 0xb9, 0xe3, 0x29, 0xca, +0x20, 0x16, 0xca, 0x6d, 0xcf, 0x1d, 0x05, 0x66, +0xcf, 0x9f, 0x68, 0x36, 0x94, 0x5a, 0xaf, 0x1b, +0xf4, 0x3a, 0xe0, 0x77, 0xb1, 0x42, 0x31, 0x61, +0x17, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0xdc, 0xee, 0x1a, 0x64, 0x22, +0x22, 0xda, 0xe7, 0x4f, 0x90, 0x74, 0x44, 0x7d, +0xcf, 0x37, 0xb2, 0x12, 0xbb, 0x43, 0xa4, 0x68, +0x37, 0xd4, 0x3b, 0xd7, 0x87, 0x70, 0x7d, 0x2e, +0xa5, 0x71, 0xfd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0xe4, 0x69, 0xb4, +0x48, 0xab, 0x86, 0x15, 0xe2, 0xc8, 0x77, 0x77, +0xbb, 0x78, 0x9b, 0xce, 0xf2, 0x47, 0x66, 0x7a, +0x88, 0x41, 0xe6, 0x6e, 0x4a, 0xcb, 0x55, 0x98, +0x84, 0x0c, 0xf9, 0x62, 0xc8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x78, +0x15, 0x66, 0x71, 0xb9, 0x92, 0xaf, 0x7f, 0x42, +0x09, 0xdb, 0xb5, 0x25, 0xee, 0x6e, 0xdf, 0xce, +0xd3, 0x06, 0x3d, 0x45, 0xf5, 0xa4, 0x04, 0xb2, +0x02, 0xa1, 0x7b, 0xfe, 0x06, 0x5d, 0x2b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0x4e, 0xdc, 0x4d, 0x77, 0x3d, 0x66, 0xb5, +0xf8, 0x93, 0xd7, 0x79, 0xb3, 0x7b, 0x67, 0x33, +0xcc, 0xad, 0xcf, 0xf8, 0xf7, 0x2d, 0xae, 0x58, +0x92, 0x45, 0xb4, 0x9a, 0x2e, 0xf7, 0xc6, 0x9c, +0x5d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0xc9, 0xb6, 0x59, 0x42, 0xaf, +0xeb, 0xd2, 0x2a, 0x15, 0xa8, 0x80, 0x16, 0xd8, +0xcc, 0x77, 0xce, 0xa1, 0x03, 0x02, 0xbb, 0xd8, +0x5d, 0x0d, 0x1e, 0xfa, 0xa6, 0x3f, 0x94, 0xe7, +0x48, 0x5c, 0x25, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0xa1, 0xf6, 0x8e, +0x2c, 0xb3, 0xf1, 0x78, 0xfd, 0x72, 0xbb, 0x91, +0x8c, 0x4f, 0x0f, 0x93, 0x0c, 0xc7, 0xe8, 0x7b, +0xc7, 0x50, 0x88, 0x4d, 0x72, 0xae, 0xd1, 0xab, +0xd2, 0x46, 0x4e, 0x1b, 0x60, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0xbf, +0x8b, 0xc7, 0xbe, 0xa6, 0xed, 0xa0, 0x80, 0x39, +0x6f, 0x31, 0x05, 0x85, 0xa8, 0x39, 0xae, 0xfd, +0x46, 0x27, 0xf9, 0xfa, 0x45, 0x92, 0x41, 0x61, +0x83, 0xad, 0x3d, 0x35, 0x3b, 0x23, 0xd1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0xe3, 0x3b, 0xa9, 0x30, 0x05, 0xe3, 0xc9, +0xc9, 0xe6, 0x43, 0x17, 0xe5, 0x49, 0x35, 0x2e, +0x9c, 0x6a, 0xa9, 0x93, 0xf9, 0x0f, 0xd7, 0x86, +0x70, 0x53, 0x59, 0x13, 0x7a, 0x75, 0x1a, 0x23, +0xa1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x9a, 0x8b, 0x40, 0x74, 0xfa, +0xb1, 0x08, 0xe6, 0x82, 0xd2, 0x4c, 0x1c, 0x03, +0x18, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x6a, 0x34, 0x21, 0xfe, +0x1a, 0x5f, 0x21, 0x6a, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0xdd, 0x27, 0xd7, +0x93, 0x60, 0x8f, 0x6f, 0xef, 0x6c, 0x46, 0xa8, +0x3b, 0xba, 0xf8, 0x70, 0x42, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0xde, +0xe2, 0x30, 0x4f, 0xc3, 0x1e, 0xcf, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0xea, +0x5d, 0x71, 0x85, 0x0d, 0x8c, 0x95, 0xff, 0x9d, +0x73, 0x81, 0xab, 0xda, 0x76, 0x6d, 0x72, 0x54, +0x48, 0x01, 0x2e, 0xeb, 0x9d, 0x73, 0xf4, 0xc0, +0x81, 0x8f, 0x0b, 0xac, 0xb9, 0x55, 0x75, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0x1d, 0x3c, 0xc1, 0x9d, 0xf4, 0x42, 0x84, +0xbe, 0x04, 0xde, 0x8b, 0x25, 0xc7, 0xbd, 0xad, +0x8e, 0xe8, 0xf5, 0x22, 0xb1, 0xa0, 0x33, 0x6e, +0xae, 0x37, 0x6e, 0xab, 0x55, 0x64, 0x3f, 0x1e, +0xf3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0xa5, 0x99, 0x1e, 0x9d, 0xd1, +0xaa, 0x21, 0xc0, 0x9b, 0x80, 0x80, 0x99, 0xae, +0x75, 0x23, 0x3a, 0x97, 0xc2, 0xf0, 0xb9, 0x38, +0x04, 0xea, 0xdd, 0x08, 0xe2, 0x63, 0x8d, 0x61, +0x38, 0x7f, 0x3a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0xf1, 0x6f, 0xea, +0x65, 0x30, 0x28, 0x38, 0x4f, 0x5d, 0x00, 0xdf, +0xa3, 0x2e, 0x2c, 0xd9, 0x74, 0x4a, 0x80, 0xc2, +0xeb, 0xc4, 0x2d, 0x88, 0x7c, 0x19, 0xc8, 0x30, +0xef, 0x68, 0x1f, 0x5e, 0x4f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0x70, +0x7e, 0x32, 0xa1, 0x1b, 0xc0, 0xb3, 0x42, 0x71, +0xa5, 0x3c, 0xe2, 0x59, 0xad, 0xdb, 0xaa, 0xde, +0xa4, 0xec, 0xc1, 0x15, 0x89, 0xb1, 0x59, 0x74, +0x96, 0xba, 0x2c, 0x0e, 0xdd, 0x97, 0x07, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0x85, 0x73, 0xe1, 0x83, 0xbd, 0xfc, 0xb7, +0x43, 0xf3, 0xbc, 0xee, 0x84, 0xe7, 0x9e, 0x53, +0x4c, 0x0c, 0x59, 0x73, 0xe4, 0x54, 0x3e, 0x07, +0x67, 0xa2, 0x6e, 0x7b, 0x19, 0x12, 0x77, 0x13, +0xce, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0xc2, 0xeb, 0x0b, 0xac, 0x0b, +0x31, 0x31, 0x4a, 0x96, 0x36, 0x91, 0xcf, 0x49, +0x12, 0x1c, 0xb9, 0xd5, 0x99, 0xf6, 0x07, 0xd0, +0x7a, 0x5b, 0xd6, 0xd7, 0xbe, 0x32, 0xc3, 0x49, +0xb5, 0x2e, 0x08, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0xba, 0x50, 0x07, +0xc0, 0x85, 0x12, 0xe5, 0x47, 0xa5, 0xcc, 0xe1, +0x39, 0xee, 0xc6, 0x69, 0x19, 0x15, 0x6a, 0x55, +0xf0, 0x67, 0x31, 0x46, 0x94, 0x3e, 0x70, 0x73, +0x24, 0xf3, 0xee, 0x50, 0x76, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0xba, +0x64, 0x9f, 0x81, 0x0e, 0x19, 0x17, 0xfe, 0x14, +0x76, 0x9e, 0x29, 0xe2, 0x59, 0xe2, 0xe7, 0x8a, +0x50, 0xb3, 0xa1, 0x90, 0xab, 0x2f, 0x54, 0xbd, +0x0b, 0xc1, 0x38, 0xdb, 0x93, 0x9a, 0x36, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0x83, 0xd2, 0x02, 0x22, 0x69, 0x13, 0x09, +0xda, 0xf6, 0x2a, 0x70, 0x6f, 0xa5, 0xc5, 0x56, +0xcf, 0x32, 0xa8, 0x0c, 0x6d, 0xeb, 0x7c, 0xf4, +0xb8, 0x68, 0x55, 0xfc, 0xb9, 0xfe, 0x2c, 0x22, +0xb2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0x68, 0x2c, 0xc8, 0x84, 0x19, +0xfc, 0x48, 0x92, 0x10, 0x0c, 0xa4, 0xf3, 0xf7, +0x17, 0x96, 0x13, 0x3a, 0x27, 0x83, 0xe2, 0x94, +0xf1, 0x6a, 0x28, 0xbd, 0x2b, 0x5f, 0xe2, 0x49, +0x0b, 0x55, 0x20, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0xe8, 0xac, 0x27, +0x96, 0xba, 0x3c, 0xd8, 0x59, 0xd1, 0xec, 0x9f, +0xe4, 0x45, 0x23, 0xb3, 0xe1, 0xd1, 0xf9, 0x1a, +0x21, 0xbc, 0x54, 0xac, 0x5f, 0x4a, 0x18, 0x4c, +0xe1, 0x63, 0x32, 0x48, 0x40, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0x8c, +0xfe, 0xd2, 0x13, 0xb0, 0x30, 0xb8, 0x5c, 0xbf, +0x0a, 0xb8, 0x18, 0x61, 0x34, 0x4e, 0x9a, 0xf4, +0x3c, 0x28, 0x3c, 0xf1, 0x7d, 0x1d, 0x26, 0xe5, +0x68, 0xdf, 0x61, 0x08, 0x09, 0x00, 0x11, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0x52, 0x5f, 0xb2, 0xb8, 0x41, 0x16, 0xbd, +0x43, 0x76, 0x85, 0x20, 0x4e, 0x1e, 0xc5, 0xa7, +0xb6, 0x2b, 0xa4, 0x63, 0xc3, 0xbe, 0x53, 0xab, +0x7c, 0x89, 0x6b, 0x84, 0xaa, 0xdc, 0x4d, 0x8f, +0xe0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0xf0, 0x12, 0xdb, 0x84, 0x7a, +0x38, 0x0b, 0x87, 0x4b, 0x39, 0x2a, 0x3b, 0x13, +0x14, 0x37, 0x81, 0xc4, 0xad, 0x47, 0x41, 0xdc, +0xf5, 0x0e, 0x98, 0xc1, 0xf9, 0xeb, 0xca, 0x46, +0xe4, 0x90, 0xe7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0xc5, 0x10, 0xcb, +0xb4, 0xb8, 0xfb, 0x0d, 0x8c, 0xfd, 0x08, 0x6d, +0xca, 0x4b, 0xd5, 0x29, 0xd8, 0x78, 0xbe, 0x09, +0x1b, 0x94, 0x6e, 0xcb, 0xd6, 0x6e, 0xf3, 0x50, +0xc5, 0xbf, 0x40, 0x54, 0x53, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0x76, +0xf7, 0xe0, 0x06, 0x7b, 0x4f, 0xb6, 0x1c, 0xeb, +0x88, 0xd9, 0xb3, 0x4f, 0x74, 0x12, 0xe0, 0x21, +0x7f, 0xa6, 0x0b, 0x4b, 0x38, 0xdf, 0xd3, 0x63, +0x04, 0x59, 0x31, 0xd5, 0x49, 0xc3, 0x75, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0x5a, 0xb3, 0xa7, 0x33, 0x4e, 0x76, 0xfa, +0x09, 0x25, 0xee, 0xcf, 0x5c, 0xc4, 0x66, 0x7d, +0x2f, 0xef, 0x48, 0x65, 0x24, 0x92, 0x7e, 0x0b, +0xdb, 0xe1, 0x1e, 0xbd, 0x89, 0xa2, 0xae, 0x0a, +0x44, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0xa5, 0x68, 0x09, 0x6b, 0x36, +0x27, 0x0e, 0x28, 0x30, 0xef, 0xc5, 0x66, 0x65, +0x86, 0xff, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xc5, 0xb4, 0x16, 0x97, +0xa0, 0xed, 0x64, 0x3a, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0x3d, 0x07, 0x53, +0x0d, 0x9c, 0x16, 0xd6, 0xcf, 0xc5, 0x98, 0x02, +0xd0, 0x1e, 0x11, 0x7d, 0x4d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x20, +0xa1, 0xc0, 0xd4, 0xb9, 0xb8, 0x2d, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0x2c, +0x5f, 0x23, 0xe8, 0x55, 0x25, 0x50, 0xf6, 0x98, +0xb9, 0x3b, 0x09, 0xbc, 0x6f, 0x48, 0xf2, 0xe2, +0x13, 0x41, 0x24, 0x55, 0x47, 0x26, 0xcf, 0x2f, +0x9a, 0x73, 0xa7, 0x4e, 0x52, 0x56, 0x5d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0x70, 0x6b, 0x14, 0xf9, 0x79, 0xbf, 0x90, +0x7c, 0x60, 0x5f, 0x68, 0x59, 0x62, 0xd3, 0x64, +0x3d, 0x60, 0x9c, 0x3a, 0xdb, 0x79, 0x5b, 0x3b, +0xe5, 0xc1, 0xcf, 0x1f, 0x14, 0x5f, 0x08, 0x1a, +0x49, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0x81, 0xbf, 0x85, 0x66, 0x88, +0x1a, 0xad, 0x13, 0x48, 0xd8, 0x68, 0x49, 0x33, +0xa1, 0x92, 0xcf, 0x51, 0x7f, 0xdf, 0x19, 0x63, +0x10, 0x41, 0x82, 0xd6, 0x20, 0x75, 0x6b, 0x17, +0x72, 0x09, 0xa2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0x16, 0xf7, 0xc4, +0x8e, 0xb6, 0xf7, 0xae, 0x80, 0xb6, 0x48, 0x77, +0x66, 0xa5, 0x03, 0x43, 0xff, 0xcd, 0x6b, 0x4c, +0xaf, 0xb1, 0x67, 0x1a, 0x81, 0x4e, 0x6f, 0xf3, +0x94, 0xee, 0xa7, 0x33, 0x04, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0x00, +0x21, 0xf1, 0x62, 0x60, 0x28, 0x7e, 0xb5, 0x89, +0x5c, 0x2f, 0x57, 0x6c, 0xd7, 0x81, 0xaf, 0xfc, +0x6e, 0x44, 0xd2, 0x61, 0x9b, 0xf9, 0x9c, 0xd5, +0xf5, 0x1d, 0x09, 0xaa, 0xa0, 0x96, 0xd4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0x58, 0x09, 0x7e, 0x33, 0xdf, 0x5b, 0x88, +0x80, 0x87, 0x8d, 0xa4, 0xf5, 0xef, 0xcc, 0x47, +0x16, 0x76, 0xc4, 0x71, 0xb3, 0x73, 0x23, 0x4d, +0xf4, 0x2e, 0xd7, 0xeb, 0x69, 0x40, 0x85, 0x11, +0x3f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0x72, 0x87, 0x5f, 0x11, 0xa9, +0xf6, 0x3b, 0xac, 0xc3, 0xfd, 0x32, 0x38, 0x50, +0x88, 0x9a, 0x34, 0xf0, 0x4c, 0x52, 0x0e, 0xd1, +0x6f, 0x66, 0xf9, 0x5a, 0xe8, 0x31, 0x9c, 0xd1, +0xe9, 0x42, 0x37, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0x30, 0x9a, 0xb1, +0x5f, 0x2f, 0xf0, 0x29, 0x78, 0x2a, 0x58, 0xd4, +0x93, 0x7c, 0xc4, 0x10, 0x61, 0xc5, 0x10, 0xcb, +0x74, 0x71, 0xc3, 0xa4, 0xb3, 0x2b, 0x83, 0x65, +0x80, 0x04, 0x01, 0x92, 0xa4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0x91, +0xc3, 0x2e, 0x21, 0x61, 0xf8, 0xfb, 0x2f, 0xf8, +0x4a, 0x5b, 0x6d, 0x34, 0x34, 0x0c, 0x5c, 0x91, +0x29, 0x34, 0x18, 0xa6, 0x99, 0x61, 0x15, 0x23, +0xa9, 0x4f, 0xfb, 0xe5, 0x59, 0xdd, 0x0a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x9a, 0x12, 0x7b, 0x79, 0x7d, 0xb8, 0xd0, +0x00, 0xaf, 0xa1, 0x33, 0x8b, 0x7e, 0x66, 0x3b, +0x7c, 0x74, 0xc0, 0x50, 0xfc, 0x57, 0x21, 0x32, +0xaa, 0xb9, 0xd0, 0xec, 0x0b, 0x54, 0x67, 0xaa, +0x06, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0xb1, 0x24, 0x28, 0x09, 0x17, +0x06, 0x39, 0x1f, 0xcd, 0x84, 0x4c, 0x5c, 0xa6, +0xdd, 0xea, 0x2b, 0xa0, 0xbc, 0xef, 0x39, 0x61, +0xb4, 0xc1, 0xb8, 0x65, 0xf0, 0xb4, 0x35, 0x3f, +0xd9, 0x42, 0x16, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x1a, 0xb7, 0x11, +0x2f, 0xce, 0xc0, 0x8e, 0x48, 0x42, 0x94, 0xfd, +0x2d, 0xdc, 0xdb, 0xdd, 0x31, 0xf1, 0x23, 0xad, +0x9b, 0x9a, 0x78, 0xb5, 0x06, 0xcb, 0x8e, 0xaf, +0x0b, 0xb1, 0x73, 0x0c, 0x3d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0x56, +0xf4, 0x54, 0x25, 0xb3, 0xb3, 0x00, 0xcc, 0x68, +0x65, 0x3f, 0xd1, 0x9c, 0x68, 0x82, 0x38, 0x67, +0x2f, 0x91, 0x06, 0x7e, 0xe5, 0x24, 0xb5, 0xca, +0x59, 0xb5, 0xed, 0x16, 0x60, 0x1d, 0xbf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0x60, 0xb5, 0x4c, 0xd9, 0xd9, 0xda, 0xed, +0xbf, 0xd9, 0xa6, 0x39, 0xca, 0x6b, 0x06, 0x4b, +0xd4, 0xbd, 0xa1, 0x63, 0x12, 0x72, 0x97, 0xa3, +0x16, 0x9c, 0xe4, 0x00, 0x85, 0x6c, 0x7c, 0x1a, +0x5a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0x79, 0xb2, 0xae, 0x5b, 0x6d, +0x59, 0x6b, 0x4b, 0x72, 0x41, 0x55, 0x5b, 0x18, +0x52, 0xf0, 0x51, 0x85, 0xc6, 0x9b, 0x41, 0x2a, +0x0a, 0x34, 0xd8, 0xd5, 0x3b, 0x68, 0xf9, 0xf0, +0x3b, 0x42, 0x32, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x5f, 0x9d, 0xdf, +0x5e, 0x4d, 0x0e, 0x86, 0x54, 0xe1, 0xed, 0xcd, +0x69, 0xdf, 0x09, 0x26, 0xb9, 0x8c, 0xf4, 0x25, +0x44, 0x67, 0x47, 0xbd, 0x30, 0x31, 0x55, 0x9e, +0xc7, 0x2b, 0x97, 0xd0, 0x93, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0xd8, +0xfd, 0x5f, 0x06, 0x81, 0xad, 0x14, 0x11, 0x87, +0xe6, 0xf8, 0x42, 0x27, 0x68, 0x5b, 0x85, 0x27, +0x41, 0xff, 0x3c, 0x39, 0x41, 0x2d, 0x5f, 0xf4, +0x85, 0x15, 0xb7, 0x56, 0xf0, 0xe3, 0x84, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0x52, 0xaa, 0x80, 0x6b, 0x92, 0x17, 0x85, +0xdc, 0xf7, 0x6a, 0xf5, 0x0a, 0xe1, 0x07, 0xa6, +0x5e, 0x50, 0xa9, 0x97, 0x61, 0x4c, 0x2f, 0x8b, +0x1a, 0xc1, 0xf4, 0xbb, 0xd2, 0xe9, 0x0b, 0xd0, +0x31, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0xee, 0x50, 0xf5, 0x67, 0x40, +0x16, 0x51, 0xda, 0xe5, 0x9a, 0xf3, 0x6e, 0xa8, +0x15, 0x5b, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x17, 0xbb, 0x52, 0xbe, +0xe8, 0x82, 0x30, 0xd0, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0x0a, 0x52, 0x9c, +0x9a, 0x2a, 0xa6, 0x05, 0x15, 0x20, 0xdf, 0x93, +0x43, 0xa4, 0x06, 0x8d, 0x5b, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0xfd, +0x8b, 0xc5, 0x29, 0x81, 0xd4, 0x64, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0xe8, +0x63, 0x82, 0x92, 0xcc, 0x41, 0x29, 0xa8, 0x9f, +0x78, 0xb1, 0xba, 0x15, 0xe6, 0xcd, 0xba, 0x87, +0x77, 0x7f, 0x8c, 0x64, 0x12, 0x88, 0x65, 0x97, +0x4b, 0xfa, 0x90, 0x33, 0x5e, 0x0a, 0x3b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0xa8, 0xc9, 0xa2, 0x6c, 0x1d, 0xd6, 0xac, +0x0a, 0x73, 0x6e, 0x00, 0xca, 0xb6, 0xa8, 0xfd, +0xc7, 0xd4, 0xe1, 0x13, 0xa4, 0x6e, 0xe7, 0xc4, +0xad, 0x9c, 0x2d, 0x65, 0x50, 0xdf, 0x7f, 0xf6, +0xdd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0xf4, 0xb0, 0x5b, 0x8f, 0x38, +0xb0, 0x38, 0x52, 0xdb, 0x43, 0xe1, 0xfb, 0x21, +0x34, 0x3f, 0x44, 0x7b, 0xd9, 0xe2, 0x35, 0xf8, +0xf5, 0x22, 0xd8, 0x4f, 0xdb, 0xb3, 0xc6, 0xc4, +0xdc, 0x6a, 0xb0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x57, 0x2e, 0xbc, +0xca, 0x2b, 0x04, 0x55, 0xad, 0x44, 0x6e, 0x8f, +0x1f, 0x21, 0xd9, 0xdf, 0xb1, 0xb0, 0xb0, 0xa6, +0x3a, 0xfc, 0x66, 0x53, 0x3b, 0x31, 0x99, 0xee, +0x4b, 0x96, 0x9e, 0xca, 0x8d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x6d, +0xfb, 0x38, 0x44, 0xf9, 0x47, 0x7f, 0xb8, 0xe6, +0x0b, 0x59, 0xbc, 0xd8, 0x99, 0x62, 0xb3, 0x2b, +0x45, 0x57, 0x34, 0xc4, 0x51, 0x77, 0x9c, 0x1c, +0x32, 0x97, 0x7b, 0x57, 0xab, 0x0c, 0x2d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0x56, 0xc2, 0xf1, 0x7a, 0x94, 0x55, 0x7b, +0xf6, 0xc7, 0x8a, 0xc6, 0x2c, 0x37, 0x5d, 0x17, +0x0e, 0xff, 0x18, 0x09, 0x74, 0x10, 0xdc, 0xe8, +0x4f, 0x54, 0x58, 0x9d, 0x94, 0xa0, 0xd6, 0x95, +0xa7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0xa7, 0xd4, 0x73, 0xd8, 0xc4, +0x6d, 0x9f, 0xdb, 0x37, 0x27, 0x00, 0x21, 0x02, +0x17, 0x5c, 0x91, 0xbf, 0x1a, 0x0b, 0xe7, 0x9f, +0xf7, 0xdf, 0xe1, 0xd0, 0xac, 0xc3, 0xbc, 0x63, +0x5d, 0xc0, 0x52, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0x5f, 0x35, 0xe8, +0x1b, 0xab, 0x1f, 0x6b, 0x96, 0x77, 0x5a, 0x67, +0x85, 0x29, 0xf1, 0x96, 0x23, 0x0a, 0x6b, 0x99, +0x10, 0xd6, 0x8d, 0xb0, 0x28, 0x41, 0xfb, 0x1a, +0x9b, 0xc7, 0xd3, 0xc6, 0xd8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0x19, +0x7b, 0x66, 0xe0, 0x16, 0x15, 0x5d, 0x37, 0xf0, +0x92, 0x16, 0x51, 0xc1, 0x40, 0xd0, 0x9b, 0xec, +0x93, 0x91, 0xe4, 0x70, 0x13, 0xa6, 0x10, 0x27, +0x17, 0xd1, 0xad, 0xc7, 0xca, 0xe0, 0xf3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x50, 0x53, 0xa5, 0x22, 0xa2, 0x17, 0x22, +0x2f, 0xc0, 0x27, 0x5b, 0xdd, 0x64, 0xa4, 0xd3, +0x71, 0x6a, 0x44, 0x8e, 0xe4, 0xc7, 0xce, 0x53, +0x14, 0x7a, 0xea, 0x1f, 0xab, 0x73, 0xa9, 0x42, +0x06, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0x87, 0xe1, 0xf8, 0xe4, 0x97, +0x8a, 0x6e, 0x5d, 0xee, 0xd3, 0x16, 0x72, 0x4a, +0xe0, 0x23, 0xca, 0x88, 0x70, 0xab, 0xe8, 0xb6, +0x6d, 0x02, 0xe6, 0x3a, 0x2e, 0xb9, 0x8e, 0xfa, +0x86, 0x31, 0x0b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0xff, 0x3f, 0x92, +0xf9, 0x9b, 0xd7, 0x60, 0x2a, 0xf4, 0x98, 0x77, +0x4e, 0xd0, 0xd8, 0x71, 0xb3, 0x35, 0x89, 0xf6, +0x30, 0xfd, 0xb2, 0xf9, 0xae, 0x3b, 0x7e, 0x7c, +0xf4, 0xa4, 0xf7, 0x95, 0x4f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0x31, +0x0b, 0x5f, 0xb9, 0x4c, 0x6a, 0xe8, 0xe0, 0xa1, +0xea, 0x75, 0x73, 0xf1, 0xa8, 0x85, 0x56, 0x80, +0x54, 0x20, 0x91, 0x90, 0x76, 0xd8, 0x37, 0x52, +0xa5, 0x9b, 0x0a, 0xda, 0xad, 0x48, 0x8c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0x26, 0x3a, 0xbf, 0x18, 0xe1, 0x74, 0x3f, +0xad, 0x17, 0x7e, 0x54, 0xa9, 0x8e, 0x6d, 0x3c, +0xa2, 0xfe, 0x06, 0x9b, 0xf4, 0x38, 0xf0, 0x32, +0xa8, 0xb5, 0xdb, 0xd6, 0xec, 0x32, 0xac, 0x84, +0x48, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0x32, 0xac, 0x07, 0xf8, 0xff, +0xf8, 0x53, 0xf2, 0xbd, 0x26, 0x04, 0xfd, 0x2e, +0xab, 0x31, 0x0d, 0x59, 0x66, 0x27, 0xc8, 0xa8, +0xb6, 0x23, 0x17, 0x8d, 0x13, 0xd6, 0xad, 0x08, +0xda, 0x7b, 0x74, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0x80, 0x77, 0xf0, +0xbf, 0x33, 0x20, 0x91, 0xed, 0xb3, 0x10, 0xaa, +0x02, 0xa1, 0x9a, 0x8c, 0xa7, 0x8c, 0xa0, 0xee, +0x73, 0x69, 0x75, 0xd5, 0x1a, 0x3d, 0x05, 0x37, +0x6f, 0x57, 0xb4, 0x88, 0x28, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0x86, +0xc2, 0x0f, 0x39, 0xe1, 0xf0, 0xb9, 0xe7, 0xc1, +0xd2, 0x70, 0x0e, 0x02, 0xd6, 0x9b, 0xf9, 0x3c, +0x08, 0x4e, 0x96, 0xaa, 0x6e, 0x1f, 0x70, 0xdc, +0x06, 0x23, 0xd8, 0x3a, 0xca, 0x90, 0x31, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0xb1, 0x55, 0x6d, 0x1d, 0x2c, 0x09, 0xd1, +0x1d, 0xbc, 0x52, 0x23, 0xe1, 0x17, 0xec, 0x26, +0x5a, 0xf3, 0xe4, 0x22, 0xee, 0x45, 0xbb, 0xb2, +0xda, 0x5c, 0x6f, 0x6e, 0xca, 0xbf, 0xda, 0x0e, +0x7e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0x91, 0x1f, 0xb2, 0x0b, 0x13, +0xda, 0xf0, 0xe0, 0x67, 0x6f, 0x6c, 0xa6, 0x70, +0x72, 0xca, 0xf9, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x8b, 0x9a, 0x61, 0xd7, +0xb6, 0x85, 0xe3, 0x76, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0xbd, 0xae, 0x6e, +0x0a, 0xc7, 0x56, 0x7a, 0xec, 0x9a, 0x1d, 0x8f, +0x01, 0x7d, 0x62, 0x13, 0x44, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xed, 0x4e, +0x32, 0x7d, 0x40, 0xe5, 0x2e, 0x51, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0x0e, +0x02, 0x3d, 0x47, 0x1c, 0xaa, 0x27, 0xf9, 0x08, +0x50, 0x4a, 0x8c, 0xed, 0x74, 0x92, 0x30, 0xa0, +0x77, 0xca, 0x0c, 0xb7, 0x97, 0xa0, 0x72, 0x57, +0x8c, 0x77, 0x19, 0x3a, 0x6d, 0xa2, 0x7b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0xb7, 0xa0, 0x71, 0x80, 0x63, 0x0e, 0xa8, +0x4c, 0x02, 0x5c, 0xf4, 0xe4, 0x4f, 0x91, 0x6a, +0x72, 0xea, 0x55, 0xb6, 0x14, 0x23, 0x73, 0x44, +0xcd, 0x1a, 0x9b, 0xb5, 0xab, 0x7c, 0x7b, 0x76, +0x11, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0x29, 0x6b, 0x8e, 0x02, 0x66, +0xaa, 0x98, 0xd4, 0x17, 0x51, 0xd5, 0x8b, 0x20, +0xa8, 0x8d, 0x6a, 0x48, 0xa2, 0xa3, 0x0a, 0x7a, +0x11, 0x16, 0x05, 0x9b, 0xf6, 0x2f, 0x48, 0xf4, +0x76, 0x5e, 0xac, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0xb9, 0xdb, 0x49, +0x1a, 0x9b, 0xec, 0xa7, 0x32, 0xcb, 0xa6, 0x9c, +0xff, 0xe6, 0xac, 0xfa, 0xb2, 0x97, 0x68, 0xff, +0x42, 0x31, 0xfb, 0xf0, 0x77, 0x71, 0xae, 0xbc, +0x9a, 0x5f, 0x72, 0x00, 0x6e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0x5a, +0xff, 0xf7, 0xd9, 0x41, 0x00, 0xc1, 0xb7, 0x70, +0x92, 0x62, 0x99, 0x17, 0x0f, 0xac, 0x40, 0x36, +0x37, 0x18, 0x46, 0xc0, 0x14, 0x63, 0xa3, 0x6e, +0xc2, 0xb4, 0x15, 0x97, 0x55, 0xb9, 0xfd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0xbf, 0x67, 0xee, 0x20, 0x6f, 0x8c, 0x4e, +0x0e, 0x26, 0x13, 0xdb, 0x7d, 0x0e, 0x3b, 0x98, +0x39, 0xdf, 0x7d, 0x01, 0x0c, 0x72, 0x39, 0xd2, +0x3e, 0x61, 0x73, 0xa9, 0x4c, 0x67, 0x26, 0xe7, +0x1c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0xd9, 0xcf, 0xa3, 0x1f, 0x16, +0xb9, 0xd2, 0x34, 0xf3, 0x6b, 0x35, 0xf3, 0x93, +0xfe, 0x2d, 0x28, 0x37, 0x87, 0xbd, 0xb9, 0x08, +0x5c, 0xf7, 0xb3, 0xea, 0x0b, 0xaf, 0x18, 0x40, +0x32, 0xaf, 0x6e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x7b, 0xd2, 0x74, +0x99, 0x75, 0xba, 0x4d, 0x98, 0xa3, 0x06, 0xdd, +0xca, 0xb0, 0x3b, 0x93, 0xc5, 0x47, 0xe0, 0x9c, +0xac, 0x4e, 0x75, 0x2c, 0x03, 0xe6, 0xa7, 0x3f, +0x64, 0x1d, 0x72, 0xd3, 0xb0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0xa6, +0x4e, 0xda, 0x41, 0x96, 0x4d, 0xe8, 0x5f, 0xa6, +0x9b, 0xe5, 0x29, 0xff, 0xd9, 0xc1, 0x07, 0x7e, +0x41, 0x1e, 0xee, 0x34, 0x17, 0x64, 0x76, 0xbd, +0x6d, 0xb8, 0xa1, 0x82, 0xbe, 0x7d, 0x40, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0xfc, 0x99, 0x4b, 0xfa, 0x5c, 0xf0, 0xf9, +0xea, 0xd0, 0x9b, 0x12, 0x93, 0x53, 0x0a, 0x24, +0xf6, 0x09, 0x8e, 0x5c, 0x7a, 0x38, 0x0e, 0xe1, +0x1f, 0xdf, 0x79, 0x47, 0x50, 0xf6, 0xea, 0xda, +0x72, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0x11, 0xb1, 0xd4, 0x78, 0x75, +0xe4, 0x23, 0xc6, 0xbb, 0xde, 0x0d, 0xf6, 0xe8, +0xaf, 0x94, 0x99, 0xf9, 0x18, 0x69, 0x96, 0xc2, +0xaa, 0xcc, 0xf1, 0x41, 0xc0, 0x42, 0xe2, 0x11, +0x63, 0x34, 0xa7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0x7d, 0xd0, 0xb3, +0xab, 0xdd, 0x42, 0x78, 0xd1, 0xb4, 0xdd, 0x96, +0xbc, 0xe0, 0x94, 0x45, 0xdb, 0x53, 0x3d, 0xf1, +0xc3, 0x5a, 0x88, 0x55, 0x4c, 0x29, 0xde, 0x59, +0xb2, 0x91, 0xfa, 0xcf, 0x09, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0xeb, +0x96, 0x99, 0x96, 0xc2, 0x6f, 0xab, 0x95, 0xa1, +0x6f, 0x80, 0xcd, 0x85, 0xc1, 0x75, 0x93, 0x3b, +0x47, 0xb5, 0x7c, 0xfc, 0xef, 0xc9, 0x5a, 0xe9, +0x04, 0x74, 0xc0, 0x6f, 0x2a, 0x65, 0xb1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0xd0, 0x7c, 0x8c, 0xdb, 0x15, 0x09, 0x44, +0x6d, 0x3f, 0x27, 0xec, 0x13, 0xa3, 0x2c, 0xbc, +0x6e, 0xff, 0xd6, 0xb7, 0x2d, 0xda, 0x27, 0x9d, +0x41, 0x88, 0xa2, 0x3a, 0xa1, 0xe8, 0x04, 0xac, +0xce, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x8a, 0x85, 0xc4, 0xb6, 0xe6, +0x30, 0xdb, 0xbf, 0x0d, 0x5b, 0x5d, 0xbe, 0x10, +0xc4, 0x49, 0x05, 0xb1, 0xb3, 0x47, 0x2d, 0x58, +0xfd, 0x3c, 0x69, 0xe7, 0x8a, 0x64, 0xb6, 0x7a, +0x06, 0x3b, 0xb8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0xba, 0x97, 0x43, +0x13, 0xdc, 0x24, 0xaf, 0xc3, 0xed, 0xdc, 0xe9, +0xb0, 0xc7, 0xb7, 0x16, 0xfc, 0x7e, 0x55, 0x63, +0x3b, 0x56, 0x4d, 0xbd, 0x60, 0xf8, 0x3b, 0x34, +0xc1, 0xbd, 0x13, 0xec, 0x90, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0x11, +0x27, 0x49, 0x18, 0xc5, 0xe9, 0x4b, 0x4a, 0x2b, +0x52, 0x8b, 0x23, 0x4a, 0x57, 0xa6, 0x38, 0xa4, +0x5a, 0xa2, 0xcb, 0xfd, 0x68, 0x39, 0x02, 0x71, +0x20, 0x5f, 0xd0, 0x3a, 0x73, 0x35, 0xb0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0xf9, 0xe1, 0xb3, 0xef, 0x51, 0xa4, 0x78, +0x36, 0x5f, 0x85, 0xc0, 0xed, 0xae, 0xbf, 0x3a, +0x9b, 0xfd, 0x1b, 0xd1, 0xa5, 0xdd, 0x7f, 0xb2, +0x33, 0x05, 0x0e, 0x8c, 0xda, 0xae, 0xcf, 0xac, +0xca, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0x80, 0xd8, 0x1f, 0xd5, 0x23, +0xdb, 0x21, 0x4b, 0x64, 0x1d, 0x17, 0x08, 0x7e, +0x89, 0x20, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xc7, 0xfc, 0x3d, 0x54, +0x3d, 0x4c, 0x8e, 0xf9, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0x39, 0x77, 0x11, +0x6c, 0xa1, 0xa4, 0x3e, 0x24, 0x12, 0x43, 0x21, +0x94, 0x5c, 0x97, 0x9c, 0x0b, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0x5a, +0xeb, 0x1b, 0x3f, 0x44, 0xdd, 0x2b, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0x81, +0xad, 0x78, 0xce, 0xb0, 0xd6, 0x4d, 0x17, 0xb4, +0xfc, 0x7e, 0x72, 0x34, 0x58, 0x04, 0x40, 0x58, +0x8b, 0x17, 0xe8, 0xcd, 0x12, 0x7e, 0xde, 0xff, +0xd9, 0x41, 0x12, 0x3e, 0x3b, 0xf9, 0xde, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0xc8, 0xd8, 0x73, 0x0e, 0x6c, 0x9b, 0x74, +0x33, 0x95, 0x1a, 0x51, 0x50, 0x58, 0xb6, 0xae, +0x70, 0xd9, 0x0f, 0x97, 0x33, 0xc8, 0x27, 0x57, +0xe7, 0x1e, 0x62, 0x3f, 0x21, 0xfb, 0x8e, 0x06, +0x42, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0x2d, 0x99, 0x6f, 0x2f, 0xac, +0x42, 0x5a, 0xe9, 0x2c, 0x43, 0x9b, 0x32, 0xfb, +0xeb, 0x97, 0xd3, 0x02, 0x6b, 0x2b, 0xb8, 0xef, +0x2f, 0x38, 0x4c, 0x43, 0xe5, 0xd1, 0x5c, 0xcf, +0xa7, 0x21, 0x49, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0xbc, 0xe8, 0x7f, +0x7c, 0xee, 0x8d, 0x78, 0x48, 0x8b, 0xbc, 0x79, +0x57, 0xe4, 0xe5, 0xdf, 0x3c, 0xb4, 0x5a, 0x26, +0x72, 0x3e, 0x72, 0xde, 0x38, 0xf6, 0x06, 0x01, +0xf7, 0x7f, 0x59, 0x40, 0xe0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0xca, +0x9e, 0x9d, 0xb9, 0x88, 0x9e, 0x64, 0xcd, 0x35, +0x6e, 0x0f, 0xea, 0x37, 0xb5, 0xc3, 0x17, 0x87, +0x72, 0x03, 0x8f, 0x46, 0xd3, 0xe6, 0x36, 0xcb, +0x5c, 0xb8, 0x81, 0x23, 0x32, 0xa9, 0xa3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0x13, 0xb8, 0xd7, 0x85, 0x49, 0xe8, 0xf0, +0x18, 0xc6, 0xd9, 0x7f, 0xe8, 0x0b, 0x49, 0xce, +0xe2, 0x3f, 0x00, 0xc1, 0xfa, 0xb1, 0x8f, 0x51, +0x01, 0x33, 0x88, 0x11, 0x58, 0x17, 0x2b, 0x36, +0x9a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0xeb, 0x89, 0x24, 0x7b, 0x8f, +0x46, 0x3e, 0x11, 0xb6, 0x61, 0xb2, 0x91, 0x73, +0x37, 0x37, 0x19, 0xec, 0xe6, 0xe7, 0x1a, 0x47, +0x7c, 0x7a, 0x04, 0x7e, 0xa0, 0xa7, 0x56, 0x56, +0xa2, 0xad, 0x28, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0x37, 0x5f, 0xd6, +0xf9, 0xa4, 0x1f, 0x1f, 0x1c, 0xb6, 0xe7, 0x06, +0x29, 0x6e, 0x45, 0x7f, 0x02, 0x19, 0x90, 0x7f, +0x86, 0xfd, 0x67, 0x59, 0x89, 0x42, 0xfd, 0xbf, +0x4f, 0x71, 0xfa, 0x98, 0xb6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0xb0, +0x77, 0xb3, 0x1d, 0xfc, 0x96, 0x3d, 0x03, 0xb8, +0x84, 0x53, 0xd3, 0xc7, 0xe1, 0x3f, 0x9e, 0x4d, +0x14, 0xe2, 0xa1, 0x47, 0x91, 0xaf, 0x94, 0xdf, +0x8b, 0xe0, 0xcb, 0x95, 0xbc, 0xee, 0x4d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0x8f, 0x42, 0xa9, 0xdc, 0xef, 0xcf, 0xfc, +0xd5, 0x92, 0x32, 0x53, 0x36, 0xbd, 0x07, 0x10, +0x52, 0x48, 0x0f, 0xd4, 0xc1, 0x91, 0x19, 0x96, +0xcc, 0x54, 0x77, 0xc1, 0xf8, 0x08, 0x03, 0xb9, +0x67, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0xc3, 0x30, 0xc7, 0x23, 0xa7, +0x92, 0x15, 0xa1, 0xe4, 0x68, 0x59, 0x15, 0x70, +0x4a, 0x31, 0x55, 0xb6, 0x26, 0x3d, 0x1c, 0x5a, +0xc7, 0xf7, 0x17, 0x9c, 0x2b, 0x3c, 0x49, 0x60, +0x11, 0xd2, 0x01, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0x36, 0xc5, 0xac, +0x2e, 0xa4, 0x9e, 0x9c, 0x59, 0x96, 0x8e, 0xbb, +0xa2, 0xb8, 0xc8, 0x50, 0x02, 0x04, 0x32, 0x8a, +0x6b, 0x1e, 0xd4, 0xcb, 0x3e, 0x8e, 0x66, 0x6b, +0xcc, 0xce, 0x70, 0x78, 0x33, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0xe0, +0x00, 0xa9, 0xa5, 0xb2, 0x56, 0x4f, 0xf9, 0x02, +0x6c, 0x46, 0x7c, 0xba, 0x92, 0x80, 0x7c, 0xf9, +0x53, 0x6d, 0x41, 0xe7, 0xf9, 0xcc, 0xee, 0x9f, +0xe5, 0x4d, 0xc8, 0x42, 0x0e, 0x5c, 0x6e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0xe6, 0x41, 0xf1, 0xe7, 0xa7, 0x04, 0x82, +0x7d, 0xff, 0x32, 0x90, 0xd7, 0xb8, 0x44, 0x79, +0x62, 0xba, 0xb5, 0x6b, 0xbb, 0x77, 0xaa, 0xac, +0xbc, 0x4e, 0x30, 0x8d, 0xfe, 0x7e, 0xc9, 0x04, +0xdd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0x71, 0xc1, 0xb6, 0xd5, 0x94, +0x38, 0x36, 0xdb, 0xd9, 0x16, 0x5a, 0x77, 0x14, +0x5f, 0x7d, 0x17, 0xf5, 0x4c, 0x4c, 0xd6, 0x4d, +0xf0, 0xba, 0x75, 0x1b, 0x3c, 0x2b, 0x97, 0x0f, +0x91, 0x69, 0xfb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0x0f, 0xaf, 0xeb, +0x1f, 0xa0, 0x7a, 0xcc, 0xc8, 0xf9, 0xdf, 0x80, +0x34, 0xf8, 0x57, 0xf1, 0x83, 0x8f, 0x69, 0x48, +0xba, 0x8a, 0x83, 0xa1, 0x87, 0x29, 0x9f, 0x82, +0x20, 0x85, 0x6c, 0xd1, 0x40, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0x9d, +0x20, 0x97, 0x5e, 0xc0, 0xc0, 0xd8, 0xaa, 0x90, +0xe6, 0xd4, 0x01, 0x9a, 0x44, 0x23, 0xda, 0xea, +0x61, 0xc0, 0xde, 0x56, 0x95, 0x6c, 0xd8, 0x12, +0x26, 0x2b, 0xcc, 0x32, 0xda, 0x53, 0x41, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0x61, 0x99, 0x76, 0x2e, 0xec, 0xd8, 0xee, +0xd4, 0x2c, 0x57, 0x35, 0xe8, 0xd6, 0x35, 0x8c, +0x57, 0xd3, 0x70, 0xd3, 0xf1, 0x78, 0x71, 0x38, +0x90, 0x22, 0x39, 0x05, 0xef, 0x93, 0x01, 0xe5, +0x44, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0x91, 0x15, 0x53, 0xa7, 0x8f, +0xce, 0xfe, 0x0c, 0x7e, 0xca, 0x6b, 0x30, 0x5e, +0x9a, 0x3f, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x50, 0x3e, 0x63, 0x0f, +0xd0, 0xb3, 0x5a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0xf4, 0xc1, 0xb6, +0xb9, 0xac, 0x87, 0x67, 0x8b, 0xd3, 0x1f, 0xa0, +0xe7, 0x25, 0xbf, 0x1d, 0x8c, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x47, +0xd3, 0x96, 0x58, 0x96, 0x91, 0xb9, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x0d, +0xc1, 0x4b, 0x57, 0x49, 0x68, 0x12, 0x8b, 0x4a, +0xf0, 0xb2, 0x18, 0x65, 0xf9, 0x81, 0x44, 0x52, +0xd0, 0x54, 0x16, 0xd6, 0x4a, 0x9c, 0xaa, 0xcc, +0xf4, 0xfa, 0x55, 0x3c, 0xf5, 0xb5, 0xd6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0xf0, 0xd9, 0xb5, 0x92, 0xc5, 0xc8, 0x19, +0x18, 0xa4, 0xc6, 0x61, 0x43, 0xf1, 0x4f, 0x53, +0xe4, 0xfb, 0x18, 0x9c, 0x7f, 0x6c, 0xe8, 0x7f, +0xba, 0x0b, 0x09, 0xd5, 0xce, 0x26, 0x5e, 0xfe, +0xa5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0x23, 0xc9, 0xe8, 0x73, 0xaa, +0x6d, 0xc9, 0x95, 0x96, 0x34, 0x48, 0x8a, 0x6a, +0x95, 0x64, 0xcd, 0xed, 0x1c, 0xb2, 0xf2, 0x8c, +0x5c, 0x5a, 0x2a, 0xa5, 0x3c, 0x68, 0xc5, 0x8a, +0x6f, 0x15, 0x9c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0x81, 0x27, 0xda, +0xf0, 0xfb, 0x5a, 0x54, 0x5c, 0x2a, 0x4a, 0x5b, +0x59, 0x37, 0xc0, 0x4e, 0x05, 0xc9, 0x0a, 0x2c, +0x18, 0xb4, 0x51, 0x97, 0x16, 0x0f, 0xd3, 0xa1, +0x3d, 0x65, 0xb9, 0x05, 0x11, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0xd2, +0xd1, 0xe8, 0x0d, 0xa8, 0x29, 0x13, 0x15, 0x03, +0x41, 0x97, 0xf5, 0xb9, 0xe9, 0xb5, 0x9a, 0x1c, +0x13, 0x42, 0x7e, 0x66, 0x19, 0xf3, 0x26, 0x0a, +0xc5, 0x49, 0x65, 0xfe, 0xfd, 0x1f, 0x21, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0x40, 0x8a, 0xc5, 0x8a, 0x9e, 0x05, 0x98, +0x58, 0x68, 0x6b, 0x10, 0x10, 0x0d, 0x43, 0x81, +0xf8, 0xcb, 0xb9, 0xfd, 0x82, 0x51, 0xda, 0x8a, +0x0d, 0x8c, 0x87, 0xf6, 0xa7, 0x14, 0x17, 0x4d, +0xe8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0xfc, 0x7f, 0x16, 0xef, 0x77, +0xb9, 0xa4, 0xbf, 0x59, 0xda, 0xd1, 0x91, 0xd1, +0x74, 0x7a, 0x61, 0xa9, 0x83, 0x9c, 0x78, 0x89, +0x86, 0x92, 0xbc, 0xa6, 0xc8, 0x11, 0xa7, 0xe1, +0x7e, 0xff, 0x69, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0xfa, 0x24, 0x08, +0xab, 0x17, 0x8d, 0x45, 0xf1, 0x68, 0x78, 0xbf, +0x6e, 0x6b, 0x5a, 0x54, 0xb4, 0x6d, 0x99, 0x86, +0x1d, 0x19, 0x22, 0x3f, 0x83, 0x1d, 0xd7, 0x16, +0x47, 0xd0, 0x2b, 0x24, 0xea, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0xd2, +0x11, 0xbb, 0x75, 0x0c, 0xb2, 0xba, 0xd1, 0x9f, +0xca, 0x31, 0xec, 0xdd, 0xcd, 0xa0, 0xac, 0x67, +0x18, 0x65, 0xae, 0x37, 0x72, 0xb3, 0xed, 0x0e, +0xcb, 0x5f, 0x4a, 0x1a, 0xe7, 0xd1, 0xb5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0x75, 0x89, 0x26, 0x16, 0x3c, 0x8b, 0xa4, +0xd1, 0x83, 0x7d, 0x0f, 0x7e, 0xc6, 0xcd, 0x27, +0xcd, 0x9c, 0xb6, 0x74, 0xd6, 0x07, 0xea, 0x6b, +0x7f, 0xb0, 0xee, 0xee, 0x4f, 0x9c, 0x7a, 0x94, +0xe0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0x9d, 0xb8, 0xe4, 0x7f, 0xb0, +0x81, 0x87, 0xd4, 0xd4, 0xb5, 0xea, 0x2d, 0x0b, +0x9e, 0xb6, 0x80, 0x8f, 0xb8, 0xaf, 0x7c, 0x40, +0x82, 0xbe, 0x4f, 0x38, 0x75, 0xda, 0x17, 0x8e, +0x04, 0xdd, 0x7d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x3d, 0x76, 0x9c, +0xab, 0x1e, 0xca, 0x9a, 0x84, 0x61, 0xcd, 0xc2, +0x5d, 0x5b, 0xd8, 0x63, 0xd3, 0x43, 0xc2, 0x75, +0x84, 0xe1, 0x8e, 0xe7, 0x59, 0x3f, 0x5c, 0xb4, +0x26, 0x9c, 0x0f, 0xd1, 0x03, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0xf1, +0x22, 0x13, 0x70, 0xf1, 0x64, 0xb4, 0xc1, 0xf5, +0x69, 0xb7, 0xb3, 0x68, 0xeb, 0xf0, 0x61, 0x12, +0x97, 0xa3, 0x71, 0x4f, 0x22, 0x7c, 0x47, 0xa9, +0xbe, 0x1f, 0xdc, 0x38, 0x79, 0xba, 0x2f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0xb8, 0xcf, 0x99, 0x82, 0xaa, 0xd3, 0x83, +0x83, 0x7b, 0xd6, 0x3f, 0xb5, 0xc3, 0xeb, 0x4c, +0x3a, 0x6d, 0x08, 0x05, 0x9d, 0x17, 0xcc, 0xd6, +0xbb, 0xfc, 0x32, 0x56, 0x28, 0x55, 0x6d, 0xbd, +0x4f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0xa5, 0xdf, 0x0f, 0xfe, 0xdf, +0x06, 0x7f, 0xf5, 0x16, 0x86, 0x96, 0xc3, 0xed, +0x65, 0x9d, 0xa2, 0x1a, 0xfa, 0x61, 0xd5, 0x6e, +0x32, 0x60, 0x69, 0x89, 0x42, 0x39, 0xc6, 0x2f, +0xf4, 0xd5, 0x39, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0x9a, 0xef, 0x20, +0xc1, 0xc5, 0x6f, 0xe6, 0xe3, 0x4c, 0xf3, 0xed, +0xad, 0xa7, 0xc7, 0x58, 0x35, 0xd3, 0x3d, 0xe9, +0x56, 0xf0, 0x74, 0xad, 0x91, 0xf2, 0xed, 0x51, +0xbf, 0xce, 0xa2, 0x52, 0x9d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x9a, +0xce, 0x83, 0x00, 0x6c, 0x0e, 0x2a, 0x75, 0xbe, +0x10, 0x88, 0x2f, 0xf1, 0xfe, 0x6a, 0x68, 0x89, +0x40, 0x0b, 0xef, 0xa3, 0x70, 0x81, 0x99, 0x64, +0x77, 0x12, 0x7e, 0xa1, 0x5f, 0x1d, 0x53, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0xa3, 0xe5, 0x39, 0xf4, 0x25, 0xab, 0x4a, +0x12, 0xe2, 0xa8, 0x75, 0x7f, 0xcc, 0xbf, 0x84, +0x16, 0x58, 0xd9, 0x0b, 0x96, 0xf3, 0x3a, 0xbd, +0xaa, 0xf8, 0x72, 0xf3, 0x91, 0xcc, 0xd2, 0x0a, +0x41, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0x1c, 0x7c, 0xca, 0xe9, 0x50, +0x2f, 0xce, 0x66, 0x2e, 0x74, 0xa4, 0x18, 0x18, +0xb4, 0xd9, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x53, 0xf3, 0x50, 0x11, +0x7d, 0xf5, 0x8a, 0x72, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0x8f, 0xb0, 0x65, +0x05, 0xeb, 0x3b, 0x33, 0x67, 0xcf, 0x72, 0x2c, +0x84, 0x18, 0xbd, 0x62, 0xed, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x7e, +0xca, 0x83, 0xce, 0x49, 0x6e, 0x26, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0x5f, +0x3d, 0x02, 0x73, 0x14, 0xde, 0x4b, 0x71, 0x1d, +0xec, 0x04, 0xb9, 0x7b, 0x5f, 0x55, 0x50, 0x8d, +0xb7, 0x62, 0xea, 0x0a, 0x78, 0x86, 0xe5, 0x7d, +0xed, 0xad, 0xd4, 0x15, 0x1e, 0x64, 0x53, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0x97, 0x6c, 0xa9, 0x75, 0x6d, 0xe4, 0x78, +0x70, 0xad, 0x03, 0xa3, 0xaf, 0x50, 0x91, 0x87, +0x55, 0x87, 0x32, 0x18, 0x12, 0x2e, 0x96, 0x68, +0xf5, 0x86, 0x84, 0x6c, 0x52, 0x2a, 0xce, 0xe5, +0xff, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0x0d, 0x70, 0xe5, 0xad, 0x65, +0xcc, 0xb0, 0x64, 0x74, 0xd3, 0x53, 0xb2, 0xd4, +0xa7, 0xdc, 0x21, 0xee, 0xed, 0xd0, 0x39, 0x5f, +0xaa, 0x62, 0xbf, 0xe5, 0xac, 0xc3, 0x67, 0x0b, +0x0c, 0xf8, 0x08, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0x59, 0x85, 0x67, +0xaa, 0x1c, 0x05, 0x21, 0xba, 0x4e, 0xbb, 0xcc, +0x1b, 0x70, 0x6d, 0xed, 0xc0, 0xfb, 0x29, 0x26, +0xb1, 0x18, 0xf6, 0x1e, 0x85, 0x7c, 0x1a, 0x49, +0xee, 0x8b, 0xf7, 0x66, 0x6e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0x3e, +0x5a, 0xd5, 0x13, 0xa7, 0xfe, 0xc1, 0xb4, 0x97, +0x2e, 0xc6, 0x76, 0x3f, 0xdf, 0x0b, 0x70, 0xaf, +0x74, 0xe3, 0x14, 0xa3, 0x61, 0xd0, 0x0d, 0x68, +0x0b, 0x2b, 0xdc, 0x4a, 0x34, 0x98, 0x0a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0xed, 0x46, 0x87, 0x31, 0xf2, 0xc9, 0xd8, +0xcf, 0xd3, 0x98, 0x94, 0x5e, 0xdf, 0xbe, 0x99, +0xc2, 0xb9, 0x16, 0x87, 0x1d, 0x4d, 0xa6, 0x2a, +0x74, 0xcb, 0x81, 0x63, 0x8c, 0x7f, 0xa0, 0x06, +0xa2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0xdd, 0x1b, 0x1b, 0x86, 0x9d, +0x54, 0x43, 0x08, 0x12, 0xfd, 0x70, 0x62, 0xbc, +0xb7, 0x70, 0xa6, 0xa4, 0x73, 0xff, 0x01, 0x58, +0x68, 0x5f, 0xb8, 0x5b, 0x20, 0x12, 0x36, 0x8e, +0x0c, 0x43, 0x8a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0xdb, 0x3e, 0xcf, +0x53, 0x74, 0x38, 0xcb, 0x02, 0x8e, 0xa2, 0x89, +0x3c, 0x3c, 0xab, 0x1f, 0xec, 0xfb, 0x53, 0x3b, +0x82, 0x6b, 0xa7, 0x99, 0x5e, 0x7b, 0x1b, 0x5f, +0xda, 0xd3, 0x10, 0xcd, 0x45, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0x10, +0x3e, 0xcb, 0x9c, 0xd4, 0xf4, 0x50, 0x74, 0x93, +0x3b, 0xa8, 0xb2, 0x18, 0xa3, 0x35, 0x8b, 0x6a, +0x29, 0x9d, 0x87, 0x27, 0x38, 0x64, 0x6f, 0x0f, +0x5e, 0xd3, 0xd0, 0xe8, 0x85, 0x06, 0x37, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0x9a, 0x62, 0x5d, 0x18, 0x6b, 0x81, 0x60, +0x06, 0xbb, 0xb5, 0xef, 0x6e, 0x57, 0xfb, 0xa5, +0x29, 0x12, 0x5c, 0x29, 0xd6, 0x81, 0x7b, 0xe2, +0x0b, 0x69, 0x06, 0x52, 0x63, 0x09, 0xac, 0x85, +0xb6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0x3f, 0xe2, 0x6f, 0xc6, 0xd3, +0xc9, 0x8c, 0xc7, 0xbe, 0x71, 0x61, 0xbc, 0x30, +0x77, 0x13, 0x10, 0x82, 0x0c, 0x22, 0x3a, 0xe1, +0xe0, 0x95, 0xe6, 0x02, 0x12, 0x0a, 0x20, 0xa1, +0x2e, 0x1f, 0xb4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0xe9, 0x12, 0x32, +0xd9, 0xf3, 0x38, 0xa9, 0x7a, 0x84, 0xa7, 0x52, +0x46, 0x47, 0x31, 0x21, 0xfe, 0x2e, 0x7b, 0x41, +0x7d, 0xfe, 0xb3, 0xb2, 0x60, 0x2c, 0xd9, 0xf7, +0xd6, 0xa0, 0x77, 0x08, 0x9d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x3a, +0x3a, 0xdb, 0x8a, 0x43, 0x91, 0x02, 0xe6, 0xa9, +0x2a, 0xbf, 0xeb, 0x3c, 0xe8, 0x37, 0x7d, 0xfb, +0x91, 0x18, 0x68, 0xc4, 0x20, 0xcc, 0xf5, 0x1c, +0x28, 0xc0, 0x56, 0x5f, 0x96, 0xe5, 0xb9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x87, 0xe4, 0xa9, 0xb9, 0xcb, 0x12, 0x32, +0x5a, 0x8f, 0x59, 0xe1, 0xcd, 0xae, 0xe8, 0xf7, +0x7b, 0x71, 0x7a, 0x92, 0x0f, 0x21, 0x54, 0x66, +0xf2, 0x78, 0x78, 0x81, 0x8a, 0x81, 0x4b, 0x00, +0x35, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0x36, 0x65, 0x24, 0xd7, 0xe2, +0x79, 0xb8, 0x3d, 0x72, 0xb6, 0x05, 0xca, 0x36, +0xaf, 0x43, 0x4d, 0xd2, 0xdb, 0xb0, 0x1d, 0x1b, +0xbd, 0x54, 0xdd, 0xb0, 0xaf, 0xe6, 0xc9, 0x5b, +0xc1, 0xbd, 0x7f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0x6b, 0x24, 0xd1, +0x29, 0xb9, 0x7b, 0x06, 0xe9, 0x50, 0x3d, 0x4a, +0xf7, 0x89, 0x31, 0xac, 0x5d, 0x37, 0x6c, 0x94, +0x41, 0xd5, 0xd6, 0xbc, 0x23, 0xa1, 0xc8, 0x58, +0xdd, 0x7e, 0x83, 0xd9, 0x44, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0xad, +0xd8, 0x3e, 0xff, 0xfa, 0x81, 0xb5, 0x76, 0x5d, +0x10, 0xb6, 0x49, 0xd7, 0x49, 0x83, 0x64, 0xdc, +0x41, 0x4b, 0x4f, 0x66, 0x50, 0x48, 0xc1, 0xf5, +0xff, 0xb6, 0x6f, 0x99, 0x13, 0xff, 0xc8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0x7a, 0x0e, 0xec, 0xb8, 0x1d, 0xfe, 0x05, +0xa5, 0xe2, 0x83, 0xb8, 0xaf, 0xe6, 0x9c, 0x7a, +0x22, 0xe5, 0x70, 0x0f, 0x05, 0x97, 0x14, 0x83, +0x0d, 0xef, 0xa6, 0xdc, 0x24, 0x8d, 0xea, 0xda, +0x5a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0x93, 0x7c, 0x57, 0x3f, 0xf2, +0x18, 0xcc, 0x72, 0x51, 0x49, 0x15, 0x47, 0xd1, +0xa9, 0xc1, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x85, 0x6a, 0x48, 0x7f, +0x3b, 0xfc, 0x6c, 0xc7, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0x5e, 0xbc, 0x6a, +0x23, 0x31, 0x33, 0x2a, 0x38, 0x5b, 0x3b, 0x46, +0xe7, 0x6b, 0xd6, 0xe1, 0x40, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x8f, +0x3a, 0x68, 0x60, 0xce, 0x39, 0x7f, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0x26, +0xd7, 0xdc, 0x3c, 0x85, 0xf8, 0xb3, 0x02, 0xff, +0x23, 0x41, 0x33, 0x8e, 0x9b, 0xae, 0x27, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x26, 0xdc, 0x71, 0x53, 0x60, 0x07, 0x30, 0xd7, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x00 +}; + diff --git a/include/linux/mfd/max77705_pass3.h b/include/linux/mfd/max77705_pass3.h new file mode 100644 index 000000000000..5990a1b35d5c --- /dev/null +++ b/include/linux/mfd/max77705_pass3.h @@ -0,0 +1,6634 @@ +const uint8_t BOOT_FLASH_FW_PASS3[] = { +0xc1, 0x66, 0xf1, 0xce, 0x2e, 0x00, 0x00, 0x00, +0x01, 0x22, 0x21, 0x70, 0x01, 0x02, 0x03, 0x04, +0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, +0x0d, 0x0e, 0x0f, 0x00, 0x2e, 0x00, 0x00, 0x00, +0x02, 0x00, 0x00, 0x00, 0xc3, 0xe2, 0xf2, 0xdd, +0x69, 0x69, 0x8b, 0x70, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0x20, 0xa9, 0xe8, +0x91, 0x2c, 0xdf, 0x69, 0xb7, 0x95, 0x9d, 0xa4, +0x69, 0x33, 0xed, 0x4a, 0x1d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xeb, 0xf6, +0xd5, 0xa0, 0x2b, 0x3e, 0x5d, 0x7a, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0x80, +0x3f, 0x65, 0x24, 0x5f, 0xea, 0x76, 0xa6, 0xd0, +0xe2, 0x6e, 0x40, 0xa5, 0x41, 0xd0, 0xbb, 0x42, +0xdf, 0xf1, 0x04, 0x4a, 0x73, 0x9e, 0x2a, 0x23, +0x59, 0x2f, 0x73, 0x7c, 0x38, 0xa5, 0x67, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0x52, 0x67, 0x18, 0xc4, 0x84, 0xdd, 0x42, +0x44, 0x10, 0x9e, 0x7e, 0x2e, 0x3f, 0x46, 0x8f, +0xb6, 0x7e, 0x68, 0x72, 0xbf, 0xcc, 0xf2, 0x33, +0xb2, 0x6a, 0x06, 0x42, 0x02, 0x10, 0x52, 0xde, +0x9e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0xf4, 0xb4, 0xba, 0xf9, 0xfc, +0xf7, 0x76, 0xdf, 0x64, 0x2e, 0x20, 0xa0, 0xc7, +0x7b, 0xed, 0xba, 0x68, 0x2b, 0xda, 0x06, 0xab, +0xc2, 0xe3, 0xf2, 0x20, 0x35, 0x4e, 0x0d, 0x1d, +0xb6, 0x1e, 0xa6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0x28, 0xe6, 0x76, +0x20, 0xce, 0x49, 0xb5, 0xc7, 0xe3, 0x91, 0x0d, +0x44, 0x89, 0x2f, 0x11, 0xf2, 0x36, 0xbc, 0xb7, +0xe9, 0xa7, 0xe4, 0xcd, 0x3d, 0x46, 0x50, 0x1b, +0xf9, 0xea, 0x0b, 0x9b, 0x61, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0xb2, +0x1f, 0x42, 0x97, 0xec, 0xc4, 0x9c, 0x9e, 0xe5, +0x19, 0x25, 0x7a, 0x16, 0x34, 0xa0, 0x03, 0x03, +0xcf, 0xd1, 0x24, 0x22, 0x24, 0x32, 0x56, 0x12, +0x6e, 0xa8, 0x42, 0x66, 0x42, 0xd7, 0x90, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0x7e, 0x74, 0x3e, 0x8e, 0x27, 0x8a, 0x35, +0xdc, 0x81, 0x99, 0x21, 0x58, 0x44, 0x25, 0x4a, +0x0c, 0xfc, 0x4a, 0x4e, 0xf3, 0xad, 0x51, 0x6d, +0x2a, 0x43, 0x8c, 0xa0, 0x3d, 0x7f, 0xcd, 0x38, +0x0a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0xa8, 0x5d, 0x88, 0x9c, 0xfc, +0xa2, 0x66, 0xb2, 0xe8, 0x89, 0xc5, 0x19, 0x38, +0xfb, 0x39, 0x23, 0xb7, 0x0f, 0xc4, 0x7a, 0x6d, +0x72, 0xec, 0x76, 0x65, 0x21, 0xee, 0x1a, 0x09, +0xfd, 0x6e, 0x69, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x91, 0x1c, 0x59, +0x29, 0x78, 0xbb, 0x1d, 0xe9, 0x0f, 0x2d, 0x75, +0xb5, 0x20, 0xa7, 0xad, 0xf8, 0x20, 0xca, 0xe9, +0xb9, 0x7b, 0x8a, 0x5d, 0xd3, 0x48, 0xc1, 0x86, +0x90, 0xcf, 0xdc, 0xc0, 0x47, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0x40, +0x14, 0x7b, 0xb5, 0xf7, 0xb6, 0x0e, 0x5d, 0x00, +0xd8, 0xdd, 0x26, 0x9b, 0x44, 0x1c, 0x08, 0x3c, +0x9d, 0x30, 0x27, 0x79, 0x00, 0xc6, 0xf5, 0x20, +0x06, 0x52, 0xc2, 0xd7, 0xbf, 0x5b, 0x5a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0xa3, 0x21, 0x54, 0xe2, 0xd7, 0xb5, 0x3c, +0x27, 0xe3, 0x16, 0x60, 0x6e, 0x52, 0x4c, 0xb3, +0xf4, 0xbd, 0x7a, 0xd3, 0xe6, 0x9d, 0x2f, 0x06, +0x76, 0xf9, 0xf6, 0x14, 0x6b, 0x8c, 0x43, 0x03, +0x39, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0x31, 0xf9, 0x70, 0x97, 0xb1, +0x5b, 0x6f, 0x79, 0x39, 0xc0, 0x0d, 0x88, 0x12, +0x39, 0x65, 0x2e, 0x5d, 0x7b, 0xb8, 0xd5, 0xc4, +0x24, 0x3a, 0x4b, 0x8c, 0x8d, 0xdc, 0x3b, 0x80, +0x24, 0x58, 0x78, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0x8a, 0x02, 0x68, +0xc2, 0x48, 0x38, 0x33, 0x0c, 0x02, 0x9a, 0x6f, +0xdb, 0x3d, 0xd0, 0xa6, 0xf4, 0xe5, 0xf4, 0xba, +0xd8, 0xd1, 0x76, 0xf8, 0x03, 0x65, 0xb5, 0x68, +0x0c, 0x3b, 0x01, 0x07, 0xc1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x66, +0x86, 0x5b, 0xd5, 0x42, 0xd2, 0x72, 0xea, 0x2f, +0xa0, 0x41, 0x5c, 0x85, 0x48, 0xf2, 0xe5, 0xbb, +0x77, 0x7e, 0x21, 0xab, 0x78, 0xb5, 0xe2, 0x97, +0x1c, 0x52, 0xd0, 0x2c, 0x9a, 0x9b, 0x9b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0x88, 0x78, 0x38, 0x21, 0x7f, 0xd5, 0x8c, +0x69, 0xad, 0x3e, 0x6f, 0x78, 0x01, 0xf5, 0x15, +0xcd, 0x72, 0xeb, 0x03, 0x98, 0x5c, 0xcc, 0xc4, +0xc8, 0xf3, 0xca, 0xac, 0xb8, 0x83, 0x2e, 0xea, +0xa6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0xae, 0x00, 0x50, 0x09, 0x0c, +0x72, 0xed, 0x79, 0xd9, 0x71, 0xf0, 0xd7, 0xb6, +0x6b, 0x88, 0x26, 0x7c, 0x4e, 0xeb, 0x3e, 0x66, +0x15, 0x27, 0x6b, 0x77, 0xe0, 0xec, 0x72, 0x7f, +0x4f, 0xe9, 0x51, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0xcc, 0x6f, 0x81, +0xec, 0x87, 0x97, 0x7f, 0xaf, 0x54, 0x60, 0xd8, +0xb1, 0xa1, 0xfe, 0x7b, 0x35, 0x03, 0xf2, 0xde, +0xa0, 0xae, 0x0f, 0x30, 0x57, 0x9f, 0x0b, 0xae, +0x03, 0x74, 0xc0, 0x22, 0xc9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x10, +0x18, 0x03, 0x1f, 0xc1, 0x25, 0xa6, 0x2c, 0x41, +0x63, 0x9b, 0x2d, 0x5e, 0x8e, 0xee, 0x36, 0xe8, +0xab, 0x5f, 0x4f, 0x05, 0x36, 0x33, 0x18, 0xc7, +0x21, 0x91, 0x60, 0x7a, 0x8c, 0x64, 0x14, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0xde, 0x4f, 0x0c, 0x46, 0x1b, 0x72, 0x26, +0x3c, 0x4f, 0x88, 0xf6, 0xbb, 0x1b, 0x94, 0x4f, +0xfa, 0xf1, 0xab, 0x05, 0xd4, 0xc3, 0x77, 0x49, +0x2e, 0x8e, 0xb0, 0x85, 0x3e, 0x7d, 0x9d, 0x41, +0x68, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0x2d, 0xb7, 0x03, 0x9a, 0x9a, +0x5b, 0xfe, 0xdc, 0xe2, 0xc5, 0x0e, 0x2b, 0x69, +0x22, 0x39, 0xd3, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x0f, 0xe4, 0xa1, 0x22, +0xde, 0xe5, 0xfb, 0xca, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0x2f, 0xc0, 0x6e, +0x9c, 0xfc, 0xc6, 0xe1, 0xbe, 0x2a, 0x22, 0x9e, +0x42, 0xae, 0x4a, 0xf2, 0x20, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0xaf, +0x5e, 0xa1, 0x0d, 0x0c, 0xcf, 0x7f, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x5c, +0x1a, 0x9d, 0x44, 0xe2, 0x58, 0xcb, 0xf6, 0xe5, +0x06, 0x03, 0xdc, 0x2d, 0x8c, 0xe3, 0x77, 0x12, +0xcb, 0xd3, 0xda, 0x40, 0x9f, 0x7b, 0xca, 0x72, +0x85, 0xe5, 0x4f, 0x43, 0x1b, 0x8a, 0xb9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0x58, 0x84, 0xef, 0x67, 0x05, 0x29, 0x71, +0xa0, 0x41, 0x05, 0xae, 0x39, 0x9e, 0x4d, 0x9e, +0x61, 0x56, 0x2a, 0x29, 0xfb, 0x7d, 0x1f, 0x8c, +0x49, 0x7b, 0xe7, 0x69, 0xc4, 0x7e, 0x3c, 0xc4, +0xb8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0x5e, 0x5e, 0x1a, 0x22, 0x39, +0xe5, 0x12, 0x33, 0x46, 0xef, 0xf1, 0x19, 0x6a, +0x9a, 0x46, 0xd7, 0x5e, 0x11, 0xec, 0x46, 0xc9, +0x4c, 0xbf, 0x10, 0x3e, 0x7d, 0x81, 0x9a, 0x0c, +0x2f, 0x32, 0x15, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0xa2, 0x84, 0x6f, +0x65, 0x5b, 0xc9, 0x02, 0x18, 0x33, 0x39, 0xf0, +0x75, 0x37, 0x4c, 0xfd, 0xb9, 0x66, 0x6c, 0x02, +0xcd, 0xf6, 0x28, 0x5f, 0xee, 0x3a, 0xaf, 0xed, +0xc2, 0xff, 0x7e, 0xa6, 0xb8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0x63, +0xf1, 0x08, 0x5c, 0xcf, 0x51, 0xf0, 0x03, 0x2a, +0xbc, 0xdf, 0x17, 0xef, 0x57, 0xfa, 0x3d, 0xc7, +0x71, 0x37, 0x95, 0x87, 0x25, 0x7d, 0x2b, 0x39, +0x9a, 0xfb, 0x5d, 0x97, 0xd7, 0xc9, 0x5d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0x96, 0x92, 0xa4, 0xb1, 0x7d, 0x5c, 0xde, +0x18, 0x39, 0x14, 0x31, 0x8f, 0x1f, 0x99, 0xba, +0xc2, 0x97, 0x33, 0x8a, 0xd3, 0xb5, 0x6b, 0x45, +0x60, 0x02, 0x0b, 0xe4, 0x0a, 0xde, 0xed, 0xac, +0xa0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0x3e, 0x92, 0x26, 0xa9, 0xb1, +0xc4, 0x82, 0x3c, 0x17, 0xe5, 0x83, 0x71, 0xce, +0xe6, 0x02, 0xcb, 0xed, 0xe8, 0x67, 0x31, 0x89, +0xc5, 0x5f, 0x23, 0xdc, 0x3c, 0x3c, 0xa7, 0xa8, +0xe0, 0xbe, 0x70, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0x61, 0xd6, 0x3c, +0xaf, 0xbf, 0x7f, 0x69, 0xf5, 0x29, 0x2e, 0x75, +0xdc, 0x6c, 0x60, 0xc0, 0x51, 0x58, 0xbe, 0x35, +0x4f, 0xf7, 0x81, 0xaf, 0x3b, 0xb5, 0xca, 0xbd, +0x10, 0x0e, 0x76, 0xda, 0xa3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0xf9, +0x6b, 0xa3, 0x5c, 0x5e, 0xd0, 0xc9, 0x91, 0x88, +0x37, 0x8c, 0xc8, 0xfe, 0x09, 0x98, 0x94, 0x4c, +0x3d, 0xe4, 0xb6, 0xc8, 0x5a, 0xce, 0xcd, 0xce, +0xe1, 0x6b, 0xc0, 0x1f, 0xf4, 0xc4, 0x26, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x56, 0x52, 0xa8, 0x64, 0xcf, 0x39, 0x58, +0x72, 0xf7, 0x0d, 0xfe, 0x25, 0x30, 0xaa, 0x7b, +0xde, 0x0e, 0x3a, 0xd0, 0xbe, 0xb1, 0xeb, 0x7e, +0xdd, 0x05, 0xb2, 0xe8, 0x33, 0xa9, 0xef, 0x61, +0x4a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x3a, 0x9e, 0x00, 0xba, 0xe9, +0xbc, 0x13, 0xa1, 0xa8, 0x77, 0xff, 0x48, 0xb5, +0x50, 0xeb, 0x67, 0x69, 0x0a, 0x5c, 0x0d, 0x31, +0x54, 0xc5, 0x5c, 0x4e, 0x84, 0x48, 0x86, 0xca, +0x24, 0x0d, 0x79, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0x75, 0x4b, 0x07, +0xa9, 0x9f, 0x5f, 0xcf, 0x51, 0x41, 0x99, 0xe0, +0x0a, 0xd5, 0x43, 0xe0, 0xc4, 0xd8, 0xbd, 0xdb, +0xab, 0x7d, 0x1d, 0x8e, 0x3a, 0x2e, 0x2d, 0xaf, +0x72, 0x25, 0xf2, 0x23, 0x13, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0xd8, +0xeb, 0xe0, 0xd3, 0xe5, 0xb9, 0x00, 0xa8, 0x99, +0xab, 0xdb, 0x8b, 0xa3, 0xab, 0xce, 0xdf, 0x95, +0xe6, 0x1e, 0xf0, 0x9e, 0x57, 0x76, 0x67, 0x22, +0x23, 0x90, 0x29, 0x05, 0x36, 0xe5, 0x50, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x88, 0xa4, 0xbe, 0x22, 0x8a, 0xef, 0xc4, +0xc2, 0x9e, 0x8a, 0x4d, 0xb4, 0x85, 0x71, 0x6e, +0x6e, 0x9a, 0xcb, 0x1f, 0xe8, 0x05, 0x49, 0xdc, +0x75, 0x50, 0xfb, 0x2c, 0xb8, 0x2d, 0xe3, 0x1d, +0x47, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0xc9, 0x31, 0xed, 0x61, 0x01, +0x69, 0x9d, 0xea, 0x51, 0xb6, 0x76, 0xbd, 0xa0, +0x7e, 0x8c, 0x6a, 0x42, 0x74, 0x3f, 0x66, 0xbb, +0xe6, 0xda, 0x4c, 0x41, 0x4d, 0xd1, 0x88, 0x68, +0x89, 0x2a, 0x1a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0xeb, 0x38, 0xf4, +0xad, 0xf4, 0xf3, 0xe4, 0xe1, 0xb6, 0x4b, 0x5b, +0x69, 0xb9, 0x2e, 0xb3, 0xe5, 0x7b, 0xf9, 0x12, +0x70, 0x82, 0x50, 0x9d, 0x52, 0x1d, 0x66, 0x4e, +0x1a, 0x7f, 0x8b, 0x12, 0x53, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x29, +0x04, 0xf4, 0xf4, 0x44, 0x16, 0xba, 0x6f, 0x3d, +0xa9, 0xa4, 0xe6, 0x6b, 0x12, 0xa5, 0xdb, 0x1c, +0x76, 0x77, 0x7e, 0xf3, 0x68, 0xea, 0x34, 0x8e, +0xf2, 0xa1, 0x35, 0x28, 0x5a, 0x63, 0x57, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0x3e, 0xda, 0xce, 0x5b, 0xd6, 0xca, 0x35, +0x3b, 0x04, 0xa7, 0x73, 0xaf, 0x10, 0xa0, 0x08, +0x21, 0x8a, 0xba, 0xc2, 0xb6, 0xd9, 0xe4, 0xbb, +0x1f, 0x95, 0x87, 0x72, 0x7a, 0xa1, 0x80, 0x7c, +0x7c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0x06, 0x4d, 0x77, 0x47, 0xc5, +0x0a, 0x5d, 0x53, 0x45, 0x36, 0xcd, 0x86, 0xf4, +0xed, 0xa6, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x34, 0xdf, 0x3b, 0xed, +0xab, 0x12, 0xfc, 0x23, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0xf6, 0xc0, 0xf5, +0x0c, 0x94, 0x08, 0xd7, 0x6e, 0x84, 0x74, 0x1e, +0xf3, 0x7d, 0xc2, 0x21, 0x60, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9e, 0x59, +0x35, 0x41, 0xd4, 0xce, 0x77, 0x53, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0x9d, +0x8c, 0xdb, 0xd3, 0xfc, 0xa6, 0x59, 0x4e, 0xe6, +0x0c, 0x99, 0x18, 0x34, 0x6a, 0x63, 0x3a, 0x9a, +0x2d, 0x0a, 0xe5, 0xf2, 0xe5, 0xc4, 0xe7, 0x15, +0xe9, 0x8f, 0xdb, 0x02, 0x19, 0x96, 0xdf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0x4a, 0xbb, 0x8c, 0x01, 0xe0, 0x0e, 0x81, +0xe1, 0xb2, 0xcb, 0x10, 0xde, 0x84, 0x77, 0x46, +0xbc, 0xf6, 0x2b, 0xb3, 0xa3, 0xb3, 0x16, 0x6f, +0x58, 0x3b, 0x62, 0x75, 0xbb, 0x77, 0xf2, 0x1f, +0x8a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0x1e, 0x14, 0x59, 0xb4, 0xab, +0x0e, 0x01, 0x08, 0xea, 0xf5, 0x01, 0xaa, 0x6e, +0x97, 0x5b, 0x32, 0x4d, 0x3f, 0xf7, 0xc5, 0x8b, +0x0b, 0x2a, 0xc8, 0xb1, 0x82, 0xd1, 0xa4, 0xbf, +0x45, 0x6d, 0x6d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0x03, 0xe1, 0x96, +0x3d, 0xfd, 0x14, 0x37, 0x4f, 0xbb, 0xe8, 0xa7, +0x08, 0x62, 0x81, 0x58, 0xbe, 0xed, 0xe8, 0x2b, +0x0f, 0xf4, 0x3b, 0x01, 0xf3, 0x97, 0xfb, 0xbb, +0x00, 0xc6, 0x7c, 0x8d, 0x02, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0x70, +0x51, 0x47, 0x9e, 0x16, 0x0d, 0x0e, 0x9c, 0xeb, +0x46, 0xd6, 0xdf, 0x1d, 0x8e, 0x95, 0xc8, 0x24, +0x6a, 0xf2, 0xae, 0x17, 0x9c, 0x42, 0xa2, 0x74, +0x10, 0x20, 0xe6, 0x7e, 0x40, 0xaf, 0x81, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0xe6, 0x85, 0x43, 0xbf, 0xc7, 0x50, 0x94, +0x76, 0x0f, 0x78, 0xda, 0xba, 0x33, 0x74, 0xe2, +0x39, 0x67, 0x6d, 0x4b, 0xb6, 0x36, 0xcb, 0x40, +0x5c, 0x64, 0xa5, 0x63, 0x12, 0x25, 0x83, 0xbc, +0x56, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0x7a, 0x43, 0xf6, 0x19, 0x58, +0xbc, 0xeb, 0xe3, 0xdc, 0x8f, 0x13, 0x29, 0xeb, +0xf9, 0xc9, 0xdd, 0x19, 0x2b, 0xfe, 0x28, 0xcb, +0xfd, 0xe8, 0xc1, 0x96, 0x14, 0xeb, 0x0d, 0x62, +0x5f, 0x73, 0x61, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0xeb, 0x30, 0xe7, +0x5b, 0xd5, 0x15, 0x81, 0x3e, 0xc1, 0xc7, 0x63, +0x6d, 0xf7, 0x5f, 0xbc, 0xa7, 0x4d, 0x57, 0xf6, +0x41, 0x10, 0x40, 0x77, 0x01, 0x57, 0x4c, 0xa4, +0xcb, 0x47, 0xbe, 0x29, 0xb9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0xf6, +0xe2, 0x41, 0x46, 0xf6, 0x1c, 0x0a, 0xff, 0x0e, +0x19, 0x25, 0xc2, 0x06, 0x58, 0x7f, 0x7d, 0x3f, +0x34, 0x32, 0x83, 0x9d, 0xcd, 0x97, 0xc4, 0x53, +0x09, 0xbb, 0x35, 0x46, 0xe1, 0x9d, 0xad, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0x40, 0xd5, 0xa4, 0x6e, 0x6f, 0x8e, 0x02, +0x2d, 0x36, 0xc1, 0xa3, 0x67, 0xc7, 0xcf, 0x6d, +0x69, 0xea, 0x8c, 0xdd, 0x46, 0x00, 0xf1, 0xec, +0x7e, 0x63, 0xce, 0xc5, 0x5d, 0x65, 0x3b, 0xfd, +0xcf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0xfd, 0xc9, 0xfa, 0xb3, 0x05, +0x24, 0x7e, 0x2e, 0x6c, 0x97, 0x56, 0x3f, 0xd3, +0x9e, 0x87, 0x5f, 0x08, 0xd1, 0xc3, 0x0f, 0xe7, +0x8b, 0x4f, 0xad, 0xc2, 0x57, 0xc2, 0x18, 0xcc, +0x91, 0x42, 0x7b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0x90, 0xb7, 0x4a, +0xcf, 0xa6, 0x75, 0xfc, 0x81, 0x09, 0x55, 0x09, +0xad, 0x07, 0x86, 0x15, 0xbe, 0x95, 0xc3, 0xe7, +0x70, 0x3d, 0xee, 0xa3, 0x9c, 0x91, 0x1b, 0x1d, +0xc7, 0xa1, 0xdd, 0x80, 0x84, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0xa1, +0x63, 0x56, 0xf9, 0xce, 0xcf, 0x7e, 0x83, 0x3a, +0x1f, 0xf1, 0x6f, 0x12, 0x8c, 0x2d, 0xa9, 0x2e, +0xd0, 0x07, 0xb8, 0x3d, 0xda, 0xa4, 0xe8, 0xe5, +0x8a, 0x0a, 0x2f, 0x2d, 0x77, 0x08, 0x43, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0x24, 0x97, 0x29, 0x7c, 0xb9, 0x18, 0xcf, +0x25, 0xa8, 0xcb, 0xc3, 0x3b, 0x44, 0x0a, 0x56, +0xc5, 0x7e, 0xad, 0xea, 0x36, 0x22, 0x5e, 0x54, +0x69, 0xc5, 0x55, 0x86, 0x2a, 0xe1, 0xba, 0xfe, +0xb7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0xae, 0x7f, 0xf3, 0x75, 0x2b, +0x6c, 0xee, 0x7e, 0x29, 0x76, 0xfa, 0xe8, 0xfe, +0x73, 0x66, 0x68, 0xc2, 0x6b, 0x8d, 0x4b, 0xc9, +0x12, 0xd7, 0xfc, 0x78, 0x10, 0x66, 0xbf, 0x83, +0x8d, 0xb9, 0xee, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0x49, 0xef, 0x71, +0x55, 0x6f, 0xaa, 0xcd, 0x54, 0x2c, 0x80, 0x29, +0xbf, 0xf6, 0x7f, 0x4a, 0x13, 0x0f, 0x83, 0x66, +0xac, 0x43, 0x3c, 0xaa, 0xd4, 0x03, 0xd0, 0xee, +0x14, 0x31, 0x16, 0xa4, 0x15, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0x1a, +0x81, 0x3f, 0x4f, 0x39, 0x3b, 0x07, 0xc2, 0x7c, +0xd5, 0xdd, 0x7e, 0xf5, 0x1a, 0x5d, 0xd0, 0xba, +0x5a, 0x7b, 0x6f, 0x0b, 0xa7, 0x6c, 0x90, 0xb6, +0xbb, 0x7a, 0x8d, 0xf1, 0x70, 0xa7, 0x54, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x56, 0x64, 0x2e, 0x20, 0xe0, 0x6d, 0x6c, +0xca, 0xa1, 0x84, 0x77, 0xc5, 0x0c, 0x52, 0xcd, +0x87, 0x94, 0x34, 0xdf, 0x6d, 0x9b, 0xd2, 0xbe, +0x1b, 0x6f, 0x57, 0x5e, 0xb3, 0x3e, 0xd0, 0x4b, +0x4a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0xe9, 0x91, 0x1d, 0x90, 0x62, +0x00, 0x50, 0xfa, 0x04, 0xa6, 0xd2, 0xd7, 0xf6, +0xe8, 0x37, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x60, 0xf0, 0xd0, 0x7b, +0xd8, 0xdd, 0xa2, 0xd4, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0x93, 0x3f, 0xb6, +0xaf, 0x15, 0x8b, 0x49, 0x1e, 0x61, 0xf6, 0xef, +0x47, 0x4d, 0x8a, 0x85, 0xe3, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdf, 0xc6, +0x8a, 0x03, 0x5f, 0xde, 0xa0, 0x47, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0xcf, +0xdd, 0x94, 0x94, 0x13, 0xb9, 0xa1, 0xf5, 0x5e, +0x98, 0xaa, 0xae, 0xe2, 0x6e, 0x45, 0x03, 0xe0, +0x51, 0xe7, 0x51, 0xa3, 0x10, 0xe4, 0x38, 0xbb, +0xca, 0xa1, 0xfc, 0x55, 0x6a, 0xcd, 0x98, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0x80, 0x52, 0xd4, 0x5a, 0x85, 0x0f, 0xcb, +0x20, 0x47, 0x9c, 0xb0, 0x0a, 0xf0, 0x29, 0x3f, +0x1b, 0x06, 0x16, 0xf1, 0xe9, 0x04, 0x3f, 0x7c, +0x65, 0xa3, 0x21, 0x8d, 0xc5, 0x1b, 0xe7, 0xbd, +0xa6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0xb9, 0x17, 0x30, 0xc3, 0x8a, +0x47, 0x8d, 0x6b, 0x53, 0x78, 0x6f, 0x73, 0x53, +0xed, 0xae, 0xd9, 0xe4, 0x8d, 0x40, 0x9c, 0x2b, +0x51, 0xfd, 0xd4, 0x0a, 0x5c, 0x78, 0xe1, 0x10, +0x8a, 0x06, 0x4d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x61, 0x97, 0xda, +0x04, 0xa9, 0x0a, 0xaf, 0xfe, 0xb3, 0xfa, 0xe3, +0x40, 0xe0, 0x54, 0x97, 0x04, 0xab, 0x97, 0xd3, +0x1f, 0xff, 0xc0, 0x09, 0x53, 0x0f, 0xeb, 0x40, +0xb4, 0x31, 0x37, 0x8f, 0x2d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0xde, +0x1e, 0x7f, 0x92, 0x76, 0xa9, 0x55, 0x1c, 0x82, +0x79, 0x72, 0x73, 0x80, 0xdb, 0x39, 0x5c, 0x17, +0x5a, 0xa8, 0x9d, 0x37, 0x9e, 0x33, 0x54, 0xa3, +0xa9, 0x0b, 0x76, 0x3c, 0xb6, 0x9a, 0x38, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0x4f, 0xd6, 0xd6, 0x67, 0xb8, 0xfa, 0xbd, +0x24, 0x48, 0xe3, 0xf5, 0x83, 0x3b, 0x80, 0xfe, +0xdb, 0x43, 0xe8, 0xf8, 0xa0, 0xfe, 0xcf, 0xd5, +0xc7, 0xa5, 0x06, 0x9a, 0x51, 0xa7, 0x37, 0xc5, +0xa5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0x88, 0xe6, 0xe8, 0x5c, 0xc5, +0xc8, 0xf5, 0x16, 0xcc, 0x7d, 0x7b, 0xb9, 0xa6, +0x8a, 0x6d, 0x84, 0xec, 0x13, 0xa9, 0x6c, 0x43, +0x0f, 0xb1, 0xff, 0x79, 0xa4, 0xb0, 0xda, 0x2a, +0x1c, 0x31, 0x5d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0xb0, 0x79, 0xde, +0x15, 0xc6, 0x8d, 0xdb, 0x3b, 0xf2, 0x7e, 0x04, +0x81, 0xbb, 0x5e, 0x1d, 0x40, 0xd0, 0x97, 0x9a, +0x25, 0xfc, 0xa5, 0x1e, 0x7d, 0x8c, 0x40, 0xb6, +0x73, 0xfb, 0x7f, 0x87, 0xd2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0x06, +0xc6, 0x72, 0xc2, 0x8b, 0x69, 0xbf, 0x01, 0x73, +0x80, 0xad, 0xb9, 0x2f, 0x4d, 0xb5, 0x41, 0x53, +0x50, 0x47, 0x6e, 0xc3, 0x39, 0xc8, 0x85, 0x60, +0x29, 0x6e, 0x45, 0x85, 0xa6, 0x95, 0x96, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0x13, 0x50, 0xcf, 0x53, 0x21, 0xb3, 0x0b, +0xb6, 0xb6, 0xfb, 0x11, 0x9b, 0xff, 0xa2, 0x10, +0xe0, 0x2f, 0x33, 0xb4, 0xe2, 0x5d, 0xed, 0xf9, +0x68, 0x9e, 0x5e, 0x10, 0x0c, 0xf9, 0x2f, 0xa0, +0xf7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0x79, 0xba, 0xcc, 0xfe, 0x0f, +0xb0, 0xc6, 0xa9, 0xf5, 0x73, 0x6c, 0x52, 0xb7, +0x0e, 0x3f, 0x01, 0x55, 0xaf, 0x97, 0x8e, 0x4f, +0xc8, 0x76, 0xce, 0xb9, 0x7e, 0x7e, 0xeb, 0x6c, +0x3b, 0x43, 0xac, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0xff, 0x2d, 0x00, +0x9c, 0xc1, 0xe9, 0x70, 0x70, 0x9b, 0x7e, 0xac, +0x4e, 0x73, 0x67, 0x00, 0x28, 0x9e, 0x01, 0x5d, +0xd8, 0xbb, 0x77, 0xa3, 0x6d, 0xc2, 0x68, 0xec, +0xe7, 0xeb, 0x3a, 0x57, 0x06, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0x33, +0x8d, 0x43, 0xfd, 0xa6, 0xae, 0x40, 0xe0, 0xe5, +0xdb, 0xbb, 0x88, 0xff, 0xc3, 0x2d, 0xb9, 0xc8, +0xe7, 0x28, 0xca, 0x60, 0xdd, 0xc1, 0x71, 0x45, +0xe9, 0x91, 0xee, 0x6c, 0xa2, 0xf3, 0x6d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0x21, 0xdd, 0x1f, 0x52, 0x5b, 0x40, 0x32, +0xa8, 0x7d, 0xa6, 0x48, 0xd3, 0x44, 0xe8, 0x9d, +0xcf, 0x32, 0xc7, 0xa3, 0xd3, 0x32, 0x9e, 0x1c, +0x11, 0x47, 0xed, 0x74, 0xc7, 0x10, 0xd3, 0xe6, +0x8e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0x5f, 0x7b, 0x2e, 0x81, 0x75, +0x05, 0xff, 0xd1, 0x15, 0xcf, 0x6e, 0xbf, 0x12, +0x1e, 0xdc, 0x53, 0xa3, 0xc6, 0x72, 0x5c, 0x0e, +0x1f, 0x51, 0x36, 0xc1, 0xb4, 0x9d, 0x07, 0x11, +0x45, 0xd2, 0x3d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x9d, 0x18, 0x08, +0x68, 0xa0, 0x60, 0xf9, 0xe8, 0x25, 0xb5, 0x3a, +0x37, 0x61, 0x2f, 0x50, 0x8e, 0x21, 0xcd, 0x8e, +0xa5, 0xd3, 0x81, 0x4d, 0xdf, 0x9e, 0x4c, 0x6b, +0xbf, 0x45, 0x45, 0xda, 0xdd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0x56, +0x38, 0x86, 0x81, 0x34, 0xde, 0x57, 0x17, 0x5a, +0x95, 0x7b, 0x2c, 0x1c, 0x0e, 0x4a, 0xe1, 0x7c, +0x08, 0xeb, 0x1a, 0xba, 0x5b, 0xfd, 0x49, 0x43, +0xa0, 0xc4, 0xaa, 0x70, 0xcd, 0xee, 0xd8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0x6a, 0x84, 0xcc, 0xc1, 0xdc, 0x50, 0x45, +0x71, 0x26, 0x69, 0xcc, 0x03, 0xcf, 0xdb, 0x02, +0x32, 0x5e, 0x60, 0x76, 0xf3, 0x96, 0x35, 0xb7, +0x6b, 0xa0, 0xd9, 0xa4, 0xdb, 0x97, 0xae, 0x1d, +0xb8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0xa6, 0x23, 0x4a, 0x9a, 0x13, +0x01, 0xc1, 0xd5, 0xda, 0x61, 0xf9, 0x7f, 0x40, +0x41, 0x94, 0x97, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x16, 0x42, 0x0c, 0xad, +0xcf, 0x2a, 0xa8, 0x0b, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x43, 0x22, 0x2b, +0x7e, 0xc0, 0xb5, 0x4d, 0x89, 0x7f, 0xe2, 0x60, +0xa4, 0x69, 0x9e, 0x4f, 0x98, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0x29, +0xf2, 0x30, 0x62, 0xda, 0x9a, 0x6d, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0xeb, +0x92, 0xbf, 0xe4, 0x0c, 0x26, 0x19, 0x28, 0xbc, +0xae, 0xed, 0x3c, 0x20, 0x16, 0x27, 0x9d, 0x94, +0x03, 0x2f, 0xa1, 0x0e, 0x10, 0x7e, 0xb0, 0x55, +0xc4, 0x6e, 0xde, 0x3e, 0xd9, 0x93, 0x48, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0x80, 0xda, 0x96, 0xe1, 0x10, 0x15, 0xf9, +0xc8, 0x81, 0x57, 0x39, 0x5a, 0x9c, 0x51, 0xc6, +0x53, 0x2e, 0x13, 0xd6, 0xd8, 0x94, 0x8b, 0x1e, +0xb9, 0xbb, 0x68, 0x2c, 0x25, 0x3e, 0xe4, 0x9e, +0x26, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0x72, 0x51, 0xb3, 0xbe, 0xae, +0x26, 0xea, 0xc2, 0x64, 0xd0, 0x20, 0x21, 0xb2, +0x23, 0x2c, 0xe8, 0xc2, 0x98, 0x92, 0xa9, 0xcb, +0x31, 0x6e, 0x1f, 0x31, 0x77, 0x8e, 0x98, 0x77, +0x3e, 0x4c, 0x96, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0x3b, 0xbf, 0x15, +0x5d, 0x1f, 0x06, 0x9e, 0x29, 0x12, 0x19, 0x6f, +0x04, 0x32, 0x3e, 0x96, 0xe7, 0x57, 0x36, 0xb5, +0x76, 0x38, 0xbf, 0x31, 0x6d, 0xd7, 0xc9, 0x7e, +0xcd, 0x23, 0x7b, 0xf1, 0x08, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x82, +0xdb, 0xe4, 0xdb, 0x50, 0xf4, 0x4d, 0x0c, 0x73, +0xc2, 0x45, 0x0a, 0xf9, 0x62, 0xf7, 0x45, 0x91, +0x77, 0x4b, 0x30, 0x94, 0x30, 0x83, 0xe3, 0xbc, +0x20, 0xaf, 0xfd, 0xb0, 0x2e, 0xf0, 0x3c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0xd4, 0xf9, 0x8a, 0xae, 0xb5, 0x9b, 0x10, +0xdc, 0xc6, 0x9e, 0x07, 0xeb, 0xd7, 0x6b, 0xac, +0x0f, 0x49, 0x25, 0xcf, 0x8b, 0x0f, 0x80, 0x10, +0xfb, 0xfe, 0x62, 0x2e, 0x61, 0x1d, 0xe9, 0xa2, +0x49, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0x5f, 0x81, 0x49, 0x75, 0x7e, +0x14, 0xd7, 0xd6, 0x96, 0x29, 0x18, 0xdc, 0x90, +0x78, 0x31, 0x22, 0x13, 0x91, 0x8e, 0x89, 0xdc, +0x5c, 0x2c, 0xfe, 0x8f, 0x72, 0xdf, 0xc8, 0x57, +0x73, 0x09, 0xf5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0x72, 0x6d, 0x96, +0xa4, 0xdb, 0x6d, 0x93, 0x3e, 0x15, 0x6c, 0xb7, +0x7c, 0x17, 0x22, 0x42, 0x37, 0xd2, 0xbf, 0x62, +0x7d, 0x83, 0x0c, 0x3f, 0x3f, 0x3d, 0x27, 0x02, +0x38, 0x85, 0x81, 0x73, 0x51, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x0d, +0x5f, 0x00, 0xc0, 0xc5, 0x5c, 0x5f, 0xd4, 0xbc, +0x75, 0x01, 0xed, 0x72, 0x4b, 0x53, 0xe8, 0xf6, +0x22, 0x58, 0xd8, 0x99, 0xe5, 0x7c, 0x07, 0x92, +0x63, 0xcb, 0x0a, 0x5d, 0xcc, 0xf9, 0x06, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0xfe, 0xdf, 0xc0, 0xc3, 0xdc, 0xd3, 0x90, +0x30, 0x80, 0x38, 0x99, 0x4c, 0xee, 0xf7, 0xd1, +0xbd, 0xa7, 0xc5, 0x61, 0xa2, 0xcb, 0xd9, 0x4a, +0x9c, 0x25, 0x2b, 0x25, 0x65, 0xe7, 0xf8, 0x0b, +0x1e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0xca, 0xff, 0x09, 0x6d, 0xfa, +0x34, 0x8d, 0x2f, 0xf6, 0x14, 0xe4, 0xe4, 0xcc, +0x81, 0x16, 0x8b, 0x7a, 0xb3, 0x7c, 0xc2, 0xab, +0x71, 0x58, 0x61, 0x9e, 0x02, 0xa2, 0xf5, 0xd5, +0x7e, 0xde, 0xc0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0xc5, 0xca, 0xd9, +0xcd, 0x98, 0xed, 0x06, 0x69, 0xd4, 0xbf, 0x15, +0xbf, 0x02, 0x6b, 0xc4, 0x2d, 0x70, 0x2d, 0xb2, +0xdb, 0x51, 0x34, 0x49, 0xc2, 0x80, 0x9a, 0x37, +0x8e, 0x71, 0x87, 0xb4, 0x17, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0x75, +0x73, 0x83, 0x13, 0x7c, 0x13, 0x45, 0xe3, 0xa4, +0x22, 0xb4, 0x54, 0x73, 0xae, 0x39, 0xe1, 0x7d, +0x8a, 0xf6, 0x17, 0xcc, 0xf9, 0xca, 0xe5, 0x55, +0x50, 0x73, 0xc7, 0x14, 0xed, 0xd0, 0xdd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0x1b, 0x4f, 0x58, 0x13, 0xf4, 0x0f, 0x16, +0x98, 0x32, 0x9c, 0xdf, 0xcf, 0x2c, 0x11, 0xdc, +0x71, 0xdd, 0xdc, 0x34, 0x88, 0x98, 0x5a, 0x3f, +0x68, 0xcc, 0x3f, 0x27, 0x12, 0xc4, 0xf5, 0xc2, +0xb5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x86, 0x1e, 0xa1, 0x1a, 0x3c, +0xee, 0x80, 0x52, 0xf9, 0x89, 0x5e, 0x07, 0xcf, +0xe2, 0x17, 0x8d, 0x84, 0x56, 0x5d, 0xdc, 0xae, +0x71, 0x8a, 0x03, 0xc4, 0xa5, 0x32, 0x56, 0xc8, +0x25, 0x6d, 0x0f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x39, 0x80, 0x16, +0x85, 0x68, 0x78, 0x3a, 0xd0, 0xf6, 0x1a, 0xdd, +0xac, 0xf3, 0x0c, 0x45, 0xe2, 0xbd, 0x9e, 0xe3, +0xe5, 0x68, 0xf1, 0xdb, 0x0a, 0x05, 0x27, 0xe6, +0xf0, 0x0c, 0x42, 0xef, 0x68, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0xc1, +0x5e, 0xc7, 0x7e, 0x43, 0x11, 0xf0, 0x86, 0x09, +0xd0, 0x08, 0x24, 0xa6, 0x7a, 0x61, 0x9e, 0xb8, +0xb9, 0xc9, 0x8c, 0x84, 0x12, 0x5a, 0x35, 0xcd, +0xe0, 0xb4, 0xde, 0xe3, 0x79, 0x29, 0x8b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0xcd, 0xe6, 0xec, 0x70, 0xcc, 0x79, 0xf2, +0x35, 0xc0, 0xd9, 0x14, 0x84, 0x29, 0xdd, 0x4f, +0x22, 0xf5, 0x5b, 0x77, 0xa9, 0xdd, 0x9a, 0x58, +0xb7, 0x9b, 0xd3, 0x23, 0x30, 0xd2, 0x7d, 0x51, +0x05, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0x2d, 0x5e, 0x78, 0x55, 0x53, +0x5d, 0xd1, 0xd8, 0x06, 0x4c, 0x95, 0xb1, 0x33, +0xfe, 0xe8, 0x85, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xd6, 0x28, 0xf0, 0x1b, +0x4f, 0x17, 0x98, 0xf7, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0xb1, 0x8c, 0x77, +0xc9, 0xd3, 0x7e, 0xd0, 0xf8, 0xb8, 0x10, 0xec, +0x6d, 0x22, 0x95, 0x89, 0x45, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x92, 0x3c, +0xe7, 0x2e, 0x8e, 0x81, 0x2b, 0x9d, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0xd0, +0x1a, 0x6e, 0x5a, 0x95, 0x2e, 0x99, 0xb6, 0x08, +0x12, 0xaf, 0xe1, 0xaf, 0x3a, 0x35, 0xb2, 0x26, +0xad, 0x11, 0x9d, 0x4e, 0xe2, 0x5c, 0x1e, 0x02, +0xae, 0xc1, 0x13, 0xfe, 0xbc, 0x61, 0x85, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0xa7, 0xc1, 0x3e, 0x19, 0x62, 0x11, 0x5c, +0xa1, 0x75, 0x36, 0x4a, 0xcf, 0xf8, 0xb4, 0xc6, +0x83, 0x4f, 0x29, 0x0f, 0xed, 0xfc, 0xe5, 0xfe, +0x7f, 0x6d, 0x86, 0x3c, 0x90, 0xa2, 0xfc, 0x7e, +0xeb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0x6f, 0xb0, 0x2a, 0x0d, 0x7d, +0x85, 0x6a, 0x43, 0xae, 0x36, 0xc2, 0x2e, 0xb2, +0xad, 0x4e, 0x34, 0xc6, 0xb1, 0x15, 0xd8, 0x55, +0x3e, 0xde, 0xb4, 0x19, 0x24, 0x80, 0x05, 0x71, +0xb7, 0xf1, 0xd2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0x8d, 0x0a, 0xb7, +0xf7, 0x13, 0x3d, 0x42, 0x59, 0x30, 0x07, 0x57, +0xd1, 0xcb, 0xcb, 0x2f, 0xb0, 0x92, 0xa1, 0x44, +0xe7, 0xcc, 0x2a, 0xbe, 0x6e, 0x8a, 0x4f, 0x04, +0x6f, 0x11, 0x20, 0x6c, 0x5e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x99, +0x07, 0x71, 0xfc, 0x3d, 0xd3, 0x9e, 0x03, 0xb8, +0xac, 0x49, 0x6b, 0xb0, 0xda, 0xb9, 0x6f, 0xe2, +0xe4, 0xb5, 0x21, 0x2a, 0xef, 0xe7, 0xf0, 0x3e, +0xee, 0x33, 0xd1, 0x9d, 0xb6, 0xda, 0x27, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0xb3, 0xc3, 0x75, 0xb9, 0x1d, 0x9b, 0xc7, +0xec, 0x13, 0xe5, 0x66, 0xee, 0xfb, 0x76, 0x23, +0x15, 0xe3, 0x23, 0x30, 0xdc, 0x47, 0x5a, 0x67, +0x42, 0x4a, 0x19, 0x42, 0xb1, 0x7e, 0x52, 0x8b, +0x53, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0x8d, 0xa1, 0x3e, 0x59, 0x1d, +0xde, 0xf4, 0xb4, 0x7c, 0x71, 0x57, 0xe8, 0xb9, +0xfa, 0x00, 0x43, 0xa8, 0xc0, 0xc9, 0xd8, 0x52, +0x7a, 0xdf, 0x58, 0x8b, 0x69, 0x30, 0x35, 0x40, +0x53, 0x02, 0x92, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0x90, 0x30, 0x8e, +0x22, 0x5d, 0x1e, 0x8e, 0x31, 0x58, 0x55, 0xb6, +0x35, 0x68, 0xac, 0xe5, 0xa2, 0x10, 0x1f, 0x9b, +0x87, 0xea, 0xa1, 0xfa, 0x6e, 0x11, 0x4c, 0x5a, +0xf7, 0xb6, 0x86, 0x5c, 0x8b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0x4a, +0x01, 0xa8, 0x73, 0xc5, 0xbe, 0x73, 0x93, 0xa0, +0x3d, 0x1d, 0xbf, 0xa1, 0xfe, 0x69, 0xff, 0x81, +0x04, 0x33, 0xd5, 0x40, 0xf8, 0xbf, 0x40, 0xc1, +0x7e, 0x56, 0xea, 0xc7, 0x8b, 0x48, 0xc0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0x26, 0x4a, 0x56, 0x93, 0x14, 0x20, 0x78, +0x14, 0x7a, 0x84, 0x2f, 0x28, 0x7f, 0x1b, 0x89, +0x8e, 0xb0, 0x26, 0x80, 0x89, 0x6c, 0xb9, 0x46, +0x5b, 0x6d, 0x49, 0xa3, 0xd6, 0x28, 0xf5, 0xed, +0x52, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x07, 0xd5, 0x08, 0xde, 0x8f, +0xba, 0x62, 0xbf, 0xc4, 0xc8, 0x90, 0xb0, 0xd1, +0xec, 0x5c, 0x6d, 0xf1, 0xa7, 0x31, 0xf9, 0x4c, +0x53, 0x5f, 0x5a, 0xec, 0x69, 0xdc, 0xab, 0x16, +0xe0, 0xda, 0x9d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0x50, 0x06, 0xb2, +0xfc, 0xbc, 0xc7, 0x99, 0xec, 0x70, 0xcb, 0x54, +0x88, 0x40, 0x21, 0x94, 0xe9, 0xbd, 0x79, 0x58, +0xe2, 0xa3, 0x5d, 0x41, 0x59, 0x18, 0xc5, 0x3a, +0xd1, 0x03, 0xb5, 0xd0, 0x23, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0xa4, +0xd2, 0x23, 0x87, 0x15, 0x5d, 0x91, 0xec, 0xd2, +0xd7, 0x0a, 0x85, 0xc3, 0x84, 0x10, 0xfa, 0x74, +0x4c, 0x05, 0x3f, 0x3f, 0x78, 0x9a, 0x55, 0xe2, +0x31, 0x1f, 0x7c, 0x53, 0x09, 0xe8, 0xfd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0x8b, 0x48, 0x9d, 0x54, 0x93, 0x03, 0xdc, +0x21, 0xf4, 0xf5, 0x4f, 0x3e, 0xf4, 0xff, 0x76, +0xa8, 0x8d, 0xd5, 0x96, 0x56, 0xd2, 0x36, 0xfd, +0x71, 0x94, 0x08, 0x17, 0xba, 0x5a, 0x41, 0xb1, +0x96, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0x96, 0xf2, 0xc7, 0x84, 0x0e, +0x45, 0x7c, 0x93, 0x82, 0x75, 0x60, 0x20, 0xa5, +0xa0, 0x3b, 0x6c, 0x4e, 0x03, 0x62, 0x94, 0x58, +0xca, 0x93, 0xd1, 0xc6, 0xde, 0x7c, 0xc8, 0x4a, +0x9a, 0xdd, 0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0xb0, 0x69, 0xfa, +0x52, 0x76, 0xbe, 0x5c, 0xbe, 0x63, 0x21, 0xdb, +0x15, 0xe8, 0x2c, 0x75, 0x7d, 0xb7, 0x57, 0x32, +0x85, 0x46, 0x0c, 0x9c, 0x47, 0x75, 0x9e, 0x4f, +0xe4, 0x9a, 0x79, 0x03, 0x08, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0xe1, +0xce, 0x57, 0xdc, 0xdb, 0x8d, 0x7c, 0xd9, 0xfe, +0xbd, 0xd7, 0x64, 0x39, 0xca, 0x1b, 0x14, 0x5a, +0x68, 0xf4, 0x4e, 0x2a, 0x92, 0x5b, 0x8f, 0xe2, +0x47, 0xcb, 0xb1, 0x46, 0x83, 0x9e, 0x9d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0x11, 0x43, 0x90, 0x68, 0x35, 0x6b, 0xdd, +0xb0, 0x6d, 0x56, 0xf3, 0x3c, 0xff, 0xf8, 0xa8, +0x0b, 0x4c, 0xdd, 0xd7, 0x12, 0xf5, 0x84, 0x61, +0x5f, 0xa2, 0x0e, 0x90, 0xa9, 0xb4, 0x1d, 0xc3, +0x24, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0x0b, 0x21, 0x17, 0x11, 0x6f, +0x5a, 0x3b, 0x62, 0xa1, 0x00, 0xd4, 0x99, 0x0b, +0x41, 0xf5, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xce, 0xb0, 0x68, 0xe4, +0x86, 0x41, 0xb7, 0x69, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0xb0, 0x13, 0xe3, +0x60, 0x36, 0x43, 0x01, 0xb1, 0xdb, 0x16, 0x0a, +0x84, 0xed, 0x0f, 0x6d, 0xdb, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0x4f, +0x46, 0xa0, 0x7d, 0x35, 0x6d, 0x75, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0xe8, +0x02, 0x51, 0xab, 0x88, 0x7e, 0x2f, 0x2f, 0x7a, +0xa0, 0xee, 0x8f, 0x34, 0xde, 0x3a, 0x61, 0x8b, +0x57, 0x09, 0x43, 0xbd, 0xe0, 0xbd, 0xbf, 0x54, +0xc8, 0x64, 0xa4, 0x3c, 0xf9, 0xaa, 0x29, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0x41, 0xa4, 0xcb, 0xc6, 0x28, 0xe0, 0x1c, +0xf4, 0x13, 0x63, 0x20, 0x97, 0x54, 0x25, 0x95, +0xbc, 0x62, 0xec, 0x52, 0xc3, 0x8b, 0xb7, 0xa0, +0x7e, 0xcb, 0x6c, 0x82, 0x86, 0xe1, 0x4e, 0x75, +0x9b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0x4a, 0x1a, 0x7e, 0x19, 0xd9, +0xba, 0x64, 0xe0, 0x29, 0x52, 0xd7, 0x74, 0xb9, +0xf1, 0x4e, 0xd7, 0x0e, 0x61, 0x6b, 0x9a, 0x74, +0x16, 0x6f, 0x17, 0xc1, 0xb8, 0xd0, 0x91, 0xb9, +0xac, 0x5f, 0x24, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0xeb, 0xb6, 0x40, +0x98, 0x78, 0x39, 0xab, 0x74, 0x02, 0x90, 0xd2, +0x51, 0x04, 0xc8, 0x12, 0x73, 0xbe, 0x2a, 0xb8, +0x13, 0x30, 0x4a, 0x8d, 0xda, 0xc8, 0x46, 0xda, +0xff, 0xdb, 0xc2, 0xac, 0x76, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0x37, +0x8e, 0x0b, 0x68, 0xae, 0x21, 0x2b, 0x6a, 0x66, +0x1d, 0xde, 0x79, 0xb3, 0xb1, 0x9b, 0xb5, 0x13, +0x95, 0x0d, 0x3f, 0x45, 0xfe, 0x90, 0x96, 0x11, +0x41, 0x75, 0x7c, 0x4b, 0xba, 0xc3, 0x8d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0xd4, 0x5c, 0x1c, 0xa1, 0xe4, 0x30, 0x44, +0x0d, 0xe4, 0x9b, 0xc6, 0x5a, 0xa5, 0x71, 0x70, +0xbb, 0xf3, 0xbb, 0x5f, 0x26, 0xed, 0xde, 0x6f, +0xf5, 0x66, 0xda, 0xbb, 0x9e, 0x59, 0x7a, 0xb2, +0xcf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0x9d, 0x33, 0x37, 0xc0, 0xea, +0x28, 0x5e, 0x38, 0x6d, 0x7f, 0x19, 0x9b, 0x7e, +0x37, 0x14, 0x58, 0x93, 0xc3, 0x10, 0xc0, 0x89, +0xb9, 0xed, 0x26, 0xf2, 0x3b, 0xfd, 0xbe, 0x2e, +0x3b, 0x36, 0x9c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0xd4, 0x84, 0x71, +0x69, 0x1b, 0xcd, 0xc5, 0xff, 0x1e, 0x6b, 0x60, +0x1c, 0xcb, 0xf1, 0xd1, 0xf5, 0xb6, 0x3a, 0x55, +0x60, 0x50, 0x96, 0xe3, 0x20, 0x78, 0x28, 0x47, +0x64, 0x0c, 0xa0, 0x95, 0xcc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0x2b, +0x95, 0xdb, 0x88, 0x64, 0x21, 0xc8, 0x6a, 0x0c, +0xbc, 0xcd, 0x83, 0x91, 0xe4, 0x45, 0x4c, 0x82, +0x73, 0x98, 0x9d, 0xd0, 0x93, 0xac, 0x94, 0x9e, +0x0f, 0x9d, 0x15, 0x73, 0xbe, 0x8d, 0x7b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0x0c, 0x59, 0x87, 0xbc, 0x95, 0x70, 0x97, +0x08, 0x62, 0x6f, 0x64, 0x99, 0x2e, 0xda, 0x9d, +0xce, 0xe6, 0x66, 0x81, 0xb9, 0x1e, 0x0f, 0xa5, +0x26, 0xa2, 0xee, 0xed, 0x30, 0x5c, 0xf2, 0x93, +0x2d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0x49, 0x1a, 0xd4, 0x3b, 0xf6, +0xf5, 0xcd, 0xae, 0x11, 0xb6, 0x29, 0xe3, 0xed, +0x94, 0xeb, 0xa9, 0xc8, 0xce, 0x4a, 0x0d, 0x9a, +0xaa, 0xe7, 0xb7, 0xd1, 0x5f, 0x08, 0x78, 0x2e, +0x80, 0xd4, 0xbf, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x17, 0x72, 0xf5, +0xa5, 0x90, 0x91, 0xfa, 0x9a, 0x70, 0x15, 0x84, +0x05, 0xff, 0xe1, 0xe9, 0x7f, 0x7b, 0x8d, 0xea, +0x1d, 0xf7, 0x77, 0x78, 0xc1, 0x62, 0xdb, 0xab, +0x1a, 0x2a, 0xf9, 0x51, 0xfd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0xf9, +0x5c, 0xff, 0xab, 0x22, 0xad, 0xb6, 0xcc, 0x4e, +0xb2, 0xf5, 0x0c, 0x5a, 0x7d, 0x4b, 0xc5, 0xd2, +0xae, 0x18, 0x51, 0x81, 0xb2, 0x53, 0xeb, 0x40, +0xcc, 0xee, 0xcb, 0x7a, 0xac, 0xd2, 0x75, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0xa8, 0x6e, 0xd5, 0xc0, 0xd5, 0x13, 0x66, +0x2b, 0xcc, 0xbf, 0x65, 0x74, 0x01, 0x30, 0x88, +0x96, 0xc0, 0xf9, 0x36, 0x26, 0x25, 0xce, 0x9c, +0xce, 0x1f, 0xf8, 0xb5, 0xc7, 0xcb, 0x52, 0x54, +0x5a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0x57, 0x2f, 0x84, 0x84, 0x49, +0x07, 0xdc, 0x47, 0xd7, 0xd0, 0x64, 0xca, 0x5e, +0xf9, 0x5b, 0xe0, 0x56, 0x37, 0x45, 0x93, 0x50, +0x8f, 0xb9, 0x5f, 0xbd, 0x75, 0x90, 0x0d, 0x58, +0xd0, 0x79, 0xd4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0x8c, 0x01, 0xab, +0x98, 0xf8, 0x0f, 0x05, 0xc8, 0x63, 0x14, 0x8a, +0x0f, 0x32, 0x09, 0xa8, 0xa5, 0x4d, 0xd5, 0xb1, +0x9b, 0x8d, 0xb3, 0x81, 0xaa, 0x49, 0x55, 0xa3, +0x62, 0x3b, 0xed, 0xde, 0xd8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0xa2, +0x68, 0x0f, 0x1a, 0x9f, 0xb0, 0x17, 0x2f, 0x93, +0x2e, 0x6d, 0xd3, 0xcf, 0xb5, 0x9f, 0xc1, 0x49, +0x55, 0x19, 0x2d, 0xae, 0xbd, 0xa4, 0x94, 0x4d, +0x6e, 0x3c, 0xc1, 0x7d, 0x84, 0xbc, 0x79, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0x45, 0x9a, 0x6c, 0xb1, 0x14, 0xfb, 0xd0, +0x50, 0xa9, 0xf3, 0x05, 0x8b, 0xa7, 0x87, 0x35, +0xf4, 0x8b, 0x72, 0x5f, 0x04, 0x72, 0xa9, 0x9c, +0x1f, 0x8b, 0x1f, 0x96, 0xd9, 0x2e, 0x5c, 0x58, +0x42, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0x07, 0x42, 0x8f, 0x78, 0x32, +0x7a, 0xbb, 0x39, 0x9f, 0x4e, 0x4c, 0x5b, 0x9d, +0x06, 0x33, 0xde, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xa8, 0x8d, 0x01, 0x20, +0xd5, 0x91, 0x9a, 0xff, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x83, 0x46, 0xbc, +0xf8, 0xee, 0xf9, 0x76, 0xa7, 0x25, 0x60, 0x32, +0x63, 0xd3, 0x8e, 0xa6, 0x3b, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0x61, +0xdd, 0x63, 0x51, 0xaa, 0x8a, 0x0d, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x4e, +0xc2, 0x79, 0xca, 0x4c, 0x31, 0xd8, 0x38, 0x28, +0x72, 0x2f, 0x68, 0x53, 0xa4, 0x10, 0xdd, 0xe5, +0x62, 0xd0, 0x46, 0xd0, 0xab, 0x02, 0x92, 0x45, +0xbb, 0x2b, 0x81, 0xdc, 0xad, 0x4d, 0x6c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0xce, 0x72, 0x37, 0x3d, 0x20, 0xe4, 0x31, +0xc0, 0x0d, 0x3e, 0x6c, 0xaa, 0x4a, 0x2f, 0x69, +0xbe, 0x74, 0xec, 0x43, 0x90, 0xf7, 0x1f, 0x4c, +0x39, 0x55, 0x90, 0x5f, 0x82, 0x26, 0x17, 0x30, +0x8d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0x32, 0x03, 0xd9, 0x98, 0xf2, +0xc7, 0xa3, 0x3a, 0xaf, 0x71, 0x92, 0xe3, 0x61, +0x60, 0x38, 0xad, 0x03, 0x24, 0xcd, 0x43, 0x13, +0xa4, 0xff, 0xaf, 0x07, 0x36, 0xea, 0x2f, 0x84, +0x22, 0x7f, 0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0x5a, 0xd3, 0x75, +0xc7, 0xdd, 0x08, 0x02, 0x74, 0x2c, 0xfd, 0x87, +0x60, 0x1b, 0xc6, 0x5e, 0xd5, 0x93, 0x0c, 0x53, +0x3f, 0x78, 0xa4, 0xb3, 0x4b, 0xf8, 0xe2, 0x42, +0xd2, 0x63, 0x42, 0x7e, 0x3f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0xff, +0x33, 0x2f, 0x66, 0xcf, 0x6e, 0x91, 0xc4, 0xa6, +0xb1, 0x01, 0x84, 0x78, 0x9c, 0x6f, 0x58, 0x9b, +0x1f, 0xdb, 0xe4, 0xbd, 0x23, 0xc1, 0x4b, 0x03, +0xf5, 0xc1, 0x8f, 0x90, 0x2d, 0x5a, 0x43, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x85, 0xbf, 0xda, 0xb7, 0x5c, 0x0f, 0xcb, +0xd2, 0x90, 0x17, 0x98, 0x01, 0x86, 0x8c, 0x98, +0x7e, 0xf3, 0xe9, 0x1d, 0x2b, 0x22, 0x30, 0x10, +0x85, 0xaf, 0xb2, 0x9a, 0xa7, 0x7e, 0x54, 0x28, +0x47, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0x44, 0x02, 0xcf, 0xb8, 0xdd, +0x80, 0x4c, 0x7c, 0x95, 0xf3, 0x7d, 0x1c, 0x93, +0xea, 0x4e, 0x98, 0xd7, 0x14, 0x1c, 0x53, 0xba, +0xdf, 0x3a, 0x06, 0x37, 0xa0, 0xc0, 0xf6, 0x0b, +0x3f, 0xb2, 0xae, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0x5f, 0x46, 0xed, +0x9c, 0xb6, 0xb6, 0xb5, 0xea, 0x88, 0x92, 0x1d, +0xc9, 0xf4, 0x66, 0xf9, 0x23, 0x1e, 0x40, 0xb9, +0x9b, 0x86, 0xc0, 0xee, 0xd9, 0xb8, 0x3a, 0xad, +0xdf, 0xe5, 0xca, 0x8f, 0x97, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0x23, +0x6f, 0x80, 0x66, 0x22, 0x63, 0xdb, 0xc7, 0xf9, +0xc0, 0x44, 0x04, 0x3a, 0x76, 0x3d, 0xdc, 0x4c, +0x54, 0x75, 0xac, 0x22, 0x56, 0x3a, 0xdb, 0x59, +0x51, 0x0f, 0x76, 0x24, 0x02, 0x6d, 0xaa, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0xf4, 0x8d, 0x3b, 0xf2, 0x79, 0x95, 0xbb, +0xbc, 0x55, 0x8d, 0xae, 0x66, 0xd6, 0x9f, 0x56, +0x68, 0xa7, 0x40, 0x1b, 0x7d, 0xec, 0xf4, 0x45, +0x3a, 0x67, 0x5c, 0x73, 0x2e, 0xfd, 0xcb, 0xf6, +0xc6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0xec, 0x66, 0xde, 0x91, 0xed, +0xe1, 0x2e, 0xbd, 0xf6, 0xc9, 0xd7, 0x63, 0xaf, +0x16, 0x0e, 0x41, 0x30, 0x28, 0x4d, 0xf4, 0xf9, +0xab, 0x3c, 0x2c, 0x27, 0x23, 0x89, 0xbd, 0x25, +0x41, 0x7a, 0xdc, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0x24, 0x7e, 0x12, +0x93, 0x6e, 0x42, 0x09, 0x8d, 0x91, 0xcb, 0x15, +0x03, 0xe4, 0x92, 0x60, 0x7a, 0x51, 0x13, 0x24, +0xe8, 0x51, 0x8e, 0x09, 0x09, 0x69, 0x63, 0x3c, +0xdb, 0x45, 0x1b, 0x28, 0x40, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0x5d, +0xab, 0x51, 0x01, 0xb4, 0xef, 0x90, 0x4d, 0x3c, +0xec, 0xc2, 0xa3, 0xda, 0x2b, 0xbb, 0xa3, 0xa2, +0x61, 0xa0, 0x5c, 0x84, 0xb2, 0xf3, 0x6e, 0x2d, +0xd8, 0x76, 0x64, 0xcd, 0x28, 0x12, 0xd3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x37, 0x01, 0xf7, 0x01, 0xfb, 0xbd, 0x0e, +0x8a, 0x54, 0xd7, 0x96, 0x2e, 0xfc, 0xad, 0xf4, +0x8b, 0x05, 0xd7, 0x5f, 0xd1, 0x98, 0x21, 0x24, +0xac, 0xd3, 0x6d, 0x96, 0x40, 0xf8, 0xdb, 0xb7, +0x55, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0xe1, 0x2e, 0xcd, 0x80, 0xdf, +0x40, 0x94, 0xef, 0x56, 0x45, 0x9d, 0xf8, 0x5e, +0xd9, 0xdf, 0x3f, 0x61, 0x75, 0x86, 0x6f, 0x5f, +0xd1, 0xf5, 0x5d, 0x7f, 0x20, 0x19, 0x3c, 0x67, +0xae, 0x57, 0xa4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0xc2, 0x64, 0x05, +0xdb, 0x40, 0x67, 0x0c, 0x39, 0xae, 0xf6, 0xdc, +0x0a, 0x3d, 0xde, 0x60, 0xe0, 0xe7, 0x8d, 0xc3, +0x04, 0xb2, 0xbf, 0x2b, 0xc3, 0x5b, 0xf0, 0xb8, +0x16, 0x67, 0x8a, 0x0f, 0x9f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0x86, +0x19, 0x30, 0x0b, 0x50, 0x6e, 0xda, 0xb4, 0x43, +0xf8, 0xfe, 0xbb, 0xaf, 0x92, 0x63, 0x4d, 0x01, +0x0b, 0x28, 0x2f, 0xd0, 0x07, 0x0f, 0xac, 0x5e, +0x2f, 0x64, 0xad, 0x35, 0x2f, 0x69, 0x20, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0x81, 0x94, 0x5d, 0xef, 0x65, 0x77, 0x81, +0x5c, 0x7a, 0xc4, 0xf6, 0x31, 0x25, 0x47, 0x47, +0x9e, 0xe3, 0x4b, 0x4e, 0x46, 0x9a, 0x16, 0x29, +0xa8, 0x50, 0x10, 0x8f, 0xa8, 0x3f, 0xb9, 0xdd, +0x92, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0x8c, 0x21, 0x96, 0x88, 0x6a, +0x71, 0xd9, 0x5c, 0x50, 0xd7, 0x1a, 0x53, 0xbc, +0x42, 0x1e, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x50, 0xd6, 0xed, 0xa4, +0xce, 0xba, 0xd4, 0xb5, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0x5e, 0xb3, 0x6d, +0xab, 0xd9, 0x31, 0xa9, 0x5d, 0xb9, 0xf8, 0xaa, +0xa3, 0x9b, 0x18, 0x33, 0x4b, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8b, 0xe5, +0x51, 0xf5, 0xbd, 0x91, 0x04, 0xf5, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0x8f, +0x8f, 0xee, 0x69, 0x1a, 0x30, 0xbd, 0x95, 0x98, +0xb7, 0x5c, 0x5a, 0x0b, 0x69, 0x34, 0xaf, 0x0a, +0xbf, 0xb1, 0x0c, 0x97, 0x47, 0x00, 0x25, 0xb7, +0xc0, 0x2e, 0xb0, 0x11, 0xd2, 0xf1, 0xfe, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0x90, 0x14, 0x90, 0x71, 0x7a, 0xb5, 0x69, +0xe4, 0xe2, 0x1e, 0x93, 0xeb, 0xaf, 0x0a, 0xfe, +0xe9, 0x3f, 0x32, 0x75, 0x2c, 0x88, 0x6f, 0x58, +0x67, 0x16, 0xf9, 0xbd, 0x4d, 0x2e, 0xdb, 0x9c, +0x03, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0xfe, 0x9e, 0x4c, 0x7f, 0x2f, +0x44, 0x58, 0xbc, 0x37, 0xc1, 0xc3, 0xc0, 0xcd, +0x12, 0x20, 0x4d, 0x97, 0x07, 0x05, 0xc5, 0xe4, +0x37, 0x45, 0xe6, 0x4a, 0x73, 0xe1, 0x04, 0x81, +0xee, 0x8c, 0xcb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x49, 0x6c, 0xcf, +0x8e, 0xcf, 0xe0, 0x7e, 0x1d, 0x60, 0x96, 0x84, +0xc3, 0x5b, 0xb3, 0xc3, 0x59, 0xd1, 0x6d, 0x69, +0xc8, 0x7c, 0xad, 0x16, 0xe8, 0x39, 0x01, 0xf1, +0x6d, 0x19, 0x75, 0x94, 0x7a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0x5b, +0xa3, 0x5f, 0x71, 0xa0, 0x16, 0x8d, 0xb4, 0x50, +0x13, 0xc2, 0xc4, 0x0a, 0x24, 0xed, 0xc2, 0xaf, +0x4c, 0x5a, 0x72, 0xca, 0x3c, 0x88, 0x0e, 0x2d, +0xcb, 0xa6, 0x58, 0x4f, 0xc0, 0x2c, 0x67, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0x8c, 0x54, 0x2e, 0x2e, 0x6c, 0x30, 0x04, +0x89, 0x86, 0x58, 0xab, 0xf7, 0xc1, 0x19, 0x12, +0x93, 0x96, 0x8d, 0x2d, 0x2e, 0xfa, 0xb0, 0xff, +0xbc, 0x32, 0xea, 0xf2, 0x1d, 0x3e, 0x29, 0xa2, +0x8e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0xf9, 0x67, 0x7d, 0xf6, 0xa2, +0x43, 0x46, 0x44, 0x06, 0x87, 0x72, 0x1b, 0x2a, +0xe1, 0xe0, 0x9c, 0xd2, 0x9c, 0x39, 0x58, 0x2f, +0xf7, 0x6a, 0xeb, 0xf9, 0x7a, 0x06, 0xc3, 0xbc, +0x33, 0xdf, 0x81, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0x93, 0x41, 0xcc, +0xe5, 0xd4, 0xac, 0x06, 0xf9, 0xaa, 0x14, 0x22, +0x6a, 0x31, 0x57, 0xde, 0x1f, 0xb4, 0xe5, 0x1c, +0x47, 0x15, 0x1d, 0x7f, 0x88, 0xfe, 0xfa, 0x4f, +0xd0, 0xba, 0x81, 0xce, 0xdd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0x1b, +0x23, 0x1d, 0x20, 0xea, 0x07, 0x57, 0xa6, 0xa4, +0x99, 0xb9, 0xfb, 0xa9, 0xca, 0xad, 0xf0, 0xdb, +0x0a, 0x9a, 0x41, 0xb5, 0x5b, 0xed, 0x38, 0x7d, +0xc6, 0x85, 0x24, 0x67, 0x08, 0xf1, 0x0e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0x57, 0x8e, 0x24, 0x43, 0x0d, 0x16, 0x7c, +0x45, 0xdf, 0xce, 0x7e, 0x22, 0x42, 0x54, 0x32, +0xac, 0xbd, 0x57, 0x3e, 0xd6, 0x6b, 0x21, 0xcf, +0xee, 0xea, 0x49, 0x4b, 0xe8, 0xaf, 0x6e, 0x39, +0x4c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x15, 0x33, 0x3e, 0x57, 0x83, +0x80, 0x41, 0xe4, 0xa6, 0x5a, 0x6b, 0x18, 0xd0, +0xdf, 0xe0, 0x97, 0xd0, 0x57, 0xde, 0xe9, 0x25, +0x8e, 0x89, 0x68, 0xd0, 0x5e, 0x31, 0xb3, 0x08, +0x27, 0xeb, 0xf7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0xef, 0x89, 0xb6, +0x82, 0x89, 0x78, 0x0c, 0x86, 0xea, 0x51, 0x31, +0x48, 0x69, 0x8b, 0x3a, 0x6a, 0xb0, 0xd3, 0xba, +0x4f, 0x8b, 0xd7, 0xd7, 0xb1, 0xfa, 0x9f, 0xe9, +0x8c, 0x63, 0x2e, 0x1e, 0xd1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0x7b, +0x6d, 0xed, 0xd0, 0x8c, 0xe7, 0x17, 0x18, 0x27, +0x63, 0xb6, 0x68, 0xd8, 0x72, 0x87, 0x9b, 0xda, +0x70, 0xe2, 0x67, 0x29, 0x63, 0xa5, 0x5d, 0x73, +0xf9, 0x81, 0xa4, 0x47, 0x76, 0xd9, 0x6a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0x86, 0x57, 0x4c, 0x2d, 0xaf, 0x4a, 0x2a, +0xec, 0x19, 0xc2, 0x52, 0x07, 0xd8, 0xa4, 0x4d, +0x29, 0xe2, 0xcf, 0x34, 0x51, 0x59, 0x41, 0x47, +0xeb, 0x8b, 0x9c, 0xc7, 0x68, 0xbe, 0x46, 0x74, +0xa6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0x5b, 0xa2, 0xd5, 0x26, 0xc9, +0xc9, 0xcc, 0x90, 0x7c, 0x31, 0x37, 0x4c, 0x1a, +0x9e, 0xfc, 0x73, 0xd0, 0x63, 0x2d, 0x86, 0xeb, +0x5f, 0x0c, 0x09, 0xa6, 0x24, 0xcf, 0xc8, 0x9e, +0x79, 0xc2, 0x5c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0x0a, 0x22, 0xc3, +0x7d, 0x1b, 0x45, 0xbf, 0xda, 0x37, 0x2e, 0x01, +0x49, 0xe9, 0x68, 0x53, 0x1a, 0x6e, 0x31, 0x75, +0x1a, 0x0b, 0xf1, 0xf2, 0x95, 0x9b, 0x8e, 0x4e, +0x74, 0x83, 0x0d, 0xe0, 0x28, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0x71, +0x17, 0x96, 0x8e, 0x57, 0x33, 0xec, 0x38, 0xaa, +0xe2, 0x8f, 0x4e, 0x20, 0xb4, 0x9c, 0x82, 0x57, +0xaa, 0x0b, 0x23, 0x58, 0xe6, 0x7e, 0x0a, 0x9f, +0xd8, 0x1b, 0x99, 0xfc, 0x2f, 0x7f, 0x92, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x05, 0x71, 0x1e, 0xd8, 0x58, 0x67, 0x0d, +0xa3, 0x53, 0x26, 0xba, 0x06, 0xdc, 0x51, 0x37, +0x04, 0x5d, 0x3b, 0x00, 0x7a, 0xd5, 0x33, 0x94, +0xf4, 0xbd, 0xf8, 0x08, 0x0d, 0xff, 0x80, 0x79, +0xfe, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0x9f, 0x07, 0x0b, 0xc4, 0x17, +0x6c, 0x14, 0xcc, 0xa0, 0x15, 0x86, 0x8f, 0x4e, +0x64, 0x0d, 0xb3, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xd2, 0x0d, 0x73, 0xf2, +0x00, 0xa3, 0x11, 0xa9, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0xf1, 0xe3, 0xbf, +0xb5, 0x7f, 0xe0, 0xe5, 0x51, 0x94, 0x6e, 0x84, +0x6d, 0xe7, 0x23, 0x62, 0x87, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xce, +0x2e, 0xae, 0x42, 0x80, 0xac, 0xc7, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0xf6, +0x69, 0xbc, 0x07, 0x93, 0xd8, 0xd2, 0x53, 0xe1, +0x2a, 0x0c, 0xfe, 0x2b, 0xff, 0x2a, 0xdc, 0x60, +0x71, 0x0c, 0x32, 0xcb, 0xbb, 0x64, 0x8d, 0xaf, +0x3f, 0x69, 0xde, 0xa8, 0x31, 0x03, 0x00, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0x8b, 0x61, 0x91, 0xaa, 0x2c, 0x80, 0xdb, +0x06, 0x34, 0xe1, 0x6d, 0x9d, 0x9d, 0x7f, 0x39, +0xa3, 0x44, 0x52, 0x16, 0xc4, 0x43, 0x4b, 0x3f, +0xb9, 0xbd, 0x7b, 0x22, 0x83, 0x73, 0x15, 0xa7, +0xc5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0x02, 0xce, 0x9e, 0x6e, 0xe8, +0xb8, 0x9f, 0xec, 0x53, 0x3d, 0xef, 0x8b, 0xf2, +0x7e, 0xe2, 0xe5, 0x7b, 0x78, 0x5f, 0x11, 0xed, +0x14, 0x08, 0xb2, 0x00, 0x1a, 0xfe, 0x0d, 0xbc, +0x41, 0x8e, 0x52, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0x50, 0x79, 0x7c, +0xf6, 0xd1, 0x32, 0xfe, 0x06, 0xd2, 0x53, 0x31, +0xd4, 0x4b, 0x56, 0xb1, 0x70, 0x5a, 0xc0, 0x77, +0x22, 0xb7, 0xdb, 0x82, 0x29, 0xa6, 0x4a, 0x04, +0x78, 0xc7, 0xb4, 0xdc, 0xf6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0x43, +0xd6, 0x15, 0x44, 0x75, 0xdc, 0xb5, 0xa1, 0x41, +0x07, 0x56, 0xa9, 0x7f, 0x4b, 0x18, 0xa0, 0x08, +0x3a, 0x72, 0xc5, 0xe7, 0xb6, 0x6f, 0x4a, 0x1d, +0x48, 0x68, 0x52, 0x2e, 0x02, 0x32, 0x7c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0xe0, 0xa8, 0x2c, 0xc3, 0x29, 0xd7, 0x41, +0xdd, 0xc2, 0xbb, 0x18, 0x5b, 0x2b, 0x78, 0xbf, +0xd8, 0x3a, 0x27, 0xaf, 0x66, 0xf4, 0xff, 0x9e, +0xff, 0x05, 0x16, 0x83, 0x3b, 0x7d, 0x83, 0xdc, +0x41, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0x24, 0xdf, 0x3a, 0xd3, 0x8d, +0x93, 0xa5, 0x4d, 0x22, 0xd8, 0xef, 0xd2, 0xb9, +0xc1, 0x4d, 0x5c, 0xcd, 0x31, 0x25, 0x5b, 0x1e, +0xe5, 0x96, 0x0d, 0x42, 0xa8, 0xf0, 0x3a, 0x3b, +0x11, 0x80, 0x5c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0x54, 0xbf, 0xdc, +0x0b, 0xb4, 0xa5, 0xcc, 0x21, 0x69, 0x5b, 0x77, +0xb6, 0xb8, 0xdd, 0x4b, 0xf8, 0xc9, 0x27, 0x01, +0xe7, 0x23, 0x29, 0x87, 0xb3, 0xd0, 0x82, 0x1a, +0x8c, 0x1d, 0xc7, 0xd7, 0x0d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0x7c, +0x4c, 0xb2, 0x7e, 0x6e, 0xf2, 0x4c, 0x02, 0x95, +0x80, 0x62, 0x69, 0x48, 0xe1, 0xda, 0x95, 0x5d, +0xe5, 0x32, 0x90, 0x4a, 0xfb, 0xcd, 0x1b, 0xba, +0x33, 0xd8, 0x73, 0x33, 0x6c, 0x67, 0x50, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0x80, 0x79, 0x46, 0x9a, 0x82, 0xd2, 0x4b, +0xe0, 0xaf, 0x09, 0xd1, 0x5a, 0x87, 0xf4, 0xd1, +0xd4, 0x57, 0xc0, 0x5c, 0x06, 0x1c, 0x43, 0x3f, +0x1d, 0x75, 0xeb, 0xbf, 0x4c, 0x63, 0xe6, 0x49, +0x7a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0xc7, 0xa6, 0x2a, 0x1a, 0xbf, +0xf6, 0x5c, 0x3a, 0xce, 0x11, 0xd7, 0x55, 0xac, +0x46, 0x9a, 0xe1, 0x11, 0x06, 0xf9, 0x15, 0x0b, +0xcb, 0x29, 0x9b, 0xb4, 0x14, 0x35, 0x09, 0x0e, +0xe6, 0x86, 0x92, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0xda, 0x60, 0x01, +0x55, 0xc3, 0x20, 0x58, 0x64, 0xfa, 0x86, 0x25, +0x41, 0x43, 0x65, 0x29, 0xa4, 0x2f, 0xba, 0x81, +0x2e, 0x4f, 0xa4, 0xa0, 0x39, 0x5e, 0xe3, 0x93, +0x22, 0x78, 0x00, 0xe6, 0xdf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0x7c, +0xde, 0x8b, 0x3d, 0xc7, 0x5b, 0x1a, 0x10, 0x72, +0xf5, 0x03, 0x5a, 0x43, 0x8e, 0x80, 0x43, 0xf5, +0x47, 0x08, 0x21, 0x10, 0x5d, 0xc6, 0x86, 0xd0, +0x89, 0x14, 0x55, 0xa0, 0xc0, 0x7a, 0xfc, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0xda, 0x7b, 0xea, 0x3d, 0xbd, 0x47, 0xd4, +0xc8, 0x9c, 0x8c, 0x70, 0x87, 0xed, 0x64, 0x15, +0x29, 0xa8, 0x52, 0x32, 0x25, 0x2f, 0x09, 0x3f, +0xb6, 0x60, 0x77, 0x34, 0xde, 0xf6, 0x93, 0xe2, +0x6e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0x9c, 0x4a, 0xa6, 0xd8, 0x69, +0x37, 0xca, 0x4b, 0x39, 0xca, 0x99, 0xe4, 0x1a, +0xff, 0x7f, 0x01, 0x25, 0xb1, 0xe5, 0xdc, 0x5d, +0x45, 0x2a, 0x0b, 0x6d, 0xba, 0xd4, 0xf7, 0x68, +0x24, 0xc9, 0x35, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0xcc, 0x3b, 0x54, +0x4e, 0xf4, 0x80, 0x56, 0x76, 0xa6, 0x14, 0x07, +0xae, 0x5c, 0x9d, 0x54, 0x37, 0x4a, 0xd4, 0x7f, +0x75, 0x9c, 0x1b, 0xd5, 0x6e, 0x77, 0x9e, 0x24, +0x64, 0x2a, 0x0b, 0x28, 0xe2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x0e, +0xf4, 0x90, 0xb4, 0xcc, 0x19, 0x8e, 0xd2, 0xd2, +0x87, 0x9e, 0x2b, 0x5b, 0xe0, 0x84, 0x29, 0x0b, +0x2d, 0x04, 0x50, 0x56, 0x03, 0xa9, 0x1f, 0xa9, +0x0b, 0x6e, 0x08, 0xc9, 0x7e, 0x43, 0x2e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0xce, 0x0d, 0x0b, 0x80, 0xa6, 0x3b, 0xab, +0xdc, 0xf5, 0x7a, 0xda, 0xe1, 0xf3, 0xa1, 0x9a, +0x86, 0x80, 0x41, 0x40, 0x22, 0x6b, 0x40, 0x2f, +0x5f, 0x2f, 0x65, 0x50, 0x79, 0xc4, 0x63, 0x4e, +0x8f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0xb0, 0xe8, 0x8c, 0x0c, 0x17, +0x50, 0xda, 0x58, 0xa4, 0x87, 0xa5, 0xe5, 0x75, +0x58, 0x76, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x38, 0x51, 0x7e, 0x1c, +0x23, 0xcd, 0xd3, 0xd4, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0xe4, 0xbe, 0x52, +0x00, 0xc0, 0x06, 0x48, 0xbc, 0x1e, 0x0e, 0x4f, +0x10, 0x7f, 0x0b, 0xf9, 0x07, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x60, +0x2c, 0xfa, 0x8b, 0x34, 0x5f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0x68, +0xe5, 0x91, 0xa0, 0xb8, 0x99, 0xbf, 0x00, 0x29, +0x3b, 0x5d, 0x34, 0xb6, 0x39, 0x80, 0x21, 0x50, +0x03, 0x69, 0x3c, 0x32, 0xb9, 0x26, 0xde, 0x9f, +0xd8, 0x0e, 0x6b, 0x80, 0x9f, 0xea, 0x78, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0x6d, 0x98, 0xe8, 0x2c, 0x3d, 0x2a, 0xca, +0xdb, 0xbe, 0x29, 0x5d, 0x89, 0x63, 0xe7, 0x27, +0xb5, 0xbe, 0x06, 0xc9, 0xfc, 0xa1, 0x23, 0x36, +0x88, 0xd1, 0xc3, 0x67, 0xd3, 0xf7, 0xbf, 0xee, +0x13, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0xce, 0x90, 0x6b, 0x4e, 0x93, +0xaf, 0x22, 0xbf, 0xf7, 0x7f, 0x47, 0x00, 0x9e, +0xfa, 0xdf, 0xc2, 0xd5, 0x52, 0xfb, 0x95, 0xf3, +0x53, 0xff, 0x62, 0x8d, 0xc6, 0xcf, 0xb1, 0xbe, +0x1c, 0x0a, 0x6d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0xd2, 0x61, 0x7a, +0x77, 0xee, 0x22, 0x01, 0xb8, 0xd0, 0x92, 0x67, +0x98, 0xb1, 0xed, 0x41, 0x8e, 0x59, 0x65, 0xe0, +0x07, 0xc6, 0xcf, 0xe2, 0x62, 0xdb, 0xc6, 0x0a, +0x3d, 0x06, 0xa7, 0x18, 0xda, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0xc2, +0x6d, 0x32, 0x63, 0xc2, 0xcb, 0xe5, 0x46, 0x1f, +0x73, 0xd1, 0x63, 0x49, 0x5f, 0x51, 0x27, 0x91, +0x08, 0x04, 0x37, 0xbc, 0x0c, 0x28, 0x06, 0x8c, +0xe6, 0x28, 0xb6, 0x40, 0x30, 0xd3, 0x36, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0x63, 0x9d, 0xaf, 0xb7, 0xa1, 0x78, 0xe9, +0xc5, 0x76, 0x70, 0x38, 0xaa, 0x4e, 0xd4, 0x1c, +0x0d, 0x00, 0x1b, 0x08, 0x2d, 0xde, 0x58, 0x53, +0xa3, 0x0b, 0xcd, 0xf8, 0xdf, 0xcf, 0xdf, 0x70, +0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0x5d, 0x18, 0xe2, 0x78, 0xeb, +0xaf, 0xde, 0xa9, 0x10, 0x43, 0xcf, 0xc5, 0x29, +0x4c, 0x26, 0x52, 0x4c, 0x67, 0x7d, 0xd2, 0xce, +0xa7, 0x1d, 0x18, 0x33, 0x04, 0x47, 0x91, 0x7b, +0x81, 0xcd, 0x30, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x08, 0x68, 0xa5, +0x39, 0xda, 0x9f, 0x74, 0x32, 0xa1, 0xc1, 0x83, +0xf4, 0x58, 0xc9, 0x1e, 0x04, 0x69, 0x6a, 0x2b, +0x73, 0x60, 0x0d, 0xa9, 0xbf, 0xcf, 0x7c, 0xfa, +0xad, 0x3b, 0x29, 0x20, 0xc0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0xf2, +0xdf, 0x31, 0x13, 0x29, 0x5e, 0x76, 0x49, 0xa9, +0xf4, 0x9f, 0x13, 0xeb, 0xec, 0xa6, 0xff, 0xb4, +0xcb, 0x74, 0x04, 0x69, 0x45, 0x50, 0x5a, 0x7c, +0x70, 0x3e, 0xf2, 0x0b, 0x9d, 0x6d, 0xee, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0xff, 0x88, 0xe0, 0x62, 0xe4, 0xde, 0x1f, +0x83, 0x15, 0x28, 0x47, 0x33, 0x6b, 0x02, 0x18, +0xc9, 0xab, 0x88, 0x21, 0x5e, 0xe5, 0xff, 0xd0, +0x86, 0xa7, 0x78, 0x8b, 0x02, 0xc7, 0xaf, 0xd4, +0xcb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0x8c, 0xe1, 0xd5, 0xb3, 0xfe, +0x38, 0x6b, 0xad, 0x60, 0x69, 0xc0, 0xb5, 0x74, +0xa6, 0xa2, 0x1e, 0x80, 0x51, 0x93, 0x29, 0x19, +0xff, 0x40, 0xdd, 0xbf, 0xe1, 0xcb, 0x52, 0xbe, +0x3f, 0x60, 0x4a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0x45, 0x2a, 0xd3, +0x0b, 0x48, 0xe6, 0x3f, 0xda, 0xa3, 0x64, 0xd0, +0x17, 0x11, 0xd0, 0x83, 0x42, 0x1d, 0x4d, 0x06, +0x8c, 0xa5, 0xab, 0xa3, 0xee, 0xce, 0xd4, 0xb2, +0x06, 0x93, 0x2b, 0x2c, 0x2a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0xe9, +0xfb, 0x3b, 0xf3, 0xd4, 0x9e, 0xdb, 0xac, 0xce, +0x93, 0x19, 0x01, 0x73, 0x24, 0x6b, 0xb3, 0x7b, +0xfd, 0x56, 0xb2, 0x18, 0xb9, 0xd4, 0x7c, 0xe5, +0x0c, 0x7a, 0xba, 0x5e, 0xb6, 0x6d, 0xf6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0x54, 0x65, 0x82, 0xf4, 0x24, 0xf6, 0xe3, +0x7e, 0xb6, 0xa5, 0x0d, 0xf7, 0x18, 0xae, 0x5f, +0x4a, 0x74, 0x94, 0xca, 0xc3, 0x61, 0x1b, 0x0e, +0x04, 0xfa, 0x88, 0x46, 0xd8, 0x5a, 0x2c, 0x99, +0xfa, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0x19, 0x6b, 0x80, 0x38, 0x4a, +0x22, 0x3a, 0x0f, 0x51, 0x27, 0x27, 0xd2, 0xee, +0x91, 0xa1, 0x70, 0x4c, 0x48, 0x3c, 0xa6, 0x82, +0x39, 0xf1, 0xf7, 0x35, 0x5f, 0xeb, 0x3a, 0x98, +0x20, 0xb8, 0x05, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0x3e, 0xb7, 0xed, +0x60, 0x24, 0x0e, 0x56, 0x7b, 0xc6, 0xc7, 0xda, +0xef, 0xf6, 0x87, 0x45, 0x96, 0xa1, 0xd4, 0x29, +0x2e, 0x48, 0x1b, 0x40, 0x18, 0x45, 0x86, 0xdf, +0x6c, 0x3d, 0x75, 0x70, 0xdb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0x56, +0x69, 0x63, 0xdc, 0xc9, 0x92, 0xcd, 0xb0, 0xb9, +0x86, 0x06, 0x04, 0xa0, 0x0a, 0x87, 0x04, 0x19, +0x53, 0xf2, 0x4f, 0xed, 0x82, 0x38, 0xad, 0x25, +0x22, 0xe7, 0xa0, 0xa5, 0x30, 0x5c, 0xf8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0xa7, 0x80, 0x5e, 0x26, 0xde, 0x37, 0x4e, +0x7a, 0xe6, 0xe1, 0xa9, 0x32, 0xf9, 0xe6, 0x3f, +0xa5, 0x6b, 0xf6, 0xfa, 0x78, 0x30, 0xb2, 0x9c, +0x9d, 0xf9, 0xb1, 0xef, 0xdd, 0xd2, 0xb4, 0x31, +0xea, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0xee, 0xa8, 0x62, 0xfa, 0x0c, +0xcd, 0x17, 0xa0, 0x2c, 0xef, 0xa4, 0x79, 0x7e, +0x33, 0x51, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xe5, 0x5d, 0x09, 0xe4, +0x4b, 0x54, 0x71, 0xc7, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0xf4, 0x8b, 0x2d, +0x8e, 0x13, 0x6d, 0x83, 0x51, 0x22, 0x94, 0xb6, +0xeb, 0x5e, 0x4b, 0xd1, 0xf1, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x3f, +0xf5, 0xea, 0xb3, 0x66, 0xc2, 0x2d, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0xab, +0x8c, 0x26, 0xb2, 0x72, 0xa5, 0xc7, 0xac, 0x02, +0x60, 0x93, 0xc8, 0x1e, 0xf5, 0xe6, 0x48, 0x89, +0x02, 0x7f, 0x47, 0x42, 0xc9, 0x24, 0xa2, 0x88, +0x58, 0xb4, 0x3c, 0x32, 0x4b, 0x13, 0x34, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0xb9, 0x74, 0xae, 0x50, 0x96, 0xaf, 0x81, +0x10, 0xfc, 0xc6, 0x0c, 0x01, 0x90, 0xbd, 0xb7, +0x80, 0x7e, 0xe6, 0x31, 0x35, 0x55, 0x4c, 0x71, +0x7a, 0x0c, 0x5c, 0x4c, 0x78, 0xaf, 0x92, 0x99, +0x5d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0x37, 0x96, 0xcd, 0xad, 0x90, +0x5f, 0x66, 0x7b, 0xfa, 0x14, 0x41, 0xb5, 0x97, +0x64, 0x4f, 0x98, 0xf7, 0x92, 0x4f, 0x3f, 0x25, +0xb3, 0xd1, 0xf7, 0x5b, 0x27, 0x16, 0x98, 0xfd, +0xa4, 0x74, 0xa3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0x1c, 0xca, 0x53, +0x84, 0x77, 0x0e, 0x92, 0x89, 0xf6, 0xe0, 0xb3, +0x4b, 0xdb, 0xf0, 0xb1, 0xd9, 0x80, 0x7c, 0xeb, +0xa4, 0x94, 0x75, 0xbe, 0xbb, 0x56, 0x95, 0x67, +0x00, 0xff, 0x02, 0x78, 0xc9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0xca, +0xc3, 0x05, 0x2f, 0xbb, 0xb8, 0x19, 0xd1, 0x84, +0x94, 0xd0, 0x24, 0x2b, 0x93, 0xe6, 0xbb, 0xf1, +0x7a, 0xb7, 0x43, 0x9f, 0xe5, 0x41, 0xaa, 0xe8, +0xf6, 0x69, 0x26, 0xe5, 0xb9, 0x74, 0x27, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0x43, 0x12, 0x66, 0xb9, 0x7e, 0xa8, 0x5e, +0xe6, 0x1f, 0x90, 0xf1, 0x15, 0x08, 0xef, 0x63, +0xa7, 0xdd, 0xd7, 0x68, 0xb7, 0x13, 0x4c, 0x29, +0xdc, 0xe2, 0x6c, 0x94, 0x60, 0x37, 0x8f, 0x3f, +0xc0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0x2e, 0x8d, 0x58, 0x1a, 0x56, +0xc6, 0x75, 0x9f, 0x19, 0xea, 0x85, 0x2a, 0x34, +0xa7, 0x73, 0x90, 0x97, 0xa7, 0x61, 0x93, 0x33, +0xf2, 0x69, 0xd1, 0x61, 0x4a, 0x3d, 0x48, 0x02, +0x9e, 0x73, 0xf0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x98, 0x97, 0xf6, +0x7b, 0x91, 0xdd, 0x4c, 0xbd, 0x35, 0xc8, 0x5c, +0x08, 0x1e, 0x8f, 0x60, 0x7d, 0xff, 0xed, 0x6b, +0x97, 0x79, 0x60, 0x08, 0xd6, 0xa8, 0xce, 0x13, +0xb0, 0x6d, 0xae, 0x1e, 0x44, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x48, +0x70, 0x0f, 0xc3, 0x6b, 0x18, 0x78, 0xce, 0xc8, +0xc1, 0xcd, 0x46, 0xdc, 0xac, 0xb3, 0x88, 0xa8, +0x66, 0x39, 0x33, 0xd5, 0x28, 0xb1, 0xe2, 0x34, +0x6e, 0x8b, 0x91, 0xd1, 0xed, 0xb7, 0xe0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0xb4, 0x2e, 0xd8, 0x0c, 0x67, 0xf1, 0x2b, +0x15, 0x3d, 0x86, 0xc6, 0xec, 0x5e, 0x4d, 0x43, +0x3b, 0x21, 0xac, 0x20, 0xc5, 0x5b, 0xf1, 0x10, +0x32, 0xa0, 0x1e, 0x04, 0xb9, 0x50, 0xcb, 0x6c, +0x96, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0xfa, 0x76, 0xcd, 0xbc, 0x34, +0x1e, 0x6c, 0xcb, 0xf9, 0xb8, 0x0a, 0xe8, 0x3e, +0x89, 0x62, 0x40, 0x29, 0x1d, 0x4a, 0x9f, 0xba, +0x4a, 0x5c, 0x9d, 0x49, 0x29, 0x19, 0xc0, 0x76, +0x87, 0xe2, 0x58, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0x1c, 0x9a, 0x11, +0x42, 0x38, 0x99, 0xfe, 0xa6, 0x2e, 0x9f, 0xaa, +0xc8, 0x9b, 0xa0, 0xe0, 0xa0, 0xfd, 0x3a, 0x94, +0x8e, 0x2d, 0x20, 0x6d, 0x27, 0x96, 0x26, 0xbc, +0xe3, 0xa1, 0x38, 0xf6, 0x84, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0xc6, +0x17, 0xca, 0x73, 0x7d, 0x2e, 0xaa, 0x78, 0xae, +0xb7, 0x64, 0x80, 0x4f, 0x0d, 0x36, 0xfe, 0x4c, +0x94, 0xc4, 0x61, 0x1d, 0x35, 0x24, 0xcd, 0x4a, +0xfd, 0x06, 0xc9, 0x18, 0x7c, 0xe7, 0xd5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0xe3, 0x4b, 0x93, 0xd8, 0xb1, 0x7e, 0x51, +0xa0, 0xe5, 0x92, 0x96, 0xa8, 0x67, 0xd6, 0x09, +0xfa, 0x96, 0xcf, 0x65, 0x36, 0x84, 0x3d, 0x0c, +0xf1, 0x42, 0x18, 0x21, 0x16, 0xe6, 0x62, 0xfa, +0x06, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0x20, 0xb8, 0x21, 0xab, 0xd7, +0x18, 0xa8, 0xf6, 0x4a, 0x29, 0x42, 0x05, 0xa1, +0xd8, 0x50, 0x74, 0x0e, 0x03, 0x40, 0x51, 0x60, +0x0f, 0xd6, 0x9d, 0xe1, 0x7e, 0x4a, 0xe0, 0x0a, +0x31, 0xe1, 0xa0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0xcc, 0x39, 0xdd, +0xc0, 0xae, 0xf0, 0xf8, 0x6a, 0xbb, 0xa8, 0xfd, +0x26, 0x46, 0x10, 0x19, 0x4f, 0xb8, 0xe0, 0xc3, +0x54, 0xbb, 0x64, 0x9b, 0xc8, 0x1a, 0x93, 0x48, +0x8c, 0x23, 0xad, 0x2c, 0x6c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0x6a, +0xf2, 0xf8, 0x7b, 0x1c, 0x09, 0x6f, 0xbc, 0x96, +0xab, 0x28, 0x0c, 0x45, 0x6a, 0x22, 0xf0, 0x32, +0xcd, 0x35, 0x46, 0xa9, 0x46, 0x34, 0xca, 0xe8, +0x22, 0xe4, 0xe4, 0xcb, 0x1d, 0x3c, 0x29, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0x19, 0x6d, 0x51, 0x86, 0xb6, 0xa9, 0x06, +0x5f, 0x49, 0xd5, 0x4d, 0x60, 0x09, 0x43, 0xbb, +0xac, 0x41, 0x5d, 0xe2, 0xb8, 0xab, 0x90, 0x65, +0xcc, 0xcc, 0x53, 0x26, 0x78, 0xc4, 0xaf, 0x36, +0xd4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0x90, 0x9a, 0xc8, 0x15, 0x6d, +0x21, 0x16, 0x1b, 0xaf, 0xd5, 0xf0, 0x2d, 0x60, +0x21, 0xcf, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xb4, 0xb1, 0x72, 0x93, +0xe9, 0x43, 0x8f, 0xc9, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0xbc, 0x4f, 0x24, +0xaa, 0x71, 0x29, 0x34, 0xe7, 0xc3, 0x11, 0xc0, +0xd2, 0x6a, 0xf4, 0x52, 0xb9, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0xaa, +0x29, 0x09, 0x17, 0xf5, 0xb5, 0x47, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0x53, +0x18, 0xea, 0x62, 0xdb, 0x7f, 0xe8, 0x48, 0x79, +0xe8, 0x0f, 0x42, 0x5d, 0x61, 0x4a, 0xb2, 0xbe, +0xd0, 0x91, 0xc3, 0x10, 0xb2, 0xaa, 0x8b, 0x33, +0x53, 0x41, 0x3e, 0x9c, 0x25, 0xdb, 0x29, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0xe6, 0x9c, 0xd0, 0x70, 0x83, 0x73, 0x23, +0x28, 0x2d, 0xa0, 0xb7, 0xe1, 0x88, 0x72, 0xd1, +0x5d, 0x24, 0x09, 0x6c, 0xd6, 0x8a, 0x36, 0x98, +0xf1, 0xb3, 0x03, 0xb4, 0x45, 0x21, 0x7f, 0xf3, +0x7f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0x2a, 0xb5, 0x6d, 0xda, 0xbf, +0x42, 0x42, 0xae, 0x85, 0x89, 0x68, 0x92, 0xf5, +0x55, 0x0d, 0xe4, 0x1d, 0xd3, 0xf0, 0xfb, 0xcc, +0x15, 0xbf, 0xfc, 0xa3, 0x3a, 0xf8, 0xbe, 0x83, +0xf0, 0x6c, 0x09, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0x60, 0x5f, 0x0b, +0xd2, 0x58, 0xc8, 0x2f, 0x28, 0x2d, 0x15, 0xde, +0x3e, 0xbb, 0x04, 0xb5, 0xb0, 0x09, 0x35, 0x6f, +0x34, 0xfd, 0xde, 0xf9, 0xe7, 0x4a, 0xf2, 0x12, +0xe6, 0xa6, 0x18, 0xf6, 0x4b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0x1e, +0xb2, 0xcd, 0x8e, 0x97, 0xeb, 0xea, 0x2d, 0x37, +0xe1, 0xae, 0xa3, 0xe7, 0x7e, 0x77, 0x28, 0x18, +0xad, 0xe0, 0x57, 0xdb, 0xd6, 0xd5, 0xa3, 0x01, +0x3c, 0x36, 0x77, 0x19, 0xcc, 0x25, 0xc4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0x4d, 0x65, 0xbb, 0x86, 0x09, 0xe9, 0xc7, +0x60, 0x26, 0xff, 0xa2, 0xef, 0xe1, 0x76, 0xa6, +0xa2, 0x1b, 0x6c, 0x37, 0x50, 0xe9, 0xff, 0x09, +0xb5, 0x50, 0xd6, 0x03, 0x56, 0x8d, 0xbe, 0x93, +0x44, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0x23, 0x9e, 0x40, 0xb6, 0xcf, +0x16, 0x2b, 0xd0, 0x34, 0xde, 0x29, 0x03, 0xc3, +0x17, 0xba, 0xda, 0xc4, 0xda, 0x6c, 0xe2, 0x0b, +0x76, 0x72, 0xe9, 0xdc, 0xa5, 0x15, 0x9d, 0x21, +0x0d, 0xe3, 0x93, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0xb3, 0xe0, 0x94, +0xd9, 0xd6, 0xc4, 0x69, 0xb5, 0x4d, 0xb1, 0x83, +0x3e, 0xff, 0xba, 0x90, 0x40, 0x3c, 0xec, 0x58, +0xde, 0x69, 0x5d, 0xe6, 0x5d, 0x9a, 0xf3, 0xa5, +0xa7, 0x22, 0x89, 0x86, 0x7d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0xfa, +0x83, 0xd8, 0xdd, 0x2d, 0x7e, 0xb9, 0xb7, 0x4e, +0x43, 0x38, 0x27, 0x6f, 0x6f, 0xe3, 0xe8, 0xb0, +0xc1, 0xf0, 0x54, 0x1f, 0x19, 0xd7, 0xdf, 0x71, +0x70, 0x96, 0x5f, 0x64, 0xac, 0x7e, 0xcf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0x64, 0x5a, 0xdb, 0x7a, 0x5f, 0xa2, 0x08, +0x76, 0x68, 0xd6, 0x9a, 0xd0, 0x7f, 0x19, 0x82, +0x24, 0x50, 0xf2, 0xfe, 0x86, 0xcd, 0xa1, 0xc2, +0x8e, 0x58, 0xa4, 0x40, 0x10, 0xb9, 0x86, 0xc2, +0x82, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0x52, 0xbb, 0xd3, 0xce, 0x50, +0xde, 0x69, 0x33, 0x86, 0xe4, 0xac, 0x8f, 0xdd, +0xf3, 0xd8, 0xeb, 0x77, 0x62, 0x2d, 0x40, 0x4b, +0x46, 0xbf, 0xec, 0xc9, 0xd7, 0x9b, 0x7f, 0x34, +0x63, 0x35, 0x59, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0x28, 0x9e, 0xb7, +0xf0, 0x6d, 0x98, 0x73, 0x6d, 0x85, 0xac, 0x7e, +0x60, 0xd3, 0x2b, 0xb7, 0x60, 0xe6, 0xb2, 0xc5, +0x76, 0x99, 0x81, 0xbb, 0x2e, 0x81, 0xa8, 0x47, +0xa3, 0xa2, 0xf2, 0xe0, 0xa0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0x11, +0xf5, 0xfc, 0xfb, 0x2e, 0xc2, 0x08, 0xfb, 0x2a, +0x2c, 0x06, 0xa8, 0x61, 0xd4, 0x71, 0xdc, 0xaf, +0x38, 0x9d, 0xbf, 0x81, 0xf3, 0x22, 0xa8, 0x4f, +0x94, 0xb7, 0xb5, 0xb8, 0x8d, 0xcf, 0xb4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0xf0, 0xac, 0x8e, 0x79, 0x85, 0xcb, 0x01, +0xd1, 0xb0, 0x5b, 0xea, 0xe3, 0x9a, 0x35, 0x12, +0xc0, 0xa4, 0x3e, 0xb1, 0xea, 0xb8, 0x0e, 0x70, +0x37, 0x0e, 0xe9, 0x24, 0xa6, 0x31, 0xa3, 0x44, +0x49, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0xe8, 0xce, 0xe4, 0xbc, 0xb2, +0xdb, 0xb3, 0xb8, 0x83, 0xdc, 0x12, 0x19, 0x01, +0xc2, 0x0c, 0xa3, 0x05, 0x9c, 0x57, 0xb4, 0x8d, +0x9b, 0xaf, 0xd8, 0x75, 0x1c, 0x9c, 0x1f, 0x74, +0x2b, 0xe4, 0xfb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0x87, 0x53, 0x84, +0x1b, 0x52, 0x79, 0x71, 0x2c, 0x90, 0x27, 0x2e, +0x7e, 0x2e, 0x93, 0x2e, 0xe1, 0x48, 0xe8, 0x1e, +0xbd, 0x02, 0x8f, 0x74, 0x81, 0x70, 0x73, 0x57, +0xef, 0x50, 0x86, 0x6c, 0x20, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0x90, +0xe5, 0xb8, 0xf7, 0x45, 0xf6, 0xfb, 0xcf, 0x5b, +0xec, 0xd8, 0x4f, 0x93, 0x9f, 0xb8, 0x18, 0x5f, +0xfa, 0x8a, 0xa3, 0xc6, 0x86, 0xd4, 0xc2, 0xbe, +0x8e, 0x6b, 0xd3, 0x5d, 0x23, 0x46, 0x9e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0xeb, 0x2f, 0xf6, 0xc4, 0x25, 0x31, 0x15, +0xd3, 0xca, 0x90, 0x2b, 0x6e, 0x06, 0x03, 0x14, +0x75, 0x01, 0xbd, 0xdc, 0x2d, 0x74, 0x0c, 0xe8, +0x13, 0xce, 0x1c, 0x6d, 0xed, 0x1e, 0xeb, 0x08, +0xa2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0x9e, 0x55, 0xa0, 0x04, 0xcb, +0xb1, 0x77, 0xd3, 0x9d, 0x03, 0x66, 0xa8, 0x83, +0xed, 0xb3, 0xbd, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x1a, 0xcb, 0xe1, 0xcf, +0x3f, 0xa7, 0x4a, 0x76, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0x9f, 0xaf, 0x1e, +0xff, 0x33, 0x15, 0x14, 0x1c, 0x57, 0x98, 0x55, +0x0a, 0x2f, 0xd7, 0xc4, 0x06, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0xb4, +0xbd, 0x46, 0xd8, 0xea, 0xcc, 0x88, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0x8b, +0x27, 0xe5, 0x64, 0x9e, 0x7f, 0xdd, 0xc2, 0x13, +0xd1, 0x91, 0x94, 0xf1, 0x28, 0xe6, 0xe2, 0x2b, +0x7c, 0x2b, 0x8c, 0x18, 0x1e, 0xd4, 0x99, 0x15, +0x93, 0x3e, 0x41, 0xbe, 0x46, 0x26, 0xd4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0xa8, 0x68, 0x23, 0xc4, 0x19, 0xaa, 0xa4, +0x84, 0x8c, 0xd3, 0x69, 0xe3, 0x68, 0x9a, 0x97, +0x05, 0x2d, 0xf7, 0x23, 0x20, 0xb7, 0x60, 0xaa, +0x7a, 0x54, 0xb5, 0x11, 0x2f, 0x5a, 0x48, 0x64, +0x46, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0x37, 0xbc, 0x89, 0xf0, 0xca, +0x71, 0x40, 0x2c, 0x94, 0x9f, 0x1b, 0xa4, 0x15, +0x72, 0x4c, 0x23, 0x1c, 0x4e, 0x0f, 0x89, 0xb5, +0x42, 0x5c, 0x05, 0x96, 0x24, 0xf4, 0xe3, 0xe1, +0xa8, 0xda, 0xa0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0xe5, 0x9e, 0x56, +0xd4, 0x73, 0xde, 0x58, 0xda, 0x06, 0xf0, 0xa0, +0x21, 0x23, 0xf5, 0x25, 0x98, 0x8d, 0x00, 0x3f, +0xe0, 0xf5, 0xd9, 0x4f, 0xd2, 0x98, 0x4c, 0x9c, +0x06, 0xc5, 0x97, 0xc0, 0x57, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0x93, +0xae, 0x78, 0x18, 0x60, 0xa9, 0x0f, 0x98, 0x27, +0x83, 0x97, 0xcd, 0x42, 0x41, 0xac, 0x13, 0x5a, +0x41, 0x05, 0x2c, 0xb5, 0x17, 0x43, 0x78, 0x4f, +0x79, 0x11, 0xef, 0x38, 0x5d, 0xb4, 0x09, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x18, 0x2c, 0xbf, 0x0f, 0xc7, 0xa9, 0xc4, +0xc4, 0xc0, 0x3f, 0x14, 0xfa, 0xa2, 0xff, 0x77, +0x49, 0xb4, 0xe3, 0x1f, 0x65, 0xa4, 0x14, 0x0c, +0x8a, 0xef, 0x4b, 0x69, 0xe1, 0xc4, 0x41, 0xf5, +0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0xf6, 0x42, 0xf8, 0xfe, 0x20, +0xa9, 0x87, 0x4a, 0x2c, 0x2f, 0x4a, 0x42, 0xec, +0x44, 0x2e, 0xa6, 0xf2, 0x48, 0xf8, 0x30, 0x1e, +0xdd, 0x5a, 0xf9, 0xcc, 0x7f, 0x98, 0xc9, 0xb1, +0xa4, 0x0d, 0x03, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0x91, 0x0d, 0xfa, +0x86, 0x91, 0x1f, 0xa3, 0xd9, 0x89, 0x67, 0xbc, +0x85, 0xd2, 0xf3, 0x22, 0xa0, 0x19, 0xfd, 0x4e, +0x0a, 0xab, 0x75, 0x66, 0xad, 0x64, 0xac, 0xe1, +0x76, 0x98, 0x23, 0xbf, 0xe2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0xac, +0xaf, 0xfb, 0x76, 0xc0, 0xa0, 0xa2, 0x37, 0x62, +0xf6, 0x23, 0xe4, 0x64, 0x85, 0x5b, 0xa5, 0x01, +0xc4, 0xe0, 0xd1, 0xe1, 0xf1, 0xec, 0x32, 0xf6, +0x2b, 0x44, 0x44, 0x74, 0x18, 0x6e, 0xce, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0x01, 0x7e, 0x59, 0xc3, 0x80, 0x3a, 0x11, +0x1f, 0x03, 0xba, 0x7a, 0xac, 0x9d, 0xb7, 0x5f, +0xc7, 0xf3, 0x26, 0xc1, 0x64, 0x60, 0x2d, 0x07, +0x5b, 0x5e, 0x40, 0x4f, 0xc3, 0x20, 0x49, 0x38, +0x27, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0x2e, 0x19, 0x1d, 0x95, 0x77, +0xfd, 0x2e, 0xce, 0x66, 0xab, 0x6f, 0x73, 0xd5, +0x6d, 0xf4, 0x1c, 0x7b, 0x2c, 0x03, 0x3b, 0x06, +0x59, 0x74, 0xfe, 0x8f, 0x20, 0x3b, 0x81, 0x40, +0xa7, 0xda, 0xe8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0xf5, 0x98, 0x24, +0x37, 0x89, 0xd5, 0x1b, 0xd8, 0xc5, 0x73, 0x59, +0x82, 0x8f, 0xc1, 0xc4, 0xdd, 0xbd, 0x81, 0xd1, +0x3f, 0xac, 0xfa, 0x7f, 0x70, 0x36, 0xf1, 0x72, +0x03, 0xae, 0x0a, 0xe3, 0x22, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x73, +0x1c, 0xad, 0xd9, 0x49, 0xd6, 0xfe, 0x6e, 0x3c, +0x00, 0x0c, 0x41, 0x55, 0x61, 0xd9, 0x2f, 0xb3, +0xee, 0x6d, 0x4c, 0xbc, 0x9a, 0x3e, 0x3a, 0x3f, +0x63, 0x39, 0x15, 0xaa, 0x9c, 0x76, 0xdf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0x2f, 0x77, 0xef, 0x95, 0x1c, 0x51, 0x95, +0xa8, 0x77, 0xfc, 0x7c, 0x74, 0x68, 0x14, 0x3a, +0x26, 0x07, 0x6e, 0x7d, 0x6c, 0x27, 0x10, 0x42, +0xa7, 0x8e, 0xff, 0x1f, 0x65, 0x97, 0xba, 0x9f, +0xac, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0x4c, 0xe9, 0x04, 0x2b, 0xf1, +0x51, 0xe6, 0xbc, 0x45, 0x54, 0xd7, 0xca, 0x0c, +0x51, 0xd1, 0xe7, 0x1c, 0xc4, 0xba, 0x7b, 0xfd, +0xed, 0x07, 0x75, 0xf4, 0xa7, 0xff, 0x7c, 0x41, +0x13, 0x6b, 0xd9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0xcc, 0x73, 0xaa, +0xb8, 0x01, 0x25, 0xef, 0x84, 0xb5, 0xbd, 0xf4, +0x74, 0xaf, 0xde, 0x10, 0x92, 0x6a, 0x5b, 0xf2, +0x22, 0x7c, 0xfa, 0x88, 0x9b, 0xc3, 0x8d, 0x73, +0x4c, 0x19, 0x2b, 0x14, 0x1d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x7f, +0xb5, 0x25, 0x0c, 0x65, 0xac, 0x24, 0x21, 0xf0, +0xec, 0x86, 0xf8, 0x09, 0x9f, 0x4b, 0xc5, 0x96, +0xca, 0x9d, 0xc2, 0x9b, 0x1a, 0x30, 0x5d, 0x99, +0x8d, 0x66, 0x48, 0xc8, 0x4f, 0xa9, 0x0a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0x43, 0x19, 0x1c, 0x65, 0x1a, 0x0c, 0xe3, +0x41, 0x5e, 0x9e, 0x03, 0x1f, 0xf4, 0xdb, 0x3e, +0x06, 0x0f, 0x84, 0xfb, 0x31, 0xda, 0xa0, 0xfd, +0x30, 0xba, 0xc1, 0x73, 0x0a, 0xfb, 0xa5, 0x64, +0xc6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0x81, 0xd1, 0xd1, 0x99, 0xb1, +0xbc, 0xfa, 0x2b, 0x81, 0x74, 0xc3, 0xf4, 0x5c, +0x3f, 0x09, 0xca, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xcc, 0x09, 0x45, 0x2c, +0x03, 0x75, 0xd0, 0xfb, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x28, 0x64, 0x2d, +0x31, 0x08, 0x71, 0xae, 0x85, 0xbe, 0x24, 0xbb, +0xa6, 0x91, 0xc1, 0x23, 0x3d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xad, 0xed, +0x3c, 0x4f, 0xbb, 0xc5, 0x8d, 0x1d, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0xbc, +0x75, 0x55, 0x6c, 0x96, 0x97, 0x9f, 0x38, 0x10, +0x36, 0xf5, 0x49, 0x80, 0x94, 0x6e, 0x3b, 0xb4, +0x2e, 0x96, 0xe4, 0x9e, 0x9e, 0x10, 0xae, 0x01, +0x0c, 0x0f, 0x32, 0xb5, 0xfd, 0x2e, 0x21, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0xc5, 0x3e, 0x5f, 0x0e, 0x8a, 0xbc, 0x32, +0xad, 0x3c, 0x7c, 0x2e, 0xe5, 0x19, 0x4c, 0x48, +0x21, 0x00, 0xeb, 0x56, 0xa7, 0x21, 0x96, 0x03, +0x48, 0x5a, 0x00, 0xed, 0xaa, 0x61, 0x35, 0xfc, +0x57, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0x65, 0x0e, 0x57, 0xeb, 0xc3, +0x69, 0x70, 0x98, 0x52, 0xad, 0x32, 0xe4, 0xfa, +0xf6, 0xf5, 0xa4, 0x01, 0x6f, 0xe3, 0x25, 0x17, +0x3e, 0xcb, 0x06, 0x02, 0xeb, 0x64, 0x43, 0x3b, +0xcc, 0x38, 0xc9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0x92, 0xa4, 0x6d, +0x0a, 0xe5, 0x4b, 0xb2, 0x35, 0x72, 0xa9, 0x60, +0x3b, 0xf4, 0xe5, 0xcc, 0x78, 0xdb, 0xb8, 0x56, +0x16, 0x00, 0xa0, 0xf4, 0xbd, 0x8a, 0xb5, 0x4f, +0x43, 0x77, 0x43, 0x93, 0x9c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0xca, +0x24, 0x5a, 0xa1, 0x1c, 0xa3, 0x95, 0x3f, 0x79, +0xbb, 0x43, 0xea, 0x49, 0x7d, 0x8f, 0xa3, 0x4a, +0x1e, 0x78, 0xc1, 0xad, 0x1c, 0x4a, 0xd8, 0x74, +0x9d, 0xf8, 0x45, 0x58, 0x62, 0xc3, 0x91, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x2e, 0x47, 0x8a, 0x07, 0xe0, 0xe9, 0xa2, +0x62, 0x12, 0x5f, 0x96, 0x40, 0xfb, 0xf8, 0x56, +0xf2, 0x90, 0xbd, 0x93, 0x8f, 0xce, 0xfd, 0xed, +0x20, 0x9e, 0x9c, 0x88, 0xeb, 0xff, 0x00, 0x9f, +0x53, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x9d, 0xda, 0x20, 0x30, 0x05, +0x4d, 0x99, 0xca, 0x93, 0xe4, 0xc0, 0x41, 0xd5, +0xa7, 0xf0, 0xc0, 0x6f, 0x69, 0x4d, 0x23, 0x7a, +0xfa, 0x91, 0x25, 0x79, 0x1b, 0x1b, 0x19, 0x7a, +0x53, 0xaa, 0x6d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0x65, 0x1d, 0x39, +0xd2, 0xcb, 0x4f, 0x08, 0x17, 0xf4, 0x12, 0xc4, +0xe3, 0xed, 0x4f, 0x9d, 0x5f, 0xa3, 0x1a, 0x16, +0x7b, 0x03, 0x32, 0x9c, 0x06, 0x7e, 0x2b, 0x4b, +0xce, 0xf6, 0xcc, 0x81, 0x13, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0x6a, +0x6c, 0x9c, 0xd8, 0x68, 0x31, 0xb0, 0xb5, 0x97, +0x51, 0x1a, 0x80, 0x45, 0x26, 0xaf, 0x72, 0x60, +0x85, 0x0f, 0x09, 0xf3, 0x50, 0x29, 0x30, 0xa0, +0x84, 0xbd, 0x57, 0x80, 0x1c, 0xa2, 0x93, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0xd2, 0x2f, 0x5c, 0xeb, 0xdd, 0x32, 0xfe, +0x35, 0xa6, 0xbb, 0x3e, 0x2f, 0x03, 0x0f, 0xcf, +0xde, 0x87, 0x2b, 0x7f, 0x0d, 0x71, 0xde, 0x9e, +0x74, 0xce, 0xfa, 0xa6, 0x69, 0xc6, 0x96, 0xd3, +0xc9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0x02, 0xbe, 0x78, 0x0b, 0x4a, +0xed, 0xe3, 0xc3, 0x27, 0x0e, 0x21, 0x83, 0xce, +0xe1, 0x28, 0xba, 0xd4, 0xb7, 0xd6, 0xba, 0xdd, +0xb1, 0xa3, 0xee, 0xbf, 0xb3, 0xbc, 0xe2, 0x1c, +0x37, 0x2e, 0xe9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0xda, 0x62, 0xdf, +0xf8, 0x53, 0x19, 0xca, 0x48, 0x6c, 0xa3, 0x05, +0x46, 0xa1, 0x7c, 0xd6, 0x2e, 0x32, 0x57, 0x63, +0xdd, 0x57, 0xb4, 0x4f, 0x3b, 0x64, 0x8a, 0xca, +0x13, 0x9d, 0x5e, 0x37, 0xcd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x5c, +0xc5, 0x1d, 0x5b, 0xeb, 0x07, 0xfd, 0xfd, 0xe9, +0xb2, 0x49, 0x61, 0xfb, 0x97, 0xab, 0x99, 0xc4, +0xf9, 0xa5, 0x8a, 0x17, 0x6e, 0xbf, 0xb3, 0xf5, +0x9d, 0xfe, 0x0b, 0x86, 0x03, 0xf1, 0x85, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0x68, 0xe8, 0xb7, 0x0b, 0x9e, 0xbd, 0xf2, +0xee, 0x88, 0xf6, 0xac, 0x46, 0x3e, 0xa5, 0xba, +0xf7, 0x9c, 0xfa, 0xa7, 0x93, 0x36, 0x9a, 0x86, +0xe4, 0xc0, 0x80, 0xc5, 0x3d, 0x7b, 0x34, 0xc8, +0xe4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0xa3, 0x06, 0x3c, 0x5f, 0xc5, +0x67, 0xfa, 0x5b, 0x53, 0x64, 0x13, 0x51, 0xb6, +0x32, 0x8d, 0x8d, 0x48, 0x45, 0x9a, 0x15, 0x62, +0x5e, 0x02, 0x7e, 0x18, 0xbb, 0xb7, 0x04, 0x69, +0x13, 0x0b, 0x3b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0x19, 0x27, 0xe5, +0xa8, 0x78, 0x8f, 0x15, 0xa1, 0xdc, 0x64, 0x77, +0xcb, 0x0a, 0x93, 0x91, 0x34, 0x2a, 0x08, 0x5a, +0xde, 0x06, 0xcf, 0x20, 0x6c, 0x3e, 0x37, 0xeb, +0xf3, 0x94, 0xc0, 0x76, 0x12, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0xe2, +0x66, 0xa1, 0x00, 0x33, 0xfb, 0x6a, 0xe9, 0x5c, +0x4f, 0x05, 0x49, 0xb1, 0xf5, 0x97, 0x18, 0x0c, +0xa0, 0x14, 0x16, 0xff, 0x9d, 0x8c, 0xdf, 0xe4, +0x94, 0xf2, 0x9f, 0x26, 0x95, 0xa9, 0x0c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0xe6, 0xc6, 0xfa, 0x81, 0x5c, 0xae, 0x2e, +0xc8, 0x13, 0x4c, 0x41, 0x2e, 0xb3, 0xd5, 0x00, +0xb3, 0xd6, 0xd6, 0x5c, 0xa2, 0x2e, 0x5b, 0xb9, +0x3d, 0x6d, 0x57, 0x8a, 0x75, 0xea, 0xf1, 0x2d, +0x36, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0xbd, 0x78, 0x9c, 0x28, 0xdb, +0x61, 0xa6, 0x1c, 0x49, 0x8e, 0x23, 0x84, 0xae, +0x10, 0xc1, 0xd1, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x5e, 0x6d, 0xa6, 0x26, +0x51, 0x61, 0x78, 0xb3, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0xe7, 0x0a, 0xe3, +0x46, 0xc3, 0x25, 0x60, 0x31, 0xc3, 0x11, 0x04, +0xe2, 0x03, 0xac, 0xe4, 0x8c, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x9e, +0x89, 0x36, 0x59, 0x66, 0x25, 0xfa, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0x7c, +0x23, 0x99, 0x54, 0x98, 0x68, 0xce, 0x5a, 0x41, +0xdf, 0xc7, 0x8a, 0xcb, 0x33, 0x5b, 0x02, 0x34, +0xd8, 0x9d, 0x6f, 0x9b, 0xe8, 0x1a, 0xf5, 0x12, +0xbd, 0x00, 0x0e, 0x7c, 0x53, 0xe6, 0xfb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0x5d, 0xff, 0xed, 0x5c, 0xc7, 0x97, 0x44, +0x9f, 0x15, 0x1e, 0x75, 0x1f, 0xd6, 0xf7, 0xb7, +0x9b, 0xe4, 0xf1, 0x47, 0x83, 0xe5, 0xad, 0xcf, +0xb4, 0xcb, 0x32, 0x4a, 0x8d, 0xc7, 0x3a, 0x37, +0xa4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0xf5, 0x6a, 0x71, 0x39, 0x67, +0xf1, 0x74, 0x23, 0x21, 0x6e, 0xc2, 0x79, 0xf3, +0x4b, 0xb1, 0x99, 0xb8, 0xb3, 0xd8, 0x3d, 0x29, +0x06, 0xe5, 0xbe, 0xda, 0x0b, 0x61, 0x9d, 0xfd, +0x9c, 0xc4, 0xd6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0xe8, 0xec, 0x61, +0x56, 0x36, 0x50, 0xb4, 0x14, 0xe2, 0x79, 0x4e, +0x75, 0x7d, 0xd4, 0x3b, 0xe6, 0x10, 0x7a, 0x5c, +0x06, 0x0c, 0x3a, 0x96, 0xab, 0xd5, 0x7e, 0x79, +0x77, 0x28, 0x9e, 0x88, 0x64, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0x00, +0x64, 0xb3, 0x22, 0x7d, 0x01, 0xf1, 0x08, 0x14, +0xf7, 0x78, 0xa5, 0x26, 0xd7, 0xec, 0x79, 0xe4, +0x57, 0x9c, 0xab, 0x4a, 0x51, 0x7e, 0xfa, 0x6c, +0x56, 0xe8, 0x96, 0x1a, 0x8f, 0x29, 0x1a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0xf1, 0xa6, 0xef, 0x7e, 0xbe, 0x09, 0x7b, +0x34, 0xb8, 0xf5, 0xcd, 0xc3, 0xdf, 0xd3, 0x73, +0x94, 0xd6, 0x06, 0x35, 0x39, 0x8f, 0x0b, 0x1a, +0xf9, 0xb7, 0x8e, 0xa5, 0x6e, 0x39, 0x65, 0x4a, +0xc0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0x4d, 0x18, 0xc3, 0x36, 0x6e, +0xf7, 0x4e, 0x59, 0xd1, 0x5e, 0xa2, 0x6f, 0xf2, +0x7e, 0xe2, 0x60, 0x23, 0xc3, 0x4d, 0x63, 0x44, +0x74, 0x03, 0xd0, 0xff, 0xcd, 0xb0, 0x39, 0x02, +0xae, 0x0a, 0x43, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0xf7, 0x6a, 0x0d, +0xd0, 0x1f, 0x53, 0x2a, 0xc5, 0x0d, 0x1f, 0xba, +0xe9, 0xd1, 0x49, 0x31, 0x42, 0x52, 0xd3, 0xad, +0x53, 0x5f, 0xfd, 0xc4, 0xc8, 0x41, 0x1a, 0x35, +0xbe, 0x48, 0x2d, 0x86, 0x8c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0x7e, +0x35, 0xa9, 0x55, 0x23, 0x93, 0x8e, 0xf9, 0xe4, +0x97, 0xa2, 0x05, 0x97, 0x8d, 0x6b, 0xc0, 0x39, +0x4d, 0xb3, 0xbf, 0x31, 0xb9, 0x76, 0x60, 0x8c, +0x6e, 0x5e, 0x8b, 0xe5, 0x56, 0x66, 0x2a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0xc2, 0x23, 0x62, 0x9c, 0x56, 0x91, 0x9a, +0xaa, 0xfe, 0xae, 0x0b, 0x43, 0xc0, 0xc1, 0x1e, +0xce, 0x56, 0x39, 0x38, 0xd1, 0x26, 0x4f, 0x81, +0x62, 0xf6, 0xda, 0xfc, 0xba, 0xf9, 0xc8, 0x2c, +0xde, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x32, 0x27, 0xc5, 0x69, 0x7d, +0x78, 0xe7, 0xad, 0x6d, 0x53, 0xcc, 0x99, 0xcb, +0xbb, 0xdb, 0xa0, 0x3f, 0x4c, 0x26, 0xd5, 0x6f, +0x29, 0xfe, 0x8c, 0x86, 0xfc, 0x9e, 0x86, 0x3b, +0xbe, 0xab, 0x0d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0xd6, 0xcf, 0x22, +0x55, 0xbe, 0x8d, 0x0a, 0x23, 0xd6, 0x60, 0x5d, +0xfa, 0x9e, 0x56, 0xb5, 0xa2, 0x3e, 0xa3, 0xb4, +0x8d, 0xbc, 0xc7, 0xf8, 0xe3, 0x14, 0x67, 0x1d, +0x5d, 0x90, 0xb0, 0xe5, 0xf7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0x0c, +0x1d, 0x6a, 0x64, 0x72, 0x9c, 0x53, 0xfa, 0x52, +0x4a, 0x25, 0x6f, 0x69, 0x27, 0xfb, 0x88, 0x93, +0xdd, 0x10, 0x4a, 0xf6, 0x64, 0xd1, 0x98, 0x60, +0x71, 0x8f, 0xda, 0xd5, 0x81, 0x72, 0x50, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0xc2, 0xd9, 0x68, 0x4a, 0x96, 0x98, 0xcc, +0x88, 0x19, 0xb2, 0x4d, 0x42, 0xc9, 0x80, 0xe3, +0xce, 0xf8, 0x58, 0x10, 0x25, 0xb0, 0x24, 0x76, +0xd4, 0x2f, 0x0d, 0x94, 0xdf, 0x7b, 0x7f, 0x57, +0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x8a, 0x39, 0xaa, 0xbb, 0x87, +0xcc, 0xba, 0xbb, 0x67, 0xa1, 0x9f, 0x5b, 0xff, +0xfa, 0x20, 0xb7, 0x3e, 0xef, 0xd1, 0x0c, 0x01, +0x50, 0x25, 0x41, 0x41, 0x8a, 0xc3, 0x17, 0x1a, +0x17, 0xe6, 0x2b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0xdb, 0xf8, 0x84, +0xd0, 0x56, 0x07, 0x9f, 0x3d, 0x65, 0xb1, 0x42, +0xce, 0xf9, 0x9d, 0xf5, 0x39, 0x76, 0x67, 0xc9, +0x2f, 0x7b, 0x80, 0x9b, 0x10, 0x95, 0x44, 0x52, +0xa2, 0x4c, 0xf9, 0xe2, 0x61, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0xcd, +0xe3, 0x9d, 0xb7, 0x7c, 0x4b, 0x67, 0xd2, 0xec, +0x5a, 0x7a, 0xb5, 0xc8, 0x20, 0x79, 0xd4, 0xc1, +0xd0, 0x34, 0x22, 0x3b, 0xcb, 0xfd, 0x0d, 0x16, +0x95, 0xaf, 0x01, 0xf2, 0x22, 0x88, 0x40, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0x0a, 0x61, 0x17, 0x19, 0x98, 0x7e, 0x89, +0xc3, 0x0f, 0x63, 0x1f, 0x1e, 0x81, 0x24, 0xe7, +0xdc, 0xeb, 0x37, 0xce, 0xe4, 0x2d, 0x16, 0x06, +0xdb, 0xef, 0x0b, 0x82, 0xda, 0x3d, 0x79, 0x1d, +0x34, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0x86, 0x69, 0xd3, 0x6c, 0xab, +0xbe, 0x64, 0xdc, 0x6d, 0x55, 0x67, 0xf1, 0x86, +0x69, 0xf7, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x1c, 0xb4, 0xac, 0xb2, +0xa3, 0x0d, 0x78, 0xd9, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x2b, 0x67, 0xe0, +0x9a, 0xf9, 0x2a, 0x94, 0xe0, 0x3b, 0x0d, 0xce, +0xc3, 0xb5, 0x7d, 0xd9, 0x73, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0xa6, +0x29, 0x50, 0x5a, 0xb3, 0xb5, 0x91, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x05, +0x7a, 0x4d, 0x12, 0xbf, 0xda, 0x31, 0x4f, 0x31, +0x25, 0x6b, 0x52, 0xe8, 0xc5, 0xeb, 0xd0, 0x91, +0x7f, 0x05, 0xd7, 0x03, 0x2c, 0x69, 0xad, 0x68, +0x9d, 0x88, 0xf4, 0xdf, 0xfe, 0x4d, 0x52, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0xd5, 0x58, 0x5c, 0x38, 0x53, 0x22, 0xa4, +0x82, 0x8f, 0x1e, 0x4a, 0x47, 0x77, 0xe9, 0x7d, +0x41, 0x8d, 0xd8, 0x85, 0xcf, 0xf3, 0xd7, 0xe6, +0x52, 0xb4, 0x15, 0xb0, 0x80, 0xf9, 0x8e, 0x02, +0x4e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0xe2, 0x37, 0x15, 0xe3, 0x50, +0x5a, 0x9f, 0x46, 0x17, 0xc7, 0x33, 0x23, 0xeb, +0x04, 0x7b, 0xb1, 0xa9, 0x9f, 0x8e, 0xeb, 0x8e, +0x68, 0xb4, 0xb6, 0x17, 0x93, 0x76, 0x20, 0xba, +0xf2, 0x98, 0x39, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0x15, 0x5d, 0x03, +0x20, 0x11, 0xfe, 0x6b, 0x3a, 0x5b, 0xe4, 0x7b, +0x63, 0x4a, 0x6f, 0x4b, 0xae, 0x7d, 0xbc, 0x07, +0xd5, 0xc6, 0x16, 0x8a, 0xd9, 0x2e, 0x9b, 0x35, +0x5c, 0xb9, 0xa5, 0x83, 0x85, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0xf1, +0xb9, 0xf6, 0x9b, 0xd2, 0xea, 0x37, 0xb0, 0x2e, +0xca, 0xc5, 0xec, 0x07, 0xdb, 0x91, 0x83, 0xe3, +0x5d, 0xf4, 0xde, 0xdf, 0x67, 0xb8, 0x10, 0x91, +0xb6, 0x66, 0xa6, 0x95, 0x26, 0x3e, 0x87, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0x5e, 0x3f, 0x46, 0x9e, 0xb1, 0x6d, 0x23, +0xea, 0xe0, 0x8d, 0xb8, 0x88, 0xd6, 0xda, 0x79, +0x6f, 0x11, 0x60, 0x33, 0x31, 0x87, 0x36, 0x0d, +0xbb, 0xb8, 0x98, 0xf0, 0xd5, 0xab, 0x1a, 0x3b, +0x66, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0xff, 0x55, 0xbd, 0x6b, 0xb3, +0x96, 0x77, 0xbc, 0x9a, 0x04, 0x95, 0x56, 0x9d, +0x3d, 0xd9, 0x85, 0xea, 0x81, 0x54, 0x4e, 0xa0, +0xdb, 0x21, 0x7c, 0x0d, 0xc2, 0xe8, 0x18, 0x26, +0xbc, 0x00, 0x60, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0xf8, 0x76, 0xd1, +0xe5, 0xed, 0x03, 0x04, 0x5f, 0x59, 0x97, 0xa0, +0xa0, 0x74, 0xf1, 0x48, 0x71, 0x03, 0xf3, 0x62, +0xe8, 0x9b, 0xc9, 0x1e, 0x82, 0xc6, 0x22, 0x3f, +0xde, 0x60, 0xcf, 0xbd, 0x82, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0xe0, +0x50, 0xad, 0x8a, 0xd2, 0xfc, 0x92, 0xe7, 0x3b, +0x2f, 0x26, 0x41, 0x70, 0xdb, 0x1c, 0x7b, 0x81, +0x08, 0x13, 0x07, 0x68, 0x8f, 0x6a, 0x19, 0x73, +0x39, 0xcf, 0x82, 0x61, 0xc1, 0x0f, 0xa9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0x0b, 0xd3, 0x7a, 0xe4, 0x2b, 0x02, 0x2a, +0xc7, 0x2b, 0x6a, 0xb8, 0xec, 0xd0, 0x68, 0xd7, +0x91, 0x67, 0x28, 0xe7, 0xcd, 0x88, 0xf2, 0x57, +0xdf, 0xc4, 0xe3, 0x6a, 0xba, 0x4d, 0x67, 0x09, +0xb0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0xa6, 0xa1, 0xd4, 0xe3, 0xc6, +0x81, 0x9b, 0x52, 0x64, 0x26, 0x50, 0xa0, 0xe5, +0x7f, 0xe0, 0xc1, 0x21, 0x5b, 0xcf, 0xf6, 0x62, +0x6a, 0x70, 0x71, 0x53, 0x3d, 0x00, 0x16, 0x56, +0xe4, 0xe0, 0x6a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x85, 0x3f, 0xa1, +0xf5, 0x06, 0xa2, 0x53, 0xbb, 0x14, 0x53, 0xe8, +0xac, 0x4b, 0x2e, 0x23, 0xeb, 0xef, 0xe5, 0x0f, +0x72, 0x70, 0x93, 0xd9, 0xbf, 0x0b, 0xd2, 0xd5, +0xb4, 0x61, 0x1a, 0xa2, 0xbf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0xc4, +0x2e, 0x30, 0x07, 0x4b, 0x26, 0xd8, 0x57, 0x26, +0x02, 0x23, 0x98, 0xdc, 0x54, 0x32, 0x1a, 0x90, +0x3c, 0xb8, 0x56, 0xcf, 0xdd, 0xf8, 0xfd, 0x51, +0x8f, 0x6e, 0x31, 0xcf, 0x77, 0x16, 0x33, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0x64, 0xd9, 0xed, 0x2f, 0x3d, 0xe4, 0xa9, +0x44, 0x56, 0x5d, 0xfa, 0x23, 0xd2, 0x54, 0x11, +0xfd, 0xf2, 0xd2, 0x34, 0x16, 0x51, 0xa7, 0xba, +0xa2, 0x09, 0xaf, 0xb3, 0x08, 0x45, 0xa5, 0x37, +0x26, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0xb3, 0x5a, 0xc2, 0xc6, 0xfe, +0x91, 0x86, 0x54, 0x77, 0xf3, 0x03, 0x8e, 0xdb, +0x47, 0xc8, 0xd0, 0xc7, 0x3e, 0xe3, 0xc2, 0xd6, +0xe4, 0xaf, 0x74, 0x76, 0xfa, 0x8f, 0xf9, 0x75, +0xdd, 0x13, 0x90, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x1b, 0x1e, 0x88, +0xcc, 0x47, 0x9a, 0xe8, 0xda, 0x26, 0x31, 0xc6, +0xaf, 0xad, 0x42, 0xa7, 0x6c, 0x9d, 0xcd, 0x77, +0xf7, 0x06, 0xb0, 0xad, 0xdc, 0xf4, 0x7d, 0x7d, +0xee, 0xbb, 0x3a, 0x02, 0xa5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0xb9, +0x04, 0x9b, 0x9f, 0xea, 0x02, 0xb1, 0x7e, 0x69, +0x25, 0xb3, 0xbc, 0x13, 0x74, 0xb6, 0x19, 0x63, +0x38, 0x62, 0xc3, 0x66, 0x86, 0x82, 0x69, 0x77, +0xa7, 0x32, 0xa0, 0xca, 0x4a, 0xed, 0xec, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0xcc, 0x93, 0x96, 0xba, 0xfb, 0x1b, 0xda, +0xcf, 0x56, 0x51, 0x47, 0x29, 0xb2, 0x30, 0xc1, +0x1e, 0x19, 0x0d, 0xda, 0xde, 0x61, 0x91, 0x40, +0xd3, 0xb8, 0x28, 0x96, 0xc6, 0xed, 0x61, 0x44, +0x0f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0x68, 0x2e, 0x9b, 0x4a, 0x6d, +0xed, 0xfd, 0x67, 0x82, 0xc3, 0xc1, 0xd7, 0x41, +0xcd, 0x51, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x88, 0xcc, 0x7b, 0xe4, +0xa5, 0x3d, 0xc3, 0x10, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0xdd, 0x1a, 0xe2, +0x5f, 0xa9, 0x8c, 0xba, 0x11, 0xe4, 0x97, 0xd8, +0x78, 0x6e, 0x02, 0xf8, 0x76, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0xcb, +0xfa, 0xcc, 0xf8, 0x5c, 0xfc, 0x16, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0xe4, +0x7f, 0x77, 0xaa, 0xab, 0x6a, 0x7c, 0x1f, 0xc2, +0xa6, 0x53, 0xfe, 0x31, 0x18, 0x5d, 0x3e, 0xb2, +0x95, 0xdd, 0x61, 0x55, 0x05, 0x87, 0x6d, 0xc5, +0xc6, 0x00, 0x11, 0xd3, 0x82, 0xbe, 0xce, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0xca, 0x4f, 0x0d, 0x1a, 0x40, 0x05, 0xf8, +0x72, 0x2d, 0x29, 0xe4, 0x67, 0x86, 0x99, 0x33, +0xe9, 0xa8, 0x48, 0xf9, 0x7a, 0x32, 0x70, 0x76, +0xc0, 0xe7, 0x89, 0x65, 0x89, 0xbe, 0xb0, 0x7e, +0x40, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0xcc, 0x33, 0x39, 0xf5, 0x0f, +0x37, 0x72, 0x2f, 0xc6, 0xff, 0xe0, 0xbd, 0x2e, +0xaf, 0x44, 0xc3, 0xa6, 0x77, 0x8b, 0xda, 0x73, +0x25, 0x9a, 0x5f, 0x3b, 0x67, 0xa3, 0x39, 0x31, +0x28, 0x25, 0xcd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0xb0, 0xd1, 0x23, +0xc3, 0x2f, 0xe6, 0x61, 0x02, 0xd3, 0x2f, 0x4a, +0xc6, 0xbb, 0xda, 0x94, 0x5f, 0x4b, 0xf5, 0xc9, +0x92, 0xc5, 0x8c, 0xd8, 0x42, 0xde, 0x37, 0xf8, +0xc8, 0xa1, 0x05, 0x17, 0xa1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x74, +0xb3, 0xc8, 0xac, 0x92, 0x6f, 0xcf, 0x6e, 0x65, +0x50, 0xfc, 0xb0, 0x01, 0x72, 0x97, 0x94, 0x22, +0xbd, 0x0d, 0x81, 0xdc, 0xa9, 0x50, 0xff, 0x35, +0x03, 0x47, 0xf7, 0xd3, 0xb4, 0xd2, 0x8c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0xd4, 0x7d, 0x58, 0xb7, 0x51, 0x66, 0xee, +0xbe, 0x1a, 0xdd, 0x85, 0xfe, 0xf3, 0xa0, 0xc8, +0x53, 0xe9, 0x54, 0xdb, 0x1c, 0x78, 0xab, 0x2f, +0xa8, 0x7a, 0x1f, 0xfd, 0xb6, 0x19, 0x68, 0xad, +0x99, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0x5a, 0x36, 0x56, 0xf5, 0x79, +0x88, 0xf7, 0xc5, 0x06, 0x42, 0xab, 0x9b, 0xc9, +0xaa, 0xc4, 0xc4, 0x5e, 0x12, 0x68, 0x5a, 0xce, +0x20, 0x6a, 0xa7, 0x42, 0x5e, 0x23, 0x36, 0x4a, +0xd1, 0x1f, 0x5c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0x5a, 0xf3, 0xe7, +0x4d, 0x39, 0x93, 0xa0, 0x6a, 0xe9, 0x59, 0xc9, +0xde, 0x5e, 0x13, 0xa8, 0x3c, 0x74, 0xe7, 0x52, +0x76, 0x25, 0x23, 0x40, 0x05, 0x21, 0xee, 0x51, +0x25, 0x5f, 0x6c, 0x8a, 0x5b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0xde, +0x8c, 0x80, 0x75, 0xd4, 0x1a, 0x74, 0x53, 0x32, +0x1e, 0x22, 0x32, 0x4a, 0x3a, 0xd9, 0xbd, 0x98, +0x4c, 0x3f, 0xa0, 0xd7, 0x0b, 0x44, 0x32, 0xac, +0xda, 0xba, 0xb2, 0x69, 0x19, 0x6b, 0x66, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0x75, 0x75, 0x21, 0x23, 0xb3, 0xce, 0xfd, +0xbd, 0x34, 0xd9, 0x43, 0xb6, 0xf5, 0x92, 0xfc, +0xa7, 0x2c, 0xc8, 0x15, 0x74, 0x99, 0x37, 0x0b, +0xd6, 0x92, 0x2e, 0x12, 0x41, 0xd9, 0xe1, 0x98, +0xa5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x57, 0x5c, 0x04, 0x2e, 0x69, +0xdc, 0x57, 0x98, 0xcf, 0x06, 0x6e, 0x92, 0x04, +0x3e, 0x67, 0x32, 0xd8, 0xde, 0x22, 0xe7, 0xeb, +0x6c, 0xfb, 0x0f, 0xe8, 0x30, 0xd8, 0xa2, 0xa9, +0x66, 0x25, 0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x49, 0x18, 0x7f, +0xa8, 0x98, 0xec, 0x80, 0xca, 0x1e, 0x76, 0x05, +0xd3, 0xf9, 0x82, 0x38, 0x01, 0x8f, 0x31, 0x7b, +0xb8, 0x59, 0x11, 0x96, 0x4a, 0xb8, 0xc7, 0x22, +0x4e, 0x92, 0x20, 0xf1, 0x80, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0x9a, +0x47, 0x9c, 0x40, 0x36, 0x95, 0x01, 0x9f, 0xb6, +0xef, 0x59, 0x79, 0x44, 0xd8, 0xd2, 0x97, 0xfe, +0x57, 0x5c, 0xc9, 0x58, 0x83, 0x8f, 0x68, 0x22, +0x68, 0x10, 0x55, 0x44, 0xd1, 0x8e, 0xf9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0x67, 0xc6, 0x0d, 0x20, 0x1d, 0x6d, 0x2d, +0xf0, 0xa7, 0xd0, 0x3e, 0x49, 0xc4, 0xd4, 0x37, +0x50, 0xcd, 0x83, 0xc0, 0xb3, 0x9e, 0xf1, 0x9c, +0x50, 0x16, 0x10, 0x18, 0x7e, 0xd4, 0x2b, 0xb6, +0x9c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0xc6, 0xc5, 0x1f, 0x2e, 0x9c, +0x17, 0x42, 0xe1, 0xed, 0x69, 0x2a, 0xca, 0x14, +0x1d, 0x60, 0xb9, 0xa6, 0x62, 0xfc, 0xd2, 0x8e, +0x5a, 0xb7, 0xe0, 0xa8, 0x5b, 0x83, 0x64, 0x77, +0x28, 0x68, 0xbe, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0x3b, 0x32, 0x6d, +0x12, 0xdb, 0xe8, 0x28, 0x81, 0x5d, 0x66, 0x37, +0x47, 0x7c, 0xaa, 0x3c, 0x48, 0x2e, 0x98, 0xb8, +0xe8, 0xb8, 0xb9, 0x9e, 0x45, 0xb3, 0x99, 0x46, +0x0b, 0xec, 0xbf, 0x7d, 0x0b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0x72, +0x51, 0xe1, 0xa6, 0x44, 0x2d, 0xb1, 0x75, 0xec, +0x00, 0x03, 0xee, 0xee, 0xfd, 0x02, 0xa4, 0xe2, +0xe0, 0x90, 0x82, 0x94, 0xbd, 0x46, 0x33, 0xd6, +0xf8, 0xe7, 0x0e, 0x33, 0xae, 0x42, 0x1b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0xb6, 0x06, 0x00, 0x66, 0xf6, 0x5d, 0xff, +0x24, 0xa3, 0xaa, 0x76, 0x86, 0xf9, 0xa9, 0x6f, +0x87, 0x62, 0x18, 0x3d, 0x3e, 0x20, 0x70, 0xb7, +0xd7, 0x21, 0x6b, 0xd3, 0x8c, 0xad, 0x6b, 0xa3, +0x5d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0x64, 0xa8, 0xb8, 0xea, 0x83, +0x38, 0xe8, 0x0c, 0x46, 0xb4, 0x8f, 0xd2, 0x21, +0xbe, 0x1c, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x3e, 0x07, 0x94, 0x02, +0x12, 0x21, 0x84, 0x80, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0x9e, 0x2e, 0xae, +0xe4, 0x19, 0x5c, 0xf6, 0xee, 0x43, 0xce, 0x93, +0x95, 0x69, 0xb4, 0x41, 0xd5, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x1a, +0x44, 0x8a, 0x59, 0x5c, 0xd7, 0xf1, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x91, +0x7f, 0x39, 0x3b, 0xa2, 0xc3, 0x92, 0x59, 0x7e, +0x21, 0xbf, 0xde, 0x4c, 0xc9, 0x7b, 0x48, 0x18, +0xea, 0xf6, 0x99, 0x3b, 0xe5, 0xaf, 0x3c, 0x65, +0x42, 0xf8, 0xc2, 0xb8, 0xbb, 0x9e, 0xa0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0x5d, 0x9e, 0x3b, 0x28, 0xa5, 0xd8, 0xdb, +0xe2, 0xbb, 0x17, 0xdf, 0x32, 0xd5, 0x77, 0x04, +0xd9, 0x08, 0x11, 0x39, 0x69, 0x5d, 0xb5, 0x9c, +0xa5, 0x68, 0x0e, 0xe3, 0x3e, 0xec, 0xe6, 0xce, +0x05, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0xe7, 0x41, 0xf7, 0x93, 0x2f, +0x4a, 0xf8, 0x51, 0xa5, 0x3a, 0x83, 0x89, 0xb2, +0x25, 0xba, 0x87, 0x44, 0x10, 0x66, 0x4e, 0xa0, +0x32, 0x28, 0xd9, 0x2c, 0xdb, 0xb8, 0x9a, 0xf4, +0x0a, 0x42, 0xb3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0x93, 0x2f, 0x66, +0x1e, 0x43, 0x90, 0xbb, 0x9c, 0x12, 0x35, 0x67, +0xe8, 0x7f, 0x27, 0x56, 0xe6, 0x40, 0xc3, 0xa9, +0x04, 0xb8, 0xf7, 0xdc, 0x6e, 0xb5, 0x94, 0xbb, +0xcb, 0x5a, 0x88, 0xd2, 0x5c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0x8a, +0x38, 0x3a, 0x07, 0xcf, 0x77, 0x55, 0xc8, 0x72, +0xfd, 0xc7, 0x86, 0x85, 0x2f, 0x8f, 0x5b, 0x23, +0xa3, 0x8c, 0x7f, 0xfa, 0xbf, 0xed, 0xfa, 0x18, +0x85, 0x80, 0xc7, 0x73, 0xbd, 0xd8, 0x1c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0x97, 0xd3, 0x63, 0xe4, 0x55, 0x0d, 0x09, +0x62, 0x42, 0x46, 0xbf, 0xa2, 0x92, 0xfe, 0x0a, +0xe9, 0xfd, 0xc2, 0x35, 0xbf, 0x2a, 0x7f, 0xd0, +0x21, 0xb7, 0x36, 0xf3, 0xd8, 0x88, 0x3a, 0x0c, +0x0f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x93, 0xd5, 0x6a, 0xac, 0x21, +0x56, 0x2b, 0x9a, 0x3e, 0xdd, 0xfd, 0xe0, 0xc7, +0x83, 0x6a, 0x22, 0x84, 0x3f, 0x00, 0x7c, 0x41, +0xd7, 0xf4, 0xed, 0xae, 0x0a, 0xa3, 0xbc, 0xa0, +0xf8, 0xad, 0x30, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0xb3, 0x9a, 0xb2, +0xff, 0x95, 0x24, 0xa1, 0xd0, 0xc6, 0xb3, 0xdb, +0x1c, 0x92, 0xaf, 0xc2, 0x1d, 0xf0, 0x83, 0xf6, +0xc2, 0x88, 0x9f, 0x1a, 0x21, 0xb8, 0xa0, 0x9c, +0x24, 0x05, 0x15, 0x99, 0xc0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0xc9, +0xc6, 0xa0, 0x0a, 0xd6, 0x79, 0xa7, 0xfe, 0x9f, +0x6c, 0x14, 0x04, 0x21, 0x79, 0x70, 0xc1, 0x25, +0x78, 0x07, 0x43, 0xed, 0x77, 0x83, 0x57, 0x9a, +0x10, 0xb2, 0xa6, 0x76, 0x88, 0x18, 0x2e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0xab, 0x64, 0x0d, 0x63, 0x0b, 0xe8, 0xe4, +0x99, 0x3d, 0x52, 0x4e, 0xc0, 0xae, 0x93, 0xa6, +0x28, 0x2e, 0x8b, 0x04, 0xfe, 0x0e, 0x7f, 0x4c, +0x82, 0xd4, 0xdb, 0x20, 0x8f, 0xb1, 0x4f, 0xab, +0xba, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0x7f, 0x0c, 0xf5, 0xda, 0x3b, +0x86, 0xed, 0xea, 0x64, 0xaf, 0xdc, 0x23, 0x71, +0x71, 0xfe, 0x3c, 0x7f, 0x78, 0x88, 0x73, 0x45, +0xab, 0x00, 0xe0, 0x15, 0x5e, 0x1f, 0x56, 0x34, +0xe6, 0x3e, 0xfd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0x3d, 0x2c, 0xac, +0x3e, 0xf6, 0xde, 0x18, 0x43, 0xd4, 0x4e, 0xff, +0x9b, 0xd0, 0x23, 0xb4, 0xc5, 0xa2, 0xcc, 0xdd, +0xcf, 0xf5, 0x66, 0xea, 0x42, 0x85, 0x7b, 0x26, +0x2e, 0x96, 0x19, 0x2d, 0xc7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0xd3, +0xd2, 0xa2, 0xc3, 0xba, 0x11, 0x6a, 0xe6, 0x44, +0x07, 0x5f, 0xe8, 0x26, 0xa8, 0x38, 0xad, 0xa9, +0x8b, 0x6f, 0x0d, 0x80, 0x24, 0xc3, 0xd2, 0x30, +0x6b, 0x7a, 0xf0, 0x08, 0xf5, 0xef, 0x0f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0x26, 0xba, 0x3a, 0x62, 0x54, 0xc2, 0x16, +0xa2, 0xcb, 0x98, 0x84, 0x26, 0x08, 0xa9, 0x5b, +0x25, 0xf7, 0xe4, 0x53, 0x47, 0xd3, 0x8d, 0xa6, +0x81, 0x2a, 0xf1, 0x48, 0xdc, 0xee, 0x92, 0x2f, +0x9d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0xee, 0x96, 0x18, 0xdb, 0x78, +0xbd, 0xa0, 0x73, 0x25, 0xea, 0xe2, 0xd2, 0x5e, +0xcb, 0x1e, 0x66, 0x7a, 0x48, 0xbb, 0xeb, 0x4a, +0x34, 0xc0, 0xdd, 0x47, 0xac, 0x81, 0x30, 0x36, +0x92, 0xd7, 0x80, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0x2c, 0xa4, 0x92, +0x05, 0x37, 0x45, 0xbd, 0x5b, 0x97, 0x0a, 0x85, +0xdb, 0xd5, 0xf6, 0xdb, 0xa3, 0x14, 0xed, 0x29, +0xcc, 0xe1, 0x5b, 0x97, 0xe3, 0xbd, 0x4e, 0x78, +0x28, 0xf5, 0x41, 0xe8, 0x86, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0xc4, +0x10, 0xaa, 0x84, 0x60, 0x7f, 0x0e, 0xc1, 0x9e, +0x05, 0xa7, 0x10, 0xa6, 0xe0, 0x9b, 0xd3, 0xd7, +0x71, 0xd7, 0x3b, 0x54, 0xe0, 0x4b, 0xae, 0xb7, +0x12, 0x1b, 0xfe, 0xe9, 0x9f, 0xbb, 0x8b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0x1e, 0xbb, 0xcd, 0x08, 0x7d, 0xc9, 0xed, +0x3c, 0x2d, 0xbb, 0x43, 0x38, 0xcd, 0xd6, 0xa6, +0x7d, 0xce, 0x13, 0x1d, 0x69, 0xda, 0xab, 0x56, +0x06, 0x29, 0xd6, 0x8c, 0xcc, 0xcf, 0xa4, 0x41, +0x2d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0xa5, 0xd5, 0x5d, 0xc4, 0xdd, +0x45, 0xd8, 0x73, 0x4e, 0x58, 0x46, 0x17, 0xa5, +0x6b, 0x25, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x05, 0x05, 0xbd, 0x72, +0x97, 0x3b, 0x07, 0x3c, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0x31, 0xff, 0xfb, +0x2b, 0x99, 0xb0, 0x25, 0x4c, 0xc7, 0x47, 0x55, +0xf3, 0x34, 0x50, 0xe3, 0xae, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x1e, +0x52, 0x6b, 0x69, 0x3a, 0x61, 0x68, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0xba, +0xeb, 0x39, 0x68, 0x95, 0x18, 0xcf, 0x8b, 0x34, +0xe2, 0x1f, 0x5b, 0x0f, 0x2b, 0x67, 0x42, 0x6c, +0xb3, 0x4e, 0xdc, 0xf8, 0xa2, 0x12, 0x1f, 0x4c, +0xaf, 0xd6, 0xac, 0x3a, 0x64, 0x93, 0x03, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0xb9, 0xc0, 0xc6, 0x6b, 0x56, 0xa8, 0x14, +0x5c, 0x48, 0xd6, 0x2b, 0xc8, 0xa7, 0xd9, 0x3b, +0x01, 0x29, 0x1f, 0x92, 0x1f, 0x05, 0xcc, 0x1e, +0x8b, 0x90, 0x31, 0x4f, 0x9f, 0x8e, 0x71, 0x9f, +0x59, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0xa9, 0x22, 0x45, 0x9e, 0x57, +0xc7, 0x74, 0xf9, 0xb9, 0xc8, 0x02, 0xd2, 0x69, +0x27, 0xcb, 0x4c, 0x68, 0x58, 0xe3, 0x3c, 0xb6, +0x7b, 0xe6, 0x3a, 0xf6, 0x25, 0x3c, 0x89, 0x84, +0xdc, 0x6d, 0xb6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x2a, 0xe3, 0x85, +0xfe, 0x22, 0xb2, 0x25, 0x0f, 0xf1, 0x18, 0x6a, +0x3e, 0x85, 0xec, 0xdc, 0x00, 0x2a, 0x75, 0x08, +0xc5, 0xab, 0x1d, 0xb7, 0x26, 0xfd, 0xe2, 0x17, +0x8c, 0x66, 0x6a, 0xc1, 0xd7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0x3d, +0xec, 0xd4, 0x1a, 0xe8, 0xac, 0x6c, 0xdb, 0x32, +0x7b, 0xa5, 0x8c, 0x7a, 0xe5, 0x70, 0x68, 0x36, +0x2d, 0x55, 0x1e, 0x9f, 0xd0, 0xf8, 0x06, 0x85, +0x97, 0x0c, 0x5b, 0x09, 0xe5, 0xf8, 0x5e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0xdd, 0x73, 0x33, 0x68, 0x4f, 0xf6, 0x67, +0x97, 0x1d, 0xab, 0x7b, 0x5c, 0x0e, 0xac, 0xda, +0x18, 0x95, 0xe8, 0x8a, 0xe4, 0x6b, 0x7a, 0xc7, +0x02, 0xf4, 0xcd, 0xa3, 0x28, 0x29, 0xc5, 0xd9, +0xa4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0x9b, 0xcf, 0xd0, 0xf1, 0x4a, +0x16, 0x11, 0x33, 0xa9, 0xc2, 0x66, 0x51, 0xc7, +0x22, 0xb5, 0xc0, 0x12, 0xda, 0x92, 0x8c, 0x53, +0xfc, 0x9e, 0x74, 0xc5, 0x56, 0x07, 0x0d, 0xe6, +0xd6, 0xfb, 0xae, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0xc0, 0x46, 0xd5, +0x81, 0xb1, 0xf7, 0xcc, 0xb8, 0x31, 0x88, 0x3d, +0x41, 0xdb, 0x05, 0x73, 0xe2, 0x99, 0xc0, 0xb4, +0xda, 0x0c, 0x32, 0xcf, 0x59, 0x16, 0x01, 0x49, +0xb6, 0x93, 0xcc, 0x2e, 0x0d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0xa8, +0x72, 0x13, 0x34, 0xb3, 0x73, 0xee, 0x8c, 0x92, +0x23, 0x21, 0xe1, 0x18, 0x62, 0xe1, 0xef, 0x95, +0x09, 0x70, 0x79, 0x7b, 0x47, 0x1d, 0x77, 0xaf, +0xf3, 0xc7, 0xe2, 0xb4, 0xc9, 0x87, 0x8a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0x12, 0x4f, 0xce, 0x4d, 0x72, 0x13, 0x77, +0xd5, 0xf9, 0x4b, 0x42, 0xfd, 0x67, 0x73, 0xaa, +0x74, 0xec, 0x39, 0xec, 0x56, 0x2a, 0x6d, 0x84, +0xc7, 0xf4, 0x4f, 0xdd, 0x52, 0xe2, 0xfa, 0x98, +0x3b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0xe7, 0xfc, 0x82, 0x62, 0xf5, +0x1b, 0xa4, 0x3a, 0xd3, 0x1a, 0x92, 0x1f, 0xc8, +0x22, 0xee, 0x60, 0x3e, 0x1d, 0x0c, 0xae, 0x56, +0xb1, 0xfa, 0xcb, 0xfd, 0x1a, 0xed, 0x6e, 0xa2, +0x13, 0x07, 0x8a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0x91, 0x7e, 0xf3, +0xd9, 0xdb, 0x2f, 0xc3, 0xad, 0xa5, 0xf7, 0x55, +0xef, 0xc2, 0x8f, 0x2c, 0x5b, 0xf8, 0x0c, 0xe1, +0xe1, 0xf6, 0x4b, 0x75, 0xbe, 0xf2, 0x63, 0xaa, +0xbc, 0xfa, 0xa8, 0xfd, 0xdb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0x5c, +0xcf, 0xf1, 0xf3, 0x64, 0x26, 0x65, 0xb5, 0xf8, +0xf5, 0x2f, 0x5f, 0x18, 0xa3, 0xc6, 0x3e, 0x34, +0xc2, 0x59, 0x2e, 0xc6, 0x71, 0x5c, 0x07, 0xf5, +0xc7, 0xae, 0xd4, 0xde, 0xf5, 0xaa, 0xad, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0x19, 0x26, 0xa2, 0xf9, 0x59, 0x4f, 0x69, +0x4c, 0x31, 0x3e, 0x24, 0x34, 0x30, 0x78, 0x19, +0x20, 0x8e, 0xbf, 0xc7, 0xc9, 0x40, 0x5d, 0xac, +0xaf, 0x66, 0xde, 0x77, 0x7f, 0x1d, 0x0d, 0x0c, +0x5b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0xdc, 0xab, 0xaf, 0x2e, 0x62, +0xe8, 0xe0, 0x53, 0x71, 0x80, 0xee, 0x77, 0xa9, +0x65, 0x97, 0x8f, 0xf0, 0xae, 0x97, 0xa5, 0xe0, +0xeb, 0xf7, 0xc5, 0x24, 0x63, 0x7e, 0x7c, 0x70, +0x2f, 0x2b, 0x72, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x92, 0x05, 0xbc, +0xaf, 0x81, 0xc8, 0x99, 0xcd, 0x1e, 0x58, 0x4b, +0xb4, 0x34, 0xb2, 0xa1, 0x1c, 0x7a, 0x39, 0x72, +0x08, 0x5a, 0xbb, 0xab, 0x8c, 0xe4, 0xb6, 0xb4, +0x0b, 0xae, 0xd7, 0x79, 0xf4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x63, +0xa6, 0x94, 0xd6, 0x21, 0xd6, 0x65, 0x7e, 0x1b, +0x6d, 0xeb, 0xe5, 0x07, 0x68, 0xbc, 0xa7, 0x93, +0xb3, 0xa3, 0x4b, 0x55, 0x7d, 0xff, 0x36, 0xa2, +0xcd, 0x0e, 0xe9, 0x43, 0x8f, 0x1f, 0x13, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0x58, 0xd2, 0x28, 0x0b, 0x6b, 0xda, 0xd2, +0x12, 0x68, 0x20, 0xd3, 0xe6, 0x8e, 0x06, 0x99, +0x74, 0x7d, 0xe0, 0xce, 0x39, 0x81, 0x90, 0x80, +0x4f, 0x81, 0xc2, 0x25, 0x56, 0xf1, 0x3b, 0x93, +0x2c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0x63, 0x09, 0xbd, 0xec, 0x65, +0xa6, 0x58, 0x4f, 0x31, 0xe6, 0xf6, 0xdd, 0xc3, +0x71, 0xa3, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xb0, 0xe4, 0x1f, 0xba, +0xed, 0x47, 0xa5, 0xdb, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0x3c, 0x61, 0x87, +0x0a, 0x83, 0x80, 0xaf, 0xde, 0xe0, 0x5f, 0x55, +0xc8, 0x53, 0x7c, 0xc1, 0x45, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x40, +0x6e, 0xa5, 0xd9, 0x13, 0xb3, 0x82, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0x02, +0xbe, 0x34, 0x42, 0x9e, 0xb8, 0xe9, 0xd5, 0x6d, +0x3c, 0x7c, 0x43, 0x31, 0x6c, 0xfc, 0xd5, 0x8f, +0x4a, 0xad, 0x13, 0x48, 0xe6, 0xb6, 0x9f, 0x9b, +0x31, 0xc3, 0x0d, 0x86, 0xf3, 0xbb, 0x85, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0xb6, 0xcc, 0x9f, 0xd1, 0x0a, 0x82, 0xda, +0xbf, 0xcb, 0x29, 0xe1, 0xe7, 0xa3, 0xe1, 0xee, +0x1f, 0x3c, 0x38, 0x4b, 0x3a, 0x5e, 0xf4, 0xb1, +0xff, 0x2c, 0x21, 0x21, 0x22, 0xe3, 0x0d, 0xa2, +0xb9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0xd1, 0x8e, 0xb3, 0x5f, 0xb1, +0x8a, 0x4d, 0xea, 0xff, 0x46, 0x0d, 0xd9, 0x68, +0xdd, 0x00, 0x4b, 0x6c, 0x14, 0x2d, 0xe3, 0x98, +0x60, 0x49, 0xa8, 0x19, 0x71, 0xfd, 0x23, 0xb5, +0xf6, 0xa3, 0x84, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0x8d, 0x5d, 0x63, +0xd4, 0x61, 0xb0, 0xd2, 0x4b, 0x8c, 0x90, 0x50, +0xb8, 0xa2, 0x7f, 0x2e, 0x5b, 0x48, 0x77, 0xe7, +0xe9, 0xa8, 0x64, 0x81, 0x4f, 0x6c, 0x5c, 0xd9, +0x08, 0x9f, 0x61, 0xf3, 0xb0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0xbd, +0x76, 0x03, 0x70, 0x1b, 0x9d, 0xce, 0x2c, 0xcb, +0xa2, 0xf1, 0xd1, 0xd3, 0xfc, 0x0b, 0x85, 0xd7, +0x34, 0xc5, 0xd8, 0x00, 0xcd, 0x5a, 0x26, 0xa7, +0x1c, 0x53, 0xcd, 0xc6, 0x7b, 0x7f, 0xc5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0x6f, 0x85, 0xd2, 0x65, 0xce, 0xb8, 0xa3, +0xf6, 0xa3, 0x2f, 0x91, 0x02, 0xf1, 0x55, 0xc7, +0x07, 0x77, 0x87, 0xaf, 0xf2, 0x72, 0x08, 0x3f, +0x25, 0x6f, 0xee, 0x1b, 0x6a, 0xb6, 0x0e, 0xfe, +0xc6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0xb6, 0x75, 0x79, 0xc3, 0xdd, +0x85, 0x4e, 0xb2, 0xed, 0xd4, 0xdb, 0x54, 0xc1, +0x87, 0x4b, 0xe8, 0xf1, 0x29, 0xfc, 0x6d, 0xe5, +0x45, 0xa8, 0xda, 0x15, 0x83, 0x61, 0x78, 0xd5, +0x68, 0xa9, 0x3e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0x18, 0x4a, 0x74, +0x38, 0x0a, 0xb9, 0x4e, 0xf3, 0x20, 0xdc, 0x88, +0xae, 0x9b, 0x9e, 0x94, 0xde, 0x3a, 0xe4, 0x74, +0x11, 0x1e, 0x83, 0x5b, 0xb6, 0x7b, 0x17, 0xa1, +0x28, 0x00, 0xd5, 0x30, 0x81, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0x12, +0xaf, 0x2f, 0xd9, 0xed, 0x08, 0xcc, 0xb6, 0x8a, +0xce, 0x96, 0x37, 0x91, 0xb1, 0x5a, 0x54, 0x19, +0x29, 0x3e, 0x69, 0x57, 0xe9, 0xb5, 0x10, 0x42, +0x4c, 0x6c, 0x20, 0x97, 0x0f, 0x34, 0x58, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0xc0, 0x78, 0xc4, 0xe2, 0x1e, 0x5e, 0xbf, +0x3c, 0x98, 0xef, 0xe2, 0xc8, 0x4c, 0x30, 0x67, +0x69, 0x89, 0xf6, 0xb9, 0x53, 0xfe, 0x4b, 0x16, +0x67, 0xf1, 0x75, 0x34, 0x16, 0xea, 0x59, 0x18, +0x42, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0x34, 0x84, 0xf4, 0xd3, 0x6a, +0x57, 0x64, 0xba, 0xc9, 0x77, 0x40, 0xf9, 0xec, +0x45, 0xa8, 0x05, 0xe8, 0x80, 0x72, 0x4f, 0x79, +0x0e, 0x8c, 0x2a, 0xcd, 0xc8, 0xfe, 0xd6, 0x06, +0x05, 0x86, 0x9d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0xd7, 0x35, 0x7b, +0x8d, 0xd3, 0xbe, 0x6d, 0xe1, 0xe3, 0x91, 0xf4, +0x9f, 0x0b, 0xad, 0x19, 0xca, 0x50, 0x76, 0xde, +0x26, 0x17, 0x1d, 0x8b, 0x71, 0xb5, 0x12, 0x3a, +0x70, 0x96, 0xdb, 0x34, 0x90, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0x84, +0x60, 0xf3, 0x1e, 0x1f, 0xe2, 0x02, 0xdf, 0x93, +0x0d, 0xde, 0xdb, 0x8f, 0x58, 0xde, 0xe5, 0x5a, +0x5b, 0x9a, 0x47, 0x60, 0x79, 0x65, 0x4e, 0x24, +0x07, 0xe2, 0x03, 0x9a, 0x4d, 0x31, 0x53, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0x97, 0x31, 0xcd, 0x30, 0xcc, 0x40, 0xbe, +0xa4, 0xf9, 0x87, 0x26, 0xfd, 0x73, 0xc0, 0x9c, +0x1c, 0xed, 0x08, 0x2b, 0x2d, 0x18, 0x4c, 0xbd, +0x40, 0x84, 0xb0, 0xbf, 0xb4, 0xe9, 0xfd, 0xdb, +0x70, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0xe5, 0xcb, 0x9f, 0xbf, 0xe8, +0x6d, 0x3e, 0x32, 0x7a, 0xf7, 0xfb, 0x9a, 0x06, +0x75, 0x70, 0x85, 0xd1, 0x82, 0xd5, 0x51, 0xa0, +0x8e, 0x64, 0x6b, 0xbc, 0xa2, 0xf9, 0x7b, 0x65, +0x6b, 0xf6, 0x0c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0xb4, 0x52, 0x3f, +0xe6, 0xfa, 0x76, 0x27, 0x1b, 0xcc, 0xc4, 0xfc, +0x0e, 0x2d, 0x3c, 0xd8, 0xc3, 0x71, 0xd1, 0xd5, +0x46, 0x0b, 0x70, 0xd6, 0x93, 0x38, 0xb2, 0x13, +0x99, 0xf8, 0x2f, 0xb2, 0x02, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0x36, +0x80, 0x9d, 0x43, 0x35, 0xb0, 0xb3, 0xf2, 0x5f, +0x1a, 0x0d, 0x99, 0x99, 0x1d, 0x16, 0x1a, 0xd0, +0xef, 0x42, 0x69, 0xbc, 0x6d, 0x9a, 0x57, 0x18, +0xfe, 0x6f, 0x88, 0xdf, 0x7b, 0x20, 0x7d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0x52, 0xad, 0x50, 0xde, 0x1e, 0xdf, 0x59, +0xd8, 0xaa, 0xdb, 0x27, 0x4b, 0xdf, 0xe2, 0x11, +0x9b, 0x40, 0xef, 0x50, 0x47, 0x6a, 0x87, 0xcc, +0x39, 0xa5, 0x78, 0xe0, 0xee, 0xde, 0x17, 0x13, +0xe3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0xcd, 0x73, 0x25, 0x8e, 0x3d, +0x72, 0x21, 0x84, 0x15, 0x01, 0x58, 0x5d, 0xd1, +0x4b, 0x11, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x82, 0x7a, 0x57, 0xc6, +0x76, 0x28, 0x22, 0x30, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0xaf, 0x54, 0xe6, +0xae, 0x45, 0x33, 0x7d, 0x03, 0x0f, 0x82, 0xaf, +0xfe, 0x34, 0xbc, 0x12, 0x41, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6d, 0xfc, +0xbe, 0x5e, 0xcf, 0xfb, 0x24, 0x22, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0xd3, +0xdb, 0x26, 0xf2, 0xc1, 0x3d, 0xbd, 0xaa, 0x2e, +0x61, 0x6f, 0xcd, 0xf8, 0xd1, 0x86, 0x76, 0x82, +0xca, 0x08, 0xdf, 0x16, 0x92, 0xea, 0x77, 0xab, +0x49, 0x9b, 0x72, 0xd1, 0xd4, 0xc0, 0x82, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0xed, 0x6a, 0x84, 0x4f, 0x9f, 0xb5, 0x0c, +0x38, 0xf5, 0xce, 0xb7, 0x7b, 0x00, 0xbe, 0x8a, +0x33, 0x27, 0x3a, 0x47, 0x94, 0xc6, 0x8f, 0xb7, +0x3b, 0x6c, 0xaf, 0xe2, 0x7a, 0x37, 0x98, 0x37, +0xf5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0x35, 0xd1, 0xb5, 0xc1, 0x87, +0x1a, 0xd1, 0x4c, 0x99, 0x75, 0x80, 0x29, 0x23, +0xb0, 0xc0, 0xfc, 0xa3, 0x85, 0x1d, 0xce, 0xea, +0xac, 0xda, 0xc6, 0x9e, 0x0f, 0x5d, 0xb9, 0xd1, +0xc2, 0x8b, 0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0x02, 0xe0, 0xbe, +0xe2, 0xe2, 0xc2, 0x79, 0x38, 0x9b, 0xc7, 0xdf, +0x9c, 0x7d, 0x74, 0x6d, 0xa9, 0x5b, 0x7b, 0x51, +0xb2, 0x82, 0x6b, 0x16, 0xf5, 0xd8, 0x76, 0xe9, +0xf6, 0xa0, 0xc2, 0xa3, 0x09, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0x13, +0x50, 0x45, 0xa9, 0x58, 0xde, 0xe5, 0x80, 0x7e, +0xa8, 0x1f, 0x39, 0x7f, 0x27, 0x06, 0xb9, 0x9a, +0x22, 0xfd, 0xac, 0x52, 0x79, 0xe2, 0xad, 0x7e, +0x21, 0x8f, 0x1e, 0x12, 0x92, 0x73, 0xe1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0x52, 0x5b, 0x9b, 0x62, 0xd4, 0x9f, 0xa8, +0xd1, 0x92, 0xea, 0x40, 0xb8, 0x54, 0xa9, 0xa8, +0xd8, 0xa2, 0x29, 0x55, 0xc2, 0xc7, 0xc4, 0x5e, +0xa5, 0x23, 0xb0, 0xe0, 0x3d, 0x11, 0xfd, 0xb0, +0x3c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0xea, 0xd0, 0xf7, 0x0e, 0xde, +0x06, 0xa9, 0x1f, 0xe8, 0xc0, 0x3b, 0x58, 0xb3, +0x24, 0x26, 0xb1, 0x66, 0xd4, 0x5c, 0xe6, 0xe4, +0xc0, 0xbf, 0xf4, 0x44, 0x8b, 0xcc, 0x98, 0x24, +0x49, 0xc3, 0x04, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0xdb, 0xf1, 0x6b, +0xf7, 0x91, 0xd2, 0x54, 0x56, 0xee, 0x5c, 0xaf, +0x55, 0x1c, 0xa1, 0x46, 0xbc, 0x69, 0xfb, 0xdb, +0x80, 0x55, 0xee, 0x20, 0x3b, 0x8b, 0x2b, 0x4f, +0x38, 0x05, 0x2b, 0x8a, 0x66, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0x6f, +0x94, 0xd0, 0xb6, 0xbb, 0x04, 0x3c, 0x72, 0x46, +0xfc, 0x07, 0x11, 0x7d, 0x41, 0x43, 0x20, 0x6d, +0xf7, 0xf2, 0x16, 0x86, 0x32, 0xcc, 0x5b, 0x19, +0xed, 0xa7, 0xd2, 0xef, 0xd4, 0xbf, 0x11, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0x14, 0xc8, 0xea, 0x72, 0xd0, 0x66, 0x3a, +0x12, 0x0e, 0xa4, 0x0b, 0x8d, 0xaf, 0x7c, 0xc6, +0xd3, 0xe5, 0x33, 0x88, 0x83, 0x09, 0x5f, 0x0e, +0x26, 0xa8, 0x56, 0xea, 0x97, 0x9a, 0x82, 0x32, +0xd9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0xf4, 0x0c, 0x07, 0xb7, 0xb9, +0x6d, 0x25, 0xe5, 0xa9, 0xef, 0xf2, 0x52, 0xc4, +0xf5, 0x86, 0x9c, 0xf5, 0xb0, 0xaa, 0x81, 0x93, +0x2c, 0xfd, 0x09, 0xae, 0xf6, 0xb6, 0xcd, 0x69, +0xe7, 0x1c, 0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0x2a, 0x2f, 0xc2, +0xb5, 0xed, 0x1e, 0x64, 0x98, 0x42, 0x48, 0x2c, +0x6c, 0x0b, 0x59, 0x1c, 0x08, 0x9e, 0xcb, 0x9c, +0xf7, 0x54, 0x80, 0x4a, 0xef, 0x9a, 0xd6, 0xf9, +0xef, 0x42, 0x6d, 0xfd, 0xa4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0x23, +0x82, 0xc7, 0x42, 0x24, 0x5d, 0x6e, 0xf7, 0x78, +0xe4, 0xbe, 0xd2, 0xaa, 0x95, 0x36, 0x7a, 0x2a, +0x17, 0x92, 0x4d, 0xb1, 0xd9, 0x39, 0x12, 0x64, +0x86, 0x15, 0x16, 0x08, 0x00, 0x42, 0x1b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0xc4, 0x03, 0x74, 0xba, 0xe6, 0xa8, 0xd1, +0x12, 0x01, 0xd3, 0x59, 0xda, 0xa3, 0x7a, 0x9b, +0x0f, 0x7e, 0x4f, 0xb5, 0x25, 0xe0, 0x40, 0xd8, +0xe3, 0xc6, 0x3d, 0x5c, 0x3d, 0xfd, 0x5b, 0x78, +0xf6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0x1a, 0x51, 0x3b, 0xb4, 0x3d, +0xaf, 0x27, 0xb8, 0x41, 0x42, 0x3d, 0xab, 0x64, +0x3e, 0x11, 0x74, 0x9f, 0x0b, 0xd7, 0x41, 0xdd, +0xf3, 0xfb, 0xd7, 0xd4, 0xa6, 0x3c, 0x2d, 0xb0, +0xe1, 0x58, 0xbd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0x23, 0xca, 0x2b, +0xc4, 0x28, 0x4b, 0x05, 0xa7, 0x37, 0x7a, 0xf6, +0x69, 0xac, 0x64, 0x66, 0x7a, 0x4c, 0x42, 0x80, +0x1f, 0xcc, 0x9b, 0xae, 0xe7, 0xb3, 0xe5, 0xee, +0x8e, 0x27, 0x0c, 0xac, 0x76, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0xf1, +0xbe, 0xb8, 0x5d, 0xf2, 0xe0, 0x32, 0xe4, 0xc6, +0x43, 0x90, 0xb3, 0xc3, 0x91, 0x39, 0x64, 0x1a, +0x53, 0x3b, 0xf7, 0x8a, 0x91, 0x3f, 0x77, 0x32, +0xa1, 0xc7, 0x24, 0x23, 0x94, 0xdf, 0xa6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0x4a, 0x02, 0xde, 0x29, 0x8a, 0xdc, 0xd2, +0xaf, 0x3d, 0x30, 0xe6, 0x30, 0x55, 0xb9, 0x9d, +0x4d, 0x3c, 0x65, 0x8e, 0xc4, 0x46, 0xab, 0xc7, +0xd1, 0x79, 0xf6, 0x3c, 0x5b, 0x7b, 0x5c, 0x05, +0x12, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0xba, 0x0b, 0xed, 0x93, 0x80, +0x43, 0x09, 0xbe, 0xc0, 0xc9, 0x7d, 0x65, 0x91, +0xdd, 0x4b, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xec, 0xb8, 0xa0, 0x47, +0x30, 0x81, 0xdc, 0xd1, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0xce, 0x81, 0xef, +0x2f, 0x54, 0x2a, 0x24, 0x02, 0x97, 0x4a, 0x96, +0xd7, 0xdc, 0xa8, 0xba, 0xb5, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe7, 0x42, +0x88, 0x02, 0x1f, 0x47, 0x72, 0x85, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0x9a, +0xd9, 0xd2, 0x8c, 0x98, 0xf7, 0xc7, 0x02, 0x5a, +0xd4, 0x73, 0xd1, 0x4e, 0xf7, 0xa3, 0x61, 0xe7, +0x1d, 0x7f, 0x45, 0x18, 0xb3, 0x57, 0xd3, 0xb8, +0xea, 0x8a, 0xa0, 0x27, 0xce, 0xa7, 0x8b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0x00, 0x93, 0x81, 0x58, 0x01, 0xf2, 0x21, +0xc1, 0x46, 0x12, 0xd3, 0x88, 0xbe, 0x8b, 0x2c, +0x96, 0xa7, 0x16, 0x01, 0x91, 0x26, 0x71, 0x79, +0x77, 0xe8, 0xe7, 0xf4, 0x11, 0xf1, 0x13, 0xeb, +0x92, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0xdb, 0x66, 0xac, 0x71, 0xcb, +0x6f, 0x73, 0xa7, 0x19, 0xd8, 0x85, 0x75, 0x2f, +0x89, 0xf9, 0x9b, 0x2c, 0x2d, 0xf6, 0x0f, 0x99, +0x51, 0x27, 0x03, 0x36, 0x65, 0xed, 0x07, 0x35, +0xde, 0x10, 0x7d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0xfe, 0xdd, 0x10, +0xcb, 0xcc, 0x5e, 0xde, 0x9c, 0xfa, 0x89, 0xed, +0xf0, 0x32, 0xf9, 0x32, 0x85, 0xcf, 0xa5, 0x38, +0x83, 0xc9, 0x41, 0x42, 0xfe, 0xdb, 0x37, 0x91, +0x08, 0xf4, 0x5e, 0x2a, 0xc8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0xec, +0x8a, 0x31, 0x8f, 0x96, 0xe9, 0x57, 0x5a, 0xd1, +0x43, 0xae, 0xcd, 0x19, 0x20, 0xe0, 0x5c, 0x12, +0xab, 0xee, 0xe8, 0xd7, 0xd4, 0x23, 0xc5, 0x0f, +0x84, 0x9b, 0xa0, 0x47, 0xbe, 0xe4, 0x9d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0x00, 0x29, 0xc2, 0x13, 0x77, 0xd8, 0x95, +0x16, 0x66, 0xf0, 0x4b, 0x6c, 0x80, 0xa0, 0x6f, +0x65, 0x85, 0x3f, 0xd7, 0xd6, 0x02, 0x94, 0x15, +0x36, 0x2a, 0x3f, 0x54, 0x70, 0xb5, 0x0e, 0xe8, +0x72, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0xaa, 0xd2, 0x2e, 0xdc, 0x54, +0xf7, 0x10, 0x6c, 0x28, 0x56, 0x40, 0x62, 0x91, +0xc4, 0x62, 0x7c, 0xf3, 0x0a, 0x55, 0xca, 0x8d, +0x3e, 0x48, 0x6f, 0x37, 0xae, 0x4b, 0x9e, 0x7d, +0xee, 0x59, 0x57, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0xba, 0x8f, 0x4f, +0xd9, 0x44, 0x5d, 0x1a, 0xe7, 0x53, 0xd8, 0x6a, +0xb5, 0x3b, 0xaf, 0x78, 0x7a, 0x4d, 0xbb, 0xe2, +0xa0, 0x32, 0x6c, 0x47, 0x98, 0xb5, 0xc8, 0xcf, +0xeb, 0xa3, 0xca, 0xe0, 0x96, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0x23, +0xd9, 0x75, 0x08, 0xeb, 0xe0, 0xe4, 0x66, 0x82, +0x35, 0x77, 0x34, 0x3f, 0x68, 0xb9, 0x21, 0x81, +0x4d, 0xa2, 0x47, 0x9c, 0xd8, 0x0f, 0xf6, 0x68, +0x9d, 0x14, 0x4c, 0xd9, 0x48, 0x03, 0x3e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0xde, 0xfe, 0xe2, 0x5e, 0x37, 0xe3, 0xbc, +0xd3, 0xbe, 0x30, 0xae, 0x72, 0x0e, 0xe3, 0xca, +0xc7, 0x66, 0x54, 0x3a, 0xf4, 0x42, 0x61, 0x94, +0xac, 0x79, 0xbc, 0x00, 0x0a, 0x11, 0xd9, 0xc0, +0x30, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0xa9, 0x74, 0x6e, 0xcc, 0x13, +0x8c, 0x82, 0xd6, 0xed, 0x12, 0xf1, 0xd2, 0xed, +0x83, 0xbc, 0x04, 0xe3, 0x8a, 0x10, 0xe6, 0x5c, +0xa1, 0x31, 0x87, 0xe2, 0xda, 0x5b, 0xdb, 0x04, +0x89, 0xad, 0x26, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0x9a, 0x6a, 0x8a, +0xf9, 0x5a, 0x73, 0xf8, 0x56, 0xf2, 0x1e, 0xcb, +0xcd, 0x6c, 0xaf, 0xa6, 0x30, 0x1c, 0x27, 0xa4, +0xab, 0x47, 0xd9, 0x49, 0x2a, 0x3d, 0xc5, 0xca, +0xee, 0xe6, 0x23, 0x0d, 0x24, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0xdf, +0xbd, 0x2e, 0x99, 0x10, 0x5a, 0xbc, 0xda, 0x8f, +0xa1, 0xb5, 0x30, 0x9d, 0x01, 0xea, 0xf9, 0xd2, +0xc5, 0x31, 0x4a, 0xf1, 0x51, 0x1a, 0x34, 0x02, +0x92, 0x8e, 0x23, 0x6d, 0x72, 0xdb, 0x69, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0x84, 0x8e, 0x7b, 0xd5, 0xa8, 0xb5, 0x2e, +0xa6, 0x5a, 0xd2, 0x76, 0x9a, 0xde, 0x9d, 0x5e, +0xb5, 0xc9, 0x26, 0x9d, 0xe7, 0xef, 0x96, 0x88, +0x92, 0xe7, 0x10, 0x40, 0xea, 0xc5, 0x50, 0x0a, +0x2b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0xf8, 0x25, 0xb7, 0xdc, 0xd0, +0x2d, 0xff, 0xbb, 0xce, 0x74, 0x77, 0x78, 0x5c, +0xdf, 0x71, 0x22, 0x67, 0x8f, 0x9e, 0x7e, 0xa9, +0x51, 0xa0, 0x43, 0x94, 0xf8, 0x24, 0xe0, 0x9b, +0x0d, 0x9a, 0xcd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0x19, 0x04, 0x16, +0x07, 0x6d, 0xac, 0xda, 0xc4, 0x27, 0x2e, 0xc8, +0x29, 0xfd, 0x1d, 0x4c, 0x89, 0x3d, 0x93, 0x71, +0xab, 0x22, 0xee, 0xa4, 0x27, 0x8a, 0xaf, 0x3f, +0x46, 0xfe, 0x60, 0x06, 0x7e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0x28, +0x97, 0xa4, 0xf0, 0x1d, 0x8f, 0x49, 0xce, 0x13, +0x29, 0x2e, 0x35, 0x22, 0xa7, 0x9a, 0xcb, 0x13, +0x8f, 0x5f, 0x17, 0x9c, 0x2d, 0x50, 0xba, 0xfd, +0xe9, 0x6d, 0x3f, 0x79, 0x7a, 0x3e, 0x34, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0xc8, 0xf8, 0xfa, 0x20, 0x27, 0xfb, 0x5a, +0x53, 0x4b, 0x31, 0xd7, 0xe9, 0x16, 0xb6, 0xad, +0x80, 0x08, 0xdd, 0x70, 0xe0, 0x98, 0x58, 0x00, +0xac, 0x96, 0xcd, 0x7b, 0xd4, 0x23, 0x5d, 0xbe, +0x44, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0x42, 0xb1, 0xf9, 0x74, 0x1b, +0xf4, 0x5a, 0x55, 0x1f, 0x27, 0xe5, 0xe0, 0x6a, +0x10, 0x2e, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x55, 0x91, 0x50, 0x88, +0xeb, 0xde, 0x64, 0xd1, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x74, 0xc4, 0x2a, +0x68, 0x2f, 0x41, 0xd4, 0xd7, 0x2b, 0x1a, 0xde, +0xaa, 0x94, 0xed, 0x71, 0xa7, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x45, +0x0a, 0x6b, 0x65, 0x6c, 0xe7, 0xb0, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0xd2, +0x7a, 0x05, 0xd2, 0xeb, 0x6e, 0x54, 0x22, 0x63, +0x3a, 0xca, 0x1c, 0x20, 0x57, 0xa4, 0xdc, 0x90, +0x55, 0x2f, 0xec, 0x4c, 0xae, 0x29, 0x41, 0x71, +0xbd, 0xd3, 0x4e, 0x42, 0xd3, 0x3a, 0x33, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0xcb, 0x4f, 0xf7, 0xff, 0x18, 0xb2, 0xee, +0x05, 0x20, 0x23, 0x0e, 0x4a, 0x27, 0x72, 0x78, +0x8e, 0x51, 0xd7, 0xa8, 0x04, 0x1a, 0xa7, 0x9f, +0x2c, 0x14, 0x76, 0x63, 0xa4, 0xcc, 0x81, 0x14, +0xf7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0xce, 0x78, 0x74, 0x1d, 0xd2, +0x6d, 0x5d, 0xd1, 0xf8, 0x2a, 0x82, 0x89, 0x6d, +0xd9, 0x73, 0x6f, 0x9f, 0x32, 0x62, 0x39, 0x1b, +0xa5, 0xb3, 0x81, 0x15, 0xb3, 0x4f, 0xda, 0x87, +0x5c, 0x26, 0xc8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x4b, 0xa7, 0xdd, +0xc9, 0x57, 0x24, 0x24, 0x36, 0xbd, 0xdd, 0xc0, +0x9e, 0x52, 0x92, 0x61, 0xd8, 0xee, 0xce, 0xf5, +0xa2, 0x57, 0x2a, 0x42, 0xbe, 0xf5, 0x87, 0x7a, +0xcb, 0xa6, 0x54, 0x28, 0xd1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0x4f, +0xb2, 0x60, 0xf0, 0xef, 0x00, 0x21, 0xb6, 0x9a, +0xc3, 0x44, 0x88, 0x5d, 0x56, 0xcb, 0xbb, 0xf2, +0x78, 0x6e, 0x28, 0x29, 0xe8, 0xb5, 0xe4, 0x94, +0x9f, 0xc8, 0xc6, 0x16, 0xe5, 0x24, 0x17, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0xf7, 0x2d, 0xb8, 0x4b, 0x71, 0xb1, 0xae, +0x69, 0xab, 0x65, 0x6b, 0x20, 0x3a, 0xef, 0x5c, +0xbc, 0xe6, 0x1f, 0xd8, 0x51, 0x61, 0xeb, 0xc6, +0xe1, 0x91, 0xca, 0x7a, 0x04, 0x40, 0x32, 0xb8, +0x4a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0x5f, 0x41, 0x5f, 0x85, 0x5c, +0x6a, 0xe0, 0xc1, 0xe3, 0xd9, 0x4a, 0xb7, 0x93, +0x4e, 0xaa, 0x2d, 0xd3, 0x80, 0xe7, 0x10, 0x73, +0x06, 0xd7, 0x4f, 0xb3, 0x5e, 0x40, 0xb4, 0xa4, +0x33, 0xfc, 0x7c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0xa9, 0xd4, 0x66, +0x62, 0x64, 0x51, 0x7e, 0xb2, 0x92, 0x44, 0x2e, +0x60, 0x70, 0x1a, 0xe8, 0xa2, 0xb2, 0x6d, 0x1d, +0x7b, 0x8d, 0xfd, 0xd1, 0x0c, 0xc8, 0x02, 0xd9, +0xa6, 0x19, 0x07, 0x23, 0x05, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x78, +0x2c, 0x60, 0x40, 0x0d, 0x3e, 0x07, 0xcd, 0x00, +0x3d, 0x21, 0x1f, 0xbb, 0x2b, 0x10, 0xd3, 0xf1, +0x56, 0xa7, 0x5c, 0x4f, 0x96, 0xf6, 0x32, 0x5e, +0x43, 0xa7, 0xec, 0x15, 0xec, 0x0b, 0xad, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0x42, 0xc4, 0xaa, 0x1c, 0xc9, 0x46, 0x4a, +0xbc, 0x89, 0x39, 0xc7, 0x88, 0x95, 0x0a, 0x5e, +0x50, 0xb3, 0x1a, 0x13, 0x8d, 0x1a, 0x90, 0xed, +0x6b, 0xa4, 0xea, 0xa6, 0x35, 0x18, 0xba, 0x59, +0x01, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0x13, 0xb9, 0x7c, 0xd0, 0x47, +0xae, 0x77, 0x69, 0xa1, 0xbb, 0x70, 0x61, 0x70, +0x47, 0xed, 0x74, 0x24, 0x47, 0x71, 0xc7, 0x30, +0x57, 0x34, 0x49, 0x0a, 0xf4, 0x49, 0x0f, 0x67, +0x30, 0x9b, 0xf8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0x5f, 0x78, 0xab, +0x58, 0x7e, 0x94, 0xe8, 0x5e, 0xdc, 0x69, 0x09, +0x2e, 0xf6, 0xe2, 0x1b, 0x7f, 0x3f, 0x60, 0xcb, +0x5e, 0x0c, 0x8a, 0xa4, 0x63, 0x63, 0x89, 0x80, +0xe1, 0x66, 0x6d, 0x0c, 0x52, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0x4e, +0x7e, 0xa1, 0x0f, 0xfd, 0xee, 0xb8, 0x48, 0x37, +0x0a, 0xb8, 0xa1, 0x05, 0x8e, 0xe0, 0x61, 0xf0, +0x13, 0x97, 0xa1, 0x20, 0xa6, 0x63, 0x52, 0x53, +0x5f, 0xdc, 0xab, 0xeb, 0x8c, 0xd1, 0x54, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0x2c, 0xd3, 0x8c, 0x0a, 0x4f, 0x88, 0x62, +0x76, 0xfb, 0xd8, 0x95, 0x30, 0x3d, 0x86, 0xe7, +0x98, 0x4e, 0xf8, 0x86, 0x5c, 0x6c, 0x4c, 0x2d, +0x33, 0x72, 0xc9, 0x5a, 0x59, 0x51, 0xcc, 0x55, +0x21, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0x4d, 0x11, 0x32, 0xe9, 0x4d, +0x16, 0x0d, 0x23, 0x00, 0x38, 0x9a, 0x2c, 0x32, +0x4d, 0xbc, 0x0d, 0x39, 0x55, 0x1a, 0x3c, 0x1d, +0xd6, 0x16, 0x83, 0x35, 0xba, 0xbf, 0x87, 0x4e, +0x52, 0xcb, 0xe7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0x35, 0xf8, 0xcd, +0xc1, 0x31, 0xe0, 0x64, 0x33, 0x8f, 0x21, 0xb9, +0xf8, 0x91, 0x01, 0xb6, 0xe1, 0x82, 0x38, 0x56, +0x68, 0xf7, 0x68, 0x20, 0x1c, 0x65, 0xc0, 0x80, +0xbf, 0xd3, 0x76, 0x58, 0xeb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0x68, +0xca, 0xda, 0x0f, 0xbd, 0xa0, 0x7f, 0xfe, 0x38, +0x0b, 0x75, 0xeb, 0xf7, 0x0e, 0x2a, 0x05, 0x51, +0x9d, 0xe6, 0x41, 0x7c, 0xd1, 0xee, 0xf6, 0x50, +0xba, 0x22, 0x86, 0xcb, 0xdc, 0x2a, 0xc5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0xb2, 0x72, 0xb0, 0x42, 0x21, 0x8c, 0xb6, +0xda, 0x34, 0x3c, 0x5c, 0x8d, 0x15, 0xde, 0x69, +0xf5, 0x37, 0xc6, 0xf8, 0x23, 0x66, 0xf4, 0x68, +0x49, 0x3f, 0x86, 0xd2, 0x44, 0x02, 0xc5, 0x0d, +0xdc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0x78, 0xa1, 0xa2, 0x53, 0x7b, +0xd3, 0x88, 0x99, 0x9a, 0x6f, 0x18, 0xbf, 0x17, +0xb2, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xb2, 0xc6, 0x07, 0x05, +0x7e, 0x3a, 0x1b, 0xe3, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0xa8, 0xcd, 0x02, +0xb7, 0x8d, 0xc1, 0x78, 0xe5, 0xde, 0xdf, 0xb2, +0xd5, 0x35, 0x2b, 0xe4, 0x87, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd9, 0xa0, +0x07, 0xa5, 0x1e, 0xc5, 0x59, 0x8f, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0x97, +0xf6, 0x83, 0x22, 0x65, 0x66, 0x7a, 0x57, 0xa4, +0x48, 0xf5, 0x39, 0x42, 0x07, 0xa8, 0x5e, 0xd4, +0xbd, 0x27, 0xf3, 0x3c, 0x6e, 0x09, 0x09, 0x6e, +0x51, 0xa7, 0x12, 0xef, 0x3e, 0x1d, 0xac, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0xaa, 0x94, 0xaf, 0x88, 0x4d, 0xf6, 0x5c, +0x33, 0x24, 0xa1, 0xe9, 0x2d, 0x0a, 0x31, 0x33, +0x2d, 0x30, 0x82, 0x71, 0x4d, 0x91, 0x65, 0x0d, +0x2b, 0xf3, 0x09, 0xeb, 0x4f, 0x05, 0x3a, 0x86, +0x36, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0x73, 0xb5, 0xd8, 0x6f, 0x5c, +0xbb, 0x92, 0xfc, 0x99, 0xe0, 0x52, 0x1a, 0x80, +0x99, 0x80, 0x8c, 0xc1, 0xf2, 0x3c, 0x03, 0xee, +0xdc, 0xf7, 0xef, 0x23, 0x83, 0xd6, 0x01, 0x3a, +0x55, 0x82, 0xb5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0xa0, 0xa8, 0xb2, +0x57, 0x4c, 0xb2, 0x3c, 0x18, 0x3d, 0x84, 0xeb, +0xf5, 0x08, 0x61, 0x88, 0x71, 0x29, 0x80, 0x6d, +0xa7, 0xf3, 0x16, 0x98, 0x5d, 0x62, 0x04, 0x04, +0xa5, 0x26, 0xca, 0xea, 0xb5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0xf1, +0x51, 0x2d, 0xe0, 0xae, 0xe2, 0xdf, 0x50, 0x59, +0x6b, 0xb4, 0x39, 0x0b, 0x11, 0x55, 0x4b, 0x9d, +0x8e, 0x38, 0x27, 0xa7, 0x8d, 0x1d, 0x9a, 0x0c, +0x1f, 0x3a, 0x2b, 0x69, 0x1e, 0xa2, 0xe6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x91, 0x67, 0x01, 0x93, 0x2e, 0x4b, 0xca, +0x2f, 0x9e, 0x01, 0x66, 0x6e, 0x8a, 0x3f, 0xf6, +0x0f, 0x46, 0x93, 0x93, 0xa0, 0xd0, 0x10, 0xc9, +0x5f, 0x73, 0x1c, 0x86, 0x11, 0x90, 0xcf, 0xce, +0x7a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0xac, 0x9f, 0x17, 0x0b, 0x2a, +0x9c, 0xea, 0x02, 0xb3, 0x5d, 0x3c, 0xc9, 0x3f, +0x2b, 0xed, 0xae, 0x4b, 0xae, 0x10, 0x8c, 0xd4, +0x62, 0xe7, 0x7d, 0xc0, 0xdd, 0xe9, 0xbd, 0xb7, +0x96, 0x20, 0x26, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0xfe, 0xf9, 0x5e, +0x7d, 0x36, 0x27, 0x01, 0x54, 0x38, 0xfd, 0xbc, +0x72, 0x0c, 0x4a, 0xf1, 0x7f, 0x02, 0x06, 0x18, +0xef, 0xef, 0xbf, 0x84, 0x4c, 0xa3, 0x3c, 0x0a, +0x33, 0xdc, 0xe3, 0xc7, 0xe2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0x44, +0x5a, 0x8a, 0xd1, 0xb9, 0xd7, 0xdb, 0xb2, 0x89, +0x03, 0x63, 0x16, 0x6b, 0x8b, 0x3f, 0xcb, 0x64, +0xfc, 0x29, 0x73, 0xf4, 0x5e, 0x28, 0x27, 0xe2, +0x42, 0x57, 0x9c, 0x4b, 0x61, 0x70, 0x9d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0x3d, 0xa8, 0xe2, 0x5b, 0x0c, 0x5a, 0x97, +0x7a, 0x7b, 0x45, 0x93, 0xa6, 0xfb, 0x17, 0x50, +0xc6, 0x4a, 0xa1, 0x46, 0x4a, 0x0c, 0x67, 0xe2, +0xfb, 0xa1, 0xc4, 0x53, 0x48, 0xfd, 0x3e, 0xd4, +0x24, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0x48, 0x9f, 0x16, 0x2e, 0x62, +0x90, 0xd1, 0x12, 0x94, 0xc9, 0xa0, 0xd7, 0x6b, +0xac, 0x09, 0x64, 0xa4, 0x78, 0x79, 0x27, 0x2c, +0x30, 0x3a, 0x0f, 0xe2, 0x74, 0x7b, 0xc0, 0xfa, +0xcd, 0xfa, 0xa7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0x01, 0xbc, 0xda, +0xc0, 0x90, 0x52, 0xb0, 0x13, 0x33, 0xbb, 0x8a, +0xf4, 0xbc, 0x39, 0x7f, 0xa7, 0x93, 0xbe, 0x67, +0x7c, 0x00, 0xaa, 0x2c, 0x30, 0x08, 0x31, 0x41, +0x49, 0xa7, 0xe0, 0xd7, 0x29, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0x4c, +0x5d, 0x48, 0x04, 0x7e, 0x85, 0x0c, 0x48, 0x5b, +0x6d, 0xe5, 0x4b, 0xa3, 0x33, 0x49, 0x6a, 0xf1, +0x24, 0xeb, 0xfa, 0xe9, 0x38, 0xbf, 0xc5, 0x83, +0xb2, 0xdc, 0xc7, 0x70, 0xfa, 0x97, 0xd0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0x41, 0x02, 0xdd, 0xe1, 0x43, 0x4e, 0x47, +0x95, 0xf8, 0x82, 0xf5, 0xb4, 0x44, 0xec, 0x0f, +0x72, 0xb9, 0xe1, 0x2a, 0x55, 0xd4, 0x0c, 0x2c, +0xf2, 0x18, 0xab, 0x12, 0x44, 0xd7, 0x05, 0x9c, +0xbd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0x83, 0x72, 0x1e, 0x93, 0x4c, +0x8a, 0xb2, 0xc7, 0x09, 0xa7, 0xf6, 0xd2, 0xe1, +0xa6, 0x56, 0xba, 0x08, 0xcb, 0x75, 0x7a, 0x83, +0x42, 0xe0, 0xad, 0x51, 0xe6, 0x52, 0xe5, 0x68, +0xf1, 0x04, 0x97, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0x07, 0xd3, 0x00, +0x77, 0x19, 0x50, 0xaa, 0x4d, 0x7b, 0x0b, 0xa5, +0x4c, 0xfc, 0x80, 0xe4, 0xc2, 0x43, 0xdd, 0xfb, +0xc3, 0x49, 0xcb, 0xd8, 0xbc, 0xce, 0x66, 0xa9, +0x81, 0x01, 0x04, 0xf1, 0x32, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0x1f, +0x22, 0xff, 0xba, 0x40, 0x38, 0x86, 0xcc, 0x50, +0x9b, 0xd2, 0x3f, 0x2d, 0x8f, 0x15, 0xd4, 0x4f, +0xb2, 0xa9, 0xe2, 0x21, 0x3c, 0x7d, 0x65, 0x75, +0x8e, 0xf5, 0xc5, 0xac, 0xe9, 0x9e, 0xbf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0x69, 0x19, 0xe2, 0x60, 0x26, 0x70, 0x8f, +0x6d, 0xd4, 0xb4, 0xfb, 0x11, 0xb0, 0x69, 0x52, +0xc4, 0xb6, 0x3f, 0x2a, 0xae, 0x5b, 0xb2, 0x90, +0x8a, 0x48, 0x13, 0xde, 0x73, 0x5e, 0x67, 0x79, +0x53, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0xb5, 0xac, 0x86, 0x31, 0x2b, +0x52, 0x1e, 0xee, 0x9b, 0x77, 0x0b, 0xe2, 0x11, +0x57, 0x49, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x17, 0xb6, 0x1f, 0xa6, +0xb3, 0xdf, 0xca, 0xe6, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0x48, 0x37, 0xa5, +0xab, 0x77, 0x5a, 0x78, 0xce, 0xe3, 0x20, 0xe6, +0xb1, 0x45, 0x07, 0x69, 0x37, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x88, +0xe0, 0x3f, 0x49, 0x74, 0x54, 0x26, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0x88, +0x28, 0x7d, 0xbb, 0x11, 0xec, 0x98, 0xbe, 0x1a, +0xb5, 0x6e, 0xe8, 0xaa, 0x06, 0x92, 0x48, 0x2e, +0x69, 0x72, 0x13, 0xe9, 0xb4, 0x2b, 0x0c, 0x50, +0x1c, 0x5e, 0x05, 0x1f, 0x45, 0x50, 0xaa, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0xef, 0x0f, 0x8d, 0xc0, 0x1d, 0xdc, 0xcb, +0x6b, 0x96, 0x0d, 0x4c, 0x59, 0x62, 0xd8, 0x4b, +0x68, 0x02, 0x0f, 0x35, 0x15, 0x91, 0xce, 0xea, +0xdc, 0x1f, 0x4f, 0xfd, 0x43, 0x66, 0xd6, 0xba, +0x05, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0xad, 0xf3, 0x0d, 0x0e, 0x3d, +0xe3, 0xb9, 0x8b, 0x34, 0x4b, 0x7e, 0xd4, 0xf5, +0x65, 0xf1, 0x85, 0x5b, 0x4a, 0xd8, 0x8c, 0xe3, +0x97, 0x60, 0xcb, 0xf4, 0x18, 0x1e, 0xd3, 0x1b, +0x11, 0x0d, 0x58, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0x03, 0x20, 0xe5, +0x97, 0x67, 0x74, 0x69, 0xa5, 0xb4, 0x74, 0x57, +0x23, 0x4b, 0x24, 0x3f, 0x43, 0xf0, 0xcd, 0x47, +0x6a, 0xfe, 0xae, 0x8c, 0x57, 0x1d, 0xf8, 0x69, +0xf3, 0x3e, 0x08, 0x07, 0x3f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0x38, +0xa7, 0xb1, 0x69, 0xdb, 0x5c, 0x27, 0x0c, 0xdd, +0xba, 0x21, 0x68, 0x90, 0xc7, 0x2d, 0xa0, 0x11, +0x98, 0x77, 0xea, 0x50, 0x10, 0x46, 0x1b, 0xf7, +0x1b, 0xd3, 0xce, 0x14, 0xf3, 0xd8, 0x4e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0xe8, 0xf7, 0xdc, 0x16, 0xc6, 0x4b, 0xad, +0x75, 0xeb, 0x53, 0x4e, 0x38, 0x0b, 0xdf, 0x7f, +0xe7, 0x2b, 0x5c, 0x35, 0xf7, 0x0a, 0x0d, 0x71, +0x86, 0xfb, 0x76, 0xf0, 0xf1, 0xbb, 0xa8, 0x9a, +0xe9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0x4a, 0x6f, 0x92, 0x8c, 0xa9, +0x74, 0xf5, 0xb0, 0x77, 0x1a, 0x0a, 0x8d, 0xa1, +0xad, 0xef, 0x46, 0x96, 0x11, 0xf2, 0x81, 0x0c, +0xe3, 0x1f, 0x3b, 0x17, 0x6f, 0xe9, 0xd3, 0x83, +0x05, 0x36, 0xb7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0xb1, 0x13, 0x01, +0xe2, 0x17, 0x3d, 0xc3, 0x59, 0x36, 0xa0, 0x8d, +0x8a, 0x1e, 0xbb, 0x50, 0xec, 0x14, 0xf2, 0x16, +0xe3, 0x59, 0x31, 0xf3, 0x23, 0x4f, 0x1e, 0x56, +0x9f, 0x4b, 0x7c, 0xb5, 0x6f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0xb6, +0x28, 0xbf, 0x4a, 0x72, 0x69, 0xc8, 0xa6, 0x1e, +0xbc, 0x32, 0x57, 0x22, 0xbb, 0xe0, 0xec, 0xbf, +0xd4, 0xf6, 0xc1, 0x36, 0x77, 0x14, 0x9e, 0xf2, +0xd5, 0x84, 0x1a, 0xc6, 0xe0, 0x4c, 0xf1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0xf2, 0x20, 0xf0, 0xad, 0x17, 0x21, 0xea, +0x44, 0x5f, 0x55, 0x50, 0xc2, 0x4f, 0x2b, 0x6b, +0x1b, 0xd5, 0x3e, 0xee, 0xc6, 0xb1, 0xa4, 0xc3, +0x4e, 0x00, 0xaf, 0xb0, 0xdc, 0x5b, 0xff, 0x7c, +0x34, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0xf0, 0x99, 0x68, 0xeb, 0xa5, +0xf1, 0x8f, 0x47, 0x56, 0x08, 0xec, 0xdf, 0xa8, +0x87, 0x2b, 0x49, 0x35, 0x0a, 0x81, 0x9b, 0xf3, +0x32, 0xeb, 0xf8, 0xc9, 0x4c, 0xef, 0x1f, 0xf5, +0x6e, 0x4e, 0x75, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0xc0, 0x6a, 0x32, +0x36, 0x94, 0xf7, 0x3f, 0x3c, 0x87, 0x97, 0x14, +0x05, 0x98, 0x29, 0xba, 0x9d, 0x5d, 0xe1, 0x7c, +0x44, 0x44, 0x72, 0x6d, 0x5e, 0x8a, 0x66, 0x81, +0xcb, 0x22, 0x9d, 0x0e, 0x56, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0x85, +0xfb, 0x58, 0xf4, 0x04, 0xa2, 0x54, 0x5e, 0xf5, +0x9f, 0xbb, 0x85, 0x17, 0x54, 0xf2, 0xea, 0x0a, +0x99, 0x84, 0xa7, 0xc6, 0x0c, 0x7f, 0x12, 0x32, +0x84, 0x34, 0xc5, 0xf2, 0x60, 0x5c, 0x7d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0xbf, 0xa0, 0x96, 0x6e, 0xb0, 0x0e, 0x4f, +0xe6, 0xba, 0x40, 0xfc, 0x07, 0x64, 0xc0, 0xd9, +0x5b, 0x0c, 0xaa, 0xb6, 0x7c, 0xa2, 0x64, 0x3c, +0xb9, 0x60, 0xef, 0xa3, 0x8f, 0xbc, 0x1a, 0x60, +0xb4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0xe7, 0x8f, 0x93, 0xcc, 0x4b, +0x9c, 0x23, 0x5e, 0xe0, 0x3e, 0x4e, 0xd8, 0x57, +0x94, 0x4c, 0xa8, 0x55, 0x68, 0x70, 0x68, 0xd5, +0x8f, 0x9e, 0xf1, 0xcc, 0x6d, 0x40, 0x2d, 0x1e, +0x4b, 0x4b, 0xe9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0x0a, 0x9a, 0xc3, +0x5d, 0xde, 0xea, 0xa0, 0x13, 0x6c, 0x50, 0x3f, +0xca, 0xfb, 0xb0, 0xe2, 0xb5, 0x93, 0x37, 0x6d, +0x20, 0x92, 0x24, 0xe8, 0x1f, 0x73, 0x4b, 0x2a, +0x32, 0x27, 0x54, 0x5a, 0x16, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0x6c, +0x04, 0xb4, 0x71, 0x35, 0x9c, 0xa9, 0x9a, 0x9f, +0xef, 0xd6, 0xce, 0xa7, 0x69, 0x9b, 0xab, 0x86, +0x07, 0xdc, 0x20, 0x36, 0xbe, 0xf8, 0xde, 0x5b, +0x0a, 0x2e, 0xa6, 0x0f, 0x0f, 0x1b, 0x39, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0x2c, 0x5e, 0x22, 0xa8, 0x9f, 0x9e, 0x35, +0xbf, 0x50, 0xf0, 0x70, 0xea, 0x71, 0x95, 0xcf, +0x4e, 0x2c, 0xa6, 0xf6, 0xcd, 0x86, 0xb7, 0xa8, +0x81, 0x53, 0xe9, 0xaa, 0x40, 0xc1, 0xb6, 0x3e, +0xfb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0xf0, 0x7d, 0xfb, 0xbc, 0x7f, +0x53, 0x4b, 0x91, 0xde, 0x55, 0xd4, 0x67, 0x4c, +0xea, 0x41, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xb0, 0xb0, 0x8f, 0x5b, +0x23, 0xc0, 0x21, 0x26, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x10, 0xb3, 0xbd, +0x7e, 0xb1, 0x11, 0x5d, 0xbc, 0x88, 0x26, 0x27, +0x80, 0x48, 0x99, 0x67, 0x68, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0x2d, +0x98, 0x78, 0x91, 0x71, 0x42, 0x30, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0x1e, +0x9a, 0xc3, 0x46, 0xb3, 0x89, 0xa4, 0xf9, 0xd3, +0xff, 0x9c, 0xda, 0x7a, 0x02, 0xf9, 0xf9, 0x31, +0xe8, 0x71, 0x4f, 0x0c, 0x4f, 0x67, 0x02, 0x70, +0xf1, 0x53, 0x6a, 0x74, 0xcb, 0xde, 0x87, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x72, 0xa2, 0xc0, 0x38, 0xde, 0x1c, 0xa4, +0xb6, 0x27, 0x71, 0x2f, 0x99, 0xf4, 0xac, 0xc6, +0x6a, 0xb7, 0x7b, 0x4b, 0x8e, 0x2c, 0x77, 0xf8, +0x4f, 0x93, 0xae, 0x10, 0xee, 0xbb, 0xe2, 0x86, +0x80, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0x3d, 0xdb, 0x5e, 0x97, 0x65, +0x4f, 0x1f, 0xe4, 0xba, 0x8e, 0x0a, 0x82, 0x0b, +0xc4, 0x5d, 0xde, 0xf1, 0x09, 0x82, 0x15, 0xa0, +0xd9, 0x1a, 0x0e, 0x0c, 0xc2, 0xcf, 0xf4, 0x44, +0x0f, 0xbc, 0x1c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0x97, 0x8a, 0x3e, +0x2f, 0x7f, 0xe7, 0x1c, 0x02, 0x7a, 0x73, 0x8b, +0xd6, 0xb1, 0xa3, 0x75, 0x5b, 0x30, 0x34, 0xa3, +0xa4, 0xba, 0x9a, 0x36, 0x77, 0xbe, 0xb0, 0x8f, +0x9d, 0x45, 0x7d, 0x56, 0xbc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0xd8, +0x08, 0x8c, 0x64, 0x07, 0x24, 0x18, 0xd9, 0x23, +0x45, 0x0f, 0xa6, 0xd9, 0xa4, 0x24, 0x03, 0x5d, +0x80, 0x91, 0x17, 0xf9, 0xc5, 0x4c, 0xea, 0x91, +0xf5, 0x4d, 0x29, 0x06, 0x7a, 0x08, 0xf3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0xdb, 0x1f, 0x00, 0x3f, 0x4f, 0xf9, 0xc6, +0x7c, 0xd8, 0xa4, 0xdb, 0xba, 0xd5, 0x77, 0x4e, +0x99, 0x29, 0x33, 0x20, 0x43, 0x91, 0x07, 0x56, +0x8a, 0x58, 0xdf, 0x82, 0x4c, 0x5e, 0xf2, 0xcd, +0xb7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0x92, 0xc4, 0x1a, 0x3b, 0xf2, +0xbb, 0xeb, 0x93, 0x3d, 0x73, 0x0b, 0x96, 0xc3, +0x12, 0xcb, 0x28, 0x2f, 0xf4, 0xc3, 0x72, 0xd7, +0xe3, 0x63, 0x17, 0x65, 0x40, 0xa6, 0xe2, 0x6c, +0x80, 0x0c, 0xf4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0xe9, 0x80, 0x00, +0xb3, 0xff, 0x27, 0x14, 0x49, 0x3c, 0x6b, 0xa6, +0x76, 0x3b, 0xc5, 0xba, 0x9a, 0x4c, 0xcd, 0xb2, +0x3d, 0xcf, 0x54, 0x41, 0x3f, 0x0b, 0x13, 0x8f, +0x53, 0x55, 0xa5, 0x3a, 0xe7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0xf4, +0xaa, 0x7a, 0x3c, 0x75, 0xc9, 0x33, 0x16, 0x7d, +0x28, 0x83, 0xf2, 0x63, 0x13, 0xba, 0x9c, 0xc1, +0x2a, 0x26, 0xc2, 0x32, 0x8f, 0x32, 0xfc, 0x57, +0x42, 0x65, 0xa7, 0xdb, 0xb9, 0x7d, 0xab, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0x67, 0x3c, 0xe9, 0x64, 0x7f, 0xb8, 0x4b, +0xef, 0xb1, 0x57, 0x81, 0x71, 0x17, 0xbf, 0xb8, +0x02, 0xfc, 0xe6, 0x9e, 0xf8, 0x7e, 0x78, 0x8d, +0x40, 0xbf, 0x70, 0x07, 0x8f, 0x94, 0xb5, 0xa3, +0xb5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0x3f, 0xc3, 0xd9, 0xab, 0x3f, +0xba, 0x38, 0xc6, 0x86, 0x62, 0x40, 0xee, 0xf8, +0x8f, 0xac, 0xf8, 0xd6, 0x51, 0xfc, 0x87, 0x3a, +0x7c, 0x3d, 0x7a, 0x3b, 0x54, 0x12, 0xbf, 0xa0, +0x09, 0xa1, 0x7c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0xeb, 0x82, 0x9e, +0x4c, 0x96, 0x9f, 0xb7, 0xcc, 0x13, 0xc6, 0xe5, +0xcc, 0xb3, 0x4e, 0xb3, 0x76, 0x5c, 0xd7, 0x73, +0x51, 0xd3, 0xa8, 0xd6, 0x18, 0x92, 0x50, 0x35, +0x59, 0x24, 0x3d, 0xd5, 0xec, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0xf0, +0xbe, 0xc8, 0x2c, 0x9e, 0x24, 0x02, 0x57, 0x41, +0x7a, 0x99, 0xcc, 0xd3, 0xf9, 0x9e, 0x40, 0x03, +0x71, 0x54, 0xc3, 0x9e, 0xf8, 0x37, 0xa0, 0x19, +0x2b, 0x13, 0xf4, 0x5d, 0xf5, 0xdf, 0x9c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0x25, 0x47, 0x70, 0x54, 0x88, 0xba, 0x4e, +0xf4, 0x4a, 0x80, 0x53, 0x64, 0x85, 0x84, 0x67, +0x26, 0xfc, 0xed, 0xcc, 0x52, 0xe8, 0xc9, 0x26, +0x42, 0x1c, 0xa4, 0xc5, 0x25, 0x39, 0x59, 0x5e, +0x90, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0xe3, 0x19, 0xf1, 0x92, 0xfd, +0x2d, 0xf6, 0x3e, 0xf5, 0xa1, 0xf2, 0x34, 0xa6, +0x32, 0x64, 0x58, 0xae, 0xb9, 0x5d, 0xb8, 0x6c, +0xdd, 0x40, 0x81, 0x76, 0x94, 0x33, 0x80, 0x1b, +0x15, 0x5c, 0x39, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x76, 0x17, 0x89, +0x64, 0x92, 0x02, 0xed, 0x01, 0xa7, 0x8c, 0x35, +0xbc, 0x85, 0x92, 0xb8, 0x19, 0x3a, 0x94, 0xe1, +0xe6, 0xb3, 0x3d, 0x37, 0xf6, 0xaf, 0x26, 0x03, +0xb3, 0xcb, 0xef, 0x49, 0x4c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0x68, +0x1b, 0xed, 0x88, 0xe0, 0x2b, 0x7b, 0xda, 0x32, +0xe3, 0x6b, 0x9b, 0x56, 0xb2, 0x12, 0xc8, 0x1f, +0x36, 0xf1, 0xfd, 0x11, 0xf9, 0x3f, 0x32, 0x83, +0xaf, 0xbf, 0x20, 0x27, 0x19, 0x5b, 0xae, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0x4f, 0x85, 0x07, 0xc3, 0x08, 0xb3, 0x3f, +0xe1, 0x42, 0x19, 0xa6, 0x4d, 0xb7, 0xf3, 0x9f, +0x08, 0x3b, 0x50, 0x9d, 0x42, 0x6c, 0xba, 0xe9, +0xa6, 0x9d, 0x5d, 0xde, 0xa1, 0xe9, 0xa2, 0x38, +0x9d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0x01, 0x30, 0x59, 0x5c, 0xf0, +0xfa, 0xe6, 0x5e, 0xe8, 0x84, 0xbe, 0xae, 0xd3, +0xc7, 0xed, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xf3, 0x5f, 0x9c, 0x55, +0x47, 0xf2, 0x16, 0x8d, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0x89, 0x45, 0x61, +0x6c, 0x8c, 0x12, 0x71, 0x49, 0x48, 0xd1, 0xc7, +0x58, 0x7b, 0xfe, 0xd3, 0x47, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x92, +0xb6, 0xac, 0x14, 0x3d, 0xac, 0x58, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0xf4, +0xfe, 0x1f, 0xa4, 0xcb, 0x5a, 0x2a, 0xbb, 0xe7, +0x26, 0xaa, 0x2d, 0x4b, 0x3d, 0xf9, 0xe7, 0x7a, +0xf5, 0x50, 0xc5, 0xf7, 0x25, 0x74, 0xed, 0x6b, +0xbd, 0x7d, 0x80, 0x56, 0xc5, 0x1b, 0x06, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0xbd, 0xea, 0xe8, 0x94, 0x61, 0xbe, 0xfa, +0x0e, 0x04, 0xdb, 0xb2, 0x85, 0x3e, 0xa8, 0x1d, +0x89, 0xf5, 0x42, 0x69, 0x56, 0x96, 0xee, 0xde, +0xe4, 0x33, 0xe1, 0x76, 0x5d, 0xe7, 0x89, 0x40, +0x68, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x12, 0x36, 0xed, 0xb5, 0x1b, +0x2a, 0x3a, 0x2f, 0x37, 0x78, 0x1b, 0x13, 0x00, +0x93, 0x0d, 0xcf, 0xf5, 0x3f, 0x73, 0xb7, 0xd6, +0xbb, 0xab, 0xdc, 0x77, 0x84, 0x5a, 0x6f, 0xa8, +0x53, 0x85, 0x36, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0x36, 0xb7, 0x00, +0x76, 0xfd, 0xe3, 0x50, 0xf3, 0x9d, 0x3f, 0x1a, +0xb1, 0xd5, 0x13, 0xc5, 0xaa, 0x2f, 0x2a, 0xbc, +0xca, 0x61, 0x9e, 0x80, 0xc2, 0x97, 0xdb, 0x03, +0xf4, 0xcc, 0x9b, 0x38, 0xc7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0x18, +0xcc, 0x75, 0xa2, 0xfd, 0x89, 0x97, 0xce, 0xcc, +0x00, 0x70, 0x4d, 0x05, 0xa3, 0x25, 0x5c, 0x0f, +0x8e, 0xe7, 0xa9, 0x01, 0x4f, 0x1a, 0x68, 0x55, +0x02, 0xfd, 0x1a, 0xc4, 0xac, 0x50, 0x4c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0xaa, 0x8e, 0x90, 0x11, 0xcd, 0x2f, 0x8b, +0x80, 0xca, 0xfc, 0x8f, 0x61, 0x82, 0x43, 0xd0, +0x20, 0x06, 0x3f, 0x36, 0xde, 0x61, 0x5d, 0x3f, +0x6e, 0x4c, 0xb9, 0x62, 0xad, 0xde, 0xf8, 0xfa, +0x73, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0xa2, 0x18, 0x53, 0xfc, 0xe9, +0xf4, 0x98, 0x4a, 0x48, 0xfe, 0x06, 0x09, 0x16, +0x28, 0x2b, 0x80, 0x5d, 0x56, 0x38, 0xa5, 0x6f, +0x87, 0x2f, 0xd4, 0xcb, 0x7f, 0x90, 0xda, 0xa7, +0x3d, 0x52, 0x97, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0x55, 0x9d, 0x93, +0x1d, 0xd3, 0xe0, 0x21, 0x42, 0xd6, 0xbd, 0x67, +0xee, 0x70, 0xcd, 0x5a, 0x90, 0x2f, 0x42, 0x84, +0x40, 0x88, 0x9d, 0xab, 0xed, 0xbb, 0xe6, 0xa6, +0xe9, 0xfa, 0x97, 0x66, 0xd0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0xae, +0x1a, 0xec, 0x67, 0xf0, 0x5a, 0x60, 0x76, 0x9b, +0xdc, 0x34, 0xea, 0x94, 0xd9, 0xfd, 0x7f, 0x43, +0xb1, 0xf6, 0xdd, 0x24, 0x3d, 0xec, 0x56, 0x17, +0x78, 0x58, 0x76, 0xbe, 0xc4, 0x66, 0xe2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0xf8, 0x8e, 0x16, 0x54, 0xa4, 0x6f, 0x37, +0xb2, 0xa6, 0x53, 0x90, 0x78, 0xfa, 0xc7, 0x0a, +0xda, 0xf1, 0x9e, 0xd2, 0x2f, 0x5a, 0x97, 0x39, +0x6e, 0x0e, 0x38, 0x1e, 0xe0, 0x6a, 0x04, 0x3e, +0xd4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0x4d, 0x99, 0xe2, 0x45, 0xa6, +0xa5, 0xad, 0x96, 0x36, 0x20, 0xa2, 0xf2, 0x31, +0xcd, 0x1d, 0x9e, 0x40, 0xa8, 0x13, 0x3c, 0x22, +0x89, 0x1f, 0x09, 0x9d, 0x3b, 0x84, 0x82, 0xad, +0x21, 0xed, 0xc6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0xf7, 0x05, 0xf0, +0xe3, 0x9c, 0x94, 0x00, 0x54, 0x58, 0x34, 0xa1, +0x72, 0xa4, 0xb2, 0x70, 0x22, 0x2f, 0xca, 0xd7, +0xff, 0xd0, 0x3c, 0x60, 0xfa, 0x02, 0x7a, 0xad, +0x81, 0x07, 0xc6, 0x44, 0xab, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0x29, +0x92, 0x5d, 0x74, 0xca, 0x16, 0x46, 0x5c, 0x6c, +0x89, 0x24, 0x2f, 0xe3, 0xc9, 0xfd, 0x6c, 0x7f, +0xc3, 0x6e, 0xc0, 0x29, 0xf0, 0xc4, 0x16, 0xcf, +0x4f, 0xcb, 0x95, 0x90, 0xbf, 0xba, 0x89, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0x79, 0x7f, 0xe9, 0x4b, 0x4c, 0xbe, 0x7e, +0x4f, 0x94, 0x00, 0x57, 0xd0, 0x9f, 0x56, 0x65, +0x09, 0xb4, 0x0c, 0x0b, 0x38, 0xfd, 0x88, 0x22, +0x34, 0xce, 0xca, 0x86, 0x6f, 0x69, 0x84, 0x02, +0x45, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0x04, 0xea, 0x6e, 0x44, 0x67, +0xaa, 0x1c, 0xe4, 0x87, 0x58, 0x77, 0xe4, 0x0c, +0x53, 0xaf, 0x97, 0xdc, 0x46, 0x10, 0x9a, 0x30, +0xa0, 0x27, 0x61, 0x37, 0x1e, 0x84, 0xbb, 0xfb, +0xd7, 0x53, 0xac, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0x58, 0x9b, 0xe8, +0xa0, 0xaf, 0xee, 0xe2, 0xc2, 0xee, 0xc1, 0xa5, +0xaf, 0xa5, 0x23, 0x72, 0x07, 0xfc, 0x0d, 0x95, +0xfe, 0x33, 0xa7, 0x6b, 0x3d, 0x9b, 0x1e, 0x13, +0x5f, 0x4c, 0x00, 0x77, 0x68, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0xc6, +0xd4, 0x8b, 0x94, 0xf2, 0xce, 0xb4, 0xb0, 0x5b, +0xf6, 0x1e, 0x69, 0x8d, 0xb0, 0x54, 0x98, 0xd5, +0xa1, 0x5c, 0x1e, 0x5b, 0x7b, 0x39, 0x64, 0x45, +0x4c, 0xed, 0xca, 0x07, 0xa1, 0xad, 0x0c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0xa3, 0x8f, 0xf4, 0x61, 0x48, 0x74, 0x2c, +0xe0, 0x26, 0x4d, 0xcd, 0x82, 0xca, 0x1c, 0x08, +0xba, 0x4b, 0xae, 0xa1, 0x2b, 0xd6, 0x18, 0x08, +0x22, 0x07, 0x5a, 0xb7, 0xc0, 0xe3, 0xee, 0x29, +0xd9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0xd9, 0x75, 0xac, 0xa1, 0x7d, +0x02, 0xc8, 0x65, 0xf7, 0xb8, 0x15, 0xd5, 0x71, +0x77, 0xb3, 0xc3, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x7b, 0x85, 0x6d, 0x9b, +0x36, 0x5d, 0x4c, 0xa8, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0x40, 0xa0, 0xf4, +0xb0, 0x8d, 0x2c, 0x68, 0xdc, 0x19, 0xc0, 0x30, +0x7d, 0x7e, 0x80, 0x03, 0xc8, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0xfc, +0x34, 0x8e, 0x4c, 0x00, 0xad, 0x81, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0x71, +0x41, 0x98, 0x64, 0xcf, 0x84, 0x77, 0xf7, 0xda, +0xe5, 0xe3, 0x6b, 0x60, 0x1e, 0x84, 0x40, 0x66, +0x97, 0xe1, 0xb5, 0x3c, 0xe6, 0x60, 0x5c, 0xf8, +0x56, 0x3c, 0x6d, 0xcc, 0x3c, 0x7f, 0x9d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0x4c, 0x25, 0x45, 0xa4, 0x24, 0x8a, 0xe9, +0x30, 0xe8, 0x9b, 0xf2, 0x14, 0xad, 0x87, 0x14, +0xb4, 0x72, 0xba, 0xe9, 0x96, 0xcf, 0x8a, 0x68, +0x79, 0x2a, 0xae, 0x8b, 0xfb, 0x8a, 0x16, 0x7d, +0xb6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0x47, 0x8b, 0xb8, 0xcc, 0xe3, +0x0d, 0xcf, 0x0e, 0xe5, 0x84, 0xf7, 0xd4, 0x35, +0xd0, 0x0a, 0x8d, 0x51, 0x85, 0x40, 0x63, 0xe9, +0x15, 0xde, 0x6d, 0xa6, 0x69, 0xa3, 0x3e, 0x1a, +0xd1, 0xa9, 0x35, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0x19, 0x5d, 0xfd, +0x2f, 0xab, 0x7e, 0x5e, 0x0a, 0xdb, 0xc5, 0x30, +0xb5, 0xe9, 0xa9, 0x35, 0x48, 0x98, 0x51, 0xb7, +0xad, 0x74, 0x12, 0x13, 0x0b, 0x6e, 0x93, 0x79, +0x80, 0x60, 0x60, 0x68, 0xb2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0xed, +0x15, 0x58, 0xea, 0x60, 0xff, 0x71, 0x44, 0x76, +0x48, 0x40, 0xa7, 0x65, 0x19, 0xda, 0x0b, 0x6f, +0x1b, 0x87, 0x0c, 0x49, 0xe5, 0xaf, 0x41, 0x10, +0xb5, 0x1d, 0x1a, 0xea, 0xfa, 0xa9, 0xfb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0x46, 0xcf, 0xb0, 0xc2, 0x41, 0x4b, 0x9b, +0x2d, 0x27, 0x80, 0x21, 0xe8, 0x1e, 0xe9, 0x95, +0xe7, 0x8c, 0xd9, 0xb5, 0x0c, 0xbe, 0x09, 0x45, +0xf5, 0xef, 0xd3, 0x70, 0x7c, 0xe6, 0x4c, 0xdc, +0x6f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x96, 0x6a, 0x46, 0x62, 0x78, +0x93, 0x33, 0x1d, 0x03, 0x86, 0x80, 0x34, 0xb8, +0x64, 0x3d, 0x9b, 0x84, 0xdc, 0xd6, 0xd8, 0x75, +0x57, 0x62, 0x9f, 0x5b, 0x67, 0x3c, 0xdf, 0xbd, +0x72, 0x67, 0x5f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0xcf, 0xbb, 0x90, +0x86, 0x23, 0x03, 0x5a, 0xfd, 0x30, 0x3a, 0x7b, +0x9e, 0x26, 0xe4, 0x21, 0x9e, 0x7a, 0x5f, 0x80, +0x65, 0x5f, 0xbf, 0x41, 0x10, 0x42, 0x92, 0x6a, +0x63, 0x0d, 0x13, 0x10, 0x50, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0xce, +0x08, 0xb3, 0xd2, 0x59, 0x48, 0x3e, 0xe5, 0xae, +0xc9, 0x83, 0xe6, 0x5c, 0xcf, 0x11, 0x90, 0x6f, +0x22, 0x1f, 0xaa, 0xa7, 0x9d, 0x92, 0xe7, 0x42, +0x31, 0x04, 0x7a, 0x90, 0xa9, 0x28, 0x0f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x19, 0x47, 0xfd, 0xaf, 0xef, 0xca, 0xb6, +0x99, 0x4d, 0x41, 0x3c, 0x5b, 0x88, 0x88, 0x07, +0xcf, 0x52, 0x21, 0x59, 0x6e, 0x9f, 0xe4, 0x07, +0x51, 0x47, 0x39, 0x7c, 0xbd, 0x50, 0x23, 0xea, +0xda, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x46, 0xb3, 0x03, 0xf0, 0x67, +0xeb, 0xf1, 0x4c, 0x44, 0x73, 0x65, 0xc8, 0x0d, +0xf2, 0x71, 0xf6, 0x64, 0x1b, 0xab, 0xe8, 0x81, +0x16, 0x5d, 0x99, 0xfd, 0x3b, 0x35, 0xa5, 0xc1, +0x25, 0x33, 0x2b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0x52, 0x99, 0x4d, +0xc5, 0x7d, 0x48, 0x69, 0x7e, 0xc4, 0x88, 0xf9, +0x8e, 0xf9, 0xb5, 0xc9, 0x44, 0xa4, 0x28, 0xaf, +0x4f, 0x01, 0xfa, 0x05, 0x76, 0xe4, 0x8e, 0xae, +0x39, 0x9e, 0xb4, 0x69, 0x2e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0x38, +0x78, 0xc6, 0x9f, 0xaa, 0xda, 0x44, 0xad, 0x34, +0xdf, 0x57, 0x42, 0x32, 0x6a, 0x66, 0xcf, 0x36, +0xe4, 0xf2, 0xde, 0xff, 0x9d, 0x36, 0xe2, 0x61, +0x28, 0x9a, 0x81, 0x79, 0x49, 0x8d, 0xab, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0xdb, 0x1d, 0x80, 0xad, 0x01, 0xdb, 0x7a, +0x23, 0x7c, 0x2a, 0x43, 0xf7, 0x3f, 0xfe, 0xe9, +0xe4, 0x8d, 0xc9, 0x33, 0x11, 0xd5, 0xab, 0xc5, +0x24, 0xbe, 0x63, 0x28, 0x70, 0x2e, 0xe7, 0x88, +0xc2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0xfe, 0x38, 0xdc, 0xb5, 0x1a, +0x10, 0xa5, 0x3d, 0x86, 0x58, 0x10, 0x08, 0x4d, +0xc4, 0xf1, 0x70, 0x9d, 0x32, 0xed, 0x72, 0x3f, +0xdb, 0x74, 0x2e, 0xac, 0x02, 0xf2, 0xb3, 0xdd, +0x5a, 0x23, 0xc6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0xb1, 0x48, 0x06, +0xd9, 0x39, 0x7d, 0x8a, 0x70, 0x4d, 0x9c, 0x7e, +0xa6, 0x7f, 0x39, 0xf8, 0x0c, 0xdf, 0x38, 0x21, +0x3b, 0x90, 0xe7, 0xd0, 0x68, 0xcb, 0xcd, 0x89, +0x0e, 0x6e, 0x6d, 0x67, 0x64, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x00, +0x47, 0x3f, 0x1d, 0xb4, 0x15, 0x75, 0x02, 0x2a, +0x2e, 0x93, 0xd5, 0x4b, 0xbe, 0xbc, 0x0a, 0x93, +0xc6, 0x63, 0x91, 0xdd, 0xf3, 0x9d, 0xc4, 0xe5, +0xa0, 0x0a, 0xd9, 0x44, 0xbf, 0x8c, 0x8a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0x1f, 0xfc, 0xc1, 0x18, 0x34, 0x02, 0x62, +0xa1, 0x3b, 0xe4, 0x0f, 0x12, 0x3e, 0x98, 0x3f, +0x85, 0xce, 0x34, 0x52, 0x3e, 0x73, 0x9b, 0xae, +0xda, 0xf5, 0xe7, 0xc8, 0xfa, 0x65, 0x7e, 0xcf, +0x4a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0x58, 0x0c, 0xdc, 0x1d, 0x99, +0x1a, 0x87, 0xd0, 0xd4, 0x87, 0x64, 0xfb, 0x5e, +0x94, 0x02, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x78, 0xf0, 0x53, 0x99, +0x44, 0x19, 0x00, 0x94, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0xe7, 0x41, 0x21, +0x4f, 0x85, 0xd7, 0xc3, 0x17, 0x13, 0x2c, 0x26, +0x12, 0xad, 0x49, 0x0d, 0x5b, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x7e, +0x07, 0x4c, 0x53, 0xf7, 0xdd, 0x67, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0x15, +0x5d, 0x5b, 0x21, 0xbc, 0x48, 0x9c, 0x88, 0x32, +0x56, 0x55, 0x8f, 0x08, 0xa7, 0x28, 0x39, 0x01, +0xce, 0x3f, 0x98, 0xa6, 0x80, 0xbe, 0xf7, 0x9c, +0xb5, 0x88, 0xa6, 0x3a, 0x9a, 0x22, 0x98, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0x3f, 0xd5, 0x93, 0xb9, 0x7b, 0x68, 0x00, +0x3d, 0x20, 0xd0, 0x90, 0xac, 0x80, 0xbd, 0xac, +0x78, 0x8c, 0x17, 0x5e, 0x9a, 0x11, 0xc8, 0x12, +0x62, 0xdd, 0x3c, 0x28, 0xea, 0x44, 0x29, 0xbb, +0x54, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0x69, 0xee, 0x1c, 0x21, 0x19, +0x8b, 0x52, 0x82, 0xdb, 0xa8, 0x50, 0x5c, 0x14, +0x4e, 0x43, 0x2a, 0xa8, 0x16, 0xf7, 0x30, 0xff, +0x75, 0xe6, 0x9f, 0x71, 0x82, 0xf8, 0x13, 0x7a, +0x11, 0x80, 0x50, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0x1b, 0xe2, 0x5d, +0x47, 0xde, 0x69, 0xc3, 0xfe, 0xf3, 0x95, 0x22, +0xfe, 0xec, 0x16, 0x59, 0xf1, 0x73, 0x14, 0x52, +0xf8, 0x02, 0x10, 0x29, 0x59, 0xce, 0xef, 0xc2, +0x35, 0xe7, 0x41, 0x5b, 0xe7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0xe6, +0x83, 0x54, 0xea, 0x8e, 0x9d, 0xc7, 0x15, 0x32, +0x1d, 0x40, 0xd5, 0xf0, 0x49, 0xff, 0xa1, 0x07, +0xfe, 0x46, 0x05, 0x4b, 0x79, 0xae, 0x66, 0x3e, +0x2f, 0x37, 0x46, 0xf0, 0x52, 0x78, 0xaf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0xac, 0xf2, 0xb4, 0x73, 0xe7, 0x7d, 0x3c, +0xe7, 0xa6, 0x97, 0xe4, 0x1e, 0xd6, 0x5d, 0xb0, +0x6f, 0x5e, 0x2a, 0xdf, 0x2b, 0x42, 0xab, 0x8a, +0x84, 0x22, 0xe6, 0x01, 0x3c, 0xf2, 0x4a, 0xd4, +0x00, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0xb8, 0xaf, 0x93, 0xd0, 0x87, +0xd5, 0xa7, 0x80, 0x17, 0xf3, 0xfa, 0x0b, 0xac, +0xf7, 0xe8, 0xfc, 0x9f, 0x09, 0xa7, 0xdb, 0xcd, +0x96, 0x09, 0xa9, 0xe0, 0x4f, 0x03, 0x91, 0x0e, +0x1f, 0x77, 0x88, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0xe5, 0x12, 0x60, +0xca, 0x6b, 0xed, 0xb8, 0x47, 0x30, 0xb8, 0x9a, +0x74, 0xae, 0xff, 0x60, 0x57, 0x33, 0xf3, 0x55, +0x06, 0xa6, 0x30, 0xab, 0xcc, 0x1c, 0x31, 0x6d, +0x78, 0xfa, 0x0d, 0x83, 0x23, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0xd5, +0x6f, 0xde, 0xa6, 0xf5, 0xe6, 0x78, 0xb3, 0xd8, +0x08, 0x7d, 0xe0, 0xaf, 0x33, 0x16, 0x85, 0xe6, +0x9d, 0x6e, 0x74, 0xfc, 0xa1, 0x1b, 0x01, 0x5e, +0x27, 0xaf, 0x14, 0x44, 0x0e, 0xef, 0xd7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0x16, 0xcf, 0xa6, 0xe4, 0xec, 0xf1, 0xc9, +0x13, 0xb5, 0xee, 0x65, 0x4d, 0xb2, 0x7b, 0x58, +0x0e, 0xaa, 0x7f, 0x57, 0x38, 0xe2, 0xae, 0x3d, +0x85, 0xfd, 0xc2, 0x27, 0xd3, 0x47, 0x37, 0xf9, +0xd1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0xc5, 0xaf, 0xdd, 0xda, 0x2a, +0x21, 0xc0, 0x9c, 0x0f, 0x6f, 0x04, 0x3f, 0xf5, +0x40, 0x0b, 0x9b, 0xbd, 0xee, 0xf0, 0x6e, 0xb3, +0x65, 0xb9, 0x5c, 0xf8, 0x1d, 0xa5, 0x96, 0xa5, +0x00, 0x14, 0xb9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x29, 0xbe, 0x02, +0xf6, 0xdc, 0xae, 0x05, 0x06, 0x36, 0xb5, 0x62, +0x72, 0x7f, 0xcc, 0xf8, 0xfd, 0xc8, 0xe2, 0x08, +0x86, 0xac, 0x0d, 0xe4, 0xeb, 0x75, 0xa4, 0x78, +0x23, 0x51, 0xd1, 0x2f, 0xf0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0xca, +0x71, 0x7c, 0xea, 0x78, 0x6f, 0x6a, 0x49, 0xaa, +0x26, 0x68, 0x40, 0xd2, 0x15, 0x46, 0xac, 0x05, +0xb9, 0x88, 0x74, 0x1f, 0x2b, 0x80, 0xc7, 0xe3, +0xe2, 0x6b, 0x76, 0xc8, 0x64, 0x94, 0x04, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0x05, 0xc0, 0x49, 0x06, 0x51, 0x47, 0xc3, +0xce, 0xf8, 0x56, 0xcd, 0x4c, 0x68, 0xf8, 0xcb, +0x7b, 0x7a, 0x7e, 0xa5, 0x43, 0x2f, 0x3e, 0x6c, +0xed, 0xaf, 0xe6, 0x08, 0x0e, 0x73, 0x8a, 0x4a, +0x55, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0xa9, 0x07, 0x43, 0xbb, 0x71, +0x92, 0xb2, 0xcc, 0x75, 0x7f, 0xa1, 0x2f, 0xd2, +0x46, 0x32, 0x32, 0x8c, 0x12, 0x8e, 0x64, 0x56, +0x5c, 0x47, 0x91, 0xf8, 0x36, 0x53, 0xe9, 0x84, +0xa2, 0x74, 0x4a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0x58, 0xc8, 0x2c, +0x07, 0x43, 0xf9, 0x90, 0x42, 0xc4, 0x4f, 0xef, +0x0c, 0x79, 0x17, 0x7c, 0xea, 0x9e, 0x36, 0xec, +0x63, 0x64, 0xcb, 0x9b, 0xa9, 0x55, 0x13, 0xed, +0x76, 0x93, 0xf7, 0x07, 0x06, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0xa7, +0x83, 0x51, 0x33, 0x90, 0x23, 0x56, 0x15, 0x35, +0x83, 0x61, 0x01, 0x71, 0xaa, 0x52, 0x4f, 0x0c, +0x4b, 0xb6, 0x58, 0xc9, 0x74, 0xc6, 0xae, 0x97, +0x4f, 0x57, 0x5f, 0x17, 0x3f, 0x64, 0x91, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0x75, 0x5f, 0xb2, 0xf5, 0xd7, 0x3a, 0xc5, +0x53, 0x25, 0xbb, 0xca, 0x45, 0xb6, 0xb7, 0x20, +0xdf, 0x87, 0xb6, 0x45, 0x1e, 0xc4, 0xb1, 0x64, +0x0e, 0x07, 0x13, 0x88, 0xf5, 0x64, 0x13, 0xfe, +0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0xaa, 0x3a, 0xb8, 0xe9, 0x44, +0x63, 0x53, 0x19, 0xda, 0x06, 0x8a, 0x15, 0x1a, +0xd6, 0xa4, 0xdb, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x14, 0x4d, 0x1c, 0x1e, +0xf2, 0xba, 0x5a, 0x6d, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0x7e, 0xef, 0x06, +0xb4, 0xe6, 0xe2, 0xe0, 0xcb, 0xc5, 0xb3, 0x5e, +0xcd, 0xd5, 0x6f, 0x19, 0x49, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x6d, +0x4b, 0xf6, 0xde, 0xa0, 0xbf, 0x2f, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x6a, +0xfa, 0x9a, 0x8f, 0x29, 0x0f, 0xba, 0xed, 0x46, +0xb7, 0x5f, 0x44, 0xfb, 0x8b, 0x0b, 0x26, 0xb8, +0x3f, 0x54, 0x22, 0xd5, 0x21, 0x8e, 0x56, 0x90, +0x38, 0xb0, 0xfb, 0xb5, 0x24, 0x46, 0x30, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0x6c, 0xfd, 0x0b, 0x34, 0x5a, 0x93, 0xbc, +0xba, 0xe0, 0xa3, 0xe7, 0xe9, 0x09, 0xa8, 0x4d, +0x7d, 0x2e, 0xf4, 0x80, 0x72, 0x1d, 0x9a, 0x9f, +0x81, 0x8d, 0x3d, 0x0d, 0x1a, 0xda, 0xba, 0xe5, +0xee, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0x5e, 0x55, 0x0b, 0x84, 0x62, +0x52, 0xe4, 0x18, 0xf9, 0xf8, 0x6c, 0x7e, 0x8c, +0x6b, 0x0c, 0xa6, 0xe8, 0x71, 0xd0, 0x5c, 0x7b, +0x2c, 0xd9, 0x09, 0x7b, 0x37, 0xd3, 0xea, 0x7c, +0x06, 0xf8, 0x5c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0xae, 0xa6, 0xff, +0x9a, 0xb0, 0x18, 0x72, 0xff, 0x91, 0x62, 0x55, +0xf2, 0xb0, 0xf7, 0x9a, 0xe1, 0x1e, 0x78, 0xb0, +0xc7, 0x92, 0xa0, 0x17, 0x77, 0x41, 0x7c, 0xa1, +0xfe, 0xee, 0xa3, 0xb5, 0x37, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0x0f, +0x2f, 0x8f, 0x98, 0x03, 0xf1, 0x4c, 0x42, 0xf5, +0x90, 0xa9, 0x79, 0x41, 0xf9, 0xf8, 0xa4, 0x4c, +0x99, 0xf8, 0xb2, 0x6c, 0xee, 0xe3, 0xc6, 0x5a, +0x63, 0xe9, 0x7e, 0x6a, 0xfb, 0xcc, 0x1b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0xc2, 0x89, 0xd1, 0x95, 0xbf, 0xa4, 0xe6, +0x76, 0x26, 0xd7, 0xb0, 0x5f, 0x94, 0x6b, 0xa8, +0xdc, 0xfc, 0x7f, 0x7e, 0x03, 0xc5, 0x2a, 0x26, +0x5b, 0x4a, 0x53, 0xd6, 0xc9, 0x55, 0x41, 0xb6, +0xbc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x08, 0x56, 0x58, 0xbb, 0x8d, +0x64, 0x48, 0xfb, 0xa1, 0x13, 0xb7, 0x6c, 0xfe, +0xbf, 0x73, 0x0e, 0xd6, 0xd0, 0x2f, 0xe0, 0x2a, +0x07, 0xad, 0x51, 0x9b, 0x16, 0xfa, 0x1a, 0xa0, +0x09, 0x79, 0x7a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x42, 0xba, 0x6e, +0x8a, 0x23, 0x09, 0x42, 0x05, 0x8f, 0x19, 0x40, +0x64, 0xc2, 0x97, 0x21, 0xef, 0x61, 0x4a, 0xd7, +0x6f, 0x4a, 0x58, 0x6b, 0xfd, 0x03, 0x3a, 0x6f, +0x98, 0x57, 0xe1, 0x0c, 0xcb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0x99, +0xf0, 0xbb, 0x06, 0x89, 0x9e, 0xb9, 0x50, 0x92, +0xd5, 0x04, 0x1c, 0x80, 0xb0, 0x77, 0xfa, 0xd3, +0xe8, 0xaf, 0x3f, 0x63, 0x4d, 0x50, 0x1a, 0x36, +0x5e, 0x66, 0xf9, 0xa1, 0x0d, 0x12, 0x0d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0x60, 0xb8, 0xd5, 0x8d, 0x40, 0xd2, 0x6b, +0x6a, 0xcd, 0xfd, 0x18, 0x7f, 0x36, 0x7b, 0xa0, +0x75, 0x22, 0x0d, 0x3b, 0xdd, 0xc2, 0xcf, 0x73, +0xb1, 0xc2, 0xc5, 0xa3, 0x2c, 0xec, 0x18, 0x01, +0x9e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0x6b, 0xe4, 0x99, 0xba, 0xd2, +0xae, 0x5f, 0x74, 0xd2, 0x38, 0xe7, 0x6b, 0x66, +0x17, 0x8f, 0xe3, 0xa1, 0x4f, 0xa8, 0xa9, 0x63, +0x0d, 0x2b, 0x79, 0x61, 0xeb, 0x39, 0x1b, 0x1a, +0x81, 0x71, 0x88, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0xe0, 0xc6, 0x0a, +0x73, 0xb1, 0x3a, 0x9c, 0x46, 0xdf, 0xb7, 0x68, +0x2d, 0xb9, 0x9c, 0x59, 0x4e, 0xa1, 0xc4, 0x6c, +0x02, 0x18, 0xe8, 0xd4, 0x01, 0x39, 0x4a, 0x81, +0xda, 0xe5, 0x75, 0xc3, 0x59, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0x6b, +0x75, 0x35, 0xb9, 0x6d, 0x0b, 0x7e, 0x80, 0x70, +0xf4, 0x98, 0x0c, 0xbb, 0xe3, 0xdb, 0x7c, 0x9b, +0x46, 0x5b, 0x96, 0x32, 0x98, 0x8a, 0x07, 0xed, +0x21, 0x97, 0x02, 0xce, 0x1d, 0x5f, 0x92, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0x0c, 0x81, 0x84, 0x25, 0xa3, 0x19, 0x6d, +0x95, 0x89, 0x0b, 0xaa, 0x20, 0xa4, 0x3b, 0xb1, +0xe8, 0x21, 0x12, 0x60, 0x7b, 0x46, 0xf0, 0xeb, +0xe3, 0xd6, 0xfb, 0x16, 0x73, 0x4e, 0xaa, 0xaf, +0x7c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0xd5, 0x93, 0xac, 0xde, 0x22, +0x04, 0x39, 0x68, 0x55, 0x89, 0x77, 0x67, 0x89, +0x35, 0x52, 0x6c, 0x92, 0x98, 0x66, 0x68, 0x8b, +0x8e, 0xb8, 0x34, 0xb8, 0x5c, 0x69, 0xa9, 0x26, +0x5f, 0x16, 0x42, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0xe7, 0x9b, 0x24, +0xee, 0xc2, 0x81, 0xbe, 0x57, 0x3c, 0x4d, 0xac, +0x05, 0xb3, 0xda, 0x07, 0xb6, 0x37, 0xe3, 0x58, +0x3d, 0x4a, 0x71, 0x14, 0x3d, 0x79, 0x6c, 0x43, +0x90, 0xd5, 0x30, 0x4a, 0x9f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x74, +0xb5, 0x50, 0xe7, 0x42, 0x10, 0xd4, 0x9e, 0xf9, +0xb6, 0x76, 0xa1, 0xee, 0xdb, 0x24, 0x38, 0xbb, +0x74, 0xd1, 0x5e, 0x74, 0x23, 0x50, 0x22, 0x7c, +0xf2, 0x81, 0x1a, 0x97, 0xa4, 0x58, 0xb4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0x25, 0x06, 0x6c, 0x61, 0xa1, 0x2d, 0x43, +0x25, 0x9e, 0x71, 0x96, 0x57, 0xcd, 0xfe, 0xe4, +0x18, 0x41, 0x88, 0x3a, 0xaa, 0x40, 0xcc, 0x16, +0x49, 0x87, 0x69, 0xca, 0xd9, 0x71, 0x66, 0x22, +0x4c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0xbf, 0xd5, 0x85, 0x8f, 0x93, +0xcd, 0x51, 0x05, 0x25, 0x61, 0xaf, 0xfd, 0x18, +0x63, 0xb8, 0xe7, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x10, 0x3c, 0x2e, 0xc9, +0x2f, 0xa5, 0x57, 0x75, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0x6d, 0xf4, 0x29, +0x5e, 0x5f, 0x8d, 0x2a, 0x98, 0x6e, 0x19, 0xc7, +0x4d, 0xdf, 0x8a, 0x50, 0x3c, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0x30, +0x2d, 0x9f, 0xdb, 0xea, 0x97, 0xa9, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0xc3, +0x67, 0x80, 0x34, 0x01, 0xfb, 0x47, 0xf0, 0x3f, +0x25, 0x98, 0x0a, 0x6a, 0xda, 0x9a, 0xaf, 0xfe, +0x0e, 0x60, 0x8c, 0x39, 0x5f, 0x23, 0xa6, 0xfa, +0xb7, 0xf8, 0x1d, 0x4d, 0x60, 0x66, 0x9b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0x5c, 0x15, 0x54, 0xc6, 0x22, 0xaf, 0x71, +0x61, 0x26, 0x6f, 0x8b, 0x44, 0x7e, 0x67, 0x4d, +0x77, 0x14, 0x5d, 0x3d, 0x57, 0x13, 0xc8, 0xd3, +0xb8, 0x9e, 0xa5, 0x3e, 0xa1, 0x8e, 0x0a, 0xda, +0x64, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x85, 0x0e, 0xc6, 0x07, 0xa3, +0x74, 0x9e, 0x15, 0x71, 0xfe, 0xf6, 0xe8, 0xc6, +0x0d, 0x20, 0x2d, 0xff, 0xdf, 0x85, 0x0b, 0xef, +0x2d, 0xf9, 0x00, 0x65, 0xa4, 0xec, 0xb7, 0x9e, +0xc6, 0xe3, 0x36, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0x45, 0xda, 0x7b, +0x91, 0x35, 0x30, 0xf7, 0xc7, 0xb9, 0x7d, 0x74, +0x99, 0x98, 0x3f, 0x84, 0xe8, 0x8f, 0x01, 0x93, +0x5c, 0x9d, 0x28, 0x32, 0x7d, 0x4e, 0x70, 0x9f, +0xc2, 0xad, 0x62, 0xfc, 0xda, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0x03, +0x9b, 0xed, 0x14, 0xe1, 0xa7, 0x96, 0x02, 0x73, +0xa6, 0xf0, 0x68, 0xea, 0x87, 0x4e, 0x0b, 0xbe, +0x67, 0x03, 0xab, 0x3f, 0xec, 0x52, 0x87, 0xa7, +0x2c, 0x62, 0xfb, 0xa8, 0x2d, 0xf4, 0xd2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0xa5, 0x8a, 0x53, 0x6e, 0xc5, 0x94, 0x3e, +0x99, 0xbb, 0xc1, 0xb7, 0xe3, 0x7e, 0xee, 0xb4, +0xd0, 0x82, 0x1c, 0xc2, 0x92, 0x2c, 0xf1, 0x42, +0x5a, 0x1c, 0xfe, 0x36, 0xcc, 0x02, 0x15, 0x3b, +0xf9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0x03, 0x15, 0xb1, 0xba, 0x4b, +0x4e, 0x42, 0xb2, 0x8a, 0x25, 0x5f, 0x4f, 0x7a, +0xb0, 0xe4, 0x55, 0x93, 0xde, 0xab, 0xb0, 0x5e, +0xd3, 0x8b, 0x1e, 0x08, 0xd5, 0x55, 0x54, 0x21, +0x04, 0xe5, 0x35, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0x8d, 0x0e, 0x92, +0xa4, 0x79, 0xde, 0xb0, 0xf6, 0x63, 0x62, 0x2f, +0xde, 0x75, 0x35, 0x07, 0x71, 0x15, 0x51, 0xd7, +0x01, 0xe9, 0x94, 0xae, 0x64, 0xe7, 0x03, 0xf3, +0x60, 0x4f, 0xe9, 0xdf, 0xb0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0x81, +0x6d, 0x5a, 0x8a, 0x49, 0x74, 0xac, 0xe4, 0x87, +0xf7, 0xcf, 0xba, 0x44, 0x6d, 0xd2, 0x3e, 0x26, +0xf5, 0x8f, 0x9e, 0x98, 0x46, 0xbe, 0x4e, 0x07, +0x11, 0x07, 0xb6, 0xc2, 0x95, 0xa9, 0xa3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0xb0, 0x27, 0x41, 0x46, 0x28, 0x8b, 0x7d, +0x73, 0x3d, 0x29, 0xa3, 0x24, 0x26, 0x37, 0x8f, +0xff, 0x53, 0x89, 0x2a, 0x93, 0x49, 0x10, 0xb9, +0xca, 0x75, 0xda, 0xd0, 0x7d, 0xf6, 0x80, 0x35, +0xea, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0xf6, 0x3e, 0x07, 0xa3, 0x2b, +0x7a, 0xe3, 0x69, 0xad, 0x4e, 0x1f, 0x7c, 0xb5, +0xdc, 0xa6, 0xa4, 0xb1, 0xe4, 0xce, 0x37, 0x95, +0x1e, 0xa6, 0x32, 0x73, 0x2e, 0x30, 0x87, 0x1c, +0xc5, 0xff, 0xf1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0x93, 0xa3, 0x33, +0x46, 0x8f, 0x22, 0x24, 0x5c, 0x3f, 0x01, 0x39, +0x80, 0x18, 0xf9, 0x91, 0x28, 0xb1, 0xaa, 0xf6, +0x4c, 0x44, 0x1c, 0xcf, 0x0a, 0x08, 0x84, 0x8d, +0xa2, 0xa8, 0x2e, 0x8e, 0x02, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0x84, +0xb7, 0x6f, 0x36, 0x67, 0x1b, 0x3e, 0x95, 0x9c, +0xad, 0x1e, 0xc8, 0xbd, 0xaa, 0xd8, 0x93, 0x66, +0xbf, 0x7b, 0x1a, 0xe1, 0x87, 0xf5, 0x66, 0x35, +0xfc, 0xfe, 0x8c, 0xec, 0x98, 0x0c, 0x3a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0x65, 0x82, 0x4f, 0x8b, 0x85, 0x51, 0x23, +0xc8, 0x03, 0x5f, 0x83, 0x7b, 0x75, 0x7a, 0x2c, +0xaf, 0x34, 0xa1, 0xf8, 0x3f, 0xb8, 0xb2, 0xff, +0xd3, 0xb0, 0x2c, 0xe9, 0x00, 0xdb, 0xc1, 0xea, +0x44, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0xbe, 0x9f, 0xb4, 0x3e, 0x8d, +0x3e, 0x99, 0x57, 0x95, 0x6e, 0x96, 0xfe, 0xe4, +0x40, 0x00, 0x31, 0xba, 0x66, 0xdb, 0xa1, 0x45, +0x7a, 0x1a, 0x75, 0x36, 0xd4, 0x00, 0x81, 0x62, +0xd2, 0xc3, 0xf8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0xd0, 0x9a, 0xd8, +0x2f, 0xf4, 0xda, 0x4e, 0x6d, 0x9f, 0xad, 0x6d, +0x7e, 0x1f, 0x34, 0x9f, 0xad, 0x86, 0xd4, 0x8e, +0x8b, 0x20, 0xfc, 0xb3, 0x38, 0x51, 0x72, 0x1a, +0xf9, 0xcb, 0x48, 0xe6, 0xfa, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0x5f, +0x24, 0xd5, 0xf7, 0xba, 0xda, 0x3b, 0x4f, 0x1e, +0xe2, 0x82, 0x33, 0x91, 0xc9, 0x4b, 0x1e, 0x11, +0x78, 0x30, 0x85, 0x95, 0xd6, 0x80, 0xe3, 0x82, +0x6a, 0xb1, 0xff, 0xb0, 0xe4, 0x82, 0x03, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x76, 0xaa, 0xcb, 0xd0, 0xce, 0x26, 0x8e, +0xd3, 0x87, 0x42, 0x6a, 0x47, 0x7b, 0xc1, 0x4a, +0xf5, 0x6f, 0x77, 0xda, 0x3e, 0xb0, 0x73, 0x23, +0x6d, 0xac, 0x53, 0x83, 0x47, 0xc1, 0xb7, 0x3d, +0xf7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0xc4, 0xb8, 0x97, 0x9f, 0x0b, +0xbf, 0xcb, 0x8c, 0xa5, 0x80, 0x81, 0xc2, 0xd9, +0xc8, 0xdb, 0xfa, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x15, 0xc5, 0x3f, 0xec, +0xe7, 0x66, 0x57, 0x5d, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x0f, 0x48, 0xb8, +0x1a, 0x5a, 0xde, 0xc6, 0xfa, 0xc7, 0x10, 0x88, +0xf4, 0xd7, 0x83, 0xf8, 0xf1, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0xa8, +0x89, 0xa1, 0x80, 0xe9, 0xfb, 0xfe, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0xbe, +0xd6, 0xa3, 0xd3, 0x92, 0x5d, 0xf0, 0x60, 0x03, +0x90, 0x55, 0xa0, 0x30, 0x55, 0xea, 0x67, 0x1c, +0x3a, 0x94, 0x85, 0x5b, 0x22, 0x0a, 0x05, 0x01, +0x46, 0x66, 0xc3, 0x50, 0xa7, 0x8f, 0xfe, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0x75, 0x52, 0x69, 0x09, 0x88, 0xa2, 0xbf, +0x0e, 0x72, 0x98, 0xbe, 0xbb, 0x74, 0xa0, 0x08, +0x68, 0x20, 0xc3, 0x3e, 0x73, 0xbe, 0x5d, 0xab, +0x4c, 0xda, 0x0d, 0x27, 0x2a, 0x86, 0x01, 0xec, +0xe3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0x00, 0xd8, 0x8a, 0xe4, 0x2f, +0x60, 0x08, 0xc7, 0xf4, 0xc8, 0xef, 0x45, 0xcd, +0x2e, 0xd7, 0xd4, 0x47, 0x9c, 0x16, 0x82, 0x40, +0xcb, 0xdc, 0x3c, 0xc4, 0x2c, 0x42, 0xdf, 0x2b, +0xe3, 0x40, 0xc7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x0c, 0xa6, 0x22, +0xcd, 0xe0, 0xd1, 0xd2, 0xb4, 0xc9, 0xcd, 0xb3, +0x04, 0x14, 0x65, 0x5a, 0x59, 0x8d, 0xe6, 0xbe, +0x79, 0xfe, 0x8a, 0xad, 0x7e, 0xdb, 0xf8, 0x8d, +0xca, 0xac, 0xe3, 0x25, 0x0a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0xaf, +0xfc, 0xd7, 0xd6, 0x62, 0x7d, 0x47, 0x4a, 0x0b, +0xf1, 0xe9, 0xa3, 0xce, 0x35, 0x17, 0xc3, 0xda, +0x74, 0x46, 0xac, 0x0d, 0x82, 0x4b, 0xda, 0x8c, +0xec, 0x83, 0x87, 0x9c, 0x37, 0x9a, 0x68, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0x02, 0xde, 0xf5, 0x1d, 0x5a, 0x7d, 0x2e, +0x67, 0xdc, 0xb9, 0xae, 0xf2, 0xc5, 0x17, 0x82, +0x3e, 0xcb, 0x3a, 0xf0, 0xfa, 0xcf, 0x78, 0x0c, +0xbe, 0x45, 0xf0, 0xd8, 0xc6, 0x45, 0x36, 0xfd, +0x42, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0xe5, 0xba, 0x6d, 0xa3, 0xb4, +0x40, 0x6a, 0x92, 0x32, 0x3f, 0xaa, 0x09, 0xd0, +0xc1, 0x4d, 0xdd, 0x9d, 0x3e, 0x63, 0x3b, 0xbb, +0xcc, 0x81, 0x01, 0x67, 0xa1, 0x9d, 0xf0, 0x69, +0x53, 0xae, 0x54, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0x13, 0x68, 0xcd, +0xcb, 0x34, 0x6e, 0x8e, 0xdc, 0x74, 0xd5, 0x75, +0x22, 0x6a, 0x86, 0xe4, 0x5f, 0xbc, 0x36, 0x82, +0x33, 0x0f, 0x48, 0x24, 0xfc, 0x08, 0x1e, 0x03, +0x7a, 0x9b, 0x1b, 0xdb, 0x3a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0x58, +0xcd, 0x25, 0x40, 0xa3, 0x23, 0xfd, 0x5b, 0x40, +0x66, 0xba, 0x89, 0xac, 0x8b, 0x9e, 0x1a, 0x36, +0x08, 0xc2, 0x15, 0x77, 0x57, 0x65, 0x22, 0xaa, +0x57, 0x26, 0xf9, 0x0b, 0x32, 0x5c, 0x9e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0xc5, 0x60, 0x88, 0x0f, 0x73, 0x39, 0xbf, +0xcf, 0xc2, 0x1b, 0x5a, 0x1d, 0x46, 0x8a, 0x93, +0xf0, 0x8e, 0xd3, 0xc2, 0xa8, 0x5d, 0x51, 0x19, +0x61, 0xb6, 0x2d, 0xeb, 0x3d, 0x34, 0xc2, 0x9b, +0x1a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0xe7, 0x78, 0xf2, 0xe4, 0xe2, +0x43, 0x48, 0x1c, 0xa0, 0xc5, 0xa6, 0x66, 0x13, +0xa3, 0x22, 0x43, 0x91, 0x64, 0x24, 0x60, 0x91, +0xa1, 0x40, 0x9a, 0x13, 0x13, 0x27, 0x64, 0x15, +0xd3, 0xaa, 0x16, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x65, 0xaf, 0xbb, +0x3e, 0xcc, 0xdc, 0xec, 0x17, 0x76, 0x81, 0xb2, +0x17, 0x65, 0xa1, 0x28, 0xec, 0x2d, 0xc0, 0x88, +0xfd, 0x18, 0xf8, 0x8d, 0x07, 0x14, 0x1e, 0xcd, +0xb6, 0xf1, 0xf9, 0xa6, 0x49, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0xb7, +0x8f, 0x36, 0x1a, 0x69, 0xc7, 0x60, 0xbe, 0x71, +0x89, 0x29, 0xff, 0xfe, 0x66, 0xd0, 0x90, 0xcd, +0x07, 0x14, 0xc3, 0xa7, 0xf9, 0x4e, 0x83, 0xe6, +0x92, 0x52, 0x11, 0xe5, 0x6f, 0xc4, 0x8f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0x4a, 0x7c, 0xa7, 0xad, 0x6f, 0x90, 0x3b, +0xcf, 0xa0, 0x84, 0x42, 0xb9, 0x8a, 0xfd, 0xa2, +0xb5, 0xd8, 0x33, 0xa4, 0x9a, 0x07, 0xea, 0x09, +0x9b, 0x58, 0x20, 0x32, 0x00, 0x1e, 0x7d, 0xb7, +0x87, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0x63, 0xe2, 0x48, 0xa3, 0xb2, +0xfb, 0x16, 0x12, 0xa5, 0x0e, 0xe0, 0x0f, 0xcf, +0x2d, 0x17, 0x26, 0x8d, 0x32, 0xd3, 0xb6, 0x3b, +0xb1, 0x49, 0x4d, 0x12, 0xd7, 0x0d, 0x1e, 0xbb, +0x6f, 0x10, 0x3f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0x88, 0xd1, 0x0d, +0xbb, 0x9a, 0x4c, 0x15, 0x49, 0x10, 0x82, 0x2e, +0xb9, 0x17, 0x8f, 0x19, 0x28, 0x32, 0xda, 0x2e, +0xde, 0x48, 0x60, 0x4b, 0x43, 0x85, 0x21, 0x24, +0x07, 0xdb, 0x54, 0xfe, 0x08, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0x54, +0x7f, 0x5f, 0xa8, 0x77, 0x09, 0xc7, 0x98, 0x66, +0x1c, 0x46, 0x57, 0xe0, 0xab, 0xdb, 0x44, 0xe6, +0xda, 0x4b, 0x48, 0x85, 0x4f, 0x7d, 0xb9, 0x02, +0x55, 0xbb, 0xa7, 0x2b, 0x8b, 0x9e, 0xfc, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x3c, 0x76, 0x3b, 0x57, 0xa9, 0xfb, 0xb5, +0xfd, 0x2e, 0x7c, 0x1c, 0x1f, 0xba, 0xfb, 0x70, +0xea, 0xeb, 0x39, 0x08, 0xf7, 0xff, 0x03, 0x7c, +0x5e, 0xbb, 0x5e, 0xdb, 0xfb, 0x90, 0x86, 0xcb, +0xde, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0x13, 0xcd, 0x01, 0x78, 0xf8, +0xf0, 0x73, 0xb5, 0x12, 0x4b, 0x91, 0xd8, 0x89, +0xf8, 0x78, 0x8d, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xcd, 0xf8, 0x70, 0xb8, +0x1e, 0xc8, 0x81, 0x9e, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0xaa, 0x73, 0x3e, +0xdd, 0x95, 0x6c, 0x1a, 0x70, 0x12, 0x71, 0xf2, +0xd0, 0xef, 0x24, 0xd0, 0x72, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x51, +0xc4, 0x6e, 0xa2, 0xd1, 0x25, 0xb2, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0xe5, +0x41, 0x3e, 0x11, 0x2e, 0x7e, 0x54, 0x1b, 0x92, +0x6a, 0x8f, 0x77, 0x47, 0x64, 0x71, 0xe4, 0xc0, +0xd3, 0xe2, 0xbd, 0x42, 0xb7, 0x04, 0xcc, 0x5b, +0x37, 0xae, 0xfc, 0x28, 0x64, 0x6b, 0x4a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0x56, 0x56, 0xda, 0xf1, 0x20, 0x34, 0x73, +0x36, 0x39, 0x52, 0x02, 0xfe, 0x4c, 0x90, 0x45, +0x22, 0x10, 0x8d, 0x32, 0xc1, 0xda, 0xd8, 0x00, +0x13, 0xc2, 0xa3, 0xd3, 0xf2, 0x43, 0x43, 0x2c, +0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0xbe, 0xf0, 0xa2, 0x97, 0x32, +0x54, 0xd5, 0xc6, 0xd0, 0x58, 0xe5, 0xbe, 0xd8, +0xd2, 0xbc, 0x05, 0x47, 0x19, 0xe1, 0x21, 0x12, +0xdd, 0x34, 0xc1, 0x7c, 0x8e, 0x91, 0xb9, 0xb1, +0x69, 0x75, 0x93, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0x2b, 0xf9, 0xeb, +0xe9, 0x41, 0x46, 0x6d, 0x48, 0xdc, 0x44, 0x3e, +0x8b, 0xfb, 0xd4, 0x97, 0x6f, 0x6f, 0x1a, 0xaa, +0x86, 0xad, 0xb6, 0x89, 0x1f, 0x33, 0xb1, 0xa8, +0xd0, 0x53, 0xc9, 0xf9, 0xa5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0xc9, +0xa0, 0x8d, 0x0a, 0x2c, 0x7b, 0x95, 0x27, 0x45, +0x91, 0x38, 0x7f, 0x20, 0x8a, 0xc8, 0x36, 0x3b, +0xa6, 0x62, 0xa3, 0xd6, 0x19, 0xc1, 0xf7, 0x72, +0x96, 0x0a, 0x67, 0xb1, 0x2a, 0x2a, 0x4a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0xef, 0x6e, 0x02, 0xc9, 0xac, 0x09, 0x9c, +0x35, 0x25, 0xbc, 0x85, 0xe5, 0xa2, 0xa8, 0xf8, +0x3f, 0xfd, 0xec, 0x4d, 0xe5, 0x0f, 0xe6, 0x4a, +0xbd, 0x27, 0xae, 0x1a, 0x2a, 0xd8, 0x71, 0xc8, +0x47, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0x0c, 0x39, 0x7c, 0x14, 0xbe, +0xe1, 0x44, 0xf5, 0x3c, 0x0a, 0xd1, 0xfe, 0x1b, +0xa8, 0xa8, 0xf3, 0xc8, 0xa6, 0xe2, 0x21, 0x33, +0xc3, 0x39, 0x72, 0x36, 0x47, 0xcb, 0x0e, 0x75, +0x14, 0x6d, 0x78, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0x2c, 0x92, 0xec, +0x11, 0xb6, 0x7a, 0xe5, 0x34, 0x5a, 0x62, 0xd3, +0x1c, 0x8c, 0x4e, 0x99, 0xbc, 0x00, 0x71, 0xe5, +0xe8, 0x8e, 0xcf, 0x2c, 0x40, 0x2b, 0x88, 0x14, +0x9a, 0x84, 0xa7, 0xf9, 0x88, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0xcd, +0x32, 0x73, 0x3c, 0x0e, 0x68, 0x05, 0x90, 0xb9, +0x25, 0x0b, 0x94, 0x15, 0x78, 0x96, 0xa6, 0x94, +0xd5, 0xa1, 0xc5, 0x23, 0x71, 0x49, 0xb0, 0xf3, +0x2e, 0x3b, 0x12, 0x59, 0xc5, 0x4d, 0x26, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0xc4, 0xbe, 0x3e, 0x51, 0xdc, 0xa2, 0x7a, +0x8a, 0xae, 0x67, 0xc3, 0x65, 0x1b, 0x56, 0xa5, +0x84, 0x85, 0xf2, 0x22, 0x18, 0x80, 0x37, 0x33, +0x6c, 0x23, 0xbd, 0x24, 0xfd, 0x38, 0x7b, 0x39, +0x23, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0xd0, 0x3e, 0x2c, 0x2b, 0x23, +0x60, 0x6f, 0x4c, 0xb4, 0x92, 0x5d, 0x94, 0xa3, +0x13, 0xb3, 0x89, 0x13, 0xf0, 0x5f, 0xb5, 0x3d, +0x2b, 0x72, 0xd1, 0xf1, 0x2c, 0xc6, 0x43, 0x87, +0xd6, 0x01, 0x08, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0x6e, 0xcf, 0x4c, +0x59, 0xb5, 0xec, 0x2d, 0x49, 0xb5, 0x26, 0x55, +0x09, 0xa1, 0x74, 0xfa, 0x7d, 0x4b, 0x90, 0x3e, +0x6d, 0xd7, 0xce, 0xc0, 0x6d, 0x9b, 0x04, 0xb2, +0xf3, 0x01, 0x2c, 0xa6, 0x93, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0x30, +0x9e, 0x4d, 0x5e, 0x24, 0xa4, 0x26, 0xb8, 0x0b, +0xdb, 0xa4, 0x90, 0x26, 0x24, 0x48, 0x9d, 0xeb, +0x6b, 0x59, 0xf0, 0x3d, 0xd9, 0x76, 0x21, 0x54, +0xd5, 0x20, 0xb4, 0x8e, 0x1f, 0x67, 0x32, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0x1e, 0xbb, 0x45, 0xc8, 0xb7, 0x78, 0x36, +0x5d, 0x43, 0xbb, 0xaa, 0x2c, 0xd2, 0x1f, 0x6f, +0xb5, 0x17, 0x4c, 0xf2, 0xdc, 0xcb, 0x05, 0x23, +0xfe, 0x30, 0xf1, 0x0b, 0x4f, 0x7a, 0xca, 0x4c, +0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0xeb, 0x6a, 0xa6, 0x26, 0x4b, +0x87, 0xe7, 0xa5, 0x0b, 0x39, 0x37, 0xed, 0x91, +0x55, 0x15, 0x1d, 0xab, 0x94, 0xcb, 0xc8, 0xa3, +0x2e, 0x5c, 0x5b, 0x83, 0x43, 0x55, 0x7e, 0x3a, +0xe2, 0x11, 0x41, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x20, 0x68, 0x82, +0x7d, 0x10, 0x3d, 0xf2, 0x45, 0xf0, 0xe7, 0x62, +0x3e, 0xa0, 0xe8, 0x79, 0x81, 0x25, 0x3b, 0x6e, +0xda, 0xfe, 0xd0, 0xb9, 0x12, 0xcf, 0x92, 0x84, +0x02, 0x24, 0x36, 0x9a, 0x79, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0x24, +0xd9, 0xbe, 0xe5, 0xa6, 0x41, 0xd6, 0x5e, 0x92, +0x83, 0x55, 0xd3, 0x46, 0x0a, 0x1d, 0x7f, 0x8e, +0x37, 0xc3, 0x6b, 0x24, 0x39, 0xad, 0x83, 0x1d, +0xb2, 0xd8, 0xcc, 0xb0, 0xa7, 0x6d, 0x16, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0x37, 0x47, 0x72, 0xdc, 0x68, 0x8b, 0x22, +0xde, 0x46, 0x6f, 0x41, 0x3f, 0xe7, 0x4d, 0xc3, +0xb9, 0x97, 0x9d, 0x70, 0x7c, 0xff, 0x6e, 0xeb, +0x9b, 0x04, 0xc1, 0xfc, 0xc2, 0x23, 0xad, 0xd7, +0x2a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0x0f, 0x45, 0x67, 0xf8, 0x79, +0xe8, 0xb0, 0x6f, 0xcf, 0x67, 0x56, 0xe4, 0xf8, +0xb0, 0xbc, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x82, 0x28, 0x14, 0x73, +0x57, 0xd4, 0x44, 0xb1, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0xd3, 0xb1, 0x26, +0xd9, 0x0b, 0xa4, 0xf2, 0xab, 0xcd, 0x2e, 0xc3, +0x67, 0x89, 0xd3, 0x49, 0xb4, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc5, 0x92, +0x15, 0xdd, 0x45, 0x7e, 0x99, 0x68, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0x1d, +0x29, 0x04, 0x4a, 0x01, 0xf2, 0xe7, 0x9b, 0xd8, +0x69, 0xf1, 0x86, 0x7d, 0x19, 0x63, 0x19, 0xbb, +0xfb, 0xc3, 0xc9, 0x35, 0xc8, 0xa1, 0x3b, 0x05, +0x25, 0xed, 0xbf, 0x54, 0xd7, 0xa4, 0xb9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0xc3, 0x6e, 0x53, 0xb2, 0xa3, 0xb5, 0x9c, +0x77, 0xf0, 0x58, 0xa5, 0xfb, 0x05, 0xb1, 0xb2, +0x43, 0x48, 0xce, 0x22, 0xc4, 0xf9, 0x1f, 0x61, +0xd1, 0x20, 0xe9, 0x36, 0x0f, 0x5c, 0x93, 0xe1, +0x9e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x22, 0x8a, 0x58, 0x38, 0x1b, +0x03, 0x1d, 0xb0, 0xd3, 0x71, 0xb3, 0xd2, 0xd7, +0xa1, 0xfc, 0x3b, 0xd1, 0x0d, 0xb6, 0xb9, 0x66, +0xaa, 0xe7, 0xfa, 0x2c, 0xd7, 0x1f, 0xc3, 0x5b, +0x36, 0x3f, 0xa1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0x10, 0xa4, 0xf3, +0xc6, 0xa6, 0x65, 0x07, 0xce, 0x8b, 0xb2, 0xaf, +0x2d, 0x93, 0x27, 0x1f, 0x0d, 0xd3, 0x06, 0x8e, +0x05, 0xeb, 0x73, 0xf6, 0x01, 0x5e, 0x1e, 0xad, +0x05, 0x8d, 0x1e, 0x82, 0x65, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0xa2, +0x4b, 0x59, 0x18, 0x1a, 0xf8, 0x4a, 0xfa, 0xd7, +0x05, 0x5a, 0x2b, 0x9b, 0xfb, 0x8c, 0x52, 0x1e, +0x65, 0x17, 0xb4, 0x65, 0x89, 0x23, 0xab, 0x61, +0x92, 0xdf, 0xe0, 0xaa, 0x86, 0x0b, 0x03, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0xfe, 0x64, 0x5d, 0x80, 0xc1, 0x4c, 0x98, +0x3d, 0x19, 0x2f, 0x08, 0x8e, 0x31, 0x49, 0x6c, +0x27, 0xa8, 0x00, 0xdb, 0xb5, 0xa5, 0x2d, 0x30, +0x6d, 0xc6, 0xd2, 0x72, 0xb2, 0x34, 0x32, 0xd6, +0x1f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0x76, 0xad, 0x70, 0x6d, 0x91, +0xea, 0x84, 0x9f, 0x48, 0x34, 0x5b, 0x94, 0xd5, +0x65, 0x03, 0x84, 0x16, 0x1f, 0xef, 0x76, 0x90, +0xd2, 0xfd, 0x1a, 0xa1, 0x23, 0x79, 0x94, 0x25, +0x09, 0x9a, 0x55, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0x69, 0xf9, 0xf0, +0xa4, 0x42, 0x5c, 0x33, 0xea, 0x37, 0x83, 0xd3, +0xfe, 0x9d, 0xeb, 0x18, 0x2e, 0xab, 0x91, 0xe1, +0x5c, 0x53, 0x28, 0x9f, 0x97, 0xa4, 0x5d, 0x1f, +0x22, 0x7e, 0xa9, 0xea, 0x29, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0xcc, +0xc0, 0x8a, 0x25, 0x28, 0xfd, 0x36, 0x35, 0x4c, +0x01, 0x92, 0x6b, 0x11, 0x6c, 0x3b, 0x8b, 0xcd, +0x72, 0x39, 0xcf, 0x8c, 0x15, 0x8b, 0xc1, 0x3e, +0x47, 0x56, 0x01, 0x34, 0xda, 0x72, 0xd7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x77, 0x8f, 0xb5, 0x15, 0x8d, 0x28, 0xdf, +0xe4, 0xf2, 0xe1, 0x1a, 0x21, 0x4b, 0xdd, 0xf4, +0x25, 0xdd, 0xd8, 0x77, 0x20, 0xd7, 0x75, 0xe6, +0x9c, 0x81, 0x87, 0xb2, 0x22, 0xeb, 0xca, 0x1b, +0x9c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0xa7, 0x58, 0x9a, 0x13, 0x6d, +0xf0, 0xc1, 0x21, 0x93, 0x23, 0x0f, 0xb3, 0xa5, +0xc9, 0xa7, 0x54, 0x3b, 0x82, 0x01, 0x84, 0xdd, +0xf7, 0xa7, 0x44, 0xf8, 0x08, 0x00, 0xd6, 0x30, +0x8b, 0x75, 0x34, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0x96, 0xcf, 0x64, +0xc0, 0xfe, 0x91, 0x7f, 0x3a, 0x93, 0x59, 0x65, +0x93, 0x98, 0xcd, 0xa8, 0xb0, 0x3b, 0x7f, 0x01, +0xf7, 0xb3, 0x10, 0x5d, 0x3a, 0x22, 0x19, 0x05, +0x0a, 0x8b, 0x40, 0x82, 0xc7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0x4c, +0xc5, 0xb6, 0x2c, 0xe0, 0x45, 0x4d, 0x22, 0xf8, +0x42, 0x76, 0xde, 0x3d, 0xbc, 0xb8, 0x06, 0x9a, +0xdd, 0xf3, 0x61, 0xbb, 0xca, 0xca, 0x67, 0xf4, +0x88, 0xcb, 0x31, 0x3a, 0x7e, 0x1e, 0xdd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0xf2, 0x5e, 0x12, 0x9d, 0xfc, 0x6b, 0x78, +0xaf, 0xec, 0xd6, 0x68, 0xc3, 0xfc, 0xe1, 0xc8, +0x87, 0xcb, 0xbf, 0xa9, 0x8b, 0x6a, 0x69, 0xf3, +0x08, 0x33, 0xeb, 0x8e, 0x9f, 0x99, 0x36, 0x33, +0x2e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0x19, 0x1d, 0x62, 0x11, 0x77, +0x45, 0xa8, 0xdb, 0x55, 0xe9, 0xda, 0x74, 0x1f, +0x8d, 0xb7, 0xdb, 0x11, 0xfc, 0xec, 0x15, 0x60, +0xf0, 0x4e, 0x4b, 0x43, 0x13, 0x32, 0x98, 0x25, +0xd6, 0x2a, 0x86, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0x50, 0x85, 0x80, +0x53, 0x30, 0x72, 0x40, 0xbb, 0x98, 0x91, 0x39, +0x01, 0x2e, 0xbe, 0x7c, 0x57, 0x62, 0x22, 0xbf, +0xe5, 0x39, 0x0c, 0x5b, 0x83, 0xe9, 0x18, 0x58, +0xbd, 0x0c, 0x91, 0x13, 0xe3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0x1f, +0x3d, 0x1d, 0x1c, 0x51, 0x30, 0x62, 0xd0, 0xc8, +0x80, 0x9b, 0x60, 0x1c, 0x3e, 0xf9, 0x95, 0xaf, +0x08, 0x88, 0x2b, 0xc1, 0xcb, 0x95, 0x18, 0x4f, +0x72, 0xbe, 0x11, 0x00, 0x1a, 0x0a, 0xe9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0xc1, 0xd6, 0xc5, 0x47, 0x92, 0x4b, 0x1e, +0x04, 0x61, 0xb2, 0xb8, 0xfb, 0x31, 0x9c, 0xfe, +0x54, 0x84, 0x7a, 0xe9, 0xac, 0xb4, 0xa8, 0x7a, +0xcc, 0x37, 0x18, 0x6a, 0x54, 0x6f, 0x72, 0x21, +0x8d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0x52, 0xd5, 0x6b, 0xd0, 0x0a, +0x9c, 0xe3, 0x34, 0xe1, 0xd4, 0x1e, 0x42, 0x5e, +0x53, 0x30, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x23, 0x6d, 0x2f, 0xd4, +0x7e, 0xd9, 0x77, 0x50, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0x48, 0x02, 0x12, +0x73, 0x61, 0x5c, 0x5e, 0xaf, 0x4e, 0xb9, 0xde, +0xc7, 0x0f, 0x4b, 0x85, 0x49, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x51, +0x31, 0x4a, 0x71, 0x1d, 0x29, 0x2b, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0xa3, +0x63, 0xa5, 0xca, 0xf2, 0x98, 0x57, 0x04, 0xae, +0x22, 0xb9, 0x97, 0xdf, 0x36, 0x23, 0x71, 0x33, +0xdf, 0xe8, 0x29, 0x97, 0xab, 0x7a, 0x3c, 0x54, +0x55, 0x42, 0x8d, 0xc2, 0x8f, 0xd1, 0x1a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0x6d, 0x24, 0x1f, 0x56, 0x92, 0x30, 0x4a, +0x0e, 0xbd, 0xe0, 0x6a, 0x2e, 0xdc, 0x2d, 0xa5, +0x2a, 0x9d, 0xcb, 0x3a, 0x6b, 0x1e, 0x2f, 0x4a, +0x1a, 0x4f, 0xe9, 0x9a, 0x62, 0xee, 0x14, 0x37, +0x70, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0x39, 0xb5, 0x51, 0x0b, 0xf6, +0x2a, 0x5b, 0xda, 0x52, 0x7d, 0xf7, 0x0a, 0xd2, +0xaa, 0x3e, 0x6a, 0xad, 0xcb, 0x74, 0xab, 0x16, +0xb6, 0xd0, 0x50, 0xfb, 0x2a, 0x02, 0xb0, 0xdf, +0x8f, 0xb8, 0x1c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0x4f, 0x29, 0x89, +0xc2, 0xbd, 0xcb, 0x7a, 0x0d, 0xc2, 0x2e, 0xb2, +0xc6, 0x7c, 0xb7, 0x9d, 0xae, 0x0c, 0xdb, 0x36, +0xe9, 0x22, 0x76, 0x1c, 0x84, 0xf0, 0xda, 0xa9, +0xe2, 0x1b, 0xb6, 0x05, 0xed, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0x29, +0x78, 0xaa, 0x39, 0x09, 0x73, 0x6b, 0xfa, 0x72, +0xd5, 0xb2, 0x43, 0x4e, 0x29, 0x06, 0x4a, 0xde, +0x4b, 0x87, 0xe2, 0xb7, 0x80, 0x71, 0x44, 0x96, +0x9e, 0x69, 0x35, 0xe7, 0x81, 0x8d, 0xce, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0x65, 0xa4, 0x57, 0x2f, 0xb4, 0x31, 0xd2, +0x89, 0x3b, 0x94, 0xb0, 0x90, 0x69, 0xc7, 0x82, +0x9a, 0xea, 0x57, 0x9a, 0xbb, 0x6c, 0x16, 0x40, +0x38, 0xfd, 0x46, 0x27, 0x81, 0x6d, 0x60, 0x77, +0x1d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0x5e, 0xc9, 0x0e, 0xe6, 0xea, +0xa0, 0xe8, 0xf1, 0xb8, 0xcb, 0x83, 0x34, 0xec, +0xc3, 0xce, 0x1e, 0x6b, 0x1a, 0x5a, 0x8d, 0x49, +0x4d, 0x74, 0x5d, 0xb9, 0x9b, 0x82, 0x22, 0x2c, +0x89, 0x86, 0xf6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0xe1, 0x39, 0x77, +0x5d, 0x98, 0x68, 0x1a, 0xf4, 0x8e, 0xf9, 0x2e, +0x21, 0x5f, 0x72, 0x4a, 0xf0, 0xa8, 0xb6, 0x5e, +0x7d, 0xd6, 0x05, 0x94, 0xcf, 0xaf, 0xd7, 0x2b, +0x60, 0x20, 0x87, 0xc4, 0xb9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x3e, +0xe4, 0xf2, 0xea, 0x7d, 0xcc, 0x71, 0xe3, 0x14, +0xb3, 0xbb, 0x94, 0x18, 0xc2, 0x01, 0xad, 0x18, +0xe9, 0xca, 0xce, 0x96, 0x08, 0x82, 0x9e, 0xf4, +0x67, 0xef, 0x44, 0x88, 0xa2, 0x4f, 0x2d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0x50, 0x45, 0x37, 0x3c, 0x82, 0xaf, 0x16, +0xf6, 0x8b, 0xbe, 0x31, 0x28, 0x0d, 0xce, 0x5b, +0xf8, 0x89, 0x0a, 0x08, 0x0c, 0x97, 0x59, 0xe9, +0x84, 0xfa, 0x66, 0x85, 0xb4, 0x6e, 0x36, 0x06, +0xb8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0x7b, 0x12, 0x72, 0x7c, 0x57, +0x98, 0xd6, 0xbc, 0xbd, 0xcd, 0x29, 0xb2, 0x60, +0xa8, 0x5a, 0xf4, 0xe8, 0xea, 0x87, 0x0e, 0x48, +0xb8, 0x61, 0x13, 0xb6, 0x08, 0xe3, 0x9e, 0x51, +0x79, 0x2c, 0x09, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0x1e, 0xde, 0x53, +0xa8, 0x3a, 0xc5, 0xd6, 0x56, 0x6c, 0xbf, 0xf1, +0x71, 0x5b, 0xc5, 0x6e, 0x90, 0xd9, 0x9f, 0xd3, +0xb0, 0xa7, 0x64, 0x11, 0x6d, 0x6d, 0x68, 0xf1, +0x7e, 0xbb, 0x54, 0xea, 0xb4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0x23, +0xf2, 0x69, 0x89, 0xfb, 0x82, 0xa3, 0x4f, 0x60, +0x38, 0x0f, 0x68, 0xbf, 0x8f, 0xed, 0x7d, 0x7d, +0x50, 0xa6, 0xb8, 0x4c, 0x74, 0xeb, 0xf7, 0xa4, +0x7b, 0xde, 0xff, 0xe3, 0x11, 0x90, 0xab, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0x83, 0x5d, 0x3f, 0xb5, 0x96, 0xe1, 0x21, +0x5b, 0xca, 0xda, 0x2a, 0xb3, 0x31, 0xcc, 0x99, +0x91, 0x4d, 0x1b, 0xcb, 0xd3, 0xc6, 0xf6, 0x12, +0xba, 0x59, 0xc5, 0xcc, 0x6b, 0x58, 0x50, 0xe3, +0x4c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0x9c, 0x97, 0x62, 0x54, 0x6a, +0xd7, 0xea, 0x3d, 0xaf, 0xe0, 0x63, 0xc5, 0xef, +0x7a, 0x96, 0x6b, 0x83, 0x29, 0x50, 0xfd, 0x41, +0xfb, 0xcd, 0xbe, 0xc7, 0x41, 0xff, 0x07, 0xeb, +0xc5, 0x44, 0xc9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x92, 0x13, 0x36, +0x8b, 0x4a, 0x85, 0xf5, 0x0b, 0x75, 0xe2, 0x6e, +0x6b, 0xcc, 0xc0, 0x00, 0x6d, 0x0e, 0xc8, 0x99, +0x8a, 0x81, 0x7b, 0x86, 0x68, 0x18, 0xae, 0x2d, +0x09, 0x69, 0xf1, 0x09, 0xdf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0x38, +0xf3, 0x09, 0x5f, 0xf9, 0xca, 0x94, 0x42, 0xbc, +0xec, 0xdc, 0x6e, 0xd4, 0xc9, 0x22, 0x37, 0xfd, +0xad, 0x51, 0xbd, 0x8f, 0x31, 0x2d, 0x17, 0x23, +0xd6, 0xdd, 0x42, 0xca, 0x06, 0xcc, 0xcf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0x7a, 0xa0, 0xb8, 0xeb, 0x36, 0x68, 0x1f, +0xef, 0x87, 0xfa, 0xad, 0xbb, 0xc8, 0x95, 0xc9, +0xd2, 0xea, 0xea, 0xad, 0xcc, 0xf0, 0x6e, 0xe7, +0x32, 0xcd, 0x35, 0x19, 0x64, 0xb8, 0x81, 0x34, +0x27, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0xe2, 0x1e, 0xca, 0xa4, 0x34, +0xe4, 0x7c, 0x37, 0xf4, 0x05, 0x6c, 0xb7, 0x24, +0x78, 0x36, 0x89, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xe9, 0x61, 0xa4, 0xc2, +0x55, 0x55, 0x5b, 0xba, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x07, 0x79, 0x3e, +0x27, 0x2d, 0xa3, 0x34, 0x72, 0x16, 0xb3, 0x2d, +0x07, 0x1a, 0xcf, 0x48, 0x7e, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9f, 0x7c, +0x85, 0x0b, 0x75, 0x1f, 0xab, 0x76, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0xf1, +0xf9, 0xc4, 0x3c, 0x7e, 0xe0, 0x8a, 0xed, 0x17, +0x24, 0xf4, 0xd3, 0x53, 0x5a, 0xfc, 0x9d, 0x92, +0xd4, 0xcb, 0xa9, 0xe9, 0x03, 0xcd, 0xe5, 0xe5, +0x76, 0x10, 0xe8, 0xe5, 0xdc, 0x23, 0x43, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0xf2, 0xba, 0xd9, 0xd0, 0x57, 0xcb, 0x20, +0x3c, 0x55, 0x46, 0x73, 0x21, 0x94, 0xe7, 0x7f, +0x4b, 0x24, 0xc9, 0xc7, 0x1b, 0x15, 0xe6, 0x68, +0x81, 0xea, 0xfe, 0x58, 0x8e, 0x79, 0x04, 0x9e, +0xae, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0x54, 0x55, 0xff, 0x9a, 0x7e, +0xc7, 0x07, 0xf3, 0xda, 0xff, 0x9e, 0x00, 0x14, +0xb9, 0x25, 0x3f, 0x85, 0xc2, 0xd9, 0x7f, 0xd1, +0x46, 0xf3, 0x1a, 0x0d, 0x2a, 0x29, 0xf6, 0x7d, +0xcf, 0xad, 0xed, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0xb8, 0x55, 0x4f, +0x06, 0xfc, 0x81, 0xd1, 0x14, 0x77, 0x4c, 0x4a, +0x86, 0xe9, 0x29, 0xfc, 0x41, 0x5f, 0x32, 0x67, +0x6c, 0x9c, 0xae, 0x08, 0x86, 0xda, 0x9d, 0x1d, +0x17, 0x99, 0x6b, 0x15, 0x00, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0xf3, +0x6a, 0x1d, 0xe4, 0x48, 0x4f, 0xd0, 0xbe, 0x19, +0x07, 0x73, 0x75, 0x5e, 0xdd, 0xaa, 0x41, 0x15, +0x35, 0x0f, 0xd3, 0x80, 0x24, 0x82, 0x96, 0x60, +0x83, 0x14, 0xd2, 0xd9, 0x5d, 0x54, 0x59, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0xf1, 0x1f, 0x17, 0xce, 0xe4, 0x0a, 0xe2, +0x35, 0x11, 0xed, 0xf1, 0x09, 0x64, 0xc5, 0x00, +0xfe, 0x47, 0x80, 0x8f, 0x25, 0x88, 0xdd, 0x6b, +0xaf, 0x1c, 0xd5, 0x32, 0x0a, 0x10, 0x48, 0xfc, +0x99, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0x2a, 0xf9, 0x80, 0xd3, 0x47, +0x52, 0x2c, 0x3f, 0xe6, 0x35, 0x09, 0x8e, 0x19, +0x28, 0xc7, 0x51, 0x48, 0x39, 0x30, 0x54, 0x4e, +0xf8, 0x93, 0x2e, 0xe4, 0xef, 0xa5, 0x83, 0x77, +0xc3, 0x5f, 0xa9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0xcc, 0xb8, 0xde, +0x4f, 0xf0, 0x29, 0xae, 0xc8, 0x87, 0xeb, 0xef, +0xca, 0x76, 0x9a, 0xbf, 0x1b, 0xb7, 0x1e, 0x74, +0xc4, 0xb9, 0x3a, 0x3a, 0x75, 0x6e, 0xbe, 0x27, +0xc0, 0x50, 0xee, 0xc9, 0xdf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0xb1, +0xfe, 0x96, 0x0e, 0x67, 0x97, 0x26, 0xa9, 0x18, +0x97, 0x53, 0x6f, 0xfb, 0x18, 0x05, 0xdd, 0xd5, +0xf7, 0x26, 0x16, 0xf1, 0x63, 0x85, 0x78, 0x8c, +0x2a, 0x12, 0xe5, 0xc5, 0xaa, 0xbb, 0x77, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0xb0, 0x92, 0x35, 0x2c, 0xf8, 0x0a, 0x0d, +0x5a, 0xa4, 0x49, 0x34, 0x79, 0xeb, 0x0d, 0xde, +0x24, 0xb2, 0x9a, 0xfc, 0x8d, 0xec, 0xac, 0xf7, +0x47, 0x41, 0xf3, 0x69, 0xa8, 0x88, 0x54, 0x13, +0xf6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0x20, 0xe0, 0x58, 0x6c, 0x89, +0xea, 0xa6, 0x53, 0xb2, 0x3a, 0xd1, 0xdf, 0x57, +0xd6, 0x56, 0x11, 0xa8, 0x99, 0xd7, 0x72, 0x67, +0xa3, 0x65, 0x4d, 0x2f, 0x62, 0x39, 0x3b, 0x5b, +0x07, 0xe2, 0x6b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0x81, 0x14, 0xff, +0xa9, 0x30, 0xc0, 0xfb, 0xb5, 0xc8, 0xb0, 0xf1, +0x9e, 0xa2, 0x5a, 0xf3, 0x6e, 0xa1, 0x1a, 0xf6, +0x45, 0xb3, 0xeb, 0x5c, 0xfa, 0x90, 0x22, 0xa9, +0x47, 0x81, 0x30, 0xd4, 0xf4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0xbb, +0x0f, 0xc4, 0x1e, 0x69, 0x04, 0xe7, 0x5b, 0xb4, +0x3e, 0x95, 0x3a, 0xc5, 0xbf, 0xc6, 0xf5, 0x2f, +0xb4, 0x5d, 0xf9, 0xf7, 0x5e, 0x35, 0x08, 0x05, +0xff, 0xe5, 0x15, 0x33, 0x3c, 0x47, 0x5a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0xb5, 0x0f, 0x9c, 0x35, 0x7a, 0x0a, 0x5e, +0xc0, 0x6c, 0xea, 0x1a, 0xa4, 0xb4, 0x34, 0x2f, +0x9e, 0x03, 0xab, 0x02, 0x8b, 0x71, 0x90, 0xf4, +0x69, 0x5e, 0xf1, 0x1f, 0xbf, 0x6a, 0x25, 0xed, +0x16, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0x6b, 0x28, 0x84, 0xe1, 0xfe, +0xca, 0xc1, 0x96, 0x14, 0xd7, 0xfd, 0xa7, 0x83, +0x97, 0x7e, 0xe8, 0x3a, 0xfd, 0x70, 0x05, 0x74, +0x55, 0x84, 0x89, 0x8d, 0x8f, 0x57, 0xea, 0xfb, +0xc4, 0xcc, 0x9d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0x4d, 0x19, 0xd9, +0x9c, 0x59, 0xe3, 0xeb, 0x27, 0x24, 0xcb, 0x6a, +0x04, 0x39, 0x4e, 0xe3, 0x02, 0xa0, 0x19, 0x69, +0x40, 0x71, 0x77, 0x7e, 0x6a, 0x09, 0xfc, 0x63, +0xfa, 0xf3, 0x4d, 0x18, 0xfe, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0x4a, +0x84, 0xb5, 0x3d, 0x2c, 0x37, 0xfe, 0x78, 0xd6, +0xbe, 0x84, 0x97, 0xf4, 0xa7, 0xa6, 0x58, 0x3c, +0x47, 0xcb, 0xcb, 0xe1, 0x1a, 0x56, 0xe9, 0x9d, +0x6f, 0xe2, 0xce, 0x49, 0xa1, 0x83, 0xe2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0x91, 0xa1, 0x3c, 0x3f, 0xe0, 0x6e, 0x27, +0x5c, 0xa8, 0x4b, 0x89, 0xb0, 0x3b, 0x16, 0x45, +0x99, 0xc1, 0xd8, 0x6d, 0xe0, 0x2e, 0xf9, 0xdf, +0x89, 0x32, 0x23, 0xf3, 0x6c, 0x1f, 0xde, 0x7c, +0x85, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0x1e, 0xbf, 0xcb, 0xd6, 0xd8, +0xa4, 0x14, 0x01, 0xcb, 0xdf, 0xa1, 0x2d, 0xc7, +0x64, 0x91, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x83, 0xab, 0xb3, 0xa8, +0x1f, 0x05, 0x45, 0xed, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x93, 0x80, 0xab, +0xe3, 0xb5, 0x6f, 0x6b, 0x9a, 0x47, 0x9f, 0x4f, +0x86, 0xef, 0x97, 0xea, 0x1d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbe, 0x12, +0x19, 0xf7, 0x62, 0x76, 0xa4, 0xe3, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x76, +0x07, 0xdb, 0x7b, 0xe9, 0xce, 0xca, 0xdb, 0xbc, +0x99, 0xd1, 0x5d, 0x27, 0x3c, 0x7c, 0x4a, 0x9a, +0xa6, 0x99, 0x08, 0x93, 0xf4, 0x35, 0x9d, 0x4f, +0x66, 0x10, 0xe7, 0xd3, 0x89, 0xd2, 0x8e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x60, 0x17, 0x8f, 0xc0, 0x35, 0x14, 0x62, +0x0b, 0xab, 0x91, 0x92, 0xab, 0x1e, 0x86, 0xf8, +0x7f, 0x5e, 0x15, 0x7e, 0x80, 0x53, 0x8a, 0xd4, +0x47, 0x22, 0x79, 0x30, 0x20, 0x95, 0xf0, 0xce, +0x8f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0x49, 0x22, 0x49, 0x88, 0x42, +0x1c, 0x40, 0xe2, 0x44, 0xd2, 0x4b, 0x31, 0x83, +0x55, 0xfd, 0xdc, 0x46, 0x8f, 0xa9, 0x5c, 0x13, +0x8b, 0x26, 0xd9, 0xcf, 0xac, 0x3d, 0xb4, 0x2f, +0x03, 0x1e, 0x13, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0xea, 0x24, 0x0e, +0x3f, 0x1d, 0x74, 0x09, 0xd6, 0x0b, 0x59, 0x8f, +0xa5, 0x8e, 0x76, 0xd6, 0xa0, 0x5a, 0xe2, 0xcc, +0xb5, 0xca, 0xef, 0x85, 0xec, 0x88, 0x5f, 0x60, +0x86, 0xdb, 0x78, 0x5a, 0x1d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0x49, +0x08, 0xb6, 0x29, 0x60, 0xe7, 0x6d, 0x54, 0xa9, +0xe0, 0x78, 0x96, 0xc5, 0xc6, 0x2e, 0x3e, 0x62, +0x80, 0x12, 0xc5, 0xc4, 0x15, 0x5f, 0x05, 0x64, +0xdb, 0xbb, 0x76, 0xab, 0x8d, 0xc3, 0x58, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0xe7, 0x8e, 0x41, 0x1f, 0xe3, 0xe8, 0xa0, +0x36, 0x2c, 0x6f, 0x85, 0x00, 0x13, 0x16, 0x05, +0xbe, 0x0f, 0x1d, 0x8d, 0x40, 0x04, 0xb5, 0x4d, +0x2a, 0xfa, 0x66, 0x96, 0x0d, 0xf1, 0xb0, 0x3b, +0xc4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0xc1, 0x9c, 0x72, 0xa7, 0xd6, +0x0b, 0xb1, 0xd6, 0xd3, 0x36, 0xe0, 0x72, 0x71, +0x15, 0x42, 0xfb, 0xf6, 0xb6, 0xe7, 0x01, 0x6c, +0xdd, 0x0c, 0x92, 0x04, 0xc4, 0x29, 0x9c, 0xc0, +0x61, 0x3e, 0x13, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0x97, 0x80, 0x36, +0xf2, 0x7e, 0xb3, 0x19, 0x33, 0xce, 0x28, 0xd5, +0x45, 0xf1, 0xe5, 0x47, 0x40, 0xe6, 0xd1, 0x03, +0x0f, 0x7f, 0xc0, 0xcb, 0x73, 0xa5, 0x42, 0x4a, +0x93, 0x4d, 0xbe, 0x3b, 0x09, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0xd9, +0xa5, 0xf7, 0xa0, 0xe2, 0x7c, 0x29, 0xe3, 0x9c, +0x6f, 0x2b, 0x7d, 0x27, 0x0e, 0xea, 0x68, 0x85, +0xe4, 0x57, 0x55, 0x1b, 0x88, 0xbf, 0x4e, 0x9d, +0x81, 0xb7, 0x48, 0x7b, 0x8a, 0x04, 0xbd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0x05, 0xa9, 0xcb, 0xd6, 0xfd, 0x16, 0x19, +0x43, 0x9a, 0x97, 0xa8, 0x07, 0x24, 0x8d, 0x95, +0x38, 0xe3, 0xb7, 0x28, 0x25, 0xc2, 0xac, 0xb1, +0x4e, 0x32, 0xd3, 0xe2, 0x85, 0x9c, 0x11, 0xd6, +0x54, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0xe8, 0x1b, 0xea, 0x64, 0x7c, +0x15, 0xdf, 0x24, 0x38, 0xbf, 0x73, 0x53, 0x8e, +0x44, 0x4c, 0xaf, 0x3b, 0xb7, 0x63, 0x73, 0x87, +0x6a, 0x2a, 0x76, 0x34, 0x73, 0x1d, 0x2a, 0xde, +0x61, 0x24, 0x85, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0x6d, 0xb1, 0xf9, +0xcd, 0x44, 0xb6, 0xcb, 0x1a, 0xd9, 0xe5, 0xcb, +0x7c, 0x4e, 0x3b, 0x37, 0x4d, 0xfe, 0xcf, 0x8e, +0x92, 0xb5, 0xa8, 0x37, 0x0f, 0xdc, 0x6d, 0xd2, +0x6f, 0x26, 0x6e, 0x04, 0x0c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0x89, +0x2f, 0x54, 0x41, 0x12, 0xad, 0xf2, 0xb4, 0xcc, +0x6a, 0x82, 0x06, 0xd1, 0xa7, 0xdb, 0x5a, 0x5a, +0xe6, 0x9a, 0xe2, 0xba, 0xc8, 0xe2, 0x45, 0xe7, +0x22, 0x75, 0x33, 0x47, 0x3e, 0xfb, 0x9c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0x28, 0xd9, 0x5e, 0x1e, 0xa8, 0x39, 0x91, +0xda, 0xcc, 0x7f, 0x0b, 0x1b, 0x9c, 0xad, 0xb1, +0x69, 0x9f, 0x7d, 0x9b, 0xf3, 0x5d, 0xb9, 0x1f, +0xe3, 0x30, 0x20, 0x86, 0xa7, 0xf0, 0xb6, 0x81, +0xfa, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0xeb, 0x65, 0x57, 0xca, 0xb3, +0xe1, 0xd9, 0xd6, 0xbf, 0x7c, 0xa1, 0x8b, 0xd1, +0x43, 0x51, 0x09, 0x09, 0x0c, 0x33, 0x79, 0x0f, +0x7a, 0x07, 0xab, 0xda, 0x75, 0xbf, 0x4a, 0xa7, +0x3a, 0x54, 0x33, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0x4b, 0xce, 0x43, +0xc0, 0xe6, 0x8e, 0x32, 0x1f, 0x36, 0xb0, 0xa3, +0xa3, 0xff, 0xe3, 0x11, 0xea, 0xfd, 0x22, 0x78, +0x17, 0x73, 0x4f, 0x7c, 0xfb, 0x88, 0x47, 0xd8, +0xe1, 0x73, 0xf9, 0x11, 0xdb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0x2e, +0xee, 0x17, 0xea, 0x6e, 0xd2, 0xfc, 0xfa, 0x76, +0x32, 0x1e, 0x50, 0x7a, 0xbe, 0xc1, 0x86, 0xa7, +0x3e, 0x83, 0x7b, 0x8b, 0xbd, 0x45, 0x5c, 0x5a, +0x16, 0x04, 0xda, 0x97, 0x01, 0x9a, 0x53, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0x4d, 0x48, 0x6e, 0x78, 0x31, 0x13, 0x7c, +0x64, 0xff, 0x38, 0x35, 0xcc, 0x53, 0xf3, 0x37, +0xf1, 0x99, 0xf0, 0xce, 0x44, 0xc9, 0x3b, 0xa2, +0x43, 0x95, 0x23, 0x44, 0xe9, 0xce, 0x89, 0x95, +0x5e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0x6d, 0x1e, 0x8f, 0x53, 0xa5, +0xc4, 0x61, 0xe5, 0xad, 0xf1, 0x38, 0xd4, 0xe4, +0x77, 0xf7, 0xd7, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x4b, 0x3a, 0xb4, 0x89, +0xf7, 0x3c, 0xb5, 0x21, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0xe0, 0xc3, 0x66, +0xa2, 0x19, 0x34, 0x86, 0xf1, 0xb0, 0x56, 0x24, +0xfd, 0xce, 0xa4, 0xd8, 0xfa, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xb4, +0x66, 0xf0, 0xd5, 0x16, 0xb4, 0xc1, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0x4e, +0x66, 0x37, 0xa3, 0xa5, 0x0c, 0x09, 0xc8, 0x80, +0x97, 0xf4, 0xd0, 0x0c, 0x1f, 0xcf, 0xca, 0xbe, +0x80, 0x62, 0xe7, 0xc2, 0xb7, 0xc3, 0x2f, 0xba, +0xa0, 0x08, 0xa6, 0x6d, 0x6c, 0xa2, 0x2e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0x43, 0x4b, 0x5f, 0x9e, 0xb0, 0xed, 0x85, +0x5f, 0x6a, 0xba, 0x99, 0x03, 0xa0, 0x2e, 0xa9, +0xb9, 0x01, 0x27, 0xaf, 0xb4, 0xbc, 0x00, 0xca, +0xbc, 0x9f, 0x9d, 0x56, 0x62, 0x53, 0x30, 0xfe, +0x62, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0x13, 0xb7, 0x15, 0xc8, 0xa0, +0x05, 0x79, 0x34, 0x4a, 0x6f, 0x14, 0xed, 0x6b, +0xa5, 0xd0, 0xe6, 0xe4, 0x56, 0x69, 0xf7, 0x5c, +0xd4, 0x03, 0x55, 0x35, 0xdd, 0x53, 0xfd, 0x4b, +0xff, 0x18, 0x69, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0x66, 0x9e, 0xac, +0x67, 0x31, 0x7a, 0x4f, 0xe1, 0x4c, 0xfa, 0x2b, +0xbd, 0xd8, 0x24, 0x0e, 0x00, 0x91, 0x6d, 0x80, +0xa7, 0x28, 0xb2, 0x15, 0x59, 0x2c, 0x7e, 0xc3, +0x63, 0x07, 0x14, 0xc1, 0xfe, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0xd8, +0x1c, 0x86, 0xd3, 0x92, 0x4d, 0x28, 0xb1, 0x4a, +0xe7, 0x3b, 0x9d, 0x0c, 0x46, 0x10, 0x08, 0xa6, +0x50, 0x16, 0x74, 0x06, 0xd0, 0xbf, 0x6d, 0xb6, +0xbb, 0x4f, 0x1b, 0x9f, 0xf0, 0xc5, 0xf7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0xd5, 0xd9, 0xb2, 0xd2, 0x16, 0x94, 0x27, +0xb8, 0x68, 0xc6, 0xba, 0x6f, 0xa8, 0x16, 0xef, +0x90, 0x34, 0x89, 0xcf, 0x08, 0xb1, 0x78, 0x94, +0xec, 0xc1, 0x96, 0x04, 0x80, 0xc0, 0x5b, 0x9e, +0xfd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0xa1, 0xb6, 0x9a, 0xb1, 0x81, +0xff, 0xed, 0x1a, 0xe6, 0x62, 0xa7, 0xbb, 0x54, +0x58, 0x8c, 0xc8, 0x75, 0xe2, 0xe1, 0x02, 0x13, +0x16, 0xa5, 0xde, 0xbb, 0x74, 0x63, 0x33, 0x02, +0x03, 0xe8, 0x31, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0xdb, 0x81, 0x8d, +0x30, 0x67, 0xfa, 0x82, 0x68, 0x91, 0xfc, 0x59, +0x60, 0x15, 0xbb, 0x99, 0x76, 0x4f, 0xf7, 0xa2, +0x23, 0xbf, 0x97, 0x83, 0xf0, 0x64, 0xe7, 0x16, +0xa5, 0xcf, 0x90, 0x4f, 0x97, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0x1f, +0xaa, 0x0f, 0x52, 0x9b, 0xbb, 0x3f, 0x34, 0xf3, +0x8d, 0xd7, 0xa2, 0x73, 0x3a, 0x69, 0x30, 0xed, +0x38, 0x66, 0xed, 0xaa, 0xf2, 0x3e, 0xde, 0x5d, +0x67, 0x5a, 0x5d, 0xcb, 0xe6, 0xb5, 0x74, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0x56, 0x4f, 0xae, 0x21, 0x37, 0xe7, 0xfd, +0xe6, 0x34, 0xf5, 0xf5, 0x23, 0xf5, 0xbb, 0x95, +0x8f, 0x1e, 0xee, 0xa6, 0x0e, 0x27, 0x49, 0x7d, +0x74, 0xc9, 0x22, 0x6c, 0xa2, 0x02, 0xee, 0xc1, +0x30, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0x27, 0x09, 0x61, 0x69, 0x03, +0x3f, 0x7b, 0x6d, 0x60, 0xb9, 0x26, 0xdd, 0x2a, +0xf3, 0x77, 0xbe, 0x70, 0x1a, 0x83, 0x3e, 0xfd, +0xbd, 0x4a, 0xf3, 0xe1, 0x3c, 0x14, 0x35, 0x2d, +0xa3, 0x05, 0x25, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0xf7, 0xf2, 0x45, +0x52, 0x66, 0xd9, 0xcf, 0x74, 0xf9, 0xc1, 0xe1, +0xf3, 0x1a, 0x80, 0x77, 0x38, 0x3d, 0xb1, 0x89, +0x84, 0xc2, 0x5f, 0xf6, 0x71, 0xb6, 0x9a, 0xe6, +0xaf, 0x46, 0x2c, 0x99, 0x4d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0x4e, +0x75, 0xe9, 0xeb, 0x98, 0x78, 0x6d, 0x56, 0x25, +0x23, 0x45, 0xb2, 0x3f, 0x5d, 0xda, 0x82, 0x86, +0xa6, 0xb5, 0xae, 0xb6, 0x42, 0xfa, 0x85, 0x5e, +0x3b, 0xc0, 0xe2, 0x30, 0xde, 0x40, 0xb9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0xca, 0xba, 0xe7, 0xde, 0x72, 0x58, 0x03, +0x39, 0x24, 0x85, 0xc8, 0x1e, 0x49, 0x89, 0x26, +0xd0, 0xa5, 0x70, 0x7e, 0x3d, 0xc4, 0xb4, 0x2a, +0x97, 0x52, 0x7f, 0x6d, 0x37, 0x69, 0x9e, 0x96, +0x84, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0x7a, 0x76, 0x20, 0xf3, 0xae, +0xb4, 0x19, 0x53, 0x87, 0xf7, 0xcd, 0xfb, 0xc7, +0x05, 0x43, 0x1c, 0x7b, 0x64, 0x9e, 0x62, 0x89, +0x3a, 0x3d, 0x0b, 0x89, 0xcc, 0x90, 0xc3, 0xa6, +0x2c, 0xff, 0x42, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x1f, 0xfd, 0xa4, +0x5b, 0x82, 0xf8, 0x7a, 0xd2, 0xd2, 0xee, 0x1e, +0x06, 0xcd, 0x7e, 0x30, 0xff, 0x54, 0xd1, 0x05, +0xf4, 0x7e, 0xa1, 0xe2, 0x33, 0xda, 0xb3, 0xde, +0x23, 0x9b, 0xe8, 0xee, 0x9f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0x43, +0x29, 0x32, 0x5c, 0xe0, 0xec, 0xae, 0xaa, 0x39, +0x23, 0x91, 0x67, 0x74, 0xd3, 0xa2, 0x0f, 0x00, +0x41, 0xf5, 0x52, 0x0c, 0xf7, 0x9c, 0x33, 0x83, +0xa7, 0x2b, 0xa0, 0x98, 0x35, 0xcc, 0x66, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x81, 0x91, 0x71, 0xc3, 0x42, 0x22, 0x71, +0xe3, 0xcc, 0xff, 0x9b, 0x87, 0x74, 0xed, 0x47, +0x91, 0x89, 0xeb, 0x03, 0x5f, 0xd5, 0x92, 0x0c, +0x38, 0xe5, 0x6e, 0xaf, 0x42, 0x39, 0x32, 0x3f, +0x2e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0x70, 0x39, 0x72, 0x6a, 0xd2, +0x3b, 0xf3, 0xfe, 0xa3, 0x16, 0x1d, 0x9c, 0x35, +0x30, 0x87, 0x7b, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x06, 0x50, 0xca, 0xfe, +0x4a, 0x85, 0xb3, 0xa7, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0x7a, 0xd7, 0x4f, +0x4d, 0x7e, 0x29, 0x65, 0xda, 0x4a, 0xba, 0xb2, +0xe3, 0x70, 0x73, 0xba, 0xc0, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf3, 0xcd, +0x68, 0xd1, 0xf5, 0xd0, 0x37, 0xe2, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0xff, +0x2e, 0xb4, 0x46, 0xd6, 0x1c, 0x2c, 0xfb, 0x14, +0xb4, 0xc2, 0x46, 0x95, 0xaf, 0xca, 0xd8, 0x7f, +0x94, 0x69, 0xa4, 0xa7, 0xd0, 0x61, 0xd3, 0x80, +0x61, 0x60, 0x8e, 0xf1, 0x0a, 0xbb, 0x03, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0x45, 0x23, 0xe3, 0xd0, 0x57, 0xc8, 0x74, +0x4e, 0x7f, 0xc5, 0x19, 0x81, 0xd1, 0x18, 0x0c, +0x10, 0x2a, 0xaa, 0x36, 0xf7, 0x10, 0xcd, 0xe3, +0xf7, 0x17, 0x4a, 0xc3, 0xb4, 0x03, 0x30, 0xad, +0xa5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0xea, 0xb4, 0x81, 0x0d, 0xff, +0xe5, 0xb7, 0xec, 0xeb, 0xe7, 0xf7, 0xe1, 0x74, +0xfe, 0x78, 0x7f, 0xb6, 0xbf, 0x32, 0x6e, 0x48, +0x78, 0x22, 0x90, 0x34, 0xf7, 0xe5, 0xf9, 0xea, +0x2c, 0xee, 0xc6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0x8f, 0x99, 0xe9, +0x92, 0x69, 0xbf, 0x8f, 0xd3, 0x85, 0x80, 0xfe, +0x65, 0x9c, 0x84, 0x00, 0x50, 0xae, 0xc9, 0xdb, +0x39, 0xcf, 0xbc, 0x3a, 0x65, 0x41, 0xd2, 0x52, +0x08, 0xa4, 0x70, 0x4f, 0x33, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x34, +0x90, 0x8c, 0xe6, 0xf8, 0x93, 0xfd, 0x49, 0xca, +0xcf, 0x48, 0x4f, 0x1d, 0x7a, 0xac, 0x5a, 0xef, +0x81, 0x40, 0x89, 0x80, 0x67, 0x48, 0x7f, 0x94, +0x48, 0x82, 0x44, 0xf5, 0x6f, 0xeb, 0x61, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0x8a, 0x26, 0x7e, 0x7c, 0xfa, 0x69, 0x53, +0x6f, 0x07, 0x27, 0x6e, 0xa5, 0xa1, 0x1a, 0xf5, +0x58, 0xb2, 0xcd, 0x24, 0xd6, 0x3a, 0xfb, 0x4a, +0x13, 0x3b, 0xbe, 0xb9, 0x22, 0x9a, 0x1f, 0x1b, +0xe4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0xa7, 0xf5, 0x41, 0x6d, 0xad, +0xf0, 0xa0, 0x50, 0x41, 0x25, 0xb1, 0xf9, 0x58, +0x3e, 0x71, 0xa6, 0x03, 0xd1, 0x59, 0x61, 0xe4, +0x93, 0xea, 0x69, 0x33, 0x9f, 0x21, 0x18, 0x14, +0x21, 0xaf, 0x0e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0xc9, 0xdb, 0x77, +0x07, 0x41, 0xb6, 0xcf, 0x30, 0x99, 0x6f, 0xbf, +0x97, 0xad, 0x06, 0x1d, 0x94, 0x82, 0xa1, 0xe5, +0x6d, 0xb0, 0xa7, 0x40, 0x9b, 0x5a, 0xeb, 0x2f, +0xd3, 0xc3, 0xd5, 0x1b, 0xed, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x92, +0xcf, 0x2c, 0xb8, 0xcf, 0x3e, 0x16, 0xda, 0xa3, +0x43, 0xa9, 0x2c, 0x38, 0x6e, 0xb0, 0x34, 0x42, +0x5e, 0x0e, 0x06, 0x90, 0x62, 0x36, 0x93, 0x66, +0x9b, 0xa5, 0x48, 0xa7, 0x9d, 0xd3, 0x86, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0xc7, 0x71, 0x77, 0x1c, 0x6d, 0x4d, 0xf4, +0xd0, 0x8f, 0x07, 0x66, 0xd5, 0x36, 0x00, 0x9c, +0xf4, 0xe5, 0x5f, 0xe0, 0x0e, 0xe0, 0x23, 0xcb, +0x8c, 0xed, 0xa7, 0xa9, 0x68, 0xf5, 0xa7, 0xfc, +0xc3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0x90, 0xe5, 0xbd, 0x5a, 0x0f, +0x20, 0x23, 0x50, 0xcc, 0xa7, 0xc6, 0x54, 0x69, +0x77, 0x09, 0xe2, 0xca, 0x14, 0xe5, 0x47, 0xfc, +0x9d, 0x05, 0x2f, 0x8b, 0x69, 0x14, 0x34, 0xb1, +0x00, 0xf3, 0x3e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0xa1, 0x9e, 0xcd, +0x45, 0x13, 0xec, 0x3d, 0x18, 0x59, 0x7c, 0x42, +0x62, 0x85, 0xba, 0x32, 0x5f, 0xfe, 0x23, 0xd9, +0xab, 0xd3, 0xe5, 0xf4, 0xae, 0x9f, 0xe6, 0xd1, +0x57, 0xd0, 0x01, 0x50, 0x19, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0x43, +0x41, 0x25, 0x81, 0xb8, 0x6d, 0x5a, 0x34, 0x13, +0x22, 0x21, 0x1f, 0x85, 0xc9, 0x50, 0x5c, 0x70, +0x4c, 0x85, 0xf4, 0x01, 0x51, 0xa4, 0x86, 0xa5, +0xf8, 0xad, 0xe0, 0x7b, 0xf9, 0x94, 0x7f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0x7a, 0x1b, 0x0e, 0xed, 0x79, 0x57, 0x07, +0xe5, 0x7f, 0x82, 0xc7, 0xcf, 0xff, 0x6e, 0x6c, +0x5f, 0x2c, 0x42, 0xb4, 0x24, 0x4a, 0x89, 0x59, +0x3b, 0x34, 0x3f, 0x31, 0x34, 0x4d, 0x45, 0x99, +0xb2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0xd5, 0x09, 0x80, 0xb3, 0x22, +0x83, 0xd8, 0x5f, 0x66, 0x1b, 0x86, 0x34, 0x4b, +0x7a, 0xb0, 0xfc, 0xe1, 0xe7, 0x81, 0x2e, 0x3a, +0x47, 0x52, 0x3c, 0x23, 0x36, 0x9b, 0xac, 0x31, +0x26, 0xc4, 0x2e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0xe9, 0x5d, 0x2a, +0x94, 0x78, 0xb1, 0x02, 0xc2, 0x49, 0x67, 0x59, +0x98, 0xbe, 0xbf, 0x35, 0x59, 0x6a, 0x11, 0xe2, +0xeb, 0x6b, 0xe0, 0xc0, 0x5a, 0xef, 0x6a, 0xf5, +0x6b, 0xf7, 0x04, 0xc4, 0x9a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0xa2, +0xf8, 0x04, 0xed, 0xbb, 0x7f, 0x1a, 0xa8, 0xe5, +0xac, 0x21, 0x4c, 0xf8, 0x3e, 0x76, 0xbb, 0xa1, +0xd1, 0x18, 0x47, 0x92, 0x6d, 0xc5, 0xbf, 0xb2, +0xc1, 0xa8, 0xca, 0xa8, 0x88, 0x1a, 0x57, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x98, 0x57, 0xba, 0x54, 0x31, 0x41, 0x3a, +0x95, 0x16, 0xb9, 0xe8, 0x80, 0x9a, 0x7f, 0xf8, +0x41, 0x65, 0x46, 0xfc, 0x46, 0x9e, 0xde, 0xbd, +0x2c, 0x83, 0x68, 0x8b, 0x21, 0x98, 0xab, 0xf1, +0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0xa5, 0x96, 0x3e, 0xc5, 0x0d, +0xf0, 0x2d, 0xdc, 0x3a, 0x70, 0xf9, 0x9e, 0x78, +0xb9, 0xbb, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x8e, 0xf2, 0xeb, 0x41, +0x69, 0x7c, 0xf3, 0x1e, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0x99, 0xe3, 0xf2, +0x94, 0x90, 0x00, 0x1e, 0x3d, 0x8d, 0x71, 0x74, +0x56, 0x4d, 0xee, 0xda, 0x8c, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x7f, +0xdf, 0x0d, 0xc7, 0xd0, 0x36, 0x56, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0x2b, +0xa6, 0x2e, 0xda, 0x3a, 0x2d, 0xf0, 0x28, 0x1d, +0x4a, 0x32, 0xe9, 0x57, 0x9b, 0x4a, 0xbd, 0xce, +0x97, 0x6e, 0x85, 0x7c, 0x6d, 0x25, 0xd8, 0xfe, +0x88, 0x36, 0x0f, 0x92, 0x96, 0x9f, 0x0d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x86, 0x96, 0xad, 0xa4, 0xc7, 0xaf, 0x63, +0xad, 0xc4, 0x7d, 0xe1, 0xc5, 0x8f, 0x8b, 0xc2, +0xa5, 0x16, 0x9a, 0x58, 0x21, 0x0c, 0xd8, 0x6e, +0x36, 0x3e, 0x80, 0x0e, 0x9e, 0x07, 0x58, 0x0b, +0x04, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0xe9, 0x63, 0x31, 0xbd, 0x35, +0x50, 0x49, 0xc8, 0x16, 0x99, 0xf5, 0x81, 0xe7, +0x2a, 0x60, 0x08, 0x9f, 0xf5, 0x15, 0xe1, 0x6e, +0xf2, 0xf6, 0xf9, 0x46, 0x5d, 0x58, 0x34, 0x21, +0xdd, 0xcc, 0x60, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0xb8, 0x65, 0x42, +0xff, 0xfa, 0xd8, 0x15, 0xbf, 0x64, 0x07, 0x3a, +0xbe, 0xac, 0x14, 0x9c, 0x29, 0x0a, 0x18, 0x5a, +0x5d, 0x13, 0x68, 0x00, 0x3d, 0xc5, 0x70, 0x53, +0x5b, 0x2b, 0xdb, 0xa6, 0x9b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x25, +0x26, 0x29, 0xca, 0x1a, 0xac, 0x49, 0x8f, 0x76, +0x54, 0x88, 0x53, 0xdc, 0x08, 0x82, 0xd4, 0x84, +0x52, 0x98, 0xa4, 0xf2, 0xec, 0xf1, 0x9b, 0x3e, +0xf6, 0x8b, 0x4a, 0x44, 0x67, 0xf2, 0x65, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0xfd, 0x58, 0x3f, 0x08, 0x26, 0x63, 0x7d, +0x6a, 0xeb, 0xd7, 0x84, 0x92, 0x7a, 0xbe, 0x47, +0xc1, 0x22, 0x64, 0x03, 0xc7, 0x49, 0xf3, 0x0c, +0x79, 0x34, 0x42, 0xb9, 0x4a, 0xdd, 0x24, 0x8e, +0xc8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0x4f, 0x41, 0x0f, 0x53, 0x16, +0x7c, 0xc5, 0xc8, 0xe9, 0x72, 0xa8, 0xaf, 0x35, +0xc5, 0x3c, 0x4a, 0x44, 0x6a, 0xea, 0xf2, 0xb8, +0xa6, 0x20, 0x1e, 0x87, 0x58, 0xdb, 0xd1, 0xe4, +0x06, 0xe0, 0x4f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0xd3, 0xd3, 0x94, +0x2c, 0xdc, 0x4d, 0xa5, 0x74, 0xd9, 0xae, 0xd9, +0xa1, 0xce, 0x90, 0xfc, 0x53, 0xd0, 0x2c, 0xb0, +0x22, 0x85, 0x1d, 0x79, 0x1d, 0x2c, 0xf0, 0x86, +0xcb, 0xb4, 0x61, 0xa4, 0x9e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0x1c, +0x1d, 0x0c, 0xf6, 0xd1, 0xe2, 0xe9, 0xfc, 0xf3, +0xbd, 0x45, 0xd6, 0xfa, 0xf9, 0xfc, 0x80, 0xd8, +0x76, 0x2b, 0x23, 0x3d, 0xc8, 0xe5, 0x9d, 0xc4, +0xbc, 0x3a, 0xa6, 0xa7, 0x82, 0xde, 0x5d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0x43, 0xdb, 0xc3, 0xe3, 0xc4, 0x2f, 0xc3, +0x49, 0xb8, 0xbd, 0x11, 0x63, 0x9a, 0x26, 0x5d, +0x95, 0xe2, 0xe1, 0x49, 0x7d, 0x2f, 0x74, 0x68, +0xb7, 0xc5, 0x75, 0xf1, 0x64, 0x52, 0xe9, 0xb6, +0x55, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0x2c, 0x93, 0xd9, 0x91, 0x99, +0xfc, 0x8e, 0xb3, 0x59, 0x23, 0x82, 0x2a, 0x52, +0xc8, 0x18, 0xd6, 0x7f, 0x51, 0x82, 0x0f, 0x19, +0x17, 0x26, 0xb3, 0x3f, 0x5e, 0x96, 0x27, 0x2a, +0x49, 0x6d, 0x0a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0x4b, 0xd3, 0x3a, +0x21, 0xd0, 0xdd, 0x0c, 0x97, 0x12, 0xa4, 0x0e, +0x0e, 0x28, 0xd4, 0x59, 0x10, 0x3f, 0xd3, 0x39, +0x9c, 0xaa, 0x16, 0x6f, 0xd1, 0x87, 0xc3, 0xee, +0xc8, 0xda, 0xd3, 0x1a, 0xa5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0x13, +0x30, 0x1d, 0xb7, 0x58, 0xb8, 0x89, 0x21, 0x2b, +0x6c, 0x35, 0xfb, 0xe1, 0x6f, 0xc5, 0x28, 0x18, +0xa9, 0xa2, 0x09, 0x5f, 0x48, 0x0e, 0xef, 0x28, +0x02, 0x5e, 0x66, 0x89, 0xfd, 0x35, 0x75, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0xf9, 0xcd, 0x6e, 0x5b, 0xc6, 0x2d, 0xfb, +0x8c, 0x77, 0xb3, 0x07, 0x43, 0x8e, 0x93, 0x19, +0xc4, 0xd7, 0xc1, 0xe9, 0x82, 0xc0, 0x3b, 0x0a, +0x1b, 0x58, 0xaa, 0x4a, 0xe4, 0x58, 0x44, 0x0e, +0xed, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0x5c, 0x74, 0x90, 0x24, 0x9d, +0xa3, 0x2b, 0x32, 0x6d, 0x48, 0x63, 0x2e, 0x8b, +0xfc, 0x01, 0x99, 0x4b, 0x70, 0x80, 0x42, 0x63, +0xa4, 0x46, 0x96, 0xae, 0x48, 0x51, 0x9a, 0xe1, +0x9c, 0x8d, 0x9d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0x78, 0xf3, 0xa4, +0x97, 0x81, 0x42, 0xf1, 0xe8, 0x00, 0xf4, 0x39, +0x07, 0xcd, 0x79, 0x36, 0xb6, 0x32, 0x62, 0x40, +0x72, 0xc2, 0xb3, 0x08, 0xa4, 0x16, 0x24, 0x2d, +0x8e, 0x9a, 0xfd, 0x77, 0x25, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0x03, +0x11, 0x8d, 0x22, 0xe6, 0x32, 0x90, 0x55, 0x0c, +0x86, 0xa0, 0xd1, 0x0d, 0xa0, 0xd9, 0x9d, 0x9f, +0x67, 0x79, 0xbf, 0x4c, 0xc5, 0x6e, 0x01, 0xd3, +0x41, 0x2b, 0xeb, 0xa1, 0xbf, 0xce, 0x8f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0x60, 0x5c, 0x13, 0x35, 0xc8, 0xb0, 0x40, +0x1e, 0x53, 0xa9, 0x7b, 0x4b, 0xee, 0x9a, 0xf0, +0x99, 0x55, 0xf3, 0x39, 0xd2, 0x0a, 0x90, 0xeb, +0xa8, 0x5f, 0x93, 0xb7, 0x09, 0x30, 0x69, 0x69, +0x3c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0xb4, 0xdc, 0xbd, 0x2f, 0xc3, +0x52, 0x2f, 0x2e, 0x3d, 0x54, 0x91, 0xc1, 0xe4, +0x70, 0x1c, 0x9f, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x05, 0x64, 0xe7, 0xfb, +0x1e, 0x5e, 0xb8, 0x1d, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0x76, 0xfc, 0xfa, +0xf7, 0x8a, 0xb7, 0xef, 0xff, 0xc4, 0x09, 0x70, +0x4b, 0x0b, 0x63, 0xca, 0x3f, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb9, 0x37, +0x24, 0xff, 0xd8, 0x9c, 0x50, 0x86, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0x76, +0x03, 0xf2, 0x36, 0xe0, 0xee, 0x4d, 0x05, 0xfb, +0x59, 0x99, 0xb4, 0xec, 0x42, 0xd9, 0x75, 0xdf, +0xb8, 0x18, 0x15, 0xc7, 0xf4, 0x83, 0x9c, 0x8a, +0x94, 0x01, 0xf8, 0xcb, 0xde, 0x14, 0xf5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0xb5, 0x11, 0x89, 0xea, 0x98, 0xb0, 0x55, +0x15, 0x8f, 0x5b, 0xa1, 0x49, 0x74, 0x98, 0x80, +0x8d, 0x3d, 0x05, 0x94, 0xfe, 0x30, 0xab, 0x3e, +0xce, 0x2b, 0x63, 0xd2, 0x27, 0x66, 0xb8, 0x68, +0x20, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x15, 0x91, 0x0a, 0xc9, 0xeb, +0x7a, 0x94, 0x7b, 0x4b, 0x92, 0xcb, 0x7b, 0x50, +0xd9, 0x2f, 0x17, 0x9e, 0xa7, 0x7d, 0x08, 0xbb, +0xf5, 0xd4, 0x76, 0x70, 0x20, 0x98, 0xcc, 0x58, +0x6d, 0x1c, 0xd5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0xab, 0xa4, 0x69, +0xe8, 0x25, 0x97, 0xdc, 0x06, 0x81, 0x80, 0x2d, +0xae, 0x85, 0xcb, 0x2d, 0x8a, 0xe7, 0x3c, 0x1f, +0x3c, 0x68, 0xa9, 0x88, 0x6b, 0xdf, 0xa9, 0x93, +0xe7, 0x9a, 0x23, 0xe4, 0xe8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0xc5, +0xc2, 0xd6, 0x50, 0xa1, 0x0f, 0xe6, 0xdc, 0x1a, +0x06, 0xa6, 0xcd, 0x82, 0xcd, 0xf6, 0xc6, 0x92, +0x0c, 0x6f, 0x85, 0x7a, 0xb9, 0x27, 0xed, 0x30, +0x1f, 0xa7, 0x46, 0xa1, 0x75, 0xfb, 0xc2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x2f, 0x39, 0x56, 0xcd, 0xdd, 0x57, 0xdb, +0xb0, 0xc7, 0x8a, 0xb4, 0xd0, 0x70, 0x03, 0xb6, +0xfa, 0x4b, 0xe5, 0x68, 0xc2, 0xec, 0xd7, 0x61, +0x40, 0xca, 0x76, 0x19, 0x56, 0x3b, 0x8d, 0xaa, +0x16, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x69, 0x08, 0xf1, 0x17, 0x4f, +0x05, 0xe3, 0x84, 0x01, 0xdd, 0xb0, 0xae, 0x44, +0x2c, 0x19, 0x98, 0x10, 0x2c, 0x69, 0x29, 0xbb, +0xf2, 0xd4, 0x5e, 0x8d, 0x3d, 0x0b, 0x39, 0x59, +0x5c, 0x8c, 0x60, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0x16, 0xef, 0x9c, +0x88, 0xf8, 0x46, 0x82, 0x13, 0xf8, 0x82, 0x92, +0x92, 0x3b, 0xb0, 0xa6, 0x71, 0x52, 0xc6, 0xd4, +0xc3, 0xb0, 0x88, 0x10, 0xf5, 0xfd, 0x3e, 0xbc, +0xf4, 0x3d, 0xf6, 0x1f, 0x52, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0x7a, +0x96, 0xce, 0x63, 0x7f, 0x24, 0xbd, 0x5f, 0x84, +0x95, 0xfc, 0x71, 0xf9, 0x89, 0xca, 0xcb, 0x1d, +0x43, 0x2b, 0x04, 0xbc, 0x36, 0xa4, 0x42, 0x1d, +0x20, 0xe4, 0x11, 0xd6, 0xf4, 0xd1, 0xff, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0x9b, 0x41, 0x70, 0x79, 0xa5, 0x8a, 0xd8, +0x9f, 0x09, 0x20, 0x46, 0x9c, 0xb5, 0x7c, 0x91, +0xcb, 0x4b, 0x25, 0x27, 0x9a, 0x01, 0xfa, 0xd4, +0x50, 0xde, 0x23, 0x73, 0x86, 0x8f, 0xc2, 0x51, +0x17, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0x17, 0x23, 0x95, 0xce, 0x0d, +0x0e, 0xb6, 0xfe, 0xba, 0xbf, 0xb8, 0xf6, 0xc6, +0x9c, 0x17, 0x3c, 0xfc, 0x7f, 0x57, 0x7d, 0xed, +0xc0, 0xa4, 0x2d, 0x7d, 0x40, 0x0c, 0x13, 0x15, +0xfa, 0x1c, 0x5a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x32, 0xcc, 0x12, +0x12, 0x59, 0xeb, 0x43, 0xf6, 0xc8, 0x30, 0x84, +0x80, 0x60, 0x36, 0x87, 0x35, 0x3d, 0xfe, 0x32, +0x01, 0x4f, 0xa6, 0x47, 0x55, 0x5c, 0x51, 0x4f, +0x6f, 0x2f, 0xb5, 0xff, 0x1a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x1d, +0x36, 0xbd, 0x67, 0xb3, 0x4a, 0x3a, 0xe4, 0xfd, +0x08, 0x15, 0x07, 0x29, 0xfd, 0x40, 0x87, 0xdf, +0x2f, 0xf8, 0x45, 0x39, 0x61, 0xe2, 0xd7, 0x86, +0x77, 0x7f, 0xcb, 0x5e, 0x1b, 0xd2, 0xaa, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0x0a, 0x97, 0x07, 0x3a, 0x72, 0x94, 0x8d, +0xbb, 0xbc, 0x21, 0x4a, 0x81, 0x1e, 0x8e, 0x71, +0x1e, 0x89, 0x32, 0x32, 0xcc, 0xbb, 0x87, 0xe4, +0x6e, 0x54, 0x57, 0x4e, 0x86, 0x69, 0x52, 0xa9, +0x28, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0x36, 0xaf, 0xbe, 0x38, 0xca, +0x6c, 0xd3, 0xcb, 0x34, 0xea, 0x9f, 0x58, 0xe6, +0xda, 0xa7, 0x92, 0x0d, 0xd5, 0x96, 0xb6, 0x58, +0x87, 0xdd, 0x38, 0x31, 0xdd, 0xbe, 0x4a, 0x8d, +0xcc, 0xd0, 0x0c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0xae, 0xc4, 0xbf, +0xcf, 0xe3, 0x88, 0x91, 0x51, 0x40, 0x20, 0x4b, +0x6d, 0xc8, 0x42, 0x39, 0x6b, 0xad, 0xbd, 0x3b, +0x74, 0x6b, 0xcc, 0x07, 0x0a, 0xbd, 0x09, 0x6e, +0x2b, 0xe8, 0x8e, 0x73, 0x4c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0xd4, +0x94, 0x32, 0xf6, 0x94, 0x47, 0x9c, 0x5a, 0x57, +0x51, 0x5a, 0x8d, 0xef, 0x67, 0x70, 0x35, 0xef, +0x26, 0xf8, 0x5a, 0x8f, 0xd7, 0x5d, 0x26, 0xdd, +0x92, 0xc1, 0x97, 0xeb, 0x40, 0xe6, 0x57, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0xfd, 0x05, 0x65, 0x4b, 0x18, 0xed, 0x11, +0xef, 0x3c, 0x8f, 0xea, 0xac, 0xdf, 0xd0, 0xee, +0xc7, 0x9e, 0x40, 0x1f, 0xa2, 0xcf, 0x4e, 0x9f, +0x13, 0x1f, 0x16, 0x92, 0xc1, 0x0d, 0xff, 0x40, +0x7a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0x4f, 0x35, 0x7a, 0x65, 0xe8, +0xf8, 0x94, 0xca, 0x2e, 0xbd, 0xaf, 0x90, 0x8a, +0x42, 0xa2, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x46, 0xed, 0xbd, 0x26, +0x27, 0xa1, 0x09, 0x41, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0x5a, 0x4a, 0xb3, +0x5a, 0x6c, 0xfb, 0x79, 0x86, 0x16, 0xaf, 0x8a, +0x54, 0xc7, 0x89, 0xb1, 0xd5, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x96, 0xe5, +0x91, 0x1e, 0xdb, 0x21, 0x99, 0x54, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0xb8, +0xcb, 0x31, 0x54, 0x61, 0xd2, 0xbe, 0x5a, 0x22, +0x18, 0x9a, 0x58, 0x59, 0x56, 0x92, 0x89, 0x77, +0x94, 0x4f, 0xac, 0xb0, 0x2a, 0x5a, 0xad, 0x76, +0xe6, 0x4f, 0x69, 0xac, 0x04, 0x44, 0xfc, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0xfb, 0xc1, 0x97, 0x4c, 0x47, 0xe9, 0x0c, +0xd2, 0x45, 0xea, 0xa0, 0x4b, 0xfb, 0xe2, 0x97, +0x05, 0xc6, 0xa5, 0xc1, 0xde, 0x5b, 0x36, 0xfb, +0x8a, 0x4d, 0xbe, 0xae, 0xae, 0x8e, 0x29, 0xd8, +0x46, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0x53, 0xe8, 0xa8, 0xc8, 0x58, +0xd7, 0x6a, 0x31, 0xe6, 0xb8, 0x98, 0x11, 0x42, +0x3a, 0xb4, 0x26, 0x4e, 0x3a, 0x7e, 0xab, 0x73, +0x72, 0xc5, 0xba, 0x35, 0x57, 0x50, 0xa4, 0x43, +0x76, 0x0d, 0xfa, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x88, 0x14, 0x18, +0xa3, 0x28, 0x3e, 0xa3, 0x63, 0x21, 0x97, 0xe0, +0x54, 0x19, 0x09, 0xb4, 0x1b, 0x00, 0x32, 0x4f, +0x2c, 0xc2, 0xd1, 0xe3, 0xb4, 0x80, 0xe7, 0x29, +0x74, 0x40, 0xef, 0x0f, 0xe6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0xd7, +0x06, 0xaf, 0x8c, 0x6f, 0x4c, 0x5e, 0x6d, 0xa0, +0xc7, 0x8c, 0xe5, 0x02, 0x58, 0x7f, 0x2b, 0xd5, +0xc0, 0x8a, 0xd2, 0x22, 0x11, 0xb6, 0x60, 0x16, +0x5d, 0x69, 0xcd, 0x9d, 0x7c, 0x64, 0xf6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0xa7, 0x3d, 0x36, 0x21, 0x40, 0xd0, 0xae, +0xfa, 0x0b, 0x63, 0x5b, 0x63, 0xf5, 0xe4, 0x85, +0x18, 0x63, 0x3c, 0xb7, 0x92, 0x3b, 0xe6, 0x2c, +0xa1, 0x55, 0x4a, 0xf3, 0x57, 0x93, 0x20, 0x68, +0xdd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0x01, 0x7e, 0x62, 0x55, 0xc8, +0xa0, 0x63, 0xbe, 0x42, 0x36, 0x71, 0x78, 0xd7, +0x65, 0x45, 0x21, 0xaa, 0x37, 0x65, 0xe3, 0x5f, +0x51, 0xa1, 0x10, 0x9d, 0x1c, 0x95, 0x39, 0x82, +0x1d, 0xd1, 0x92, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x5c, 0x0a, 0xbd, +0x85, 0x78, 0xfa, 0x0f, 0xa2, 0xb8, 0x44, 0xcf, +0xf6, 0xfa, 0x12, 0xa7, 0x60, 0x30, 0x9e, 0x96, +0x06, 0xdf, 0x28, 0x32, 0x80, 0xba, 0xbf, 0x24, +0xd8, 0x5d, 0x5e, 0xdc, 0xcc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x16, +0x49, 0x7d, 0xdd, 0x6a, 0xb2, 0xba, 0x58, 0x95, +0x23, 0xb8, 0xd6, 0x0c, 0x7b, 0x66, 0x2c, 0x18, +0xe8, 0x9c, 0x47, 0x7d, 0x72, 0xe4, 0x76, 0xf5, +0x7b, 0x3b, 0xb1, 0xaa, 0x79, 0xff, 0xd1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0x03, 0xfa, 0xce, 0x6f, 0xe4, 0x9f, 0xf6, +0x81, 0x13, 0x38, 0xc6, 0xbd, 0x44, 0x90, 0x5c, +0x02, 0x0b, 0x40, 0x2a, 0xb9, 0x4f, 0x82, 0x72, +0x38, 0x4c, 0xc2, 0x70, 0xf4, 0xb4, 0xd2, 0xbb, +0x97, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0xbf, 0x6a, 0x16, 0xce, 0xcd, +0x2d, 0xf1, 0x91, 0x39, 0x9f, 0x98, 0xa9, 0xa8, +0x66, 0xbe, 0x91, 0xdc, 0x29, 0x1a, 0x11, 0xb0, +0x7c, 0x35, 0xc4, 0x86, 0x85, 0xbb, 0xa2, 0x26, +0x1a, 0x4a, 0xd8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0x86, 0xf8, 0x54, +0x5c, 0x92, 0xe3, 0x53, 0xd7, 0x15, 0x04, 0xf2, +0x69, 0xc1, 0x4a, 0x49, 0x24, 0xac, 0x5b, 0xd0, +0x54, 0x18, 0xe3, 0x75, 0xdb, 0xf1, 0x2a, 0x57, +0xd2, 0xd2, 0x3e, 0x22, 0x2c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0xdc, +0x71, 0x2e, 0x93, 0x8c, 0x4a, 0x4c, 0x41, 0x55, +0xb0, 0xde, 0xb8, 0x99, 0xff, 0x66, 0x40, 0xa4, +0xd0, 0xed, 0x0f, 0x66, 0xe3, 0x22, 0x33, 0xd5, +0x61, 0xf1, 0x20, 0x17, 0xd7, 0x3a, 0xe6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0x59, 0xac, 0x63, 0x64, 0x14, 0xc5, 0xa9, +0x5f, 0xbf, 0x1c, 0xb1, 0x5d, 0xda, 0xc5, 0x12, +0x81, 0x4d, 0x3a, 0x91, 0xdf, 0x84, 0xd5, 0xb5, +0x55, 0x1c, 0xda, 0x8d, 0x40, 0x72, 0x8e, 0x06, +0x57, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0x6d, 0xe7, 0x77, 0x36, 0xc2, +0xb2, 0x4c, 0x93, 0x83, 0x95, 0xf5, 0x15, 0xce, +0x6e, 0xc1, 0x61, 0x92, 0xdc, 0x38, 0xfa, 0x8a, +0xa6, 0x95, 0x5e, 0xac, 0x08, 0xef, 0x4e, 0x37, +0xa2, 0x35, 0xfa, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0x51, 0x68, 0x2b, +0xe1, 0x08, 0x5a, 0x27, 0x7d, 0x66, 0x6e, 0x2d, +0x08, 0xbb, 0xd1, 0x0e, 0xd2, 0x94, 0x6a, 0x38, +0xe2, 0x8e, 0x5e, 0xff, 0x10, 0xf0, 0xa1, 0xd2, +0x57, 0x5d, 0x0e, 0xd2, 0x62, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x3d, +0xcf, 0x43, 0x89, 0xeb, 0xbd, 0x75, 0x6c, 0x60, +0xa6, 0x15, 0x97, 0xf9, 0xf6, 0xad, 0x28, 0x83, +0x55, 0x8b, 0x51, 0xb5, 0x04, 0x8d, 0x2c, 0x22, +0xa4, 0x8f, 0x4b, 0x90, 0xaf, 0xf5, 0xa6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0xbd, 0xd0, 0x3a, 0xb7, 0xb1, 0x05, 0xad, +0xb2, 0x44, 0xcb, 0x6d, 0xa3, 0xc5, 0xa7, 0x77, +0x0e, 0x2d, 0x55, 0xf1, 0x51, 0xd6, 0x03, 0x5a, +0x9c, 0x47, 0xa6, 0xab, 0x40, 0x16, 0x1f, 0xaf, +0x82, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0x43, 0x00, 0xcd, 0xfd, 0x56, +0x58, 0xb6, 0x88, 0x98, 0x7a, 0x06, 0xd4, 0x13, +0x2c, 0x15, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xf5, 0xfa, 0xde, 0x7e, +0x4c, 0xf6, 0x58, 0xba, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0xd1, 0x64, 0x25, +0x14, 0xff, 0x33, 0xf3, 0x36, 0x01, 0x9e, 0x0c, +0x07, 0x63, 0x54, 0x61, 0x04, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0xd5, +0xab, 0xb0, 0x52, 0xbd, 0xef, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0x8a, +0x47, 0x4b, 0x8d, 0x99, 0x4c, 0x83, 0x31, 0xdb, +0xc9, 0x63, 0x1c, 0x0b, 0x38, 0x96, 0xb7, 0xcf, +0xd4, 0x10, 0x5b, 0x44, 0x50, 0x37, 0xca, 0x46, +0x26, 0xd7, 0x2b, 0xe2, 0x26, 0xf8, 0x87, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0x09, 0x0a, 0x75, 0xf0, 0x03, 0xeb, 0x0c, +0x6c, 0x8a, 0xdc, 0xd1, 0xf3, 0x85, 0x82, 0xd5, +0x7e, 0xad, 0x8f, 0x29, 0xcf, 0x83, 0x5f, 0xb6, +0x6a, 0x1b, 0x87, 0x27, 0xc7, 0xd8, 0x0c, 0xee, +0x10, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x06, 0x95, 0x77, 0xb6, 0x28, +0xdf, 0x3e, 0x81, 0xe4, 0x6f, 0xf4, 0x11, 0x85, +0x28, 0x58, 0x6e, 0x8f, 0x9b, 0x20, 0x55, 0xb7, +0xda, 0x66, 0x55, 0x5a, 0xb8, 0x37, 0x22, 0x9f, +0xb5, 0x20, 0xa7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0xf9, 0x5d, 0x73, +0x49, 0xb7, 0x8a, 0x82, 0xa8, 0x24, 0x3d, 0xe8, +0x04, 0xd9, 0x04, 0xce, 0xbc, 0x7f, 0xc7, 0xbb, +0xa8, 0x5e, 0xe8, 0xcb, 0x1f, 0x5f, 0x11, 0x17, +0x48, 0x6b, 0x02, 0xed, 0x8e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0xe0, +0xfe, 0x6b, 0xf1, 0xe7, 0xd7, 0xdd, 0xfe, 0xad, +0x46, 0x8f, 0xb2, 0xc8, 0x91, 0x2e, 0xeb, 0xec, +0x23, 0xec, 0xcc, 0x66, 0x13, 0xcf, 0xaf, 0xf1, +0xca, 0xa0, 0xd3, 0xa1, 0x38, 0xc9, 0xbb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0x8e, 0x24, 0x11, 0x37, 0x9b, 0xac, 0x7f, +0x97, 0x8a, 0x83, 0xa1, 0x6a, 0xab, 0xf5, 0x35, +0x60, 0x64, 0x60, 0xd9, 0xc2, 0xf6, 0x03, 0x85, +0x10, 0xaf, 0x67, 0x92, 0x4e, 0x6a, 0xb0, 0x49, +0xc9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0x98, 0x0f, 0x7d, 0xc4, 0x36, +0x72, 0x40, 0x61, 0x66, 0xd5, 0x8c, 0x6f, 0xbd, +0xa7, 0x53, 0x22, 0x78, 0x8f, 0x44, 0x46, 0x12, +0x64, 0x8b, 0x86, 0x2e, 0x84, 0xe0, 0x09, 0x1d, +0xe9, 0xc1, 0x93, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0x8f, 0xaf, 0x1c, +0xc3, 0xff, 0x79, 0x55, 0x63, 0x7b, 0x8e, 0x61, +0x6b, 0x98, 0x4c, 0x13, 0xcb, 0xd5, 0x5e, 0xea, +0xd3, 0xc8, 0xbb, 0x62, 0x84, 0x33, 0x9b, 0x18, +0x64, 0xea, 0x99, 0x6f, 0xd9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0x26, +0xd5, 0xef, 0x98, 0x59, 0x85, 0x00, 0x8a, 0x2b, +0xd9, 0x9a, 0x59, 0x0d, 0xde, 0xce, 0xcd, 0x46, +0xa3, 0x3b, 0xd1, 0x75, 0x16, 0x8f, 0x7b, 0x91, +0xc5, 0xb9, 0x25, 0xa3, 0x3f, 0x61, 0xc5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0x5f, 0x58, 0xe5, 0x4c, 0xd7, 0x51, 0xf1, +0x17, 0x25, 0xf4, 0xb2, 0x1a, 0x7f, 0x67, 0xd4, +0xdb, 0x93, 0xef, 0x2c, 0xe1, 0xa5, 0x22, 0x0a, +0x16, 0xca, 0x00, 0x34, 0x45, 0x28, 0x92, 0xed, +0xce, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0x10, 0x19, 0xae, 0x3f, 0xe1, +0xf6, 0x29, 0x0c, 0x89, 0x0c, 0xbb, 0x0f, 0x1e, +0x88, 0x71, 0x16, 0xcb, 0x40, 0x18, 0xaa, 0xbc, +0x5d, 0x5c, 0xf5, 0x0c, 0x6a, 0x9c, 0xa7, 0x57, +0x61, 0x70, 0x7a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0x71, 0x9e, 0x7c, +0x29, 0x4c, 0x29, 0x5f, 0xd8, 0xd9, 0x49, 0xda, +0xae, 0x03, 0x5d, 0x65, 0xb0, 0x57, 0xcd, 0xae, +0x5c, 0x87, 0x11, 0x4a, 0x33, 0x2d, 0x79, 0xbc, +0x68, 0x8e, 0x26, 0x12, 0x9a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x3c, +0x9e, 0x1a, 0x5d, 0x2f, 0x92, 0x19, 0x39, 0xec, +0xdd, 0xe4, 0x73, 0xf6, 0xc9, 0x47, 0xfb, 0xab, +0xf1, 0x8b, 0xb6, 0x0d, 0x7f, 0xfd, 0xb1, 0x4f, +0xbd, 0xc5, 0x71, 0xc6, 0x39, 0x59, 0xcd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0x93, 0x24, 0x58, 0xff, 0xb9, 0xa4, 0xa7, +0x18, 0x61, 0xc2, 0x4a, 0xeb, 0xf8, 0x0d, 0x91, +0xd5, 0x63, 0xb6, 0x67, 0xd6, 0xe5, 0xc4, 0xe9, +0x94, 0xdd, 0x25, 0xb7, 0x9b, 0x19, 0xbd, 0x1e, +0x47, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0x60, 0xd8, 0x2e, 0x90, 0x7c, +0x19, 0x0d, 0xce, 0xcd, 0x81, 0x53, 0x45, 0x24, +0x0c, 0xe9, 0xa2, 0x89, 0xc9, 0x35, 0x80, 0x84, +0x33, 0xaa, 0xbc, 0x9a, 0x0a, 0x8a, 0x11, 0x21, +0xaa, 0xf1, 0x91, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0xb8, 0x80, 0x0c, +0x1f, 0x27, 0x9e, 0x04, 0x17, 0x44, 0x97, 0xd7, +0x63, 0x45, 0xae, 0x93, 0xed, 0xed, 0x64, 0x6d, +0xcb, 0x0f, 0xbf, 0x00, 0xc7, 0x6f, 0xd5, 0xfa, +0x6e, 0xa5, 0x38, 0xf5, 0xbb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0x91, +0x61, 0xe7, 0xd1, 0x0d, 0x50, 0x38, 0x60, 0xa9, +0x94, 0xb5, 0x5e, 0xf0, 0x05, 0xf9, 0x3e, 0xe9, +0x23, 0xb1, 0x93, 0xb6, 0xdd, 0x1a, 0xf0, 0xc9, +0x73, 0xaf, 0x26, 0x48, 0x31, 0x35, 0xa4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0x3e, 0xe4, 0x4c, 0x13, 0x98, 0x4f, 0x76, +0x55, 0x76, 0xa8, 0xd5, 0x06, 0x0b, 0x26, 0x4d, +0xcd, 0xd8, 0x56, 0x79, 0x7a, 0x9a, 0x01, 0xa1, +0x6f, 0xd5, 0xd2, 0xa1, 0xf1, 0x2f, 0x76, 0x93, +0x5b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x66, 0xd3, 0x4c, 0x97, 0x46, +0x01, 0xba, 0x75, 0x01, 0xdf, 0x2c, 0x16, 0x82, +0x8b, 0x70, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xdf, 0x66, 0xa8, 0x98, +0x76, 0x87, 0xe8, 0x1f, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0x19, 0x25, 0x4f, +0x55, 0x24, 0xe9, 0x19, 0x5c, 0x9f, 0x3b, 0xb6, +0xf6, 0xd9, 0xa7, 0xa4, 0x5d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x02, +0x6f, 0x38, 0x1a, 0x64, 0xcd, 0xc4, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0xdb, +0x57, 0x3a, 0xfe, 0xc1, 0xcb, 0x22, 0xe6, 0x43, +0x8e, 0xb2, 0x3f, 0x1d, 0xf9, 0xdb, 0xfc, 0x95, +0x21, 0xcf, 0x74, 0x35, 0xb9, 0x82, 0x7c, 0x05, +0x45, 0xc8, 0x5d, 0xce, 0x16, 0x28, 0xf3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0x55, 0x3e, 0x09, 0x9a, 0xae, 0xb7, 0xc4, +0xac, 0xb1, 0x74, 0x15, 0x51, 0x7a, 0x36, 0x9c, +0xaf, 0x64, 0x4b, 0x62, 0xd8, 0x95, 0x45, 0xef, +0x92, 0x9f, 0x25, 0xec, 0xfe, 0xcc, 0x5a, 0xef, +0xa2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0x2a, 0xf8, 0x17, 0x57, 0xeb, +0xf8, 0xb5, 0x4e, 0x58, 0x59, 0x80, 0xd3, 0x15, +0x09, 0x47, 0x06, 0x77, 0x03, 0x20, 0x69, 0xc5, +0xd8, 0xe5, 0xcc, 0x70, 0x8c, 0x99, 0x3d, 0x91, +0xb3, 0x06, 0xd2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0x72, 0xb8, 0xf2, +0x36, 0xdb, 0x59, 0x60, 0x6e, 0x88, 0x62, 0x24, +0x06, 0x06, 0x8f, 0x88, 0x9f, 0xf4, 0x2b, 0xa3, +0xbb, 0xa1, 0x80, 0xaf, 0x25, 0xb9, 0xd5, 0xc9, +0x86, 0xc4, 0xd6, 0x11, 0xe4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0x11, +0x96, 0x8e, 0x05, 0x02, 0xad, 0xd2, 0x87, 0xec, +0x4e, 0xb0, 0xdf, 0x0e, 0x64, 0xb0, 0x78, 0x0b, +0xc9, 0x3e, 0x5d, 0xf7, 0xaa, 0x61, 0x1c, 0xf7, +0xf6, 0xaf, 0x35, 0x57, 0x74, 0x21, 0x6d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0x9b, 0xe4, 0x55, 0xb0, 0xeb, 0xb8, 0x94, +0x11, 0xb1, 0xd3, 0xa4, 0x19, 0x7d, 0xa3, 0x13, +0x7d, 0x28, 0x89, 0xaf, 0xa1, 0x9d, 0xb1, 0xcb, +0x4f, 0x7e, 0x6e, 0x98, 0x94, 0x87, 0xc3, 0xb3, +0xc4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0x40, 0x63, 0xba, 0x05, 0x6d, +0x4a, 0xb7, 0x39, 0x76, 0x17, 0x53, 0xd0, 0x74, +0x5d, 0x60, 0x12, 0x2a, 0x84, 0x28, 0xc5, 0x5b, +0x16, 0xf2, 0xda, 0x4c, 0xcd, 0x69, 0xb9, 0xe9, +0xf6, 0x3f, 0xe7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0x35, 0x7e, 0x79, +0xf5, 0xe1, 0x6e, 0x0d, 0xb0, 0x00, 0x57, 0x51, +0x1c, 0x3e, 0x67, 0x46, 0xd1, 0xc4, 0xed, 0xd8, +0x90, 0x94, 0xbb, 0x27, 0x0e, 0xc0, 0xe2, 0x3e, +0x0f, 0x54, 0xe2, 0x36, 0x7b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0x98, +0x69, 0xc9, 0x71, 0x3b, 0x52, 0xc2, 0xbc, 0x4d, +0xf3, 0x9b, 0x6a, 0x98, 0xf1, 0xcd, 0x7c, 0x84, +0xf6, 0xa0, 0xc2, 0xa6, 0xa9, 0xfa, 0x28, 0x92, +0x67, 0x05, 0x69, 0xd6, 0xc6, 0x92, 0x86, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0x62, 0x7a, 0x3f, 0xb0, 0x88, 0xee, 0x0d, +0xa8, 0x6c, 0x3b, 0xcb, 0xa2, 0xbb, 0x8f, 0xfe, +0xb3, 0xae, 0x43, 0x22, 0x2b, 0x51, 0x30, 0xd1, +0xfd, 0x70, 0xbe, 0x1d, 0xe1, 0x9b, 0x71, 0x21, +0x1f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0xa3, 0xa8, 0x8d, 0x78, 0x18, +0x13, 0xb6, 0xce, 0x95, 0x43, 0xcb, 0xc5, 0xe4, +0xed, 0x17, 0xa4, 0xf1, 0x7e, 0xa6, 0x56, 0x9a, +0x05, 0x20, 0xb4, 0x6f, 0xfc, 0x2e, 0xd6, 0x07, +0xed, 0x2c, 0xe0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0xd4, 0x77, 0x5d, +0x94, 0x32, 0xe9, 0x39, 0xfd, 0xe1, 0xf0, 0x30, +0x9c, 0xe2, 0x7b, 0x29, 0x2a, 0x30, 0x8d, 0xec, +0x5f, 0x11, 0x4e, 0x38, 0x68, 0x96, 0x0f, 0xed, +0x69, 0x6a, 0xae, 0xd3, 0xe2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0xae, +0x37, 0x21, 0xbc, 0x26, 0xf9, 0xc6, 0xbd, 0x4e, +0xcc, 0x36, 0xd8, 0x7e, 0xdf, 0x61, 0x89, 0xb0, +0xe8, 0x5c, 0xeb, 0x14, 0xb0, 0xe2, 0x35, 0x13, +0x6a, 0x49, 0xcc, 0xd5, 0xf7, 0x99, 0xf6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x8d, 0x28, 0x28, 0x37, 0x38, 0xe9, 0x43, +0xd4, 0x40, 0x9f, 0x40, 0x06, 0xf8, 0xde, 0xf9, +0xa1, 0x19, 0x6d, 0xb5, 0xce, 0xc6, 0xa7, 0xf5, +0xe0, 0x5a, 0xa3, 0x15, 0x23, 0x84, 0xfe, 0x5a, +0x94, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0xa6, 0x61, 0x1a, 0xc6, 0xc2, +0x96, 0x05, 0x87, 0x80, 0x96, 0xd4, 0xfe, 0x57, +0x72, 0x81, 0x17, 0xb9, 0xd4, 0xd8, 0x6d, 0x7d, +0x47, 0x5a, 0x74, 0xba, 0x05, 0x7c, 0x1b, 0xfd, +0x07, 0xbb, 0x75, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0xa1, 0x33, 0x30, +0xf1, 0xd2, 0x85, 0x15, 0x72, 0xbc, 0x3d, 0xd9, +0x6f, 0x0e, 0xff, 0x9b, 0xb8, 0x01, 0x27, 0x9d, +0xea, 0x1c, 0x48, 0x7a, 0x93, 0xa9, 0x50, 0x2e, +0xe0, 0xc9, 0x69, 0x31, 0x03, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0x4a, +0x21, 0x10, 0x8d, 0x8f, 0x2b, 0x78, 0xe0, 0x69, +0x7d, 0xc5, 0x35, 0xe3, 0x4d, 0x14, 0x00, 0x8d, +0xf5, 0x52, 0xf0, 0x27, 0x35, 0xbe, 0xc5, 0xb0, +0xd1, 0xd7, 0x81, 0x38, 0x55, 0x9e, 0x32, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0xb3, 0xa9, 0xbf, 0x70, 0x7e, 0x6a, 0xdf, +0xc4, 0x66, 0xc0, 0xc6, 0x0b, 0x6e, 0x4f, 0xe4, +0x7c, 0xe7, 0xa5, 0x4c, 0x4e, 0xe7, 0x79, 0x43, +0x5a, 0x3b, 0xaf, 0xbe, 0x40, 0x0e, 0x0d, 0xb6, +0x5f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0x09, 0x01, 0xdc, 0xef, 0x55, +0x26, 0x58, 0xd9, 0x76, 0x43, 0xd0, 0x3e, 0x19, +0x54, 0x6e, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xaf, 0x28, 0xea, 0x6b, +0xc0, 0xa7, 0xfe, 0xf5, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x15, 0x14, 0xbd, +0x9e, 0xfc, 0xdd, 0xf6, 0xa5, 0xb4, 0x95, 0x44, +0xc7, 0x76, 0x66, 0x59, 0x75, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x9a, +0x7a, 0x0f, 0x91, 0xf5, 0xbe, 0x4e, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0x24, +0xf9, 0xec, 0xcb, 0xc1, 0x20, 0x97, 0x0b, 0x9d, +0x27, 0x39, 0xf8, 0x03, 0x20, 0xa2, 0xd5, 0x8d, +0x70, 0x0e, 0x7a, 0xc3, 0x4c, 0xcc, 0x56, 0xa6, +0x98, 0x73, 0x17, 0x28, 0x34, 0xc5, 0x14, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0x35, 0x91, 0xf6, 0x24, 0x00, 0x54, 0x66, +0x41, 0x90, 0xb1, 0x56, 0x5b, 0xf5, 0x7d, 0x0c, +0xaa, 0x6f, 0x7c, 0x2c, 0x8e, 0xe6, 0x22, 0xbd, +0x0e, 0x63, 0x28, 0x44, 0x6c, 0xc6, 0xbb, 0xd0, +0x3e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0xd4, 0x0e, 0xe6, 0xf6, 0xba, +0xbf, 0x97, 0xfd, 0x28, 0xa4, 0x5b, 0xd3, 0xaa, +0xda, 0x09, 0x28, 0x7e, 0xba, 0x04, 0x35, 0x92, +0x90, 0x6f, 0x97, 0x55, 0xad, 0xdc, 0x54, 0x3f, +0x45, 0x79, 0xdc, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0x23, 0xd0, 0xa8, +0x5f, 0xfc, 0xe4, 0xdf, 0x3d, 0xe5, 0x1e, 0xf3, +0x96, 0x31, 0x93, 0x91, 0xd0, 0x0f, 0x76, 0x07, +0x08, 0xf4, 0x82, 0x06, 0xe9, 0xbe, 0x6e, 0xd8, +0xa3, 0xd2, 0x6a, 0xf7, 0x27, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0x2a, +0xb5, 0xac, 0x54, 0x92, 0x48, 0xd5, 0x86, 0x2f, +0xf2, 0xde, 0x18, 0x88, 0xec, 0xb7, 0xe8, 0x8b, +0x36, 0x7c, 0x9b, 0x59, 0x55, 0x52, 0x72, 0xd5, +0x99, 0x57, 0x4f, 0xf0, 0x27, 0x25, 0xa4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0x77, 0x7d, 0xbe, 0x61, 0x94, 0x1f, 0x57, +0x56, 0x8a, 0xca, 0x28, 0x9c, 0x6e, 0xe8, 0x2f, +0x3c, 0xce, 0xc2, 0x44, 0x38, 0x35, 0x9c, 0xbb, +0xbe, 0xfe, 0xd4, 0x1a, 0x8c, 0x8e, 0xa1, 0xf4, +0x64, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0x3e, 0xba, 0xe2, 0x54, 0xd0, +0xcf, 0x32, 0x75, 0x4e, 0xa9, 0xd4, 0xca, 0x82, +0x2d, 0x88, 0x51, 0x68, 0xa2, 0x5e, 0xbe, 0xf1, +0x64, 0x35, 0xf9, 0x61, 0x08, 0x05, 0x30, 0x3e, +0xb8, 0xde, 0xed, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0xa6, 0x85, 0x68, +0xeb, 0x11, 0x35, 0x23, 0x9b, 0xd9, 0xcd, 0xb2, +0x52, 0xea, 0x67, 0x28, 0xaa, 0x19, 0x87, 0x73, +0x6c, 0xe2, 0x83, 0x35, 0xc0, 0xeb, 0xde, 0xc3, +0xcc, 0x2d, 0x31, 0x68, 0xbb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x5e, +0x5a, 0x51, 0x0a, 0x92, 0xa9, 0x40, 0x2e, 0xd0, +0xd4, 0x84, 0xc2, 0x86, 0x15, 0xd0, 0xb0, 0x53, +0x85, 0x3c, 0x08, 0x1c, 0xc5, 0x7b, 0x82, 0x08, +0x3a, 0x51, 0xa9, 0x83, 0xd9, 0x15, 0xd6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0x98, 0x69, 0x74, 0x27, 0x47, 0xb2, 0x54, +0x48, 0x49, 0xf8, 0x51, 0x5c, 0xf7, 0x33, 0x12, +0x3e, 0xee, 0xf8, 0x4e, 0xcf, 0xdb, 0x04, 0x8c, +0xcf, 0x7d, 0x6a, 0x25, 0x8c, 0xcf, 0xf4, 0x90, +0xd0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0x7b, 0x0c, 0x70, 0xfb, 0xc0, +0xa1, 0xf9, 0x2a, 0x91, 0x86, 0x6e, 0x45, 0x17, +0x8e, 0x99, 0x34, 0x29, 0xa0, 0x59, 0x63, 0xe0, +0x53, 0x5e, 0x61, 0xe5, 0xe3, 0x86, 0x57, 0x59, +0x0b, 0x47, 0xa2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0xe9, 0xef, 0x5f, +0x20, 0xc7, 0x5b, 0xa0, 0x69, 0x45, 0x8c, 0x12, +0x72, 0x51, 0xf1, 0x0b, 0xfa, 0x7d, 0xdb, 0xa8, +0x2b, 0xea, 0x20, 0x35, 0x22, 0x3a, 0x6c, 0x0c, +0x8b, 0x24, 0x15, 0xab, 0x9a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0x0a, +0xba, 0x00, 0x51, 0x27, 0x6d, 0x40, 0x53, 0x9b, +0xc9, 0x66, 0x10, 0xde, 0x69, 0x16, 0x02, 0x29, +0xc1, 0x40, 0xcb, 0x1d, 0x22, 0xae, 0xc0, 0x08, +0xd8, 0xc6, 0x91, 0xfe, 0x62, 0xa5, 0x76, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x9e, 0x24, 0x76, 0xb2, 0x3c, 0xba, 0xc6, +0x0b, 0xd5, 0x8a, 0x83, 0xe1, 0x1f, 0x85, 0x44, +0x7b, 0x5c, 0xa1, 0xa5, 0x00, 0xc0, 0x4c, 0x45, +0x8a, 0x2f, 0x51, 0x09, 0x3e, 0xe0, 0xbf, 0x7e, +0x43, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0xc5, 0x27, 0x3b, 0xf1, 0x5b, +0x96, 0xac, 0xbc, 0xc2, 0xf0, 0x62, 0xcf, 0x28, +0x50, 0x9a, 0xe1, 0x89, 0xdd, 0xb1, 0x4b, 0x59, +0xf1, 0x8c, 0x3d, 0xb9, 0x80, 0x7d, 0xd6, 0x13, +0xba, 0x1e, 0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0xbb, 0xd8, 0xe4, +0xe0, 0x89, 0x7f, 0x82, 0x73, 0x0d, 0x66, 0x6f, +0x3d, 0xd2, 0x66, 0x78, 0x36, 0xbe, 0x36, 0x96, +0xb1, 0xd2, 0xa8, 0x1c, 0xe2, 0xd3, 0x43, 0xa0, +0xc5, 0x3c, 0x25, 0x14, 0xd2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0x41, +0xbb, 0x82, 0xb9, 0xfd, 0xe7, 0x39, 0x9e, 0xfc, +0x45, 0x2d, 0xe9, 0xcb, 0x5e, 0xf1, 0x17, 0x62, +0x57, 0xa3, 0x46, 0x81, 0xbf, 0xcf, 0x0b, 0xb2, +0xbe, 0x82, 0x09, 0xc1, 0xc8, 0x18, 0x53, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0xd4, 0x9c, 0xe7, 0x40, 0x44, 0x9c, 0xc4, +0x97, 0x9a, 0xf2, 0x6e, 0x92, 0x7f, 0x28, 0x83, +0x12, 0x55, 0x0e, 0x34, 0x5e, 0xa5, 0xba, 0x6b, +0xa3, 0xbc, 0xf2, 0xaa, 0x63, 0x24, 0xfb, 0x9b, +0x05, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0x4b, 0xc4, 0x93, 0xa5, 0xa5, +0x39, 0xa8, 0x5d, 0xb9, 0x43, 0x2b, 0x7d, 0x6d, +0x8a, 0xd1, 0xb2, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x4d, 0x89, 0xad, 0x54, +0x9e, 0x40, 0x66, 0xcb, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0x1f, 0xde, 0x2c, +0x20, 0xeb, 0x3c, 0xa8, 0x8a, 0xf6, 0xd5, 0x50, +0xb3, 0x69, 0x7a, 0xa5, 0xa1, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd2, 0xb3, +0xda, 0x3b, 0xdc, 0x39, 0xae, 0xa0, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0x17, +0x8f, 0xe6, 0xc9, 0x8d, 0x10, 0x40, 0x33, 0xaf, +0x69, 0x39, 0x3c, 0x60, 0xfc, 0xa6, 0x75, 0x27, +0x02, 0x2d, 0xe4, 0xf4, 0x0a, 0x24, 0xa7, 0x93, +0x3f, 0xa5, 0xfa, 0x3f, 0x25, 0xf7, 0x33, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x92, 0xaa, 0x0d, 0xd5, 0x12, 0x84, 0x99, +0xfd, 0x03, 0x47, 0xae, 0xdc, 0x47, 0xf9, 0xe3, +0x3a, 0xfe, 0x5c, 0xbc, 0x0c, 0x64, 0x75, 0x11, +0x9f, 0x14, 0x29, 0xed, 0x67, 0xc0, 0x78, 0x27, +0xc0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0x41, 0x9c, 0xc0, 0x69, 0x71, +0x63, 0x85, 0x71, 0x9a, 0xe2, 0x41, 0x2b, 0xee, +0x3d, 0xa0, 0xcc, 0xcf, 0x83, 0x87, 0x51, 0x09, +0xf4, 0x28, 0x45, 0xf9, 0x1e, 0xc0, 0x0d, 0xdf, +0xdb, 0x96, 0x8c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0x77, 0xea, 0xaa, +0xd5, 0x7e, 0x8e, 0xc4, 0xd9, 0x59, 0x41, 0x65, +0x5b, 0x91, 0x06, 0xbe, 0x8d, 0x44, 0xee, 0xc6, +0x9a, 0x8f, 0x17, 0x36, 0x71, 0x37, 0x92, 0x26, +0x9e, 0x01, 0xe8, 0x04, 0xa5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0xd3, +0x84, 0x48, 0x2b, 0x59, 0x35, 0x6e, 0x71, 0x17, +0x02, 0xf4, 0x91, 0xa1, 0x66, 0xa1, 0x37, 0xf8, +0x43, 0x21, 0x9d, 0xa1, 0x44, 0xf3, 0xa2, 0xda, +0x5e, 0x49, 0xce, 0xc7, 0xa5, 0x3d, 0x7e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0x13, 0x75, 0xaf, 0x4b, 0x18, 0xc3, 0x22, +0x56, 0x28, 0x7b, 0x45, 0xea, 0x79, 0x0d, 0x5b, +0xe8, 0x18, 0x2d, 0x8b, 0xf0, 0x36, 0x5d, 0x5b, +0x78, 0xe5, 0xb8, 0x68, 0xf3, 0x97, 0x85, 0x5f, +0x07, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0xfc, 0x63, 0xf4, 0x2c, 0xaf, +0x44, 0x06, 0x59, 0x6e, 0xc7, 0xbc, 0xb0, 0x63, +0x0c, 0x38, 0x17, 0xdd, 0x81, 0xcc, 0xa8, 0x65, +0xf0, 0x62, 0x41, 0x86, 0x4a, 0x9f, 0x4d, 0x44, +0x02, 0x4c, 0x2a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0x87, 0x08, 0x5d, +0x9c, 0x70, 0x7a, 0x13, 0x0c, 0xcc, 0xe6, 0x36, +0x55, 0xf6, 0x08, 0x83, 0x80, 0xb2, 0xea, 0x0d, +0x1b, 0xb8, 0xbb, 0xac, 0x80, 0x82, 0x19, 0xa0, +0x02, 0x9f, 0x5b, 0x19, 0xf2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0x23, +0x62, 0x2d, 0x83, 0x5f, 0x15, 0x4e, 0x56, 0x1f, +0xa0, 0x3c, 0xc3, 0xe4, 0xf7, 0xd4, 0xe1, 0xa7, +0xad, 0x6e, 0x3f, 0x4c, 0x77, 0x9c, 0x13, 0x6e, +0x39, 0xa1, 0xf4, 0x04, 0x8e, 0xe5, 0x25, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0x1d, 0xb3, 0xcd, 0x1a, 0xf6, 0xd8, 0x86, +0xf7, 0x02, 0x25, 0xcd, 0x01, 0x6b, 0xce, 0x9e, +0x2f, 0x19, 0x39, 0xdf, 0x55, 0x77, 0x1a, 0x5c, +0x30, 0x24, 0x7c, 0xf5, 0x11, 0xe0, 0x58, 0x67, +0x90, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0xf6, 0x5d, 0x51, 0x41, 0x25, +0xf8, 0x79, 0xe1, 0x63, 0xd2, 0x55, 0xbf, 0x77, +0x0c, 0xc0, 0x99, 0xf0, 0x5f, 0x94, 0x0b, 0xb3, +0xa4, 0xac, 0x83, 0x19, 0x07, 0x1d, 0x6a, 0x64, +0x65, 0x6e, 0x73, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x02, 0x39, 0x8b, +0xf8, 0xcb, 0xee, 0x2d, 0x9e, 0xe4, 0xef, 0x48, +0x8c, 0xb0, 0x0d, 0xed, 0x13, 0x88, 0xde, 0x73, +0x7d, 0x61, 0x66, 0xf8, 0xf0, 0xc5, 0x46, 0x31, +0x55, 0x1a, 0x69, 0x9c, 0x05, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0x82, +0x14, 0x16, 0x24, 0x64, 0x76, 0xbf, 0x3c, 0x64, +0xdd, 0xb8, 0xf4, 0x78, 0x4b, 0x76, 0x33, 0x1b, +0x22, 0xa5, 0x1e, 0x82, 0x8f, 0xe6, 0xd1, 0x28, +0xf3, 0x19, 0xec, 0xbd, 0x39, 0x8c, 0x41, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0xc2, 0x84, 0xe8, 0x2d, 0x24, 0xc3, 0xd0, +0xa6, 0x6a, 0xec, 0xbc, 0x29, 0x4c, 0x3a, 0x85, +0xd3, 0x77, 0x80, 0x90, 0x2a, 0xb8, 0xf1, 0xb6, +0x1e, 0x63, 0xec, 0xbb, 0x14, 0xd9, 0xe3, 0x4a, +0x0c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0x3d, 0x27, 0xac, 0x04, 0x88, +0x2d, 0xc1, 0x7a, 0xa8, 0xb3, 0x5d, 0x54, 0x81, +0x73, 0xc0, 0x30, 0x7f, 0x95, 0x5e, 0xa7, 0xdd, +0xba, 0x49, 0xc5, 0xaf, 0x57, 0x51, 0xdd, 0xce, +0xad, 0xe4, 0x74, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0x51, 0xab, 0x73, +0xe9, 0xb6, 0x6c, 0x12, 0x50, 0x97, 0xd8, 0xb8, +0x22, 0xda, 0x8c, 0xde, 0x13, 0xa5, 0x56, 0xaa, +0x4f, 0x89, 0x8e, 0x12, 0x6a, 0xd9, 0x2a, 0x8b, +0xb9, 0x5a, 0xd1, 0xc8, 0x2d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0x9a, +0xe1, 0x8f, 0x96, 0xd6, 0x01, 0xb4, 0x34, 0x8f, +0x95, 0xdf, 0xd6, 0x6f, 0x50, 0x9d, 0x05, 0xd8, +0x11, 0x4c, 0x89, 0x19, 0x7b, 0x30, 0x82, 0xb6, +0x77, 0x29, 0x63, 0xb3, 0xe2, 0x91, 0xfe, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0x90, 0xe9, 0xf8, 0x23, 0x1e, 0x26, 0xb9, +0xa5, 0xa8, 0x87, 0x98, 0x00, 0x2a, 0x53, 0x41, +0x6f, 0xef, 0x56, 0x6e, 0xc4, 0xca, 0x45, 0x45, +0x91, 0xb8, 0x66, 0x4a, 0xbf, 0xc5, 0xbf, 0xaa, +0x3c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0xf7, 0xa7, 0x9e, 0x1b, 0xfc, +0xa4, 0x31, 0x65, 0xd4, 0xa1, 0x2f, 0xb2, 0x4f, +0x5c, 0x12, 0x8d, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xe7, 0x60, 0x7f, 0x0d, +0xa0, 0xc9, 0xd8, 0x9f, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0xa5, 0x23, 0xcd, +0x71, 0x1d, 0x76, 0xcf, 0x16, 0x33, 0x29, 0x01, +0x76, 0xf2, 0x20, 0x9c, 0xb5, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x4b, +0x3e, 0xcd, 0x24, 0xc2, 0xc5, 0xb0, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0x6f, +0xbc, 0xd0, 0x38, 0xa0, 0x93, 0x89, 0x66, 0xf6, +0x52, 0x10, 0xd8, 0x9e, 0xfd, 0xcf, 0xff, 0xdc, +0xf3, 0xbe, 0x15, 0x14, 0x6b, 0xdc, 0xf0, 0xd1, +0x15, 0x72, 0x80, 0xf8, 0xc4, 0xd1, 0xdf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0xd9, 0x76, 0x5e, 0x09, 0xa5, 0xc3, 0xc9, +0xf4, 0x95, 0xf5, 0xb7, 0x7a, 0x70, 0x32, 0xee, +0xeb, 0x9e, 0x6a, 0x33, 0xf4, 0x6e, 0xb4, 0x5a, +0xda, 0xce, 0xe1, 0x84, 0x05, 0xe7, 0x73, 0x91, +0x7e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0x18, 0x60, 0xfb, 0xbd, 0x0f, +0x5b, 0x73, 0xc2, 0xd2, 0xeb, 0xd7, 0x7b, 0xb2, +0xbf, 0x4c, 0xde, 0x75, 0xad, 0x66, 0xf0, 0xdd, +0xf1, 0x48, 0xc4, 0xc8, 0xb0, 0x23, 0x0e, 0xf5, +0x50, 0x6c, 0xf0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0xf2, 0x64, 0xf3, +0x8e, 0x78, 0x4e, 0x93, 0x37, 0x04, 0x68, 0xc1, +0xcc, 0x0b, 0xbb, 0xc7, 0x1f, 0x3c, 0xfc, 0x15, +0x6f, 0x68, 0xc7, 0x9b, 0xc3, 0x8c, 0x14, 0xfc, +0x22, 0x7b, 0xfc, 0xf9, 0x1a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0x5a, +0xcf, 0x06, 0x1c, 0x96, 0xb6, 0xaf, 0x8e, 0xfe, +0xbc, 0xdb, 0x9a, 0x15, 0x69, 0xed, 0x06, 0xdf, +0x77, 0x51, 0x4c, 0xcf, 0x0b, 0x50, 0xcc, 0x9c, +0x44, 0x62, 0xcf, 0x51, 0x0e, 0x8a, 0xe9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x43, 0x28, 0xfd, 0xa3, 0x10, 0x84, 0x0e, +0x9e, 0x73, 0x4a, 0x59, 0x02, 0x2b, 0xce, 0x21, +0x27, 0x5f, 0xbe, 0x20, 0xca, 0x08, 0xd0, 0xda, +0x4e, 0xde, 0x39, 0xa5, 0xbe, 0x14, 0x12, 0x49, +0x7e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0xcd, 0x71, 0x66, 0xc1, 0xee, +0xb6, 0xcd, 0x7a, 0x4e, 0xb4, 0xde, 0x3b, 0x85, +0x78, 0x39, 0x4a, 0x14, 0x32, 0x9b, 0xe6, 0x9b, +0x34, 0x90, 0x05, 0x6a, 0xc0, 0x43, 0xab, 0x3a, +0x3c, 0x80, 0x4f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0xbe, 0x93, 0xa7, +0xe7, 0x56, 0x6f, 0x61, 0x66, 0x30, 0xbb, 0x05, +0x88, 0xb2, 0x62, 0xb7, 0x7a, 0x20, 0x14, 0xba, +0x1d, 0x40, 0x99, 0xec, 0xfa, 0xdc, 0xb4, 0x31, +0x33, 0xdf, 0x5c, 0x2d, 0xf0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0x78, +0x02, 0x3a, 0x02, 0xe6, 0x39, 0x9f, 0x82, 0x8f, +0xa6, 0x49, 0xa7, 0x82, 0x6b, 0x22, 0xc8, 0x5e, +0xcc, 0x59, 0x27, 0xe4, 0xdb, 0x57, 0x34, 0x31, +0x48, 0xfb, 0xdb, 0xca, 0xfc, 0x55, 0xc5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0x1a, 0xa6, 0x0f, 0xd5, 0x68, 0xdb, 0x03, +0x85, 0x8c, 0x78, 0x1e, 0x7a, 0x17, 0xea, 0xb5, +0x92, 0x2e, 0x70, 0x48, 0x98, 0x5f, 0x96, 0xcf, +0x87, 0x82, 0x68, 0x6e, 0x1a, 0xfe, 0xba, 0xf7, +0x6a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0x74, 0x06, 0x6e, 0x17, 0x0c, +0x70, 0xbc, 0xc6, 0xe9, 0xb3, 0xcb, 0x8c, 0x8b, +0x1e, 0x89, 0x43, 0xb0, 0x66, 0x9e, 0xf0, 0x55, +0xc1, 0xc4, 0x50, 0xdb, 0x5a, 0x93, 0xdf, 0x9f, +0x5c, 0xb1, 0xad, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0x2c, 0x9c, 0xd5, +0xbf, 0xd1, 0xbd, 0x7a, 0x3f, 0xa0, 0x42, 0xfb, +0xd8, 0x04, 0xc6, 0xe3, 0xc4, 0x5c, 0xf8, 0xe4, +0x6b, 0x69, 0x0b, 0xaa, 0xac, 0x23, 0x3a, 0xaf, +0x93, 0xdb, 0x86, 0xab, 0x52, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0x46, +0xf5, 0xab, 0x8c, 0xc5, 0x7b, 0x36, 0x5e, 0xb4, +0x72, 0x6d, 0x76, 0x71, 0xe4, 0x26, 0xdc, 0xb2, +0x84, 0xd0, 0xa9, 0x16, 0x60, 0xb4, 0x03, 0x93, +0x63, 0x2b, 0x20, 0xae, 0x0f, 0xca, 0xee, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0xaa, 0x40, 0xdf, 0x4e, 0x06, 0x42, 0xef, +0x0a, 0xe3, 0xd4, 0x64, 0x6c, 0xca, 0x2a, 0x97, +0xd7, 0xcb, 0xae, 0xfd, 0xd1, 0xaf, 0x37, 0x53, +0xae, 0xa2, 0x40, 0x66, 0x55, 0xf6, 0x48, 0xc1, +0xa5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0x7b, 0x8f, 0xd7, 0x50, 0x7f, +0x97, 0x18, 0xc7, 0xc2, 0x27, 0x2e, 0x4b, 0x6c, +0x95, 0x80, 0xef, 0x2a, 0xd4, 0x88, 0x7f, 0xde, +0xb3, 0x53, 0xe4, 0xa6, 0x7b, 0xbc, 0x1b, 0x27, +0xa7, 0x6c, 0x3e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0xf6, 0x4f, 0xf4, +0x86, 0x27, 0xf0, 0x86, 0x32, 0xbd, 0x5b, 0xad, +0xb4, 0x75, 0x75, 0xb2, 0x2f, 0x36, 0x25, 0x3e, +0xa8, 0xf6, 0x35, 0x18, 0xcd, 0x7a, 0x48, 0x35, +0x57, 0x4f, 0xa5, 0x9b, 0x1b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0x49, +0x60, 0x79, 0x75, 0xf8, 0x4f, 0x7e, 0xcf, 0xcc, +0xa3, 0x31, 0x3e, 0x9d, 0xe8, 0x9d, 0x2a, 0x27, +0x8a, 0x90, 0x59, 0x92, 0x41, 0x2d, 0xbd, 0x88, +0x3c, 0x21, 0xa0, 0xc3, 0x4c, 0x8e, 0xff, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0x59, 0x3a, 0xf1, 0x83, 0x64, 0x20, 0x90, +0x3b, 0xb2, 0xf2, 0xec, 0xba, 0x10, 0x06, 0x57, +0xd1, 0x2c, 0x89, 0xf0, 0xe1, 0x04, 0x27, 0x12, +0xf7, 0xdb, 0xb6, 0xbc, 0x8e, 0xdf, 0xa5, 0x2e, +0xaf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0x44, 0xc7, 0xd8, 0x78, 0xf7, +0xdc, 0x1b, 0x7b, 0xf1, 0x00, 0x5d, 0x20, 0x56, +0xe9, 0x41, 0xe9, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x4e, 0x06, 0x4c, 0x33, +0x66, 0x50, 0x1b, 0x95, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0x5e, 0x35, 0xd2, +0x03, 0x47, 0x5a, 0x92, 0x1b, 0x5d, 0x89, 0x2b, +0x22, 0xc8, 0x20, 0x0d, 0x1f, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdb, 0xb9, +0x16, 0x64, 0x9a, 0x53, 0x87, 0xbd, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0xa9, +0x20, 0xe2, 0xc2, 0x54, 0xb6, 0xb0, 0x94, 0x5e, +0xbf, 0x06, 0xc3, 0x9a, 0x87, 0xd2, 0x0c, 0x49, +0xe7, 0xf5, 0xae, 0xaf, 0x82, 0x8f, 0x7c, 0xb5, +0x39, 0x36, 0xa1, 0xcd, 0x6d, 0x2a, 0xea, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0x60, 0x3a, 0x47, 0xe4, 0x61, 0xc6, 0xc3, +0x6b, 0x54, 0xc5, 0xb1, 0x03, 0x95, 0x72, 0xeb, +0xf3, 0xd1, 0xdf, 0xd3, 0x18, 0x90, 0x25, 0x7f, +0x93, 0x5a, 0x18, 0x5e, 0x31, 0xfe, 0x93, 0x2d, +0xae, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0xea, 0x2a, 0x39, 0xea, 0x6f, +0xe7, 0xab, 0xde, 0x26, 0x48, 0x47, 0xdf, 0xd2, +0xa1, 0x87, 0x06, 0x0d, 0xed, 0x32, 0xe1, 0xc5, +0x51, 0xd8, 0xe1, 0xdb, 0x43, 0xae, 0xc1, 0xe5, +0xa9, 0x58, 0xd5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0xc3, 0x29, 0xb9, +0x69, 0x6a, 0x4d, 0xab, 0xe9, 0xf7, 0xe1, 0x6a, +0x53, 0xdb, 0xb6, 0xd4, 0x91, 0x3a, 0xfb, 0x83, +0xde, 0xe2, 0xef, 0x36, 0x08, 0xb2, 0xeb, 0x4f, +0x84, 0x72, 0x49, 0xa6, 0x32, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x17, +0x7d, 0x40, 0x22, 0x3d, 0x99, 0x30, 0x8f, 0x1f, +0x3b, 0x50, 0x7b, 0x2f, 0xde, 0x70, 0xb0, 0x26, +0x15, 0xda, 0x97, 0xd5, 0x4f, 0x28, 0xcf, 0x0b, +0x47, 0xf3, 0x89, 0x90, 0xaf, 0x25, 0x3e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0xa1, 0x6e, 0xbd, 0x06, 0x14, 0x70, 0x1a, +0x10, 0x61, 0x25, 0xae, 0xfe, 0xa6, 0x8a, 0x7e, +0xce, 0xaf, 0x66, 0x9b, 0x92, 0x1f, 0x47, 0x07, +0x87, 0x96, 0xf8, 0x77, 0xec, 0x3b, 0x4d, 0xca, +0xca, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0xeb, 0x29, 0x78, 0x7a, 0xb4, +0xeb, 0x77, 0x4a, 0x88, 0xc8, 0xda, 0x30, 0x89, +0x17, 0x8a, 0xb1, 0x26, 0x56, 0xb5, 0xf7, 0xa0, +0x3f, 0x17, 0x6b, 0x20, 0x48, 0xc9, 0xab, 0x76, +0xa3, 0x85, 0x9b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0xe1, 0x18, 0x1f, +0xe8, 0xab, 0x66, 0x80, 0x1a, 0x52, 0x13, 0x33, +0x14, 0x52, 0x67, 0x20, 0xdb, 0x99, 0xbb, 0xd3, +0x36, 0x92, 0x61, 0xd4, 0x2e, 0x52, 0x54, 0x36, +0xcd, 0x10, 0x2c, 0xed, 0xe8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0xfa, +0x06, 0x03, 0x4b, 0x3f, 0x40, 0xc5, 0x97, 0xf9, +0xa3, 0x7a, 0xa9, 0x56, 0xfe, 0xa3, 0xf1, 0x53, +0xd6, 0x8f, 0x47, 0xe9, 0x5e, 0xb3, 0xec, 0x49, +0x74, 0x1f, 0xe7, 0x77, 0xe6, 0x0b, 0x80, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0x8b, 0x68, 0x72, 0x35, 0xe0, 0x0d, 0xd4, +0x2a, 0x9e, 0x55, 0xe9, 0xea, 0xf1, 0x7a, 0xd8, +0x49, 0x16, 0x3a, 0x54, 0x5a, 0x10, 0xd4, 0x13, +0x90, 0x3d, 0x86, 0xdd, 0x6f, 0x24, 0xb8, 0x62, +0xc5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0xe2, 0x69, 0x2a, 0x8e, 0x98, +0x15, 0xcb, 0x0e, 0x8f, 0x45, 0xdc, 0xcb, 0x72, +0xec, 0xb2, 0x6e, 0x2b, 0xe7, 0xdf, 0x75, 0x6d, +0xef, 0xf8, 0xcd, 0x1c, 0x1d, 0xa6, 0x2c, 0xed, +0x7b, 0xb0, 0xc2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x51, 0x79, 0xec, +0x45, 0xb9, 0xb7, 0x7e, 0x88, 0x29, 0x60, 0x07, +0x2d, 0xb0, 0x3a, 0x6b, 0xef, 0x1d, 0x62, 0x2e, +0x5f, 0x24, 0x58, 0x24, 0xe6, 0x4e, 0x92, 0xbc, +0x44, 0xf7, 0xef, 0x39, 0x09, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0x8f, +0xa8, 0x02, 0x52, 0x6c, 0xe3, 0x34, 0xe4, 0x01, +0xd0, 0x58, 0x2b, 0x55, 0x16, 0xd7, 0x97, 0xf9, +0x20, 0x73, 0x6e, 0x6d, 0x67, 0x86, 0xbb, 0xc9, +0xb1, 0x15, 0x62, 0x38, 0x27, 0x6c, 0xb3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0xcb, 0x6f, 0x9d, 0x6c, 0x2c, 0x96, 0x7f, +0x82, 0x7b, 0x8a, 0x2b, 0xe5, 0xe6, 0x9e, 0x9f, +0x8b, 0xc4, 0xe6, 0x41, 0xae, 0x82, 0xfc, 0xb1, +0x6e, 0x22, 0x97, 0x2b, 0xa9, 0x8a, 0xcb, 0x9e, +0xff, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0xa2, 0x6c, 0x63, 0xfe, 0x51, +0x10, 0xf7, 0x84, 0xa0, 0x76, 0x4c, 0x95, 0x8e, +0xe0, 0x48, 0x1a, 0x33, 0x17, 0x9c, 0x3f, 0x61, +0x05, 0xd6, 0xe9, 0x34, 0xae, 0x2d, 0xec, 0xc7, +0xe2, 0x74, 0xa2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x68, 0x7e, 0x92, +0xec, 0x5e, 0x47, 0xed, 0x34, 0x98, 0x6e, 0xf1, +0x8b, 0x68, 0x90, 0xd6, 0x95, 0x9f, 0x4a, 0xee, +0xc6, 0x62, 0xde, 0x84, 0xce, 0x5a, 0x3f, 0x88, +0x9d, 0x50, 0xb7, 0x03, 0x5d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0x70, +0x7b, 0x65, 0xe6, 0xb7, 0x32, 0xe2, 0x91, 0x02, +0xd2, 0x80, 0xa2, 0x3c, 0x8a, 0xf1, 0x7a, 0xf3, +0x8a, 0x5d, 0x74, 0xf4, 0x77, 0x4c, 0xc6, 0xdf, +0xe6, 0x49, 0x65, 0xd7, 0x23, 0xc2, 0x7d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0xb4, 0x15, 0x3f, 0x9b, 0x0b, 0x32, 0x65, +0x00, 0x81, 0x1c, 0xf5, 0xa9, 0xec, 0x98, 0x2e, +0x43, 0xf2, 0x4d, 0x88, 0x6a, 0x01, 0x27, 0x33, +0xb8, 0x1f, 0xa8, 0x41, 0x6a, 0x04, 0xd4, 0xca, +0x04, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0xb7, 0x29, 0x29, 0x6e, 0xf9, +0xa6, 0xf3, 0x0f, 0x76, 0x9e, 0xf9, 0x3c, 0x3f, +0x58, 0x5f, 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x23, 0xdf, 0xb2, 0x6e, +0x6e, 0x2b, 0xc3, 0x0c, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0x03, 0x2d, 0x86, +0x8a, 0x70, 0x5d, 0xbc, 0xa4, 0x52, 0x7b, 0xb5, +0xa9, 0x3f, 0x6f, 0x0d, 0x95, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x74, +0xc9, 0x67, 0x84, 0xcf, 0xc8, 0x16, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0xd8, +0xb4, 0x3f, 0xe6, 0xeb, 0x9c, 0xf5, 0x8c, 0xb7, +0x30, 0x80, 0xa8, 0x26, 0xa6, 0xd2, 0x06, 0xd2, +0x4f, 0xfd, 0x5a, 0xa1, 0x64, 0xa7, 0x23, 0x9b, +0x4c, 0x54, 0xe7, 0x22, 0x56, 0x3b, 0x22, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0xd9, 0xac, 0x67, 0xab, 0x77, 0x08, 0x2e, +0x16, 0x45, 0xb0, 0xb9, 0xef, 0xa4, 0xd5, 0x0f, +0x5b, 0xc1, 0xd1, 0xf0, 0x8e, 0x71, 0x20, 0xf9, +0xc9, 0xaa, 0x76, 0xdf, 0x11, 0x12, 0x3f, 0x19, +0x97, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0xe3, 0xfb, 0x10, 0x81, 0xe0, +0xdb, 0xac, 0x4b, 0x1c, 0x7e, 0xc9, 0x8d, 0x0f, +0xca, 0x1c, 0x05, 0x4c, 0x71, 0xfd, 0x1d, 0x3e, +0x3b, 0x53, 0x0d, 0xd0, 0x20, 0x4f, 0x12, 0xb3, +0x5c, 0xc0, 0x6b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0xbe, 0xe0, 0x71, +0x24, 0xf8, 0x89, 0x70, 0xb9, 0x5d, 0x13, 0xbc, +0xc8, 0x6b, 0xf4, 0x92, 0x72, 0xe1, 0x1b, 0x98, +0x4b, 0x0a, 0x49, 0x4f, 0xbd, 0x6f, 0xa9, 0xe6, +0xb4, 0x27, 0x3c, 0x6a, 0xc3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0xc3, +0x0e, 0x27, 0x17, 0x7a, 0xe4, 0xf6, 0x55, 0xa2, +0xad, 0x60, 0xe2, 0xb1, 0x1f, 0xc5, 0xdd, 0x59, +0x02, 0x79, 0x58, 0xf5, 0x19, 0xd5, 0x4b, 0x39, +0xd8, 0x0c, 0x5e, 0xfd, 0x95, 0x68, 0x3c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0x3c, 0xd2, 0x55, 0x5b, 0x80, 0x66, 0x0f, +0xa8, 0x22, 0x51, 0xd4, 0x22, 0x3f, 0x4a, 0x86, +0xde, 0xe1, 0x6f, 0x26, 0x88, 0x8c, 0xc9, 0xae, +0xf0, 0xa4, 0x54, 0x9f, 0x1c, 0x81, 0xe8, 0xc0, +0x6e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0x20, 0xe6, 0x1c, 0x03, 0x4f, +0x09, 0x8c, 0xb2, 0xe6, 0x8a, 0x6a, 0x8b, 0xf2, +0x38, 0x69, 0xd0, 0x7c, 0xe8, 0x82, 0xb1, 0x52, +0xfc, 0x06, 0x1a, 0x07, 0xa5, 0x1c, 0x6d, 0x07, +0x73, 0xde, 0xbd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0xef, 0x22, 0xf0, +0x5c, 0x8c, 0xda, 0x4b, 0xea, 0x0f, 0x5b, 0xcf, +0x6f, 0x83, 0x2b, 0x9f, 0x5c, 0x1f, 0xcf, 0x75, +0xb1, 0x8a, 0x60, 0x7c, 0x3b, 0x8e, 0x74, 0x06, +0x66, 0x5e, 0xfa, 0x8f, 0xb5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0xd5, +0xb7, 0x84, 0x8a, 0x43, 0x10, 0x25, 0xd4, 0xc7, +0x55, 0x78, 0x05, 0x6c, 0xa6, 0x9e, 0xbe, 0x69, +0xd4, 0xe3, 0xdd, 0x5a, 0xa6, 0xa3, 0x50, 0x17, +0x99, 0xce, 0x2b, 0x49, 0x0e, 0x39, 0x1f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0x13, 0x6b, 0x2e, 0x7b, 0x8b, 0x9c, 0xda, +0x16, 0x28, 0xd6, 0x2a, 0x71, 0x5b, 0x52, 0xca, +0xbd, 0x89, 0xab, 0xcc, 0x9f, 0x17, 0x9a, 0xac, +0x8b, 0xcb, 0x05, 0x2b, 0xf3, 0xbc, 0xa8, 0xb9, +0x82, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0xd0, 0x57, 0x22, 0x96, 0x42, +0x90, 0x4f, 0x2d, 0x60, 0x95, 0xa0, 0x07, 0xc4, +0x8b, 0x02, 0xa2, 0x42, 0x0b, 0x81, 0x2c, 0x40, +0x2e, 0x8f, 0xb6, 0xd5, 0x13, 0x00, 0xdd, 0x52, +0x5a, 0xb5, 0xc0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0xd1, 0x47, 0xbe, +0xaf, 0x3a, 0xc6, 0x4b, 0x4e, 0xb3, 0xbc, 0xd7, +0xec, 0x67, 0xae, 0x6d, 0xf5, 0xb7, 0x99, 0x77, +0x52, 0x3e, 0xe9, 0xb9, 0xc8, 0xea, 0x2e, 0xc1, +0x4b, 0xad, 0x5b, 0xfa, 0x01, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0xa2, +0x0c, 0x5a, 0xaf, 0x28, 0x2f, 0x7d, 0xda, 0x57, +0xd6, 0x39, 0xe5, 0x06, 0x2d, 0x7f, 0xc9, 0xb7, +0xe6, 0x0d, 0x6c, 0xca, 0xb2, 0x7a, 0x27, 0x94, +0xbd, 0xf7, 0xa6, 0x00, 0xc2, 0x49, 0x5f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0x67, 0xa0, 0xc5, 0x72, 0x6d, 0xc7, 0xa3, +0x6b, 0xff, 0x82, 0x1f, 0xff, 0x42, 0x15, 0x90, +0x0d, 0x4c, 0x93, 0xa6, 0xd1, 0x29, 0x6a, 0x10, +0x59, 0xd1, 0xce, 0xd3, 0x49, 0xf7, 0xa2, 0xa0, +0x0f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0x40, 0xc1, 0x17, 0x9b, 0x41, +0x28, 0xa2, 0x75, 0xfc, 0x5b, 0xba, 0xde, 0x45, +0xf6, 0xce, 0x47, 0x6a, 0x4b, 0x70, 0xff, 0x16, +0xa6, 0x71, 0x06, 0xa6, 0xcb, 0x77, 0x76, 0xa0, +0x07, 0x0e, 0xab, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0xef, 0x52, 0x57, +0x16, 0xae, 0x2e, 0x5b, 0x74, 0xb1, 0x70, 0x29, +0x77, 0xd3, 0xfe, 0x47, 0xb6, 0x2a, 0xde, 0x12, +0x07, 0x50, 0x0f, 0xd1, 0x56, 0xcd, 0xda, 0xb3, +0x41, 0x16, 0x6d, 0xcc, 0x2f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x21, +0xc7, 0x84, 0x6e, 0x9a, 0x41, 0x3b, 0x50, 0x76, +0x5a, 0x46, 0xc3, 0x06, 0x63, 0x60, 0x00, 0x26, +0x84, 0xea, 0xe0, 0x4e, 0x04, 0xa7, 0xb9, 0xc0, +0xc3, 0x09, 0x04, 0x2f, 0x46, 0x18, 0xfa, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x2c, 0x0b, 0x94, 0x5e, 0xe5, 0x7e, 0xf9, +0x0b, 0xa0, 0x87, 0x5e, 0x23, 0xc3, 0x54, 0xb2, +0xe3, 0xbf, 0xa0, 0xc4, 0x29, 0xcc, 0x65, 0x68, +0x31, 0x7a, 0x8f, 0x1f, 0x5b, 0x26, 0xd4, 0x8f, +0xb5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0xff, 0x61, 0x1f, 0xdf, 0x32, +0xdc, 0x7e, 0x2f, 0xa0, 0xbf, 0x88, 0xd7, 0x6d, +0x41, 0x09, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x61, 0x5e, 0x21, 0x51, +0x8d, 0x3f, 0xe3, 0x03, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0x4c, 0xab, 0x89, +0xa6, 0x51, 0x90, 0x8f, 0x9a, 0x9e, 0xe6, 0x8d, +0xb1, 0xc7, 0xd0, 0x5a, 0x4d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0xb6, +0xd7, 0x31, 0x12, 0xb9, 0x46, 0xde, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0x13, +0x60, 0xe9, 0x9a, 0xc3, 0x89, 0x51, 0xc3, 0xab, +0x0a, 0xe2, 0xb8, 0x16, 0x56, 0xf1, 0x89, 0x92, +0xb0, 0x9f, 0x72, 0x2b, 0xd8, 0x50, 0x23, 0x36, +0xfe, 0x18, 0x72, 0xcf, 0x0f, 0xa2, 0xc7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0xc9, 0xd7, 0xf0, 0x4b, 0x0b, 0x6c, 0x45, +0x60, 0x41, 0x49, 0xf8, 0x9c, 0x13, 0xd2, 0xa8, +0x2e, 0xf9, 0x93, 0x43, 0xfb, 0xc4, 0x57, 0x2c, +0x50, 0xd1, 0x2d, 0xcd, 0xd6, 0xf8, 0x64, 0xca, +0x2b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0x30, 0x45, 0xdb, 0xeb, 0x8e, +0x91, 0x38, 0x5b, 0xb7, 0xaa, 0x96, 0x8d, 0x00, +0x4e, 0xfd, 0x00, 0x0d, 0x84, 0x2b, 0x89, 0xdd, +0x0e, 0xb3, 0x22, 0xd8, 0xb1, 0x25, 0xc9, 0x0f, +0x46, 0xbc, 0x37, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0x14, 0xd4, 0x98, +0xa4, 0xf0, 0xcd, 0x4c, 0x8f, 0xea, 0xf4, 0x49, +0x29, 0x87, 0x03, 0xfe, 0x04, 0x9f, 0xdc, 0x54, +0xa2, 0xcc, 0xe7, 0xa6, 0xbe, 0x86, 0x82, 0x17, +0x03, 0x07, 0xac, 0xd1, 0x76, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0xc5, +0x9c, 0xe8, 0xb0, 0xa1, 0x52, 0xa3, 0xb2, 0xd8, +0x5e, 0x20, 0x79, 0xcd, 0xfa, 0x16, 0xf5, 0xd7, +0x39, 0xbe, 0x6e, 0x25, 0x6c, 0x32, 0xbf, 0x16, +0xdf, 0x2d, 0x05, 0x05, 0x36, 0x67, 0x0e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0x46, 0xa7, 0x51, 0xc0, 0x78, 0x8e, 0x3d, +0x1e, 0x0d, 0xec, 0x27, 0x40, 0x22, 0xa9, 0xb5, +0x24, 0x77, 0x5b, 0xe1, 0xf5, 0x62, 0x2b, 0xf8, +0xd1, 0x2e, 0xe2, 0x85, 0x46, 0x35, 0xe4, 0x2f, +0x4f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0xfd, 0x22, 0x16, 0x23, 0x3f, +0x7a, 0x34, 0xc3, 0x7d, 0x8e, 0x9d, 0x2b, 0x39, +0x8d, 0x47, 0xc1, 0x73, 0x65, 0x20, 0x58, 0x9a, +0x35, 0x79, 0x92, 0xbc, 0xdf, 0xc9, 0x75, 0x49, +0x4e, 0xa8, 0x32, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0xf6, 0xd1, 0xd8, +0x7d, 0x28, 0x7b, 0xd8, 0x09, 0x29, 0xee, 0x33, +0x0e, 0x8b, 0xb8, 0x36, 0xc8, 0x33, 0x36, 0xd4, +0x34, 0xb1, 0x23, 0xb9, 0x80, 0x5a, 0x8a, 0x88, +0x44, 0xe8, 0x09, 0xfe, 0xab, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0xa9, +0x8e, 0x3f, 0xac, 0x11, 0xe2, 0x56, 0x68, 0x63, +0xfe, 0x08, 0x8a, 0xce, 0x81, 0xf3, 0xe8, 0x2a, +0xd9, 0x81, 0xef, 0x0e, 0x46, 0x84, 0xf0, 0x51, +0x4e, 0xb5, 0xba, 0x3a, 0x07, 0x35, 0x62, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0xe6, 0x9f, 0xef, 0x7e, 0x47, 0x1e, 0x0f, +0x54, 0xff, 0x60, 0x82, 0xd3, 0x40, 0xd9, 0xa1, +0x82, 0xaf, 0x36, 0x7f, 0x0d, 0xb8, 0x8f, 0xc3, +0x46, 0x98, 0x39, 0x6b, 0x98, 0x7c, 0x03, 0xa0, +0xc5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0x50, 0xbe, 0x84, 0x1b, 0x05, +0x4d, 0x5d, 0x83, 0x3e, 0xcf, 0xe1, 0xad, 0x3f, +0x6a, 0x5b, 0x74, 0xee, 0x28, 0xde, 0xe4, 0x81, +0x4e, 0x5c, 0x9c, 0x7f, 0x55, 0x37, 0x24, 0x68, +0x3a, 0x05, 0x12, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0x26, 0xf0, 0x60, +0x95, 0x30, 0x06, 0x34, 0xb5, 0xc5, 0xcc, 0x1d, +0x31, 0x72, 0x57, 0x75, 0x2b, 0xef, 0xdb, 0x7f, +0xde, 0xd7, 0x55, 0x99, 0x9e, 0x43, 0xe9, 0xd0, +0x18, 0x84, 0x6a, 0x1c, 0xb4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0xde, +0xee, 0xa8, 0xc4, 0x6c, 0x8e, 0x45, 0xd6, 0x63, +0xb4, 0xff, 0x01, 0x8f, 0xfe, 0xde, 0x8a, 0x81, +0x3e, 0x3d, 0xc8, 0x6b, 0x34, 0x76, 0x25, 0x2b, +0x53, 0x84, 0x90, 0xa2, 0xae, 0xdd, 0x64, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0x8a, 0xbb, 0x4f, 0x3f, 0x85, 0x38, 0xaa, +0xcc, 0x6a, 0xf2, 0x29, 0xa2, 0x52, 0x83, 0x89, +0xc5, 0x0b, 0xcf, 0xc4, 0x83, 0x04, 0x3b, 0xcc, +0x93, 0xac, 0xbf, 0x04, 0xed, 0xb8, 0x6a, 0x3a, +0xd4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0x7e, 0xe9, 0x02, 0xc6, 0x90, +0x8b, 0xf1, 0x28, 0xd4, 0x14, 0x30, 0x3c, 0xe1, +0xaa, 0x17, 0x6c, 0xb5, 0x6b, 0x8e, 0xa8, 0xa7, +0x3a, 0x33, 0x32, 0x3f, 0x9f, 0x96, 0x9e, 0x4a, +0xe3, 0x23, 0xff, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0x81, 0xa0, 0xee, +0x4b, 0x94, 0x4c, 0x0b, 0x50, 0x7d, 0x30, 0x15, +0xb4, 0x10, 0xe3, 0x30, 0x21, 0x22, 0x81, 0x11, +0x4e, 0xb7, 0x1f, 0xac, 0x56, 0x0f, 0x9a, 0xf7, +0xf6, 0xf5, 0xb2, 0x19, 0xf4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0x04, +0x71, 0x08, 0x4e, 0xbb, 0xa7, 0xf6, 0xc5, 0x31, +0xb7, 0xc4, 0xfc, 0x55, 0xf8, 0x77, 0x0f, 0xe2, +0xe3, 0x4b, 0x73, 0xd4, 0x74, 0xa7, 0x5f, 0x25, +0x83, 0x77, 0xb2, 0x80, 0x7d, 0x37, 0x99, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0xbb, 0xe5, 0xe9, 0x80, 0x87, 0x43, 0x36, +0x5a, 0x4e, 0xd8, 0x04, 0x61, 0xc3, 0xc4, 0x3d, +0x2c, 0x5f, 0x80, 0x10, 0x5b, 0xe8, 0x81, 0x60, +0x6f, 0x0a, 0x41, 0x36, 0x0d, 0x37, 0xe5, 0x8a, +0x1e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0x03, 0x89, 0xe3, 0x7d, 0x3c, +0x77, 0x15, 0xb0, 0x19, 0x2d, 0x93, 0x9e, 0x3b, +0xb1, 0x53, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x86, 0xab, 0x33, 0x41, +0x5a, 0x8a, 0x3e, 0x72, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0xda, 0xd6, 0xb0, +0x42, 0x2b, 0xc0, 0x02, 0x25, 0x61, 0x44, 0x99, +0x3a, 0x9a, 0x61, 0xa8, 0xac, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x80, +0x14, 0x9d, 0x4a, 0x9d, 0xf2, 0xb1, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0x83, +0xe5, 0x3c, 0x0a, 0x24, 0x65, 0x7a, 0x7e, 0x9c, +0x3a, 0xaa, 0xe4, 0x7f, 0xe2, 0xc9, 0x97, 0x02, +0x91, 0x02, 0x6b, 0xcd, 0xec, 0xac, 0xcd, 0xf6, +0x86, 0xce, 0x91, 0xc7, 0x6b, 0x3b, 0xa8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0x12, 0x51, 0x44, 0x60, 0x39, 0x60, 0xde, +0xf6, 0x68, 0xe9, 0x7f, 0x03, 0x7d, 0x42, 0x3b, +0xc0, 0x9f, 0x4f, 0xb4, 0x83, 0x2f, 0x30, 0xcf, +0xa5, 0xfd, 0x3f, 0x49, 0xf3, 0x2d, 0xd4, 0x27, +0x91, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0xf8, 0x8d, 0x60, 0xd9, 0xdf, +0x69, 0x53, 0x53, 0x3c, 0xaf, 0x6c, 0x8a, 0xad, +0xb5, 0x4c, 0x50, 0x89, 0xad, 0x05, 0xcb, 0x7a, +0xf5, 0x45, 0xf3, 0x52, 0x5d, 0x93, 0x61, 0x73, +0xa3, 0x86, 0xe0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0xc3, 0x94, 0x16, +0xbf, 0xf3, 0x8e, 0x78, 0x2e, 0xe8, 0x06, 0x49, +0x7a, 0x5d, 0xb9, 0x8d, 0x19, 0xe5, 0x3b, 0x6b, +0xee, 0x29, 0x88, 0x8e, 0x29, 0xf7, 0x88, 0x77, +0x17, 0x78, 0x0f, 0x40, 0xec, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0xbc, +0xe1, 0x52, 0x5e, 0x1d, 0x7b, 0xf6, 0x91, 0x94, +0x0c, 0x32, 0x33, 0xb0, 0x49, 0xac, 0x65, 0xde, +0x0c, 0x8f, 0xbd, 0x45, 0x0f, 0xd1, 0x20, 0xbe, +0x3d, 0x6b, 0x3e, 0x85, 0x73, 0x1f, 0x72, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0x62, 0xc0, 0x05, 0x94, 0x5f, 0x95, 0x86, +0x9f, 0x7b, 0x0d, 0x58, 0xb8, 0x9b, 0x27, 0x68, +0x35, 0xf1, 0xdd, 0x0e, 0x39, 0x85, 0x75, 0xed, +0x32, 0x06, 0xf1, 0x9e, 0x4c, 0x7d, 0x60, 0x0b, +0x47, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0xc8, 0xc0, 0x10, 0x1f, 0x8d, +0x61, 0x72, 0x76, 0x7f, 0x49, 0x72, 0x6e, 0x63, +0x9a, 0x32, 0xd2, 0x75, 0xab, 0x8e, 0x20, 0x75, +0xa6, 0x50, 0x6b, 0xad, 0xb0, 0x74, 0x85, 0xc7, +0xba, 0xbd, 0x83, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0x18, 0xc8, 0x4a, +0x4b, 0xba, 0xd9, 0x43, 0xb6, 0x96, 0xeb, 0xd7, +0x88, 0xee, 0x22, 0x55, 0xb6, 0xe4, 0x69, 0xcf, +0xd9, 0x94, 0x26, 0x5b, 0x0e, 0x14, 0x48, 0x29, +0x61, 0xad, 0x12, 0x3e, 0xd1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0xac, +0xc7, 0xa7, 0xae, 0xea, 0x81, 0x4e, 0x7d, 0xa6, +0x34, 0xf7, 0x68, 0xe9, 0x90, 0x23, 0x02, 0x78, +0x1f, 0xae, 0xd3, 0x97, 0x77, 0x9c, 0x25, 0x4d, +0x58, 0x82, 0x07, 0x4e, 0x91, 0x45, 0x04, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0x98, 0xa7, 0x82, 0x9c, 0x1c, 0xac, 0xac, +0xa8, 0xa3, 0x9b, 0x0e, 0x27, 0xf2, 0xa2, 0x96, +0x98, 0xb3, 0xe2, 0x22, 0x86, 0x20, 0x02, 0x49, +0xa8, 0x8f, 0x98, 0x12, 0x5c, 0xb8, 0x01, 0x27, +0x13, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0x1f, 0x54, 0xfa, 0x2c, 0x99, +0x09, 0xa0, 0xf4, 0xa1, 0x23, 0x6d, 0x02, 0x43, +0x0d, 0x90, 0x7d, 0x7d, 0xab, 0x2e, 0x6b, 0xaf, +0x7f, 0xe6, 0x8e, 0x58, 0x98, 0x31, 0x71, 0x56, +0x1f, 0x4c, 0x95, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x2c, 0xdc, 0x45, +0xdf, 0x09, 0x2b, 0x7a, 0xff, 0xa7, 0xe0, 0xb7, +0xba, 0x26, 0x49, 0xef, 0xcf, 0x1c, 0x6a, 0x02, +0x1a, 0x46, 0x9f, 0x5d, 0x6d, 0x51, 0x7c, 0x60, +0xf1, 0xde, 0x30, 0x1c, 0xbb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0x67, +0x0a, 0x12, 0xad, 0x3a, 0xd3, 0x84, 0x97, 0x82, +0x38, 0x31, 0x1e, 0xc7, 0xaa, 0x98, 0x0f, 0x1e, +0x1d, 0x12, 0x87, 0x8d, 0x2d, 0x9b, 0xca, 0xa1, +0x9f, 0x9a, 0x86, 0x5a, 0x8d, 0xa8, 0xaf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x95, 0x7b, 0x2f, 0x19, 0xda, 0x27, 0xc2, +0xef, 0x6b, 0xef, 0x6d, 0x78, 0xb3, 0xec, 0xad, +0xa9, 0xcf, 0xd1, 0x89, 0x3a, 0xa2, 0x6d, 0xf5, +0xdd, 0xd5, 0x58, 0x65, 0x13, 0x2d, 0x1d, 0xaa, +0x05, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0xef, 0x50, 0xfa, 0xd6, 0xff, +0x8c, 0x11, 0x98, 0x14, 0x65, 0x9b, 0xb9, 0x5e, +0xa5, 0x4b, 0x8e, 0xb2, 0xdd, 0xec, 0x3c, 0xc3, +0xbd, 0xd3, 0xce, 0x71, 0x5d, 0x4a, 0x76, 0x71, +0x27, 0x04, 0x11, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0xdd, 0x64, 0xc4, +0x18, 0x8c, 0x9e, 0xb4, 0x93, 0xc8, 0x81, 0x4e, +0x2e, 0x38, 0xb6, 0x3c, 0x09, 0x0c, 0xca, 0xc0, +0x8f, 0x95, 0x57, 0xef, 0xa5, 0xe3, 0x10, 0x39, +0xf6, 0x52, 0x9a, 0xd6, 0x1c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x62, +0x5c, 0xa6, 0x6e, 0xef, 0x93, 0x87, 0xe3, 0xe9, +0x80, 0xb7, 0x95, 0x94, 0x58, 0x66, 0x68, 0x9f, +0xc2, 0x57, 0x47, 0x24, 0xd6, 0x5d, 0xa2, 0x5a, +0x6c, 0x02, 0xb9, 0xd7, 0x33, 0x32, 0xa7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0xde, 0x93, 0x15, 0xea, 0xdd, 0x42, 0x9f, +0x0b, 0x3f, 0xf7, 0x23, 0xc5, 0xd1, 0x58, 0x36, +0x35, 0x85, 0x73, 0x7a, 0x57, 0x74, 0xbd, 0x7d, +0x97, 0xdc, 0xe5, 0x32, 0x18, 0x27, 0xd4, 0x6f, +0x9a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0x9d, 0xf2, 0x07, 0xd4, 0xe1, +0xae, 0x9d, 0xdf, 0x2a, 0xbc, 0xb6, 0xd7, 0xd6, +0x5f, 0x04, 0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x1d, 0x82, 0x53, 0x4b, +0xe6, 0x42, 0x2a, 0x4e, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0xbb, 0x77, 0x04, +0x72, 0x18, 0x2e, 0x49, 0x3f, 0x59, 0xb2, 0x78, +0xa8, 0x29, 0xfe, 0x34, 0x99, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x1a, +0x39, 0x07, 0x6c, 0x96, 0x59, 0xaf, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0xe1, +0xb4, 0xfe, 0x5c, 0x79, 0xe9, 0x63, 0x57, 0xbf, +0x7f, 0xbd, 0xe0, 0xb0, 0x61, 0x2f, 0x1a, 0xc7, +0xc2, 0xf2, 0x80, 0x9b, 0x88, 0x8f, 0x20, 0x6d, +0xcc, 0xec, 0xf5, 0x1a, 0x37, 0x57, 0xbf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0x7e, 0x4a, 0x7f, 0x7b, 0x9a, 0xdf, 0x2b, +0x9e, 0xbc, 0x19, 0x97, 0x94, 0x8f, 0x3e, 0x59, +0x7d, 0xa8, 0xb7, 0x14, 0xb5, 0xcd, 0x70, 0x67, +0xb0, 0x47, 0x8d, 0xa2, 0x8b, 0xf9, 0x9a, 0x8e, +0x1e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0x0d, 0x87, 0x54, 0xd6, 0xd4, +0xe5, 0x50, 0x6d, 0x62, 0x42, 0x77, 0xb1, 0xa3, +0x80, 0xfa, 0xe2, 0xe0, 0x05, 0xf0, 0xbd, 0xb1, +0x44, 0x6e, 0x7a, 0x96, 0xa9, 0x3c, 0x50, 0xae, +0x00, 0xae, 0x45, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0xc7, 0x0b, 0xaa, +0x73, 0x17, 0xb9, 0x68, 0x48, 0x52, 0xdd, 0x8d, +0xde, 0x87, 0xd7, 0x1f, 0x5d, 0xcf, 0xc5, 0x83, +0x53, 0x52, 0x99, 0x9f, 0x6d, 0xc2, 0xf3, 0x7a, +0xd9, 0x71, 0x64, 0x53, 0xbf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0xb0, +0x80, 0x67, 0x37, 0x65, 0x09, 0x07, 0xaa, 0xd4, +0xaf, 0x4b, 0x3c, 0xb4, 0x68, 0xf8, 0xd6, 0x65, +0xa2, 0x3f, 0x01, 0x3a, 0x48, 0x66, 0x7e, 0x26, +0xf3, 0x5f, 0x08, 0x38, 0x9d, 0x22, 0x13, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0xa6, 0x55, 0xd3, 0x68, 0xb5, 0xc1, 0xfe, +0x4a, 0x41, 0xf1, 0x37, 0xe8, 0xea, 0xaf, 0x21, +0x5d, 0xfd, 0x2f, 0x30, 0x2c, 0x88, 0xff, 0x5f, +0x7b, 0xff, 0x6e, 0xb6, 0xdb, 0xc6, 0x46, 0x0d, +0xab, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0x77, 0x51, 0x47, 0x62, 0x2c, +0x09, 0x49, 0xab, 0x50, 0xd5, 0x77, 0x0c, 0xf8, +0xb3, 0xce, 0x7d, 0x17, 0xda, 0x97, 0x10, 0xff, +0x6c, 0x38, 0x28, 0x0e, 0x7a, 0x37, 0xdd, 0x41, +0x2e, 0x1b, 0xa2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x78, 0x0c, 0xdc, +0x3c, 0x64, 0xe8, 0x62, 0xd8, 0xc2, 0xfe, 0x63, +0xe7, 0xd2, 0x68, 0xd2, 0x5a, 0x2f, 0x7f, 0x17, +0xa8, 0xb2, 0x1e, 0xb3, 0xde, 0x49, 0xb7, 0x6a, +0x8a, 0x59, 0x32, 0x22, 0x1e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0x48, +0x40, 0xb8, 0xf1, 0xeb, 0xfa, 0xd6, 0x49, 0x06, +0x4c, 0xd3, 0x50, 0xc2, 0x09, 0x54, 0x85, 0x93, +0x37, 0x61, 0xb5, 0x0b, 0x42, 0xac, 0xb3, 0xca, +0x04, 0x65, 0xa6, 0xbd, 0xbd, 0xcb, 0x99, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0x73, 0xfd, 0x54, 0x3a, 0xf7, 0x58, 0x9a, +0xa6, 0x3c, 0xa2, 0xfc, 0xa9, 0xf6, 0xf4, 0x07, +0x8f, 0xcc, 0x5f, 0x1c, 0xb9, 0xb7, 0xf4, 0x6f, +0xaa, 0xac, 0xe5, 0x3a, 0x7c, 0x93, 0xa2, 0xd5, +0xe6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0x79, 0x5c, 0xc8, 0x15, 0xdf, +0xd6, 0x86, 0xf5, 0x2d, 0x2e, 0xf0, 0x8a, 0x18, +0x34, 0x55, 0xec, 0x0e, 0x25, 0xd8, 0xc1, 0xe1, +0x94, 0xae, 0x2b, 0xd1, 0xe0, 0x22, 0x17, 0x51, +0x67, 0x7d, 0x2a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0x51, 0xe9, 0xfa, +0x9f, 0x44, 0xac, 0xf6, 0x02, 0x34, 0xa2, 0x13, +0x5c, 0x3e, 0xe9, 0x87, 0x2a, 0xa5, 0x73, 0x97, +0x1b, 0x8e, 0x39, 0x19, 0x5f, 0x79, 0xc5, 0xa7, +0x83, 0x30, 0xaf, 0xca, 0xbc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0xb9, +0xda, 0x71, 0xdc, 0xb0, 0x33, 0x15, 0x6a, 0xb6, +0xb6, 0xd4, 0x4d, 0x86, 0x2b, 0xc2, 0x77, 0x46, +0x19, 0x7e, 0x77, 0xb7, 0x13, 0xa0, 0x58, 0xbf, +0xba, 0xb6, 0xf9, 0xd5, 0x77, 0xbb, 0xaf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x0c, 0x43, 0xb5, 0x37, 0x1e, 0xb2, 0xe2, +0x62, 0x50, 0x29, 0xe5, 0x9b, 0xe1, 0x35, 0xbc, +0x36, 0x27, 0x29, 0xd3, 0x6f, 0xcf, 0xdb, 0xb2, +0xf8, 0xdd, 0x0c, 0x66, 0xb7, 0x7d, 0x48, 0x68, +0x35, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x29, 0x0a, 0x55, 0x9f, 0x8c, +0x16, 0x5d, 0xd0, 0xcd, 0x2e, 0xf0, 0x7f, 0x16, +0x6f, 0xa5, 0x3c, 0x5a, 0xe7, 0xf0, 0x5d, 0x8b, +0xd5, 0xc8, 0xbd, 0x99, 0xc2, 0x2f, 0xec, 0xfd, +0x66, 0xae, 0x74, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0x97, 0xf3, 0xbb, +0x69, 0xe5, 0x79, 0x61, 0x42, 0x76, 0xeb, 0xe7, +0xf5, 0x73, 0x6f, 0x8d, 0x3d, 0xad, 0x0d, 0x08, +0x9a, 0xd7, 0x2c, 0x69, 0x50, 0xf0, 0x56, 0x9f, +0x3e, 0x28, 0x86, 0x99, 0x8b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0x25, +0xf2, 0x86, 0xb3, 0x2e, 0xce, 0x22, 0x37, 0x9f, +0xa7, 0x00, 0x17, 0x6d, 0x45, 0xbf, 0xe8, 0xa6, +0x66, 0xfe, 0xc7, 0x75, 0xb3, 0x11, 0xf9, 0x5a, +0x35, 0x73, 0x12, 0xa5, 0x32, 0x5b, 0x47, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x1e, 0x84, 0x93, 0x07, 0x48, 0x07, 0x7f, +0xf2, 0xf0, 0x10, 0x50, 0xbb, 0x99, 0x6c, 0xdf, +0x11, 0xfb, 0xab, 0x5f, 0x91, 0xc6, 0xe5, 0xbe, +0x88, 0xc7, 0xf2, 0x07, 0x9b, 0x81, 0x4a, 0x61, +0x5b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0x50, 0x9c, 0x41, 0x84, 0xaa, +0x02, 0xfb, 0x3b, 0xe7, 0x32, 0x94, 0x11, 0x2b, +0x6a, 0xec, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x5a, 0xae, 0x20, 0x3f, +0x6e, 0x1e, 0x72, 0xf5, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0x5f, 0x45, 0x74, +0x5b, 0x66, 0x48, 0xc5, 0xc3, 0xd4, 0xfe, 0xaf, +0x75, 0xfe, 0x83, 0x82, 0xf1, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd1, 0x0d, +0xe9, 0x25, 0xa1, 0x30, 0xed, 0x38, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x2e, +0x01, 0x98, 0x33, 0x54, 0xa2, 0x9f, 0x68, 0x10, +0xda, 0x93, 0xa4, 0xf8, 0x78, 0x96, 0xc6, 0x51, +0x01, 0x01, 0x1f, 0xf2, 0x37, 0x61, 0x00, 0xbe, +0x6c, 0x83, 0x19, 0x06, 0x9c, 0x77, 0x6a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0xd9, 0xe4, 0xb3, 0xdd, 0x10, 0xa5, 0x6e, +0xcf, 0xcd, 0x1b, 0xa1, 0x2d, 0x1e, 0x20, 0x95, +0x03, 0x5c, 0x8d, 0xeb, 0x02, 0x15, 0xd5, 0xea, +0xa3, 0x5c, 0xf7, 0xb3, 0x75, 0x5a, 0xc2, 0x65, +0x22, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0x1a, 0x01, 0x28, 0x2b, 0xb1, +0xf0, 0x1d, 0xd9, 0x93, 0x36, 0x51, 0xdf, 0x3d, +0x44, 0x10, 0x6d, 0x5d, 0xf3, 0x27, 0x48, 0x28, +0x0c, 0xac, 0x04, 0x83, 0xce, 0x97, 0x57, 0x7f, +0x6d, 0xc2, 0xd6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0x0f, 0x24, 0x78, +0xf6, 0xd6, 0xd4, 0x82, 0xd6, 0xd8, 0x55, 0x74, +0x9f, 0xd8, 0xd9, 0x89, 0x7f, 0x67, 0x6c, 0xc8, +0xb9, 0xe0, 0xc9, 0xe0, 0xd5, 0xe8, 0xd2, 0x5b, +0xfe, 0xfb, 0x48, 0x14, 0x8b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0x8b, +0x80, 0x61, 0x4c, 0x6e, 0x0c, 0x34, 0x33, 0x6e, +0x83, 0xda, 0xfc, 0x1a, 0xc0, 0xf4, 0xd6, 0x97, +0x32, 0xf8, 0xdd, 0xf8, 0xde, 0x30, 0x31, 0x1b, +0x4a, 0x99, 0xea, 0x87, 0x76, 0xe3, 0xbf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0xf4, 0xf2, 0x7e, 0xe3, 0x36, 0xd7, 0x64, +0xdf, 0x45, 0x5f, 0x5e, 0x96, 0x71, 0xb8, 0xad, +0x3d, 0xad, 0xe2, 0x5c, 0x06, 0xc7, 0xd9, 0xa8, +0x7b, 0x8b, 0x29, 0x73, 0xd8, 0x4d, 0x89, 0x51, +0x14, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0xed, 0x39, 0x3d, 0x9f, 0x19, +0x6d, 0x1a, 0x83, 0xaa, 0x0d, 0x01, 0xcc, 0x8c, +0xc9, 0x86, 0xce, 0xde, 0xa4, 0xe4, 0x99, 0x54, +0xb4, 0xb6, 0xa1, 0xeb, 0x42, 0xa0, 0xb9, 0x0a, +0xfd, 0xa9, 0x57, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0xac, 0x1d, 0x75, +0x58, 0xae, 0x56, 0x79, 0x38, 0xb8, 0x52, 0xcc, +0xfa, 0x85, 0xc0, 0x26, 0xac, 0x53, 0x87, 0x95, +0x01, 0xcf, 0xd0, 0x92, 0xe1, 0xf9, 0x93, 0xa6, +0x5f, 0xae, 0x17, 0x07, 0xc6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0x20, +0x45, 0x7b, 0xa3, 0xcb, 0x90, 0x7b, 0x0c, 0x6f, +0x33, 0x9e, 0xe8, 0xa2, 0x4b, 0x47, 0x86, 0xd5, +0x43, 0x04, 0xa9, 0xf6, 0xe4, 0x93, 0xe7, 0xac, +0x94, 0x86, 0x8b, 0xe4, 0x4a, 0x13, 0x45, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0xcb, 0xc2, 0x44, 0xa7, 0x66, 0xae, 0x87, +0x69, 0x62, 0x60, 0xf0, 0x53, 0x47, 0x7f, 0xc3, +0xe2, 0x4b, 0x98, 0xdc, 0x08, 0xa5, 0xc1, 0x5a, +0x4b, 0x9f, 0xda, 0x07, 0x57, 0x22, 0xe2, 0x70, +0x95, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0xe3, 0x42, 0xe6, 0x0b, 0xdd, +0x06, 0x01, 0x57, 0xcd, 0x8c, 0x63, 0x45, 0xe5, +0x5e, 0x43, 0x6b, 0x15, 0xe3, 0x7e, 0x12, 0xc0, +0x57, 0x46, 0x80, 0xbf, 0x46, 0x5d, 0xa4, 0x41, +0xb6, 0x18, 0x64, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0xfc, 0x1a, 0xf8, +0xf0, 0x5e, 0xfc, 0xf3, 0x16, 0x8a, 0xdb, 0x9f, +0x83, 0x15, 0x11, 0x01, 0x50, 0xb0, 0x65, 0x1f, +0x10, 0xe2, 0x4e, 0x42, 0x16, 0x0f, 0xf6, 0x6c, +0x25, 0xd3, 0xb0, 0xb0, 0x7a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0x1b, +0xaf, 0x0c, 0x45, 0xb2, 0x72, 0x7b, 0x8d, 0x4f, +0x90, 0xa1, 0x33, 0xcc, 0x4c, 0x29, 0x1f, 0x57, +0xc5, 0x21, 0xf7, 0x39, 0x3d, 0x15, 0xd0, 0x50, +0x8b, 0x91, 0xf9, 0xe5, 0x03, 0xf9, 0x8f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0xc8, 0x01, 0x0c, 0x64, 0xb8, 0x27, 0x58, +0x28, 0x1c, 0x69, 0x22, 0xbd, 0x66, 0xd3, 0x64, +0x28, 0x5d, 0x0b, 0x53, 0x94, 0x16, 0x39, 0x23, +0x5e, 0xae, 0xc2, 0xba, 0xf8, 0x7a, 0x53, 0x09, +0x32, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0x7b, 0xc6, 0xb2, 0xe2, 0x78, +0xe0, 0xd6, 0x13, 0x05, 0xe1, 0x8b, 0x78, 0x77, +0x97, 0x67, 0x62, 0x88, 0x6e, 0x05, 0x7d, 0xbd, +0x1b, 0xab, 0x44, 0x3e, 0x1a, 0xf2, 0x71, 0x27, +0x67, 0x6d, 0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0xa4, 0xf2, 0xb3, +0xca, 0x33, 0xc4, 0xaf, 0x46, 0x57, 0xe0, 0xcd, +0xec, 0xdf, 0xe7, 0x1b, 0x9d, 0x2a, 0x47, 0x9d, +0x23, 0xf8, 0x19, 0x6d, 0x68, 0x12, 0x4c, 0x45, +0x91, 0xc2, 0xde, 0x01, 0x1a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0x0a, +0x29, 0xeb, 0x36, 0x27, 0xe8, 0x5a, 0xcb, 0xb5, +0x4b, 0xfb, 0xab, 0xb0, 0x8a, 0xa8, 0x96, 0x44, +0xd8, 0x4f, 0xbc, 0xab, 0xce, 0x98, 0xdb, 0xb4, +0x22, 0x60, 0x2f, 0x0f, 0x06, 0xbf, 0xd9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0x22, 0xa5, 0x52, 0xc7, 0x99, 0x22, 0x5c, +0x1b, 0x8f, 0x11, 0x03, 0xe2, 0xde, 0x84, 0xe0, +0x72, 0xa2, 0x4c, 0xec, 0x48, 0xd4, 0x7d, 0x26, +0x2a, 0xf4, 0x88, 0x6a, 0xf7, 0x34, 0x4d, 0xf2, +0x98, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x2f, 0x0c, 0x53, 0x13, 0xa5, +0x99, 0x01, 0x7e, 0x7e, 0x23, 0x61, 0x9b, 0x99, +0x04, 0x7f, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xe4, 0x49, 0xff, 0x2f, +0xbb, 0x65, 0x95, 0x73, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0x44, 0xf9, 0x1d, +0x74, 0x88, 0x2f, 0xe4, 0x95, 0xeb, 0xcd, 0x2c, +0xcc, 0xbb, 0x19, 0x1f, 0x69, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd4, 0x9d, +0x7d, 0xad, 0x08, 0x4b, 0xf5, 0xb7, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0xc3, +0xf2, 0xc8, 0x2b, 0x4c, 0x6b, 0x80, 0xa5, 0xaf, +0xc0, 0x88, 0xd9, 0x5e, 0xc7, 0x76, 0xfe, 0x8f, +0x0a, 0x3d, 0xa9, 0xf8, 0x08, 0x69, 0x11, 0x31, +0xd1, 0xe5, 0x5d, 0x84, 0xe9, 0xbe, 0x4e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0xeb, 0x61, 0x3e, 0x97, 0xcb, 0xcc, 0x59, +0x20, 0x2d, 0xf9, 0xc9, 0x7b, 0x1d, 0xb6, 0xda, +0x1b, 0xc8, 0x8d, 0x4f, 0xa7, 0x0a, 0x4f, 0x95, +0xd3, 0xf2, 0xeb, 0x5a, 0x43, 0x62, 0xc2, 0x5b, +0x62, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x37, 0xf3, 0xcb, 0xea, 0x1e, +0x97, 0x02, 0x7b, 0xf4, 0xe8, 0x44, 0x18, 0xf1, +0xb0, 0x2c, 0xcc, 0xf6, 0x75, 0x43, 0xf8, 0x05, +0x90, 0x04, 0xe1, 0x0f, 0x26, 0xe5, 0x04, 0xe0, +0x0b, 0x6f, 0x29, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0xe1, 0xfe, 0x18, +0xe5, 0x01, 0xe5, 0x74, 0xbf, 0x0d, 0xb8, 0x9a, +0x03, 0xa1, 0xff, 0xbd, 0xb6, 0x56, 0x37, 0x8a, +0x7f, 0x7e, 0x7f, 0x1b, 0xaa, 0x0f, 0x8b, 0xe9, +0xfb, 0xba, 0x9c, 0x77, 0xcd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0xea, +0x59, 0x9f, 0x09, 0x81, 0x4c, 0x7f, 0xda, 0x8c, +0x83, 0xac, 0xce, 0xc8, 0x45, 0x50, 0x42, 0xfe, +0x44, 0xb7, 0x1b, 0xe7, 0x64, 0x34, 0x29, 0x85, +0x38, 0x4a, 0xa0, 0xae, 0x04, 0xf1, 0x9e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0xda, 0xfb, 0x94, 0xbe, 0x39, 0xe5, 0x3b, +0xdf, 0xde, 0x23, 0x70, 0x64, 0x34, 0x05, 0xa7, +0xef, 0x7e, 0xc6, 0x34, 0x46, 0x63, 0xda, 0x0a, +0xf6, 0x65, 0x53, 0xdd, 0x6f, 0x25, 0x27, 0x25, +0x7c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0x0a, 0xf2, 0xd4, 0x77, 0x49, +0xb6, 0x2d, 0x23, 0x04, 0xda, 0x12, 0xea, 0x6c, +0x81, 0x6b, 0xa8, 0x03, 0x33, 0x2b, 0x2d, 0x82, +0x33, 0xea, 0x98, 0xe8, 0x82, 0x3e, 0x5e, 0x21, +0x82, 0xf6, 0x27, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x18, 0xd8, 0x4c, +0x86, 0x17, 0xba, 0x0f, 0x95, 0x70, 0x57, 0x8d, +0xfb, 0x44, 0x2d, 0xa6, 0x17, 0x77, 0x67, 0x8b, +0x89, 0x3f, 0xc7, 0xbf, 0xaf, 0x11, 0x4c, 0x13, +0x1d, 0x03, 0x99, 0x1d, 0xa6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x50, +0x6a, 0x7d, 0x14, 0xc5, 0x09, 0x8f, 0xb1, 0x1b, +0x87, 0xaa, 0x86, 0x31, 0x7f, 0xdc, 0x67, 0xfb, +0xfb, 0xcb, 0x07, 0xd5, 0xcd, 0x9d, 0xb9, 0xcb, +0xc9, 0xa1, 0xb2, 0x79, 0x5d, 0x09, 0xb1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0x7b, 0x73, 0xea, 0xe4, 0xd3, 0x77, 0xdb, +0x95, 0x38, 0xf9, 0x87, 0xba, 0x61, 0x44, 0x42, +0x27, 0x02, 0x83, 0x99, 0xa9, 0xd8, 0x60, 0xf4, +0x69, 0x7f, 0x1d, 0xb7, 0x7e, 0x11, 0xd1, 0x1c, +0x24, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0xa2, 0xb0, 0x31, 0x62, 0x7c, +0x9f, 0x89, 0x61, 0x5f, 0xf6, 0x9e, 0xcc, 0xef, +0xd9, 0x72, 0x40, 0xe2, 0x33, 0x67, 0xbc, 0x4a, +0xac, 0xb8, 0xea, 0x37, 0x2d, 0x89, 0x97, 0xec, +0xcc, 0xf0, 0x70, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0x3c, 0x17, 0xe8, +0x1a, 0x18, 0xb6, 0x12, 0x31, 0x51, 0x27, 0xf0, +0x92, 0xe1, 0x0e, 0xe9, 0x27, 0x30, 0x98, 0x09, +0x67, 0x04, 0x8a, 0x6b, 0x7c, 0x33, 0x59, 0x98, +0xbe, 0x50, 0xa6, 0x83, 0x5f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0x7e, +0x02, 0x01, 0x8a, 0x8c, 0xa0, 0x02, 0x88, 0x95, +0xa2, 0x12, 0x7e, 0xb6, 0x37, 0x51, 0x80, 0x2d, +0x71, 0x06, 0x70, 0xf2, 0x5a, 0x4c, 0x9a, 0xbe, +0x8a, 0x5f, 0xd7, 0xc0, 0x7d, 0x78, 0x77, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0x30, 0x4d, 0xb0, 0x1d, 0xe7, 0x4f, 0xe6, +0x8e, 0xec, 0xa2, 0x0f, 0x8f, 0xbd, 0x33, 0x5b, +0xc5, 0x6d, 0x9d, 0x9c, 0xe2, 0x8b, 0xa7, 0x5c, +0xbf, 0xc1, 0x25, 0xe3, 0x2e, 0xa2, 0x78, 0x93, +0xdf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0x87, 0x5f, 0xad, 0x62, 0x65, +0xf4, 0x3e, 0xd1, 0xa3, 0x3a, 0xec, 0x80, 0x96, +0xa8, 0x3d, 0x8f, 0xf0, 0x2a, 0x53, 0x1e, 0xe7, +0x54, 0x17, 0x65, 0x37, 0x69, 0x8e, 0x6e, 0x56, +0x3e, 0xc3, 0x4a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0x3d, 0x23, 0x8c, +0x2b, 0x3e, 0x71, 0x23, 0x44, 0x1e, 0xf2, 0xb5, +0xa8, 0x4b, 0x2f, 0x67, 0x90, 0xe7, 0xb0, 0xf7, +0xaa, 0x7b, 0x92, 0x63, 0xed, 0x51, 0xbd, 0xda, +0x88, 0x45, 0xa6, 0xee, 0x3e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0xb9, +0xa2, 0xe3, 0x24, 0xb9, 0xff, 0x45, 0xd3, 0xb7, +0x01, 0x99, 0x95, 0xa7, 0x89, 0x23, 0x60, 0x88, +0xf7, 0x91, 0x8a, 0x0c, 0x8e, 0x69, 0xdc, 0x6a, +0xcc, 0x5c, 0xc5, 0xca, 0xfc, 0xee, 0x78, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0xca, 0x7f, 0x00, 0x17, 0x77, 0xca, 0xa1, +0x4a, 0x08, 0xde, 0xad, 0x9c, 0x43, 0x79, 0x35, +0xb0, 0xc2, 0x64, 0xee, 0xf5, 0x87, 0xdb, 0x97, +0x29, 0xb6, 0x94, 0xe7, 0x6a, 0x3c, 0x01, 0x6e, +0xa5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0x4c, 0xc0, 0x88, 0xbc, 0xdc, +0x65, 0xb6, 0x0b, 0xed, 0xd6, 0xd2, 0x4c, 0x1f, +0x83, 0x4a, 0x9d, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x47, 0xcf, 0xba, 0x36, +0x4c, 0x46, 0xba, 0xdd, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x58, 0xf5, 0x8b, +0x4e, 0xc1, 0x16, 0x20, 0x6f, 0x95, 0xe1, 0x62, +0xff, 0xde, 0x86, 0xc3, 0xd4, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x43, +0xa1, 0x35, 0x47, 0x4d, 0xb9, 0x8c, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0x1f, +0x3b, 0x87, 0x77, 0x3e, 0x07, 0xae, 0x62, 0xff, +0x68, 0xd0, 0x31, 0x89, 0x63, 0x2f, 0x88, 0xac, +0xae, 0x63, 0xfa, 0xb2, 0x3e, 0xe7, 0xe6, 0x24, +0xe1, 0x6a, 0x3c, 0xdb, 0x6c, 0x47, 0xd7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0x77, 0x56, 0xbd, 0x96, 0xb3, 0x9a, 0x17, +0x4a, 0xe3, 0xf2, 0xfc, 0x17, 0x25, 0x80, 0xbb, +0x20, 0x3f, 0x97, 0xf3, 0x33, 0x16, 0x31, 0x56, +0x8f, 0xa1, 0x40, 0xf8, 0xc6, 0x92, 0xd9, 0xd6, +0xbb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0xdd, 0x9f, 0x64, 0x7e, 0x44, +0x34, 0xab, 0x85, 0x6d, 0x19, 0xc0, 0xa4, 0x48, +0xa3, 0x69, 0xce, 0xdd, 0xb2, 0x08, 0x95, 0x50, +0x54, 0x1d, 0x49, 0xec, 0xcc, 0xe6, 0x91, 0xe2, +0x53, 0xa6, 0x5f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x26, 0x65, 0x25, +0xff, 0x76, 0xd2, 0x9b, 0x1c, 0x9d, 0x69, 0xf3, +0x6a, 0x15, 0x2e, 0xe2, 0xf1, 0x87, 0x6b, 0xd1, +0x21, 0x79, 0x10, 0x7b, 0xf4, 0xdc, 0x0e, 0xb2, +0x6b, 0x57, 0xb6, 0x27, 0x0e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x43, +0x3f, 0xcd, 0x74, 0x5f, 0xc8, 0xbe, 0x34, 0x02, +0x88, 0xdf, 0x4f, 0x10, 0x85, 0xac, 0xd3, 0xa6, +0x8d, 0x40, 0x06, 0x2e, 0xcf, 0x53, 0xb2, 0x31, +0xcf, 0xad, 0x09, 0x9d, 0xb2, 0x18, 0x9b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0x0f, 0xcb, 0x73, 0x5f, 0x64, 0x20, 0xe1, +0x8c, 0xde, 0x1b, 0xc1, 0x42, 0x89, 0x1b, 0x3d, +0x19, 0x90, 0x12, 0x9f, 0x16, 0xd8, 0x76, 0x55, +0xf6, 0xd5, 0x88, 0xcd, 0x8d, 0x9b, 0x92, 0x6a, +0xd5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0xd9, 0x54, 0xd6, 0x0c, 0x05, +0x16, 0x00, 0x80, 0x3c, 0x55, 0xf3, 0x96, 0x13, +0xe4, 0xa7, 0xd6, 0xf1, 0x02, 0x8e, 0xc8, 0xc9, +0xf5, 0x98, 0xee, 0xfd, 0x09, 0xbc, 0x7b, 0x19, +0x87, 0x8d, 0x82, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0x54, 0x62, 0x7a, +0x4e, 0x76, 0x7b, 0x93, 0xd6, 0x5e, 0xb8, 0x86, +0xc1, 0x1e, 0xb6, 0xab, 0xcb, 0xad, 0x85, 0xfe, +0x41, 0x5b, 0x11, 0x15, 0xae, 0x09, 0xce, 0x5d, +0x6c, 0x13, 0xfa, 0xcd, 0x2e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x32, +0xd3, 0x0e, 0x7e, 0xae, 0xa2, 0xeb, 0xfe, 0xb6, +0x8f, 0xff, 0xdc, 0x4c, 0x7e, 0x9c, 0x25, 0xd6, +0x62, 0x5b, 0x7a, 0x40, 0x67, 0x99, 0x5a, 0xe8, +0xa9, 0x36, 0xb0, 0x92, 0x0c, 0xf4, 0xf4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0x6f, 0x82, 0x07, 0xfa, 0xbc, 0x7f, 0x3a, +0xb4, 0xb8, 0x5a, 0xb8, 0xa7, 0x26, 0xfa, 0x0a, +0x6a, 0xd7, 0x03, 0xa1, 0x4f, 0x5f, 0x47, 0xcb, +0x93, 0x73, 0x73, 0x17, 0x47, 0x7c, 0xcd, 0xe3, +0x0a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0x99, 0xfa, 0x57, 0xd8, 0x5b, +0x24, 0x48, 0x3f, 0xd8, 0xa6, 0x8d, 0xc7, 0x87, +0x65, 0xd3, 0x82, 0xa1, 0x92, 0xd3, 0x24, 0x19, +0x08, 0x68, 0x5e, 0xa6, 0xc8, 0x63, 0x47, 0xef, +0x2f, 0xbc, 0xa3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0xee, 0x47, 0x02, +0xa7, 0x8e, 0x21, 0x4b, 0x91, 0xc7, 0xa4, 0xed, +0xeb, 0x0a, 0x06, 0x73, 0x7e, 0x53, 0xd2, 0x61, +0x1d, 0x29, 0x69, 0x00, 0xca, 0x6b, 0x02, 0xa7, +0x2f, 0xfc, 0xb4, 0x8c, 0xf8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x73, +0x99, 0x95, 0xee, 0x36, 0x9f, 0xf3, 0x96, 0x81, +0x19, 0x2b, 0x2b, 0x86, 0xca, 0x90, 0xfc, 0x52, +0x4d, 0xb1, 0xa6, 0x09, 0xed, 0x0e, 0xc2, 0xc4, +0x18, 0xcd, 0xf6, 0xb9, 0xb1, 0x93, 0x5e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0xe3, 0x01, 0xd6, 0x3f, 0x56, 0x66, 0x8b, +0x53, 0xa0, 0x94, 0x57, 0x84, 0xce, 0x95, 0x4b, +0x7e, 0x35, 0x59, 0xee, 0x4f, 0xb1, 0xd5, 0xa3, +0xe1, 0xa2, 0xfe, 0x55, 0x1e, 0x31, 0x04, 0x26, +0x20, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0x95, 0xcd, 0xa5, 0xb9, 0x89, +0x02, 0x09, 0xc8, 0x5a, 0xd8, 0x33, 0xf7, 0x2d, +0xa0, 0x21, 0xd8, 0x33, 0xd0, 0xee, 0x9f, 0x49, +0x58, 0x40, 0x57, 0xa5, 0x1b, 0x0a, 0x13, 0xe5, +0xc6, 0xd2, 0x56, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0x63, 0x0c, 0xa3, +0x2f, 0x19, 0xdf, 0x0a, 0x54, 0xbc, 0x2a, 0x96, +0x9f, 0x2e, 0x98, 0xb0, 0x71, 0xd2, 0x52, 0xea, +0x04, 0x6a, 0xd6, 0xf5, 0x01, 0xaf, 0x88, 0x8c, +0xa9, 0xe5, 0xa9, 0x95, 0x07, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0x82, +0xd5, 0x85, 0x91, 0x44, 0x76, 0x68, 0xc4, 0x42, +0xa3, 0xe1, 0x87, 0x64, 0xfd, 0xb9, 0x6e, 0x0d, +0x5b, 0xfa, 0x39, 0x51, 0xca, 0xe2, 0xbf, 0xaa, +0xa3, 0x08, 0x0e, 0x5f, 0xb3, 0x58, 0xb6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0xb1, 0x0c, 0x4e, 0x02, 0x41, 0xe9, 0x17, +0x09, 0x90, 0x4e, 0x7d, 0x04, 0xdc, 0xa7, 0xa8, +0xda, 0x29, 0x32, 0x0c, 0x20, 0x02, 0xe4, 0x91, +0xaa, 0xb1, 0x65, 0xd7, 0x4a, 0x1a, 0x48, 0xce, +0x8c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x7c, 0x9f, 0xc2, 0x9e, 0x4c, +0x7e, 0x82, 0xae, 0xc9, 0x28, 0x91, 0x24, 0x47, +0x1e, 0x56, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x58, 0x83, 0xd7, 0xe5, +0xaf, 0xe3, 0x59, 0x77, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x31, 0x99, 0xf6, +0x55, 0x0a, 0x3b, 0xee, 0x5f, 0xe2, 0xa1, 0xe3, +0x19, 0xe6, 0xe4, 0x9d, 0x0f, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0x3b, +0xa2, 0x8e, 0x7c, 0x50, 0x6c, 0x49, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0xc5, +0xd1, 0x07, 0xd0, 0x5e, 0x9b, 0x4f, 0xc7, 0xa9, +0x6e, 0x70, 0x85, 0x3c, 0xce, 0xff, 0xc2, 0xe5, +0xfb, 0x3d, 0xa6, 0x71, 0x15, 0x48, 0x3b, 0x6c, +0xf6, 0x7f, 0x04, 0x7e, 0x0e, 0x26, 0xc1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0x24, 0x90, 0xb0, 0x70, 0xba, 0x5b, 0x27, +0x67, 0xdd, 0xe1, 0xb8, 0x73, 0x2b, 0xdc, 0xbf, +0xef, 0x74, 0x4b, 0x01, 0xb1, 0x42, 0xeb, 0x47, +0xaa, 0x70, 0x2b, 0xc7, 0x2f, 0x30, 0xe9, 0x06, +0xc6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0xc2, 0x23, 0x11, 0x73, 0x81, +0x01, 0x05, 0xfc, 0xc2, 0x96, 0x3e, 0xc3, 0x58, +0x63, 0x96, 0x6e, 0xfe, 0x0c, 0x11, 0x88, 0x01, +0x63, 0x04, 0xef, 0x8e, 0x0f, 0x00, 0x8c, 0x50, +0x78, 0x23, 0x39, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0xab, 0xa9, 0x49, +0xb3, 0xcb, 0xae, 0xe5, 0x2c, 0x37, 0x37, 0xaf, +0xd8, 0x41, 0xcb, 0x7a, 0xa9, 0x3e, 0x3e, 0x9f, +0xf8, 0x57, 0x87, 0xe8, 0x87, 0x61, 0x21, 0x1d, +0x28, 0xe9, 0xcb, 0xe9, 0xfb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0xa6, +0xce, 0xbc, 0x90, 0x5c, 0x6b, 0x4c, 0x2a, 0xa0, +0xc6, 0xdb, 0x4a, 0x0a, 0x9e, 0x9c, 0x56, 0xfa, +0xd3, 0x51, 0x0f, 0x35, 0x2c, 0x88, 0x10, 0xb3, +0x4f, 0x7d, 0x56, 0x64, 0x23, 0x10, 0x4f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0xc6, 0x88, 0xcd, 0x1f, 0x3a, 0xda, 0x39, +0x95, 0x1d, 0xef, 0x3a, 0x9d, 0xf7, 0x43, 0x7c, +0x78, 0x95, 0x86, 0x51, 0x6b, 0xca, 0x46, 0x3d, +0xef, 0xbe, 0xcc, 0xa1, 0x34, 0x96, 0xbb, 0x28, +0xea, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0xd1, 0xf7, 0xb3, 0x93, 0x60, +0x5d, 0x7e, 0x52, 0xae, 0x75, 0x2d, 0x5e, 0x3a, +0x63, 0xa7, 0x59, 0xea, 0xb1, 0xce, 0x66, 0xf3, +0x7e, 0x5d, 0x98, 0x81, 0xda, 0x82, 0xc6, 0x0e, +0x82, 0x79, 0xa0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0xef, 0x51, 0x70, +0x54, 0x32, 0xf3, 0x47, 0x25, 0x1e, 0x39, 0x29, +0xa5, 0x1b, 0xa1, 0x1c, 0x0c, 0xe3, 0x96, 0x92, +0xec, 0x8b, 0xa1, 0x66, 0xc9, 0xa7, 0x96, 0xe5, +0x28, 0xc9, 0xf9, 0xb2, 0x41, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x23, +0xac, 0x1d, 0x84, 0xbe, 0xf4, 0xcb, 0x30, 0x08, +0xeb, 0xe7, 0xdb, 0xda, 0x4c, 0xec, 0xbc, 0xf2, +0xbb, 0x1b, 0x8e, 0x14, 0xb5, 0x64, 0xab, 0xd5, +0x4d, 0x50, 0x00, 0x06, 0x6e, 0x48, 0xa4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0x4f, 0x03, 0xbf, 0x9a, 0x3f, 0x41, 0x3e, +0x74, 0xcf, 0xd4, 0x06, 0xa0, 0x18, 0x4a, 0x5f, +0x5d, 0x41, 0x56, 0xfb, 0xdd, 0xac, 0xc3, 0xb1, +0x6f, 0x68, 0x7b, 0x4d, 0x3e, 0x43, 0x87, 0x8c, +0x44, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0xe7, 0xb5, 0x63, 0x62, 0xc3, +0x45, 0xeb, 0x41, 0x34, 0x01, 0x85, 0xae, 0x69, +0xbc, 0xfe, 0x2d, 0xb7, 0x11, 0xb2, 0x2f, 0x65, +0x70, 0xa9, 0x1f, 0xba, 0xbe, 0x3d, 0xc2, 0xac, +0x8a, 0xca, 0x68, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0xea, 0x5b, 0x6f, +0x13, 0xce, 0x22, 0xbb, 0x2c, 0x9f, 0x9a, 0xa8, +0xf5, 0xaf, 0x4b, 0xc3, 0x00, 0x26, 0x32, 0x4c, +0x66, 0xee, 0x66, 0x2b, 0x37, 0xb9, 0xe2, 0xcf, +0xdc, 0xa1, 0xc8, 0x78, 0x4e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0xc3, +0x5f, 0x8a, 0x5f, 0xef, 0xb8, 0x5e, 0x4d, 0xf4, +0x7f, 0x48, 0x7f, 0x37, 0x32, 0x01, 0xd5, 0xed, +0x8a, 0xc7, 0xc2, 0xce, 0xdf, 0xaa, 0x9a, 0xfc, +0xe1, 0x16, 0xfb, 0xc3, 0xf4, 0xeb, 0xb5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0x33, 0x06, 0xf5, 0x4d, 0xc5, 0x41, 0xbc, +0x2d, 0x69, 0xea, 0x1a, 0x6c, 0x0d, 0x58, 0x75, +0xaa, 0x8c, 0x9b, 0xb8, 0x0a, 0xb4, 0xbc, 0xd7, +0x14, 0xa6, 0x60, 0xbc, 0xe9, 0x2e, 0x6c, 0xa6, +0x91, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x1b, 0x44, 0x66, 0x6c, 0x2e, +0xbb, 0x18, 0x8a, 0x22, 0x35, 0x7c, 0x15, 0x43, +0x1b, 0x89, 0xfe, 0x4a, 0x15, 0x38, 0xc9, 0x19, +0x1c, 0x75, 0x7e, 0x29, 0x85, 0x99, 0x78, 0xe6, +0xda, 0x2f, 0x66, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0x37, 0x90, 0x97, +0x0e, 0x2b, 0xfe, 0x1c, 0xf2, 0x74, 0xd5, 0x04, +0x78, 0x9c, 0x45, 0x73, 0xad, 0x92, 0x3c, 0x06, +0x6b, 0x13, 0x24, 0xdf, 0xdd, 0xa7, 0x37, 0x88, +0x35, 0xf5, 0x66, 0xa1, 0x69, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0x36, +0x32, 0x75, 0x28, 0xf6, 0x0f, 0xa1, 0x5e, 0x54, +0x67, 0x4a, 0x84, 0xff, 0x18, 0x06, 0x9a, 0x8e, +0x30, 0x37, 0xc1, 0x89, 0x0e, 0x59, 0x58, 0x3c, +0xe4, 0x3f, 0x59, 0x35, 0x23, 0x18, 0xcd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0x1e, 0xf9, 0xb7, 0x63, 0x8c, 0x0a, 0x93, +0x3f, 0xae, 0x2f, 0x9a, 0xa2, 0x62, 0x9b, 0x9b, +0x71, 0x82, 0xf0, 0x6b, 0xc9, 0x5f, 0x4f, 0x82, +0x3b, 0x63, 0x75, 0xf3, 0x10, 0xca, 0x50, 0x8e, +0x60, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0x9a, 0x4c, 0x7b, 0x04, 0x1d, +0x47, 0x22, 0x1e, 0xd6, 0x24, 0x40, 0x38, 0xae, +0x9a, 0x9d, 0xdb, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xa3, 0x95, 0x7b, 0x55, +0x62, 0xc5, 0xe8, 0x19, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0x38, 0x1e, 0x51, +0xec, 0x35, 0x40, 0xd7, 0xbe, 0x32, 0x03, 0x02, +0xeb, 0xcc, 0xdc, 0x93, 0x89, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0xb5, +0xf5, 0x7b, 0x5a, 0x95, 0x12, 0xe6, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0x1b, +0x07, 0xbb, 0x8b, 0xa0, 0x91, 0x34, 0x22, 0x2e, +0x2d, 0x53, 0x3f, 0xbb, 0xfa, 0xc6, 0x7a, 0x0f, +0xd9, 0x1e, 0xb3, 0x6b, 0xf9, 0x44, 0x57, 0x6e, +0x1d, 0x23, 0x11, 0x39, 0x5c, 0xea, 0x2e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0x01, 0x65, 0x3e, 0x1b, 0x18, 0xf0, 0x60, +0xcc, 0x46, 0xf6, 0x81, 0x4a, 0x71, 0xaf, 0x9e, +0x54, 0xc0, 0xf8, 0x57, 0xb7, 0x0d, 0xf2, 0x5f, +0x83, 0x2f, 0xdb, 0x39, 0x37, 0x31, 0x04, 0x81, +0x1c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0x5f, 0x00, 0x5c, 0x11, 0x15, +0x89, 0x80, 0x22, 0xa5, 0x7b, 0xa9, 0x38, 0x25, +0x0d, 0xaa, 0x37, 0xb1, 0x1b, 0xcb, 0xb4, 0x5c, +0xa5, 0x1c, 0xf6, 0xbd, 0x7d, 0x77, 0xe3, 0xa1, +0x2a, 0x8b, 0x2c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0x35, 0x12, 0x51, +0x68, 0xba, 0x0d, 0x2b, 0x10, 0x1e, 0x6b, 0xf0, +0xb6, 0xc8, 0x5a, 0x6c, 0xc2, 0x07, 0xb0, 0xad, +0x59, 0x4b, 0xf1, 0x59, 0xe8, 0x96, 0xac, 0xc4, +0x64, 0x92, 0xac, 0x3d, 0x2f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0x39, +0x38, 0xb3, 0x82, 0xb9, 0xa1, 0x94, 0x0f, 0x4d, +0xd9, 0x6d, 0xe8, 0xc9, 0x6f, 0x98, 0x46, 0xa7, +0x01, 0xe0, 0xbb, 0x79, 0x56, 0xbd, 0x58, 0x6a, +0xfd, 0x60, 0x5c, 0xdb, 0xb3, 0x90, 0x73, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0xb2, 0x69, 0x4e, 0x64, 0x40, 0x10, 0x05, +0x4c, 0x06, 0x23, 0xd4, 0x2b, 0x7a, 0x74, 0x07, +0x0d, 0x25, 0xa0, 0xff, 0xab, 0xe4, 0x69, 0x90, +0x34, 0x6e, 0xcc, 0x36, 0x49, 0x7d, 0xde, 0x17, +0x37, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0xde, 0xfe, 0x1a, 0x86, 0xd7, +0x60, 0xa9, 0x84, 0x4b, 0x5b, 0xcc, 0x83, 0xa4, +0x48, 0x98, 0x14, 0x91, 0x59, 0xc6, 0xf7, 0xbe, +0xfb, 0x5e, 0x3e, 0x8b, 0xa2, 0x95, 0x02, 0x61, +0x7a, 0x0e, 0xa4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0x02, 0x47, 0xb2, +0x21, 0x83, 0x20, 0x55, 0x8f, 0xee, 0xb0, 0xb5, +0xac, 0x46, 0x23, 0xf6, 0x1f, 0x95, 0x4c, 0xcf, +0xc8, 0xa5, 0x7d, 0x6f, 0x63, 0xb2, 0x5e, 0xba, +0x8a, 0x2d, 0x90, 0x39, 0x13, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0xfe, +0xed, 0x2b, 0x8a, 0x15, 0x31, 0xb9, 0xb4, 0x3a, +0x80, 0x71, 0xc2, 0xfa, 0x93, 0x03, 0xba, 0x5d, +0x22, 0xb6, 0x1f, 0x27, 0xf9, 0x64, 0xe5, 0x43, +0x78, 0x26, 0x34, 0x16, 0xb4, 0x59, 0xe5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0xa8, 0xc5, 0xa3, 0xfe, 0xfa, 0xaa, 0x61, +0x33, 0x02, 0x53, 0x60, 0x0f, 0x83, 0x31, 0xde, +0x02, 0xc3, 0xbf, 0xe8, 0x05, 0x4b, 0x0e, 0x91, +0x99, 0xdf, 0xa6, 0x34, 0x35, 0x0b, 0xcc, 0x05, +0x3e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0xef, 0x84, 0x6f, 0x05, 0x39, +0x21, 0xee, 0xab, 0xe2, 0xc2, 0xb8, 0xa2, 0x68, +0x41, 0x5f, 0xeb, 0x6e, 0x11, 0xe5, 0x46, 0x8a, +0xe1, 0x31, 0xed, 0x05, 0x49, 0xbc, 0xfc, 0xb5, +0xec, 0xcd, 0xcc, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x66, 0x68, 0xa1, +0x35, 0xb2, 0xcd, 0x73, 0x73, 0xa0, 0x46, 0x54, +0x80, 0x00, 0x70, 0x16, 0x33, 0xec, 0x0b, 0x71, +0x54, 0xe8, 0x60, 0x55, 0x10, 0x01, 0x91, 0x3b, +0x3c, 0x68, 0x52, 0x24, 0x66, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0x86, +0x2a, 0xc5, 0x0c, 0xff, 0x53, 0x49, 0xb2, 0xde, +0xb5, 0xe5, 0xb1, 0x5e, 0x34, 0x60, 0x8d, 0xce, +0x70, 0xe9, 0xef, 0xeb, 0xa7, 0x03, 0xd3, 0x67, +0x3e, 0xb7, 0x51, 0x98, 0x0e, 0x43, 0x20, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0x92, 0x55, 0x2d, 0x11, 0x59, 0xb2, 0x07, +0xaf, 0x63, 0x3b, 0x30, 0x92, 0x92, 0x18, 0x42, +0x71, 0xd2, 0x98, 0xd8, 0x2d, 0xd4, 0xbe, 0xe4, +0x66, 0xfb, 0xce, 0x4c, 0xc2, 0x4f, 0xbc, 0xb5, +0x45, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0x10, 0xe1, 0x51, 0x3a, 0x7d, +0x20, 0x04, 0x2d, 0x16, 0x3b, 0xbb, 0x1a, 0x8c, +0x65, 0x93, 0xa2, 0xc5, 0xca, 0x91, 0xdb, 0x9d, +0x7a, 0x99, 0x0e, 0xcb, 0xde, 0xa9, 0x7c, 0x9f, +0x13, 0x4b, 0x1e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x51, 0x87, 0xb7, +0x27, 0xa4, 0x8e, 0x33, 0xf8, 0xd5, 0xa3, 0xa1, +0xa8, 0x5a, 0x0f, 0xa0, 0xcc, 0xee, 0xeb, 0x77, +0x09, 0x15, 0x57, 0xd9, 0x79, 0xc0, 0x95, 0x69, +0x03, 0x7e, 0xa8, 0x8a, 0x3a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0x51, +0xa3, 0x0d, 0x3f, 0x5f, 0xdb, 0xa7, 0x4c, 0xda, +0x33, 0xed, 0x01, 0x0b, 0x76, 0x3b, 0xb4, 0xd3, +0x6e, 0xa3, 0x77, 0xb9, 0xa6, 0x9b, 0xe4, 0x26, +0x7c, 0x84, 0x95, 0x26, 0x23, 0x81, 0x47, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0xd5, 0x58, 0xc6, 0xea, 0x5e, 0xdc, 0x79, +0xb5, 0x39, 0xca, 0xb6, 0xf7, 0xa5, 0x24, 0xdf, +0x56, 0x47, 0x73, 0x01, 0x96, 0x25, 0x84, 0x5b, +0x4b, 0x6e, 0x61, 0xfb, 0x4a, 0xa4, 0xbc, 0x53, +0x8e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0xca, 0x87, 0xdc, 0x9e, 0xfe, +0x6a, 0xaa, 0xcc, 0x3b, 0x22, 0xeb, 0x6b, 0x50, +0xbf, 0xcc, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xbf, 0x0a, 0xf4, 0x67, +0xe1, 0x45, 0x99, 0x34, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0xff, 0x9d, 0xce, +0xda, 0xf5, 0x63, 0xb7, 0xd3, 0xe3, 0x0f, 0xd5, +0x37, 0x93, 0x5f, 0x68, 0x6a, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x1e, +0x73, 0x21, 0x36, 0xae, 0x36, 0x0a, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0xbf, +0xf5, 0xdc, 0x3e, 0xb8, 0x27, 0x4e, 0x7c, 0x60, +0x77, 0x62, 0x8e, 0x8c, 0xed, 0xe3, 0x6e, 0x16, +0xf3, 0xa1, 0x86, 0x95, 0xe5, 0x20, 0x52, 0xa2, +0xa5, 0x97, 0xac, 0x7d, 0x7b, 0x9f, 0xcb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0x06, 0xf7, 0x94, 0x71, 0x89, 0x0a, 0x1a, +0x1e, 0x98, 0x1b, 0x3a, 0xd6, 0x33, 0x4e, 0xe0, +0x3a, 0x7b, 0x4a, 0xf0, 0x21, 0x0c, 0x7e, 0xb6, +0x6d, 0xf9, 0x9d, 0x1d, 0x2c, 0x47, 0x98, 0xa6, +0xf5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0xeb, 0x25, 0xf6, 0x97, 0xe8, +0xcf, 0x8b, 0xb3, 0x05, 0xcc, 0x31, 0x06, 0xab, +0x46, 0xde, 0x31, 0xa2, 0xc6, 0x65, 0x3d, 0x11, +0xcf, 0x3e, 0x07, 0xb4, 0xee, 0xa9, 0x44, 0x14, +0x5d, 0xdd, 0x6f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x14, 0x10, 0x98, +0x87, 0x27, 0xd1, 0x32, 0x25, 0xec, 0xb2, 0x1e, +0x64, 0x49, 0x1e, 0x6a, 0x96, 0xf7, 0xff, 0xb5, +0xca, 0x5e, 0x47, 0xc0, 0x76, 0xb5, 0xaf, 0xc1, +0xd2, 0x77, 0x73, 0x2a, 0x65, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x1e, +0x44, 0x71, 0x2c, 0x4e, 0xaa, 0xbb, 0x7a, 0x3c, +0x14, 0x61, 0x25, 0x93, 0x7b, 0x13, 0x85, 0x42, +0x8d, 0x4f, 0x92, 0x96, 0x03, 0xf0, 0x35, 0x7d, +0xda, 0x6f, 0x05, 0x0d, 0x94, 0xce, 0x3c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0x3c, 0x2d, 0x63, 0x97, 0xe3, 0xb3, 0xe7, +0x32, 0x91, 0xe9, 0x91, 0x14, 0x84, 0x10, 0x17, +0xb1, 0xa0, 0xb2, 0x9c, 0xde, 0x9d, 0x40, 0xff, +0x57, 0xc0, 0xc0, 0xf1, 0x9c, 0xd7, 0x32, 0xf7, +0xba, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0xdd, 0xa4, 0x4e, 0xf3, 0xb1, +0x32, 0x55, 0xaf, 0xd1, 0x11, 0xf8, 0xf9, 0x7f, +0x25, 0xc1, 0x88, 0x91, 0x33, 0x6d, 0x2e, 0xd3, +0xe5, 0xea, 0x94, 0x06, 0x36, 0x0c, 0x03, 0x23, +0x99, 0x39, 0xb6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0x17, 0xdd, 0x02, +0xf8, 0x71, 0x49, 0x9c, 0xe4, 0x83, 0xc3, 0x20, +0x50, 0xbc, 0x20, 0x11, 0x9d, 0xcd, 0x4d, 0xe5, +0x34, 0x12, 0xaa, 0xe2, 0x81, 0x59, 0xf0, 0x50, +0x88, 0xa3, 0x75, 0x3d, 0xfe, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0x91, +0xbd, 0x0d, 0xaa, 0xce, 0x95, 0x62, 0xb2, 0x91, +0x63, 0xb9, 0x10, 0xaf, 0x09, 0xf0, 0x42, 0x64, +0x08, 0x36, 0x9b, 0xd5, 0xe4, 0xda, 0x81, 0x51, +0xa1, 0xa1, 0x3d, 0xe8, 0x59, 0x10, 0x2a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x92, 0xf6, 0x25, 0x95, 0x18, 0x59, 0x71, +0x8f, 0xae, 0x36, 0x73, 0x55, 0x0d, 0x0c, 0x61, +0x78, 0xe2, 0xd0, 0xf5, 0x0f, 0x09, 0xc5, 0x09, +0x81, 0x88, 0xc9, 0x10, 0x25, 0x76, 0x30, 0xce, +0x5f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0x86, 0x1c, 0x28, 0x13, 0xa3, +0xe6, 0xe6, 0xce, 0x35, 0x0c, 0x75, 0x9d, 0x06, +0x23, 0x58, 0x92, 0x89, 0xba, 0xdb, 0x0e, 0xf8, +0x36, 0xfa, 0x0d, 0x87, 0x60, 0x06, 0x15, 0xca, +0xf7, 0xd2, 0x7a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0x66, 0x8b, 0x63, +0x18, 0x3a, 0xee, 0xef, 0x36, 0x23, 0xc7, 0x6f, +0x66, 0x97, 0x31, 0x50, 0x14, 0x70, 0xf1, 0x57, +0xfb, 0x1d, 0xc4, 0x31, 0x2d, 0xe9, 0x26, 0xc3, +0xa2, 0x9a, 0xd8, 0x2d, 0x81, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0xaa, +0x32, 0xfe, 0x03, 0x1a, 0x0e, 0xb2, 0x8c, 0x27, +0x51, 0x19, 0x79, 0xb1, 0xe8, 0x7a, 0xdc, 0xb9, +0x30, 0xa9, 0xc3, 0xbd, 0xf0, 0xc3, 0xdd, 0x73, +0x49, 0x0c, 0x5d, 0x5e, 0x3b, 0xad, 0x2a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0xc6, 0x73, 0x1e, 0x00, 0x2a, 0x49, 0x45, +0xa6, 0xe6, 0xf9, 0x80, 0x88, 0x49, 0xf6, 0x2b, +0x71, 0x12, 0xdb, 0x86, 0x15, 0x25, 0x72, 0x7b, +0xa4, 0x78, 0x95, 0x38, 0x9b, 0x86, 0x04, 0x14, +0x15, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0x9b, 0x3f, 0xe1, 0xca, 0xe4, +0xd8, 0xdb, 0x6a, 0xcb, 0xc6, 0x9b, 0x7d, 0x42, +0x45, 0x33, 0xf9, 0x14, 0x00, 0x6b, 0xc1, 0xba, +0x10, 0x54, 0xbd, 0x21, 0xcf, 0x64, 0xcb, 0xb6, +0x3a, 0x7a, 0xa3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0xea, 0xca, 0xf4, +0xc3, 0xe7, 0xea, 0x0d, 0x65, 0x1d, 0xc0, 0xe2, +0x79, 0x0b, 0xcb, 0xf1, 0xd0, 0x02, 0x01, 0x19, +0xfd, 0x68, 0x94, 0x30, 0x2c, 0x75, 0xe7, 0x42, +0xfb, 0x27, 0xcb, 0x7a, 0x28, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0x27, +0xa1, 0x0d, 0xb0, 0x32, 0xd6, 0x72, 0xcc, 0xe3, +0xe1, 0x20, 0x72, 0xb3, 0x59, 0x09, 0x50, 0x4d, +0x1a, 0x91, 0xb6, 0x9a, 0x37, 0x5d, 0xa9, 0x18, +0xfc, 0xc1, 0x57, 0xcc, 0x21, 0x5b, 0xf9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0xf9, 0xb7, 0x98, 0xe8, 0xe9, 0x2a, 0xad, +0x2f, 0x41, 0x48, 0x9a, 0xd6, 0xd7, 0xda, 0x0d, +0x14, 0xb6, 0xce, 0x76, 0x2e, 0x4a, 0x8c, 0x87, +0x85, 0xfa, 0xc2, 0xa8, 0xee, 0x1f, 0xdd, 0x8b, +0xef, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0x00, 0x02, 0x08, 0x03, 0x04, +0x50, 0x4a, 0xe8, 0x7c, 0x8f, 0x76, 0x7d, 0x58, +0x10, 0x18, 0x89, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xf4, 0x25, 0x05, 0x49, +0x1b, 0x7f, 0x3b, 0xda, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0xbe, 0x87, 0x75, +0xef, 0xfc, 0x16, 0x09, 0xcf, 0xc9, 0xcd, 0x0d, +0x0e, 0x72, 0x69, 0xe0, 0x4a, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x42, +0x40, 0xaf, 0xb3, 0x68, 0xae, 0x30, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0x9d, +0x8e, 0xd1, 0x50, 0x7e, 0x34, 0xd5, 0x67, 0xd0, +0x76, 0xb8, 0x93, 0x72, 0x5c, 0x4d, 0x92, 0xf0, +0x36, 0x0d, 0x9d, 0xd5, 0xe6, 0x28, 0xb3, 0x89, +0x0c, 0x6d, 0x41, 0xf1, 0x33, 0x1a, 0xdd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0x0d, 0x8d, 0x6b, 0x37, 0x4e, 0x9b, 0x9b, +0xe9, 0x7d, 0xfa, 0xc2, 0x61, 0x9e, 0x95, 0x20, +0xf1, 0x11, 0x2a, 0xeb, 0xf0, 0x9e, 0xdc, 0xba, +0x9b, 0xaf, 0xde, 0x65, 0x34, 0xa8, 0x65, 0xb5, +0xcb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0x12, 0x66, 0x85, 0xea, 0xfb, +0x2a, 0xc8, 0x22, 0x82, 0xaf, 0x58, 0xbb, 0xe4, +0xff, 0xba, 0x85, 0x6b, 0xd7, 0xda, 0x54, 0x40, +0x3a, 0xbf, 0xce, 0xd8, 0xdc, 0x0b, 0x9e, 0xaa, +0x5a, 0x11, 0xbe, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0xc2, 0x4e, 0x63, +0xf0, 0x0f, 0x69, 0xd6, 0xa2, 0xff, 0x02, 0x18, +0x96, 0x71, 0xab, 0x31, 0xc7, 0xb4, 0xb8, 0xdf, +0xbc, 0xd9, 0x40, 0x5a, 0x8f, 0xf2, 0x63, 0x65, +0xc8, 0xcb, 0x4e, 0xb2, 0xce, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0x37, +0xb3, 0x65, 0xf1, 0xeb, 0xb8, 0xf5, 0x64, 0xa4, +0x64, 0x6e, 0xdc, 0x19, 0xb7, 0x3f, 0x67, 0x8e, +0xb8, 0x96, 0x03, 0xb0, 0x76, 0x39, 0xd3, 0x14, +0x0b, 0x0c, 0x7c, 0xad, 0x91, 0x7f, 0xd9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0xe6, 0x60, 0x50, 0x2c, 0xb5, 0xc9, 0xa1, +0xf0, 0x83, 0x4a, 0xd3, 0xaa, 0xe7, 0x52, 0x06, +0x3e, 0xfa, 0x26, 0xb3, 0xee, 0x6f, 0x46, 0x4f, +0x34, 0x6b, 0xd9, 0xdf, 0x58, 0x70, 0x6a, 0x64, +0xa7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0x03, 0xa0, 0xe7, 0x59, 0x73, +0x8b, 0x67, 0xb3, 0xc1, 0x29, 0x86, 0x30, 0x85, +0xc4, 0x0b, 0x3b, 0xb4, 0xd3, 0x3e, 0xb1, 0x78, +0x88, 0x4d, 0x95, 0xe2, 0x6a, 0x34, 0x35, 0x00, +0x6f, 0x47, 0x0c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x37, 0x26, 0x01, +0x9d, 0x36, 0x52, 0x3d, 0x9e, 0x87, 0x91, 0x9e, +0xf2, 0xbb, 0xd2, 0x55, 0xcc, 0x41, 0x62, 0x19, +0xac, 0x75, 0x59, 0xaf, 0xcf, 0x3b, 0xe0, 0xb1, +0xd5, 0x55, 0xfb, 0x2a, 0x7d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0x7a, +0x6a, 0xdf, 0x9f, 0x38, 0xc2, 0x13, 0xe6, 0xe5, +0xa5, 0x38, 0x17, 0xec, 0x5e, 0x1e, 0x17, 0xf8, +0xf2, 0xda, 0xa5, 0x02, 0xd0, 0x41, 0x4e, 0x35, +0x42, 0x43, 0x28, 0xba, 0x6b, 0x86, 0x6a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0x71, 0x41, 0xc6, 0x83, 0x76, 0x53, 0xfa, +0x10, 0x2a, 0xfc, 0x7e, 0xce, 0xf2, 0xeb, 0x4a, +0x0f, 0xef, 0x28, 0x56, 0x6a, 0x50, 0x41, 0x55, +0x5e, 0x43, 0x86, 0xfc, 0xf2, 0x9d, 0x76, 0xb7, +0x0f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0x10, 0x60, 0xf7, 0x70, 0x45, +0x4a, 0xaa, 0x62, 0x12, 0x56, 0x9b, 0xbf, 0xfb, +0x59, 0x94, 0x44, 0x84, 0x1e, 0x34, 0x04, 0xe3, +0xb9, 0x7b, 0xb0, 0x87, 0xe4, 0xfd, 0x6b, 0x3d, +0xa8, 0x37, 0x97, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0x20, 0xc6, 0xc1, +0x69, 0x5b, 0xcd, 0xaf, 0xb7, 0x87, 0xf2, 0xe0, +0xe7, 0xa5, 0xbc, 0x33, 0x3d, 0x76, 0x61, 0xf6, +0x40, 0xce, 0x78, 0x37, 0x36, 0xfa, 0x85, 0x22, +0x97, 0x28, 0x32, 0xe8, 0xef, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0xe7, +0xcd, 0x3f, 0xb7, 0x57, 0x75, 0xdc, 0xde, 0x07, +0x88, 0x16, 0xf6, 0x91, 0x14, 0x68, 0x18, 0x44, +0xd7, 0x87, 0x8f, 0x5b, 0x0a, 0xfd, 0x66, 0xf8, +0xc3, 0xf8, 0xc7, 0xb8, 0x11, 0x6b, 0x51, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0x73, 0xc1, 0x31, 0xb2, 0x98, 0x25, 0x72, +0x80, 0x58, 0x4d, 0xbf, 0xa9, 0xcb, 0x9b, 0xca, +0xc8, 0x9f, 0x8e, 0xa0, 0x5a, 0x56, 0x11, 0xb1, +0x30, 0x25, 0xa3, 0xf2, 0x5c, 0x12, 0x91, 0xaf, +0x82, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x6d, 0x25, 0xd7, 0x41, 0x14, +0x4b, 0x52, 0x37, 0xe8, 0xe4, 0xfc, 0xd0, 0x2a, +0x72, 0x18, 0x9a, 0x9d, 0xb3, 0x1c, 0xb2, 0x06, +0x3b, 0xf7, 0xc0, 0xcd, 0x02, 0xdb, 0xec, 0x35, +0x08, 0xcb, 0x6a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0x2e, 0x81, 0x0d, +0x1e, 0x83, 0xdd, 0xee, 0x13, 0xdf, 0xef, 0x1b, +0x8f, 0xc0, 0xd5, 0x2f, 0xcf, 0xed, 0x92, 0x66, +0x9c, 0xc9, 0x18, 0x82, 0x1b, 0xd0, 0x46, 0x83, +0x3d, 0xc2, 0xeb, 0x30, 0x55, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0xd9, +0x2b, 0x85, 0xdf, 0x86, 0x43, 0x9a, 0x10, 0x1c, +0x99, 0xd6, 0xdc, 0xd4, 0x73, 0x31, 0x93, 0xc0, +0x33, 0x78, 0xbf, 0x6c, 0x81, 0x56, 0xb3, 0x89, +0x41, 0x7e, 0x25, 0x40, 0x84, 0xc9, 0x97, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0xf5, 0x9d, 0x72, 0x69, 0x93, 0xdd, 0x1e, +0xdc, 0x94, 0x54, 0x2a, 0xdc, 0x8f, 0xac, 0x54, +0x35, 0x2f, 0xd0, 0x23, 0x7a, 0x41, 0x98, 0xd0, +0xea, 0xaa, 0xfd, 0xda, 0xc4, 0x60, 0x07, 0xa1, +0x22, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0x9a, 0x87, 0x26, 0x92, 0x8c, +0x48, 0x2c, 0x4c, 0x28, 0x89, 0xa6, 0x5b, 0xb3, +0xbc, 0xf2, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x74, 0x62, 0x63, 0x8c, +0x68, 0xbb, 0x25, 0xab, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0x87, 0x86, 0xa8, +0x28, 0x94, 0xcb, 0x0a, 0xf2, 0xc9, 0x9c, 0x34, +0xc2, 0x3a, 0xc0, 0xc9, 0x7d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0x22, +0xba, 0xad, 0x76, 0xe4, 0xb9, 0xc0, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0x0e, +0x05, 0xcb, 0x2e, 0x41, 0x7b, 0xe1, 0x97, 0xb4, +0x5d, 0x1d, 0x1a, 0x9d, 0xd6, 0x2e, 0xda, 0x7a, +0x09, 0x15, 0xad, 0x5b, 0x6d, 0x7f, 0x74, 0x77, +0x8a, 0xaf, 0x1b, 0xc7, 0x5b, 0x6e, 0x6d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x0a, 0xeb, 0xce, 0xe6, 0x34, 0x5a, 0xae, +0xf4, 0xa4, 0xc8, 0xb6, 0xcd, 0xc7, 0x11, 0x80, +0xf0, 0x08, 0xc1, 0x9f, 0x86, 0xaa, 0xc3, 0x60, +0x81, 0xbc, 0x87, 0xc3, 0xd7, 0x2f, 0x04, 0x4e, +0xe5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0x71, 0x0f, 0x9f, 0xb6, 0xc1, +0x47, 0x27, 0xca, 0x8d, 0x9e, 0xda, 0xbe, 0x9f, +0xed, 0x2f, 0xbd, 0x70, 0xf3, 0x0e, 0xe6, 0x33, +0xfe, 0xc3, 0x84, 0x3f, 0x9a, 0x22, 0x72, 0x48, +0x1b, 0x5e, 0x7a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0x34, 0xbb, 0x53, +0xa6, 0xb9, 0x3d, 0xd7, 0x92, 0xa0, 0xc9, 0x34, +0x9a, 0xf9, 0x6d, 0xb7, 0xf0, 0x06, 0xa7, 0xc8, +0x68, 0x3e, 0xf7, 0xff, 0xb1, 0x25, 0xa1, 0xac, +0x95, 0x05, 0x2f, 0xda, 0x47, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0x67, +0xa4, 0x76, 0x8d, 0x9d, 0xec, 0x31, 0x1a, 0x42, +0x9e, 0x4b, 0xc4, 0x8a, 0x09, 0x14, 0x20, 0x7f, +0xe0, 0x6e, 0xf1, 0xd4, 0x82, 0x72, 0x94, 0x52, +0x72, 0x2e, 0xb7, 0x97, 0x79, 0x0a, 0x51, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0x7d, 0xf9, 0x43, 0xc2, 0xa4, 0xc6, 0x84, +0x3f, 0x80, 0x7e, 0xaf, 0xf1, 0xe3, 0xde, 0x3d, +0x5d, 0x1a, 0x60, 0x51, 0x8d, 0xa7, 0xfb, 0xfa, +0xfb, 0x63, 0x8e, 0xbd, 0xfc, 0x39, 0x07, 0x74, +0x93, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0x9f, 0xcb, 0x04, 0x08, 0x39, +0x21, 0x1a, 0x8d, 0x2d, 0x3c, 0x90, 0x05, 0x1f, +0x00, 0x96, 0x8f, 0x61, 0xdf, 0xcd, 0x79, 0x20, +0x36, 0xe8, 0xa4, 0x7f, 0xd9, 0xa1, 0x8c, 0x43, +0x65, 0x0a, 0x22, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0xc2, 0xf3, 0x1b, +0xfd, 0xd1, 0x9e, 0x33, 0x27, 0x9f, 0xa5, 0xa2, +0x0b, 0x3f, 0x1f, 0x20, 0x49, 0x53, 0x00, 0xfe, +0xe0, 0xc7, 0xca, 0xfb, 0x85, 0x77, 0x91, 0x13, +0x8e, 0x45, 0xaf, 0x39, 0x75, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0xe6, +0xee, 0x0f, 0x8d, 0x86, 0xb7, 0xad, 0x3f, 0x13, +0xd3, 0x91, 0x11, 0xf2, 0x7d, 0xf1, 0x15, 0x63, +0x4b, 0xfb, 0xdf, 0xff, 0x67, 0x97, 0x93, 0xa9, +0xa3, 0x87, 0xdb, 0x67, 0xf2, 0x75, 0x84, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0xe1, 0x50, 0x7e, 0xcd, 0x89, 0x34, 0xd7, +0xc4, 0x2e, 0x9a, 0x9c, 0xb8, 0xe3, 0xb4, 0x75, +0xa1, 0x34, 0xf8, 0xb4, 0xc7, 0xfb, 0x86, 0xbb, +0x9d, 0xa1, 0xb8, 0xd9, 0xfd, 0x9d, 0x78, 0xd1, +0x65, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0x4f, 0x9a, 0x08, 0xe9, 0xb2, +0x40, 0x2a, 0xa4, 0xf7, 0xe5, 0xa0, 0xe4, 0xa4, +0xaf, 0xc5, 0x70, 0x07, 0xd2, 0x03, 0xb7, 0xf3, +0xee, 0x81, 0x99, 0x88, 0x6a, 0xd7, 0x5e, 0x22, +0xb2, 0x12, 0x9c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0x39, 0x81, 0x3b, +0x5c, 0x27, 0xf3, 0xe2, 0xca, 0x7a, 0x30, 0x73, +0x94, 0x26, 0xcb, 0xc8, 0xf0, 0xa5, 0xc8, 0x22, +0x65, 0x56, 0xee, 0x8a, 0xe5, 0x0f, 0xcd, 0x9c, +0x0a, 0xd7, 0xc2, 0xb5, 0xed, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0xf2, +0xc2, 0xdb, 0x4c, 0x93, 0x87, 0xa3, 0x05, 0xcc, +0xbd, 0x1a, 0xbd, 0xfb, 0x8f, 0x73, 0x1b, 0x43, +0xf3, 0x38, 0x2d, 0xed, 0x9f, 0xc8, 0x70, 0x01, +0x26, 0x97, 0x76, 0x0c, 0xdb, 0x6e, 0x61, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0x8c, 0xfb, 0x04, 0x37, 0x0e, 0x13, 0xb7, +0xdf, 0x18, 0x1f, 0x5b, 0xdd, 0x23, 0x38, 0x11, +0xe8, 0x53, 0xd6, 0xc7, 0xbd, 0x02, 0xb1, 0x09, +0x83, 0x0d, 0xe6, 0xdc, 0x80, 0x0a, 0x9f, 0x1f, +0x7d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0x2f, 0x39, 0xc5, 0xdb, 0x1e, +0x0c, 0xcb, 0xd4, 0x5f, 0xf4, 0x63, 0x13, 0x9c, +0x4d, 0x34, 0xd9, 0xcb, 0x40, 0x97, 0x8b, 0x7e, +0xd7, 0x23, 0x64, 0x50, 0x0f, 0xdd, 0x12, 0x98, +0xd5, 0xd6, 0x35, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0x8b, 0x79, 0x7d, +0x52, 0x6f, 0x42, 0x3e, 0x87, 0x70, 0xb4, 0xd5, +0x41, 0xd1, 0x05, 0xfa, 0x28, 0x4d, 0x2f, 0x13, +0xe8, 0x87, 0x09, 0xea, 0x6e, 0x44, 0xb9, 0xef, +0x41, 0x38, 0x37, 0xc7, 0xbb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0x7d, +0x94, 0x4c, 0xaf, 0x6c, 0xf2, 0x0d, 0xa5, 0xe4, +0x10, 0x32, 0x10, 0x29, 0x85, 0x3b, 0x4d, 0xb9, +0xeb, 0xfc, 0x0e, 0x9c, 0x5b, 0xd3, 0x80, 0xf2, +0xb7, 0x31, 0xbb, 0x23, 0xb2, 0xad, 0x91, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0x46, 0x2c, 0x4f, 0xdc, 0x66, 0x6e, 0xac, +0xb6, 0x95, 0x8f, 0x24, 0x06, 0x09, 0xc7, 0x20, +0xe8, 0x4b, 0x8d, 0x1a, 0xf3, 0xbb, 0xb5, 0x8d, +0xd7, 0x87, 0x72, 0x8e, 0xd0, 0x6e, 0xda, 0x27, +0x33, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0x07, 0x82, 0x91, 0xc7, 0x1b, +0xc6, 0x9d, 0x81, 0x27, 0xc6, 0x81, 0xc6, 0x79, +0x51, 0x48, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x0a, 0xc3, 0x61, 0xd0, +0xdb, 0xbf, 0xf1, 0x3a, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0x4e, 0x40, 0x84, +0xd8, 0x8b, 0x52, 0x1e, 0x05, 0x05, 0xc2, 0x50, +0x33, 0x40, 0xcb, 0xe6, 0x45, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x44, +0x52, 0xee, 0x28, 0xc1, 0x1a, 0xdb, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x38, +0xa6, 0x8c, 0x65, 0xc2, 0xef, 0x0e, 0xe8, 0x47, +0xe0, 0x6a, 0xdb, 0xac, 0x55, 0x5e, 0xe7, 0x8f, +0x8d, 0xe4, 0x97, 0xc7, 0x4c, 0x31, 0x2d, 0xdd, +0xef, 0xd5, 0xa9, 0x71, 0x4a, 0x27, 0xa3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0x4b, 0x6f, 0x44, 0xab, 0x18, 0x9b, 0x03, +0x3b, 0xba, 0x3a, 0xca, 0xc1, 0xd4, 0x38, 0x2f, +0x92, 0x8a, 0x87, 0x82, 0xff, 0xdd, 0xd2, 0xa8, +0xcd, 0x7a, 0xe8, 0x5e, 0x6a, 0x96, 0xc6, 0xee, +0xd5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0x7c, 0x13, 0x60, 0x21, 0x6e, +0xf7, 0x9a, 0xe9, 0x96, 0x0b, 0x29, 0x3b, 0x9b, +0xc2, 0x39, 0x2e, 0xb2, 0x1d, 0x53, 0xff, 0x12, +0x22, 0xac, 0x02, 0xa3, 0x80, 0x45, 0x32, 0x78, +0xd7, 0x82, 0x7f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0x87, 0xdb, 0x67, +0x27, 0x00, 0x94, 0x09, 0x1b, 0x96, 0x73, 0x34, +0xb3, 0xe2, 0x27, 0x97, 0x36, 0xfb, 0xa9, 0x3e, +0x7a, 0x2e, 0x45, 0x13, 0x13, 0xfa, 0x44, 0xc1, +0x73, 0x2a, 0x19, 0x93, 0x35, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0xee, +0x63, 0x3a, 0x86, 0x60, 0x86, 0x01, 0xf0, 0x86, +0xc6, 0x89, 0x7d, 0x7b, 0xca, 0x00, 0x8c, 0x1e, +0x3e, 0x81, 0xb1, 0x0a, 0xcc, 0x5d, 0xbe, 0x4d, +0x7b, 0xe2, 0xee, 0xaa, 0x98, 0xc0, 0x68, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0xe6, 0xf8, 0xce, 0x83, 0x03, 0x29, 0x26, +0xf9, 0x23, 0x1c, 0x57, 0xe7, 0x47, 0x73, 0x64, +0x10, 0x67, 0x44, 0xf1, 0xc9, 0x6b, 0x44, 0x4d, +0x3b, 0xb2, 0x92, 0x7a, 0x86, 0xda, 0x48, 0xb6, +0x58, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0x01, 0xcc, 0x67, 0x15, 0x25, +0xdb, 0xaa, 0xa5, 0x6f, 0xcc, 0xd5, 0xbe, 0x43, +0x62, 0x25, 0x49, 0x23, 0x38, 0x39, 0xa5, 0xdb, +0x5c, 0x61, 0xc9, 0xb7, 0xb2, 0x89, 0x58, 0x5d, +0xf6, 0x00, 0x66, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0xe9, 0x51, 0xbd, +0xbb, 0x83, 0xb6, 0x11, 0xf5, 0x9d, 0xe5, 0xcb, +0x24, 0x10, 0x2e, 0x23, 0x14, 0xf4, 0x38, 0x55, +0xfa, 0x21, 0x33, 0x2a, 0x18, 0x44, 0xf9, 0x80, +0xe5, 0x97, 0x12, 0x3a, 0x4b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0x7c, +0x4f, 0x87, 0x48, 0xf5, 0x56, 0x2c, 0xb9, 0xf9, +0x69, 0xe9, 0xdf, 0x2f, 0xe3, 0x35, 0x85, 0x3f, +0x98, 0xe5, 0x65, 0xa0, 0x8b, 0x60, 0x93, 0x85, +0xdb, 0xc6, 0xc8, 0xf7, 0xda, 0xd6, 0x00, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0x0b, 0x38, 0xbe, 0xdd, 0x6e, 0x3e, 0xe8, +0xe0, 0x1d, 0x59, 0x2a, 0x9c, 0x7e, 0xab, 0xd3, +0x02, 0x3b, 0x50, 0x08, 0xeb, 0xb7, 0x2d, 0x69, +0x41, 0xdb, 0x2e, 0xf3, 0x84, 0x9e, 0x69, 0x4c, +0x97, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0x86, 0x39, 0x3e, 0x51, 0x17, +0x44, 0xad, 0x6f, 0xa4, 0x53, 0x3f, 0xe3, 0xe6, +0xc6, 0x7a, 0xf3, 0x48, 0x69, 0x2e, 0x5a, 0x51, +0x1d, 0x07, 0xb0, 0xae, 0xbf, 0x24, 0x98, 0x5b, +0x28, 0x8c, 0xd4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0xfd, 0x1d, 0xb2, +0xff, 0xaf, 0xaf, 0x23, 0xfb, 0xa9, 0xf4, 0xab, +0x9b, 0x9c, 0xdd, 0x6f, 0x61, 0x3f, 0xa6, 0x1c, +0xa5, 0xff, 0x49, 0x66, 0xa8, 0xa3, 0x55, 0xe1, +0x29, 0x77, 0x86, 0x68, 0xc4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0x68, +0x82, 0x9e, 0xf5, 0xd9, 0x07, 0x9b, 0x6e, 0xc8, +0x5f, 0xa5, 0x46, 0x1c, 0xd4, 0xaf, 0x80, 0x01, +0x91, 0x22, 0x94, 0x53, 0xdf, 0xf9, 0xfa, 0x58, +0x7f, 0x6a, 0xa3, 0x74, 0x79, 0x8a, 0xe7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0xe8, 0x94, 0xb9, 0x48, 0xf4, 0x29, 0x07, +0xc5, 0xa5, 0xc8, 0x5d, 0x06, 0xb6, 0x5f, 0x8f, +0x96, 0x54, 0x98, 0x00, 0xff, 0xdb, 0xd7, 0xca, +0x10, 0xcb, 0xac, 0x22, 0xb3, 0xb7, 0x5d, 0xf6, +0xe1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0x95, 0x81, 0x47, 0xb8, 0xf4, +0x2e, 0x43, 0xb5, 0x9a, 0x2d, 0x03, 0x9d, 0xed, +0x3b, 0x73, 0x36, 0x37, 0x0c, 0xf9, 0xed, 0xab, +0x86, 0x39, 0x9c, 0xff, 0x8b, 0x4c, 0xf6, 0x6e, +0x6a, 0x8b, 0x14, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0x6a, 0x2e, 0x76, +0x32, 0x95, 0x5e, 0xab, 0x2d, 0x6e, 0x0c, 0x06, +0x59, 0x04, 0xb4, 0x31, 0xad, 0x65, 0xe0, 0xa2, +0xb4, 0xf2, 0xc2, 0x2a, 0xfd, 0x90, 0x46, 0xc6, +0x07, 0xa9, 0x7f, 0xa4, 0x43, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0xae, +0x5a, 0xa0, 0xf4, 0xfa, 0xc9, 0xfa, 0x84, 0x91, +0x55, 0x70, 0x39, 0x79, 0xad, 0xbf, 0x3b, 0x8c, +0x1b, 0x05, 0xf3, 0xce, 0xf0, 0xd7, 0x9b, 0xe7, +0x90, 0x16, 0xd1, 0x0c, 0x28, 0xee, 0xa2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0xf4, 0x1f, 0x6b, 0x03, 0xe3, 0x20, 0x51, +0x28, 0xce, 0x32, 0x7c, 0x97, 0xf5, 0x21, 0x81, +0xae, 0x88, 0x52, 0xc0, 0x4b, 0x6f, 0xb5, 0x74, +0x2d, 0xfe, 0x6f, 0xf7, 0xc7, 0x13, 0x7d, 0x0a, +0xe2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0x24, 0xce, 0x11, 0x5c, 0xa9, +0xf8, 0xcf, 0x67, 0x0b, 0x4d, 0x12, 0x7e, 0xa6, +0x85, 0x5f, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x31, 0x91, 0x35, 0xc1, +0x12, 0xcc, 0x90, 0xba, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0xbe, 0x38, 0x71, +0x91, 0xc3, 0x14, 0xb6, 0x00, 0x07, 0xf7, 0x63, +0xe1, 0xf5, 0x9d, 0x54, 0xa3, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xff, +0x58, 0x80, 0x90, 0x68, 0x80, 0x0d, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0x58, +0xa1, 0x87, 0x1e, 0xf5, 0x96, 0x7e, 0x08, 0xec, +0x18, 0x72, 0x20, 0xfe, 0x21, 0x73, 0x19, 0x72, +0x30, 0xf2, 0x0a, 0x1c, 0x15, 0x42, 0xa4, 0x7a, +0x9a, 0x31, 0xec, 0x4d, 0x45, 0x6c, 0xe3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0x8d, 0x94, 0xce, 0x89, 0x58, 0x1c, 0x7a, +0x01, 0x23, 0xf7, 0x1f, 0xbc, 0xf9, 0xc7, 0xd3, +0x98, 0xac, 0xfc, 0x12, 0xf6, 0x5a, 0x56, 0xcb, +0xcb, 0xd1, 0x03, 0xf0, 0x55, 0xac, 0xee, 0x0c, +0x26, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0x47, 0xd2, 0x75, 0xb4, 0x8b, +0x04, 0x1a, 0x23, 0x20, 0x9f, 0x03, 0x21, 0xbe, +0xa4, 0x85, 0x55, 0xbb, 0xb1, 0xc4, 0x81, 0x1a, +0x35, 0x01, 0x83, 0xd5, 0x72, 0x08, 0xcb, 0xbb, +0x9d, 0x0d, 0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0xc8, 0x0d, 0x45, +0x98, 0xed, 0xb7, 0x55, 0xc4, 0x7c, 0x80, 0x86, +0x82, 0x55, 0x61, 0x5e, 0x7e, 0x2b, 0x07, 0xde, +0x15, 0x60, 0xc2, 0x35, 0xb0, 0x67, 0xcf, 0x52, +0x3e, 0x70, 0x6f, 0x4a, 0x23, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0xd4, +0xc6, 0x7b, 0xd0, 0x43, 0x46, 0xb5, 0xbf, 0xd1, +0x9b, 0x34, 0x1d, 0xee, 0x51, 0xf5, 0x08, 0x50, +0xc5, 0x25, 0x64, 0xb4, 0x1c, 0xe6, 0x36, 0xd6, +0x6a, 0xa0, 0xad, 0x7e, 0x22, 0xaa, 0x77, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0x18, 0x42, 0xe9, 0xaa, 0xa0, 0xf5, 0x4f, +0x28, 0x1a, 0xad, 0x93, 0x60, 0x72, 0xf9, 0xf0, +0x52, 0x49, 0xc7, 0xab, 0x5c, 0x61, 0x4e, 0x3e, +0x01, 0x1d, 0xa2, 0x41, 0xd5, 0x8e, 0xc7, 0x99, +0x14, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0x11, 0x8a, 0xc1, 0xd5, 0xd9, +0x93, 0x39, 0xc6, 0x50, 0xf5, 0x74, 0x0f, 0xcb, +0xb8, 0x0d, 0xdb, 0xb7, 0x4c, 0x14, 0x2e, 0x9b, +0xda, 0x86, 0x8e, 0x91, 0x87, 0x2c, 0x93, 0x48, +0x10, 0xfb, 0x43, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0x6f, 0xc9, 0x7c, +0x76, 0x11, 0x29, 0x15, 0x2f, 0x38, 0xa8, 0x18, +0x05, 0x50, 0xc8, 0x69, 0xe5, 0x8a, 0x92, 0xdf, +0x72, 0xc9, 0x59, 0x8f, 0x92, 0x4b, 0x80, 0x62, +0xc9, 0x91, 0xbe, 0x6a, 0x49, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0xab, +0x8b, 0x73, 0xa2, 0xa9, 0x00, 0x2f, 0x08, 0xcd, +0xaa, 0xe9, 0xef, 0xbf, 0x8c, 0x86, 0x4b, 0xc5, +0xa7, 0x6a, 0x77, 0xf5, 0xb2, 0x0c, 0xe1, 0xd1, +0xe7, 0x10, 0x0f, 0x03, 0xe4, 0x70, 0xf8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0xe1, 0xb7, 0xbf, 0x4e, 0x07, 0x40, 0xc1, +0x29, 0x0e, 0x55, 0xe3, 0x4c, 0x8a, 0xc3, 0xca, +0xcb, 0xc1, 0x12, 0x40, 0x37, 0xb1, 0x20, 0x93, +0xb4, 0x96, 0xbe, 0x6b, 0x73, 0xd9, 0x39, 0x73, +0x97, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0x44, 0xec, 0x23, 0xc6, 0x43, +0x2f, 0xd1, 0x5d, 0x42, 0xdf, 0x87, 0x90, 0xdf, +0x07, 0xa7, 0x11, 0xb1, 0x3b, 0x49, 0xff, 0x9c, +0x02, 0x2b, 0x9f, 0x0f, 0x2f, 0xcb, 0x40, 0x92, +0xc8, 0xe9, 0xe1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x0e, 0x95, 0x29, +0x98, 0x1f, 0x5c, 0x5b, 0xd5, 0x12, 0x66, 0xab, +0x85, 0x51, 0x14, 0xd3, 0x40, 0x38, 0x80, 0x37, +0x1e, 0xed, 0x7c, 0x79, 0x7b, 0x4b, 0x4c, 0xf2, +0x9e, 0x99, 0x86, 0x05, 0x04, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x1d, +0x51, 0x86, 0x6b, 0x22, 0xc2, 0x32, 0xdd, 0xde, +0xe7, 0x07, 0xb3, 0x76, 0x66, 0x4f, 0x66, 0x2f, +0x3f, 0x79, 0x56, 0x7d, 0x1e, 0x83, 0x52, 0x9d, +0x25, 0x2d, 0x72, 0xac, 0x9a, 0x32, 0x97, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x00, 0x00, 0x6b, 0x91, 0xd1, 0x6e, 0xdf, +0xb6, 0x37, 0xbd, 0x02, 0x5e, 0xe0, 0xee, 0xba, +0xa5, 0xb9, 0x1d, 0x41, 0xef, 0xca, 0x1f, 0x6f, +0x65, 0x1e, 0xdf, 0x8e, 0xd3, 0xdc, 0xd6, 0x28, +0x37, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0xd5, 0xd4, 0x31, 0xe2, 0xf8, +0xf3, 0xdd, 0x91, 0xf6, 0xf1, 0x5b, 0xeb, 0x9e, +0x98, 0x60, 0x6f, 0xec, 0x22, 0x27, 0xbc, 0x76, +0x42, 0xa1, 0x71, 0x38, 0x62, 0x0b, 0x0f, 0xe3, +0xa2, 0xed, 0x37, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0xfb, 0x11, 0x64, +0x51, 0x80, 0x09, 0x27, 0x18, 0xb5, 0xb7, 0xcb, +0x01, 0x36, 0x14, 0xaa, 0x2f, 0x8e, 0x52, 0x2d, +0x0f, 0x52, 0x8f, 0xf2, 0x09, 0x7d, 0x33, 0xdd, +0x83, 0xb4, 0xb1, 0xfa, 0xc1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0xf4, +0xb4, 0xc3, 0x49, 0xb8, 0x89, 0x2e, 0x80, 0x21, +0x2b, 0x09, 0xc8, 0xb6, 0x64, 0x22, 0x94, 0x83, +0xef, 0x4b, 0xa4, 0x87, 0x93, 0x23, 0xed, 0x98, +0x20, 0xd8, 0x8a, 0xff, 0x72, 0x85, 0x15, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0x75, 0x02, 0xb7, 0x82, 0x51, 0xb5, 0x7f, +0x0c, 0x9e, 0x82, 0xa0, 0xed, 0xe2, 0x81, 0xab, +0x86, 0x53, 0x74, 0xec, 0x03, 0x74, 0x52, 0x66, +0xd5, 0x67, 0x0f, 0x8c, 0xc2, 0x4c, 0x31, 0x4f, +0x97, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0xfa, 0x07, 0x01, 0xf2, 0xdb, +0x7d, 0x8f, 0x59, 0x8b, 0xbc, 0x92, 0xe7, 0x28, +0x70, 0xfa, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x1b, 0xee, 0xa4, 0x08, +0xa1, 0x06, 0x27, 0x2a, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0xec, 0xe0, 0x06, +0x47, 0xb5, 0xd7, 0x7b, 0xdc, 0x1d, 0xab, 0xbd, +0xdf, 0xb4, 0x34, 0xe1, 0xa2, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8d, 0x12, +0x3e, 0xd4, 0x80, 0x07, 0xbb, 0x6a, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0x8a, +0x2d, 0xf9, 0xe5, 0x40, 0x94, 0x8e, 0x53, 0x88, +0x91, 0x5e, 0x07, 0x9a, 0x4d, 0x27, 0x67, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x24, 0xce, 0xf6, 0xd4, 0x56, 0x85, 0x8e, 0xb8, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x00 +}; diff --git a/include/linux/mfd/max77705_pass4.h b/include/linux/mfd/max77705_pass4.h new file mode 100644 index 000000000000..e17dff05ffe8 --- /dev/null +++ b/include/linux/mfd/max77705_pass4.h @@ -0,0 +1,6634 @@ +const uint8_t BOOT_FLASH_FW_PASS4[] = { +0xc1, 0x66, 0xf1, 0xce, 0x01, 0x10, 0x00, 0x00, +0x01, 0x22, 0x21, 0x70, 0x01, 0x02, 0x03, 0x04, +0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, +0x0d, 0x0e, 0x0f, 0x00, 0x01, 0x10, 0x00, 0x00, +0x02, 0x00, 0x00, 0x00, 0x3d, 0x0d, 0xd5, 0x60, +0xea, 0x7a, 0xf3, 0xc5, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0x20, 0xa9, 0xe8, +0x91, 0x2c, 0xdf, 0x69, 0xb7, 0x95, 0x9d, 0xa4, +0x69, 0x33, 0xed, 0x4a, 0x1d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x72, +0x4c, 0xbe, 0x4e, 0x39, 0xae, 0x3f, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0xb4, +0x5b, 0xac, 0x30, 0x09, 0x98, 0xdd, 0x16, 0xdc, +0x99, 0xcb, 0xf3, 0xf5, 0x8e, 0xb9, 0x25, 0x76, +0x4b, 0x41, 0x8a, 0xb8, 0x6d, 0x5c, 0xe6, 0x5f, +0xe9, 0x5d, 0xd1, 0xf4, 0xa7, 0xa9, 0xee, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0xa2, 0xde, 0x59, 0x94, 0xe7, 0xa0, 0x22, +0x13, 0x79, 0xbc, 0xaf, 0x4f, 0x2d, 0x04, 0xfd, +0x7e, 0xe0, 0xdc, 0xaf, 0xcb, 0x55, 0xc7, 0x6b, +0xd7, 0x47, 0xff, 0xcf, 0x8c, 0x2a, 0x9c, 0xf2, +0x95, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0x6d, 0xe8, 0x58, 0x11, 0x26, +0x41, 0xac, 0x7f, 0x51, 0x2b, 0x81, 0x1d, 0x08, +0x15, 0xbd, 0x68, 0x13, 0x90, 0x9a, 0x0d, 0x29, +0x3c, 0xb5, 0xf5, 0x46, 0x51, 0xc6, 0xe2, 0x85, +0xbf, 0xe0, 0x85, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0x77, 0x12, 0xb9, +0xe0, 0x8f, 0xe4, 0xcf, 0xaa, 0xa9, 0x93, 0x0e, +0xab, 0x44, 0xec, 0x31, 0x21, 0xe7, 0x46, 0x22, +0xb6, 0xc2, 0x6d, 0xc3, 0x5e, 0x02, 0x92, 0x96, +0x81, 0x76, 0xe4, 0xe6, 0xaf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0x4f, +0x4e, 0xaa, 0x48, 0x7f, 0x6b, 0xc1, 0x91, 0xf5, +0x5f, 0xda, 0x68, 0x9f, 0x32, 0x17, 0x66, 0xcd, +0x95, 0x42, 0x26, 0xfe, 0xc3, 0x4a, 0x50, 0x9b, +0xec, 0x82, 0x16, 0x80, 0x0f, 0x16, 0x79, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0x6f, 0x6e, 0x13, 0x81, 0xa6, 0x29, 0x55, +0xc7, 0x95, 0x64, 0xbf, 0x6c, 0x76, 0x30, 0xd9, +0xb6, 0x46, 0x75, 0xa8, 0x59, 0x9c, 0x43, 0x08, +0xde, 0xb1, 0xf4, 0xe7, 0x50, 0xaf, 0x1a, 0xeb, +0x2a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0x2b, 0xb0, 0x96, 0xe5, 0x60, +0xb8, 0xf3, 0x16, 0xb9, 0x36, 0x5f, 0x0c, 0x82, +0x6e, 0x57, 0xb7, 0xc7, 0x18, 0xc9, 0x42, 0x77, +0x70, 0xd3, 0x9f, 0xdd, 0x46, 0x62, 0x9d, 0x45, +0x8c, 0xce, 0xf7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0xb0, 0x52, 0xb4, +0xcf, 0x04, 0x53, 0xcc, 0xfb, 0x99, 0x6b, 0x17, +0xd7, 0x70, 0x7c, 0xe1, 0x1a, 0x06, 0x23, 0x9f, +0x1a, 0x8a, 0xe4, 0x50, 0x5b, 0x5d, 0x25, 0x67, +0xbd, 0xe5, 0xc6, 0x7d, 0x6b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0x73, +0x4a, 0x9f, 0x31, 0x2a, 0x39, 0x30, 0x68, 0xba, +0x7b, 0xaa, 0xf5, 0x78, 0x81, 0x93, 0xfe, 0x0d, +0x33, 0x82, 0xdd, 0x6b, 0xfc, 0x48, 0x34, 0x4d, +0xbf, 0x4e, 0x4a, 0x5e, 0x6f, 0x95, 0x87, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x05, 0x10, 0x6d, 0x2f, 0xd8, 0x35, 0xcb, +0x3f, 0xf3, 0x53, 0xbb, 0x8a, 0xc6, 0x88, 0x56, +0x90, 0x86, 0x64, 0x58, 0xc5, 0x8f, 0x88, 0xb1, +0xbb, 0xc6, 0xb7, 0x82, 0xa8, 0xf1, 0xa7, 0xbe, +0xe3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0x51, 0x92, 0x19, 0x75, 0x63, +0x3c, 0x75, 0x98, 0x58, 0x6c, 0x88, 0xcd, 0xdf, +0xec, 0x22, 0x56, 0xd3, 0xb3, 0x0a, 0x4d, 0xc3, +0xa7, 0xe1, 0x87, 0x00, 0x19, 0xc8, 0xee, 0x0c, +0xac, 0xf9, 0xf5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0x05, 0x30, 0xda, +0x95, 0x0c, 0x3c, 0xa1, 0x6a, 0xf0, 0xa8, 0x3e, +0x6f, 0x1f, 0x0d, 0x61, 0x4c, 0x02, 0x32, 0xd8, +0xe0, 0x06, 0xe1, 0xe8, 0xe0, 0xdc, 0x18, 0x4c, +0x20, 0xd6, 0x94, 0xa8, 0xfc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x6d, +0xa5, 0x2b, 0x79, 0x1c, 0xe0, 0x3d, 0x74, 0xb3, +0x9c, 0xf2, 0x07, 0x3e, 0x8a, 0x76, 0xd7, 0xb8, +0x5e, 0x59, 0x58, 0xb9, 0x5e, 0xa9, 0x32, 0x23, +0x67, 0x74, 0x4e, 0x7a, 0x2b, 0x17, 0xac, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0xa0, 0x3c, 0xc3, 0xdf, 0xdc, 0xd0, 0xd9, +0x57, 0xac, 0x86, 0xa7, 0xcb, 0x35, 0x86, 0x95, +0xec, 0xb0, 0xdf, 0xad, 0xec, 0xc5, 0xa9, 0xb0, +0xed, 0xea, 0x63, 0x38, 0xc7, 0xe3, 0xd4, 0x35, +0x52, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0xed, 0xa0, 0x23, 0x3f, 0x7d, +0xf9, 0x43, 0x3f, 0x77, 0x37, 0xf3, 0xb2, 0x20, +0x98, 0xd4, 0x28, 0xa3, 0x20, 0xb6, 0x34, 0x08, +0xc0, 0x24, 0x99, 0xb5, 0x88, 0x4e, 0xb4, 0x2a, +0x53, 0x44, 0xe9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0xd7, 0x17, 0xe3, +0x92, 0x02, 0xcc, 0x3d, 0x0c, 0x6a, 0x4d, 0xc7, +0x9a, 0x88, 0x71, 0x4f, 0x82, 0x62, 0x95, 0x17, +0x41, 0xd0, 0xbd, 0xfd, 0xf2, 0xcf, 0x92, 0x58, +0x80, 0xd8, 0x0c, 0x7f, 0x5d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x4d, +0xbd, 0x0a, 0xb0, 0xf9, 0x6c, 0x3d, 0x62, 0x0f, +0xdb, 0x30, 0x4d, 0xb9, 0x96, 0xed, 0xe1, 0xf8, +0x25, 0x3a, 0x36, 0x9c, 0x43, 0xbb, 0x66, 0x5f, +0xe3, 0xcb, 0xa4, 0x76, 0x05, 0x35, 0x86, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0x76, 0x18, 0xb8, 0x40, 0xc0, 0xd8, 0xc0, +0x9c, 0x76, 0x0d, 0x81, 0x93, 0xd0, 0x11, 0x99, +0xd3, 0x14, 0x28, 0xeb, 0x01, 0xad, 0x19, 0x7e, +0xab, 0x98, 0x22, 0xfd, 0x66, 0xf7, 0x56, 0x5a, +0x77, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0x92, 0x5a, 0x1c, 0x5f, 0x9a, +0x1a, 0x80, 0x2c, 0xff, 0xf6, 0x1e, 0xa5, 0x73, +0x3d, 0xc8, 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x4c, 0x78, 0xe6, 0x91, +0xfb, 0xca, 0xac, 0x86, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0x13, 0x35, 0x50, +0x5a, 0x90, 0x7c, 0x3e, 0x14, 0x80, 0x46, 0x1c, +0xd2, 0xc5, 0xa0, 0x6d, 0x46, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0xd8, +0x1b, 0x16, 0x42, 0xff, 0x9c, 0x9a, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x0e, +0x3b, 0x29, 0x27, 0xf5, 0xf1, 0xbc, 0xa2, 0x49, +0x85, 0x27, 0xab, 0x45, 0x36, 0x2e, 0x2e, 0xec, +0xd8, 0x35, 0x8e, 0x66, 0xb1, 0xc3, 0xb7, 0x29, +0x93, 0xa1, 0xd8, 0xff, 0x90, 0x38, 0xf7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0x33, 0x92, 0xe8, 0x0a, 0x14, 0x44, 0xe8, +0x61, 0xd5, 0xc0, 0x40, 0xb0, 0x58, 0xf4, 0x38, +0x76, 0x5d, 0x82, 0x89, 0x2f, 0x7a, 0x7c, 0xb6, +0xac, 0x5d, 0x3a, 0xf9, 0x38, 0x93, 0xf3, 0x66, +0xb5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0x5a, 0xae, 0xde, 0x57, 0x86, +0xf0, 0x1d, 0x69, 0xd7, 0xfd, 0xd9, 0x6b, 0x14, +0x98, 0xec, 0xb5, 0xa2, 0x85, 0x1a, 0xc0, 0x39, +0x52, 0xfc, 0xdb, 0xac, 0x58, 0x03, 0xba, 0xa8, +0x5f, 0x06, 0x9f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x1a, 0x9f, 0x94, +0xf9, 0x2a, 0xa3, 0x84, 0x25, 0x3f, 0x56, 0xc4, +0x85, 0x6d, 0x68, 0xe1, 0xab, 0x82, 0x03, 0x68, +0x6f, 0xea, 0xc2, 0x34, 0xee, 0x19, 0xec, 0xd1, +0x25, 0xab, 0x3e, 0x8a, 0x10, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0xa3, +0xf3, 0xa8, 0xf8, 0xcb, 0x21, 0xbc, 0x43, 0xf5, +0xf5, 0xe6, 0xf7, 0xb1, 0x7c, 0x2f, 0x14, 0xec, +0x19, 0x57, 0x60, 0x0a, 0x64, 0x76, 0x37, 0xa3, +0x72, 0x6d, 0xce, 0xb0, 0xc4, 0x76, 0xae, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0xcd, 0xf0, 0x94, 0x0f, 0xa3, 0xf3, 0x81, +0x86, 0x1b, 0x99, 0x87, 0x61, 0xd2, 0xcb, 0xf9, +0x00, 0x78, 0x20, 0xc3, 0xc6, 0x1e, 0xca, 0x1d, +0x68, 0xf8, 0xa7, 0x92, 0xa6, 0xde, 0x5d, 0x23, +0x5d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0x27, 0x5e, 0x75, 0x56, 0x90, +0xf2, 0x71, 0x67, 0xe1, 0x5f, 0x72, 0xb1, 0x63, +0x24, 0x02, 0x0d, 0x7c, 0x7c, 0xe5, 0x11, 0x0d, +0x05, 0x57, 0x9d, 0xd3, 0x52, 0x9f, 0x35, 0xcc, +0x96, 0x4e, 0x3f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0x39, 0x7e, 0xba, +0x78, 0xd6, 0x24, 0x8f, 0xce, 0x6b, 0x18, 0x4e, +0xdd, 0x58, 0x18, 0x85, 0xd4, 0x10, 0xa5, 0x9b, +0xb2, 0xee, 0x46, 0xc2, 0x5f, 0xa5, 0x43, 0x81, +0x33, 0x5a, 0xab, 0x70, 0xbb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0x71, +0x5a, 0x2b, 0xfa, 0x95, 0xe1, 0x5e, 0x33, 0x3f, +0xf6, 0x1d, 0x6d, 0x77, 0xa5, 0x33, 0x52, 0x91, +0x20, 0xf9, 0xb9, 0x3c, 0xd6, 0xc4, 0xe4, 0x5b, +0xcb, 0x39, 0x64, 0xab, 0xbb, 0x67, 0x7d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x28, 0x1e, 0xce, 0x1d, 0x2c, 0x86, 0x64, +0xbb, 0xb3, 0xa9, 0x6e, 0x72, 0x9d, 0x84, 0xb9, +0x2b, 0xf7, 0xc1, 0x29, 0xa0, 0x5b, 0x99, 0xfe, +0xd6, 0x7b, 0xe3, 0xc1, 0x5b, 0x2d, 0x2b, 0x57, +0x09, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x7c, 0x06, 0xce, 0xe4, 0xd9, +0x01, 0xc7, 0x38, 0xc9, 0x95, 0x0b, 0x68, 0xb1, +0xd3, 0xfd, 0x08, 0x94, 0xba, 0x9e, 0x17, 0x9b, +0x1d, 0x11, 0x00, 0x43, 0x1f, 0x71, 0x8a, 0xfc, +0xac, 0x8f, 0xcb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0x80, 0x58, 0x82, +0x0d, 0x3f, 0x00, 0xc2, 0xc6, 0x7a, 0xf7, 0x1c, +0x18, 0x69, 0x26, 0xb0, 0x92, 0x9a, 0xcc, 0x28, +0x68, 0xeb, 0xb5, 0xdb, 0xc2, 0x18, 0x9c, 0x4c, +0x72, 0xca, 0xc4, 0xb0, 0xa2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0xdf, +0x03, 0xe7, 0x77, 0xf2, 0x63, 0x3e, 0x86, 0x39, +0xb7, 0x7c, 0xd1, 0xaf, 0x94, 0x1e, 0x72, 0x25, +0x0c, 0x78, 0x7f, 0xbf, 0xac, 0xd5, 0xc1, 0xa5, +0x07, 0x9d, 0xc5, 0x8d, 0xe4, 0xdb, 0x1f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x32, 0x60, 0x64, 0xfd, 0xd1, 0x72, 0xe0, +0x9a, 0xaf, 0xcc, 0x32, 0x7a, 0xe2, 0x53, 0x14, +0x56, 0xa7, 0x4f, 0x00, 0x84, 0xe0, 0x4d, 0x0e, +0x6c, 0x7b, 0xb3, 0x9d, 0xe6, 0x49, 0x82, 0xd2, +0x3e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0x42, 0xf3, 0x8c, 0x0f, 0x21, +0x96, 0x14, 0xec, 0x78, 0xe2, 0xdf, 0x66, 0x7a, +0x6a, 0x49, 0x14, 0xd3, 0xcf, 0x05, 0x6d, 0x7a, +0x69, 0x94, 0xc8, 0x04, 0xdd, 0xf4, 0x73, 0xe6, +0x1b, 0xc3, 0xa4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0x51, 0xa8, 0x2d, +0xf7, 0xbd, 0x51, 0x41, 0xd3, 0xa5, 0x18, 0x39, +0xd6, 0x9c, 0x77, 0xe9, 0xc3, 0x29, 0x18, 0x07, +0x73, 0x71, 0xd5, 0xef, 0x30, 0x84, 0x10, 0xfe, +0xb8, 0xfa, 0xfd, 0xfc, 0xa8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x7f, +0xc0, 0xb8, 0x94, 0x6a, 0x5e, 0xe2, 0x72, 0x0a, +0xfb, 0xff, 0xbd, 0x5c, 0xbe, 0x91, 0x45, 0x93, +0x02, 0x21, 0x58, 0x51, 0x5b, 0x1d, 0x56, 0xf4, +0x57, 0x6e, 0x91, 0x66, 0xb6, 0xda, 0x7d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0x10, 0x04, 0x45, 0x88, 0x2b, 0xf7, 0x42, +0x92, 0x89, 0x42, 0xb6, 0x47, 0xa6, 0x6f, 0x0c, +0x13, 0x15, 0x80, 0x42, 0x5c, 0xfb, 0xe4, 0xf9, +0xee, 0xe5, 0xa1, 0x19, 0x7a, 0xf3, 0x42, 0xf0, +0x30, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0x62, 0xfe, 0x20, 0x24, 0x5e, +0xc2, 0x9e, 0x62, 0xd0, 0x1b, 0x06, 0xba, 0xc7, +0x2a, 0x6e, 0xfb, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xd5, 0x61, 0x41, 0x25, +0x08, 0x9c, 0xe2, 0x71, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0x7b, 0x7c, 0x73, +0xa3, 0xdf, 0xc6, 0xb3, 0xd6, 0xa4, 0x99, 0xe2, +0x5a, 0x55, 0x3a, 0x81, 0xf9, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x3d, +0xa1, 0xc0, 0x70, 0xa9, 0x91, 0x05, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0xcc, +0xc6, 0x72, 0x0a, 0xc8, 0xc7, 0xc7, 0x4c, 0x69, +0x3e, 0x0f, 0x63, 0x41, 0x1a, 0x00, 0xbd, 0x64, +0xd6, 0x0a, 0xd1, 0xd8, 0xc0, 0xd7, 0x0a, 0x73, +0x17, 0x3d, 0xee, 0x66, 0x4a, 0xc5, 0x64, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0xc3, 0x98, 0x9b, 0xcf, 0xcb, 0x4a, 0x14, +0x52, 0x85, 0x73, 0x95, 0x53, 0x95, 0x04, 0x38, +0x46, 0x9b, 0xd7, 0x36, 0xb0, 0x9f, 0x7c, 0xb1, +0x32, 0xb1, 0x0b, 0xc3, 0x2e, 0x69, 0x1b, 0xec, +0x89, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0xc1, 0x86, 0x7b, 0xae, 0xd2, +0xc5, 0x4e, 0x02, 0x2d, 0xf8, 0x85, 0xd4, 0xe2, +0x17, 0x50, 0xd6, 0x32, 0xe4, 0x80, 0x8b, 0x60, +0x76, 0x2f, 0x10, 0x78, 0x36, 0x35, 0xe8, 0x45, +0x7f, 0x05, 0x3f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0x55, 0xee, 0x2c, +0xda, 0xcd, 0x63, 0x89, 0xc9, 0xce, 0x44, 0x54, +0x85, 0x29, 0xa3, 0x5f, 0x05, 0x5a, 0xf8, 0x95, +0x0d, 0xae, 0xa2, 0x26, 0x5c, 0x21, 0x15, 0x96, +0x90, 0x81, 0x30, 0x62, 0x90, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0xb1, +0x54, 0x3f, 0xc2, 0x92, 0xb4, 0x51, 0x60, 0xc6, +0x7d, 0x5d, 0xba, 0xda, 0x5d, 0xf8, 0x42, 0x3d, +0x81, 0x53, 0x69, 0x8e, 0x75, 0x57, 0x82, 0x3a, +0xc7, 0xef, 0xfe, 0xbb, 0x89, 0xd3, 0xee, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0x25, 0x03, 0x94, 0x56, 0x8d, 0xf0, 0xbd, +0x21, 0x46, 0x62, 0x33, 0x68, 0xde, 0x7c, 0xe4, +0xef, 0x4c, 0x2f, 0xb0, 0x5a, 0x40, 0xbb, 0x49, +0x73, 0x1a, 0x1e, 0x17, 0x60, 0xae, 0x0c, 0xef, +0x8b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0xf6, 0xc7, 0x98, 0x9a, 0x9f, +0xde, 0x6e, 0x33, 0xc3, 0x82, 0x07, 0xea, 0x10, +0xd2, 0xf6, 0x05, 0x6b, 0x93, 0x55, 0xd8, 0xdd, +0x8d, 0xa5, 0x9a, 0x10, 0x4d, 0xd6, 0x19, 0xc9, +0x36, 0x00, 0x1f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0x43, 0x6c, 0xc1, +0xc8, 0xd8, 0x71, 0x77, 0x84, 0x4e, 0xff, 0xde, +0x0b, 0x46, 0x65, 0xaf, 0xa4, 0xa1, 0x5d, 0x41, +0x81, 0xa8, 0x28, 0x4d, 0x3d, 0xe3, 0xb2, 0x08, +0x09, 0xe1, 0xd0, 0x1c, 0x4d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0x39, +0x1a, 0xdc, 0x06, 0x57, 0xa1, 0xb0, 0xf3, 0x96, +0x26, 0xe0, 0xc4, 0xc8, 0x8e, 0x6e, 0x24, 0x2c, +0xca, 0x12, 0x81, 0x85, 0x38, 0xbf, 0x07, 0x15, +0x51, 0x96, 0x8d, 0x11, 0x53, 0x82, 0xeb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0x5c, 0xf3, 0xfe, 0xb5, 0xa5, 0xe5, 0xa1, +0xbd, 0xd1, 0x9c, 0x62, 0x03, 0x4f, 0xdb, 0x44, +0x05, 0xc2, 0xa8, 0x74, 0x42, 0x9c, 0x38, 0xba, +0xf7, 0x72, 0xc9, 0x7a, 0xbe, 0x51, 0x67, 0x92, +0x69, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0xdd, 0x0b, 0x85, 0xf8, 0xd2, +0x36, 0x3d, 0x5a, 0x8f, 0xe2, 0x66, 0xab, 0xf4, +0x19, 0x81, 0x19, 0x1a, 0x3a, 0xda, 0x25, 0xa8, +0x0a, 0xe4, 0xb6, 0xf6, 0x28, 0x63, 0xb1, 0xc7, +0xbc, 0xcd, 0x1a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0x33, 0xad, 0xde, +0xd0, 0x64, 0xd6, 0xdc, 0x76, 0x8d, 0xa4, 0xb7, +0x18, 0xe9, 0x66, 0x30, 0x34, 0xba, 0x5e, 0x5c, +0x06, 0xa7, 0xbf, 0x33, 0x16, 0x3e, 0xca, 0x6e, +0xad, 0xb4, 0xdb, 0xd4, 0x42, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0x19, +0xf4, 0xed, 0xea, 0x78, 0x68, 0x6c, 0x97, 0x67, +0x4b, 0x3c, 0x6d, 0x7c, 0x67, 0xac, 0x21, 0x9f, +0xd7, 0x0d, 0xd2, 0x45, 0xc8, 0x47, 0xac, 0xdf, +0xc5, 0x4f, 0x14, 0xb6, 0xd6, 0xc8, 0xaf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0xde, 0xb9, 0xc6, 0x03, 0x20, 0x55, 0x62, +0x59, 0x7f, 0x10, 0xf1, 0x31, 0xa9, 0x44, 0xbf, +0x1c, 0x63, 0xec, 0xd6, 0x52, 0xa1, 0x27, 0xa1, +0xfe, 0x1c, 0x56, 0xc9, 0x6a, 0x36, 0x4a, 0x29, +0xfb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x5c, 0x64, 0x14, 0xe3, 0x89, +0x47, 0x82, 0x26, 0xb2, 0xe6, 0x4c, 0xa8, 0x76, +0x67, 0x30, 0xf1, 0x15, 0xe3, 0x71, 0x9d, 0x6d, +0x8f, 0x81, 0xc2, 0x3e, 0xcc, 0x85, 0xe5, 0xbd, +0x9f, 0xff, 0x30, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0x7b, 0xd9, 0xf7, +0xba, 0x89, 0x6d, 0xbc, 0x3d, 0x06, 0xa7, 0x78, +0x7a, 0x93, 0xbc, 0x47, 0x26, 0xb8, 0xf7, 0xe8, +0x60, 0x1f, 0xca, 0x45, 0x7e, 0x03, 0x81, 0x41, +0xac, 0xbb, 0xc6, 0xbb, 0x48, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0xde, +0xe9, 0xc2, 0xd0, 0xba, 0x23, 0x95, 0x15, 0x05, +0xc0, 0x36, 0x6f, 0x5e, 0x5f, 0xfd, 0xe2, 0xa6, +0x18, 0x6a, 0x3a, 0xe3, 0x21, 0xdb, 0x57, 0xd1, +0xdb, 0xf0, 0x41, 0xbe, 0x1e, 0xeb, 0x97, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x27, 0x45, 0x59, 0x42, 0xf9, 0x52, 0xeb, +0x45, 0xf0, 0xc2, 0xef, 0xd1, 0xa4, 0x1f, 0x68, +0xbc, 0x45, 0xa3, 0xd7, 0xb1, 0x6d, 0x22, 0x26, +0x2e, 0xaf, 0x9c, 0xac, 0xea, 0x39, 0xaf, 0xeb, +0x07, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x3d, 0xed, 0xbe, 0x33, 0x2b, +0x0c, 0x6d, 0x65, 0x0e, 0xc1, 0x58, 0x92, 0x75, +0xab, 0x9f, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x6a, 0x26, 0xab, 0x5e, +0x69, 0xad, 0xbb, 0x42, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0x5b, 0x81, 0xdf, +0x14, 0xe2, 0x01, 0xda, 0xaf, 0x3f, 0xfa, 0xfc, +0xa9, 0x2f, 0x4c, 0xbb, 0x28, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x01, +0x95, 0x96, 0x6d, 0xf7, 0xda, 0xe9, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0xc5, +0x47, 0xaf, 0xc2, 0xd3, 0x4f, 0x74, 0x08, 0xb7, +0x1f, 0x37, 0xf9, 0xc5, 0x80, 0x84, 0x65, 0x19, +0x29, 0xfa, 0x01, 0xc1, 0xa3, 0x71, 0x8f, 0xb2, +0x3c, 0x58, 0x34, 0xf1, 0x05, 0xa1, 0x3e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0x3e, 0x2f, 0xe3, 0x6c, 0x0a, 0xc3, 0x55, +0x45, 0x68, 0xad, 0xec, 0x8c, 0xe0, 0x58, 0xa8, +0x64, 0x25, 0x54, 0x01, 0x84, 0x68, 0xf8, 0x2c, +0xa7, 0x27, 0x80, 0x38, 0x06, 0x61, 0x20, 0x0d, +0x2f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0x90, 0xa4, 0xf3, 0x57, 0x8b, +0x6e, 0xa9, 0xb5, 0x28, 0xdd, 0x8c, 0x9d, 0xc4, +0xff, 0x5c, 0xab, 0x5c, 0xf6, 0x2f, 0x6b, 0xfd, +0xdb, 0xb4, 0xda, 0xd6, 0x5a, 0x4e, 0xfc, 0xe4, +0x24, 0xb1, 0xe6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x96, 0x52, 0x2c, +0x63, 0x8d, 0x3c, 0x4d, 0xd7, 0xa9, 0x71, 0x8b, +0x54, 0x99, 0x6b, 0x58, 0xae, 0x8b, 0x75, 0x26, +0x0f, 0xf3, 0x3d, 0xdc, 0x25, 0x64, 0xf5, 0xcb, +0x0f, 0x36, 0x30, 0x88, 0xe1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x45, +0x0b, 0x03, 0x60, 0x90, 0x8b, 0x30, 0xaf, 0x59, +0xc5, 0x1f, 0x77, 0xb7, 0xe2, 0x4a, 0x3b, 0x54, +0x66, 0x89, 0x94, 0x53, 0x78, 0x32, 0xbd, 0x85, +0xed, 0x90, 0xe5, 0xdc, 0xb0, 0xdd, 0x61, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0xa3, 0xde, 0x5a, 0xf8, 0x42, 0x1e, 0x10, +0x35, 0x31, 0x63, 0x02, 0x48, 0x01, 0x15, 0xd1, +0xd1, 0xf3, 0x80, 0xce, 0xd8, 0x42, 0x19, 0x98, +0x6c, 0x2d, 0xda, 0xa1, 0xc1, 0x0e, 0x79, 0x12, +0x94, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0x30, 0xe9, 0x9a, 0x36, 0x26, +0xe8, 0xa7, 0x19, 0x1b, 0x8b, 0xa0, 0x0e, 0x25, +0x60, 0x97, 0x1f, 0xd5, 0xd8, 0x97, 0xcd, 0x83, +0xfd, 0x47, 0x98, 0x95, 0x91, 0x76, 0x8d, 0x67, +0x75, 0x94, 0x02, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0x62, 0x07, 0xd6, +0x4b, 0x93, 0x1f, 0x03, 0xcd, 0xa9, 0x43, 0xb0, +0xba, 0x6a, 0x46, 0xba, 0x0c, 0xc9, 0x27, 0x29, +0x38, 0xf0, 0x4c, 0x4a, 0x05, 0x27, 0xaa, 0x9e, +0xe6, 0x00, 0x5a, 0xba, 0x81, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0xc5, +0x19, 0x8d, 0x57, 0xe2, 0x6c, 0xc2, 0x80, 0x1b, +0x1d, 0x70, 0xbc, 0xd2, 0x3a, 0xae, 0x83, 0x67, +0x3e, 0xbd, 0x7f, 0xba, 0x07, 0x57, 0x7c, 0xca, +0x76, 0x54, 0x16, 0x1a, 0xe4, 0x95, 0x39, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0x4a, 0xdd, 0x72, 0x6e, 0xf3, 0x9a, 0x3a, +0x06, 0x16, 0x7e, 0xaa, 0x11, 0x4c, 0x6f, 0x53, +0x2f, 0x56, 0x20, 0x8d, 0x0b, 0x9f, 0x2d, 0x11, +0x17, 0x38, 0x2e, 0x65, 0xc1, 0x83, 0x37, 0x92, +0x2c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0xc3, 0x85, 0x87, 0xf3, 0x5e, +0x3d, 0x62, 0xa7, 0x8d, 0xf8, 0x8a, 0xc4, 0x43, +0xb5, 0x8e, 0xe9, 0x1f, 0xac, 0xfe, 0xf7, 0xe2, +0x7c, 0xcb, 0x67, 0x41, 0x86, 0x2a, 0xcc, 0xdb, +0x95, 0x40, 0x4c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0x1f, 0xd4, 0xff, +0x9e, 0x33, 0xb1, 0xfb, 0x39, 0x14, 0x3d, 0x10, +0xc9, 0x2e, 0x3b, 0xe7, 0xf2, 0x74, 0x37, 0x63, +0xd4, 0x1f, 0xa7, 0x16, 0x19, 0x14, 0x9e, 0xfd, +0xea, 0x67, 0x69, 0xa0, 0xd3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0x55, +0x9a, 0x86, 0x35, 0x8c, 0x7a, 0xea, 0xc2, 0x80, +0x70, 0x27, 0xc5, 0xbd, 0xb6, 0x41, 0x6e, 0x38, +0xdd, 0x01, 0x23, 0xd9, 0x5c, 0xe7, 0x9a, 0xbb, +0xad, 0x2f, 0xbb, 0x78, 0xe3, 0x8e, 0x54, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0xf6, 0x0c, 0xe8, 0xcf, 0xbd, 0x7c, 0x59, +0x7b, 0xd9, 0x47, 0x06, 0x73, 0xff, 0xbc, 0x59, +0x48, 0xcd, 0x52, 0x55, 0x26, 0xce, 0x91, 0xbd, +0x1a, 0xdb, 0x91, 0xe1, 0x13, 0x7d, 0x5d, 0xa4, +0xab, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0xe1, 0x24, 0x8d, 0xc8, 0xc9, +0xc8, 0x83, 0x90, 0x29, 0x35, 0xf6, 0xc0, 0x83, +0x98, 0x1b, 0x8a, 0x4e, 0x44, 0x21, 0x10, 0xe3, +0xc1, 0x8a, 0x7a, 0xc3, 0x60, 0x50, 0x42, 0x16, +0x9f, 0x33, 0x7c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x99, 0x71, 0x83, +0xa9, 0xd4, 0x22, 0x46, 0x4f, 0xaf, 0xf0, 0x48, +0x68, 0xec, 0x14, 0x5a, 0xf6, 0xc0, 0xa1, 0xd0, +0x63, 0x6e, 0x19, 0x36, 0x2a, 0x86, 0x52, 0x3f, +0x6f, 0x9c, 0x41, 0x91, 0x1f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0x1e, +0xf1, 0x48, 0xe7, 0xad, 0x44, 0x40, 0x37, 0xfb, +0x8f, 0xbe, 0x57, 0xca, 0x8d, 0xf4, 0xfb, 0xc7, +0x8f, 0xb6, 0x2f, 0xb7, 0x88, 0x93, 0x42, 0x0e, +0x0d, 0xed, 0xe4, 0x0a, 0x44, 0xec, 0x81, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0x4a, 0xe0, 0x57, 0x69, 0x6f, 0xac, 0x7f, +0x62, 0xd8, 0x36, 0x91, 0xfd, 0xce, 0x90, 0x13, +0xa5, 0xc4, 0x31, 0x01, 0x63, 0x98, 0x9e, 0xff, +0x19, 0x12, 0x93, 0xb8, 0xd7, 0x1c, 0x08, 0x42, +0x3e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0x48, 0xb4, 0xbd, 0xa4, 0x9d, +0x5a, 0x56, 0xff, 0x96, 0x04, 0x05, 0x9d, 0xc7, +0xfc, 0xb6, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x5a, 0xb1, 0x98, 0xf1, +0x44, 0x07, 0x83, 0x67, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0xc5, 0xca, 0x10, +0x7a, 0x6c, 0x96, 0xf1, 0x65, 0x51, 0x73, 0x43, +0x65, 0x90, 0xe3, 0x07, 0xf2, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd7, 0x79, +0x66, 0x61, 0xc6, 0xb4, 0x39, 0x35, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0xb1, +0x25, 0x40, 0xea, 0x6a, 0xff, 0x4f, 0xf1, 0x3e, +0x76, 0x59, 0x50, 0xd8, 0xb1, 0x05, 0xa0, 0x1c, +0xc9, 0x2d, 0xbf, 0xa4, 0xec, 0xe3, 0xfe, 0x4a, +0x23, 0xf2, 0xd2, 0x8f, 0x95, 0x65, 0x8a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0x57, 0x50, 0x05, 0xfd, 0xac, 0x57, 0x92, +0x64, 0x49, 0xf1, 0x21, 0x77, 0xd5, 0xd4, 0xc5, +0x20, 0x9b, 0x3b, 0x46, 0x67, 0xa7, 0x6d, 0x0d, +0x59, 0xec, 0x39, 0x18, 0xdf, 0x6a, 0x5d, 0x02, +0x30, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0xfc, 0x27, 0xb2, 0xb3, 0xc8, +0x0e, 0x2a, 0x83, 0x1f, 0x4b, 0x44, 0x9a, 0xdb, +0xd6, 0xe2, 0x3e, 0xa5, 0xc3, 0xe4, 0x6f, 0x9c, +0xe8, 0xa8, 0x5d, 0x74, 0x36, 0x9d, 0x84, 0x8e, +0x1e, 0x56, 0xa0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0xad, 0xed, 0xab, +0x1d, 0xec, 0x9b, 0x39, 0x04, 0xc1, 0x31, 0xd9, +0x3e, 0x3e, 0x43, 0x25, 0xc7, 0x3d, 0x08, 0x27, +0x56, 0x5b, 0xbd, 0x54, 0x57, 0xbb, 0x04, 0x7f, +0x50, 0x1b, 0xfb, 0x07, 0x9a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x16, +0xad, 0x37, 0x03, 0x04, 0xd5, 0x77, 0x58, 0x7f, +0x6a, 0x56, 0xdd, 0x1b, 0x14, 0xef, 0x4d, 0xe0, +0x64, 0xe0, 0x8d, 0xe2, 0xa8, 0xda, 0xbf, 0x62, +0x1a, 0xaf, 0xec, 0xe1, 0xcb, 0x52, 0x9b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0x2e, 0xd2, 0x75, 0x63, 0x48, 0xba, 0x49, +0x18, 0x75, 0x7d, 0xb6, 0xd8, 0x61, 0x5a, 0x04, +0x77, 0xdc, 0x8a, 0x87, 0xf0, 0x95, 0x02, 0xa9, +0x83, 0xe6, 0x28, 0x55, 0x23, 0xbc, 0x11, 0x19, +0x61, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0x9e, 0x5a, 0x37, 0xa9, 0xd6, +0xa8, 0x85, 0xf4, 0xd0, 0x63, 0x29, 0x4e, 0xab, +0x8f, 0x33, 0xb3, 0x09, 0xa2, 0x28, 0x17, 0x2c, +0xc5, 0x8f, 0x9a, 0x60, 0x0a, 0x23, 0x68, 0xa2, +0x34, 0xeb, 0x7d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0xab, 0x16, 0x3f, +0x91, 0xf2, 0x83, 0x0b, 0xff, 0x73, 0xe7, 0x0b, +0x31, 0x1c, 0x53, 0x0c, 0x17, 0x91, 0x19, 0x47, +0x73, 0x5b, 0xe0, 0xa5, 0x3b, 0x55, 0x2c, 0x2e, +0x7e, 0x5f, 0x9d, 0x25, 0xce, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x62, +0xee, 0x4c, 0x8a, 0x78, 0x20, 0xca, 0xe7, 0x69, +0x83, 0x41, 0x1e, 0xba, 0x10, 0xc7, 0x35, 0x2b, +0x67, 0x36, 0x38, 0x5a, 0xaa, 0x9b, 0xd4, 0x38, +0x7d, 0x69, 0x27, 0x85, 0x59, 0x43, 0xe0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0x5e, 0x0f, 0x49, 0xf9, 0x0e, 0x65, 0x7d, +0xad, 0xac, 0x59, 0x04, 0xdf, 0x38, 0xe3, 0x0d, +0xca, 0x67, 0x18, 0x69, 0x89, 0x91, 0x88, 0xaa, +0x76, 0xe8, 0x6f, 0xaa, 0x86, 0x48, 0xbf, 0x16, +0x3a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0x7f, 0x6c, 0xbf, 0xbc, 0xfc, +0xdd, 0xba, 0x5c, 0x91, 0xde, 0xd4, 0x17, 0x88, +0x5a, 0x0d, 0xfd, 0x6b, 0xcd, 0xed, 0xcd, 0xa2, +0x47, 0xb6, 0x7f, 0x50, 0x09, 0x72, 0x96, 0x6e, +0x70, 0x22, 0xc1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0x30, 0x40, 0x14, +0xba, 0x45, 0x81, 0xa8, 0xcb, 0xb0, 0x70, 0xeb, +0x4d, 0xe6, 0x99, 0xb4, 0x44, 0x2e, 0x8d, 0xcc, +0x36, 0xa2, 0xa2, 0xf4, 0x1e, 0xc2, 0xd7, 0xea, +0x18, 0x3b, 0xda, 0xbd, 0x86, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0xba, +0xda, 0x5c, 0x51, 0x83, 0xc8, 0xf3, 0xf0, 0x71, +0xde, 0x2a, 0x44, 0x86, 0xad, 0x75, 0x35, 0xa2, +0x69, 0xa8, 0x09, 0xb6, 0x19, 0xf6, 0x66, 0xca, +0x9e, 0xd5, 0x7f, 0xbd, 0x1d, 0x57, 0xa7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0x56, 0x8b, 0x60, 0x2d, 0xef, 0x5f, 0xa8, +0xb0, 0x5d, 0x9e, 0xb9, 0xab, 0x35, 0x94, 0x8d, +0x47, 0x81, 0x81, 0xc0, 0x4d, 0xcd, 0x71, 0x92, +0xec, 0x4d, 0xc5, 0x77, 0xdd, 0xc7, 0x81, 0x81, +0x1b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x80, 0x93, 0xde, 0xfa, 0x40, +0xbf, 0x71, 0x5d, 0x7b, 0xf5, 0xe9, 0x10, 0xcc, +0x03, 0x6f, 0x06, 0x66, 0x00, 0xef, 0xcf, 0xf3, +0xf2, 0x58, 0xb5, 0x76, 0x99, 0xc3, 0x68, 0xda, +0x6f, 0x52, 0x7d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x6d, 0x6f, 0x10, +0x33, 0x01, 0xfe, 0x56, 0x40, 0x39, 0x75, 0xce, +0xbe, 0x6d, 0x55, 0x33, 0x84, 0x4f, 0x3b, 0xdb, +0x6c, 0xa2, 0xf6, 0x77, 0xfc, 0xc5, 0x98, 0x5c, +0x01, 0x5e, 0x04, 0x17, 0x5d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0xde, +0x04, 0x97, 0x55, 0x6b, 0xce, 0x21, 0xfc, 0x6a, +0xbe, 0x39, 0xee, 0x99, 0xb1, 0x9a, 0xbf, 0xa8, +0x26, 0x21, 0x4d, 0xcd, 0x75, 0x4b, 0x9e, 0x21, +0x45, 0x86, 0xa1, 0x40, 0x80, 0x71, 0x5a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0x89, 0xeb, 0x24, 0x31, 0x38, 0x73, 0x59, +0x5b, 0x1b, 0x02, 0x8d, 0x30, 0x59, 0x81, 0xac, +0xf7, 0xd1, 0x3a, 0x7e, 0x71, 0x67, 0x5e, 0xc0, +0x7e, 0x12, 0x89, 0x68, 0xbd, 0x63, 0x44, 0xaf, +0x5c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0x64, 0x39, 0x29, 0x16, 0x6e, +0xf3, 0x03, 0x1e, 0x91, 0xc0, 0xb9, 0xbe, 0xcb, +0x17, 0xe7, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x08, 0x51, 0x0a, 0x4b, +0xc1, 0xd7, 0x80, 0x28, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0xa8, 0x9c, 0x56, +0x37, 0xb0, 0xb7, 0x05, 0x87, 0x16, 0xdb, 0x75, +0x66, 0x17, 0xde, 0x03, 0x4c, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa9, 0x26, +0x75, 0x6b, 0x35, 0x36, 0x72, 0x26, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0xcd, +0xb2, 0xee, 0xb1, 0x59, 0x94, 0xb3, 0x90, 0xd3, +0xee, 0xf7, 0x8e, 0x32, 0x4a, 0x1e, 0xa1, 0xd7, +0x34, 0xec, 0x82, 0xfb, 0x63, 0x07, 0x33, 0xe7, +0x74, 0x9f, 0xd9, 0xa8, 0xe9, 0xb2, 0xa4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0xa8, 0x01, 0x37, 0xf0, 0x78, 0xc4, 0x4b, +0xb4, 0x76, 0x4a, 0x20, 0x6d, 0x19, 0x95, 0x1b, +0x6e, 0x59, 0xe6, 0xc3, 0x4c, 0x1e, 0xe3, 0xf3, +0xef, 0xe7, 0x30, 0xd0, 0x13, 0x28, 0x31, 0x29, +0x51, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0x90, 0x67, 0x31, 0x1e, 0xd9, +0xc4, 0x43, 0x5d, 0xe2, 0xee, 0x1d, 0x6a, 0xf9, +0x2b, 0x73, 0x35, 0x3b, 0xd4, 0xa3, 0xf4, 0x87, +0x6b, 0x35, 0x4d, 0x44, 0x11, 0x1c, 0x97, 0x63, +0x69, 0xc3, 0x8e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0x83, 0x5e, 0x8d, +0xae, 0xe3, 0x0f, 0x73, 0x86, 0xda, 0xf8, 0x48, +0x47, 0x3c, 0x87, 0x55, 0xe8, 0x8e, 0xfa, 0x1f, +0x8f, 0xb0, 0xbd, 0x55, 0x1f, 0x50, 0xbf, 0x4f, +0xb9, 0x58, 0xa1, 0x10, 0xa6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0xba, +0xa2, 0x4a, 0x85, 0x22, 0xef, 0x3a, 0xf8, 0x92, +0x74, 0xe0, 0xc7, 0xca, 0x18, 0x54, 0x9d, 0x99, +0x75, 0xe1, 0x76, 0xdb, 0xe3, 0xde, 0x6d, 0x00, +0x93, 0x08, 0x5b, 0xb6, 0xda, 0xa7, 0x74, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0x59, 0x7a, 0xe5, 0x81, 0x65, 0xc8, 0x6e, +0xda, 0xc8, 0x46, 0x32, 0x97, 0x5a, 0xf0, 0x25, +0xd6, 0x7a, 0xa1, 0xe7, 0x23, 0xb4, 0xf2, 0xe6, +0x2b, 0x2d, 0x6f, 0x5e, 0x12, 0xc5, 0xd2, 0xb8, +0x44, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0x8d, 0x87, 0xc5, 0x16, 0xa0, +0x80, 0xb6, 0xbc, 0xe3, 0x37, 0xa0, 0x1a, 0x5c, +0x55, 0x75, 0xc8, 0xac, 0x02, 0x55, 0xad, 0x7e, +0x13, 0xba, 0x09, 0x29, 0x74, 0x5b, 0xe7, 0x57, +0xa7, 0x76, 0x1e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0x20, 0x80, 0xf2, +0xf5, 0xbe, 0x80, 0xe6, 0xfd, 0x29, 0xe7, 0xaf, +0x47, 0xf6, 0xcc, 0x14, 0x0f, 0xb3, 0xe1, 0x77, +0xe6, 0xb9, 0xd3, 0xd3, 0x5f, 0xfb, 0x70, 0x28, +0x23, 0x9b, 0x63, 0x85, 0xf5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0xe4, +0xf7, 0xf5, 0xb5, 0xb7, 0x91, 0xb6, 0x10, 0x92, +0x06, 0x23, 0xd1, 0x58, 0x14, 0x87, 0xb8, 0x5e, +0x88, 0x3f, 0x62, 0xba, 0x86, 0x24, 0xd1, 0xab, +0xb1, 0x8c, 0x7f, 0x37, 0x3e, 0xab, 0xf7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0x10, 0x31, 0xbf, 0x55, 0xf3, 0x80, 0x70, +0x70, 0xcc, 0xe4, 0x64, 0x73, 0xe9, 0xd9, 0x21, +0x06, 0xb0, 0x3d, 0x7d, 0x0e, 0x5a, 0x46, 0x4f, +0x93, 0x74, 0x2f, 0xec, 0x44, 0x2b, 0xe8, 0xfb, +0x59, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0xce, 0xad, 0xa9, 0xed, 0x46, +0x93, 0xba, 0x79, 0x0b, 0xb7, 0xae, 0xc8, 0x94, +0x13, 0xaf, 0x45, 0x57, 0xb0, 0x70, 0x8f, 0x73, +0x65, 0x01, 0x02, 0x8d, 0x53, 0x0d, 0x8f, 0xfd, +0x84, 0xfa, 0xa4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0x38, 0xce, 0xbc, +0xd8, 0x10, 0x36, 0x1d, 0xd0, 0x5f, 0xd8, 0x62, +0x5d, 0x3c, 0x6e, 0xfc, 0x4d, 0xf5, 0xa8, 0x37, +0xa9, 0x9d, 0x2f, 0x00, 0x2a, 0xe9, 0x9e, 0x2a, +0x3e, 0x65, 0xec, 0x8e, 0xf0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0xa6, +0xfb, 0xb7, 0x7e, 0x3c, 0x08, 0x48, 0xeb, 0xe2, +0xa3, 0x60, 0x2f, 0x2f, 0xda, 0xbd, 0x2a, 0x91, +0x98, 0xe6, 0x78, 0xa8, 0xc9, 0x1b, 0xb2, 0x1d, +0x4e, 0xa1, 0x8c, 0x65, 0x1f, 0x75, 0xd8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0x91, 0xe4, 0x4b, 0x76, 0x36, 0x87, 0x56, +0x84, 0xc8, 0x35, 0xd9, 0x2d, 0x58, 0x44, 0xf2, +0xf5, 0xd1, 0x51, 0x29, 0xc7, 0x40, 0xf7, 0xce, +0xa9, 0xfe, 0x4e, 0x61, 0xa2, 0x26, 0x74, 0x4f, +0x31, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0xb9, 0x83, 0xed, 0x99, 0x49, +0x95, 0xd9, 0xcc, 0xd4, 0xce, 0x4a, 0xc5, 0x74, +0x2e, 0xf4, 0xa8, 0x78, 0xe2, 0xd8, 0xb0, 0xa3, +0x6b, 0xe4, 0xfc, 0xed, 0xcc, 0xdb, 0x2e, 0x61, +0xfb, 0x41, 0x3e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0x56, 0x33, 0x95, +0xbb, 0x3c, 0x98, 0x2b, 0x95, 0x02, 0x1d, 0xb8, +0x61, 0xad, 0x67, 0x5c, 0x59, 0xd7, 0x28, 0x3d, +0x2b, 0x9f, 0xcc, 0x95, 0x7b, 0xb8, 0x3c, 0x67, +0xf9, 0xf2, 0xe1, 0x0e, 0x1a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0x10, +0xeb, 0x4f, 0xcb, 0x37, 0x1e, 0xc7, 0xd6, 0xc2, +0x51, 0x3f, 0x16, 0xc1, 0x78, 0x6f, 0x6a, 0x31, +0x93, 0xee, 0x43, 0x7c, 0x6f, 0xe4, 0x07, 0xb5, +0xe9, 0x35, 0xd0, 0x51, 0x97, 0xe3, 0x7a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0xd6, 0x07, 0x13, 0xda, 0x0b, 0x78, 0x64, +0x10, 0x3c, 0x95, 0xfb, 0x46, 0x9a, 0x52, 0x24, +0x40, 0x4a, 0x5b, 0xce, 0xbf, 0xd7, 0x6c, 0x9f, +0x59, 0xc4, 0x7b, 0x1b, 0xce, 0x97, 0x91, 0x92, +0x95, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0x03, 0xcc, 0xa3, 0x23, 0xc9, +0xc1, 0x6c, 0xa4, 0x63, 0x60, 0x46, 0x73, 0x8f, +0x8b, 0x55, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x64, 0x11, 0xc1, 0xc2, +0x15, 0xcc, 0xd2, 0xb8, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0x35, 0xe0, 0x1b, +0xd0, 0xb0, 0x94, 0xe8, 0xf6, 0x2e, 0xb7, 0x5c, +0xe6, 0x96, 0xcb, 0x86, 0xe4, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x14, +0xa2, 0xb8, 0xe9, 0x86, 0x2d, 0xdc, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0x96, +0xe2, 0xef, 0x91, 0x2a, 0x3a, 0xb3, 0x47, 0xd7, +0xe2, 0x8c, 0x4b, 0x49, 0x45, 0x63, 0x7d, 0xfa, +0x41, 0x8e, 0x3c, 0xf4, 0x49, 0x1e, 0xaa, 0xd8, +0x1c, 0x6a, 0xd4, 0xcc, 0x1b, 0x46, 0xc0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0x68, 0xfe, 0xdf, 0x4d, 0x87, 0xf3, 0x60, +0x8c, 0x92, 0xe5, 0xdb, 0x64, 0x1b, 0x08, 0x57, +0x44, 0x91, 0x3d, 0xda, 0xf4, 0xd2, 0x47, 0xaa, +0x4a, 0xbb, 0xdc, 0xa1, 0x2b, 0x44, 0xc7, 0x9f, +0xda, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0xff, 0x9a, 0x3e, 0xbf, 0x06, +0xf3, 0xc1, 0x81, 0xb9, 0xca, 0x56, 0xec, 0x97, +0x1d, 0xe6, 0x9c, 0x39, 0x17, 0x0a, 0xcf, 0xaa, +0x5a, 0x7d, 0xd3, 0xa2, 0xa7, 0xb5, 0x44, 0x92, +0x84, 0x8c, 0x56, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0xe3, 0x3c, 0x9b, +0x2a, 0x24, 0x91, 0xf9, 0x45, 0x52, 0x46, 0x78, +0x05, 0x47, 0xfd, 0x18, 0xd0, 0x61, 0xe4, 0x83, +0xc5, 0x43, 0x92, 0x07, 0x6b, 0x5a, 0x30, 0x97, +0xa6, 0x5b, 0xd5, 0x0d, 0xb4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0x4d, +0xa5, 0xfb, 0x3b, 0x32, 0xf6, 0xaa, 0x18, 0x37, +0xc1, 0xc7, 0x57, 0x82, 0x9f, 0xde, 0x5b, 0x7a, +0xb8, 0x68, 0x26, 0x71, 0x4e, 0xa9, 0x69, 0xf3, +0x29, 0xdf, 0x62, 0xaf, 0x6f, 0xd1, 0x41, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x2d, 0xd0, 0x80, 0xfc, 0xcb, 0xff, 0x58, +0xcd, 0x23, 0xcf, 0x71, 0xae, 0x29, 0xe8, 0xae, +0xce, 0xa7, 0xbd, 0x0e, 0xec, 0xb5, 0xda, 0x53, +0xdf, 0x9e, 0x8d, 0x25, 0x3d, 0x2f, 0x34, 0x1b, +0xbb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0x49, 0x48, 0x47, 0x22, 0x06, +0x9a, 0xae, 0x67, 0xab, 0x5d, 0xce, 0x14, 0x01, +0x50, 0xad, 0x79, 0xa4, 0x47, 0xfd, 0x45, 0xf6, +0x92, 0xe5, 0x8a, 0x85, 0x72, 0xc1, 0x86, 0x14, +0x40, 0x9b, 0x7b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x5e, 0x3d, 0xba, +0xa5, 0x8a, 0xc6, 0x14, 0xa8, 0xb6, 0x64, 0xe2, +0x66, 0xf5, 0x31, 0x0e, 0xd9, 0x11, 0x7b, 0xcc, +0x62, 0xde, 0x4b, 0x59, 0x69, 0xc0, 0x2a, 0x5b, +0x3b, 0xbc, 0xfc, 0x68, 0x20, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0xef, +0x26, 0x53, 0x55, 0xfe, 0x2b, 0xe4, 0xbc, 0x95, +0x5a, 0x00, 0x26, 0x91, 0xfc, 0xb8, 0xdb, 0xb1, +0xe1, 0x24, 0x17, 0x36, 0xcc, 0x53, 0xca, 0x9c, +0xc7, 0xc0, 0xaa, 0xe1, 0x98, 0xab, 0xe1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0xd3, 0xea, 0x1e, 0xdf, 0xbf, 0xd1, 0xa6, +0xcc, 0xc1, 0xff, 0x64, 0x21, 0xa9, 0xc6, 0x2c, +0x98, 0x9e, 0x2b, 0x21, 0x09, 0x57, 0x3f, 0x1a, +0x6e, 0x99, 0xaa, 0x24, 0x0f, 0x17, 0x8d, 0xc7, +0xe9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0x9f, 0xe7, 0x75, 0xdc, 0xf8, +0x3a, 0x11, 0x39, 0xdb, 0x00, 0x59, 0x5b, 0xb8, +0x6a, 0xa5, 0x6a, 0xe7, 0xbc, 0xfe, 0x17, 0x64, +0x29, 0xdb, 0xa5, 0x79, 0x1e, 0xb3, 0x82, 0x6c, +0x30, 0x62, 0xa0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x26, 0x45, 0x25, +0x4d, 0x5b, 0x97, 0xcb, 0x60, 0x02, 0xee, 0xbf, +0x1a, 0x79, 0xef, 0x38, 0x25, 0x3b, 0x9b, 0x08, +0x43, 0xcd, 0xa6, 0x28, 0x62, 0x8e, 0x1a, 0x14, +0xe7, 0xee, 0x03, 0x24, 0x33, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0x01, +0xee, 0xc3, 0xf5, 0x8e, 0x44, 0x59, 0x90, 0x84, +0xa8, 0xd1, 0xa0, 0xad, 0xd6, 0x1c, 0xff, 0xfa, +0x99, 0x7d, 0xe4, 0xe1, 0xb1, 0x35, 0x38, 0xa4, +0xa3, 0xb2, 0x3f, 0x43, 0xa1, 0xe7, 0xb7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0xa5, 0xfe, 0xd8, 0x28, 0xef, 0x40, 0xe4, +0x10, 0xab, 0x19, 0x13, 0xff, 0x2a, 0x59, 0xfa, +0xbc, 0xc0, 0xc9, 0x81, 0xc5, 0x37, 0xb6, 0xcf, +0xac, 0x48, 0x02, 0xf9, 0x91, 0x0b, 0x8f, 0x26, +0x6b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0xe0, 0x29, 0x2e, 0xaa, 0x68, +0xba, 0xcc, 0x47, 0xdf, 0x92, 0xf8, 0xdf, 0x27, +0x31, 0x79, 0x08, 0x53, 0x89, 0x0e, 0xc9, 0x7f, +0x39, 0x28, 0x8d, 0x81, 0xd4, 0x77, 0xdb, 0xd1, +0x31, 0x24, 0x3a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0xc7, 0x8f, 0x2a, +0xcf, 0xa7, 0x36, 0xc8, 0x9a, 0x85, 0x2f, 0x5e, +0xed, 0x88, 0x38, 0x0e, 0x4f, 0xcb, 0x28, 0x3f, +0x2a, 0x85, 0x03, 0xb3, 0x32, 0x03, 0x1a, 0x82, +0x13, 0xcf, 0xc5, 0xcb, 0x22, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0x2d, +0xd2, 0x5e, 0x8a, 0x63, 0x68, 0x0d, 0x78, 0x50, +0xb0, 0x49, 0xbe, 0xb8, 0x78, 0x00, 0xa3, 0x91, +0x77, 0xba, 0x08, 0x46, 0x01, 0x3c, 0xbd, 0x28, +0x0b, 0x7a, 0xbb, 0xa2, 0xae, 0x6a, 0x37, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0x79, 0x40, 0xe6, 0xab, 0x53, 0x20, 0xe2, +0x41, 0xf9, 0xaf, 0x4a, 0xb1, 0xa4, 0x8d, 0x43, +0xb0, 0xa7, 0x3b, 0x74, 0xb9, 0xd9, 0x5c, 0xd1, +0x72, 0xba, 0xbe, 0xd3, 0x72, 0x56, 0x51, 0x68, +0x31, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0xea, 0x84, 0x10, 0x6a, 0xa1, +0x0f, 0x99, 0x81, 0x17, 0xdf, 0x9a, 0x54, 0x25, +0x18, 0xb0, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xfd, 0x03, 0xb1, 0xf3, +0xe0, 0xcd, 0x51, 0x38, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x21, 0x90, 0xe4, +0x66, 0x5a, 0x38, 0x69, 0x2d, 0x07, 0xae, 0x86, +0x0c, 0xca, 0x80, 0x34, 0x09, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xae, 0x87, +0xe2, 0x9a, 0x3d, 0x5a, 0x00, 0x4b, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x9a, +0xad, 0x6b, 0x1f, 0x6f, 0x45, 0x68, 0xbe, 0xaa, +0x15, 0x62, 0x7f, 0x1e, 0xc9, 0x05, 0xea, 0x22, +0x9e, 0xd5, 0xf7, 0x5d, 0x8f, 0x77, 0x26, 0x4c, +0x50, 0x0e, 0xd0, 0x32, 0xb3, 0x1c, 0x4d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0x1d, 0x60, 0xee, 0xc0, 0x9a, 0x9e, 0xfc, +0xf7, 0xef, 0x5b, 0x07, 0x56, 0xfc, 0xa1, 0x4f, +0x5a, 0x4b, 0x31, 0xbd, 0x82, 0xc9, 0x22, 0x59, +0xa1, 0x15, 0x40, 0x0e, 0xec, 0x2d, 0x42, 0xc9, +0xbc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0x7e, 0xb7, 0xb4, 0xe1, 0x6b, +0x37, 0x74, 0x85, 0xae, 0xda, 0xd3, 0x77, 0x79, +0xc0, 0x5c, 0xdd, 0x5f, 0x5a, 0x10, 0x20, 0x09, +0xbd, 0x5d, 0x09, 0x2b, 0x7a, 0x2d, 0xb7, 0x8b, +0x22, 0x75, 0xca, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0xf3, 0xe7, 0x86, +0x6b, 0xc1, 0x5b, 0x33, 0x9d, 0x3e, 0x85, 0xc6, +0xe9, 0xbc, 0xa2, 0xf8, 0xee, 0xe1, 0xdf, 0xe4, +0x8d, 0xf5, 0x47, 0x51, 0x67, 0xae, 0x7e, 0x2e, +0x6b, 0x81, 0xdb, 0x4e, 0x47, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0xb7, +0xca, 0x17, 0xe7, 0x5f, 0x96, 0x0c, 0x01, 0xa6, +0x84, 0x36, 0x07, 0x07, 0x3f, 0x4a, 0x89, 0x7f, +0x51, 0x86, 0x20, 0x7a, 0x90, 0x24, 0xe4, 0x13, +0xfe, 0x4a, 0x46, 0x25, 0x26, 0x20, 0x39, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x51, 0xfb, 0x5a, 0x4f, 0x21, 0x21, 0x75, +0xb2, 0x51, 0x5f, 0x65, 0x5c, 0x4b, 0xa3, 0x61, +0x7f, 0x83, 0x86, 0xc4, 0x6c, 0x24, 0x18, 0x70, +0x1f, 0x82, 0x9a, 0xce, 0x6d, 0x69, 0xda, 0x34, +0x55, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0x0b, 0xa9, 0xbb, 0x2b, 0x68, +0x90, 0xd1, 0xc6, 0xd3, 0x7b, 0x0d, 0x7a, 0x0c, +0xd4, 0xe6, 0x7d, 0x52, 0xe8, 0x96, 0x70, 0xde, +0x7c, 0xd4, 0x95, 0x50, 0x53, 0x44, 0x23, 0x39, +0x3e, 0x68, 0xbf, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0x50, 0xd6, 0xc5, +0xaa, 0x8c, 0x53, 0xf2, 0x73, 0xcb, 0xca, 0x99, +0x93, 0xa6, 0xb1, 0xf1, 0xc1, 0x30, 0x5a, 0xcb, +0x31, 0x30, 0xf5, 0x65, 0xae, 0x32, 0x6c, 0x00, +0xb3, 0xd2, 0x90, 0x91, 0x6a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0x65, +0xaa, 0x3f, 0x4d, 0xa6, 0xdf, 0x3b, 0xbc, 0xbf, +0xea, 0xed, 0xbe, 0x67, 0x01, 0xfe, 0xd6, 0x05, +0xdb, 0x84, 0x54, 0xa4, 0x5f, 0x4e, 0xf2, 0xad, +0xa6, 0x28, 0x85, 0x51, 0xba, 0x08, 0x66, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0x31, 0x8e, 0x54, 0xb7, 0x10, 0x76, 0xe0, +0x71, 0xc4, 0xe0, 0x1c, 0xf4, 0x24, 0xd6, 0x84, +0x03, 0x4c, 0x8d, 0x31, 0xfa, 0xf3, 0x25, 0x01, +0xcd, 0xd8, 0xda, 0x59, 0x1e, 0xee, 0x56, 0xa7, +0xcf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0x03, 0xda, 0x6f, 0x2a, 0x8d, +0x39, 0x00, 0x08, 0x18, 0x4a, 0x21, 0x19, 0x13, +0xb0, 0xe3, 0x0e, 0x09, 0x1d, 0x7b, 0xbd, 0x7a, +0xdc, 0x2f, 0x97, 0x30, 0x47, 0x11, 0x4b, 0x48, +0xcb, 0xdd, 0x86, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0xae, 0xf3, 0x43, +0x38, 0xd5, 0x61, 0xb5, 0xc6, 0xb6, 0xc6, 0xa5, +0xba, 0x4c, 0x58, 0x6f, 0xfa, 0x65, 0x72, 0x33, +0xf6, 0x48, 0x9e, 0x86, 0xb6, 0xfe, 0x99, 0xef, +0xa8, 0x2c, 0xd8, 0xc8, 0x5c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0xe1, +0x56, 0x6f, 0x68, 0x64, 0x05, 0xe4, 0x8d, 0xb9, +0x7a, 0xfa, 0xbf, 0x46, 0x6a, 0xbc, 0xd5, 0x49, +0xe0, 0x04, 0x15, 0xfd, 0x20, 0x98, 0x3a, 0x50, +0xe0, 0x94, 0xa8, 0xa9, 0xe1, 0x30, 0x65, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0xa9, 0x08, 0xa5, 0x91, 0xbc, 0x3a, 0x0b, +0x05, 0xb5, 0x1f, 0x77, 0xec, 0x1a, 0x96, 0xff, +0x52, 0x1e, 0x9e, 0x88, 0x8a, 0x93, 0x73, 0x48, +0x8d, 0xb4, 0xf3, 0x19, 0xe1, 0xd2, 0x26, 0xb2, +0xdc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0x97, 0x10, 0xcd, 0x00, 0xe0, +0xb2, 0x56, 0x68, 0xe3, 0xa6, 0x24, 0xe8, 0x23, +0xb3, 0x4d, 0x7a, 0x08, 0xc9, 0xed, 0x9b, 0xc1, +0x78, 0x10, 0xb6, 0xb4, 0x86, 0x32, 0x66, 0x6f, +0x20, 0x1b, 0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0xf3, 0xb1, 0x54, +0x46, 0x78, 0x90, 0xf7, 0x6d, 0xb6, 0x10, 0x65, +0x33, 0xc0, 0xec, 0x0c, 0x88, 0x4d, 0x22, 0xab, +0xab, 0xe2, 0xd3, 0xd7, 0xb5, 0x1b, 0xe3, 0x53, +0xab, 0x20, 0xe1, 0x64, 0x04, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0x6d, +0x8c, 0xa2, 0x5f, 0x5f, 0x24, 0xf1, 0xc5, 0xea, +0x40, 0x4b, 0xd6, 0x4f, 0x0c, 0x6c, 0x62, 0x11, +0x44, 0xbc, 0x87, 0x52, 0x6e, 0x50, 0x5f, 0xe2, +0x6b, 0x50, 0x9e, 0x9f, 0x16, 0x70, 0x4d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0x1e, 0xa7, 0x00, 0x45, 0x89, 0x90, 0x28, +0xda, 0x47, 0x34, 0x43, 0x05, 0x35, 0xff, 0xfb, +0x06, 0x04, 0xdb, 0xaa, 0x41, 0x36, 0x36, 0xb3, +0x70, 0x8a, 0x45, 0xb4, 0x25, 0x95, 0x60, 0x12, +0x46, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0xc4, 0x44, 0x88, 0xec, 0xc6, +0xaa, 0x9b, 0x24, 0x8b, 0x00, 0x6e, 0xe6, 0x77, +0x25, 0x4b, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x48, 0xc2, 0x5b, 0xce, +0x36, 0x8c, 0xd7, 0xb8, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0xfd, 0x89, 0x12, +0x3f, 0x75, 0x7f, 0x45, 0x5c, 0x02, 0x40, 0xa7, +0x4d, 0x75, 0x88, 0x0e, 0xa3, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb, 0xd0, +0xf9, 0xdb, 0x18, 0x90, 0x87, 0x75, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0x93, +0x92, 0x3b, 0x1e, 0x13, 0x73, 0x2d, 0x59, 0x83, +0xdb, 0xac, 0x12, 0xf0, 0x82, 0x83, 0x41, 0x5b, +0xe6, 0xd4, 0x32, 0xb9, 0x23, 0x95, 0xd7, 0x3c, +0x55, 0x3f, 0x03, 0xe6, 0x36, 0xaa, 0x6f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0x94, 0x37, 0x80, 0x9d, 0x72, 0xd6, 0x38, +0xf7, 0x10, 0x99, 0xfc, 0xed, 0xce, 0x4b, 0x9b, +0xfc, 0x05, 0x0a, 0xd2, 0x64, 0x2d, 0x6f, 0x78, +0x5a, 0x66, 0x38, 0x28, 0xb7, 0x07, 0x96, 0xfc, +0x4e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0x30, 0x5c, 0x34, 0xb6, 0xf3, +0xd4, 0xf6, 0xfe, 0x29, 0x4c, 0xe0, 0x72, 0xdc, +0xcb, 0xcb, 0x1c, 0x06, 0x22, 0xbf, 0x54, 0x43, +0x13, 0x95, 0x4b, 0xf0, 0x69, 0xaf, 0xa7, 0x92, +0x94, 0x65, 0xe6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x56, 0x74, 0xec, +0x7c, 0x44, 0x43, 0x7d, 0x02, 0xfd, 0xf3, 0x1f, +0x09, 0x85, 0x80, 0x70, 0x6f, 0x81, 0xad, 0x27, +0x3e, 0x55, 0x6c, 0x1b, 0x4e, 0x9b, 0x19, 0xd9, +0xf1, 0x6e, 0xd1, 0xce, 0x6c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0xf4, +0x64, 0xf2, 0x57, 0xde, 0x71, 0xb5, 0xb6, 0x1f, +0x06, 0x0d, 0x58, 0x22, 0x48, 0xa4, 0xaf, 0x20, +0x95, 0x65, 0xdc, 0xb7, 0x63, 0xe1, 0xc9, 0x1a, +0x9c, 0x1e, 0x2d, 0x8f, 0xeb, 0x48, 0x87, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0xbf, 0x9f, 0xd9, 0x68, 0x26, 0xcf, 0x66, +0xc0, 0x01, 0xbe, 0x05, 0x11, 0x3d, 0x0c, 0x20, +0xcf, 0x4c, 0xa0, 0x6d, 0x58, 0x15, 0x5b, 0xc8, +0xac, 0xe1, 0x06, 0x54, 0xa9, 0xb5, 0xcb, 0x19, +0xcf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0x09, 0x45, 0x45, 0x7c, 0x5e, +0xfa, 0xa5, 0xf8, 0x71, 0x19, 0x12, 0x91, 0xeb, +0xef, 0x20, 0xb5, 0x0e, 0x23, 0x7d, 0xdb, 0xd5, +0xb1, 0xb1, 0x2d, 0xc3, 0x39, 0x5e, 0xb4, 0x9e, +0xd7, 0x61, 0x33, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0x4f, 0x50, 0x3d, +0x15, 0x7b, 0x86, 0x9f, 0x97, 0x6a, 0x9d, 0x47, +0x36, 0x33, 0xe1, 0xf0, 0x63, 0xde, 0x0a, 0xa2, +0x96, 0x41, 0x06, 0xbb, 0xf7, 0x25, 0x79, 0x19, +0xef, 0x00, 0x82, 0xbf, 0x5d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0x57, +0xe4, 0x52, 0x9e, 0xb2, 0x70, 0x5d, 0x4e, 0xa2, +0x3b, 0xe9, 0xc3, 0x14, 0x80, 0x59, 0xb3, 0x16, +0x7b, 0xc7, 0x6f, 0xac, 0x90, 0xf6, 0x15, 0xe8, +0xa9, 0xeb, 0xf0, 0xcb, 0x24, 0x60, 0x14, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0x6a, 0xd7, 0xf2, 0x66, 0x19, 0xf4, 0xa9, +0xc9, 0x1c, 0x76, 0xa9, 0x91, 0xf9, 0xf7, 0x23, +0xbd, 0xb9, 0x8a, 0xda, 0x7c, 0x04, 0x2d, 0x56, +0xe4, 0xc2, 0x9e, 0x7b, 0xf0, 0x42, 0x05, 0x99, +0x1c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x4f, 0xad, 0x9a, 0x0d, 0xee, +0x0b, 0xeb, 0x93, 0x18, 0x75, 0x5f, 0x24, 0xae, +0x1c, 0xae, 0x55, 0xa3, 0x5e, 0x0b, 0x65, 0x5a, +0x03, 0x74, 0x9c, 0xad, 0xba, 0x6a, 0xeb, 0x8a, +0xb7, 0x16, 0x8a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0xf0, 0xdb, 0x2f, +0xcf, 0x89, 0xcb, 0xed, 0x53, 0x57, 0xb0, 0xb7, +0x00, 0x15, 0x91, 0xde, 0x69, 0x02, 0xb1, 0x86, +0xd3, 0x18, 0x50, 0xe5, 0x0c, 0xa2, 0x81, 0xce, +0x9c, 0x21, 0x68, 0xd4, 0xb4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0x98, +0x88, 0x2b, 0x1a, 0xef, 0xcf, 0x91, 0x3f, 0x38, +0xf6, 0x36, 0x78, 0xfb, 0x1e, 0x73, 0x5f, 0x91, +0xb0, 0x54, 0xe1, 0xe0, 0xb1, 0x15, 0xc2, 0x7b, +0x8c, 0xb9, 0x38, 0x4c, 0x04, 0x6a, 0xcf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0x67, 0x4c, 0x42, 0x74, 0xf2, 0xed, 0xfc, +0x43, 0x12, 0xba, 0xcf, 0x6c, 0x3d, 0xa0, 0xa6, +0xa2, 0x70, 0xe7, 0xef, 0xfd, 0x86, 0x64, 0x0d, +0xaf, 0x67, 0x58, 0x8b, 0x52, 0x05, 0x98, 0x9d, +0xd3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0xc0, 0xa0, 0x3c, 0xe0, 0x9b, +0x91, 0x9e, 0x69, 0x93, 0x38, 0xe5, 0xa4, 0x15, +0xa3, 0x86, 0x12, 0x02, 0x9b, 0x97, 0x04, 0x37, +0x93, 0x30, 0xc7, 0xa2, 0x15, 0x8f, 0xb7, 0x67, +0xd2, 0xe2, 0xa5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0xb3, 0x2e, 0x1e, +0x54, 0xc9, 0x51, 0xe0, 0x15, 0xb9, 0xae, 0xeb, +0x31, 0x2c, 0x7b, 0x83, 0x20, 0xe1, 0xb5, 0xb5, +0xcd, 0xa9, 0x3d, 0x83, 0xce, 0xe0, 0xa5, 0x31, +0x9d, 0x3d, 0x2d, 0x1f, 0x59, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0x67, +0x99, 0xf9, 0x2d, 0x5f, 0x3a, 0x38, 0x8a, 0xab, +0xf0, 0x34, 0xd3, 0x7a, 0x68, 0x42, 0xa8, 0xe3, +0xeb, 0x79, 0xd6, 0xd5, 0x14, 0x2c, 0xf1, 0xfa, +0x27, 0x5c, 0x71, 0xe0, 0xec, 0x64, 0xd6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x44, 0xd6, 0x9d, 0x42, 0x39, 0x48, 0x84, +0xeb, 0x23, 0xb1, 0x63, 0x52, 0x64, 0x85, 0x11, +0x1c, 0xd7, 0x94, 0xcf, 0x58, 0x0f, 0x69, 0xaf, +0x06, 0x2b, 0xa0, 0xd5, 0x07, 0x4d, 0xa8, 0x69, +0x0b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0x65, 0x44, 0xe1, 0xac, 0xf7, +0x55, 0xad, 0x5b, 0x0e, 0x42, 0x90, 0x74, 0xca, +0x88, 0xd4, 0xc3, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x87, 0x59, 0x34, 0xb7, +0x83, 0xf9, 0x73, 0x27, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0x21, 0x6f, 0x7b, +0x5e, 0xb0, 0xe8, 0x6d, 0xa5, 0x81, 0xc2, 0x15, +0x02, 0x11, 0xec, 0xee, 0x5a, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9e, 0x16, +0x09, 0x4e, 0xfc, 0x9f, 0x34, 0x92, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0xdb, +0xee, 0x18, 0x5c, 0xd3, 0x64, 0x64, 0x75, 0xb3, +0xb5, 0x5a, 0x5b, 0xbb, 0x4f, 0x3e, 0x91, 0xcf, +0x60, 0x01, 0x3b, 0x86, 0x38, 0xd7, 0x69, 0xe8, +0x63, 0xca, 0xc9, 0x46, 0xe2, 0xbf, 0xf8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0xe9, 0x7f, 0x8f, 0x51, 0x4c, 0x57, 0x62, +0x00, 0xb7, 0x34, 0x68, 0xb8, 0x52, 0x4d, 0x7e, +0xef, 0xe7, 0x31, 0xf1, 0x98, 0xa8, 0x89, 0x36, +0x46, 0xc2, 0xf9, 0x04, 0xc3, 0xfb, 0x52, 0x0f, +0x3d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0xe5, 0xd0, 0x9d, 0xbf, 0x01, +0xb1, 0x46, 0x39, 0xff, 0xa6, 0x9e, 0xe7, 0xe5, +0xf7, 0x9a, 0xee, 0x6b, 0x71, 0x13, 0x7c, 0x39, +0xe2, 0xe9, 0xc2, 0x23, 0xf9, 0x50, 0xea, 0x97, +0x42, 0x2d, 0xbd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0xaa, 0xb7, 0x50, +0x32, 0xfb, 0xfa, 0xcd, 0x79, 0x3d, 0xb1, 0xa4, +0x11, 0x72, 0xd4, 0x74, 0x95, 0x05, 0x43, 0xa0, +0xdb, 0x7c, 0x91, 0xf8, 0xba, 0xa4, 0x77, 0x11, +0x25, 0xe6, 0x1c, 0xcd, 0x28, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0x3d, +0x41, 0x74, 0x48, 0x8d, 0x98, 0xac, 0x87, 0x10, +0x13, 0xdf, 0x68, 0xdf, 0xa0, 0xe2, 0xa1, 0xd8, +0x5c, 0x7f, 0x22, 0x4b, 0x01, 0x08, 0x73, 0xb6, +0x2c, 0x2e, 0x19, 0xac, 0x7f, 0x0d, 0x9d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0x22, 0x00, 0x63, 0x78, 0xc3, 0x19, 0x88, +0xc8, 0x05, 0x5f, 0x90, 0x0c, 0x1e, 0x79, 0xfe, +0xff, 0x7f, 0x4c, 0xb2, 0x2a, 0x4e, 0xcc, 0x3e, +0x12, 0x47, 0x4d, 0xec, 0xa0, 0x67, 0x2e, 0x49, +0x21, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0x9c, 0xb1, 0xf8, 0xdd, 0x2a, +0xf5, 0x5f, 0x37, 0xf6, 0x55, 0x83, 0xad, 0x3b, +0xcd, 0xc7, 0x8b, 0x82, 0x3d, 0x3b, 0x10, 0xb2, +0x8e, 0x44, 0x37, 0x20, 0x84, 0x18, 0x07, 0xdc, +0xc3, 0xdd, 0x44, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0x28, 0x57, 0xbd, +0xce, 0x9e, 0x2e, 0x7c, 0x91, 0x13, 0x6c, 0xf4, +0x6d, 0xa9, 0xc2, 0x98, 0x30, 0xde, 0x60, 0xf4, +0xe7, 0x93, 0x55, 0xfd, 0xb2, 0x76, 0xaa, 0xed, +0x82, 0x61, 0xa7, 0x89, 0x5c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0x23, +0xcd, 0x59, 0xcf, 0xdd, 0xd4, 0xbf, 0x2e, 0x53, +0x33, 0x6f, 0x0e, 0xe2, 0x16, 0xd0, 0xab, 0xa6, +0x11, 0x53, 0x55, 0xce, 0x16, 0x6d, 0x21, 0xc5, +0x31, 0xf0, 0xe3, 0x6d, 0xc7, 0xf7, 0x4b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0xe9, 0xfc, 0x35, 0xca, 0xab, 0xd0, 0x09, +0x62, 0x8e, 0xe2, 0x1a, 0xe1, 0x28, 0x43, 0x25, +0xb8, 0x46, 0x59, 0x5d, 0xb1, 0x27, 0x4d, 0x0e, +0x9e, 0xc2, 0xe6, 0x72, 0xd2, 0x62, 0xbe, 0x4b, +0x79, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0xa8, 0x11, 0x0c, 0x36, 0x52, +0xe4, 0x18, 0xc7, 0xfc, 0x18, 0x0f, 0xb9, 0x50, +0x77, 0x6d, 0x91, 0xb0, 0xf4, 0xea, 0x0a, 0xb5, +0x07, 0xcf, 0xb7, 0x76, 0x22, 0x15, 0xdd, 0x8f, +0xb8, 0x35, 0x22, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0x59, 0xcd, 0xd3, +0x46, 0xf9, 0x82, 0xe1, 0x27, 0x98, 0x2d, 0x89, +0x46, 0x92, 0xa8, 0xae, 0x45, 0xcf, 0xb5, 0xa7, +0x37, 0xff, 0x22, 0x49, 0xe6, 0xca, 0xd8, 0xde, +0x34, 0x0c, 0x94, 0x39, 0x19, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0x17, +0x32, 0xb4, 0xd5, 0x6a, 0x71, 0xfd, 0x76, 0x80, +0xa5, 0x5b, 0x9a, 0x9b, 0x73, 0xac, 0x72, 0x41, +0x48, 0x6e, 0xc3, 0x4f, 0x3f, 0xdf, 0x0a, 0x66, +0xa7, 0x10, 0x87, 0x79, 0x5a, 0x03, 0x3f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0x93, 0x7e, 0xdf, 0xaa, 0x4c, 0x12, 0x04, +0x12, 0x15, 0x5b, 0x50, 0xc6, 0xb4, 0x30, 0x91, +0xd8, 0xd7, 0xa5, 0x43, 0xbc, 0xe8, 0x75, 0x0e, +0x01, 0x4e, 0xb4, 0x41, 0x2a, 0xf4, 0x95, 0x06, +0x1e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0x0a, 0x7a, 0x8e, 0x78, 0xbe, +0xe3, 0x97, 0x39, 0xd8, 0xb7, 0x70, 0x43, 0x5e, +0x8c, 0xf9, 0x93, 0xb1, 0x29, 0xa6, 0x85, 0xeb, +0x05, 0x75, 0x65, 0x6f, 0x3b, 0xbf, 0xe9, 0x58, +0x5c, 0x2f, 0xed, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0xd9, 0xb5, 0x8b, +0x45, 0xd3, 0x2d, 0x23, 0x6d, 0x0f, 0x72, 0xd8, +0x4c, 0xec, 0x9d, 0x21, 0x31, 0x52, 0xb3, 0xf4, +0x32, 0x0b, 0x90, 0x97, 0x7c, 0x40, 0x45, 0xef, +0x78, 0x2c, 0x28, 0xe1, 0x43, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x67, +0xa9, 0xc9, 0x32, 0xb5, 0x91, 0x13, 0x81, 0xf9, +0xf0, 0xd8, 0xc1, 0xd8, 0x1d, 0xe2, 0x89, 0xe7, +0x39, 0xf6, 0x44, 0x33, 0xb5, 0x22, 0x3f, 0xf1, +0xd3, 0x8f, 0xc6, 0x7d, 0x9d, 0xa3, 0x4c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0xa2, 0xbe, 0xd5, 0xbb, 0x7d, 0x01, 0x61, +0xf3, 0x98, 0x33, 0x50, 0x00, 0xb8, 0xce, 0x8f, +0xb6, 0x17, 0xa6, 0xfc, 0xb2, 0x88, 0xbf, 0xc8, +0xf1, 0x17, 0x12, 0x42, 0x85, 0x91, 0x58, 0x32, +0x0a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0x76, 0x80, 0x1f, 0xe2, 0x74, +0xc7, 0x05, 0x7f, 0x66, 0x4a, 0x81, 0x7f, 0x4e, +0xdb, 0x21, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x31, 0x29, 0xc9, 0x0f, +0x1c, 0x05, 0xae, 0x17, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0x74, 0x1f, 0x34, +0xbd, 0x30, 0x61, 0x77, 0xc2, 0x0d, 0x41, 0xe2, +0xd5, 0xee, 0x70, 0x20, 0xee, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x7d, +0x08, 0xd8, 0x5b, 0x73, 0x0d, 0x17, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0xc7, +0x5b, 0xe4, 0x23, 0x6b, 0xcf, 0x4b, 0x9e, 0x45, +0x8d, 0x13, 0x4a, 0x96, 0xb1, 0xa8, 0x73, 0x75, +0xce, 0x67, 0xfd, 0x3e, 0x18, 0xc7, 0x5e, 0xa5, +0xbb, 0x3c, 0x22, 0x91, 0x19, 0xc9, 0x17, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0x5c, 0x20, 0x36, 0x16, 0x3e, 0xa5, 0x7e, +0x1c, 0x83, 0xe2, 0xed, 0x8c, 0x48, 0xd4, 0xc6, +0xd3, 0x38, 0xd8, 0xd0, 0xaf, 0xe0, 0x0c, 0x92, +0x95, 0x36, 0xdf, 0x5d, 0xec, 0x2d, 0xbc, 0x68, +0x30, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0x51, 0xd0, 0x07, 0xba, 0x13, +0x84, 0xce, 0x79, 0x07, 0x72, 0xf1, 0x83, 0x00, +0x7f, 0x39, 0x76, 0x28, 0x6a, 0x64, 0x8c, 0x37, +0xc5, 0xf9, 0xe0, 0xc9, 0x95, 0xa2, 0x1f, 0x01, +0x1f, 0x5f, 0xcc, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x17, 0xd0, 0xd0, +0x64, 0x0d, 0x7d, 0x6e, 0xee, 0xc4, 0x0d, 0x85, +0x0e, 0x5e, 0xcb, 0xc5, 0x0e, 0xd9, 0x97, 0x69, +0xee, 0x9d, 0xe3, 0x00, 0x2e, 0x86, 0x95, 0x1d, +0x1f, 0x87, 0x35, 0xc3, 0xf4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0xf4, +0x50, 0xb6, 0x7b, 0x54, 0xa7, 0x79, 0xbd, 0xff, +0x1d, 0x87, 0xc6, 0x6e, 0x28, 0x79, 0xfd, 0x60, +0xb3, 0x8d, 0x72, 0x41, 0xaf, 0x4a, 0x55, 0xc3, +0x35, 0x76, 0x2e, 0x61, 0xcf, 0x34, 0x03, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0xdd, 0xef, 0xdb, 0x13, 0x3c, 0x80, 0x26, +0x25, 0x7a, 0x05, 0xb1, 0x42, 0x8c, 0x5f, 0x98, +0x83, 0x91, 0x7a, 0x2b, 0x86, 0x8a, 0x6a, 0x92, +0x42, 0x8c, 0x3c, 0xb9, 0x7e, 0xba, 0x86, 0xeb, +0xc9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0xf6, 0x29, 0xd6, 0xeb, 0x79, +0x1f, 0xd3, 0x4e, 0x05, 0xbf, 0x13, 0x46, 0x6a, +0x85, 0x9a, 0x92, 0xb6, 0x1c, 0x4d, 0x01, 0x8f, +0x7c, 0xd8, 0xae, 0x1f, 0xfa, 0xf2, 0xca, 0xf3, +0x4e, 0xea, 0x87, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x1e, 0x80, 0x38, +0x5e, 0x0f, 0x3b, 0x6a, 0x77, 0xa5, 0x7d, 0x7e, +0xeb, 0xb0, 0x7a, 0xb0, 0x78, 0xc8, 0x9d, 0xf3, +0xe0, 0x2d, 0x54, 0x66, 0x31, 0x55, 0x88, 0x0f, +0xf6, 0xcc, 0x70, 0xcd, 0xd6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0x2d, +0x69, 0x49, 0x25, 0xc2, 0xf4, 0xb7, 0x22, 0xe6, +0x0b, 0xfe, 0xa3, 0x32, 0xec, 0x30, 0xa8, 0x08, +0x28, 0xce, 0xbb, 0xc5, 0xfe, 0x5a, 0x9d, 0xd4, +0x38, 0xa9, 0xd5, 0x87, 0x4a, 0xfe, 0xb8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0x16, 0x5b, 0x62, 0x6d, 0x94, 0x0e, 0x2e, +0x98, 0x27, 0xac, 0x79, 0xcc, 0x15, 0xfb, 0x44, +0x51, 0x5f, 0xcb, 0xc2, 0x11, 0x3a, 0x0b, 0x31, +0x06, 0xeb, 0x17, 0xe2, 0xe7, 0xa0, 0xec, 0xc5, +0x12, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0xdf, 0x8a, 0xef, 0x40, 0xe4, +0x51, 0x44, 0xd5, 0xe4, 0xa9, 0x0c, 0x84, 0x57, +0x88, 0x18, 0x61, 0x80, 0x41, 0x46, 0x0c, 0x7d, +0xa7, 0x84, 0x8c, 0xa0, 0x8f, 0x87, 0xcf, 0x41, +0x1d, 0x1c, 0xaf, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0x5d, 0x1e, 0x88, +0x11, 0x30, 0x71, 0xfb, 0x64, 0x3d, 0xd4, 0x42, +0x32, 0x80, 0xfb, 0x4d, 0xd2, 0x7e, 0x54, 0xb1, +0x3e, 0x9f, 0x41, 0xb3, 0x86, 0x5b, 0xfd, 0x5e, +0x76, 0xa1, 0x13, 0xff, 0xf7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x4a, +0x3d, 0x90, 0xee, 0x57, 0x87, 0x84, 0xaf, 0xa9, +0x3a, 0x43, 0xc7, 0xe4, 0x1c, 0x22, 0x44, 0xf3, +0xf2, 0x89, 0x6a, 0x5c, 0xe5, 0xc7, 0xc0, 0x16, +0xc6, 0xd5, 0x89, 0x46, 0x90, 0x0b, 0x8a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0xa1, 0x47, 0xaa, 0x2c, 0xd2, 0x9b, 0xc7, +0x62, 0x08, 0x60, 0xe1, 0x23, 0x2c, 0xf2, 0xa9, +0x55, 0xec, 0x9e, 0x2d, 0x32, 0xf8, 0x33, 0x9a, +0xba, 0xa7, 0x09, 0xb8, 0xf5, 0x73, 0x40, 0x71, +0x6b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0x25, 0x98, 0xca, 0xbb, 0x75, +0x78, 0x7f, 0xac, 0xa9, 0x40, 0x60, 0x79, 0x0c, +0xb0, 0x7d, 0x8e, 0xc8, 0xc8, 0x4a, 0xff, 0xe9, +0x59, 0xeb, 0x58, 0x9b, 0x7d, 0x9e, 0x7b, 0x6f, +0x14, 0xe2, 0x8c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0xde, 0x15, 0x0b, +0xb1, 0x60, 0xf1, 0x96, 0x9e, 0x0b, 0x8e, 0x74, +0x82, 0xc8, 0xd3, 0x8f, 0xce, 0x09, 0xab, 0x61, +0x89, 0x60, 0x70, 0x8e, 0xa0, 0x51, 0xbf, 0xdc, +0xa6, 0x13, 0xfe, 0x2a, 0xef, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0xe8, +0xbf, 0xcd, 0x88, 0x7f, 0x31, 0xc3, 0x20, 0xbd, +0xfe, 0xc9, 0x1b, 0x55, 0xa9, 0x4a, 0x1b, 0x72, +0xb8, 0xe3, 0x48, 0x58, 0xbd, 0x8a, 0xb0, 0x0f, +0x59, 0xec, 0x78, 0xf2, 0xdc, 0x62, 0xf0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0xdb, 0x0e, 0x3a, 0x35, 0xeb, 0x03, 0xcd, +0x79, 0x1e, 0x6f, 0x62, 0xab, 0x8b, 0x1b, 0x10, +0xe2, 0x7a, 0x0d, 0xc6, 0x91, 0xe5, 0x08, 0x2a, +0xe0, 0xcd, 0xc8, 0x96, 0xab, 0x86, 0xaf, 0xe6, +0xf3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0x64, 0x1f, 0xfe, 0xba, 0xa9, +0xdd, 0x23, 0x11, 0xe7, 0xf9, 0x2d, 0xd1, 0x9d, +0xcb, 0xa4, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xdd, 0x88, 0x96, 0x47, +0xc6, 0xb1, 0x4c, 0x94, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0x31, 0xdb, 0x6a, +0xab, 0x6e, 0xcf, 0x31, 0x87, 0x24, 0xb5, 0x38, +0x62, 0x8f, 0xcc, 0xae, 0x04, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x6c, +0x49, 0x78, 0x93, 0x19, 0xa0, 0xdd, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0xd1, +0xf7, 0x65, 0x2f, 0x88, 0x99, 0x80, 0x38, 0x2a, +0x78, 0x43, 0xc6, 0xd0, 0x97, 0x45, 0x18, 0xb1, +0x60, 0x0c, 0x2c, 0x19, 0xb2, 0x2a, 0x12, 0xc8, +0x11, 0x8d, 0xf2, 0x18, 0xf3, 0x2f, 0x55, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0x29, 0x7c, 0x91, 0x85, 0xcd, 0xcc, 0x8c, +0xc5, 0xf2, 0xb3, 0xe3, 0x5c, 0x26, 0x63, 0x48, +0x23, 0x15, 0x8a, 0x79, 0xd3, 0xa4, 0x1f, 0xc2, +0x29, 0xec, 0x7f, 0x69, 0xec, 0xb5, 0x6b, 0xe4, +0x96, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0xc6, 0x0a, 0xf1, 0x1d, 0xca, +0xb3, 0xda, 0x1e, 0x74, 0xb2, 0x14, 0xcc, 0x24, +0x4e, 0xb2, 0x41, 0x65, 0x5a, 0x99, 0x0c, 0xec, +0xc9, 0xbb, 0xbb, 0x17, 0x9d, 0xd2, 0x8f, 0x86, +0xb4, 0x8a, 0x83, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0xe0, 0x91, 0xe4, +0xa3, 0x7d, 0xc8, 0x78, 0x50, 0x79, 0x6f, 0xa3, +0x1a, 0xad, 0xd6, 0x5b, 0xa2, 0x94, 0x1f, 0x8b, +0x5a, 0xb0, 0x54, 0x7c, 0xbc, 0x08, 0x6f, 0x3a, +0x17, 0x0e, 0xc3, 0xfc, 0x33, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0xc4, +0x5f, 0x47, 0x32, 0xa1, 0xcf, 0x5d, 0x47, 0x1b, +0x6d, 0xcf, 0xed, 0x2d, 0x1c, 0xdb, 0x46, 0x4b, +0xee, 0x29, 0xa4, 0x67, 0x85, 0xb3, 0x70, 0x3b, +0xa1, 0xab, 0x9d, 0xd7, 0x00, 0x41, 0x09, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0xc7, 0x45, 0x5a, 0x1d, 0xfe, 0xa1, 0x4f, +0x10, 0xff, 0xcf, 0x21, 0xe2, 0xaf, 0xde, 0x06, +0xaf, 0xd2, 0x49, 0x9e, 0xd5, 0x6a, 0x4c, 0x15, +0x30, 0xde, 0xd5, 0x54, 0xaa, 0xc7, 0x62, 0xac, +0x43, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0xd2, 0x81, 0x92, 0xf8, 0xd1, +0x24, 0xe8, 0xe0, 0x2d, 0x38, 0x44, 0x28, 0x48, +0xae, 0x19, 0xc2, 0x05, 0x59, 0x1a, 0xee, 0xd3, +0xf4, 0xdf, 0xd8, 0xf3, 0xa7, 0x7d, 0x8d, 0xcf, +0x2e, 0x63, 0xa9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x22, 0x6a, 0xed, +0x3b, 0x3c, 0x5c, 0xe0, 0xe5, 0x90, 0xd0, 0x90, +0x09, 0x3c, 0x78, 0xda, 0x17, 0xc4, 0xac, 0x8b, +0xe4, 0xbf, 0x89, 0x6d, 0x20, 0xc9, 0x0e, 0xa1, +0x1f, 0x2d, 0xa3, 0x66, 0x9c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x8a, +0x24, 0x37, 0x28, 0xe6, 0x67, 0x8f, 0xbb, 0xdb, +0x78, 0xf9, 0x01, 0x1b, 0x14, 0x7c, 0x9b, 0xf5, +0x06, 0x7a, 0xf1, 0x31, 0x8c, 0x4e, 0x8e, 0x13, +0xeb, 0xf6, 0xab, 0xba, 0xf6, 0x47, 0xb2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0xb9, 0xaa, 0xfb, 0x58, 0xbb, 0x8d, 0x7b, +0xff, 0xc6, 0x4f, 0x18, 0x4b, 0x0d, 0xb2, 0xc8, +0xdf, 0x03, 0x1c, 0xe7, 0x04, 0xa1, 0xaa, 0xa7, +0x9d, 0x0b, 0x40, 0xda, 0xec, 0x83, 0xcf, 0x97, +0xbf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0xa9, 0x02, 0x23, 0x78, 0xb9, +0x56, 0x65, 0xb5, 0xb0, 0xd8, 0x62, 0x8e, 0xfc, +0x0d, 0x9b, 0xcd, 0xd8, 0x58, 0xca, 0x58, 0xb1, +0x50, 0xf2, 0xc3, 0x83, 0x4f, 0x63, 0x6b, 0x35, +0x0c, 0x4b, 0x09, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0x52, 0x9f, 0xbc, +0x68, 0xdf, 0xb8, 0xec, 0xa7, 0x66, 0xd1, 0xb7, +0x4a, 0x06, 0x53, 0x9f, 0xf7, 0x19, 0xb2, 0x36, +0xbf, 0x74, 0xe2, 0x28, 0x3b, 0x99, 0x96, 0xcf, +0x6c, 0x97, 0x14, 0x8b, 0x61, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0xb0, +0x88, 0x6e, 0xac, 0x94, 0x86, 0x53, 0xcb, 0x79, +0x52, 0xe1, 0x51, 0x24, 0x5d, 0x96, 0x75, 0x36, +0xaf, 0x07, 0xd0, 0x32, 0x0e, 0x44, 0x1c, 0xfc, +0xc7, 0x5f, 0x37, 0x1c, 0x09, 0x6d, 0xa2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0x52, 0xf7, 0xfa, 0xb9, 0xb5, 0x16, 0x95, +0x9e, 0x6e, 0x03, 0x81, 0x70, 0x9e, 0xd9, 0x78, +0x16, 0x3b, 0x01, 0x0a, 0xdd, 0x30, 0x73, 0xa5, +0x55, 0x97, 0x62, 0xb3, 0xb2, 0xba, 0x48, 0x54, +0xed, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0x35, 0x1b, 0xbc, 0x7f, 0x6b, +0x31, 0x68, 0xd6, 0x8e, 0x4b, 0xea, 0xe7, 0x5a, +0x6d, 0x98, 0x0e, 0xcc, 0x10, 0x8f, 0xe3, 0x0f, +0x62, 0x37, 0x91, 0x44, 0x5e, 0x39, 0xe1, 0xe1, +0x4b, 0x60, 0x34, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0xfe, 0x16, 0x14, +0x96, 0x25, 0x1a, 0x3d, 0x06, 0x14, 0x8a, 0xd7, +0xd4, 0xac, 0x2f, 0xf5, 0x9c, 0x02, 0xc9, 0x13, +0x13, 0xee, 0xbb, 0x42, 0x27, 0xe8, 0x55, 0x2b, +0x1a, 0xb7, 0xa4, 0x8f, 0xa7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0x4c, +0xf8, 0x5f, 0x76, 0xe0, 0xa1, 0xa8, 0x68, 0x36, +0xa9, 0xf2, 0x98, 0x41, 0xc3, 0x7f, 0xaf, 0x26, +0xa7, 0xf7, 0xe4, 0x3f, 0xdf, 0x63, 0xdd, 0x13, +0x45, 0xfd, 0xd5, 0x01, 0xe7, 0x30, 0x9c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0xea, 0x03, 0x8d, 0x8c, 0xb8, 0x62, 0x8e, +0xe0, 0x26, 0x27, 0x69, 0x67, 0x21, 0x57, 0x5f, +0x76, 0x49, 0x81, 0x53, 0xdb, 0xec, 0x24, 0xa5, +0x9e, 0xcb, 0x91, 0xf1, 0x09, 0xc4, 0x25, 0xd9, +0x23, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0x0f, 0x33, 0xfd, 0xd7, 0xf3, +0x76, 0x8e, 0x91, 0x21, 0x67, 0xfd, 0x77, 0x36, +0x17, 0x96, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x07, 0xe5, 0xce, 0xad, +0x73, 0xd3, 0x76, 0x56, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0x49, 0xa4, 0x98, +0x4e, 0x97, 0x86, 0x45, 0x37, 0x7b, 0x76, 0xc1, +0x2b, 0x12, 0x86, 0x6d, 0xb1, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xb4, +0x5b, 0x21, 0x3e, 0x7b, 0xec, 0xe3, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0x11, +0xec, 0xb1, 0xe9, 0xbf, 0x01, 0x48, 0xdd, 0x9b, +0x21, 0xaf, 0x7e, 0x22, 0x86, 0xf5, 0xf3, 0x20, +0x48, 0xfa, 0x65, 0x14, 0x25, 0xff, 0x12, 0x2a, +0xb0, 0x51, 0xdd, 0x46, 0xc2, 0xa5, 0xd9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0x24, 0xc3, 0x96, 0x97, 0x98, 0xe3, 0x09, +0xa0, 0xb9, 0x26, 0x6e, 0xe4, 0xb2, 0x2f, 0xbd, +0x6d, 0x19, 0x8c, 0x1b, 0xf7, 0xfb, 0xe3, 0x33, +0xf1, 0x74, 0xc8, 0x77, 0xf8, 0x14, 0x39, 0x4a, +0xa5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0x97, 0x2f, 0xa7, 0x69, 0xed, +0xe2, 0x41, 0x6d, 0xe8, 0xab, 0xcb, 0x03, 0x9c, +0xeb, 0xab, 0x51, 0x1a, 0xf1, 0x8e, 0xf2, 0xed, +0x5b, 0xa0, 0xa7, 0x39, 0xee, 0x02, 0x92, 0xc2, +0xb4, 0x2e, 0x2e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0x86, 0xdf, 0xbb, +0xc0, 0x80, 0x52, 0xe2, 0xc3, 0xa6, 0x6e, 0x87, +0xd1, 0x45, 0x59, 0xcd, 0xb2, 0xfd, 0x63, 0xd6, +0x43, 0x05, 0xc7, 0xd3, 0xd2, 0x36, 0x86, 0xe9, +0x19, 0x50, 0xad, 0x47, 0x06, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0x73, +0x09, 0x76, 0x89, 0x8c, 0xb0, 0x7b, 0x6f, 0x7e, +0xe6, 0xbf, 0xde, 0xd4, 0xb2, 0x8f, 0xf4, 0xf6, +0x65, 0xf2, 0xe2, 0xe3, 0x4d, 0xd8, 0x46, 0x56, +0x32, 0x6e, 0x38, 0xb2, 0xf4, 0xed, 0xec, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0xc7, 0x44, 0xfc, 0x8a, 0x27, 0xd3, 0x6d, +0xbb, 0xd5, 0xf0, 0xab, 0x51, 0x61, 0xd3, 0xbf, +0x92, 0x9b, 0x04, 0x0a, 0x3a, 0x9e, 0x5c, 0x0e, +0x35, 0x00, 0xf2, 0x56, 0x00, 0x69, 0x69, 0x42, +0xd2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0xd7, 0xd5, 0xbb, 0xd8, 0x5e, +0x62, 0x93, 0xe7, 0xca, 0x3c, 0x53, 0xf5, 0xcc, +0x8f, 0xf7, 0xdc, 0x4c, 0x6c, 0xa2, 0xd2, 0x7d, +0x4e, 0x35, 0x08, 0xb9, 0xbd, 0xd4, 0xd7, 0xef, +0xca, 0x1e, 0x63, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0x1f, 0x49, 0x60, +0x53, 0x20, 0xec, 0x11, 0x7a, 0x75, 0x43, 0x7c, +0x8f, 0x5d, 0x37, 0xf0, 0x91, 0x5f, 0x7e, 0x91, +0xea, 0x8f, 0xf3, 0x24, 0x06, 0xe5, 0x42, 0x2a, +0x00, 0x58, 0x91, 0xca, 0xfb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0x63, +0x00, 0x1f, 0x7d, 0x48, 0x9f, 0x9c, 0x6e, 0x51, +0xb5, 0xc9, 0x5b, 0x2f, 0x1e, 0x41, 0x5d, 0x5a, +0x30, 0x04, 0xbc, 0xba, 0xba, 0x6c, 0x67, 0x15, +0xe5, 0x2c, 0x11, 0xb7, 0xc6, 0x40, 0xd3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0xf7, 0xf3, 0x9c, 0x62, 0x28, 0x6a, 0xd0, +0xbb, 0x23, 0x49, 0xd9, 0x96, 0x39, 0x1b, 0x52, +0x9a, 0xe4, 0xeb, 0xb3, 0x07, 0x6a, 0xe2, 0xa5, +0x88, 0x64, 0x06, 0x3c, 0xf2, 0x66, 0xe1, 0x34, +0xa9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0x8f, 0x4e, 0xa4, 0x80, 0x73, +0x39, 0x54, 0x7b, 0x48, 0x05, 0x14, 0xff, 0xf6, +0xb8, 0x3c, 0x77, 0x73, 0xeb, 0xfd, 0xc4, 0x62, +0x3d, 0xc0, 0xa1, 0xf2, 0xea, 0xe4, 0xa6, 0x0f, +0x7a, 0x96, 0xad, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0x38, 0x69, 0x67, +0x90, 0xf6, 0x75, 0x3e, 0x39, 0x86, 0x55, 0x9e, +0x58, 0x3d, 0xdb, 0x56, 0xe8, 0x08, 0xb0, 0xfe, +0xdb, 0xbe, 0x91, 0x70, 0xc8, 0x35, 0xa4, 0xbe, +0x96, 0x1d, 0x75, 0x7d, 0x7a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0x14, +0x92, 0x43, 0x50, 0x00, 0xa6, 0xd3, 0xd9, 0x6d, +0x48, 0xbd, 0x8f, 0x66, 0x8f, 0x9e, 0xba, 0x0d, +0xf7, 0xe8, 0xc9, 0x44, 0xa9, 0xc0, 0x55, 0x13, +0x2f, 0xb6, 0x71, 0x61, 0x76, 0x25, 0xb3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0x13, 0x41, 0xf8, 0xb4, 0xe3, 0x1d, 0xf6, +0xc4, 0x0e, 0x47, 0xe6, 0x4e, 0x4a, 0xa3, 0x20, +0xe3, 0xd7, 0x3f, 0x38, 0x94, 0x9a, 0x60, 0x0b, +0x66, 0x5b, 0x3b, 0xce, 0x02, 0x97, 0xe4, 0x6b, +0xcd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0xbb, 0xa1, 0x89, 0x7d, 0xcd, +0x29, 0xbe, 0xd9, 0x7f, 0xeb, 0xee, 0x3c, 0x62, +0x52, 0x6c, 0x1e, 0x60, 0x8c, 0x50, 0xfb, 0x08, +0x93, 0x91, 0xa0, 0x67, 0x29, 0x6a, 0xf9, 0x05, +0xb6, 0x7e, 0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0x28, 0xc4, 0x13, +0x7f, 0xc1, 0x47, 0x5b, 0x52, 0xf7, 0x66, 0xc4, +0x23, 0x6e, 0x76, 0x78, 0x3e, 0x8e, 0x94, 0xd6, +0xbb, 0x0c, 0x62, 0x6f, 0x2c, 0xf6, 0xd5, 0xf0, +0xda, 0x60, 0xbc, 0x30, 0x05, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0x84, +0x82, 0x71, 0xf3, 0x52, 0x87, 0xbf, 0xaf, 0xf9, +0xdf, 0x2a, 0xd2, 0x50, 0x20, 0x34, 0xeb, 0x95, +0x2d, 0x06, 0x16, 0x7f, 0xd8, 0xe9, 0x0e, 0x40, +0xb3, 0x1d, 0xcb, 0xa4, 0x1a, 0x0a, 0x77, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0x94, 0x6a, 0x40, 0x2b, 0xd6, 0xb2, 0x23, +0x92, 0x90, 0x28, 0x67, 0xcb, 0x2d, 0x83, 0xf4, +0x85, 0x66, 0xe0, 0x63, 0xb9, 0x54, 0xd4, 0x99, +0xfe, 0x43, 0x0f, 0x4d, 0xbb, 0x52, 0x34, 0xdf, +0xd3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0x58, 0x1e, 0x4b, 0x20, 0x86, +0x29, 0x3e, 0x67, 0xc3, 0x6d, 0x88, 0xca, 0x8a, +0xac, 0xe8, 0xd2, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xf6, 0x44, 0x06, 0xfb, +0xf4, 0x0d, 0xa1, 0x75, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0xf1, 0x7f, 0xef, +0x71, 0x1e, 0xbf, 0xf5, 0xdf, 0xff, 0x07, 0xf4, +0xff, 0x26, 0x32, 0x1a, 0x98, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0xeb, +0x54, 0x8f, 0xf9, 0x7f, 0x4e, 0x92, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0xeb, +0x98, 0x31, 0x59, 0xe6, 0x17, 0xf9, 0x8c, 0x75, +0xdf, 0x90, 0x3d, 0x34, 0x77, 0xa5, 0xc5, 0x4a, +0xd7, 0xfe, 0xa2, 0xa8, 0x3e, 0x4e, 0xc3, 0x6d, +0xa4, 0x35, 0x2c, 0x0d, 0x29, 0x07, 0x81, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0x38, 0xd5, 0x2d, 0x15, 0xa0, 0x0e, 0x52, +0x64, 0xfd, 0x7b, 0x19, 0xe2, 0x96, 0xa7, 0xb2, +0xd1, 0xfa, 0xa8, 0xd5, 0xce, 0x6b, 0x75, 0x15, +0xc5, 0x38, 0xa8, 0x70, 0xe0, 0xc3, 0xc2, 0xd5, +0x99, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0x3a, 0x0c, 0x6b, 0xa2, 0x84, +0x60, 0x24, 0x42, 0x5c, 0xbf, 0x60, 0x71, 0xd0, +0x95, 0x39, 0x15, 0x1b, 0xd6, 0xf6, 0x25, 0x1d, +0x7a, 0x4f, 0xf5, 0x60, 0x7b, 0x82, 0x54, 0x7d, +0x55, 0xec, 0x66, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x02, 0x11, 0x10, +0x09, 0xb5, 0x16, 0xb5, 0xdd, 0x9c, 0x88, 0x73, +0x4a, 0x76, 0x09, 0xe4, 0xd4, 0xd2, 0x6a, 0xfc, +0x51, 0xe5, 0x5e, 0xf3, 0x21, 0xdf, 0xd7, 0x59, +0x13, 0xf8, 0x66, 0xb7, 0xa3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0x4d, +0x56, 0x0a, 0xc6, 0xc8, 0x23, 0xb2, 0xd5, 0x6b, +0x21, 0xeb, 0x77, 0x14, 0x88, 0x4f, 0x1d, 0xaa, +0x56, 0x0d, 0x14, 0x96, 0x1b, 0x09, 0xc6, 0x22, +0x67, 0x3e, 0xfd, 0x52, 0xb7, 0xc7, 0xf0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x85, 0x80, 0xd1, 0xb9, 0xae, 0xa8, 0x0e, +0xd5, 0x47, 0x41, 0x30, 0x53, 0x0a, 0x4c, 0xc9, +0x17, 0x40, 0x6a, 0xae, 0xd6, 0xd1, 0x5a, 0x86, +0x0d, 0x26, 0x2d, 0xd4, 0x97, 0x29, 0xd2, 0xf2, +0xec, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0xf0, 0xd1, 0xae, 0x5e, 0xd3, +0x8b, 0x35, 0x8d, 0x87, 0xf6, 0xb5, 0xf6, 0xfc, +0xa3, 0x13, 0x6b, 0xd9, 0xb4, 0x19, 0x27, 0x5c, +0x24, 0xb4, 0xc1, 0xdb, 0x52, 0xc6, 0xd1, 0xd0, +0x0d, 0x39, 0x4e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0x0e, 0xf1, 0x29, +0xc1, 0xe8, 0x28, 0x74, 0xf5, 0x73, 0x17, 0xcc, +0x1c, 0x68, 0x08, 0x07, 0x43, 0x37, 0x56, 0x8e, +0x0e, 0x4d, 0x7f, 0x6b, 0xf7, 0xbb, 0xda, 0x9a, +0x07, 0xa4, 0xf6, 0xb7, 0x2a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x71, +0x87, 0x15, 0x38, 0x91, 0xe3, 0x0d, 0xf4, 0xbc, +0x1d, 0x77, 0x9b, 0x24, 0x21, 0x36, 0x65, 0x29, +0xcc, 0x17, 0x4f, 0x7b, 0x0e, 0xca, 0x21, 0x3d, +0xfc, 0xcd, 0x65, 0x6f, 0x47, 0x7e, 0x08, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0xbb, 0x7c, 0x25, 0xa0, 0xc8, 0x2f, 0x41, +0x6e, 0x15, 0x89, 0xd5, 0x4a, 0xe7, 0xdc, 0x2d, +0x31, 0x5d, 0x7b, 0x2d, 0xf1, 0xa6, 0xaa, 0x37, +0x7f, 0x7a, 0xa1, 0x48, 0x2f, 0x88, 0x01, 0x38, +0xf0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0x3e, 0x7b, 0x02, 0x33, 0x66, +0x45, 0x49, 0xc5, 0xe2, 0x44, 0x10, 0x3f, 0x37, +0xf5, 0xb7, 0x85, 0x84, 0xf7, 0xfb, 0xfe, 0x64, +0x1f, 0xd8, 0x7e, 0xff, 0x67, 0x61, 0xdf, 0x3f, +0x25, 0x49, 0x28, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0x17, 0x6a, 0x1a, +0x30, 0x6f, 0xed, 0x6d, 0xe3, 0xad, 0x36, 0x21, +0x48, 0xf4, 0x72, 0x26, 0x1d, 0xd6, 0x0c, 0x54, +0x89, 0x8d, 0xe2, 0x10, 0x79, 0xaa, 0x09, 0xf0, +0x66, 0x60, 0x7e, 0xbd, 0x71, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x6d, +0x78, 0xa1, 0xf3, 0x02, 0xf0, 0x46, 0xbb, 0xe2, +0xa6, 0x63, 0x64, 0x69, 0x19, 0x56, 0x41, 0x1f, +0x1e, 0x16, 0x5d, 0x0d, 0x39, 0xac, 0xe4, 0x66, +0xe4, 0xd5, 0xa5, 0x27, 0x74, 0xa5, 0x4d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0xbe, 0xdc, 0x82, 0x8c, 0x90, 0x3c, 0x4a, +0x7e, 0xcc, 0x07, 0xd5, 0x4b, 0x03, 0x66, 0xcc, +0xe6, 0x3c, 0xb4, 0xa0, 0x5f, 0x2d, 0x71, 0x90, +0x20, 0x34, 0x86, 0x4f, 0xfd, 0x19, 0x54, 0x6c, +0xb0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0x05, 0x13, 0x74, 0x72, 0x09, +0xab, 0x44, 0xba, 0x5a, 0xc3, 0x44, 0x37, 0x61, +0x99, 0xe1, 0x39, 0xbe, 0x93, 0xc2, 0x43, 0xe5, +0xac, 0x59, 0x5b, 0x1a, 0x9d, 0x57, 0xb5, 0x5c, +0x2c, 0x29, 0xaf, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0xac, 0x1b, 0x78, +0x94, 0x91, 0x6b, 0x8e, 0xc4, 0xf6, 0x46, 0x32, +0x08, 0xea, 0x71, 0x62, 0xd4, 0x22, 0x6a, 0x3f, +0xb5, 0x23, 0x21, 0x16, 0x3e, 0x8b, 0xe4, 0xe5, +0xbb, 0xbd, 0xed, 0x3c, 0xc6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x6c, +0xd6, 0x00, 0xaa, 0xc7, 0xa3, 0x16, 0x6e, 0x93, +0x42, 0xc9, 0x1b, 0xd2, 0x26, 0x74, 0x5a, 0x96, +0x05, 0x4e, 0x73, 0x58, 0xe0, 0x8c, 0x24, 0x18, +0x65, 0x14, 0x8a, 0xc5, 0x6c, 0x74, 0x55, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0x17, 0x30, 0x43, 0xf3, 0x55, 0xd6, 0x85, +0xd5, 0x83, 0x69, 0x8c, 0x86, 0x3d, 0x11, 0x04, +0xa5, 0x97, 0x92, 0x4b, 0xf2, 0xe5, 0x26, 0x1b, +0xe2, 0xe5, 0x22, 0x06, 0xe3, 0x76, 0x2d, 0x8e, +0xbf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0xfc, 0xbb, 0x67, 0xee, 0xee, +0xab, 0x9c, 0x57, 0x6f, 0x27, 0xc0, 0xbf, 0xa9, +0xac, 0x2c, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x2c, 0xc9, 0x8b, 0x16, +0xf2, 0xee, 0x3b, 0x68, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x84, 0x4e, 0x00, +0x6c, 0xf1, 0x55, 0x85, 0x21, 0xb3, 0x14, 0xc3, +0x65, 0x35, 0xad, 0xe7, 0x4c, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0xb8, +0x9b, 0x12, 0x13, 0xac, 0xd1, 0x14, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0x0f, +0x6d, 0xe1, 0xd8, 0x56, 0xa7, 0x49, 0x77, 0x82, +0x98, 0x45, 0x7b, 0x86, 0x3e, 0x5e, 0x46, 0xbb, +0xed, 0x65, 0xd5, 0x6c, 0x61, 0xe8, 0x03, 0xd2, +0x9c, 0x53, 0xf6, 0x4c, 0x34, 0x28, 0x70, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0x81, 0xe6, 0x44, 0x13, 0x9b, 0x1e, 0x31, +0x34, 0xbc, 0xe7, 0x57, 0xae, 0xef, 0xbf, 0x27, +0x81, 0x2b, 0x59, 0x37, 0x76, 0xa0, 0xfa, 0x7c, +0x56, 0x3b, 0xa1, 0x67, 0xc4, 0xa0, 0x42, 0x9b, +0x66, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0x85, 0x74, 0x2e, 0x23, 0x7f, +0xc5, 0x75, 0xca, 0x59, 0xe4, 0x9e, 0xfd, 0x7d, +0xfa, 0x1c, 0xeb, 0x07, 0xb2, 0xbd, 0x23, 0xa5, +0xa6, 0x75, 0x91, 0x60, 0x7b, 0x9d, 0x47, 0xbe, +0xcd, 0xfd, 0x37, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0x0d, 0x61, 0xf0, +0x2a, 0x45, 0xff, 0xd9, 0xf5, 0x26, 0xd5, 0x0d, +0x7c, 0x9e, 0xa1, 0x57, 0x48, 0x8d, 0xee, 0x74, +0x70, 0x5c, 0x6d, 0xa7, 0xf1, 0x17, 0x3f, 0x17, +0x94, 0x02, 0xe2, 0xf3, 0x77, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0xdc, +0x28, 0x83, 0xfd, 0xb7, 0xd0, 0x70, 0x92, 0x11, +0x84, 0xcd, 0xa7, 0x22, 0xd5, 0x41, 0x05, 0x77, +0xde, 0x83, 0x89, 0xf8, 0x79, 0x3f, 0x24, 0xa6, +0xc1, 0x7c, 0x5e, 0x71, 0x4d, 0x35, 0x53, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0xb7, 0xdf, 0xbd, 0x3b, 0xae, 0xa2, 0x7a, +0x07, 0xdc, 0xfa, 0xfb, 0x0e, 0x75, 0xed, 0x1b, +0x97, 0xf2, 0x8c, 0x44, 0xb7, 0x14, 0x49, 0x6f, +0x34, 0x94, 0x77, 0xa9, 0x1d, 0xfc, 0xed, 0x17, +0x2a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x0b, 0x08, 0xd5, 0x34, 0x3e, +0x10, 0x18, 0x94, 0x13, 0xec, 0xd5, 0x5f, 0x13, +0x49, 0x99, 0xaf, 0x0e, 0x51, 0xb8, 0xf4, 0x1f, +0xff, 0x92, 0xb3, 0xba, 0x84, 0xbb, 0x2b, 0x99, +0x4e, 0x67, 0x75, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0xed, 0xbb, 0x06, +0x87, 0xbd, 0x69, 0x27, 0x61, 0x0f, 0xa7, 0x29, +0x4f, 0x09, 0x6a, 0x93, 0x1e, 0x8d, 0x3b, 0x67, +0xbd, 0x29, 0x93, 0x10, 0x2c, 0x49, 0x44, 0xcf, +0x29, 0xcd, 0x41, 0xe8, 0xba, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0x5e, +0xa2, 0x4e, 0x57, 0x9f, 0x47, 0x20, 0xce, 0x88, +0xc9, 0x35, 0x6f, 0x84, 0xad, 0x56, 0x2c, 0x7f, +0x5d, 0x87, 0x03, 0x03, 0x2b, 0xc2, 0x81, 0x18, +0xd0, 0xb5, 0xf7, 0x12, 0x96, 0x6c, 0x76, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x3d, 0x65, 0x1c, 0x57, 0x42, 0xd7, 0x9b, +0x39, 0x53, 0x97, 0x89, 0x4e, 0xa6, 0x5a, 0x79, +0x84, 0x8b, 0xe6, 0x3f, 0xf6, 0xb0, 0x91, 0x8d, +0x82, 0x93, 0x2d, 0x6d, 0xbc, 0x2f, 0x6c, 0x58, +0xce, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0x92, 0x61, 0x2b, 0x80, 0x0a, +0x63, 0x62, 0x4c, 0x6a, 0x01, 0xb8, 0x22, 0x0f, +0x29, 0x05, 0x5b, 0xb9, 0x64, 0x6b, 0xc5, 0x75, +0x9a, 0xcd, 0x80, 0x8d, 0xc3, 0x40, 0x87, 0x37, +0x26, 0x62, 0x69, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0xef, 0x2e, 0x79, +0x0f, 0x25, 0xe4, 0xf6, 0x11, 0x2a, 0xa6, 0x41, +0x59, 0xd6, 0x22, 0x87, 0xa4, 0xc1, 0x55, 0x70, +0xfe, 0xab, 0xb7, 0xfe, 0xa7, 0x9d, 0xc2, 0xe5, +0xb0, 0xd6, 0x06, 0x66, 0x3e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0xaf, +0x06, 0x60, 0x0c, 0x6b, 0xcf, 0x19, 0xd1, 0x2a, +0xa2, 0x64, 0xf4, 0x9f, 0x9d, 0xfb, 0xfd, 0xe9, +0xe1, 0x67, 0xc0, 0x9f, 0x43, 0x8a, 0x80, 0xa1, +0x13, 0x6a, 0xdf, 0x13, 0xeb, 0xe0, 0xb7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0xdc, 0x81, 0x12, 0xeb, 0xae, 0x01, 0x56, +0x34, 0xdc, 0xa1, 0x0e, 0x8a, 0x91, 0xbb, 0x31, +0x3a, 0xf2, 0x8d, 0xe5, 0x7e, 0xd1, 0xd6, 0x58, +0xe4, 0x11, 0xb3, 0x5d, 0x8c, 0x87, 0xb1, 0xc5, +0x65, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0x5a, 0xbb, 0x17, 0xd7, 0x34, +0x6b, 0x05, 0xf1, 0x4b, 0x6f, 0xcd, 0x40, 0x67, +0xa6, 0xa9, 0x07, 0xb3, 0x6d, 0x73, 0x8d, 0xfe, +0x0d, 0x5d, 0x09, 0xa0, 0xc2, 0x5f, 0x37, 0x9b, +0x66, 0x15, 0x8f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0x4d, 0xa5, 0x0b, +0xc0, 0xa9, 0x14, 0xab, 0x03, 0x67, 0xda, 0xe3, +0x72, 0xc2, 0xfa, 0xc1, 0xcb, 0x1d, 0x49, 0xc4, +0x35, 0x96, 0xb7, 0xb2, 0x37, 0x6d, 0xc4, 0xaf, +0x58, 0x29, 0xc2, 0x15, 0x37, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0xeb, +0x90, 0x1b, 0x63, 0x96, 0xa6, 0x5e, 0x13, 0x9e, +0x07, 0x43, 0x47, 0xda, 0x52, 0x29, 0x11, 0xd7, +0x33, 0xec, 0x09, 0xda, 0xd4, 0x54, 0xc2, 0xd3, +0x66, 0xdb, 0xfa, 0xb3, 0xc3, 0x0a, 0xe6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0x92, 0x64, 0x9f, 0x95, 0x14, 0x1a, 0x51, +0x02, 0xab, 0x3a, 0x18, 0x29, 0x1d, 0x02, 0x91, +0xa6, 0x33, 0xeb, 0x7e, 0xf7, 0x50, 0x5e, 0xca, +0x51, 0xc4, 0x0a, 0x5a, 0xeb, 0x19, 0xa2, 0xd4, +0x48, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0xad, 0x57, 0x9a, 0x14, 0x73, +0x9c, 0x4a, 0xbc, 0x6c, 0xf9, 0x07, 0x63, 0xab, +0xec, 0xdb, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x76, 0x5a, 0xaf, 0x9c, +0x3d, 0xb0, 0x00, 0xdd, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0xf1, 0x6b, 0x22, +0x6a, 0xc6, 0x73, 0x6f, 0xa8, 0x0f, 0xf3, 0xeb, +0x37, 0x55, 0x5c, 0xba, 0xca, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x5d, +0xc3, 0x18, 0x3f, 0xf8, 0x65, 0x49, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0x92, +0xc8, 0xc7, 0xb9, 0xb7, 0x98, 0xb1, 0x94, 0xe8, +0x7a, 0xc8, 0xb9, 0xfe, 0x71, 0xfb, 0xa5, 0xa0, +0x38, 0x57, 0x60, 0x95, 0x6b, 0x99, 0xc2, 0x45, +0x1c, 0x23, 0xfa, 0x69, 0xea, 0x0b, 0x10, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0xc3, 0xc4, 0x58, 0x82, 0x1d, 0xe3, 0x6a, +0xd4, 0xa7, 0x12, 0x10, 0xb4, 0xb5, 0xde, 0xd5, +0x80, 0x5d, 0xaa, 0x16, 0xb9, 0xad, 0xb7, 0x11, +0x04, 0x64, 0x1d, 0x3d, 0x5b, 0x10, 0xa8, 0x75, +0x1e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0x0a, 0x6e, 0x80, 0x7e, 0x03, +0x76, 0xba, 0x12, 0x50, 0xc1, 0x98, 0xc6, 0xf8, +0x9f, 0x0d, 0xf4, 0xd5, 0x7e, 0x24, 0xc7, 0xa2, +0x68, 0xeb, 0xd1, 0x21, 0xeb, 0x80, 0x64, 0xa0, +0xaf, 0x4f, 0x56, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0xf3, 0x97, 0xb4, +0x83, 0xda, 0x7b, 0x56, 0x22, 0xa5, 0xb8, 0xe5, +0xc3, 0x40, 0xe4, 0x2c, 0x38, 0x79, 0xed, 0xd1, +0xd3, 0x36, 0x9a, 0xd5, 0x5f, 0xd2, 0xa4, 0xb5, +0x99, 0xf8, 0x42, 0x08, 0x72, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0xfa, +0xc2, 0x4b, 0xc9, 0x1d, 0xa0, 0x2f, 0xec, 0x8c, +0xde, 0xbb, 0x9e, 0x4f, 0x19, 0x19, 0x64, 0x8c, +0xd5, 0x19, 0x3d, 0xec, 0xac, 0xb5, 0xa6, 0x96, +0xb4, 0x18, 0x7a, 0xea, 0xdc, 0x68, 0x32, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0x2c, 0xef, 0x99, 0xfe, 0x8c, 0x0d, 0x59, +0x3f, 0xc8, 0xc2, 0xa4, 0xcf, 0xab, 0x06, 0x08, +0x44, 0xf9, 0x97, 0x1f, 0x07, 0x6f, 0x5e, 0x48, +0xa6, 0x0a, 0x05, 0x24, 0xdc, 0x82, 0xbd, 0x27, +0xaa, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0x1b, 0x17, 0xb1, 0x41, 0x3e, +0xfa, 0xdb, 0x04, 0x0a, 0xdc, 0x23, 0xe7, 0x73, +0xa0, 0x8f, 0x54, 0x66, 0x68, 0x69, 0x2e, 0xe3, +0xf6, 0x9e, 0x06, 0x99, 0xdb, 0xfe, 0x50, 0x6a, +0xef, 0x50, 0x59, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0x05, 0x87, 0x10, +0xf7, 0x47, 0xd5, 0x2e, 0x65, 0x2e, 0xe8, 0xce, +0x21, 0xfc, 0x98, 0x04, 0x57, 0x36, 0x5c, 0x57, +0x4f, 0x1d, 0x28, 0xf3, 0x96, 0x25, 0xc6, 0x57, +0x7a, 0x8d, 0x30, 0xd0, 0xf9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0x95, +0x9f, 0x14, 0x76, 0xd8, 0x86, 0x53, 0x2c, 0x02, +0x38, 0xcf, 0x3e, 0x94, 0x21, 0xdf, 0xd8, 0xdd, +0xaf, 0xbb, 0x87, 0x9f, 0xfe, 0xec, 0xaf, 0x33, +0x7f, 0xa8, 0x20, 0xe0, 0x30, 0xd8, 0xb1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0x8f, 0x86, 0xbc, 0x82, 0x3e, 0xe8, 0x3a, +0xb4, 0xbe, 0x4d, 0xcd, 0x36, 0x1f, 0x38, 0x7e, +0xe6, 0x47, 0x65, 0x67, 0x9d, 0xb8, 0x8b, 0xd7, +0xac, 0xe9, 0x7c, 0x1d, 0xce, 0xb0, 0x89, 0x30, +0x9a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x08, 0x6f, 0xea, 0x48, 0x25, +0x06, 0x59, 0x7f, 0x8d, 0x9a, 0xd8, 0xf6, 0x77, +0xb4, 0xc5, 0xd2, 0xc9, 0x07, 0xfc, 0x4f, 0x76, +0x8b, 0xbd, 0x2e, 0x49, 0xef, 0xa3, 0xdd, 0x59, +0xa6, 0x5c, 0xf3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0x51, 0x14, 0x78, +0x72, 0x63, 0xfd, 0x4d, 0x4c, 0x00, 0x65, 0x64, +0x4e, 0x1d, 0xeb, 0x1b, 0x3a, 0xa2, 0xda, 0x9f, +0x39, 0x06, 0xff, 0x31, 0xbd, 0xdb, 0x32, 0xca, +0x3b, 0xb9, 0x49, 0x9a, 0x54, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0x46, +0xa6, 0x97, 0x0d, 0x82, 0x06, 0x21, 0x9e, 0x2d, +0x72, 0x8c, 0x69, 0x90, 0x08, 0x83, 0x30, 0xd8, +0xfe, 0x31, 0x7f, 0xb4, 0x3f, 0x6a, 0x6d, 0xc5, +0xfb, 0x49, 0x7e, 0xc4, 0x18, 0xae, 0x5a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x13, 0x33, 0x57, 0xb6, 0x25, 0xe5, 0xbe, +0x35, 0xa3, 0xf8, 0xb8, 0x55, 0x5a, 0x14, 0x7b, +0x5a, 0x81, 0x7e, 0xdd, 0xc0, 0xc5, 0x5a, 0xfc, +0x2b, 0x64, 0x01, 0x07, 0x0a, 0x7b, 0x21, 0x97, +0x29, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x44, 0xd6, 0x4f, 0x05, 0x3e, +0xbb, 0x04, 0x18, 0xe8, 0xa8, 0x8e, 0xb9, 0x70, +0x3b, 0x9c, 0xaf, 0x1e, 0x69, 0x90, 0x43, 0x28, +0x64, 0x93, 0x80, 0x7b, 0xa0, 0x1f, 0xa3, 0x21, +0x10, 0x0a, 0x55, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0x57, 0xe8, 0x59, +0x2c, 0x4d, 0x91, 0xd1, 0x39, 0x0b, 0x2f, 0xb2, +0xff, 0x51, 0x99, 0x85, 0xe0, 0x54, 0xdf, 0x4a, +0xe2, 0xbd, 0xa1, 0x00, 0xce, 0xdb, 0x1a, 0x83, +0x0e, 0x24, 0x07, 0xc7, 0xe4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0xbc, +0x4d, 0x0c, 0x74, 0x49, 0xaf, 0xe9, 0x33, 0xf2, +0xe2, 0x89, 0x0d, 0x31, 0xb9, 0x28, 0x61, 0x41, +0x84, 0x6b, 0x65, 0x24, 0x02, 0x9a, 0x6e, 0xdb, +0xf3, 0xd7, 0xaf, 0x7d, 0x98, 0x8d, 0xb7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0x8e, 0x47, 0x48, 0x0c, 0x56, 0xb2, 0x89, +0x74, 0xe5, 0xcc, 0x77, 0x75, 0x5f, 0x72, 0x0f, +0xb2, 0x4f, 0xc5, 0xae, 0x59, 0x0a, 0x6e, 0xe1, +0xfa, 0x12, 0x2e, 0x88, 0x8d, 0x0b, 0xfc, 0x8b, +0x16, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0x08, 0x80, 0x34, 0x51, 0x25, +0xef, 0x6e, 0x7b, 0x01, 0xd6, 0x45, 0x8e, 0x2c, +0x3c, 0x8f, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xff, 0x3a, 0x1b, 0xd8, +0x81, 0xf0, 0x55, 0xaf, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0xab, 0x26, 0xde, +0x7b, 0x53, 0xd4, 0x74, 0x77, 0x33, 0x97, 0xf8, +0x83, 0x59, 0x2f, 0x7c, 0x9a, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0x5a, +0xac, 0x9f, 0x58, 0x97, 0x30, 0x0f, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x3d, +0x1e, 0x90, 0xc6, 0x60, 0x0a, 0xba, 0x55, 0x13, +0x46, 0x44, 0x42, 0x8d, 0xaf, 0xad, 0x4d, 0x87, +0x66, 0xe5, 0xfb, 0x84, 0x28, 0x78, 0x30, 0xa0, +0xd7, 0x84, 0x14, 0x49, 0x19, 0x57, 0xe6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0xb2, 0x69, 0x1f, 0x10, 0xc5, 0x7e, 0x38, +0x0b, 0x17, 0xed, 0xbb, 0x9f, 0x93, 0x1b, 0x51, +0xa7, 0x59, 0x39, 0xb1, 0x68, 0x92, 0x56, 0x2c, +0xa0, 0x51, 0xe2, 0xd0, 0x3f, 0xa5, 0x10, 0x15, +0xf5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0x14, 0x52, 0xf9, 0x1e, 0xa9, +0xca, 0xba, 0xf9, 0x98, 0xc0, 0xb6, 0x04, 0xf6, +0xd0, 0xf8, 0xb4, 0x7a, 0x24, 0x58, 0xd0, 0x88, +0x28, 0x2e, 0xfd, 0x30, 0x20, 0x37, 0x15, 0xa3, +0xf8, 0x5b, 0x65, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0x81, 0x5e, 0xa8, +0xc0, 0x4f, 0xfe, 0x23, 0x71, 0x01, 0xbc, 0xf1, +0xcc, 0x2a, 0x40, 0x05, 0x01, 0xe6, 0x56, 0xe6, +0x88, 0xc0, 0xe1, 0x6c, 0xa3, 0x86, 0x9f, 0x9f, +0x79, 0x6f, 0xe4, 0xb1, 0x58, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0x4f, +0x7d, 0xc0, 0x9b, 0x12, 0x2f, 0xff, 0xa8, 0xe1, +0xb3, 0xc2, 0xac, 0xb7, 0xba, 0x2e, 0x7a, 0x5e, +0xd9, 0xc4, 0x7b, 0xf4, 0x5e, 0x86, 0xba, 0x2f, +0xa6, 0x35, 0x87, 0x4b, 0x8b, 0x35, 0xbf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0xae, 0xe8, 0x23, 0x38, 0x9e, 0x85, 0xf6, +0x0d, 0x5d, 0x69, 0xeb, 0xa6, 0x4c, 0x9d, 0x95, +0x57, 0x4e, 0x31, 0x60, 0x18, 0x28, 0x3b, 0x39, +0x00, 0x60, 0x47, 0x6e, 0x9a, 0xa3, 0x41, 0x92, +0xe2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0xac, 0x99, 0x24, 0xac, 0xca, +0x7d, 0x39, 0x6e, 0x37, 0x65, 0x42, 0xb2, 0xe9, +0xce, 0xb0, 0x04, 0x1e, 0x76, 0x8a, 0xf6, 0x7d, +0xf8, 0x93, 0xc9, 0xff, 0x58, 0x29, 0x18, 0xe0, +0xc7, 0x49, 0x0f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0x1d, 0xa8, 0xc1, +0xd1, 0xb1, 0x35, 0x04, 0xf1, 0x19, 0xf8, 0x9d, +0x4e, 0xe3, 0xc0, 0x67, 0x07, 0x17, 0xc4, 0xb2, +0x42, 0x64, 0x5f, 0xac, 0xbb, 0x88, 0xa8, 0x15, +0xd6, 0xd8, 0x83, 0xec, 0x1b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0xeb, +0x7e, 0x69, 0xcd, 0x6e, 0x2c, 0xf1, 0x45, 0x50, +0x1b, 0x90, 0x31, 0x75, 0x37, 0xa6, 0xf2, 0x15, +0x30, 0xf9, 0x31, 0x9e, 0xad, 0x31, 0x9a, 0x0b, +0x4e, 0x78, 0x94, 0xac, 0x3b, 0x3a, 0x01, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0x6d, 0x59, 0x72, 0x9b, 0xb6, 0xf5, 0x23, +0x95, 0xd7, 0x22, 0xfc, 0x34, 0x47, 0x83, 0xb5, +0x0e, 0x71, 0x13, 0x0c, 0x45, 0x35, 0x78, 0xb4, +0x32, 0xf3, 0x41, 0x16, 0x86, 0x5a, 0x68, 0x54, +0x61, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0x76, 0x96, 0xda, 0x49, 0xfa, +0x1b, 0xc2, 0xac, 0x20, 0x36, 0x94, 0xde, 0x80, +0x79, 0x38, 0x34, 0x30, 0x4e, 0x65, 0xb5, 0x86, +0x01, 0x30, 0xbd, 0x4c, 0x16, 0x8e, 0x55, 0x19, +0xa9, 0x44, 0x26, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0xa8, 0xef, 0xe6, +0x5f, 0x46, 0x21, 0x4d, 0x21, 0x53, 0x2a, 0x7b, +0xf8, 0xaa, 0x7f, 0x51, 0x49, 0x61, 0x39, 0x25, +0x63, 0xc0, 0xd7, 0x6c, 0x08, 0x07, 0x67, 0x16, +0x33, 0xac, 0xd7, 0x48, 0x50, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0x7a, +0xd7, 0x50, 0x52, 0xb2, 0x66, 0x94, 0x8a, 0x3c, +0x18, 0x69, 0x57, 0x17, 0xb4, 0x84, 0xe6, 0xc2, +0xca, 0x11, 0x25, 0x84, 0x38, 0x3b, 0x09, 0x74, +0x6c, 0xbc, 0x66, 0x5e, 0x9c, 0x3e, 0x16, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0x90, 0x78, 0x3f, 0x26, 0x18, 0x3a, 0x4a, +0x8f, 0xa9, 0x07, 0x06, 0xdc, 0xa1, 0xee, 0xa0, +0x3f, 0xea, 0x04, 0xcf, 0x82, 0x94, 0x3c, 0xc4, +0x20, 0xcc, 0x0f, 0x74, 0xe7, 0xd9, 0x76, 0xc3, +0x5b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0x54, 0x99, 0xb2, 0x77, 0x24, +0xcf, 0xd0, 0x41, 0xce, 0x28, 0x0b, 0x7d, 0x44, +0x8d, 0xed, 0x13, 0x83, 0x7a, 0xcd, 0xad, 0xac, +0x78, 0xe4, 0x79, 0x7a, 0xec, 0x72, 0x31, 0x84, +0x31, 0x82, 0x44, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x3f, 0xae, 0xdc, +0xcd, 0x99, 0xa2, 0xb6, 0x1f, 0xbb, 0xfa, 0x6d, +0xdb, 0x7c, 0xc4, 0x51, 0xfa, 0x9e, 0x03, 0x79, +0xcd, 0xd8, 0x67, 0x98, 0x32, 0xc5, 0x30, 0x84, +0x0f, 0x05, 0x06, 0x87, 0x7a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x15, +0x35, 0x8d, 0x2a, 0xba, 0xc6, 0x04, 0x5d, 0x98, +0xd9, 0x72, 0xb4, 0x0c, 0x2f, 0x1b, 0xf9, 0xec, +0x20, 0xaa, 0xae, 0x84, 0xd8, 0x54, 0x16, 0xdc, +0x2c, 0x2c, 0xd2, 0xef, 0x54, 0x59, 0x30, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0x45, 0x96, 0xf3, 0x32, 0x12, 0x45, 0xe0, +0xae, 0x78, 0xbd, 0x81, 0x74, 0x7f, 0x00, 0xb9, +0x84, 0x2b, 0x3c, 0x17, 0x30, 0xd5, 0xf6, 0x8b, +0x76, 0xb9, 0xf6, 0xf5, 0x4c, 0x9c, 0xd2, 0xbb, +0xe4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0x03, 0x4b, 0xf8, 0x37, 0x82, +0x5f, 0xc7, 0xc2, 0xc9, 0x3b, 0x3e, 0x67, 0x98, +0xeb, 0x09, 0xea, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xb1, 0x0e, 0xe7, 0x46, +0xb8, 0x0f, 0xd0, 0x5a, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0xe6, 0x7b, 0x95, +0x36, 0x38, 0xf1, 0x71, 0xc2, 0xb9, 0x8f, 0x4b, +0x51, 0xa7, 0xb8, 0x57, 0x88, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0x64, +0xcb, 0x5a, 0x79, 0xb2, 0x4a, 0x63, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x7a, +0x53, 0x39, 0x5c, 0xbb, 0x74, 0x87, 0xfe, 0xdc, +0xac, 0x65, 0x24, 0x4a, 0x8d, 0xb1, 0xf8, 0xaa, +0x47, 0xe4, 0xf8, 0x74, 0x3e, 0x41, 0x4e, 0x41, +0x79, 0x67, 0x58, 0xd8, 0xf1, 0x59, 0x7b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0x78, 0xcb, 0xbf, 0x3f, 0xe6, 0x60, 0x8a, +0x88, 0xd0, 0x45, 0x71, 0xcf, 0x2d, 0x82, 0x4b, +0x9c, 0xb3, 0x3f, 0x03, 0xcd, 0xcd, 0x60, 0xaf, +0x08, 0x5b, 0x17, 0xdb, 0xfe, 0x8d, 0x83, 0xe1, +0x41, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0xfd, 0x47, 0x36, 0xcb, 0x76, +0x7e, 0x3a, 0x32, 0xc1, 0xf0, 0xbf, 0x51, 0xfe, +0xab, 0x20, 0xc3, 0x63, 0x97, 0x9f, 0x2b, 0xb3, +0xea, 0x4e, 0xbd, 0x16, 0x4f, 0x7f, 0x69, 0x05, +0xee, 0x63, 0xe0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0x62, 0x5a, 0xdc, +0xf2, 0xbd, 0x1f, 0x40, 0x78, 0xe2, 0x85, 0x7b, +0x76, 0xdc, 0x43, 0xde, 0xfc, 0xfe, 0xd4, 0xc4, +0x25, 0xb7, 0x22, 0xfa, 0xce, 0xed, 0x31, 0x2a, +0x50, 0xe8, 0x43, 0x40, 0xe0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0xf3, +0xca, 0x37, 0x96, 0x79, 0xa0, 0x40, 0xd3, 0xbd, +0x40, 0x49, 0x7e, 0xb5, 0x81, 0x6c, 0x44, 0xe9, +0xb0, 0xad, 0x53, 0x67, 0xa9, 0x89, 0x0f, 0x82, +0x1b, 0x52, 0xa9, 0x14, 0x74, 0x7e, 0xe4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0x43, 0x67, 0x96, 0xda, 0xc8, 0xad, 0xfb, +0xd7, 0xdf, 0x68, 0x18, 0xd5, 0xdc, 0x2b, 0x36, +0x4a, 0x9a, 0x73, 0xfa, 0xda, 0xdd, 0x6a, 0x82, +0x1f, 0x78, 0x69, 0x8e, 0xb0, 0x0f, 0x77, 0x4a, +0x45, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0x91, 0x36, 0x31, 0x1b, 0x5f, +0xa9, 0x96, 0x81, 0x80, 0x06, 0xf7, 0x49, 0x1d, +0xc9, 0x04, 0x90, 0xaf, 0x39, 0xd4, 0xae, 0x53, +0xef, 0xeb, 0xe5, 0x1c, 0x8c, 0x6c, 0xbc, 0xb0, +0xbe, 0x34, 0x73, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0xc9, 0xb8, 0x04, +0xa9, 0x19, 0x81, 0x4f, 0x59, 0x94, 0xc5, 0x30, +0x6e, 0xd9, 0xc6, 0x7a, 0x23, 0xda, 0x2f, 0x76, +0xa7, 0xd8, 0x2a, 0xb1, 0x04, 0x6e, 0x96, 0x18, +0xe8, 0xdb, 0x95, 0xe3, 0x56, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0xfd, +0x6b, 0x89, 0xe9, 0x04, 0x68, 0x9d, 0x85, 0xb0, +0x60, 0x75, 0x48, 0x70, 0xe9, 0xe1, 0x4b, 0xb6, +0x22, 0xf2, 0x20, 0x07, 0x8a, 0x7d, 0xb5, 0xa3, +0x33, 0xcf, 0x67, 0x22, 0xdd, 0xd6, 0x7b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0x06, 0x77, 0xca, 0x73, 0x87, 0xb9, 0xa7, +0x1b, 0xc9, 0xe0, 0xfa, 0x22, 0x1d, 0x70, 0x19, +0xaf, 0xd0, 0xa0, 0xb2, 0x84, 0xaa, 0x49, 0x7e, +0xbe, 0x71, 0x5f, 0x27, 0x04, 0x95, 0x52, 0xa0, +0x90, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x18, 0x0d, 0x66, 0x03, 0xa2, +0x21, 0xcf, 0x33, 0x89, 0xa8, 0x0a, 0x01, 0x27, +0x76, 0x54, 0x1c, 0xa4, 0xff, 0xff, 0xa0, 0x53, +0x67, 0x3b, 0x9e, 0xa8, 0xf1, 0xaa, 0x72, 0xf7, +0x11, 0x2e, 0x34, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0x39, 0xf7, 0x19, +0xb8, 0x9d, 0xfc, 0x63, 0x94, 0xc5, 0x24, 0xed, +0x81, 0x69, 0xf0, 0xab, 0x8e, 0x6a, 0xd4, 0xd8, +0x49, 0x90, 0x84, 0x5d, 0x85, 0xc1, 0x88, 0x25, +0x2e, 0x9d, 0xef, 0x3e, 0x15, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0x99, +0x51, 0xae, 0x66, 0x31, 0x52, 0xf4, 0xf6, 0x8d, +0xb1, 0x0f, 0x0c, 0x75, 0x4b, 0x8c, 0xa6, 0x8e, +0x64, 0x01, 0xf9, 0x1e, 0xbb, 0x03, 0xb7, 0xf3, +0xa3, 0x52, 0x0b, 0xd2, 0x45, 0xab, 0xf2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0x36, 0x5d, 0xd7, 0xa7, 0x62, 0x7c, 0x77, +0x76, 0x1c, 0x1b, 0xed, 0xff, 0xc5, 0x54, 0x84, +0x9c, 0xa9, 0x56, 0xd0, 0x3b, 0x7b, 0x3e, 0x7e, +0x75, 0xae, 0xab, 0x14, 0x25, 0xb0, 0x8b, 0xb4, +0xd5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0x22, 0x1e, 0x9a, 0x9a, 0xf7, +0x99, 0x42, 0x0b, 0x80, 0xa9, 0x68, 0x04, 0x46, +0x0c, 0x33, 0xe8, 0x19, 0x2c, 0x0a, 0x1e, 0x96, +0xbd, 0xc4, 0xca, 0xcc, 0x44, 0x9e, 0x97, 0xd5, +0xa7, 0x55, 0x05, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0x87, 0x72, 0xc7, +0xa8, 0x07, 0x0d, 0xe9, 0x9f, 0x1d, 0xe6, 0x84, +0x2a, 0x8f, 0x2a, 0x5d, 0x15, 0xbf, 0x6d, 0x56, +0xda, 0x18, 0xde, 0xce, 0x87, 0xa0, 0xcd, 0x61, +0x7d, 0x2a, 0x79, 0x57, 0x0c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0x67, +0xac, 0x89, 0xfe, 0x8c, 0x8e, 0xd3, 0xf6, 0x16, +0xab, 0x7c, 0x21, 0x27, 0x73, 0x01, 0xb5, 0x95, +0x76, 0x2e, 0x68, 0xc9, 0xb3, 0x93, 0x52, 0xe5, +0xad, 0xdb, 0x3a, 0xab, 0xd2, 0x5d, 0x5d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0xdc, 0x0e, 0x5b, 0xf5, 0x21, 0x30, 0x82, +0x97, 0x4c, 0x24, 0xcf, 0x9c, 0xec, 0xfc, 0x85, +0x90, 0x81, 0xb8, 0xc4, 0x9e, 0x0f, 0xff, 0x41, +0xfd, 0xa7, 0xe1, 0x4d, 0xb0, 0x91, 0x21, 0x1a, +0xb1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0xa3, 0x07, 0x64, 0x1d, 0x83, +0xae, 0x15, 0x0a, 0xbe, 0x2c, 0x9a, 0x27, 0x1f, +0x30, 0x1d, 0xa1, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xe0, 0x39, 0xb4, 0x62, +0x5e, 0xdc, 0xfb, 0x8f, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0xfa, 0xd2, 0xb3, +0xc7, 0xd6, 0x50, 0x7e, 0x7a, 0x48, 0xa9, 0xee, +0x52, 0x98, 0x57, 0xd4, 0x9c, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0xe4, +0x15, 0x8b, 0x59, 0xc0, 0x12, 0xae, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0x8f, +0x4a, 0xaa, 0x74, 0x40, 0x8d, 0xc9, 0x07, 0x0e, +0x0e, 0xb4, 0xad, 0x69, 0xe5, 0x59, 0xcb, 0xf7, +0xe8, 0x19, 0x95, 0x09, 0xdd, 0x35, 0x35, 0xb6, +0x6a, 0x67, 0x65, 0x2b, 0x22, 0xff, 0xb5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0xa3, 0x3e, 0x6b, 0xfc, 0x73, 0xd7, 0xa9, +0xd2, 0x47, 0xaf, 0x0f, 0x80, 0xf9, 0x06, 0x8e, +0x17, 0xa9, 0x20, 0x33, 0x2c, 0x53, 0x84, 0x09, +0x2f, 0x0f, 0x87, 0x20, 0x0f, 0xa0, 0x0a, 0x2e, +0x1c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0x03, 0x61, 0x5a, 0x9e, 0x53, +0xec, 0x5c, 0xb7, 0x3b, 0x4f, 0x6c, 0xbc, 0x90, +0x6d, 0xad, 0x76, 0x5d, 0x06, 0x00, 0xc4, 0x86, +0x91, 0x87, 0xf2, 0x00, 0xb3, 0xd2, 0xfa, 0xc7, +0x18, 0x68, 0xdc, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0x01, 0x56, 0x99, +0x1b, 0x1f, 0x45, 0xa9, 0xec, 0xc5, 0xa3, 0x28, +0x48, 0x77, 0x02, 0xe6, 0x97, 0x58, 0x7b, 0x59, +0x53, 0xd1, 0xfe, 0xc9, 0x97, 0xc9, 0xe0, 0xb4, +0xa8, 0x43, 0xa9, 0x39, 0x26, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0xb1, +0x6e, 0x50, 0x1c, 0x2f, 0x53, 0x0c, 0x9a, 0x8a, +0xfc, 0xee, 0x75, 0x6b, 0x9d, 0x26, 0xe8, 0xa3, +0x6d, 0x97, 0x0a, 0x8e, 0xa3, 0x3f, 0xf9, 0xc2, +0x8a, 0x49, 0xcd, 0x0d, 0xff, 0x9e, 0x63, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0xb5, 0x78, 0x72, 0x66, 0xda, 0x93, 0x0b, +0x14, 0x33, 0x90, 0x6a, 0x8d, 0x57, 0x99, 0xfd, +0xc6, 0xf5, 0xbd, 0x1c, 0x66, 0x14, 0x3c, 0x44, +0xdd, 0xdc, 0xf4, 0x3a, 0x75, 0x12, 0x69, 0x7d, +0xe9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0xe6, 0xfe, 0xb7, 0x6f, 0xc8, +0xb7, 0x10, 0x3d, 0x49, 0x9c, 0x2c, 0x0a, 0x79, +0x99, 0xcf, 0xb8, 0x09, 0xf7, 0xbc, 0x06, 0x92, +0x87, 0x2a, 0xf4, 0x2f, 0x3a, 0xdb, 0x85, 0x9a, +0xab, 0x49, 0x3f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0xe7, 0xba, 0xcd, +0x4b, 0xa6, 0x93, 0xf4, 0xd5, 0x2e, 0x9c, 0xb6, +0xab, 0x91, 0x80, 0xb2, 0xb9, 0x59, 0xef, 0x39, +0x26, 0x90, 0x2d, 0xc2, 0xac, 0x89, 0x15, 0x53, +0x5c, 0x42, 0xe4, 0x7f, 0x95, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0x85, +0x3d, 0x43, 0xaa, 0xce, 0x42, 0x10, 0x14, 0x38, +0x19, 0x55, 0x26, 0xae, 0x75, 0xe5, 0xce, 0x2c, +0x1d, 0x33, 0x18, 0x8a, 0x2f, 0xe4, 0xf5, 0xcf, +0x30, 0xde, 0x39, 0x5a, 0x40, 0x2d, 0x77, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0x3a, 0xc5, 0xdf, 0xcf, 0x75, 0x68, 0xf4, +0x54, 0x20, 0xf8, 0x69, 0xc4, 0xc3, 0x63, 0xd3, +0xf8, 0x8c, 0x41, 0xaa, 0x95, 0x03, 0xf9, 0x7f, +0x09, 0xa1, 0x5c, 0xba, 0xc4, 0xbc, 0x68, 0x5c, +0xb9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0x9c, 0xe2, 0x9c, 0x32, 0x9f, +0x0a, 0x87, 0xf6, 0x72, 0xac, 0xbd, 0xc0, 0xe7, +0xb5, 0x47, 0x4b, 0xc6, 0x4d, 0xdc, 0x4c, 0x1c, +0x00, 0xea, 0xd9, 0xad, 0xc8, 0x82, 0xfa, 0x6f, +0xe6, 0x0d, 0xf8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0x51, 0xbc, 0x01, +0x56, 0xbc, 0x33, 0xbf, 0x6c, 0xc6, 0x3e, 0xf3, +0xd7, 0xdd, 0xb5, 0x11, 0x93, 0x3e, 0x74, 0x6a, +0x0e, 0xa5, 0x99, 0xb8, 0xb1, 0x6c, 0x86, 0x20, +0x03, 0x2d, 0xbe, 0xba, 0x97, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0x19, +0x62, 0x64, 0x4b, 0x54, 0xb7, 0xad, 0x47, 0xdb, +0xd2, 0x30, 0xe3, 0x77, 0x7a, 0x63, 0xcd, 0x44, +0x74, 0xc9, 0xf0, 0x25, 0x04, 0xc2, 0x81, 0x43, +0xff, 0x0a, 0x1c, 0x8d, 0x70, 0xea, 0x65, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0x0f, 0x5c, 0x75, 0x21, 0x77, 0xfb, 0xa6, +0x91, 0xcd, 0xa8, 0xad, 0xe9, 0x2b, 0x64, 0x40, +0x90, 0xe1, 0x92, 0xc6, 0xf7, 0x7b, 0xab, 0xf9, +0xba, 0x96, 0xa2, 0xf4, 0x08, 0xdf, 0xba, 0xce, +0xb8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0xb2, 0x48, 0xc7, 0x40, 0x40, +0x8b, 0xe6, 0xed, 0x1d, 0xa8, 0xec, 0x6f, 0x43, +0xd0, 0xec, 0xc9, 0xd4, 0xa5, 0x7e, 0x5b, 0x97, +0x7c, 0xd1, 0x3f, 0xaf, 0x40, 0xb9, 0x71, 0x3d, +0xce, 0x15, 0xec, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0x8c, 0x6f, 0x05, +0xb1, 0xbe, 0x03, 0x42, 0x95, 0x44, 0x5a, 0x53, +0x09, 0x2b, 0x49, 0xa0, 0xd1, 0x63, 0xcc, 0x63, +0xdb, 0x65, 0x59, 0xb5, 0x10, 0x42, 0x89, 0x31, +0x11, 0xa1, 0xd3, 0x84, 0xde, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0xe0, +0x79, 0x0d, 0xa9, 0x53, 0xc8, 0x51, 0xc2, 0xbd, +0x71, 0x9b, 0xa3, 0xcb, 0xee, 0xc8, 0xb1, 0xa8, +0xfd, 0x5c, 0x73, 0xb5, 0xaf, 0x7f, 0x6f, 0xe1, +0xab, 0x3d, 0x2b, 0xd8, 0xb3, 0x0a, 0x0d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0xc2, 0xbe, 0xa0, 0x60, 0xff, 0xb5, 0xaf, +0xc7, 0x46, 0x15, 0x43, 0x48, 0x11, 0xd3, 0x36, +0xf4, 0x1a, 0x71, 0x4e, 0x7a, 0x6a, 0xe7, 0x54, +0x03, 0xb5, 0x38, 0xe8, 0x56, 0x60, 0xc3, 0x67, +0xa2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0x53, 0x62, 0x84, 0x0d, 0x84, +0x5a, 0xf4, 0xb6, 0x6d, 0xdb, 0x37, 0x4a, 0xd6, +0x0f, 0x31, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x8c, 0x45, 0x36, 0x33, +0x63, 0x4f, 0xed, 0xfa, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0xea, 0xec, 0xf2, +0xfa, 0x20, 0xa1, 0x83, 0x56, 0x9a, 0x2a, 0xf9, +0x0f, 0xcf, 0x4c, 0x75, 0xe4, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x82, +0xcc, 0xba, 0xcb, 0xb9, 0x4d, 0xf3, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0xd5, +0x83, 0xc7, 0x79, 0xff, 0x68, 0x7e, 0xd3, 0xf4, +0xdf, 0x4c, 0x09, 0x92, 0xc0, 0x7e, 0x52, 0x4a, +0x13, 0xdd, 0x06, 0x58, 0xc2, 0x0c, 0xfb, 0x5c, +0x11, 0x24, 0x5e, 0x00, 0xf6, 0x2d, 0x5e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x70, 0x2f, 0xac, 0xb9, 0x36, 0xe7, 0xfa, +0x42, 0x09, 0xc0, 0x2f, 0x8e, 0x7a, 0xfc, 0x8f, +0x34, 0x8f, 0xd3, 0x70, 0x25, 0x8e, 0xec, 0x29, +0xbb, 0x73, 0x55, 0xce, 0x74, 0xe6, 0x87, 0x23, +0xe6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0x8f, 0x9e, 0x17, 0xd6, 0xa3, +0x83, 0xb6, 0xd4, 0xe0, 0x40, 0xda, 0xf0, 0x97, +0xe3, 0xcd, 0xee, 0x98, 0x97, 0x35, 0xcc, 0xee, +0x43, 0x2e, 0xba, 0x70, 0x03, 0x25, 0x0a, 0x4e, +0x82, 0x81, 0xbc, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0xa0, 0xf5, 0x7c, +0xb7, 0xe3, 0x66, 0x0e, 0xec, 0x17, 0x1a, 0x6a, +0x02, 0x88, 0x83, 0x35, 0xe2, 0xb0, 0x84, 0x5f, +0x06, 0x2a, 0xa1, 0x1b, 0x1d, 0x0c, 0x49, 0x83, +0xc1, 0x2a, 0xa3, 0x0d, 0xe9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0xf3, +0xe2, 0xad, 0xcf, 0x1c, 0x01, 0x57, 0x51, 0x87, +0x4a, 0xbe, 0x19, 0xb4, 0xe9, 0xaf, 0x9b, 0x94, +0x4a, 0xea, 0x9d, 0x20, 0x61, 0xba, 0x06, 0x3a, +0xec, 0xc0, 0x1c, 0x5a, 0x59, 0x96, 0xc8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0xdd, 0x28, 0xed, 0xb9, 0x96, 0xeb, 0xe1, +0x69, 0xda, 0xa5, 0x83, 0x54, 0x51, 0x62, 0xf4, +0x74, 0x79, 0xb1, 0xdf, 0xa9, 0x0e, 0x9d, 0x0c, +0x56, 0xdc, 0x98, 0xbd, 0x69, 0x21, 0x06, 0xd3, +0x56, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0xb3, 0x87, 0x58, 0x84, 0x2e, +0x88, 0xc0, 0x7b, 0xc5, 0xe0, 0x37, 0x03, 0x05, +0x92, 0xd9, 0x34, 0xaa, 0x51, 0xed, 0xb0, 0x3e, +0xf1, 0xe0, 0xf8, 0x89, 0x34, 0x2b, 0x1c, 0xd8, +0x4d, 0xa4, 0x26, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x1e, 0xac, 0xcf, +0x34, 0x87, 0xd4, 0x55, 0x72, 0x33, 0x95, 0x54, +0x8d, 0xd7, 0xc4, 0x59, 0xb9, 0x37, 0xe2, 0x41, +0xde, 0xe5, 0x1f, 0x5c, 0x24, 0x1e, 0xde, 0xe3, +0x7f, 0xef, 0x5e, 0xa4, 0x94, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0x14, +0xc7, 0xa1, 0x03, 0x79, 0x85, 0x11, 0x3a, 0x01, +0xc6, 0x84, 0x3b, 0xd3, 0xf4, 0xcc, 0xc8, 0x9d, +0x37, 0x7c, 0x60, 0xf7, 0x94, 0xd8, 0x80, 0x83, +0xb4, 0xdf, 0x0a, 0xe4, 0xf4, 0xd1, 0x26, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0xf1, 0x07, 0xda, 0x28, 0x8a, 0x8c, 0xa8, +0xc3, 0x35, 0xf9, 0x78, 0x2e, 0x48, 0xd4, 0x60, +0x77, 0x55, 0x20, 0x71, 0x15, 0xee, 0xf8, 0xf0, +0x03, 0x21, 0x20, 0xf2, 0x72, 0x34, 0x13, 0x5b, +0xd5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0xc4, 0x59, 0xce, 0x7d, 0x05, +0xa9, 0xca, 0x6e, 0x7f, 0x1c, 0x33, 0x55, 0x9b, +0x83, 0x47, 0x20, 0x6c, 0xec, 0xf2, 0x44, 0xef, +0x87, 0x00, 0x15, 0x15, 0x01, 0xcf, 0x18, 0x83, +0xc6, 0x2d, 0x15, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0xa2, 0x48, 0x07, +0x82, 0x41, 0xb5, 0x34, 0xbd, 0xc3, 0xb0, 0x90, +0x7f, 0x27, 0x09, 0x5c, 0xab, 0xc4, 0xc9, 0x4f, +0x6c, 0xf3, 0x49, 0xea, 0xba, 0x98, 0xc3, 0x18, +0xb4, 0x04, 0x9a, 0xd0, 0x80, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0x11, +0x9c, 0x64, 0x34, 0xab, 0xb0, 0xb1, 0x43, 0xb7, +0x05, 0x80, 0xf6, 0xa1, 0x0b, 0x6d, 0xdd, 0x80, +0xf4, 0x98, 0x79, 0x7b, 0xf3, 0xd5, 0x81, 0x97, +0x78, 0xc5, 0x22, 0x11, 0x03, 0x96, 0xc3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0x14, 0x7e, 0xa3, 0x39, 0xa4, 0x5c, 0x95, +0x27, 0x08, 0xe6, 0xfb, 0x02, 0xed, 0xd6, 0xb3, +0x9d, 0x21, 0x65, 0xc9, 0x88, 0x78, 0x94, 0xc6, +0xee, 0x00, 0xbd, 0x1a, 0x07, 0x64, 0x3a, 0xb7, +0x35, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0x10, 0xf4, 0xbd, 0x18, 0x31, +0xdf, 0xa0, 0x88, 0xbd, 0x3f, 0xa7, 0x38, 0x95, +0x57, 0x52, 0xee, 0x20, 0xda, 0x84, 0x5f, 0x65, +0x94, 0xa5, 0xf5, 0xbb, 0x34, 0x5c, 0xf4, 0x59, +0x77, 0xaf, 0xaa, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0xbb, 0xf5, 0xb9, +0x63, 0x49, 0xfb, 0x55, 0x11, 0x7a, 0x2f, 0xa1, +0xe6, 0x56, 0x72, 0x59, 0xfb, 0x45, 0x2e, 0x8c, +0x65, 0xd2, 0xc8, 0xb2, 0x95, 0x4b, 0x65, 0xb4, +0xf5, 0x64, 0xb1, 0xb9, 0x3a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x34, +0xfb, 0x80, 0x46, 0x5f, 0x71, 0xc0, 0xea, 0x1c, +0x5f, 0x11, 0xd9, 0x28, 0xe3, 0x9f, 0x35, 0x54, +0x12, 0x04, 0xe7, 0xdc, 0x89, 0x16, 0x45, 0xf6, +0x7f, 0x61, 0xa1, 0xa7, 0x24, 0x60, 0x2b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0x1e, 0x88, 0xc6, 0x65, 0x75, 0xbf, 0x7e, +0xc5, 0xa2, 0xbf, 0xd7, 0x70, 0x89, 0xa5, 0x63, +0x4e, 0x3c, 0xc4, 0xb6, 0x2f, 0xf0, 0xb7, 0xfb, +0x77, 0xe3, 0x2a, 0x1a, 0xb3, 0xda, 0x46, 0x21, +0x65, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0x57, 0x6d, 0x03, 0xc3, 0x1d, +0xc6, 0xf7, 0x94, 0xc0, 0x94, 0x12, 0xde, 0xc0, +0x58, 0x5c, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x57, 0x40, 0x57, 0x34, +0xe9, 0x3b, 0x39, 0xc0, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0x6e, 0x8b, 0x2d, +0xf0, 0x61, 0x30, 0x1b, 0x8a, 0xc4, 0x45, 0xb8, +0x38, 0x0d, 0xcb, 0x57, 0xca, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda, 0x7d, +0x2a, 0xf6, 0xe7, 0x81, 0x3a, 0x70, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0xe5, +0xd8, 0x31, 0x7e, 0x9e, 0x3a, 0xaf, 0x4e, 0x64, +0xcc, 0xd5, 0x0b, 0x58, 0xb2, 0x42, 0x8a, 0xa2, +0xb8, 0x6c, 0x4b, 0xb8, 0xcf, 0x91, 0x86, 0x66, +0x3f, 0xab, 0x17, 0xeb, 0x1d, 0x27, 0xa3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x6e, 0xf0, 0x23, 0xa0, 0x2d, 0xef, 0xd4, +0xb4, 0x1c, 0x99, 0x74, 0x60, 0x32, 0x51, 0x36, +0xd0, 0x91, 0xf3, 0x05, 0x1c, 0xc3, 0x6c, 0xdd, +0xcd, 0x4e, 0x17, 0xe4, 0xd1, 0xe6, 0x72, 0xa8, +0x49, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0x02, 0xe0, 0xfb, 0x65, 0x8c, +0x89, 0x39, 0xfb, 0x30, 0x1e, 0xe9, 0xdc, 0x0c, +0x9e, 0x81, 0xd4, 0xd9, 0x7c, 0x8a, 0xb7, 0x4d, +0x8b, 0x02, 0xd3, 0x8d, 0xab, 0x85, 0xee, 0x5b, +0x50, 0x75, 0xbc, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0xd2, 0x6f, 0x43, +0xd8, 0x0c, 0x1f, 0x49, 0x02, 0xca, 0xeb, 0x98, +0xc9, 0xb9, 0x9a, 0xcc, 0x7d, 0x2a, 0x54, 0x74, +0x1c, 0xe6, 0x92, 0x99, 0x20, 0xd4, 0xbf, 0x09, +0x47, 0x64, 0xa9, 0xbb, 0x2e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0x14, +0xff, 0xbe, 0x15, 0x7f, 0xfc, 0xab, 0xf1, 0xe1, +0xd6, 0xab, 0x8f, 0xce, 0x2b, 0xd3, 0x78, 0x2d, +0x94, 0xaf, 0x98, 0x4e, 0x4c, 0xc1, 0x34, 0xcb, +0x26, 0xc6, 0x4f, 0x23, 0xa4, 0x85, 0x2a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0x01, 0x82, 0xed, 0xf0, 0x42, 0x34, 0xd0, +0x7a, 0xcb, 0x8b, 0x2b, 0xaf, 0x44, 0x0a, 0x7c, +0xae, 0x04, 0xc1, 0x04, 0x15, 0xc8, 0x2e, 0xd6, +0x8c, 0xc1, 0x0d, 0x29, 0xbc, 0x98, 0x01, 0x63, +0x02, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0x0b, 0x15, 0xe6, 0xef, 0x30, +0xa2, 0x6f, 0x4a, 0xfe, 0x0a, 0xa9, 0x92, 0x0d, +0x9f, 0xe5, 0x49, 0x17, 0x27, 0x9a, 0x73, 0xbd, +0xab, 0xc8, 0x9b, 0x91, 0x0d, 0xb7, 0xff, 0x07, +0x13, 0x8a, 0x8f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0x02, 0xf5, 0xdf, +0x4d, 0x44, 0xda, 0xdd, 0x0f, 0x23, 0xc7, 0x8c, +0x8e, 0xff, 0xa3, 0xd1, 0x1d, 0xa0, 0x35, 0x80, +0x1e, 0xb7, 0x96, 0x02, 0x90, 0x6a, 0xd1, 0x65, +0xb6, 0xb0, 0x00, 0x0d, 0x01, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0x5f, +0xfb, 0xc5, 0xdb, 0xcb, 0xc8, 0x4c, 0x59, 0x9e, +0xb6, 0x28, 0x55, 0x86, 0x7e, 0x4b, 0x86, 0x7b, +0x07, 0x0e, 0x90, 0xdd, 0x45, 0x21, 0xa0, 0xfc, +0x4f, 0xd1, 0x73, 0x99, 0x61, 0xa9, 0xe9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x8f, 0xb0, 0x27, 0xc7, 0xe7, 0x61, 0x88, +0xb7, 0xb0, 0x2d, 0x5a, 0x5c, 0x8a, 0xfb, 0x9c, +0x44, 0xb6, 0xc9, 0xdd, 0x50, 0xf1, 0xae, 0x27, +0xe2, 0x6c, 0xa9, 0x68, 0x8e, 0xbd, 0x55, 0xbf, +0x95, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0xf0, 0xe4, 0x11, 0xd3, 0xd7, +0xe2, 0x88, 0x61, 0x14, 0xcc, 0x2c, 0xe5, 0xd6, +0x39, 0x00, 0x46, 0xa9, 0x38, 0xb2, 0x79, 0x41, +0xf9, 0x00, 0x09, 0x90, 0xff, 0xb3, 0x18, 0x1a, +0x30, 0xbb, 0x97, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0x9e, 0xb4, 0x07, +0xfd, 0x84, 0x8e, 0x24, 0x24, 0x9d, 0xd3, 0x8a, +0x2b, 0xfa, 0xba, 0xa5, 0x6a, 0x22, 0x00, 0xe2, +0x58, 0xc0, 0xf6, 0x62, 0x2f, 0x27, 0x4f, 0x32, +0x52, 0x30, 0xc5, 0xb7, 0x61, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0xba, +0x13, 0x3b, 0x01, 0xa6, 0x6f, 0x76, 0x65, 0xca, +0x1b, 0xb1, 0xc3, 0xd6, 0xc8, 0x7e, 0x0f, 0x78, +0x1f, 0x5d, 0x49, 0x1c, 0x32, 0x7a, 0xf7, 0x2a, +0x5c, 0xc4, 0x8c, 0x85, 0xa1, 0xa4, 0x49, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0x2b, 0x51, 0x13, 0x83, 0xad, 0x68, 0x3e, +0x8f, 0x87, 0xee, 0xff, 0x54, 0xa6, 0x4c, 0x82, +0xe6, 0x5b, 0x27, 0x16, 0x8c, 0x07, 0x7f, 0x96, +0xa0, 0xf2, 0xab, 0x2a, 0x54, 0xc1, 0xd0, 0x9f, +0xe7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0x70, 0x93, 0x44, 0x22, 0x5f, +0x8f, 0xfd, 0x1d, 0x27, 0x3b, 0x20, 0xa7, 0x16, +0xa8, 0x56, 0xc4, 0x36, 0x50, 0xf0, 0x70, 0xd5, +0x0c, 0x78, 0x57, 0x0b, 0xb6, 0xcd, 0x7b, 0x83, +0x27, 0xa6, 0x71, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0xc7, 0x68, 0x64, +0xe4, 0x5b, 0x0d, 0x16, 0x09, 0x03, 0xbb, 0xa1, +0x1a, 0x84, 0xb6, 0x15, 0x59, 0xb2, 0x98, 0xbc, +0x52, 0x13, 0xd6, 0x9b, 0x15, 0xee, 0xc8, 0x25, +0x2d, 0xb5, 0xf9, 0xae, 0x00, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0x21, +0xc6, 0x07, 0x28, 0x5c, 0xf4, 0x45, 0xa8, 0xe4, +0x2b, 0x13, 0x34, 0xa8, 0xb9, 0x20, 0x26, 0x5e, +0x0c, 0x07, 0x63, 0x61, 0xfa, 0xc2, 0x40, 0xc3, +0x71, 0x6b, 0x1f, 0x4a, 0xb8, 0x65, 0x4f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0x7f, 0x0d, 0x9f, 0x0e, 0x90, 0x57, 0xbd, +0x9f, 0x0d, 0x48, 0x10, 0x37, 0x71, 0xf6, 0x04, +0xa0, 0x97, 0x06, 0x3b, 0xc3, 0x78, 0x43, 0x11, +0xaf, 0x57, 0xb0, 0xd6, 0xbc, 0x73, 0x28, 0xd2, +0xfe, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0x5e, 0xe6, 0xd1, 0x72, 0x47, +0x90, 0xfc, 0x06, 0x04, 0x6a, 0x0d, 0x48, 0x99, +0x92, 0xcb, 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xe4, 0xb2, 0x6a, 0x8b, +0x1a, 0x06, 0xd6, 0x0b, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x38, 0x02, 0xba, +0x26, 0x43, 0xfd, 0x1c, 0xf0, 0xea, 0x68, 0x80, +0x73, 0xea, 0xb6, 0xec, 0xb4, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9f, 0x69, +0x0f, 0xb7, 0x96, 0x22, 0xca, 0x45, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0x4d, +0x94, 0x14, 0x61, 0xb3, 0x81, 0xd9, 0x63, 0xeb, +0xa2, 0xde, 0xb3, 0xdb, 0x28, 0x9b, 0x57, 0x7b, +0xa9, 0xd5, 0x43, 0x8e, 0x3a, 0x4b, 0x79, 0x4c, +0x5a, 0xf4, 0x04, 0xd8, 0xff, 0x60, 0x21, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0x41, 0xf2, 0x17, 0x52, 0x2a, 0x1c, 0x6a, +0xa8, 0xa3, 0xa8, 0x28, 0xe3, 0xef, 0xaf, 0x0f, +0xdd, 0xb0, 0x2d, 0xcb, 0x0c, 0x66, 0x3e, 0x9c, +0xc0, 0xfc, 0xd0, 0xb2, 0xc7, 0xc8, 0xb1, 0x9b, +0x07, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0xc7, 0xc8, 0x73, 0x6a, 0xbe, +0x7c, 0x73, 0xf8, 0xd7, 0x00, 0xeb, 0x94, 0x77, +0xc0, 0x19, 0x21, 0xb2, 0x9c, 0xf7, 0x4d, 0x99, +0x26, 0xd6, 0xdc, 0xd3, 0x3e, 0x82, 0xfb, 0x24, +0xb1, 0x3b, 0xb3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0x53, 0x94, 0x77, +0x86, 0x66, 0xe2, 0x2c, 0x59, 0x07, 0x93, 0x6d, +0x2c, 0x64, 0x05, 0x94, 0xa1, 0x63, 0xa7, 0x37, +0x96, 0xfe, 0xea, 0x00, 0x19, 0xca, 0xc9, 0x82, +0x6b, 0x7a, 0xba, 0xcc, 0x43, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0x93, +0x44, 0xc8, 0x5b, 0xc8, 0xa4, 0x0f, 0x27, 0x68, +0xc3, 0x41, 0xd8, 0xa1, 0x6c, 0x38, 0x9e, 0xb8, +0xf5, 0xba, 0x88, 0xad, 0x6b, 0xe9, 0x26, 0xe0, +0x3b, 0x35, 0xc4, 0x4d, 0xd0, 0x7f, 0x08, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0xb0, 0x38, 0xca, 0x5c, 0xe6, 0x3d, 0xd7, +0x1c, 0xa6, 0x89, 0x94, 0x69, 0x55, 0xc9, 0x55, +0x73, 0x43, 0x91, 0xaa, 0xf9, 0xfa, 0xcc, 0xd1, +0xc8, 0x6f, 0x70, 0xef, 0x8f, 0x04, 0xb1, 0x3d, +0x4c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x60, 0x79, 0xca, 0xc9, 0x56, +0x01, 0x40, 0xd4, 0x52, 0x7f, 0x17, 0x5a, 0xb0, +0xe0, 0x24, 0xb8, 0x44, 0xc1, 0x2c, 0xd9, 0x0e, +0x0f, 0x90, 0xc2, 0x8b, 0x47, 0x48, 0xac, 0x39, +0xc9, 0xa7, 0xfc, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0x4e, 0xa6, 0x3f, +0x82, 0x18, 0x05, 0x7f, 0xae, 0x8f, 0xdb, 0x20, +0x15, 0x3e, 0x6a, 0x82, 0x3d, 0x32, 0xe3, 0x5c, +0x46, 0x04, 0x37, 0xb8, 0xc8, 0xe9, 0x1f, 0x55, +0xeb, 0xd4, 0x1c, 0xb2, 0xd5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0xa3, +0xd5, 0x7a, 0x83, 0x03, 0x2b, 0x2e, 0x59, 0xde, +0xa5, 0x1a, 0x0c, 0x55, 0x34, 0xbd, 0xe7, 0x03, +0xa1, 0xb7, 0x82, 0x7b, 0xc8, 0x44, 0x81, 0xda, +0x08, 0x31, 0x3b, 0xc7, 0x99, 0xe2, 0x82, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0x1c, 0x27, 0xb2, 0x06, 0xf1, 0x5b, 0x2f, +0x4c, 0x44, 0x31, 0xa7, 0x2f, 0xed, 0xdf, 0xc5, +0xa1, 0x55, 0x8e, 0x37, 0xb5, 0x6a, 0xbd, 0xa2, +0xfe, 0x02, 0x0e, 0xb2, 0x5f, 0xdd, 0x73, 0xfc, +0xcd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0x9d, 0xcb, 0x71, 0xdf, 0x14, +0xb9, 0x0a, 0x5b, 0x1c, 0xf1, 0x43, 0x2d, 0x71, +0xc7, 0x09, 0x37, 0xf9, 0x7f, 0x30, 0xcc, 0xc1, +0x12, 0x39, 0xd7, 0x82, 0x02, 0xe3, 0x90, 0x9a, +0xa2, 0x91, 0xc9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0x9e, 0x19, 0x8b, +0x06, 0x3f, 0xde, 0x38, 0x32, 0xb0, 0xa9, 0xb8, +0x11, 0xf0, 0xbe, 0x70, 0x1b, 0xe2, 0xd5, 0xad, +0xa3, 0x5e, 0x09, 0x73, 0x26, 0x59, 0x9d, 0xd7, +0xfe, 0xf2, 0xc3, 0xbb, 0xf7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0xca, +0x42, 0xe7, 0x02, 0x73, 0x66, 0x95, 0xba, 0x30, +0x9c, 0x61, 0x91, 0xe9, 0xec, 0xd8, 0x3b, 0xa9, +0xc3, 0x4c, 0x2b, 0xaf, 0x68, 0x9d, 0x4e, 0x4b, +0x33, 0x33, 0xd0, 0x2c, 0x00, 0xba, 0xfb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x5b, 0x56, 0x53, 0x69, 0x01, 0xf4, 0x5a, +0xa8, 0xf5, 0xb4, 0x70, 0xe4, 0x35, 0xa1, 0x45, +0x76, 0xbe, 0xad, 0xb1, 0x49, 0x44, 0x2b, 0xc3, +0x12, 0x31, 0xc9, 0x9b, 0x32, 0x8f, 0x17, 0xb0, +0x31, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0xbb, 0xcb, 0x0d, 0x14, 0x6e, +0xb1, 0xce, 0x2b, 0xa1, 0x3b, 0xe4, 0xc6, 0xab, +0x03, 0x66, 0x02, 0x99, 0x6e, 0x8b, 0x80, 0x83, +0x32, 0x87, 0xa2, 0x8a, 0x9a, 0xf2, 0xf3, 0x08, +0xf6, 0x87, 0xeb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0x84, 0x6f, 0xfb, +0x7f, 0xc2, 0xe2, 0xf6, 0xe6, 0x9f, 0x17, 0x25, +0x08, 0x48, 0xe9, 0x36, 0xff, 0x2d, 0xa4, 0x5b, +0xdc, 0x4f, 0x92, 0xd4, 0x26, 0x4c, 0x5e, 0x4a, +0x6f, 0x70, 0x07, 0x8b, 0xd4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0x80, +0xd4, 0x98, 0xe5, 0x26, 0x3b, 0x65, 0x3c, 0x12, +0xee, 0xce, 0x17, 0xdf, 0x3c, 0x93, 0xc8, 0xf7, +0x8d, 0x2d, 0xc3, 0xb6, 0xcc, 0x5e, 0x6e, 0x89, +0xca, 0x4b, 0x3f, 0x1b, 0xca, 0x67, 0x15, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0x72, 0x21, 0x41, 0xbf, 0x5d, 0x6a, 0xd4, +0x2c, 0x83, 0xbe, 0x5c, 0x66, 0x12, 0x1c, 0x68, +0xa5, 0xcb, 0x6d, 0x7b, 0xdc, 0xe7, 0x61, 0xa1, +0xc5, 0xf2, 0x5e, 0xc1, 0xe7, 0xcf, 0xbe, 0xd3, +0xde, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0xaa, 0x6c, 0xdb, 0x58, 0xda, +0xe1, 0xac, 0xf7, 0xc3, 0xec, 0xf5, 0xb7, 0xd5, +0x92, 0x10, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xc1, 0xe5, 0xed, 0x02, +0x88, 0x13, 0x1c, 0x0f, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0x14, 0x43, 0x9f, +0xa3, 0x4a, 0x09, 0x0d, 0xe5, 0x59, 0x62, 0x7e, +0x68, 0x29, 0xff, 0x21, 0x47, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8d, 0xec, +0x6e, 0x52, 0x9f, 0x34, 0x4a, 0xca, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0xe2, +0xf4, 0x55, 0x68, 0x47, 0x99, 0xec, 0x2c, 0xd2, +0x94, 0x7f, 0xab, 0xbd, 0xd0, 0xc0, 0x18, 0x77, +0xb0, 0xad, 0x77, 0x7f, 0x5d, 0xe7, 0x5d, 0x94, +0x26, 0xc8, 0xbd, 0xca, 0xe7, 0xdd, 0x23, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0x54, 0x3f, 0xba, 0xe8, 0x8b, 0x1a, 0x54, +0x4f, 0x73, 0xee, 0xd4, 0x03, 0x89, 0xa2, 0x84, +0x4d, 0x3e, 0x3b, 0x3b, 0xd1, 0xd8, 0x69, 0xd7, +0x78, 0xfb, 0x1c, 0x3d, 0x90, 0x60, 0x35, 0xf6, +0x0a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0x6b, 0x1f, 0x8d, 0x42, 0x8e, +0x6c, 0xb6, 0x11, 0xf1, 0xf9, 0x3f, 0x36, 0xa5, +0x13, 0x0e, 0x71, 0xb0, 0x48, 0xf6, 0xf0, 0x4b, +0x63, 0x49, 0xc1, 0xb4, 0x24, 0x28, 0xff, 0x1e, +0x07, 0x4d, 0x8e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0x3c, 0xe4, 0x98, +0xec, 0xe9, 0x6c, 0x42, 0x85, 0x6b, 0x54, 0xef, +0xa9, 0x30, 0x28, 0xfa, 0x38, 0xc1, 0xd8, 0x52, +0xd7, 0x3b, 0x65, 0x9e, 0xe4, 0xd8, 0x3f, 0x8c, +0xff, 0x30, 0x47, 0x21, 0x97, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0xc2, +0xd2, 0xd5, 0x68, 0x05, 0x08, 0x2a, 0xda, 0x23, +0x67, 0xc3, 0x3d, 0x60, 0x16, 0x65, 0x3b, 0xf8, +0x19, 0xd5, 0x7e, 0xa4, 0xb3, 0xe6, 0x88, 0x59, +0x41, 0x42, 0x8d, 0xc4, 0x80, 0x04, 0xdd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0x06, 0x65, 0xd9, 0xbe, 0x98, 0xbf, 0x82, +0xe1, 0xfd, 0x87, 0x6d, 0xf6, 0x87, 0x5f, 0xba, +0x9b, 0xca, 0x71, 0xb5, 0xc5, 0x83, 0x0e, 0x63, +0xac, 0x7d, 0x31, 0x13, 0x59, 0xf7, 0xaa, 0x4a, +0xd1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0x48, 0x82, 0xdb, 0xe1, 0x3c, +0x5d, 0x98, 0x75, 0x22, 0xeb, 0xaf, 0xd8, 0x61, +0x4c, 0x60, 0xf1, 0x05, 0x95, 0xb9, 0xdb, 0xd8, +0x59, 0xd3, 0xbd, 0xb5, 0x84, 0xac, 0x00, 0x72, +0x41, 0xbb, 0x54, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0x21, 0x5b, 0x85, +0x88, 0xf0, 0x72, 0x3d, 0xef, 0x10, 0xe6, 0x6a, +0x84, 0x87, 0xa7, 0x95, 0xa7, 0xd2, 0x45, 0xf8, +0x5b, 0x3e, 0x46, 0x61, 0xbf, 0xc6, 0x40, 0x1a, +0xff, 0x8b, 0xdd, 0x69, 0x4b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0x68, +0x0c, 0x11, 0x60, 0x3f, 0x6e, 0x1c, 0x23, 0xa6, +0x4c, 0xdf, 0x16, 0xc5, 0xba, 0x11, 0xc7, 0x78, +0x6d, 0xee, 0x72, 0x2e, 0xe8, 0x1a, 0x70, 0xe4, +0xd1, 0x11, 0xb8, 0x66, 0xa0, 0x42, 0xfb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0x5b, 0xcc, 0x90, 0x36, 0x6f, 0x5a, 0x74, +0xd3, 0x39, 0x6f, 0x39, 0x01, 0x6b, 0x3a, 0xc9, +0xa4, 0x2c, 0xb7, 0xd1, 0x73, 0x2d, 0x69, 0x8c, +0x5c, 0xe9, 0xf0, 0x0b, 0x4c, 0xbc, 0xf2, 0xab, +0x16, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0xda, 0x89, 0x43, 0xc1, 0x48, +0x50, 0x6b, 0x8f, 0x9b, 0x56, 0x09, 0x06, 0xbd, +0x01, 0x32, 0xe3, 0x9c, 0x51, 0x81, 0x2b, 0x1d, +0xcf, 0x48, 0x31, 0x52, 0x8d, 0x67, 0xab, 0x58, +0x66, 0x16, 0x82, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0xe7, 0xb5, 0x34, +0xe4, 0x78, 0xb7, 0x25, 0x15, 0xef, 0x21, 0x59, +0xd2, 0x34, 0x8f, 0xe6, 0x36, 0x26, 0xd6, 0x56, +0x0a, 0xed, 0xde, 0x47, 0xab, 0xe0, 0x6d, 0x32, +0x7f, 0xd7, 0xf1, 0xee, 0x75, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0xc7, +0xa8, 0x09, 0x20, 0x3b, 0xc7, 0x46, 0x8d, 0x5e, +0x61, 0x59, 0x37, 0xb3, 0x68, 0x53, 0xba, 0x5b, +0x6a, 0x48, 0xe8, 0xb0, 0xd2, 0xe9, 0x5d, 0x5b, +0x1a, 0x22, 0xdd, 0x92, 0xae, 0x32, 0xc9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0xd5, 0xc2, 0x5d, 0x59, 0x8e, 0x0d, 0xc8, +0x00, 0xe4, 0x11, 0x88, 0x82, 0xf1, 0xe9, 0xd0, +0xde, 0x9d, 0x55, 0xdb, 0x4e, 0x82, 0x81, 0x29, +0xb7, 0x15, 0x83, 0xc8, 0x22, 0x21, 0xe5, 0x76, +0xd5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0xe2, 0x0f, 0x8e, 0x3b, 0x3c, +0xf1, 0x0f, 0x61, 0xae, 0x92, 0x37, 0x8e, 0x7d, +0x5c, 0x9b, 0x49, 0x39, 0xff, 0xe3, 0x1e, 0x91, +0xa8, 0x3c, 0x63, 0xa0, 0xb7, 0x73, 0xdd, 0x9d, +0x36, 0x5c, 0x9b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0x70, 0x86, 0x30, +0xfc, 0xbf, 0xa4, 0x56, 0x28, 0xfe, 0xad, 0x3a, +0x12, 0x9a, 0x0c, 0x16, 0x00, 0xe7, 0x4a, 0xa2, +0x4a, 0x0f, 0x51, 0x3e, 0x33, 0xe3, 0xe8, 0x07, +0x62, 0x2a, 0x3a, 0x59, 0xbe, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0x0f, +0x85, 0xd3, 0xc6, 0x11, 0x7e, 0x48, 0x31, 0x94, +0xc6, 0xd5, 0x77, 0x02, 0x07, 0x98, 0x7c, 0x25, +0x26, 0x71, 0x14, 0x89, 0x28, 0xed, 0x9b, 0x40, +0x4e, 0x68, 0xaf, 0x11, 0x3a, 0x8e, 0xf5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0x75, 0x02, 0x6c, 0x05, 0x00, 0x0c, 0x42, +0xf4, 0xc9, 0x60, 0x91, 0x0e, 0x80, 0x8a, 0x41, +0x37, 0x56, 0x2c, 0x36, 0x5f, 0x5c, 0x25, 0x7f, +0x6f, 0x96, 0xc7, 0x98, 0xf3, 0x8a, 0x32, 0x7e, +0x7d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0x29, 0xdd, 0xa1, 0xe4, 0xf0, +0x33, 0xcf, 0x3c, 0x1f, 0x45, 0xf0, 0xbf, 0xec, +0x35, 0x09, 0xba, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xc5, 0x8d, 0x2b, 0x1e, +0x2e, 0x39, 0xc3, 0xf9, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x34, 0xb1, 0x1e, +0x3c, 0x3f, 0xea, 0xe4, 0xb7, 0x72, 0xd6, 0xa5, +0x9f, 0xe8, 0x96, 0x49, 0x71, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x42, +0x7f, 0x26, 0x26, 0x99, 0x40, 0xdf, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0x51, +0xea, 0xf6, 0x32, 0x3c, 0xc1, 0xa3, 0x6c, 0x75, +0xfa, 0x79, 0x9a, 0xcc, 0x1f, 0xc8, 0x75, 0x48, +0x6d, 0xf4, 0xf9, 0xd1, 0x8d, 0x25, 0x1b, 0x4e, +0x75, 0x56, 0x90, 0xb3, 0x24, 0xa1, 0x30, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0x61, 0x1b, 0x1d, 0xa0, 0x4a, 0x25, 0x20, +0x5a, 0x94, 0x17, 0xa1, 0x6a, 0x17, 0x4b, 0xc9, +0x94, 0xd4, 0x8f, 0x54, 0x83, 0x7b, 0xc7, 0x04, +0x46, 0xb9, 0xf3, 0xcd, 0xdd, 0x2c, 0x08, 0x9a, +0x0b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0x19, 0x5f, 0xe3, 0x5e, 0x02, +0x26, 0x4b, 0x54, 0x84, 0x8b, 0x6d, 0x61, 0x58, +0x9d, 0x91, 0x1e, 0xe1, 0x26, 0xf5, 0x6c, 0x31, +0x22, 0xfc, 0xd5, 0x2c, 0x08, 0xca, 0xfc, 0x10, +0xa5, 0xe4, 0xb8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x1d, 0x52, 0xb4, +0xba, 0x8f, 0x43, 0x0c, 0x22, 0x31, 0x9b, 0x3b, +0xd9, 0x76, 0x7b, 0x38, 0x7e, 0x13, 0x20, 0x0a, +0xbf, 0x91, 0x84, 0xb1, 0x57, 0xb3, 0x82, 0x2f, +0x92, 0x30, 0xd9, 0x7c, 0x4a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0x6e, +0xc3, 0x52, 0x98, 0x95, 0x5e, 0xba, 0xbf, 0x42, +0x11, 0x3f, 0x95, 0x8d, 0xa6, 0xac, 0x66, 0xf9, +0x06, 0xd2, 0x18, 0xeb, 0x60, 0x7a, 0x43, 0x68, +0xa9, 0xdb, 0xfe, 0x47, 0x10, 0x17, 0xdd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0x69, 0x19, 0x1c, 0x07, 0x9e, 0x94, 0x52, +0xb1, 0xa4, 0x99, 0x14, 0x6b, 0xa0, 0xd5, 0x34, +0x91, 0xab, 0xbb, 0x69, 0xb0, 0x46, 0x5c, 0xf0, +0x8f, 0xbf, 0xdb, 0x79, 0xe2, 0x6b, 0x16, 0xf0, +0x03, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0x05, 0x0f, 0xbc, 0xe6, 0xd1, +0x5b, 0x26, 0xf9, 0x50, 0x08, 0x50, 0xf9, 0x11, +0x32, 0xc4, 0x57, 0x6e, 0x23, 0x59, 0x8e, 0x47, +0xc7, 0x6d, 0x88, 0x88, 0x23, 0xa5, 0xb0, 0x5f, +0xc7, 0x2c, 0xe6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0xaf, 0xc0, 0x17, +0x6f, 0xd1, 0xde, 0x19, 0x0a, 0x4c, 0xa5, 0x45, +0x1a, 0xcd, 0x13, 0x7c, 0x0d, 0xfc, 0x0f, 0x9a, +0xf1, 0xae, 0x54, 0x37, 0x74, 0x83, 0x91, 0x98, +0xa4, 0xbe, 0x45, 0xaa, 0x05, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x97, +0xf2, 0x95, 0xd5, 0x08, 0x29, 0xc5, 0x2f, 0x5a, +0xdf, 0xfb, 0x0f, 0xf7, 0xc8, 0xdd, 0xf4, 0x06, +0x6f, 0xa9, 0xab, 0xa0, 0xed, 0xc2, 0xae, 0xe7, +0xe1, 0xea, 0x49, 0xfa, 0x0b, 0xc9, 0x5f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0xd9, 0x79, 0x55, 0x67, 0x5d, 0x36, 0x64, +0xec, 0x32, 0xb3, 0x2f, 0xf1, 0xd1, 0xd3, 0xa1, +0x24, 0x72, 0xb9, 0xe7, 0xca, 0x64, 0xd4, 0x5a, +0x7d, 0x50, 0xbe, 0x38, 0x03, 0x52, 0x66, 0xab, +0x39, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0xc4, 0xa3, 0x45, 0xab, 0x5a, +0x87, 0xda, 0xa0, 0x85, 0xb2, 0xfe, 0x6f, 0x2a, +0x41, 0x64, 0x26, 0x73, 0xc7, 0xbe, 0x5b, 0x79, +0x27, 0xea, 0x8b, 0x62, 0x37, 0xe8, 0xc6, 0xe9, +0x61, 0x3c, 0xcf, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0x85, 0x29, 0xb3, +0xa2, 0x18, 0x41, 0x0d, 0xb7, 0x18, 0xbd, 0x53, +0xef, 0x8b, 0xef, 0x22, 0x2d, 0x8c, 0xee, 0xab, +0x3f, 0x69, 0x1c, 0x56, 0x85, 0x04, 0x10, 0x79, +0x66, 0xec, 0xb9, 0x70, 0xff, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0x16, +0x20, 0x1e, 0xa3, 0x2f, 0x01, 0xca, 0x5e, 0xd0, +0x0c, 0x57, 0x3d, 0x87, 0x95, 0x8f, 0x16, 0x1e, +0x3a, 0x9a, 0xb6, 0xe3, 0x3a, 0x1f, 0xeb, 0xa9, +0x3f, 0x7e, 0xfe, 0xfd, 0x9a, 0x4d, 0x8f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0xa8, 0xcb, 0x9c, 0xf5, 0x49, 0x20, 0x32, +0xb3, 0x4a, 0xac, 0xbc, 0x57, 0xf0, 0x61, 0x44, +0x50, 0x36, 0x14, 0x74, 0x73, 0x1e, 0x67, 0x62, +0x42, 0x87, 0xe8, 0x52, 0x6b, 0xe8, 0x14, 0x07, +0x8d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0xad, 0x6e, 0xd4, 0xd4, 0xf7, +0xcb, 0xc9, 0x84, 0xb3, 0x0a, 0x8e, 0x32, 0xe8, +0xf3, 0xf3, 0xd8, 0x91, 0x11, 0x98, 0x6f, 0x41, +0xd4, 0x12, 0x23, 0xe5, 0x8f, 0x4a, 0x82, 0x7a, +0x53, 0x1b, 0xe5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0x29, 0x0b, 0x5a, +0xb3, 0x7c, 0xba, 0x13, 0x57, 0xe6, 0x39, 0xf2, +0x50, 0x83, 0x62, 0x83, 0xa5, 0x5d, 0xa5, 0x44, +0x70, 0x18, 0xf0, 0x80, 0xda, 0x5d, 0xb5, 0x9b, +0x35, 0xc3, 0xb2, 0xad, 0xa7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0x65, +0xf1, 0x4a, 0x75, 0xd9, 0x02, 0x6c, 0x94, 0x9e, +0x65, 0x24, 0x0d, 0x68, 0x81, 0xd2, 0x7b, 0x6f, +0x75, 0x23, 0x85, 0x43, 0x62, 0xdb, 0xbb, 0x93, +0x95, 0x00, 0x42, 0xcb, 0xcc, 0x81, 0x3a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0x62, 0xcf, 0x02, 0x15, 0x76, 0xe8, 0x80, +0xa8, 0x39, 0x7f, 0x5d, 0x5a, 0x21, 0xb4, 0x8a, +0xec, 0x19, 0x19, 0xcc, 0x6d, 0x8b, 0x59, 0x89, +0x2b, 0xe6, 0x4d, 0x57, 0x03, 0x96, 0x2f, 0x47, +0x32, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0x2e, 0x6c, 0x17, 0xa9, 0x75, +0xe7, 0xd1, 0x7f, 0x92, 0x4c, 0xbb, 0x34, 0x14, +0xa8, 0x64, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xdf, 0x55, 0xf9, 0x4e, +0x4b, 0x81, 0x8d, 0x01, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0x6c, 0xee, 0x99, +0x32, 0x01, 0xdb, 0xd2, 0x32, 0xb9, 0xe8, 0x9c, +0x9c, 0x17, 0x29, 0x8c, 0xca, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xce, +0x13, 0x7c, 0xdd, 0xae, 0xff, 0xa9, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0x66, +0x6c, 0xaa, 0xef, 0x91, 0xd4, 0xfd, 0x36, 0xe8, +0x44, 0xe4, 0x54, 0x6d, 0x33, 0x72, 0x55, 0x52, +0xc9, 0x9f, 0xf7, 0x31, 0x84, 0x9f, 0xde, 0x87, +0x76, 0x5e, 0x08, 0x9a, 0xca, 0x6d, 0xf9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0xca, 0x6c, 0x64, 0x3a, 0x4c, 0x0c, 0xf3, +0x2e, 0x8e, 0xd6, 0x0e, 0x1a, 0x1d, 0xb2, 0x35, +0xdf, 0x6b, 0xbd, 0x33, 0x7c, 0x7c, 0xe3, 0x41, +0x17, 0x19, 0x29, 0xc2, 0x06, 0xe7, 0x49, 0xe0, +0xaf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0x95, 0x50, 0x3d, 0x0c, 0x6f, +0xee, 0xe8, 0x5c, 0x94, 0xf7, 0xae, 0x71, 0xda, +0xbc, 0x1b, 0x1d, 0x54, 0xda, 0x7c, 0x01, 0x43, +0x7e, 0xd2, 0xd1, 0x59, 0x97, 0x22, 0xb0, 0xcd, +0x5d, 0xd7, 0xcb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x11, 0x57, 0xbd, +0x31, 0x24, 0x8b, 0x73, 0x80, 0xa0, 0x6f, 0x65, +0xc6, 0x7a, 0x88, 0x89, 0x70, 0xa5, 0xe2, 0xd2, +0xda, 0x6b, 0xe6, 0x8c, 0xcc, 0x0a, 0xe0, 0xc7, +0xd5, 0x48, 0xca, 0xc5, 0x49, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x14, +0xdb, 0x03, 0xc7, 0xe3, 0xfe, 0x4e, 0x9c, 0x38, +0xfa, 0xec, 0xab, 0xe9, 0x01, 0x2e, 0xd2, 0x3e, +0xc6, 0xba, 0xd6, 0xcb, 0x27, 0xad, 0xd3, 0x8d, +0x25, 0xaf, 0x73, 0xe0, 0xc4, 0x5f, 0x3d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0xf8, 0x40, 0xfe, 0x89, 0x8b, 0xfb, 0x00, +0x85, 0xee, 0x54, 0x6b, 0x7b, 0x96, 0x37, 0x44, +0x26, 0x4d, 0x4d, 0x27, 0xfa, 0x7f, 0x59, 0xa0, +0x3d, 0x02, 0xba, 0x4a, 0x74, 0xac, 0xc7, 0x03, +0x79, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0xd1, 0xb0, 0x0b, 0xed, 0xe3, +0x8d, 0x36, 0xcc, 0x4a, 0x01, 0xd5, 0xb4, 0x9a, +0x52, 0x4d, 0x62, 0xd4, 0x41, 0xe9, 0x52, 0x1c, +0x9b, 0xef, 0x76, 0x6e, 0x0c, 0xfd, 0x6e, 0xec, +0xdc, 0x5c, 0x42, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0x4a, 0xe6, 0x3c, +0x47, 0xb2, 0x1d, 0xe3, 0xeb, 0xf2, 0x02, 0x40, +0xcb, 0xb8, 0xf2, 0xa9, 0xd5, 0x68, 0xb3, 0x57, +0xe3, 0x3d, 0xb2, 0xdf, 0x67, 0x80, 0xce, 0xf9, +0xc2, 0xf4, 0x03, 0xd4, 0x11, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0x27, +0xf7, 0xed, 0x04, 0x42, 0x81, 0xa5, 0x96, 0xfd, +0x06, 0x7b, 0x38, 0x1c, 0x40, 0x00, 0xad, 0x2d, +0xe2, 0xcd, 0xf5, 0x54, 0x7d, 0x77, 0x22, 0xe3, +0xe0, 0xc8, 0xb1, 0x2a, 0x22, 0xca, 0x99, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0xed, 0xda, 0xee, 0x29, 0x33, 0x6a, 0xba, +0x5c, 0x1a, 0x9d, 0xd4, 0x67, 0x4e, 0x7f, 0xba, +0xc1, 0xa2, 0xac, 0xb1, 0x91, 0xc9, 0x7b, 0x68, +0xc1, 0xa5, 0xb0, 0x15, 0xb9, 0x10, 0xa7, 0x75, +0xfe, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0xd6, 0x7d, 0xa7, 0xb9, 0x90, +0x54, 0xa5, 0xd1, 0x10, 0x8f, 0x63, 0xaf, 0xf7, +0xc9, 0xff, 0xb7, 0x13, 0x20, 0x77, 0xf4, 0xc8, +0xb8, 0x5c, 0xa0, 0x98, 0x90, 0x6a, 0xd4, 0xc8, +0xc6, 0x09, 0x3f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0xa0, 0x2c, 0xa2, +0xbd, 0xf8, 0x11, 0x4c, 0xa3, 0xad, 0x4c, 0x30, +0xf4, 0xe3, 0x7a, 0x8d, 0xe3, 0x4a, 0x32, 0xe4, +0x8f, 0x5f, 0x66, 0x42, 0x2d, 0xc5, 0xc3, 0x45, +0xd1, 0x88, 0x98, 0xff, 0xaf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0x3e, +0xea, 0x0d, 0xad, 0x13, 0x8f, 0x55, 0xba, 0x46, +0x3b, 0xeb, 0x11, 0x99, 0xbb, 0x80, 0x66, 0xf7, +0xe5, 0xc6, 0x08, 0x1d, 0x21, 0x85, 0xc8, 0x8b, +0xdb, 0x4a, 0xab, 0x9e, 0xf1, 0x8d, 0xa1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0x7a, 0x42, 0xae, 0xf8, 0xe7, 0xf0, 0x40, +0x7a, 0x98, 0x61, 0x79, 0x0d, 0x84, 0xd4, 0xba, +0x92, 0x55, 0x5f, 0x95, 0x8a, 0x1c, 0x9c, 0x14, +0x91, 0x51, 0xb2, 0xa2, 0x04, 0x28, 0xa7, 0x9f, +0x10, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0xf3, 0xed, 0x34, 0x00, 0x4b, +0x40, 0x3c, 0x69, 0x9e, 0x43, 0xf5, 0xd1, 0x11, +0xb4, 0x37, 0xca, 0x8b, 0x0d, 0x0d, 0x11, 0x67, +0x11, 0xe7, 0xe7, 0x96, 0xe7, 0x6b, 0x66, 0x39, +0x97, 0x22, 0xf3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0xc2, 0x96, 0xb2, +0x44, 0x84, 0x99, 0x17, 0xfe, 0x0b, 0xf4, 0x29, +0x84, 0x7d, 0xf6, 0x09, 0xf4, 0xe5, 0x79, 0xfb, +0x5f, 0x35, 0x6b, 0xf4, 0xcd, 0x74, 0xc4, 0x31, +0x8d, 0x83, 0x30, 0x7b, 0xba, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0xe0, +0x34, 0x6b, 0xb6, 0x2e, 0x22, 0x68, 0x3b, 0x4b, +0x45, 0x77, 0x15, 0x88, 0x32, 0xc4, 0x0f, 0x06, +0x23, 0x20, 0x13, 0x97, 0x23, 0x58, 0xbe, 0xb3, +0xf0, 0x0a, 0x93, 0xf2, 0xca, 0x6c, 0x83, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0xc2, 0x9e, 0xb9, 0x7a, 0x48, 0xf6, 0x05, +0x6d, 0x45, 0x38, 0x92, 0x7b, 0x01, 0x5f, 0x20, +0x18, 0x16, 0xf0, 0x7c, 0xb0, 0xae, 0x43, 0x35, +0xbd, 0x2d, 0xce, 0xbc, 0x9e, 0x00, 0x69, 0xb7, +0x10, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0x1d, 0xed, 0xd6, 0x0c, 0x41, +0xe6, 0x44, 0xf2, 0xf6, 0xe5, 0xb1, 0x67, 0xac, +0x3f, 0xa5, 0xd5, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x9a, 0xc5, 0xdb, 0xf7, +0x70, 0x37, 0x1c, 0x0c, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0xe8, 0x84, 0x47, +0x71, 0x31, 0x87, 0x86, 0x3a, 0xd0, 0x59, 0x48, +0xdd, 0x1f, 0x54, 0x30, 0x9b, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xba, 0xcb, +0x25, 0x90, 0x1e, 0xc3, 0xe9, 0xfd, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0xa0, +0xdc, 0x7a, 0x32, 0xe0, 0xe7, 0x71, 0x38, 0xd1, +0x80, 0x8b, 0x80, 0x61, 0x2c, 0x42, 0x65, 0x07, +0xd2, 0xf9, 0x68, 0x3a, 0x4d, 0xb8, 0xa7, 0x2b, +0x12, 0x89, 0xc1, 0x75, 0x1f, 0x74, 0x94, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0x87, 0xc4, 0xff, 0xe6, 0x7f, 0x9b, 0x0d, +0xfa, 0xc4, 0x73, 0x83, 0x7b, 0xc5, 0x0a, 0x8e, +0x9c, 0x47, 0x3b, 0xa6, 0x03, 0x8d, 0x28, 0x8c, +0xa4, 0xa0, 0x12, 0x76, 0x7f, 0x93, 0xa8, 0xc1, +0xc6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0x12, 0x89, 0x45, 0xa3, 0x22, +0xab, 0x2e, 0x37, 0xa1, 0xf4, 0x89, 0x7c, 0xfa, +0xa4, 0x28, 0xb9, 0xf7, 0x92, 0xe7, 0x58, 0xb9, +0x32, 0x11, 0xa1, 0xa3, 0xd5, 0xcf, 0x56, 0xd4, +0xcd, 0x8c, 0x38, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0x59, 0xbe, 0xb7, +0x4e, 0x42, 0xa4, 0x23, 0x1a, 0xb4, 0x88, 0x78, +0x6e, 0xe2, 0xed, 0x47, 0xd6, 0x83, 0x2b, 0x51, +0xc3, 0x3f, 0xb5, 0xcc, 0xa4, 0x12, 0x9f, 0x5b, +0x99, 0xdf, 0x24, 0x25, 0x41, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0xf3, +0xcf, 0x22, 0xef, 0xba, 0xa3, 0xb9, 0xca, 0x7b, +0x2a, 0x2c, 0x51, 0x69, 0xa0, 0x69, 0xa6, 0x39, +0x86, 0x55, 0x11, 0x79, 0x0b, 0xfe, 0xc5, 0xff, +0x72, 0xdd, 0x54, 0x3c, 0x08, 0xd7, 0x69, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0x8e, 0xac, 0x4d, 0x57, 0x00, 0xc1, 0x07, +0x77, 0x63, 0xae, 0xd4, 0x9a, 0xf9, 0xa9, 0x22, +0x22, 0xa0, 0x28, 0x9d, 0x48, 0x5e, 0x66, 0x29, +0x8e, 0xac, 0x2b, 0xbd, 0x03, 0x12, 0x38, 0x26, +0x86, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0x5d, 0x09, 0x0c, 0x62, 0x49, +0x4c, 0x32, 0xb8, 0x78, 0x87, 0x2c, 0xaa, 0x85, +0x5f, 0x6f, 0x42, 0x26, 0x93, 0x51, 0x7c, 0xc5, +0xbf, 0x41, 0x2d, 0x18, 0x9a, 0x99, 0xa7, 0x97, +0x24, 0x3b, 0xfd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0x4b, 0x3b, 0xa5, +0x58, 0x3b, 0xf5, 0x2e, 0x18, 0x4d, 0xe8, 0xb8, +0x47, 0xb5, 0xd5, 0x0b, 0x61, 0x63, 0x45, 0x40, +0xbb, 0x5e, 0x99, 0x8e, 0x8c, 0x34, 0xe6, 0xce, +0x29, 0xdb, 0xff, 0xb6, 0xf1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0x99, +0xcb, 0x1e, 0xa9, 0x33, 0x1b, 0x72, 0x32, 0xbc, +0x44, 0x1e, 0xf4, 0x0f, 0x84, 0xb4, 0x4b, 0xbe, +0xf7, 0x6a, 0x0d, 0xcb, 0x11, 0x7f, 0x86, 0xe4, +0x64, 0xda, 0xa9, 0xba, 0x72, 0x9b, 0xc3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0xe0, 0x63, 0x4e, 0x7e, 0x3d, 0xb4, 0x0f, +0x31, 0x93, 0x6a, 0x49, 0x60, 0xe5, 0x59, 0xd9, +0xe1, 0x84, 0x66, 0x77, 0x23, 0x36, 0x7d, 0xbd, +0xdb, 0x6e, 0x8c, 0xa1, 0x3a, 0xbf, 0xfc, 0x23, +0xa8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0xd0, 0xe8, 0xf9, 0xd8, 0x0d, +0x53, 0x7d, 0xd6, 0x76, 0xb8, 0x46, 0xe5, 0xa9, +0x47, 0x93, 0xe6, 0x56, 0x4a, 0xd2, 0x16, 0x2a, +0x11, 0xc2, 0xcf, 0xc2, 0x52, 0x32, 0x38, 0xa7, +0x46, 0x47, 0x17, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0xa1, 0x45, 0xe0, +0x17, 0x55, 0x80, 0x1e, 0x34, 0x46, 0x16, 0xe0, +0x47, 0x29, 0x7f, 0xd4, 0x85, 0xbe, 0xc5, 0xf1, +0x7c, 0x11, 0x4f, 0x30, 0x86, 0xba, 0x27, 0x5e, +0xe7, 0x04, 0x0e, 0x1a, 0x9f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0xca, +0x27, 0x36, 0xa6, 0x35, 0xc1, 0x11, 0x94, 0xc3, +0x7f, 0x1e, 0x69, 0x87, 0xaf, 0xb2, 0xa9, 0xee, +0xc7, 0x65, 0x82, 0x91, 0x1b, 0x3e, 0xe2, 0xcc, +0x2f, 0xc7, 0xcc, 0x7c, 0x0d, 0x5b, 0xff, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0xe7, 0x26, 0x9f, 0x3d, 0x8b, 0x13, 0xea, +0xee, 0xdc, 0x31, 0x7f, 0x5a, 0x3e, 0x1d, 0x02, +0x54, 0x96, 0xaa, 0x0d, 0xdf, 0xa1, 0x04, 0xb6, +0x3f, 0x02, 0xe7, 0x15, 0x49, 0xad, 0xc4, 0x27, +0x66, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0x42, 0x21, 0xe0, 0xab, 0xff, +0x2c, 0x2c, 0x8a, 0x6d, 0xe3, 0x05, 0x1e, 0xda, +0x0a, 0xc4, 0x93, 0x3e, 0xad, 0xe5, 0x0b, 0xcf, +0x30, 0x97, 0x48, 0xc9, 0x07, 0x04, 0x55, 0x33, +0xb6, 0xb0, 0x3f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0x58, 0x41, 0xef, +0xee, 0x26, 0x16, 0x81, 0xf3, 0x19, 0xb3, 0x33, +0x2a, 0xbc, 0x4b, 0x49, 0xfd, 0x6d, 0x3e, 0xdc, +0xaf, 0xf7, 0x64, 0xf1, 0x6e, 0xaf, 0xdf, 0xe4, +0x83, 0x7a, 0x7f, 0xdd, 0x78, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0x7a, +0x63, 0xbd, 0x8f, 0xb1, 0x59, 0xff, 0x94, 0x6f, +0x4c, 0x55, 0x9b, 0x08, 0x93, 0x53, 0xfc, 0xfa, +0xa1, 0xd9, 0x9f, 0x46, 0x45, 0xb0, 0xbf, 0x4f, +0x2a, 0x7f, 0x51, 0x8e, 0x6f, 0x1d, 0x42, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0xa1, 0x8f, 0x03, 0xe5, 0xec, 0x61, 0x3b, +0xc4, 0x29, 0x67, 0xbb, 0x6a, 0x2c, 0x62, 0xaf, +0x69, 0x48, 0xd1, 0x39, 0x6f, 0x0f, 0x8e, 0xd3, +0x16, 0x29, 0x33, 0x22, 0x4c, 0xb1, 0x20, 0x3a, +0xcd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0xa8, 0x86, 0x9e, 0xd0, 0xc7, +0x65, 0x60, 0x1a, 0x62, 0x5a, 0xd7, 0x11, 0x1d, +0x6f, 0x90, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x4f, 0x2d, 0x07, 0x52, +0xfa, 0x9f, 0x28, 0x48, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x30, 0x72, 0xe2, +0xec, 0x51, 0x93, 0x4b, 0x63, 0x4d, 0x3e, 0x8a, +0xae, 0x27, 0x36, 0x93, 0x74, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa3, 0xf2, +0x20, 0x96, 0x18, 0x87, 0x8d, 0xef, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0xfd, +0xb9, 0x76, 0xd5, 0xed, 0x63, 0xbf, 0x85, 0x6d, +0xd8, 0x6f, 0x12, 0x33, 0x62, 0x34, 0x93, 0xfe, +0x35, 0x0b, 0x2a, 0xe8, 0x83, 0x96, 0x0a, 0xcd, +0x36, 0xd0, 0xd0, 0xf9, 0x13, 0xa9, 0xf1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x13, 0x0e, 0x50, 0xe2, 0xde, 0x9b, 0x27, +0xab, 0x79, 0xe8, 0x7a, 0x0d, 0xf4, 0x66, 0x53, +0x06, 0x75, 0xd6, 0x64, 0xb4, 0x14, 0xf3, 0xbf, +0x85, 0x71, 0x6e, 0xaa, 0xe7, 0xeb, 0xc8, 0x0c, +0x82, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0xb7, 0xb5, 0x6f, 0x5a, 0xed, +0x36, 0x83, 0x50, 0x0d, 0xf9, 0xe4, 0x1d, 0x3e, +0x72, 0x75, 0xb4, 0xcd, 0x34, 0x04, 0x66, 0xaf, +0x77, 0x4a, 0x2e, 0x9d, 0x8b, 0x39, 0x53, 0x02, +0xb8, 0x88, 0x70, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0xe4, 0x4c, 0xb9, +0x9a, 0x81, 0xcc, 0xcc, 0x42, 0xbb, 0xaa, 0xa8, +0x26, 0xbf, 0xfc, 0x20, 0x38, 0x70, 0xf8, 0x80, +0xa3, 0x20, 0x07, 0x7a, 0x83, 0x14, 0xdf, 0x70, +0x39, 0x8c, 0x79, 0xc5, 0x23, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x67, +0x49, 0xd6, 0xe7, 0xec, 0xa4, 0x65, 0x91, 0x1f, +0x98, 0x1b, 0xfe, 0xb3, 0x5a, 0x6e, 0xb0, 0xc4, +0x19, 0x95, 0xbe, 0xc8, 0x56, 0xd7, 0xe6, 0x78, +0x2a, 0xdc, 0xfc, 0x79, 0xdf, 0x6f, 0x61, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0x15, 0xd9, 0x72, 0x41, 0x18, 0x22, 0x89, +0x24, 0xa0, 0xa3, 0x47, 0xde, 0x09, 0xc0, 0xec, +0x12, 0x93, 0xa7, 0xc1, 0x91, 0xd4, 0xf4, 0x1c, +0x53, 0x81, 0xbd, 0xbc, 0xee, 0x91, 0xaa, 0x35, +0xaa, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0x96, 0xb6, 0x31, 0x96, 0xe6, +0x8b, 0xab, 0x2b, 0x71, 0xd5, 0xf2, 0x10, 0x4e, +0xfb, 0x41, 0xf7, 0x9f, 0x18, 0x49, 0x15, 0x41, +0x1d, 0x04, 0xb4, 0xa3, 0x92, 0x09, 0xf6, 0x41, +0xa5, 0x01, 0x39, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0xe6, 0x69, 0xdc, +0xc6, 0x3c, 0xc2, 0xd3, 0x2b, 0x4e, 0x8d, 0x69, +0xd5, 0x59, 0x98, 0x22, 0x6d, 0x04, 0x4e, 0xec, +0x3f, 0xee, 0xa8, 0xde, 0xbe, 0x0b, 0x2a, 0x42, +0x78, 0x73, 0xdb, 0xdc, 0x79, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x14, +0x45, 0x63, 0xd1, 0xbb, 0x38, 0x01, 0xe0, 0x15, +0x96, 0x1a, 0x4a, 0x04, 0x52, 0x21, 0x9d, 0x39, +0x50, 0x3d, 0x45, 0xac, 0x43, 0x59, 0xd4, 0xfb, +0xcf, 0xc5, 0xa1, 0x1c, 0x8c, 0x3e, 0xb5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0x05, 0xfc, 0x3f, 0xea, 0x6a, 0x86, 0x5e, +0xd5, 0x0c, 0xdd, 0x29, 0x90, 0x18, 0xac, 0xe2, +0xfe, 0xf7, 0x6d, 0xcf, 0xde, 0x5a, 0x77, 0xcf, +0xbd, 0x36, 0x86, 0x42, 0x76, 0x2b, 0x3b, 0xd2, +0x5d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0x43, 0xef, 0x8d, 0xb9, 0x47, +0x6f, 0x1a, 0xbb, 0x79, 0xbe, 0xa6, 0x1b, 0xf0, +0xb2, 0x4d, 0x22, 0x03, 0xed, 0x87, 0x07, 0x21, +0x97, 0xd8, 0xc2, 0xca, 0xf7, 0xe4, 0xaa, 0xd5, +0xf9, 0x19, 0x36, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0x4b, 0x5f, 0x0b, +0xec, 0xc9, 0x5c, 0x8d, 0x63, 0xff, 0x6e, 0x82, +0x0d, 0x13, 0xd3, 0xc7, 0x44, 0x42, 0x6f, 0x0d, +0xc0, 0xf8, 0x21, 0x26, 0x14, 0x9f, 0x63, 0x2d, +0x02, 0x44, 0xe0, 0x5b, 0x3f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x78, +0x91, 0x11, 0xb1, 0x1a, 0x53, 0xc9, 0x99, 0xa4, +0xa8, 0xf6, 0xae, 0xc9, 0xb1, 0xc7, 0x7e, 0x58, +0x33, 0x24, 0x20, 0x11, 0x0c, 0x75, 0x76, 0xd7, +0x80, 0xc3, 0xd5, 0x75, 0xc8, 0x7d, 0x50, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0x40, 0x64, 0x8a, 0x24, 0xb0, 0x89, 0x84, +0x0c, 0x9c, 0x41, 0x0c, 0x20, 0x9a, 0x80, 0x6b, +0xfb, 0x38, 0xb2, 0x2e, 0x4d, 0xd5, 0xfa, 0xb2, +0xbe, 0x22, 0x40, 0x25, 0x7c, 0x4e, 0x75, 0xb9, +0xde, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0xa5, 0xf0, 0x36, 0xfe, 0x49, +0x10, 0x6a, 0xac, 0x8f, 0x0a, 0xd3, 0x3d, 0x04, +0xb3, 0x2a, 0xb9, 0x0f, 0x88, 0x1e, 0x60, 0xf9, +0x80, 0x16, 0xb1, 0xfd, 0xc0, 0x7e, 0xb0, 0x55, +0x0f, 0x78, 0x7b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x0a, 0xee, 0x57, +0x42, 0x00, 0xf1, 0xdc, 0x58, 0x2b, 0x93, 0xcf, +0xaa, 0x58, 0x49, 0xa7, 0x2d, 0x28, 0x2d, 0xe8, +0x08, 0x93, 0xa9, 0x21, 0x25, 0xb5, 0x2f, 0x69, +0x91, 0x11, 0xc4, 0xae, 0x8d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0x86, +0x91, 0xde, 0x1e, 0x8b, 0x68, 0x26, 0xfe, 0xf0, +0xc8, 0xd2, 0xf6, 0x25, 0x26, 0x49, 0xdd, 0x94, +0x72, 0x7d, 0x50, 0xc7, 0x28, 0x5c, 0x9b, 0xf5, +0x9f, 0xc9, 0x6b, 0x6b, 0xd7, 0xaa, 0x19, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0xd9, 0x35, 0x0e, 0xd9, 0xff, 0xd0, 0xf8, +0x8d, 0x60, 0xbf, 0xac, 0xea, 0xef, 0xa0, 0x47, +0x5f, 0x22, 0x77, 0x39, 0xb3, 0x0b, 0x4c, 0x05, +0xd3, 0x1c, 0xd4, 0x24, 0x36, 0xa8, 0x37, 0x32, +0x89, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0xe7, 0x3c, 0x58, 0xae, 0x08, +0xca, 0x82, 0x2c, 0x77, 0x30, 0x31, 0x07, 0xcf, +0x22, 0x5e, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x17, 0x94, 0x0b, 0x5e, +0xde, 0xe0, 0x03, 0x10, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0xdd, 0xad, 0x2e, +0x79, 0x6f, 0xb8, 0xb4, 0xb0, 0x1b, 0xfb, 0x6c, +0xed, 0x5c, 0x0e, 0xe4, 0x9f, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf7, 0xac, +0xf2, 0x56, 0x84, 0xcc, 0x5d, 0xfe, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0xb2, +0xad, 0xf2, 0x66, 0x40, 0x79, 0x9a, 0x59, 0xaa, +0x24, 0xe2, 0x24, 0x2e, 0xc0, 0xb5, 0xd8, 0xaf, +0x7e, 0x15, 0xe4, 0x51, 0xb8, 0x03, 0xdd, 0xca, +0xd9, 0x22, 0x83, 0xef, 0x1d, 0x0e, 0x78, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x60, 0x67, 0x3c, 0xf4, 0xcf, 0xf3, 0x91, +0x6e, 0x2b, 0x1b, 0x9b, 0x68, 0xd7, 0x1b, 0xdf, +0x8c, 0xc3, 0x48, 0xa4, 0x5c, 0xfc, 0x68, 0x1c, +0x94, 0x50, 0xa8, 0xea, 0x24, 0xe5, 0x8f, 0x22, +0x84, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0xc5, 0x88, 0xc2, 0xab, 0x35, +0xee, 0x90, 0x66, 0x84, 0x12, 0xa9, 0xf6, 0x61, +0x09, 0x8a, 0x8e, 0xca, 0xfc, 0x0b, 0xb3, 0x41, +0x08, 0xa4, 0xc8, 0xa3, 0x7f, 0x91, 0xf4, 0xab, +0xb0, 0x1b, 0xde, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0xb1, 0x7d, 0xc9, +0xc3, 0x83, 0xaf, 0xc3, 0x09, 0x5e, 0x30, 0x63, +0x5c, 0xf8, 0xc4, 0x16, 0xb6, 0x2b, 0x7d, 0x1a, +0x70, 0x47, 0xf5, 0xec, 0x82, 0x4c, 0x69, 0x54, +0x08, 0xa1, 0xb8, 0xe8, 0x57, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0x27, +0x2e, 0x2d, 0xd2, 0x11, 0x14, 0xa8, 0xda, 0x7c, +0x88, 0x3e, 0x7f, 0x87, 0x6b, 0x33, 0xa1, 0x10, +0x1f, 0x46, 0xb6, 0xf4, 0x37, 0x3c, 0x8c, 0x5e, +0x72, 0x59, 0x18, 0xd4, 0xf5, 0x04, 0x1a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x37, 0x2f, 0x96, 0x71, 0xa7, 0x06, 0x58, +0x1c, 0xe8, 0x4b, 0x84, 0xb3, 0x97, 0xe3, 0x5e, +0x4a, 0x02, 0x9f, 0x77, 0xc0, 0x59, 0x39, 0x44, +0xe8, 0xae, 0x9a, 0xc3, 0x23, 0xc1, 0x90, 0x95, +0x94, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0xd1, 0x59, 0x08, 0x00, 0x72, +0x8b, 0x27, 0x61, 0xc7, 0xae, 0xbb, 0x97, 0x21, +0x5a, 0xa1, 0x25, 0x9e, 0x09, 0xfa, 0x55, 0x22, +0xca, 0x90, 0x89, 0xd4, 0x87, 0xd2, 0xe4, 0xee, +0xc1, 0xfe, 0x76, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0xda, 0x69, 0x47, +0x3a, 0xc1, 0xcf, 0xea, 0x3a, 0x24, 0xd4, 0xcf, +0x02, 0x34, 0x6a, 0x06, 0xd7, 0xc6, 0xbd, 0x0e, +0x1e, 0x54, 0x14, 0xff, 0x19, 0xb9, 0x14, 0xc9, +0xa6, 0x8a, 0x9b, 0xa9, 0x29, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0xe6, +0x49, 0x5e, 0xd6, 0xf2, 0xb4, 0xc4, 0xb7, 0x99, +0x3c, 0xdf, 0x8e, 0x2c, 0x99, 0x9a, 0x3e, 0x6c, +0x5e, 0x1a, 0x8e, 0xf5, 0x45, 0x99, 0xfa, 0x90, +0x5f, 0xba, 0xf7, 0x20, 0x5b, 0x09, 0xf2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0xdd, 0xa2, 0xd8, 0xdf, 0x2e, 0x54, 0xce, +0x07, 0x20, 0xd3, 0x80, 0xe5, 0x73, 0xc7, 0x4a, +0xa1, 0x5c, 0xd8, 0x2b, 0xc5, 0xa5, 0x57, 0x2c, +0x76, 0x81, 0x6b, 0x77, 0xb7, 0xcc, 0xe5, 0x37, +0x0e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0xd0, 0x76, 0x66, 0x27, 0xd0, +0xa2, 0xe1, 0x01, 0x75, 0x24, 0x68, 0xfc, 0x4a, +0x1d, 0xf6, 0x4e, 0xe0, 0xe6, 0xc0, 0xe1, 0xc3, +0x24, 0x3c, 0x4f, 0xf4, 0x15, 0x57, 0x9c, 0x33, +0x7a, 0xfc, 0x16, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0x3e, 0x7d, 0xd2, +0x90, 0xd9, 0x76, 0x91, 0x77, 0x98, 0x74, 0xf5, +0x23, 0x55, 0x1d, 0x22, 0x8f, 0x2a, 0x51, 0xb6, +0xdc, 0xe5, 0x43, 0xaa, 0x95, 0x1d, 0x94, 0xd4, +0xa9, 0x90, 0x8d, 0x1c, 0xb1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0xe6, +0x54, 0x30, 0x93, 0xc1, 0x83, 0x03, 0xb4, 0x94, +0x79, 0xbe, 0x6f, 0x47, 0xbe, 0x20, 0x54, 0x73, +0x3f, 0xb3, 0x06, 0x5c, 0xe3, 0xb0, 0x69, 0xed, +0xf7, 0x6c, 0x85, 0xa9, 0xab, 0x3c, 0x4c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0xbe, 0x0a, 0x13, 0x1d, 0xb8, 0x40, 0xe7, +0x4b, 0x19, 0x9f, 0x3c, 0x53, 0x43, 0x85, 0x35, +0x73, 0xda, 0x00, 0x7a, 0x8c, 0x01, 0xec, 0xa6, +0xbd, 0xb3, 0xd4, 0x11, 0x20, 0x7a, 0x2b, 0x12, +0x3f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0x91, 0x19, 0x07, 0x20, 0x3f, +0x58, 0xea, 0x66, 0x03, 0x57, 0x13, 0x0d, 0x2f, +0x28, 0x93, 0x90, 0xeb, 0x3c, 0xeb, 0x83, 0xec, +0x91, 0xde, 0x0c, 0xf6, 0x33, 0x6d, 0x8c, 0x59, +0x3d, 0x1c, 0x42, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0xc5, 0x28, 0x47, +0xce, 0x4d, 0x45, 0x91, 0x39, 0xa4, 0x92, 0x9e, +0xf6, 0x16, 0x3f, 0x78, 0x07, 0xd5, 0x4b, 0x91, +0x6b, 0x44, 0xcf, 0xc1, 0xdf, 0x2b, 0x81, 0x93, +0x42, 0xf2, 0xb3, 0xc0, 0x1d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0x96, +0xcb, 0xc7, 0x5e, 0xf7, 0xba, 0xce, 0x3c, 0xde, +0x9f, 0xf1, 0xf5, 0x69, 0xbe, 0xa8, 0xda, 0x02, +0x6b, 0x65, 0x10, 0x49, 0xc6, 0x3a, 0xe0, 0xcb, +0xef, 0x8b, 0xcd, 0x7e, 0x98, 0xf4, 0xa3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0xef, 0x5f, 0xda, 0xd9, 0xd8, 0x8b, 0x80, +0xd6, 0xbf, 0xfb, 0x67, 0xa8, 0x04, 0x22, 0xa4, +0x69, 0xbe, 0x3a, 0x15, 0x07, 0x58, 0xfa, 0x82, +0x86, 0xc6, 0xec, 0x16, 0x12, 0x94, 0xc4, 0x22, +0xed, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0x4b, 0x48, 0xda, 0x4f, 0xd8, +0x7a, 0x84, 0xbe, 0x35, 0x57, 0x2a, 0x14, 0x56, +0x8c, 0x31, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x85, 0x54, 0xc4, 0x66, +0x5e, 0xcd, 0xee, 0x78, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0xac, 0x15, 0x26, +0xad, 0x95, 0x3a, 0x22, 0x60, 0x03, 0xb6, 0x8f, +0x18, 0x36, 0x52, 0x4e, 0xe9, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x09, +0x1f, 0x8c, 0x7d, 0x8c, 0xe1, 0x86, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0xbb, +0x27, 0xe1, 0xbd, 0x33, 0xf6, 0x16, 0x48, 0x29, +0xec, 0xc1, 0xae, 0x8c, 0x91, 0xa5, 0x5f, 0x1a, +0x39, 0x5e, 0x08, 0x1a, 0xab, 0x9e, 0xcb, 0x0d, +0x24, 0x2b, 0xf2, 0x60, 0xe8, 0x23, 0x7c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0x8d, 0x75, 0xb5, 0xe3, 0x96, 0x56, 0xe3, +0x81, 0x34, 0x9c, 0x04, 0x03, 0x58, 0x67, 0x4c, +0xc8, 0xf2, 0x44, 0x2a, 0x8d, 0x8f, 0xe1, 0xb6, +0x49, 0xdd, 0xa5, 0x13, 0x46, 0x7b, 0x95, 0x09, +0xe1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0xb1, 0xcb, 0x7d, 0x25, 0x1a, +0x9d, 0x86, 0xe2, 0x50, 0xf4, 0xe7, 0x8f, 0x52, +0x9a, 0x3e, 0xcb, 0x94, 0xfd, 0xda, 0x9f, 0xcc, +0x0d, 0x18, 0xd0, 0xdb, 0x53, 0x8e, 0x70, 0xde, +0x26, 0x94, 0x3c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0xc4, 0x6a, 0x6e, +0xd0, 0x18, 0xb2, 0xe0, 0xe5, 0xea, 0xc8, 0x00, +0xe8, 0xa4, 0x70, 0x55, 0xc9, 0x06, 0x17, 0xd2, +0xd7, 0xe3, 0x7c, 0xe3, 0x40, 0x4b, 0xa7, 0xbe, +0x38, 0x37, 0x47, 0x65, 0x9c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0x2b, +0x20, 0xde, 0x46, 0x6b, 0x0b, 0x7e, 0xd6, 0x20, +0xee, 0x2c, 0xec, 0x94, 0xa1, 0x50, 0xc4, 0x4f, +0x36, 0x99, 0x31, 0x2d, 0x09, 0x64, 0x85, 0x9c, +0xf9, 0xe5, 0x66, 0x70, 0xe3, 0x0f, 0x6e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0xb8, 0xed, 0x76, 0xc0, 0xea, 0x4f, 0xae, +0xc0, 0x28, 0x0e, 0x0b, 0xa9, 0x28, 0x84, 0x48, +0xee, 0x4f, 0xcc, 0xfe, 0x5d, 0x93, 0xac, 0x50, +0xc2, 0x2d, 0xac, 0xae, 0xdf, 0x84, 0x65, 0x9e, +0xfa, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x42, 0x2e, 0x43, 0x6e, 0xeb, +0x92, 0xa5, 0x6e, 0x41, 0x6c, 0x42, 0x2c, 0x6c, +0xe6, 0xd3, 0x13, 0x16, 0x59, 0x32, 0x76, 0xb4, +0x3d, 0xcc, 0x54, 0x9a, 0xcd, 0x2e, 0x7f, 0x20, +0x09, 0xed, 0xc7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0xa3, 0x5a, 0xfe, +0x46, 0x0e, 0x76, 0x2c, 0xf2, 0x5e, 0x56, 0xbf, +0x81, 0x74, 0xb1, 0xc3, 0x49, 0xe1, 0x67, 0xca, +0x60, 0xae, 0x53, 0x7b, 0xc2, 0xee, 0xd7, 0x99, +0xfb, 0x03, 0x6d, 0xc2, 0x90, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0x85, +0x69, 0x99, 0x4b, 0x0f, 0x16, 0x46, 0x28, 0x1c, +0x12, 0x3b, 0x3f, 0x95, 0x57, 0xf7, 0x3b, 0xd1, +0xce, 0x24, 0xb6, 0xe3, 0xba, 0x64, 0xd7, 0xe7, +0x0f, 0x8e, 0x14, 0x35, 0x4b, 0x2a, 0xc3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0xe8, 0x17, 0xeb, 0xae, 0x02, 0x6d, 0x0e, +0x23, 0x89, 0x36, 0x19, 0x06, 0xfb, 0xb7, 0x37, +0x1e, 0x05, 0x1c, 0xd1, 0x06, 0xa4, 0x6d, 0x42, +0xc8, 0x8f, 0x43, 0x47, 0x14, 0xcd, 0xaf, 0x4c, +0x2f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0xca, 0x78, 0xf8, 0x4e, 0x23, +0xda, 0x10, 0x60, 0x2a, 0xe5, 0xd6, 0x52, 0x88, +0x00, 0xfe, 0xf4, 0x86, 0x2c, 0xf1, 0x94, 0x52, +0x94, 0xc4, 0x17, 0xcc, 0xaf, 0x03, 0x7c, 0x89, +0xd6, 0x7b, 0xeb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0x05, 0xe3, 0xcd, +0x5e, 0xdf, 0x12, 0x55, 0x41, 0x49, 0x3c, 0x88, +0x8a, 0x70, 0x6b, 0xb5, 0x1b, 0x65, 0x54, 0x68, +0x0b, 0xe4, 0x6c, 0x56, 0xc0, 0xaf, 0x24, 0x8b, +0xfd, 0x2e, 0x81, 0xc6, 0x6c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0xa6, +0xa2, 0x52, 0xf5, 0x16, 0xb9, 0x8c, 0xfb, 0x8f, +0x15, 0xdc, 0x2e, 0xc2, 0xc0, 0x3c, 0x4f, 0x3c, +0x0e, 0x62, 0x06, 0x7a, 0xc6, 0x15, 0x78, 0xcd, +0x0a, 0xcb, 0x01, 0xae, 0x5f, 0x59, 0x8d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0xbb, 0xd1, 0x85, 0xa6, 0x6f, 0x9e, 0x29, +0x3e, 0x66, 0x5c, 0xe3, 0xad, 0x66, 0xca, 0x2a, +0x5e, 0xb6, 0x2f, 0x45, 0xc5, 0xbe, 0x5f, 0x90, +0xa7, 0xb0, 0xd7, 0x3a, 0x3c, 0x8b, 0xc8, 0x85, +0x62, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0xb3, 0x89, 0x18, 0xd4, 0x4f, +0xea, 0xec, 0x7a, 0x01, 0xec, 0xc4, 0xba, 0x36, +0xbd, 0x5a, 0x03, 0x27, 0x9c, 0x2c, 0x62, 0x5c, +0xd0, 0x78, 0x25, 0xb4, 0x22, 0x15, 0xc6, 0x37, +0x90, 0xa0, 0x8f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x4e, 0x96, 0xda, +0x70, 0xe5, 0xb0, 0x6b, 0x21, 0x03, 0xdd, 0xba, +0x3e, 0x4d, 0xa9, 0x10, 0xf2, 0xf4, 0x86, 0x9c, +0x15, 0x63, 0x12, 0x24, 0x3e, 0xa5, 0x00, 0x3a, +0xe2, 0xf8, 0x6a, 0x8c, 0x25, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x1f, +0xa5, 0x4d, 0xe4, 0x29, 0xd6, 0x5d, 0xb7, 0x16, +0xa0, 0xe9, 0xc8, 0xd7, 0x2a, 0xe8, 0xb2, 0x58, +0xe0, 0x28, 0xb0, 0x8c, 0xae, 0x6c, 0x2e, 0xc5, +0x83, 0xb9, 0x97, 0x70, 0xa2, 0x07, 0x40, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0x41, 0xaf, 0x66, 0x66, 0x58, 0x33, 0xbb, +0x13, 0x9d, 0x87, 0xdf, 0xf8, 0x5b, 0x6b, 0x6c, +0x5a, 0x57, 0x42, 0xca, 0x69, 0xad, 0xd2, 0xfb, +0xdb, 0x1a, 0xe8, 0xe6, 0xe2, 0xf9, 0x18, 0xbb, +0xfb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0x53, 0xbd, 0x63, 0x79, 0x13, +0xc5, 0x9b, 0x83, 0x7f, 0x8d, 0x69, 0xb7, 0xa4, +0x90, 0xd8, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xf9, 0x6b, 0x05, 0xd1, +0xfc, 0x7f, 0xde, 0x7a, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0xa8, 0x97, 0x94, +0xac, 0xae, 0x96, 0xda, 0xf6, 0xe2, 0x2d, 0xea, +0xb4, 0x4e, 0xf7, 0x33, 0xd6, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x47, +0x3d, 0xd2, 0x32, 0x35, 0x9b, 0xfa, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0xf9, +0xee, 0xb8, 0x76, 0x19, 0x5c, 0x17, 0x78, 0x7a, +0xc5, 0x55, 0xb9, 0xa9, 0xdd, 0xca, 0x32, 0x58, +0x93, 0x51, 0x92, 0x30, 0x16, 0xcb, 0x2e, 0xc8, +0xe0, 0x97, 0x8d, 0xe4, 0x19, 0xb8, 0xc4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0x84, 0xa8, 0xe3, 0x30, 0x19, 0x23, 0x7a, +0xf6, 0x49, 0x33, 0x43, 0xdf, 0x42, 0x9b, 0x14, +0x0d, 0xb0, 0x13, 0xb7, 0xa5, 0x89, 0x7b, 0x0f, +0xeb, 0xcf, 0xbb, 0x44, 0x5b, 0x29, 0xed, 0x19, +0x54, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0x0f, 0xae, 0x3f, 0x18, 0xfb, +0x5b, 0xc7, 0xd7, 0x14, 0x78, 0x8a, 0x8d, 0x43, +0x72, 0x15, 0x52, 0x1c, 0x9c, 0x21, 0xf9, 0x1e, +0x0e, 0x18, 0x08, 0x81, 0x3f, 0x2b, 0x51, 0x37, +0xa2, 0x5c, 0x8a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0xa8, 0x1f, 0x4f, +0x85, 0xa8, 0xb9, 0x2e, 0x1f, 0x11, 0x94, 0xf4, +0x69, 0xe0, 0x34, 0xc3, 0xed, 0x38, 0x33, 0x08, +0xed, 0xef, 0x04, 0x8e, 0xe4, 0x69, 0xe0, 0xbe, +0x7c, 0x8a, 0x2d, 0x0d, 0x15, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0x6e, +0x69, 0xbe, 0x54, 0xe0, 0xc4, 0xb0, 0xe0, 0x82, +0xfa, 0x2c, 0x98, 0x08, 0x55, 0x6a, 0x4a, 0xbe, +0xaf, 0x5c, 0x1b, 0xd6, 0xf5, 0xab, 0xdf, 0xb7, +0x93, 0x6e, 0x4e, 0x05, 0xac, 0x5d, 0xe9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0xca, 0x67, 0x49, 0x8d, 0x08, 0xb0, 0xfa, +0x70, 0xac, 0x16, 0xb1, 0xdf, 0x3e, 0xfe, 0xdf, +0x45, 0xb6, 0xfb, 0x6f, 0x9a, 0x63, 0x24, 0x10, +0xd2, 0x18, 0xaa, 0xb4, 0x5e, 0x3d, 0x71, 0x50, +0x6c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0x5b, 0x10, 0x2a, 0x0a, 0x5d, +0x7b, 0xf7, 0x51, 0xbf, 0x6c, 0xd5, 0x65, 0x34, +0x7f, 0x3b, 0x52, 0x22, 0x65, 0x0c, 0xaf, 0x08, +0x79, 0x24, 0xa1, 0x93, 0xb7, 0x72, 0x9e, 0x23, +0x9a, 0x56, 0x45, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x91, 0x16, 0x6b, +0x15, 0xd6, 0xa2, 0xf3, 0xa5, 0x6d, 0x47, 0x24, +0x3c, 0x68, 0x3a, 0x7f, 0x3d, 0x04, 0x6c, 0x2e, +0x25, 0x15, 0x9a, 0x54, 0xce, 0x7b, 0x2e, 0xdc, +0x3b, 0x5c, 0x0e, 0xd3, 0x9b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0xb5, +0x54, 0x2b, 0x7b, 0x3b, 0xc2, 0x33, 0xde, 0xd0, +0x18, 0xff, 0x0a, 0x76, 0x54, 0x2b, 0xae, 0xc9, +0x8e, 0xdc, 0x21, 0x7c, 0xa3, 0x35, 0xde, 0x6b, +0x9c, 0x4f, 0x9c, 0xd9, 0x72, 0x79, 0xcd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0xc0, 0x72, 0x43, 0x69, 0x71, 0xe9, 0xd0, +0x45, 0xbc, 0xf4, 0x96, 0x09, 0xc6, 0x58, 0xd7, +0x09, 0x02, 0x13, 0xc0, 0xc2, 0x40, 0x30, 0x48, +0xfb, 0xc6, 0x5a, 0xd9, 0xe8, 0x0d, 0xc6, 0x41, +0x37, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0x39, 0x46, 0x07, 0x55, 0x36, +0x72, 0x89, 0xac, 0xe5, 0xb6, 0x71, 0xb3, 0x9c, +0xd4, 0xdf, 0x3a, 0xd4, 0x06, 0x53, 0xe7, 0x82, +0x73, 0x62, 0x4b, 0xb5, 0xf1, 0x5c, 0x6f, 0xf1, +0x9d, 0xda, 0xfb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0xe4, 0x97, 0x4d, +0xe3, 0x6b, 0x32, 0x1e, 0xb4, 0x8a, 0xd6, 0x21, +0x5b, 0xe3, 0xf5, 0xc1, 0x77, 0xfc, 0x1f, 0xdb, +0x6e, 0x42, 0x48, 0xcb, 0x90, 0xd0, 0x64, 0xbe, +0xf3, 0x8e, 0x80, 0x07, 0x12, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x39, +0xb7, 0x82, 0x9a, 0x2d, 0x97, 0x11, 0xef, 0x69, +0xaa, 0x1a, 0xb4, 0x30, 0x33, 0x2b, 0xbd, 0x94, +0xcc, 0xb2, 0xfb, 0xc7, 0xdd, 0x46, 0x5e, 0x56, +0xbd, 0xaf, 0xd0, 0x1d, 0xd2, 0x60, 0xb4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0x2b, 0xe0, 0x6c, 0x3b, 0x44, 0xda, 0x97, +0x94, 0xc0, 0x09, 0x6a, 0xf3, 0x48, 0x36, 0x60, +0xea, 0x3f, 0x64, 0xe3, 0x68, 0x72, 0x3a, 0x96, +0x50, 0xab, 0xe1, 0x37, 0x66, 0xac, 0x31, 0x42, +0xfa, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0xe3, 0xd4, 0xb2, 0x48, 0x76, +0xe2, 0xdf, 0xf6, 0x5f, 0x9c, 0x4f, 0x6e, 0x2f, +0xf9, 0x15, 0x77, 0xe4, 0xd0, 0x5d, 0x38, 0xd0, +0x71, 0xb1, 0xd7, 0x1b, 0xe1, 0x52, 0x2b, 0x4e, +0xa8, 0xd3, 0xdf, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0xb1, 0x94, 0xe4, +0xfa, 0x5a, 0x42, 0x61, 0x7f, 0xe8, 0x79, 0xc9, +0x70, 0x41, 0xbb, 0xfd, 0xcb, 0xc4, 0x26, 0x3d, +0xd2, 0xc5, 0xcd, 0xf5, 0x57, 0x19, 0xa3, 0xb8, +0x75, 0xd7, 0x79, 0x94, 0xc1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0xbf, +0x22, 0xc8, 0x90, 0xa3, 0x57, 0x67, 0xdb, 0xc2, +0xcc, 0x45, 0x81, 0xd4, 0x09, 0x3d, 0x8f, 0x80, +0xb1, 0xa5, 0x0c, 0x47, 0xaf, 0x2c, 0xe8, 0x16, +0xde, 0xe2, 0xbc, 0x4d, 0xf0, 0xc7, 0xfc, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0x91, 0xe8, 0x5b, 0xf7, 0xa0, 0x50, 0x52, +0xcb, 0x22, 0xb1, 0x30, 0x81, 0xd1, 0xcf, 0x82, +0xf5, 0x60, 0x27, 0x3c, 0x9c, 0x88, 0x01, 0xf0, +0xc6, 0xb1, 0x2f, 0x73, 0xe0, 0xe2, 0xa0, 0x7f, +0x90, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0x09, 0x1b, 0x6d, 0x8a, 0xc0, +0xfb, 0x4d, 0x59, 0x2e, 0x4f, 0x47, 0xf7, 0x8f, +0xb9, 0xdd, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xbb, 0xb1, 0xe9, 0xbe, +0x7f, 0xf1, 0x4a, 0x74, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0x81, 0xb5, 0x52, +0x8f, 0x00, 0x81, 0xa2, 0x30, 0x8e, 0x60, 0xde, +0x26, 0x35, 0x54, 0xb3, 0xa8, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe7, 0x95, +0xda, 0xbe, 0xa4, 0xb1, 0xcf, 0x69, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x69, +0x11, 0x06, 0x75, 0x88, 0x9a, 0xc6, 0x07, 0x9f, +0x33, 0xbb, 0xff, 0x86, 0xb0, 0x3a, 0xb7, 0x9b, +0xe8, 0x8c, 0x7e, 0xe9, 0x7d, 0x28, 0x4b, 0xd5, +0xfd, 0x66, 0x3c, 0x88, 0xf3, 0xb2, 0x7b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0x83, 0x0f, 0x7a, 0xae, 0xd1, 0x11, 0x1a, +0xd5, 0xb5, 0x01, 0x56, 0xbb, 0x28, 0x0c, 0x0c, +0x81, 0x2a, 0x86, 0x13, 0x60, 0xd7, 0x6a, 0xbe, +0xb4, 0xbf, 0x73, 0x8e, 0xe5, 0xe6, 0x26, 0x94, +0x6a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0xe9, 0x87, 0x1c, 0xde, 0xab, +0x50, 0xe7, 0x38, 0x31, 0xf4, 0x43, 0x4d, 0x3a, +0xbc, 0x0a, 0x1c, 0x78, 0xe6, 0xca, 0x78, 0x91, +0x57, 0x3d, 0x20, 0x22, 0xec, 0x69, 0x7b, 0x95, +0x90, 0x59, 0xc1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0x2a, 0xab, 0x16, +0x2a, 0xf3, 0xa4, 0xb1, 0x8e, 0xc4, 0x9d, 0xfb, +0x92, 0x1d, 0x41, 0x72, 0xe3, 0xeb, 0x6b, 0xa9, +0xa6, 0x53, 0xcb, 0xd9, 0x5a, 0x36, 0xdb, 0x85, +0x2b, 0x9e, 0x76, 0x39, 0x61, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0x40, +0xe0, 0x3f, 0xb1, 0x7b, 0x75, 0x6b, 0xab, 0x2f, +0x75, 0x42, 0x72, 0x23, 0x2c, 0x5b, 0xd5, 0x3c, +0x96, 0x08, 0xac, 0xd0, 0x74, 0x8f, 0xba, 0x5b, +0xbb, 0x63, 0xe1, 0xfb, 0x12, 0x63, 0x30, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0xeb, 0x63, 0x2d, 0x36, 0x41, 0x0b, 0xb8, +0x1a, 0x35, 0x46, 0x7a, 0xe0, 0xac, 0x31, 0xe5, +0x93, 0x73, 0x09, 0x19, 0x9c, 0x0b, 0x0c, 0xaf, +0x87, 0x31, 0xb8, 0x06, 0x6d, 0x8c, 0xc0, 0x42, +0xd4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x19, 0xe1, 0x90, 0x96, 0x65, +0x9d, 0xc6, 0xa4, 0x84, 0x90, 0xda, 0xc9, 0x91, +0x1e, 0xb8, 0xbe, 0x38, 0x87, 0xb2, 0x2b, 0x1b, +0xd6, 0xe0, 0x7d, 0xd7, 0x0a, 0x2a, 0x93, 0x47, +0x37, 0xd8, 0x16, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0xe2, 0x78, 0x19, +0x8d, 0x0a, 0xf6, 0x48, 0x15, 0x8a, 0x09, 0x9f, +0xed, 0x71, 0x10, 0x09, 0x56, 0xa6, 0xa9, 0xac, +0x54, 0xe7, 0x88, 0xe4, 0x25, 0x60, 0xd5, 0x36, +0x70, 0x3c, 0xda, 0xb3, 0x9b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0x43, +0x59, 0x77, 0x98, 0x7c, 0x2a, 0x55, 0xeb, 0x70, +0x77, 0x6d, 0x80, 0x4d, 0xa1, 0xc8, 0x57, 0x0b, +0xa3, 0xaf, 0xcf, 0x79, 0xd4, 0xb9, 0x75, 0x18, +0xd7, 0xc7, 0xd2, 0x19, 0xb9, 0xc5, 0x75, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0xa1, 0x0c, 0x3b, 0x35, 0x5a, 0x65, 0x3e, +0xab, 0x71, 0xc1, 0x4c, 0xed, 0x30, 0xb3, 0x04, +0x7f, 0x47, 0xd2, 0xe6, 0xe2, 0x23, 0xa1, 0x74, +0x27, 0x24, 0xfa, 0xfe, 0xd1, 0x76, 0x16, 0x2e, +0x05, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0x47, 0x4b, 0x72, 0x85, 0xb4, +0xd0, 0xdb, 0x8d, 0x3e, 0xf4, 0xa2, 0x9f, 0x14, +0x3e, 0x39, 0x2b, 0x6f, 0x48, 0x9c, 0x16, 0xb1, +0x4c, 0xb8, 0xd7, 0xdb, 0xb3, 0x87, 0x9f, 0x3e, +0xe3, 0x8a, 0x3f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0x61, 0x97, 0xd8, +0x1e, 0x57, 0xdb, 0x25, 0x0b, 0xa4, 0x1d, 0xb3, +0x7a, 0xde, 0xcd, 0xcc, 0x61, 0xe0, 0x10, 0x35, +0xe3, 0x7e, 0x09, 0x56, 0x84, 0xeb, 0xb3, 0xb7, +0x31, 0xb1, 0xad, 0x3a, 0x16, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0xdd, +0x70, 0xbb, 0x6b, 0xcc, 0xb0, 0x35, 0xfb, 0x63, +0xa2, 0x9d, 0xf6, 0xb9, 0xf6, 0xf9, 0x5d, 0x4a, +0x6c, 0xa1, 0xdc, 0xc6, 0x96, 0x33, 0xfe, 0x22, +0x7a, 0xd8, 0x86, 0x29, 0xfb, 0xea, 0x43, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0xd0, 0x58, 0xf5, 0x44, 0xe4, 0x68, 0xe3, +0xac, 0xad, 0x78, 0x2f, 0xfc, 0x7b, 0xf9, 0xfb, +0xb1, 0x5f, 0x3e, 0x3d, 0xde, 0x93, 0x64, 0x38, +0x9e, 0x08, 0x30, 0x1f, 0xb6, 0xdd, 0xc2, 0x48, +0x85, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0xb7, 0xc9, 0x5d, 0x92, 0x7d, +0x77, 0x4f, 0xb3, 0x17, 0x39, 0x44, 0x79, 0x9e, +0x9d, 0x28, 0xb6, 0x57, 0x29, 0xf1, 0xbb, 0x5f, +0x47, 0xd1, 0xe8, 0xe9, 0x3a, 0x3f, 0xf8, 0x3b, +0x19, 0x95, 0x9a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0x1a, 0x09, 0x03, +0x5f, 0x1a, 0x13, 0x0c, 0x62, 0x20, 0x52, 0x2f, +0x08, 0xbc, 0xfb, 0xc4, 0x12, 0x53, 0xe0, 0xf6, +0xda, 0xda, 0x06, 0xb1, 0xf9, 0x2b, 0x14, 0xc4, +0xb6, 0x5c, 0x25, 0x68, 0x0d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0xb7, +0xc9, 0x7b, 0xd9, 0x3a, 0x5e, 0x8d, 0x87, 0x3e, +0x34, 0x32, 0x1b, 0xd1, 0x41, 0x04, 0x24, 0x62, +0xe7, 0x5d, 0x8d, 0xa6, 0x0d, 0xe4, 0x2d, 0x5e, +0xa4, 0xa1, 0x55, 0xc1, 0xd1, 0xa9, 0xdb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0x65, 0xfc, 0xdc, 0xa0, 0x3e, 0x26, 0x8c, +0xd9, 0x84, 0x3d, 0x34, 0x65, 0x48, 0x63, 0x36, +0xf1, 0x8d, 0xd5, 0x33, 0x21, 0x49, 0x94, 0x70, +0x2b, 0x69, 0x2e, 0x9a, 0xda, 0xe8, 0x82, 0xb5, +0xaf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0x39, 0xee, 0xa1, 0x95, 0x42, +0xbb, 0xf3, 0xd6, 0x39, 0x79, 0x07, 0xeb, 0x42, +0x10, 0xc2, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x04, 0x24, 0x20, 0xc5, +0x16, 0x35, 0x3e, 0x39, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0x57, 0x69, 0x57, +0x4d, 0x6d, 0xa7, 0x94, 0x31, 0x7c, 0x69, 0xad, +0x62, 0xb4, 0x43, 0x0f, 0x05, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x2c, +0xab, 0xeb, 0x5c, 0x25, 0x05, 0xa8, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0x92, +0x46, 0x94, 0x20, 0x6a, 0xef, 0xdc, 0xac, 0xd9, +0x18, 0x18, 0xe4, 0xa6, 0x2a, 0x0a, 0xac, 0xd7, +0x66, 0x86, 0x9c, 0xbd, 0x52, 0x96, 0x66, 0xff, +0x23, 0xa6, 0x1c, 0x8f, 0xc5, 0x95, 0x10, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0x07, 0xd7, 0x6b, 0xf0, 0x36, 0xfa, 0xf6, +0x2c, 0x12, 0x0f, 0xe4, 0x1c, 0xf1, 0x72, 0x5b, +0x4a, 0x62, 0xd7, 0x95, 0xd0, 0x64, 0x1b, 0xea, +0x9f, 0x39, 0x70, 0xad, 0x14, 0x12, 0x91, 0xff, +0xa3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0xb1, 0x4b, 0xb2, 0x33, 0x3e, +0xa7, 0xe7, 0xf8, 0xb0, 0x7e, 0x70, 0xa4, 0xbf, +0xf7, 0x69, 0x5b, 0xa5, 0x91, 0x55, 0x2d, 0x77, +0x70, 0xcf, 0x7c, 0xa1, 0x78, 0xef, 0x43, 0xa7, +0x84, 0x98, 0x34, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0xf4, 0x20, 0x2f, +0x89, 0x2b, 0x4a, 0xeb, 0x62, 0xd2, 0x02, 0x3e, +0x9d, 0x0b, 0xdb, 0xa2, 0x1f, 0x92, 0x11, 0x38, +0x37, 0x19, 0x40, 0xb0, 0x40, 0xfe, 0x88, 0xe6, +0xb6, 0x9f, 0xd8, 0x57, 0x94, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0x04, +0x3b, 0x01, 0xe2, 0x0c, 0x64, 0xad, 0x93, 0x26, +0x44, 0x35, 0x7c, 0xaf, 0xee, 0x9d, 0x53, 0xa0, +0x39, 0xf7, 0x63, 0xd6, 0x67, 0x37, 0x4e, 0x57, +0x6b, 0x8e, 0x7e, 0x74, 0xac, 0xc0, 0x12, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0xf6, 0xb0, 0x01, 0x52, 0x1e, 0xe9, 0xa9, +0x3a, 0x5b, 0xdf, 0xec, 0x52, 0x5d, 0xbe, 0xec, +0x47, 0xa2, 0x00, 0x30, 0x41, 0xbc, 0x2f, 0x3c, +0x3f, 0x99, 0xb1, 0xd4, 0xc9, 0x2c, 0xa1, 0xce, +0x78, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0xbd, 0xd0, 0x8c, 0xf1, 0xed, +0x97, 0xdf, 0x3e, 0xe6, 0x60, 0x08, 0x09, 0x51, +0x12, 0x57, 0x7a, 0x70, 0xb3, 0x3c, 0xfa, 0x25, +0xa1, 0x48, 0x39, 0x8e, 0x7b, 0x32, 0xe2, 0xad, +0x1d, 0x21, 0x9c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0xbd, 0xbe, 0x68, +0x9f, 0x11, 0xf0, 0xb8, 0xbd, 0x79, 0x83, 0xe9, +0x98, 0x39, 0x01, 0x95, 0x42, 0xa5, 0x88, 0xf8, +0x9f, 0x23, 0x25, 0x15, 0x01, 0xf2, 0x92, 0xe1, +0xdf, 0x45, 0x13, 0xff, 0xf9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0xc2, +0x61, 0x4c, 0x06, 0x1a, 0x06, 0x3d, 0xdc, 0x17, +0x47, 0x57, 0x8f, 0x88, 0x5a, 0xe7, 0x6d, 0x9d, +0xb1, 0x14, 0xb2, 0xa7, 0x0d, 0xb0, 0x1e, 0x9c, +0x0e, 0x59, 0x5a, 0xc9, 0x1e, 0xfb, 0x3e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0xb7, 0x6a, 0x32, 0x72, 0x31, 0xe6, 0xe0, +0x6b, 0xb5, 0x92, 0x10, 0x90, 0x01, 0x75, 0x53, +0x06, 0x8e, 0x95, 0xf7, 0x19, 0xa8, 0x18, 0xb9, +0xf4, 0x9f, 0x6a, 0x33, 0x08, 0x94, 0x9f, 0x2f, +0x4f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0xbb, 0x55, 0x0d, 0x5d, 0x62, +0xe2, 0x7c, 0x29, 0x3d, 0xb9, 0xd3, 0xa7, 0xc6, +0x21, 0x6b, 0x44, 0xe7, 0xf0, 0x48, 0x41, 0x9d, +0x98, 0x87, 0x64, 0xf3, 0xef, 0x73, 0x2e, 0x66, +0xbe, 0xf9, 0x32, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0x25, 0x6c, 0x1d, +0x26, 0x90, 0x83, 0x24, 0xbd, 0x0b, 0x12, 0x9d, +0x8a, 0x56, 0x24, 0xea, 0x36, 0x82, 0xba, 0x5b, +0xc8, 0x2e, 0xbd, 0x4e, 0xd8, 0x3a, 0x87, 0xd4, +0xac, 0x98, 0x0e, 0x99, 0x03, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0xc7, +0xed, 0xd8, 0xc5, 0x6c, 0x6f, 0x47, 0x96, 0x0f, +0x39, 0xa9, 0x04, 0x66, 0x0e, 0x80, 0xfa, 0xf9, +0xbe, 0x14, 0xfd, 0x1b, 0xf9, 0x74, 0xc0, 0x6d, +0xae, 0xf9, 0x34, 0xe8, 0x23, 0x8a, 0x4d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0xce, 0xe6, 0xd9, 0xed, 0xc3, 0x45, 0xe9, +0xc3, 0x72, 0xd3, 0xd4, 0xdc, 0x11, 0x36, 0x58, +0xc6, 0x56, 0xeb, 0xd1, 0x38, 0xee, 0xe7, 0x08, +0xa9, 0xdc, 0xbf, 0x36, 0x10, 0xa8, 0xc7, 0xe7, +0x97, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0x7d, 0x8f, 0x16, 0x0f, 0x71, +0x8f, 0xc9, 0x43, 0xc7, 0xa3, 0xff, 0xb0, 0x25, +0x47, 0xca, 0x61, 0xe7, 0xce, 0x33, 0x62, 0xa8, +0x57, 0x42, 0x37, 0x35, 0x50, 0xac, 0x77, 0xb3, +0xa9, 0x42, 0xf0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0xf3, 0xde, 0x48, +0x42, 0xb3, 0x52, 0x7b, 0x27, 0xb8, 0x21, 0x97, +0x1d, 0xe3, 0xda, 0xe8, 0x1c, 0xab, 0x79, 0x15, +0xbe, 0x45, 0xb9, 0x0e, 0x07, 0xe9, 0x38, 0x19, +0x3f, 0xca, 0x97, 0x19, 0x5c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0x38, +0x29, 0x9b, 0x20, 0x86, 0xdb, 0x73, 0xc4, 0x93, +0x1f, 0xa6, 0x2a, 0xa5, 0x1c, 0xad, 0x1b, 0xdf, +0xd2, 0x67, 0xc5, 0x03, 0xec, 0xb5, 0x18, 0x90, +0x81, 0x32, 0x25, 0x7d, 0xe2, 0xa6, 0x2f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x83, 0x58, 0x3d, 0x18, 0x7a, 0x7b, 0x5b, +0x08, 0xd1, 0x76, 0x94, 0xf8, 0x53, 0x96, 0xd7, +0x10, 0xb0, 0xe6, 0x8d, 0xf2, 0x41, 0xfa, 0x25, +0x56, 0x32, 0xbe, 0xca, 0xbd, 0x3b, 0xde, 0x56, +0x48, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0xa6, 0x7b, 0x3b, 0xed, 0x62, +0x10, 0x2c, 0x05, 0x08, 0xf9, 0x92, 0x39, 0x65, +0xe3, 0x98, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x2d, 0xea, 0xa8, 0x19, +0x9c, 0x1e, 0xad, 0x38, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x4a, 0x9a, 0xad, +0x99, 0xb1, 0x2e, 0x51, 0x96, 0x75, 0x22, 0x53, +0x55, 0xed, 0x67, 0xde, 0xfc, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xba, 0x53, +0x1a, 0x58, 0x81, 0xfc, 0x67, 0xc7, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0x57, +0x9b, 0x82, 0x4c, 0xbe, 0x2c, 0xd0, 0x5e, 0x90, +0xc7, 0xdb, 0x70, 0xb3, 0x2b, 0xe2, 0xd2, 0x3b, +0x4c, 0x74, 0x97, 0x1b, 0xf8, 0x8e, 0x66, 0x82, +0x1a, 0x1c, 0xa9, 0x1c, 0xbd, 0xa9, 0x5b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0x73, 0x2c, 0x5b, 0xda, 0x0f, 0xc1, 0x9b, +0x27, 0x76, 0xc2, 0x1a, 0x8a, 0xb8, 0x2d, 0x20, +0x09, 0x4d, 0x4e, 0x57, 0xbf, 0x90, 0x5e, 0x4b, +0x50, 0xc3, 0x93, 0xf2, 0xd9, 0x18, 0x0a, 0x0a, +0x21, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0xbc, 0xd1, 0x9c, 0xb7, 0x80, +0xad, 0xf3, 0xe4, 0x71, 0x27, 0x74, 0x92, 0x71, +0x3d, 0xe6, 0xb8, 0xba, 0xb3, 0x1f, 0xae, 0xc7, +0xbc, 0x11, 0xd5, 0x01, 0xce, 0x05, 0x8a, 0x2f, +0xd8, 0x2d, 0xf8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0xa5, 0x17, 0x4f, +0x54, 0xd0, 0x18, 0xa0, 0x71, 0x4f, 0x9d, 0x58, +0xc2, 0xb5, 0xd5, 0xc8, 0xae, 0xa4, 0xb5, 0xb9, +0x90, 0x20, 0xfe, 0x19, 0x4f, 0xd8, 0xed, 0xc8, +0x25, 0xb4, 0x2d, 0x20, 0xa7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0xab, +0x63, 0x61, 0xf2, 0x76, 0x8c, 0x01, 0x9a, 0x00, +0x83, 0x8f, 0xcd, 0xfc, 0x96, 0x23, 0xa7, 0xb1, +0x56, 0x92, 0xb9, 0x2f, 0x07, 0x5f, 0x4f, 0x7a, +0x7d, 0x27, 0xd6, 0x35, 0x44, 0x29, 0xb8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0x7c, 0x6a, 0x4b, 0x83, 0xbd, 0xf3, 0xcf, +0x00, 0xdd, 0x2a, 0xfc, 0x62, 0xf4, 0xaf, 0x8d, +0xde, 0x12, 0x94, 0x7c, 0xfc, 0x97, 0x45, 0x84, +0x03, 0xec, 0xb8, 0x85, 0xdc, 0x44, 0xb1, 0xac, +0x28, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0xf1, 0x75, 0xea, 0xe6, 0x18, +0x92, 0x8b, 0x59, 0x5c, 0xb1, 0x16, 0x7a, 0x6c, +0x2e, 0xb0, 0x15, 0xec, 0xcd, 0x4e, 0x1a, 0x3e, +0x9b, 0x43, 0x72, 0xe4, 0xfe, 0xfa, 0x42, 0x88, +0x7a, 0xa2, 0xaa, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0xa5, 0x5b, 0x0c, +0x89, 0xe3, 0xf2, 0x0a, 0xea, 0xc6, 0x82, 0xdb, +0x39, 0xc1, 0x1f, 0xa8, 0x6f, 0x40, 0xa0, 0xee, +0xd4, 0xe1, 0xb2, 0xc2, 0xe4, 0xec, 0x73, 0xa2, +0x41, 0xf2, 0x4d, 0x8a, 0xea, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0xf9, +0x09, 0xce, 0xea, 0xbb, 0x03, 0xe7, 0xb0, 0xe8, +0x5e, 0x8d, 0x54, 0x2a, 0x62, 0xba, 0x3c, 0xae, +0xd5, 0xfc, 0x59, 0x7f, 0xdc, 0x50, 0xed, 0x19, +0xfb, 0x9a, 0x2e, 0x24, 0x79, 0x6d, 0x5b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0x68, 0xc7, 0x16, 0x54, 0x93, 0x13, 0x15, +0x90, 0x03, 0x07, 0x01, 0x1c, 0xc7, 0xbf, 0x3d, +0x97, 0x9b, 0x36, 0x03, 0xb6, 0xb5, 0xd1, 0x6c, +0xe4, 0x78, 0xe4, 0x6c, 0x15, 0xe2, 0xee, 0x82, +0x7f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0x77, 0x41, 0xe7, 0x7b, 0xa1, +0xc5, 0xca, 0xae, 0x94, 0x70, 0xa4, 0x93, 0x8d, +0xf9, 0xc4, 0xdd, 0xf6, 0x2d, 0x59, 0xb0, 0x87, +0x52, 0x27, 0x2d, 0x4b, 0x8f, 0x36, 0x1d, 0x80, +0x55, 0xf4, 0x5b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0xcb, 0x24, 0xec, +0xd4, 0x97, 0x6a, 0x92, 0xd0, 0xcb, 0xd4, 0x12, +0x54, 0x71, 0xc2, 0xf8, 0xb4, 0x0f, 0x3f, 0x40, +0xa1, 0x81, 0xfe, 0x24, 0xee, 0xbd, 0xc3, 0xd6, +0x3e, 0x41, 0xae, 0xbf, 0xc8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x27, +0xe8, 0x5d, 0xb4, 0x4d, 0x52, 0x95, 0x13, 0x2b, +0xa1, 0x89, 0x14, 0x6d, 0x25, 0x80, 0x56, 0x08, +0xcc, 0xd8, 0xac, 0x6a, 0xb6, 0xf2, 0x2c, 0x50, +0x9d, 0xd2, 0x5a, 0x02, 0x28, 0x12, 0x7d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0xe2, 0xc1, 0x1e, 0x4b, 0xfa, 0x9b, 0x78, +0x63, 0xc7, 0xdb, 0x45, 0x07, 0x28, 0xca, 0xc7, +0x40, 0x5b, 0xcb, 0xf7, 0x5b, 0x36, 0xec, 0x3e, +0x81, 0xb4, 0x34, 0x6c, 0xef, 0x03, 0xcf, 0xe0, +0x63, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0xaf, 0xb8, 0xb9, 0xed, 0xfb, +0x64, 0xa8, 0xc9, 0x02, 0x8d, 0x15, 0x4a, 0x38, +0x4c, 0x8a, 0xa5, 0xe8, 0xa2, 0x14, 0x61, 0x4d, +0xd3, 0x1d, 0xf1, 0x7e, 0x64, 0x8b, 0xa2, 0xcc, +0x0f, 0xdb, 0x88, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0xf2, 0x88, 0x05, +0xda, 0x8e, 0x68, 0x41, 0xe4, 0x96, 0xd7, 0xcc, +0xf6, 0x73, 0x53, 0xe8, 0x5e, 0x2b, 0x2c, 0xa3, +0xdc, 0x4c, 0x26, 0xad, 0xf2, 0xdd, 0xb7, 0x5f, +0xea, 0x9c, 0x85, 0x42, 0xee, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0x27, +0x10, 0x13, 0xcd, 0x63, 0x20, 0xc2, 0x1f, 0x09, +0xb8, 0x42, 0x49, 0x46, 0x83, 0x2c, 0xe6, 0xac, +0x56, 0x3a, 0xc7, 0xf5, 0x32, 0x4e, 0x37, 0x41, +0xb6, 0x38, 0xf6, 0x10, 0xef, 0xf3, 0x51, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x3c, 0x1c, 0x6f, 0x8c, 0x1c, 0xa8, 0x45, +0xf5, 0x6a, 0xfb, 0xd1, 0x6b, 0x50, 0xee, 0x6d, +0x74, 0x31, 0x7e, 0x40, 0x60, 0x80, 0xfd, 0xd7, +0x1b, 0x9f, 0x09, 0xa3, 0x8f, 0x90, 0x7e, 0x4c, +0x29, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0xc8, 0x7c, 0x14, 0x88, 0x63, +0x7e, 0x82, 0x77, 0xb4, 0x0c, 0x3e, 0x29, 0x8b, +0x69, 0xac, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xb5, 0x85, 0x46, 0xe7, +0x2d, 0x2e, 0xd5, 0xc3, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0x1e, 0x21, 0xa6, +0x6a, 0x1c, 0x6c, 0x7c, 0x04, 0x0c, 0xb3, 0x2b, +0xf9, 0x20, 0x12, 0x24, 0xbe, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x17, +0x37, 0xd2, 0xf4, 0x87, 0x5b, 0x02, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0x58, +0x01, 0x4b, 0x9f, 0xc9, 0x89, 0x06, 0x12, 0x33, +0x9f, 0x94, 0xd6, 0x26, 0x44, 0x01, 0xf6, 0xf1, +0xe1, 0xce, 0x92, 0x87, 0x4d, 0x14, 0xbb, 0xca, +0x02, 0xfe, 0xca, 0x52, 0xde, 0xad, 0x7e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0x6c, 0x4d, 0x90, 0xbd, 0x3b, 0x76, 0xde, +0x6f, 0x78, 0x64, 0xce, 0xde, 0x13, 0xc3, 0xd0, +0x17, 0x6c, 0x95, 0xfa, 0xca, 0x7b, 0x3a, 0x11, +0x9e, 0xb3, 0x19, 0xa9, 0x7b, 0xf5, 0xc0, 0x2d, +0x9f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0x61, 0x66, 0x41, 0x28, 0xbd, +0xb6, 0x67, 0x22, 0x37, 0xd9, 0x73, 0x3f, 0xe9, +0x38, 0x03, 0x97, 0x47, 0x60, 0x3b, 0x3c, 0x3c, +0x58, 0x50, 0xa8, 0xc4, 0x4f, 0x26, 0x69, 0xfb, +0x7e, 0x0d, 0x3d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0xf8, 0x40, 0x94, +0xe9, 0x67, 0xc5, 0x24, 0x62, 0xf3, 0xeb, 0x63, +0x3e, 0x11, 0xca, 0xf3, 0x59, 0x3d, 0xfd, 0x0a, +0xad, 0x58, 0x45, 0x4a, 0x8f, 0xcf, 0x36, 0x7f, +0x27, 0x09, 0xb3, 0x40, 0xb3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0x75, +0x63, 0xc0, 0x8c, 0xfb, 0xc5, 0xfe, 0x9c, 0xa2, +0xea, 0x12, 0x7a, 0x9d, 0x6f, 0x60, 0x69, 0x50, +0x54, 0x7c, 0x3c, 0x60, 0x1a, 0x34, 0xd5, 0x00, +0xfa, 0x59, 0x70, 0x5d, 0xda, 0xb6, 0x14, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x3a, 0xb8, 0xdd, 0xa6, 0xb6, 0xe7, 0x60, +0x02, 0x60, 0x4b, 0x94, 0x53, 0x2f, 0xae, 0x7e, +0x43, 0x84, 0x52, 0x75, 0x23, 0xf8, 0x14, 0x5e, +0xf8, 0x47, 0x2e, 0x41, 0xc5, 0x23, 0xb7, 0x2d, +0x4c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0x08, 0x69, 0x34, 0x4e, 0x10, +0x2c, 0x0f, 0x12, 0x81, 0x33, 0xc2, 0xa9, 0x38, +0xca, 0xcf, 0x2e, 0x01, 0x8f, 0xac, 0xd3, 0xe7, +0x72, 0xc3, 0x68, 0x15, 0xbb, 0x12, 0x3e, 0x93, +0xe5, 0xa1, 0x20, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0x19, 0xa6, 0x5c, +0x66, 0x83, 0x44, 0x56, 0x96, 0xcb, 0x4b, 0x14, +0x80, 0x02, 0x72, 0x75, 0xa2, 0xb8, 0x9d, 0x4c, +0x28, 0xb2, 0xa5, 0xfa, 0xc9, 0x8a, 0x43, 0x4c, +0x43, 0x8f, 0xa9, 0x99, 0x72, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0xfe, +0x76, 0x94, 0x35, 0x48, 0xaa, 0x5f, 0x70, 0x84, +0xba, 0x1b, 0x0b, 0x90, 0xb5, 0x5a, 0xcb, 0x4e, +0x05, 0x31, 0xaf, 0x46, 0x06, 0xad, 0x54, 0xed, +0x8d, 0xb2, 0x67, 0x61, 0x04, 0x61, 0x26, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0x4e, 0x10, 0xb1, 0x1b, 0x87, 0xce, 0xe3, +0xcc, 0xc3, 0x04, 0x32, 0x18, 0xf3, 0x4e, 0xd0, +0x2b, 0x99, 0xb0, 0x7c, 0xcd, 0x77, 0x11, 0x5c, +0x87, 0xbf, 0xaa, 0xee, 0xdd, 0xff, 0xdf, 0x20, +0x6f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0x1c, 0xde, 0xbf, 0x46, 0x99, +0x72, 0x99, 0xdf, 0x61, 0x31, 0xd6, 0xce, 0xfa, +0x59, 0xd0, 0x13, 0x85, 0x4f, 0x2b, 0xe6, 0xd4, +0x04, 0xbc, 0xd5, 0xba, 0xeb, 0xb0, 0xb7, 0x3e, +0x86, 0xd5, 0xb4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0xad, 0xd8, 0xd3, +0x8b, 0xd1, 0x00, 0xf0, 0x08, 0x81, 0xec, 0x93, +0x9d, 0xa1, 0x22, 0x63, 0xa0, 0x7b, 0x19, 0xdf, +0xae, 0x4a, 0x9a, 0x74, 0x9d, 0x4a, 0x84, 0xf3, +0x62, 0xa0, 0x85, 0x1c, 0x74, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0x48, +0xd7, 0x92, 0x17, 0x48, 0x16, 0x43, 0x8b, 0x5c, +0xe0, 0xf9, 0x8c, 0x8e, 0xa3, 0x64, 0x24, 0x95, +0xa0, 0x5d, 0x42, 0x94, 0x78, 0x84, 0x72, 0x47, +0x95, 0x37, 0x14, 0xec, 0x12, 0x22, 0x91, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0x04, 0x17, 0x02, 0xa5, 0x45, 0xf9, 0xee, +0x91, 0x92, 0xe9, 0x42, 0xea, 0x85, 0x8d, 0xa0, +0x1f, 0xa7, 0xf3, 0xdb, 0xbf, 0xc4, 0xb5, 0x29, +0x47, 0xa4, 0xbe, 0x33, 0x0a, 0x34, 0xd1, 0xf2, +0xd0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0x27, 0xd0, 0x16, 0x1c, 0xa7, +0xa6, 0xbd, 0xb0, 0xb6, 0xfe, 0x8e, 0xf1, 0xd3, +0x7f, 0x01, 0xcb, 0x0f, 0x5f, 0x48, 0x7a, 0x05, +0x0d, 0x10, 0xb9, 0xef, 0x03, 0xcd, 0xaf, 0x8a, +0xae, 0x6b, 0x8e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x6f, 0x36, 0x7f, +0x6a, 0xd4, 0xb7, 0x03, 0xaf, 0x9f, 0x2c, 0xf8, +0x12, 0x7a, 0x7f, 0xf0, 0x59, 0xed, 0x48, 0xfe, +0xf1, 0xc1, 0x83, 0xe1, 0xff, 0xb7, 0x8d, 0xc5, +0xdd, 0x5a, 0x6b, 0x04, 0x65, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0xf1, +0xcc, 0x9c, 0x4d, 0x8d, 0xa6, 0x9d, 0xcc, 0x78, +0x85, 0x6e, 0x2a, 0x29, 0x27, 0x8f, 0x10, 0xa6, +0x06, 0xe9, 0x8d, 0xfc, 0x5f, 0xdf, 0xc1, 0x2f, +0x48, 0x51, 0xbf, 0x72, 0x5f, 0x7c, 0x47, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0x81, 0x1e, 0x7d, 0xb9, 0xfa, 0x36, 0x67, +0xac, 0xa3, 0xa2, 0x70, 0x89, 0x4d, 0xd6, 0xf2, +0xe1, 0x54, 0xdb, 0xad, 0x2a, 0x10, 0xae, 0x7a, +0x28, 0x3a, 0x8c, 0x30, 0xe2, 0x63, 0xe9, 0xbc, +0x6f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0x66, 0xd2, 0x4d, 0xbb, 0x88, +0x50, 0xc6, 0x96, 0x2c, 0xfd, 0x8c, 0x9e, 0xba, +0x2b, 0xc7, 0xfa, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xac, 0x04, 0x50, 0xe7, +0x4b, 0xc8, 0x5e, 0xca, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0xcc, 0x42, 0x54, +0xfd, 0x21, 0xfd, 0x80, 0x7e, 0x42, 0x4d, 0x53, +0x5d, 0x39, 0xa5, 0xcd, 0x86, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x30, +0x61, 0xad, 0xd0, 0x17, 0xfd, 0x25, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0x4d, +0xe2, 0x08, 0x07, 0xc5, 0x92, 0x64, 0x60, 0x83, +0x7a, 0x13, 0x11, 0x1f, 0x1d, 0x56, 0x53, 0xba, +0xcb, 0x20, 0x15, 0xed, 0x59, 0x96, 0x2f, 0xc1, +0x13, 0xf7, 0x50, 0xb3, 0x7c, 0x57, 0xfd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0xc7, 0xf2, 0x0c, 0xd8, 0xab, 0xa8, 0xbf, +0x7c, 0x3c, 0xfa, 0x87, 0x4b, 0x3e, 0xa3, 0xfb, +0xbf, 0xf5, 0x45, 0xf3, 0xc4, 0xa1, 0x3a, 0x74, +0xf3, 0x6d, 0x60, 0xa5, 0x3c, 0x3e, 0x99, 0xb2, +0xe4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x11, 0x78, 0xd2, 0xa3, 0x22, +0x5e, 0xda, 0x6d, 0xaf, 0x0a, 0x83, 0xc9, 0xc2, +0x7a, 0x59, 0x91, 0x03, 0x51, 0x48, 0x9b, 0x58, +0x82, 0x46, 0xae, 0x8f, 0xc4, 0xf7, 0xd1, 0x23, +0x17, 0x3f, 0xe4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0x8b, 0x14, 0x6f, +0xc6, 0x7d, 0x37, 0x7d, 0x59, 0x2c, 0xb9, 0x40, +0xad, 0x46, 0x69, 0x96, 0x44, 0x03, 0xf8, 0x36, +0xcc, 0x1b, 0x82, 0x7b, 0xac, 0xc3, 0xf3, 0x70, +0x15, 0x54, 0x43, 0xad, 0x22, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0x3a, +0x3c, 0x7d, 0x5d, 0x23, 0x73, 0xf1, 0x2b, 0x72, +0xc4, 0x50, 0x00, 0x4c, 0x6e, 0xc3, 0x3f, 0xaf, +0x46, 0xdf, 0x5a, 0xf4, 0xae, 0xa2, 0x11, 0xd6, +0xd2, 0xc4, 0xfc, 0xb4, 0x9d, 0x40, 0x4c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0x0a, 0xee, 0x39, 0xde, 0xa5, 0x7b, 0xbd, +0x01, 0x71, 0x9a, 0xdc, 0xd7, 0x9b, 0x9f, 0xd2, +0x33, 0x2a, 0x7d, 0xeb, 0x33, 0xfe, 0x15, 0x75, +0xf6, 0x76, 0xec, 0xdc, 0x9e, 0x3f, 0x94, 0x93, +0x8d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0x1e, 0x8e, 0x9c, 0x5b, 0x84, +0xba, 0x46, 0x3c, 0xdc, 0x61, 0x83, 0x53, 0x8f, +0x0e, 0xce, 0xad, 0xe2, 0xd4, 0xdd, 0xe5, 0xab, +0x9d, 0x82, 0x09, 0xc6, 0xaf, 0xf5, 0x9f, 0x32, +0x00, 0x9c, 0x2c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0xc6, 0x2a, 0x7c, +0x3e, 0x1b, 0xa5, 0x84, 0x75, 0x6a, 0x84, 0xb4, +0x2d, 0xb8, 0x80, 0x46, 0x13, 0x13, 0x63, 0xc8, +0xc5, 0xd2, 0xd3, 0xdc, 0xea, 0xf3, 0x68, 0x49, +0xc4, 0xcd, 0x2b, 0xfe, 0x3c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0xf5, +0x6b, 0xa9, 0x48, 0x3b, 0x50, 0x31, 0x4e, 0x08, +0x0b, 0x48, 0x15, 0xbc, 0x03, 0x3c, 0x66, 0x43, +0x8f, 0xa7, 0x40, 0xa8, 0x18, 0xc2, 0x94, 0x06, +0x7c, 0x87, 0x25, 0xe2, 0xbe, 0xac, 0x0e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x5a, 0x9c, 0x67, 0x1f, 0xd1, 0xd1, 0xd0, +0x27, 0x52, 0x52, 0x06, 0xba, 0x17, 0x2f, 0x29, +0xff, 0xfe, 0xbc, 0x30, 0x62, 0x1d, 0x39, 0x46, +0x4e, 0xc5, 0x78, 0xeb, 0x44, 0x16, 0x9f, 0x63, +0xd8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0xf6, 0x49, 0x28, 0x42, 0xad, +0x23, 0x26, 0xe6, 0x02, 0x26, 0x34, 0x7a, 0xf8, +0xc2, 0xc3, 0xe1, 0x71, 0x62, 0x13, 0x35, 0xe1, +0x2a, 0xb9, 0x65, 0x4f, 0x14, 0x2d, 0x36, 0xec, +0x85, 0xf8, 0x33, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0x1e, 0xdb, 0x29, +0xdc, 0x65, 0x3c, 0x61, 0xd0, 0xc3, 0x8b, 0xd8, +0x04, 0xb6, 0xf4, 0x57, 0x09, 0xcb, 0xab, 0x03, +0x64, 0xb0, 0x54, 0xc7, 0x87, 0x7a, 0x15, 0x81, +0xe1, 0x59, 0xb9, 0xce, 0x4e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0x75, +0x40, 0x1b, 0x75, 0x24, 0xbe, 0xe6, 0x27, 0xef, +0x75, 0x35, 0x42, 0xc5, 0x9c, 0x65, 0x31, 0x24, +0x26, 0x84, 0x5e, 0x20, 0x6a, 0xa6, 0x72, 0x73, +0xe5, 0x24, 0x2a, 0x22, 0xf8, 0xfc, 0x2c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0x5f, 0xc4, 0xd6, 0x1d, 0x53, 0x2c, 0x2a, +0xd2, 0x80, 0xe9, 0xca, 0xdd, 0x42, 0x27, 0x65, +0x91, 0x8c, 0xb8, 0xba, 0x3a, 0xfb, 0x80, 0x51, +0x84, 0xf4, 0x2a, 0xd1, 0x59, 0xd2, 0xf8, 0xb6, +0xb9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0x5f, 0xb5, 0x51, 0xca, 0xb1, +0x19, 0xf7, 0x5a, 0xf1, 0x38, 0x9f, 0xad, 0x61, +0xca, 0xd7, 0x9a, 0x1d, 0x97, 0xfe, 0x83, 0xef, +0xaa, 0x35, 0xba, 0x37, 0x21, 0xb6, 0x87, 0x7d, +0x9e, 0xe6, 0x70, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0x17, 0x7c, 0x65, +0x54, 0xbf, 0xff, 0x9b, 0x1e, 0x9a, 0x9d, 0x37, +0xb4, 0x98, 0xcd, 0x66, 0xaa, 0x93, 0x00, 0x86, +0xe8, 0x5b, 0xc1, 0x43, 0x09, 0x82, 0x74, 0x09, +0x19, 0x09, 0x35, 0x1e, 0x6c, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0x02, +0x47, 0xa5, 0x1d, 0x77, 0x31, 0x04, 0xd4, 0x95, +0xce, 0xd7, 0x09, 0x74, 0x59, 0x5d, 0x56, 0xb2, +0x95, 0x01, 0x89, 0xb7, 0x2f, 0x57, 0x5d, 0x6a, +0xfc, 0x36, 0x28, 0x42, 0x18, 0x0c, 0x1d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0xf1, 0xd4, 0x98, 0x16, 0x95, 0x9f, 0x3e, +0xca, 0x07, 0xc4, 0x81, 0x4f, 0x3c, 0xda, 0xf7, +0xf1, 0xf3, 0xdd, 0xd6, 0xd3, 0x80, 0xd8, 0xea, +0xdb, 0x1b, 0xea, 0xaf, 0x31, 0x6b, 0xd8, 0x17, +0x7e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0xa3, 0x09, 0xc4, 0x95, 0xfd, +0x5f, 0xf9, 0xba, 0x8e, 0x2a, 0x29, 0xf5, 0xcf, +0xde, 0x70, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xa9, 0xf5, 0xb2, 0xb6, +0x5c, 0x4a, 0x1b, 0x4a, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0xb1, 0xd9, 0x79, +0xf9, 0x58, 0xda, 0x1c, 0xa5, 0xc9, 0xe8, 0x59, +0x97, 0x9e, 0xee, 0xc9, 0xcf, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf9, 0x8a, +0x49, 0x24, 0x1d, 0x7d, 0xd1, 0x5b, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0x4a, +0x2c, 0xf0, 0xc4, 0xd4, 0xb5, 0xac, 0x8c, 0xa0, +0xff, 0x58, 0x3d, 0xb4, 0x21, 0x6d, 0x4b, 0xcd, +0x05, 0x9f, 0xc8, 0x22, 0x6a, 0x39, 0xae, 0x35, +0xef, 0xea, 0x67, 0x34, 0x27, 0x6e, 0x34, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0x1b, 0xf4, 0xc9, 0x6d, 0xb0, 0xd5, 0x30, +0x4c, 0xfe, 0x60, 0x6b, 0xda, 0xe9, 0xa0, 0xc1, +0xcb, 0xc4, 0x8e, 0xe2, 0x0c, 0x05, 0xc2, 0x96, +0xa9, 0xc3, 0xdc, 0xfc, 0xdb, 0xbf, 0x55, 0xc6, +0x28, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0xd4, 0xfa, 0x87, 0x4d, 0x25, +0xde, 0x3b, 0x47, 0x1e, 0x2a, 0xd9, 0xaf, 0xb6, +0xbe, 0x25, 0xd7, 0xb8, 0x9d, 0x52, 0xf9, 0xfd, +0x92, 0x33, 0x5a, 0x1a, 0x02, 0xb3, 0xa9, 0xe6, +0x5a, 0xb1, 0xb3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0xdd, 0x2b, 0x27, +0xcf, 0x45, 0xdf, 0xee, 0x21, 0x71, 0x00, 0xf7, +0x41, 0x0f, 0xa4, 0x21, 0xd2, 0x55, 0x56, 0xeb, +0x36, 0xb8, 0x9a, 0x23, 0x8c, 0x60, 0x6b, 0xb2, +0xb6, 0x34, 0xea, 0xa9, 0x61, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0x4c, +0x57, 0xc5, 0x03, 0x92, 0x78, 0x8a, 0xc9, 0xa7, +0x52, 0x7a, 0x5f, 0x77, 0x6b, 0x08, 0xe9, 0x67, +0x14, 0x15, 0x83, 0xa5, 0xaa, 0x0f, 0x51, 0x5a, +0x89, 0xed, 0x37, 0x74, 0x53, 0x98, 0x87, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0x0c, 0x19, 0x38, 0x27, 0x17, 0x2a, 0xf9, +0xb1, 0xb9, 0xc7, 0x60, 0xc3, 0xe0, 0xef, 0x39, +0xb0, 0x75, 0x35, 0x84, 0x2d, 0xf9, 0xfa, 0xde, +0x06, 0xdc, 0x4b, 0xf0, 0xba, 0xf2, 0x32, 0x9e, +0xb3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0x91, 0x4a, 0xab, 0x76, 0x9a, +0x60, 0x91, 0x67, 0x0b, 0x2e, 0xb8, 0x9d, 0x0f, +0xdd, 0x47, 0xb0, 0xd7, 0x59, 0xf3, 0xfa, 0x43, +0x33, 0x46, 0x80, 0xa4, 0x69, 0xf6, 0x98, 0x22, +0x0a, 0xb1, 0x65, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0xe6, 0x7c, 0xbe, +0x2c, 0x9d, 0x39, 0xde, 0xf1, 0x7b, 0x0e, 0xab, +0x1d, 0x10, 0x16, 0xdf, 0xcc, 0x90, 0x89, 0x38, +0xea, 0x1a, 0xca, 0xdb, 0x93, 0x2e, 0x93, 0x16, +0xe2, 0xc9, 0x58, 0x9c, 0xc1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0xe2, +0x6c, 0x7e, 0xb7, 0x43, 0x25, 0x26, 0xef, 0xbf, +0xa7, 0x38, 0x3a, 0x1b, 0x0a, 0x66, 0x7e, 0x1b, +0xcb, 0x7e, 0xd6, 0x81, 0xbd, 0xcc, 0x8d, 0xc3, +0xc5, 0xc5, 0x14, 0xc1, 0xd1, 0x76, 0xc8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0x56, 0x9d, 0xb5, 0x43, 0xae, 0x46, 0x23, +0x41, 0x4d, 0xc7, 0xc6, 0x56, 0x93, 0x58, 0xfe, +0x5c, 0x8e, 0xc6, 0x9b, 0xcd, 0xfb, 0x02, 0xbf, +0x48, 0x23, 0x24, 0xe6, 0x0f, 0x38, 0x45, 0xac, +0x8f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0xc4, 0x7a, 0xe8, 0xf1, 0x61, +0x9e, 0x68, 0xda, 0xea, 0x12, 0xdf, 0xd4, 0xb6, +0x46, 0x00, 0x13, 0xd3, 0xdc, 0x27, 0xbb, 0xaa, +0x90, 0x9a, 0x95, 0x48, 0x2f, 0xbb, 0xee, 0xbc, +0x1a, 0xb2, 0x13, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0xda, 0x99, 0xea, +0x13, 0x8a, 0xe2, 0x48, 0xc4, 0xd7, 0x1b, 0xad, +0xd7, 0x26, 0x7b, 0x90, 0x3c, 0x6d, 0x49, 0xaa, +0x8c, 0x6f, 0xb9, 0xc4, 0x11, 0x65, 0x09, 0x63, +0xd9, 0x27, 0x9a, 0xd4, 0x14, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0xba, +0xbe, 0x5f, 0x41, 0x16, 0xc1, 0x03, 0xde, 0xf2, +0xdb, 0x34, 0x00, 0x4c, 0x50, 0xd8, 0xfc, 0x88, +0x75, 0x51, 0x36, 0x10, 0x73, 0x5b, 0x74, 0x5b, +0xb7, 0xda, 0xbc, 0xda, 0xf8, 0x7e, 0x0a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0x87, 0x08, 0x2a, 0x77, 0x02, 0xf2, 0x69, +0x1e, 0xdb, 0x92, 0x06, 0x26, 0x13, 0x43, 0xb2, +0x43, 0x36, 0xe0, 0xa5, 0x8a, 0x08, 0xee, 0xba, +0x24, 0xf7, 0x7d, 0xdd, 0x83, 0xa3, 0x68, 0x53, +0x03, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0x7f, 0x47, 0xa2, 0xe9, 0xc0, +0xf1, 0xa8, 0x06, 0xf5, 0x65, 0x32, 0x41, 0x16, +0x79, 0xe2, 0xc7, 0x12, 0x31, 0xca, 0x06, 0x4f, +0xa3, 0x09, 0x66, 0xf0, 0x24, 0x8b, 0xf9, 0x86, +0xc0, 0xa4, 0x53, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x68, 0x94, 0x77, +0x27, 0x41, 0x6f, 0xb0, 0xbf, 0x6a, 0x40, 0xff, +0x06, 0x37, 0xe5, 0x19, 0x77, 0x7b, 0xac, 0xbd, +0xcf, 0x62, 0xbe, 0x9c, 0x50, 0x3e, 0x49, 0x40, +0x9a, 0x07, 0x23, 0xd6, 0xd7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0x6e, +0xff, 0x86, 0x8f, 0x37, 0xa5, 0x60, 0x5c, 0x40, +0xa4, 0xc3, 0x70, 0x88, 0x2e, 0x45, 0xac, 0xc1, +0x59, 0xd6, 0x16, 0xa1, 0x60, 0x61, 0x88, 0x03, +0x1e, 0x17, 0xb3, 0x01, 0xf2, 0xb7, 0xc8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0x80, 0x29, 0x15, 0x28, 0xd5, 0xe7, 0x7a, +0xc3, 0xe6, 0x1e, 0x86, 0x7b, 0x60, 0x04, 0x76, +0x28, 0x24, 0x6c, 0xa0, 0x88, 0x19, 0x47, 0xad, +0x88, 0x1a, 0xf3, 0xce, 0xf4, 0xaf, 0xb6, 0xd1, +0xd6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0x43, 0xd5, 0x40, 0xc7, 0x8a, +0x1e, 0x83, 0xb3, 0x04, 0x3a, 0xbe, 0x9f, 0x7f, +0x7b, 0xb9, 0xc3, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x30, 0xb8, 0xd7, 0x22, +0xfa, 0xa1, 0xe5, 0x05, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x23, 0x00, 0x40, +0x4a, 0x6e, 0x21, 0x56, 0x0f, 0xe1, 0xd6, 0xa6, +0x4e, 0xc3, 0xb4, 0x24, 0x3a, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x4e, +0x81, 0xdb, 0x36, 0xa3, 0xa5, 0x8c, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0x6e, +0x45, 0xe2, 0x4e, 0xc3, 0x70, 0x05, 0xa6, 0x46, +0x25, 0x6b, 0x28, 0x32, 0x29, 0x09, 0xd3, 0xff, +0x90, 0x7c, 0xa9, 0xc3, 0x5b, 0x8e, 0x28, 0x6b, +0x09, 0x9a, 0xf8, 0x7f, 0xb3, 0xfb, 0x9d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0x0a, 0x3f, 0xd2, 0xca, 0xf0, 0xfd, 0x66, +0x96, 0xd5, 0x7c, 0xc6, 0x39, 0x34, 0x3f, 0xc5, +0x73, 0x91, 0xce, 0xba, 0x9f, 0x72, 0xa8, 0xb4, +0x92, 0x7b, 0x25, 0xb3, 0xde, 0x32, 0x36, 0xa6, +0x06, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0x84, 0x4d, 0xa2, 0x1c, 0x2c, +0xa2, 0x5e, 0xd2, 0x74, 0xd0, 0x65, 0xb4, 0xee, +0xd0, 0xc1, 0x82, 0xba, 0xb9, 0xe1, 0x48, 0x6d, +0x33, 0x22, 0xd0, 0xd8, 0x3a, 0x89, 0xa4, 0x5a, +0xb8, 0xe3, 0x37, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0x1a, 0xb9, 0xaf, +0x58, 0x74, 0x2a, 0x81, 0x56, 0xf6, 0x7f, 0x86, +0x79, 0x6f, 0xca, 0xb0, 0x79, 0xa4, 0xfc, 0x75, +0xd2, 0xd3, 0x9c, 0x27, 0x31, 0x06, 0xe8, 0xc2, +0xc3, 0x33, 0xb8, 0x0e, 0xb9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x62, +0xf0, 0xa2, 0x21, 0xc5, 0x09, 0xe9, 0x61, 0x52, +0xeb, 0x11, 0x4c, 0x75, 0xae, 0xdf, 0x03, 0xd0, +0x08, 0x7d, 0xb3, 0x8b, 0x56, 0xbb, 0x34, 0xed, +0x16, 0x5d, 0xdb, 0xcf, 0x7e, 0x3f, 0x6c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0x93, 0x8a, 0x33, 0x0c, 0xc8, 0xb7, 0x17, +0x27, 0xe4, 0xa9, 0xa5, 0x84, 0xbc, 0x97, 0x1e, +0x53, 0x37, 0xad, 0x7b, 0xcd, 0x76, 0x4d, 0x75, +0x59, 0x5c, 0x48, 0xc8, 0x52, 0xa1, 0x05, 0x9e, +0x86, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0x81, 0xad, 0x18, 0xcb, 0xd9, +0x2b, 0x39, 0xaf, 0xf4, 0xe1, 0x0b, 0xfa, 0xea, +0x98, 0x46, 0x50, 0xf8, 0x8c, 0x0d, 0x56, 0xdf, +0xdc, 0x4f, 0xa8, 0x13, 0x5d, 0x9c, 0xa3, 0x46, +0xe5, 0xdd, 0x3e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0x1f, 0x9e, 0x57, +0xba, 0x91, 0x29, 0x2d, 0x9f, 0x27, 0x4d, 0x5f, +0xd6, 0x9e, 0x39, 0x49, 0x90, 0x7e, 0xed, 0x5a, +0x28, 0x17, 0x58, 0x7f, 0xd8, 0x12, 0xd4, 0x66, +0x6e, 0xab, 0x73, 0xdc, 0xed, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0x36, +0xf9, 0xda, 0x83, 0x82, 0x25, 0x66, 0x3d, 0xb2, +0xda, 0x0a, 0x9e, 0xd4, 0x34, 0x51, 0x3b, 0x84, +0xd9, 0x75, 0x30, 0xdd, 0x89, 0x24, 0x2a, 0x0e, +0xa4, 0x86, 0x25, 0xab, 0xbf, 0x53, 0x3a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0x49, 0xf8, 0x3b, 0xcb, 0x1b, 0x60, 0xc5, +0xa0, 0x50, 0x42, 0x34, 0xa3, 0xf7, 0x80, 0x1b, +0x3b, 0x58, 0x24, 0x12, 0xac, 0xd4, 0x18, 0xe5, +0xac, 0x96, 0xb6, 0x97, 0x7e, 0xf8, 0xcb, 0x2f, +0x17, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0xce, 0x00, 0x98, 0xd6, 0x94, +0x63, 0xd3, 0x3c, 0xa9, 0x96, 0x32, 0x1e, 0x6b, +0x81, 0x9f, 0xc3, 0xe9, 0x13, 0x40, 0x48, 0x23, +0x32, 0x60, 0xb9, 0xf4, 0x14, 0xbc, 0x8f, 0x08, +0xa9, 0xd0, 0x29, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0x8e, 0xbf, 0xc7, +0x5f, 0x81, 0x81, 0xdd, 0x32, 0x63, 0x27, 0x57, +0x95, 0x5a, 0xdf, 0xd3, 0x70, 0x6d, 0x15, 0x2b, +0x0d, 0xf3, 0xd4, 0x55, 0x01, 0x3d, 0xf9, 0x2e, +0x25, 0xcf, 0xd3, 0x69, 0x97, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0x7f, +0x53, 0x65, 0x7e, 0x51, 0x0c, 0xe6, 0x90, 0x64, +0x47, 0x75, 0xbf, 0x55, 0x4c, 0xec, 0x29, 0x4c, +0x32, 0x97, 0xfb, 0x9a, 0x23, 0x1e, 0x98, 0xf3, +0x35, 0xfe, 0xc2, 0xf8, 0x8e, 0xc1, 0xe4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0x65, 0x74, 0x44, 0xab, 0xf1, 0x21, 0x00, +0x79, 0x00, 0xc8, 0x0f, 0x4a, 0xee, 0x59, 0xbe, +0x32, 0xce, 0x00, 0x6c, 0xa1, 0xe1, 0xea, 0x3e, +0x05, 0xb8, 0x38, 0x16, 0xe2, 0x9b, 0x23, 0xa2, +0x01, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0x8a, 0x0e, 0x1b, 0x43, 0x22, +0xf2, 0x6d, 0x19, 0x35, 0xc2, 0x99, 0xa6, 0xe4, +0xef, 0x45, 0xb9, 0x99, 0x3a, 0x81, 0x10, 0x1d, +0x2f, 0x6e, 0x39, 0x5e, 0xd8, 0x59, 0xae, 0x3f, +0x81, 0xc3, 0x49, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0x56, 0x93, 0x6f, +0x60, 0xd8, 0x7e, 0x42, 0xc1, 0x08, 0xce, 0xbc, +0x3f, 0x2c, 0x54, 0x14, 0x7e, 0x11, 0x02, 0x42, +0x41, 0xc8, 0x1c, 0xfc, 0x28, 0xe0, 0xd3, 0x38, +0xab, 0xee, 0x59, 0xbd, 0xb0, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0xc6, +0xaa, 0x30, 0x23, 0xa2, 0x8c, 0x7d, 0x48, 0x9d, +0x1d, 0x0f, 0x8f, 0x3e, 0x3a, 0x63, 0x71, 0x50, +0x06, 0x4f, 0x38, 0xba, 0x2e, 0xde, 0x6e, 0x84, +0xed, 0x24, 0x08, 0x08, 0x18, 0x09, 0xbd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0x6c, 0x19, 0x78, 0x73, 0x2b, 0xa9, 0xaa, +0x14, 0x79, 0x0c, 0x58, 0x90, 0x09, 0x76, 0xfb, +0x14, 0x63, 0x66, 0xe5, 0x07, 0x7a, 0x45, 0x54, +0x86, 0xf2, 0x85, 0xb7, 0x8d, 0x1d, 0x58, 0xd8, +0xcb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0xd5, 0x2c, 0x3a, 0x21, 0x9d, +0x97, 0xab, 0xc0, 0xcb, 0x3d, 0x77, 0x71, 0xbd, +0xb2, 0x95, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xd1, 0x92, 0xf5, 0xa2, +0x5d, 0x4a, 0x54, 0xa0, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x2e, 0xca, 0x83, +0xa5, 0x9f, 0x4f, 0x6c, 0x4a, 0xfd, 0xbc, 0x0e, +0x6e, 0xea, 0xb1, 0x9b, 0x8c, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x75, +0x93, 0x1d, 0xde, 0x48, 0x68, 0x0f, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x80, +0x30, 0x31, 0x09, 0x26, 0x7c, 0xda, 0x61, 0xde, +0x18, 0x52, 0xcf, 0xcd, 0xda, 0xfc, 0xa0, 0x1d, +0x20, 0xfa, 0x6f, 0x85, 0x55, 0x2a, 0xd3, 0xb1, +0xc3, 0x09, 0xb3, 0xc9, 0x43, 0xfb, 0x01, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x40, 0x18, 0x44, 0xfa, 0x01, 0xad, 0x12, +0x4b, 0xa4, 0x41, 0x3c, 0xa1, 0x97, 0x67, 0x09, +0x5d, 0xf0, 0x02, 0x18, 0x9c, 0x82, 0x3e, 0x73, +0x9e, 0x66, 0x80, 0x1c, 0x0e, 0xce, 0xd6, 0xd7, +0xd0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0x92, 0x44, 0xa1, 0x96, 0xcd, +0x77, 0xa1, 0x8d, 0x7d, 0x80, 0xa4, 0xd2, 0xc9, +0x52, 0xc7, 0x15, 0x86, 0x12, 0x8c, 0x65, 0x12, +0x91, 0x07, 0x3e, 0xc2, 0xea, 0x26, 0xe9, 0x73, +0x75, 0x51, 0x1a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0xc9, 0xe7, 0x10, +0x80, 0xad, 0x75, 0x91, 0x3d, 0x3c, 0xec, 0xde, +0xdd, 0x6f, 0x70, 0x16, 0x32, 0xe6, 0x11, 0x94, +0xe7, 0x3a, 0x24, 0x64, 0x27, 0x8a, 0xa1, 0xc4, +0xf9, 0xba, 0x46, 0xd5, 0x8b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0x0d, +0xf2, 0x83, 0x44, 0x07, 0xf8, 0x9f, 0x9e, 0x8f, +0xc3, 0x71, 0x0b, 0x53, 0x8a, 0x48, 0x41, 0x94, +0x8b, 0x11, 0x7d, 0x9c, 0xf5, 0xce, 0x31, 0xf6, +0xde, 0x38, 0x95, 0x42, 0x7d, 0x8e, 0xe3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0xd7, 0x12, 0x6c, 0xc4, 0x90, 0x48, 0x87, +0xc8, 0x1a, 0x11, 0xa7, 0xad, 0x66, 0x11, 0x02, +0xbd, 0xac, 0x0d, 0xdd, 0x4c, 0x5c, 0x3b, 0x90, +0xfe, 0x50, 0x9d, 0xf4, 0x4d, 0x2d, 0x61, 0xb8, +0x84, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0x1c, 0xb6, 0x86, 0x33, 0x5f, +0x71, 0x46, 0x73, 0xfc, 0xe4, 0x6f, 0xe0, 0x8d, +0x65, 0x59, 0xac, 0x2f, 0xe7, 0x44, 0xf0, 0xd1, +0x0f, 0x72, 0x60, 0x96, 0xaa, 0x9d, 0x54, 0xfb, +0x24, 0x84, 0x58, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0xb4, 0xb7, 0x8a, +0xf8, 0xf1, 0x37, 0x0b, 0x6b, 0x7f, 0xc9, 0x5a, +0x86, 0xa2, 0x07, 0x18, 0x12, 0x76, 0xa7, 0xa2, +0xbf, 0xd1, 0x20, 0x57, 0x19, 0xc4, 0xe3, 0xf3, +0xc9, 0xe7, 0x2f, 0x38, 0x91, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0xfb, +0xb6, 0xaa, 0xb9, 0x6a, 0x47, 0x6a, 0x1f, 0xe5, +0x38, 0xe1, 0xfb, 0x36, 0x72, 0x67, 0xa3, 0xb8, +0x57, 0x75, 0x42, 0x17, 0x9d, 0xe7, 0xbf, 0xd6, +0x62, 0x62, 0xa2, 0xe4, 0x42, 0x9c, 0x3a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0x9e, 0x0c, 0xe9, 0xf1, 0x9d, 0xb2, 0x8f, +0x84, 0xc0, 0xb4, 0x62, 0xf8, 0x63, 0x15, 0x81, +0x4d, 0x5a, 0x9e, 0x16, 0x74, 0x51, 0x7c, 0x52, +0x86, 0x09, 0xbf, 0x75, 0xcd, 0x91, 0xe1, 0x0b, +0xef, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0xf0, 0xd4, 0x15, 0x0f, 0x78, +0xf0, 0xab, 0x10, 0x99, 0x66, 0x6c, 0x4f, 0x15, +0xe1, 0x1a, 0xed, 0xe6, 0x2c, 0x39, 0x59, 0x7b, +0xe9, 0x19, 0x1f, 0xe4, 0xd0, 0x48, 0xe2, 0x3b, +0x54, 0xb2, 0x01, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0xaf, 0x76, 0xd8, +0x72, 0x34, 0xaf, 0x50, 0x8f, 0xb2, 0xd9, 0xe7, +0x13, 0x86, 0xe2, 0x65, 0x09, 0x78, 0x06, 0x57, +0x92, 0x51, 0x10, 0xac, 0x41, 0xdd, 0xdb, 0xf1, +0x42, 0x2b, 0x3d, 0xe9, 0x2d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0xc2, +0x1e, 0x1c, 0xeb, 0x94, 0xd5, 0xe5, 0xe0, 0xe3, +0x2a, 0x2e, 0x7a, 0xb6, 0x62, 0x15, 0xfe, 0x77, +0x1b, 0x65, 0xe5, 0x64, 0x0a, 0x78, 0xca, 0x6a, +0xe1, 0x9e, 0xd5, 0xe2, 0x4b, 0x22, 0x3d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0x63, 0x34, 0xc4, 0xb3, 0x3c, 0x14, 0x47, +0xb1, 0x23, 0x65, 0x64, 0x09, 0x01, 0x16, 0x9d, +0x93, 0xb9, 0x5b, 0x0d, 0x64, 0xcf, 0xb6, 0x0c, +0x1d, 0xcc, 0xcd, 0x66, 0x5e, 0xc3, 0xa6, 0x7a, +0x6f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0xe3, 0x8f, 0xc1, 0xb8, 0x58, +0x49, 0x9d, 0xdc, 0xea, 0x35, 0x0e, 0x0a, 0x4d, +0x21, 0x2e, 0xa6, 0x23, 0x1d, 0xda, 0x7e, 0x9b, +0xd4, 0xa5, 0xbf, 0x40, 0x94, 0x58, 0xd9, 0xd6, +0x99, 0xc2, 0x02, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0x46, 0x20, 0x77, +0x6a, 0x0e, 0xe9, 0xed, 0xd3, 0x44, 0x37, 0xe1, +0xeb, 0xf2, 0x22, 0x4c, 0x77, 0xa0, 0x7a, 0xa7, +0x6a, 0x84, 0xdd, 0xd3, 0x35, 0x6b, 0xc2, 0x5b, +0xac, 0xb0, 0xfb, 0x5d, 0x26, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0x8f, +0x06, 0xb4, 0x92, 0x30, 0xc6, 0xdd, 0xd3, 0x17, +0x2d, 0xa6, 0x91, 0x67, 0xfb, 0xb0, 0x42, 0x99, +0xd6, 0xd6, 0x81, 0xf9, 0xaf, 0xfb, 0x13, 0x36, +0xc6, 0x73, 0x7a, 0x33, 0x62, 0x65, 0xb9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0x9f, 0x18, 0x5e, 0xd3, 0x5e, 0xe4, 0x58, +0x9d, 0x93, 0x31, 0xd6, 0x55, 0x17, 0x00, 0x02, +0x23, 0x0e, 0x7a, 0x33, 0xb5, 0x97, 0xf8, 0x65, +0xda, 0x7b, 0x56, 0x9d, 0xd2, 0xbf, 0xad, 0x49, +0x60, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0xac, 0x92, 0xd6, 0x85, 0xb9, +0x0f, 0x2d, 0x84, 0xd5, 0x2b, 0x57, 0x9b, 0x9d, +0xf6, 0x71, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x42, 0x46, 0x80, 0x3c, +0x72, 0xce, 0x8b, 0xb8, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0x6e, 0x3a, 0xd9, +0x3d, 0xba, 0xa4, 0x27, 0x0c, 0x07, 0xea, 0x62, +0xd3, 0x87, 0x5e, 0xe2, 0x8b, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x43, +0xa0, 0x0b, 0x7b, 0x28, 0x61, 0x26, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0x11, +0x40, 0x7f, 0xa6, 0xd1, 0x7f, 0x24, 0xe1, 0x70, +0x8b, 0x65, 0xc0, 0x66, 0x51, 0x61, 0x74, 0x01, +0xba, 0x2a, 0x7c, 0x78, 0x13, 0x50, 0xd5, 0xa8, +0xe4, 0xad, 0x0d, 0xd9, 0x47, 0xa3, 0xfa, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0x35, 0x8f, 0xf3, 0x40, 0x39, 0x54, 0xae, +0xdc, 0xb0, 0x16, 0x57, 0x0e, 0x6b, 0xf6, 0x1b, +0x39, 0x60, 0x58, 0x6b, 0x58, 0xcf, 0xdd, 0x12, +0x1a, 0xee, 0xf4, 0xc9, 0x9d, 0xcd, 0xec, 0xb5, +0x01, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0xa6, 0xe0, 0xc7, 0x8d, 0x9c, +0x66, 0x29, 0x3d, 0xa0, 0x68, 0x08, 0x76, 0xd5, +0x41, 0x91, 0x2e, 0x89, 0x9d, 0xb1, 0xba, 0xff, +0xa6, 0x8a, 0xc2, 0x1c, 0xd8, 0x9a, 0x9d, 0x86, +0xe2, 0xe0, 0x32, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0x6c, 0x25, 0xeb, +0x7b, 0x75, 0x2c, 0xcd, 0x20, 0x55, 0x79, 0x42, +0x8c, 0x01, 0x7f, 0xac, 0xe6, 0x3a, 0x02, 0x2a, +0x09, 0xe5, 0xb0, 0x0f, 0x73, 0xfc, 0x01, 0x50, +0x90, 0xbc, 0x5e, 0x8b, 0x2f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0x80, +0x09, 0xa0, 0x54, 0x43, 0x4c, 0xb1, 0x85, 0xca, +0x19, 0x9c, 0x2a, 0x5b, 0xe1, 0xae, 0xf8, 0x70, +0x05, 0x6f, 0xa8, 0x0a, 0x1e, 0x05, 0x9a, 0x79, +0xc3, 0x50, 0x64, 0x19, 0xac, 0xbb, 0xa2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0x9c, 0xa5, 0xc4, 0x72, 0x30, 0x9f, 0xff, +0x57, 0x76, 0x0f, 0xdc, 0x6f, 0x3a, 0x43, 0x6e, +0x5b, 0x76, 0x2d, 0x77, 0xb0, 0xe3, 0x65, 0x43, +0xe9, 0x46, 0x9e, 0x0f, 0xd1, 0xc6, 0x52, 0x5c, +0xd8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0xce, 0x58, 0x7d, 0xb7, 0x6e, +0x15, 0xba, 0xdb, 0x60, 0x93, 0x55, 0x9f, 0x8b, +0x09, 0xeb, 0xb3, 0x56, 0x1c, 0x3e, 0xb7, 0x8a, +0xf8, 0x32, 0x08, 0x57, 0xa5, 0xc7, 0xd0, 0xcc, +0x1b, 0x6a, 0xe1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0xed, 0xfc, 0x07, +0xaf, 0x97, 0x81, 0x46, 0xde, 0x8a, 0xa4, 0x4e, +0x9f, 0x22, 0x46, 0x81, 0x95, 0xb6, 0x5c, 0xe5, +0x76, 0x14, 0xe4, 0x9a, 0x3d, 0x5a, 0x9c, 0xdc, +0x6a, 0x08, 0x40, 0x31, 0xe8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0xb9, +0xb3, 0xc6, 0x09, 0x32, 0x4c, 0xdf, 0xe8, 0x5b, +0xae, 0x69, 0xc1, 0x83, 0x09, 0xfc, 0xd1, 0x12, +0xa1, 0x6b, 0x4e, 0x51, 0x65, 0x2e, 0xf9, 0xc5, +0xcd, 0xe9, 0xb3, 0xf0, 0x98, 0xa0, 0x8c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0x11, 0x90, 0xa0, 0xcc, 0xbf, 0xd5, 0x82, +0xc2, 0x11, 0xb1, 0x9a, 0x71, 0x6a, 0x15, 0x35, +0x19, 0xf2, 0x5c, 0x86, 0xc0, 0x18, 0x49, 0x81, +0xad, 0xe7, 0x1a, 0xeb, 0x9d, 0x5f, 0x0e, 0x0e, +0xcd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0x8a, 0x8e, 0x9e, 0xb8, 0xe3, +0x63, 0x3c, 0x74, 0x0a, 0x9f, 0x35, 0xc4, 0x3c, +0xf3, 0xf7, 0xb0, 0xb4, 0x03, 0x72, 0x26, 0x04, +0x34, 0x54, 0xb2, 0x5a, 0x9e, 0x3f, 0xa2, 0x08, +0x51, 0xf5, 0x0d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0xb6, 0x26, 0x42, +0x7d, 0x00, 0x03, 0x69, 0xd9, 0x5c, 0x57, 0xff, +0xa3, 0x12, 0xdf, 0xcf, 0x91, 0x5e, 0x92, 0x33, +0x34, 0xba, 0xb6, 0x95, 0xd5, 0x13, 0x02, 0x08, +0x58, 0x97, 0xc5, 0x1a, 0xe7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0x4b, +0x87, 0x5a, 0xa1, 0xd8, 0x2c, 0x97, 0xba, 0xb5, +0xbe, 0x7b, 0x32, 0x3d, 0x53, 0x31, 0x49, 0x70, +0xa4, 0xd6, 0x5f, 0x2c, 0x51, 0xb8, 0x74, 0xa7, +0xc4, 0x8d, 0x2e, 0xec, 0x63, 0x82, 0x30, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0x2b, 0x35, 0x7d, 0xef, 0x29, 0x13, 0x69, +0xa2, 0xc0, 0x76, 0xf5, 0x66, 0x73, 0x8c, 0x28, +0x5b, 0x2b, 0xc2, 0xd0, 0x73, 0x30, 0xd0, 0x20, +0x82, 0x3c, 0xfe, 0xd7, 0x08, 0x06, 0xc9, 0x10, +0x95, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0xeb, 0x9a, 0x85, 0x4c, 0x8a, +0x8c, 0x57, 0x28, 0x6e, 0x62, 0x23, 0x17, 0x44, +0x0f, 0x42, 0xc9, 0x9f, 0x36, 0x07, 0xea, 0xb0, +0xef, 0x52, 0x29, 0xa0, 0xcf, 0xcb, 0xa7, 0xbf, +0xc1, 0xc3, 0x61, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0xbc, 0x19, 0xea, +0xff, 0x7d, 0xf3, 0xbb, 0x96, 0x1d, 0x37, 0x96, +0x39, 0x9f, 0xf5, 0x39, 0x07, 0x0d, 0xce, 0xf2, +0xda, 0x91, 0x72, 0x75, 0x5e, 0x85, 0xc3, 0x71, +0xfb, 0xe8, 0xf9, 0x3d, 0x06, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0x58, +0xda, 0x67, 0x32, 0x4b, 0xce, 0x4c, 0x17, 0xf7, +0x19, 0x66, 0xba, 0x47, 0x4b, 0x44, 0x75, 0xbb, +0x80, 0x8b, 0x03, 0x7e, 0xf6, 0x0a, 0xb1, 0x49, +0x08, 0x6c, 0x9e, 0xbf, 0x04, 0xb2, 0xce, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0x10, 0xbc, 0xd7, 0x40, 0x91, 0x13, 0x97, +0x8c, 0x8e, 0x77, 0xd2, 0x2d, 0x7b, 0x30, 0x47, +0xb8, 0x15, 0x8c, 0x42, 0x64, 0x2e, 0xe9, 0x33, +0xd3, 0x45, 0x95, 0x30, 0x3b, 0xa5, 0x24, 0x5c, +0xa7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0x5e, 0xcc, 0x6f, 0x4e, 0x78, +0xf3, 0xaa, 0x83, 0x31, 0xb4, 0x21, 0xa1, 0x2d, +0xb4, 0x28, 0xed, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x61, 0xe5, 0xe8, 0x56, +0x4b, 0xe7, 0xb3, 0xf5, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0x16, 0x01, 0x6e, +0x0b, 0xa1, 0xa8, 0xf8, 0xa0, 0x0e, 0xe6, 0xd6, +0x3c, 0x2c, 0x8e, 0x27, 0x8d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x55, +0xe9, 0x9d, 0x94, 0x56, 0x06, 0x05, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x62, +0xb9, 0x1a, 0xa9, 0xb1, 0x31, 0x22, 0x59, 0x4b, +0x44, 0x0c, 0xab, 0x58, 0x09, 0x3a, 0x87, 0x4c, +0xcc, 0x00, 0x71, 0x18, 0xab, 0x1f, 0xf4, 0x44, +0x71, 0xbd, 0x5d, 0x23, 0xcf, 0xac, 0x3d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0x97, 0x6b, 0x65, 0x08, 0xf0, 0x76, 0xfb, +0x7e, 0xf5, 0xc1, 0x77, 0xd3, 0xbb, 0xee, 0x34, +0x37, 0x37, 0x49, 0xdb, 0xa6, 0xc6, 0x88, 0xe7, +0x2d, 0xac, 0xa1, 0x89, 0x34, 0x51, 0x8c, 0x11, +0xd7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0x33, 0x62, 0x66, 0xf2, 0xa3, +0x43, 0xf1, 0x5b, 0x4c, 0x2d, 0xb6, 0x78, 0xaa, +0xd5, 0xb2, 0x45, 0x20, 0xa0, 0xa1, 0xe7, 0x5a, +0x01, 0x8a, 0x86, 0x59, 0xe9, 0xbb, 0xff, 0xbc, +0x51, 0x69, 0x8a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0x1c, 0x59, 0x84, +0xaf, 0x0f, 0xc0, 0x56, 0xe5, 0x40, 0x67, 0x42, +0x7d, 0x23, 0x5b, 0xcb, 0x6e, 0x80, 0x4e, 0x0a, +0xe5, 0xee, 0xd9, 0x03, 0xc6, 0x1d, 0xfa, 0xfb, +0xfc, 0xfa, 0xf6, 0x71, 0xf7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0xb0, +0xf1, 0x03, 0xeb, 0xe9, 0x52, 0x79, 0x2a, 0x1d, +0x9a, 0xe6, 0xd7, 0xb1, 0x00, 0xf3, 0x00, 0xd2, +0x6d, 0x3f, 0x71, 0x73, 0xab, 0x07, 0x09, 0xe0, +0x3d, 0xb2, 0x92, 0xd3, 0xdc, 0xc7, 0x55, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0xa1, 0x6c, 0xfb, 0x51, 0x01, 0x3b, 0xca, +0xf0, 0xcb, 0x72, 0xa0, 0x92, 0xf8, 0x49, 0x3f, +0x82, 0x5c, 0xfd, 0x1c, 0xa4, 0x51, 0x74, 0x3f, +0xbc, 0xe9, 0x03, 0x02, 0xec, 0x35, 0x6c, 0xa0, +0x3f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0x5c, 0xdf, 0x99, 0xf2, 0x1c, +0xbb, 0x9a, 0xdd, 0xad, 0x35, 0x4a, 0x93, 0x5f, +0x40, 0xc2, 0x4f, 0x14, 0x90, 0x31, 0x14, 0xf7, +0x8e, 0x13, 0x56, 0x42, 0xaa, 0x36, 0x17, 0x89, +0x6e, 0xff, 0xea, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0x27, 0xd5, 0xa2, +0x58, 0x7d, 0xcd, 0x88, 0xd0, 0xe5, 0x86, 0x40, +0x66, 0x6a, 0x30, 0x8d, 0xf8, 0x8d, 0x4e, 0xe2, +0x55, 0x46, 0x22, 0x59, 0xbb, 0xbe, 0x60, 0x37, +0x72, 0xbc, 0xef, 0x6a, 0x00, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x37, +0x2a, 0xe3, 0x34, 0x6c, 0xf4, 0xdf, 0x80, 0x11, +0x5f, 0x58, 0xb3, 0xce, 0xfa, 0xe9, 0xf2, 0x38, +0x48, 0x94, 0x55, 0x63, 0xf2, 0x8d, 0xe0, 0x15, +0x80, 0x49, 0x89, 0x6a, 0x5e, 0x04, 0xe0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0xc2, 0x06, 0xda, 0x0d, 0xd4, 0x59, 0x08, +0x29, 0xc4, 0x48, 0xe5, 0x55, 0x97, 0xb9, 0xf1, +0xb5, 0xc7, 0x00, 0x0e, 0x08, 0x38, 0x41, 0xba, +0xd3, 0x87, 0xda, 0x38, 0xe9, 0xc8, 0x49, 0x51, +0x0c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0x04, 0xc7, 0xc9, 0x22, 0xc0, +0x74, 0x48, 0xfd, 0x83, 0xe1, 0x2f, 0x98, 0x63, +0x6d, 0xcb, 0x1b, 0x12, 0xe4, 0xe6, 0xee, 0x00, +0x9c, 0x8d, 0xb9, 0x7e, 0xb9, 0x03, 0x3d, 0x73, +0x6e, 0x45, 0xb6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x9e, 0x88, 0xd5, +0x4d, 0x7c, 0x94, 0x8f, 0x0c, 0x38, 0x9a, 0x56, +0xba, 0xc9, 0xdd, 0x4f, 0x35, 0xf6, 0xc1, 0xbd, +0x6d, 0x0f, 0x89, 0xbb, 0x9f, 0xef, 0x68, 0xc5, +0x71, 0x41, 0x3f, 0x57, 0x3a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0x15, +0x81, 0xda, 0x33, 0x01, 0x89, 0x3e, 0x35, 0xe8, +0x96, 0x52, 0xe6, 0x24, 0x88, 0x44, 0x17, 0xf9, +0x6e, 0xff, 0xee, 0x2c, 0x59, 0xa4, 0x1f, 0x5c, +0x8d, 0x69, 0xfd, 0x45, 0xc2, 0x94, 0x36, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0x85, 0x53, 0x4a, 0xe0, 0x05, 0xb8, 0x3d, +0x80, 0x52, 0x9f, 0xd3, 0x77, 0x01, 0xf3, 0x32, +0xa3, 0x8a, 0x38, 0x26, 0x32, 0xbc, 0x46, 0xf2, +0xae, 0x8f, 0x5f, 0xe1, 0x6a, 0x48, 0x34, 0x55, +0x9d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0xb1, 0x1a, 0x31, 0x71, 0x6a, +0x61, 0x3f, 0x64, 0x09, 0xe5, 0x6a, 0xbc, 0xfc, +0xe3, 0x7c, 0x4b, 0xcb, 0xd3, 0x1c, 0xbc, 0x8d, +0x0b, 0x96, 0xbc, 0xf4, 0x6f, 0xe5, 0x9d, 0x99, +0x6e, 0x97, 0x65, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0xb5, 0x95, 0xe4, +0x16, 0xca, 0xd7, 0x24, 0x26, 0x8d, 0xf5, 0x72, +0xe7, 0xb6, 0x0c, 0x14, 0x8f, 0x72, 0x58, 0x02, +0xb5, 0x2e, 0x0e, 0x0c, 0xf8, 0x58, 0xc9, 0x2c, +0xe5, 0xd0, 0x3a, 0xa3, 0x3d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0xde, +0x18, 0xca, 0xef, 0x78, 0xf1, 0x84, 0x68, 0x34, +0x1f, 0xb6, 0x63, 0x68, 0x57, 0x72, 0x89, 0xfa, +0x83, 0x7b, 0x62, 0xe1, 0xbc, 0x2d, 0x6d, 0xf9, +0x7d, 0x9a, 0x28, 0x6c, 0xe3, 0x16, 0x1a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0xa0, 0xbc, 0x15, 0xc7, 0x2a, 0x1d, 0x44, +0x2a, 0x42, 0x78, 0x0f, 0xa1, 0x87, 0xe2, 0xb1, +0x51, 0xee, 0x93, 0xeb, 0x61, 0x45, 0x34, 0xc8, +0xa5, 0x28, 0x97, 0x93, 0x41, 0xf6, 0x49, 0x02, +0x56, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x59, 0x6d, 0xef, 0xfd, 0xa6, +0x3b, 0xb7, 0xe7, 0xea, 0x9f, 0x1a, 0xb7, 0xf3, +0x25, 0x4a, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x15, 0x1c, 0x5d, 0x7b, +0x7a, 0xe2, 0x5b, 0xf1, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0x8a, 0x29, 0x5b, +0xc0, 0x1f, 0x6e, 0xba, 0xeb, 0x0f, 0x23, 0x94, +0xbd, 0x16, 0xd3, 0x3e, 0x2d, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0xfd, +0xf7, 0xe2, 0x7d, 0x41, 0x96, 0x76, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0x42, +0x28, 0x62, 0xed, 0xc6, 0x2e, 0xad, 0x74, 0xa1, +0xce, 0xd0, 0xa1, 0x6b, 0xbc, 0x93, 0x89, 0x8e, +0xf9, 0x99, 0xc2, 0xb3, 0x0e, 0x4d, 0x05, 0xc8, +0x05, 0x16, 0xfb, 0x82, 0xae, 0x57, 0x96, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x09, 0xa5, 0xd3, 0x68, 0xfa, 0xdb, 0x54, +0x6d, 0xe1, 0xcf, 0x54, 0x92, 0xea, 0x18, 0x04, +0x38, 0x9c, 0xe8, 0x0f, 0x60, 0x3a, 0xdf, 0x12, +0xbc, 0x60, 0xa9, 0x39, 0xa9, 0x31, 0x1e, 0xe7, +0x21, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0x77, 0xf1, 0x23, 0xb6, 0x32, +0x78, 0xf5, 0x91, 0x7f, 0x8e, 0x66, 0x55, 0xa3, +0x57, 0xcd, 0x28, 0xd4, 0x77, 0x88, 0x0e, 0xc9, +0x88, 0x5f, 0x6b, 0xca, 0x7b, 0x60, 0xf7, 0xf2, +0xed, 0x91, 0x12, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0xef, 0x8c, 0xe5, +0x42, 0x64, 0x1e, 0x23, 0xc9, 0xb2, 0xbb, 0xbd, +0x3c, 0x62, 0xea, 0x8a, 0xc4, 0x85, 0xd2, 0xd4, +0xf7, 0xa5, 0x6b, 0x95, 0x92, 0xd1, 0x22, 0xfa, +0xd0, 0xc7, 0x7e, 0x42, 0x2d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0xb2, +0x35, 0xad, 0xda, 0x8a, 0x5d, 0x84, 0x61, 0xbf, +0x9c, 0xdd, 0xb8, 0xd2, 0x1d, 0x29, 0x2e, 0xde, +0xce, 0xe7, 0x1d, 0xa4, 0x25, 0x5d, 0xb3, 0xb1, +0x48, 0xbe, 0xe9, 0x76, 0xce, 0x64, 0x9c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0xb8, 0x1c, 0xdd, 0x51, 0xf3, 0x2d, 0xa0, +0x0a, 0xd2, 0x46, 0x37, 0xbc, 0x50, 0x41, 0xcd, +0x33, 0x26, 0x93, 0x17, 0xb2, 0x0c, 0x2d, 0x01, +0x65, 0x53, 0xc0, 0x20, 0xbb, 0xd3, 0x42, 0x30, +0x19, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0x26, 0xfa, 0x40, 0xfb, 0xb4, +0x5c, 0x6b, 0x56, 0x12, 0x98, 0x2b, 0x6e, 0x8f, +0xe4, 0xdb, 0x43, 0x57, 0x13, 0x0c, 0x91, 0x27, +0xfd, 0x07, 0x67, 0xae, 0xbb, 0x06, 0x71, 0x92, +0x25, 0x8d, 0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0xfa, 0x1a, 0x46, +0x2a, 0xa9, 0xef, 0x0b, 0xce, 0xde, 0xe7, 0x82, +0x3a, 0x36, 0x6f, 0x84, 0x22, 0x5c, 0xc6, 0x2f, +0xda, 0xe5, 0xde, 0xb9, 0x48, 0x36, 0x01, 0x44, +0x25, 0x0b, 0x08, 0x02, 0xde, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0xf5, +0xe8, 0xd0, 0xb3, 0xcd, 0x2e, 0xfd, 0xfe, 0x83, +0xc3, 0x1f, 0x94, 0x30, 0xee, 0x77, 0x83, 0x72, +0x50, 0xc0, 0x5c, 0xcb, 0xef, 0xc0, 0x11, 0x39, +0x8d, 0xb6, 0xb9, 0x38, 0xa7, 0x6e, 0xdb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0x9b, 0x40, 0x7d, 0x27, 0xb4, 0x92, 0x50, +0x0a, 0x89, 0x38, 0xd6, 0x85, 0x4d, 0xc2, 0x88, +0xc4, 0x90, 0x52, 0xae, 0xc0, 0xd9, 0x4c, 0x2e, +0x95, 0xe5, 0x95, 0xd7, 0x56, 0x74, 0x8d, 0xba, +0x7e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0x66, 0x8e, 0x28, 0xcf, 0x55, +0xb6, 0xc1, 0xda, 0x6b, 0x96, 0xeb, 0xac, 0xfc, +0x76, 0x5b, 0x35, 0x4d, 0x47, 0x51, 0x55, 0x89, +0xfb, 0x54, 0x6c, 0xc0, 0x25, 0x90, 0xc1, 0x8b, +0x6c, 0xf9, 0x25, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0x61, 0x8c, 0xe3, +0x0a, 0x9f, 0x9a, 0xb4, 0x56, 0xb2, 0xe0, 0xa6, +0x66, 0x30, 0xa7, 0x0f, 0x10, 0x2e, 0xaf, 0xe6, +0xd0, 0xac, 0xb5, 0x9d, 0xea, 0x30, 0x0d, 0x72, +0xae, 0x36, 0x3d, 0xd1, 0xad, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0xbe, +0x22, 0x0e, 0x50, 0x69, 0xbc, 0x1d, 0x12, 0x83, +0xef, 0x48, 0xe4, 0x1c, 0xda, 0x1a, 0x23, 0xb5, +0x2b, 0xfa, 0x14, 0xfb, 0x0b, 0x88, 0x1c, 0xfb, +0xa6, 0xd8, 0x6d, 0x7a, 0xcf, 0x7b, 0x26, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0xe7, 0x3d, 0xaf, 0xfa, 0xf3, 0x9e, 0x2c, +0x3d, 0x1d, 0x62, 0x1b, 0xd5, 0xe5, 0xf3, 0xf0, +0xa1, 0xfe, 0xb3, 0xaf, 0x0b, 0x3d, 0x1b, 0x43, +0x38, 0x41, 0x1e, 0xa7, 0x54, 0xde, 0xeb, 0xdb, +0x6b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0x53, 0x7a, 0x2d, 0x83, 0x99, +0xf7, 0xdf, 0xa2, 0xb6, 0x9b, 0x60, 0xf5, 0x23, +0x49, 0x72, 0xd0, 0x8d, 0x3c, 0x6a, 0xa8, 0x7a, +0xb9, 0xeb, 0x49, 0xb8, 0xad, 0x21, 0x60, 0xfa, +0x1e, 0x8d, 0xb9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0x2b, 0xa8, 0xb3, +0xbd, 0x12, 0xd3, 0x27, 0x31, 0x7b, 0x3c, 0x30, +0x56, 0xc4, 0x75, 0x4e, 0x2f, 0xb9, 0x64, 0x15, +0xe8, 0x5b, 0xff, 0x8d, 0x79, 0x9b, 0x3a, 0xd1, +0x47, 0x28, 0x9e, 0x03, 0x05, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0xc8, +0x84, 0x30, 0x7e, 0xdb, 0x02, 0xa2, 0xdb, 0x54, +0x4f, 0xa5, 0x22, 0x03, 0x8e, 0x66, 0x47, 0x19, +0xeb, 0x56, 0xab, 0x8c, 0x86, 0xd3, 0xce, 0xb8, +0xa2, 0xc2, 0x8b, 0xfd, 0x02, 0x06, 0xa9, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0x13, 0x9b, 0xfe, 0x23, 0x14, 0xe6, 0x3f, +0x7d, 0x5e, 0x7b, 0x50, 0x84, 0x52, 0x22, 0x47, +0xd0, 0xba, 0x3d, 0xb6, 0x6d, 0x9b, 0xe6, 0x4b, +0x66, 0x7b, 0x83, 0xb3, 0x1e, 0xe3, 0x6c, 0xa2, +0x06, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0x0d, 0xf4, 0x0c, 0xff, 0xa0, +0x58, 0x48, 0xf5, 0xc9, 0x44, 0x3a, 0x75, 0x82, +0x9d, 0x5b, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x1e, 0x37, 0xf6, 0x86, +0xf1, 0xa0, 0x03, 0x75, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0x77, 0x93, 0xd5, +0x4c, 0x9b, 0x69, 0x97, 0x1b, 0xf2, 0xc7, 0x24, +0x48, 0x79, 0x18, 0x85, 0xc6, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0xc8, +0xeb, 0xd1, 0xa0, 0x1a, 0xf3, 0xe2, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0x2c, +0x9b, 0xc0, 0xcd, 0x22, 0x38, 0xe1, 0xb8, 0x4c, +0xb0, 0x34, 0xb8, 0x2f, 0x93, 0x06, 0x65, 0x16, +0x2f, 0x2e, 0xfc, 0x75, 0xad, 0x55, 0x0a, 0x8b, +0xaa, 0x16, 0xe9, 0xf1, 0xa9, 0xaa, 0x66, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0x82, 0x45, 0x89, 0x71, 0x74, 0x7a, 0x50, +0xbd, 0x45, 0x9f, 0x90, 0xc6, 0xaf, 0xcf, 0x94, +0x0b, 0xea, 0x8b, 0x74, 0xb5, 0x18, 0x80, 0xe9, +0x85, 0x27, 0xa4, 0xbe, 0x59, 0x31, 0x30, 0xe1, +0x2f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0x09, 0xc6, 0xfb, 0xaf, 0x25, +0x81, 0x01, 0xb5, 0x93, 0x55, 0x1d, 0xce, 0xde, +0x20, 0x4b, 0x4d, 0x94, 0xfd, 0xb5, 0x21, 0x1d, +0xf1, 0x8d, 0x0f, 0x0f, 0x17, 0x96, 0x63, 0x49, +0x6a, 0x9e, 0x92, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0xf0, 0x47, 0xd2, +0x11, 0xd0, 0x13, 0x9e, 0xce, 0x53, 0xc5, 0x01, +0xf5, 0x88, 0x46, 0x9a, 0x3e, 0x94, 0x4c, 0xc7, +0x32, 0x64, 0x84, 0xd2, 0x83, 0xf9, 0x08, 0xe6, +0xb2, 0x84, 0x21, 0x3c, 0xb1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0x3a, +0x0c, 0x94, 0x4e, 0x24, 0x66, 0x68, 0xad, 0xa9, +0x71, 0xc4, 0x12, 0xb8, 0x34, 0x8d, 0x73, 0x82, +0xae, 0xdd, 0x8c, 0xce, 0xc2, 0x9d, 0x7d, 0x86, +0x92, 0xe6, 0xfa, 0x7b, 0x6a, 0x66, 0x98, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0xce, 0xef, 0x41, 0xb2, 0xab, 0x08, 0xce, +0x63, 0x63, 0xb1, 0x63, 0x06, 0x97, 0xdb, 0xc3, +0x97, 0x3d, 0x98, 0x4e, 0x22, 0xe6, 0xa6, 0x3e, +0x64, 0xf1, 0x7e, 0x3d, 0x02, 0x9d, 0xe9, 0x47, +0x42, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0x6a, 0x96, 0xe1, 0x84, 0x31, +0xc9, 0x6f, 0x36, 0xcf, 0x6f, 0x51, 0x39, 0xdb, +0x81, 0xf8, 0xa6, 0xf4, 0xe9, 0x13, 0x48, 0xef, +0x91, 0x73, 0x88, 0x54, 0x55, 0x4b, 0x13, 0xf0, +0x16, 0xbd, 0x2b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0x44, 0x0b, 0x88, +0x8d, 0x18, 0x11, 0x91, 0xc5, 0x7e, 0x85, 0x73, +0x87, 0xdb, 0x3c, 0x9d, 0x5f, 0xcd, 0xaa, 0x6a, +0xad, 0xd3, 0x11, 0x2d, 0x6b, 0xa3, 0x47, 0x6f, +0xdf, 0xfa, 0xef, 0xa0, 0x45, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0xb9, +0x16, 0x84, 0xaf, 0x36, 0x55, 0x68, 0x6f, 0xba, +0xdb, 0x12, 0xc5, 0xf5, 0x1b, 0x1c, 0x19, 0x8b, +0x75, 0xed, 0x20, 0x50, 0x70, 0x88, 0x9d, 0x65, +0x45, 0xe3, 0x99, 0x7a, 0x40, 0x73, 0xf2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0x1f, 0x16, 0x4f, 0x59, 0x42, 0x91, 0xa7, +0xb4, 0x24, 0x35, 0x7e, 0x0e, 0x6b, 0x26, 0x88, +0x7d, 0x20, 0xac, 0xd3, 0x32, 0x3c, 0x3d, 0x6d, +0xbd, 0x47, 0x5c, 0x34, 0x37, 0x46, 0xff, 0xee, +0xf7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0xf3, 0x31, 0x60, 0xb5, 0x7d, +0x50, 0xd7, 0xf9, 0xf9, 0xb0, 0x15, 0x19, 0xc5, +0xfb, 0xd2, 0x4e, 0x5a, 0xc2, 0x4f, 0x4f, 0x33, +0x43, 0x4c, 0x9b, 0xec, 0xe4, 0x81, 0x31, 0x7b, +0x19, 0x8b, 0xdd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x6e, 0x82, 0xb5, +0x6e, 0xfd, 0xa3, 0x70, 0x57, 0xa4, 0xe9, 0x4d, +0xe8, 0xdb, 0xda, 0x81, 0x92, 0xf9, 0xf8, 0x9a, +0x2a, 0xaf, 0x22, 0xe5, 0x47, 0xb6, 0x28, 0x0c, +0xfc, 0x0a, 0x8a, 0xdf, 0xe1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x11, +0x5c, 0x2c, 0x19, 0xa8, 0x5f, 0x36, 0xe0, 0x97, +0xb8, 0x0e, 0xb9, 0x01, 0x8f, 0x4d, 0x29, 0xc0, +0x84, 0x85, 0xaf, 0x1e, 0x4d, 0x02, 0x43, 0xcc, +0x60, 0xcd, 0x2d, 0xd1, 0xb5, 0x5d, 0xcd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0x8b, 0x2d, 0x4f, 0xba, 0x05, 0xe1, 0x17, +0xa9, 0x45, 0x27, 0x77, 0x77, 0x9d, 0x1d, 0x4b, +0xae, 0xe3, 0x54, 0x53, 0xaa, 0x07, 0x42, 0x61, +0x52, 0x3a, 0x7f, 0x95, 0xdf, 0xce, 0xdb, 0x46, +0xaa, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0x61, 0x01, 0xd1, 0x8e, 0xbd, +0x72, 0x7b, 0x92, 0xf8, 0x9c, 0x86, 0x73, 0xb2, +0x89, 0x56, 0x11, 0x9a, 0x82, 0xd8, 0xcc, 0x43, +0x18, 0xa4, 0xf9, 0xc1, 0x97, 0xf8, 0x3e, 0x72, +0x7b, 0x2e, 0x6f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0x81, 0x6c, 0xad, +0x90, 0xcd, 0xad, 0x59, 0x5d, 0x02, 0xe9, 0x7f, +0x3f, 0xc9, 0x5a, 0x91, 0x60, 0x42, 0x3e, 0x2d, +0x08, 0x6f, 0x4a, 0x2b, 0xc1, 0x9d, 0x3c, 0x64, +0x54, 0x03, 0x4a, 0x02, 0x3a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0xb4, +0x4f, 0xed, 0x34, 0x42, 0xc0, 0xc7, 0x45, 0xe8, +0x05, 0x89, 0xe8, 0x0d, 0x87, 0xa3, 0x8d, 0x3f, +0x7b, 0x88, 0xc2, 0x11, 0xb8, 0xf3, 0x9e, 0xed, +0xd4, 0x8c, 0x93, 0x18, 0x4a, 0x5d, 0x55, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0x84, 0x43, 0x95, 0x95, 0xa3, 0xe4, 0xdc, +0x22, 0x3e, 0x17, 0x9b, 0x33, 0x58, 0x52, 0x9b, +0x67, 0x8b, 0x69, 0xf1, 0x88, 0x2e, 0x56, 0x7c, +0x76, 0xb6, 0xbc, 0x5d, 0x37, 0x87, 0xad, 0xa5, +0x10, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0xe9, 0xe9, 0xa7, 0xa4, 0xb3, +0x0b, 0x6c, 0x68, 0x30, 0xf3, 0x5d, 0xd0, 0x3d, +0xdd, 0xed, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x25, 0xe3, 0xe0, 0x92, +0xd3, 0x26, 0x14, 0x18, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0x22, 0xd6, 0xf0, +0x04, 0xab, 0x65, 0xf6, 0xb9, 0x41, 0x39, 0xfe, +0x7e, 0x7d, 0x8f, 0x64, 0x8b, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0xe9, +0xe0, 0x19, 0x0b, 0x93, 0xa1, 0x0c, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0x47, +0x4a, 0x16, 0x4f, 0xa2, 0x2f, 0x59, 0x3e, 0xa5, +0x4b, 0x4d, 0x10, 0x49, 0x50, 0xb1, 0xc4, 0x81, +0x99, 0x4a, 0x94, 0xb1, 0xd1, 0x92, 0x0c, 0xb4, +0x25, 0x65, 0x84, 0xa5, 0x98, 0x94, 0xc8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0x16, 0x9f, 0xb2, 0xb4, 0x3f, 0xef, 0x2f, +0x8f, 0x86, 0x05, 0xc8, 0x88, 0x9c, 0x88, 0xc3, +0x80, 0xdd, 0xb6, 0x74, 0xdb, 0x59, 0x69, 0x35, +0x48, 0x13, 0xe6, 0xaf, 0xdf, 0x28, 0xcb, 0x0b, +0x8e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0xfc, 0xbb, 0x9c, 0xa5, 0x96, +0x14, 0x42, 0xa0, 0xcc, 0x51, 0x17, 0x46, 0xa7, +0x47, 0x79, 0x5b, 0x78, 0xe7, 0x04, 0xb7, 0x1f, +0x33, 0xd6, 0x15, 0x68, 0xd6, 0x69, 0x39, 0x2b, +0x33, 0x49, 0x29, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0x15, 0xfd, 0x13, +0xf2, 0x28, 0x93, 0x1d, 0x5b, 0xbe, 0xbc, 0x02, +0x11, 0xde, 0x86, 0x52, 0x19, 0x4c, 0xd2, 0xc2, +0x5c, 0x12, 0xe2, 0x0b, 0x33, 0x76, 0x0c, 0xb8, +0x8b, 0x73, 0x11, 0x0d, 0x17, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0x58, +0xa9, 0xa0, 0xee, 0x6b, 0xf4, 0xf5, 0x59, 0xc2, +0xe1, 0x1c, 0x47, 0x5c, 0x85, 0x15, 0x60, 0x24, +0x2c, 0x80, 0x76, 0x88, 0xd3, 0xc2, 0xa0, 0x93, +0x14, 0xbb, 0xea, 0x0a, 0x3d, 0x78, 0xb0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0x0c, 0x96, 0xe9, 0x57, 0x1b, 0x5c, 0xdf, +0x82, 0x29, 0x89, 0x3d, 0xcf, 0x1c, 0x86, 0x04, +0x55, 0x57, 0x5d, 0xfd, 0x9d, 0xe8, 0x0c, 0x79, +0x06, 0x36, 0x45, 0x42, 0x38, 0x13, 0x72, 0x82, +0x77, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0xf6, 0x90, 0xee, 0x40, 0xa2, +0x5e, 0xee, 0xf4, 0x7b, 0x34, 0xd4, 0x48, 0xbe, +0x55, 0xb9, 0xf7, 0x03, 0xc0, 0x57, 0x0e, 0x0e, +0xa7, 0xda, 0x44, 0x47, 0x69, 0xe3, 0xf7, 0x75, +0x4d, 0x3b, 0xac, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x1a, 0x39, 0x9a, +0xc3, 0xd9, 0x00, 0xf1, 0xdc, 0xc5, 0x24, 0x07, +0x93, 0x8c, 0x47, 0xa5, 0xd9, 0xd7, 0x7d, 0x1b, +0xd7, 0x51, 0x97, 0x36, 0x24, 0x41, 0x59, 0x72, +0x4d, 0xb8, 0x8e, 0x3a, 0x9d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0x6a, +0xb9, 0xfa, 0xdb, 0xc8, 0xe5, 0x4c, 0x3c, 0x6a, +0xd8, 0xf2, 0x4e, 0x2c, 0x81, 0x42, 0xcb, 0xbe, +0xaf, 0x93, 0x72, 0x5e, 0x94, 0xfd, 0x58, 0x09, +0x61, 0x27, 0x62, 0xe9, 0xbc, 0x99, 0xb6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0x8b, 0x61, 0x59, 0xd8, 0x5a, 0x9f, 0x34, +0xff, 0xc9, 0xc2, 0xeb, 0x3c, 0x84, 0xc5, 0x98, +0xae, 0x00, 0xb9, 0x57, 0x0e, 0x0e, 0x65, 0x6e, +0xa1, 0xbd, 0x1a, 0xdc, 0xa3, 0x7d, 0x6b, 0x6f, +0x3b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0x50, 0xeb, 0x7f, 0x79, 0xe4, +0x38, 0x85, 0xc7, 0xf6, 0x3a, 0x8b, 0x5f, 0x8c, +0xb4, 0x30, 0x4a, 0xb0, 0x3b, 0x65, 0x04, 0x86, +0xa9, 0x4e, 0x39, 0xcd, 0xbc, 0x16, 0x7b, 0x88, +0xc7, 0x7b, 0x9a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0xe3, 0x3a, 0x4c, +0xe3, 0xce, 0x57, 0xfe, 0xb0, 0x7f, 0x1b, 0xae, +0x33, 0xfb, 0x22, 0xea, 0xdf, 0x3d, 0xc8, 0x86, +0xdf, 0xad, 0x2e, 0x8e, 0xfd, 0x49, 0x7c, 0x4e, +0x46, 0x3b, 0xfc, 0x5a, 0x12, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0x62, +0x7a, 0xc7, 0x1e, 0x0f, 0x6d, 0xc0, 0xff, 0xa2, +0xf2, 0x21, 0x98, 0xa8, 0xd5, 0xe5, 0x36, 0xf9, +0xc6, 0xee, 0xc3, 0x0d, 0x21, 0x4a, 0x6c, 0xef, +0x30, 0xa2, 0xac, 0x1c, 0xa3, 0x27, 0xfb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0xf5, 0xcc, 0xc4, 0x3a, 0x3e, 0xb0, 0x70, +0x74, 0x62, 0x40, 0x0b, 0x45, 0xe5, 0xfe, 0x76, +0x1a, 0x18, 0x46, 0x15, 0x12, 0xa7, 0xd4, 0xcd, +0xdc, 0x60, 0xe7, 0xb2, 0x55, 0x97, 0x96, 0x57, +0xcf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0x95, 0x22, 0x1b, 0x12, 0x1b, +0xc2, 0xf9, 0x16, 0x08, 0xc7, 0x67, 0x49, 0x6f, +0x7f, 0x11, 0x46, 0x7f, 0x19, 0xcc, 0xbf, 0x01, +0x66, 0xe2, 0x8c, 0xc9, 0x10, 0xd7, 0x5e, 0xdf, +0x96, 0x74, 0xf8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0xfc, 0xac, 0x0e, +0x8c, 0xb8, 0x98, 0x7b, 0xb6, 0xf1, 0x86, 0xfc, +0xfe, 0x8b, 0xa7, 0xcc, 0x52, 0xa2, 0x6e, 0xa3, +0x50, 0x8c, 0xd9, 0x16, 0x98, 0x21, 0x88, 0x2e, +0xff, 0xbe, 0xd1, 0xff, 0x85, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0xb9, +0x97, 0xbc, 0xd3, 0xb1, 0x59, 0x09, 0xd2, 0x17, +0xb6, 0x8b, 0xa6, 0xd0, 0xfa, 0xc8, 0xef, 0x00, +0xc0, 0x4a, 0x05, 0xeb, 0xab, 0x55, 0x52, 0xfc, +0x24, 0x75, 0x60, 0x19, 0xf7, 0x85, 0x88, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0x25, 0xe6, 0x8a, 0x43, 0x31, 0x11, 0x82, +0x2a, 0xbc, 0x02, 0x6e, 0x7f, 0xd3, 0x91, 0xcc, +0xab, 0x44, 0x6c, 0xe3, 0x0c, 0xc2, 0x80, 0x6c, +0x0b, 0x03, 0x98, 0x5d, 0x14, 0xf3, 0xbe, 0xce, +0x29, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0xb1, 0x53, 0x5c, 0x13, 0x7a, +0xb6, 0xe8, 0x03, 0x65, 0xec, 0x15, 0x8b, 0xcd, +0x4a, 0xf4, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x2a, 0x59, 0x2a, 0xf4, +0xd5, 0x16, 0x8b, 0x6f, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0x93, 0x07, 0x33, +0xd2, 0x3e, 0xde, 0x0c, 0xe0, 0x59, 0x75, 0x2b, +0x10, 0x24, 0xe1, 0xb2, 0x99, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xec, 0xbb, +0x3b, 0xca, 0x44, 0x92, 0x9a, 0x92, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0xed, +0xd0, 0x87, 0x94, 0x9e, 0x49, 0x63, 0x56, 0x82, +0xc9, 0xe8, 0x5c, 0xa0, 0x08, 0x0f, 0xe0, 0x66, +0x2f, 0xa1, 0x85, 0x5e, 0xcf, 0xf4, 0x90, 0x88, +0x00, 0x7f, 0xa3, 0x76, 0xe8, 0xc0, 0x6b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0x4a, 0x2f, 0x68, 0xe9, 0x19, 0x13, 0x26, +0xc4, 0x0a, 0x9b, 0xb2, 0x54, 0x23, 0x08, 0xa4, +0x10, 0x27, 0x8c, 0x57, 0x86, 0x6d, 0xa2, 0x72, +0x28, 0x3e, 0xed, 0x5f, 0xf7, 0x96, 0x2b, 0xdb, +0x59, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x72, 0xc9, 0x69, 0xc1, 0x58, +0x92, 0x5e, 0x00, 0x40, 0xf2, 0x5a, 0x48, 0x70, +0xe3, 0x5b, 0xdd, 0x21, 0xfc, 0x83, 0xcd, 0xb1, +0x45, 0x3d, 0x4b, 0x41, 0x14, 0xf3, 0x01, 0xfc, +0x99, 0x13, 0x7d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0xe3, 0x95, 0x03, +0x16, 0x57, 0xee, 0x6f, 0x4f, 0x7e, 0x41, 0x92, +0x33, 0x62, 0xe0, 0x4c, 0x00, 0x44, 0xf1, 0x33, +0xd1, 0x1d, 0xa7, 0xa6, 0xb2, 0x75, 0xbf, 0x41, +0xfd, 0x3d, 0x4c, 0x87, 0xc1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0xbe, +0x02, 0x8c, 0xf1, 0xde, 0xfa, 0x7a, 0xe3, 0xd4, +0x10, 0xe6, 0x84, 0x84, 0x57, 0x96, 0x5e, 0x0e, +0xd4, 0x8f, 0x0a, 0x23, 0xfe, 0x5f, 0xee, 0xde, +0xae, 0xff, 0x28, 0x38, 0xa9, 0xc9, 0x5a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0xe7, 0xdd, 0xca, 0xfc, 0x9f, 0x1e, 0x9a, +0x4e, 0x0f, 0xd7, 0x23, 0x7d, 0x11, 0x21, 0x48, +0x9f, 0x91, 0xda, 0x19, 0xa3, 0x10, 0xeb, 0x5c, +0xeb, 0x8a, 0xfd, 0x7b, 0xf7, 0xc5, 0x52, 0x19, +0x71, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0x66, 0x08, 0xbf, 0x4f, 0x43, +0x30, 0xe7, 0xab, 0xfa, 0xaf, 0x61, 0xe8, 0x44, +0xb5, 0x7a, 0xde, 0xf0, 0x90, 0x4b, 0x47, 0xe6, +0xbe, 0xa3, 0x73, 0x2c, 0xd8, 0x1e, 0xf0, 0xde, +0x3a, 0xf8, 0x0d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0x19, 0x49, 0xee, +0x8c, 0x82, 0xf3, 0xdc, 0x81, 0x0b, 0x57, 0x04, +0xaa, 0x84, 0xbd, 0xca, 0xd0, 0x44, 0x5d, 0xb5, +0xe1, 0x08, 0x40, 0x68, 0x7f, 0x21, 0x0c, 0x52, +0x93, 0x77, 0xab, 0x36, 0xa5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0x34, +0xb6, 0x93, 0xee, 0xb6, 0xd8, 0x00, 0x0d, 0xfc, +0xb9, 0x0e, 0x6b, 0xd4, 0x2d, 0xa6, 0xe4, 0xf5, +0x18, 0x08, 0xab, 0x0b, 0x11, 0x29, 0x27, 0x11, +0xc4, 0x01, 0x45, 0xea, 0xa8, 0x30, 0x9d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0x8a, 0x70, 0x51, 0x63, 0x22, 0x01, 0x95, +0xb6, 0xe4, 0x5d, 0xc7, 0x45, 0xa4, 0x4e, 0x5b, +0x25, 0x67, 0x37, 0x2f, 0x83, 0xa1, 0xd0, 0xc8, +0xdf, 0x8a, 0xdf, 0x7b, 0x3f, 0x65, 0x7e, 0x91, +0x65, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0x1c, 0xfb, 0xd2, 0x4c, 0x5d, +0xe4, 0xad, 0xa6, 0xce, 0x1a, 0xfa, 0x1c, 0x2e, +0x28, 0xa4, 0x2d, 0x78, 0x6e, 0xfd, 0x81, 0x3f, +0xc1, 0x9e, 0xe8, 0xaf, 0x12, 0xce, 0x89, 0xd2, +0x94, 0xce, 0x53, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0x31, 0xee, 0x45, +0x2a, 0x45, 0x77, 0x69, 0xcf, 0x43, 0x83, 0xa9, +0x9c, 0x0f, 0xbf, 0xe8, 0xec, 0x26, 0x91, 0x80, +0x94, 0x04, 0x52, 0x1a, 0x9c, 0xe2, 0x67, 0x11, +0xfe, 0xa4, 0xa6, 0xb7, 0x03, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0xff, +0x12, 0xad, 0x13, 0x3e, 0x96, 0x25, 0x0d, 0x8b, +0x9f, 0x7b, 0x41, 0x42, 0xc7, 0x31, 0xfc, 0x9b, +0x56, 0xa1, 0xdf, 0xa9, 0xd9, 0xab, 0x06, 0xd4, +0x72, 0x7f, 0xb8, 0xe3, 0x93, 0xc8, 0xae, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0xf0, 0xda, 0x55, 0x2e, 0x3f, 0xda, 0x16, +0xf3, 0x63, 0x8d, 0xb4, 0x33, 0x1f, 0xd0, 0x8c, +0xda, 0xfe, 0x2b, 0x02, 0xf0, 0xb9, 0xfe, 0x5c, +0x02, 0x41, 0x84, 0x1e, 0x30, 0x23, 0xd5, 0xbc, +0x83, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0x7c, 0x3a, 0x8b, 0xfe, 0xb7, +0x88, 0xb1, 0x35, 0x14, 0x23, 0x54, 0x0b, 0x95, +0x36, 0x16, 0x01, 0x22, 0xb0, 0x73, 0xeb, 0x22, +0x7b, 0x35, 0xb8, 0x99, 0x97, 0xd4, 0x22, 0x6f, +0x63, 0x1b, 0x58, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0xa4, 0xad, 0xb7, +0xdf, 0x82, 0x0c, 0xf3, 0x76, 0x17, 0xdf, 0xe1, +0x2e, 0x4e, 0x44, 0xad, 0xa5, 0xa7, 0x32, 0x8f, +0x5f, 0x3d, 0x5c, 0x83, 0x78, 0x16, 0x1c, 0x19, +0x2e, 0x42, 0x16, 0xcc, 0xbc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0x98, +0xe8, 0xf9, 0x09, 0x9e, 0x6d, 0xc1, 0xf5, 0x10, +0x8a, 0x97, 0x6b, 0xc1, 0x25, 0xd2, 0xe1, 0x1f, +0x89, 0xde, 0x54, 0x07, 0x7e, 0xbe, 0x1d, 0xb2, +0x9c, 0x62, 0xa0, 0x1b, 0x62, 0x84, 0x50, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0x5a, 0x0b, 0xe7, 0x35, 0x6d, 0x32, 0x9e, +0xb7, 0xfe, 0x00, 0x31, 0x8c, 0xea, 0xd7, 0x82, +0x36, 0x2c, 0xf3, 0xfa, 0xd7, 0xa1, 0x28, 0xe8, +0x1d, 0xf2, 0x2f, 0xe2, 0x4b, 0x4a, 0x53, 0xec, +0x01, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x1f, 0x80, 0xf3, 0x26, 0x9c, +0x60, 0xd7, 0xba, 0xa6, 0x2b, 0xf9, 0x5a, 0xb1, +0xf1, 0xc9, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xd7, 0x14, 0x03, 0xd8, +0x36, 0x87, 0xcd, 0x36, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0xcd, 0xba, 0x3a, +0x3d, 0xe5, 0x76, 0x29, 0xae, 0x23, 0xaa, 0xa2, +0xbb, 0xde, 0xe3, 0x6c, 0x4a, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0xed, +0xbd, 0xb9, 0x8e, 0xf8, 0xdd, 0xb0, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0xd6, +0x10, 0x96, 0xfb, 0xc8, 0x0f, 0xfa, 0x0b, 0xd1, +0x60, 0x3c, 0x3e, 0x13, 0x5f, 0x81, 0x93, 0xe5, +0x8c, 0x0d, 0x54, 0xdc, 0x64, 0x55, 0x11, 0x57, +0xf2, 0x0b, 0xaa, 0x6d, 0xd3, 0x93, 0xf7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0x0d, 0x80, 0xe1, 0xc9, 0xfe, 0xb0, 0x39, +0xe2, 0x84, 0x8e, 0x6f, 0x47, 0x69, 0x30, 0xa8, +0xe9, 0x97, 0x14, 0x12, 0xe1, 0xa1, 0xed, 0x11, +0xc6, 0xbe, 0xe0, 0x1d, 0xb1, 0xf8, 0x3b, 0x4b, +0x61, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0x0d, 0x24, 0x90, 0x13, 0x25, +0xca, 0xfd, 0x0f, 0x1b, 0xf7, 0xa1, 0x2c, 0xad, +0x5f, 0x09, 0x03, 0x6c, 0x5d, 0x5d, 0x43, 0x25, +0x49, 0xe9, 0xf5, 0x55, 0xac, 0xaf, 0x33, 0x92, +0x8a, 0x9b, 0xc3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0x56, 0xe7, 0x4f, +0x1c, 0x8b, 0xe6, 0x3d, 0x0f, 0xf9, 0x6f, 0xd1, +0x8c, 0xe9, 0x93, 0x3c, 0x37, 0x68, 0x0d, 0x7e, +0xc2, 0xc8, 0x63, 0x9f, 0x51, 0xf6, 0xcb, 0xd7, +0x7f, 0x7e, 0x83, 0x13, 0x2d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0x15, +0xf0, 0x78, 0xde, 0xf0, 0x04, 0xfe, 0x96, 0x5a, +0x94, 0x0c, 0x87, 0x9f, 0x59, 0x53, 0xc3, 0x1f, +0x48, 0xfb, 0x39, 0xcc, 0x2b, 0x67, 0xf4, 0x75, +0x87, 0x09, 0x15, 0x8a, 0x54, 0x3e, 0x42, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0x75, 0x0e, 0xab, 0xc8, 0x5e, 0xe8, 0xc6, +0x41, 0xd5, 0xd3, 0x27, 0x6b, 0x8e, 0x46, 0xfe, +0x99, 0xb6, 0xff, 0xb0, 0xaf, 0x74, 0x76, 0x8f, +0x2f, 0x93, 0x39, 0x11, 0xc9, 0xdb, 0x72, 0xf2, +0xa4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0xad, 0x58, 0x66, 0x3b, 0xed, +0xe5, 0x77, 0x30, 0x56, 0xd2, 0x39, 0x88, 0x9d, +0x72, 0xf1, 0xd0, 0x05, 0xbb, 0x4e, 0xf4, 0x66, +0xe0, 0xb0, 0xa0, 0x2f, 0xa2, 0x42, 0x30, 0xcf, +0x6a, 0x31, 0x67, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0x12, 0x72, 0x54, +0x4d, 0x42, 0x9a, 0xc1, 0x1f, 0x9b, 0x56, 0x38, +0x63, 0x67, 0xf1, 0x7a, 0xd2, 0x62, 0xb0, 0x57, +0xd3, 0xba, 0x8a, 0xb1, 0x9d, 0x48, 0x33, 0xdd, +0x0f, 0xe7, 0xee, 0xaf, 0x5f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0x24, +0x2c, 0xc3, 0x3d, 0xc0, 0xa2, 0xed, 0x6b, 0x5a, +0x48, 0x6f, 0x69, 0xd5, 0x21, 0x45, 0xae, 0x5c, +0x58, 0x90, 0x2d, 0x59, 0x10, 0x65, 0x7a, 0x31, +0x88, 0xf9, 0x2e, 0x01, 0xa3, 0x36, 0x03, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0xe4, 0xa6, 0x3c, 0x0d, 0x2f, 0x58, 0x67, +0x51, 0x26, 0x3f, 0xa1, 0x2a, 0x7f, 0x16, 0xff, +0xac, 0xbb, 0x01, 0x9c, 0xe5, 0xed, 0xd0, 0xa6, +0xe2, 0x79, 0x9b, 0xc1, 0x8e, 0x4a, 0x15, 0x4f, +0xde, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0xce, 0xee, 0x67, 0x8c, 0xe6, +0xfb, 0x9d, 0xce, 0x7b, 0x05, 0xc1, 0xb7, 0x32, +0xe5, 0x1f, 0xf8, 0x14, 0x06, 0x4d, 0x04, 0x38, +0x73, 0x90, 0xfa, 0xeb, 0x73, 0x39, 0x40, 0x3c, +0x53, 0x4e, 0xd1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0x59, 0xef, 0x63, +0xa6, 0xe3, 0xe1, 0x60, 0xdb, 0x6d, 0xac, 0x0b, +0x8c, 0xda, 0xdb, 0xbb, 0x9e, 0x43, 0xb5, 0x8d, +0xf1, 0xb0, 0xd5, 0xda, 0x36, 0x82, 0x8c, 0x68, +0xe8, 0x0b, 0x03, 0xb8, 0xdb, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0x96, +0x97, 0xf6, 0x9d, 0x14, 0x0c, 0x13, 0x2f, 0x2a, +0xd6, 0x33, 0x9f, 0x12, 0xe5, 0xf8, 0x3f, 0x9b, +0x38, 0x22, 0x57, 0x5d, 0x61, 0xac, 0xa9, 0xc3, +0x10, 0xb8, 0xd6, 0x30, 0xb3, 0x7d, 0x12, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0xd4, 0x0b, 0x4a, 0xff, 0x7d, 0x90, 0x0e, +0x7c, 0x73, 0x3e, 0xbb, 0x68, 0x60, 0x1b, 0x67, +0x5d, 0x78, 0x03, 0x50, 0xec, 0x9c, 0xcf, 0x49, +0xea, 0xec, 0xe1, 0x0f, 0x79, 0xdc, 0x3b, 0x37, +0x9f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0x9c, 0x84, 0xef, 0x8d, 0xc9, +0x57, 0x8e, 0x87, 0x3e, 0xf8, 0x97, 0x03, 0x8d, +0x15, 0xbc, 0x4c, 0x1e, 0xaf, 0x75, 0x0c, 0x20, +0x8a, 0xad, 0x49, 0xc9, 0xd2, 0x3b, 0xea, 0x75, +0xcb, 0xd5, 0x96, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0xd9, 0x95, 0xc9, +0x99, 0xb2, 0xeb, 0x3e, 0x7f, 0x05, 0xf2, 0xdb, +0x4b, 0x64, 0xfd, 0x50, 0x72, 0x78, 0xb4, 0x95, +0x15, 0x10, 0x52, 0x7b, 0xfe, 0x1a, 0x72, 0xed, +0xf8, 0xba, 0x8e, 0x07, 0x93, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0xeb, +0x3e, 0x15, 0x63, 0xc2, 0x5b, 0x19, 0x94, 0x73, +0x1d, 0x31, 0x8e, 0x22, 0x65, 0x90, 0x92, 0x71, +0x3f, 0x84, 0xf1, 0x35, 0x41, 0xd2, 0xba, 0x28, +0x5d, 0xf3, 0x2d, 0xaa, 0xf1, 0x3d, 0x26, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0x7a, 0x8e, 0xd5, 0xab, 0x5e, 0x42, 0xc8, +0xce, 0x36, 0xda, 0x70, 0x49, 0x79, 0x4c, 0xf6, +0x14, 0x4f, 0xc7, 0x17, 0x5d, 0xf5, 0x1f, 0xb3, +0x63, 0xd2, 0xad, 0x07, 0x10, 0x64, 0x43, 0xb1, +0x16, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0xc0, 0xf7, 0x1a, 0xeb, 0x95, +0xff, 0xb6, 0xbf, 0x72, 0x4d, 0xca, 0x26, 0x40, +0x89, 0xc7, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xf3, 0x65, 0x8a, 0x33, +0x04, 0x83, 0xa3, 0x55, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x20, 0x53, 0xe2, +0x16, 0xe9, 0xf8, 0xf9, 0x64, 0x24, 0x08, 0x27, +0x4e, 0x8a, 0x43, 0xbc, 0x40, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0xcb, +0x55, 0x2c, 0xe4, 0xc2, 0xfa, 0x1d, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0x87, +0xff, 0x47, 0xd8, 0x8d, 0xae, 0x7f, 0xee, 0xb5, +0xd0, 0x76, 0x5c, 0x81, 0xe3, 0xdf, 0x66, 0x69, +0x01, 0x13, 0x1e, 0x89, 0xae, 0x54, 0x7d, 0xaf, +0x1a, 0xfe, 0xa6, 0xdf, 0x59, 0x3e, 0x2d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0x1d, 0xee, 0x06, 0x85, 0x5e, 0xa5, 0x53, +0x99, 0xb8, 0x9b, 0x6e, 0x4d, 0x53, 0xe2, 0x51, +0x0f, 0x92, 0x13, 0x66, 0xbd, 0xe4, 0xb8, 0x05, +0x22, 0x73, 0xee, 0xf1, 0xce, 0x7f, 0x05, 0x87, +0x8c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0xaf, 0x10, 0x6c, 0x39, 0xbc, +0xac, 0x7f, 0x31, 0x85, 0x47, 0x74, 0x5c, 0x00, +0xdc, 0x48, 0x4d, 0xb8, 0x35, 0x41, 0xa7, 0x58, +0x15, 0xa9, 0xc0, 0x61, 0x2c, 0x4b, 0x3c, 0x43, +0x09, 0x76, 0xab, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0xe5, 0x7f, 0x3d, +0x1c, 0xdc, 0x5a, 0x36, 0xaa, 0x9e, 0x2e, 0x50, +0x3b, 0x9b, 0x29, 0x12, 0x63, 0xd5, 0xc4, 0xbd, +0x3a, 0x0f, 0xef, 0x89, 0xe9, 0xa0, 0xd2, 0xbf, +0x25, 0xe6, 0xd8, 0x83, 0x20, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0x08, +0x1b, 0x6f, 0xab, 0x76, 0x84, 0x8b, 0xbd, 0x56, +0x52, 0x59, 0x70, 0x3f, 0x88, 0x68, 0x33, 0x4b, +0xd8, 0x0f, 0x08, 0x20, 0xd2, 0xed, 0x9e, 0x8d, +0xb2, 0xa1, 0x1d, 0x0c, 0x19, 0x04, 0x8a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0xea, 0x38, 0x30, 0x0c, 0xf2, 0x25, 0x1c, +0x32, 0xb1, 0x9e, 0x49, 0xbf, 0x1a, 0xc4, 0x5b, +0x70, 0x44, 0x43, 0xb8, 0x93, 0xff, 0xa1, 0x29, +0x89, 0x8e, 0xac, 0xf4, 0x97, 0x0a, 0x87, 0xc5, +0x49, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0x58, 0x96, 0x6b, 0x21, 0x1a, +0xb2, 0xf1, 0x32, 0x93, 0x15, 0xef, 0xc9, 0xea, +0xa3, 0x00, 0xc2, 0x93, 0xa7, 0x7e, 0xe0, 0xad, +0x0c, 0xdd, 0xa3, 0x42, 0x88, 0x3f, 0xf7, 0x04, +0x65, 0xe2, 0x99, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x8f, 0x6e, 0x61, +0x4f, 0x97, 0xe9, 0xfb, 0xfa, 0x54, 0x4d, 0x42, +0xca, 0xad, 0x71, 0x54, 0x30, 0xcc, 0xd8, 0xac, +0x8d, 0xa5, 0xbe, 0x76, 0xb5, 0xd9, 0x7b, 0xea, +0xe5, 0x9c, 0x6b, 0xa6, 0x0a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x1c, +0xc3, 0x6e, 0x5a, 0xf6, 0x8c, 0x4e, 0xab, 0xcc, +0x41, 0x74, 0x50, 0xef, 0xa6, 0x80, 0xb0, 0x19, +0x58, 0x56, 0xac, 0x55, 0xd3, 0x82, 0x25, 0x76, +0xfe, 0x76, 0x6d, 0x20, 0x6e, 0xe4, 0xed, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0x1b, 0x08, 0xd5, 0xd0, 0xca, 0x1d, 0x01, +0xa4, 0xc6, 0x0f, 0xe6, 0x31, 0x5e, 0x1b, 0xd3, +0xc6, 0x3a, 0xba, 0x1e, 0x33, 0x64, 0xc6, 0x9a, +0xa4, 0x0f, 0xd7, 0x5b, 0x60, 0x46, 0x6f, 0x01, +0xcc, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0x35, 0xca, 0xb7, 0x66, 0x61, +0x53, 0x9c, 0x08, 0x33, 0x74, 0x8e, 0x27, 0xac, +0x55, 0x97, 0x5c, 0x11, 0xbc, 0x03, 0x7e, 0xc4, +0xf5, 0x07, 0x23, 0x82, 0x4a, 0xfd, 0xdb, 0x13, +0xd3, 0xf6, 0xc7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0xe3, 0xda, 0xaf, +0x6e, 0xa1, 0xb8, 0xf8, 0xc5, 0xa6, 0xa1, 0x01, +0xb9, 0xe2, 0x27, 0x46, 0x1b, 0xdd, 0x58, 0x97, +0x2b, 0xb5, 0x6c, 0x15, 0x73, 0xf4, 0x18, 0x9b, +0x4f, 0xf4, 0x1a, 0x95, 0xd4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0x6a, +0xe9, 0xbb, 0x69, 0x31, 0x80, 0x54, 0x4e, 0x8a, +0xe7, 0x96, 0x53, 0x33, 0xd2, 0xb4, 0xae, 0x4a, +0x26, 0x20, 0xf2, 0x5b, 0x52, 0xbb, 0x42, 0x76, +0xed, 0xba, 0x56, 0x61, 0xf7, 0x7e, 0x6d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x7b, 0xad, 0x9a, 0xd7, 0x28, 0x54, 0xc7, +0xf9, 0xbb, 0x16, 0x51, 0x47, 0x35, 0x04, 0x1b, +0x38, 0xc4, 0x3d, 0x31, 0x81, 0x91, 0x64, 0x11, +0xd9, 0x4f, 0x53, 0x05, 0x58, 0x05, 0xde, 0x93, +0x7c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0xdc, 0x78, 0xfa, 0x2b, 0xd4, +0xc0, 0x95, 0x48, 0x0a, 0x92, 0x8c, 0xf9, 0x6b, +0xf5, 0x4f, 0x0a, 0x2b, 0x3b, 0xf5, 0xd4, 0x81, +0x23, 0xa5, 0x4e, 0x68, 0xbd, 0x93, 0x1c, 0xe4, +0xdc, 0x3d, 0x81, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0xf6, 0x61, 0xf8, +0x8c, 0x1b, 0xfc, 0x33, 0x88, 0xa8, 0x3e, 0xbb, +0x35, 0x48, 0x94, 0xa3, 0x3f, 0x36, 0x17, 0x1d, +0xfe, 0x8c, 0x7e, 0x01, 0x0e, 0x27, 0xbe, 0x51, +0xa5, 0x4c, 0xbe, 0xa1, 0x66, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0x45, +0x7d, 0xb5, 0x44, 0x40, 0xdb, 0x6d, 0x93, 0x8c, +0x01, 0x00, 0x29, 0x3e, 0x98, 0xfc, 0x91, 0x93, +0xf9, 0x24, 0xe6, 0x66, 0xce, 0x9e, 0x9e, 0xbb, +0x0c, 0xab, 0x6b, 0xd1, 0x92, 0x1b, 0xeb, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0x6b, 0x62, 0xbc, 0x10, 0x21, 0x53, 0xd7, +0xae, 0x08, 0x65, 0xc6, 0xe7, 0x4b, 0x26, 0x69, +0x03, 0x6f, 0x2a, 0x70, 0x83, 0x13, 0xaa, 0x52, +0xeb, 0x34, 0x35, 0x96, 0x0c, 0x83, 0xce, 0x11, +0x8e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0x47, 0xb1, 0x84, 0xee, 0x45, +0x74, 0x83, 0xec, 0xf2, 0xde, 0x72, 0x95, 0x3a, +0x50, 0xc5, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xa9, 0xbc, 0xa4, 0x09, +0xd2, 0x66, 0x1c, 0xbd, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0x9f, 0x4c, 0x72, +0xca, 0xeb, 0x7d, 0xc6, 0xca, 0x56, 0xd8, 0x06, +0xc8, 0xc9, 0x96, 0xe6, 0xa5, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0xe7, +0x83, 0xec, 0x97, 0x89, 0x6b, 0xf6, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0x59, +0x94, 0xfa, 0xfe, 0x89, 0x08, 0xd7, 0x73, 0x82, +0xad, 0xff, 0xb2, 0x50, 0x2c, 0xf6, 0xbf, 0x3c, +0xe1, 0x81, 0x91, 0x09, 0x09, 0x2f, 0x35, 0x36, +0x03, 0xf2, 0x8a, 0x13, 0x9d, 0xc0, 0x20, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x17, 0x7d, 0xd4, 0xc6, 0x7d, 0xd8, 0x96, +0x45, 0x28, 0x14, 0xdb, 0xe9, 0x24, 0x8f, 0x37, +0xbc, 0x02, 0x67, 0x5e, 0x30, 0xe5, 0x97, 0x01, +0x14, 0xa9, 0xca, 0x05, 0x70, 0x58, 0x5e, 0x47, +0xc7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0x77, 0xc7, 0xca, 0x75, 0x87, +0xdb, 0x26, 0x4f, 0x91, 0x3f, 0x66, 0xe5, 0x5d, +0xfb, 0xa2, 0x36, 0x65, 0xbf, 0x3c, 0xcf, 0xf3, +0xbd, 0x97, 0xc6, 0xf2, 0x2f, 0x69, 0xad, 0x92, +0x76, 0xc6, 0x3f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0xc7, 0x41, 0x0d, +0x2f, 0x49, 0x3c, 0x7d, 0xc8, 0xd6, 0x89, 0xfb, +0x31, 0x63, 0x01, 0x88, 0x52, 0xf6, 0x0d, 0x85, +0xaf, 0x38, 0xdb, 0x9d, 0xeb, 0x64, 0x6a, 0xb4, +0x3c, 0x31, 0xe5, 0x9d, 0xc3, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0x70, +0x40, 0x72, 0x95, 0xb1, 0x8e, 0x01, 0x22, 0x23, +0xa4, 0xa3, 0xe1, 0xc7, 0x1b, 0xba, 0x0c, 0x04, +0x20, 0xda, 0x78, 0xe9, 0xc1, 0x0c, 0x7f, 0x5b, +0x34, 0xa2, 0x2c, 0x73, 0x24, 0xee, 0xc5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0x21, 0x26, 0x1f, 0xd8, 0xe6, 0xa3, 0x7b, +0x76, 0xa9, 0x4a, 0x51, 0xf0, 0x6c, 0x2f, 0xdd, +0x33, 0x8e, 0x05, 0xe6, 0xf1, 0x0d, 0x52, 0x9d, +0xdf, 0xd4, 0x42, 0x05, 0xab, 0x6b, 0x3e, 0x9c, +0xe9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0xf8, 0x6c, 0x03, 0x04, 0x54, +0xb9, 0x60, 0xd1, 0xb4, 0xd7, 0x31, 0xbd, 0xd0, +0x99, 0xfb, 0x42, 0xf8, 0xc4, 0x05, 0x06, 0x65, +0xd5, 0x22, 0x03, 0x5a, 0x01, 0xb3, 0xc0, 0x81, +0x0b, 0x2a, 0x51, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0x24, 0xcd, 0xbe, +0x46, 0x7d, 0x93, 0xd9, 0xa8, 0x80, 0xe4, 0xfa, +0x8e, 0x54, 0x24, 0xeb, 0x83, 0xb3, 0x88, 0x34, +0x9f, 0xff, 0xf5, 0x82, 0x61, 0x4e, 0x62, 0x9e, +0x1d, 0x50, 0xa9, 0xcc, 0xc6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0x60, +0xe3, 0x0d, 0x9e, 0x53, 0xc7, 0x87, 0xc0, 0x86, +0xbf, 0xe3, 0x0b, 0x2c, 0x0c, 0xc4, 0x55, 0x3d, +0x95, 0x91, 0x2c, 0xd9, 0x1d, 0x2b, 0x08, 0x14, +0x67, 0xc7, 0xd1, 0xfe, 0x83, 0xe5, 0xec, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0xc0, 0xf9, 0x06, 0xbd, 0xdb, 0xe9, 0xac, +0xc6, 0x6b, 0xba, 0xa4, 0xd1, 0xcc, 0xf3, 0x08, +0x5a, 0x2b, 0xca, 0x4a, 0x04, 0xa8, 0x55, 0x17, +0x6f, 0x7e, 0x7a, 0x7f, 0x39, 0x17, 0xee, 0xae, +0xc7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0xc9, 0xcc, 0xdb, 0x50, 0x66, +0x0a, 0x76, 0x18, 0xab, 0xc1, 0x41, 0x1b, 0x85, +0xab, 0x5f, 0x11, 0x19, 0xfa, 0x90, 0x85, 0xb0, +0x73, 0xb3, 0x05, 0x2c, 0x80, 0x44, 0xba, 0x1e, +0x77, 0x36, 0x53, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x0d, 0x35, 0xa5, +0x82, 0x3c, 0x59, 0x65, 0x2b, 0xa7, 0xfb, 0x78, +0xc8, 0xe4, 0x60, 0x2a, 0xef, 0x9a, 0xa9, 0x9b, +0x19, 0x8d, 0xeb, 0x61, 0x87, 0xab, 0xfc, 0x43, +0xf4, 0x14, 0xcd, 0x96, 0xbf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0x28, +0xc8, 0x10, 0x5d, 0xe4, 0xb3, 0xb7, 0xf6, 0x68, +0x18, 0xb7, 0x6a, 0x88, 0xd4, 0xec, 0xe9, 0xc2, +0x25, 0xbb, 0x66, 0x71, 0x4b, 0x24, 0xe0, 0x99, +0x6f, 0xd9, 0x2c, 0x77, 0xcf, 0x79, 0xdc, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0xf7, 0x5e, 0x2c, 0x80, 0x2c, 0x13, 0xde, +0x84, 0x7e, 0x7e, 0xe6, 0xd1, 0x46, 0x7f, 0x94, +0x90, 0x57, 0x8e, 0xaa, 0x0b, 0x4d, 0x7b, 0x58, +0xa7, 0x2c, 0x7d, 0xac, 0x41, 0x33, 0xcb, 0x68, +0x20, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0x58, 0xd4, 0xdd, 0x81, 0xd2, +0x20, 0xe4, 0x44, 0xd6, 0x12, 0x2c, 0x38, 0x17, +0x62, 0xcf, 0x89, 0x09, 0x4f, 0xef, 0x0e, 0xa6, +0xbc, 0x9e, 0x32, 0x7b, 0x45, 0xc0, 0xc2, 0xe5, +0x19, 0xe9, 0xb2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0xb5, 0x50, 0xf9, +0x4d, 0x11, 0x71, 0x54, 0x27, 0x1f, 0x91, 0x3a, +0x2b, 0x9e, 0xfc, 0xab, 0xa5, 0x94, 0x52, 0x5f, +0x5c, 0xac, 0xbe, 0x61, 0x8b, 0xe4, 0x98, 0x03, +0x20, 0xfa, 0xb0, 0xde, 0x4a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0xb2, +0x9a, 0x98, 0x12, 0xb8, 0x1d, 0x6d, 0x93, 0x8a, +0x19, 0xc6, 0x19, 0xb7, 0xc6, 0xb0, 0xae, 0x33, +0x1a, 0xc9, 0x3c, 0xea, 0x4d, 0x55, 0x74, 0xfc, +0xf7, 0x6d, 0x2e, 0xba, 0x51, 0x22, 0x5b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0x9c, 0x91, 0x08, 0x73, 0xb3, 0x0d, 0xf8, +0xe0, 0xbf, 0xf3, 0xa4, 0x03, 0x5f, 0x0b, 0x51, +0x37, 0xcb, 0x93, 0xcc, 0x0b, 0x85, 0xc5, 0xd4, +0x0f, 0xaa, 0xc4, 0xfc, 0x69, 0x66, 0x08, 0x52, +0x4c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0x26, 0x59, 0x6d, 0x75, 0x83, +0x91, 0x64, 0xee, 0x93, 0x8e, 0xa9, 0x14, 0x0b, +0x61, 0x36, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xc2, 0x06, 0x5f, 0x9c, +0x87, 0xb3, 0x26, 0x9d, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0xd2, 0x3d, 0x49, +0x00, 0xc4, 0x24, 0x50, 0x63, 0xae, 0xb6, 0x51, +0x82, 0x1e, 0x6a, 0xe5, 0x1f, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x43, +0x44, 0xc1, 0xe9, 0x96, 0x0c, 0x3f, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0x33, +0xab, 0x16, 0x34, 0xe3, 0xe6, 0x73, 0x4b, 0x18, +0x24, 0x62, 0x9b, 0x33, 0xae, 0xb8, 0xde, 0x8d, +0x96, 0x45, 0x74, 0xdd, 0x54, 0x9e, 0x9c, 0xcc, +0x2b, 0xec, 0x64, 0x57, 0xa2, 0xde, 0x03, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0x12, 0x68, 0xef, 0xd5, 0xe4, 0xb0, 0x8e, +0x0d, 0x26, 0xa3, 0xd0, 0x26, 0x42, 0x8b, 0xb5, +0x60, 0x57, 0xf1, 0xda, 0x40, 0x98, 0x3d, 0x5a, +0x25, 0x07, 0x07, 0xcb, 0x8f, 0x0f, 0x9c, 0x95, +0xa3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0xf9, 0xaa, 0x4e, 0xb6, 0xf2, +0xcb, 0x56, 0xe3, 0x81, 0x96, 0xa5, 0x36, 0x3a, +0x2d, 0x83, 0xa3, 0x65, 0xd9, 0x08, 0x65, 0x5c, +0xbc, 0xa3, 0x66, 0x8f, 0xb2, 0x27, 0x1b, 0x9e, +0x40, 0x21, 0xf2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0x42, 0xda, 0xe7, +0x53, 0xcd, 0xf9, 0x2c, 0x76, 0xbd, 0xc8, 0xd1, +0xba, 0x56, 0xb9, 0x36, 0xc5, 0x5a, 0xd6, 0x68, +0x27, 0x43, 0x1e, 0x00, 0x4d, 0x7c, 0xcd, 0xce, +0xe6, 0x71, 0x55, 0xa8, 0x0f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0xa7, +0xef, 0x3d, 0x51, 0x09, 0x6f, 0x27, 0xef, 0xf8, +0x01, 0x1c, 0x28, 0xcb, 0xf1, 0xf0, 0x63, 0x71, +0xa5, 0x3a, 0xad, 0x47, 0x54, 0xce, 0x8a, 0xcb, +0x66, 0xe4, 0x32, 0xff, 0x55, 0xa5, 0x77, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0x84, 0x53, 0x4a, 0x4a, 0xf5, 0xc4, 0x74, +0x3a, 0xe3, 0xa3, 0x0d, 0x7d, 0x28, 0xb0, 0xfc, +0xaa, 0x77, 0x5b, 0xb2, 0xdc, 0x3d, 0xb5, 0x6d, +0xdd, 0x42, 0x7a, 0x77, 0xb3, 0x24, 0x59, 0xe7, +0xfe, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0xc9, 0xfb, 0x43, 0xa6, 0xe6, +0x05, 0xa9, 0x47, 0xd1, 0xc2, 0x94, 0xe1, 0x2e, +0x6e, 0xa0, 0x6e, 0x62, 0x67, 0x2d, 0xb1, 0x85, +0xb6, 0xb0, 0x93, 0x43, 0x06, 0xaf, 0x45, 0xdb, +0x30, 0xcb, 0xf5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0x5b, 0xe0, 0x69, +0xeb, 0xbb, 0x7f, 0x09, 0xc0, 0x66, 0xf9, 0x72, +0xc5, 0x5a, 0xae, 0x6c, 0x26, 0x49, 0xa7, 0x73, +0x18, 0x3b, 0x31, 0x6a, 0x38, 0x64, 0xec, 0x1a, +0xf7, 0xfb, 0xba, 0x26, 0xf5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0x7b, +0x39, 0x92, 0xb5, 0xc1, 0xa2, 0xa7, 0xc7, 0xee, +0xc0, 0xd5, 0x9b, 0xc9, 0xf6, 0x79, 0xa3, 0x64, +0x65, 0x0f, 0x66, 0xa3, 0x0e, 0xfd, 0x51, 0x9a, +0x80, 0x0a, 0x73, 0x77, 0x4f, 0x1e, 0xf4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0xaa, 0xe0, 0xd0, 0x9d, 0xd8, 0x31, 0x2b, +0x44, 0xed, 0xd7, 0x4a, 0x1f, 0x6e, 0xc5, 0x98, +0x41, 0xa5, 0x53, 0xa9, 0xa8, 0x3f, 0xf4, 0x7c, +0x0a, 0x11, 0x5c, 0x42, 0xa2, 0xa8, 0x5b, 0x8d, +0x0f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0x4e, 0xef, 0x8b, 0x00, 0x45, +0xb2, 0x11, 0x01, 0x60, 0x0d, 0x90, 0xee, 0x9c, +0x5a, 0xc6, 0x3d, 0x9e, 0xfb, 0x9e, 0xa5, 0xb1, +0x8f, 0xe7, 0x90, 0x05, 0x22, 0x00, 0x01, 0x76, +0x06, 0x77, 0x56, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0x75, 0x43, 0x15, +0xc9, 0x8d, 0xa0, 0x00, 0x51, 0x48, 0x30, 0x86, +0x18, 0xa2, 0x80, 0xdd, 0xe6, 0x95, 0x88, 0xa0, +0x8c, 0x50, 0xc4, 0xd4, 0x97, 0xa6, 0x9b, 0x55, +0xe9, 0x9d, 0x05, 0x86, 0x91, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0xe5, +0x9c, 0xc7, 0x44, 0x3e, 0xa4, 0x34, 0xb1, 0x5d, +0xd5, 0x5e, 0x4e, 0x9d, 0x92, 0xa2, 0x1a, 0xe9, +0x61, 0x87, 0x50, 0x6f, 0x4c, 0x77, 0xff, 0x4f, +0x6f, 0x63, 0x7e, 0x8d, 0x37, 0x36, 0xca, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0x0e, 0x0e, 0x13, 0x8c, 0x25, 0x29, 0x0c, +0x8e, 0xb1, 0x63, 0x8c, 0xc5, 0xb9, 0xd6, 0xc5, +0x7c, 0x0d, 0x5f, 0xb2, 0x42, 0x7c, 0xbe, 0x22, +0xd8, 0x40, 0xb5, 0x7a, 0xc2, 0x6c, 0xea, 0xb5, +0xf0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0xb5, 0xf0, 0xc8, 0x81, 0x57, +0x95, 0x22, 0x02, 0x10, 0xdb, 0x97, 0x47, 0x66, +0xf4, 0x56, 0x01, 0x90, 0xf7, 0x04, 0x53, 0x9f, +0x29, 0xda, 0x3c, 0xc3, 0xf4, 0x0a, 0xef, 0xf1, +0x3f, 0xd7, 0x07, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0xfc, 0x27, 0x3f, +0x96, 0x1f, 0x4b, 0xce, 0x93, 0xbb, 0xe2, 0x89, +0x7d, 0xb6, 0x58, 0xc8, 0xc7, 0xa4, 0x4f, 0x27, +0x10, 0xce, 0x8f, 0x9d, 0x66, 0x0e, 0x36, 0xc9, +0x04, 0x48, 0x5c, 0x3c, 0xdf, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0x61, +0x26, 0x87, 0x33, 0xe3, 0x15, 0xf4, 0x8b, 0x8a, +0xdc, 0x3a, 0xfa, 0x07, 0x7e, 0xa0, 0xdf, 0x08, +0x28, 0xdd, 0xc9, 0xd9, 0x64, 0x3e, 0x34, 0xa6, +0xbd, 0xc6, 0x37, 0xb6, 0x96, 0x14, 0xc6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0x55, 0x2c, 0xa7, 0xa8, 0x18, 0xa7, 0x89, +0xe5, 0x4d, 0xa3, 0xf6, 0xf0, 0x3f, 0x8a, 0x91, +0x8f, 0xdb, 0x54, 0xc6, 0x63, 0xf9, 0xbe, 0x9e, +0xe7, 0x3d, 0xab, 0x94, 0x7f, 0xd7, 0x11, 0x42, +0xba, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0x86, 0x22, 0x0a, 0x38, 0x8e, +0x86, 0xc6, 0x92, 0x10, 0xae, 0xb0, 0xd2, 0xbc, +0xe9, 0x69, 0xd2, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xf7, 0xc2, 0x57, 0xf7, +0x3c, 0x41, 0x56, 0xad, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0xa6, 0x0e, 0x4a, +0x8a, 0x17, 0x17, 0xe1, 0x21, 0xfc, 0x04, 0xe5, +0x68, 0x72, 0x9c, 0x06, 0xb9, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb5, 0x6e, +0x38, 0x20, 0x1b, 0x51, 0x2c, 0xee, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0x0d, +0x3d, 0x0c, 0x59, 0xfa, 0x79, 0x05, 0xf9, 0x57, +0x06, 0xaf, 0x1c, 0x57, 0xd2, 0x37, 0x56, 0x0d, +0x3b, 0x0c, 0xb4, 0xaa, 0x01, 0x2b, 0x53, 0x2f, +0xc9, 0xe3, 0x1d, 0xd1, 0x60, 0x2f, 0xed, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0xa8, 0xbb, 0x8e, 0x7f, 0x01, 0x5a, 0xeb, +0xed, 0xec, 0x7c, 0x86, 0x82, 0x6a, 0x8d, 0xd0, +0xf7, 0x71, 0xa1, 0xf8, 0x0f, 0x0d, 0x60, 0x4f, +0x22, 0x15, 0xbd, 0xad, 0x16, 0x18, 0x19, 0x34, +0x71, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0x0e, 0x15, 0xb7, 0xe2, 0x4f, +0x47, 0x9f, 0x0d, 0x1e, 0x98, 0xe4, 0x96, 0x10, +0xa7, 0xf7, 0x43, 0x9e, 0xd9, 0x32, 0x7a, 0x99, +0xf0, 0xa1, 0xae, 0x32, 0x63, 0x1c, 0x4f, 0x44, +0x7f, 0xa0, 0x8d, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0xd1, 0xbb, 0xf6, +0xe8, 0x6f, 0xf0, 0x0e, 0xe4, 0xf5, 0xf6, 0xb1, +0x8c, 0x90, 0x89, 0xce, 0xb1, 0x0b, 0xbc, 0xe7, +0xee, 0xb6, 0x25, 0xf7, 0xdc, 0x19, 0x20, 0xb2, +0x5f, 0x40, 0x76, 0xf3, 0x4e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x1d, +0xd8, 0x42, 0x22, 0x70, 0x7d, 0x2c, 0xef, 0x05, +0xa9, 0x98, 0x0b, 0xc1, 0x33, 0x21, 0x8e, 0x82, +0xc6, 0xec, 0x9f, 0x39, 0x51, 0xb6, 0x76, 0x1b, +0x0e, 0xb9, 0xb6, 0xd7, 0x8f, 0x39, 0x51, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0x66, 0x83, 0x61, 0x21, 0xb0, 0x01, 0xe9, +0x24, 0x8f, 0xab, 0x7c, 0x93, 0x94, 0xa9, 0xbe, +0x3d, 0x11, 0x37, 0x0d, 0x15, 0x29, 0x13, 0x97, +0xd7, 0x29, 0xfe, 0xcf, 0x66, 0x63, 0xac, 0x90, +0x34, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0x27, 0x54, 0xdf, 0xdb, 0xee, +0xe3, 0xcf, 0xf0, 0x1a, 0x6f, 0x4f, 0xfd, 0x37, +0xe1, 0x39, 0x9f, 0x42, 0x5c, 0xce, 0x2e, 0xe5, +0x0f, 0xa7, 0xc4, 0x30, 0x21, 0x87, 0x05, 0xbf, +0x95, 0xc1, 0x1e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0x8a, 0x41, 0xd0, +0x13, 0x92, 0x2d, 0x79, 0x8c, 0x19, 0xa2, 0x9a, +0xfe, 0x29, 0x88, 0x88, 0x81, 0x42, 0x52, 0xf4, +0xfe, 0x4d, 0xa0, 0x2e, 0x8b, 0x49, 0x2f, 0x40, +0x3c, 0xa9, 0xd6, 0x00, 0x01, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0xfa, +0x82, 0xe1, 0x6d, 0xcf, 0x5c, 0x48, 0x69, 0x28, +0x53, 0x88, 0x79, 0x40, 0x86, 0x48, 0xde, 0x86, +0xad, 0x5d, 0xb4, 0x6c, 0x76, 0xa2, 0x6f, 0x28, +0x6a, 0x25, 0x34, 0x45, 0xfc, 0x0a, 0xb2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0xc0, 0x0e, 0x53, 0xe3, 0x51, 0x2d, 0x1a, +0x57, 0x52, 0xdd, 0xba, 0x23, 0xbe, 0xc2, 0x28, +0xb3, 0xab, 0xcf, 0x9b, 0x51, 0x1c, 0xa8, 0xff, +0xa6, 0xca, 0x4a, 0x75, 0x97, 0xbb, 0x94, 0x1a, +0x15, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0x97, 0x8b, 0xef, 0x0f, 0x7e, +0x63, 0x57, 0xb8, 0xcc, 0xc0, 0x9e, 0xc9, 0x67, +0x9d, 0x99, 0x13, 0xcb, 0xea, 0x8b, 0x0e, 0x00, +0x7b, 0x51, 0x36, 0x07, 0x7e, 0x58, 0x1c, 0xf7, +0xd9, 0xa2, 0x60, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0x3d, 0x51, 0xe4, +0x09, 0xe8, 0xaf, 0x28, 0x1d, 0x9c, 0x3b, 0x95, +0x9c, 0x27, 0x70, 0x7f, 0x67, 0xbf, 0xaa, 0x8e, +0x6b, 0x37, 0x3a, 0x0e, 0xda, 0xd3, 0x5a, 0xcf, +0xf5, 0x56, 0xa1, 0x18, 0xc1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0x0d, +0xca, 0x60, 0x7c, 0x92, 0x6c, 0x02, 0xca, 0xef, +0xa7, 0xf7, 0xa6, 0xcd, 0x20, 0x1a, 0xdb, 0xb4, +0x8c, 0xe2, 0x6e, 0x68, 0xc9, 0x55, 0x99, 0x30, +0x9b, 0xf8, 0x5b, 0x00, 0xb3, 0xbd, 0x8d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0x98, 0x63, 0x25, 0x6d, 0x65, 0x2d, 0x65, +0xf7, 0x3f, 0x82, 0x5a, 0xf2, 0x1e, 0x51, 0x72, +0x33, 0xbc, 0x5a, 0xe8, 0xab, 0xa4, 0x50, 0x28, +0x6b, 0x76, 0xea, 0x2d, 0x4d, 0x72, 0x34, 0x64, +0x26, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0x01, 0x2f, 0x37, 0x61, 0x5b, +0x3d, 0x3e, 0x4a, 0x81, 0xb2, 0x67, 0x12, 0x12, +0x9b, 0xff, 0xa8, 0x64, 0x8b, 0x3f, 0x64, 0xa8, +0x9a, 0x3a, 0x1c, 0x8c, 0x19, 0x3b, 0x3d, 0x3e, +0xe0, 0x24, 0x89, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x06, 0xb7, 0x14, +0x23, 0xca, 0x17, 0x44, 0xfd, 0xc3, 0x85, 0x58, +0x18, 0xa9, 0x58, 0x90, 0x35, 0x9a, 0x17, 0xbe, +0x5a, 0x33, 0x6d, 0x94, 0x7a, 0x02, 0xf1, 0x9a, +0x27, 0xf9, 0x22, 0xe6, 0xe9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0x88, +0xb3, 0x11, 0x72, 0x47, 0x2f, 0x3e, 0x52, 0xfb, +0x31, 0x45, 0x2b, 0xe0, 0x4d, 0x9c, 0x61, 0x1b, +0xa5, 0x2b, 0x62, 0x4d, 0x59, 0x82, 0xf6, 0xf2, +0x1c, 0x00, 0x75, 0x5b, 0x24, 0x50, 0x06, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0xc7, 0x79, 0x5f, 0x75, 0x25, 0xfe, 0xe0, +0x3b, 0x3b, 0xa7, 0xd7, 0x8d, 0x89, 0xc2, 0x6c, +0x16, 0x1e, 0x39, 0x0c, 0x51, 0x95, 0xba, 0x05, +0x67, 0xfa, 0x4c, 0xd6, 0x2f, 0x93, 0x9b, 0x18, +0xee, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0x4c, 0xf0, 0x30, 0xa0, 0x66, +0x51, 0x0d, 0x31, 0x50, 0xf9, 0xd1, 0xb8, 0xf3, +0xae, 0xd7, 0x9d, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xee, 0xa7, 0x33, 0x65, +0xb1, 0xe8, 0x74, 0x67, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0xce, 0x1d, 0x82, +0x66, 0x7c, 0x35, 0x29, 0x4a, 0x4b, 0x90, 0xd6, +0xdd, 0xe3, 0x17, 0x1b, 0x34, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x1b, +0x5f, 0xba, 0xaf, 0x8c, 0x29, 0x1a, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0x52, +0x0e, 0xb6, 0x69, 0x77, 0x1a, 0x5a, 0x02, 0x80, +0x1d, 0xc2, 0x53, 0xcb, 0x15, 0xba, 0x09, 0x66, +0x44, 0x13, 0x27, 0x14, 0x83, 0xb2, 0x51, 0xae, +0xb0, 0x99, 0x49, 0x20, 0x57, 0x95, 0x97, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0x66, 0x1e, 0x9d, 0x08, 0x7d, 0xa8, 0xba, +0xbb, 0xc4, 0xa5, 0xab, 0x89, 0x37, 0x00, 0xcc, +0x72, 0xd9, 0x8e, 0xcd, 0x46, 0xb6, 0x87, 0x9e, +0xb9, 0xdf, 0x7d, 0x22, 0xfd, 0x5b, 0x14, 0x7c, +0x6d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0x2a, 0xe9, 0x3c, 0xea, 0xc2, +0x80, 0x2e, 0x3a, 0x5c, 0xd5, 0xda, 0x1c, 0x4f, +0xd3, 0xb5, 0x5f, 0x43, 0x4b, 0xba, 0xbf, 0xfb, +0xcd, 0xcf, 0x97, 0x43, 0x33, 0xfe, 0x1b, 0x6a, +0x09, 0x79, 0xf8, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0xfd, 0x74, 0xa7, +0xcf, 0x5d, 0x0c, 0xb8, 0x35, 0x90, 0xf6, 0x3c, +0xe5, 0x04, 0x27, 0xb0, 0x3e, 0x46, 0xe7, 0x17, +0xd5, 0xc1, 0x9f, 0x05, 0x40, 0xe3, 0x16, 0x7c, +0x28, 0x81, 0x00, 0x0e, 0x20, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0x3e, +0xb9, 0x2d, 0xb8, 0x9b, 0x07, 0x8a, 0xb8, 0xd4, +0x7e, 0x06, 0xa0, 0xa2, 0x22, 0x68, 0xc5, 0x64, +0xc5, 0x1d, 0xcc, 0x33, 0x48, 0x38, 0x1a, 0x8a, +0x2e, 0x81, 0x19, 0x18, 0x04, 0xce, 0xd7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0xef, 0x08, 0x75, 0x5f, 0xa1, 0x64, 0xf2, +0xb1, 0xf0, 0x4f, 0x4e, 0x75, 0x77, 0x8b, 0xa1, +0x64, 0x93, 0x31, 0xc6, 0xe8, 0x0d, 0xf1, 0xbf, +0x12, 0x94, 0xf0, 0xc1, 0xd7, 0xcc, 0x1f, 0x5d, +0xd7, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0xfc, 0x3a, 0x5a, 0xe5, 0x1d, +0xf0, 0x38, 0xc7, 0x0e, 0x11, 0xc6, 0xfc, 0x10, +0x61, 0xde, 0x1b, 0xff, 0x3d, 0xb9, 0x7c, 0xc1, +0xc3, 0xd2, 0xcc, 0x18, 0xcf, 0x3d, 0x1f, 0xcc, +0xde, 0x0d, 0x90, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0x3c, 0xb3, 0xaf, +0x35, 0x1f, 0xa1, 0xe2, 0x45, 0x9c, 0xde, 0x6b, +0xd3, 0x62, 0x27, 0x0f, 0xfa, 0x68, 0xf0, 0xcc, +0xca, 0xd9, 0x09, 0x7f, 0x0e, 0x82, 0xdc, 0x48, +0x1e, 0xe8, 0xfe, 0x78, 0x4a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0x99, +0x3f, 0xb4, 0x86, 0xf7, 0x03, 0x3e, 0xd6, 0xcb, +0x38, 0x38, 0x10, 0xc7, 0x10, 0x8c, 0xb5, 0x20, +0x53, 0x34, 0xdc, 0x5d, 0x0c, 0x74, 0x61, 0x32, +0x4e, 0x8d, 0xf0, 0x05, 0x00, 0x5a, 0x4d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0xf4, 0x1d, 0x39, 0xeb, 0x09, 0x3e, 0x4b, +0x01, 0x82, 0x19, 0xd5, 0x08, 0x28, 0xbd, 0x7d, +0x00, 0x3f, 0x0b, 0x98, 0xa0, 0xbc, 0x30, 0x8e, +0x22, 0x17, 0x81, 0xc6, 0x39, 0x1e, 0x24, 0x31, +0x31, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0x0d, 0x59, 0x54, 0x2d, 0xdc, +0x06, 0x5e, 0xd9, 0xac, 0xd2, 0x33, 0x5b, 0xb7, +0x13, 0xc3, 0x2f, 0x1f, 0x54, 0xce, 0xd7, 0x30, +0xee, 0x70, 0x88, 0x82, 0x48, 0x42, 0xbd, 0xd3, +0x96, 0x47, 0xe6, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0x03, 0xf6, 0xe1, +0x90, 0x02, 0x8a, 0x8a, 0xa9, 0x87, 0xb1, 0xd8, +0x72, 0xbc, 0xf6, 0x85, 0x16, 0xcd, 0x5c, 0x36, +0x0b, 0x3f, 0x72, 0xe1, 0xe7, 0xef, 0x83, 0x21, +0x03, 0x38, 0x65, 0xad, 0xc6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0x4a, +0x8c, 0x42, 0x0b, 0x83, 0xc1, 0xdb, 0x8b, 0xfc, +0xd0, 0xb1, 0xf3, 0x85, 0x97, 0x49, 0xfe, 0x2e, +0xc1, 0x8f, 0xb7, 0x76, 0x21, 0x40, 0x49, 0x29, +0x2c, 0xf0, 0x21, 0xab, 0x61, 0x3b, 0xdd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0x7b, 0xfd, 0x93, 0x19, 0xdd, 0x0b, 0xf5, +0x8c, 0x8b, 0xec, 0xab, 0x14, 0x46, 0xe0, 0x67, +0xa8, 0x11, 0xfb, 0x8f, 0x96, 0x0e, 0x6a, 0x77, +0x3e, 0x25, 0x1e, 0x1b, 0x00, 0xc8, 0x4f, 0x6d, +0x1d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0xb6, 0x89, 0x3c, 0xec, 0xa3, +0xa1, 0xfb, 0x95, 0x48, 0xeb, 0x14, 0x92, 0xb2, +0xb8, 0xf7, 0xb9, 0xa6, 0x53, 0xff, 0xb0, 0x5b, +0x72, 0xa7, 0x95, 0x18, 0x56, 0x45, 0x39, 0x1c, +0x64, 0x92, 0x5a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0x59, 0xc7, 0x0b, +0x18, 0xbc, 0x67, 0xa3, 0x8a, 0xc0, 0x8c, 0xa2, +0x6d, 0xa4, 0x85, 0xa9, 0xfc, 0x72, 0xfc, 0x38, +0xc4, 0x50, 0x53, 0x6c, 0xe7, 0x33, 0x07, 0x55, +0x6f, 0xf1, 0xa5, 0xca, 0x42, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x1e, +0x26, 0x16, 0xef, 0x01, 0xe0, 0xc9, 0x2b, 0xb7, +0x74, 0xc6, 0x35, 0x69, 0x12, 0x17, 0x9b, 0x19, +0xf0, 0x35, 0xb0, 0x3f, 0x0f, 0x76, 0x3f, 0x85, +0x6f, 0xed, 0x47, 0x31, 0xd6, 0x8c, 0x4c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0x45, 0x0f, 0x4e, 0x8b, 0x5b, 0x89, 0xb6, +0xb8, 0x48, 0x44, 0x41, 0x01, 0xbc, 0xdb, 0xb2, +0xe6, 0x2c, 0x17, 0xfb, 0x2f, 0xa4, 0xdb, 0x63, +0xb3, 0x7c, 0x86, 0x40, 0x7e, 0xd6, 0x74, 0x4b, +0x59, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0xa8, 0x92, 0xa6, 0x22, 0x74, +0x31, 0xdf, 0x37, 0x9d, 0xfd, 0x36, 0xf7, 0xa2, +0xf1, 0xf4, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x03, 0x4b, 0x80, 0x63, +0x48, 0x7b, 0x68, 0x80, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0xd7, 0x81, 0x4a, +0x06, 0x18, 0x54, 0x5c, 0x98, 0x6c, 0xd1, 0x09, +0x37, 0x34, 0x2c, 0x9d, 0x0a, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0x94, +0xda, 0xdf, 0xd0, 0x88, 0xcf, 0xea, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0x0b, +0xa8, 0xbe, 0x60, 0x9a, 0x5c, 0xc0, 0x70, 0x71, +0x38, 0xa4, 0x45, 0xc7, 0xc3, 0xbf, 0x3b, 0x9a, +0xf8, 0xf8, 0xca, 0x4f, 0xd3, 0x17, 0x84, 0x20, +0x10, 0x54, 0xe8, 0x3f, 0x7f, 0x8a, 0x6d, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0xeb, 0x1c, 0x66, 0x01, 0x40, 0x96, 0xe0, +0x76, 0xee, 0x54, 0x3b, 0x9b, 0xc8, 0xa1, 0x78, +0x66, 0x74, 0xbb, 0x7f, 0xe2, 0x86, 0xf4, 0xd8, +0x39, 0x65, 0xa8, 0xea, 0xca, 0x7a, 0x8c, 0x1b, +0x0a, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0xf6, 0x6e, 0x63, 0xaa, 0x12, +0x5d, 0xad, 0x6a, 0x57, 0x64, 0x54, 0x08, 0x0d, +0x33, 0xe0, 0x26, 0xe7, 0x9b, 0x88, 0xff, 0x5e, +0x0d, 0x84, 0x8c, 0xe3, 0xb4, 0x08, 0xe6, 0x99, +0x7e, 0xb0, 0xe9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0xc8, 0x9b, 0xf0, +0x43, 0x4c, 0x76, 0xba, 0xc7, 0xf2, 0xb8, 0x18, +0xcd, 0xd7, 0x43, 0x06, 0xd4, 0x75, 0x2e, 0xd6, +0xb6, 0x81, 0x12, 0x80, 0x3c, 0x9d, 0x15, 0x64, +0xce, 0x2a, 0x94, 0xa1, 0xaa, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0xe9, +0x9b, 0x3c, 0x81, 0xc7, 0x98, 0xdd, 0xa3, 0x45, +0x4e, 0x4c, 0x4f, 0x20, 0x73, 0xe2, 0xad, 0x0b, +0xa0, 0xe9, 0xc7, 0x32, 0x45, 0xe0, 0x9d, 0x2e, +0x3c, 0xbd, 0x07, 0x8b, 0x1c, 0xec, 0xde, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x01, 0x22, +0x21, 0xa2, 0x0e, 0xd7, 0x4d, 0xed, 0x78, 0xee, +0x1b, 0x3c, 0x44, 0xe8, 0x77, 0xff, 0x65, 0x8f, +0x95, 0x00, 0x46, 0x3c, 0xb8, 0x6d, 0xe3, 0xdc, +0xb0, 0xc2, 0xd3, 0xfb, 0x06, 0xb8, 0x30, 0x56, +0x6c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf0, +0x01, 0x22, 0x21, 0xf3, 0xf5, 0xbc, 0xa5, 0x47, +0x55, 0x47, 0x1c, 0x84, 0xeb, 0x71, 0xf7, 0xe0, +0x85, 0xeb, 0x6d, 0xab, 0x70, 0xc3, 0x5d, 0x32, +0x23, 0x24, 0xc5, 0xbb, 0xf4, 0xe2, 0xa1, 0xa9, +0x79, 0x8a, 0x5e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf1, 0x01, 0x22, 0x21, 0x35, 0x12, 0x85, +0x1a, 0xd0, 0xdc, 0xcd, 0xef, 0xf7, 0x18, 0xfb, +0x67, 0x18, 0x92, 0x0e, 0x88, 0x2f, 0x90, 0xd9, +0xe5, 0x2b, 0x75, 0xd5, 0xcf, 0x45, 0xe3, 0xdf, +0x68, 0xb3, 0x56, 0x27, 0x58, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf2, 0x01, 0x22, 0x21, 0x58, +0x61, 0xf0, 0x49, 0xa7, 0x73, 0x29, 0xa1, 0x2c, +0x2d, 0xa7, 0x4f, 0x22, 0xfb, 0x0d, 0x99, 0xdc, +0xf9, 0x8e, 0x79, 0x5e, 0x83, 0x57, 0x3d, 0x63, +0x9f, 0x89, 0xb8, 0x42, 0x16, 0x76, 0x98, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf3, 0x01, 0x22, +0x21, 0xf4, 0x57, 0x53, 0x6d, 0x49, 0xd3, 0xa6, +0xca, 0xd0, 0x69, 0x2c, 0xec, 0xd4, 0x29, 0x5f, +0x5e, 0x9c, 0x2c, 0xfb, 0x59, 0xcb, 0x76, 0x3d, +0x34, 0x6f, 0x5c, 0xbc, 0x79, 0xdf, 0x0b, 0x13, +0xa6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf4, +0x01, 0x22, 0x21, 0x52, 0x46, 0xa2, 0x83, 0xf7, +0xc7, 0xf8, 0x31, 0x8e, 0x19, 0xd6, 0xae, 0xd6, +0x77, 0xbd, 0x80, 0x8a, 0xe1, 0xcb, 0x82, 0x68, +0xdf, 0xa9, 0xeb, 0x6b, 0x40, 0x1d, 0x0f, 0x63, +0x08, 0xd0, 0x6c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf5, 0x01, 0x22, 0x21, 0x65, 0xce, 0xe6, +0x9f, 0x77, 0x09, 0x55, 0x5e, 0x58, 0xe7, 0x98, +0x34, 0x23, 0xfe, 0xac, 0xf0, 0xfd, 0x60, 0xc5, +0xe5, 0x74, 0xbe, 0x88, 0x3e, 0x64, 0xdb, 0x92, +0xd5, 0xe1, 0x30, 0x6d, 0x54, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xf6, 0x01, 0x22, 0x21, 0xf4, +0x2b, 0xa3, 0xc0, 0xde, 0x6c, 0x00, 0x8f, 0xf7, +0xc5, 0x82, 0x5b, 0xbd, 0xf5, 0x5c, 0x21, 0x04, +0x1d, 0x95, 0x7f, 0x61, 0x3f, 0x3a, 0xfd, 0xb3, +0x1f, 0xc8, 0x67, 0x35, 0x98, 0xa8, 0xe8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xf7, 0x01, 0x22, +0x21, 0xb0, 0x6f, 0xd5, 0x65, 0x75, 0x98, 0x76, +0xc4, 0xa0, 0x64, 0xdd, 0x52, 0xd1, 0xb7, 0xf3, +0x30, 0xc1, 0x39, 0x2a, 0x75, 0x2b, 0x25, 0x9c, +0x25, 0xbe, 0x70, 0x0b, 0xb6, 0x1a, 0xb2, 0x28, +0xbb, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xf8, +0x01, 0x22, 0x21, 0xc1, 0xee, 0x84, 0x99, 0x67, +0xb2, 0xf3, 0xf5, 0xc6, 0x8c, 0xb8, 0x1d, 0xa9, +0x39, 0x20, 0x01, 0x45, 0xac, 0x7e, 0xdd, 0xfa, +0xf6, 0xc2, 0xba, 0xd6, 0xab, 0xf5, 0xec, 0x34, +0x9d, 0x79, 0x58, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xf9, 0x01, 0x22, 0x21, 0x3f, 0x85, 0xf0, +0xb3, 0x16, 0xbc, 0x28, 0x42, 0x53, 0x03, 0xf2, +0x16, 0xf1, 0x44, 0xc6, 0xc1, 0xbe, 0x91, 0x5f, +0x88, 0xe4, 0x5f, 0x5a, 0x55, 0xf0, 0x5a, 0xef, +0xe5, 0x6a, 0xc1, 0x0a, 0x84, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfa, 0x01, 0x22, 0x21, 0x03, +0x3a, 0x06, 0x27, 0x34, 0x17, 0xdb, 0x61, 0xa7, +0xfd, 0x1e, 0x4e, 0x2b, 0xd2, 0x80, 0x95, 0x70, +0x4d, 0x26, 0xd4, 0x04, 0xfd, 0xe9, 0x38, 0x66, +0x94, 0x42, 0xdd, 0x58, 0xa4, 0x47, 0xf4, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xfb, 0x01, 0x22, +0x21, 0x35, 0xd8, 0xc7, 0x64, 0x62, 0x0f, 0x73, +0xed, 0xd8, 0x29, 0x33, 0x0d, 0x06, 0xf2, 0x02, +0x81, 0x77, 0xa0, 0x04, 0x55, 0x8e, 0x3b, 0xec, +0x24, 0xa7, 0x11, 0xe0, 0x80, 0x7a, 0xae, 0xc7, +0x27, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xfc, +0x01, 0x22, 0x21, 0x11, 0x50, 0x6b, 0x45, 0x10, +0xeb, 0x8d, 0x86, 0xa2, 0xb7, 0xe3, 0x03, 0x3d, +0x3c, 0xed, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xf1, 0x0c, 0x6f, 0x25, +0xdb, 0xaa, 0xb7, 0x80, 0x03, 0x04, 0x51, 0x02, +0x00, 0xfd, 0x01, 0x22, 0x21, 0xdb, 0x11, 0xe9, +0x6a, 0xc9, 0x77, 0xc6, 0x98, 0x03, 0x02, 0x7a, +0x14, 0x37, 0x5b, 0x66, 0xe2, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0xc8, +0x44, 0xba, 0x12, 0xaf, 0xbc, 0x3c, 0x03, 0x04, +0x51, 0x02, 0x00, 0xfe, 0x01, 0x22, 0x21, 0xd6, +0xc8, 0x27, 0x5c, 0x20, 0xea, 0x07, 0x57, 0xef, +0x34, 0x05, 0x35, 0x47, 0xab, 0xc0, 0xd6, 0x2b, +0x5b, 0x72, 0x5a, 0xa9, 0x2c, 0xd8, 0x0d, 0x31, +0xee, 0xb7, 0x74, 0x7c, 0xde, 0x60, 0x45, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xff, 0x01, 0x22, +0x21, 0x8f, 0xb0, 0xa4, 0x50, 0xc7, 0x8e, 0x69, +0x16, 0x1b, 0x4f, 0xa2, 0xbc, 0xa8, 0xa5, 0x68, +0xf4, 0x93, 0x84, 0xb6, 0xef, 0xf4, 0xa1, 0xa1, +0xfd, 0x8c, 0xd5, 0xa5, 0xc4, 0xd7, 0xd7, 0xb2, +0xcf, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x00, +0x01, 0x22, 0x21, 0x55, 0xf8, 0xf2, 0xac, 0x55, +0x8f, 0x01, 0x91, 0x53, 0x3e, 0xeb, 0x00, 0x73, +0x3b, 0xef, 0xeb, 0xb6, 0x0b, 0xcd, 0xd3, 0x94, +0x18, 0xa4, 0xeb, 0xb0, 0x20, 0x57, 0x84, 0x54, +0x2d, 0xa2, 0x0a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x01, 0x01, 0x22, 0x21, 0x18, 0xb3, 0xb1, +0xeb, 0x1c, 0xe2, 0xeb, 0xfe, 0x7a, 0x88, 0x3b, +0x85, 0x9e, 0xe4, 0x15, 0x64, 0x9d, 0xcb, 0x09, +0xd0, 0x80, 0x77, 0x67, 0xb7, 0x7d, 0xb7, 0xe6, +0xbf, 0xfb, 0x2f, 0xa9, 0x06, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x02, 0x01, 0x22, 0x21, 0x5b, +0x94, 0x92, 0x93, 0x30, 0x67, 0xbc, 0x65, 0x7e, +0x1b, 0x44, 0x65, 0x66, 0x01, 0x08, 0xd2, 0xaa, +0xec, 0x02, 0x6f, 0x06, 0x92, 0x41, 0x90, 0x3e, +0xa3, 0xab, 0xca, 0xae, 0x35, 0x08, 0x7f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x03, 0x01, 0x22, +0x21, 0xb9, 0x70, 0x50, 0x65, 0x86, 0xd0, 0x9d, +0xbc, 0xdc, 0x70, 0x99, 0xdb, 0x24, 0xb7, 0x67, +0x02, 0xcf, 0x02, 0x65, 0x7d, 0x67, 0x26, 0x9d, +0x20, 0x15, 0xa7, 0x84, 0xeb, 0xab, 0xda, 0x4d, +0x15, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x04, +0x01, 0x22, 0x21, 0x43, 0xb7, 0x70, 0x65, 0xe5, +0x6f, 0x42, 0x2c, 0x8d, 0x10, 0xe0, 0xd2, 0x23, +0x36, 0xca, 0xce, 0xe3, 0x7c, 0x94, 0x27, 0x92, +0xda, 0xfe, 0xaa, 0xcf, 0x39, 0xed, 0x86, 0x2a, +0x20, 0x93, 0xba, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x05, 0x01, 0x22, 0x21, 0x42, 0xc0, 0xd9, +0x11, 0x1a, 0x9b, 0xb6, 0x8c, 0x35, 0xcb, 0x66, +0x15, 0x22, 0x08, 0xc3, 0x03, 0x3d, 0x22, 0x3f, +0xa9, 0xdc, 0xcd, 0xb9, 0x4f, 0x58, 0x1d, 0x09, +0xcd, 0xcd, 0x2e, 0xd1, 0x8e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x06, 0x01, 0x22, 0x21, 0x56, +0xfc, 0xdf, 0x59, 0xd5, 0xbf, 0x49, 0x8e, 0x7b, +0xf4, 0xef, 0x49, 0xb0, 0xd2, 0xe2, 0x39, 0x39, +0xf0, 0xad, 0x20, 0x3c, 0x89, 0x48, 0xf5, 0x1a, +0xd3, 0x36, 0x9a, 0x83, 0xb2, 0x88, 0x94, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x07, 0x01, 0x22, +0x21, 0x9f, 0xd2, 0x83, 0x74, 0xb6, 0x42, 0x3d, +0x89, 0x4b, 0xda, 0xcf, 0xa5, 0x79, 0xf1, 0xd5, +0xc0, 0x40, 0xa8, 0xb4, 0x8d, 0x3e, 0x58, 0x0c, +0xc8, 0xc8, 0x76, 0xdb, 0xc9, 0xfe, 0xa3, 0x6d, +0xcd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x08, +0x01, 0x22, 0x21, 0xf1, 0x54, 0x57, 0x58, 0x92, +0x13, 0x12, 0x70, 0xd5, 0xd2, 0xad, 0xe3, 0x3d, +0x7a, 0x9c, 0x77, 0xa4, 0xbc, 0xa5, 0x07, 0xd2, +0xca, 0x97, 0xe7, 0x2e, 0x6c, 0xc3, 0xe8, 0x94, +0xe2, 0xf0, 0xf7, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x09, 0x01, 0x22, 0x21, 0x6b, 0x6e, 0xe2, +0xba, 0x91, 0x33, 0xb3, 0xeb, 0x5f, 0xb8, 0x1a, +0xa9, 0xcd, 0x04, 0x60, 0xb1, 0x3b, 0x99, 0x59, +0x97, 0x17, 0x66, 0xeb, 0xa4, 0xee, 0x4f, 0x48, +0xc0, 0xa5, 0x8a, 0x8b, 0x5e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0a, 0x01, 0x22, 0x21, 0x1f, +0x55, 0xe4, 0xa2, 0x2f, 0xbc, 0xac, 0x18, 0xc7, +0x27, 0xf7, 0xc2, 0x11, 0x08, 0x84, 0xec, 0xd0, +0x81, 0xce, 0xca, 0x36, 0xd5, 0x4c, 0x3a, 0x09, +0x07, 0x0b, 0xb3, 0x4f, 0xd3, 0xe6, 0x6e, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0b, 0x01, 0x22, +0x21, 0xf6, 0x01, 0x54, 0x88, 0x75, 0x3d, 0x2b, +0x38, 0x3d, 0x39, 0xf5, 0x8c, 0x5f, 0xf4, 0x23, +0x5f, 0x62, 0x61, 0xbf, 0x27, 0x1f, 0xb3, 0xcb, +0x1a, 0x32, 0x8f, 0xba, 0xed, 0xfb, 0x7b, 0x55, +0x27, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x0c, +0x01, 0x22, 0x21, 0x59, 0x52, 0x41, 0x97, 0xc2, +0xdd, 0x3d, 0xc3, 0xec, 0x75, 0xf0, 0x67, 0xdc, +0x2d, 0x0e, 0x73, 0x9a, 0xf7, 0xd6, 0x59, 0xd9, +0xe5, 0x0d, 0xe1, 0x3c, 0x7e, 0x9e, 0xcd, 0x8b, +0x13, 0x65, 0xa0, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x0d, 0x01, 0x22, 0x21, 0xb4, 0xd8, 0xcb, +0xf6, 0x1b, 0x4d, 0xbb, 0x1d, 0x6b, 0xba, 0x3c, +0x2e, 0x6a, 0x24, 0xd3, 0xb5, 0xbd, 0x1d, 0x9c, +0xc3, 0x07, 0xc8, 0xdf, 0xe1, 0xd4, 0xd4, 0xb3, +0xb2, 0xa8, 0xee, 0xd4, 0xc9, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x0e, 0x01, 0x22, 0x21, 0x50, +0x9c, 0xee, 0x93, 0xbd, 0x4e, 0xe9, 0xd8, 0x56, +0x50, 0xff, 0xac, 0x6e, 0x7f, 0x57, 0xd7, 0xb9, +0xa8, 0x89, 0x3d, 0x5c, 0x0e, 0x29, 0x03, 0x2d, +0xe4, 0x01, 0xdb, 0x41, 0x6b, 0x73, 0xfe, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x0f, 0x01, 0x22, +0x21, 0xe7, 0x38, 0x46, 0x19, 0x5a, 0xfa, 0xc8, +0xd2, 0xc7, 0xa4, 0x60, 0xc6, 0xfb, 0x38, 0x82, +0x41, 0x4f, 0x1a, 0xf0, 0xae, 0x97, 0x04, 0x4f, +0x83, 0x8d, 0xfb, 0x9b, 0xe6, 0x44, 0x34, 0x0b, +0xb9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x10, +0x01, 0x22, 0x21, 0x41, 0xe1, 0xae, 0xe4, 0x1a, +0xeb, 0x15, 0x64, 0xe8, 0x8a, 0x89, 0xd0, 0xe6, +0x15, 0x78, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x4d, 0xb8, 0xf4, 0x7b, +0xbd, 0xe0, 0x36, 0x8e, 0x03, 0x04, 0x51, 0x02, +0x00, 0x11, 0x01, 0x22, 0x21, 0xa4, 0x68, 0x3c, +0xc9, 0xcd, 0x01, 0xa9, 0x66, 0xae, 0xcf, 0x8a, +0x9d, 0x05, 0x55, 0xba, 0xee, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd4, 0x75, +0x06, 0xf2, 0xcd, 0x1a, 0xe5, 0x3d, 0x03, 0x04, +0x51, 0x02, 0x00, 0x12, 0x01, 0x22, 0x21, 0x91, +0x34, 0x71, 0x65, 0x1a, 0x76, 0x00, 0x17, 0xf1, +0xd3, 0x15, 0x79, 0xb1, 0xa8, 0x10, 0x13, 0x90, +0x4f, 0x33, 0xe1, 0x9e, 0xf5, 0x86, 0xaa, 0x8e, +0x81, 0x06, 0x11, 0xfe, 0x56, 0x00, 0x61, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x13, 0x01, 0x22, +0x21, 0xcf, 0x27, 0x5e, 0xec, 0x2c, 0x6d, 0x60, +0x22, 0xaa, 0x0d, 0x3b, 0xf7, 0x65, 0x2c, 0xfc, +0x08, 0xd0, 0x9e, 0xf8, 0x55, 0xe4, 0x07, 0x2a, +0xfc, 0xad, 0x44, 0xec, 0xf1, 0xfe, 0x00, 0xad, +0x72, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x14, +0x01, 0x22, 0x21, 0x4b, 0xee, 0x0d, 0xad, 0x41, +0x4a, 0x87, 0x79, 0x50, 0xb8, 0x09, 0xdb, 0x45, +0x79, 0x8f, 0x43, 0x40, 0x72, 0xfd, 0x92, 0xba, +0x3f, 0x04, 0x9a, 0xcf, 0x50, 0xa2, 0x26, 0x91, +0x0b, 0x23, 0xa9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x15, 0x01, 0x22, 0x21, 0xae, 0x05, 0x50, +0x7e, 0x72, 0x2c, 0xe0, 0x8b, 0x63, 0xd3, 0x94, +0x4a, 0x5a, 0x48, 0xb6, 0xe4, 0x16, 0x8b, 0xc4, +0xcb, 0x48, 0x68, 0x96, 0xe1, 0x69, 0x70, 0x01, +0xb9, 0x87, 0x88, 0xe7, 0xad, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x16, 0x01, 0x22, 0x21, 0x2e, +0xef, 0x7e, 0x2e, 0x99, 0xaf, 0xa8, 0xaa, 0xe8, +0x3f, 0x03, 0x83, 0x3c, 0x4a, 0x09, 0x8d, 0x46, +0x2d, 0x4d, 0x07, 0x6f, 0xab, 0x23, 0x6f, 0xc8, +0x67, 0xdf, 0x32, 0x3e, 0x46, 0x8c, 0xdd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x17, 0x01, 0x22, +0x21, 0xe0, 0x5a, 0xb3, 0x6c, 0x8d, 0xae, 0x4a, +0x9e, 0xd1, 0x89, 0x7c, 0x81, 0x90, 0x21, 0xa1, +0x80, 0x8c, 0x05, 0xab, 0x82, 0x50, 0x09, 0xa8, +0x5a, 0x7c, 0x83, 0x11, 0x1a, 0x60, 0x13, 0x7e, +0x8e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x18, +0x01, 0x22, 0x21, 0x17, 0xd7, 0x10, 0x5e, 0xa0, +0xca, 0x39, 0xff, 0xbe, 0x95, 0xa7, 0xce, 0xe6, +0x21, 0x0c, 0xa8, 0xd8, 0xa1, 0x0f, 0x99, 0xf7, +0x1a, 0x08, 0x20, 0x9b, 0xc6, 0x4e, 0x87, 0x71, +0x9c, 0x0c, 0x2a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x19, 0x01, 0x22, 0x21, 0x7e, 0x68, 0xd5, +0x39, 0xed, 0x0e, 0xf4, 0x15, 0x48, 0x6d, 0xfe, +0xb2, 0x85, 0x0c, 0x62, 0xcb, 0xe0, 0x37, 0x40, +0x30, 0x81, 0xab, 0x6d, 0x4c, 0xfb, 0x70, 0xbe, +0x9c, 0x5c, 0xbe, 0x27, 0xb5, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1a, 0x01, 0x22, 0x21, 0x0b, +0xf7, 0xe1, 0x34, 0xa4, 0x0e, 0x3e, 0xa1, 0xd1, +0xa5, 0x5e, 0x36, 0x7a, 0x02, 0xe7, 0xec, 0x1c, +0xae, 0x09, 0x2c, 0x0b, 0xcd, 0x22, 0x8d, 0x29, +0x4a, 0x20, 0xe4, 0xc6, 0xea, 0x7c, 0x62, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1b, 0x01, 0x22, +0x21, 0xde, 0x8c, 0x07, 0x55, 0x6f, 0x6c, 0xc1, +0xc3, 0x6d, 0xa5, 0x53, 0x09, 0x0b, 0x62, 0xe3, +0xe4, 0xd7, 0x24, 0x73, 0x5a, 0x66, 0xa8, 0x39, +0xd1, 0xe8, 0x7f, 0x64, 0x5e, 0x62, 0xb7, 0xe7, +0x7e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x1c, +0x01, 0x22, 0x21, 0x3f, 0x06, 0x04, 0x91, 0x14, +0x20, 0xd0, 0x6a, 0xef, 0x33, 0x5b, 0x43, 0xef, +0x6f, 0xd9, 0x68, 0x1e, 0xbe, 0x93, 0x7a, 0x90, +0x85, 0x59, 0xa2, 0xb2, 0x29, 0x21, 0x46, 0xac, +0x47, 0x30, 0xd9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x1d, 0x01, 0x22, 0x21, 0x9a, 0x2a, 0x03, +0xd3, 0xd8, 0x35, 0xd0, 0x78, 0xda, 0xac, 0x68, +0x80, 0x06, 0xdc, 0x2b, 0xfb, 0xa1, 0xd0, 0xc6, +0x0a, 0xa8, 0xe9, 0xdf, 0x1f, 0x99, 0x1e, 0xd3, +0xf6, 0x95, 0x4e, 0x45, 0x09, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x1e, 0x01, 0x22, 0x21, 0xb7, +0xe1, 0x43, 0x33, 0xac, 0x7e, 0x55, 0xe5, 0xf0, +0xb9, 0xf5, 0x1d, 0xa1, 0x9c, 0x70, 0x18, 0xf8, +0x38, 0x2b, 0x8f, 0x92, 0xa4, 0x78, 0xaa, 0xab, +0x40, 0xad, 0x65, 0x94, 0x36, 0xa5, 0x44, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x1f, 0x01, 0x22, +0x21, 0x39, 0xaa, 0x24, 0x67, 0x51, 0x4e, 0xde, +0xd1, 0x44, 0x4b, 0xfd, 0xa0, 0xc2, 0x7c, 0x9d, +0x5c, 0xc5, 0x85, 0x8f, 0x9d, 0x02, 0x95, 0x43, +0xa7, 0xab, 0xf4, 0xe2, 0x65, 0x7f, 0xeb, 0x4d, +0x38, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x20, +0x01, 0x22, 0x21, 0x58, 0x00, 0xd3, 0x1b, 0x27, +0x8d, 0x88, 0xea, 0x11, 0x95, 0x50, 0xe1, 0x77, +0xfd, 0xce, 0x01, 0x95, 0xac, 0x68, 0xd9, 0xf8, +0x42, 0xf0, 0xe1, 0x52, 0x29, 0x71, 0x75, 0x99, +0x3f, 0x34, 0x22, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x21, 0x01, 0x22, 0x21, 0x68, 0x42, 0xb3, +0x88, 0xdd, 0x5a, 0x3e, 0x92, 0xcf, 0x04, 0x4d, +0xc0, 0x02, 0x26, 0x43, 0xc2, 0x8c, 0x12, 0xef, +0x4d, 0x17, 0x3c, 0xb9, 0x74, 0x62, 0x7d, 0xf0, +0x96, 0x20, 0xb9, 0xa2, 0x52, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x22, 0x01, 0x22, 0x21, 0x7b, +0x3d, 0xde, 0x0a, 0xd0, 0x7c, 0x6f, 0x53, 0x98, +0x1b, 0x74, 0xef, 0x0f, 0xb6, 0x1e, 0x91, 0xff, +0x44, 0x63, 0x0c, 0xe2, 0x10, 0x0e, 0x26, 0xee, +0xe8, 0x17, 0xa1, 0x8a, 0x76, 0x0a, 0x35, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x23, 0x01, 0x22, +0x21, 0x9f, 0x13, 0x73, 0xe5, 0xdb, 0xf8, 0xaf, +0x07, 0x21, 0xa1, 0x42, 0x14, 0x49, 0x6f, 0x44, +0x19, 0x9e, 0x55, 0x56, 0x96, 0xd2, 0xbf, 0xe1, +0x81, 0xa8, 0x9b, 0xd7, 0xff, 0x5d, 0xfa, 0xe6, +0x63, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x24, +0x01, 0x22, 0x21, 0x7a, 0x53, 0x47, 0xb1, 0x84, +0xe8, 0x00, 0x57, 0x2e, 0x26, 0x2d, 0xcc, 0xd4, +0xd0, 0x81, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xc0, 0x60, 0x80, 0x9c, +0x3e, 0x0b, 0xee, 0xbb, 0x03, 0x04, 0x51, 0x02, +0x00, 0x25, 0x01, 0x22, 0x21, 0x9c, 0x02, 0xfb, +0x8b, 0x22, 0x4b, 0xb2, 0x75, 0x3a, 0x78, 0x24, +0x1d, 0x57, 0x6c, 0x02, 0x6a, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xee, +0xae, 0x9b, 0x9c, 0x50, 0x86, 0x0a, 0x03, 0x04, +0x51, 0x02, 0x00, 0x26, 0x01, 0x22, 0x21, 0x24, +0xb2, 0x84, 0x97, 0x1d, 0x72, 0xfa, 0xb4, 0x06, +0x33, 0x31, 0x30, 0x64, 0x35, 0x6e, 0x15, 0x0d, +0x1b, 0xad, 0xb1, 0x4e, 0x7e, 0x45, 0x03, 0x43, +0x99, 0x79, 0xc8, 0xd4, 0xce, 0x6b, 0x90, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x27, 0x01, 0x22, +0x21, 0x25, 0x7e, 0x43, 0x6e, 0x7b, 0x39, 0xa4, +0x71, 0x97, 0x78, 0x31, 0xde, 0x82, 0x56, 0xe2, +0xd3, 0xbd, 0x03, 0xe3, 0x0b, 0x9b, 0x70, 0xc4, +0x98, 0x9e, 0x24, 0x53, 0xee, 0x64, 0xa2, 0x55, +0x84, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x28, +0x01, 0x22, 0x21, 0xbe, 0x01, 0x69, 0xe8, 0x72, +0xbb, 0x64, 0x58, 0x72, 0xa2, 0x6c, 0x46, 0xa1, +0x2e, 0xa4, 0x80, 0xb2, 0xf2, 0x37, 0xd3, 0x01, +0xe6, 0x91, 0xf8, 0x70, 0xd5, 0xbc, 0x08, 0x09, +0x2b, 0x8c, 0xdd, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x29, 0x01, 0x22, 0x21, 0xb5, 0x17, 0xa1, +0x51, 0x29, 0x7c, 0x46, 0x0f, 0x7e, 0x47, 0x32, +0x4b, 0x17, 0x2c, 0x63, 0xf1, 0xe9, 0x7b, 0x48, +0xa1, 0x46, 0xf2, 0xda, 0xe4, 0x9e, 0xd7, 0x9b, +0x7e, 0x5d, 0x66, 0x28, 0x5f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2a, 0x01, 0x22, 0x21, 0x80, +0x22, 0x5e, 0xac, 0x64, 0x21, 0xa5, 0x5e, 0xb9, +0xd0, 0xce, 0xdc, 0xfc, 0x88, 0x9c, 0xc4, 0xad, +0x79, 0xd0, 0xc2, 0xf6, 0xfb, 0x2a, 0x11, 0x85, +0x33, 0x06, 0xf9, 0xc3, 0x42, 0xaf, 0x8b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2b, 0x01, 0x22, +0x21, 0x03, 0x72, 0xf2, 0x07, 0xfa, 0xf2, 0xc6, +0x83, 0x74, 0xda, 0xa3, 0x1c, 0x16, 0x8a, 0xab, +0x79, 0x84, 0x87, 0xde, 0x77, 0x86, 0x50, 0xe0, +0x52, 0x0e, 0x6b, 0x46, 0x06, 0xd5, 0x9c, 0x9f, +0x9b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x2c, +0x01, 0x22, 0x21, 0x92, 0x4a, 0xdd, 0xf2, 0x3d, +0x62, 0xb2, 0xc9, 0x14, 0xc3, 0xff, 0x49, 0x6d, +0xd2, 0xc8, 0xa1, 0x63, 0xb4, 0xfa, 0x21, 0xb1, +0x89, 0x34, 0xda, 0xd4, 0x9f, 0x7a, 0x82, 0x6f, +0x66, 0x63, 0xa4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x2d, 0x01, 0x22, 0x21, 0x8b, 0xc1, 0x61, +0x01, 0x0b, 0x67, 0xe4, 0x5c, 0xa8, 0xf1, 0x4d, +0x43, 0xb2, 0xe6, 0x6e, 0x1a, 0x89, 0xc7, 0xbf, +0xf3, 0xef, 0x0f, 0xa0, 0xc7, 0x1d, 0x5b, 0x04, +0x0d, 0x54, 0xc9, 0x25, 0x57, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x2e, 0x01, 0x22, 0x21, 0xec, +0x10, 0x9e, 0x9e, 0xf8, 0xb1, 0x81, 0xa0, 0xc4, +0x4b, 0xca, 0x92, 0xc7, 0x99, 0x7b, 0xc9, 0xb7, +0xd4, 0xe8, 0x8e, 0x94, 0xd4, 0xdf, 0x43, 0xfb, +0x5a, 0x98, 0x38, 0x7c, 0x41, 0x41, 0x62, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x2f, 0x01, 0x22, +0x21, 0x82, 0xad, 0x4a, 0x71, 0x52, 0x98, 0x01, +0x24, 0x2a, 0xcc, 0xcc, 0x8c, 0x99, 0x09, 0x16, +0x1b, 0xa5, 0x5f, 0x02, 0xd2, 0x92, 0x31, 0x5d, +0xfb, 0x61, 0x5a, 0x38, 0x58, 0x23, 0x98, 0x00, +0x3d, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x30, +0x01, 0x22, 0x21, 0x2d, 0x69, 0x71, 0x0a, 0x19, +0xc9, 0xad, 0xc2, 0x96, 0xe1, 0x0e, 0x8b, 0x6c, +0xa3, 0xc7, 0x4f, 0x53, 0x06, 0x0f, 0x70, 0xf6, +0x9c, 0x30, 0xdb, 0x27, 0xea, 0x5f, 0xb4, 0x9d, +0x51, 0x26, 0xb4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x31, 0x01, 0x22, 0x21, 0x1f, 0x7f, 0x5d, +0x74, 0x5c, 0xe3, 0xad, 0xa6, 0x34, 0xd7, 0xd1, +0x23, 0xa6, 0x76, 0x9d, 0xaf, 0x7c, 0x48, 0x3c, +0xf8, 0x04, 0x08, 0x9a, 0xa3, 0xba, 0xb7, 0xa3, +0x33, 0xff, 0x5b, 0x92, 0x47, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x32, 0x01, 0x22, 0x21, 0xad, +0x0b, 0x36, 0xff, 0x18, 0xed, 0x8b, 0x0d, 0x7b, +0xd8, 0x4c, 0x27, 0x4d, 0xf9, 0x4c, 0x27, 0x25, +0x19, 0x63, 0x52, 0x9c, 0x97, 0x6b, 0xf6, 0xf0, +0xa5, 0x02, 0x0a, 0x5a, 0x1f, 0x59, 0xf2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x33, 0x01, 0x22, +0x21, 0x52, 0x35, 0xd9, 0x48, 0x4c, 0x2c, 0x76, +0x72, 0x3c, 0xa6, 0x6f, 0x46, 0x05, 0xfb, 0x81, +0x73, 0x5b, 0x86, 0xf6, 0xea, 0x92, 0xc5, 0x98, +0x0a, 0xb9, 0xab, 0xb7, 0x5f, 0x5b, 0xae, 0x30, +0xb6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x34, +0x01, 0x22, 0x21, 0xeb, 0xcd, 0xa5, 0x9b, 0x5b, +0x17, 0xda, 0x2c, 0x4b, 0xf1, 0x2a, 0x80, 0xd4, +0x8c, 0xbc, 0xef, 0x12, 0x5b, 0x99, 0xfe, 0x12, +0x60, 0x88, 0xcf, 0x91, 0x88, 0xea, 0x7e, 0x7d, +0x3d, 0xde, 0xdb, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x35, 0x01, 0x22, 0x21, 0x20, 0x50, 0xd3, +0x89, 0x25, 0x28, 0xdb, 0xf9, 0xb1, 0x91, 0x75, +0x15, 0x5c, 0xad, 0x79, 0xc9, 0xeb, 0x82, 0xa7, +0xd1, 0x7e, 0xeb, 0xa5, 0xeb, 0x21, 0x8e, 0x4b, +0x42, 0x8f, 0xa6, 0xd3, 0x4f, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x36, 0x01, 0x22, 0x21, 0xa3, +0xb4, 0xac, 0x02, 0x20, 0x92, 0xc7, 0x4e, 0xad, +0x70, 0x07, 0x0f, 0xea, 0xa4, 0xad, 0xaf, 0xc7, +0xa2, 0xce, 0xdb, 0xd8, 0x07, 0x52, 0x10, 0x3b, +0x4d, 0xb0, 0x9e, 0x9f, 0x13, 0x7d, 0xfe, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x37, 0x01, 0x22, +0x21, 0xb9, 0x3c, 0xf4, 0xcd, 0x31, 0xe1, 0x4e, +0x0b, 0x3e, 0x25, 0x3b, 0x13, 0x92, 0x2e, 0x49, +0x0a, 0xb8, 0xa3, 0xcf, 0x46, 0xe9, 0xa2, 0x20, +0x3d, 0x62, 0xb4, 0x72, 0x8d, 0xbb, 0xc7, 0x80, +0xb8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x38, +0x01, 0x22, 0x21, 0xb6, 0xba, 0xef, 0x7e, 0x2b, +0xc6, 0x37, 0x65, 0x06, 0x49, 0xf1, 0xeb, 0xb3, +0x10, 0xc6, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xa0, 0x24, 0xc8, 0x68, +0x92, 0x7e, 0xd5, 0x66, 0x03, 0x04, 0x51, 0x02, +0x00, 0x39, 0x01, 0x22, 0x21, 0x2b, 0x06, 0x00, +0x46, 0x80, 0xf2, 0x2e, 0xb8, 0x48, 0x64, 0x77, +0x1b, 0xa2, 0x65, 0xd6, 0x77, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x78, +0x15, 0x63, 0x56, 0xde, 0xbb, 0xe9, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3a, 0x01, 0x22, 0x21, 0x14, +0xb8, 0xe2, 0xd6, 0xc1, 0x7f, 0x7c, 0x4a, 0xaf, +0x45, 0xdc, 0x3b, 0xce, 0xac, 0xb4, 0x23, 0xe8, +0x3e, 0x2a, 0x5b, 0x72, 0x3d, 0x70, 0xa9, 0x4c, +0x9b, 0xe3, 0x31, 0xe9, 0x0a, 0x09, 0x21, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3b, 0x01, 0x22, +0x21, 0x0e, 0x54, 0xe1, 0x53, 0x25, 0xbc, 0xfd, +0x20, 0x17, 0xf4, 0xe3, 0x00, 0xbb, 0x63, 0x62, +0xf1, 0xd1, 0x95, 0x71, 0x22, 0x93, 0x6f, 0x79, +0x3a, 0x25, 0x29, 0xa6, 0x18, 0xd5, 0xf0, 0x3f, +0x10, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x3c, +0x01, 0x22, 0x21, 0xda, 0x37, 0x53, 0x35, 0x0d, +0x96, 0xe9, 0x12, 0xf0, 0x43, 0xff, 0xd5, 0x26, +0xec, 0xb0, 0x0c, 0x03, 0xf5, 0xb1, 0x9d, 0x44, +0x05, 0x0a, 0xaf, 0x98, 0xb4, 0x6f, 0x8f, 0x13, +0x61, 0xb6, 0xe1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x3d, 0x01, 0x22, 0x21, 0xd9, 0x84, 0x8f, +0x03, 0xab, 0x48, 0x5f, 0xbb, 0xd6, 0xc3, 0xe9, +0x7b, 0x11, 0xca, 0xf1, 0x0c, 0xf4, 0x90, 0xc3, +0xea, 0x55, 0xc1, 0x37, 0xe5, 0x36, 0x59, 0xc9, +0xd7, 0xaf, 0x18, 0xfd, 0x16, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x3e, 0x01, 0x22, 0x21, 0x2d, +0x59, 0x11, 0x80, 0x16, 0xe5, 0x03, 0x26, 0x5d, +0x49, 0xb6, 0x84, 0x28, 0x9b, 0x00, 0x18, 0xb3, +0x92, 0xaf, 0xe3, 0x87, 0x94, 0x58, 0xc8, 0x35, +0x9d, 0xf9, 0x9d, 0x75, 0x37, 0xb0, 0xa8, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x3f, 0x01, 0x22, +0x21, 0xb4, 0xff, 0xba, 0x15, 0x2a, 0xd1, 0x2f, +0xc1, 0x30, 0x74, 0x71, 0x4f, 0x6e, 0x36, 0x70, +0x0c, 0xab, 0x2a, 0x26, 0xa0, 0xb3, 0xaf, 0xcb, +0xaa, 0xb3, 0x2b, 0xbd, 0x8c, 0x45, 0x9e, 0xfa, +0xb6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x40, +0x01, 0x22, 0x21, 0x4c, 0xa8, 0xbb, 0x3d, 0x9a, +0x85, 0xa0, 0xb0, 0xef, 0x1f, 0x78, 0xf2, 0x24, +0x3e, 0xf0, 0x70, 0xff, 0x43, 0xe9, 0x22, 0x2f, +0x6e, 0xab, 0x97, 0x6a, 0x48, 0x03, 0x79, 0x6d, +0x67, 0x46, 0x2a, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x41, 0x01, 0x22, 0x21, 0x3d, 0xe3, 0x35, +0xd1, 0xa1, 0xfc, 0x71, 0x31, 0xe4, 0x1c, 0xb4, +0xe8, 0xcb, 0x1a, 0x20, 0x7c, 0x6a, 0xf9, 0x45, +0x4b, 0x74, 0x9a, 0x9f, 0xf3, 0x89, 0xe8, 0x8d, +0xd8, 0xf1, 0xbf, 0xe0, 0x1b, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x42, 0x01, 0x22, 0x21, 0x1e, +0xd7, 0xb3, 0x17, 0x3e, 0x67, 0xa6, 0x4a, 0xd0, +0x1a, 0x8a, 0x80, 0x80, 0xf1, 0x6e, 0xf5, 0x3c, +0x85, 0x03, 0xe8, 0x5a, 0xad, 0xeb, 0x33, 0xca, +0x41, 0x8b, 0xe7, 0xce, 0xa7, 0x0f, 0x16, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x43, 0x01, 0x22, +0x21, 0x05, 0xd8, 0x4f, 0x53, 0xc0, 0xd0, 0x23, +0xd0, 0x33, 0xfc, 0xc6, 0xe8, 0xad, 0x04, 0xb7, +0x75, 0xb3, 0x9f, 0xb0, 0xd8, 0x69, 0x2c, 0x9c, +0x1e, 0x17, 0x61, 0x25, 0xbc, 0xb0, 0x04, 0x02, +0x0f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x44, +0x01, 0x22, 0x21, 0xb0, 0xd5, 0x4f, 0x4c, 0xf2, +0xb9, 0xd6, 0x77, 0xe4, 0x64, 0xae, 0x51, 0xc2, +0x44, 0xa8, 0x38, 0x34, 0x8d, 0x94, 0x58, 0x97, +0xe0, 0x3b, 0x80, 0x0d, 0x11, 0xd1, 0x0f, 0x49, +0x7c, 0xd3, 0x8c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x45, 0x01, 0x22, 0x21, 0x2c, 0xd5, 0xb8, +0xe2, 0xe3, 0x2e, 0xc6, 0xaa, 0x71, 0xf2, 0xcf, +0x25, 0xf2, 0xc1, 0xae, 0x2b, 0x92, 0xdb, 0xa8, +0x3a, 0x5e, 0xb6, 0xd2, 0x55, 0x29, 0x49, 0x23, +0x96, 0xdb, 0xb0, 0x8f, 0x14, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x46, 0x01, 0x22, 0x21, 0x64, +0x11, 0x66, 0xc3, 0x9c, 0x87, 0x7b, 0xa1, 0x4e, +0xd2, 0x47, 0xba, 0x62, 0x3f, 0x98, 0xdf, 0x41, +0x45, 0x06, 0xaf, 0xc1, 0xce, 0x86, 0xcf, 0x3b, +0xf7, 0x57, 0x1e, 0x61, 0x7a, 0xb3, 0x18, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x47, 0x01, 0x22, +0x21, 0x01, 0xb4, 0x92, 0x55, 0xe5, 0xb2, 0x62, +0x07, 0x30, 0xdd, 0x24, 0x21, 0x57, 0x8d, 0xfe, +0x20, 0x29, 0x74, 0xa3, 0xf4, 0x6e, 0xb3, 0x4f, +0x76, 0x6c, 0x90, 0xa5, 0x30, 0xb3, 0x4f, 0xaf, +0xa3, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x48, +0x01, 0x22, 0x21, 0x4a, 0x41, 0x1d, 0x42, 0x7d, +0x88, 0x87, 0xa9, 0xcd, 0x99, 0x8d, 0x6b, 0xa9, +0xc3, 0x63, 0x8a, 0x89, 0x36, 0x44, 0x0d, 0xc0, +0x2c, 0xfa, 0x90, 0xb7, 0xeb, 0xe8, 0x91, 0x7c, +0xa8, 0x6d, 0x29, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x49, 0x01, 0x22, 0x21, 0x98, 0x9f, 0xc8, +0x3d, 0x69, 0xe6, 0x11, 0x25, 0x7c, 0xb3, 0x33, +0xdd, 0xf2, 0x90, 0xf6, 0x5a, 0xb3, 0x73, 0xf5, +0x61, 0x3b, 0xcc, 0xa6, 0x42, 0xae, 0x30, 0xf2, +0xbf, 0x5d, 0x7f, 0x8d, 0xb4, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4a, 0x01, 0x22, 0x21, 0xf9, +0x78, 0xf3, 0x02, 0x64, 0xb5, 0x72, 0x71, 0xcd, +0x0b, 0xfa, 0xf0, 0xd2, 0x04, 0xba, 0xee, 0x9d, +0xa3, 0x01, 0x89, 0xf9, 0x92, 0xf6, 0x6c, 0xcd, +0x0a, 0x8f, 0x66, 0xf1, 0xbc, 0xad, 0xb6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4b, 0x01, 0x22, +0x21, 0x0b, 0x88, 0xd3, 0xef, 0x96, 0x86, 0xd9, +0x1b, 0xbc, 0x2a, 0x61, 0x6c, 0x92, 0xb1, 0x99, +0x3c, 0x4a, 0x2e, 0xde, 0x80, 0x8c, 0xb2, 0x97, +0x6a, 0x0c, 0x95, 0x3c, 0x94, 0x21, 0x2a, 0xd9, +0x8b, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x4c, +0x01, 0x22, 0x21, 0xee, 0x9a, 0xb7, 0x6b, 0x09, +0x00, 0x65, 0x02, 0x39, 0xca, 0x6f, 0x13, 0xa1, +0x77, 0x54, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xe2, 0xf6, 0x29, 0xff, +0xb9, 0xc0, 0x7c, 0x8d, 0x03, 0x04, 0x51, 0x02, +0x00, 0x4d, 0x01, 0x22, 0x21, 0xa3, 0x2c, 0xee, +0xbc, 0x0c, 0xbd, 0x37, 0x89, 0xf7, 0x12, 0x44, +0x5c, 0x2f, 0x63, 0x79, 0xb5, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0xc4, +0x8e, 0x34, 0x9e, 0x6d, 0x02, 0x6d, 0x03, 0x04, +0x51, 0x02, 0x00, 0x4e, 0x01, 0x22, 0x21, 0xb9, +0x55, 0xa8, 0xf5, 0x61, 0x4f, 0x42, 0x5d, 0x73, +0x6f, 0x71, 0xe7, 0x41, 0xbe, 0x0c, 0xc5, 0xbc, +0x40, 0x20, 0x1f, 0xa4, 0x1f, 0xb9, 0xff, 0xcf, +0xdf, 0xa3, 0xf6, 0x99, 0x3d, 0x10, 0xb1, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x4f, 0x01, 0x22, +0x21, 0x20, 0xdf, 0x96, 0xda, 0xc0, 0xe0, 0x83, +0x6e, 0x6a, 0xd5, 0x42, 0x53, 0x0d, 0xa6, 0x87, +0xab, 0xd7, 0x9f, 0x94, 0x8b, 0x3e, 0xb6, 0x2f, +0xc1, 0x37, 0xe7, 0x7c, 0x63, 0xaf, 0xb7, 0xee, +0x1c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x50, +0x01, 0x22, 0x21, 0x85, 0xe7, 0xe3, 0xf4, 0x3c, +0x8a, 0x17, 0x42, 0x93, 0x47, 0xb2, 0x76, 0xe0, +0x9d, 0x05, 0x64, 0xa4, 0xf2, 0x72, 0x58, 0x4d, +0xda, 0xf6, 0xd1, 0x2b, 0xac, 0xa9, 0xa8, 0x93, +0xde, 0xc3, 0x49, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x51, 0x01, 0x22, 0x21, 0x2c, 0x36, 0x8a, +0xea, 0x68, 0x23, 0xeb, 0x10, 0x2e, 0x4b, 0xaa, +0x79, 0xef, 0x33, 0x42, 0xee, 0xb6, 0x1b, 0x04, +0x9b, 0x1d, 0xd2, 0xbc, 0xd6, 0xe4, 0x63, 0x3a, +0x07, 0xdd, 0x24, 0x99, 0x88, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x52, 0x01, 0x22, 0x21, 0xe9, +0x21, 0x1b, 0xee, 0xfd, 0x79, 0x2a, 0x4b, 0x15, +0xac, 0x3e, 0xc5, 0x8e, 0xf8, 0xf8, 0x9f, 0x04, +0xb6, 0xf0, 0x9b, 0xbd, 0x77, 0x7f, 0x87, 0xd6, +0xb3, 0x5e, 0x81, 0x41, 0x9f, 0x55, 0xc7, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x53, 0x01, 0x22, +0x21, 0x4b, 0xea, 0x85, 0x76, 0xc5, 0x18, 0x98, +0x7f, 0xb5, 0xad, 0xf5, 0xbb, 0x9c, 0xbd, 0x80, +0x09, 0x17, 0x68, 0x93, 0x47, 0x0b, 0x71, 0x4c, +0xed, 0xa4, 0xa2, 0xc6, 0xa4, 0xf6, 0x6d, 0xf3, +0x29, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x54, +0x01, 0x22, 0x21, 0x83, 0x63, 0x62, 0x81, 0xe9, +0x08, 0x7e, 0xb1, 0x07, 0x4c, 0xdb, 0x3f, 0x16, +0xf1, 0x39, 0xd5, 0xb4, 0x5b, 0x3f, 0xd5, 0x83, +0x4e, 0xfa, 0xcb, 0x8b, 0x23, 0x43, 0xf2, 0x0b, +0x61, 0xc6, 0xc5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x55, 0x01, 0x22, 0x21, 0xdf, 0xba, 0xd3, +0x98, 0x1a, 0x65, 0x3b, 0x2a, 0x65, 0x6c, 0xc3, +0x13, 0x76, 0x34, 0x3a, 0x45, 0xa5, 0xa9, 0xc5, +0xdc, 0x4e, 0x6d, 0xcb, 0xbe, 0xc8, 0xbf, 0xb4, +0xeb, 0xf1, 0xeb, 0xa9, 0x80, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x56, 0x01, 0x22, 0x21, 0xf0, +0x78, 0x28, 0xc8, 0xaf, 0x9a, 0xed, 0x6e, 0xba, +0xdc, 0xf6, 0x69, 0xb5, 0x19, 0xcc, 0x41, 0x75, +0x70, 0xc3, 0x18, 0x17, 0xe6, 0xa1, 0x92, 0x47, +0x00, 0x84, 0x1c, 0xd4, 0x77, 0xa2, 0x5a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x57, 0x01, 0x22, +0x21, 0xdd, 0x4d, 0xc1, 0xe2, 0x44, 0xee, 0x5e, +0x93, 0xf0, 0x9a, 0xb9, 0xfe, 0x75, 0xb4, 0x1e, +0xa8, 0xc8, 0x11, 0x37, 0x5e, 0x4a, 0xd8, 0xaa, +0xdf, 0x9b, 0xd8, 0x4f, 0xe7, 0xa3, 0x21, 0x95, +0x09, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x58, +0x01, 0x22, 0x21, 0x8c, 0x9c, 0x35, 0x7f, 0x33, +0x7c, 0x69, 0x60, 0x5f, 0x68, 0x01, 0x23, 0x44, +0x33, 0x2c, 0x5c, 0x55, 0x2e, 0x40, 0xb6, 0xea, +0xb3, 0xdf, 0x73, 0xf3, 0xa0, 0x7c, 0x43, 0x93, +0x93, 0x50, 0x61, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x59, 0x01, 0x22, 0x21, 0x8b, 0x91, 0x79, +0xf9, 0x27, 0xb1, 0xf5, 0x00, 0x10, 0x37, 0x96, +0xf7, 0x0b, 0x62, 0xa6, 0x66, 0x22, 0x6d, 0x6d, +0xa0, 0xfa, 0x7f, 0xef, 0xec, 0x65, 0x18, 0xda, +0x11, 0x65, 0xd5, 0x7e, 0x41, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5a, 0x01, 0x22, 0x21, 0x81, +0xb9, 0xea, 0x12, 0x6f, 0x13, 0x0e, 0xcc, 0xd8, +0x32, 0xad, 0x62, 0xf1, 0x87, 0x33, 0x36, 0x56, +0x15, 0xeb, 0xc8, 0xff, 0x2f, 0x16, 0xc1, 0xc3, +0xc3, 0xec, 0x0c, 0x9f, 0xf2, 0xdb, 0x9f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5b, 0x01, 0x22, +0x21, 0x00, 0xf4, 0xb1, 0x95, 0x5a, 0x95, 0xdc, +0xd2, 0xdb, 0x67, 0x50, 0xfb, 0x65, 0x41, 0x66, +0x8f, 0x5e, 0x71, 0xf3, 0xb1, 0x9a, 0x40, 0x90, +0xcb, 0xe0, 0x7d, 0xdf, 0x08, 0xe1, 0x23, 0xe7, +0x66, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x5c, +0x01, 0x22, 0x21, 0xf0, 0xed, 0x71, 0x87, 0x80, +0x83, 0xe2, 0xbc, 0x82, 0x26, 0xc9, 0x4d, 0xfc, +0xfd, 0x76, 0xc6, 0xa4, 0xd4, 0x17, 0xb0, 0xee, +0xd7, 0xad, 0x07, 0x39, 0x52, 0x85, 0xcf, 0x87, +0x9d, 0x5b, 0xe5, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x5d, 0x01, 0x22, 0x21, 0x8d, 0x24, 0xf7, +0xe6, 0x2b, 0x47, 0x23, 0x86, 0xc6, 0xc3, 0xf3, +0xb1, 0x2a, 0x53, 0x90, 0x34, 0x01, 0xc9, 0x68, +0x71, 0x87, 0x50, 0x13, 0x2c, 0xcb, 0x9f, 0xcd, +0x6b, 0x4c, 0x2c, 0x52, 0xb2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x5e, 0x01, 0x22, 0x21, 0x0f, +0x4d, 0x6d, 0xb1, 0x2c, 0x5e, 0x66, 0x53, 0x81, +0x3e, 0x5d, 0xb2, 0x63, 0x19, 0x2d, 0xa1, 0x14, +0x07, 0xcd, 0xc1, 0xca, 0x61, 0x35, 0x9c, 0xaf, +0xec, 0xa9, 0xaf, 0x8a, 0x31, 0x39, 0xba, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x5f, 0x01, 0x22, +0x21, 0x38, 0x57, 0xd9, 0xf4, 0xeb, 0x9e, 0x47, +0xde, 0xdd, 0xd0, 0x9a, 0x57, 0xba, 0xb7, 0x14, +0x4b, 0x52, 0x0c, 0xd9, 0x1c, 0x2e, 0x2b, 0xb0, +0x15, 0x77, 0x2d, 0x58, 0xe2, 0x7e, 0x96, 0xd8, +0x25, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x60, +0x01, 0x22, 0x21, 0x29, 0x96, 0x96, 0x56, 0xcc, +0x92, 0x1b, 0x45, 0x38, 0x53, 0x87, 0x9d, 0xed, +0xdf, 0x90, 0x9d, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x26, 0x91, 0xc4, 0xa5, +0xc2, 0x5f, 0x9a, 0xbb, 0x03, 0x04, 0x51, 0x02, +0x00, 0x61, 0x01, 0x22, 0x21, 0xd3, 0xa1, 0x2a, +0x36, 0x74, 0xab, 0x65, 0x64, 0x94, 0x2b, 0x28, +0x34, 0xbd, 0x8d, 0xfa, 0x70, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb, 0xaa, +0x3f, 0x63, 0x91, 0x78, 0xe5, 0x22, 0x03, 0x04, +0x51, 0x02, 0x00, 0x62, 0x01, 0x22, 0x21, 0xc0, +0x1e, 0x61, 0x81, 0xd7, 0xa2, 0x0c, 0x63, 0x1f, +0x14, 0xed, 0x33, 0x24, 0x4a, 0xaa, 0x7f, 0xa6, +0x32, 0xe3, 0x33, 0x6c, 0x94, 0xbf, 0xc5, 0x62, +0x48, 0xcd, 0x26, 0x12, 0x0c, 0x40, 0x45, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x63, 0x01, 0x22, +0x21, 0x5e, 0x56, 0xc0, 0x8b, 0xce, 0xda, 0x3c, +0x37, 0xed, 0xde, 0xd5, 0x89, 0xe8, 0x5e, 0x44, +0x13, 0x56, 0x22, 0x2e, 0x7b, 0x6d, 0x40, 0xd8, +0x72, 0x76, 0x8a, 0x89, 0xab, 0xfa, 0xbd, 0xcc, +0x8c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x64, +0x01, 0x22, 0x21, 0xc2, 0x44, 0x9b, 0x78, 0xce, +0x4f, 0x42, 0xa2, 0xff, 0x8f, 0xd0, 0x27, 0xa8, +0xce, 0x1a, 0xb9, 0xe4, 0xdf, 0x13, 0xe5, 0x66, +0xa1, 0x05, 0xa8, 0xa1, 0x10, 0xce, 0x1e, 0x0d, +0xf3, 0x18, 0x86, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x65, 0x01, 0x22, 0x21, 0x78, 0xdb, 0xee, +0x74, 0x6f, 0x4d, 0xe2, 0x63, 0xbe, 0x40, 0xfd, +0xf1, 0x71, 0x15, 0x5f, 0x28, 0xa4, 0x0f, 0x48, +0x64, 0x09, 0xa7, 0x33, 0x10, 0x55, 0xa4, 0xe0, +0x40, 0x70, 0xee, 0xcf, 0x93, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x66, 0x01, 0x22, 0x21, 0x91, +0x2d, 0x46, 0xd8, 0xcd, 0xdd, 0xe6, 0xab, 0x93, +0xe2, 0xae, 0xc2, 0x2d, 0xaf, 0xef, 0x0e, 0x0b, +0xa8, 0xb0, 0xab, 0x3e, 0x25, 0x6a, 0x69, 0x24, +0x02, 0xdc, 0x4f, 0x67, 0xca, 0x13, 0x92, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x67, 0x01, 0x22, +0x21, 0x10, 0x55, 0xa8, 0xd3, 0x6d, 0xa6, 0x21, +0xba, 0x91, 0xf7, 0xb8, 0x2c, 0xed, 0xbf, 0x73, +0x7e, 0x44, 0x3e, 0x5f, 0x14, 0x2b, 0xcd, 0x06, +0x70, 0x99, 0x3f, 0x6f, 0xf3, 0x94, 0xe6, 0xb0, +0x9f, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x68, +0x01, 0x22, 0x21, 0x12, 0x90, 0xa5, 0xce, 0xcc, +0x7a, 0x51, 0x75, 0x19, 0x63, 0xa0, 0x48, 0x70, +0x36, 0xdb, 0xf1, 0xaa, 0xb6, 0x0b, 0x6f, 0x0e, +0x38, 0x11, 0x51, 0x98, 0xeb, 0x8c, 0x9b, 0xe5, +0xb6, 0xcc, 0x68, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x69, 0x01, 0x22, 0x21, 0xe9, 0x77, 0xbe, +0xc8, 0x97, 0xc3, 0xab, 0xe7, 0x81, 0x97, 0x29, +0xa3, 0x92, 0x6d, 0x82, 0xb3, 0x88, 0x41, 0x11, +0x3a, 0x3b, 0xed, 0xd0, 0xd2, 0x04, 0xa1, 0x5b, +0xbe, 0xd2, 0x39, 0xac, 0xcd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6a, 0x01, 0x22, 0x21, 0xba, +0x74, 0xf0, 0x41, 0xfc, 0xf6, 0x18, 0x37, 0xd2, +0x57, 0x91, 0x15, 0x34, 0x9e, 0x24, 0x4e, 0x9f, +0x1e, 0x4a, 0xfd, 0xc9, 0xff, 0x69, 0x9b, 0x7e, +0xa1, 0x71, 0x46, 0x49, 0x71, 0xfa, 0x66, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6b, 0x01, 0x22, +0x21, 0x06, 0x3c, 0x42, 0x2f, 0x6d, 0xf7, 0x15, +0x8d, 0xde, 0x89, 0xd8, 0x56, 0xeb, 0xfc, 0xc4, +0xf9, 0x5e, 0x26, 0x4e, 0x61, 0x63, 0xfb, 0x10, +0x7d, 0xe1, 0x6c, 0xaf, 0xf3, 0xf6, 0xd4, 0x85, +0x4e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x6c, +0x01, 0x22, 0x21, 0x70, 0x2a, 0x73, 0x56, 0xc1, +0x28, 0xbb, 0xf3, 0xc6, 0x35, 0xbc, 0xe5, 0xb3, +0xf9, 0xd4, 0x06, 0xd4, 0xb0, 0xd8, 0xa8, 0x69, +0x7a, 0x75, 0xca, 0x18, 0x79, 0xe0, 0x84, 0x28, +0x25, 0xac, 0x25, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x6d, 0x01, 0x22, 0x21, 0xdb, 0x73, 0x97, +0x36, 0x82, 0x92, 0x7b, 0xdd, 0xdd, 0xa4, 0xc7, +0x2d, 0xb6, 0xb9, 0x7b, 0x6f, 0x80, 0x0e, 0xba, +0x54, 0x7e, 0xb7, 0xb6, 0x27, 0x31, 0x86, 0x87, +0x02, 0x5a, 0x54, 0x37, 0xdd, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x6e, 0x01, 0x22, 0x21, 0x21, +0x02, 0xa0, 0xe9, 0x3d, 0x56, 0xf7, 0x08, 0x44, +0x65, 0x76, 0xd4, 0xf7, 0xff, 0x70, 0x77, 0xe3, +0x11, 0x5e, 0x88, 0x35, 0xd5, 0xd7, 0xbe, 0x78, +0xc7, 0x35, 0xb3, 0xd5, 0x77, 0x9e, 0x75, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x6f, 0x01, 0x22, +0x21, 0xa2, 0x5c, 0x8d, 0x55, 0x51, 0x1e, 0x1d, +0xe5, 0xaf, 0xd4, 0x0b, 0xff, 0x51, 0x76, 0xc6, +0x6f, 0x80, 0xa7, 0x8b, 0x81, 0x5b, 0x41, 0xf3, +0x67, 0x9b, 0x1d, 0x90, 0x74, 0xc9, 0x5e, 0xc2, +0x96, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x70, +0x01, 0x22, 0x21, 0x66, 0x26, 0x31, 0xed, 0x28, +0x33, 0xcb, 0xb5, 0x34, 0xda, 0xeb, 0x55, 0x6f, +0x3a, 0x1d, 0x9d, 0x8a, 0x68, 0x90, 0xb2, 0xf9, +0xc3, 0x3d, 0x12, 0x9c, 0xb8, 0xf6, 0xc5, 0xa7, +0x5a, 0xa7, 0x87, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x71, 0x01, 0x22, 0x21, 0xb0, 0x69, 0x86, +0xcf, 0x26, 0x95, 0xae, 0x4e, 0x27, 0x92, 0xce, +0x71, 0xe5, 0xea, 0x44, 0x85, 0xa6, 0xb1, 0xe7, +0xda, 0x02, 0x5b, 0x44, 0x88, 0x37, 0xe3, 0x9a, +0x31, 0xd9, 0x4e, 0x55, 0x5a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x72, 0x01, 0x22, 0x21, 0xc8, +0xdc, 0x6d, 0x52, 0x49, 0xdb, 0xe1, 0x12, 0xeb, +0xa4, 0x36, 0x1b, 0x19, 0x5f, 0xc9, 0x4c, 0x68, +0x37, 0x33, 0x7c, 0x9b, 0x81, 0xd6, 0xc9, 0x67, +0xba, 0xf1, 0xb6, 0x88, 0x58, 0x50, 0x80, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x73, 0x01, 0x22, +0x21, 0x2c, 0xd0, 0xb2, 0xba, 0xa9, 0xd4, 0x93, +0x8f, 0x9e, 0x33, 0x5f, 0x99, 0xb6, 0xdf, 0x7f, +0x91, 0x69, 0x26, 0x6d, 0x43, 0x6a, 0xfb, 0xcd, +0xb5, 0x71, 0x9c, 0x92, 0xcd, 0x2a, 0xae, 0x92, +0xf6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x74, +0x01, 0x22, 0x21, 0x16, 0x56, 0x14, 0x05, 0xa1, +0x3e, 0x8e, 0x31, 0xca, 0xb4, 0x6c, 0x56, 0x61, +0x0a, 0x49, 0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xc1, 0x8a, 0x15, 0xe5, +0xb2, 0xd0, 0x99, 0x93, 0x03, 0x04, 0x51, 0x02, +0x00, 0x75, 0x01, 0x22, 0x21, 0x6b, 0x61, 0x83, +0xc7, 0x74, 0x23, 0x0c, 0x2a, 0x9c, 0xe6, 0xb0, +0x20, 0x86, 0x01, 0x74, 0x78, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1e, +0xf2, 0x39, 0x92, 0xe4, 0xa0, 0x66, 0x03, 0x04, +0x51, 0x02, 0x00, 0x76, 0x01, 0x22, 0x21, 0xcd, +0xbe, 0x8b, 0xb0, 0x2d, 0x7e, 0xfd, 0xd8, 0xbc, +0x51, 0xed, 0x48, 0x48, 0xa2, 0xd5, 0xeb, 0x31, +0x5b, 0x84, 0xb1, 0x69, 0xbd, 0x6e, 0x88, 0x8b, +0x38, 0x46, 0x6c, 0xa2, 0xa3, 0xbf, 0xc0, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x77, 0x01, 0x22, +0x21, 0xe4, 0xad, 0x93, 0xf8, 0xba, 0x1f, 0x39, +0x2a, 0x2a, 0xe1, 0x12, 0x97, 0x6d, 0x89, 0x5e, +0xa2, 0xa3, 0xa6, 0x3a, 0x11, 0x8c, 0x3b, 0x0e, +0xc1, 0xec, 0xde, 0xea, 0x4c, 0xfe, 0xdd, 0x8e, +0x06, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x78, +0x01, 0x22, 0x21, 0xa4, 0x91, 0x1a, 0x88, 0x1d, +0x2d, 0x04, 0xaf, 0x56, 0x29, 0xd2, 0x9f, 0x84, +0x44, 0xcc, 0x1b, 0x02, 0xb4, 0x8c, 0x03, 0x5b, +0x42, 0xc2, 0x92, 0x12, 0x40, 0x1c, 0xec, 0x04, +0x23, 0xee, 0x57, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x79, 0x01, 0x22, 0x21, 0xef, 0x4a, 0x3e, +0x1c, 0x9b, 0x82, 0xb3, 0x7d, 0xfb, 0xfd, 0x14, +0x49, 0x10, 0x3b, 0xec, 0xba, 0x62, 0x19, 0x3f, +0xa5, 0x77, 0x70, 0xac, 0x60, 0x3c, 0x28, 0x4d, +0x5a, 0xc7, 0x2c, 0x8a, 0x62, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7a, 0x01, 0x22, 0x21, 0x3a, +0x38, 0x92, 0x88, 0x4b, 0xdc, 0xf4, 0xf9, 0xe7, +0x1d, 0x75, 0x70, 0x29, 0xba, 0xa1, 0x5d, 0x34, +0x7a, 0x6b, 0x71, 0x2b, 0x83, 0x21, 0x4c, 0x93, +0xa5, 0x11, 0x46, 0x88, 0xea, 0x92, 0x8a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7b, 0x01, 0x22, +0x21, 0xaf, 0xb5, 0x51, 0xc1, 0x27, 0x79, 0x0f, +0x2f, 0xcd, 0x5f, 0xf4, 0xc8, 0xf1, 0x93, 0xe4, +0xe4, 0x90, 0x77, 0xa5, 0x8e, 0xaf, 0xbf, 0x82, +0x96, 0x6a, 0x3c, 0x4d, 0x9e, 0x37, 0x40, 0x9a, +0xc9, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x7c, +0x01, 0x22, 0x21, 0xd1, 0x46, 0x0d, 0xce, 0xbb, +0x0d, 0x09, 0xff, 0xa9, 0x7a, 0x19, 0xcb, 0x40, +0x8b, 0xd1, 0x21, 0xee, 0xfa, 0xec, 0x7e, 0x20, +0x0f, 0xf6, 0x44, 0x24, 0xb9, 0x59, 0xee, 0x97, +0xb6, 0xaa, 0x29, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x7d, 0x01, 0x22, 0x21, 0xf9, 0xcd, 0x86, +0x3f, 0xf1, 0x01, 0x92, 0x37, 0x6c, 0x09, 0xa4, +0x72, 0x25, 0x05, 0x08, 0xc8, 0xb4, 0xfb, 0x51, +0xde, 0x27, 0x61, 0x3f, 0x7a, 0x4e, 0x51, 0x01, +0x1d, 0xd3, 0xe1, 0xfd, 0x7e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x7e, 0x01, 0x22, 0x21, 0xaf, +0xd7, 0x49, 0x50, 0x86, 0xe3, 0x04, 0xff, 0x78, +0x9f, 0x64, 0x5d, 0xe2, 0x74, 0x28, 0x2d, 0x52, +0x73, 0x50, 0xd4, 0x95, 0x91, 0xb0, 0xaf, 0x85, +0x7e, 0x6a, 0xb9, 0xd7, 0x0f, 0x1c, 0x0f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x7f, 0x01, 0x22, +0x21, 0x0b, 0x0c, 0x05, 0x67, 0xd8, 0xaf, 0xb4, +0xe6, 0xc5, 0x92, 0xda, 0x63, 0x56, 0xb5, 0x41, +0x90, 0x01, 0x88, 0x70, 0xf9, 0x27, 0xba, 0xfa, +0x48, 0x9f, 0x72, 0xd9, 0x46, 0x72, 0x3b, 0x17, +0x54, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x80, +0x01, 0x22, 0x21, 0x6e, 0xc5, 0x6a, 0x52, 0x32, +0xab, 0xbd, 0x23, 0x76, 0x9d, 0x7c, 0x21, 0x19, +0x76, 0xdd, 0x97, 0xf7, 0x85, 0x73, 0x41, 0x36, +0xc3, 0xad, 0x6c, 0xd9, 0xe3, 0x13, 0x20, 0x19, +0x67, 0xe8, 0x5b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x81, 0x01, 0x22, 0x21, 0x49, 0xa8, 0x64, +0xa6, 0x9b, 0xe8, 0xae, 0x39, 0x03, 0x45, 0x21, +0x36, 0x45, 0x55, 0x18, 0xf1, 0x46, 0x28, 0xda, +0xc5, 0xd7, 0x24, 0x15, 0x4b, 0x11, 0x43, 0x81, +0x61, 0x89, 0x5d, 0xfc, 0xd6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x82, 0x01, 0x22, 0x21, 0xc2, +0x1b, 0x5a, 0xda, 0x83, 0xc1, 0xa7, 0xf0, 0x37, +0x4d, 0x8a, 0x06, 0x04, 0xb8, 0x42, 0x66, 0x9c, +0x9d, 0xa8, 0xaa, 0xc0, 0x6b, 0x53, 0x8d, 0x29, +0xfa, 0x4b, 0xb7, 0xe8, 0x0c, 0x93, 0x76, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x83, 0x01, 0x22, +0x21, 0x62, 0x37, 0xb6, 0x15, 0x30, 0xab, 0x86, +0x98, 0xf0, 0x89, 0x9c, 0xfa, 0xc2, 0xb9, 0xaa, +0x8c, 0x49, 0xcd, 0x98, 0xd2, 0x3a, 0x78, 0x9e, +0x54, 0x2d, 0x93, 0x15, 0xb0, 0x08, 0xed, 0xc7, +0x02, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x84, +0x01, 0x22, 0x21, 0xe5, 0xa5, 0x5a, 0x5f, 0x1a, +0x56, 0xb8, 0xe3, 0x64, 0xe4, 0x13, 0xd8, 0x59, +0x77, 0x55, 0x0c, 0x90, 0x27, 0x4c, 0x66, 0x50, +0x2b, 0xeb, 0x8f, 0x20, 0xc4, 0xc2, 0xb1, 0x61, +0x21, 0xf9, 0xed, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x85, 0x01, 0x22, 0x21, 0x77, 0x03, 0x08, +0xbe, 0x9a, 0x0e, 0x56, 0xaf, 0x87, 0xca, 0x36, +0x5b, 0x08, 0xfe, 0x0c, 0xa3, 0xab, 0x98, 0x6f, +0xdf, 0x68, 0xf1, 0x95, 0xeb, 0xc2, 0x44, 0x2b, +0x5b, 0x85, 0xd9, 0x60, 0xce, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x86, 0x01, 0x22, 0x21, 0xe5, +0xf4, 0x34, 0x7c, 0x9f, 0x11, 0x0f, 0xd5, 0xa4, +0x3a, 0x03, 0x77, 0x95, 0x2f, 0x53, 0xe3, 0x32, +0x4a, 0xcb, 0x4c, 0x0e, 0xa2, 0x35, 0x5a, 0x6c, +0x84, 0x5f, 0x0e, 0xf8, 0x81, 0x07, 0x85, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x87, 0x01, 0x22, +0x21, 0x55, 0x37, 0x56, 0x57, 0x6b, 0x44, 0x4a, +0x2c, 0xd2, 0x11, 0x6d, 0x6e, 0x11, 0xbf, 0x50, +0x2b, 0x81, 0xef, 0x37, 0x7c, 0xdb, 0x18, 0xfc, +0x8a, 0xcc, 0x07, 0xe0, 0x1d, 0x96, 0x92, 0x4d, +0xd4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x88, +0x01, 0x22, 0x21, 0x35, 0xb7, 0xc7, 0xf4, 0xd4, +0x0a, 0x33, 0xd1, 0x0f, 0x52, 0xc2, 0xee, 0xff, +0x48, 0x4b, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x3f, 0x56, 0x7c, 0xeb, +0xd5, 0x79, 0x4c, 0x80, 0x03, 0x04, 0x51, 0x02, +0x00, 0x89, 0x01, 0x22, 0x21, 0x0a, 0x74, 0x20, +0x64, 0x09, 0x76, 0x37, 0x27, 0xc5, 0xca, 0xdd, +0xdf, 0x33, 0xb6, 0x68, 0x9a, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x09, +0xf0, 0x52, 0x53, 0x3c, 0xa0, 0x84, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8a, 0x01, 0x22, 0x21, 0x5b, +0x31, 0x57, 0x57, 0x3c, 0x5c, 0x92, 0xc1, 0x9b, +0x8e, 0xf1, 0x5b, 0x2b, 0xb8, 0xd2, 0x43, 0x40, +0x97, 0x69, 0x2c, 0x0a, 0xf5, 0x03, 0xf8, 0x8d, +0x95, 0xa6, 0x1d, 0x94, 0x2e, 0x85, 0x79, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8b, 0x01, 0x22, +0x21, 0x94, 0x97, 0x49, 0xce, 0x3f, 0x7b, 0x16, +0xd1, 0x3b, 0xd3, 0xeb, 0xb5, 0xeb, 0x14, 0x19, +0x61, 0x41, 0xc8, 0x41, 0x6b, 0x7a, 0x9c, 0xac, +0x45, 0x0c, 0x1c, 0x79, 0x0b, 0xe4, 0x9d, 0x46, +0xc1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x8c, +0x01, 0x22, 0x21, 0xf8, 0x5e, 0x5c, 0x78, 0x11, +0xf6, 0xfd, 0xd2, 0x38, 0xc6, 0xc2, 0x02, 0x7d, +0xa1, 0x84, 0xa0, 0xc1, 0x17, 0xd2, 0x6c, 0xbb, +0xc4, 0xb5, 0x40, 0xaf, 0x7a, 0x17, 0xe7, 0x10, +0x1e, 0x04, 0x1c, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x8d, 0x01, 0x22, 0x21, 0x04, 0x6c, 0x3e, +0x09, 0x4d, 0x32, 0x16, 0x34, 0xf5, 0xdf, 0xab, +0x8b, 0xe4, 0x27, 0xe7, 0xd7, 0xde, 0xe7, 0xaf, +0x5c, 0xf0, 0xbe, 0x73, 0x85, 0x67, 0x76, 0xc8, +0xff, 0x7f, 0xe5, 0x91, 0x0e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x8e, 0x01, 0x22, 0x21, 0x94, +0x10, 0xdd, 0x93, 0xff, 0xd2, 0x36, 0x6d, 0x90, +0x24, 0x9f, 0x3a, 0x9e, 0x57, 0x19, 0x3d, 0xca, +0xd4, 0xbb, 0xff, 0x4f, 0x1a, 0x0c, 0x6f, 0xc2, +0xf9, 0x96, 0xbb, 0x34, 0x1c, 0xb2, 0x94, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x8f, 0x01, 0x22, +0x21, 0x42, 0xf9, 0x72, 0xa0, 0xe9, 0xac, 0x40, +0xfd, 0x05, 0xa2, 0x36, 0x5f, 0x91, 0x6b, 0xc1, +0x04, 0xfc, 0xcd, 0xa9, 0x70, 0x1f, 0x79, 0xba, +0xba, 0xab, 0xa5, 0xa9, 0x3a, 0xfc, 0x68, 0x7a, +0xef, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x90, +0x01, 0x22, 0x21, 0xfa, 0x63, 0xa7, 0x7e, 0x1d, +0xe8, 0x11, 0xa6, 0x51, 0xed, 0x1e, 0x00, 0x97, +0x0b, 0xd9, 0xbf, 0x8b, 0x14, 0x5e, 0xf2, 0x39, +0x68, 0x22, 0x53, 0x06, 0x6e, 0xcb, 0xf4, 0xe3, +0x06, 0xd8, 0xdf, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x91, 0x01, 0x22, 0x21, 0x5f, 0x7e, 0x0b, +0x6c, 0x49, 0x0e, 0xcb, 0x2b, 0x09, 0x32, 0x80, +0x0c, 0x81, 0x74, 0xe6, 0xf0, 0xf5, 0x79, 0x34, +0xc0, 0x7f, 0x68, 0x18, 0xf6, 0xf0, 0x3a, 0x5c, +0xd1, 0x9c, 0x7a, 0x0b, 0x34, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x92, 0x01, 0x22, 0x21, 0xc1, +0x23, 0xe9, 0x77, 0xa4, 0x56, 0xcb, 0xa5, 0x8d, +0x2f, 0x6e, 0xe3, 0xf8, 0x6b, 0xd0, 0x8e, 0xd8, +0x49, 0xe5, 0xe0, 0xc5, 0xdc, 0x64, 0xbe, 0x80, +0x00, 0x83, 0xad, 0x1e, 0xea, 0xd6, 0xa5, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x93, 0x01, 0x22, +0x21, 0x0d, 0xd0, 0xb1, 0x95, 0xa4, 0x53, 0x0d, +0xf8, 0x7d, 0x75, 0x1a, 0x17, 0xcb, 0xfb, 0x90, +0x88, 0xaf, 0xa9, 0xb6, 0xa1, 0xe8, 0x85, 0xc2, +0x99, 0xf5, 0x32, 0xb3, 0x62, 0x42, 0x3d, 0xe9, +0x78, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x94, +0x01, 0x22, 0x21, 0x52, 0x8e, 0x51, 0x53, 0x02, +0xc4, 0x34, 0xaa, 0x95, 0xbc, 0x9e, 0x6c, 0x5c, +0x08, 0x7b, 0x28, 0x5e, 0x1c, 0xc1, 0xe0, 0x15, +0x1b, 0xe3, 0xa4, 0x9d, 0xb1, 0x19, 0x51, 0xb2, +0xc4, 0x7d, 0x42, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x95, 0x01, 0x22, 0x21, 0x72, 0xa2, 0x95, +0xdc, 0xe9, 0x33, 0x1f, 0x58, 0xfc, 0xfb, 0xb9, +0xee, 0xe5, 0xd8, 0xbb, 0xa6, 0x92, 0x09, 0xce, +0xb9, 0x9e, 0x99, 0x87, 0x5f, 0xe7, 0x7f, 0xcb, +0x49, 0x0a, 0x6d, 0x8e, 0x8d, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x96, 0x01, 0x22, 0x21, 0xcb, +0xd2, 0x5f, 0xc9, 0xc0, 0x48, 0x16, 0x3c, 0x23, +0xa1, 0x81, 0xbc, 0x96, 0x75, 0x4a, 0xbd, 0x54, +0x04, 0x38, 0xad, 0x6c, 0xd8, 0x83, 0xd8, 0x27, +0xf4, 0xf7, 0xc4, 0x64, 0x0c, 0xef, 0x6f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x97, 0x01, 0x22, +0x21, 0x7c, 0xc2, 0x36, 0x00, 0x1c, 0x48, 0x11, +0x88, 0x05, 0x98, 0x22, 0x48, 0x1d, 0x7a, 0x01, +0xb3, 0xf9, 0x3d, 0x4e, 0xf5, 0x8d, 0xeb, 0xbc, +0x36, 0x8b, 0xcf, 0xfd, 0x83, 0xca, 0xd2, 0x19, +0x5e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x98, +0x01, 0x22, 0x21, 0xaf, 0x07, 0x73, 0x1c, 0x56, +0xbe, 0xa1, 0x63, 0x1b, 0x89, 0xab, 0x05, 0x79, +0xcc, 0xd2, 0x0e, 0x12, 0x8c, 0x53, 0x96, 0x2e, +0x00, 0x9f, 0xfd, 0xa9, 0xc3, 0x49, 0x6c, 0x69, +0x04, 0x0b, 0x5f, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0x99, 0x01, 0x22, 0x21, 0xe5, 0x3a, 0xca, +0xd4, 0x26, 0x57, 0x01, 0x89, 0x79, 0xd2, 0x40, +0x62, 0xcc, 0xad, 0xc2, 0x53, 0x79, 0x72, 0x28, +0xdb, 0x41, 0x18, 0x83, 0xd8, 0xd4, 0x1a, 0xc2, +0x35, 0xdf, 0x59, 0x8a, 0x46, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9a, 0x01, 0x22, 0x21, 0xef, +0x6d, 0x2f, 0x6b, 0xa9, 0xba, 0x93, 0x80, 0xcb, +0x73, 0x79, 0x5c, 0xa2, 0xe0, 0x79, 0xfd, 0x30, +0xe3, 0xc0, 0x59, 0xb9, 0x95, 0xc5, 0xdf, 0x08, +0xeb, 0xf4, 0xe0, 0xf5, 0xca, 0x74, 0x14, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9b, 0x01, 0x22, +0x21, 0x1d, 0xe1, 0x6e, 0x32, 0xd7, 0x7c, 0xa3, +0x51, 0x40, 0xe4, 0xae, 0x97, 0x72, 0x98, 0x69, +0xef, 0x4f, 0xab, 0xe8, 0x1b, 0x0d, 0x03, 0xd5, +0x89, 0x42, 0x1e, 0x46, 0x26, 0xd6, 0xcd, 0x20, +0xc0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0x9c, +0x01, 0x22, 0x21, 0xc9, 0xf8, 0x0a, 0xfb, 0x28, +0x7e, 0x82, 0xeb, 0x22, 0x33, 0xae, 0xae, 0xf5, +0x7e, 0x2f, 0xa4, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xdc, 0x7d, 0xa8, 0x86, +0xd5, 0x82, 0xcb, 0x52, 0x03, 0x04, 0x51, 0x02, +0x00, 0x9d, 0x01, 0x22, 0x21, 0x0d, 0x6d, 0x21, +0xe5, 0x38, 0xd5, 0x55, 0x0d, 0x27, 0x3a, 0xd8, +0x47, 0x17, 0xd7, 0xc1, 0x2f, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xed, 0x08, +0xb3, 0xa3, 0xf9, 0xcb, 0xbf, 0x87, 0x03, 0x04, +0x51, 0x02, 0x00, 0x9e, 0x01, 0x22, 0x21, 0xc9, +0xc9, 0xc0, 0x38, 0xd3, 0x23, 0xf8, 0xc6, 0x4c, +0x8d, 0x51, 0x16, 0xde, 0xb7, 0xf3, 0x16, 0xbf, +0x6f, 0x77, 0x14, 0xf7, 0xc2, 0x2d, 0x1d, 0x8b, +0x04, 0xe7, 0xca, 0xd8, 0x60, 0x88, 0x71, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0x9f, 0x01, 0x22, +0x21, 0x75, 0x64, 0xf5, 0xc9, 0xa8, 0xf0, 0x9c, +0x1f, 0x96, 0x7b, 0xfc, 0xbf, 0x80, 0x6a, 0x84, +0x6f, 0x82, 0x06, 0x50, 0x47, 0x0b, 0x60, 0x2c, +0xde, 0x71, 0xb7, 0xbd, 0x73, 0x41, 0x2f, 0xc6, +0xb8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa0, +0x01, 0x22, 0x21, 0xb4, 0x58, 0xce, 0xc2, 0x84, +0x74, 0x0e, 0x7b, 0x98, 0x89, 0x3b, 0xc5, 0x37, +0x2e, 0x0c, 0x01, 0x68, 0xe8, 0x03, 0x89, 0x97, +0xc1, 0xd5, 0x62, 0x1c, 0x80, 0x4e, 0x35, 0xe8, +0xdc, 0xd6, 0x56, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa1, 0x01, 0x22, 0x21, 0x04, 0x26, 0x74, +0x95, 0x90, 0x14, 0xce, 0xa7, 0x9a, 0x11, 0xc5, +0x79, 0xfc, 0xed, 0xde, 0xf7, 0x84, 0x90, 0xbf, +0xec, 0x7b, 0x6d, 0xa2, 0x52, 0xcb, 0xc5, 0x11, +0xb2, 0x41, 0x02, 0xbf, 0x74, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa2, 0x01, 0x22, 0x21, 0x8a, +0xa6, 0xd7, 0xa3, 0xdd, 0x44, 0x0f, 0xc4, 0xc0, +0x88, 0x1d, 0xfb, 0xf9, 0x18, 0xe6, 0x30, 0x96, +0xb8, 0x8a, 0x49, 0x21, 0x23, 0xc3, 0x81, 0x5d, +0x2b, 0x0b, 0xa8, 0x99, 0xc2, 0x92, 0x14, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa3, 0x01, 0x22, +0x21, 0x76, 0x17, 0xdb, 0xf3, 0x60, 0x18, 0x7a, +0xf1, 0x82, 0x30, 0xda, 0x43, 0x16, 0xe9, 0x26, +0x5f, 0x4c, 0x4e, 0xc0, 0x15, 0x5b, 0x77, 0x77, +0x84, 0x12, 0xe0, 0xb2, 0x43, 0xec, 0x56, 0xaa, +0x34, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa4, +0x01, 0x22, 0x21, 0xe6, 0xb2, 0xb4, 0x82, 0x66, +0xc6, 0xa9, 0x08, 0x03, 0xa0, 0x00, 0xaa, 0x1b, +0x99, 0x5a, 0x05, 0x29, 0x9d, 0xfc, 0x48, 0x30, +0xfc, 0x50, 0xb0, 0xca, 0xba, 0xe1, 0xf8, 0x22, +0x65, 0xd2, 0xe4, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa5, 0x01, 0x22, 0x21, 0x6d, 0x95, 0x71, +0x2e, 0x9f, 0xcf, 0xea, 0xf6, 0x68, 0xbd, 0x29, +0xe0, 0x0d, 0x4a, 0xf6, 0x4f, 0x28, 0xde, 0x05, +0x7c, 0x05, 0xc1, 0xcd, 0x9c, 0xd6, 0xfa, 0x4d, +0x09, 0xcb, 0xe6, 0x23, 0x53, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xa6, 0x01, 0x22, 0x21, 0x63, +0x9c, 0x31, 0x66, 0x7b, 0xd6, 0xd9, 0xb5, 0xc4, +0x20, 0x77, 0xbe, 0x2c, 0xe5, 0x84, 0xb6, 0x03, +0x9b, 0xc5, 0x98, 0xb6, 0xb4, 0x33, 0x71, 0x00, +0x34, 0x07, 0xf3, 0x64, 0xbb, 0xce, 0xe6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xa7, 0x01, 0x22, +0x21, 0x2c, 0x1c, 0x80, 0xda, 0x4c, 0xa5, 0x71, +0xfe, 0xd5, 0x86, 0xf1, 0x6c, 0x31, 0x1e, 0xcd, +0x78, 0xf2, 0x9d, 0x4e, 0x69, 0x55, 0xfe, 0x17, +0x64, 0x24, 0x0a, 0xbe, 0x0d, 0x9f, 0x61, 0x6d, +0xf0, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xa8, +0x01, 0x22, 0x21, 0x77, 0x25, 0xf1, 0xcc, 0x1a, +0x02, 0xf7, 0xb9, 0xc4, 0xbb, 0x11, 0x0a, 0x32, +0x52, 0xd9, 0x1d, 0x8e, 0x89, 0xd6, 0x0f, 0xb5, +0x72, 0x58, 0xa5, 0x86, 0x08, 0x7f, 0x3b, 0x43, +0xa0, 0x0c, 0xac, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xa9, 0x01, 0x22, 0x21, 0xb9, 0xce, 0x77, +0x8b, 0xce, 0x0e, 0xdb, 0xf1, 0x55, 0xb2, 0x1b, +0x1a, 0xc7, 0x87, 0xd3, 0xc3, 0x2a, 0xb4, 0x21, +0x3d, 0xac, 0x2d, 0xf2, 0x88, 0xec, 0xe1, 0x9d, +0xbe, 0x80, 0xd8, 0x5f, 0xa8, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xaa, 0x01, 0x22, 0x21, 0xa7, +0x6c, 0x9f, 0x59, 0xc4, 0x1b, 0x1f, 0xa4, 0x6c, +0xe8, 0x57, 0x2a, 0x51, 0xb3, 0xb3, 0xf2, 0x02, +0x23, 0x6f, 0x5a, 0x5f, 0x5b, 0x44, 0xbb, 0x3e, +0x6f, 0xb6, 0x6d, 0x8a, 0x89, 0xae, 0x4a, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xab, 0x01, 0x22, +0x21, 0x48, 0x5e, 0x47, 0x3e, 0x9b, 0xa7, 0x28, +0xb4, 0xd6, 0x66, 0xdc, 0x36, 0x39, 0x42, 0xc5, +0xd2, 0xe4, 0xaa, 0xc6, 0x45, 0x96, 0x48, 0x28, +0xad, 0xb5, 0x0b, 0x6f, 0x3b, 0x2d, 0x67, 0x11, +0xcd, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xac, +0x01, 0x22, 0x21, 0xdb, 0x6d, 0x54, 0x3c, 0xc3, +0xc5, 0x11, 0x38, 0xac, 0xc4, 0x87, 0x04, 0xac, +0xf5, 0xc6, 0x03, 0x73, 0x8a, 0x0d, 0xb4, 0xe8, +0x88, 0x88, 0x65, 0x7b, 0x85, 0x4c, 0xc8, 0x0d, +0x28, 0xd2, 0x0b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xad, 0x01, 0x22, 0x21, 0xd5, 0xf6, 0xf2, +0x87, 0x8c, 0x97, 0x94, 0x43, 0x07, 0xa5, 0xc6, +0xde, 0x7c, 0x10, 0xa9, 0x28, 0xe0, 0x1d, 0x0c, +0x9f, 0x30, 0x84, 0xbe, 0x72, 0x07, 0xef, 0x0d, +0xd9, 0x29, 0xb9, 0x7a, 0xb2, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xae, 0x01, 0x22, 0x21, 0x94, +0xa1, 0xd7, 0x80, 0xf5, 0x75, 0xfa, 0x60, 0xff, +0x2e, 0xd4, 0x3e, 0x5a, 0xe7, 0xe1, 0xfb, 0x78, +0x7f, 0xac, 0x89, 0x71, 0xdf, 0xf8, 0xb6, 0x3d, +0x1b, 0x7b, 0x1c, 0x8a, 0x32, 0xb1, 0xf3, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xaf, 0x01, 0x22, +0x21, 0xd8, 0x80, 0xed, 0x99, 0xdb, 0xd1, 0x20, +0x82, 0xc4, 0xab, 0xab, 0xf6, 0xfe, 0x48, 0x70, +0x2d, 0x59, 0x19, 0x7d, 0x45, 0x19, 0xef, 0x49, +0x07, 0x81, 0x93, 0x7c, 0x3e, 0xf1, 0xa1, 0x79, +0xd6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb0, +0x01, 0x22, 0x21, 0xef, 0xba, 0x4d, 0x47, 0xe5, +0x43, 0x3d, 0x8b, 0x88, 0x94, 0x37, 0xe5, 0xeb, +0x9d, 0xd6, 0xc5, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xe5, 0x33, 0x90, 0x13, +0x0b, 0x3a, 0x2b, 0x71, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb1, 0x01, 0x22, 0x21, 0xed, 0xdd, 0x1d, +0xe1, 0xc3, 0x93, 0x2b, 0x49, 0xcb, 0x77, 0xf3, +0xe7, 0xe8, 0x4c, 0x02, 0x42, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x9a, +0xdc, 0x8a, 0x58, 0x68, 0x1b, 0x5b, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb2, 0x01, 0x22, 0x21, 0xda, +0xae, 0xc6, 0x21, 0x97, 0x20, 0xf9, 0xc7, 0xa7, +0x3d, 0x59, 0x8e, 0x61, 0x88, 0xce, 0x4f, 0x0b, +0x5e, 0x0d, 0x3f, 0xbf, 0x6e, 0x21, 0x8e, 0xa1, +0x59, 0xcf, 0xf8, 0xb8, 0xd7, 0x8d, 0xb2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb3, 0x01, 0x22, +0x21, 0xaf, 0x14, 0x07, 0x3a, 0xc2, 0x64, 0xa2, +0xdc, 0x16, 0x49, 0x71, 0xcc, 0x91, 0x96, 0x78, +0x9f, 0x82, 0x59, 0xf2, 0x2e, 0x0b, 0x26, 0x7d, +0x62, 0x92, 0xcf, 0xbb, 0xfc, 0x04, 0xca, 0x96, +0x02, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb4, +0x01, 0x22, 0x21, 0xb6, 0xb3, 0x45, 0xbf, 0x20, +0x3a, 0x6d, 0x97, 0x53, 0x73, 0xd8, 0x30, 0xd3, +0x56, 0x09, 0x29, 0xcb, 0x9d, 0x24, 0xc1, 0xa9, +0x33, 0xa1, 0x7a, 0x1b, 0x47, 0x7f, 0xd7, 0x56, +0xd4, 0xff, 0x3e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb5, 0x01, 0x22, 0x21, 0xd0, 0xcd, 0x58, +0x20, 0x50, 0x82, 0x63, 0xac, 0x0e, 0x71, 0x54, +0x92, 0x5e, 0xd2, 0xd0, 0xa1, 0x50, 0xa0, 0xe6, +0x01, 0x32, 0xb5, 0x5a, 0xa5, 0x7e, 0x44, 0x94, +0x88, 0x38, 0x45, 0x62, 0x24, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xb6, 0x01, 0x22, 0x21, 0xf5, +0xe7, 0x5f, 0x68, 0x22, 0x1f, 0x42, 0x5c, 0x24, +0xc2, 0x43, 0xec, 0x65, 0xbb, 0xbc, 0xc9, 0x02, +0x39, 0xea, 0xf1, 0x09, 0x40, 0x3c, 0x2a, 0x1a, +0xb2, 0xec, 0xed, 0xf1, 0x83, 0x4e, 0x77, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xb7, 0x01, 0x22, +0x21, 0x71, 0xf6, 0x51, 0xbd, 0x59, 0x26, 0x79, +0x90, 0x14, 0xba, 0x5d, 0x37, 0x95, 0xfc, 0x4d, +0x69, 0x4d, 0x7d, 0x38, 0x3e, 0x56, 0x34, 0x96, +0xb2, 0xf0, 0x14, 0xbd, 0xec, 0xbf, 0xfb, 0x77, +0xd6, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xb8, +0x01, 0x22, 0x21, 0x4c, 0xd2, 0xa1, 0xbf, 0x98, +0x79, 0xf9, 0x04, 0xfb, 0x5e, 0xb1, 0xa2, 0xc6, +0x45, 0x08, 0xa3, 0xca, 0x42, 0x75, 0xa3, 0xf4, +0x67, 0x3e, 0xc5, 0x18, 0x3a, 0xfe, 0xcd, 0xd1, +0x6f, 0xa0, 0xe9, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xb9, 0x01, 0x22, 0x21, 0x2d, 0x04, 0x3e, +0xab, 0xc4, 0x05, 0x9d, 0x63, 0x9d, 0xd4, 0x1b, +0x20, 0xc2, 0x54, 0x9a, 0x2f, 0xf9, 0xb0, 0xb3, +0x81, 0xfe, 0x62, 0xe3, 0x6a, 0x01, 0xbe, 0x15, +0xa0, 0xa7, 0x52, 0x18, 0x73, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xba, 0x01, 0x22, 0x21, 0x8a, +0x97, 0xb2, 0xad, 0xfa, 0x29, 0x16, 0x31, 0xbd, +0x8e, 0x7d, 0x22, 0xf2, 0x31, 0x60, 0x88, 0x39, +0xeb, 0xff, 0x2a, 0x14, 0x6b, 0xe5, 0x14, 0x05, +0x3f, 0x34, 0xb6, 0xa5, 0x70, 0xa6, 0x8c, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbb, 0x01, 0x22, +0x21, 0xb9, 0x63, 0x60, 0xbb, 0x0d, 0xd8, 0x1a, +0xef, 0xb6, 0x62, 0xe8, 0x89, 0x34, 0xae, 0x1e, +0x01, 0x66, 0x95, 0xf4, 0xbf, 0x12, 0x5f, 0xf6, +0x3a, 0x6e, 0x39, 0x6f, 0x0a, 0x8c, 0x0b, 0xbb, +0x85, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xbc, +0x01, 0x22, 0x21, 0xeb, 0x82, 0x85, 0xb6, 0xce, +0x70, 0xa0, 0xa8, 0xe8, 0xb5, 0xea, 0xc7, 0x1f, +0xce, 0x12, 0x21, 0x4a, 0xd8, 0x70, 0xa0, 0x59, +0x89, 0xbf, 0x65, 0x5e, 0x16, 0x1f, 0xfe, 0x1d, +0x1f, 0x0f, 0x27, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xbd, 0x01, 0x22, 0x21, 0xeb, 0xe9, 0x42, +0x1a, 0xbd, 0x90, 0x51, 0xc7, 0x72, 0x1b, 0xfe, +0xa6, 0x1d, 0xb3, 0x58, 0xae, 0xe7, 0xfb, 0xa8, +0x20, 0xb6, 0xa4, 0xe6, 0x93, 0xc2, 0x56, 0x58, +0xcb, 0xac, 0x53, 0x1e, 0xc1, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xbe, 0x01, 0x22, 0x21, 0x99, +0x7c, 0x22, 0x54, 0x31, 0x3c, 0x80, 0x35, 0x13, +0x8a, 0x21, 0x58, 0x0f, 0x9b, 0xa7, 0x62, 0xda, +0x29, 0xa2, 0x2f, 0x84, 0xd6, 0x37, 0xe0, 0x1b, +0xb0, 0x39, 0x13, 0xed, 0x18, 0xc4, 0x9f, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xbf, 0x01, 0x22, +0x21, 0xd1, 0xc6, 0xcd, 0x42, 0x02, 0xaf, 0x4e, +0xc6, 0xfb, 0xca, 0x94, 0xaa, 0xed, 0x3d, 0xed, +0x01, 0xe3, 0x14, 0x08, 0x7b, 0x77, 0x57, 0x6a, +0x01, 0xeb, 0x18, 0x17, 0x26, 0x5f, 0x4b, 0x50, +0x8c, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc0, +0x01, 0x22, 0x21, 0xf0, 0x88, 0xde, 0xc7, 0x63, +0x20, 0xec, 0xdf, 0x50, 0x17, 0x6e, 0x57, 0xa4, +0xf0, 0x46, 0xe8, 0x4e, 0x42, 0x07, 0x07, 0x79, +0xa7, 0x76, 0x15, 0x56, 0x25, 0x71, 0x24, 0x9e, +0x13, 0x08, 0xb2, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc1, 0x01, 0x22, 0x21, 0x72, 0xdf, 0x79, +0x57, 0xe5, 0x7b, 0x6c, 0x93, 0xef, 0x64, 0xe2, +0x17, 0x6b, 0x3c, 0xf8, 0x0e, 0xd7, 0x36, 0x60, +0xa6, 0xd7, 0x79, 0xc7, 0x6c, 0xeb, 0x96, 0x5a, +0xa4, 0xb4, 0xb6, 0x71, 0x44, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc2, 0x01, 0x22, 0x21, 0xf6, +0x09, 0x39, 0x27, 0xed, 0xa1, 0x8c, 0xfb, 0x65, +0x21, 0xfd, 0x6d, 0x9a, 0xe3, 0xfa, 0x1c, 0xaa, +0x5a, 0x21, 0x63, 0xcb, 0x4b, 0xff, 0x99, 0x25, +0xc1, 0x85, 0xe8, 0xc4, 0xa0, 0xdb, 0xae, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc3, 0x01, 0x22, +0x21, 0x71, 0x38, 0x43, 0xed, 0x65, 0x31, 0x40, +0x19, 0x3d, 0x90, 0x8a, 0x83, 0x48, 0xa9, 0xe8, +0x04, 0x3d, 0xc6, 0x55, 0x45, 0xb9, 0xc6, 0xbb, +0xb9, 0x0b, 0x4e, 0x1b, 0xc1, 0x8a, 0x2b, 0x21, +0x29, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc4, +0x01, 0x22, 0x21, 0x5f, 0xc9, 0x1c, 0xba, 0xd4, +0x26, 0xf8, 0x3d, 0xde, 0x9a, 0x20, 0xd4, 0xcd, +0x2b, 0xb5, 0xd1, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x88, 0xff, 0x4d, 0x04, +0x2d, 0x62, 0x20, 0x09, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc5, 0x01, 0x22, 0x21, 0x74, 0x6c, 0x12, +0x3f, 0xca, 0xea, 0xca, 0x01, 0x56, 0xf1, 0xe2, +0xa1, 0x34, 0x7f, 0x26, 0xb0, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0xef, +0x18, 0xba, 0x01, 0xdd, 0xb6, 0x25, 0x03, 0x04, +0x51, 0x02, 0x00, 0xc6, 0x01, 0x22, 0x21, 0x52, +0x37, 0x80, 0x7b, 0x04, 0xf4, 0x71, 0x85, 0x0c, +0xbe, 0xd6, 0xea, 0xd3, 0xb7, 0xda, 0xfe, 0x27, +0x3c, 0x20, 0x9c, 0x1d, 0x09, 0xc1, 0x55, 0x88, +0x8d, 0x08, 0x2a, 0x04, 0x8a, 0x3f, 0xa2, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xc7, 0x01, 0x22, +0x21, 0x8a, 0x33, 0x26, 0x5a, 0xc9, 0x72, 0x8c, +0x76, 0xfb, 0xd4, 0xe5, 0x4b, 0x5a, 0x38, 0xf6, +0x8a, 0x88, 0x1a, 0xa0, 0x50, 0x64, 0x24, 0x62, +0x99, 0x59, 0x05, 0x9a, 0x37, 0x8e, 0x03, 0x24, +0xe5, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xc8, +0x01, 0x22, 0x21, 0xb4, 0x27, 0x83, 0x13, 0xcb, +0x05, 0x60, 0xf1, 0xe9, 0x3b, 0xc9, 0x65, 0xc4, +0xe9, 0x12, 0x83, 0x78, 0x8e, 0x8e, 0xda, 0xb9, +0xf3, 0x2e, 0xcc, 0xb8, 0x4f, 0x2d, 0xa7, 0x3c, +0xef, 0x38, 0xf1, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xc9, 0x01, 0x22, 0x21, 0xd3, 0x64, 0xfa, +0x3d, 0x69, 0x20, 0xb1, 0xdb, 0xfc, 0x9a, 0xd6, +0x14, 0xe7, 0xe6, 0x85, 0xa2, 0xac, 0xf6, 0x97, +0xe0, 0x79, 0xa1, 0x67, 0xd7, 0x2b, 0x6e, 0x0e, +0x4e, 0x74, 0x4f, 0xbf, 0xe6, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xca, 0x01, 0x22, 0x21, 0xde, +0x96, 0xc2, 0x90, 0x9e, 0x2d, 0x94, 0x62, 0x78, +0x68, 0xae, 0x93, 0x38, 0x1b, 0xd8, 0xa5, 0x4f, +0x75, 0x14, 0x4c, 0x1f, 0xe9, 0x1c, 0x69, 0x37, +0x2e, 0x83, 0xd0, 0x65, 0x40, 0x0a, 0x01, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcb, 0x01, 0x22, +0x21, 0xfd, 0xa5, 0x2d, 0xfb, 0x27, 0xc9, 0xeb, +0xb4, 0xb1, 0x0b, 0x12, 0xaa, 0x1c, 0xf8, 0x47, +0x06, 0x57, 0x99, 0x70, 0xa5, 0xe4, 0x52, 0x0e, +0x15, 0x85, 0xe3, 0x3a, 0xb4, 0x3a, 0xde, 0xfc, +0xc4, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xcc, +0x01, 0x22, 0x21, 0xa2, 0xc5, 0x0c, 0x0b, 0x51, +0xb4, 0xa1, 0xd4, 0x5c, 0xa0, 0x44, 0xb5, 0x27, +0x06, 0x5a, 0xd6, 0x4f, 0xcf, 0x02, 0xdf, 0xef, +0x7d, 0x86, 0xf2, 0x4e, 0xe5, 0x70, 0x3e, 0x10, +0xf8, 0xa4, 0x77, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xcd, 0x01, 0x22, 0x21, 0xf8, 0x48, 0x16, +0x71, 0x0e, 0x60, 0xed, 0xe7, 0x25, 0xb8, 0x69, +0x07, 0x9b, 0x32, 0xb6, 0x41, 0x1a, 0x42, 0x44, +0xe5, 0x0e, 0x77, 0xc0, 0xd7, 0x02, 0x66, 0xf4, +0xf4, 0x0d, 0x19, 0x55, 0x67, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xce, 0x01, 0x22, 0x21, 0x95, +0x81, 0xf5, 0x6f, 0xd6, 0xf8, 0x18, 0x69, 0xa3, +0x2c, 0xe2, 0x7b, 0xf7, 0x27, 0x2e, 0x19, 0xe7, +0x60, 0x5f, 0x9c, 0xc2, 0xe3, 0x13, 0x08, 0x5f, +0x2c, 0x79, 0x84, 0x19, 0xdf, 0xff, 0x11, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xcf, 0x01, 0x22, +0x21, 0x9f, 0xfe, 0x1a, 0x83, 0x9b, 0x30, 0xaa, +0x47, 0x85, 0xae, 0xab, 0xd9, 0x18, 0xe6, 0x20, +0xd9, 0x2d, 0x0a, 0xea, 0xb0, 0x25, 0x76, 0xa4, +0x48, 0x6d, 0x18, 0x69, 0x38, 0x40, 0x83, 0x16, +0xd2, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd0, +0x01, 0x22, 0x21, 0xe1, 0x96, 0xe8, 0x78, 0x59, +0xc1, 0x93, 0xdb, 0x11, 0xf3, 0xf4, 0xb9, 0x48, +0xf4, 0xc7, 0x83, 0xd7, 0xbb, 0x62, 0x2b, 0x3a, +0xf6, 0xa9, 0x6e, 0x10, 0xc1, 0xee, 0x9c, 0x5b, +0xbb, 0x95, 0xc3, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd1, 0x01, 0x22, 0x21, 0x98, 0x5d, 0x1f, +0x35, 0xee, 0xa4, 0x4c, 0x36, 0xc4, 0x9d, 0x93, +0x82, 0xb2, 0x96, 0xde, 0xa2, 0x94, 0xba, 0x55, +0x87, 0x2f, 0x4c, 0x50, 0x24, 0x3a, 0xb5, 0x04, +0xd6, 0x7c, 0xa1, 0x6c, 0x20, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd2, 0x01, 0x22, 0x21, 0xb7, +0x29, 0x3f, 0x65, 0x87, 0xf9, 0x69, 0xac, 0x5f, +0x12, 0x86, 0xc2, 0x09, 0x5a, 0x75, 0x6b, 0x9a, +0x87, 0xbe, 0xd6, 0x8b, 0x33, 0x8f, 0x13, 0x3c, +0x19, 0x0f, 0xc4, 0x33, 0xe6, 0x15, 0xaf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd3, 0x01, 0x22, +0x21, 0xd3, 0x8b, 0xc4, 0xce, 0x9b, 0x15, 0xfe, +0x31, 0x94, 0x7e, 0xe2, 0xac, 0x50, 0x89, 0xc9, +0xef, 0x6a, 0x0b, 0x78, 0x7e, 0x93, 0x05, 0x80, +0x3e, 0x5f, 0x12, 0x28, 0x82, 0xce, 0x34, 0x53, +0xfe, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd4, +0x01, 0x22, 0x21, 0x7c, 0x37, 0x0a, 0x7e, 0x28, +0x08, 0x9a, 0x9e, 0x2c, 0x5d, 0x24, 0x43, 0xf2, +0x52, 0x1d, 0x02, 0xbb, 0x26, 0xe7, 0x99, 0x0b, +0xe9, 0x85, 0x63, 0x96, 0xfb, 0x56, 0xf6, 0x74, +0x0a, 0xf5, 0x9e, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd5, 0x01, 0x22, 0x21, 0x75, 0xa3, 0xb2, +0x63, 0x8c, 0xc3, 0xe6, 0xaa, 0x02, 0x57, 0x4d, +0x84, 0xdd, 0xc6, 0x5f, 0x09, 0xc1, 0x93, 0x65, +0x1d, 0xf3, 0x0d, 0x72, 0xfa, 0x8c, 0xb4, 0xbb, +0x4d, 0x4b, 0x08, 0x35, 0x3e, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xd6, 0x01, 0x22, 0x21, 0xbc, +0xaa, 0xcd, 0x8e, 0x63, 0x8e, 0x26, 0x5a, 0xb2, +0x16, 0xa4, 0x8d, 0xde, 0x5c, 0xbe, 0x7c, 0x98, +0x8e, 0x44, 0x38, 0xc1, 0xc1, 0x39, 0x9b, 0x35, +0x16, 0xa8, 0x52, 0x4c, 0xfd, 0x3e, 0x5b, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xd7, 0x01, 0x22, +0x21, 0x79, 0xf4, 0xe7, 0x86, 0xf5, 0x6d, 0x71, +0xf2, 0xc6, 0x45, 0x57, 0xa7, 0x3a, 0x95, 0xda, +0x97, 0x99, 0x1c, 0xe2, 0xec, 0x29, 0x23, 0x26, +0x04, 0x9e, 0x82, 0x0b, 0xf9, 0xff, 0xb7, 0xf3, +0xfa, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xd8, +0x01, 0x22, 0x21, 0x57, 0x6b, 0xe5, 0xcc, 0xf7, +0xa2, 0x6f, 0x0d, 0x74, 0xb9, 0x72, 0x01, 0xe5, +0xbe, 0x69, 0xd3, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x71, 0xb5, 0x50, 0xf6, +0xe2, 0xca, 0xab, 0x56, 0x03, 0x04, 0x51, 0x02, +0x00, 0xd9, 0x01, 0x22, 0x21, 0x97, 0x2a, 0x7d, +0xc0, 0x43, 0x01, 0x22, 0xa5, 0x92, 0x54, 0x96, +0xee, 0xe6, 0x03, 0xcd, 0x98, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x21, +0xed, 0x66, 0xc1, 0xa8, 0xff, 0x8e, 0x03, 0x04, +0x51, 0x02, 0x00, 0xda, 0x01, 0x22, 0x21, 0x3a, +0x9e, 0x61, 0x60, 0x86, 0x43, 0xc3, 0x03, 0x2d, +0x2e, 0x62, 0xf6, 0xe5, 0xd3, 0x94, 0x34, 0x6a, +0x9a, 0x8a, 0x23, 0x68, 0x28, 0x75, 0x8a, 0x89, +0xc7, 0x3e, 0x8b, 0x8e, 0x71, 0x60, 0xcf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdb, 0x01, 0x22, +0x21, 0xf8, 0x97, 0x7a, 0x0a, 0x96, 0x8a, 0x21, +0x26, 0x51, 0x1f, 0xd9, 0x84, 0x60, 0x93, 0xce, +0x2d, 0x3f, 0x5c, 0x2f, 0x17, 0xfd, 0xc4, 0xe2, +0x1b, 0xef, 0xd8, 0xc8, 0x64, 0xcd, 0x7a, 0x70, +0x6e, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xdc, +0x01, 0x22, 0x21, 0x73, 0x7b, 0xf3, 0x12, 0x03, +0xd6, 0xed, 0xa4, 0xb7, 0x6d, 0xd0, 0xfb, 0x8c, +0x70, 0xd0, 0x63, 0x02, 0x8d, 0xf7, 0x9b, 0x8c, +0xf1, 0x13, 0x8f, 0xec, 0x5a, 0x1e, 0x31, 0xd9, +0x3a, 0x63, 0x40, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xdd, 0x01, 0x22, 0x21, 0xff, 0x53, 0x10, +0xad, 0xfa, 0x0e, 0xfb, 0x82, 0xbd, 0xfd, 0x7a, +0x15, 0x9b, 0x9f, 0xbe, 0xdc, 0xc7, 0x45, 0xf6, +0xbe, 0xa2, 0x22, 0x49, 0x8d, 0x1a, 0x30, 0x93, +0x35, 0xfa, 0xe1, 0xe8, 0x3a, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xde, 0x01, 0x22, 0x21, 0x82, +0xd4, 0x5e, 0xd9, 0xeb, 0x0d, 0x0f, 0x7f, 0x8d, +0xbd, 0x8f, 0x53, 0x53, 0x95, 0xa2, 0x73, 0x06, +0xc8, 0x15, 0x2f, 0xfa, 0x18, 0x78, 0xa5, 0xe0, +0x5d, 0x95, 0xd3, 0xcc, 0x10, 0x8e, 0xee, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xdf, 0x01, 0x22, +0x21, 0xaf, 0x89, 0x80, 0xb1, 0xc1, 0xed, 0xb2, +0x9e, 0x5a, 0xb7, 0x35, 0x7b, 0xe6, 0x2d, 0xf9, +0x42, 0xcf, 0xf0, 0x4a, 0x83, 0x7a, 0xe5, 0x77, +0x7c, 0x97, 0x17, 0x2d, 0x44, 0xbe, 0x3f, 0x70, +0xc8, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe0, +0x01, 0x22, 0x21, 0x0d, 0xa9, 0xb0, 0x3a, 0xb7, +0x2a, 0x33, 0x68, 0xda, 0xb2, 0x9a, 0x5f, 0x5b, +0x80, 0x65, 0x45, 0xaf, 0x76, 0x19, 0x49, 0x54, +0x8d, 0x35, 0x55, 0x90, 0x91, 0xcb, 0xaa, 0x89, +0xbc, 0xb8, 0x8b, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe1, 0x01, 0x22, 0x21, 0xe5, 0x49, 0xca, +0x69, 0x67, 0x59, 0xb8, 0xd2, 0x97, 0x4c, 0x9f, +0x42, 0xfa, 0x5b, 0x7b, 0xcf, 0x5b, 0xea, 0x5b, +0x7f, 0xb5, 0xad, 0x25, 0x64, 0xe8, 0xb3, 0xb7, +0x62, 0x51, 0x9d, 0x70, 0xd7, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe2, 0x01, 0x22, 0x21, 0xcf, +0xd1, 0x76, 0x76, 0x34, 0x25, 0xab, 0xc0, 0x1b, +0x53, 0x28, 0xfd, 0x9e, 0x1d, 0x12, 0x09, 0x1a, +0xe7, 0x1e, 0x46, 0x2d, 0xb3, 0x65, 0x51, 0xe4, +0xc3, 0xcd, 0x41, 0xfb, 0x42, 0x30, 0xc6, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe3, 0x01, 0x22, +0x21, 0x00, 0x88, 0x9e, 0x66, 0x16, 0x02, 0xcc, +0xc8, 0x5d, 0xd0, 0xb0, 0x9c, 0xaf, 0x8c, 0x97, +0xe9, 0x8a, 0x25, 0xbb, 0xbd, 0x6d, 0xad, 0xf8, +0x9b, 0x7f, 0x23, 0xba, 0xd2, 0x8c, 0xd2, 0x23, +0x64, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe4, +0x01, 0x22, 0x21, 0x37, 0x43, 0x72, 0x80, 0xaf, +0x97, 0x39, 0x4f, 0x0e, 0xbe, 0x2b, 0x2a, 0x2a, +0xe0, 0xa3, 0x2b, 0x00, 0x3b, 0x8f, 0xe6, 0xbf, +0xdf, 0xc5, 0x69, 0xd8, 0x5c, 0xb8, 0x6c, 0xc4, +0xfa, 0x77, 0x68, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe5, 0x01, 0x22, 0x21, 0xba, 0x41, 0xbd, +0x8e, 0x29, 0x4d, 0x2b, 0x78, 0x2a, 0x65, 0x09, +0x48, 0x89, 0x91, 0xc6, 0xd7, 0xfe, 0xae, 0x3c, +0xb9, 0x71, 0x63, 0xb2, 0x77, 0x0d, 0xa5, 0x86, +0xe8, 0xbb, 0x81, 0xd6, 0xaa, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xe6, 0x01, 0x22, 0x21, 0x38, +0x9f, 0x29, 0xf4, 0xc0, 0xef, 0x8f, 0xcd, 0x8d, +0x5b, 0x07, 0xb6, 0x73, 0xe4, 0x93, 0xd2, 0x9f, +0x45, 0x1f, 0xfb, 0x9d, 0x0a, 0x8e, 0x84, 0x21, +0xb7, 0x10, 0xbe, 0x61, 0x13, 0xfb, 0xdf, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xe7, 0x01, 0x22, +0x21, 0xab, 0x2e, 0xf6, 0x94, 0x99, 0x9b, 0x5c, +0x73, 0xc3, 0xe0, 0x53, 0xed, 0x50, 0xba, 0x9d, +0x23, 0x2a, 0x2c, 0x32, 0xba, 0xee, 0x4e, 0x24, +0xcf, 0xc7, 0x51, 0xdf, 0xf1, 0xe6, 0x95, 0x8d, +0xa1, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xe8, +0x01, 0x22, 0x21, 0x5e, 0x9b, 0xda, 0x99, 0x23, +0x99, 0x3c, 0xd2, 0x4b, 0x96, 0xcb, 0xa9, 0x2a, +0x42, 0xb4, 0x23, 0x5d, 0x9f, 0x6e, 0x02, 0x91, +0x50, 0x1c, 0x98, 0xdb, 0x60, 0x2e, 0x24, 0x23, +0xd3, 0x6f, 0xad, 0x00, 0x03, 0x04, 0x51, 0x02, +0x00, 0xe9, 0x01, 0x22, 0x21, 0x6b, 0x7a, 0x38, +0x8b, 0xf8, 0x23, 0x48, 0x62, 0x22, 0xc0, 0x79, +0x03, 0xba, 0x7e, 0x99, 0x0f, 0x77, 0x0e, 0xcd, +0x90, 0x97, 0x85, 0xbb, 0xd6, 0x9b, 0xe8, 0x5b, +0xa8, 0x2d, 0x19, 0xcb, 0xbc, 0x00, 0x03, 0x04, +0x51, 0x02, 0x00, 0xea, 0x01, 0x22, 0x21, 0x82, +0x84, 0xa9, 0x55, 0x34, 0x92, 0xa7, 0x67, 0x0d, +0x8a, 0xcc, 0xc9, 0x92, 0xbd, 0x79, 0x6b, 0xa0, +0xc5, 0x1b, 0xb1, 0x23, 0xad, 0x33, 0x31, 0x07, +0xd5, 0xd6, 0x44, 0xb1, 0x8a, 0xef, 0xbd, 0x00, +0x03, 0x04, 0x51, 0x02, 0x00, 0xeb, 0x01, 0x22, +0x21, 0x25, 0x3b, 0xd5, 0xbb, 0x41, 0x23, 0x1e, +0x59, 0x58, 0x41, 0x7b, 0x3e, 0xb6, 0x8c, 0x27, +0xe1, 0xed, 0x96, 0xcf, 0x9e, 0xc0, 0x3d, 0xe0, +0x52, 0x26, 0x3e, 0x5c, 0x04, 0x8f, 0x4e, 0xcd, +0x40, 0x00, 0x03, 0x04, 0x51, 0x02, 0x00, 0xec, +0x01, 0x22, 0x21, 0x6e, 0xc0, 0x43, 0x5f, 0x8a, +0xfc, 0xef, 0x67, 0xde, 0x14, 0x71, 0x2c, 0xa7, +0x91, 0x76, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x66, 0xfb, 0x4b, 0x80, +0xb2, 0x93, 0x58, 0x5d, 0x03, 0x04, 0x51, 0x02, +0x00, 0xed, 0x01, 0x22, 0x21, 0xeb, 0x00, 0x27, +0x6e, 0x96, 0xf2, 0x4d, 0x71, 0xea, 0xd5, 0x84, +0x07, 0x1e, 0xab, 0x0d, 0x93, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaf, 0xe8, +0x56, 0x53, 0xdb, 0x28, 0x94, 0x1d, 0x03, 0x04, +0x51, 0x02, 0x00, 0xee, 0x01, 0x22, 0x21, 0x28, +0xd3, 0x13, 0x74, 0xc8, 0xcf, 0x4c, 0xad, 0xcd, +0xa4, 0x96, 0x24, 0x1c, 0x00, 0xb0, 0xb9, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x91, 0x38, 0x73, 0x63, 0x42, 0x12, 0x3a, 0xf2, +0x03, 0x04, 0x51, 0x02, 0x00, 0xef, 0x00 +}; diff --git a/include/linux/mfd/max77775-private.h b/include/linux/mfd/max77775-private.h new file mode 100755 index 000000000000..027cfeb0fe64 --- /dev/null +++ b/include/linux/mfd/max77775-private.h @@ -0,0 +1,431 @@ +/* + * max77775-private.h - Voltage regulator driver for the Maxim 77775 + * + * Copyright (C) 2016 Samsung Electrnoics + * Insun Choi + * + * 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, see . + */ + +#ifndef __LINUX_MFD_MAX77775_PRIV_H +#define __LINUX_MFD_MAX77775_PRIV_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* To Do : Customer Definition for product id */ +#define MAX77775_USBC_PRODUCT_ID (0x75) + +#define MAX77775_REG_INVALID (0xff) + +#define MAX77775_IRQSRC_CHG (1 << 0) +#define MAX77775_IRQSRC_TOP (1 << 1) +#define MAX77775_IRQSRC_FG (1 << 2) +#define MAX77775_IRQSRC_USBC (1 << 3) +#define MAX77775_ELRN (1 << 0) +#define MAX77775_FILT_EMPTY (1 << 2) + +enum max77775_hw_rev { + MAX77775_PASS1 = 0x1, + MAX77775_PASS2 = 0x2, + MAX77775_PASS3 = 0x3, + MAX77775_PASS4 = 0x4, + MAX77775_PASS5 = 0x5, +}; + +enum max77775_reg { + /* Slave addr = 0xCC */ + /* PMIC Top-Level Registers */ + MAX77775_PMIC_REG_PMICID = 0x00, + MAX77775_PMIC_REG_PMICREV = 0x01, + MAX77775_PMIC_REG_MAINCTRL1 = 0x02, + MAX77775_PMIC_REG_INTSRC = 0x22, + MAX77775_PMIC_REG_INTSRC_MASK = 0x23, + MAX77775_PMIC_REG_SYSTEM_INT = 0x24, + MAX77775_PMIC_REG_SYSTEM_INT_MASK = 0x26, + MAX77775_PMIC_REG_SW_RESET = 0x50, + + MAX77775_CHG_REG_INT = 0xB0, + MAX77775_CHG_REG_INT_MASK = 0xB1, + MAX77775_CHG_REG_INT_OK = 0xB2, + MAX77775_CHG_REG_DETAILS_00 = 0xB3, + MAX77775_CHG_REG_DETAILS_01 = 0xB4, + MAX77775_CHG_REG_DETAILS_02 = 0xB5, + MAX77775_CHG_REG_CNFG_00 = 0xB7, + MAX77775_CHG_REG_CNFG_01 = 0xB8, + MAX77775_CHG_REG_CNFG_02 = 0xB9, + MAX77775_CHG_REG_CNFG_03 = 0xBA, + MAX77775_CHG_REG_CNFG_04 = 0xBB, + MAX77775_CHG_REG_CNFG_05 = 0xBC, + MAX77775_CHG_REG_CNFG_06 = 0xBD, + MAX77775_CHG_REG_CNFG_07 = 0xBE, + MAX77775_CHG_REG_CNFG_08 = 0xBF, + MAX77775_CHG_REG_CNFG_09 = 0xC0, + MAX77775_CHG_REG_CNFG_10 = 0xC1, + MAX77775_CHG_REG_CNFG_11 = 0xC2, + MAX77775_CHG_REG_CNFG_12 = 0xC3, + MAX77775_CHG_REG_CNFG_13 = 0xC4, + MAX77775_CHG_REG_CNFG_14 = 0xC5, + MAX77775_CHG_REG_CNFG_16 = 0xC7, + MAX77775_CHG_REG_CNFG_17 = 0xC8, + + MAX77775_PMIC_REG_END, +}; + +/* Slave addr = 0x6C : Fuelgauge */ +enum max77775_fuelgauge_reg { + MAX77775_FG_REG_STATUS = 0x00, + MAX77775_FG_REG_VALRTTH = 0x01, + MAX77775_FG_REG_TALRTTH = 0x02, + MAX77775_FG_REG_SALRTTH = 0x03, + MAX77775_FG_REG_REPCAP = 0x05, + MAX77775_FG_REG_REPSOC = 0x06, + MAX77775_FG_REG_TEMP = 0x08, + MAX77775_FG_REG_VCELL = 0x09, + MAX77775_FG_REG_CURRENT = 0x0A, + MAX77775_FG_REG_AVGCURRENT = 0x0B, + MAX77775_FG_REG_AVSOC = 0x0E, + MAX77775_FG_REG_MIXCAP = 0x0F, + MAX77775_FG_REG_FULLCAP = 0x10, + MAX77775_FG_QRTABLE00 = 0x12, + MAX77775_FG_REG_FULLSOCTHR = 0x13, + MAX77775_FG_REG_CYCLES = 0x17, + MAX77775_FG_REG_DESIGNCAP = 0x18, + MAX77775_FG_REG_AVGVCELL = 0x19, + MAX77775_FG_REG_CONFIG = 0x1D, + MAX77775_FG_REG_ICHGTERM = 0x1E, + MAX77775_FG_REG_REMCAPAV = 0x1F, + MAX77775_FG_QRTABLE10 = 0x22, + MAX77775_FG_REG_FULLCAPNOM = 0x23, + MAX77775_FG_REG_LEARNCFG = 0x28, + MAX77775_FG_REG_FILTERCFG = 0x29, + MAX77775_FG_REG_MISCCFG = 0x2B, + MAX77775_FG_REG_CGAIN = 0x2E, + MAX77775_FG_REG_COFF = 0x2F, + MAX77775_FG_QRTABLE20 = 0x32, + MAX77775_FG_REG_FULLCAPREP = 0x35, + MAX77775_FG_REG_RCOMP0 = 0x38, + MAX77775_FG_REG_TEMPCO = 0x39, + MAX77775_FG_REG_VEMPTY = 0x3A, + MAX77775_FG_QRTABLE30 = 0x42, + MAX77775_FG_REG_ISYS = 0x43, + MAX77775_FG_REG_DQACC = 0x45, + MAX77775_FG_REG_DPACC = 0x46, + MAX77775_FG_REG_AVGISYS = 0x4B, + MAX77775_FG_REG_QH = 0x4D, + MAX77775_FG_REG_VSYS = 0xB1, + MAX77775_FG_REG_TALRTTH2 = 0xB2, + /* "not used REG(0xB2)" is for checking fuelgague init result. */ + MAX77775_FG_INIT_RESULT_REG = MAX77775_FG_REG_TALRTTH2, + MAX77775_FG_REG_VBYP = 0xB3, + MAX77775_FG_REG_CONFIG2 = 0xBB, + MAX77775_FG_REG_IIN = 0xD0, + MAX77775_FG_REG_VFOCV = 0xFB, + MAX77775_FG_REG_VFSOC = 0xFF, + + MAX77775_FG_REG_END +}; + +#define MAX77775_REG_MAINCTRL1_BIASEN (1 << 7) + +/* Slave addr = 0x4A: USBC */ +enum max77775_usbc_reg { + MAX77775_USBC_REG_PRODUCT_ID = 0x10, /* replaced address */ + MAX77775_USBC_REG_UIC_FW_REV = 0x01, + MAX77775_USBC_REG_UIC_INT = 0x02, + MAX77775_USBC_REG_CC_INT = 0x03, + MAX77775_USBC_REG_PD_INT = 0x04, + MAX77775_USBC_REG_VDM_INT = 0x05, + MAX77775_USBC_REG_SPARE_INT = 0x06, + MAX77775_USBC_REG_USBC_STATUS1 = 0x08, + MAX77775_USBC_REG_USBC_STATUS2 = 0x09, + MAX77775_USBC_REG_BC_STATUS = 0x0A, + MAX77775_USBC_REG_UIC_FW_REV2 = 0x0B, + MAX77775_USBC_REG_CC_STATUS1 = 0x0C, + MAX77775_USBC_REG_CC_STATUS2 = 0x0D, + MAX77775_USBC_REG_PD_STATUS1 = 0x0E, + MAX77775_USBC_REG_PD_STATUS2 = 0x0F, + MAX77775_USBC_REG_SPARE_STATUS1 = 0x11, + MAX77775_USBC_REG_UIC_INT_M = 0x12, + MAX77775_USBC_REG_CC_INT_M = 0x13, + MAX77775_USBC_REG_PD_INT_M = 0x14, + MAX77775_USBC_REG_VDM_INT_M = 0x15, + MAX77775_USBC_REG_SPARE_INT_M = 0x16, + MAX77775_USBC_REG_AP_DATAOUT_M1 = 0x20, + MAX77775_USBC_REG_AP_DATAOUT0 = 0x21, + MAX77775_USBC_REG_AP_DATAOUT1 = 0x22, + MAX77775_USBC_REG_AP_DATAOUT2 = 0x23, + MAX77775_USBC_REG_AP_DATAOUT3 = 0x24, + MAX77775_USBC_REG_AP_DATAOUT4 = 0x25, + MAX77775_USBC_REG_AP_DATAOUT5 = 0x26, + MAX77775_USBC_REG_AP_DATAOUT6 = 0x27, + MAX77775_USBC_REG_AP_DATAOUT7 = 0x28, + MAX77775_USBC_REG_AP_DATAOUT8 = 0x29, + MAX77775_USBC_REG_AP_DATAOUT9 = 0x2a, + MAX77775_USBC_REG_AP_DATAOUT10 = 0x2b, + MAX77775_USBC_REG_AP_DATAOUT11 = 0x2c, + MAX77775_USBC_REG_AP_DATAOUT12 = 0x2d, + MAX77775_USBC_REG_AP_DATAOUT13 = 0x2e, + MAX77775_USBC_REG_AP_DATAOUT14 = 0x2f, + MAX77775_USBC_REG_AP_DATAOUT15 = 0x30, + MAX77775_USBC_REG_AP_DATAOUT16 = 0x31, + MAX77775_USBC_REG_AP_DATAOUT17 = 0x32, + MAX77775_USBC_REG_AP_DATAOUT18 = 0x33, + MAX77775_USBC_REG_AP_DATAOUT19 = 0x34, + MAX77775_USBC_REG_AP_DATAOUT20 = 0x35, + MAX77775_USBC_REG_AP_DATAOUT21 = 0x36, + MAX77775_USBC_REG_AP_DATAOUT22 = 0x37, + MAX77775_USBC_REG_AP_DATAOUT23 = 0x38, + MAX77775_USBC_REG_AP_DATAOUT24 = 0x39, + MAX77775_USBC_REG_AP_DATAOUT25 = 0x3a, + MAX77775_USBC_REG_AP_DATAOUT26 = 0x3b, + MAX77775_USBC_REG_AP_DATAOUT27 = 0x3c, + MAX77775_USBC_REG_AP_DATAOUT28 = 0x3d, + MAX77775_USBC_REG_AP_DATAOUT29 = 0x3e, + MAX77775_USBC_REG_AP_DATAOUT30 = 0x3f, + MAX77775_USBC_REG_AP_DATAOUT31 = 0x40, + MAX77775_USBC_REG_AP_DATAOUT32 = 0x41, + MAX77775_USBC_REG_AP_DATAIN_M1 = 0x50, + MAX77775_USBC_REG_AP_DATAIN0 = 0x51, + MAX77775_USBC_REG_AP_DATAIN1 = 0x52, + MAX77775_USBC_REG_AP_DATAIN2 = 0x53, + MAX77775_USBC_REG_AP_DATAIN3 = 0x54, + MAX77775_USBC_REG_AP_DATAIN4 = 0x55, + MAX77775_USBC_REG_AP_DATAIN5 = 0x56, + MAX77775_USBC_REG_AP_DATAIN6 = 0x57, + MAX77775_USBC_REG_AP_DATAIN7 = 0x58, + MAX77775_USBC_REG_AP_DATAIN8 = 0x59, + MAX77775_USBC_REG_AP_DATAIN9 = 0x5a, + MAX77775_USBC_REG_AP_DATAIN10 = 0x5b, + MAX77775_USBC_REG_AP_DATAIN11 = 0x5c, + MAX77775_USBC_REG_AP_DATAIN12 = 0x5d, + MAX77775_USBC_REG_AP_DATAIN13 = 0x5e, + MAX77775_USBC_REG_AP_DATAIN14 = 0x5f, + MAX77775_USBC_REG_AP_DATAIN15 = 0x60, + MAX77775_USBC_REG_AP_DATAIN16 = 0x61, + MAX77775_USBC_REG_AP_DATAIN17 = 0x62, + MAX77775_USBC_REG_AP_DATAIN18 = 0x63, + MAX77775_USBC_REG_AP_DATAIN19 = 0x64, + MAX77775_USBC_REG_AP_DATAIN20 = 0x65, + MAX77775_USBC_REG_AP_DATAIN21 = 0x66, + MAX77775_USBC_REG_AP_DATAIN22 = 0x67, + MAX77775_USBC_REG_AP_DATAIN23 = 0x68, + MAX77775_USBC_REG_AP_DATAIN24 = 0x69, + MAX77775_USBC_REG_AP_DATAIN25 = 0x6a, + MAX77775_USBC_REG_AP_DATAIN26 = 0x6b, + MAX77775_USBC_REG_AP_DATAIN27 = 0x6c, + MAX77775_USBC_REG_AP_DATAIN28 = 0x6d, + MAX77775_USBC_REG_AP_DATAIN29 = 0x6e, + MAX77775_USBC_REG_AP_DATAIN30 = 0x6f, + MAX77775_USBC_REG_AP_DATAIN31 = 0x70, + MAX77775_USBC_REG_AP_DATAIN32 = 0x71, + MAX77775_USBC_REG_UIC_SWRST = 0x80, + + MAX77775_USBC_REG_END, +}; + +enum max77775_irq_source { + SYS_INT = 0, + CHG_INT, + FUEL_INT, + USBC_INT, + CC_INT, + PD_INT, + VDM_INT, + SPARE_INT, + VIR_INT, + MAX77775_IRQ_GROUP_NR, +}; + +enum max77775_irq { + /* PMIC; TOPSYS */ + MAX77775_SYSTEM_IRQ_SYSUVLO_INT, + MAX77775_SYSTEM_IRQ_SYSOVLO_INT, + MAX77775_SYSTEM_IRQ_TSHDN_INT, + MAX77775_SYSTEM_IRQ_SCP_INT, + + /* PMIC; Charger */ + MAX77775_CHG_IRQ_BYP_I, + MAX77775_CHG_IRQ_BATP_I, + MAX77775_CHG_IRQ_BAT_I, + MAX77775_CHG_IRQ_CHG_I, + MAX77775_CHG_IRQ_WCIN_I, + MAX77775_CHG_IRQ_CHGIN_I, + MAX77775_CHG_IRQ_AICL_I, + + /* Fuelgauge */ + MAX77775_FG_IRQ_ALERT, + + /* USBC */ + MAX77775_USBC_IRQ_APC_INT, + + /* CC */ + MAX77775_CC_IRQ_VCONNCOP_INT, + MAX77775_CC_IRQ_VSAFE0V_INT, + MAX77775_CC_IRQ_DETABRT_INT, + MAX77775_CC_IRQ_VCONNSC_INT, + MAX77775_CC_IRQ_CCPINSTAT_INT, + MAX77775_CC_IRQ_CCISTAT_INT, + MAX77775_CC_IRQ_CCVCNSTAT_INT, + MAX77775_CC_IRQ_CCSTAT_INT, + + MAX77775_USBC_IRQ_VBUS_INT, + MAX77775_USBC_IRQ_VBADC_INT, + MAX77775_USBC_IRQ_DCD_INT, + MAX77775_USBC_IRQ_STOPMODE_INT, + MAX77775_USBC_IRQ_CHGT_INT, + MAX77775_USBC_IRQ_UIDADC_INT, + + /* + * USBC: SYSMSG INT should be after CC INT + * because of 2 times of CC Sync INT at WDT reset + */ + MAX77775_USBC_IRQ_SYSM_INT, + + /* PD */ + MAX77775_PD_IRQ_PDMSG_INT, + MAX77775_PD_IRQ_PS_RDY_INT, + MAX77775_PD_IRQ_DATAROLE_INT, + MAX77775_PD_IRQ_SSACCI_INT, + MAX77775_PD_IRQ_FCTIDI_INT, + + + /* VDM */ + MAX77775_IRQ_VDM_DISCOVER_ID_INT, + MAX77775_IRQ_VDM_DISCOVER_SVIDS_INT, + MAX77775_IRQ_VDM_DISCOVER_MODES_INT, + MAX77775_IRQ_VDM_ENTER_MODE_INT, + MAX77775_IRQ_VDM_DP_STATUS_UPDATE_INT, + MAX77775_IRQ_VDM_DP_CONFIGURE_INT, + MAX77775_IRQ_VDM_ATTENTION_INT, + + /* SPARE */ + MAX77775_IRQ_USBID_INT, + MAX77775_IRQ_TACONN_INT, + + /* VIRTUAL */ + MAX77775_VIR_IRQ_ALTERROR_INT, + + MAX77775_IRQ_NR, +}; + +struct max77775_dev { + struct device *dev; + struct i2c_client *i2c; /* 0xCC; PMIC */ + struct i2c_client *charger; /* 0xD2; Charger */ + struct i2c_client *fuelgauge; /* 0x6C; Fuelgauge */ + struct i2c_client *muic; /* 0x4A; MUIC */ + struct i2c_client *testsid; /* 0xC4; TestSID */ + struct mutex i2c_lock; + struct wakeup_source ws; + + int type; + + int irq; + int irq_base; + int irq_gpio; + bool blocking_waterevent; + u32 required_hw_rev; + u32 required_fw_pid; + struct mutex irqlock; + int irq_masks_cur[MAX77775_IRQ_GROUP_NR]; + int irq_masks_cache[MAX77775_IRQ_GROUP_NR]; + + u8 FW_Revision; + u8 FW_Minor_Revision; + u8 FW_Product_ID; + u8 FW_Revision_bin; + u8 FW_Minor_Revision_bin; + u8 FW_Product_ID_bin; + + struct work_struct fw_work; + struct workqueue_struct *fw_workqueue; + struct completion fw_completion; + int fw_update_state; + int fw_size; + + /* For hibernation */ + u8 reg_pmic_dump[MAX77775_PMIC_REG_END]; + u8 reg_muic_dump[MAX77775_USBC_REG_END]; + + u8 pmic_id; /* pmic id. 0x75 */ + u8 pmic_rev; /* pmic Rev */ + + u8 cc_booting_complete; + + int set_altmode_en; + int enable_nested_irq; + u8 usbc_irq; + + bool shutdown; + bool suspended; + wait_queue_head_t suspend_wait; + + void (*check_pdmsg)(void *data, u8 pdmsg); + void *usbc_data; + + struct max77775_platform_data *pdata; +}; + +enum max77775_types { + TYPE_MAX77775, +}; + +extern int max77775_irq_init(struct max77775_dev *max77775); +extern void max77775_irq_exit(struct max77775_dev *max77775); + +/* MAX77775 shared i2c API function */ +extern int max77775_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest); +extern int max77775_bulk_read(struct i2c_client *i2c, u8 reg, int count, + u8 *buf); +extern int max77775_write_reg(struct i2c_client *i2c, u8 reg, u8 value); +extern int max77775_write_reg_nolock(struct i2c_client *i2c, u8 reg, u8 value); +extern int max77775_bulk_write(struct i2c_client *i2c, u8 reg, int count, + u8 *buf); +extern int max77775_write_word(struct i2c_client *i2c, u8 reg, u16 value); +extern int max77775_read_word(struct i2c_client *i2c, u8 reg); + +extern int max77775_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask); + +/* MAX77775 check muic path function */ +extern bool is_muic_usb_path_ap_usb(void); +extern bool is_muic_usb_path_cp_usb(void); + +/* for charger api */ +extern void max77775_hv_muic_charger_init(void); +extern int max77775_usbc_fw_update(struct max77775_dev *max77775, const u8 *fw_bin, int fw_bin_len, int enforce_do); +extern int max77775_usbc_fw_setting(struct max77775_dev *max77775, int enforce_do); +extern void max77775_register_pdmsg_func(struct max77775_dev *max77775, + void (*check_pdmsg)(void *, u8), void *data); + +#define MAX77775_DEBUG_ENABLED +#ifdef MAX77775_DEBUG_ENABLED +#define msg_maxim(format, args...) \ + md75_info_usb("max77775: %s: " format "\n", __func__, ## args) +#define err_maxim(format, args...) \ + md75_err_usb("max77775: %s: " format "\n", __func__, ## args) +#else +#define msg_maxim(format, args...) +#define err_maxim(format, args...) +#endif // MAX77775_DEBUG_ENABLED + +#endif /* __LINUX_MFD_MAX77775_PRIV_H */ + diff --git a/include/linux/mfd/max77775.h b/include/linux/mfd/max77775.h new file mode 100755 index 000000000000..bf9dbb66d13f --- /dev/null +++ b/include/linux/mfd/max77775.h @@ -0,0 +1,119 @@ +/* + * max77775.h - Driver for the Maxim 77775 + * + * Copyright (C) 2016 Samsung Electrnoics + * Insun Choi + * + * 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, see . + * + * This driver is based on max8997.h + * + * MAX77775 has Flash LED, SVC LED, Haptic, MUIC devices. + * The devices share the same I2C bus and included in + * this mfd driver. + */ + +#ifndef __MAX77775_H__ +#define __MAX77775_H__ +#include +#include + +#define MFD_DEV_NAME "max77775" +#define M2SH(m) ((m) & 0x0F ? ((m) & 0x03 ? ((m) & 0x01 ? 0 : 1) : ((m) & 0x04 ? 2 : 3)) : \ + ((m) & 0x30 ? ((m) & 0x10 ? 4 : 5) : ((m) & 0x40 ? 6 : 7))) + +struct max77775_vibrator_pdata { + int gpio; + char *regulator_name; + struct pwm_device *pwm; + const char *motor_type; + + int freq; + /* for multi-frequency */ + int freq_nums; + u32 *freq_array; + u32 *ratio_array; /* not used now */ + int normal_ratio; + int overdrive_ratio; + int high_temp_ratio; + int high_temp_ref; + int fold_open_ratio; + int fold_close_ratio; +#if defined(CONFIG_SEC_VIBRATOR) + bool calibration; + int steps; + int *intensities; + int *haptic_intensities; +#endif +}; + +struct max77775_regulator_data { + int id; + struct regulator_init_data *initdata; + struct device_node *reg_node; +}; + +struct max77775_platform_data { + /* IRQ */ + int irq_base; + int irq_gpio; + bool wakeup; + bool blocking_waterevent; + bool extra_fw_enable; + int wpc_en; + u32 rev; + u32 fw_product_id; + struct muic_platform_data *muic_pdata; + + int num_regulators; + struct max77775_regulator_data *regulators; + struct max77775_vibrator_pdata *vibrator_data; + struct mfd_cell *sub_devices; + int num_subdevs; + bool support_audio; + char *wireless_charger_name; +}; + +struct max77775 { + struct regmap *regmap; +}; + +typedef struct { + u32 magic; /* magic number */ + u8 major; /* major version */ + u8 minor; /* minor version */ + u8 id; /* id */ + u8 rev; /* rev */ +} max77775_fw_header; +#define MAX77775_SIGN 0xCEF166C1 + +enum { + FW_UPDATE_START = 0x00, + FW_UPDATE_WAIT_RESP_START, + FW_UPDATE_WAIT_RESP_STOP, + FW_UPDATE_DOING, + FW_UPDATE_END, +}; + +enum { + FW_UPDATE_FAIL = 0xF0, + FW_UPDATE_I2C_FAIL, + FW_UPDATE_TIMEOUT_FAIL, + FW_UPDATE_VERIFY_FAIL, + FW_UPDATE_CMD_FAIL, + FW_UPDATE_MAX_LENGTH_FAIL, +}; + +#endif /* __MAX77775_H__ */ + diff --git a/include/linux/mfd/max77775_log.h b/include/linux/mfd/max77775_log.h new file mode 100755 index 000000000000..8431f620463b --- /dev/null +++ b/include/linux/mfd/max77775_log.h @@ -0,0 +1,35 @@ +/* + * max77775_log.h - Driver for the Maxim 77775 + * + */ + +#ifndef __MAX77775_LOG_H__ +#define __MAX77775_LOG_H__ + +#ifdef CONFIG_USB_NOTIFY_PROC_LOG +#include +#endif + +#ifdef CONFIG_USB_USING_ADVANCED_USBLOG +#define md75_info_usb(fmt, ...) \ + ({ \ + pr_info(fmt, ##__VA_ARGS__); \ + printk_usb(NOTIFY_PRINTK_USB_NORMAL, fmt, ##__VA_ARGS__); \ + }) +#define md75_err_usb(fmt, ...) \ + ({ \ + pr_err(fmt, ##__VA_ARGS__); \ + printk_usb(NOTIFY_PRINTK_USB_NORMAL, fmt, ##__VA_ARGS__); \ + }) +#else +#define md75_info_usb(fmt, ...) \ + ({ \ + pr_info(fmt, ##__VA_ARGS__); \ + }) +#define md75_err_usb(fmt, ...) \ + ({ \ + pr_err(fmt, ##__VA_ARGS__); \ + }) +#endif + +#endif /* __MAX77775_LOG_H__ */ diff --git a/include/linux/mfd/samsung/s2mpb02-regulator.h b/include/linux/mfd/samsung/s2mpb02-regulator.h new file mode 100644 index 000000000000..f9a7b4e74761 --- /dev/null +++ b/include/linux/mfd/samsung/s2mpb02-regulator.h @@ -0,0 +1,134 @@ +/* + * s2mpb02-regulator.h - Voltage regulator driver for the Samsung s2mpb02 + * + * Copyright (C) 2014 Samsung Electrnoics + * + * 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 __LINUX_MFD_S2MPB02_REGULATOR_H +#define __LINUX_MFD_S2MPB02_REGULATOR_H + + +/* S2MPB02 registers */ +enum s2mpb02_reg { + S2MPB02_REG_ID, + S2MPB02_REG_INT1, + S2MPB02_REG_INT1M, + S2MPB02_REG_ST1, + S2MPB02_REG_CTRL, + S2MPB02_REG_RSVD5, + S2MPB02_REG_RSVD6, + S2MPB02_REG_RSVD7, + S2MPB02_REG_RSVD8, + S2MPB02_REG_BBEN_DBT, + S2MPB02_REG_RSVDA, + S2MPB02_REG_B1CTRL1, + S2MPB02_REG_B1CTRL2, + S2MPB02_REG_B2CTRL1, + S2MPB02_REG_B2CTRL2, + S2MPB02_REG_BB1CTRL1, + S2MPB02_REG_BB1CTRL2, + S2MPB02_REG_BUCK_RAMP, + S2MPB02_REG_FLED_CTRL1, + S2MPB02_REG_FLED_CTRL2, + S2MPB02_REG_FLED_CUR1, + S2MPB02_REG_FLED_TIME1, + S2MPB02_REG_FLED_CUR2, + S2MPB02_REG_FLED_TIME2, + S2MPB02_REG_FLED_IRON1, + S2MPB02_REG_FLED_IRON2, + S2MPB02_REG_FLED_IRD1, + S2MPB02_REG_FLED_IRD2, + S2MPB02_REG_BST_CTRL1, + S2MPB02_REG_BST_CTRL2, + S2MPB02_REG_L1CTRL, + S2MPB02_REG_L2CTRL, + S2MPB02_REG_L3CTRL, + S2MPB02_REG_L4CTRL, + S2MPB02_REG_L5CTRL, + S2MPB02_REG_L6CTRL, + S2MPB02_REG_L7CTRL, + S2MPB02_REG_L8CTRL, + S2MPB02_REG_L9CTRL, + S2MPB02_REG_L10CTRL, + S2MPB02_REG_L11CTRL, + S2MPB02_REG_L12CTRL, + S2MPB02_REG_L13CTRL, + S2MPB02_REG_L14CTRL, + S2MPB02_REG_L15CTRL, + S2MPB02_REG_L16CTRL, + S2MPB02_REG_L17CTRL, + S2MPB02_REG_L18CTRL, + S2MPB02_REG_LDO_DSCH1, + S2MPB02_REG_LDO_DSCH2, + S2MPB02_REG_LDO_DSCH3, +}; + +/* S2MPB02 regulator ids */ +enum S2MPB02_regulators { + S2MPB02_LDO1, + S2MPB02_LDO2, + S2MPB02_LDO3, + S2MPB02_LDO4, + S2MPB02_LDO5, + S2MPB02_LDO6, + S2MPB02_LDO7, + S2MPB02_LDO8, + S2MPB02_LDO9, + S2MPB02_LDO10, + S2MPB02_LDO11, + S2MPB02_LDO12, + S2MPB02_LDO13, + S2MPB02_LDO14, + S2MPB02_LDO15, + S2MPB02_LDO16, + S2MPB02_LDO17, + S2MPB02_LDO18, + S2MPB02_BUCK1, + S2MPB02_BUCK2, + S2MPB02_BB1, + + S2MPB02_REG_MAX, +}; + +#define S2MPB02_BUCK_MIN1 400000 +#define S2MPB02_BUCK_MIN2 2600000 +#define S2MPB02_LDO_MIN1 600000 +#define S2MPB02_BUCK_STEP1 6250 +#define S2MPB02_BUCK_STEP2 12500 +#define S2MPB02_LDO_STEP1 12500 +#define S2MPB02_LDO_STEP2 25000 +#define S2MPB02_LDO_VSEL_MASK 0x7F +#define S2MPB02_BUCK_VSEL_MASK 0xFF +#define S2MPB02_BUCK_ENABLE_MASK 0xC0 +#define S2MPB02_LDO_ENABLE_MASK 0x80 + +#define S2MPB02_RAMP_DELAY 12000 + +#define S2MPB02_ENABLE_TIME_LDO 180 +#define S2MPB02_ENABLE_TIME_BUCK 100 +#define S2MPB02_ENABLE_TIME_BB 210 + +#define S2MPB02_BUCK_ENABLE_SHIFT 0x06 +#define S2MPB02_LDO_ENABLE_SHIFT 0x07 +#define S2MPB02_LDO_N_VOLTAGES (S2MPB02_LDO_VSEL_MASK + 1) +#define S2MPB02_BUCK_N_VOLTAGES (S2MPB02_BUCK_VSEL_MASK + 1) + +#define S2MPB02_REGULATOR_MAX (S2MPB02_REG_MAX) + +#define DVS_DEFAULT_VALUE (0x90) + +#endif /* __LINUX_MFD_S2MPB02_PRIV_H */ diff --git a/include/linux/mfd/samsung/s2mpb02.h b/include/linux/mfd/samsung/s2mpb02.h new file mode 100644 index 000000000000..37986f176056 --- /dev/null +++ b/include/linux/mfd/samsung/s2mpb02.h @@ -0,0 +1,121 @@ +/* + * s2mpb02.h - Driver for the Samsung s2mpb02 + * + * Copyright (C) 2014 Samsung Electrnoics + * + * 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 + * + * + * S2MPB02 has Flash LED devices. + * The devices share the same I2C bus and included in + * this mfd driver. + */ + +#ifndef __S2MPB02_H__ +#define __S2MPB02_H__ + +#include + +#define MFD_DEV_NAME "s2mpb02" + +#define S2MPB02_I2C_ADDR (0xB2 >> 1) +#define S2MPB02_REG_INVALID (0xff) + +#define S2MPB02_PMIC_REV(iodev) (iodev)->rev_num + +enum s2mpb02_types { + TYPE_S2MPB02, +}; + +enum s2mpb02_reg_types { + TYPE_S2MPB02_REG_MAIN = 0, + TYPE_S2MPB02_REG_SUB, + TYPE_S2MPB02_REG_MAX +}; + +enum s2mpb02_irq_source { + LED_INT = 0, + S2MPB02_IRQ_GROUP_NR, +}; + +enum s2mpb02_irq { + /* FLASH */ + S2MPB02_LED_IRQ_IRLED_END, + + S2MPB02_IRQ_NR, +}; + +struct s2mpb02_dev { + struct device *dev; + struct i2c_client *i2c; /* 0xB2; PMIC, Flash LED */ + struct mutex i2c_lock; + + int type; + u8 rev_num; /* pmic Rev */ + int irq; + int irq_base; + int irq_gpio; + bool wakeup; + struct mutex irqlock; + int irq_masks_cur[S2MPB02_IRQ_GROUP_NR]; + int irq_masks_cache[S2MPB02_IRQ_GROUP_NR]; + + struct s2mpb02_platform_data *pdata; +}; + +#ifdef CONFIG_LEDS_S2MPB02 +struct s2mpb02_led_platform_data; +#endif + +struct s2mpb02_regulator_data { + int id; + struct regulator_init_data *initdata; + struct device_node *reg_node; +}; + +struct s2mpb02_platform_data { + /* IRQ */ + int irq_base; + int irq_gpio; + bool wakeup; + bool need_recovery;; + + int num_regulators; + int num_rdata; + struct s2mpb02_regulator_data *regulators; +#ifdef CONFIG_LEDS_S2MPB02 + /* led (flash/torch) data */ + struct s2mpb02_led_platform_data *led_data; +#endif + + int devs_num; + struct mfd_cell *devs; +}; + +extern int s2mpb02_irq_init(struct s2mpb02_dev *s2mpb02); +extern void s2mpb02_irq_exit(struct s2mpb02_dev *s2mpb02); +extern int s2mpb02_irq_resume(struct s2mpb02_dev *s2mpb02); + +/* S2MPB02 shared i2c API function */ +extern int s2mpb02_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest); +extern int s2mpb02_bulk_read(struct i2c_client *i2c, u8 reg, int count, + u8 *buf); +extern int s2mpb02_write_reg(struct i2c_client *i2c, u8 reg, u8 value); +extern int s2mpb02_bulk_write(struct i2c_client *i2c, u8 reg, int count, + u8 *buf); +extern int s2mpb02_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask); + +#endif /* __S2MPB02_H__ */ + diff --git a/include/linux/mfd/sec_ap_pmic.h b/include/linux/mfd/sec_ap_pmic.h new file mode 100644 index 000000000000..f5612aabff9f --- /dev/null +++ b/include/linux/mfd/sec_ap_pmic.h @@ -0,0 +1,32 @@ +#ifndef __SEC_AP_PMIC_H__ +#define __SEC_AP_PMIC_H__ + +#define SEC_PON_KEY_CNT 2 + +struct sec_ap_pmic_info { + struct device *dev; + int chg_det_gpio; +}; + +enum sec_pon_type { + SEC_PON_KPDPWR = 0, + SEC_PON_RESIN, + SEC_PON_KPDPWR_RESIN, +}; + +/* for enable/disable manual reset, from retail group's request */ +extern int sec_get_s2_reset(enum sec_pon_type type); +extern int sec_set_pm_key_wk_init(enum sec_pon_type type, int en); +extern int sec_get_pm_key_wk_init(enum sec_pon_type type); + +extern void msm_gpio_print_enabled(void); +extern void pmic_gpio_sec_dbg_enabled(void); + +#if IS_ENABLED(CONFIG_SEC_GPIO_DUMP) +extern void sec_ap_gpio_debug_print(void); +extern void sec_pmic_gpio_debug_print(void); +static bool gpio_dump_enabled; +#endif + +#endif /* __SEC_AP_PMIC_H__ */ + diff --git a/include/linux/mm.h b/include/linux/mm.h index a17bd3a6413f..4e1ff1e09aa9 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -3714,6 +3714,26 @@ void __init setup_nr_node_ids(void); static inline void setup_nr_node_ids(void) {} #endif +struct seq_file; +void seq_printf(struct seq_file *m, const char *f, ...); + +static inline void show_val_meminfo(struct seq_file *m, + const char *str, long size) +{ + char name[17]; + int len = strlen(str); + + if (len <= 15) { + sprintf(name, "%s:", str); + } else { + strncpy(name, str, 15); + name[15] = ':'; + name[16] = '\0'; + } + + seq_printf(m, "%-16s%8ld kB\n", name, size); +} + extern int memcmp_pages(struct page *page1, struct page *page2); static inline int pages_identical(struct page *page1, struct page *page2) @@ -3786,4 +3806,6 @@ madvise_set_anon_name(struct mm_struct *mm, unsigned long start, } #endif +#define GPU_PAGE_MAGIC (0x9A0E06B9A0E) + #endif /* _LINUX_MM_H */ diff --git a/include/linux/msm_gsi.h b/include/linux/msm_gsi.h new file mode 100644 index 000000000000..0779fd7b2dfd --- /dev/null +++ b/include/linux/msm_gsi.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015-2020, The Linux Foundation. All rights reserved. + */ + +#ifndef MSM_GSI_H +#define MSM_GSI_H +#include +#include + +enum gsi_chan_dir { + GSI_CHAN_DIR_FROM_GSI = 0x0, + GSI_CHAN_DIR_TO_GSI = 0x1 +}; + +/** + * @GSI_USE_PREFETCH_BUFS: Channel will use normal prefetch buffers if possible + * @GSI_ESCAPE_BUF_ONLY: Channel will always use escape buffers only + * @GSI_SMART_PRE_FETCH: Channel will work in smart prefetch mode. + * relevant starting GSI 2.5 + * @GSI_FREE_PRE_FETCH: Channel will work in free prefetch mode. + * relevant starting GSI 2.5 + */ +enum gsi_prefetch_mode { + GSI_USE_PREFETCH_BUFS = 0x0, + GSI_ESCAPE_BUF_ONLY = 0x1, + GSI_SMART_PRE_FETCH = 0x2, + GSI_FREE_PRE_FETCH = 0x3, +}; + +#endif diff --git a/include/linux/msm_pcie.h b/include/linux/msm_pcie.h index de4c318c5c99..e4ff044c6d49 100644 --- a/include/linux/msm_pcie.h +++ b/include/linux/msm_pcie.h @@ -322,4 +322,33 @@ static inline int msm_pcie_fmd_enable(struct pci_dev *pci_dev) } #endif /* CONFIG_PCI_MSM */ +#ifdef CONFIG_SEC_PCIE_L1SS +enum l1ss_ctrl_ids { + L1SS_SYSFS, + L1SS_MST, + L1SS_AUDIO, + L1SS_MAX +}; + +extern void sec_pcie_set_use_ep_loaded(struct pci_dev *dev); +extern void sec_pcie_set_ep_driver_loaded(struct pci_dev *dev, bool is_loaded); + +extern int sec_pcie_l1ss_enable(int ctrl_id); +extern int sec_pcie_l1ss_disable(int ctrl_id); +#else + +static inline void sec_pcie_set_use_ep_loaded(dev) {} +static inline void sec_pcie_set_ep_driver_loaded(dev, is_loaded) {} + +static inline int sec_pcie_l1ss_enable(int ctrl_id) +{ + return -ENODEV; +} + +static inline int sec_pcie_l1ss_disable(int ctrl_id) +{ + return -ENODEV; +} +#endif + #endif /* __MSM_PCIE_H */ diff --git a/include/linux/muic/common/muic.h b/include/linux/muic/common/muic.h new file mode 100644 index 000000000000..7990f5528deb --- /dev/null +++ b/include/linux/muic/common/muic.h @@ -0,0 +1,708 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * include/linux/muic/common/muic.h + * + * header file supporting MUIC common information + * + * Copyright (C) 2022 Samsung Electronics + * + * 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, see . + * + */ + +#ifndef __MUIC_H__ +#define __MUIC_H__ + +#ifdef CONFIG_IFCONN_NOTIFIER +#include +#endif +#include +#include + +#define MUIC_CORE "MUIC_CORE" +#define SIOP (1 << 0) +#define AFC_REQUEST_CHARGER SIOP +#define FLED (1 << 1) +#define AFC_REQUEST_MST (1 << 2) +#define AFC_REQUEST_MFC (1 << 3) +#define AFC_REQUEST_DETACH_CLEAR_BIT ((SIOP)) +/* Status of IF PMIC chip (suspend and resume) */ +enum { + MUIC_SUSPEND = 0, + MUIC_RESUME, +}; + +/* MUIC Interrupt */ +enum { + MUIC_INTR_DETACH = 0, + MUIC_INTR_ATTACH +}; + +enum muic_op_mode { + OPMODE_MUIC = 0<<0, + OPMODE_PDIC = 1<<0, +}; + +/* MUIC Dock Observer Callback parameter */ +enum { + MUIC_DOCK_DETACHED = 0, + MUIC_DOCK_DESKDOCK = 1, + MUIC_DOCK_CARDOCK = 2, + MUIC_DOCK_AUDIODOCK = 101, + MUIC_DOCK_SMARTDOCK = 102, + MUIC_DOCK_HMT = 105, + MUIC_DOCK_ABNORMAL = 106, + MUIC_DOCK_GAMEPAD = 107, + MUIC_DOCK_GAMEPAD_WITH_EARJACK = 108, +}; + +/* MUIC Path */ +enum { + MUIC_PATH_USB_AP = 0, + MUIC_PATH_USB_CP, + MUIC_PATH_UART_AP, + MUIC_PATH_UART_CP, + MUIC_PATH_OPEN, + MUIC_PATH_AUDIO, +}; + +/* bootparam SWITCH_SEL */ +enum { + SWITCH_SEL_USB_MASK = 0x1, + SWITCH_SEL_UART_MASK = 0x2, + SWITCH_SEL_RUSTPROOF_MASK = 0x8, + SWITCH_SEL_AFC_DISABLE_MASK = 0x100, +}; + +/* bootparam CHARGING_MODE */ +enum { + CH_MODE_AFC_DISABLE_VAL = 0x31, /* char '1' */ +}; + +enum driver_probe_flag { + MUIC_PROBE_DONE = 1 << 0, + CHARGER_PROBE_DONE = 1 << 1, +}; + +/* MUIC ADC table */ +typedef enum { + ADC_GND = 0x00, + ADC_SEND_END = 0x01, /* 0x00001 2K ohm */ + ADC_REMOTE_S11 = 0x0c, /* 0x01100 20.5K ohm */ + ADC_REMOTE_S12 = 0x0d, /* 0x01101 24.07K ohm */ + ADC_RESERVED_VZW = 0x0e, /* 0x01110 28.7K ohm */ + ADC_INCOMPATIBLE_VZW = 0x0f, /* 0x01111 34K ohm */ + ADC_SMARTDOCK = 0x10, /* 0x10000 40.2K ohm */ + ADC_RDU_TA = 0x10, /* 0x10000 40.2K ohm */ + ADC_HMT = 0x11, /* 0x10001 49.9K ohm */ + ADC_AUDIODOCK = 0x12, /* 0x10010 64.9K ohm */ + ADC_USB_LANHUB = 0x13, /* 0x10011 80.07K ohm */ + ADC_CHARGING_CABLE = 0x14, /* 0x10100 102K ohm */ + ADC_UNIVERSAL_MMDOCK = 0x15, /* 0x10101 121K ohm */ + ADC_GAMEPAD = 0x15, /* 0x10101 121K ohm */ + ADC_UART_CABLE = 0x16, /* 0x10110 150K ohm */ + ADC_CEA936ATYPE1_CHG = 0x17, /* 0x10111 200K ohm */ + ADC_JIG_USB_OFF = 0x18, /* 0x11000 255K ohm */ + ADC_JIG_USB_ON = 0x19, /* 0x11001 301K ohm */ + ADC_DESKDOCK = 0x1a, /* 0x11010 365K ohm */ + ADC_CEA936ATYPE2_CHG = 0x1b, /* 0x11011 442K ohm */ + ADC_JIG_UART_OFF = 0x1c, /* 0x11100 523K ohm */ + ADC_JIG_UART_ON = 0x1d, /* 0x11101 619K ohm */ + ADC_AUDIOMODE_W_REMOTE = 0x1e, /* 0x11110 1000K ohm */ + ADC_OPEN = 0x1f, + ADC_OPEN_219 = 0xfb, /* ADC open or 219.3K ohm */ + ADC_219 = 0xfc, /* ADC open or 219.3K ohm */ + + ADC_UNDEFINED = 0xfd, /* Undefied range */ + ADC_DONTCARE = 0xfe, /* ADC don't care for MHL */ + ADC_ERROR = 0xff, /* ADC value read error */ +} muic_adc_t; + +#define IS_JIG_ADC(adc) \ + (((adc == ADC_JIG_USB_OFF) \ + || (adc == ADC_JIG_USB_ON) \ + || (adc == ADC_JIG_UART_OFF) \ + || (adc == ADC_JIG_UART_ON)) ? 1 : 0) + +#define ADC_WATER_THRESHOLD ADC_OPEN + +/* MUIC attached device type */ +typedef enum { + ATTACHED_DEV_NONE_MUIC = 0, + + ATTACHED_DEV_USB_MUIC = 1, + ATTACHED_DEV_CDP_MUIC, + ATTACHED_DEV_OTG_MUIC, + ATTACHED_DEV_TA_MUIC, + ATTACHED_DEV_UNOFFICIAL_MUIC, + ATTACHED_DEV_UNOFFICIAL_TA_MUIC, + ATTACHED_DEV_UNOFFICIAL_ID_MUIC, + ATTACHED_DEV_UNOFFICIAL_ID_TA_MUIC, + ATTACHED_DEV_UNOFFICIAL_ID_ANY_MUIC, + ATTACHED_DEV_UNOFFICIAL_ID_USB_MUIC, + + ATTACHED_DEV_UNOFFICIAL_ID_CDP_MUIC = 11, + ATTACHED_DEV_UNDEFINED_CHARGING_MUIC, + ATTACHED_DEV_DESKDOCK_MUIC, + ATTACHED_DEV_UNKNOWN_VB_MUIC, + ATTACHED_DEV_DESKDOCK_VB_MUIC, + ATTACHED_DEV_CARDOCK_MUIC, + ATTACHED_DEV_JIG_UART_OFF_MUIC, + ATTACHED_DEV_JIG_UART_OFF_VB_MUIC, /* VBUS enabled */ + ATTACHED_DEV_JIG_UART_OFF_VB_OTG_MUIC, /* for otg test */ + ATTACHED_DEV_JIG_UART_OFF_VB_FG_MUIC, /* for fuelgauge test */ + + ATTACHED_DEV_JIG_UART_ON_MUIC = 21, + ATTACHED_DEV_JIG_UART_ON_VB_MUIC, /* VBUS enabled */ + ATTACHED_DEV_JIG_USB_OFF_MUIC, + ATTACHED_DEV_JIG_USB_ON_MUIC, + ATTACHED_DEV_SMARTDOCK_MUIC, + ATTACHED_DEV_SMARTDOCK_VB_MUIC, + ATTACHED_DEV_SMARTDOCK_TA_MUIC, + ATTACHED_DEV_SMARTDOCK_USB_MUIC, + ATTACHED_DEV_UNIVERSAL_MMDOCK_MUIC, + ATTACHED_DEV_AUDIODOCK_MUIC, + + ATTACHED_DEV_MHL_MUIC = 31, + ATTACHED_DEV_CHARGING_CABLE_MUIC, + ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC, + ATTACHED_DEV_AFC_CHARGER_PREPARE_DUPLI_MUIC, + ATTACHED_DEV_AFC_CHARGER_5V_MUIC, + ATTACHED_DEV_AFC_CHARGER_5V_DUPLI_MUIC, + ATTACHED_DEV_AFC_CHARGER_9V_MUIC, + ATTACHED_DEV_AFC_CHARGER_9V_DUPLI_MUIC, + ATTACHED_DEV_AFC_CHARGER_12V_MUIC, + ATTACHED_DEV_AFC_CHARGER_12V_DUPLI_MUIC, + + ATTACHED_DEV_AFC_CHARGER_ERR_V_MUIC = 41, + ATTACHED_DEV_AFC_CHARGER_ERR_V_DUPLI_MUIC, + ATTACHED_DEV_AFC_CHARGER_DISABLED_MUIC, + ATTACHED_DEV_QC_CHARGER_PREPARE_MUIC, + ATTACHED_DEV_QC_CHARGER_5V_MUIC, + ATTACHED_DEV_QC_CHARGER_ERR_V_MUIC, + ATTACHED_DEV_QC_CHARGER_9V_MUIC, + ATTACHED_DEV_HV_ID_ERR_UNDEFINED_MUIC, + ATTACHED_DEV_HV_ID_ERR_UNSUPPORTED_MUIC, + ATTACHED_DEV_HV_ID_ERR_SUPPORTED_MUIC, + + ATTACHED_DEV_HMT_MUIC = 51, + ATTACHED_DEV_VZW_ACC_MUIC, + ATTACHED_DEV_VZW_INCOMPATIBLE_MUIC, + ATTACHED_DEV_USB_LANHUB_MUIC, + ATTACHED_DEV_TYPE1_CHG_MUIC, + ATTACHED_DEV_TYPE2_CHG_MUIC, + ATTACHED_DEV_TYPE3_MUIC, + ATTACHED_DEV_TYPE3_MUIC_TA, + ATTACHED_DEV_TYPE3_ADAPTER_MUIC, + ATTACHED_DEV_TYPE3_CHARGER_MUIC, + + ATTACHED_DEV_NONE_TYPE3_MUIC = 61, + ATTACHED_DEV_UNSUPPORTED_ID_MUIC, + ATTACHED_DEV_UNSUPPORTED_ID_VB_MUIC, + ATTACHED_DEV_TIMEOUT_OPEN_MUIC, + ATTACHED_DEV_WIRELESS_PAD_MUIC, + ATTACHED_DEV_CARKIT_MUIC, + ATTACHED_DEV_POWERPACK_MUIC, + ATTACHED_DEV_UNDEFINED_RANGE_MUIC, + ATTACHED_DEV_HICCUP_MUIC, + ATTACHED_DEV_CHK_WATER_REQ, + + ATTACHED_DEV_CHK_WATER_DRY_REQ = 71, + ATTACHED_DEV_GAMEPAD_MUIC, + ATTACHED_DEV_CHECK_OCP, + ATTACHED_DEV_RDU_TA_MUIC, + ATTACHED_DEV_FACTORY_UART_MUIC, + ATTACHED_DEV_PE_CHARGER_PREPARE_MUIC, + ATTACHED_DEV_PE_CHARGER_9V_MUIC, + ATTACHED_DEV_TURBO_CHARGER, + ATTACHED_DEV_SPECOUT_CHARGER_MUIC, + ATTACHED_DEV_UNKNOWN_MUIC, + + ATTACHED_DEV_POGO_DOCK_MUIC = 81, + ATTACHED_DEV_POGO_DOCK_5V_MUIC, + ATTACHED_DEV_POGO_DOCK_9V_MUIC, + ATTACHED_DEV_POGO_DOCK_34K_MUIC, + ATTACHED_DEV_POGO_DOCK_49_9K_MUIC, + ATTACHED_DEV_ABNORMAL_OTG_MUIC, + ATTACHED_DEV_RETRY_TIMEOUT_OPEN_MUIC, + ATTACHED_DEV_RETRY_AFC_CHARGER_5V_MUIC, + ATTACHED_DEV_RETRY_AFC_CHARGER_9V_MUIC, + ATTACHED_DEV_WIRELESS_TA_MUIC, + + ATTACHED_DEV_LO_TA_MUIC = 91, + ATTACHED_DEV_NUM, +} muic_attached_dev_t; + +#ifdef CONFIG_MUIC_HV_FORCE_LIMIT +/* MUIC attached device type */ +typedef enum { + SILENT_CHG_DONE = 0, + SILENT_CHG_CHANGING = 1, + + SILENT_CHG_NUM, +} muic_silent_change_state_t; +#endif + +/* MUIC HV State type */ +typedef enum { + HV_STATE_INVALID = -1, + HV_STATE_IDLE = 0, + HV_STATE_DCP_CHARGER = 1, + HV_STATE_FAST_CHARGE_ADAPTOR = 2, + HV_STATE_FAST_CHARGE_COMMUNICATION = 3, + HV_STATE_AFC_5V_CHARGER = 4, + HV_STATE_AFC_9V_CHARGER = 5, + HV_STATE_QC_CHARGER = 6, + HV_STATE_QC_5V_CHARGER = 7, + HV_STATE_QC_9V_CHARGER = 8, + HV_STATE_QC_FAILED, + HV_STATE_MAX_NUM, +} muic_hv_state_t; + +typedef enum { + HV_TRANS_INVALID = -1, + HV_TRANS_MUIC_DETACH = 0, + HV_TRANS_DCP_DETECTED, + HV_TRANS_NO_RESPONSE, + HV_TRANS_VDNMON_LOW, + HV_TRANS_FAST_CHARGE_PING_RESPONSE, + HV_TRANS_AFC_TA_DETECTED, + HV_TRANS_QC_TA_DETECTED, + HV_TRANS_VBUS_5V_BOOST, + HV_TRANS_VBUS_BOOST, + HV_TRANS_VBUS_REDUCE, + HV_TRANS_VBUS_UPDATE, + HV_TRANS_FAST_CHARGE_REOPEN, + HV_TRANS_MAX_NUM, +} muic_hv_transaction_t; + +typedef enum { + HV_9V = 0, + HV_5V, +} muic_hv_voltage_t; + +#ifdef CONFIG_MUIC_COMMON_SYSFS +struct muic_sysfs_cb { + int (*set_uart_en)(void *data, int en); + void (*set_uart_sel)(void *data); + int (*get_usb_en)(void *data); + int (*set_usb_en)(void *data, int en); + int (*get_adc)(void *data); + int (*get_mansw)(void *data, char *mesg); + int (*get_interrupt_status)(void *data, char *mesg); + int (*get_register)(void *data, char *mesg); + int (*get_attached_dev)(void *data); + int (*get_otg_test)(void *data); + int (*set_otg_test)(void *data, int en); + void (*set_audio_path)(void *data); + void (*set_apo_factory)(void *data); + int (*get_vbus_value)(void *data); + void (*set_afc_disable)(void *data); + int (*afc_set_voltage)(void *data, int vol); + int (*get_hiccup)(void *data); + int (*set_hiccup)(void *data, int en); + int (*set_overheat_hiccup)(void *data, int en); +}; +#endif +/* muic common callback driver internal data structure + * that setted at muic-core.c file + */ +struct muic_platform_data { +#ifdef CONFIG_MUIC_COMMON_SYSFS + struct device *switch_device; + struct mutex sysfs_mutex; + struct muic_sysfs_cb sysfs_cb; +#endif + struct device *muic_device; + +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + struct muic_dev *muic_d; + struct if_cb_manager *man; +#endif + + int switch_sel; + + /* muic current USB/UART path */ + int usb_path; + int uart_path; + + bool rustproof_on; + bool afc_disable; + + int afc_disabled_updated; + + enum muic_op_mode opmode; + + int vbvolt; + int adc; + + bool is_factory_start; + + unsigned long driver_probe_flag; + + /* muic switch dev register function for DockObserver */ + void (*init_switch_dev_cb) (void); + void (*cleanup_switch_dev_cb) (void); + + void (*jig_uart_cb)(int jig_state); + + /* muic GPIO control function */ +#if IS_MODULE(CONFIG_MUIC_NOTIFIER) + int (*init_gpio_cb)(int switch_sel); +#else + int (*init_gpio_cb)(void); +#endif + int (*set_gpio_usb_sel)(void *data, int usb_path); + int (*set_gpio_uart_sel)(void *data, int uart_path); + int (*set_safeout) (int safeout_path); + + /* muic cable data collecting function */ + void (*init_cable_data_collect_cb)(void); + + /* muic AFC voltage switching function */ + int (*muic_afc_set_voltage_cb)(int voltage); + + /* muic AFC get voltage function */ + int (*muic_afc_get_voltage_cb)(void); + + /* muic hv charger disable function */ + int (*muic_hv_charger_disable_cb)(bool en); + + /* muic check charger init function */ + int (*muic_hv_charger_init_cb)(void); + + /* muic set hiccup mode function */ + int (*muic_set_hiccup_mode_cb)(int on_off); + + /* muic set pogo adc function */ + int (*muic_set_pogo_adc_cb)(int adc); + + /* muic request afc cause */ + int afc_request_cause; + + void *drv_data; +}; + +#define MUIC_PDATA_VOID_FUNC(func, param) \ +{\ + if (func) \ + func(param); \ + else \ + pr_err("[muic_core] func not defined %s\n", __func__); \ +} + +#define MUIC_PDATA_VOID_FUNC_MULTI_PARAM(func, param1, param2) \ +{\ + if (func) \ + func(param1, param2); \ + else \ + pr_err("[muic_core] func not defined %s\n", __func__); \ +} + +#define MUIC_PDATA_FUNC(func, param, ret) \ +{\ + *ret = 0; \ + if (func) \ + *ret = func(param); \ + else \ + pr_err("[muic_core] func not defined %s\n", __func__); \ +} + +#define MUIC_PDATA_FUNC_MULTI_PARAM(func, param1, param2, ret) \ +{ \ + *ret = 0; \ + if (func) \ + *ret = func(param1, param2); \ + else \ + pr_err("[muic_core] func not defined %s\n", __func__); \ +} + +#define MUIC_IS_ATTACHED(dev) \ + (((dev != ATTACHED_DEV_UNKNOWN_MUIC) && (dev != ATTACHED_DEV_NONE_MUIC)) ? (1) : (0)) + +enum muic_param_en { + MUIC_DISABLE = 0, + MUIC_ENABLE +}; + +/* Integration */ +#define ENUM_STR(x, r) { case x: r = #x; break; } + +#define MASK_1b (1) +#define MASK_2b (0x3) +#define MASK_3b (0x7) +#define MASK_4b (0xf) +#define MASK_5b (0x1f) +#define MASK_6b (0x3f) +#define MASK_7b (0x7f) +#define MASK_8b (0xff) + +#define IS_VCHGIN_9V(x) ((8000 <= x) && (x <= 10300)) +#define IS_VCHGIN_5V(x) ((4000 <= x) && (x <= 6000)) + +#define AFC_MRXRDY_CNT_LIMIT (3) +#define AFC_MPING_RETRY_CNT_LIMIT (10) +#define AFC_QC_RETRY_CNT_LIMIT (3) +#define VCHGIN_CHECK_CNT_LIMIT (3) +#define AFC_QC_RETRY_WAIT_CNT_LIMIT (3) + +typedef enum { + AFC_IRQ_VDNMON = 1, + AFC_IRQ_DNRES, + AFC_IRQ_MPNACK, + AFC_IRQ_MRXBUFOW, + AFC_IRQ_MRXTRF, + AFC_IRQ_MRXPERR, + AFC_IRQ_MRXRDY = 7, +} afc_int_t; + +typedef enum { + AFC_NOT_MASK = 0, + AFC_MASK = 1, +} int_mask_t; + +typedef enum { + QC_PROTOCOL, + AFC_PROTOCOL, +} protocol_sw_t; + +typedef enum { + QC_UNKHOWN, + QC_5V, + QC_9V, + QC_12V, +} qc_2p0_type_t; + +typedef enum { + VDNMON_LOW = 0x00, + VDNMON_HIGH = (0x1 << 1), + + VDNMON_DONTCARE = 0xff, +} vdnmon_t; + +/* MUIC afc irq type */ +typedef enum { + MUIC_AFC_IRQ_VDNMON = 0, + MUIC_AFC_IRQ_MRXRDY, + MUIC_AFC_IRQ_VBADC, + MUIC_AFC_IRQ_MPNACK, + MUIC_AFC_IRQ_DONTCARE = 0xff, +} muic_afc_irq_t; + +typedef enum tx_data{ + MUIC_HV_5V = 0, + MUIC_HV_9V, +} muic_afc_txdata_t; + +enum power_supply_lsi_property { +#if !defined(CONFIG_BATTERY_SAMSUNG) || \ + IS_ENABLED(CONFIG_MFD_S2MU106) || IS_ENABLED(CONFIG_MFD_S2MF301) || defined(CONFIG_BATTERY_GKI) + POWER_SUPPLY_LSI_PROP_MIN = 10000, +#else + POWER_SUPPLY_LSI_PROP_MIN = POWER_SUPPLY_EXT_PROP_MAX + 1, +#endif + POWER_SUPPLY_LSI_PROP_POWER_ROLE, + POWER_SUPPLY_LSI_PROP_WATER_CHECK, + POWER_SUPPLY_LSI_PROP_DRY_CHECK, + POWER_SUPPLY_LSI_PROP_WATER_CHECKDONE, + POWER_SUPPLY_LSI_PROP_PM_IRQ_TIME, + POWER_SUPPLY_LSI_PROP_USBPD_OPMODE, + POWER_SUPPLY_LSI_PROP_USBPD_RPCUR, + POWER_SUPPLY_LSI_PROP_USBPD_ATTACHED, + POWER_SUPPLY_LSI_PROP_USBPD_SOURCE_ATTACH, + POWER_SUPPLY_LSI_PROP_WATER_GET_POWER_ROLE, + POWER_SUPPLY_LSI_PROP_GET_CC_STATE, + POWER_SUPPLY_LSI_PROP_WATER_STATUS, + POWER_SUPPLY_LSI_PROP_PD_PSY, + POWER_SUPPLY_LSI_PROP_HICCUP_MODE, + POWER_SUPPLY_LSI_PROP_FAC_WATER_CHECK, + POWER_SUPPLY_LSI_PROP_SET_TH, + POWER_SUPPLY_LSI_PROP_PM_VCHGIN, + POWER_SUPPLY_LSI_PROP_2LV_3LV_CHG_MODE, + POWER_SUPPLY_LSI_PROP_USBPD_RESET, + POWER_SUPPLY_LSI_PROP_PD_SUPPORT, + POWER_SUPPLY_LSI_PROP_VCHGIN, + POWER_SUPPLY_LSI_PROP_VWCIN, + POWER_SUPPLY_LSI_PROP_VBYP, + POWER_SUPPLY_LSI_PROP_VSYS, + POWER_SUPPLY_LSI_PROP_VBAT, + POWER_SUPPLY_LSI_PROP_VGPADC, + POWER_SUPPLY_LSI_PROP_VGPADC1, + POWER_SUPPLY_LSI_PROP_VGPADC2, + POWER_SUPPLY_LSI_PROP_ENABLE_WATER, + POWER_SUPPLY_LSI_PROP_VCC1, + POWER_SUPPLY_LSI_PROP_VCC2, + POWER_SUPPLY_LSI_PROP_ICHGIN, + POWER_SUPPLY_LSI_PROP_IWCIN, + POWER_SUPPLY_LSI_PROP_IOTG, + POWER_SUPPLY_LSI_PROP_ITX, + POWER_SUPPLY_LSI_PROP_CO_ENABLE, + POWER_SUPPLY_LSI_PROP_RR_ENABLE, + POWER_SUPPLY_LSI_PROP_PM_FACTORY, + POWER_SUPPLY_LSI_PROP_PCP_CLK, + POWER_SUPPLY_LSI_PROP_RID_OPS, + POWER_SUPPLY_LSI_PROP_RID_DISABLE, + POWER_SUPPLY_LSI_PROP_GET_REV, +#if IS_ENABLED(CONFIG_MFD_S2MU106) || IS_ENABLED(CONFIG_MFD_S2MF301) || defined(CONFIG_BATTERY_GKI) + POWER_SUPPLY_LSI_PROP_MAX, +#endif +}; + +#ifdef CONFIG_IFCONN_NOTIFIER +#define MUIC_SEND_NOTI_ATTACH(dev) \ +{ \ + int ret; \ + struct ifconn_notifier_template template; \ + template.cable_type = dev; \ + ret = ifconn_notifier_notify( \ + IFCONN_NOTIFY_MUIC, \ + IFCONN_NOTIFY_MANAGER, \ + IFCONN_NOTIFY_ID_ATTACH, \ + IFCONN_NOTIFY_EVENT_ATTACH, \ + &template); \ + if (ret < 0) { \ + pr_err("%s: Fail to send noti\n", \ + __func__); \ + } \ +} + +#define MUIC_SEND_NOTI_ATTACH_ALL(dev) \ +{ \ + int ret; \ + ret = ifconn_notifier_notify( \ + IFCONN_NOTIFY_MUIC, \ + IFCONN_NOTIFY_ALL, \ + IFCONN_NOTIFY_ID_ATTACH, \ + dev, \ + IFCONN_NOTIFY_PARAM_DATA, \ + NULL); \ + if (ret < 0) { \ + pr_err("%s: Fail to send noti\n", \ + __func__); \ + } \ +} + +#define MUIC_SEND_NOTI_DETACH_ALL(dev) \ +{ \ + int ret; \ + ret = ifconn_notifier_notify( \ + IFCONN_NOTIFY_MUIC, \ + IFCONN_NOTIFY_ALL, \ + IFCONN_NOTIFY_ID_DETACH, \ + dev, \ + IFCONN_NOTIFY_PARAM_DATA, \ + NULL); \ + if (ret < 0) { \ + pr_err("%s: Fail to send noti\n", \ + __func__); \ + } \ +} + +#define MUIC_SEND_NOTI_TO_PDIC_ATTACH(dev) \ +{ \ + int ret; \ + struct ifconn_notifier_template template; \ + template.cable_type = dev; \ + ret = ifconn_notifier_notify( \ + IFCONN_NOTIFY_MUIC, \ + IFCONN_NOTIFY_PDIC, \ + IFCONN_NOTIFY_ID_ATTACH, \ + IFCONN_NOTIFY_EVENT_ATTACH, \ + IFCONN_NOTIFY_PARAM_DATA, \ + &template); \ + if (ret < 0) { \ + pr_err("%s: Fail to send noti\n", \ + __func__); \ + } \ +} + +#define MUIC_SEND_NOTI_TO_PDIC_DETACH(dev) \ +{ \ + int ret; \ + struct ifconn_notifier_template template; \ + template.cable_type = dev; \ + ret = ifconn_notifier_notify( \ + IFCONN_NOTIFY_MUIC, \ + IFCONN_NOTIFY_PDIC, \ + IFCONN_NOTIFY_ID_DETACH, \ + IFCONN_NOTIFY_EVENT_DETACH, \ + IFCONN_NOTIFY_PARAM_DATA, \ + &template); \ + if (ret < 0) { \ + pr_err("%s: Fail to send noti\n", \ + __func__); \ + } \ +} + +#define MUIC_SEND_NOTI_DETACH(dev) \ +{ \ + int ret; \ + struct ifconn_notifier_template template; \ + template.cable_type = dev; \ + ret = ifconn_notifier_notify( \ + IFCONN_NOTIFY_MUIC, \ + IFCONN_NOTIFY_MANAGER, \ + IFCONN_NOTIFY_ID_DETACH, \ + IFCONN_NOTIFY_EVENT_DETACH, \ + IFCONN_NOTIFY_PARAM_DATA, \ + &template); \ + if (ret < 0) { \ + pr_err("%s: Fail to send noti\n", \ + __func__); \ + } \ +} +#else +#define MUIC_SEND_NOTI_ATTACH(dev) \ + muic_notifier_attach_attached_dev(dev) +#define MUIC_SEND_NOTI_DETACH(dev) \ + muic_notifier_detach_attached_dev(dev) +#define MUIC_SEND_NOTI_TO_PDIC_ATTACH(dev) \ + muic_pdic_notifier_attach_attached_dev(dev) +#define MUIC_SEND_NOTI_TO_PDIC_DETACH(dev) \ + muic_pdic_notifier_detach_attached_dev(dev) +#endif + +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) +extern void muic_send_lcd_on_uevent(struct muic_platform_data *muic_pdata); +extern int muic_set_hiccup_mode(int on_off); +extern int muic_hv_charger_init(void); +#if IS_ENABLED(CONFIG_MUIC_POGO) +extern int muic_set_pogo_adc(int adc); +#endif +extern int muic_afc_get_voltage(void); +extern int muic_afc_set_voltage(int voltage); +extern int muic_afc_request_voltage(int cause, int voltage); +extern int muic_afc_request_cause_clear(void); +extern int muic_afc_get_request_cause(void); +extern bool muic_is_enable_afc_request(void); +extern int muic_hv_charger_disable(bool en); + +#else +static inline void muic_send_lcd_on_uevent(struct muic_platform_data *muic_pdata) + {return; } +static inline int muic_set_hiccup_mode(int on_off) {return 0; } +static inline int muic_hv_charger_init(void) {return 0; } +static inline int muic_afc_get_voltage(void) {return 0; } +#if IS_ENABLED(CONFIG_MUIC_POGO) +static inline int muic_set_pogo_adc(int adc) {return 0}; +#endif +static inline int muic_afc_set_voltage(int voltage) {return 0; } +static inline int muic_afc_request_voltage(int cause, int voltage); +static inline int muic_afc_request_cause_clear(void); +static inline int muic_afc_get_request_cause(void) {return 0;} +static inline bool muic_is_enable_afc_request(void) {return false;} +static inline int muic_hv_charger_disable(bool en) {return 0; } +#endif + +#endif /* __MUIC_H__ */ diff --git a/include/linux/muic/common/muic_kunit.h b/include/linux/muic/common/muic_kunit.h new file mode 100755 index 000000000000..64b641688061 --- /dev/null +++ b/include/linux/muic/common/muic_kunit.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __MUIC_KUNIT_H__ +#define __MUIC_KUNIT_H__ + +#if IS_ENABLED(CONFIG_SEC_KUNIT) +#include +#endif + +#ifndef __mockable +#define __mockable +#endif + +#ifndef __visible_for_testing +#define __visible_for_testing static +#endif + +#ifndef EXPORT_SYMBOL_KUNIT +#define EXPORT_SYMBOL_KUNIT(sym) /* nothing */ +#endif + +#endif /* __MUIC_KUNIT_H__ */ diff --git a/include/linux/muic/common/muic_notifier.h b/include/linux/muic/common/muic_notifier.h new file mode 100644 index 000000000000..1bcb6bf4dfce --- /dev/null +++ b/include/linux/muic/common/muic_notifier.h @@ -0,0 +1,112 @@ +/* + * include/linux/muic/common/muic_notifier.h + * + * header file supporting MUIC notifier call chain information + * + * Copyright (C) 2010 Samsung Electronics + * Seung-Jin Hahn + * + * 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 __MUIC_NOTIFIER_H__ +#define __MUIC_NOTIFIER_H__ + +#include +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) +#include +#endif + +/* MUIC notifier call chain command */ +typedef enum { + MUIC_NOTIFY_CMD_DETACH = 0, + MUIC_NOTIFY_CMD_ATTACH, + MUIC_NOTIFY_CMD_LOGICALLY_DETACH, + MUIC_NOTIFY_CMD_LOGICALLY_ATTACH, + MUIC_PDIC_NOTIFY_CMD_ATTACH, + MUIC_PDIC_NOTIFY_CMD_DETACH, + PDIC_MUIC_NOTIFY_CMD_JIG_ATTACH, + PDIC_MUIC_NOTIFY_CMD_JIG_DETACH, +} muic_notifier_cmd_t; + +/* MUIC notifier call sequence, + * largest priority number device will be called first. */ +typedef enum { + MUIC_NOTIFY_DEV_DOCK = 0, + MUIC_NOTIFY_DEV_MHL, + MUIC_NOTIFY_DEV_USB, + MUIC_NOTIFY_DEV_TSP, + MUIC_NOTIFY_DEV_CHARGER, + MUIC_NOTIFY_DEV_PDIC, + MUIC_NOTIFY_DEV_CPUIDLE, + MUIC_NOTIFY_DEV_CPUFREQ, +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + MUIC_NOTIFY_DEV_MANAGER, +#endif + MUIC_NOTIFY_DEV_CABLE_DATA, +} muic_notifier_device_t; + +struct muic_notifier_struct { + muic_attached_dev_t attached_dev; + muic_notifier_cmd_t cmd; +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + PD_NOTI_ATTACH_TYPEDEF cxt; +#if IS_ENABLED(CONFIG_MUIC_POGO) + PD_NOTI_ATTACH_TYPEDEF pogo_cxt; +#endif /* CONFIG_MUIC_POGO */ +#endif + struct blocking_notifier_head notifier_call_chain; +}; + +#define MUIC_NOTIFIER_BLOCK(name) \ + struct notifier_block (name) + +/* muic notifier init/notify function + * this function is for JUST MUIC device driver. + * DON'T use function anywhrer else!! + */ +extern struct device *switch_device; + +extern void muic_notifier_attach_attached_dev(muic_attached_dev_t new_dev); +extern void muic_notifier_detach_attached_dev(muic_attached_dev_t cur_dev); +extern void muic_pdic_notifier_attach_attached_dev(muic_attached_dev_t new_dev); +extern void muic_pdic_notifier_detach_attached_dev(muic_attached_dev_t new_dev); +extern void muic_notifier_logically_attach_attached_dev(muic_attached_dev_t new_dev); +extern void muic_notifier_logically_detach_attached_dev(muic_attached_dev_t cur_dev); +#if IS_ENABLED(CONFIG_VIRTUAL_MUIC) +extern void vt_muic_notifier_attach_attached_dev(muic_attached_dev_t new_dev); +extern void vt_muic_notifier_detach_attached_dev(muic_attached_dev_t cur_dev); +#endif + +#if IS_ENABLED(CONFIG_PDIC_SLSI_NON_MCU) +extern int muic_pdic_notifier_register(struct notifier_block *nb, + notifier_fn_t notifier, muic_notifier_device_t listener); +extern int muic_pdic_notifier_unregister(struct notifier_block *nb); +#endif +/* muic notifier register/unregister API + * for used any where want to receive muic attached device attach/detach. */ +extern int muic_notifier_register(struct notifier_block *nb, + notifier_fn_t notifier, muic_notifier_device_t listener); +extern int muic_notifier_unregister(struct notifier_block *nb); + +/* Choose a proper noti. interface for a test */ +extern void muic_notifier_set_new_noti(bool flag); + +#if IS_ENABLED(CONFIG_MUIC_POGO) +extern void muic_pogo_notifier_attach_attached_dev(muic_attached_dev_t new_dev); +extern void muic_pogo_notifier_detach_attached_dev(muic_attached_dev_t cur_dev); +#endif /* CONFIG_MUIC_POGO */ + +#endif /* __MUIC_NOTIFIER_H__ */ diff --git a/include/linux/muic/common/muic_param.h b/include/linux/muic/common/muic_param.h new file mode 100755 index 000000000000..ff6a839dc3c8 --- /dev/null +++ b/include/linux/muic/common/muic_param.h @@ -0,0 +1,35 @@ +/* + * + * Copyright (C) 2021 Samsung Electronics + * + * + * 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, see . + * + */ + +#ifndef __LINUX_MUIC_PARAM_H__ +#define __LINUX_MUIC_PARAM_H__ + +#if IS_ENABLED(CONFIG_MUIC_NOTIFIER) +extern int get_switch_sel(void); +extern int get_uart_sel(void); +extern int get_afc_mode(void); +extern int get_pdic_info(void); +#else +static inline int get_switch_sel(void) {return 0; } +static inline int get_uart_sel(void) {return 0; } +static inline int get_afc_mode(void) {return 0; } +static inline int get_pdic_info(void) {return 1; } +#endif +#endif /* __LINUX_MUIC_PARAM_H__ */ diff --git a/include/linux/muic/common/muic_sysfs.h b/include/linux/muic/common/muic_sysfs.h new file mode 100755 index 000000000000..b3a42311af60 --- /dev/null +++ b/include/linux/muic/common/muic_sysfs.h @@ -0,0 +1,15 @@ +#ifndef MUIC_SYSFS_H +#define MUIC_SYSFS_H + +#include + +#ifdef CONFIG_MUIC_COMMON_SYSFS +extern int muic_sysfs_init(struct muic_platform_data *pdata); +extern void muic_sysfs_deinit(struct muic_platform_data *pdata); +#else +static inline int muic_sysfs_init(struct muic_platform_data *pdata) + {return 0; } +static inline void muic_sysfs_deinit(struct muic_platform_data *pdata) {} +#endif + +#endif /* MUIC_SYSFS_H */ diff --git a/include/linux/netfilter/xt_domainfilter.h b/include/linux/netfilter/xt_domainfilter.h new file mode 100644 index 000000000000..2257ee27874c --- /dev/null +++ b/include/linux/netfilter/xt_domainfilter.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. + * + * Domain Filter Module:Implementation. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _XT_DOMAINFILTER_MATCH_H +#define _XT_DOMAINFILTER_MATCH_H + +#include + +enum { + XT_DOMAINFILTER_WHITE = 1 << 0, + XT_DOMAINFILTER_BLACK = 1 << 1, + + XT_DOMAINFILTER_NAME_LEN = 256, // lenght of a domain name +}; + +struct xt_domainfilter_match_info { + char domain_name[XT_DOMAINFILTER_NAME_LEN]; + __u8 flags; +}; + +#endif //_XT_DOMAINFILTER_MATCH_H diff --git a/include/linux/olog.pb.h b/include/linux/olog.pb.h new file mode 100644 index 000000000000..53ddbaca357d --- /dev/null +++ b/include/linux/olog.pb.h @@ -0,0 +1,88 @@ +// Generated by the protocol buffer compiler for perflog!! DO NOT EDIT! +#ifndef _OLOG_PROTOCOL_BUFFER_H_ +#define _OLOG_PROTOCOL_BUFFER_H_ + +//EnumGenerator::GenerateDefinition in perflog_enum.cc +enum OlogTestEnum_Type { + PERFLOG_DEF = 0, + PERFLOG_LOG = 1, + PERFLOG_EVT = 2, + PERFLOG_WRN = 3, + PERFLOG_CRI = 4 +}; +#if defined(KPERFMON_KERNEL) +int OlogTestEnum_Type_maxnum = 5; +char * OlogTestEnum_Type_strings[5] = { + "DEF", + "LOG", + "EVT", + "WRN", + "CRI" +}; +#endif //KPERFMON_KERNEL +//EnumGenerator::GenerateDefinition in perflog_enum.cc +enum OlogTestEnum_ID { + PERFLOG_UNKNOWN = 0, + PERFLOG_LCDV = 2, + PERFLOG_ARGOS = 3, + PERFLOG_APPLAUNCH = 4, + PERFLOG_LOADAPK = 5, + PERFLOG_MAINLOOPER = 6, + PERFLOG_EXCESSIVECPUUSAGE = 7, + PERFLOG_ACTIVITYSLOW = 8, + PERFLOG_BROADCAST = 9, + PERFLOG_STORE = 10, + PERFLOG_CPUTOP = 11, + PERFLOG_LCD = 12, + PERFLOG_CPU = 13, + PERFLOG_LOCKCONTENTION = 14, + PERFLOG_CPUFREQ = 15, + PERFLOG_MEMPRESSURE = 16, + PERFLOG_INPUTD = 17, + PERFLOG_AMPSS = 18, + PERFLOG_SERVICEMANAGERSLOW = 19, + PERFLOG_IPCSTARVE = 20, + PERFLOG_SCREENSHOT = 21, + PERFLOG_MUTEX = 22, + PERFLOG_SYSTEMSERVER = 23, + PERFLOG_PERFETTOLOGGINGENABLED = 24, + PERFLOG_BIGDATA = 25, + PERFLOG_PSI = 26, + PERFLOG_JANK = 27 +}; +#if defined(KPERFMON_KERNEL) +int OlogTestEnum_ID_maxnum = 28; +char * OlogTestEnum_ID_strings[28] = { + "UNKNOWN", + " ", + "LCDV", + "ARGOS", + "APPLAUNCH", + "LOADAPK", + "MAINLOOPER", + "EXCESSIVECPUUSAGE", + "ACTIVITYSLOW", + "BROADCAST", + "STORE", + "CPUTOP", + "LCD", + "CPU", + "LOCKCONTENTION", + "CPUFREQ", + "MEMPRESSURE", + "INPUTD", + "AMPSS", + "SERVICEMANAGERSLOW", + "IPCSTARVE", + "SCREENSHOT", + "MUTEX", + "SYSTEMSERVER", + "PERFETTOLOGGINGENABLED", + "BIGDATA", + "PSI", + "JANK" +}; +#endif //KPERFMON_KERNEL + +#endif //_OLOG_PROTOCOL_BUFFER_H_ + diff --git a/include/linux/ologk.h b/include/linux/ologk.h new file mode 100644 index 000000000000..0af51d89eb10 --- /dev/null +++ b/include/linux/ologk.h @@ -0,0 +1,15 @@ +#ifndef _OLOG_KERNEL_H_ +#define _OLOG_KERNEL_H_ + +#include +#include "olog.pb.h" + +#define OLOG_CPU_FREQ_FILTER 1500000 +#define PERFLOG_MUTEX_THRESHOLD 20 + +#define ologk(...) _perflog(PERFLOG_LOG, PERFLOG_UNKNOWN, __VA_ARGS__) +#define perflog(...) _perflog(PERFLOG_LOG, __VA_ARGS__) +extern void _perflog(int type, int logid, const char *fmt, ...); +extern void perflog_evt(int logid, int arg1); + +#endif diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index 92d866352f35..19e7b77ef0cf 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h @@ -1719,9 +1719,9 @@ int perf_event_exit_cpu(unsigned int cpu); #define perf_event_exit_cpu NULL #endif -extern void arch_perf_update_userpage(struct perf_event *event, - struct perf_event_mmap_page *userpg, - u64 now); +extern void __weak arch_perf_update_userpage(struct perf_event *event, + struct perf_event_mmap_page *userpg, + u64 now); #ifdef CONFIG_MMU extern __weak u64 arch_perf_get_page_size(struct mm_struct *mm, unsigned long addr); diff --git a/include/linux/platform_data/sec_thermistor.h b/include/linux/platform_data/sec_thermistor.h new file mode 100644 index 000000000000..8d0faa3383ea --- /dev/null +++ b/include/linux/platform_data/sec_thermistor.h @@ -0,0 +1,39 @@ +/* + * sec_thermistor.h - SEC Thermistor + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * Minsung Kim + * + * 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. + */ +#ifndef __LINUX_SEC_THERMISTOR_H +#define __LINUX_SEC_THERMISTOR_H __FILE__ + +/** + * struct sec_therm_adc_table - adc to temperature table for sec thermistor + * driver + * @adc: adc value + * @temperature: temperature(Celsius) * 10 + */ +struct sec_therm_adc_table { + int adc; + int temperature; +}; + +/** + * struct sec_bat_plaform_data - init data for sec batter driver + * @adc_channel: adc channel that connected to thermistor + * @adc_table: array of adc to temperature data + * @adc_arr_size: size of adc_table + */ +struct sec_therm_platform_data { + unsigned int adc_channel; + unsigned int adc_arr_size; + bool iio_processed; + struct sec_therm_adc_table *adc_table; +}; + +#endif /* __LINUX_SEC_THERMISTOR_H */ diff --git a/include/linux/platform_device_mock.h b/include/linux/platform_device_mock.h new file mode 100644 index 000000000000..9e110b19f730 --- /dev/null +++ b/include/linux/platform_device_mock.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Fake platform device API for unit testing platform drivers. + * + * Copyright (C) 2018, Google LLC. + * Author: Brendan Higgins + */ + +#include +#include + +static inline struct platform_driver *platform_driver_find(const char *name) +{ + struct device_driver *driver; + + driver = driver_find(name, &platform_bus_type); + if (!driver) + return NULL; + + return to_platform_driver(driver); +} + +/** + * of_fake_node() + * @test: the test to associate node with + * @name: name of the node + * + * The &struct device_node returned is allocated as a root node with the given + * name and otherwise behaves as a real &struct device_node. + * + * Returns: the faked &struct device_node + */ +struct device_node *of_fake_node(struct kunit *test, const char *name); + +/** + * of_fake_probe_platform() + * @test: the test to associate the fake platform device with + * @driver: driver to probe + * @node_name: name of the device node created + * + * Creates a &struct platform_device and an associated &struct device_node, + * probes the provided &struct platform_driver with the &struct platform_device. + * + * Returns: the &struct platform_device that was created + */ +struct platform_device * +of_fake_probe_platform(struct kunit *test, + struct platform_driver *driver, + const char *node_name); + +/** + * of_fake_probe_platform_by_name() + * @test: the test to associate the fake platform device with + * @driver_name: name of the driver to probe + * @node_name: name of the device node created + * + * Same as of_fake_probe_platform() but looks up the &struct platform_driver by + * the provided name. + * + * Returns: the &struct platform_device that was created + */ +struct platform_device *of_fake_probe_platform_by_name(struct kunit *test, + const char *driver_name, + const char *node_name); diff --git a/include/linux/qcom_dma_heap_dt_constants.h b/include/linux/qcom_dma_heap_dt_constants.h index 088d88a9e009..4bfc6f8054fc 100644 --- a/include/linux/qcom_dma_heap_dt_constants.h +++ b/include/linux/qcom_dma_heap_dt_constants.h @@ -10,5 +10,6 @@ #define HEAP_TYPE_CARVEOUT 1 #define HEAP_TYPE_CMA 2 #define HEAP_TYPE_TUI_CARVEOUT 3 +#define HEAP_TYPE_RBIN 4 #endif /* _QCOM_DMA_HEAP_DT_CONSTATS_H */ diff --git a/include/linux/regulator/pmic_class.h b/include/linux/regulator/pmic_class.h new file mode 100644 index 000000000000..576cf1137e47 --- /dev/null +++ b/include/linux/regulator/pmic_class.h @@ -0,0 +1,19 @@ +#ifndef PMIC_CLASS_H +#define PMIC_CLASS_H + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) +extern struct device *pmic_device_create(void *drvdata, const char *fmt); +extern void pmic_device_destroy(dev_t devt); + +struct pmic_device_attribute { + struct device_attribute dev_attr; +}; + +#define PMIC_ATTR(_name, _mode, _show, _store) \ + { .dev_attr = __ATTR(_name, _mode, _show, _store) } +#else +#define pmic_device_create(a, b) (-1) +#define pmic_device_destroy(a) do { } while (0) +#endif + +#endif /* PMIC_CLASS_H */ diff --git a/include/linux/regulator/s2dos05.h b/include/linux/regulator/s2dos05.h new file mode 100644 index 000000000000..805bacd1da3c --- /dev/null +++ b/include/linux/regulator/s2dos05.h @@ -0,0 +1,225 @@ +/* + * s2dos05.h + * + * Copyright (c) 2016 Samsung Electronics Co., Ltd + * http://www.samsung.com + * + * 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. + * + */ + +#ifndef __LINUX_MFD_S2DOS05_H +#define __LINUX_MFD_S2DOS05_H +#include +#include + +#define MFD_DEV_NAME "s2dos05" + +/** + * sec_regulator_data - regulator data + * @id: regulator id + * @initdata: regulator init data (contraints, supplies, ...) + */ + +struct s2dos05_dev { + struct device *dev; + struct i2c_client *i2c; /* 0xB2; PMIC, Flash LED */ + struct mutex i2c_lock; + + int type; + u8 rev_num; /* pmic Rev */ + bool wakeup; + int dp_pmic_irq; + int adc_mode; + int adc_sync_mode; + u8 adc_en_val; + + struct s2dos05_platform_data *pdata; +#if IS_ENABLED(CONFIG_DRV_SAMSUNG_PMIC) + struct device *powermeter_dev; +#endif + struct adc_info *adc_meter; +#if IS_ENABLED(CONFIG_SEC_PM) + struct device *sec_disp_pmic_dev; + bool is_sm3080; +#endif /* CONFIG_SEC_PM */ +}; + +struct s2dos05_regulator_data { + int id; + struct regulator_init_data *initdata; + struct device_node *reg_node; +}; + +struct s2dos05_platform_data { + bool wakeup; + int num_regulators; + int num_rdata; + struct s2dos05_regulator_data *regulators; + int device_type; + int dp_pmic_irq; + + /* adc_mode + * 0 : not use + * 1 : current meter + * 2 : power meter + */ + int adc_mode; + /* 1 : sync mode, 2 : async mode */ + int adc_sync_mode; +#if IS_ENABLED(CONFIG_SEC_PM) + const char *sec_disp_pmic_name; + + /* OCL_ELVSS + * 0: 1.3A + * 1: 1.5A + * 2: 1.7A (default) + * 3: 1.9A + */ + int ocl_elvss; + + unsigned int enable_fd_delay_ms; +#endif /* CONFIG_SEC_PM */ +}; + +struct s2dos05 { + struct regmap *regmap; +}; + +/* S2DOS05 registers */ +/* Slave Addr : 0xC0 */ +enum S2DOS05_reg { + S2DOS05_REG_DEV_ID, + S2DOS05_REG_TOPSYS_STAT, + S2DOS05_REG_STAT, + S2DOS05_REG_EN, + S2DOS05_REG_LDO1_CFG, + S2DOS05_REG_LDO2_CFG, + S2DOS05_REG_LDO3_CFG, + S2DOS05_REG_LDO4_CFG, + S2DOS05_REG_BUCK_CFG, + S2DOS05_REG_BUCK_VOUT, + S2DOS05_REG_IRQ_MASK = 0x0D, +#if IS_ENABLED(CONFIG_SEC_PM) + S2DOS05_REG_SSD_TSD = 0x0E, + S2DOS05_REG_UVLO_FD = 0x0F, + S2DOS05_REG_OCL = 0x10, +#endif /* CONFIG_SEC_PM */ + S2DOS05_REG_IRQ = 0x11, +#if IS_ENABLED(CONFIG_SEC_PM) + S2DOS05_REG_DEVICE_ID_PGM = 0x61, +#endif /* CONFIG_SEC_PM */ +}; + +/* S2DOS05 regulator ids */ +enum S2DOS05_regulators { + S2DOS05_LDO1, + S2DOS05_LDO2, + S2DOS05_LDO3, + S2DOS05_LDO4, + S2DOS05_BUCK1, +#if IS_ENABLED(CONFIG_SEC_PM) + S2DOS05_ELVSS_SSD, + S2DOS05_ELVSS_FD, +#endif /* CONFIG_SEC_PM */ + S2DOS05_REG_MAX, +}; + +#define S2DOS05_IRQ_PWRMT_MASK (1 << 5) +#define S2DOS05_IRQ_TSD_MASK (1 << 4) +#define S2DOS05_IRQ_SSD_MASK (1 << 3) +#define S2DOS05_IRQ_SCP_MASK (1 << 2) +#define S2DOS05_IRQ_UVLO_MASK (1 << 1) +#define S2DOS05_IRQ_OCD_MASK (1 << 0) + +#define S2DOS05_BUCK_MIN1 506250 +#define S2DOS05_LDO_MIN1 1500000 +#define S2DOS05_LDO_MIN2 2700000 +#define S2DOS05_BUCK_STEP1 6250 +#define S2DOS05_LDO_STEP1 25000 +#define S2DOS05_LDO_VSEL_MASK 0x7F +#define S2DOS05_BUCK_VSEL_MASK 0xFF +#if IS_ENABLED(CONFIG_SEC_PM) +#define S2DOS05_ELVSS_SEL_SSD_MASK (3 << 5) +#define S2DOS05_ELVSS_SSD_EN_MASK (3 << 3) +#endif /* CONFIG_SEC_PM */ + +#define S2DOS05_ENABLE_MASK_L1 (1 << 0) +#define S2DOS05_ENABLE_MASK_L2 (1 << 1) +#define S2DOS05_ENABLE_MASK_L3 (1 << 2) +#define S2DOS05_ENABLE_MASK_L4 (1 << 3) +#define S2DOS05_ENABLE_MASK_B1 (1 << 4) + +#define S2DOS05_OCL_ELVSS_MASK (3 << 0) + +/* hidden for SM3080 only */ +#define SM3080_AVDD 5 +#define SM3080_ELVSS 6 +#define SM3080_ELVDD 7 +#define SM3080_ENABLE_MASK_AVDD (1 << SM3080_AVDD) +#define SM3080_ENABLE_MASK_ELVSS (1 << SM3080_ELVSS) +#define SM3080_ENABLE_MASK_ELVDD (1 << SM3080_ELVDD) + +#define S2DOS05_RAMP_DELAY 12000 + +#define S2DOS05_ENABLE_TIME_LDO 50 +#define S2DOS05_ENABLE_TIME_BUCK 350 + +#define S2DOS05_ENABLE_SHIFT 0x06 +#define S2DOS05_LDO_N_VOLTAGES (S2DOS05_LDO_VSEL_MASK + 1) +#define S2DOS05_BUCK_N_VOLTAGES (S2DOS05_BUCK_VSEL_MASK + 1) + +#define S2DOS05_PMIC_EN_SHIFT 6 +#define S2DOS05_REGULATOR_MAX (S2DOS05_REG_MAX) + +/* ----------power meter ----------*/ +#define S2DOS05_REG_PWRMT_CTRL1 0x0A +#define S2DOS05_REG_PWRMT_CTRL2 0x0B +#define S2DOS05_REG_PWRMT_DATA 0x0C +#define S2DOS05_REG_IRQ_MASK 0x0D + +#define CURRENT_ELVDD 2450 +#define CURRENT_ELVSS 2450 +#define CURRENT_AVDD 612 +#define CURRENT_BUCK 1220 +#define CURRENT_L1 2000 +#define CURRENT_L2 2000 +#define CURRENT_L3 2000 +#define CURRENT_L4 2000 + +#define POWER_ELVDD 24500 +#define POWER_ELVSS 24500 +#define POWER_AVDD 3060 +#define POWER_BUCK 1525 +#define POWER_L1 5000 +#define POWER_L2 5000 +#define POWER_L3 5000 +#define POWER_L4 5000 + +#define ADC_EN_MASK 0x80 +#define ADC_ASYNCRD_MASK 0x80 +#define ADC_PTR_MASK 0x0F +#define ADC_PGEN_MASK 0x30 +#define CURRENT_MODE 0x00 +#define POWER_MODE 0x10 +#define RAWCURRENT_MODE 0x20 +#define FAULT_STATUS1 0x67 /* S2DOS05 SCP */ +#define FAULT_STATUS2 0x68 +#define INT_STATUS1 0xB8 /* SM3080 SCP */ +#define SMPNUM_MASK 0x0F + +#define S2DOS05_MAX_ADC_CHANNEL 8 + +extern void s2dos05_powermeter_init(struct s2dos05_dev *s2dos05); +extern void s2dos05_powermeter_deinit(struct s2dos05_dev *s2dos05); + +/* S2DOS05 shared i2c API function */ +extern int s2dos05_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest); +extern int s2dos05_write_reg(struct i2c_client *i2c, u8 reg, u8 value); +extern int s2dos05_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask); + + +#endif /* __LINUX_MFD_S2DOS05_H */ diff --git a/include/linux/regulator/s2dos07.h b/include/linux/regulator/s2dos07.h new file mode 100644 index 000000000000..622e45214a4a --- /dev/null +++ b/include/linux/regulator/s2dos07.h @@ -0,0 +1,129 @@ +/* + * s2dos07.h + * + * Copyright (c) 2023 Samsung Electronics Co., Ltd + * http://www.samsung.com + * + * 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. + * + */ + +#ifndef __LINUX_MFD_S2DOS07_H +#define __LINUX_MFD_S2DOS07_H +#include +#include + +#define MFD_DEV_NAME "s2dos07" + +/** + * sec_regulator_data - regulator data + * @id: regulator id + * @initdata: regulator init data (contraints, supplies, ...) + */ + +struct s2dos07_dev { + struct device *dev; + struct i2c_client *i2c; + struct mutex i2c_lock; + + int type; + u8 rev_num; /* pmic Rev */ + bool wakeup; + bool vgl_bypass_n_fd; + int dp_pmic_irq; + + struct s2dos07_platform_data *pdata; +#if IS_ENABLED(CONFIG_SEC_PM) + struct device *sec_disp_pmic_dev; +#endif /* CONFIG_SEC_PM */ +}; + +struct s2dos07_regulator_data { + int id; + struct regulator_init_data *initdata; + struct device_node *reg_node; +}; + +struct s2dos07_platform_data { + bool wakeup; + bool vgl_bypass_n_fd; + int num_regulators; + int num_rdata; + struct s2dos07_regulator_data *regulators; + int device_type; + int dp_pmic_irq; +}; + +struct s2dos07 { + struct regmap *regmap; +}; + +/* S2DOS07 registers */ +/* Slave Addr : 0xC0 */ +enum S2DOS07_reg { + S2DOS07_REG_STAT = 0x02, + S2DOS07_REG_BUCK_VOUT = 0x03, + S2DOS07_REG_BUCK_EN = 0x04, + S2DOS07_REG_IRQ_MASK = 0x06, + S2DOS07_REG_IRQ = 0x07, + S2DOS07_REG_UVP_STATUS, + S2DOS07_REG_OVP_STATUS, + S2DOS07_REG_OVP_MODE, + + S2DOS07_REG_0D_AUTHORITY = 0x0C, + S2DOS07_REG_0D_CONTROL = 0x0D, + + S2DOS07_REG_EN_CTRL = 0x20, + S2DOS07_REG_SS_FD_CTRL = 0x21, + S2DOS07_REG_VGX_EN_CTRL = 0x26, +}; + +/* S2DOS07 regulator ids */ +enum S2DOS07_regulators { + S2DOS07_BUCK1, + +#if IS_ENABLED(CONFIG_SEC_PM) + S2DOS07_ELVXX, +#endif /* CONFIG_SEC_PM */ + + S2DOS07_REG_MAX, +}; + +#define S2DOS07_IRQ_UVP_MASK (1 << 5) +#define S2DOS07_IRQ_OVP_MASK (1 << 4) +#define S2DOS07_IRQ_PRETSD_MASK (1 << 3) +#define S2DOS07_IRQ_TSD_MASK (1 << 2) +#define S2DOS07_IRQ_SSD_MASK (1 << 1) +#define S2DOS07_IRQ_UVLO_MASK (1 << 0) + +#define S2DOS07_BUCK_MIN1 512500 +#define S2DOS07_BUCK_STEP1 12500 +#define S2DOS07_BUCK_VSEL_MASK 0x7F + +#define S2DOS07_ENABLE_MASK_B1 (1 << 0) + +#define S2DOS07_RAMP_DELAY 4000 + +#define S2DOS07_ENABLE_TIME_BUCK 350 + +#define S2DOS07_BUCK_N_VOLTAGES (S2DOS07_BUCK_VSEL_MASK + 1) + +#define S2DOS07_REGULATOR_MAX (S2DOS07_REG_MAX) + +/* S2DOS07 shared i2c API function */ +extern int s2dos07_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest); +extern int s2dos07_write_reg(struct i2c_client *i2c, u8 reg, u8 value); +extern int s2dos07_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask); + +#if IS_ENABLED(CONFIG_SEC_KUNIT) +extern int s2m_set_voltage_time_sel(struct regulator_dev *rdev, + unsigned int old_selector, + unsigned int new_selector); + +extern struct regulator_desc regulators[]; +#endif + +#endif /* __LINUX_MFD_S2DOS07_H */ diff --git a/include/linux/regulator/s2mpb03.h b/include/linux/regulator/s2mpb03.h new file mode 100644 index 000000000000..26157100345d --- /dev/null +++ b/include/linux/regulator/s2mpb03.h @@ -0,0 +1,103 @@ +/* + * s2mpb03.h + * + * Copyright (c) 2016 Samsung Electronics Co., Ltd + * http://www.samsung.com + * + * 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. + * + */ + +#ifndef __LINUX_MFD_S2MPB03_H +#define __LINUX_MFD_S2MPB03_H +#include +#include + +#define MFD_DEV_NAME "s2mpb03" + +/** + * sec_regulator_data - regulator data + * @id: regulator id + * @initdata: regulator init data (contraints, supplies, ...) + */ + +struct s2mpb03_dev { + struct device *dev; + struct i2c_client *i2c; + struct mutex i2c_lock; + + int type; + u8 rev_num; /* pmic Rev */ + bool wakeup; + + struct s2mpb03_platform_data *pdata; +}; + +struct s2mpb03_regulator_data { + int id; + struct regulator_init_data *initdata; + struct device_node *reg_node; +}; + +struct s2mpb03_platform_data { + bool wakeup; + bool need_recovery; + int num_regulators; + int num_rdata; + struct s2mpb03_regulator_data *regulators; + int device_type; +}; + +struct s2mpb03 { + struct regmap *regmap; +}; + +/* S2MPB03 registers */ +/* Slave Addr : 0xAC */ +enum S2MPB03_reg { + S2MPB03_REG_PMIC_ID, + S2MPB03_REG_STATUS, + S2MPB03_REG_CTRL, + S2MPB03_REG_LDO1_CTRL, + S2MPB03_REG_LDO2_CTRL, + S2MPB03_REG_LDO3_CTRL, + S2MPB03_REG_LDO4_CTRL, + S2MPB03_REG_LDO5_CTRL, + S2MPB03_REG_LDO6_CTRL, + S2MPB03_REG_LDO7_CTRL, + S2MPB03_REG_LDO_SLEW1, + S2MPB03_REG_LDO_SLEW2, +}; + +/* S2MPB03 regulator ids */ +enum S2MPB03_regulators { + S2MPB03_LDO1, + S2MPB03_LDO2, + S2MPB03_LDO3, + S2MPB03_LDO4, + S2MPB03_LDO5, + S2MPB03_LDO6, + S2MPB03_LDO7, + S2MPB03_REG_MAX, +}; + +#define S2MPB03_LDO_MIN1 700000 +#define S2MPB03_LDO_MIN2 1800000 +#define S2MPB03_LDO_STEP1 25000 +#define S2MPB03_LDO_STEP2 12500 +#define S2MPB03_LDO_VSEL_MASK 0x3F +#define S2MPB03_LDO_ENABLE_MASK 0x80 + +#define S2MPB03_RAMP_DELAY 12000 + +#define S2MPB03_ENABLE_TIME_LDO 150 + +#define S2MPB03_ENABLE_SHIFT 0x07 +#define S2MPB03_LDO_N_VOLTAGES (S2MPB03_LDO_VSEL_MASK + 1) + +#define S2MPB03_REGULATOR_MAX (S2MPB03_REG_MAX) + +#endif /* __LINUX_MFD_S2MPB03_H */ diff --git a/include/linux/remoteproc.h b/include/linux/remoteproc.h index fe8978eb69f1..c91dcf097084 100644 --- a/include/linux/remoteproc.h +++ b/include/linux/remoteproc.h @@ -585,6 +585,11 @@ struct rproc { u16 elf_machine; struct cdev cdev; bool cdev_put_on_release; +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) + bool prev_recovery_disabled; + bool fssr; + bool fssr_dump; +#endif DECLARE_BITMAP(features, RPROC_MAX_FEATURES); }; diff --git a/include/linux/remoteproc/qcom_rproc.h b/include/linux/remoteproc/qcom_rproc.h index c598cf32250f..cdc409338db6 100644 --- a/include/linux/remoteproc/qcom_rproc.h +++ b/include/linux/remoteproc/qcom_rproc.h @@ -9,7 +9,9 @@ #include struct notifier_block; struct rproc; - +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) +struct device_node; +#endif /** * enum qcom_ssr_notify_type - Startup/Shutdown events related to a remoteproc * processor. @@ -75,4 +77,8 @@ static inline int rproc_set_state(struct rproc *rproc, bool state) } #endif +#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) +int adsp_init_subsensor_regulator(struct rproc *rproc, struct device_node *sub_sns_reg_np); +#endif + #endif diff --git a/include/linux/rkp.h b/include/linux/rkp.h new file mode 100644 index 000000000000..1c8ce903611d --- /dev/null +++ b/include/linux/rkp.h @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __RKP_H__ +#define __RKP_H__ + +#ifndef __ASSEMBLY__ +#include +#include +#include +#include + +#ifdef CONFIG_RKP_TEST +#define RKP_INIT_MAGIC 0x5afe0002 +#else +#define RKP_INIT_MAGIC 0x5afe0001 +#endif + +#define __rkp_ro __section(".rkp_ro") + +enum __RKP_CMD_ID { + RKP_START = 0x00, + RKP_DEFERRED_START = 0x01, + /* RKP robuffer cmds*/ + RKP_GET_RO_INFO = 0x2, + RKP_CHG_RO = 0x03, + RKP_CHG_RW = 0x04, + RKP_PGD_RO = 0x05, + RKP_PGD_RW = 0x06, + RKP_ROBUFFER_ALLOC = 0x07, + RKP_ROBUFFER_FREE = 0x08, + /* module, binary load */ + RKP_DYNAMIC_LOAD = 0x09, + RKP_MODULE_LOAD = 0x0A, + RKP_BPF_LOAD = 0x0B, + /* Log */ + RKP_LOG = 0x0C, +#ifdef CONFIG_RKP_TEST + RKP_TEST_INIT = 0x0D, + RKP_TEST_GET_PAR = 0x0E, + RKP_TEST_EXIT = 0x0F, + RKP_TEST_TEXT_VALID = 0x12, +#endif + RKP_KPROBE_PAGE = 0x11, + RKP_GET_GUEST_LINEAR_MEM = 0x13, + RKP_FREE_GUEST_LINEAR_MEM = 0x14, +}; + +#define RKP_DYN_COMMAND_BREAKDOWN 0x00 +#define RKP_DYN_COMMAND_INS 0x01 +#define RKP_DYN_COMMAND_RM 0x10 + +#define RKP_DYN_FIMC 0x02 +#define RKP_DYN_FIMC_COMBINED 0x03 + +#define RKP_MODULE_PXN_CLEAR 0x1 +#define RKP_MODULE_PXN_SET 0x2 + +struct rkp_init { //copy from uh (app/rkp/rkp.h) + u32 magic; + u64 vmalloc_start; + u64 vmalloc_end; + u64 init_mm_pgd; + u64 id_map_pgd; + u64 zero_pg_addr; + u64 rkp_pgt_bitmap; + u64 rkp_dbl_bitmap; + u32 rkp_bitmap_size; + u32 no_fimc_verify; + u64 fimc_phys_addr; + u64 _text; + u64 _etext; + u64 extra_memory_addr; + u32 extra_memory_size; + u64 physmap_addr; //not used. what is this for? + u64 _srodata; + u64 _erodata; + u32 large_memory; + u64 tramp_pgd; + u64 tramp_valias; +}; + +struct rkp_dynamic_load { + u32 type; + u64 binary_base; + u64 binary_size; + u64 code_base1; + u64 code_size1; + u64 code_base2; + u64 code_size2; +}; +struct module_info { + u64 base_va; + u64 vm_size; + u64 core_base_va; + u64 core_text_size; + u64 core_ro_size; + u64 init_base_va; + u64 init_text_size; +}; + +extern bool rkp_started; + +extern void __init rkp_init(void); +extern void rkp_deferred_init(void); +extern void rkp_robuffer_init(void); + +extern inline phys_addr_t rkp_ro_alloc_phys(int shift); +extern inline phys_addr_t rkp_ro_alloc_phys_for_text(void); +extern inline void *rkp_ro_alloc(void); +extern inline void rkp_ro_free(void *free_addr); +extern inline bool is_rkp_ro_buffer(u64 addr); + +void *module_alloc_by_rkp(unsigned int core_layout_size, unsigned int core_text_size); + +#endif //__ASSEMBLY__ +#endif //__RKP_H__ + diff --git a/include/linux/samsung/bsp/qcom/sec_qc_param.h b/include/linux/samsung/bsp/qcom/sec_qc_param.h new file mode 100644 index 000000000000..da903d968d8d --- /dev/null +++ b/include/linux/samsung/bsp/qcom/sec_qc_param.h @@ -0,0 +1,49 @@ +#ifndef __SEC_QC_PARAM_H__ +#define __SEC_QC_PARAM_H__ + +#define SAPA_KPARAM_MAGIC 0x41504153 +#define FMMLOCK_MAGIC_NUM 0x464D4F4E + +#define CP_MEM_RESERVE_OFF 0x00000000 +#define CP_MEM_RESERVE_ON_1 0x00004350 +#define CP_MEM_RESERVE_ON_2 0x00004D42 + +enum sec_qc_param_index { + param_index_debuglevel, + param_index_uartsel, + param_rory_control, + param_index_product_device, + param_cp_debuglevel, + param_index_sapa, + param_index_normal_poweroff, + param_index_wireless_ic, + param_index_wireless_charging_mode, + param_index_afc_disable, + param_index_cp_reserved_mem, + param_index_api_gpio_test, + param_index_api_gpio_test_result, + param_index_reboot_recovery_cause, + param_index_user_partition_flashed, + param_index_force_upload_flag, + param_index_cp_reserved_mem_backup, + param_index_FMM_lock, + param_index_dump_sink, + param_index_fiemap_update, + param_index_fiemap_result, + param_index_window_color, + param_index_VrrStatus, + param_index_pd_hv_disable, + param_vib_le_est, + /* */ + param_num_of_param_index, +}; + +#if IS_ENABLED(CONFIG_SEC_QC_PARAM) +extern ssize_t sec_qc_param_read_raw(void *buf, size_t len, loff_t pos); +extern ssize_t sec_qc_param_write_raw(const void *buf, size_t len, loff_t pos); +#else +static inline ssize_t sec_qc_param_read_raw(void *buf, size_t len, loff_t pos) { return -ENODEV; } +static inline ssize_t sec_qc_param_write_raw(const void *buf, size_t len, loff_t pos) { return -ENODEV; } +#endif + +#endif /* __SEC_QC_PARAM_H__ */ diff --git a/include/linux/samsung/bsp/sec_class.h b/include/linux/samsung/bsp/sec_class.h new file mode 100644 index 000000000000..72ce67d99826 --- /dev/null +++ b/include/linux/samsung/bsp/sec_class.h @@ -0,0 +1,14 @@ +#ifndef __SEC_CLASS_H__ +#define __SEC_CLASS_H__ + +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) +extern struct device *sec_dev_get_by_name(const char *name); +extern void sec_device_destroy(dev_t devt); +extern struct device *sec_device_create(void *drvdata, const char *fmt); +#else /* CONFIG_DRV_SAMSUNG */ +static inline struct device *sec_dev_get_by_name(const char *name) { return NULL; } +static inline void sec_device_destroy(dev_t devt) {}; +#define sec_device_create(...) (NULL) +#endif /* CONFIG_DRV_SAMSUNG */ + +#endif /* __SEC_CLASS_H__ */ diff --git a/include/linux/samsung/bsp/sec_cmdline.h b/include/linux/samsung/bsp/sec_cmdline.h new file mode 100644 index 000000000000..524128d9c342 --- /dev/null +++ b/include/linux/samsung/bsp/sec_cmdline.h @@ -0,0 +1,10 @@ +#ifndef __SEC_CMDLINE_H__ +#define __SEC_CMDLINE_H__ + +#if IS_ENABLED(CONFIG_SEC_CMDLINE) +extern const char *sec_cmdline_get_val(const char *param); +#else +static inline char *sec_cmdline_get_val(const char *param) { return NULL; } +#endif + +#endif /* __SEC_CMDLINE_H__ */ diff --git a/include/linux/samsung/bsp/sec_key_notifier.h b/include/linux/samsung/bsp/sec_key_notifier.h new file mode 100644 index 000000000000..407802c82188 --- /dev/null +++ b/include/linux/samsung/bsp/sec_key_notifier.h @@ -0,0 +1,27 @@ +#ifndef __SEC_KEY_NOTIFIER_H__ +#define __SEC_KEY_NOTIFIER_H__ + +#include + +struct sec_key_notifier_param { + unsigned int keycode; + int down; +}; + +#define SEC_KEY_EVENT_KEY_PRESS(__keycode) \ + { .keycode = __keycode, .down = 1, } +#define SEC_KEY_EVENT_KEY_RELEASE(__keycode) \ + { .keycode = __keycode, .down = 0, } +#define SEC_KEY_EVENT_KEY_PRESS_AND_RELEASE(__keycode) \ + SEC_KEY_EVENT_KEY_PRESS(__keycode), \ + SEC_KEY_EVENT_KEY_RELEASE(__keycode) + +#if IS_ENABLED(CONFIG_SEC_KEY_NOTIFIER) +extern int sec_kn_register_notifier(struct notifier_block *nb, const unsigned int *events, const size_t nr_events); +extern int sec_kn_unregister_notifier(struct notifier_block *nb, const unsigned int *events, const size_t nr_events); +#else +static inline int sec_kn_register_notifier(struct notifier_block *nb, const unsigned int *events, const size_t nr_events) { return 0; } +static inline int sec_kn_unregister_notifier(struct notifier_block *nb, const unsigned int *events, const size_t nr_events) { return 0; } +#endif + +#endif /* __SEC_KEY_NOTIFIER_H__ */ diff --git a/include/linux/samsung/bsp/sec_param.h b/include/linux/samsung/bsp/sec_param.h new file mode 100644 index 000000000000..34c6e3bf0856 --- /dev/null +++ b/include/linux/samsung/bsp/sec_param.h @@ -0,0 +1,30 @@ +#ifndef __SEC_PARAM_H__ +#define __SEC_PARAM_H__ + +/* TODO: currently, we use a different defenition for param index for each + * SoCs. + * This should be improved in future. + */ +#include "qcom/sec_qc_param.h" + +typedef bool (*sec_param_read_t)(size_t, void *); +typedef bool (*sec_param_write_t)(size_t, const void *); + +struct sec_param_operations { + sec_param_read_t read; + sec_param_write_t write; +}; + +#if IS_ENABLED(CONFIG_SEC_PARAM) +extern bool sec_param_get(size_t index, void *value); +extern bool sec_param_set(size_t index, const void *value); +extern int sec_param_register_operations(struct sec_param_operations *ops); +extern void sec_param_unregister_operations(struct sec_param_operations *ops); +#else +static inline bool sec_param_get(size_t index, void *value) { return false; } +static inline bool sec_param_set(size_t index, const void *value) { return false; } +static inline int sec_param_register_operations(struct sec_param_operations *ops) { return 0; } +static inline void sec_param_unregister_operations(struct sec_param_operations *ops) {} +#endif + +#endif /* __SEC_PARAM_H__ */ diff --git a/include/linux/samsung/bsp/sec_sysup.h b/include/linux/samsung/bsp/sec_sysup.h new file mode 100644 index 000000000000..049e36e7eb3f --- /dev/null +++ b/include/linux/samsung/bsp/sec_sysup.h @@ -0,0 +1,25 @@ +#ifndef __SEC_SYSUP_H__ +#define __SEC_SYSUP_H__ + +#define EDTBO_FIEMAP_MAGIC 0x00007763 + +struct fiemap_extent_p { + unsigned long fe_logical; /* logical offset in bytes for the start of the extent from the beginning of the file */ + unsigned long fe_physical; /* physical offset in bytes for the start of the extent from the beginning of the disk */ + unsigned long fe_length; /* length in bytes for this extent */ + unsigned long fe_reserved64[2]; + unsigned int fe_flags; /* FIEMAP_EXTENT_* flags for this extent */ + unsigned int fe_reserved[3]; +}; + +struct fiemap_p { + unsigned long fm_start; /* logical offset (inclusive) at which to start mapping (in) */ + unsigned long fm_length; /* logical length of mapping which userspace wants (in) */ + unsigned int fm_flags; /* FIEMAP_FLAG_* flags for request (in/out) */ + unsigned int fm_mapped_extents; /* number of extents that were mapped (out) */ + unsigned int fm_extent_count; /* size of fm_extents array (in) */ + unsigned int fm_reserved; + struct fiemap_extent_p fm_extents[128]; /* array of mapped extents (out) */ +}; + +#endif /* __SEC_SYSUP_H__ */ diff --git a/include/linux/samsung/builder_pattern.h b/include/linux/samsung/builder_pattern.h new file mode 100644 index 000000000000..bc8b00fd0321 --- /dev/null +++ b/include/linux/samsung/builder_pattern.h @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2020-2021 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#ifndef __BUILDER_PATTERN_H__ +#define __BUILDER_PATTERN_H__ + +#include +#include +#include +#include + +struct director_threaded; + +/* An interface 'Builder' struct + * TODO: This struct should be embedded if the drvdata use + * this 'Builder Pattern'. + */ +struct builder { + struct device *dev; + struct director_threaded *drct; +}; + +#define DT_BUILDER(__parse_dt) \ + { .parse_dt = __parse_dt, } + +/* The prototype of 'Concrete Builde' parsing a device tree */ +typedef int (*parse_dt_t)(struct builder *bd, struct device_node *np); + +struct dt_builder { + parse_dt_t parse_dt; +}; + +#define DEVICE_BUILDER(__construct_dev, __destruct_dev) \ + { .construct_dev = __construct_dev, .destruct_dev = __destruct_dev, } + +/* The prototype of 'Concrete Builde' constructing a device */ +typedef int (*construct_dev_t)(struct builder *bd); + +/* The prototype of 'Concrete Builde' destructing a device */ +typedef void (*destruct_dev_t)(struct builder *bd); + +struct dev_builder { + construct_dev_t construct_dev; + destruct_dev_t destruct_dev; +}; + +#ifdef _DEBUG_BUILDER_PATTERN +static inline int __bp_call_parse_dt(parse_dt_t parse_dt, + struct builder *bd, struct device_node *np) +{ + ktime_t calltime, rettime; + int err; + + calltime = ktime_get(); + err = parse_dt(bd, np); + rettime = ktime_get(); + + dev_info(bd->dev, "%ps returned %d after %llu\n", + parse_dt, err, + (unsigned long long)ktime_sub(rettime, calltime)); + + return err; +} + +static inline int __bp_call_construct_dev(construct_dev_t construct_dev, + struct builder *bd) +{ + ktime_t calltime, rettime; + int err; + + calltime = ktime_get(); + err = construct_dev(bd); + rettime = ktime_get(); + + dev_info(bd->dev, "%ps returned %d after %llu\n", + construct_dev , err, + (unsigned long long)ktime_sub(rettime, calltime)); + + return err; +} + +static inline void __bp_call_destruct_dev(destruct_dev_t destruct_dev, + struct builder *bd) +{ + ktime_t calltime, rettime; + + calltime = ktime_get(); + destruct_dev(bd); + rettime = ktime_get(); + + dev_info(bd->dev, "%ps returned after %llu\n", + destruct_dev, + (unsigned long long)ktime_sub(rettime, calltime)); +} +#else +static inline int __bp_call_parse_dt(parse_dt_t parse_dt, + struct builder *bd, struct device_node *np) +{ + return parse_dt(bd, np); +} + +static inline int __bp_call_construct_dev(construct_dev_t construct_dev, + struct builder *bd) +{ + return construct_dev(bd); +} + +static inline void __bp_call_destruct_dev(destruct_dev_t destruct_dev, + struct builder *bd) +{ + destruct_dev(bd); +} +#endif + +static inline int __bp_call_concrete_parse_dt(struct builder *bd, + const struct dt_builder *builder, size_t i) +{ + struct device_node *np = dev_of_node(bd->dev); + int err; + parse_dt_t parse_dt = builder[i].parse_dt; + + err = __bp_call_parse_dt(parse_dt, bd, np); + if (err) + dev_err(bd->dev, "failed to parse a device tree - [%zu] %ps (%d)\n", + i, parse_dt, err); + + return err; +} + +static inline int __bp_call_concrete_construct_dev(struct builder *bd, + const struct dev_builder *builder, size_t i) +{ + int err; + construct_dev_t construct_dev = builder[i].construct_dev; + + if (!construct_dev) + return 0; + + err = __bp_call_construct_dev(construct_dev, bd); + if (err) + dev_err(bd->dev, "failed to construct_dev a device - [%zu] %ps (%d)\n", + i, construct_dev, err); + + return err; +} + +static inline void __bp_call_concrete_destruct_dev(struct builder *bd, + const struct dev_builder *builder, size_t i) +{ + destruct_dev_t destruct_dev = builder[i].destruct_dev; + + if (!destruct_dev) + return; + + __bp_call_destruct_dev(destruct_dev, bd); +} + +/* A common 'Director' parsing a device tree + * @return - 0 on success. 'errno' of last failed on failure. + */ +static inline int sec_director_parse_dt(struct builder *bd, + const struct dt_builder *builder, size_t n) +{ + int err; + size_t i; + + for (i = 0; i < n; i++) { + err = __bp_call_concrete_parse_dt(bd, builder, i); + if (err) + return err; + } + + return 0; +} + +/* A common 'Director' constructing a device + * @last_failed - The number of called builders on success. + * The "NEGATIVE" index of last failed on failure. + * @return - 0 on success. + * return value of the last concrete builder on failure. + */ +static inline int sec_director_construct_dev(struct builder *bd, + const struct dev_builder *builder, ssize_t n, + ssize_t *last_failed) +{ + int err; + ssize_t i; + + for (i = 0; i < n; i++) { + err = __bp_call_concrete_construct_dev(bd, builder, i); + if (err) { + *last_failed = -i; + return err; + } + } + + *last_failed = n; + + return 0; +} + +/* A common 'Director' destructing a device */ +static inline void sec_director_destruct_dev(struct builder *bd, + const struct dev_builder *builder, ssize_t n, + ssize_t last_failed) +{ + ssize_t i; + + BUG_ON((last_failed > n) || (last_failed < 0)); + + for (i = last_failed - 1; i >= 0; i--) + __bp_call_concrete_destruct_dev(bd, builder, i); +} + +/* A wrapper function for probe call-backs */ +static inline int sec_director_probe_dev(struct builder *bd, + const struct dev_builder *builder, ssize_t n) +{ + int err; + ssize_t last_failed; + + err = sec_director_construct_dev(bd, builder, n, &last_failed); + if (last_failed <= 0) + goto err_dev_director; + + return 0; + +err_dev_director: + sec_director_destruct_dev(bd, builder, n, -last_failed); + return err; +} + +struct director_threaded { + struct builder *bd; + const struct dev_builder *builder; + ssize_t n; + int *construct_result; +}; + +/* A common 'Director' constructing a device - threaded */ +static int sec_director_construct_dev_threaded(void *__drct) +{ + struct director_threaded *drct = __drct; + struct builder *bd = drct->bd; + const struct dev_builder *builder = drct->builder; + ssize_t n = drct->n; + int *construct_result = drct->construct_result; + ssize_t i; + + for (i = 0; i < n; i++) + construct_result[i] = + __bp_call_concrete_construct_dev(bd, builder, i); + + return 0; +} + +/* A common 'Director' destructing a device */ +static inline void sec_director_destruct_dev_threaded( + struct director_threaded *drct) +{ + struct builder *bd = drct->bd; + const struct dev_builder *builder = drct->builder; + ssize_t n = drct->n; + int *construct_result = drct->construct_result; + ssize_t i; + + for (i = n - 1; i >= 0; i--) { + if (!construct_result[i]) + __bp_call_concrete_destruct_dev(bd, builder, i); + } +} + +/* A wrapper function for probe call-backs - threaded */ +static inline int sec_director_probe_dev_threaded(struct builder *bd, + const struct dev_builder *builder, ssize_t n, const char *name) +{ + struct device *dev = bd->dev; + struct director_threaded *drct; + int *construct_result; + struct task_struct *thread; + + drct = devm_kzalloc(dev, sizeof(*drct), GFP_KERNEL); + if (!drct) + return -ENOMEM; + + construct_result = devm_kcalloc(dev, n, sizeof(*construct_result), + GFP_KERNEL); + if (!construct_result) + return -ENOMEM; + + drct->bd = bd; + drct->builder = builder; + drct->n = n; + drct->construct_result = construct_result; + + thread = kthread_run(sec_director_construct_dev_threaded, + drct, "drct-%s", name); + if (IS_ERR_OR_NULL(thread)) { + dev_err(dev, "failed to created drct thread - (%ld)!\n", + PTR_ERR(thread)); + return -ENOMEM; + } + + bd->drct = drct; + + return 0; +} + +#endif /* __BUILDER_PATTERN_H__ */ + diff --git a/include/linux/samsung/debug/qcom/mock/sec_qc_mock_epss-l3.h b/include/linux/samsung/debug/qcom/mock/sec_qc_mock_epss-l3.h new file mode 100644 index 000000000000..91f81cc3e616 --- /dev/null +++ b/include/linux/samsung/debug/qcom/mock/sec_qc_mock_epss-l3.h @@ -0,0 +1,15 @@ +#ifndef __SEC_QC_MOCK_EPSS_L3_H__ +#define __SEC_QC_MOCK_EPSS_L3_H__ + +#include + +/* implemented @ drivers/interconnect/qcom/epss-l3.c */ +#if IS_ENABLED(CONFIG_INTERCONNECT_QCOM_EPSS_L3) +extern int qcom_icc_epss_l3_cpu_set_register_notifier(struct notifier_block *nb); +extern int qcom_icc_epss_l3_cpu_set_unregister_notifier(struct notifier_block *nb); +#else +static inline int qcom_icc_epss_l3_cpu_set_register_notifier(struct notifier_block *nb) { return 0; } +static inline int qcom_icc_epss_l3_cpu_set_unregister_notifier(struct notifier_block *nb) { return 0; } +#endif + +#endif /* */ diff --git a/include/linux/samsung/debug/qcom/mock/sec_qc_mock_kryo_arm64_edac.h b/include/linux/samsung/debug/qcom/mock/sec_qc_mock_kryo_arm64_edac.h new file mode 100644 index 000000000000..3040281c295c --- /dev/null +++ b/include/linux/samsung/debug/qcom/mock/sec_qc_mock_kryo_arm64_edac.h @@ -0,0 +1,18 @@ +#ifndef __SEC_QC_MOCK_KRYO_ARM64_EDAC_H__ +#define __SEC_QC_MOCK_KRYO_ARM64_EDAC_H__ + +#include + +#include +#include + +#if IS_ENABLED(CONFIG_SEC_QC_MOCK) +/* implemented @ drivers/edac/kryo_arm64_edac.c */ +extern ap_health_t *qcom_kryo_arm64_edac_error_register_notifier(struct notifier_block *nb); +extern int qcom_kryo_arm64_edac_error_unregister_notifier(struct notifier_block *nb); +#else +static inline ap_health_t *qcom_kryo_arm64_edac_error_register_notifier(struct notifier_block *nb) { return ERR_PTR(-ENODEV); } +static inline int qcom_kryo_arm64_edac_error_unregister_notifier(struct notifier_block *nb) { return -ENODEV; } +#endif + +#endif /* __SEC_QC_MOCK_KRYO_ARM64_EDAC_H__ */ diff --git a/include/linux/samsung/debug/qcom/mock/sec_qc_mock_memory_dump_v2.h b/include/linux/samsung/debug/qcom/mock/sec_qc_mock_memory_dump_v2.h new file mode 100644 index 000000000000..56ccb82f76ad --- /dev/null +++ b/include/linux/samsung/debug/qcom/mock/sec_qc_mock_memory_dump_v2.h @@ -0,0 +1,11 @@ +#ifndef __SEC_QC_MOCK_MEMORY_DUMP_V2_H__ +#define __SEC_QC_MOCK_MEMORY_DUMP_V2_H__ + +/* implemented @ drivers/soc/qcom/memory_dump_v2.c */ +#if IS_ENABLED(CONFIG_SEC_QC_MOCK) +extern void sec_qc_summary_set_msm_memdump_info(struct sec_qc_summary_data_apss *apss); +#else +static inline void sec_qc_summary_set_msm_memdump_info(struct sec_qc_summary_data_apss *apss) {} +#endif + +#endif /* __SEC_QC_MOCK_MEMORY_DUMP_V2_H__ */ diff --git a/include/linux/samsung/debug/qcom/mock/sec_qc_mock_msm_rtb.h b/include/linux/samsung/debug/qcom/mock/sec_qc_mock_msm_rtb.h new file mode 100644 index 000000000000..234e43592a11 --- /dev/null +++ b/include/linux/samsung/debug/qcom/mock/sec_qc_mock_msm_rtb.h @@ -0,0 +1,11 @@ +#ifndef __SEC_QC_MOCK_MSM_RTB_H__ +#define __SEC_QC_MOCK_MSM_RTB_H__ + +/* implemented @ kernel/trace/msm_rtb.c */ +#if IS_ENABLED(CONFIG_SEC_QC_MOCK) +extern void sec_qc_summary_set_rtb_info(struct sec_qc_summary_data_apss *apss); +#else +static inline void sec_qc_summary_set_rtb_info(struct sec_qc_summary_data_apss *apss) {} +#endif + +#endif /* __SEC_QC_MOCK_MSM_RTB_H__ */ diff --git a/include/linux/samsung/debug/qcom/mock/sec_qc_mock_qcom-cpufreq-hw.h b/include/linux/samsung/debug/qcom/mock/sec_qc_mock_qcom-cpufreq-hw.h new file mode 100644 index 000000000000..b7d42216e1a1 --- /dev/null +++ b/include/linux/samsung/debug/qcom/mock/sec_qc_mock_qcom-cpufreq-hw.h @@ -0,0 +1,13 @@ +#ifndef __SEC_QC_MOCK_QCOM_CPUFREQ_HW_H__ +#define __SEC_QC_MOCK_QCOM_CPUFREQ_HW_H__ + +/* implemented @ drivers/cpufreq/qcom-cpufreq-hw.c */ +#if IS_ENABLED(CONFIG_SEC_QC_MOCK) +extern int qcom_cpufreq_hw_target_index_register_notifier(struct notifier_block *nb); +extern int qcom_cpufreq_hw_target_index_unregister_notifier(struct notifier_block *nb); +#else +static inline int qcom_cpufreq_hw_target_index_register_notifier(struct notifier_block *nb) { return 0; } +static inline int qcom_cpufreq_hw_target_index_unregister_notifier(struct notifier_block *nb) { return 0; } +#endif + +#endif /* __SEC_QC_MOCK_QCOM_CPUFREQ_HW_H__ */ diff --git a/include/linux/samsung/debug/qcom/mock/sec_qc_mock_qcom_lpm.h b/include/linux/samsung/debug/qcom/mock/sec_qc_mock_qcom_lpm.h new file mode 100644 index 000000000000..9bd7c4c6af39 --- /dev/null +++ b/include/linux/samsung/debug/qcom/mock/sec_qc_mock_qcom_lpm.h @@ -0,0 +1,13 @@ +#ifndef __SEC_QC_MOCK_QCOM_WDT_CORE_H__ +#define __SEC_QC_MOCK_QCOM_WDT_CORE_H__ + +#if IS_ENABLED(CONFIG_SEC_QC_MOCK) +/* implemented @drivers/cpuidle/governors/qcom-lpm-sec-extra.c */ +extern void qcom_lpm_set_sleep_disabled(void); +extern void qcom_lpm_unset_sleep_disabled(void); +#else +static inline void qcom_lpm_set_sleep_disabled(void) {} +static inline void qcom_lpm_unset_sleep_disabled(void) {} +#endif + +#endif /* __SEC_QC_MOCK_QCOM_WDT_CORE_H__ */ diff --git a/include/linux/samsung/debug/qcom/mock/sec_qc_mock_qcom_wdt_core.h b/include/linux/samsung/debug/qcom/mock/sec_qc_mock_qcom_wdt_core.h new file mode 100644 index 000000000000..c6064259eab1 --- /dev/null +++ b/include/linux/samsung/debug/qcom/mock/sec_qc_mock_qcom_wdt_core.h @@ -0,0 +1,17 @@ +#ifndef __SEC_QC_MOCK_QCOM_WDT_CORE_H__ +#define __SEC_QC_MOCK_QCOM_WDT_CORE_H__ + +#if IS_ENABLED(CONFIG_SEC_QC_MOCK) +/* implemented @drivers/soc/qcom/qcom_wdt_core.c */ +extern int qcom_wdt_pet_register_notifier(struct notifier_block *nb); +extern int qcom_wdt_pet_unregister_notifier(struct notifier_block *nb); +extern int qcom_wdt_bark_register_notifier(struct notifier_block *nb); +extern int qcom_wdt_bark_unregister_notifier(struct notifier_block *nb); +#else +static inline int qcom_wdt_pet_register_notifier(struct notifier_block *nb) { return 0; } +static inline int qcom_wdt_pet_unregister_notifier(struct notifier_block *nb) { return 0; } +static inline int qcom_wdt_bark_register_notifier(struct notifier_block *nb) { return 0; } +static inline int qcom_wdt_bark_unregister_notifier(struct notifier_block *nb) { return 0; } +#endif + +#endif /* __SEC_QC_MOCK_QCOM_WDT_CORE_H__ */ diff --git a/include/linux/samsung/debug/qcom/mock/sec_qc_mock_smem.h b/include/linux/samsung/debug/qcom/mock/sec_qc_mock_smem.h new file mode 100644 index 000000000000..05014d14d6a4 --- /dev/null +++ b/include/linux/samsung/debug/qcom/mock/sec_qc_mock_smem.h @@ -0,0 +1,19 @@ +#ifndef __SEC_QC_MOCK_SMEM_H__ +#define __SEC_QC_MOCK_SMEM_H__ + +#ifndef QCOM_SMEM_HOST_ANY +#define QCOM_SMEM_HOST_ANY -1 +#endif + +#if IS_ENABLED(CONFIG_SEC_QC_MOCK) +/* implemented @ drivers/soc/qcom/smsm.c */ +extern int qcom_smem_alloc(unsigned host, unsigned item, size_t size); +extern void *qcom_smem_get(unsigned host, unsigned item, size_t *size); +extern phys_addr_t qcom_smem_virt_to_phys(void *p); +#else +static inline int qcom_smem_alloc(unsigned host, unsigned item, size_t size) { return -ENODEV; } +static inline void *qcom_smem_get(unsigned host, unsigned item, size_t *size) { return ERR_PTR(-ENODEV); } +static inline phys_addr_t qcom_smem_virt_to_phys(void *p) { return 0; } +#endif /* CONFIG_SEC_QC_MOCK */ + +#endif /* __SEC_QC_MOCK_SMEM_H__ */ diff --git a/include/linux/samsung/debug/qcom/mock/sec_qc_mock_walt.h b/include/linux/samsung/debug/qcom/mock/sec_qc_mock_walt.h new file mode 100644 index 000000000000..bb21e9e1757c --- /dev/null +++ b/include/linux/samsung/debug/qcom/mock/sec_qc_mock_walt.h @@ -0,0 +1,11 @@ +#ifndef __SEC_QC_MOCK_WALT_H__ +#define __SEC_QC_MOCK_WALT_H__ + +/* implemented @ kernel/sched/walt/walt.c */ +#if IS_ENABLED(CONFIG_SEC_QC_MOCK) +extern void sec_qc_summary_set_sched_walt_info(struct sec_qc_summary_data_apss *apss); +#else +static inline void sec_qc_summary_set_sched_walt_info(struct sec_qc_summary_data_apss *apss) {} +#endif + +#endif /* __SEC_QC_MOCK_WALT_H__ */ diff --git a/include/linux/samsung/debug/qcom/sec_qc_dbg_partition.h b/include/linux/samsung/debug/qcom/sec_qc_dbg_partition.h new file mode 100644 index 000000000000..d22d8b883090 --- /dev/null +++ b/include/linux/samsung/debug/qcom/sec_qc_dbg_partition.h @@ -0,0 +1,16 @@ +#ifndef __SEC_QC_DBG_PARTITION_H__ +#define __SEC_QC_DBG_PARTITION_H__ + +#include "sec_qc_dbg_partition_type.h" + +#if IS_ENABLED(CONFIG_SEC_QC_DEBUG_PARTITION) +extern ssize_t sec_qc_dbg_part_get_size(size_t index); +extern bool sec_qc_dbg_part_read(size_t index, void *value); +extern bool sec_qc_dbg_part_write(size_t index, const void *value); +#else +static inline ssize_t sec_qc_dbg_part_get_size(size_t index) { return -ENODEV; } +static inline bool sec_qc_dbg_part_read(size_t index, void *value) { return false; } +static inline bool sec_qc_dbg_part_write(size_t index, const void *value) { return false; } +#endif + +#endif /* __SEC_QC_DBG_PARTITION_H__ */ diff --git a/include/linux/samsung/debug/qcom/sec_qc_dbg_partition_type.h b/include/linux/samsung/debug/qcom/sec_qc_dbg_partition_type.h new file mode 100644 index 000000000000..32a9a825d081 --- /dev/null +++ b/include/linux/samsung/debug/qcom/sec_qc_dbg_partition_type.h @@ -0,0 +1,187 @@ +#ifndef __SEC_QC_DBG_PARTITION_TYPE_H__ +#define __SEC_QC_DBG_PARTITION_TYPE_H__ + +#include "sec_qc_user_reset_type.h" + +enum debug_partition_index { + debug_index_reset_summary_info = 0, + debug_index_reset_header = 0, + debug_index_reset_ex_info, + debug_index_ap_health, + debug_index_lcd_debug_info, + debug_index_reset_history, + debug_index_reserve_0, + debug_index_reserve_1, + debug_index_onoff_history, + debug_index_reset_tzlog, + debug_index_reset_extrc_info, + debug_index_auto_comment, + debug_index_reset_rkplog, + debug_index_modem_info, + debug_index_reset_klog, + debug_index_reset_lpm_klog, + debug_index_reset_summary, + debug_index_max, +}; + +#define DEBUG_PART_MAX_TABLE (debug_index_max) + +#define AP_HEALTH_MAGIC 0x48544C4145485041 +#define AP_HEALTH_VER 2 +#define MAX_PCIE_NUM 3 + +typedef struct { + uint64_t magic; + uint32_t size; + uint16_t version; + uint16_t need_write; +} ap_health_header_t; + +struct edac_cnt { + uint32_t ue_cnt; + uint32_t ce_cnt; +}; + +typedef struct { + struct edac_cnt edac[CONFIG_SEC_QC_NR_CPUS][2]; // L1, L2 + struct edac_cnt edac_l3; // L3 + uint32_t edac_bus_cnt; + struct edac_cnt edac_llcc_data_ram; // LLCC Data RAM + struct edac_cnt edac_llcc_tag_ram; // LLCC Tag RAM +} cache_health_t; + +typedef struct { + uint32_t phy_init_fail_cnt; + uint32_t link_down_cnt; + uint32_t link_up_fail_cnt; + uint32_t link_up_fail_ltssm; +} pcie_health_t; + +typedef struct { + uint32_t np; + uint32_t rp; + uint32_t mp; + uint32_t kp; + uint32_t dp; + uint32_t wp; + uint32_t tp; + uint32_t sp; + uint32_t pp; + uint32_t cp; +} reset_reason_t; + +enum { + L3, + PWR_CLUSTER, + PERF_CLUSTER, + PRIME_CLUSTER, +}; + +#define MAX_CLUSTER_NUM 4 +#define CPU_NUM_PER_CLUSTER 4 +#define MAX_VREG_CNT 3 +#define MAX_BATT_DCVS 10 + +typedef struct { + uint32_t cpu_KHz; + uint32_t reserved; +} apps_dcvs_t; + +typedef struct { + uint32_t ddr_KHz; + uint16_t mV[MAX_VREG_CNT]; +} rpm_dcvs_t; + +typedef struct { + uint64_t ktime; + int32_t cap; + int32_t volt; + int32_t temp; + int32_t curr; +} batt_dcvs_t; + +typedef struct { + uint32_t tail; + batt_dcvs_t batt[MAX_BATT_DCVS]; +} battery_health_t; + +typedef struct { + uint64_t pon_reason; + uint64_t fault_reason; +} pon_dcvs_t; + +typedef struct { + apps_dcvs_t apps[MAX_CLUSTER_NUM]; + rpm_dcvs_t rpm; + batt_dcvs_t batt; + pon_dcvs_t pon; +} dcvs_info_t; + +typedef struct { + ap_health_header_t header; + uint32_t last_rst_reason; + dcvs_info_t last_dcvs; + uint64_t spare_magic1; + reset_reason_t rr; + cache_health_t cache; + pcie_health_t pcie[MAX_PCIE_NUM]; + battery_health_t battery; + uint64_t spare_magic2; + reset_reason_t daily_rr; + cache_health_t daily_cache; + pcie_health_t daily_pcie[MAX_PCIE_NUM]; + uint64_t spare_magic3; +} ap_health_t; + +/* Synchronize with Fence Name Length */ +#define MAX_FTOUT_NAME 128 + +struct lcd_debug_ftout { + uint32_t count; + char name[MAX_FTOUT_NAME]; +}; + +#define FW_UP_MAX_RETRY 20 +struct lcd_debug_fw_up { + uint32_t try_count; + uint32_t pass_count; + uint32_t fail_line_count; + uint32_t fail_line[FW_UP_MAX_RETRY]; + uint32_t fail_count; + uint32_t fail_address[FW_UP_MAX_RETRY]; +}; + +struct lcd_debug_t { + struct lcd_debug_ftout ftout; + struct lcd_debug_fw_up fw_up; +}; + +#define SEC_DEBUG_ONOFF_HISTORY_MAX_CNT 20 +#define SEC_DEBUG_ONOFF_REASON_STR_SIZE 128 + +typedef struct debug_onoff_reason { + int64_t rtc_offset; + int64_t local_time; + uint32_t boot_cnt; + char reason[SEC_DEBUG_ONOFF_REASON_STR_SIZE]; +} onoff_reason_t; + +typedef struct debug_onoff_history { + uint32_t magic; + uint32_t size; + uint32_t index; + onoff_reason_t history[SEC_DEBUG_ONOFF_HISTORY_MAX_CNT]; +} onoff_history_t; + +#define DEBUG_PARTITION_MAGIC 0x41114729 +#define SECTOR_UNIT_SIZE (4096) /* UFS */ +#define SEC_DEBUG_PARTITION_SIZE (0xA00000) /* 10MB */ +#define SEC_DEBUG_RESET_EXTRC_SIZE (2 * 1024) /* 2KB */ +#define SEC_DEBUG_AUTO_COMMENT_SIZE (0x01000) /* 4KB */ +#define SEC_DEBUG_RESET_HISTORY_MAX_CNT (10) +#define SEC_DEBUG_RESET_HISTORY_SIZE (SEC_DEBUG_AUTO_COMMENT_SIZE*SEC_DEBUG_RESET_HISTORY_MAX_CNT) +#define SEC_DEBUG_RESET_ETRM_SIZE (0x3c0) +#define SEC_DEBUG_RESET_KLOG_SIZE (0x200000) /* 2MB */ +#define SEC_DEBUG_RESET_LPM_KLOG_SIZE (0x200000) /* 2MB */ + +#endif /* __SEC_QC_DBG_PARTITION_TYPE_H__ */ diff --git a/include/linux/samsung/debug/qcom/sec_qc_hw_param.h b/include/linux/samsung/debug/qcom/sec_qc_hw_param.h new file mode 100644 index 000000000000..c214a0e61588 --- /dev/null +++ b/include/linux/samsung/debug/qcom/sec_qc_hw_param.h @@ -0,0 +1,10 @@ +#ifndef __SEC_QC_HW_PARAM_H__ +#define __SEC_QC_HW_PARAM_H__ + +#if IS_ENABLED(CONFIG_SEC_QC_HW_PARAM) +extern void battery_last_dcvs(int cap, int volt, int temp, int curr); +#else +static inline void battery_last_dcvs(int cap, int volt, int temp, int curr) {} +#endif + +#endif /* __SEC_QC_HW_PARAM_H__ */ diff --git a/include/linux/samsung/debug/qcom/sec_qc_irq_exit_log.h b/include/linux/samsung/debug/qcom/sec_qc_irq_exit_log.h new file mode 100644 index 000000000000..e52fe17361b0 --- /dev/null +++ b/include/linux/samsung/debug/qcom/sec_qc_irq_exit_log.h @@ -0,0 +1,27 @@ +#ifndef __SEC_QC_IRQ_EXIT_LOG_H__ +#define __SEC_QC_IRQ_EXIT_LOG_H__ + +#include + +#define SEC_QC_IRQ_EXIT_LOG_MAX 512 + +struct sec_qc_irq_exit_buf { + unsigned int irq; + u64 time; + u64 end_time; + u64 elapsed_time; + pid_t pid; +}; + +struct sec_qc_irq_exit_log_data { + unsigned int idx; + struct sec_qc_irq_exit_buf buf[SEC_QC_IRQ_EXIT_LOG_MAX]; +} ____cacheline_aligned_in_smp; + +#if IS_BUILTIN(CONFIG_SEC_QC_LOGGER) +extern void __deprecated sec_debug_irq_enterexit_log(unsigned int irq, u64 start_time); +#else +static inline void sec_debug_irq_enterexit_log(unsigned int irq, u64 start_time) {} +#endif + +#endif /* __SEC_QC_IRQ_EXIT_LOG_H__ */ diff --git a/include/linux/samsung/debug/qcom/sec_qc_irq_log.h b/include/linux/samsung/debug/qcom/sec_qc_irq_log.h new file mode 100644 index 000000000000..1abc1f9f9b3b --- /dev/null +++ b/include/linux/samsung/debug/qcom/sec_qc_irq_log.h @@ -0,0 +1,40 @@ +#ifndef __SEC_QC_IRQ_LOG_H__ +#define __SEC_QC_IRQ_LOG_H__ + +#include + +#define SEC_QC_IRQ_LOG_MAX 512 + +#define IRQ_ENTRY 0x4945 +#define IRQ_EXIT 0x4958 + +#define SOFTIRQ_ENTRY 0x5345 +#define SOFTIRQ_EXIT 0x5358 + +struct sec_qc_irq_buf { + u64 time; + unsigned int irq; + void *fn; + const char *name; + int en; + int preempt_count; + void *context; + pid_t pid; + unsigned int entry_exit; +}; + +struct sec_qc_irq_log_data { + unsigned int idx; + struct sec_qc_irq_buf buf[SEC_QC_IRQ_LOG_MAX]; +} ____cacheline_aligned_in_smp; + +#if IS_BUILTIN(CONFIG_SEC_QC_LOGGER) +/* called @ kernel/irq/chip.c */ +/* called @ kernel/irq/handle.c */ +/* called @ kernel/softirq.c */ +extern void sec_debug_irq_sched_log(unsigned int irq, void *fn, char *name, unsigned int en); +#else +static inline void sec_debug_irq_sched_log(unsigned int irq, void *fn, char *name, unsigned int en) {} +#endif + +#endif /* __SEC_QC_IRQ_LOG_H__ */ diff --git a/include/linux/samsung/debug/qcom/sec_qc_mem_debug.h b/include/linux/samsung/debug/qcom/sec_qc_mem_debug.h new file mode 100644 index 000000000000..5e2d5909e189 --- /dev/null +++ b/include/linux/samsung/debug/qcom/sec_qc_mem_debug.h @@ -0,0 +1,75 @@ +#ifndef __SEC_QC_MEM_DEBUG_H__ +#define __SEC_QC_MEM_DEBUG_H__ + +#include +#include + +#if IS_ENABLED(CONFIG_ARM64) +/* In line with aarch64_insn_read from arch/arm64/kernel/insn.c */ +static inline int ___qc_memdbg_instruction_read(void *addr, u32 *insnp) +{ + int ret; + __le32 val; + + ret = copy_from_kernel_nofault(&val, addr, AARCH64_INSN_SIZE); + if (!ret) + *insnp = le32_to_cpu(val); + + return ret; +} + +/* In line with dump_kernel_instr from arch/arm64/kernel/traps.c */ +static inline void sec_qc_memdbg_dump_instr(const char *rname, u64 instr) +{ + /* 2 instructions + 16 instructions (cache line size) + 2 instructions + "()" + null */ + char str[sizeof("00000000 ") * 20 + 2 + 1], *p = str; + u64 i; + u64 start = ALIGN_DOWN(instr, cache_line_size()) - 0x8; + u64 end = ALIGN(instr, cache_line_size()) + 0x8; + u64 nr_entries = (end - start) / AARCH64_INSN_SIZE; + u64 current_idx = (instr - start) / AARCH64_INSN_SIZE; + + for (i = 0; i < nr_entries; i++) { + unsigned int val, bad; + + bad = ___qc_memdbg_instruction_read(&((u32 *)start)[i], &val); + if (!bad) + p += scnprintf(p, sizeof(str) - (p - str), + i == current_idx ? "(%08x) " : "%08x ", val); + else { + p += scnprintf(p, sizeof(str) - (p - str), "bad value"); + break; + } + } + + pr_emerg("%s Code: %s\n", rname, str); +} + +/* In line with __show_regs from arch/arm64/kernel/process.c */ +static inline void sec_qc_memdbg_show_regs_min(struct pt_regs *regs) +{ + ssize_t i = 29; + + pr_emerg("pc : %pS\n", (void *)regs->pc); + pr_emerg("lr : %pS\n", (void *)ptrauth_strip_insn_pac(regs->regs[30])); + pr_emerg("sp : %016llx\n", regs->sp); + + while (i >= 0) { + pr_emerg("x%-2zd: %016llx ", i, regs->regs[i]); + i--; + + if (i % 2 == 0) { + pr_cont("x%-2zd: %016llx ", i, regs->regs[i]); + i--; + } + + pr_cont("\n"); + } +} +#else +static inline int ___qc_memdbg_instruction_read(void *addr, u32 *insnp) { return -ENODEV; } +static inline void sec_qc_memdbg_dump_instr(const char *rname, u64 instr) {} +static inline void sec_qc_memdbg_show_regs_min(struct pt_regs *regs) {} +#endif + +#endif /* __SEC_QC_MEM_DEBUG_H__ */ diff --git a/include/linux/samsung/debug/qcom/sec_qc_msg_log.h b/include/linux/samsung/debug/qcom/sec_qc_msg_log.h new file mode 100644 index 000000000000..e8760a6ea781 --- /dev/null +++ b/include/linux/samsung/debug/qcom/sec_qc_msg_log.h @@ -0,0 +1,36 @@ +#ifndef __SEC_QC_MSG_LOG_H__ +#define __SEC_QC_MSG_LOG_H__ + +#include + +#define SEC_QC_MSG_LOG_MAX 1024 + +struct sec_qc_msg_buf { + u64 time; + char msg[64]; + void *caller0; + void *caller1; + char *task; +}; + +struct sec_qc_msg_log_data { + unsigned int idx; + struct sec_qc_msg_buf buf[SEC_QC_MSG_LOG_MAX]; +} ____cacheline_aligned_in_smp; + +#if IS_BUILTIN(CONFIG_SEC_QC_LOGGER) +/* TODO: do not call this function directly. + * plz use sec_debug_msg_log macro instead. + */ +extern int ___sec_debug_msg_log(void *caller, const char *fmt, ...); + +/* called @ kernel/softirq.c */ +/* called @ kernel/time/timer.c */ +/* called @ kernel/time/hrtimer.c */ +#define sec_debug_msg_log(fmt, ...) \ + ___sec_debug_msg_log(__builtin_return_address(0), fmt, ##__VA_ARGS__) +#else +#define sec_debug_msg_log(fmt, ...) +#endif + +#endif /* __SEC_QC_MSG_LOG_H__ */ diff --git a/include/linux/samsung/debug/qcom/sec_qc_rbcmd.h b/include/linux/samsung/debug/qcom/sec_qc_rbcmd.h new file mode 100644 index 000000000000..08a168d402fe --- /dev/null +++ b/include/linux/samsung/debug/qcom/sec_qc_rbcmd.h @@ -0,0 +1,142 @@ +#ifndef __SEC_QC_RBCMD_H__ +#define __SEC_QC_RBCMD_H__ + +/* qualcomm pon restart reason magic */ +enum { + PON_RESTART_REASON_UNKNOWN = 0x0, + PON_RESTART_REASON_RECOVERY = 0x01, + PON_RESTART_REASON_BOOTLOADER = 0x02, + PON_RESTART_REASON_RTC = 0x03, + PON_RESTART_REASON_DMVERITY_CORRUPTED = 0x04, + PON_RESTART_REASON_DMVERITY_ENFORCE = 0x05, + PON_RESTART_REASON_KEYS_CLEAR = 0x06, +#if IS_ENABLED(CONFIG_SEC_QPNP_PON_SPARE_BITS) && (CONFIG_SEC_QPNP_PON_SPARE_BITS == 6) + /* 32 ~ 63 for OEMs/ODMs secific features */ + PON_RESTART_REASON_OEM_MIN = 0x20, + PON_RESTART_REASON_OEM_MAX = 0x3F, + + /* FIXME: DUMP_SINK feature is not used MSM8953 families */ + PON_RESTART_REASON_DUMP_SINK_BOOTDEV = 0x07, + PON_RESTART_REASON_DUMP_SINK_SDCARD = 0x08, + PON_RESTART_REASON_DUMP_SINK_USB = 0x09, + + PON_RESTART_REASON_FORCE_UPLOAD_ON = 0x10, + PON_RESTART_REASON_FORCE_UPLOAD_OFF = 0x11, +#endif /* CONFIG_SEC_QPNP_PON_SPARE_BITS == 6 */ + + PON_RESTART_REASON_CP_CRASH = 0x12, + PON_RESTART_REASON_MANUAL_RESET = 0x13, + PON_RESTART_REASON_NORMALBOOT = 0x14, + PON_RESTART_REASON_DOWNLOAD = 0x15, + PON_RESTART_REASON_NVBACKUP = 0x16, + PON_RESTART_REASON_NVRESTORE = 0x17, + PON_RESTART_REASON_NVERASE = 0x18, + PON_RESTART_REASON_NVRECOVERY = 0x19, + PON_RESTART_REASON_SECURE_CHECK_FAIL = 0x1A, + PON_RESTART_REASON_WATCH_DOG = 0x1B, + PON_RESTART_REASON_KERNEL_PANIC = 0x1C, + PON_RESTART_REASON_THERMAL = 0x1D, + PON_RESTART_REASON_POWER_RESET = 0x1E, + PON_RESTART_REASON_WTSR = 0x1F, + PON_RESTART_REASON_RORY_START = 0x20, + /* TODO: 0x20 ~ 0x2A are reserved for RORY */ + PON_RESTART_REASON_RORY_END = 0x2A, + PON_RESTART_REASON_MULTICMD = 0x2B, + PON_RESTART_REASON_CROSS_FAIL = 0x2C, + PON_RESTART_REASON_LIMITED_DRAM_SETTING = 0x2D, + PON_RESTART_REASON_SLT_COMPLETE = 0x2F, + PON_RESTART_REASON_DBG_LOW = 0x30, + PON_RESTART_REASON_DBG_MID = 0x31, + PON_RESTART_REASON_DBG_HIGH = 0x32, + PON_RESTART_REASON_CP_DBG_ON = 0x33, + PON_RESTART_REASON_CP_DBG_OFF = 0x34, + PON_RESTART_REASON_CP_MEM_RESERVE_ON = 0x35, + PON_RESTART_REASON_CP_MEM_RESERVE_OFF = 0x36, + PON_RESTART_REASON_FIRMWAREUPDATE = 0x37, + PON_RESTART_REASON_SWITCHSEL = 0x38, /* SET_SWITCHSEL_MODE */ + /* TODO: 0x38 ~ 0x3D are reserved for SWITCHSEL */ + PON_RESTART_REASON_SWITCHSEL_END = 0x3D, + PON_RESTART_REASON_RUSTPROOF = 0x3D, /* SET_RUSTPROOF_MODE */ + PON_RESTART_REASON_MBS_MEM_RESERVE_ON = 0x3E, + PON_RESTART_REASON_MBS_MEM_RESERVE_OFF = 0x3F, + +#if IS_ENABLED(CONFIG_SEC_QPNP_PON_SPARE_BITS) && (CONFIG_SEC_QPNP_PON_SPARE_BITS == 6) + PON_RESTART_REASON_MAX = 0x40 +#else + PON_RESTART_REASON_USER_DRAM_TEST = 0x40, + PON_RESTART_REASON_QUEST_UEFI_START = 0x41, + PON_RESTART_REASON_QUEST_NMCHECKER_START = 0x42, + PON_RESTART_REASON_QUEST_NMCHECKER_SMD_START = 0x43, + PON_RESTART_REASON_QUEST_DRAM_START = 0x44, + PON_RESTART_REASON_QUEST_UEFI_DRAM_START = 0x45, + PON_RESTART_REASON_QUEST_DRAM_FAIL = 0x46, + PON_RESTART_REASON_QUEST_DRAM_TRAINIG_FAIL = 0x47, + + PON_RESTART_REASON_FORCE_UPLOAD_ON = 0x48, + PON_RESTART_REASON_FORCE_UPLOAD_OFF = 0x49, + PON_RESTART_REASON_DUMP_SINK_BOOTDEV = 0x4D, + PON_RESTART_REASON_DUMP_SINK_SDCARD = 0x4E, + PON_RESTART_REASON_DUMP_SINK_USB = 0x4F, + + PON_RESTART_REASON_QUEST_REWORK = 0x50, + PON_RESTART_REASON_QUEST_QUEFI_USER_START = 0x51, + PON_RESTART_REASON_QUEST_SUEFI_USER_START = 0x52, + PON_RESTART_REASON_QUEST_EDL_QUEST_DONE = 0x53, + PON_RESTART_REASON_QUEST_ERASE_PARAM = 0x54, + PON_RESTART_REASON_QUEST_QUEFI_PLUS_USER_START = 0x55, + PON_RESTART_REASON_QUEST_SUEFI_PLUS_USER_START = 0x56, + PON_RESTART_REASON_USER_DRAM_TEST_PLUS = 0x57, + PON_RESTART_REASON_USER_FLEX_CLK_START = 0x58, + PON_RESTART_REASON_USER_SVS_CLK_START = 0x59, + PON_RESTART_REASON_USER_NOMINAL_CLK_START = 0x5A, + PON_RESTART_REASON_USER_TURBO_CLK_START = 0x5B, + + PON_RESTART_REASON_RECOVERY_UPDATE = 0x60, + PON_RESTART_REASON_BOTA_COMPLETE = 0x61, + + PON_RESTART_REASON_CDSP_BLOCK = 0x62, + PON_RESTART_REASON_CDSP_ON = 0x63, + + PON_RESTART_REASON_MAX = 0x80 +#endif +}; + +#define PON_RESTART_REASON_NOT_HANDLE PON_RESTART_REASON_MAX + +/* samsung reboot reason magic */ +enum { + RESTART_REASON_NORMAL = 0x00000000, + RESTART_REASON_BOOTLOADER = 0x77665500, + RESTART_REASON_REBOOT = 0x77665501, + RESTART_REASON_RECOVERY = 0x77665502, + RESTART_REASON_RTC = 0x77665503, + RESTART_REASON_DMVERITY_CORRUPTED = 0x77665508, + RESTART_REASON_DMVERITY_ENFORCE = 0x77665509, + RESTART_REASON_KEYS_CLEAR = 0x7766550A, + RESTART_REASON_SEC_DEBUG_MODE = 0x776655EE, + RESTART_REASON_END = 0xFFFFFFFF, +}; + +#define RESTART_REASON_NOT_HANDLE RESTART_REASON_END + +struct sec_reboot_param; + +#if IS_ENABLED(CONFIG_SEC_QC_RBCMD) +extern int sec_qc_rbcmd_register_pon_rr_writer(struct notifier_block *nb); +extern void sec_qc_rbcmd_unregister_pon_rr_writer(struct notifier_block *nb); + +extern int sec_qc_rbcmd_register_sec_rr_writer(struct notifier_block *nb); +extern void sec_qc_rbcmd_unregister_sec_rr_writer(struct notifier_block *nb); + +extern void sec_qc_rbcmd_set_restart_reason(unsigned int pon_rr, unsigned int sec_rr, struct sec_reboot_param *param); +#else +static inline int sec_qc_rbcmd_register_pon_rr_writer(struct notifier_block *nb) { return -ENODEV; } +static inline void sec_qc_rbcmd_unregister_pon_rr_writer(struct notifier_block *nb) {} + +static inline int sec_qc_rbcmd_register_sec_rr_writer(struct notifier_block *nb) { return -ENODEV; } +static inline void sec_qc_rbcmd_unregister_sec_rr_writer(struct notifier_block *nb) {} + +static inline void sec_qc_rbcmd_set_restart_reason(unsigned int pon_rr, unsigned int sec_rr, struct sec_reboot_param *param) {} +#endif + +#endif /* __SEC_QC_RBCMD_H__ */ diff --git a/include/linux/samsung/debug/qcom/sec_qc_sched_log.h b/include/linux/samsung/debug/qcom/sec_qc_sched_log.h new file mode 100644 index 000000000000..bc1360a06a46 --- /dev/null +++ b/include/linux/samsung/debug/qcom/sec_qc_sched_log.h @@ -0,0 +1,40 @@ +#ifndef __SEC_QC_SCHED_LOG_H__ +#define __SEC_QC_SCHED_LOG_H__ + +#include + +#define SEC_QC_SCHED_LOG_MAX 512 + +struct sec_qc_sched_buf { + u64 time; + union { + char comm[TASK_COMM_LEN]; + u64 addr; + }; + pid_t pid; + struct task_struct *task; + char prev_comm[TASK_COMM_LEN]; + int prio; + pid_t prev_pid; + int prev_prio; + int prev_state; +}; + +struct sec_qc_sched_log_data { + unsigned int idx; + struct sec_qc_sched_buf buf[SEC_QC_SCHED_LOG_MAX]; +} ____cacheline_aligned_in_smp; + +#if IS_BUILTIN(CONFIG_SEC_QC_LOGGER) +/* called @ kernel/sched/core.c */ +extern void sec_debug_task_sched_log(int cpu, bool preempt, struct task_struct *task, struct task_struct *prev); + +/* called @ drivers/cpuidle/lpm-levels.c */ +/* called @ kernel/workqueue.c */ +extern int sec_debug_sched_msg(char *fmt, ...); +#else +static inline void sec_debug_task_sched_log(int cpu, bool preempt, struct task_struct *task, struct task_struct *prev) {} +static inline int sec_debug_sched_msg(char *fmt, ...) { return -ENODEV; } +#endif + +#endif /* __SEC_QC_SCHED_LOG_H__ */ diff --git a/include/linux/samsung/debug/qcom/sec_qc_smem.h b/include/linux/samsung/debug/qcom/sec_qc_smem.h new file mode 100644 index 000000000000..79f210b6e49d --- /dev/null +++ b/include/linux/samsung/debug/qcom/sec_qc_smem.h @@ -0,0 +1,12 @@ +#ifndef __SEC_QC_SMEM_H__ +#define __SEC_QC_SMEM_H__ + +#define __SEC_QC_SMEM_LPDDR_INDIRECT +#include "sec_qc_smem_lpddr.h" +#undef __SEC_QC_SMEM_LPDDR_INDIRECT + +#define SMEM_ID_VENDOR0 134 +#define SMEM_ID_VENDOR1 135 +#define SMEM_ID_VENDOR2 136 + +#endif /* __SEC_QC_SMEM_H__ */ diff --git a/include/linux/samsung/debug/qcom/sec_qc_smem_lpddr.h b/include/linux/samsung/debug/qcom/sec_qc_smem_lpddr.h new file mode 100644 index 000000000000..abd54765332c --- /dev/null +++ b/include/linux/samsung/debug/qcom/sec_qc_smem_lpddr.h @@ -0,0 +1,55 @@ +#ifndef __SEC_QC_SMEM_LPDDR_INDIRECT +#warning "samsung/debug/qcom/sec_qc_smem_lpddr.h is included directly." +#error "please include sec_qc_smem.h instead of this file" +#endif + +#ifndef __INDIRECT__SEC_QC_SMEM_LPDDR_H__ +#define __INDIRECT__SEC_QC_SMEM_LPDDR_H__ + +#define NUM_CH 4 +#define NUM_CS 2 +#define NUM_DQ_PCH 2 + +#if IS_ENABLED(CONFIG_SEC_QC_SMEM) +extern uint8_t sec_qc_smem_lpddr_get_revision_id1(void); +extern uint8_t sec_qc_smem_lpddr_get_revision_id2(void); +extern uint8_t sec_qc_smem_lpddr_get_total_density(void); +extern const char *sec_qc_smem_lpddr_get_vendor_name(void); +extern uint32_t sec_qc_smem_lpddr_get_DSF_version(void); +extern uint8_t sec_qc_smem_lpddr_get_rcw_tDQSCK(size_t ch, size_t cs, size_t dq); +extern uint8_t sec_qc_smem_lpddr_get_wr_coarseCDC(size_t ch, size_t cs, size_t dq); +extern uint8_t sec_qc_smem_lpddr_get_wr_fineCDC(size_t ch, size_t cs, size_t dq); +extern uint8_t sec_qc_smem_lpddr_get_wr_pr_width(size_t ch, size_t cs, size_t dq); +extern uint8_t sec_qc_smem_lpddr_get_wr_min_eye_height(size_t ch, size_t cs); +extern uint8_t sec_qc_smem_lpddr_get_wr_best_vref(size_t ch, size_t cs); +extern uint8_t sec_qc_smem_lpddr_get_wr_vmax_to_vmid(size_t ch, size_t cs, size_t dq); +extern uint8_t sec_qc_smem_lpddr_get_wr_vmid_to_vmin(size_t ch, size_t cs, size_t dq); +extern uint8_t sec_qc_smem_lpddr_get_dqs_dcc_adj(size_t ch, size_t dq); +extern uint8_t sec_qc_smem_lpddr_get_rd_pr_width(size_t ch, size_t cs, size_t dq); +extern uint8_t sec_qc_smem_lpddr_get_rd_min_eye_height(size_t ch, size_t cs); +extern uint8_t sec_qc_smem_lpddr_get_rd_best_vref(size_t ch, size_t cs, size_t dq); +extern uint8_t sec_qc_smem_lpddr_get_dq_dcc_abs(size_t ch, size_t cs, size_t dq); +extern uint8_t sec_qc_smem_lpddr_get_small_eye_detected(void); +#else +static inline uint8_t sec_qc_smem_lpddr_get_revision_id1(void) { return -ENODEV; } +static inline uint8_t sec_qc_smem_lpddr_get_revision_id2(void) { return -ENODEV; } +static inline uint8_t sec_qc_smem_lpddr_get_total_density(void) { return -ENODEV; } +static inline const char *sec_qc_smem_lpddr_get_vendor_name(void) { return ERR_PTR(-ENODEV); } +static inline uint32_t sec_qc_smem_lpddr_get_DSF_version(void) { return -ENODEV; } +static inline uint8_t sec_qc_smem_lpddr_get_rcw_tDQSCK(size_t ch, size_t cs, size_t dq) { return -ENODEV; } +static inline uint8_t sec_qc_smem_lpddr_get_wr_coarseCDC(size_t ch, size_t cs, size_t dq) { return -ENODEV; } +static inline uint8_t sec_qc_smem_lpddr_get_wr_fineCDC(size_t ch, size_t cs, size_t dq) { return -ENODEV; } +static inline uint8_t sec_qc_smem_lpddr_get_wr_pr_width(size_t ch, size_t cs, size_t dq) { return -ENODEV; } +static inline uint8_t sec_qc_smem_lpddr_get_wr_min_eye_height(size_t ch, size_t cs) { return -ENODEV; } +static inline uint8_t sec_qc_smem_lpddr_get_wr_best_vref(size_t ch, size_t cs) { return -ENODEV; } +static inline uint8_t sec_qc_smem_lpddr_get_wr_vmax_to_vmid(size_t ch, size_t cs, size_t dq) { return -ENODEV; } +static inline uint8_t sec_qc_smem_lpddr_get_wr_vmid_to_vmin(size_t ch, size_t cs, size_t dq) { return -ENODEV; } +static inline uint8_t sec_qc_smem_lpddr_get_dqs_dcc_adj(size_t ch, size_t dq) { return -ENODEV; } +static inline uint8_t sec_qc_smem_lpddr_get_rd_pr_width(size_t ch, size_t cs, size_t dq) { return -ENODEV; } +static inline uint8_t sec_qc_smem_lpddr_get_rd_min_eye_height(size_t ch, size_t cs) { return -ENODEV; } +static inline uint8_t sec_qc_smem_lpddr_get_rd_best_vref(size_t ch, size_t cs, size_t dq) { return -ENODEV; } +static inline uint8_t sec_qc_smem_lpddr_get_dq_dcc_abs(size_t ch, size_t cs, size_t dq) { return -ENODEV; } +static inline uint8_t sec_qc_smem_lpddr_get_small_eye_detected(void) { return -ENODEV; } +#endif + +#endif /* __INDIRECT__SEC_QC_SMEM_LPDDR_H__ */ diff --git a/include/linux/samsung/debug/qcom/sec_qc_summary.h b/include/linux/samsung/debug/qcom/sec_qc_summary.h new file mode 100644 index 000000000000..a34291c7de63 --- /dev/null +++ b/include/linux/samsung/debug/qcom/sec_qc_summary.h @@ -0,0 +1,21 @@ +#ifndef __SEC_QC_SUMMARY_H__ +#define __SEC_QC_SUMMARY_H__ + +#include "sec_qc_summary_type.h" + +#define SEC_DEBUG_SUMMARY_MAGIC0 0xFFFFFFFF +#define SEC_DEBUG_SUMMARY_MAGIC1 0x05ECDEB6 +#define SEC_DEBUG_SUMMARY_MAGIC2 0x14F014F0 +/* high word : major version + * low word : minor version + * minor version changes should not affect the Boot Loader behavior + */ +#define SEC_DEBUG_SUMMARY_MAGIC3 0x00060000 + +#if IS_ENABLED(CONFIG_SEC_QC_SUMMARY) +extern struct sec_qc_summary_data_modem *sec_qc_summary_get_modem(void); +#else +static inline struct sec_qc_summary_data_modem *sec_qc_summary_get_modem(void) { return ERR_PTR(-ENODEV); } +#endif + +#endif /* __SEC_QC_SUMMARY_H__ */ diff --git a/include/linux/samsung/debug/qcom/sec_qc_summary_type.h b/include/linux/samsung/debug/qcom/sec_qc_summary_type.h new file mode 100644 index 000000000000..b7be8abc16b7 --- /dev/null +++ b/include/linux/samsung/debug/qcom/sec_qc_summary_type.h @@ -0,0 +1,372 @@ +#ifndef __SEC_QC_SUMMARY_TYPE_H__ +#define __SEC_QC_SUMMARY_TYPE_H__ + +struct core_reg_info { + char name[12]; + uint64_t value; +}; + +struct sec_qc_summary_excp { + char type[16]; + char task[16]; + char file[32]; + int line; + char msg[256]; + struct core_reg_info core_reg[64]; +}; + +struct sec_qc_summary_excp_apss { + char pc_sym[64]; + char lr_sym[64]; + char panic_caller[64]; + char panic_msg[128]; + char thread[32]; + char timeout_subsys[2][16]; +}; + +struct sec_qc_summary_log { + uint64_t idx_paddr; + uint64_t log_paddr; + uint64_t size; +}; + +struct sec_qc_summary_kernel_log { + uint64_t first_idx_paddr; + uint64_t next_idx_paddr; + uint64_t log_paddr; + uint64_t size_paddr; +}; + +struct rgb_bit_info { + unsigned char r_off; + unsigned char r_len; + unsigned char g_off; + unsigned char g_len; + unsigned char b_off; + unsigned char b_len; + unsigned char a_off; + unsigned char a_len; +}; + +struct var_info { + char name[32]; + int sizeof_type; + uint64_t var_paddr; +} __attribute__((aligned(32))); + +struct sec_qc_summary_simple_var_mon { + int idx; + struct var_info var[32]; +}; + +struct sec_qc_summary_fb { + uint64_t fb_paddr; + int xres; + int yres; + int bpp; + struct rgb_bit_info rgb_bitinfo; +}; + +struct sec_qc_summary_sched_log { + uint64_t sched_idx_paddr; + uint64_t sched_buf_paddr; + unsigned int sched_struct_buf_sz; + unsigned int sched_struct_log_sz; + unsigned int sched_array_cnt; + uint64_t irq_idx_paddr; + uint64_t irq_buf_paddr; + unsigned int irq_struct_buf_sz; + unsigned int irq_struct_log_sz; + unsigned int irq_array_cnt; + uint64_t secure_idx_paddr; + uint64_t secure_buf_paddr; + unsigned int secure_struct_buf_sz; + unsigned int secure_struct_log_sz; + unsigned int secure_array_cnt; + uint64_t irq_exit_idx_paddr; + uint64_t irq_exit_buf_paddr; + unsigned int irq_exit_struct_buf_sz; + unsigned int irq_exit_struct_log_sz; + unsigned int irq_exit_array_cnt; + uint64_t msglog_idx_paddr; + uint64_t msglog_buf_paddr; + unsigned int msglog_struct_buf_sz; + unsigned int msglog_struct_log_sz; + unsigned int msglog_array_cnt; +}; + +struct __log_struct_info { + unsigned int buffer_offset; + unsigned int w_off_offset; + unsigned int head_offset; + unsigned int size_offset; + unsigned int size_t_typesize; +}; + +struct __log_data { + uint64_t log_paddr; + uint64_t buffer_paddr; +}; + +struct sec_qc_summary_logger_log_info { + struct __log_struct_info stinfo; + struct __log_data main; + struct __log_data system; + struct __log_data events; + struct __log_data radio; +}; + +struct sec_qc_summary_data { + unsigned int magic; + char name[16]; + char state[16]; + struct sec_qc_summary_log log; + struct sec_qc_summary_excp excp; + struct sec_qc_summary_simple_var_mon var_mon; +}; + +struct sec_qc_summary_data_modem_ext_log { + uint32_t idx; + uint8_t log[0]; +}; + +struct sec_qc_summary_data_modem { + unsigned int magic; + char name[16]; + char state[16]; + struct sec_qc_summary_log log; + struct sec_qc_summary_excp excp; + struct sec_qc_summary_simple_var_mon var_mon; + unsigned int separate_debug; + union { + struct sec_qc_summary_data_modem_ext_log ext_log; + uint8_t __reserved_0[2048]; + }; +}; + +struct sec_qc_summary_avc_log { + uint64_t secavc_idx_paddr; + uint64_t secavc_buf_paddr; + uint64_t secavc_struct_sz; + uint64_t secavc_array_cnt; +}; + +struct sec_qc_summary_ksyms { + uint32_t magic; + uint32_t kallsyms_all; + uint64_t addresses_pa; + uint64_t names_pa; + uint64_t num_syms; + uint64_t token_table_pa; + uint64_t token_index_pa; + uint64_t markers_pa; + struct ksect { + uint64_t sinittext; + uint64_t einittext; + uint64_t stext; + uint64_t etext; + uint64_t end; + } sect; + uint64_t relative_base; + uint64_t offsets_pa; +}; + +struct basic_type_int { + uint64_t pa; /* physical address of the variable */ + uint32_t size; /* size of basic type. eg sizeof(unsigned long) goes here */ + uint32_t count; /* for array types */ +}; + +struct member_type { + uint16_t size; + uint16_t offset; +}; + +typedef struct member_type member_type_int; +typedef struct member_type member_type_long; +typedef struct member_type member_type_longlong; +typedef struct member_type member_type_ptr; +typedef struct member_type member_type_str; + +struct struct_thread_info { + uint32_t struct_size; + member_type_long flags; + member_type_ptr task; + member_type_int cpu; + member_type_long rrk; +}; + +struct struct_task_struct { + uint32_t struct_size; + member_type_long state; + member_type_long exit_state; + member_type_ptr stack; + member_type_int flags; + member_type_int on_cpu; + member_type_int cpu; + member_type_int pid; + member_type_str comm; + member_type_ptr tasks_next; + member_type_ptr thread_group_next; + member_type_long fp; + member_type_long sp; + member_type_long pc; + member_type_long sched_info__pcount; + member_type_longlong sched_info__run_delay; + member_type_longlong sched_info__last_arrival; + member_type_longlong sched_info__last_queued; +}; + +struct ropp_info { + uint64_t magic; + uint64_t master_key_pa; + uint64_t master_key_val; +}; + +struct irq_stack_info { + uint64_t pcpu_stack; /* IRQ_STACK_PTR(0) */ + uint64_t size; /* IRQ_STACK_SIZE */ + uint64_t start_sp; /* IRQ_STACK_START_SP */ +}; + +struct sec_qc_summary_task { + uint64_t stack_size; /* THREAD_SIZE */ + uint64_t start_sp; /* TRHEAD_START_SP */ + struct struct_thread_info ti; + struct struct_task_struct ts; + uint64_t init_task; + struct irq_stack_info irq_stack; + struct ropp_info ropp; +}; + +struct rtb_state_struct { + uint32_t struct_size; + member_type_int rtb_phys; + member_type_int nentries; + member_type_int size; + member_type_int enabled; + member_type_int initialized; + member_type_int step_size; +}; + +struct rtb_entry_struct { + uint32_t struct_size; + member_type_int log_type; + member_type_int idx; + member_type_int caller; + member_type_int data; + member_type_int timestamp; + member_type_int cycle_count; +}; + +struct sec_qc_summary_iolog { + uint64_t rtb_state_pa; + struct rtb_state_struct rtb_state; + struct rtb_entry_struct rtb_entry; + uint64_t rtb_pcpu_idx_pa; +}; + +struct sec_qc_summary_kconst { + uint64_t nr_cpus; + struct basic_type_int per_cpu_offset; + uint64_t phys_offset; + uint64_t page_offset; + uint64_t va_bits; + uint64_t kimage_vaddr; + uint64_t kimage_voffset; + uint64_t swapper_pg_dir_paddr; + int vmap_stack; +}; + + +struct sec_qc_summary_msm_dump_info { + uint64_t cpu_data_paddr; + uint64_t cpu_buf_paddr; + uint32_t cpu_ctx_size; //2048 + uint32_t offset; //0x10 + + //this_cpu_offset = cpu_ctx_size*cpu+offset + // x0 = *((unsigned long long *)(cpu_buf_paddr + this_cpu_offset) + 0x0) + // x1 = *((unsigned long long *)(cpu_buf_paddr + this_cpu_offset) + 0x1) + // ... +}; + +struct sec_qc_summary_cpu_context { + uint64_t sec_debug_core_reg_paddr; + struct sec_qc_summary_msm_dump_info msm_dump_info; +}; + +struct sec_qc_summary_aplpm { + uint64_t p_cci; + uint32_t num_clusters; + uint64_t p_cluster; + uint32_t dstate_offset; + uint64_t p_runqueues; + uint32_t cstate_offset; +}; + +struct sec_qc_summary_coreinfo { + uint64_t coreinfo_data; + uint64_t coreinfo_size; + uint64_t coreinfo_note; +}; + +struct sec_qc_summary_data_apss { + char name[16]; + char state[16]; + char mdmerr_info[128]; + int nr_cpus; + struct sec_qc_summary_kernel_log log; + struct sec_qc_summary_excp_apss excp; + struct sec_qc_summary_simple_var_mon var_mon; + struct sec_qc_summary_simple_var_mon info_mon; + struct sec_qc_summary_fb fb_info; + struct sec_qc_summary_sched_log sched_log; + struct sec_qc_summary_logger_log_info logger_log; + struct sec_qc_summary_avc_log avc_log; + union { + struct msm_dump_data ** tz_core_dump; + uint64_t _tz_core_dump; + }; + struct sec_qc_summary_ksyms ksyms; + struct sec_qc_summary_kconst kconst; + struct sec_qc_summary_iolog iolog; + struct sec_qc_summary_cpu_context cpu_reg; + struct sec_qc_summary_task task; + struct sec_qc_summary_aplpm aplpm; + uint64_t msm_memdump_paddr; + uint64_t dump_sink_paddr; + struct sec_qc_summary_coreinfo coreinfo; +}; + +struct sec_qc_summary_private { + struct sec_qc_summary_data_apss apss; + struct sec_qc_summary_data rpm; + struct sec_qc_summary_data_modem modem; + struct sec_qc_summary_data dsps; +}; + +struct sec_qc_summary { + unsigned int magic[4]; + union { + struct sec_qc_summary_data_apss *apss; + uint64_t _apss; + }; + union { + struct sec_qc_summary_data *rpm; + uint64_t _rpm; + }; + union { + struct sec_qc_summary_data_modem *modem; + uint64_t _modem; + }; + union { + struct sec_qc_summary_data *dsps; + uint64_t _dsps; + }; + uint64_t secure_app_start_addr; + uint64_t secure_app_size; + struct sec_qc_summary_private priv; +}; + +#endif /* __SEC_QC_SUMMARY_TYPE_H__ */ diff --git a/include/linux/samsung/debug/qcom/sec_qc_upload_cause.h b/include/linux/samsung/debug/qcom/sec_qc_upload_cause.h new file mode 100644 index 000000000000..40f2214f5f16 --- /dev/null +++ b/include/linux/samsung/debug/qcom/sec_qc_upload_cause.h @@ -0,0 +1,105 @@ +#ifndef __SEC_QC_UPLOAD_CAUSE_H__ +#define __SEC_QC_UPLOAD_CAUSE_H__ + +enum { + UPLOAD_CAUSE_INIT = 0xCAFEBABE, +/* ++ KP : 0xC8xx_xxxx ++ */ + UPLOAD_CAUSE_KERNEL_PANIC = 0xC8000000, + UPLOAD_CAUSE_USER_FAULT, + UPLOAD_CAUSE_HSIC_DISCONNECTED, + UPLOAD_CAUSE_BUS_HANG, + UPLOAD_CAUSE_PF_WD_BITE, + UPLOAD_CAUSE_PF_WD_INIT_FAIL, + UPLOAD_CAUSE_PF_WD_RESTART_FAIL, + UPLOAD_CAUSE_PF_WD_KICK_FAIL, +/* ++ SubSystem : 0xC8_5353_xx ++ */ + UPLOAD_CAUSE_SS_START = 0xC8535300, + UPLOAD_CAUSE_CP_ERROR_FATAL = UPLOAD_CAUSE_SS_START, + UPLOAD_CAUSE_MDM_ERROR_FATAL, + UPLOAD_CAUSE_MDM_CRITICAL_FATAL, + UPLOAD_CAUSE_MODEM_RST_ERR, + UPLOAD_CAUSE_ADSP_ERROR_FATAL, + UPLOAD_CAUSE_SLPI_ERROR_FATAL, /* 5 */ + UPLOAD_CAUSE_SPSS_ERROR_FATAL, + UPLOAD_CAUSE_NPU_ERROR_FATAL, + UPLOAD_CAUSE_CDSP_ERROR_FATAL, + UPLOAD_CAUSE_SUBSYS_IF_TIMEOUT, + UPLOAD_CAUSE_RIVA_RST_ERR, /* A */ + UPLOAD_CAUSE_LPASS_RST_ERR, + UPLOAD_CAUSE_DSPS_RST_ERR, + UPLOAD_CAUSE_PERIPHERAL_ERR, + UPLOAD_CAUSE_SS_END = UPLOAD_CAUSE_PERIPHERAL_ERR, +/* -- SubSystem : 0xC8_5353_xx -- */ +/* ++ Quest : 0xC8_5153_xx ++ */ + UPLOAD_CAUSE_QUEST_START = 0xC8515300, + UPLOAD_CAUSE_QUEST_CRYPTO = UPLOAD_CAUSE_QUEST_START, + UPLOAD_CAUSE_QUEST_ICACHE, + UPLOAD_CAUSE_QUEST_CACHECOHERENCY, + UPLOAD_CAUSE_QUEST_SUSPEND, + UPLOAD_CAUSE_QUEST_VDDMIN, + UPLOAD_CAUSE_QUEST_QMESADDR, /* 5 */ + UPLOAD_CAUSE_QUEST_QMESACACHE, + UPLOAD_CAUSE_QUEST_PMIC, + UPLOAD_CAUSE_QUEST_UFS, + UPLOAD_CAUSE_QUEST_SDCARD, + UPLOAD_CAUSE_QUEST_SENSOR, /* A */ + UPLOAD_CAUSE_QUEST_SENSORPROBE, + UPLOAD_CAUSE_QUEST_NATURESCENE, + UPLOAD_CAUSE_QUEST_A75G, + UPLOAD_CAUSE_QUEST_Q65G, + UPLOAD_CAUSE_QUEST_THERMAL, /* F */ + UPLOAD_CAUSE_QUEST_QDAF_FAIL, /* 10 */ + UPLOAD_CAUSE_QUEST_FAIL, + UPLOAD_CAUSE_QUEST_DDR_TEST_MAIN, + UPLOAD_CAUSE_QUEST_DDR_TEST_CAL, + UPLOAD_CAUSE_QUEST_DDR_TEST_SMD, + UPLOAD_CAUSE_SOD_RESULT, + UPLOAD_CAUSE_QUEST_ZIP_UNZIP, + UPLOAD_CAUSE_DRAM_SCAN, + UPLOAD_CAUSE_QUEST_AOSSTHERMALDIFF, + UPLOAD_CAUSE_QUEST_STRESSAPPTEST, + UPLOAD_CAUSE_SUEFI_SWT_UPLOAD, + UPLOAD_CAUSE_QUEFI_SWT_UPLOAD, + UPLOAD_CAUSE_QUEST_END = UPLOAD_CAUSE_QUEFI_SWT_UPLOAD, +/* --Quest : 0xC8_5153_xx -- */ +/* -- KP : 0xC8xx_xxxx -- */ +/* ++ TP ++ */ + UPLOAD_CAUSE_POWER_THERMAL_RESET = 0x54500000, +/* -- TP -- */ +/* ++ DP ++ */ + UPLOAD_CAUSE_NON_SECURE_WDOG_BARK = 0x44500000, +/* -- DP -- */ +/* ++ WP ++ */ + UPLOAD_CAUSE_NON_SECURE_WDOG_BITE = 0x57500000, + UPLOAD_CAUSE_SECURE_WDOG_BITE, +/* -- WP -- */ +/* ++ MP ++ */ +/* Intended reset (MP) 0x4D50_xxxx */ + UPLOAD_CAUSE_MP_START = 0x4D500000, + UPLOAD_CAUSE_POWER_LONG_PRESS = UPLOAD_CAUSE_MP_START, + UPLOAD_CAUSE_FORCED_UPLOAD, + UPLOAD_CAUSE_USER_FORCED_UPLOAD, + UPLOAD_CAUSE_MP_END = UPLOAD_CAUSE_USER_FORCED_UPLOAD, +/* -- MP -- */ +/* ++ PP ++ */ + UPLOAD_CAUSE_EDL_FORCED_UPLOAD = 0x50500000, + UPLOAD_CAUSE_PM_OCP, +/* -- PP -- */ +/* ++ SP -- */ + UPLOAD_CAUSE_SMPL = 0x53500000, +/* -- SP -- */ +}; + +#if IS_ENABLED(CONFIG_SEC_QC_UPLOAD_CAUSE) +extern void sec_qc_upldc_write_cause(unsigned int type); +extern unsigned int sec_qc_upldc_read_cause(void); + +extern void sec_qc_upldc_type_to_cause(unsigned int type, char *cause, size_t len); +#else +static inline void sec_qc_upldc_write_cause(unsigned int type) {} +static inline unsigned int sec_qc_upldc_read_cause(void) { return UPLOAD_CAUSE_INIT; } + +static inline void sec_qc_upldc_type_to_cause(unsigned int type, char *cause, size_t len) {} +#endif + +#endif /* __SEC_QC_UPLOAD_CAUSE_H__ */ diff --git a/include/linux/samsung/debug/qcom/sec_qc_user_reset.h b/include/linux/samsung/debug/qcom/sec_qc_user_reset.h new file mode 100644 index 000000000000..81e1c7bdbfb1 --- /dev/null +++ b/include/linux/samsung/debug/qcom/sec_qc_user_reset.h @@ -0,0 +1,102 @@ +#ifndef __SEC_QC_USER_RESET_H__ +#define __SEC_QC_USER_RESET_H__ + +#include "sec_qc_user_reset_type.h" +#include "sec_qc_dbg_partition_type.h" + +#if IS_ENABLED(CONFIG_SEC_QC_USER_RESET) +extern struct debug_reset_header *sec_qc_user_reset_get_reset_header(void); +extern int sec_qc_get_reset_write_cnt(void); +extern ap_health_t *sec_qc_ap_health_data_read(void); +extern int sec_qc_ap_health_data_write(ap_health_t *data); +extern int sec_qc_ap_health_data_write_delayed(ap_health_t *data); +extern unsigned int sec_qc_reset_reason_get(void); +extern const char *sec_qc_reset_reason_to_str(unsigned int reason); +#else +static inline struct debug_reset_header *sec_qc_user_reset_get_reset_header(void) { return NULL; } +static inline int sec_qc_get_reset_write_cnt(void) { return 0; } +static inline ap_health_t *sec_qc_ap_health_data_read(void) { return NULL; } +static inline int sec_qc_ap_health_data_write(ap_health_t *data) { return 0; } +static inline int sec_qc_ap_health_data_write_delayed(ap_health_t *data) { return 0; } +static inline unsigned int sec_qc_reset_reason_get(void) { return 0xFFEEFFEE; } +static inline const char *sec_qc_reset_reason_to_str(unsigned int reason) { return "NP"; } +#endif + +/* TODO: data type which is not shared with BL */ +struct kryo_arm64_edac_err_ctx { + int cpu; + int block; + int etype; +}; + +enum { + ID_L1_CACHE = 0, + ID_L2_CACHE, + ID_L3_CACHE, + ID_BUS_ERR, +}; + +/* FIXME: KRYO_XX_XX are arleady defined in 'kryo_arm64_edac.c' */ +#define __KRYO_L1_CE 0 +#define __KRYO_L1_UE 1 +#define __KRYO_L2_CE 2 +#define __KRYO_L2_UE 3 +#define __KRYO_L3_CE 4 +#define __KRYO_L3_UE 5 + +static __always_inline int __qc_kryo_arm64_edac_update(ap_health_t *health, + int cpu, int block, int etype) +{ + if (cpu < 0 || cpu >= num_present_cpus()) { + pr_warn("not a valid cpu = %d\n", cpu); + return -EINVAL; + } + + switch (block) { + case ID_L1_CACHE: + if (etype == __KRYO_L1_CE) { + health->cache.edac[cpu][block].ce_cnt++; + health->daily_cache.edac[cpu][block].ce_cnt++; + } else if (etype == __KRYO_L1_UE) { + health->cache.edac[cpu][block].ue_cnt++; + health->daily_cache.edac[cpu][block].ue_cnt++; + } + break; + case ID_L2_CACHE: + if (etype == __KRYO_L2_CE) { + health->cache.edac[cpu][block].ce_cnt++; + health->daily_cache.edac[cpu][block].ce_cnt++; + } else if (etype == __KRYO_L2_UE) { + health->cache.edac[cpu][block].ue_cnt++; + health->daily_cache.edac[cpu][block].ue_cnt++; + } + break; + case ID_L3_CACHE: + if (etype == __KRYO_L3_CE) { + health->cache.edac_l3.ce_cnt++; + health->daily_cache.edac_l3.ce_cnt++; + } else if (etype == __KRYO_L3_UE) { + health->cache.edac_l3.ue_cnt++; + health->daily_cache.edac_l3.ue_cnt++; + } + break; + case ID_BUS_ERR: + health->cache.edac_bus_cnt++; + health->daily_cache.edac_bus_cnt++; + break; + } + + return 0; +} + +static inline int sec_qc_kryo_arm64_edac_update(ap_health_t *health, + const struct kryo_arm64_edac_err_ctx *ctx) +{ + if (!IS_ENABLED(CONFIG_SEC_QC_USER_RESET)) + return 0; + + return __qc_kryo_arm64_edac_update(health, + ctx->cpu, ctx->block, ctx->etype); +} + +#endif // __SEC_QC_USER_RESET_H__ diff --git a/include/linux/samsung/debug/qcom/sec_qc_user_reset_type.h b/include/linux/samsung/debug/qcom/sec_qc_user_reset_type.h new file mode 100644 index 000000000000..336ba6d2ea29 --- /dev/null +++ b/include/linux/samsung/debug/qcom/sec_qc_user_reset_type.h @@ -0,0 +1,334 @@ +#ifndef __SEC_QC_USER_RESET_TYPE_H__ +#define __SEC_QC_USER_RESET_TYPE_H__ + +#include + +enum extra_info_dbg_type { + DBG_0_RESERBED= 0, + DBG_1_UFS_ERR, + DBG_2_RESERVED, + DBG_3_RESERVED, + DBG_4_RESERVED, + DBG_5_RESERVED, + DBG_MAX, +}; + +#define EXINFO_UPLOAD_CAUSE_STR_SIZE 64 +#define EXINFO_RPM_LOG_SIZE 50 +#define EXINFO_TZ_LOG_SIZE 40 +#define EXINFO_HYP_LOG_SIZE 460 + +typedef struct { + unsigned int esr; + char str[52]; + u64 var1; + u64 var2; + u64 pte[6]; +} ex_info_fault_t; + +extern ex_info_fault_t ex_info_fault[CONFIG_SEC_QC_NR_CPUS]; + +typedef struct { + char dev_name[24]; + u32 fsr; + u32 fsynr0; + u32 fsynr1; + unsigned long iova; + unsigned long far; + char mas_name[24]; + u8 cbndx; + phys_addr_t phys_soft; + phys_addr_t phys_atos; + u32 sid; +} ex_info_smmu_t; + +typedef struct { + int reason; + char handler_str[24]; + unsigned int esr; + char esr_str[32]; +} ex_info_badmode_t; + +typedef struct { + u64 ktime; + u32 extc_idx; + u32 upload_cause; + int cpu; + char task_name[TASK_COMM_LEN]; + char bug_buf[64]; + char panic_buf[64]; + ex_info_smmu_t smmu; + ex_info_fault_t fault[CONFIG_SEC_QC_NR_CPUS]; + ex_info_badmode_t badmode; + char pc[64]; + char lr[64]; + char ufs_err[23]; + u32 lpm_state[CONFIG_SEC_QC_NR_CPUS]; + u64 lr_val[CONFIG_SEC_QC_NR_CPUS]; + u64 pc_val[CONFIG_SEC_QC_NR_CPUS]; + u32 pko; + char backtrace[0]; +} _kern_ex_info_t; + +typedef struct { + u32 magic; + u32 reserved; + char str[EXINFO_UPLOAD_CAUSE_STR_SIZE]; +} upload_cause_exinfo_t; + +typedef struct { + u64 nsec; + u32 arg[4]; + char msg[EXINFO_RPM_LOG_SIZE]; +} __rpm_log_t; + +typedef struct { + u32 magic; + u32 ver; + u32 nlog; + __rpm_log_t log[5]; +} _rpm_ex_info_t; + +typedef struct { + _rpm_ex_info_t info; +} rpm_exinfo_t; + +typedef struct { + u8 cpu_status[CONFIG_SEC_QC_NR_CPUS]; + char msg[EXINFO_TZ_LOG_SIZE]; +} tz_exinfo_t; + +typedef struct { + u32 esr; + u32 ear0; + u32 esr_sdi; + u32 ear0_sdi; +} pimem_exinfo_t; + +typedef struct { + s64 cpu[CONFIG_SEC_QC_NR_CPUS]; +} cpu_stuck_exinfo_t ; + +typedef struct { + int s2_fault_counter; + char msg[EXINFO_HYP_LOG_SIZE]; +} hyp_exinfo_t; + +#define EXINFO_UPLOAD_CAUSE_DATA_SIZE sizeof(upload_cause_exinfo_t) +#define EXINFO_RPM_DATA_SIZE sizeof(rpm_exinfo_t) +#define EXINFO_TZ_DATA_SIZE sizeof(tz_exinfo_t) +#define EXINFO_PIMEM_DATA_SIZE sizeof(pimem_exinfo_t) +#define EXINFO_CPU_STUCK_DATA_SIZE sizeof(cpu_stuck_exinfo_t) +#define EXINFO_HYP_DATA_SIZE sizeof(hyp_exinfo_t) +#define EXINFO_SUBSYS_DATA_SIZE (EXINFO_UPLOAD_CAUSE_DATA_SIZE + EXINFO_RPM_DATA_SIZE + EXINFO_TZ_DATA_SIZE + EXINFO_PIMEM_DATA_SIZE + EXINFO_CPU_STUCK_DATA_SIZE + EXINFO_HYP_DATA_SIZE) +#define EXINFO_KERNEL_SPARE_SIZE (2048 - EXINFO_SUBSYS_DATA_SIZE) +#define EXINFO_KERNEL_DEFAULT_SIZE 2048 + +typedef union { + _kern_ex_info_t info; + char ksize[EXINFO_KERNEL_DEFAULT_SIZE + EXINFO_KERNEL_SPARE_SIZE]; +} kern_exinfo_t ; + +#define RPM_EX_INFO_MAGIC 0x584D5052 + +typedef struct { + kern_exinfo_t kern_ex_info; + upload_cause_exinfo_t uc_ex_info; + rpm_exinfo_t rpm_ex_info; + tz_exinfo_t tz_ex_info; + pimem_exinfo_t pimem_info; + cpu_stuck_exinfo_t cpu_stuck_info; + hyp_exinfo_t hyp_ex_info; +} rst_exinfo_t; + +/* rst_exinfo_t sec_debug_reset_ex_info; */ + +#define SEC_DEBUG_EX_INFO_SIZE (sizeof(rst_exinfo_t)) /* 4KB */ + +enum debug_reset_header_state { + DRH_STATE_INIT=0, + DRH_STATE_VALID, + DRH_STATE_INVALID, + DRH_STATE_MAX, +}; + +struct debug_reset_header { + uint32_t magic; + uint32_t write_times; + uint32_t read_times; + uint32_t ap_klog_idx; + uint32_t summary_size; + uint32_t stored_tzlog; + uint32_t fac_write_times; + uint32_t auto_comment_size; + uint32_t reset_history_valid; + uint32_t reset_history_cnt; +}; + +#define TZ_DIAG_LOG_MAGIC 0x747a6461 /* tzda */ + +/* copy from drivers/firmware/qcom/tz_log.c */ +#define TZBSP_AES_256_ENCRYPTED_KEY_SIZE 256 +#define TZBSP_NONCE_LEN 12 +#define TZBSP_TAG_LEN 16 + +enum tz_boot_info_cpu_status_type { + TZ_BOOT_INFO_NONE = 0, + RUNNING, + POWER_COLLAPSED, + WARM_BOOTING, + INVALID_WARM_ENTRY_EXIT_COUNT, + INVALID_WARM_TERM_ENTRY_EXIT_COUNT, +}; +/* + * Log ring buffer position + */ +struct tzdbg_log_pos_t { + uint16_t wrap; + uint16_t offset; +}; + + /* + * Log ring buffer + */ +struct tzdbg_log_t { + struct tzdbg_log_pos_t log_pos; + /* open ended array to the end of the 4K IMEM buffer */ + uint8_t log_buf[]; +}; + +#define TZBSP_DIAG_VERSION_V9_2 0x00090002 + +struct tzdbg_log_pos_v9_2_t { + uint32_t wrap; + uint32_t offset; +}; + +struct tzdbg_log_v9_2_t { + struct tzdbg_log_pos_v9_2_t log_pos; + /* open ended array to the end of the 4K IMEM buffer */ + uint8_t log_buf[]; +}; + +struct tzbsp_encr_info_for_log_chunk_t { + uint32_t size_to_encr; + uint8_t nonce[TZBSP_NONCE_LEN]; + uint8_t tag[TZBSP_TAG_LEN]; +}; + +/* + * Only `ENTIRE_LOG` will be used unless the + * "OEM_tz_num_of_diag_log_chunks_to_encr" devcfg field >= 2. + * If this is true, the diag log will be encrypted in two + * separate chunks: a smaller chunk containing only error + * fatal logs and a bigger "rest of the log" chunk. In this + * case, `ERR_FATAL_LOG_CHUNK` and `BIG_LOG_CHUNK` will be + * used instead of `ENTIRE_LOG`. + */ +enum tzbsp_encr_info_for_log_chunks_idx_t { + BIG_LOG_CHUNK = 0, + ENTIRE_LOG = 1, + ERR_FATAL_LOG_CHUNK = 1, + MAX_NUM_OF_CHUNKS, +}; + +struct tzbsp_encr_info_t { + uint32_t num_of_chunks; + struct tzbsp_encr_info_for_log_chunk_t chunks[MAX_NUM_OF_CHUNKS]; + uint8_t key[TZBSP_AES_256_ENCRYPTED_KEY_SIZE]; +}; + +/* + * Diagnostic Table + * Note: This is the reference data structure for tz diagnostic table + * supporting TZBSP_MAX_CPU_COUNT, the real diagnostic data is directly + * copied into buffer from i/o memory. + */ +struct tzdbg_t { + uint32_t magic_num; + uint32_t version; + /* + * Number of CPU's + */ + uint32_t cpu_count; + /* + * Offset of VMID Table + */ + uint32_t vmid_info_off; + /* + * Offset of Boot Table + */ + uint32_t boot_info_off; + /* + * Offset of Reset info Table + */ + uint32_t reset_info_off; + /* + * Offset of Interrupt info Table + */ + uint32_t int_info_off; + /* + * Ring Buffer Offset + */ + uint32_t ring_off; + /* + * Ring Buffer Length + */ + uint32_t ring_len; + + /* Offset for Wakeup info */ + uint32_t wakeup_info_off; + + union { + /* The elements in below structure have to be used for TZ where + * diag version = TZBSP_DIAG_MINOR_VERSION_V2 + */ + struct { + uint8_t reserve[512]; + + uint32_t num_interrupts; + + uint8_t reserve2[3648]; + + uint8_t key[TZBSP_AES_256_ENCRYPTED_KEY_SIZE]; + + uint8_t nonce[TZBSP_NONCE_LEN]; + + uint8_t tag[TZBSP_TAG_LEN]; + } v9_2; + /* The elements in below structure have to be used for TZ where + * diag version = TZBSP_DIAG_MINOR_VERSION_V21 + */ + struct { + uint32_t encr_info_for_log_off; + + uint8_t reserve[4172]; + + struct tzbsp_encr_info_t encr_info_for_log; + } v9_3; + }; + + /* + * We need at least 2K for the ring buffer + */ + struct tzdbg_log_t ring_buffer; /* TZ Ring Buffer */ +}; + +enum { + USER_UPLOAD_CAUSE_MIN = 1, + USER_UPLOAD_CAUSE_SMPL = USER_UPLOAD_CAUSE_MIN, /* RESET_REASON_SMPL */ + USER_UPLOAD_CAUSE_WTSR, /* RESET_REASON_WTSR */ + USER_UPLOAD_CAUSE_WATCHDOG, /* RESET_REASON_WATCHDOG */ + USER_UPLOAD_CAUSE_PANIC, /* RESET_REASON_PANIC */ + USER_UPLOAD_CAUSE_MANUAL_RESET, /* RESET_REASON_MANUAL_RESET */ + USER_UPLOAD_CAUSE_POWER_RESET, /* RESET_REASON_POWER_RESET */ + USER_UPLOAD_CAUSE_REBOOT, /* RESET_REASON_REBOOT */ + USER_UPLOAD_CAUSE_BOOTLOADER_REBOOT, /* RESET_REASON_BOOTLOADER_REBOOT */ + USER_UPLOAD_CAUSE_POWER_ON, /* RESET_REASON_POWER_ON */ + USER_UPLOAD_CAUSE_THERMAL, /* RESET_REASON_THERMAL_RESET */ + USER_UPLOAD_CAUSE_CP_CRASH, /* RESET_REASON_CP_CRASH_RESET */ + USER_UPLOAD_CAUSE_UNKNOWN, /* RESET_REASON_UNKNOWN */ + USER_UPLOAD_CAUSE_MAX = USER_UPLOAD_CAUSE_UNKNOWN, +}; + +#endif /* __SEC_QC_USER_RESET_TYPE_H__ */ diff --git a/include/linux/samsung/debug/sec_arm64_ap_context.h b/include/linux/samsung/debug/sec_arm64_ap_context.h new file mode 100644 index 000000000000..918d7f87546e --- /dev/null +++ b/include/linux/samsung/debug/sec_arm64_ap_context.h @@ -0,0 +1,47 @@ +#ifndef __SEC_ARM64_AP_CONTEXT_H__ +#define __SEC_ARM64_AP_CONTEXT_H__ + +#include + +enum { + IDX_CORE_EXTRA_SP_EL0 = 0, + IDX_CORE_EXTRA_SP_EL1, + IDX_CORE_EXTRA_ELR_EL1, + IDX_CORE_EXTRA_SPSR_EL1, + IDX_CORE_EXTRA_SP_EL2, + IDX_CORE_EXTRA_ELR_EL2, + IDX_CORE_EXTRA_SPSR_EL2, + /* */ + IDX_CORE_EXTRA_REGS_MAX, +}; + +enum { + IDX_MMU_TTBR0_EL1 = 0, + IDX_MMU_TTBR1_EL1, + IDX_MMU_TCR_EL1, + IDX_MMU_MAIR_EL1, + IDX_MMU_ATCR_EL1, + IDX_MMU_AMAIR_EL1, + IDX_MMU_HSTR_EL2, + IDX_MMU_HACR_EL2, + IDX_MMU_TTBR0_EL2, + IDX_MMU_VTTBR_EL2, + IDX_MMU_TCR_EL2, + IDX_MMU_VTCR_EL2, + IDX_MMU_MAIR_EL2, + IDX_MMU_ATCR_EL2, + IDX_MMU_TTBR0_EL3, + IDX_MMU_MAIR_EL3, + IDX_MMU_ATCR_EL3, + /* */ + IDX_MMU_REG_MAX, +}; + +struct sec_arm64_ap_context { + struct pt_regs core_regs; + uint64_t core_extra_regs[IDX_CORE_EXTRA_REGS_MAX]; + uint64_t mmu_regs[IDX_MMU_REG_MAX]; + bool used; +}; + +#endif /* __SEC_ARM64_AP_CONTEXT_H__ */ diff --git a/include/linux/samsung/debug/sec_boot_stat.h b/include/linux/samsung/debug/sec_boot_stat.h new file mode 100644 index 000000000000..41b50380cfa4 --- /dev/null +++ b/include/linux/samsung/debug/sec_boot_stat.h @@ -0,0 +1,23 @@ +#ifndef __SEC_BOOT_STATH_H__ +#define __SEC_BOOT_STATH_H__ + +#include + +struct sec_boot_stat_soc_operations { + unsigned long long (*ktime_to_time)(unsigned long long ktime); + void (*show_on_boot_stat)(struct seq_file *m); + void (*show_on_enh_boot_time)(struct seq_file *m); + void (*show_on_enh_boot_stat)(struct seq_file *m); +}; + +#if IS_ENABLED(CONFIG_SEC_BOOT_STAT) +extern void sec_boot_stat_add(const char *log); +extern int sec_boot_stat_register_soc_ops(struct sec_boot_stat_soc_operations *soc_ops); +extern int sec_boot_stat_unregister_soc_ops(struct sec_boot_stat_soc_operations *soc_ops); +#else +static inline void sec_boot_stat_add(const char *log) {} +static inline int sec_boot_stat_register_soc_ops(struct sec_boot_stat_soc_operations *soc_ops) { return -ENODEV; } +static inline int sec_boot_stat_unregister_soc_ops(struct sec_boot_stat_soc_operations *soc_ops) { return -ENODEV; } +#endif + +#endif /* __SEC_BOOT_STATH_H__ */ diff --git a/include/linux/samsung/debug/sec_crashkey.h b/include/linux/samsung/debug/sec_crashkey.h new file mode 100644 index 000000000000..4ae99faa824c --- /dev/null +++ b/include/linux/samsung/debug/sec_crashkey.h @@ -0,0 +1,12 @@ +#ifndef __SEC_CRASHKEY_H__ +#define __SEC_CRASHKEY_H__ + +#if IS_ENABLED(CONFIG_SEC_CRASHKEY) +extern int sec_crashkey_add_preparing_panic(struct notifier_block *nb, const char *name); +extern int sec_crashkey_del_preparing_panic(struct notifier_block *nb, const char *name); +#else +static inline int sec_crashkey_add_preparing_panic(struct notifier_block *nb, const char *name) { return -ENODEV; } +static inline int sec_crashkey_del_preparing_panic(struct notifier_block *nb, const char *name) { return -ENODEV; } +#endif + +#endif /* __SEC_CRASHKEY_H__ */ diff --git a/include/linux/samsung/debug/sec_crashkey_long.h b/include/linux/samsung/debug/sec_crashkey_long.h new file mode 100644 index 000000000000..4e11c486741f --- /dev/null +++ b/include/linux/samsung/debug/sec_crashkey_long.h @@ -0,0 +1,22 @@ +#ifndef __SEC_CRASHKEY_LONG_H__ +#define __SEC_CRASHKEY_LONG_H__ + +#define SEC_CRASHKEY_LONG_NOTIFY_TYPE_MATCHED 0 +#define SEC_CRASHKEY_LONG_NOTIFY_TYPE_UNMATCHED 1 +#define SEC_CRASHKEY_LONG_NOTIFY_TYPE_EXPIRED 2 + +#if IS_ENABLED(CONFIG_SEC_CRASHKEY_LONG) +extern int sec_crashkey_long_add_preparing_panic(struct notifier_block *nb); +extern int sec_crashkey_long_del_preparing_panic(struct notifier_block *nb); + +extern int sec_crashkey_long_connect_to_input_evnet(void); +extern int sec_crashkey_long_disconnect_from_input_event(void); +#else +static inline int sec_crashkey_long_add_preparing_panic(struct notifier_block *nb) { return -ENODEV; } +static inline int sec_crashkey_long_del_preparing_panic(struct notifier_block *nb) { return -ENODEV; } + +static inline int sec_crashkey_long_connect_to_input_evnet(void) { return -ENODEV; } +static inline int sec_crashkey_long_disconnect_from_input_event(void) { return -ENODEV; } +#endif + +#endif /* __SEC_CRASHKEY_LONG_H__ */ diff --git a/include/linux/samsung/debug/sec_debug.h b/include/linux/samsung/debug/sec_debug.h new file mode 100644 index 000000000000..2fed40020aa6 --- /dev/null +++ b/include/linux/samsung/debug/sec_debug.h @@ -0,0 +1,19 @@ +#ifndef __SEC_DEBUG_H__ +#define __SEC_DEBUG_H__ + +#include + +#define SEC_DEBUG_CP_DEBUG_ON 0x5500 +#define SEC_DEBUG_CP_DEBUG_OFF 0x55ff + +#if IS_ENABLED(CONFIG_SEC_DEBUG) +extern unsigned int sec_debug_level(void); +extern bool sec_debug_is_enabled(void); +extern phys_addr_t sec_debug_get_dump_sink_phys(void); +#else +static inline unsigned int sec_debug_level(void) { return SEC_DEBUG_LEVEL_LOW; } +static inline bool sec_debug_is_enabled(void) { return false; } +static inline phys_addr_t sec_debug_get_dump_sink_phys(void) { return 0; } +#endif + +#endif /* __SEC_DEBUG_H__ */ diff --git a/include/linux/samsung/debug/sec_debug_region.h b/include/linux/samsung/debug/sec_debug_region.h new file mode 100644 index 000000000000..23fda8b969d6 --- /dev/null +++ b/include/linux/samsung/debug/sec_debug_region.h @@ -0,0 +1,32 @@ +#ifndef __SEC_DEBUG_REGION_H__ +#define __SEC_DEBUG_REGION_H__ + +/* crc32 <(echo -n "SEC_DBG_REGION_ROOT_MAGIC") | tr '[a-z]' '[A-Z]' */ +#define SEC_DBG_REGION_ROOT_MAGIC 0xC317F7EF +/* crc32 <(echo -n "SEC_DBG_REGION_CLIENT_MAGIC") | tr '[a-z]' '[A-Z]' */ +#define SEC_DBG_REGION_CLIENT_MAGIC 0x3EF44F24 + +struct sec_dbg_region_client { + struct list_head list; + uint32_t magic; + uint32_t unique_id; + phys_addr_t __client; /* physical address of myself */ + phys_addr_t phys; + size_t size; + unsigned long virt; + const char *name; +} __packed __aligned(1); + +#if IS_ENABLED(CONFIG_SEC_DEBUG_REGION) +extern struct sec_dbg_region_client *sec_dbg_region_alloc(uint32_t unique_id, size_t size); +extern int sec_dbg_region_free(struct sec_dbg_region_client *client); + +extern const struct sec_dbg_region_client *sec_dbg_region_find(uint32_t unique_id); +#else +static inline struct sec_dbg_region_client *sec_dbg_region_alloc(uint32_t unique_id, size_t size) { return ERR_PTR(-ENODEV); } +static inline int sec_dbg_region_free(struct sec_dbg_region_client *client) { return -ENODEV; } + +static inline const struct sec_dbg_region_client *sec_dbg_region_find(uint32_t unique_id) { return ERR_PTR(-ENODEV); } +#endif + +#endif /* __SEC_DEBUG_REGION_H__ */ diff --git a/include/linux/samsung/debug/sec_force_err.h b/include/linux/samsung/debug/sec_force_err.h new file mode 100644 index 000000000000..87d16f6fea8c --- /dev/null +++ b/include/linux/samsung/debug/sec_force_err.h @@ -0,0 +1,22 @@ +#ifndef __SEC_FORCE_ERR_H__ +#define __SEC_FORCE_ERR_H__ + +struct force_err_handle { + struct hlist_node node; + const char *val; + const char *msg; + void (*func)(struct force_err_handle *); +}; + +#define FORCE_ERR_HANDLE(__val, __msg, __func) \ + { .val = __val, .msg = __msg, .func = __func } + +#if IS_ENABLED(CONFIG_SEC_FORCE_ERR) +extern int sec_force_err_add_custom_handle(struct force_err_handle *h); +extern int sec_force_err_del_custom_handle(struct force_err_handle *h); +#else +static inline int sec_force_err_add_custom_handle(struct force_err_handle *h) { return -ENODEV; } +static inline int sec_force_err_del_custom_handle(struct force_err_handle *h) { return -ENODEV; } +#endif + +#endif /* __SEC_FORCE_ERR_H__ */ diff --git a/include/linux/samsung/debug/sec_log_buf.h b/include/linux/samsung/debug/sec_log_buf.h new file mode 100644 index 000000000000..1aaa5c59bd0f --- /dev/null +++ b/include/linux/samsung/debug/sec_log_buf.h @@ -0,0 +1,32 @@ +#ifndef __SEC_LOG_BUF_H__ +#define __SEC_LOG_BUF_H__ + +#include + +#include + +#define SEC_LOG_MAGIC 0x4d474f4c /* "LOGM" */ + +struct sec_log_buf_head { + uint32_t boot_cnt; + uint32_t magic; + uint32_t idx; + uint32_t prev_idx; + char buf[]; +}; + +#if IS_ENABLED(CONFIG_SEC_LOG_BUF) +extern void sec_log_buf_store_on_vprintk_emit(void); +extern const struct sec_log_buf_head *sec_log_buf_get_header(void); +extern ssize_t sec_log_buf_get_buf_size(void); +extern int sec_log_buf_register_sync_handler(struct notifier_block *nb); +extern int sec_log_buf_unregister_sync_handler(struct notifier_block *nb); +#else +static inline void sec_log_buf_store_on_vprintk_emit(void) {} +static inline struct sec_log_buf_head *sec_log_buf_get_header(void) { return ERR_PTR(-ENODEV); } +static inline ssize_t sec_log_buf_get_buf_size(void) { return -ENODEV; } +static inline int sec_log_buf_register_sync_handler(struct notifier_block *nb) { return -ENODEV; } +static inline int sec_log_buf_unregister_sync_handler(struct notifier_block *nb) { return -ENODEV; } +#endif + +#endif /* __SEC_LOG_BUF_H__ */ diff --git a/include/linux/samsung/debug/sec_reboot_cmd.h b/include/linux/samsung/debug/sec_reboot_cmd.h new file mode 100644 index 000000000000..27a8cad48824 --- /dev/null +++ b/include/linux/samsung/debug/sec_reboot_cmd.h @@ -0,0 +1,47 @@ +#ifndef __SEC_REBOOT_CMD_H__ +#define __SEC_REBOOT_CMD_H__ + +#include + +enum sec_rbcmd_stage { + SEC_RBCMD_STAGE_1ST = 0, + SEC_RBCMD_STAGE_REBOOT_NOTIFIER = 0, + SEC_RBCMD_STAGE_RESTART_HANDLER, + SEC_RBCMD_STAGE_MAX, +}; + +/* similar to NOTIFY_flags */ +#define SEC_RBCMD_HANDLE_STOP_MASK 0x80000000 /* Don't call further */ +#define SEC_RBCMD_HANDLE_ONESHOT_MASK 0x40000000 /* Don't call further during handling multi cmd */ +#define SEC_RBCMD_HANDLE_DONE 0x00000000 /* Don't care */ +#define __SEC_RBCMD_HANDLE_OK 0x00000001 /* OK. It's handled by me. */ +#define SEC_RBCMD_HANDLE_OK (SEC_RBCMD_HANDLE_STOP_MASK | __SEC_RBCMD_HANDLE_OK) +#define __SEC_RBCMD_HANDLE_BAD 0x00000002 /* Bad. It's mine but some error occur. */ +#define SEC_RBCMD_HANDLE_BAD (SEC_RBCMD_HANDLE_STOP_MASK | __SEC_RBCMD_HANDLE_BAD) + +struct sec_reboot_param { + const char *cmd; + unsigned long mode; +}; + +struct sec_reboot_cmd { + struct list_head list; + const char *cmd; + int (*func)(const struct sec_reboot_cmd *, struct sec_reboot_param *, bool); +}; + +#if IS_ENABLED(CONFIG_SEC_REBOOT_CMD) +extern int sec_rbcmd_add_cmd(enum sec_rbcmd_stage s, struct sec_reboot_cmd *rc); +extern int sec_rbcmd_del_cmd(enum sec_rbcmd_stage s, struct sec_reboot_cmd *rc); + +extern int sec_rbcmd_set_default_cmd(enum sec_rbcmd_stage s, struct sec_reboot_cmd *rc); +extern int sec_rbcmd_unset_default_cmd(enum sec_rbcmd_stage s, struct sec_reboot_cmd *rc); +#else +static inline int sec_rbcmd_add_cmd(enum sec_rbcmd_stage s, struct sec_reboot_cmd *rc) { return -ENODEV; } +static inline int sec_rbcmd_del_cmd(enum sec_rbcmd_stage s, struct sec_reboot_cmd *rc) { return -ENODEV; } + +static inline int sec_rbcmd_set_default_cmd(enum sec_rbcmd_stage s, struct sec_reboot_cmd *rc) { return -ENODEV; } +static inline int sec_rbcmd_unset_default_cmd(enum sec_rbcmd_stage s, struct sec_reboot_cmd *rc) { return -ENODEV; } +#endif + +#endif /* __SEC_REBOOT_CMD_H__ */ diff --git a/include/linux/samsung/debug/sec_riscv64_ap_context.h b/include/linux/samsung/debug/sec_riscv64_ap_context.h new file mode 100644 index 000000000000..0e5690406c92 --- /dev/null +++ b/include/linux/samsung/debug/sec_riscv64_ap_context.h @@ -0,0 +1,18 @@ +#ifndef __SEC_RISCV64_AP_CONTEXT_H__ +#define __SEC_RISCV64_AP_CONTEXT_H__ + +#include + +enum { + ID_RV64_CSR_SATP = 0, + /* */ + IDX_RV64_NR_CSR_REG, +}; + +struct sec_riscv64_ap_context { + struct pt_regs core_regs; + uint64_t csr_regs[IDX_RV64_NR_CSR_REG]; + bool used; +}; + +#endif /* __SEC_RISCV64_AP_CONTEXT_H__ */ diff --git a/include/linux/samsung/debug/sec_upload_cause.h b/include/linux/samsung/debug/sec_upload_cause.h new file mode 100644 index 000000000000..71896484286a --- /dev/null +++ b/include/linux/samsung/debug/sec_upload_cause.h @@ -0,0 +1,37 @@ +#ifndef __SEC_UPLOAD_CAUSE_H__ +#define __SEC_UPLOAD_CAUSE_H__ + +/* similar to NOTIFY_flags */ +#define SEC_UPLOAD_CAUSE_HANDLE_STOP_MASK 0x80000000 /* Don't call further */ +#define SEC_UPLOAD_CAUSE_HANDLE_DONE 0x00000000 /* Don't care */ +#define __SEC_UPLOAD_CAUSE_HANDLE_OK 0x00000001 /* OK. It's handled by me. */ +#define SEC_UPLOAD_CAUSE_HANDLE_OK (SEC_UPLOAD_CAUSE_HANDLE_STOP_MASK | __SEC_UPLOAD_CAUSE_HANDLE_OK) +#define __SEC_UPLOAD_CAUSE_HANDLE_BAD 0x00000002 /* Bad. It's mine but some error occur. */ +#define SEC_UPLOAD_CAUSE_HANDLE_BAD (SEC_UPLOAD_CAUSE_HANDLE_STOP_MASK | __SEC_UPLOAD_CAUSE_HANDLE_BAD) + +struct sec_upload_cause { + struct list_head list; + const char *cause; + unsigned int type; + int (*func)(const struct sec_upload_cause *, const char *); +}; + +#if IS_ENABLED(CONFIG_SEC_UPLOAD_CAUSE) +extern int sec_upldc_add_cause(struct sec_upload_cause *uc); +extern int sec_upldc_del_cause(struct sec_upload_cause *uc); + +extern int sec_upldc_set_default_cause(struct sec_upload_cause *uc); +extern int sec_upldc_unset_default_cause(struct sec_upload_cause *uc); + +extern void sec_upldc_type_to_cause(unsigned int type, char *cause, size_t len); +#else +static inline int sec_upldc_add_cause(struct sec_upload_cause *uc) { return -ENODEV; } +static inline int sec_upldc_del_cause(struct sec_upload_cause *uc) { return -ENODEV; } + +static inline int sec_upldc_set_default_cause(struct sec_upload_cause *uc) { return -ENODEV; } +static inline int sec_upldc_unset_default_cause(struct sec_upload_cause *uc) { return -ENODEV; } + +static inline void sec_upldc_type_to_cause(unsigned int type, char *cause, size_t len) {} +#endif + +#endif /* #define __SEC_UPLOAD_CAUSE_H__ */ diff --git a/include/linux/samsung/of_early_populate.h b/include/linux/samsung/of_early_populate.h new file mode 100644 index 000000000000..d37278d11d50 --- /dev/null +++ b/include/linux/samsung/of_early_populate.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2020-2021 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#ifndef __OF_EARLY_POPULATE_H__ +#define __OF_EARLY_POPULATE_H__ + +/* FIXME: This is tricky! */ +/* NOTE: A helper function to enable the device before + * 'of_platform_default_populate' is called. + * The idea is inspired from 'of_platform_default_populate_init' that + * this function calls a 'of_platform_device_create' before calling + * 'of_platform_default_populate_init' to make some devices earlier. + */ +static __always_inline int __of_platform_early_populate_init( + const struct of_device_id *matches) +{ +#if IS_BUILTIN(CONFIG_SEC_DEBUG) + struct device_node *np; + struct platform_device *pdev; + + np = of_find_matching_node(NULL, matches); + if (!np) + return -EINVAL; + + pdev = of_platform_device_create(np, NULL, NULL); + if (!pdev) + return -ENODEV; +#endif + + return 0; +} + +#endif /* __OF_EARLY_POPULATE_H__ */ diff --git a/include/linux/samsung/sec_kunit.h b/include/linux/samsung/sec_kunit.h new file mode 100644 index 000000000000..677c82edaf9d --- /dev/null +++ b/include/linux/samsung/sec_kunit.h @@ -0,0 +1,38 @@ +#ifndef __SEC_KUNIT_H__ +#define __SEC_KUNIT_H__ + +#include +#include + +#if IS_ENABLED(CONFIG_KUNIT) +#define __ss_static +#define __ss_inline +#define __ss_always_inline + +static inline int sec_kunit_init_miscdevice(struct miscdevice *misc, const char *name) +{ + static struct file_operations dummy_fops = { + .owner = THIS_MODULE, + }; + + misc->minor = MISC_DYNAMIC_MINOR; + misc->name = name; + misc->fops = &dummy_fops; + + return misc_register(misc); +} + +static inline void sec_kunit_exit_miscdevice(struct miscdevice *misc) +{ + misc_deregister(misc); +} +#else +#define __ss_static static +#define __ss_inline inline +#define __ss_always_inline __always_inline + +static inline int sec_kunit_init_miscdevice(struct miscdevice *misc, const char *name) { return -EINVAL; } +static inline void sec_kunit_exit_miscdevice(struct miscdevice *misc) {} +#endif + +#endif /* __SEC_KUNIT_H__ */ diff --git a/include/linux/samsung/sec_of.h b/include/linux/samsung/sec_of.h new file mode 100644 index 000000000000..715b61b121ca --- /dev/null +++ b/include/linux/samsung/sec_of.h @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * COPYRIGHT(C) 2022 Samsung Electronics Co., Ltd. All Right Reserved. + */ + +#ifndef __SEC_OF_H__ +#define __SEC_OF_H__ + +#include +#include + +#ifdef CONFIG_OF +static inline int sec_of_parse_reg_prop(struct device_node *np, + phys_addr_t *base, phys_addr_t *size) +{ + const __be32 *cell; + int address_cells; + int size_cells; + + cell = of_get_property(np, "reg", NULL); + if (IS_ERR_OR_NULL(cell)) + return -EINVAL; + + /* NOTE: 'of_n_addr_cells' and 'of_n_size_cells' always + * search from the parent node except 'root'. + * To use '#address-cells' and '#size-cells' of the current node, + * these 'if' statements are rquired. + */ + if (of_property_read_u32(np, "#address-cells", &address_cells)) + address_cells = of_n_addr_cells(np); + + if (of_property_read_u32(np, "#size-cells", &size_cells)) + size_cells = of_n_size_cells(np); + + *base = (phys_addr_t)of_read_number(cell, address_cells); + *size = (phys_addr_t)of_read_number(cell + address_cells, size_cells); + + return 0; +} + +static inline int sec_of_test_debug_level(const struct device_node *np, + const char *node_name, unsigned int sec_dbg_level) +{ + int nr_dbg_level; + int i; + + nr_dbg_level = (int)of_property_count_u32_elems(np, node_name); + if (nr_dbg_level <= 0) + return -ENOENT; + + for (i = 0; i < nr_dbg_level; i++) { + u32 dbg_level; + + of_property_read_u32_index(np, node_name, i, &dbg_level); + if (sec_dbg_level == (unsigned int)dbg_level) + return 0; + } + + return -EINVAL; +} +#else +static inline int sec_of_parse_reg_prop(struct device_node *np, phys_addr_t *base, phys_addr_t *size) { return -ENODEV; } +static inline int sec_of_test_debug_level(const struct device_node *np, const char *node_name, unsigned int sec_dbg_level) { return -ENODEV; }; +#endif + +#endif /* __SEC_OF_H__ */ diff --git a/include/linux/samsung/sec_of_kunit.h b/include/linux/samsung/sec_of_kunit.h new file mode 100644 index 000000000000..523228cfeb56 --- /dev/null +++ b/include/linux/samsung/sec_of_kunit.h @@ -0,0 +1,45 @@ +#ifndef __SEC_OF_KUNIT_H__ +#define __SEC_OF_KUNIT_H__ + +#include +#include + +#include + +struct sec_of_kunit_data { + struct miscdevice misc; + struct device_node *root; + struct device_node *of_node; + struct builder *bd; +}; + +/* NOTE: Inspired from 'drivers/of/unittest.c'. */ + +#define SEC_OF_KUNIT_DTB_INFO_EXTERN(name) \ + extern uint8_t __dtb_##name##_begin[]; \ + extern uint8_t __dtb_##name##_end[] + +#define SEC_OF_KUNIT_DTB_INFO(overlay_name) \ +struct sec_of_dtb_info overlay_name ## _info = { \ + .dtb_begin = __dtb_##overlay_name##_begin, \ + .dtb_end = __dtb_##overlay_name##_end, \ + .name = #overlay_name, \ +} + +struct sec_of_dtb_info { + uint8_t *dtb_begin; + uint8_t *dtb_end; + char *name; +}; + +#if IS_ENABLED(CONFIG_KUNIT) +extern int sec_of_kunit_data_init(struct sec_of_kunit_data *testdata, const char *name, struct builder *bd, const char *compatible, struct sec_of_dtb_info *info); +extern void sec_of_kunit_data_exit(struct sec_of_kunit_data *testdata); +extern struct device_node *sec_of_kunit_dtb_to_fdt(struct sec_of_dtb_info *info); +#else +static int sec_of_kunit_data_init(struct sec_of_kunit_data *testdata, const char *name, struct builder *bd, const char *compatible, struct sec_of_dtb_info *info) { return -EINVAL; } +static void sec_of_kunit_data_exit(struct sec_of_kunit_data *testdata) {} +static inline struct device_node *sec_of_kunit_dtb_to_fdt(struct sec_of_dtb_info *info) { return ERR_PTR(-EINVAL); } +#endif + +#endif /* __SEC_OF_KUNIT_H__ */ diff --git a/include/linux/samsung/sec_param.h b/include/linux/samsung/sec_param.h new file mode 100644 index 000000000000..6c1c410217bb --- /dev/null +++ b/include/linux/samsung/sec_param.h @@ -0,0 +1,16 @@ +#ifndef __LEGACY__SEC_PARAM_H__ +#define __LEGACY__SEC_PARAM_H__ + +#include + +static inline bool sec_get_param(size_t index, void *value) +{ + return sec_param_get(index, value); +} + +static inline bool sec_set_param(size_t index, void *value) +{ + return sec_param_set(index, value); +} + +#endif diff --git a/include/linux/sdp/adaptive_mipi_v2.h b/include/linux/sdp/adaptive_mipi_v2.h new file mode 100644 index 000000000000..c9154fadad47 --- /dev/null +++ b/include/linux/sdp/adaptive_mipi_v2.h @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) Samsung Electronics Co., Ltd. + * + * 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. + */ + +#ifndef __ADAPTIVE_MIPI_V2_H__ +#define __ADAPTIVE_MIPI_V2_H__ + +/* Adaptive MIPI v2 interfaces for display drivers */ +#define MAX_MIPI_FREQ_CNT (4) +#define INV_OSC_CLK (0) + +enum { + DEFAULT_OSC_ID = 0, + ALTERNATIVE_OSC_ID, + MAX_OSC_FREQ_CNT, +}; + +enum { + BANDWIDTH_10M_IDX = 0, + BANDWIDTH_20M_IDX, + MAX_BANDWIDTH_IDX, +}; + +struct adaptive_mipi_v2_table_element { + int rat; + int band; + int from_ch; + int end_ch; + + union { + int mipi_clocks_rating[MAX_MIPI_FREQ_CNT]; + int osc_idx; + }; +}; + +struct adaptive_mipi_v2_adapter_funcs { + int (*apply_freq)(int mipi_clk_kbps, int osc_clk_khz, void *ctx); +}; + +struct adaptive_mipi_v2_info { + void *ctx; + + int mipi_clocks_kbps[MAX_MIPI_FREQ_CNT]; + int mipi_clocks_size; + int osc_clocks_khz[MAX_OSC_FREQ_CNT]; + int osc_clocks_size; + + struct adaptive_mipi_v2_table_element *mipi_table[MAX_BANDWIDTH_IDX]; + int mipi_table_size[MAX_BANDWIDTH_IDX]; + + struct adaptive_mipi_v2_table_element *osc_table; + int osc_table_size; + + struct adaptive_mipi_v2_adapter_funcs *funcs; + + struct notifier_block ril_nb; +}; + +extern int sdp_init_adaptive_mipi_v2(struct adaptive_mipi_v2_info *info); +extern int sdp_cleanup_adaptive_mipi_v2(struct adaptive_mipi_v2_info *info); + +#endif /* __ADAPTIVE_MIPI_V2_H__ */ diff --git a/include/linux/sdp/adaptive_mipi_v2_cp_info.h b/include/linux/sdp/adaptive_mipi_v2_cp_info.h new file mode 100644 index 000000000000..8f631283daac --- /dev/null +++ b/include/linux/sdp/adaptive_mipi_v2_cp_info.h @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) Samsung Electronics Co., Ltd. + * + * 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. + */ + +#ifndef __ADAPTIVE_MIPI_V2_CP_INFO_H__ +#define __ADAPTIVE_MIPI_V2_CP_INFO_H__ + +#if IS_ENABLED(CONFIG_DEV_RIL_BRIDGE) +#include +#else +#define IPC_SYSTEM_CP_ADAPTIVE_MIPI_INFO (0x05) + +struct dev_ril_bridge_msg { + unsigned int dev_id; + unsigned int data_len; + void *data; +}; + +static inline int register_dev_ril_bridge_event_notifier(struct notifier_block *nb) { return 0; } +static inline int unregister_dev_ril_bridge_event_notifier(struct notifier_block *nb) { return 0; } +#endif /* CONFIG_DEV_RIL_BRIDGE */ + +/* IPC structure from CP */ +#define MAX_BAND (16) +#define STATUS_PRIMARY_SERVING (0x01) +#define STATUS_SECONDARY_SERVING (0x02) + +struct __packed band_info { + u8 rat; + u32 band; + u32 channel; + u8 connection_status; + u32 bandwidth; /* khz */ + s32 sinr; + s32 rsrp; + s32 rsrq; + u8 cqi; + u8 dl_mcs; + s32 pusch_power; +}; + +struct __packed cp_info { + u32 cell_count; + struct band_info infos[MAX_BAND]; +}; + +/* Tunable value */ +#define WEIGHT_PRIMARY_SERVING (10) +#define WEIGHT_SECONDARY_SERVING (1) + +#define DEFAULT_WEAK_SIGNAL (0x7FFFFFFF) +#define JUDGE_STRONG_SIGNAL (20) +#define WEIGHT_STRONG_SIGNAL (0) +#define WEIGHT_WEAK_SIGNAL (1) + +#endif /* __ADAPTIVE_MIPI_V2_CP_INFO_H__ */ diff --git a/include/linux/sdp/sdp_debug.h b/include/linux/sdp/sdp_debug.h new file mode 100644 index 000000000000..1d769af7081f --- /dev/null +++ b/include/linux/sdp/sdp_debug.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) Samsung Electronics Co., Ltd. + * + * 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. + */ + +#ifndef __SDP_DEBUG_H__ +#define __SDP_DEBUG_H__ + +#define SDP_LOG_PREFIX "SDP" + +enum { + MAIN_DISP_ID = 0, + SUB_DISP_ID = 1, + UNKNOWN_DISP_ID = -1, +}; + +#define sdp_err(ctx, fmt, ...) \ + pr_err(pr_fmt("[" SDP_LOG_PREFIX "_%d] %s:%d: error: " fmt), \ + (ctx) ? sdp_get_disp_id(ctx) : UNKNOWN_DISP_ID, \ + __func__, __LINE__, ##__VA_ARGS__) + +#define sdp_info(ctx, fmt, ...) \ + pr_info(pr_fmt("[" SDP_LOG_PREFIX "_%d] %s:%d: " fmt), \ + (ctx) ? sdp_get_disp_id(ctx) : UNKNOWN_DISP_ID, \ + __func__, __LINE__, ##__VA_ARGS__) + +#define sdp_dbg(ctx, fmt, ...) \ + pr_debug(pr_fmt("[" SDP_LOG_PREFIX "_%d] %s:%d: " fmt), \ + (ctx) ? sdp_get_disp_id(ctx) : UNKNOWN_DISP_ID, \ + __func__, __LINE__, ##__VA_ARGS__) + +extern int sdp_get_disp_id(void *ctx); + +#endif /* __SDP_DEBUG_H__ */ diff --git a/include/linux/sec_class.h b/include/linux/sec_class.h new file mode 100644 index 000000000000..e3e38824f5de --- /dev/null +++ b/include/linux/sec_class.h @@ -0,0 +1,6 @@ +#ifndef __LEGACY__SEC_CLASS_H__ +#define __LEGACY__SEC_CLASS_H__ + +#include + +#endif /* __LEGACY_SEC_CLASS_H__ */ diff --git a/include/linux/sec_debug.h b/include/linux/sec_debug.h new file mode 100644 index 000000000000..f5f4bc870685 --- /dev/null +++ b/include/linux/sec_debug.h @@ -0,0 +1,28 @@ +#ifndef __LEGACY__SEC_DEBUG_H__ +#define __LEGACY__SEC_DEBUG_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static __always_inline bool read_debug_partition(size_t index, void *value) +{ + return sec_qc_dbg_part_read(index, value); +} + +static __always_inline bool write_debug_partition(size_t index, void *value) +{ + return sec_qc_dbg_part_write(index, value); +} + +#include + +#endif /* __LEGACY__SEC_DEBUG_H__ */ diff --git a/include/linux/sec_panel_notifier_v2.h b/include/linux/sec_panel_notifier_v2.h new file mode 100644 index 000000000000..4fe58584885a --- /dev/null +++ b/include/linux/sec_panel_notifier_v2.h @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) Samsung Electronics Co., Ltd. + * + * 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. + */ + +#ifndef __SEC_PANEL_NOTIFIER_V2_H__ +#define __SEC_PANEL_NOTIFIER_V2_H__ +#include + +enum panel_notifier_event_t { + PANEL_EVENT_BL_STATE_CHANGED, + PANEL_EVENT_VRR_STATE_CHANGED, + PANEL_EVENT_LFD_STATE_CHANGED, + PANEL_EVENT_PANEL_STATE_CHANGED, + PANEL_EVENT_UB_CON_STATE_CHANGED, + PANEL_EVENT_COPR_STATE_CHANGED, + PANEL_EVENT_TEST_MODE_STATE_CHANGED, + PANEL_EVENT_SCREEN_MODE_STATE_CHANGED, + PANEL_EVENT_ESD_STATE_CHANGED, + MAX_PANEL_EVENT, +}; + +enum panel_notifier_event_state_t { + PANEL_EVENT_STATE_NONE, + + /* PANEL_EVENT_BL_STATE_CHANGED */ + + /* PANEL_EVENT_VRR_STATE_CHANGED */ + + /* PANEL_EVENT_LFD_STATE_CHANGED */ + + /* PANEL_EVENT_PANEL_STATE_CHANGED */ + PANEL_EVENT_PANEL_STATE_OFF, + PANEL_EVENT_PANEL_STATE_ON, + PANEL_EVENT_PANEL_STATE_LPM, + + /* PANEL_EVENT_UB_CON_STATE_CHANGED */ + PANEL_EVENT_UB_CON_STATE_CONNECTED, + PANEL_EVENT_UB_CON_STATE_DISCONNECTED, + + /* PANEL_EVENT_COPR_STATE_CHANGED */ + PANEL_EVENT_COPR_STATE_DISABLED, + PANEL_EVENT_COPR_STATE_ENABLED, + + /* PANEL_EVENT_TEST_MODE_STATE_CHANGED */ + PANEL_EVENT_TEST_MODE_STATE_NONE, + PANEL_EVENT_TEST_MODE_STATE_GCT, + + /* PANEL_EVENT_SCREEN_MODE_STATE_CHANGED */ + + /* PANEL_EVENT_ESD_STATE_CHANGED */ + + MAX_PANEL_EVENT_STATE, +}; + +struct panel_event_bl_data { + int level; + int aor; + int finger_mask_hbm_on; + int acl_status; + int gradual_acl_val; +}; + +/* dms: display modes */ +struct panel_event_dms_data { + int fps; + int lfd_min_freq; + int lfd_max_freq; +}; + +struct panel_notifier_event_data { + /* base */ + unsigned int display_index; + /* state */ + enum panel_notifier_event_state_t state; + /* additional data */ + union { + struct panel_event_bl_data bl; + struct panel_event_dms_data dms; + unsigned int screen_mode; + } d; +}; + +#if IS_ENABLED(CONFIG_SEC_PANEL_NOTIFIER_V2) +extern int panel_notifier_register(struct notifier_block *nb); +extern int panel_notifier_unregister(struct notifier_block *nb); +extern int panel_notifier_call_chain(unsigned long val, void *v); +#else +static inline int panel_notifier_register(struct notifier_block *nb) +{ + return 0; +}; + +static inline int panel_notifier_unregister(struct notifier_block *nb) +{ + return 0; +}; + +static inline int panel_notifier_call_chain(unsigned long val, void *v) +{ + return 0; +}; +#endif /* CONFIG_SEC_PANEL_NOTIFIER_V2 */ +#endif /* __SEC_PANEL_NOTIFIER_V2_H__ */ diff --git a/include/linux/sec_param.h b/include/linux/sec_param.h new file mode 100644 index 000000000000..6ef7899174e6 --- /dev/null +++ b/include/linux/sec_param.h @@ -0,0 +1,4 @@ +/* DO NOT Edit this file! */ + +#include "samsung/sec_param.h" + diff --git a/include/linux/sec_pm_log.h b/include/linux/sec_pm_log.h new file mode 100644 index 000000000000..9aca02d2a498 --- /dev/null +++ b/include/linux/sec_pm_log.h @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * sec_pm_log.h - header for SAMSUNG Power/Thermal logging. + * + */ + +#if IS_ENABLED(CONFIG_SEC_PM_LOG) +void ss_thermal_print(const char *fmt, ...); +void ss_power_print(const char *fmt, ...); +#endif diff --git a/include/linux/sensor/sensors_core.h b/include/linux/sensor/sensors_core.h new file mode 100755 index 000000000000..b20349d20555 --- /dev/null +++ b/include/linux/sensor/sensors_core.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2013 Samsung Electronics. 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 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef _SENSORS_CORE_H_ +#define _SENSORS_CORE_H_ + +#define TAG "" + +#define SENSOR_ERR(fmt, ...) pr_err("[SENSOR]%s %s: "fmt, TAG, __func__, ##__VA_ARGS__) +#define SENSOR_INFO(fmt, ...) pr_info("[SENSOR]%s %s: "fmt, TAG, __func__, ##__VA_ARGS__) +#define SENSOR_WARN(fmt, ...) pr_warn("[SENSOR]%s %s: "fmt, TAG, __func__, ##__VA_ARGS__) + +int sensors_create_symlink(struct kobject *, const char *); +void sensors_remove_symlink(struct kobject *, const char *); +int sensors_register(struct device **, void *, + struct device_attribute *[], char *); +void sensors_unregister(struct device *, struct device_attribute *[]); +void destroy_sensor_class(void); +void remap_sensor_data(s16 *, u32); +void remap_sensor_data_32(int *, u32); +/* report timestamp from kernel (for Android L) */ +#define TIME_LO_MASK 0x00000000FFFFFFFF +#define TIME_HI_MASK 0xFFFFFFFF00000000 +#define TIME_HI_SHIFT 32 +#include + +extern int sensordump_notifier_register(struct notifier_block *nb); +extern int sensordump_notifier_unregister(struct notifier_block *nb); +extern int sensordump_notifier_call_chain(unsigned long val, void *v); +#if IS_ENABLED(CONFIG_SUPPORT_SENSOR_FOLD) +extern int sensorfold_notifier_register(struct notifier_block *nb); +extern int sensorfold_notifier_unregister(struct notifier_block *nb); +extern int sensorfold_notifier_notify(unsigned long fold_state); +#endif +#endif diff --git a/include/linux/sti/abc_common.h b/include/linux/sti/abc_common.h new file mode 100644 index 000000000000..d1efdc608f9a --- /dev/null +++ b/include/linux/sti/abc_common.h @@ -0,0 +1,242 @@ +/* abc_common.h + * + * Abnormal Behavior Catcher Common Driver + * + * Copyright (C) 2017 Samsung Electronics + * + * Hyeokseon Yu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef SEC_ABC_H +#define SEC_ABC_H +#include +#include +#include +#if IS_ENABLED(CONFIG_DRV_SAMSUNG) +#include +#else +extern struct class *sec_class; +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_SEC_KUNIT) +#include +#include +#else +#define __visible_for_testing static +#endif +#if (KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE) +#include +#else +#include +#endif +#if IS_ENABLED(CONFIG_SEC_ABC_MOTTO) +#include +#endif +#ifndef EXPORT_SYMBOL_KUNIT +#define EXPORT_SYMBOL_KUNIT(sym) /* nothing */ +#endif +#define ABC_CMD_MAX 8 +#define ABC_UEVENT_MAX 10 +#define ABC_BUFFER_MAX 128 +#define ABC_CMD_STR_MAX 16 +#define ABC_TYPE_STR_MAX 8 +#define ABC_EVENT_STR_MAX 60 +#define ABC_SPEC_CMD_STR_MAX 3 +#define ABC_WORK_MAX 5 +#define ABC_WAIT_ENABLE_TIMEOUT 10000 +#define ERROR_REPORT_MODE_BIT (1<<0) +#define ALL_REPORT_MODE_BIT (1<<1) +#define PRE_EVENT_ENABLE_BIT (1<<7) +#define ABC_DISABLED 0 +#define ABC_PREOCCURRED_EVENT_MAX 30 +#define TIME_STAMP_STR_MAX 25 +#define ABC_CLEAR_EVENT_TIMEOUT 300000 +#define ABC_EVENT_BUFFER_MAX 30 +#define ABC_DEFAULT_COUNT 0 +#define ABC_TEST_STR_MAX 128 +#define ABC_SKIP_EVENT_COUNT_THRESHOLD 100 + +enum abc_enable_cmd { + ERROR_REPORT_MODE_ENABLE = 0, + ERROR_REPORT_MODE_DISABLE, + ALL_REPORT_MODE_ENABLE, + ALL_REPORT_MODE_DISABLE, + PRE_EVENT_ENABLE, + PRE_EVENT_DISABLE, + ABC_ENABLE_CMD_MAX, +}; + +enum abc_event_group { + ABC_GROUP_NONE = -1, + ABC_GROUP_CAMERA_MIPI_ERROR_ALL = 0, + ABC_GROUP_MAX, +}; + +struct registered_abc_event_struct { + char module_name[ABC_EVENT_STR_MAX]; + char error_name[ABC_EVENT_STR_MAX]; + char host[ABC_EVENT_STR_MAX]; + bool enabled; + bool singular_spec; + int error_count; + enum abc_event_group group; +}; + +struct abc_event_group_struct { + enum abc_event_group group; + char module[ABC_EVENT_STR_MAX]; + char name[ABC_EVENT_STR_MAX]; +}; + +struct abc_spec_cmd { + char module[ABC_EVENT_STR_MAX]; + char name[ABC_EVENT_STR_MAX]; + char spec[ABC_EVENT_STR_MAX]; +}; + +struct abc_enable_cmd_struct { + enum abc_enable_cmd cmd; + int enable_value; + char abc_cmd_str[ABC_CMD_STR_MAX]; +}; + +enum DATA_TYPE { + TYPE_STRING, + TYPE_INT, +}; + +struct abc_key_data { + char event_type[ABC_TYPE_STR_MAX]; + char event_module[ABC_EVENT_STR_MAX]; + char event_name[ABC_EVENT_STR_MAX]; + char ext_log[ABC_EVENT_STR_MAX]; + unsigned int cur_time; + int idx; +}; + +struct abc_pre_event { + struct list_head node; + int error_cnt; + int all_cnt; + int idx; + struct abc_key_data key_data; +}; + +struct abc_fault_info { + unsigned int cur_time; + int cur_cnt; +}; + +struct abc_event_buffer { + int size; + int rear; + int front; + int warn_cnt; + int buffer_max; + struct abc_fault_info *abc_element; +}; + +struct abc_common_spec_data { + char *module_name; + char *error_name; + int idx; + //int spec_type; In case a new spec type is added +}; + +struct spec_data_type1 { + int threshold_cnt; + int threshold_time; + int default_count; + bool default_enabled; + struct list_head node; + struct abc_common_spec_data common_spec; + struct abc_event_buffer buffer; +}; + +struct abc_platform_data { + unsigned int nItem; +#if IS_ENABLED(CONFIG_SEC_ABC_MOTTO) + struct abc_motto_data *motto_data; +#endif + struct list_head abc_spec_list; +}; + +struct abc_event_work { + char abc_str[ABC_BUFFER_MAX]; + struct work_struct work; +}; + +struct abc_info { + struct device *dev; + struct workqueue_struct *workqueue; + struct abc_event_work event_work_data[ABC_WORK_MAX]; + struct completion enable_done; + struct delayed_work clear_pre_events; +#if IS_ENABLED(CONFIG_SEC_KUNIT) + struct completion test_work_done; + struct completion test_uevent_done; +#endif + struct abc_platform_data *pdata; + struct mutex work_mutex; + struct mutex spec_mutex; + struct mutex pre_event_mutex; + struct mutex enable_mutex; +}; +void abc_common_test_get_log_str(char *log_str); +int sec_abc_get_idx_of_registered_event(char *module_name, char *error_name); +extern void sec_abc_change_spec(const char *str); +extern int sec_abc_read_spec(char *str); +extern void sec_abc_send_event(char *str); +extern int sec_abc_get_enabled(void); +extern int sec_abc_wait_enabled(void); +int sec_abc_save_pre_events(struct abc_key_data *key_data, char *uevent_type); +extern struct registered_abc_event_struct abc_event_list[]; +extern int REGISTERED_ABC_EVENT_TOTAL; + +#define ABC_PRINT(format, ...) pr_info("[sec_abc] %s : " format, __func__, ##__VA_ARGS__) +#define ABC_DEBUG(format, ...) pr_debug("[sec_abc] %s : " format, __func__, ##__VA_ARGS__) + +#ifdef CONFIG_SEC_KUNIT +#define ABC_PRINT_KUNIT(format, ...) do { \ + char temp[ABC_TEST_STR_MAX]; \ + ABC_PRINT(format, ##__VA_ARGS__); \ + snprintf(temp, ABC_TEST_STR_MAX, format, ##__VA_ARGS__); \ + abc_common_test_get_log_str(temp); \ +} while (0) +#define ABC_DEBUG_KUNIT(format, ...) do { \ + char temp[ABC_TEST_STR_MAX]; \ + ABC_PRINT(format, ##__VA_ARGS__); \ + snprintf(temp, ABC_TEST_STR_MAX, format, ##__VA_ARGS__); \ + abc_common_test_get_log_str(temp); \ +} while (0) +#else +#define ABC_PRINT_KUNIT(format, ...) ABC_PRINT(format, ##__VA_ARGS__) +#define ABC_DEBUG_KUNIT(format, ...) ABC_DEBUG(format, ##__VA_ARGS__) +#endif + +#endif diff --git a/include/linux/sti/abc_hub.h b/include/linux/sti/abc_hub.h new file mode 100644 index 000000000000..607532d5a2c6 --- /dev/null +++ b/include/linux/sti/abc_hub.h @@ -0,0 +1,110 @@ +/* abc_hub.h + * + * Abnormal Behavior Catcher Hub Driver + * + * Copyright (C) 2017 Samsung Electronics + * + * Sangsu Ha + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef SEC_ABC_HUB_H +#define SEC_ABC_HUB_H + +#include + +/******************************************/ +/****************** Data ******************/ +/******************************************/ + +/*********** sub module : cond ************/ +#if IS_ENABLED(CONFIG_SEC_ABC_HUB_COND) + +struct sub_cond_pdata { + // common + int init; + int enabled; +}; +#endif +/*********** sub module : bootc ************/ +#if IS_ENABLED(CONFIG_SEC_ABC_HUB_BOOTC) +#define BOOTC_OFFSET_DATA_CNT 1 +#define BOOTC_OFFSET_STR_MAX 100 + +struct abc_hub_bootc_offset_data { + char module[BOOTC_OFFSET_STR_MAX]; + int offset; +}; + +struct sub_bootc_pdata { + // common + int init; + int enabled; + int bootc_time; + // custom + int time_spec; + int time_spec_offset; + struct abc_hub_bootc_offset_data offset_data[BOOTC_OFFSET_DATA_CNT]; + struct workqueue_struct *workqueue; + struct delayed_work bootc_work; +}; +#endif + +/****************** Common *****************/ + +/* It will be added if sub module is added. */ +enum { + ABC_HUB_DISABLED, + ABC_HUB_ENABLED, +}; + +struct abc_hub_platform_data { + unsigned int nSub; +#if IS_ENABLED(CONFIG_SEC_ABC_HUB_COND) + struct sub_cond_pdata cond; +#endif +#if IS_ENABLED(CONFIG_SEC_ABC_HUB_BOOTC) + struct sub_bootc_pdata bootc_pdata; +#endif +}; + +struct abc_hub_info { + struct device *dev; + struct abc_hub_platform_data *pdata; + int enabled; +}; + +/******************************************/ +/**************** Function ****************/ +/******************************************/ + +/*********** sub module : cond ************/ +#if IS_ENABLED(CONFIG_SEC_ABC_HUB_COND) +void abc_hub_cond_enable(struct device *dev, int enable); +#endif + +/*********** sub module : bootc ************/ +#if IS_ENABLED(CONFIG_SEC_ABC_HUB_BOOTC) +int parse_bootc_data(struct device *dev, + struct abc_hub_platform_data *pdata, + struct device_node *np); +int abc_hub_bootc_init(struct device *dev); +void abc_hub_bootc_enable(struct device *dev, int enable); +#endif + +extern struct abc_hub_info *abc_hub_pinfo; +/****************** Common *****************/ +void abc_hub_send_event(char *str); +int abc_hub_get_enabled(void); + +#endif diff --git a/include/linux/sti/abc_motto.h b/include/linux/sti/abc_motto.h new file mode 100644 index 000000000000..9f122c178b41 --- /dev/null +++ b/include/linux/sti/abc_motto.h @@ -0,0 +1,44 @@ +/* abc_motto.h + * + * Abnormal Behavior Catcher MOTTO Support + * + * Copyright (C) 2020 Samsung Electronics + * + * 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. + * + */ + +enum motto_event_module { + MOTTO_MODULE_NONE = -1, + MOTTO_MODULE_GPU = 0, + MOTTO_MODULE_DECON, + MOTTO_MODULE_CAMERA, + MOTTO_MODULE_NPU, + MOTTO_MODULE_MAX, +}; + +#define MOTTO_MODULE_NAME_LEN 32 +#define MOTTO_BUFFER_MAX 256 +struct motto_event_type { + enum motto_event_module module; + char motto_event_module_name[MOTTO_MODULE_NAME_LEN]; + char motto_event_type_str[MOTTO_BUFFER_MAX]; +}; + +struct abc_motto_data { + u8 boot_time; + u8 dev_err_count[MOTTO_MODULE_MAX]; +}; + +extern void motto_send_bootcheck_info(int boot_time); + +void motto_send_device_info(char *module_str, char *event_type); +void motto_init(struct platform_device *); diff --git a/include/linux/sti/abc_spec_manager.h b/include/linux/sti/abc_spec_manager.h new file mode 100644 index 000000000000..e8ff29348a30 --- /dev/null +++ b/include/linux/sti/abc_spec_manager.h @@ -0,0 +1,64 @@ +/* abc_spec_manager.h + * + * Abnormal Behavior Catcher's spec manager module. + * + * Copyright 2021 Samsung Electronics + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef SEC_ABC_SPEC_MANAGER_H +#define SEC_ABC_SPEC_MANAGER_H +#include +#define ERROR_KEY "name_list" +#define MODULE_KEY "module_list" +#define THRESHOLD_CNT_KEY "threshold_cnt" +#define THRESHOLD_TIME_KEY "threshold_time" + +extern struct list_head abc_spec_list; + +int abc_parse_dt(struct device *dev); +void sec_abc_reset_event_buffer(struct abc_key_data *key_data); +int sec_abc_make_key_data(struct abc_key_data *key_data, char *str); +bool sec_abc_reached_spec(struct abc_key_data *key_data); +void sec_abc_enqueue_event_data(struct abc_key_data *key_data); +struct abc_common_spec_data *sec_abc_get_matched_common_spec(char *module_name, char *error_name); +int sec_abc_get_buffer_size_from_threshold_cnt(int th_max); +int sec_abc_parse_spec_cmd(char *str, struct abc_spec_cmd *abc_spec); +int sec_abc_apply_changed_spec(char *module_name, char *error_name, char *spec); +enum abc_event_group sec_abc_get_group(char *module, char *name); +int sec_abc_apply_changed_group_spec(enum abc_event_group group, char *spec); +int sec_abc_get_event_module(char *dst, char *src); +int sec_abc_get_event_name(char *dst, char *src); +int sec_abc_get_event_type(char *dst, char *src); +int sec_abc_get_ext_log(char *dst, char *src); +int sec_abc_get_count(int *dst, char *src); +void sec_abc_reset_all_spec(void); +void sec_abc_free_spec_buffer(void); +void sec_abc_reset_all_buffer(void); +/* spec_type1 */ +int abc_parse_dt_type1(struct device *dev, + struct device_node *np, int idx, + struct spec_data_type1 *spec_type1); +bool sec_abc_is_full_type1(struct abc_event_buffer *buffer); +bool sec_abc_is_empty_type1(struct abc_event_buffer *buffer); +int sec_abc_get_diff_time_type1(struct abc_event_buffer *buffer); +void sec_abc_reset_buffer_type1(struct spec_data_type1 *common_spec); +struct abc_fault_info sec_abc_dequeue_type1(struct abc_event_buffer *buffer); +void sec_abc_dequeue_event_data_type1(struct abc_common_spec_data *common_spec); +void sec_abc_enqueue_type1(struct abc_event_buffer *buffer, struct abc_fault_info in); +bool sec_abc_reached_spec_type1(struct abc_common_spec_data *common_spec, unsigned int cur_time); +struct abc_common_spec_data *sec_abc_get_matched_common_spec_type1(char *module_name, char *error_name); +void sec_abc_enqueue_event_data_type1(struct abc_common_spec_data *common_spec, unsigned int cur_time); +int abc_alloc_memory_to_buffer_type1(struct spec_data_type1 *spec_type1, int size); +#endif diff --git a/include/linux/sti/sec_abc_detect_conn.h b/include/linux/sti/sec_abc_detect_conn.h new file mode 100644 index 000000000000..c6efc719cb62 --- /dev/null +++ b/include/linux/sti/sec_abc_detect_conn.h @@ -0,0 +1,74 @@ +/* + * include/linux/sti/sec_abc_detect_conn.h + * + * COPYRIGHT(C) 2017 Samsung Electronics Co., Ltd. All Right 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 SEC_DETECT_CONN_H +#define SEC_DETECT_CONN_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DET_CONN_MAX_NUM_GPIOS 32 +#define UEVENT_CONN_MAX_DEV_NAME 64 +#define DET_CONN_GPIO_IRQ_NOT_INIT 0 +#define DET_CONN_GPIO_IRQ_ENABLED 1 +#define DET_CONN_GPIO_IRQ_DISABLED 2 +#define DET_CONN_DEBOUNCE_TIME_MS 300 + +#define SEC_CONN_PRINT(format, ...) \ + pr_info("[sec_abc_detect_conn] " format, ##__VA_ARGS__) + +struct sec_det_conn_p_data { + const char *name[DET_CONN_MAX_NUM_GPIOS]; + int irq_gpio[DET_CONN_MAX_NUM_GPIOS]; + int irq_number[DET_CONN_MAX_NUM_GPIOS]; + unsigned int irq_type[DET_CONN_MAX_NUM_GPIOS]; + struct sec_det_conn_info *pinfo; + int gpio_last_cnt; + int gpio_total_cnt; +}; + +struct sec_det_conn_info { + struct device *dev; + int irq_enabled[DET_CONN_MAX_NUM_GPIOS]; + struct sec_det_conn_p_data *pdata; +}; + +static char sec_detect_available_pins_string[15 * 10] = {0,}; +extern struct sec_det_conn_info *gpinfo; + +void create_current_connection_state_sysnode_files(struct sec_det_conn_info *pinfo); +void create_connector_disconnected_count_sysnode_file(struct sec_det_conn_info *pinfo); +void increase_connector_disconnected_count(int index, struct sec_det_conn_info *pinfo); + +#if IS_ENABLED(CONFIG_SEC_ABC_HUB) +#define ABCEVENT_CONN_MAX_DEV_STRING 120 +void sec_abc_send_event(char *str); +int sec_abc_get_enabled(void); +#endif +#endif diff --git a/include/linux/switch.h b/include/linux/switch.h new file mode 100644 index 000000000000..1c24758d502a --- /dev/null +++ b/include/linux/switch.h @@ -0,0 +1,53 @@ +/* + * Switch class driver + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * + * 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_SWITCH_H__ +#define __LINUX_SWITCH_H__ + +struct switch_dev { + const char *name; + struct device *dev; + int index; + int state; + + ssize_t (*print_name)(struct switch_dev *sdev, char *buf); + ssize_t (*print_state)(struct switch_dev *sdev, char *buf); +}; + +struct gpio_switch_platform_data { + const char *name; + unsigned int gpio; + + /* if NULL, switch_dev.name will be printed */ + const char *name_on; + const char *name_off; + /* if NULL, "0" or "1" will be printed */ + const char *state_on; + const char *state_off; +}; + +extern int switch_dev_register(struct switch_dev *sdev); +extern void switch_dev_unregister(struct switch_dev *sdev); + +static inline int switch_get_state(struct switch_dev *sdev) +{ + return sdev->state; +} + +extern void switch_set_state(struct switch_dev *sdev, int state); + +#endif /* __LINUX_SWITCH_H__ */ diff --git a/include/linux/uh.h b/include/linux/uh.h new file mode 100644 index 000000000000..ed9db0f9025a --- /dev/null +++ b/include/linux/uh.h @@ -0,0 +1,33 @@ +#ifndef __UH_H__ +#define __UH_H__ + +#ifndef __ASSEMBLY__ + +/* For uH Command */ +#define APP_INIT 0 +#define APP_RKP 1 +#define APP_KDP 2 +#define APP_HDM 6 + +#define UH_PREFIX UL(0xc300c000) +#define UH_APPID(APP_ID) ((UL(APP_ID) & UL(0xFF)) | UH_PREFIX) + +enum __UH_APP_ID { + UH_APP_INIT = UH_APPID(APP_INIT), + UH_APP_RKP = UH_APPID(APP_RKP), + UH_APP_KDP = UH_APPID(APP_KDP), + UH_APP_HDM = UH_APPID(APP_HDM), +}; + +struct test_case_struct { + int (*fn)(void); + char *describe; +}; + +#define UH_LOG_START 0xB0200000 +#define UH_LOG_SIZE 0x40000 + +unsigned long uh_call(u64 app_id, u64 command, u64 arg0, u64 arg1, u64 arg2, u64 arg3); + +#endif //__ASSEMBLY__ +#endif //__UH_H__ diff --git a/include/linux/usb/f_ss_mon_gadget.h b/include/linux/usb/f_ss_mon_gadget.h new file mode 100644 index 000000000000..e6e3574d228e --- /dev/null +++ b/include/linux/usb/f_ss_mon_gadget.h @@ -0,0 +1,24 @@ +/* + * Gadget Function Driver for Android USB accessories + * + * Copyright (C) 2021 samsung.com. + * + * 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 _F_SS_MON_GADGHET_H_ +#define _F_SS_MON_GADGHET_H_ + +#include + +extern void usb_reset_notify(struct usb_gadget *gadget); +extern void vbus_session_notify(struct usb_gadget *gadget, int is_active, int ret); +extern void make_suspend_current_event(void); +#endif diff --git a/include/linux/usb/repeater.h b/include/linux/usb/repeater.h index 45cb44349080..4e7c6be85a01 100644 --- a/include/linux/usb/repeater.h +++ b/include/linux/usb/repeater.h @@ -15,7 +15,9 @@ struct usb_repeater { struct device *dev; const char *label; unsigned int flags; - +#if IS_ENABLED(CONFIG_USB_PHY_SETTING_QCOM) + bool is_host; +#endif struct list_head head; int (*reset)(struct usb_repeater *x, bool bring_out_of_reset); int (*init)(struct usb_repeater *x); diff --git a/include/linux/usb/typec/common/pdic_core.h b/include/linux/usb/typec/common/pdic_core.h new file mode 100644 index 000000000000..244531b40f99 --- /dev/null +++ b/include/linux/usb/typec/common/pdic_core.h @@ -0,0 +1,345 @@ +/* + * + * Copyright (C) 2017-2020 Samsung Electronics + * + * Author:Wookwang Lee. , + * 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, see . + * + */ + +#ifndef __LINUX_PDIC_CORE_H__ +#define __LINUX_PDIC_CORE_H__ + +/* PDIC Dock Observer Callback parameter */ +enum { + PDIC_DOCK_DETACHED = 0, + PDIC_DOCK_HMT = 105, /* Samsung Gear VR */ + PDIC_DOCK_ABNORMAL = 106, + PDIC_DOCK_MPA = 109, /* Samsung Multi Port Adaptor */ + PDIC_DOCK_DEX = 110, /* Samsung Dex */ + PDIC_DOCK_HDMI = 111, /* Samsung HDMI Dongle */ + PDIC_DOCK_T_VR = 112, + PDIC_DOCK_UVDM = 113, + PDIC_DOCK_DEXPAD = 114, + PDIC_DOCK_UNSUPPORTED_AUDIO = 115, /* Ra/Ra TypeC Analog Earphone*/ + PDIC_DOCK_NEW = 200, /* For New uevent */ +}; + +typedef enum { + TYPE_C_DETACH = 0, + TYPE_C_ATTACH_DFP = 1, /* Host */ + TYPE_C_ATTACH_UFP = 2, /* Device */ + TYPE_C_ATTACH_DRP = 3, /* Dual role */ + TYPE_C_ATTACH_SRC = 4, /* SRC */ + TYPE_C_ATTACH_SNK = 5, /* SNK */ + TYPE_C_RR_SWAP = 6, + TYPE_C_DR_SWAP = 7, +} PDIC_OTP_MODE; + +#if defined(CONFIG_TYPEC) +typedef enum { + TRY_ROLE_SWAP_NONE = 0, + TRY_ROLE_SWAP_PR = 1, /* pr_swap */ + TRY_ROLE_SWAP_DR = 2, /* dr_swap */ + TRY_ROLE_SWAP_TYPE = 3, /* type */ + TRY_ROLE_SWAP_VC = 4, /* vconn swap */ +} PDIC_ROLE_SWAP_MODE; + +#define TRY_ROLE_SWAP_WAIT_MS 5000 +#endif +#define DUAL_ROLE_SET_MODE_WAIT_MS 2000 +#define GEAR_VR_DETACH_WAIT_MS 1000 +#define SAMSUNG_PRODUCT_ID 0x6860 +#define SAMSUNG_PRODUCT_TYPE 0x2 +/* Samsung Acc VID */ +#define SAMSUNG_VENDOR_ID 0x04E8 +#define SAMSUNG_MPA_VENDOR_ID 0x04B4 +#define TypeC_DP_SUPPORT (0xFF01) +/* Samsung Acc PID */ +#define GEARVR_PRODUCT_ID 0xA500 +#define GEARVR_PRODUCT_ID_1 0xA501 +#define GEARVR_PRODUCT_ID_2 0xA502 +#define GEARVR_PRODUCT_ID_3 0xA503 +#define GEARVR_PRODUCT_ID_4 0xA504 +#define GEARVR_PRODUCT_ID_5 0xA505 +#define DEXDOCK_PRODUCT_ID 0xA020 +#define HDMI_PRODUCT_ID 0xA025 +#define MPA2_PRODUCT_ID 0xA027 +#define UVDM_PROTOCOL_ID 0xA028 +#define DEXPAD_PRODUCT_ID 0xA029 +#define MPA_PRODUCT_ID 0x2122 +#define FRIENDS_PRODUCT_ID 0xB002 + +/* Samsung UVDM structure */ +#define SEC_UVDM_SHORT_DATA 0x0 +#define SEC_UVDM_LONG_DATA 0x1 +#define SEC_UVDM_ININIATOR 0x0 +#define SEC_UVDM_RESPONDER_INIT 0x0 +#define SEC_UVDM_RESPONDER_ACK 0x1 +#define SEC_UVDM_RESPONDER_NAK 0x2 +#define SEC_UVDM_RESPONDER_BUSY 0x3 +#define SEC_UVDM_RX_HEADER_BUSY 0x2 +#define SEC_UVDM_UNSTRUCTURED_VDM 0x4 +#define SEC_UVDM_RX_HEADER_ACK 0x0 +#define SEC_UVDM_RX_HEADER_NAK 0x1 + + +#define SEC_UVDM_ALIGN (4) +#define SEC_UVDM_MAXDATA_FIRST (12) +#define SEC_UVDM_MAXDATA_NORMAL (16) +#define SEC_UVDM_CHECKSUM_COUNT (20) + +/*For DP Pin Assignment */ +#define DP_PIN_ASSIGNMENT_NODE 0x00000000 +#define DP_PIN_ASSIGNMENT_A 0x00000001 /* ( 1 << 0 ) */ +#define DP_PIN_ASSIGNMENT_B 0x00000002 /* ( 1 << 1 ) */ +#define DP_PIN_ASSIGNMENT_C 0x00000004 /* ( 1 << 2 ) */ +#define DP_PIN_ASSIGNMENT_D 0x00000008 /* ( 1 << 3 ) */ +#define DP_PIN_ASSIGNMENT_E 0x00000010 /* ( 1 << 4 ) */ +#define DP_PIN_ASSIGNMENT_F 0x00000020 /* ( 1 << 5 ) */ + +#define MAX_BUF_DATA 256 + +typedef union { + u16 word; + u8 byte[2]; + + struct { + unsigned msg_type:5; + unsigned port_data_role:1; + unsigned spec_revision:2; + unsigned port_power_role:1; + unsigned msg_id:3; + unsigned num_data_objs:3; + unsigned extended:1; + }; +} msg_header_type; + +typedef union { + u32 object; + u16 word[2]; + u8 byte[4]; + struct { + unsigned vendor_defined:15; + unsigned vdm_type:1; + unsigned vendor_id:16; + }; + struct { + uint32_t VDM_command:5, + Rsvd2_VDM_header:1, + VDM_command_type:2, + Object_Position:3, + Rsvd_VDM_header:2, + Structured_VDM_Version:2, + VDM_Type:1, + Standard_Vendor_ID:16; + } BITS; +} uvdm_header; + +typedef union { + u32 object; + u16 word[2]; + u8 byte[4]; + + struct{ + unsigned data:8; + unsigned total_set_num:4; + unsigned direction:1; + unsigned cmd_type:2; + unsigned data_type:1; + unsigned pid:16; + }; +} s_uvdm_header; + +typedef union { + u32 object; + u16 word[2]; + u8 byte[4]; + + struct{ + unsigned cur_size:8; + unsigned total_size:8; + unsigned reserved:12; + unsigned order_cur_set:4; + }; +} s_tx_header; + +typedef union { + u32 object; + u16 word[2]; + u8 byte[4]; + + struct{ + unsigned checksum:16; + unsigned reserved:16; + }; +} s_tx_tailer; + +typedef union { + u32 object; + u16 word[2]; + u8 byte[4]; + + struct{ + unsigned reserved:18; + unsigned result_value:2; + unsigned rcv_data_size:8; + unsigned order_cur_set:4; + }; +} s_rx_header; + +enum usbpd_port_data_role { + USBPD_UFP, + USBPD_DFP, +}; + +enum usbpd_port_power_role { + USBPD_SINK, + USBPD_SOURCE, + USBPD_DRP, +}; + +enum usbpd_port_vconn_role { + USBPD_VCONN_OFF, + USBPD_VCONN_ON, +}; + +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) +struct pdic_state_work { + struct work_struct pdic_work; + int dest; + int id; + int attach; + int event; + int sub; +}; +typedef enum { + CLIENT_OFF = 0, + CLIENT_ON = 1, +} PDIC_DEVICE_REASON; + +typedef enum { + HOST_OFF = 0, + HOST_ON = 1, +} PDIC_HOST_REASON; +#endif + +enum uvdm_data_type { + TYPE_SHORT = 0, + TYPE_LONG, +}; + +enum uvdm_direction_type { + DIR_OUT = 0, + DIR_IN, +}; + +struct uvdm_data { + unsigned short pid; /* Product ID */ + char type; /* uvdm_data_type */ + char dir; /* uvdm_direction_type */ + unsigned int size; /* data size */ + void __user *pData; /* data pointer */ +}; +#ifdef CONFIG_COMPAT +struct uvdm_data_32 { + unsigned short pid; /* Product ID */ + char type; /* uvdm_data_type */ + char dir; /* uvdm_direction_type */ + unsigned int size; /* data size */ + compat_uptr_t pData; /* data pointer */ +}; +#endif + +struct pdic_misc_dev { + struct uvdm_data u_data; +#ifdef CONFIG_COMPAT + struct uvdm_data_32 u_data_32; +#endif + atomic_t open_excl; + atomic_t ioctl_excl; + int (*uvdm_write)(void *data, int size); + int (*uvdm_read)(void *data); + int (*uvdm_ready)(void); + void (*uvdm_close)(void); + bool (*pps_control)(int en); +}; + +struct pdic_misc_data { + void *fw_buf; + size_t offset; + size_t fw_buf_size; + int is_error; +}; + +struct pdic_data { + int (*firmware_update)(void *data, + void *fw_bin, size_t fw_size); + size_t (*get_prev_fw_size)(void *data); + void *data; +}; + +struct pdic_fwupdate_data { + struct pdic_misc_data *misc_data; + struct pdic_data *ic_data; + atomic_t opened; +}; + +struct pdic_misc_core { + struct pdic_misc_dev c_dev; + struct pdic_fwupdate_data fw_data; +}; + +typedef struct _pdic_data_t { + const char *name; + void *pdic_sysfs_prop; + void *drv_data; + void (*set_enable_alternate_mode)(int); + struct pdic_misc_dev *misc_dev; + struct pdic_data fw_data; +} pdic_data_t, *ppdic_data_t; + +/* ---------------------------------- + * pdic_core.c functions + *----------------------------------- + */ +int pdic_core_init(void); +int pdic_core_register_chip(ppdic_data_t ppdic_data); +void pdic_core_unregister_chip(void); +int pdic_register_switch_device(int mode); +void pdic_send_dock_intent(int type); +void pdic_send_dock_uevent(u32 vid, u32 pid, int state); +void *pdic_core_get_drvdata(void); +/* ---------------------------------- + * pdic_misc.c functions + *----------------------------------- + */ +int pdic_misc_init(ppdic_data_t ppdic_data); +void pdic_misc_exit(void); +/* SEC UVDM Utility function */ +int get_checksum(const char *data, int start_addr, int size); +int get_data_size(bool is_first_data, int data_size); +int set_endian(const char *src, char *dest, int size); +int set_uvdmset_count(int size); +void set_msg_header(void *data, int msg_type, int obj_num); +void set_uvdm_header(void *data, int vid, int vdm_type); +void set_sec_uvdm_header(void *data, int pid, bool data_type, int cmd_type, + bool dir, int total_set_num, uint8_t received_data); +void set_sec_uvdm_tx_header(void *data, int first_set, int cur_set, int total_size, + int remained_size); +void set_sec_uvdm_tx_tailer(void *data); +void set_sec_uvdm_rx_header(void *data, int cur_num, int cur_set, int ack); +struct device *get_pdic_device(void); +#endif + diff --git a/include/linux/usb/typec/common/pdic_notifier.h b/include/linux/usb/typec/common/pdic_notifier.h new file mode 100644 index 000000000000..ad716a61add4 --- /dev/null +++ b/include/linux/usb/typec/common/pdic_notifier.h @@ -0,0 +1,281 @@ +/* + * Copyrights (C) 2019 Samsung Electronics, 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. + */ + +#ifndef __PDIC_NOTIFIER_H__ +#define __PDIC_NOTIFIER_H__ + +/* PDIC notifier call sequence, + * largest priority number device will be called first. + * refer Team Docs > ETC > 1. USB Type C > + * code managing > pdic_notifier, pdic_notifier + */ +typedef enum { + PDIC_NOTIFY_DEV_INITIAL = 0, + PDIC_NOTIFY_DEV_USB = 1, + PDIC_NOTIFY_DEV_BATT = 2, + PDIC_NOTIFY_DEV_PDIC = 3, + PDIC_NOTIFY_DEV_MUIC = 4, + PDIC_NOTIFY_DEV_CCIC = 5, + PDIC_NOTIFY_DEV_MANAGER = 6, + PDIC_NOTIFY_DEV_DP = 7, + PDIC_NOTIFY_DEV_USB_DP = 8, + PDIC_NOTIFY_DEV_SUB_BATTERY = 9, + PDIC_NOTIFY_DEV_SECOND_MUIC = 10, + PDIC_NOTIFY_DEV_DEDICATED_MUIC = 11, + PDIC_NOTIFY_DEV_ALL = 12, +} pdic_notifier_device; + +typedef enum { + PDIC_NOTIFY_ID_INITIAL = 0, + PDIC_NOTIFY_ID_ATTACH = 1, + PDIC_NOTIFY_ID_RID = 2, + PDIC_NOTIFY_ID_USB = 3, + PDIC_NOTIFY_ID_POWER_STATUS = 4, + PDIC_NOTIFY_ID_WATER = 5, + PDIC_NOTIFY_ID_VCONN = 6, + PDIC_NOTIFY_ID_OTG = 7, + PDIC_NOTIFY_ID_TA = 8, + PDIC_NOTIFY_ID_DP_CONNECT = 9, + PDIC_NOTIFY_ID_DP_HPD = 10, + PDIC_NOTIFY_ID_DP_LINK_CONF = 11, + PDIC_NOTIFY_ID_USB_DP = 12, + PDIC_NOTIFY_ID_ROLE_SWAP = 13, + PDIC_NOTIFY_ID_FAC = 14, + PDIC_NOTIFY_ID_CC_PIN_STATUS = 15, + PDIC_NOTIFY_ID_WATER_CABLE = 16, + PDIC_NOTIFY_ID_POFF_WATER = 17, + PDIC_NOTIFY_ID_DEVICE_INFO = 18, + PDIC_NOTIFY_ID_SVID_INFO = 19, + PDIC_NOTIFY_ID_CLEAR_INFO = 20, +#if IS_ENABLED(CONFIG_MUIC_POGO) + PDIC_NOTIFY_ID_POGO = 21, +#endif + PDIC_NOTIFY_ID_RPLEVEL = 22, +} pdic_notifier_id_t; + +typedef enum { + RID_UNDEFINED = 0, + RID_000K = 1, + RID_001K = 2, + RID_255K = 3, + RID_301K = 4, + RID_523K = 5, + RID_619K = 6, + RID_OPEN = 7, + RID_056K = 8, +} pdic_notifier_rid_t; + +typedef enum { + USB_STATUS_NOTIFY_DETACH = 0, + USB_STATUS_NOTIFY_ATTACH_DFP = 1, + USB_STATUS_NOTIFY_ATTACH_UFP = 2, + USB_STATUS_NOTIFY_ATTACH_DRP = 3, +} USB_STATUS; + +typedef enum { + PDIC_NOTIFY_PIN_STATUS_NO_DETERMINATION = 0, + PDIC_NOTIFY_PIN_STATUS_CC1_ACTIVE = 1, + PDIC_NOTIFY_PIN_STATUS_CC2_ACTIVE = 2, + PDIC_NOTIFY_PIN_STATUS_AUDIO_ACCESSORY = 3, + PDIC_NOTIFY_PIN_STATUS_DEBUG_ACCESSORY = 4, + PDIC_NOTIFY_PIN_STATUS_PDIC_ERROR = 5, + PDIC_NOTIFY_PIN_STATUS_DISABLED = 6, + PDIC_NOTIFY_PIN_STATUS_RFU = 7, + PDIC_NOTIFY_PIN_STATUS_NOCC_USB_ACTIVE = 8, +} pdic_notifier_pin_status_t; + +typedef enum { + PDIC_NOTIFY_DP_PIN_UNKNOWN = 0, + PDIC_NOTIFY_DP_PIN_A = 1, + PDIC_NOTIFY_DP_PIN_B = 2, + PDIC_NOTIFY_DP_PIN_C = 3, + PDIC_NOTIFY_DP_PIN_D = 4, + PDIC_NOTIFY_DP_PIN_E = 5, + PDIC_NOTIFY_DP_PIN_F = 6, +} pdic_notifier_dp_pinconf_t; + +typedef enum { + PDIC_NOTIFY_DETACH = 0, + PDIC_NOTIFY_ATTACH = 1, +} pdic_notifier_attach_t; + +typedef enum { + PDIC_NOTIFY_DEVICE = 0, + PDIC_NOTIFY_HOST = 1, +} pdic_notifier_attach_rprd_t; + +typedef enum { + PDIC_NOTIFY_LOW = 0, + PDIC_NOTIFY_HIGH = 1, + PDIC_NOTIFY_IRQ = 2, +} pdic_notifier_dp_hpd_t; + +typedef enum { + PDIC_NOTIFY_PD_EVENT_DETACH = 0, + PDIC_NOTIFY_PD_EVENT_CCIC_ATTACH, + PDIC_NOTIFY_PD_EVENT_SINK, + PDIC_NOTIFY_PD_EVENT_SOURCE, + PDIC_NOTIFY_PD_EVENT_SINK_CAP, + PDIC_NOTIFY_PD_EVENT_PRSWAP_SNKTOSRC, +} pdic_notifier_pd_event_t; + +typedef struct { + uint64_t src:4; + uint64_t dest:4; + uint64_t id:8; + uint64_t sub1:16; + uint64_t sub2:16; + uint64_t sub3:16; +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + void *pd; +#endif +} PD_NOTI_TYPEDEF; + +/* ID = 1 : Attach */ +typedef struct { + uint64_t src:4; + uint64_t dest:4; + uint64_t id:8; + uint64_t attach:16; + uint64_t rprd:16; + uint64_t cable_type:16; +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + void *pd; +#endif +} PD_NOTI_ATTACH_TYPEDEF; + +/* ID = 2 : RID */ +typedef struct { + uint64_t src:4; + uint64_t dest:4; + uint64_t id:8; + uint64_t rid:16; + uint64_t sub2:16; + uint64_t sub3:16; +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + void *pd; +#endif +} PD_NOTI_RID_TYPEDEF; + +/* ID = 3 : USB status */ +typedef struct { + uint64_t src:4; + uint64_t dest:4; + uint64_t id:8; + uint64_t attach:16; + uint64_t drp:16; + uint64_t sub3:16; +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + void *pd; +#endif +} PD_NOTI_USB_STATUS_TYPEDEF; + +typedef struct { + uint64_t src:4; + uint64_t dest:4; + uint64_t id:8; + uint64_t is_connect:16; + uint64_t hs_connect:16; + uint64_t reserved:16; +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + void *pd; +#endif +} USB_DP_NOTI_TYPEDEF; + +/* ID = 4 : POWER STATUS */ +typedef struct { + uint64_t src:4; + uint64_t dest:4; + uint64_t id:8; + uint64_t attach:16; + uint64_t event:16; + uint64_t data:16; +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + void *pd; +#endif +} PD_NOTI_POWER_STATUS_TYPEDEF; + +/* ID = 18 : Device Info */ +typedef struct { + uint64_t src:4; + uint64_t dest:4; + uint64_t id:8; + uint64_t vendor_id:16; + uint64_t product_id:16; + uint64_t version:8; + uint64_t ifpmic_index:8; +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + void *pd; +#endif +} PD_NOTI_DEVICE_INFO_TYPEDEF; + +/* ID = 19 : Standard Vendor ID Info */ +typedef struct { + uint64_t src:4; + uint64_t dest:4; + uint64_t id:8; + uint64_t standard_vendor_id:16; + uint64_t reserved:32; +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + void *pd; +#endif +} PD_NOTI_SVID_INFO_TYPEDEF; + +/* ID = 20 : Clear ID Info */ +typedef struct { + uint64_t src:4; + uint64_t dest:4; + uint64_t id:8; + uint64_t clear_id:16; + uint64_t reserved:32; +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + void *pd; +#endif +} PD_NOTI_CLEAR_INFO_TYPEDEF; + +struct pdic_notifier_data { + PD_NOTI_TYPEDEF pdic_template; + struct blocking_notifier_head notifier_call_chain; + + struct mutex notify_mutex; +}; + +#define PDIC_NOTIFIER_BLOCK(name) \ + struct notifier_block (name) + +#define SET_PDIC_NOTIFIER_BLOCK(nb, fn, dev) do { \ + (nb)->notifier_call = (fn); \ + (nb)->priority = (dev); \ + } while (0) + +#define DESTROY_PDIC_NOTIFIER_BLOCK(nb) \ + SET_PDIC_NOTIFIER_BLOCK(nb, NULL, -1) + +extern int pdic_notifier_notify(PD_NOTI_TYPEDEF *noti, void *pd, + int pdic_attach); +extern int pdic_notifier_register(struct notifier_block *nb, + notifier_fn_t notifier, pdic_notifier_device listener); +extern int pdic_notifier_unregister(struct notifier_block *nb); +extern void pdic_uevent_work(int id, int state); + +const char *pdic_event_src_string(pdic_notifier_device src); +const char *pdic_event_dest_string(pdic_notifier_device dest); +const char *pdic_event_id_string(pdic_notifier_id_t id); +const char *pdic_rid_string(pdic_notifier_rid_t rid); +const char *pdic_usbstatus_string(USB_STATUS usbstatus); + +#ifndef MODULE +extern int pdic_notifier_init(void); +#endif +#endif /* __PDIC_NOTIFIER_H__ */ diff --git a/include/linux/usb/typec/common/pdic_param.h b/include/linux/usb/typec/common/pdic_param.h new file mode 100755 index 000000000000..caf61c0bab39 --- /dev/null +++ b/include/linux/usb/typec/common/pdic_param.h @@ -0,0 +1,50 @@ +/* + * + * Copyright (C) 2021 Samsung Electronics + * + * + * 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, see . + * + */ + +#ifndef __LINUX_PDIC_PARAM_H__ +#define __LINUX_PDIC_PARAM_H__ + +enum pdic_param_usbmode { + PDIC_PARAM_MODE_NO, + PDIC_PARAM_MODE_OB, + PDIC_PARAM_MODE_IB, + PDIC_PARAM_MODE_DL, + PDIC_PARAM_MODE_LC, +}; + +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) +extern int check_factory_mode_boot(void); +extern int get_usb_factory_mode(void); +extern int is_lpcharge_pdic_param(void); +extern int is_factory_mode_pdic_param(void); +extern int is_recovery_mode_pdic_param(void); +#else +static inline int check_factory_mode_boot(void) + {return 0; } +static inline int get_usb_factory_mode(void) + {return PDIC_PARAM_MODE_NO;} +static inline int is_lpcharge_pdic_param(void) + {return 0; } +static inline int is_factory_mode_pdic_param(void) + {return 0; } +static inline int is_recovery_mode_pdic_param(void) + {return 0; } +#endif +#endif /* __LINUX_PDIC_PARAM_H__ */ diff --git a/include/linux/usb/typec/common/pdic_policy.h b/include/linux/usb/typec/common/pdic_policy.h new file mode 100755 index 000000000000..82e95e92d475 --- /dev/null +++ b/include/linux/usb/typec/common/pdic_policy.h @@ -0,0 +1,145 @@ +/* + * + * Copyright (C) 2021 Samsung Electronics + * + * + * 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, see . + * + */ + +#ifndef __LINUX_PDIC_POLICY_H__ +#define __LINUX_PDIC_POLICY_H__ + +#include +#include + +enum pdic_message { + MSG_INITIAL, + MSG_SRC, + MSG_SNK, + MSG_VBUSON, + MSG_VBUSOFF, + MSG_RP56K, + MSG_RP22K, + MSG_RP10K, + MSG_UFP, + MSG_DFP, + MSG_AUDIO, + MSG_DEBUG, + MSG_CC1, + MSG_CC2, + MSG_NOCC_WAKE, + MSG_CC_SHORT, + MSG_SBU_SHORT, + MSG_WATER, + MSG_DRY, + MSG_ROPEN, + MSG_R301K, + MSG_R255K, + MSG_R523K, + MSG_R619K, + MSG_UOPEN, + MSG_U301K, + MSG_U255K, + MSG_U523K, + MSG_U619K, + MSG_FAC_ERR, + MSG_EX_CNT, + MSG_KILLER, + MSG_DCOVER, + MSG_DP_CONN, + MSG_DP_DISCONN, + MSG_DP_LINK_CONF, + MSG_DP_HPD, + MSG_DEVICE_INFO, + MSG_SVID_INFO, + MSG_SELECT_PDO, + MSG_CURRENT_PDO, + MSG_PD_POWER_STATUS, + MSG_FAC_MODE_NOTI_TO_MUIC, + MSG_GET_ROLESWAP_CHECK, + MSG_GET_ACC, + MSG_MUIC_SET_BC12, + MSG_SHUTDOWN, + MSG_CCOFF, + MSG_MAX, +}; + +enum pdic_status { + PP_STATUS_CC_ON, + PP_STATUS_CC_STATE, + PP_STATUS_CC_DIRECTION, + PP_STATUS_CC_RP_CURRENT, + PP_STATUS_POWER_ROLE, + PP_STATUS_DATA_ROLE, + PP_STATUS_VBUS_BOOSTER, + PP_STATUS_RID, + PP_STATUS_UID, + PP_STATUS_WATER, + PP_STATUS_CC_SHORT, + PP_STATUS_SBU_SHORT, + PP_STATUS_EXPLICIT_CONTRACT, +}; + +struct pdic_alt_info { + u16 vendor_id; + u16 product_id; + u16 bcd_device; + u16 svid[12]; + int dp_device; + int dp_pin_assignment; + int dp_selected_pin; + int hpd_state; + int hpd_irq; +}; + +struct pdic_ops { + int (*dr_set)(void *data, enum typec_data_role role); + int (*pr_set)(void *data, enum typec_role role); + int (*vconn_set)(void *data, enum typec_role role); + int (*port_type_set)(void *data, enum typec_port_type type); + int (*get_alt_info)(void *data, struct pdic_alt_info *alt_info); + void (*set_alt_mode)(int); + void (*dp_info_clear)(void *data); + int (*usbpd_sbu_test_read)(void *data); + void (*cc_control_command)(void *data, int is_off); + void (*alt_info_clear)(void *data); +}; + +struct pp_ic_data { + struct device *dev; + const struct pdic_ops *p_ops; + struct pdic_notifier_struct *pd_noti; + int support_pd; + int vbus_dischar_gpio; + void *pp_data; + void *drv_data; + int typec_implemented; +}; + +#ifdef CONFIG_PDIC_POLICY +extern int pdic_policy_update_pdo_list(void *data, int max_v, int min_v, + int max_icl, int cnt, int num); +extern int pdic_policy_send_msg(void *data, int msg, int param1, int param2); +extern void *pdic_policy_init(struct pp_ic_data *ic_data); +extern void pdic_policy_deinit(struct pp_ic_data *ic_data); +#else +static inline int pdic_policy_update_pdo_list(void *data, int max_v, int min_v, + int max_icl, int cnt, int num) {return 0; } +static inline int pdic_policy_send_msg(void *data, int msg, + int param1, int param2) {return 0; } +static inline void *pdic_policy_init(struct pp_ic_data *ic_data) {return NULL; } +static inline void pdic_policy_deinit(struct pp_ic_data *ic_data) {} +#endif +#endif /* __LINUX_PDIC_POLICY_H__ */ diff --git a/include/linux/usb/typec/common/pdic_spec_def.h b/include/linux/usb/typec/common/pdic_spec_def.h new file mode 100755 index 000000000000..2dc8c0388c0a --- /dev/null +++ b/include/linux/usb/typec/common/pdic_spec_def.h @@ -0,0 +1,268 @@ +/* + * + * + * + * Copyright (C) 2021 Samsung Electronics + * + * + * 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 __PDIC_SPEC_DEF_H__ +#define __PDIC_SPEC_DEF_H__ + +#define DP_SVID (0xFF01) +#define SAMSUNG_SVID (0x04E8) +#define PD_SID (0xFF00) + +typedef enum { + S_DISCOVER_IDENTIFY = 0x1, + S_DISCOVER_SVIDS, + S_DISCOVER_MODES, + S_ENTER_MODE, + S_EXIT_MODE, + S_ATTENTION, + S_DISPLAYPORT_STATUS_UPDATE = 0x10, + S_DISPLAYPORT_CONFIGURE = 0x11, +} S_VDM_HEADER_COMMAND; + +typedef enum { + S_TYPE_REQ = 0, + S_TYPE_ACK, + S_TYPE_NAK, + S_TYPE_BUSY, +} S_VDM_HEADER_COMMAND_TYPE; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t Number_of_obj:3, + MSG_ID:3, + Port_Power_Role:1, + Specification_Rev:2, + Port_Data_Role:1, + Reserved:1, + MSG_Type:4; + } BITS; +} S_MSG_HEADER; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t VDM_command:5, + Rsvd2_VDM_header:1, + VDM_command_type:2, + Object_Position:3, + Rsvd_VDM_header:2, + Structured_VDM_Version:2, + VDM_Type:1, + Standard_Vendor_ID:16; + } BITS; +} S_VDM_HEADER; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t USB_Vendor_ID:16, + Rsvd_ID_header:10, + Modal_Operation_Supported:1, + Product_Type:3, + Data_Capable_USB_Device:1, + Data_Capable_USB_Host:1; + } BITS; +} S_ID_HEADER_VDO; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t Cert_TID:20, + Rsvd_cert_VDOer:12; + } BITS; +} S_CERT_STAT_VDO; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t Device_Version:16, + Product_ID:16; + } BITS; +} S_PRODUCT_VDO; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t USB_Superspeed_Signaling_Support:3, + SOP_contoller_present:1, + Vbus_through_cable:1, + Vbus_Current_Handling_Capability:2, + SSRX2_Directionality_Support:1, + SSRX1_Directionality_Support:1, + SSTX2_Directionality_Support:1, + SSTX1_Directionality_Support:1, + Cable_Termination_Type:2, + Cable_Latency:4, + TypeC_to_Plug_Receptacle:1, + TypeC_to_ABC:2, + Rsvd_CABLE_VDO:4, + Cable_Firmware_Version:4, + Cable_HW_Version:4; + } BITS; +} S_CABLE_VDO; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t SVID_1:16, + SVID_0:16; + } BITS; +} S_SVID_VDO; + +typedef struct { + S_MSG_HEADER msg_header; + S_VDM_HEADER vdm_header; + S_ID_HEADER_VDO id_header; + S_CERT_STAT_VDO cert_stat; + S_PRODUCT_VDO product; +} DISCOVER_IDENTITY_RESPONSE; + +typedef struct { + S_MSG_HEADER msg_header; + S_VDM_HEADER vdm_header; + S_SVID_VDO svid_vdo[6]; +} DISCOVER_SVID_RESPONSE; + +typedef struct { + S_MSG_HEADER msg_header; + S_VDM_HEADER vdm_header; +} ENTER_MODE_RESPONSE; + +/* + * + * Display port defines + * + */ + +/* For DP VDM Modes VDO Port_Capability */ +typedef enum +{ + S_RESERVED = 0, + S_UFP_D_CAPABLE = 1, + S_DFP_D_CAPABLE = 2, + S_DFP_D_AND_UFP_D_CAPABLE = 3 +} S_DP_CAPABILITIES_VDO_PORT_CAPABILITY; + +/* For DP VDM Modes VDO Receptacle_Indication */ +typedef enum +{ + S_USB_TYPE_C_PLUG = 0, + S_USB_TYPE_C_RECEPTACLE = 1 +} S_DP_CAPABILITIES_VDO_RECEPTACLE_INDICATION; + +/* For DP_Status_Update Port_Connected */ +typedef enum +{ + S_ADAPTOR_DISABLE = 0, + S_CONNECT_DFP_D = 1, + S_CONNECT_UFP_D = 2, + S_CONNECT_DFP_D_and_UFP_D = 3 +} S_DP_STATUS_UPDATE_VDO_CONNECTED; + +/* For DP_Configure Select_Configuration */ +typedef enum +{ + S_CFG_FOR_USB = 0, + S_CFG_UFP_U_AS_DFP_D = 1, + S_CFG_UFP_U_AS_UFP_D = 2, + S_CFG_RESERVED = 3 +} S_DP_STATUS_UPDATE_VDO_SELECT_CONFIGUTATION; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t Port_Capability:2, + Signalling_DP:4, + Receptacle_Indication:1, + USB_2p0_Not_Used:1, + DFP_D_Pin_Assignments:8, + UFP_D_Pin_Assignments:8, + DP_MODE_VDO_Reserved:8; + } BITS; +} S_DP_CAPABILITIES_VDO; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t Port_Connected:2, + Power_Low:1, + Enabled:1, + Multi_Function_Preference:1, + USB_Configuration_Req:1, + Exit_DP_Mode_Req:1, + HPD_State:1, + HPD_Interrupt:1, + Reserved:23; + } BITS; +} S_DP_STATUS_UPDATE_VDO; + +typedef struct { + S_MSG_HEADER msg_header; + S_VDM_HEADER vdm_header; + S_DP_CAPABILITIES_VDO dp_mode_vdo; +} DP_DISCOVER_MODE_RESPONSE; + +typedef struct { + S_MSG_HEADER msg_header; + S_VDM_HEADER vdm_header; + S_DP_STATUS_UPDATE_VDO dp_status_update_vdo; +} DP_STATUS_RESPONSE; + +typedef struct { + S_MSG_HEADER msg_header; + S_VDM_HEADER vdm_header; +} DP_CONFIGURATION_RESPONSE; + +typedef struct { + S_MSG_HEADER msg_header; + S_VDM_HEADER vdm_header; + S_DP_STATUS_UPDATE_VDO dp_status_update_vdo; +} DP_ATTENTION_REQUEST; +#endif /* __PDIC_SPEC_DEF_H__ */ diff --git a/include/linux/usb/typec/common/pdic_sysfs.h b/include/linux/usb/typec/common/pdic_sysfs.h new file mode 100644 index 000000000000..4da11771df75 --- /dev/null +++ b/include/linux/usb/typec/common/pdic_sysfs.h @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * + * Copyright (C) 2017-2021 Samsung Electronics + * + * Author:Wookwang Lee. , + * 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, see . + * + */ + +#ifndef __PDIC_SYSFS__ +#define __PDIC_SYSFS__ + +extern const struct attribute_group pdic_sysfs_group; + +enum { + BUILT_IN = 0, + UMS = 1, + SPU = 2, + SPU_VERIFICATION = 3, + FWUP_CMD_MAX = 4, +}; + +static const int RO_PERM = 0444; +static const int WO_PERM = 0200; +static const int RW_PERM = 0644; + +enum pdic_sysfs_property { + PDIC_SYSFS_PROP_CHIP_NAME = 0, + PDIC_SYSFS_PROP_CUR_VERSION, + PDIC_SYSFS_PROP_SRC_VERSION, + PDIC_SYSFS_PROP_LPM_MODE, + PDIC_SYSFS_PROP_STATE, + PDIC_SYSFS_PROP_RID, + PDIC_SYSFS_PROP_CTRL_OPTION, + PDIC_SYSFS_PROP_BOOTING_DRY, + PDIC_SYSFS_PROP_FW_UPDATE, + PDIC_SYSFS_PROP_FW_UPDATE_STATUS, + PDIC_SYSFS_PROP_FW_WATER, + PDIC_SYSFS_PROP_DEX_FAN_UVDM, + PDIC_SYSFS_PROP_ACC_DEVICE_VERSION, + PDIC_SYSFS_PROP_DEBUG_OPCODE, + PDIC_SYSFS_PROP_CONTROL_GPIO, + PDIC_SYSFS_PROP_USBPD_IDS, + PDIC_SYSFS_PROP_USBPD_TYPE, /* for SWITCH_STATE */ + PDIC_SYSFS_PROP_CC_PIN_STATUS, + PDIC_SYSFS_PROP_RAM_TEST, + PDIC_SYSFS_PROP_SBU_ADC, + PDIC_SYSFS_PROP_CC_ADC, + PDIC_SYSFS_PROP_VSAFE0V_STATUS, + PDIC_SYSFS_PROP_OVP_IC_SHUTDOWN, + PDIC_SYSFS_PROP_HMD_POWER, + PDIC_SYSFS_PROP_SET_WATER_THRESHOLD, + PDIC_SYSFS_PROP_USBPD_WATER_CHECK, + PDIC_SYSFS_PROP_15MODE_WATERTEST_TYPE, + PDIC_SYSFS_PROP_VBUS_ADC, + PDIC_SYSFS_PROP_USB_BOOT_MODE, + PDIC_SYSFS_PROP_DP_SBU_SW_SEL, + PDIC_SYSFS_PROP_NOVBUS_RP22K, + PDIC_SYSFS_PROP_MAX_COUNT, +}; +struct _pdic_data_t; +typedef struct _pdic_sysfs_property_t { + enum pdic_sysfs_property *properties; + size_t num_properties; + int (*get_property)(struct _pdic_data_t *ppdic_data, + enum pdic_sysfs_property prop, + char *buf); + ssize_t (*set_property)(struct _pdic_data_t *ppdic_data, + enum pdic_sysfs_property prop, + const char *buf, + size_t size); + /* Decides whether userspace can change a specific property */ + int (*property_is_writeable)(struct _pdic_data_t *ppdic_data, + enum pdic_sysfs_property prop); + int (*property_is_writeonly)(struct _pdic_data_t *ppdic_data, + enum pdic_sysfs_property prop); +} pdic_sysfs_property_t, *ppdic_sysfs_property_t; + +void pdic_sysfs_init_attrs(void); +#endif + diff --git a/include/linux/usb/typec/manager/if_cb_manager.h b/include/linux/usb/typec/manager/if_cb_manager.h new file mode 100644 index 000000000000..4584addad6bc --- /dev/null +++ b/include/linux/usb/typec/manager/if_cb_manager.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2018-2019 Samsung Electronics + * + * 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. + * + * + */ + +#ifndef __IF_CB_MANAGER_H__ +#define __IF_CB_MANAGER_H__ + +enum sbu_switch_status { + UNDEFINED = -1, + OPEN_SBU = 0, + CLOSE_SBU_CC1_ACTIVE = 1, + CLOSE_SBU_CC2_ACTIVE = 2, +}; + +struct usb_ops { + void (*usb_set_vbus_current)(void *data, int state); + int (*usb_restart_host_mode)(void *data, int lanes); +}; + +struct muic_ops { + int (*muic_check_usb_killer)(void *data); + void (*muic_set_bypass)(void *data, int enable); + void (*muic_set_bc12)(void *data, int enable); +}; + +struct usbpd_ops { + int (*usbpd_sbu_test_read)(void *data); + void (*usbpd_set_host_on)(void *data, int mode); + void (*usbpd_cc_control_command)(void *data, int is_off); + void (*usbpd_wait_entermode)(void *data, int on); + void (*usbpd_sbu_switch_control)(void *data, int on); +}; + +struct lvs_ops { + int (*lvs_cc_attach)(void *data, int on); +}; + +struct usb_dev { + const struct usb_ops *ops; + void *data; +}; + +struct muic_dev { + const struct muic_ops *ops; + void *data; +}; + +struct usbpd_dev { + const struct usbpd_ops *ops; + void *data; +}; + +struct lvs_dev { + const struct lvs_ops *ops; + void *data; +}; + +struct if_cb_manager { + struct usb_dev *usb_d; + struct muic_dev *muic_d; + struct usbpd_dev *usbpd_d; + struct lvs_dev *lvs_d; +}; + +extern struct if_cb_manager *register_usb(struct usb_dev *usb); +extern struct if_cb_manager *register_muic(struct muic_dev *muic); +extern struct if_cb_manager *register_usbpd(struct usbpd_dev *usbpd); +extern struct if_cb_manager *register_lvs(struct lvs_dev *lvs); +extern void usb_set_vbus_current(struct if_cb_manager *man_core, int state); +extern int usb_restart_host_mode(struct if_cb_manager *man_core, int lanes); +extern int muic_check_usb_killer(struct if_cb_manager *man_core); +extern void muic_set_bypass(struct if_cb_manager *man_core, int enable); +extern void muic_set_bc12(struct if_cb_manager *man_core, int enable); +extern int usbpd_sbu_test_read(struct if_cb_manager *man_core); +extern void usbpd_set_host_on(struct if_cb_manager *man_core, int mode); +extern void usbpd_cc_control_command(struct if_cb_manager *man_core, int is_off); +extern void usbpd_wait_entermode(struct if_cb_manager *man_core, int on); +extern void usbpd_sbu_switch_control(int on); + +#endif /* __IF_CB_MANAGER_H__ */ diff --git a/include/linux/usb/typec/manager/usb_typec_manager_hwparam.h b/include/linux/usb/typec/manager/usb_typec_manager_hwparam.h new file mode 100644 index 000000000000..c68c33b7209c --- /dev/null +++ b/include/linux/usb/typec/manager/usb_typec_manager_hwparam.h @@ -0,0 +1,31 @@ +/* + * header file supporting USB Type-C Manager hwpram call chain information + * + * Copyright (C) 2020 Samsung Electronics + * + * 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 __USB_TYPEC_MANAGER_HWPARAM_H__ +#define __USB_TYPEC_MANAGER_HWPARAM_H__ + +void water_dry_time_update(int mode); +void wVbus_time_update(int status); +unsigned long manager_hw_param_update(int param); +void manager_hw_param_init(void); +void usb_enum_hw_param_data_update(int speed); + +#endif /* __USB_TYPEC_MANAGER_HWPARAM_H__ */ diff --git a/include/linux/usb/typec/manager/usb_typec_manager_notifier.h b/include/linux/usb/typec/manager/usb_typec_manager_notifier.h new file mode 100644 index 000000000000..fe0a308cc1a4 --- /dev/null +++ b/include/linux/usb/typec/manager/usb_typec_manager_notifier.h @@ -0,0 +1,277 @@ +/* + * header file supporting USB Type-C Manager notifier call chain information + * + * Copyright (C) 2020 Samsung Electronics + * + * 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 __USB_TYPEC_MANAGER_NOTIFIER_H__ +#define __USB_TYPEC_MANAGER_NOTIFIER_H__ + +#include +#include +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) +#include +#endif +#include + +#define TYPEC_MANAGER_MAJ_VERSION 2 +#define TYPEC_MANAGER_MIN_VERSION 2 + +/* USB TypeC Manager notifier call sequence, + * largest priority number device will be called first. */ +typedef enum { +/* MUIC */ + MANAGER_NOTIFY_MUIC_NONE = 0, + MANAGER_NOTIFY_MUIC_USB, + MANAGER_NOTIFY_MUIC_OTG, + MANAGER_NOTIFY_MUIC_CHARGER, + MANAGER_NOTIFY_MUIC_TIMEOUT_OPEN_DEVICE, + MANAGER_NOTIFY_MUIC_UART, + +/* PDIC */ + MANAGER_NOTIFY_PDIC_INITIAL = 20, + MANAGER_NOTIFY_PDIC_WACOM, /* Low Priority */ + MANAGER_NOTIFY_PDIC_SENSORHUB, + MANAGER_NOTIFY_PDIC_USBDP, + MANAGER_NOTIFY_PDIC_DP, + MANAGER_NOTIFY_PDIC_SUB_BATTERY, + MANAGER_NOTIFY_PDIC_BATTERY, + MANAGER_NOTIFY_PDIC_USB, + MANAGER_NOTIFY_PDIC_MUIC, /* High Priority */ + MANAGER_NOTIFY_PDIC_DELAY_DONE, +} manager_notifier_device_t; + +typedef enum { + PD_USB_TYPE, + PD_TA_TYPE, + PD_NONE_TYPE, +} pd_usb_state_t; + +typedef enum +{ + VBUS_NOTIFIER = 1 << 0, + PDIC_NOTIFIER = 1 << 1, + MUIC_NOTIFIER = 1 << 2, + ALL_NOTIFIER = VBUS_NOTIFIER | PDIC_NOTIFIER | MUIC_NOTIFIER +}notifier_register; + +typedef struct +{ + uint64_t src:4; + uint64_t dest:4; + uint64_t id:8; + uint64_t sub1:16; + uint64_t sub2:16; + uint64_t sub3:16; + void *pd; +} MANAGER_NOTI_TYPEDEF; + +struct typec_manager_event_work +{ + struct work_struct typec_manager_work; + MANAGER_NOTI_TYPEDEF event; +}; + +struct manager_dwork { + struct delayed_work dwork; + bool pending; +}; + +struct manager_usb_dwork { + struct delayed_work dwork; + int data; + bool pending; +}; + +struct manager_dp { + int is_support; + int attach_state; + int cable_type; + int hpd_state; + int is_connect; + int hs_connect; + int check_done; +}; + +struct manager_water { + int detected; + int detOnPowerOff; + int wVbus_det; + int report_type; +}; + +struct typec_manager_muic { + int attach_state; + int cable_type; +}; + +struct typec_manager_usb { + int dr; + int notified_dr; + int enum_state; + bool enable_state; + u64 event_time_stamp; +}; + +typedef struct _manager_data_t +{ + struct blocking_notifier_head manager_muic_notifier; + struct blocking_notifier_head manager_notifier; + struct notifier_block pdic_nb; + struct notifier_block muic_nb; +#if IS_ENABLED(CONFIG_VBUS_NOTIFIER) + struct notifier_block vbus_nb; +#endif +#if defined(CONFIG_CABLE_TYPE_NOTIFIER) + struct notifier_block cable_type_nb; +#endif + int confirm_notifier_register; + int notifier_register_try_count; + + struct delayed_work manager_init_work; + struct workqueue_struct *manager_noti_wq; + struct workqueue_struct *manager_muic_noti_wq; + struct manager_dwork usb_enum_check; + struct manager_dwork usb_event_by_vbus; +#if IS_ENABLED(CONFIG_MUIC_POGO) + struct manager_dwork usb_event_by_pogo; +#endif + struct manager_usb_dwork manager_usb_event_delayed_work; + + struct mutex mo_lock; + int vbus_state; +#if IS_ENABLED(CONFIG_MUIC_POGO) + int is_muic_pogo; +#endif + int classified_cable_type; + + int pdic_attach_state; + int pdic_rid_state; + int alt_is_support; + int usb_factory; + + u64 otg_stamp; + int vbus_by_otg_detection; + + int pd_con_state; + int svid_info; + void *pd; + + char fac_control[16]; + + struct typec_manager_usb usb; + struct typec_manager_muic muic; +#ifdef CONFIG_USE_SECOND_MUIC + struct typec_manager_muic second_muic; +#endif + struct manager_water water; + struct manager_dp dp; + struct notifier_block manager_external_notifier_nb; + struct typec_manager_gadget_ops *gadget_ops; +}manager_data_t; + +struct typec_manager_gadget_ops { + void *driver_data; + int (*get_cmply_link_state)(void *dev); +}; + +typedef union { + struct { + uint64_t src:4; + uint64_t dest:4; + uint64_t id:8; + uint64_t sub1:16; + uint64_t sub2:16; + uint64_t sub3:16; + }; + uint64_t noti_event; + +} MANAGER_NOTI_TYPEDEF_REF; + +#define PDIC_BATTERY (1<<0) +#define PDIC_USB (1<<1) +#define PDIC_DP (1<<2) +#define PDIC_DELAY_DONE (1<<3) + +/* Timeout to check for USB enumeration */ +#define CANCEL_USB_DWORK 0 +#define MAX_USB_DWORK_TIME 120000 +#define MIN_USB_DWORK_TIME 2000 +#ifdef CONFIG_USB_CONFIGFS_F_MBIM +#define BOOT_USB_DWORK_TIME 9000 +#else +#define BOOT_USB_DWORK_TIME 18000 +#endif + +/* Timeout for USB off when Vbus is in LOW state */ +#define VBUS_USB_OFF_TIMEOUT 1000 + +/* Time to check whether it is VBUS by OTG to prevent moisture popup error */ +#define OTG_VBUS_CHECK_TIME 300 + +/* Time to retry when Notifier registration fails */ +#define NOTIFIER_REG_RETRY_TIME 2000 +#define NOTIFIER_REG_RETRY_COUNT 5 + +#define USB_EVENT_INTERVAL_CHECK_TIME 300 + +#define MANAGER_NOTIFIER_BLOCK(name) \ + struct notifier_block (name) + +#define SET_MANAGER_NOTIFIER_BLOCK(nb, fn, dev) do { \ + (nb)->notifier_call = (fn); \ + (nb)->priority = (dev); \ + } while (0) + +#define DESTROY_MANAGER_NOTIFIER_BLOCK(nb) \ + SET_MANAGER_NOTIFIER_BLOCK(nb, NULL, -1) + +/* pdic notifier register/unregister API + * for used any where want to receive pdic attached device attach/detach. */ +int manager_notifier_register(struct notifier_block *nb, + notifier_fn_t notifier, manager_notifier_device_t listener); +int manager_notifier_unregister(struct notifier_block *nb); + +void manager_notifier_usbdp_support(void); +void set_usb_enumeration_state(int state); +void set_usb_enable_state(void); +void probe_typec_manager_gadget_ops (struct typec_manager_gadget_ops *ops); + +#ifdef CONFIG_USB_USING_ADVANCED_USBLOG +#define utmanager_info(fmt, ...) \ + ({ \ + pr_info(fmt, ##__VA_ARGS__); \ + printk_usb(NOTIFY_PRINTK_USB_NORMAL, fmt, ##__VA_ARGS__); \ + }) +#define utmanager_err(fmt, ...) \ + ({ \ + pr_err(fmt, ##__VA_ARGS__); \ + printk_usb(NOTIFY_PRINTK_USB_NORMAL, fmt, ##__VA_ARGS__); \ + }) +#else +#define utmanager_info(fmt, ...) \ + ({ \ + pr_info(fmt, ##__VA_ARGS__); \ + }) +#define utmanager_err(fmt, ...) \ + ({ \ + pr_err(fmt, ##__VA_ARGS__); \ + }) +#endif + +#endif /* __USB_TYPEC_MANAGER_NOTIFIER_H__ */ diff --git a/include/linux/usb/typec/maxim/max77705-muic.h b/include/linux/usb/typec/maxim/max77705-muic.h new file mode 100755 index 000000000000..27ffb2b5afae --- /dev/null +++ b/include/linux/usb/typec/maxim/max77705-muic.h @@ -0,0 +1,435 @@ +/* + * max77705-muic.h - MUIC for the Maxim 77705 + * + * Copyright (C) 2015 Samsung Electrnoics + * Insun Choi + * + * 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, see . + * + * This driver is based on max14577-muic.h + * + */ + +#ifndef __MAX77705_MUIC_H__ +#define __MAX77705_MUIC_H__ + +#include +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) +#include +#endif + +#define MUIC_DEV_NAME "muic-max77705" + +#define MUIC_IRQ_INIT_DETECT (-1) +#define MUIC_IRQ_PDIC_HANDLER (-2) +#define MUIC_IRQ_VBUS_WA (-3) +#define MUIC_IRQ_POGO_ADC (-4) + +enum max77705_adc { + MAX77705_UIADC_GND = 0x00, + MAX77705_UIADC_255K = 0x03, + MAX77705_UIADC_301K = 0x04, + MAX77705_UIADC_523K = 0x05, + MAX77705_UIADC_619K = 0x06, + MAX77705_UIADC_OPEN = 0x07, + + MAX77705_UIADC_DONTCARE = 0xfe, /* ADC don't care for MHL */ + MAX77705_UIADC_ERROR = 0xff, /* ADC value read error */ +}; + +enum max77705_vbadc { + MAX77705_VBADC_3_8V_UNDER = 0x0, + MAX77705_VBADC_3_8V_TO_4_5V = 0x1, + MAX77705_VBADC_4_5V_TO_5_5V = 0x2, + MAX77705_VBADC_5_5V_TO_6_5V = 0x3, + MAX77705_VBADC_6_5V_TO_7_5V = 0x4, + MAX77705_VBADC_7_5V_TO_8_5V = 0x5, + MAX77705_VBADC_8_5V_TO_9_5V = 0x6, + MAX77705_VBADC_9_5V_TO_10_5V = 0x7, + MAX77705_VBADC_10_5V_TO_11_5V = 0x8, + MAX77705_VBADC_11_5V_TO_12_5V = 0x9, + MAX77705_VBADC_12_5V_OVER = 0xa, +}; + +enum max77705_muic_command_opcode { + COMMAND_BC_CTRL1_READ = 0x01, + COMMAND_BC_CTRL1_WRITE = 0x02, + COMMAND_BC_CTRL2_READ = 0x03, + COMMAND_BC_CTRL2_WRITE = 0x04, + COMMAND_CONTROL1_READ = 0x05, + COMMAND_CONTROL1_WRITE = 0x06, + COMMAND_CONTROL2_READ = 0x07, + COMMAND_CONTROL2_WRITE = 0x08, + /* AFC */ + COMMAND_HV_CONTROL_READ = 0x11, + COMMAND_HV_CONTROL_WRITE = 0x12, + COMMAND_AFC_HV_WRITE = 0x20, + COMMAND_AFC_RESULT_READ = 0x21, + COMMAND_QC_2_0_SET = 0x22, + + /* not cmd opcode, for notifier */ + NOTI_ATTACH = 0xfa, + NOTI_DETACH = 0xfb, + NOTI_LOGICALLY_ATTACH = 0xfc, + NOTI_LOGICALLY_DETACH = 0xfd, + + COMMAND_NONE = 0xff, +}; + +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77705_AFC) +enum max77705_afc_status_type { + MAX77705_MUIC_AFC_STATUS_CLEAR = (0x0), + MAX77705_MUIC_AFC_DISABLE_CHANGE_DURING_WORK = (0x1 << 0), + MAX77705_MUIC_AFC_DISABLE_CHANGE_DURING_WORK_END = ~(0x1 << 0), + MAX77705_MUIC_AFC_SET_VOLTAGE_CHANGE_DURING_WORK = (0x1 << 1), + MAX77705_MUIC_AFC_SET_VOLTAGE_CHANGE_DURING_WORK_END = ~(0x1 << 1), + MAX77705_MUIC_AFC_WORK_PROCESS = (0x1 << 2), + MAX77705_MUIC_AFC_WORK_PROCESS_END = ~(0x1 << 2) +}; +#endif /* CONFIG_HV_MUIC_MAX77705_AFC */ + +#define AFC_OP_OUT_LEN 11 /* OPCODE(1) + Result(1) + VBADC(1) + RX Data(8) */ + +#if defined(CONFIG_HICCUP_CHARGER) +enum MUIC_HICCUP_MODE { + MUIC_HICCUP_MODE_OFF = 0, + MUIC_HICCUP_MODE_ON, +}; +#endif + +#if IS_ENABLED(CONFIG_MUIC_MAX77705_PDIC) +#define MUIC_PDIC_NOTI_ATTACH (1) +#define MUIC_PDIC_NOTI_DETACH (-1) +#define MUIC_PDIC_NOTI_UNDEFINED (0) + +struct max77705_muic_ccic_evt { + int ccic_evt_attached; /* 1: attached, -1: detached, 0: undefined */ + int ccic_evt_rid; /* the last rid */ + int ccic_evt_rprd; /*rprd */ + int ccic_evt_roleswap; /* check rprd role swap event */ + int ccic_evt_dcdcnt; /* count dcd timeout */ +}; +#endif /* CONFIG_MUIC_MAX77705_PDIC */ + +/* muic chip specific internal data structure */ +struct max77705_muic_data { + struct device *dev; + struct i2c_client *i2c; /* i2c addr: 0x4A; MUIC */ + struct mutex muic_mutex; + struct wakeup_source *muic_ws; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + struct wakeup_source *afc_retry_ws; +#endif + /* model dependent mfd platform data */ + struct max77705_platform_data *mfd_pdata; + struct max77705_usbc_platform_data *usbc_pdata; + + int irq_uiadc; + int irq_chgtyp; + int irq_spr; + int irq_dcdtmo; + int irq_vbadc; + int irq_vbusdet; + + /* model dependent muic platform data */ + struct muic_platform_data *pdata; + + /* muic current attached device */ + muic_attached_dev_t attached_dev; + void *attached_func; + + /* muic support vps list */ + bool muic_support_list[ATTACHED_DEV_NUM]; + + bool is_muic_ready; + bool is_muic_reset; + + u8 adcmode; + u8 switch_val; + + /* check is otg test */ + bool is_otg_test; + + /* muic HV charger */ + bool is_factory_start; + bool is_check_hv; + bool is_charger_ready; + bool is_afc_reset; + bool is_skip_bigdata; + bool is_charger_mode; + bool is_usb_fail; + + u8 is_boot_dpdnvden; + + struct delayed_work afc_work; + struct work_struct afc_handle_work; + struct mutex afc_lock; + unsigned char afc_op_dataout[AFC_OP_OUT_LEN]; + int hv_voltage; + int reserve_hv_voltage; + int afc_retry; + int dcdtmo_retry; + int bc1p2_retry_count; + + /* hiccup mode flag */ + int is_hiccup_mode; + + /* muic status value */ + u8 status1; + u8 status2; + u8 status3; + u8 status4; + u8 status5; + u8 status6; + + struct delayed_work debug_work; + struct delayed_work vbus_wa_work; + + /* Fake vbus flag */ + int fake_chgtyp; + +#if defined(CONFIG_USB_EXTERNAL_NOTIFY) + /* USB Notifier */ + struct notifier_block usb_nb; +#endif +#if IS_ENABLED(CONFIG_MUIC_MAX77705_PDIC) + struct max77705_muic_ccic_evt ccic_info_data; + struct work_struct ccic_info_data_work; + bool afc_water_disable; + int ccic_evt_id; + /* PDIC Notifier */ +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + struct notifier_block manager_nb; +#else + struct notifier_block ccic_nb; +#endif +#if IS_ENABLED(CONFIG_MUIC_POGO) + int pogo_adc; +#endif /* CONFIG_MUIC_POGO */ +#endif /* CONFIG_MUIC_MAX77705_PDIC */ +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + struct muic_dev muic_d; + struct if_cb_manager *man; +#endif + +}; + +/* max77705 muic register read/write related information defines. */ +#define REG_NONE 0xff +#define REG_FULL_MASKING 0xff + +#define INTMASK3_RESET 0x40 +#define INTMASK3_INIT 0xc3 + +/* MAX77705 REGISTER ENABLE or DISABLE bit */ +enum max77705_reg_bit_control { + MAX77705_DISABLE_BIT = 0, + MAX77705_ENABLE_BIT, +}; + +/* MAX77705 STATUS1 register */ +#define USBC_STATUS1_UIADC_SHIFT 0 +#define USBC_STATUS1_VBADC_SHIFT 4 +#define USBC_STATUS1_UIADC_MASK (0x7 << USBC_STATUS1_UIADC_SHIFT) +#define USBC_STATUS1_VBADC_MASK (0xf << USBC_STATUS1_VBADC_SHIFT) + +/* MAX77705 BC STATUS register */ +#define BC_STATUS_CHGTYP_SHIFT 0 +#define BC_STATUS_DCDTMO_SHIFT 2 +#define BC_STATUS_PRCHGTYP_SHIFT 3 +#define BC_STATUS_VBUSDET_SHIFT 7 +#define BC_STATUS_CHGTYP_MASK (0x3 << BC_STATUS_CHGTYP_SHIFT) +#define BC_STATUS_DCDTMO_MASK (0x1 << BC_STATUS_DCDTMO_SHIFT) +#define BC_STATUS_PRCHGTYP_MASK (0x7 << BC_STATUS_PRCHGTYP_SHIFT) +#define BC_STATUS_VBUSDET_MASK (0x1 << BC_STATUS_VBUSDET_SHIFT) + +/* MAX77705 USBC_STATUS2 register - include ERROR message. */ +#define USBC_STATUS2_SYSMSG_SHIFT 0 +#define USBC_STATUS2_SYSMSG_MASK (0xff << USBC_STATUS2_SYSMSG_SHIFT) + +/* MAX77705 DAT_IN register */ +#define DAT_IN_SHIFT 0 +#define DAT_IN_MASK (0xff << DAT_IN_SHIFT) + +/* MAX77705 DAT_OUT register */ +#define DAT_OUT_SHIFT 0 +#define DAT_OUT_MASK (0xff << DAT_OUT_SHIFT) + +/* MAX77705 BC_CTRL1 */ +#define BC_CTRL1_DCDCpl_SHIFT 7 +#define BC_CTRL1_UIDEN_SHIFT 6 +#define BC_CTRL1_NoAutoIBUS_SHIFT 5 +#define BC_CTRL1_3ADCPDet_SHIFT 4 +#define BC_CTRL1_CHGDetMan_SHIFT 1 +#define BC_CTRL1_CHGDetEn_SHIFT 0 +#define BC_CTRL1_DCDCpl_MASK (0x1 << BC_CTRL1_DCDCpl_SHIFT) +#define BC_CTRL1_UIDEN_MASK (0x1 << BC_CTRL1_UIDEN_SHIFT) +#define BC_CTRL1_NoAutoIBUS_MASK (0x1 << BC_CTRL1_NoAutoIBUS_SHIFT) +#define BC_CTRL1_3ADCPDet_MASK (0x1 << BC_CTRL1_3ADCPDet_SHIFT) +#define BC_CTRL1_CHGDetMan_MASK (0x1 << BC_CTRL1_CHGDetMan_SHIFT) +#define BC_CTRL1_CHGDetEn_MASK (0x1 << BC_CTRL1_CHGDetEn_SHIFT) + +/* MAX77705 BC_CTRL2 */ +#define BC_CTRL2_DNMonEn_SHIFT 5 +#define BC_CTRL2_DPDNMan_SHIFT 4 +#define BC_CTRL2_DPDrv_SHIFT 2 +#define BC_CTRL2_DNDrv_SHIFT 0 +#define BC_CTRL2_DNMonEn_MASK (0x1 << BC_CTRL2_DNMonEn_SHIFT) +#define BC_CTRL2_DPDNMan_MASK (0x1 << BC_CTRL2_DPDNMan_SHIFT) +#define BC_CTRL2_DPDrv_MASK (0x3 << BC_CTRL2_DPDrv_SHIFT) +#define BC_CTRL2_DNDrv_MASK (0x3 << BC_CTRL2_DNDrv_SHIFT) + +/* MAX77705 SWITCH COMMAND */ +#define COMN1SW_SHIFT 0 +#define COMP2SW_SHIFT 3 +#define RCPS_SHIFT 6 +#define NOBCCOMP_SHIFT 7 +#define COMN1SW_MASK (0x7 << COMN1SW_SHIFT) +#define COMP2SW_MASK (0x7 << COMP2SW_SHIFT) +#define RCPS_MASK (0x1 << RCPS_SHIFT) +#define NOBCCOMP_MASK (0x1 << NOBCCOMP_SHIFT) + +/* MAX77705 ID Monitor Config */ +#define MODE_SHIFT 2 +#define MODE_MASK (0x3 << MODE_SHIFT) + +enum { + VB_LOW = 0x00, + VB_HIGH = (0x1 << BC_STATUS_VBUSDET_SHIFT), + + VB_DONTCARE = 0xff, +}; + +/* MAX77705 MUIC Charger Type Detection Output Value */ +enum { + /* No Valid voltage at VB (Vvb < Vvbdet) */ + CHGTYP_NO_VOLTAGE = 0x00, + /* Unknown (D+/D- does not present a valid USB charger signature) */ + CHGTYP_USB = 0x01, + /* Charging Downstream Port */ + CHGTYP_CDP = 0x02, + /* Dedicated Charger (D+/D- shorted) */ + CHGTYP_DEDICATED_CHARGER = 0x03, + + /* Hiccup mode, Set D+/D- to GND */ + CHGTYP_HICCUP_MODE = 0xfa, + /* DCD Timeout, Open D+/D- */ + CHGTYP_TIMEOUT_OPEN = 0xfb, + /* Any charger w/o USB */ + CHGTYP_UNOFFICIAL_CHARGER = 0xfc, + /* Any charger type */ + CHGTYP_ANY = 0xfd, + /* Don't care charger type */ + CHGTYP_DONTCARE = 0xfe, + + CHGTYP_MAX, + CHGTYP_INIT, + CHGTYP_MIN = CHGTYP_NO_VOLTAGE +}; + +/* MAX77705 MUIC Special Charger Type Detection Output value */ +enum { + PRCHGTYP_UNKNOWN = 0x00, + PRCHGTYP_SAMSUNG_2A = 0x01, + PRCHGTYP_APPLE_500MA = 0x02, + PRCHGTYP_APPLE_1A = 0x03, + PRCHGTYP_APPLE_2A = 0x04, + PRCHGTYP_APPLE_12W = 0x05, + PRCHGTYP_3A_DCP = 0x06, + PRCHGTYP_RFU = 0x07, +}; + +enum { + PROCESS_ATTACH = 0, + PROCESS_LOGICALLY_DETACH, + PROCESS_NONE, +}; + +/* muic register value for COMN1, COMN2 in Switch command */ + +/* + * MAX77705 Switch values + * NoBCComp [7] / RCPS [6] / D+ [5:3] / D- [2:0] + * 0: Compare with BC1.2 / 1: Ignore BC1.2, Manual control + * 0: Disable / 1: Enable + * 000: Open / 001, 100: USB / 011, 101: UART + */ +enum max77705_switch_val { + MAX77705_MUIC_NOBCCOMP_DIS = 0x0, + MAX77705_MUIC_NOBCCOMP_EN = 0x1, + + MAX77705_MUIC_RCPS_DIS = 0x0, + MAX77705_MUIC_RCPS_EN = 0x1, + MAX77705_MUIC_RCPS_VAL = MAX77705_MUIC_RCPS_DIS, + + MAX77705_MUIC_COM_USB = 0x01, + MAX77705_MUIC_COM_AUDIO = 0x02, + MAX77705_MUIC_COM_UART = 0x03, + MAX77705_MUIC_COM_USB_CP = 0x04, + MAX77705_MUIC_COM_UART_CP = 0x05, + MAX77705_MUIC_COM_OPEN = 0x07, +}; + +enum { + COM_OPEN = (MAX77705_MUIC_NOBCCOMP_DIS << NOBCCOMP_SHIFT) | + (MAX77705_MUIC_RCPS_VAL << RCPS_SHIFT) | + (MAX77705_MUIC_COM_OPEN << COMP2SW_SHIFT) | + (MAX77705_MUIC_COM_OPEN << COMN1SW_SHIFT), + COM_USB = (MAX77705_MUIC_NOBCCOMP_DIS << NOBCCOMP_SHIFT) | + (MAX77705_MUIC_RCPS_VAL << RCPS_SHIFT) | + (MAX77705_MUIC_COM_USB << COMP2SW_SHIFT) | + (MAX77705_MUIC_COM_USB << COMN1SW_SHIFT), + COM_UART = (MAX77705_MUIC_NOBCCOMP_EN << NOBCCOMP_SHIFT) | + (MAX77705_MUIC_RCPS_VAL << RCPS_SHIFT) | + (MAX77705_MUIC_COM_UART << COMP2SW_SHIFT) | + (MAX77705_MUIC_COM_UART << COMN1SW_SHIFT), + COM_USB_CP = (MAX77705_MUIC_NOBCCOMP_EN << NOBCCOMP_SHIFT) | + (MAX77705_MUIC_RCPS_VAL << RCPS_SHIFT) | + (MAX77705_MUIC_COM_USB_CP << COMP2SW_SHIFT) | + (MAX77705_MUIC_COM_USB_CP << COMN1SW_SHIFT), + COM_UART_CP = (MAX77705_MUIC_NOBCCOMP_EN << NOBCCOMP_SHIFT) | + (MAX77705_MUIC_RCPS_VAL << RCPS_SHIFT) | + (MAX77705_MUIC_COM_UART_CP << COMP2SW_SHIFT) | + (MAX77705_MUIC_COM_UART_CP << COMN1SW_SHIFT), +}; + +extern struct muic_platform_data muic_pdata; +extern struct device *switch_device; +extern int max77705_muic_probe(struct max77705_usbc_platform_data *usbc_data); +extern int max77705_muic_remove(struct max77705_usbc_platform_data *usbc_data); +extern void max77705_muic_shutdown(struct max77705_usbc_platform_data *usbc_data); +extern int max77705_muic_suspend(struct max77705_usbc_platform_data *usbc_data); +extern int max77705_muic_resume(struct max77705_usbc_platform_data *usbc_data); +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77705_AFC) +extern bool max77705_muic_check_is_enable_afc(struct max77705_muic_data *muic_data, muic_attached_dev_t new_dev); +extern void max77705_muic_check_afc_disabled(struct max77705_muic_data *muic_data); +extern void max77705_muic_clear_hv_control(struct max77705_muic_data *muic_data); +extern int max77705_muic_afc_hv_set(struct max77705_muic_data *muic_data, int voltage); +extern int max77705_muic_qc_hv_set(struct max77705_muic_data *muic_data, int voltage); +extern void max77705_muic_handle_detect_dev_afc(struct max77705_muic_data *muic_data, unsigned char *data); +extern void max77705_muic_handle_detect_dev_qc(struct max77705_muic_data *muic_data, unsigned char *data); +extern void max77705_muic_handle_detect_dev_hv(struct max77705_muic_data *muic_data, unsigned char *data); +extern void max77705_muic_disable_afc_protocol(struct max77705_muic_data *muic_data); +extern int __max77705_muic_afc_set_voltage(struct max77705_muic_data *muic_data, int voltage); +#endif /* CONFIG_HV_MUIC_MAX77705_AFC */ +#if IS_ENABLED(CONFIG_MUIC_MAX77705_PDIC) +extern void max77705_muic_register_ccic_notifier(struct max77705_muic_data *muic_data); +extern void max77705_muic_unregister_ccic_notifier(struct max77705_muic_data *muic_data); +#endif /* CONFIG_MUIC_MAX77705_PDIC */ +#if defined(CONFIG_USB_EXTERNAL_NOTIFY) +extern void muic_send_dock_intent(int type); +#endif /* CONFIG_USB_EXTERNAL_NOTIFY */ +extern void max77705_muic_enable_detecting_short(struct max77705_muic_data *muic_data); +#endif /* __MAX77705_MUIC_H__ */ + diff --git a/include/linux/usb/typec/maxim/max77705.h b/include/linux/usb/typec/maxim/max77705.h new file mode 100644 index 000000000000..0d7fb03d57c3 --- /dev/null +++ b/include/linux/usb/typec/maxim/max77705.h @@ -0,0 +1,639 @@ +/* + * Copyrights (C) 2017 Samsung Electronics, Inc. + * Copyrights (C) 2017 Maxim Integrated Products, 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. + */ + +#ifndef __LINUX_MFD_MAX77705_H +#define __LINUX_MFD_MAX77705_H + +#define MAX77705_CCPD_NAME "MAX77705" + +#undef __CONST_FFS +#define __CONST_FFS(_x) \ + ((_x) & 0x0F ? \ + ((_x) & 0x03 ? ((_x) & 0x01 ? 0 : 1) : ((_x) & 0x04 ? 2 : 3)) : \ + ((_x) & 0x30 ? ((_x) & 0x10 ? 4 : 5) : ((_x) & 0x40 ? 6 : 7))) + +#undef FFS +#define FFS(_x) \ + ((_x) ? __CONST_FFS(_x) : 0) + +#undef BIT_RSVD +#define BIT_RSVD 0 + +#undef BITS +#define BITS(_end, _start) \ + ((BIT(_end) - BIT(_start)) + BIT(_end)) + +#undef __BITS_GET +#define __BITS_GET(_word, _mask, _shift) \ + (((_word) & (_mask)) >> (_shift)) + +#undef BITS_GET +#define BITS_GET(_word, _bit) \ + __BITS_GET(_word, _bit, FFS(_bit)) + +#undef __BITS_SET +#define __BITS_SET(_word, _mask, _shift, _val) \ + (((_word) & ~(_mask)) | (((_val) << (_shift)) & (_mask))) + +#undef BITS_SET +#define BITS_SET(_word, _bit, _val) \ + __BITS_SET(_word, _bit, FFS(_bit), _val) + +#undef BITS_MATCH +#define BITS_MATCH(_word, _bit) \ + (((_word) & (_bit)) == (_bit)) + +/* + * Register address + */ +#define REG_UIC_HW_REV 0x00 +#define REG_UIC_FW_REV 0x01 + +#define REG_UIC_INT 0x02 +#define REG_CC_INT 0x03 +#define REG_PD_INT 0x04 +#define REG_VDM_INT 0x05 + +#define REG_USBC_STATUS1 0x06 +#define REG_USBC_STATUS2 0x07 +#define REG_BC_STATUS 0x08 + +#define REG_UIC_FW_MINOR 0x09 + + +#define REG_CC_STATUS0 0x0A +#define REG_CC_STATUS1 0x0B + +#define REG_PD_STATUS0 0x0C +#define REG_PD_STATUS1 0x0D + +#define REG_UIC_INT_M 0x0E +#define REG_CC_INT_M 0x0F +#define REG_PD_INT_M 0x10 +#define REG_VDM_INT_M 0x11 + +#define REG_OPCODE 0x21 +#define REG_OPCODE_DATA 0x22 +#define REG_OPCDE_RES 0x51 + +#define REG_END_DATA 0x41 + +/* + * REG_INT_M Initial values + */ +#define REG_UIC_INT_M_INIT 0x04 +#define REG_CC_INT_M_INIT 0x20 +#define REG_PD_INT_M_INIT 0x00 +#define REG_VDM_INT_M_INIT 0xF0 + +/* + * REG_UIC_INT Interrupts + */ +#define BIT_APCmdResI BIT(7) +#define BIT_SYSMsgI BIT(6) +#define BIT_VBUSDetI BIT(5) +#define BIT_VbADCI BIT(4) +#define BIT_DCDTmoI BIT(3) +#define BIT_CHGTypI BIT(1) +#define BIT_UIDADCI BIT(0) + +/* + * REG_CC_INT Interrupts + */ +#define BIT_VCONNOCPI BIT(7) +#define BIT_VSAFE0VI BIT(6) +#define BIT_AttachSrcErrI BIT(5) +#define BIT_VCONNSCI BIT(4) +#define BIT_CCPinStatI BIT(3) +#define BIT_CCIStatI BIT(2) +#define BIT_CCVcnStatI BIT(1) +#define BIT_CCStatI BIT(0) + +/* + * REG_PD_INT Interrupts + */ +#define BIT_PDMsgI BIT(7) +#define BIT_DataRole BIT(5) +#define BIT_SSAccI BIT(1) +#define BIT_FCTIDI BIT(0) + +/* + * REG_USBC_STATUS1 + */ +#define BIT_VBADC BITS(7, 4) +#define BIT_UIDADC BITS(2, 0) + +/* + * REG_USBC_STATUS2 + */ +#define BIT_SYSMsg BITS(7, 0) + +/* + * REG_BC_STATUS + */ +#define BIT_VBUSDet BIT(7) +#define BIT_PrChgTyp BITS(5, 3) +#define BIT_DCDTmo BIT(2) +#define BIT_ChgTyp BITS(1, 0) + +/* + * REG_CC_STATUS1 + */ +#define BIT_CCPinStat BITS(7, 6) +#define BIT_CCIStat BITS(5, 4) +#define BIT_CCVcnStat BIT(3) +#define BIT_CCStat BITS(2, 0) + +/* + * REG_CC_STATUS2 + */ +#define BIT_CCSBUSHORT BITS(7, 6) +#define BIT_VCONNOCP BIT(5) +#define BIT_VCONNSC BIT(4) +#define BIT_VSAFE0V BIT(3) +#define BIT_AttachSrcErr BIT(2) +#define BIT_ConnStat BIT(1) +#define BIT_Altmode BIT(0) + +/* + * REG_PD_STATUS0 + */ +#define BIT_PDMsg BITS(7, 0) + +/* + * REG_PD_STATUS1 + */ +#define BIT_PD_DataRole BIT(7) +#define BIT_PD_ENTER_MODE BIT(5) +#define BIT_PD_PSRDY BIT(4) +#define BIT_FCT_ID BITS(3, 0) + + +/** opcode reg **/ + +/* + * CC Control1 Write + */ +#define BIT_CCSrcCurCh BIT(7) +#define BIT_CCSrcCur BITS(6, 5) +#define BIT_CCSrcSnk BIT(4) +#define BIT_CCSnkSrc BIT(3) +#define BIT_CCDbgEn BIT(2) +#define BIT_CCAudEn BIT(1) +#define BIT_CCDetEn BIT(0) + + + + +/* + * max77766 role + */ +enum max77705_data_role { + UFP = 0, + DFP, +}; +enum max77705_power_role { + SNK = 0, + SRC, +}; +enum max77705_vcon_role { + OFF = 0, + ON +}; + +/* + * F/W update + */ +#define FW_CMD_READ 0x3 +#define FW_CMD_READ_SIZE 6 /* cmd(1) + len(1) + data(4) */ + +#define FW_CMD_WRITE 0x1 +#define FW_CMD_WRITE_SIZE 36 /* cmd(1) + len(1) + data(34) */ + +#define FW_CMD_END 0x0 + +#define FW_HEADER_SIZE 8 +#define FW_VERIFY_DATA_SIZE 3 + +#define FW_VERIFY_TRY_COUNT 10 +#define FW_SECURE_MODE_TRY_COUNT 10 + +#define FW_WAIT_TIMEOUT (1000 * 5) /* 5 sec */ +#define I2C_SMBUS_BLOCK_HALF (I2C_SMBUS_BLOCK_MAX / 2) + +#define GET_CONTROL3_LOCK_ERROR_EN(_x) ((_x & (0x1 << 1)) >> 1) + +typedef struct { + u32 magic; /* magic number */ + u8 major; /* major version */ + u8 minor:3; /* minor version */ + u8 product_id:5; /* product id */ + u8 id; /* id */ + u8 rev; /* rev */ +} max77705_fw_header; +#define MAX77705_SIGN 0xCEF166C1 + +enum { + FW_UPDATE_START = 0x00, + FW_UPDATE_WAIT_RESP_START, + FW_UPDATE_WAIT_RESP_STOP, + FW_UPDATE_DOING, + FW_UPDATE_END, +}; + +enum { + FW_UPDATE_FAIL = 0xF0, + FW_UPDATE_I2C_FAIL, + FW_UPDATE_TIMEOUT_FAIL, + FW_UPDATE_VERIFY_FAIL, + FW_UPDATE_CMD_FAIL, + FW_UPDATE_MAX_LENGTH_FAIL, +}; + + +enum { + NO_DETERMINATION = 0, + CC1_ACTIVE, + CC2_ACTVIE, + AUDIO_ACCESSORY, + DEBUG_ACCESSORY, + ERROR, + DISABLED, + RFU, +}; + +enum { + NOT_IN_UFP_MODE = 0, + CCI_500mA, + CCI_1_5A, + CCI_3_0A, + CCI_SHORT, +}; + +/* + * All type of Interrupts + */ +enum { + AP_Command_respond = 7, + USBC_System_message = 6, + CHGIN_Voltage = 5, + CHGIN_Voltage_ADC = 4, + DCD_Timer = 3, + Charge_Type = 1, + UID_ADC = 0, +}; + +enum max77705_chg_type { + CHGTYP_NOTHING = 0, + CHGTYP_USB_SDP, + CHGTYP_CDP_T, + CHGTYP_DCP, +}; + +enum max77705_pr_chg_type { + Unknown = 0, + Samsung_2A, + Apple_05A, + Apple_1A, + Apple_2A, + Apple_12W, + DCP_3A, + RFU_CHG, +}; + +enum max77705_ccpd_device { + DEV_NONE = 0, + DEV_OTG, + + DEV_USB, + DEV_CDP, + DEV_DCP, + + DEV_APPLE500MA, + DEV_APPLE1A, + DEV_APPLE2A, + DEV_APPLE12W, + DEV_DCP3A, + DEV_HVDCP, + DEV_QC, + + DEV_FCT_0, + DEV_FCT_1K, + DEV_FCT_255K, + DEV_FCT_301K, + DEV_FCT_523K, + DEV_FCT_619K, + DEV_FCT_OPEN, + + DEV_PD_TA, + DEV_PD_AMA, + + DEV_UNKNOWN, +}; + +enum max77705_uidadc { + UID_GND = 0, + UID_255Kohm = 3, + UID_301Kohm = 4, + UID_523Kohm = 5, + UID_619Kohm = 6, + UID_Open = 7, +}; + +enum max77705_fctid { + FCT_GND = 1, + FCT_1Kohm, + FCT_255Kohm, + FCT_301Kohm, + FCT_523Kohm, + FCT_619Kohm, + FCT_OPEN, +}; + +enum max77705_cc_pin_state { + cc_No_Connection = 0, + cc_SINK, + cc_SOURCE, + cc_Audio_Accessory, + cc_Debug_Accessory, + cc_Error, + cc_Disabled, + cc_RFU, +}; + +enum max77705_usbc_SYSMsg { + SYSERROR_NONE = 0x00, + /*Reserved = 0x01,*/ + /*Reserved = 0x02,*/ + SYSERROR_BOOT_WDT = 0x03, + SYSERROR_BOOT_SWRSTREQ = 0x04, + SYSMSG_BOOT_POR = 0x05, + + SYSERROR_HV_NOVBUS = 0x10, + SYSERROR_HV_FMETHOD_RXPERR = 0x11, + SYSERROR_HV_FMETHOD_RXBUFOW = 0x12, + SYSERROR_HV_FMETHOD_RXTFR = 0x13, + SYSERROR_HV_FMETHOD_MPNACK = 0x14, + SYSERROR_HV_FMETHOD_RESET_FAIL = 0x15, + + SYSMsg_AFC_Done = 0x20, + + SYSERROR_SYSPOS = 0x30, + SYSERROR_APCMD_UNKNOWN = 0x31, + SYSERROR_APCMD_INPROGRESS = 0x32, + SYSERROR_APCMD_FAIL = 0x33, + + SYSMSG_CCx_5V_SHORT = 0x61, + SYSMSG_SBUx_GND_SHORT = 0x62, + SYSMSG_SBUx_5V_SHORT = 0x63, +#ifdef MAX77705_GRL_ENABLE + SYSMSG_SET_GRL = 0x64, +#endif + SYSMSG_PD_CCx_5V_SHORT = 0x65, + SYSMSG_PD_SBUx_5V_SHORT = 0x66, + SYSMSG_PD_SHORT_NONE = 0x67, + SYSERROR_DROP5V_SRCRDY = 0x68, + SYSERROR_DROP5V_SNKRDY = 0x69, + SYSMSG_PD_GENDER_SHORT = 0x6A, + + SYSERROR_FACTORY_RID0 = 0x70, + SYSERROR_POWER_NEGO = 0x80, + SYSERROR_CCRP_HIGH = 0x90, /* PD Charger Connected while Water state */ + SYSERROR_CCRP_LOW = 0x91, /* PD Charger Disconnected while Water state */ + + /* TypeC earphone is attached during PD charging */ + SYSMSG_10K_TO_22K = 0xB0, + SYSMSG_10K_TO_56K = 0xB1, + SYSMSG_22K_TO_56K = 0xB2, + /* TypeC earphone is detached during PD charging */ + SYSMSG_56K_TO_22K = 0xB3, + SYSMSG_56K_TO_10K = 0xB4, + SYSMSG_22K_TO_10K = 0xB5, +}; + +enum max77705_pdmsg { + Nothing_happened = 0x00, + Sink_PD_PSRdy_received = 0x01, + Sink_PD_Error_Recovery = 0x02, + Sink_PD_SenderResponseTimer_Timeout = 0x03, + Source_PD_PSRdy_Sent = 0x04, + Source_PD_Error_Recovery = 0x05, + Source_PD_SenderResponseTimer_Timeout = 0x06, + PD_DR_Swap_Request_Received = 0x07, + PD_PR_Swap_Request_Received = 0x08, + PD_VCONN_Swap_Request_Received = 0x09, + Received_PD_Message_in_illegal_state = 0x0A, + SRC_CAP_RECEIVED = 0x0B, + + Samsung_Accessory_is_attached = 0x10, + VDM_Attention_message_Received = 0x11, + Rejcet_Received = 0x12, + Not_Supported_Received = 0x13, + Prswap_Snktosrc_Sent = 0x14, + Prswap_Srctosnk_Sent = 0x15, + HARDRESET_RECEIVED = 0x16, + Get_Vbus_turn_on = 0x17, + Get_Vbus_turn_off = 0x18, + HARDRESET_SENT = 0x19, + PRSWAP_SRCTOSWAP = 0x1A, + PRSWAP_SWAPTOSNK = 0X1B, + PRSWAP_SNKTOSWAP = 0x1C, + PRSWAP_SWAPTOSRC = 0x1D, + + Sink_PD_Disabled = 0x20, + Source_PD_Disabled = 0x21, + Current_Cable_Connected = 0x22, + + Get_Source_Capabilities_Extended_Received = 0x30, + Get_Status_Received = 0x31, + Get_Battery_Cap_Received = 0x32, + Get_Battery_Status_Received = 0x33, + Get_Manufacturer_Info_Received = 0x34, + Source_Capabilities_Extended_Received = 0x35, + Status_Received = 0x36, + Battery_Capabilities_Received = 0x37, + Batery_Status_Received = 0x38, + Manufacturer_Info_Received = 0x39, + Alert_Message = 0x3e, + VDM_NAK_Recevied = 0x40, + VDM_BUSY_Recevied = 0x41, + VDM_ACK_Recevied = 0x42, + VDM_REQ_Recevied = 0x43, + + AFC_Sink_PD_Capabilities_Received = 0x50, + AFC_Sink_PD_PSRdy_Received = 0x51, + AFC_VDM_ResponseTimer_Timout = 0x52, + + + PDMSG_DP_DISCOVER_IDENTITY = 0x61, + PDMSG_DP_DISCOVER_SVID = 0x62, + PDMSG_DP_DISCOVER_MODES = 0x63, + PDMSG_DP_ENTER_MODE = 0x64, + PDMSG_DP_EXIT_MODE = 0x65, + PDMSG_DP_STATUS = 0x66, + PDMSG_DP_CONFIGURE = 0x67, + PDMSG_DP_ATTENTION = 0x68, + + PDMSG_SRC_ACCEPT = 0x70, + +}; +enum max77705_connstat { + DRY = 0x00, + WATER = 0x01, +}; + +/* + * External type definition + */ +#define MAX77705_AUTOIBUS_FW_AT_OFF 3 +#define MAX77705_AUTOIBUS_FW_OFF 2 +#define MAX77705_AUTOIBUS_AT_OFF 1 +#define MAX77705_AUTOIBUS_ON 0 + +#define OPCODE_WAIT_TIMEOUT (3000) /* 3000ms */ + +#define OPCODE_WRITE_COMMAND 0x21 +#define OPCODE_READ_COMMAND 0x51 +#define OPCODE_SIZE 1 +#define OPCODE_HEADER_SIZE 1 +#define OPCODE_DATA_LENGTH 32 +#define OPCODE_MAX_LENGTH (OPCODE_DATA_LENGTH + OPCODE_SIZE) +#define OPCODE_WRITE 0x21 +#define OPCODE_WRITE_END 0x41 +#define OPCODE_READ 0x51 +#define OPCODE_WRITE_SEQ 0x1 +#define OPCODE_READ_SEQ 0x2 +#define OPCODE_RW_SEQ 0x3 +#define OPCODE_PUSH_SEQ 0x4 +#define OPCODE_UPDATE_SEQ 0x5 + +typedef enum { + OPCODE_BCCTRL1_R = 0x01, + OPCODE_BCCTRL1_W, + OPCODE_BCCTRL2_R, + OPCODE_BCCTRL2_W, + OPCODE_CTRL1_R = 0x05, + OPCODE_CTRL1_W, + OPCODE_CTRL2_R, + OPCODE_CTRL2_W, + OPCODE_CTRL3_R, + OPCODE_CTRL3_W, + OPCODE_CCCTRL1_R = 0x0B, + OPCODE_CCCTRL1_W, + OPCODE_CCCTRL2_R, + OPCODE_CCCTRL2_W, + OPCODE_CCCTRL3_R, + OPCODE_CCCTRL3_W, + OPCODE_HVCTRL_R = 0x11, + OPCODE_HVCTRL_W, + OPCODE_OPCODE_VCONN_ILIM_R = 0x13, + OPCODE_OPCODE_VCONN_ILIM_W, + OPCODE_CHGIN_ILIM_R = 0x15, + OPCODE_CHGIN_ILIM_W, + OPCODE_CHGIN_ILIM2_R, + OPCODE_CHGIN_ILIM2_W, + OPCODE_CTRLREG_INIT_R = 0x1A, + OPCODE_CTRLREG_INIT_W, + OPCODE_CTRLREG3_R = 0x1C, + OPCODE_CTRLREG3_W = 0x1D, + OPCODE_AFC_HV_W = 0x20, + OPCODE_AFC_RESULT_R, + OPCODE_QC2P0_SET = 0x22, + OPCODE_SET_SNKCAP = 0x2E, + OPCODE_READ_SBU = 0x25, + OPCODE_READ_CC = 0x2D, + OPCODE_CURRENT_SRCCAP = 0x30, + OPCODE_GET_SRCCAP = 0x31, + OPCODE_SRCCAP_REQUEST = 0x32, + OPCODE_SET_SRCCAP = 0x33, + OPCODE_SEND_GET_REQUEST = 0x34, + OPCODE_READ_RESPONSE_FOR_GET_REQUEST = 0x35, + OPCODE_SEND_GET_RESPONSE = 0x36, + OPCODE_SWAP_REQUEST = 0x37, + OPCODE_SWAP_REQUEST_RESPONSE = 0x38, + OPCODE_APDO_SRCCAP_REQUEST = 0x3A, + OPCODE_GET_PPS_STATUS = 0x3B, + OPCODE_SET_PPS = 0x3C, + OPCODE_VDM_DISCOVER_IDENETITY_RESPONSE = 0x40, + OPCODE_VDM_DISCOVER_SET_VDM_REQ = 0x48, + OPCODE_VDM_DISCOVER_GET_VDM_RESP = 0x4b, + OPCODE_SEND_VDM_AFC3P0_COMMAND, + OPCODE_GET_PD_MESSAGE, + OPCODE_SAMSUNG_ACC_COMMAND_RECIEVED, + OPCODE_SAMSUNG_ACC_COMMAND_RESPOND, + OPCODE_SAMSUNG_SECURE_KEY_REVOCATION, + OPCODE_SAMSUNG_FACTORY_TEST = 0x54, + OPCODE_SET_ALTERNATEMODE = 0x55, + OPCODE_ENABLE_DETECTING_SHORT = 0x56, + OPCODE_SAMSUNG_FW_AUTOIBUS = 0x57, + OPCODE_READ_SELFTEST = 0x59, + OPCODE_SAMSUNG_GPIO5_CONTROL = 0x5B, + OPCODE_SAMSUNG_READ_MESSAGE = 0x5D, + OPCODE_SAMSUNG_SHIPMODE_EN = 0x61, + OPCODE_SNK_SELECTED_PDO = 0x65, +#ifdef MAX77705_GRL_ENABLE + OPCODE_GRL_COMMAND = 0x70, +#else + OPCODE_FW_OPCODE_CLEAR = 0x70, +#endif + OPCODE_RAM_TEST_COMMAND = 0xD1, + OPCODE_NONE = 0xff, +} max77705_opcode_list; + +typedef enum{ + OPCODE_ID_VDM_DISCOVER_IDENTITY = 0x1, + OPCODE_ID_VDM_DISCOVER_SVIDS = 0x2, + OPCODE_ID_VDM_DISCOVER_MODES = 0x3, + OPCODE_ID_VDM_ENTER_MODE = 0x4, + OPCODE_ID_VDM_EXIT_MODE = 0x5, + OPCODE_ID_VDM_ATTENTION = 0x6, + OPCODE_ID_VDM_SVID_DP_STATUS = 0x10, + OPCODE_ID_VDM_SVID_DP_CONFIGURE = 0x11, +} max77705_vdm_list; + +enum{ + OPCODE_GET_SRC_CAP_EXT = 0, + OPCODE_GET_STATUS, + OPCODE_GET_BAT_CAP, + OPCODE_GET_BAT_STS, + OPCODE_GET_MANUFACTURE_INFO, +}; + +typedef enum { + OPCODE_WAIT_START = 0, + OPCODE_WAIT_END, +} max77705_opcode_wait_type; + + +typedef enum { + OPCODE_NOTI_START = 0x0, + OPCODE_NOTI_NONE = 0xff, +} max77705_opcode_noti_cmd; + + +/* SAMSUNG OPCODE */ +#define REG_NONE 0xff +#define CCIC_IRQ_INIT_DETECT (-1) + +#define MINOR_VERSION_MASK 0b00000111 + +/* PRODUCT ID */ +#define FW_PRODUCT_ID_REG 3 +//#define STAR_PRODUCT_ID 0b0000 +//#define Lykan_PRODUCT_ID 0b0001 +//#define BEYOND_PRODUCT_ID 0b0010 +#endif + + diff --git a/include/linux/usb/typec/maxim/max77705_alternate.h b/include/linux/usb/typec/maxim/max77705_alternate.h new file mode 100644 index 000000000000..3962a516bb3f --- /dev/null +++ b/include/linux/usb/typec/maxim/max77705_alternate.h @@ -0,0 +1,614 @@ +/* + * Copyrights (C) 2017 Samsung Electronics, Inc. + * Copyrights (C) 2017 Maxim Integrated Products, 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. + */ + +#ifndef __LINUX_MAXIM_CCIC_ALTERNATE_MODE_H__ +#define __LINUX_MAXIM_CCIC_ALTERNATE_MODE_H__ + +#if defined(CONFIG_MAXIM_CCIC_ALTERNATE_MODE) +typedef union { /* new defined union for MD05 Op Code Command Data */ + uint8_t DATA; + struct { + uint8_t BDATA[1]; + } BYTES; + struct { + uint8_t Num_Of_VDO:3, + Cmd_Type:2, + Reserved:3; + } BITS; +} SEND_VDM_BYTE_DATA; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t Number_of_obj:3, + MSG_ID:3, + Port_Power_Role:1, + Specification_Rev:2, + Port_Data_Role:1, + Reserved:1, + MSG_Type:4; + } BITS; +} UND_DATA_MSG_HEADER_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t VDM_command:5, + Rsvd2_VDM_header:1, + VDM_command_type:2, + Object_Position:3, + Rsvd_VDM_header:2, + Structured_VDM_Version:2, + VDM_Type:1, + Standard_Vendor_ID:16; + } BITS; +} UND_DATA_MSG_VDM_HEADER_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t USB_Vendor_ID:16, + Rsvd_ID_header:10, + Modal_Operation_Supported:1, + Product_Type:3, + Data_Capable_USB_Device:1, + Data_Capable_USB_Host:1; + } BITS; +} UND_DATA_MSG_ID_HEADER_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t Cert_TID:20, + Rsvd_cert_VDOer:12; + } BITS; +} UND_CERT_STAT_VDO_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t Device_Version:16, + Product_ID:16; + } BITS; +} UND_PRODUCT_VDO_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t USB_Superspeed_Signaling_Support:3, + SOP_contoller_present:1, + Vbus_through_cable:1, + Vbus_Current_Handling_Capability:2, + SSRX2_Directionality_Support:1, + SSRX1_Directionality_Support:1, + SSTX2_Directionality_Support:1, + SSTX1_Directionality_Support:1, + Cable_Termination_Type:2, + Cable_Latency:4, + TypeC_to_Plug_Receptacle:1, + TypeC_to_ABC:2, + Rsvd_CABLE_VDO:4, + Cable_Firmware_Version:4, + Cable_HW_Version:4; + } BITS; +} UND_CABLE_VDO_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t SVID_1:16, + SVID_0:16; + } BITS; +} UND_VDO1_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t VENDOR_DEFINED_MESSAGE:15, + VDM_TYPE:1, + USB_Vendor_ID:16; + } BITS; +} UND_UNSTRUCTURED_VDM_HEADER_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t DATA:8, + TOTAL_NUMBER_OF_UVDM_SET:4, + RESERVED:1, + COMMAND_TYPE:2, + DATA_TYPE:1, + PID:16; + } BITS; +} UND_SEC_UNSTRUCTURED_VDM_HEADER_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t VENDOR_DEFINED_MESSAGE:15, + VDM_TYPE:1, + USB_Vendor_ID:16; + } BITS; +} UND_SEC_DATA_HEADER_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t VENDOR_DEFINED_MESSAGE:15, + VDM_TYPE:1, + USB_Vendor_ID:16; + } BITS; +} UND_SEC_DATA_TAILER_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t ORDER_OF_CURRENT_UVDM_SET:4, + RESERVED:9, + COMMAND_TYPE:2, + DATA_TYPE:1, + PID:16; + } BITS; +} UND_SEC_UNSTRUCTURED_VDM_RESPONSE_HEADER_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t VENDOR_DEFINED_MESSAGE:15, + VDM_TYPE:1, + USB_Vendor_ID:16; + } BITS; +} UND_SEC_DATA_RESPONSE_HEADER_Type; + +typedef struct { + uint32_t VDO[7]; +} VDO_MESSAGE_Type; + +/* For DP */ +#define TypeC_POWER_SINK_INPUT 0 +#define TypeC_POWER_SOURCE_OUTPUT 1 +#define TypeC_DP_SUPPORT (0xFF01) + +/* For Dex */ +#define TypeC_Dex_SUPPORT (0x04E8) +#define SWAP_UINT32(x) (((x) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | ((x) << 24)) + +/* For DP VDM Modes VDO Port_Capability */ +typedef enum { + num_Reserved_Capable = 0, + num_UFP_D_Capable = 1, + num_DFP_D_Capable = 2, + num_DFP_D_and_UFP_D_Capable = 3 +} Num_DP_Port_Capability_Type; + +/* For DP VDM Modes VDO Receptacle_Indication */ +typedef enum { + num_USB_TYPE_C_PLUG = 0, + num_USB_TYPE_C_Receptacle = 1 +} Num_DP_Receptacle_Indication_Type; + + +/* For DP_Status_Update Port_Connected */ +typedef enum { + num_Adaptor_Disable = 0, + num_Connect_DFP_D = 1, + num_Connect_UFP_D = 2, + num_Connect_DFP_D_and_UFP_D = 3 +} Num_DP_Port_Connected_Type; + +/* For DP_Configure Select_Configuration */ +typedef enum { + num_Cfg_for_USB = 0, + num_Cfg_UFP_U_as_DFP_D = 1, + num_Cfg_UFP_U_as_UFP_D = 2, + num_Cfg_Reserved = 3 +} Num_DP_Sel_Configuration_Type; + +typedef enum { /* There is another Macro definitions which are similiar to this */ + REQ = 0, + ACK = 1, + NAK = 2, + BUSY = 3 +} VDM_CMD_TYPE; + +typedef enum { + Reserved = 0, + Discover_Identity = 1, + Discover_SVIDs = 2, + Discover_Modes = 3, + Enter_Mode = 4, + Exit_Mode = 5, + Attention = 6, + Configure = 17 +} VDM_HEADER_COMMAND; + +typedef enum { + Version_1_0 = 0, + Version_2_0 = 1, + Reserved1 = 2, + Reserved2 = 3 +} STRUCTURED_VDM_VERSION; +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t Port_Capability:2, + Signalling_DP:4, + Receptacle_Indication:1, + USB_2p0_Not_Used:1, + DFP_D_Pin_Assignments:8, + UFP_D_Pin_Assignments:8, + DP_MODE_VDO_Reserved:8; + } BITS; +} UND_VDO_MODE_DP_CAPABILITY_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t Port_Connected:2, + Power_Low:1, + Enabled:1, + Multi_Function_Preference:1, + USB_Configuration_Req:1, + Exit_DP_Mode_Req:1, + HPD_State:1, + HPD_Interrupt:1, + Reserved:23; + } BITS; +} UND_VDO_DP_STATUS_UPDATES_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t SEL_Configuration:2, + Select_DP_V1p3:1, + Select_USB_Gen2:1, + Select_Reserved_1:2, + Select_Reserved_2:2, + DFP_D_PIN_Assign_A:1, + DFP_D_PIN_Assign_B:1, + DFP_D_PIN_Assign_C:1, + DFP_D_PIN_Assign_D:1, + DFP_D_PIN_Assign_E:1, + DFP_D_PIN_Assign_F:1, + DFP_D_PIN_Reserved:2, + UFP_D_PIN_Assign_A:1, + UFP_D_PIN_Assign_B:1, + UFP_D_PIN_Assign_C:1, + UFP_D_PIN_Assign_D:1, + UFP_D_PIN_Assign_E:1, + UFP_D_PIN_Assign_F:1, + UFP_D_PIN_Reserved:2, + DP_MODE_Reserved:8; + } BITS; +} UND_DP_CONFIG_UPDATE_Type; + +typedef struct { + UND_DATA_MSG_HEADER_Type MSG_HEADER; + UND_DATA_MSG_VDM_HEADER_Type DATA_MSG_VDM_HEADER; + UND_VDO_MODE_DP_CAPABILITY_Type DATA_MSG_MODE_VDO_DP; +} DIS_MODE_DP_CAPA_Type; + +typedef struct { + UND_DATA_MSG_HEADER_Type MSG_HEADER; + UND_DATA_MSG_VDM_HEADER_Type DATA_MSG_VDM_HEADER; + UND_VDO_DP_STATUS_UPDATES_Type DATA_DP_STATUS_UPDATE; +} DP_STATUS_UPDATE_Type; + +typedef struct { + UND_DATA_MSG_HEADER_Type MSG_HEADER; + UND_DATA_MSG_VDM_HEADER_Type DATA_MSG_VDM_HEADER; + UND_VDO_DP_STATUS_UPDATES_Type DATA_MSG_DP_STATUS; +} DIS_ATTENTION_MESSAGE_DP_STATUS_Type; + +enum VDM_MSG_IRQ_State { + VDM_DISCOVER_ID = (1 << 0), + VDM_DISCOVER_SVIDS = (1 << 1), + VDM_DISCOVER_MODES = (1 << 2), + VDM_ENTER_MODE = (1 << 3), + VDM_EXIT_MODE = (1 << 4), + VDM_ATTENTION = (1 << 5), + VDM_DP_STATUS_UPDATE = (1 << 6), + VDM_DP_CONFIGURE = (1 << 7), +}; + +#define ALTERNATE_MODE_NOT_READY (1 << 0) +#define ALTERNATE_MODE_READY (1 << 1) +#define ALTERNATE_MODE_STOP (1 << 2) +#define ALTERNATE_MODE_START (1 << 3) +#define ALTERNATE_MODE_RESET (1 << 4) + +/* VMD Message Register I2C address by S.LSI */ +#define REG_VDM_MSG_REQ 0x02C0 +#define REG_SSM_MSG_READ 0x0340 +#define REG_SSM_MSG_SEND 0x0360 + +#define REG_TX_DIS_ID_RESPONSE 0x0400 +#define REG_TX_DIS_SVID_RESPONSE 0x0420 +#define REG_TX_DIS_MODE_RESPONSE 0x0440 +#define REG_TX_ENTER_MODE_RESPONSE 0x0460 +#define REG_TX_EXIT_MODE_RESPONSE 0x0480 +#define REG_TX_DIS_ATTENTION_RESPONSE 0x04A0 + +#define REG_RX_DIS_ID_CABLE 0x0500 +#define REG_RX_DIS_ID 0x0520 +#define REG_RX_DIS_SVID 0x0540 +#define REG_RX_MODE 0x0560 +#define REG_RX_ENTER_MODE 0x0580 +#define REG_RX_EXIT_MODE 0x05A0 +#define REG_RX_DIS_ATTENTION 0x05C0 +#define REG_RX_DIS_DP_STATUS_UPDATE 0x0600 +#define REG_RX_DIS_DP_CONFIGURE 0x0620 + +#define MODE_INT_CLEAR 0x01 +#define PD_NEXT_STATE 0x02 +#define MODE_INTERFACE 0x03 +#define SVID_SELECT 0x07 +#define REQ_PR_SWAP 0x10 +#define REQ_DR_SWAP 0x11 +#define SEL_SSM_MSG_REQ 0x20 +#define DP_ALT_MODE_REQ 0x30 +/* Samsung Acc VID */ +#define SAMSUNG_VENDOR_ID 0x04E8 +#define SAMSUNG_MPA_VENDOR_ID 0x04B4 +/* Samsung Acc PID */ +#define GEARVR_PRODUCT_ID 0xA500 +#define GEARVR_PRODUCT_ID_1 0xA501 +#define GEARVR_PRODUCT_ID_2 0xA502 +#define GEARVR_PRODUCT_ID_3 0xA503 +#define GEARVR_PRODUCT_ID_4 0xA504 +#define GEARVR_PRODUCT_ID_5 0xA505 +#define DEXDOCK_PRODUCT_ID 0xA020 +#define HDMI_PRODUCT_ID 0xA025 +#define MPA2_PRODUCT_ID 0xA027 +#define UVDM_PROTOCOL_ID 0xA028 +#define DEXPAD_PRODUCT_ID 0xA029 +#define MPA_PRODUCT_ID 0x2122 +/* Samsung UVDM structure */ +#define SEC_UVDM_SHORT_DATA 0x0 +#define SEC_UVDM_LONG_DATA 0x1 +#define SEC_UVDM_ININIATOR 0x0 +#define SEC_UVDM_RESPONDER_ACK 0x1 +#define SEC_UVDM_RESPONDER_NAK 0x2 +#define SEC_UVDM_RESPONDER_BUSY 0x3 +#define UNSTRUCTURED_VDM 0x0 +#define STRUCTURED_VDM 1 +/*For DP Pin Assignment */ +#define DP_PIN_ASSIGNMENT_NODE 0x00000000 +#define DP_PIN_ASSIGNMENT_A 0x00000001 /* ( 1 << 0 ) */ +#define DP_PIN_ASSIGNMENT_B 0x00000002 /* ( 1 << 1 ) */ +#define DP_PIN_ASSIGNMENT_C 0x00000004 /* ( 1 << 2 ) */ +#define DP_PIN_ASSIGNMENT_D 0x00000008 /* ( 1 << 3 ) */ +#define DP_PIN_ASSIGNMENT_E 0x00000010 /* ( 1 << 4 ) */ +#define DP_PIN_ASSIGNMENT_F 0x00000020 /* ( 1 << 5 ) */ + +#define SAMSUNGUVDM_MAX_LONGPACKET_SIZE (236) +#define SAMSUNGUVDM_MAX_SHORTPACKET_SIZE (1) +#define SAMSUNGUVDM_WAIT_MS (2000) +#define SAMSUNGUVDM_ALIGN (4) +#define SAMSUNGUVDM_MAXDATA_FIRST_UVDMSET (12) +#define SAMSUNGUVDM_MAXDATA_NORMAL_UVDMSET (16) +#define SAMSUNGUVDM_CHECKSUM_DATA_COUNT (20) + +enum uvdm_rx_type { + RX_ACK = 0, + RX_NAK, + RX_BUSY, +}; + +typedef union sec_uvdm_header { + uint32_t data; + struct { + uint8_t bdata[4]; + } BYTES; + struct { + uint32_t data:8, + total_number_of_uvdm_set:4, + direction:1, + command_type:2, + data_type:1, + pid:16; + } BITS; +} U_SEC_UVDM_HEADER; + +typedef U_SEC_UVDM_HEADER U_SEC_UVDM_RESPONSE_HEADER; + +typedef union sec_tx_data_header { + uint32_t data; + struct { + uint8_t bdata[4]; + } BYTES; + struct { + uint32_t data_size_of_current_set:8, + total_data_size:8, + reserved:12, + order_of_current_uvdm_set:4; + } BITS; +} U_SEC_TX_DATA_HEADER; + +typedef union sec_data_tx_tailer { + uint32_t data; + struct { + uint8_t bdata[4]; + } BYTES; + struct { + uint32_t checksum:16, + reserved:16; + } BITS; +} U_SEC_TX_DATA_TAILER; + +typedef union sec_data_rx_header { + uint32_t data; + struct { + uint8_t bdata[4]; + } BYTES; + struct { + uint32_t reserved:18, + result_value:2, + received_data_size_of_current_set:8, + order_of_current_uvdm_set:4; + } BITS; +} U_SEC_RX_DATA_HEADER; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t VDM_command:5, + Rsvd2_VDM_header:1, + VDM_command_type:2, + Object_Position:3, + Rsvd_VDM_header:2, + Structured_VDM_Version:2, + VDM_Type:1, + Standard_Vendor_ID:16; + } BITS; +} U_DATA_MSG_VDM_HEADER_Type; + +#define SEC_UVDM_LONGPACKET_WAIT_MS (6000) +#define SEC_UVDM_RX_HEADER_ACK 0x0 +#define SEC_UVDM_RX_HEADER_NAK 0x1 + +#define MAXIM_ENABLE_ALTERNATE_SRCCAP 0x1 +#define MAXIM_ENABLE_ALTERNATE_VDM 0x2 +#define MAXIM_ENABLE_ALTERNATE_SRC_VDM 0x3 +#define MAXIM_DISABLE_ALTERNATE_SRC_VDM 0x0 + +typedef union { + uint32_t DATA; + uint8_t BYTES[4]; + struct { + uint32_t Vdm_Flag_Reserve_b0:1, /* b0 */ + Vdm_Flag_Discover_ID:1, /* b1 */ + Vdm_Flag_Discover_SVIDs:1, /* b2 */ + Vdm_Flag_Discover_MODEs:1, /* b3 */ + Vdm_Flag_Enter_Mode:1, /* b4 */ + Vdm_Flag_Exit_Mode:1, /* b5 */ + Vdm_Flag_Attention:1, /* b6 */ + Vdm_Flag_Reserved:9, /* b7 - b15 */ + Vdm_Flag_DP_Status_Update:1, /* b16 */ + Vdm_Flag_DP_Configure:1, /* b17 */ + Vdm_Flag_Reserved2:14; /* b18 - b31 */ + } BITS; +} MAX77705_VDM_MSG_IRQ_STATUS_Type; + +struct DP_DP_DISCOVER_IDENTITY { /* struct type definition */ + SEND_VDM_BYTE_DATA byte_data; + UND_DATA_MSG_VDM_HEADER_Type vdm_header; + +} __attribute__((aligned(1), packed)); + +struct DP_DP_DISCOVER_ENTER_MODE { /* struct type definition */ + SEND_VDM_BYTE_DATA byte_data; + UND_DATA_MSG_VDM_HEADER_Type vdm_header; + +} __attribute__((aligned(1), packed)); + +struct DP_DP_STATUS { /* struct type definition */ + SEND_VDM_BYTE_DATA byte_data; + UND_DATA_MSG_VDM_HEADER_Type vdm_header; + UND_VDO_DP_STATUS_UPDATES_Type vdo_status; +} __attribute__((aligned(1), packed)); + +struct DP_DP_CONFIGURE { /* struct type definition */ + SEND_VDM_BYTE_DATA byte_data; + UND_DATA_MSG_VDM_HEADER_Type vdm_header; + UND_DP_CONFIG_UPDATE_Type vdo_config; +} __attribute__((aligned(1), packed)); + +struct SS_DEX_DISCOVER_MODE { /* struct type definition */ + SEND_VDM_BYTE_DATA byte_data; + UND_DATA_MSG_VDM_HEADER_Type vdm_header; +} __attribute__((aligned(1), packed)); + +struct SS_DEX_ENTER_MODE { /* struct type definition */ + SEND_VDM_BYTE_DATA byte_data; + UND_DATA_MSG_VDM_HEADER_Type vdm_header; +} __attribute__((aligned(1), packed)); + +struct SS_UNSTRUCTURED_VDM_MSG{ + SEND_VDM_BYTE_DATA byte_data; + VDO_MESSAGE_Type VDO_MSG; +} __attribute__((aligned(1), packed)); + +void max77705_receive_alternate_message(struct max77705_usbc_platform_data *data, + MAX77705_VDM_MSG_IRQ_STATUS_Type *VDM_MSG_IRQ_State); +void max77705_vdm_message_handler(struct max77705_usbc_platform_data *usbpd_data, + char *opcode_data, int len); +void max77705_sec_unstructured_message_handler(struct max77705_usbc_platform_data *usbpd_data, + char *opcode_data, int len); +void max77705_send_dex_fan_unstructured_vdm_message(void *data, int cmd); +void max77705_acc_detach_check(struct work_struct *work); +void max77705_set_enable_alternate_mode(int mode); +void max77705_vdm_process_set_samsung_alternate_mode(void *data, int mode); +int max77705_process_check_accessory(void *data); +extern void max77705_vdm_process_set_identity_req(void *data); +extern void max77705_vdm_process_set_DP_configure_mode_req(void *data, uint8_t W_DATA); +extern void max77705_vdm_process_set_Dex_enter_mode_req(void *data); +extern int max77705_sec_uvdm_in_request_message(void *data); +extern int max77705_sec_uvdm_out_request_message(void *data, int size); +extern int max77705_sec_uvdm_ready(void); +extern void max77705_sec_uvdm_close(void); +#endif +#endif diff --git a/include/linux/usb/typec/maxim/max77705_cc.h b/include/linux/usb/typec/maxim/max77705_cc.h new file mode 100644 index 000000000000..d96c0943941e --- /dev/null +++ b/include/linux/usb/typec/maxim/max77705_cc.h @@ -0,0 +1,70 @@ +/* + * Copyrights (C) 2017 Samsung Electronics, Inc. + * Copyrights (C) 2017 Maxim Integrated Products, 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. + */ + +#ifndef __LINUX_MFD_MAX77705_CC_H +#define __LINUX_MFD_MAX77705_CC_H + +#include +#define MAX77705_CC_NAME "max77705_CC" +#define MAX77865_IRQSRC_CHG (1 << 0) +#define MAX77865_IRQSRC_FG (1 << 2) + +struct max77705_cc_data { + + /* interrupt pin */ + int irq_vconncop; + int irq_vsafe0v; + int irq_detabrt; + int irq_vconnsc; + int irq_ccpinstat; + int irq_ccistat; + int irq_ccvcnstat; + int irq_ccstat; + + u8 usbc_status1; + u8 usbc_status2; + u8 bc_status; + u8 cc_status0; + u8 cc_status1; + u8 pd_status0; + u8 pd_status1; + + u8 opcode_res; + + /* VCONN Over Current Detection */ + u8 vconnocp; + /* VCONN Over Short Circuit Detection */ + u8 vconnsc; + /* Status of VBUS Detection */ + u8 vsafe0v; + /* Charger Detection Abort Status */ + u8 detabrt; + /* Output of active CC pin */ + u8 ccpinstat; + /* CC Pin Detected Allowed VBUS Current in UFP mode */ + u8 ccistat; + /* Status of Vconn Output */ + u8 ccvcnstat; + /* CC Pin State Machine Detection */ + u8 ccstat; + + enum max77705_vcon_role current_vcon; + enum max77705_vcon_role previous_vcon; + enum max77705_power_role current_pr; + enum max77705_power_role previous_pr; + + struct wakeup_source ccstat_ws; +}; +#endif diff --git a/include/linux/usb/typec/maxim/max77705_debug.h b/include/linux/usb/typec/maxim/max77705_debug.h new file mode 100644 index 000000000000..7101c9d6ed30 --- /dev/null +++ b/include/linux/usb/typec/maxim/max77705_debug.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2017 Maxim Integrated Products, 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 + * published by the Free Software Foundation. + */ + +#define OPCODE_READ_MAX_LENGTH (32 + 1) +#define OPCODE_WRITE_MAX_LENGTH (32 + 1) /* opcode + data 31bytes */ +#define OPCODE_WRITE_START_ADDR 0x21 +#define OPCODE_WRITE_END_ADDR 0x41 +#define OPCODE_READ_START_ADDR 0x51 +#define OPCODE_READ_END_ADDR 0x71 + +enum mxim_ioctl_cmd { + MXIM_DEBUG_OPCODE_WRITE = 1000, + MXIM_DEBUG_OPCODE_READ, + MXIM_DEBUG_REG_WRITE = 1002, + MXIM_DEBUG_REG_READ, + MXIM_DEBUG_REG_DUMP, + MXIM_DEBUG_FW_VERSION = 1005, +}; + +enum mxim_registers { + MXIM_REG_UIC_HW_REV = 0x00, + MXIM_REG_UIC_FW_REV, + MXIM_REG_USBC_IRQ = 0x02, + MXIM_REG_CC_IRQ, + MXIM_REG_PD_IRQ, + MXIM_REG_RSVD1, + MXIM_REG_USBC_STATUS1 = 0x06, + MXIM_REG_USBC_STATUS2, + MXIM_REG_BC_STATUS, + MXIM_REG_RSVD2, + MXIM_REG_CC_STATUS1 = 0x0A, + MXIM_REG_CC_STATUS2, + MXIM_REG_PD_STATUS1, + MXIM_REG_PD_STATUS2, + MXIM_REG_USBC_IRQM = 0x0E, + MXIM_REG_CC_IRQM, + MXIM_REG_PD_IRQM, + MXIM_REG_MAX, +}; + +struct mxim_debug_registers { + unsigned char reg; + unsigned char val; + int ignore; +}; + +struct mxim_debug_pdev { + struct mxim_debug_registers registers; + struct i2c_client *client; + struct class *class; + struct device *dev; + struct mutex lock; + unsigned char opcode_wdata[OPCODE_WRITE_MAX_LENGTH]; + unsigned char opcode_rdata[OPCODE_READ_MAX_LENGTH]; +}; + +extern void mxim_debug_set_i2c_client(struct i2c_client *client); +extern int mxim_debug_init(void); +extern void mxim_debug_exit(void); diff --git a/include/linux/usb/typec/maxim/max77705_pd.h b/include/linux/usb/typec/maxim/max77705_pd.h new file mode 100644 index 000000000000..3bb509ebb80f --- /dev/null +++ b/include/linux/usb/typec/maxim/max77705_pd.h @@ -0,0 +1,141 @@ +/* + * Copyrights (C) 2017 Samsung Electronics, Inc. + * Copyrights (C) 2017 Maxim Integrated Products, 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. + */ + +#ifndef __LINUX_MFD_MAX77705_PD_H +#define __LINUX_MFD_MAX77705_PD_H +#include "max77705.h" +#include + +#define MAX77705_PD_NAME "MAX77705_PD" + +enum { + CC_SNK = 0, + CC_SRC, + CC_NO_CONN, +}; + +enum { + D2D_NONE = 0, + D2D_SNKONLY, + D2D_SRCSNK, +}; + +typedef enum { + PDO_TYPE_FIXED = 0, + PDO_TYPE_BATTERY, + PDO_TYPE_VARIABLE, + PDO_TYPE_APDO +} pdo_supply_type_t; + +typedef union sec_pdo_object { + uint32_t data; + struct { + uint8_t bdata[4]; + } BYTES; + struct { + uint32_t reserved:30, + type:2; + } BITS_supply; + struct { + uint32_t max_current:10, /* 10mA units */ + voltage:10, /* 50mV units */ + peak_current:2, + reserved:2, + unchuncked_extended_messages_supported:1, + data_role_data:1, + usb_communications_capable:1, + unconstrained_power:1, + usb_suspend_supported:1, + dual_role_power:1, + supply:2; /* Fixed supply : 00b */ + } BITS_pdo_fixed; + struct { + uint32_t max_current:10, /* 10mA units */ + min_voltage:10, /* 50mV units */ + max_voltage:10, /* 50mV units */ + supply:2; /* Variable Supply (non-Battery) : 10b */ + } BITS_pdo_variable; + struct { + uint32_t max_allowable_power:10, /* 250mW units */ + min_voltage:10, /* 50mV units */ + max_voltage:10, /* 50mV units */ + supply:2; /* Battery : 01b */ + } BITS_pdo_battery; + struct { + uint32_t max_current:7, /* 50mA units */ + reserved1:1, + min_voltage:8, /* 100mV units */ + reserved2:1, + max_voltage:8, /* 100mV units */ + reserved3:2, + pps_power_limited:1, + pps_supply:2, + supply:2; /* APDO : 11b */ + } BITS_pdo_programmable; +} U_SEC_PDO_OBJECT; + +struct max77705_pd_data { + /* interrupt pin */ + int irq_pdmsg; + int irq_psrdy; + int irq_datarole; + int irq_ssacc; + int irq_fct_id; + + u8 usbc_status1; + u8 usbc_status2; + u8 bc_status; + u8 cc_status0; + u8 cc_status1; + u8 pd_status0; + u8 pd_status1; + + u8 opcode_res; + + /* PD Message */ + u8 pdsmg; + + /* Data Role */ + enum max77705_data_role current_dr; + enum max77705_data_role previous_dr; + /* SSacc */ + u8 ssacc; + /* FCT cable */ + u8 fct_id; + enum max77705_ccpd_device device; + + struct pdic_notifier_struct pd_noti; + bool pdo_list; + bool psrdy_received; + bool cc_sbu_short; + bool bPPS_on; + bool sent_chg_info; + + struct workqueue_struct *wqueue; + struct delayed_work retry_work; + struct delayed_work d2d_work; + struct delayed_work abnormal_pdo_work; + struct delayed_work send_identity_work; + + int cc_status; + + int src_cap_done; + int auth_type; + int d2d_type; + int req_pdo_type; + bool psrdy_sent; +}; + +#endif diff --git a/include/linux/usb/typec/maxim/max77705_usbc.h b/include/linux/usb/typec/maxim/max77705_usbc.h new file mode 100644 index 000000000000..bb6bf6dd818f --- /dev/null +++ b/include/linux/usb/typec/maxim/max77705_usbc.h @@ -0,0 +1,361 @@ +/* + * Copyrights (C) 2017 Samsung Electronics, Inc. + * Copyrights (C) 2017 Maxim Integrated Products, 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. + */ + +#ifndef __LINUX_MFD_MAX77705_UIC_H +#define __LINUX_MFD_MAX77705_UIC_H +#include +#include +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) +#include +#endif +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) +#include +#endif +#define MAX77705_SYS_FW_UPDATE + +#include "max77705_pd.h" +#include "max77705_cc.h" + +struct max77705_opcode { + unsigned char opcode; + unsigned char data[OPCODE_DATA_LENGTH]; + int read_length; + int write_length; +}; + +typedef struct max77705_usbc_command_data { + u8 opcode; + u8 prev_opcode; + u8 response; + u8 read_data[OPCODE_DATA_LENGTH]; + u8 write_data[OPCODE_DATA_LENGTH]; + int read_length; + int write_length; + u8 reg; + u8 val; + u8 mask; + u8 seq; + int noti_cmd; + u8 is_uvdm; +} usbc_cmd_data; + +typedef struct max77705_usbc_command_node { + usbc_cmd_data cmd_data; + struct max77705_usbc_command_node *next; +} usbc_cmd_node; + +typedef struct max77705_usbc_command_node *usbc_cmd_node_p; + +typedef struct max77705_usbc_command_queue { + struct mutex command_mutex; + usbc_cmd_node *front; + usbc_cmd_node *rear; + usbc_cmd_node tmp_cmd_node; +} usbc_cmd_queue_t; + +#if defined(CONFIG_SEC_FACTORY) +#define FAC_ABNORMAL_REPEAT_STATE 12 +#define FAC_ABNORMAL_REPEAT_RID 5 +#define FAC_ABNORMAL_REPEAT_RID0 3 +struct AP_REQ_GET_STATUS_Type { + uint32_t FAC_Abnormal_Repeat_State; + uint32_t FAC_Abnormal_Repeat_RID; + uint32_t FAC_Abnormal_RID0; +}; +#endif + +#define NAME_LEN_HMD 14 +#define MAX_NUM_HMD 32 +#define TAG_HMD "HMD" +#define MAX_NVCN_CNT 30 /* No vbus & connection */ +#define MAX_CHK_TIME 30 + +struct max77705_hmd_power_dev { + uint vid; + uint pid; + char hmd_name[NAME_LEN_HMD]; +}; + +struct max77705_usbc_platform_data { + struct max77705_dev *max77705; + struct device *dev; + struct i2c_client *i2c; /*0xCC */ + struct i2c_client *muic; /*0x4A */ + struct i2c_client *charger; /*0x2A; Charger */ + + int irq_base; + + /* interrupt pin */ + int irq_apcmd; + int irq_sysmsg; + + /* VDM pin */ + int irq_vdm0; + int irq_vdm1; + int irq_vdm2; + int irq_vdm3; + int irq_vdm4; + int irq_vdm5; + int irq_vdm6; + int irq_vdm7; + + int irq_vir0; + + /* register information */ + u8 usbc_status1; + u8 usbc_status2; + u8 bc_status; + u8 cc_status0; + u8 cc_status1; + u8 pd_status0; + u8 pd_status1; + + /* opcode register information */ + u8 op_ctrl1_w; + + int watchdog_count; + int por_count; + + u8 opcode_res; + /* USBC System message interrupt */ + u8 sysmsg; + u8 pd_msg; + + /* F/W state */ + u8 HW_Revision; + u8 FW_Revision; + u8 FW_Minor_Revision; + u8 plug_attach_done; + int op_code_done; + enum max77705_connstat prev_connstat; + enum max77705_connstat current_connstat; + + /* F/W opcode Thread */ + + struct work_struct op_wait_work; + struct work_struct op_send_work; + struct work_struct cc_open_req_work; + struct work_struct dp_configure_work; +#ifdef MAX77705_SYS_FW_UPDATE + struct work_struct fw_update_work; +#endif + struct workqueue_struct *op_wait_queue; + struct workqueue_struct *op_send_queue; + struct completion op_completion; + int op_code; + int is_first_booting; + usbc_cmd_data last_opcode; + unsigned long opcode_stamp; + struct mutex op_lock; + + /* F/W opcode command data */ + usbc_cmd_queue_t usbc_cmd_queue; + + uint32_t alternate_state; + uint32_t acc_type; + uint32_t Vendor_ID; + uint32_t Product_ID; + uint32_t Device_Version; + uint32_t SVID_0; + uint32_t SVID_1; + uint32_t SVID_DP; + struct delayed_work acc_detach_work; + uint32_t dp_is_connect; + uint32_t dp_hs_connect; + uint32_t dp_selected_pin; + u8 pin_assignment; + uint32_t is_sent_pin_configuration; + wait_queue_head_t host_turn_on_wait_q; + wait_queue_head_t device_add_wait_q; + int host_turn_on_event; + int host_turn_on_wait_time; + int device_add; + int is_samsung_accessory_enter_mode; + int send_enter_mode_req; + u8 sbu[2]; + u8 cc[2]; + struct completion ccic_sysfs_completion; + struct completion psrdy_wait; + struct max77705_muic_data *muic_data; + struct max77705_pd_data *pd_data; + struct max77705_cc_data *cc_data; + + struct max77705_platform_data *max77705_data; + +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + ppdic_data_t ppdic_data; + struct workqueue_struct *ccic_wq; + int manual_lpm_mode; + int fac_water_enable; + int cur_rid; + int pd_state; + u8 vconn_test; + u8 vconn_en; + u8 fw_update; + int is_host; + int is_client; + bool auto_vbus_en; + u8 cc_pin_status; + int ccrp_state; + int vsafe0v_status; +#endif + struct typec_port *port; + struct typec_partner *partner; + struct usb_pd_identity partner_identity; + struct typec_capability typec_cap; + struct completion typec_reverse_completion; + int typec_power_role; + int typec_data_role; + int typec_try_state_change; + int pwr_opmode; + bool pd_support; + struct delayed_work usb_external_notifier_register_work; + struct notifier_block usb_external_notifier_nb; + int mpsm_mode; + bool mdm_block; + int vbus_enable; + int pd_pr_swap; + int shut_down; + struct delayed_work vbus_hard_reset_work; + uint8_t ReadMSG[32]; + int ram_test_enable; + int ram_test_retry; + int ram_test_result; + struct completion uvdm_longpacket_out_wait; + struct completion uvdm_longpacket_in_wait; + int is_in_first_sec_uvdm_req; + int is_in_sec_uvdm_out; + bool pn_flag; + int uvdm_error; + +#if defined(CONFIG_SEC_FACTORY) + struct AP_REQ_GET_STATUS_Type factory_mode; + struct delayed_work factory_state_work; + struct delayed_work factory_rid_work; +#endif + int detach_done_wait; + int set_altmode; + int set_altmode_error; + +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + struct usbpd_dev *usbpd_d; + struct if_cb_manager *man; +#endif + u8 control3_reg; + int cc_open_req; + + bool recover_opcode_list[OPCODE_NONE]; + int need_recover; + bool srcccap_request_retry; + + int ovp_gpio; + struct mutex hmd_power_lock; + struct max77705_hmd_power_dev *hmd_list; +#if defined(CONFIG_SUPPORT_SHIP_MODE) + int ship_mode_en; +#endif + + bool rid_check; + int lapse_idx; + u64 time_lapse[MAX_NVCN_CNT]; + + int wait_entermode; +}; + +/* Function Status from s2mm005 definition */ +typedef enum { + max77705_State_PE_Initial_detach = 0, + max77705_State_PE_SRC_Send_Capabilities = 3, + max77705_State_PE_SNK_Wait_for_Capabilities = 17, +} max77705_pd_state_t; + +typedef enum { + MPSM_OFF = 0, + MPSM_ON = 1, +} CCIC_DEVICE_MPSM; + +#define DATA_ROLE_SWAP 1 +#define POWER_ROLE_SWAP 2 +#define VCONN_ROLE_SWAP 3 +#define MANUAL_ROLE_SWAP 4 +#define ROLE_ACCEPT 0x1 +#define ROLE_REJECT 0x2 +#define ROLE_BUSY 0x3 + +int max77705_pd_init(struct max77705_usbc_platform_data *usbc_data); +int max77705_cc_init(struct max77705_usbc_platform_data *usbc_data); +int max77705_muic_init(struct max77705_usbc_platform_data *usbc_data); +int max77705_i2c_opcode_read(struct max77705_usbc_platform_data *usbc_data, + u8 opcode, u8 length, u8 *values); + +void init_usbc_cmd_data(usbc_cmd_data *cmd_data); +void max77705_usbc_clear_queue(struct max77705_usbc_platform_data *usbc_data); +int max77705_usbc_opcode_rw(struct max77705_usbc_platform_data *usbc_data, + usbc_cmd_data *opcode_r, usbc_cmd_data *opcode_w); +int max77705_usbc_opcode_write(struct max77705_usbc_platform_data *usbc_data, + usbc_cmd_data *write_op); +int max77705_usbc_opcode_read(struct max77705_usbc_platform_data *usbc_data, + usbc_cmd_data *read_op); +int max77705_usbc_opcode_push(struct max77705_usbc_platform_data *usbc_data, + usbc_cmd_data *read_op); +int max77705_usbc_opcode_update(struct max77705_usbc_platform_data *usbc_data, + usbc_cmd_data *read_op); + +void max77705_ccic_event_work(void *data, int dest, int id, + int attach, int event, int sub); +void max77705_notify_dr_status(struct max77705_usbc_platform_data *usbpd_data, + uint8_t attach); +void max77705_pdo_list(struct max77705_usbc_platform_data *usbc_data, + unsigned char *data); +void max77705_response_pdo_request(struct max77705_usbc_platform_data *usbc_data, + unsigned char *data); +void max77705_response_apdo_request(struct max77705_usbc_platform_data *usbc_data, + unsigned char *data); +void max77705_response_set_pps(struct max77705_usbc_platform_data *usbc_data, + unsigned char *data); +void max77705_send_new_src_cap_push(struct max77705_usbc_platform_data *pusbpd, int auth, int d2d_type); +void max77705_response_req_pdo(struct max77705_usbc_platform_data *usbc_data, + unsigned char *data); +void max77705_current_pdo(struct max77705_usbc_platform_data *usbc_data, + unsigned char *data); +void max77705_check_pdo(struct max77705_usbc_platform_data *usbc_data); +void max77705_detach_pd(struct max77705_usbc_platform_data *usbc_data); +void max77705_notify_rp_current_level(struct max77705_usbc_platform_data *usbc_data); +extern void max77705_manual_jig_on(struct max77705_usbc_platform_data *usbpd_data, int mode); +extern void max77705_vbus_turn_on_ctrl(struct max77705_usbc_platform_data *usbc_data, bool enable, bool swaped); +extern void max77705_dp_detach(void *data); +void max77705_usbc_disable_auto_vbus(struct max77705_usbc_platform_data *usbc_data); +extern void pdic_manual_ccopen_request(int is_on); +int max77705_get_pd_support(struct max77705_usbc_platform_data *usbc_data); +bool max77705_sec_pps_control(int en); +bool max77705_check_hmd_dev(struct max77705_usbc_platform_data *usbpd_data); + +extern const uint8_t BOOT_FLASH_FW_PASS2[]; +extern const uint8_t BOOT_FLASH_FW_PASS3[]; +extern const uint8_t BOOT_FLASH_FW_PASS4[]; +extern const uint8_t BOOT_FLASH_FW_PASS5[]; + +#if defined(CONFIG_SEC_FACTORY) +void factory_execute_monitor(int); +#endif + +#define DEBUG_MAX77705 +#ifdef DEBUG_MAX77705 +#define msg_maxim(format, args...) \ + pr_info("max77705: %s: " format "\n", __func__, ## args) +#else +#define msg_maxim(format, args...) +#endif /* DEBUG_MAX77766*/ +#endif diff --git a/include/linux/usb/typec/maxim/max77775-muic.h b/include/linux/usb/typec/maxim/max77775-muic.h new file mode 100755 index 000000000000..b400da6e5c69 --- /dev/null +++ b/include/linux/usb/typec/maxim/max77775-muic.h @@ -0,0 +1,394 @@ +/* + * max77775-muic.h - MUIC for the Maxim 77775 + * + * Copyright (C) 2015 Samsung Electrnoics + * Insun Choi + * + * 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, see . + * + * This driver is based on max14577-muic.h + * + */ + +#ifndef __MAX77775_MUIC_H__ +#define __MAX77775_MUIC_H__ + +#include +#include +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) +#include +#endif +#include "max77775.h" + +#define MUIC_DEV_NAME "muic-max77775" + +#define MUIC_IRQ_INIT_DETECT (-1) +#define MUIC_IRQ_PDIC_HANDLER (-2) + +enum max77775_adc { + MAX77775_UIADC_GND = 0x00, + MAX77775_UIADC_255K = 0x03, + MAX77775_UIADC_301K = 0x04, + MAX77775_UIADC_523K = 0x05, + MAX77775_UIADC_619K = 0x06, + MAX77775_UIADC_OPEN = 0x07, + + MAX77775_UIADC_DONTCARE = 0xfe, /* ADC don't care for MHL */ + MAX77775_UIADC_ERROR = 0xff, /* ADC value read error */ +}; + +enum max77775_vbadc { + MAX77775_VBADC_3_8V_UNDER = 0x0, + MAX77775_VBADC_3_8V_TO_4_5V = 0x1, + MAX77775_VBADC_4_5V_TO_5_5V = 0x2, + MAX77775_VBADC_5_5V_TO_6_5V = 0x3, + MAX77775_VBADC_6_5V_TO_7_5V = 0x4, + MAX77775_VBADC_7_5V_TO_8_5V = 0x5, + MAX77775_VBADC_8_5V_TO_9_5V = 0x6, + MAX77775_VBADC_9_5V_TO_10_5V = 0x7, + MAX77775_VBADC_10_5V_TO_11_5V = 0x8, + MAX77775_VBADC_11_5V_TO_12_5V = 0x9, + MAX77775_VBADC_12_5V_OVER = 0xa, +}; + +enum max77775_muic_command_opcode { + COMMAND_BC_CTRL1_READ = OPCODE_BCCTRL1_R, + COMMAND_BC_CTRL2_READ = OPCODE_BCCTRL2_R, + COMMAND_BC_CTRL2_WRITE = OPCODE_BCCTRL2_W, + COMMAND_CONTROL1_READ = OPCODE_CTRL1_R, + COMMAND_CONTROL1_WRITE = OPCODE_CTRL1_W, + /* AFC */ + COMMAND_HV_CONTROL_WRITE = OPCODE_FCCTRL1_W, + COMMAND_AFC_RESULT_READ = OPCODE_AFC_HV_RESULT_W, + COMMAND_QC_2_0_SET = OPCODE_AFC_QC_2_0_SET, + + COMMAND_NONE = 0xFF, +}; + +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77775_AFC) +enum max77775_afc_status_type { + MAX77775_MUIC_AFC_STATUS_CLEAR = (0x0), + MAX77775_MUIC_AFC_DISABLE_CHANGE_DURING_WORK = (0x1 << 0), + MAX77775_MUIC_AFC_DISABLE_CHANGE_DURING_WORK_END = ~(0x1 << 0), + MAX77775_MUIC_AFC_SET_VOLTAGE_CHANGE_DURING_WORK = (0x1 << 1), + MAX77775_MUIC_AFC_SET_VOLTAGE_CHANGE_DURING_WORK_END = ~(0x1 << 1), + MAX77775_MUIC_AFC_WORK_PROCESS = (0x1 << 2), + MAX77775_MUIC_AFC_WORK_PROCESS_END = ~(0x1 << 2) +}; +#endif /* CONFIG_HV_MUIC_MAX77775_AFC */ + +#define AFC_OP_OUT_LEN 11 /* OPCODE(1) + Result(1) + VBADC(1) + RX Data(8) */ + +#if defined(CONFIG_HICCUP_CHARGER) +enum MUIC_HICCUP_MODE { + MUIC_HICCUP_MODE_OFF = 0, + MUIC_HICCUP_MODE_ON, +}; +#endif + +#define MUIC_PDIC_NOTI_ATTACH (1) +#define MUIC_PDIC_NOTI_DETACH (-1) +#define MUIC_PDIC_NOTI_UNDEFINED (0) + +struct max77775_muic_ccic_evt { + int ccic_evt_attached; /* 1: attached, -1: detached, 0: undefined */ + int ccic_evt_rid; /* the last rid */ + int ccic_evt_rprd; /*rprd */ + int ccic_evt_roleswap; /* check rprd role swap event */ + int ccic_evt_dcdcnt; /* count dcd timeout */ +}; + +/* muic chip specific internal data structure */ +struct max77775_muic_data { + struct device *dev; + struct i2c_client *i2c; /* i2c addr: 0x4A; MUIC */ + struct mutex muic_mutex; + struct wakeup_source *muic_ws; +#if IS_ENABLED(CONFIG_MUIC_AFC_RETRY) + struct wakeup_source *afc_retry_ws; +#endif + /* model dependent mfd platform data */ + struct max77775_platform_data *mfd_pdata; + struct max77775_usbc_platform_data *usbc_pdata; + + int irq_uiadc; + int irq_chgtyp; + int irq_spr; + int irq_dcdtmo; + int irq_vbadc; + int irq_vbusdet; + + /* model dependent muic platform data */ + struct muic_platform_data *pdata; + + /* muic current attached device */ + muic_attached_dev_t attached_dev; + void *attached_func; + + bool is_muic_ready; + bool is_muic_reset; + + u8 adcmode; + u8 switch_val; + + /* check is otg test */ + bool is_otg_test; + + /* muic HV charger */ + bool is_factory_start; + bool is_check_hv; + bool is_charger_ready; + bool is_afc_reset; + bool is_skip_bigdata; + bool is_charger_mode; + bool is_usb_fail; + + u8 is_boot_dpdnvden; + + struct delayed_work afc_work; + struct work_struct afc_handle_work; + struct mutex afc_lock; + unsigned char afc_op_dataout[AFC_OP_OUT_LEN]; + int hv_voltage; + int reserve_hv_voltage; + int afc_retry; + int dcdtmo_retry; + int bc1p2_retry_count; + + /* hiccup mode flag */ + int is_hiccup_mode; + + /* muic status value */ + u8 status1; + u8 status2; + u8 status3; + u8 status4; + u8 status5; + u8 status6; + + struct delayed_work debug_work; + + /* Fake vbus flag */ + int fake_chgtyp; + +#if defined(CONFIG_USB_EXTERNAL_NOTIFY) + /* USB Notifier */ + struct notifier_block usb_nb; +#endif + struct max77775_muic_ccic_evt ccic_info_data; + struct work_struct ccic_info_data_work; + bool afc_water_disable; + int ccic_evt_id; + /* PDIC Notifier */ +#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) + struct notifier_block manager_nb; +#else + struct notifier_block ccic_nb; +#endif +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + struct muic_dev muic_d; + struct if_cb_manager *man; +#endif + +}; + +/* max77775 muic register read/write related information defines. */ +#define REG_NONE 0xff + +/* MAX77775 REGISTER ENABLE or DISABLE bit */ +enum max77775_reg_bit_control { + MAX77775_DISABLE_BIT = 0, + MAX77775_ENABLE_BIT, +}; + +/* MAX77775 STATUS1 register */ +#define USBC_STATUS1_UIADC_SHIFT 0 +#define USBC_STATUS1_VBADC_SHIFT 4 +#define USBC_STATUS1_UIADC_MASK (0x7 << USBC_STATUS1_UIADC_SHIFT) +#define USBC_STATUS1_VBADC_MASK (0xf << USBC_STATUS1_VBADC_SHIFT) + +/* MAX77775 BC STATUS register */ +#define BC_STATUS_CHGTYP_SHIFT 0 +#define BC_STATUS_DCDTMO_SHIFT 2 +#define BC_STATUS_PRCHGTYP_SHIFT 3 +#define BC_STATUS_VBUSDET_SHIFT 7 +#define BC_STATUS_CHGTYP_MASK (0x3 << BC_STATUS_CHGTYP_SHIFT) +#define BC_STATUS_DCDTMO_MASK (0x1 << BC_STATUS_DCDTMO_SHIFT) +#define BC_STATUS_PRCHGTYP_MASK (0x7 << BC_STATUS_PRCHGTYP_SHIFT) +#define BC_STATUS_VBUSDET_MASK (0x1 << BC_STATUS_VBUSDET_SHIFT) + +/* MAX77775 USBC_STATUS2 register - include ERROR message. */ +#define USBC_STATUS2_SYSMSG_SHIFT 0 +#define USBC_STATUS2_SYSMSG_MASK (0xff << USBC_STATUS2_SYSMSG_SHIFT) + +/* MAX77775 DAT_IN register */ +#define DAT_IN_SHIFT 0 +#define DAT_IN_MASK (0xff << DAT_IN_SHIFT) + +/* MAX77775 DAT_OUT register */ +#define DAT_OUT_SHIFT 0 +#define DAT_OUT_MASK (0xff << DAT_OUT_SHIFT) + +/* MAX77775 BC_CTRL1 */ +#define BC_CTRL1_DCDCpl_SHIFT 7 +#define BC_CTRL1_3ADCPDet_SHIFT 4 +#define BC_CTRL1_CHGDetMan_SHIFT 1 +#define BC_CTRL1_CHGDetEn_SHIFT 0 +#define BC_CTRL1_DCDCpl_MASK (0x1 << BC_CTRL1_DCDCpl_SHIFT) +#define BC_CTRL1_3ADCPDet_MASK (0x1 << BC_CTRL1_3ADCPDet_SHIFT) +#define BC_CTRL1_CHGDetMan_MASK (0x1 << BC_CTRL1_CHGDetMan_SHIFT) +#define BC_CTRL1_CHGDetEn_MASK (0x1 << BC_CTRL1_CHGDetEn_SHIFT) + +/* MAX77775 BC_CTRL2 */ +#define BC_CTRL2_DNMonEn_SHIFT 5 +#define BC_CTRL2_DPDNMan_SHIFT 4 +#define BC_CTRL2_DPDrv_SHIFT 2 +#define BC_CTRL2_DNDrv_SHIFT 0 +#define BC_CTRL2_DNMonEn_MASK (0x1 << BC_CTRL2_DNMonEn_SHIFT) +#define BC_CTRL2_DPDNMan_MASK (0x1 << BC_CTRL2_DPDNMan_SHIFT) +#define BC_CTRL2_DPDrv_MASK (0x3 << BC_CTRL2_DPDrv_SHIFT) +#define BC_CTRL2_DNDrv_MASK (0x3 << BC_CTRL2_DNDrv_SHIFT) + +/* MAX77775 SWITCH COMMAND */ +#define COMN1SW_SHIFT 0 +#define COMP2SW_SHIFT 3 +#define USBAUTODET_SHIFT 6 +#define NOBCCOMP_SHIFT 7 +#define COMN1SW_MASK (0x7 << COMN1SW_SHIFT) +#define COMP2SW_MASK (0x7 << COMP2SW_SHIFT) +#define USBAUTODET_MASK (0x1 << USBAUTODET_SHIFT) +#define NOBCCOMP_MASK (0x1 << NOBCCOMP_SHIFT) +#define NOBCCOMP_USBAUTODET_MASK (NOBCCOMP_MASK | USBAUTODET_MASK) +#define COMSW_MASK (COMN1SW_MASK | COMP2SW_MASK) + +enum { + VB_LOW = 0x00, + VB_HIGH = (0x1 << BC_STATUS_VBUSDET_SHIFT), + + VB_DONTCARE = 0xff, +}; + +/* MAX77775 MUIC Charger Type Detection Output Value */ +enum { + /* No Valid voltage at VB (Vvb < Vvbdet) */ + CHGTYP_NO_VOLTAGE = 0x00, + /* Unknown (D+/D- does not present a valid USB charger signature) */ + CHGTYP_USB = 0x01, + /* Charging Downstream Port */ + CHGTYP_CDP = 0x02, + /* Dedicated Charger (D+/D- shorted) */ + CHGTYP_DEDICATED_CHARGER = 0x03, + + /* Hiccup mode, Set D+/D- to GND */ + CHGTYP_HICCUP_MODE = 0xfa, + /* DCD Timeout, Open D+/D- */ + CHGTYP_TIMEOUT_OPEN = 0xfb, + /* Any charger w/o USB */ + CHGTYP_UNOFFICIAL_CHARGER = 0xfc, + /* Any charger type */ + CHGTYP_ANY = 0xfd, + /* Don't care charger type */ + CHGTYP_DONTCARE = 0xfe, + + CHGTYP_MAX, + CHGTYP_INIT, + CHGTYP_MIN = CHGTYP_NO_VOLTAGE +}; + +/* MAX77775 MUIC Special Charger Type Detection Output value (BC_STATUS.PrChgTyp) */ +enum { + PRCHGTYP_UNKNOWN = 0x00, + PRCHGTYP_SAMSUNG_2A = 0x01, + PRCHGTYP_APPLE_500MA = 0x02, + PRCHGTYP_APPLE_1A = 0x03, + PRCHGTYP_APPLE_2A = 0x04, + PRCHGTYP_APPLE_12W = 0x05, + PRCHGTYP_3A_DCP = 0x06, + PRCHGTYP_RFU = 0x07, +}; + +enum { + PROCESS_ATTACH = 0, + PROCESS_LOGICALLY_DETACH, + PROCESS_NONE, +}; + +/* muic register value for COMN1, COMN2 in Switch command */ + +/* + * MAX77775 Switch values + * NoBCComp [7] / USBAutoDet [6] / D+ [5:3] / D- [2:0] + * 0: Compare with BC1.2 / 1: Ignore BC1.2, Manual control + * 0: Disable / 1: Enable + * 000: Open / 001, 100: USB / 011, 101: UART + */ +enum max77775_switch_val { + MAX77775_MUIC_NOBCCOMP_DIS = 0x0, + MAX77775_MUIC_NOBCCOMP_EN = 0x1, + + MAX77775_MUIC_USBAUTODET_DIS = 0x0, + MAX77775_MUIC_USBAUTODET_EN = 0x1, + MAX77775_MUIC_USBAUTODET_VAL = MAX77775_MUIC_USBAUTODET_DIS, + + MAX77775_MUIC_COM_OPEN = 0x00, + MAX77775_MUIC_COM_USB = 0x01, + MAX77775_MUIC_COM_UART = 0x02, + MAX77775_MUIC_COM_UART_CP = 0x03, +}; + +enum { + COM_OPEN = (MAX77775_MUIC_NOBCCOMP_DIS << NOBCCOMP_SHIFT) | + (MAX77775_MUIC_USBAUTODET_VAL << USBAUTODET_SHIFT) | + (MAX77775_MUIC_COM_OPEN << COMP2SW_SHIFT) | + (MAX77775_MUIC_COM_OPEN << COMN1SW_SHIFT), + COM_USB = (MAX77775_MUIC_NOBCCOMP_DIS << NOBCCOMP_SHIFT) | + (MAX77775_MUIC_USBAUTODET_VAL << USBAUTODET_SHIFT) | + (MAX77775_MUIC_COM_USB << COMP2SW_SHIFT) | + (MAX77775_MUIC_COM_USB << COMN1SW_SHIFT), + COM_UART = (MAX77775_MUIC_NOBCCOMP_EN << NOBCCOMP_SHIFT) | + (MAX77775_MUIC_USBAUTODET_VAL << USBAUTODET_SHIFT) | + (MAX77775_MUIC_COM_UART << COMP2SW_SHIFT) | + (MAX77775_MUIC_COM_UART << COMN1SW_SHIFT), + COM_UART_CP = (MAX77775_MUIC_NOBCCOMP_EN << NOBCCOMP_SHIFT) | + (MAX77775_MUIC_USBAUTODET_VAL << USBAUTODET_SHIFT) | + (MAX77775_MUIC_COM_UART_CP << COMP2SW_SHIFT) | + (MAX77775_MUIC_COM_UART_CP << COMN1SW_SHIFT), +}; + +extern struct muic_platform_data muic_pdata; +extern struct device *switch_device; +extern int max77775_muic_probe(struct max77775_usbc_platform_data *usbc_data); +extern int max77775_muic_remove(struct max77775_usbc_platform_data *usbc_data); +extern void max77775_muic_shutdown(struct max77775_usbc_platform_data *usbc_data); +extern int max77775_muic_suspend(struct max77775_usbc_platform_data *usbc_data); +extern int max77775_muic_resume(struct max77775_usbc_platform_data *usbc_data); +#if IS_ENABLED(CONFIG_HV_MUIC_MAX77775_AFC) +extern bool max77775_muic_check_is_enable_afc(struct max77775_muic_data *muic_data, muic_attached_dev_t new_dev); +extern void max77775_muic_check_afc_disabled(struct max77775_muic_data *muic_data); +extern void max77775_muic_clear_hv_control(struct max77775_muic_data *muic_data); +extern int max77775_muic_afc_hv_set(struct max77775_muic_data *muic_data, int voltage); +extern int max77775_muic_qc_hv_set(struct max77775_muic_data *muic_data, int voltage); +extern void max77775_muic_handle_detect_dev_afc(struct max77775_muic_data *muic_data, unsigned char *data); +extern void max77775_muic_handle_detect_dev_qc(struct max77775_muic_data *muic_data, unsigned char *data); +extern void max77775_muic_handle_detect_dev_hv(struct max77775_muic_data *muic_data, unsigned char *data); +extern int __max77775_muic_afc_set_voltage(struct max77775_muic_data *muic_data, int voltage); +#endif /* CONFIG_HV_MUIC_MAX77775_AFC */ +extern void max77775_muic_register_ccic_notifier(struct max77775_muic_data *muic_data); +extern void max77775_muic_unregister_ccic_notifier(struct max77775_muic_data *muic_data); +#if defined(CONFIG_USB_EXTERNAL_NOTIFY) +extern void muic_send_dock_intent(int type); +#endif /* CONFIG_USB_EXTERNAL_NOTIFY */ +extern void max77775_muic_enable_detecting_short(struct max77775_muic_data *muic_data); +#endif /* __MAX77775_MUIC_H__ */ + diff --git a/include/linux/usb/typec/maxim/max77775.h b/include/linux/usb/typec/maxim/max77775.h new file mode 100755 index 000000000000..5967bc33b86d --- /dev/null +++ b/include/linux/usb/typec/maxim/max77775.h @@ -0,0 +1,607 @@ +/* + * Copyrights (C) 2017 Samsung Electronics, Inc. + * Copyrights (C) 2017 Maxim Integrated Products, 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. + */ + +#ifndef __LINUX_MFD_MAX77775_H +#define __LINUX_MFD_MAX77775_H + +#define MAX77775_CCPD_NAME "MAX77775" + +#undef __CONST_FFS +#define __CONST_FFS(_x) \ + ((_x) & 0x0F ? \ + ((_x) & 0x03 ? ((_x) & 0x01 ? 0 : 1) : ((_x) & 0x04 ? 2 : 3)) : \ + ((_x) & 0x30 ? ((_x) & 0x10 ? 4 : 5) : ((_x) & 0x40 ? 6 : 7))) + +#undef FFS +#define FFS(_x) \ + ((_x) ? __CONST_FFS(_x) : 0) + +#undef BIT_RSVD +#define BIT_RSVD 0 + +#undef BITS +#define BITS(_end, _start) \ + ((BIT(_end) - BIT(_start)) + BIT(_end)) + +#undef __BITS_GET +#define __BITS_GET(_word, _mask, _shift) \ + (((_word) & (_mask)) >> (_shift)) + +#undef BITS_GET +#define BITS_GET(_word, _bit) \ + __BITS_GET(_word, _bit, FFS(_bit)) + +#undef __BITS_SET +#define __BITS_SET(_word, _mask, _shift, _val) \ + (((_word) & ~(_mask)) | (((_val) << (_shift)) & (_mask))) + +#undef BITS_SET +#define BITS_SET(_word, _bit, _val) \ + __BITS_SET(_word, _bit, FFS(_bit), _val) + +#undef BITS_MATCH +#define BITS_MATCH(_word, _bit) \ + (((_word) & (_bit)) == (_bit)) + +/* + * Register address + */ +#define REG_PRODUCT_ID 0x10 /* replaced address */ +#define REG_UIC_FW_REV 0x01 + +#define REG_UIC_INT 0x02 +#define REG_CC_INT 0x03 +#define REG_PD_INT 0x04 +#define REG_VDM_INT 0x05 +#define REG_SPARE_INT 0x06 +#define REG_USBC_STATUS1 0x08 +#define REG_USBC_STATUS2 0x09 +#define REG_BC_STATUS 0x0A +#define REG_UIC_FW_REV2 0x0B +#define REG_CC_STATUS1 0x0C +#define REG_CC_STATUS2 0x0D +#define REG_PD_STATUS1 0x0E +#define REG_PD_STATUS2 0x0F +#define REG_UIC_INT_M 0x12 +#define REG_CC_INT_M 0x13 +#define REG_PD_INT_M 0x14 +#define REG_VDM_INT_M 0x15 +#define REG_SPARE_INT_M 0x16 + +#define REG_OPCODE 0x21 +#define REG_OPCODE_DATA 0x22 +#define REG_OPCDE_RES 0x51 + +#define REG_END_DATA 0x41 + +/* + * REG_INT_M Initial values + */ +#define REG_UIC_INT_M_INIT 0x04 +#define REG_CC_INT_M_INIT 0x20 +#define REG_PD_INT_M_INIT 0x00 +#define REG_VDM_INT_M_INIT 0xF0 +#if defined(CONFIG_MAX77775_CCOPEN_AFTER_WATERCABLE) +#define REG_SPARE_INT_M_INIT 0x3F +#else +#define REG_SPARE_INT_M_INIT 0x7F +#endif + +/* + * REG_UIC_INT Interrupts + */ +#define BIT_APCmdResI BIT(7) +#define BIT_SYSMsgI BIT(6) +#define BIT_VBUSDetI BIT(5) +#define BIT_VbADCI BIT(4) +#define BIT_DCDTmoI BIT(3) +#define BIT_CHGTypI BIT(1) +#define BIT_UIDADCI BIT(0) + +/* + * REG_CC_INT Interrupts + */ +#define BIT_VCONNOCPI BIT(7) +#define BIT_VSAFE0VI BIT(6) +#define BIT_DETABRTI BIT(5) +#define BIT_VCONNSCI BIT(4) +#define BIT_CCPinStatI BIT(3) +#define BIT_CCIStatI BIT(2) +#define BIT_CCVcnStatI BIT(1) +#define BIT_CCStatI BIT(0) + +/* + * REG_PD_INT Interrupts + */ +#define BIT_PDMsgI BIT(7) +#define BIT_DataRole BIT(5) +#define BIT_SSAccI BIT(1) +#define BIT_FCTIDI BIT(0) + +/* + * REG_SPARE_INT Interrupts + */ +#define BIT_USBID BIT(7) +#define BIT_TACONNI BIT(6) + +/* + * REG_USBC_STATUS1 + */ +#define BIT_VBADC BITS(7, 4) +#define BIT_UIDADC BITS(2, 0) + +/* + * REG_USBC_STATUS2 + */ +#define BIT_SYSMsg BITS(7, 0) + +/* + * REG_BC_STATUS + */ +#define BIT_VBUSDet BIT(7) +#define BIT_PrChgTyp BITS(5, 3) +#define BIT_DCDTmo BIT(2) +#define BIT_ChgTyp BITS(1, 0) + +/* + * REG_CC_STATUS1 + */ +#define BIT_CCPinStat BITS(7, 6) +#define BIT_CCIStat BITS(5, 4) +#define BIT_CCVcnStat BIT(3) +#define BIT_CCStat BITS(2, 0) + +/* + * REG_CC_STATUS2 + */ +#define BIT_CCSBUSHORT BITS(7, 6) +#define BIT_VCONNOCP BIT(5) +#define BIT_VCONNSC BIT(4) +#define BIT_VSAFE0V BIT(3) +#define BIT_DETABRT BIT(2) +#define BIT_ConnStat BIT(1) +#define BIT_Altmode BIT(0) + +/* + * REG_PD_STATUS1 + */ +#define BIT_PDMsg BITS(7, 0) + +/* + * REG_PD_STATUS2 + */ +#define BIT_PD_DataRole BIT(7) +#define BIT_PD_ENTER_MODE BIT(5) +#define BIT_PD_PSRDY BIT(4) +#define BIT_FCT_ID BITS(3, 0) + +/* + * REG_SPARE_STATUS + */ +#define BIT_SPARE_CC_OPEN BIT(1) +#define BIT_SPARE_TA_CONNECT BIT(0) + +/** opcode reg **/ + +/* + * CC Control1 Write + */ +#define BIT_CCVcnEn BIT(7) +#define BIT_CCTrySnkEn BIT(6) +#define BIT_CCSrcDbgEn BIT(4) +#define BIT_CCSnkDbgEn BIT(3) +#define BIT_CCAudEn BIT(2) +#define BIT_CCSrcSnk BIT(1) // CCSrcEn +#define BIT_CCSnkSrc BIT(0) // CCSnkEn + +/* + * max77766 role + */ +enum max77775_data_role { + UFP = 0, + DFP, +}; +enum max77775_power_role { + SNK = 0, + SRC, +}; +enum max77775_vcon_role { + OFF = 0, + ON +}; + +/* + * F/W update + */ +#define FW_CMD_READ 0x3 +#define FW_CMD_READ_SIZE 6 /* cmd(1) + len(1) + data(4) */ + +#define FW_CMD_WRITE 0x1 +#define FW_CMD_WRITE_SIZE 36 /* cmd(1) + len(1) + data(34) */ + +#define FW_CMD_END 0x0 + +#define FW_HEADER_SIZE 8 +#define FW_VERIFY_DATA_SIZE 3 + +#define FW_VERIFY_TRY_COUNT 10 +#define FW_SECURE_MODE_TRY_COUNT 10 + +#define FW_WAIT_TIMEOUT (1000 * 5) /* 5 sec */ +#define I2C_SMBUS_BLOCK_HALF (I2C_SMBUS_BLOCK_MAX / 2) + +#define GET_CCCTRL4_LOCK_ERROR_EN(_x) ((_x & (0x1 << 4)) >> 1) + +enum { + NO_DETERMINATION = 0, + CC1_ACTIVE, + CC2_ACTVIE, + AUDIO_ACCESSORY, + DEBUG_ACCESSORY, + ERROR, + DISABLED, + DEBUG_SNK, +}; + +enum { + NOT_IN_UFP_MODE = 0, + CCI_500mA, + CCI_1_5A, + CCI_3_0A, + CCI_SHORT, +}; + +/* + * All type of Interrupts + */ +enum { + AP_Command_respond = 7, + USBC_System_message = 6, + CHGIN_Voltage = 5, + CHGIN_Voltage_ADC = 4, + DCD_Timer = 3, + Charge_Type = 1, + UID_ADC = 0, +}; + +enum max77775_chg_type { + CHGTYP_NOTHING = 0, + CHGTYP_USB_SDP, + CHGTYP_CDP_T, + CHGTYP_DCP, +}; + +enum max77775_pr_chg_type { + Unknown = 0, + Samsung_2A, + Apple_05A, + Apple_1A, + Apple_2A, + Apple_12W, + DCP_3A, + RFU_CHG, +}; + +enum max77775_ccpd_device { + DEV_NONE = 0, + DEV_OTG, + + DEV_USB, + DEV_CDP, + DEV_DCP, + + DEV_APPLE500MA, + DEV_APPLE1A, + DEV_APPLE2A, + DEV_APPLE12W, + DEV_DCP3A, + DEV_HVDCP, + DEV_QC, + + DEV_FCT_GND, + DEV_FCT_1K, + DEV_FCT_56K, + DEV_FCT_255K, + DEV_FCT_301K, + DEV_FCT_523K, + DEV_FCT_619K, + DEV_FCT_OPEN, + + DEV_PD_TA, + DEV_PD_AMA, + + DEV_UNKNOWN, +}; + +enum max77775_uidadc { + UID_GND = 0, + UID_100Kohm = 1, + UID_255Kohm = 3, + UID_301Kohm = 4, + UID_523Kohm = 5, + UID_619Kohm = 6, + UID_Open = 7, +}; + +enum max77775_fctid { + FCT_GND = 1, + FCT_56Kohm = 2, + FCT_255Kohm = 3, + FCT_301Kohm = 4, + FCT_523Kohm = 5, + FCT_619Kohm = 6, + FCT_OPEN = 7, +}; + +enum max77775_cc_pin_state { + cc_No_Connection = 0, + cc_SINK, + cc_SOURCE, + cc_Audio_Accessory, + cc_Debug_Accessory, + cc_Error, + cc_Disabled, + cc_Debug_Sink, +}; + +enum max77775_usbc_SYSMsg { + SYSERROR_NONE = 0x00, + /*Reserved = 0x01,*/ + /*Reserved = 0x02,*/ + SYSERROR_BOOT_WDT = 0x03, + SYSERROR_BOOT_SWRSTREQ = 0x04, + SYSMSG_BOOT_POR = 0x05, + + SYSERROR_HV_NOVBUS = 0x10, + SYSERROR_HV_FMETHOD_RXPERR = 0x11, + SYSERROR_HV_FMETHOD_RXBUFOW = 0x12, + SYSERROR_HV_FMETHOD_RXTFR = 0x13, + SYSERROR_HV_FMETHOD_MPNACK = 0x14, + SYSERROR_HV_FMETHOD_RESET_FAIL = 0x15, + + SYSMsg_AFC_Done = 0x20, + + SYSERROR_SYSPOS = 0x30, + SYSERROR_APCMD_UNKNOWN = 0x31, + SYSERROR_APCMD_INPROGRESS = 0x32, + SYSERROR_APCMD_FAIL = 0x33, + + SYSMSG_CCx_5V_AFC_SHORT = 0x61, + SYSMSG_SBUx_GND_SHORT = 0x62, + SYSMSG_CCx_5V_SHORT = 0x65, + SYSMSG_SBUx_5V_SHORT = 0x66, + + SYSERROR_FACTORY_RID0 = 0x70, + SYSERROR_POWER_NEGO = 0x80, + SYSERROR_CCRP_HIGH = 0x90, /* PD Charger Connected while Water state */ + SYSERROR_CCRP_LOW = 0x91, /* PD Charger Disconnected while Water state */ + + SYSMSG_RELEASE_CC1_SHORT = 0xB3, + SYSMSG_RELEASE_CC2_SHORT = 0xB4, + + SYSMSG_ABNORMAL_TA = 0xC1, +}; + +enum max77775_pdmsg { + Nothing_happened = 0x00, + Sink_PD_PSRdy_received = 0x01, + Sink_PD_Error_Recovery = 0x02, + Sink_PD_SenderResponseTimer_Timeout = 0x03, + Source_PD_PSRdy_Sent = 0x04, + Source_PD_Error_Recovery = 0x05, + Source_PD_SenderResponseTimer_Timeout = 0x06, + PD_DR_Swap_Request_Received = 0x07, + PD_PR_Swap_Request_Received = 0x08, + PD_VCONN_Swap_Request_Received = 0x09, + Received_PD_Message_in_illegal_state = 0x0A, + SRC_CAP_RECEIVED = 0x0B, + + Samsung_Accessory_is_attached = 0x10, + VDM_Attention_message_Received = 0x11, + Rejcet_Received = 0x12, + Not_Supported_Received = 0x13, + Prswap_Snktosrc_Sent = 0x14, + Prswap_Srctosnk_Sent = 0x15, + HARDRESET_RECEIVED = 0x16, + Get_Vbus_turn_on = 0x17, + Get_Vbus_turn_off = 0x18, + HARDRESET_SENT = 0x19, + PRSWAP_SRCTOSWAP = 0x1A, + PRSWAP_SWAPTOSNK = 0X1B, + PRSWAP_SNKTOSWAP = 0x1C, + PRSWAP_SWAPTOSRC = 0x1D, + + Sink_PD_Disabled = 0x20, + Source_PD_Disabled = 0x21, + Current_Cable_Connected = 0x22, + + Get_Source_Capabilities_Extended_Received = 0x30, + Get_Status_Received = 0x31, + Get_Battery_Cap_Received = 0x32, + Get_Battery_Status_Received = 0x33, + Get_Manufacturer_Info_Received = 0x34, + Source_Capabilities_Extended_Received = 0x35, + Status_Received = 0x36, + Battery_Capabilities_Received = 0x37, + Batery_Status_Received = 0x38, + Manufacturer_Info_Received = 0x39, + Alert_Message = 0x3e, + VDM_NAK_Recevied = 0x40, + VDM_BUSY_Recevied = 0x41, + VDM_ACK_Recevied = 0x42, + VDM_REQ_Recevied = 0x43, + + AFC_Sink_PD_Capabilities_Received = 0x50, + AFC_Sink_PD_PSRdy_Received = 0x51, + AFC_VDM_ResponseTimer_Timout = 0x52, + + PDMSG_DP_DISCOVER_IDENTITY = 0x61, + PDMSG_DP_DISCOVER_SVID = 0x62, + PDMSG_DP_DISCOVER_MODES = 0x63, + PDMSG_DP_ENTER_MODE = 0x64, + PDMSG_DP_EXIT_MODE = 0x65, + PDMSG_DP_STATUS = 0x66, + PDMSG_DP_CONFIGURE = 0x67, + PDMSG_DP_ATTENTION = 0x68, + + PDMSG_SRC_ACCEPT = 0x70, + +}; +enum max77775_connstat { + DRY = 0x00, + WATER = 0x01, +}; + +/* + * External type definition + */ +#define MAX77775_AUTOIBUS_FW_AT_OFF 3 +#define MAX77775_AUTOIBUS_FW_OFF 2 +#define MAX77775_AUTOIBUS_AT_OFF 1 +#define MAX77775_AUTOIBUS_ON 0 + +#define OPCODE_WAIT_TIMEOUT (3000) /* 3000ms */ + +#define OPCODE_WRITE_COMMAND 0x21 +#define OPCODE_READ_COMMAND 0x51 +#define OPCODE_SIZE 1 +#define OPCODE_HEADER_SIZE 1 +#define OPCODE_DATA_LENGTH 32 +#define OPCODE_MAX_LENGTH (OPCODE_DATA_LENGTH + OPCODE_SIZE) +#define OPCODE_WRITE 0x21 +#define OPCODE_DATAOUT1 0x22 +#define OPCODE_WRITE_END 0x41 +#define OPCODE_READ 0x51 +#define OPCODE_WRITE_SEQ 0x1 +#define OPCODE_READ_SEQ 0x2 +#define OPCODE_RW_SEQ 0x3 +#define OPCODE_PUSH_SEQ 0x4 +#define OPCODE_UPDATE_SEQ 0x5 + +typedef enum { + OPCODE_BCCTRL1_R = 0x01, + OPCODE_BCCTRL1_W, + OPCODE_BCCTRL2_R, + OPCODE_BCCTRL2_W, + OPCODE_CTRL1_R = 0x05, + OPCODE_CTRL1_W, + + OPCODE_CCCTRL1_R = 0x0B, + OPCODE_CCCTRL1_W, + OPCODE_CCCTRL2_R, + OPCODE_CCCTRL2_W, + OPCODE_CCCTRL3_R, + OPCODE_CCCTRL3_W, + OPCODE_CCCTRL4_R = 0x11, + OPCODE_CCCTRL4_W, + OPCODE_VCONN_ILIM_R = 0x13, + OPCODE_VCONN_ILIM_W, + + OPCODE_CCCTRL5_W = 0x16, + + OPCODE_AFC_HV_W = 0x20, + OPCODE_AFC_HV_RESULT_W = 0x21, + OPCODE_AFC_QC_2_0_SET = 0x22, + + OPCODE_CHGIN_ILIM_R = 0x25, + OPCODE_CHGIN_ILIM_W, + + OPCODE_USB_ID_SET = 0x27, + OPCODE_READ_SBU = 0x28, + OPCODE_CONTROL_JIG_R = 0x29, + OPCODE_CONTROL_JIG_W = 0x2A, + OPCODE_ICURR_AUTOIBUS_ON = 0x2C, + + OPCODE_SET_SNKCAP = 0x2E, + OPCODE_READ_CC = 0x2D, + OPCODE_CURRENT_SRCCAP = 0x30, + OPCODE_GET_SRCCAP = 0x31, + OPCODE_SRCCAP_REQUEST = 0x32, + OPCODE_SET_SRCCAP = 0x33, + OPCODE_SEND_GET_REQUEST = 0x34, + OPCODE_READ_RESPONSE_FOR_GET_REQUEST = 0x35, + OPCODE_SWAP_REQUEST = 0x37, + OPCODE_APDO_SRCCAP_REQUEST = 0x3A, + OPCODE_SET_PPS = 0x3C, + + OPCODE_VDM_DISCOVER_SET_VDM_REQ = 0x48, + OPCODE_VDM_DISCOVER_GET_VDM_RESP = 0x4B, + + OPCODE_SAMSUNG_FACTORY_TEST = 0x54, + OPCODE_SET_ALTERNATEMODE = 0x55, + OPCODE_SAMSUNG_CCSBU_SHORT = 0x56, + OPCODE_SAMSUNG_FW_AUTOIBUS = 0x57, + + OPCODE_SAMSUNG_READ_MESSAGE = 0x5D, + OPCODE_SNK_SELECTED_PDO = 0x65, + OPCODE_FW_OPCODE_CLEAR = 0x70, + OPCODE_SBU_CTRL1_R = 0x85, + OPCODE_SBU_CTRL1_W = 0x86, + OPCODE_FCCTRL1_W = 0x88, + OPCODE_MOISTURE_CC_OPEN = 0x9C, + + OPCODE_ACTIVE_DISCHARGE = 0x91, + OPCODE_HICCUP_ENABLE = 0x92, + OPCODE_AUTO_SHIPMODE = 0x97, + OPCODE_BYPASS_MTN = 0x98, + OPCODE_CHGRCV_RAMP = 0x9F, + OPCODE_NONE = 0xff, +} max77775_opcode_list; + +typedef enum{ + OPCODE_ID_VDM_DISCOVER_IDENTITY = 0x1, + OPCODE_ID_VDM_DISCOVER_SVIDS = 0x2, + OPCODE_ID_VDM_DISCOVER_MODES = 0x3, + OPCODE_ID_VDM_ENTER_MODE = 0x4, + OPCODE_ID_VDM_EXIT_MODE = 0x5, + OPCODE_ID_VDM_ATTENTION = 0x6, + OPCODE_ID_VDM_SVID_DP_STATUS = 0x10, + OPCODE_ID_VDM_SVID_DP_CONFIGURE = 0x11, +} max77775_vdm_list; + +enum{ + OPCODE_GET_SRC_CAP_EXT = 0, + OPCODE_GET_STATUS, + OPCODE_GET_BAT_CAP, + OPCODE_GET_BAT_STS, + OPCODE_GET_MANUFACTURE_INFO, +}; + +typedef enum { + OPCODE_WAIT_START = 0, + OPCODE_WAIT_END, +} max77775_opcode_wait_type; + + +typedef enum { + OPCODE_NOTI_START = 0x0, + OPCODE_NOTI_NONE = 0xff, +} max77775_opcode_noti_cmd; + + +/* SAMSUNG OPCODE */ +#define REG_NONE 0xff +#define CCIC_IRQ_INIT_DETECT (-1) + +//#define MINOR_VERSION_MASK 0b00000111 + +/* PRODUCT ID */ +#define FW_PRODUCT_ID_REG 3 +//#define STAR_PRODUCT_ID 0b0000 +//#define Lykan_PRODUCT_ID 0b0001 +//#define BEYOND_PRODUCT_ID 0b0010 +#endif + + diff --git a/include/linux/usb/typec/maxim/max77775_alternate.h b/include/linux/usb/typec/maxim/max77775_alternate.h new file mode 100755 index 000000000000..5684bb38c087 --- /dev/null +++ b/include/linux/usb/typec/maxim/max77775_alternate.h @@ -0,0 +1,628 @@ +/* + * Copyrights (C) 2017 Samsung Electronics, Inc. + * Copyrights (C) 2017 Maxim Integrated Products, 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. + */ + +#ifndef __LINUX_MAXIM_CCIC_ALTERNATE_MODE_H__ +#define __LINUX_MAXIM_CCIC_ALTERNATE_MODE_H__ + +#if defined(CONFIG_MAX77775_CCIC_ALTERNATE_MODE) +typedef union { /* new defined union for MD05 Op Code Command Data */ + uint8_t DATA; + struct { + uint8_t BDATA[1]; + } BYTES; + struct { + uint8_t Num_Of_VDO:3, + Cmd_Type:2, + Reserved:3; + } BITS; +} SEND_VDM_BYTE_DATA; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t Number_of_obj:3, + MSG_ID:3, + Port_Power_Role:1, + Specification_Rev:2, + Port_Data_Role:1, + Reserved:1, + MSG_Type:4; + } BITS; +} UND_DATA_MSG_HEADER_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t VDM_command:5, + Rsvd2_VDM_header:1, + VDM_command_type:2, + Object_Position:3, + Rsvd_VDM_header:2, + Structured_VDM_Version:2, + VDM_Type:1, + Standard_Vendor_ID:16; + } BITS; +} UND_DATA_MSG_VDM_HEADER_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t USB_Vendor_ID:16, + Rsvd_ID_header:10, + Modal_Operation_Supported:1, + Product_Type:3, + Data_Capable_USB_Device:1, + Data_Capable_USB_Host:1; + } BITS; +} UND_DATA_MSG_ID_HEADER_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t Cert_TID:20, + Rsvd_cert_VDOer:12; + } BITS; +} UND_CERT_STAT_VDO_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t Device_Version:16, + Product_ID:16; + } BITS; +} UND_PRODUCT_VDO_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t USB_Superspeed_Signaling_Support:3, + SOP_contoller_present:1, + Vbus_through_cable:1, + Vbus_Current_Handling_Capability:2, + SSRX2_Directionality_Support:1, + SSRX1_Directionality_Support:1, + SSTX2_Directionality_Support:1, + SSTX1_Directionality_Support:1, + Cable_Termination_Type:2, + Cable_Latency:4, + TypeC_to_Plug_Receptacle:1, + TypeC_to_ABC:2, + Rsvd_CABLE_VDO:4, + Cable_Firmware_Version:4, + Cable_HW_Version:4; + } BITS; +} UND_CABLE_VDO_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t SVID_1:16, + SVID_0:16; + } BITS; +} UND_VDO1_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t VENDOR_DEFINED_MESSAGE:15, + VDM_TYPE:1, + USB_Vendor_ID:16; + } BITS; +} UND_UNSTRUCTURED_VDM_HEADER_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t DATA:8, + TOTAL_NUMBER_OF_UVDM_SET:4, + RESERVED:1, + COMMAND_TYPE:2, + DATA_TYPE:1, + PID:16; + } BITS; +} UND_SEC_UNSTRUCTURED_VDM_HEADER_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t VENDOR_DEFINED_MESSAGE:15, + VDM_TYPE:1, + USB_Vendor_ID:16; + } BITS; +} UND_SEC_DATA_HEADER_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t VENDOR_DEFINED_MESSAGE:15, + VDM_TYPE:1, + USB_Vendor_ID:16; + } BITS; +} UND_SEC_DATA_TAILER_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t ORDER_OF_CURRENT_UVDM_SET:4, + RESERVED:9, + COMMAND_TYPE:2, + DATA_TYPE:1, + PID:16; + } BITS; +} UND_SEC_UNSTRUCTURED_VDM_RESPONSE_HEADER_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t VENDOR_DEFINED_MESSAGE:15, + VDM_TYPE:1, + USB_Vendor_ID:16; + } BITS; +} UND_SEC_DATA_RESPONSE_HEADER_Type; + +typedef struct { + uint32_t VDO[7]; +} VDO_MESSAGE_Type; + +/* For DP */ +#define TypeC_POWER_SINK_INPUT 0 +#define TypeC_POWER_SOURCE_OUTPUT 1 +#define TypeC_DP_SUPPORT (0xFF01) + +/* For Dex */ +#define TypeC_Dex_SUPPORT (0x04E8) +#define SWAP_UINT32(x) (((x) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | ((x) << 24)) + +/* For DP VDM Modes VDO Port_Capability */ +typedef enum { + num_Reserved_Capable = 0, + num_UFP_D_Capable = 1, + num_DFP_D_Capable = 2, + num_DFP_D_and_UFP_D_Capable = 3 +} Num_DP_Port_Capability_Type; + +/* For DP VDM Modes VDO Receptacle_Indication */ +typedef enum { + num_USB_TYPE_C_PLUG = 0, + num_USB_TYPE_C_Receptacle = 1 +} Num_DP_Receptacle_Indication_Type; + + +/* For DP_Status_Update Port_Connected */ +typedef enum { + num_Adaptor_Disable = 0, + num_Connect_DFP_D = 1, + num_Connect_UFP_D = 2, + num_Connect_DFP_D_and_UFP_D = 3 +} Num_DP_Port_Connected_Type; + +/* For DP_Configure Select_Configuration */ +typedef enum { + num_Cfg_for_USB = 0, + num_Cfg_UFP_U_as_DFP_D = 1, + num_Cfg_UFP_U_as_UFP_D = 2, + num_Cfg_Reserved = 3 +} Num_DP_Sel_Configuration_Type; + +typedef enum { /* There is another Macro definitions which are similiar to this */ + REQ = 0, + ACK = 1, + NAK = 2, + BUSY = 3 +} VDM_CMD_TYPE; + +typedef enum { + Reserved = 0, + Discover_Identity = 1, + Discover_SVIDs = 2, + Discover_Modes = 3, + Enter_Mode = 4, + Exit_Mode = 5, + Attention = 6, + Configure = 17 +} VDM_HEADER_COMMAND; + +typedef enum { + Version_1_0 = 0, + Version_2_0 = 1, + Reserved1 = 2, + Reserved2 = 3 +} STRUCTURED_VDM_VERSION; +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t Port_Capability:2, + Signalling_DP:4, + Receptacle_Indication:1, + USB_2p0_Not_Used:1, + DFP_D_Pin_Assignments:8, + UFP_D_Pin_Assignments:8, + DP_MODE_VDO_Reserved:8; + } BITS; +} UND_VDO_MODE_DP_CAPABILITY_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t Port_Connected:2, + Power_Low:1, + Enabled:1, + Multi_Function_Preference:1, + USB_Configuration_Req:1, + Exit_DP_Mode_Req:1, + HPD_State:1, + HPD_Interrupt:1, + Reserved:23; + } BITS; +} UND_VDO_DP_STATUS_UPDATES_Type; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t SEL_Configuration:2, + Select_DP_V1p3:1, + Select_USB_Gen2:1, + Select_Reserved_1:2, + Select_Reserved_2:2, + DFP_D_PIN_Assign_A:1, + DFP_D_PIN_Assign_B:1, + DFP_D_PIN_Assign_C:1, + DFP_D_PIN_Assign_D:1, + DFP_D_PIN_Assign_E:1, + DFP_D_PIN_Assign_F:1, + DFP_D_PIN_Reserved:2, + UFP_D_PIN_Assign_A:1, + UFP_D_PIN_Assign_B:1, + UFP_D_PIN_Assign_C:1, + UFP_D_PIN_Assign_D:1, + UFP_D_PIN_Assign_E:1, + UFP_D_PIN_Assign_F:1, + UFP_D_PIN_Reserved:2, + DP_MODE_Reserved:8; + } BITS; +} UND_DP_CONFIG_UPDATE_Type; + +typedef struct { + UND_DATA_MSG_HEADER_Type MSG_HEADER; + UND_DATA_MSG_VDM_HEADER_Type DATA_MSG_VDM_HEADER; + UND_VDO_MODE_DP_CAPABILITY_Type DATA_MSG_MODE_VDO_DP; +} DIS_MODE_DP_CAPA_Type; + +typedef struct { + UND_DATA_MSG_HEADER_Type MSG_HEADER; + UND_DATA_MSG_VDM_HEADER_Type DATA_MSG_VDM_HEADER; + UND_VDO_DP_STATUS_UPDATES_Type DATA_DP_STATUS_UPDATE; +} DP_STATUS_UPDATE_Type; + +typedef struct { + UND_DATA_MSG_HEADER_Type MSG_HEADER; + UND_DATA_MSG_VDM_HEADER_Type DATA_MSG_VDM_HEADER; + UND_VDO_DP_STATUS_UPDATES_Type DATA_MSG_DP_STATUS; +} DIS_ATTENTION_MESSAGE_DP_STATUS_Type; + +enum VDM_MSG_IRQ_State { + VDM_DISCOVER_ID = (1 << 0), + VDM_DISCOVER_SVIDS = (1 << 1), + VDM_DISCOVER_MODES = (1 << 2), + VDM_ENTER_MODE = (1 << 3), + VDM_EXIT_MODE = (1 << 4), + VDM_ATTENTION = (1 << 5), + VDM_DP_STATUS_UPDATE = (1 << 6), + VDM_DP_CONFIGURE = (1 << 7), +}; + +#define ALTERNATE_MODE_NOT_READY (1 << 0) +#define ALTERNATE_MODE_READY (1 << 1) +#define ALTERNATE_MODE_STOP (1 << 2) +#define ALTERNATE_MODE_START (1 << 3) +#define ALTERNATE_MODE_RESET (1 << 4) + +/* VMD Message Register I2C address by S.LSI */ +#define REG_VDM_MSG_REQ 0x02C0 +#define REG_SSM_MSG_READ 0x0340 +#define REG_SSM_MSG_SEND 0x0360 + +#define REG_TX_DIS_ID_RESPONSE 0x0400 +#define REG_TX_DIS_SVID_RESPONSE 0x0420 +#define REG_TX_DIS_MODE_RESPONSE 0x0440 +#define REG_TX_ENTER_MODE_RESPONSE 0x0460 +#define REG_TX_EXIT_MODE_RESPONSE 0x0480 +#define REG_TX_DIS_ATTENTION_RESPONSE 0x04A0 + +#define REG_RX_DIS_ID_CABLE 0x0500 +#define REG_RX_DIS_ID 0x0520 +#define REG_RX_DIS_SVID 0x0540 +#define REG_RX_MODE 0x0560 +#define REG_RX_ENTER_MODE 0x0580 +#define REG_RX_EXIT_MODE 0x05A0 +#define REG_RX_DIS_ATTENTION 0x05C0 +#define REG_RX_DIS_DP_STATUS_UPDATE 0x0600 +#define REG_RX_DIS_DP_CONFIGURE 0x0620 + +#define MODE_INT_CLEAR 0x01 +#define PD_NEXT_STATE 0x02 +#define MODE_INTERFACE 0x03 +#define SVID_SELECT 0x07 +#define REQ_PR_SWAP 0x10 +#define REQ_DR_SWAP 0x11 +#define SEL_SSM_MSG_REQ 0x20 +#define DP_ALT_MODE_REQ 0x30 +/* Samsung Acc VID */ +#define SAMSUNG_VENDOR_ID 0x04E8 +#define SAMSUNG_MPA_VENDOR_ID 0x04B4 +/* Samsung Acc PID */ +#define GEARVR_PRODUCT_ID 0xA500 +#define GEARVR_PRODUCT_ID_1 0xA501 +#define GEARVR_PRODUCT_ID_2 0xA502 +#define GEARVR_PRODUCT_ID_3 0xA503 +#define GEARVR_PRODUCT_ID_4 0xA504 +#define GEARVR_PRODUCT_ID_5 0xA505 +#define DEXDOCK_PRODUCT_ID 0xA020 +#define HDMI_PRODUCT_ID 0xA025 +#define MPA2_PRODUCT_ID 0xA027 +#define UVDM_PROTOCOL_ID 0xA028 +#define DEXPAD_PRODUCT_ID 0xA029 +#define MPA_PRODUCT_ID 0x2122 +/* Samsung UVDM structure */ +#define SEC_UVDM_SHORT_DATA 0x0 +#define SEC_UVDM_LONG_DATA 0x1 +#define SEC_UVDM_ININIATOR 0x0 +#define SEC_UVDM_RESPONDER_ACK 0x1 +#define SEC_UVDM_RESPONDER_NAK 0x2 +#define SEC_UVDM_RESPONDER_BUSY 0x3 +#define UNSTRUCTURED_VDM 0x0 +#define STRUCTURED_VDM 1 +/*For DP Pin Assignment */ +#define DP_PIN_ASSIGNMENT_NODE 0x00000000 +#define DP_PIN_ASSIGNMENT_A 0x00000001 /* ( 1 << 0 ) */ +#define DP_PIN_ASSIGNMENT_B 0x00000002 /* ( 1 << 1 ) */ +#define DP_PIN_ASSIGNMENT_C 0x00000004 /* ( 1 << 2 ) */ +#define DP_PIN_ASSIGNMENT_D 0x00000008 /* ( 1 << 3 ) */ +#define DP_PIN_ASSIGNMENT_E 0x00000010 /* ( 1 << 4 ) */ +#define DP_PIN_ASSIGNMENT_F 0x00000020 /* ( 1 << 5 ) */ + +#define DISCOVER_IDENTITY_RESPONSE_SIZE 31 +#define DISCOVER_SVIDS_RESPONSE_SIZE 31 +#define DISCOVER_MODES_RESPONSE_SIZE 11 +#define ENTER_MODE_RESPONSE_SIZE 7 +#define ATTENTION_RESPONSE_SIZE 11 +#define DP_STATUS_RESPONSE_SIZE 11 +#define DP_CONFIGURE_RESPONSE_SIZE 11 + +#define SAMSUNGUVDM_DEXFAN_RESPONSE_SIZE 31 + +#define SAMSUNGUVDM_MAX_LONGPACKET_SIZE (236) +#define SAMSUNGUVDM_MAX_SHORTPACKET_SIZE (1) +#define SAMSUNGUVDM_WAIT_MS (2000) +#define SAMSUNGUVDM_ALIGN (4) +#define SAMSUNGUVDM_UVDM_HEADER_OFFSET (4) +#define SAMSUNGUVDM_MAXDATA_FIRST_UVDMSET (12) +#define SAMSUNGUVDM_MAXDATA_NORMAL_UVDMSET (16) +#define SAMSUNGUVDM_CHECKSUM_DATA_COUNT (20) + +#define MSG_HEADER_OFFSET (0) +#define VDM_HEADER_OFFSET (2) +#define VDO1_OFFSET (6) +#define VDO2_OFFSET (10) + +enum uvdm_rx_type { + RX_ACK = 0, + RX_NAK, + RX_BUSY, +}; + +typedef union sec_uvdm_header { + uint32_t data; + struct { + uint8_t bdata[4]; + } BYTES; + struct { + uint32_t data:8, + total_number_of_uvdm_set:4, + direction:1, + command_type:2, + data_type:1, + pid:16; + } BITS; +} U_SEC_UVDM_HEADER; + +typedef U_SEC_UVDM_HEADER U_SEC_UVDM_RESPONSE_HEADER; + +typedef union sec_tx_data_header { + uint32_t data; + struct { + uint8_t bdata[4]; + } BYTES; + struct { + uint32_t data_size_of_current_set:8, + total_data_size:8, + reserved:12, + order_of_current_uvdm_set:4; + } BITS; +} U_SEC_TX_DATA_HEADER; + +typedef union sec_data_tx_tailer { + uint32_t data; + struct { + uint8_t bdata[4]; + } BYTES; + struct { + uint32_t checksum:16, + reserved:16; + } BITS; +} U_SEC_TX_DATA_TAILER; + +typedef union sec_data_rx_header { + uint32_t data; + struct { + uint8_t bdata[4]; + } BYTES; + struct { + uint32_t reserved:18, + result_value:2, + received_data_size_of_current_set:8, + order_of_current_uvdm_set:4; + } BITS; +} U_SEC_RX_DATA_HEADER; + +typedef union { + uint32_t DATA; + struct { + uint8_t BDATA[4]; + } BYTES; + struct { + uint32_t VDM_command:5, + Rsvd2_VDM_header:1, + VDM_command_type:2, + Object_Position:3, + Rsvd_VDM_header:2, + Structured_VDM_Version:2, + VDM_Type:1, + Standard_Vendor_ID:16; + } BITS; +} U_DATA_MSG_VDM_HEADER_Type; + +#define SEC_UVDM_LONGPACKET_WAIT_MS (6000) +#define SEC_UVDM_RX_HEADER_ACK 0x0 +#define SEC_UVDM_RX_HEADER_NAK 0x1 + +#define MAXIM_ENABLE_ALTERNATE_SRCCAP 0x1 +#define MAXIM_ENABLE_ALTERNATE_VDM 0x2 +#define MAXIM_ENABLE_ALTERNATE_SRC_VDM 0x3 +#define MAXIM_DISABLE_ALTERNATE_SRC_VDM 0x0 + +typedef union { + uint32_t DATA; + uint8_t BYTES[4]; + struct { + uint32_t Vdm_Flag_Reserve_b0:1, /* b0 */ + Vdm_Flag_Discover_ID:1, /* b1 */ + Vdm_Flag_Discover_SVIDs:1, /* b2 */ + Vdm_Flag_Discover_MODEs:1, /* b3 */ + Vdm_Flag_Enter_Mode:1, /* b4 */ + Vdm_Flag_Exit_Mode:1, /* b5 */ + Vdm_Flag_Attention:1, /* b6 */ + Vdm_Flag_Reserved:9, /* b7 - b15 */ + Vdm_Flag_DP_Status_Update:1, /* b16 */ + Vdm_Flag_DP_Configure:1, /* b17 */ + Vdm_Flag_Reserved2:14; /* b18 - b31 */ + } BITS; +} MAX77775_VDM_MSG_IRQ_STATUS_Type; + +struct DP_DP_DISCOVER_IDENTITY { /* struct type definition */ + SEND_VDM_BYTE_DATA byte_data; + UND_DATA_MSG_VDM_HEADER_Type vdm_header; + +} __attribute__((aligned(1), packed)); + +struct DP_DP_DISCOVER_ENTER_MODE { /* struct type definition */ + SEND_VDM_BYTE_DATA byte_data; + UND_DATA_MSG_VDM_HEADER_Type vdm_header; + +} __attribute__((aligned(1), packed)); + +struct DP_DP_STATUS { /* struct type definition */ + SEND_VDM_BYTE_DATA byte_data; + UND_DATA_MSG_VDM_HEADER_Type vdm_header; + UND_VDO_DP_STATUS_UPDATES_Type vdo_status; +} __attribute__((aligned(1), packed)); + +struct DP_DP_CONFIGURE { /* struct type definition */ + SEND_VDM_BYTE_DATA byte_data; + UND_DATA_MSG_VDM_HEADER_Type vdm_header; + UND_DP_CONFIG_UPDATE_Type vdo_config; +} __attribute__((aligned(1), packed)); + +struct SS_DEX_DISCOVER_MODE { /* struct type definition */ + SEND_VDM_BYTE_DATA byte_data; + UND_DATA_MSG_VDM_HEADER_Type vdm_header; +} __attribute__((aligned(1), packed)); + +struct SS_DEX_ENTER_MODE { /* struct type definition */ + SEND_VDM_BYTE_DATA byte_data; + UND_DATA_MSG_VDM_HEADER_Type vdm_header; +} __attribute__((aligned(1), packed)); + +struct SS_UNSTRUCTURED_VDM_MSG{ + SEND_VDM_BYTE_DATA byte_data; + VDO_MESSAGE_Type VDO_MSG; +} __attribute__((aligned(1), packed)); + +void max77775_receive_alternate_message(struct max77775_usbc_platform_data *data, + MAX77775_VDM_MSG_IRQ_STATUS_Type *VDM_MSG_IRQ_State); +void max77775_vdm_message_handler(struct max77775_usbc_platform_data *usbpd_data, + char *opcode_data, int len); +void max77775_uvdm_opcode_response_handler(struct max77775_usbc_platform_data *usbpd_data, + char *opcode_data, int len); +void max77775_send_dex_fan_unstructured_vdm_message(void *data, int cmd); +void max77775_set_enable_alternate_mode(int mode); +int max77775_process_check_accessory(void *data); +extern void max77775_set_discover_identity(void *data); +extern void max77775_set_dp_configure(void *data, uint8_t w_data); +extern void max77775_set_dex_enter_mode(void *data); +extern int max77775_sec_uvdm_in_request_message(void *data); +extern int max77775_sec_uvdm_out_request_message(void *data, int size); +extern int max77775_sec_uvdm_ready(void); +extern void max77775_sec_uvdm_close(void); +#endif +#endif diff --git a/include/linux/usb/typec/maxim/max77775_cc.h b/include/linux/usb/typec/maxim/max77775_cc.h new file mode 100755 index 000000000000..56b235bf0838 --- /dev/null +++ b/include/linux/usb/typec/maxim/max77775_cc.h @@ -0,0 +1,70 @@ +/* + * Copyrights (C) 2017 Samsung Electronics, Inc. + * Copyrights (C) 2017 Maxim Integrated Products, 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. + */ + +#ifndef __LINUX_MFD_MAX77775_CC_H +#define __LINUX_MFD_MAX77775_CC_H + +#include +#define MAX77775_CC_NAME "max77775_CC" +#define MAX77865_IRQSRC_CHG (1 << 0) +#define MAX77865_IRQSRC_FG (1 << 2) + +struct max77775_cc_data { + + /* interrupt pin */ + int irq_vconncop; + int irq_vsafe0v; + int irq_detabrt; + int irq_vconnsc; + int irq_ccpinstat; + int irq_ccistat; + int irq_ccvcnstat; + int irq_ccstat; + + u8 usbc_status1; + u8 usbc_status2; + u8 bc_status; + u8 cc_status1; + u8 cc_status2; + u8 pd_status1; + u8 pd_status2; + + u8 opcode_res; + + /* VCONN Over Current Detection */ + u8 vconnocp; + /* VCONN Over Short Circuit Detection */ + u8 vconnsc; + /* Status of VBUS Detection */ + u8 vsafe0v; + /* Charger Detection Abort Status */ + u8 detabrt; + /* Output of active CC pin */ + u8 ccpinstat; + /* CC Pin Detected Allowed VBUS Current in UFP mode */ + u8 ccistat; + /* Status of Vconn Output */ + u8 ccvcnstat; + /* CC Pin State Machine Detection */ + u8 ccstat; + + enum max77775_vcon_role current_vcon; + enum max77775_vcon_role previous_vcon; + enum max77775_power_role current_pr; + enum max77775_power_role previous_pr; + + struct wakeup_source ccstat_ws; +}; +#endif diff --git a/include/linux/usb/typec/maxim/max77775_kunit.h b/include/linux/usb/typec/maxim/max77775_kunit.h new file mode 100755 index 000000000000..f9d5b1acfedd --- /dev/null +++ b/include/linux/usb/typec/maxim/max77775_kunit.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __MAX77775_KUNIT_H__ +#define __MAX77775_KUNIT_H__ + +#if IS_ENABLED(CONFIG_SEC_KUNIT) +#include +#endif + +#ifndef __mockable +#define __mockable +#endif + +#ifndef __visible_for_testing +#define __visible_for_testing static +#endif + +#ifndef EXPORT_SYMBOL_KUNIT +#define EXPORT_SYMBOL_KUNIT(sym) /* nothing */ +#endif + +#endif /* __MAX77775_KUNIT_H__ */ + diff --git a/include/linux/usb/typec/maxim/max77775_pd.h b/include/linux/usb/typec/maxim/max77775_pd.h new file mode 100755 index 000000000000..7c8b82e89ab3 --- /dev/null +++ b/include/linux/usb/typec/maxim/max77775_pd.h @@ -0,0 +1,140 @@ +/* + * Copyrights (C) 2017 Samsung Electronics, Inc. + * Copyrights (C) 2017 Maxim Integrated Products, 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. + */ + +#ifndef __LINUX_MFD_MAX77775_PD_H +#define __LINUX_MFD_MAX77775_PD_H +#include "max77775.h" +#include + +#define MAX77775_PD_NAME "MAX77775_PD" + +enum { + CC_SNK = 0, + CC_SRC, + CC_NO_CONN, +}; + +enum { + D2D_NONE = 0, + D2D_SNKONLY, + D2D_SRCSNK, +}; + +typedef enum { + PDO_TYPE_FIXED = 0, + PDO_TYPE_BATTERY, + PDO_TYPE_VARIABLE, + PDO_TYPE_APDO +} pdo_supply_type_t; + +typedef union sec_pdo_object { + uint32_t data; + struct { + uint8_t bdata[4]; + } BYTES; + struct { + uint32_t reserved:30, + type:2; + } BITS_supply; + struct { + uint32_t max_current:10, /* 10mA units */ + voltage:10, /* 50mV units */ + peak_current:2, + reserved:2, + unchuncked_extended_messages_supported:1, + data_role_data:1, + usb_communications_capable:1, + unconstrained_power:1, + usb_suspend_supported:1, + dual_role_power:1, + supply:2; /* Fixed supply : 00b */ + } BITS_pdo_fixed; + struct { + uint32_t max_current:10, /* 10mA units */ + min_voltage:10, /* 50mV units */ + max_voltage:10, /* 50mV units */ + supply:2; /* Variable Supply (non-Battery) : 10b */ + } BITS_pdo_variable; + struct { + uint32_t max_allowable_power:10, /* 250mW units */ + min_voltage:10, /* 50mV units */ + max_voltage:10, /* 50mV units */ + supply:2; /* Battery : 01b */ + } BITS_pdo_battery; + struct { + uint32_t max_current:7, /* 50mA units */ + reserved1:1, + min_voltage:8, /* 100mV units */ + reserved2:1, + max_voltage:8, /* 100mV units */ + reserved3:2, + pps_power_limited:1, + pps_supply:2, + supply:2; /* APDO : 11b */ + } BITS_pdo_programmable; +} U_SEC_PDO_OBJECT; + +struct max77775_pd_data { + /* interrupt pin */ + int irq_pdmsg; + int irq_psrdy; + int irq_datarole; + int irq_ssacc; + int irq_fct_id; + + u8 usbc_status1; + u8 usbc_status2; + u8 bc_status; + u8 cc_status1; + u8 cc_status2; + u8 pd_status1; + u8 pd_status2; + + u8 opcode_res; + + /* PD Message */ + u8 pdsmg; + + /* Data Role */ + enum max77775_data_role current_dr; + enum max77775_data_role previous_dr; + /* SSacc */ + u8 ssacc; + /* FCT cable */ + u8 fct_id; + enum max77775_ccpd_device device; + + struct pdic_notifier_struct pd_noti; + bool pdo_list; + bool psrdy_received; + bool cc_sbu_short; + bool bPPS_on; + bool sent_chg_info; + + struct workqueue_struct *wqueue; + struct delayed_work retry_work; + struct delayed_work d2d_work; + struct delayed_work abnormal_pdo_work; + + int cc_status; + + int src_cap_done; + int auth_type; + int d2d_type; + int req_pdo_type; + bool psrdy_sent; +}; + +#endif diff --git a/include/linux/usb/typec/maxim/max77775_usbc.h b/include/linux/usb/typec/maxim/max77775_usbc.h new file mode 100755 index 000000000000..a005926b1ec9 --- /dev/null +++ b/include/linux/usb/typec/maxim/max77775_usbc.h @@ -0,0 +1,385 @@ +/* + * Copyrights (C) 2017 Samsung Electronics, Inc. + * Copyrights (C) 2017 Maxim Integrated Products, 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. + */ + +#ifndef __LINUX_MFD_MAX77775_UIC_H +#define __LINUX_MFD_MAX77775_UIC_H +#include +#include +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) +#include +#endif +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) +#include +#endif +#include + +#include "max77775_pd.h" +#include "max77775_cc.h" + +#define MAX77775_SYS_FW_UPDATE +#define MAX77775_MAX_APDCMD_TIME (7*HZ) + +struct max77775_opcode { + unsigned char opcode; + unsigned char data[OPCODE_DATA_LENGTH]; + int read_length; + int write_length; +}; + +typedef struct max77775_usbc_command_data { + u8 opcode; + u8 prev_opcode; + u8 response; + u8 read_data[OPCODE_DATA_LENGTH]; + u8 write_data[OPCODE_DATA_LENGTH]; + int read_length; + int write_length; + u8 reg; + u8 val; + u8 mask; + u8 seq; + int noti_cmd; + u8 is_uvdm; +} usbc_cmd_data; + +typedef struct max77775_usbc_command_node { + usbc_cmd_data cmd_data; + struct max77775_usbc_command_node *next; +} usbc_cmd_node; + +typedef struct max77775_usbc_command_node *usbc_cmd_node_p; + +typedef struct max77775_usbc_command_queue { + struct mutex command_mutex; + usbc_cmd_node *front; + usbc_cmd_node *rear; + usbc_cmd_node tmp_cmd_node; +} usbc_cmd_queue_t; + +#if defined(CONFIG_SEC_FACTORY) +#define FAC_ABNORMAL_REPEAT_STATE 12 +#define FAC_ABNORMAL_REPEAT_RID 5 +#define FAC_ABNORMAL_REPEAT_RID0 3 +struct AP_REQ_GET_STATUS_Type { + uint32_t FAC_Abnormal_Repeat_State; + uint32_t FAC_Abnormal_Repeat_RID; + uint32_t FAC_Abnormal_RID0; +}; +#endif + +#define NAME_LEN_HMD 14 +#define MAX_NUM_HMD 32 +#define TAG_HMD "HMD" +#define MAX_NVCN_CNT 30 /* No vbus & connection */ +#define MAX_CHK_TIME 30 + +struct max77775_hmd_power_dev { + uint vid; + uint pid; + char hmd_name[NAME_LEN_HMD]; +}; + +struct max77775_usb_mock { + int (*opcode_write)(void *usbc_data, + usbc_cmd_data *write_op); + int (*opcode_read)(void *usbc_data, + usbc_cmd_data *read_op); + int (*opcode_push)(void *usbc_data, + usbc_cmd_data *read_op); + int (*opcode_update)(void *usbc_data, + usbc_cmd_data *update_op); + int (*opcode_rw)(void *usbc_data, + usbc_cmd_data *opcode_r, usbc_cmd_data *opcode_w); + void (*ccic_event_work)(void *data, int dest, + int id, int attach, int event, int sub); + int (*check_accessory)(void *data); +}; + +struct max77775_usbc_platform_data { + struct max77775_dev *max77775; + struct device *dev; + struct i2c_client *i2c; /*0xCC */ + struct i2c_client *muic; /*0x4A */ + struct i2c_client *charger; /*0x2A; Charger */ + + int irq_base; + + /* interrupt pin */ + int irq_apcmd; + int irq_sysmsg; + + /* VDM pin */ + int irq_vdm0; + int irq_vdm1; + int irq_vdm2; + int irq_vdm3; + int irq_vdm4; + int irq_vdm5; + int irq_vdm6; + int irq_vdm7; + + int irq_vir0; + + /* USBID pin */ + int irq_usbid; + +#if defined(CONFIG_MAX77775_CCOPEN_AFTER_WATERCABLE) + /* TA Connect pin */ + int irq_taconn; + u8 ta_conn_status; + struct delayed_work set_ccopen_for_watercable_work; +#endif + + /* register information */ + u8 usbc_status1; + u8 usbc_status2; + u8 bc_status; + u8 cc_status1; + u8 cc_status2; + u8 pd_status1; + u8 pd_status2; + + /* opcode register information */ + u8 op_ctrl1_w; + + int watchdog_count; + int por_count; + int opcode_fail_count; + int stuck_suppose; + + u8 opcode_res; + /* USBC System message interrupt */ + u8 sysmsg; + u8 pd_msg; + + /* F/W state */ + u8 HW_Revision; + u8 FW_Revision; + u8 FW_Minor_Revision; + u8 plug_attach_done; + int op_code_done; + enum max77775_connstat prev_connstat; + enum max77775_connstat current_connstat; + + /* F/W opcode Thread */ + + struct work_struct op_wait_work; + struct work_struct op_send_work; + struct work_struct cc_open_req_work; + struct work_struct dp_configure_work; +#ifdef MAX77775_SYS_FW_UPDATE + struct work_struct fw_update_work; +#endif + struct workqueue_struct *op_wait_queue; + struct workqueue_struct *op_send_queue; + struct completion op_completion; + int op_code; + int is_first_booting; + usbc_cmd_data last_opcode; + unsigned long opcode_stamp; + struct mutex op_lock; + + /* F/W opcode command data */ + usbc_cmd_queue_t usbc_cmd_queue; + + uint32_t alternate_state; + uint32_t acc_type; + uint32_t Vendor_ID; + uint32_t Product_ID; + uint32_t Device_Version; + uint32_t SVID_0; + uint32_t SVID_1; + uint32_t SVID_DP; + struct delayed_work acc_detach_work; + uint32_t dp_is_connect; + uint32_t dp_hs_connect; + uint32_t dp_selected_pin; + u8 pin_assignment; + uint32_t is_sent_pin_configuration; + wait_queue_head_t host_turn_on_wait_q; + wait_queue_head_t device_add_wait_q; + int host_turn_on_event; + int host_turn_on_wait_time; + int device_add; + int is_samsung_accessory_enter_mode; + int send_enter_mode_req; + u8 sbu[2]; + u8 cc[2]; + struct completion ccic_sysfs_completion; + struct completion psrdy_wait; + struct max77775_muic_data *muic_data; + struct max77775_pd_data *pd_data; + struct max77775_cc_data *cc_data; + + struct max77775_platform_data *max77775_data; + +#if IS_ENABLED(CONFIG_PDIC_NOTIFIER) + ppdic_data_t ppdic_data; + struct workqueue_struct *ccic_wq; + int manual_lpm_mode; + int fac_water_enable; + int cur_rid; + int pd_state; + u8 vconn_test; + u8 vconn_en; + u8 fw_update; + int is_host; + int is_client; + bool auto_vbus_en; + u8 cc_pin_status; + int ccrp_state; + int vsafe0v_status; +#endif + struct typec_port *port; + struct typec_partner *partner; + struct usb_pd_identity partner_identity; + struct typec_capability typec_cap; + struct completion typec_reverse_completion; + int typec_power_role; + int typec_data_role; + int typec_try_state_change; + int pwr_opmode; + bool pd_support; + struct delayed_work usb_external_notifier_register_work; + struct notifier_block usb_external_notifier_nb; + int mpsm_mode; + bool mdm_block; + int vbus_enable; + int pd_pr_swap; + int shut_down; + struct delayed_work vbus_hard_reset_work; + uint8_t ReadMSG[32]; + int ram_test_enable; + int ram_test_retry; + int ram_test_result; + struct completion uvdm_longpacket_out_wait; + struct completion uvdm_longpacket_in_wait; + int is_in_first_sec_uvdm_req; + int is_in_sec_uvdm_out; + bool pn_flag; + int uvdm_error; + +#if defined(CONFIG_SEC_FACTORY) + struct AP_REQ_GET_STATUS_Type factory_mode; + struct delayed_work factory_state_work; + struct delayed_work factory_rid_work; +#endif + int detach_done_wait; + int set_altmode; + int set_altmode_error; + +#if IS_ENABLED(CONFIG_IF_CB_MANAGER) + struct usbpd_dev *usbpd_d; + struct if_cb_manager *man; + int sbu_switch_status; +#endif + u8 ccctrl4_reg; + int cc_open_req; + + bool recover_opcode_list[OPCODE_NONE]; + int need_recover; + bool srcccap_request_retry; + + int ovp_gpio; + struct mutex hmd_power_lock; + struct max77775_hmd_power_dev *hmd_list; +#if defined(CONFIG_SUPPORT_SHIP_MODE) + int ship_mode_en; + u8 ship_mode_data; +#endif + + bool rid_check; + int lapse_idx; + u64 time_lapse[MAX_NVCN_CNT]; + + int wait_entermode; + struct max77775_usb_mock usb_mock; +}; + +/* Function Status from s2mm005 definition */ +typedef enum { + max77775_State_PE_Initial_detach = 0, + max77775_State_PE_SRC_Send_Capabilities = 3, + max77775_State_PE_SNK_Wait_for_Capabilities = 17, +} max77775_pd_state_t; + +typedef enum { + MPSM_OFF = 0, + MPSM_ON = 1, +} CCIC_DEVICE_MPSM; + +#define DATA_ROLE_SWAP 1 +#define POWER_ROLE_SWAP 2 +#define VCONN_ROLE_SWAP 3 +#define MANUAL_ROLE_SWAP 4 +#define ROLE_ACCEPT 0x1 +#define ROLE_REJECT 0x2 +#define ROLE_BUSY 0x3 + +int max77775_pd_init(struct max77775_usbc_platform_data *usbc_data); +int max77775_cc_init(struct max77775_usbc_platform_data *usbc_data); +int max77775_muic_init(struct max77775_usbc_platform_data *usbc_data); +int max77775_i2c_opcode_read(struct max77775_usbc_platform_data *usbc_data, + u8 opcode, u8 length, u8 *values); + +void init_usbc_cmd_data(usbc_cmd_data *cmd_data); +void max77775_usbc_clear_queue(struct max77775_usbc_platform_data *usbc_data); +int max77775_usbc_opcode_rw(struct max77775_usbc_platform_data *usbc_data, + usbc_cmd_data *opcode_r, usbc_cmd_data *opcode_w); +int max77775_usbc_opcode_write(struct max77775_usbc_platform_data *usbc_data, + usbc_cmd_data *write_op); +int max77775_usbc_opcode_read(struct max77775_usbc_platform_data *usbc_data, + usbc_cmd_data *read_op); +int max77775_usbc_opcode_push(struct max77775_usbc_platform_data *usbc_data, + usbc_cmd_data *read_op); +int max77775_usbc_opcode_update(struct max77775_usbc_platform_data *usbc_data, + usbc_cmd_data *read_op); + +void max77775_ccic_event_work(void *data, int dest, int id, + int attach, int event, int sub); +void max77775_notify_dr_status(struct max77775_usbc_platform_data *usbpd_data, + uint8_t attach); +void max77775_pdo_list(struct max77775_usbc_platform_data *usbc_data, + unsigned char *data); +void max77775_response_pdo_request(struct max77775_usbc_platform_data *usbc_data, + unsigned char *data); +void max77775_response_apdo_request(struct max77775_usbc_platform_data *usbc_data, + unsigned char *data); +void max77775_response_set_pps(struct max77775_usbc_platform_data *usbc_data, + unsigned char *data); +void max77775_send_new_src_cap_push(struct max77775_usbc_platform_data *pusbpd, int auth, int d2d_type); +void max77775_response_req_pdo(struct max77775_usbc_platform_data *usbc_data, + unsigned char *data); +void max77775_current_pdo(struct max77775_usbc_platform_data *usbc_data, + unsigned char *data); +void max77775_check_pdo(struct max77775_usbc_platform_data *usbc_data); +void max77775_detach_pd(struct max77775_usbc_platform_data *usbc_data); +void max77775_notify_rp_current_level(struct max77775_usbc_platform_data *usbc_data); +extern void max77775_set_jig_on(struct max77775_usbc_platform_data *usbpd_data, int mode); +extern void max77775_vbus_turn_on_ctrl(struct max77775_usbc_platform_data *usbc_data, bool enable, bool swaped); +extern void max77775_dp_detach(void *data); +void max77775_usbc_disable_auto_vbus(struct max77775_usbc_platform_data *usbc_data); +extern void max77775_pdic_manual_ccopen_request(int is_on); +int max77775_get_pd_support(struct max77775_usbc_platform_data *usbc_data); +bool max77775_sec_pps_control(int en); +bool max77775_check_hmd_dev(struct max77775_usbc_platform_data *usbpd_data); + +#if defined(CONFIG_SEC_FACTORY) +void factory_execute_monitor(int); +#endif +bool max77775_need_check_stuck(struct max77775_usbc_platform_data *usbc_data); +void max77775_send_check_stuck_opcode(struct max77775_usbc_platform_data *usbpd_data); +bool is_empty_usbc_cmd_queue(usbc_cmd_queue_t *usbc_cmd_queue); +#endif diff --git a/include/linux/usb_hw_param.h b/include/linux/usb_hw_param.h new file mode 100644 index 000000000000..db5ed6496ee0 --- /dev/null +++ b/include/linux/usb_hw_param.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2017-2023 Samsung Electronics Co. Ltd. + * + * 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. + */ + +/* usb hw param */ +/* usb notify layer v4.0 */ + +#define MAX_HWPARAM_STR_LEN 1024 +#define MAX_HWPARAM_STRING 10 +#define HWPARAM_DATA_LIMIT 100000 + +enum usb_hw_param { + USB_CCIC_WATER_INT_COUNT, + USB_CCIC_DRY_INT_COUNT, + USB_CCIC_I2C_ERROR_COUNT, + USB_CCIC_OVC_COUNT, + USB_CCIC_OTG_USE_COUNT, + USB_CCIC_DP_USE_COUNT, + USB_CCIC_VR_USE_COUNT, + USB_HOST_SUPER_SPEED_COUNT, + USB_HOST_HIGH_SPEED_COUNT, + USB_HOST_FULL_SPEED_COUNT, + USB_HOST_LOW_SPEED_COUNT, + USB_CLIENT_SUPER_SPEED_COUNT, + USB_CLIENT_HIGH_SPEED_COUNT, + USB_HOST_CLASS_AUDIO_COUNT, + USB_HOST_CLASS_AUDIO_SAMSUNG_COUNT, + USB_HOST_REVERSE_BYPASS_COUNT, + USB_HOST_CLASS_COMM_COUNT, + USB_HOST_CLASS_HID_COUNT, + USB_HOST_CLASS_PHYSICAL_COUNT, + USB_HOST_CLASS_IMAGE_COUNT, + USB_HOST_CLASS_PRINTER_COUNT, + USB_HOST_CLASS_STORAGE_COUNT, + USB_HOST_STORAGE_SUPER_COUNT, + USB_HOST_STORAGE_HIGH_COUNT, + USB_HOST_STORAGE_FULL_COUNT, + USB_HOST_CLASS_HUB_COUNT, + USB_HOST_CLASS_CDC_COUNT, + USB_HOST_CLASS_CSCID_COUNT, + USB_HOST_CLASS_CONTENT_COUNT, + USB_HOST_CLASS_VIDEO_COUNT, + USB_HOST_CLASS_WIRELESS_COUNT, + USB_HOST_CLASS_MISC_COUNT, + USB_HOST_CLASS_APP_COUNT, + USB_HOST_CLASS_VENDOR_COUNT, + USB_CCIC_DEX_USE_COUNT, + USB_CCIC_WATER_TIME_DURATION, + USB_CCIC_WATER_VBUS_COUNT, + USB_CCIC_WATER_VBUS_TIME_DURATION, + USB_CCIC_WATER_LPM_VBUS_COUNT, + USB_CCIC_WATER_LPM_VBUS_TIME_DURATION, + USB_CCIC_VBUS_CC_SHORT_COUNT, + USB_CCIC_VBUS_SBU_SHORT_COUNT, + USB_CCIC_GND_SBU_SHORT_COUNT, + USB_MUIC_AFC_NACK_COUNT, + USB_MUIC_AFC_ERROR_COUNT, + USB_MUIC_DCD_TIMEOUT_COUNT, + USB_HALL_FOLDING_COUNT, + USB_CCIC_USB_KILLER_COUNT, + USB_CCIC_FWUP_ERROR_COUNT, + USB_MUIC_BC12_RETRY_SUCCESS_COUNT, + USB_CCIC_PR_SWAP_COUNT, + USB_CCIC_DR_SWAP_COUNT, + USB_CLIENT_ANDROID_AUTO_RESET_POPUP_COUNT, + USB_CCIC_UNMASK_VBUS_COUNT, + USB_CCIC_STUCK_COUNT, + USB_HOST_SB_COUNT, + USB_HOST_OVER_AUDIO_DESCRIPTOR_COUNT, + USB_CCIC_VERSION, + USB_CCIC_HW_PARAM_MAX, +}; diff --git a/include/linux/usb_notify.h b/include/linux/usb_notify.h new file mode 100644 index 000000000000..4ff74c68172b --- /dev/null +++ b/include/linux/usb_notify.h @@ -0,0 +1,394 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * usb notify header + * + * Copyright (C) 2011-2023 Samsung, Inc. + * Author: Dongrak Shin + * + */ + + /* usb notify layer v4.0 */ + +#ifndef __LINUX_USB_NOTIFY_H__ +#define __LINUX_USB_NOTIFY_H__ + +#include +#include +#include +#include +#if defined(CONFIG_USB_HW_PARAM) +#include +#endif +#include + +enum otg_notify_events { + NOTIFY_EVENT_NONE, + NOTIFY_EVENT_VBUS, + NOTIFY_EVENT_HOST, + NOTIFY_EVENT_CHARGER, + NOTIFY_EVENT_SMARTDOCK_TA, + NOTIFY_EVENT_SMARTDOCK_USB, + NOTIFY_EVENT_AUDIODOCK, + NOTIFY_EVENT_LANHUB, + NOTIFY_EVENT_LANHUB_TA, + NOTIFY_EVENT_MMDOCK, + NOTIFY_EVENT_HMT, + NOTIFY_EVENT_GAMEPAD, + NOTIFY_EVENT_POGO, + NOTIFY_EVENT_HOST_RELOAD, + NOTIFY_EVENT_DRIVE_VBUS, + NOTIFY_EVENT_ALL_DISABLE, + NOTIFY_EVENT_HOST_DISABLE, + NOTIFY_EVENT_CLIENT_DISABLE, + NOTIFY_EVENT_MDM_ON_OFF, + NOTIFY_EVENT_MDM_ON_OFF_FOR_ID, + NOTIFY_EVENT_MDM_ON_OFF_FOR_SERIAL, + NOTIFY_EVENT_OVERCURRENT, + NOTIFY_EVENT_SMSC_OVC, + NOTIFY_EVENT_SMTD_EXT_CURRENT, + NOTIFY_EVENT_MMD_EXT_CURRENT, + NOTIFY_EVENT_HMD_EXT_CURRENT, + NOTIFY_EVENT_DEVICE_CONNECT, + NOTIFY_EVENT_GAMEPAD_CONNECT, + NOTIFY_EVENT_LANHUB_CONNECT, + NOTIFY_EVENT_POWER_SOURCE, + NOTIFY_EVENT_PD_CONTRACT, + NOTIFY_EVENT_VBUS_RESET, + NOTIFY_EVENT_RESERVE_BOOSTER, + NOTIFY_EVENT_USB_CABLE, + NOTIFY_EVENT_USBD_SUSPENDED, + NOTIFY_EVENT_USBD_UNCONFIGURED, + NOTIFY_EVENT_USBD_CONFIGURED, + NOTIFY_EVENT_VBUSPOWER, + NOTIFY_EVENT_DR_SWAP, + NOTIFY_EVENT_REVERSE_BYPASS_DEVICE_CONNECT, + NOTIFY_EVENT_REVERSE_BYPASS_DEVICE_ATTACH, + NOTIFY_EVENT_VIRTUAL, +}; + +#define VIRT_EVENT(a) (a+NOTIFY_EVENT_VIRTUAL) +#define PHY_EVENT(a) (a%NOTIFY_EVENT_VIRTUAL) +#define IS_VIRTUAL(a) (a >= NOTIFY_EVENT_VIRTUAL ? 1 : 0) + +enum otg_notify_event_status { + NOTIFY_EVENT_DISABLED, + NOTIFY_EVENT_DISABLING, + NOTIFY_EVENT_ENABLED, + NOTIFY_EVENT_ENABLING, + NOTIFY_EVENT_BLOCKED, + NOTIFY_EVENT_BLOCKING, +}; + +enum otg_notify_evt_type { + NOTIFY_EVENT_EXTRA = (1 << 0), + NOTIFY_EVENT_STATE = (1 << 1), + NOTIFY_EVENT_DELAY = (1 << 2), + NOTIFY_EVENT_NEED_VBUSDRIVE = (1 << 3), + NOTIFY_EVENT_NOBLOCKING = (1 << 4), + NOTIFY_EVENT_NOSAVE = (1 << 5), + NOTIFY_EVENT_NEED_HOST = (1 << 6), + NOTIFY_EVENT_NEED_CLIENT = (1 << 7), +}; + +enum otg_notify_block_type { + NOTIFY_BLOCK_TYPE_NONE = 0, + NOTIFY_BLOCK_TYPE_HOST = (1 << 0), + NOTIFY_BLOCK_TYPE_CLIENT = (1 << 1), + NOTIFY_BLOCK_TYPE_ALL = (1 << 0 | 1 << 1), +}; + +enum otg_notify_mdm_type { + NOTIFY_MDM_TYPE_OFF, + NOTIFY_MDM_TYPE_ON, +}; + +enum otg_notify_gpio { + NOTIFY_VBUS, + NOTIFY_REDRIVER, +}; + +enum otg_op_pos { + NOTIFY_OP_OFF, + NOTIFY_OP_POST, + NOTIFY_OP_PRE, +}; + +enum ovc_check_value { + HNOTIFY_LOW, + HNOTIFY_HIGH, + HNOTIFY_INITIAL, +}; + +enum otg_notify_power_role { + HNOTIFY_SINK, + HNOTIFY_SOURCE, +}; + +enum otg_notify_data_role { + HNOTIFY_UFP, + HNOTIFY_DFP, +}; + +enum usb_restrict_type { + USB_SECURE_RESTRICTED, + USB_TIME_SECURE_RESTRICTED, + USB_SECURE_RELEASE, +}; + +enum usb_restrict_group { + USB_GROUP_AUDIO, + USB_GROUP_OTEHR, + USB_GROUP_MAX, +}; + +enum usb_certi_type { + USB_CERTI_UNSUPPORT_ACCESSORY, + USB_CERTI_NO_RESPONSE, + USB_CERTI_HUB_DEPTH_EXCEED, + USB_CERTI_HUB_POWER_EXCEED, + USB_CERTI_HOST_RESOURCE_EXCEED, + USB_CERTI_WARM_RESET, +}; + +enum usb_err_type { + USB_ERR_ABNORMAL_RESET, +}; + +enum usb_itracker_type { + NOTIFY_USB_CC_REPEAT, +}; + +enum usb_current_state { + NOTIFY_USB_UNCONFIGURED, + NOTIFY_USB_SUSPENDED, + NOTIFY_USB_CONFIGURED, +}; + +enum usb_allowlist_state { + NOTIFY_MDM_NONE = 0, + NOTIFY_MDM_SERIAL, + NOTIFY_MDM_ID, + NOTIFY_MDM_ID_AND_SERIAL, +}; + +enum usb_request_action_type { + USB_REQUEST_NOTHING, + USB_REQUEST_DUMPSTATE, +}; + +enum otg_notify_reverse_bypass_status { + NOTIFY_EVENT_REVERSE_BYPASS_OFF, + NOTIFY_EVENT_REVERSE_BYPASS_PREPARE, + NOTIFY_EVENT_REVERSE_BYPASS_ON, +}; + +enum otg_notify_illegal_type { + NOTIFY_EVENT_AUDIO_DESCRIPTOR, + NOTIFY_EVENT_SECURE_DISCONNECTION, +}; + +enum usb_lock_state { + USB_NOTIFY_UNLOCK = 0, + USB_NOTIFY_LOCK_USB_WORK, + USB_NOTIFY_LOCK_USB_RESTRICT, + USB_NOTIFY_INIT_STATE, +}; + +struct otg_notify { + int vbus_detect_gpio; + int redriver_en_gpio; + int is_wakelock; + int is_host_wakelock; /*unused field*/ + int unsupport_host; + int smsc_ovc_poll_sec; + int auto_drive_vbus; + int booting_delay_sec; + int disable_control; + int device_check_sec; + int pre_peri_delay_us; + int booting_delay_sync_usb; + int (*pre_gpio)(int gpio, int use); + int (*post_gpio)(int gpio, int use); + int (*vbus_drive)(bool enable); + int (*reverse_bypass_drive)(int mode); + int (*get_support_reverse_bypass_en)(void *data); + int (*set_host)(bool enable); + int (*set_peripheral)(bool enable); + int (*set_charger)(bool enable); + int (*post_vbus_detect)(bool on); + int (*set_lanhubta)(int enable); + int (*set_battcall)(int event, int enable); + int (*set_chg_current)(int state); + void (*set_ldo_onoff)(void *data, unsigned int onoff); + int (*get_gadget_speed)(void); + int (*is_skip_list)(int index); + int (*usb_maximum_speed)(int speed); + void *o_data; + void *u_notify; +}; + +struct otg_booster { + char *name; + int (*booster)(bool enable); +}; + +#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) +extern const char *event_string(enum otg_notify_events event); +extern const char *status_string(enum otg_notify_event_status status); +extern void send_usb_mdm_uevent(void); +extern void send_usb_certi_uevent(int usb_certi); +extern void send_usb_err_uevent(int usb_certi, int mode); +extern void send_usb_itracker_uevent(int err_type); +extern int usb_check_whitelist_for_id(struct usb_device *dev); +extern int usb_check_whitelist_for_serial(struct usb_device *dev); +extern int usb_check_whitelist_for_mdm(struct usb_device *dev); +extern int usb_check_whitelist_enable_state(void); +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION +extern int usb_check_allowlist_for_lockscreen_enabled_id(struct usb_device *dev); +#endif +extern int usb_otg_restart_accessory(struct usb_device *dev); +extern void send_otg_notify(struct otg_notify *n, + unsigned long event, int enable); +extern int get_typec_status(struct otg_notify *n, int event); +extern struct otg_booster *find_get_booster(struct otg_notify *n); +extern int register_booster(struct otg_notify *n, struct otg_booster *b); +extern int register_ovc_func(struct otg_notify *n, + int (*check_state)(void *), void *data); +extern int get_booster(struct otg_notify *n); +extern int get_usb_mode(struct otg_notify *n); +extern unsigned long get_cable_type(struct otg_notify *n); +extern int is_usb_host(struct otg_notify *n); +extern bool is_blocked(struct otg_notify *n, int type); +extern bool is_snkdfp_usb_device_connected(struct otg_notify *n); +extern int get_con_dev_max_speed(struct otg_notify *n); +extern void set_con_dev_max_speed + (struct otg_notify *n, int speed); +extern void set_con_dev_hub(struct otg_notify *n, int speed, int conn); +extern void set_request_action(struct otg_notify *n, unsigned int request_action); +extern int is_known_usbaudio(struct usb_device *dev); +extern void set_usb_audio_cardnum(int card_num, int bundle, int attach); +extern void send_usb_audio_uevent(struct usb_device *dev, + int cardnum, int attach); +extern int send_usb_notify_uevent + (struct otg_notify *n, char *envp_ext[]); +extern int check_new_device_added(struct usb_device *udev); +extern int set_lpm_charging_type_done(struct otg_notify *n, + unsigned int state); +extern int detect_illegal_condition(int type); +extern int check_usbaudio(struct usb_device *dev); +extern int check_usbgroup(struct usb_device *dev); +extern int is_usbhub(struct usb_device *dev); +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION +extern int disconnect_unauthorized_device(struct usb_device *dev); +#endif +extern void send_usb_restrict_uevent(int usb_restrict); +#if defined(CONFIG_USB_HW_PARAM) +extern unsigned long long *get_hw_param(struct otg_notify *n, + enum usb_hw_param index); +extern int inc_hw_param(struct otg_notify *n, + enum usb_hw_param index); +extern int inc_hw_param_host(struct host_notify_dev *dev, + enum usb_hw_param index); +extern int register_hw_param_manager(struct otg_notify *n, + unsigned long (*fptr)(int)); +#endif +extern void *get_notify_data(struct otg_notify *n); +extern void set_notify_data(struct otg_notify *n, void *data); +extern struct otg_notify *get_otg_notify(void); +extern void enable_usb_notify(void); +extern int set_otg_notify(struct otg_notify *n); +extern void put_otg_notify(struct otg_notify *n); +#else +static inline const char *event_string(enum otg_notify_events event) + {return NULL; } +static inline const char *status_string(enum otg_notify_event_status status) + {return NULL; } +static inline void send_usb_mdm_uevent(void) {} +static inline void send_usb_certi_uevent(int usb_certi) {} +static inline void send_usb_err_uevent(int usb_certi, int mode) {} +static inline void send_usb_itracker_uevent(int err_type) {} +static inline int usb_check_whitelist_for_mdm(struct usb_device *dev) + {return 0; } +static inline int usb_check_whitelist_for_id(struct usb_device *dev) + {return 0; } +static inline int usb_check_whitelist_for_serial(struct usb_device *dev) + {return 0; } +extern inline int usb_check_whitelist_enable_state(void) + {return 0; } +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION +extern inline int usb_check_allowlist_for_lockscreen_enabled_id(struct usb_device *dev) + {return 0; } +#endif +static inline int usb_otg_restart_accessory(struct usb_device *dev) + {return 0; } +static inline void send_otg_notify(struct otg_notify *n, + unsigned long event, int enable) { } +static inline int get_typec_status(struct otg_notify *n, int event) {return 0; } +static inline struct otg_booster *find_get_booster(struct otg_notify *n) + {return NULL; } +static inline int register_booster(struct otg_notify *n, + struct otg_booster *b) {return 0; } +static inline int register_ovc_func(struct otg_notify *n, + int (*check_state)(void *), void *data) {return 0; } +static inline int get_booster(struct otg_notify *n) {return 0; } +static inline int get_usb_mode(struct otg_notify *n) {return 0; } +static inline unsigned long get_cable_type(struct otg_notify *n) {return 0; } +static inline int is_usb_host(struct otg_notify *n) {return 0; } +static inline bool is_blocked(struct otg_notify *n, int type) {return false; } +static inline bool is_snkdfp_usb_device_connected(struct otg_notify *n) + {return false; } +static inline int get_con_dev_max_speed(struct otg_notify *n) + {return 0; } +static inline void set_con_dev_max_speed + (struct otg_notify *n, int speed) {} +static inline void set_con_dev_hub(struct otg_notify *n, int speed, int conn) {} +static inline void set_request_action + (struct otg_notify *n, unsigned int request_action) {} +static inline int is_known_usbaudio(struct usb_device *dev) {return 0; } +static inline void set_usb_audio_cardnum(int card_num, + int bundle, int attach) {} +static inline void send_usb_audio_uevent(struct usb_device *dev, + int cardnum, int attach) {} +static inline int send_usb_notify_uevent + (struct otg_notify *n, char *envp_ext[]) {return 0; } +static inline int check_new_device_added(struct usb_device *udev) {return 0; } +static inline int set_lpm_charging_type_done(struct otg_notify *n, + unsigned int state) {return 0; } +static inline int detect_illegal_condition(int type) {return 0; } +static inline int check_usbaudio(struct usb_device *dev) {return 0; } +static inline int check_usbgroup(struct usb_device *dev) {return 0; } +static inline int is_usbhub(struct usb_device *dev) {return 0; } +#ifndef CONFIG_DISABLE_LOCKSCREEN_USB_RESTRICTION +static inline int disconnect_unauthorized_device(struct usb_device *dev) {return 0; } +#endif +static inline void send_usb_restrict_uevent(int usb_restrict) {} +#if defined(CONFIG_USB_HW_PARAM) +static inline unsigned long long *get_hw_param(struct otg_notify *n, + enum usb_hw_param index) {return NULL; } +static inline int inc_hw_param(struct otg_notify *n, + enum usb_hw_param index) {return 0; } +static inline int inc_hw_param_host(struct host_notify_dev *dev, + enum usb_hw_param index) {return 0; } +static inline int register_hw_param_manager(struct otg_notify *n, + unsigned long (*fptr)(int)) {return 0; } +#endif +static inline void *get_notify_data(struct otg_notify *n) {return NULL; } +static inline void set_notify_data(struct otg_notify *n, void *data) {} +static inline struct otg_notify *get_otg_notify(void) {return NULL; } +static inline void enable_usb_notify(void) {} +static inline int set_otg_notify(struct otg_notify *n) {return 0; } +static inline void put_otg_notify(struct otg_notify *n) {} +#endif + +#define unl_info(fmt, ...) \ + ({ \ + pr_info(fmt, ##__VA_ARGS__); \ + printk_usb(NOTIFY_PRINTK_USB_NORMAL, fmt, ##__VA_ARGS__); \ + }) +#define unl_err(fmt, ...) \ + ({ \ + pr_err(fmt, ##__VA_ARGS__); \ + printk_usb(NOTIFY_PRINTK_USB_NORMAL, fmt, ##__VA_ARGS__); \ + }) + +#endif /* __LINUX_USB_NOTIFY_H__ */ diff --git a/include/linux/usb_vendor_notify.h b/include/linux/usb_vendor_notify.h new file mode 100644 index 000000000000..c0e31115f9cb --- /dev/null +++ b/include/linux/usb_vendor_notify.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USB_VENDOR_NOTIFY_H +#define __USB_VENDOR_NOTIFY_H + +#include + +int send_usb_vendor_notify_pcm_info(int direction, int enable); +int send_usb_vendor_notify_cardnum(int card_num, int bundle, int attach); +int send_usb_vendor_notify_audio_uevent(struct usb_device *dev, int card_num, + int attach); +int send_usb_vendor_notify_new_device(struct usb_device *dev); + +#endif /* __USB_VENDOR_NOTIFY_H */ diff --git a/include/linux/usb_vendor_notify_defs.h b/include/linux/usb_vendor_notify_defs.h new file mode 100644 index 000000000000..982e37021c89 --- /dev/null +++ b/include/linux/usb_vendor_notify_defs.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USB_VENDOR_NOTIFY_DEFS_H +#define __USB_VENDOR_NOTIFY_DEFS_H + +#include + +enum { + USB_VENDOR_NOTIFY_NONE, + USB_VENDOR_NOTIFY_PCM_INFO, + USB_VENDOR_NOTIFY_CARDNUM, + USB_VENDOR_NOTIFY_AUDIO_UEVENT, + USB_VENDOR_NOTIFY_NEW_DEVICE, +}; + +/* USB_VENDOR_NOTIFY_PCM_INFO */ +struct data_pcm_info { + int direction; + int enable; +}; + +/* USB_VENDOR_NOTIFY_CARDNUM */ +struct data_cardnum { + int card_num; + int bundle; + int attach; +}; + +/* USB_VENDOR_NOTIFY_AUDIO_UEVENT */ +struct data_audio_uevent { + struct usb_device *dev; + int card_num; + int attach; +}; + +/* USB_VENDOR_NOTIFY_NEW_DEVICE */ +struct data_new_device { + struct usb_device *dev; + int ret; +}; + +#endif /* __USB_VENDOR_NOTIFY_DEFS_H */ diff --git a/include/linux/usblog_proc_notify.h b/include/linux/usblog_proc_notify.h new file mode 100644 index 000000000000..2312405eda0c --- /dev/null +++ b/include/linux/usblog_proc_notify.h @@ -0,0 +1,366 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2016-2023 Samsung Electronics Co. Ltd. + * + * 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. + */ + + /* usb notify layer v4.0 */ + +#ifndef __LINUX_USBLOG_PROC_NOTIFY_H__ +#define __LINUX_USBLOG_PROC_NOTIFY_H__ + +#define NOTIFY_FOREACH_STATE(S) \ + S(NOTIFY_UNDEFINED_STATE), \ + S(NOTIFY_INVALID_STATE), \ + S(NOTIFY_TOGGLING), \ + S(NOTIFY_SRC_UNATTACHED), \ + S(NOTIFY_SRC_ATTACH_WAIT), \ + S(NOTIFY_SRC_ATTACHED), \ + S(NOTIFY_SRC_STARTUP), \ + S(NOTIFY_SRC_SEND_CAPABILITIES), \ + S(NOTIFY_SRC_SEND_CAPABILITIES_TIMEOUT), \ + S(NOTIFY_SRC_NEGOTIATE_CAPABILITIES), \ + S(NOTIFY_SRC_TRANSITION_SUPPLY), \ + S(NOTIFY_SRC_READY), \ + S(NOTIFY_SRC_WAIT_NEW_CAPABILITIES), \ + \ + S(NOTIFY_SNK_UNATTACHED), \ + S(NOTIFY_SNK_ATTACH_WAIT), \ + S(NOTIFY_SNK_DEBOUNCED), \ + S(NOTIFY_SNK_ATTACHED), \ + S(NOTIFY_SNK_STARTUP), \ + S(NOTIFY_SNK_DISCOVERY), \ + S(NOTIFY_SNK_DISCOVERY_DEBOUNCE), \ + S(NOTIFY_SNK_DISCOVERY_DEBOUNCE_DONE), \ + S(NOTIFY_SNK_WAIT_CAPABILITIES), \ + S(NOTIFY_SNK_NEGOTIATE_CAPABILITIES), \ + S(NOTIFY_SNK_NEGOTIATE_PPS_CAPABILITIES), \ + S(NOTIFY_SNK_TRANSITION_SINK), \ + S(NOTIFY_SNK_TRANSITION_SINK_VBUS), \ + S(NOTIFY_SNK_READY), \ + \ + S(NOTIFY_ACC_UNATTACHED), \ + S(NOTIFY_DEBUG_ACC_ATTACHED), \ + S(NOTIFY_AUDIO_ACC_ATTACHED), \ + S(NOTIFY_AUDIO_ACC_DEBOUNCE), \ + \ + S(NOTIFY_HARD_RESET_SEND), \ + S(NOTIFY_HARD_RESET_START), \ + S(NOTIFY_SRC_HARD_RESET_VBUS_OFF), \ + S(NOTIFY_SRC_HARD_RESET_VBUS_ON), \ + S(NOTIFY_SNK_HARD_RESET_SINK_OFF), \ + S(NOTIFY_SNK_HARD_RESET_WAIT_VBUS), \ + S(NOTIFY_SNK_HARD_RESET_SINK_ON), \ + \ + S(NOTIFY_SOFT_RESET), \ + S(NOTIFY_SOFT_RESET_SEND), \ + \ + S(NOTIFY_DR_SWAP_ACCEPT), \ + S(NOTIFY_DR_SWAP_SEND), \ + S(NOTIFY_DR_SWAP_SEND_TIMEOUT), \ + S(NOTIFY_DR_SWAP_CANCEL), \ + S(NOTIFY_DR_SWAP_CHANGE_DR), \ + \ + S(NOTIFY_PR_SWAP_ACCEPT), \ + S(NOTIFY_PR_SWAP_SEND), \ + S(NOTIFY_PR_SWAP_SEND_TIMEOUT), \ + S(NOTIFY_PR_SWAP_CANCEL), \ + S(NOTIFY_PR_SWAP_START), \ + S(NOTIFY_PR_SWAP_SRC_SNK_TRANSITION_OFF), \ + S(NOTIFY_PR_SWAP_SRC_SNK_SOURCE_OFF), \ + S(NOTIFY_PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED), \ + S(NOTIFY_PR_SWAP_SRC_SNK_SINK_ON), \ + S(NOTIFY_PR_SWAP_SNK_SRC_SINK_OFF), \ + S(NOTIFY_PR_SWAP_SNK_SRC_SOURCE_ON), \ + S(NOTIFY_PR_SWAP_SNK_SRC_SOURCE_ON_VBUS_RAMPED_UP), \ + \ + S(NOTIFY_VCONN_SWAP_ACCEPT), \ + S(NOTIFY_VCONN_SWAP_SEND), \ + S(NOTIFY_VCONN_SWAP_SEND_TIMEOUT), \ + S(NOTIFY_VCONN_SWAP_CANCEL), \ + S(NOTIFY_VCONN_SWAP_START), \ + S(NOTIFY_VCONN_SWAP_WAIT_FOR_VCONN), \ + S(NOTIFY_VCONN_SWAP_TURN_ON_VCONN), \ + S(NOTIFY_VCONN_SWAP_TURN_OFF_VCONN), \ + \ + S(NOTIFY_SNK_TRY), \ + S(NOTIFY_SNK_TRY_WAIT), \ + S(NOTIFY_SNK_TRY_WAIT_DEBOUNCE), \ + S(NOTIFY_SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS), \ + S(NOTIFY_SRC_TRYWAIT), \ + S(NOTIFY_SRC_TRYWAIT_DEBOUNCE), \ + S(NOTIFY_SRC_TRYWAIT_UNATTACHED), \ + \ + S(NOTIFY_SRC_TRY), \ + S(NOTIFY_SRC_TRY_WAIT), \ + S(NOTIFY_SRC_TRY_DEBOUNCE), \ + S(NOTIFY_SNK_TRYWAIT), \ + S(NOTIFY_SNK_TRYWAIT_DEBOUNCE), \ + S(NOTIFY_SNK_TRYWAIT_VBUS), \ + S(NOTIFY_BIST_RX), \ + \ + S(NOTIFY_GET_STATUS_SEND), \ + S(NOTIFY_GET_STATUS_SEND_TIMEOUT), \ + S(NOTIFY_GET_PPS_STATUS_SEND), \ + S(NOTIFY_GET_PPS_STATUS_SEND_TIMEOUT), \ + \ + S(NOTIFY_ERROR_RECOVERY), \ + S(NOTIFY_PORT_RESET), \ + S(NOTIFY_PORT_RESET_WAIT_OFF), \ + \ + S(NOTIFY_SNK_TRY_WAIT_PE), \ + S(NOTIFY_SRC_TRY_WAIT_PE), \ + S(NOTIFY_DEBUG_ACC_SNK_ATTACHED), \ + S(NOTIFY_CUSTOM_SRC_ATTACHED), \ + S(NOTIFY_NORP_SRC_ATTACHED), \ + S(NOTIFY_ROLE_SWAP), \ + S(NOTIFY_WATER_PROTECTION_WAIT), \ + S(NOTIFY_WATER_PROTECTION), \ + S(NOTIFY_UNATTACH_WAIT_PE) + + +#define NOTIFY_GENERATE_ENUM(e) e +#define NOTIFY_GENERATE_STRING(s) #s + +enum usblog_tcpm_state { + NOTIFY_FOREACH_STATE(NOTIFY_GENERATE_ENUM) +}; + +enum usblog_type { + NOTIFY_FUNCSTATE, + NOTIFY_TCPMSTATE, + NOTIFY_CCSTATE, + NOTIFY_ALTERNATEMODE, + NOTIFY_CCIC_EVENT, + NOTIFY_MANAGER, + NOTIFY_USBMODE, + NOTIFY_USBMODE_EXTRA, + NOTIFY_USBSTATE, + NOTIFY_EVENT, + NOTIFY_PORT_CONNECT, + NOTIFY_PORT_DISCONNECT, + NOTIFY_PORT_CLASS, + NOTIFY_PORT_CLASS_BLOCK, + NOTIFY_PCM_PLAYBACK, + NOTIFY_PCM_CAPTURE, + NOTIFY_EXTRA, +}; + +enum usblog_state { + NOTIFY_CONFIGURED = 1, + NOTIFY_CONNECTED, + NOTIFY_DISCONNECTED, + NOTIFY_RESET, + NOTIFY_RESET_FULL, + NOTIFY_RESET_HIGH, + NOTIFY_RESET_SUPER, + NOTIFY_ACCSTART, + NOTIFY_PULLUP, + NOTIFY_PULLUP_ENABLE, + NOTIFY_PULLUP_EN_SUCCESS, + NOTIFY_PULLUP_EN_FAIL, + NOTIFY_PULLUP_DISABLE, + NOTIFY_PULLUP_DIS_SUCCESS, + NOTIFY_PULLUP_DIS_FAIL, + NOTIFY_VBUS_SESSION, + NOTIFY_VBUS_SESSION_ENABLE, + NOTIFY_VBUS_EN_SUCCESS, + NOTIFY_VBUS_EN_FAIL, + NOTIFY_VBUS_SESSION_DISABLE, + NOTIFY_VBUS_DIS_SUCCESS, + NOTIFY_VBUS_DIS_FAIL, + NOTIFY_HIGH, + NOTIFY_SUPER, + NOTIFY_GET_DES, + NOTIFY_SET_CON, + NOTIFY_CONNDONE_SSP, + NOTIFY_CONNDONE_SS, + NOTIFY_CONNDONE_HS, + NOTIFY_CONNDONE_FS, + NOTIFY_CONNDONE_LS, +}; + +enum usblog_status { + NOTIFY_DETACH = 0, + NOTIFY_ATTACH_DFP, + NOTIFY_ATTACH_UFP, + NOTIFY_ATTACH_DRP, +}; + +/* + * You should refer "linux/usb/typec/common/pdic_notifier.h" + * ccic_device, ccic_id may be different at each branch + */ +enum ccic_device { + NOTIFY_DEV_INITIAL = 0, + NOTIFY_DEV_USB, + NOTIFY_DEV_BATTERY, + NOTIFY_DEV_PDIC, + NOTIFY_DEV_MUIC, + NOTIFY_DEV_CCIC, + NOTIFY_DEV_MANAGER, + NOTIFY_DEV_DP, + NOTIFY_DEV_USB_DP, + NOTIFY_DEV_SUB_BATTERY, + NOTIFY_DEV_SECOND_MUIC, + NOTIFY_DEV_DEDICATED_MUIC, + NOTIFY_DEV_ALL, +}; + +enum ccic_id { + NOTIFY_ID_INITIAL = 0, + NOTIFY_ID_ATTACH, + NOTIFY_ID_RID, + NOTIFY_ID_USB, + NOTIFY_ID_POWER_STATUS, + NOTIFY_ID_WATER, + NOTIFY_ID_VCONN, + NOTIFY_ID_OTG, + NOTIFY_ID_TA, + NOTIFY_ID_DP_CONNECT, + NOTIFY_ID_DP_HPD, + NOTIFY_ID_DP_LINK_CONF, + NOTIFY_ID_USB_DP, + NOTIFY_ID_ROLE_SWAP, + NOTIFY_ID_FAC, + NOTIFY_ID_CC_PIN_STATUS, + NOTIFY_ID_WATER_CABLE, + NOTIFY_ID_POFF_WATER, + NOTIFY_ID_DEVICE_INFO, + NOTIFY_ID_SVID_INFO, + NOTIFY_ID_CLEAR_INFO, +}; + +enum ccic_rid { + NOTIFY_RID_UNDEFINED = 0, +#if defined(CONFIG_USB_CCIC_NOTIFIER_USING_QC) + NOTIFY_RID_GND, + NOTIFY_RID_056K, +#else + NOTIFY_RID_000K, + NOTIFY_RID_001K, +#endif + NOTIFY_RID_255K, + NOTIFY_RID_301K, + NOTIFY_RID_523K, + NOTIFY_RID_619K, + NOTIFY_RID_OPEN, +}; + +enum ccic_con { + NOTIFY_CON_DETACH = 0, + NOTIFY_CON_ATTACH, +}; + +enum ccic_rprd { + NOTIFY_RD = 0, + NOTIFY_RP, +}; + +enum ccic_rpstatus { + NOTIFY_RP_NONE = 0, + NOTIFY_RP_56K, /* 80uA */ + NOTIFY_RP_22K, /* 180uA */ + NOTIFY_RP_10K, /* 330uA */ + NOTIFY_RP_ABNORMAL, +}; + +enum ccic_hpd { + NOTIFY_HPD_LOW = 0, + NOTIFY_HPD_HIGH, + NOTIFY_HPD_IRQ, +}; + +enum ccic_pin_assignment { + NOTIFY_DP_PIN_UNKNOWN = 0, + NOTIFY_DP_PIN_A, + NOTIFY_DP_PIN_B, + NOTIFY_DP_PIN_C, + NOTIFY_DP_PIN_D, + NOTIFY_DP_PIN_E, + NOTIFY_DP_PIN_F, +}; + +enum ccic_pin_status { + NOTIFY_PIN_NOTERMINATION = 0, + NOTIFY_PIN_CC1_ACTIVE, + NOTIFY_PIN_CC2_ACTIVE, + NOTIFY_PIN_AUDIO_ACCESSORY, +}; + +enum ccic_voltage_status { + NOTIFY_CC_VOLT_OPEN = 0, + NOTIFY_CC_VOLT_RA, + NOTIFY_CC_VOLT_RD, + + NOTIFY_CC_VOLT_SNK_DFT, + NOTIFY_CC_VOLT_SNK_1_5, + NOTIFY_CC_VOLT_SNK_3_0, + + NOTIFY_CC_DRP_TOGGLING, + + NOTIFY_CC_UNDEFINED, +}; + +enum extra { + NOTIFY_EXTRA_USBKILLER = 0, + NOTIFY_EXTRA_HARDRESET_SENT, + NOTIFY_EXTRA_HARDRESET_RECEIVED, + NOTIFY_EXTRA_SYSERROR_BOOT_WDT, + NOTIFY_EXTRA_SYSMSG_BOOT_POR, + NOTIFY_EXTRA_SYSMSG_CC_SHORT, + NOTIFY_EXTRA_SYSMSG_SBU_GND_SHORT, + NOTIFY_EXTRA_SYSMSG_SBU_VBUS_SHORT, + NOTIFY_EXTRA_UVDM_TIMEOUT, + NOTIFY_EXTRA_CCOPEN_REQ_SET, + NOTIFY_EXTRA_CCOPEN_REQ_CLEAR, + NOTIFY_EXTRA_USB_ANALOGAUDIO, + NOTIFY_EXTRA_USBHOST_OVERCURRENT, + NOTIFY_EXTRA_ROOTHUB_SUSPEND_FAIL, + NOTIFY_EXTRA_PORT_SUSPEND_FAIL, + NOTIFY_EXTRA_PORT_SUSPEND_WAKEUP_FAIL, + NOTIFY_EXTRA_PORT_SUSPEND_LTM_FAIL, + NOTIFY_EXTRA_VIB_FW_LOAD_SUCCESS, +}; + +enum printk_usb_op { + NOTIFY_PRINTK_USB_NORMAL = 0, + NOTIFY_PRINTK_USB_SNAPSHOT = 1, +}; + +#define ALTERNATE_MODE_NOT_READY (1 << 0) +#define ALTERNATE_MODE_READY (1 << 1) +#define ALTERNATE_MODE_STOP (1 << 2) +#define ALTERNATE_MODE_START (1 << 3) +#define ALTERNATE_MODE_RESET (1 << 4) + +#ifdef CONFIG_USB_NOTIFY_PROC_LOG +extern void store_usblog_notify(int type, void *param1, void *param2); +extern void store_ccic_version(unsigned char *hw, unsigned char *sw_main, + unsigned char *sw_boot); +extern unsigned long long show_ccic_version(void); +extern void store_ccic_bin_version(const unsigned char *sw_main, + const unsigned char *sw_boot); +extern void store_tcpc_name(char *name); +extern void printk_usb(int snapshot, char *fmt, ...); +extern int register_usblog_proc(void); +extern void unregister_usblog_proc(void); +#else +static inline void store_usblog_notify(int type, void *param1, void *param2) {} +static inline void store_ccic_version(unsigned char *hw, unsigned char *sw_main, + unsigned char *sw_boot) {} +static inline unsigned long long show_ccic_version(void) {return 0; } +static inline void store_ccic_bin_version(const unsigned char *sw_main, + const unsigned char *sw_boot) {} +static inline void store_tcpc_name(char *name) {} +static inline void printk_usb(int snapshot, char *fmt, ...) {} +static inline int register_usblog_proc(void) + {return 0; } +static inline void unregister_usblog_proc(void) {} +#endif +#endif + diff --git a/include/linux/vbus_notifier.h b/include/linux/vbus_notifier.h new file mode 100644 index 000000000000..05c6d7a99a35 --- /dev/null +++ b/include/linux/vbus_notifier.h @@ -0,0 +1,77 @@ +/* + * include/linux/vbus_notifier.h + * + * header file supporting VBUS notifier call chain information + * + * Copyright (C) 2010 Samsung Electronics + * + * 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 __VBUS_NOTIFIER_H__ +#define __VBUS_NOTIFIER_H__ + +/* VBUS notifier call chain command */ +typedef enum { + VBUS_NOTIFY_CMD_NONE = 0, + VBUS_NOTIFY_CMD_FALLING, + VBUS_NOTIFY_CMD_RISING, +} vbus_notifier_cmd_t; + +/* VBUS notifier call sequence, + * largest priority number device will be called first. */ +typedef enum { + VBUS_NOTIFY_DEV_USB, + VBUS_NOTIFY_DEV_CHARGER, + VBUS_NOTIFY_DEV_MANAGER, + VBUS_NOTIFY_DEV_MUIC, + VBUS_NOTIFY_DEV_BATTERY, +} vbus_notifier_device_t; + +typedef enum { + STATUS_VBUS_UNKNOWN = 0, + STATUS_VBUS_LOW, + STATUS_VBUS_HIGH, +} vbus_status_t; + +typedef enum { + VBUS_NOTIFIER_NOT_READY = 0, + VBUS_NOTIFIER_NOT_READY_DETECT, + VBUS_NOTIFIER_READY, +} vbus_notifier_stat_t; + +struct vbus_notifier_struct { + vbus_notifier_stat_t status; + vbus_status_t vbus_type; + vbus_notifier_cmd_t cmd; +}; + +#define VBUS_NOTIFIER_BLOCK(name) \ + struct notifier_block (name) + +/* vbus notifier init/notify function + * this function is for JUST VBUS device driver. + * DON'T use function anywhrer else!! + */ +extern void vbus_notifier_handle(vbus_status_t new_dev); + +/* vbus notifier register/unregister API + * for used any where want to receive vbus attached device attach/detach. */ +extern int vbus_notifier_register(struct notifier_block *nb, + notifier_fn_t notifier, vbus_notifier_device_t listener); +extern int vbus_notifier_unregister(struct notifier_block *nb); + +#endif /* __VBUS_NOTIFIER_H__ */ diff --git a/include/linux/vibrator/cs40l26.h b/include/linux/vibrator/cs40l26.h new file mode 100644 index 000000000000..25cc5d7c4068 --- /dev/null +++ b/include/linux/vibrator/cs40l26.h @@ -0,0 +1,1248 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * cs40l26.h -- CS40L26 Boosted Haptic Driver with Integrated DSP and + * Waveform Memory with Advanced Closed Loop Algorithms and LRA protection + * + * Copyright 2022 Cirrus Logic, Inc. + * + * Author: Fred Treven + */ + +#ifndef __CS40L26_H__ +#define __CS40L26_H__ + +#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 +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE +#include +#include +#endif + +#define CS40L26_LASTREG 0x3C7DFE8 + +#define CS40L26_DEVID 0x0 +#define CS40L26_REVID 0x4 +#define CS40L26_TEST_KEY_CTRL 0x40 +#define CS40L26_GLOBAL_ENABLES 0x2014 +#define CS40L26_BLOCK_ENABLES2 0x201C +#define CS40L26_ERROR_RELEASE 0x2034 +#define CS40L26_PWRMGT_CTL 0x2900 +#define CS40L26_PWRMGT_STS 0x290C +#define CS40L26_REFCLK_INPUT 0x2C04 +#define CS40L26_GLOBAL_SAMPLE_RATE 0x2C0C +#define CS40L26_PLL_REFCLK_DETECT_0 0x2C28 +#define CS40L26_VBST_CTL_1 0x3800 +#define CS40L26_VBST_CTL_2 0x3804 +#define CS40L26_BST_IPK_CTL 0x3808 +#define CS40L26_BST_DCM_CTL 0x381C +#define CS40L26_TEST_LBST 0x391C +#define CS40L26_MONITOR_FILT 0x4008 +#define CS40L26_SPKMON_VMON_DEC_OUT_DATA 0x41B4 +#define CS40L26_ENABLES_AND_CODES_DIG 0x4308 +#define CS40L26_ASP_ENABLES1 0x4800 +#define CS40L26_ASP_CONTROL2 0x4808 +#define CS40L26_ASP_FRAME_CONTROL5 0x4820 +#define CS40L26_ASP_DATA_CONTROL5 0x4840 +#define CS40L26_DACPCM1_INPUT 0x4C00 +#define CS40L26_ASPTX1_INPUT 0x4C20 +#define CS40L26_DSP1RX1_INPUT 0x4C40 +#define CS40L26_DSP1RX5_INPUT 0x4C50 +#define CS40L26_NGATE1_INPUT 0x4C60 +#define CS40L26_VPBR_CONFIG 0x6404 +#define CS40L26_VBBR_CONFIG 0x6408 +#define CS40L26_VPBR_STATUS 0x640C +#define CS40L26_VBBR_STATUS 0x6410 +#define CS40L26_NG_CONFIG 0x6808 +#define CS40L26_DIGPWM_CONFIG2 0x7068 +#define CS40L26_TST_DAC_MSM_CONFIG 0x7404 +#define CS40L26_IRQ1_CFG 0x10000 +#define CS40L26_IRQ1_STATUS 0x10004 +#define CS40L26_IRQ1_EINT_1 0x10010 +#define CS40L26_IRQ1_EINT_2 0x10014 +#define CS40L26_IRQ1_STS_1 0x10090 +#define CS40L26_IRQ1_STS_2 0x10094 +#define CS40L26_IRQ1_MASK_1 0x10110 +#define CS40L26_IRQ1_MASK_2 0x10114 +#define CS40L26_MIXER_NGATE_CH1_CFG 0x12004 +#define CS40L26_DSP_MBOX_1 0x13000 +#define CS40L26_DSP_MBOX_2 0x13004 +#define CS40L26_DSP_MBOX_3 0x13008 +#define CS40L26_DSP_MBOX_4 0x1300C +#define CS40L26_DSP_MBOX_5 0x13010 +#define CS40L26_DSP_MBOX_6 0x13014 +#define CS40L26_DSP_MBOX_7 0x13018 +#define CS40L26_DSP_MBOX_8 0x1301C +#define CS40L26_DSP_VIRTUAL1_MBOX_1 0x13020 +#define CS40L26_DSP1_XMEM_PACKED_0 0x2000000 +#define CS40L26_DSP1_XMEM_PACKED_6143 0x2005FFC +#define CS40L26_DSP1_XROM_PACKED_0 0x2006000 +#define CS40L26_DSP1_XROM_PACKED_4604 0x200A7F0 +#define CS40L26_DSP1_XMEM_UNPACKED32_0 0x2400000 +#define CS40L26_DSP1_XROM_UNPACKED32_3070 0x2406FF8 +#define CS40L26_DSP1_XMEM_UNPACKED24_0 0x2800000 +#define CS40L26_DSP1_XMEM_UNPACKED24_8191 0x2807FFC +#define CS40L26_DSP1_XROM_UNPACKED24_0 0x2808000 +#define CS40L26_DSP1_XROM_UNPACKED24_6141 0x280DFF4 +#define CS40L26_DSP1_CCM_CORE_CONTROL 0x2BC1000 +#define CS40L26_DSP1_YMEM_PACKED_0 0x2C00000 +#define CS40L26_DSP1_YMEM_PACKED_1532 0x2C017F0 +#define CS40L26_DSP1_YMEM_UNPACKED32_0 0x3000000 +#define CS40L26_DSP1_YMEM_UNPACKED32_1022 0x3000FF8 +#define CS40L26_DSP1_YMEM_UNPACKED24_0 0x3400000 +#define CS40L26_DSP1_YMEM_UNPACKED24_2045 0x3401FF4 +#define CS40L26_DSP1_PMEM_0 0x3800000 +#define CS40L26_DSP1_PMEM_5114 0x3804FE8 +#define CS40L26_DSP1_PROM_0 0x3C60000 +#define CS40L26_DSP1_PROM_30714 0x3C7DFE8 + +#ifndef CONFIG_CS40L26_SAMSUNG_USE_MAX_DATA_TX_SIZE +/* this is not a CS40L26 restriction and modified by samsung for i3c */ +#define CS40L26_MAX_I2C_READ_SIZE_WORDS 16 +#else +#define CS40L26_MAX_I2C_READ_SIZE_WORDS 32 +#endif + +/* Register default changes */ +#define CS40L26_TST_DAC_MSM_CONFIG_DEFAULT_CHANGE_VALUE_FULL 0x11330000 +#define CS40L26_TST_DAC_MSM_CONFIG_DEFAULT_CHANGE_VALUE_H16 (\ + CS40L26_TST_DAC_MSM_CONFIG_DEFAULT_CHANGE_VALUE_FULL >> 16) +#define CS40L26_SPK_DEFAULT_HIZ_MASK BIT(28) +#define CS40L26_SPK_DEFAULT_HIZ_SHIFT 28 + +/* Device */ +#define CS40L26_DEV_NAME "CS40L26" +#define CS40L26_DEVID_A 0x40A260 +#define CS40L26_DEVID_B 0x40A26B +#define CS40L26_DEVID_L27_A 0x40A270 +#define CS40L26_DEVID_L27_B 0x40A27B +#define CS40L26_DEVID_MASK GENMASK(23, 0) +#define CS40L26_NUM_DEVS 4 + +#define CS40L26_REVID_A1 0xA1 +#define CS40L26_REVID_B0 0xB0 +#define CS40L26_REVID_B1 0xB1 +#define CS40L26_REVID_B2 0xB2 +#define CS40L26_REVID_MASK GENMASK(7, 0) + +#define CS40L26_ID_L26A_A1 ((CS40L26_DEVID_A << 8) | CS40L26_REVID_A1) +#define CS40L26_ID_L26B_A1 ((CS40L26_DEVID_B << 8) | CS40L26_REVID_A1) +#define CS40L26_ID_L27A_A1 ((CS40L26_DEVID_L27_A << 8) | CS40L26_REVID_A1) +#define CS40L26_ID_L27B_A1 ((CS40L26_DEVID_L27_B << 8) | CS40L26_REVID_A1) +#define CS40L26_ID_L26A_B0 ((CS40L26_DEVID_A << 8) | CS40L26_REVID_B0) +#define CS40L26_ID_L26B_B0 ((CS40L26_DEVID_B << 8) | CS40L26_REVID_B0) +#define CS40L26_ID_L27A_B0 ((CS40L26_DEVID_L27_A << 8) | CS40L26_REVID_B0) +#define CS40L26_ID_L27B_B0 ((CS40L26_DEVID_L27_B << 8) | CS40L26_REVID_B0) +#define CS40L26_ID_L27A_B1 ((CS40L26_DEVID_L27_A << 8) | CS40L26_REVID_B1) +#define CS40L26_ID_L27A_B2 ((CS40L26_DEVID_L27_A << 8) | CS40L26_REVID_B2) + +#define CS40L26_GLOBAL_EN_MASK BIT(0) + +#define CS40L26_DSP_CCM_CORE_KILL 0x00000080 +#define CS40L26_DSP_CCM_CORE_RESET 0x00000281 + +#define CS40L26_GLOBAL_FS_MASK GENMASK(4, 0) +#define CS40L26_GLOBAL_FS_48K 0x03 +#define CS40L26_GLOBAL_FS_96K 0x04 + +#define CS40L26_MEM_RDY_MASK BIT(1) +#define CS40L26_MEM_RDY_SHIFT 1 + +#define CS40L26_DSP_HALO_STATE_RUN 2 + +#define CS40L26_NUM_PCT_MAP_VALUES 101 + +#define CS40L26_TEST_KEY_UNLOCK_CODE1 0x00000055 +#define CS40L26_TEST_KEY_UNLOCK_CODE2 0x000000AA +#define CS40L26_TEST_KEY_LOCK_CODE 0x00000000 + +/* DSP State */ +#define CS40L26_DSP_STATE_HIBERNATE 0 +#define CS40L26_DSP_STATE_SHUTDOWN 1 +#define CS40L26_DSP_STATE_STANDBY 2 +#define CS40L26_DSP_STATE_ACTIVE 3 + +#define CS40L26_DSP_STATE_MASK GENMASK(7, 0) + +#define CS40L26_DSP_STATE_ATTEMPTS 5 + +#define CS40L26_DSP_LOCK3_OFFSET 8 +#define CS40L26_DSP_LOCK3_MASK BIT(1) + +#define CS40L26_DSP_SHUTDOWN_MAX_ATTEMPTS 10 + +/* Algorithms */ +#define CS40L26_A2H_ALGO_ID 0x00040110 +#define CS40L26_BUZZGEN_ALGO_ID 0x0001F202 +#define CS40L26_DYNAMIC_F0_ALGO_ID 0x0001F21B +#define CS40L26_EVENT_HANDLER_ALGO_ID 0x0001F200 +#define CS40L26_F0_EST_ALGO_ID 0x0001F20C +#define CS40L26_GPIO_ALGO_ID 0x0001F201 +#define CS40L26_MAILBOX_ALGO_ID 0x0001F203 +#define CS40L26_MDSYNC_ALGO_ID 0x0001F20F +#define CS40L26_PM_ALGO_ID 0x0001F206 +#define CS40L26_SVC_ALGO_ID 0x0001F207 +#define CS40L26_VIBEGEN_ALGO_ID 0x000100BD +#define CS40L26_LOGGER_ALGO_ID 0x0004013D +#define CS40L26_EVENT_LOGGER_ALGO_ID 0x0004F222 +#define CS40L26_EXT_ALGO_ID 0x0004013C +#define CS40L26_DVL_ALGO_ID 0x00040140 +#define CS40L26_EP_ALGO_ID 0x00040141 +#define CS40L26_LF0T_ALGO_ID 0x00040143 + +/* DebugFS */ +#define CS40L26_ALGO_ID_MAX_STR_LEN 12 +#define CS40L26_NUM_DEBUGFS 3 + +/* Power management */ +#define CS40L26_PSEQ_MAX_WORDS 129 +#define CS40L26_PSEQ_NUM_OPS 8 +#define CS40L26_PSEQ_OP_MASK GENMASK(23, 16) +#define CS40L26_PSEQ_OP_SHIFT 16 +#define CS40L26_PSEQ_OP_WRITE_FULL 0x00 +#define CS40L26_PSEQ_OP_WRITE_FULL_WORDS 3 +#define CS40L26_PSEQ_OP_WRITE_FIELD 0x01 +#define CS40L26_PSEQ_OP_WRITE_FIELD_WORDS 4 +#define CS40L26_PSEQ_OP_WRITE_ADDR8 0x02 +#define CS40L26_PSEQ_OP_WRITE_ADDR8_WORDS 2 +#define CS40L26_PSEQ_OP_WRITE_INCR 0x03 +#define CS40L26_PSEQ_OP_WRITE_INCR_WORDS 2 +#define CS40L26_PSEQ_OP_WRITE_L16 0x04 +#define CS40L26_PSEQ_OP_WRITE_H16 0x05 +#define CS40L26_PSEQ_OP_WRITE_X16_WORDS 2 +#define CS40L26_PSEQ_OP_DELAY 0xFE +#define CS40L26_PSEQ_OP_DELAY_WORDS 1 +#define CS40L26_PSEQ_OP_END 0xFF +#define CS40L26_PSEQ_OP_END_WORDS 1 +#define CS40L26_PSEQ_OP_END_ADDR 0xFFFFFF +#define CS40L26_PSEQ_OP_END_DATA 0xFFFFFF +#define CS40L26_PSEQ_INVALID_ADDR 0xFF000000 +#define CS40L26_PSEQ_WRITE_FULL_LOWER_ADDR_SHIFT 8 +#define CS40L26_PSEQ_WRITE_FULL_UPPER_ADDR_SHIFT 16 +#define CS40L26_PSEQ_WRITE_FULL_LOWER_ADDR_MASK GENMASK(15, 0) +#define CS40L26_PSEQ_WRITE_FULL_UPPER_ADDR_MASK GENMASK(31, 0) +#define CS40L26_PSEQ_WRITE_FULL_UPPER_DATA_SHIFT 24 +#define CS40L26_PSEQ_WRITE_FULL_LOWER_DATA_MASK GENMASK(23, 0) +#define CS40L26_PSEQ_WRITE_FULL_UPPER_DATA_MASK GENMASK(31, 24) +#define CS40L26_PSEQ_WRITE_X16_LOWER_ADDR_SHIFT 16 +#define CS40L26_PSEQ_WRITE_X16_LOWER_ADDR_MASK GENMASK(7, 0) +#define CS40L26_PSEQ_WRITE_X16_UPPER_ADDR_SHIFT 8 +#define CS40L26_PSEQ_WRITE_X16_UPPER_ADDR_MASK GENMASK(23, 8) +#define CS40L26_PSEQ_WRITE_X16_UPPER_DATA_SHIFT 0 +#define CS40L26_PSEQ_WRITE_X16_UPPER_DATA_MASK GENMASK(31, 0) +#define CS40L26_PSEQ_WRITE_ADDR8_ADDR_SHIFT 8 +#define CS40L26_PSEQ_WRITE_ADDR8_ADDR_MASK GENMASK(7, 0) +#define CS40L26_PSEQ_WRITE_ADDR8_UPPER_DATA_SHIFT 24 +#define CS40L26_PSEQ_WRITE_ADDR8_UPPER_DATA_MASK GENMASK(31, 24) +#define CS40L26_PSEQ_WRITE_ADDR8_LOWER_DATA_MASK GENMASK(23, 0) + +#define CS40L26_PM_STDBY_TIMEOUT_OFFSET 16 +#define CS40L26_PM_STDBY_TIMEOUT_MS_MIN 100 +#define CS40L26_PM_TIMEOUT_MS_MAX 10000 +#define CS40L26_PM_ACTIVE_TIMEOUT_OFFSET 24 +#define CS40L26_PM_ACTIVE_TIMEOUT_MS_DEFAULT 250 +#define CS40L26_PM_ACTIVE_TIMEOUT_MS_MIN 0 +#define CS40L26_PM_TIMEOUT_TICKS_LOWER_MASK GENMASK(23, 0) +#define CS40L26_PM_TIMEOUT_TICKS_UPPER_MASK GENMASK(7, 0) +#define CS40L26_PM_TIMEOUT_TICKS_UPPER_SHIFT 24 +#define CS40L26_PM_TICKS_PER_MS 32 + +#define CS40L26_AUTOSUSPEND_DELAY_MS 2000 + +#define CS40L26_WKSRC_STS_MASK GENMASK(9, 4) +#define CS40L26_WKSRC_STS_SHIFT 4 + +#define CS40L26_WKSRC_GPIO_POL_MASK GENMASK(3, 0) + +#define CS40L26_WKSRC_STS_EN BIT(7) + + +#define CS40L26_NG_THRESHOLD_MASK GENMASK(2, 0) +#define CS40L26_NG_DELAY_MASK GENMASK(6, 4) +#define CS40L26_NG_ENABLE_MASK GENMASK(13, 8) +#define CS40L26_NG_THRESHOLD_DEFAULT 3 +#define CS40L26_NG_THRESHOLD_MIN 0 +#define CS40L26_NG_THRESHOLD_MAX 7 +#define CS40L26_NG_DELAY_DEFAULT 3 +#define CS40L26_NG_DELAY_MIN 0 +#define CS40L26_NG_DELAY_MAX 7 + +#define CS40L26_AUX_NG_THLD_MASK GENMASK(2, 0) +#define CS40L26_AUX_NG_HOLD_MASK GENMASK(11, 8) +#define CS40L26_AUX_NG_EN_MASK BIT(16) +#define CS40L26_AUX_NG_THLD_DEFAULT 3 +#define CS40L26_AUX_NG_THLD_MAX 7 +#define CS40L26_AUX_NG_HOLD_DEFAULT 3 +#define CS40L26_AUX_NG_HOLD_MAX 15 + +/* DSP mailbox controls */ +#define CS40L26_DSP_TIMEOUT_US_MIN 1000 +#define CS40L26_DSP_TIMEOUT_US_MAX 1100 +#define CS40L26_DSP_TIMEOUT_COUNT 100 + +#define CS40L26_DSP_MBOX_CMD_HIBER 0x02000001 +#define CS40L26_DSP_MBOX_CMD_WAKEUP 0x02000002 +#define CS40L26_DSP_MBOX_CMD_PREVENT_HIBER 0x02000003 +#define CS40L26_DSP_MBOX_CMD_ALLOW_HIBER 0x02000004 +#define CS40L26_DSP_MBOX_CMD_SHUTDOWN 0x02000005 +#define CS40L26_DSP_MBOX_PM_CMD_BASE CS40L26_DSP_MBOX_CMD_HIBER + +#define CS40L26_DSP_MBOX_CMD_START_I2S 0x03000002 +#define CS40L26_DSP_MBOX_CMD_STOP_I2S 0x03000003 +#define CS40L26_DSP_MBOX_CMD_LOGGER_MAX_RESET 0x03000004 +#define CS40L26_DSP_MBOX_CMD_A2H_REINIT 0x03000007 +#define CS40L26_DSP_MBOX_CMD_OWT_PUSH 0x03000008 +#define CS40L26_DSP_MBOX_CMD_OWT_RESET 0x03000009 + +#define CS40L26_DSP_MBOX_CMD_LE_EST 0x07000004 + +#define CS40L26_DSP_MBOX_CMD_OWT_DELETE_BASE 0x0D000000 +#define CS40L26_DSP_MBOX_CMD_HE_TIME_BASE 0x0E000000 + +#define CS40L26_DSP_MBOX_CMD_INDEX_MASK GENMASK(28, 24) +#define CS40L26_DSP_MBOX_CMD_INDEX_SHIFT 24 + +#define CS40L26_DSP_MBOX_CMD_PAYLOAD_MASK GENMASK(23, 0) + +#define CS40L26_DSP_MBOX_CMD_INDEX_CALIBRATION_CONTROL 0x7 + +#define CS40L26_DSP_MBOX_BUFFER_NUM_REGS 4 + +#define CS40L26_DSP_MBOX_COMPLETE_MBOX 0x01000000 +#define CS40L26_DSP_MBOX_COMPLETE_GPIO 0x01000001 +#define CS40L26_DSP_MBOX_COMPLETE_I2S 0x01000002 +#define CS40L26_DSP_MBOX_TRIGGER_CP 0x01000010 +#define CS40L26_DSP_MBOX_TRIGGER_GPIO 0x01000011 +#define CS40L26_DSP_MBOX_TRIGGER_I2S 0x01000012 +#define CS40L26_DSP_MBOX_PM_AWAKE 0x02000002 +#define CS40L26_DSP_MBOX_F0_EST_START 0x07000011 +#define CS40L26_DSP_MBOX_F0_EST_DONE 0x07000021 +#define CS40L26_DSP_MBOX_REDC_EST_START 0x07000012 +#define CS40L26_DSP_MBOX_REDC_EST_DONE 0x07000022 +#define CS40L26_DSP_MBOX_LE_EST_START 0x07000014 +#define CS40L26_DSP_MBOX_LE_EST_DONE 0x07000024 +#define CS40L26_DSP_MBOX_PEQ_CALCULATION_START 0x07000018 +#define CS40L26_DSP_MBOX_PEQ_CALCULATION_DONE 0x07000028 +#define CS40L26_DSP_MBOX_LS_CALIBRATION_START 0x07000010 +#define CS40L26_DSP_MBOX_LS_CALIBRATION_DONE 0x07000030 +#define CS40L26_DSP_MBOX_LS_CALIBRATION_ERROR 0x07000040 + +#define CS40L26_DSP_MBOX_SYS_ACK 0x0A000000 +#define CS40L26_DSP_MBOX_PANIC 0x0C000000 +#define CS40L26_DSP_MBOX_WATERMARK 0x0D000000 + +#define CS40L26_DSP_MBOX_HE_PAYLOAD_MAX_MS GENMASK(22, 0) +#define CS40L26_DSP_MBOX_HE_PAYLOAD_OVERFLOW BIT(23) + +/* Firmware Mode */ +#define CS40L26_FW_FILE_NAME "cs40l26.wmfw" +#define CS40L26_FW_CALIB_NAME "cs40l26-calib.wmfw" + +#define CS40L26_MAX_TUNING_FILES 6 + +#define CS40L26_WT_FILE_NAME "cs40l26.bin" +#define CS40L26_WT_FILE_PREFIX "cs40l26-wt" +#define CS40L26_WT_FILE_PREFIX_LEN 11 +#define CS40L26_SVC_TUNING_FILE_PREFIX "cs40l26-svc" +#define CS40L26_SVC_TUNING_FILE_PREFIX_LEN 12 +#define CS40L26_SVC_TUNING_FILE_NAME "cs40l26-svc.bin" +#define CS40L26_A2H_TUNING_FILE_NAME "cs40l26-a2h.bin" +#define CS40L26_TUNING_FILE_NAME_MAX_LEN 20 +#define CS40L26_TUNING_FILE_SUFFIX ".bin" +#define CS40L26_TUNING_FILE_SUFFIX_LEN 4 +#define CS40L26_DVL_FILE_NAME "cs40l26-dvl.bin" +#define CS40L26_CALIB_BIN_FILE_NAME "cs40l26-calib.bin" +#define CS40L26_EP_TUNING_FILE_NAME "cs40l26-ep.bin" +#define CS40L26_LF0T_FILE_NAME "cs40l26-lf0t.bin" + +#define CS40L26_SVC_LE_EST_TIME_US 8000 +#define CS40L26_SVC_LE_MAX_ATTEMPTS 2 +#define CS40L26_SVC_DT_PREFIX "svc-le" + +#define CS40L26_FW_ID 0x1800D4 +#define CS40L26_FW_MIN_REV 0x07022B +#define CS40L26_FW_BRANCH 0x07 +#define CS40L26_FW_CALIB_ID 0x1800DA +#define CS40L26_FW_CALIB_MIN_REV 0x010123 +#define CS40L26_FW_CALIB_BRANCH 0x01 +#define CS40L26_FW_MAINT_MIN_REV 0x270216 +#define CS40L26_FW_MAINT_BRANCH 0x27 +#define CS40L26_FW_MAINT_CALIB_MIN_REV 0x21010D +#define CS40L26_FW_MAINT_CALIB_BRANCH 0x21 +#define CS40L26_FW_B2_MIN_REV 0x080100 +#define CS40L26_FW_B2_BRANCH 0x08 +#define CS40L26_FW_GPI_TIMEOUT_MIN_REV 0x07022A +#define CS40L26_FW_GPI_TIMEOUT_CALIB_MIN_REV 0x010122 +#define CS40L26_FW_BRANCH_MASK GENMASK(23, 21) + +#define CS40L26_CCM_CORE_RESET 0x00000200 +#define CS40L26_CCM_CORE_ENABLE 0x00000281 + +/* Wavetable */ +#define CS40L26_WT_NAME_XM "WAVE_XM_TABLE" +#define CS40L26_WT_NAME_YM "WAVE_YM_TABLE" + +/* Power supplies */ +#define CS40L26_VP_SUPPLY 0 +#define CS40L26_VA_SUPPLY 1 +#define CS40L26_NUM_SUPPLIES 2 +#define CS40L26_VP_SUPPLY_NAME "VP" +#define CS40L26_VA_SUPPLY_NAME "VA" + +#define CS40L26_MIN_RESET_PULSE_WIDTH 1500 +#define CS40L26_CONTROL_PORT_READY_DELAY 6000 + +/* Haptic triggering */ +#define CS40L26_STOP_PLAYBACK 0x05000000 + +#define CS40L26_MAX_INDEX_MASK 0x0000FFFF + +#define CS40L26_CUSTOM_DATA_SIZE 2 + +#define CS40L26_RAM_INDEX_START 0x01000000 +#define CS40L26_RAM_INDEX_END 0x0100007F + +#define CS40L26_ROM_INDEX_START 0x01800000 +#define CS40L26_ROM_INDEX_END 0x01800026 + +#define CS40L26_OWT_INDEX_START 0x01400000 +#define CS40L26_OWT_INDEX_END 0x01400010 + +#define CS40L26_RAM_BANK_ID 0 +#define CS40L26_ROM_BANK_ID 1 +#define CS40L26_OWT_BANK_ID 2 +#define CS40L26_BUZ_BANK_ID 3 + +#define CS40L26_BUZZGEN_NUM_CONFIGS (CS40L26_BUZZGEN_INDEX_END - CS40L26_BUZZGEN_INDEX_START) + +#define CS40L26_BUZZGEN_INDEX_START 0x01800080 +#define CS40L26_BUZZGEN_INDEX_END 0x01800085 + +#define CS40L26_BUZZGEN_PER_MAX 10 /* ms */ +#define CS40L26_BUZZGEN_PER_MIN 4 + +#define CS40L26_BUZZGEN_LEVEL_MIN 0x00 +#define CS40L26_BUZZGEN_LEVEL_MAX 0xFF + +#define CS40L26_AMP_CTRL_VOL_PCM_MASK GENMASK(13, 3) +#define CS40L26_AMP_CTRL_VOL_PCM_SHIFT 3 + +#define CS40L26_AMP_VOL_PCM_MAX 0x07FF + +#define CS40L26_ERASE_BUFFER_MS 500 +#define CS40L26_MAX_WAIT_VIBE_COMPLETE_MS 10000 + +/* GPI Triggering */ +#define CS40L26_GPIO1 1 +#define CS40L26_EVENT_MAP_INDEX_MASK GENMASK(8, 0) +#define CS40L26_EVENT_MAP_NUM_GPI_REGS 4 +#define CS40L26_EVENT_MAP_GPI_DISABLE 0x1FF + +#define CS40L26_BTN_INDEX_MASK GENMASK(7, 0) +#define CS40L26_BTN_BUZZ_MASK BIT(7) +#define CS40L26_BTN_BUZZ_SHIFT 7 +#define CS40L26_BTN_BANK_MASK BIT(8) +#define CS40L26_BTN_BANK_SHIFT 8 +#define CS40L26_BTN_NUM_MASK GENMASK(14, 12) +#define CS40L26_BTN_NUM_SHIFT 12 +#define CS40L26_BTN_EDGE_MASK BIT(15) +#define CS40L26_BTN_EDGE_SHIFT 15 +#define CS40L26_BTN_OWT_MASK BIT(16) +#define CS40L26_BTN_OWT_SHIFT 16 + +/* Interrupts */ +#define CS40L26_IRQ_STATUS_MASK BIT(0) +#define CS40L26_GPIO1_RISE_MASK BIT(0) +#define CS40L26_GPIO1_FALL_MASK BIT(1) +#define CS40L26_GPIO2_RISE_MASK BIT(2) +#define CS40L26_GPIO2_FALL_MASK BIT(3) +#define CS40L26_GPIO3_RISE_MASK BIT(4) +#define CS40L26_GPIO3_FALL_MASK BIT(5) +#define CS40L26_GPIO4_RISE_MASK BIT(6) +#define CS40L26_GPIO4_FALL_MASK BIT(7) +#define CS40L26_WKSRC_STS_ANY_MASK BIT(8) +#define CS40L26_WKSRC_STS_GPIO1_MASK BIT(9) +#define CS40L26_WKSRC_STS_GPIO2_MASK BIT(10) +#define CS40L26_WKSRC_STS_GPIO3_MASK BIT(11) +#define CS40L26_WKSRC_STS_GPIO4_MASK BIT(12) +#define CS40L26_WKSRC_STS_SPI_MASK BIT(13) +#define CS40L26_WKSRC_STS_I2C_MASK BIT(14) +#define CS40L26_BST_OVP_ERR_MASK BIT(20) +#define CS40L26_BST_DCM_UVP_ERR_MASK BIT(21) +#define CS40L26_BST_SHORT_ERR_MASK BIT(22) +#define CS40L26_BST_IPK_FLAG_MASK BIT(23) +#define CS40L26_TEMP_ERR_MASK BIT(26) +#define CS40L26_AMP_ERR_MASK BIT(27) +#define CS40L26_VIRTUAL2_MBOX_WR_MASK BIT(31) +#define CS40L26_VPBR_FLAG_MASK BIT(17) +#define CS40L26_VPBR_ATT_CLR_MASK BIT(18) +#define CS40L26_VBBR_FLAG_MASK BIT(19) +#define CS40L26_VBBR_ATT_CLR_MASK BIT(20) +#define CS40L26_IRQ(_irq, _name, _hand) \ + { \ + .irq = CS40L26_ ## _irq ## _IRQ, \ + .name = _name, \ + .handler = _hand, \ + } +#define CS40L26_REG_IRQ(_reg, _irq) \ + [CS40L26_ ## _irq ## _IRQ] = { \ + .reg_offset = (CS40L26_ ## _reg) - CS40L26_IRQ1_EINT_1, \ + .mask = CS40L26_ ## _irq ## _MASK \ + } + + +/* temp monitoring */ +#define CS40L26_TEMP_RESULT_FILT_MASK GENMASK(24, 16) +#define CS40L26_TEMP_RESULT_FILT_SHIFT 16 + +/* BST */ +#define CS40L26_BST_DCM_EN_DEFAULT 1 +#define CS40L26_BST_DCM_EN_MASK BIT(0) +#define CS40L26_BST_DCM_EN_SHIFT 0 + +#define CS40L26_BST_IPK_UA_MAX 4800000 +#define CS40L26_BST_IPK_UA_MIN 800000 +#define CS40L26_BST_IPK_UA_STEP 50000 +#define CS40L26_BST_IPK_UA_DEFAULT 4500000 +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE +#define CS40L26_BST_IPK_DEFAULT 0x40 +#else +#define CS40L26_BST_IPK_DEFAULT 0x4A +#endif + +#define CS40L26_BST_UV_MIN 2500000 +#define CS40L26_BST_UV_MAX 11000000 +#define CS40L26_BST_UV_STEP 50000 +#define CS40L26_BST_CTL_DEFAULT 0xAA +#define CS40L26_BST_CTL_VP 0x00 +#define CS40L26_BST_CTL_MASK GENMASK(7, 0) +#define CS40L26_BST_CTL_SEL_MASK GENMASK(1, 0) +#define CS40L26_BST_CTL_SEL_FIXED 0x0 + +#define CS40L26_CLIP_LVL_UV_MAX 11000000 +#define CS40L26_CLIP_LVL_UV_MIN 250000 +#define CS40L26_CLIP_LVL_UV_STEP 250000 +#define CS40L26_CLIP_LVL_DEFAULT 0x2C +#define CS40L26_CLIP_LVL_MASK GENMASK(17, 12) +#define CS40L26_CLIP_LVL_SHIFT 12 + +#define CS40L26_BST_TIME_MIN_US 10000 +#define CS40L26_BST_TIME_MAX_US 10100 + +#define CS40L26_BST_CTL_LIM_EN_MASK BIT(2) +#define CS40L26_BST_CTL_LIM_EN_SHIFT 2 + +#define CS40L26_OVERPROTECTION_GAIN_MIN BIT(20) + +#define CS40L26_BOOST_DISABLE_DELAY_MIN 0 +#define CS40L26_BOOST_DISABLE_DELAY_MAX 8388608 + +/* Brownout prevention */ +#define CS40L26_VXBR_STATUS_DIV_STEP 625 +#define CS40L26_VXBR_STATUS_MASK GENMASK(7, 0) +#define CS40L26_VXBR_DEFAULT_MASK GENMASK(31, 24) + +#define CS40L26_VBBR_EN_MASK BIT(13) +#define CS40L26_VBBR_EN_SHIFT 13 + +#define CS40L26_VPBR_EN_MASK BIT(12) +#define CS40L26_VPBR_EN_SHIFT 12 +#define CS40L26_VPBR_THLD_MASK GENMASK(4, 0) + +#define CS40L26_VPBR_THLD_MIN 0x02 +#define CS40L26_VPBR_THLD_MAX 0x1F +#define CS40L26_VPBR_THLD_UV_DIV 47000 +#define CS40L26_VPBR_THLD_UV_MIN 2497000 +#define CS40L26_VPBR_THLD_UV_MAX 3874000 +#define CS40L26_VPBR_THLD_UV_DEFAULT 2639000 + +#define CS40L26_VBBR_THLD_MASK GENMASK(5, 0) +#define CS40L26_VBBR_THLD_MIN 0x02 +#define CS40L26_VBBR_THLD_MAX 0x3F +#define CS40L26_VBBR_THLD_UV_DIV 55000 +#define CS40L26_VBBR_THLD_UV_MIN 109000 +#define CS40L26_VBBR_THLD_UV_MAX 3445000 +#define CS40L26_VBBR_THLD_UV_DEFAULT 273000 + +#define CS40L26_VXBR_MAX_ATT_MASK GENMASK(11, 8) +#define CS40L26_VXBR_MAX_ATT_SHIFT 8 +#define CS40L26_VXBR_MAX_ATT_MAX 15 +#define CS40L26_VXBR_MAX_ATT_MIN 0 +#define CS40L26_VXBR_MAX_ATT_DEFAULT 9 + +#define CS40L26_VXBR_ATK_STEP_MASK GENMASK(15, 12) +#define CS40L26_VXBR_ATK_STEP_SHIFT 12 +#define CS40L26_VXBR_ATK_STEP_MIN 0 +#define CS40L26_VXBR_ATK_STEP_MAX 7 +#define CS40L26_VXBR_ATK_STEP_DEFAULT 1 + +#define CS40L26_VXBR_ATK_RATE_MASK GENMASK(18, 16) +#define CS40L26_VXBR_ATK_RATE_SHIFT 16 +#define CS40L26_VXBR_ATK_RATE_MIN 0 +#define CS40L26_VXBR_ATK_RATE_MAX 7 +#define CS40L26_VXBR_ATK_RATE_DEFAULT 2 + +#define CS40L26_VXBR_WAIT_MASK GENMASK(20, 19) +#define CS40L26_VXBR_WAIT_SHIFT 19 +#define CS40L26_VXBR_WAIT_MAX 3 +#define CS40L26_VXBR_WAIT_MIN 0 +#define CS40L26_VXBR_WAIT_DEFAULT 1 + +#define CS40L26_VXBR_REL_RATE_MASK GENMASK(23, 21) +#define CS40L26_VXBR_REL_RATE_SHIFT 21 +#define CS40L26_VXBR_REL_RATE_MAX 7 +#define CS40L26_VXBR_REL_RATE_MIN 0 +#define CS40L26_VXBR_REL_RATE_DEFAULT 5 + +/* Mixer noise gate */ +#define CS40L26_MIXER_NGATE_CH1_CFG_DEFAULT_NEW 0x00010003 + +/* Audio */ +#define CS40L26_PLL_CLK_CFG_32768 0x00 +#define CS40L26_PLL_CLK_CFG_1536000 0x1B +#define CS40L26_PLL_CLK_CFG_3072000 0x21 +#define CS40L26_PLL_CLK_CFG_6144000 0x28 +#define CS40L26_PLL_CLK_CFG_9600000 0x30 +#define CS40L26_PLL_CLK_CFG_12288000 0x33 + +#define CS40L26_PLL_CLK_FRQ_32768 32768 +#define CS40L26_PLL_CLK_FRQ_1536000 1536000 +#define CS40L26_PLL_CLK_FRQ_3072000 3072000 +#define CS40L26_PLL_CLK_FRQ_6144000 6144000 +#define CS40L26_PLL_CLK_FRQ_9600000 9600000 +#define CS40L26_PLL_CLK_FRQ_12288000 12288000 + +#define CS40L26_PLL_CLK_SEL_BCLK 0x0 +#define CS40L26_PLL_CLK_SEL_FSYNC 0x1 +#define CS40L26_PLL_CLK_SEL_MCLK 0x5 + +#define CS40L26_PLL_CLK_FREQ_MASK GENMASK(31, 0) +#define CS40L26_PLL_CLK_CFG_MASK GENMASK(5, 0) + +#define CS40L26_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) +#define CS40L26_RATES (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000) + +#define CS40L26_ASP_RX_WIDTH_MASK GENMASK(31, 24) +#define CS40L26_ASP_RX_WIDTH_SHIFT 24 +#define CS40L26_ASP_FMT_MASK GENMASK(10, 8) +#define CS40L26_ASP_FMT_SHIFT 8 +#define CS40L26_ASP_BCLK_INV_MASK BIT(6) +#define CS40L26_ASP_BCLK_INV_SHIFT 6 +#define CS40L26_ASP_FSYNC_INV_MASK BIT(2) +#define CS40L26_ASP_FSYNC_INV_SHIFT 2 + +#define CS40L26_ASP_FMT_TDM1_DSPA 0x0 +#define CS40L26_ASP_FMT_I2S 0x2 +#define CS40L26_ASP_FMT_TDM1P5 0x4 + +#define CS40L26_ASP_START_TIMEOUT 50 /* milliseconds */ + +#define CS40L26_PLL_REFCLK_BCLK 0x0 +#define CS40L26_PLL_REFCLK_FSYNC 0x1 +#define CS40L26_PLL_REFCLK_MCLK 0x5 + +#define CS40L26_PLL_REFCLK_SEL_MASK GENMASK(2, 0) +#define CS40L26_PLL_REFCLK_EN_MASK BIT(4) +#define CS40L26_PLL_REFCLK_EN_SHIFT 4 +#define CS40L26_PLL_REFCLK_FREQ_MASK GENMASK(10, 5) +#define CS40L26_PLL_REFCLK_FREQ_SHIFT 5 +#define CS40L26_PLL_REFCLK_LOOP_MASK BIT(11) +#define CS40L26_PLL_REFCLK_LOOP_SHIFT 11 +#define CS40L26_PLL_REFCLK_SET_OPEN_LOOP 1 +#define CS40L26_PLL_REFCLK_SET_CLOSED_LOOP 0 +#define CS40L26_PLL_REFCLK_SET_ATTEMPTS 5 +#define CS40L26_PLL_REFCLK_FORCE_EN_MASK BIT(16) +#define CS40L26_PLL_REFCLK_FORCE_EN_SHIFT 16 + +#define CS40L26_ASP_RX_WL_MASK GENMASK(5, 0) + +#define CS40L26_DATA_SRC_ASPRX1 0x08 +#define CS40L26_DATA_SRC_ASPRX2 0x09 +#define CS40L26_DATA_SRC_VMON 0x18 +#define CS40L26_DATA_SRC_DSP1TX1 0x32 +#define CS40L26_DATA_SRC_DSP1TX2 0x33 +#define CS40L26_DATA_SRC_DSP1TX4 0x35 + +#define CS40L26_DATA_SRC_MASK GENMASK(6, 0) + +#define CS40L26_ASP_TX1_EN_MASK BIT(0) +#define CS40L26_ASP_TX2_EN_MASK BIT(1) +#define CS40L26_ASP_TX2_EN_SHIFT 1 +#define CS40L26_ASP_TX3_EN_MASK BIT(2) +#define CS40L26_ASP_TX3_EN_SHIFT 2 +#define CS40L26_ASP_TX4_EN_MASK BIT(3) +#define CS40L26_ASP_TX4_EN_SHIFT 3 +#define CS40L26_ASP_RX1_EN_MASK BIT(16) +#define CS40L26_ASP_RX1_EN_SHIFT 16 +#define CS40L26_ASP_RX2_EN_MASK BIT(17) +#define CS40L26_ASP_RX2_EN_SHIFT 17 +#define CS40L26_ASP_RX3_EN_MASK BIT(18) +#define CS40L26_ASP_RX3_EN_SHIFT 18 + +#define CS40L26_ASP_RX1_SLOT_MASK GENMASK(5, 0) +#define CS40L26_ASP_RX2_SLOT_MASK GENMASK(13, 8) +#define CS40L26_ASP_RX2_SLOT_SHIFT 8 + +#define CS40L26_A2H_MAX_TUNINGS 5 + +#define CS40L26_A2H_LEVEL_MAX 0x7FFFFF +#define CS40L26_A2H_LEVEL_MIN 0x000001 + +#define CS40L26_A2H_DELAY_MAX 0x190 + +#define CS40L26_VMON_DEC_OUT_DATA_MASK GENMASK(23, 0) +#define CS40L26_VMON_OVFL_FLAG_MASK BIT(31) +#define CS40L26_VMON_DEC_OUT_DATA_MAX CS40L26_VMON_DEC_OUT_DATA_MASK +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE +#define VMON_100_MV 0x10A68 +#define VMON_20_MV 0x3548 +#endif + +#define CS40L26_GAIN_FULL_SCALE 100 + +#define CS40L26_VIMON_DUAL_RATE_MASK BIT(16) + +/* OWT */ +#define CS40L26_WT_HEADER_OFFSET 3 +#define CS40L26_WT_METADATA_OFFSET 3 +#define CS40L26_WT_HEADER_DEFAULT_FLAGS 0x0000 +#define CS40L26_WT_HEADER_PWLE_SIZE 12 +#define CS40L26_WT_HEADER_COMP_SIZE 20 +#define CS40L26_WT_SVC_METADATA BIT(10) +#define CS40L26_WT_TYPE12_IDENTIFIER 0xC00 + +#define CS40L26_WT_TYPE10_SECTION_BYTES_MIN 8 +#define CS40L26_WT_TYPE10_SECTION_BYTES_MAX 12 +#define CS40L26_WT_TYPE10_WAVELEN_MAX 0x3FFFFF +#define CS40L26_WT_TYPE10_WAVELEN_INDEF 0x400000 +#define CS40L26_WT_TYPE10_WAVELEN_CALCULATED 0x800000 +#define CS40L26_WT_TYPE10_COMP_DURATION_FLAG 0x80 +#define CS40L26_WT_TYPE10_COMP_ROM_FLAG 0x40 +#define CS40L26_WT_TYPE10_COMP_BUFFER 0x0000 + +/* F0 Offset represented as Q10.14 format */ +#define CS40L26_F0_OFFSET_MAX 0x190000 /* +100 Hz */ +#define CS40L26_F0_OFFSET_MIN 0xE70000 /* -100 Hz */ + +/* Calibration */ +#define CS40L26_F0_EST_MIN 0xC8000 +#define CS40L26_F0_EST_MAX 0x7FC000 +#define CS40L26_Q_EST_MIN 0 +#define CS40L26_Q_EST_MAX 0x7FFFFF + +#define CS40L26_DVL_PEQ_COEFFICIENTS_NUM_REGS 6 + +#define CS40L26_F0_EST_FREQ_FRAC_BITS 14 + +#define CS40L26_SVC_INITIALIZATION_PERIOD_MS 6 +#define CS40L26_REDC_CALIBRATION_BUFFER_MS 10 +#define CS40L26_F0_AND_Q_CALIBRATION_MIN_MS 100 +#define CS40L26_F0_AND_Q_CALIBRATION_MAX_MS 1800 +#define CS40L26_F0_CHIRP_DURATION_FACTOR 3750 +#define CS40L26_F0_FREQ_SPAN_HZ_MAX 120 +#define CS40L26_F0_FREQ_SPAN_HZ_MIN 20 +#define CS40L26_F0_FREQ_SPAN_MAX (CS40L26_F0_FREQ_SPAN_HZ_MAX << CS40L26_F0_EST_FREQ_FRAC_BITS) +#define CS40L26_F0_FREQ_SPAN_MIN (CS40L26_F0_FREQ_SPAN_HZ_MIN << CS40L26_F0_EST_FREQ_FRAC_BITS) +#define CS40L26_F0_FREQ_CENTRE_HZ_MAX 511 +#define CS40L26_F0_FREQ_CENTRE_HZ_MIN 50 +#define CS40L26_F0_FREQ_CENTRE_MAX (CS40L26_F0_FREQ_CENTRE_HZ_MAX << CS40L26_F0_EST_FREQ_FRAC_BITS) +#define CS40L26_F0_FREQ_CENTRE_MIN (CS40L26_F0_FREQ_CENTRE_HZ_MIN << CS40L26_F0_EST_FREQ_FRAC_BITS) + +#define CS40L26_LOGGER_EN_MASK BIT(0) + +#define CS40L26_LOGGER_SRC_ID_BEMF 1 +#define CS40L26_LOGGER_SRC_ID_VBST 2 +#define CS40L26_LOGGER_SRC_ID_VMON 3 +#define CS40L26_LOGGER_SRC_ID_EP 4 + +#define CS40L26_LOGGER_SRC_TYPE_XM_TO_XM 1 + +#define CS40L26_LOGGER_SRC_FF_OUT 2 +#define CS40L26_LOGGER_SRC_PROTECTION_OUT 3 + +#define CS40L26_LOGGER_SRC_SIGN_MASK BIT(23) +#define CS40L26_LOGGER_SRC_SIZE_MASK BIT(22) +#define CS40L26_LOGGER_SRC_TYPE_MASK GENMASK(21, 20) +#define CS40L26_LOGGER_SRC_ID_MASK GENMASK(19, 16) +#define CS40L26_LOGGER_SRC_ADDR_MASK GENMASK(15, 0) + +#define CS40L26_LOGGER_DATA_MAX_STEP 12 +#define CS40L26_LOGGER_DATA_MAX_OFFSET 4 + +#define CS40L26_UINT_24_BITS_MAX 16777215 + +#define CS40L26_CALIBRATION_TIMEOUT_MS 2000 + +/* Compensation */ +#define CS40L26_COMP_EN_REDC_SHIFT 1 +#define CS40L26_COMP_EN_F0_SHIFT 0 + +/* FW EXT */ +#define CS40L26_SVC_EN_MASK BIT(0) + +/* DBC */ +#define CS40L26_DBC_ENABLE_MASK BIT(1) +#define CS40L26_DBC_ENABLE_SHIFT 1 +#define CS40L26_DBC_CONTROLS_MAX 0x7FFFFF +#define CS40L26_DBC_ENV_REL_COEF_MIN 8384414 +#define CS40L26_DBC_ENV_REL_COEF_NAME "DBC_ENV_REL_COEF" +#define CS40L26_DBC_RISE_HEADROOM_MIN 1432204 +#define CS40L26_DBC_RISE_HEADROOM_NAME "DBC_RISE_HEADROOM" +#define CS40L26_DBC_FALL_HEADROOM_MIN 750193 +#define CS40L26_DBC_FALL_HEADROOM_NAME "DBC_FALL_HEADROOM" +#define CS40L26_DBC_TX_LVL_THRESH_FS_MIN 839 +#define CS40L26_DBC_TX_LVL_THRESH_FS_NAME "DBC_TX_LVL_THRESH_FS" +#define CS40L26_DBC_TX_LVL_HOLD_OFF_MS_MAX 1000 +#define CS40L26_DBC_TX_LVL_HOLD_OFF_MS_MIN 10 +#define CS40L26_DBC_TX_LVL_HOLD_OFF_MS_NAME "DBC_TX_LVL_HOLD_OFF_MS" +#define CS40L26_DBC_USE_DEFAULT 0xFFFFFFFF + +/* Errata */ +#define CS40L26_ERRATA_A1_NUM_WRITES 5 +#define CS40L26_ERRATA_A1_EXPL_EN_NUM_WRITES 1 +#define CS40L26_PLL_REFCLK_DET_EN 0x00000001 +#define CS40L26_DISABLE_EXPL_MODE 0x0100C080 + +/* MFD */ +#define CS40L26_NUM_MFD_DEVS 1 + +/* macros */ +#define CS40L26_MS_TO_US(n) ((n) * 1000) + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE +#define DELAY_BEFORE_STOP_PLAYBACK_US 8500 +/* defined by Samsung */ +#define CS40L26_SAMSUNG_DEFAULT_HIGH_TEMP INT_MAX +#define CS40L26_SAMSUNG_DEFAULT_HIGH_TEMP_PERCENT 100 +#define CS40L26_SAMSUNG_F0_MIN 0x250000 +#define CS40L26_SAMSUNG_F0_MAX 0x2A0000 +#define CS40L26_SAMSUNG_F0_OFFSET 0x4000 +#endif + +/* enums */ +enum cs40l26_brwnout_type { + CS40L26_VBBR_THLD, + CS40L26_VPBR_THLD, + CS40L26_VXBR_MAX_ATT, + CS40L26_VXBR_ATK_STEP, + CS40L26_VXBR_ATK_RATE, + CS40L26_VXBR_WAIT, + CS40L26_VXBR_REL_RATE, + CS40L26_NUM_BRWNOUT_TYPES, +}; + +enum cs40l26_gpio_map { + CS40L26_GPIO_MAP_A_PRESS, + CS40L26_GPIO_MAP_A_RELEASE, + CS40L26_GPIO_MAP_NUM_AVAILABLE, + CS40L26_GPIO_MAP_INVALID, +}; + +enum cs40l26_dbc_type { + CS40L26_DBC_ENV_REL_COEF, /* 0 */ + CS40L26_DBC_RISE_HEADROOM, + CS40L26_DBC_FALL_HEADROOM, + CS40L26_DBC_TX_LVL_THRESH_FS, + CS40L26_DBC_TX_LVL_HOLD_OFF_MS, + CS40L26_DBC_NUM_CONTROLS, /* 5 */ +}; + +enum cs40l26_vibe_state { + CS40L26_VIBE_STATE_STOPPED, + CS40L26_VIBE_STATE_HAPTIC, + CS40L26_VIBE_STATE_ASP, +}; + +enum cs40l26_vibe_state_event { + CS40L26_VIBE_STATE_EVENT_MBOX_PLAYBACK, + CS40L26_VIBE_STATE_EVENT_MBOX_COMPLETE, + CS40L26_VIBE_STATE_EVENT_GPIO_TRIGGER, + CS40L26_VIBE_STATE_EVENT_GPIO_COMPLETE, + CS40L26_VIBE_STATE_EVENT_ASP_START, + CS40L26_VIBE_STATE_EVENT_ASP_STOP, +}; + +enum cs40l26_err_rls { + CS40L26_RSRVD_ERR_RLS,/* 0 */ + CS40L26_AMP_SHORT_ERR_RLS,/* 1 */ + CS40L26_BST_SHORT_ERR_RLS,/* 2 */ + CS40L26_BST_OVP_ERR_RLS,/* 3 */ + CS40L26_BST_UVP_ERR_RLS,/* 4 */ + CS40L26_TEMP_WARN_ERR_RLS,/* 5 */ + CS40L26_TEMP_ERR_RLS,/* 6 */ +}; + +enum cs40l26_pm_state { + CS40L26_PM_STATE_HIBERNATE, + CS40L26_PM_STATE_WAKEUP, + CS40L26_PM_STATE_PREVENT_HIBERNATE, + CS40L26_PM_STATE_ALLOW_HIBERNATE, + CS40L26_PM_STATE_SHUTDOWN, +}; + +enum cs40l26_calibration_control_request { + CS40L26_CALIBRATION_CONTROL_REQUEST_F0_AND_Q = 0x1, + CS40L26_CALIBRATION_CONTROL_REQUEST_REDC = 0x2, + CS40L26_CALIBRATION_CONTROL_REQUEST_DVL_PEQ = 0x8, + CS40L26_CALIBRATION_CONTROL_REQUEST_LS_CALIBRATION = 0x10, +}; + +enum cs40l26_irq_list { + CS40L26_GPIO1_RISE_IRQ, + CS40L26_GPIO1_FALL_IRQ, + CS40L26_GPIO2_RISE_IRQ, + CS40L26_GPIO2_FALL_IRQ, + CS40L26_GPIO3_RISE_IRQ, + CS40L26_GPIO3_FALL_IRQ, + CS40L26_GPIO4_RISE_IRQ, + CS40L26_GPIO4_FALL_IRQ, + CS40L26_WKSRC_STS_ANY_IRQ, + CS40L26_WKSRC_STS_GPIO1_IRQ, + CS40L26_WKSRC_STS_GPIO2_IRQ, + CS40L26_WKSRC_STS_GPIO3_IRQ, + CS40L26_WKSRC_STS_GPIO4_IRQ, + CS40L26_WKSRC_STS_I2C_IRQ, + CS40L26_BST_OVP_ERR_IRQ, + CS40L26_BST_DCM_UVP_ERR_IRQ, + CS40L26_BST_SHORT_ERR_IRQ, + CS40L26_BST_IPK_FLAG_IRQ, + CS40L26_TEMP_ERR_IRQ, + CS40L26_AMP_ERR_IRQ, + CS40L26_VIRTUAL2_MBOX_WR_IRQ, + CS40L26_VPBR_FLAG_IRQ, + CS40L26_VPBR_ATT_CLR_IRQ, + CS40L26_VBBR_FLAG_IRQ, + CS40L26_VBBR_ATT_CLR_IRQ +}; + +/* structs */ +struct cs40l26_log_src { + u8 sign; + u8 size; + u8 type; + u8 id; + u16 addr; +}; + +struct cs40l26_ls_cal_param { + const char *calib_name; + const char *runtime_name; + int word_num; +}; + +struct cs40l26_irq { + int irq; + const char *name; + irqreturn_t (*handler)(int irq, void *data); +}; + +struct cs40l26_brwnout_limits { + u32 max; + u32 min; +}; + +struct cs40l26_dbc { + enum cs40l26_dbc_type type; + const char *const name; + u32 max; + u32 min; +}; + +struct cs40l26_buzzgen_config { + const char *duration_name; + const char *freq_name; + const char *level_name; + int effect_id; +}; + +struct cs40l26_owt_section { + u8 flags; + u8 repeat; + u8 amplitude; + u8 index; + u16 delay; + u16 duration; + u16 wvfrm_bank; +}; + +struct cs40l26_pseq_op { + u8 size; + u16 offset; /* offset in bytes from pseq_base */ + u8 operation; + u32 words[3]; + struct list_head list; +}; + +struct cs40l26_svc_le { + s32 gain_adjust; + u32 min; + u32 max; + u32 n; +}; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE +struct cs40l26_samsung_platform_data { + bool is_f0_tracking; + bool is_mv_support; + int f0_offset; + const char *owt_lib_compat_version; + const char *ap_chipset; +}; +#endif + +struct cs40l26_rom_regs { + u32 pm_cur_state; + u32 pm_state_locks; + u32 pm_timeout_ticks; + u32 dsp_halo_state; + u32 event_map_table_event_data_packed; + u32 p_vibegen_rom; + u32 rom_pseq_end_of_script; +}; + +struct cs40l26_uploaded_effect { + int id; + u32 trigger_index; + u16 wvfrm_bank; + enum cs40l26_gpio_map mapping; + struct list_head list; +}; + +struct cs40l26_brwnout { + bool enable; + u32 thld_uv; + u32 max_att_db; + u32 atk_step; + u32 atk_rate; + u32 wait; + u32 rel_rate; +}; + +struct cs40l26_private { + struct device *dev; + struct regmap *regmap; + u32 devid : 24; + u8 revid; + struct mutex lock; + struct gpio_desc *reset_gpio; + struct input_dev *input; + struct cl_dsp *dsp; + struct list_head effect_head; + unsigned int cur_index; + struct ff_effect *trigger_effect; + struct ff_effect upload_effect; + struct ff_effect *erase_effect; + s16 *raw_custom_data; + int raw_custom_data_len; + struct work_struct vibe_start_work; + struct work_struct vibe_stop_work; + struct work_struct set_gain_work; + struct work_struct upload_work; + struct work_struct erase_work; + struct workqueue_struct *vibe_workqueue; + int irq; + bool vibe_init_success; + int pseq_num_ops; + u32 pseq_base; + struct list_head pseq_op_head; + enum cs40l26_pm_state pm_state; + u32 fw_id; + bool fw_defer; + bool fw_rom_only; + bool fw_loaded; + bool calib_fw; + enum cs40l26_vibe_state vibe_state; + bool vibe_state_reporting; + bool asp_enable; + u8 last_wksrc_pol; + u8 wksrc_sts; + int num_owt_effects; + int cal_requested; + u16 gain_pct; + u16 gain_tmp; + bool scaling_applied; + u32 event_map_base; + struct cs40l26_svc_le **svc_le_vals; + int num_svc_le_vals; + u32 delay_before_stop_playback_us; + int upload_ret; + int erase_ret; + int effects_in_flight; + bool comp_enable_pend; + bool comp_enable_redc; + bool comp_enable_f0; + struct completion i2s_cont; + struct completion erase_cont; + struct completion cal_f0_cont; + struct completion cal_redc_cont; + struct completion cal_ls_cont; + struct completion cal_dvl_peq_cont; + unsigned int svc_le_est_stored; + u32 *no_wait_ram_indices; + ssize_t num_no_wait_ram_indices; + struct timer_list hibernate_timer; + ktime_t allow_hibernate_ts; + bool allow_hibernate_sent; + const struct cs40l26_rom_regs *rom_regs; +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + struct cs40l26_samsung_platform_data pdata; + unsigned int irq_gpio; + struct sec_vib_inputff_drvdata sec_vib_ddata; + u8 busy_state; + bool use_sep_index; +#endif +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs_root; + char *dbg_fw_ctrl_name; + u32 dbg_fw_algo_id; + bool dbg_fw_ym; + struct cl_dsp_debugfs *cl_dsp_db; +#endif + struct cs40l26_brwnout vbbr; + struct cs40l26_brwnout vpbr; + bool bst_dcm_en; + u32 bst_ipk; + u32 asp_scale_pct; + u32 pm_active_timeout_ms; + u32 pm_stdby_timeout_ms; + u32 f0_default; + u32 redc_default; + u32 q_default; + u32 bst_ctl; + bool expl_mode_enabled; + bool dbc_enable_default; + u32 dbc_defaults[CS40L26_DBC_NUM_CONTROLS]; + bool pwle_zero_cross; + u32 press_idx; + u32 release_idx; + u32 clip_lvl; + struct regmap_irq_chip_data *irq_data; + struct cs40l26_log_src *log_srcs; + u32 num_log_srcs; + u32 ng_thld; + u32 ng_delay; + bool ng_enable; + u32 aux_ng_thld; + u32 aux_ng_delay; + bool aux_ng_enable; +}; + +struct cs40l26_codec { + struct cs40l26_private *core; + struct device *dev; + struct regmap *regmap; + int sysclk_rate; + int tuning; + int tuning_prev; + char *bin_file; + u32 daifmt; + int tdm_width; + int tdm_slots; + int tdm_slot[2]; + bool dsp_bypass; +}; + +struct cs40l26_pll_sysclk_config { + u32 freq; + u8 clk_cfg; +}; + +/* exported function prototypes */ +int cs40l26_svc_le_estimate(struct cs40l26_private *cs40l26, unsigned int *le); +int cs40l26_set_pll_loop(struct cs40l26_private *cs40l26, unsigned int pll_loop); +int cs40l26_dbc_enable(struct cs40l26_private *cs40l26, u32 enable); +int cs40l26_dbc_get(struct cs40l26_private *cs40l26, enum cs40l26_dbc_type dbc, unsigned int *val); +int cs40l26_dbc_set(struct cs40l26_private *cs40l26, enum cs40l26_dbc_type dbc, u32 val); +int cs40l26_asp_start(struct cs40l26_private *cs40l26); +int cs40l26_get_num_waves(struct cs40l26_private *cs40l26, u32 *num_waves); +int cs40l26_fw_swap(struct cs40l26_private *cs40l26, const u32 id); +void cs40l26_vibe_state_update(struct cs40l26_private *cs40l26, + enum cs40l26_vibe_state_event event); +int cs40l26_pm_timeout_ms_set(struct cs40l26_private *cs40l26, unsigned int dsp_state, + u32 timeout_ms); +int cs40l26_pm_timeout_ms_get(struct cs40l26_private *cs40l26, unsigned int dsp_state, + u32 *timeout_ms); +int cs40l26_pm_state_transition(struct cs40l26_private *cs40l26, enum cs40l26_pm_state state); +int cs40l26_mailbox_write(struct cs40l26_private *cs40l26, u32 write_val); +int cs40l26_pm_enter(struct device *dev); +void cs40l26_pm_exit(struct device *dev); +void cs40l26_resume_error_handle(struct device *dev, int ret); +int cs40l26_resume(struct device *dev); +int cs40l26_sys_resume(struct device *dev); +int cs40l26_sys_resume_noirq(struct device *dev); +int cs40l26_suspend(struct device *dev); +int cs40l26_sys_suspend(struct device *dev); +int cs40l26_sys_suspend_noirq(struct device *dev); +int cs40l26_dsp_state_get(struct cs40l26_private *cs40l26, u8 *state); +int cs40l26_probe(struct cs40l26_private *cs40l26); +int cs40l26_remove(struct cs40l26_private *cs40l26); +bool cs40l26_precious_reg(struct device *dev, unsigned int ret); +bool cs40l26_readable_reg(struct device *dev, unsigned int reg); +bool cs40l26_volatile_reg(struct device *dev, unsigned int reg); +int cs40l26_pseq_write(struct cs40l26_private *cs40l26, u32 addr, u32 data, bool update, + u8 op_code); +int cs40l26_copy_f0_est_to_dvl(struct cs40l26_private *cs40l26); + +/* external tables */ +extern struct regulator_bulk_data cs40l26_supplies[CS40L26_NUM_SUPPLIES]; +extern const struct dev_pm_ops cs40l26_pm_ops; +extern const struct regmap_config cs40l26_regmap; +extern const struct mfd_cell cs40l26_devs[CS40L26_NUM_MFD_DEVS]; +extern const u8 cs40l26_pseq_op_sizes[CS40L26_PSEQ_NUM_OPS][2]; +extern const u32 cs40l26_attn_q21_2_vals[CS40L26_NUM_PCT_MAP_VALUES]; +extern const struct reg_sequence cs40l26_a1_errata[CS40L26_ERRATA_A1_NUM_WRITES]; +extern const struct cs40l26_dbc cs40l26_dbc_params[CS40L26_DBC_NUM_CONTROLS]; + +/* sysfs */ +extern struct attribute_group cs40l26_dev_attr_group; +extern struct attribute_group cs40l26_dev_attr_cal_group; +extern struct attribute_group cs40l26_dev_attr_dbc_group; + +/* debugfs */ +#ifdef CONFIG_DEBUG_FS +void cs40l26_debugfs_init(struct cs40l26_private *cs40l26); +void cs40l26_debugfs_cleanup(struct cs40l26_private *cs40l26); +#endif /* CONFIG_DEBUG_FS */ + +/* kunit test */ +#ifdef CONFIG_SEC_KUNIT +#include +#include +#include +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE +__visible_for_testing bool samsung_is_valid_vmon(struct cs40l26_private *cs40l26, u32 vmon); +#endif /* CONFIG_CS40L26_SAMSUNG_FEATURE */ +#endif /* CONFIG_SEC_KUNIT */ + +#endif /* __CS40L26_H__ */ diff --git a/include/linux/vibrator/sec_vibrator_inputff.h b/include/linux/vibrator/sec_vibrator_inputff.h new file mode 100644 index 000000000000..2a2d128090b7 --- /dev/null +++ b/include/linux/vibrator/sec_vibrator_inputff.h @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 Samsung Electronics Co. Ltd. + * + * 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. + */ + +/* sec vibrator inputff */ +#ifndef __SEC_VIBRATOR_INPUTFF_H__ +#define __SEC_VIBRATOR_INPUTFF_H__ + +#include +#include +#include +#include +#include + +#define VIB_NOTIFIER_ON (1) +#define FWLOAD_TRY (3) + +enum EVENT_CMD { + EVENT_CMD_NONE = 0, + EVENT_CMD_FOLDER_CLOSE, + EVENT_CMD_FOLDER_OPEN, + EVENT_CMD_ACCESSIBILITY_BOOST_ON, + EVENT_CMD_ACCESSIBILITY_BOOST_OFF, + EVENT_CMD_TENT_CLOSE, + EVENT_CMD_TENT_OPEN, + EVENT_CMD_MAX, +}; + +#define MAX_STR_LEN_VIB_TYPE 32 +#define MAX_STR_LEN_EVENT_CMD 32 +#define MIN_STR_LEN_EVENT_CMD 4 + +#define MAX_COMPOSE_EFFECT (32) + +#define SEC_VIBRATOR_INPUTFF_DEFAULT_HIGH_TEMP_REF INT_MAX +#define SEC_VIBRATOR_INPUTFF_DEFAULT_HIGH_TEMP_RATIO 100 + +#define VIB_FREE_DURATION 0 + +enum compose_thread_state { + COMPOSE_STOP = 0, + COMPOSE_RUN = 1, + COMPOSE_START = 2, + COMPOSE_EXIT = 3, +}; + +struct common_inputff_effect { + int type; + int effect_id; + int scale; + int duration; + int frequency; +}; + +struct common_inputff_effects { + struct common_inputff_effect effects[MAX_COMPOSE_EFFECT]; + int num_of_effects; + int repeat; +}; + +struct sec_vib_inputff_ops { + int (*upload)(struct input_dev *dev, + struct ff_effect *effect, struct ff_effect *old); + int (*erase)(struct input_dev *dev, int effect_id); + int (*playback)(struct input_dev *dev, + int effect_id, int val); + void (*set_gain)(struct input_dev *dev, u16 gain); + int (*get_i2c_test)(struct input_dev *dev); + int (*get_i2s_test)(struct input_dev *dev); + int (*fw_load)(struct input_dev *dev, unsigned int fw_id); + int (*get_ls_temp)(struct input_dev *dev, u32 *val); + int (*set_ls_temp)(struct input_dev *dev, u32 val); + int (*set_trigger_cal)(struct input_dev *dev, u32 val); + int (*get_ls_calib_res)(struct input_dev *dev, char *buf); + int (*set_ls_calib_res)(struct input_dev *dev, char *buf); + int (*get_ls_calib_res_name)(struct input_dev *dev, char *buf); + u32 (*get_f0_measured)(struct input_dev *dev); + int (*get_f0_offset)(struct input_dev *dev); + int (*set_f0_offset)(struct input_dev *dev, u32 val); + u32 (*get_f0_stored)(struct input_dev *dev); + u32 (*set_f0_stored)(struct input_dev *dev, u32 val); + int (*set_le_stored)(struct input_dev *dev, u32 val); + u32 (*get_le_stored)(struct input_dev *dev); + int (*get_le_est)(struct input_dev *dev, u32 *le); + int (*set_use_sep_index)(struct input_dev *dev, bool use_sep_index); + int (*get_lra_resistance)(struct input_dev *dev); + const char * (*get_owt_lib_compat_version)(struct input_dev *dev); + const char * (*get_ap_chipset)(struct input_dev *dev); +}; + +struct sec_vib_inputff_fwdata { + int id; + int retry; + int ret[FWLOAD_TRY]; + int stat; + struct workqueue_struct *fw_workqueue; + struct work_struct wk; + struct delayed_work retry_wk; + struct delayed_work store_wk; + struct wakeup_source ws; + struct mutex stat_lock; +}; + +struct sec_vib_inputff_compose_effects { + struct ff_effect compose_effects; + u16 gain; +}; + +struct sec_vib_inputff_compose { + struct task_struct *compose_thread; + struct kthread_worker kworker; + struct kthread_work kwork; + int thread_exit; + int thread_state; + wait_queue_head_t delay_wait; + int num_of_compose_effects; + int upload_compose_effect; + int compose_effect_id; + int upload_partial_effect; + int compose_repeat; +}; + +struct sec_vib_inputff_pdata { + bool probe_done; + int normal_ratio; + int overdrive_ratio; + int high_temp_ratio; + int high_temp_ref; +#if defined(CONFIG_SEC_VIB_FOLD_MODEL) + int fold_open_ratio; + int fold_close_ratio; + int tent_open_ratio; + int tent_close_ratio; + const char *fold_cmd; +#endif + const char *f0_cal_way; +}; + +struct sec_vib_inputff_drvdata { + struct class *sec_vib_inputff_class; + struct device *virtual_dev; + u32 devid : 24; + u8 revid; + u64 ff_val; + bool vibe_init_success; + struct device *dev; + struct input_dev *input; + struct attribute_group **vendor_dev_attr_groups; + struct attribute_group *sec_vib_attr_group; + const struct sec_vib_inputff_ops *vib_ops; + void *private_data; + int temperature; + int ach_percent; + u32 le_stored; + u32 f0_stored; + int support_fw; + int trigger_calibration; + struct sec_vib_inputff_fwdata fw; + bool is_ls_calibration; + bool is_f0_tracking; + bool is_le_support; + struct sec_vib_inputff_pdata *pdata; + + enum EVENT_CMD event_idx; + char event_cmd[MAX_STR_LEN_EVENT_CMD + 1]; + + bool use_common_inputff; + u16 effect_gain; + struct sec_vib_inputff_compose_effects effects[MAX_COMPOSE_EFFECT]; + struct sec_vib_inputff_compose compose; + + struct work_struct cal_work; + struct workqueue_struct *cal_workqueue; + + bool fw_init_attempted; +}; + +/* firmware load status. if fail, return err number */ +#define FW_LOAD_INIT (1<<0) +#define FW_LOAD_STORE (1<<1) +#define FW_LOAD_SUCCESS (1<<2) + +extern int sec_vib_inputff_notifier_register(struct notifier_block *nb); +extern int sec_vib_inputff_notifier_unregister(struct notifier_block *nb); +extern int sec_vib_inputff_notifier_notify(void); +extern int sec_vib_inputff_setbit(struct sec_vib_inputff_drvdata *ddata, int val); +extern int sec_vib_inputff_register(struct sec_vib_inputff_drvdata *ddata); +extern void sec_vib_inputff_unregister(struct sec_vib_inputff_drvdata *ddata); +extern int sec_vib_inputff_get_current_temp(struct sec_vib_inputff_drvdata *ddata); +extern int sec_vib_inputff_get_ach_percent(struct sec_vib_inputff_drvdata *ddata); +extern int sec_vib_inputff_tune_gain(struct sec_vib_inputff_drvdata *ddata, int gain); +#if defined(CONFIG_SEC_VIB_FOLD_MODEL) +extern void sec_vib_inputff_event_cmd(struct sec_vib_inputff_drvdata *ddata); +extern int set_fold_model_ratio(struct sec_vib_inputff_drvdata *ddata); +#endif +extern int sec_vib_inputff_sysfs_init(struct sec_vib_inputff_drvdata *ddata); +extern void sec_vib_inputff_sysfs_exit(struct sec_vib_inputff_drvdata *ddata); +#endif //__SEC_VIBRATOR_INPUTFF_H__ diff --git a/include/net/dropdump.h b/include/net/dropdump.h new file mode 100644 index 000000000000..aaf0ecaa7770 --- /dev/null +++ b/include/net/dropdump.h @@ -0,0 +1,174 @@ +#ifndef __NET_DROPDUMP_H +#define __NET_DROPDUMP_H + +#include +#include + +//#define DRD_WQ + +/* vendor driver couldn't be used by builtin, with GKI. + when using dropdump on GKI, check about that /trace/hoooks/net.h + otherwise, by builtin driver, include /net/dropdump.h at /net/dst.h */ + +/* add definition for logging */ +#define ETH_P_LOG 0x00FA + +#define SKB_CLONE 0 +#define SKB_STACK 1 +#define SKB_DUMMY 2 + +#define ST_MAX 20 +#define ST_SIZE 0x30 +#define ST_BUF_SIZE (ST_SIZE * ST_MAX) +#define ST_START 5 + +#define DEBUG_LOG 0x01 +#define DEBUG_TRACE 0x02 +#define DEBUG_HASH 0x04 +#define DEBUG_SAVE 0x10 +#define DEBUG_RESTORE 0x20 +#define DEBUG_RAW 0x40 + +#define PKTINFO_COPYLEN_MAX 0x100 +#define PKTINFO_OFFSET(skb) (*(u64 *)((u64)skb->data + skb->len - sizeof(u64))) + +#define LIMIT_DEPTH_BIT (1 << 0) +#define UPDATE_TIME_BIT (1 << 1) +#define RESTORE_FAIL_BIT (1 << 2) + +#define DRD_HSIZE 256 + +struct _dmy_info { + char magic[3]; + u8 depth; + u8 flag; + u8 reason_id; + u16 skip_count; + u64 count; + char reason_str[16]; + u64 stack; +} __packed; + +#ifdef DRD_WQ +#define BUDGET_DEFAULT 64 + +struct _drd_worker { + struct workqueue_struct *wq; + struct delayed_work dwork; + struct list_head list; + spinlock_t lock; + u64 num; +}; +#endif + +struct st_item { + struct list_head list; + u64 p; + u64 matched; + char st[ST_BUF_SIZE]; +}; + +#if IS_ENABLED(CONFIG_SUPPORT_DROPDUMP) +extern void trace_android_vh_ptype_head + (const struct packet_type *pt, struct list_head *vendor_pt); +extern void trace_android_vh_kfree_skb(struct sk_buff *skb); +#else +#define trace_android_vh_ptype_head(pt, vendor_pt) +#define trace_android_vh_kfree_skb(skb) +#endif + +const char *const drd_reasons[] = { +#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0)) + "UNUSED_KERN_VER", +}; +#define DRD_REASON_MAX 1 +#else + "NOT_SPECIFIED", + "NO_SOCKET", + "PKT_TOO_SMALL", + "TCP_CSUM", + "SOCKET_FILTER", + "UDP_CSUM", + "NETFILTER_DROP", + "OTHERHOST", + "IP_CSUM", + "IP_INHDR", + "IP_RPFILTER", + "UNICAST_IN_L2_MULTICAST", + "XFRM_POLICY", + "IP_NOPROTO", + "SOCKET_RCVBUFF", + "PROTO_MEM", + "TCP_MD5NOTFOUND", + "TCP_MD5UNEXPECTED", + "TCP_MD5FAILURE", + "SOCKET_BACKLOG", + "TCP_FLAGS", + "TCP_ZEROWINDOW", + "TCP_OLD_DATA", + "TCP_OVERWINDOW", + "TCP_OFOMERGE", + "TCP_RFC7323_PAWS", +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)) + "TCP_OLD_SEQUENCE", +#endif + "TCP_INVALID_SEQUENCE", + "TCP_RESET", + "TCP_INVALID_SYN", + "TCP_CLOSE", + "TCP_FASTOPEN", + "TCP_OLD_ACK", + "TCP_TOO_OLD_ACK", + "TCP_ACK_UNSENT_DATA", + "TCP_OFO_QUEUE_PRUNE", + "TCP_OFO_DROP", + "IP_OUTNOROUTES", + "BPF_CGROUP_EGRESS", + "IPV6DISABLED", + "NEIGH_CREATEFAIL", + "NEIGH_FAILED", + "NEIGH_QUEUEFULL", + "NEIGH_DEAD", + "TC_EGRESS", + "QDISC_DROP", + "CPU_BACKLOG", + "XDP", + "TC_INGRESS", + "UNHANDLED_PROTO", + "SKB_CSUM", + "SKB_GSO_SEG", + "SKB_UCOPY_FAULT", + "DEV_HDR", + "DEV_READY", + "FULL_RING", + "NOMEM", + "HDR_TRUNC", + "TAP_FILTER", + "TAP_TXFILTER", + "ICMP_CSUM", + "INVALID_PROTO", + "IP_INADDRERRORS", + "IP_INNOROUTES", + "PKT_TOO_BIG", +#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)) +}; +#define DRD_REASON_MAX 64 +#else + "DUP_FRAG", + "FRAG_REASM_TIMEOUT", + "FRAG_TOO_FAR", + "TCP_MINTTL", + "IPV6_BAD_EXTHDR", + "IPV6_NDISC_FRAG", + "IPV6_NDISC_HOP_LIMIT", + "IPV6_NDISC_BAD_CODE", + "IPV6_NDISC_BAD_OPTIONS", + "IPV6_NDISC_NS_OTHERHOST", + "QUEUE_PURGE", +}; +#define DRD_REASON_MAX 76 +#endif + +#endif + +#endif //__NET_DROPDUMP_H diff --git a/include/net/ncm.h b/include/net/ncm.h new file mode 100644 index 000000000000..b326afc6d33e --- /dev/null +++ b/include/net/ncm.h @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * + * Network Context Metadata Module[NCM]:Implementation. + * + * 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 . + */ + +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA { +#ifndef NCM_COMMON_H__ +#define NCM_COMMON_H__ + +#define NCM_VERSION 11 + +#define INIT_UID_NAP 0 +#define INIT_PID_NAP 1 +#define DNS_PORT_NAP 53 +#define IPV4_FAMILY_NAP 2 +#define IPV6_FAMILY_NAP 10 +#define INET6_ADDRSTRLEN_NAP 48 + +#define NCM_FLOW_TYPE_DEFAULT -1 +#define NCM_FLOW_TYPE_ALL 0 +#define NCM_FLOW_TYPE_OPEN 1 +#define NCM_FLOW_TYPE_CLOSE 2 +#define NCM_FLOW_TYPE_INTERMEDIATE 3 + +#include +#include +#include +#include +#include + +#define isIpv4AddressEqualsNull(srcaddr, dstaddr) ((((strcmp(srcaddr, "0.0.0.0")) || (strcmp(dstaddr, "0.0.0.0"))) == 0) ? 1 : 0) +#define isIpv6AddressEqualsNull(srcaddr, dstaddr) ((((strcmp(srcaddr, "0000:0000:0000:0000:0000:0000:0000:0000")) || (strcmp(dstaddr, "0000:0000:0000:0000:0000:0000:0000:0000"))) == 0) ? 1 : 0) + +/* Struct Socket definition */ +struct knox_socket_metadata { +/* The source port of the socket */ + __u16 srcport; +/* The destination port of the socket */ + __u16 dstport; +/* The Transport layer protocol of the socket*/ + __u16 trans_proto; +/* The number of application layer bytes sent by the socket */ + __u64 knox_sent; +/* The number of application layer bytes recieved by the socket */ + __u64 knox_recv; +/* The uid which created the socket */ + uid_t knox_uid; +/* The pid under which the socket was created */ + pid_t knox_pid; +/* The parent user id under which the socket was created */ + uid_t knox_puid; +/* The epoch time at which the socket was opened */ + __u64 open_time; +/* The epoch time at which the socket was closed */ + __u64 close_time; +/* The source address of the socket */ + char srcaddr[INET6_ADDRSTRLEN_NAP]; +/* The destination address of the socket */ + char dstaddr[INET6_ADDRSTRLEN_NAP]; +/* The name of the process which created the socket */ + char process_name[PROCESS_NAME_LEN_NAP]; +/* The name of the parent process which created the socket */ + char parent_process_name[PROCESS_NAME_LEN_NAP]; +/* The Domain name associated with the ip address of the socket. The size needs to be in sync with the userspace implementation */ + char domain_name[DOMAIN_NAME_LEN_NAP]; +/* The uid which originated the dns request */ + uid_t knox_uid_dns; +/* The parent process id under which the socket was created */ + pid_t knox_ppid; +/* The interface used by the flow to transmit packet */ + char interface_name[IFNAMSIZ]; +/* The flow type is used identify the current state of the network flow*/ + int flow_type; +/* The struct defined is responsible for inserting the socket meta-data into kfifo */ + struct work_struct work_kfifo; +}; + +/* Struct Socket definition */ +struct knox_user_socket_metadata { +/* The source port of the socket */ + __u16 srcport; +/* The destination port of the socket */ + __u16 dstport; +/* The Transport layer protocol of the socket*/ + __u16 trans_proto; +/* The number of application layer bytes sent by the socket */ + __u64 knox_sent; +/* The number of application layer bytes recieved by the socket */ + __u64 knox_recv; +/* The uid which created the socket */ + uid_t knox_uid; +/* The pid under which the socket was created */ + pid_t knox_pid; +/* The parent user id under which the socket was created */ + uid_t knox_puid; +/* The epoch time at which the socket was opened */ + __u64 open_time; +/* The epoch time at which the socket was closed */ + __u64 close_time; +/* The source address of the socket */ + char srcaddr[INET6_ADDRSTRLEN_NAP]; +/* The destination address of the socket */ + char dstaddr[INET6_ADDRSTRLEN_NAP]; +/* The name of the process which created the socket */ + char process_name[PROCESS_NAME_LEN_NAP]; +/* The name of the parent process which created the socket */ + char parent_process_name[PROCESS_NAME_LEN_NAP]; +/* The Domain name associated with the ip address of the socket. The size needs to be in sync with the userspace implementation */ + char domain_name[DOMAIN_NAME_LEN_NAP]; +/* The uid which originated the dns request */ + uid_t knox_uid_dns; +/* The parent process id under which the socket was created */ + pid_t knox_ppid; +/* The interface used by the flow to transmit packet */ + char interface_name[IFNAMSIZ]; +/* The flow type is used identify the current state of the network flow*/ + int flow_type; +}; + +/* The list of function which is being referenced */ +extern unsigned int check_ncm_flag(void); +extern void knox_collect_conntrack_data(struct nf_conn *ct, int startStop, int where); +extern bool kfifo_status(void); +extern void insert_data_kfifo_kthread(struct knox_socket_metadata* knox_socket_metadata); +extern unsigned int check_intermediate_flag(void); +extern unsigned int get_intermediate_timeout(void); + +/* Debug */ +#define NCM_DEBUG 1 +#if NCM_DEBUG +#define NCM_LOGD(...) printk("ncm: "__VA_ARGS__) +#else +#define NCM_LOGD(...) +#endif /* NCM_DEBUG */ +#define NCM_LOGE(...) printk("ncm: "__VA_ARGS__) + +/* IOCTL definitions*/ +#define __NCMIOC 0x120 +#define NCM_ACTIVATED_OPEN _IO(__NCMIOC, 2) +#define NCM_DEACTIVATED _IO(__NCMIOC, 4) +#define NCM_ACTIVATED_CLOSE _IO(__NCMIOC, 8) +#define NCM_ACTIVATED_ALL _IO(__NCMIOC, 16) +#define NCM_GETVERSION _IO(__NCMIOC, 32) +#define NCM_MATCH_VERSION _IO(__NCMIOC, 64) + +#endif +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA } diff --git a/include/net/netfilter/nf_conntrack.h b/include/net/netfilter/nf_conntrack.h index ca124b19de34..f80b2b5c7e81 100644 --- a/include/net/netfilter/nf_conntrack.h +++ b/include/net/netfilter/nf_conntrack.h @@ -26,6 +26,14 @@ #include + +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA { +#ifdef CONFIG_KNOX_NCM +#define PROCESS_NAME_LEN_NAP 128 +#define DOMAIN_NAME_LEN_NAP 255 +#endif +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA } + struct nf_ct_udp { unsigned long stream_ts; }; @@ -74,6 +82,29 @@ struct nf_conntrack_net { #include #include +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA { +#ifdef CONFIG_KNOX_NCM +struct nf_conn_npa_vendor_data { + __u64 knox_sent; + __u64 knox_recv; + uid_t knox_uid; + pid_t knox_pid; + uid_t knox_puid; + __u64 open_time; + char process_name[PROCESS_NAME_LEN_NAP]; + char parent_process_name[PROCESS_NAME_LEN_NAP]; + char domain_name[DOMAIN_NAME_LEN_NAP]; + pid_t knox_ppid; + char interface_name[IFNAMSIZ]; + atomic_t startFlow; + u32 npa_timeout; + atomic_t intermediateFlow; +}; + +#define NF_CONN_NPA_VENDOR_DATA_GET(nf_conn) ((struct nf_conn_npa_vendor_data*)((nf_conn)->android_oem_data1)) +#endif +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA } + struct nf_conn { /* Usage count in here is 1 for hash table, 1 per skb, * plus 1 for any connection(s) we are `master' for diff --git a/include/net/sock.h b/include/net/sock.h index 104128387226..1d688af22e1d 100644 --- a/include/net/sock.h +++ b/include/net/sock.h @@ -72,6 +72,13 @@ #include #include +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA { +#ifdef CONFIG_KNOX_NCM +#define NAP_PROCESS_NAME_LEN 128 +#define NAP_DOMAIN_NAME_LEN 255 +#endif +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA } + /* * This structure really needs to be cleaned up. * Most of it is for TCP, and not used by any of @@ -249,6 +256,25 @@ struct sock_common { struct bpf_local_storage; struct sk_filter; +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA { +#ifdef CONFIG_KNOX_NCM +struct sock_npa_vendor_data { + uid_t knox_uid; + pid_t knox_pid; + uid_t knox_dns_uid; + char domain_name[NAP_DOMAIN_NAME_LEN]; + char process_name[NAP_PROCESS_NAME_LEN]; + uid_t knox_puid; + pid_t knox_ppid; + char parent_process_name[NAP_PROCESS_NAME_LEN]; + pid_t knox_dns_pid; + char dns_process_name[NAP_PROCESS_NAME_LEN]; +}; + +#define SOCK_NPA_VENDOR_DATA_GET(sock) ((struct sock_npa_vendor_data*)((sock)->android_oem_data1)) +#endif +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA } + /** * struct sock - network layer representation of sockets * @__sk_common: shared layout with inet_timewait_sock diff --git a/include/sound/cirrus/big_data.h b/include/sound/cirrus/big_data.h new file mode 100644 index 000000000000..1cad35381aaf --- /dev/null +++ b/include/sound/cirrus/big_data.h @@ -0,0 +1,34 @@ +/* + * big_data.h -- Big Data defines for Cirrus Logic Smart Amplifiers + * + * Copyright 2017 Cirrus Logic + * + * Author: David Rhodes + * + * 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. + */ + +/* These values are specific to Playback 5.00.5 */ + +#define CIRRUS_BD_MAX_TEMP 0x28003a4 +#define CIRRUS_BD_MAX_EXC 0x28003a8 +#define CIRRUS_BD_OVER_TEMP_COUNT 0x28003ac +#define CIRRUS_BD_OVER_EXC_COUNT 0x28003b0 +#define CIRRUS_BD_ABNORMAL_MUTE 0x28003b4 + +#define CIRRUS_BD_ERR_TEMP 2472346 + +#define CIRRUS_BD_TEMP_RADIX 14 +#define CIRRUS_BD_EXC_RADIX 19 + +#define CIRRUS_BD_NUM_ATTRS_BASE 1 +#define CIRRUS_BD_NUM_ATTRS_AMP 7 + +void cirrus_bd_amp_err(const char *mfd_suffix); +void cirrus_bd_bst_short(const char *mfd_suffix); + +void cirrus_bd_store_values(const char *mfd_suffix); +int cirrus_bd_init(void); +void cirrus_bd_exit(void); diff --git a/include/sound/cirrus/calibration.h b/include/sound/cirrus/calibration.h new file mode 100644 index 000000000000..cd0a6e5436f4 --- /dev/null +++ b/include/sound/cirrus/calibration.h @@ -0,0 +1,84 @@ +/* + * calibration.h -- Calibration defines for Cirrus Logic CS35L41 codec + * + * Copyright 2017 Cirrus Logic + * + * Author: David Rhodes + * + * 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. + */ + +#include +#include + + + +#define CS35L41_NG_ENABLE_MASK 0x00010000 + +#define CSPL_STATE_RUNNING 0x00000000 +#define CSPL_STATE_ERROR 0x00000001 + +#define CS35L41_VBST_CTL_11 0xAA +#define CIRRUS_CAL_CLASSH_DELAY_50MS 0x32 +#define CIRRUS_CAL_CLASSD_DELAY_50MS 0x32 + +#define CIRRUS_CAL_VIMON_STATUS_INVALID 1 +#define CIRRUS_CAL_VIMON_STATUS_SUCCESS 2 + +#define CSPL_STATUS_OUT_OF_RANGE 0x00000003 +#define CSPL_STATUS_INCOMPLETE 0x00000002 + +#define CIRRUS_CAL_AMP_CONSTANT 5.85714 +#define CIRRUS_CAL_AMP_CONSTANT_NUM 292857 +#define CIRRUS_CAL_AMP_CONSTANT_DENOM 50000 +#define CIRRUS_CAL_RDC_RADIX 13 + +#define CIRRUS_CAL_RDC_DEFAULT 8580 + +#define CIRRUS_CAL_VFS_MV 12300 +#define CIRRUS_CAL_IFS_MA 2100 + +#define CIRRUS_CAL_V_VAL_UB_MV 2000 +#define CIRRUS_CAL_V_VAL_LB_MV 50 + +#define CIRRUS_CAL_VIMON_CAL_VSC_UB 0x00010624 +#define CIRRUS_CAL_VIMON_CAL_VSC_LB 0x00FEF9DC +#define CIRRUS_CAL_VIMON_CAL_ISC_UB 0x00004189 +#define CIRRUS_CAL_VIMON_CAL_ISC_LB 0x00FFBE77 + +#define CS35L41_CAL_RTLOG_ID_V_PEAK 947 +#define CS35L41_CAL_RTLOG_ID_I_PEAK 948 +#define CIRRUS_CAL_RTLOG_ID_V_PEAK 1064 +#define CIRRUS_CAL_RTLOG_ID_I_PEAK 1065 +#define CIRRUS_CAL_RTLOG_ID_TEMP 111 +#define CIRRUS_CAL_RTLOG_RADIX_TEMP 14 + +#define CS35L41_MPU_UNLOCK_CODE_0 0x5555 +#define CS35L41_MPU_UNLOCK_CODE_1 0xaaaa + +#ifdef CONFIG_SND_SOC_CIRRUS_REINIT_SYSFS +#define CIRRUS_CAL_NUM_ATTRS_BASE 4 +#else +#define CIRRUS_CAL_NUM_ATTRS_BASE 3 +#endif + +#define CIRRUS_CAL_NUM_ATTRS_AMP 7 + +extern struct cirrus_cal_ops cirrus_cspl_cal_ops; +extern struct cirrus_cal_ops cirrus_cs35l43_cal_ops; + +/* needs to match ops container struct in cirrus-amp.c */ +enum cirrus_cal_ops_idx { + CIRRUS_CAL_OPS_INVALID, + CIRRUS_CAL_CSPL_CAL_OPS_IDX, + CIRRUS_CAL_CS35L43_CAL_OPS_IDX, +}; + +int cirrus_cal_read_temp(const char *mfd_suffix); +int cirrus_cal_apply(const char *mfd_suffix); +int cirrus_cal_set_surface_temp(const char *suffix, int temperature); +int cirrus_cal_init(void); +void cirrus_cal_exit(void); + diff --git a/include/sound/cirrus/core.h b/include/sound/cirrus/core.h new file mode 100644 index 000000000000..0ead5dbf66af --- /dev/null +++ b/include/sound/cirrus/core.h @@ -0,0 +1,199 @@ +/* + * core.h -- MFD includes for Cirrus Logic Smart Amplifiers + * + * Copyright 2017 Cirrus Logic + * + * Author: David Rhodes + * + * 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. + */ + +#ifndef CIRRUS_MFD_CORE_H +#define CIRRUS_MFD_CORE_H + +#include +#include + +#define CIRRUS_MAX_AMPS 8 + +#define CIRRUS_AMP_CTL_RETRY 5 + +#define CS35L41_ALG_ID_HALO 0x400a4 +#define CIRRUS_AMP_ALG_ID_HALO 0x4fa00 +#define CIRRUS_AMP_ALG_ID_CSPL 0xcd + +extern struct class *cirrus_amp_class; + +struct cirrus_amp; + +struct cs35l41_data { + struct cs35l41_platform_data *pdata; + struct device *dev; + struct regmap *regmap; + struct class *mfd_class; + struct regulator_bulk_data supplies[2]; + struct gpio_desc *reset_gpio; + int num_supplies; + int irq; +}; + +struct cirrus_cal_control { + const char *name; + int alg_id; +}; + +struct cirrus_cal_controls { + struct cirrus_cal_control cal_r; + struct cirrus_cal_control cal_checksum; + struct cirrus_cal_control cal_set_status; +}; + +struct cirrus_cal_ops { + struct cirrus_cal_controls controls; + int (*cal_start)(void); + void (*cal_complete)(void); + int (*v_val)(struct cirrus_amp *amps, int num_amps, bool separate); + int (*cal_apply)(struct cirrus_amp *amp); + int (*read_temp)(struct cirrus_amp *amp); + int (*set_temp)(struct cirrus_amp *amp, int temperature); +}; + +struct cirrus_amp_config { + struct snd_soc_component *component; + struct regmap *regmap; + struct reg_sequence *pre_config; + struct reg_sequence *post_config; + int cal_ops_idx; + const char *dsp_part_name; + const char *bd_prefix; + unsigned int num_pre_configs; + unsigned int num_post_configs; + unsigned int mbox_cmd; + unsigned int mbox_sts; + unsigned int global_en; + unsigned int global_en_mask; + unsigned int vimon_alg_id; + unsigned int halo_alg_id; + unsigned int bd_alg_id; + unsigned int bd_max_temp; + unsigned int target_temp; + unsigned int exit_temp; + unsigned int default_redc; + unsigned int cal_vpk_id; + unsigned int cal_ipk_id; + unsigned int cal_vsc_ub; + unsigned int cal_vsc_lb; + unsigned int cal_isc_ub; + unsigned int cal_isc_lb; + bool perform_vimon_cal; + bool calibration_disable; + bool pwr_enable; + int (*amp_reinit)(struct snd_soc_component *component); + bool runtime_pm; +}; + +struct cirrus_bd { + const char *bd_suffix; + const char *bd_prefix; + unsigned int max_exc; + unsigned int over_exc_count; + unsigned int max_temp; + unsigned int max_temp_keep; + unsigned int over_temp_count; + unsigned int abnm_mute; + int max_temp_limit; + int bd_alg_id; +}; + +struct cirrus_cal { + unsigned int efs_cache_rdc; + unsigned int efs_cache_vsc; + unsigned int efs_cache_isc; + unsigned int v_validation; + unsigned int dsp_input1_cache; + unsigned int dsp_input2_cache; + int efs_cache_valid; +}; + +struct cirrus_pwr { + unsigned int target_temp; + unsigned int exit_temp; + unsigned int amb_temp; + unsigned int spk_temp; + unsigned int passport_enable; + bool amp_active; +}; + +struct cirrus_amp { + struct regmap *regmap; + struct snd_soc_component *component; + struct cirrus_bd bd; + struct cirrus_cal cal; + struct cirrus_pwr pwr; + struct reg_sequence *pre_config; + struct reg_sequence *post_config; + struct cirrus_cal_ops *cal_ops; + const char *dsp_part_name; + const char *mfd_suffix; + unsigned int num_pre_configs; + unsigned int num_post_configs; + unsigned int mbox_cmd; + unsigned int mbox_sts; + unsigned int global_en; + unsigned int global_en_mask; + unsigned int vimon_alg_id; + unsigned int halo_alg_id; + unsigned int default_redc; + unsigned int cal_vpk_id; + unsigned int cal_ipk_id; + unsigned int cal_vsc_ub; + unsigned int cal_vsc_lb; + unsigned int cal_isc_ub; + unsigned int cal_isc_lb; + int index; + bool perform_vimon_cal; + bool calibration_disable; + bool v_val_separate; + bool runtime_pm; + int (*amp_reinit)(struct snd_soc_component *component); + void (*i2c_callback)(const char *suffix); + void (*error_callback)(const char *suffix); +}; + +struct cirrus_amp_group { + struct device *bd_dev; + struct device *cal_dev; + struct device *pwr_dev; + struct mutex cal_lock; + struct mutex pwr_lock; + struct delayed_work cal_complete_work; + struct delayed_work pwr_work; + struct workqueue_struct *pwr_workqueue; + unsigned long long last_bd_update; + unsigned int efs_cache_temp; + unsigned int uptime_ms; + unsigned int interval; + unsigned int status; + unsigned int target_min_time_ms; + unsigned int pwr_enable; + bool cal_running; + int cal_retry; + unsigned int num_amps; + struct cirrus_amp amps[]; +}; + +void cirrus_amp_register_i2c_error_callback(const char *suffix, void *func); +void cirrus_amp_register_error_callback(const char *suffix, void *func); + +struct cirrus_amp *cirrus_get_amp_from_suffix(const char *suffix); +int cirrus_amp_add(const char *mfd_suffix, struct cirrus_amp_config cfg); +int cirrus_amp_read_ctl(struct cirrus_amp *amp, const char *name, + int type, unsigned int id, unsigned int *value); +int cirrus_amp_write_ctl(struct cirrus_amp *amp, const char *name, + int type, unsigned int id, unsigned int value); + +extern struct cirrus_amp_group *amp_group; + +#endif diff --git a/include/sound/cirrus/power.h b/include/sound/cirrus/power.h new file mode 100644 index 000000000000..4b2d41f2bccf --- /dev/null +++ b/include/sound/cirrus/power.h @@ -0,0 +1,22 @@ +/* + * power.h - Power-management defines for Cirrus Logic Smart Amplifiers + * + * Copyright 2018 Cirrus Logic + * + * Author: David Rhodes + * + * 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. + */ + +#define CIRRUS_PWR_CSPL_PASSPORT_ENABLE 0x28003b8 +#define CIRRUS_PWR_CSPL_OUTPUT_POWER_SQ 0x28003bc + +#define CIRRUS_PWR_NUM_ATTRS_BASE 6 +#define CIRRUS_PWR_NUM_ATTRS_AMP 5 + +void cirrus_pwr_start(const char *mfd_suffix); +void cirrus_pwr_stop(const char *mfd_suffix); +int cirrus_pwr_init(void); +void cirrus_pwr_exit(void); diff --git a/include/sound/cs35l43.h b/include/sound/cs35l43.h new file mode 100644 index 000000000000..8fa82bf4d734 --- /dev/null +++ b/include/sound/cs35l43.h @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * linux/sound/cs35l43.h -- Platform data for CS35L43 + * + * Copyright (c) 2021 Cirrus Logic 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 + * published by the Free Software Foundation. + */ + +#ifndef __CS35L43_H +#define __CS35L43_H + +#define CS35L43_REGMAP_RETRY 5 +#define CS35L43_IRQ_UNHANDLED_ALERT_INTERVAL_MS 300 +#define CS35L43_IRQ_UNHANDLED_ALERT_THRESH 15 + +#define CS35L43_RX_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) +#define CS35L43_TX_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE \ + | SNDRV_PCM_FMTBIT_S32_LE) + +#define CS35L43_VALID_PDATA 0x80000000 +#define CS35L43_NUM_DEFAULTS 41 + +struct cs35l43_platform_data { + bool gpio1_out_enable; + bool gpio2_out_enable; + bool classh_disable; + bool dsp_ng_enable; + bool vpbr_enable; + int asp_sdout_hiz; + int dsp_ng_pcm_thld; + int dsp_ng_delay; + int dout_hiz; + int bst_vctrl; + int bst_ipk; + int hw_ng_sel; + int hw_ng_delay; + int hw_ng_thld; + int gpio1_src_sel; + int gpio2_src_sel; + int vpbr_rel_rate; + int vpbr_wait; + int vpbr_atk_rate; + int vpbr_atk_vol; + int vpbr_max_att; + int vpbr_thld; + const char *dsp_part_name; + const char *mfd_suffix; +}; + +struct cs35l43_pll_sysclk_config { + int freq; + int clk_cfg; +}; + +struct cs35l43_fs_mon_config { + int freq; + unsigned int fs1; + unsigned int fs2; +}; + +extern const struct cs35l43_pll_sysclk_config cs35l43_pll_sysclk[64]; +extern const struct cs35l43_fs_mon_config cs35l43_fs_mon[7]; +extern const unsigned int cs35l43_hibernate_update_regs[CS35L43_POWER_SEQ_LENGTH]; +extern const u8 cs35l43_write_seq_op_sizes[CS35L43_POWER_SEQ_NUM_OPS][2]; + +enum cs35l43_hibernate_state { + CS35L43_HIBERNATE_AWAKE = 0, + CS35L43_HIBERNATE_STANDBY = 1, + CS35L43_HIBERNATE_UPDATE = 2, + CS35L43_HIBERNATE_NOT_LOADED = 3, + CS35L43_HIBERNATE_DISABLED = 4, +}; + +struct cs35l43_write_seq_elem { + u8 size; + u16 offset; /* offset in words from pseq_base */ + u8 operation; + u32 *words; + struct list_head list; +}; + +struct cs35l43_write_seq { + const char *name; + struct list_head list_head; + unsigned int num_ops; + unsigned int length; +}; + +struct cs35l43_dsp_reg { + const char *name; + unsigned int id; + unsigned int check_value; +}; + +enum cs35l43_hibernate_mode { + CS35L43_ULTRASONIC_MODE_DISABLED = 0, + CS35L43_ULTRASONIC_MODE_INBAND = 1, + CS35L43_ULTRASONIC_MODE_OUT_OF_BAND = 2, +}; + +enum cs35l43_low_pwr_mode { + CS35L43_LOW_PWR_MODE_HIBERNATE = 0, + CS35L43_LOW_PWR_MODE_STANDBY = 1, +}; + +struct cs35l43_private { + struct wm_adsp dsp; /* needs to be first member */ + struct snd_soc_component *component; + struct cs35l43_platform_data pdata; + struct device *dev; + struct regmap *regmap; + struct regulator_bulk_data supplies[2]; + int num_supplies; + int irq; + int extclk_cfg; + int clk_id; + int lrclk_fmt; + int sclk_fmt; + int asp_fmt; + int hibernate_state; + int hibernate_delay_ms; + int ultrasonic_mode; + int slot_width; + int amp_switch; + int delta_requested; + int delta_applied; + int low_pwr_mode; + int pcm_vol; + int amp_mute; + int mbox_err_pmu; + int mbox_err_pmd; + int irq_unhandled_events; + bool first_event; + bool write_seq_initialized; + bool pcm_muted; + unsigned int max_spi_freq; + struct gpio_desc *reset_gpio; + struct mutex hb_lock; + struct mutex err_lock; + struct workqueue_struct *err_wq; + struct work_struct err_work; + struct workqueue_struct *mbox_wq; + struct work_struct mbox_work; + struct workqueue_struct *irq_wq; + struct delayed_work irq_work; + struct cs35l43_write_seq power_on_seq; + void (*limit_spi_clock)(struct cs35l43_private *cs35l43, bool state); +}; + +int cs35l43_reinit(struct snd_soc_component *component); + +int cs35l43_probe(struct cs35l43_private *cs35l43, + struct cs35l43_platform_data *pdata); +int cs35l43_remove(struct cs35l43_private *cs35l43); + +bool cs35l43_readable_reg(struct device *dev, unsigned int reg); +bool cs35l43_precious_reg(struct device *dev, unsigned int reg); +bool cs35l43_volatile_reg(struct device *dev, unsigned int reg); + +/* Power management */ +#define CS35L43_PM_AUTOSUSPEND_DELAY_MS 150 +int cs35l43_suspend_runtime(struct device *dev); +int cs35l43_resume_runtime(struct device *dev); + +int cs35l43_sys_suspend(struct device *dev); +int cs35l43_sys_suspend_noirq(struct device *dev); +int cs35l43_sys_resume(struct device *dev); +int cs35l43_sys_resume_noirq(struct device *dev); + +extern const struct dev_pm_ops cs35l43_pm_ops; +extern const struct reg_default cs35l43_reg[CS35L43_NUM_DEFAULTS]; + +#endif /* __CS35L43_H */ diff --git a/include/sound/samsung/bigdata_cirrus_sysfs_cb.h b/include/sound/samsung/bigdata_cirrus_sysfs_cb.h new file mode 100644 index 000000000000..29a9efffc641 --- /dev/null +++ b/include/sound/samsung/bigdata_cirrus_sysfs_cb.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#ifndef _BIGDATA_CS35L41_SYSFS_CB_H +#define _BIGDATA_CS35L41_SYSFS_CB_H + +void register_cirrus_bigdata_cb(struct snd_soc_component *component); + +#endif /*_BIGDATA_CS35L41_SYSFS_CB_H */ diff --git a/include/sound/samsung/sec_audio_sysfs.h b/include/sound/samsung/sec_audio_sysfs.h new file mode 100644 index 000000000000..6aa0b7d70791 --- /dev/null +++ b/include/sound/samsung/sec_audio_sysfs.h @@ -0,0 +1,150 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#ifndef _SEC_AUDIO_SYSFS_H +#define _SEC_AUDIO_SYSFS_H + +/* + * 1 AMP project - mono(AMP_0) + * 2 AMP project - L(AMP_0), R(AMP_1) + * 3 AMP project - L(AMP_0), R(AMP_1), receiver(AMP_2) + * 4 AMP project - FL(AMP_0), FR(AMP_1), RL(AMP_2), RR(AMP_3) + */ +enum amp_id { + AMP_0, + AMP_1, + AMP_2, + AMP_3, + AMP_ID_MAX, +}; + +struct sec_audio_sysfs_data { + struct class *audio_class; + struct device *jack_dev; + struct device *codec_dev; + struct device *amp_dev; + struct device *adsp_dev; + bool no_earjack; + int (*get_jack_state)(void); + int (*get_key_state)(void); + int (*set_jack_state)(int); + int (*get_mic_adc)(void); + int (*get_codec_id_state)(void); + int (*set_force_enable_antenna)(int); + int (*get_antenna_state)(void); + + /* bigdata */ + unsigned int num_amp; + int (*get_amp_temperature_max)(enum amp_id); + int (*get_amp_temperature_keep_max)(enum amp_id); + int (*get_amp_temperature_overcount)(enum amp_id); + int (*get_amp_excursion_max)(enum amp_id); + int (*get_amp_excursion_overcount)(enum amp_id); + int (*get_amp_curr_temperature)(enum amp_id); + int (*set_amp_surface_temperature)(enum amp_id, int); + int (*get_amp_ready)(enum amp_id); +}; + +#if IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_AUDIO) +int audio_register_jack_select_cb(int (*set_jack) (int)); +int audio_register_jack_state_cb(int (*jack_status) (void)); +int audio_register_key_state_cb(int (*key_state) (void)); +int audio_register_mic_adc_cb(int (*mic_adc) (void)); +int audio_register_codec_id_state_cb(int (*codec_id_state) (void)); +int audio_register_force_enable_antenna_cb(int (*force_enable_antenna) (int)); +int audio_register_antenna_state_cb(int (*antenna_state) (void)); + +/* bigdata */ +int audio_register_temperature_max_cb(int (*temperature_max) (enum amp_id)); +int audio_register_temperature_keep_max_cb(int (*temperature_keep_max) (enum amp_id)); +int audio_register_temperature_overcount_cb(int (*temperature_overcount) (enum amp_id)); +int audio_register_excursion_max_cb(int (*excursion_max) (enum amp_id)); +int audio_register_excursion_overcount_cb(int (*excursion_overcount) (enum amp_id)); +int audio_register_curr_temperature_cb(int (*curr_temperature) (enum amp_id)); +int audio_register_surface_temperature_cb(int (*surface_temperature) (enum amp_id, int temperature)); +int audio_register_ready_cb(int (*ready) (enum amp_id)); +void send_adsp_silent_reset_ev(void); +#else +inline int audio_register_jack_select_cb(int (*set_jack) (int)) +{ + return -EACCES; +} + +inline int audio_register_jack_state_cb(int (*jack_status) (void)) +{ + return -EACCES; +} + +inline int audio_register_key_state_cb(int (*key_state) (void)) +{ + return -EACCES; +} + +inline int audio_register_mic_adc_cb(int (*mic_adc) (void)) +{ + return -EACCES; +} + +inline int audio_register_codec_id_state_cb(int (*codec_id_state) (void)) +{ + return -EACCES; +} + +inline int audio_register_force_enable_antenna_cb(int (*force_enable_antenna) (int)) +{ + return -EACCES; +} + +inline int audio_register_antenna_state_cb(int (*antenna_state) (void)) +{ + return -EACCES; +} + +inline int audio_register_temperature_max_cb(int (*temperature_max) (enum amp_id)) +{ + return -EACCES; +} + +inline int audio_register_temperature_keep_max_cb(int (*temperature_keep_max) (enum amp_id)) +{ + return -EACCES; +} + +inline int audio_register_temperature_overcount_cb(int (*temperature_overcount) (enum amp_id)) +{ + return -EACCES; +} + +inline int audio_register_excursion_max_cb(int (*excursion_max) (enum amp_id)) +{ + return -EACCES; +} + +inline int audio_register_excursion_overcount_cb(int (*excursion_overcount) (enum amp_id)) +{ + return -EACCES; +} + +inline int audio_register_curr_temperature_cb(int (*curr_temperature) (enum amp_id)) +{ + return -EACCES; +} + +inline int audio_register_surface_temperature_cb(int (*surface_temperature) (enum amp_id, int temperature)) +{ + return -EACCES; +} + +inline int audio_register_ready_cb(int (*ready) (enum amp_id)) +{ + return -EACCES; +} + +inline void send_adsp_silent_reset_ev(void) +{ +} +#endif + +#endif /* _SEC_AUDIO_SYSFS_H */ diff --git a/include/sound/samsung/snd_debug_proc.h b/include/sound/samsung/snd_debug_proc.h new file mode 100644 index 000000000000..4d5ab933fab0 --- /dev/null +++ b/include/sound/samsung/snd_debug_proc.h @@ -0,0 +1,74 @@ + +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * snd_debug_proc.h - header for SAMSUNG Audio debugging. + */ + +#ifndef _SND_DEBUG_PROC_H +#define _SND_DEBUG_PROC_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_SEC_KUNIT) +#include +#include +#else +#define __visible_for_testing static +#endif + +#define AUD_LOG_BUF_SIZE SZ_64K +#define MAX_LOG_LINE_LEN 256 + +#define PROC_SDP_DIR "snd_debug_proc" +#define SDP_INFO_LOG_NAME "sdp_info_log" +#define SDP_BOOT_LOG_NAME "sdp_boot_log" + +struct snd_debug_proc { + char log_buf[AUD_LOG_BUF_SIZE]; + bool is_enabled; + unsigned int buf_pos; + unsigned int buf_full; + struct mutex lock; + void (*save_log)(char *buf, int len); +}; + +#if IS_ENABLED(CONFIG_SND_SOC_SAMSUNG_AUDIO) +void sdp_info_print(const char *fmt, ...); +void sdp_boot_print(const char *fmt, ...); +struct snd_debug_proc *get_sdp_info(void); +struct snd_debug_proc *get_sdp_boot(void); +#else +inline void sdp_info_print(const char *fmt, ...) +{ +} + +inline void sdp_boot_print(const char *fmt, ...) +{ +} + +inline struct snd_debug_proc *get_sdp_info(void) +{ + return NULL; +} + +inline struct snd_debug_proc *get_sdp_boot(void) +{ + return NULL; +} +#endif + +#endif + diff --git a/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h index 638230899e98..1b4746289dd9 100644 --- a/include/uapi/asm-generic/socket.h +++ b/include/uapi/asm-generic/socket.h @@ -119,6 +119,11 @@ #define SO_DETACH_REUSEPORT_BPF 68 +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA { +#define SO_SET_DOMAIN_NAME 1000 +#define SO_SET_DNS_UID 1001 +#define SO_SET_DNS_PID 1002 +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA } #define SO_PREFER_BUSY_POLL 69 #define SO_BUSY_POLL_BUDGET 70 diff --git a/include/uapi/linux/if_tun.h b/include/uapi/linux/if_tun.h index b6d7b868f290..5c20ddf298bd 100644 --- a/include/uapi/linux/if_tun.h +++ b/include/uapi/linux/if_tun.h @@ -91,6 +91,15 @@ #define TUN_F_TSO_ECN 0x08 /* I can handle TSO with ECN bits. */ #define TUN_F_UFO 0x10 /* I can handle UFO packets */ +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN { +#define TUN_META_HDR 0x0020 +#define TUNGETMETAPARAM _IOR('T', 218, int) +#define IFF_META_HDR 0x0004 +#define TUN_GET_META_HDR_SZ 0 +#define TUN_GET_META_MARK_OFFSET 1 +#define DEFAULT_IHL 5 +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN } + /* Protocol info prepended to the packets (when IFF_NO_PI is not set) */ #define TUN_PKT_STRIP 0x0001 struct tun_pi { diff --git a/include/uapi/linux/ipa_qmi_service_v01.h b/include/uapi/linux/ipa_qmi_service_v01.h new file mode 100644 index 000000000000..b962654e1158 --- /dev/null +++ b/include/uapi/linux/ipa_qmi_service_v01.h @@ -0,0 +1,3236 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * Copyright (c) 2013-2021, The Linux Foundation. All rights reserved. + * + * Copyright (c) 2021-2023, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +/* + * This header file defines the types and structures that were defined in + * ipa. It contains the constant values defined, enums, structures, + * messages, and service message IDs (in that order) Structures that were + * defined in the IDL as messages contain mandatory elements, optional + * elements, a combination of mandatory and optional elements (mandatory + * always come before optionals in the structure), or nothing (null message) + + * An optional element in a message is preceded by a __u8 value that must be + * set to true if the element is going to be included. When decoding a received + * message, the __u8 values will be set to true or false by the decode + * routine, and should be checked before accessing the values that they + * correspond to. + + * Variable sized arrays are defined as static sized arrays with an unsigned + * integer (32 bit) preceding it that must be set to the number of elements + * in the array that are valid. For Example: + + * __u32 test_opaque_len; + * __u8 test_opaque[16]; + + * If only 4 elements are added to test_opaque[] then test_opaque_len must be + * set to 4 before sending the message. When decoding, the _len value is set + * by the decode routine and should be checked so that the correct number of + * elements in the array will be accessed. + */ +#ifndef IPA_QMI_SERVICE_V01_H +#define IPA_QMI_SERVICE_V01_H + +#include + +#define QMI_IPA_REMOTE_MHI_CHANNELS_NUM_MAX_V01 6 +#define QMI_IPA_MAX_FILTERS_EX_V01 128 +#define QMI_IPA_MAX_FILTERS_EX2_V01 256 +#define QMI_IPA_IPFLTR_NUM_IHL_RANGE_16_EQNS_V01 2 +#define QMI_IPA_MAX_FILTERS_V01 64 +#define QMI_IPA_IPFLTR_NUM_MEQ_128_EQNS_V01 2 +#define QMI_IPA_MAX_IPV4_ADD_LEN_V01 34 +#define QMI_IPA_MAX_IPV6_ADD_LEN_V01 35 +#define QMI_IPA_IPV6_WORD_ADDR_LEN_V01 4 +#define QMI_IPA_MAX_ETH_HDR_SIZE_V01 64 +#define QMI_IPA_ENDP_DESC_NUM_MAX_V01 31 +#define QMI_IPA_MAX_APN_V01 8 +/* Currently max we can use is only 1. But for scalability purpose + * we are having max value as 8. + */ +#define QMI_IPA_MAX_CLIENT_DST_PIPES_V01 8 +#define QMI_IPA_IPFLTR_NUM_IHL_MEQ_32_EQNS_V01 2 +#define QMI_IPA_MAX_UL_FIREWALL_RULES_V01 64 +#define QMI_IPA_REMOTE_MHI_MEMORY_MAPPING_NUM_MAX_V01 6 +#define QMI_IPA_IPFLTR_NUM_MEQ_32_EQNS_V01 2 +#define QMI_IPA_MAX_PIPES_V01 20 +#define QMI_IPA_MAX_PER_CLIENTS_V01 64 + +/* + * Indicates presence of newly added member to support HW stats. + */ +#define IPA_QMI_SUPPORTS_STATS +#define IPA_QMI_SUPPORT_MHI_DEFAULT + +#define IPA_INT_MAX ((int)(~0U>>1)) +#define IPA_INT_MIN (-IPA_INT_MAX - 1) + +/* IPA definition as msm_qmi_interface.h */ + +enum ipa_qmi_result_type_v01 { + /* To force a 32 bit signed enum. Do not change or use*/ + IPA_QMI_RESULT_TYPE_MIN_ENUM_VAL_V01 = IPA_INT_MIN, + IPA_QMI_RESULT_SUCCESS_V01 = 0, + IPA_QMI_RESULT_FAILURE_V01 = 1, + IPA_QMI_RESULT_TYPE_MAX_ENUM_VAL_V01 = IPA_INT_MAX, +}; + +enum ipa_qmi_error_type_v01 { + /* To force a 32 bit signed enum. Do not change or use*/ + IPA_QMI_ERROR_TYPE_MIN_ENUM_VAL_V01 = IPA_INT_MIN, + IPA_QMI_ERR_NONE_V01 = 0x0000, + IPA_QMI_ERR_MALFORMED_MSG_V01 = 0x0001, + IPA_QMI_ERR_NO_MEMORY_V01 = 0x0002, + IPA_QMI_ERR_INTERNAL_V01 = 0x0003, + IPA_QMI_ERR_CLIENT_IDS_EXHAUSTED_V01 = 0x0005, + IPA_QMI_ERR_INVALID_ID_V01 = 0x0029, + IPA_QMI_ERR_ENCODING_V01 = 0x003A, + IPA_QMI_ERR_INCOMPATIBLE_STATE_V01 = 0x005A, + IPA_QMI_ERR_NOT_SUPPORTED_V01 = 0x005E, + IPA_QMI_ERROR_TYPE_MAX_ENUM_VAL_V01 = IPA_INT_MAX, +}; + +struct ipa_qmi_response_type_v01 { + __u16 result; + __u16 error; +}; + +enum ipa_platform_type_enum_v01 { + IPA_PLATFORM_TYPE_ENUM_MIN_ENUM_VAL_V01 = + -2147483647, /* To force a 32 bit signed enum. Do not change or use */ + QMI_IPA_PLATFORM_TYPE_INVALID_V01 = 0, + /* Invalid platform identifier */ + QMI_IPA_PLATFORM_TYPE_TN_V01 = 1, + /* Platform identifier - Data card device */ + QMI_IPA_PLATFORM_TYPE_LE_V01 = 2, + /* Platform identifier - Data router device */ + QMI_IPA_PLATFORM_TYPE_MSM_ANDROID_V01 = 3, + /* Platform identifier - MSM device with Android HLOS */ + QMI_IPA_PLATFORM_TYPE_MSM_WINDOWS_V01 = 4, + /* Platform identifier - MSM device with Windows HLOS */ + QMI_IPA_PLATFORM_TYPE_MSM_QNX_V01 = 5, + /* Platform identifier - MDM device with LE HLOS, MHI data router */ + QMI_IPA_PLATFORM_TYPE_LE_MHI_V01 = 6, + /* Platform identifier - MSM device with QNX HLOS */ + IPA_PLATFORM_TYPE_ENUM_MAX_ENUM_VAL_V01 = 2147483647 + /* To force a 32 bit signed enum. Do not change or use */ +}; + +#define QMI_IPA_PLATFORM_TYPE_LE_MHI_V01 \ + QMI_IPA_PLATFORM_TYPE_LE_MHI_V01 + +struct ipa_hdr_tbl_info_type_v01 { + __u32 modem_offset_start; + /* Offset from the start of IPA Shared memory from which + * modem driver may insert header table entries. + */ + __u32 modem_offset_end; + /* Offset from the start of IPA shared mem beyond which modem + * driver shall not insert header table entries. The space + * available for the modem driver shall include the + * modem_offset_start and modem_offset_end. + */ +}; /* Type */ + +struct ipa_route_tbl_info_type_v01 { + __u32 route_tbl_start_addr; + /* Identifies the start of the routing table. Denotes the offset + * from the start of the IPA Shared Mem + */ + + __u32 num_indices; + /* Number of indices (starting from 0) that is being allocated to + * the modem. The number indicated here is also included in the + * allocation. The value of num_indices shall not exceed 31 + * (5 bits used to specify the routing table index), unless there + * is a change in the hardware. + */ +}; /* Type */ + +#define IPA_RQOS_FILTER_STATS_INFO +struct ipa_filter_stats_info_type_v01 { + __u32 hw_filter_stats_start_addr; + /* Identifies the start of the filter stats. Denotes the offset + * from the start of the IPA Shared Mem + */ + + __u32 hw_filter_stats_size; + /* Identifies size in bytes of the HW filter statistics table. */ + + __u8 hw_filter_stats_start_index; + /* Identifies the start index of the modem driver managed + * indices in the hw filter statistics table. + */ + + __u8 hw_filter_stats_end_index; + /* Identifies the end index os the modem driver managed + * indices in the hw filter statistics table. + */ +}; /* Type */ + +struct ipa_modem_mem_info_type_v01 { + + __u32 block_start_addr; + /* Identifies the start of the memory block allocated for the + * modem. Denotes the offset from the start of the IPA Shared Mem + */ + + __u32 size; + /* Size of the block allocated for the modem driver */ +}; /* Type */ + +struct ipa_hdr_proc_ctx_tbl_info_type_v01 { + + __u32 modem_offset_start; + /* Offset from the start of IPA shared memory from which the modem + * driver may insert header processing context table entries. + */ + + __u32 modem_offset_end; + /* Offset from the start of IPA shared memory beyond which the modem + * driver may not insert header proc table entries. The space + * available for the modem driver includes modem_offset_start and + * modem_offset_end. + */ +}; /* Type */ + +struct ipa_zip_tbl_info_type_v01 { + + __u32 modem_offset_start; + /* Offset from the start of IPA shared memory from which the modem + * driver may insert compression/decompression command entries. + */ + + __u32 modem_offset_end; + /* Offset from the start of IPA shared memory beyond which the modem + * driver may not insert compression/decompression command entries. + * The space available for the modem driver includes + * modem_offset_start and modem_offset_end. + */ +}; /* Type */ + +/** + * Request Message; Requests the modem IPA driver + * to perform initialization + */ +struct ipa_init_modem_driver_req_msg_v01 { + + /* Optional */ + /* Platform info */ + __u8 platform_type_valid; + /* Must be set to true if platform_type is being passed */ + enum ipa_platform_type_enum_v01 platform_type; + /* Provides information about the platform (ex. TN/MN/LE/MSM,etc) */ + + /* Optional */ + /* Header table info */ + __u8 hdr_tbl_info_valid; + /* Must be set to true if hdr_tbl_info is being passed */ + struct ipa_hdr_tbl_info_type_v01 hdr_tbl_info; + /* Provides information about the header table */ + + /* Optional */ + /* IPV4 Routing table info */ + __u8 v4_route_tbl_info_valid; + /* Must be set to true if v4_route_tbl_info is being passed */ + struct ipa_route_tbl_info_type_v01 v4_route_tbl_info; + /* Provides information about the IPV4 routing table */ + + /* Optional */ + /* IPV6 Routing table info */ + __u8 v6_route_tbl_info_valid; + /* Must be set to true if v6_route_tbl_info is being passed */ + struct ipa_route_tbl_info_type_v01 v6_route_tbl_info; + /* Provides information about the IPV6 routing table */ + + /* Optional */ + /* IPV4 Filter table start address */ + __u8 v4_filter_tbl_start_addr_valid; + /* Must be set to true if v4_filter_tbl_start_addr is being passed */ + __u32 v4_filter_tbl_start_addr; + /* Provides information about the starting address of IPV4 filter + * table in IPAv2 or non-hashable IPv4 filter table in IPAv3. + * Denotes the offset from the start of the IPA Shared Mem + */ + + /* Optional */ + /* IPV6 Filter table start address */ + __u8 v6_filter_tbl_start_addr_valid; + /* Must be set to true if v6_filter_tbl_start_addr is being passed */ + __u32 v6_filter_tbl_start_addr; + /* Provides information about the starting address of IPV6 filter + * table in IPAv2 or non-hashable IPv6 filter table in IPAv3. + * Denotes the offset from the start of the IPA Shared Mem + */ + + /* Optional */ + /* Modem memory block */ + __u8 modem_mem_info_valid; + /* Must be set to true if modem_mem_info is being passed */ + struct ipa_modem_mem_info_type_v01 modem_mem_info; + /* Provides information about the start address and the size of + * the memory block that is being allocated to the modem driver. + * Denotes the physical address + */ + + /* Optional */ + /* Destination end point for control commands from modem */ + __u8 ctrl_comm_dest_end_pt_valid; + /* Must be set to true if ctrl_comm_dest_end_pt is being passed */ + __u32 ctrl_comm_dest_end_pt; + /* Provides information about the destination end point on the + * application processor to which the modem driver can send + * control commands. The value of this parameter cannot exceed + * 19 since IPA only supports 20 end points. + */ + + /* Optional */ + /* Modem Bootup Information */ + __u8 is_ssr_bootup_valid; + /* Must be set to true if is_ssr_bootup is being passed */ + __u8 is_ssr_bootup; + /* Specifies whether the modem is booting up after a modem only + * sub-system restart or not. This will let the modem driver + * know that it doesn't have to reinitialize some of the HW + * blocks because IPA has not been reset since the previous + * initialization. + */ + + /* Optional */ + /* Header Processing Context Table Information */ + __u8 hdr_proc_ctx_tbl_info_valid; + /* Must be set to true if hdr_proc_ctx_tbl_info is being passed */ + struct ipa_hdr_proc_ctx_tbl_info_type_v01 hdr_proc_ctx_tbl_info; + /* Provides information about the header processing context table. + */ + + /* Optional */ + /* Compression Decompression Table Information */ + __u8 zip_tbl_info_valid; + /* Must be set to true if zip_tbl_info is being passed */ + struct ipa_zip_tbl_info_type_v01 zip_tbl_info; + /* Provides information about the zip table. + */ + + /* Optional */ + /* IPv4 Hashable Routing Table Information */ + /** Must be set to true if v4_hash_route_tbl_info is being passed */ + __u8 v4_hash_route_tbl_info_valid; + struct ipa_route_tbl_info_type_v01 v4_hash_route_tbl_info; + + /* Optional */ + /* IPv6 Hashable Routing Table Information */ + /** Must be set to true if v6_hash_route_tbl_info is being passed */ + __u8 v6_hash_route_tbl_info_valid; + struct ipa_route_tbl_info_type_v01 v6_hash_route_tbl_info; + + /* + * Optional + * IPv4 Hashable Filter Table Start Address + * Must be set to true if v4_hash_filter_tbl_start_addr + * is being passed + */ + __u8 v4_hash_filter_tbl_start_addr_valid; + __u32 v4_hash_filter_tbl_start_addr; + /* Identifies the starting address of the IPv4 hashable filter + * table in IPAv3 onwards. Denotes the offset from the start of + * the IPA shared memory. + */ + + /* Optional + * IPv6 Hashable Filter Table Start Address + * Must be set to true if v6_hash_filter_tbl_start_addr + * is being passed + */ + __u8 v6_hash_filter_tbl_start_addr_valid; + __u32 v6_hash_filter_tbl_start_addr; + /* Identifies the starting address of the IPv6 hashable filter + * table in IPAv3 onwards. Denotes the offset from the start of + * the IPA shared memory. + */ + + /* Optional + * Modem HW Stats Quota Base address + * Must be set to true if hw_stats_quota_base_addr + * is being passed + */ + __u8 hw_stats_quota_base_addr_valid; + __u32 hw_stats_quota_base_addr; + + /* Optional + * Modem HW Stats Quota Size + * Must be set to true if hw_stats_quota_size + * is being passed + */ + __u8 hw_stats_quota_size_valid; + __u32 hw_stats_quota_size; + + /* Optional + * Modem HW Drop Stats Table Start Address + * Must be set to true if hw_drop_stats_base_addr + * is being passed + */ + __u8 hw_drop_stats_base_addr_valid; + __u32 hw_drop_stats_base_addr; + + /* Optional + * Modem HW Drop Stats Table size + * Must be set to true if hw_drop_stats_table_size + * is being passed + */ + __u8 hw_drop_stats_table_size_valid; + __u32 hw_drop_stats_table_size; + + /* optional + * Modem HW flt stats info + * Must be set to true if filter_stats_info + * is being passed + */ + __u8 hw_fiter_stats_info_valid; + struct ipa_filter_stats_info_type_v01 hw_filter_stats_info; + + /* optional + * Filter table smem info + * Must be set to true if smem_info + * is being passed(Currently not using it) + */ + __u8 smem_info_valid; + struct ipa_modem_mem_info_type_v01 smem_info; + + /* optional + * IPA Peripheral stats info + * Must be set to true if per_stats_info + * is being passed + */ + __u8 per_stats_smem_info_valid; + struct ipa_modem_mem_info_type_v01 per_stats_smem_info; +}; /* Message */ + +/* Response Message; Requests the modem IPA driver about initialization */ +struct ipa_init_modem_driver_resp_msg_v01 { + /* Mandatory */ + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; + /* Standard response type.*/ + + /* Optional */ + /* Destination end point for control commands from master driver */ + __u8 ctrl_comm_dest_end_pt_valid; + /* Must be set to true if ctrl_comm_dest_ep is being passed */ + __u32 ctrl_comm_dest_end_pt; + /* Provides information about the destination end point on the + * modem processor to which the master driver can send control + * commands. The value of this parameter cannot exceed 19 since + * IPA only supports 20 end points. This field is looked at only + * if the result in TLV RESULT_CODE is QMI_RESULT_SUCCESS + */ + + /* Optional */ + /* Default end point */ + __u8 default_end_pt_valid; + /* Must be set to true if default_end_pt is being passed */ + __u32 default_end_pt; + /* Provides information about the default end point. The master + * driver may or may not set the register in the hardware with + * this value. The value of this parameter cannot exceed 19 + * since IPA only supports 20 end points. This field is looked + * at only if the result in TLV RESULT_CODE is QMI_RESULT_SUCCESS + */ + + /* Optional */ + /* Modem Driver Initialization Pending */ + __u8 modem_driver_init_pending_valid; + /* Must be set to true if modem_driver_init_pending is being passed */ + __u8 modem_driver_init_pending; + /* + * Identifies if second level message handshake is needed + * between drivers to indicate when IPA HWP loading is completed. + * If this is set by modem driver, AP driver will need to wait + * for a INIT_MODEM_DRIVER_CMPLT message before communicating with + * IPA HWP. + */ +}; /* Message */ + +/* + * Request Message; Request from Modem IPA driver to indicate + * modem driver init completion + */ +struct ipa_init_modem_driver_cmplt_req_msg_v01 { + /* Mandatory */ + /* Modem Driver init complete status; */ + __u8 status; + /* + * Specifies whether the modem driver initialization is complete + * including the micro controller image loading. + */ +}; /* Message */ + +/* + * Response Message; Request from Modem IPA driver to indicate + * modem driver init completion + */ +struct ipa_init_modem_driver_cmplt_resp_msg_v01 { + /* Mandatory */ + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; + /**< Standard response type.*/ +}; /* Message */ + +/* Request Message; This is the message that is exchanged between the + * control point and the service in order to register for indications. + */ +struct ipa_indication_reg_req_msg_v01 { + /* Optional */ + /* Master driver initialization completion */ + __u8 master_driver_init_complete_valid; + /* Must be set to true if master_driver_init_complete is being passed */ + __u8 master_driver_init_complete; + /* If set to TRUE, this field indicates that the client is + * interested in getting indications about the completion + * of the initialization sequence of the master driver. + * Setting this field in the request message makes sense + * only when the QMI_IPA_INDICATION_REGISTER_REQ is being + * originated from the modem driver + */ + + /* Optional */ + /* Data Usage Quota Reached */ + __u8 data_usage_quota_reached_valid; + /* Must be set to true if data_usage_quota_reached is being passed */ + __u8 data_usage_quota_reached; + /* If set to TRUE, this field indicates that the client wants to + * receive indications about reaching the data usage quota that + * previously set via QMI_IPA_SET_DATA_USAGE_QUOTA. Setting this field + * in the request message makes sense only when the + * QMI_IPA_INDICATION_REGISTER_REQ is being originated from the Master + * driver + */ + + /* Optional */ + /* IPA MHI Ready Indication */ + __u8 ipa_mhi_ready_ind_valid; + /* Must be set to true if ipa_mhi_ready_ind is being passed */ + __u8 ipa_mhi_ready_ind; + /* + * If set to TRUE, this field indicates that the client wants to + * receive indications about MHI ready for Channel allocations. + */ + + /* Optional */ + /* Endpoint Desc Info Indication */ + __u8 endpoint_desc_ind_valid; + /* Must be set to true if endpoint_desc_ind is being passed */ + __u8 endpoint_desc_ind; + /* + * If set to TRUE, this field indicates that the client wants to + * receive indications for Endpoint descriptor information via + * QMI_IPA_ENDP_DESC_INDICATION. Setting this field in the request + * message makes sense only when the QMI_IPA_INDICATION_REGISTER_REQ + * is being originated from the master driver. + */ + + /* Optional */ + /* BW CHANGE Indication */ + __u8 bw_change_ind_valid; + /* Must be set to true if bw_change_ind is being passed */ + __u8 bw_change_ind; + /* + * If set to TRUE, this field indicates that the client wants to + * receive indications for BW change information via + * QMI_IPA_BW_CHANGE_INDICATION. Setting this field in the request + * message makes sense only when the QMI_IPA_INDICATION_REGISTER_REQ + * is being originated from the master driver. + */ +}; /* Message */ + + +/* Response Message; This is the message that is exchanged between the + * control point and the service in order to register for indications. + */ +struct ipa_indication_reg_resp_msg_v01 { + /* Mandatory */ + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; + /**< Standard response type.*/ +}; /* Message */ + + +/* Indication Message; Indication sent to the Modem IPA driver from + * master IPA driver about initialization being complete. + */ +struct ipa_master_driver_init_complt_ind_msg_v01 { + /* Mandatory */ + /* Master driver initialization completion status */ + struct ipa_qmi_response_type_v01 master_driver_init_status; + /* Indicates the status of initialization. If everything went + * as expected, this field is set to SUCCESS. ERROR is set + * otherwise. Extended error info may be used to convey + * additional information about the error + */ +}; /* Message */ + +struct ipa_ipfltr_range_eq_16_type_v01 { + __u8 offset; + /* Specifies the offset from the IHL (Internet Header length) */ + + __u16 range_low; + /* Specifies the lower bound of the range */ + + __u16 range_high; + /* Specifies the upper bound of the range */ +}; /* Type */ + +struct ipa_ipfltr_mask_eq_32_type_v01 { + __u8 offset; + /* Specifies the offset either from IHL or from the start of + * the IP packet. This depends on the equation that this structure + * is used in. + */ + + __u32 mask; + /* Specifies the mask that has to be used in the comparison. + * The field is ANDed with the mask and compared against the value. + */ + + __u32 value; + /* Specifies the 32 bit value that used in the comparison. */ +}; /* Type */ + +struct ipa_ipfltr_eq_16_type_v01 { + __u8 offset; + /* Specifies the offset into the packet */ + + __u16 value; + /* Specifies the 16 bit value that should be used in the comparison. */ +}; /* Type */ + +struct ipa_ipfltr_eq_32_type_v01 { + __u8 offset; + /* Specifies the offset into the packet */ + + __u32 value; + /* Specifies the 32 bit value that should be used in the comparison. */ +}; /* Type */ + +struct ipa_ipfltr_mask_eq_128_type_v01 { + __u8 offset; + /* Specifies the offset into the packet */ + + __u8 mask[16]; + /* Specifies the mask that has to be used in the comparison. + * The field is ANDed with the mask and compared against the value. + */ + + __u8 value[16]; + /* Specifies the 128 bit value that should be used in the comparison. */ +}; /* Type */ + + +struct ipa_filter_rule_type_v01 { + __u16 rule_eq_bitmap; + /* 16-bit Bitmask to indicate how many eqs are valid in this rule */ + + __u8 tos_eq_present; + /* + * tos_eq_present field has two meanings: + * IPA ver < 4.5: + * specifies if a type of service check rule is present + * (as the field name reveals). + * IPA ver >= 4.5: + * specifies if a tcp pure ack check rule is present + */ + + __u8 tos_eq; + /* The value to check against the type of service (ipv4) field */ + + __u8 protocol_eq_present; + /* Specifies if a protocol check rule is present */ + + __u8 protocol_eq; + /* The value to check against the protocol field */ + + __u8 num_ihl_offset_range_16; + /* The number of 16 bit range check rules at the location + * determined by IP header length plus a given offset + * in this rule. See the definition of the ipa_filter_range_eq_16 + * for better understanding. The value of this field cannot exceed + * IPA_IPFLTR_NUM_IHL_RANGE_16_EQNS which is set as 2 + */ + + struct ipa_ipfltr_range_eq_16_type_v01 + ihl_offset_range_16[QMI_IPA_IPFLTR_NUM_IHL_RANGE_16_EQNS_V01]; + /* Array of the registered IP header length offset 16 bit range + * check rules. + */ + + __u8 num_offset_meq_32; + /* The number of 32 bit masked comparison rules present + * in this rule + */ + + struct ipa_ipfltr_mask_eq_32_type_v01 + offset_meq_32[QMI_IPA_IPFLTR_NUM_MEQ_32_EQNS_V01]; + /* An array of all the possible 32bit masked comparison rules + * in this rule + */ + + __u8 tc_eq_present; + /* Specifies if the traffic class rule is present in this rule */ + + __u8 tc_eq; + /* The value against which the IPV4 traffic class field has to + * be checked + */ + + __u8 flow_eq_present; + /* Specifies if the "flow equals" rule is present in this rule */ + + __u32 flow_eq; + /* The value against which the IPV6 flow field has to be checked */ + + __u8 ihl_offset_eq_16_present; + /* Specifies if there is a 16 bit comparison required at the + * location in the packet determined by "Intenet Header length + * + specified offset" + */ + + struct ipa_ipfltr_eq_16_type_v01 ihl_offset_eq_16; + /* The 16 bit comparison equation */ + + __u8 ihl_offset_eq_32_present; + /* Specifies if there is a 32 bit comparison required at the + * location in the packet determined by "Intenet Header length + * + specified offset" + */ + + struct ipa_ipfltr_eq_32_type_v01 ihl_offset_eq_32; + /* The 32 bit comparison equation */ + + __u8 num_ihl_offset_meq_32; + /* The number of 32 bit masked comparison equations in this + * rule. The location of the packet to be compared is + * determined by the IP Header length + the give offset + */ + + struct ipa_ipfltr_mask_eq_32_type_v01 + ihl_offset_meq_32[QMI_IPA_IPFLTR_NUM_IHL_MEQ_32_EQNS_V01]; + /* Array of 32 bit masked comparison equations. + */ + + __u8 num_offset_meq_128; + /* The number of 128 bit comparison equations in this rule */ + + struct ipa_ipfltr_mask_eq_128_type_v01 + offset_meq_128[QMI_IPA_IPFLTR_NUM_MEQ_128_EQNS_V01]; + /* Array of 128 bit comparison equations. The location in the + * packet is determined by the specified offset + */ + + __u8 metadata_meq32_present; + /* Boolean indicating if the 32 bit masked comparison equation + * is present or not. Comparison is done against the metadata + * in IPA. Metadata can either be extracted from the packet + * header or from the "metadata" register. + */ + + struct ipa_ipfltr_mask_eq_32_type_v01 + metadata_meq32; + /* The metadata 32 bit masked comparison equation */ + + __u8 ipv4_frag_eq_present; + /* Specifies if the IPv4 Fragment equation is present in this rule */ +}; /* Type */ + + +struct ipa_filter_rule_req2_type_v01 { + __u16 rule_eq_bitmap; + /* 16-bit Bitmask to indicate how many eqs are valid in this rule */ + + __u8 pure_ack_eq_present; + /* + * specifies if a tcp pure ack check rule is present + */ + + __u8 pure_ack_eq; + /* The value to check against the type of service (ipv4) field */ + + __u8 protocol_eq_present; + /* Specifies if a protocol check rule is present */ + + __u8 protocol_eq; + /* The value to check against the protocol field */ + + __u8 num_ihl_offset_range_16; + /* The number of 16 bit range check rules at the location + * determined by IP header length plus a given offset + * in this rule. See the definition of the ipa_filter_range_eq_16 + * for better understanding. The value of this field cannot exceed + * IPA_IPFLTR_NUM_IHL_RANGE_16_EQNS which is set as 2 + */ + + struct ipa_ipfltr_range_eq_16_type_v01 + ihl_offset_range_16[QMI_IPA_IPFLTR_NUM_IHL_RANGE_16_EQNS_V01]; + /* Array of the registered IP header length offset 16 bit range + * check rules. + */ + + __u8 num_offset_meq_32; + /* The number of 32 bit masked comparison rules present + * in this rule + */ + + struct ipa_ipfltr_mask_eq_32_type_v01 + offset_meq_32[QMI_IPA_IPFLTR_NUM_MEQ_32_EQNS_V01]; + /* An array of all the possible 32bit masked comparison rules + * in this rule + */ + + __u8 tc_eq_present; + /* Specifies if the traffic class rule is present in this rule */ + + __u8 tc_eq; + /* The value against which the IPV4 traffic class field has to + * be checked + */ + + __u8 flow_eq_present; + /* Specifies if the "flow equals" rule is present in this rule */ + + __u32 flow_eq; + /* The value against which the IPV6 flow field has to be checked */ + + __u8 ihl_offset_eq_16_present; + /* Specifies if there is a 16 bit comparison required at the + * location in the packet determined by "Intenet Header length + * + specified offset" + */ + + struct ipa_ipfltr_eq_16_type_v01 ihl_offset_eq_16; + /* The 16 bit comparison equation */ + + __u8 ihl_offset_eq_32_present; + /* Specifies if there is a 32 bit comparison required at the + * location in the packet determined by "Intenet Header length + * + specified offset" + */ + + struct ipa_ipfltr_eq_32_type_v01 ihl_offset_eq_32; + /* The 32 bit comparison equation */ + + __u8 num_ihl_offset_meq_32; + /* The number of 32 bit masked comparison equations in this + * rule. The location of the packet to be compared is + * determined by the IP Header length + the give offset + */ + + struct ipa_ipfltr_mask_eq_32_type_v01 + ihl_offset_meq_32[QMI_IPA_IPFLTR_NUM_IHL_MEQ_32_EQNS_V01]; + /* Array of 32 bit masked comparison equations. + */ + + __u8 num_offset_meq_128; + /* The number of 128 bit comparison equations in this rule */ + + struct ipa_ipfltr_mask_eq_128_type_v01 + offset_meq_128[QMI_IPA_IPFLTR_NUM_MEQ_128_EQNS_V01]; + /* Array of 128 bit comparison equations. The location in the + * packet is determined by the specified offset + */ + + __u8 metadata_meq32_present; + /* Boolean indicating if the 32 bit masked comparison equation + * is present or not. Comparison is done against the metadata + * in IPA. Metadata can either be extracted from the packet + * header or from the "metadata" register. + */ + + struct ipa_ipfltr_mask_eq_32_type_v01 + metadata_meq32; + /* The metadata 32 bit masked comparison equation */ + + __u8 ipv4_frag_eq_present; + /* Specifies if the IPv4 Fragment equation is present in this rule */ +}; /* Type */ + +enum ipa_ip_type_enum_v01 { + IPA_IP_TYPE_ENUM_MIN_ENUM_VAL_V01 = -2147483647, + /* To force a 32 bit signed enum. Do not change or use*/ + QMI_IPA_IP_TYPE_INVALID_V01 = 0, + /* Invalid IP type identifier */ + QMI_IPA_IP_TYPE_V4_V01 = 1, + /* IP V4 type */ + QMI_IPA_IP_TYPE_V6_V01 = 2, + /* IP V6 type */ + QMI_IPA_IP_TYPE_V4V6_V01 = 3, + /* Applies to both IP types */ + IPA_IP_TYPE_ENUM_MAX_ENUM_VAL_V01 = 2147483647 + /* To force a 32 bit signed enum. Do not change or use*/ +}; + + +enum ipa_filter_action_enum_v01 { + IPA_FILTER_ACTION_ENUM_MIN_ENUM_VAL_V01 = -2147483647, + /* To force a 32 bit signed enum. Do not change or use */ + QMI_IPA_FILTER_ACTION_INVALID_V01 = 0, + /* Invalid action on filter hit */ + QMI_IPA_FILTER_ACTION_SRC_NAT_V01 = 1, + /* Pass packet to NAT block for Source NAT */ + QMI_IPA_FILTER_ACTION_DST_NAT_V01 = 2, + /* Pass packet to NAT block for Destination NAT */ + QMI_IPA_FILTER_ACTION_ROUTING_V01 = 3, + /* Pass packet to Routing block */ + QMI_IPA_FILTER_ACTION_EXCEPTION_V01 = 4, + /* Treat packet as exception and send to exception pipe */ + IPA_FILTER_ACTION_ENUM_MAX_ENUM_VAL_V01 = 2147483647 + /* To force a 32 bit signed enum. Do not change or use*/ +}; + +struct ipa_filter_spec_type_v01 { + __u32 filter_spec_identifier; + /* This field is used to identify a filter spec in the list + * of filter specs being sent from the client. This field + * is applicable only in the filter install request and response. + */ + + enum ipa_ip_type_enum_v01 ip_type; + /* This field identifies the IP type for which this rule is + * applicable. The driver needs to identify the filter table + * (V6 or V4) and this field is essential for that + */ + + struct ipa_filter_rule_type_v01 filter_rule; + /* This field specifies the rules in the filter spec. These rules + * are the ones that are matched against fields in the packet. + */ + + enum ipa_filter_action_enum_v01 filter_action; + /* This field specifies the action to be taken when a filter match + * occurs. The remote side should install this information into the + * hardware along with the filter equations. + */ + + __u8 is_routing_table_index_valid; + /* Specifies whether the routing table index is present or not. + * If the action is "QMI_IPA_FILTER_ACTION_EXCEPTION", this + * parameter need not be provided. + */ + + __u32 route_table_index; + /* This is the index in the routing table that should be used + * to route the packets if the filter rule is hit + */ + + __u8 is_mux_id_valid; + /* Specifies whether the mux_id is valid */ + + __u32 mux_id; + /* This field identifies the QMAP MUX ID. As a part of QMAP + * protocol, several data calls may be multiplexed over the + * same physical transport channel. This identifier is used to + * identify one such data call. The maximum value for this + * identifier is 255. + */ +}; /* Type */ + +struct ipa_filter_spec_ex_type_v01 { + enum ipa_ip_type_enum_v01 ip_type; + /* This field identifies the IP type for which this rule is + * applicable. The driver needs to identify the filter table + * (V6 or V4) and this field is essential for that + */ + + struct ipa_filter_rule_type_v01 filter_rule; + /* This field specifies the rules in the filter spec. These rules + * are the ones that are matched against fields in the packet. + */ + + enum ipa_filter_action_enum_v01 filter_action; + /* This field specifies the action to be taken when a filter match + * occurs. The remote side should install this information into the + * hardware along with the filter equations. + */ + + __u8 is_routing_table_index_valid; + /* Specifies whether the routing table index is present or not. + * If the action is "QMI_IPA_FILTER_ACTION_EXCEPTION", this + * parameter need not be provided. + */ + + __u32 route_table_index; + /* This is the index in the routing table that should be used + * to route the packets if the filter rule is hit + */ + + __u8 is_mux_id_valid; + /* Specifies whether the mux_id is valid */ + + __u32 mux_id; + /* This field identifies the QMAP MUX ID. As a part of QMAP + * protocol, several data calls may be multiplexed over the + * same physical transport channel. This identifier is used to + * identify one such data call. The maximum value for this + * identifier is 255. + */ + + __u32 rule_id; + /* Rule Id of the given filter. The Rule Id is populated in the rule + * header when installing the rule in IPA. + */ + + __u8 is_rule_hashable; + /** Specifies whether the given rule is hashable. + */ +}; /* Type */ + +struct ipa_filter_spec_ex2_type_v01 { + enum ipa_ip_type_enum_v01 ip_type; + /* This field identifies the IP type for which this rule is + * applicable. The driver needs to identify the filter table + * (V6 or V4) and this field is essential for that + */ + + struct ipa_filter_rule_req2_type_v01 filter_rule; + /* This field specifies the rules in the filter spec. These rules + * are the ones that are matched against fields in the packet. + */ + + enum ipa_filter_action_enum_v01 filter_action; + /* This field specifies the action to be taken when a filter match + * occurs. The remote side should install this information into the + * hardware along with the filter equations. + */ + + __u8 is_routing_table_index_valid; + /* Specifies whether the routing table index is present or not. + * If the action is "QMI_IPA_FILTER_ACTION_EXCEPTION", this + * parameter need not be provided. + */ + + __u32 route_table_index; + /* This is the index in the routing table that should be used + * to route the packets if the filter rule is hit + */ + + __u8 is_mux_id_valid; + /* Specifies whether the mux_id is valid */ + + __u32 mux_id; + /* This field identifies the QMAP MUX ID. As a part of QMAP + * protocol, several data calls may be multiplexed over the + * same physical transport channel. This identifier is used to + * identify one such data call. The maximum value for this + * identifier is 255. + */ + + __u32 rule_id; + /* Rule Id of the given filter. The Rule Id is populated in the rule + * header when installing the rule in IPA. + */ + + __u8 is_rule_hashable; + /** Specifies whether the given rule is hashable. + */ +}; /* Type */ + +/* Request Message; This is the message that is exchanged between the + * control point and the service in order to request the installation + * of filtering rules in the hardware block by the remote side. + */ +struct ipa_install_fltr_rule_req_msg_v01 { + /* Optional + * IP type that this rule applies to + * Filter specification to be installed in the hardware + */ + __u8 filter_spec_list_valid; + /* Must be set to true if filter_spec_list is being passed */ + __u32 filter_spec_list_len; + /* Must be set to # of elements in filter_spec_list */ + struct ipa_filter_spec_type_v01 + filter_spec_list[QMI_IPA_MAX_FILTERS_V01]; + /* This structure defines the list of filters that have + * to be installed in the hardware. The driver installing + * these rules shall do so in the same order as specified + * in this list. + */ + + /* Optional */ + /* Pipe index to intall rule */ + __u8 source_pipe_index_valid; + /* Must be set to true if source_pipe_index is being passed */ + __u32 source_pipe_index; + /* This is the source pipe on which the filter rule is to be + * installed. The requestor may always not know the pipe + * indices. If not specified, the receiver shall install + * this rule on all the pipes that it controls through + * which data may be fed into IPA. + */ + + /* Optional */ + /* Total number of IPv4 filters in the filter spec list */ + __u8 num_ipv4_filters_valid; + /* Must be set to true if num_ipv4_filters is being passed */ + __u32 num_ipv4_filters; + /* Number of IPv4 rules included in filter spec list */ + + /* Optional */ + /* Total number of IPv6 filters in the filter spec list */ + __u8 num_ipv6_filters_valid; + /* Must be set to true if num_ipv6_filters is being passed */ + __u32 num_ipv6_filters; + /* Number of IPv6 rules included in filter spec list */ + + /* Optional */ + /* List of XLAT filter indices in the filter spec list */ + __u8 xlat_filter_indices_list_valid; + /* Must be set to true if xlat_filter_indices_list + * is being passed + */ + __u32 xlat_filter_indices_list_len; + /* Must be set to # of elements in xlat_filter_indices_list */ + __u32 xlat_filter_indices_list[QMI_IPA_MAX_FILTERS_V01]; + /* List of XLAT filter indices. Filter rules at specified indices + * will need to be modified by the receiver if the PDN is XLAT + * before installing them on the associated IPA consumer pipe. + */ + + /* Optional */ + /* Extended Filter Specification */ + __u8 filter_spec_ex_list_valid; + /* Must be set to true if filter_spec_ex_list is being passed */ + __u32 filter_spec_ex_list_len; + /* Must be set to # of elements in filter_spec_ex_list */ + struct ipa_filter_spec_ex_type_v01 + filter_spec_ex_list[QMI_IPA_MAX_FILTERS_V01]; + /* + * List of filter specifications of filters that must be installed in + * the IPAv3.x hardware. + * The driver installing these rules must do so in the same + * order as specified in this list. + */ + + /* Optional */ + /* Extended Type 2 Filter Specification */ + __u8 filter_spec_ex2_list_valid; + /* Must be set to true if filter_spec_ex2_list is being passed */ + __u32 filter_spec_ex2_list_len; + /* Must be set to # of elements in filter_spec_ex2_list */ + struct ipa_filter_spec_ex2_type_v01 + filter_spec_ex2_list[QMI_IPA_MAX_FILTERS_V01]; + + /* Optional */ + /* List of modem UL Filters in the Spec List which need be to + * replicated with AP UL firewall filters + */ + __u8 ul_firewall_indices_list_valid; + /* Must be set to # of elements in ul_firewall_indices_list */ + __u32 ul_firewall_indices_list_len; + __u32 ul_firewall_indices_list[QMI_IPA_MAX_FILTERS_V01]; + /* List of UL firewall filter indices. + * Filter rules at specified indices must be replicated across + * the firewall filters by the receiver and installed on the + * associated IPA consumer pipe. + */ +}; /* Message */ + +struct ipa_filter_rule_identifier_to_handle_map_v01 { + __u32 filter_spec_identifier; + /* This field is used to identify a filter spec in the list of + * filter specs being sent from the client. This field is + * applicable only in the filter install request and response. + */ + __u32 filter_handle; + /* This field is used to identify a rule in any subsequent message. + * This is a value that is provided by the server to the control + * point + */ +}; /* Type */ + +/* Response Message; This is the message that is exchanged between the + * control point and the service in order to request the + * installation of filtering rules in the hardware block by + * the remote side. + */ +struct ipa_install_fltr_rule_resp_msg_v01 { + /* Mandatory */ + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; + /* Standard response type. + * Standard response type. Contains the following data members: + * - qmi_result_type -- QMI_RESULT_SUCCESS or QMI_RESULT_FAILURE + * - qmi_error_type -- Error code. Possible error code values are + * described in the error codes section of each message definition. + */ + + /* Optional */ + /* Filter Handle List */ + __u8 filter_handle_list_valid; + /* Must be set to true if filter_handle_list is being passed */ + __u32 filter_handle_list_len; + /* Must be set to # of elements in filter_handle_list */ + struct ipa_filter_rule_identifier_to_handle_map_v01 + filter_handle_list[QMI_IPA_MAX_FILTERS_V01]; + /* + * List of handles returned to the control point. Each handle is + * mapped to the rule identifier that was specified in the + * request message. Any further reference to the rule is done + * using the filter handle. + */ + + /* Optional */ + /* Rule id List */ + __u8 rule_id_valid; + /* Must be set to true if rule_id is being passed */ + __u32 rule_id_len; + /* Must be set to # of elements in rule_id */ + __u32 rule_id[QMI_IPA_MAX_FILTERS_V01]; + /* + * List of rule ids returned to the control point. + * Any further reference to the rule is done using the + * filter rule id specified in this list. + */ +}; /* Message */ + +struct ipa_filter_handle_to_index_map_v01 { + __u32 filter_handle; + /* This is a handle that was given to the remote client that + * requested the rule addition. + */ + __u32 filter_index; + /* This index denotes the location in a filter table, where the + * filter rule has been installed. The maximum value of this + * field is 64. + */ +}; /* Type */ + +/* Request Message; This is the message that is exchanged between the + * control point and the service in order to notify the remote driver + * of the installation of the filter rule supplied earlier by the + * remote driver. + */ +struct ipa_fltr_installed_notif_req_msg_v01 { + /* Mandatory */ + /* Pipe index */ + __u32 source_pipe_index; + /* This is the source pipe on which the filter rule has been + * installed or was attempted to be installed + */ + + /* Mandatory */ + /* Installation Status */ + enum ipa_qmi_result_type_v01 install_status; + /* This is the status of installation. If this indicates + * SUCCESS, other optional fields carry additional + * information + */ + + /* Mandatory */ + /* List of Filter Indices */ + __u32 filter_index_list_len; + /* Must be set to # of elements in filter_index_list */ + struct ipa_filter_handle_to_index_map_v01 + filter_index_list[QMI_IPA_MAX_FILTERS_V01]; + /* + * Provides the list of filter indices and the corresponding + * filter handle. If the installation_status indicates a + * failure, the filter indices must be set to a reserve + * index (255). + */ + + /* Optional */ + /* Embedded pipe index */ + __u8 embedded_pipe_index_valid; + /* Must be set to true if embedded_pipe_index is being passed */ + __u32 embedded_pipe_index; + /* This index denotes the embedded pipe number on which a call to + * the same PDN has been made. If this field is set, it denotes + * that this is a use case where PDN sharing is happening. The + * embedded pipe is used to send data from the embedded client + * in the device + */ + + /* Optional */ + /* Retain Header Configuration */ + __u8 retain_header_valid; + /* Must be set to true if retain_header is being passed */ + __u8 retain_header; + /* This field indicates if the driver installing the rule has + * turned on the "retain header" bit. If this is true, the + * header that is removed by IPA is reinserted after the + * packet processing is completed. + */ + + /* Optional */ + /* Embedded call Mux Id */ + __u8 embedded_call_mux_id_valid; + /**< Must be set to true if embedded_call_mux_id is being passed */ + __u32 embedded_call_mux_id; + /* This identifies one of the many calls that have been originated + * on the embedded pipe. This is how we identify the PDN gateway + * to which traffic from the source pipe has to flow. + */ + + /* Optional */ + /* Total number of IPv4 filters in the filter index list */ + __u8 num_ipv4_filters_valid; + /* Must be set to true if num_ipv4_filters is being passed */ + __u32 num_ipv4_filters; + /* Number of IPv4 rules included in filter index list */ + + /* Optional */ + /* Total number of IPv6 filters in the filter index list */ + __u8 num_ipv6_filters_valid; + /* Must be set to true if num_ipv6_filters is being passed */ + __u32 num_ipv6_filters; + /* Number of IPv6 rules included in filter index list */ + + /* Optional */ + /* Start index on IPv4 filters installed on source pipe */ + __u8 start_ipv4_filter_idx_valid; + /* Must be set to true if start_ipv4_filter_idx is being passed */ + __u32 start_ipv4_filter_idx; + /* Start index of IPv4 rules in filter index list */ + + /* Optional */ + /* Start index on IPv6 filters installed on source pipe */ + __u8 start_ipv6_filter_idx_valid; + /* Must be set to true if start_ipv6_filter_idx is being passed */ + __u32 start_ipv6_filter_idx; + /* Start index of IPv6 rules in filter index list */ + + /* Optional */ + /* List of Rule Ids */ + __u8 rule_id_valid; + /* Must be set to true if rule_id is being passed */ + __u32 rule_id_len; + /* Must be set to # of elements in rule_id */ + __u32 rule_id[QMI_IPA_MAX_FILTERS_V01]; + /* + * Provides the list of Rule Ids of rules added in IPA on the given + * source pipe index. If the install_status TLV indicates a + * failure, the Rule Ids in this list must be set to a reserved + * index (255). + */ + + /* Optional */ + /* List of destination pipe IDs. */ + __u8 dst_pipe_id_valid; + /* Must be set to true if dst_pipe_id is being passed. */ + __u32 dst_pipe_id_len; + /* Must be set to # of elements in dst_pipe_id. */ + __u32 dst_pipe_id[QMI_IPA_MAX_CLIENT_DST_PIPES_V01]; + /* Provides the list of destination pipe IDs for a source pipe. */ + + /* Optional */ + /* List of Rule IDs extended */ + __u8 rule_id_ex_valid; + /* Must be set to true if rule_id_ex is being passed. */ + __u32 rule_id_ex_len; + /* Must be set to # of elements in rule_id_ex */ + __u32 rule_id_ex[QMI_IPA_MAX_FILTERS_EX2_V01]; + /* Provides the list of Rule IDs of rules added in IPA on the + * given source pipe index. If the install_status TLV indicates + * a failure, the Rule IDs in this list must be set to a + * reserved index (255). + */ +}; /* Message */ + +/* Response Message; This is the message that is exchanged between the + * control point and the service in order to notify the remote driver + * of the installation of the filter rule supplied earlier by the + * remote driver. + */ +struct ipa_fltr_installed_notif_resp_msg_v01 { + /* Mandatory */ + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; + /* Standard response type */ +}; /* Message */ + +/* Request Message; Notifies the remote driver of the need to clear the data + * path to prevent the IPA from being blocked at the head of the processing + * pipeline + */ +struct ipa_enable_force_clear_datapath_req_msg_v01 { + /* Mandatory */ + /* Pipe Mask */ + __u32 source_pipe_bitmask; + /* Set of consumer (source) pipes that must be clear of + * active data transfers. + */ + + /* Mandatory */ + /* Request ID */ + __u32 request_id; + /* Identifies the ID of the request that is sent to the server + * The same request ID is used in the message to remove the force_clear + * request. The server is expected to keep track of the request ID and + * the source_pipe_bitmask so that it can revert as needed + */ + + /* Optional */ + /* Source Throttle State */ + __u8 throttle_source_valid; + /* Must be set to true if throttle_source is being passed */ + __u8 throttle_source; + /* Specifies whether the server is to throttle the data from + * these consumer (source) pipes after clearing the exisiting + * data present in the IPA that were pulled from these pipes + * The server is expected to put all the source pipes in the + * source_pipe_bitmask in the same state + */ + + /* Optional */ + /* Pipe Mask Ext State */ + __u8 source_pipe_bitmask_ext_valid; + /* Pipe Mask Ext */ + __u32 source_pipe_bitmask_ext[4]; + /* Set of consumer (source) pipes that must be clear of + * active data transfers. + * The extended mask supports up to 128 endpoints to accommodate newer + * architectures, which use more than 32 endpoints. + * If this new field is used, the old field source_pipe_bitmask + * shall be ignored. + */ +}; /* Message */ + +/* Response Message; Notifies the remote driver of the need to clear the + * data path to prevent the IPA from being blocked at the head of the + * processing pipeline + */ +struct ipa_enable_force_clear_datapath_resp_msg_v01 { + /* Mandatory */ + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; + /* Standard response type */ +}; /* Message */ + +/* Request Message; Notifies the remote driver that the forceful clearing + * of the data path can be lifted + */ +struct ipa_disable_force_clear_datapath_req_msg_v01 { + /* Mandatory */ + /* Request ID */ + __u32 request_id; + /* Identifies the request that was sent to the server to + * forcibly clear the data path. This request simply undoes + * the operation done in that request + */ +}; /* Message */ + +/* Response Message; Notifies the remote driver that the forceful clearing + * of the data path can be lifted + */ +struct ipa_disable_force_clear_datapath_resp_msg_v01 { + /* Mandatory */ + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; + /* Standard response type */ +}; /* Message */ + +enum ipa_peripheral_speed_enum_v01 { + IPA_PERIPHERAL_SPEED_ENUM_MIN_ENUM_VAL_V01 = -2147483647, + /* To force a 32 bit signed enum. Do not change or use */ + QMI_IPA_PER_USB_FS_V01 = 1, + /* Full-speed USB connection */ + QMI_IPA_PER_USB_HS_V01 = 2, + /* High-speed USB connection */ + QMI_IPA_PER_USB_SS_V01 = 3, + /* Super-speed USB connection */ + QMI_IPA_PER_WLAN_V01 = 4, + /* WLAN connection */ + IPA_PERIPHERAL_SPEED_ENUM_MAX_ENUM_VAL_V01 = 2147483647 + /* To force a 32 bit signed enum. Do not change or use*/ +}; + +enum ipa_pipe_mode_enum_v01 { + IPA_PIPE_MODE_ENUM_MIN_ENUM_VAL_V01 = -2147483647, + /* To force a 32 bit signed enum. Do not change or use */ + QMI_IPA_PIPE_MODE_HW_V01 = 1, + /* Pipe is connected with a hardware block */ + QMI_IPA_PIPE_MODE_SW_V01 = 2, + /* Pipe is controlled by the software */ + IPA_PIPE_MODE_ENUM_MAX_ENUM_VAL_V01 = 2147483647 + /* To force a 32 bit signed enum. Do not change or use */ +}; + +enum ipa_peripheral_type_enum_v01 { + IPA_PERIPHERAL_TYPE_ENUM_MIN_ENUM_VAL_V01 = -2147483647, + /* To force a 32 bit signed enum. Do not change or use */ + QMI_IPA_PERIPHERAL_USB_V01 = 1, + /* Specifies a USB peripheral */ + QMI_IPA_PERIPHERAL_HSIC_V01 = 2, + /* Specifies an HSIC peripheral */ + QMI_IPA_PERIPHERAL_PCIE_V01 = 3, + /* Specifies a PCIe peripheral */ + IPA_PERIPHERAL_TYPE_ENUM_MAX_ENUM_VAL_V01 = 2147483647 + /* To force a 32 bit signed enum. Do not change or use */ +}; + +struct ipa_config_req_msg_v01 { + /* Optional */ + /* Peripheral Type */ + __u8 peripheral_type_valid; + /* Must be set to true if peripheral_type is being passed */ + enum ipa_peripheral_type_enum_v01 peripheral_type; + /* Informs the remote driver about the perhipheral for + * which this configuration information is relevant. Values: + * - QMI_IPA_PERIPHERAL_USB (1) -- Specifies a USB peripheral + * - QMI_IPA_PERIPHERAL_HSIC(2) -- Specifies an HSIC peripheral + * - QMI_IPA_PERIPHERAL_PCIE(3) -- Specifies a PCIe peripheral + */ + + /* Optional */ + /* HW Deaggregation Support */ + __u8 hw_deaggr_supported_valid; + /* Must be set to true if hw_deaggr_supported is being passed */ + __u8 hw_deaggr_supported; + /* Informs the remote driver whether the local IPA driver + * allows de-aggregation to be performed in the hardware + */ + + /* Optional */ + /* Maximum Aggregation Frame Size */ + __u8 max_aggr_frame_size_valid; + /* Must be set to true if max_aggr_frame_size is being passed */ + __u32 max_aggr_frame_size; + /* Specifies the maximum size of the aggregated frame that + * the remote driver can expect from this execution environment + * - Valid range: 128 bytes to 32768 bytes + */ + + /* Optional */ + /* IPA Ingress Pipe Mode */ + __u8 ipa_ingress_pipe_mode_valid; + /* Must be set to true if ipa_ingress_pipe_mode is being passed */ + + enum ipa_pipe_mode_enum_v01 ipa_ingress_pipe_mode; + /* Indicates to the remote driver if the ingress pipe into the + * IPA is in direct connection with another hardware block or + * if the producer of data to this ingress pipe is a software + * module. Values: + * -QMI_IPA_PIPE_MODE_HW(1) --Pipe is connected with hardware block + * -QMI_IPA_PIPE_MODE_SW(2) --Pipe is controlled by the software + */ + + /* Optional */ + /* Peripheral Speed Info */ + __u8 peripheral_speed_info_valid; + /* Must be set to true if peripheral_speed_info is being passed */ + + enum ipa_peripheral_speed_enum_v01 peripheral_speed_info; + /* Indicates the speed that the peripheral connected to the IPA supports + * Values: + * - QMI_IPA_PER_USB_FS (1) -- Full-speed USB connection + * - QMI_IPA_PER_USB_HS (2) -- High-speed USB connection + * - QMI_IPA_PER_USB_SS (3) -- Super-speed USB connection + * - QMI_IPA_PER_WLAN (4) -- WLAN connection + */ + + /* Optional */ + /* Downlink Accumulation Time limit */ + __u8 dl_accumulation_time_limit_valid; + /* Must be set to true if dl_accumulation_time_limit is being passed */ + __u32 dl_accumulation_time_limit; + /* Informs the remote driver about the time for which data + * is accumulated in the downlink direction before it is pushed into the + * IPA (downlink is with respect to the WWAN air interface) + * - Units: milliseconds + * - Maximum value: 255 + */ + + /* Optional */ + /* Downlink Accumulation Packet limit */ + __u8 dl_accumulation_pkt_limit_valid; + /* Must be set to true if dl_accumulation_pkt_limit is being passed */ + __u32 dl_accumulation_pkt_limit; + /* Informs the remote driver about the number of packets + * that are to be accumulated in the downlink direction before it is + * pushed into the IPA - Maximum value: 1023 + */ + + /* Optional */ + /* Downlink Accumulation Byte Limit */ + __u8 dl_accumulation_byte_limit_valid; + /* Must be set to true if dl_accumulation_byte_limit is being passed */ + __u32 dl_accumulation_byte_limit; + /* Inform the remote driver about the number of bytes + * that are to be accumulated in the downlink direction before it + * is pushed into the IPA - Maximum value: TBD + */ + + /* Optional */ + /* Uplink Accumulation Time Limit */ + __u8 ul_accumulation_time_limit_valid; + /* Must be set to true if ul_accumulation_time_limit is being passed */ + __u32 ul_accumulation_time_limit; + /* Inform thes remote driver about the time for which data + * is to be accumulated in the uplink direction before it is pushed into + * the IPA (downlink is with respect to the WWAN air interface). + * - Units: milliseconds + * - Maximum value: 255 + */ + + /* Optional */ + /* HW Control Flags */ + __u8 hw_control_flags_valid; + /* Must be set to true if hw_control_flags is being passed */ + __u32 hw_control_flags; + /* Informs the remote driver about the hardware control flags: + * - Bit 0: IPA_HW_FLAG_HALT_SYSTEM_ON_NON_TERMINAL_FAILURE -- + * Indicates to the hardware that it must not continue with + * any subsequent operation even if the failure is not terminal + * - Bit 1: IPA_HW_FLAG_NO_REPORT_MHI_CHANNEL_ERORR -- + * Indicates to the hardware that it is not required to report + * channel errors to the host. + * - Bit 2: IPA_HW_FLAG_NO_REPORT_MHI_CHANNEL_WAKE_UP -- + * Indicates to the hardware that it is not required to generate + * wake-up events to the host. + * - Bit 4: IPA_HW_FLAG_WORK_OVER_DDR -- + * Indicates to the hardware that it is accessing addresses in + * the DDR and not over PCIe + * - Bit 5: IPA_HW_FLAG_INTERRUPT_MODE_CTRL_FLAG -- + * Indicates whether the device must + * raise an event to let the host know that it is going into an + * interrupt mode (no longer polling for data/buffer availability) + */ + + /* Optional */ + /* Uplink MSI Event Threshold */ + __u8 ul_msi_event_threshold_valid; + /* Must be set to true if ul_msi_event_threshold is being passed */ + __u32 ul_msi_event_threshold; + /* Informs the remote driver about the threshold that will + * cause an interrupt (MSI) to be fired to the host. This ensures + * that the remote driver does not accumulate an excesive number of + * events before firing an interrupt. + * This threshold is applicable for data moved in the UL direction. + * - Maximum value: 65535 + */ + + /* Optional */ + /* Downlink MSI Event Threshold */ + __u8 dl_msi_event_threshold_valid; + /* Must be set to true if dl_msi_event_threshold is being passed */ + __u32 dl_msi_event_threshold; + /* Informs the remote driver about the threshold that will + * cause an interrupt (MSI) to be fired to the host. This ensures + * that the remote driver does not accumulate an excesive number of + * events before firing an interrupt + * This threshold is applicable for data that is moved in the + * DL direction - Maximum value: 65535 + */ + + /* Optional */ + /* Uplink Fifo Size */ + __u8 ul_fifo_size_valid; + /* Must be set to true if ul_fifo_size is being passed */ + __u32 ul_fifo_size; + /* + * Informs the remote driver about the total Uplink xDCI + * buffer size that holds the complete aggregated frame + * or BAM data fifo size of the peripheral channel/pipe(in Bytes). + * This deprecates the max_aggr_frame_size field. This TLV + * deprecates max_aggr_frame_size TLV from version 1.9 onwards + * and the max_aggr_frame_size TLV will be ignored in the presence + * of this TLV. + */ + + /* Optional */ + /* Downlink Fifo Size */ + __u8 dl_fifo_size_valid; + /* Must be set to true if dl_fifo_size is being passed */ + __u32 dl_fifo_size; + /* + * Informs the remote driver about the total Downlink xDCI buffering + * capacity or BAM data fifo size of the peripheral channel/pipe. + * (In Bytes). dl_fifo_size = n * dl_buf_size. This deprecates the + * max_aggr_frame_size field. If this value is set + * max_aggr_frame_size is ignored. + */ + + /* Optional */ + /* Downlink Buffer Size */ + __u8 dl_buf_size_valid; + /* Must be set to true if dl_buf_size is being passed */ + __u32 dl_buf_size; + /* Informs the remote driver about the single xDCI buffer size. + * This is applicable only in GSI mode(in Bytes).\n + */ +}; /* Message */ + +/* Response Message; Notifies the remote driver of the configuration + * information + */ +struct ipa_config_resp_msg_v01 { + /* Mandatory */ + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; + /**< Standard response type.*/ +}; /* Message */ + +enum ipa_stats_type_enum_v01 { + IPA_STATS_TYPE_ENUM_MIN_ENUM_VAL_V01 = -2147483647, + /* To force a 32 bit signed enum. Do not change or use */ + QMI_IPA_STATS_TYPE_INVALID_V01 = 0, + /* Invalid stats type identifier */ + QMI_IPA_STATS_TYPE_PIPE_V01 = 1, + /* Pipe stats type */ + QMI_IPA_STATS_TYPE_FILTER_RULES_V01 = 2, + /* Filter rule stats type */ + IPA_STATS_TYPE_ENUM_MAX_ENUM_VAL_V01 = 2147483647 + /* To force a 32 bit signed enum. Do not change or use */ +}; + +struct ipa_pipe_stats_info_type_v01 { + __u32 pipe_index; + /* Pipe index for statistics to be retrieved. */ + + __u64 num_ipv4_packets; + /* Accumulated number of IPv4 packets over this pipe. */ + + __u64 num_ipv4_bytes; + /* Accumulated number of IPv4 bytes over this pipe. */ + + __u64 num_ipv6_packets; + /* Accumulated number of IPv6 packets over this pipe. */ + + __u64 num_ipv6_bytes; + /* Accumulated number of IPv6 bytes over this pipe. */ +}; + +struct ipa_stats_type_filter_rule_v01 { + __u32 filter_rule_index; + /* Filter rule index for statistics to be retrieved. */ + + __u64 num_packets; + /* Accumulated number of packets over this filter rule. */ +}; + +/* Request Message; Retrieve the data statistics collected on modem + * IPA driver. + */ +struct ipa_get_data_stats_req_msg_v01 { + /* Mandatory */ + /* Stats Type */ + enum ipa_stats_type_enum_v01 ipa_stats_type; + /* Indicates the type of statistics to be retrieved. */ + + /* Optional */ + /* Reset Statistics */ + __u8 reset_stats_valid; + /* Must be set to true if reset_stats is being passed */ + __u8 reset_stats; + /* Option to reset the specific type of data statistics + * currently collected. + */ +}; /* Message */ + +/* Response Message; Retrieve the data statistics collected + * on modem IPA driver. + */ +struct ipa_get_data_stats_resp_msg_v01 { + /* Mandatory */ + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; + /* Standard response type. */ + + /* Optional */ + /* Stats Type */ + __u8 ipa_stats_type_valid; + /* Must be set to true if ipa_stats_type is passed */ + enum ipa_stats_type_enum_v01 ipa_stats_type; + /* Indicates the type of statistics that are retrieved. */ + + /* Optional */ + /* Uplink Source Pipe Statistics List */ + __u8 ul_src_pipe_stats_list_valid; + /* Must be set to true if ul_src_pipe_stats_list is being passed */ + __u32 ul_src_pipe_stats_list_len; + /* Must be set to # of elements in ul_src_pipe_stats_list */ + struct ipa_pipe_stats_info_type_v01 + ul_src_pipe_stats_list[QMI_IPA_MAX_PIPES_V01]; + /* List of all Uplink pipe statistics that are retrieved. */ + + /* Optional */ + /* Downlink Destination Pipe Statistics List */ + __u8 dl_dst_pipe_stats_list_valid; + /* Must be set to true if dl_dst_pipe_stats_list is being passed */ + __u32 dl_dst_pipe_stats_list_len; + /* Must be set to # of elements in dl_dst_pipe_stats_list */ + struct ipa_pipe_stats_info_type_v01 + dl_dst_pipe_stats_list[QMI_IPA_MAX_PIPES_V01]; + /* List of all Downlink pipe statistics that are retrieved. */ + + /* Optional */ + /* Downlink Filter Rule Stats List */ + __u8 dl_filter_rule_stats_list_valid; + /* Must be set to true if dl_filter_rule_stats_list is being passed */ + __u32 dl_filter_rule_stats_list_len; + /* Must be set to # of elements in dl_filter_rule_stats_list */ + struct ipa_stats_type_filter_rule_v01 + dl_filter_rule_stats_list[QMI_IPA_MAX_FILTERS_V01]; + /* List of all Downlink filter rule statistics retrieved. */ +}; /* Message */ + +struct ipa_apn_data_stats_info_type_v01 { + __u32 mux_id; + /* Indicates the MUX ID associated with the APN for which the data + * usage statistics is queried + */ + + __u64 num_ul_packets; + /* Accumulated number of uplink packets corresponding to + * this Mux ID + */ + + __u64 num_ul_bytes; + /* Accumulated number of uplink bytes corresponding to + * this Mux ID + */ + + __u64 num_dl_packets; + /* Accumulated number of downlink packets corresponding + * to this Mux ID + */ + + __u64 num_dl_bytes; + /* Accumulated number of downlink bytes corresponding to + * this Mux ID + */ +}; /* Type */ + +/* Request Message; Retrieve the APN data statistics collected from modem */ +struct ipa_get_apn_data_stats_req_msg_v01 { + /* Optional */ + /* Mux ID List */ + __u8 mux_id_list_valid; + /* Must be set to true if mux_id_list is being passed */ + __u32 mux_id_list_len; + /* Must be set to # of elements in mux_id_list */ + __u32 mux_id_list[QMI_IPA_MAX_APN_V01]; + /* The list of MUX IDs associated with APNs for which the data usage + * statistics is being retrieved + */ +}; /* Message */ + +/* Response Message; Retrieve the APN data statistics collected from modem */ +struct ipa_get_apn_data_stats_resp_msg_v01 { + /* Mandatory */ + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; + /* Standard response type.*/ + + /* Optional */ + /* APN Data Statistics List */ + __u8 apn_data_stats_list_valid; + /* Must be set to true if apn_data_stats_list is being passed */ + __u32 apn_data_stats_list_len; + /* Must be set to # of elements in apn_data_stats_list */ + struct ipa_apn_data_stats_info_type_v01 + apn_data_stats_list[QMI_IPA_MAX_APN_V01]; + /* List of APN data retrieved as per request on mux_id. + * For now, only one APN monitoring is supported on modem driver. + * Making this as list for expandability to support more APNs in future. + */ +}; /* Message */ + +struct ipa_data_usage_quota_info_type_v01 { + __u32 mux_id; + /* Indicates the MUX ID associated with the APN for which the data usage + * quota needs to be set + */ + + __u64 num_Mbytes; + /* Number of Mega-bytes of quota value to be set on this APN associated + * with this Mux ID. + */ +}; /* Type */ + +#define IPA_DATA_WARNING_QUOTA + +/* Request Message; Master driver sets a data usage quota value on + * modem driver + */ +struct ipa_set_data_usage_quota_req_msg_v01 { + /* Optional */ + /* APN Quota List */ + __u8 apn_quota_list_valid; + /* Must be set to true if apn_quota_list is being passed */ + __u32 apn_quota_list_len; + /* Must be set to # of elements in apn_quota_list */ + struct ipa_data_usage_quota_info_type_v01 + apn_quota_list[QMI_IPA_MAX_APN_V01]; + /* The list of APNs on which a data usage quota to be set on modem + * driver. For now, only one APN monitoring is supported on modem + * driver. Making this as list for expandability to support more + * APNs in future. + */ + + /* Optional */ + /* APN Warning List */ + __u8 apn_warning_list_valid; + /* Must be set to true if apn_warning_list is being passed */ + __u32 apn_warning_list_len; + /* Must be set to # of elements in apn_warning_list */ + struct ipa_data_usage_quota_info_type_v01 + apn_warning_list[QMI_IPA_MAX_APN_V01]; + /* The list of APNs on which a data usage warning to be set on modem + * driver. For now, only one APN monitoring is supported on modem + * driver. Making this as list for expandability to support more + * APNs in future. + */ + +}; /* Message */ + +/* Response Message; Master driver sets a data usage on modem driver. */ +struct ipa_set_data_usage_quota_resp_msg_v01 { + /* Mandatory */ + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; + /* Standard response type.*/ +}; /* Message */ + +/* Indication Message; Modem driver sends this indication to master + * driver when the data usage quota is reached + */ +struct ipa_data_usage_quota_reached_ind_msg_v01 { + /* Mandatory */ + /* APN Quota List */ + struct ipa_data_usage_quota_info_type_v01 apn; + /* This message indicates which APN has the previously set quota + * or warning reached. For now, only one APN monitoring is supported + * on modem driver. + */ + /* Optional */ + /* Warning Limit reached indication */ + /* Must be set to true if is_warning_limit is being passed */ + __u8 is_warning_limit_valid; + __u8 is_warning_limit; + /* If set to TRUE, Warning Limit is reached. + * If set to FALSE, Quota Limit is reached. + */ +}; /* Message */ + +/* Request Message; Master driver request modem driver to terminate + * the current data usage quota monitoring session. + */ +struct ipa_stop_data_usage_quota_req_msg_v01 { + /* Optional */ + /* Stop monitoring Quota Limit */ + /* Must be set to true if is_quota_limit is being passed */ + __u8 is_quota_limit_valid; + __u8 is_quota_limit; + /* If set to TRUE, Quota Limit will not be monitored */ + + /* Optional */ + /* Stop monitoring Warning Limit */ + /* Must be set to true if is_warning_limit is being passed */ + __u8 is_warning_limit_valid; + __u8 is_warning_limit; + /* If set to TRUE, Warning Limit will not be monitored */ +}; /* Message */ + +/* Response Message; Master driver request modem driver to terminate + * the current quota or warning limit monitoring session. + */ +struct ipa_stop_data_usage_quota_resp_msg_v01 { + /* Mandatory */ + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; + /**< Standard response type.*/ +}; /* Message */ + +/* Request Message; Request from Modem IPA driver to set DPL peripheral pipe */ +struct ipa_install_fltr_rule_req_ex_msg_v01 { + + /* Optional */ + /* Extended Filter Specification */ + __u8 filter_spec_ex_list_valid; + __u32 filter_spec_ex_list_len; + struct ipa_filter_spec_ex_type_v01 + filter_spec_ex_list[QMI_IPA_MAX_FILTERS_EX_V01]; + /* List of filter specifications of filters that must be installed in + * the IPAv3.x hardware. + * The driver installing these rules must do so in the same order as + * specified in this list. + */ + + /* Optional */ + /* Pipe Index to Install Rule */ + __u8 source_pipe_index_valid; + __u32 source_pipe_index; + /* Pipe index to install the filter rule. + * The requester may not always know the pipe indices. If not specified, + * the receiver must install this rule on all pipes that it controls, + * through which data may be fed into the IPA. + */ + + /* Optional */ + /* Total Number of IPv4 Filters in the Filter Spec List */ + __u8 num_ipv4_filters_valid; + __u32 num_ipv4_filters; + /* Number of IPv4 rules included in the filter specification list. */ + + /* Optional */ + /* Total Number of IPv6 Filters in the Filter Spec List */ + __u8 num_ipv6_filters_valid; + __u32 num_ipv6_filters; + /* Number of IPv6 rules included in the filter specification list. */ + + /* Optional */ + /* List of XLAT Filter Indices in the Filter Spec List */ + __u8 xlat_filter_indices_list_valid; + __u32 xlat_filter_indices_list_len; + __u32 xlat_filter_indices_list[QMI_IPA_MAX_FILTERS_EX_V01]; + /* List of XLAT filter indices. + * Filter rules at specified indices must be modified by the + * receiver if the PDN is XLAT before installing them on the associated + * IPA consumer pipe. + */ + + /* Optional */ + /* Extended Type 2 Filter Specification */ + __u8 filter_spec_ex2_list_valid; + /* Must be set to true if filter_spec_ex2_list is being passed */ + __u32 filter_spec_ex2_list_len; + /* Must be set to # of elements in filter_spec_ex2_list */ + struct ipa_filter_spec_ex2_type_v01 + filter_spec_ex2_list[QMI_IPA_MAX_FILTERS_V01]; + /* Optional */ + /* List of modem UL Filters in the Spec List which need be to + * replicated with AP UL firewall filters + */ + __u8 ul_firewall_indices_list_valid; + /* Must be set to # of elements in ul_firewall_indices_list */ + __u32 ul_firewall_indices_list_len; + __u32 ul_firewall_indices_list[QMI_IPA_MAX_FILTERS_V01]; + /* List of UL firewall filter indices. + * Filter rules at specified indices must be replicated across + * the firewall filters by the receiver and installed on the + * associated IPA consumer pipe. + */ +}; /* Message */ + +/* Response Message; Requests installation of filtering rules in the hardware + * block on the remote side. + */ +struct ipa_install_fltr_rule_resp_ex_msg_v01 { + /* Mandatory */ + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; + /* Standard response type. + * Standard response type. Contains the following data members: + * - qmi_result_type -- QMI_RESULT_SUCCESS or QMI_RESULT_FAILURE + * - qmi_error_type -- Error code. Possible error code values are + * described in the error codes + * section of each message + * definition. + */ + + /* Optional */ + /* Rule ID List */ + __u8 rule_id_valid; + __u32 rule_id_len; + __u32 rule_id[QMI_IPA_MAX_FILTERS_EX_V01]; + /* List of rule IDs returned to the control point. + * Any further reference to the rule is done using the filter rule ID + * specified in this list. + */ +}; /* Message */ + +/* + * Request Message; Requests the modem IPA driver to enable or + * disable collection of per client statistics. + */ +struct ipa_enable_per_client_stats_req_msg_v01 { + + /* Mandatory */ + /* Collect statistics per client; */ + __u8 enable_per_client_stats; + /* + * Indicates whether to start or stop collecting + * per client statistics. + */ +}; /* Message */ + +/* + * Response Message; Requests the modem IPA driver to enable or disable + * collection of per client statistics. + */ +struct ipa_enable_per_client_stats_resp_msg_v01 { + + /* Mandatory */ + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; + /* Standard response type. */ +}; /* Message */ + +struct ipa_per_client_stats_info_type_v01 { + + __u32 client_id; + /* + * Id of the client on APPS processor side for which Modem processor + * needs to send uplink/downlink statistics. + */ + + __u32 src_pipe_id; + /* + * IPA consumer pipe on which client on APPS side sent uplink + * data to modem. + */ + + __u64 num_ul_ipv4_bytes; + /* + * Accumulated number of uplink IPv4 bytes for a client. + */ + + __u64 num_ul_ipv6_bytes; + /* + * Accumulated number of uplink IPv6 bytes for a client. + */ + + __u64 num_dl_ipv4_bytes; + /* + * Accumulated number of downlink IPv4 bytes for a client. + */ + + __u64 num_dl_ipv6_bytes; + /* + * Accumulated number of downlink IPv6 byes for a client. + */ + + + __u32 num_ul_ipv4_pkts; + /* + * Accumulated number of uplink IPv4 packets for a client. + */ + + __u32 num_ul_ipv6_pkts; + /* + * Accumulated number of uplink IPv6 packets for a client. + */ + + __u32 num_dl_ipv4_pkts; + /* + * Accumulated number of downlink IPv4 packets for a client. + */ + + __u32 num_dl_ipv6_pkts; + /* + * Accumulated number of downlink IPv6 packets for a client. + */ +}; /* Type */ + +/* + * Request Message; Requests the modem IPA driver to provide statistics + * for a givenclient. + */ +struct ipa_get_stats_per_client_req_msg_v01 { + + /* Mandatory */ + /* Client id */ + __u32 client_id; + /* + * Id of the client on APPS processor side for which Modem processor + * needs to send uplink/downlink statistics. if client id is specified + * as 0xffffffff, then Q6 will send the stats for all the clients of + * the specified source pipe. + */ + + /* Mandatory */ + /* Source pipe id */ + __u32 src_pipe_id; + /* + * IPA consumer pipe on which client on APPS side sent uplink + * data to modem. In future, this implementation can be extended + * to provide 0xffffffff as the source pipe id, where Q6 will send + * the stats of all the clients across all different tethered-pipes. + */ + + /* Optional */ + /* Reset client statistics. */ + __u8 reset_stats_valid; + /* Must be set to true if reset_stats is being passed. */ + __u8 reset_stats; + /* + * Option to reset the statistics currently collected by modem for this + * particular client. + */ +}; /* Message */ + +/* + * Response Message; Requests the modem IPA driver to provide statistics + * for a given client. + */ +struct ipa_get_stats_per_client_resp_msg_v01 { + + /* Mandatory */ + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; + /* Standard response type. */ + + /* Optional */ + /* Per clients Statistics List */ + __u8 per_client_stats_list_valid; + /* Must be set to true if per_client_stats_list is being passed. */ + __u32 per_client_stats_list_len; + /* Must be set to # of elements in per_client_stats_list. */ + struct ipa_per_client_stats_info_type_v01 + per_client_stats_list[QMI_IPA_MAX_PER_CLIENTS_V01]; + /* + * List of all per client statistics that are retrieved. + */ +}; /* Message */ + +struct ipa_ul_firewall_rule_type_v01 { + + enum ipa_ip_type_enum_v01 ip_type; + /* + * IP type for which this rule is applicable. + * The driver must identify the filter table (v6 or v4), and this + * field is essential for that. Values: + * - QMI_IPA_IP_TYPE_INVALID (0) -- Invalid IP type identifier + * - QMI_IPA_IP_TYPE_V4 (1) -- IPv4 type + * - QMI_IPA_IP_TYPE_V6 (2) -- IPv6 type + */ + + struct ipa_filter_rule_type_v01 filter_rule; + /* + * Rules in the filter specification. These rules are the + * ones that are matched against fields in the packet. + * Currently we only send IPv6 whitelist rules to Q6. + */ +}; /* Type */ + +/* + * Request Message; Requestes remote IPA driver to install uplink + * firewall rules. + */ +struct ipa_configure_ul_firewall_rules_req_msg_v01 { + + /* Optional */ + /* Uplink Firewall Specification */ + __u32 firewall_rules_list_len; + /* Must be set to # of elements in firewall_rules_list. */ + struct ipa_ul_firewall_rule_type_v01 + firewall_rules_list[QMI_IPA_MAX_UL_FIREWALL_RULES_V01]; + /* + * List of uplink firewall specifications of filters that must be + * installed. + */ + + __u32 mux_id; + /* + * QMAP Mux ID. As a part of the QMAP protocol, + * several data calls may be multiplexed over the same physical + * transport channel. This identifier is used to identify one + * such data call. The maximum value for this identifier is 255. + */ + + /* Optional */ + __u8 disable_valid; + /* Must be set to true if enable is being passed. */ + __u8 disable; + /* + * Indicates whether uplink firewall needs to be enabled or disabled. + */ + + /* Optional */ + __u8 are_blacklist_filters_valid; + /* Must be set to true if are_blacklist_filters is being passed. */ + __u8 are_blacklist_filters; + /* + * Indicates whether the filters received as part of this message are + * blacklist filters. i.e. drop uplink packets matching these rules. + */ +}; /* Message */ + +/* + * Response Message; Requestes remote IPA driver to install + * uplink firewall rules. + */ +struct ipa_configure_ul_firewall_rules_resp_msg_v01 { + + /* Mandatory */ + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; + /* + * Standard response type. + * Standard response type. Contains the following data members: + * qmi_result_type -- QMI_RESULT_SUCCESS or QMI_RESULT_FAILURE + * qmi_error_type -- Error code. Possible error code values are + * described in the error codes section of each message definition. + */ +}; /* Message */ + +enum ipa_ul_firewall_status_enum_v01 { + IPA_UL_FIREWALL_STATUS_ENUM_MIN_ENUM_VAL_V01 = -2147483647, + /* To force a 32 bit signed enum. Do not change or use*/ + QMI_IPA_UL_FIREWALL_STATUS_SUCCESS_V01 = 0, + /* Indicates that the uplink firewall rules + * are configured successfully. + */ + QMI_IPA_UL_FIREWALL_STATUS_FAILURE_V01 = 1, + /* Indicates that the uplink firewall rules + * are not configured successfully. + */ + IPA_UL_FIREWALL_STATUS_ENUM_MAX_ENUM_VAL_V01 = 2147483647 + /* To force a 32 bit signed enum. Do not change or use*/ +}; + +struct ipa_ul_firewall_config_result_type_v01 { + + enum ipa_ul_firewall_status_enum_v01 is_success; + /* + * Indicates whether the uplink firewall rules are configured + * successfully. + */ + + __u32 mux_id; + /* + * QMAP Mux ID. As a part of the QMAP protocol, + * several data calls may be multiplexed over the same physical + * transport channel. This identifier is used to identify one + * such data call. The maximum value for this identifier is 255. + */ +}; + +/* + * Indication Message; Requestes remote IPA driver to install + * uplink firewall rules. + */ +struct ipa_configure_ul_firewall_rules_ind_msg_v01 { + struct ipa_ul_firewall_config_result_type_v01 result; +}; /* Message */ + + +struct ipa_mhi_ch_init_info_type_v01 { + __u8 ch_id; + /* Remote MHI channel ID */ + + __u8 er_id; + /* Remote MHI Event ring ID */ + + __u32 ch_doorbell_addr; + /* TR Channel Doorbell addr */ + + __u32 er_doorbell_addr; + /* Event ring Doorbell addr */ + + __u32 direction_type; + /* Direction type */ +}; + +struct ipa_mhi_smmu_info_type_v01 { + __u64 iova_ctl_base_addr; + /* IOVA mapped Control Region base address */ + + __u64 iova_ctl_size; + /* IOVA Control region size */ + + __u64 iova_data_base_addr; + /* IOVA mapped Data Region base address */ + + __u64 iova_data_size; + /* IOVA Data Region size */ +}; + +struct ipa_mhi_ready_indication_msg_v01 { + /* Mandatory */ + __u32 ch_info_arr_len; + /* Must be set to # of elements in ch_info_arr. */ + struct ipa_mhi_ch_init_info_type_v01 + ch_info_arr[QMI_IPA_REMOTE_MHI_CHANNELS_NUM_MAX_V01]; + /* Channel Information array */ + + /* Mandatory */ + __u8 smmu_info_valid; + /* Must be set to true if smmu_info is being passed. */ + struct ipa_mhi_smmu_info_type_v01 smmu_info; + /* SMMU enabled indication */ +}; +#define IPA_MHI_READY_INDICATION_MSG_V01_MAX_MSG_LEN 123 + +struct ipa_mhi_mem_addr_info_type_v01 { + __u64 pa; + /* Memory region start physical addr */ + + __u64 iova; + /* Memory region start iova mapped addr */ + + __u64 size; + /* Memory region size */ +}; + +enum ipa_mhi_brst_mode_enum_v01 { + IPA_MHI_BRST_MODE_ENUM_MIN_VAL_V01 = IPA_INT_MIN, + + QMI_IPA_BURST_MODE_DEFAULT_V01 = 0, + /* + * Default - burst mode enabled for hardware channels, + * disabled for software channels + */ + + QMI_IPA_BURST_MODE_ENABLED_V01 = 1, + /* Burst mode is enabled for this channel */ + + QMI_IPA_BURST_MODE_DISABLED_V01 = 2, + /* Burst mode is disabled for this channel */ + + IPA_MHI_BRST_MODE_ENUM_MAX_VAL_V01 = IPA_INT_MAX, +}; + +struct ipa_mhi_tr_info_type_v01 { + __u8 ch_id; + /* TR Channel ID */ + + __u16 poll_cfg; + /* + * Poll Configuration - Default or timer to poll the + * MHI context in milliseconds + */ + + enum ipa_mhi_brst_mode_enum_v01 brst_mode_type; + /* Burst mode configuration */ + + __u64 ring_iova; + /* IOVA mapped ring base address */ + + __u64 ring_len; + /* Ring Length in bytes */ + + __u64 rp; + /* IOVA mapped Read pointer address */ + + __u64 wp; + /* IOVA mapped write pointer address */ +}; + +struct ipa_mhi_er_info_type_v01 { + __u8 er_id; + /* Event ring ID */ + + __u32 intmod_cycles; + /* Interrupt moderation cycles */ + + __u32 intmod_count; + /* Interrupt moderation count */ + + __u32 msi_addr; + /* IOVA mapped MSI address for this ER */ + + __u64 ring_iova; + /* IOVA mapped ring base address */ + + __u64 ring_len; + /* Ring length in bytes */ + + __u64 rp; + /* IOVA mapped Read pointer address */ + + __u64 wp; + /* IOVA mapped Write pointer address */ +}; + +struct ipa_mhi_alloc_channel_req_msg_v01 { + /* Mandatory */ + __u32 tr_info_arr_len; + /* Must be set to # of elements in tr_info_arr. */ + struct ipa_mhi_tr_info_type_v01 + tr_info_arr[QMI_IPA_REMOTE_MHI_CHANNELS_NUM_MAX_V01]; + /* Array of TR context information for Remote MHI channels */ + + /* Mandatory */ + __u32 er_info_arr_len; + /* Must be set to # of elements in er_info_arr. */ + struct ipa_mhi_er_info_type_v01 + er_info_arr[QMI_IPA_REMOTE_MHI_CHANNELS_NUM_MAX_V01]; + /* Array of ER context information for Remote MHI channels */ + + /* Mandatory */ + __u32 ctrl_addr_map_info_len; + /* Must be set to # of elements in ctrl_addr_map_info. */ + + struct ipa_mhi_mem_addr_info_type_v01 + ctrl_addr_map_info[QMI_IPA_REMOTE_MHI_MEMORY_MAPPING_NUM_MAX_V01]; + /* + * List of PA-IOVA address mappings for control regions + * used by Modem + */ + + /* Mandatory */ + __u32 data_addr_map_info_len; + /* Must be set to # of elements in data_addr_map_info. */ + struct ipa_mhi_mem_addr_info_type_v01 + data_addr_map_info[QMI_IPA_REMOTE_MHI_MEMORY_MAPPING_NUM_MAX_V01]; + /* List of PA-IOVA address mappings for data regions used by Modem */ +}; +#define IPA_MHI_ALLOC_CHANNEL_REQ_MSG_V01_MAX_MSG_LEN 808 + +struct ipa_mhi_ch_alloc_resp_type_v01 { + __u8 ch_id; + /* Remote MHI channel ID */ + + __u8 is_success; + /* Channel Allocation Status */ +}; + +struct ipa_mhi_alloc_channel_resp_msg_v01 { + /* Mandatory */ + struct ipa_qmi_response_type_v01 resp; + /* Standard response type. Contains the following data members: + * - qmi_result_type -- QMI_RESULT_SUCCESS or QMI_RESULT_FAILURE + * - qmi_error_type -- Error code. Possible error code values + * are described in the error codes section + * of each message definition. + */ + + /* Optional */ + __u8 alloc_resp_arr_valid; + /* Must be set to true if alloc_resp_arr is being passed. */ + __u32 alloc_resp_arr_len; + /* Must be set to # of elements in alloc_resp_arr. */ + struct ipa_mhi_ch_alloc_resp_type_v01 + alloc_resp_arr[QMI_IPA_REMOTE_MHI_CHANNELS_NUM_MAX_V01]; + /* MHI channel allocation response array */ +}; +#define IPA_MHI_ALLOC_CHANNEL_RESP_MSG_V01_MAX_MSG_LEN 23 + +enum ipa_clock_rate_enum_v01 { + IPA_CLOCK_RATE_ENUM_MIN_ENUM_VAL_V01 = IPA_INT_MIN, + + QMI_IPA_CLOCK_RATE_INVALID_V01 = 0, + + QMI_IPA_CLOCK_RATE_LOW_SVS_V01 = 1, + + QMI_IPA_CLOCK_RATE_SVS_V01 = 2, + + QMI_IPA_CLOCK_RATE_NOMINAL_V01 = 3, + + QMI_IPA_CLOCK_RATE_TURBO_V01 = 4, + + IPA_CLOCK_RATE_ENUM_MAX_ENUM_VAL_V01 = IPA_INT_MAX, +}; + +struct ipa_mhi_clk_vote_req_msg_v01 { + /* Mandatory */ + __u8 mhi_vote; + /* + * MHI vote request + * TRUE - ON + * FALSE - OFF + */ + /* Optional */ + /* Throughput Value */ + __u8 tput_value_valid; + __u32 tput_value; + + /* Optional */ + /* IPA Clock Rate */ + __u8 clk_rate_valid; + enum ipa_clock_rate_enum_v01 clk_rate; +}; +#define IPA_MHI_CLK_VOTE_REQ_MSG_V01_MAX_MSG_LEN 18 + +struct ipa_mhi_clk_vote_resp_msg_v01 { + /* Mandatory */ + struct ipa_qmi_response_type_v01 resp; + /* Standard response type. Contains the following data members: + * - qmi_result_type -- QMI_RESULT_SUCCESS or QMI_RESULT_FAILURE + * - qmi_error_type -- Error code. Possible error code values + * are described in the error codes section + * of each message definition. + */ +}; +#define IPA_MHI_CLK_VOTE_RESP_MSG_V01_MAX_MSG_LEN 7 + +struct ipa_mhi_cleanup_req_msg_v01 { + /* Optional */ + __u8 cleanup_valid; + /* Must be set to true if cleanup is being passed. */ + __u8 cleanup; + /* + * a Flag to indicate the type of action + * 1 - Cleanup Request + */ +}; +#define IPA_MHI_CLEANUP_REQ_MSG_V01_MAX_MSG_LEN 4 + +struct ipa_mhi_cleanup_resp_msg_v01 { + /* Mandatory */ + struct ipa_qmi_response_type_v01 resp; + /* Standard response type. Contains the following data members: + * - qmi_result_type -- QMI_RESULT_SUCCESS or QMI_RESULT_FAILURE + * - qmi_error_type -- Error code. Possible error code values + * are described in the error codes section + * of each message definition. + */ +}; +#define IPA_MHI_CLEANUP_RESP_MSG_V01_MAX_MSG_LEN 7 + +enum ipa_ep_desc_type_enum_v01 { + /* To force a 32 bit signed enum. Do not change or use*/ + IPA_EP_DESC_TYPE_ENUM_MIN_VAL_V01 = IPA_INT_MIN, + DATA_EP_DESC_TYPE_RESERVED_V01 = 0x00, + DATA_EP_DESC_TYPE_EMB_CONS_V01 = 0x01, + DATA_EP_DESC_TYPE_EMB_PROD_V01 = 0x02, + DATA_EP_DESC_TYPE_RSC_PROD_V01 = 0x03, + DATA_EP_DESC_TYPE_QDSS_PROD_V01 = 0x04, + DATA_EP_DESC_TYPE_DPL_PROD_V01 = 0x05, + DATA_EP_DESC_TYPE_TETH_CONS_V01 = 0x06, + DATA_EP_DESC_TYPE_TETH_PROD_V01 = 0x07, + DATA_EP_DESC_TYPE_TETH_RMNET_CONS_V01 = 0x08, + DATA_EP_DESC_TYPE_TETH_RMNET_PROD_V01 = 0x09, + DATA_EP_DESC_TYPE_EMB_FLOW_CTL_CONS_V01 = 0x0A, + DATA_EP_DESC_TYPE_EMB_FLOW_CTL_PROD_V01 = 0x0B, + IPA_EP_DESC_TYPE_ENUM_MAX_VAL_V01 = IPA_INT_MAX, +}; + +enum ipa_ic_type_enum_v01 { + /* To force a 32 bit signed enum. Do not change or use*/ + IPA_IC_TYPE_ENUM_MIN_VAL_V01 = IPA_INT_MIN, + DATA_IC_TYPE_RESERVED_V01 = 0x00, + DATA_IC_TYPE_MHI_V01 = 0x01, + DATA_IC_TYPE_MHI_PRIME_V01 = 0x02, + DATA_IC_TYPE_USB_V01 = 0x03, + DATA_IC_TYPE_AP_V01 = 0x04, + DATA_IC_TYPE_Q6_V01 = 0x05, + DATA_IC_TYPE_UC_V01 = 0x06, + IPA_IC_TYPE_ENUM_MAX_VAL_V01 = IPA_INT_MAX, +}; + +enum ipa_ep_status_type_v01 { + /* To force a 32 bit signed enum. Do not change or use*/ + IPA_EP_STATUS_TYPE_MIN_VAL_V01 = IPA_INT_MIN, + DATA_EP_STATUS_RESERVED_V01 = 0x00, + DATA_EP_STATUS_STATIC_V01 = 0x01, + DATA_EP_STATUS_CONNECTED_V01 = 0x02, + DATA_EP_STATUS_DISCONNECTED_V01 = 0x03, + IPA_EP_STATUS_TYPE_MAX_VAL_V01 = IPA_INT_MAX, +}; + +struct ipa_ep_id_type_v01 { + /* Interconnect type. See ipa_ic_desc_type_enum type */ + enum ipa_ic_type_enum_v01 ic_type; + /* Peripheral end point type */ + enum ipa_ep_desc_type_enum_v01 ep_type; + /* Peripheral interface number */ + __u32 ep_id; + /* Status of endpoint */ + enum ipa_ep_status_type_v01 ep_status; +}; + +struct ipa_endp_desc_indication_msg_v01 { + /* Optional */ + __u8 ep_info_valid; + /* Must be set to true if type_arr is being passed */ + __u32 ep_info_len; + /* Must be set to # of elements in type_arr */ + struct ipa_ep_id_type_v01 ep_info[QMI_IPA_ENDP_DESC_NUM_MAX_V01]; + /* Optional */ + __u8 num_eps_valid; + /* Must be set to true if num_of_eps is being passed */ + /* Must be set to # of elements of num_of_eps */ + __u32 num_eps; +}; /* Message */ +#define IPA_ENDP_DESC_INDICATION_MSG_V01_MAX_MSG_LEN 507 + +enum ipa_aggr_enum_type_v01 { + IPA_AGGR_ENUM_TYPE_MIN_VAL_V01 = IPA_INT_MIN, + DATA_AGGR_TYPE_RESERVED_V01 = 0x00, + DATA_AGGR_TYPE_QMAP_V01 = 0x01, + DATA_AGGR_TYPE_QMAPv5_V01 = 0x02, + DATA_AGGR_TYPE_INHERITED_V01 = 0x03, + IPA_AGGR_ENUM_TYPE_MAX_VAL_V01 = IPA_INT_MAX, +}; + +struct ipa_mhi_prime_aggr_info_type_v01 { + enum ipa_ic_type_enum_v01 ic_type; + /* Peripheral end point type */ + enum ipa_ep_desc_type_enum_v01 ep_type; + /* Bytes count in KB */ + __u32 bytes_count; + /* packet count */ + __u32 pkt_count; + /* aggr_type */ + enum ipa_aggr_enum_type_v01 aggr_type; +}; /* Message */ +#define IPA_MHI_PRIME_AGGR_INFO_REQ_MSG_V01_MAX_MSG_LEN 631 + +struct ipa_mhi_prime_aggr_info_req_msg_v01 { + /* optional */ + __u8 aggr_info_valid; + /* Aggregration info for MHI prime */ + /* Must be set to true if aggr_info is being passed*/ + __u32 aggr_info_len; + /* Must be set to # of elements in aggr_info */ + struct ipa_mhi_prime_aggr_info_type_v01 + aggr_info[QMI_IPA_ENDP_DESC_NUM_MAX_V01]; + /* optional */ + /* Must be set to true if num_eps_valid is being passed*/ + __u8 num_eps_valid; + /* Must be set to # of num_eps */ + __u32 num_eps; +}; /* Message */ +#define IPA_MHI_PRIME_AGGR_INFO_RESP_MSG_V01_MAX_MSG_LEN 7 + +struct ipa_mhi_prime_aggr_info_resp_msg_v01 { + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; +}; /* Message */ + +struct ipa_add_offload_connection_req_msg_v01 { + /* optional */ + /* Must be set to true if num_ipv4_filters is being passed*/ + __u8 num_ipv4_filters_valid; + /* Must be set to # of ipv4_filters*/ + __u32 num_ipv4_filters; + /* optional */ + /* Must be set to true if num_ipv6_filters is being passed*/ + __u8 num_ipv6_filters_valid; + /* Must be set to # of ipv6_filters*/ + __u32 num_ipv6_filters; + /* optional */ + __u8 xlat_filter_indices_list_valid; + /* Must be set to true if xlat_filter_indices_list is being passed*/ + __u32 xlat_filter_indices_list_len; + /* Must be set to # of xlat_filter_indices_list*/ + __u32 xlat_filter_indices_list[QMI_IPA_MAX_FILTERS_V01]; + /* optional */ + /* Must be set to true if filter_spec_ex_list is being passed*/ + __u8 filter_spec_ex2_list_valid; + /* Must be set to # of filter_spec_ex_list*/ + __u32 filter_spec_ex2_list_len; + struct ipa_filter_spec_ex2_type_v01 + filter_spec_ex2_list[QMI_IPA_MAX_FILTERS_V01]; + /* Optional */ + /* Mux ID for embedded call */ + __u8 embedded_call_mux_id_valid; + /* Must be set to true if embedded_call_mux_id is being passed */ + __u32 embedded_call_mux_id; + /* Mux ID for the new embedded call */ + /* Optional */ + /* Default MHI path */ + __u8 default_mhi_path_valid; + /* Must be set to true if default_mhi_path is being passed */ + __u8 default_mhi_path; + /* Default MHI path */ +}; /* Message */ +#define IPA_ADD_OFFLOAD_CONNECTION_REQ_MSG_V01_MAX_MSG_LEN 11361 + +struct ipa_add_offload_connection_resp_msg_v01 { + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; + /* optional */ + /* Must be set to true if filter_handle_list is being passed*/ + __u8 filter_handle_list_valid; + /* Must be set to # of filter_handle_list*/ + __u32 filter_handle_list_len; + struct ipa_filter_rule_identifier_to_handle_map_v01 + filter_handle_list[QMI_IPA_MAX_FILTERS_V01]; +}; /* Message */ +#define IPA_ADD_OFFLOAD_CONNECTION_RESP_MSG_V01_MAX_MSG_LEN 523 + +struct ipa_remove_offload_connection_req_msg_v01 { + /* optional */ + /* Must be set to true if filter_handle_list is being passed*/ + __u8 filter_handle_list_valid; + /* Must be set to # of filter_handle_list*/ + __u32 filter_handle_list_len; + struct ipa_filter_rule_identifier_to_handle_map_v01 + filter_handle_list[QMI_IPA_MAX_FILTERS_V01]; + /* Optional */ + /* Clean All rules */ + __u8 clean_all_rules_valid; + /* Must be set to true if clean_all_rules is being passed */ + __u8 clean_all_rules; + /* Clean All rules */ +}; /* Message */ +#define IPA_REMOVE_OFFLOAD_CONNECTION_REQ_MSG_V01_MAX_MSG_LEN 520 + +struct ipa_remove_offload_connection_resp_msg_v01 { + /* optional */ + /* Must be set to true if filter_handle_list is being passed*/ + __u8 resp_valid; + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; +}; /* Message */ +#define IPA_REMOVE_OFFLOAD_CONNECTION_RESP_MSG_V01_MAX_MSG_LEN 7 + +struct ipa_bw_change_ind_msg_v01 { + /* optional */ + /* Must be set to true if peak_bw_ul is being passed*/ + __u8 peak_bw_ul_valid; + /* Must be set to true if peak_bw_dl is being passed*/ + __u8 peak_bw_dl_valid; + /* Kbps */ + __u32 peak_bw_ul; + /* Kbps */ + __u32 peak_bw_dl; +}; /* Message */ +#define IPA_BW_CHANGE_IND_MSG_V01_MAX_MSG_LEN 14 + +enum ipa_move_nat_type_enum_v01 { + QMI_IPA_MOVE_NAT_TO_DDR_V01 = 0, + QMI_IPA_MOVE_NAT_TO_SRAM_V01 = 1, +}; + +/* + * Request Message; Requestes remote IPA driver to move IPA NAT table + * according to requested direction TO_DDR\TO_SRAM. + */ +struct ipa_move_nat_req_msg_v01 { + enum ipa_move_nat_type_enum_v01 nat_move_direction; +}; +#define IPA_MOVE_NAT_REQ_MSG_V01_MAX_MSG_LEN 8 + +/* + * Response Message; Requestes remote IPA driver to move IPA NAT table + * according to requested direction TO_DDR\TO_SRAM. + */ +struct ipa_move_nat_resp_msg_v01 { + + /* Mandatory */ + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; + /* + * Standard response type. + * Standard response type. Contains the following data members: + * qmi_result_type -- QMI_RESULT_SUCCESS or QMI_RESULT_FAILURE + * qmi_error_type -- Error code. Possible error code values are + * described in the error codes section of each message definition. + */ +}; /* Message */ +#define IPA_MOVE_NAT_RESP_MSG_V01_MAX_MSG_LEN 7 + + /* Indication Message; Indication sent to the Modem IPA driver from + * master IPA driver about NAT table move result. + */ +struct ipa_move_nat_table_complt_ind_msg_v01 { + /* Mandatory */ + /* Master driver initialization completion status */ + struct ipa_qmi_response_type_v01 nat_table_move_status; + /* Indicates the status of nat table mvoe. If everything went + * as expected, this field is set to SUCCESS. ERROR is set + * otherwise. Extended error info may be used to convey + * additional information about the error + */ +}; /* Message */ +#define QMI_IPA_NAT_TABLE_MOVE_COMPLETE_IND_MAX_MSG_LEN_V01 7 + +/* + * Request Message; Sends IPA WLAN OPT DATA PATH RESERVED FILTER REQUEST + */ +struct ipa_wlan_opt_dp_rsrv_filter_req_msg_v01 { + + /* Mandatory */ + /* Number of filters */ + __u8 num_filters; + /* Mandatory */ + /* Timeout value in milisecond */ + __u32 timeout_val_ms; + /* Mandatory */ + /* Q6 routing table index */ + __u32 q6_rtng_table_index; + +}; +#define IPA_WLAN_OPT_DP_RSRV_FILTER_REQ_MSG_V01_MAX_MSG_LEN 18 + +/* + * Response Message; Sent for IPA WLAN OPT DATA PATH RESERVED FILTER REQUEST + */ +struct ipa_wlan_opt_dp_rsrv_filter_resp_msg_v01 { + + /* Mandatory */ + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; + /* + * Standard response type. + * Standard response type. Contains the following data members: + * qmi_result_type -- QMI_RESULT_SUCCESS or QMI_RESULT_FAILURE + * qmi_error_type -- Error code. Possible error code values are + * described in the error codes section of each message definition. + */ +}; +#define IPA_WLAN_OPT_DP_RSRV_FILTER_RESP_MSG_V01_MAX_MSG_LEN 7 + +/* + * Response Message; Indicates completion of reserve filter status + * Apps driver sends indication to the modem driver that filter reservation + * was successful. + */ +struct ipa_wlan_opt_dp_rsrv_filter_complt_ind_msg_v01 { + + /* Mandatory */ + /* Result Code */ + struct ipa_qmi_response_type_v01 rsrv_filter_status; + /* + * Standard response type. + * Standard response type. Contains the following data members: + * qmi_result_type -- QMI_RESULT_SUCCESS or QMI_RESULT_FAILURE + * qmi_error_type -- Error code. Possible error code values are + * described in the error codes section of each message definition. + */ +}; +#define IPA_WLAN_OPT_DP_RSRV_FILTER_COMPLT_IND_MSG_V01_MAX_MSG_LEN 7 + +struct ip_hdr_v4_address_info_v01 { + /* V4 source IP address */ + __u32 source; + /* V4 destination IP address */ + __u32 dest; +}; + +struct ip_hdr_v6_address_info_v01 { + /* V6 source IP address */ + __u32 source[QMI_IPA_IPV6_WORD_ADDR_LEN_V01]; + /* V6 destination IP address */ + __u32 dest[QMI_IPA_IPV6_WORD_ADDR_LEN_V01]; +}; + +/** Request Message; Sends QMI_IPA_WLAN_OPT_DATAPATH_ADD_FILTER_REQ */ +struct ipa_wlan_opt_dp_add_filter_req_msg_v01 { + /* Mandatory */ + /* filter index */ + __u32 filter_idx; + /* Mandatory */ + /* IP type */ + enum ipa_ip_type_enum_v01 ip_type; + /* Optional */ + __u8 v4_addr_valid; /**< Must be set to true if v4_addr is being passed */ + /* IPv4 address */ + struct ip_hdr_v4_address_info_v01 v4_addr; + __u8 v6_addr_valid; /**< Must be set to true if v6_addr is being passed */ + /* IPv6 address */ + struct ip_hdr_v6_address_info_v01 v6_addr; +}; +#define IPA_WLAN_OPT_DP_ADD_FILTER_REQ_MSG_V01_MAX_MSG_LEN 60 + +/* + * Response Message; Indicates completion of add filter status + * Apps driver sends indication to the modem driver that filter addition + * was successful. + */ + +struct ipa_wlan_opt_dp_add_filter_resp_msg_v01 { + + /* Mandatory */ + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; + /* + * Standard response type. + * Standard response type. Contains the following data members: + * qmi_result_type -- QMI_RESULT_SUCCESS or QMI_RESULT_FAILURE + * qmi_error_type -- Error code. Possible error code values are + * described in the error codes section of each message definition. + */ +}; +#define IPA_WLAN_OPT_DP_ADD_FILTER_RESP_MSG_V01_MAX_MSG_LEN 7 + +/* + * Indication Message; Indicates completion of add filter request + * Apps driver sends indication to the modem driver that filter addition + * was successful. + */ +struct ipa_wlan_opt_dp_add_filter_complt_ind_msg_v01 { + /* Mandatory */ + /* Filter removal status */ + struct ipa_qmi_response_type_v01 filter_add_status; + /* + * Standard response type. + * Standard response type. Contains the following data members: + * qmi_result_type -- QMI_RESULT_SUCCESS or QMI_RESULT_FAILURE + * qmi_error_type -- Error code. Possible error code values are + * described in the error codes section of each message definition. + */ + + /* Mandatory */ + /* filter index */ + __u32 filter_idx; + + /* Optional */ + __u8 filter_handle_valid; /**< Must be set to true if filter_handle is being passed */ + + /* filter handle */ + __u32 filter_handle; +}; +#define IPA_WLAN_OPT_DP_ADD_FILTER_COMPLT_IND_MSG_V01_MAX_MSG_LEN 21 + +/* + * Request Message; Sends QMI_IPA_WLAN_OPT_DATAPATH_REMOVE_FILTER_REQ + */ +struct ipa_wlan_opt_dp_remove_filter_req_msg_v01 { + /* Mandatory */ + /* filter index */ + __u32 filter_idx; + + /* Mandatory */ + /* filter handle */ + __u32 filter_handle; +}; +#define IPA_WLAN_OPT_DP_REMOVE_FILTER_REQ_MSG_V01_MAX_MSG_LEN 14 + +/* + * Response Message; Indicates completion of remove filter status + * Apps driver sends indication to the modem driver that filter removal + * was successful. + */ + +struct ipa_wlan_opt_dp_remove_filter_resp_msg_v01 { + + /* Mandatory */ + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; + /* + * Standard response type. + * Standard response type. Contains the following data members: + * qmi_result_type -- QMI_RESULT_SUCCESS or QMI_RESULT_FAILURE + * qmi_error_type -- Error code. Possible error code values are + * described in the error codes section of each message definition. + */ +}; +#define IPA_WLAN_OPT_DP_REMOVE_FILTER_RESP_MSG_V01_MAX_MSG_LEN 7 + +struct ipa_wlan_opt_dp_remove_filter_complt_ind_msg_v01 { + /* Mandatory */ + /* Filter removal status */ + struct ipa_qmi_response_type_v01 filter_removal_status; + /* + * Standard response type. + * Standard response type. Contains the following data members: + * qmi_result_type -- QMI_RESULT_SUCCESS or QMI_RESULT_FAILURE + * qmi_error_type -- Error code. Possible error code values are + * described in the error codes section of each message definition. + */ + + /* Mandatory */ + /* filter index */ + __u32 filter_idx; +}; +#define IPA_WLAN_OPT_DP_REM_FILTER_COMPLT_IND_MSG_V01_MAX_MSG_LEN 14 + +/** Request Message; Sends QMI_IPA_WLAN_OPT_DATAPATH_REMOVE_ALL_FILTER_REQ */ +struct ipa_wlan_opt_dp_remove_all_filter_req_msg_v01 { + + /* Optional */ + /* REMOVE ALL FILTER */ + __u8 reserved_valid; /**< Must be set to true if reserved is being passed */ + __u8 reserved; + +}; +#define IPA_WLAN_OPT_DP_REM_ALL_FILTER_REQ_MSG_V01_MAX_MSG_LEN 4 +/* + * Response Message; Indicates completion of remove filter status + * Apps driver sends indication to the modem driver that filter removal + * was successful. + */ + +struct ipa_wlan_opt_dp_remove_all_filter_resp_msg_v01 { + + /* Mandatory */ + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; + /* + * Standard response type. + * Standard response type. Contains the following data members: + * qmi_result_type -- QMI_RESULT_SUCCESS or QMI_RESULT_FAILURE + * qmi_error_type -- Error code. Possible error code values are + * described in the error codes section of each message definition. + */ +}; +#define IPA_WLAN_OPT_DP_REMOVE_ALL_FILTER_RESP_MSG_V01_MAX_MSG_LEN 7 + +struct ipa_wlan_opt_dp_remove_all_filter_complt_ind_msg_v01 { + + /* Mandatory */ + /* Filter removal status */ + struct ipa_qmi_response_type_v01 filter_removal_all_status; + /* + * Standard response type. + * Standard response type. Contains the following data members: + * qmi_result_type -- QMI_RESULT_SUCCESS or QMI_RESULT_FAILURE + * qmi_error_type -- Error code. Possible error code values are + * described in the error codes section of each message definition. + */ + +}; +#define IPA_WLAN_OPT_DP_REM_ALL_FILTER_COMPLT_IND_MSG_V01_MAX_MSG_LEN 7 + + +struct ipa_wlan_opt_dp_set_wlan_per_info_req_msg_v01 { + + /* Mandatory */ + /* Source WLAN EP ID */ + __u32 src_wlan_endp_id; + /* Mandatory */ + /* Destination WLAN EP ID */ + __u32 dest_wlan_endp_id; + /* Mandatory */ + /* Destination APPS EP ID */ + __u32 dest_apps_endp_id; + /* Mandatory */ + /* HDR LEN */ + __u32 hdr_len; + /* Mandatory */ + /* ETH HDR Offset */ + __u32 eth_hdr_offset; + /* Mandatory */ + /* PARTIAL HDR INFO */ + __u8 hdr_info[QMI_IPA_MAX_ETH_HDR_SIZE_V01]; +}; +#define IPA_WLAN_OPT_DP_SET_WLAN_PER_INFO_REQ_MSG_V1_MAX_MSG_LEN 102 + +/** Response Message; Sends QMI_IPA_WLAN_OPT_DATAPATH_SET_WLAN_PER_INFO_REQ */ +struct ipa_wlan_opt_dp_set_wlan_per_info_resp_msg_v01 { + + /* Mandatory */ + /* Result Code */ + struct ipa_qmi_response_type_v01 resp; + /* + * Standard response type. + * Standard response type. Contains the following data members: + * qmi_result_type -- QMI_RESULT_SUCCESS or QMI_RESULT_FAILURE + * qmi_error_type -- Error code. Possible error code values are + * described in the error codes section of each message definition. + */ + +}; +#define IPA_WLAN_OPT_DP_SET_WLAN_PER_INFO_RESP_MSG_V1_MAX_MSG_LEN 7 + + +/*Service Message Definition*/ +#define QMI_IPA_INDICATION_REGISTER_REQ_V01 0x0020 +#define QMI_IPA_INDICATION_REGISTER_RESP_V01 0x0020 +#define QMI_IPA_INIT_MODEM_DRIVER_REQ_V01 0x0021 +#define QMI_IPA_INIT_MODEM_DRIVER_RESP_V01 0x0021 +#define QMI_IPA_MASTER_DRIVER_INIT_COMPLETE_IND_V01 0x0022 +#define QMI_IPA_INSTALL_FILTER_RULE_REQ_V01 0x0023 +#define QMI_IPA_INSTALL_FILTER_RULE_RESP_V01 0x0023 +#define QMI_IPA_FILTER_INSTALLED_NOTIF_REQ_V01 0x0024 +#define QMI_IPA_FILTER_INSTALLED_NOTIF_RESP_V01 0x0024 +#define QMI_IPA_ENABLE_FORCE_CLEAR_DATAPATH_REQ_V01 0x0025 +#define QMI_IPA_ENABLE_FORCE_CLEAR_DATAPATH_RESP_V01 0x0025 +#define QMI_IPA_DISABLE_FORCE_CLEAR_DATAPATH_REQ_V01 0x0026 +#define QMI_IPA_DISABLE_FORCE_CLEAR_DATAPATH_RESP_V01 0x0026 +#define QMI_IPA_CONFIG_REQ_V01 0x0027 +#define QMI_IPA_CONFIG_RESP_V01 0x0027 +#define QMI_IPA_DISABLE_LINK_LOW_PWR_STATE_REQ_V01 0x0028 +#define QMI_IPA_DISABLE_LINK_LOW_PWR_STATE_RESP_V01 0x0028 +#define QMI_IPA_ENABLE_LINK_LOW_PWR_STATE_REQ_V01 0x0029 +#define QMI_IPA_ENABLE_LINK_LOW_PWR_STATE_RESP_V01 0x0029 +#define QMI_IPA_GET_DATA_STATS_REQ_V01 0x0030 +#define QMI_IPA_GET_DATA_STATS_RESP_V01 0x0030 +#define QMI_IPA_GET_APN_DATA_STATS_REQ_V01 0x0031 +#define QMI_IPA_GET_APN_DATA_STATS_RESP_V01 0x0031 +#define QMI_IPA_SET_DATA_USAGE_QUOTA_REQ_V01 0x0032 +#define QMI_IPA_SET_DATA_USAGE_QUOTA_RESP_V01 0x0032 +#define QMI_IPA_DATA_USAGE_QUOTA_REACHED_IND_V01 0x0033 +#define QMI_IPA_STOP_DATA_USAGE_QUOTA_REQ_V01 0x0034 +#define QMI_IPA_STOP_DATA_USAGE_QUOTA_RESP_V01 0x0034 +#define QMI_IPA_INIT_MODEM_DRIVER_CMPLT_REQ_V01 0x0035 +#define QMI_IPA_INIT_MODEM_DRIVER_CMPLT_RESP_V01 0x0035 +#define QMI_IPA_INSTALL_FILTER_RULE_EX_REQ_V01 0x0037 +#define QMI_IPA_INSTALL_FILTER_RULE_EX_RESP_V01 0x0037 +#define QMI_IPA_ENABLE_PER_CLIENT_STATS_REQ_V01 0x0038 +#define QMI_IPA_ENABLE_PER_CLIENT_STATS_RESP_V01 0x0038 +#define QMI_IPA_GET_STATS_PER_CLIENT_REQ_V01 0x0039 +#define QMI_IPA_GET_STATS_PER_CLIENT_RESP_V01 0x0039 +#define QMI_IPA_INSTALL_UL_FIREWALL_RULES_REQ_V01 0x003A +#define QMI_IPA_INSTALL_UL_FIREWALL_RULES_RESP_V01 0x003A +#define QMI_IPA_INSTALL_UL_FIREWALL_RULES_IND_V01 0x003A +#define QMI_IPA_MHI_CLK_VOTE_REQ_V01 0x003B +#define QMI_IPA_MHI_CLK_VOTE_RESP_V01 0x003B +#define QMI_IPA_MHI_READY_IND_V01 0x003C +#define QMI_IPA_MHI_ALLOC_CHANNEL_REQ_V01 0x003D +#define QMI_IPA_MHI_ALLOC_CHANNEL_RESP_V01 0x003D +#define QMI_IPA_MHI_CLEANUP_REQ_V01 0x003E +#define QMI_IPA_MHI_CLEANUP_RESP_V01 0x003E +#define QMI_IPA_ENDP_DESC_INDICATION_V01 0x003F +#define QMI_IPA_MHI_PRIME_AGGR_INFO_REQ_V01 0x0040 +#define QMI_IPA_MHI_PRIME_AGGR_INFO_RESP_V01 0x0040 +#define QMI_IPA_ADD_OFFLOAD_CONNECTION_REQ_V01 0x0041 +#define QMI_IPA_ADD_OFFLOAD_CONNECTION_RESP_V01 0x0041 +#define QMI_IPA_REMOVE_OFFLOAD_CONNECTION_REQ_V01 0x0042 +#define QMI_IPA_REMOVE_OFFLOAD_CONNECTION_RESP_V01 0x0042 +#define QMI_IPA_BW_CHANGE_INDICATION_V01 0x0044 +#define QMI_IPA_MOVE_NAT_REQ_V01 0x0046 +#define QMI_IPA_MOVE_NAT_RESP_V01 0x0046 +#define QMI_IPA_MOVE_NAT_COMPLETE_IND_V01 0x0046 +#define QMI_IPA_WLAN_OPT_DATAPATH_RSRV_FILTER_REQ_V01 0x0049 +#define QMI_IPA_WLAN_OPT_DATAPATH_RSRV_FILTER_RESP_V01 0x0049 +#define QMI_IPA_WLAN_OPT_DATAPATH_RSRV_FILTER_COMPLT_IND_V01 0x0049 +#define QMI_IPA_WLAN_OPT_DATAPATH_ADD_FILTER_REQ_V01 0x004A +#define QMI_IPA_WLAN_OPT_DATAPATH_ADD_FILTER_RESP_V01 0x004A +#define QMI_IPA_WLAN_OPT_DATAPATH_ADD_FILTER_COMPLT_IND_V01 0x004A +#define QMI_IPA_WLAN_OPT_DATAPATH_REMOVE_FILTER_REQ_V01 0x004B +#define QMI_IPA_WLAN_OPT_DATAPATH_REMOVE_FILTER_RESP_V01 0x004B +#define QMI_IPA_WLAN_OPT_DATAPATH_REMOVE_FILTER_COMPLT_IND_V01 0x004B +#define QMI_IPA_WLAN_OPT_DATAPATH_REMOVE_ALL_FILTER_REQ_V01 0x004C +#define QMI_IPA_WLAN_OPT_DATAPATH_REMOVE_ALL_FILTER_RESP_V01 0x004C +#define QMI_IPA_WLAN_OPT_DATAPATH_REMOVE_ALL_FILTER_COMPLT_IND_V01 0x004C +#define QMI_IPA_WLAN_OPT_DATAPATH_SET_WLAN_PER_INFO_REQ_V01 0x004D +#define QMI_IPA_WLAN_OPT_DATAPATH_SET_WLAN_PER_INFO_RESP_V01 0x004D + + +/* add for max length*/ +#define QMI_IPA_INIT_MODEM_DRIVER_REQ_MAX_MSG_LEN_V01 197 +#define QMI_IPA_INIT_MODEM_DRIVER_RESP_MAX_MSG_LEN_V01 25 +#define QMI_IPA_INDICATION_REGISTER_REQ_MAX_MSG_LEN_V01 16 +#define QMI_IPA_INDICATION_REGISTER_RESP_MAX_MSG_LEN_V01 7 +#define QMI_IPA_INSTALL_FILTER_RULE_REQ_MAX_MSG_LEN_V01 33705 +#define QMI_IPA_INSTALL_FILTER_RULE_RESP_MAX_MSG_LEN_V01 783 +#define QMI_IPA_FILTER_INSTALLED_NOTIF_REQ_MAX_MSG_LEN_V01 1899 +#define QMI_IPA_FILTER_INSTALLED_NOTIF_RESP_MAX_MSG_LEN_V01 7 +#define QMI_IPA_MASTER_DRIVER_INIT_COMPLETE_IND_MAX_MSG_LEN_V01 7 +#define QMI_IPA_DATA_USAGE_QUOTA_REACHED_IND_MAX_MSG_LEN_V01 19 + + +#define QMI_IPA_ENABLE_FORCE_CLEAR_DATAPATH_REQ_MAX_MSG_LEN_V01 37 +#define QMI_IPA_DISABLE_FORCE_CLEAR_DATAPATH_REQ_MAX_MSG_LEN_V01 7 +#define QMI_IPA_ENABLE_FORCE_CLEAR_DATAPATH_RESP_MAX_MSG_LEN_V01 7 +#define QMI_IPA_DISABLE_FORCE_CLEAR_DATAPATH_RESP_MAX_MSG_LEN_V01 7 + + +#define QMI_IPA_CONFIG_REQ_MAX_MSG_LEN_V01 102 +#define QMI_IPA_CONFIG_RESP_MAX_MSG_LEN_V01 7 +#define QMI_IPA_DISABLE_LINK_LOW_PWR_STATE_REQ_MAX_MSG_LEN_V01 18 +#define QMI_IPA_DISABLE_LINK_LOW_PWR_STATE_RESP_MAX_MSG_LEN_V01 7 +#define QMI_IPA_ENABLE_LINK_LOW_PWR_STATE_REQ_MAX_MSG_LEN_V01 7 +#define QMI_IPA_ENABLE_LINK_LOW_PWR_STATE_RESP_MAX_MSG_LEN_V01 7 +#define QMI_IPA_GET_DATA_STATS_REQ_MAX_MSG_LEN_V01 11 +#define QMI_IPA_GET_DATA_STATS_RESP_MAX_MSG_LEN_V01 2234 +#define QMI_IPA_GET_APN_DATA_STATS_REQ_MAX_MSG_LEN_V01 36 +#define QMI_IPA_GET_APN_DATA_STATS_RESP_MAX_MSG_LEN_V01 299 +#define QMI_IPA_SET_DATA_USAGE_QUOTA_REQ_MAX_MSG_LEN_V01 200 +#define QMI_IPA_SET_DATA_USAGE_QUOTA_RESP_MAX_MSG_LEN_V01 7 +#define QMI_IPA_STOP_DATA_USAGE_QUOTA_REQ_MAX_MSG_LEN_V01 8 +#define QMI_IPA_STOP_DATA_USAGE_QUOTA_RESP_MAX_MSG_LEN_V01 7 + +#define QMI_IPA_INIT_MODEM_DRIVER_CMPLT_REQ_MAX_MSG_LEN_V01 4 +#define QMI_IPA_INIT_MODEM_DRIVER_CMPLT_RESP_MAX_MSG_LEN_V01 7 + +#define QMI_IPA_INSTALL_FILTER_RULE_EX_REQ_MAX_MSG_LEN_V01 34021 +#define QMI_IPA_INSTALL_FILTER_RULE_EX_RESP_MAX_MSG_LEN_V01 523 + +#define QMI_IPA_ENABLE_PER_CLIENT_STATS_REQ_MAX_MSG_LEN_V01 4 +#define QMI_IPA_ENABLE_PER_CLIENT_STATS_RESP_MAX_MSG_LEN_V01 7 + +#define QMI_IPA_GET_STATS_PER_CLIENT_REQ_MAX_MSG_LEN_V01 18 +#define QMI_IPA_GET_STATS_PER_CLIENT_RESP_MAX_MSG_LEN_V01 3595 + +#define QMI_IPA_INSTALL_UL_FIREWALL_RULES_REQ_MAX_MSG_LEN_V01 9875 +#define QMI_IPA_INSTALL_UL_FIREWALL_RULES_RESP_MAX_MSG_LEN_V01 7 +#define QMI_IPA_INSTALL_UL_FIREWALL_RULES_IND_MAX_MSG_LEN_V01 11 +/* Service Object Accessor */ + +/* This is the largest MAX_MSG_LEN we have for all the messages + * we expect to receive. This argument will be used in + * qmi_handle_init to allocate a receive buffer for the socket + * associated with our qmi_handle + */ +#define QMI_IPA_MAX_MSG_LEN 22685 + +#endif/* IPA_QMI_SERVICE_V01_H */ diff --git a/include/uapi/linux/msm_ipa.h b/include/uapi/linux/msm_ipa.h new file mode 100644 index 000000000000..0c9ffd639b54 --- /dev/null +++ b/include/uapi/linux/msm_ipa.h @@ -0,0 +1,4015 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2021-2022, 2024 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef _UAPI_MSM_IPA_H_ +#define _UAPI_MSM_IPA_H_ + +#ifndef __KERNEL__ +#include +#include +#include +#include +#endif +#include +#include +#include + +/** + * unique magic number of the IPA device + */ +#define IPA_IOC_MAGIC 0xCF + +/** + * IPA device full path + */ +#define IPA_DEV_NAME "/dev/ipa" + +/** + * IPA NAT table character device name + */ +#define IPA_NAT_DEV_NAME "ipaNatTable" + +/** + * IPA IPv6CT table character device name + */ +#define IPA_IPV6CT_DEV_NAME "ipaIpv6CTTable" + +/** + * name of the default routing tables for v4 and v6 + */ +#define IPA_DFLT_RT_TBL_NAME "ipa_dflt_rt" + +/** + * name for default value of invalid protocol of NAT + */ +#define IPAHAL_NAT_INVALID_PROTOCOL 0xFF + +/** + * commands supported by IPA driver + */ +#define IPA_IOCTL_ADD_HDR 0 +#define IPA_IOCTL_DEL_HDR 1 +#define IPA_IOCTL_ADD_RT_RULE 2 +#define IPA_IOCTL_DEL_RT_RULE 3 +#define IPA_IOCTL_ADD_FLT_RULE 4 +#define IPA_IOCTL_DEL_FLT_RULE 5 +#define IPA_IOCTL_COMMIT_HDR 6 +#define IPA_IOCTL_RESET_HDR 7 +#define IPA_IOCTL_COMMIT_RT 8 +#define IPA_IOCTL_RESET_RT 9 +#define IPA_IOCTL_COMMIT_FLT 10 +#define IPA_IOCTL_RESET_FLT 11 +#define IPA_IOCTL_DUMP 12 +#define IPA_IOCTL_GET_RT_TBL 13 +#define IPA_IOCTL_PUT_RT_TBL 14 +#define IPA_IOCTL_COPY_HDR 15 +#define IPA_IOCTL_QUERY_INTF 16 +#define IPA_IOCTL_QUERY_INTF_TX_PROPS 17 +#define IPA_IOCTL_QUERY_INTF_RX_PROPS 18 +#define IPA_IOCTL_GET_HDR 19 +#define IPA_IOCTL_PUT_HDR 20 +#define IPA_IOCTL_SET_FLT 21 +#define IPA_IOCTL_ALLOC_NAT_MEM 22 +#define IPA_IOCTL_V4_INIT_NAT 23 +#define IPA_IOCTL_TABLE_DMA_CMD 24 +#define IPA_IOCTL_NAT_DMA IPA_IOCTL_TABLE_DMA_CMD +#define IPA_IOCTL_INIT_IPV6CT_TABLE 25 +#define IPA_IOCTL_V4_DEL_NAT 26 +#define IPA_IOCTL_PULL_MSG 27 +#define IPA_IOCTL_GET_NAT_OFFSET 28 +#define IPA_IOCTL_RM_ADD_DEPENDENCY 29 +#define IPA_IOCTL_RM_DEL_DEPENDENCY 30 +#define IPA_IOCTL_GENERATE_FLT_EQ 31 +#define IPA_IOCTL_QUERY_INTF_EXT_PROPS 32 +#define IPA_IOCTL_QUERY_EP_MAPPING 33 +#define IPA_IOCTL_QUERY_RT_TBL_INDEX 34 +#define IPA_IOCTL_WRITE_QMAPID 35 +#define IPA_IOCTL_MDFY_FLT_RULE 36 +#define IPA_IOCTL_NOTIFY_WAN_UPSTREAM_ROUTE_ADD 37 +#define IPA_IOCTL_NOTIFY_WAN_UPSTREAM_ROUTE_DEL 38 +#define IPA_IOCTL_NOTIFY_WAN_EMBMS_CONNECTED 39 +#define IPA_IOCTL_ADD_HDR_PROC_CTX 40 +#define IPA_IOCTL_DEL_HDR_PROC_CTX 41 +#define IPA_IOCTL_MDFY_RT_RULE 42 +#define IPA_IOCTL_ADD_RT_RULE_AFTER 43 +#define IPA_IOCTL_ADD_FLT_RULE_AFTER 44 +#define IPA_IOCTL_GET_HW_VERSION 45 +#define IPA_IOCTL_ADD_RT_RULE_EXT 46 +#define IPA_IOCTL_ADD_VLAN_IFACE 47 +#define IPA_IOCTL_DEL_VLAN_IFACE 48 +#define IPA_IOCTL_ADD_L2TP_VLAN_MAPPING 49 +#define IPA_IOCTL_DEL_L2TP_VLAN_MAPPING 50 +#define IPA_IOCTL_NAT_MODIFY_PDN 51 +#define IPA_IOCTL_ALLOC_NAT_TABLE 52 +#define IPA_IOCTL_ALLOC_IPV6CT_TABLE 53 +#define IPA_IOCTL_DEL_NAT_TABLE 54 +#define IPA_IOCTL_DEL_IPV6CT_TABLE 55 +#define IPA_IOCTL_CLEANUP 56 +#define IPA_IOCTL_QUERY_WLAN_CLIENT 57 +#define IPA_IOCTL_GET_VLAN_MODE 58 +#define IPA_IOCTL_ADD_BRIDGE_VLAN_MAPPING 59 +#define IPA_IOCTL_DEL_BRIDGE_VLAN_MAPPING 60 +#define IPA_IOCTL_ODL_QUERY_ADAPL_EP_INFO 61 +#define IPA_IOCTL_ODL_GET_AGG_BYTE_LIMIT 62 +#define IPA_IOCTL_ODL_QUERY_MODEM_CONFIG 63 +#define IPA_IOCTL_GSB_CONNECT 64 +#define IPA_IOCTL_GSB_DISCONNECT 65 +#define IPA_IOCTL_WIGIG_FST_SWITCH 66 +#define IPA_IOCTL_ADD_RT_RULE_V2 67 +#define IPA_IOCTL_ADD_RT_RULE_EXT_V2 68 +#define IPA_IOCTL_ADD_RT_RULE_AFTER_V2 69 +#define IPA_IOCTL_MDFY_RT_RULE_V2 70 +#define IPA_IOCTL_ADD_FLT_RULE_V2 71 +#define IPA_IOCTL_ADD_FLT_RULE_AFTER_V2 72 +#define IPA_IOCTL_MDFY_FLT_RULE_V2 73 +#define IPA_IOCTL_FNR_COUNTER_ALLOC 74 +#define IPA_IOCTL_FNR_COUNTER_DEALLOC 75 +#define IPA_IOCTL_FNR_COUNTER_QUERY 76 +#define IPA_IOCTL_SET_FNR_COUNTER_INFO 77 +#define IPA_IOCTL_GET_NAT_IN_SRAM_INFO 78 +#define IPA_IOCTL_APP_CLOCK_VOTE 79 +#define IPA_IOCTL_PDN_CONFIG 80 +#define IPA_IOCTL_SET_MAC_FLT 81 +#define IPA_IOCTL_GET_PHERIPHERAL_EP_INFO 82 +#define IPA_IOCTL_ADD_UC_ACT_ENTRY 83 +#define IPA_IOCTL_DEL_UC_ACT_ENTRY 84 +#define IPA_IOCTL_SET_SW_FLT 85 +#define IPA_IOCTL_SET_PKT_THRESHOLD 87 +#define IPA_IOCTL_ADD_EoGRE_MAPPING 88 +#define IPA_IOCTL_DEL_EoGRE_MAPPING 89 +#define IPA_IOCTL_SET_IPPT_SW_FLT 90 +#define IPA_IOCTL_ADD_MACSEC_MAPPING 92 +#define IPA_IOCTL_DEL_MACSEC_MAPPING 93 +#define IPA_IOCTL_SET_NAT_EXC_RT_TBL_IDX 94 +#define IPA_IOCTL_SET_CONN_TRACK_EXC_RT_TBL_IDX 95 +#define IPA_IOCTL_COAL_EVICT_POLICY 96 +#define IPA_IOCTL_SET_EXT_ROUTER_MODE 97 +/** + * max size of the header to be inserted + */ +#define IPA_HDR_MAX_SIZE 255 + +/** + * max size of the name of the resource (routing table, header) + */ +#define IPA_RESOURCE_NAME_MAX 32 + +/** + * max number of interface properties + */ +#define IPA_NUM_PROPS_MAX 35 + +/** + * size of the mac address + */ +#define IPA_MAC_ADDR_SIZE 6 + +/** + * max number of mbim streams + */ +#define IPA_MBIM_MAX_STREAM_NUM 8 + +/** + * size of the ipv6 address + */ +#define IPA_WAN_MSG_IPv6_ADDR_GW_LEN 4 + +/** + * max number of lan clients supported per device type + * for LAN stats via HW. + */ +#define IPA_MAX_NUM_HW_PATH_CLIENTS 16 + +/** + * max number of destination pipes possible for a client. + */ +#define QMI_IPA_MAX_CLIENT_DST_PIPES 4 + +/** + * Max number of clients supported for mac based exception + */ + +#define IPA_MAX_NUM_MAC_FLT 32 +#define IPA_MAX_NUM_IPv4_SEGS_FLT 16 +#define IPA_MAX_NUM_IFACE_FLT 4 + + +/** + * MAX number of the FLT_RT stats counter supported. + */ +#define IPA_MAX_FLT_RT_CNT_INDEX (128) +#define IPA_FLT_RT_HW_COUNTER (120) +#define IPA_FLT_RT_SW_COUNTER \ + (IPA_MAX_FLT_RT_CNT_INDEX - IPA_FLT_RT_HW_COUNTER) +#define IPA_MAX_FLT_RT_CLIENTS 60 + +/** + * Max number of ports/IPs IPPT exception + */ + +#define IPA_MAX_IPPT_NUM_PORT_FLT 5 + +/** + * New feature flag for CV2X config. + */ + +#define IPA_CV2X_SUPPORT + +/** + * the attributes of the rule (routing or filtering) + */ +#define IPA_FLT_TOS (1ul << 0) +#define IPA_FLT_PROTOCOL (1ul << 1) +#define IPA_FLT_SRC_ADDR (1ul << 2) +#define IPA_FLT_DST_ADDR (1ul << 3) +#define IPA_FLT_SRC_PORT_RANGE (1ul << 4) +#define IPA_FLT_DST_PORT_RANGE (1ul << 5) +#define IPA_FLT_TYPE (1ul << 6) +#define IPA_FLT_CODE (1ul << 7) +#define IPA_FLT_SPI (1ul << 8) +#define IPA_FLT_SRC_PORT (1ul << 9) +#define IPA_FLT_DST_PORT (1ul << 10) +#define IPA_FLT_TC (1ul << 11) +#define IPA_FLT_FLOW_LABEL (1ul << 12) +#define IPA_FLT_NEXT_HDR (1ul << 13) +#define IPA_FLT_META_DATA (1ul << 14) +#define IPA_FLT_FRAGMENT (1ul << 15) +#define IPA_FLT_TOS_MASKED (1ul << 16) +#define IPA_FLT_MAC_SRC_ADDR_ETHER_II (1ul << 17) +#define IPA_FLT_MAC_DST_ADDR_ETHER_II (1ul << 18) +#define IPA_FLT_MAC_SRC_ADDR_802_3 (1ul << 19) +#define IPA_FLT_MAC_DST_ADDR_802_3 (1ul << 20) +#define IPA_FLT_MAC_ETHER_TYPE (1ul << 21) +#define IPA_FLT_MAC_DST_ADDR_L2TP (1ul << 22) +#define IPA_FLT_TCP_SYN (1ul << 23) +#define IPA_FLT_TCP_SYN_L2TP (1ul << 24) +#define IPA_FLT_L2TP_INNER_IP_TYPE (1ul << 25) +#define IPA_FLT_L2TP_INNER_IPV4_DST_ADDR (1ul << 26) +#define IPA_FLT_IS_PURE_ACK (1ul << 27) +#define IPA_FLT_VLAN_ID (1ul << 28) +#define IPA_FLT_MAC_SRC_ADDR_802_1Q (1ul << 29) +#define IPA_FLT_MAC_DST_ADDR_802_1Q (1ul << 30) +#define IPA_FLT_L2TP_UDP_INNER_MAC_DST_ADDR (1ul << 31) + +/* Extended attributes for the rule (routing or filtering) */ +#define IPA_FLT_EXT_L2TP_UDP_TCP_SYN (1ul << 0) +#define IPA_FLT_EXT_L2TP_UDP_INNER_ETHER_TYPE (1ul << 1) +#define IPA_FLT_EXT_MTU (1ul << 2) +#define IPA_FLT_EXT_L2TP_UDP_INNER_NEXT_HDR (1ul << 3) +#define IPA_FLT_EXT_NEXT_HDR (1ul << 4) + + +/** + * maximal number of NAT PDNs in the PDN config table + */ +#define IPA_MAX_PDN_NUM 16 +#define IPA_MAX_PDN_NUM_v4 5 + +/** + * Macros duplicated from ipa_lnx_spearhead_stats.h and + * ipa_lnx_stats.h. All three macros should match. + * This needs to be updated whenever the header file structure + * and structure length macros are updated to match exactly + * the same. This is done to overcome backward and forward + * compatibility between userspace and driver spearhead structures. + */ +/* IPA Linux basic stats structure macros */ +#define IPA_LNX_PG_RECYCLE_STATS_STRUCT_LEN 32 +#define IPA_LNX_EXCEPTION_STATS_STRUCT_LEN 40 +#define IPA_LNX_ODL_EP_STATS_STRUCT_LEN 16 +#define IPA_LNX_HOLB_DISCARD_STATS_STRUCT_LEN 16 +#define IPA_LNX_HOLB_MONITOR_STATS_STRUCT_LEN 16 +#define IPA_LNX_HOLB_DROP_AND_MON_STATS_STRUCT_LEN (8 + 16 + 16) +#define IPA_LNX_GENERIC_STATS_STRUCT_LEN (40 + 32 + 40 + 16 + 40) +/* IPA Linux clock stats structures */ +#define IPA_LNX_PM_CLIENT_STATS_STRUCT_LEN 24 +#define IPA_LNX_CLOCK_STATS_STRUCT_LEN (24 + 24) +/* Generic instance structures */ +#define IPA_LNX_GSI_RX_DEBUG_STATS_STRUCT_LEN 48 +#define IPA_LNX_GSI_TX_DEBUG_STATS_STRUCT_LEN 56 +#define IPA_LNX_GSI_DEBUG_STATS_STRUCT_LEN (8 + 48 + 56) +#define IPA_LNX_PIPE_INFO_STATS_STRUCT_LEN 120 +/* IPA Linux wlan instance stats structures */ +#define IPA_LNX_WLAN_INSTANCE_INFO_STRUCT_LEN (32 + 112 + 120) +#define IPA_LNX_WLAN_INST_STATS_STRUCT_LEN (8 + 264) +/* IPA Linux eth instance stats structures */ +#define IPA_LNX_ETH_INSTANCE_INFO_STRUCT_LEN (16 + 112 + 120) +#define IPA_LNX_ETH_INST_STATS_STRUCT_LEN (8 + 248) +/* IPA Linux usb instance stats structures */ +#define IPA_LNX_USB_INSTANCE_INFO_STRUCT_LEN (16 + 112 + 120) +#define IPA_LNX_USB_INST_STATS_STRUCT_LEN (8 + 248) +/* IPA Linux mhip instance stats structures */ +#define IPA_LNX_MHIP_INSTANCE_INFO_STRUCT_LEN (16 + 112 + 120) +#define IPA_LNX_MHIP_INST_STATS_STRUCT_LEN (8 + 248) +/* IPA Linux consolidated stats structure */ +#define IPA_LNX_CONSOLIDATED_STATS_STRUCT_LEN (8 + 48) +/* IPA Linux Instance allocation info structures */ +#define IPA_LNX_EACH_INST_ALLOC_INFO_STRUCT_LEN (24 + 12 + 12 + 16) +#define IPA_LNX_STATS_ALL_INFO_STRUCT_LEN (32 + 128 + 128 + 128) +#define IPA_LNX_STATS_SPEARHEAD_CTX_STRUCT_LEN (8 + 4 + 416) + +/** + * enum ipa_client_type - names for the various IPA "clients" + * these are from the perspective of the clients, for e.g. + * HSIC1_PROD means HSIC client is the producer and IPA is the + * consumer. + * PROD clients are always even, and CONS clients are always odd. + * Add new clients in the end of the list or replace reserved one, + * update IPA_CLIENT_MAX and update the strings array ipa_clients_strings[] + * while keeping the ordering of the clients the same + */ +enum ipa_client_type { + IPA_CLIENT_HSIC1_PROD = 0, + IPA_CLIENT_HSIC1_CONS = 1, + + IPA_CLIENT_HSIC2_PROD = 2, + IPA_CLIENT_HSIC2_CONS = 3, + + IPA_CLIENT_HSIC3_PROD = 4, + IPA_CLIENT_HSIC3_CONS = 5, + + IPA_CLIENT_HSIC4_PROD = 6, + IPA_CLIENT_HSIC4_CONS = 7, + + IPA_CLIENT_HSIC5_PROD = 8, + IPA_CLIENT_HSIC5_CONS = 9, + + IPA_CLIENT_WLAN1_PROD = 10, + IPA_CLIENT_WLAN1_CONS = 11, + + IPA_CLIENT_A5_WLAN_AMPDU_PROD = 12, + IPA_CLIENT_WLAN2_CONS = 13, + + IPA_CLIENT_WLAN3_PROD = 14, + IPA_CLIENT_WLAN3_CONS = 15, + + /* RESERVED PROD = 16, */ + IPA_CLIENT_WLAN4_CONS = 17, + + IPA_CLIENT_USB_PROD = 18, + IPA_CLIENT_USB_CONS = 19, + + IPA_CLIENT_USB2_PROD = 20, + IPA_CLIENT_USB2_CONS = 21, + + IPA_CLIENT_USB3_PROD = 22, + IPA_CLIENT_USB3_CONS = 23, + + IPA_CLIENT_USB4_PROD = 24, + IPA_CLIENT_USB4_CONS = 25, + + IPA_CLIENT_UC_USB_PROD = 26, + IPA_CLIENT_USB_DPL_CONS = 27, + + IPA_CLIENT_A2_EMBEDDED_PROD = 28, + IPA_CLIENT_A2_EMBEDDED_CONS = 29, + + IPA_CLIENT_A2_TETHERED_PROD = 30, + IPA_CLIENT_A2_TETHERED_CONS = 31, + + IPA_CLIENT_APPS_LAN_PROD = 32, + IPA_CLIENT_APPS_LAN_CONS = 33, + + IPA_CLIENT_APPS_WAN_PROD = 34, + IPA_CLIENT_APPS_LAN_WAN_PROD = IPA_CLIENT_APPS_WAN_PROD, + IPA_CLIENT_APPS_WAN_CONS = 35, + + IPA_CLIENT_APPS_CMD_PROD = 36, + IPA_CLIENT_A5_LAN_WAN_CONS = 37, + + IPA_CLIENT_ODU_PROD = 38, + IPA_CLIENT_ODU_EMB_CONS = 39, + + /* RESERVED PROD = 40, */ + IPA_CLIENT_ODU_TETH_CONS = 41, + + IPA_CLIENT_MHI_PROD = 42, + IPA_CLIENT_MHI_CONS = 43, + + IPA_CLIENT_MEMCPY_DMA_SYNC_PROD = 44, + IPA_CLIENT_MEMCPY_DMA_SYNC_CONS = 45, + + IPA_CLIENT_MEMCPY_DMA_ASYNC_PROD = 46, + IPA_CLIENT_MEMCPY_DMA_ASYNC_CONS = 47, + + IPA_CLIENT_ETHERNET_PROD = 48, + IPA_CLIENT_ETHERNET_CONS = 49, + + IPA_CLIENT_Q6_LAN_PROD = 50, + IPA_CLIENT_Q6_LAN_CONS = 51, + + IPA_CLIENT_Q6_WAN_PROD = 52, + IPA_CLIENT_Q6_WAN_CONS = 53, + + IPA_CLIENT_Q6_CMD_PROD = 54, + IPA_CLIENT_Q6_DUN_CONS = 55, + + IPA_CLIENT_Q6_DECOMP_PROD = 56, + IPA_CLIENT_Q6_DECOMP_CONS = 57, + + IPA_CLIENT_Q6_DECOMP2_PROD = 58, + IPA_CLIENT_Q6_DECOMP2_CONS = 59, + + /* RESERVED PROD = 60, */ + IPA_CLIENT_Q6_LTE_WIFI_AGGR_CONS = 61, + + IPA_CLIENT_TEST_PROD = 62, + IPA_CLIENT_TEST_CONS = 63, + + IPA_CLIENT_TEST1_PROD = 64, + IPA_CLIENT_TEST1_CONS = 65, + + IPA_CLIENT_TEST2_PROD = 66, + IPA_CLIENT_TEST2_CONS = 67, + + IPA_CLIENT_TEST3_PROD = 68, + IPA_CLIENT_TEST3_CONS = 69, + + IPA_CLIENT_TEST4_PROD = 70, + IPA_CLIENT_TEST4_CONS = 71, + + /* RESERVED PROD = 72, */ + IPA_CLIENT_DUMMY_CONS = 73, + + IPA_CLIENT_Q6_DL_NLO_DATA_PROD = 74, + IPA_CLIENT_Q6_UL_NLO_DATA_CONS = 75, + + /* RESERVERD PROD = 76, */ + IPA_CLIENT_Q6_UL_NLO_ACK_CONS = 77, + + /* RESERVERD PROD = 78, */ + IPA_CLIENT_Q6_QBAP_STATUS_CONS = 79, + + /* RESERVERD PROD = 80, */ + IPA_CLIENT_MHI_DPL_CONS = 81, + + /* RESERVERD PROD = 82, */ + IPA_CLIENT_ODL_DPL_CONS = 83, + + IPA_CLIENT_Q6_AUDIO_DMA_MHI_PROD = 84, + IPA_CLIENT_Q6_AUDIO_DMA_MHI_CONS = 85, + + IPA_CLIENT_WIGIG_PROD = 86, + IPA_CLIENT_WIGIG1_CONS = 87, + + /* RESERVERD PROD = 88, */ + IPA_CLIENT_WIGIG2_CONS = 89, + + /* RESERVERD PROD = 90, */ + IPA_CLIENT_WIGIG3_CONS = 91, + + /* RESERVERD PROD = 92, */ + IPA_CLIENT_WIGIG4_CONS = 93, + + /* RESERVED PROD = 94, */ + IPA_CLIENT_APPS_WAN_COAL_CONS = 95, + + IPA_CLIENT_MHI_PRIME_TETH_PROD = 96, + IPA_CLIENT_MHI_PRIME_TETH_CONS = 97, + + IPA_CLIENT_MHI_PRIME_RMNET_PROD = 98, + IPA_CLIENT_MHI_PRIME_RMNET_CONS = 99, + + IPA_CLIENT_MHI_PRIME_DPL_PROD = 100, + IPA_CLIENT_MHI_COAL_CONS = 101, + + IPA_CLIENT_AQC_ETHERNET_PROD = 102, + IPA_CLIENT_AQC_ETHERNET_CONS = 103, + + IPA_CLIENT_APPS_WAN_LOW_LAT_PROD = 104, + IPA_CLIENT_APPS_WAN_LOW_LAT_CONS = 105, + + IPA_CLIENT_QDSS_PROD = 106, + IPA_CLIENT_MHI_QDSS_CONS = 107, + + IPA_CLIENT_RTK_ETHERNET_PROD = 108, + IPA_CLIENT_RTK_ETHERNET_CONS = 109, + + IPA_CLIENT_MHI_LOW_LAT_PROD = 110, + IPA_CLIENT_MHI_LOW_LAT_CONS = 111, + + IPA_CLIENT_MHI2_PROD = 112, + IPA_CLIENT_MHI2_CONS = 113, + + IPA_CLIENT_Q6_CV2X_PROD = 114, + IPA_CLIENT_Q6_CV2X_CONS = 115, + + IPA_CLIENT_ETHERNET2_PROD = 116, + IPA_CLIENT_ETHERNET2_CONS = 117, + + /* RESERVED PROD = 118, */ + IPA_CLIENT_WLAN2_CONS1 = 119, + + IPA_CLIENT_APPS_WAN_LOW_LAT_DATA_PROD = 120, + IPA_CLIENT_APPS_WAN_LOW_LAT_DATA_CONS = 121, + + IPA_CLIENT_Q6_DL_NLO_LL_DATA_PROD = 122, + /* RESERVED CONS = 123, */ + + /* RESERVED PROD = 124, */ + IPA_CLIENT_TPUT_CONS = 125, + + /* RESERVED PROD = 126, */ + IPA_CLIENT_APPS_LAN_COAL_CONS = 127, + + IPA_CLIENT_IPSEC_DECAP_PROD = 128, + IPA_CLIENT_IPSEC_DECAP_RECOVERABLE_ERR_CONS = 129, + + IPA_CLIENT_IPSEC_ENCAP_PROD = 130, + IPA_CLIENT_IPSEC_DECAP_NON_RECOVERABLE_ERR_CONS = 131, + + IPA_CLIENT_Q6_DL_NLO_DATA_XLAT_PROD = 132, + IPA_CLIENT_IPSEC_ENCAP_ERR_CONS = 133, + + /* RESERVED PROD = 134, */ + IPA_CLIENT_UC_RTP1_CONS = 135, + + /* RESERVED PROD = 136, */ + IPA_CLIENT_UC_RTP2_CONS = 137, + + /* RESERVED PROD = 138, */ + IPA_CLIENT_UC_RTP3_CONS = 139, + + /* RESERVED PROD = 140, */ + IPA_CLIENT_UC_RTP4_CONS = 141, +}; + +#define IPA_CLIENT_MAX (IPA_CLIENT_UC_RTP4_CONS + 1) + +#define IPA_CLIENT_WLAN2_PROD IPA_CLIENT_A5_WLAN_AMPDU_PROD +#define IPA_CLIENT_Q6_DL_NLO_DATA_PROD IPA_CLIENT_Q6_DL_NLO_DATA_PROD +#define IPA_CLIENT_Q6_UL_NLO_ACK_CONS IPA_CLIENT_Q6_UL_NLO_ACK_CONS +#define IPA_CLIENT_Q6_QBAP_STATUS_CONS IPA_CLIENT_Q6_QBAP_STATUS_CONS +#define IPA_CLIENT_MHI_DPL_CONS IPA_CLIENT_MHI_DPL_CONS +#define IPA_CLIENT_Q6_AUDIO_DMA_MHI_PROD IPA_CLIENT_Q6_AUDIO_DMA_MHI_PROD +#define IPA_CLIENT_Q6_AUDIO_DMA_MHI_CONS IPA_CLIENT_Q6_AUDIO_DMA_MHI_CONS +#define IPA_CLIENT_WIGIG_PROD IPA_CLIENT_WIGIG_PROD +#define IPA_CLIENT_WIGIG1_CONS IPA_CLIENT_WIGIG1_CONS +#define IPA_CLIENT_WIGIG2_CONS IPA_CLIENT_WIGIG2_CONS +#define IPA_CLIENT_WIGIG3_CONS IPA_CLIENT_WIGIG3_CONS +#define IPA_CLIENT_WIGIG4_CONS IPA_CLIENT_WIGIG4_CONS +#define IPA_CLIENT_APPS_WAN_COAL_CONS IPA_CLIENT_APPS_WAN_COAL_CONS +#define IPA_CLIENT_MHI_PRIME_TETH_PROD IPA_CLIENT_MHI_PRIME_TETH_PROD +#define IPA_CLIENT_MHI_PRIME_TETH_CONS IPA_CLIENT_MHI_PRIME_TETH_CONS +#define IPA_CLIENT_MHI_PRIME_RMNET_PROD IPA_CLIENT_MHI_PRIME_RMNET_PROD +#define IPA_CLIENT_MHI_PRIME_RMNET_CONS IPA_CLIENT_MHI_PRIME_RMNET_CONS +#define IPA_CLIENT_MHI_PRIME_DPL_PROD IPA_CLIENT_MHI_PRIME_DPL_PROD +#define IPA_CLIENT_AQC_ETHERNET_PROD IPA_CLIENT_AQC_ETHERNET_PROD +#define IPA_CLIENT_AQC_ETHERNET_CONS IPA_CLIENT_AQC_ETHERNET_CONS +#define IPA_CLIENT_MHI_QDSS_CONS IPA_CLIENT_MHI_QDSS_CONS +#define IPA_CLIENT_QDSS_PROD IPA_CLIENT_QDSS_PROD +#define IPA_CLIENT_WLAN2_CONS1 IPA_CLIENT_WLAN2_CONS1 +#define IPA_CLIENT_APPS_WAN_LOW_LAT_DATA_PROD IPA_CLIENT_APPS_WAN_LOW_LAT_DATA_PROD +#define IPA_CLIENT_APPS_WAN_LOW_LAT_DATA_CONS IPA_CLIENT_APPS_WAN_LOW_LAT_DATA_CONS +#define IPA_CLIENT_Q6_DL_NLO_LL_DATA_PROD IPA_CLIENT_Q6_DL_NLO_LL_DATA_PROD +#define IPA_CLIENT_APPS_LAN_COAL_CONS IPA_CLIENT_APPS_LAN_COAL_CONS +#define IPA_CLIENT_MHI_COAL_CONS IPA_CLIENT_MHI_COAL_CONS +#define IPA_CLIENT_IPSEC_DECAP_PROD IPA_CLIENT_IPSEC_DECAP_PROD +#define IPA_CLIENT_IPSEC_ENCAP_PROD IPA_CLIENT_IPSEC_ENCAP_PROD +#define IPA_CLIENT_Q6_DL_NLO_DATA_XLAT_PROD IPA_CLIENT_Q6_DL_NLO_DATA_XLAT_PROD +#define IPA_CLIENT_IPSEC_DECAP_RECOVERABLE_ERR_CONS IPA_CLIENT_IPSEC_DECAP_RECOVERABLE_ERR_CONS +#define IPA_CLIENT_IPSEC_DECAP_NON_RECOVERABLE_ERR_CONS \ + IPA_CLIENT_IPSEC_DECAP_NON_RECOVERABLE_ERR_CONS +#define IPA_CLIENT_IPSEC_ENCAP_ERR_CONS IPA_CLIENT_IPSEC_ENCAP_ERR_CONS + +#define IPA_CLIENT_IS_APPS_CONS(client) \ + ((client) == IPA_CLIENT_APPS_LAN_CONS || \ + (client) == IPA_CLIENT_APPS_LAN_COAL_CONS || \ + (client) == IPA_CLIENT_APPS_WAN_CONS || \ + (client) == IPA_CLIENT_APPS_WAN_COAL_CONS || \ + (client) == IPA_CLIENT_APPS_WAN_LOW_LAT_CONS || \ + (client) == IPA_CLIENT_APPS_WAN_LOW_LAT_DATA_CONS) + +#define IPA_CLIENT_IS_APPS_PROD(client) \ + ((client) == IPA_CLIENT_APPS_LAN_PROD || \ + (client) == IPA_CLIENT_APPS_WAN_PROD || \ + (client) == IPA_CLIENT_APPS_WAN_LOW_LAT_PROD || \ + (client) == IPA_CLIENT_APPS_WAN_LOW_LAT_DATA_PROD) + +#define IPA_CLIENT_IS_USB_CONS(client) \ + ((client) == IPA_CLIENT_USB_CONS || \ + (client) == IPA_CLIENT_USB2_CONS || \ + (client) == IPA_CLIENT_USB3_CONS || \ + (client) == IPA_CLIENT_USB_DPL_CONS || \ + (client) == IPA_CLIENT_USB4_CONS) + +#define IPA_CLIENT_IS_WAN_CONS(client) \ + ((client) == IPA_CLIENT_APPS_WAN_CONS || \ + (client) == IPA_CLIENT_APPS_WAN_COAL_CONS) + +#define IPA_CLIENT_IS_LAN_CONS(client) \ + ((client) == IPA_CLIENT_APPS_LAN_CONS || \ + (client) == IPA_CLIENT_APPS_LAN_COAL_CONS) + +#define IPA_CLIENT_IS_LAN_or_WAN_CONS(client) \ + ((client) == IPA_CLIENT_APPS_LAN_CONS || \ + (client) == IPA_CLIENT_APPS_WAN_CONS) + +#define IPA_CLIENT_IS_APPS_COAL_CONS(client) \ + ((client) == IPA_CLIENT_APPS_LAN_COAL_CONS || \ + (client) == IPA_CLIENT_APPS_WAN_COAL_CONS) + +#define IPA_CLIENT_IS_LOW_LAT_CONS(client) \ + ((client) == IPA_CLIENT_APPS_WAN_LOW_LAT_CONS) + +#define IPA_CLIENT_IS_WLAN_CONS(client) \ + ((client) == IPA_CLIENT_WLAN1_CONS || \ + (client) == IPA_CLIENT_WLAN2_CONS || \ + (client) == IPA_CLIENT_WLAN3_CONS || \ + (client) == IPA_CLIENT_WLAN2_CONS1 || \ + (client) == IPA_CLIENT_WLAN4_CONS) + +#define IPA_CLIENT_IS_ODU_CONS(client) \ + ((client) == IPA_CLIENT_ODU_EMB_CONS || \ + (client) == IPA_CLIENT_ODU_TETH_CONS) + +#define IPA_CLIENT_IS_Q6_CONS(client) \ + ((client) == IPA_CLIENT_Q6_LAN_CONS || \ + (client) == IPA_CLIENT_Q6_WAN_CONS || \ + (client) == IPA_CLIENT_Q6_DUN_CONS || \ + (client) == IPA_CLIENT_Q6_DECOMP_CONS || \ + (client) == IPA_CLIENT_Q6_DECOMP2_CONS || \ + (client) == IPA_CLIENT_Q6_LTE_WIFI_AGGR_CONS || \ + (client) == IPA_CLIENT_Q6_UL_NLO_DATA_CONS || \ + (client) == IPA_CLIENT_Q6_UL_NLO_ACK_CONS || \ + (client) == IPA_CLIENT_Q6_QBAP_STATUS_CONS || \ + (client) == IPA_CLIENT_Q6_CV2X_CONS || \ + (client) == IPA_CLIENT_Q6_AUDIO_DMA_MHI_CONS) + +#define IPA_CLIENT_IS_Q6_PROD(client) \ + ((client) == IPA_CLIENT_Q6_LAN_PROD || \ + (client) == IPA_CLIENT_Q6_WAN_PROD || \ + (client) == IPA_CLIENT_Q6_CMD_PROD || \ + (client) == IPA_CLIENT_Q6_DECOMP_PROD || \ + (client) == IPA_CLIENT_Q6_DECOMP2_PROD || \ + (client) == IPA_CLIENT_Q6_DL_NLO_LL_DATA_PROD || \ + (client) == IPA_CLIENT_Q6_DL_NLO_DATA_PROD || \ + (client) == IPA_CLIENT_Q6_CV2X_PROD || \ + (client) == IPA_CLIENT_Q6_AUDIO_DMA_MHI_PROD) + +#define IPA_CLIENT_IS_Q6_NON_ZIP_CONS(client) \ + ((client) == IPA_CLIENT_Q6_LAN_CONS || \ + (client) == IPA_CLIENT_Q6_WAN_CONS || \ + (client) == IPA_CLIENT_Q6_DUN_CONS || \ + (client) == IPA_CLIENT_Q6_LTE_WIFI_AGGR_CONS || \ + (client) == IPA_CLIENT_Q6_UL_NLO_DATA_CONS || \ + (client) == IPA_CLIENT_Q6_UL_NLO_ACK_CONS || \ + (client) == IPA_CLIENT_Q6_QBAP_STATUS_CONS || \ + (client) == IPA_CLIENT_Q6_CV2X_CONS || \ + (client) == IPA_CLIENT_Q6_AUDIO_DMA_MHI_CONS) + +#define IPA_CLIENT_IS_Q6_ZIP_CONS(client) \ + ((client) == IPA_CLIENT_Q6_DECOMP_CONS || \ + (client) == IPA_CLIENT_Q6_DECOMP2_CONS) + +#define IPA_CLIENT_IS_Q6_NON_ZIP_PROD(client) \ + ((client) == IPA_CLIENT_Q6_LAN_PROD || \ + (client) == IPA_CLIENT_Q6_WAN_PROD || \ + (client) == IPA_CLIENT_Q6_CMD_PROD || \ + (client) == IPA_CLIENT_Q6_DL_NLO_DATA_PROD || \ + (client) == IPA_CLIENT_Q6_DL_NLO_LL_DATA_PROD || \ + (client) == IPA_CLIENT_Q6_CV2X_PROD || \ + (client) == IPA_CLIENT_Q6_AUDIO_DMA_MHI_PROD) + +#define IPA_CLIENT_IS_Q6_ZIP_PROD(client) \ + ((client) == IPA_CLIENT_Q6_DECOMP_PROD || \ + (client) == IPA_CLIENT_Q6_DECOMP2_PROD) + +#define IPA_CLIENT_IS_MEMCPY_DMA_CONS(client) \ + ((client) == IPA_CLIENT_MEMCPY_DMA_SYNC_CONS || \ + (client) == IPA_CLIENT_MEMCPY_DMA_ASYNC_CONS) + +#define IPA_CLIENT_IS_MEMCPY_DMA_PROD(client) \ + ((client) == IPA_CLIENT_MEMCPY_DMA_SYNC_PROD || \ + (client) == IPA_CLIENT_MEMCPY_DMA_ASYNC_PROD) + +#define IPA_CLIENT_IS_MHI(client) \ + ((client) == IPA_CLIENT_MHI_CONS || \ + (client) == IPA_CLIENT_MHI_PROD || \ + (client) == IPA_CLIENT_MHI2_PROD || \ + (client) == IPA_CLIENT_MHI2_CONS || \ + (client) == IPA_CLIENT_MHI_DPL_CONS || \ + (client) == IPA_CLIENT_MHI_LOW_LAT_CONS || \ + (client) == IPA_CLIENT_MHI_LOW_LAT_PROD || \ + (client) == IPA_CLIENT_MHI_QDSS_CONS || \ + (client) == IPA_CLIENT_MHI_COAL_CONS) + +#define IPA_CLIENT_IS_TEST_PROD(client) \ + ((client) == IPA_CLIENT_TEST_PROD || \ + (client) == IPA_CLIENT_TEST1_PROD || \ + (client) == IPA_CLIENT_TEST2_PROD || \ + (client) == IPA_CLIENT_TEST3_PROD || \ + (client) == IPA_CLIENT_TEST4_PROD) + +#define IPA_CLIENT_IS_TEST_CONS(client) \ + ((client) == IPA_CLIENT_TEST_CONS || \ + (client) == IPA_CLIENT_TEST1_CONS || \ + (client) == IPA_CLIENT_TEST2_CONS || \ + (client) == IPA_CLIENT_TEST3_CONS || \ + (client) == IPA_CLIENT_TEST4_CONS) + +#define IPA_CLIENT_IS_TEST(client) \ + (IPA_CLIENT_IS_TEST_PROD(client) || IPA_CLIENT_IS_TEST_CONS(client)) + +/** + * The following is used to describe the types of memory NAT can + * reside in. + * + * PLEASE KEEP THE FOLLOWING IN SYNC WITH ipa3_nat_mem_in_as_str() + * BELOW. + */ +enum ipa3_nat_mem_in { + IPA_NAT_MEM_IN_DDR = 0, + IPA_NAT_MEM_IN_SRAM = 1, + + IPA_NAT_MEM_IN_MAX +}; + +#define IPA_VALID_NAT_MEM_IN(t) \ + ((t) >= IPA_NAT_MEM_IN_DDR && (t) < IPA_NAT_MEM_IN_MAX) + +/** + * enum ipa_ip_type - Address family: IPv4 or IPv6 + * + * PLEASE KEEP THE FOLLOWING IN SYNC WITH ipa_ip_type_as_str() + * BELOW. + */ +enum ipa_ip_type { + IPA_IP_v4, + IPA_IP_v6, + IPA_IP_MAX +}; + +#define VALID_IPA_IP_TYPE(t) \ + ((t) >= IPA_IP_v4 && (t) < IPA_IP_MAX) + +/** + * enum ipa_rule_type - Type of routing or filtering rule + * Hashable: Rule will be located at the hashable tables + * Non_Hashable: Rule will be located at the non-hashable tables + */ +enum ipa_rule_type { + IPA_RULE_HASHABLE, + IPA_RULE_NON_HASHABLE, +}; +#define IPA_RULE_TYPE_MAX (IPA_RULE_NON_HASHABLE + 1) + +/** + * enum ipa_flt_action - action field of filtering rule + * + * Pass to routing: 5'd0 + * Pass to source NAT: 5'd1 + * Pass to destination NAT: 5'd2 + * Pass to default output pipe (e.g., Apps or Modem): 5'd3 + */ +enum ipa_flt_action { + IPA_PASS_TO_ROUTING, + IPA_PASS_TO_SRC_NAT, + IPA_PASS_TO_DST_NAT, + IPA_PASS_TO_EXCEPTION +}; + +/** + * enum ipa_wlan_event - Events for wlan client + * + * wlan client connect: New wlan client connected + * wlan client disconnect: wlan client disconnected + * wlan client power save: wlan client moved to power save + * wlan client normal: wlan client moved out of power save + * sw routing enable: ipa routing is disabled + * sw routing disable: ipa routing is enabled + * wlan ap connect: wlan AP(access point) is up + * wlan ap disconnect: wlan AP(access point) is down + * wlan sta connect: wlan STA(station) is up + * wlan sta disconnect: wlan STA(station) is down + * wlan client connect ex: new wlan client connected + * wlan scc switch: wlan interfaces in scc mode + * wlan mcc switch: wlan interfaces in mcc mode + * wlan wdi enable: wdi data path completed + * wlan wdi disable: wdi data path teardown + */ +enum ipa_wlan_event { + WLAN_CLIENT_CONNECT, + WLAN_CLIENT_DISCONNECT, + WLAN_CLIENT_POWER_SAVE_MODE, + WLAN_CLIENT_NORMAL_MODE, + SW_ROUTING_ENABLE, + SW_ROUTING_DISABLE, + WLAN_AP_CONNECT, + WLAN_AP_DISCONNECT, + WLAN_STA_CONNECT, + WLAN_STA_DISCONNECT, + WLAN_CLIENT_CONNECT_EX, + WLAN_SWITCH_TO_SCC, + WLAN_SWITCH_TO_MCC, + WLAN_WDI_ENABLE, + WLAN_WDI_DISABLE, + IPA_WLAN_EVENT_MAX +}; + +/** + * enum ipa_wan_event - Events for wan client + * + * wan default route add/del + * wan embms connect: New wan embms interface connected + */ +enum ipa_wan_event { + WAN_UPSTREAM_ROUTE_ADD = IPA_WLAN_EVENT_MAX, + WAN_UPSTREAM_ROUTE_DEL, + WAN_EMBMS_CONNECT, + WAN_XLAT_CONNECT, + IPA_WAN_EVENT_MAX +}; + +enum ipa_ecm_event { + ECM_CONNECT = IPA_WAN_EVENT_MAX, + ECM_DISCONNECT, + IPA_ECM_EVENT_MAX, +}; + +enum ipa_tethering_stats_event { + IPA_TETHERING_STATS_UPDATE_STATS = IPA_ECM_EVENT_MAX, + IPA_TETHERING_STATS_UPDATE_NETWORK_STATS, + IPA_TETHERING_STATS_EVENT_MAX, +}; + + +enum ipa_quota_event { + IPA_QUOTA_REACH = IPA_TETHERING_STATS_EVENT_MAX, + IPA_QUOTA_EVENT_MAX, +}; + +enum ipa_ssr_event { + IPA_SSR_BEFORE_SHUTDOWN = IPA_QUOTA_EVENT_MAX, + IPA_SSR_AFTER_POWERUP, + IPA_SSR_EVENT_MAX, +}; + +enum ipa_vlan_l2tp_event { + ADD_VLAN_IFACE = IPA_SSR_EVENT_MAX, + DEL_VLAN_IFACE, + ADD_L2TP_VLAN_MAPPING, + DEL_L2TP_VLAN_MAPPING, + IPA_VLAN_L2TP_EVENT_MAX, +}; + +enum ipa_per_client_stats_event { + IPA_PER_CLIENT_STATS_CONNECT_EVENT = IPA_VLAN_L2TP_EVENT_MAX, + IPA_PER_CLIENT_STATS_DISCONNECT_EVENT, + IPA_PER_CLIENT_STATS_EVENT_MAX, +}; + +enum ipa_vlan_bridge_event { + ADD_BRIDGE_VLAN_MAPPING = IPA_PER_CLIENT_STATS_EVENT_MAX, + DEL_BRIDGE_VLAN_MAPPING, + BRIDGE_VLAN_MAPPING_MAX, +}; + +enum ipa_wlan_fw_ssr_event { + WLAN_FWR_SSR_BEFORE_SHUTDOWN = BRIDGE_VLAN_MAPPING_MAX, + IPA_WLAN_FW_SSR_EVENT_MAX, +#define IPA_WLAN_FW_SSR_EVENT_MAX IPA_WLAN_FW_SSR_EVENT_MAX +}; + +enum ipa_gsb_event { + IPA_GSB_CONNECT = IPA_WLAN_FW_SSR_EVENT_MAX, + IPA_GSB_DISCONNECT, + IPA_GSB_EVENT_MAX, +}; + +enum ipa_coalesce_event { + IPA_COALESCE_ENABLE = IPA_GSB_EVENT_MAX, + IPA_COALESCE_DISABLE, + IPA_COALESCE_EVENT_MAX +#define IPA_COALESCE_EVENT_MAX IPA_COALESCE_EVENT_MAX +}; + +enum ipa_mtu_event { + IPA_SET_MTU = IPA_COALESCE_EVENT_MAX, + IPA_MTU_EVENT_MAX +#define IPA_MTU_EVENT_MAX IPA_MTU_EVENT_MAX +}; + +enum ipa_peripheral_event { + IPA_PERIPHERAL_CONNECT = ECM_CONNECT, + IPA_PERIPHERAL_DISCONNECT = ECM_DISCONNECT +}; + +#define WIGIG_CLIENT_CONNECT (IPA_MTU_EVENT_MAX) +#define WIGIG_FST_SWITCH (WIGIG_CLIENT_CONNECT + 1) +#define WIGIG_EVENT_MAX (WIGIG_FST_SWITCH + 1) + +enum ipa_pdn_config_event { + IPA_PDN_DEFAULT_MODE_CONFIG = WIGIG_EVENT_MAX, /* Default mode. */ + IPA_PDN_IP_COLLISION_MODE_CONFIG, /* IP Collision detected. */ + IPA_PDN_IP_PASSTHROUGH_MODE_CONFIG, /* IP Passthrough mode. */ + IPA_PDN_CONFIG_EVENT_MAX +#define IPA_PDN_CONFIG_EVENT_MAX IPA_PDN_CONFIG_EVENT_MAX +}; + +enum ipa_mac_flt_event { + IPA_MAC_FLT_EVENT = IPA_PDN_CONFIG_EVENT_MAX, + IPA_MAC_FLT_EVENT_MAX +#define IPA_MAC_FLT_EVENT_MAX IPA_MAC_FLT_EVENT_MAX +}; + +enum ipa_sockv5_event { + IPA_SOCKV5_ADD = IPA_MAC_FLT_EVENT_MAX, + IPA_SOCKV5_DEL, + IPA_SOCKV5_EVENT_MAX +#define IPA_SOCKV5_EVENT_MAX IPA_SOCKV5_EVENT_MAX +}; + +enum ipa_warning_limit_event { + IPA_WARNING_LIMIT_REACHED = IPA_SOCKV5_EVENT_MAX, + IPA_WARNING_LIMIT_EVENT_MAX, +#define IPA_WARNING_LIMIT_EVENT_MAX IPA_WARNING_LIMIT_EVENT_MAX +}; + +enum ipa_sw_flt_event { + IPA_SW_FLT_EVENT = IPA_WARNING_LIMIT_EVENT_MAX, + IPA_SW_FLT_EVENT_MAX +#define IPA_SW_FLT_EVENT_MAX IPA_SW_FLT_EVENT_MAX +}; + +enum ipa_pkt_threshold_event { + IPA_PKT_THRESHOLD_EVENT = IPA_SW_FLT_EVENT_MAX, + IPA_PKT_THRESHOLD_EVENT_MAX +#define IPA_PKT_THRESHOLD_EVENT_MAX IPA_PKT_THRESHOLD_EVENT_MAX +}; + + +enum ipa_move_nat_table_event { + IPA_MOVE_NAT_TABLE = IPA_PKT_THRESHOLD_EVENT_MAX, + IPA_MOVE_NAT_EVENT_MAX +#define IPA_MOVE_NAT_EVENT_MAX IPA_MOVE_NAT_EVENT_MAX +}; + +enum ipa_eogre_event { + IPA_EoGRE_UP_EVENT = IPA_MOVE_NAT_EVENT_MAX, + IPA_EoGRE_DOWN_EVENT, + IPA_EoGRE_EVENT_MAX +#define IPA_EoGRE_EVENT_MAX IPA_EoGRE_EVENT_MAX +}; + +enum ipa_ippt_sw_flt_event { + IPA_IPPT_SW_FLT_EVENT = IPA_EoGRE_EVENT_MAX, + IPA_IPPT_SW_FLT_EVENT_MAX +#define IPA_IPPT_SW_FLT_EVENT_MAX IPA_IPPT_SW_FLT_EVENT_MAX +}; + +enum ipa_macsec_event { + IPA_MACSEC_ADD_EVENT = IPA_IPPT_SW_FLT_EVENT_MAX, + IPA_MACSEC_DEL_EVENT, + IPA_MACSEC_EVENT_MAX +#define IPA_MACSEC_EVENT_MAX IPA_MACSEC_EVENT_MAX +}; + +enum ipa_ext_route_evt { + IPA_SET_EXT_ROUTER_MODE_EVENT = IPA_MACSEC_EVENT_MAX, + IPA_SET_EXT_ROUTER_MODE_EVENT_MAX +#define IPA_SET_EXT_ROUTER_MODE_EVENT_MAX IPA_SET_EXT_ROUTER_MODE_EVENT_MAX +}; + +#define IPA_EVENT_MAX_NUM (IPA_SET_EXT_ROUTER_MODE_EVENT_MAX) +#define IPA_EVENT_MAX ((int)IPA_EVENT_MAX_NUM) + +/** + * enum ipa_rm_resource_name - IPA RM clients identification names + * + * PROD resources are always even, and CONS resources are always odd. + * Add new clients in the end of the list and update IPA_RM_RESOURCE_MAX + */ +enum ipa_rm_resource_name { + IPA_RM_RESOURCE_Q6_PROD = 0, + IPA_RM_RESOURCE_Q6_CONS = 1, + + IPA_RM_RESOURCE_USB_PROD = 2, + IPA_RM_RESOURCE_USB_CONS = 3, + + IPA_RM_RESOURCE_USB_DPL_DUMMY_PROD = 4, + IPA_RM_RESOURCE_USB_DPL_CONS = 5, + + IPA_RM_RESOURCE_HSIC_PROD = 6, + IPA_RM_RESOURCE_HSIC_CONS = 7, + + IPA_RM_RESOURCE_STD_ECM_PROD = 8, + IPA_RM_RESOURCE_APPS_CONS = 9, + + IPA_RM_RESOURCE_RNDIS_PROD = 10, + /* RESERVED CONS = 11, */ + + IPA_RM_RESOURCE_WWAN_0_PROD = 12, + /* RESERVED CONS = 13, */ + + IPA_RM_RESOURCE_WLAN_PROD = 14, + IPA_RM_RESOURCE_WLAN_CONS = 15, + + IPA_RM_RESOURCE_ODU_ADAPT_PROD = 16, + IPA_RM_RESOURCE_ODU_ADAPT_CONS = 17, + + IPA_RM_RESOURCE_MHI_PROD = 18, + IPA_RM_RESOURCE_MHI_CONS = 19, + + IPA_RM_RESOURCE_ETHERNET_PROD = 20, + IPA_RM_RESOURCE_ETHERNET_CONS = 21, +}; +#define IPA_RM_RESOURCE_MAX (IPA_RM_RESOURCE_ETHERNET_CONS + 1) + +/** + * enum ipa_hw_type - IPA hardware version type + * @IPA_HW_None: IPA hardware version not defined + * @IPA_HW_v1_0: IPA hardware version 1.0 + * @IPA_HW_v1_1: IPA hardware version 1.1 + * @IPA_HW_v2_0: IPA hardware version 2.0 + * @IPA_HW_v2_1: IPA hardware version 2.1 + * @IPA_HW_v2_5: IPA hardware version 2.5 + * @IPA_HW_v2_6: IPA hardware version 2.6 + * @IPA_HW_v2_6L: IPA hardware version 2.6L + * @IPA_HW_v3_0: IPA hardware version 3.0 + * @IPA_HW_v3_1: IPA hardware version 3.1 + * @IPA_HW_v3_5: IPA hardware version 3.5 + * @IPA_HW_v3_5_1: IPA hardware version 3.5.1 + * @IPA_HW_v4_0: IPA hardware version 4.0 + * @IPA_HW_v4_1: IPA hardware version 4.1 + * @IPA_HW_v4_2: IPA hardware version 4.2 + * @IPA_HW_v4_5: IPA hardware version 4.5 + * @IPA_HW_v4_7: IPA hardware version 4.7 + * @IPA_HW_v4_9: IPA hardware version 4.9 + * @IPA_HW_v4_11: IPA hardware version 4.11 + * @IPA_HW_v5_0: IPA hardware version 5.0 + * @IPA_HW_v5_1: IPA hardware version 5.1 + * @IPA_HW_v5_2: IPA hardware version 5.2 + * @IPA_HW_v5_5: IPA hardware version 5.5 + * @IPA_HW_v6_0: IPA hardware version 6.0 + */ +enum ipa_hw_type { + IPA_HW_None = 0, + IPA_HW_v1_0 = 1, + IPA_HW_v1_1 = 2, + IPA_HW_v2_0 = 3, + IPA_HW_v2_1 = 4, + IPA_HW_v2_5 = 5, + IPA_HW_v2_6 = IPA_HW_v2_5, + IPA_HW_v2_6L = 6, + IPA_HW_v3_0 = 10, + IPA_HW_v3_1 = 11, + IPA_HW_v3_5 = 12, + IPA_HW_v3_5_1 = 13, + IPA_HW_v4_0 = 14, + IPA_HW_v4_1 = 15, + IPA_HW_v4_2 = 16, + IPA_HW_v4_5 = 17, + IPA_HW_v4_7 = 18, + IPA_HW_v4_9 = 19, + IPA_HW_v4_11 = 20, + IPA_HW_v5_0 = 21, + IPA_HW_v5_1 = 22, + IPA_HW_v5_2 = 23, + IPA_HW_v5_5 = 24, + IPA_HW_v6_0 = 25, +}; + +#define IPA_HW_MAX (IPA_HW_v6_0 + 1) + +#define IPA_HW_v4_0 IPA_HW_v4_0 +#define IPA_HW_v4_1 IPA_HW_v4_1 +#define IPA_HW_v4_2 IPA_HW_v4_2 +#define IPA_HW_v4_5 IPA_HW_v4_5 +#define IPA_HW_v4_7 IPA_HW_v4_7 +#define IPA_HW_v4_9 IPA_HW_v4_9 +#define IPA_HW_v4_11 IPA_HW_v4_11 +#define IPA_HW_v5_0 IPA_HW_v5_0 +#define IPA_HW_v5_1 IPA_HW_v5_1 +#define IPA_HW_v5_2 IPA_HW_v5_2 +#define IPA_HW_v5_5 IPA_HW_v5_5 +#define IPA_HW_v6_0 IPA_HW_v6_0 + +/** + * struct ipa_rule_attrib - attributes of a routing/filtering + * rule, all in LE + * @attrib_mask: what attributes are valid + * @src_port_lo: low port of src port range + * @src_port_hi: high port of src port range + * @dst_port_lo: low port of dst port range + * @dst_port_hi: high port of dst port range + * @type: ICMP/IGMP type + * @code: ICMP/IGMP code + * @spi: IPSec SPI + * @src_port: exact src port + * @dst_port: exact dst port + * @meta_data: metadata val + * @meta_data_mask: metadata mask + * @u.v4.tos: type of service + * @u.v4.protocol: protocol + * @u.v4.src_addr: src address value + * @u.v4.src_addr_mask: src address mask + * @u.v4.dst_addr: dst address value + * @u.v4.dst_addr_mask: dst address mask + * @u.v6.tc: traffic class + * @u.v6.flow_label: flow label + * @u.v6.next_hdr: next header + * @u.v6.src_addr: src address val + * @u.v6.src_addr_mask: src address mask + * @u.v6.dst_addr: dst address val + * @u.v6.dst_addr_mask: dst address mask + * @vlan_id: vlan id value + * @payload_length: Payload length. + * @ext_attrib_mask: Extended attributes. + * @l2tp_udp_next_hdr: next header in L2TP tunneling + * @frag_encoding: is-frag equation + */ +struct ipa_rule_attrib { + uint32_t attrib_mask; + uint16_t src_port_lo; + uint16_t src_port_hi; + uint16_t dst_port_lo; + uint16_t dst_port_hi; + uint8_t type; + uint8_t code; + uint8_t tos_value; + uint8_t tos_mask; + uint32_t spi; + uint16_t src_port; + uint16_t dst_port; + uint32_t meta_data; + uint32_t meta_data_mask; + uint8_t src_mac_addr[ETH_ALEN]; + uint8_t src_mac_addr_mask[ETH_ALEN]; + uint8_t dst_mac_addr[ETH_ALEN]; + uint8_t dst_mac_addr_mask[ETH_ALEN]; + uint16_t ether_type; + union { + struct { + uint8_t tos; + uint8_t protocol; + uint32_t src_addr; + uint32_t src_addr_mask; + uint32_t dst_addr; + uint32_t dst_addr_mask; + } v4; + struct { + uint8_t tc; + uint32_t flow_label; + uint8_t next_hdr; + uint32_t src_addr[4]; + uint32_t src_addr_mask[4]; + uint32_t dst_addr[4]; + uint32_t dst_addr_mask[4]; + } v6; + } u; + __u16 vlan_id; + __u16 payload_length; + __u32 ext_attrib_mask; + __u8 l2tp_udp_next_hdr; + __u8 is_frag_encoding; + __u32 padding2; +}; + + +/*! @brief The maximum number of Mask Equal 32 Eqns */ +#define IPA_IPFLTR_NUM_MEQ_32_EQNS 2 + +/*! @brief The maximum number of IHL offset Mask Equal 32 Eqns */ +#define IPA_IPFLTR_NUM_IHL_MEQ_32_EQNS 2 + +/*! @brief The maximum number of Mask Equal 128 Eqns */ +#define IPA_IPFLTR_NUM_MEQ_128_EQNS 2 + +/*! @brief The maximum number of IHL offset Range Check 16 Eqns */ +#define IPA_IPFLTR_NUM_IHL_RANGE_16_EQNS 2 + +/*! @brief Offset and 16 bit comparison equation */ +struct ipa_ipfltr_eq_16 { + int8_t offset; + uint16_t value; +}; + +/*! @brief Offset and 32 bit comparison equation */ +struct ipa_ipfltr_eq_32 { + int8_t offset; + uint32_t value; +}; + +/*! @brief Offset and 128 bit masked comparison equation */ +struct ipa_ipfltr_mask_eq_128 { + int8_t offset; + uint8_t mask[16]; + uint8_t value[16]; +}; + +/*! @brief Offset and 32 bit masked comparison equation */ +struct ipa_ipfltr_mask_eq_32 { + int8_t offset; + uint32_t mask; + uint32_t value; +}; + +/*! @brief Equation for identifying a range. Ranges are inclusive */ +struct ipa_ipfltr_range_eq_16 { + int8_t offset; + uint16_t range_low; + uint16_t range_high; +}; + +/*! @brief Rule equations which are set according to DS filter installation */ +struct ipa_ipfltri_rule_eq { + /*! 16-bit Bitmask to indicate how many eqs are valid in this rule */ + uint16_t rule_eq_bitmap; + + /* + * tos_eq_present field has two meanings: + * IPA ver < 4.5: + * specifies if a type of service check rule is present + * (as the field name reveals). + * IPA ver >= 4.5: + * specifies if a tcp pure ack check rule is present + */ + uint8_t tos_eq_present; + /*! The value to check against the type of service (ipv4) field */ + uint8_t tos_eq; + /*! Specifies if a protocol check rule is present */ + uint8_t protocol_eq_present; + /*! The value to check against the protocol (ipv6) field */ + uint8_t protocol_eq; + /*! The number of ip header length offset 16 bit range check + * rules in this rule + */ + uint8_t num_ihl_offset_range_16; + /*! An array of the registered ip header length offset 16 bit + * range check rules + */ + struct ipa_ipfltr_range_eq_16 + ihl_offset_range_16[IPA_IPFLTR_NUM_IHL_RANGE_16_EQNS]; + /*! The number of mask equal 32 rules present in this rule */ + uint8_t num_offset_meq_32; + /*! An array of all the possible mask equal 32 rules in this rule */ + struct ipa_ipfltr_mask_eq_32 + offset_meq_32[IPA_IPFLTR_NUM_MEQ_32_EQNS]; + /*! Specifies if the traffic class rule is present in this rule */ + uint8_t tc_eq_present; + /*! The value to check the traffic class (ipv4) field against */ + uint8_t tc_eq; + /*! Specifies if the flow equals rule is present in this rule */ + uint8_t fl_eq_present; + /*! The value to check the flow (ipv6) field against */ + uint32_t fl_eq; + /*! The number of ip header length offset 16 bit equations in this + * rule + */ + uint8_t ihl_offset_eq_16_present; + /*! The ip header length offset 16 bit equation */ + struct ipa_ipfltr_eq_16 ihl_offset_eq_16; + /*! The number of ip header length offset 32 bit equations in this + * rule + */ + uint8_t ihl_offset_eq_32_present; + /*! The ip header length offset 32 bit equation */ + struct ipa_ipfltr_eq_32 ihl_offset_eq_32; + /*! The number of ip header length offset 32 bit mask equations in + * this rule + */ + uint8_t num_ihl_offset_meq_32; + /*! The ip header length offset 32 bit mask equation */ + struct ipa_ipfltr_mask_eq_32 + ihl_offset_meq_32[IPA_IPFLTR_NUM_IHL_MEQ_32_EQNS]; + /*! The number of ip header length offset 128 bit equations in this + * rule + */ + uint8_t num_offset_meq_128; + /*! The ip header length offset 128 bit equation */ + struct ipa_ipfltr_mask_eq_128 + offset_meq_128[IPA_IPFLTR_NUM_MEQ_128_EQNS]; + /*! The metadata 32 bit masked comparison equation present or not */ + /* Metadata based rules are added internally by IPA driver */ + uint8_t metadata_meq32_present; + /*! The metadata 32 bit masked comparison equation */ + struct ipa_ipfltr_mask_eq_32 metadata_meq32; + /*! Specifies if the Fragment equation is present in this rule */ + uint8_t ipv4_frag_eq_present; + /*! The IS-FRAG equation enhancement change since IPA6.0 + * values: IS-FRAG-0, Is-Primary-1, Is-Secondary-2, Not-Frag-3 + */ + uint8_t is_frag_encoding; +}; + +/** + * struct ipa_flt_rule - attributes of a filtering rule + * @retain_hdr: bool switch to instruct IPA core to add back to the packet + * the header removed as part of header removal + * @to_uc: bool switch to pass packet to micro-controller + * @action: action field + * @rt_tbl_hdl: handle of table from "get" + * @attrib: attributes of the rule + * @eq_attrib: attributes of the rule in equation form (valid when + * eq_attrib_type is true) + * @rt_tbl_idx: index of RT table referred to by filter rule (valid when + * eq_attrib_type is true and non-exception action) + * @eq_attrib_type: true if equation level form used to specify attributes + * @max_prio: bool switch. is this rule with Max priority? meaning on rule hit, + * IPA will use the rule and will not look for other rules that may have + * higher priority + * @hashable: bool switch. is this rule hashable or not? + * ipa uses hashable rules to cache their hit results to be used in + * consecutive packets + * @rule_id: rule_id to be assigned to the filter rule. In case client specifies + * rule_id as 0 the driver will assign a new rule_id + * @set_metadata: bool switch. should metadata replacement at the NAT block + * take place? + * @pdn_idx: if action is "pass to source\destination NAT" then a comparison + * against the PDN index in the matching PDN entry will take place as an + * additional condition for NAT hit. + */ +struct ipa_flt_rule { + uint8_t retain_hdr; + uint8_t to_uc; + enum ipa_flt_action action; + uint32_t rt_tbl_hdl; + struct ipa_rule_attrib attrib; + struct ipa_ipfltri_rule_eq eq_attrib; + uint32_t rt_tbl_idx; + uint8_t eq_attrib_type; + uint8_t max_prio; + uint8_t hashable; + uint16_t rule_id; + uint8_t set_metadata; + uint8_t pdn_idx; +}; + +#define IPA_FLTRT_TTL_UPDATE + +/** + * struct ipa_flt_rule_v2 - attributes of a filtering rule + * @retain_hdr: bool switch to instruct IPA core to add back to the packet + * the header removed as part of header removal + * @to_uc: bool switch to pass packet to micro-controller + * @action: action field + * @rt_tbl_hdl: handle of table from "get" + * @attrib: attributes of the rule + * @eq_attrib: attributes of the rule in equation form (valid when + * eq_attrib_type is true) + * @rt_tbl_idx: index of RT table referred to by filter rule (valid when + * eq_attrib_type is true and non-exception action) + * @eq_attrib_type: true if equation level form used to specify attributes + * @max_prio: bool switch. is this rule with Max priority? meaning on rule hit, + * IPA will use the rule and will not look for other rules that may have + * higher priority + * @hashable: bool switch. is this rule hashable or not? + * ipa uses hashable rules to cache their hit results to be used in + * consecutive packets + * @rule_id: rule_id to be assigned to the filter rule. In case client specifies + * rule_id as 0 the driver will assign a new rule_id + * @set_metadata: bool switch. should metadata replacement at the NAT block + * take place? + * @pdn_idx: if action is "pass to source\destination NAT" then a comparison + * against the PDN index in the matching PDN entry will take place as an + * additional condition for NAT hit. + * @enable_stats: is true when we want to enable stats for this + * flt rule. + * @cnt_idx: if 0 means disable, otherwise use for index. + * will be assigned by ipa driver. + * @close_aggr_irq_mod: close aggregation/coalescing and close GSI + * interrupt moderation + * @ttl_update: bool to indicate whether TTL update is needed or not. + * @qos_class: QOS classification value. + */ +struct ipa_flt_rule_v2 { + uint8_t retain_hdr; + uint8_t to_uc; + enum ipa_flt_action action; + uint32_t rt_tbl_hdl; + struct ipa_rule_attrib attrib; + struct ipa_ipfltri_rule_eq eq_attrib; + uint32_t rt_tbl_idx; + uint8_t eq_attrib_type; + uint8_t max_prio; + uint8_t hashable; + uint16_t rule_id; + uint8_t set_metadata; + uint8_t pdn_idx; + uint8_t enable_stats; + uint8_t cnt_idx; + uint8_t close_aggr_irq_mod; + uint8_t ttl_update; + uint8_t qos_class; +}; + +/** + * enum ipa_hdr_l2_type - L2 header type + * IPA_HDR_L2_NONE: L2 header which isn't Ethernet II and isn't 802_3 + * IPA_HDR_L2_ETHERNET_II: L2 header of type Ethernet II + * IPA_HDR_L2_802_3: L2 header of type 802_3 + * IPA_HDR_L2_802_1Q: L2 header of type 802_1Q + */ +enum ipa_hdr_l2_type { + IPA_HDR_L2_NONE, + IPA_HDR_L2_ETHERNET_II, + IPA_HDR_L2_802_3, + IPA_HDR_L2_802_1Q, +}; +#define IPA_HDR_L2_MAX (IPA_HDR_L2_802_1Q + 1) + +#define IPA_HDR_L2_802_1Q IPA_HDR_L2_802_1Q + +/** + * enum ipa_hdr_l2_type - Processing context type + * + * IPA_HDR_PROC_NONE: No processing context + * IPA_HDR_PROC_ETHII_TO_ETHII: Process Ethernet II to Ethernet II + * IPA_HDR_PROC_ETHII_TO_802_3: Process Ethernet II to 802_3 + * IPA_HDR_PROC_802_3_TO_ETHII: Process 802_3 to Ethernet II + * IPA_HDR_PROC_802_3_TO_802_3: Process 802_3 to 802_3 + * IPA_HDR_PROC_L2TP_HEADER_ADD: + * IPA_HDR_PROC_L2TP_HEADER_REMOVE: + * IPA_HDR_PROC_ETHII_TO_ETHII_EX: Process Ethernet II to Ethernet II with + * generic lengths of src and dst headers + * IPA_HDR_PROC_L2TP_UDP_HEADER_ADD: Process WLAN To Ethernet packets to + * add L2TP UDP header. + * IPA_HDR_PROC_L2TP_UDP_HEADER_REMOVE: Process Ethernet To WLAN packets to + * remove L2TP UDP header. + * IPA_HDR_PROC_SET_DSCP: + * IPA_HDR_PROC_EoGRE_HEADER_ADD: Add IPV[46] GRE header + * IPA_HDR_PROC_EoGRE_HEADER_REMOVE: Remove IPV[46] GRE header + * IPA_HDR_PROC_RTP_METADATA_STREAM: Process RTP Frames at uCP + */ +enum ipa_hdr_proc_type { + IPA_HDR_PROC_NONE, + IPA_HDR_PROC_ETHII_TO_ETHII, + IPA_HDR_PROC_ETHII_TO_802_3, + IPA_HDR_PROC_802_3_TO_ETHII, + IPA_HDR_PROC_802_3_TO_802_3, + IPA_HDR_PROC_L2TP_HEADER_ADD, + IPA_HDR_PROC_L2TP_HEADER_REMOVE, + IPA_HDR_PROC_ETHII_TO_ETHII_EX, + IPA_HDR_PROC_L2TP_UDP_HEADER_ADD, + IPA_HDR_PROC_L2TP_UDP_HEADER_REMOVE, + IPA_HDR_PROC_SET_DSCP, + IPA_HDR_PROC_EoGRE_HEADER_ADD, + IPA_HDR_PROC_EoGRE_HEADER_REMOVE, + IPA_HDR_PROC_RTP_METADATA_STREAM0, + IPA_HDR_PROC_RTP_METADATA_STREAM1, + IPA_HDR_PROC_RTP_METADATA_STREAM2, + IPA_HDR_PROC_RTP_METADATA_STREAM3, +}; +#define IPA_HDR_PROC_MAX (IPA_HDR_PROC_RTP_METADATA_STREAM3 + 1) + +/** + * struct ipa_rt_rule - attributes of a routing rule + * @dst: dst "client" + * @hdr_hdl: handle to the dynamic header + it is not an index or an offset + * @hdr_proc_ctx_hdl: handle to header processing context. if it is provided + hdr_hdl shall be 0 + * @attrib: attributes of the rule + * @max_prio: bool switch. is this rule with Max priority? meaning on rule hit, + * IPA will use the rule and will not look for other rules that may have + * higher priority + * @hashable: bool switch. is this rule hashable or not? + * ipa uses hashable rules to cache their hit results to be used in + * consecutive packets + * @retain_hdr: bool switch to instruct IPA core to add back to the packet + * the header removed as part of header removal + * @coalesce: bool to decide whether packets should be coalesced or not + */ +struct ipa_rt_rule { + enum ipa_client_type dst; + uint32_t hdr_hdl; + uint32_t hdr_proc_ctx_hdl; + struct ipa_rule_attrib attrib; + uint8_t max_prio; + uint8_t hashable; + uint8_t retain_hdr; + uint8_t coalesce; +}; +#define IPA_RT_SUPPORT_COAL + +/** + * struct ipa_rt_rule_v2 - attributes of a routing rule + * @dst: dst "client" + * @hdr_hdl: handle to the dynamic header + it is not an index or an offset + * @hdr_proc_ctx_hdl: handle to header processing context. if it is provided + hdr_hdl shall be 0 + * @attrib: attributes of the rule + * @max_prio: bool switch. is this rule with Max priority? meaning on rule hit, + * IPA will use the rule and will not look for other rules that may have + * higher priority + * @hashable: bool switch. is this rule hashable or not? + * ipa uses hashable rules to cache their hit results to be used in + * consecutive packets + * @retain_hdr: bool switch to instruct IPA core to add back to the packet + * the header removed as part of header removal + * @coalesce: bool to decide whether packets should be coalesced or not + * @enable_stats: is true when we want to enable stats for this + * rt rule. + * @cnt_idx: if enable_stats is 1 and cnt_idx is 0, then cnt_idx + * will be assigned by ipa driver. + * @close_aggr_irq_mod: close aggregation/coalescing and close GSI + * interrupt moderation + * @ttl_update: bool to indicate whether TTL update is needed or not. + * @qos_class: QOS classification value. + * @skip_ingress: bool to skip ingress policing. + */ +struct ipa_rt_rule_v2 { + enum ipa_client_type dst; + uint32_t hdr_hdl; + uint32_t hdr_proc_ctx_hdl; + struct ipa_rule_attrib attrib; + uint8_t max_prio; + uint8_t hashable; + uint8_t retain_hdr; + uint8_t coalesce; + uint8_t enable_stats; + uint8_t cnt_idx; + uint8_t close_aggr_irq_mod; + uint8_t ttl_update; + uint8_t qos_class; + uint8_t skip_ingress; +}; + +/** + * struct ipa_hdr_add - header descriptor includes in and out + * parameters + * @name: name of the header + * @hdr: actual header to be inserted + * @hdr_len: size of above header + * @type: l2 header type + * @is_partial: header not fully specified + * @hdr_hdl: out parameter, handle to header, valid when status is 0 + * @status: out parameter, status of header add operation, + * 0 for success, + * -1 for failure + * @is_eth2_ofst_valid: is eth2_ofst field valid? + * @eth2_ofst: offset to start of Ethernet-II/802.3 header + */ +struct ipa_hdr_add { + char name[IPA_RESOURCE_NAME_MAX]; + uint8_t hdr[IPA_HDR_MAX_SIZE]; + uint8_t hdr_len; + enum ipa_hdr_l2_type type; + uint8_t is_partial; + uint32_t hdr_hdl; + int status; + uint8_t is_eth2_ofst_valid; + uint16_t eth2_ofst; +}; + +/** + * struct ipa_ioc_add_hdr - header addition parameters (support + * multiple headers and commit) + * @commit: should headers be written to IPA HW also? + * @num_hdrs: num of headers that follow + * @ipa_hdr_add hdr: all headers need to go here back to + * back, no pointers + */ +struct ipa_ioc_add_hdr { + uint8_t commit; + uint8_t num_hdrs; + struct ipa_hdr_add hdr[0]; +}; + +/** + * struct ipa_l2tp_header_add_procparams - + * @eth_hdr_retained: Specifies if Ethernet header is retained or not + * @input_ip_version: Specifies if Input header is IPV4(0) or IPV6(1) + * @output_ip_version: Specifies if template header is IPV4(0) or IPV6(1) + * @single_pass: Specifies if second pass is required or not + */ +struct ipa_l2tp_header_add_procparams { + __u32 eth_hdr_retained:1; + __u32 input_ip_version:1; + __u32 output_ip_version:1; + __u32 second_pass:1; + __u32 reserved:28; + __u32 padding; +}; + +/** + * struct ipa_l2tp_header_remove_procparams - + * @hdr_len_remove: Specifies how much of the header needs to + be removed in bytes + * @eth_hdr_retained: Specifies if Ethernet header is retained or not + * @hdr_ofst_pkt_size_valid: Specifies if the Header offset is valid + * @hdr_ofst_pkt_size: If hdr_ofst_pkt_size_valid =1, this indicates where the + packet size field (2bytes) resides + * @hdr_endianness: 0:little endian, 1:big endian + */ +struct ipa_l2tp_header_remove_procparams { + uint32_t hdr_len_remove:8; + uint32_t eth_hdr_retained:1; + /* Following fields are valid if eth_hdr_retained =1 ( bridge mode) */ + uint32_t hdr_ofst_pkt_size_valid:1; + uint32_t hdr_ofst_pkt_size:6; + uint32_t hdr_endianness:1; + uint32_t reserved:15; +}; + +/** + * struct ipa_l2tp_hdr_proc_ctx_params - + * @hdr_add_param: parameters for header add + * @hdr_remove_param: parameters for header remove + * @is_dst_pipe_valid: if dst pipe is valid + * @dst_pipe: destination pipe + */ +struct ipa_l2tp_hdr_proc_ctx_params { + struct ipa_l2tp_header_add_procparams hdr_add_param; + struct ipa_l2tp_header_remove_procparams hdr_remove_param; + uint8_t is_dst_pipe_valid; + enum ipa_client_type dst_pipe; +}; + +#define IPA_EoGRE_MAX_PCP_IDX 8 /* From 802.1Q tag format (reflects IEEE P802.1p) */ +#define IPA_EoGRE_MAX_VLAN 8 /* Our supported number of VLAN id's */ + +/* vlan 12 bits + pcp 3 bites <-> dscp 6 bits */ +struct IpaDscpVlanPcpMap_t { + /* + * valid only lower 12 bits + */ + uint16_t vlan[IPA_EoGRE_MAX_VLAN]; + /* + * dscp[vlan][pcp], valid only lower 6 bits, using pcp as index + */ + uint8_t dscp[IPA_EoGRE_MAX_VLAN][IPA_EoGRE_MAX_PCP_IDX]; + uint8_t num_vlan; /* indicate how many vlans valid */ + uint8_t reserved0; +} __packed; + +struct ipa_ipgre_info { + /* ip address type */ + enum ipa_ip_type iptype; + /* ipv4 */ + uint32_t ipv4_src; + uint32_t ipv4_dst; + /* ipv6 */ + uint32_t ipv6_src[4]; + uint32_t ipv6_dst[4]; + /* gre header info */ + uint16_t gre_protocol; +}; + +struct ipa_ioc_eogre_info { + /* ip and gre info */ + struct ipa_ipgre_info ipgre_info; + /* mapping info */ + struct IpaDscpVlanPcpMap_t map_info; +}; + +/** + * struct ipa_eogre_header_add_procparams - + * @eth_hdr_retained: Specifies if Ethernet header is retained or not + * @input_ip_version: Specifies if Input header is IPV4(0) or IPV6(1) + * @output_ip_version: Specifies if template header's outer IP is IPV4(0) or IPV6(1) + * @second_pass: Specifies if the data should be processed again. + */ +struct ipa_eogre_header_add_procparams { + uint32_t eth_hdr_retained :1; + uint32_t input_ip_version :1; + uint32_t output_ip_version :1; + uint32_t second_pass :1; + uint32_t reserved :28; +}; + +/** + * struct ipa_eogre_header_remove_procparams - + * @hdr_len_remove: Specifies how much (in bytes) of the header needs + * to be removed + */ +struct ipa_eogre_header_remove_procparams { + uint32_t hdr_len_remove:8; /* 44 bytes for IPV6, 24 for IPV4 */ + uint32_t reserved:24; +}; + +/** + * struct ipa_eogre_hdr_proc_ctx_params - + * @hdr_add_param: parameters for header add + * @hdr_remove_param: parameters for header remove + */ +struct ipa_eogre_hdr_proc_ctx_params { + struct ipa_eogre_header_add_procparams hdr_add_param; + struct ipa_eogre_header_remove_procparams hdr_remove_param; +}; + +/** + * struct ipa_eth_II_to_eth_II_ex_procparams - + * @input_ethhdr_negative_offset: Specifies where the ethernet hdr offset is + * (in bytes) from the start of the input IP hdr + * @output_ethhdr_negative_offset: Specifies where the ethernet hdr offset is + * (in bytes) from the end of the template hdr + * @reserved: for future use + */ +struct ipa_eth_II_to_eth_II_ex_procparams { + uint32_t input_ethhdr_negative_offset : 8; + uint32_t output_ethhdr_negative_offset : 8; + uint32_t reserved : 16; +}; + +#define L2TP_USER_SPACE_SPECIFY_DST_PIPE + +/** + * struct ipa_hdr_proc_ctx_add - processing context descriptor includes + * in and out parameters + * @type: processing context type + * @hdr_hdl: in parameter, handle to header + * @l2tp_params: l2tp parameters + * @eogre_params: eogre parameters + * @generic_params: generic proc_ctx params + * @proc_ctx_hdl: out parameter, handle to proc_ctx, valid when status is 0 + * @status: out parameter, status of header add operation, + * 0 for success, + * -1 for failure + */ +struct ipa_hdr_proc_ctx_add { + enum ipa_hdr_proc_type type; + uint32_t hdr_hdl; + uint32_t proc_ctx_hdl; + int status; + struct ipa_l2tp_hdr_proc_ctx_params l2tp_params; + struct ipa_eogre_hdr_proc_ctx_params eogre_params; + struct ipa_eth_II_to_eth_II_ex_procparams generic_params; +}; + +#define IPA_L2TP_HDR_PROC_SUPPORT + +/** + * struct ipa_ioc_add_hdr - processing context addition parameters (support + * multiple processing context and commit) + * @commit: should processing context be written to IPA HW also? + * @num_proc_ctxs: num of processing context that follow + * @proc_ctx: all processing context need to go here back to + * back, no pointers + */ +struct ipa_ioc_add_hdr_proc_ctx { + uint8_t commit; + uint8_t num_proc_ctxs; + struct ipa_hdr_proc_ctx_add proc_ctx[0]; +}; + +/** + * struct ipa_ioc_copy_hdr - retrieve a copy of the specified + * header - caller can then derive the complete header + * @name: name of the header resource + * @hdr: out parameter, contents of specified header, + * valid only when ioctl return val is non-negative + * @hdr_len: out parameter, size of above header + * valid only when ioctl return val is non-negative + * @type: l2 header type + * valid only when ioctl return val is non-negative + * @is_partial: out parameter, indicates whether specified header is partial + * valid only when ioctl return val is non-negative + * @is_eth2_ofst_valid: is eth2_ofst field valid? + * @eth2_ofst: offset to start of Ethernet-II/802.3 header + */ +struct ipa_ioc_copy_hdr { + char name[IPA_RESOURCE_NAME_MAX]; + uint8_t hdr[IPA_HDR_MAX_SIZE]; + uint8_t hdr_len; + enum ipa_hdr_l2_type type; + uint8_t is_partial; + uint8_t is_eth2_ofst_valid; + uint16_t eth2_ofst; +}; + +/** + * struct ipa_ioc_get_hdr - header entry lookup parameters, if lookup was + * successful caller must call put to release the reference count when done + * @name: name of the header resource + * @hdl: out parameter, handle of header entry + * valid only when ioctl return val is non-negative + */ +struct ipa_ioc_get_hdr { + char name[IPA_RESOURCE_NAME_MAX]; + uint32_t hdl; +}; + +/** + * struct ipa_hdr_del - header descriptor includes in and out + * parameters + * + * @hdl: handle returned from header add operation + * @status: out parameter, status of header remove operation, + * 0 for success, + * -1 for failure + */ +struct ipa_hdr_del { + uint32_t hdl; + int status; +}; + +/** + * struct ipa_ioc_del_hdr - header deletion parameters (support + * multiple headers and commit) + * @commit: should headers be removed from IPA HW also? + * @num_hdls: num of headers being removed + * @ipa_hdr_del hdl: all handles need to go here back to back, no pointers + */ +struct ipa_ioc_del_hdr { + uint8_t commit; + uint8_t num_hdls; + struct ipa_hdr_del hdl[0]; +}; + +/** + * struct ipa_hdr_proc_ctx_del - processing context descriptor includes + * in and out parameters + * @hdl: handle returned from processing context add operation + * @status: out parameter, status of header remove operation, + * 0 for success, + * -1 for failure + */ +struct ipa_hdr_proc_ctx_del { + uint32_t hdl; + int status; +}; + +/** + * ipa_ioc_del_hdr_proc_ctx - processing context deletion parameters (support + * multiple headers and commit) + * @commit: should processing contexts be removed from IPA HW also? + * @num_hdls: num of processing contexts being removed + * @ipa_hdr_proc_ctx_del hdl: all handles need to go here back to back, + * no pointers + */ +struct ipa_ioc_del_hdr_proc_ctx { + uint8_t commit; + uint8_t num_hdls; + struct ipa_hdr_proc_ctx_del hdl[0]; +}; + +/** + * struct ipa_rt_rule_add - routing rule descriptor includes in + * and out parameters + * @rule: actual rule to be added + * @at_rear: add at back of routing table, it is NOT possible to add rules at + * the rear of the "default" routing tables + * @rt_rule_hdl: output parameter, handle to rule, valid when status is 0 + * @status: output parameter, status of routing rule add operation, + * 0 for success, + * -1 for failure + */ +struct ipa_rt_rule_add { + struct ipa_rt_rule rule; + uint8_t at_rear; + uint32_t rt_rule_hdl; + int status; +}; + +/** + * struct ipa_rt_rule_add_v2 - routing rule descriptor includes + * in and out parameters + * @rule: actual rule to be added + * @at_rear: add at back of routing table, it is NOT possible to add rules at + * the rear of the "default" routing tables + * @rt_rule_hdl: output parameter, handle to rule, valid when status is 0 + * @status: output parameter, status of routing rule add operation, + * 0 for success, + * -1 for failure + */ +struct ipa_rt_rule_add_v2 { + uint8_t at_rear; + uint32_t rt_rule_hdl; + int status; + struct ipa_rt_rule_v2 rule; +}; + + +/** + * struct ipa_ioc_add_rt_rule - routing rule addition parameters (supports + * multiple rules and commit); + * + * all rules MUST be added to same table + * @commit: should rules be written to IPA HW also? + * @ip: IP family of rule + * @rt_tbl_name: name of routing table resource + * @num_rules: number of routing rules that follow + * @ipa_rt_rule_add rules: all rules need to go back to back here, no pointers + */ +struct ipa_ioc_add_rt_rule { + uint8_t commit; + enum ipa_ip_type ip; + char rt_tbl_name[IPA_RESOURCE_NAME_MAX]; + uint8_t num_rules; + struct ipa_rt_rule_add rules[0]; +}; + +/** + * struct ipa_ioc_add_rt_rule_v2 - routing rule addition + * parameters (supports multiple rules and commit); + * + * all rules MUST be added to same table + * @commit: should rules be written to IPA HW also? + * @ip: IP family of rule + * @rt_tbl_name: name of routing table resource + * @num_rules: number of routing rules that follow + * @rule_add_size: sizeof(struct ipa_rt_rule_add_v2) + * @reserved1: reserved bits for alignment + * @reserved2: reserved bits for alignment + * @ipa_rt_rule_add rules: all rules need to go back to back here, no pointers + */ +struct ipa_ioc_add_rt_rule_v2 { + uint8_t commit; + enum ipa_ip_type ip; + char rt_tbl_name[IPA_RESOURCE_NAME_MAX]; + uint8_t num_rules; + uint32_t rule_add_size; + uint32_t reserved1; + uint8_t reserved2; + uint64_t rules; +}; + +/** + * struct ipa_ioc_add_rt_rule_after - routing rule addition after a specific + * rule parameters(supports multiple rules and commit); + * + * all rules MUST be added to same table + * @commit: should rules be written to IPA HW also? + * @ip: IP family of rule + * @rt_tbl_name: name of routing table resource + * @num_rules: number of routing rules that follow + * @add_after_hdl: the rules will be added after this specific rule + * @ipa_rt_rule_add rules: all rules need to go back to back here, no pointers + * at_rear field will be ignored when using this IOCTL + */ +struct ipa_ioc_add_rt_rule_after { + uint8_t commit; + enum ipa_ip_type ip; + char rt_tbl_name[IPA_RESOURCE_NAME_MAX]; + uint8_t num_rules; + uint32_t add_after_hdl; + struct ipa_rt_rule_add rules[0]; +}; + +/** + * struct ipa_ioc_add_rt_rule_after_v2 - routing rule addition + * after a specific rule parameters(supports multiple rules and + * commit); + * + * all rules MUST be added to same table + * @commit: should rules be written to IPA HW also? + * @ip: IP family of rule + * @rt_tbl_name: name of routing table resource + * @num_rules: number of routing rules that follow + * @add_after_hdl: the rules will be added after this specific rule + * @rule_add_size: sizeof(struct ipa_rt_rule_add_v2) + * @reserved: reserved bits for alignment + * @ipa_rt_rule_add rules: all rules need to go back to back here, no pointers + * at_rear field will be ignored when using this IOCTL + */ +struct ipa_ioc_add_rt_rule_after_v2 { + uint8_t commit; + enum ipa_ip_type ip; + char rt_tbl_name[IPA_RESOURCE_NAME_MAX]; + uint8_t num_rules; + uint32_t add_after_hdl; + uint32_t rule_add_size; + uint8_t reserved; + uint64_t rules; +}; + +/** + * struct ipa_rt_rule_mdfy - routing rule descriptor includes + * in and out parameters + * @rule: actual rule to be added + * @rt_rule_hdl: handle to rule which supposed to modify + * @status: output parameter, status of routing rule modify operation, + * 0 for success, + * -1 for failure + * + */ +struct ipa_rt_rule_mdfy { + struct ipa_rt_rule rule; + uint32_t rt_rule_hdl; + int status; +}; + +/** + * struct ipa_rt_rule_mdfy_v2 - routing rule descriptor includes + * in and out parameters + * @rule: actual rule to be added + * @rt_rule_hdl: handle to rule which supposed to modify + * @status: output parameter, status of routing rule modify operation, + * 0 for success, + * -1 for failure + * + */ +struct ipa_rt_rule_mdfy_v2 { + uint32_t rt_rule_hdl; + int status; + struct ipa_rt_rule_v2 rule; +}; + +/** + * struct ipa_ioc_mdfy_rt_rule - routing rule modify parameters (supports + * multiple rules and commit) + * @commit: should rules be written to IPA HW also? + * @ip: IP family of rule + * @num_rules: number of routing rules that follow + * @rules: all rules need to go back to back here, no pointers + */ +struct ipa_ioc_mdfy_rt_rule { + uint8_t commit; + enum ipa_ip_type ip; + uint8_t num_rules; + struct ipa_rt_rule_mdfy rules[0]; +}; + +/** + * struct ipa_ioc_mdfy_rt_rule_v2 - routing rule modify + * parameters (supports multiple rules and commit) + * @commit: should rules be written to IPA HW also? + * @ip: IP family of rule + * @num_rules: number of routing rules that follow + * @rule_mdfy_size: sizeof(struct ipa_rt_rule_mdfy_v2) + * @reserved: reserved bits for alignment + * @rules: all rules need to go back to back here, no pointers + */ +struct ipa_ioc_mdfy_rt_rule_v2 { + uint8_t commit; + enum ipa_ip_type ip; + uint8_t num_rules; + uint32_t rule_mdfy_size; + uint8_t reserved; + uint64_t rules; +}; + +/** + * struct ipa_rt_rule_del - routing rule descriptor includes in + * and out parameters + * @hdl: handle returned from route rule add operation + * @status: output parameter, status of route rule delete operation, + * 0 for success, + * -1 for failure + */ +struct ipa_rt_rule_del { + uint32_t hdl; + int status; +}; + +/** + * struct ipa_rt_rule_add_ext - routing rule descriptor includes in + * and out parameters + * @rule: actual rule to be added + * @at_rear: add at back of routing table, it is NOT possible to add rules at + * the rear of the "default" routing tables + * @rt_rule_hdl: output parameter, handle to rule, valid when status is 0 + * @status: output parameter, status of routing rule add operation, + * @rule_id: rule_id to be assigned to the routing rule. In case client + * specifies rule_id as 0 the driver will assign a new rule_id + * 0 for success, + * -1 for failure + */ +struct ipa_rt_rule_add_ext { + struct ipa_rt_rule rule; + uint8_t at_rear; + uint32_t rt_rule_hdl; + int status; + uint16_t rule_id; +}; + +/** + * struct ipa_rt_rule_add_ext_v2 - routing rule descriptor + * includes in and out parameters + * @rule: actual rule to be added + * @at_rear: add at back of routing table, it is NOT possible to add rules at + * the rear of the "default" routing tables + * @rt_rule_hdl: output parameter, handle to rule, valid when status is 0 + * @status: output parameter, status of routing rule add operation, + * @rule_id: rule_id to be assigned to the routing rule. In case client + * specifies rule_id as 0 the driver will assign a new rule_id + * 0 for success, + * -1 for failure + */ +struct ipa_rt_rule_add_ext_v2 { + uint8_t at_rear; + uint32_t rt_rule_hdl; + int status; + uint16_t rule_id; + struct ipa_rt_rule_v2 rule; +}; + +/** + * struct ipa_ioc_add_rt_rule_ext - routing rule addition + * parameters (supports multiple rules and commit with rule_id); + * + * all rules MUST be added to same table + * @commit: should rules be written to IPA HW also? + * @ip: IP family of rule + * @rt_tbl_name: name of routing table resource + * @num_rules: number of routing rules that follow + * @ipa_rt_rule_add_ext rules: all rules need to go back to back here, + * no pointers + */ +struct ipa_ioc_add_rt_rule_ext { + uint8_t commit; + enum ipa_ip_type ip; + char rt_tbl_name[IPA_RESOURCE_NAME_MAX]; + uint8_t num_rules; + struct ipa_rt_rule_add_ext rules[0]; +}; + +/** + * struct ipa_ioc_add_rt_rule_ext_v2 - routing rule addition + * parameters (supports multiple rules and commit with rule_id); + * + * all rules MUST be added to same table + * @commit: should rules be written to IPA HW also? + * @ip: IP family of rule + * @rt_tbl_name: name of routing table resource + * @num_rules: number of routing rules that follow + * @rule_add_ext_size: sizeof(struct ipa_rt_rule_add_ext_v2) + * @reserved1: reserved bits for alignment + * @reserved2: reserved bits for alignment + * @ipa_rt_rule_add_ext rules: all rules need to go back to back here, + * no pointers + */ +struct ipa_ioc_add_rt_rule_ext_v2 { + uint8_t commit; + enum ipa_ip_type ip; + char rt_tbl_name[IPA_RESOURCE_NAME_MAX]; + uint8_t num_rules; + uint32_t rule_add_ext_size; + uint32_t reserved1; + uint8_t reserved2; + uint64_t rules; +}; + + +/** + * struct ipa_ioc_del_rt_rule - routing rule deletion parameters (supports + * multiple headers and commit) + * @commit: should rules be removed from IPA HW also? + * @ip: IP family of rules + * @num_hdls: num of rules being removed + * @ipa_rt_rule_del hdl: all handles need to go back to back here, no pointers + */ +struct ipa_ioc_del_rt_rule { + uint8_t commit; + enum ipa_ip_type ip; + uint8_t num_hdls; + struct ipa_rt_rule_del hdl[0]; +}; + +/** + * struct ipa_ioc_get_rt_tbl_indx - routing table index lookup parameters + * @ip: IP family of table + * @name: name of routing table resource + * @index: output parameter, routing table index, valid only when ioctl + * return val is non-negative + */ +struct ipa_ioc_get_rt_tbl_indx { + enum ipa_ip_type ip; + char name[IPA_RESOURCE_NAME_MAX]; + uint32_t idx; +}; + +/** + * struct ipa_flt_rule_add - filtering rule descriptor includes + * in and out parameters + * @rule: actual rule to be added + * @at_rear: add at back of filtering table? + * @flt_rule_hdl: out parameter, handle to rule, valid when status is 0 + * @status: output parameter, status of filtering rule add operation, + * 0 for success, + * -1 for failure + * + */ +struct ipa_flt_rule_add { + struct ipa_flt_rule rule; + uint8_t at_rear; + uint32_t flt_rule_hdl; + int status; +}; + +/** + * struct ipa_flt_rule_add_v2 - filtering rule descriptor + * includes in and out parameters + * @rule: actual rule to be added + * @at_rear: add at back of filtering table? + * @flt_rule_hdl: out parameter, handle to rule, valid when status is 0 + * @status: output parameter, status of filtering rule add operation, + * 0 for success, + * -1 for failure + * + */ +struct ipa_flt_rule_add_v2 { + uint8_t at_rear; + uint32_t flt_rule_hdl; + int status; + struct ipa_flt_rule_v2 rule; +}; + +/** + * struct ipa_ioc_add_flt_rule - filtering rule addition parameters (supports + * multiple rules and commit) + * all rules MUST be added to same table + * @commit: should rules be written to IPA HW also? + * @ip: IP family of rule + * @ep: which "clients" pipe does this rule apply to? + * valid only when global is 0 + * @global: does this apply to global filter table of specific IP family + * @num_rules: number of filtering rules that follow + * @rules: all rules need to go back to back here, no pointers + */ +struct ipa_ioc_add_flt_rule { + uint8_t commit; + enum ipa_ip_type ip; + enum ipa_client_type ep; + uint8_t global; + uint8_t num_rules; + struct ipa_flt_rule_add rules[0]; +}; + +/** + * struct ipa_ioc_add_flt_rule_v2 - filtering rule addition + * parameters (supports multiple rules and commit) + * all rules MUST be added to same table + * @commit: should rules be written to IPA HW also? + * @ip: IP family of rule + * @ep: which "clients" pipe does this rule apply to? + * valid only when global is 0 + * @global: does this apply to global filter table of specific IP family + * @num_rules: number of filtering rules that follow + * @flt_rule_size: sizeof(struct ipa_flt_rule_add_v2) + * @reserved1: reserved bits for alignment + * @reserved2: reserved bits for alignment + * @reserved3: reserved bits for alignment + * @rules: all rules need to go back to back here, no pointers + */ +struct ipa_ioc_add_flt_rule_v2 { + uint8_t commit; + enum ipa_ip_type ip; + enum ipa_client_type ep; + uint8_t global; + uint8_t num_rules; + uint32_t flt_rule_size; + uint32_t reserved1; + uint16_t reserved2; + uint8_t reserved3; + uint64_t rules; +}; + + +/** + * struct ipa_ioc_add_flt_rule_after - filtering rule addition after specific + * rule parameters (supports multiple rules and commit) + * all rules MUST be added to same table + * @commit: should rules be written to IPA HW also? + * @ip: IP family of rule + * @ep: which "clients" pipe does this rule apply to? + * @num_rules: number of filtering rules that follow + * @add_after_hdl: rules will be added after the rule with this handle + * @rules: all rules need to go back to back here, no pointers. at rear field + * is ignored when using this IOCTL + */ +struct ipa_ioc_add_flt_rule_after { + uint8_t commit; + enum ipa_ip_type ip; + enum ipa_client_type ep; + uint8_t num_rules; + uint32_t add_after_hdl; + struct ipa_flt_rule_add rules[0]; +}; + +/** + * struct ipa_ioc_add_flt_rule_after_v2 - filtering rule + * addition after specific rule parameters (supports multiple + * rules and commit) all rules MUST be added to same table + * @commit: should rules be written to IPA HW also? + * @ip: IP family of rule + * @ep: which "clients" pipe does this rule apply to? + * @num_rules: number of filtering rules that follow + * @add_after_hdl: rules will be added after the rule with this handle + * @flt_rule_size: sizeof(struct ipa_flt_rule_add_v2) + * @reserved: reserved bits for alignment + * @rules: all rules need to go back to back here, no pointers. at rear field + * is ignored when using this IOCTL + */ +struct ipa_ioc_add_flt_rule_after_v2 { + uint8_t commit; + enum ipa_ip_type ip; + enum ipa_client_type ep; + uint8_t num_rules; + uint32_t add_after_hdl; + uint32_t flt_rule_size; + uint32_t reserved; + uint64_t rules; +}; + +/** + * struct ipa_flt_rule_mdfy - filtering rule descriptor includes + * in and out parameters + * @rule: actual rule to be added + * @flt_rule_hdl: handle to rule + * @status: output parameter, status of filtering rule modify operation, + * 0 for success, + * -1 for failure + * + */ +struct ipa_flt_rule_mdfy { + struct ipa_flt_rule rule; + uint32_t rule_hdl; + int status; +}; + +/** + * struct ipa_flt_rule_mdfy_v2 - filtering rule descriptor + * includes in and out parameters + * @rule: actual rule to be added + * @flt_rule_hdl: handle to rule + * @status: output parameter, status of filtering rule modify operation, + * 0 for success, + * -1 for failure + * + */ +struct ipa_flt_rule_mdfy_v2 { + uint32_t rule_hdl; + int status; + struct ipa_flt_rule_v2 rule; +}; + +/** + * struct ipa_ioc_mdfy_flt_rule - filtering rule modify parameters (supports + * multiple rules and commit) + * @commit: should rules be written to IPA HW also? + * @ip: IP family of rule + * @num_rules: number of filtering rules that follow + * @rules: all rules need to go back to back here, no pointers + */ +struct ipa_ioc_mdfy_flt_rule { + uint8_t commit; + enum ipa_ip_type ip; + uint8_t num_rules; + struct ipa_flt_rule_mdfy rules[0]; +}; + +/** + * struct ipa_ioc_mdfy_flt_rule_v2 - filtering rule modify + * parameters (supports multiple rules and commit) + * @commit: should rules be written to IPA HW also? + * @ip: IP family of rule + * @num_rules: number of filtering rules that follow + * @rule_mdfy_size: sizeof(struct ipa_flt_rule_mdfy_v2) + * @reserved: reserved bits for alignment + * @rules: all rules need to go back to back here, no pointers + */ +struct ipa_ioc_mdfy_flt_rule_v2 { + uint8_t commit; + enum ipa_ip_type ip; + uint8_t num_rules; + uint32_t rule_mdfy_size; + uint8_t reserved; + uint64_t rules; +}; + +/** + * struct ipa_flt_rule_del - filtering rule descriptor includes + * in and out parameters + * + * @hdl: handle returned from filtering rule add operation + * @status: output parameter, status of filtering rule delete operation, + * 0 for success, + * -1 for failure + */ +struct ipa_flt_rule_del { + uint32_t hdl; + int status; +}; + +/** + * struct ipa_ioc_del_flt_rule - filtering rule deletion parameters (supports + * multiple headers and commit) + * @commit: should rules be removed from IPA HW also? + * @ip: IP family of rules + * @num_hdls: num of rules being removed + * @hdl: all handles need to go back to back here, no pointers + */ +struct ipa_ioc_del_flt_rule { + uint8_t commit; + enum ipa_ip_type ip; + uint8_t num_hdls; + struct ipa_flt_rule_del hdl[0]; +}; + +/** + * struct ipa_ioc_get_rt_tbl - routing table lookup parameters, if lookup was + * successful caller must call put to release the reference + * count when done + * @ip: IP family of table + * @name: name of routing table resource + * @htl: output parameter, handle of routing table, valid only when ioctl + * return val is non-negative + */ +struct ipa_ioc_get_rt_tbl { + enum ipa_ip_type ip; + char name[IPA_RESOURCE_NAME_MAX]; + uint32_t hdl; +}; + +/** + * struct ipa_ioc_query_intf - used to lookup number of tx and + * rx properties of interface + * @name: name of interface + * @num_tx_props: output parameter, number of tx properties + * valid only when ioctl return val is non-negative + * @num_rx_props: output parameter, number of rx properties + * valid only when ioctl return val is non-negative + * @num_ext_props: output parameter, number of ext properties + * valid only when ioctl return val is non-negative + * @excp_pipe: exception packets of this interface should be + * routed to this pipe + */ +struct ipa_ioc_query_intf { + char name[IPA_RESOURCE_NAME_MAX]; + uint32_t num_tx_props; + uint32_t num_rx_props; + uint32_t num_ext_props; + enum ipa_client_type excp_pipe; +}; + +/** + * struct ipa_ioc_tx_intf_prop - interface tx property + * @ip: IP family of routing rule + * @attrib: routing rule + * @dst_pipe: routing output pipe + * @alt_dst_pipe: alternate routing output pipe + * @hdr_name: name of associated header if any, empty string when no header + * @hdr_l2_type: type of associated header if any, use NONE when no header + */ +struct ipa_ioc_tx_intf_prop { + enum ipa_ip_type ip; + struct ipa_rule_attrib attrib; + enum ipa_client_type dst_pipe; + enum ipa_client_type alt_dst_pipe; + char hdr_name[IPA_RESOURCE_NAME_MAX]; + enum ipa_hdr_l2_type hdr_l2_type; +}; + +/** + * struct ipa_ioc_query_intf_tx_props - interface tx propertie + * @name: name of interface + * @num_tx_props: number of TX properties + * @tx[0]: output parameter, the tx properties go here back to back + */ +struct ipa_ioc_query_intf_tx_props { + char name[IPA_RESOURCE_NAME_MAX]; + uint32_t num_tx_props; + struct ipa_ioc_tx_intf_prop tx[0]; +}; + +/** + * struct ipa_ioc_ext_intf_prop - interface extended property + * @ip: IP family of routing rule + * @eq_attrib: attributes of the rule in equation form + * @action: action field + * @rt_tbl_idx: index of RT table referred to by filter rule + * @mux_id: MUX_ID + * @filter_hdl: handle of filter (as specified by provider of filter rule) + * @is_xlat_rule: it is xlat flt rule or not + */ +struct ipa_ioc_ext_intf_prop { + enum ipa_ip_type ip; + struct ipa_ipfltri_rule_eq eq_attrib; + enum ipa_flt_action action; + uint32_t rt_tbl_idx; + uint8_t mux_id; + uint32_t filter_hdl; + uint8_t is_xlat_rule; + uint32_t rule_id; + uint8_t is_rule_hashable; +#define IPA_V6_UL_WL_FIREWALL_HANDLE + uint8_t replicate_needed; +}; + +/** + * struct ipa_ioc_query_intf_ext_props - interface ext propertie + * @name: name of interface + * @num_ext_props: number of EXT properties + * @ext[0]: output parameter, the ext properties go here back to back + */ +struct ipa_ioc_query_intf_ext_props { + char name[IPA_RESOURCE_NAME_MAX]; + uint32_t num_ext_props; + struct ipa_ioc_ext_intf_prop ext[0]; +}; + +/** + * struct ipa_ioc_rx_intf_prop - interface rx property + * @ip: IP family of filtering rule + * @attrib: filtering rule + * @src_pipe: input pipe + * @hdr_l2_type: type of associated header if any, use NONE when no header + */ +struct ipa_ioc_rx_intf_prop { + enum ipa_ip_type ip; + struct ipa_rule_attrib attrib; + enum ipa_client_type src_pipe; + enum ipa_hdr_l2_type hdr_l2_type; +}; + +/** + * struct ipa_ioc_query_intf_rx_props - interface rx propertie + * @name: name of interface + * @num_rx_props: number of RX properties + * @rx: output parameter, the rx properties go here back to back + */ +struct ipa_ioc_query_intf_rx_props { + char name[IPA_RESOURCE_NAME_MAX]; + uint32_t num_rx_props; + struct ipa_ioc_rx_intf_prop rx[0]; +}; + +/** + * struct ipa_ioc_nat_alloc_mem - nat table memory allocation + * properties + * @dev_name: input parameter, the name of table + * @size: input parameter, size of table in bytes + * @offset: output parameter, offset into page in case of system memory + */ +struct ipa_ioc_nat_alloc_mem { + char dev_name[IPA_RESOURCE_NAME_MAX]; + size_t size; + __kernel_off_t offset; +}; + +/** + * struct ipa_ioc_nat_ipv6ct_table_alloc - NAT/IPv6CT table memory allocation + * properties + * @size: input parameter, size of table in bytes + * @offset: output parameter, offset into page in case of system memory + */ +struct ipa_ioc_nat_ipv6ct_table_alloc { + size_t size; + __kernel_off_t offset; +}; + +/** + * struct ipa_ioc_v4_nat_init - nat table initialization parameters + * @tbl_index: input parameter, index of the table + * @ipv4_rules_offset: input parameter, ipv4 rules address offset + * @expn_rules_offset: input parameter, ipv4 expansion rules address offset + * @index_offset: input parameter, index rules offset + * @index_expn_offset: input parameter, index expansion rules offset + * @table_entries: input parameter, ipv4 rules table number of entries + * @expn_table_entries: input parameter, ipv4 expansion rules table number of + * entries + * @ip_addr: input parameter, public ip address + * @mem_type: input parameter, type of memory the table resides in + * @focus_change: input parameter, are we moving to/from sram or ddr + */ +struct ipa_ioc_v4_nat_init { + uint8_t tbl_index; + uint32_t ipv4_rules_offset; + uint32_t expn_rules_offset; + + uint32_t index_offset; + uint32_t index_expn_offset; + + uint16_t table_entries; + uint16_t expn_table_entries; + uint32_t ip_addr; + + uint8_t mem_type; + uint8_t focus_change; +}; + +/** + * struct ipa_ioc_ipv6ct_init - IPv6CT table initialization parameters + * @base_table_offset: input parameter, IPv6CT base table address offset + * @expn_table_offset: input parameter, IPv6CT expansion table address offset + * @table_entries: input parameter, IPv6CT table number of entries + * @expn_table_entries: input parameter, IPv6CT expansion table number of + * entries + * @tbl_index: input parameter, index of the table + */ +struct ipa_ioc_ipv6ct_init { + uint32_t base_table_offset; + uint32_t expn_table_offset; + uint16_t table_entries; + uint16_t expn_table_entries; + uint8_t tbl_index; +}; + +/** + * struct ipa_ioc_v4_nat_del - nat table delete parameter + * @table_index: input parameter, index of the table + * @public_ip_addr: input parameter, public ip address + */ +struct ipa_ioc_v4_nat_del { + uint8_t table_index; + uint32_t public_ip_addr; +}; + +/** + * struct ipa_ioc_nat_ipv6ct_table_del - NAT/IPv6CT table delete parameter + * @table_index: input parameter, index of the table + * @mem_type: input parameter, type of memory the table resides in + */ +struct ipa_ioc_nat_ipv6ct_table_del { + uint8_t table_index; + uint8_t mem_type; +}; + +/** + * struct ipa_ioc_nat_dma_one - nat/ipv6ct dma command parameter + * @table_index: input parameter, index of the table + * @base_addr: type of table, from which the base address of the table + * can be inferred + * @offset: destination offset within the NAT table + * @data: data to be written. + */ +struct ipa_ioc_nat_dma_one { + uint8_t table_index; + uint8_t base_addr; + + uint32_t offset; + uint16_t data; + +}; + +/** + * struct ipa_ioc_nat_dma_cmd - To hold multiple nat/ipv6ct dma commands + * @entries: number of dma commands in use + * @dma: data pointer to the dma commands + * @mem_type: input parameter, type of memory the table resides in + */ +struct ipa_ioc_nat_dma_cmd { + uint8_t entries; + uint8_t mem_type; + struct ipa_ioc_nat_dma_one dma[0]; +}; + +/** + * struct ipa_ioc_nat_pdn_entry - PDN entry modification data + * @pdn_index: index of the entry in the PDN config table to be changed + * @public_ip: PDN's public ip + * @src_metadata: PDN's source NAT metadata for metadata replacement + * @dst_metadata: PDN's destination NAT metadata for metadata replacement + */ +struct ipa_ioc_nat_pdn_entry { + uint8_t pdn_index; + uint32_t public_ip; + uint32_t src_metadata; + uint32_t dst_metadata; +}; + +/** + * struct ipa_ioc_vlan_iface_info - add vlan interface + * @name: interface name + * @vlan_id: VLAN ID + */ +struct ipa_ioc_vlan_iface_info { + char name[IPA_RESOURCE_NAME_MAX]; + uint8_t vlan_id; +}; + +/** + * enum ipa_l2tp_tunnel_type - IP or UDP + */ +enum ipa_l2tp_tunnel_type { + IPA_L2TP_TUNNEL_IP = 1, + IPA_L2TP_TUNNEL_UDP = 2 +#define IPA_L2TP_TUNNEL_UDP IPA_L2TP_TUNNEL_UDP +}; + +/** + * struct ipa_ioc_l2tp_vlan_mapping_info - l2tp->vlan mapping info + * @iptype: l2tp tunnel IP type + * @l2tp_iface_name: l2tp interface name + * @l2tp_session_id: l2tp session id + * @vlan_iface_name: vlan interface name + * @tunnel_type: l2tp tunnel type + * @src_port: UDP source port + * @dst_port: UDP destination port + * @mtu: MTU of the L2TP interface + */ +struct ipa_ioc_l2tp_vlan_mapping_info { + enum ipa_ip_type iptype; + char l2tp_iface_name[IPA_RESOURCE_NAME_MAX]; + uint8_t l2tp_session_id; + char vlan_iface_name[IPA_RESOURCE_NAME_MAX]; + enum ipa_l2tp_tunnel_type tunnel_type; + __u16 src_port; + __u16 dst_port; + __u16 mtu; + __u8 padding; +}; + +/** + * struct ipa_ioc_gsb_info - connect/disconnect + * @name: interface name + */ +struct ipa_ioc_gsb_info { + char name[IPA_RESOURCE_NAME_MAX]; +}; + +#define QUERY_MAX_EP_PAIRS 2 + +#define IPA_USB0_EP_ID 11 +#define IPA_USB1_EP_ID 12 + +#define IPA_PCIE0_EP_ID 21 +#define IPA_PCIE1_EP_ID 22 + +#define IPA_ETH0_EP_ID 31 +#define IPA_ETH1_EP_ID 32 + +enum ipa_peripheral_ep_type { + IPA_DATA_EP_TYP_RESERVED = 0, + IPA_DATA_EP_TYP_HSIC = 1, + IPA_DATA_EP_TYP_HSUSB = 2, + IPA_DATA_EP_TYP_PCIE = 3, + IPA_DATA_EP_TYP_EMBEDDED = 4, + IPA_DATA_EP_TYP_BAM_DMUX = 5, + IPA_DATA_EP_TYP_ETH, +}; + +enum ipa_data_ep_prot_type { + IPA_PROT_RMNET = 0, + IPA_PROT_RMNET_CV2X = 1, + IPA_PROT_MAX +}; + +struct ipa_ep_pair_info { + __u32 consumer_pipe_num; + __u32 producer_pipe_num; + __u32 ep_id; + __u32 padding; +}; + +/** + * struct ipa_ioc_get_ep_info - query usb/pcie ep info + * @ep_type: type USB/PCIE - i/p param + * @max_ep_pairs: max number of ep_pairs (constant), + (QUERY_MAX_EP_PAIRS) + * @num_ep_pairs: number of ep_pairs - o/p param + * @ep_pair_size: sizeof(ipa_ep_pair_info) * max_ep_pairs + * @info: structure contains ep pair info + * @teth_prot : RMNET/CV2X --i/p param + * @teth_prot_valid - validity of i/p param protocol + */ +struct ipa_ioc_get_ep_info { + enum ipa_peripheral_ep_type ep_type; + __u32 ep_pair_size; + __u8 max_ep_pairs; + __u8 num_ep_pairs; + __u16 padding; + __u64 info; + enum ipa_data_ep_prot_type teth_prot; + __u8 teth_prot_valid; +}; + +/** + * struct ipa_set_pkt_threshold + * @pkt_threshold_enable: indicate pkt_thr enable or not + * @pkt_threshold: if pkt_threshold_enable = true, given the values + */ +struct ipa_set_pkt_threshold { + uint8_t pkt_threshold_enable; + int pkt_threshold; +}; + +/** + * struct ipa_ioc_set_pkt_threshold + * @ioctl_ptr: has to be typecasted to (__u64)(uintptr_t) + * @ioctl_data_size: + * Eg: For ipa_set_pkt_threshold = sizeof(ipa_set_pkt_threshold) + */ +struct ipa_ioc_set_pkt_threshold { + __u64 ioctl_ptr; + __u32 ioctl_data_size; + __u32 padding; +}; + +/** + * struct ipa_ioc_wigig_fst_switch - switch between wigig and wlan + * @netdev_name: wigig interface name + * @client_mac_addr: client to switch between netdevs + * @to_wigig: shall wlan client switch to wigig or the opposite? + */ +struct ipa_ioc_wigig_fst_switch { + uint8_t netdev_name[IPA_RESOURCE_NAME_MAX]; + uint8_t client_mac_addr[IPA_MAC_ADDR_SIZE]; + int to_wigig; +}; + +/** + * struct ipa_msg_meta - Format of the message metadata. + * @msg_type: the type of the message + * @rsvd: reserved bits for future use. + * @msg_len: the length of the message in bytes + * + * For push model: + * Client in user-space should issue a read on the device (/dev/ipa) with a + * sufficiently large buffer in a continuous loop, call will block when there is + * no message to read. Upon return, client can read the ipa_msg_meta from start + * of buffer to find out type and length of message + * size of buffer supplied >= (size of largest message + size of metadata) + * + * For pull model: + * Client in user-space can also issue a pull msg IOCTL to device (/dev/ipa) + * with a payload containing space for the ipa_msg_meta and the message specific + * payload length. + * size of buffer supplied == (len of specific message + size of metadata) + */ +struct ipa_msg_meta { + uint8_t msg_type; + uint8_t rsvd; + uint16_t msg_len; +}; + +/** + * struct ipa_wlan_msg - To hold information about wlan client + * @name: name of the wlan interface + * @mac_addr: mac address of wlan client + * @if_index: netdev interface index + * + * wlan drivers need to pass name of wlan iface and mac address of + * wlan client along with ipa_wlan_event, whenever a wlan client is + * connected/disconnected/moved to power save/come out of power save + */ +struct ipa_wlan_msg { + char name[IPA_RESOURCE_NAME_MAX]; + uint8_t mac_addr[IPA_MAC_ADDR_SIZE]; + int16_t if_index; +}; + +/** + * enum ipa_wlan_hdr_attrib_type - attribute type + * in wlan client header + * + * WLAN_HDR_ATTRIB_MAC_ADDR: attrib type mac address + * WLAN_HDR_ATTRIB_STA_ID: attrib type station id + */ +enum ipa_wlan_hdr_attrib_type { + WLAN_HDR_ATTRIB_MAC_ADDR, + WLAN_HDR_ATTRIB_STA_ID +}; + +/** + * struct ipa_wlan_hdr_attrib_val - header attribute value + * @attrib_type: type of attribute + * @offset: offset of attribute within header + * @u.mac_addr: mac address + * @u.sta_id: station id + */ +struct ipa_wlan_hdr_attrib_val { + enum ipa_wlan_hdr_attrib_type attrib_type; + uint8_t offset; + union { + uint8_t mac_addr[IPA_MAC_ADDR_SIZE]; + uint8_t sta_id; + } u; +}; + +/** + * struct ipa_wlan_msg_ex - To hold information about wlan client + * @name: name of the wlan interface + * @num_of_attribs: number of attributes + * @attrib_val: holds attribute values + * + * wlan drivers need to pass name of wlan iface and mac address + * of wlan client or station id along with ipa_wlan_event, + * whenever a wlan client is connected/disconnected/moved to + * power save/come out of power save + */ +struct ipa_wlan_msg_ex { + char name[IPA_RESOURCE_NAME_MAX]; + uint8_t num_of_attribs; + struct ipa_wlan_hdr_attrib_val attribs[0]; +}; + +/** + * struct ipa_wigig_msg- To hold information about wigig event + * @name: name of the wigig interface + * @client_mac_addr: the relevant wigig client mac address + * @ipa_client: TX pipe associated with the wigig client in case of connect + * @to_wigig: FST switch direction wlan->wigig? + */ +struct ipa_wigig_msg { + char name[IPA_RESOURCE_NAME_MAX]; + uint8_t client_mac_addr[IPA_MAC_ADDR_SIZE]; + union { + enum ipa_client_type ipa_client; + uint8_t to_wigig; + } u; +}; + +struct ipa_ecm_msg { + char name[IPA_RESOURCE_NAME_MAX]; + int ifindex; +}; + +/** + * struct ipa_wan_msg - To hold information about wan client + * @name: name of the wan interface + * + * CnE need to pass the name of default wan iface when connected/disconnected. + * CNE need to pass the gw info in wlan AP+STA mode. + * netmgr need to pass the name of wan eMBMS iface when connected. + */ +struct ipa_wan_msg { + char upstream_ifname[IPA_RESOURCE_NAME_MAX]; + char tethered_ifname[IPA_RESOURCE_NAME_MAX]; + enum ipa_ip_type ip; + uint32_t ipv4_addr_gw; + uint32_t ipv6_addr_gw[IPA_WAN_MSG_IPv6_ADDR_GW_LEN]; +}; + +/* uc activation command Ids */ +#define IPA_SOCKSV5_ADD_COM_ID 15 +#define IPA_IPv6_NAT_COM_ID 16 + +/** + * ipa_kernel_tests_socksv5_uc_tmpl - uc activation entry info + * @cmd_id: uc command id + * @cmd_param: uC command param + * @ipa_kernel_tests_ip_hdr_temp: ip header + * @src_port: source port + * @dst_port: destination port + * @ipa_sockv5_mask: uc attribute mask for options/etc + * @out_irs: 4B/4B Seq/Ack/SACK + * @out_iss + * @in_irs + * @in_iss + * @out_ircv_tsval: timestamp attributes + * @in_ircv_tsecr + * @out_ircv_tsecr + * @in_ircv_tsval + * @in_isnd_wscale: window scale attributes + * @out_isnd_wscale + * @in_ircv_wscale + * @out_ircv_wscale + * @direction: 1 for UL 0 for DL + * @handle: uc activation table index + */ +struct ipa_kernel_tests_socksv5_uc_tmpl { + /* direction 1 = UL, 0 = DL */ + __u8 direction; + __u8 padding1; + /* output: handle (index) */ + __u16 handle; + __u16 cmd_id; + __u16 padding2; + __u32 cmd_param; + + __be32 ip_src_addr; + __be32 ip_dst_addr; + __be32 ipv6_src_addr[4]; + __be32 ipv6_dst_addr[4]; + + /* 2B src/dst port */ + __u16 src_port; + __u16 dst_port; + + /* attribute mask */ + __u32 ipa_sockv5_mask; + + /* required update 4B/4B Seq/Ack/SACK */ + __u32 out_irs; + __u32 out_iss; + __u32 in_irs; + __u32 in_iss; + + /* option 10B: time-stamp */ + __u32 out_ircv_tsval; + __u32 in_ircv_tsecr; + __u32 out_ircv_tsecr; + __u32 in_ircv_tsval; + + /* option 2B: window-scaling/dynamic */ + __u16 in_isnd_wscale : 4; + __u16 out_isnd_wscale : 4; + __u16 in_ircv_wscale : 4; + __u16 out_ircv_wscale : 4; + __u32 padding3; + +}; + +/** + * struct ipacm_socksv5_info - To hold information about socksv5 connections + * @ip_type: ip type + * @ipv4_src: ipv4 src address + * @ipv4_dst: ipv4 dst address + * @ipv6_src: ipv6 src address + * @ipv6_dst: ipv6 dst address + * @src_port: src port number + * @dst_port: dst port number + * @index: the uc activation tbl index + */ + +struct ipacm_socksv5_info { + /* ip-type */ + enum ipa_ip_type ip_type; + + /* ipv4 */ + __u32 ipv4_src; + __u32 ipv4_dst; + + /* ipv6 */ + __u32 ipv6_src[4]; + __u32 ipv6_dst[4]; + + /* 2B src/dst port */ + __u16 src_port; + __u16 dst_port; + + /* uc-tbl index */ + __u16 index; + __u16 padding; +}; + +/** + * struct ipa_socksv5_msg - To hold information about socksv5 client + * @ul_in: uplink connection info + * @dl_in: downlink connection info + * @handle: used for ipacm to distinguish connections + * + * CnE need to pass the name of default wan iface when connected/disconnected. + * CNE need to pass the gw info in wlan AP+STA mode. + * netmgr need to pass the name of wan eMBMS iface when connected. + */ +struct ipa_socksv5_msg { + struct ipacm_socksv5_info ul_in; + struct ipacm_socksv5_info dl_in; + + /* handle (index) */ + __u16 handle; + __u16 padding; +}; + +/** + * struct ipa_ioc_ipv6_nat_uc_act_entry - To hold information about IPv6 NAT + * uC entry + * @cmd_id[in]: IPv6 NAT uC CMD ID - used for identifying uc activation type + * @private_address_lsb[in]: client private address lsb + * @private_address_msb[in]: client private address msbst + * @public_address_lsb[in]: client public address lsb + * @public_address_msb[in]: client public address msb + * @private_port[in]: client private port + * @public_port[in]: client public port + * @index[out]: uC activation entry index + */ +struct ipa_ioc_ipv6_nat_uc_act_entry { + __u16 cmd_id; + __u16 index; + __u32 padding; + __u32 private_port; + __u32 public_port; + __u64 private_address_lsb; + __u64 private_address_msb; + __u64 public_address_lsb; + __u64 public_address_msb; +}; + +/** + * union ipa_ioc_uc_activation_entry - To hold information about uC activation + * entry + * @socks[in]: fill here if entry is Socksv5 entry + * @ipv6_nat[in]: fill here if entry is IPv6 NAT entry + */ +union ipa_ioc_uc_activation_entry { + struct ipa_kernel_tests_socksv5_uc_tmpl socks; + struct ipa_ioc_ipv6_nat_uc_act_entry ipv6_nat; +}; + +/** + * struct ipa_ioc_rm_dependency - parameters for add/delete dependency + * @resource_name: name of dependent resource + * @depends_on_name: name of its dependency + */ +struct ipa_ioc_rm_dependency { + enum ipa_rm_resource_name resource_name; + enum ipa_rm_resource_name depends_on_name; +}; + +struct ipa_ioc_generate_flt_eq { + enum ipa_ip_type ip; + struct ipa_rule_attrib attrib; + struct ipa_ipfltri_rule_eq eq_attrib; +}; + +/** + * struct ipa_ioc_write_qmapid - to write mux id to endpoint metadata register + * @mux_id: mux id of wan + */ +struct ipa_ioc_write_qmapid { + enum ipa_client_type client; + uint8_t qmap_id; +}; + +/** + * struct ipa_flt_rt_counter_alloc - flt/rt counter id allocation + * @num_counters: input param, num of counters need to be allocated + * @allow_less: input param, if true, success even few counter than request + * @start_id: output param, allocated start_id, 0 when allocation fails + * @end_id: output param, allocated start_id, 0 when allocation fails + */ +struct ipa_flt_rt_counter_alloc { + uint8_t num_counters; + uint8_t allow_less; + uint8_t start_id; + uint8_t end_id; +}; + +/** + * struct ipa_ioc_flt_rt_counter_alloc - flt/rt counter id allocation ioctl + * @hdl: output param, hdl used for deallocation, negative if allocation fails + * @hw_counter: HW counters for HW process + * @sw_counter: SW counters for uC / non-HW process + */ +struct ipa_ioc_flt_rt_counter_alloc { + int hdl; + struct ipa_flt_rt_counter_alloc hw_counter; + struct ipa_flt_rt_counter_alloc sw_counter; +}; + +/** + * struct ipa_flt_rt_stats - flt/rt stats info + * @num_pkts: number of packets + * @num_pkts_hash: number of packets in hash entry + * @num_bytes: number of bytes + */ +struct ipa_flt_rt_stats { + uint32_t num_pkts; + uint32_t num_pkts_hash; + uint64_t num_bytes; +}; + +/** + * struct ipa_ioc_flt_rt_query - flt/rt counter id query + * @start_id: start counter id for query + * @end_id: end counter id for query + * @reset: this query need hw counter to be reset or not + * @stats_size: sizeof(ipa_flt_rt_stats) + * @reserved: reserved bits for alignment + * @stats: structure contains the query result + */ +struct ipa_ioc_flt_rt_query { + uint8_t start_id; + uint8_t end_id; + uint8_t reset; + uint32_t stats_size; + uint8_t reserved; + uint64_t stats; +}; + +enum ipacm_client_enum { + IPACM_CLIENT_USB = 1, + IPACM_CLIENT_WLAN, + IPACM_CLIENT_MAX +}; + +#define IPACM_SUPPORT_OF_LAN_STATS_FOR_ODU_CLIENTS + +enum ipacm_per_client_device_type { + IPACM_CLIENT_DEVICE_TYPE_USB = 0, + IPACM_CLIENT_DEVICE_TYPE_WLAN = 1, + IPACM_CLIENT_DEVICE_TYPE_ETH = 2, + IPACM_CLIENT_DEVICE_TYPE_ODU = 3, + IPACM_CLIENT_DEVICE_MAX +}; + +/** + * max number of device types supported. + */ +#define IPACM_MAX_CLIENT_DEVICE_TYPES IPACM_CLIENT_DEVICE_MAX + +/** + * @lanIface - Name of the lan interface + * @mac: Mac address of the client. + */ +struct ipa_lan_client_msg { + char lanIface[IPA_RESOURCE_NAME_MAX]; + uint8_t mac[IPA_MAC_ADDR_SIZE]; +}; + +/** + * struct ipa_lan_client - lan client data + * @mac: MAC Address of the client. + * @client_idx: Client Index. + * @inited: Bool to indicate whether client info is set. + */ +struct ipa_lan_client { + uint8_t mac[IPA_MAC_ADDR_SIZE]; + int8_t client_idx; + uint8_t inited; +}; + +/** + * struct ipa_lan_client_cntr_index + * @ul_cnt_idx: H/w counter index for uplink stats + * @dl_cnt_idx: H/w counter index for downlink stats + */ +struct ipa_lan_client_cntr_index { + __u8 ul_cnt_idx; + __u8 dl_cnt_idx; +}; + +/** + * struct ipa_tether_device_info - tether device info indicated from IPACM + * @ul_src_pipe: Source pipe of the lan client. + * @hdr_len: Header length of the client. + * @num_clients: Number of clients connected. + */ +struct ipa_tether_device_info { + __s32 ul_src_pipe; + __u8 hdr_len; + __u8 padding1; + __u16 padding2; + __u32 num_clients; + struct ipa_lan_client lan_client[IPA_MAX_NUM_HW_PATH_CLIENTS]; + struct ipa_lan_client_cntr_index + lan_client_indices[IPA_MAX_NUM_HW_PATH_CLIENTS]; +}; + +/** + * enum ipa_vlan_ifaces - vlan interfaces types + */ +enum ipa_vlan_ifaces { + IPA_VLAN_IF_ETH, + IPA_VLAN_IF_ETH0, + IPA_VLAN_IF_ETH1, + IPA_VLAN_IF_RNDIS, + IPA_VLAN_IF_ECM +}; + +#define IPA_VLAN_IF_EMAC IPA_VLAN_IF_ETH +#define IPA_VLAN_IF_MAX (IPA_VLAN_IF_ECM + 1) + +/** + * struct ipa_get_vlan_mode - get vlan mode of a Lan interface + * @iface: Lan interface type to be queried. + * @is_vlan_mode: output parameter, is interface in vlan mode, valid only when + * ioctl return val is non-negative + */ +struct ipa_ioc_get_vlan_mode { + enum ipa_vlan_ifaces iface; + uint32_t is_vlan_mode; +}; + +/** + * struct ipa_ioc_bridge_vlan_mapping_info - vlan to bridge mapping info + * @bridge_name: bridge interface name + * @vlan_id: vlan ID bridge is mapped to + * @bridge_ipv4: bridge interface ipv4 address + * @subnet_mask: bridge interface subnet mask + * @lan2lan_sw: indicate lan2lan traffic take sw-path or not + */ +struct ipa_ioc_bridge_vlan_mapping_info { + char bridge_name[IPA_RESOURCE_NAME_MAX]; + uint8_t lan2lan_sw; + uint16_t vlan_id; + uint32_t bridge_ipv4; + uint32_t subnet_mask; +}; + +struct ipa_coalesce_info { + uint8_t qmap_id; + uint8_t tcp_enable; + uint8_t udp_enable; +}; + +struct ipa_mtu_info { + char if_name[IPA_RESOURCE_NAME_MAX]; + enum ipa_ip_type ip_type; + uint16_t mtu_v4; + uint16_t mtu_v6; +}; + +struct ipa_odl_ep_info { + __u32 cons_pipe_num; + __u32 prod_pipe_num; + __u32 peripheral_iface_id; + __u32 ep_type; +}; + +struct odl_agg_pipe_info { + __u16 agg_byte_limit; +}; + +struct ipa_odl_modem_config { + __u8 config_status; +}; + +struct ipa_ioc_fnr_index_info { + uint8_t hw_counter_offset; + uint8_t sw_counter_offset; +}; + +enum ipacm_hw_index_counter_type { + UL_HW = 0, + DL_HW, + DL_ALL, + UL_ALL, +}; + +enum ipacm_hw_index_counter_virtual_type { + UL_HW_CACHE = 0, + DL_HW_CACHE, + UL_WLAN_TX, + DL_WLAN_TX +}; + +/** + * struct ipa_ioc_pdn_config - provide pdn configuration + * @dev_name: PDN interface name + * @pdn_cfg_type: type of the pdn config applied. + * @enable: enable/disable pdn config type. + * @u.collison_cfg.pdn_ip_addr: pdn_ip_address used in collision config. + * @u.passthrough_cfg.pdn_ip_addr: pdn_ip_address used in passthrough config. + * @u.passthrough_cfg.device_type: Device type of the client. + * @u.passthrough_cfg.vlan_id: VLAN ID of the client. + * @u.passthrough_cfg.client_mac_addr: client mac for which passthough + *» is enabled. + * @u.passthrough_cfg.skip_nat: skip NAT processing. + * @default_pdn: bool to indicate the config is for default pdn. + */ +struct ipa_ioc_pdn_config { + char dev_name[IPA_RESOURCE_NAME_MAX]; + enum ipa_pdn_config_event pdn_cfg_type; + __u8 enable; + union { + struct ipa_pdn_ip_collision_cfg { + __u32 pdn_ip_addr; + } collison_cfg; + + struct ipa_pdn_ip_passthrough_cfg { + __u32 pdn_ip_addr; + enum ipacm_per_client_device_type device_type; + __u16 vlan_id; + __u8 client_mac_addr[IPA_MAC_ADDR_SIZE]; + __u8 skip_nat; + } passthrough_cfg; + } u; + __u8 default_pdn; +}; + +/** + * struct ipa_ioc_mac_client_list_type- mac addr exception list + * @mac_addr: an array to hold clients mac addrs + * @num_of_clients: holds num of clients to blacklist or whitelist + * @flt_state: true to block current mac addrs and false to clean + * up all previous mac addrs + */ +struct ipa_ioc_mac_client_list_type { + int num_of_clients; + __u8 mac_addr[IPA_MAX_NUM_MAC_FLT][IPA_MAC_ADDR_SIZE]; + __u8 flt_state; + __u8 padding; +}; + +/** + * struct ipa_sw_flt_list_type- exception list + * @mac_enable: true to block current mac addrs and false to clean + * up all previous mac addrs + * @num_of_mac: holds num of clients to blacklist + * @mac_addr: an array to hold clients mac addrs + * @ipv4_segs_enable: true to block current ipv4 addrs and false to clean + * up all previous ipv4 addrs + * @ipv4_segs_ipv6_offload: reserved flexibility for future use. + * true will indicate ipv6 could be still offloaded and + * default is set to false as sw-path for ipv6 as well. + * @num_of_ipv4_segs: holds num of ipv4 segs to blacklist + * @ipv4_segs: an array to hold clients ipv4 segs addrs + * @iface_enable: true to block current ifaces and false to clean + * up all previous ifaces + * @num_of_iface: holds num of ifaces to blacklist + * @iface: an array to hold netdev ifaces + */ +struct ipa_sw_flt_list_type { + uint8_t mac_enable; + int num_of_mac; + uint8_t mac_addr[IPA_MAX_NUM_MAC_FLT][IPA_MAC_ADDR_SIZE]; + uint8_t ipv4_segs_enable; + uint8_t ipv4_segs_ipv6_offload; + int num_of_ipv4_segs; + uint32_t ipv4_segs[IPA_MAX_NUM_IPv4_SEGS_FLT][2]; + uint8_t iface_enable; + int num_of_iface; + char iface[IPA_MAX_NUM_IFACE_FLT][IPA_RESOURCE_NAME_MAX]; +}; + +/** + * struct ipa_ippt_sw_flt_list_type- exception list + * @ipv4_enable: true to block ipv4 addrs given below and false to clean + * up all previous ipv4 addrs + * @num_of_ipv4: holds num of ipv4 to SW-exception + * @ipv4: an array to hold ipv4 addrs to SW-exception + * @port_enable: true to block current ports and false to clean + * up all previous ports + * @num_of_port: holds num of ports to SW-exception + * @port: an array to hold connection ports to SW-exception + */ + +struct ipa_ippt_sw_flt_list_type { + uint8_t ipv4_enable; + int num_of_ipv4; + uint32_t ipv4[IPA_MAX_PDN_NUM]; + uint8_t port_enable; + int num_of_port; + uint16_t port[IPA_MAX_IPPT_NUM_PORT_FLT]; +}; + +/** + * struct ipa_ioc_sw_flt_list_type + * @ioctl_ptr: has to be typecasted to (__u64)(uintptr_t) + * @ioctl_data_size: + * Eg: For ipa_sw_flt_list_type = sizeof(ipa_sw_flt_list_type) + * Eg: For ipa_ippt_sw_flt_list_type = sizeof(ipa_ippt_sw_flt_list_type) + */ +struct ipa_ioc_sw_flt_list_type { + __u64 ioctl_ptr; + __u32 ioctl_data_size; + __u32 padding; +}; + +/** + * struct ipa_macsec_map - mapping between ethX to macsecY + * @phy_name: name of the physical NIC (ethX) + * - must be equal to an existing physical NIC name + * @macsec_name: name of the macsec NIC (macsecY) + */ +struct ipa_macsec_map { + char phy_name[IPA_RESOURCE_NAME_MAX]; + char macsec_name[IPA_RESOURCE_NAME_MAX]; +}; + +/** + * struct ipa_ioc_macsec_info - provide macsec info + * @ioctl_ptr: has to be typecasted to (__u64)(uintptr_t) + * @ioctl_data_size: + * Eg: For ipa_macsec_map = sizeof(ipa_macsec_map) + */ +struct ipa_ioc_macsec_info { + __u64 ioctl_ptr; + __u32 ioctl_data_size; + __u32 padding; +}; + + +enum ipa_ext_router_mode { + IPA_PREFIX_DISABLED = 0, + IPA_PREFIX_SHARING, + IPA_PREFIX_DELEGATION +}; + +/** + * struct ipa_ioc_ext_router_info - provide ext_router info + * @ipa_ext_router_mode: prefix sharing, prefix delegation, or disabled mode + * @pdn_name: PDN interface name + * @ipv6_addr: the prefix addr used for dummy or delegated prefixes + * @ipv6_mask: the ipv6 mask used to mask above addr to get the correct prefix + */ +struct ipa_ioc_ext_router_info { + enum ipa_ext_router_mode mode; + char pdn_name[IPA_RESOURCE_NAME_MAX]; + uint32_t ipv6_addr[4]; + uint32_t ipv6_mask[4]; +}; + +/** + * actual IOCTLs supported by IPA driver + */ +#define IPA_IOC_COAL_EVICT_POLICY _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_COAL_EVICT_POLICY, \ + struct ipa_ioc_coal_evict_policy *) +#define IPA_IOC_ADD_HDR _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_ADD_HDR, \ + struct ipa_ioc_add_hdr *) +#define IPA_IOC_DEL_HDR _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_DEL_HDR, \ + struct ipa_ioc_del_hdr *) +#define IPA_IOC_ADD_RT_RULE _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_ADD_RT_RULE, \ + struct ipa_ioc_add_rt_rule *) +#define IPA_IOC_ADD_RT_RULE_V2 _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_ADD_RT_RULE_V2, \ + struct ipa_ioc_add_rt_rule_v2 *) +#define IPA_IOC_ADD_RT_RULE_EXT _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_ADD_RT_RULE_EXT, \ + struct ipa_ioc_add_rt_rule_ext *) +#define IPA_IOC_ADD_RT_RULE_EXT_V2 _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_ADD_RT_RULE_EXT_V2, \ + struct ipa_ioc_add_rt_rule_ext_v2 *) +#define IPA_IOC_ADD_RT_RULE_AFTER _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_ADD_RT_RULE_AFTER, \ + struct ipa_ioc_add_rt_rule_after *) +#define IPA_IOC_ADD_RT_RULE_AFTER_V2 _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_ADD_RT_RULE_AFTER_V2, \ + struct ipa_ioc_add_rt_rule_after_v2 *) +#define IPA_IOC_DEL_RT_RULE _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_DEL_RT_RULE, \ + struct ipa_ioc_del_rt_rule *) +#define IPA_IOC_ADD_FLT_RULE _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_ADD_FLT_RULE, \ + struct ipa_ioc_add_flt_rule *) +#define IPA_IOC_ADD_FLT_RULE_V2 _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_ADD_FLT_RULE_V2, \ + struct ipa_ioc_add_flt_rule_v2 *) +#define IPA_IOC_ADD_FLT_RULE_AFTER _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_ADD_FLT_RULE_AFTER, \ + struct ipa_ioc_add_flt_rule_after *) +#define IPA_IOC_ADD_FLT_RULE_AFTER_V2 _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_ADD_FLT_RULE_AFTER_V2, \ + struct ipa_ioc_add_flt_rule_after_v2 *) +#define IPA_IOC_DEL_FLT_RULE _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_DEL_FLT_RULE, \ + struct ipa_ioc_del_flt_rule *) +#define IPA_IOC_COMMIT_HDR _IO(IPA_IOC_MAGIC,\ + IPA_IOCTL_COMMIT_HDR) +#define IPA_IOC_RESET_HDR _IO(IPA_IOC_MAGIC,\ + IPA_IOCTL_RESET_HDR) +#define IPA_IOC_COMMIT_RT _IOW(IPA_IOC_MAGIC, \ + IPA_IOCTL_COMMIT_RT, \ + enum ipa_ip_type) +#define IPA_IOC_RESET_RT _IOW(IPA_IOC_MAGIC, \ + IPA_IOCTL_RESET_RT, \ + enum ipa_ip_type) +#define IPA_IOC_COMMIT_FLT _IOW(IPA_IOC_MAGIC, \ + IPA_IOCTL_COMMIT_FLT, \ + enum ipa_ip_type) +#define IPA_IOC_RESET_FLT _IOW(IPA_IOC_MAGIC, \ + IPA_IOCTL_RESET_FLT, \ + enum ipa_ip_type) +#define IPA_IOC_DUMP _IO(IPA_IOC_MAGIC, \ + IPA_IOCTL_DUMP) +#define IPA_IOC_GET_RT_TBL _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_GET_RT_TBL, \ + struct ipa_ioc_get_rt_tbl *) +#define IPA_IOC_PUT_RT_TBL _IOW(IPA_IOC_MAGIC, \ + IPA_IOCTL_PUT_RT_TBL, \ + uint32_t) +#define IPA_IOC_COPY_HDR _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_COPY_HDR, \ + struct ipa_ioc_copy_hdr *) +#define IPA_IOC_QUERY_INTF _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_QUERY_INTF, \ + struct ipa_ioc_query_intf *) +#define IPA_IOC_QUERY_INTF_TX_PROPS _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_QUERY_INTF_TX_PROPS, \ + struct ipa_ioc_query_intf_tx_props *) +#define IPA_IOC_QUERY_INTF_RX_PROPS _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_QUERY_INTF_RX_PROPS, \ + struct ipa_ioc_query_intf_rx_props *) +#define IPA_IOC_QUERY_INTF_EXT_PROPS _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_QUERY_INTF_EXT_PROPS, \ + struct ipa_ioc_query_intf_ext_props *) +#define IPA_IOC_GET_HDR _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_GET_HDR, \ + struct ipa_ioc_get_hdr *) +#define IPA_IOC_PUT_HDR _IOW(IPA_IOC_MAGIC, \ + IPA_IOCTL_PUT_HDR, \ + uint32_t) +#define IPA_IOC_ALLOC_NAT_MEM _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_ALLOC_NAT_MEM, \ + struct ipa_ioc_nat_alloc_mem *) +#define IPA_IOC_ALLOC_NAT_TABLE _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_ALLOC_NAT_TABLE, \ + struct ipa_ioc_nat_ipv6ct_table_alloc *) +#define IPA_IOC_ALLOC_IPV6CT_TABLE _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_ALLOC_IPV6CT_TABLE, \ + struct ipa_ioc_nat_ipv6ct_table_alloc *) +#define IPA_IOC_V4_INIT_NAT _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_V4_INIT_NAT, \ + struct ipa_ioc_v4_nat_init *) +#define IPA_IOC_INIT_IPV6CT_TABLE _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_INIT_IPV6CT_TABLE, \ + struct ipa_ioc_ipv6ct_init *) +#define IPA_IOC_NAT_DMA _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_NAT_DMA, \ + struct ipa_ioc_nat_dma_cmd *) +#define IPA_IOC_TABLE_DMA_CMD _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_TABLE_DMA_CMD, \ + struct ipa_ioc_nat_dma_cmd *) +#define IPA_IOC_V4_DEL_NAT _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_V4_DEL_NAT, \ + struct ipa_ioc_v4_nat_del *) +#define IPA_IOC_DEL_NAT_TABLE _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_DEL_NAT_TABLE, \ + struct ipa_ioc_nat_ipv6ct_table_del *) +#define IPA_IOC_DEL_IPV6CT_TABLE _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_DEL_IPV6CT_TABLE, \ + struct ipa_ioc_nat_ipv6ct_table_del *) +#define IPA_IOC_GET_NAT_OFFSET _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_GET_NAT_OFFSET, \ + uint32_t *) +#define IPA_IOC_NAT_MODIFY_PDN _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_NAT_MODIFY_PDN, \ + struct ipa_ioc_nat_pdn_entry *) +#define IPA_IOC_SET_FLT _IOW(IPA_IOC_MAGIC, \ + IPA_IOCTL_SET_FLT, \ + uint32_t) +#define IPA_IOC_PULL_MSG _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_PULL_MSG, \ + struct ipa_msg_meta *) +#define IPA_IOC_RM_ADD_DEPENDENCY _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_RM_ADD_DEPENDENCY, \ + struct ipa_ioc_rm_dependency *) +#define IPA_IOC_RM_DEL_DEPENDENCY _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_RM_DEL_DEPENDENCY, \ + struct ipa_ioc_rm_dependency *) +#define IPA_IOC_GENERATE_FLT_EQ _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_GENERATE_FLT_EQ, \ + struct ipa_ioc_generate_flt_eq *) +#define IPA_IOC_QUERY_EP_MAPPING _IOR(IPA_IOC_MAGIC, \ + IPA_IOCTL_QUERY_EP_MAPPING, \ + uint32_t) +#define IPA_IOC_QUERY_RT_TBL_INDEX _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_QUERY_RT_TBL_INDEX, \ + struct ipa_ioc_get_rt_tbl_indx *) +#define IPA_IOC_WRITE_QMAPID _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_WRITE_QMAPID, \ + struct ipa_ioc_write_qmapid *) +#define IPA_IOC_MDFY_FLT_RULE _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_MDFY_FLT_RULE, \ + struct ipa_ioc_mdfy_flt_rule *) +#define IPA_IOC_MDFY_FLT_RULE_V2 _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_MDFY_FLT_RULE_V2, \ + struct ipa_ioc_mdfy_flt_rule_v2 *) +#define IPA_IOC_MDFY_RT_RULE _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_MDFY_RT_RULE, \ + struct ipa_ioc_mdfy_rt_rule *) +#define IPA_IOC_MDFY_RT_RULE_V2 _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_MDFY_RT_RULE_V2, \ + struct ipa_ioc_mdfy_rt_rule_v2 *) + +#define IPA_IOC_NOTIFY_WAN_UPSTREAM_ROUTE_ADD _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_NOTIFY_WAN_UPSTREAM_ROUTE_ADD, \ + struct ipa_wan_msg *) + +#define IPA_IOC_NOTIFY_WAN_UPSTREAM_ROUTE_DEL _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_NOTIFY_WAN_UPSTREAM_ROUTE_DEL, \ + struct ipa_wan_msg *) +#define IPA_IOC_NOTIFY_WAN_EMBMS_CONNECTED _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_NOTIFY_WAN_EMBMS_CONNECTED, \ + struct ipa_wan_msg *) +#define IPA_IOC_ADD_HDR_PROC_CTX _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_ADD_HDR_PROC_CTX, \ + struct ipa_ioc_add_hdr_proc_ctx *) +#define IPA_IOC_DEL_HDR_PROC_CTX _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_DEL_HDR_PROC_CTX, \ + struct ipa_ioc_del_hdr_proc_ctx *) + +#define IPA_IOC_GET_HW_VERSION _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_GET_HW_VERSION, \ + enum ipa_hw_type *) + +#define IPA_IOC_ADD_VLAN_IFACE _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_ADD_VLAN_IFACE, \ + struct ipa_ioc_vlan_iface_info *) + +#define IPA_IOC_DEL_VLAN_IFACE _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_DEL_VLAN_IFACE, \ + struct ipa_ioc_vlan_iface_info *) + +#define IPA_IOC_ADD_L2TP_VLAN_MAPPING _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_ADD_L2TP_VLAN_MAPPING, \ + struct ipa_ioc_l2tp_vlan_mapping_info *) + +#define IPA_IOC_DEL_L2TP_VLAN_MAPPING _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_DEL_L2TP_VLAN_MAPPING, \ + struct ipa_ioc_l2tp_vlan_mapping_info *) +#define IPA_IOC_GET_VLAN_MODE _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_GET_VLAN_MODE, \ + struct ipa_ioc_get_vlan_mode *) +#define IPA_IOC_ADD_BRIDGE_VLAN_MAPPING _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_ADD_BRIDGE_VLAN_MAPPING, \ + struct ipa_ioc_bridge_vlan_mapping_info) + +#define IPA_IOC_DEL_BRIDGE_VLAN_MAPPING _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_DEL_BRIDGE_VLAN_MAPPING, \ + struct ipa_ioc_bridge_vlan_mapping_info) +#define IPA_IOC_CLEANUP _IO(IPA_IOC_MAGIC,\ + IPA_IOCTL_CLEANUP) +#define IPA_IOC_QUERY_WLAN_CLIENT _IO(IPA_IOC_MAGIC,\ + IPA_IOCTL_QUERY_WLAN_CLIENT) + +#define IPA_IOC_ODL_QUERY_ADAPL_EP_INFO _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_ODL_QUERY_ADAPL_EP_INFO, \ + struct ipa_odl_ep_info) +#define IPA_IOC_ODL_GET_AGG_BYTE_LIMIT _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_ODL_GET_AGG_BYTE_LIMIT, \ + struct odl_agg_pipe_info) + +#define IPA_IOC_ODL_QUERY_MODEM_CONFIG _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_ODL_QUERY_MODEM_CONFIG, \ + struct ipa_odl_modem_config) + +#define IPA_IOC_GSB_CONNECT _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_GSB_CONNECT, \ + struct ipa_ioc_gsb_info) + +#define IPA_IOC_GSB_DISCONNECT _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_GSB_DISCONNECT, \ + struct ipa_ioc_gsb_info) + +#define IPA_IOC_WIGIG_FST_SWITCH _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_WIGIG_FST_SWITCH, \ + struct ipa_ioc_wigig_fst_switch) + +#define IPA_IOC_FNR_COUNTER_ALLOC _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_FNR_COUNTER_ALLOC, \ + struct ipa_ioc_flt_rt_counter_alloc) + +#define IPA_IOC_FNR_COUNTER_DEALLOC _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_FNR_COUNTER_DEALLOC, \ + int) + +#define IPA_IOC_FNR_COUNTER_QUERY _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_FNR_COUNTER_QUERY, \ + struct ipa_ioc_flt_rt_query) + +#define IPA_IOC_SET_FNR_COUNTER_INFO _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_SET_FNR_COUNTER_INFO, \ + struct ipa_ioc_fnr_index_info) + +#define IPA_IOC_GET_NAT_IN_SRAM_INFO _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_GET_NAT_IN_SRAM_INFO, \ + struct ipa_nat_in_sram_info) + +#define IPA_IOC_APP_CLOCK_VOTE _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_APP_CLOCK_VOTE, \ + uint32_t) + +#define IPA_IOC_PDN_CONFIG _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_PDN_CONFIG, \ + struct ipa_ioc_pdn_config) + +#define IPA_IOC_SET_MAC_FLT _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_SET_MAC_FLT, \ + struct ipa_ioc_mac_client_list_type) + +#define IPA_IOC_GET_PHERIPHERAL_EP_INFO _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_GET_PHERIPHERAL_EP_INFO, \ + struct ipa_ioc_get_ep_info) + +#define IPA_IOC_ADD_UC_ACT_ENTRY _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_ADD_UC_ACT_ENTRY, \ + union ipa_ioc_uc_activation_entry) + +#define IPA_IOC_DEL_UC_ACT_ENTRY _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_DEL_UC_ACT_ENTRY, \ + __u16) + +#define IPA_IOC_SET_SW_FLT _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_SET_SW_FLT, \ + struct ipa_ioc_sw_flt_list_type) + +#define IPA_IOC_SET_PKT_THRESHOLD _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_SET_PKT_THRESHOLD, \ + struct ipa_ioc_set_pkt_threshold) + +#define IPA_IOC_ADD_EoGRE_MAPPING _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_ADD_EoGRE_MAPPING, \ + struct ipa_ioc_eogre_info) +#define IPA_IOC_DEL_EoGRE_MAPPING _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_DEL_EoGRE_MAPPING, \ + struct ipa_ioc_eogre_info) + +#define IPA_IOC_SET_IPPT_SW_FLT _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_SET_IPPT_SW_FLT, \ + struct ipa_ioc_sw_flt_list_type) + +#define IPA_IOC_ADD_MACSEC_MAPPING _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_ADD_MACSEC_MAPPING, \ + struct ipa_ioc_macsec_info) +#define IPA_IOC_DEL_MACSEC_MAPPING _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_DEL_MACSEC_MAPPING, \ + struct ipa_ioc_macsec_info) + +#define IPA_IOC_SET_NAT_EXC_RT_TBL_IDX _IOW(IPA_IOC_MAGIC, \ + IPA_IOCTL_SET_NAT_EXC_RT_TBL_IDX, \ + uint32_t) + +#define IPA_IOC_SET_CONN_TRACK_EXC_RT_TBL_IDX _IOW(IPA_IOC_MAGIC, \ + IPA_IOCTL_SET_CONN_TRACK_EXC_RT_TBL_IDX, \ + uint32_t) + +#define IPA_IOC_SET_EXT_ROUTER_MODE _IOWR(IPA_IOC_MAGIC, \ + IPA_IOCTL_SET_EXT_ROUTER_MODE, \ + struct ipa_ioc_ext_router_info) +/* + * unique magic number of the Tethering bridge ioctls + */ +#define TETH_BRIDGE_IOC_MAGIC 0xCE + +/* + * Ioctls supported by Tethering bridge driver + */ +#define TETH_BRIDGE_IOCTL_SET_BRIDGE_MODE 0 +#define TETH_BRIDGE_IOCTL_SET_AGGR_PARAMS 1 +#define TETH_BRIDGE_IOCTL_GET_AGGR_PARAMS 2 +#define TETH_BRIDGE_IOCTL_GET_AGGR_CAPABILITIES 3 +#define TETH_BRIDGE_IOCTL_MAX 4 + + +/** + * enum teth_link_protocol_type - link protocol (IP / Ethernet) + */ +enum teth_link_protocol_type { + TETH_LINK_PROTOCOL_IP, + TETH_LINK_PROTOCOL_ETHERNET, + TETH_LINK_PROTOCOL_MAX, +}; + +/** + * enum teth_aggr_protocol_type - Aggregation protocol (MBIM / TLP) + */ +enum teth_aggr_protocol_type { + TETH_AGGR_PROTOCOL_NONE, + TETH_AGGR_PROTOCOL_MBIM, + TETH_AGGR_PROTOCOL_TLP, + TETH_AGGR_PROTOCOL_MAX, +}; + +/** + * struct teth_aggr_params_link - Aggregation parameters for uplink/downlink + * @aggr_prot: Aggregation protocol (MBIM / TLP) + * @max_transfer_size_byte: Maximal size of aggregated packet in bytes. + * Default value is 16*1024. + * @max_datagrams: Maximal number of IP packets in an aggregated + * packet. Default value is 16 + */ +struct teth_aggr_params_link { + enum teth_aggr_protocol_type aggr_prot; + uint32_t max_transfer_size_byte; + uint32_t max_datagrams; +}; + + +/** + * struct teth_aggr_params - Aggregation parmeters + * @ul: Uplink parameters + * @dl: Downlink parmaeters + */ +struct teth_aggr_params { + struct teth_aggr_params_link ul; + struct teth_aggr_params_link dl; +}; + +/** + * struct teth_aggr_capabilities - Aggregation capabilities + * @num_protocols: Number of protocols described in the array + * @prot_caps[]: Array of aggregation capabilities per protocol + */ +struct teth_aggr_capabilities { + uint16_t num_protocols; + struct teth_aggr_params_link prot_caps[0]; +}; + +/** + * struct teth_ioc_set_bridge_mode + * @link_protocol: link protocol (IP / Ethernet) + * @lcid: logical channel number + */ +struct teth_ioc_set_bridge_mode { + enum teth_link_protocol_type link_protocol; + uint16_t lcid; +}; + +/** + * struct teth_ioc_set_aggr_params + * @aggr_params: Aggregation parmeters + * @lcid: logical channel number + */ +struct teth_ioc_aggr_params { + struct teth_aggr_params aggr_params; + uint16_t lcid; +}; + +/** + * struct ipa_ioc_coal_evict_policy - + * + * Structure used with the IPA_IOCTL_COAL_EVICT_POLICY ioctl to + * control TCP/UDP eviction policy. + * + * @coal_vp_thrshld: + * + * Connection that is opened below this val will not get + * evicted. valid till v5_2. + * + * @coal_eviction_en: + * + * bool -> Enable eviction + * + * @coal_vp_gran_sel: + * + * Select the appropriate time granularity: four possible values (0-3) + * Valid from v5_5. + * + * @coal_vp_udp_thrshld: + * + * Coalescing eviction threshold. LRU VP stickness/inactivity + * defined by this threshold fot UDP connectiom. 0 mean all UDP's + * non sticky. Valid from v5_5. + * + * @coal_vp_tcp_thrshld: + * + * Coalescing eviction threshold. LRU VP stickness/inactivity + * defined by this threshold fot TCP connection. 0 mean all TCP's + * non sticky. Valid from v5_5. + * + * @coal_vp_udp_thrshld_en: + * + * bool -> Coalescing eviction enable for UDP connections when UDP + * pacjet arrived. 0-disable these evictions. Valid from v5_5. + * + * @coal_vp_tcp_thrshld_en: + * + * bool -> Coalescing eviction enable for TCP connections when TCP + * pacjet arrived. 0-disable these evictions. Valid from v5_5. + * + * @coal_vp_tcp_num: + * + * Configured TCP NUM value. SW define when TCP/UDP will treat as + * excess during eviction process. Valid from v5_5. + */ +struct ipa_ioc_coal_evict_policy { + uint32_t coal_vp_thrshld; + uint32_t reserved1; /* reserved bits for alignment */ + uint8_t coal_eviction_en; + uint8_t coal_vp_gran_sel; + uint8_t coal_vp_udp_thrshld; + uint8_t coal_vp_tcp_thrshld; + uint8_t coal_vp_udp_thrshld_en; + uint8_t coal_vp_tcp_thrshld_en; + uint8_t coal_vp_tcp_num; + uint8_t reserved2; /* reserved bits for alignment */ +}; + +/** + * struct ipa_nat_in_sram_info - query for nat in sram particulars + * @sram_mem_available_for_nat: Amount SRAM available to fit nat table + * @nat_table_offset_into_mmap: Offset into mmap'd vm where table will be + * @best_nat_in_sram_size_rqst: The size to request for mmap + * + * The last two elements above are required to deal with situations + * where the SRAM's physical address and size don't play nice with + * mmap'ings page size and boundary attributes. + */ +struct ipa_nat_in_sram_info { + uint32_t sram_mem_available_for_nat; + uint32_t nat_table_offset_into_mmap; + uint32_t best_nat_in_sram_size_rqst; +}; + +/** + * enum ipa_app_clock_vote_type + * + * The types of votes that can be accepted by the + * IPA_IOC_APP_CLOCK_VOTE ioctl + */ +enum ipa_app_clock_vote_type { + IPA_APP_CLK_DEVOTE = 0, + IPA_APP_CLK_VOTE = 1, + IPA_APP_CLK_RESET_VOTE = 2, +}; + +#define TETH_BRIDGE_IOC_SET_BRIDGE_MODE _IOW(TETH_BRIDGE_IOC_MAGIC, \ + TETH_BRIDGE_IOCTL_SET_BRIDGE_MODE, \ + struct teth_ioc_set_bridge_mode *) +#define TETH_BRIDGE_IOC_SET_AGGR_PARAMS _IOW(TETH_BRIDGE_IOC_MAGIC, \ + TETH_BRIDGE_IOCTL_SET_AGGR_PARAMS, \ + struct teth_ioc_aggr_params *) +#define TETH_BRIDGE_IOC_GET_AGGR_PARAMS _IOR(TETH_BRIDGE_IOC_MAGIC, \ + TETH_BRIDGE_IOCTL_GET_AGGR_PARAMS, \ + struct teth_ioc_aggr_params *) +#define TETH_BRIDGE_IOC_GET_AGGR_CAPABILITIES _IOWR(TETH_BRIDGE_IOC_MAGIC, \ + TETH_BRIDGE_IOCTL_GET_AGGR_CAPABILITIES, \ + struct teth_aggr_capabilities *) + +/* + * unique magic number of the ODU bridge ioctls + */ +#define ODU_BRIDGE_IOC_MAGIC 0xCD + +/* + * Ioctls supported by ODU bridge driver + */ +#define ODU_BRIDGE_IOCTL_SET_MODE 0 +#define ODU_BRIDGE_IOCTL_SET_LLV6_ADDR 1 +#define ODU_BRIDGE_IOCTL_MAX 2 + + +/** + * enum odu_bridge_mode - bridge mode + * (ROUTER MODE / BRIDGE MODE) + */ +enum odu_bridge_mode { + ODU_BRIDGE_MODE_ROUTER, + ODU_BRIDGE_MODE_BRIDGE, + ODU_BRIDGE_MODE_MAX, +}; + +#define ODU_BRIDGE_IOC_SET_MODE _IOW(ODU_BRIDGE_IOC_MAGIC, \ + ODU_BRIDGE_IOCTL_SET_MODE, \ + enum odu_bridge_mode) + +#define ODU_BRIDGE_IOC_SET_LLV6_ADDR _IOW(ODU_BRIDGE_IOC_MAGIC, \ + ODU_BRIDGE_IOCTL_SET_LLV6_ADDR, \ + struct in6_addr *) + +#endif /* _UAPI_MSM_IPA_H_ */ diff --git a/include/uapi/linux/netfilter/xt_domainfilter.h b/include/uapi/linux/netfilter/xt_domainfilter.h new file mode 100644 index 000000000000..2257ee27874c --- /dev/null +++ b/include/uapi/linux/netfilter/xt_domainfilter.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. + * + * Domain Filter Module:Implementation. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _XT_DOMAINFILTER_MATCH_H +#define _XT_DOMAINFILTER_MATCH_H + +#include + +enum { + XT_DOMAINFILTER_WHITE = 1 << 0, + XT_DOMAINFILTER_BLACK = 1 << 1, + + XT_DOMAINFILTER_NAME_LEN = 256, // lenght of a domain name +}; + +struct xt_domainfilter_match_info { + char domain_name[XT_DOMAINFILTER_NAME_LEN]; + __u8 flags; +}; + +#endif //_XT_DOMAINFILTER_MATCH_H diff --git a/include/uapi/linux/rmnet_ipa_fd_ioctl.h b/include/uapi/linux/rmnet_ipa_fd_ioctl.h new file mode 100644 index 000000000000..12aa9a1ba3c6 --- /dev/null +++ b/include/uapi/linux/rmnet_ipa_fd_ioctl.h @@ -0,0 +1,314 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * Copyright (c) 2013-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2021, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef _RMNET_IPA_FD_IOCTL_H +#define _RMNET_IPA_FD_IOCTL_H + +#include +#include +#include + +/** + * unique magic number of the IPA_WAN device + */ +#define WAN_IOC_MAGIC 0x69 + +#define WAN_IOCTL_ADD_FLT_RULE 0 +#define WAN_IOCTL_ADD_FLT_INDEX 1 +#define WAN_IOCTL_VOTE_FOR_BW_MBPS 2 +#define WAN_IOCTL_POLL_TETHERING_STATS 3 +#define WAN_IOCTL_SET_DATA_QUOTA 4 +#define WAN_IOCTL_SET_TETHER_CLIENT_PIPE 5 +#define WAN_IOCTL_QUERY_TETHER_STATS 6 +#define WAN_IOCTL_RESET_TETHER_STATS 7 +#define WAN_IOCTL_QUERY_DL_FILTER_STATS 8 +#define WAN_IOCTL_ADD_FLT_RULE_EX 9 +#define WAN_IOCTL_QUERY_TETHER_STATS_ALL 10 +#define WAN_IOCTL_NOTIFY_WAN_STATE 11 +#define WAN_IOCTL_ADD_UL_FLT_RULE 12 +#define WAN_IOCTL_ENABLE_PER_CLIENT_STATS 13 +#define WAN_IOCTL_QUERY_PER_CLIENT_STATS 14 +#define WAN_IOCTL_SET_LAN_CLIENT_INFO 15 +#define WAN_IOCTL_CLEAR_LAN_CLIENT_INFO 16 +#define WAN_IOCTL_SEND_LAN_CLIENT_MSG 17 +#define WAN_IOCTL_ADD_OFFLOAD_CONNECTION 18 +#define WAN_IOCTL_RMV_OFFLOAD_CONNECTION 19 +#define WAN_IOCTL_GET_WAN_MTU 20 +#define WAN_IOCTL_SET_DATA_QUOTA_WARNING 21 +#define WAN_IOCTL_NOTIFY_NAT_MOVE_RES 22 + +/* User space may not have this defined. */ +#ifndef IFNAMSIZ +#define IFNAMSIZ 16 +#endif + +/** + * struct wan_ioctl_poll_tethering_stats - structure used for + * WAN_IOCTL_POLL_TETHERING_STATS IOCTL. + * + * @polling_interval_secs: Polling interval in seconds. + * @reset_stats: Indicate whether to reset the stats (use 1) or not. + * + * The structure to be used by the user space in order to request for the + * tethering stats to be polled. Setting the interval to 0 indicates to stop + * the polling process. + */ +struct wan_ioctl_poll_tethering_stats { + uint64_t polling_interval_secs; + uint8_t reset_stats; +}; + +/** + * struct wan_ioctl_set_data_quota - structure used for + * WAN_IOCTL_SET_DATA_QUOTA IOCTL. + * + * @interface_name: Name of the interface on which to set the quota. + * @quota_mbytes: Quota (in Mbytes) for the above interface. + * @set_quota: Indicate whether to set the quota (use 1) or + * unset the quota. + * + * The structure to be used by the user space in order to request + * a quota to be set on a specific interface (by specifying its name). + */ +struct wan_ioctl_set_data_quota { + char interface_name[IFNAMSIZ]; + uint64_t quota_mbytes; + uint8_t set_quota; +}; + +/** + * struct wan_ioctl_set_data_quota_warning - structure used for + * WAN_IOCTL_SET_DATA_QUOTA_WARNING IOCTL. + * + * @interface_name: Name of the interface on which to set the quota or + * warning. + * @quota_mbytes: Quota (in Mbytes) for the above interface. + * @set_quota: Indicate whether to set the quota/warning (use 1) or + * unset the quota/warning. + * @set_warning: Indicate whether to set the quota/warning (use 1) or + * unset the quota/warning. + * @warning_mbytes: Warning (in Mbytes) for the above interface. + * @set_warning: Indicate whether to set the warning (use 1) or + * unset the warning. + * + * The structure to be used by the user space in order to request + * a quota to be set on a specific interface (by specifying its name). + */ +struct wan_ioctl_set_data_quota_warning { + char interface_name[IFNAMSIZ]; + uint64_t quota_mbytes; + uint8_t set_quota; + uint8_t set_warning; + uint16_t padding2; + uint64_t warning_mbytes; +}; + +struct wan_ioctl_set_tether_client_pipe { + /* enum of tether interface */ + enum ipacm_client_enum ipa_client; + uint8_t reset_client; + uint32_t ul_src_pipe_len; + uint32_t ul_src_pipe_list[QMI_IPA_MAX_PIPES_V01]; + uint32_t dl_dst_pipe_len; + uint32_t dl_dst_pipe_list[QMI_IPA_MAX_PIPES_V01]; +}; + +struct wan_ioctl_query_tether_stats { + /* Name of the upstream interface */ + char upstreamIface[IFNAMSIZ]; + /* Name of the tethered interface */ + char tetherIface[IFNAMSIZ]; + /* enum of tether interface */ + enum ipacm_client_enum ipa_client; + uint64_t ipv4_tx_packets; + uint64_t ipv4_tx_bytes; + uint64_t ipv4_rx_packets; + uint64_t ipv4_rx_bytes; + uint64_t ipv6_tx_packets; + uint64_t ipv6_tx_bytes; + uint64_t ipv6_rx_packets; + uint64_t ipv6_rx_bytes; +}; + +struct wan_ioctl_query_tether_stats_all { + /* Name of the upstream interface */ + char upstreamIface[IFNAMSIZ]; + /* enum of tether interface */ + enum ipacm_client_enum ipa_client; + uint8_t reset_stats; + uint64_t tx_bytes; + uint64_t rx_bytes; +}; + +struct wan_ioctl_reset_tether_stats { + /* Name of the upstream interface, not support now */ + char upstreamIface[IFNAMSIZ]; + /* Indicate whether to reset the stats (use 1) or not */ + uint8_t reset_stats; +}; + +struct wan_ioctl_query_dl_filter_stats { + /* Indicate whether to reset the filter stats (use 1) or not*/ + uint8_t reset_stats; + /* Modem response QMI */ + struct ipa_get_data_stats_resp_msg_v01 stats_resp; + /* provide right index to 1st firewall rule */ + uint32_t index; +}; + +struct wan_ioctl_notify_wan_state { + uint8_t up; + /* Name of the upstream interface */ + char upstreamIface[IFNAMSIZ]; +#define WAN_IOCTL_NOTIFY_WAN_INTF_NAME WAN_IOCTL_NOTIFY_WAN_INTF_NAME +}; +struct wan_ioctl_send_lan_client_msg { + /* Lan client info. */ + struct ipa_lan_client_msg lan_client; + /* Event to indicate whether client is + * connected or disconnected. + */ + enum ipa_per_client_stats_event client_event; +}; + +struct wan_ioctl_lan_client_info { + /* Device type of the client. */ + enum ipacm_per_client_device_type device_type; + /* MAC Address of the client. */ + uint8_t mac[IPA_MAC_ADDR_SIZE]; + /* Init client. */ + uint8_t client_init; + /* Client Index */ + int8_t client_idx; + /* Header length of the client. */ + uint8_t hdr_len; + /* Source pipe of the lan client. */ + enum ipa_client_type ul_src_pipe; + /* Counter indices for h/w fnr stats */ +#define IPA_HW_FNR_STATS + uint8_t ul_cnt_idx; + uint8_t dl_cnt_idx; +}; + +struct wan_ioctl_per_client_info { + /* MAC Address of the client. */ + uint8_t mac[IPA_MAC_ADDR_SIZE]; + /* Ipv4 UL traffic bytes. */ + uint64_t ipv4_tx_bytes; + /* Ipv4 DL traffic bytes. */ + uint64_t ipv4_rx_bytes; + /* Ipv6 UL traffic bytes. */ + uint64_t ipv6_tx_bytes; + /* Ipv6 DL traffic bytes. */ + uint64_t ipv6_rx_bytes; +}; + +struct wan_ioctl_query_per_client_stats { + /* Device type of the client. */ + enum ipacm_per_client_device_type device_type; + /* Indicate whether to reset the stats (use 1) or not */ + uint8_t reset_stats; + /* Indicates whether client is disconnected. */ + uint8_t disconnect_clnt; + /* Number of clients. */ + uint8_t num_clients; + /* Client information. */ + struct wan_ioctl_per_client_info + client_info[IPA_MAX_NUM_HW_PATH_CLIENTS]; +}; + +#define WAN_IOC_ADD_FLT_RULE _IOWR(WAN_IOC_MAGIC, \ + WAN_IOCTL_ADD_FLT_RULE, \ + struct ipa_install_fltr_rule_req_msg_v01 *) + +#define WAN_IOC_ADD_FLT_RULE_INDEX _IOWR(WAN_IOC_MAGIC, \ + WAN_IOCTL_ADD_FLT_INDEX, \ + struct ipa_fltr_installed_notif_req_msg_v01 *) + +#define WAN_IOC_VOTE_FOR_BW_MBPS _IOWR(WAN_IOC_MAGIC, \ + WAN_IOCTL_VOTE_FOR_BW_MBPS, \ + uint32_t *) + +#define WAN_IOC_POLL_TETHERING_STATS _IOWR(WAN_IOC_MAGIC, \ + WAN_IOCTL_POLL_TETHERING_STATS, \ + struct wan_ioctl_poll_tethering_stats *) + +#define WAN_IOC_SET_DATA_QUOTA _IOWR(WAN_IOC_MAGIC, \ + WAN_IOCTL_SET_DATA_QUOTA, \ + struct wan_ioctl_set_data_quota *) + +#define WAN_IOC_SET_TETHER_CLIENT_PIPE _IOWR(WAN_IOC_MAGIC, \ + WAN_IOCTL_SET_TETHER_CLIENT_PIPE, \ + struct wan_ioctl_set_tether_client_pipe *) + +#define WAN_IOC_QUERY_TETHER_STATS _IOWR(WAN_IOC_MAGIC, \ + WAN_IOCTL_QUERY_TETHER_STATS, \ + struct wan_ioctl_query_tether_stats *) + +#define WAN_IOC_RESET_TETHER_STATS _IOWR(WAN_IOC_MAGIC, \ + WAN_IOCTL_RESET_TETHER_STATS, \ + struct wan_ioctl_reset_tether_stats *) + +#define WAN_IOC_QUERY_DL_FILTER_STATS _IOWR(WAN_IOC_MAGIC, \ + WAN_IOCTL_QUERY_DL_FILTER_STATS, \ + struct wan_ioctl_query_dl_filter_stats *) + +#define WAN_IOC_ADD_FLT_RULE_EX _IOWR(WAN_IOC_MAGIC, \ + WAN_IOCTL_ADD_FLT_RULE_EX, \ + struct ipa_install_fltr_rule_req_ex_msg_v01 *) + +#define WAN_IOC_QUERY_TETHER_STATS_ALL _IOWR(WAN_IOC_MAGIC, \ + WAN_IOCTL_QUERY_TETHER_STATS_ALL, \ + struct wan_ioctl_query_tether_stats_all *) + +#define WAN_IOC_NOTIFY_WAN_STATE _IOWR(WAN_IOC_MAGIC, \ + WAN_IOCTL_NOTIFY_WAN_STATE, \ + struct wan_ioctl_notify_wan_state *) + +#define WAN_IOC_ADD_UL_FLT_RULE _IOWR(WAN_IOC_MAGIC, \ + WAN_IOCTL_ADD_UL_FLT_RULE, \ + struct ipa_configure_ul_firewall_rules_req_msg_v01 *) + +#define WAN_IOC_ENABLE_PER_CLIENT_STATS _IOWR(WAN_IOC_MAGIC, \ + WAN_IOCTL_ENABLE_PER_CLIENT_STATS, \ + bool *) + +#define WAN_IOC_QUERY_PER_CLIENT_STATS _IOWR(WAN_IOC_MAGIC, \ + WAN_IOCTL_QUERY_PER_CLIENT_STATS, \ + struct wan_ioctl_query_per_client_stats *) + +#define WAN_IOC_SET_LAN_CLIENT_INFO _IOWR(WAN_IOC_MAGIC, \ + WAN_IOCTL_SET_LAN_CLIENT_INFO, \ + struct wan_ioctl_lan_client_info *) + +#define WAN_IOC_SEND_LAN_CLIENT_MSG _IOWR(WAN_IOC_MAGIC, \ + WAN_IOCTL_SEND_LAN_CLIENT_MSG, \ + struct wan_ioctl_send_lan_client_msg *) + +#define WAN_IOC_CLEAR_LAN_CLIENT_INFO _IOWR(WAN_IOC_MAGIC, \ + WAN_IOCTL_CLEAR_LAN_CLIENT_INFO, \ + struct wan_ioctl_lan_client_info *) + +#define WAN_IOC_ADD_OFFLOAD_CONNECTION _IOWR(WAN_IOC_MAGIC, \ + WAN_IOCTL_ADD_OFFLOAD_CONNECTION, \ + struct ipa_add_offload_connection_req_msg_v01 *) + +#define WAN_IOC_RMV_OFFLOAD_CONNECTION _IOWR(WAN_IOC_MAGIC, \ + WAN_IOCTL_RMV_OFFLOAD_CONNECTION, \ + struct ipa_remove_offload_connection_req_msg_v01 *) + +#define WAN_IOC_GET_WAN_MTU _IOWR(WAN_IOC_MAGIC, \ + WAN_IOCTL_GET_WAN_MTU, \ + struct ipa_mtu_info *) + +#define WAN_IOC_SET_DATA_QUOTA_WARNING _IOWR(WAN_IOC_MAGIC, \ + WAN_IOCTL_SET_DATA_QUOTA_WARNING, \ + struct wan_ioctl_set_data_quota_warning) + +#define WAN_IOC_NOTIFY_NAT_MOVE_RES _IOWR(WAN_IOC_MAGIC, \ + WAN_IOCTL_NOTIFY_NAT_MOVE_RES, \ + bool) +#endif /* _RMNET_IPA_FD_IOCTL_H */ diff --git a/include/uapi/linux/sensor/range_sensor.h b/include/uapi/linux/sensor/range_sensor.h new file mode 100644 index 000000000000..a76cf5f8b934 --- /dev/null +++ b/include/uapi/linux/sensor/range_sensor.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ + +#ifndef LINUX_RANGE_SENSOR_H +#define LINUX_RANGE_SENSOR_H + +#if defined(__linux__) +#include +#else +#include +#include +#endif + +#define NUMBER_OF_ZONE 64 +#define NUMBER_OF_TARGET 1 + +struct range_sensor_data_t { + __u16 depth16[NUMBER_OF_ZONE]; + __u16 dmax[NUMBER_OF_ZONE]; + __u32 peak_signal[NUMBER_OF_ZONE]; + __u8 glass_detection_flag; +}; + +#endif diff --git a/include/ufs/ufs.h b/include/ufs/ufs.h index 29b91ada1dab..fda1b153975b 100644 --- a/include/ufs/ufs.h +++ b/include/ufs/ufs.h @@ -104,6 +104,12 @@ enum { UPIU_CMD_FLAGS_READ = 0x40, }; +/* UPIU Command Priority flags */ +enum { + UPIU_CMD_PRIO_NONE = 0x00, + UPIU_CMD_PRIO_HIGH = 0x04, +}; + /* UPIU Task Attributes */ enum { UPIU_TASK_ATTR_SIMPLE = 0x00, @@ -649,7 +655,8 @@ struct ufs_dev_info { enum ufs_trace_str_t { UFS_CMD_SEND, UFS_CMD_COMP, UFS_DEV_COMP, UFS_QUERY_SEND, UFS_QUERY_COMP, UFS_QUERY_ERR, - UFS_TM_SEND, UFS_TM_COMP, UFS_TM_ERR + UFS_TM_SEND, UFS_TM_COMP, UFS_TM_ERR, + UFS_CMD_ERR }; /* diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index d2032ef7c865..1a6cbacfff31 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -1273,7 +1273,7 @@ static size_t print_time(u64 ts, char *buf) { unsigned long rem_nsec = do_div(ts, 1000000000); - return sprintf(buf, "[%5lu.%06lu]", + return sprintf(buf, "AOSP [%5lu.%06lu]", (unsigned long)ts, rem_nsec / 1000); } diff --git a/kernel/sched/android.h b/kernel/sched/android.h new file mode 100644 index 000000000000..5fe6bb772368 --- /dev/null +++ b/kernel/sched/android.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Android scheduler hooks and modifications + * + * Put all of the android-specific scheduler hooks and changes + * in this .h file to make merges and modifications easier. It's also + * simpler to notice what is, and is not, an upstream change this way over time. + */ + + +/* + * task_may_not_preempt - check whether a task may not be preemptible soon + */ +static inline bool task_may_not_preempt(struct task_struct *task, int cpu) +{ + return false; +} + +static inline bool uclamp_boosted(struct task_struct *p) +{ + return false; +} + +static inline bool uclamp_latency_sensitive(struct task_struct *p) +{ + return false; +} diff --git a/kernel/sched/walt/Kconfig b/kernel/sched/walt/Kconfig index 4157dffa013c..49a236ca943e 100644 --- a/kernel/sched/walt/Kconfig +++ b/kernel/sched/walt/Kconfig @@ -38,4 +38,23 @@ config SCHED_CONSERVATIVE_BOOST_LPM_BIAS modes on a cpu if conservative boost is active. The cpu will not enter low power mode for a hysteresis time period, which can be configured from userspace. + +config RQ_STAT_SHOW + bool "Enable rq_stat node to check each Cluster's number of task" + default n + help + This feature creats "/proc/rq_stat" node. + It's read only node. + shows number of task in each Cluster's rq. + first is little/titanium cluster and last one is gold/gold+ cluster + +config SCHED_POWER_OPTIMIZE + bool "Enable Power optimize tunning feature" + default n + help + This feature make "up_delay_freq" sysfs node in Walt Governor. + Also, change prediction target load from 80 to 90 + when conservative prediction load enable condition. + if we enable and apply some value, + we can make overall system power better. endmenu diff --git a/kernel/sched/walt/Makefile b/kernel/sched/walt/Makefile index 6595bb7ae0a1..6aa4ad5a1a02 100644 --- a/kernel/sched/walt/Makefile +++ b/kernel/sched/walt/Makefile @@ -4,7 +4,7 @@ KCOV_INSTRUMENT := n KCSAN_SANITIZE := n obj-$(CONFIG_SCHED_WALT) += sched-walt.o -sched-walt-$(CONFIG_SCHED_WALT) := walt.o boost.o sched_avg.o walt_halt.o core_ctl.o trace.o input-boost.o sysctl.o cpufreq_walt.o fixup.o walt_lb.o walt_rt.o walt_cfs.o walt_tp.o mvp_locking.o +sched-walt-$(CONFIG_SCHED_WALT) := walt.o boost.o sched_avg.o walt_halt.o core_ctl.o trace.o input-boost.o sysctl.o cpufreq_walt.o fixup.o walt_lb.o walt_rt.o walt_cfs.o walt_tp.o hyst_qos.o mvp_locking.o obj-$(CONFIG_SCHED_WALT_DEBUG) += sched-walt-debug.o sched-walt-debug-$(CONFIG_SCHED_WALT_DEBUG) := walt_debug.o preemptirq_long.o diff --git a/kernel/sched/walt/cpufreq_walt.c b/kernel/sched/walt/cpufreq_walt.c index 7e180e327092..5ea50d6ef211 100644 --- a/kernel/sched/walt/cpufreq_walt.c +++ b/kernel/sched/walt/cpufreq_walt.c @@ -5,7 +5,7 @@ * * Copyright (C) 2016, Intel Corporation * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. - * Copyright (c) 2022-2024, Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2022-2023, Qualcomm Innovation Center, Inc. All rights reserved. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -29,6 +29,9 @@ struct waltgov_tunables { unsigned int adaptive_high_freq_kernel; unsigned int target_load_thresh; unsigned int target_load_shift; +#if IS_ENABLED(CONFIG_SCHED_POWER_OPTIMIZE) + unsigned int up_delay_freq; +#endif bool pl; int boost; }; @@ -64,6 +67,9 @@ struct waltgov_policy { bool limits_changed; bool need_freq_update; bool thermal_isolated; +#if IS_ENABLED(CONFIG_SCHED_POWER_OPTIMIZE) + bool force_up_delay; +#endif }; struct waltgov_cpu { @@ -112,6 +118,9 @@ static bool waltgov_up_down_rate_limit(struct waltgov_policy *wg_policy, u64 tim delta_ns = time - wg_policy->last_freq_update_time; if (next_freq > wg_policy->next_freq && +#if IS_ENABLED(CONFIG_SCHED_POWER_OPTIMIZE) + next_freq > wg_policy->tunables->up_delay_freq && +#endif delta_ns < wg_policy->up_rate_delay_ns) return true; @@ -128,6 +137,10 @@ static void __waltgov_update_next_freq(struct waltgov_policy *wg_policy, wg_policy->cached_raw_freq = raw_freq; wg_policy->next_freq = next_freq; wg_policy->last_freq_update_time = time; +#if IS_ENABLED(CONFIG_SCHED_POWER_OPTIMIZE) + wg_policy->force_up_delay + = (next_freq < wg_policy->tunables->up_delay_freq)? true : false; +#endif } static bool waltgov_update_next_freq(struct waltgov_policy *wg_policy, u64 time, @@ -229,6 +242,9 @@ static void waltgov_deferred_update(struct waltgov_policy *wg_policy, u64 time, walt_irq_work_queue(&wg_policy->irq_work); } +#if IS_ENABLED(CONFIG_SCHED_POWER_OPTIMIZE) +#define TARGET_LOAD_PL 90 +#endif #define TARGET_LOAD 80 static inline unsigned long walt_map_util_freq(unsigned long util, struct waltgov_policy *wg_policy, @@ -299,7 +315,10 @@ static unsigned int get_next_freq(struct waltgov_policy *wg_policy, is_state1()) skip = true; - if (wg_policy->tunables->adaptive_high_freq && !skip) { + /* ss power: add kernel condition */ + if ((wg_policy->tunables->adaptive_high_freq || + wg_policy->tunables->adaptive_high_freq_kernel) && + !skip) { if (raw_freq < get_adaptive_low_freq(wg_policy)) { freq = get_adaptive_low_freq(wg_policy); wg_driv_cpu->reasons = CPUFREQ_REASON_ADAPTIVE_LOW; @@ -309,6 +328,11 @@ static unsigned int get_next_freq(struct waltgov_policy *wg_policy, } } +#if IS_ENABLED(CONFIG_SCHED_POWER_OPTIMIZE) + if (wg_policy->force_up_delay) + freq = min(wg_policy->tunables->up_delay_freq, freq); +#endif + if (freq > fmax_cap[SMART_FMAX_CAP][cluster->id]) { freq = fmax_cap[SMART_FMAX_CAP][cluster->id]; wg_driv_cpu->reasons |= CPUFREQ_REASON_SMART_FMAX_CAP; @@ -410,7 +434,11 @@ static void waltgov_walt_adjust(struct waltgov_cpu *wg_cpu, unsigned long cpu_ut if (wg_policy->tunables->pl) { if (sysctl_sched_conservative_pl) +#if IS_ENABLED(CONFIG_SCHED_POWER_OPTIMIZE) + pl = mult_frac(pl, TARGET_LOAD_PL, 100); +#else pl = mult_frac(pl, TARGET_LOAD, 100); +#endif max_and_reason(util, pl, wg_cpu, CPUFREQ_REASON_PL); } @@ -756,6 +784,43 @@ static ssize_t boost_store(struct gov_attr_set *attr_set, const char *buf, return count; } +#if IS_ENABLED(CONFIG_SCHED_POWER_OPTIMIZE) +static ssize_t up_delay_freq_show(struct gov_attr_set *attr_set, char *buf) +{ + struct waltgov_tunables *tunables = to_waltgov_tunables(attr_set); + + return scnprintf(buf, PAGE_SIZE, "%u\n", tunables->up_delay_freq); +} + +static ssize_t up_delay_freq_store(struct gov_attr_set *attr_set, + const char *buf, size_t count) +{ + struct waltgov_tunables *tunables = to_waltgov_tunables(attr_set); + unsigned int val; + struct waltgov_policy *wg_policy; + unsigned long flags; + + if (kstrtouint(buf, 10, &val)) + return -EINVAL; + + if (val < 0) + val = 0; + + list_for_each_entry(wg_policy, &attr_set->policy_list, tunables_hook) { + raw_spin_lock_irqsave(&wg_policy->update_lock, flags); + + if (val > wg_policy->policy->cpuinfo.max_freq) + val = wg_policy->policy->cpuinfo.max_freq; + + raw_spin_unlock_irqrestore(&wg_policy->update_lock, flags); + } + + tunables->up_delay_freq = val; + + return count; +} +#endif + /** * cpufreq_walt_set_adaptive_freq() - set the waltgov adaptive freq for cpu * @cpu: the cpu for which the values should be set @@ -782,9 +847,20 @@ int cpufreq_walt_set_adaptive_freq(unsigned int cpu, unsigned int adaptive_low_f if (!cpu_possible(cpu)) return -EFAULT; + /* ss power: check NULL condition */ wg_policy = wg_cpu->wg_policy; + if (!wg_policy) { + pr_err("%s: wg_policy NULL !!\n", __func__); + return -EFAULT; + } policy = wg_policy->policy; - if (policy->min <= adaptive_low_freq && policy->max >= adaptive_high_freq) { + if (!policy) { + pr_err("%s: policy NULL !!\n", __func__); + return -EFAULT; + } + + if (policy->cpuinfo.min_freq <= adaptive_low_freq && + policy->cpuinfo.max_freq >= adaptive_high_freq) { wg_policy->tunables->adaptive_low_freq_kernel = adaptive_low_freq; wg_policy->tunables->adaptive_high_freq_kernel = adaptive_high_freq; return 0; @@ -816,7 +892,13 @@ int cpufreq_walt_get_adaptive_freq(unsigned int cpu, unsigned int *adaptive_low_ if (!cpu_possible(cpu)) return -EFAULT; + /* ss power: check NULL condition */ wg_policy = wg_cpu->wg_policy; + if (!wg_policy) { + pr_err("%s: wg_policy NULL !!\n", __func__); + return -EFAULT; + } + if (adaptive_low_freq && adaptive_high_freq) { *adaptive_low_freq = get_adaptive_low_freq(wg_policy); *adaptive_high_freq = get_adaptive_high_freq(wg_policy); @@ -846,7 +928,13 @@ int cpufreq_walt_reset_adaptive_freq(unsigned int cpu) if (!cpu_possible(cpu)) return -EFAULT; + /* ss power: check NULL condition */ wg_policy = wg_cpu->wg_policy; + if (!wg_policy) { + pr_err("%s: wg_policy NULL !!\n", __func__); + return -EFAULT; + } + wg_policy->tunables->adaptive_low_freq_kernel = 0; wg_policy->tunables->adaptive_high_freq_kernel = 0; @@ -912,11 +1000,22 @@ static struct governor_attr hispeed_freq = __ATTR_RW(hispeed_freq); static struct governor_attr rtg_boost_freq = __ATTR_RW(rtg_boost_freq); static struct governor_attr pl = __ATTR_RW(pl); static struct governor_attr boost = __ATTR_RW(boost); +#if IS_ENABLED(CONFIG_SCHED_POWER_OPTIMIZE) +static struct governor_attr up_delay_freq = __ATTR_RW(up_delay_freq); +#endif WALTGOV_ATTR_RW(adaptive_low_freq); WALTGOV_ATTR_RW(adaptive_high_freq); WALTGOV_ATTR_RW(target_load_thresh); WALTGOV_ATTR_RW(target_load_shift); +/* ss power: add kernel freq node */ +show_attr(adaptive_low_freq_kernel); +store_attr(adaptive_low_freq_kernel); +show_attr(adaptive_high_freq_kernel); +store_attr(adaptive_high_freq_kernel); +WALTGOV_ATTR_RW(adaptive_low_freq_kernel); +WALTGOV_ATTR_RW(adaptive_high_freq_kernel); + static struct attribute *waltgov_attrs[] = { &up_rate_limit_us.attr, &down_rate_limit_us.attr, @@ -925,10 +1024,15 @@ static struct attribute *waltgov_attrs[] = { &rtg_boost_freq.attr, &pl.attr, &boost.attr, +#if IS_ENABLED(CONFIG_SCHED_POWER_OPTIMIZE) + &up_delay_freq.attr, +#endif &adaptive_low_freq.attr, &adaptive_high_freq.attr, &target_load_thresh.attr, &target_load_shift.attr, + &adaptive_low_freq_kernel.attr, + &adaptive_high_freq_kernel.attr, NULL }; ATTRIBUTE_GROUPS(waltgov); @@ -1037,6 +1141,9 @@ static void waltgov_tunables_save(struct cpufreq_policy *policy, cached->adaptive_high_freq_kernel = tunables->adaptive_high_freq_kernel; cached->target_load_thresh = tunables->target_load_thresh; cached->target_load_shift = tunables->target_load_shift; +#if IS_ENABLED(CONFIG_SCHED_POWER_OPTIMIZE) + cached->up_delay_freq = tunables->up_delay_freq; +#endif } static void waltgov_tunables_restore(struct cpufreq_policy *policy) @@ -1061,6 +1168,9 @@ static void waltgov_tunables_restore(struct cpufreq_policy *policy) tunables->adaptive_high_freq_kernel = cached->adaptive_high_freq_kernel; tunables->target_load_thresh = cached->target_load_thresh; tunables->target_load_shift = cached->target_load_shift; +#if IS_ENABLED(CONFIG_SCHED_POWER_OPTIMIZE) + tunables->up_delay_freq = cached->up_delay_freq; +#endif } bool waltgov_disabled = true; @@ -1107,6 +1217,10 @@ static int waltgov_init(struct cpufreq_policy *policy) else tunables->rtg_boost_freq = DEFAULT_GOLD_RTG_BOOST_FREQ; +#if IS_ENABLED(CONFIG_SCHED_POWER_OPTIMIZE) + tunables->up_delay_freq = policy->cpuinfo.max_freq; +#endif + policy->governor_data = wg_policy; wg_policy->tunables = tunables; waltgov_tunables_restore(policy); @@ -1167,6 +1281,9 @@ static int waltgov_start(struct cpufreq_policy *policy) wg_policy->limits_changed = false; wg_policy->need_freq_update = false; wg_policy->cached_raw_freq = 0; +#if IS_ENABLED(CONFIG_SCHED_POWER_OPTIMIZE) + wg_policy->force_up_delay = true; +#endif for_each_cpu(cpu, policy->cpus) { struct waltgov_cpu *wg_cpu = &per_cpu(waltgov_cpu, cpu); @@ -1228,8 +1345,9 @@ static void waltgov_limits(struct cpufreq_policy *policy) * to do any sort of additional validation here. */ final_freq = cpufreq_driver_resolve_freq(policy, freq); - if (wg_policy->next_freq != final_freq) { - __waltgov_update_next_freq(wg_policy, now, final_freq, final_freq); + + if (waltgov_update_next_freq(wg_policy, now, final_freq, + final_freq)) { waltgov_fast_switch(wg_policy, now, final_freq); } } diff --git a/kernel/sched/walt/hyst_qos.c b/kernel/sched/walt/hyst_qos.c new file mode 100644 index 000000000000..eb4ee16e48cc --- /dev/null +++ b/kernel/sched/walt/hyst_qos.c @@ -0,0 +1,124 @@ +#include +#include +#include +#include +#include +#include "hyst_qos.h" + +unsigned int busy_hyst_qos_value; +int initialized; +unsigned int cur_max_val = U32_MAX, cur_min_val; +spinlock_t hyst_qos_lock; +struct mutex qos_lock; +struct list_head hyst_req_list; + +void hyst_add_request(struct user_req *req, int res_type, char *name) +{ + unsigned long flags; + struct user_req *iter_req; + + if (!initialized) + hyst_init(); + + spin_lock_irqsave(&hyst_qos_lock, flags); + list_for_each_entry_rcu(iter_req, &hyst_req_list, list) { + if (list_empty(&hyst_req_list)) + break; + + if (!iter_req) + continue; + + if (iter_req == req) { + pr_err(HQTAG " Try to add duplicated Req\n"); + spin_unlock_irqrestore(&hyst_qos_lock, flags); + return; + } + } + list_add_tail_rcu(&(req->list), &hyst_req_list); + spin_unlock_irqrestore(&hyst_qos_lock, flags); + + req->name = name; + req->res_type = res_type; +} +EXPORT_SYMBOL_GPL(hyst_add_request); + +void hyst_update_request(struct user_req *req, int type, unsigned int value) +{ + unsigned long flags; + unsigned int qos_value = 0, max_of_min_reqval = 0, min_of_max_reqval = U32_MAX; + struct user_req *iter_req; + + if (!initialized) + hyst_init(); + + mutex_lock(&qos_lock); + + req->values[type] = value; + + /* Update Min Limit */ + if (type == PM_QOS_MIN_LIMIT) { + spin_lock_irqsave(&hyst_qos_lock, flags); + list_for_each_entry_rcu(iter_req, &hyst_req_list, list) { + if (list_empty(&hyst_req_list)) + break; + + if (!iter_req || req->res_type != iter_req->res_type) + continue; + + if (iter_req->values[PM_QOS_MIN_LIMIT] > max_of_min_reqval) + max_of_min_reqval = iter_req->values[PM_QOS_MIN_LIMIT]; + } + qos_value = (cur_max_val < max_of_min_reqval) ? cur_max_val : max_of_min_reqval; + + cur_min_val = max_of_min_reqval; + spin_unlock_irqrestore(&hyst_qos_lock, flags); + + pr_info(HQTAG" %s ::: Rqst Val(%u), Type(%d), Qos Value(%u)\n", req->name, value, req->res_type, qos_value); + } else if (type == PM_QOS_MAX_LIMIT) { + /* Update Max Limit */ + spin_lock_irqsave(&hyst_qos_lock, flags); + list_for_each_entry_rcu(iter_req, &hyst_req_list, list) { + if (list_empty(&hyst_req_list)) + break; + + if (!iter_req || req->res_type != iter_req->res_type) + continue; + + if (iter_req->values[PM_QOS_MAX_LIMIT] < min_of_max_reqval) + min_of_max_reqval = iter_req->values[PM_QOS_MAX_LIMIT]; + } + qos_value = (cur_min_val > min_of_max_reqval) ? min_of_max_reqval : cur_min_val; + + cur_max_val = min_of_max_reqval; + spin_unlock_irqrestore(&hyst_qos_lock, flags); + } + mutex_unlock(&qos_lock); + + busy_hyst_qos_value = qos_value; + + sched_update_hyst_times(); + +} +EXPORT_SYMBOL_GPL(hyst_update_request); + +void hyst_remove_request(struct user_req *req) +{ + + unsigned long flags; + + hyst_update_request(req, PM_QOS_MIN_LIMIT, MIN_DEFAULT_VALUE); + hyst_update_request(req, PM_QOS_MAX_LIMIT, MAX_DEFAULT_VALUE); + + spin_lock_irqsave(&hyst_qos_lock, flags); + list_del_rcu(&(req->list)); + spin_unlock_irqrestore(&hyst_qos_lock, flags); +} +EXPORT_SYMBOL_GPL(hyst_remove_request); + +void hyst_init(void) +{ + INIT_LIST_HEAD(&hyst_req_list); + spin_lock_init(&hyst_qos_lock); + mutex_init(&qos_lock); + initialized = true; +} diff --git a/kernel/sched/walt/hyst_qos.h b/kernel/sched/walt/hyst_qos.h new file mode 100644 index 000000000000..85fb78f31403 --- /dev/null +++ b/kernel/sched/walt/hyst_qos.h @@ -0,0 +1,34 @@ + +#ifndef _QOS_H +#define _QOS_H + +#include "walt.h" + +#define MIN_DEFAULT_VALUE 0 +#define MAX_DEFAULT_VALUE 0 + +#define HQTAG " [Hyst QoS] " + +enum qos_ctrl_type { + PM_QOS_MIN_LIMIT = 0, + PM_QOS_MAX_LIMIT, + TYPE_END, +}; + +struct user_req { + char *name; + int values[TYPE_END]; + int res_type; + u64 residency_time; + struct list_head list; +}; + +extern unsigned int busy_hyst_qos_value; + +void hyst_add_request(struct user_req *req, int value, char *name); +void hyst_update_request(struct user_req *req, int type, unsigned int value); +void hyst_request_enable(struct user_req *req, bool enabled); +void hyst_remove_request(struct user_req *req); +void hyst_init(void); + +#endif /* _QOS_H */ diff --git a/kernel/sched/walt/sched_avg.c b/kernel/sched/walt/sched_avg.c index a5b7c9efb39b..3e0e3075cc06 100644 --- a/kernel/sched/walt/sched_avg.c +++ b/kernel/sched/walt/sched_avg.c @@ -14,6 +14,10 @@ #include "walt.h" #include "trace.h" +#include "hyst_qos.h" + +struct user_req us_req; +bool qos_reg; static DEFINE_PER_CPU(u64, nr_prod_sum); static DEFINE_PER_CPU(u64, last_time); @@ -176,7 +180,7 @@ void sched_update_hyst_times(void) coloc_busy_pct = sysctl_sched_coloc_busy_hyst_cpu_busy_pct[cpu]; per_cpu(hyst_time, cpu) = (BIT(cpu) & sysctl_sched_busy_hyst_enable_cpus) ? - sysctl_sched_busy_hyst : 0; + busy_hyst_qos_value : 0; per_cpu(coloc_hyst_time, cpu) = ((BIT(cpu) & sysctl_sched_coloc_busy_hyst_enable_cpus) && rtgb_active) ? @@ -257,8 +261,15 @@ int sched_busy_hyst_handler(struct ctl_table *table, int write, ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos); - if (!ret && write) + if (!ret && write) { + if (!qos_reg) { + hyst_add_request(&us_req, 0, "PerfLock"); + qos_reg = true; + } + + hyst_update_request(&us_req, PM_QOS_MIN_LIMIT, sysctl_sched_busy_hyst); sched_update_hyst_times(); + } return ret; } diff --git a/kernel/sched/walt/walt.c b/kernel/sched/walt/walt.c index 8440a5caa48d..a187833bdeda 100644 --- a/kernel/sched/walt/walt.c +++ b/kernel/sched/walt/walt.c @@ -5488,6 +5488,47 @@ static void walt_init_tg_pointers(void) rcu_read_unlock(); } +#if IS_ENABLED(CONFIG_RQ_STAT_SHOW) +static int rq_stat_show(struct seq_file *m, void *data) +{ + int cpu; + char buf[64]; + int len = 0; + int g_gp_sum = 0; + int s_t_sum = 0; + //8650 specific cluster id + int gold_id = 1; + int prime_id = 3; + + for_each_possible_cpu(cpu) { + struct rq *rq = cpu_rq(cpu); + struct walt_rq *wrq = &per_cpu(walt_rq, cpu); + if (wrq->cluster->id == gold_id || wrq->cluster->id == prime_id) + g_gp_sum += rq->nr_running; + else + s_t_sum += rq->nr_running; + } + len += snprintf(buf + len, 64 - len, "%u ", s_t_sum); + len += snprintf(buf + len, 64 - len, "%u ", g_gp_sum); + seq_printf(m, "%s\n", buf); + + return 0; +} + +static int rq_stat_open(struct inode *inode, struct file *file) +{ + return single_open(file, rq_stat_show, NULL); +} + +static const struct proc_ops proc_rq_stat_op = { + .proc_open = rq_stat_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = single_release, +}; +#endif + + static void walt_init(struct work_struct *work) { struct ctl_table_header *hdr; @@ -5550,6 +5591,11 @@ static void walt_init(struct work_struct *work) walt_boost_init(); waltgov_register(); +#if IS_ENABLED(CONFIG_RQ_STAT_SHOW) + if (!proc_create("rq_stat", 0444, NULL, &proc_rq_stat_op)) + pr_err("Failed to register proc interface 'rq_stat'\n"); +#endif + i = match_string(sched_feat_names, __SCHED_FEAT_NR, "TTWU_QUEUE"); if (i >= 0) { static_key_disable(&sched_feat_keys[i]); @@ -5565,6 +5611,24 @@ static void android_vh_update_topology_flags_workfn(void *unused, void *unused2) schedule_work(&walt_init_work); } +static void walt_devicetree_init(void) +{ + struct device_node *np; + int ret; + + np = of_find_node_by_name(NULL, "sched_walt"); + if (!np) { + pr_err("Failed to find node of sched_walt\n"); + return; + } + + ret = of_property_read_u32(np, "panic_on_walt_bug", &sysctl_panic_on_walt_bug); + if (ret < 0) { + pr_err("Failed to read panic_on_walt_bug property\n"); + return; + } +} + #define WALT_VENDOR_DATA_SIZE_TEST(wstruct, kstruct) \ BUILD_BUG_ON(sizeof(wstruct) > (sizeof(u64) * \ ARRAY_SIZE(((kstruct *)0)->android_vendor_data1))) @@ -5575,6 +5639,8 @@ static int walt_module_init(void) WALT_VENDOR_DATA_SIZE_TEST(struct walt_task_struct, struct task_struct); WALT_VENDOR_DATA_SIZE_TEST(struct walt_task_group, struct task_group); + walt_devicetree_init(); + register_trace_android_vh_update_topology_flags_workfn( android_vh_update_topology_flags_workfn, NULL); @@ -5590,3 +5656,14 @@ MODULE_LICENSE("GPL v2"); #if IS_ENABLED(CONFIG_SCHED_WALT_DEBUG) MODULE_SOFTDEP("pre: sched-walt-debug"); #endif + +#if IS_ENABLED(CONFIG_SEC_QC_SUMMARY) +#include + +void sec_qc_summary_set_sched_walt_info(struct sec_qc_summary_data_apss *apss) +{ + apss->aplpm.num_clusters = num_sched_clusters; + apss->aplpm.p_cluster = virt_to_phys(sched_cluster); +} +EXPORT_SYMBOL(sec_qc_summary_set_sched_walt_info); +#endif diff --git a/kernel/time/sched_clock.c b/kernel/time/sched_clock.c index 4052d72ff013..e4d35e5274e0 100644 --- a/kernel/time/sched_clock.c +++ b/kernel/time/sched_clock.c @@ -81,6 +81,11 @@ notrace int sched_clock_read_retry(unsigned int seq) return read_seqcount_latch_retry(&cd.seq, seq); } +/* FIXME: This can make a cache contension problem. + * This valiable should be refactored as per_cpu variables. + */ +static atomic64_t sec_qc_summary_last_ns __used; + unsigned long long notrace sched_clock(void) { u64 cyc, res; @@ -95,6 +100,8 @@ unsigned long long notrace sched_clock(void) res = rd->epoch_ns + cyc_to_ns(cyc, rd->mult, rd->shift); } while (sched_clock_read_retry(seq)); + atomic64_set(&sec_qc_summary_last_ns, res); + return res; } diff --git a/kernel/trace/ipc_logging.c b/kernel/trace/ipc_logging.c index 537203abdbd6..d623abee2403 100644 --- a/kernel/trace/ipc_logging.c +++ b/kernel/trace/ipc_logging.c @@ -23,6 +23,8 @@ #include #include +#include + #include "ipc_logging_private.h" #define LOG_PAGE_DATA_SIZE sizeof(((struct ipc_log_page *)0)->data) @@ -37,6 +39,8 @@ static DEFINE_RWLOCK(context_list_lock_lha1); static void *get_deserialization_func(struct ipc_log_context *ilctxt, int type); +static void *debug_level_dummy_ctx; + static struct ipc_log_page *get_first_page(struct ipc_log_context *ilctxt) { struct ipc_log_page_header *p_pghdr; @@ -307,7 +311,7 @@ static void msg_drop(struct ipc_log_context *ilctxt) * Commits messages to the FIFO. If the FIFO is full, then enough * messages are dropped to create space for the new message. */ -void ipc_log_write(void *ctxt, struct encode_context *ectxt) +static void __ipc_log_write(void *ctxt, struct encode_context *ectxt) { struct ipc_log_context *ilctxt = (struct ipc_log_context *)ctxt; int bytes_to_write; @@ -356,6 +360,13 @@ void ipc_log_write(void *ctxt, struct encode_context *ectxt) spin_unlock(&ilctxt->context_lock_lhb1); read_unlock_irqrestore(&context_list_lock_lha1, flags); } + +void ipc_log_write(void *ctxt, struct encode_context *ectxt) +{ + if (!sec_debug_is_enabled()) + return; + __ipc_log_write(ctxt, ectxt); +} EXPORT_SYMBOL(ipc_log_write); /* @@ -531,6 +542,9 @@ int ipc_log_string(void *ilctxt, const char *fmt, ...) int avail_size, data_size, hdr_size = sizeof(struct tsv_header); va_list arg_list; + if (!sec_debug_is_enabled()) + return 0; + if (!ilctxt) return -EINVAL; @@ -822,7 +836,7 @@ static void *get_deserialization_func(struct ipc_log_context *ilctxt, * * returns context id on success, NULL on failure */ -void *ipc_log_context_create(int max_num_pages, +static void *__ipc_log_context_create(int max_num_pages, const char *mod_name, uint32_t feature_version) { struct ipc_log_context *ctxt = NULL, *tmp; @@ -921,6 +935,17 @@ void *ipc_log_context_create(int max_num_pages, kfree(ctxt); return 0; } + +void *ipc_log_context_create(int max_num_pages, + const char *mod_name, uint32_t feature_version) +{ + + if (sec_debug_is_enabled() || !debug_level_dummy_ctx) + return __ipc_log_context_create(max_num_pages, mod_name, feature_version); + + return debug_level_dummy_ctx; + +} EXPORT_SYMBOL(ipc_log_context_create); void ipc_log_context_free(struct kref *kref) @@ -937,13 +962,14 @@ void ipc_log_context_free(struct kref *kref) kfree(ilctxt); } +EXPORT_SYMBOL(ipc_log_context_free); /* * Destroy debug log context * * @ctxt: debug log context created by calling ipc_log_context_create API. */ -int ipc_log_context_destroy(void *ctxt) +static int __ipc_log_context_destroy(void *ctxt) { struct ipc_log_context *ilctxt = (struct ipc_log_context *)ctxt; struct dfunc_info *df_info = NULL, *tmp = NULL; @@ -971,8 +997,77 @@ int ipc_log_context_destroy(void *ctxt) return 0; } + +int ipc_log_context_destroy(void *ctxt) +{ + if (sec_debug_is_enabled() || (ctxt != (void *)debug_level_dummy_ctx)) + return __ipc_log_context_destroy(ctxt); + + return 0; +} EXPORT_SYMBOL(ipc_log_context_destroy); +#define LOG_CTX_PAGE_CNT 150 +static void *log_ctx; +#define MAX_LINE_SIZE 512 +static char *buf; + +void net_log(const char *fmt, ...) +{ + va_list arg_list; + + va_start(arg_list, fmt); + if (log_ctx && buf) { + vscnprintf(buf, MAX_LINE_SIZE, fmt, arg_list); + ipc_log_string(log_ctx, "%s", buf); + } + va_end(arg_list); +} +EXPORT_SYMBOL_GPL(net_log); + +static int __init net_ipc_log_init(void) +{ + + if (!log_ctx) + log_ctx = ipc_log_context_create(LOG_CTX_PAGE_CNT, + "net_log", 0); + if (!buf) + buf = kmalloc(MAX_LINE_SIZE, GFP_KERNEL); + + // error ???? + // + return 0; +} + +static void __exit net_ipc_log_exit(void) +{ + if (log_ctx) + ipc_log_context_destroy(log_ctx); + if (buf) + kfree(buf); +} + +static int __init __create_dummy_ipc_context(void) +{ + if (sec_debug_is_enabled()) + return 0; + + if (!debug_level_dummy_ctx) + debug_level_dummy_ctx = __ipc_log_context_create(1, "dummy_log", 0); + + return 0; +} + +static void __exit __destory_dummy_ipc_context(void) +{ + + if (sec_debug_is_enabled()) + return; + + if (debug_level_dummy_ctx) + __ipc_log_context_destroy(debug_level_dummy_ctx); +} + static int __init ipc_logging_init(void) { check_and_create_debugfs(); @@ -980,10 +1075,21 @@ static int __init ipc_logging_init(void) register_minidump((u64)&ipc_log_context_list, sizeof(struct list_head), "ipc_log_ctxt_list", minidump_buf_cnt); + __create_dummy_ipc_context(); + net_ipc_log_init(); + return 0; } +static void __exit ipc_logging_exit(void) +{ + net_ipc_log_exit(); + + __destory_dummy_ipc_context(); +} + module_init(ipc_logging_init); +module_exit(ipc_logging_exit); MODULE_DESCRIPTION("ipc logging"); MODULE_LICENSE("GPL"); diff --git a/net/Kconfig b/net/Kconfig index 5400a05b1fe6..988ebb607640 100644 --- a/net/Kconfig +++ b/net/Kconfig @@ -243,6 +243,9 @@ source "net/l3mdev/Kconfig" source "net/qrtr/Kconfig" source "net/qmsgq/Kconfig" source "net/ncsi/Kconfig" +# SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA { +source "net/ncm/Kconfig" +# SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA } config PCPU_DEV_REFCNT bool "Use percpu variables to maintain network device refcount" diff --git a/net/Makefile b/net/Makefile index 064e4dbecb05..4ebb01c76248 100644 --- a/net/Makefile +++ b/net/Makefile @@ -77,6 +77,9 @@ obj-$(CONFIG_NET_L3_MASTER_DEV) += l3mdev/ obj-$(CONFIG_QRTR) += qrtr/ obj-$(CONFIG_QMSGQ) += qmsgq/ obj-$(CONFIG_NET_NCSI) += ncsi/ +# SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA { +obj-$(CONFIG_KNOX_NCM) += ncm/ +# SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA } obj-$(CONFIG_XDP_SOCKETS) += xdp/ obj-$(CONFIG_MPTCP) += mptcp/ obj-$(CONFIG_MCTP) += mctp/ diff --git a/net/ncm/Kconfig b/net/ncm/Kconfig new file mode 100644 index 000000000000..1a61cc23296a --- /dev/null +++ b/net/ncm/Kconfig @@ -0,0 +1,9 @@ +# +# Network Context Module Configuration +# +# SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA { +config KNOX_NCM + tristate "Network Context Module Support" + depends on NET + default n +# SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA } diff --git a/net/ncm/Makefile b/net/ncm/Makefile new file mode 100644 index 000000000000..fc43ea15dcf5 --- /dev/null +++ b/net/ncm/Makefile @@ -0,0 +1,3 @@ +# SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA { +obj-$(CONFIG_KNOX_NCM) := ncm.o +# SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA } diff --git a/net/ncm/ncm.c b/net/ncm/ncm.c new file mode 100644 index 000000000000..3552fb00a06b --- /dev/null +++ b/net/ncm/ncm.c @@ -0,0 +1,1018 @@ +/* + * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * + * Network Context Metadata Module[NCM]:Implementation. + * + * 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 . + */ + +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA { +#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 SUCCESS 0 + +#define FAILURE 1 +/* fifo size in elements (bytes) */ +#define FIFO_SIZE 1024 +#define WAIT_TIMEOUT 10000 /*milliseconds */ +/* Lock to maintain orderly insertion of elements into kfifo */ +static DEFINE_MUTEX(ncm_lock); + +static unsigned int ncm_activated_flag = 1; + +static unsigned int ncm_deactivated_flag; // default = 0 + +static unsigned int intermediate_activated_flag = 1; + +static unsigned int intermediate_deactivated_flag; // default = 0 + +static unsigned int device_open_count; // default = 0 + +static int ncm_activated_type = NCM_FLOW_TYPE_DEFAULT; + +static struct nf_hook_ops nfho_ipv4_pr_conntrack; + +static struct nf_hook_ops nfho_ipv6_pr_conntrack; + +static struct nf_hook_ops nfho_ipv4_li_conntrack; + +static struct nf_hook_ops nfho_ipv6_li_conntrack; + +static struct workqueue_struct *eWq; // default = 0 + +wait_queue_head_t ncm_wq; + +static atomic_t isNCMEnabled = ATOMIC_INIT(0); + +static atomic_t isIntermediateFlowEnabled = ATOMIC_INIT(0); + +static unsigned int intermediate_flow_timeout; // default = 0 + +extern struct knox_socket_metadata knox_socket_metadata; + +DECLARE_KFIFO(knox_sock_info, struct knox_socket_metadata, FIFO_SIZE); + +/* The function is used to check if ncm feature has been enabled or not; The default value is disabled */ +unsigned int check_ncm_flag(void) { + return atomic_read(&isNCMEnabled); +} +EXPORT_SYMBOL(check_ncm_flag); + +/* This function is used to check if ncm feature has been enabled with intermediate flow feature */ +unsigned int check_intermediate_flag(void) { + return atomic_read(&isIntermediateFlowEnabled); +} +EXPORT_SYMBOL(check_intermediate_flag); + +/** The funcation is used to chedk if the kfifo is active or not; + * If the kfifo is active, then the socket metadata would be inserted into the queue which will be read by the user-space; + * By default the kfifo is inactive; + */ +bool kfifo_status(void) { + bool isKfifoActive = false; + if (kfifo_initialized(&knox_sock_info)) { + NCM_LOGD("The fifo queue for ncm was already intialized \n"); + isKfifoActive = true; + } else { + NCM_LOGE("The fifo queue for ncm is not intialized \n"); + isKfifoActive = false; + } + return isKfifoActive; +} +EXPORT_SYMBOL(kfifo_status); + +/** The function is used to insert the socket meta-data into the fifo queue; insertion of data will happen in a seperate kernel thread; + * The meta data information will be collected from the context of the process which originates it; + * If the kfifo is full, then the kfifo is freed before inserting new meta-data; + */ +void insert_data_kfifo(struct work_struct *pwork) { + struct knox_socket_metadata *knox_socket_metadata; + + knox_socket_metadata = container_of(pwork, struct knox_socket_metadata, work_kfifo); + if (IS_ERR(knox_socket_metadata)) { + NCM_LOGE("inserting data into the kfifo failed due to unknown error \n"); + goto err; + } + + if (mutex_lock_interruptible(&ncm_lock)) { + NCM_LOGE("inserting data into the kfifo failed due to an interuppt \n"); + goto err; + } + + if (kfifo_initialized(&knox_sock_info)) { + if (kfifo_is_full(&knox_sock_info)) { + NCM_LOGD("The kfifo is full and need to free it \n"); + kfree(knox_socket_metadata); + } else { + kfifo_in(&knox_sock_info, knox_socket_metadata, 1); + kfree(knox_socket_metadata); + } + } else { + kfree(knox_socket_metadata); + } + mutex_unlock(&ncm_lock); + return; + + err: + if (knox_socket_metadata != NULL) + kfree(knox_socket_metadata); + return; +} + +/** The function is used to insert the socket meta-data into the kfifo in a seperate kernel thread; + * The kernel threads which handles the responsibility of inserting the meta-data into the kfifo is manintained by the workqueue function; + */ +void insert_data_kfifo_kthread(struct knox_socket_metadata* knox_socket_metadata) { + if (knox_socket_metadata != NULL) + { + INIT_WORK(&(knox_socket_metadata->work_kfifo), insert_data_kfifo); + if (!eWq) { + NCM_LOGD("ewq ncmworkqueue not initialized. Data not collected\r\n"); + kfree(knox_socket_metadata); + } + if (eWq) { + queue_work(eWq, &(knox_socket_metadata->work_kfifo)); + } + } +} +EXPORT_SYMBOL(insert_data_kfifo_kthread); + + +/* The function is used to check if the caller is system server or not; */ +static int is_system_server(void) { + uid_t uid = current_uid().val; + switch (uid) { + case 1000: + return 1; + case 0: + return 1; + default: + break; + } + return 0; +} + +/* The function is used to intialize the kfifo */ +static void initialize_kfifo(void) { + INIT_KFIFO(knox_sock_info); + if (kfifo_initialized(&knox_sock_info)) { + NCM_LOGD("The kfifo for knox ncm has been initialized \n"); + init_waitqueue_head(&ncm_wq); + } +} + +/* The function is used to create work queue */ +static void initialize_ncmworkqueue(void) { + if (!eWq) { + NCM_LOGD("ewq..Single Thread created\r\n"); + eWq = create_workqueue("ncmworkqueue"); + } +} + +/* The function is ued to free the kfifo */ +static void free_kfifo(void) { + if (kfifo_status()) { + NCM_LOGD("The kfifo for knox ncm which was intialized is freed \n"); + kfifo_free(&knox_sock_info); + } +} + +/* The function is used to update the flag indicating whether the feature has been enabled or not */ +static void update_ncm_flag(unsigned int ncmFlag) { + if (ncmFlag == ncm_activated_flag) + atomic_set(&isNCMEnabled, ncm_activated_flag); + else + atomic_set(&isNCMEnabled, ncm_deactivated_flag); +} + +/* The function is used to update the flag indicating whether the intermediate flow feature has been enabled or not */ +static void update_intermediate_flag(unsigned int ncmIntermediateFlag) { + if (ncmIntermediateFlag == intermediate_activated_flag) + atomic_set(&isIntermediateFlowEnabled, intermediate_activated_flag); + else + atomic_set(&isIntermediateFlowEnabled, intermediate_deactivated_flag); +} + +/* The function is used to update the flag indicating start or stop flow */ +static void update_ncm_flow_type(int ncmFlowType) { + ncm_activated_type = ncmFlowType; +} + +/* This function is used to update the intermediate flow timeout value */ +static void update_intermediate_timeout(unsigned int timeout) { + intermediate_flow_timeout = timeout; +} + +/* This function is used to get the intermediate flow timeout value */ +unsigned int get_intermediate_timeout(void) { + return intermediate_flow_timeout; +} +EXPORT_SYMBOL(get_intermediate_timeout); + +/* IPv4 hook function to copy information from struct socket into struct nf_conn during first packet of the network flow */ +static unsigned int hook_func_ipv4_out_conntrack(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { + + struct iphdr *ip_header = NULL; + struct tcphdr *tcp_header = NULL; + struct udphdr *udp_header = NULL; + struct nf_conn *ct = NULL; + enum ip_conntrack_info ctinfo; + struct nf_conntrack_tuple *tuple = NULL; + char srcaddr[INET6_ADDRSTRLEN_NAP]; + char dstaddr[INET6_ADDRSTRLEN_NAP]; + + if ((skb) && (skb->sk) && (skb->sk->sk_protocol != IPPROTO_UDP) && (skb->sk->sk_protocol != IPPROTO_TCP) && (skb->sk->sk_protocol != IPPROTO_ICMP) && (skb->sk->sk_protocol != IPPROTO_SCTP) && (skb->sk->sk_protocol != IPPROTO_ICMPV6)) { + return NF_ACCEPT; + } + if ((current == NULL) || (current->cred == NULL)) { + return NF_ACCEPT; + } + if ((current->cred->uid.val == INIT_UID_NAP && current->tgid == INIT_UID_NAP) || (current->cred->uid.val == INIT_UID_NAP && current->tgid == INIT_PID_NAP)) { + return NF_ACCEPT; + } + + if ( (skb) && (skb->sk) && (SOCK_NPA_VENDOR_DATA_GET(skb->sk) != NULL) ) { + if ( (SOCK_NPA_VENDOR_DATA_GET(skb->sk)->knox_pid == INIT_PID_NAP) && (SOCK_NPA_VENDOR_DATA_GET(skb->sk)->knox_uid == INIT_UID_NAP) && (skb->sk->sk_protocol == IPPROTO_TCP) ) { + return NF_ACCEPT; + } + if ( (skb->sk->sk_protocol == IPPROTO_UDP) || (skb->sk->sk_protocol == IPPROTO_TCP) || (skb->sk->sk_protocol == IPPROTO_ICMP) || (skb->sk->sk_protocol == IPPROTO_SCTP) || (skb->sk->sk_protocol == IPPROTO_ICMPV6) ) { + ct = nf_ct_get(skb, &ctinfo); + if ( (ct) && (NF_CONN_NPA_VENDOR_DATA_GET(ct) != NULL) && (!atomic_read(&NF_CONN_NPA_VENDOR_DATA_GET(ct)->startFlow)) && (!nf_ct_is_dying(ct)) ) { + tuple = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple; + if (tuple) { + sprintf(srcaddr,"%pI4",(void *)&tuple->src.u3.ip); + sprintf(dstaddr,"%pI4",(void *)&tuple->dst.u3.ip); + if ( isIpv4AddressEqualsNull(srcaddr, dstaddr) ) { + return NF_ACCEPT; + } + } else { + return NF_ACCEPT; + } + atomic_set(&NF_CONN_NPA_VENDOR_DATA_GET(ct)->startFlow, 1); + if ( check_intermediate_flag() ) { + NF_CONN_NPA_VENDOR_DATA_GET(ct)->npa_timeout = ((u32)(jiffies)) + (get_intermediate_timeout() * HZ); + atomic_set(&NF_CONN_NPA_VENDOR_DATA_GET(ct)->intermediateFlow, 1); + } + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_uid = SOCK_NPA_VENDOR_DATA_GET(skb->sk)->knox_uid; + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_pid = SOCK_NPA_VENDOR_DATA_GET(skb->sk)->knox_pid; + memcpy(NF_CONN_NPA_VENDOR_DATA_GET(ct)->process_name,SOCK_NPA_VENDOR_DATA_GET(skb->sk)->process_name,sizeof(NF_CONN_NPA_VENDOR_DATA_GET(ct)->process_name)-1); + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_puid = SOCK_NPA_VENDOR_DATA_GET(skb->sk)->knox_puid; + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_ppid = SOCK_NPA_VENDOR_DATA_GET(skb->sk)->knox_ppid; + memcpy(NF_CONN_NPA_VENDOR_DATA_GET(ct)->parent_process_name,SOCK_NPA_VENDOR_DATA_GET(skb->sk)->parent_process_name,sizeof(NF_CONN_NPA_VENDOR_DATA_GET(ct)->parent_process_name)-1); + memcpy(NF_CONN_NPA_VENDOR_DATA_GET(ct)->domain_name,SOCK_NPA_VENDOR_DATA_GET(skb->sk)->domain_name,sizeof(NF_CONN_NPA_VENDOR_DATA_GET(ct)->domain_name)-1); + if ( (skb->dev) ) { + memcpy(NF_CONN_NPA_VENDOR_DATA_GET(ct)->interface_name,skb->dev->name,sizeof(NF_CONN_NPA_VENDOR_DATA_GET(ct)->interface_name)-1); + } else { + sprintf(NF_CONN_NPA_VENDOR_DATA_GET(ct)->interface_name,"%s","null"); + } + ip_header = (struct iphdr *)skb_network_header(skb); + if ( (ip_header) && (ip_header->protocol == IPPROTO_UDP) ) { + udp_header = (struct udphdr *)skb_transport_header(skb); + if (udp_header) { + int udp_payload_size = (ntohs(udp_header->len)) - sizeof(struct udphdr); + if ( (NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent + udp_payload_size) > ULLONG_MAX ) + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent = ULLONG_MAX; + else + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent = NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent + udp_payload_size; + if ( (ntohs(udp_header->dest) == DNS_PORT_NAP) && (NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_uid == INIT_UID_NAP) && (SOCK_NPA_VENDOR_DATA_GET(skb->sk)->knox_dns_uid > INIT_UID_NAP) ) { + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_puid = SOCK_NPA_VENDOR_DATA_GET(skb->sk)->knox_dns_uid; + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_ppid = SOCK_NPA_VENDOR_DATA_GET(skb->sk)->knox_dns_pid; + memcpy(NF_CONN_NPA_VENDOR_DATA_GET(ct)->parent_process_name,SOCK_NPA_VENDOR_DATA_GET(skb->sk)->dns_process_name,sizeof(NF_CONN_NPA_VENDOR_DATA_GET(ct)->parent_process_name)-1); + } + } + } else if ( (ip_header) && (ip_header->protocol == IPPROTO_TCP) ) { + tcp_header = (struct tcphdr *)skb_transport_header(skb); + if (tcp_header) { + int tcp_payload_size = (ntohs(ip_header->tot_len)) - (ip_header->ihl * 4) - (tcp_header->doff * 4); + if ( (NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent + tcp_payload_size) > ULLONG_MAX ) + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent = ULLONG_MAX; + else + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent = NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent + tcp_payload_size; + if ( (ntohs(tcp_header->dest) == DNS_PORT_NAP) && (NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_uid == INIT_UID_NAP) && (SOCK_NPA_VENDOR_DATA_GET(skb->sk)->knox_dns_uid > INIT_UID_NAP) ) { + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_puid = SOCK_NPA_VENDOR_DATA_GET(skb->sk)->knox_dns_uid; + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_ppid = SOCK_NPA_VENDOR_DATA_GET(skb->sk)->knox_dns_pid; + memcpy(NF_CONN_NPA_VENDOR_DATA_GET(ct)->parent_process_name,SOCK_NPA_VENDOR_DATA_GET(skb->sk)->dns_process_name,sizeof(NF_CONN_NPA_VENDOR_DATA_GET(ct)->parent_process_name)-1); + } + } + } else { + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent = 0; + } + knox_collect_conntrack_data(ct, NCM_FLOW_TYPE_OPEN, 1); + } else if ( (ct) && (NF_CONN_NPA_VENDOR_DATA_GET(ct) != NULL) && (!nf_ct_is_dying(ct)) ) { + ip_header = (struct iphdr *)skb_network_header(skb); + if ( (ip_header) && (ip_header->protocol == IPPROTO_UDP) ) { + udp_header = (struct udphdr *)skb_transport_header(skb); + if (udp_header) { + int udp_payload_size = (ntohs(udp_header->len)) - sizeof(struct udphdr); + if ( (NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent + udp_payload_size) > ULLONG_MAX ) + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent = ULLONG_MAX; + else + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent = NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent + udp_payload_size; + } + } else if ( (ip_header) && (ip_header->protocol == IPPROTO_TCP) ) { + tcp_header = (struct tcphdr *)skb_transport_header(skb); + if (tcp_header) { + int tcp_payload_size = (ntohs(ip_header->tot_len)) - (ip_header->ihl * 4) - (tcp_header->doff * 4); + if ( (NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent + tcp_payload_size) > ULLONG_MAX ) + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent = ULLONG_MAX; + else + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent = NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent + tcp_payload_size; + } + } else { + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent = 0; + } + } + } + } + + return NF_ACCEPT; +} + +/* IPv6 hook function to copy information from struct socket into struct nf_conn during first packet of the network flow */ +static unsigned int hook_func_ipv6_out_conntrack(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { + struct ipv6hdr *ipv6_header = NULL; + struct tcphdr *tcp_header = NULL; + struct udphdr *udp_header = NULL; + struct nf_conn *ct = NULL; + enum ip_conntrack_info ctinfo; + struct nf_conntrack_tuple *tuple = NULL; + char srcaddr[INET6_ADDRSTRLEN_NAP]; + char dstaddr[INET6_ADDRSTRLEN_NAP]; + + if ((skb) && (skb->sk) && (skb->sk->sk_protocol != IPPROTO_UDP) && (skb->sk->sk_protocol != IPPROTO_TCP) && (skb->sk->sk_protocol != IPPROTO_ICMP) && (skb->sk->sk_protocol != IPPROTO_SCTP) && (skb->sk->sk_protocol != IPPROTO_ICMPV6)) { + return NF_ACCEPT; + } + if ((current == NULL) || (current->cred == NULL)) { + return NF_ACCEPT; + } + if ((current->cred->uid.val == INIT_UID_NAP && current->tgid == INIT_UID_NAP) || (current->cred->uid.val == INIT_UID_NAP && current->tgid == INIT_PID_NAP)) { + return NF_ACCEPT; + } + + if ( (skb) && (skb->sk) && (SOCK_NPA_VENDOR_DATA_GET(skb->sk) != NULL) ) { + if ( (SOCK_NPA_VENDOR_DATA_GET(skb->sk)->knox_pid == INIT_PID_NAP) && (SOCK_NPA_VENDOR_DATA_GET(skb->sk)->knox_uid == INIT_UID_NAP) && (skb->sk->sk_protocol == IPPROTO_TCP) ) { + return NF_ACCEPT; + } + if ( (skb->sk->sk_protocol == IPPROTO_UDP) || (skb->sk->sk_protocol == IPPROTO_TCP) || (skb->sk->sk_protocol == IPPROTO_ICMP) || (skb->sk->sk_protocol == IPPROTO_SCTP) || (skb->sk->sk_protocol == IPPROTO_ICMPV6) ) { + ct = nf_ct_get(skb, &ctinfo); + if ( (ct) && (NF_CONN_NPA_VENDOR_DATA_GET(ct) != NULL) && (!atomic_read(&NF_CONN_NPA_VENDOR_DATA_GET(ct)->startFlow)) && (!nf_ct_is_dying(ct)) ) { + tuple = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple; + if (tuple) { + sprintf(srcaddr,"%pI6",(void *)&tuple->src.u3.ip6); + sprintf(dstaddr,"%pI6",(void *)&tuple->dst.u3.ip6); + if ( isIpv6AddressEqualsNull(srcaddr, dstaddr) ) { + return NF_ACCEPT; + } + } else { + return NF_ACCEPT; + } + atomic_set(&NF_CONN_NPA_VENDOR_DATA_GET(ct)->startFlow, 1); + if ( check_intermediate_flag() ) { + NF_CONN_NPA_VENDOR_DATA_GET(ct)->npa_timeout = ((u32)(jiffies)) + (get_intermediate_timeout() * HZ); + atomic_set(&NF_CONN_NPA_VENDOR_DATA_GET(ct)->intermediateFlow, 1); + } + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_uid = SOCK_NPA_VENDOR_DATA_GET(skb->sk)->knox_uid; + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_pid = SOCK_NPA_VENDOR_DATA_GET(skb->sk)->knox_pid; + memcpy(NF_CONN_NPA_VENDOR_DATA_GET(ct)->process_name,SOCK_NPA_VENDOR_DATA_GET(skb->sk)->process_name,sizeof(NF_CONN_NPA_VENDOR_DATA_GET(ct)->process_name)-1); + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_puid = SOCK_NPA_VENDOR_DATA_GET(skb->sk)->knox_puid; + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_ppid = SOCK_NPA_VENDOR_DATA_GET(skb->sk)->knox_ppid; + memcpy(NF_CONN_NPA_VENDOR_DATA_GET(ct)->parent_process_name,SOCK_NPA_VENDOR_DATA_GET(skb->sk)->parent_process_name,sizeof(NF_CONN_NPA_VENDOR_DATA_GET(ct)->parent_process_name)-1); + memcpy(NF_CONN_NPA_VENDOR_DATA_GET(ct)->domain_name,SOCK_NPA_VENDOR_DATA_GET(skb->sk)->domain_name,sizeof(NF_CONN_NPA_VENDOR_DATA_GET(ct)->domain_name)-1); + if ( (skb->dev) ) { + memcpy(NF_CONN_NPA_VENDOR_DATA_GET(ct)->interface_name,skb->dev->name,sizeof(NF_CONN_NPA_VENDOR_DATA_GET(ct)->interface_name)-1); + } else { + sprintf(NF_CONN_NPA_VENDOR_DATA_GET(ct)->interface_name,"%s","null"); + } + ipv6_header = (struct ipv6hdr *)skb_network_header(skb); + if ( (ipv6_header) && (ipv6_header->nexthdr == IPPROTO_UDP) ) { + udp_header = (struct udphdr *)skb_transport_header(skb); + if (udp_header) { + int udp_payload_size = (ntohs(udp_header->len)) - sizeof(struct udphdr); + if ( (NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent + udp_payload_size) > ULLONG_MAX ) + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent = ULLONG_MAX; + else + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent = NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent + udp_payload_size; + if ( (ntohs(udp_header->dest) == DNS_PORT_NAP) && (NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_uid == INIT_UID_NAP) && (SOCK_NPA_VENDOR_DATA_GET(skb->sk)->knox_dns_uid > INIT_UID_NAP) ) { + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_puid = SOCK_NPA_VENDOR_DATA_GET(skb->sk)->knox_dns_uid; + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_ppid = SOCK_NPA_VENDOR_DATA_GET(skb->sk)->knox_dns_pid; + memcpy(NF_CONN_NPA_VENDOR_DATA_GET(ct)->parent_process_name,SOCK_NPA_VENDOR_DATA_GET(skb->sk)->dns_process_name,sizeof(NF_CONN_NPA_VENDOR_DATA_GET(ct)->parent_process_name)-1); + } + } + } else if ( (ipv6_header) && (ipv6_header->nexthdr == IPPROTO_TCP) ) { + tcp_header = (struct tcphdr *)skb_transport_header(skb); + if (tcp_header) { + int tcp_payload_size = (ntohs(ipv6_header->payload_len)) - (tcp_header->doff * 4); + if ( (NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent + tcp_payload_size) > ULLONG_MAX ) + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent = ULLONG_MAX; + else + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent = NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent + tcp_payload_size; + if ( (ntohs(tcp_header->dest) == DNS_PORT_NAP) && (NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_uid == INIT_UID_NAP) && (SOCK_NPA_VENDOR_DATA_GET(skb->sk)->knox_dns_uid > INIT_UID_NAP) ) { + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_puid = SOCK_NPA_VENDOR_DATA_GET(skb->sk)->knox_dns_uid; + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_ppid = SOCK_NPA_VENDOR_DATA_GET(skb->sk)->knox_dns_pid; + memcpy(NF_CONN_NPA_VENDOR_DATA_GET(ct)->parent_process_name,SOCK_NPA_VENDOR_DATA_GET(skb->sk)->dns_process_name,sizeof(NF_CONN_NPA_VENDOR_DATA_GET(ct)->parent_process_name)-1); + } + } + } else { + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent = 0; + } + knox_collect_conntrack_data(ct, NCM_FLOW_TYPE_OPEN, 2); + } else if ( (ct) && (NF_CONN_NPA_VENDOR_DATA_GET(ct) != NULL) && (!nf_ct_is_dying(ct)) ) { + ipv6_header = (struct ipv6hdr *)skb_network_header(skb); + if ( (ipv6_header) && (ipv6_header->nexthdr == IPPROTO_UDP) ) { + udp_header = (struct udphdr *)skb_transport_header(skb); + if (udp_header) { + int udp_payload_size = (ntohs(udp_header->len)) - sizeof(struct udphdr); + if ( (NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent + udp_payload_size) > ULLONG_MAX ) + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent = ULLONG_MAX; + else + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent = NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent + udp_payload_size; + } + } else if ( (ipv6_header) && (ipv6_header->nexthdr == IPPROTO_TCP) ) { + tcp_header = (struct tcphdr *)skb_transport_header(skb); + if (tcp_header) { + int tcp_payload_size = (ntohs(ipv6_header->payload_len)) - (tcp_header->doff * 4); + if ( (NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent + tcp_payload_size) > ULLONG_MAX ) + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent = ULLONG_MAX; + else + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent = NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent + tcp_payload_size; + } + } else { + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent = 0; + } + } + } + } + + return NF_ACCEPT; +} + +static unsigned int hook_func_ipv4_in_conntrack(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { + struct iphdr *ip_header = NULL; + struct tcphdr *tcp_header = NULL; + struct udphdr *udp_header = NULL; + struct nf_conn *ct = NULL; + enum ip_conntrack_info ctinfo; + + if (skb){ + ip_header = (struct iphdr *)skb_network_header(skb); + if ( (ip_header) && (ip_header->protocol == IPPROTO_TCP || ip_header->protocol == IPPROTO_UDP || ip_header->protocol == IPPROTO_SCTP || ip_header->protocol == IPPROTO_ICMP || ip_header->protocol == IPPROTO_ICMPV6) ) { + ct = nf_ct_get(skb, &ctinfo); + if ( (ct) && (NF_CONN_NPA_VENDOR_DATA_GET(ct) != NULL) && (!nf_ct_is_dying(ct)) ) { + if (ip_header->protocol == IPPROTO_TCP) { + tcp_header = (struct tcphdr *)skb_transport_header(skb); + if (tcp_header) { + int tcp_payload_size = (ntohs(ip_header->tot_len)) - (ip_header->ihl * 4) - (tcp_header->doff * 4); + if ( (NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_recv + tcp_payload_size) > ULLONG_MAX ) + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_recv = ULLONG_MAX; + else + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_recv = NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_recv + tcp_payload_size; + } + } else if (ip_header->protocol == IPPROTO_UDP) { + udp_header = (struct udphdr *)skb_transport_header(skb); + if (udp_header) { + int udp_payload_size = (ntohs(udp_header->len)) - sizeof(struct udphdr); + if ( (NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_recv + udp_payload_size) > ULLONG_MAX ) + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_recv = ULLONG_MAX; + else + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_recv = NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_recv + udp_payload_size; + } + } else { + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_recv = 0; + } + } + } + } + + return NF_ACCEPT; +} + +static unsigned int hook_func_ipv6_in_conntrack(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { + struct ipv6hdr *ipv6_header = NULL; + struct tcphdr *tcp_header = NULL; + struct udphdr *udp_header = NULL; + struct nf_conn *ct = NULL; + enum ip_conntrack_info ctinfo; + + if (skb){ + ipv6_header = (struct ipv6hdr *)skb_network_header(skb); + if ( (ipv6_header) && (ipv6_header->nexthdr == IPPROTO_TCP || ipv6_header->nexthdr == IPPROTO_UDP || ipv6_header->nexthdr == IPPROTO_SCTP || ipv6_header->nexthdr == IPPROTO_ICMP || ipv6_header->nexthdr == IPPROTO_ICMPV6) ) { + ct = nf_ct_get(skb, &ctinfo); + if ( (ct) && (NF_CONN_NPA_VENDOR_DATA_GET(ct) != NULL) && (!nf_ct_is_dying(ct)) ) { + if (ipv6_header->nexthdr == IPPROTO_TCP) { + tcp_header = (struct tcphdr *)skb_transport_header(skb); + if (tcp_header) { + int tcp_payload_size = (ntohs(ipv6_header->payload_len)) - (tcp_header->doff * 4); + if ( (NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_recv + tcp_payload_size) > ULLONG_MAX ) + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_recv = ULLONG_MAX; + else + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_recv = NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_recv + tcp_payload_size; + } + } else if (ipv6_header->nexthdr == IPPROTO_UDP) { + udp_header = (struct udphdr *)skb_transport_header(skb); + if (udp_header) { + int udp_payload_size = (ntohs(udp_header->len)) - sizeof(struct udphdr); + if ( (NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_recv + udp_payload_size) > ULLONG_MAX ) + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_recv = ULLONG_MAX; + else + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_recv = NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_recv + udp_payload_size; + } + } else { + NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_recv = 0; + } + } + } + } + + return NF_ACCEPT; +} + +/* The fuction registers to listen for packets in the post-routing chain to collect detail; */ +static void registerNetfilterHooks(void) { + nfho_ipv4_pr_conntrack.hook = hook_func_ipv4_out_conntrack; + nfho_ipv4_pr_conntrack.hooknum = NF_INET_POST_ROUTING; + nfho_ipv4_pr_conntrack.pf = PF_INET; + nfho_ipv4_pr_conntrack.priority = NF_IP_PRI_LAST; + + nfho_ipv6_pr_conntrack.hook = hook_func_ipv6_out_conntrack; + nfho_ipv6_pr_conntrack.hooknum = NF_INET_POST_ROUTING; + nfho_ipv6_pr_conntrack.pf = PF_INET6; + nfho_ipv6_pr_conntrack.priority = NF_IP6_PRI_LAST; + + nfho_ipv4_li_conntrack.hook = hook_func_ipv4_in_conntrack; + nfho_ipv4_li_conntrack.hooknum = NF_INET_LOCAL_IN; + nfho_ipv4_li_conntrack.pf = PF_INET; + nfho_ipv4_li_conntrack.priority = NF_IP_PRI_LAST; + + nfho_ipv6_li_conntrack.hook = hook_func_ipv6_in_conntrack; + nfho_ipv6_li_conntrack.hooknum = NF_INET_LOCAL_IN; + nfho_ipv6_li_conntrack.pf = PF_INET6; + nfho_ipv6_li_conntrack.priority = NF_IP6_PRI_LAST; + + nf_register_net_hook(&init_net,&nfho_ipv4_pr_conntrack); + nf_register_net_hook(&init_net,&nfho_ipv6_pr_conntrack); + nf_register_net_hook(&init_net,&nfho_ipv4_li_conntrack); + nf_register_net_hook(&init_net,&nfho_ipv6_li_conntrack); +} + +/* The function un-registers the netfilter hook */ +static void unregisterNetFilterHooks(void) { + nf_unregister_net_hook(&init_net,&nfho_ipv4_pr_conntrack); + nf_unregister_net_hook(&init_net,&nfho_ipv6_pr_conntrack); + nf_unregister_net_hook(&init_net,&nfho_ipv4_li_conntrack); + nf_unregister_net_hook(&init_net,&nfho_ipv6_li_conntrack); +} + +/* Function to collect the conntrack meta-data information. This function is called from ncm.c during the flows first send data and nf_conntrack_core.c when flow is removed. */ +void knox_collect_conntrack_data(struct nf_conn *ct, int startStop, int where) { + if ( check_ncm_flag() && (ncm_activated_type == startStop || ncm_activated_type == NCM_FLOW_TYPE_ALL) && (NF_CONN_NPA_VENDOR_DATA_GET(ct) != NULL) ) { + struct knox_socket_metadata *ksm = kzalloc(sizeof(struct knox_socket_metadata), GFP_ATOMIC); + struct nf_conntrack_tuple *tuple = NULL; + struct timespec64 close_timespec; + + if (ksm == NULL) { + printk("kzalloc atomic memory allocation failed\n"); + return; + } + + ksm->knox_uid = NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_uid; + ksm->knox_pid = NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_pid; + memcpy(ksm->process_name, NF_CONN_NPA_VENDOR_DATA_GET(ct)->process_name, sizeof(ksm->process_name)-1); + ksm->trans_proto = nf_ct_protonum(ct); + tuple = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple; + if (tuple != NULL) { + if (nf_ct_l3num(ct) == IPV4_FAMILY_NAP) { + sprintf(ksm->srcaddr,"%pI4",(void *)&tuple->src.u3.ip); + sprintf(ksm->dstaddr,"%pI4",(void *)&tuple->dst.u3.ip); + } else if (nf_ct_l3num(ct) == IPV6_FAMILY_NAP) { + sprintf(ksm->srcaddr,"%pI6",(void *)&tuple->src.u3.ip6); + sprintf(ksm->dstaddr,"%pI6",(void *)&tuple->dst.u3.ip6); + } + if (nf_ct_protonum(ct) == IPPROTO_UDP) { + ksm->srcport = ntohs(tuple->src.u.udp.port); + ksm->dstport = ntohs(tuple->dst.u.udp.port); + } else if (nf_ct_protonum(ct) == IPPROTO_TCP) { + ksm->srcport = ntohs(tuple->src.u.tcp.port); + ksm->dstport = ntohs(tuple->dst.u.tcp.port); + } else if (nf_ct_protonum(ct) == IPPROTO_SCTP) { + ksm->srcport = ntohs(tuple->src.u.sctp.port); + ksm->dstport = ntohs(tuple->dst.u.sctp.port); + } else { + ksm->srcport = 0; + ksm->dstport = 0; + } + } + memcpy(ksm->domain_name, NF_CONN_NPA_VENDOR_DATA_GET(ct)->domain_name, sizeof(ksm->domain_name)-1); + ksm->open_time = NF_CONN_NPA_VENDOR_DATA_GET(ct)->open_time; + if (startStop == NCM_FLOW_TYPE_OPEN) { + ksm->close_time = 0; + } else if (startStop == NCM_FLOW_TYPE_CLOSE) { + ktime_get_ts64(&close_timespec); + ksm->close_time = close_timespec.tv_sec; + } else if (startStop == NCM_FLOW_TYPE_INTERMEDIATE) { + ktime_get_ts64(&close_timespec); + ksm->close_time = close_timespec.tv_sec; + } + ksm->knox_puid = NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_puid; + ksm->knox_ppid = NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_ppid; + memcpy(ksm->parent_process_name, NF_CONN_NPA_VENDOR_DATA_GET(ct)->parent_process_name, sizeof(ksm->parent_process_name)-1); + if ( (nf_ct_protonum(ct) == IPPROTO_UDP) || (nf_ct_protonum(ct) == IPPROTO_TCP) || (nf_ct_protonum(ct) == IPPROTO_SCTP) ) { + ksm->knox_sent = NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_sent; + ksm->knox_recv = NF_CONN_NPA_VENDOR_DATA_GET(ct)->knox_recv; + } else { + ksm->knox_sent = 0; + ksm->knox_recv = 0; + } + if (ksm->dstport == DNS_PORT_NAP && ksm->knox_uid > INIT_UID_NAP) { + ksm->knox_uid_dns = ksm->knox_uid; + } else { + ksm->knox_uid_dns = ksm->knox_puid; + } + memcpy(ksm->interface_name, NF_CONN_NPA_VENDOR_DATA_GET(ct)->interface_name, sizeof(ksm->interface_name)-1); + if (startStop == NCM_FLOW_TYPE_OPEN) { + ksm->flow_type = 1; + } else if (startStop == NCM_FLOW_TYPE_CLOSE) { + ksm->flow_type = 2; + } else if (startStop == NCM_FLOW_TYPE_INTERMEDIATE) { + ksm->flow_type = 3; + } else { + ksm->flow_type = 0; + } + + insert_data_kfifo_kthread(ksm); + } +} +EXPORT_SYMBOL(knox_collect_conntrack_data); + +/* The function opens the char device through which the userspace reads the socket meta-data information */ +static int ncm_open(struct inode *inode, struct file *file) { + NCM_LOGD("ncm_open is being called. \n"); + + if ( !(IS_ENABLED(CONFIG_NF_CONNTRACK)) ) { + NCM_LOGE("ncm_open failed:Trying to open in device conntrack module is not enabled \n"); + return -EACCES; + } + + if (!is_system_server()) { + NCM_LOGE("ncm_open failed:Caller is a non system process with uid %u \n", (current_uid().val)); + return -EACCES; + } + + if (device_open_count) { + NCM_LOGE("ncm_open failed:The device is already in open state \n"); + return -EBUSY; + } + + device_open_count++; + + try_module_get(THIS_MODULE); + + return SUCCESS; +} + +#ifdef CONFIG_64BIT +static ssize_t ncm_copy_data_user_64(char __user *buf, size_t count) +{ + struct knox_socket_metadata kcm = {0}; + struct knox_user_socket_metadata user_copy = {0}; + + unsigned long copied; + int read = 0; + + if (mutex_lock_interruptible(&ncm_lock)) { + NCM_LOGE("ncm_copy_data_user failed:Signal interuption \n"); + return 0; + } + read = kfifo_out(&knox_sock_info, &kcm, 1); + mutex_unlock(&ncm_lock); + if (read == 0) { + return 0; + } + + user_copy.srcport = kcm.srcport; + user_copy.dstport = kcm.dstport; + user_copy.trans_proto = kcm.trans_proto; + user_copy.knox_sent = kcm.knox_sent; + user_copy.knox_recv = kcm.knox_recv; + user_copy.knox_uid = kcm.knox_uid; + user_copy.knox_pid = kcm.knox_pid; + user_copy.knox_puid = kcm.knox_puid; + user_copy.open_time = kcm.open_time; + user_copy.close_time = kcm.close_time; + user_copy.knox_uid_dns = kcm.knox_uid_dns; + user_copy.knox_ppid = kcm.knox_ppid; + user_copy.flow_type = kcm.flow_type; + + memcpy(user_copy.srcaddr, kcm.srcaddr, sizeof(user_copy.srcaddr)); + memcpy(user_copy.dstaddr, kcm.dstaddr, sizeof(user_copy.dstaddr)); + + memcpy(user_copy.process_name, kcm.process_name, sizeof(user_copy.process_name)); + memcpy(user_copy.parent_process_name, kcm.parent_process_name, sizeof(user_copy.parent_process_name)); + + memcpy(user_copy.domain_name, kcm.domain_name, sizeof(user_copy.domain_name)-1); + + memcpy(user_copy.interface_name, kcm.interface_name, sizeof(user_copy.interface_name)-1); + + copied = copy_to_user(buf, &user_copy, sizeof(struct knox_user_socket_metadata)); + return count; +} +#else +static ssize_t ncm_copy_data_user(char __user *buf, size_t count) +{ + struct knox_socket_metadata *kcm = NULL; + struct knox_user_socket_metadata user_copy = {0}; + + unsigned long copied; + int read = 0; + + if (mutex_lock_interruptible(&ncm_lock)) { + NCM_LOGE("ncm_copy_data_user failed:Signal interuption \n"); + return 0; + } + + kcm = kzalloc(sizeof (struct knox_socket_metadata), GFP_KERNEL); + if (kcm == NULL) { + mutex_unlock(&ncm_lock); + return 0; + } + + read = kfifo_out(&knox_sock_info, kcm, 1); + mutex_unlock(&ncm_lock); + if (read == 0) { + kfree(kcm); + return 0; + } + + user_copy.srcport = kcm->srcport; + user_copy.dstport = kcm->dstport; + user_copy.trans_proto = kcm->trans_proto; + user_copy.knox_sent = kcm->knox_sent; + user_copy.knox_recv = kcm->knox_recv; + user_copy.knox_uid = kcm->knox_uid; + user_copy.knox_pid = kcm->knox_pid; + user_copy.knox_puid = kcm->knox_puid; + user_copy.open_time = kcm->open_time; + user_copy.close_time = kcm->close_time; + user_copy.knox_uid_dns = kcm->knox_uid_dns; + user_copy.knox_ppid = kcm->knox_ppid; + user_copy.flow_type = kcm->flow_type; + + memcpy(user_copy.srcaddr, kcm->srcaddr, sizeof(user_copy.srcaddr)); + memcpy(user_copy.dstaddr, kcm->dstaddr, sizeof(user_copy.dstaddr)); + + memcpy(user_copy.process_name, kcm->process_name, sizeof(user_copy.process_name)); + memcpy(user_copy.parent_process_name, kcm->parent_process_name, sizeof(user_copy.parent_process_name)); + + memcpy(user_copy.domain_name, kcm->domain_name, sizeof(user_copy.domain_name)-1); + + memcpy(user_copy.interface_name, kcm->interface_name, sizeof(user_copy.interface_name)-1); + + copied = copy_to_user(buf, &user_copy, sizeof(struct knox_user_socket_metadata)); + + kfree(kcm); + + return count; +} +#endif + +/* The function writes the socket meta-data to the user-space */ +static ssize_t ncm_read(struct file *file, char __user *buf, size_t count, loff_t *off) { + if (!is_system_server()) { + NCM_LOGE("ncm_read failed:Caller is a non system process with uid %u \n", (current_uid().val)); + return -EACCES; + } + + if (!eWq) { + NCM_LOGD("ewq..Single Thread created\r\n"); + eWq = create_workqueue("ncmworkqueue"); + } + + #ifdef CONFIG_64BIT + return ncm_copy_data_user_64(buf, count); + #else + return ncm_copy_data_user(buf, count); + #endif + + return 0; +} + +static ssize_t ncm_write(struct file *file, const char __user *buf, size_t count, loff_t *off) { + char intermediate_string[6]; + int intermediate_value = 0; + if (!is_system_server()) { + NCM_LOGE("ncm_write failed:Caller is a non system process with uid %u \n", (current_uid().val)); + return -EACCES; + } + memset(intermediate_string,'\0',sizeof(intermediate_string)); + (void)copy_from_user(intermediate_string,buf,sizeof(intermediate_string)-1); + intermediate_value = simple_strtol(intermediate_string, NULL, 10); + if (intermediate_value > 0) { + update_intermediate_timeout(intermediate_value); + update_intermediate_flag(intermediate_activated_flag); + return strlen(intermediate_string); + } + return intermediate_value; +} + +/* The function closes the char device */ +static int ncm_close(struct inode *inode, struct file *file) { + NCM_LOGD("ncm_close is being called \n"); + if (!is_system_server()) { + NCM_LOGE("ncm_close failed:Caller is a non system process with uid %u \n", (current_uid().val)); + return -EACCES; + } + device_open_count--; + module_put(THIS_MODULE); + if (!check_ncm_flag()) { + NCM_LOGD("ncm_close success: The device was already in closed state \n"); + return SUCCESS; + } + update_ncm_flag(ncm_deactivated_flag); + free_kfifo(); + unregisterNetFilterHooks(); + return SUCCESS; +} + +/* The function sets the flag which indicates whether the ncm feature needs to be enabled or disabled */ +static long ncm_ioctl_evt(struct file *file, unsigned int cmd, unsigned long arg) { + if (!is_system_server()) { + NCM_LOGE("ncm_ioctl_evt failed:Caller is a non system process with uid %u \n", (current_uid().val)); + return -EACCES; + } + switch (cmd) { + case NCM_ACTIVATED_ALL: { + NCM_LOGD("ncm_ioctl_evt is being NCM_ACTIVATED with the ioctl command %u \n", cmd); + if (check_ncm_flag()) + return SUCCESS; + registerNetfilterHooks(); + initialize_kfifo(); + initialize_ncmworkqueue(); + update_ncm_flag(ncm_activated_flag); + update_ncm_flow_type(NCM_FLOW_TYPE_ALL); + break; + } + case NCM_ACTIVATED_OPEN: { + NCM_LOGD("ncm_ioctl_evt is being NCM_ACTIVATED with the ioctl command %u \n", cmd); + if (check_ncm_flag()) + return SUCCESS; + update_intermediate_timeout(0); + update_intermediate_flag(intermediate_deactivated_flag); + registerNetfilterHooks(); + initialize_kfifo(); + initialize_ncmworkqueue(); + update_ncm_flag(ncm_activated_flag); + update_ncm_flow_type(NCM_FLOW_TYPE_OPEN); + break; + } + case NCM_ACTIVATED_CLOSE: { + NCM_LOGD("ncm_ioctl_evt is being NCM_ACTIVATED with the ioctl command %u \n", cmd); + if (check_ncm_flag()) + return SUCCESS; + update_intermediate_timeout(0); + update_intermediate_flag(intermediate_deactivated_flag); + registerNetfilterHooks(); + initialize_kfifo(); + initialize_ncmworkqueue(); + update_ncm_flag(ncm_activated_flag); + update_ncm_flow_type(NCM_FLOW_TYPE_CLOSE); + break; + } + case NCM_DEACTIVATED: { + NCM_LOGD("ncm_ioctl_evt is being NCM_DEACTIVATED with the ioctl command %u \n", cmd); + if (!check_ncm_flag()) + return SUCCESS; + update_intermediate_flag(intermediate_deactivated_flag); + update_ncm_flow_type(NCM_FLOW_TYPE_DEFAULT); + update_ncm_flag(ncm_deactivated_flag); + free_kfifo(); + unregisterNetFilterHooks(); + update_intermediate_timeout(0); + break; + } + case NCM_GETVERSION: { + NCM_LOGD("ncm_ioctl_evt is being NCM_GETVERSION with the ioctl command %u \n", cmd); + return NCM_VERSION; + break; + } + case NCM_MATCH_VERSION: { + NCM_LOGD("ncm_ioctl_evt is being NCM_MATCH_VERSION with the ioctl command %u \n", cmd); + return sizeof(struct knox_user_socket_metadata); + break; + } + default: + break; + } + return SUCCESS; +} + +static unsigned int ncm_poll(struct file *file, poll_table *pt) { + int mask = 0; + int ret = 0; + if (kfifo_is_empty(&knox_sock_info)) { + ret = wait_event_interruptible_timeout(ncm_wq, !kfifo_is_empty(&knox_sock_info), msecs_to_jiffies(WAIT_TIMEOUT)); + switch (ret) { + case -ERESTARTSYS: + mask = -EINTR; + break; + case 0: + mask = 0; + break; + case 1: + mask |= POLLIN | POLLRDNORM; + break; + default: + mask |= POLLIN | POLLRDNORM; + break; + } + return mask; + } else { + mask |= POLLIN | POLLRDNORM; + } + return mask; +} + +static const struct file_operations ncm_fops = { + .owner = THIS_MODULE, + .open = ncm_open, + .read = ncm_read, + .write = ncm_write, + .release = ncm_close, + .unlocked_ioctl = ncm_ioctl_evt, + .compat_ioctl = ncm_ioctl_evt, + .poll = ncm_poll, +}; + +struct miscdevice ncm_misc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "ncm_dev", + .fops = &ncm_fops, +}; + +static int __init ncm_init(void) { + int ret; + ret = misc_register(&ncm_misc_device); + if (unlikely(ret)) { + NCM_LOGE("failed to register ncm misc device!\n"); + return ret; + } + NCM_LOGD("Network Context Metadata Module: initialized\n"); + return SUCCESS; +} + +static void __exit ncm_exit(void) { + misc_deregister(&ncm_misc_device); + NCM_LOGD("Network Context Metadata Module: unloaded\n"); +} + +module_init(ncm_init) +module_exit(ncm_exit) + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Network Context Metadata Module:"); +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA } diff --git a/net/netfilter/Makefile b/net/netfilter/Makefile index 7756b2ba3183..e013dc49f033 100644 --- a/net/netfilter/Makefile +++ b/net/netfilter/Makefile @@ -218,6 +218,7 @@ obj-$(CONFIG_NETFILTER_XT_MATCH_STRING) += xt_string.o obj-$(CONFIG_NETFILTER_XT_MATCH_TCPMSS) += xt_tcpmss.o obj-$(CONFIG_NETFILTER_XT_MATCH_TIME) += xt_time.o obj-$(CONFIG_NETFILTER_XT_MATCH_U32) += xt_u32.o +obj-$(CONFIG_KNOX_NCM) += xt_domainfilter.o # ipset obj-$(CONFIG_IP_SET) += ipset/ diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c index 262f2e17c5d0..07bfaba1615b 100644 --- a/net/netfilter/nf_conntrack_core.c +++ b/net/netfilter/nf_conntrack_core.c @@ -52,8 +52,14 @@ #include #include #include +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA { +#ifdef CONFIG_KNOX_NCM +#include +#endif +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA } #include "nf_internals.h" + #include __cacheline_aligned_in_smp spinlock_t nf_conntrack_locks[CONNTRACK_LOCKS]; EXPORT_SYMBOL_GPL(nf_conntrack_locks); @@ -587,6 +593,14 @@ void nf_ct_destroy(struct nf_conntrack *nfct) { struct nf_conn *ct = (struct nf_conn *)nfct; + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA { +#ifdef CONFIG_KNOX_NCM + if (NF_CONN_NPA_VENDOR_DATA_GET(ct)) { + kfree(NF_CONN_NPA_VENDOR_DATA_GET(ct)); + ct->android_oem_data1 = (u64)NULL; + } +#endif + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA } pr_debug("%s(%p)\n", __func__, ct); WARN_ON(refcount_read(&nfct->use) != 0); @@ -1537,6 +1551,16 @@ static void gc_worker(struct work_struct *work) nf_ct_gc_expired(tmp); expired_count++; continue; + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA { +#ifdef CONFIG_KNOX_NCM + } else if ( (tmp != NULL) && (check_ncm_flag()) && (check_intermediate_flag()) && (NF_CONN_NPA_VENDOR_DATA_GET(tmp)) && (atomic_read(&NF_CONN_NPA_VENDOR_DATA_GET(tmp)->startFlow)) && (atomic_read(&NF_CONN_NPA_VENDOR_DATA_GET(tmp)->intermediateFlow)) ) { + s32 npa_timeout = NF_CONN_NPA_VENDOR_DATA_GET(tmp)->npa_timeout - ((u32)(jiffies)); + if (npa_timeout <= 0) { + NF_CONN_NPA_VENDOR_DATA_GET(tmp)->npa_timeout = ((u32)(jiffies)) + (get_intermediate_timeout() * HZ); + knox_collect_conntrack_data(tmp, NCM_FLOW_TYPE_INTERMEDIATE, 20); + } +#endif + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA } } expires = clamp(nf_ct_expires(tmp), GC_SCAN_INTERVAL_MIN, GC_SCAN_INTERVAL_CLAMP); @@ -1625,6 +1649,11 @@ __nf_conntrack_alloc(struct net *net, struct nf_conntrack_net *cnet = nf_ct_pernet(net); unsigned int ct_count; struct nf_conn *ct; + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA { +#ifdef CONFIG_KNOX_NCM + struct timespec64 open_timespec; +#endif + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA } /* We don't want any race condition at early drop stage */ ct_count = atomic_inc_return(&cnet->count); @@ -1648,6 +1677,11 @@ __nf_conntrack_alloc(struct net *net, goto out; spin_lock_init(&ct->lock); + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA { +#ifdef CONFIG_KNOX_NCM + ct->android_oem_data1 = (u64)NULL; +#endif + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA } ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple = *orig; ct->tuplehash[IP_CT_DIR_ORIGINAL].hnnode.pprev = NULL; ct->tuplehash[IP_CT_DIR_REPLY].tuple = *repl; @@ -1661,6 +1695,15 @@ __nf_conntrack_alloc(struct net *net, nf_ct_zone_add(ct, zone); trace_android_rvh_nf_conn_alloc(ct); + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA { +#ifdef CONFIG_KNOX_NCM + ct->android_oem_data1 = (u64)kzalloc(sizeof(struct nf_conn_npa_vendor_data), gfp); + ktime_get_ts64(&open_timespec); + if (NF_CONN_NPA_VENDOR_DATA_GET(ct) != NULL){ + NF_CONN_NPA_VENDOR_DATA_GET(ct)->open_time = open_timespec.tv_sec; + } +#endif + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA } /* Because we use RCU lookups, we set ct_general.use to zero before * this is inserted in any list. @@ -1707,6 +1750,14 @@ void nf_conntrack_free(struct nf_conn *ct) cnet = nf_ct_pernet(net); smp_mb__before_atomic(); + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA { +#ifdef CONFIG_KNOX_NCM + if (NF_CONN_NPA_VENDOR_DATA_GET(ct)) { + kfree(NF_CONN_NPA_VENDOR_DATA_GET(ct)); + ct->android_oem_data1 = (u64)NULL; + } +#endif + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_NPA } trace_android_rvh_nf_conn_free(ct); atomic_dec(&cnet->count); } diff --git a/net/netfilter/xt_connmark.c b/net/netfilter/xt_connmark.c index ad3c033db64e..e3ef1e4b106a 100644 --- a/net/netfilter/xt_connmark.c +++ b/net/netfilter/xt_connmark.c @@ -15,6 +15,16 @@ #include #include +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN { +#ifdef CONFIG_KNOX_NCM +#include +#include +#include +#include +#include +#endif +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN } + MODULE_AUTHOR("Henrik Nordstrom "); MODULE_DESCRIPTION("Xtables: connection mark operations"); MODULE_LICENSE("GPL"); @@ -23,6 +33,44 @@ MODULE_ALIAS("ip6t_CONNMARK"); MODULE_ALIAS("ipt_connmark"); MODULE_ALIAS("ip6t_connmark"); +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN { +#ifdef CONFIG_KNOX_NCM +#define META_MARK_BASE_LOWER 100 +#define META_MARK_BASE_UPPER 500 + +static void knoxvpn_uidpid(struct sk_buff *skb, u_int32_t newmark) +{ + struct skb_shared_info *knox_shinfo = NULL; + + if (skb != NULL) { + knox_shinfo = skb_shinfo(skb); + } else { + pr_err("KNOX: NULL SKB - no KNOX processing"); + return; + } + + if( skb->sk == NULL) { + pr_err("KNOX: skb->sk value is null"); + return; + } + + if( knox_shinfo == NULL) { + pr_err("KNOX: knox_shinfo is null"); + return; + } + + if (newmark < META_MARK_BASE_LOWER || newmark > META_MARK_BASE_UPPER) { + pr_err("KNOX: The mark is out of range"); + return; + } else { + if ((current) && (current->cred)) knox_shinfo->android_oem_data1[0] = (u64)current->cred->uid.val; + if (current) knox_shinfo->android_oem_data1[1] = (u64)current->tgid; + knox_shinfo->android_oem_data1[2] = (u64)newmark; + } +} +#endif +// SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN } + static unsigned int connmark_tg_shift(struct sk_buff *skb, const struct xt_connmark_tginfo2 *info) { @@ -74,6 +122,11 @@ connmark_tg_shift(struct sk_buff *skb, const struct xt_connmark_tginfo2 *info) newmark = (skb->mark & ~info->nfmask) ^ new_targetmark; skb->mark = newmark; + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN { +#ifdef CONFIG_KNOX_NCM + knoxvpn_uidpid(skb, newmark); +#endif + // SEC_PRODUCT_FEATURE_KNOX_SUPPORT_VPN } break; } return XT_CONTINUE; diff --git a/net/netfilter/xt_domainfilter.c b/net/netfilter/xt_domainfilter.c new file mode 100644 index 000000000000..de58fb69a85b --- /dev/null +++ b/net/netfilter/xt_domainfilter.c @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. + * + * Domain Filter Module:Implementation. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +// Below char works as wildcard (*), it can be used as part or whole domain +static const char WILDCARD = '%'; + +/* + * Check if a given string is the ending substring of another. + */ +static bool endsWith(const char *host, const char *rule) { + size_t hostLen = strlen(host); + size_t ruleLen = strlen(rule); + if (hostLen >= ruleLen) { + unsigned int offSet = hostLen - ruleLen; + return strncmp(host + offSet , rule, ruleLen) == 0; + } else { + return false; + } +} + +/* + * Check if a given string is the beginning substring of another. + */ +static bool beginsWith(const char *host, const char *rule) { + size_t hostLen = strlen(host); + size_t ruleLen = strlen(rule); + if (hostLen >= ruleLen) { + return strncmp(host, rule, ruleLen) == 0; + } else { + return false; + } +} + +/* + * Check if the given host matches the provided white/black list rules. + */ +static bool matchHost(const char *rule, const char *host) { + size_t ruleLen = strlen(rule); + if (ruleLen == 1 && rule[0] == WILDCARD) { // rule is *, means all hosts + return true; + } + if (rule[0] == WILDCARD) { // starts with * + if (rule[ruleLen -1] == WILDCARD) { // also ends with * + // get the substring between the '*'s + char substrRule[XT_DOMAINFILTER_NAME_LEN]; + strncpy(substrRule, rule+1, ruleLen-2); + substrRule[ruleLen-2] = '\0'; + if(strstr(host, substrRule) != NULL) { + return true; + } + } else { // only starts with * + // remove * from beginning, so host must end if rule + char substrRule[XT_DOMAINFILTER_NAME_LEN]; + strncpy(substrRule, rule+1, ruleLen-1); + substrRule[ruleLen-1] = '\0'; + if (endsWith(host, substrRule)) + return true; + } + } else if (rule[ruleLen -1] == WILDCARD) { // only ends with '*' + char substrRule[XT_DOMAINFILTER_NAME_LEN]; + strncpy(substrRule, rule, ruleLen-1); + substrRule[ruleLen-1] = '\0'; + if (beginsWith(host, substrRule)) + return true; + } else if (strlen(host) == ruleLen && + strcmp(host, rule) == 0) { // exact match + return true; + } + return false; +} + +static int domainfilter_check(const struct xt_mtchk_param *par) +{ + struct xt_domainfilter_match_info *info = par->matchinfo; + if (!(info->flags & (XT_DOMAINFILTER_WHITE|XT_DOMAINFILTER_BLACK))) { + return -EINVAL; + } + return 0; +} + +static bool +domainfilter_mt(const struct sk_buff *skb, struct xt_action_param *par) +{ + const struct xt_domainfilter_match_info *info = par->matchinfo; + struct sock *sk = skb_to_full_sk(skb); + + if (sk == NULL) { + return false; + } + + // check domain name match + if (SOCK_NPA_VENDOR_DATA_GET(sk)->domain_name != NULL && SOCK_NPA_VENDOR_DATA_GET(sk)->domain_name[0] != '\0') { + return matchHost(info->domain_name, SOCK_NPA_VENDOR_DATA_GET(sk)->domain_name); + } + + // didn't match + return false; +} + +static struct xt_match domainfilter_mt_reg __read_mostly = { + .name = "domainfilter", + .revision = 1, + .family = NFPROTO_UNSPEC, + .checkentry = domainfilter_check, + .match = domainfilter_mt, + .matchsize = sizeof(struct xt_domainfilter_match_info), + .hooks = (1 << NF_INET_LOCAL_OUT) | + (1 << NF_INET_LOCAL_IN), + .me = THIS_MODULE, +}; + +static int __init domainfilter_mt_init(void) +{ + return xt_register_match(&domainfilter_mt_reg); +} + +static void __exit domainfilter_mt_exit(void) +{ + xt_unregister_match(&domainfilter_mt_reg); +} + +module_init(domainfilter_mt_init); +module_exit(domainfilter_mt_exit); +MODULE_AUTHOR("Antonio Junqueira "); +MODULE_DESCRIPTION("Xtables: domain name matching"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("ipt_domainfilter"); +MODULE_ALIAS("ip6t_domainfilter"); diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib index 1e1f2ccf42b5..20e3fb6d0ef8 100644 --- a/scripts/Makefile.lib +++ b/scripts/Makefile.lib @@ -250,6 +250,12 @@ dtc_cpp_flags = -Wp,-MMD,$(depfile).pre.tmp -nostdinc \ $(addprefix -I,$(DTC_INCLUDE)) \ -undef -D__DTS__ +ifeq ($(CONFIG_SEC_FACTORY),y) +dtc_cpp_flags += -DCONFIG_SEC_FACTORY +ifeq ($(CONFIG_SEC_FACTORY_INTERPOSER),y) +dtc_cpp_flags += -DCONFIG_SEC_FACTORY_INTERPOSER +endif +endif ifdef CONFIG_OBJTOOL objtool := $(objtree)/tools/objtool/objtool diff --git a/security/Kconfig b/security/Kconfig index e6db09a779b7..9d59688189c8 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -258,7 +258,17 @@ config LSM If unsure, leave this as the default. +config DDAR_KEY_DUMP + bool "DDAR KEY DUMP" + default n + help + For dumping sensitive keying material for CC Certification test. + Enabled only for eng and userdebug builds. + source "security/Kconfig.hardening" +source "security/samsung/proca/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "security/samsung/proca/gaf/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +source "security/samsung/five/Kconfig" # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT endmenu diff --git a/security/Makefile b/security/Makefile index 18121f8f85cd..ab46c5c13782 100644 --- a/security/Makefile +++ b/security/Makefile @@ -27,3 +27,6 @@ obj-$(CONFIG_SECURITY_LANDLOCK) += landlock/ # Object integrity file lists obj-$(CONFIG_INTEGRITY) += integrity/ +obj-y += samsung/proca/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += samsung/proca/gaf/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT +obj-y += samsung/five/ # ADDED BY LEGO AUTOMATICALLY: DO NOT SUBMIT diff --git a/security/samsung/five/Kconfig b/security/samsung/five/Kconfig new file mode 100644 index 000000000000..4bb10c1928c2 --- /dev/null +++ b/security/samsung/five/Kconfig @@ -0,0 +1,108 @@ +# Task Integrity Verifier + +config FIVE + bool "File Based Task Integrity Verifier (FIVE)(based on IMA)" + depends on INTEGRITY && DM_VERITY && BLK_DEV_LOOP + select CRYPTO + select CRYPTO_SHA1 + select CRYPTO_SHA1_ARM64_CE if ARM64_CRYPTO && KERNEL_MODE_NEON + select CRYPTO_HASH_INFO + select INTEGRITY_SIGNATURE + select INTEGRITY_ASYMMETRIC_KEYS + default n + help + File Based Task Integrity Verifier (FIVE) maintains + signatures of executables and other sensitive system files, + as they are read or executed. If an attacker manages + to change the contents of an important system file + being measured, we can tell. + +config FIVE_GKI_10 + bool "GKI 1.0 compatible version of FIVE" + depends on FIVE + default n + help + Build GKI 1.0 compatible version of FIVE + +config FIVE_GKI_20 + bool "GKI 2.0 compatible version of FIVE" + depends on FIVE + default n + help + Build GKI 2.0 compatible version of FIVE + +config FIVE_DEBUG + bool "FIVE Debug mode" + depends on FIVE + default n + help + Enable the debug mode in the FIVE + +config FIVE_CERT_ENG + string "FIVE certificate to verify signatures for eng binary" + depends on FIVE_DEBUG + default "x509_five_eng.der" + help + Path to CERT which will be built-in to eng binary + +config FIVE_CERT_USER + string "FIVE certificate to verify signatures for user binary" + depends on FIVE + default "x509_five_user.der" + help + Path to CERT which will be built-in to user binary + +choice + prompt "Default integrity hash algorithm" + depends on FIVE + default FIVE_DEFAULT_HASH_SHA1 + help + Select the default hash algorithm used for the measurement + list, integrity appraisal and audit log. + + config FIVE_DEFAULT_HASH_SHA1 + bool "SHA1 (default)" + depends on CRYPTO_SHA1 + + config FIVE_DEFAULT_HASH_SHA256 + bool "SHA256" + depends on CRYPTO_SHA256 + + config FIVE_DEFAULT_HASH_SHA512 + bool "SHA512" + depends on CRYPTO_SHA512 + + config FIVE_DEFAULT_HASH_WP512 + bool "WP512" + depends on CRYPTO_WP512 +endchoice + +config FIVE_DEFAULT_HASH + string + depends on FIVE + default "sha1" if FIVE_DEFAULT_HASH_SHA1 + default "sha256" if FIVE_DEFAULT_HASH_SHA256 + default "sha512" if FIVE_DEFAULT_HASH_SHA512 + default "wp512" if FIVE_DEFAULT_HASH_WP512 + +config FIVE_TRUSTED_KEYRING + bool "Require all keys on the .five keyring be signed" + depends on FIVE && SYSTEM_TRUSTED_KEYRING + default y + help + This option requires that all keys added to the .five + keyring be signed by a key on the system trusted keyring. + +config FIVE_PA_FEATURE + bool "Process authenticator" + depends on FIVE && !PROCA + default y + help + Enable Process Authenticator related code + +config FIVE_AUDIT_VERBOSE + bool "FIVE verbose audit logs" + depends on FIVE_DEBUG + default n + help + Enable verbose audit logs. diff --git a/security/samsung/five/Makefile b/security/samsung/five/Makefile new file mode 100644 index 000000000000..34daec38d710 --- /dev/null +++ b/security/samsung/five/Makefile @@ -0,0 +1,46 @@ + +obj-$(CONFIG_FIVE) += five.o +obj-$(CONFIG_FIVE_PA_FEATURE) += five_pa.o + +EXTRA_CFLAGS += -I$(src) +asflags-y += -Isecurity/integrity/five +asflags-y += -Isecurity/samsung/five +ccflags-y += -I$(srctree) +ccflags-y += -Wformat + +five-y := five_lv.o five_cert.o five_keyring.o five_init.o \ + five_cert_builtin.o five_cache.o \ + five_dmverity.o +five-$(CONFIG_FIVE_DSMS) += five_dsms.o + +ifdef CONFIG_FIVE_GKI_20 + FILES := five_main.o five_appraise.o five_crypto.o five_audit.o \ + five_hooks.o task_integrity.o five_state.o five_vfs.o \ + five_tint_dev.o five_tee_interface.o + five-y += $(addprefix gki/,$(FILES)) +else + five-y += five_crypto.o five_audit.o \ + five_hooks.o task_integrity.o five_state.o + ifdef CONFIG_PROCA_S_OS + five-y += s_os/five_main.o s_os/five_appraise.o + else + five-y += five_main.o five_appraise.o five_tee_interface.o + endif +endif + +# kunit tests options: +ENABLED_TARGETS_LIST = b0s g0s +ifeq ($(CONFIG_SEC_KUNIT), y) + GCOV_PROFILE := y + EXTRA_CFLAGS += -DFIVE_KUNIT_ENABLED + five-$(CONFIG_FIVE) += five_dsms.o + ifeq ($(CONFIG_UML), y) + else + ifneq ($(filter $(TARGET_DEVICE),$(ENABLED_TARGETS_LIST)),) + ifeq ($(CONFIG_FIVE_PA_FEATURE), y) + endif + else + $(warning security/five: TARGET_DEVICE: $(TARGET_DEVICE). skip on-device kunit test) + endif + endif +endif diff --git a/security/samsung/five/five.h b/security/samsung/five/five.h new file mode 100644 index 000000000000..6f67c195cf25 --- /dev/null +++ b/security/samsung/five/five.h @@ -0,0 +1,116 @@ +/* + * This code is based on IMA's code + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * + * Egor Uleyskiy, + * Viacheslav Vovchenko + * Yevgen Kopylov + * + * 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_FIVE_H +#define __LINUX_FIVE_H + +#include +#include +#include +#include +#include +#include +#include + +#include "five_cert.h" +#include "five_crypto.h" + +#define XATTR_FIVE_SUFFIX "five" +#define XATTR_NAME_FIVE (XATTR_SECURITY_PREFIX XATTR_FIVE_SUFFIX) + +#define XATTR_PA_SUFFIX "pa" +#define XATTR_NAME_PA (XATTR_USER_PREFIX XATTR_PA_SUFFIX) + +/* set during initialization */ +extern int five_hash_algo; +struct worker_context { + struct work_struct data_work; + struct task_integrity *tint; +}; + +struct f_signature_task { + struct task_integrity *tint; + struct file *file; +}; + +struct f_signature_context { + struct work_struct data_work; + struct f_signature_task payload; +}; + +struct five_stat { + u64 inode_iversion; + u64 cache_iversion; + u32 cache_status; + u32 is_dm_verity; +}; + +/* Internal FIVE function definitions */ +int five_init(void); + +/* FIVE policy related functions */ +enum five_hooks { + FILE_CHECK = 1, + MMAP_CHECK, + BPRM_CHECK, + POST_SETATTR +}; + +struct file_verification_result { + struct task_struct *task; + struct file *file; + struct integrity_iint_cache *iint; + enum five_hooks fn; + int five_result; + void *xattr; + size_t xattr_len; +}; + +static inline void file_verification_result_init( + struct file_verification_result *result) +{ + memset(result, 0, sizeof(*result)); +} + +static inline void file_verification_result_deinit( + struct file_verification_result *result) +{ + kfree(result->xattr); + memset(result, 0, sizeof(*result)); +} + +int five_appraise_measurement(struct task_struct *task, int func, + struct integrity_iint_cache *iint, + struct file *file, + struct five_cert *cert); + +int five_read_xattr(struct dentry *dentry, char **xattr_value); +int five_check_params(struct task_struct *task, struct file *file); +const char *five_d_path(const struct path *path, char **pathbuf, + char *namebuf); + +int five_digsig_verify(struct five_cert *cert, + const char *digest, int digestlen); +int five_reboot_notifier(struct notifier_block *nb, + unsigned long action, void *unused); +int __init five_load_built_x509(void); +int __init five_keyring_init(void); + +const char *five_get_string_fn(enum five_hooks fn); +#endif diff --git a/security/samsung/five/five_appraise.c b/security/samsung/five/five_appraise.c new file mode 100644 index 000000000000..5253c6f172a3 --- /dev/null +++ b/security/samsung/five/five_appraise.c @@ -0,0 +1,852 @@ +/* + * This code is based on IMA's code + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * + * Egor Uleyskiy, + * Viacheslav Vovchenko + * Yevgen Kopylov + * + * 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 "five.h" +#include "five_audit.h" +#include "five_hooks.h" +#include "five_tee_api.h" +#include "five_porting.h" +#include "five_cache.h" +#include "five_dmverity.h" + +#define FIVE_RSA_SIGNATURE_MAX_LENGTH (2048/8) +/* Identify extend structure of integrity label */ +#define FIVE_ID_INTEGRITY_LABEL_EX 0xFFFF +#define FIVE_LABEL_VERSION1 1 +/* Maximum length of data integrity label. + * This limit is applied because: + * 1. TEEgris doesn't support signing data longer than 480 bytes; + * 2. The label's length is limited to 3965 byte according to the data + * transmission protocol between five_tee_driver and TA. + */ +#define FIVE_LABEL_MAX_LEN 256 + +/** + * Extend structure of integrity label. + * If field "len" equals 0xffff then it is extend integrity label, + * otherwise simple integrity label. + */ +struct integrity_label_ex { + uint16_t len; + uint8_t version; + uint8_t reserved[2]; + uint8_t hash_algo; + uint8_t hash[64]; + struct integrity_label label; +} __packed; + +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP +static const bool panic_on_error = true; +#else +static const bool panic_on_error; +#endif + +static DECLARE_RWSEM(sign_fcntl_lock); + +/* + * five_collect_measurement - collect file measurement + * + * Must be called with iint->mutex held. + * + * Return 0 on success, error code otherwise. + */ +static int five_collect_measurement(struct file *file, u8 hash_algo, + u8 *hash, size_t hash_len) +{ + int result = 0; + + BUG_ON(!file || !hash); + + result = five_calc_file_hash(file, hash_algo, hash, &hash_len); + if (result) { + five_audit_err(current, file, "collect_measurement", 0, + 0, "calculate file hash failed", result); + } + return result; +} + +static int get_integrity_label(struct five_cert *cert, + void **label_data, size_t *label_len) +{ + int rc = -ENODATA; + struct five_cert_header *header = + (struct five_cert_header *)cert->body.header->value; + + if (header && header->signature_type == FIVE_XATTR_HMAC) { + *label_data = cert->body.label->value; + *label_len = cert->body.label->length; + rc = 0; + } + return rc; +} + +static int get_signature(struct five_cert *cert, void **sig, + size_t *sig_len) +{ + int rc = -ENODATA; + struct five_cert_header *header = + (struct five_cert_header *)cert->body.header->value; + + if (header && header->signature_type == FIVE_XATTR_HMAC) { + if (cert->signature->length == 0) + return rc; + *sig = cert->signature->value; + *sig_len = cert->signature->length; + + rc = 0; + } + + return rc; +} + +static int update_label(struct integrity_iint_cache *iint, + const void *label_data, size_t label_len) +{ + struct integrity_label *l; + + if (!label_data) + return 0; + + l = kmalloc(sizeof(struct integrity_label) + label_len, GFP_NOFS); + if (l) { + l->len = label_len; + memcpy(l->data, label_data, label_len); + kfree(iint->five_label); + iint->five_label = l; + } else { + return -ENOMEM; + } + + return 0; +} + +static int five_fix_xattr(struct task_struct *task, + struct dentry *dentry, + struct file *file, + void **raw_cert, + size_t *raw_cert_len, + struct integrity_iint_cache *iint, + struct integrity_label_ex *label) +{ + int rc = 0; + u8 hash[FIVE_MAX_DIGEST_SIZE], *hash_file, *sig = NULL; + size_t hash_len = sizeof(hash), hash_file_len, sig_len; + void *file_label = label->label.data; + u16 file_label_len = label->label.len; + struct five_cert_body body_cert = {0}; + struct five_cert_header *header; + + BUG_ON(!task || !dentry || !file || !raw_cert || !(*raw_cert) || !iint); + BUG_ON(!raw_cert_len); + + rc = five_cert_body_fillout(&body_cert, *raw_cert, *raw_cert_len); + if (unlikely(rc)) + return -EINVAL; + + header = (struct five_cert_header *)body_cert.header->value; + hash_file = body_cert.hash->value; + hash_file_len = body_cert.hash->length; + if (unlikely(!header || !hash_file)) + return -EINVAL; + + if (label->version == FIVE_LABEL_VERSION1) { + rc = five_collect_measurement(file, header->hash_algo, + hash_file, hash_file_len); + if (unlikely(rc)) + return rc; + } else { + memcpy(hash_file, label->hash, hash_file_len); + } + + rc = five_cert_calc_hash(&body_cert, hash, &hash_len); + if (unlikely(rc)) + return rc; + + sig_len = (size_t)body_cert.hash->length + file_label_len; + + sig = kzalloc(sig_len, GFP_NOFS); + if (!sig) + return -ENOMEM; + + rc = sign_hash(header->hash_algo, hash, hash_len, + file_label, file_label_len, sig, &sig_len); + + if (!rc) { + rc = five_cert_append_signature(raw_cert, raw_cert_len, + sig, sig_len); + if (!rc) { + int count = 1; + + do { + rc = __vfs_setxattr_noperm(d_real_comp(dentry), + XATTR_NAME_FIVE, + *raw_cert, + *raw_cert_len, + 0); + count--; + } while (count >= 0 && rc != 0); + + if (!rc) { + rc = update_label(iint, + file_label, file_label_len); + } + } + } else if (panic_on_error) { + panic("FIVE failed to sign %s (ret code = %d)", + dentry->d_name.name, rc); + } else { + five_audit_sign_err(current, file, "fix_xattr", 0, + 0, "can't sign the file", rc); + } + + kfree(sig); + + return rc; +} + +int five_read_xattr(struct dentry *dentry, char **xattr_value) +{ + ssize_t ret; + + ret = vfs_getxattr_alloc(dentry, XATTR_NAME_FIVE, xattr_value, + 0, GFP_NOFS); + if (ret < 0) + ret = 0; + + return ret; +} + +static bool bad_fs(struct inode *inode) +{ + if (inode->i_sb->s_magic == EXT4_SUPER_MAGIC || + inode->i_sb->s_magic == F2FS_SUPER_MAGIC || + inode->i_sb->s_magic == OVERLAYFS_SUPER_MAGIC || + inode->i_sb->s_magic == EROFS_SUPER_MAGIC_V1) + return false; + + return true; +} + +static bool readonly_sb(struct inode *inode) +{ + if (inode->i_sb->s_flags & MS_RDONLY) + return true; + + return false; +} + +/* + * five_is_fsverity_protected - checks if file is protected by FSVERITY + * + * Return true/false + */ +static bool five_is_fsverity_protected(const struct inode *inode) +{ + return IS_VERITY(inode); +} + +/* + * five_appraise_measurement - appraise file measurement + * + * Return 0 on success, error code otherwise + */ +int five_appraise_measurement(struct task_struct *task, int func, + struct integrity_iint_cache *iint, + struct file *file, + struct five_cert *cert) +{ + enum task_integrity_reset_cause cause = CAUSE_UNKNOWN; + struct dentry *dentry = NULL; + struct inode *inode = NULL; + enum five_file_integrity status = FIVE_FILE_UNKNOWN; + enum task_integrity_value prev_integrity; + int rc = 0; + u8 *file_hash; + u8 stored_file_hash[FIVE_MAX_DIGEST_SIZE] = {0}; + size_t file_hash_len = 0; + struct five_cert_header *header = NULL; + + BUG_ON(!task || !iint || !file); + + prev_integrity = task_integrity_read(TASK_INTEGRITY(task)); + dentry = file->f_path.dentry; + inode = d_backing_inode(dentry); + + if (bad_fs(inode)) { + status = FIVE_FILE_FAIL; + cause = CAUSE_BAD_FS; + rc = -ENOTSUPP; + goto out; + } + + if (!cert) { + cause = CAUSE_NO_CERT; + if (five_is_fsverity_protected(inode)) + status = FIVE_FILE_FSVERITY; + else if (five_is_dmverity_protected(file)) + status = FIVE_FILE_DMVERITY; + goto out; + } + + header = (struct five_cert_header *)cert->body.header->value; + file_hash = cert->body.hash->value; + file_hash_len = cert->body.hash->length; + if (file_hash_len > sizeof(stored_file_hash)) { + cause = CAUSE_INVALID_HASH_LENGTH; + rc = -EINVAL; + goto out; + } + + memcpy(stored_file_hash, file_hash, file_hash_len); + + if (unlikely(!header || !file_hash)) { + cause = CAUSE_INVALID_HEADER; + rc = -EINVAL; + goto out; + } + + rc = five_collect_measurement(file, header->hash_algo, file_hash, + file_hash_len); + if (rc) { + cause = CAUSE_CALC_HASH_FAILED; + goto out; + } + + switch (header->signature_type) { + case FIVE_XATTR_HMAC: { + u8 *sig = NULL; + u8 algo = header->hash_algo; + void *file_label_data; + size_t file_label_len, sig_len = 0; + u8 cert_hash[FIVE_MAX_DIGEST_SIZE] = {0}; + size_t cert_hash_len = sizeof(cert_hash); + + status = FIVE_FILE_FAIL; + + rc = get_integrity_label(cert, &file_label_data, + &file_label_len); + if (unlikely(rc)) { + cause = CAUSE_INVALID_LABEL_DATA; + break; + } + + if (unlikely(file_label_len > PAGE_SIZE)) { + cause = CAUSE_INVALID_LABEL_DATA; + break; + } + + rc = get_signature(cert, (void **)&sig, &sig_len); + if (unlikely(rc)) { + cause = CAUSE_INVALID_SIGNATURE_DATA; + break; + } + + rc = five_cert_calc_hash(&cert->body, cert_hash, + &cert_hash_len); + if (unlikely(rc)) { + cause = CAUSE_INVALID_CALC_CERT_HASH; + break; + } + + rc = verify_hash(algo, cert_hash, + cert_hash_len, + file_label_data, file_label_len, + sig, sig_len); + if (unlikely(rc)) { + cause = CAUSE_INVALID_HASH; + if (cert) { + five_audit_hexinfo(file, "stored hash", + stored_file_hash, file_hash_len); + five_audit_hexinfo(file, "calculated hash", + file_hash, file_hash_len); + five_audit_hexinfo(file, "HMAC signature", + sig, sig_len); + } + break; + } + + rc = update_label(iint, file_label_data, file_label_len); + if (unlikely(rc)) { + cause = CAUSE_INVALID_UPDATE_LABEL; + break; + } + + status = FIVE_FILE_HMAC; + + break; + } + case FIVE_XATTR_DIGSIG: { + u8 cert_hash[FIVE_MAX_DIGEST_SIZE] = {0}; + size_t cert_hash_len = sizeof(cert_hash); + + status = FIVE_FILE_FAIL; + + rc = five_cert_calc_hash(&cert->body, cert_hash, + &cert_hash_len); + if (unlikely(rc)) { + cause = CAUSE_INVALID_CALC_CERT_HASH; + break; + } + + rc = five_digsig_verify(cert, cert_hash, cert_hash_len); + + if (rc) { + cause = CAUSE_INVALID_SIGNATURE; + if (cert) { + five_audit_hexinfo(file, "stored hash", + stored_file_hash, file_hash_len); + five_audit_hexinfo(file, "calculated hash", + file_hash, file_hash_len); + five_audit_hexinfo(file, "RSA signature", + cert->signature->value, + cert->signature->length); + } + break; + } + + status = FIVE_FILE_RSA; + + break; + } + default: + status = FIVE_FILE_FAIL; + cause = CAUSE_UKNOWN_FIVE_DATA; + break; + } + +out: + if (status == FIVE_FILE_FAIL || status == FIVE_FILE_UNKNOWN) { + task_integrity_set_reset_reason(TASK_INTEGRITY(task), + cause, file); + five_audit_verbose(task, file, five_get_string_fn(func), + prev_integrity, prev_integrity, + tint_reset_cause_to_string(cause), rc); + } + + five_set_cache_status(iint, status); + + return rc; +} + +/* + * five_update_xattr - update 'security.five' hash value + */ +static int five_update_xattr(struct task_struct *task, + struct integrity_iint_cache *iint, struct file *file, + struct integrity_label_ex *label) +{ + struct dentry *dentry; + int rc = 0; + uint8_t *hash; + size_t hash_len; + uint8_t *raw_cert; + size_t raw_cert_len; + struct five_cert_header header = { + .version = FIVE_CERT_VERSION1, + .privilege = FIVE_PRIV_DEFAULT, + .hash_algo = five_hash_algo, + .signature_type = FIVE_XATTR_HMAC }; + + BUG_ON(!task || !iint || !file || !label); + + if (label->version == FIVE_LABEL_VERSION1) { + hash_len = (size_t)hash_digest_size[five_hash_algo]; + } else { + header.hash_algo = label->hash_algo; + if (label->hash_algo >= HASH_ALGO__LAST) + return -EINVAL; + + hash_len = (size_t)hash_digest_size[label->hash_algo]; + if (hash_len > sizeof(label->hash)) + return -EINVAL; + } + + hash = kzalloc(hash_len, GFP_KERNEL); + if (!hash) + return -ENOMEM; + + dentry = file->f_path.dentry; + + /* do not collect and update hash for digital signatures */ + if (five_get_cache_status(iint) == FIVE_FILE_RSA) { + char dummy[512]; + struct inode *inode = file_inode(file); + + rc = __vfs_getxattr(d_real_comp(dentry), inode, XATTR_NAME_FIVE, + dummy, sizeof(dummy), XATTR_NOSECURITY); + + // Check if xattr is exist + if (rc > 0 || rc != -ENODATA) { + kfree(hash); + return -EPERM; + } else { // xattr does not exist. + five_set_cache_status(iint, FIVE_FILE_UNKNOWN); + pr_err("FIVE: ERROR: Cache is unsynchronized"); + } + } + + rc = five_cert_body_alloc(&header, hash, hash_len, + label->label.data, label->label.len, + &raw_cert, &raw_cert_len); + if (rc) + goto exit; + + if (task_integrity_allow_sign(TASK_INTEGRITY(task))) { + rc = five_fix_xattr(task, dentry, file, + (void **)&raw_cert, &raw_cert_len, iint, label); + if (rc) + pr_err("FIVE: Can't sign hash: rc=%d\n", rc); + } else { + rc = -EPERM; + } + + five_hook_file_signed(task, file, raw_cert, raw_cert_len, rc); + + five_cert_free(raw_cert); + +exit: + kfree(hash); + return rc; +} + +static void five_reset_appraise_flags(struct dentry *dentry) +{ + struct inode *inode = d_backing_inode(dentry); + struct integrity_iint_cache *iint; + + if (!S_ISREG(inode->i_mode)) + return; + + iint = integrity_iint_find(inode); + if (iint) + five_set_cache_status(iint, FIVE_FILE_UNKNOWN); +} + +/** + * five_inode_post_setattr - reflect file metadata changes + * @dentry: pointer to the affected dentry + * + * Changes to a dentry's metadata might result in needing to appraise. + * + * This function is called from notify_change(), which expects the caller + * to lock the inode's i_mutex. + */ +void five_inode_post_setattr(struct task_struct *task, struct dentry *dentry) +{ + five_reset_appraise_flags(dentry); +} + +/* + * five_protect_xattr - protect 'security.five' + * + * Ensure that not just anyone can modify or remove 'security.five'. + */ +static int five_protect_xattr(struct dentry *dentry, const char *xattr_name, + const void *xattr_value, size_t xattr_value_len) +{ + if (strcmp(xattr_name, XATTR_NAME_FIVE) == 0) { + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + return 1; + } + return 0; +} + +int five_inode_setxattr(struct dentry *dentry, const char *xattr_name, + const void *xattr_value, size_t xattr_value_len) +{ + int result = five_protect_xattr(dentry, xattr_name, xattr_value, + xattr_value_len); + + if (result == 1 && xattr_value_len == 0) { + five_reset_appraise_flags(dentry); + return 0; + } + + if (result == 1) { + bool digsig; + struct five_cert_header *header; + struct five_cert cert = { {0} }; + + result = five_cert_fillout(&cert, xattr_value, xattr_value_len); + if (result) + return result; + + header = (struct five_cert_header *)cert.body.header->value; + + if (!xattr_value_len || !header || + (header->signature_type >= FIVE_XATTR_END)) + return -EINVAL; + + digsig = (header->signature_type == FIVE_XATTR_DIGSIG); + if (!digsig) + return -EPERM; + + five_reset_appraise_flags(dentry); + result = 0; + } + + return result; +} + +int five_inode_removexattr(struct dentry *dentry, const char *xattr_name) +{ + int result; + + result = five_protect_xattr(dentry, xattr_name, NULL, 0); + if (result == 1) { + five_reset_appraise_flags(dentry); + result = 0; + } + return result; +} + +int five_reboot_notifier(struct notifier_block *nb, + unsigned long action, void *unused) +{ + down_write(&sign_fcntl_lock); + /* Need to wait for five_fcntl_sign finish */ + up_write(&sign_fcntl_lock); + + return NOTIFY_DONE; +} + +static int copy_label(const struct integrity_label __user *ulabel, + struct integrity_label_ex **out_label) +{ + u16 len; + size_t label_len; + int rc = 0; + struct integrity_label_ex header = {0}; + struct integrity_label_ex *label = NULL; + + if (unlikely(!ulabel || !out_label)) { + rc = -EINVAL; + goto error; + } + + if (unlikely(copy_from_user(&len, ulabel, sizeof(len)))) { + rc = -EFAULT; + goto error; + } + + if (len == FIVE_ID_INTEGRITY_LABEL_EX) { + if (unlikely(copy_from_user(&header, ulabel, sizeof(header)))) { + rc = -EFAULT; + goto error; + } + + if (len != header.len || + header.label.len > FIVE_LABEL_MAX_LEN || + header.version <= FIVE_LABEL_VERSION1) { + rc = -EINVAL; + goto error; + } + + label_len = sizeof(header) + header.label.len; + + label = kzalloc(label_len, GFP_NOFS); + if (unlikely(!label)) { + rc = -ENOMEM; + goto error; + } + + memcpy(label, &header, sizeof(header)); + if (unlikely(copy_from_user(&label->label.data[0], + (const u8 __user *)ulabel + sizeof(header), + label_len - sizeof(header)))) { + rc = -EFAULT; + goto error; + } + } else { + if (len > FIVE_LABEL_MAX_LEN) { + rc = -EINVAL; + goto error; + } + + label_len = sizeof(header) + len; + + label = kzalloc(label_len, GFP_NOFS); + if (unlikely(!label)) { + rc = -ENOMEM; + goto error; + } + + if (unlikely(copy_from_user(&label->label, ulabel, + sizeof(len) + len))) { + rc = -EFAULT; + goto error; + } + + if (len != label->label.len) { + rc = -EINVAL; + goto error; + } + + label->version = FIVE_LABEL_VERSION1; + } + + *out_label = label; +error: + if (rc) + kfree(label); + + return rc; +} + +/* Called from do_fcntl */ +int five_fcntl_sign(struct file *file, struct integrity_label __user *label) +{ + struct integrity_iint_cache *iint; + struct inode *inode = file_inode(file); + struct integrity_label_ex *l = NULL; + int rc = 0; + + if (!S_ISREG(inode->i_mode)) + return -EINVAL; + + if (readonly_sb(inode)) { + pr_err("FIVE: Can't sign a file on RO FS\n"); + return -EROFS; + } + + if (task_integrity_allow_sign(TASK_INTEGRITY(current))) { + rc = copy_label(label, &l); + if (rc) { + pr_err("FIVE: Can't copy integrity label\n"); + return rc; + } + } else { + enum task_integrity_value tint = + task_integrity_read(TASK_INTEGRITY(current)); + + five_audit_err(current, file, "fcntl_sign", tint, tint, + "sign:no-perm", -EPERM); + return -EPERM; + } + + iint = integrity_inode_get(inode); + if (!iint) { + kfree(l); + return -ENOMEM; + } + + if (file->f_op && file->f_op->flush) { + if (file->f_op->flush(file, current->files)) { + kfree(l); + return -EOPNOTSUPP; + } + } + + down_read(&sign_fcntl_lock); + inode_lock(inode); + rc = five_update_xattr(current, iint, file, l); + iint->five_signing = false; + inode_unlock(inode); + up_read(&sign_fcntl_lock); + + kfree(l); + + return rc; +} + +static int check_input_inode(struct inode *inode) +{ + if (!S_ISREG(inode->i_mode)) + return -EINVAL; + + if (readonly_sb(inode)) { + pr_err("FIVE: Can't sign a file on RO FS\n"); + return -EROFS; + } + + return 0; +} + +int five_fcntl_edit(struct file *file) +{ + int rc; + struct dentry *dentry; + uint8_t *raw_cert = NULL; + size_t raw_cert_len = 0; + struct integrity_iint_cache *iint; + struct inode *inode = file_inode(file); + + rc = check_input_inode(inode); + if (rc) + return rc; + + if (!task_integrity_allow_sign(TASK_INTEGRITY(current))) + return -EPERM; + + inode_lock(inode); + dentry = file->f_path.dentry; + rc = __vfs_setxattr_noperm(d_real_comp(dentry), + XATTR_NAME_FIVE, + raw_cert, + raw_cert_len, + 0); + iint = integrity_inode_get(inode); + if (iint) + iint->five_signing = true; + inode_unlock(inode); + + return rc; +} + +int five_fcntl_close(struct file *file) +{ + int rc; + ssize_t xattr_len; + struct dentry *dentry; + struct integrity_iint_cache *iint; + struct inode *inode = file_inode(file); + + rc = check_input_inode(inode); + if (rc) + return rc; + + inode_lock(inode); + iint = integrity_inode_get(inode); + if (!iint) { + inode_unlock(inode); + return -ENOMEM; + } + + if (iint->five_signing) { + dentry = file->f_path.dentry; + xattr_len = __vfs_getxattr(d_real_comp(dentry), inode, + XATTR_NAME_FIVE, NULL, 0, XATTR_NOSECURITY); + if (xattr_len == 0) + rc = __vfs_removexattr(d_real_comp(dentry), + XATTR_NAME_FIVE); + + iint->five_signing = false; + } + inode_unlock(inode); + + return rc; +} diff --git a/security/samsung/five/five_audit.c b/security/samsung/five/five_audit.c new file mode 100644 index 000000000000..d2908c078315 --- /dev/null +++ b/security/samsung/five/five_audit.c @@ -0,0 +1,252 @@ +/* + * Audit calls for FIVE audit subsystem. + * + * Copyright (C) 2017 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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 "five.h" +#include "five_audit.h" +#include "five_cache.h" +#include "five_porting.h" +#include "five_dsms.h" +#include "five_testing.h" + +__visible_for_testing __mockable +void five_audit_msg(struct task_struct *task, struct file *file, + const char *op, enum task_integrity_value prev, + enum task_integrity_value tint, const char *cause, int result); + +#ifdef CONFIG_FIVE_AUDIT_VERBOSE +__mockable +void five_audit_verbose(struct task_struct *task, struct file *file, + const char *op, enum task_integrity_value prev, + enum task_integrity_value tint, const char *cause, int result) +{ + five_audit_msg(task, file, op, prev, tint, cause, result); +} +#else +__mockable +void five_audit_verbose(struct task_struct *task, struct file *file, + const char *op, enum task_integrity_value prev, + enum task_integrity_value tint, const char *cause, int result) +{ +} +#endif + +void five_audit_info(struct task_struct *task, struct file *file, + const char *op, enum task_integrity_value prev, + enum task_integrity_value tint, const char *cause, int result) +{ + five_audit_msg(task, file, op, prev, tint, cause, result); +} + +__visible_for_testing __mockable +void call_five_dsms_reset_integrity(const char *task_name, int result, + const char *file_name) +{ + five_dsms_reset_integrity(task_name, result, file_name); +} + +/** + * There are two kind of event that can come to the function: error + * and tampering attempt. 'result' is for identification of error type + * and it should be non-zero in case of error but is always zero in + * case of tampering. + */ +void five_audit_err(struct task_struct *task, struct file *file, + const char *op, enum task_integrity_value prev, + enum task_integrity_value tint, const char *cause, int result) +{ + five_audit_msg(task, file, op, prev, tint, cause, result); + + if (!result) { + char comm[TASK_COMM_LEN]; + struct task_struct *tsk = task ? task : current; + + call_five_dsms_reset_integrity(get_task_comm(comm, tsk), 0, op); + } +} + +__visible_for_testing __mockable +void call_five_dsms_sign_err(const char *app, int result) +{ + five_dsms_sign_err(app, result); +} + +void five_audit_sign_err(struct task_struct *task, struct file *file, + const char *op, enum task_integrity_value prev, + enum task_integrity_value tint, const char *cause, int result) +{ + char comm[TASK_COMM_LEN]; + struct task_struct *tsk = task ? task : current; + + get_task_comm(comm, tsk); + call_five_dsms_sign_err(comm, result); +} + +__visible_for_testing __mockable +void five_audit_msg(struct task_struct *task, struct file *file, + const char *op, enum task_integrity_value prev, + enum task_integrity_value tint, const char *cause, int result) +{ + struct audit_buffer *ab; + struct inode *inode = NULL; + const char *fname = NULL; + char *pathbuf = NULL; + char filename[NAME_MAX]; + char comm[TASK_COMM_LEN]; + const char *name = ""; + struct task_struct *tsk = task ? task : current; + struct integrity_iint_cache *iint = NULL; + + if (file) { + inode = file_inode(file); + fname = five_d_path(&file->f_path, &pathbuf, filename); + } + + ab = audit_log_start(current->audit_context, GFP_KERNEL, + AUDIT_INTEGRITY_DATA); + if (unlikely(!ab)) { + pr_err("Can't get a context of audit logs\n"); + goto exit; + } + + audit_log_format(ab, " pid=%d", task_pid_nr(tsk)); + audit_log_format(ab, " tgid=%d", task_tgid_nr(tsk)); + audit_log_task_context(ab); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)) + audit_log_format(ab, " op=%s", op); +#else + audit_log_format(ab, " op="); + audit_log_string(ab, op); +#endif + audit_log_format(ab, " cint=0x%x", tint); + audit_log_format(ab, " pint=0x%x", prev); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)) + audit_log_format(ab, " cause=%s", cause); +#else + audit_log_format(ab, " cause="); + audit_log_string(ab, cause); +#endif + audit_log_format(ab, " comm="); + audit_log_untrustedstring(ab, get_task_comm(comm, tsk)); + if (fname) { + audit_log_format(ab, " name="); + audit_log_untrustedstring(ab, fname); + name = fname; + } + if (inode) { + audit_log_format(ab, " dev="); + audit_log_untrustedstring(ab, inode->i_sb->s_id); + audit_log_format(ab, " ino=%lu", inode->i_ino); + audit_log_format(ab, " i_version=%llu ", + inode_query_iversion(inode)); + iint = integrity_inode_get(inode); + if (iint) { + audit_log_format(ab, " five_status=%d ", + five_get_cache_status(iint)); + audit_log_format(ab, " version=%llu ", + (unsigned long long)iint->version); + } + } + audit_log_format(ab, " res=%d", result); + audit_log_end(ab); + +exit: + if (pathbuf) + __putname(pathbuf); +} + +void five_audit_tee_msg(const char *func, const char *cause, int rc, + uint32_t origin) +{ + struct audit_buffer *ab; + + ab = audit_log_start(current->audit_context, GFP_KERNEL, + AUDIT_INTEGRITY_DATA); + if (unlikely(!ab)) { + pr_err("Can't get a context of audit logs\n"); + return; + } + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)) + audit_log_format(ab, " func=%s", func); + audit_log_format(ab, " cause=%s", cause); +#else + audit_log_format(ab, " func="); + audit_log_string(ab, func); + audit_log_format(ab, " cause="); + audit_log_string(ab, cause); +#endif + audit_log_format(ab, " rc=0x%x, ", rc); + audit_log_format(ab, " origin=0x%x", origin); + audit_log_end(ab); +} + +void five_audit_hexinfo(struct file *file, const char *msg, char *data, + size_t data_length) +{ + struct audit_buffer *ab; + struct inode *inode = NULL; + const unsigned char *fname = NULL; + char filename[NAME_MAX]; + char *pathbuf = NULL; + struct integrity_iint_cache *iint = NULL; + + if (file) { + fname = five_d_path(&file->f_path, &pathbuf, filename); + inode = file_inode(file); + } + + ab = audit_log_start(current->audit_context, GFP_KERNEL, + AUDIT_INTEGRITY_DATA); + if (unlikely(!ab)) { + pr_err("Can't get a context of audit logs\n"); + goto exit; + } + + if (fname) { + audit_log_format(ab, " name="); + audit_log_untrustedstring(ab, fname); + } + if (inode) { + audit_log_format(ab, " i_version=%llu ", + inode_query_iversion(inode)); + + iint = integrity_inode_get(inode); + if (iint) { + audit_log_format(ab, " cache_value=%lu ", + iint->five_flags); + audit_log_format(ab, " five_status=%d ", + five_get_cache_status(iint)); + audit_log_format(ab, " version=%llu ", + (unsigned long long)iint->version); + } + } + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)) + audit_log_format(ab, "%s", msg); +#else + audit_log_string(ab, msg); +#endif + audit_log_n_hex(ab, data, data_length); + audit_log_end(ab); +exit: + if (pathbuf) + __putname(pathbuf); +} diff --git a/security/samsung/five/five_audit.h b/security/samsung/five/five_audit.h new file mode 100644 index 000000000000..e0838399a305 --- /dev/null +++ b/security/samsung/five/five_audit.h @@ -0,0 +1,40 @@ +/* + * Audit calls for FIVE audit subsystem. + * + * Copyright (C) 2017 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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_FIVE_AUDIT_H +#define __LINUX_FIVE_AUDIT_H + +#include + +void five_audit_verbose(struct task_struct *task, struct file *file, + const char *op, enum task_integrity_value prev, + enum task_integrity_value tint, const char *cause, int result); +void five_audit_info(struct task_struct *task, struct file *file, + const char *op, enum task_integrity_value prev, + enum task_integrity_value tint, const char *cause, int result); +void five_audit_err(struct task_struct *task, struct file *file, + const char *op, enum task_integrity_value prev, + enum task_integrity_value tint, const char *cause, int result); +void five_audit_sign_err(struct task_struct *task, struct file *file, + const char *op, enum task_integrity_value prev, + enum task_integrity_value tint, const char *cause, int result); +void five_audit_tee_msg(const char *func, const char *cause, int rc, + uint32_t origin); + +void five_audit_hexinfo(struct file *file, + const char *msg, char *data, size_t data_length); + +#endif diff --git a/security/samsung/five/five_cache.c b/security/samsung/five/five_cache.c new file mode 100644 index 000000000000..60f6fe8127b4 --- /dev/null +++ b/security/samsung/five/five_cache.c @@ -0,0 +1,48 @@ +/* + * FIVE cache functions + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * + * Egor Uleyskiy, + * Viacheslav Vovchenko + * Yevgen Kopylov + * + * 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 "five_cache.h" +#include "five_porting.h" + +enum five_file_integrity five_get_cache_status( + const struct integrity_iint_cache *iint) +{ + if (unlikely(!iint)) + return FIVE_FILE_UNKNOWN; + + if (!inode_eq_iversion(iint->inode, iint->version)) + return FIVE_FILE_UNKNOWN; + + return iint->five_status; +} + +void five_set_cache_status(struct integrity_iint_cache *iint, + enum five_file_integrity status) +{ + if (unlikely(!iint)) + return; + + iint->version = inode_query_iversion(iint->inode); + iint->five_status = status; +} + +#if defined(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_GPL(five_get_cache_status); +EXPORT_SYMBOL_GPL(five_set_cache_status); +#endif diff --git a/security/samsung/five/five_cache.h b/security/samsung/five/five_cache.h new file mode 100644 index 000000000000..a40bfa019f96 --- /dev/null +++ b/security/samsung/five/five_cache.h @@ -0,0 +1,30 @@ +/* + * FIVE cache functions + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * + * Egor Uleyskiy, + * Viacheslav Vovchenko + * Yevgen Kopylov + * + * 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_FIVE_CACHE_H +#define __LINUX_FIVE_CACHE_H + +#include "security/integrity/integrity.h" + +enum five_file_integrity five_get_cache_status( + const struct integrity_iint_cache *iint); +void five_set_cache_status(struct integrity_iint_cache *iint, + enum five_file_integrity status); + +#endif // __LINUX_FIVE_CACHE_H diff --git a/security/samsung/five/five_cert.c b/security/samsung/five/five_cert.c new file mode 100644 index 000000000000..026e36b7a9b5 --- /dev/null +++ b/security/samsung/five/five_cert.c @@ -0,0 +1,225 @@ +/* + * 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. + * + * This code is originated from Samsung Electronics proprietary sources. + * Author: Viacheslav Vovchenko, + * Created: 10 Jul 2017 + * + * Copyright (C) 2016 Samsung Electronics, Inc. + */ + +#include +#include +#include "five_crypto.h" +#include "five_cert.h" + +int five_cert_body_alloc(struct five_cert_header *header, + uint8_t *hash, size_t hash_len, + uint8_t *label, size_t label_len, + uint8_t **raw_cert, size_t *raw_cert_len) +{ + size_t data_len; + uint8_t *data; + struct lv *next; + const void *end; + int rc = 0; + struct five_cert_body body_cert = {0}; + + if (unlikely(!header || !raw_cert || !raw_cert_len)) + return -EINVAL; + + data_len = sizeof(*body_cert.header) + sizeof(*header) + + sizeof(*body_cert.hash) + hash_len + + sizeof(*body_cert.label) + label_len; + + if (unlikely(data_len > FIVE_MAX_CERTIFICATE_SIZE)) + return -EINVAL; + + data = kzalloc(data_len, GFP_NOFS); + if (unlikely(!data)) + return -ENOMEM; + + next = body_cert.header = (struct lv *)data; + end = data + data_len; + + /* Fill header data */ + rc = lv_set(next, header, sizeof(*header), end); + if (unlikely(rc)) + goto exit; + next = body_cert.hash = lv_get_next(next, end); + if (unlikely(!next)) + goto exit; + + /* Fill hash data */ + rc = lv_set(next, hash, hash_len, end); + if (unlikely(rc)) + goto exit; + next = body_cert.label = lv_get_next(next, end); + if (unlikely(!next)) + goto exit; + + /* Fill label data */ + rc = lv_set(next, label, label_len, end); + if (unlikely(rc)) + goto exit; + +exit: + if (likely(!rc)) { + *raw_cert = data; + *raw_cert_len = data_len; + } else { + kfree(data); + } + + return rc; +} + +void five_cert_free(void *raw_cert) +{ + kfree(raw_cert); +} + +int five_cert_append_signature(void **raw_cert, size_t *raw_cert_len, + void *signature, size_t signature_len) +{ + int rc = 0; + void *new_raw_cert; + size_t new_raw_cert_len; + const void *end; + struct five_cert cert = { {0} }; + + if (unlikely(!raw_cert || !raw_cert_len || !signature)) + return -EINVAL; + + new_raw_cert_len = *raw_cert_len + sizeof(cert.signature->length) + + signature_len; + if (unlikely(new_raw_cert_len > FIVE_MAX_CERTIFICATE_SIZE)) + return -EINVAL; + + new_raw_cert = krealloc(*raw_cert, new_raw_cert_len, GFP_NOFS); + if (unlikely(!new_raw_cert)) + return -ENOMEM; + + *raw_cert = new_raw_cert; + *raw_cert_len = new_raw_cert_len; + end = (uint8_t *)new_raw_cert + new_raw_cert_len; + + rc = five_cert_fillout(&cert, new_raw_cert, new_raw_cert_len); + if (unlikely(rc)) + return rc; + + rc = lv_set(cert.signature, signature, signature_len, end); + + return rc; +} + +int five_cert_body_fillout(struct five_cert_body *body_cert, + const void *raw_cert, size_t raw_cert_len) +{ + struct lv *next; + const void *end; + struct five_cert_header *hdr; + + if (unlikely(!body_cert || !raw_cert || + raw_cert_len > FIVE_MAX_CERTIFICATE_SIZE)) + return -EINVAL; + + next = body_cert->header = (struct lv *)raw_cert; + end = (uint8_t *)raw_cert + raw_cert_len; + + /* Check if we had an error at the previous step */ + if (!lv_get_next(next, end)) + return -EINVAL; + + if (sizeof(*hdr) != body_cert->header->length) + return -EINVAL; + + hdr = (struct five_cert_header *)body_cert->header->value; + if (hdr->version != FIVE_CERT_VERSION1) + return -EINVAL; + + next = body_cert->hash = lv_get_next(next, end); + if (unlikely(!next)) + return -EINVAL; + + next = body_cert->label = lv_get_next(next, end); + if (unlikely(!next)) + return -EINVAL; + + return 0; +} + +int five_cert_fillout(struct five_cert *cert, const void *raw_cert, + size_t raw_cert_len) +{ + int rc; + struct lv *next; + const void *end; + + if (unlikely(!cert || !raw_cert)) + return -EINVAL; + + if (unlikely(raw_cert_len > FIVE_MAX_CERTIFICATE_SIZE)) + return -EINVAL; + + rc = five_cert_body_fillout(&cert->body, raw_cert, raw_cert_len); + if (unlikely(rc)) + return rc; + + next = cert->body.label; + end = (uint8_t *)raw_cert + raw_cert_len; + + if (!lv_get_next(next, end)) + return -EINVAL; + + next = cert->signature = lv_get_next(next, end); + if (unlikely(!next)) + return -EINVAL; + + return 0; +} + +int five_cert_calc_hash(struct five_cert_body *body_cert, uint8_t *out_hash, + size_t *out_hash_len) +{ + int rc; + size_t hash_len, body_len; + struct five_cert_header *header; + + if (unlikely(!body_cert || !out_hash || !out_hash_len)) + return -EINVAL; + + hash_len = *out_hash_len; + header = (struct five_cert_header *)body_cert->header->value; + body_len = sizeof(*body_cert->header) + body_cert->header->length + + sizeof(*body_cert->hash) + body_cert->hash->length + + sizeof(*body_cert->label) + body_cert->label->length; + + if (unlikely(body_len > FIVE_MAX_CERTIFICATE_SIZE)) + return -EINVAL; + + rc = five_calc_data_hash((const uint8_t *)body_cert->header, body_len, + header->hash_algo, out_hash, &hash_len); + if (unlikely(rc)) + return rc; + + *out_hash_len = hash_len; + + return 0; +} + +#if defined(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_GPL(five_cert_body_alloc); +EXPORT_SYMBOL_GPL(five_cert_free); +EXPORT_SYMBOL_GPL(five_cert_append_signature); +EXPORT_SYMBOL_GPL(five_cert_body_fillout); +EXPORT_SYMBOL_GPL(five_cert_fillout); +EXPORT_SYMBOL_GPL(five_cert_calc_hash); +#endif diff --git a/security/samsung/five/five_cert.h b/security/samsung/five/five_cert.h new file mode 100644 index 000000000000..e3b9e6845f6c --- /dev/null +++ b/security/samsung/five/five_cert.h @@ -0,0 +1,124 @@ +/* + * 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. + * + * This code is originated from Samsung Electronics proprietary sources. + * Author: Viacheslav Vovchenko, + * Created: 10 Jul 2017 + * + * Copyright (C) 2016 Samsung Electronics, Inc. + */ + +#ifndef __FIVE_CERT_H__ +#define __FIVE_CERT_H__ + +#include "five_lv.h" + +#define FIVE_MAX_DIGEST_SIZE 64 +#define FIVE_MAX_CERTIFICATE_SIZE 4096 + +enum five_signature_type { + FIVE_XATTR_NONE = 0x00, + FIVE_XATTR_DIGSIG = 0x01, + FIVE_XATTR_HMAC = 0x02, + FIVE_XATTR_END +}; + +enum five_version { + FIVE_CERT_VERSION1 = 1 +}; + +enum five_privilege { + FIVE_PRIV_DEFAULT = 0, + FIVE_PRIV_ALLOW_SIGN +}; + +struct five_cert_header { + uint8_t version; /* signature format version */ + uint8_t signature_type; /* signature type, e.g. HMAC, RSA */ + uint8_t privilege; /* privilege of file saved in certificate */ + uint8_t hash_algo; /* use hash algorithm for file and signature */ + uint32_t key_id; /* use for find key in keyrings */ +} __packed; + +/* + * --------------------------------------------- + * | Raw data of certificate FIVE in memory | + * --------------------------------------------- + * | u16 | header_len | + * | struct five_cert_header | header_value | + * | u16 | hash_len | + * | array | hash_value | + * | u16 | label_len | + * | array | label_value | + * | u16 | signature_len | + * | array | signature_value | + * --------------------------------------------- + */ +struct five_cert { + struct five_cert_body { + struct lv *header; + struct lv *hash; + struct lv *label; + } body; + struct lv *signature; +}; + +/** + * Allocates and fills raw certificate buffer without signature. + * Call five_cert_free() to release the allocated buffer. + * Returns 0 on success, otherwise - error. + */ +int five_cert_body_alloc(struct five_cert_header *header, + uint8_t *hash, size_t hash_len, + uint8_t *label, size_t label_len, + uint8_t **raw_cert, size_t *raw_cert_len); + +/** + * Releases raw certificate buffer allocated previously by five_cert_alloc(). + */ +void five_cert_free(void *raw_cert); + +/** + * Appends signature to raw certificate buffer. raw_cert memory will be + * re-allocated to contain the signature. + * Returns 0 on success, otherwise - error + */ +int five_cert_append_signature(void **raw_cert, size_t *raw_cert_len, + void *signature, size_t signature_len); + +/** + * Parse certificate raw data and fill items helper body_cert structure. + * Notice, body_cert object is reference to data of raw_cert. It will valid + * until raw_cert memory is valid. + * In case of raw_cert is changed we should be update body_cert again. + * Returns 0 on success, otherwise - error. + */ +int five_cert_body_fillout(struct five_cert_body *body_cert, + const void *raw_cert, size_t raw_cert_len); + +/** + * Parse certificate raw data and fill items helper cert structure. + * Notice, cert object is reference to data of raw_cert. It will valid until + * raw_cert memory is valid. + * In case of raw_cert is changed we should be update cert again. + * Returns 0 on success, otherwise - error. + */ +int five_cert_fillout(struct five_cert *cert, const void *raw_cert, + size_t raw_cert_len); + +/** + * Calculates hash of certificate, that contains header structure, + * hash and label. + * Returns 0 on success, otherwise - error. + */ +int five_cert_calc_hash(struct five_cert_body *body_cert, uint8_t *out_hash, + size_t *out_hash_len); + +#endif /* __FIVE_CERT_H__ */ diff --git a/security/samsung/five/five_cert_builtin.S b/security/samsung/five/five_cert_builtin.S new file mode 100644 index 000000000000..122db38133e9 --- /dev/null +++ b/security/samsung/five/five_cert_builtin.S @@ -0,0 +1,43 @@ +/* + * five_cert_builtin.S + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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 + + __INITRODATA + +.align 8 + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) +#define VMLINUX_SYMBOL(name) name +#endif + +#define GLOBAL(name) \ + .globl VMLINUX_SYMBOL(name); \ + VMLINUX_SYMBOL(name): + +#ifdef CONFIG_FIVE_CERT_ENG +GLOBAL(five_local_ca_start_eng) + .incbin CONFIG_FIVE_CERT_ENG +GLOBAL(five_local_ca_end_eng) +#endif + +#ifdef CONFIG_FIVE_CERT_USER +GLOBAL(five_local_ca_start_user) + .incbin CONFIG_FIVE_CERT_USER +GLOBAL(five_local_ca_end_user) +#endif diff --git a/security/samsung/five/five_crypto.c b/security/samsung/five/five_crypto.c new file mode 100644 index 000000000000..795be8e6ce2e --- /dev/null +++ b/security/samsung/five/five_crypto.c @@ -0,0 +1,550 @@ +/* + * This code is based on IMA's code + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * + * Egor Uleyskiy, + * Viacheslav Vovchenko + * Yevgen Kopylov + * + * 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "five.h" +#include "five_crypto_comp.h" +#include "five_porting.h" +#include "security/integrity/integrity.h" +#include "five_testing.h" + +struct ahash_completion { + struct completion completion; + int err; +}; + +/* minimum file size for ahash use */ +static unsigned long five_ahash_minsize; +module_param_named(ahash_minsize, five_ahash_minsize, ulong, 0644); +MODULE_PARM_DESC(ahash_minsize, "Minimum file size for ahash use"); + +/* default is 0 - 1 page. */ +static int five_maxorder; +static unsigned long five_bufsize = PAGE_SIZE; + +__visible_for_testing __mockable struct crypto_shash *call_crypto_alloc_shash( + const char *alg_name, u32 type, u32 mask) +{ + return crypto_alloc_shash(alg_name, type, mask); +} + +static int param_set_bufsize(const char *val, const struct kernel_param *kp) +{ + unsigned long long size; + int order; + + size = memparse(val, NULL); + order = get_order(size); + if (order >= MAX_ORDER) + return -EINVAL; + five_maxorder = order; + five_bufsize = PAGE_SIZE << order; + return 0; +} + +static const struct kernel_param_ops param_ops_bufsize = { + .set = param_set_bufsize, + .get = param_get_uint, +}; + +#define param_check_bufsize(name, p) __param_check(name, p, unsigned int) + +module_param_named(ahash_bufsize, five_bufsize, ulong, 0644); +MODULE_PARM_DESC(ahash_bufsize, "Maximum ahash buffer size"); + +static struct crypto_shash *five_shash_tfm; +static struct crypto_ahash *five_ahash_tfm; + +int __init five_init_crypto(void) +{ + long rc; + + five_shash_tfm = call_crypto_alloc_shash( + hash_algo_name[five_hash_algo], 0, 0); + if (IS_ERR(five_shash_tfm)) { + rc = PTR_ERR(five_shash_tfm); + pr_err("Can not allocate %s (reason: %ld)\n", + hash_algo_name[five_hash_algo], rc); + return rc; + } + return 0; +} + +static struct crypto_shash *five_alloc_tfm(enum hash_algo algo) +{ + struct crypto_shash *tfm = five_shash_tfm; + int rc; + + if (algo < 0 || algo >= HASH_ALGO__LAST) + algo = five_hash_algo; + + if (algo != five_hash_algo) { + tfm = crypto_alloc_shash(hash_algo_name[algo], 0, 0); + if (IS_ERR(tfm)) { + rc = PTR_ERR(tfm); + pr_err("Can not allocate %s (reason: %d)\n", + hash_algo_name[algo], rc); + } + } + return tfm; +} + +static void five_free_tfm(struct crypto_shash *tfm) +{ + if (tfm != five_shash_tfm) + crypto_free_shash(tfm); +} + +/** + * five_alloc_pages() - Allocate contiguous pages. + * @max_size: Maximum amount of memory to allocate. + * @allocated_size: Returned size of actual allocation. + * @last_warn: Should the min_size allocation warn or not. + * + * Tries to do opportunistic allocation for memory first trying to allocate + * max_size amount of memory and then splitting that until zero order is + * reached. Allocation is tried without generating allocation warnings unless + * last_warn is set. Last_warn set affects only last allocation of zero order. + * + * By default, five_maxorder is 0 and it is equivalent to kmalloc(GFP_KERNEL) + * + * Return pointer to allocated memory, or NULL on failure. + */ +static void *five_alloc_pages(loff_t max_size, size_t *allocated_size, + int last_warn) +{ + void *ptr; + int order = five_maxorder; + gfp_t gfp_mask = __GFP_RECLAIM | __GFP_NOWARN | __GFP_NORETRY; + + if (order) + order = min(get_order(max_size), order); + + for (; order; order--) { + ptr = (void *)__get_free_pages(gfp_mask, order); + if (ptr) { + *allocated_size = PAGE_SIZE << order; + return ptr; + } + } + + /* order is zero - one page */ + + gfp_mask = GFP_KERNEL; + + if (!last_warn) + gfp_mask |= __GFP_NOWARN; + + ptr = (void *)__get_free_pages(gfp_mask, 0); + if (ptr) { + *allocated_size = PAGE_SIZE; + return ptr; + } + + *allocated_size = 0; + return NULL; +} + +/** + * five_free_pages() - Free pages allocated by five_alloc_pages(). + * @ptr: Pointer to allocated pages. + * @size: Size of allocated buffer. + */ +static void five_free_pages(void *ptr, size_t size) +{ + if (!ptr) + return; + free_pages((unsigned long)ptr, get_order(size)); +} + +static struct crypto_ahash *five_alloc_atfm(enum hash_algo algo) +{ + struct crypto_ahash *tfm = five_ahash_tfm; + int rc; + + if (algo < 0 || algo >= HASH_ALGO__LAST) + algo = five_hash_algo; + + if (algo != five_hash_algo || !tfm) { + tfm = crypto_alloc_ahash(hash_algo_name[algo], 0, 0); + if (!IS_ERR(tfm)) { + if (algo == five_hash_algo) + five_ahash_tfm = tfm; + } else { + rc = PTR_ERR(tfm); + pr_err("Can not allocate %s (reason: %d)\n", + hash_algo_name[algo], rc); + } + } + return tfm; +} + +static void five_free_atfm(struct crypto_ahash *tfm) +{ + if (tfm != five_ahash_tfm) + crypto_free_ahash(tfm); +} + +static void ahash_complete(struct crypto_async_request *req, int err) +{ + struct ahash_completion *res = req->data; + + if (err == -EINPROGRESS) + return; + res->err = err; + complete(&res->completion); +} + +static int ahash_wait(int err, struct ahash_completion *res) +{ + try_to_freeze(); + + switch (err) { + case 0: + break; + case -EINPROGRESS: + case -EBUSY: + wait_for_completion(&res->completion); + reinit_completion(&res->completion); + err = res->err; + fallthrough; + default: + pr_crit_ratelimited("ahash calculation failed: err: %d\n", err); + } + + return err; +} + +static int five_calc_file_hash_atfm(struct file *file, + u8 *hash, size_t *hash_len, + struct crypto_ahash *tfm) +{ + const size_t len = crypto_ahash_digestsize(tfm); + loff_t i_size, offset; + char *rbuf[2] = { NULL, }; + int rc, read = 0, rbuf_len, active = 0, ahash_rc = 0; + struct ahash_request *req; + struct scatterlist sg[1]; + struct ahash_completion res; + size_t rbuf_size[2]; + + if (*hash_len < len) + return -EINVAL; + + req = ahash_request_alloc(tfm, GFP_KERNEL); + if (!req) + return -ENOMEM; + + init_completion(&res.completion); + ahash_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG | + CRYPTO_TFM_REQ_MAY_SLEEP, + ahash_complete, &res); + + rc = ahash_wait(crypto_ahash_init(req), &res); + if (rc) + goto out1; + + i_size = i_size_read(file_inode(file)); + + if (i_size == 0) + goto out2; + + /* + * Try to allocate maximum size of memory. + * Fail if even a single page cannot be allocated. + */ + rbuf[0] = five_alloc_pages(i_size, &rbuf_size[0], 1); + if (!rbuf[0]) { + rc = -ENOMEM; + goto out1; + } + + /* Only allocate one buffer if that is enough. */ + if (i_size > rbuf_size[0]) { + /* + * Try to allocate secondary buffer. If that fails fallback to + * using single buffering. Use previous memory allocation size + * as baseline for possible allocation size. + */ + rbuf[1] = five_alloc_pages(i_size - rbuf_size[0], + &rbuf_size[1], 0); + } + + if (!(file->f_mode & FMODE_READ)) { + file->f_mode |= FMODE_READ; + read = 1; + } + + for (offset = 0; offset < i_size; offset += rbuf_len) { + if (!rbuf[1] && offset) { + /* Not using two buffers, and it is not the first + * read/request, wait for the completion of the + * previous ahash_update() request. + */ + rc = ahash_wait(ahash_rc, &res); + if (rc) + goto out3; + } + /* read buffer */ + rbuf_len = min_t(loff_t, i_size - offset, rbuf_size[active]); + rc = integrity_kernel_read(file, offset, rbuf[active], + rbuf_len); + if (rc != rbuf_len) + goto out3; + + if (rbuf[1] && offset) { + /* Using two buffers, and it is not the first + * read/request, wait for the completion of the + * previous ahash_update() request. + */ + rc = ahash_wait(ahash_rc, &res); + if (rc) + goto out3; + } + + sg_init_one(&sg[0], rbuf[active], rbuf_len); + ahash_request_set_crypt(req, sg, NULL, rbuf_len); + + ahash_rc = crypto_ahash_update(req); + + if (rbuf[1]) + active = !active; /* swap buffers, if we use two */ + } + /* wait for the last update request to complete */ + rc = ahash_wait(ahash_rc, &res); +out3: + if (read) + file->f_mode &= ~FMODE_READ; + five_free_pages(rbuf[0], rbuf_size[0]); + five_free_pages(rbuf[1], rbuf_size[1]); +out2: + if (!rc) { + ahash_request_set_crypt(req, NULL, hash, 0); + rc = ahash_wait(crypto_ahash_final(req), &res); + if (!rc) + *hash_len = len; + } +out1: + ahash_request_free(req); + return rc; +} + +static int five_calc_file_ahash(struct file *file, + u8 hash_algo, u8 *hash, + size_t *hash_len) +{ + struct crypto_ahash *tfm; + int rc; + + tfm = five_alloc_atfm(hash_algo); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + + rc = five_calc_file_hash_atfm(file, hash, hash_len, tfm); + + five_free_atfm(tfm); + + return rc; +} + +static int five_calc_file_hash_tfm(struct file *file, + u8 *hash, size_t *hash_len, + struct crypto_shash *tfm) +{ + SHASH_DESC_ON_STACK(shash, tfm); + const size_t len = crypto_shash_digestsize(tfm); + loff_t i_size, offset = 0; + char *rbuf; + int rc, read = 0; + + if (*hash_len < len) + return -EINVAL; + + shash->tfm = tfm; + #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0) + shash->flags = 0; + #endif + + rc = crypto_shash_init(shash); + if (rc != 0) + return rc; + + i_size = i_size_read(file_inode(file)); + + if (i_size == 0) + goto out; + + rbuf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!rbuf) + return -ENOMEM; + + if (!(file->f_mode & FMODE_READ)) { + file->f_mode |= FMODE_READ; + read = 1; + } + + while (offset < i_size) { + int rbuf_len; + + rbuf_len = integrity_kernel_read(file, offset, rbuf, PAGE_SIZE); + if (rbuf_len < 0) { + rc = rbuf_len; + break; + } + if (rbuf_len == 0) + break; + offset += rbuf_len; + + try_to_freeze(); + + rc = crypto_shash_update(shash, rbuf, rbuf_len); + if (rc) + break; + } + if (read) + file->f_mode &= ~FMODE_READ; + kfree(rbuf); +out: + if (!rc) + rc = crypto_shash_final(shash, hash); + + if (!rc) + *hash_len = len; + + return rc; +} + +static int five_calc_hash_tfm(const u8 *data, size_t data_len, + u8 *hash, size_t *hash_len, struct crypto_shash *tfm) +{ + SHASH_DESC_ON_STACK(shash, tfm); + const size_t len = crypto_shash_digestsize(tfm); + int rc; + + if (*hash_len < len || data_len == 0) + return -EINVAL; + + shash->tfm = tfm; + #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0) + shash->flags = 0; + #endif + + rc = crypto_shash_init(shash); + if (rc != 0) + return rc; + + rc = crypto_shash_update(shash, data, data_len); + if (!rc) { + rc = crypto_shash_final(shash, hash); + + if (!rc) + *hash_len = len; + } + + return rc; +} + +static int five_calc_file_shash(struct file *file, + u8 hash_algo, + u8 *hash, + size_t *hash_len) +{ + struct crypto_shash *tfm; + int rc; + + tfm = five_alloc_tfm(hash_algo); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + + rc = five_calc_file_hash_tfm(file, hash, hash_len, tfm); + + five_free_tfm(tfm); + + return rc; +} + +static int five_calc_data_shash(const u8 *data, size_t data_len, u8 hash_algo, + u8 *hash, size_t *hash_len) +{ + struct crypto_shash *tfm; + int rc; + + tfm = five_alloc_tfm(hash_algo); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + + rc = five_calc_hash_tfm(data, data_len, hash, hash_len, tfm); + + five_free_tfm(tfm); + + return rc; +} + +/* + * five_calc_file_hash - calculate file hash + * + * Asynchronous hash (ahash) allows using HW acceleration for calculating + * a hash. ahash performance varies for different data sizes on different + * crypto accelerators. shash performance might be better for smaller files. + * The 'five.ahash_minsize' module parameter allows specifying the best + * minimum file size for using ahash on the system. + * + * If the five.ahash_minsize parameter is not specified, this function uses + * shash for the hash calculation. If ahash fails, it falls back to using + * shash. + */ +int five_calc_file_hash(struct file *file, u8 hash_algo, u8 *hash, + size_t *hash_len) +{ + loff_t i_size; + int rc; + + i_size = i_size_read(file_inode(file)); + + if (five_ahash_minsize && i_size >= five_ahash_minsize) { + rc = five_calc_file_ahash(file, hash_algo, hash, hash_len); + if (!rc) + return 0; + } + + return five_calc_file_shash(file, hash_algo, hash, hash_len); +} + +int five_calc_data_hash(const uint8_t *data, size_t data_len, + uint8_t hash_algo, uint8_t *hash, size_t *hash_len) +{ + return five_calc_data_shash(data, data_len, hash_algo, hash, hash_len); +} + +#if defined(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_GPL(five_calc_file_hash); +EXPORT_SYMBOL_GPL(five_calc_data_hash); +#endif diff --git a/security/samsung/five/five_crypto.h b/security/samsung/five/five_crypto.h new file mode 100644 index 000000000000..1f675a3b058e --- /dev/null +++ b/security/samsung/five/five_crypto.h @@ -0,0 +1,29 @@ +/* + * FIVE crypto API + * + * Copyright (C) 2017 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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_FIVE_CRYPTO_H +#define __LINUX_FIVE_CRYPTO_H + +#include + +int five_init_crypto(void); +int five_calc_file_hash(struct file *file, u8 hash_algo, u8 *hash, + size_t *hash_len); + +int five_calc_data_hash(const uint8_t *data, size_t data_len, + uint8_t hash_algo, uint8_t *hash, size_t *hash_len); + +#endif // __LINUX_FIVE_CRYPTO_H diff --git a/security/samsung/five/five_crypto_comp.h b/security/samsung/five/five_crypto_comp.h new file mode 100644 index 000000000000..c93e65220cd4 --- /dev/null +++ b/security/samsung/five/five_crypto_comp.h @@ -0,0 +1,104 @@ +/* + * Wrappers for backward compatibility with old Kernels. + * + * Copyright (C) 2019 Samsung Electronics, Inc. + * + * Egor Uleyskiy, + * + * 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_FIVE_CRYPTO_COMP_H +#define __LINUX_FIVE_CRYPTO_COMP_H + +#include +#include + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 6, 0) +static inline int five_verify_signature(struct key *key, + struct public_key_signature *pks, + struct five_cert *cert, + struct five_cert_header *header) +{ + int ret = -ENOMEM; + + pks->pkey_hash_algo = header->hash_algo; + pks->nr_mpi = 1; + pks->rsa.s = mpi_read_raw_data(cert->signature->value, + cert->signature->length); + + if (pks->rsa.s) + ret = verify_signature(key, pks); + + mpi_free(pks->rsa.s); + + return ret; +} +#elif LINUX_VERSION_CODE < KERNEL_VERSION(4, 20, 0) +static inline int five_verify_signature(struct key *key, + struct public_key_signature *pks, + struct five_cert *cert, + struct five_cert_header *header) +{ + int ret = -ENOMEM; + + pks->pkey_algo = "rsa"; + pks->hash_algo = hash_algo_name[header->hash_algo]; + pks->s = cert->signature->value; + pks->s_size = cert->signature->length; + + ret = verify_signature(key, pks); + + return ret; +} +#else +static inline int five_verify_signature(struct key *key, + struct public_key_signature *pks, + struct five_cert *cert, + struct five_cert_header *header) +{ + int ret = -ENOMEM; + + pks->pkey_algo = "rsa"; + pks->encoding = "pkcs1"; + pks->hash_algo = hash_algo_name[header->hash_algo]; + pks->s = cert->signature->value; + pks->s_size = cert->signature->length; + + ret = verify_signature(key, pks); + + return ret; +} +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0) +static inline struct key *five_keyring_alloc(const char *description, + kuid_t uid, kgid_t gid, const struct cred *cred, + key_perm_t perm, unsigned long flags) +{ + return keyring_alloc(description, uid, gid, cred, + perm, flags, NULL); +} +#else +static inline struct key *five_keyring_alloc(const char *description, + kuid_t uid, kgid_t gid, const struct cred *cred, + key_perm_t perm, unsigned long flags) +{ + return keyring_alloc(description, uid, gid, cred, + perm, flags, NULL, NULL); +} +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 3, 0) +#define keyring_search(keyring, type, desc, recurse) \ + keyring_search(keyring, type, desc) +#endif + +#endif /* __LINUX_FIVE_CRYPTO_COMP_H */ diff --git a/security/samsung/five/five_dmverity.c b/security/samsung/five/five_dmverity.c new file mode 100644 index 000000000000..367cd3ab1fd2 --- /dev/null +++ b/security/samsung/five/five_dmverity.c @@ -0,0 +1,432 @@ +/* + * FIVE dmverity functions + * + * Copyright (C) 2019 Samsung Electronics, Inc. + * + * Egor Uleyskiy, + * Viacheslav Vovchenko + * Yevgen Kopylov + * + * 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 "five_dmverity.h" +#include "five.h" +#include "five_testing.h" +#include "five_porting.h" + +#include "drivers/md/dm.h" + +#ifdef CONFIG_FIVE_DEBUG +#include +#endif + +#if !defined(CONFIG_SAMSUNG_PRODUCT_SHIP) || defined(CONFIG_FIVE_DEBUG) +__visible_for_testing +bool check_prebuilt_paths_dmverity; +#endif + +static inline int __init init_fs(void); + +__visible_for_testing __mockable +struct mapped_device *call_dm_get_md(dev_t dev) +{ + return dm_get_md(dev); +} + +__visible_for_testing __mockable +struct dm_table *call_dm_get_live_table( + struct mapped_device *md, int *srcu_idx) +{ + return dm_get_live_table(md, srcu_idx); +} + +__visible_for_testing __mockable +fmode_t call_dm_table_get_mode(struct dm_table *t) +{ + return dm_table_get_mode(t); +} + +__visible_for_testing __mockable +unsigned int call_dm_table_get_num_targets(struct dm_table *t) +{ + return dm_table_get_num_targets(t); +} + +__visible_for_testing __mockable +struct dm_target *call_dm_table_get_target( + struct dm_table *t, unsigned int index) +{ + return dm_table_get_target(t, index); +} + +__visible_for_testing __mockable +void call_dm_put_live_table(struct mapped_device *md, int srcu_idx) +{ + dm_put_live_table(md, srcu_idx); + return; +} + +__visible_for_testing __mockable +void call_dm_put(struct mapped_device *md) +{ + dm_put(md); + return; +} + +__visible_for_testing __mockable +struct block_device *call_blkdev_get_by_dev( + dev_t dev, fmode_t mode, void *holder) +{ + return blkdev_get_by_dev(dev, mode, holder); +} + +__visible_for_testing __mockable +void call_blkdev_put(struct block_device *bdev, fmode_t mode) +{ + blkdev_put(bdev, mode); + return; +} + +int __init five_init_dmverity(void) +{ + return init_fs(); +} + +__visible_for_testing +bool is_loop_device(const struct file *file) +{ + const struct inode *inode; + + inode = file_inode(file); + if (unlikely(!inode || !inode->i_sb)) + return false; + + return MAJOR(inode->i_sb->s_dev) == LOOP_MAJOR ? true : false; +} + +/* Check whether the file belongs to the device mapper's enabled target + * Need to check: + * - Super block points to a block device. + * - The block device is created by the device mapper + * (dm_get_md/dm_put_md functions). + * - The block device is a disk with a name "dm-[1-9]". + * - The disk is in readonly mode. + * - There is only one enabled target in the device mapper table. + * - The target name is known target name. + */ +__visible_for_testing +enum five_dmverity_codes is_dmverity_partition( + const struct file *file) +{ + const char dm_dev_prefix[] = "dm-"; + const char * const dm_targets_name[] = { + "verity", "verity-fec" + }; + enum five_dmverity_codes result = FIVE_DMV_NO_DM_TARGET_NAME; + int srcu_idx; + unsigned int num_targets; + size_t i; + const struct inode *inode; + struct super_block *i_sb; + struct mapped_device *md; + struct gendisk *disk; + struct dm_table *table; + struct dm_target *target; + + inode = file_inode(file); + if (unlikely(!inode)) { + result = FIVE_DMV_BAD_INPUT; + goto exit; + } + + i_sb = inode->i_sb; + if (unlikely(!i_sb)) { + result = FIVE_DMV_BAD_INPUT; + goto exit; + } + + md = call_dm_get_md(i_sb->s_dev); + if (!md) { + result = FIVE_DMV_NO_DM_DEVICE; + goto exit; + } + + disk = dm_disk(md); + if (!disk) { + result = FIVE_DMV_NO_DM_DISK; + goto exit_free_dm; + } + + if (!get_disk_ro(disk)) { + result = FIVE_DMV_NOT_READONLY_DM_DISK; + goto exit_free_dm; + } + + if (strncmp(disk->disk_name, dm_dev_prefix, + sizeof(dm_dev_prefix) - 1)) { + result = FIVE_DMV_BAD_DM_PREFIX_NAME; + goto exit_free_dm; + } + + table = call_dm_get_live_table(md, &srcu_idx); + if (!table) { + result = FIVE_DMV_NO_DM_TABLE; + goto exit_free_dm; + } + + if (call_dm_table_get_mode(table) & ~FMODE_READ) { + result = FIVE_DMV_NOT_READONLY_DM_TABLE; + goto exit_free_table; + } + + num_targets = call_dm_table_get_num_targets(table); + target = call_dm_table_get_target(table, 0); + if (!target) { + result = FIVE_DMV_NO_DM_TARGET; + goto exit_free_table; + } + + /* Only support devices that have a single target */ + if (num_targets != 1) { + result = FIVE_DMV_NOT_SINGLE_TARGET; + goto exit_free_table; + } + + /* Checks the specific target name from available targets + * in device mapper. + */ + for (i = 0; i < ARRAY_SIZE(dm_targets_name); ++i) { + if (!strncmp(target->type->name, dm_targets_name[i], + strlen(dm_targets_name[i]) + 1)) { + result = FIVE_DMV_PARTITION; + break; + } + } + +exit_free_table: + call_dm_put_live_table(md, srcu_idx); +exit_free_dm: + call_dm_put(md); +exit: + return result; +} + +__visible_for_testing +enum five_dmverity_codes is_dmverity_loop( + const struct file *file) +{ + const fmode_t mode_bdev = FMODE_READ; + enum five_dmverity_codes result = FIVE_DMV_NO_SB_LOOP_DEVICE; + struct super_block *i_sb; + const struct inode *inode; + struct block_device *bdev; + struct gendisk *bd_disk; + struct file *file_mount_to_lo_dev; + struct loop_device *lo_dev; + + inode = file_inode(file); + i_sb = inode->i_sb; + + if (MAJOR(i_sb->s_dev) != LOOP_MAJOR) + goto exit; + + bdev = call_blkdev_get_by_dev(i_sb->s_dev, mode_bdev, NULL); + if (IS_ERR_OR_NULL(bdev) || MAJOR(bdev->bd_dev) != LOOP_MAJOR) { + result = FIVE_DMV_NO_BD_LOOP_DEVICE; + goto exit; + } + + bd_disk = bdev->bd_disk; + if (unlikely(!bd_disk)) { + result = FIVE_DMV_NO_BD_DISK; + goto exit_free_blkdev; + } + + lo_dev = bd_disk->private_data; + if (unlikely(!lo_dev)) { + result = FIVE_DMV_NO_LOOP_DEV; + goto exit_free_blkdev; + } + + file_mount_to_lo_dev = lo_dev->lo_backing_file; + if (unlikely(!file_mount_to_lo_dev)) { + result = FIVE_DMV_NO_LOOP_BACK_FILE; + goto exit_free_blkdev; + } + + result = is_dmverity_partition(file_mount_to_lo_dev); + +exit_free_blkdev: + call_blkdev_put(bdev, mode_bdev); +exit: + return result; +} + +#if defined(CONFIG_SAMSUNG_PRODUCT_SHIP) && !defined(CONFIG_FIVE_DEBUG) +bool five_is_dmverity_protected(const struct file *file) +{ + enum five_dmverity_codes dmv_file_status; + bool result = false; + + if (unlikely(!file)) + return result; + + /* Checks is loop device /dev/block/loopX or no */ + if (is_loop_device(file)) + /* Checks /dev/block/loopX mounted to /apex/name@version and + * it should specify to /system/apex/[name].apex under dmverity + * protection. + */ + dmv_file_status = is_dmverity_loop(file); + else + /* Checks file on dmverity partition, in case of dmverity is + * disabled need to check prebuilt paths of dmverity. + */ + dmv_file_status = is_dmverity_partition(file); + + if (dmv_file_status == FIVE_DMV_PARTITION) + result = true; + + return result; +} +#else +static bool is_dmverity_prebuit_path(const struct file *file) +{ + const char * const paths[] = { + "/system/", "/system_ext/", "/vendor/", "/apex/", + "/product/", "/odm/", "/prism/", "/optics/" + }; + const char *pathname = NULL; + char *pathbuf = NULL; + char filename[NAME_MAX]; + bool result = false; + size_t i; + + pathname = five_d_path(&file->f_path, &pathbuf, filename); + + for (i = 0; i < ARRAY_SIZE(paths); ++i) { + if (!strncmp(pathname, paths[i], strlen(paths[i]))) { + result = true; + break; + } + } + + if (pathbuf) + __putname(pathbuf); + + return result; +} + +bool five_is_dmverity_protected(const struct file *file) +{ + enum five_dmverity_codes dmv_file_status; + bool result = false; + + if (unlikely(!file)) + return result; + + if (check_prebuilt_paths_dmverity) + return is_dmverity_prebuit_path(file); + + /* Checks is loop device /dev/block/loopX or no */ + if (is_loop_device(file)) + /* Checks /dev/block/loopX mounted to /apex/name@version/ and + * it should specify to /system/apex/[name].apex under dmverity + * protection. + */ + dmv_file_status = is_dmverity_loop(file); + else + /* Checks file on dmverity partition, in case of dmverity is + * disabled need to check prebuilt paths dmverity. + */ + dmv_file_status = is_dmverity_partition(file); + + if (dmv_file_status == FIVE_DMV_PARTITION) + result = true; + else + result = is_dmverity_prebuit_path(file); + + return result; +} +#endif + +#ifdef CONFIG_FIVE_DEBUG +static ssize_t five_check_prebuilt_paths_dmverity_read(struct file *file, + char __user *user_buf, size_t count, loff_t *pos) +{ + char buf[2]; + + buf[0] = check_prebuilt_paths_dmverity ? '1' : '0'; + buf[1] = '\n'; + + return simple_read_from_buffer(user_buf, count, pos, buf, sizeof(buf)); +} + +static ssize_t five_check_prebuilt_paths_dmverity_write(struct file *file, + const char __user *buf, size_t count, loff_t *pos) +{ + char command; + + if (get_user(command, buf)) + return -EFAULT; + + switch (command) { + case '0': + check_prebuilt_paths_dmverity = false; + break; + case '1': + check_prebuilt_paths_dmverity = true; + break; + default: + pr_err("FIVE: %s: unknown cmd: %hhx\n", __func__, command); + return -EINVAL; + } + + pr_info("FIVE debug: Check dm-verity file paths %s\n", + check_prebuilt_paths_dmverity ? "enabled" : "disabled"); + return count; +} + +static const struct file_operations five_dmverity_fops = { + .owner = THIS_MODULE, + .read = five_check_prebuilt_paths_dmverity_read, + .write = five_check_prebuilt_paths_dmverity_write +}; + +static inline int __init init_fs(void) +{ + struct dentry *debug_file = NULL; + const umode_t umode = 0664; + + debug_file = debugfs_create_file("five_prebuilt_paths_dmverity", + umode, NULL, NULL, &five_dmverity_fops); + if (IS_ERR_OR_NULL(debug_file)) + goto error; + + return 0; +error: + if (debug_file) + return -PTR_ERR(debug_file); + + return -EEXIST; +} +#else +static inline int __init init_fs(void) +{ + return 0; +} +#endif + +#if defined(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_GPL(is_loop_device); +EXPORT_SYMBOL_GPL(is_dmverity_partition); +EXPORT_SYMBOL_GPL(five_is_dmverity_protected); +#endif diff --git a/security/samsung/five/five_dmverity.h b/security/samsung/five/five_dmverity.h new file mode 100644 index 000000000000..93c625403dbb --- /dev/null +++ b/security/samsung/five/five_dmverity.h @@ -0,0 +1,49 @@ +/* + * FIVE dmverity functions + * + * Copyright (C) 2019 Samsung Electronics, Inc. + * + * Egor Uleyskiy, + * Viacheslav Vovchenko + * Yevgen Kopylov + * + * 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_FIVE_DMVERITY_H +#define __LINUX_FIVE_DMVERITY_H + +#include + +struct file; + +enum five_dmverity_codes { + FIVE_DMV_PARTITION, + FIVE_DMV_BAD_INPUT, + FIVE_DMV_NO_DM_DEVICE, + FIVE_DMV_NO_DM_DISK, + FIVE_DMV_NOT_READONLY_DM_DISK, + FIVE_DMV_BAD_DM_PREFIX_NAME, + FIVE_DMV_NO_DM_TABLE, + FIVE_DMV_NOT_READONLY_DM_TABLE, + FIVE_DMV_NO_DM_TARGET, + FIVE_DMV_NOT_SINGLE_TARGET, + FIVE_DMV_NO_DM_TARGET_NAME, + FIVE_DMV_NO_SB_LOOP_DEVICE, + FIVE_DMV_NO_BD_LOOP_DEVICE, + FIVE_DMV_NO_BD_DISK, + FIVE_DMV_NO_LOOP_DEV, + FIVE_DMV_NO_LOOP_BACK_FILE +}; + +int __init five_init_dmverity(void); +bool five_is_dmverity_protected(const struct file *file); + +#endif // __LINUX_FIVE_DMVERITY_H diff --git a/security/samsung/five/five_dsms.c b/security/samsung/five/five_dsms.c new file mode 100644 index 000000000000..1d022501b82b --- /dev/null +++ b/security/samsung/five/five_dsms.c @@ -0,0 +1,183 @@ +/* + * FIVE-DSMS integration. + * + * Copyright (C) 2020 Samsung Electronics, Inc. + * Yevgen Kopylov, + * + * 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 "five.h" + +#include "five_testing.h" +#ifndef FIVE_KUNIT_ENABLED +#include +#include "five_dsms.h" +#endif + +#define MESSAGE_BUFFER_SIZE 600 +#define MESSAGE_FIVE_INIT_SIZE 32 +#define DEFERRED_TIME (1000 * 300) +#define MAX_FIV1_NUM 16 +#define MAX_FIV2_NUM 96 + +#define FIV3_FIRST 1 +#define FIV3_FEW 20 +#define FIV3_LOT 100 + +struct sign_err_event { + char comm[TASK_COMM_LEN]; + int result; + int count; +}; + +static struct five_dsms_report_context { + struct delayed_work work; + char message[MESSAGE_FIVE_INIT_SIZE]; +} context; + +static DEFINE_SPINLOCK(five_dsms_lock); +static struct sign_err_event sign_err_events[MAX_FIV1_NUM]; +static bool fiv3_overflow; +static DECLARE_BITMAP(mask, MAX_FIV2_NUM); +static bool oem_unlocking_state __ro_after_init; + +static int __init verifiedboot_state_setup(char *str) +{ + static const char unlocked[] = "orange"; + + oem_unlocking_state = !strncmp(str, unlocked, sizeof(unlocked)); + + if (oem_unlocking_state) + pr_err("FIVE: Device is unlocked\n"); + + return 0; +} + +__setup("androidboot.verifiedbootstate=", verifiedboot_state_setup); + +// proxy to call kernel func +u16 __mockable call_crc16(u16 crc, u8 const *buffer, size_t len) +{ + return crc16(crc, buffer, len); +} + +/*`noinline` is required by DSMS. + * Don't rename this function. File_name/function_name + * is used by DSMS in dsms_whitelist as an access rule. + */ +noinline void __mockable five_dsms_msg(const char *tag, const char *msg) +{ + int ret; + + ret = dsms_send_message(tag, msg, 0); + if (ret) + pr_err("FIVE: unable to send dsms message(result:%d)\n", ret); +} + +static void five_dsms_init_report(struct work_struct *in_data) +{ + struct five_dsms_report_context *context = container_of(in_data, + struct five_dsms_report_context, work.work); + + five_dsms_msg("FIV0", context->message); +} + +void five_dsms_sign_err(const char *app, int result) +{ + int i, current_count; + struct sign_err_event *same_event = NULL; + char dsms_msg[MESSAGE_BUFFER_SIZE]; + bool send_overflow = false; + + spin_lock(&five_dsms_lock); + for (i = 0; i < MAX_FIV1_NUM; i++) { + if (sign_err_events[i].count == 0) { + same_event = &sign_err_events[i]; + current_count = ++same_event->count; + strlcpy(same_event->comm, app, TASK_COMM_LEN); + same_event->result = result; + break; + } else if (sign_err_events[i].result == result && + !strncmp(sign_err_events[i].comm, app, TASK_COMM_LEN)) { + same_event = &sign_err_events[i]; + current_count = ++same_event->count; + break; + } + } + if (!same_event && !fiv3_overflow) { + fiv3_overflow = true; + send_overflow = true; + } + spin_unlock(&five_dsms_lock); + + if (same_event) { + switch (current_count) { + case FIV3_FIRST: + case FIV3_FEW: + case FIV3_LOT: + snprintf(dsms_msg, MESSAGE_BUFFER_SIZE, + "%s res = %d count = %d", same_event->comm, + same_event->result, current_count); + five_dsms_msg("FIV3", dsms_msg); + } + } else if (unlikely(send_overflow)) { + five_dsms_msg("FIV3", "data buffer overflow"); + } +} + +void five_dsms_reset_integrity(const char *task_name, int result, + const char *file_name) +{ + char dsms_msg[MESSAGE_BUFFER_SIZE]; + unsigned short crc; + int msg_size; + bool sent = true; + + if (oem_unlocking_state) + return; + + msg_size = snprintf(dsms_msg, MESSAGE_BUFFER_SIZE, "%s|%d|%s", + task_name, result, file_name ? kbasename(file_name) : ""); + + if (unlikely(msg_size < 0)) { + pr_err("FIVE: unable to create dsms message from task_name: %s, result: %d, file_name: %s\n", + task_name, result, file_name); + return; + } + if (unlikely(msg_size >= MESSAGE_BUFFER_SIZE)) { + pr_warn("FIVE: dsms message size: %d exceeds max buffer size: %d. The tail was truncated! Resulting message is: %s\n", + msg_size, MESSAGE_BUFFER_SIZE, dsms_msg); + msg_size = MESSAGE_BUFFER_SIZE - 1; + } + crc = call_crc16(0, dsms_msg, msg_size) % MAX_FIV2_NUM; + + spin_lock(&five_dsms_lock); + if (!test_bit(crc, mask)) { + sent = false; + set_bit(crc, mask); + } + spin_unlock(&five_dsms_lock); + + if (!sent) + five_dsms_msg("FIV2", dsms_msg); + + return; +} + +void five_dsms_init(const char *version, int result) +{ + snprintf(context.message, MESSAGE_FIVE_INIT_SIZE, "%s", version); + INIT_DELAYED_WORK(&context.work, five_dsms_init_report); + schedule_delayed_work(&context.work, msecs_to_jiffies(DEFERRED_TIME)); +} diff --git a/security/samsung/five/five_dsms.h b/security/samsung/five/five_dsms.h new file mode 100644 index 000000000000..f4be8271640d --- /dev/null +++ b/security/samsung/five/five_dsms.h @@ -0,0 +1,42 @@ + +/* + * FIVE-DSMS integration. + * + * Copyright (C) 2020 Samsung Electronics, Inc. + * Yevgen Kopylov, + * + * 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_FIVE_DSMS_H +#define __LINUX_FIVE_DSMS_H + +#ifdef CONFIG_FIVE_DSMS +void five_dsms_sign_err(const char *app, int result); +void five_dsms_reset_integrity(const char *task_name, int result, + const char *file_name); +void five_dsms_init(const char *version, int result); +#else +static inline void five_dsms_sign_err(const char *app, int result) +{ +} + +static inline void five_dsms_reset_integrity(const char *task_name, int result, + const char *file_name) +{ +} + +static inline void five_dsms_init(const char *version, int result) +{ + pr_debug("FIVE: DSMS is not supported\n"); +} +#endif + +#endif diff --git a/security/samsung/five/five_hooks.c b/security/samsung/five/five_hooks.c new file mode 100644 index 000000000000..bfb437268157 --- /dev/null +++ b/security/samsung/five/five_hooks.c @@ -0,0 +1,102 @@ +/* + * Five Event interface + * + * Copyright (C) 2018 Samsung Electronics, Inc. + * Ivan Vorobiov, + * + * 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 "five_hooks.h" +#include "five_porting.h" +#include "five_testing.h" + +#include +#include + +#define call_void_hook(FUNC, ...) \ + do { \ + struct five_hook_list *P; \ + \ + list_for_each_entry(P, &five_hook_heads.FUNC, list) \ + P->hook.FUNC(__VA_ARGS__); \ + } while (0) + +struct five_hook_heads five_hook_heads = { + .file_processed = + LIST_HEAD_INIT(five_hook_heads.file_processed), + .file_skipped = + LIST_HEAD_INIT(five_hook_heads.file_skipped), + .file_signed = + LIST_HEAD_INIT(five_hook_heads.file_signed), + .task_forked = + LIST_HEAD_INIT(five_hook_heads.task_forked), + .integrity_reset = + LIST_HEAD_INIT(five_hook_heads.integrity_reset), + .integrity_reset2 = + LIST_HEAD_INIT(five_hook_heads.integrity_reset2), +}; +EXPORT_SYMBOL_GPL(five_hook_heads); + +void five_hook_file_processed(struct task_struct *task, + struct file *file, void *xattr, + size_t xattr_size, int result) +{ + call_void_hook(file_processed, + task, + task_integrity_read(TASK_INTEGRITY(task)), + file, + xattr, + xattr_size, + result); +} + +void five_hook_file_signed(struct task_struct *task, + struct file *file, void *xattr, + size_t xattr_size, int result) +{ + call_void_hook(file_signed, + task, + task_integrity_read(TASK_INTEGRITY(task)), + file, + xattr, + xattr_size, + result); +} + +void five_hook_file_skipped(struct task_struct *task, struct file *file) +{ + call_void_hook(file_skipped, + task, + task_integrity_read(TASK_INTEGRITY(task)), + file); +} + +void five_hook_task_forked(struct task_struct *parent, + struct task_struct *child) +{ + call_void_hook(task_forked, + parent, + task_integrity_read(TASK_INTEGRITY(parent)), + child, + task_integrity_read(TASK_INTEGRITY(child))); +} + +__mockable +void five_hook_integrity_reset(struct task_struct *task, + struct file *file, + enum task_integrity_reset_cause cause) +{ + if (task == NULL) + return; + + call_void_hook(integrity_reset, task); + call_void_hook(integrity_reset2, task, file, cause); +} diff --git a/security/samsung/five/five_hooks.h b/security/samsung/five/five_hooks.h new file mode 100644 index 000000000000..bd2bdfb34061 --- /dev/null +++ b/security/samsung/five/five_hooks.h @@ -0,0 +1,109 @@ +/* + * Five Event interface + * + * Copyright (C) 2018 Samsung Electronics, Inc. + * Ivan Vorobiov, + * + * 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 _FIVE_HOOKS_H +#define _FIVE_HOOKS_H + +#include +#include +#include +#include + +void five_hook_file_processed(struct task_struct *task, + struct file *file, void *xattr, + size_t xattr_size, int result); + +void five_hook_task_forked(struct task_struct *parent, + struct task_struct *child); + +void five_hook_file_skipped(struct task_struct *task, + struct file *file); + +void five_hook_file_signed(struct task_struct *task, + struct file *file, void *xattr, + size_t xattr_size, int result); + +void five_hook_integrity_reset(struct task_struct *task, + struct file *file, + enum task_integrity_reset_cause cause); + +union five_list_options { + void (*file_processed)(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file, + void *xattr, + size_t xattr_size, + int result); + + void (*file_skipped)(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file); + + void (*task_forked)(struct task_struct *parent, + enum task_integrity_value parent_tint_value, + struct task_struct *child, + enum task_integrity_value child_tint_value); + + void (*file_signed)(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file, void *xattr, + size_t xattr_size, int result); + void (*integrity_reset)(struct task_struct *task); + void (*integrity_reset2)(struct task_struct *task, + struct file *file, + enum task_integrity_reset_cause cause); +}; + +struct five_hook_heads { + struct list_head file_processed; + struct list_head file_skipped; + struct list_head file_signed; + struct list_head task_forked; + struct list_head integrity_reset; + struct list_head integrity_reset2; +}; + +/* + * FIVE module hook list structure. + * For use with generic list macros for common operations. + */ +struct five_hook_list { + struct list_head list; + struct list_head *head; + union five_list_options hook; +}; + +/* + * Initializing a five_hook_list structure takes + * up a lot of space in a source file. This macro takes + * care of the common case and reduces the amount of + * text involved. + */ +#define FIVE_HOOK_INIT(HEAD, HOOK) \ + { .head = &five_hook_heads.HEAD, .hook = { .HEAD = HOOK } } + +extern struct five_hook_heads five_hook_heads; + +static inline void five_add_hooks(struct five_hook_list *hooks, + int count) +{ + int i; + + for (i = 0; i < count; i++) + list_add_tail_rcu(&hooks[i].list, hooks[i].head); +} + +#endif /* _FIVE_HOOKS_H */ diff --git a/security/samsung/five/five_init.c b/security/samsung/five/five_init.c new file mode 100644 index 000000000000..b46c3c464c25 --- /dev/null +++ b/security/samsung/five/five_init.c @@ -0,0 +1,42 @@ +/* + * This code is based on IMA's code + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * + * Egor Uleyskiy, + * Viacheslav Vovchenko + * Yevgen Kopylov + * + * 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include "five.h" + +int __init five_init(void) +{ + int rc; + + rc = five_keyring_init(); + if (rc) + return rc; + rc = five_load_built_x509(); + if (rc) + return rc; + rc = five_init_crypto(); + + return rc; +} diff --git a/security/samsung/five/five_keyring.c b/security/samsung/five/five_keyring.c new file mode 100644 index 000000000000..0996ece17d08 --- /dev/null +++ b/security/samsung/five/five_keyring.c @@ -0,0 +1,291 @@ +/* + * FIVE keyring + * + * Copyright (C) 2017 Samsung Electronics, Inc. + * Yevgen Kopylov, + * + * 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 "five.h" +#include "five_crypto_comp.h" +#include "five_porting.h" +#include "five_testing.h" + +__visible_for_testing +struct key *five_keyring; + +#ifndef CONFIG_FIVE_TRUSTED_KEYRING +__visible_for_testing +const char *five_keyring_name = "_five"; +#else +__visible_for_testing +const char *five_keyring_name = ".five"; +#endif + +__visible_for_testing __mockable +key_ref_t call_keyring_search( + key_ref_t keyring, struct key_type *type, + const char *description, bool recurse) +{ + return keyring_search(keyring, type, description, recurse); +} + +__visible_for_testing __mockable +struct key *call_request_key(struct key_type *type, + const char *description, + const char *callout_info) +{ + return request_key(type, description, callout_info); +} + +__visible_for_testing __mockable +key_ref_t call_key_create_or_update( + key_ref_t keyring_ref, + const char *type, + const char *description, + const void *payload, + size_t plen, + key_perm_t perm, + unsigned long flags) +{ + return key_create_or_update( + keyring_ref, type, description, + payload, plen, perm, flags); +} + +__visible_for_testing __mockable +void call_key_ref_put(key_ref_t key_ref) +{ + key_ref_put(key_ref); +} + +__visible_for_testing __mockable +void call_key_put(struct key *key) +{ + key_put(key); +} + +__visible_for_testing __mockable +int call_five_verify_signature(struct key *key, + struct public_key_signature *pks, + struct five_cert *cert, + struct five_cert_header *header) +{ + return five_verify_signature(key, pks, cert, header); +} + +__visible_for_testing __mockable +struct key *call_five_keyring_alloc( + const char *description, + kuid_t uid, kgid_t gid, const struct cred *cred, + key_perm_t perm, unsigned long flags) +{ + return five_keyring_alloc(description, uid, gid, cred, perm, flags); +} + +/* + * Request an asymmetric key. + */ +__visible_for_testing +struct key *five_request_asymmetric_key(uint32_t keyid) +{ + struct key *key; + char name[12]; + + snprintf(name, sizeof(name), "id:%08x", keyid); + + pr_debug("key search: \"%s\"\n", name); + + if (five_keyring) { + /* search in specific keyring */ + key_ref_t kref; + + kref = call_keyring_search(make_key_ref(five_keyring, 1), + &key_type_asymmetric, name, true); + if (IS_ERR(kref)) + key = ERR_CAST(kref); + else + key = key_ref_to_ptr(kref); + } else { + return ERR_PTR(-ENOKEY); + } + + if (IS_ERR(key)) { + switch (PTR_ERR(key)) { + /* Hide some search errors */ + case -EACCES: + case -ENOTDIR: + case -EAGAIN: + return ERR_PTR(-ENOKEY); + default: + return key; + } + } + + pr_debug("%s() = 0 [%x]\n", __func__, key_serial(key)); + + return key; +} + +static int five_asymmetric_verify(struct five_cert *cert, + const char *data, int datalen) +{ + struct public_key_signature pks; + struct five_cert_header *header; + struct key *key; + int ret = -ENOMEM; + + header = (struct five_cert_header *)cert->body.header->value; + if (header->hash_algo >= HASH_ALGO__LAST) + return -ENOPKG; + + key = five_request_asymmetric_key(__be32_to_cpu(header->key_id)); + if (IS_ERR(key)) + return PTR_ERR(key); + + memset(&pks, 0, sizeof(pks)); + + pks.digest = (u8 *)data; + pks.digest_size = datalen; + ret = call_five_verify_signature(key, &pks, cert, header); + + call_key_put(key); + pr_debug("%s() = %d\n", __func__, ret); + return ret; +} + +int five_digsig_verify(struct five_cert *cert, + const char *digest, int digestlen) +{ + if (!five_keyring) { + five_keyring = call_request_key( + &key_type_keyring, five_keyring_name, NULL); + if (IS_ERR(five_keyring)) { + int err = PTR_ERR(five_keyring); + + pr_err("no %s keyring: %d\n", five_keyring_name, err); + five_keyring = NULL; + return err; + } + } + + return five_asymmetric_verify(cert, digest, digestlen); +} + +__visible_for_testing +int __init five_load_x509_from_mem(const char *data, size_t size) +{ + key_ref_t key; + int rc = 0; + + if (!five_keyring || size == 0) + return -EINVAL; + + key = call_key_create_or_update(make_key_ref(five_keyring, 1), + "asymmetric", + NULL, + data, + size, + ((KEY_POS_ALL & ~KEY_POS_SETATTR) | + KEY_USR_VIEW | KEY_USR_READ), + KEY_ALLOC_NOT_IN_QUOTA); + if (IS_ERR(key)) { + rc = PTR_ERR(key); + pr_err("Problem loading X.509 certificate (%d): %s\n", + rc, "built-in"); + } else { + pr_notice("Loaded X.509 cert '%s': %s\n", + key_ref_to_ptr(key)->description, "built-in"); + call_key_ref_put(key); + } + + return rc; +} + +#ifdef CONFIG_FIVE_CERT_ENG +extern char five_local_ca_start_eng[]; +extern char five_local_ca_end_eng[]; + +__visible_for_testing +int __init five_import_eng_key(void) +{ + size_t size = five_local_ca_end_eng - five_local_ca_start_eng; + + return five_load_x509_from_mem(five_local_ca_start_eng, size); +} +#else +__visible_for_testing +int __init five_import_eng_key(void) +{ + return 0; +} +#endif + +#ifdef CONFIG_FIVE_CERT_USER +extern char five_local_ca_start_user[]; +extern char five_local_ca_end_user[]; + +__visible_for_testing +int __init five_import_user_key(void) +{ + size_t size = five_local_ca_end_user - five_local_ca_start_user; + + return five_load_x509_from_mem(five_local_ca_start_user, size); +} +#else +__visible_for_testing +int __init five_import_user_key(void) +{ + return 0; +} +#endif + +int __init five_load_built_x509(void) +{ + int rc; + + rc = five_import_eng_key(); + if (rc) + return rc; + + rc = five_import_user_key(); + + return rc; +} + +int __init five_keyring_init(void) +{ + const struct cred *cred = current_cred(); + int err = 0; + + five_keyring = call_five_keyring_alloc(five_keyring_name, KUIDT_INIT(0), + KGIDT_INIT(0), cred, + ((KEY_POS_ALL & ~KEY_POS_SETATTR) | + KEY_USR_VIEW | KEY_USR_READ | + KEY_USR_SEARCH), + KEY_ALLOC_NOT_IN_QUOTA); + if (IS_ERR(five_keyring)) { + err = PTR_ERR(five_keyring); + pr_info("Can't allocate %s keyring (%d)\n", + five_keyring_name, err); + five_keyring = NULL; + } + return err; +} + +#if defined(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_GPL(five_request_asymmetric_key); +EXPORT_SYMBOL_GPL(five_keyring); +#endif diff --git a/security/samsung/five/five_lv.c b/security/samsung/five/five_lv.c new file mode 100644 index 000000000000..93c51762aacc --- /dev/null +++ b/security/samsung/five/five_lv.c @@ -0,0 +1,69 @@ +/* + * 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. + * + * This code is originated from Samsung Electronics proprietary sources. + * Author: Viacheslav Vovchenko, + * Created: 10 Jul 2017 + * + * Copyright (C) 2016 Samsung Electronics, Inc. + */ + +#include +#include "five_lv.h" + +struct lv *lv_get_next(struct lv *field, const void *end) +{ + size_t lv_len; + uint8_t *pbegin = (uint8_t *)field; + const uint8_t *pend = (const uint8_t *)end; + + if (unlikely(!pbegin || !pend || pbegin >= pend)) + return NULL; + + /* Check if we can read 'length' field */ + if (unlikely(pend - pbegin < sizeof(field->length))) + return NULL; + + lv_len = sizeof(field->length) + field->length; + /* Check if we can read the value */ + if (unlikely(pend - pbegin < lv_len)) + return NULL; + + return (struct lv *)(pbegin + lv_len); +} + +int lv_set(struct lv *field, const void *data, uint16_t data_len, + const void *end) +{ + size_t lv_len; + uint8_t *pbegin = (uint8_t *)field; + const uint8_t *pend = (const uint8_t *)end; + + if (unlikely(!pbegin || !pend)) + return -EINVAL; + + lv_len = sizeof(field->length) + data_len; + + if (unlikely(pbegin >= pend || (pend - pbegin < lv_len) || + (data_len > 0 && !data))) + return -EINVAL; + + field->length = data_len; + + if (data) + memcpy(field->value, data, data_len); + + return 0; +} + +#if defined(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_GPL(lv_get_next); +EXPORT_SYMBOL_GPL(lv_set); +#endif diff --git a/security/samsung/five/five_lv.h b/security/samsung/five/five_lv.h new file mode 100644 index 000000000000..03dbc2ce327b --- /dev/null +++ b/security/samsung/five/five_lv.h @@ -0,0 +1,44 @@ +/* + * 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. + * + * This code is originated from Samsung Electronics proprietary sources. + * Author: Viacheslav Vovchenko, + * Created: 10 Jul 2017 + * + * Copyright (C) 2016 Samsung Electronics, Inc. + */ + +#ifndef __FIVE_LV_H__ +#define __FIVE_LV_H__ + +#include + +/** + * Length/Value structure + */ +struct lv { + uint16_t length; + uint8_t value[]; +} __packed; + +/** + * Returns the next element from the chain of LV (length/value) data. + * Returns NULL in a case of memory limits violation + */ +struct lv *lv_get_next(struct lv *field, const void *end); + +/** + * Sets value of LV data and checks memory bounds. + * Returns 0 on success, otherwise - error. + */ +int lv_set(struct lv *field, const void *data, uint16_t data_len, + const void *end); + +#endif /* __FIVE_LV_H__ */ diff --git a/security/samsung/five/five_main.c b/security/samsung/five/five_main.c new file mode 100644 index 000000000000..c4e1274378ce --- /dev/null +++ b/security/samsung/five/five_main.c @@ -0,0 +1,1111 @@ +/* + * This code is based on IMA's code + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * + * Egor Uleyskiy, + * Viacheslav Vovchenko + * Yevgen Kopylov + * + * 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 + +#include "five.h" +#include "five_audit.h" +#include "five_hooks.h" +#include "five_state.h" +#include "five_pa.h" +#include "five_porting.h" +#include "five_cache.h" +#include "five_dmverity.h" +#include "five_dsms.h" +#include "five_testing.h" + +/* crash_dump in Android 12 uses this request even if Kernel doesn't + * support it */ +#ifndef PTRACE_PEEKMTETAGS +#define PTRACE_PEEKMTETAGS 33 +#endif + +static const bool check_dex2oat_binary = true; +static const bool check_memfd_file = true; + +static struct file *memfd_file __ro_after_init; +static bool is_five_initialized __ro_after_init; + +static struct workqueue_struct *g_five_workqueue; + +static inline void task_integrity_processing(struct task_integrity *tint); +static inline void task_integrity_done(struct task_integrity *tint); +static void process_measurement(const struct processing_event_list *params); +static inline struct processing_event_list *five_event_create( + enum five_event event, struct task_struct *task, + struct file *file, int function, gfp_t flags); +static inline void five_event_destroy( + const struct processing_event_list *file); + +#ifdef CONFIG_FIVE_DEBUG +static int five_enabled = 1; + +static ssize_t five_enabled_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + char command; + + if (get_user(command, buf)) + return -EFAULT; + + switch (command) { + case '0': + five_enabled = 0; + break; + case '1': + five_enabled = 1; + break; + default: + pr_err("FIVE: %s: unknown cmd: %hhx\n", __func__, command); + return -EINVAL; + } + + pr_info("FIVE debug: FIVE %s\n", five_enabled ? "enabled" : "disabled"); + return count; +} + +static ssize_t five_enabled_read(struct file *file, char __user *user_buf, + size_t count, loff_t *pos) +{ + char buf[2]; + + buf[0] = five_enabled ? '1' : '0'; + buf[1] = '\n'; + + return simple_read_from_buffer(user_buf, count, pos, buf, sizeof(buf)); +} + +static const struct file_operations five_enabled_fops = { + .owner = THIS_MODULE, + .read = five_enabled_read, + .write = five_enabled_write +}; + +static int __init init_fs(void) +{ + struct dentry *debug_file = NULL; + umode_t umode = (S_IRUGO | S_IWUSR | S_IWGRP); + + debug_file = debugfs_create_file( + "five_enabled", umode, NULL, NULL, &five_enabled_fops); + if (IS_ERR_OR_NULL(debug_file)) + goto error; + + return 0; +error: + if (debug_file) + return -PTR_ERR(debug_file); + + return -EEXIST; +} + +static inline int is_five_enabled(void) +{ + return five_enabled; +} + +int five_fcntl_debug(struct file *file, void __user *argp) +{ + struct inode *inode; + struct five_stat stat = {0}; + struct integrity_iint_cache *iint; + + if (unlikely(!file || !argp)) + return -EINVAL; + + inode = file_inode(file); + + inode_lock(inode); + iint = integrity_inode_get(inode); + if (unlikely(!iint)) { + inode_unlock(inode); + return -ENOMEM; + } + + stat.cache_status = five_get_cache_status(iint); + stat.cache_iversion = inode_query_iversion(iint->inode); + stat.inode_iversion = inode_query_iversion(inode); + + inode_unlock(inode); + + if (unlikely(copy_to_user(argp, &stat, sizeof(stat)))) + return -EFAULT; + + return 0; +} +#else +static int __init init_fs(void) +{ + return 0; +} + +static inline int is_five_enabled(void) +{ + return 1; +} +#endif + +static void work_handler(struct work_struct *in_data) +{ + struct worker_context *context = container_of(in_data, + struct worker_context, data_work); + struct task_integrity *intg; + + if (unlikely(!context)) + return; + + intg = context->tint; + + spin_lock(&intg->list_lock); + while (!list_empty(&(intg->events.list))) { + struct processing_event_list *five_file; + + five_file = list_entry(intg->events.list.next, + struct processing_event_list, list); + spin_unlock(&intg->list_lock); + switch (five_file->event) { + case FIVE_VERIFY_BUNCH_FILES: { + process_measurement(five_file); + break; + } + case FIVE_RESET_INTEGRITY: { + task_integrity_reset(intg); + five_hook_integrity_reset(five_file->task, + NULL, CAUSE_UNKNOWN); + break; + } + default: + break; + } + spin_lock(&intg->list_lock); + list_del(&five_file->list); + five_event_destroy(five_file); + } + + task_integrity_done(intg); + spin_unlock(&intg->list_lock); + task_integrity_put(intg); + + kfree(context); +} + +__mockable +const char *five_d_path(const struct path *path, char **pathbuf, char *namebuf) +{ + char *pathname = NULL; + + *pathbuf = __getname(); + if (*pathbuf) { + pathname = d_absolute_path(path, *pathbuf, PATH_MAX); + if (IS_ERR(pathname)) { + __putname(*pathbuf); + *pathbuf = NULL; + pathname = NULL; + } + } + + if (!pathname) { + strlcpy(namebuf, path->dentry->d_name.name, NAME_MAX); + pathname = namebuf; + } + + return pathname; +} + +int five_check_params(struct task_struct *task, struct file *file) +{ + struct inode *inode; + + if (unlikely(!file)) + return 1; + + inode = file_inode(file); + if (!S_ISREG(inode->i_mode)) + return 1; + + return 0; +} + +/* cut a list into two + * @cur_list: a list with entries + * @new_list: a new list to add all removed entries. Should be an empty list + * + * Use it under spin_lock + * + * The function moves second entry and following entries to new list. + * First entry is left in cur_list. + * + * Initial state: + * [cur_list]<=>[first]<=>[second]<=>[third]<=>...<=>[last] [new_list] + * ^=================================================^ ^====^ + * Result: + * [cur_list]<=>[first] + * ^==========^ + * [new_list]<=>[second]<=>[third]<=>...<=>[last] + * ^=====================================^ + * + * the function is similar to kernel list_cut_position, but there are few + * differences: + * - cut position is second entry + * - original list is left with only first entry + * - moving entries are from second entry to last entry + */ +static void list_cut_tail(struct list_head *cur_list, + struct list_head *new_list) +{ + if ((!list_empty(cur_list)) && + (!list_is_singular(cur_list))) { + new_list->next = cur_list->next->next; + cur_list->next->next->prev = new_list; + cur_list->next->next = cur_list; + new_list->prev = cur_list->prev; + cur_list->prev->next = new_list; + cur_list->prev = cur_list->next; + } +} + +static void free_files_list(struct list_head *list) +{ + struct list_head *tmp, *list_entry; + struct processing_event_list *file_entry; + + list_for_each_safe(list_entry, tmp, list) { + file_entry = list_entry(list_entry, + struct processing_event_list, list); + list_del(&file_entry->list); + five_event_destroy(file_entry); + } +} + +static int push_file_event_bunch(struct task_struct *task, struct file *file, + int function) +{ + int rc = 0; + struct worker_context *context; + struct processing_event_list *five_file; + + if (unlikely(!is_five_enabled()) || five_check_params(task, file)) + return 0; + + context = kmalloc(sizeof(struct worker_context), GFP_KERNEL); + if (unlikely(!context)) + return -ENOMEM; + + five_file = five_event_create(FIVE_VERIFY_BUNCH_FILES, task, file, + function, GFP_KERNEL); + if (unlikely(!five_file)) { + kfree(context); + return -ENOMEM; + } + + spin_lock(&TASK_INTEGRITY(task)->list_lock); + + if (list_empty(&(TASK_INTEGRITY(task)->events.list))) { + task_integrity_get(TASK_INTEGRITY(task)); + task_integrity_processing(TASK_INTEGRITY(task)); + + context->tint = TASK_INTEGRITY(task); + + list_add_tail(&five_file->list, + &TASK_INTEGRITY(task)->events.list); + spin_unlock(&TASK_INTEGRITY(task)->list_lock); + INIT_WORK(&context->data_work, work_handler); + rc = queue_work(g_five_workqueue, &context->data_work) ? 0 : 1; + } else { + struct list_head dead_list; + + INIT_LIST_HEAD(&dead_list); + if ((function == BPRM_CHECK) && + (!list_is_singular(&(TASK_INTEGRITY(task)->events.list)))) { + list_cut_tail(&TASK_INTEGRITY(task)->events.list, + &dead_list); + } + list_add_tail(&five_file->list, + &TASK_INTEGRITY(task)->events.list); + spin_unlock(&TASK_INTEGRITY(task)->list_lock); + free_files_list(&dead_list); + kfree(context); + } + return rc; +} + +static int push_reset_event(struct task_struct *task, + enum task_integrity_reset_cause cause, struct file *file) +{ + struct list_head dead_list; + struct task_integrity *current_tint; + struct processing_event_list *five_reset; + + if (unlikely(!is_five_enabled())) + return 0; + + INIT_LIST_HEAD(&dead_list); + current_tint = TASK_INTEGRITY(task); + task_integrity_get(current_tint); + + task_integrity_set_reset_reason(current_tint, cause, file); + + five_reset = five_event_create(FIVE_RESET_INTEGRITY, task, NULL, 0, + GFP_KERNEL); + if (unlikely(!five_reset)) { + task_integrity_reset_both(current_tint); + five_hook_integrity_reset(task, file, cause); + task_integrity_put(current_tint); + return -ENOMEM; + } + + task_integrity_reset_both(current_tint); + five_hook_integrity_reset(task, file, cause); + spin_lock(¤t_tint->list_lock); + if (!list_empty(¤t_tint->events.list)) { + list_cut_tail(¤t_tint->events.list, &dead_list); + five_reset->event = FIVE_RESET_INTEGRITY; + list_add_tail(&five_reset->list, ¤t_tint->events.list); + spin_unlock(¤t_tint->list_lock); + } else { + spin_unlock(¤t_tint->list_lock); + five_event_destroy(five_reset); + } + + task_integrity_put(current_tint); + /* remove dead_list */ + free_files_list(&dead_list); + return 0; +} + +void task_integrity_delayed_reset(struct task_struct *task, + enum task_integrity_reset_cause cause, struct file *file) +{ + push_reset_event(task, cause, file); +} + +static void five_check_last_writer(struct integrity_iint_cache *iint, + struct inode *inode, struct file *file) +{ + fmode_t mode = file->f_mode; + + if (!(mode & FMODE_WRITE)) + return; + + inode_lock(inode); + if (atomic_read(&inode->i_writecount) == 1) { + if (!inode_eq_iversion(inode, iint->version)) + five_set_cache_status(iint, FIVE_FILE_UNKNOWN); + } + iint->five_signing = false; + inode_unlock(inode); +} + +/** + * five_file_free - called on __fput() + * @file: pointer to file structure being freed + * + * Flag files that changed, based on i_version + */ +void five_file_free(struct file *file) +{ + struct inode *inode = file_inode(file); + struct integrity_iint_cache *iint; + + if (!S_ISREG(inode->i_mode)) + return; + + fivepa_fsignature_free(file); + + iint = integrity_iint_find(inode); + if (!iint) + return; + + five_check_last_writer(iint, inode, file); +} + +void five_task_free(struct task_struct *task) +{ + task_integrity_put(TASK_INTEGRITY(task)); +} + +/* Returns string representation of input function */ +const char *five_get_string_fn(enum five_hooks fn) +{ + switch (fn) { + case FILE_CHECK: + return "file-check"; + case MMAP_CHECK: + return "mmap-check"; + case BPRM_CHECK: + return "bprm-check"; + case POST_SETATTR: + return "post-setattr"; + } + return "unknown-function"; +} + +static inline bool is_dex2oat_binary(const struct file *file) +{ + const char *pathname = NULL; + char *pathbuf = NULL; + const char * const dex2oat_full_path[] = { + /* R OS */ + "/apex/com.android.art/bin/dex2oat", + "/apex/com.android.art/bin/dex2oat32", + "/apex/com.android.art/bin/dex2oat64", + /* Q OS */ + "/apex/com.android.runtime/bin/dex2oat" + }; + char filename[NAME_MAX]; + bool res = false; + size_t i; + + if (!file || !file->f_path.dentry) + return false; + + if (strncmp(file->f_path.dentry->d_iname, "dex2oat", + sizeof("dex2oat")) && + strncmp(file->f_path.dentry->d_iname, "dex2oat32", + sizeof("dex2oat32")) && + strncmp(file->f_path.dentry->d_iname, "dex2oat64", + sizeof("dex2oat64"))) + return false; + + pathname = five_d_path(&file->f_path, &pathbuf, filename); + + for (i = 0; i < ARRAY_SIZE(dex2oat_full_path); ++i) { + if (!strncmp(pathname, dex2oat_full_path[i], + strlen(dex2oat_full_path[i]) + 1)) { + res = true; + break; + } + } + + if (pathbuf) + __putname(pathbuf); + + return res; +} + +static inline bool match_trusted_executable(const struct five_cert *cert, + const struct integrity_iint_cache *iint, + const struct file *file) +{ + const struct five_cert_header *hdr = NULL; + + if (!cert) + return check_dex2oat_binary && is_dex2oat_binary(file); + + if (five_get_cache_status(iint) != FIVE_FILE_RSA) + return false; + + hdr = (const struct five_cert_header *)cert->body.header->value; + + if (hdr->privilege == FIVE_PRIV_ALLOW_SIGN) + return true; + + return false; +} + +static inline void task_integrity_processing(struct task_integrity *tint) +{ + tint->user_value = INTEGRITY_PROCESSING; +} + +static inline void task_integrity_done(struct task_integrity *tint) +{ + tint->user_value = task_integrity_read(tint); +} + +static void process_file(struct task_struct *task, + struct file *file, + int function, + struct file_verification_result *result) +{ + struct inode *inode = d_real_inode(file_dentry(file)); + struct integrity_iint_cache *iint = NULL; + struct five_cert cert = { {0} }; + struct five_cert *pcert = NULL; + int rc = -ENOMEM; + char *xattr_value = NULL; + int xattr_len = 0; + + if (!S_ISREG(inode->i_mode)) { + rc = 0; + goto out; + } + + iint = integrity_inode_get(inode); + if (!iint) + goto out; + + /* Nothing to do, just return existing appraised status */ + if (five_get_cache_status(iint) != FIVE_FILE_UNKNOWN) { + rc = 0; + goto out; + } + + xattr_len = five_read_xattr(d_real_comp(file->f_path.dentry), + &xattr_value); + if (xattr_value) { + if (xattr_len) { + rc = five_cert_fillout(&cert, xattr_value, xattr_len); + if (rc) { + pr_err("FIVE: certificate is incorrect inode=%lu\n", + inode->i_ino); + goto out; + } + + pcert = &cert; + + if (file->f_flags & O_DIRECT) { + rc = -EACCES; + goto out; + } + } else { + five_audit_info(task, file, "zero length", 0, 0, + "Found a dummy-cert", rc); + } + } + + rc = five_appraise_measurement(task, function, iint, file, pcert); + if (!rc && match_trusted_executable(pcert, iint, file)) + iint->five_flags |= FIVE_TRUSTED_FILE; + +out: + if (rc && iint) + iint->five_flags &= ~FIVE_TRUSTED_FILE; + + result->file = file; + result->task = task; + result->iint = iint; + result->fn = function; + result->xattr = xattr_value; + result->xattr_len = (size_t)xattr_len; + + if (!iint || five_get_cache_status(iint) == FIVE_FILE_UNKNOWN + || five_get_cache_status(iint) == FIVE_FILE_FAIL) + result->five_result = 1; + else + result->five_result = 0; +} + +static void process_measurement(const struct processing_event_list *params) +{ + struct task_struct *task = params->task; + struct task_integrity *integrity = TASK_INTEGRITY(task); + struct file *file = params->file; + struct inode *inode = file_inode(file); + int function = params->function; + struct file_verification_result file_result; + + if (function != BPRM_CHECK) { + if (task_integrity_read(integrity) == INTEGRITY_NONE) + return; + } + + file_verification_result_init(&file_result); + inode_lock(inode); + + process_file(task, file, function, &file_result); + + five_hook_file_processed(task, file, + file_result.xattr, file_result.xattr_len, + file_result.five_result); + + five_state_proceed(integrity, &file_result); + + inode_unlock(inode); + file_verification_result_deinit(&file_result); +} + +#define MFD_NAME_PREFIX "memfd:" +#define MFD_NAME_PREFIX_LEN (sizeof(MFD_NAME_PREFIX) - 1) + +static bool is_memfd_file(struct file *file) +{ + struct inode *inode; + struct inode *memfd_inode; + + if (!file) + return false; + + memfd_inode = file_inode(memfd_file); + inode = file_inode(file); + if (inode && memfd_inode && inode->i_sb == memfd_inode->i_sb) + if (file->f_path.dentry && + !strncmp(file->f_path.dentry->d_iname, MFD_NAME_PREFIX, + MFD_NAME_PREFIX_LEN)) + return true; + + return false; +} + +/** + * five_file_mmap - measure files being mapped executable based on + * the process_measurement() policy decision. + * @file: pointer to the file to be measured (May be NULL) + * @prot: contains the protection that will be applied by the kernel. + * + * On success return 0. + */ +int five_file_mmap(struct file *file, unsigned long prot) +{ + int rc = 0; + struct task_struct *task = current; + struct task_integrity *tint = TASK_INTEGRITY(task); + + if (unlikely(!is_five_initialized) || five_check_params(task, file)) + return 0; + + if (check_memfd_file && is_memfd_file(file)) + return 0; + + if (file && task_integrity_user_read(tint)) { + if (prot & PROT_EXEC) { + rc = push_file_event_bunch(task, file, MMAP_CHECK); + if (rc) + return rc; + } else { + five_hook_file_skipped(task, file); + } + } + + return rc; +} + +/** + * five_bprm_check - Measure executable being launched based on + * the process_measurement() policy decision. + * @bprm: contains the linux_binprm structure + * + * Notes: + * bprm_check could be called few times for one process when few binary loaders + * are used. Example: execution of shell script. + * In this case we should process first file (e.g. shell script) as main and + * use BPRM_CHECK. The second file (interpetator ) will be processed as general + * mapping (MMAP_CHECK). + * To implement this option variable bprm->recursion_depth is used. + * + * On success return 0. + */ +int __five_bprm_check(struct linux_binprm *bprm, int depth) +{ + int rc = 0; + struct task_struct *task = current; + struct task_integrity *old_tint = TASK_INTEGRITY(task); + + if (unlikely(!is_five_initialized) || unlikely(task->ptrace)) + return rc; + + if (depth > 0) { + rc = push_file_event_bunch(task, bprm->file, MMAP_CHECK); + } else { + struct task_integrity *tint = task_integrity_alloc(); + + task_integrity_assign(task, tint); + if (likely(TASK_INTEGRITY(task))) { + rc = push_file_event_bunch(task, + bprm->file, BPRM_CHECK); + } else { + rc = -ENOMEM; + } + task_integrity_put(old_tint); + } + + return rc; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)) +int five_bprm_check(struct linux_binprm *bprm, int depth) +{ + return __five_bprm_check(bprm, depth); +} +#else +int five_bprm_check(struct linux_binprm *bprm) +{ + return __five_bprm_check(bprm, bprm->recursion_depth); +} +#endif + +/** + * This function handles two situations: + * 1. Device had been rebooted before five_sign finished. + * Then xattr_len will be zero and iint->five_signing will be false. + * 2. The file is being signing when another process tries to open it. + * Then xattr_len will be zero and iint->five_signing will be true. + * + * - five_fcntl_edit stores the xattr with zero length and set + * iint->five_signing to true + * - five_fcntl_sign stores correct certificates and set + * iint->five_signing to false + * + * On success returns 0 + */ +int five_file_open(struct file *file) +{ + ssize_t xattr_len; + struct inode *inode = file_inode(file); + + if (!S_ISREG(inode->i_mode)) + return 0; + + xattr_len = vfs_getxattr(file->f_path.dentry, XATTR_NAME_FIVE, + NULL, 0); + if (xattr_len == 0) { + five_audit_verbose(current, file, "dummy-cert", 0, 0, + "Found a dummy-cert", 0); + } + + return 0; +} + +/** + * five_file_verify - force five integrity measurements for file + * the process_measurement() policy decision. This check affects + * task integrity. + * @file: pointer to the file to be measured (May be NULL) + * + * On success return 0. + */ +int five_file_verify(struct task_struct *task, struct file *file) +{ + int rc = 0; + struct task_integrity *tint = TASK_INTEGRITY(task); + + if (file && task_integrity_user_read(tint)) + rc = push_file_event_bunch(task, file, FILE_CHECK); + + return rc; +} + +static struct notifier_block five_reboot_nb = { + .notifier_call = five_reboot_notifier, + .priority = INT_MAX, +}; + +int five_hash_algo __ro_after_init = HASH_ALGO_SHA1; + +static int __init hash_setup(const char *str) +{ + int i; + + for (i = 0; i < HASH_ALGO__LAST; i++) { + if (strcmp(str, hash_algo_name[i]) == 0) { + five_hash_algo = i; + break; + } + } + + return 1; +} + +int __init init_five(void) +{ + int error; + + g_five_workqueue = alloc_workqueue("%s", WQ_FREEZABLE | WQ_MEM_RECLAIM, + 0, "five_wq"); + if (!g_five_workqueue) + return -ENOMEM; + + hash_setup(CONFIG_FIVE_DEFAULT_HASH); + error = five_init(); + if (error) + return error; + + error = register_reboot_notifier(&five_reboot_nb); + if (error) + return error; + +/** + * This empty file is needed in is_memfd_file() function. + * The only way to check whether the file was created using memfd_create() + * syscall is to compare its superblock address with address of another memfd + * file. + */ + memfd_file = shmem_kernel_file_setup( + "five_memfd_check", 0, VM_NORESERVE); + if (IS_ERR(memfd_file)) { + error = PTR_ERR(memfd_file); + memfd_file = NULL; + return error; + } + + error = init_fs(); + if (error) + return error; + + five_dsms_init("1", 0); + + error = five_init_dmverity(); + + if (!error) + is_five_initialized = true; + + return error; +} + +static int fcntl_verify(struct file *file) +{ + int rc = 0; + struct task_struct *task = current; + struct task_integrity *tint = TASK_INTEGRITY(task); + + if (task_integrity_user_read(tint)) + rc = push_file_event_bunch(task, file, FILE_CHECK); + return rc; +} + +/* Called from do_fcntl */ +int five_fcntl_verify_async(struct file *file) +{ + return fcntl_verify(file); +} + +/* Called from do_fcntl */ +int five_fcntl_verify_sync(struct file *file) +{ + return -EINVAL; +} + +struct bprm_hook_context { + struct work_struct data_work; + struct task_struct *task; + struct task_struct *child_task; +}; + +static void bprm_hook_handler(struct work_struct *in_data) +{ + struct bprm_hook_context *context = container_of(in_data, + struct bprm_hook_context, data_work); + + if (unlikely(!context)) + return; + + five_hook_task_forked(context->task, context->child_task); + + put_task_struct(context->task); + put_task_struct(context->child_task); + + kfree(context); +} + +int five_fork(struct task_struct *task, struct task_struct *child_task) +{ + int rc = 0; + struct bprm_hook_context *context; + + spin_lock(&TASK_INTEGRITY(task)->list_lock); + + if (!list_empty(&TASK_INTEGRITY(task)->events.list)) { + /*copy the list*/ + struct list_head *tmp; + struct processing_event_list *from_entry; + struct worker_context *context; + + context = kmalloc(sizeof(struct worker_context), GFP_ATOMIC); + if (unlikely(!context)) { + spin_unlock(&TASK_INTEGRITY(task)->list_lock); + return -ENOMEM; + } + + list_for_each(tmp, &TASK_INTEGRITY(task)->events.list) { + struct processing_event_list *five_file; + + from_entry = list_entry(tmp, + struct processing_event_list, list); + + five_file = five_event_create( + from_entry->event, + child_task, + from_entry->file, + from_entry->function, + GFP_ATOMIC); + if (unlikely(!five_file)) { + kfree(context); + spin_unlock(&TASK_INTEGRITY(task)->list_lock); + return -ENOMEM; + } + + list_add_tail(&five_file->list, + &TASK_INTEGRITY(child_task)->events.list); + } + + context->tint = TASK_INTEGRITY(child_task); + + rc = task_integrity_copy(TASK_INTEGRITY(task), + TASK_INTEGRITY(child_task)); + spin_unlock(&TASK_INTEGRITY(task)->list_lock); + task_integrity_get(context->tint); + task_integrity_processing(TASK_INTEGRITY(child_task)); + INIT_WORK(&context->data_work, work_handler); + rc = queue_work(g_five_workqueue, &context->data_work) ? 0 : 1; + } else { + rc = task_integrity_copy(TASK_INTEGRITY(task), + TASK_INTEGRITY(child_task)); + spin_unlock(&TASK_INTEGRITY(task)->list_lock); + } + + if (rc) + return rc; + + context = kmalloc(sizeof(struct bprm_hook_context), GFP_ATOMIC); + if (unlikely(!context)) + return -ENOMEM; + + get_task_struct(task); + get_task_struct(child_task); + context->task = task; + context->child_task = child_task; + INIT_WORK(&context->data_work, bprm_hook_handler); + rc = queue_work(g_five_workqueue, &context->data_work) ? 0 : 1; + + return rc; +} + +int five_ptrace(struct task_struct *task, long request) +{ + switch (request) { + case PTRACE_TRACEME: + case PTRACE_ATTACH: + case PTRACE_SEIZE: + case PTRACE_INTERRUPT: + case PTRACE_CONT: + case PTRACE_DETACH: + case PTRACE_PEEKTEXT: + case PTRACE_PEEKDATA: + case PTRACE_PEEKUSR: + case PTRACE_GETREGSET: + case PTRACE_GETSIGINFO: + case PTRACE_PEEKSIGINFO: + case PTRACE_GETSIGMASK: + case PTRACE_GETEVENTMSG: + case PTRACE_PEEKMTETAGS: +#if defined(CONFIG_ARM64) || defined(KUNIT_UML) + case COMPAT_PTRACE_GETREGS: + case COMPAT_PTRACE_GET_THREAD_AREA: + case COMPAT_PTRACE_GETVFPREGS: + case COMPAT_PTRACE_GETHBPREGS: +#else + case PTRACE_GETREGS: + case PTRACE_GET_THREAD_AREA: + case PTRACE_GETVFPREGS: + case PTRACE_GETHBPREGS: +#endif + break; + default: { + struct task_integrity *tint = TASK_INTEGRITY(task); + + if (task_integrity_user_read(tint) == INTEGRITY_NONE) + break; + + task_integrity_delayed_reset(task, CAUSE_PTRACE, NULL); + five_audit_err(task, NULL, "ptrace", task_integrity_read(tint), + INTEGRITY_NONE, "reset-integrity", 0); + break; + } + } + + return 0; +} + +int five_process_vm_rw(struct task_struct *task, int write) +{ + if (write) { + struct task_integrity *tint = TASK_INTEGRITY(task); + + if (task_integrity_user_read(tint) == INTEGRITY_NONE) + goto exit; + + task_integrity_delayed_reset(task, CAUSE_VMRW, NULL); + five_audit_err(task, NULL, "process_vm_rw", + task_integrity_read(tint), INTEGRITY_NONE, + "reset-integrity", 0); + } + +exit: + return 0; +} + +static inline struct processing_event_list *five_event_create( + enum five_event event, struct task_struct *task, + struct file *file, int function, gfp_t flags) +{ + struct processing_event_list *five_file; + + five_file = kzalloc(sizeof(struct processing_event_list), flags); + if (unlikely(!five_file)) + return NULL; + + five_file->event = event; + + switch (five_file->event) { + case FIVE_VERIFY_BUNCH_FILES: { + get_task_struct(task); + get_file(file); + five_file->task = task; + five_file->file = file; + five_file->function = function; + break; + } + case FIVE_RESET_INTEGRITY: { + get_task_struct(task); + five_file->task = task; + break; + } + default: + break; + } + + return five_file; +} + +static inline void five_event_destroy( + const struct processing_event_list *file) +{ + switch (file->event) { + case FIVE_VERIFY_BUNCH_FILES: { + fput(file->file); + put_task_struct(file->task); + break; + } + case FIVE_RESET_INTEGRITY: { + put_task_struct(file->task); + break; + } + default: + break; + } + kfree(file); +} + +late_initcall(init_five); + +MODULE_DESCRIPTION("File-based process Integrity Verifier"); +MODULE_LICENSE("GPL"); diff --git a/security/samsung/five/five_pa.c b/security/samsung/five/five_pa.c new file mode 100644 index 000000000000..e98b42fd085c --- /dev/null +++ b/security/samsung/five/five_pa.c @@ -0,0 +1,192 @@ +/* + * Process Authentificator helpers + * + * Copyright (C) 2017 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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 "five.h" +#include "five_pa.h" +#include "five_hooks.h" +#include "five_lv.h" +#include "five_porting.h" +#include "five_testing.h" + +__visible_for_testing __mockable +int call_five_read_xattr(struct dentry *dentry, char **xattr_value) +{ + return five_read_xattr(dentry, xattr_value); +} + +__visible_for_testing __mockable +int call_vfs_setxattr_noperm(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags) +{ + return __vfs_setxattr_noperm(dentry, name, value, size, flags); +} + +__visible_for_testing __mockable +bool call_task_integrity_allow_sign(struct task_integrity *intg) +{ + return task_integrity_allow_sign(intg); +} + +#ifdef CONFIG_FIVE_GKI_10 + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)) +#define F_SIGNATURE(file) ((void *)((file)->android_oem_data1)) + +static inline void f_signature_assign(struct file *file, void *f_signature) +{ + file->android_oem_data1 = (u64)f_signature; +} +#else +#define F_SIGNATURE(file) ((void *)((file)->android_vendor_data1)) + +static inline void f_signature_assign(struct file *file, void *f_signature) +{ + file->android_vendor_data1 = (u64)f_signature; +} +#endif + +#else +#define F_SIGNATURE(file) ((file)->f_signature) + +static inline void f_signature_assign(struct file *file, void *f_signature) +{ + file->f_signature = f_signature; +} +#endif + +__visible_for_testing +void pa_process_file(struct task_struct *task, struct file *file) +{ + char *xattr_value = NULL; + + if (five_check_params(task, file)) + return; + + if (F_SIGNATURE(file)) + return; + + call_five_read_xattr(d_real_comp(file->f_path.dentry), &xattr_value); + f_signature_assign(file, xattr_value); +} + +void fivepa_fsignature_free(struct file *file) +{ + kfree(F_SIGNATURE(file)); + f_signature_assign(file, NULL); +} + +int proca_fcntl_setxattr(struct file *file, void __user *lv_xattr) +{ + struct inode *inode; + struct lv lv_hdr = {0}; + int rc = -EPERM; + void *x = NULL; + + if (unlikely(!file || !lv_xattr)) + return -EINVAL; + + inode = file_inode(file); + + if (unlikely(copy_from_user(&lv_hdr, lv_xattr, sizeof(lv_hdr)))) + return -EFAULT; + + if (unlikely(lv_hdr.length > PAGE_SIZE)) + return -EINVAL; + + x = kmalloc(lv_hdr.length, GFP_NOFS); + if (unlikely(!x)) + return -ENOMEM; + + if (unlikely(copy_from_user(x, lv_xattr + sizeof(lv_hdr), + lv_hdr.length))) { + rc = -EFAULT; + goto out; + } + + if (file->f_op && file->f_op->flush) + if (file->f_op->flush(file, current->files)) { + rc = -EOPNOTSUPP; + goto out; + } + + inode_lock(inode); + + if (call_task_integrity_allow_sign(TASK_INTEGRITY(current))) { + rc = call_vfs_setxattr_noperm(d_real_comp(file->f_path.dentry), + XATTR_NAME_PA, + x, + lv_hdr.length, + 0); + } + inode_unlock(inode); + +out: + kfree(x); + + return rc; +} + +static void proca_hook_file_processed(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file, void *xattr, + size_t xattr_size, int result); + +static void proca_hook_file_skipped(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file); + +static struct five_hook_list five_ops[] = { + FIVE_HOOK_INIT(file_processed, proca_hook_file_processed), + FIVE_HOOK_INIT(file_skipped, proca_hook_file_skipped), +}; + +static void proca_hook_file_processed(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file, void *xattr, + size_t xattr_size, int result) +{ + pa_process_file(task, file); +} + +static void proca_hook_file_skipped(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file) +{ + pa_process_file(task, file); +} + +static __init int proca_module_init(void) +{ + five_add_hooks(five_ops, ARRAY_SIZE(five_ops)); + pr_info("PROCA was initialized\n"); + + return 0; +} +late_initcall(proca_module_init); + +#if defined(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_GPL(proca_fcntl_setxattr); +EXPORT_SYMBOL_GPL(pa_process_file); +#endif + +MODULE_DESCRIPTION("PROCA module"); +MODULE_LICENSE("GPL"); diff --git a/security/samsung/five/five_pa.h b/security/samsung/five/five_pa.h new file mode 100644 index 000000000000..1e55ebf22c28 --- /dev/null +++ b/security/samsung/five/five_pa.h @@ -0,0 +1,28 @@ +/* + * Process Authentificator helpers + * + * Copyright (C) 2017 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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_FIVE_PROCA_H +#define __LINUX_FIVE_PROCA_H + +#ifdef CONFIG_FIVE_PA_FEATURE +void fivepa_fsignature_free(struct file *file); +#else +static inline void fivepa_fsignature_free(struct file *file) +{ +} +#endif + +#endif // __LINUX_FIVE_PROCA_H diff --git a/security/samsung/five/five_porting.h b/security/samsung/five/five_porting.h new file mode 100644 index 000000000000..1ee59c8e41c7 --- /dev/null +++ b/security/samsung/five/five_porting.h @@ -0,0 +1,297 @@ +/* + * This is needed backporting of source code from Kernel version 4.x + * + * Copyright (C) 2018 Samsung Electronics, Inc. + * + * Egor Uleyskiy, + * + * 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_FIVE_PORTING_H +#define __LINUX_FIVE_PORTING_H + +#include +#include + +/* fallthrough is defined since v5.4.0 */ +#ifndef fallthrough +#define fallthrough do {} while (0) /* fallthrough */ +#endif + + +/* OVERLAYFS_SUPER_MAGIC is defined since v4.5.0 */ +#ifndef OVERLAYFS_SUPER_MAGIC +#define OVERLAYFS_SUPER_MAGIC 0x794c7630 +#endif + +/* EROFS_SUPER_MAGIC_V1 is defined since v5.4 */ +#ifndef EROFS_SUPER_MAGIC_V1 +#define EROFS_SUPER_MAGIC_V1 0xE0F5E1E2 +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 21) +/* d_backing_inode is absent on some Linux Kernel 3.x. but it back porting for + * few Samsung kernels: + * Exynos7570 (3.18.14): CL 13680422 + * Exynos7870 (3.18.14): CL 14632149 + * SDM450 (3.18.71): initially + */ +#if !defined(CONFIG_SOC_EXYNOS7570) && !defined(CONFIG_ARCH_SDM450) && \ + !defined(CONFIG_SOC_EXYNOS7870) +#define d_backing_inode(dentry) ((dentry)->d_inode) +#endif +#define inode_lock(inode) mutex_lock(&(inode)->i_mutex) +#define inode_lock_nested(inode, subclass) \ + mutex_lock_nested(&(inode)->i_mutex, subclass) +#define inode_unlock(inode) mutex_unlock(&(inode)->i_mutex) +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0) +#include + +#ifndef IS_VERITY +#define IS_VERITY(inode) 0 +#endif +#endif + +#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 20, 0) +/* It is added for initialization purposes. + * For developing LSM, please, use DEFINE_LSM + */ +#define security_initcall(fn) late_initcall(fn) +#endif + +#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 20, 17) +/* This file was added in v5.0.0 */ +#include +#endif + +#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 14, 20) +/* kmemcheck is gone. + * Since Kernel 4.14.21 SLAB_NOTRACK isn't present in Kernel. + * But for backward compatibility with old Kernels + * we have to define it here. + */ +#define SLAB_NOTRACK 0 +#endif + +#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 10, 17) +/* Some linux headers are moved. + * Since Kernel 4.11 get_task_struct moved to sched/ folder. + */ +#include +#else +#include +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0) +#include + +static inline int __vfs_removexattr(struct dentry *dentry, const char *name) +{ + struct inode *inode = d_inode(dentry); + + if (!inode->i_op->removexattr) + return -EOPNOTSUPP; + + return inode->i_op->removexattr(dentry, name); +} + +static inline ssize_t __vfs_getxattr(struct dentry *dentry, struct inode *inode, + const char *name, void *value, size_t size) +{ + if (!inode->i_op->getxattr) + return -EOPNOTSUPP; + + return inode->i_op->getxattr(dentry, name, value, size); +} +#endif + +#if (defined(CONFIG_ANDROID) && (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0) || \ + LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0))) || \ + LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +/* + * __vfs_getxattr was changed in Android Kernel v5.4 + * https://android.googlesource.com/kernel/common/+/3484eba91d6b529cc606486a2db79513f3db6c67 + * and was reverted in Android Kernel v5.15 + * https://android.googlesource.com/kernel/common/+/e884438aa554219a6d0df3a18ff0b23ea678c36c + */ +#define XATTR_NOSECURITY 0x4 /* get value, do not involve security check */ +#define __vfs_getxattr(dentry, inode, name, value, size, flags) \ + __vfs_getxattr(dentry, inode, name, value, size) +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +#define vfs_getxattr_alloc(dentry, name, xattr_value, size, flags) \ + vfs_getxattr_alloc(&init_user_ns, dentry, name, xattr_value, \ + size, flags) +#define __vfs_setxattr_noperm(dentry, name, value, size, flags) \ + __vfs_setxattr_noperm(&init_user_ns, dentry, name, value, size, flags) +#define __vfs_removexattr(dentry, name) \ + __vfs_removexattr(&init_user_ns, dentry, name) +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) +/* __GFP_WAIT was changed to __GFP_RECLAIM in + * https://lore.kernel.org/patchwork/patch/592262/ + */ +#define __GFP_RECLAIM __GFP_WAIT +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 19, 0) +#include + +static inline ssize_t __vfs_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + ssize_t ret; + + if (file->f_op->read) + ret = file->f_op->read(file, buf, count, pos); + else if (file->f_op->aio_read) + ret = do_sync_read(file, buf, count, pos); + else if (file->f_op->read_iter) + ret = new_sync_read(file, buf, count, pos); + else + ret = -EINVAL; + + return ret; +} + +static inline int integrity_kernel_read(struct file *file, loff_t offset, + char *addr, unsigned long count) +{ + mm_segment_t old_fs; + char __user *buf = (char __user *)addr; + ssize_t ret; + struct inode *inode = file_inode(file); + + if (!(file->f_mode & FMODE_READ)) + return -EBADF; + + old_fs = get_fs(); + set_fs(get_ds()); + + if (inode->i_sb->s_magic == OVERLAYFS_SUPER_MAGIC && file->private_data) + file = (struct file *)file->private_data; + + ret = __vfs_read(file, buf, count, &offset); + set_fs(old_fs); + + return ret; +} +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0) +#include + +static inline bool +inode_eq_iversion(const struct inode *inode, u64 old) +{ + return inode->i_version == old; +} + +static inline u64 +inode_query_iversion(struct inode *inode) +{ + return inode->i_version; +} +#else +#include +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 8) +static inline struct dentry *file_dentry(const struct file *file) +{ + return file->f_path.dentry; +} +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 2) +static inline struct dentry *d_real_comp(struct dentry *dentry) +{ + return dentry; +} +#elif LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) +static inline struct dentry *d_real_comp(struct dentry *dentry) +{ + return d_real(dentry); +} +#elif LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) +static inline struct dentry *d_real_comp(struct dentry *dentry) +{ + return d_real(dentry, NULL, 0); +} +#elif LINUX_VERSION_CODE < KERNEL_VERSION(4, 19, 0) +static inline struct dentry *d_real_comp(struct dentry *dentry) +{ + return d_real(dentry, NULL, 0, 0); +} +#else +static inline struct dentry *d_real_comp(struct dentry *dentry) +{ + return d_real(dentry, d_real_inode(dentry)); +} +#endif + +/* d_real_inode was added in v4.4.16, removed in v4.5.0 and added again in v4.6.5 */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 16) || \ + (LINUX_VERSION_CODE > KERNEL_VERSION(4, 5, 0) && \ + LINUX_VERSION_CODE < KERNEL_VERSION(4, 6, 5)) +static inline struct inode *d_real_inode(struct dentry *dentry) +{ + return d_backing_inode(d_real_comp(dentry)); +} +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) +#include "drivers/md/dm-core.h" +#include "uapi/linux/major.h" +#include "uapi/linux/loop.h" +#include "linux/device-mapper.h" + +struct loop_device { + int lo_number; + loff_t lo_offset; + loff_t lo_sizelimit; + int lo_flags; + char lo_file_name[LO_NAME_SIZE]; + + struct file * lo_backing_file; + struct block_device *lo_device; + + gfp_t old_gfp_mask; + + spinlock_t lo_lock; + int lo_state; + spinlock_t lo_work_lock; + struct workqueue_struct *workqueue; + struct work_struct rootcg_work; + struct list_head rootcg_cmd_list; + struct list_head idle_worker_list; + struct rb_root worker_tree; + struct timer_list timer; + bool use_dio; + bool sysfs_inited; + + struct request_queue *lo_queue; + struct blk_mq_tag_set tag_set; + struct gendisk *lo_disk; + struct mutex lo_mutex; + bool idr_visible; +}; +#else +#include "drivers/block/loop.h" +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0) +#define dm_table_get_num_targets(t) t->num_targets +#endif +#endif /* __LINUX_FIVE_PORTING_H */ diff --git a/security/samsung/five/five_state.c b/security/samsung/five/five_state.c new file mode 100644 index 000000000000..4d99d0bdaca0 --- /dev/null +++ b/security/samsung/five/five_state.c @@ -0,0 +1,478 @@ +/* + * FIVE State machine + * + * Copyright (C) 2017 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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 "five_audit.h" +#include "five_state.h" +#include "five_hooks.h" +#include "five_cache.h" +#ifndef FIVE_KUNIT_ENABLED +#include "five_dsms.h" +#else +void five_dsms_reset_integrity(const char *task_name, int result, + const char *file_name); +#endif +#include "five_testing.h" + +enum task_integrity_state_cause { + STATE_CAUSE_UNKNOWN, + STATE_CAUSE_DIGSIG, + STATE_CAUSE_DMV_PROTECTED, + STATE_CAUSE_TRUSTED, + STATE_CAUSE_HMAC, + STATE_CAUSE_SYSTEM_LABEL, + STATE_CAUSE_NOCERT, + STATE_CAUSE_TAMPERED, + STATE_CAUSE_MISMATCH_LABEL, + STATE_CAUSE_FSV_PROTECTED +}; + +struct task_verification_result { + enum task_integrity_value new_tint; + enum task_integrity_value prev_tint; + enum task_integrity_state_cause cause; +}; + +__visible_for_testing +const char *task_integrity_state_str( + enum task_integrity_state_cause cause) +{ + const char *str = "unknown"; + + switch (cause) { + case STATE_CAUSE_DIGSIG: + str = "digsig"; + break; + case STATE_CAUSE_DMV_PROTECTED: + str = "dmv_protected"; + break; + case STATE_CAUSE_FSV_PROTECTED: + str = "fsv_protected"; + break; + case STATE_CAUSE_TRUSTED: + str = "trusted"; + break; + case STATE_CAUSE_HMAC: + str = "hmac"; + break; + case STATE_CAUSE_SYSTEM_LABEL: + str = "system_label"; + break; + case STATE_CAUSE_NOCERT: + str = "nocert"; + break; + case STATE_CAUSE_MISMATCH_LABEL: + str = "mismatch_label"; + break; + case STATE_CAUSE_TAMPERED: + str = "tampered"; + break; + case STATE_CAUSE_UNKNOWN: + str = "unknown"; + break; + } + + return str; +} + +__visible_for_testing +enum task_integrity_reset_cause state_to_reason_cause( + enum task_integrity_state_cause cause) +{ + enum task_integrity_reset_cause reset_cause; + + switch (cause) { + case STATE_CAUSE_UNKNOWN: + reset_cause = CAUSE_UNKNOWN; + break; + case STATE_CAUSE_TAMPERED: + reset_cause = CAUSE_TAMPERED; + break; + case STATE_CAUSE_NOCERT: + reset_cause = CAUSE_NO_CERT; + break; + case STATE_CAUSE_MISMATCH_LABEL: + reset_cause = CAUSE_MISMATCH_LABEL; + break; + default: + /* Integrity is not NONE. */ + reset_cause = CAUSE_UNSET; + break; + } + + return reset_cause; +} + +const char *task_integrity_reset_str( + enum task_integrity_reset_cause cause) +{ + const char *str = NULL; + + switch (cause) { + case CAUSE_TAMPERED: + str = "tampered"; + break; + case CAUSE_NO_CERT: + str = "nocert"; + break; + case CAUSE_MISMATCH_LABEL: + str = "mismatch_label"; + break; + case CAUSE_UNSET: + str = "unset"; + break; + case CAUSE_BAD_FS: + str = "bad_fs"; + break; + case CAUSE_INVALID_HASH_LENGTH: + str = "invalid_hash_len"; + break; + case CAUSE_INVALID_HEADER: + str = "invalid_header"; + break; + case CAUSE_CALC_HASH_FAILED: + str = "calc_hash_failed"; + break; + case CAUSE_INVALID_LABEL_DATA: + str = "invalid_label_data"; + break; + case CAUSE_INVALID_SIGNATURE_DATA: + str = "invalid_sign_data"; + break; + case CAUSE_INVALID_HASH: + str = "invalid_hash"; + break; + case CAUSE_INVALID_CALC_CERT_HASH: + str = "invalid_calc_cert_hash"; + break; + case CAUSE_INVALID_UPDATE_LABEL: + str = "invalid_update_label"; + break; + case CAUSE_INVALID_SIGNATURE: + str = "invalid_signature"; + break; + case CAUSE_UKNOWN_FIVE_DATA: + str = "unknown_five_data"; + break; + case CAUSE_PTRACE: + str = "ptrace"; + break; + case CAUSE_VMRW: + str = "vmrw"; + break; + case CAUSE_EXEC: + str = "exec"; + break; + default: + str = "reset_integrity"; + break; + } + + return str; +} + +__visible_for_testing +int is_system_label(struct integrity_label *label) +{ + if (label && label->len == 0) + return 1; /* system label */ + + return 0; +} + +__visible_for_testing +inline int integrity_label_cmp(struct integrity_label *l1, + struct integrity_label *l2) +{ + return 0; +} + +__visible_for_testing +int verify_or_update_label(struct task_integrity *intg, + struct integrity_iint_cache *iint) +{ + struct integrity_label *l; + struct integrity_label *file_label = iint->five_label; + int rc = 0; + + if (!file_label) /* digsig doesn't have label */ + return 0; + + if (is_system_label(file_label)) + return 0; + + spin_lock(&intg->value_lock); + l = intg->label; + + if (l) { + if (integrity_label_cmp(file_label, l)) { + rc = -EPERM; + goto out; + } + } else { + struct integrity_label *new_label; + + new_label = kmalloc(sizeof(file_label->len) + file_label->len, + GFP_ATOMIC); + if (!new_label) { + rc = -ENOMEM; + goto out; + } + + new_label->len = file_label->len; + memcpy(new_label->data, file_label->data, new_label->len); + intg->label = new_label; + } + +out: + spin_unlock(&intg->value_lock); + + return rc; +} + +__visible_for_testing +bool set_first_state(struct integrity_iint_cache *iint, + struct task_integrity *integrity, + struct task_verification_result *result) +{ + enum task_integrity_value tint = INTEGRITY_NONE; + enum five_file_integrity status = five_get_cache_status(iint); + bool trusted_file = iint->five_flags & FIVE_TRUSTED_FILE; + enum task_integrity_state_cause cause = STATE_CAUSE_UNKNOWN; + + result->new_tint = result->prev_tint = task_integrity_read(integrity); + task_integrity_clear(integrity); + + switch (status) { + case FIVE_FILE_RSA: + if (trusted_file) { + cause = STATE_CAUSE_TRUSTED; + tint = INTEGRITY_PRELOAD_ALLOW_SIGN; + } else { + cause = STATE_CAUSE_DIGSIG; + tint = INTEGRITY_PRELOAD; + } + break; + case FIVE_FILE_FSVERITY: + case FIVE_FILE_DMVERITY: + if (trusted_file) { + cause = STATE_CAUSE_TRUSTED; + tint = INTEGRITY_DMVERITY_ALLOW_SIGN; + } else { + cause = (status == FIVE_FILE_FSVERITY) + ? STATE_CAUSE_FSV_PROTECTED + : STATE_CAUSE_DMV_PROTECTED; + tint = INTEGRITY_DMVERITY; + } + break; + case FIVE_FILE_HMAC: + cause = STATE_CAUSE_HMAC; + tint = INTEGRITY_MIXED; + break; + case FIVE_FILE_FAIL: + cause = STATE_CAUSE_TAMPERED; + tint = INTEGRITY_NONE; + break; + case FIVE_FILE_UNKNOWN: + cause = STATE_CAUSE_NOCERT; + tint = INTEGRITY_NONE; + break; + default: + cause = STATE_CAUSE_NOCERT; + tint = INTEGRITY_NONE; + break; + } + + task_integrity_set(integrity, tint); + result->new_tint = tint; + result->cause = cause; + + return true; +} + +__visible_for_testing +bool set_next_state(struct integrity_iint_cache *iint, + struct task_integrity *integrity, + struct task_verification_result *result) +{ + bool is_newstate = false; + enum five_file_integrity status = five_get_cache_status(iint); + bool has_digsig = (status == FIVE_FILE_RSA); + bool dmv_protected = (status == FIVE_FILE_DMVERITY); + bool fsv_protected = (status == FIVE_FILE_FSVERITY); + bool xv_protected = dmv_protected || fsv_protected; + struct integrity_label *label = iint->five_label; + enum task_integrity_state_cause cause = STATE_CAUSE_UNKNOWN; + enum task_integrity_value state_tint = INTEGRITY_NONE; + + result->new_tint = result->prev_tint = task_integrity_read(integrity); + + if (has_digsig) + return is_newstate; + + if (status == FIVE_FILE_UNKNOWN || status == FIVE_FILE_FAIL) { + spin_lock(&integrity->value_lock); + + if (status == FIVE_FILE_UNKNOWN) + cause = STATE_CAUSE_NOCERT; + else + cause = STATE_CAUSE_TAMPERED; + + state_tint = INTEGRITY_NONE; + is_newstate = true; + goto out; + } + + if (verify_or_update_label(integrity, iint)) { + spin_lock(&integrity->value_lock); + cause = STATE_CAUSE_MISMATCH_LABEL; + state_tint = INTEGRITY_NONE; + is_newstate = true; + goto out; + } + + spin_lock(&integrity->value_lock); + switch (integrity->value) { + case INTEGRITY_PRELOAD_ALLOW_SIGN: + if (xv_protected) { + cause = fsv_protected ? STATE_CAUSE_FSV_PROTECTED + : STATE_CAUSE_DMV_PROTECTED; + state_tint = INTEGRITY_DMVERITY_ALLOW_SIGN; + } else if (is_system_label(label)) { + cause = STATE_CAUSE_SYSTEM_LABEL; + state_tint = INTEGRITY_MIXED_ALLOW_SIGN; + } else { + cause = STATE_CAUSE_HMAC; + state_tint = INTEGRITY_MIXED; + } + is_newstate = true; + break; + case INTEGRITY_PRELOAD: + if (xv_protected) { + cause = fsv_protected ? STATE_CAUSE_FSV_PROTECTED + : STATE_CAUSE_DMV_PROTECTED; + state_tint = INTEGRITY_DMVERITY; + } else { + cause = STATE_CAUSE_HMAC; + state_tint = INTEGRITY_MIXED; + } + is_newstate = true; + break; + case INTEGRITY_MIXED_ALLOW_SIGN: + if (!xv_protected && !is_system_label(label)) { + cause = STATE_CAUSE_HMAC; + state_tint = INTEGRITY_MIXED; + is_newstate = true; + } + break; + case INTEGRITY_DMVERITY: + if (!xv_protected) { + cause = STATE_CAUSE_HMAC; + state_tint = INTEGRITY_MIXED; + is_newstate = true; + } + break; + case INTEGRITY_DMVERITY_ALLOW_SIGN: + if (!xv_protected) { + if (is_system_label(label)) { + cause = STATE_CAUSE_SYSTEM_LABEL; + state_tint = INTEGRITY_MIXED_ALLOW_SIGN; + } else { + cause = STATE_CAUSE_HMAC; + state_tint = INTEGRITY_MIXED; + } + is_newstate = true; + } + break; + case INTEGRITY_MIXED: + break; + case INTEGRITY_NONE: + break; + default: + // Unknown state + cause = STATE_CAUSE_UNKNOWN; + state_tint = INTEGRITY_NONE; + is_newstate = true; + } + +out: + + if (is_newstate) { + __task_integrity_set(integrity, state_tint); + result->new_tint = state_tint; + result->cause = cause; + } + spin_unlock(&integrity->value_lock); + + return is_newstate; +} + +void five_state_proceed(struct task_integrity *integrity, + struct file_verification_result *file_result) +{ + struct integrity_iint_cache *iint = file_result->iint; + enum five_hooks fn = file_result->fn; + struct task_struct *task = file_result->task; + struct file *file = file_result->file; + bool is_newstate; + struct task_verification_result task_result = {}; + + if (!iint) + return; + + if (fn == BPRM_CHECK) + is_newstate = set_first_state(iint, integrity, &task_result); + else + is_newstate = set_next_state(iint, integrity, &task_result); + + if (is_newstate) { + if (task_result.new_tint == INTEGRITY_NONE) { + task_integrity_set_reset_reason(integrity, + state_to_reason_cause(task_result.cause), file); + five_hook_integrity_reset(task, file, + state_to_reason_cause(task_result.cause)); + + if (fn != BPRM_CHECK) { + char comm[TASK_COMM_LEN]; + char filename[NAME_MAX]; + char *pathbuf = NULL; + + five_dsms_reset_integrity( + get_task_comm(comm, task), + task_result.cause, + five_d_path(&file->f_path, &pathbuf, + filename)); + if (pathbuf) + __putname(pathbuf); + } + } + five_audit_verbose(task, file, five_get_string_fn(fn), + task_result.prev_tint, task_result.new_tint, + task_integrity_state_str(task_result.cause), + file_result->five_result); + } +} + +#if defined(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_GPL(verify_or_update_label); +EXPORT_SYMBOL_GPL(task_integrity_state_str); +EXPORT_SYMBOL_GPL(state_to_reason_cause); +EXPORT_SYMBOL_GPL(set_next_state); +EXPORT_SYMBOL_GPL(set_first_state); +EXPORT_SYMBOL_GPL(is_system_label); +EXPORT_SYMBOL_GPL(integrity_label_cmp); +EXPORT_SYMBOL_GPL(five_state_proceed); +#endif diff --git a/security/samsung/five/five_state.h b/security/samsung/five/five_state.h new file mode 100644 index 000000000000..c993c3749f45 --- /dev/null +++ b/security/samsung/five/five_state.h @@ -0,0 +1,27 @@ +/* + * FIVE State machine + * + * Copyright (C) 2017 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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_FIVE_STATE_MACHINE_H +#define __LINUX_FIVE_STATE_MACHINE_H + +#include "five.h" + +void five_state_proceed(struct task_integrity *integrity, + struct file_verification_result *result); + +const char *task_integrity_reset_str(enum task_integrity_reset_cause cause); + +#endif // __LINUX_FIVE_STATE_MACHINE_H \ No newline at end of file diff --git a/security/samsung/five/five_tee_api.h b/security/samsung/five/five_tee_api.h new file mode 100644 index 000000000000..6312813fd376 --- /dev/null +++ b/security/samsung/five/five_tee_api.h @@ -0,0 +1,31 @@ +/* + * FIVE TEE API + * + * Copyright (C) 2017 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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_FIVE_TEE_API_H +#define __LINUX_FIVE_TEE_API_H + +#include +#include +#include + +int verify_hash(enum hash_algo algo, const void *hash, size_t hash_len, + const void *label, size_t label_len, + const void *signature, size_t signature_len); +int sign_hash(enum hash_algo algo, const void *hash, size_t hash_len, + const void *label, size_t label_len, + void *signature, size_t *signature_len); + +#endif /* __LINUX_FIVE_TEE_API_H */ diff --git a/security/samsung/five/five_tee_interface.c b/security/samsung/five/five_tee_interface.c new file mode 100644 index 000000000000..e7291c06ee43 --- /dev/null +++ b/security/samsung/five/five_tee_interface.c @@ -0,0 +1,116 @@ +/* + * Interface for TEE Driver + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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 "five_tee_interface.h" +#include "five_tee_api.h" + +static struct five_tee_driver_fns *g_tee_driver_fn; +static char is_registered; +static DECLARE_RWSEM(usage_lock); + +int register_five_tee_driver( + struct five_tee_driver_fns *tee_driver_fns) +{ + int rc = 0; + + if (!tee_driver_fns) + return -EINVAL; + + down_write(&usage_lock); + if (is_registered) { + rc = -EACCES; + goto exit; + } + + g_tee_driver_fn = kmalloc(sizeof(*g_tee_driver_fn), GFP_KERNEL); + if (!g_tee_driver_fn) { + rc = -ENOMEM; + goto exit; + } + + g_tee_driver_fn->verify_hmac = tee_driver_fns->verify_hmac; + g_tee_driver_fn->sign_hmac = tee_driver_fns->sign_hmac; + is_registered = 1; + +exit: + up_write(&usage_lock); + + return rc; +} +EXPORT_SYMBOL_GPL(register_five_tee_driver); + +void unregister_five_tee_driver(void) +{ + down_write(&usage_lock); + if (is_registered) { + kfree(g_tee_driver_fn); + g_tee_driver_fn = NULL; + is_registered = 0; + } + up_write(&usage_lock); +} +EXPORT_SYMBOL_GPL(unregister_five_tee_driver); + +int verify_hash(enum hash_algo algo, const void *hash, size_t hash_len, + const void *label, size_t label_len, + const void *signature, size_t signature_len) +{ + int rc = -ENODEV; + struct tee_iovec args = { + .algo = algo, + .hash = hash, + .hash_len = hash_len, + .label = label, + .label_len = label_len, + .signature = (void *)signature, + .signature_len = signature_len + }; + + down_read(&usage_lock); + if (is_registered) + rc = g_tee_driver_fn->verify_hmac(&args); + up_read(&usage_lock); + + return rc; +} + +int sign_hash(enum hash_algo algo, const void *hash, size_t hash_len, + const void *label, size_t label_len, + void *signature, size_t *signature_len) +{ + int rc = -ENODEV; + struct tee_iovec args = { + .algo = algo, + .hash = hash, + .hash_len = hash_len, + .label = label, + .label_len = label_len, + .signature = signature, + .signature_len = *signature_len + }; + + down_read(&usage_lock); + if (is_registered) + rc = g_tee_driver_fn->sign_hmac(&args); + up_read(&usage_lock); + + *signature_len = args.signature_len; + + return rc; +} diff --git a/security/samsung/five/five_tee_interface.h b/security/samsung/five/five_tee_interface.h new file mode 100644 index 000000000000..6ff986d050ce --- /dev/null +++ b/security/samsung/five/five_tee_interface.h @@ -0,0 +1,47 @@ +/* + * Interface for TEE Driver + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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 INTEGRITY_TEE_DRIVER_H +#define INTEGRITY_TEE_DRIVER_H + +#include +#include + +struct tee_iovec { + enum hash_algo algo; + + const void *hash; + size_t hash_len; + + const void *label; + size_t label_len; + + void *signature; + size_t signature_len; + + int rc; +}; + +struct five_tee_driver_fns { + int (*verify_hmac)(const struct tee_iovec *verify_args); + int (*sign_hmac)(struct tee_iovec *sign_args); +}; + +int register_five_tee_driver( + struct five_tee_driver_fns *tee_driver_fns); +void unregister_five_tee_driver(void); + +#endif diff --git a/security/samsung/five/five_testing.h b/security/samsung/five/five_testing.h new file mode 100644 index 000000000000..d058cbce81ee --- /dev/null +++ b/security/samsung/five/five_testing.h @@ -0,0 +1,27 @@ +#ifndef __LINUX_FIVE_TESTING_H +#define __LINUX_FIVE_TESTING_H + +#if defined(FIVE_KUNIT_ENABLED) || defined(PROCA_KUNIT_ENABLED) +#define KUNIT_UML // this define should be used for adding UML-specific modifications +#define __mockable __weak +#define __visible_for_testing +/* dsms_send_message stub. Never called. + * To isolate FIVE from DSMS during KUnit testing + */ +static inline int dsms_send_message(const char *feature_code, + const char *detail, + int64_t value) +{ return 1; } +// +#ifdef KUNIT_UML +#define COMPAT_PTRACE_GETREGS 12 +#define COMPAT_PTRACE_GET_THREAD_AREA 22 +#define COMPAT_PTRACE_GETVFPREGS 27 +#define COMPAT_PTRACE_GETHBPREGS 29 +#endif +#else +#define __mockable +#define __visible_for_testing static +#endif // FIVE_KUNIT_ENABLED || PROCA_KUNIT_ENABLED + +#endif // __LINUX_FIVE_TESTING_H diff --git a/security/samsung/five/gki/five.h b/security/samsung/five/gki/five.h new file mode 100644 index 000000000000..516811e77d96 --- /dev/null +++ b/security/samsung/five/gki/five.h @@ -0,0 +1,110 @@ +/* + * This code is based on IMA's code + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * + * Egor Uleyskiy, + * Viacheslav Vovchenko + * Yevgen Kopylov + * + * 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_FIVE_H +#define __LINUX_FIVE_H + +#include +#include +#include +#include +#include +#include + +#include "five_cert.h" +#include "five_crypto.h" + +/* set during initialization */ +extern int five_hash_algo; +struct worker_context { + struct work_struct data_work; + struct task_integrity *tint; +}; + +struct f_signature_task { + struct task_integrity *tint; + struct file *file; +}; + +struct f_signature_context { + struct work_struct data_work; + struct f_signature_task payload; +}; + +struct five_stat { + u64 inode_iversion; + u64 cache_iversion; + u32 cache_status; + u32 is_dm_verity; +}; + +/* Internal FIVE function definitions */ +int five_init(void); + +/* FIVE policy related functions */ +enum five_hooks { + FILE_CHECK = 1, + MMAP_CHECK, + BPRM_CHECK, + POST_SETATTR +}; + +struct file_verification_result { + struct task_struct *task; + struct file *file; + struct integrity_iint_cache *iint; + enum five_hooks fn; + int five_result; + void *xattr; + size_t xattr_len; +}; + +static inline void file_verification_result_init( + struct file_verification_result *result) +{ + memset(result, 0, sizeof(*result)); +} + +static inline void file_verification_result_deinit( + struct file_verification_result *result) +{ + kfree(result->xattr); + memset(result, 0, sizeof(*result)); +} + +int five_appraise_measurement(struct task_struct *task, int func, + struct integrity_iint_cache *iint, + struct file *file, + struct five_cert *cert); + +int five_read_xattr(struct dentry *dentry, char **xattr_value); +int five_check_params(struct task_struct *task, struct file *file); +const char *five_d_path(const struct path *path, char **pathbuf, + char *namebuf); + +int five_digsig_verify(struct five_cert *cert, + const char *digest, int digestlen); +int five_reboot_notifier(struct notifier_block *nb, + unsigned long action, void *unused); +int __init five_load_built_x509(void); +int __init five_keyring_init(void); +int __init five_task_integrity_cache_init(void); + +const char *five_get_string_fn(enum five_hooks fn); +#endif diff --git a/security/samsung/five/gki/five_appraise.c b/security/samsung/five/gki/five_appraise.c new file mode 100644 index 000000000000..8f00e7d8492a --- /dev/null +++ b/security/samsung/five/gki/five_appraise.c @@ -0,0 +1,854 @@ +/* + * This code is based on IMA's code + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * + * Egor Uleyskiy, + * Viacheslav Vovchenko + * Yevgen Kopylov + * + * 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 "five.h" +#include "five_audit.h" +#include "five_hooks.h" +#include "five_tee_api.h" +#include "five_porting.h" +#include "five_cache.h" +#include "five_dmverity.h" +#include "five_vfs.h" + +#define FIVE_RSA_SIGNATURE_MAX_LENGTH (2048/8) +/* Identify extend structure of integrity label */ +#define FIVE_ID_INTEGRITY_LABEL_EX 0xFFFF +#define FIVE_LABEL_VERSION1 1 +/* Maximum length of data integrity label. + * This limit is applied because: + * 1. TEEgris doesn't support signing data longer than 480 bytes; + * 2. The label's length is limited to 3965 byte according to the data + * transmission protocol between five_tee_driver and TA. + */ +#define FIVE_LABEL_MAX_LEN 256 + +/** + * Extend structure of integrity label. + * If field "len" equals 0xffff then it is extend integrity label, + * otherwise simple integrity label. + */ +struct integrity_label_ex { + uint16_t len; + uint8_t version; + uint8_t reserved[2]; + uint8_t hash_algo; + uint8_t hash[64]; + struct integrity_label label; +} __packed; + +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP +static const bool panic_on_error = true; +#else +static const bool panic_on_error; +#endif + +static DECLARE_RWSEM(sign_fcntl_lock); + +/* + * five_collect_measurement - collect file measurement + * + * Must be called with iint->mutex held. + * + * Return 0 on success, error code otherwise. + */ +static int five_collect_measurement(struct file *file, u8 hash_algo, + u8 *hash, size_t hash_len) +{ + int result = 0; + + BUG_ON(!file || !hash); + + result = five_calc_file_hash(file, hash_algo, hash, &hash_len); + if (result) { + five_audit_err(current, file, "collect_measurement", 0, + 0, "calculate file hash failed", result); + } + return result; +} + +static int get_integrity_label(struct five_cert *cert, + void **label_data, size_t *label_len) +{ + int rc = -ENODATA; + struct five_cert_header *header = + (struct five_cert_header *)cert->body.header->value; + + if (header && header->signature_type == FIVE_XATTR_HMAC) { + *label_data = cert->body.label->value; + *label_len = cert->body.label->length; + rc = 0; + } + return rc; +} + +static int get_signature(struct five_cert *cert, void **sig, + size_t *sig_len) +{ + int rc = -ENODATA; + struct five_cert_header *header = + (struct five_cert_header *)cert->body.header->value; + + if (header && header->signature_type == FIVE_XATTR_HMAC) { + if (cert->signature->length == 0) + return rc; + *sig = cert->signature->value; + *sig_len = cert->signature->length; + + rc = 0; + } + + return rc; +} + +static int update_label(struct integrity_iint_cache *iint, + const void *label_data, size_t label_len) +{ + struct integrity_label *l; + + if (!label_data) + return 0; + + l = kmalloc(sizeof(struct integrity_label) + label_len, GFP_NOFS); + if (l) { + l->len = label_len; + memcpy(l->data, label_data, label_len); + kfree(iint->five_label); + iint->five_label = l; + } else { + return -ENOMEM; + } + + return 0; +} + +static int five_fix_xattr(struct task_struct *task, + struct dentry *dentry, + struct file *file, + void **raw_cert, + size_t *raw_cert_len, + struct integrity_iint_cache *iint, + struct integrity_label_ex *label) +{ + int rc = 0; + u8 hash[FIVE_MAX_DIGEST_SIZE], *hash_file, *sig = NULL; + size_t hash_len = sizeof(hash), hash_file_len, sig_len; + void *file_label = label->label.data; + u16 file_label_len = label->label.len; + struct five_cert_body body_cert = {0}; + struct five_cert_header *header; + + BUG_ON(!task || !dentry || !file || !raw_cert || !(*raw_cert) || !iint); + BUG_ON(!raw_cert_len); + + rc = five_cert_body_fillout(&body_cert, *raw_cert, *raw_cert_len); + if (unlikely(rc)) + return -EINVAL; + + header = (struct five_cert_header *)body_cert.header->value; + hash_file = body_cert.hash->value; + hash_file_len = body_cert.hash->length; + if (unlikely(!header || !hash_file)) + return -EINVAL; + + if (label->version == FIVE_LABEL_VERSION1) { + rc = five_collect_measurement(file, header->hash_algo, + hash_file, hash_file_len); + if (unlikely(rc)) + return rc; + } else { + memcpy(hash_file, label->hash, hash_file_len); + } + + rc = five_cert_calc_hash(&body_cert, hash, &hash_len); + if (unlikely(rc)) + return rc; + + sig_len = (size_t)body_cert.hash->length + file_label_len; + + sig = kzalloc(sig_len, GFP_NOFS); + if (!sig) + return -ENOMEM; + + rc = sign_hash(header->hash_algo, hash, hash_len, + file_label, file_label_len, sig, &sig_len); + + if (!rc) { + rc = five_cert_append_signature(raw_cert, raw_cert_len, + sig, sig_len); + if (!rc) { + int count = 1; + + do { + rc = five_setxattr_noperm(d_real_comp(dentry), + XATTR_NAME_FIVE, + *raw_cert, + *raw_cert_len, + 0); + count--; + } while (count >= 0 && rc != 0); + + if (!rc) { + rc = update_label(iint, + file_label, file_label_len); + } + } + } else if (panic_on_error) { + panic("FIVE failed to sign %s (ret code = %d)", + dentry->d_name.name, rc); + } else { + five_audit_sign_err(current, file, "fix_xattr", 0, + 0, "can't sign the file", rc); + } + + kfree(sig); + + return rc; +} + +int five_read_xattr(struct dentry *dentry, char **xattr_value) +{ + ssize_t ret; + + ret = five_getxattr_alloc(dentry, XATTR_NAME_FIVE, xattr_value, + 0, GFP_NOFS); + if (ret < 0) + ret = 0; + + return ret; +} + +static bool bad_fs(struct inode *inode) +{ + if (inode->i_sb->s_magic == EXT4_SUPER_MAGIC || + inode->i_sb->s_magic == F2FS_SUPER_MAGIC || + inode->i_sb->s_magic == OVERLAYFS_SUPER_MAGIC || + inode->i_sb->s_magic == EROFS_SUPER_MAGIC_V1) + return false; + + return true; +} + +static bool readonly_sb(struct inode *inode) +{ + if (inode->i_sb->s_flags & MS_RDONLY) + return true; + + return false; +} + +/* + * five_is_fsverity_protected - checks if file is protected by FSVERITY + * + * Return true/false + */ +static bool five_is_fsverity_protected(const struct inode *inode) +{ + return IS_VERITY(inode); +} + +/* + * five_appraise_measurement - appraise file measurement + * + * Return 0 on success, error code otherwise + */ +int five_appraise_measurement(struct task_struct *task, int func, + struct integrity_iint_cache *iint, + struct file *file, + struct five_cert *cert) +{ + enum task_integrity_reset_cause cause = CAUSE_UNKNOWN; + struct dentry *dentry = NULL; + struct inode *inode = NULL; + enum five_file_integrity status = FIVE_FILE_UNKNOWN; + enum task_integrity_value prev_integrity; + int rc = 0; + u8 *file_hash; + u8 stored_file_hash[FIVE_MAX_DIGEST_SIZE] = {0}; + size_t file_hash_len = 0; + struct five_cert_header *header = NULL; + + BUG_ON(!task || !iint || !file); + + prev_integrity = task_integrity_read(TASK_INTEGRITY(task)); + dentry = file->f_path.dentry; + inode = d_backing_inode(dentry); + + if (bad_fs(inode)) { + status = FIVE_FILE_FAIL; + cause = CAUSE_BAD_FS; + rc = -ENOTSUPP; + goto out; + } + + if (!cert) { + cause = CAUSE_NO_CERT; + if (five_is_fsverity_protected(inode)) + status = FIVE_FILE_FSVERITY; + else if (five_is_dmverity_protected(file)) + status = FIVE_FILE_DMVERITY; + goto out; + } + + header = (struct five_cert_header *)cert->body.header->value; + file_hash = cert->body.hash->value; + file_hash_len = cert->body.hash->length; + if (file_hash_len > sizeof(stored_file_hash)) { + cause = CAUSE_INVALID_HASH_LENGTH; + rc = -EINVAL; + goto out; + } + + memcpy(stored_file_hash, file_hash, file_hash_len); + + if (unlikely(!header || !file_hash)) { + cause = CAUSE_INVALID_HEADER; + rc = -EINVAL; + goto out; + } + + rc = five_collect_measurement(file, header->hash_algo, file_hash, + file_hash_len); + if (rc) { + cause = CAUSE_CALC_HASH_FAILED; + goto out; + } + + switch (header->signature_type) { + case FIVE_XATTR_HMAC: { + u8 *sig = NULL; + u8 algo = header->hash_algo; + void *file_label_data; + size_t file_label_len, sig_len = 0; + u8 cert_hash[FIVE_MAX_DIGEST_SIZE] = {0}; + size_t cert_hash_len = sizeof(cert_hash); + + status = FIVE_FILE_FAIL; + + rc = get_integrity_label(cert, &file_label_data, + &file_label_len); + if (unlikely(rc)) { + cause = CAUSE_INVALID_LABEL_DATA; + break; + } + + if (unlikely(file_label_len > PAGE_SIZE)) { + cause = CAUSE_INVALID_LABEL_DATA; + break; + } + + rc = get_signature(cert, (void **)&sig, &sig_len); + if (unlikely(rc)) { + cause = CAUSE_INVALID_SIGNATURE_DATA; + break; + } + + rc = five_cert_calc_hash(&cert->body, cert_hash, + &cert_hash_len); + if (unlikely(rc)) { + cause = CAUSE_INVALID_CALC_CERT_HASH; + break; + } + + rc = verify_hash(algo, cert_hash, + cert_hash_len, + file_label_data, file_label_len, + sig, sig_len); + if (unlikely(rc)) { + cause = CAUSE_INVALID_HASH; + if (cert) { + five_audit_hexinfo(file, "stored hash", + stored_file_hash, file_hash_len); + five_audit_hexinfo(file, "calculated hash", + file_hash, file_hash_len); + five_audit_hexinfo(file, "HMAC signature", + sig, sig_len); + } + break; + } + + rc = update_label(iint, file_label_data, file_label_len); + if (unlikely(rc)) { + cause = CAUSE_INVALID_UPDATE_LABEL; + break; + } + + status = FIVE_FILE_HMAC; + + break; + } + case FIVE_XATTR_DIGSIG: { + u8 cert_hash[FIVE_MAX_DIGEST_SIZE] = {0}; + size_t cert_hash_len = sizeof(cert_hash); + + status = FIVE_FILE_FAIL; + + rc = five_cert_calc_hash(&cert->body, cert_hash, + &cert_hash_len); + if (unlikely(rc)) { + cause = CAUSE_INVALID_CALC_CERT_HASH; + break; + } + + rc = five_digsig_verify(cert, cert_hash, cert_hash_len); + + if (rc) { + cause = CAUSE_INVALID_SIGNATURE; + if (cert) { + five_audit_hexinfo(file, "stored hash", + stored_file_hash, file_hash_len); + five_audit_hexinfo(file, "calculated hash", + file_hash, file_hash_len); + five_audit_hexinfo(file, "RSA signature", + cert->signature->value, + cert->signature->length); + } + break; + } + + status = FIVE_FILE_RSA; + + break; + } + default: + status = FIVE_FILE_FAIL; + cause = CAUSE_UKNOWN_FIVE_DATA; + break; + } + +out: + if (status == FIVE_FILE_FAIL || status == FIVE_FILE_UNKNOWN) { + task_integrity_set_reset_reason(TASK_INTEGRITY(task), + cause, file); + five_audit_verbose(task, file, five_get_string_fn(func), + prev_integrity, prev_integrity, + tint_reset_cause_to_string(cause), rc); + } + + five_set_cache_status(iint, status); + + return rc; +} + +/* + * five_update_xattr - update 'security.five' hash value + */ +static int five_update_xattr(struct task_struct *task, + struct integrity_iint_cache *iint, struct file *file, + struct integrity_label_ex *label) +{ + struct dentry *dentry; + int rc = 0; + uint8_t *hash; + size_t hash_len; + uint8_t *raw_cert; + size_t raw_cert_len; + struct five_cert_header header = { + .version = FIVE_CERT_VERSION1, + .privilege = FIVE_PRIV_DEFAULT, + .hash_algo = five_hash_algo, + .signature_type = FIVE_XATTR_HMAC }; + + BUG_ON(!task || !iint || !file || !label); + + if (label->version == FIVE_LABEL_VERSION1) { + hash_len = (size_t)hash_digest_size[five_hash_algo]; + } else { + header.hash_algo = label->hash_algo; + if (label->hash_algo >= HASH_ALGO__LAST) + return -EINVAL; + + hash_len = (size_t)hash_digest_size[label->hash_algo]; + if (hash_len > sizeof(label->hash)) + return -EINVAL; + } + + hash = kzalloc(hash_len, GFP_KERNEL); + if (!hash) + return -ENOMEM; + + dentry = file->f_path.dentry; + + /* do not collect and update hash for digital signatures */ + if (five_get_cache_status(iint) == FIVE_FILE_RSA) { + char dummy[512]; + struct inode *inode = file_inode(file); + + rc = __vfs_getxattr(d_real_comp(dentry), inode, XATTR_NAME_FIVE, + dummy, sizeof(dummy), XATTR_NOSECURITY); + + // Check if xattr is exist + if (rc > 0 || rc != -ENODATA) { + kfree(hash); + return -EPERM; + } else { // xattr does not exist. + five_set_cache_status(iint, FIVE_FILE_UNKNOWN); + pr_err("FIVE: ERROR: Cache is unsynchronized"); + } + } + + rc = five_cert_body_alloc(&header, hash, hash_len, + label->label.data, label->label.len, + &raw_cert, &raw_cert_len); + if (rc) + goto exit; + + if (task_integrity_allow_sign(TASK_INTEGRITY(task))) { + rc = five_fix_xattr(task, dentry, file, + (void **)&raw_cert, &raw_cert_len, iint, label); + if (rc) + pr_err("FIVE: Can't sign hash: rc=%d\n", rc); + } else { + rc = -EPERM; + } + + five_hook_file_signed(task, file, raw_cert, raw_cert_len, rc); + + five_cert_free(raw_cert); + +exit: + kfree(hash); + return rc; +} + +static void five_reset_appraise_flags(struct dentry *dentry) +{ + struct inode *inode = d_backing_inode(dentry); + struct integrity_iint_cache *iint; + + if (!S_ISREG(inode->i_mode)) + return; + + iint = integrity_iint_find(inode); + if (iint) + five_set_cache_status(iint, FIVE_FILE_UNKNOWN); +} + +/** + * five_inode_post_setattr - reflect file metadata changes + * @dentry: pointer to the affected dentry + * + * Changes to a dentry's metadata might result in needing to appraise. + * + * This function is called from notify_change(), which expects the caller + * to lock the inode's i_mutex. + */ +void five_inode_post_setattr(struct task_struct *task, struct dentry *dentry) +{ + five_reset_appraise_flags(dentry); +} + +/* + * five_protect_xattr - protect 'security.five' + * + * Ensure that not just anyone can modify or remove 'security.five'. + */ +static int five_protect_xattr(struct dentry *dentry, const char *xattr_name, + const void *xattr_value, size_t xattr_value_len) +{ + if (strcmp(xattr_name, XATTR_NAME_FIVE) == 0) { + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + return 1; + } + return 0; +} + +int five_inode_setxattr(struct dentry *dentry, const char *xattr_name, + const void *xattr_value, size_t xattr_value_len) +{ + int result = five_protect_xattr(dentry, xattr_name, xattr_value, + xattr_value_len); + + if (result == 1 && xattr_value_len == 0) { + five_reset_appraise_flags(dentry); + return 0; + } + + if (result == 1) { + bool digsig; + struct five_cert_header *header; + struct five_cert cert = { {0} }; + + result = five_cert_fillout(&cert, xattr_value, xattr_value_len); + if (result) + return result; + + header = (struct five_cert_header *)cert.body.header->value; + + if (!xattr_value_len || !header || + (header->signature_type >= FIVE_XATTR_END)) + return -EINVAL; + + digsig = (header->signature_type == FIVE_XATTR_DIGSIG); + if (!digsig) + return -EPERM; + + five_reset_appraise_flags(dentry); + result = 0; + } + + return result; +} + +int five_inode_removexattr(struct dentry *dentry, const char *xattr_name) +{ + int result; + + result = five_protect_xattr(dentry, xattr_name, NULL, 0); + if (result == 1) { + five_reset_appraise_flags(dentry); + result = 0; + } + return result; +} + +int five_reboot_notifier(struct notifier_block *nb, + unsigned long action, void *unused) +{ + down_write(&sign_fcntl_lock); + /* Need to wait for five_fcntl_sign finish */ + up_write(&sign_fcntl_lock); + + return NOTIFY_DONE; +} + +static int copy_label(const struct integrity_label __user *ulabel, + struct integrity_label_ex **out_label) +{ + u16 len; + size_t label_len; + int rc = 0; + struct integrity_label_ex header = {0}; + struct integrity_label_ex *label = NULL; + + if (unlikely(!ulabel || !out_label)) { + rc = -EINVAL; + goto error; + } + + if (unlikely(copy_from_user(&len, ulabel, sizeof(len)))) { + rc = -EFAULT; + goto error; + } + + if (len == FIVE_ID_INTEGRITY_LABEL_EX) { + if (unlikely(copy_from_user(&header, ulabel, sizeof(header)))) { + rc = -EFAULT; + goto error; + } + + if (len != header.len || + header.label.len > FIVE_LABEL_MAX_LEN || + header.version <= FIVE_LABEL_VERSION1) { + rc = -EINVAL; + goto error; + } + + label_len = sizeof(header) + header.label.len; + + label = kzalloc(label_len, GFP_NOFS); + if (unlikely(!label)) { + rc = -ENOMEM; + goto error; + } + + memcpy(label, &header, sizeof(header)); + if (unlikely(copy_from_user(&label->label.data[0], + (const u8 __user *)ulabel + sizeof(header), + label_len - sizeof(header)))) { + rc = -EFAULT; + goto error; + } + } else { + if (len > FIVE_LABEL_MAX_LEN) { + rc = -EINVAL; + goto error; + } + + label_len = sizeof(header) + len; + + label = kzalloc(label_len, GFP_NOFS); + if (unlikely(!label)) { + rc = -ENOMEM; + goto error; + } + + if (unlikely(copy_from_user(&label->label, ulabel, + sizeof(len) + len))) { + rc = -EFAULT; + goto error; + } + + if (len != label->label.len) { + rc = -EINVAL; + goto error; + } + + label->version = FIVE_LABEL_VERSION1; + } + + *out_label = label; +error: + if (rc) + kfree(label); + + return rc; +} + +/* Called from do_fcntl */ +int five_fcntl_sign(struct file *file, struct integrity_label __user *label) +{ + struct integrity_iint_cache *iint; + struct inode *inode = file_inode(file); + struct integrity_label_ex *l = NULL; + int rc = 0; + + if (!S_ISREG(inode->i_mode)) + return -EINVAL; + + if (readonly_sb(inode)) { + pr_err("FIVE: Can't sign a file on RO FS\n"); + return -EROFS; + } + + if (task_integrity_allow_sign(TASK_INTEGRITY(current))) { + rc = copy_label(label, &l); + if (rc) { + pr_err("FIVE: Can't copy integrity label\n"); + return rc; + } + } else { + enum task_integrity_value tint = + task_integrity_read(TASK_INTEGRITY(current)); + + five_audit_err(current, file, "fcntl_sign", tint, tint, + "sign:no-perm", -EPERM); + return -EPERM; + } + + iint = integrity_inode_get(inode); + if (!iint) { + kfree(l); + return -ENOMEM; + } + + if (file->f_op && file->f_op->flush) { + if (file->f_op->flush(file, current->files)) { + kfree(l); + return -EOPNOTSUPP; + } + } + + down_read(&sign_fcntl_lock); + inode_lock(inode); + rc = five_update_xattr(current, iint, file, l); + iint->five_signing = false; + inode_unlock(inode); + up_read(&sign_fcntl_lock); + + kfree(l); + + return rc; +} + +static int check_input_inode(struct inode *inode) +{ + if (!S_ISREG(inode->i_mode)) + return -EINVAL; + + if (readonly_sb(inode)) { + pr_err("FIVE: Can't sign a file on RO FS\n"); + return -EROFS; + } + + return 0; +} + +int five_fcntl_edit(struct file *file) +{ + int rc; + struct dentry *dentry; + uint8_t *raw_cert = NULL; + size_t raw_cert_len = 0; + struct integrity_iint_cache *iint; + struct inode *inode = file_inode(file); + + rc = check_input_inode(inode); + if (rc) + return rc; + + if (!task_integrity_allow_sign(TASK_INTEGRITY(current))) + return -EPERM; + + inode_lock(inode); + dentry = file->f_path.dentry; + rc = five_setxattr_noperm(d_real_comp(dentry), + XATTR_NAME_FIVE, + raw_cert, + raw_cert_len, + 0); + iint = integrity_inode_get(inode); + if (iint) + iint->five_signing = true; + inode_unlock(inode); + + return rc; +} + +int five_fcntl_close(struct file *file) +{ + int rc; + ssize_t xattr_len; + struct dentry *dentry; + struct integrity_iint_cache *iint; + struct inode *inode = file_inode(file); + + rc = check_input_inode(inode); + if (rc) + return rc; + + inode_lock(inode); + iint = integrity_inode_get(inode); + if (!iint) { + inode_unlock(inode); + return -ENOMEM; + } + + if (iint->five_signing) { + dentry = file->f_path.dentry; + xattr_len = __vfs_getxattr(d_real_comp(dentry), inode, + XATTR_NAME_FIVE, NULL, 0, XATTR_NOSECURITY); + if (xattr_len == 0) + rc = __vfs_removexattr(d_real_comp(dentry), + XATTR_NAME_FIVE); + + iint->five_signing = false; + } + inode_unlock(inode); + + return rc; +} diff --git a/security/samsung/five/gki/five_audit.c b/security/samsung/five/gki/five_audit.c new file mode 100644 index 000000000000..dd49bd0bcef1 --- /dev/null +++ b/security/samsung/five/gki/five_audit.c @@ -0,0 +1,216 @@ +/* + * Audit calls for FIVE audit subsystem. + * + * Copyright (C) 2017 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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 "five.h" +#include "five_audit.h" +#include "five_cache.h" +#include "five_porting.h" +#include "five_dsms.h" + +static void five_audit_msg(struct task_struct *task, struct file *file, + const char *op, enum task_integrity_value prev, + enum task_integrity_value tint, const char *cause, int result); + +#ifdef CONFIG_FIVE_AUDIT_VERBOSE +void five_audit_verbose(struct task_struct *task, struct file *file, + const char *op, enum task_integrity_value prev, + enum task_integrity_value tint, const char *cause, int result) +{ + five_audit_msg(task, file, op, prev, tint, cause, result); +} +#else +void five_audit_verbose(struct task_struct *task, struct file *file, + const char *op, enum task_integrity_value prev, + enum task_integrity_value tint, const char *cause, int result) +{ +} +#endif + +void five_audit_info(struct task_struct *task, struct file *file, + const char *op, enum task_integrity_value prev, + enum task_integrity_value tint, const char *cause, int result) +{ + five_audit_msg(task, file, op, prev, tint, cause, result); +} + +/** + * There are two kind of event that can come to the function: error + * and tampering attempt. 'result' is for identification of error type + * and it should be non-zero in case of error but is always zero in + * case of tampering. + */ +void five_audit_err(struct task_struct *task, struct file *file, + const char *op, enum task_integrity_value prev, + enum task_integrity_value tint, const char *cause, int result) +{ + five_audit_msg(task, file, op, prev, tint, cause, result); + + if (!result) { + char comm[TASK_COMM_LEN]; + struct task_struct *tsk = task ? task : current; + + five_dsms_reset_integrity(get_task_comm(comm, tsk), 0, op); + } +} + +void five_audit_sign_err(struct task_struct *task, struct file *file, + const char *op, enum task_integrity_value prev, + enum task_integrity_value tint, const char *cause, int result) +{ + char comm[TASK_COMM_LEN]; + struct task_struct *tsk = task ? task : current; + + get_task_comm(comm, tsk); + five_dsms_sign_err(comm, result); +} + +static void five_audit_msg(struct task_struct *task, struct file *file, + const char *op, enum task_integrity_value prev, + enum task_integrity_value tint, const char *cause, int result) +{ + struct audit_buffer *ab; + struct inode *inode = NULL; + const char *fname = NULL; + char *pathbuf = NULL; + char filename[NAME_MAX]; + char comm[TASK_COMM_LEN]; + const char *name = ""; + struct task_struct *tsk = task ? task : current; + struct integrity_iint_cache *iint = NULL; + + if (file) { + inode = file_inode(file); + fname = five_d_path(&file->f_path, &pathbuf, filename); + } + + ab = audit_log_start(current->audit_context, GFP_KERNEL, + AUDIT_INTEGRITY_DATA); + if (unlikely(!ab)) { + pr_err("Can't get a context of audit logs\n"); + goto exit; + } + + audit_log_format(ab, " pid=%d", task_pid_nr(tsk)); + audit_log_format(ab, " tgid=%d", task_tgid_nr(tsk)); + audit_log_task_context(ab); + audit_log_format(ab, " op="); + audit_log_string(ab, op); + audit_log_format(ab, " cint=0x%x", tint); + audit_log_format(ab, " pint=0x%x", prev); + audit_log_format(ab, " cause="); + audit_log_string(ab, cause); + audit_log_format(ab, " comm="); + audit_log_untrustedstring(ab, get_task_comm(comm, tsk)); + if (fname) { + audit_log_format(ab, " name="); + audit_log_untrustedstring(ab, fname); + name = fname; + } + if (inode) { + audit_log_format(ab, " dev="); + audit_log_untrustedstring(ab, inode->i_sb->s_id); + audit_log_format(ab, " ino=%lu", inode->i_ino); + audit_log_format(ab, " i_version=%llu ", + inode_query_iversion(inode)); + iint = integrity_inode_get(inode); + if (iint) { + audit_log_format(ab, " five_status=%d ", + five_get_cache_status(iint)); + audit_log_format(ab, " version=%llu ", + (unsigned long long)iint->version); + } + } + audit_log_format(ab, " res=%d", result); + audit_log_end(ab); + +exit: + if (pathbuf) + __putname(pathbuf); +} + +void five_audit_tee_msg(const char *func, const char *cause, int rc, + uint32_t origin) +{ + struct audit_buffer *ab; + + ab = audit_log_start(current->audit_context, GFP_KERNEL, + AUDIT_INTEGRITY_DATA); + if (unlikely(!ab)) { + pr_err("Can't get a context of audit logs\n"); + return; + } + + audit_log_format(ab, " func="); + audit_log_string(ab, func); + audit_log_format(ab, " cause="); + audit_log_string(ab, cause); + audit_log_format(ab, " rc=0x%x, ", rc); + audit_log_format(ab, " origin=0x%x", origin); + audit_log_end(ab); +} + +void five_audit_hexinfo(struct file *file, const char *msg, char *data, + size_t data_length) +{ + struct audit_buffer *ab; + struct inode *inode = NULL; + const unsigned char *fname = NULL; + char filename[NAME_MAX]; + char *pathbuf = NULL; + struct integrity_iint_cache *iint = NULL; + + if (file) { + fname = five_d_path(&file->f_path, &pathbuf, filename); + inode = file_inode(file); + } + + ab = audit_log_start(current->audit_context, GFP_KERNEL, + AUDIT_INTEGRITY_DATA); + if (unlikely(!ab)) { + pr_err("Can't get a context of audit logs\n"); + goto exit; + } + + if (fname) { + audit_log_format(ab, " name="); + audit_log_untrustedstring(ab, fname); + } + if (inode) { + audit_log_format(ab, " i_version=%llu ", + inode_query_iversion(inode)); + + iint = integrity_inode_get(inode); + if (iint) { + audit_log_format(ab, " cache_value=%lu ", + iint->five_flags); + audit_log_format(ab, " five_status=%d ", + five_get_cache_status(iint)); + audit_log_format(ab, " version=%llu ", + (unsigned long long)iint->version); + } + } + + audit_log_string(ab, msg); + audit_log_n_hex(ab, data, data_length); + audit_log_end(ab); +exit: + if (pathbuf) + __putname(pathbuf); +} diff --git a/security/samsung/five/gki/five_audit.h b/security/samsung/five/gki/five_audit.h new file mode 100644 index 000000000000..e0838399a305 --- /dev/null +++ b/security/samsung/five/gki/five_audit.h @@ -0,0 +1,40 @@ +/* + * Audit calls for FIVE audit subsystem. + * + * Copyright (C) 2017 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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_FIVE_AUDIT_H +#define __LINUX_FIVE_AUDIT_H + +#include + +void five_audit_verbose(struct task_struct *task, struct file *file, + const char *op, enum task_integrity_value prev, + enum task_integrity_value tint, const char *cause, int result); +void five_audit_info(struct task_struct *task, struct file *file, + const char *op, enum task_integrity_value prev, + enum task_integrity_value tint, const char *cause, int result); +void five_audit_err(struct task_struct *task, struct file *file, + const char *op, enum task_integrity_value prev, + enum task_integrity_value tint, const char *cause, int result); +void five_audit_sign_err(struct task_struct *task, struct file *file, + const char *op, enum task_integrity_value prev, + enum task_integrity_value tint, const char *cause, int result); +void five_audit_tee_msg(const char *func, const char *cause, int rc, + uint32_t origin); + +void five_audit_hexinfo(struct file *file, + const char *msg, char *data, size_t data_length); + +#endif diff --git a/security/samsung/five/gki/five_cache.c b/security/samsung/five/gki/five_cache.c new file mode 100644 index 000000000000..c23f6028c78b --- /dev/null +++ b/security/samsung/five/gki/five_cache.c @@ -0,0 +1,44 @@ +/* + * FIVE cache functions + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * + * Egor Uleyskiy, + * Viacheslav Vovchenko + * Yevgen Kopylov + * + * 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 "five_cache.h" +#include "five_porting.h" + +enum five_file_integrity five_get_cache_status( + const struct integrity_iint_cache *iint) +{ + if (unlikely(!iint)) + return FIVE_FILE_UNKNOWN; + + if (!inode_eq_iversion(iint->inode, iint->version)) + return FIVE_FILE_UNKNOWN; + + return iint->five_status; +} + +void five_set_cache_status(struct integrity_iint_cache *iint, + enum five_file_integrity status) +{ + if (unlikely(!iint)) + return; + + iint->version = inode_query_iversion(iint->inode); + iint->five_status = status; +} + diff --git a/security/samsung/five/gki/five_cache.h b/security/samsung/five/gki/five_cache.h new file mode 100644 index 000000000000..a40bfa019f96 --- /dev/null +++ b/security/samsung/five/gki/five_cache.h @@ -0,0 +1,30 @@ +/* + * FIVE cache functions + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * + * Egor Uleyskiy, + * Viacheslav Vovchenko + * Yevgen Kopylov + * + * 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_FIVE_CACHE_H +#define __LINUX_FIVE_CACHE_H + +#include "security/integrity/integrity.h" + +enum five_file_integrity five_get_cache_status( + const struct integrity_iint_cache *iint); +void five_set_cache_status(struct integrity_iint_cache *iint, + enum five_file_integrity status); + +#endif // __LINUX_FIVE_CACHE_H diff --git a/security/samsung/five/gki/five_crypto.c b/security/samsung/five/gki/five_crypto.c new file mode 100644 index 000000000000..08c9399fd83c --- /dev/null +++ b/security/samsung/five/gki/five_crypto.c @@ -0,0 +1,537 @@ +/* + * This code is based on IMA's code + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * + * Egor Uleyskiy, + * Viacheslav Vovchenko + * Yevgen Kopylov + * + * 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "five.h" +#include "five_porting.h" +#include "five_vfs.h" + +struct ahash_completion { + struct completion completion; + int err; +}; + +/* minimum file size for ahash use */ +static unsigned long five_ahash_minsize; +module_param_named(ahash_minsize, five_ahash_minsize, ulong, 0644); +MODULE_PARM_DESC(ahash_minsize, "Minimum file size for ahash use"); + +/* default is 0 - 1 page. */ +static int five_maxorder; +static unsigned long five_bufsize = PAGE_SIZE; + +static int param_set_bufsize(const char *val, const struct kernel_param *kp) +{ + unsigned long long size; + int order; + + size = memparse(val, NULL); + order = get_order(size); + if (order >= MAX_ORDER) + return -EINVAL; + five_maxorder = order; + five_bufsize = PAGE_SIZE << order; + return 0; +} + +static const struct kernel_param_ops param_ops_bufsize = { + .set = param_set_bufsize, + .get = param_get_uint, +}; + +#define param_check_bufsize(name, p) __param_check(name, p, unsigned int) + +module_param_named(ahash_bufsize, five_bufsize, ulong, 0644); +MODULE_PARM_DESC(ahash_bufsize, "Maximum ahash buffer size"); + +static struct crypto_shash *five_shash_tfm; +static struct crypto_ahash *five_ahash_tfm; + +int __init five_init_crypto(void) +{ + long rc; + + five_shash_tfm = crypto_alloc_shash( + hash_algo_name[five_hash_algo], 0, 0); + if (IS_ERR(five_shash_tfm)) { + rc = PTR_ERR(five_shash_tfm); + pr_err("Can not allocate %s (reason: %ld)\n", + hash_algo_name[five_hash_algo], rc); + return rc; + } + return 0; +} + +static struct crypto_shash *five_alloc_tfm(enum hash_algo algo) +{ + struct crypto_shash *tfm = five_shash_tfm; + int rc; + + if (algo < 0 || algo >= HASH_ALGO__LAST) + algo = five_hash_algo; + + if (algo != five_hash_algo) { + tfm = crypto_alloc_shash(hash_algo_name[algo], 0, 0); + if (IS_ERR(tfm)) { + rc = PTR_ERR(tfm); + pr_err("Can not allocate %s (reason: %d)\n", + hash_algo_name[algo], rc); + } + } + return tfm; +} + +static void five_free_tfm(struct crypto_shash *tfm) +{ + if (tfm != five_shash_tfm) + crypto_free_shash(tfm); +} + +/** + * five_alloc_pages() - Allocate contiguous pages. + * @max_size: Maximum amount of memory to allocate. + * @allocated_size: Returned size of actual allocation. + * @last_warn: Should the min_size allocation warn or not. + * + * Tries to do opportunistic allocation for memory first trying to allocate + * max_size amount of memory and then splitting that until zero order is + * reached. Allocation is tried without generating allocation warnings unless + * last_warn is set. Last_warn set affects only last allocation of zero order. + * + * By default, five_maxorder is 0 and it is equivalent to kmalloc(GFP_KERNEL) + * + * Return pointer to allocated memory, or NULL on failure. + */ +static void *five_alloc_pages(loff_t max_size, size_t *allocated_size, + int last_warn) +{ + void *ptr; + int order = five_maxorder; + gfp_t gfp_mask = __GFP_RECLAIM | __GFP_NOWARN | __GFP_NORETRY; + + if (order) + order = min(get_order(max_size), order); + + for (; order; order--) { + ptr = (void *)__get_free_pages(gfp_mask, order); + if (ptr) { + *allocated_size = PAGE_SIZE << order; + return ptr; + } + } + + /* order is zero - one page */ + + gfp_mask = GFP_KERNEL; + + if (!last_warn) + gfp_mask |= __GFP_NOWARN; + + ptr = (void *)__get_free_pages(gfp_mask, 0); + if (ptr) { + *allocated_size = PAGE_SIZE; + return ptr; + } + + *allocated_size = 0; + return NULL; +} + +/** + * five_free_pages() - Free pages allocated by five_alloc_pages(). + * @ptr: Pointer to allocated pages. + * @size: Size of allocated buffer. + */ +static void five_free_pages(void *ptr, size_t size) +{ + if (!ptr) + return; + free_pages((unsigned long)ptr, get_order(size)); +} + +static struct crypto_ahash *five_alloc_atfm(enum hash_algo algo) +{ + struct crypto_ahash *tfm = five_ahash_tfm; + int rc; + + if (algo < 0 || algo >= HASH_ALGO__LAST) + algo = five_hash_algo; + + if (algo != five_hash_algo || !tfm) { + tfm = crypto_alloc_ahash(hash_algo_name[algo], 0, 0); + if (!IS_ERR(tfm)) { + if (algo == five_hash_algo) + five_ahash_tfm = tfm; + } else { + rc = PTR_ERR(tfm); + pr_err("Can not allocate %s (reason: %d)\n", + hash_algo_name[algo], rc); + } + } + return tfm; +} + +static void five_free_atfm(struct crypto_ahash *tfm) +{ + if (tfm != five_ahash_tfm) + crypto_free_ahash(tfm); +} + +static void ahash_complete(struct crypto_async_request *req, int err) +{ + struct ahash_completion *res = req->data; + + if (err == -EINPROGRESS) + return; + res->err = err; + complete(&res->completion); +} + +static int ahash_wait(int err, struct ahash_completion *res) +{ + try_to_freeze(); + + switch (err) { + case 0: + break; + case -EINPROGRESS: + case -EBUSY: + wait_for_completion(&res->completion); + reinit_completion(&res->completion); + err = res->err; + /* fall through */ + default: + pr_crit_ratelimited("ahash calculation failed: err: %d\n", err); + } + + return err; +} + +static int five_calc_file_hash_atfm(struct file *file, + u8 *hash, size_t *hash_len, + struct crypto_ahash *tfm) +{ + const size_t len = crypto_ahash_digestsize(tfm); + loff_t i_size, offset; + char *rbuf[2] = { NULL, }; + int rc, read = 0, rbuf_len, active = 0, ahash_rc = 0; + struct ahash_request *req; + struct scatterlist sg[1]; + struct ahash_completion res; + size_t rbuf_size[2]; + + if (*hash_len < len) + return -EINVAL; + + req = ahash_request_alloc(tfm, GFP_KERNEL); + if (!req) + return -ENOMEM; + + init_completion(&res.completion); + ahash_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG | + CRYPTO_TFM_REQ_MAY_SLEEP, + ahash_complete, &res); + + rc = ahash_wait(crypto_ahash_init(req), &res); + if (rc) + goto out1; + + i_size = i_size_read(file_inode(file)); + + if (i_size == 0) + goto out2; + + /* + * Try to allocate maximum size of memory. + * Fail if even a single page cannot be allocated. + */ + rbuf[0] = five_alloc_pages(i_size, &rbuf_size[0], 1); + if (!rbuf[0]) { + rc = -ENOMEM; + goto out1; + } + + /* Only allocate one buffer if that is enough. */ + if (i_size > rbuf_size[0]) { + /* + * Try to allocate secondary buffer. If that fails fallback to + * using single buffering. Use previous memory allocation size + * as baseline for possible allocation size. + */ + rbuf[1] = five_alloc_pages(i_size - rbuf_size[0], + &rbuf_size[1], 0); + } + + if (!(file->f_mode & FMODE_READ)) { + file->f_mode |= FMODE_READ; + read = 1; + } + + for (offset = 0; offset < i_size; offset += rbuf_len) { + if (!rbuf[1] && offset) { + /* Not using two buffers, and it is not the first + * read/request, wait for the completion of the + * previous ahash_update() request. + */ + rc = ahash_wait(ahash_rc, &res); + if (rc) + goto out3; + } + /* read buffer */ + rbuf_len = min_t(loff_t, i_size - offset, rbuf_size[active]); + rc = five_kernel_read(file, offset, rbuf[active], + rbuf_len); + if (rc != rbuf_len) + goto out3; + + if (rbuf[1] && offset) { + /* Using two buffers, and it is not the first + * read/request, wait for the completion of the + * previous ahash_update() request. + */ + rc = ahash_wait(ahash_rc, &res); + if (rc) + goto out3; + } + + sg_init_one(&sg[0], rbuf[active], rbuf_len); + ahash_request_set_crypt(req, sg, NULL, rbuf_len); + + ahash_rc = crypto_ahash_update(req); + + if (rbuf[1]) + active = !active; /* swap buffers, if we use two */ + } + /* wait for the last update request to complete */ + rc = ahash_wait(ahash_rc, &res); +out3: + if (read) + file->f_mode &= ~FMODE_READ; + five_free_pages(rbuf[0], rbuf_size[0]); + five_free_pages(rbuf[1], rbuf_size[1]); +out2: + if (!rc) { + ahash_request_set_crypt(req, NULL, hash, 0); + rc = ahash_wait(crypto_ahash_final(req), &res); + if (!rc) + *hash_len = len; + } +out1: + ahash_request_free(req); + return rc; +} + +static int five_calc_file_ahash(struct file *file, + u8 hash_algo, u8 *hash, + size_t *hash_len) +{ + struct crypto_ahash *tfm; + int rc; + + tfm = five_alloc_atfm(hash_algo); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + + rc = five_calc_file_hash_atfm(file, hash, hash_len, tfm); + + five_free_atfm(tfm); + + return rc; +} + +static int five_calc_file_hash_tfm(struct file *file, + u8 *hash, size_t *hash_len, + struct crypto_shash *tfm) +{ + SHASH_DESC_ON_STACK(shash, tfm); + const size_t len = crypto_shash_digestsize(tfm); + loff_t i_size, offset = 0; + char *rbuf; + int rc, read = 0; + + if (*hash_len < len) + return -EINVAL; + + shash->tfm = tfm; + #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0) + shash->flags = 0; + #endif + + rc = crypto_shash_init(shash); + if (rc != 0) + return rc; + + i_size = i_size_read(file_inode(file)); + + if (i_size == 0) + goto out; + + rbuf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!rbuf) + return -ENOMEM; + + if (!(file->f_mode & FMODE_READ)) { + file->f_mode |= FMODE_READ; + read = 1; + } + + while (offset < i_size) { + int rbuf_len; + + rbuf_len = five_kernel_read(file, offset, rbuf, PAGE_SIZE); + if (rbuf_len < 0) { + rc = rbuf_len; + break; + } + if (rbuf_len == 0) + break; + offset += rbuf_len; + + try_to_freeze(); + + rc = crypto_shash_update(shash, rbuf, rbuf_len); + if (rc) + break; + } + if (read) + file->f_mode &= ~FMODE_READ; + kfree(rbuf); +out: + if (!rc) + rc = crypto_shash_final(shash, hash); + + if (!rc) + *hash_len = len; + + return rc; +} + +static int five_calc_hash_tfm(const u8 *data, size_t data_len, + u8 *hash, size_t *hash_len, struct crypto_shash *tfm) +{ + SHASH_DESC_ON_STACK(shash, tfm); + const size_t len = crypto_shash_digestsize(tfm); + int rc; + + if (*hash_len < len || data_len == 0) + return -EINVAL; + + shash->tfm = tfm; + #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0) + shash->flags = 0; + #endif + + rc = crypto_shash_init(shash); + if (rc != 0) + return rc; + + rc = crypto_shash_update(shash, data, data_len); + if (!rc) { + rc = crypto_shash_final(shash, hash); + + if (!rc) + *hash_len = len; + } + + return rc; +} + +static int five_calc_file_shash(struct file *file, + u8 hash_algo, + u8 *hash, + size_t *hash_len) +{ + struct crypto_shash *tfm; + int rc; + + tfm = five_alloc_tfm(hash_algo); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + + rc = five_calc_file_hash_tfm(file, hash, hash_len, tfm); + + five_free_tfm(tfm); + + return rc; +} + +static int five_calc_data_shash(const u8 *data, size_t data_len, u8 hash_algo, + u8 *hash, size_t *hash_len) +{ + struct crypto_shash *tfm; + int rc; + + tfm = five_alloc_tfm(hash_algo); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + + rc = five_calc_hash_tfm(data, data_len, hash, hash_len, tfm); + + five_free_tfm(tfm); + + return rc; +} + +/* + * five_calc_file_hash - calculate file hash + * + * Asynchronous hash (ahash) allows using HW acceleration for calculating + * a hash. ahash performance varies for different data sizes on different + * crypto accelerators. shash performance might be better for smaller files. + * The 'five.ahash_minsize' module parameter allows specifying the best + * minimum file size for using ahash on the system. + * + * If the five.ahash_minsize parameter is not specified, this function uses + * shash for the hash calculation. If ahash fails, it falls back to using + * shash. + */ +int five_calc_file_hash(struct file *file, u8 hash_algo, u8 *hash, + size_t *hash_len) +{ + loff_t i_size; + int rc; + + i_size = i_size_read(file_inode(file)); + + if (five_ahash_minsize && i_size >= five_ahash_minsize) { + rc = five_calc_file_ahash(file, hash_algo, hash, hash_len); + if (!rc) + return 0; + } + + return five_calc_file_shash(file, hash_algo, hash, hash_len); +} + +int five_calc_data_hash(const uint8_t *data, size_t data_len, + uint8_t hash_algo, uint8_t *hash, size_t *hash_len) +{ + return five_calc_data_shash(data, data_len, hash_algo, hash, hash_len); +} diff --git a/security/samsung/five/gki/five_hooks.c b/security/samsung/five/gki/five_hooks.c new file mode 100644 index 000000000000..6bc3fd9ece89 --- /dev/null +++ b/security/samsung/five/gki/five_hooks.c @@ -0,0 +1,99 @@ +/* + * Five Event interface + * + * Copyright (C) 2018 Samsung Electronics, Inc. + * Ivan Vorobiov, + * + * 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 "five_hooks.h" +#include "five_porting.h" + +#include +#include + +#define call_void_hook(FUNC, ...) \ + do { \ + struct five_hook_list *P; \ + \ + list_for_each_entry(P, &five_hook_heads.FUNC, list) \ + P->hook.FUNC(__VA_ARGS__); \ + } while (0) + +struct five_hook_heads five_hook_heads = { + .file_processed = + LIST_HEAD_INIT(five_hook_heads.file_processed), + .file_skipped = + LIST_HEAD_INIT(five_hook_heads.file_skipped), + .file_signed = + LIST_HEAD_INIT(five_hook_heads.file_signed), + .task_forked = + LIST_HEAD_INIT(five_hook_heads.task_forked), + .integrity_reset = + LIST_HEAD_INIT(five_hook_heads.integrity_reset), + .integrity_reset2 = + LIST_HEAD_INIT(five_hook_heads.integrity_reset2), +}; + +void five_hook_file_processed(struct task_struct *task, + struct file *file, void *xattr, + size_t xattr_size, int result) +{ + call_void_hook(file_processed, + task, + task_integrity_read(TASK_INTEGRITY(task)), + file, + xattr, + xattr_size, + result); +} + +void five_hook_file_signed(struct task_struct *task, + struct file *file, void *xattr, + size_t xattr_size, int result) +{ + call_void_hook(file_signed, + task, + task_integrity_read(TASK_INTEGRITY(task)), + file, + xattr, + xattr_size, + result); +} + +void five_hook_file_skipped(struct task_struct *task, struct file *file) +{ + call_void_hook(file_skipped, + task, + task_integrity_read(TASK_INTEGRITY(task)), + file); +} + +void five_hook_task_forked(struct task_struct *parent, + struct task_struct *child) +{ + call_void_hook(task_forked, + parent, + task_integrity_read(TASK_INTEGRITY(parent)), + child, + task_integrity_read(TASK_INTEGRITY(child))); +} + +void five_hook_integrity_reset(struct task_struct *task, + struct file *file, + enum task_integrity_reset_cause cause) +{ + if (task == NULL) + return; + + call_void_hook(integrity_reset, task); + call_void_hook(integrity_reset2, task, file, cause); +} diff --git a/security/samsung/five/gki/five_hooks.h b/security/samsung/five/gki/five_hooks.h new file mode 100644 index 000000000000..bd2bdfb34061 --- /dev/null +++ b/security/samsung/five/gki/five_hooks.h @@ -0,0 +1,109 @@ +/* + * Five Event interface + * + * Copyright (C) 2018 Samsung Electronics, Inc. + * Ivan Vorobiov, + * + * 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 _FIVE_HOOKS_H +#define _FIVE_HOOKS_H + +#include +#include +#include +#include + +void five_hook_file_processed(struct task_struct *task, + struct file *file, void *xattr, + size_t xattr_size, int result); + +void five_hook_task_forked(struct task_struct *parent, + struct task_struct *child); + +void five_hook_file_skipped(struct task_struct *task, + struct file *file); + +void five_hook_file_signed(struct task_struct *task, + struct file *file, void *xattr, + size_t xattr_size, int result); + +void five_hook_integrity_reset(struct task_struct *task, + struct file *file, + enum task_integrity_reset_cause cause); + +union five_list_options { + void (*file_processed)(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file, + void *xattr, + size_t xattr_size, + int result); + + void (*file_skipped)(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file); + + void (*task_forked)(struct task_struct *parent, + enum task_integrity_value parent_tint_value, + struct task_struct *child, + enum task_integrity_value child_tint_value); + + void (*file_signed)(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file, void *xattr, + size_t xattr_size, int result); + void (*integrity_reset)(struct task_struct *task); + void (*integrity_reset2)(struct task_struct *task, + struct file *file, + enum task_integrity_reset_cause cause); +}; + +struct five_hook_heads { + struct list_head file_processed; + struct list_head file_skipped; + struct list_head file_signed; + struct list_head task_forked; + struct list_head integrity_reset; + struct list_head integrity_reset2; +}; + +/* + * FIVE module hook list structure. + * For use with generic list macros for common operations. + */ +struct five_hook_list { + struct list_head list; + struct list_head *head; + union five_list_options hook; +}; + +/* + * Initializing a five_hook_list structure takes + * up a lot of space in a source file. This macro takes + * care of the common case and reduces the amount of + * text involved. + */ +#define FIVE_HOOK_INIT(HEAD, HOOK) \ + { .head = &five_hook_heads.HEAD, .hook = { .HEAD = HOOK } } + +extern struct five_hook_heads five_hook_heads; + +static inline void five_add_hooks(struct five_hook_list *hooks, + int count) +{ + int i; + + for (i = 0; i < count; i++) + list_add_tail_rcu(&hooks[i].list, hooks[i].head); +} + +#endif /* _FIVE_HOOKS_H */ diff --git a/security/samsung/five/gki/five_iint.c b/security/samsung/five/gki/five_iint.c new file mode 100644 index 000000000000..36e6c0824f43 --- /dev/null +++ b/security/samsung/five/gki/five_iint.c @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2008 IBM Corporation + * + * Authors: + * Mimi Zohar + * + * File: integrity_iint.c + * - implements the integrity hooks: integrity_inode_alloc, + * integrity_inode_free + * - cache integrity information associated with an inode + * using a rbtree tree. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_FIVE +#include +#endif +#include "integrity.h" + +static struct rb_root integrity_iint_tree = RB_ROOT; +static DEFINE_RWLOCK(integrity_iint_lock); +static struct kmem_cache *iint_cache __read_mostly; + +struct dentry *integrity_dir; + +/* + * __integrity_iint_find - return the iint associated with an inode + */ +static struct integrity_iint_cache *__integrity_iint_find(struct inode *inode) +{ + struct integrity_iint_cache *iint; + struct rb_node *n = integrity_iint_tree.rb_node; + + while (n) { + iint = rb_entry(n, struct integrity_iint_cache, rb_node); + + if (inode < iint->inode) + n = n->rb_left; + else if (inode > iint->inode) + n = n->rb_right; + else + break; + } + if (!n) + return NULL; + + return iint; +} + +/* + * integrity_iint_find - return the iint associated with an inode + */ +struct integrity_iint_cache *integrity_iint_find(struct inode *inode) +{ + struct integrity_iint_cache *iint; + + if (!IS_IMA(inode)) + return NULL; + + read_lock(&integrity_iint_lock); + iint = __integrity_iint_find(inode); + read_unlock(&integrity_iint_lock); + + return iint; +} + +static void iint_free(struct integrity_iint_cache *iint) +{ +#ifdef CONFIG_FIVE + kfree(iint->five_label); + iint->five_label = NULL; + iint->five_flags = 0UL; + iint->five_status = FIVE_FILE_UNKNOWN; + iint->five_signing = false; +#endif + kfree(iint->ima_hash); + iint->ima_hash = NULL; + iint->version = 0; + iint->flags = 0UL; + iint->atomic_flags = 0UL; + iint->ima_file_status = INTEGRITY_UNKNOWN; + iint->ima_mmap_status = INTEGRITY_UNKNOWN; + iint->ima_bprm_status = INTEGRITY_UNKNOWN; + iint->ima_read_status = INTEGRITY_UNKNOWN; + iint->ima_creds_status = INTEGRITY_UNKNOWN; + iint->evm_status = INTEGRITY_UNKNOWN; + iint->measured_pcrs = 0; + kmem_cache_free(iint_cache, iint); +} + +/** + * integrity_inode_get - find or allocate an iint associated with an inode + * @inode: pointer to the inode + * @return: allocated iint + * + * Caller must lock i_mutex + */ +struct integrity_iint_cache *integrity_inode_get(struct inode *inode) +{ + struct rb_node **p; + struct rb_node *node, *parent = NULL; + struct integrity_iint_cache *iint, *test_iint; + + iint = integrity_iint_find(inode); + if (iint) + return iint; + + iint = kmem_cache_alloc(iint_cache, GFP_NOFS); + if (!iint) + return NULL; + + write_lock(&integrity_iint_lock); + + p = &integrity_iint_tree.rb_node; + while (*p) { + parent = *p; + test_iint = rb_entry(parent, struct integrity_iint_cache, + rb_node); + if (inode < test_iint->inode) + p = &(*p)->rb_left; + else + p = &(*p)->rb_right; + } + + iint->inode = inode; + node = &iint->rb_node; + inode->i_flags |= S_IMA; + rb_link_node(node, parent, p); + rb_insert_color(node, &integrity_iint_tree); + + write_unlock(&integrity_iint_lock); + return iint; +} + +/** + * integrity_inode_free - called on security_inode_free + * @inode: pointer to the inode + * + * Free the integrity information(iint) associated with an inode. + */ +void integrity_inode_free(struct inode *inode) +{ + struct integrity_iint_cache *iint; + + if (!IS_IMA(inode)) + return; + + write_lock(&integrity_iint_lock); + iint = __integrity_iint_find(inode); + rb_erase(&iint->rb_node, &integrity_iint_tree); + write_unlock(&integrity_iint_lock); + + iint_free(iint); +} + +static void init_once(void *foo) +{ + struct integrity_iint_cache *iint = foo; + + memset(iint, 0, sizeof(*iint)); +#ifdef CONFIG_FIVE + iint->five_flags = 0UL; + iint->five_status = FIVE_FILE_UNKNOWN; + iint->five_signing = false; +#endif + iint->ima_file_status = INTEGRITY_UNKNOWN; + iint->ima_mmap_status = INTEGRITY_UNKNOWN; + iint->ima_bprm_status = INTEGRITY_UNKNOWN; + iint->ima_read_status = INTEGRITY_UNKNOWN; + iint->ima_creds_status = INTEGRITY_UNKNOWN; + iint->evm_status = INTEGRITY_UNKNOWN; + mutex_init(&iint->mutex); +} + +static int __init integrity_iintcache_init(void) +{ + iint_cache = + kmem_cache_create("iint_cache", sizeof(struct integrity_iint_cache), + 0, SLAB_PANIC, init_once); + return 0; +} + +DEFINE_LSM(integrity) = { + .name = "integrity", + .init = integrity_iintcache_init, +}; + +/* + * integrity_kernel_read - read data from the file + * + * This is a function for reading file content instead of kernel_read(). + * It does not perform locking checks to ensure it cannot be blocked. + * It does not perform security checks because it is irrelevant for IMA. + * + */ +int integrity_kernel_read(struct file *file, loff_t offset, + void *addr, unsigned long count) +{ + mm_segment_t old_fs; + char __user *buf = (char __user *)addr; + ssize_t ret; +#ifdef CONFIG_FIVE + struct inode *inode = file_inode(file); +#endif + + if (!(file->f_mode & FMODE_READ)) + return -EBADF; + + old_fs = get_fs(); + set_fs(KERNEL_DS); + +#ifdef CONFIG_FIVE + if (inode->i_sb->s_magic == OVERLAYFS_SUPER_MAGIC && file->private_data) + file = (struct file *)file->private_data; +#endif + + ret = __vfs_read(file, buf, count, &offset); + set_fs(old_fs); + + return ret; +} + +/* + * integrity_load_keys - load integrity keys hook + * + * Hooks is called from init/main.c:kernel_init_freeable() + * when rootfs is ready + */ +void __init integrity_load_keys(void) +{ + ima_load_x509(); + evm_load_x509(); +} + +static int __init integrity_fs_init(void) +{ + integrity_dir = securityfs_create_dir("integrity", NULL); + if (IS_ERR(integrity_dir)) { + int ret = PTR_ERR(integrity_dir); + + if (ret != -ENODEV) + pr_err("Unable to create integrity sysfs dir: %d\n", + ret); + integrity_dir = NULL; + return ret; + } + + return 0; +} + +late_initcall(integrity_fs_init) diff --git a/security/samsung/five/gki/five_iint.h b/security/samsung/five/gki/five_iint.h new file mode 100644 index 000000000000..03413eca10da --- /dev/null +++ b/security/samsung/five/gki/five_iint.h @@ -0,0 +1,286 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2009-2010 IBM Corporation + * + * Authors: + * Mimi Zohar + */ + +#include +#include +#include +#include +#include + +struct integrity_label; + +enum five_file_integrity { + FIVE_FILE_UNKNOWN, + FIVE_FILE_FAIL, + FIVE_FILE_RSA, + FIVE_FILE_DMVERITY, + FIVE_FILE_FSVERITY, + FIVE_FILE_HMAC +}; + +/* iint action cache flags */ +#define IMA_MEASURE 0x00000001 +#define IMA_MEASURED 0x00000002 +#define IMA_APPRAISE 0x00000004 +#define IMA_APPRAISED 0x00000008 +/*#define IMA_COLLECT 0x00000010 do not use this flag */ +#define IMA_COLLECTED 0x00000020 +#define IMA_AUDIT 0x00000040 +#define IMA_AUDITED 0x00000080 +#define IMA_HASH 0x00000100 +#define IMA_HASHED 0x00000200 + +/* iint cache flags */ +#define IMA_ACTION_FLAGS 0xff000000 +#define IMA_DIGSIG_REQUIRED 0x01000000 +#define IMA_PERMIT_DIRECTIO 0x02000000 +#define IMA_NEW_FILE 0x04000000 +#define EVM_IMMUTABLE_DIGSIG 0x08000000 +#define IMA_FAIL_UNVERIFIABLE_SIGS 0x10000000 +#define IMA_MODSIG_ALLOWED 0x20000000 + +#define IMA_DO_MASK (IMA_MEASURE | IMA_APPRAISE | IMA_AUDIT | \ + IMA_HASH | IMA_APPRAISE_SUBMASK) +#define IMA_DONE_MASK (IMA_MEASURED | IMA_APPRAISED | IMA_AUDITED | \ + IMA_HASHED | IMA_COLLECTED | \ + IMA_APPRAISED_SUBMASK) + +/* iint subaction appraise cache flags */ +#define IMA_FILE_APPRAISE 0x00001000 +#define IMA_FILE_APPRAISED 0x00002000 +#define IMA_MMAP_APPRAISE 0x00004000 +#define IMA_MMAP_APPRAISED 0x00008000 +#define IMA_BPRM_APPRAISE 0x00010000 +#define IMA_BPRM_APPRAISED 0x00020000 +#define IMA_READ_APPRAISE 0x00040000 +#define IMA_READ_APPRAISED 0x00080000 +#define IMA_CREDS_APPRAISE 0x00100000 +#define IMA_CREDS_APPRAISED 0x00200000 + +#define FIVE_DMVERITY_PROTECTED 0x00040000 +#define FIVE_TRUSTED_FILE 0x00080000 + +#define IMA_APPRAISE_SUBMASK (IMA_FILE_APPRAISE | IMA_MMAP_APPRAISE | \ + IMA_BPRM_APPRAISE | IMA_READ_APPRAISE | \ + IMA_CREDS_APPRAISE) +#define IMA_APPRAISED_SUBMASK (IMA_FILE_APPRAISED | IMA_MMAP_APPRAISED | \ + IMA_BPRM_APPRAISED | IMA_READ_APPRAISED | \ + IMA_CREDS_APPRAISED) + +/* iint cache atomic_flags */ +#define IMA_CHANGE_XATTR 0 +#define IMA_UPDATE_XATTR 1 +#define IMA_CHANGE_ATTR 2 +#define IMA_DIGSIG 3 +#define IMA_MUST_MEASURE 4 + +enum evm_ima_xattr_type { + IMA_XATTR_DIGEST = 0x01, + EVM_XATTR_HMAC, + EVM_IMA_XATTR_DIGSIG, + IMA_XATTR_DIGEST_NG, + EVM_XATTR_PORTABLE_DIGSIG, + IMA_XATTR_LAST +}; + +struct evm_ima_xattr_data { + u8 type; + u8 data[]; +} __packed; + +/* Only used in the EVM HMAC code. */ +struct evm_xattr { + struct evm_ima_xattr_data data; + u8 digest[SHA1_DIGEST_SIZE]; +} __packed; + +#define IMA_MAX_DIGEST_SIZE 64 + +struct ima_digest_data { + u8 algo; + u8 length; + union { + struct { + u8 unused; + u8 type; + } sha1; + struct { + u8 type; + u8 algo; + } ng; + u8 data[2]; + } xattr; + u8 digest[0]; +} __packed; + +/* + * signature format v2 - for using with asymmetric keys + */ +struct signature_v2_hdr { + uint8_t type; /* xattr type */ + uint8_t version; /* signature format version */ + uint8_t hash_algo; /* Digest algorithm [enum hash_algo] */ + __be32 keyid; /* IMA key identifier - not X509/PGP specific */ + __be16 sig_size; /* signature size */ + uint8_t sig[0]; /* signature payload */ +} __packed; + +/* integrity data associated with an inode */ +struct integrity_iint_cache { + struct rb_node rb_node; /* rooted in integrity_iint_tree */ + struct mutex mutex; /* protects: version, flags, digest */ + struct inode *inode; /* back pointer to inode in question */ + u64 version; /* track inode changes */ + unsigned long flags; + unsigned long measured_pcrs; + unsigned long atomic_flags; + enum integrity_status ima_file_status:4; + enum integrity_status ima_mmap_status:4; + enum integrity_status ima_bprm_status:4; + enum integrity_status ima_read_status:4; + enum integrity_status ima_creds_status:4; + enum integrity_status evm_status:4; + struct ima_digest_data *ima_hash; +#ifdef CONFIG_FIVE + unsigned long five_flags; + enum five_file_integrity five_status; + struct integrity_label *five_label; + bool five_signing; +#endif +}; + +/* rbtree tree calls to lookup, insert, delete + * integrity data associated with an inode. + */ +struct integrity_iint_cache *integrity_iint_find(struct inode *inode); + +int integrity_kernel_read(struct file *file, loff_t offset, + void *addr, unsigned long count); + +#define INTEGRITY_KEYRING_EVM 0 +#define INTEGRITY_KEYRING_IMA 1 +#define INTEGRITY_KEYRING_PLATFORM 2 +#define INTEGRITY_KEYRING_MAX 3 + +extern struct dentry *integrity_dir; + +struct modsig; + +#ifdef CONFIG_INTEGRITY_SIGNATURE + +int integrity_digsig_verify(const unsigned int id, const char *sig, int siglen, + const char *digest, int digestlen); +int integrity_modsig_verify(unsigned int id, const struct modsig *modsig); + +int __init integrity_init_keyring(const unsigned int id); +int __init integrity_load_x509(const unsigned int id, const char *path); +int __init integrity_load_cert(const unsigned int id, const char *source, + const void *data, size_t len, key_perm_t perm); +#else + +static inline int integrity_digsig_verify(const unsigned int id, + const char *sig, int siglen, + const char *digest, int digestlen) +{ + return -EOPNOTSUPP; +} + +static inline int integrity_modsig_verify(unsigned int id, + const struct modsig *modsig) +{ + return -EOPNOTSUPP; +} + +static inline int integrity_init_keyring(const unsigned int id) +{ + return 0; +} + +static inline int __init integrity_load_cert(const unsigned int id, + const char *source, + const void *data, size_t len, + key_perm_t perm) +{ + return 0; +} +#endif /* CONFIG_INTEGRITY_SIGNATURE */ + +#ifdef CONFIG_INTEGRITY_ASYMMETRIC_KEYS +int asymmetric_verify(struct key *keyring, const char *sig, + int siglen, const char *data, int datalen); +#else +static inline int asymmetric_verify(struct key *keyring, const char *sig, + int siglen, const char *data, int datalen) +{ + return -EOPNOTSUPP; +} +#endif + +#ifdef CONFIG_IMA_APPRAISE_MODSIG +int ima_modsig_verify(struct key *keyring, const struct modsig *modsig); +#else +static inline int ima_modsig_verify(struct key *keyring, + const struct modsig *modsig) +{ + return -EOPNOTSUPP; +} +#endif + +#ifdef CONFIG_IMA_LOAD_X509 +void __init ima_load_x509(void); +#else +static inline void ima_load_x509(void) +{ +} +#endif + +#ifdef CONFIG_EVM_LOAD_X509 +void __init evm_load_x509(void); +#else +static inline void evm_load_x509(void) +{ +} +#endif + +#ifdef CONFIG_INTEGRITY_AUDIT +/* declarations */ +void integrity_audit_msg(int audit_msgno, struct inode *inode, + const unsigned char *fname, const char *op, + const char *cause, int result, int info); + +static inline struct audit_buffer * +integrity_audit_log_start(struct audit_context *ctx, gfp_t gfp_mask, int type) +{ + return audit_log_start(ctx, gfp_mask, type); +} + +#else +static inline void integrity_audit_msg(int audit_msgno, struct inode *inode, + const unsigned char *fname, + const char *op, const char *cause, + int result, int info) +{ +} + +static inline struct audit_buffer * +integrity_audit_log_start(struct audit_context *ctx, gfp_t gfp_mask, int type) +{ + return NULL; +} + +#endif + +#ifdef CONFIG_INTEGRITY_PLATFORM_KEYRING +void __init add_to_platform_keyring(const char *source, const void *data, + size_t len); +#else +static inline void __init add_to_platform_keyring(const char *source, + const void *data, size_t len) +{ +} +#endif diff --git a/security/samsung/five/gki/five_init.c b/security/samsung/five/gki/five_init.c new file mode 100644 index 000000000000..0c6598226620 --- /dev/null +++ b/security/samsung/five/gki/five_init.c @@ -0,0 +1,45 @@ +/* + * This code is based on IMA's code + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * + * Egor Uleyskiy, + * Viacheslav Vovchenko + * Yevgen Kopylov + * + * 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include "five.h" + +int __init five_init(void) +{ + int rc; + + rc = five_keyring_init(); + if (rc) + return rc; + rc = five_load_built_x509(); + if (rc) + return rc; + rc = five_task_integrity_cache_init(); + if (rc) + return rc; + rc = five_init_crypto(); + + return rc; +} diff --git a/security/samsung/five/gki/five_main.c b/security/samsung/five/gki/five_main.c new file mode 100644 index 000000000000..44e290ddeb85 --- /dev/null +++ b/security/samsung/five/gki/five_main.c @@ -0,0 +1,1171 @@ +/* + * This code is based on IMA's code + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * + * Egor Uleyskiy, + * Viacheslav Vovchenko + * Yevgen Kopylov + * + * 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 + +#include "five.h" +#include "five_audit.h" +#include "five_hooks.h" +#include "five_state.h" +#include "five_pa.h" +#include "five_porting.h" +#include "five_cache.h" +#include "five_dmverity.h" +#include "five_dsms.h" +#include "five_tint_dev.h" + +static const bool unlink_on_error; // false + +static const bool check_dex2oat_binary = true; +static const bool check_memfd_file = true; + +static struct file *memfd_file __ro_after_init; +static bool is_five_initialized __ro_after_init; + +static struct workqueue_struct *g_five_workqueue; + +static inline void task_integrity_processing(struct task_integrity *tint); +static inline void task_integrity_done(struct task_integrity *tint); +static void process_measurement(const struct processing_event_list *params); +static inline struct processing_event_list *five_event_create( + enum five_event event, struct task_struct *task, + struct file *file, int function, gfp_t flags); +static inline void five_event_destroy( + const struct processing_event_list *file); + +#ifdef CONFIG_FIVE_DEBUG +static int five_enabled = 1; + +static ssize_t five_enabled_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + char command; + + if (get_user(command, buf)) + return -EFAULT; + + switch (command) { + case '0': + five_enabled = 0; + break; + case '1': + five_enabled = 1; + break; + default: + pr_err("FIVE: %s: unknown cmd: %hhx\n", __func__, command); + return -EINVAL; + } + + pr_info("FIVE debug: FIVE %s\n", five_enabled ? "enabled" : "disabled"); + return count; +} + +static ssize_t five_enabled_read(struct file *file, char __user *user_buf, + size_t count, loff_t *pos) +{ + char buf[2]; + + buf[0] = five_enabled ? '1' : '0'; + buf[1] = '\n'; + + return simple_read_from_buffer(user_buf, count, pos, buf, sizeof(buf)); +} + +static const struct file_operations five_enabled_fops = { + .owner = THIS_MODULE, + .read = five_enabled_read, + .write = five_enabled_write +}; + +static int __init init_fs(void) +{ + struct dentry *debug_file = NULL; + umode_t umode = (S_IRUGO | S_IWUSR | S_IWGRP); + + debug_file = debugfs_create_file( + "five_enabled", umode, NULL, NULL, &five_enabled_fops); + if (IS_ERR_OR_NULL(debug_file)) + goto error; + + return 0; +error: + if (debug_file) + return -PTR_ERR(debug_file); + + return -EEXIST; +} + +static inline int is_five_enabled(void) +{ + return five_enabled; +} + +int five_fcntl_debug(struct file *file, void __user *argp) +{ + struct inode *inode; + struct five_stat stat = {0}; + struct integrity_iint_cache *iint; + + if (unlikely(!file || !argp)) + return -EINVAL; + + inode = file_inode(file); + + inode_lock(inode); + iint = integrity_inode_get(inode); + if (unlikely(!iint)) { + inode_unlock(inode); + return -ENOMEM; + } + + stat.cache_status = five_get_cache_status(iint); + stat.cache_iversion = inode_query_iversion(iint->inode); + stat.inode_iversion = inode_query_iversion(inode); + + inode_unlock(inode); + + if (unlikely(copy_to_user(argp, &stat, sizeof(stat)))) + return -EFAULT; + + return 0; +} +#else +static int __init init_fs(void) +{ + return 0; +} + +static inline int is_five_enabled(void) +{ + return 1; +} +#endif + +static void work_handler(struct work_struct *in_data) +{ + struct worker_context *context = container_of(in_data, + struct worker_context, data_work); + struct task_integrity *intg; + + if (unlikely(!context)) + return; + + intg = context->tint; + + spin_lock(&intg->list_lock); + while (!list_empty(&(intg->events.list))) { + struct processing_event_list *five_file; + + five_file = list_entry(intg->events.list.next, + struct processing_event_list, list); + spin_unlock(&intg->list_lock); + switch (five_file->event) { + case FIVE_VERIFY_BUNCH_FILES: { + process_measurement(five_file); + break; + } + case FIVE_RESET_INTEGRITY: { + task_integrity_reset(intg); + five_hook_integrity_reset(five_file->task, + NULL, CAUSE_UNKNOWN); + break; + } + default: + break; + } + spin_lock(&intg->list_lock); + list_del(&five_file->list); + five_event_destroy(five_file); + } + + task_integrity_done(intg); + spin_unlock(&intg->list_lock); + task_integrity_put(intg); + + kfree(context); +} + +static void fix_dpath(const struct path *path, char *pathbuf, char *pathname) +{ + /* `d_path' appends " (deleted)" string if a file is unlinked. Below + * code removes it. + * `d_path' fills the buffer from the end of it therefore we can easily + * calculate the length of the pathname. + */ + const long pathname_size = pathbuf + PATH_MAX - pathname; + const char str_deleted[] = " (deleted)"; + const int deleted_size = sizeof(str_deleted); + + if (pathname_size > deleted_size && d_unlinked(path->dentry)) { + char *start_deleted = pathbuf + PATH_MAX - deleted_size; + + if (!strncmp(str_deleted, start_deleted, deleted_size)) + *start_deleted = '\0'; + } +} + +const char *five_d_path(const struct path *path, char **pathbuf, char *namebuf) +{ + char *pathname = NULL; + + *pathbuf = __getname(); + if (*pathbuf) { + pathname = d_path(path, *pathbuf, PATH_MAX); + if (IS_ERR(pathname)) { + __putname(*pathbuf); + *pathbuf = NULL; + pathname = NULL; + } + else { + fix_dpath(path, *pathbuf, pathname); + } + } + + if (!pathname) { + strlcpy(namebuf, path->dentry->d_name.name, NAME_MAX); + pathname = namebuf; + } + + return pathname; +} + +int five_check_params(struct task_struct *task, struct file *file) +{ + struct inode *inode; + + if (unlikely(!file)) + return 1; + + inode = file_inode(file); + if (!S_ISREG(inode->i_mode)) + return 1; + + return 0; +} + +/* cut a list into two + * @cur_list: a list with entries + * @new_list: a new list to add all removed entries. Should be an empty list + * + * Use it under spin_lock + * + * The function moves second entry and following entries to new list. + * First entry is left in cur_list. + * + * Initial state: + * [cur_list]<=>[first]<=>[second]<=>[third]<=>...<=>[last] [new_list] + * ^=================================================^ ^====^ + * Result: + * [cur_list]<=>[first] + * ^==========^ + * [new_list]<=>[second]<=>[third]<=>...<=>[last] + * ^=====================================^ + * + * the function is similar to kernel list_cut_position, but there are few + * differences: + * - cut position is second entry + * - original list is left with only first entry + * - moving entries are from second entry to last entry + */ +static void list_cut_tail(struct list_head *cur_list, + struct list_head *new_list) +{ + if ((!list_empty(cur_list)) && + (!list_is_singular(cur_list))) { + new_list->next = cur_list->next->next; + cur_list->next->next->prev = new_list; + cur_list->next->next = cur_list; + new_list->prev = cur_list->prev; + cur_list->prev->next = new_list; + cur_list->prev = cur_list->next; + } +} + +static void free_files_list(struct list_head *list) +{ + struct list_head *tmp, *list_entry; + struct processing_event_list *file_entry; + + list_for_each_safe(list_entry, tmp, list) { + file_entry = list_entry(list_entry, + struct processing_event_list, list); + list_del(&file_entry->list); + five_event_destroy(file_entry); + } +} + +static int push_file_event_bunch(struct task_struct *task, struct file *file, + int function) +{ + int rc = 0; + struct worker_context *context; + struct processing_event_list *five_file; + + if (unlikely(!is_five_enabled()) || five_check_params(task, file)) + return 0; + + context = kmalloc(sizeof(struct worker_context), GFP_KERNEL); + if (unlikely(!context)) + return -ENOMEM; + + five_file = five_event_create(FIVE_VERIFY_BUNCH_FILES, task, file, + function, GFP_KERNEL); + if (unlikely(!five_file)) { + kfree(context); + return -ENOMEM; + } + + spin_lock(&TASK_INTEGRITY(task)->list_lock); + + if (list_empty(&(TASK_INTEGRITY(task)->events.list))) { + task_integrity_get(TASK_INTEGRITY(task)); + task_integrity_processing(TASK_INTEGRITY(task)); + + context->tint = TASK_INTEGRITY(task); + + list_add_tail(&five_file->list, + &TASK_INTEGRITY(task)->events.list); + spin_unlock(&TASK_INTEGRITY(task)->list_lock); + INIT_WORK(&context->data_work, work_handler); + rc = queue_work(g_five_workqueue, &context->data_work) ? 0 : 1; + } else { + struct list_head dead_list; + + INIT_LIST_HEAD(&dead_list); + if ((function == BPRM_CHECK) && + (!list_is_singular(&(TASK_INTEGRITY(task)->events.list)))) { + list_cut_tail(&TASK_INTEGRITY(task)->events.list, + &dead_list); + } + list_add_tail(&five_file->list, + &TASK_INTEGRITY(task)->events.list); + spin_unlock(&TASK_INTEGRITY(task)->list_lock); + free_files_list(&dead_list); + kfree(context); + } + return rc; +} + +static int push_reset_event(struct task_struct *task, + enum task_integrity_reset_cause cause, struct file *file) +{ + struct list_head dead_list; + struct task_integrity *current_tint; + struct processing_event_list *five_reset; + + if (unlikely(!is_five_enabled())) + return 0; + + INIT_LIST_HEAD(&dead_list); + current_tint = TASK_INTEGRITY(task); + task_integrity_get(current_tint); + + task_integrity_set_reset_reason(current_tint, cause, file); + + five_reset = five_event_create(FIVE_RESET_INTEGRITY, task, NULL, 0, + GFP_KERNEL); + if (unlikely(!five_reset)) { + task_integrity_reset_both(current_tint); + five_hook_integrity_reset(task, file, cause); + task_integrity_put(current_tint); + return -ENOMEM; + } + + task_integrity_reset_both(current_tint); + five_hook_integrity_reset(task, file, cause); + spin_lock(¤t_tint->list_lock); + if (!list_empty(¤t_tint->events.list)) { + list_cut_tail(¤t_tint->events.list, &dead_list); + five_reset->event = FIVE_RESET_INTEGRITY; + list_add_tail(&five_reset->list, ¤t_tint->events.list); + spin_unlock(¤t_tint->list_lock); + } else { + spin_unlock(¤t_tint->list_lock); + five_event_destroy(five_reset); + } + + task_integrity_put(current_tint); + /* remove dead_list */ + free_files_list(&dead_list); + return 0; +} + +void task_integrity_delayed_reset(struct task_struct *task, + enum task_integrity_reset_cause cause, struct file *file) +{ + push_reset_event(task, cause, file); +} + +static void five_check_last_writer(struct integrity_iint_cache *iint, + struct inode *inode, struct file *file) +{ + fmode_t mode = file->f_mode; + + if (!(mode & FMODE_WRITE)) + return; + + inode_lock(inode); + if (atomic_read(&inode->i_writecount) == 1) { + if (!inode_eq_iversion(inode, iint->version)) + five_set_cache_status(iint, FIVE_FILE_UNKNOWN); + } + iint->five_signing = false; + inode_unlock(inode); +} + +/** + * five_file_free - called on __fput() + * @file: pointer to file structure being freed + * + * Flag files that changed, based on i_version + */ +void five_file_free(struct file *file) +{ + struct inode *inode = file_inode(file); + struct integrity_iint_cache *iint; + + if (!S_ISREG(inode->i_mode)) + return; + + fivepa_fsignature_free(file); + + iint = integrity_iint_find(inode); + if (!iint) + return; + + five_check_last_writer(iint, inode, file); +} + +void five_task_free(struct task_struct *task) +{ + task_integrity_put(TASK_INTEGRITY(task)); +} + +/* Returns string representation of input function */ +const char *five_get_string_fn(enum five_hooks fn) +{ + switch (fn) { + case FILE_CHECK: + return "file-check"; + case MMAP_CHECK: + return "mmap-check"; + case BPRM_CHECK: + return "bprm-check"; + case POST_SETATTR: + return "post-setattr"; + } + return "unknown-function"; +} + +static inline bool is_dex2oat_binary(const struct file *file) +{ + const char *pathname = NULL; + char *pathbuf = NULL; + const char * const dex2oat_full_path[] = { + /* R OS */ + "/apex/com.android.art/bin/dex2oat", + "/apex/com.android.art/bin/dex2oat32", + "/apex/com.android.art/bin/dex2oat64", + /* Q OS */ + "/apex/com.android.runtime/bin/dex2oat" + }; + char filename[NAME_MAX]; + bool res = false; + size_t i; + + if (!file || !file->f_path.dentry) + return false; + + if (strncmp(file->f_path.dentry->d_iname, "dex2oat", + sizeof("dex2oat")) && + strncmp(file->f_path.dentry->d_iname, "dex2oat32", + sizeof("dex2oat32")) && + strncmp(file->f_path.dentry->d_iname, "dex2oat64", + sizeof("dex2oat64"))) + return false; + + pathname = five_d_path(&file->f_path, &pathbuf, filename); + + for (i = 0; i < ARRAY_SIZE(dex2oat_full_path); ++i) { + if (!strncmp(pathname, dex2oat_full_path[i], + strlen(dex2oat_full_path[i]) + 1)) { + res = true; + break; + } + } + + if (pathbuf) + __putname(pathbuf); + + return res; +} + +static inline bool match_trusted_executable(const struct five_cert *cert, + const struct integrity_iint_cache *iint, + const struct file *file) +{ + const struct five_cert_header *hdr = NULL; + + if (!cert) + return check_dex2oat_binary && is_dex2oat_binary(file); + + if (five_get_cache_status(iint) != FIVE_FILE_RSA) + return false; + + hdr = (const struct five_cert_header *)cert->body.header->value; + + if (hdr->privilege == FIVE_PRIV_ALLOW_SIGN) + return true; + + return false; +} + +static inline void task_integrity_processing(struct task_integrity *tint) +{ + tint->user_value = INTEGRITY_PROCESSING; +} + +static inline void task_integrity_done(struct task_integrity *tint) +{ + tint->user_value = task_integrity_read(tint); +} + +static void process_file(struct task_struct *task, + struct file *file, + int function, + struct file_verification_result *result) +{ + struct inode *inode = d_real_inode(file_dentry(file)); + struct integrity_iint_cache *iint = NULL; + struct five_cert cert = { {0} }; + struct five_cert *pcert = NULL; + int rc = -ENOMEM; + char *xattr_value = NULL; + int xattr_len = 0; + + if (!S_ISREG(inode->i_mode)) { + rc = 0; + goto out; + } + + iint = integrity_inode_get(inode); + if (!iint) + goto out; + + /* Nothing to do, just return existing appraised status */ + if (five_get_cache_status(iint) != FIVE_FILE_UNKNOWN) { + rc = 0; + goto out; + } + + xattr_len = five_read_xattr(d_real_comp(file->f_path.dentry), + &xattr_value); + if (xattr_value && xattr_len) { + rc = five_cert_fillout(&cert, xattr_value, xattr_len); + if (rc) { + pr_err("FIVE: certificate is incorrect inode=%lu\n", + inode->i_ino); + goto out; + } + + pcert = &cert; + + if (file->f_flags & O_DIRECT) { + rc = -EACCES; + goto out; + } + } + + rc = five_appraise_measurement(task, function, iint, file, pcert); + if (!rc && match_trusted_executable(pcert, iint, file)) + iint->five_flags |= FIVE_TRUSTED_FILE; + +out: + if (rc && iint) + iint->five_flags &= ~FIVE_TRUSTED_FILE; + + result->file = file; + result->task = task; + result->iint = iint; + result->fn = function; + result->xattr = xattr_value; + result->xattr_len = (size_t)xattr_len; + + if (!iint || five_get_cache_status(iint) == FIVE_FILE_UNKNOWN + || five_get_cache_status(iint) == FIVE_FILE_FAIL) + result->five_result = 1; + else + result->five_result = 0; +} + +static void process_measurement(const struct processing_event_list *params) +{ + struct task_struct *task = params->task; + struct task_integrity *integrity = TASK_INTEGRITY(task); + struct file *file = params->file; + struct inode *inode = file_inode(file); + int function = params->function; + struct file_verification_result file_result; + + if (function != BPRM_CHECK) { + if (task_integrity_read(integrity) == INTEGRITY_NONE) + return; + } + + file_verification_result_init(&file_result); + inode_lock(inode); + + process_file(task, file, function, &file_result); + + five_hook_file_processed(task, file, + file_result.xattr, file_result.xattr_len, + file_result.five_result); + + five_state_proceed(integrity, &file_result); + + inode_unlock(inode); + file_verification_result_deinit(&file_result); +} + +static bool is_memfd_file(struct file *file) +{ + struct inode *inode; + struct inode *memfd_inode; + + if (!file) + return false; + + memfd_inode = file_inode(memfd_file); + inode = file_inode(file); + if (inode && memfd_inode && inode->i_sb == memfd_inode->i_sb) + return true; + + return false; +} + +/** + * five_file_mmap - measure files being mapped executable based on + * the process_measurement() policy decision. + * @file: pointer to the file to be measured (May be NULL) + * @prot: contains the protection that will be applied by the kernel. + * + * On success return 0. + */ +int five_file_mmap(struct file *file, unsigned long prot) +{ + int rc = 0; + struct task_struct *task = current; + struct task_integrity *tint = TASK_INTEGRITY(task); + + if (unlikely(!is_five_initialized) || five_check_params(task, file)) + return 0; + + if (check_memfd_file && is_memfd_file(file)) + return 0; + + if (file && task_integrity_user_read(tint)) { + if (prot & PROT_EXEC) { + rc = push_file_event_bunch(task, file, MMAP_CHECK); + if (rc) + return rc; + } else { + five_hook_file_skipped(task, file); + } + } + + return rc; +} + +/** + * five_bprm_check - Measure executable being launched based on + * the process_measurement() policy decision. + * @bprm: contains the linux_binprm structure + * + * Notes: + * bprm_check could be called few times for one process when few binary loaders + * are used. Example: execution of shell script. + * In this case we should process first file (e.g. shell script) as main and + * use BPRM_CHECK. The second file (interpetator ) will be processed as general + * mapping (MMAP_CHECK). + * To implement this option variable bprm->recursion_depth is used. + * + * On success return 0. + */ +int five_bprm_check(struct linux_binprm *bprm) +{ + int rc = 0; + struct task_struct *task = current; + struct task_integrity *old_tint = TASK_INTEGRITY(task); + + if (unlikely(!is_five_initialized) || unlikely(task->ptrace)) + return rc; + + if (bprm->recursion_depth > 0) { + rc = push_file_event_bunch(task, bprm->file, MMAP_CHECK); + } else { + struct task_integrity *tint = task_integrity_alloc(); + + task_integrity_assign(task, tint); + if (likely(TASK_INTEGRITY(task))) { + rc = push_file_event_bunch(task, + bprm->file, BPRM_CHECK); + } else { + rc = -ENOMEM; + } + task_integrity_put(old_tint); + } + + return rc; +} + +/* Does `unlink' of the `file'. + * This function breaks delegation (drops file's leases. See + * man 2 fcntl "Leases"). do_unlinkat function in fs/namei.c was used + * as an example. + */ +static int five_unlink(struct file *file) +{ + int rc; + struct dentry *dentry = file->f_path.dentry; + struct inode *inode = d_backing_inode(dentry->d_parent); + struct inode *delegated_inode = NULL; + bool retry; + + do { + delegated_inode = NULL; + retry = false; + inode_lock_nested(inode, I_MUTEX_PARENT); + ihold(inode); + rc = vfs_unlink(inode, dentry, &delegated_inode); + inode_unlock(inode); + iput(inode); + if (rc == -EWOULDBLOCK && delegated_inode) { + rc = break_deleg_wait(&delegated_inode); + if (!rc) + retry = true; + } + } while (retry); + + five_audit_info(current, file, "five_unlink", 0, 0, + "Unlink a file", rc); + + return rc; +} + +/** + * This function handles two situations: + * 1. Device had been rebooted before five_sign finished. + * Then xattr_len will be zero and iint->five_signing will be false. + * 2. The file is being signing when another process tries to open it. + * Then xattr_len will be zero and iint->five_signing will be true. + * + * - five_fcntl_edit stores the xattr with zero length and set + * iint->five_signing to true + * - five_fcntl_sign stores correct certificates and set + * iint->five_signing to false + * + * On success returns 0 + */ +int five_file_open(struct file *file) +{ + ssize_t xattr_len; + struct inode *inode = file_inode(file); + + if (!S_ISREG(inode->i_mode)) + return 0; + + xattr_len = vfs_getxattr(file->f_path.dentry, XATTR_NAME_FIVE, + NULL, 0); + if (xattr_len == 0) { + struct integrity_iint_cache *iint; + bool is_signing = false; + + if (!unlink_on_error) { + five_audit_info(current, file, "five_unlink", 0, 0, + "Found a dummy-cert", 0); + return 0; + } + + inode_lock(inode); + iint = integrity_iint_find(inode); + if (iint) + is_signing = iint->five_signing; + inode_unlock(inode); + + if (!is_signing) { + int rc; + + rc = five_unlink(file); + rc = rc ?: -ENOENT; + + return rc; + } + return -EPERM; + } + + return 0; +} + +/** + * five_file_verify - force five integrity measurements for file + * the process_measurement() policy decision. This check affects + * task integrity. + * @file: pointer to the file to be measured (May be NULL) + * + * On success return 0. + */ +int five_file_verify(struct task_struct *task, struct file *file) +{ + int rc = 0; + struct task_integrity *tint = TASK_INTEGRITY(task); + + if (file && task_integrity_user_read(tint)) + rc = push_file_event_bunch(task, file, FILE_CHECK); + + return rc; +} + +static struct notifier_block five_reboot_nb = { + .notifier_call = five_reboot_notifier, + .priority = INT_MAX, +}; + +int five_hash_algo __ro_after_init = HASH_ALGO_SHA1; + +static int __init hash_setup(const char *str) +{ + int i; + + for (i = 0; i < HASH_ALGO__LAST; i++) { + if (strcmp(str, hash_algo_name[i]) == 0) { + five_hash_algo = i; + break; + } + } + + return 1; +} + +static int __init init_five(void) +{ + int error; + + g_five_workqueue = alloc_workqueue("%s", WQ_FREEZABLE | WQ_MEM_RECLAIM, + 0, "five_wq"); + if (!g_five_workqueue) + return -ENOMEM; + + hash_setup(CONFIG_FIVE_DEFAULT_HASH); + error = five_init(); + if (error) + return error; + + error = register_reboot_notifier(&five_reboot_nb); + if (error) + return error; + +/** + * This empty file is needed in is_memfd_file() function. + * The only way to check whether the file was created using memfd_create() + * syscall is to compare its superblock address with address of another memfd + * file. + * + * Below code is copied from shmem_kernel_file_setup(). The difference between + * shmem_file_setup() isshmem_kernel_file_setup() calls __shmem_file_setup() + * with S_PRIVATE flag but shmem_file_setup() calls it without one. After that + * __shmem_file_setup() sets S_PRIVATE flag in inode->i_flags: + inode->i_flags |= i_flags; + */ + memfd_file = shmem_file_setup( + "five_memfd_check", 0, VM_NORESERVE); + if (IS_ERR(memfd_file)) { + error = PTR_ERR(memfd_file); + memfd_file = NULL; + return error; + } + file_inode(memfd_file)->i_flags |= S_PRIVATE; + + error = init_fs(); + if (error) + return error; + + five_dsms_init("1", 0); + + error = five_init_dmverity(); + if (error) + return error; + + error = five_tint_init_dev(); + + if (!error) + is_five_initialized = true; + + return error; +} + +static int fcntl_verify(struct file *file) +{ + int rc = 0; + struct task_struct *task = current; + struct task_integrity *tint = TASK_INTEGRITY(task); + + if (task_integrity_user_read(tint)) + rc = push_file_event_bunch(task, file, FILE_CHECK); + return rc; +} + +/* Called from do_fcntl */ +int five_fcntl_verify_async(struct file *file) +{ + return fcntl_verify(file); +} + +/* Called from do_fcntl */ +int five_fcntl_verify_sync(struct file *file) +{ + return -EINVAL; +} + +struct bprm_hook_context { + struct work_struct data_work; + struct task_struct *task; + struct task_struct *child_task; +}; + +static void bprm_hook_handler(struct work_struct *in_data) +{ + struct bprm_hook_context *context = container_of(in_data, + struct bprm_hook_context, data_work); + + if (unlikely(!context)) + return; + + five_hook_task_forked(context->task, context->child_task); + + put_task_struct(context->task); + put_task_struct(context->child_task); + + kfree(context); +} + +int five_fork(struct task_struct *task, struct task_struct *child_task) +{ + int rc = 0; + struct bprm_hook_context *context; + + spin_lock(&TASK_INTEGRITY(task)->list_lock); + + if (!list_empty(&TASK_INTEGRITY(task)->events.list)) { + /*copy the list*/ + struct list_head *tmp; + struct processing_event_list *from_entry; + struct worker_context *context; + + context = kmalloc(sizeof(struct worker_context), GFP_ATOMIC); + if (unlikely(!context)) { + spin_unlock(&TASK_INTEGRITY(task)->list_lock); + return -ENOMEM; + } + + list_for_each(tmp, &TASK_INTEGRITY(task)->events.list) { + struct processing_event_list *five_file; + + from_entry = list_entry(tmp, + struct processing_event_list, list); + + five_file = five_event_create( + from_entry->event, + child_task, + from_entry->file, + from_entry->function, + GFP_ATOMIC); + if (unlikely(!five_file)) { + kfree(context); + spin_unlock(&TASK_INTEGRITY(task)->list_lock); + return -ENOMEM; + } + + list_add_tail(&five_file->list, + &TASK_INTEGRITY(child_task)->events.list); + } + + context->tint = TASK_INTEGRITY(child_task); + + rc = task_integrity_copy(TASK_INTEGRITY(task), + TASK_INTEGRITY(child_task)); + spin_unlock(&TASK_INTEGRITY(task)->list_lock); + task_integrity_get(context->tint); + task_integrity_processing(TASK_INTEGRITY(child_task)); + INIT_WORK(&context->data_work, work_handler); + rc = queue_work(g_five_workqueue, &context->data_work) ? 0 : 1; + } else { + rc = task_integrity_copy(TASK_INTEGRITY(task), + TASK_INTEGRITY(child_task)); + spin_unlock(&TASK_INTEGRITY(task)->list_lock); + } + + if (rc) + return rc; + + context = kmalloc(sizeof(struct bprm_hook_context), GFP_ATOMIC); + if (unlikely(!context)) + return -ENOMEM; + + get_task_struct(task); + get_task_struct(child_task); + context->task = task; + context->child_task = child_task; + INIT_WORK(&context->data_work, bprm_hook_handler); + rc = queue_work(g_five_workqueue, &context->data_work) ? 0 : 1; + + return rc; +} + +int five_ptrace(struct task_struct *task, long request) +{ + switch (request) { + case PTRACE_TRACEME: + case PTRACE_ATTACH: + case PTRACE_SEIZE: + case PTRACE_INTERRUPT: + case PTRACE_CONT: + case PTRACE_DETACH: + case PTRACE_PEEKTEXT: + case PTRACE_PEEKDATA: + case PTRACE_PEEKUSR: + case PTRACE_GETREGSET: + case PTRACE_GETSIGINFO: + case PTRACE_PEEKSIGINFO: + case PTRACE_GETSIGMASK: + case PTRACE_GETEVENTMSG: +#ifdef CONFIG_ARM64 + case COMPAT_PTRACE_GETREGS: + case COMPAT_PTRACE_GET_THREAD_AREA: + case COMPAT_PTRACE_GETVFPREGS: + case COMPAT_PTRACE_GETHBPREGS: +#else + case PTRACE_GETREGS: + case PTRACE_GET_THREAD_AREA: + case PTRACE_GETVFPREGS: + case PTRACE_GETHBPREGS: +#endif + break; + default: { + struct task_integrity *tint = TASK_INTEGRITY(task); + + if (task_integrity_user_read(tint) == INTEGRITY_NONE) + break; + + task_integrity_delayed_reset(task, CAUSE_PTRACE, NULL); + five_audit_err(task, NULL, "ptrace", task_integrity_read(tint), + INTEGRITY_NONE, "reset-integrity", 0); + break; + } + } + + return 0; +} + +int five_process_vm_rw(struct task_struct *task, int write) +{ + if (write) { + struct task_integrity *tint = TASK_INTEGRITY(task); + + if (task_integrity_user_read(tint) == INTEGRITY_NONE) + goto exit; + + task_integrity_delayed_reset(task, CAUSE_VMRW, NULL); + five_audit_err(task, NULL, "process_vm_rw", + task_integrity_read(tint), INTEGRITY_NONE, + "reset-integrity", 0); + } + +exit: + return 0; +} + +static inline struct processing_event_list *five_event_create( + enum five_event event, struct task_struct *task, + struct file *file, int function, gfp_t flags) +{ + struct processing_event_list *five_file; + + five_file = kzalloc(sizeof(struct processing_event_list), flags); + if (unlikely(!five_file)) + return NULL; + + five_file->event = event; + + switch (five_file->event) { + case FIVE_VERIFY_BUNCH_FILES: { + get_task_struct(task); + get_file(file); + five_file->task = task; + five_file->file = file; + five_file->function = function; + break; + } + case FIVE_RESET_INTEGRITY: { + get_task_struct(task); + five_file->task = task; + break; + } + default: + break; + } + + return five_file; +} + +static inline void five_event_destroy( + const struct processing_event_list *file) +{ + switch (file->event) { + case FIVE_VERIFY_BUNCH_FILES: { + fput(file->file); + put_task_struct(file->task); + break; + } + case FIVE_RESET_INTEGRITY: { + put_task_struct(file->task); + break; + } + default: + break; + } + kfree(file); +} + +late_initcall(init_five); + +MODULE_DESCRIPTION("File-based process Integrity Verifier"); +MODULE_LICENSE("GPL"); diff --git a/security/samsung/five/gki/five_pa.c b/security/samsung/five/gki/five_pa.c new file mode 100644 index 000000000000..eac7742b6353 --- /dev/null +++ b/security/samsung/five/gki/five_pa.c @@ -0,0 +1,154 @@ +/* + * Process Authentificator helpers + * + * Copyright (C) 2017 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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 "five.h" +#include "five_pa.h" +#include "five_hooks.h" +#include "five_lv.h" +#include "five_porting.h" + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)) +#define F_SIGNATURE(file) ((void *)((file)->android_oem_data1)) + +static inline void f_signature_assign(struct file *file, void *f_signature) +{ + file->android_oem_data1 = (u64)f_signature; +} +#else +#define F_SIGNATURE(file) ((void *)((file)->android_vendor_data1)) + +static inline void f_signature_assign(struct file *file, void *f_signature) +{ + file->android_vendor_data1 = (u64)f_signature; +} +#endif + +static void process_file(struct task_struct *task, struct file *file) +{ + char *xattr_value = NULL; + + if (F_SIGNATURE(file)) + return; + + if (five_check_params(task, file)) + return; + + five_read_xattr(d_real_comp(file->f_path.dentry), &xattr_value); + f_signature_assign(file, xattr_value); +} + +void fivepa_fsignature_free(struct file *file) +{ + kfree(F_SIGNATURE(file)); + f_signature_assign(file, NULL); +} + +int proca_fcntl_setxattr(struct file *file, void __user *lv_xattr) +{ + struct inode *inode = file_inode(file); + struct lv lv_hdr = {0}; + int rc = -EPERM; + void *x = NULL; + + if (unlikely(!file || !lv_xattr)) + return -EINVAL; + + if (unlikely(copy_from_user(&lv_hdr, lv_xattr, sizeof(lv_hdr)))) + return -EFAULT; + + if (unlikely(lv_hdr.length > PAGE_SIZE)) + return -EINVAL; + + x = kmalloc(lv_hdr.length, GFP_NOFS); + if (unlikely(!x)) + return -ENOMEM; + + if (unlikely(copy_from_user(x, lv_xattr + sizeof(lv_hdr), + lv_hdr.length))) { + rc = -EFAULT; + goto out; + } + + if (file->f_op && file->f_op->flush) + if (file->f_op->flush(file, current->files)) { + rc = -EOPNOTSUPP; + goto out; + } + + inode_lock(inode); + + if (task_integrity_allow_sign(TASK_INTEGRITY(current))) { + rc = __vfs_setxattr_noperm(d_real_comp(file->f_path.dentry), + XATTR_NAME_PA, + x, + lv_hdr.length, + 0); + } + inode_unlock(inode); + +out: + kfree(x); + + return rc; +} + +static void proca_hook_file_processed(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file, void *xattr, + size_t xattr_size, int result); + +static void proca_hook_file_skipped(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file); + +static struct five_hook_list five_ops[] = { + FIVE_HOOK_INIT(file_processed, proca_hook_file_processed), + FIVE_HOOK_INIT(file_skipped, proca_hook_file_skipped), +}; + +static void proca_hook_file_processed(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file, void *xattr, + size_t xattr_size, int result) +{ + process_file(task, file); +} + +static void proca_hook_file_skipped(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file) +{ + process_file(task, file); +} + +static __init int proca_module_init(void) +{ + five_add_hooks(five_ops, ARRAY_SIZE(five_ops)); + pr_info("PROCA was initialized\n"); + + return 0; +} +late_initcall(proca_module_init); + +MODULE_DESCRIPTION("PROCA module"); +MODULE_LICENSE("GPL"); diff --git a/security/samsung/five/gki/five_state.c b/security/samsung/five/gki/five_state.c new file mode 100644 index 000000000000..a8c03ce464e3 --- /dev/null +++ b/security/samsung/five/gki/five_state.c @@ -0,0 +1,387 @@ +/* + * FIVE State machine + * + * Copyright (C) 2017 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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 "five_audit.h" +#include "five_state.h" +#include "five_hooks.h" +#include "five_cache.h" +#include "five_dsms.h" + +enum task_integrity_state_cause { + STATE_CAUSE_UNKNOWN, + STATE_CAUSE_DIGSIG, + STATE_CAUSE_DMV_PROTECTED, + STATE_CAUSE_TRUSTED, + STATE_CAUSE_HMAC, + STATE_CAUSE_SYSTEM_LABEL, + STATE_CAUSE_NOCERT, + STATE_CAUSE_TAMPERED, + STATE_CAUSE_MISMATCH_LABEL, + STATE_CAUSE_FSV_PROTECTED +}; + +struct task_verification_result { + enum task_integrity_value new_tint; + enum task_integrity_value prev_tint; + enum task_integrity_state_cause cause; +}; + +static const char *task_integrity_state_str( + enum task_integrity_state_cause cause) +{ + const char *str = "unknown"; + + switch (cause) { + case STATE_CAUSE_DIGSIG: + str = "digsig"; + break; + case STATE_CAUSE_DMV_PROTECTED: + str = "dmv_protected"; + break; + case STATE_CAUSE_FSV_PROTECTED: + str = "fsv_protected"; + break; + case STATE_CAUSE_TRUSTED: + str = "trusted"; + break; + case STATE_CAUSE_HMAC: + str = "hmac"; + break; + case STATE_CAUSE_SYSTEM_LABEL: + str = "system_label"; + break; + case STATE_CAUSE_NOCERT: + str = "nocert"; + break; + case STATE_CAUSE_MISMATCH_LABEL: + str = "mismatch_label"; + break; + case STATE_CAUSE_TAMPERED: + str = "tampered"; + break; + case STATE_CAUSE_UNKNOWN: + str = "unknown"; + break; + } + + return str; +} + +static enum task_integrity_reset_cause state_to_reason_cause( + enum task_integrity_state_cause cause) +{ + enum task_integrity_reset_cause reset_cause; + + switch (cause) { + case STATE_CAUSE_UNKNOWN: + reset_cause = CAUSE_UNKNOWN; + break; + case STATE_CAUSE_TAMPERED: + reset_cause = CAUSE_TAMPERED; + break; + case STATE_CAUSE_NOCERT: + reset_cause = CAUSE_NO_CERT; + break; + case STATE_CAUSE_MISMATCH_LABEL: + reset_cause = CAUSE_MISMATCH_LABEL; + break; + default: + /* Integrity is not NONE. */ + reset_cause = CAUSE_UNSET; + break; + } + + return reset_cause; +} + +static int is_system_label(struct integrity_label *label) +{ + if (label && label->len == 0) + return 1; /* system label */ + + return 0; +} + +static inline int integrity_label_cmp(struct integrity_label *l1, + struct integrity_label *l2) +{ + return 0; +} + +static int verify_or_update_label(struct task_integrity *intg, + struct integrity_iint_cache *iint) +{ + struct integrity_label *l; + struct integrity_label *file_label = iint->five_label; + int rc = 0; + + if (!file_label) /* digsig doesn't have label */ + return 0; + + if (is_system_label(file_label)) + return 0; + + spin_lock(&intg->value_lock); + l = intg->label; + + if (l) { + if (integrity_label_cmp(file_label, l)) { + rc = -EPERM; + goto out; + } + } else { + struct integrity_label *new_label; + + new_label = kmalloc(sizeof(file_label->len) + file_label->len, + GFP_ATOMIC); + if (!new_label) { + rc = -ENOMEM; + goto out; + } + + new_label->len = file_label->len; + memcpy(new_label->data, file_label->data, new_label->len); + intg->label = new_label; + } + +out: + spin_unlock(&intg->value_lock); + + return rc; +} + +static bool set_first_state(struct integrity_iint_cache *iint, + struct task_integrity *integrity, + struct task_verification_result *result) +{ + enum task_integrity_value tint = INTEGRITY_NONE; + enum five_file_integrity status = five_get_cache_status(iint); + bool trusted_file = iint->five_flags & FIVE_TRUSTED_FILE; + enum task_integrity_state_cause cause = STATE_CAUSE_UNKNOWN; + + result->new_tint = result->prev_tint = task_integrity_read(integrity); + task_integrity_clear(integrity); + + switch (status) { + case FIVE_FILE_RSA: + if (trusted_file) { + cause = STATE_CAUSE_TRUSTED; + tint = INTEGRITY_PRELOAD_ALLOW_SIGN; + } else { + cause = STATE_CAUSE_DIGSIG; + tint = INTEGRITY_PRELOAD; + } + break; + case FIVE_FILE_FSVERITY: + case FIVE_FILE_DMVERITY: + if (trusted_file) { + cause = STATE_CAUSE_TRUSTED; + tint = INTEGRITY_DMVERITY_ALLOW_SIGN; + } else { + cause = (status == FIVE_FILE_FSVERITY) + ? STATE_CAUSE_FSV_PROTECTED + : STATE_CAUSE_DMV_PROTECTED; + tint = INTEGRITY_DMVERITY; + } + break; + case FIVE_FILE_HMAC: + cause = STATE_CAUSE_HMAC; + tint = INTEGRITY_MIXED; + break; + case FIVE_FILE_FAIL: + cause = STATE_CAUSE_TAMPERED; + tint = INTEGRITY_NONE; + break; + case FIVE_FILE_UNKNOWN: + cause = STATE_CAUSE_NOCERT; + tint = INTEGRITY_NONE; + break; + default: + cause = STATE_CAUSE_NOCERT; + tint = INTEGRITY_NONE; + break; + } + + task_integrity_set(integrity, tint); + result->new_tint = tint; + result->cause = cause; + + return true; +} + +static bool set_next_state(struct integrity_iint_cache *iint, + struct task_integrity *integrity, + struct task_verification_result *result) +{ + bool is_newstate = false; + enum five_file_integrity status = five_get_cache_status(iint); + bool has_digsig = (status == FIVE_FILE_RSA); + bool dmv_protected = (status == FIVE_FILE_DMVERITY); + bool fsv_protected = (status == FIVE_FILE_FSVERITY); + bool xv_protected = dmv_protected || fsv_protected; + struct integrity_label *label = iint->five_label; + enum task_integrity_state_cause cause = STATE_CAUSE_UNKNOWN; + enum task_integrity_value state_tint = INTEGRITY_NONE; + + result->new_tint = result->prev_tint = task_integrity_read(integrity); + + if (has_digsig) + return is_newstate; + + if (status == FIVE_FILE_UNKNOWN || status == FIVE_FILE_FAIL) { + spin_lock(&integrity->value_lock); + + if (status == FIVE_FILE_UNKNOWN) + cause = STATE_CAUSE_NOCERT; + else + cause = STATE_CAUSE_TAMPERED; + + state_tint = INTEGRITY_NONE; + is_newstate = true; + goto out; + } + + if (verify_or_update_label(integrity, iint)) { + spin_lock(&integrity->value_lock); + cause = STATE_CAUSE_MISMATCH_LABEL; + state_tint = INTEGRITY_NONE; + is_newstate = true; + goto out; + } + + spin_lock(&integrity->value_lock); + switch (integrity->value) { + case INTEGRITY_PRELOAD_ALLOW_SIGN: + if (xv_protected) { + cause = fsv_protected ? STATE_CAUSE_FSV_PROTECTED + : STATE_CAUSE_DMV_PROTECTED; + state_tint = INTEGRITY_DMVERITY_ALLOW_SIGN; + } else if (is_system_label(label)) { + cause = STATE_CAUSE_SYSTEM_LABEL; + state_tint = INTEGRITY_MIXED_ALLOW_SIGN; + } else { + cause = STATE_CAUSE_HMAC; + state_tint = INTEGRITY_MIXED; + } + is_newstate = true; + break; + case INTEGRITY_PRELOAD: + if (xv_protected) { + cause = fsv_protected ? STATE_CAUSE_FSV_PROTECTED + : STATE_CAUSE_DMV_PROTECTED; + state_tint = INTEGRITY_DMVERITY; + } else { + cause = STATE_CAUSE_HMAC; + state_tint = INTEGRITY_MIXED; + } + is_newstate = true; + break; + case INTEGRITY_MIXED_ALLOW_SIGN: + if (!xv_protected && !is_system_label(label)) { + cause = STATE_CAUSE_HMAC; + state_tint = INTEGRITY_MIXED; + is_newstate = true; + } + break; + case INTEGRITY_DMVERITY: + if (!xv_protected) { + cause = STATE_CAUSE_HMAC; + state_tint = INTEGRITY_MIXED; + is_newstate = true; + } + break; + case INTEGRITY_DMVERITY_ALLOW_SIGN: + if (!xv_protected) { + if (is_system_label(label)) { + cause = STATE_CAUSE_SYSTEM_LABEL; + state_tint = INTEGRITY_MIXED_ALLOW_SIGN; + } else { + cause = STATE_CAUSE_HMAC; + state_tint = INTEGRITY_MIXED; + } + is_newstate = true; + } + break; + case INTEGRITY_MIXED: + break; + case INTEGRITY_NONE: + break; + default: + // Unknown state + cause = STATE_CAUSE_UNKNOWN; + state_tint = INTEGRITY_NONE; + is_newstate = true; + } + +out: + + if (is_newstate) { + __task_integrity_set(integrity, state_tint); + result->new_tint = state_tint; + result->cause = cause; + } + spin_unlock(&integrity->value_lock); + + return is_newstate; +} + +void five_state_proceed(struct task_integrity *integrity, + struct file_verification_result *file_result) +{ + struct integrity_iint_cache *iint = file_result->iint; + enum five_hooks fn = file_result->fn; + struct task_struct *task = file_result->task; + struct file *file = file_result->file; + bool is_newstate; + struct task_verification_result task_result = {}; + + if (!iint) + return; + + if (fn == BPRM_CHECK) + is_newstate = set_first_state(iint, integrity, &task_result); + else + is_newstate = set_next_state(iint, integrity, &task_result); + + if (is_newstate) { + if (task_result.new_tint == INTEGRITY_NONE) { + task_integrity_set_reset_reason(integrity, + state_to_reason_cause(task_result.cause), file); + five_hook_integrity_reset(task, file, + state_to_reason_cause(task_result.cause)); + + if (fn != BPRM_CHECK) { + char comm[TASK_COMM_LEN]; + char filename[NAME_MAX]; + char *pathbuf = NULL; + + five_dsms_reset_integrity( + get_task_comm(comm, task), + task_result.cause, + five_d_path(&file->f_path, &pathbuf, + filename)); + if (pathbuf) + __putname(pathbuf); + } + } + five_audit_verbose(task, file, five_get_string_fn(fn), + task_result.prev_tint, task_result.new_tint, + task_integrity_state_str(task_result.cause), + file_result->five_result); + } +} + diff --git a/security/samsung/five/gki/five_tee_interface.c b/security/samsung/five/gki/five_tee_interface.c new file mode 100644 index 000000000000..e7291c06ee43 --- /dev/null +++ b/security/samsung/five/gki/five_tee_interface.c @@ -0,0 +1,116 @@ +/* + * Interface for TEE Driver + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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 "five_tee_interface.h" +#include "five_tee_api.h" + +static struct five_tee_driver_fns *g_tee_driver_fn; +static char is_registered; +static DECLARE_RWSEM(usage_lock); + +int register_five_tee_driver( + struct five_tee_driver_fns *tee_driver_fns) +{ + int rc = 0; + + if (!tee_driver_fns) + return -EINVAL; + + down_write(&usage_lock); + if (is_registered) { + rc = -EACCES; + goto exit; + } + + g_tee_driver_fn = kmalloc(sizeof(*g_tee_driver_fn), GFP_KERNEL); + if (!g_tee_driver_fn) { + rc = -ENOMEM; + goto exit; + } + + g_tee_driver_fn->verify_hmac = tee_driver_fns->verify_hmac; + g_tee_driver_fn->sign_hmac = tee_driver_fns->sign_hmac; + is_registered = 1; + +exit: + up_write(&usage_lock); + + return rc; +} +EXPORT_SYMBOL_GPL(register_five_tee_driver); + +void unregister_five_tee_driver(void) +{ + down_write(&usage_lock); + if (is_registered) { + kfree(g_tee_driver_fn); + g_tee_driver_fn = NULL; + is_registered = 0; + } + up_write(&usage_lock); +} +EXPORT_SYMBOL_GPL(unregister_five_tee_driver); + +int verify_hash(enum hash_algo algo, const void *hash, size_t hash_len, + const void *label, size_t label_len, + const void *signature, size_t signature_len) +{ + int rc = -ENODEV; + struct tee_iovec args = { + .algo = algo, + .hash = hash, + .hash_len = hash_len, + .label = label, + .label_len = label_len, + .signature = (void *)signature, + .signature_len = signature_len + }; + + down_read(&usage_lock); + if (is_registered) + rc = g_tee_driver_fn->verify_hmac(&args); + up_read(&usage_lock); + + return rc; +} + +int sign_hash(enum hash_algo algo, const void *hash, size_t hash_len, + const void *label, size_t label_len, + void *signature, size_t *signature_len) +{ + int rc = -ENODEV; + struct tee_iovec args = { + .algo = algo, + .hash = hash, + .hash_len = hash_len, + .label = label, + .label_len = label_len, + .signature = signature, + .signature_len = *signature_len + }; + + down_read(&usage_lock); + if (is_registered) + rc = g_tee_driver_fn->sign_hmac(&args); + up_read(&usage_lock); + + *signature_len = args.signature_len; + + return rc; +} diff --git a/security/samsung/five/gki/five_tee_interface.h b/security/samsung/five/gki/five_tee_interface.h new file mode 100644 index 000000000000..6ff986d050ce --- /dev/null +++ b/security/samsung/five/gki/five_tee_interface.h @@ -0,0 +1,47 @@ +/* + * Interface for TEE Driver + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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 INTEGRITY_TEE_DRIVER_H +#define INTEGRITY_TEE_DRIVER_H + +#include +#include + +struct tee_iovec { + enum hash_algo algo; + + const void *hash; + size_t hash_len; + + const void *label; + size_t label_len; + + void *signature; + size_t signature_len; + + int rc; +}; + +struct five_tee_driver_fns { + int (*verify_hmac)(const struct tee_iovec *verify_args); + int (*sign_hmac)(struct tee_iovec *sign_args); +}; + +int register_five_tee_driver( + struct five_tee_driver_fns *tee_driver_fns); +void unregister_five_tee_driver(void); + +#endif diff --git a/security/samsung/five/gki/five_tint_dev.c b/security/samsung/five/gki/five_tint_dev.c new file mode 100644 index 000000000000..6172b5bcd39f --- /dev/null +++ b/security/samsung/five/gki/five_tint_dev.c @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FIVE task integrity + * + * Copyright (C) 2020 Samsung Electronics, Inc. + * Egor Uleyskiy, + * Viacheslav Vovchenko + * + * 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 "task_integrity.h" +#include "five_tint_dev.h" + +struct tint_message { + uint32_t version; + uint32_t param; + uint64_t reserved; + union __packed { + enum task_integrity_value value; + struct __packed { + uint64_t value_ptr; + uint16_t len_buf; + } label; + struct __packed { + uint64_t i_ino; + enum task_integrity_reset_cause cause; + struct __packed { + uint64_t pathname_ptr; + uint16_t len_buf; + } path; + } reset_file; + struct __packed { + uint64_t label_ptr; + } sign; + } data; +} __packed; + +#define TINT_VERSION 1 +#define TINT_DEV "task_integrity" +#define TINT_IOC_MAGIC 'f' +#define TINT_IOCTL_GET_VALUE \ + _IOWR(TINT_IOC_MAGIC, 1, struct tint_message) +#define TINT_IOCTL_GET_LABEL \ + _IOWR(TINT_IOC_MAGIC, 2, struct tint_message) +#define TINT_IOCTL_GET_RESET_FILE \ + _IOWR(TINT_IOC_MAGIC, 3, struct tint_message) +#define TINT_IOCTL_SIGN_FILE \ + _IOWR(TINT_IOC_MAGIC, 4, struct tint_message) +#define TINT_IOCTL_EDIT_FILE \ + _IOWR(TINT_IOC_MAGIC, 5, struct tint_message) +#define TINT_IOCTL_CLOSE_FILE \ + _IOWR(TINT_IOC_MAGIC, 6, struct tint_message) +#define TINT_IOCTL_VERIFY_SYNC_FILE \ + _IOWR(TINT_IOC_MAGIC, 7, struct tint_message) +#define TINT_IOCTL_VERIFY_ASYNC_FILE \ + _IOWR(TINT_IOC_MAGIC, 8, struct tint_message) + +static struct tint_control { + struct device *pdev; + struct class *driver_class; + dev_t device_no; + struct cdev cdev; +} dev_ctrl; + +static struct task_struct *get_task_by_pid(pid_t pid) +{ + struct task_struct *task = NULL; + struct pid *pid_data; + + pid_data = find_get_pid(pid); + if (unlikely(!pid_data)) { + pr_err("FIVE: Can't find PID: %u\n", pid); + return NULL; + } + + task = get_pid_task(pid_data, PIDTYPE_PID); + if (unlikely(!task)) + pr_err("FIVE: Can't find task by PID: %u\n", pid); + + put_pid(pid_data); + + return task; +} + +static struct task_struct *get_tint_and_task(pid_t pid) +{ + struct task_struct *task = get_task_by_pid(pid); + + if (!task) + return NULL; + + task_integrity_get(TASK_INTEGRITY(task)); + + return task; +} + +static void put_tint_and_task(struct task_struct *task) +{ + if (!task) + return; + + task_integrity_put(TASK_INTEGRITY(task)); + put_task_struct(task); +} + +static int do_command_file(unsigned int cmd, unsigned int fd, + struct integrity_label __user *label) +{ + int ret = 0; + struct file *file; + + file = fget(fd); + if (!file) { + pr_err("FIVE: Can't get file struct: %u\n", cmd); + return -EFAULT; + } + + switch (cmd) { + case TINT_IOCTL_SIGN_FILE: { + ret = five_fcntl_sign(file, label); + break; + } + case TINT_IOCTL_EDIT_FILE: { + ret = five_fcntl_edit(file); + break; + } + case TINT_IOCTL_CLOSE_FILE: { + ret = five_fcntl_close(file); + break; + } + case TINT_IOCTL_VERIFY_SYNC_FILE: { + ret = five_fcntl_verify_sync(file); + break; + } + case TINT_IOCTL_VERIFY_ASYNC_FILE: { + ret = five_fcntl_verify_async(file); + break; + } + default: { + pr_err("FIVE: Invalid IOCTL command: %u\n", cmd); + ret = -ENOIOCTLCMD; + } + } + + if (ret) + pr_err("FIVE: Command failed: %u %d\n", cmd, ret); + + fput(file); + + return ret; +} + +static long tint_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + long ret = 0; + struct tint_message __user *argp = (void __user *) arg; + struct task_struct *task; + struct tint_message msg = {0}; + + if (_IOC_TYPE(cmd) != TINT_IOC_MAGIC) { + pr_err("FIVE: IOCTL type is wrong\n"); + return -ENOTTY; + } + + ret = copy_from_user(&msg, argp, sizeof(msg)); + if (ret) { + pr_err("FIVE: copy_from_user failed ret: %ld\n", ret); + return -EFAULT; + } + + if (msg.version != TINT_VERSION) { + pr_err("FIVE: Unsupported protocol version: %u\n", msg.version); + return -EINVAL; + } + + switch (cmd) { + case TINT_IOCTL_GET_VALUE: { + task = get_tint_and_task(msg.param); + if (!task) { + ret = -ESRCH; + break; + } + + msg.data.value = task_integrity_user_read(TASK_INTEGRITY(task)); + + ret = copy_to_user( + (void __user *) &argp->data.value, &msg.data.value, + sizeof(msg.data.value)); + if (unlikely(ret)) { + pr_err("FIVE: copy_to_user failed: %u %ld\n", + cmd, ret); + ret = -EFAULT; + } + + put_tint_and_task(task); + break; + } + case TINT_IOCTL_GET_LABEL: { + const struct integrity_label *label; + + task = get_tint_and_task(msg.param); + if (!task) { + ret = -ESRCH; + break; + } + + spin_lock(&TASK_INTEGRITY(task)->value_lock); + label = TASK_INTEGRITY(task)->label; + spin_unlock(&TASK_INTEGRITY(task)->value_lock); + + if (!label) { + ret = -ENOENT; + put_tint_and_task(task); + break; + } + + if (msg.data.label.len_buf < sizeof(label->len) + label->len) { + pr_err("FIVE: User buffer is small for label: %u %u", + cmd, msg.data.label.len_buf); + ret = -EINVAL; + put_tint_and_task(task); + break; + } + + ret = copy_to_user( + (void __user *) msg.data.label.value_ptr, label, + sizeof(label->len) + label->len); + if (unlikely(ret)) { + pr_err("FIVE: copy_to_user failed len: %u %ld\n", + cmd, ret); + ret = -EFAULT; + } + + put_tint_and_task(task); + break; + } + case TINT_IOCTL_GET_RESET_FILE: { + const struct file *reset_file; + const struct inode *inode; + uint16_t len_buf; + char *buf, *pathname; + + task = get_tint_and_task(msg.param); + if (!task) { + ret = -ESRCH; + break; + } + + reset_file = TASK_INTEGRITY(task)->reset_file; + if (!reset_file) { + ret = -ENOENT; + put_tint_and_task(task); + break; + } + + inode = file_inode(reset_file); + msg.data.reset_file.i_ino = inode->i_ino; + msg.data.reset_file.cause = TASK_INTEGRITY(task)->reset_cause; + len_buf = msg.data.reset_file.path.len_buf; + + if (!len_buf || len_buf > PAGE_SIZE) { + pr_err("FIVE: Bad size of user buffer: %u %u", + cmd, len_buf); + ret = -EINVAL; + put_tint_and_task(task); + break; + } + + buf = kmalloc(len_buf, GFP_KERNEL); + if (unlikely(!buf)) { + pr_err("FIVE: Can't allocate memory: %u %u", + cmd, len_buf); + ret = -ENOMEM; + put_tint_and_task(task); + break; + } + + pathname = d_path(&reset_file->f_path, buf, len_buf); + if (IS_ERR(pathname)) { + pr_err("FIVE: Can't obtain path: %u", len_buf); + ret = -ENOMEM; + kfree(buf); + put_tint_and_task(task); + break; + } + + len_buf = strnlen(pathname, len_buf) + 1; + + ret = copy_to_user( + (void __user *) msg.data.reset_file.path.pathname_ptr, + pathname, len_buf); + if (unlikely(ret)) { + pr_err("FIVE: copy_to_user failed path: %u %ld\n", + cmd, ret); + ret = -EFAULT; + kfree(buf); + put_tint_and_task(task); + break; + } + + ret = copy_to_user((void __user *) &argp->data.reset_file, + &msg.data.reset_file, + sizeof(msg.data.reset_file)); + + if (unlikely(ret)) { + pr_err("FIVE: copy_to_user failed: %u %ld\n", + cmd, ret); + ret = -EFAULT; + } + + kfree(buf); + put_tint_and_task(task); + break; + } + case TINT_IOCTL_SIGN_FILE: + case TINT_IOCTL_EDIT_FILE: + case TINT_IOCTL_CLOSE_FILE: + case TINT_IOCTL_VERIFY_SYNC_FILE: + case TINT_IOCTL_VERIFY_ASYNC_FILE: { + ret = do_command_file(cmd, msg.param, + (struct integrity_label __user *) msg.data.sign.label_ptr); + break; + } + default: { + pr_err("FIVE: Invalid IOCTL command: %u\n", cmd); + ret = -ENOIOCTLCMD; + } + } + + return ret; +} + +#ifdef CONFIG_COMPAT +static long tint_compact_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return tint_ioctl(file, cmd, (unsigned long) compat_ptr(arg)); +} +#endif + +static const struct file_operations tint_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = tint_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = tint_compact_ioctl, +#endif +}; + +int __init five_tint_init_dev(void) +{ + int rc = 0; + + rc = alloc_chrdev_region(&dev_ctrl.device_no, 0, 1, TINT_DEV); + if (unlikely(rc < 0)) { + pr_err("FIVE: alloc_chrdev_region failed %d\n", rc); + return rc; + } + + dev_ctrl.driver_class = class_create(THIS_MODULE, TINT_DEV); + if (IS_ERR(dev_ctrl.driver_class)) { + rc = PTR_ERR(dev_ctrl.driver_class); + pr_err("FIVE: class_create failed %d\n", rc); + goto exit_unreg_chrdev_region; + } + + dev_ctrl.pdev = device_create(dev_ctrl.driver_class, NULL, + dev_ctrl.device_no, NULL, TINT_DEV); + if (IS_ERR(dev_ctrl.pdev)) { + rc = PTR_ERR(dev_ctrl.pdev); + pr_err("FIVE: class_device_create failed %d\n", rc); + goto exit_destroy_class; + } + + cdev_init(&dev_ctrl.cdev, &tint_fops); + dev_ctrl.cdev.owner = THIS_MODULE; + + rc = cdev_add(&dev_ctrl.cdev, + MKDEV(MAJOR(dev_ctrl.device_no), 0), 1); + if (unlikely(rc < 0)) { + pr_err("FIVE: cdev_add failed %d\n", rc); + goto exit_destroy_device; + } + + return 0; + +exit_destroy_device: + device_destroy(dev_ctrl.driver_class, dev_ctrl.device_no); +exit_destroy_class: + class_destroy(dev_ctrl.driver_class); +exit_unreg_chrdev_region: + unregister_chrdev_region(dev_ctrl.device_no, 1); + + return rc; +} + +void __exit five_tint_deinit_dev(void) +{ + cdev_del(&dev_ctrl.cdev); + device_destroy(dev_ctrl.driver_class, dev_ctrl.device_no); + class_destroy(dev_ctrl.driver_class); + unregister_chrdev_region(dev_ctrl.device_no, 1); +} diff --git a/security/samsung/five/gki/five_tint_dev.h b/security/samsung/five/gki/five_tint_dev.h new file mode 100644 index 000000000000..18014364bd53 --- /dev/null +++ b/security/samsung/five/gki/five_tint_dev.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * FIVE task integrity + * + * Copyright (C) 2020 Samsung Electronics, Inc. + * Egor Uleyskiy, + * Viacheslav Vovchenko + * + * 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_FIVE_GKI_FIVE_TINT_DEV_H +#define __LINUX_FIVE_GKI_FIVE_TINT_DEV_H + +int __init five_tint_init_dev(void); +void __exit five_tint_deinit_dev(void); + +#endif diff --git a/security/samsung/five/gki/five_vfs.c b/security/samsung/five/gki/five_vfs.c new file mode 100644 index 000000000000..40f4761602c8 --- /dev/null +++ b/security/samsung/five/gki/five_vfs.c @@ -0,0 +1,130 @@ +/* + * FIVE vfs functions + * + * Copyright (C) 2020 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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 "five_vfs.h" + +/* This function is an alternative implementation of vfs_getxattr_alloc() */ +ssize_t five_getxattr_alloc(struct dentry *dentry, const char *name, + char **xattr_value, size_t xattr_size, gfp_t flags) +{ + struct inode *inode = dentry->d_inode; + char *value = *xattr_value; + int error; + + error = __vfs_getxattr(dentry, inode, name, NULL, 0, XATTR_NOSECURITY); + if (error < 0) + return error; + + if (!value || (error > xattr_size)) { + value = krealloc(*xattr_value, error + 1, flags); + if (!value) + return -ENOMEM; + memset(value, 0, error + 1); + } + + error = __vfs_getxattr(dentry, inode, name, value, error, + XATTR_NOSECURITY); + *xattr_value = value; + return error; +} + +/* This function is copied from new_sync_read() */ +static ssize_t new_sync_read(struct file *filp, char __user *buf, + size_t len, loff_t *ppos) +{ + struct iovec iov = { .iov_base = buf, .iov_len = len }; + struct kiocb kiocb; + struct iov_iter iter; + ssize_t ret; + + init_sync_kiocb(&kiocb, filp); + kiocb.ki_pos = (ppos ? *ppos : 0); + iov_iter_init(&iter, READ, &iov, 1, len); + + ret = call_read_iter(filp, &kiocb, &iter); + BUG_ON(ret == -EIOCBQUEUED); + if (ppos) + *ppos = kiocb.ki_pos; + return ret; +} + +/* This function is copied from __vfs_read() */ +ssize_t five_vfs_read(struct file *file, char __user *buf, size_t count, + loff_t *pos) +{ + if (file->f_op->read) + return file->f_op->read(file, buf, count, pos); + else if (file->f_op->read_iter) + return new_sync_read(file, buf, count, pos); + else + return -EINVAL; +} + +/* This function is copied from __vfs_setxattr_noperm() */ +int five_setxattr_noperm(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags) +{ + struct inode *inode = dentry->d_inode; + int error = -EAGAIN; + int issec = !strncmp(name, XATTR_SECURITY_PREFIX, + XATTR_SECURITY_PREFIX_LEN); + + if (issec) + inode->i_flags &= ~S_NOSEC; + if (inode->i_opflags & IOP_XATTR) { + error = __vfs_setxattr(dentry, inode, name, value, size, flags); + } else { + if (unlikely(is_bad_inode(inode))) + return -EIO; + } + + return error; +} + +/* + * five_kernel_read - read data from the file + * + * This is a function for reading file content instead of kernel_read(). + * It does not perform locking checks to ensure it cannot be blocked. + * It does not perform security checks because it is irrelevant for IMA. + * + * This function is copied from integrity_kernel_read() + */ +int five_kernel_read(struct file *file, loff_t offset, + void *addr, unsigned long count) +{ + mm_segment_t old_fs; + char __user *buf = (char __user *)addr; + ssize_t ret; + struct inode *inode = file_inode(file); + + if (!(file->f_mode & FMODE_READ)) + return -EBADF; + + old_fs = get_fs(); + set_fs(KERNEL_DS); + + if (inode->i_sb->s_magic == OVERLAYFS_SUPER_MAGIC && file->private_data) + file = (struct file *)file->private_data; + + ret = five_vfs_read(file, buf, count, &offset); + set_fs(old_fs); + + return ret; +} diff --git a/security/samsung/five/gki/five_vfs.h b/security/samsung/five/gki/five_vfs.h new file mode 100644 index 000000000000..980ad105b9a7 --- /dev/null +++ b/security/samsung/five/gki/five_vfs.h @@ -0,0 +1,24 @@ +/* + * FIVE vfs functions + * + * Copyright (C) 2020 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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. + */ + +ssize_t five_getxattr_alloc(struct dentry *dentry, const char *name, + char **xattr_value, size_t xattr_size, gfp_t flags); +ssize_t five_vfs_read(struct file *file, char __user *buf, + size_t count, loff_t *pos); +int five_kernel_read(struct file *file, loff_t offset, + void *addr, unsigned long count); +int five_setxattr_noperm(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags); diff --git a/security/samsung/five/gki/task_integrity.c b/security/samsung/five/gki/task_integrity.c new file mode 100644 index 000000000000..449409814c94 --- /dev/null +++ b/security/samsung/five/gki/task_integrity.c @@ -0,0 +1,196 @@ +/* + * Task Integrity Verifier + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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 "five_porting.h" + +static struct kmem_cache *task_integrity_cache; + +static void init_once(void *foo) +{ + struct task_integrity *tint = foo; + + memset(tint, 0, sizeof(*tint)); + spin_lock_init(&tint->value_lock); + spin_lock_init(&tint->list_lock); +} + +int __init five_task_integrity_cache_init(void) +{ + task_integrity_cache = kmem_cache_create("task_integrity_cache", + sizeof(struct task_integrity), 0, + SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_NOTRACK, init_once); + + if (!task_integrity_cache) + return -ENOMEM; + + return 0; +} + +struct task_integrity *task_integrity_alloc(void) +{ + struct task_integrity *tint; + + tint = kmem_cache_alloc(task_integrity_cache, GFP_KERNEL); + if (tint) { + atomic_set(&tint->usage_count, 1); + INIT_LIST_HEAD(&tint->events.list); + } + + return tint; +} + +void task_integrity_free(struct task_integrity *tint) +{ + if (tint) { + /* These values should be changed under "value_lock" spinlock. + But then lockdep prints warning because this function can be called + from sw-irq (from function free_task). + Actually deadlock never happens because this function is called + only if usage_count is 0 (no reference to this struct), + so changing these values without spinlock is safe. + */ + kfree(tint->label); + tint->label = NULL; + tint->user_value = INTEGRITY_NONE; + tint->value = INTEGRITY_NONE; + atomic_set(&tint->usage_count, 0); + + tint->reset_cause = CAUSE_UNSET; + if (tint->reset_file) { + fput(tint->reset_file); + tint->reset_file = NULL; + } + + kmem_cache_free(task_integrity_cache, tint); + } +} + +void task_integrity_clear(struct task_integrity *tint) +{ + task_integrity_set(tint, INTEGRITY_NONE); + spin_lock(&tint->value_lock); + kfree(tint->label); + tint->label = NULL; + spin_unlock(&tint->value_lock); + + tint->reset_cause = CAUSE_UNSET; + if (tint->reset_file) { + fput(tint->reset_file); + tint->reset_file = NULL; + } +} + +static int copy_label(struct task_integrity *from, struct task_integrity *to) +{ + int ret = 0; + struct integrity_label *l = NULL; + + if (task_integrity_read(from) && from->label) + l = from->label; + + if (l) { + struct integrity_label *label; + + label = kmalloc(sizeof(*label) + l->len, GFP_ATOMIC); + if (!label) { + ret = -ENOMEM; + goto exit; + } + + label->len = l->len; + memcpy(label->data, l->data, label->len); + to->label = label; + } + +exit: + return ret; +} + +int task_integrity_copy(struct task_integrity *from, struct task_integrity *to) +{ + int rc = -EPERM; + enum task_integrity_value value = task_integrity_read(from); + + task_integrity_set(to, value); + + if (list_empty(&from->events.list)) + to->user_value = value; + + rc = copy_label(from, to); + + to->reset_cause = from->reset_cause; + if (from->reset_file) { + get_file(from->reset_file); + to->reset_file = from->reset_file; + } + + return rc; +} + +char const * const tint_reset_cause_to_string( + enum task_integrity_reset_cause cause) +{ + static const char * const tint_cause2str[] = { + [CAUSE_UNSET] = "unset", + [CAUSE_UNKNOWN] = "unknown", + [CAUSE_MISMATCH_LABEL] = "mismatch-label", + [CAUSE_BAD_FS] = "bad-fs", + [CAUSE_NO_CERT] = "no-cert", + [CAUSE_INVALID_HASH_LENGTH] = "invalid-hash-length", + [CAUSE_INVALID_HEADER] = "invalid-header", + [CAUSE_CALC_HASH_FAILED] = "calc-hash-failed", + [CAUSE_INVALID_LABEL_DATA] = "invalid-label-data", + [CAUSE_INVALID_SIGNATURE_DATA] = "invalid-signature-data", + [CAUSE_INVALID_HASH] = "invalid-hash", + [CAUSE_INVALID_CALC_CERT_HASH] = "invalid-calc-cert-hash", + [CAUSE_INVALID_UPDATE_LABEL] = "invalid-update-label", + [CAUSE_INVALID_SIGNATURE] = "invalid-signature", + [CAUSE_UKNOWN_FIVE_DATA] = "unknown-five-data", + [CAUSE_PTRACE] = "ptrace", + [CAUSE_VMRW] = "vmrw", + [CAUSE_EXEC] = "exec", + [CAUSE_TAMPERED] = "tampered", + [CAUSE_MAX] = "incorrect-cause", + }; + + if (cause > CAUSE_MAX) + cause = CAUSE_MAX; + + return tint_cause2str[cause]; +} + +/* + * task_integrity_set_reset_reason + * + * Only first call of this function per task will have effect, because first + * reason will be root cause. + */ +void task_integrity_set_reset_reason(struct task_integrity *tint, + enum task_integrity_reset_cause cause, struct file *file) +{ + if (tint->reset_cause != CAUSE_UNSET) + return; + + tint->reset_cause = cause; + if (file) { + get_file(file); + tint->reset_file = file; + } +} diff --git a/security/samsung/five/gki/task_integrity.h b/security/samsung/five/gki/task_integrity.h new file mode 100644 index 000000000000..5f8e0d0e59b6 --- /dev/null +++ b/security/samsung/five/gki/task_integrity.h @@ -0,0 +1,438 @@ +/* + * Task Integrity Verifier + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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_TASK_INTEGRITY_H +#define _LINUX_TASK_INTEGRITY_H + +#include +#include +#include +#include +#include +#include + +struct linux_binprm; +struct task_integrity; + +struct integrity_label { + uint16_t len; + uint8_t data[0]; +} __packed; + +enum five_event { + FIVE_RESET_INTEGRITY, + FIVE_VERIFY_BUNCH_FILES, +}; + +/* + * This is list of numbers which Hamming distance is 16 + * + * 0x00000000 <-- used + * 0x0f0f0f0f, + * 0xf0f0f0f0, + * 0x0000ffff, + * 0xffff0000, + * 0xff00ff00, + * 0x00ff00ff, + * 0x33333333,<-- used + * 0xcccccccc,<-- used + * 0x55555555,<-- used + * 0x5a5a5a5a,<-- used + * 0xa5a5a5a5, + * 0xaaaaaaaa,<-- used + * 0x3c3c3c3c,<-- used + * 0xc3c3c3c3, + * 0xffffffff <-- used + */ +enum task_integrity_value { + INTEGRITY_NONE = 0x00000000, + INTEGRITY_PRELOAD = 0x33333333, + INTEGRITY_PRELOAD_ALLOW_SIGN = 0xcccccccc, + INTEGRITY_MIXED = 0x55555555, + INTEGRITY_MIXED_ALLOW_SIGN = 0x5a5a5a5a, + INTEGRITY_DMVERITY = 0xaaaaaaaa, + INTEGRITY_DMVERITY_ALLOW_SIGN = 0x3c3c3c3c, + INTEGRITY_PROCESSING = 0xffffffff +}; + +struct processing_event_list { + enum five_event event; + struct task_struct *task; + struct file *file; + int function; + struct list_head list; +}; + +enum task_integrity_reset_cause { + CAUSE_UNSET, + CAUSE_UNKNOWN, + CAUSE_MISMATCH_LABEL, + CAUSE_BAD_FS, + CAUSE_NO_CERT, + CAUSE_INVALID_HASH_LENGTH, + CAUSE_INVALID_HEADER, + CAUSE_CALC_HASH_FAILED, + CAUSE_INVALID_LABEL_DATA, + CAUSE_INVALID_SIGNATURE_DATA, + CAUSE_INVALID_HASH, + CAUSE_INVALID_CALC_CERT_HASH, + CAUSE_INVALID_UPDATE_LABEL, + CAUSE_INVALID_SIGNATURE, + CAUSE_UKNOWN_FIVE_DATA, + CAUSE_PTRACE, + CAUSE_VMRW, + CAUSE_EXEC, + CAUSE_TAMPERED, + CAUSE_MAX, +}; + +struct task_integrity { + enum task_integrity_value user_value; + enum task_integrity_value value; + atomic_t usage_count; + spinlock_t value_lock; + spinlock_t list_lock; + struct integrity_label *label; + struct processing_event_list events; + enum task_integrity_reset_cause reset_cause; + struct file *reset_file; +}; + +#ifdef CONFIG_FIVE + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)) +#define TASK_INTEGRITY(task) \ + ((struct task_integrity *)((task)->android_oem_data1[2])) + +static inline void task_integrity_assign(struct task_struct *task, + struct task_integrity *tint) +{ + task->android_oem_data1[2] = (u64)tint; +} +#else +#define TASK_INTEGRITY(task) \ + ((struct task_integrity *)((task)->android_vendor_data1[2])) + +static inline void task_integrity_assign(struct task_struct *task, + struct task_integrity *tint) +{ + task->android_vendor_data1[2] = (u64)tint; +} +#endif + +extern void task_integrity_set_reset_reason(struct task_integrity *tint, + enum task_integrity_reset_cause cause, struct file *file); + +struct task_integrity *task_integrity_alloc(void); +void task_integrity_free(struct task_integrity *tint); +void task_integrity_clear(struct task_integrity *tint); + +static inline void task_integrity_get(struct task_integrity *tint) +{ + BUG_ON(!atomic_read(&tint->usage_count)); + atomic_inc(&tint->usage_count); +} + +static inline void task_integrity_put(struct task_integrity *tint) +{ + BUG_ON(!atomic_read(&tint->usage_count)); + if (atomic_dec_and_test(&tint->usage_count)) + task_integrity_free(tint); +} + +static inline void __task_integrity_set(struct task_integrity *tint, + enum task_integrity_value value) +{ + tint->value = value; +} + +static inline void task_integrity_set(struct task_integrity *tint, + enum task_integrity_value value) +{ + spin_lock(&tint->value_lock); + tint->value = value; + spin_unlock(&tint->value_lock); +} + +static inline void task_integrity_reset(struct task_integrity *tint) +{ + task_integrity_set(tint, INTEGRITY_NONE); + // If cause is already set, this function will be skipped + task_integrity_set_reset_reason(tint, CAUSE_UNKNOWN, NULL); +} + +extern void task_integrity_delayed_reset(struct task_struct *task, + enum task_integrity_reset_cause cause, struct file *file); + +static inline enum task_integrity_value task_integrity_read( + struct task_integrity *tint) +{ + enum task_integrity_value value; + + spin_lock(&tint->value_lock); + value = tint->value; + spin_unlock(&tint->value_lock); + + return value; +} + +static inline bool task_integrity_value_allow_sign( + enum task_integrity_value value) +{ + if (value == INTEGRITY_PRELOAD_ALLOW_SIGN + || value == INTEGRITY_MIXED_ALLOW_SIGN + || value == INTEGRITY_DMVERITY_ALLOW_SIGN) { + return true; + } + + return false; +} + +/** + * task_integrity_allow_sign - check whether application is allowed to sign + * @tint: pointer to the corresponding integrity struct (Should not be NULL) + * + * On success return true. + */ +static inline bool task_integrity_allow_sign(struct task_integrity *tint) +{ + enum task_integrity_value value = task_integrity_read(tint); + + return task_integrity_value_allow_sign(value); +} + +static inline enum task_integrity_value task_integrity_user_read( + struct task_integrity *tint) +{ + return tint->user_value; +} + +static inline void task_integrity_user_set(struct task_integrity *tint, + enum task_integrity_value value) +{ + tint->user_value = value; +} + +static inline void task_integrity_reset_both(struct task_integrity *tint) +{ + task_integrity_reset(tint); + tint->user_value = INTEGRITY_NONE; +} + +extern int task_integrity_copy(struct task_integrity *from, + struct task_integrity *to); +extern int five_bprm_check(struct linux_binprm *bprm); +extern void five_file_free(struct file *file); +extern int five_file_mmap(struct file *file, unsigned long prot); +extern int five_file_open(struct file *file); +extern int five_file_verify(struct task_struct *task, struct file *file); +extern void five_task_free(struct task_struct *task); + +extern void five_inode_post_setattr(struct task_struct *tsk, + struct dentry *dentry); +extern int five_inode_setxattr(struct dentry *dentry, const char *xattr_name, + const void *xattr_value, size_t xattr_value_len); +extern int five_inode_removexattr(struct dentry *dentry, + const char *xattr_name); +extern int five_fcntl_sign(struct file *file, + struct integrity_label __user *label); +extern int five_fcntl_verify_async(struct file *file); +extern int five_fcntl_verify_sync(struct file *file); +extern int five_fcntl_edit(struct file *file); +extern int five_fcntl_close(struct file *file); +extern int five_fcntl_debug(struct file *file, void __user *arg); +extern int five_fork(struct task_struct *task, struct task_struct *child_task); +extern int five_ptrace(struct task_struct *task, long request); +extern int five_process_vm_rw(struct task_struct *task, int write); +extern char const * const tint_reset_cause_to_string( + enum task_integrity_reset_cause cause); +#else +#define TASK_INTEGRITY(task) (NULL) + +static inline void task_integrity_assign(struct task_struct *task, + struct task_integrity *tint) +{ +} + +static inline struct task_integrity *task_integrity_alloc(void) +{ + return NULL; +} + +static inline void task_integrity_free(struct task_integrity *tint) +{ +} + +static inline void task_integrity_clear(struct task_integrity *tint) +{ +} + +static inline void task_integrity_set(struct task_integrity *tint, + enum task_integrity_value value) +{ +} + +static inline void task_integrity_reset(struct task_integrity *tint) +{ +} + +static inline enum task_integrity_value task_integrity_read( + struct task_integrity *tint) +{ + return INTEGRITY_NONE; +} + +static inline void task_integrity_user_set(struct task_integrity *tint, + enum task_integrity_value value) +{ +} + +static inline enum task_integrity_value task_integrity_user_read( + struct task_integrity *tint) +{ + return INTEGRITY_NONE; +} + +static inline void task_integrity_delayed_reset(struct task_struct *task, + enum task_integrity_reset_cause cause, struct file *file) +{ +} + +static inline void task_integrity_reset_both(struct task_integrity *tint) +{ +} + +static inline void task_integrity_add_file(struct task_integrity *tint) +{ +} + +static inline void task_integrity_report_file(struct task_integrity *tint) +{ +} + +static inline int five_bprm_check(struct linux_binprm *bprm) +{ + return 0; +} + +static inline void five_file_free(struct file *file) +{ +} + +static inline int five_file_mmap(struct file *file, unsigned long prot) +{ + return 0; +} + +static inline int five_file_open(struct file *file) +{ + return 0; +} + +static inline int five_file_verify(struct task_struct *task, struct file *file) +{ + return 0; +} + +static inline void five_task_free(struct task_struct *task) +{ +} + +static inline void five_inode_post_setattr(struct task_struct *tsk, + struct dentry *dentry) +{ +} + +static inline int five_inode_setxattr(struct dentry *dentry, + const char *xattr_name, + const void *xattr_value, + size_t xattr_value_len) +{ + return 0; +} + +static inline int five_inode_removexattr(struct dentry *dentry, + const char *xattr_name) +{ + return 0; +} + +static inline int five_fcntl_sign(struct file *file, + struct integrity_label __user *label) +{ + return 0; +} + +static inline int five_fcntl_verify_async(struct file *file) +{ + return 0; +} + +static inline int five_fcntl_verify_sync(struct file *file) +{ + return 0; +} + +static inline int five_fcntl_edit(struct file *file) +{ + return 0; +} + +static inline int five_fcntl_close(struct file *file) +{ + return 0; +} + +static inline int five_fcntl_debug(struct file *file, void __user *arg) +{ + return 0; +} + +static inline int five_fork(struct task_struct *task, + struct task_struct *child_task) +{ + return 0; +} + +static inline int five_ptrace(struct task_struct *task, long request) +{ + return 0; +} + +static inline int five_process_vm_rw(struct task_struct *task, int write) +{ + return 0; +} + +static inline int task_integrity_copy(struct task_integrity *from, + struct task_integrity *to) +{ + return 0; +} + +static inline char const * const tint_reset_cause_to_string( + enum task_integrity_reset_cause cause) +{ + return NULL; +} + +static inline void task_integrity_set_reset_reason(struct task_integrity *tint, + enum task_integrity_reset_cause cause, struct file *file) +{ +} +#endif + +#endif /* _LINUX_TASK_INTEGRITY_H */ diff --git a/security/samsung/five/s_os/dummy.h b/security/samsung/five/s_os/dummy.h new file mode 100644 index 000000000000..668fc5389ef0 --- /dev/null +++ b/security/samsung/five/s_os/dummy.h @@ -0,0 +1,15 @@ +/* + * Dummy header + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * Jonghun Song, + * + * 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. + */ diff --git a/security/samsung/five/s_os/five_appraise.c b/security/samsung/five/s_os/five_appraise.c new file mode 100644 index 000000000000..a731272f0fed --- /dev/null +++ b/security/samsung/five/s_os/five_appraise.c @@ -0,0 +1,300 @@ +/* + * This code is based on IMA's code + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * + * Egor Uleyskiy, + * Viacheslav Vovchenko + * Yevgen Kopylov + * + * 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 "five.h" +#include "five_audit.h" +#include "five_hooks.h" +#include "five_tee_api.h" +#include "five_porting.h" +#include "five_cache.h" +#include "five_dmverity.h" + +int five_read_xattr(struct dentry *dentry, char **xattr_value) +{ + ssize_t ret; + + ret = vfs_getxattr_alloc(dentry, XATTR_NAME_FIVE, xattr_value, + 0, GFP_NOFS); + if (ret < 0) + ret = 0; + + return ret; +} + +static bool bad_fs(struct inode *inode) +{ + if (inode->i_sb->s_magic == EXT4_SUPER_MAGIC || + inode->i_sb->s_magic == F2FS_SUPER_MAGIC || + inode->i_sb->s_magic == OVERLAYFS_SUPER_MAGIC || + inode->i_sb->s_magic == EROFS_SUPER_MAGIC_V1) + return false; + + return true; +} + +static bool readonly_sb(struct inode *inode) +{ + if (inode->i_sb->s_flags & MS_RDONLY) + return true; + + return false; +} + +/* + * five_is_fsverity_protected - checks if file is protected by FSVERITY + * + * Return true/false + */ +static bool five_is_fsverity_protected(const struct inode *inode) +{ + return IS_VERITY(inode); +} + +/* + * five_appraise_measurement - appraise file measurement + * + * Return 0 on success, error code otherwise + */ +int five_appraise_measurement(struct task_struct *task, int func, + struct integrity_iint_cache *iint, + struct file *file, + struct five_cert *cert) +{ + enum task_integrity_reset_cause cause = CAUSE_UNKNOWN; + struct dentry *dentry = NULL; + struct inode *inode = NULL; + enum five_file_integrity status = FIVE_FILE_UNKNOWN; + enum task_integrity_value prev_integrity; + int rc = 0; + + BUG_ON(!task || !iint || !file); + + prev_integrity = task_integrity_read(TASK_INTEGRITY(task)); + dentry = file->f_path.dentry; + inode = d_backing_inode(dentry); + + if (bad_fs(inode)) { + status = FIVE_FILE_FAIL; + cause = CAUSE_BAD_FS; + rc = -ENOTSUPP; + goto out; + } + + cause = CAUSE_NO_CERT; + if (five_is_fsverity_protected(inode)) + status = FIVE_FILE_FSVERITY; + else if (five_is_dmverity_protected(file)) + status = FIVE_FILE_DMVERITY; + +out: + if (status == FIVE_FILE_FAIL || status == FIVE_FILE_UNKNOWN) { + task_integrity_set_reset_reason(TASK_INTEGRITY(task), + cause, file); + five_audit_verbose(task, file, five_get_string_fn(func), + prev_integrity, prev_integrity, + tint_reset_cause_to_string(cause), rc); + } + + five_set_cache_status(iint, status); + + return rc; +} + +static void five_reset_appraise_flags(struct dentry *dentry) +{ + struct inode *inode = d_backing_inode(dentry); + struct integrity_iint_cache *iint; + + if (!S_ISREG(inode->i_mode)) + return; + + iint = integrity_iint_find(inode); + if (iint) + five_set_cache_status(iint, FIVE_FILE_UNKNOWN); +} + +/** + * five_inode_post_setattr - reflect file metadata changes + * @dentry: pointer to the affected dentry + * + * Changes to a dentry's metadata might result in needing to appraise. + * + * This function is called from notify_change(), which expects the caller + * to lock the inode's i_mutex. + */ +void five_inode_post_setattr(struct task_struct *task, struct dentry *dentry) +{ + five_reset_appraise_flags(dentry); +} + +/* + * five_protect_xattr - protect 'security.five' + * + * Ensure that not just anyone can modify or remove 'security.five'. + */ +static int five_protect_xattr(struct dentry *dentry, const char *xattr_name, + const void *xattr_value, size_t xattr_value_len) +{ + if (strcmp(xattr_name, XATTR_NAME_FIVE) == 0) { + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + return 1; + } + return 0; +} + +int five_inode_setxattr(struct dentry *dentry, const char *xattr_name, + const void *xattr_value, size_t xattr_value_len) +{ + int result = five_protect_xattr(dentry, xattr_name, xattr_value, + xattr_value_len); + + if (result == 1 && xattr_value_len == 0) { + five_reset_appraise_flags(dentry); + return 0; + } + + if (result == 1) { + bool digsig; + struct five_cert_header *header; + struct five_cert cert = { {0} }; + + result = five_cert_fillout(&cert, xattr_value, xattr_value_len); + if (result) + return result; + + header = (struct five_cert_header *)cert.body.header->value; + + if (!xattr_value_len || !header || + (header->signature_type >= FIVE_XATTR_END)) + return -EINVAL; + + digsig = (header->signature_type == FIVE_XATTR_DIGSIG); + if (!digsig) + return -EPERM; + + five_reset_appraise_flags(dentry); + result = 0; + } + + return result; +} + +int five_inode_removexattr(struct dentry *dentry, const char *xattr_name) +{ + int result; + + result = five_protect_xattr(dentry, xattr_name, NULL, 0); + if (result == 1) { + five_reset_appraise_flags(dentry); + result = 0; + } + return result; +} + +/* Called from do_fcntl */ +int five_fcntl_sign(struct file *file, struct integrity_label __user *label) +{ + return -EOPNOTSUPP; +} + +static int check_input_inode(struct inode *inode) +{ + if (!S_ISREG(inode->i_mode)) + return -EINVAL; + + if (readonly_sb(inode)) { + pr_err("FIVE: Can't sign a file on RO FS\n"); + return -EROFS; + } + + return 0; +} + +int five_fcntl_edit(struct file *file) +{ + int rc; + struct dentry *dentry; + uint8_t *raw_cert = NULL; + size_t raw_cert_len = 0; + struct integrity_iint_cache *iint; + struct inode *inode = file_inode(file); + + rc = check_input_inode(inode); + if (rc) + return rc; + + if (!task_integrity_allow_sign(TASK_INTEGRITY(current))) + return -EPERM; + + inode_lock(inode); + dentry = file->f_path.dentry; + rc = __vfs_setxattr_noperm(d_real_comp(dentry), + XATTR_NAME_FIVE, + raw_cert, + raw_cert_len, + 0); + iint = integrity_inode_get(inode); + if (iint) + iint->five_signing = true; + inode_unlock(inode); + + return rc; +} + +int five_fcntl_close(struct file *file) +{ + int rc; + ssize_t xattr_len; + struct dentry *dentry; + struct integrity_iint_cache *iint; + struct inode *inode = file_inode(file); + + rc = check_input_inode(inode); + if (rc) + return rc; + + inode_lock(inode); + iint = integrity_inode_get(inode); + if (!iint) { + inode_unlock(inode); + return -ENOMEM; + } + + if (iint->five_signing) { + dentry = file->f_path.dentry; + xattr_len = __vfs_getxattr(d_real_comp(dentry), inode, + XATTR_NAME_FIVE, NULL, 0, XATTR_NOSECURITY); + if (xattr_len == 0) + rc = __vfs_removexattr(d_real_comp(dentry), + XATTR_NAME_FIVE); + + iint->five_signing = false; + } + inode_unlock(inode); + + return rc; +} diff --git a/security/samsung/five/s_os/five_main.c b/security/samsung/five/s_os/five_main.c new file mode 100644 index 000000000000..bf17b6c9ec59 --- /dev/null +++ b/security/samsung/five/s_os/five_main.c @@ -0,0 +1,998 @@ +/* + * This code is based on IMA's code + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * + * Egor Uleyskiy, + * Viacheslav Vovchenko + * Yevgen Kopylov + * + * 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 "five.h" +#include "five_audit.h" +#include "five_hooks.h" +#include "five_state.h" +#include "five_pa.h" +#include "five_porting.h" +#include "five_cache.h" +#include "five_dmverity.h" +#include "five_dsms.h" +#include "five_testing.h" + +/* crash_dump in Android 12 uses this request even if Kernel doesn't + * support it */ +#ifndef PTRACE_PEEKMTETAGS +#define PTRACE_PEEKMTETAGS 33 +#endif + +static const bool check_memfd_file = true; + +static struct file *memfd_file __ro_after_init; +static bool is_five_initialized __ro_after_init; + +static struct workqueue_struct *g_five_workqueue; + +static inline void task_integrity_processing(struct task_integrity *tint); +static inline void task_integrity_done(struct task_integrity *tint); +static void process_measurement(const struct processing_event_list *params); +static inline struct processing_event_list *five_event_create( + enum five_event event, struct task_struct *task, + struct file *file, int function, gfp_t flags); +static inline void five_event_destroy( + const struct processing_event_list *file); + +#ifdef CONFIG_FIVE_DEBUG +static int five_enabled = 1; + +static ssize_t five_enabled_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + char command; + + if (get_user(command, buf)) + return -EFAULT; + + switch (command) { + case '0': + five_enabled = 0; + break; + case '1': + five_enabled = 1; + break; + default: + pr_err("FIVE: %s: unknown cmd: %hhx\n", __func__, command); + return -EINVAL; + } + + pr_info("FIVE debug: FIVE %s\n", five_enabled ? "enabled" : "disabled"); + return count; +} + +static ssize_t five_enabled_read(struct file *file, char __user *user_buf, + size_t count, loff_t *pos) +{ + char buf[2]; + + buf[0] = five_enabled ? '1' : '0'; + buf[1] = '\n'; + + return simple_read_from_buffer(user_buf, count, pos, buf, sizeof(buf)); +} + +static const struct file_operations five_enabled_fops = { + .owner = THIS_MODULE, + .read = five_enabled_read, + .write = five_enabled_write +}; + +static int __init init_fs(void) +{ + struct dentry *debug_file = NULL; + umode_t umode = (S_IRUGO | S_IWUSR | S_IWGRP); + + debug_file = debugfs_create_file( + "five_enabled", umode, NULL, NULL, &five_enabled_fops); + if (IS_ERR_OR_NULL(debug_file)) + goto error; + + return 0; +error: + if (debug_file) + return -PTR_ERR(debug_file); + + return -EEXIST; +} + +static inline int is_five_enabled(void) +{ + return five_enabled; +} + +int five_fcntl_debug(struct file *file, void __user *argp) +{ + struct inode *inode; + struct five_stat stat = {0}; + struct integrity_iint_cache *iint; + + if (unlikely(!file || !argp)) + return -EINVAL; + + inode = file_inode(file); + + inode_lock(inode); + iint = integrity_inode_get(inode); + if (unlikely(!iint)) { + inode_unlock(inode); + return -ENOMEM; + } + + stat.cache_status = five_get_cache_status(iint); + stat.cache_iversion = inode_query_iversion(iint->inode); + stat.inode_iversion = inode_query_iversion(inode); + + inode_unlock(inode); + + if (unlikely(copy_to_user(argp, &stat, sizeof(stat)))) + return -EFAULT; + + return 0; +} +#else +static int __init init_fs(void) +{ + return 0; +} + +static inline int is_five_enabled(void) +{ + return 1; +} +#endif + +static void work_handler(struct work_struct *in_data) +{ + struct worker_context *context = container_of(in_data, + struct worker_context, data_work); + struct task_integrity *intg; + + if (unlikely(!context)) + return; + + intg = context->tint; + + spin_lock(&intg->list_lock); + while (!list_empty(&(intg->events.list))) { + struct processing_event_list *five_file; + + five_file = list_entry(intg->events.list.next, + struct processing_event_list, list); + spin_unlock(&intg->list_lock); + switch (five_file->event) { + case FIVE_VERIFY_BUNCH_FILES: { + process_measurement(five_file); + break; + } + case FIVE_RESET_INTEGRITY: { + task_integrity_reset(intg); + five_hook_integrity_reset(five_file->task, + NULL, CAUSE_UNKNOWN); + break; + } + default: + break; + } + spin_lock(&intg->list_lock); + list_del(&five_file->list); + five_event_destroy(five_file); + } + + task_integrity_done(intg); + spin_unlock(&intg->list_lock); + task_integrity_put(intg); + + kfree(context); +} + +__mockable +const char *five_d_path(const struct path *path, char **pathbuf, char *namebuf) +{ + char *pathname = NULL; + + *pathbuf = __getname(); + if (*pathbuf) { + pathname = d_absolute_path(path, *pathbuf, PATH_MAX); + if (IS_ERR(pathname)) { + __putname(*pathbuf); + *pathbuf = NULL; + pathname = NULL; + } + } + + if (!pathname) { + strlcpy(namebuf, path->dentry->d_name.name, NAME_MAX); + pathname = namebuf; + } + + return pathname; +} + +int five_check_params(struct task_struct *task, struct file *file) +{ + struct inode *inode; + + if (unlikely(!file)) + return 1; + + inode = file_inode(file); + if (!S_ISREG(inode->i_mode)) + return 1; + + return 0; +} + +/* cut a list into two + * @cur_list: a list with entries + * @new_list: a new list to add all removed entries. Should be an empty list + * + * Use it under spin_lock + * + * The function moves second entry and following entries to new list. + * First entry is left in cur_list. + * + * Initial state: + * [cur_list]<=>[first]<=>[second]<=>[third]<=>...<=>[last] [new_list] + * ^=================================================^ ^====^ + * Result: + * [cur_list]<=>[first] + * ^==========^ + * [new_list]<=>[second]<=>[third]<=>...<=>[last] + * ^=====================================^ + * + * the function is similar to kernel list_cut_position, but there are few + * differences: + * - cut position is second entry + * - original list is left with only first entry + * - moving entries are from second entry to last entry + */ +static void list_cut_tail(struct list_head *cur_list, + struct list_head *new_list) +{ + if ((!list_empty(cur_list)) && + (!list_is_singular(cur_list))) { + new_list->next = cur_list->next->next; + cur_list->next->next->prev = new_list; + cur_list->next->next = cur_list; + new_list->prev = cur_list->prev; + cur_list->prev->next = new_list; + cur_list->prev = cur_list->next; + } +} + +static void free_files_list(struct list_head *list) +{ + struct list_head *tmp, *list_entry; + struct processing_event_list *file_entry; + + list_for_each_safe(list_entry, tmp, list) { + file_entry = list_entry(list_entry, + struct processing_event_list, list); + list_del(&file_entry->list); + five_event_destroy(file_entry); + } +} + +static int push_file_event_bunch(struct task_struct *task, struct file *file, + int function) +{ + int rc = 0; + struct worker_context *context; + struct processing_event_list *five_file; + + if (unlikely(!is_five_enabled()) || five_check_params(task, file)) + return 0; + + context = kmalloc(sizeof(struct worker_context), GFP_KERNEL); + if (unlikely(!context)) + return -ENOMEM; + + five_file = five_event_create(FIVE_VERIFY_BUNCH_FILES, task, file, + function, GFP_KERNEL); + if (unlikely(!five_file)) { + kfree(context); + return -ENOMEM; + } + + spin_lock(&TASK_INTEGRITY(task)->list_lock); + + if (list_empty(&(TASK_INTEGRITY(task)->events.list))) { + task_integrity_get(TASK_INTEGRITY(task)); + task_integrity_processing(TASK_INTEGRITY(task)); + + context->tint = TASK_INTEGRITY(task); + + list_add_tail(&five_file->list, + &TASK_INTEGRITY(task)->events.list); + spin_unlock(&TASK_INTEGRITY(task)->list_lock); + INIT_WORK(&context->data_work, work_handler); + rc = queue_work(g_five_workqueue, &context->data_work) ? 0 : 1; + } else { + struct list_head dead_list; + + INIT_LIST_HEAD(&dead_list); + if ((function == BPRM_CHECK) && + (!list_is_singular(&(TASK_INTEGRITY(task)->events.list)))) { + list_cut_tail(&TASK_INTEGRITY(task)->events.list, + &dead_list); + } + list_add_tail(&five_file->list, + &TASK_INTEGRITY(task)->events.list); + spin_unlock(&TASK_INTEGRITY(task)->list_lock); + free_files_list(&dead_list); + kfree(context); + } + return rc; +} + +static int push_reset_event(struct task_struct *task, + enum task_integrity_reset_cause cause, struct file *file) +{ + struct list_head dead_list; + struct task_integrity *current_tint; + struct processing_event_list *five_reset; + + if (unlikely(!is_five_enabled())) + return 0; + + INIT_LIST_HEAD(&dead_list); + current_tint = TASK_INTEGRITY(task); + task_integrity_get(current_tint); + + task_integrity_set_reset_reason(current_tint, cause, file); + + five_reset = five_event_create(FIVE_RESET_INTEGRITY, task, NULL, 0, + GFP_KERNEL); + if (unlikely(!five_reset)) { + task_integrity_reset_both(current_tint); + five_hook_integrity_reset(task, file, cause); + task_integrity_put(current_tint); + return -ENOMEM; + } + + task_integrity_reset_both(current_tint); + five_hook_integrity_reset(task, file, cause); + spin_lock(¤t_tint->list_lock); + if (!list_empty(¤t_tint->events.list)) { + list_cut_tail(¤t_tint->events.list, &dead_list); + five_reset->event = FIVE_RESET_INTEGRITY; + list_add_tail(&five_reset->list, ¤t_tint->events.list); + spin_unlock(¤t_tint->list_lock); + } else { + spin_unlock(¤t_tint->list_lock); + five_event_destroy(five_reset); + } + + task_integrity_put(current_tint); + /* remove dead_list */ + free_files_list(&dead_list); + return 0; +} + +void task_integrity_delayed_reset(struct task_struct *task, + enum task_integrity_reset_cause cause, struct file *file) +{ + push_reset_event(task, cause, file); +} + +static void five_check_last_writer(struct integrity_iint_cache *iint, + struct inode *inode, struct file *file) +{ + fmode_t mode = file->f_mode; + + if (!(mode & FMODE_WRITE)) + return; + + inode_lock(inode); + if (atomic_read(&inode->i_writecount) == 1) { + if (!inode_eq_iversion(inode, iint->version)) + five_set_cache_status(iint, FIVE_FILE_UNKNOWN); + } + iint->five_signing = false; + inode_unlock(inode); +} + +/** + * five_file_free - called on __fput() + * @file: pointer to file structure being freed + * + * Flag files that changed, based on i_version + */ +void five_file_free(struct file *file) +{ + struct inode *inode = file_inode(file); + struct integrity_iint_cache *iint; + + if (!S_ISREG(inode->i_mode)) + return; + + fivepa_fsignature_free(file); + + iint = integrity_iint_find(inode); + if (!iint) + return; + + five_check_last_writer(iint, inode, file); +} + +void five_task_free(struct task_struct *task) +{ + task_integrity_put(TASK_INTEGRITY(task)); +} + +/* Returns string representation of input function */ +const char *five_get_string_fn(enum five_hooks fn) +{ + switch (fn) { + case FILE_CHECK: + return "file-check"; + case MMAP_CHECK: + return "mmap-check"; + case BPRM_CHECK: + return "bprm-check"; + case POST_SETATTR: + return "post-setattr"; + } + return "unknown-function"; +} + +static inline void task_integrity_processing(struct task_integrity *tint) +{ + tint->user_value = INTEGRITY_PROCESSING; +} + +static inline void task_integrity_done(struct task_integrity *tint) +{ + tint->user_value = task_integrity_read(tint); +} + +static void process_file(struct task_struct *task, + struct file *file, + int function, + struct file_verification_result *result) +{ + struct inode *inode = d_real_inode(file_dentry(file)); + struct integrity_iint_cache *iint = NULL; + int rc = -ENOMEM; + + if (!S_ISREG(inode->i_mode)) { + rc = 0; + goto out; + } + + iint = integrity_inode_get(inode); + if (!iint) + goto out; + + /* Nothing to do, just return existing appraised status */ + if (five_get_cache_status(iint) != FIVE_FILE_UNKNOWN) { + rc = 0; + goto out; + } + + rc = five_appraise_measurement(task, function, iint, file, NULL); + +out: + if (rc && iint) + iint->five_flags &= ~FIVE_TRUSTED_FILE; + + result->file = file; + result->task = task; + result->iint = iint; + result->fn = function; + result->xattr = NULL; + result->xattr_len = 0; + + if (!iint || five_get_cache_status(iint) == FIVE_FILE_UNKNOWN + || five_get_cache_status(iint) == FIVE_FILE_FAIL) + result->five_result = 1; + else + result->five_result = 0; +} + +static void process_measurement(const struct processing_event_list *params) +{ + struct task_struct *task = params->task; + struct task_integrity *integrity = TASK_INTEGRITY(task); + struct file *file = params->file; + struct inode *inode = file_inode(file); + int function = params->function; + struct file_verification_result file_result; + + if (function != BPRM_CHECK) { + if (task_integrity_read(integrity) == INTEGRITY_NONE) + return; + } + + file_verification_result_init(&file_result); + inode_lock(inode); + + process_file(task, file, function, &file_result); + + five_hook_file_processed(task, file, + file_result.xattr, file_result.xattr_len, + file_result.five_result); + + five_state_proceed(integrity, &file_result); + + inode_unlock(inode); + file_verification_result_deinit(&file_result); +} + +static bool is_memfd_file(struct file *file) +{ + struct inode *inode; + struct inode *memfd_inode; + + if (!file) + return false; + + memfd_inode = file_inode(memfd_file); + inode = file_inode(file); + if (inode && memfd_inode && inode->i_sb == memfd_inode->i_sb) + return true; + + return false; +} + +static bool is_dalvik_cache_file(struct file *file) +{ + const char dalvik_prefix[] = "/data/dalvik-cache/arm"; + + const char *pathname = NULL; + char *pathbuf = NULL; + + char filename[NAME_MAX]; + bool res = false; + + if (!file || !file->f_path.dentry) + return false; + + pathname = five_d_path(&file->f_path, &pathbuf, filename); + if (!strncmp(pathname, dalvik_prefix, strlen(dalvik_prefix))) + res = true; + + if (pathbuf) + __putname(pathbuf); + + return res; +} + +/** + * five_file_mmap - measure files being mapped executable based on + * the process_measurement() policy decision. + * @file: pointer to the file to be measured (May be NULL) + * @prot: contains the protection that will be applied by the kernel. + * + * On success return 0. + */ +int five_file_mmap(struct file *file, unsigned long prot) +{ + int rc = 0; + struct task_struct *task = current; + struct task_integrity *tint = TASK_INTEGRITY(task); + + if (unlikely(!is_five_initialized) || five_check_params(task, file)) + return 0; + + if (check_memfd_file && is_memfd_file(file)) + return 0; + + if (file && task_integrity_user_read(tint)) { + if ((prot & PROT_EXEC) && !is_dalvik_cache_file(file)) { + rc = push_file_event_bunch(task, file, MMAP_CHECK); + if (rc) + return rc; + } else { + five_hook_file_skipped(task, file); + } + } + + return rc; +} + +/** + * five_bprm_check - Measure executable being launched based on + * the process_measurement() policy decision. + * @bprm: contains the linux_binprm structure + * + * Notes: + * bprm_check could be called few times for one process when few binary loaders + * are used. Example: execution of shell script. + * In this case we should process first file (e.g. shell script) as main and + * use BPRM_CHECK. The second file (interpetator ) will be processed as general + * mapping (MMAP_CHECK). + * To implement this option variable bprm->recursion_depth is used. + * + * On success return 0. + */ +int __five_bprm_check(struct linux_binprm *bprm, int depth) +{ + int rc = 0; + struct task_struct *task = current; + struct task_integrity *old_tint = TASK_INTEGRITY(task); + + if (unlikely(!is_five_initialized) || unlikely(task->ptrace)) + return rc; + + if (depth > 0) { + rc = push_file_event_bunch(task, bprm->file, MMAP_CHECK); + } else { + struct task_integrity *tint = task_integrity_alloc(); + + task_integrity_assign(task, tint); + if (likely(TASK_INTEGRITY(task))) { + rc = push_file_event_bunch(task, + bprm->file, BPRM_CHECK); + } else { + rc = -ENOMEM; + } + task_integrity_put(old_tint); + } + + return rc; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)) +int five_bprm_check(struct linux_binprm *bprm, int depth) +{ + return __five_bprm_check(bprm, depth); +} +#else +int five_bprm_check(struct linux_binprm *bprm) +{ + return __five_bprm_check(bprm, bprm->recursion_depth); +} +#endif + +int five_file_open(struct file *file) +{ + return 0; +} + +/** + * five_file_verify - force five integrity measurements for file + * the process_measurement() policy decision. This check affects + * task integrity. + * @file: pointer to the file to be measured (May be NULL) + * + * On success return 0. + */ +int five_file_verify(struct task_struct *task, struct file *file) +{ + int rc = 0; + struct task_integrity *tint = TASK_INTEGRITY(task); + + if (file && task_integrity_user_read(tint)) + rc = push_file_event_bunch(task, file, FILE_CHECK); + + return rc; +} + +int five_hash_algo __ro_after_init = HASH_ALGO_SHA1; + +static int __init hash_setup(const char *str) +{ + int i; + + for (i = 0; i < HASH_ALGO__LAST; i++) { + if (strcmp(str, hash_algo_name[i]) == 0) { + five_hash_algo = i; + break; + } + } + + return 1; +} + +int __init init_five(void) +{ + int error; + + g_five_workqueue = alloc_workqueue("%s", WQ_FREEZABLE | WQ_MEM_RECLAIM, + 0, "five_wq"); + if (!g_five_workqueue) + return -ENOMEM; + + hash_setup(CONFIG_FIVE_DEFAULT_HASH); + error = five_init(); + if (error) + return error; + +/** + * This empty file is needed in is_memfd_file() function. + * The only way to check whether the file was created using memfd_create() + * syscall is to compare its superblock address with address of another memfd + * file. + */ + memfd_file = shmem_kernel_file_setup( + "five_memfd_check", 0, VM_NORESERVE); + if (IS_ERR(memfd_file)) { + error = PTR_ERR(memfd_file); + memfd_file = NULL; + return error; + } + + error = init_fs(); + if (error) + return error; + + five_dsms_init("1", 0); + + error = five_init_dmverity(); + + if (!error) + is_five_initialized = true; + + return error; +} + +static int fcntl_verify(struct file *file) +{ + int rc = 0; + struct task_struct *task = current; + struct task_integrity *tint = TASK_INTEGRITY(task); + + if (task_integrity_user_read(tint)) + rc = push_file_event_bunch(task, file, FILE_CHECK); + return rc; +} + +/* Called from do_fcntl */ +int five_fcntl_verify_async(struct file *file) +{ + return fcntl_verify(file); +} + +/* Called from do_fcntl */ +int five_fcntl_verify_sync(struct file *file) +{ + return -EINVAL; +} + +struct bprm_hook_context { + struct work_struct data_work; + struct task_struct *task; + struct task_struct *child_task; +}; + +static void bprm_hook_handler(struct work_struct *in_data) +{ + struct bprm_hook_context *context = container_of(in_data, + struct bprm_hook_context, data_work); + + if (unlikely(!context)) + return; + + five_hook_task_forked(context->task, context->child_task); + + put_task_struct(context->task); + put_task_struct(context->child_task); + + kfree(context); +} + +int five_fork(struct task_struct *task, struct task_struct *child_task) +{ + int rc = 0; + struct bprm_hook_context *context; + + spin_lock(&TASK_INTEGRITY(task)->list_lock); + + if (!list_empty(&TASK_INTEGRITY(task)->events.list)) { + /*copy the list*/ + struct list_head *tmp; + struct processing_event_list *from_entry; + struct worker_context *context; + + context = kmalloc(sizeof(struct worker_context), GFP_ATOMIC); + if (unlikely(!context)) { + spin_unlock(&TASK_INTEGRITY(task)->list_lock); + return -ENOMEM; + } + + list_for_each(tmp, &TASK_INTEGRITY(task)->events.list) { + struct processing_event_list *five_file; + + from_entry = list_entry(tmp, + struct processing_event_list, list); + + five_file = five_event_create( + from_entry->event, + child_task, + from_entry->file, + from_entry->function, + GFP_ATOMIC); + if (unlikely(!five_file)) { + kfree(context); + spin_unlock(&TASK_INTEGRITY(task)->list_lock); + return -ENOMEM; + } + + list_add_tail(&five_file->list, + &TASK_INTEGRITY(child_task)->events.list); + } + + context->tint = TASK_INTEGRITY(child_task); + + rc = task_integrity_copy(TASK_INTEGRITY(task), + TASK_INTEGRITY(child_task)); + spin_unlock(&TASK_INTEGRITY(task)->list_lock); + task_integrity_get(context->tint); + task_integrity_processing(TASK_INTEGRITY(child_task)); + INIT_WORK(&context->data_work, work_handler); + rc = queue_work(g_five_workqueue, &context->data_work) ? 0 : 1; + } else { + rc = task_integrity_copy(TASK_INTEGRITY(task), + TASK_INTEGRITY(child_task)); + spin_unlock(&TASK_INTEGRITY(task)->list_lock); + } + + if (rc) + return rc; + + context = kmalloc(sizeof(struct bprm_hook_context), GFP_ATOMIC); + if (unlikely(!context)) + return -ENOMEM; + + get_task_struct(task); + get_task_struct(child_task); + context->task = task; + context->child_task = child_task; + INIT_WORK(&context->data_work, bprm_hook_handler); + rc = queue_work(g_five_workqueue, &context->data_work) ? 0 : 1; + + return rc; +} + +int five_ptrace(struct task_struct *task, long request) +{ + switch (request) { + case PTRACE_TRACEME: + case PTRACE_ATTACH: + case PTRACE_SEIZE: + case PTRACE_INTERRUPT: + case PTRACE_CONT: + case PTRACE_DETACH: + case PTRACE_PEEKTEXT: + case PTRACE_PEEKDATA: + case PTRACE_PEEKUSR: + case PTRACE_GETREGSET: + case PTRACE_GETSIGINFO: + case PTRACE_PEEKSIGINFO: + case PTRACE_GETSIGMASK: + case PTRACE_GETEVENTMSG: + case PTRACE_PEEKMTETAGS: +#if defined(CONFIG_ARM64) || defined(KUNIT_UML) + case COMPAT_PTRACE_GETREGS: + case COMPAT_PTRACE_GET_THREAD_AREA: + case COMPAT_PTRACE_GETVFPREGS: + case COMPAT_PTRACE_GETHBPREGS: +#else + case PTRACE_GETREGS: + case PTRACE_GET_THREAD_AREA: + case PTRACE_GETVFPREGS: + case PTRACE_GETHBPREGS: +#endif + break; + default: { + struct task_integrity *tint = TASK_INTEGRITY(task); + + if (task_integrity_user_read(tint) == INTEGRITY_NONE) + break; + + task_integrity_delayed_reset(task, CAUSE_PTRACE, NULL); + five_audit_err(task, NULL, "ptrace", task_integrity_read(tint), + INTEGRITY_NONE, "reset-integrity", 0); + break; + } + } + + return 0; +} + +int five_process_vm_rw(struct task_struct *task, int write) +{ + if (write) { + struct task_integrity *tint = TASK_INTEGRITY(task); + + if (task_integrity_user_read(tint) == INTEGRITY_NONE) + goto exit; + + task_integrity_delayed_reset(task, CAUSE_VMRW, NULL); + five_audit_err(task, NULL, "process_vm_rw", + task_integrity_read(tint), INTEGRITY_NONE, + "reset-integrity", 0); + } + +exit: + return 0; +} + +static inline struct processing_event_list *five_event_create( + enum five_event event, struct task_struct *task, + struct file *file, int function, gfp_t flags) +{ + struct processing_event_list *five_file; + + five_file = kzalloc(sizeof(struct processing_event_list), flags); + if (unlikely(!five_file)) + return NULL; + + five_file->event = event; + + switch (five_file->event) { + case FIVE_VERIFY_BUNCH_FILES: { + get_task_struct(task); + get_file(file); + five_file->task = task; + five_file->file = file; + five_file->function = function; + break; + } + case FIVE_RESET_INTEGRITY: { + get_task_struct(task); + five_file->task = task; + break; + } + default: + break; + } + + return five_file; +} + +static inline void five_event_destroy( + const struct processing_event_list *file) +{ + switch (file->event) { + case FIVE_VERIFY_BUNCH_FILES: { + fput(file->file); + put_task_struct(file->task); + break; + } + case FIVE_RESET_INTEGRITY: { + put_task_struct(file->task); + break; + } + default: + break; + } + kfree(file); +} + +late_initcall(init_five); + +MODULE_DESCRIPTION("File-based process Integrity Verifier"); +MODULE_LICENSE("GPL"); diff --git a/security/samsung/five/task_integrity.c b/security/samsung/five/task_integrity.c new file mode 100644 index 000000000000..50ed32594a7f --- /dev/null +++ b/security/samsung/five/task_integrity.c @@ -0,0 +1,211 @@ +/* + * Task Integrity Verifier + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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 "five_porting.h" +#include "five_testing.h" + +static struct kmem_cache *task_integrity_cache; + +__visible_for_testing +void init_once(void *foo) +{ + struct task_integrity *intg = foo; + + memset(intg, 0, sizeof(*intg)); + spin_lock_init(&intg->value_lock); + spin_lock_init(&intg->list_lock); +} + +static int __init task_integrity_cache_init(void) +{ + task_integrity_cache = kmem_cache_create("task_integrity_cache", + sizeof(struct task_integrity), 0, + SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_NOTRACK, init_once); + + if (!task_integrity_cache) + return -ENOMEM; + + return 0; +} + +security_initcall(task_integrity_cache_init); + +struct task_integrity *task_integrity_alloc(void) +{ + struct task_integrity *intg; + + intg = kmem_cache_alloc(task_integrity_cache, GFP_KERNEL); + if (intg) { + atomic_set(&intg->usage_count, 1); + INIT_LIST_HEAD(&intg->events.list); + } + + return intg; +} + +void task_integrity_free(struct task_integrity *intg) +{ + if (intg) { + /* These values should be changed under "value_lock" spinlock. + But then lockdep prints warning because this function can be called + from sw-irq (from function free_task). + Actually deadlock never happens because this function is called + only if usage_count is 0 (no reference to this struct), + so changing these values without spinlock is safe. + */ + kfree(intg->label); + intg->label = NULL; + intg->user_value = INTEGRITY_NONE; + intg->value = INTEGRITY_NONE; + atomic_set(&intg->usage_count, 0); + + intg->reset_cause = CAUSE_UNSET; + if (intg->reset_file) { + fput(intg->reset_file); + intg->reset_file = NULL; + } + + kmem_cache_free(task_integrity_cache, intg); + } +} + +void task_integrity_clear(struct task_integrity *tint) +{ + task_integrity_set(tint, INTEGRITY_NONE); + spin_lock(&tint->value_lock); + kfree(tint->label); + tint->label = NULL; + spin_unlock(&tint->value_lock); + + tint->reset_cause = CAUSE_UNSET; + if (tint->reset_file) { + fput(tint->reset_file); + tint->reset_file = NULL; + } +} + +__visible_for_testing +int copy_label(struct task_integrity *from, struct task_integrity *to) +{ + int ret = 0; + struct integrity_label *l = NULL; + + if (task_integrity_read(from) && from->label) + l = from->label; + + if (l) { + struct integrity_label *label; + + label = kmalloc(sizeof(*label) + l->len, GFP_ATOMIC); + if (!label) { + ret = -ENOMEM; + goto exit; + } + + label->len = l->len; + memcpy(label->data, l->data, label->len); + to->label = label; + } + +exit: + return ret; +} + +int task_integrity_copy(struct task_integrity *from, struct task_integrity *to) +{ + int rc = -EPERM; + enum task_integrity_value value = task_integrity_read(from); + + task_integrity_set(to, value); + + if (list_empty(&from->events.list)) + to->user_value = value; + + rc = copy_label(from, to); + + to->reset_cause = from->reset_cause; + if (from->reset_file) { + get_file(from->reset_file); + to->reset_file = from->reset_file; + } + + return rc; +} + +char const * const tint_reset_cause_to_string( + enum task_integrity_reset_cause cause) +{ + static const char * const tint_cause2str[] = { + [CAUSE_UNSET] = "unset", + [CAUSE_UNKNOWN] = "unknown", + [CAUSE_MISMATCH_LABEL] = "mismatch-label", + [CAUSE_BAD_FS] = "bad-fs", + [CAUSE_NO_CERT] = "no-cert", + [CAUSE_INVALID_HASH_LENGTH] = "invalid-hash-length", + [CAUSE_INVALID_HEADER] = "invalid-header", + [CAUSE_CALC_HASH_FAILED] = "calc-hash-failed", + [CAUSE_INVALID_LABEL_DATA] = "invalid-label-data", + [CAUSE_INVALID_SIGNATURE_DATA] = "invalid-signature-data", + [CAUSE_INVALID_HASH] = "invalid-hash", + [CAUSE_INVALID_CALC_CERT_HASH] = "invalid-calc-cert-hash", + [CAUSE_INVALID_UPDATE_LABEL] = "invalid-update-label", + [CAUSE_INVALID_SIGNATURE] = "invalid-signature", + [CAUSE_UKNOWN_FIVE_DATA] = "unknown-five-data", + [CAUSE_PTRACE] = "ptrace", + [CAUSE_VMRW] = "vmrw", + [CAUSE_EXEC] = "exec", + [CAUSE_TAMPERED] = "tampered", + [CAUSE_MAX] = "incorrect-cause", + }; + + if (cause > CAUSE_MAX) + cause = CAUSE_MAX; + + return tint_cause2str[cause]; +} + +/* + * task_integrity_set_reset_reason + * + * Only first call of this function per task will have effect, because first + * reason will be root cause. + */ +void task_integrity_set_reset_reason(struct task_integrity *intg, + enum task_integrity_reset_cause cause, struct file *file) +{ + if (intg->reset_cause != CAUSE_UNSET) + return; + + intg->reset_cause = cause; + if (file) { + get_file(file); + intg->reset_file = file; + } +} + +#if defined(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_GPL(tint_reset_cause_to_string); +EXPORT_SYMBOL_GPL(task_integrity_set_reset_reason); +EXPORT_SYMBOL_GPL(task_integrity_free); +EXPORT_SYMBOL_GPL(task_integrity_copy); +EXPORT_SYMBOL_GPL(task_integrity_clear); +EXPORT_SYMBOL_GPL(init_once); +EXPORT_SYMBOL_GPL(copy_label); +#endif diff --git a/security/samsung/five/x509_five_eng.der b/security/samsung/five/x509_five_eng.der new file mode 100644 index 000000000000..2dfd0ad73583 Binary files /dev/null and b/security/samsung/five/x509_five_eng.der differ diff --git a/security/samsung/five/x509_five_user.der b/security/samsung/five/x509_five_user.der new file mode 100644 index 000000000000..7c15ba096276 Binary files /dev/null and b/security/samsung/five/x509_five_user.der differ diff --git a/security/samsung/proca/Kconfig b/security/samsung/proca/Kconfig new file mode 100644 index 000000000000..81aa9e646aca --- /dev/null +++ b/security/samsung/proca/Kconfig @@ -0,0 +1,78 @@ +# +# PROCA feature configuration +# + +config PROCA + bool "Process Authenticator" + depends on FIVE + default n + help + Enable Process Authenticator + +config PROCA_GKI_10 + bool "GKI 1.0 compatible version of PROCA" + depends on PROCA + default y if FIVE_GKI_10 + default n + help + Build GKI 1.0 compatible version of PROCA + +config PROCA_S_OS + bool "S OS compatible version of PROCA" + depends on PROCA + default n + help + Build S OS compatible version of PROCA + +choice + prompt "Choose PROCA certificate storage type" + default PROCA_CERTIFICATES_XATTR + depends on PROCA_S_OS + help + This option allows to choose type of storage for certificates. + There are two options: storage in xattr or in the database. + +config PROCA_CERTIFICATES_XATTR + bool "Xattr for certificates" + help + Use xattr for PROCA certificates + +config PROCA_CERTIFICATES_DB + bool "PROCA database for certificates" + help + Use database for PROCA certificates +endchoice + +config PROCA_CERT_ENG + string "PROCA certificate to verify signatures for eng binary" + depends on PROCA + default "x509_proca_eng.der" + help + Path to CERT which will be built-in to eng binary + +config PROCA_CERT_USER + string "PROCA certificate to verify signatures for user binary" + depends on PROCA + default "x509_proca_user.der" + help + Path to CERT which will be built-in to user binary + +config PROCA_CERT_DEVICE + bool "PROCA certificate device" + depends on PROCA_CERTIFICATES_DB && !SAMSUNG_PRODUCT_SHIP + default y if PROCA_CERTIFICATES_DB && !SAMSUNG_PRODUCT_SHIP + default n + help + Enable the certificate device /dev/proca_cert for reading and + updating test database in /data/proca.db + +config PROCA_DEBUG + bool "PROCA Debug mode" + depends on PROCA && !SAMSUNG_PRODUCT_SHIP + default y + help + Enable the debug mode in the PROCA. When this option is enabled + PROCA driver creates file /proc//integrity/proca_certificate + which contains hex representation of binary proca certificate. It + might be used to check existence of task descriptor for some task + in proca table. diff --git a/security/samsung/proca/Makefile b/security/samsung/proca/Makefile new file mode 100644 index 000000000000..b73a6224bae9 --- /dev/null +++ b/security/samsung/proca/Makefile @@ -0,0 +1,98 @@ +SHELL := /bin/bash + +KERNEL := $(VERSION)_$(PATCHLEVEL) + +# For kernel version 4.14 or lower +ifeq ($(KERNEL),4_14) +$(obj)/proca_certificate-asn1.o: $(obj)/proca_certificate-asn1.c \ + $(obj)/proca_certificate-asn1.h + +$(obj)/proca_certificate.o: $(obj)/proca_certificate-asn1.h + +obj-$(CONFIG_PROCA) += proca_identity.o proca_fcntl.o \ + proca_task_descr.o proca_certificate-asn1.o \ + proca_certificate.o proca_table.o \ + proca_config.o + +$(obj)/proca_certificate_db-asn1.o: $(obj)/proca_certificate_db-asn1.c \ + $(obj)/proca_certificate_db-asn1.h + +$(obj)/s_os/proca_certificate_db.o: $(obj)/proca_certificate_db-asn1.h + +obj-$(CONFIG_PROCA_CERTIFICATES_DB) += proca_certificate_db-asn1.o +else +$(obj)/proca_certificate.asn1.o: $(obj)/proca_certificate.asn1.c \ + $(obj)/proca_certificate.asn1.h + +$(obj)/proca_certificate.o: $(obj)/proca_certificate.asn1.h + +obj-$(CONFIG_PROCA) += proca_identity.o proca_fcntl.o \ + proca_task_descr.o proca_certificate.asn1.o \ + proca_certificate.o proca_table.o \ + proca_config.o + +$(obj)/proca_certificate_db.asn1.o: $(obj)/proca_certificate_db.asn1.c \ + $(obj)/proca_certificate_db.asn1.h + +$(obj)/s_os/proca_certificate_db.o: $(obj)/proca_certificate_db.asn1.h + +obj-$(CONFIG_PROCA_CERTIFICATES_DB) += proca_certificate_db.asn1.o +endif + +ccflags-y += -I$(srctree)/security/samsung/proca/ +arflags-y += -I$(srctree)/security/samsung/proca/ +asflags-y += -I$(srctree)/security/samsung/proca/ +ccflags-y += -I$(srctree)/security/samsung/five/ + +ifdef CONFIG_PROCA_S_OS + +obj-$(CONFIG_PROCA_CERTIFICATES_DB) += s_os/proca_certificate_db.o \ + s_os/proca_keyring.o \ + proca_cert_builtin.o \ + s_os/proca_storage_db.o + +obj-$(CONFIG_PROCA_CERT_DEVICE) += s_os/proca_certificate_dev.o + +obj-$(CONFIG_PROCA_CERTIFICATES_XATTR) += s_os/proca_storage_xattr.o + +obj-$(CONFIG_PROCA) += s_os/proca_lsm.o \ + s_os/proca_vfs.o \ + s_os/proca_audit.o + +ccflags-y += -Isecurity/samsung/proca/s_os +else +obj-$(CONFIG_PROCA) += proca_lsm.o +endif # CONFIG_PROCA_S_OS + +# clean-files is given relative to the current directory, +# so don't use (obj)/ prefix here. +# Look at file ../../scripts/Makefile.clean for details. +ifeq ($(KERNEL),4_14) +clean-files += proca_certificate-asn1.c proca_certificate-asn1.h +ifdef CONFIG_PROCA_CERTIFICATES_DB +clean-files += proca_certificate_db-asn1.c proca_certificate_db-asn1.h +endif +else +clean-files += proca_certificate.asn1.c proca_certificate.asn1.h +ifdef CONFIG_PROCA_CERTIFICATES_DB +clean-files += proca_certificate_db.asn1.c proca_certificate_db.asn1.h +endif +endif + +subdir-ccflags-y += -Wformat + +# kunit tests options: +# //KERNEL/LEGO specific list. do no sync to other branches +ENABLED_TARGETS_LIST = b0s g0s b0q g0q b5q q5q dm3q +ifeq ($(CONFIG_SEC_KUNIT), y) + GCOV_PROFILE := y + ifeq ($(CONFIG_UML), y) + ccflags-$(CONFIG_PROCA) += -DPROCA_KUNIT_ENABLED + else + ifneq ($(filter $(TARGET_DEVICE),$(ENABLED_TARGETS_LIST)),) + $(warning security/proca: TARGET_DEVICE: $(TARGET_DEVICE). build on-device kunit test) + else + $(warning security/proca: TARGET_DEVICE: $(TARGET_DEVICE). skip on-device kunit test) + endif + endif +endif diff --git a/security/samsung/proca/gaf/Kconfig b/security/samsung/proca/gaf/Kconfig new file mode 100644 index 000000000000..6f49c684a7ea --- /dev/null +++ b/security/samsung/proca/gaf/Kconfig @@ -0,0 +1,33 @@ +config GAF + bool "Use GAF structure" + default y if PROCA || FIVE + default n + help + Enable GAF structure + +if GAF +# When adding new entries keep the list in alphabetical order +choice + prompt "Version of GAF structure" + default GAF_V5 if PROCA + default GAF_V4 if FIVE + default GAF_V3 + config GAF_V3 + bool "Use GAF structure version 3" + help + Default option for FIVE-disabled kernel + config GAF_V4 + bool "Use GAF structure version 4" + help + Default option for kernel without PROCA LSM module. + config GAF_V5 + bool "Use GAF structure version 5" + help + Default option for kernel with PROCA 2.0-2.1 LSM module. + config GAF_V6 + bool "Use GAF structure version 6" + help + Default option for kernel with PROCA 2.2+ LSM module. +endchoice + +endif #GAF diff --git a/security/samsung/proca/gaf/Makefile b/security/samsung/proca/gaf/Makefile new file mode 100644 index 000000000000..44edbc2a9a79 --- /dev/null +++ b/security/samsung/proca/gaf/Makefile @@ -0,0 +1,14 @@ +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_GAF_V3) += gaf_v3.o +obj-$(CONFIG_GAF_V4) += gaf_v4.o +obj-$(CONFIG_GAF_V5) += gaf_v5.o +obj-$(CONFIG_GAF_V6) += gaf_v6.o + +ccflags-y += -I$(srctree)/security/samsung/proca + +# kunit tests options: +ifeq ($(CONFIG_SEC_KUNIT)$(CONFIG_UML), yy) + GCOV_PROFILE := y + ccflags-$(CONFIG_PROCA) += -DPROCA_KUNIT_ENABLED +endif + diff --git a/security/samsung/proca/gaf/gaf_v3.c b/security/samsung/proca/gaf/gaf_v3.c new file mode 100644 index 000000000000..f45deb4a1969 --- /dev/null +++ b/security/samsung/proca/gaf/gaf_v3.c @@ -0,0 +1,251 @@ +/* + * gaf_v3.c + * + */ + +#include +#include +#include +#include +#include +#include + +static struct GAForensicINFO { + unsigned short ver; + unsigned int size; + unsigned short task_struct_struct_state; + unsigned short task_struct_struct_comm; + unsigned short task_struct_struct_tasks; + unsigned short task_struct_struct_pid; + unsigned short task_struct_struct_stack; + unsigned short task_struct_struct_mm; + unsigned short mm_struct_struct_start_data; + unsigned short mm_struct_struct_end_data; + unsigned short mm_struct_struct_start_brk; + unsigned short mm_struct_struct_brk; + unsigned short mm_struct_struct_start_stack; + unsigned short mm_struct_struct_arg_start; + unsigned short mm_struct_struct_arg_end; + unsigned short mm_struct_struct_pgd; + unsigned short mm_struct_struct_mmap; + unsigned short vm_area_struct_struct_vm_start; + unsigned short vm_area_struct_struct_vm_end; + unsigned short vm_area_struct_struct_vm_next; + unsigned short vm_area_struct_struct_vm_file; +#ifndef CONFIG_ARM64 + unsigned short thread_info_struct_cpu_context; + unsigned short cpu_context_save_struct_sp; +#endif + unsigned short file_struct_f_path; + unsigned short path_struct_mnt; + unsigned short path_struct_dentry; + unsigned short dentry_struct_d_parent; + unsigned short dentry_struct_d_name; + unsigned short qstr_struct_name; + unsigned short vfsmount_struct_mnt_mountpoint; + unsigned short vfsmount_struct_mnt_root; + unsigned short vfsmount_struct_mnt_parent; +#ifdef CONFIG_ARM64 + unsigned long pgdir_shift; + unsigned long ptrs_per_pte; + unsigned long phys_offset; + unsigned long page_offset; + unsigned long page_shift; + unsigned long page_size; +#else + unsigned int pgdir_shift; + unsigned int ptrs_per_pte; + unsigned int phys_offset; + unsigned int page_offset; + unsigned int page_shift; + unsigned int page_size; +#endif + unsigned short task_struct_struct_thread_group; + unsigned short task_struct_struct_thread; + unsigned short task_struct_struct_utime; + unsigned short task_struct_struct_stime; + unsigned short list_head_struct_next; + unsigned short list_head_struct_prev; + unsigned short rq_struct_curr; + + unsigned short thread_info_struct_cpu; + + unsigned short task_struct_struct_prio; + unsigned short task_struct_struct_static_prio; + unsigned short task_struct_struct_normal_prio; + unsigned short task_struct_struct_rt_priority; + + unsigned short task_struct_struct_se; + + unsigned short sched_entity_struct_exec_start; + unsigned short sched_entity_struct_sum_exec_runtime; + unsigned short sched_entity_struct_prev_sum_exec_runtime; + + unsigned short task_struct_struct_sched_info; + + unsigned short sched_info_struct_pcount; + unsigned short sched_info_struct_run_delay; + unsigned short sched_info_struct_last_arrival; + unsigned short sched_info_struct_last_queued; + + unsigned short task_struct_struct_blocked_on; + + unsigned short mutex_waiter_struct_list; + unsigned short mutex_waiter_struct_task; + + unsigned short sched_entity_struct_cfs_rq_struct; + unsigned short cfs_rq_struct_rq_struct; + unsigned short gaf_fp; + unsigned short GAFINFOCheckSum; +} GAFINFO = { + .ver = 0x0300, /* by dh3s.choi 2010 12 14 */ + .size = sizeof(GAFINFO), + .task_struct_struct_state = offsetof(struct task_struct, state), + .task_struct_struct_comm = offsetof(struct task_struct, comm), + .task_struct_struct_tasks = offsetof(struct task_struct, tasks), + .task_struct_struct_pid = offsetof(struct task_struct, pid), + .task_struct_struct_stack = offsetof(struct task_struct, stack), + .task_struct_struct_mm = offsetof(struct task_struct, mm), + .mm_struct_struct_start_data = offsetof(struct mm_struct, start_data), + .mm_struct_struct_end_data = offsetof(struct mm_struct, end_data), + .mm_struct_struct_start_brk = offsetof(struct mm_struct, start_brk), + .mm_struct_struct_brk = offsetof(struct mm_struct, brk), + .mm_struct_struct_start_stack = offsetof(struct mm_struct, start_stack), + .mm_struct_struct_arg_start = offsetof(struct mm_struct, arg_start), + .mm_struct_struct_arg_end = offsetof(struct mm_struct, arg_end), + .mm_struct_struct_pgd = offsetof(struct mm_struct, pgd), + .mm_struct_struct_mmap = offsetof(struct mm_struct, mmap), + .vm_area_struct_struct_vm_start + = offsetof(struct vm_area_struct, vm_start), + .vm_area_struct_struct_vm_end + = offsetof(struct vm_area_struct, vm_end), + .vm_area_struct_struct_vm_next + = offsetof(struct vm_area_struct, vm_next), + .vm_area_struct_struct_vm_file + = offsetof(struct vm_area_struct, vm_file), +#ifndef CONFIG_ARM64 + .thread_info_struct_cpu_context + = offsetof(struct thread_info, cpu_context), + .cpu_context_save_struct_sp = offsetof(struct cpu_context_save, sp), +#endif + .file_struct_f_path = offsetof(struct file, f_path), + .path_struct_mnt = offsetof(struct path, mnt), + .path_struct_dentry = offsetof(struct path, dentry), + .dentry_struct_d_parent = offsetof(struct dentry, d_parent), + .dentry_struct_d_name = offsetof(struct dentry, d_name), + .qstr_struct_name = offsetof(struct qstr, name), +#if 0 + .vfsmount_struct_mnt_mountpoint + = offsetof(struct vfsmount, mnt_mountpoint), +#endif + .vfsmount_struct_mnt_root = offsetof(struct vfsmount, mnt_root), +#if 0 + .vfsmount_struct_mnt_parent = offsetof(struct vfsmount, mnt_parent), +#endif + .pgdir_shift = PGDIR_SHIFT, + .ptrs_per_pte = PTRS_PER_PTE, + /* .phys_offset = PHYS_OFFSET, */ + .page_offset = PAGE_OFFSET, + .page_shift = PAGE_SHIFT, + .page_size = PAGE_SIZE, + .task_struct_struct_thread_group + = offsetof(struct task_struct, thread_group), + .task_struct_struct_thread = offsetof(struct task_struct, thread), + .task_struct_struct_utime = offsetof(struct task_struct, utime), + .task_struct_struct_stime = offsetof(struct task_struct, stime), + .list_head_struct_next = offsetof(struct list_head, next), + .list_head_struct_prev = offsetof(struct list_head, prev), + + .rq_struct_curr = 0, + + .thread_info_struct_cpu = 0, + + .task_struct_struct_prio = offsetof(struct task_struct, prio), + .task_struct_struct_static_prio + = offsetof(struct task_struct, static_prio), + .task_struct_struct_normal_prio + = offsetof(struct task_struct, normal_prio), + .task_struct_struct_rt_priority + = offsetof(struct task_struct, rt_priority), + + .task_struct_struct_se = offsetof(struct task_struct, se), + + .sched_entity_struct_exec_start + = offsetof(struct sched_entity, exec_start), + .sched_entity_struct_sum_exec_runtime + = offsetof(struct sched_entity, sum_exec_runtime), + .sched_entity_struct_prev_sum_exec_runtime + = offsetof(struct sched_entity, prev_sum_exec_runtime), + +#if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT) + .task_struct_struct_sched_info + = offsetof(struct task_struct, sched_info), + .sched_info_struct_pcount + = offsetof(struct sched_info, pcount), + .sched_info_struct_run_delay + = offsetof(struct sched_info, run_delay), + .sched_info_struct_last_arrival + = offsetof(struct sched_info, last_arrival), + .sched_info_struct_last_queued + = offsetof(struct sched_info, last_queued), +#else + .task_struct_struct_sched_info = 0x1223, + .sched_info_struct_pcount = 0x1224, + .sched_info_struct_run_delay = 0x1225, + .sched_info_struct_last_arrival = 0x1226, + .sched_info_struct_last_queued = 0x1227, +#endif + +#ifdef CONFIG_DEBUG_MUTEXES + .task_struct_struct_blocked_on + = offsetof(struct task_struct, blocked_on), + .mutex_waiter_struct_list = offsetof(struct mutex_waiter, list), + .mutex_waiter_struct_task = offsetof(struct mutex_waiter, task), +#else + .task_struct_struct_blocked_on = 0x1228, + .mutex_waiter_struct_list = 0x1229, + .mutex_waiter_struct_task = 0x122a, +#endif + +#ifdef CONFIG_FAIR_GROUP_SCHED + .sched_entity_struct_cfs_rq_struct + = offsetof(struct sched_entity, cfs_rq), +#else + .sched_entity_struct_cfs_rq_struct = 0x1223, +#endif + + .cfs_rq_struct_rq_struct = 0, + +#ifdef CONFIG_FRAME_POINTER + .gaf_fp = 1, +#else + .gaf_fp = 0, +#endif + + .GAFINFOCheckSum = 0 +}; + +static int __init proca_init_gaf(void) +{ + unsigned short *checksum = &(GAFINFO.GAFINFOCheckSum); + unsigned char *memory = (unsigned char *)&GAFINFO; + unsigned char address; + /* + * Add GAForensic init for preventing symbol removal for optimization. + */ + GAFINFO.phys_offset = PHYS_OFFSET; + GAFINFO.rq_struct_curr = 0; + + for (*checksum = 0, address = 0; + address < (sizeof(GAFINFO) - sizeof(GAFINFO.GAFINFOCheckSum)); + address++) { + if ((*checksum) & 0x8000) + (*checksum) = + (((*checksum) << 1) | 1) ^ memory[address]; + else + (*checksum) = ((*checksum) << 1) ^ memory[address]; + } + + return 0; +} +core_initcall(proca_init_gaf); diff --git a/security/samsung/proca/gaf/gaf_v4.c b/security/samsung/proca/gaf/gaf_v4.c new file mode 100644 index 000000000000..020fb56bbb90 --- /dev/null +++ b/security/samsung/proca/gaf/gaf_v4.c @@ -0,0 +1,281 @@ +/* + * gaf_v4.c + * + */ +#include "proca_gaf.h" + +#include +#include +#include +#include +#include +#include +#include +#include "../fs/mount.h" + +#ifdef CONFIG_FIVE_GKI_10 +#define OFFSETOF_INTEGRITY offsetof(struct task_struct, android_vendor_data1[2]) +#define OFFSETOF_F_SIGNATURE offsetof(struct file, android_vendor_data1) +#else +#define OFFSETOF_INTEGRITY offsetof(struct task_struct, integrity) +#define OFFSETOF_F_SIGNATURE offsetof(struct file, f_signature) +#endif + +static struct GAForensicINFO { + unsigned short ver; + unsigned int size; + unsigned short task_struct_struct_state; + unsigned short task_struct_struct_comm; + unsigned short task_struct_struct_tasks; + unsigned short task_struct_struct_pid; + unsigned short task_struct_struct_stack; + unsigned short task_struct_struct_mm; + unsigned short mm_struct_struct_start_data; + unsigned short mm_struct_struct_end_data; + unsigned short mm_struct_struct_start_brk; + unsigned short mm_struct_struct_brk; + unsigned short mm_struct_struct_start_stack; + unsigned short mm_struct_struct_arg_start; + unsigned short mm_struct_struct_arg_end; + unsigned short mm_struct_struct_pgd; + unsigned short mm_struct_struct_mmap; + unsigned short vm_area_struct_struct_vm_start; + unsigned short vm_area_struct_struct_vm_end; + unsigned short vm_area_struct_struct_vm_next; + unsigned short vm_area_struct_struct_vm_flags; + unsigned short vm_area_struct_struct_vm_file; +#ifndef CONFIG_ARM64 + unsigned short thread_info_struct_cpu_context; + unsigned short cpu_context_save_struct_sp; +#endif + unsigned short file_struct_f_path; + unsigned short path_struct_mnt; + unsigned short path_struct_dentry; + unsigned short dentry_struct_d_parent; + unsigned short dentry_struct_d_name; + unsigned short qstr_struct_name; + unsigned short vfsmount_struct_mnt_mountpoint; + unsigned short vfsmount_struct_mnt_root; + unsigned short vfsmount_struct_mnt_parent; +#ifdef CONFIG_ARM64 + unsigned long pgdir_shift; + unsigned long ptrs_per_pte; + unsigned long phys_offset; + unsigned long page_offset; + unsigned long page_shift; + unsigned long page_size; +#else + unsigned int pgdir_shift; + unsigned int ptrs_per_pte; + unsigned int phys_offset; + unsigned int page_offset; + unsigned int page_shift; + unsigned int page_size; +#endif + unsigned short task_struct_struct_thread_group; +#ifdef CONFIG_ARM64 + unsigned short task_struct_struct_thread; +#endif + unsigned short task_struct_struct_utime; + unsigned short task_struct_struct_stime; + unsigned short list_head_struct_next; + unsigned short list_head_struct_prev; + unsigned short rq_struct_curr; + unsigned short thread_info_struct_cpu; + unsigned short task_struct_struct_prio; + unsigned short task_struct_struct_static_prio; + unsigned short task_struct_struct_normal_prio; + unsigned short task_struct_struct_rt_priority; + unsigned short task_struct_struct_se; + unsigned short sched_entity_struct_exec_start; + unsigned short sched_entity_struct_sum_exec_runtime; + unsigned short sched_entity_struct_prev_sum_exec_runtime; + unsigned short task_struct_struct_sched_info; + unsigned short sched_info_struct_pcount; + unsigned short sched_info_struct_run_delay; + unsigned short sched_info_struct_last_arrival; + unsigned short sched_info_struct_last_queued; + unsigned short task_struct_struct_blocked_on; + unsigned short mutex_waiter_struct_list; + unsigned short mutex_waiter_struct_task; + unsigned short sched_entity_struct_cfs_rq_struct; + unsigned short cfs_rq_struct_rq_struct; + unsigned short gaf_fp; + unsigned short task_struct_integrity; + unsigned short file_struct_f_signature; + unsigned short mm_struct_struct_mm_rb; + unsigned short vm_area_struct_struct_vm_rb; + unsigned short qstr_struct_len; + unsigned short mount_struct_mnt_mountpoint; + unsigned short pid_struct_numbers; + unsigned short upid_struct_pid_chain; + unsigned short upid_struct_nr; + unsigned short hlist_node_struct_next; + unsigned short task_struct_pids; + unsigned short pid_struct_first; + signed short vfsmount_struct_bp_mount; + unsigned short GAFINFOCheckSum; +} GAFINFO = { + .ver = 0x0400, /* by i.vorobiov 2017 08 25 */ + .size = sizeof(GAFINFO), + .task_struct_struct_state = offsetof(struct task_struct, state), + .task_struct_struct_comm = offsetof(struct task_struct, comm), + .task_struct_struct_tasks = offsetof(struct task_struct, tasks), + .task_struct_struct_pid = offsetof(struct task_struct, pid), + .task_struct_struct_stack = offsetof(struct task_struct, stack), + .task_struct_struct_mm = offsetof(struct task_struct, mm), + .mm_struct_struct_start_data = offsetof(struct mm_struct, start_data), + .mm_struct_struct_end_data = offsetof(struct mm_struct, end_data), + .mm_struct_struct_start_brk = offsetof(struct mm_struct, start_brk), + .mm_struct_struct_brk = offsetof(struct mm_struct, brk), + .mm_struct_struct_start_stack = offsetof(struct mm_struct, start_stack), + .mm_struct_struct_arg_start = offsetof(struct mm_struct, arg_start), + .mm_struct_struct_arg_end = offsetof(struct mm_struct, arg_end), + .mm_struct_struct_pgd = offsetof(struct mm_struct, pgd), + .mm_struct_struct_mmap = offsetof(struct mm_struct, mmap), + .mm_struct_struct_mm_rb = offsetof(struct mm_struct, mm_rb), + .vm_area_struct_struct_vm_start = + offsetof(struct vm_area_struct, vm_start), + .vm_area_struct_struct_vm_end = offsetof(struct vm_area_struct, vm_end), + .vm_area_struct_struct_vm_next = + offsetof(struct vm_area_struct, vm_next), + .vm_area_struct_struct_vm_flags = + offsetof(struct vm_area_struct, vm_flags), + .vm_area_struct_struct_vm_file = + offsetof(struct vm_area_struct, vm_file), + .vm_area_struct_struct_vm_rb + = offsetof(struct vm_area_struct, vm_rb), + .pid_struct_numbers = offsetof(struct pid, numbers[0]), + .upid_struct_pid_chain = 0, + .upid_struct_nr = 0, + .hlist_node_struct_next = offsetof(struct hlist_node, next), + .task_struct_pids = 0, + .pid_struct_first + = offsetof(struct pid, tasks[0]) + + offsetof(struct hlist_head, first), +#ifndef CONFIG_ARM64 + .thread_info_struct_cpu_context = + offsetof(struct thread_info, cpu_context), + .cpu_context_save_struct_sp = offsetof(struct cpu_context_save, sp), +#endif + .file_struct_f_path = offsetof(struct file, f_path), + .path_struct_mnt = offsetof(struct path, mnt), + .path_struct_dentry = offsetof(struct path, dentry), + .dentry_struct_d_parent = offsetof(struct dentry, d_parent), + .dentry_struct_d_name = offsetof(struct dentry, d_name), + .qstr_struct_name = offsetof(struct qstr, name), + .qstr_struct_len = offsetof(struct qstr, len), + .vfsmount_struct_mnt_root = offsetof(struct vfsmount, mnt_root), + .pgdir_shift = PGDIR_SHIFT, + .ptrs_per_pte = PTRS_PER_PTE, +//TEMP .phys_offset = PHYS_OFFSET, + .page_offset = PAGE_OFFSET, + .page_shift = PAGE_SHIFT, + .page_size = PAGE_SIZE, + .task_struct_struct_thread_group = + offsetof(struct task_struct, thread_group), +#ifdef CONFIG_ARM64 + .task_struct_struct_thread = offsetof(struct task_struct, thread), +#endif + .task_struct_struct_utime = offsetof(struct task_struct, utime), + .task_struct_struct_stime = offsetof(struct task_struct, stime), + .list_head_struct_next = offsetof(struct list_head, next), + .list_head_struct_prev = offsetof(struct list_head, prev), + .rq_struct_curr = 0, + .thread_info_struct_cpu = 0, + .task_struct_struct_prio = offsetof(struct task_struct, prio), + .task_struct_struct_static_prio = + offsetof(struct task_struct, static_prio), + .task_struct_struct_normal_prio = + offsetof(struct task_struct, normal_prio), + .task_struct_struct_rt_priority = + offsetof(struct task_struct, rt_priority), + .task_struct_struct_se = offsetof(struct task_struct, se), + .sched_entity_struct_exec_start = + offsetof(struct sched_entity, exec_start), + .sched_entity_struct_sum_exec_runtime = + offsetof(struct sched_entity, sum_exec_runtime), + .sched_entity_struct_prev_sum_exec_runtime = + offsetof(struct sched_entity, prev_sum_exec_runtime), +#if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT) + .task_struct_struct_sched_info = + offsetof(struct task_struct, sched_info), + .sched_info_struct_pcount = offsetof(struct sched_info, pcount), + .sched_info_struct_run_delay = offsetof(struct sched_info, run_delay), + .sched_info_struct_last_arrival = + offsetof(struct sched_info, last_arrival), + .sched_info_struct_last_queued = + offsetof(struct sched_info, last_queued), +#else + .task_struct_struct_sched_info = 0x1223, + .sched_info_struct_pcount = 0x1224, + .sched_info_struct_run_delay = 0x1225, + .sched_info_struct_last_arrival = 0x1226, + .sched_info_struct_last_queued = 0x1227, +#endif +#ifdef CONFIG_DEBUG_MUTEXES + .task_struct_struct_blocked_on = + offsetof(struct task_struct, blocked_on), + .mutex_waiter_struct_list = offsetof(struct mutex_waiter, list), + .mutex_waiter_struct_task = offsetof(struct mutex_waiter, task), +#else + .task_struct_struct_blocked_on = 0x1228, + .mutex_waiter_struct_list = 0x1229, + .mutex_waiter_struct_task = 0x122a, +#endif +#ifdef CONFIG_FAIR_GROUP_SCHED + .sched_entity_struct_cfs_rq_struct = + offsetof(struct sched_entity, cfs_rq), +#else + .sched_entity_struct_cfs_rq_struct = 0x1223, +#endif + .cfs_rq_struct_rq_struct = 0, +#ifdef CONFIG_FRAME_POINTER + .gaf_fp = 1, +#else + .gaf_fp = 0, +#endif +#ifdef CONFIG_FIVE + .task_struct_integrity = OFFSETOF_INTEGRITY, +#if defined(CONFIG_FIVE_PA_FEATURE) || defined(CONFIG_PROCA) + .file_struct_f_signature = OFFSETOF_F_SIGNATURE, +#endif +#endif + .mount_struct_mnt_mountpoint = offsetof(struct mount, mnt_mountpoint), +#if defined(CONFIG_KDP_NS) || defined(CONFIG_RUSTUH_KDP_NS) +#if defined(CONFIG_SOC_EXYNOS2100) || defined(CONFIG_ARCH_LAHAINA) + .vfsmount_struct_bp_mount = offsetof(struct kdp_vfsmount, bp_mount), +#else + .vfsmount_struct_bp_mount = offsetof(struct vfsmount, bp_mount), +#endif +#else + .vfsmount_struct_bp_mount = + (short)(offsetof(struct mount, mnt_mountpoint) + - offsetof(struct mount, mnt)), +#endif + .GAFINFOCheckSum = 0 +}; + +static int __init proca_init_gaf(void) +{ + const unsigned short size = + offsetof(struct GAForensicINFO, GAFINFOCheckSum); + unsigned char *memory = (unsigned char *)&GAFINFO; + unsigned short i = 0; + unsigned short checksum = 0; + /* + * Add GAForensic init for preventing symbol removal for optimization. + */ + GAFINFO.rq_struct_curr = 0; + + for (i = 0; i < size; i++) { + if (checksum & 0x8000) + checksum = ((checksum << 1) | 1) ^ memory[i]; + else + checksum = (checksum << 1) ^ memory[i]; + } + GAFINFO.GAFINFOCheckSum = checksum; + + return 0; +} +core_initcall(proca_init_gaf) diff --git a/security/samsung/proca/gaf/gaf_v5.c b/security/samsung/proca/gaf/gaf_v5.c new file mode 100644 index 000000000000..92eb209367cd --- /dev/null +++ b/security/samsung/proca/gaf/gaf_v5.c @@ -0,0 +1,326 @@ +/* + * gaf_v5.c + * + */ +#include "proca_gaf.h" + +#include +#include +#include +#include +#include +#include +#include +#include "../fs/mount.h" + +#include "proca_certificate.h" +#include "proca_identity.h" +#include "proca_task_descr.h" +#include "proca_table.h" + +static struct GAForensicINFO { + unsigned short ver; + unsigned int size; + unsigned short task_struct_struct_state; + unsigned short task_struct_struct_comm; + unsigned short task_struct_struct_tasks; + unsigned short task_struct_struct_pid; + unsigned short task_struct_struct_stack; + unsigned short task_struct_struct_mm; + unsigned short mm_struct_struct_start_data; + unsigned short mm_struct_struct_end_data; + unsigned short mm_struct_struct_start_brk; + unsigned short mm_struct_struct_brk; + unsigned short mm_struct_struct_start_stack; + unsigned short mm_struct_struct_arg_start; + unsigned short mm_struct_struct_arg_end; + unsigned short mm_struct_struct_pgd; + unsigned short mm_struct_struct_mmap; + unsigned short vm_area_struct_struct_vm_start; + unsigned short vm_area_struct_struct_vm_end; + unsigned short vm_area_struct_struct_vm_next; + unsigned short vm_area_struct_struct_vm_flags; + unsigned short vm_area_struct_struct_vm_file; +#ifndef CONFIG_ARM64 + unsigned short thread_info_struct_cpu_context; + unsigned short cpu_context_save_struct_sp; +#endif + unsigned short file_struct_f_path; + unsigned short path_struct_mnt; + unsigned short path_struct_dentry; + unsigned short dentry_struct_d_parent; + unsigned short dentry_struct_d_name; + unsigned short qstr_struct_name; + unsigned short vfsmount_struct_mnt_mountpoint; + unsigned short vfsmount_struct_mnt_root; + unsigned short vfsmount_struct_mnt_parent; +#ifdef CONFIG_ARM64 + unsigned long pgdir_shift; + unsigned long ptrs_per_pte; + unsigned long phys_offset; + unsigned long page_offset; + unsigned long page_shift; + unsigned long page_size; +#else + unsigned int pgdir_shift; + unsigned int ptrs_per_pte; + unsigned int phys_offset; + unsigned int page_offset; + unsigned int page_shift; + unsigned int page_size; +#endif + unsigned short task_struct_struct_thread_group; +#ifdef CONFIG_ARM64 + unsigned short task_struct_struct_thread; +#endif + unsigned short task_struct_struct_utime; + unsigned short task_struct_struct_stime; + unsigned short list_head_struct_next; + unsigned short list_head_struct_prev; + unsigned short rq_struct_curr; + unsigned short thread_info_struct_cpu; + unsigned short task_struct_struct_prio; + unsigned short task_struct_struct_static_prio; + unsigned short task_struct_struct_normal_prio; + unsigned short task_struct_struct_rt_priority; + unsigned short task_struct_struct_se; + unsigned short sched_entity_struct_exec_start; + unsigned short sched_entity_struct_sum_exec_runtime; + unsigned short sched_entity_struct_prev_sum_exec_runtime; + unsigned short task_struct_struct_sched_info; + unsigned short sched_info_struct_pcount; + unsigned short sched_info_struct_run_delay; + unsigned short sched_info_struct_last_arrival; + unsigned short sched_info_struct_last_queued; + unsigned short task_struct_struct_blocked_on; + unsigned short mutex_waiter_struct_list; + unsigned short mutex_waiter_struct_task; + unsigned short sched_entity_struct_cfs_rq_struct; + unsigned short cfs_rq_struct_rq_struct; + unsigned short gaf_fp; + unsigned short task_struct_integrity; + unsigned short proca_task_descr_task; + unsigned short proca_task_descr_proca_identity; + unsigned short proca_task_descr_pid_map_node; + unsigned short proca_task_descr_app_name_map_node; + unsigned short proca_identity_struct_certificate; + unsigned short proca_identity_struct_certificate_size; + unsigned short proca_identity_struct_parsed_cert; + unsigned short proca_identity_struct_file; + unsigned short file_struct_f_signature; + unsigned short proca_table_hash_tables_shift; + unsigned short proca_table_pid_map; + unsigned short proca_table_app_name_map; + unsigned short proca_certificate_struct_app_name; + unsigned short proca_certificate_struct_app_name_size; + unsigned short mm_struct_struct_mm_rb; + unsigned short vm_area_struct_struct_vm_rb; + unsigned short qstr_struct_len; + unsigned short mount_struct_mnt_mountpoint; + unsigned short pid_struct_numbers; + unsigned short upid_struct_pid_chain; + unsigned short upid_struct_nr; + unsigned short hlist_node_struct_next; + unsigned short task_struct_pids; + unsigned short pid_struct_first; + signed short vfsmount_struct_bp_mount; + unsigned short GAFINFOCheckSum; +} GAFINFO = { + .ver = 0x0500, /* by hryhorii tur 2018 09 14 */ + .size = sizeof(GAFINFO), + .task_struct_struct_state = offsetof(struct task_struct, state), + .task_struct_struct_comm = offsetof(struct task_struct, comm), + .task_struct_struct_tasks = offsetof(struct task_struct, tasks), + .task_struct_struct_pid = offsetof(struct task_struct, pid), + .task_struct_struct_stack = offsetof(struct task_struct, stack), + .task_struct_struct_mm = offsetof(struct task_struct, mm), + .mm_struct_struct_start_data = offsetof(struct mm_struct, start_data), + .mm_struct_struct_end_data = offsetof(struct mm_struct, end_data), + .mm_struct_struct_start_brk = offsetof(struct mm_struct, start_brk), + .mm_struct_struct_brk = offsetof(struct mm_struct, brk), + .mm_struct_struct_start_stack = offsetof(struct mm_struct, start_stack), + .mm_struct_struct_arg_start = offsetof(struct mm_struct, arg_start), + .mm_struct_struct_arg_end = offsetof(struct mm_struct, arg_end), + .mm_struct_struct_pgd = offsetof(struct mm_struct, pgd), + .mm_struct_struct_mmap = offsetof(struct mm_struct, mmap), + .mm_struct_struct_mm_rb = offsetof(struct mm_struct, mm_rb), + .vm_area_struct_struct_vm_start = + offsetof(struct vm_area_struct, vm_start), + .vm_area_struct_struct_vm_end = offsetof(struct vm_area_struct, vm_end), + .vm_area_struct_struct_vm_next = + offsetof(struct vm_area_struct, vm_next), + .vm_area_struct_struct_vm_flags = + offsetof(struct vm_area_struct, vm_flags), + .vm_area_struct_struct_vm_file = + offsetof(struct vm_area_struct, vm_file), + .vm_area_struct_struct_vm_rb + = offsetof(struct vm_area_struct, vm_rb), + .pid_struct_numbers = offsetof(struct pid, numbers[0]), + .upid_struct_pid_chain = 0, + .upid_struct_nr = 0, + .hlist_node_struct_next = offsetof(struct hlist_node, next), + .task_struct_pids = 0, + .pid_struct_first + = offsetof(struct pid, tasks[0]) + + offsetof(struct hlist_head, first), +#ifndef CONFIG_ARM64 + .thread_info_struct_cpu_context = + offsetof(struct thread_info, cpu_context), + .cpu_context_save_struct_sp = offsetof(struct cpu_context_save, sp), +#endif + .file_struct_f_path = offsetof(struct file, f_path), + .path_struct_mnt = offsetof(struct path, mnt), + .path_struct_dentry = offsetof(struct path, dentry), + .dentry_struct_d_parent = offsetof(struct dentry, d_parent), + .dentry_struct_d_name = offsetof(struct dentry, d_name), + .qstr_struct_name = offsetof(struct qstr, name), + .qstr_struct_len = offsetof(struct qstr, len), + .vfsmount_struct_mnt_root = offsetof(struct vfsmount, mnt_root), + .pgdir_shift = PGDIR_SHIFT, + .ptrs_per_pte = PTRS_PER_PTE, +//TEMP .phys_offset = PHYS_OFFSET, + .page_offset = PAGE_OFFSET, + .page_shift = PAGE_SHIFT, + .page_size = PAGE_SIZE, + .task_struct_struct_thread_group = + offsetof(struct task_struct, thread_group), +#ifdef CONFIG_ARM64 + .task_struct_struct_thread = offsetof(struct task_struct, thread), +#endif + .task_struct_struct_utime = offsetof(struct task_struct, utime), + .task_struct_struct_stime = offsetof(struct task_struct, stime), + .list_head_struct_next = offsetof(struct list_head, next), + .list_head_struct_prev = offsetof(struct list_head, prev), + .rq_struct_curr = 0, + .thread_info_struct_cpu = 0, + .task_struct_struct_prio = offsetof(struct task_struct, prio), + .task_struct_struct_static_prio = + offsetof(struct task_struct, static_prio), + .task_struct_struct_normal_prio = + offsetof(struct task_struct, normal_prio), + .task_struct_struct_rt_priority = + offsetof(struct task_struct, rt_priority), + .task_struct_struct_se = offsetof(struct task_struct, se), + .sched_entity_struct_exec_start = + offsetof(struct sched_entity, exec_start), + .sched_entity_struct_sum_exec_runtime = + offsetof(struct sched_entity, sum_exec_runtime), + .sched_entity_struct_prev_sum_exec_runtime = + offsetof(struct sched_entity, prev_sum_exec_runtime), +#if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT) + .task_struct_struct_sched_info = + offsetof(struct task_struct, sched_info), + .sched_info_struct_pcount = offsetof(struct sched_info, pcount), + .sched_info_struct_run_delay = offsetof(struct sched_info, run_delay), + .sched_info_struct_last_arrival = + offsetof(struct sched_info, last_arrival), + .sched_info_struct_last_queued = + offsetof(struct sched_info, last_queued), +#else + .task_struct_struct_sched_info = 0x1223, + .sched_info_struct_pcount = 0x1224, + .sched_info_struct_run_delay = 0x1225, + .sched_info_struct_last_arrival = 0x1226, + .sched_info_struct_last_queued = 0x1227, +#endif +#ifdef CONFIG_DEBUG_MUTEXES + .task_struct_struct_blocked_on = + offsetof(struct task_struct, blocked_on), + .mutex_waiter_struct_list = offsetof(struct mutex_waiter, list), + .mutex_waiter_struct_task = offsetof(struct mutex_waiter, task), +#else + .task_struct_struct_blocked_on = 0x1228, + .mutex_waiter_struct_list = 0x1229, + .mutex_waiter_struct_task = 0x122a, +#endif +#ifdef CONFIG_FAIR_GROUP_SCHED + .sched_entity_struct_cfs_rq_struct = + offsetof(struct sched_entity, cfs_rq), +#else + .sched_entity_struct_cfs_rq_struct = 0x1223, +#endif + .cfs_rq_struct_rq_struct = 0, +#ifdef CONFIG_FRAME_POINTER + .gaf_fp = 1, +#else + .gaf_fp = 0, +#endif +#ifdef CONFIG_FIVE + .task_struct_integrity = offsetof(struct task_struct, integrity), +#else + .task_struct_integrity = 0xECEF, +#endif +#if defined(CONFIG_FIVE_PA_FEATURE) || defined(CONFIG_PROCA) + .file_struct_f_signature = offsetof(struct file, f_signature), +#endif +#ifdef CONFIG_PROCA + .proca_task_descr_task = + offsetof(struct proca_task_descr, task), + .proca_task_descr_proca_identity = + offsetof(struct proca_task_descr, proca_identity), + .proca_task_descr_pid_map_node = + offsetof(struct proca_task_descr, pid_map_node), + .proca_task_descr_app_name_map_node = + offsetof(struct proca_task_descr, app_name_map_node), + .proca_identity_struct_certificate = + offsetof(struct proca_identity, certificate), + .proca_identity_struct_certificate_size = + offsetof(struct proca_identity, certificate_size), + .proca_identity_struct_parsed_cert = + offsetof(struct proca_identity, parsed_cert), + .proca_table_hash_tables_shift = + offsetof(struct proca_table, hash_tables_shift), + .proca_table_pid_map = + offsetof(struct proca_table, pid_map), + .proca_table_app_name_map = + offsetof(struct proca_table, app_name_map), + .proca_identity_struct_file = + offsetof(struct proca_identity, file), + .proca_certificate_struct_app_name = + offsetof(struct proca_certificate, app_name), + .proca_certificate_struct_app_name_size = + offsetof(struct proca_certificate, app_name_size), +#endif + .mount_struct_mnt_mountpoint = offsetof(struct mount, mnt_mountpoint), +#if defined(CONFIG_KDP_NS) || defined(CONFIG_RUSTUH_KDP_NS) +#if defined(CONFIG_SOC_EXYNOS2100) || defined(CONFIG_ARCH_LAHAINA) + .vfsmount_struct_bp_mount = offsetof(struct kdp_vfsmount, bp_mount), +#else + .vfsmount_struct_bp_mount = offsetof(struct vfsmount, bp_mount), +#endif +#else + .vfsmount_struct_bp_mount = + (short)(offsetof(struct mount, mnt_mountpoint) + - offsetof(struct mount, mnt)), +#endif + .GAFINFOCheckSum = 0 +}; + +const void *proca_gaf_get_addr(void) +{ + return &GAFINFO; +} + +static int __init proca_init_gaf(void) +{ + const unsigned short size = + offsetof(struct GAForensicINFO, GAFINFOCheckSum); + unsigned char *memory = (unsigned char *)&GAFINFO; + unsigned short i = 0; + unsigned short checksum = 0; + /* + * Add GAForensic init for preventing symbol removal for optimization. + */ + GAFINFO.rq_struct_curr = 0; + + for (i = 0; i < size; i++) { + if (checksum & 0x8000) + checksum = ((checksum << 1) | 1) ^ memory[i]; + else + checksum = (checksum << 1) ^ memory[i]; + } + GAFINFO.GAFINFOCheckSum = checksum; + + return 0; +} +core_initcall(proca_init_gaf) diff --git a/security/samsung/proca/gaf/gaf_v6.c b/security/samsung/proca/gaf/gaf_v6.c new file mode 100644 index 000000000000..e93137ba9341 --- /dev/null +++ b/security/samsung/proca/gaf/gaf_v6.c @@ -0,0 +1,205 @@ +/* + * gaf_v6.c + * + */ +#include "proca_gaf.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../fs/mount.h" + +#include "proca_certificate.h" +#include "proca_identity.h" +#include "proca_task_descr.h" +#include "proca_table.h" + +#ifdef CONFIG_PROCA_GKI_10 +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)) +#define OFFSETOF_INTEGRITY offsetof(struct task_struct, android_oem_data1[2]) +#define OFFSETOF_F_SIGNATURE 0 +#else +#define OFFSETOF_INTEGRITY offsetof(struct task_struct, android_vendor_data1[2]) +#define OFFSETOF_F_SIGNATURE offsetof(struct file, android_vendor_data1) +#endif +#else +#define OFFSETOF_INTEGRITY offsetof(struct task_struct, integrity) +#define OFFSETOF_F_SIGNATURE offsetof(struct file, f_signature) +#endif + +static struct GAForensicINFO { + unsigned short ver; + unsigned int size; + unsigned short task_struct_struct_state; + unsigned short task_struct_struct_comm; + unsigned short task_struct_struct_tasks; + unsigned short task_struct_struct_pid; + unsigned short task_struct_struct_mm; + unsigned short mm_struct_struct_pgd; + unsigned short mm_struct_struct_mmap; + unsigned short mm_struct_struct_mm_rb; + unsigned short vm_area_struct_struct_vm_start; + unsigned short vm_area_struct_struct_vm_end; + unsigned short vm_area_struct_struct_vm_next; + unsigned short vm_area_struct_struct_vm_flags; + unsigned short vm_area_struct_struct_vm_file; + unsigned short vm_area_struct_struct_vm_rb; + unsigned short file_struct_f_path; + unsigned short path_struct_mnt; + unsigned short path_struct_dentry; + unsigned short dentry_struct_d_parent; + unsigned short dentry_struct_d_name; + unsigned short qstr_struct_name; + unsigned short qstr_struct_len; + unsigned short struct_mount_mnt_mountpoint; + unsigned short struct_mount_mnt; + unsigned short struct_mount_mnt_parent; + unsigned short list_head_struct_next; + unsigned short list_head_struct_prev; + unsigned short is_kdp_ns_on; + unsigned short task_struct_integrity; + unsigned short proca_task_descr_task; + unsigned short proca_task_descr_proca_identity; + unsigned short proca_task_descr_pid_map_node; + unsigned short proca_task_descr_app_name_map_node; + unsigned short proca_identity_struct_certificate; + unsigned short proca_identity_struct_certificate_size; + unsigned short proca_identity_struct_parsed_cert; + unsigned short proca_identity_struct_file; + unsigned short file_struct_f_signature; + unsigned short proca_table_hash_tables_shift; + unsigned short proca_table_pid_map; + unsigned short proca_table_app_name_map; + unsigned short proca_certificate_struct_app_name; + unsigned short proca_certificate_struct_app_name_size; + unsigned short hlist_node_struct_next; + unsigned short struct_vfsmount_bp_mount; + char reserved[1022]; + unsigned short GAFINFOCheckSum; +} GAFINFO = { + .ver = 0x0600, /* by hryhorii tur 2019 10 21 */ + .size = sizeof(GAFINFO), +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 14, 0)) + .task_struct_struct_state = offsetof(struct task_struct, __state), +#else + .task_struct_struct_state = offsetof(struct task_struct, state), +#endif + .task_struct_struct_comm = offsetof(struct task_struct, comm), + .task_struct_struct_tasks = offsetof(struct task_struct, tasks), + .task_struct_struct_pid = offsetof(struct task_struct, pid), + .task_struct_struct_mm = offsetof(struct task_struct, mm), + .mm_struct_struct_pgd = offsetof(struct mm_struct, pgd), +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)) + .mm_struct_struct_mmap = offsetof(struct mm_struct, mm_mt), + .mm_struct_struct_mm_rb = offsetof(struct mm_struct, mm_mt), + .vm_area_struct_struct_vm_next = + offsetof(struct vm_area_struct, vm_start), + .vm_area_struct_struct_vm_rb + = offsetof(struct vm_area_struct, vm_mm), +#else + .mm_struct_struct_mmap = offsetof(struct mm_struct, mmap), + .mm_struct_struct_mm_rb = offsetof(struct mm_struct, mm_rb), + .vm_area_struct_struct_vm_next = + offsetof(struct vm_area_struct, vm_next), + .vm_area_struct_struct_vm_rb + = offsetof(struct vm_area_struct, vm_rb), +#endif + .vm_area_struct_struct_vm_start = + offsetof(struct vm_area_struct, vm_start), + .vm_area_struct_struct_vm_end = offsetof(struct vm_area_struct, vm_end), + .vm_area_struct_struct_vm_flags = + offsetof(struct vm_area_struct, vm_flags), + .vm_area_struct_struct_vm_file = + offsetof(struct vm_area_struct, vm_file), + .hlist_node_struct_next = offsetof(struct hlist_node, next), + .file_struct_f_path = offsetof(struct file, f_path), + .path_struct_mnt = offsetof(struct path, mnt), + .path_struct_dentry = offsetof(struct path, dentry), + .dentry_struct_d_parent = offsetof(struct dentry, d_parent), + .dentry_struct_d_name = offsetof(struct dentry, d_name), + .qstr_struct_name = offsetof(struct qstr, name), + .qstr_struct_len = offsetof(struct qstr, len), + .struct_mount_mnt_mountpoint = offsetof(struct mount, mnt_mountpoint), + .struct_mount_mnt = offsetof(struct mount, mnt), + .struct_mount_mnt_parent = offsetof(struct mount, mnt_parent), + .list_head_struct_next = offsetof(struct list_head, next), + .list_head_struct_prev = offsetof(struct list_head, prev), +#if defined(CONFIG_KDP_NS) || defined(CONFIG_RKP_NS_PROT) || defined(CONFIG_RUSTUH_KDP_NS) + .is_kdp_ns_on = true, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) + .struct_vfsmount_bp_mount = offsetof(struct kdp_vfsmount, bp_mount), +#else + .struct_vfsmount_bp_mount = offsetof(struct vfsmount, bp_mount), +#endif +#else + .is_kdp_ns_on = false, +#endif + +#ifdef CONFIG_FIVE + .task_struct_integrity = OFFSETOF_INTEGRITY, +#else + .task_struct_integrity = 0xECEF, +#endif +#if defined(CONFIG_FIVE_PA_FEATURE) || defined(CONFIG_PROCA) + .file_struct_f_signature = OFFSETOF_F_SIGNATURE, +#endif +#ifdef CONFIG_PROCA + .proca_task_descr_task = + offsetof(struct proca_task_descr, task), + .proca_task_descr_proca_identity = + offsetof(struct proca_task_descr, proca_identity), + .proca_task_descr_pid_map_node = + offsetof(struct proca_task_descr, pid_map_node), + .proca_task_descr_app_name_map_node = + offsetof(struct proca_task_descr, app_name_map_node), + .proca_identity_struct_certificate = + offsetof(struct proca_identity, certificate), + .proca_identity_struct_certificate_size = + offsetof(struct proca_identity, certificate_size), + .proca_identity_struct_parsed_cert = + offsetof(struct proca_identity, parsed_cert), + .proca_table_hash_tables_shift = + offsetof(struct proca_table, hash_tables_shift), + .proca_table_pid_map = + offsetof(struct proca_table, pid_map), + .proca_table_app_name_map = + offsetof(struct proca_table, app_name_map), + .proca_identity_struct_file = + offsetof(struct proca_identity, file), + .proca_certificate_struct_app_name = + offsetof(struct proca_certificate, app_name), + .proca_certificate_struct_app_name_size = + offsetof(struct proca_certificate, app_name_size), +#endif + .GAFINFOCheckSum = 0 +}; + +const void *proca_gaf_get_addr(void) +{ + return &GAFINFO; +} + +static int __init proca_init_gaf(void) +{ + const unsigned short size = + offsetof(struct GAForensicINFO, GAFINFOCheckSum); + unsigned char *memory = (unsigned char *)&GAFINFO; + unsigned short i = 0; + unsigned short checksum = 0; + + for (i = 0; i < size; i++) { + if (checksum & 0x8000) + checksum = ((checksum << 1) | 1) ^ memory[i]; + else + checksum = (checksum << 1) ^ memory[i]; + } + GAFINFO.GAFINFOCheckSum = checksum; + + return 0; +} +core_initcall(proca_init_gaf) diff --git a/security/samsung/proca/gaf/proca_gaf.h b/security/samsung/proca/gaf/proca_gaf.h new file mode 100644 index 000000000000..58c76b76ebac --- /dev/null +++ b/security/samsung/proca/gaf/proca_gaf.h @@ -0,0 +1,19 @@ +/* + * PROCA GAF + * + * Copyright (C) 2019 Samsung Electronics, Inc. + * Ivan Vorobiov + * + * 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 _SECURITY_PROCA_GAF_H +#define _SECURITY_PROCA_GAF_H +const void *proca_gaf_get_addr(void); +#endif /* _SECURITY_PROCA_GAF_H */ diff --git a/security/samsung/proca/proca_cert_builtin.S b/security/samsung/proca/proca_cert_builtin.S new file mode 100644 index 000000000000..c7d14f45eaa7 --- /dev/null +++ b/security/samsung/proca/proca_cert_builtin.S @@ -0,0 +1,43 @@ +/* + * proca_cert_builtin.S + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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 + + __INITRODATA + +.align 8 + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) +#define VMLINUX_SYMBOL(name) name +#endif + +#define GLOBAL(name) \ + .globl VMLINUX_SYMBOL(name); \ + VMLINUX_SYMBOL(name): + +#ifdef CONFIG_PROCA_CERT_ENG +GLOBAL(proca_local_ca_start_eng) + .incbin CONFIG_PROCA_CERT_ENG +GLOBAL(proca_local_ca_end_eng) +#endif + +#ifdef CONFIG_PROCA_CERT_USER +GLOBAL(proca_local_ca_start_user) + .incbin CONFIG_PROCA_CERT_USER +GLOBAL(proca_local_ca_end_user) +#endif diff --git a/security/samsung/proca/proca_certificate.asn1 b/security/samsung/proca/proca_certificate.asn1 new file mode 100644 index 000000000000..78be3c180db9 --- /dev/null +++ b/security/samsung/proca/proca_certificate.asn1 @@ -0,0 +1,13 @@ +PaCertificate ::= SEQUENCE { + paData PaData, + signature OCTET STRING +} + +PaData ::= SEQUENCE { + paVersion INTEGER, + paFlags INTEGER ({ proca_certificate_get_flags }), + paKeyId INTEGER, + paId OCTET STRING, + paAppName OCTET STRING ({ proca_certificate_get_app_name }), + fiveSignatureHash OCTET STRING ({ proca_certificate_get_five_signature_hash }) +} diff --git a/security/samsung/proca/proca_certificate.c b/security/samsung/proca/proca_certificate.c new file mode 100644 index 000000000000..2b853ebc8e51 --- /dev/null +++ b/security/samsung/proca/proca_certificate.c @@ -0,0 +1,318 @@ +/* + * Utility functions to work with PROCA certificate + * + * Copyright (C) 2018 Samsung Electronics, Inc. + * Hryhorii Tur, + * + * 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 +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0) +#include +#include +#else +#include +#endif + +#include "proca_log.h" +#include "proca_certificate.h" +#include "five_crypto.h" +#include "five_testing.h" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 42) +#include "proca_certificate.asn1.h" +#else +#include "proca_certificate-asn1.h" +#endif + +static struct crypto_shash *g_validation_shash; + +int proca_certificate_get_flags(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct proca_certificate *parsed_cert = context; + + if (!value || !vlen) + return -EINVAL; + + parsed_cert->flags = *(const uint8_t *)value; + + return 0; +} + +int proca_certificate_get_app_name(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct proca_certificate *parsed_cert = context; + + if (!value || !vlen) + return -EINVAL; + + parsed_cert->app_name = kmalloc(vlen + 1, GFP_KERNEL); + if (!parsed_cert->app_name) + return -ENOMEM; + + memcpy(parsed_cert->app_name, value, vlen); + parsed_cert->app_name[vlen] = '\0'; + parsed_cert->app_name_size = vlen; + + return 0; +} + +int proca_certificate_get_five_signature_hash(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct proca_certificate *parsed_cert = context; + + if (!value || !vlen) + return -EINVAL; + + parsed_cert->five_signature_hash = kmalloc(vlen, GFP_KERNEL); + if (!parsed_cert->five_signature_hash) + return -ENOMEM; + + memcpy(parsed_cert->five_signature_hash, value, vlen); + parsed_cert->five_signature_hash_size = vlen; + + return 0; +} + +int parse_proca_certificate(const char *certificate_buff, + const size_t buff_size, + struct proca_certificate *parsed_cert) +{ + int rc = 0; + + memset(parsed_cert, 0, sizeof(*parsed_cert)); + + rc = asn1_ber_decoder(&proca_certificate_decoder, parsed_cert, + certificate_buff, + buff_size); + if (!parsed_cert->app_name || !parsed_cert->five_signature_hash) { + PROCA_INFO_LOG("Failed to parse proca certificate.\n"); + deinit_proca_certificate(parsed_cert); + return -EINVAL; + } + + return rc; +} + +void deinit_proca_certificate(struct proca_certificate *certificate) +{ + kfree(certificate->app_name); + kfree(certificate->five_signature_hash); +} + +int init_certificate_validation_hash(void) +{ + g_validation_shash = crypto_alloc_shash("sha256", 0, 0); + if (IS_ERR(g_validation_shash)) { + PROCA_WARN_LOG("can't alloc sha256 alg, rc - %ld.\n", + PTR_ERR(g_validation_shash)); + return PTR_ERR(g_validation_shash); + } + return 0; +} + +int compare_with_five_signature(const struct proca_certificate *certificate, + const void *five_signature, + const size_t five_signature_size) +{ + SHASH_DESC_ON_STACK(sdesc, g_validation_shash); + char five_sign_hash[SHA256_DIGEST_SIZE]; + int rc = 0; + + if (sizeof(five_sign_hash) != certificate->five_signature_hash_size) { + PROCA_DEBUG_LOG( + "Size of five sign hash is invalid %zu, expected %zu", + certificate->five_signature_hash_size, + sizeof(five_sign_hash)); + return rc; + } + + sdesc->tfm = g_validation_shash; +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0) +/* + * flags was deleted from struct shash_desc in Android Kernel v5.2.0 + */ + sdesc->flags = 0; +#endif + + rc = crypto_shash_init(sdesc); + if (rc != 0) { + PROCA_WARN_LOG("crypto_shash_init failed, rc - %d.\n", rc); + return 0; + } + + rc = crypto_shash_digest(sdesc, five_signature, + five_signature_size, five_sign_hash); + if (rc != 0) { + PROCA_WARN_LOG("crypto_shash_digest failed, rc - %d.\n", rc); + return 0; + } + + return !memcmp(five_sign_hash, + certificate->five_signature_hash, + certificate->five_signature_hash_size); +} + +int proca_certificate_copy(struct proca_certificate *dst, + const struct proca_certificate *src) +{ + BUG_ON(!dst || !src); + + memset(dst, 0, sizeof(*dst)); + + if (src->app_name) { + /* + * app_name is NULL-terminated string with len == app_name_size, + * so we should duplicate app_name_size + 1 bytes + */ + dst->app_name = kmemdup(src->app_name, src->app_name_size + 1, + GFP_KERNEL); + dst->app_name_size = src->app_name_size; + + if (unlikely(!dst->app_name)) + return -ENOMEM; + } + + if (src->five_signature_hash) { + dst->five_signature_hash = kmemdup( + src->five_signature_hash, + src->five_signature_hash_size, + GFP_KERNEL); + dst->five_signature_hash_size = src->five_signature_hash_size; + + if (unlikely(!dst->five_signature_hash)) { + kfree(dst->app_name); + return -ENOMEM; + } + } + + return 0; +} + +/* Copied from TA sources */ +enum PaFlagBits { + PaFlagBits_bitAndroid = 0, + PaFlagBits_bitThirdParty = 1, + PaFlagBits_bitHmac = 2 +}; + +__visible_for_testing __mockable +bool check_native_pa_id(const struct proca_certificate *parsed_cert, + struct task_struct *task) +{ + struct file *exe; + char *path_buff; + char *path; + bool res = false; + + path_buff = kmalloc(parsed_cert->app_name_size + 1, GFP_KERNEL); + if (!path_buff) + return false; + + exe = get_task_exe_file(task); + if (!exe) + goto path_buff_cleanup; + + path = d_path(&exe->f_path, path_buff, parsed_cert->app_name_size + 1); + if (IS_ERR(path)) + goto exe_file_cleanup; + + res = !strcmp(path, parsed_cert->app_name); + if (!res) + PROCA_WARN_LOG( + "file path %s and cert app name %s doesn't match\n", + path, parsed_cert->app_name); + +exe_file_cleanup: + fput(exe); + +path_buff_cleanup: + kfree(path_buff); + + return res; +} + +bool is_certificate_relevant_to_task( + const struct proca_certificate *parsed_cert, + struct task_struct *task) +{ + const char system_server_app_name[] = "/system/framework/services.jar"; + const char *system_proc_names[] = {"system_server", "zygote64"}; + const size_t max_app_name = 1024; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) || defined(PROCA_KUNIT_ENABLED)) + char cmdline[1024 + 1]; +#else + char cmdline[max_app_name + 1]; +#endif + int cmdline_size; + + if (!(parsed_cert->flags & (1 << PaFlagBits_bitAndroid))) + if (!check_native_pa_id(parsed_cert, task)) + return false; + + cmdline_size = get_cmdline(task, cmdline, max_app_name); + cmdline[cmdline_size] = 0; + + // Special case for system_server + if (!strncmp(parsed_cert->app_name, system_server_app_name, + parsed_cert->app_name_size)) { + if (strncmp(cmdline, system_proc_names[0], strlen(system_proc_names[0])) && + strncmp(cmdline, system_proc_names[1], strlen(system_proc_names[1]))) + return false; + } else if (parsed_cert->app_name[0] != '/') { + // Case for Android applications + PROCA_DEBUG_LOG("Task %d has cmdline : %s\n", + task->pid, cmdline); + if (strncmp(cmdline, parsed_cert->app_name, + parsed_cert->app_name_size)) + return false; + } + + return true; +} + +#define PROCA_MAX_DIGEST_SIZE 64 + +bool is_certificate_relevant_to_file( + const struct proca_certificate *parsed_cert, + struct file *file) +{ + int result = 0; + u8 stored_file_hash[PROCA_MAX_DIGEST_SIZE] = {0}; + size_t hash_len = sizeof(stored_file_hash); + + BUG_ON(!file || !parsed_cert); + + result = five_calc_file_hash(file, HASH_ALGO_SHA1, stored_file_hash, &hash_len); + if (result) { + PROCA_WARN_LOG("File hash calculation is failed"); + return false; + } + + return compare_with_five_signature(parsed_cert, stored_file_hash, hash_len); +} + +#if defined(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_GPL(parse_proca_certificate); +EXPORT_SYMBOL_GPL(init_certificate_validation_hash); +#endif diff --git a/security/samsung/proca/proca_certificate.h b/security/samsung/proca/proca_certificate.h new file mode 100644 index 000000000000..1f83ecdecb87 --- /dev/null +++ b/security/samsung/proca/proca_certificate.h @@ -0,0 +1,57 @@ +/* + * PROCA certificate + * + * Copyright (C) 2018 Samsung Electronics, Inc. + * Hryhorii Tur, + * + * 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_PROCA_CERTIFICATE_H +#define _LINUX_PROCA_CERTIFICATE_H + +#include + +#include "proca_porting.h" + +struct proca_certificate { + char *app_name; + size_t app_name_size; + + char *five_signature_hash; + size_t five_signature_hash_size; + + uint32_t flags; +}; + +int parse_proca_certificate(const char *certificate_buff, + const size_t buff_size, + struct proca_certificate *parsed_cert); + +void deinit_proca_certificate(struct proca_certificate *certificate); + +int compare_with_five_signature(const struct proca_certificate *certificate, + const void *five_signature, + const size_t five_signature_size); + +int init_certificate_validation_hash(void); + +int proca_certificate_copy(struct proca_certificate *dst, + const struct proca_certificate *src); + +bool is_certificate_relevant_to_task( + const struct proca_certificate *parsed_cert, + struct task_struct *task); + +bool is_certificate_relevant_to_file( + const struct proca_certificate *parsed_cert, + struct file *file); + +#endif //_LINUX_PROCA_CERTIFICATE_H diff --git a/security/samsung/proca/proca_certificate_db.asn1 b/security/samsung/proca/proca_certificate_db.asn1 new file mode 100644 index 000000000000..27b9d791beae --- /dev/null +++ b/security/samsung/proca/proca_certificate_db.asn1 @@ -0,0 +1,19 @@ +SignedDb ::= SEQUENCE { + db CertificateDb ({ proca_certificate_db_get_signed_data }), + signature OCTET STRING ({ proca_certificate_db_get_signature }) +} + +CertificateDb ::= SEQUENCE { + certificateSeq CertificateSeq, + version INTEGER, + keyId OCTET STRING ({ proca_certificate_db_get_key_id }), + dbMetadata OCTET STRING +} + +CertificateSeq ::= SEQUENCE OF CertificateEntry + +CertificateEntry ::= SEQUENCE { + fileName OCTET STRING ({ proca_certificate_db_get_filename }), + certificate OCTET STRING ({ proca_certificate_db_get_certificate }), + metadata OCTET STRING +} diff --git a/security/samsung/proca/proca_config.c b/security/samsung/proca/proca_config.c new file mode 100644 index 000000000000..7aa66f515fcd --- /dev/null +++ b/security/samsung/proca/proca_config.c @@ -0,0 +1,141 @@ +/* + * Information about kernel needed for proca ta + * + * Copyright (C) 2018 Samsung Electronics, Inc. + * Hryhorii Tur, + * + * 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 "proca_config.h" +#include "proca_log.h" +#include "proca_porting.h" +#include "gaf/proca_gaf.h" + +#define PROCA_CONFIG_VERSION 3U +#define PROCA_CONFIG_MAGIC 0xCD0436EAU + +static int append_sys_ram_range(struct resource *res, void *arg) +{ + struct proca_config *conf = arg; + + PROCA_DEBUG_LOG("System RAM region %llx-%llx was found\n", + res->start, res->end); + + if (conf->sys_ram_ranges_num == MAX_MEMORY_RANGES_NUM) { + PROCA_ERROR_LOG("Unsupported number of sys ram regions %llu\n", + MAX_MEMORY_RANGES_NUM); + return -ENOMEM; + } + conf->sys_ram_ranges[conf->sys_ram_ranges_num].start = res->start; + conf->sys_ram_ranges[conf->sys_ram_ranges_num].end = res->end; + + ++conf->sys_ram_ranges_num; + + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 42) +#define __walk_system_ram_res_cb append_sys_ram_range +#else +static int __walk_system_ram_res_cb(u64 start, u64 end, void *arg) +{ + struct resource res; + + res.start = start; + res.end = end; + return append_sys_ram_range(&res, arg); +} +#endif + +static int prepare_sys_ram_ranges(struct proca_config *conf) +{ + int ret = 0; + + ret = walk_system_ram_res(0, ULONG_MAX, conf, __walk_system_ram_res_cb); + if (ret) + conf->sys_ram_ranges_num = 0; + + PROCA_DEBUG_LOG("Found %llu system RAM ranges\n", + conf->sys_ram_ranges_num); + + return ret; +} + +#ifndef PROCA_KUNIT_ENABLED +static void prepare_kernel_constants(struct proca_config *conf) +{ + conf->page_offset = PAGE_OFFSET; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) + conf->va_bits = vabits_actual; +#else + conf->va_bits = VA_BITS; +#endif + conf->va_start = VA_START; + conf->kimage_vaddr = get_kimage_vaddr(); + conf->kimage_voffset = get_kimage_voffset(); +} +#else +static void prepare_kernel_constants(struct proca_config *conf) {} +#endif + +static void dump_proca_config(const struct proca_config *conf) +{ + size_t i; + + PROCA_DEBUG_LOG("version: %u\n", conf->version); + PROCA_DEBUG_LOG("size: %u\n", conf->size); + PROCA_DEBUG_LOG("magic: %u\n", conf->magic); + + PROCA_DEBUG_LOG("gaf_addr: %llx\n", (uint64_t)conf->gaf_addr); + PROCA_DEBUG_LOG("proca_table_addr: %llx\n", (uint64_t)conf->proca_table_addr); + + PROCA_DEBUG_LOG("page_offset: %llx\n", conf->page_offset); + PROCA_DEBUG_LOG("va_bits: %llu\n", conf->va_bits); + PROCA_DEBUG_LOG("va_start: %llx\n", conf->va_start); + PROCA_DEBUG_LOG("kimage_vaddr: %llx\n", conf->kimage_vaddr); + PROCA_DEBUG_LOG("kimage_voffset: %llx\n", conf->kimage_voffset); + + PROCA_DEBUG_LOG("Discovered %llu system RAM ranges:\n", + conf->sys_ram_ranges_num); + + for (i = 0; i < conf->sys_ram_ranges_num; ++i) { + PROCA_DEBUG_LOG("%llx-%llx.\n", + conf->sys_ram_ranges[i].start, + conf->sys_ram_ranges[i].end); + } +} + +int init_proca_config(struct proca_config *conf, + const struct proca_table *proca_table_addr) +{ + int ret; + + BUG_ON(!conf || !proca_table_addr); + + prepare_kernel_constants(conf); + conf->gaf_addr = proca_gaf_get_addr(); + conf->proca_table_addr = proca_table_addr; + conf->version = PROCA_CONFIG_VERSION; + conf->size = sizeof(*conf); + conf->magic = PROCA_CONFIG_MAGIC; + ret = prepare_sys_ram_ranges(conf); + if (ret) + return ret; + dump_proca_config(conf); + return ret; +} + diff --git a/security/samsung/proca/proca_config.h b/security/samsung/proca/proca_config.h new file mode 100644 index 000000000000..9c604702812f --- /dev/null +++ b/security/samsung/proca/proca_config.h @@ -0,0 +1,53 @@ +/* + * Memory information needed for address translation in tz driver + * + * Copyright (C) 2018 Samsung Electronics, Inc. + * Hryhorii Tur, + * + * 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_PROCA_MEM_INFO_H +#define _LINUX_PROCA_MEM_INFO_H + +#include +#include + +struct memory_range { + uint64_t start; + uint64_t end; + uint64_t flags; +} __packed; + +#define MAX_MEMORY_RANGES_NUM 64ULL + +struct proca_config { + uint32_t version; + uint32_t size; + + uint64_t va_bits; + uint64_t va_start; + uint64_t page_offset; + uint64_t kimage_vaddr; + uint64_t kimage_voffset; + + const void *gaf_addr; + const struct proca_table *proca_table_addr; + + struct memory_range sys_ram_ranges[MAX_MEMORY_RANGES_NUM]; + uint64_t sys_ram_ranges_num; + + uint32_t magic; +} __packed; + +int init_proca_config(struct proca_config *conf, + const struct proca_table *proca_table_addr); + +#endif /* _LINUX_PROCA_MEM_INFO_H */ diff --git a/security/samsung/proca/proca_fcntl.c b/security/samsung/proca/proca_fcntl.c new file mode 100644 index 000000000000..bad89579c527 --- /dev/null +++ b/security/samsung/proca/proca_fcntl.c @@ -0,0 +1,84 @@ +/* + * PROCA fcntl implementation + * + * Copyright (C) 2018 Samsung Electronics, Inc. + * Hryhorii Tur, + * + * 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 + +#define XATTR_PA_SUFFIX "pa" +#define XATTR_NAME_PA (XATTR_USER_PREFIX XATTR_PA_SUFFIX) + +#include "proca_porting.h" + +/** + * Length/Value structure + */ +struct lv { + uint16_t length; + uint8_t value[]; +} __packed; + +int proca_fcntl_setxattr(struct file *file, void __user *lv_xattr) +{ + struct inode *inode = file_inode(file); + struct lv lv_hdr = {0}; + int rc = -EPERM; + void *x = NULL; + + if (unlikely(!file || !lv_xattr)) + return -EINVAL; + + if (unlikely(copy_from_user(&lv_hdr, lv_xattr, sizeof(lv_hdr)))) + return -EFAULT; + + if (unlikely(lv_hdr.length > PAGE_SIZE)) + return -EINVAL; + + x = kmalloc(lv_hdr.length, GFP_NOFS); + if (unlikely(!x)) + return -ENOMEM; + + if (unlikely(copy_from_user(x, lv_xattr + sizeof(lv_hdr), + lv_hdr.length))) { + rc = -EFAULT; + goto out; + } + + if (file->f_op && file->f_op->flush) + if (file->f_op->flush(file, current->files)) { + rc = -EOPNOTSUPP; + goto out; + } + + inode_lock(inode); + + if (task_integrity_allow_sign(TASK_INTEGRITY(current))) { + rc = __vfs_setxattr_noperm(d_real_comp(file->f_path.dentry), + XATTR_NAME_PA, + x, + lv_hdr.length, + 0); + } + inode_unlock(inode); + +out: + kfree(x); + + return rc; +} diff --git a/security/samsung/proca/proca_identity.c b/security/samsung/proca/proca_identity.c new file mode 100644 index 000000000000..e94eb483ea14 --- /dev/null +++ b/security/samsung/proca/proca_identity.c @@ -0,0 +1,99 @@ +/* + * PROCA identity + * + * Copyright (C) 2018 Samsung Electronics, Inc. + * Ivan Vorobiov, + * Egor Uleyskiy, + * + * 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 "proca_identity.h" +#include "proca_certificate.h" +#include +#include +#include + +int init_proca_identity(struct proca_identity *identity, struct file *file, + char **cert_value, const size_t cert_size, + struct proca_certificate *parsed_cert) +{ + int rc = 0; + + if (!file) + return -EINVAL; + + get_file(file); + identity->file = file; + identity->certificate_size = cert_size; + identity->certificate = NULL; + if (cert_value) { + identity->certificate = *cert_value; + *cert_value = NULL; + } + + if (parsed_cert) + identity->parsed_cert = *parsed_cert; + else + memset(&identity->parsed_cert, + 0, sizeof(identity->parsed_cert)); + + return rc; +} + +int proca_identity_copy(struct proca_identity *dst, struct proca_identity *src) +{ + int rc = 0; + + BUG_ON(!dst || !src); + + memset(dst, 0, sizeof(*dst)); + + get_file(src->file); + dst->file = src->file; + + if (src->certificate) { + dst->certificate = kmemdup( + src->certificate, + src->certificate_size, + GFP_KERNEL); + dst->certificate_size = + src->certificate_size; + + if (unlikely(!dst->certificate)) { + fput(src->file); + return -ENOMEM; + } + } + + rc = proca_certificate_copy(&dst->parsed_cert, &src->parsed_cert); + if (rc != 0) { + kfree(dst->certificate); + fput(src->file); + } + + return rc; +} + +void deinit_proca_identity(struct proca_identity *identity) +{ + if (unlikely(!identity)) + return; + + deinit_proca_certificate(&identity->parsed_cert); + if (identity->file) + fput(identity->file); + kfree(identity->certificate); +} + +#if defined(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_GPL(proca_identity_copy); +EXPORT_SYMBOL_GPL(init_proca_identity); +#endif diff --git a/security/samsung/proca/proca_identity.h b/security/samsung/proca/proca_identity.h new file mode 100644 index 000000000000..9132ff73216a --- /dev/null +++ b/security/samsung/proca/proca_identity.h @@ -0,0 +1,42 @@ +/* + * PROCA identity + * + * Copyright (C) 2018 Samsung Electronics, Inc. + * Ivan Vorobiov, + * Egor Uleyskiy, + * + * 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_PROCA_IDENTITY_H +#define _LINUX_PROCA_IDENTITY_H + +#include + +#include "proca_certificate.h" + +struct proca_identity { + void *certificate; + size_t certificate_size; + struct proca_certificate parsed_cert; + struct file *file; +}; + +int init_proca_identity(struct proca_identity *identity, + struct file *file, + char **cert_value, + const size_t cert_size, + struct proca_certificate *parsed_cert); + +void deinit_proca_identity(struct proca_identity *identity); + +int proca_identity_copy(struct proca_identity *dst, struct proca_identity *src); + +#endif /* _LINUX_PROCA_IDENTITY_H */ diff --git a/security/samsung/proca/proca_log.h b/security/samsung/proca/proca_log.h new file mode 100644 index 000000000000..fd9e3d94d060 --- /dev/null +++ b/security/samsung/proca/proca_log.h @@ -0,0 +1,33 @@ +/* + * PROCA logging definitions + * + * Copyright (C) 2018 Samsung Electronics, Inc. + * Hryhorii Tur, + * + * 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_PROCA_LOG_H +#define _LINUX_PROCA_LOG_H + +#ifdef CONFIG_PROCA_DEBUG +#define PROCA_DEBUG_LOG(msg, ...) pr_info("PROCA: "msg, ##__VA_ARGS__) +#else +#define PROCA_DEBUG_LOG(msg, ...) +#endif + +#define PROCA_ERROR_LOG(msg, ...) pr_err("PROCA: "msg, ##__VA_ARGS__) + +#define PROCA_INFO_LOG(msg, ...) pr_info("PROCA: "msg, ##__VA_ARGS__) + +#define PROCA_WARN_LOG(msg, ...) pr_warn("PROCA: "msg, ##__VA_ARGS__) + +#endif /* _LINUX_PROCA_LOG_H */ + diff --git a/security/samsung/proca/proca_lsm.c b/security/samsung/proca/proca_lsm.c new file mode 100644 index 000000000000..82aa4a97cb80 --- /dev/null +++ b/security/samsung/proca/proca_lsm.c @@ -0,0 +1,471 @@ +/* + * PROCA LSM module + * + * Copyright (C) 2018 Samsung Electronics, Inc. + * Ivan Vorobiov, + * + * 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 "proca_identity.h" +#include "proca_certificate.h" +#include "proca_task_descr.h" +#include "proca_table.h" +#include "proca_log.h" +#include "proca_config.h" +#include "proca_porting.h" + +#define XATTR_FIVE_SUFFIX "five" +#define XATTR_NAME_FIVE (XATTR_SECURITY_PREFIX XATTR_FIVE_SUFFIX) + +#define XATTR_PA_SUFFIX "pa" +#define XATTR_NAME_PA (XATTR_USER_PREFIX XATTR_PA_SUFFIX) + +#include "five_hooks.h" + +#ifdef CONFIG_PROCA_GKI_10 +#define F_SIGNATURE(file) ((void *)((file)->android_vendor_data1)) + +static inline void f_signature_assign(struct file *file, void *f_signature) +{ + file->android_vendor_data1 = (u64)f_signature; +} +#else +#define F_SIGNATURE(file) ((file)->f_signature) + +static inline void f_signature_assign(struct file *file, void *f_signature) +{ + file->f_signature = f_signature; +} +#endif + +static void proca_task_free_hook(struct task_struct *task); + +static void proca_file_free_security_hook(struct file *file); + +#ifdef LINUX_LSM_SUPPORTED +static struct security_hook_list proca_ops[] = { + LSM_HOOK_INIT(task_free, proca_task_free_hook), + LSM_HOOK_INIT(file_free_security, proca_file_free_security_hook), +}; +#endif + +static void proca_hook_task_forked(struct task_struct *parent, + enum task_integrity_value parent_tint_value, + struct task_struct *child, + enum task_integrity_value child_tint_value); + +static void proca_hook_file_processed(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file, void *xattr, + size_t xattr_size, int result); + +static void proca_hook_file_signed(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file, void *xattr, + size_t xattr_size, int result); + +static void proca_hook_file_skipped(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file); + +static struct five_hook_list five_ops[] = { + FIVE_HOOK_INIT(task_forked, proca_hook_task_forked), + FIVE_HOOK_INIT(file_processed, proca_hook_file_processed), + FIVE_HOOK_INIT(file_signed, proca_hook_file_signed), + FIVE_HOOK_INIT(file_skipped, proca_hook_file_skipped), +}; + +static struct proca_table g_proca_table; +struct proca_config g_proca_config; + +static int g_proca_inited; + +static int read_xattr(struct dentry *dentry, const char *name, + char **xattr_value) +{ + ssize_t ret; + void *buffer = NULL; + + dentry = d_real_comp(dentry); + + *xattr_value = NULL; + ret = __vfs_getxattr(dentry, dentry->d_inode, name, + NULL, 0, XATTR_NOSECURITY); + if (ret <= 0) + return 0; + + buffer = kmalloc(ret + 1, GFP_NOFS); + if (!buffer) + return 0; + + ret = __vfs_getxattr(dentry, dentry->d_inode, name, + buffer, ret + 1, XATTR_NOSECURITY); + + if (ret <= 0) { + ret = 0; + kfree(buffer); + } else { + *xattr_value = buffer; + } + + return ret; +} + +static struct proca_task_descr *prepare_unsigned_proca_task_descr( + struct task_struct *task, + struct file *file) +{ + struct proca_identity ident; + struct proca_task_descr *task_descr = NULL; + + if (init_proca_identity(&ident, file, NULL, 0, NULL)) + return task_descr; + + task_descr = create_proca_task_descr(task, &ident); + if (!task_descr) + deinit_proca_identity(&ident); + + return task_descr; +} + +static struct proca_task_descr *prepare_proca_task_descr( + struct task_struct *task, struct file *file, + const enum task_integrity_value tint_value, + void *xattr, size_t xattr_size, + char **out_five_xattr_value) +{ + struct proca_certificate parsed_cert; + struct proca_identity ident; + char *pa_xattr_value = NULL; + size_t pa_xattr_size; + char *five_sign_xattr_value = NULL; + size_t five_sign_xattr_size; + struct proca_task_descr *task_descr = NULL; + + pa_xattr_size = read_xattr(file->f_path.dentry, + XATTR_NAME_PA, &pa_xattr_value); + + if (!pa_xattr_value) { + if (task_integrity_value_allow_sign(tint_value)) + return prepare_unsigned_proca_task_descr(task, file); + else + return NULL; + } + + if (xattr) { + five_sign_xattr_value = kmemdup( + xattr, xattr_size, GFP_KERNEL); + five_sign_xattr_size = xattr_size; + } else { + five_sign_xattr_size = read_xattr(file->f_path.dentry, + XATTR_NAME_FIVE, + &five_sign_xattr_value); + } + + if (!five_sign_xattr_value) { + PROCA_INFO_LOG( + "Failed to read five xattr, pid %d, integrity 0x%x\n", + task->pid, tint_value); + goto pa_xattr_cleanup; + } + + if (parse_proca_certificate(pa_xattr_value, pa_xattr_size, + &parsed_cert)) + goto five_xattr_cleanup; + + if (!is_certificate_relevant_to_task(&parsed_cert, task)) + goto proca_cert_cleanup; + + PROCA_DEBUG_LOG("%s xattr was found for task %d\n", XATTR_NAME_PA, + task->pid); + + if (!compare_with_five_signature(&parsed_cert, five_sign_xattr_value, + five_sign_xattr_size)) { + PROCA_INFO_LOG( + "Comparison with five signature for %s failed.\n", + parsed_cert.app_name); + goto proca_cert_cleanup; + } + + if (init_proca_identity(&ident, file, + &pa_xattr_value, pa_xattr_size, + &parsed_cert)) + goto proca_cert_cleanup; + + task_descr = create_proca_task_descr(task, &ident); + if (!task_descr) + goto proca_identity_cleanup; + + *out_five_xattr_value = five_sign_xattr_value; + + return task_descr; + +proca_identity_cleanup:; + deinit_proca_identity(&ident); + +proca_cert_cleanup:; + deinit_proca_certificate(&parsed_cert); + +five_xattr_cleanup:; + kfree(five_sign_xattr_value); + +pa_xattr_cleanup:; + kfree(pa_xattr_value); + + return NULL; +} + +static bool is_bprm(struct task_struct *task, struct file *old_file, + struct file *new_file) +{ + struct file *exe; + bool res; + + exe = get_task_exe_file(task); + if (!exe) + return false; + + res = locks_inode(exe) == locks_inode(new_file) && + locks_inode(old_file) != locks_inode(new_file); + + fput(exe); + return res; +} + +static struct file *get_real_file(struct file *file) +{ + if (locks_inode(file)->i_sb->s_magic == OVERLAYFS_SUPER_MAGIC && + file->private_data) + file = (struct file *)file->private_data; + + return file; +} + +static void proca_hook_file_processed(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file, void *xattr, + size_t xattr_size, int result) +{ + char *five_xattr_value = NULL; + bool need_set_five = false; + struct proca_task_descr *target_task_descr = NULL; + + file = get_real_file(file); + if (!file) + return; + + if (task->flags & PF_KTHREAD) + return; + + target_task_descr = proca_table_get_by_task(&g_proca_table, task); + if (target_task_descr && + is_bprm(task, target_task_descr->proca_identity.file, file)) { + PROCA_DEBUG_LOG( + "Task descr for task %d already exists before exec\n", + task->pid); + + proca_table_remove_task_descr(&g_proca_table, + target_task_descr); + destroy_proca_task_descr(target_task_descr); + target_task_descr = NULL; + } + + if (!target_task_descr) { + target_task_descr = prepare_proca_task_descr( + task, file, tint_value, + xattr, xattr_size, + &five_xattr_value); + if (target_task_descr) + proca_table_add_task_descr(&g_proca_table, + target_task_descr); + } + + need_set_five |= task_integrity_value_allow_sign(tint_value); + + if ((five_xattr_value || need_set_five) && !F_SIGNATURE(file)) { + if (!five_xattr_value && xattr) + five_xattr_value = kmemdup( + xattr, xattr_size, GFP_KERNEL); + else if (!five_xattr_value && !xattr) + read_xattr(file->f_path.dentry, XATTR_NAME_FIVE, + &five_xattr_value); + f_signature_assign(file, five_xattr_value); + } else if (five_xattr_value && F_SIGNATURE(file)) { + kfree(five_xattr_value); + } +} + +static void proca_hook_file_signed(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file, void *xattr, + size_t xattr_size, int result) +{ + char *xattr_value = NULL; + + if (!file || result != 0 || !xattr) + return; + + file = get_real_file(file); + if (!file) + return; + + kfree(F_SIGNATURE(file)); + + xattr_value = kmemdup(xattr, xattr_size, GFP_KERNEL); + f_signature_assign(file, xattr_value); +} + +static void proca_hook_file_skipped(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file) +{ + char *xattr_value = NULL; + struct dentry *dentry; + + if (!task || !file) + return; + + if (F_SIGNATURE(file)) + return; + + file = get_real_file(file); + if (!file) + return; + + dentry = file->f_path.dentry; + + if (task_integrity_value_allow_sign(tint_value) && + read_xattr(dentry, XATTR_NAME_FIVE, &xattr_value) != 0) { + // PROCA get FIVE signature for runtime provisioning + // from kernel, so + // we should set f_signature for each signed file + f_signature_assign(file, xattr_value); + } else if (__vfs_getxattr(dentry, dentry->d_inode, XATTR_NAME_PA, + NULL, 0, XATTR_NOSECURITY) > 0) { + // Workaround for Android applications. + // If file has user.pa - check it. + five_file_verify(task, file); + } +} + +static void proca_hook_task_forked(struct task_struct *parent, + enum task_integrity_value parent_tint_value, + struct task_struct *child, + enum task_integrity_value child_tint_value) +{ + struct proca_task_descr *target_task_descr = NULL; + struct proca_identity ident; + + if (!parent || !child) + return; + + target_task_descr = proca_table_get_by_task(&g_proca_table, parent); + if (!target_task_descr) + return; + + PROCA_DEBUG_LOG("Going to clone proca identity from task %d to %d\n", + parent->pid, child->pid); + + if (proca_identity_copy(&ident, &target_task_descr->proca_identity)) + return; + + target_task_descr = create_proca_task_descr(child, &ident); + if (!target_task_descr) { + deinit_proca_identity(&ident); + return; + } + + proca_table_add_task_descr(&g_proca_table, target_task_descr); +} + +static void proca_task_free_hook(struct task_struct *task) +{ + struct proca_task_descr *target_task_descr = NULL; + + target_task_descr = proca_table_remove_by_task(&g_proca_table, task); + + destroy_proca_task_descr(target_task_descr); +} + +static void proca_file_free_security_hook(struct file *file) +{ + kfree(F_SIGNATURE(file)); + f_signature_assign(file, NULL); +} + +#ifndef LINUX_LSM_SUPPORTED +void proca_compat_task_free_hook(struct task_struct *task) +{ + if (unlikely(!g_proca_inited)) + return; + + proca_task_free_hook(task); +} + +void proca_compat_file_free_security_hook(struct file *file) +{ + if (unlikely(!g_proca_inited)) + return; + + proca_file_free_security_hook(file); +} +#endif + +int proca_get_task_cert(const struct task_struct *task, + const char **cert, size_t *cert_size) +{ + struct proca_task_descr *task_descr = NULL; + + BUG_ON(!task || !cert || !cert_size); + + task_descr = proca_table_get_by_task(&g_proca_table, task); + if (!task_descr) + return -ESRCH; + + *cert = task_descr->proca_identity.certificate; + *cert_size = task_descr->proca_identity.certificate_size; + return 0; +} + +static __init int proca_module_init(void) +{ + int ret; + + ret = init_proca_config(&g_proca_config, &g_proca_table); + if (ret) + return ret; + + ret = init_certificate_validation_hash(); + if (ret) + return ret; + + proca_table_init(&g_proca_table); + + security_add_hooks(proca_ops, ARRAY_SIZE(proca_ops), "proca_lsm"); + five_add_hooks(five_ops, ARRAY_SIZE(five_ops)); + + PROCA_INFO_LOG("LSM module was initialized\n"); + g_proca_inited = 1; + + return 0; +} +late_initcall(proca_module_init); + +MODULE_DESCRIPTION("PROCA LSM module"); +MODULE_LICENSE("GPL"); + diff --git a/security/samsung/proca/proca_porting.h b/security/samsung/proca/proca_porting.h new file mode 100644 index 000000000000..ca7430d099c6 --- /dev/null +++ b/security/samsung/proca/proca_porting.h @@ -0,0 +1,282 @@ +/* + * This is needed backporting of source code from Kernel version 4.x + * + * Copyright (C) 2018 Samsung Electronics, Inc. + * + * Hryhorii Tur, + * + * 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_PROCA_PORTING_H +#define __LINUX_PROCA_PORTING_H + +#include +#include +#include +#include +#include +#include +#include +#if defined(CONFIG_SEC_KUNIT) && defined(CONFIG_UML) +#include "asm-generic/io.h" +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0) + +static inline struct inode *locks_inode(const struct file *f) +{ + return f->f_path.dentry->d_inode; +} + +static inline ssize_t +__vfs_getxattr(struct dentry *dentry, struct inode *inode, const char *name, + void *value, size_t size) +{ + if (inode->i_op->getxattr) + return inode->i_op->getxattr(dentry, name, value, size); + else + return -EOPNOTSUPP; +} + +#endif + +#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 10, 17) +/* Some linux headers are moved. + * Since Kernel 4.11 get_task_struct moved to sched/ folder. + */ +#include +#else +#include +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 21) +/* d_backing_inode is absent on some Linux Kernel 3.x. but it back porting for + * few Samsung kernels: + * Exynos7570 (3.18.14): CL 13680422 + * Exynos7870 (3.18.14): CL 14632149 + * SDM450 (3.18.71): initially + */ +#if !defined(CONFIG_SOC_EXYNOS7570) && !defined(CONFIG_ARCH_SDM450) && \ + !defined(CONFIG_SOC_EXYNOS7870) +#define d_backing_inode(dentry) ((dentry)->d_inode) +#endif +#define inode_lock(inode) mutex_lock(&(inode)->i_mutex) +#define inode_unlock(inode) mutex_unlock(&(inode)->i_mutex) +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 2, 0) +#define security_add_hooks(hooks, count, name) +#else +#define LINUX_LSM_SUPPORTED +#include +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) +#define security_add_hooks(hooks, count, name) security_add_hooks(hooks, count) +#endif +#endif + +/* + * VA_BITS is present only on 64 bit kernels + */ +#if defined(CONFIG_ARM) +#define VA_BITS 30 +#endif + +/* + * VA_START macro is backported to SDM450 kernel (3.18.120) + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) && \ + !(defined(CONFIG_ARCH_SDM450) && defined(CONFIG_ARM64)) +#define VA_START (UL(0xffffffffffffffff) - \ + (UL(1) << VA_BITS) + 1) +#endif + +/* + * VA_START macro is not used since Android Kernel v5.4 + */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) +#define VA_START (PAGE_OFFSET) +#endif + +/* + * KASLR is backported to 4.4 kernels + */ +#ifndef PROCA_KUNIT_ENABLED +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) + +static inline uintptr_t get_kimage_vaddr(void) +{ + return PAGE_OFFSET; +} + +static inline uintptr_t get_kimage_voffset(void) +{ + return get_kimage_vaddr() - virt_to_phys((void *)get_kimage_vaddr()); +} + +#else + +static inline u64 get_kimage_vaddr(void) +{ + return kimage_vaddr; +} + +static inline u64 get_kimage_voffset(void) +{ + return kimage_voffset; +} +#endif +#endif + +#ifndef OVERLAYFS_SUPER_MAGIC +#define OVERLAYFS_SUPER_MAGIC 0x794c7630 +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 8) +static inline struct dentry *d_real_comp(struct dentry *dentry) +{ + return dentry; +} +#elif LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) +static inline struct dentry *d_real_comp(struct dentry *dentry) +{ + return d_real(dentry); +} +#elif LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) +static inline struct dentry *d_real_comp(struct dentry *dentry) +{ + return d_real(dentry, NULL, 0); +} +#elif LINUX_VERSION_CODE < KERNEL_VERSION(4, 19, 0) +static inline struct dentry *d_real_comp(struct dentry *dentry) +{ + return d_real(dentry, NULL, 0, 0); +} +#else +static inline struct dentry *d_real_comp(struct dentry *dentry) +{ + return d_real(dentry, d_real_inode(dentry)); +} +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 22) + +#define get_file_rcu(x) atomic_long_inc_not_zero(&(x)->f_count) + +static inline struct file *_get_mm_exe_file(struct mm_struct *mm) +{ + struct file *exe_file; + + rcu_read_lock(); + exe_file = rcu_dereference(mm->exe_file); + if (exe_file && !get_file_rcu(exe_file)) + exe_file = NULL; + rcu_read_unlock(); + return exe_file; +} + +static inline struct file *get_task_exe_file(struct task_struct *task) +{ + struct file *exe_file = NULL; + struct mm_struct *mm; + + task_lock(task); + mm = task->mm; + if (mm) { + if (!(task->flags & PF_KTHREAD)) + exe_file = _get_mm_exe_file(mm); + } + task_unlock(task); + return exe_file; +} +#endif + +#if (defined(CONFIG_ANDROID) && (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0) || \ + LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0))) || \ + LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) +/* + * __vfs_getxattr was changed in Android Kernel v5.4 + * https://android.googlesource.com/kernel/common/+/3484eba91d6b529cc606486a2db79513f3db6c67 + * and was reverted in Android Kernel v5.15 + * https://android.googlesource.com/kernel/common/+/e884438aa554219a6d0df3a18ff0b23ea678c36c + */ +#define XATTR_NOSECURITY 0x4 /* get value, do not involve security check */ +#define __vfs_getxattr(dentry, inode, name, value, size, flags) \ + __vfs_getxattr(dentry, inode, name, value, size) +#endif + +/* + * BASE64 lib appears since kernel v6.0 + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 0, 0) + +#define BASE64_CHARS(nbytes) DIV_ROUND_UP(nbytes, 3) * 4 + +static const char base64_table[65] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static inline int base64_encode(const u8 *src, int srclen, char *dst) +{ + u32 ac = 0; + int bits = 0; + int i; + char *cp = dst; + + for (i = 0; i < srclen; i++) { + ac = (ac << 8) | src[i]; + bits += 8; + do { + bits -= 6; + *cp++ = base64_table[(ac >> bits) & 0x3f]; + } while (bits >= 6); + } + if (bits) { + *cp++ = base64_table[(ac << (6 - bits)) & 0x3f]; + bits -= 6; + } + while (bits < 0) { + *cp++ = '='; + bits += 2; + } + return cp - dst; +} +#else +/* + * Note: The BASE64_CHARS macro is incorrect in the original code. + * #define BASE64_CHARS(nbytes) DIV_ROUND_UP((nbytes) * 4, 3) + * + * It should be defined as follows to correctly calculate the size + * of the base64-encoded output buffer, including padding: + * + */ +#undef BASE64_CHARS +#define BASE64_CHARS(nbytes) DIV_ROUND_UP(nbytes, 3) * 4 +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +#define __vfs_setxattr_noperm(dentry, name, value, size, flags) \ + __vfs_setxattr_noperm(&init_user_ns, dentry, name, value, size, flags) +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 3, 0) +#define keyring_search(keyring, type, desc, recurse) \ + keyring_search(keyring, type, desc) +#endif + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(4, 20, 0) +static inline size_t str_has_prefix(const char *str, const char *prefix) +{ + size_t len = strlen(prefix); + return strncmp(str, prefix, len) == 0 ? len : 0; +} +#endif + +#endif /* __LINUX_PROCA_PORTING_H */ diff --git a/security/samsung/proca/proca_table.c b/security/samsung/proca/proca_table.c new file mode 100644 index 000000000000..a2bf6a25e307 --- /dev/null +++ b/security/samsung/proca/proca_table.c @@ -0,0 +1,171 @@ +/* + * PROCA task descriptors table + * + * Copyright (C) 2018 Samsung Electronics, Inc. + * Hryhorii Tur, + * + * 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 "proca_table.h" + +#include +#include + +void proca_table_init(struct proca_table *table) +{ + BUG_ON(!table); + + memset(table, 0, sizeof(*table)); + + spin_lock_init(&table->pid_map_lock); + hash_init(table->pid_map); + + spin_lock_init(&table->app_name_map_lock); + hash_init(table->app_name_map); + + table->hash_tables_shift = PROCA_TASKS_TABLE_SHIFT; +} + +/* + * Following hash functions and constants were taken from 4.9.59 kernel + * in order to simplify porting to new devices. + */ + +#define GOLDEN_RATIO_32 0x61C88647 + +static inline u32 proca_hash_32(u32 val) +{ + return val * GOLDEN_RATIO_32; +} + +/* Hash courtesy of the R5 hash in reiserfs modulo sign bits */ +#define proca_init_name_hash(salt) (unsigned long)(salt) + +/* partial hash update function. Assume roughly 4 bits per character */ +static inline unsigned long +proca_partial_name_hash(unsigned long c, unsigned long prevhash) +{ + return (prevhash + (c << 4) + (c >> 4)) * 11; +} + +/* + * Finally: cut down the number of bits to a int value (and try to avoid + * losing bits). This also has the property (wanted by the dcache) + * that the msbits make a good hash table index. + */ +static inline unsigned long proca_end_name_hash(unsigned long hash) +{ + return proca_hash_32((unsigned int)hash); +} + +static unsigned long calculate_app_name_hash(struct proca_table *table, + const char *app_name, + size_t app_name_size) +{ + size_t i; + unsigned long hash = proca_init_name_hash(0); + + if (!app_name) + return proca_end_name_hash(hash); + + for (i = 0; i < app_name_size; ++i) + hash = proca_partial_name_hash(app_name[i], hash); + return proca_end_name_hash(hash) % (1 << table->hash_tables_shift); +} + +static unsigned long calculate_pid_hash(struct proca_table *table, pid_t pid) +{ + return proca_hash_32(pid) >> (32 - table->hash_tables_shift); +} + +void proca_table_add_task_descr(struct proca_table *table, + struct proca_task_descr *descr) +{ + unsigned long hash_key; + unsigned long irqsave_flags; + + hash_key = calculate_pid_hash(table, descr->task->pid); + spin_lock_irqsave(&table->pid_map_lock, irqsave_flags); + hlist_add_head(&descr->pid_map_node, + &table->pid_map[hash_key]); + spin_unlock_irqrestore(&table->pid_map_lock, irqsave_flags); + + if (descr->proca_identity.certificate) { + hash_key = calculate_app_name_hash(table, + descr->proca_identity.parsed_cert.app_name, + descr->proca_identity.parsed_cert.app_name_size); + spin_lock_irqsave(&table->app_name_map_lock, irqsave_flags); + hlist_add_head(&descr->app_name_map_node, + &table->app_name_map[hash_key]); + spin_unlock_irqrestore( + &table->app_name_map_lock, irqsave_flags); + } +} + +void proca_table_remove_task_descr(struct proca_table *table, + struct proca_task_descr *descr) +{ + unsigned long irqsave_flags; + + if (!descr) + return; + + spin_lock_irqsave(&table->pid_map_lock, irqsave_flags); + hash_del(&descr->pid_map_node); + spin_unlock_irqrestore(&table->pid_map_lock, irqsave_flags); + + spin_lock_irqsave(&table->app_name_map_lock, irqsave_flags); + hash_del(&descr->app_name_map_node); + spin_unlock_irqrestore(&table->app_name_map_lock, irqsave_flags); +} + +struct proca_task_descr *proca_table_get_by_task( + struct proca_table *table, + const struct task_struct *task) +{ + struct proca_task_descr *descr; + struct proca_task_descr *target_task_descr = NULL; + unsigned long hash_key; + unsigned long irqsave_flags; + + hash_key = calculate_pid_hash(table, task->pid); + + spin_lock_irqsave(&table->pid_map_lock, irqsave_flags); + hlist_for_each_entry(descr, &table->pid_map[hash_key], pid_map_node) { + if (task == descr->task) { + target_task_descr = descr; + break; + } + } + spin_unlock_irqrestore(&table->pid_map_lock, irqsave_flags); + + return target_task_descr; +} + +struct proca_task_descr *proca_table_remove_by_task( + struct proca_table *table, + const struct task_struct *task) +{ + struct proca_task_descr *target_task_descr = NULL; + + target_task_descr = proca_table_get_by_task(table, task); + proca_table_remove_task_descr(table, target_task_descr); + + return target_task_descr; +} + +#if defined(CONFIG_SEC_KUNIT) +EXPORT_SYMBOL_GPL(proca_table_remove_by_task); +EXPORT_SYMBOL_GPL(proca_table_init); +EXPORT_SYMBOL_GPL(proca_table_get_by_task); +EXPORT_SYMBOL_GPL(proca_table_add_task_descr); +EXPORT_SYMBOL_GPL(compare_with_five_signature); +#endif diff --git a/security/samsung/proca/proca_table.h b/security/samsung/proca/proca_table.h new file mode 100644 index 000000000000..63ad70429fec --- /dev/null +++ b/security/samsung/proca/proca_table.h @@ -0,0 +1,52 @@ +/* + * PROCA task descriptors table + * + * Copyright (C) 2018 Samsung Electronics, Inc. + * Hryhorii Tur, + * + * 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_PROCA_TABLE_H +#define _LINUX_PROCA_TABLE_H + +#include +#include + +#include "proca_task_descr.h" + +#define PROCA_TASKS_TABLE_SHIFT 10 + +struct proca_table { + unsigned int hash_tables_shift; + + DECLARE_HASHTABLE(pid_map, PROCA_TASKS_TABLE_SHIFT); + spinlock_t pid_map_lock; + + DECLARE_HASHTABLE(app_name_map, PROCA_TASKS_TABLE_SHIFT); + spinlock_t app_name_map_lock; +}; + +void proca_table_init(struct proca_table *table); + +void proca_table_add_task_descr(struct proca_table *table, + struct proca_task_descr *descr); + +struct proca_task_descr *proca_table_get_by_task( + struct proca_table *table, + const struct task_struct *task); + +struct proca_task_descr *proca_table_remove_by_task( + struct proca_table *table, + const struct task_struct *task); + +void proca_table_remove_task_descr(struct proca_table *table, + struct proca_task_descr *descr); +#endif //_LINUX_PROCA_TABLE_H diff --git a/security/samsung/proca/proca_task_descr.c b/security/samsung/proca/proca_task_descr.c new file mode 100644 index 000000000000..e9b090f35e11 --- /dev/null +++ b/security/samsung/proca/proca_task_descr.c @@ -0,0 +1,52 @@ +/* + * PROCA task descriptor + * + * Copyright (C) 2018 Samsung Electronics, Inc. + * Hryhorii Tur, + * + * 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 "proca_task_descr.h" +#include "proca_identity.h" +#include "proca_log.h" + +#include +#include + +struct proca_task_descr *create_proca_task_descr(struct task_struct *task, + struct proca_identity *ident) +{ + struct proca_task_descr *task_descr = kzalloc(sizeof(*task_descr), + GFP_KERNEL); + if (unlikely(!task_descr)) + return NULL; + + task_descr->task = task; + task_descr->proca_identity = *ident; + + PROCA_DEBUG_LOG("Task descriptor for task %d was created\n", + task->pid); + PROCA_DEBUG_LOG("Task %d has application name %s\n", + task->pid, ident->parsed_cert.app_name); + + return task_descr; +} + +void destroy_proca_task_descr(struct proca_task_descr *proca_task_descr) +{ + if (!proca_task_descr) + return; + + PROCA_DEBUG_LOG("Destroying proca task descriptor for task %d\n", + proca_task_descr->task->pid); + deinit_proca_identity(&proca_task_descr->proca_identity); + kfree(proca_task_descr); +} diff --git a/security/samsung/proca/proca_task_descr.h b/security/samsung/proca/proca_task_descr.h new file mode 100644 index 000000000000..9938a364b12b --- /dev/null +++ b/security/samsung/proca/proca_task_descr.h @@ -0,0 +1,38 @@ +/* + * PROCA task descriptor interface + * + * Copyright (C) 2018 Samsung Electronics, Inc. + * Hryhorii Tur, + * + * 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_PROCA_TASK_DESCR_H +#define __LINUX_PROCA_TASK_DESCR_H + +#include +#include +#include + +#include "proca_identity.h" + +struct proca_task_descr { + struct task_struct *task; + struct proca_identity proca_identity; + struct hlist_node pid_map_node; + struct hlist_node app_name_map_node; +}; + +struct proca_task_descr *create_proca_task_descr(struct task_struct *task, + struct proca_identity *ident); + +void destroy_proca_task_descr(struct proca_task_descr *proca_task_descr); + +#endif /* __LINUX_PROCA_H */ diff --git a/security/samsung/proca/s_os/dummy.h b/security/samsung/proca/s_os/dummy.h new file mode 100644 index 000000000000..668fc5389ef0 --- /dev/null +++ b/security/samsung/proca/s_os/dummy.h @@ -0,0 +1,15 @@ +/* + * Dummy header + * + * Copyright (C) 2016 Samsung Electronics, Inc. + * Jonghun Song, + * + * 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. + */ diff --git a/security/samsung/proca/s_os/proca_audit.c b/security/samsung/proca/s_os/proca_audit.c new file mode 100644 index 000000000000..b738ce90173b --- /dev/null +++ b/security/samsung/proca/s_os/proca_audit.c @@ -0,0 +1,79 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "proca_log.h" +#include "proca_vfs.h" + +static void proca_audit_msg(struct task_struct *task, struct file *file, + const char *op, const char *cause); + +void proca_audit_err(struct task_struct *task, struct file *file, + const char *op, const char *cause) +{ + proca_audit_msg(task, file, op, cause); +} + +static void proca_audit_msg(struct task_struct *task, struct file *file, + const char *op, const char *cause) +{ + struct audit_buffer *ab; + struct inode *inode = NULL; + const char *fname = NULL; + char *pathbuf = NULL; + char filename[NAME_MAX]; + char comm[TASK_COMM_LEN]; + const char *name = ""; + struct task_struct *tsk = task ? task : current; + + if (file) { + inode = file_inode(file); + fname = proca_d_path(file, &pathbuf, filename); + } + + ab = audit_log_start(current->audit_context, GFP_KERNEL, + AUDIT_INTEGRITY_DATA); + if (unlikely(!ab)) { + PROCA_ERROR_LOG("Can't get a context of audit logs\n"); + goto exit; + } + + audit_log_format(ab, " pid=%d", task_pid_nr(tsk)); + audit_log_format(ab, " tgid=%d", task_tgid_nr(tsk)); + audit_log_task_context(ab); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)) + audit_log_format(ab, " op=%s", op); +#else + audit_log_format(ab, " op="); + audit_log_string(ab, op); +#endif +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)) + audit_log_format(ab, " cause=%s", cause); +#else + audit_log_format(ab, " cause="); + audit_log_string(ab, cause); +#endif + audit_log_format(ab, " comm="); + audit_log_untrustedstring(ab, get_task_comm(comm, tsk)); + if (fname) { + audit_log_format(ab, " name="); + audit_log_untrustedstring(ab, fname); + name = fname; + } + if (inode) { + audit_log_format(ab, " dev="); + audit_log_untrustedstring(ab, inode->i_sb->s_id); + audit_log_format(ab, " ino=%lu", inode->i_ino); + audit_log_format(ab, " i_version=%llu ", + inode_query_iversion(inode)); + } + audit_log_end(ab); + +exit: + if (pathbuf) + __putname(pathbuf); +} diff --git a/security/samsung/proca/s_os/proca_audit.h b/security/samsung/proca/s_os/proca_audit.h new file mode 100644 index 000000000000..0fd26ee4337b --- /dev/null +++ b/security/samsung/proca/s_os/proca_audit.h @@ -0,0 +1,9 @@ +#ifndef __LINUX_PROCA_AUDIT_H +#define __LINUX_PROCA_AUDIT_H + +#include + +void proca_audit_err(struct task_struct *task, struct file *file, + const char *op, const char *cause); + +#endif diff --git a/security/samsung/proca/s_os/proca_certificate_db.c b/security/samsung/proca/s_os/proca_certificate_db.c new file mode 100644 index 000000000000..cb7e2db9b809 --- /dev/null +++ b/security/samsung/proca/s_os/proca_certificate_db.c @@ -0,0 +1,566 @@ +/* + * DB with PROCA certificates + * + * Copyright (C) 2021 Samsung Electronics, Inc. + * Ivan Vorobiov, + * + * 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 "proca_certificate_db.h" +#include "proca_log.h" +#include "proca_vfs.h" +#include "proca_porting.h" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 42) +#include "proca_certificate_db.asn1.h" +#else +#include "proca_certificate_db-asn1.h" +#endif + +struct certificates_db proca_test_db = { + .name = "test", .path = "/data/proca.db", .partition = "data",}; +static struct certificates_db system_db = { + .name = "system", .path = "/system/etc/proca.db", .partition = "system"}; +static struct certificates_db vendor_db = { + .name = "vendor", .path = "/vendor/etc/proca.db", .partition = "vendor"}; +static struct list_head proca_dbs; + +static struct crypto_shash *g_db_validation_shash; + +static int proca_verify_digsig(struct certificates_db *db); +static int proca_calc_data_shash(const u8 *data, size_t data_len, + u8 *hash, size_t *hash_len); + +static inline bool is_test_db(struct certificates_db *db) +{ + if (db == &proca_test_db) + return true; + + return false; +} + +int proca_certificate_db_get_filename(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct certificate_db *db = context; + struct certificate_entry *entry; + + if (!db || !value || !vlen) + return -EINVAL; + + // create new entry + entry = kzalloc(sizeof(struct certificate_entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + list_add(&entry->list, &db->entries); + + entry->file_name = kmalloc(vlen + 1, GFP_KERNEL); + if (!entry->file_name) + return -ENOMEM; + + memcpy(entry->file_name, value, vlen); + entry->file_name[vlen] = '\0'; + entry->file_name_size = vlen; + + PROCA_INFO_LOG("Load certificate for %s.\n", entry->file_name); + + return 0; +} + +int proca_certificate_db_get_certificate(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct certificate_db *db = context; + struct certificate_entry *entry; + + if (!db) + return -EINVAL; + + entry = list_first_entry(&db->entries, struct certificate_entry, list); + + if (!value || !vlen) { + entry->certificate = NULL; + entry->certificate_size = 0; + return 0; + } + + entry->certificate = kmalloc(vlen + 1, GFP_KERNEL); + if (!entry->certificate) + return -ENOMEM; + + memcpy(entry->certificate, value, vlen); + entry->certificate[vlen] = '\0'; + entry->certificate_size = vlen; + + return 0; +} + +int proca_certificate_db_get_signed_data(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct certificate_db *certificate = context; + struct certificates_db *db = container_of(certificate, + struct certificates_db, proca_certificates_db); + struct signed_db *signed_db = &db->proca_signed_db; + int rc = 0; + uint8_t request_hash[PROCA_DB_MAX_DIGEST_SIZE]; + size_t request_hash_size = PROCA_DB_MAX_DIGEST_SIZE; + + if (is_test_db(db)) /* there is no signature in test db */ + return 0; + + if (!certificate || !value || !vlen || + !((const u8 *)value - hdrlen)) /* check all data with header */ + return -EINVAL; + + signed_db->db_hash = kmalloc(PROCA_DB_MAX_DIGEST_SIZE, GFP_KERNEL); + if (!signed_db->db_hash) + return -ENOMEM; + + signed_db->db_hash_size = PROCA_DB_MAX_DIGEST_SIZE; + memset(signed_db->db_hash, 0, signed_db->db_hash_size); + + /* During the signing process, both data with a header is hashed. + * To calculate and verify hash correctly make offset to data with header. + */ + rc = proca_calc_data_shash((const u8 *)value - hdrlen, vlen + hdrlen, + request_hash, &request_hash_size); + if (unlikely(rc)) { + PROCA_INFO_LOG("Failed to calculate request hash\n"); + return rc; + } + + rc = proca_calc_data_shash((const u8 *)request_hash, request_hash_size, + signed_db->db_hash, &signed_db->db_hash_size); + if (unlikely(rc)) { + PROCA_INFO_LOG("Failed to calculate db hash\n"); + return rc; + } + + return 0; +} + +int proca_certificate_db_get_signature(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct certificate_db *certificate = context; + struct certificates_db *db = container_of(certificate, + struct certificates_db, proca_certificates_db); + struct signed_db *signed_db = &db->proca_signed_db; + + if (is_test_db(db)) /* there is no signature in test db */ + return 0; + + if (!certificate || !value || !vlen) + return -EINVAL; + + signed_db->signature = kmalloc(vlen, GFP_KERNEL); + if (!signed_db->signature) + return -ENOMEM; + memcpy(signed_db->signature, value, vlen); + signed_db->signature_size = vlen; + + return 0; +} + +int proca_certificate_db_get_key_id(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + char buff[12] = {0}; + struct certificate_db *certificate = context; + struct certificates_db *db = container_of(certificate, + struct certificates_db, proca_certificates_db); + + if (!db || !value || !vlen) + return -EINVAL; + + memcpy(buff, value, vlen); + if (kstrtouint(buff, 10, &(db->key_id)) != 0) + return -EINVAL; + + return 0; +} + +int parse_proca_db(const char *certificate_buff, + const size_t buff_size, + struct certificate_db *db) +{ + int rc = 0; + + INIT_LIST_HEAD(&db->entries); + + rc = asn1_ber_decoder(&proca_certificate_db_decoder, db, + certificate_buff, + buff_size); + + return rc; +} + +void deinit_proca_db(struct certificates_db *db) +{ + struct list_head *l; + struct certificate_entry *entry; + struct certificate_db *cert_db = &db->proca_certificates_db; + + mutex_lock(&db->lock); + list_for_each(l, &cert_db->entries) { + entry = list_entry(l, struct certificate_entry, list); + kfree(entry->file_name); + kfree(entry->certificate); + } + + if (db->proca_signed_db.db_hash) + kfree(db->proca_signed_db.db_hash); + if (db->proca_signed_db.signature) + kfree(db->proca_signed_db.signature); + mutex_unlock(&db->lock); + atomic_set(&db->status, NOT_READY); +} + +struct certificate_entry *proca_certificate_db_find_entry( + struct certificates_db *db, const char *path) +{ + struct list_head *l; + struct certificate_entry *entry = NULL; + + /* Check that DB is inited */ + if (atomic_read(&db->status) != INITED) + return NULL; + + list_for_each(l, &db->proca_certificates_db.entries) { + entry = list_entry(l, struct certificate_entry, list); + if (strncmp(path, entry->file_name, entry->file_name_size) == 0) + return entry; + } + + return NULL; +} + +/* + * proca_db_is_ready() - Verify if partition of database is + * mounted and actual db file exist + */ +static bool proca_db_is_ready(const char *partition, const char *db_path) +{ + struct path path; + int error; + + /* Checks if the partition where the database file is located exists */ + if (!proca_path_is_mounted(partition)) + return false; + + /* Check if database file exist */ + error = kern_path(db_path, LOOKUP_FOLLOW, &path); + if (error) + return false; + + path_put(&path); + return true; +} + +int __proca_get_certificate_db(const char *pathname, char **certificate) +{ + struct certificate_entry *entry = NULL; + bool check_system, check_vendor; + +#if defined(CONFIG_PROCA_DEBUG) + + if (atomic_read(&proca_test_db.status) == NOT_READY && + proca_db_is_ready(proca_test_db.partition, proca_test_db.path)) { + load_db(proca_test_db.path, &proca_test_db); + } + + mutex_lock(&proca_test_db.lock); + entry = proca_certificate_db_find_entry(&proca_test_db, + pathname); + + if (entry) { + PROCA_INFO_LOG("Certificate for '%s' is found in TEST DB.\n", entry->file_name); + + if (certificate && entry->certificate) { + *certificate = kmemdup(entry->certificate, + entry->certificate_size, GFP_KERNEL); + } + + mutex_unlock(&proca_test_db.lock); + return entry->certificate_size; + } + mutex_unlock(&proca_test_db.lock); + +#endif + + check_system = str_has_prefix(pathname, "/system"); + check_vendor = str_has_prefix(pathname, "/vendor"); + if (!check_vendor && !check_system) + check_system = check_vendor = true; + + if (check_system) { + if (atomic_read(&system_db.status) == NOT_READY && + proca_db_is_ready(system_db.partition, system_db.path)) { + load_db(system_db.path, &system_db); + } + + entry = proca_certificate_db_find_entry(&system_db, + pathname); + if (entry) + goto exit; + } + + if (check_vendor) { + if (atomic_read(&vendor_db.status) == NOT_READY && + proca_db_is_ready(vendor_db.partition, vendor_db.path)) { + load_db(vendor_db.path, &vendor_db); + } + + entry = proca_certificate_db_find_entry(&vendor_db, + pathname); + if (entry) + goto exit; + } + +exit: + if (entry) { + PROCA_INFO_LOG("Certificate for '%s' is found.\n", entry->file_name); + if (certificate && entry->certificate) { + *certificate = kmemdup(entry->certificate, + entry->certificate_size, GFP_KERNEL); + } + + return entry->certificate_size; + } + + return -1; +} + +int proca_get_certificate_db(struct file *file, char **certificate) +{ + const char *pathname = NULL; + char *pathbuf = NULL; + char filename[NAME_MAX]; + int ret = 0; + + if (!file) + return -EINVAL; + + if (certificate) + *certificate = NULL; + + pathname = proca_d_path(file, &pathbuf, filename); + if (!pathbuf) + return -ENOMEM; + + ret = __proca_get_certificate_db(pathname, certificate); + __putname(pathbuf); + + return ret; +} + +bool proca_is_certificate_present_db(struct file *file) +{ + return proca_get_certificate_db(file, NULL) >= 0; +} + +int load_db(const char *file_path, + struct certificates_db *proca_db) +{ + struct file *f; + int data_size, db_size, res = -1; + unsigned char *data_buff = NULL; + struct certificate_db *db; + long error_code; + + if (atomic_read(&proca_db->status) == INITED) + return 0; + + f = proca_kernel_open(file_path, O_RDONLY, 0); + if (IS_ERR(f)) { + error_code = PTR_ERR(f); + if (error_code == -ENOENT) + atomic_set(&proca_db->status, ABSENT); + + PROCA_ERROR_LOG("Failed to open DB file '%s' (%ld)\n", + file_path, (long)PTR_ERR(f)); + goto do_exit; + } + + data_size = i_size_read(file_inode(f)); + if (data_size <= 0 || data_size > 10 * 1024 * 1024) + goto do_clean; + data_buff = vmalloc(data_size); + if (!data_buff) + goto do_clean; + + db_size = proca_kernel_read(f, 0, data_buff, data_size); + if (db_size <= 0) { + PROCA_ERROR_LOG("Failed to read DB file (%d)\n", db_size); + goto do_clean; + } + + PROCA_INFO_LOG("Read %d bytes.\n", db_size); + + mutex_lock(&proca_db->lock); + + if (atomic_read(&proca_db->status) == INITED) { + mutex_unlock(&proca_db->lock); + goto do_clean; + } + + db = &proca_db->proca_certificates_db; + res = parse_proca_db(data_buff, db_size, db); + if (res) { + mutex_unlock(&proca_db->lock); + PROCA_ERROR_LOG("Failed to parse DB asn1 data\n"); + deinit_proca_db(proca_db); + goto do_clean; + } + + if (is_test_db(proca_db)) /* don't need verify signature for test db */ + atomic_set(&proca_db->status, INITED); + else { + res = proca_verify_digsig(proca_db); + if (res) { + mutex_unlock(&proca_db->lock); + PROCA_ERROR_LOG("Failed to verify DB digsig\n"); + deinit_proca_db(proca_db); + goto do_clean; + } + + kfree(proca_db->proca_signed_db.db_hash); + proca_db->proca_signed_db.db_hash = NULL; + kfree(proca_db->proca_signed_db.signature); + proca_db->proca_signed_db.signature = NULL; + atomic_set(&proca_db->status, INITED); + } + mutex_unlock(&proca_db->lock); + +do_clean: + filp_close(f, NULL); + if (data_buff) + vfree(data_buff); +do_exit: + return res; +} + +static int init_db_validation_hash(void) +{ + g_db_validation_shash = crypto_alloc_shash("sha256", 0, 0); + if (IS_ERR(g_db_validation_shash)) { + PROCA_ERROR_LOG("can't alloc sha256 alg, rc - %ld.\n", + PTR_ERR(g_db_validation_shash)); + return PTR_ERR(g_db_validation_shash); + } + return 0; +} + +static int proca_calc_hash_tfm(const u8 *data, size_t data_len, + u8 *hash, size_t *hash_len, struct crypto_shash *tfm) +{ + SHASH_DESC_ON_STACK(shash, tfm); + const size_t len = crypto_shash_digestsize(tfm); + int rc; + + if (*hash_len < len || data_len == 0) + return -EINVAL; + + shash->tfm = tfm; +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0) + shash->flags = 0; +#endif + + rc = crypto_shash_init(shash); + if (rc != 0) + return rc; + + rc = crypto_shash_update(shash, data, data_len); + if (!rc) { + rc = crypto_shash_final(shash, hash); + if (!rc) + *hash_len = len; + } + + return rc; +} + +static int proca_calc_data_shash(const u8 *data, size_t data_len, + u8 *hash, size_t *hash_len) +{ + struct crypto_shash *tfm; + int rc; + + tfm = g_db_validation_shash; + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + + rc = proca_calc_hash_tfm(data, data_len, hash, hash_len, tfm); + + return rc; +} + +static int proca_verify_digsig(struct certificates_db *db) +{ + int rc = 0; + struct signed_db *sig_db = &db->proca_signed_db; + + rc = proca_digsig_verify(sig_db->signature, sig_db->signature_size, + sig_db->db_hash, sig_db->db_hash_size, db->key_id); + + return rc; +} + +int __init proca_certificate_db_init(void) +{ + struct list_head *l; + struct certificates_db *db; + + INIT_LIST_HEAD(&proca_dbs); + list_add(&proca_test_db.list, &proca_dbs); + list_add(&system_db.list, &proca_dbs); + list_add(&vendor_db.list, &proca_dbs); + + list_for_each(l, &proca_dbs) { + db = list_entry(l, struct certificates_db, list); + mutex_init(&db->lock); + atomic_set(&db->status, NOT_READY); + } + + init_db_validation_hash(); + return 0; +} + +void __exit proca_certificate_db_deinit(void) +{ + struct list_head *l; + struct certificates_db *db; + + list_for_each(l, &proca_dbs) { + db = list_entry(l, struct certificates_db, list); + if (atomic_read(&db->status) == INITED) + deinit_proca_db(db); + atomic_set(&db->status, ABSENT); + } + crypto_free_shash(g_db_validation_shash); +} diff --git a/security/samsung/proca/s_os/proca_certificate_db.h b/security/samsung/proca/s_os/proca_certificate_db.h new file mode 100644 index 000000000000..cf3cf8b42109 --- /dev/null +++ b/security/samsung/proca/s_os/proca_certificate_db.h @@ -0,0 +1,128 @@ +/* + * DB with PROCA certificates + * + * Copyright (C) 2021 Samsung Electronics, Inc. + * Ivan Vorobiov, + * + * 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_PROCA_CERTIFICATE_DB_H +#define _LINUX_PROCA_CERTIFICATE_DB_H + +#include +#include + +#include "proca_porting.h" + +#define PROCA_DB_MAX_DIGEST_SIZE 64 + +struct certificate_entry { + char *file_name; + size_t file_name_size; + + char *certificate; + size_t certificate_size; + struct list_head list; +}; + +struct certificate_db { + struct list_head entries; +}; + +struct signed_db { + char *db_hash; + size_t db_hash_size; + char *signature; + size_t signature_size; +}; + +enum db_status { + NOT_READY, + INITED, + ABSENT, +}; + +struct certificates_db { + struct certificate_db proca_certificates_db; + struct signed_db proca_signed_db; + struct mutex lock; + atomic_t status; + const char *name; + const char *path; + const char *partition; + uint32_t key_id; + struct list_head list; +}; + +int parse_proca_db(const char *certificate_buff, + const size_t buff_size, + struct certificate_db *db); + +void deinit_proca_db(struct certificates_db *db); + +int load_db(const char *file_path, + struct certificates_db *proca_db); + +int proca_digsig_verify(const char *signature, int sig_len, + const char *hash, int hash_len, uint32_t key_id); + +int __init proca_certificate_db_init(void); +void __exit proca_certificate_db_deinit(void); + +int __init proca_keyring_init(void); +int __init proca_load_built_x509(void); + +#ifdef CONFIG_PROCA_CERT_DEVICE +int __init init_proca_cert_device(void); +#endif + +/* + * Public API for certificate DB + * This is the main functionality of the PROCA database, + * for reading, searching and verifying file ceritificate. + */ + +/* + * proca_get_certificate_db() - Read certificate for specific file. + * @file: The file struct to get certificate for. + * @certificate: Buffer to copy certificate + * + * Return: Size of certificate on success or error on failure. + */ +int proca_get_certificate_db(struct file *file, char **certificate); + +/* + * __proca_get_certificate_db() - Read certificate for specific file path. + * @pathname: The file path to get certificate for. + * @certificate: Buffer to copy certificate. + * + * Return: Size of certificate on success or error on failure. + */ +int __proca_get_certificate_db(const char *pathname, char **certificate); + +/* + * proca_is_certificate_present_db() - Check if file certificate exist. + * @file: The file struct to check certificate for. + * + * Return: True if certificate presents or false if certificate for file not found. + */ +bool proca_is_certificate_present_db(struct file *file); + +/* + * proca_certificate_db_find_entry() - Searches the database entry for the passed file path. + * @path: The file path to get entry for. + * + * Return: certificate_entry if certificate presents or NULL if certificate for file not found. + */ +struct certificate_entry *proca_certificate_db_find_entry(struct certificates_db *db, + const char *path); + +#endif /* _LINUX_PROCA_CERTIFICATE_DB_H */ diff --git a/security/samsung/proca/s_os/proca_certificate_dev.c b/security/samsung/proca/s_os/proca_certificate_dev.c new file mode 100644 index 000000000000..a9940a245d05 --- /dev/null +++ b/security/samsung/proca/s_os/proca_certificate_dev.c @@ -0,0 +1,296 @@ +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0) +#include +#endif + +#include "proca_certificate_db.h" +#include "proca_vfs.h" +#include "proca_log.h" +#include "proca_porting.h" + +#define PROCA_CERT_DEV_NAME "proca_cert" +#define MAX_DEV_CMD_SIZE 9UL + +extern struct certificates_db proca_test_db; +static dev_t proca_cert_dev; +static struct cdev proca_cert_cdev; +static struct class *proca_cert_class; + +enum { + GET_CMD, + TEST_LIST_CMD, + UPDATE, + MAX_CMD +}; + +static const char *cmd[] = { + [GET_CMD] = "GET", + [TEST_LIST_CMD] = "TEST_LIST", + [UPDATE] = "update" +}; + +static void init_db(struct certificates_db *proca_db) +{ + if (atomic_read(&proca_db->status) != INITED && + proca_path_is_mounted(proca_db->partition)) + load_db(proca_db->path, proca_db); +} + +static ssize_t read_certificate(char **return_buffer, size_t *len, + char __user *buf, size_t count) +{ + size_t data_len = 0; + int cert_size = 0; + char *cert_buff = NULL; + char *path_buf = NULL; + char *user_buf = NULL; + unsigned long cmd_size = strlen(cmd[GET_CMD]); + + /* Parse and load test database in memory if not inited */ + init_db(&proca_test_db); + /* alloc memory for input pathname */ + user_buf = kzalloc(min(PATH_MAX + cmd_size, count), GFP_KERNEL); + if (!user_buf) + return -ENOMEM; + + /* Copy file pathname with command */ + if (copy_from_user(user_buf, buf, min(PATH_MAX + cmd_size, count)) != 0) { + kfree(user_buf); + return -EFAULT; + } + + path_buf = user_buf + cmd_size; /* Skip first 3 bytes of command and get PATH */ + cert_size = __proca_get_certificate_db(path_buf, &cert_buff); + if (!cert_buff) { + PROCA_INFO_LOG("Certificate dev: fail to get ceritificate for %s\n", path_buf); + kfree(user_buf); + return -EINVAL; + } + + data_len = cert_size; + *return_buffer = kzalloc(data_len, GFP_KERNEL); + if (!(*return_buffer)) { + kfree(user_buf); + kfree(cert_buff); + return -ENOMEM; + } + memcpy(*return_buffer, cert_buff, cert_size); + *len = data_len; + + kfree(user_buf); + kfree(cert_buff); + return 0; +} + +ssize_t read_all_certificates(char **return_buffer, size_t *len, struct certificates_db *proca_db) +{ + struct certificate_entry *entry = NULL; + struct list_head *l = NULL; + size_t data_len = 0; + size_t str_len = 0; + char *encoded_cert = NULL; + char *tmp_buf = NULL; + + /* Parse and load test database in memory if not inited */ + init_db(proca_db); + if (atomic_read(&proca_db->status) != INITED) + return -EINVAL; + mutex_lock(&proca_db->lock); + + /* + * Calculate final len of returned data. + * Final buffer consists all entry in db. + * Buffer with 2 entries is in format: + * " \n \n\0" + */ + list_for_each(l, &proca_db->proca_certificates_db.entries) { + entry = list_entry(l, struct certificate_entry, list); + if (entry->certificate) + data_len += entry->file_name_size + + BASE64_CHARS(entry->certificate_size) + 2; + else + data_len += entry->file_name_size + strlen("NULL") + 2; + } + data_len += 1; /* add '\0' at the end */ + PROCA_DEBUG_LOG("Certificate dev: size of data = %lu\n", data_len); + + *return_buffer = kzalloc(data_len, GFP_KERNEL); + if (!(*return_buffer)) { + mutex_unlock(&proca_db->lock); + return -ENOMEM; + } + + /* Fill the final buffer with data */ + tmp_buf = *return_buffer; + list_for_each(l, &proca_db->proca_certificates_db.entries) { + entry = list_entry(l, struct certificate_entry, list); + if (entry->certificate) { + /* Alloc memory and encode certificate */ + encoded_cert = kzalloc(BASE64_CHARS(entry->certificate_size) + 1, GFP_KERNEL); + if (!encoded_cert) { + mutex_unlock(&proca_db->lock); + return -ENOMEM; + } + base64_encode((const u8 *)entry->certificate, + entry->certificate_size, encoded_cert); + } + else { + encoded_cert = kzalloc(strlen("NULL") + 1, GFP_KERNEL); + if (!encoded_cert) { + mutex_unlock(&proca_db->lock); + return -ENOMEM; + } + memcpy(encoded_cert, "NULL", strlen("NULL")); + } + + /* Fill the buffer with each entry */ + str_len = entry->file_name_size + strlen(encoded_cert); + snprintf(tmp_buf, str_len + 3, "%s %s\n", entry->file_name, encoded_cert); + tmp_buf += str_len + 2; + kfree(encoded_cert); + } + mutex_unlock(&proca_db->lock); + *len = data_len; + + return 0; +} + +/* + * proca_dev_read_cert() - device read func: read test database + * There two options how to read db: + * 1) Get certificate for specific path. + * 2) Get all certificates. + * + * @buf: input buffer to write the certificate(s). In case of readind + * specific certificate, input buffer must contain command and path: + * "GET". + * + * And input command to get all cetrificates form test db: + * "TEST_LIST". + */ +static ssize_t proca_dev_read_cert(struct file *filp, char __user *buf, + size_t count, loff_t *f_pos) +{ + ssize_t res = 0; + char *cmd_buff = NULL; + char *return_buffer = NULL; + size_t len = 0; + + /* Need to alloc memory to copy input cmd */ + cmd_buff = kzalloc(min(MAX_DEV_CMD_SIZE + 1, count), GFP_KERNEL); + if (!cmd_buff) + return -ENOMEM; + + if (copy_from_user(cmd_buff, buf, min(MAX_DEV_CMD_SIZE, count)) != 0) { + kfree(cmd_buff); + return -EFAULT; + } + + if (strncmp(cmd_buff, cmd[GET_CMD], strlen(cmd[GET_CMD])) == 0) { + /* Get certificate for the passed file path */ + res = read_certificate(&return_buffer, &len, buf, count); + } + else if (strncmp(cmd_buff, cmd[TEST_LIST_CMD], strlen(cmd[TEST_LIST_CMD])) == 0) { + /* Read All certificates from test db*/ + res = read_all_certificates(&return_buffer, &len, &proca_test_db); + } + + if (len) { + res = simple_read_from_buffer(buf, count, f_pos, return_buffer, len); + PROCA_DEBUG_LOG("Certificate dev: return buf size = %lu\n", len); + } + + kfree(cmd_buff); + kfree(return_buffer); + return res; +} + +/* + * proca_dev_write_cert() - device write func: update test database + * In case if database file is changed proca_dev_write_cert() can update + * database in memory. + * + * @buf: input buffer to read the command. To update database, buffer + * must contain command "update". + * After getting command, firstly old database is deinited. + * Then new database file is read, parsed and loaded in memory. + * + */ +static ssize_t proca_dev_write_cert(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + char *cmd_buf = NULL; + ssize_t ret = 0; + + if (count >= 0) { + cmd_buf = kzalloc(count, GFP_KERNEL); + if (!cmd_buf) + return -ENOMEM; + } + else + return -EINVAL; + + ret = simple_write_to_buffer(cmd_buf, count, f_pos, buf, count); + if (ret <= 0) { + kfree(cmd_buf); + return ret; + } + + if (strncmp(cmd_buf, cmd[UPDATE], strlen(cmd[UPDATE])) == 0) { + if (atomic_read(&proca_test_db.status) == INITED) + deinit_proca_db(&proca_test_db); + if (proca_path_is_mounted(proca_test_db.partition)) + load_db(proca_test_db.path, &proca_test_db); + } + + kfree(cmd_buf); + return ret; +} + +static const struct file_operations proca_cert_cdev_fops = { + .owner = THIS_MODULE, + .read = proca_dev_read_cert, + .write = proca_dev_write_cert, +}; + +int __init init_proca_cert_device(void) +{ + if ((alloc_chrdev_region(&proca_cert_dev, 0, 1, PROCA_CERT_DEV_NAME)) < 0) { + PROCA_ERROR_LOG("Cannot allocate major number\n"); + return -1; + } + + proca_cert_class = class_create(THIS_MODULE, PROCA_CERT_DEV_NAME); + if (IS_ERR(proca_cert_class)) { + PROCA_ERROR_LOG("Cannot create class\n"); + goto region_cleanup; + } + + cdev_init(&proca_cert_cdev, &proca_cert_cdev_fops); + if ((cdev_add(&proca_cert_cdev, proca_cert_dev, 1)) < 0) { + PROCA_ERROR_LOG("Cannot add the device to the system\n"); + goto class_cleanup; + } + + if (!device_create(proca_cert_class, NULL, proca_cert_dev, NULL, PROCA_CERT_DEV_NAME)) { + PROCA_ERROR_LOG("Cannot create device\n"); + goto device_cleanup; + } + + PROCA_INFO_LOG("Certificate device is inited.\n"); + return 0; + +device_cleanup: + cdev_del(&proca_cert_cdev); + +class_cleanup: + class_destroy(proca_cert_class); + +region_cleanup: + unregister_chrdev_region(proca_cert_dev, 1); + return -1; +} diff --git a/security/samsung/proca/s_os/proca_keyring.c b/security/samsung/proca/s_os/proca_keyring.c new file mode 100644 index 000000000000..37a772fe4261 --- /dev/null +++ b/security/samsung/proca/s_os/proca_keyring.c @@ -0,0 +1,260 @@ +#include +#include +#include +#include +#include +#include + +#include "proca_certificate_db.h" +#include "proca_log.h" +#include "proca_porting.h" + +static struct key *proca_keyring; +static const char *proca_keyring_name = "_proca"; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 6, 0) +static inline int proca_verify_signature(struct key *key, + struct public_key_signature *pks, + const char *signature, int sig_len) +{ + int ret = -ENOMEM; + + pks->hash_algo = hash_algo_name[HASH_ALGO_SHA256]; + pks->nr_mpi = 1; + pks->rsa.s = mpi_read_raw_data(signature, sig_len); + + if (pks->rsa.s) + ret = verify_signature(key, pks); + + mpi_free(pks->rsa.s); + + return ret; +} +#elif LINUX_VERSION_CODE < KERNEL_VERSION(4, 20, 0) +static inline int proca_verify_signature(struct key *key, + struct public_key_signature *pks, + const char *signature, int sig_len) +{ + int ret = -ENOMEM; + + pks->pkey_algo = "rsa"; + pks->hash_algo = hash_algo_name[HASH_ALGO_SHA256]; + pks->s = (u8 *)signature; + pks->s_size = sig_len; + ret = verify_signature(key, pks); + + return ret; +} +#else +static inline int proca_verify_signature(struct key *key, + struct public_key_signature *pks, + const char *signature, int sig_len) +{ + int ret = -ENOMEM; + + pks->pkey_algo = "rsa"; + pks->encoding = "pkcs1"; + pks->hash_algo = hash_algo_name[HASH_ALGO_SHA256]; + pks->s = (u8 *)signature; + pks->s_size = sig_len; + ret = verify_signature(key, pks); + + return ret; +} +#endif + +static struct key *proca_request_asymmetric_key(uint32_t keyid) +{ + struct key *key; + char name[12]; + + snprintf(name, sizeof(name), "id:%08x", keyid); + + PROCA_DEBUG_LOG("key search: \"%s\"\n", name); + + if (proca_keyring) { + /* search in specific keyring */ + key_ref_t kref; + + kref = keyring_search(make_key_ref(proca_keyring, 1), + &key_type_asymmetric, name, true); + if (IS_ERR(kref)) + key = ERR_CAST(kref); + else + key = key_ref_to_ptr(kref); + } else { + return ERR_PTR(-ENOKEY); + } + + if (IS_ERR(key)) { + switch (PTR_ERR(key)) { + /* Hide some search errors */ + case -EACCES: + case -ENOTDIR: + case -EAGAIN: + return ERR_PTR(-ENOKEY); + default: + return key; + } + } + + PROCA_DEBUG_LOG("%s() = 0 [%x]\n", __func__, key_serial(key)); + + return key; +} + +static int proca_asymmetric_verify(const char *signature, int sig_len, + const char *hash, int hash_len, uint32_t key_id) +{ + struct public_key_signature pks; + struct key *key; + int ret = -ENOMEM; + + key = proca_request_asymmetric_key(__be32_to_cpu(key_id)); + + memset(&pks, 0, sizeof(pks)); + + pks.digest = (u8 *)hash; + pks.digest_size = hash_len; + ret = proca_verify_signature(key, &pks, signature, sig_len); + key_put(key); + + PROCA_DEBUG_LOG("%s() = %d\n", __func__, ret); + + return ret; +} + +int proca_digsig_verify(const char *signature, int sig_len, + const char *hash, int hash_len, uint32_t key_id) +{ + if (!proca_keyring) { + proca_keyring = request_key( + &key_type_keyring, proca_keyring_name, NULL); + if (IS_ERR(proca_keyring)) { + int err = PTR_ERR(proca_keyring); + + PROCA_ERROR_LOG("no %s keyring: %d\n", proca_keyring_name, err); + proca_keyring = NULL; + return err; + } + } + + return proca_asymmetric_verify(signature, sig_len, hash, hash_len, key_id); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0) +static inline struct key *proca_keyring_alloc(const char *description, + kuid_t uid, kgid_t gid, const struct cred *cred, + key_perm_t perm, unsigned long flags) +{ + return keyring_alloc(description, uid, gid, cred, + perm, flags, NULL); +} +#else +static inline struct key *proca_keyring_alloc(const char *description, + kuid_t uid, kgid_t gid, const struct cred *cred, + key_perm_t perm, unsigned long flags) +{ + return keyring_alloc(description, uid, gid, cred, + perm, flags, NULL, NULL); +} +#endif + +int __init proca_load_x509_from_mem(const char *data, size_t size) +{ + key_ref_t key; + int rc = 0; + + if (!proca_keyring || size == 0) + return -EINVAL; + + key = key_create_or_update(make_key_ref(proca_keyring, 1), + "asymmetric", + NULL, + data, + size, + ((KEY_POS_ALL & ~KEY_POS_SETATTR) | + KEY_USR_VIEW | KEY_USR_READ), + KEY_ALLOC_NOT_IN_QUOTA); + if (IS_ERR(key)) { + rc = PTR_ERR(key); + PROCA_ERROR_LOG("Problem loading X.509 certificate (%d): %s\n", + rc, "built-in"); + } else { + pr_notice("Loaded X.509 cert '%s': %s\n", + key_ref_to_ptr(key)->description, "built-in"); + key_ref_put(key); + } + + return rc; +} + +#ifdef CONFIG_PROCA_CERT_ENG +extern char proca_local_ca_start_eng[]; +extern char proca_local_ca_end_eng[]; + +int __init proca_import_eng_key(void) +{ + size_t size = proca_local_ca_end_eng - proca_local_ca_start_eng; + + return proca_load_x509_from_mem(proca_local_ca_start_eng, size); +} +#else + +int __init proca_import_eng_key(void) +{ + return 0; +} +#endif + +#ifdef CONFIG_PROCA_CERT_USER +extern char proca_local_ca_start_user[]; +extern char proca_local_ca_end_user[]; + +int __init proca_import_user_key(void) +{ + size_t size = proca_local_ca_end_user - proca_local_ca_start_user; + + return proca_load_x509_from_mem(proca_local_ca_start_user, size); +} +#else + +int __init proca_import_user_key(void) +{ + return 0; +} +#endif + +int __init proca_load_built_x509(void) +{ + int rc; + + rc = proca_import_eng_key(); + if (rc) + return rc; + + rc = proca_import_user_key(); + + return rc; +} + +int __init proca_keyring_init(void) +{ + const struct cred *cred = current_cred(); + int err = 0; + + proca_keyring = proca_keyring_alloc(proca_keyring_name, KUIDT_INIT(0), + KGIDT_INIT(0), cred, + ((KEY_POS_ALL & ~KEY_POS_SETATTR) | + KEY_USR_VIEW | KEY_USR_READ | + KEY_USR_SEARCH), + KEY_ALLOC_NOT_IN_QUOTA); + if (IS_ERR(proca_keyring)) { + err = PTR_ERR(proca_keyring); + PROCA_ERROR_LOG("Can't allocate %s keyring (%d)\n", + proca_keyring_name, err); + proca_keyring = NULL; + } + + return err; +} diff --git a/security/samsung/proca/s_os/proca_lsm.c b/security/samsung/proca/s_os/proca_lsm.c new file mode 100644 index 000000000000..8bbe68f4700a --- /dev/null +++ b/security/samsung/proca/s_os/proca_lsm.c @@ -0,0 +1,393 @@ +/* + * PROCA LSM module + * + * Copyright (C) 2018 Samsung Electronics, Inc. + * Ivan Vorobiov, + * + * 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 "five_hooks.h" +#include "five_state.h" + +#include "proca_audit.h" +#include "proca_identity.h" +#include "proca_certificate.h" +#include "proca_task_descr.h" +#include "proca_table.h" +#include "proca_log.h" +#include "proca_config.h" +#include "proca_porting.h" +#include "proca_storage.h" + +#define PROCA_DEV_NAME "proca_config" + +static void proca_task_free_hook(struct task_struct *task); + +#ifdef LINUX_LSM_SUPPORTED +static struct security_hook_list proca_ops[] = { + LSM_HOOK_INIT(task_free, proca_task_free_hook), +}; +#endif + +static void proca_hook_task_forked(struct task_struct *parent, + enum task_integrity_value parent_tint_value, + struct task_struct *child, + enum task_integrity_value child_tint_value); + +static void proca_hook_file_processed(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file, void *cert, + size_t cert_size, int result); + +static void proca_hook_file_signed(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file, void *cert, + size_t cert_size, int result); + +static void proca_hook_file_skipped(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file); + +static void proca_hook_reset_integrity(struct task_struct *task, + struct file *file, + enum task_integrity_reset_cause cause); + +static struct five_hook_list five_ops[] = { + FIVE_HOOK_INIT(task_forked, proca_hook_task_forked), + FIVE_HOOK_INIT(file_processed, proca_hook_file_processed), + FIVE_HOOK_INIT(file_signed, proca_hook_file_signed), + FIVE_HOOK_INIT(file_skipped, proca_hook_file_skipped), + FIVE_HOOK_INIT(integrity_reset2, proca_hook_reset_integrity), +}; + +static struct proca_table g_proca_table; +struct proca_config g_proca_config; +static dev_t proca_dev; +static struct cdev proca_cdev; +static struct class *proca_class; + +static int g_proca_inited; + +static struct proca_task_descr *prepare_proca_task_descr( + struct task_struct *task, struct file *file, + const enum task_integrity_value tint_value) +{ + struct proca_certificate parsed_cert; + struct proca_identity ident; + char *cert_buff = NULL; + int cert_size; + struct proca_task_descr *task_descr = NULL; + + cert_size = proca_get_certificate(file, &cert_buff); + if (!cert_buff) + return NULL; + + if (parse_proca_certificate(cert_buff, cert_size, + &parsed_cert)) + goto cert_buff_cleanup; + + if (!is_certificate_relevant_to_task(&parsed_cert, task)) + goto proca_cert_cleanup; + + PROCA_DEBUG_LOG("PROCA certificate was found for task %d\n", + task->pid); + + if (!is_certificate_relevant_to_file(&parsed_cert, file)) { + PROCA_DEBUG_LOG( + "Certificate %s doesn't relate to file.\n", + parsed_cert.app_name); + goto proca_cert_cleanup; + } + + if (init_proca_identity(&ident, file, + &cert_buff, cert_size, + &parsed_cert)) + goto proca_cert_cleanup; + + task_descr = create_proca_task_descr(task, &ident); + if (!task_descr) + goto proca_identity_cleanup; + + return task_descr; + +proca_identity_cleanup:; + deinit_proca_identity(&ident); + +proca_cert_cleanup:; + deinit_proca_certificate(&parsed_cert); + +cert_buff_cleanup:; + kfree(cert_buff); + + return NULL; +} + +static bool is_bprm(struct task_struct *task, struct file *old_file, + struct file *new_file) +{ + struct file *exe; + bool res; + + exe = get_task_exe_file(task); + if (!exe) + return false; + + res = locks_inode(exe) == locks_inode(new_file) && + locks_inode(old_file) != locks_inode(new_file); + + fput(exe); + return res; +} + +static struct file *get_real_file(struct file *file) +{ + if (locks_inode(file)->i_sb->s_magic == OVERLAYFS_SUPER_MAGIC && + file->private_data) + file = (struct file *)file->private_data; + + return file; +} + +static void proca_hook_file_processed(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file, void *cert, + size_t cert_size, int result) +{ + struct proca_task_descr *target_task_descr = NULL; + + file = get_real_file(file); + if (!file) + return; + + if (task->flags & PF_KTHREAD) + return; + + target_task_descr = proca_table_get_by_task(&g_proca_table, task); + if (target_task_descr && + is_bprm(task, target_task_descr->proca_identity.file, file)) { + PROCA_DEBUG_LOG( + "Task descr for task %d already exists before exec\n", + task->pid); + + proca_table_remove_task_descr(&g_proca_table, + target_task_descr); + destroy_proca_task_descr(target_task_descr); + target_task_descr = NULL; + } + + if (!target_task_descr) { + target_task_descr = prepare_proca_task_descr( + task, file, tint_value); + if (target_task_descr) + proca_table_add_task_descr(&g_proca_table, + target_task_descr); + } +} + +static void proca_hook_file_signed(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file, void *cert, + size_t cert_size, int result) +{ + return; +} + +static void proca_hook_file_skipped(struct task_struct *task, + enum task_integrity_value tint_value, + struct file *file) +{ + if (!task || !file) + return; + + file = get_real_file(file); + if (!file) + return; + + if (proca_is_certificate_present(file)) { + + // Workaround for Android applications. + // If file has user.pa - check it. + five_file_verify(task, file); + } +} + +static void proca_hook_task_forked(struct task_struct *parent, + enum task_integrity_value parent_tint_value, + struct task_struct *child, + enum task_integrity_value child_tint_value) +{ + struct proca_task_descr *target_task_descr = NULL; + struct proca_identity ident; + + if (!parent || !child) + return; + + target_task_descr = proca_table_get_by_task(&g_proca_table, parent); + if (!target_task_descr) + return; + + PROCA_DEBUG_LOG("Going to clone proca identity from task %d to %d\n", + parent->pid, child->pid); + + if (proca_identity_copy(&ident, &target_task_descr->proca_identity)) + return; + + target_task_descr = create_proca_task_descr(child, &ident); + if (!target_task_descr) { + deinit_proca_identity(&ident); + return; + } + + proca_table_add_task_descr(&g_proca_table, target_task_descr); +} + +static void proca_task_free_hook(struct task_struct *task) +{ + struct proca_task_descr *target_task_descr = NULL; + + target_task_descr = proca_table_remove_by_task(&g_proca_table, task); + + destroy_proca_task_descr(target_task_descr); +} + +static void proca_hook_reset_integrity(struct task_struct *task, + struct file *file, + enum task_integrity_reset_cause cause) +{ + if (proca_table_get_by_task(&g_proca_table, task)) + proca_audit_err(task, file, "proca_reset_integrity", + task_integrity_reset_str(cause)); +} + +#ifndef LINUX_LSM_SUPPORTED +void proca_compat_task_free_hook(struct task_struct *task) +{ + if (unlikely(!g_proca_inited)) + return; + + proca_task_free_hook(task); +} +#endif + +int proca_get_task_cert(const struct task_struct *task, + const char **cert, size_t *cert_size) +{ + struct proca_task_descr *task_descr = NULL; + + BUG_ON(!task || !cert || !cert_size); + + task_descr = proca_table_get_by_task(&g_proca_table, task); + if (!task_descr) + return -ESRCH; + + *cert = task_descr->proca_identity.certificate; + *cert_size = task_descr->proca_identity.certificate_size; + return 0; +} + +static ssize_t proca_cdev_read(struct file *filp, char __user *buf, size_t count, + loff_t *f_pos) +{ + phys_addr_t p = virt_to_phys(&g_proca_config); + + if (!proca_table_get_by_task(&g_proca_table, current) || + !task_integrity_user_read(TASK_INTEGRITY(current))) { + PROCA_ERROR_LOG("Access to config is restricted.\n"); + return -EPERM; + } + + return simple_read_from_buffer(buf, count, f_pos, &p, sizeof(p)); +} + +static const struct file_operations proca_cdev_fops = { + .owner = THIS_MODULE, + .read = proca_cdev_read, +}; + +static int init_proca_config_device(void) +{ + if ((alloc_chrdev_region(&proca_dev, 0, 1, PROCA_DEV_NAME)) < 0) { + PROCA_ERROR_LOG("Cannot allocate major number\n"); + return -1; + } + + proca_class = class_create(THIS_MODULE, PROCA_DEV_NAME); + if (IS_ERR(proca_class)) { + PROCA_ERROR_LOG("Cannot create class\n"); + goto region_cleanup; + } + + cdev_init(&proca_cdev, &proca_cdev_fops); + if ((cdev_add(&proca_cdev, proca_dev, 1)) < 0) { + PROCA_ERROR_LOG("Cannot add the device to the system\n"); + goto class_cleanup; + } + + if (!device_create(proca_class, NULL, proca_dev, NULL, PROCA_DEV_NAME)) { + PROCA_ERROR_LOG("Cannot create device\n"); + goto device_cleanup; + } + + PROCA_INFO_LOG("Config driver is inited.\n"); + return 0; + +device_cleanup: + cdev_del(&proca_cdev); + +class_cleanup: + class_destroy(proca_class); + +region_cleanup: + unregister_chrdev_region(proca_dev, 1); + return -1; +} + +static __init int proca_module_init(void) +{ + int ret; + + ret = init_proca_config(&g_proca_config, &g_proca_table); + if (ret) + return ret; + + ret = init_proca_config_device(); + if (ret) + return ret; + + ret = init_certificate_validation_hash(); + if (ret) + return ret; + + proca_table_init(&g_proca_table); + + ret = init_proca_storage(); + if (ret) + return ret; + + security_add_hooks(proca_ops, ARRAY_SIZE(proca_ops), "proca_lsm"); + five_add_hooks(five_ops, ARRAY_SIZE(five_ops)); + + PROCA_INFO_LOG("LSM module was initialized\n"); + g_proca_inited = 1; + + return 0; +} +late_initcall(proca_module_init); + +MODULE_DESCRIPTION("PROCA LSM module"); +MODULE_LICENSE("GPL"); diff --git a/security/samsung/proca/s_os/proca_storage.h b/security/samsung/proca/s_os/proca_storage.h new file mode 100644 index 000000000000..85b3f80042d2 --- /dev/null +++ b/security/samsung/proca/s_os/proca_storage.h @@ -0,0 +1,24 @@ +#ifndef _LINUX_PROCA_STORAGE_H +#define _LINUX_PROCA_STORAGE_H + +/* Public API for certificate storage */ + +/* + * There are two options for certificates storage: xattr or database. + * According to the selected storage type in config, the corresponding + * implementation of API will be applied. + */ + +/* Copy certificate content in cert_buff and return size of certificate */ +int proca_get_certificate(struct file *file, char **cert_buff); + +/* Check if certificate exists for current file */ +bool proca_is_certificate_present(struct file *file); + +/* Init proca Database resources in case of PROCA_CERTIFICATES_DB, + * in case of PROCA_CERTIFICATES_XATTR init function is empty (no + * additional initialization is required for xattr). + */ +int init_proca_storage(void); + +#endif diff --git a/security/samsung/proca/s_os/proca_storage_db.c b/security/samsung/proca/s_os/proca_storage_db.c new file mode 100644 index 000000000000..7e4a11447974 --- /dev/null +++ b/security/samsung/proca/s_os/proca_storage_db.c @@ -0,0 +1,38 @@ +#include "proca_certificate_db.h" +#include "proca_storage.h" + +int proca_get_certificate(struct file *file, char **cert_buff) +{ + int ret = 0; + + ret = proca_get_certificate_db(file, cert_buff); + return ret; +} + +bool proca_is_certificate_present(struct file *file) +{ + return proca_is_certificate_present_db(file); +} + +int __init init_proca_storage(void) +{ + int ret = 0; + + ret = proca_keyring_init(); + if (ret) + return ret; + + ret = proca_load_built_x509(); + if (ret) + return ret; + + ret = proca_certificate_db_init(); + if (ret) + return ret; + +#ifdef CONFIG_PROCA_CERT_DEVICE + ret = init_proca_cert_device(); +#endif + + return ret; +} diff --git a/security/samsung/proca/s_os/proca_storage_xattr.c b/security/samsung/proca/s_os/proca_storage_xattr.c new file mode 100644 index 000000000000..c6c8a23bb425 --- /dev/null +++ b/security/samsung/proca/s_os/proca_storage_xattr.c @@ -0,0 +1,63 @@ +#include +#include + +#include "proca_porting.h" +#include "proca_storage.h" + +#define XATTR_PA_SUFFIX "pa" +#define XATTR_NAME_PA (XATTR_USER_PREFIX XATTR_PA_SUFFIX) + +static int read_xattr(struct dentry *dentry, const char *name, + char **cert_buff) +{ + ssize_t ret; + void *buffer = NULL; + + dentry = d_real_comp(dentry); + *cert_buff = NULL; + ret = __vfs_getxattr(dentry, dentry->d_inode, name, + NULL, 0, XATTR_NOSECURITY); + if (ret <= 0) + return 0; + + buffer = kmalloc(ret + 1, GFP_NOFS); + if (!buffer) + return 0; + + ret = __vfs_getxattr(dentry, dentry->d_inode, name, + buffer, ret + 1, XATTR_NOSECURITY); + + if (ret <= 0) { + ret = 0; + kfree(buffer); + } else { + *cert_buff = buffer; + } + + return ret; +} + +int proca_get_certificate(struct file *file, char **cert_buff) +{ + int ret = 0; + + ret = read_xattr(file->f_path.dentry, XATTR_NAME_PA, cert_buff); + return ret; +} + +bool proca_is_certificate_present(struct file *file) +{ + struct dentry *dentry; + + dentry = file->f_path.dentry; + if (__vfs_getxattr(dentry, dentry->d_inode, XATTR_NAME_PA, + NULL, 0, XATTR_NOSECURITY) > 0) + return true; + + return false; +} + +int init_proca_storage(void) +{ + return 0; +} diff --git a/security/samsung/proca/s_os/proca_vfs.c b/security/samsung/proca/s_os/proca_vfs.c new file mode 100644 index 000000000000..56955699efe4 --- /dev/null +++ b/security/samsung/proca/s_os/proca_vfs.c @@ -0,0 +1,141 @@ +/* + * PROCA vfs functions + * + * Copyright (C) 2020 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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 "proca_vfs.h" + +static int warn_unsupported(struct file *file, const char *op) +{ + pr_warn_ratelimited( + "kernel %s not supported for file %pD4 (pid: %d comm: %.20s)\n", + op, file, current->pid, current->comm); + return -EINVAL; +} + +/* + * This function is copied from __kernel_read() + */ +static ssize_t __proca_kernel_read(struct file *file, void *buf, size_t count, loff_t *pos) +{ + struct kvec iov = { + .iov_base = buf, + .iov_len = min_t(size_t, count, MAX_RW_COUNT), + }; + struct kiocb kiocb; + struct iov_iter iter; + ssize_t ret; + + if (WARN_ON_ONCE(!(file->f_mode & FMODE_READ))) + return -EINVAL; + if (!(file->f_mode & FMODE_CAN_READ)) + return -EINVAL; + /* + * Also fail if ->read_iter and ->read are both wired up as that + * implies very convoluted semantics. + */ + if (unlikely(!file->f_op->read_iter || file->f_op->read)) + return warn_unsupported(file, "read"); + + init_sync_kiocb(&kiocb, file); + kiocb.ki_pos = pos ? *pos : 0; + iov_iter_kvec(&iter, READ, &iov, 1, iov.iov_len); + ret = file->f_op->read_iter(&kiocb, &iter); + if (ret > 0) { + if (pos) + *pos = kiocb.ki_pos; + fsnotify_access(file); + add_rchar(current, ret); + } + inc_syscr(current); + return ret; +} + +/* + * proca_kernel_read - read data from the file + * + * This is a function for reading file content instead of kernel_read(). + * It does not perform locking checks to ensure it cannot be blocked. + * It does not perform security checks because it is irrelevant for IMA. + * + * This function is copied from integrity_kernel_read() + */ +int proca_kernel_read(struct file *file, loff_t offset, + void *addr, unsigned long count) +{ + return __proca_kernel_read(file, addr, count, &offset); +} + +struct file *proca_kernel_open(const char *path, int flags, int rights) +{ + struct file *filp = NULL; + + filp = filp_open(path, flags, rights); + return filp; +} + +bool proca_path_is_mounted(const char *str_path) +{ + struct path path; + int error; + + /* Get path struct for given path name */ + error = kern_path(str_path, LOOKUP_FOLLOW, &path); + if (error) + return false; + + /* A struct vfsmount describes a mount. + * Field 'mnt' represent the mount point if file is mounted. + */ + if (!(path.mnt)) { + path_put(&path); + return false; + } + + path_put(&path); + return true; +} + +const char *proca_d_path(struct file *file, char **pathbuf, char *namebuf) +{ + const struct path *path = &file->f_path; + char *pathname = NULL; + + *pathbuf = __getname(); + if (*pathbuf) { + pathname = d_absolute_path(path, *pathbuf, PATH_MAX); + if (IS_ERR(pathname)) { + __putname(*pathbuf); + *pathbuf = NULL; + pathname = NULL; + } + } + + if (!pathname) { + strlcpy(namebuf, path->dentry->d_name.name, NAME_MAX); + pathname = namebuf; + } + + return pathname; +} diff --git a/security/samsung/proca/s_os/proca_vfs.h b/security/samsung/proca/s_os/proca_vfs.h new file mode 100644 index 000000000000..0e6d296cc274 --- /dev/null +++ b/security/samsung/proca/s_os/proca_vfs.h @@ -0,0 +1,26 @@ +/* + * PROCA vfs functions + * + * Copyright (C) 2020 Samsung Electronics, Inc. + * Egor Uleyskiy, + * + * 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_PROCA_VFS_H +#define _LINUX_PROCA_VFS_H + +struct file *proca_kernel_open(const char *path, int flags, int rights); +int proca_kernel_read(struct file *file, loff_t offset, + void *addr, unsigned long count); +bool proca_path_is_mounted(const char *path); +const char *proca_d_path(struct file *file, char **pathbuf, char *namebuf); + +#endif /* _LINUX_PROCA_VFS_H */ diff --git a/security/samsung/proca/x509_proca_eng.der b/security/samsung/proca/x509_proca_eng.der new file mode 100644 index 000000000000..2dfd0ad73583 Binary files /dev/null and b/security/samsung/proca/x509_proca_eng.der differ diff --git a/security/samsung/proca/x509_proca_user.der b/security/samsung/proca/x509_proca_user.der new file mode 100644 index 000000000000..7c15ba096276 Binary files /dev/null and b/security/samsung/proca/x509_proca_user.der differ diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 78f3da39b031..d88c399b0e86 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3718,33 +3718,6 @@ static int selinux_file_ioctl(struct file *file, unsigned int cmd, return error; } -static int selinux_file_ioctl_compat(struct file *file, unsigned int cmd, - unsigned long arg) -{ - /* - * If we are in a 64-bit kernel running 32-bit userspace, we need to - * make sure we don't compare 32-bit flags to 64-bit flags. - */ - switch (cmd) { - case FS_IOC32_GETFLAGS: - cmd = FS_IOC_GETFLAGS; - break; - case FS_IOC32_SETFLAGS: - cmd = FS_IOC_SETFLAGS; - break; - case FS_IOC32_GETVERSION: - cmd = FS_IOC_GETVERSION; - break; - case FS_IOC32_SETVERSION: - cmd = FS_IOC_SETVERSION; - break; - default: - break; - } - - return selinux_file_ioctl(file, cmd, arg); -} - static int default_noexec __ro_after_init; static int file_map_prot_check(struct file *file, unsigned long prot, int shared) @@ -4717,13 +4690,6 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in return -EINVAL; addr4 = (struct sockaddr_in *)address; if (family_sa == AF_UNSPEC) { - if (family == PF_INET6) { - /* Length check from inet6_bind_sk() */ - if (addrlen < SIN6_LEN_RFC2133) - return -EINVAL; - /* Family check from __inet6_bind() */ - goto err_af; - } /* see __inet_bind(), we only want to allow * AF_UNSPEC if the address is INADDR_ANY */ @@ -7162,7 +7128,6 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(file_permission, selinux_file_permission), LSM_HOOK_INIT(file_alloc_security, selinux_file_alloc_security), LSM_HOOK_INIT(file_ioctl, selinux_file_ioctl), - LSM_HOOK_INIT(file_ioctl_compat, selinux_file_ioctl_compat), LSM_HOOK_INIT(mmap_file, selinux_mmap_file), LSM_HOOK_INIT(mmap_addr, selinux_mmap_addr), LSM_HOOK_INIT(file_mprotect, selinux_file_mprotect), diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 0904827e2f3d..d9fffa35995f 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -358,6 +358,9 @@ config SND_SOC_WM_ADSP default y if SND_SOC_WM2200=y default y if SND_SOC_CS35L41_SPI=y default y if SND_SOC_CS35L41_I2C=y + default y if SND_SOC_CS35L43=y + default y if SND_SOC_CS35L45_SPI=y + default y if SND_SOC_CS35L45_I2C=y default m if SND_SOC_MADERA=m default m if SND_SOC_CS47L24=m default m if SND_SOC_WM5102=m @@ -365,6 +368,9 @@ config SND_SOC_WM_ADSP default m if SND_SOC_WM2200=m default m if SND_SOC_CS35L41_SPI=m default m if SND_SOC_CS35L41_I2C=m + default m if SND_SOC_CS35L43=m + default m if SND_SOC_CS35L45_SPI=m + default m if SND_SOC_CS35L45_I2C=m config SND_SOC_AB8500_CODEC tristate @@ -609,6 +615,14 @@ config SND_SOC_BD28623 config SND_SOC_BT_SCO tristate "Dummy BT SCO codec driver" +config SND_SOC_CIRRUS_AMP + tristate "Cirrus Logic Amp Feature drivers" + help + Enable support for Cirrus Logic smart speaker amplifier factory + driver. Supports various smart amp functions, such as firmware + calibration, data logging, etc. Compatible with CS35L40 and CS35L46 + amplifiers. + config SND_SOC_CPCAP tristate "Motorola CPCAP codec" depends on MFD_CPCAP || COMPILE_TEST @@ -665,6 +679,21 @@ config SND_SOC_CS35L41_I2C select SND_SOC_CS35L41 select REGMAP_I2C +config SND_SOC_CS35L43 + tristate "Cirrus Logic CS35L43 CODEC" + +config SND_SOC_CS35L43_I2C + tristate "Cirrus Logic CS35L43 CODEC (I2C)" + depends on I2C + select REGMAP_I2C + select SND_SOC_CS35L43 + +config SND_SOC_CS35L43_SPI + tristate "Cirrus Logic CS35L43 CODEC (SPI)" + depends on SPI_MASTER + select REGMAP_SPI + select SND_SOC_CS35L43 + config SND_SOC_CS35L45_TABLES tristate @@ -696,6 +725,9 @@ config SND_SOC_CS35L45_I2C config SND_SOC_CS42L42_CORE tristate +config SND_SOC_CIRRUS_REINIT_SYSFS + tristate "Cirrus Logic Amp SysFS Reinit" + config SND_SOC_CS42L42 tristate "Cirrus Logic CS42L42 CODEC (I2C)" depends on I2C @@ -2170,3 +2202,7 @@ config SND_SOC_LPASS_TX_MACRO tristate "Qualcomm TX Macro in LPASS(Low Power Audio SubSystem)" endmenu + +config SND_SOC_CS40L26 + tristate "Cirrus Logic CS40L26 Stub CODEC" + diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 71d3ce5867e4..7ebd533557e8 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -48,6 +48,7 @@ snd-soc-arizona-objs := arizona.o arizona-jack.o snd-soc-aw8738-objs := aw8738.o snd-soc-bd28623-objs := bd28623.o snd-soc-bt-sco-objs := bt-sco.o +snd-soc-cirrus-amp-objs := cirrus-amp.o cirrus-bd.o cirrus-cal.o cirrus-pwr.o cirrus-cal-cspl.o cirrus-cal-cs35l43.o bigdata_cirrus_sysfs_cb.o snd-soc-cpcap-objs := cpcap.o snd-soc-cq93vc-objs := cq93vc.o snd-soc-cros-ec-codec-objs := cros_ec_codec.o @@ -60,6 +61,8 @@ snd-soc-cs35l41-lib-objs := cs35l41-lib.o snd-soc-cs35l41-objs := cs35l41.o snd-soc-cs35l41-spi-objs := cs35l41-spi.o snd-soc-cs35l41-i2c-objs := cs35l41-i2c.o +snd-soc-cs35l43-i2c-objs := cs35l43-i2c.o cs35l43.o cs35l43-tables.o +snd-soc-cs35l43-spi-objs := cs35l43-spi.o cs35l43.o cs35l43-tables.o snd-soc-cs35l45-tables-objs := cs35l45-tables.o snd-soc-cs35l45-objs := cs35l45.o snd-soc-cs35l45-spi-objs := cs35l45-spi.o @@ -410,6 +413,7 @@ obj-$(CONFIG_SND_SOC_AW8738) += snd-soc-aw8738.o obj-$(CONFIG_SND_SOC_BD28623) += snd-soc-bd28623.o obj-$(CONFIG_SND_SOC_BT_SCO) += snd-soc-bt-sco.o obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o +obj-$(CONFIG_SND_SOC_CIRRUS_AMP) += snd-soc-cirrus-amp.o obj-$(CONFIG_SND_SOC_CPCAP) += snd-soc-cpcap.o obj-$(CONFIG_SND_SOC_CROS_EC_CODEC) += snd-soc-cros-ec-codec.o obj-$(CONFIG_SND_SOC_CS35L32) += snd-soc-cs35l32.o @@ -421,6 +425,8 @@ obj-$(CONFIG_SND_SOC_CS35L41) += snd-soc-cs35l41.o obj-$(CONFIG_SND_SOC_CS35L41_LIB) += snd-soc-cs35l41-lib.o obj-$(CONFIG_SND_SOC_CS35L41_SPI) += snd-soc-cs35l41-spi.o obj-$(CONFIG_SND_SOC_CS35L41_I2C) += snd-soc-cs35l41-i2c.o +obj-$(CONFIG_SND_SOC_CS35L43_I2C) += snd-soc-cs35l43-i2c.o +obj-$(CONFIG_SND_SOC_CS35L43_SPI) += snd-soc-cs35l43-spi.o obj-$(CONFIG_SND_SOC_CS35L45_TABLES) += snd-soc-cs35l45-tables.o obj-$(CONFIG_SND_SOC_CS35L45) += snd-soc-cs35l45.o obj-$(CONFIG_SND_SOC_CS35L45_SPI) += snd-soc-cs35l45-spi.o @@ -721,3 +727,7 @@ obj-$(CONFIG_SND_SOC_LPASS_TX_MACRO) += snd-soc-lpass-tx-macro.o # Mux obj-$(CONFIG_SND_SOC_SIMPLE_MUX) += snd-soc-simple-mux.o + + +snd-soc-cs40l26-objs := cs40l26-a2h.o +obj-$(CONFIG_SND_SOC_CS40L26) += snd-soc-cs40l26.o diff --git a/sound/soc/codecs/bigdata_cirrus_sysfs_cb.c b/sound/soc/codecs/bigdata_cirrus_sysfs_cb.c new file mode 100644 index 000000000000..38e7fe1c86d6 --- /dev/null +++ b/sound/soc/codecs/bigdata_cirrus_sysfs_cb.c @@ -0,0 +1,229 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ +/* + * bigdata_cirrus_sysfs_cb.c + * Copyright (c) Samsung Electronics + * + * 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 + +static struct snd_soc_component *cirrus_amp_component; + +static const char *cirrus_amp_suffix[AMP_ID_MAX] = { + [AMP_0] = "_0", + [AMP_1] = "_1", + [AMP_2] = "_2", + [AMP_3] = "_3", +}; + +static int check_component_and_id(struct snd_soc_component *component, + enum amp_id id) +{ + if (!component) { + pr_err("%s: component NULL\n", __func__); + return -EPERM; + } + + if (id >= AMP_ID_MAX) { + dev_err(component->dev, "%s: invalid id\n", __func__); + return -EINVAL; + } + + return 0; +} + +static int get_cirrus_amp_temperature_max(enum amp_id id) +{ + struct snd_soc_component *component = cirrus_amp_component; + struct cirrus_amp *amp; + unsigned int value = 0; + int ret = 0; + + ret = check_component_and_id(component, id); + if (ret < 0) + return ret; + + amp = cirrus_get_amp_from_suffix(cirrus_amp_suffix[id]); + if (!amp) { + dev_err(component->dev, "%s: invalid suffix\n", __func__); + return -EINVAL; + } + + value = amp->bd.max_temp >> CIRRUS_BD_TEMP_RADIX; + amp->bd.max_temp = 0; + + dev_info(component->dev, "%s: id %d value %d\n", __func__, id, value); + + return value; +} + +static int get_cirrus_amp_temperature_keep_max(enum amp_id id) +{ + struct snd_soc_component *component = cirrus_amp_component; + struct cirrus_amp *amp; + unsigned int value = 0; + int ret = 0; + + ret = check_component_and_id(component, id); + if (ret < 0) + return ret; + + amp = cirrus_get_amp_from_suffix(cirrus_amp_suffix[id]); + if (!amp) { + dev_err(component->dev, "%s: invalid suffix\n", __func__); + return -EINVAL; + } + + value = amp->bd.max_temp_keep >> CIRRUS_BD_TEMP_RADIX; + + dev_info(component->dev, "%s: id %d value %d\n", __func__, id, value); + + return value; +} + +static int get_cirrus_amp_temperature_overcount(enum amp_id id) +{ + struct snd_soc_component *component = cirrus_amp_component; + struct cirrus_amp *amp; + unsigned int value = 0; + int ret = 0; + + ret = check_component_and_id(component, id); + if (ret < 0) + return ret; + + amp = cirrus_get_amp_from_suffix(cirrus_amp_suffix[id]); + if (!amp) { + dev_err(component->dev, "%s: invalid suffix\n", __func__); + return -EINVAL; + } + + value = amp->bd.over_temp_count; + amp->bd.over_temp_count = 0; + + dev_info(component->dev, "%s: id %d value %d\n", __func__, id, value); + + return value; +} + +static int get_cirrus_amp_excursion_max(enum amp_id id) +{ + struct snd_soc_component *component = cirrus_amp_component; + struct cirrus_amp *amp; + unsigned int value = 0; + int ret = 0; + + ret = check_component_and_id(component, id); + if (ret < 0) + return ret; + + amp = cirrus_get_amp_from_suffix(cirrus_amp_suffix[id]); + if (!amp) { + dev_err(component->dev, "%s: invalid suffix\n", __func__); + return -EINVAL; + } + + value = (amp->bd.max_exc & + (((1 << CIRRUS_BD_EXC_RADIX) - 1))) * + 10000 / (1 << CIRRUS_BD_EXC_RADIX); + amp->bd.max_exc = 0; + + dev_info(component->dev, "%s: id %d value %d\n", __func__, id, value); + + return value; +} + +static int get_cirrus_amp_excursion_overcount(enum amp_id id) +{ + struct snd_soc_component *component = cirrus_amp_component; + struct cirrus_amp *amp; + unsigned int value = 0; + int ret = 0; + + ret = check_component_and_id(component, id); + if (ret < 0) + return ret; + + amp = cirrus_get_amp_from_suffix(cirrus_amp_suffix[id]); + if (!amp) { + dev_err(component->dev, "%s: invalid suffix\n", __func__); + return -EINVAL; + } + + value = amp->bd.over_exc_count; + amp->bd.over_exc_count = 0; + + dev_info(component->dev, "%s: id %d value %d\n", __func__, id, value); + + return value; +} + +static int get_cirrus_amp_curr_temperature(enum amp_id id) +{ + struct snd_soc_component *component = cirrus_amp_component; + int value = 0; + int ret = 0; + + ret = check_component_and_id(component, id); + if (ret < 0) + return ret; + + value = cirrus_cal_read_temp(cirrus_amp_suffix[id]); + if (value < 0) { + dev_err(component->dev, "%s: component is not enabled\n", __func__); + return -EINVAL; + } + + dev_info(component->dev, "%s: id %d value %d\n", __func__, id, value); + + return value; +} + +static int set_cirrus_amp_surface_temperature(enum amp_id id, int temperature) +{ + struct snd_soc_component *component = cirrus_amp_component; + int value = 0; + int ret = 0; + + ret = check_component_and_id(component, id); + if (ret < 0) + return ret; + + value = cirrus_cal_set_surface_temp(cirrus_amp_suffix[id], temperature); + if (value < 0) { + dev_err(component->dev, "%s: Amp%d is not enabled\n", + __func__, id); + return -EINVAL; + } + + return value; +} + +void register_cirrus_bigdata_cb(struct snd_soc_component *component) +{ + cirrus_amp_component = component; + + dev_info(component->dev, "%s\n", __func__); + + audio_register_temperature_max_cb(get_cirrus_amp_temperature_max); + audio_register_temperature_keep_max_cb(get_cirrus_amp_temperature_keep_max); + audio_register_temperature_overcount_cb(get_cirrus_amp_temperature_overcount); + audio_register_excursion_max_cb(get_cirrus_amp_excursion_max); + audio_register_excursion_overcount_cb(get_cirrus_amp_excursion_overcount); + audio_register_curr_temperature_cb(get_cirrus_amp_curr_temperature); + audio_register_surface_temperature_cb(set_cirrus_amp_surface_temperature); +} +EXPORT_SYMBOL_GPL(register_cirrus_bigdata_cb); diff --git a/sound/soc/codecs/cirrus-amp.c b/sound/soc/codecs/cirrus-amp.c new file mode 100644 index 000000000000..a4b814fe958a --- /dev/null +++ b/sound/soc/codecs/cirrus-amp.c @@ -0,0 +1,425 @@ +/* + * Extended support for Cirrus Logic Smart Amplifiers + * + * Copyright 2017 Cirrus Logic + * + * Author: David Rhodes + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm_adsp.h" +#include +#include + +struct class *cirrus_amp_class; +EXPORT_SYMBOL_GPL(cirrus_amp_class); + +struct cirrus_amp_group *amp_group; + +static struct cirrus_cal_ops *cirrus_cal_ops[3] = { + &cirrus_cspl_cal_ops, + &cirrus_cspl_cal_ops, +#if IS_ENABLED(CONFIG_SND_SOC_CS35L43) + &cirrus_cs35l43_cal_ops +#endif +}; + +struct cirrus_amp *cirrus_get_amp_from_suffix(const char *suffix) +{ + int i; + + if (suffix == NULL) + return NULL; + + if (amp_group == NULL || (amp_group->num_amps == 0)) + return NULL; + + pr_debug("%s: suffix = %s\n", __func__, suffix); + + for (i = 0; i < amp_group->num_amps; i++) { + if (amp_group->amps[i].bd.bd_suffix) { + pr_debug("%s: comparing %s & %s\n", __func__, + amp_group->amps[i].bd.bd_suffix, suffix); + if (!strcmp(amp_group->amps[i].bd.bd_suffix, suffix)) + return &_group->amps[i]; + } + } + + for (i = 0; i < amp_group->num_amps; i++) { + pr_debug("%s: comparing %s & %s\n", __func__, + amp_group->amps[i].mfd_suffix, suffix); + if (!strcmp(amp_group->amps[i].mfd_suffix, suffix)) + return &_group->amps[i]; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(cirrus_get_amp_from_suffix); + +void cirrus_amp_register_i2c_error_callback(const char *suffix, void *func) +{ + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + + if (amp) + amp->i2c_callback = func; +} +EXPORT_SYMBOL_GPL(cirrus_amp_register_i2c_error_callback); + +void cirrus_amp_register_error_callback(const char *suffix, void *func) +{ + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + + if (amp) + amp->error_callback = func; +} +EXPORT_SYMBOL_GPL(cirrus_amp_register_error_callback); + +int cirrus_amp_add(const char *mfd_suffix, struct cirrus_amp_config cfg) +{ + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(mfd_suffix); + + if (amp) { + dev_info(amp_group->cal_dev, + "Amp added, suffix: %s dsp_part_name: %s\n", + mfd_suffix, cfg.dsp_part_name); + + amp->component = cfg.component; + amp->regmap = cfg.regmap; + amp->dsp_part_name = cfg.dsp_part_name; + amp->num_pre_configs = cfg.num_pre_configs; + amp->num_post_configs = cfg.num_post_configs; + amp->mbox_cmd = cfg.mbox_cmd; + amp->mbox_sts = cfg.mbox_sts; + amp->global_en = cfg.global_en; + amp->global_en_mask = cfg.global_en_mask; + amp->vimon_alg_id = cfg.vimon_alg_id; + amp->pwr.target_temp = cfg.target_temp; + amp->pwr.exit_temp = cfg.exit_temp; + amp->perform_vimon_cal = cfg.perform_vimon_cal; + amp->calibration_disable = cfg.calibration_disable; + amp->cal_vpk_id = cfg.cal_vpk_id ? cfg.cal_vpk_id : + CIRRUS_CAL_RTLOG_ID_V_PEAK; + amp->cal_ipk_id = cfg.cal_ipk_id ? cfg.cal_ipk_id : + CIRRUS_CAL_RTLOG_ID_I_PEAK; + amp->halo_alg_id = cfg.halo_alg_id ? cfg.halo_alg_id : + CIRRUS_AMP_ALG_ID_HALO; + amp->bd.bd_alg_id = cfg.bd_alg_id ? cfg.bd_alg_id : + CIRRUS_AMP_ALG_ID_CSPL; + amp->bd.bd_prefix = cfg.bd_prefix ? cfg.bd_prefix : + "BDLOG_"; + amp->cal_vsc_ub = cfg.cal_vsc_ub ? cfg.cal_vsc_ub : + CIRRUS_CAL_VIMON_CAL_VSC_UB; + amp->cal_vsc_lb = cfg.cal_vsc_lb ? cfg.cal_vsc_lb : + CIRRUS_CAL_VIMON_CAL_VSC_LB; + amp->cal_isc_ub = cfg.cal_isc_ub ? cfg.cal_isc_ub : + CIRRUS_CAL_VIMON_CAL_ISC_UB; + amp->cal_isc_lb = cfg.cal_isc_lb ? cfg.cal_isc_lb : + CIRRUS_CAL_VIMON_CAL_ISC_LB; + amp->bd.max_temp_limit = cfg.bd_max_temp ? + cfg.bd_max_temp - 1 : 99; + amp->default_redc = cfg.default_redc ? cfg.default_redc : + CIRRUS_CAL_RDC_DEFAULT; + amp->cal_ops = cfg.cal_ops_idx ? cirrus_cal_ops[cfg.cal_ops_idx] : + cirrus_cal_ops[CIRRUS_CAL_CSPL_CAL_OPS_IDX]; + + amp->pre_config = kcalloc(cfg.num_pre_configs, + sizeof(struct reg_sequence), + GFP_KERNEL); + + amp->post_config = kcalloc(cfg.num_post_configs, + sizeof(struct reg_sequence), + GFP_KERNEL); + + amp->amp_reinit = cfg.amp_reinit; + amp->runtime_pm = cfg.runtime_pm; + + amp_group->pwr_enable |= cfg.pwr_enable; + + memcpy(amp->pre_config, cfg.pre_config, + sizeof(struct reg_sequence) * cfg.num_pre_configs); + + memcpy(amp->post_config, cfg.post_config, + sizeof(struct reg_sequence) * cfg.num_post_configs); + } else { + dev_err(amp_group->cal_dev, + "No amp with suffix %s registered\n", mfd_suffix); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(cirrus_amp_add); + +int cirrus_amp_read_ctl(struct cirrus_amp *amp, const char *name, + int type, unsigned int id, unsigned int *value) +{ + struct wm_adsp *dsp = snd_soc_component_get_drvdata(amp->component); + unsigned int tmp; + int ret = 0, retry = CIRRUS_AMP_CTL_RETRY; + + if (amp->component) { + do { + ret = wm_adsp_read_ctl(dsp, name, type, id, (void *)&tmp, 4); + *value = (tmp & 0xff0000) >> 8 | + (tmp & 0xff00) << 8 | + (tmp & 0xff000000) >> 24; + if (ret) + dev_err(dsp->cs_dsp.dev, "%s: ret = %d\n", __func__, ret); + } while (ret != 0 && ret != -EINVAL && retry-- > 0); + } + + return ret; +} +EXPORT_SYMBOL_GPL(cirrus_amp_read_ctl); + +int cirrus_amp_write_ctl(struct cirrus_amp *amp, const char *name, + int type, unsigned int id, unsigned int value) +{ + struct wm_adsp *dsp = snd_soc_component_get_drvdata(amp->component); + unsigned int tmp; + int ret = 0, retry = CIRRUS_AMP_CTL_RETRY; + + tmp = (value & 0xff0000) >> 8 | + (value & 0xff00) << 8 | + (value & 0xff000000) >> 24 | + (value & 0xff) << 24; + + if (amp->component) { + do { + ret = wm_adsp_write_ctl(dsp, name, type, id, (void *)&tmp, 4); + if (ret && ret != -EINVAL) + dev_err(dsp->cs_dsp.dev, "%s: ret = %d\n", __func__, ret); + } while (ret != 0 && ret != -EINVAL && retry-- > 0); + } + + return ret; +} +EXPORT_SYMBOL_GPL(cirrus_amp_write_ctl); + +static const struct of_device_id cirrus_amp_of_match[] = { + { .compatible = "cirrus-amp", }, + {}, +}; +MODULE_DEVICE_TABLE(of, cirrus_amp_of_match); + +static int cirrus_amp_probe(struct platform_device *pdev) +{ + struct device_node *np; + struct device_node *amp_node; + const char **mfd_suffixes; + const char **bd_suffixes; + bool v_val_separate[CIRRUS_MAX_AMPS]; + int ret = 0, num = 0, num_amps, i, j; + + cirrus_amp_class = class_create(THIS_MODULE, "cirrus"); + if (IS_ERR(cirrus_amp_class)) { + ret = PTR_ERR(cirrus_amp_class); + pr_err("%s: Unable to register cirrus_amp class (%d)", + __func__, ret); + return ret; + } + + for_each_matching_node(np, cirrus_amp_of_match) + num++; + + if (num != 1) { + pr_info("%s: Exactly 1 OF entry is allowed (%d detected)\n", + __func__, num); + ret = -EINVAL; + goto class_err; + } + + np = of_find_matching_node(NULL, cirrus_amp_of_match); + if (!np) { + pr_err("%s: Device node required\n", __func__); + ret = -ENODEV; + goto class_err; + } + + num_amps = of_count_phandle_with_args(np, "cirrus,amps", NULL); + if (num_amps <= 0) { + pr_err("%s: Failed to parse 'cirrus,amps'\n", __func__); + ret = -ENODEV; + goto class_err; + } + + amp_group = kzalloc(sizeof(struct cirrus_amp_group) + + sizeof(struct cirrus_amp) * num_amps, + GFP_KERNEL); + mfd_suffixes = kcalloc(num_amps, sizeof(char *), GFP_KERNEL); + bd_suffixes = kcalloc(num_amps, sizeof(char *), GFP_KERNEL); + + amp_group->num_amps = num_amps; + + for (i = 0; i < num_amps; i++) { + amp_node = of_parse_phandle(np, "cirrus,amps", i); + if (IS_ERR(amp_node)) { + pr_err("%s: Failed to parse 'cirrus,amps' (%d)\n", + __func__, i); + ret = PTR_ERR(amp_node); + goto suffix_free; + } + + pr_debug("%s: Found linked amp: %s\n", __func__, + amp_node->full_name); + + ret = of_property_read_string(amp_node, "cirrus,mfd-suffix", + &mfd_suffixes[i]); + if (ret < 0) { + pr_err("%s: No MFD suffix found for amp: %s\n", + __func__, amp_node->full_name); + + of_node_put(amp_node); + goto suffix_free; + } + + ret = of_property_read_string(amp_node, "cirrus,bd-suffix", + &bd_suffixes[i]); + if (ret < 0) + pr_debug("%s: No BD suffix found for amp: %s\n", + __func__, amp_node->full_name); + + v_val_separate[i] = of_property_read_bool(amp_node, + "cirrus,v-val_separate"); + + of_node_put(amp_node); + } + + for (i = 0; i < num_amps; i++) { + for (j = 0; j < num_amps; j++) { + if (i == j) + continue; + + if (strcmp(mfd_suffixes[i], mfd_suffixes[j]) == 0) { + pr_err("%s: MFD suffixes must be unique\n", + __func__); + pr_err("%s: Found duplicate suffix: %s\n", + __func__, mfd_suffixes[i]); + + ret = -EINVAL; + goto suffix_err; + } + + /* bd_suffixes can be empty but must be unique */ + if (bd_suffixes[i] && bd_suffixes[j] && + (strcmp(bd_suffixes[i], bd_suffixes[j]) == 0)) { + pr_err("%s: BD suffixes must be unique\n", + __func__); + pr_err("%s: Found duplicate suffix: %s\n", + __func__, bd_suffixes[i]); + + ret = -EINVAL; + goto suffix_err; + } + } + + pr_info("%s: Found MFD suffix: %s\n", __func__, + mfd_suffixes[i]); + + amp_group->amps[i].mfd_suffix = + kstrdup(mfd_suffixes[i], GFP_KERNEL); + + if (bd_suffixes[i]) { + pr_info("%s: Found BD suffix: %s\n", __func__, + bd_suffixes[i]); + + amp_group->amps[i].bd.bd_suffix = + kstrdup(bd_suffixes[i], GFP_KERNEL); + } + + if (v_val_separate[i]) + amp_group->amps[i].v_val_separate = true; + } + + ret = cirrus_bd_init(); + if (ret < 0) { + pr_err("%s: Error in BD init (%d)\n", __func__, ret); + goto suffix_err; + } + + ret = cirrus_cal_init(); + if (ret < 0) { + pr_err("%s: Error in CAL init (%d)\n", __func__, ret); + cirrus_bd_exit(); + goto suffix_err; + } + + ret = cirrus_pwr_init(); + if (ret < 0) { + pr_err("%s: Error in PWR init (%d)\n", __func__, ret); + cirrus_bd_exit(); + cirrus_cal_exit(); + goto suffix_err; + } + + ret = 0; + + goto suffix_free; + +suffix_err: + for (i = 0; i < num_amps; i++) { + kfree(amp_group->amps[i].mfd_suffix); + kfree(amp_group->amps[i].bd.bd_suffix); + } + +suffix_free: + kfree(mfd_suffixes); + kfree(bd_suffixes); + + if (ret < 0) + kfree(amp_group); + +class_err: + if (ret < 0) + class_destroy(cirrus_amp_class); + + return ret; +} + +static int cirrus_amp_remove(struct platform_device *pdev) +{ + int i; + + cirrus_cal_exit(); + cirrus_bd_exit(); + cirrus_pwr_exit(); + + for (i = 0; i < amp_group->num_amps; i++) { + kfree(amp_group->amps[i].pre_config); + kfree(amp_group->amps[i].post_config); + } + + kfree(amp_group); + + class_destroy(cirrus_amp_class); + + return 0; +} + +static struct platform_driver cirrus_amp_driver = { + .driver = { + .name = "cirrus-amp", + .of_match_table = cirrus_amp_of_match, + }, + .probe = cirrus_amp_probe, + .remove = cirrus_amp_remove, +}; +module_platform_driver(cirrus_amp_driver); + +MODULE_AUTHOR("David Rhodes "); +MODULE_DESCRIPTION("Cirrus Amp driver"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/codecs/cirrus-bd.c b/sound/soc/codecs/cirrus-bd.c new file mode 100644 index 000000000000..23658121cb9a --- /dev/null +++ b/sound/soc/codecs/cirrus-bd.c @@ -0,0 +1,533 @@ +/* + * Big-data logging support for Cirrus Logic Smart Amplifiers + * + * Copyright 2017 Cirrus Logic + * + * Author: David Rhodes + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "wm_adsp.h" + +#define CIRRUS_BD_VERSION "5.01.18" +#define CIRRUS_BD_DIR_NAME "cirrus_bd" + +int cirrus_bd_write_ctl(struct cirrus_amp *amp, const char *name, + int type, unsigned int id, unsigned int value) +{ + int ret; + char *ctl_name = kzalloc(PAGE_SIZE, GFP_KERNEL); + + if (!ctl_name) + return -ENOMEM; + + snprintf(ctl_name, PAGE_SIZE, "%s%s", amp->bd.bd_prefix, name); + + ret = cirrus_amp_write_ctl(amp, ctl_name, type, id, value); + kfree(ctl_name); + + return ret; +} + +int cirrus_bd_read_ctl(struct cirrus_amp *amp, const char *name, + int type, unsigned int id, unsigned int *value) +{ + int ret; + char *ctl_name = kzalloc(PAGE_SIZE, GFP_KERNEL); + + if (!ctl_name) + return -ENOMEM; + + snprintf(ctl_name, PAGE_SIZE, "%s%s", amp->bd.bd_prefix, name); + + ret = cirrus_amp_read_ctl(amp, ctl_name, type, id, value); + kfree(ctl_name); + + return ret; +} + +void cirrus_bd_store_values(const char *mfd_suffix) +{ + unsigned int max_exc = 0, over_exc_count = 0, max_temp = 0, + over_temp_count = 0, abnm_mute = 0; + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(mfd_suffix); + + if (!amp) + return; + + if (amp->runtime_pm) + pm_runtime_get_sync(amp->component->dev); + + cirrus_bd_read_ctl(amp, "MAX_TEMP", WMFW_ADSP2_XM, + amp->bd.bd_alg_id, &max_temp); + cirrus_bd_read_ctl(amp, "MAX_EXC", WMFW_ADSP2_XM, + amp->bd.bd_alg_id, &max_exc); + cirrus_bd_read_ctl(amp, "OVER_TEMP_COUNT", WMFW_ADSP2_XM, + amp->bd.bd_alg_id, &over_temp_count); + cirrus_bd_read_ctl(amp, "OVER_EXC_COUNT", WMFW_ADSP2_XM, + amp->bd.bd_alg_id, &over_exc_count); + cirrus_bd_read_ctl(amp, "ABNORMAL_MUTE", WMFW_ADSP2_XM, + amp->bd.bd_alg_id, &abnm_mute); + + if (abnm_mute) + max_temp = CIRRUS_BD_ERR_TEMP; + else if (max_temp > (amp->bd.max_temp_limit * (1 << CIRRUS_BD_TEMP_RADIX)) + && over_temp_count == 0) + max_temp = (amp->bd.max_temp_limit * + (1 << CIRRUS_BD_TEMP_RADIX)); + + amp->bd.over_temp_count += over_temp_count; + amp->bd.over_exc_count += over_exc_count; + if (max_exc > amp->bd.max_exc) + amp->bd.max_exc = max_exc; + if (max_temp > amp->bd.max_temp) + amp->bd.max_temp = max_temp; + amp->bd.abnm_mute += abnm_mute; + + amp->bd.max_temp_keep = amp->bd.max_temp; + + amp_group->last_bd_update = ktime_to_ns(ktime_get()); + + dev_info(amp_group->bd_dev, "Values stored for amp%s:\n", mfd_suffix); + dev_info(amp_group->bd_dev, "Max Excursion:\t\t%d.%04d\n", + amp->bd.max_exc >> CIRRUS_BD_EXC_RADIX, + (amp->bd.max_exc & (((1 << CIRRUS_BD_EXC_RADIX) - 1))) * + 10000 / (1 << CIRRUS_BD_EXC_RADIX)); + dev_info(amp_group->bd_dev, "Over Excursion Count:\t%d\n", + amp->bd.over_exc_count); + dev_info(amp_group->bd_dev, "Max Temp:\t\t\t%d.%04d\n", + amp->bd.max_temp >> CIRRUS_BD_TEMP_RADIX, + (amp->bd.max_temp & (((1 << CIRRUS_BD_TEMP_RADIX) - 1))) * + 10000 / (1 << CIRRUS_BD_TEMP_RADIX)); + dev_info(amp_group->bd_dev, "Over Temp Count:\t\t%d\n", + amp->bd.over_temp_count); + dev_info(amp_group->bd_dev, "Abnormal Mute:\t\t%d\n", + amp->bd.abnm_mute); + dev_info(amp_group->bd_dev, "Timestamp:\t\t%llu\n", + amp_group->last_bd_update); + + cirrus_bd_write_ctl(amp, "MAX_TEMP", WMFW_ADSP2_XM, + amp->bd.bd_alg_id, 0); + cirrus_bd_write_ctl(amp, "MAX_EXC", WMFW_ADSP2_XM, + amp->bd.bd_alg_id, 0); + cirrus_bd_write_ctl(amp, "OVER_TEMP_COUNT", WMFW_ADSP2_XM, + amp->bd.bd_alg_id, 0); + cirrus_bd_write_ctl(amp, "OVER_EXC_COUNT", WMFW_ADSP2_XM, + amp->bd.bd_alg_id, 0); + cirrus_bd_write_ctl(amp, "ABNORMAL_MUTE", WMFW_ADSP2_XM, + amp->bd.bd_alg_id, 0); + + if (amp->runtime_pm) { + pm_runtime_mark_last_busy(amp->component->dev); + pm_runtime_put_autosuspend(amp->component->dev); + } +} +EXPORT_SYMBOL_GPL(cirrus_bd_store_values); + + +void cirrus_bd_amp_err(const char *mfd_suffix) +{ + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(mfd_suffix); + + if (!amp) + return; + + if (amp->error_callback) + amp->error_callback(mfd_suffix); +} +EXPORT_SYMBOL_GPL(cirrus_bd_amp_err); + +void cirrus_bd_bst_short(const char *mfd_suffix) +{ + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(mfd_suffix); + + if (!amp) + return; + + if (amp->error_callback) + amp->error_callback(mfd_suffix); +} +EXPORT_SYMBOL_GPL(cirrus_bd_bst_short); + +/***** SYSFS Interfaces *****/ + +static ssize_t cirrus_bd_version_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, CIRRUS_BD_VERSION "\n"); +} + +static ssize_t cirrus_bd_version_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + return 0; +} + +static ssize_t cirrus_bd_max_exc_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const char *suffix = &(attr->attr.name[strlen("max_exc")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + int ret; + + if (!amp) + return 0; + + ret = sprintf(buf, "%d.%04d\n", amp->bd.max_exc >> CIRRUS_BD_EXC_RADIX, + (amp->bd.max_exc & ((1 << CIRRUS_BD_EXC_RADIX) - 1)) * + 10000 / (1 << CIRRUS_BD_EXC_RADIX)); + + amp->bd.max_exc = 0; + + return ret; +} + +static ssize_t cirrus_bd_max_exc_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + return 0; +} + +static ssize_t cirrus_bd_over_exc_count_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const char *suffix = &(attr->attr.name[strlen("over_exc_count")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + int ret; + + if (!amp) + return 0; + + ret = sprintf(buf, "%d\n", amp->bd.over_exc_count); + + amp->bd.over_exc_count = 0; + + return ret; +} + +static ssize_t cirrus_bd_over_exc_count_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + return 0; +} + +static ssize_t cirrus_bd_max_temp_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const char *suffix = &(attr->attr.name[strlen("max_temp")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + int ret; + + if (!amp) + return 0; + + ret = sprintf(buf, "%d.%04d\n", + amp->bd.max_temp >> CIRRUS_BD_TEMP_RADIX, + (amp->bd.max_temp & ((1 << CIRRUS_BD_TEMP_RADIX) - 1)) * + 10000 / (1 << CIRRUS_BD_TEMP_RADIX)); + + amp->bd.max_temp = 0; + + return ret; +} + +static ssize_t cirrus_bd_max_temp_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + return 0; +} + +static ssize_t cirrus_bd_max_temp_keep_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const char *suffix = &(attr->attr.name[strlen("max_temp_keep")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + int ret; + + if (!amp) + return 0; + + ret = sprintf(buf, "%d.%04d\n", + amp->bd.max_temp_keep >> CIRRUS_BD_TEMP_RADIX, + (amp->bd.max_temp_keep & + ((1 << CIRRUS_BD_TEMP_RADIX) - 1)) * + 10000 / (1 << CIRRUS_BD_TEMP_RADIX)); + + return ret; +} + +static ssize_t cirrus_bd_max_temp_keep_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + return 0; +} + +static ssize_t cirrus_bd_over_temp_count_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const char *suffix = &(attr->attr.name[strlen("over_temp_count")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + int ret; + + if (!amp) + return 0; + + ret = sprintf(buf, "%d\n", amp->bd.over_temp_count); + + amp->bd.over_temp_count = 0; + + return ret; +} + +static ssize_t cirrus_bd_over_temp_count_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + return 0; +} + +static ssize_t cirrus_bd_abnm_mute_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const char *suffix = &(attr->attr.name[strlen("abnm_mute")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + int ret; + + if (!amp) + return 0; + + ret = sprintf(buf, "%d\n", amp->bd.abnm_mute); + + amp->bd.abnm_mute = 0; + + return ret; +} + +static ssize_t cirrus_bd_abnm_mute_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + return 0; +} + +static ssize_t cirrus_bd_store_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return 0; +} + +static ssize_t cirrus_bd_store_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int store; + int ret = kstrtos32(buf, 10, &store); + const char *suffix = &(attr->attr.name[strlen("store")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + + if (ret == 0 && store == 1 && amp) + cirrus_bd_store_values(suffix); + + return size; +} + +static DEVICE_ATTR(version, 0444, cirrus_bd_version_show, + cirrus_bd_version_store); + +static struct attribute *cirrus_bd_attr_base[] = { + &dev_attr_version.attr, + NULL, +}; + +static struct device_attribute generic_amp_attrs[CIRRUS_BD_NUM_ATTRS_AMP] = { + { + .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0444)}, + .show = cirrus_bd_max_exc_show, + .store = cirrus_bd_max_exc_store, + }, + { + .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0444)}, + .show = cirrus_bd_over_exc_count_show, + .store = cirrus_bd_over_exc_count_store, + }, + { + .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0444)}, + .show = cirrus_bd_max_temp_show, + .store = cirrus_bd_max_temp_store, + }, + { + .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0444)}, + .show = cirrus_bd_max_temp_keep_show, + .store = cirrus_bd_max_temp_keep_store, + }, + { + .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0444)}, + .show = cirrus_bd_over_temp_count_show, + .store = cirrus_bd_over_temp_count_store, + }, + { + .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0444)}, + .show = cirrus_bd_abnm_mute_show, + .store = cirrus_bd_abnm_mute_store, + }, + { + .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0644)}, + .show = cirrus_bd_store_show, + .store = cirrus_bd_store_store, + }, +}; + +static const char *generic_amp_attr_names[CIRRUS_BD_NUM_ATTRS_AMP] = { + "max_exc", + "over_exc_count", + "max_temp", + "max_temp_keep", + "over_temp_count", + "abnm_mute", + "store" +}; + +static struct attribute_group cirrus_bd_attr_grp; +static struct device_attribute + amp_attrs_prealloc[CIRRUS_MAX_AMPS][CIRRUS_BD_NUM_ATTRS_AMP]; +static char attr_names_prealloc[CIRRUS_MAX_AMPS][CIRRUS_BD_NUM_ATTRS_AMP][30]; + +struct device_attribute *cirrus_bd_create_amp_attrs(const char *mfd_suffix, + const char *bd_suffix, + int index) +{ + struct device_attribute *amp_attrs_new; + int i, suffix_len; + const char *suffix; + + suffix = (bd_suffix) ? bd_suffix : mfd_suffix; + suffix_len = strlen(suffix); + + if (index >= CIRRUS_MAX_AMPS) + return NULL; + + amp_attrs_new = &(amp_attrs_prealloc[index][0]); + + memcpy(amp_attrs_new, &generic_amp_attrs, + sizeof(struct device_attribute) * + CIRRUS_BD_NUM_ATTRS_AMP); + + for (i = 0; i < CIRRUS_BD_NUM_ATTRS_AMP - 1; i++) { + amp_attrs_new[i].attr.name = attr_names_prealloc[index][i]; + snprintf((char *)amp_attrs_new[i].attr.name, + strlen(generic_amp_attr_names[i]) + suffix_len + 1, + "%s%s", generic_amp_attr_names[i], suffix); + } + + /* "store" is special and will always be assigned the MFD suffix */ + amp_attrs_new[CIRRUS_BD_NUM_ATTRS_AMP - 1].attr.name = + attr_names_prealloc[index][CIRRUS_BD_NUM_ATTRS_AMP - 1]; + snprintf((char *)amp_attrs_new[CIRRUS_BD_NUM_ATTRS_AMP - 1].attr.name, + strlen("store") + strlen(mfd_suffix) + 1, + "%s%s", "store", mfd_suffix); + + return amp_attrs_new; +} + +int cirrus_bd_init(void) +{ + struct device_attribute *new_attrs; + struct cirrus_amp *amp; + int ret = 0, i, j, num_amps; + + if (!amp_group) { + pr_err("%s: Empty amp group\n", __func__); + return -ENODATA; + } + + amp_group->bd_dev = device_create(cirrus_amp_class, NULL, 1, NULL, + CIRRUS_BD_DIR_NAME); + if (IS_ERR(amp_group->bd_dev)) { + ret = PTR_ERR(amp_group->bd_dev); + pr_err("%s: Failed to create BD device (%d)\n", __func__, ret); + return ret; + } + + dev_set_drvdata(amp_group->bd_dev, amp_group); + + num_amps = amp_group->num_amps; + + cirrus_bd_attr_grp.attrs = kzalloc(sizeof(struct attribute *) * + (CIRRUS_BD_NUM_ATTRS_AMP * num_amps + + CIRRUS_BD_NUM_ATTRS_BASE + 1), + GFP_KERNEL); + for (i = 0; i < num_amps; i++) { + amp = &_group->amps[i]; + new_attrs = cirrus_bd_create_amp_attrs(amp->mfd_suffix, + amp->bd.bd_suffix, i); + for (j = 0; j < CIRRUS_BD_NUM_ATTRS_AMP; j++) { + dev_dbg(amp_group->bd_dev, "New attribute: %s\n", + new_attrs[j].attr.name); + cirrus_bd_attr_grp.attrs[i * CIRRUS_BD_NUM_ATTRS_AMP + + j] = &new_attrs[j].attr; + } + } + + memcpy(&cirrus_bd_attr_grp.attrs[num_amps * CIRRUS_BD_NUM_ATTRS_AMP], + cirrus_bd_attr_base, sizeof(struct attribute *) * + CIRRUS_BD_NUM_ATTRS_BASE); + cirrus_bd_attr_grp.attrs[num_amps * CIRRUS_BD_NUM_ATTRS_AMP + + CIRRUS_BD_NUM_ATTRS_BASE] = NULL; + + ret = sysfs_create_group(&_group->bd_dev->kobj, &cirrus_bd_attr_grp); + if (ret < 0) { + dev_err(amp_group->bd_dev, + "Failed to create sysfs group (%d)\n", ret); + device_del(amp_group->bd_dev); + } + + return ret; +} +EXPORT_SYMBOL_GPL(cirrus_bd_init); + +void cirrus_bd_exit(void) +{ + kfree(cirrus_bd_attr_grp.attrs); + device_del(amp_group->bd_dev); +} +EXPORT_SYMBOL_GPL(cirrus_bd_exit); diff --git a/sound/soc/codecs/cirrus-cal-cs35l43.c b/sound/soc/codecs/cirrus-cal-cs35l43.c new file mode 100644 index 000000000000..e64e12788617 --- /dev/null +++ b/sound/soc/codecs/cirrus-cal-cs35l43.c @@ -0,0 +1,738 @@ +/* + * Calibration support for CS35L43 Cirrus Logic Smart Amplifier + * + * Copyright 2021 Cirrus Logic + * + * Author: David Rhodes + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "wm_adsp.h" +#include "cs35l43.h" +#include +#include + +#define CIRRUS_CAL_COMPLETE_DELAY_MS 1250 +#define CIRRUS_CAL_RETRIES 2 +#define CIRRUS_CAL_AMBIENT_DEFAULT 23 + +#define CIRRUS_CAL_CS35L43_ALG_ID_PROT 0x5f210 +#define CIRRUS_CAL_CS35L43_ALG_ID_SURFACE 0x5f222 + +#define CIRRUS_CAL_CS35L43_TEMP_RADIX 14 +#define CIRRUS_CAL_CS35L43_SURFACE_TEMP_RADIX 16 + +static unsigned long long cirrus_cal_rdc_to_ohms(unsigned long rdc) +{ + return ((rdc * CIRRUS_CAL_AMP_CONSTANT_NUM) / + CIRRUS_CAL_AMP_CONSTANT_DENOM); +} + +static unsigned int cirrus_cal_vpk_to_mv(unsigned int vpk) +{ + return (vpk * CIRRUS_CAL_VFS_MV) >> 19; +} + +static int32_t cirrus_cal_sign_extend(uint32_t in) +{ + uint8_t shift = 8; + return (int32_t)(in << shift) >> shift; +} + +static int cirrus_cal_get_power_temp(void) +{ + union power_supply_propval value = {0}; + struct power_supply *psy; + int ret; + + psy = power_supply_get_by_name("battery"); + if (!psy) { + dev_warn(amp_group->cal_dev, + "failed to get battery, assuming %d\n", + CIRRUS_CAL_AMBIENT_DEFAULT); + return CIRRUS_CAL_AMBIENT_DEFAULT; + } + + ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_TEMP, &value); + if (ret) { + dev_warn(amp_group->cal_dev, + "failed to get battery temp prop (%d), assuming %d\n", + ret, CIRRUS_CAL_AMBIENT_DEFAULT); + return CIRRUS_CAL_AMBIENT_DEFAULT; + } + + return DIV_ROUND_CLOSEST(value.intval, 10); +} + +static int cirrus_cal_cs35l43_start(void); + +static void cirrus_cal_cs35l43_complete(void) +{ + struct cirrus_amp *amp; + struct reg_sequence *post_config; + struct regmap *regmap; + unsigned long long ohms; + int rdc, status, checksum, temp, cal_state, vsc, isc, i; + int delay = msecs_to_jiffies(CIRRUS_CAL_COMPLETE_DELAY_MS); + bool vsc_in_range, isc_in_range; + bool cal_retry = false; + + + dev_info(amp_group->cal_dev, "Complete Calibration\n"); + + mutex_lock(&_group->cal_lock); + + for (i = 0; i < amp_group->num_amps; i++) { + amp = &_group->amps[i]; + if (amp->calibration_disable) + continue; + + regmap = amp->regmap; + post_config = amp->post_config; + + cirrus_amp_read_ctl(amp, "CAL_STATUS", WMFW_ADSP2_XM, + CIRRUS_CAL_CS35L43_ALG_ID_PROT, &status); + cirrus_amp_read_ctl(amp, "CAL_R", WMFW_ADSP2_XM, + CIRRUS_CAL_CS35L43_ALG_ID_PROT, &rdc); + cirrus_amp_read_ctl(amp, "CAL_AMBIENT", WMFW_ADSP2_XM, + CIRRUS_CAL_CS35L43_ALG_ID_PROT, &temp); + cirrus_amp_read_ctl(amp, "CAL_CHECKSUM", WMFW_ADSP2_XM, + CIRRUS_CAL_CS35L43_ALG_ID_PROT, &checksum); + + ohms = cirrus_cal_rdc_to_ohms((unsigned long)rdc); + + if (amp->perform_vimon_cal) { + cirrus_amp_read_ctl(amp, "VIMON_CAL_VSC", WMFW_ADSP2_XM, + amp->vimon_alg_id, &vsc); + cirrus_amp_read_ctl(amp, "VIMON_CAL_ISC", WMFW_ADSP2_XM, + amp->vimon_alg_id, &isc); + cirrus_amp_read_ctl(amp, "VIMON_CAL_STATE", + WMFW_ADSP2_XM, amp->vimon_alg_id, + &cal_state); + if (cal_state == CIRRUS_CAL_VIMON_STATUS_INVALID || + cal_state == 0) { + dev_err(amp_group->cal_dev, + "Error during VIMON cal, invalidating results\n"); + rdc = status = checksum = 0; + } + + vsc_in_range = ((vsc <= amp->cal_vsc_ub) || + (vsc >= amp->cal_vsc_lb && vsc <= 0x00FFFFFF)); + + isc_in_range = ((isc <= amp->cal_isc_ub) || + (isc >= amp->cal_isc_lb && isc <= 0x00FFFFFF)); + + if (!vsc_in_range) + dev_err(amp_group->cal_dev, "VIMON Cal %s (%s): VSC out of range (%x)\n", + amp->dsp_part_name, amp->mfd_suffix, vsc); + if (!isc_in_range) + dev_err(amp_group->cal_dev, "VIMON Cal %s (%s): ISC out of range (%x)\n", + amp->dsp_part_name, amp->mfd_suffix, isc); + if (!vsc_in_range || !isc_in_range) { + dev_err(amp_group->cal_dev, "VIMON cal out of range, invalidating results\n"); + rdc = status = checksum = 0; + + cirrus_amp_write_ctl(amp, "VIMON_CAL_STATE", + WMFW_ADSP2_XM, amp->vimon_alg_id, + CIRRUS_CAL_VIMON_STATUS_INVALID); + + if (amp_group->cal_retry < CIRRUS_CAL_RETRIES) { + dev_info(amp_group->cal_dev, "Retry Calibration\n"); + cal_retry = true; + } + } + } else { + vsc = 0; + isc = 0; + cirrus_amp_write_ctl(amp, "VIMON_CAL_STATE", + WMFW_ADSP2_XM, amp->vimon_alg_id, + CIRRUS_CAL_VIMON_STATUS_INVALID); + } + + dev_info(amp_group->cal_dev, + "Calibration finished: %s (%s)\n", + amp->dsp_part_name, amp->mfd_suffix); + dev_info(amp_group->cal_dev, "Duration:\t%d ms\n", + CIRRUS_CAL_COMPLETE_DELAY_MS); + dev_info(amp_group->cal_dev, "Status:\t%d\n", status); + if (status == CSPL_STATUS_OUT_OF_RANGE) + dev_err(amp_group->cal_dev, + "Calibration out of range\n"); + if (status == CSPL_STATUS_INCOMPLETE) + dev_err(amp_group->cal_dev, "Calibration incomplete\n"); + dev_info(amp_group->cal_dev, "R :\t\t%d (%llu.%04llu Ohms)\n", + rdc, ohms >> CIRRUS_CAL_RDC_RADIX, + (ohms & (((1 << CIRRUS_CAL_RDC_RADIX) - 1))) * + 10000 / (1 << CIRRUS_CAL_RDC_RADIX)); + dev_info(amp_group->cal_dev, "Checksum:\t%d\n", checksum); + dev_info(amp_group->cal_dev, "Ambient:\t%d\n", temp); + + usleep_range(5000, 5500); + + cirrus_amp_write_ctl(amp, "CAL_EN", WMFW_ADSP2_XM, + CIRRUS_CAL_CS35L43_ALG_ID_PROT, 0); + cirrus_amp_write_ctl(amp, "PILOT_THRESH", WMFW_ADSP2_XM, + CIRRUS_CAL_CS35L43_ALG_ID_PROT, 0x611); + cirrus_amp_write_ctl(amp, "CAL_R_SEL", WMFW_ADSP2_XM, + CIRRUS_CAL_CS35L43_ALG_ID_PROT, 0xFFFFFF); + + regmap_write(amp->regmap, amp->mbox_cmd, CS35L43_MBOX_CMD_AUDIO_REINIT); + + regmap_multi_reg_write(regmap, post_config, + amp->num_post_configs); + + amp->cal.efs_cache_rdc = rdc; + amp->cal.efs_cache_vsc = vsc; + amp->cal.efs_cache_isc = isc; + amp_group->efs_cache_temp = temp; + amp->cal.efs_cache_valid = 1; + } + + if (cal_retry == true) { + cirrus_cal_cs35l43_start(); + queue_delayed_work(system_unbound_wq, + &_group->cal_complete_work, + delay); + amp_group->cal_retry++; + } else { + amp_group->cal_running = 0; + } + + dev_dbg(amp_group->cal_dev, "Calibration complete\n"); + mutex_unlock(&_group->cal_lock); +} + + +static void cirrus_cal_cs35l43_v_val_complete(struct cirrus_amp *amps, int num_amps, + bool separate) +{ + struct regmap *regmap; + struct reg_sequence *post_config; + unsigned int mbox_cmd; + int amp; + + + dev_info(amp_group->cal_dev, "Complete v-val\n"); + + for (amp = 0; amp < num_amps; amp++) { + if (amps[amp].v_val_separate && !separate) + continue; + + regmap = amps[amp].regmap; + post_config = amps[amp].post_config; + mbox_cmd = amps[amp].mbox_cmd; + + cirrus_amp_write_ctl(&s[amp], "CAL_EN", WMFW_ADSP2_XM, + CIRRUS_CAL_CS35L43_ALG_ID_PROT, 0); + cirrus_amp_write_ctl(&s[amp], "PILOT_THRESH", WMFW_ADSP2_XM, + CIRRUS_CAL_CS35L43_ALG_ID_PROT, 0x611); + cirrus_amp_write_ctl(&s[amp], "CAL_R_SEL", WMFW_ADSP2_XM, + CIRRUS_CAL_CS35L43_ALG_ID_PROT, 0xFFFFFF); + + regmap_write(regmap, mbox_cmd, CS35L43_MBOX_CMD_AUDIO_REINIT); + + regmap_multi_reg_write(regmap, post_config, + amps[amp].num_post_configs); + } + + dev_info(amp_group->cal_dev, "V validation complete\n"); +} + + +static int cirrus_cal_cs35l43_vimon_cal_start(struct cirrus_amp *amp) +{ + int ret = 0; + + ret = cirrus_amp_write_ctl(amp, "VIMON_LBST_DELY", WMFW_ADSP2_XM, + amp->vimon_alg_id, CIRRUS_CAL_CLASSH_DELAY_50MS); + if (ret < 0) { + dev_err(amp_group->cal_dev, "Could not access control VIMON_LBST_DELY\n"); + return ret; + } + + ret = cirrus_amp_write_ctl(amp, "VIMON_HBST_DELY", WMFW_ADSP2_XM, + amp->vimon_alg_id, CIRRUS_CAL_CLASSD_DELAY_50MS); + if (ret < 0) { + dev_err(amp_group->cal_dev, "Could not access control VIMON_HBST_DELY\n"); + return ret; + } + + regmap_write(amp->regmap, CS35L43_DSP1RX3_INPUT, CS35L43_INPUT_SRC_VBSTMON); + + cirrus_amp_write_ctl(amp, "VIMON_CAL_STATE", WMFW_ADSP2_XM, + amp->vimon_alg_id, 0); + + return ret; +} + +static int cirrus_cal_cs35l43_vimon_cal_complete(struct cirrus_amp *amp) +{ + unsigned int vimon_cal, vsc, isc, spkmon_otp; + bool vsc_in_range, isc_in_range; + + cirrus_amp_read_ctl(amp, "VIMON_CAL_STATE", WMFW_ADSP2_XM, + amp->vimon_alg_id, &vimon_cal); + cirrus_amp_read_ctl(amp, "VIMON_CAL_VSC", WMFW_ADSP2_XM, amp->vimon_alg_id, &vsc); + cirrus_amp_read_ctl(amp, "VIMON_CAL_ISC", WMFW_ADSP2_XM, amp->vimon_alg_id, &isc); + regmap_read(amp->regmap, CS35L43_SPKMON_OTP_3, &spkmon_otp); + + dev_info(amp_group->cal_dev, + "VIMON Cal results %s (%s), status=%d vsc=%x isc=%x\n", + amp->dsp_part_name, amp->mfd_suffix, vimon_cal, vsc, isc); + dev_info(amp_group->cal_dev, + "spkmon_otp_3=0x%x, vsc=0x%x, isc=0x%x\n", spkmon_otp, + (spkmon_otp & CS35L43_VMON_BST_COEFF_MASK) >> CS35L43_VMON_BST_COEFF_SHIFT, + (spkmon_otp & CS35L43_IMON_BST_COEFF_MASK) >> CS35L43_IMON_BST_COEFF_SHIFT); + + vsc_in_range = ((vsc <= amp->cal_vsc_ub) || + (vsc >= amp->cal_vsc_lb && vsc <= 0x00FFFFFF)); + + isc_in_range = ((isc <= amp->cal_isc_ub) || + (isc >= amp->cal_isc_lb && isc <= 0x00FFFFFF)); + + if (!vsc_in_range || !isc_in_range) + vimon_cal = CIRRUS_CAL_VIMON_STATUS_INVALID; + + return vimon_cal; +} + + +static int cirrus_cal_wait_for_active(struct cirrus_amp *amp) +{ + struct regmap *regmap = amp->regmap; + unsigned int global_en; + int timeout = 50; + + regmap_read(regmap, amp->global_en, &global_en); + + while ((global_en & amp->global_en_mask) == 0) { + usleep_range(1000, 1500); + regmap_read(regmap, amp->global_en, &global_en); + } + + if (timeout == 0) { + dev_err(amp_group->cal_dev, "Failed to setup calibration\n"); + return -EINVAL; + } + + return 0; +} + +static void cirrus_cal_cs35l43_redc_start(struct cirrus_amp *amp) +{ + int ambient; + + dev_info(amp_group->cal_dev, "ReDC Calibration\n"); + + cirrus_amp_write_ctl(amp, "CAL_EN", WMFW_ADSP2_XM, + CIRRUS_CAL_CS35L43_ALG_ID_PROT, 1); + cirrus_amp_write_ctl(amp, "PILOT_THRESH", WMFW_ADSP2_XM, + CIRRUS_CAL_CS35L43_ALG_ID_PROT, 0); + + cirrus_amp_write_ctl(amp, "CALIB_FIRST_RUN", WMFW_ADSP2_XM, + CIRRUS_CAL_CS35L43_ALG_ID_PROT, 1); + + ambient = cirrus_cal_get_power_temp(); + cirrus_amp_write_ctl(amp, "CAL_AMBIENT", WMFW_ADSP2_XM, + CIRRUS_CAL_CS35L43_ALG_ID_PROT, ambient); + + regmap_write(amp->regmap, amp->mbox_cmd, CS35L43_MBOX_CMD_AUDIO_REINIT); + +} + +int cirrus_cal_cs35l43_cal_apply(struct cirrus_amp *amp) +{ + unsigned int temp, rdc, status, checksum, vsc, isc; + unsigned int vimon_cal_status = CIRRUS_CAL_VIMON_STATUS_SUCCESS; + int ret = 0; + + if (!amp) + return 0; + + if (amp->cal.efs_cache_valid == 1) { + rdc = amp->cal.efs_cache_rdc; + vsc = amp->cal.efs_cache_vsc; + isc = amp->cal.efs_cache_isc; + vimon_cal_status = CIRRUS_CAL_VIMON_STATUS_SUCCESS; + temp = amp_group->efs_cache_temp; + } else if (amp->cal.efs_cache_rdc && amp_group->efs_cache_temp && + (!amp->cal.efs_cache_vsc && !amp->cal.efs_cache_isc)) { + dev_info(amp_group->cal_dev, + "No VIMON, writing RDC only\n"); + rdc = amp->cal.efs_cache_rdc; + temp = amp_group->efs_cache_temp; + vimon_cal_status = CIRRUS_CAL_VIMON_STATUS_INVALID; + } else { + + dev_info(amp_group->cal_dev, + "No saved EFS, writing defaults\n"); + rdc = amp->default_redc; + temp = CIRRUS_CAL_AMBIENT_DEFAULT; + vimon_cal_status = CIRRUS_CAL_VIMON_STATUS_INVALID; + amp->cal.efs_cache_rdc = rdc; + amp_group->efs_cache_temp = temp; + } + + status = 1; + checksum = status + rdc; + + dev_info(amp_group->cal_dev, "Writing calibration to %s (%s)\n", + amp->dsp_part_name, amp->mfd_suffix); + + dev_info(amp_group->cal_dev, + "RDC = %d, Temp = %d, Status = %d Checksum = %d\n", + rdc, temp, status, checksum); + + cirrus_amp_write_ctl(amp, "CAL_R_SEL", WMFW_ADSP2_XM, + CIRRUS_CAL_CS35L43_ALG_ID_PROT, rdc); + + if (!amp->perform_vimon_cal) { + cirrus_amp_write_ctl(amp, "VIMON_CAL_STATE", WMFW_ADSP2_XM, + amp->vimon_alg_id, + CIRRUS_CAL_VIMON_STATUS_INVALID); + goto skip_vimon_cal; + } + + cirrus_amp_write_ctl(amp, "VIMON_CAL_STATE", WMFW_ADSP2_XM, + amp->vimon_alg_id, vimon_cal_status); + + if (amp->perform_vimon_cal && + vimon_cal_status != CIRRUS_CAL_VIMON_STATUS_INVALID) { + dev_info(amp_group->cal_dev, + "VIMON Cal status=%d vsc=0x%x isc=0x%x\n", + vimon_cal_status, vsc, isc); + cirrus_amp_write_ctl(amp, "VIMON_CAL_VSC", WMFW_ADSP2_XM, + amp->vimon_alg_id, vsc); + cirrus_amp_write_ctl(amp, "VIMON_CAL_ISC", WMFW_ADSP2_XM, + amp->vimon_alg_id, isc); + regmap_write(amp->regmap, CS35L43_TEST_KEY_CTRL, 0x55); + regmap_write(amp->regmap, CS35L43_TEST_KEY_CTRL, 0xAA); + regmap_update_bits(amp->regmap, CS35L43_SPKMON_OTP_3, + CS35L43_VMON_BST_COEFF_MASK, + vsc << CS35L43_VMON_BST_COEFF_SHIFT); + regmap_update_bits(amp->regmap, CS35L43_SPKMON_OTP_3, + CS35L43_IMON_BST_COEFF_MASK, + isc << CS35L43_IMON_BST_COEFF_SHIFT); + regmap_write(amp->regmap, CS35L43_TEST_KEY_CTRL, 0xCC); + regmap_write(amp->regmap, CS35L43_TEST_KEY_CTRL, 0x33); + } else { + dev_info(amp_group->cal_dev, "VIMON Cal status invalid\n"); + } + +skip_vimon_cal: + return ret; +} + +int cirrus_cal_cs35l43_read_temp(struct cirrus_amp *amp) +{ + int reg = 0, ret; + struct wm_adsp *adsp; + unsigned int global_en; + unsigned int halo_alg_id; + + if (!amp) + goto err; + + if (pm_runtime_enabled(amp->component->dev) && + pm_runtime_status_suspended(amp->component->dev)) + goto err; + + regmap_read(amp->regmap, amp->global_en, &global_en); + + if ((global_en & amp->global_en_mask) == 0) + goto err; + + if (amp_group->cal_running) + goto err; + + adsp = snd_soc_component_get_drvdata(amp->component); + halo_alg_id = adsp->cs_dsp.fw_id; + + ret = cirrus_amp_read_ctl(amp, "HALO_HEARTBEAT", WMFW_ADSP2_XM, + halo_alg_id, ®); + + if (reg == 0 || ret < 0) + goto err; + + ret = cirrus_amp_read_ctl(amp, "AUDIO_STATE", WMFW_ADSP2_XM, + 0x5f212, ®); + + if (reg == 0 || ret < 0) + goto err; + + ret = cirrus_amp_read_ctl(amp, "TEMP_COIL_C", WMFW_ADSP2_XM, + CIRRUS_CAL_CS35L43_ALG_ID_PROT, ®); + + reg = cirrus_cal_sign_extend(reg); + + if (ret == 0) { + dev_info(amp_group->cal_dev, + "Read temp: %d.%04d degrees C\n", + reg >> CIRRUS_CAL_CS35L43_TEMP_RADIX, + (reg & (((1 << CIRRUS_CAL_CS35L43_TEMP_RADIX) - 1))) * + 10000 / (1 << CIRRUS_CAL_CS35L43_TEMP_RADIX)); + return (reg >> CIRRUS_CAL_CS35L43_TEMP_RADIX); + } +err: + return -1; +} + +int cirrus_cal_cs35l43_set_surface_temp(struct cirrus_amp *amp, int temperature) +{ + unsigned int global_en; + + if (!amp) + return -EINVAL; + + if (pm_runtime_enabled(amp->component->dev) && + pm_runtime_status_suspended(amp->component->dev)) + return -EINVAL; + + regmap_read(amp->regmap, amp->global_en, &global_en); + + if ((global_en & amp->global_en_mask) == 0) + return -EINVAL; + + if (temperature < 0) { + dev_info(amp->component->dev, "Input surface temp: %d degrees\n", temperature); + temperature = 0; + } + + dev_info(amp->component->dev, "Set surface temp: %d degrees\n", temperature); + cirrus_amp_write_ctl(amp, "SURFACE_TEMP", WMFW_ADSP2_XM, + CIRRUS_CAL_CS35L43_ALG_ID_SURFACE, + temperature << CIRRUS_CAL_CS35L43_SURFACE_TEMP_RADIX); + return 0; +} + +static int cirrus_cal_cs35l43_start(void) +{ + bool vimon_calibration_failed = false; + int amp; + struct reg_sequence *config; + struct regmap *regmap; + int ret, vimon_cal_retries = 5; + + for (amp = 0; amp < amp_group->num_amps; amp++) { + if (amp_group->amps[amp].calibration_disable) + continue; + + cirrus_amp_write_ctl(&_group->amps[amp], "CAL_STATUS", + WMFW_ADSP2_XM, CIRRUS_CAL_CS35L43_ALG_ID_PROT, 0); + cirrus_amp_write_ctl(&_group->amps[amp], "CAL_R", + WMFW_ADSP2_XM, CIRRUS_CAL_CS35L43_ALG_ID_PROT, 0); + cirrus_amp_write_ctl(&_group->amps[amp], "CAL_AMBIENT", + WMFW_ADSP2_XM, CIRRUS_CAL_CS35L43_ALG_ID_PROT, 0); + cirrus_amp_write_ctl(&_group->amps[amp], "CAL_CHECKSUM", + WMFW_ADSP2_XM, CIRRUS_CAL_CS35L43_ALG_ID_PROT, 0); + if (amp_group->amps[amp].perform_vimon_cal) { + cirrus_amp_write_ctl(&_group->amps[amp], "VIMON_CAL_VSC", + WMFW_ADSP2_XM, + amp_group->amps[amp].vimon_alg_id, 0); + cirrus_amp_write_ctl(&_group->amps[amp], "VIMON_CAL_ISC", + WMFW_ADSP2_XM, + amp_group->amps[amp].vimon_alg_id, 0); + } + + ret = cirrus_cal_wait_for_active(&_group->amps[amp]); + if (ret < 0) { + dev_err(amp_group->cal_dev, + "Could not start amp%s (%d)\n", + amp_group->amps[amp].mfd_suffix, ret); + return -ETIMEDOUT; + } + } + + do { + vimon_calibration_failed = false; + + for (amp = 0; amp < amp_group->num_amps; amp++) { + if (amp_group->amps[amp].calibration_disable || + vimon_calibration_failed) + continue; + + regmap = amp_group->amps[amp].regmap; + config = amp_group->amps[amp].pre_config; + + regmap_multi_reg_write(regmap, config, + amp_group->amps[amp].num_pre_configs); + if (amp_group->amps[amp].perform_vimon_cal) { + ret = cirrus_cal_cs35l43_vimon_cal_start(&_group->amps[amp]); + if (ret < 0) { + dev_info(amp_group->cal_dev, + "VIMON Calibration Start Error %s (%s)\n", + amp_group->amps[amp].dsp_part_name, + amp_group->amps[amp].mfd_suffix); + vimon_calibration_failed = true; + vimon_cal_retries = -1; + } + } + } + + msleep(300); + + for (amp = 0; amp < amp_group->num_amps; amp++) { + if (amp_group->amps[amp].calibration_disable || + vimon_calibration_failed) + continue; + + if (amp_group->amps[amp].perform_vimon_cal) { + ret = cirrus_cal_cs35l43_vimon_cal_complete( + &_group->amps[amp]); + + if (ret != CIRRUS_CAL_VIMON_STATUS_SUCCESS) { + vimon_calibration_failed = true; + dev_info(amp_group->cal_dev, + "VIMON Calibration Error %s (%s)\n", + amp_group->amps[amp].dsp_part_name, + amp_group->amps[amp].mfd_suffix); + } + } + } + + vimon_cal_retries--; + } while (vimon_cal_retries >= 0 && vimon_calibration_failed); + + for (amp = 0; amp < amp_group->num_amps; amp++) { + if (amp_group->amps[amp].calibration_disable) + continue; + + cirrus_cal_cs35l43_redc_start(&_group->amps[amp]); + } + + return 0; +} + +int cirrus_cal_cs35l43_v_val_start(struct cirrus_amp *amps, int num_amps, bool separate) +{ + struct reg_sequence *config; + struct regmap *regmap; + unsigned int vmax[CIRRUS_MAX_AMPS] = {0, 0, 0, 0, 0, 0, 0, 0}; + unsigned int vmin[CIRRUS_MAX_AMPS] = {0, 0, 0, 0, 0, 0, 0, 0}; + int ret = 0, i, j, reg, count; + + for (i = 0; i < amp_group->num_amps; i++) { + if (amps[i].v_val_separate && !separate) + continue; + + regmap = amps[i].regmap; + config = amps[i].pre_config; + + vmax[i] = 0; + vmin[i] = INT_MAX; + + ret = cirrus_cal_wait_for_active(&s[i]); + if (ret < 0) { + dev_err(amp_group->cal_dev, + "Could not start amp%s\n", + amps[i].mfd_suffix); + goto err; + } + + regmap_multi_reg_write(regmap, config, + amps[i].num_pre_configs); + cirrus_cal_cs35l43_redc_start(&s[i]); + + } + + dev_info(amp_group->cal_dev, "V validation prepare complete\n"); + + for (i = 0; i < 1000; i++) { + count = 0; + for (j = 0; j < num_amps; j++) { + if (amps[j].v_val_separate && !separate) + continue; + + cirrus_amp_read_ctl(&s[j], "CAL_VOLTAGE_PEAK", WMFW_ADSP2_XM, + CIRRUS_CAL_CS35L43_ALG_ID_PROT, ®); + if (reg > vmax[j]) + vmax[j] = reg; + if (reg < vmin[j]) + vmin[j] = reg; + + cirrus_amp_read_ctl(&s[j], "CAL_STATUS", + WMFW_ADSP2_XM, + CIRRUS_CAL_CS35L43_ALG_ID_PROT, ®); + + if (reg != 0 && reg != CSPL_STATUS_INCOMPLETE) + count++; + } + + if (count == num_amps) { + dev_info(amp_group->cal_dev, + "V Validation complete (%d)\n", i); + break; + } + } + + for (i = 0; i < num_amps; i++) { + if (amps[i].v_val_separate && !separate) + continue; + + dev_info(amp_group->cal_dev, + "V Validation results for amp%s\n", + amps[i].mfd_suffix); + + dev_dbg(amp_group->cal_dev, "V Max: 0x%x\n", vmax[i]); + vmax[i] = cirrus_cal_vpk_to_mv(vmax[i]); + dev_info(amp_group->cal_dev, "V Max: %d mV\n", vmax[i]); + + dev_dbg(amp_group->cal_dev, "V Min: 0x%x\n", vmin[i]); + vmin[i] = cirrus_cal_vpk_to_mv(vmin[i]); + dev_info(amp_group->cal_dev, "V Min: %d mV\n", vmin[i]); + + if (vmax[i] < CIRRUS_CAL_V_VAL_UB_MV && + vmax[i] > CIRRUS_CAL_V_VAL_LB_MV) { + amps[i].cal.v_validation = 1; + dev_info(amp_group->cal_dev, + "V validation success\n"); + } else { + amps[i].cal.v_validation = 0xCC; + dev_err(amp_group->cal_dev, + "V validation failed\n"); + } + + } + + cirrus_cal_cs35l43_v_val_complete(amps, num_amps, separate); + return 0; + +err: + return -1; +} + +struct cirrus_cal_ops cirrus_cs35l43_cal_ops = { + .controls = { + { "CAL_R", CIRRUS_CAL_CS35L43_ALG_ID_PROT}, + { "CAL_CHECKSUM", CIRRUS_CAL_CS35L43_ALG_ID_PROT}, + { "CAL_R_INIT", CIRRUS_CAL_CS35L43_ALG_ID_PROT}, + }, + .cal_start = cirrus_cal_cs35l43_start, + .cal_complete = cirrus_cal_cs35l43_complete, + .v_val = cirrus_cal_cs35l43_v_val_start, + .cal_apply = cirrus_cal_cs35l43_cal_apply, + .read_temp = cirrus_cal_cs35l43_read_temp, + .set_temp = cirrus_cal_cs35l43_set_surface_temp, +}; diff --git a/sound/soc/codecs/cirrus-cal-cspl.c b/sound/soc/codecs/cirrus-cal-cspl.c new file mode 100644 index 000000000000..6f6767b6d0db --- /dev/null +++ b/sound/soc/codecs/cirrus-cal-cspl.c @@ -0,0 +1,942 @@ +/* + * Calibration support for Cirrus Logic Smart Amplifiers + * + * Copyright 2021 Cirrus Logic + * + * Author: David Rhodes + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include "wm_adsp.h" + + +#define CIRRUS_CAL_COMPLETE_DELAY_MS 1250 +#define CIRRUS_CAL_RETRIES 2 +#define CIRRUS_CAL_AMBIENT_DEFAULT 23 + +#define CIRRUS_CAL_CONFIG_FILENAME_SUFFIX "-dsp1-spk-prot-calib.bin" +#define CIRRUS_CAL_PLAYBACK_FILENAME_SUFFIX "-dsp1-spk-prot.bin" + +enum cirrus_cspl_mboxstate { + CSPL_MBOX_STS_RUNNING = 0, + CSPL_MBOX_STS_PAUSED = 1, + CSPL_MBOX_STS_RDY_FOR_REINIT = 2, +}; + +enum cirrus_cspl_mboxcmd { + CSPL_MBOX_CMD_NONE = 0, + CSPL_MBOX_CMD_PAUSE = 1, + CSPL_MBOX_CMD_RESUME = 2, + CSPL_MBOX_CMD_REINIT = 3, + CSPL_MBOX_CMD_STOP_PRE_REINIT = 4, + CSPL_MBOX_CMD_UNKNOWN_CMD = -1, + CSPL_MBOX_CMD_INVALID_SEQUENCE = -2, +}; + +static unsigned long long cirrus_cal_rdc_to_ohms(unsigned long rdc) +{ + return ((rdc * CIRRUS_CAL_AMP_CONSTANT_NUM) / + CIRRUS_CAL_AMP_CONSTANT_DENOM); +} + +static unsigned int cirrus_cal_vpk_to_mv(unsigned int vpk) +{ + return (vpk * CIRRUS_CAL_VFS_MV) >> 19; +} + +static int32_t cirrus_cal_sign_extend(uint32_t in) +{ + uint8_t shift = 8; + + return (int32_t)(in << shift) >> shift; +} + +int cirrus_cal_logger_get_variable(struct cirrus_amp *amp, unsigned int id, + unsigned int *result) +{ + unsigned int state; + int retries = 100; + + cirrus_amp_write_ctl(amp, "RTLOG_COUNT", WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, 0); + cirrus_amp_write_ctl(amp, "RTLOG_VARIABLE", WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, id); + cirrus_amp_write_ctl(amp, "RTLOG_COUNT", WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, 1); + cirrus_amp_write_ctl(amp, "RTLOG_STATE", WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, 0); + cirrus_amp_write_ctl(amp, "RTLOG_ENABLE", WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, 1); + + do { + usleep_range(20, 50); + cirrus_amp_read_ctl(amp, "RTLOG_STATE", WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, &state); + } while (state == 0 && --retries > 0); + + if (retries == 0) { + dev_err(amp_group->cal_dev, "variable read failed\n"); + return -1; + } + + cirrus_amp_read_ctl(amp, "RTLOG_DATA", WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, result); + cirrus_amp_write_ctl(amp, "RTLOG_ENABLE", WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, 0); + + return 0; +} + +static int cirrus_cal_load_config(const char *file, struct cirrus_amp *amp) +{ + struct wm_adsp *dsp = snd_soc_component_get_drvdata(amp->component); + const struct firmware *firmware; + int ret; + + ret = request_firmware(&firmware, file, amp->component->dev); + if (ret != 0) { + dev_err(amp_group->cal_dev, "Failed to request '%s'\n", file); + return -EINVAL; + } + + ret = cs_dsp_load_coeff(&dsp->cs_dsp, firmware, file); + + return ret; +} + +static int cirrus_cal_get_power_temp(void) +{ + union power_supply_propval value = {0}; + struct power_supply *psy; + int ret; + + psy = power_supply_get_by_name("battery"); + if (!psy) { + dev_warn(amp_group->cal_dev, + "failed to get battery, assuming %d\n", + CIRRUS_CAL_AMBIENT_DEFAULT); + return CIRRUS_CAL_AMBIENT_DEFAULT; + } + + ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_TEMP, &value); + if (ret) { + dev_warn(amp_group->cal_dev, + "failed to get battery temp prop (%d), assuming %d\n", + ret, CIRRUS_CAL_AMBIENT_DEFAULT); + return CIRRUS_CAL_AMBIENT_DEFAULT; + } + + return DIV_ROUND_CLOSEST(value.intval, 10); +} + +static int cirrus_cal_cspl_start(void); + +static void cirrus_cal_cspl_complete(void) +{ + struct cirrus_amp *amp; + struct reg_sequence *post_config; + struct regmap *regmap; + const char *dsp_part_name; + char *playback_config_filename; + unsigned long long ohms; + unsigned int cal_state, mbox_cmd, mbox_sts; + int rdc, status, checksum, temp, vsc, isc, timeout = 100, i; + int delay = msecs_to_jiffies(CIRRUS_CAL_COMPLETE_DELAY_MS); + bool vsc_in_range, isc_in_range; + bool cal_retry = false; + + mutex_lock(&_group->cal_lock); + + for (i = 0; i < amp_group->num_amps; i++) { + amp = &_group->amps[i]; + if (amp->calibration_disable) + continue; + + regmap = amp->regmap; + dsp_part_name = amp->dsp_part_name; + post_config = amp->post_config; + mbox_cmd = amp->mbox_cmd; + mbox_sts = amp->mbox_sts; + + playback_config_filename = kzalloc(PAGE_SIZE, GFP_KERNEL); + snprintf(playback_config_filename, PAGE_SIZE, "%s%s", + dsp_part_name, CIRRUS_CAL_PLAYBACK_FILENAME_SUFFIX); + + cirrus_amp_read_ctl(amp, "CAL_STATUS", WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, &status); + cirrus_amp_read_ctl(amp, "CAL_R", WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, &rdc); + cirrus_amp_read_ctl(amp, "CAL_AMBIENT", WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, &temp); + cirrus_amp_read_ctl(amp, "CAL_CHECKSUM", WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, &checksum); + + ohms = cirrus_cal_rdc_to_ohms((unsigned long)rdc); + + cirrus_amp_read_ctl(amp, "CSPL_STATE", WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, &cal_state); + if (cal_state == CSPL_STATE_ERROR) { + dev_err(amp_group->cal_dev, + "Error during ReDC cal, invalidating results\n"); + rdc = status = checksum = 0; + } + + if (amp->perform_vimon_cal) { + cirrus_amp_read_ctl(amp, "VSC", WMFW_ADSP2_XM, + amp->vimon_alg_id, &vsc); + cirrus_amp_read_ctl(amp, "ISC", WMFW_ADSP2_XM, + amp->vimon_alg_id, &isc); + cirrus_amp_read_ctl(amp, "VIMON_CAL_STATE", + WMFW_ADSP2_XM, amp->vimon_alg_id, + &cal_state); + if (cal_state == CIRRUS_CAL_VIMON_STATUS_INVALID || + cal_state == 0) { + dev_err(amp_group->cal_dev, + "Error during VIMON cal, invalidating results\n"); + rdc = status = checksum = 0; + } + + vsc_in_range = ((vsc <= amp->cal_vsc_ub) || + (vsc >= amp->cal_vsc_lb && vsc <= 0x00FFFFFF)); + + isc_in_range = ((isc <= amp->cal_isc_ub) || + (isc >= amp->cal_isc_lb && isc <= 0x00FFFFFF)); + + if (!vsc_in_range) + dev_err(amp_group->cal_dev, "VIMON Cal %s (%s): VSC out of range (%x)\n", + amp->dsp_part_name, amp->mfd_suffix, vsc); + if (!isc_in_range) + dev_err(amp_group->cal_dev, "VIMON Cal %s (%s): ISC out of range (%x)\n", + amp->dsp_part_name, amp->mfd_suffix, isc); + if (!vsc_in_range || !isc_in_range) { + dev_err(amp_group->cal_dev, "VIMON cal out of range, invalidating results\n"); + rdc = status = checksum = 0; + + cirrus_amp_write_ctl(amp, "VIMON_CAL_STATE", + WMFW_ADSP2_XM, amp->vimon_alg_id, + CIRRUS_CAL_VIMON_STATUS_INVALID); + + if (amp_group->cal_retry < CIRRUS_CAL_RETRIES) { + dev_info(amp_group->cal_dev, "Retry Calibration\n"); + cal_retry = true; + } + } + } else { + vsc = 0; + isc = 0; + cirrus_amp_write_ctl(amp, "VIMON_CAL_STATE", + WMFW_ADSP2_XM, amp->vimon_alg_id, + CIRRUS_CAL_VIMON_STATUS_INVALID); + } + + dev_info(amp_group->cal_dev, + "Calibration finished: %s (%s)\n", + amp->dsp_part_name, amp->mfd_suffix); + dev_info(amp_group->cal_dev, "Duration:\t%d ms\n", + CIRRUS_CAL_COMPLETE_DELAY_MS); + dev_info(amp_group->cal_dev, "Status:\t%d\n", status); + if (status == CSPL_STATUS_OUT_OF_RANGE) + dev_err(amp_group->cal_dev, + "Calibration out of range\n"); + if (status == CSPL_STATUS_INCOMPLETE) + dev_err(amp_group->cal_dev, "Calibration incomplete\n"); + dev_info(amp_group->cal_dev, "R :\t\t%d (%llu.%04llu Ohms)\n", + rdc, ohms >> CIRRUS_CAL_RDC_RADIX, + (ohms & (((1 << CIRRUS_CAL_RDC_RADIX) - 1))) * + 10000 / (1 << CIRRUS_CAL_RDC_RADIX)); + dev_info(amp_group->cal_dev, "Checksum:\t%d\n", checksum); + dev_info(amp_group->cal_dev, "Ambient:\t%d\n", temp); + + usleep_range(5000, 5500); + + /* Send STOP_PRE_REINIT command and poll for response */ + regmap_write(regmap, mbox_cmd, CSPL_MBOX_CMD_STOP_PRE_REINIT); + timeout = 100; + do { + dev_info(amp_group->cal_dev, + "waiting for REINIT ready...\n"); + usleep_range(1000, 1500); + regmap_read(regmap, mbox_sts, &cal_state); + } while ((cal_state != CSPL_MBOX_STS_RDY_FOR_REINIT) && + --timeout > 0); + + msleep(100); + + cirrus_cal_load_config(playback_config_filename, amp); + + cirrus_amp_write_ctl(amp, "CAL_STATUS", WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, status); + cirrus_amp_write_ctl(amp, "CAL_R", WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, rdc); + cirrus_amp_write_ctl(amp, "CAL_AMBIENT", WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, temp); + cirrus_amp_write_ctl(amp, "CAL_CHECKSUM", WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, checksum); + + /* Send REINIT command and poll for response */ + regmap_write(regmap, mbox_cmd, CSPL_MBOX_CMD_REINIT); + timeout = 100; + do { + dev_info(amp_group->cal_dev, + "waiting for REINIT done...\n"); + usleep_range(1000, 1500); + regmap_read(regmap, mbox_sts, &cal_state); + + } while ((cal_state != CSPL_MBOX_STS_RUNNING) && + --timeout > 0); + + msleep(100); + + cirrus_amp_read_ctl(amp, "CSPL_STATE", WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, &cal_state); + if (cal_state == CSPL_STATE_ERROR) + dev_err(amp_group->cal_dev, + "Playback config load error\n"); + + regmap_multi_reg_write(regmap, post_config, + amp->num_post_configs); + + amp->cal.efs_cache_rdc = rdc; + amp->cal.efs_cache_vsc = vsc; + amp->cal.efs_cache_isc = isc; + amp_group->efs_cache_temp = temp; + amp->cal.efs_cache_valid = 1; + + kfree(playback_config_filename); + } + + if (cal_retry == true) { + cirrus_cal_cspl_start(); + queue_delayed_work(system_unbound_wq, + &_group->cal_complete_work, + delay); + amp_group->cal_retry++; + } else { + amp_group->cal_running = 0; + } + + dev_dbg(amp_group->cal_dev, "Calibration complete\n"); + mutex_unlock(&_group->cal_lock); +} + + +static void cirrus_cal_v_val_complete(struct cirrus_amp *amps, int num_amps, + bool separate) +{ + struct regmap *regmap; + struct reg_sequence *post_config; + const char *dsp_part_name; + char *playback_config_filename; + unsigned int mbox_cmd, mbox_sts, cal_state; + int timeout = 100, amp; + + for (amp = 0; amp < num_amps; amp++) { + if (amps[amp].v_val_separate && !separate) + continue; + + regmap = amps[amp].regmap; + dsp_part_name = amps[amp].dsp_part_name; + post_config = amps[amp].post_config; + mbox_cmd = amps[amp].mbox_cmd; + mbox_sts = amps[amp].mbox_sts; + + playback_config_filename = kzalloc(PAGE_SIZE, GFP_KERNEL); + snprintf(playback_config_filename, PAGE_SIZE, "%s%s", + dsp_part_name, CIRRUS_CAL_PLAYBACK_FILENAME_SUFFIX); + + /* Send STOP_PRE_REINIT command and poll for response */ + regmap_write(regmap, mbox_cmd, CSPL_MBOX_CMD_STOP_PRE_REINIT); + timeout = 100; + do { + dev_info(amp_group->cal_dev, + "waiting for REINIT ready...\n"); + usleep_range(1000, 1500); + regmap_read(regmap, mbox_sts, &cal_state); + + } while ((cal_state != CSPL_MBOX_STS_RDY_FOR_REINIT) && + --timeout > 0); + + msleep(100); + + cirrus_cal_load_config(playback_config_filename, + &_group->amps[amp]); + + /* Send REINIT command and poll for response */ + regmap_write(regmap, mbox_cmd, CSPL_MBOX_CMD_REINIT); + + timeout = 100; + do { + dev_info(amp_group->cal_dev, + "waiting for REINIT done...\n"); + usleep_range(1000, 1500); + regmap_read(regmap, mbox_sts, &cal_state); + + } while ((cal_state != CSPL_MBOX_STS_RUNNING) && + --timeout > 0); + + msleep(100); + + cirrus_amp_read_ctl(&_group->amps[amp], "CSPL_STATE", + WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, + &cal_state); + if (cal_state == CSPL_STATE_ERROR) + dev_err(amp_group->cal_dev, + "Playback config load error\n"); + + regmap_multi_reg_write(regmap, post_config, + amps[amp].num_post_configs); + + kfree(playback_config_filename); + } + + dev_info(amp_group->cal_dev, "V validation complete\n"); +} + + +static void cirrus_cal_cspl_vimon_cal_start(struct cirrus_amp *amp) +{ + + cirrus_amp_write_ctl(amp, "VIMON_CLASS_H_CAL_DELAY", WMFW_ADSP2_XM, + amp->vimon_alg_id, CIRRUS_CAL_CLASSH_DELAY_50MS); + cirrus_amp_write_ctl(amp, "VIMON_CLASS_D_CAL_DELAY", WMFW_ADSP2_XM, + amp->vimon_alg_id, CIRRUS_CAL_CLASSD_DELAY_50MS); + + cirrus_amp_write_ctl(amp, "VIMON_CAL_STATE", WMFW_ADSP2_XM, + amp->vimon_alg_id, 0); + cirrus_amp_write_ctl(amp, "HALO_HEARTBEAT", WMFW_ADSP2_XM, + amp->halo_alg_id, 0); +} + +static int cirrus_cal_cspl_vimon_cal_complete(struct cirrus_amp *amp) +{ + unsigned int vimon_cal, vsc, isc; + bool vsc_in_range, isc_in_range; + + cirrus_amp_read_ctl(amp, "VIMON_CAL_STATE", WMFW_ADSP2_XM, + amp->vimon_alg_id, &vimon_cal); + cirrus_amp_read_ctl(amp, "VSC", WMFW_ADSP2_XM, amp->vimon_alg_id, &vsc); + cirrus_amp_read_ctl(amp, "ISC", WMFW_ADSP2_XM, amp->vimon_alg_id, &isc); + + dev_info(amp_group->cal_dev, + "VIMON Cal results %s (%s), status=%d vsc=%x isc=%x\n", + amp->dsp_part_name, amp->mfd_suffix, vimon_cal, vsc, isc); + + vsc_in_range = ((vsc <= amp->cal_vsc_ub) || + (vsc >= amp->cal_vsc_lb && vsc <= 0x00FFFFFF)); + + isc_in_range = ((isc <= amp->cal_isc_ub) || + (isc >= amp->cal_isc_lb && isc <= 0x00FFFFFF)); + + if (!vsc_in_range || !isc_in_range) + vimon_cal = CIRRUS_CAL_VIMON_STATUS_INVALID; + + return vimon_cal; +} + + +static int cirrus_cal_wait_for_active(struct cirrus_amp *amp) +{ + struct regmap *regmap = amp->regmap; + unsigned int global_en; + unsigned int halo_state; + int timeout = 50; + + regmap_read(regmap, amp->global_en, &global_en); + + while ((global_en & amp->global_en_mask) == 0) { + usleep_range(1000, 1500); + regmap_read(regmap, amp->global_en, &global_en); + } + + do { + dev_info(amp_group->cal_dev, "waiting for HALO start...\n"); + + usleep_range(16000, 16100); + + cirrus_amp_read_ctl(amp, "HALO_STATE", WMFW_ADSP2_XM, + amp->halo_alg_id, &halo_state); + timeout--; + } while ((halo_state == 0) && timeout > 0); + + if (timeout == 0) { + dev_err(amp_group->cal_dev, "Failed to setup calibration\n"); + return -EINVAL; + } + + return 0; +} + +static void cirrus_cal_cspl_redc_start(struct cirrus_amp *amp) +{ + struct regmap *regmap = amp->regmap; + const char *dsp_part_name = amp->dsp_part_name; + char *cal_config_filename; + unsigned int halo_state; + int timeout = 50; + int ambient; + + cal_config_filename = kzalloc(PAGE_SIZE, GFP_KERNEL); + snprintf(cal_config_filename, PAGE_SIZE, "%s%s", dsp_part_name, + CIRRUS_CAL_CONFIG_FILENAME_SUFFIX); + + dev_info(amp_group->cal_dev, "ReDC Calibration load start\n"); + + /* Send STOP_PRE_REINIT command and poll for response */ + regmap_write(regmap, amp->mbox_cmd, CSPL_MBOX_CMD_STOP_PRE_REINIT); + timeout = 100; + do { + dev_info(amp_group->cal_dev, "waiting for REINIT ready...\n"); + usleep_range(1000, 1500); + regmap_read(regmap, amp->mbox_sts, &halo_state); + } while ((halo_state != CSPL_MBOX_STS_RDY_FOR_REINIT) && + --timeout > 0); + + if (timeout == 0) + dev_err(amp->component->dev, "REINIT ready not found\n"); + + msleep(100); + + dev_dbg(amp_group->cal_dev, "load %s\n", dsp_part_name); + cirrus_cal_load_config(cal_config_filename, amp); + + ambient = cirrus_cal_get_power_temp(); + cirrus_amp_write_ctl(amp, "CAL_AMBIENT", WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, ambient); + + /* Send REINIT command and poll for response */ + regmap_write(regmap, amp->mbox_cmd, CSPL_MBOX_CMD_REINIT); + timeout = 100; + do { + dev_info(amp_group->cal_dev, "waiting for REINIT done...\n"); + usleep_range(1000, 1500); + regmap_read(regmap, amp->mbox_sts, &halo_state); + } while ((halo_state != CSPL_MBOX_STS_RUNNING) && + --timeout > 0); + + if (timeout == 0) + dev_err(amp->component->dev, "REINIT done not found\n"); + + kfree(cal_config_filename); +} + +int cirrus_cal_cspl_cal_apply(struct cirrus_amp *amp) +{ + unsigned int temp, rdc, status, checksum, vsc, isc; + unsigned int vimon_cal_status = CIRRUS_CAL_VIMON_STATUS_SUCCESS; + int ret = 0; + + if (!amp) + return 0; + + if (amp->cal.efs_cache_valid == 1) { + rdc = amp->cal.efs_cache_rdc; + vsc = amp->cal.efs_cache_vsc; + isc = amp->cal.efs_cache_isc; + vimon_cal_status = CIRRUS_CAL_VIMON_STATUS_SUCCESS; + temp = amp_group->efs_cache_temp; + } else if (amp->cal.efs_cache_rdc && amp_group->efs_cache_temp && + (!amp->cal.efs_cache_vsc && !amp->cal.efs_cache_isc)) { + dev_info(amp_group->cal_dev, + "No VIMON, writing RDC only\n"); + rdc = amp->cal.efs_cache_rdc; + temp = amp_group->efs_cache_temp; + vimon_cal_status = CIRRUS_CAL_VIMON_STATUS_INVALID; + } else { + + dev_info(amp_group->cal_dev, + "No saved EFS, writing defaults\n"); + rdc = amp->default_redc; + temp = CIRRUS_CAL_AMBIENT_DEFAULT; + vimon_cal_status = CIRRUS_CAL_VIMON_STATUS_INVALID; + amp->cal.efs_cache_rdc = rdc; + amp_group->efs_cache_temp = temp; + } + + status = 1; + checksum = status + rdc; + + dev_info(amp_group->cal_dev, "Writing calibration to %s (%s)\n", + amp->dsp_part_name, amp->mfd_suffix); + + dev_info(amp_group->cal_dev, + "RDC = %d, Temp = %d, Status = %d Checksum = %d\n", + rdc, temp, status, checksum); + + cirrus_amp_write_ctl(amp, "CAL_STATUS", WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, status); + cirrus_amp_write_ctl(amp, "CAL_R", WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, rdc); + cirrus_amp_write_ctl(amp, "CAL_AMBIENT", WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, temp); + cirrus_amp_write_ctl(amp, "CAL_CHECKSUM", WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, checksum); + + if (!amp->perform_vimon_cal) { + cirrus_amp_write_ctl(amp, "VIMON_CAL_STATE", WMFW_ADSP2_XM, + amp->vimon_alg_id, + CIRRUS_CAL_VIMON_STATUS_INVALID); + goto skip_vimon_cal; + } + + cirrus_amp_write_ctl(amp, "VIMON_CAL_STATE", WMFW_ADSP2_XM, + amp->vimon_alg_id, vimon_cal_status); + + if (amp->perform_vimon_cal && + vimon_cal_status != CIRRUS_CAL_VIMON_STATUS_INVALID) { + dev_info(amp_group->cal_dev, + "VIMON Cal status=%d vsc=0x%x isc=0x%x\n", + vimon_cal_status, vsc, isc); + cirrus_amp_write_ctl(amp, "VSC", WMFW_ADSP2_XM, + amp->vimon_alg_id, vsc); + cirrus_amp_write_ctl(amp, "ISC", WMFW_ADSP2_XM, + amp->vimon_alg_id, isc); + } else { + dev_info(amp_group->cal_dev, "VIMON Cal status invalid\n"); + } + +skip_vimon_cal: + return ret; +} + +int cirrus_cal_cspl_read_temp(struct cirrus_amp *amp) +{ + int reg = 0, ret; + unsigned int halo_state; + unsigned int global_en; + + if (!amp) + goto err; + + regmap_read(amp->regmap, amp->global_en, &global_en); + + if ((global_en & amp->global_en_mask) == 0) + goto err; + + regmap_read(amp->regmap, amp->mbox_sts, &halo_state); + + if (halo_state != CSPL_MBOX_STS_RUNNING) + goto err; + + if (amp_group->cal_running) + goto err; + + ret = cirrus_cal_logger_get_variable(amp, + CIRRUS_CAL_RTLOG_ID_TEMP, + ®); + if (ret == 0) { + if (reg == 0) + cirrus_cal_logger_get_variable(amp, + CIRRUS_CAL_RTLOG_ID_TEMP, + ®); + + reg = cirrus_cal_sign_extend(reg); + dev_info(amp_group->cal_dev, + "Read temp: %d.%04d degrees C\n", + reg >> CIRRUS_CAL_RTLOG_RADIX_TEMP, + (reg & (((1 << CIRRUS_CAL_RTLOG_RADIX_TEMP) - 1))) * + 10000 / (1 << CIRRUS_CAL_RTLOG_RADIX_TEMP)); + return (reg >> CIRRUS_CAL_RTLOG_RADIX_TEMP); + } +err: + return -1; +} + +int cirrus_cal_cspl_set_surface_temp(struct cirrus_amp *amp, int temperature) +{ + unsigned int global_en; + + if (!amp) + return -EINVAL; + + regmap_read(amp->regmap, amp->global_en, &global_en); + + if ((global_en & amp->global_en_mask) == 0) + return -EINVAL; + + dev_info(amp->component->dev, "Set surface temp: %d degrees\n", temperature); + cirrus_amp_write_ctl(amp, "CSPL_SURFACE_TEMP", WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, temperature); + return 0; +} + +static int cirrus_cal_cspl_start(void) +{ + int redc_cal_start_retries, vimon_cal_retries = 0; + bool vimon_calibration_failed = false; + unsigned int cal_state; + int amp; + struct reg_sequence *config; + struct regmap *regmap; + int ret = 0; + + for (amp = 0; amp < amp_group->num_amps; amp++) { + if (amp_group->amps[amp].calibration_disable) + continue; + + cirrus_amp_write_ctl(&_group->amps[amp], "CAL_STATUS", + WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, 0); + cirrus_amp_write_ctl(&_group->amps[amp], "CAL_R", + WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, 0); + cirrus_amp_write_ctl(&_group->amps[amp], "CAL_AMBIENT", + WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, 0); + cirrus_amp_write_ctl(&_group->amps[amp], "CAL_CHECKSUM", + WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, 0); + if (amp_group->amps[amp].perform_vimon_cal) { + cirrus_amp_write_ctl(&_group->amps[amp], "VSC", + WMFW_ADSP2_XM, + amp_group->amps[amp].vimon_alg_id, 0); + cirrus_amp_write_ctl(&_group->amps[amp], "ISC", + WMFW_ADSP2_XM, + amp_group->amps[amp].vimon_alg_id, 0); + } + + ret = cirrus_cal_wait_for_active(&_group->amps[amp]); + if (ret < 0) { + dev_err(amp_group->cal_dev, + "Could not start amp%s (%d)\n", + amp_group->amps[amp].mfd_suffix, ret); + return -ETIMEDOUT; + } + } + + do { + vimon_calibration_failed = false; + + for (amp = 0; amp < amp_group->num_amps; amp++) { + if (amp_group->amps[amp].calibration_disable) + continue; + + regmap = amp_group->amps[amp].regmap; + config = amp_group->amps[amp].pre_config; + + regmap_multi_reg_write(regmap, config, + amp_group->amps[amp].num_pre_configs); + if (amp_group->amps[amp].perform_vimon_cal) + cirrus_cal_cspl_vimon_cal_start(&_group->amps[amp]); + } + + + msleep(112); + + for (amp = 0; amp < amp_group->num_amps; amp++) { + if (amp_group->amps[amp].calibration_disable) + continue; + + if (amp_group->amps[amp].perform_vimon_cal) { + ret = cirrus_cal_cspl_vimon_cal_complete( + &_group->amps[amp]); + + if (ret != CIRRUS_CAL_VIMON_STATUS_SUCCESS) { + vimon_calibration_failed = true; + dev_info(amp_group->cal_dev, + "VIMON Calibration Error %s (%s)\n", + amp_group->amps[amp].dsp_part_name, + amp_group->amps[amp].mfd_suffix); + } + } + } + + vimon_cal_retries--; + } while (vimon_cal_retries >= 0 && vimon_calibration_failed); + + for (amp = 0; amp < amp_group->num_amps; amp++) { + if (amp_group->amps[amp].calibration_disable) + continue; + + regmap = amp_group->amps[amp].regmap; + + cirrus_cal_cspl_redc_start(&_group->amps[amp]); + + cirrus_amp_read_ctl(&_group->amps[amp], "CSPL_STATE", + WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, &cal_state); + + redc_cal_start_retries = 5; + while (cal_state == CSPL_STATE_ERROR && + redc_cal_start_retries > 0) { + if (cal_state == CSPL_STATE_ERROR) + dev_err(amp_group->cal_dev, + "Calibration load error\n"); + + cirrus_cal_cspl_redc_start(&_group->amps[amp]); + + cirrus_amp_read_ctl(&_group->amps[amp], "CSPL_STATE", + WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, &cal_state); + redc_cal_start_retries--; + } + + if (redc_cal_start_retries == 0) { + config = amp_group->amps[amp].post_config; + + dev_err(amp_group->cal_dev, + "Calibration setup fail amp%s (%d)\n", + amp_group->amps[amp].mfd_suffix, ret); + + regmap_multi_reg_write(regmap, config, + amp_group->amps[amp].num_post_configs); + return -ETIMEDOUT; + } + } + + return 0; +} + +int cirrus_cal_cspl_v_val_start(struct cirrus_amp *amps, int num_amps, bool separate) +{ + struct reg_sequence *config; + struct regmap *regmap; + unsigned int vmax[CIRRUS_MAX_AMPS] = {0, 0, 0, 0, 0, 0, 0, 0}; + unsigned int vmin[CIRRUS_MAX_AMPS] = {0, 0, 0, 0, 0, 0, 0, 0}; + unsigned int cal_state; + int ret = 0, i, j, reg, retries, count; + + for (i = 0; i < amp_group->num_amps; i++) { + if (amps[i].v_val_separate && !separate) + continue; + + regmap = amps[i].regmap; + config = amps[i].pre_config; + + vmax[i] = 0; + vmin[i] = INT_MAX; + + ret = cirrus_cal_wait_for_active(&s[i]); + if (ret < 0) { + dev_err(amp_group->cal_dev, + "Could not start amp%s\n", + amps[i].mfd_suffix); + goto err; + } + + regmap_multi_reg_write(regmap, config, + amps[i].num_pre_configs); + cirrus_cal_cspl_redc_start(&s[i]); + + cirrus_amp_read_ctl(&s[i], "CSPL_STATE", + WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, &cal_state); + + retries = 5; + while (cal_state == CSPL_STATE_ERROR && retries > 0) { + if (cal_state == CSPL_STATE_ERROR) + dev_err(amp_group->cal_dev, + "Calibration load error\n"); + + cirrus_cal_cspl_redc_start(&s[i]); + + cirrus_amp_read_ctl(&s[i], "CSPL_STATE", + WMFW_ADSP2_XM, CIRRUS_AMP_ALG_ID_CSPL, &cal_state); + retries--; + } + + if (retries == 0) { + config = amps[i].post_config; + dev_err(amp_group->cal_dev, + "Calibration setup fail @ %d\n", i); + regmap_multi_reg_write(regmap, config, + amps[i].num_post_configs); + goto err; + } + } + + dev_info(amp_group->cal_dev, "V validation prepare complete\n"); + + for (i = 0; i < 1000; i++) { + count = 0; + for (j = 0; j < num_amps; j++) { + if (amps[j].v_val_separate && !separate) + continue; + + cirrus_cal_logger_get_variable(&s[j], + amps[j].cal_vpk_id, + ®); + if (reg > vmax[j]) + vmax[j] = reg; + if (reg < vmin[j]) + vmin[j] = reg; + + cirrus_amp_read_ctl(&_group->amps[j], "CAL_STATUS", + WMFW_ADSP2_XM, + CIRRUS_AMP_ALG_ID_CSPL, ®); + + if (reg != 0 && reg != CSPL_STATUS_INCOMPLETE) + count++; + } + + if (count == num_amps) { + dev_info(amp_group->cal_dev, + "V Validation complete (%d)\n", i); + break; + } + } + + for (i = 0; i < num_amps; i++) { + if (amps[i].v_val_separate && !separate) + continue; + + dev_info(amp_group->cal_dev, + "V Validation results for amp%s\n", + amps[i].mfd_suffix); + + dev_dbg(amp_group->cal_dev, "V Max: 0x%x\n", vmax[i]); + vmax[i] = cirrus_cal_vpk_to_mv(vmax[i]); + dev_info(amp_group->cal_dev, "V Max: %d mV\n", vmax[i]); + + dev_dbg(amp_group->cal_dev, "V Min: 0x%x\n", vmin[i]); + vmin[i] = cirrus_cal_vpk_to_mv(vmin[i]); + dev_info(amp_group->cal_dev, "V Min: %d mV\n", vmin[i]); + + if (vmax[i] < CIRRUS_CAL_V_VAL_UB_MV && + vmax[i] > CIRRUS_CAL_V_VAL_LB_MV) { + amps[i].cal.v_validation = 1; + dev_info(amp_group->cal_dev, + "V validation success\n"); + } else { + amps[i].cal.v_validation = 0xCC; + dev_err(amp_group->cal_dev, + "V validation failed\n"); + } + + } + + cirrus_cal_v_val_complete(amps, num_amps, separate); + return 0; + +err: + return -1; +} + +struct cirrus_cal_ops cirrus_cspl_cal_ops = { + .controls = { + { "CAL_R", CIRRUS_AMP_ALG_ID_CSPL}, + { "CAL_CHECKSUM", CIRRUS_AMP_ALG_ID_CSPL}, + { "CAL_SET_STATUS", CIRRUS_AMP_ALG_ID_CSPL}, + }, + .cal_start = cirrus_cal_cspl_start, + .cal_complete = cirrus_cal_cspl_complete, + .v_val = cirrus_cal_cspl_v_val_start, + .cal_apply = cirrus_cal_cspl_cal_apply, + .read_temp = cirrus_cal_cspl_read_temp, + .set_temp = cirrus_cal_cspl_set_surface_temp, +}; diff --git a/sound/soc/codecs/cirrus-cal.c b/sound/soc/codecs/cirrus-cal.c new file mode 100644 index 000000000000..9f0e4cb34faa --- /dev/null +++ b/sound/soc/codecs/cirrus-cal.c @@ -0,0 +1,691 @@ +/* + * Calibration support for Cirrus Logic Smart Amplifiers + * + * Copyright 2017 Cirrus Logic + * + * Author: David Rhodes + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "wm_adsp.h" + +#define CIRRUS_CAL_VERSION "5.01.18" + +#define CIRRUS_CAL_DIR_NAME "cirrus_cal" +#define CIRRUS_CAL_RDC_SAVE_LOCATION "/efs/cirrus/rdc_cal" +#define CIRRUS_CAL_TEMP_SAVE_LOCATION "/efs/cirrus/temp_cal" +#define CIRRUS_CAL_VSC_SAVE_LOCATION "/efs/cirrus/vsc_cal" +#define CIRRUS_CAL_ISC_SAVE_LOCATION "/efs/cirrus/isc_cal" + +#define CIRRUS_CAL_AMBIENT_DEFAULT 23 +#define CIRRUS_CAL_COMPLETE_DELAY_MS 1250 + +int cirrus_cal_apply(const char *mfd_suffix) +{ + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(mfd_suffix); + + if (amp == NULL) + return -EINVAL; + + return amp->cal_ops->cal_apply(amp); +} +EXPORT_SYMBOL_GPL(cirrus_cal_apply); + +int cirrus_cal_read_temp(const char *mfd_suffix) +{ + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(mfd_suffix); + + if (amp == NULL) + return -EINVAL; + + return amp->cal_ops->read_temp(amp); +} +EXPORT_SYMBOL_GPL(cirrus_cal_read_temp); + +int cirrus_cal_set_surface_temp(const char *suffix, int temperature) +{ + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + + if (amp == NULL) + return -EINVAL; + + return amp->cal_ops->set_temp(amp, temperature); +} +EXPORT_SYMBOL_GPL(cirrus_cal_set_surface_temp); + +void cirrus_cal_complete_work(struct work_struct *work) +{ + amp_group->amps[0].cal_ops->cal_complete(); +} + + +/***** SYSFS Interfaces *****/ + +static ssize_t cirrus_cal_version_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, CIRRUS_CAL_VERSION "\n"); +} + +static ssize_t cirrus_cal_version_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + return 0; +} + +static ssize_t cirrus_cal_status_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%s\n", amp_group->cal_running ? + "Enabled" : "Disabled"); +} + +static ssize_t cirrus_cal_status_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret = 0, prepare; + + if (amp_group->cal_running) { + dev_err(amp_group->cal_dev, + "cirrus_cal measurement in progress\n"); + return size; + } + + mutex_lock(&_group->cal_lock); + + ret = kstrtos32(buf, 10, &prepare); + if (ret != 0 || prepare != 1) + goto err; + + amp_group->cal_running = true; + amp_group->cal_retry = 0; + + amp_group->amps[0].cal_ops->cal_start(); + + dev_dbg(amp_group->cal_dev, "Calibration prepare complete\n"); + + queue_delayed_work(system_unbound_wq, &_group->cal_complete_work, + msecs_to_jiffies(CIRRUS_CAL_COMPLETE_DELAY_MS)); + +err: + mutex_unlock(&_group->cal_lock); + if (ret < 0) + amp_group->cal_running = false; + + return size; +} + +static ssize_t cirrus_cal_v_status_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%s\n", amp_group->cal_running ? + "Enabled" : "Disabled"); +} + +static ssize_t cirrus_cal_v_status_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret = 0, prepare, num_amps; + const char *suffix; + struct cirrus_amp *amps; + bool separate = false; + + if (amp_group->cal_running) { + dev_err(amp_group->cal_dev, + "cirrus_cal measurement in progress\n"); + return size; + } + + mutex_lock(&_group->cal_lock); + + ret = kstrtos32(buf, 10, &prepare); + if (ret != 0 || prepare != 1) + goto err; + + amp_group->cal_running = true; + + if (strlen(attr->attr.name) > strlen("v_status")) { + suffix = &(attr->attr.name[strlen("v_status")]); + amps = cirrus_get_amp_from_suffix(suffix); + if (amps) { + dev_info(dev, "V-validation for amp: %s (%s)\n", + amps->dsp_part_name, suffix); + num_amps = 1; + separate = true; + } else { + mutex_unlock(&_group->cal_lock); + return size; + } + } else { + num_amps = amp_group->num_amps; + amps = amp_group->amps; + separate = false; + } + + amps[0].cal_ops->v_val(amps, num_amps, separate); + +err: + amp_group->cal_running = false; + mutex_unlock(&_group->cal_lock); + + return size; +} + +#ifdef CONFIG_SND_SOC_CIRRUS_REINIT_SYSFS +static ssize_t cirrus_cal_reinit_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "\n"); +} + +static ssize_t cirrus_cal_reinit_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int reinit, i; + int ret = kstrtos32(buf, 10, &reinit); + + if (amp_group->cal_running) { + dev_err(amp_group->cal_dev, + "cirrus_cal measurement in progress\n"); + return size; + } + + if (ret == 0 && reinit == 1) { + mutex_lock(&_group->cal_lock); + + for (i = 0; i < amp_group->num_amps; i++) { + if (amp_group->amps[i].amp_reinit != NULL) + amp_group->amps[i].amp_reinit( + amp_group->amps[i].component); + } + + mutex_unlock(&_group->cal_lock); + } + + return size; +} +#endif /* CONFIG_SND_SOC_CIRRUS_REINIT_SYSFS*/ + +static ssize_t cirrus_cal_vval_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const char *suffix = &(attr->attr.name[strlen("v_validation")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + + dev_info(dev, "%s\n", __func__); + + return sprintf(buf, "%d", amp->cal.v_validation); +} + +static ssize_t cirrus_cal_vval_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + dev_info(dev, "%s\n", __func__); + return 0; +} + +static ssize_t cirrus_cal_rdc_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + unsigned int rdc; + const char *suffix = &(attr->attr.name[strlen("rdc")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + + if (amp) { + rdc = amp->cal.efs_cache_rdc; + return sprintf(buf, "%d", rdc); + } else + return 0; +} + +static ssize_t cirrus_cal_rdc_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int rdc, ret; + const char *suffix = &(attr->attr.name[strlen("rdc")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + bool vimon_valid; + + ret = kstrtos32(buf, 10, &rdc); + if (ret == 0 && amp) { + if (rdc < 0) { + amp->cal.efs_cache_vsc = 0; + amp->cal.efs_cache_isc = 0; + amp->cal.efs_cache_rdc = 0; + amp->cal.efs_cache_valid = 0; + return size; + } + + amp->cal.efs_cache_rdc = rdc; + + dev_info(dev, "EFS Cache RDC set: 0x%x\n", rdc); + vimon_valid = (!amp->perform_vimon_cal) || (amp->cal.efs_cache_vsc && + amp->cal.efs_cache_isc); + if (amp->cal.efs_cache_rdc && amp_group->efs_cache_temp && + vimon_valid) + amp->cal.efs_cache_valid = 1; + } + return size; +} + +static ssize_t cirrus_cal_vsc_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + unsigned int vsc; + const char *suffix = &(attr->attr.name[strlen("vsc")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + + if (amp) { + vsc = amp->cal.efs_cache_vsc; + return sprintf(buf, "%d", vsc); + } else + return 0; +} + +static ssize_t cirrus_cal_vsc_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int vsc, ret; + const char *suffix = &(attr->attr.name[strlen("vsc")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + bool vimon_valid; + + ret = kstrtos32(buf, 10, &vsc); + if (ret == 0 && amp) { + if (vsc < 0) { + amp->cal.efs_cache_vsc = 0; + amp->cal.efs_cache_isc = 0; + amp->cal.efs_cache_rdc = 0; + amp->cal.efs_cache_valid = 0; + return size; + } + + amp->cal.efs_cache_vsc = vsc; + + dev_info(dev, "EFS Cache VSC set: 0x%x\n", vsc); + vimon_valid = (!amp->perform_vimon_cal) || (amp->cal.efs_cache_vsc && + amp->cal.efs_cache_isc); + if (amp->cal.efs_cache_rdc && amp_group->efs_cache_temp && + vimon_valid) + amp->cal.efs_cache_valid = 1; + } + return size; +} + +static ssize_t cirrus_cal_isc_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + unsigned int isc; + const char *suffix = &(attr->attr.name[strlen("isc")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + + if (amp) { + isc = amp->cal.efs_cache_isc; + return sprintf(buf, "%d", isc); + } else + return 0; +} + +static ssize_t cirrus_cal_isc_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int isc, ret; + const char *suffix = &(attr->attr.name[strlen("isc")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + bool vimon_valid; + + ret = kstrtos32(buf, 10, &isc); + if (ret == 0 && amp) { + if (isc < 0) { + amp->cal.efs_cache_vsc = 0; + amp->cal.efs_cache_isc = 0; + amp->cal.efs_cache_rdc = 0; + amp->cal.efs_cache_valid = 0; + return size; + } + + amp->cal.efs_cache_isc = isc; + + dev_info(dev, "EFS Cache ISC set: 0x%x\n", isc); + vimon_valid = (!amp->perform_vimon_cal) || (amp->cal.efs_cache_vsc && + amp->cal.efs_cache_isc); + if (amp->cal.efs_cache_rdc && amp_group->efs_cache_temp && + vimon_valid) + amp->cal.efs_cache_valid = 1; + } + return size; +} + +static ssize_t cirrus_cal_temp_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + unsigned int temp; + const char *suffix = &(attr->attr.name[strlen("temp")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + + if (amp) { + temp = amp_group->efs_cache_temp; + return sprintf(buf, "%d", temp); + } else + return 0; +} + +static ssize_t cirrus_cal_temp_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int temp, ret; + const char *suffix = &(attr->attr.name[strlen("temp")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + bool vimon_valid; + + ret = kstrtos32(buf, 10, &temp); + if (ret == 0 && amp) { + amp_group->efs_cache_temp = temp; + + dev_info(dev, "EFS Cache temp set: %d\n", temp); + vimon_valid = (!amp->perform_vimon_cal) || (amp->cal.efs_cache_vsc && + amp->cal.efs_cache_isc); + if (amp->cal.efs_cache_rdc && amp_group->efs_cache_temp && + vimon_valid) + amp->cal.efs_cache_valid = 1; + } + return size; +} + +static ssize_t cirrus_cal_checksum_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + unsigned int checksum; + const char *suffix = &(attr->attr.name[strlen("checksum")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + + if (amp) { + cirrus_amp_read_ctl(amp, amp->cal_ops->controls.cal_checksum.name, + WMFW_ADSP2_XM, amp->cal_ops->controls.cal_checksum.alg_id, &checksum); + return sprintf(buf, "%d", checksum); + } else + return 0; +} + +static ssize_t cirrus_cal_checksum_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int checksum, ret; + const char *suffix = &(attr->attr.name[strlen("checksum")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + + ret = kstrtos32(buf, 10, &checksum); + if (ret == 0 && amp) + cirrus_amp_write_ctl(amp, amp->cal_ops->controls.cal_checksum.name, + WMFW_ADSP2_XM, amp->cal_ops->controls.cal_checksum.alg_id, checksum); + return size; +} + +static ssize_t cirrus_cal_set_status_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + unsigned int set_status; + const char *suffix = &(attr->attr.name[strlen("set_status")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + + if (amp) { + cirrus_amp_read_ctl(amp, amp->cal_ops->controls.cal_set_status.name, + WMFW_ADSP2_XM, amp->cal_ops->controls.cal_checksum.alg_id, &set_status); + return sprintf(buf, "%d", set_status); + } else + return 0; +} + +static ssize_t cirrus_cal_set_status_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + return 0; +} + +static DEVICE_ATTR(version, 0444, cirrus_cal_version_show, + cirrus_cal_version_store); +static DEVICE_ATTR(status, 0664, cirrus_cal_status_show, + cirrus_cal_status_store); +static DEVICE_ATTR(v_status, 0664, cirrus_cal_v_status_show, + cirrus_cal_v_status_store); +#ifdef CONFIG_SND_SOC_CIRRUS_REINIT_SYSFS +static DEVICE_ATTR(reinit, 0664, cirrus_cal_reinit_show, + cirrus_cal_reinit_store); +#endif /* CONFIG_SND_SOC_CIRRUS_REINIT_SYSFS */ + +static struct device_attribute v_val_attribute = { + .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0664)}, + .show = cirrus_cal_v_status_show, + .store = cirrus_cal_v_status_store, +}; + +static struct device_attribute generic_amp_attrs[CIRRUS_CAL_NUM_ATTRS_AMP] = { + { + .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0444)}, + .show = cirrus_cal_vval_show, + .store = cirrus_cal_vval_store, + }, + { + .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0664)}, + .show = cirrus_cal_rdc_show, + .store = cirrus_cal_rdc_store, + }, + { + .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0664)}, + .show = cirrus_cal_vsc_show, + .store = cirrus_cal_vsc_store, + }, + { + .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0664)}, + .show = cirrus_cal_isc_show, + .store = cirrus_cal_isc_store, + }, + { + .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0664)}, + .show = cirrus_cal_temp_show, + .store = cirrus_cal_temp_store, + }, + { + .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0664)}, + .show = cirrus_cal_checksum_show, + .store = cirrus_cal_checksum_store, + }, + { + .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0444)}, + .show = cirrus_cal_set_status_show, + .store = cirrus_cal_set_status_store, + }, +}; + +static const char *generic_amp_attr_names[CIRRUS_CAL_NUM_ATTRS_AMP] = { + "v_validation", + "rdc", + "vsc", + "isc", + "temp", + "checksum", + "set_status" +}; + +static struct attribute *cirrus_cal_attr_base[] = { + &dev_attr_version.attr, + &dev_attr_status.attr, + &dev_attr_v_status.attr, +#ifdef CONFIG_SND_SOC_CIRRUS_REINIT_SYSFS + &dev_attr_reinit.attr, +#endif /* CONFIG_SND_SOC_CIRRUS_REINIT_SYSFS */ + NULL, +}; + +/* Kernel does not allow attributes to be dynamically allocated */ +static struct attribute_group cirrus_cal_attr_grp; +static struct device_attribute + amp_attrs_prealloc[CIRRUS_MAX_AMPS][CIRRUS_CAL_NUM_ATTRS_AMP]; +static char attr_names_prealloc[CIRRUS_MAX_AMPS][CIRRUS_CAL_NUM_ATTRS_AMP][20]; +static char v_val_attr_names_prealloc[CIRRUS_MAX_AMPS][20]; +static struct device_attribute v_val_attrs_prealloc[CIRRUS_MAX_AMPS]; + +struct device_attribute *cirrus_cal_create_amp_attrs(const char *mfd_suffix, + int index) +{ + struct device_attribute *amp_attrs_new; + int i, suffix_len = strlen(mfd_suffix); + + if (index >= CIRRUS_MAX_AMPS) + return NULL; + + amp_attrs_new = &(amp_attrs_prealloc[index][0]); + + memcpy(amp_attrs_new, &generic_amp_attrs, + sizeof(struct device_attribute) * + CIRRUS_CAL_NUM_ATTRS_AMP); + + for (i = 0; i < CIRRUS_CAL_NUM_ATTRS_AMP; i++) { + amp_attrs_new[i].attr.name = attr_names_prealloc[index][i]; + snprintf((char *)amp_attrs_new[i].attr.name, + strlen(generic_amp_attr_names[i]) + suffix_len + 1, + "%s%s", generic_amp_attr_names[i], mfd_suffix); + } + + return amp_attrs_new; +} + +int cirrus_cal_init(void) +{ + struct device_attribute *new_attrs; + int ret = 0, i, j, num_amps, v_val_num_attrs = 0; + + if (!amp_group) { + pr_err("%s: Empty amp group\n", __func__); + return -ENODATA; + } + + amp_group->cal_dev = device_create(cirrus_amp_class, NULL, 1, NULL, + CIRRUS_CAL_DIR_NAME); + if (IS_ERR(amp_group->cal_dev)) { + ret = PTR_ERR(amp_group->cal_dev); + pr_err("%s: Failed to create CAL device (%d)\n", __func__, ret); + return ret; + } + + dev_set_drvdata(amp_group->cal_dev, amp_group); + + num_amps = amp_group->num_amps; + + for (i = 0; i < num_amps; i++) { + if (amp_group->amps[i].v_val_separate) + v_val_num_attrs++; + } + + cirrus_cal_attr_grp.attrs = kzalloc(sizeof(struct attribute *) * + (CIRRUS_CAL_NUM_ATTRS_AMP * num_amps + + v_val_num_attrs + + CIRRUS_CAL_NUM_ATTRS_BASE + 1), + GFP_KERNEL); + for (i = 0; i < num_amps; i++) { + new_attrs = cirrus_cal_create_amp_attrs( + amp_group->amps[i].mfd_suffix, i); + for (j = 0; j < CIRRUS_CAL_NUM_ATTRS_AMP; j++) { + dev_dbg(amp_group->cal_dev, "New attribute: %s\n", + new_attrs[j].attr.name); + cirrus_cal_attr_grp.attrs[i * CIRRUS_CAL_NUM_ATTRS_AMP + + j] = &new_attrs[j].attr; + } + } + + for (i = j = 0; i < num_amps; i++) { + if (amp_group->amps[i].v_val_separate) { + memcpy(&v_val_attrs_prealloc[j], + &v_val_attribute, sizeof(struct device_attribute)); + + v_val_attrs_prealloc[j].attr.name = + v_val_attr_names_prealloc[j]; + snprintf((char *)v_val_attrs_prealloc[j].attr.name, + strlen("v_status") + + strlen(amp_group->amps[i].mfd_suffix) + 1, + "v_status%s", amp_group->amps[i].mfd_suffix); + dev_info(amp_group->cal_dev, "New attribute: %s\n", + v_val_attrs_prealloc[j].attr.name); + cirrus_cal_attr_grp.attrs[num_amps * CIRRUS_CAL_NUM_ATTRS_AMP + + j] = &v_val_attrs_prealloc[j].attr; + j++; + } + } + + memcpy(&cirrus_cal_attr_grp.attrs[num_amps * CIRRUS_CAL_NUM_ATTRS_AMP + + v_val_num_attrs], + cirrus_cal_attr_base, sizeof(struct attribute *) * + CIRRUS_CAL_NUM_ATTRS_BASE); + cirrus_cal_attr_grp.attrs[num_amps * CIRRUS_CAL_NUM_ATTRS_AMP + + CIRRUS_CAL_NUM_ATTRS_BASE + v_val_num_attrs] = NULL; + + ret = sysfs_create_group(&_group->cal_dev->kobj, + &cirrus_cal_attr_grp); + if (ret) { + dev_err(amp_group->cal_dev, "Failed to create sysfs group\n"); + device_del(amp_group->bd_dev); + return ret; + } + + mutex_init(&_group->cal_lock); + INIT_DELAYED_WORK(&_group->cal_complete_work, cirrus_cal_complete_work); + + return ret; +} + +void cirrus_cal_exit(void) +{ + flush_work(&_group->cal_complete_work.work); + mutex_destroy(&_group->cal_lock); + kfree(cirrus_cal_attr_grp.attrs); + device_del(amp_group->bd_dev); +} + + diff --git a/sound/soc/codecs/cirrus-pwr.c b/sound/soc/codecs/cirrus-pwr.c new file mode 100644 index 000000000000..42dff5f0f78a --- /dev/null +++ b/sound/soc/codecs/cirrus-pwr.c @@ -0,0 +1,712 @@ +/* + * Power-management support for Cirrus Logic Smart Amplifiers + * + * Copyright 2018 Cirrus Logic + * + * Author: David Rhodes + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define CIRRUS_PWR_VERSION "5.01.18" + +#define CIRRUS_PWR_DIR_NAME "cirrus_pwr" +#define CIRRUS_PWR_WORKQ_NAME "cirrus_pwr_wq" + +#define CIRRUS_PWR_STATUS_DISABLED 0 +#define CIRRUS_PWR_STATUS_ENABLED 1 +#define CIRRUS_PWR_STATUS_ERROR 3 + +#define CIRRUS_PWR_AMB_TEMP_OFFSET 500 +#define CIRRUS_PWR_SCALING_Q15 846397 + +static unsigned int sqrt_q24(unsigned long x) +{ + u32 root, remHi, remLo, testDiv, count; + + root = 0; + remHi = 0; + remLo = x; + count = 24; + + do { + remHi = (remHi << 2) | (remLo >> 30); + remLo <<= 2; + root <<= 1; + testDiv = (root << 1) + 1; + if (remHi >= testDiv) { + remHi -= testDiv; + root++; + } + } while (count-- != 0); + + return root; /* Q21 result */ +} + +static unsigned int convert_power(unsigned int power_squared) +{ + unsigned long long power; + + power = sqrt_q24(power_squared*2); + power *= CIRRUS_PWR_SCALING_Q15; + + dev_dbg(amp_group->pwr_dev, + "converted power (%d W^2): %llu.%04llu W\n", + power_squared, + power >> 36, + (power & (((1ull << 36) - 1ull))) * + 10000 / (1ull << 36)); + + power *= 1000; + power >>= 28; + + dev_dbg(amp_group->pwr_dev, + "converted power q8 mW: %d mW = 0x%x\n", + (unsigned int)(power / 256), (unsigned int)(power)); + + return (unsigned int)power; +} + +static void cirrus_pwr_passport_enable(struct regmap *regmap_enable, + bool enable) +{ + if (regmap_enable) + regmap_write(regmap_enable, + CIRRUS_PWR_CSPL_PASSPORT_ENABLE, + (uint)enable); +} + +void cirrus_pwr_start(const char *mfd_suffix) +{ + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(mfd_suffix); + + if (!amp) + return; + + amp->pwr.amp_active = 1; + + if (!amp_group->pwr_enable) + return; + + mutex_lock(&_group->pwr_lock); + + if (amp_group->status == CIRRUS_PWR_STATUS_ENABLED) { + /* State machine already active on one amp */ + dev_dbg(amp_group->pwr_dev, + "%s(), additional amp activated", __func__); + } else { + /* Init state machine */ + dev_dbg(amp_group->pwr_dev, + "%s() Entering wait period.\n", __func__); + amp_group->status = CIRRUS_PWR_STATUS_ENABLED; + + /* Queue state machine operation */ + queue_delayed_work(amp_group->pwr_workqueue, + &_group->pwr_work, + msecs_to_jiffies(amp_group->interval)); + } + + mutex_unlock(&_group->pwr_lock); +} +EXPORT_SYMBOL_GPL(cirrus_pwr_start); + +void cirrus_pwr_stop(const char *mfd_suffix) +{ + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(mfd_suffix); + int i; + bool amps_active = 0; + + if (!amp) + return; + + amp->pwr.amp_active = 0; + + if (!amp_group->pwr_enable) + return; + + mutex_lock(&_group->pwr_lock); + + for (i = 0; i < amp_group->num_amps; i++) + amps_active |= amp->pwr.amp_active; + + if (amps_active) { + /* One amp still active */ + dev_dbg(amp_group->pwr_dev, "Amp %s%s deactivated\n", + amp->dsp_part_name, amp->mfd_suffix); + } else { + /* Exit state machine */ + dev_dbg(amp_group->pwr_dev, + "%s(). Disabling PASSPORT\n", __func__); + + for (i = 0; i < amp_group->num_amps; i++) { + cirrus_pwr_passport_enable( + amp_group->amps[i].regmap, false); + amp_group->amps[i].pwr.passport_enable = 0; + } + + /* Reset state machine variables */ + amp_group->uptime_ms = 0; + amp_group->status = CIRRUS_PWR_STATUS_DISABLED; + + /* cancel workqueue */ + if (delayed_work_pending(&_group->pwr_work)) + cancel_delayed_work(&_group->pwr_work); + } + + mutex_unlock(&_group->pwr_lock); +} +EXPORT_SYMBOL_GPL(cirrus_pwr_stop); + +static void cirrus_pwr_work(struct work_struct *work) +{ + int i; + struct cirrus_amp *amp; + + mutex_lock(&_group->pwr_lock); + + /* Run state machine and enable/disable Passport accordingly */ + + if (amp_group->status != CIRRUS_PWR_STATUS_ENABLED) + goto exit; + + amp_group->uptime_ms += amp_group->interval; + + if (amp_group->uptime_ms <= amp_group->target_min_time_ms) { + dev_dbg(amp_group->pwr_dev, + "Waiting for min time... (%d / %d ms)\n", + amp_group->uptime_ms, + amp_group->target_min_time_ms); + goto exit; + } + + /* Enabled and > min time */ + /* Evaluate temp for each amp and enable/disable Passport */ + for (i = 0; i < amp_group->num_amps; i++) { + amp = &_group->amps[i]; + + dev_dbg(amp_group->pwr_dev, "Amp %s%s\n", + amp->dsp_part_name, amp->mfd_suffix); + dev_dbg(amp_group->pwr_dev, + "Spk Temp:\t%d.%d C\t(Target: %d.%d C)\n", + amp->pwr.spk_temp / 100, + amp->pwr.spk_temp % 100, + amp->pwr.target_temp / 100, + amp->pwr.target_temp % 100); + dev_dbg(amp_group->pwr_dev, "Amb Temp:\t%d.%d\n", + amp->pwr.amb_temp / 100, + amp->pwr.amb_temp % 100); + + if (!amp->pwr.amp_active) + continue; + + if (amp->pwr.passport_enable) { + /* Evaluate exit criteria */ + if (amp->pwr.spk_temp < amp->pwr.exit_temp) { + cirrus_pwr_passport_enable( + amp->regmap, + false); + + dev_info(amp_group->pwr_dev, + "Amp %s%s below exit temp. Disabling PASSPORT\n", + amp->dsp_part_name, amp->mfd_suffix); + + amp->pwr.passport_enable = 0; + } + } else { + /* Evaluate entry criteria */ + if ((amp->pwr.amb_temp + CIRRUS_PWR_AMB_TEMP_OFFSET < + amp->pwr.spk_temp) && (amp->pwr.spk_temp > + amp->pwr.target_temp)) { + cirrus_pwr_passport_enable(amp->regmap, true); + + dev_info(amp_group->pwr_dev, + "Amp %s%s above target temp and ambient + 5.\n", + amp->dsp_part_name, amp->mfd_suffix); + + dev_info(amp_group->pwr_dev, + "Enabling PASSPORT\n"); + + amp->pwr.passport_enable = 1; + } + + } + + dev_dbg(amp_group->pwr_dev, "Amp %s%s: Passport %s\n", + amp->dsp_part_name, amp->mfd_suffix, amp->pwr.passport_enable ? + "Enabled" : "Disabled"); + } + +exit: + mutex_unlock(&_group->pwr_lock); + + /* Queue next operation */ + if (amp_group->pwr_enable) + queue_delayed_work(amp_group->pwr_workqueue, + &_group->pwr_work, + msecs_to_jiffies(amp_group->interval)); +} + +/***** SYSFS Interfaces *****/ + +static ssize_t cirrus_pwr_version_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, CIRRUS_PWR_VERSION "\n"); +} + +static ssize_t cirrus_pwr_version_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + return size; +} + +static ssize_t cirrus_pwr_uptime_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", amp_group->uptime_ms); +} + +static ssize_t cirrus_pwr_uptime_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + return size; +} + +static ssize_t cirrus_pwr_power_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const char *suffix = &(attr->attr.name[strlen("value")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + unsigned int power_squared; + unsigned int power = 0; + + if (!amp) + return 0; + + if (amp->pwr.amp_active) { + regmap_read(amp->regmap, + CIRRUS_PWR_CSPL_OUTPUT_POWER_SQ, + &power_squared); + power = convert_power(power_squared); + } + + return sprintf(buf, "%x\n", power); +} + +static ssize_t cirrus_pwr_power_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + return size; +} + +static ssize_t cirrus_pwr_interval_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", amp_group->interval); +} + +static ssize_t cirrus_pwr_interval_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + if (kstrtou32(buf, 0, &_group->interval)) + dev_err(amp_group->pwr_dev, + "%s: Failed to convert from str to u32.\n", + __func__); + return size; +} + +static ssize_t cirrus_pwr_status_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + switch (amp_group->status) { + case CIRRUS_PWR_STATUS_DISABLED: + return sprintf(buf, "Disabled\n"); + case CIRRUS_PWR_STATUS_ENABLED: + return sprintf(buf, "Enabled\n"); + case CIRRUS_PWR_STATUS_ERROR: + return sprintf(buf, "Error\n"); + default: + return sprintf(buf, "\n"); + } +} + +static ssize_t cirrus_pwr_status_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + return size; +} + +static ssize_t cirrus_pwr_target_min_time_ms_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", amp_group->target_min_time_ms); +} + +static ssize_t cirrus_pwr_target_min_time_ms_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + if (kstrtou32(buf, 0, &_group->target_min_time_ms)) + dev_err(amp_group->pwr_dev, + "%s: Failed to convert from str to u32.\n", __func__); + return size; +} + +static ssize_t cirrus_pwr_target_temp_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const char *suffix = &(attr->attr.name[strlen("target_temp")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + + if (!amp) + return 0; + + return sprintf(buf, "%d\n", amp->pwr.target_temp); +} + +static ssize_t cirrus_pwr_target_temp_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + const char *suffix = &(attr->attr.name[strlen("target_temp")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + + if (!amp) + return 0; + + if (kstrtou32(buf, 0, &->pwr.target_temp)) + dev_err(amp_group->pwr_dev, + "%s: Failed to convert from str to u32.\n", __func__); + return size; +} + +static ssize_t cirrus_pwr_exit_temp_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const char *suffix = &(attr->attr.name[strlen("exit_temp")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + + if (!amp) + return 0; + + return sprintf(buf, "%d\n", amp->pwr.exit_temp); +} + +static ssize_t cirrus_pwr_exit_temp_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + const char *suffix = &(attr->attr.name[strlen("exit_temp")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + + if (!amp) + return 0; + + if (kstrtou32(buf, 0, &->pwr.exit_temp)) + dev_err(amp_group->pwr_dev, + "%s: Failed to convert from str to u32.\n", __func__); + return size; +} + +static ssize_t cirrus_pwr_amb_temp_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const char *suffix = &(attr->attr.name[strlen("amb_temp")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + + if (!amp) + return 0; + + return sprintf(buf, "%d\n", amp->pwr.amb_temp); +} + +static ssize_t cirrus_pwr_amb_temp_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + const char *suffix = &(attr->attr.name[strlen("amb_temp")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + + if (!amp) + return 0; + + if (kstrtou32(buf, 0, &->pwr.amb_temp)) + dev_err(amp_group->pwr_dev, + "%s: Failed to convert from str to u32.\n", __func__); + return size; +} + +static ssize_t cirrus_pwr_spk_temp_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const char *suffix = &(attr->attr.name[strlen("spk_t")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + + if (!amp) + return 0; + + return sprintf(buf, "%d\n", amp->pwr.spk_temp); +} + +static ssize_t cirrus_pwr_spk_temp_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + const char *suffix = &(attr->attr.name[strlen("spk_t")]); + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); + + if (!amp) + return 0; + + if (kstrtou32(buf, 0, &->pwr.spk_temp)) + dev_err(amp_group->pwr_dev, + "%s: Failed to convert from str to u32.\n", __func__); + return size; +} + +static ssize_t cirrus_pwr_global_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", amp_group->pwr_enable); +} + +static ssize_t cirrus_pwr_global_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned int enable; + int i; + + if (kstrtou32(buf, 0, &enable)) { + dev_err(amp_group->pwr_dev, + "%s: Failed to convert from str to u32.\n", __func__); + return size; + } + + amp_group->pwr_enable = enable; + + if (enable == 0 && + amp_group->status == CIRRUS_PWR_STATUS_ENABLED) { + /* Stop all amps */ + for (i = 0; i < amp_group->num_amps; i++) + cirrus_pwr_stop(amp_group->amps[i].mfd_suffix); + } + + return size; +} + +static DEVICE_ATTR(version, 0444, cirrus_pwr_version_show, + cirrus_pwr_version_store); +static DEVICE_ATTR(uptime, 0444, cirrus_pwr_uptime_show, + cirrus_pwr_uptime_store); +static DEVICE_ATTR(global_enable, 0664, cirrus_pwr_global_enable_show, + cirrus_pwr_global_enable_store); +static DEVICE_ATTR(interval, 0664, cirrus_pwr_interval_show, + cirrus_pwr_interval_store); +static DEVICE_ATTR(status, 0664, cirrus_pwr_status_show, + cirrus_pwr_status_store); +static DEVICE_ATTR(target_min_time_ms, 0664, cirrus_pwr_target_min_time_ms_show, + cirrus_pwr_target_min_time_ms_store); + +static struct attribute *cirrus_pwr_attr_base[] = { + &dev_attr_version.attr, + &dev_attr_uptime.attr, + &dev_attr_interval.attr, + &dev_attr_status.attr, + &dev_attr_target_min_time_ms.attr, + &dev_attr_global_enable.attr, + NULL, +}; + +static struct device_attribute generic_amp_attrs[CIRRUS_PWR_NUM_ATTRS_AMP] = { + { + .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0444)}, + .show = cirrus_pwr_power_show, + .store = cirrus_pwr_power_store, + }, + { + .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0664)}, + .show = cirrus_pwr_target_temp_show, + .store = cirrus_pwr_target_temp_store, + }, + { + .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0664)}, + .show = cirrus_pwr_exit_temp_show, + .store = cirrus_pwr_exit_temp_store, + }, + { + .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0664)}, + .show = cirrus_pwr_amb_temp_show, + .store = cirrus_pwr_amb_temp_store, + }, + { + .attr = {.mode = VERIFY_OCTAL_PERMISSIONS(0664)}, + .show = cirrus_pwr_spk_temp_show, + .store = cirrus_pwr_spk_temp_store, + }, +}; + +static const char *generic_amp_attr_names[CIRRUS_PWR_NUM_ATTRS_AMP] = { + "value", + "target_temp", + "exit_temp", + "env_temp", + "spk_t", +}; + +static struct attribute_group cirrus_pwr_attr_grp; +static struct device_attribute + amp_attrs_prealloc[CIRRUS_MAX_AMPS][CIRRUS_PWR_NUM_ATTRS_AMP]; +static char attr_names_prealloc[CIRRUS_MAX_AMPS][CIRRUS_PWR_NUM_ATTRS_AMP][20]; + +struct device_attribute *cirrus_pwr_create_amp_attrs(const char *mfd_suffix, + int index) +{ + struct device_attribute *amp_attrs_new; + int i, suffix_len = strlen(mfd_suffix); + + if (index >= CIRRUS_MAX_AMPS) + return NULL; + + amp_attrs_new = &(amp_attrs_prealloc[index][0]); + + memcpy(amp_attrs_new, &generic_amp_attrs, + sizeof(struct device_attribute) * + CIRRUS_PWR_NUM_ATTRS_AMP); + + for (i = 0; i < CIRRUS_PWR_NUM_ATTRS_AMP; i++) { + amp_attrs_new[i].attr.name = attr_names_prealloc[index][i]; + snprintf((char *)amp_attrs_new[i].attr.name, + strlen(generic_amp_attr_names[i]) + suffix_len + 1, + "%s%s", generic_amp_attr_names[i], mfd_suffix); + } + + return amp_attrs_new; +} + +int cirrus_pwr_init(void) +{ + struct device_attribute *new_attrs; + struct cirrus_amp *amp; + int ret = 0, i, j, num_amps; + + if (!amp_group) { + pr_err("%s: Empty amp group\n", __func__); + return -ENODATA; + } + + amp_group->pwr_dev = device_create(cirrus_amp_class, NULL, 1, NULL, + CIRRUS_PWR_DIR_NAME); + if (IS_ERR(amp_group->pwr_dev)) { + ret = PTR_ERR(amp_group->pwr_dev); + pr_err("%s: Failed to create PWR device (%d)\n", __func__, ret); + return ret; + } + + dev_set_drvdata(amp_group->pwr_dev, amp_group); + + num_amps = amp_group->num_amps; + + for (i = 0; i < num_amps; i++) { + amp_group->amps[i].pwr.amb_temp = 2500; + amp_group->amps[i].pwr.spk_temp = 2500; + amp_group->amps[i].pwr.target_temp = 3400; + amp_group->amps[i].pwr.exit_temp = 3250; + amp_group->amps[i].pwr.passport_enable = 0; + } + + cirrus_pwr_attr_grp.attrs = kzalloc(sizeof(struct attribute *) * + (CIRRUS_PWR_NUM_ATTRS_AMP * num_amps + + CIRRUS_PWR_NUM_ATTRS_BASE + 1), + GFP_KERNEL); + for (i = 0; i < num_amps; i++) { + amp = &_group->amps[i]; + new_attrs = cirrus_pwr_create_amp_attrs(amp->mfd_suffix, i); + for (j = 0; j < CIRRUS_PWR_NUM_ATTRS_AMP; j++) { + dev_dbg(amp_group->pwr_dev, "New attribute: %s\n", + new_attrs[j].attr.name); + cirrus_pwr_attr_grp.attrs[i * CIRRUS_PWR_NUM_ATTRS_AMP + + j] = &new_attrs[j].attr; + } + } + + memcpy(&cirrus_pwr_attr_grp.attrs[num_amps * CIRRUS_PWR_NUM_ATTRS_AMP], + cirrus_pwr_attr_base, sizeof(struct attribute *) * + CIRRUS_PWR_NUM_ATTRS_BASE); + cirrus_pwr_attr_grp.attrs[num_amps * CIRRUS_PWR_NUM_ATTRS_AMP + + CIRRUS_PWR_NUM_ATTRS_BASE] = NULL; + + amp_group->pwr_workqueue = create_singlethread_workqueue( + CIRRUS_PWR_WORKQ_NAME); + if (amp_group->pwr_workqueue == NULL) { + dev_err(amp_group->pwr_dev, "Failed to create workqueue\n"); + ret = -ENOENT; + goto err; + } + + amp_group->interval = 10000; + amp_group->uptime_ms = 0; + amp_group->target_min_time_ms = 300000; + amp_group->pwr_enable = 0; + + ret = sysfs_create_group(&_group->pwr_dev->kobj, + &cirrus_pwr_attr_grp); + if (ret) { + dev_err(amp_group->pwr_dev, "Failed to create sysfs group\n"); + goto err; + } + + mutex_init(&_group->pwr_lock); + INIT_DELAYED_WORK(&_group->pwr_work, cirrus_pwr_work); + + return 0; + +err: + return ret; +} + +void cirrus_pwr_exit(void) +{ + kfree(cirrus_pwr_attr_grp.attrs); + device_del(amp_group->pwr_dev); +} + diff --git a/sound/soc/codecs/cs35l43-i2c.c b/sound/soc/codecs/cs35l43-i2c.c new file mode 100644 index 000000000000..d78438b7a376 --- /dev/null +++ b/sound/soc/codecs/cs35l43-i2c.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * cs35l43-i2c.c -- CS35l41 I2C driver + * + * Copyright 2020 Cirrus Logic, Inc. + * + * Author: David Rhodes + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm_adsp.h" +#include "cs35l43.h" +#include + +static struct regmap_config cs35l43_regmap_i2c = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_BIG, + .max_register = CS35L43_DSP1_PMEM_5114, + .reg_defaults = cs35l43_reg, + .num_reg_defaults = CS35L43_NUM_DEFAULTS, + .volatile_reg = cs35l43_volatile_reg, + .readable_reg = cs35l43_readable_reg, + .precious_reg = cs35l43_precious_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct i2c_device_id cs35l43_id_i2c[] = { + {"cs35l43", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, cs35l43_id_i2c); + +static int cs35l43_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct cs35l43_private *cs35l43; + struct device *dev = &client->dev; + struct cs35l43_platform_data *pdata = dev_get_platdata(dev); + const struct regmap_config *regmap_config = &cs35l43_regmap_i2c; + int ret; + + cs35l43 = devm_kzalloc(dev, sizeof(struct cs35l43_private), GFP_KERNEL); + + if (cs35l43 == NULL) + return -ENOMEM; + + cs35l43->dev = dev; + cs35l43->irq = client->irq; + + i2c_set_clientdata(client, cs35l43); + cs35l43->regmap = devm_regmap_init_i2c(client, regmap_config); + if (IS_ERR(cs35l43->regmap)) { + ret = PTR_ERR(cs35l43->regmap); + dev_err(cs35l43->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + return cs35l43_probe(cs35l43, pdata); +} + +static void cs35l43_i2c_remove(struct i2c_client *client) +{ + struct cs35l43_private *cs35l43 = i2c_get_clientdata(client); + + cs35l43_remove(cs35l43); +} + +static const struct of_device_id cs35l43_of_match[] = { + {.compatible = "cirrus,cs35l43"}, + {}, +}; +MODULE_DEVICE_TABLE(of, cs35l43_of_match); + +static struct i2c_driver cs35l43_i2c_driver = { + .driver = { + .name = "cs35l43", + .of_match_table = cs35l43_of_match, + .pm = &cs35l43_pm_ops, + }, + .id_table = cs35l43_id_i2c, + .probe = cs35l43_i2c_probe, + .remove = cs35l43_i2c_remove, +}; + +module_i2c_driver(cs35l43_i2c_driver); + +MODULE_DESCRIPTION("I2C CS35L43 driver"); +MODULE_AUTHOR("David Rhodes, Cirrus Logic Inc, "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs35l43-spi.c b/sound/soc/codecs/cs35l43-spi.c new file mode 100644 index 000000000000..8018e36c22c6 --- /dev/null +++ b/sound/soc/codecs/cs35l43-spi.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * cs35l43-spi.c -- CS35l41 SPI driver + * + * Copyright 2017 Cirrus Logic, Inc. + * + * Author: David Rhodes + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm_adsp.h" +#include "cs35l43.h" +#include + +static struct regmap_config cs35l43_regmap_spi = { + .reg_bits = 32, + .val_bits = 32, + .pad_bits = 16, + .reg_stride = 4, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_BIG, + .max_register = CS35L43_DSP1_PMEM_5114, + .reg_defaults = cs35l43_reg, + .num_reg_defaults = CS35L43_NUM_DEFAULTS, + .volatile_reg = cs35l43_volatile_reg, + .readable_reg = cs35l43_readable_reg, + .precious_reg = cs35l43_precious_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct spi_device_id cs35l43_id_spi[] = { + {"cs35l43", 0}, + {} +}; + +MODULE_DEVICE_TABLE(spi, cs35l43_id_spi); + +static void cs35l43_limit_spi_clock(struct cs35l43_private *cs35l43, bool state) +{ + struct spi_device *spi; + + spi = to_spi_device(cs35l43->dev); + if (!spi) { + dev_err(cs35l43->dev, "%s: No SPI device\n", __func__); + return; + } + + if (state) + spi->max_speed_hz = CS35L43_SPI_MAX_FREQ_NO_PLL; + else + spi->max_speed_hz = cs35l43->max_spi_freq; + + dev_dbg(&spi->dev, "Set SPI freq: %d\n", spi->max_speed_hz); + spi_setup(spi); +} + +static int cs35l43_spi_probe(struct spi_device *spi) +{ + const struct regmap_config *regmap_config = &cs35l43_regmap_spi; + struct cs35l43_platform_data *pdata = + dev_get_platdata(&spi->dev); + struct cs35l43_private *cs35l43; + int ret; + + cs35l43 = devm_kzalloc(&spi->dev, + sizeof(struct cs35l43_private), + GFP_KERNEL); + if (cs35l43 == NULL) + return -ENOMEM; + + spi_set_drvdata(spi, cs35l43); + cs35l43->regmap = devm_regmap_init_spi(spi, regmap_config); + if (IS_ERR(cs35l43->regmap)) { + ret = PTR_ERR(cs35l43->regmap); + dev_err(&spi->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + cs35l43->dev = &spi->dev; + cs35l43->irq = spi->irq; + cs35l43->max_spi_freq = spi->max_speed_hz; + + cs35l43->limit_spi_clock = cs35l43_limit_spi_clock; + + cs35l43_limit_spi_clock(cs35l43, true); + + return cs35l43_probe(cs35l43, pdata); +} + +static void cs35l43_spi_remove(struct spi_device *spi) +{ + struct cs35l43_private *cs35l43 = spi_get_drvdata(spi); + + cs35l43_remove(cs35l43); +} + +static const struct of_device_id cs35l43_of_match[] = { + {.compatible = "cirrus,cs35l43"}, + {}, +}; +MODULE_DEVICE_TABLE(of, cs35l43_of_match); + +static struct spi_driver cs35l43_spi_driver = { + .driver = { + .name = "cs35l43", + .of_match_table = cs35l43_of_match, + .pm = &cs35l43_pm_ops, + }, + .id_table = cs35l43_id_spi, + .probe = cs35l43_spi_probe, + .remove = cs35l43_spi_remove, +}; + +module_spi_driver(cs35l43_spi_driver); + +MODULE_DESCRIPTION("SPI CS35L43 driver"); +MODULE_AUTHOR("David Rhodes, Cirrus Logic Inc, "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs35l43-tables.c b/sound/soc/codecs/cs35l43-tables.c new file mode 100644 index 000000000000..3940da428b10 --- /dev/null +++ b/sound/soc/codecs/cs35l43-tables.c @@ -0,0 +1,550 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * cs35l43-tables.c -- CS35L43 ALSA SoC audio driver + * + * Copyright 2021 Cirrus Logic, Inc. + * + * Author: David Rhodes + * + * 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. + * + */ + +#include +#include +#include "wm_adsp.h" +#include "cs35l43.h" +#include + +bool cs35l43_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L43_DEVID: + case CS35L43_REVID: + case CS35L43_FABID: + case CS35L43_RELID: + case CS35L43_OTPID: + case CS35L43_SFT_RESET: + case CS35L43_TEST_KEY_CTRL: + case CS35L43_USER_KEY_CTRL: + case CS35L43_CTRL_ASYNC0: + case CS35L43_CTRL_ASYNC1: + case CS35L43_CTRL_ASYNC2: + case CS35L43_CTRL_ASYNC3: + case CS35L43_CTRL_IF_CONFIG1: + case CS35L43_CTRL_IF_STATUS1: + case CS35L43_CTRL_IF_STATUS2: + case CS35L43_CTRL_IF_CONFIG2: + case CS35L43_CTRL_IF_DEBUG1: + case CS35L43_CTRL_IF_DEBUG2: + case CS35L43_CTRL_IF_DEBUG3: + case CS35L43_CIF_MON1: + case CS35L43_CIF_MON2: + case CS35L43_CIF_MON_PADDR: + case CS35L43_CTRL_IF_SPARE1: + case CS35L43_CTRL_IF_I2C: + case CS35L43_CTRL_IF_I2C_1_CONTROL: + case CS35L43_CTRL_IF_I2C_1_BROADCAST: + case CS35L43_APB_MSTR_DSP_BRIDGE_ERR: + case CS35L43_CIF1_BRIDGE_ERR: + case CS35L43_CIF2_BRIDGE_ERR: + case CS35L43_OTP_MEM0 ... CS35L43_OTP_MEM31: + case CS35L43_OTP_CTRL0: + case CS35L43_OTP_CTRL1: + case CS35L43_OTP_CTRL3: + case CS35L43_OTP_CTRL4: + case CS35L43_OTP_CTRL5: + case CS35L43_OTP_CTRL6: + case CS35L43_OTP_CTRL7: + case CS35L43_OTP_CTRL8: + case CS35L43_DEVICE_ID: + case CS35L43_FAB_ID: + case CS35L43_REV_ID: + case CS35L43_GLOBAL_ENABLES: + case CS35L43_BLOCK_ENABLES: + case CS35L43_BLOCK_ENABLES2: + case CS35L43_GLOBAL_OVERRIDES: + case CS35L43_GLOBAL_SYNC: + case CS35L43_GLOBAL_STATUS: + case CS35L43_DISCH_FILT: + case CS35L43_OSC_TRIM: + case CS35L43_ERROR_RELEASE: + case CS35L43_PLL_OVERRIDE: + case CS35L43_CHIP_STATUS: + case CS35L43_CHIP_STATUS2: + case CS35L43_TST_OSC: + case CS35L43_LRCK_PAD_CONTROL: + case CS35L43_SCLK_PAD_CONTROL: + case CS35L43_SDIN_PAD_CONTROL: + case CS35L43_SDOUT_PAD_CONTROL: + case CS35L43_GPIO_PAD_CONTROL: + case CS35L43_GPIO_GLOBAL_ENABLE_CONTROL: + case CS35L43_PWRMGT_CTL: + case CS35L43_WAKESRC_CTL: + case CS35L43_WAKEI2C_CTL: + case CS35L43_PWRMGT_STS: + case CS35L43_PWRMGT_RST: + case CS35L43_TEST_CTL: + case CS35L43_REFCLK_INPUT: + case CS35L43_DSP_CLOCK_GEARING: + case CS35L43_GLOBAL_SAMPLE_RATE: + case CS35L43_FS_MON_0: + case CS35L43_DSP1_SAMPLE_RATE_RX1: + case CS35L43_DSP1_SAMPLE_RATE_RX2: + case CS35L43_DSP1_SAMPLE_RATE_TX1: + case CS35L43_DSP1_SAMPLE_RATE_TX2: + case CS35L43_LDOA_CTRL: + case CS35L43_SYNC_TX_RX_ENABLES: + case CS35L43_VBST_CTL_1: + case CS35L43_VBST_CTL_2: + case CS35L43_BST_IPK_CTL: + case CS35L43_SOFT_RAMP: + case CS35L43_BST_LOOP_COEFF: + case CS35L43_LBST_SLOPE: + case CS35L43_BST_SW_FREQ: + case CS35L43_BST_DCM_CTL: + case CS35L43_DCM_FORCE: + case CS35L43_VBST_OVP: + case CS35L43_BST_RSVD_1: + case CS35L43_MONITOR_FILT: + case CS35L43_WARN_LIMIT_THRESHOLD: + case CS35L43_CONFIGURATION: + case CS35L43_STATUS: + case CS35L43_ENABLES_AND_CODES_ANA: + case CS35L43_ENABLES_AND_CODES_DIG: + case CS35L43_ASP_ENABLES1: + case CS35L43_ASP_CONTROL1: + case CS35L43_ASP_CONTROL2: + case CS35L43_ASP_CONTROL3: + case CS35L43_ASP_FRAME_CONTROL1: + case CS35L43_ASP_FRAME_CONTROL5: + case CS35L43_ASP_DATA_CONTROL1: + case CS35L43_ASP_DATA_CONTROL5: + case CS35L43_DACPCM1_INPUT: + case CS35L43_DACPCM2_INPUT: + case CS35L43_ASPTX1_INPUT: + case CS35L43_ASPTX2_INPUT: + case CS35L43_ASPTX3_INPUT: + case CS35L43_ASPTX4_INPUT: + case CS35L43_DSP1RX1_INPUT: + case CS35L43_DSP1RX2_INPUT: + case CS35L43_DSP1RX3_INPUT: + case CS35L43_DSP1RX4_INPUT: + case CS35L43_DSP1RX5_INPUT: + case CS35L43_DSP1RX6_INPUT: + case CS35L43_NGATE1_INPUT: + case CS35L43_NGATE2_INPUT: + case CS35L43_AMP_CTRL: + case CS35L43_HPF_TST: + case CS35L43_VC_TST1: + case CS35L43_VC_TST2: + case CS35L43_INTP_TST: + case CS35L43_SRC_MAGCOMP_TST: + case CS35L43_SRC_MAGCOMP_B0_OVERRIDE: + case CS35L43_SRC_MAGCOMP_B1_OVERRIDE: + case CS35L43_SRC_MAGCOMP_A1_N_OVERRIDE: + case CS35L43_VPBR_CONFIG: + case CS35L43_VBBR_CONFIG: + case CS35L43_VPBR_STATUS: + case CS35L43_VBBR_STATUS: + case CS35L43_OTW_CONFIG: + case CS35L43_AMP_ERROR_VOL_SEL: + case CS35L43_VOL_STATUS_TO_DSP: + case CS35L43_CLASSH_CONFIG: + case CS35L43_WKFET_AMP_CONFIG: + case CS35L43_NG_CONFIG: + case CS35L43_AMP_GAIN: + case CS35L43_DAC_MSM_CONFIG: + case CS35L43_TST_DAC_MSM_CONFIG: + case CS35L43_ALIVE_DCIN_WD: + case CS35L43_SPKMON_OTP_3: + case CS35L43_IRQ1_CFG: + case CS35L43_IRQ1_STATUS: + case CS35L43_IRQ1_EINT_1: + case CS35L43_IRQ1_EINT_2: + case CS35L43_IRQ1_EINT_3: + case CS35L43_IRQ1_EINT_4: + case CS35L43_IRQ1_EINT_5: + case CS35L43_IRQ1_STS_1: + case CS35L43_IRQ1_STS_2: + case CS35L43_IRQ1_STS_3: + case CS35L43_IRQ1_STS_4: + case CS35L43_IRQ1_STS_5: + case CS35L43_IRQ1_MASK_1: + case CS35L43_IRQ1_MASK_2: + case CS35L43_IRQ1_MASK_3: + case CS35L43_IRQ1_MASK_4: + case CS35L43_IRQ1_MASK_5: + case CS35L43_IRQ1_FRC_1: + case CS35L43_IRQ1_FRC_2: + case CS35L43_IRQ1_FRC_3: + case CS35L43_IRQ1_FRC_4: + case CS35L43_IRQ1_FRC_5: + case CS35L43_IRQ1_EDGE_1: + case CS35L43_IRQ1_EDGE_4: + case CS35L43_IRQ1_POL_1: + case CS35L43_IRQ1_POL_2: + case CS35L43_IRQ1_POL_3: + case CS35L43_IRQ1_POL_4: + case CS35L43_IRQ1_DB_2: + case CS35L43_GPIO_STATUS1: + case CS35L43_GPIO_FORCE: + case CS35L43_GPIO1_CTRL1: + case CS35L43_GPIO2_CTRL1: + case CS35L43_GPIO3_CTRL1: + case CS35L43_GPIO4_CTRL1: + case CS35L43_MIXER_NGATE_CFG: + case CS35L43_MIXER_NGATE_CH1_CFG: + case CS35L43_MIXER_NGATE_CH2_CFG: + case CS35L43_DSP_MBOX_1: + case CS35L43_DSP_MBOX_2: + case CS35L43_DSP_MBOX_3: + case CS35L43_DSP_MBOX_4: + case CS35L43_DSP_MBOX_5: + case CS35L43_DSP_MBOX_6: + case CS35L43_DSP_MBOX_7: + case CS35L43_DSP_MBOX_8: + case CS35L43_DSP_VIRTUAL1_MBOX_1: + case CS35L43_DSP_VIRTUAL1_MBOX_2: + case CS35L43_DSP_VIRTUAL1_MBOX_3: + case CS35L43_DSP_VIRTUAL1_MBOX_4: + case CS35L43_DSP_VIRTUAL1_MBOX_5: + case CS35L43_DSP_VIRTUAL1_MBOX_6: + case CS35L43_DSP_VIRTUAL1_MBOX_7: + case CS35L43_DSP_VIRTUAL1_MBOX_8: + case CS35L43_DSP_VIRTUAL2_MBOX_1: + case CS35L43_DSP_VIRTUAL2_MBOX_2: + case CS35L43_DSP_VIRTUAL2_MBOX_3: + case CS35L43_DSP_VIRTUAL2_MBOX_4: + case CS35L43_DSP_VIRTUAL2_MBOX_5: + case CS35L43_DSP_VIRTUAL2_MBOX_6: + case CS35L43_DSP_VIRTUAL2_MBOX_7: + case CS35L43_DSP_VIRTUAL2_MBOX_8: + case CS35L43_DSP1_SYS_INFO_ID: + case CS35L43_DSP1_CLOCK_FREQ: + case CS35L43_DSP1_CORE_SOFT_RESET: + case CS35L43_DSP1_SCRATCH1: + case CS35L43_DSP1_SCRATCH2: + case CS35L43_DSP1_SCRATCH3: + case CS35L43_DSP1_SCRATCH4: + case CS35L43_DSP1_CCM_CORE_CONTROL: + case CS35L43_DSP1_MPU_LOCK_STATE: + case CS35L43_DSP1_MPU_XM_VIO_STATUS: + case CS35L43_DSP1_MPU_YM_VIO_STATUS: + case CS35L43_DSP1_MPU_PM_VIO_STATUS: + case CS35L43_DSP1_WDT_CONTROL: + case CS35L43_DSP1_WDT_STATUS: + case CS35L43_DSP1_XMEM_PACKED_0 ... CS35L43_DSP1_XMEM_PACKED_6143: + case CS35L43_DSP1_XMEM_UNPACKED32_0 ... + CS35L43_DSP1_XMEM_UNPACKED32_4095: + case CS35L43_DSP1_XMEM_UNPACKED24_0 ... + CS35L43_DSP1_XMEM_UNPACKED24_8191: + case CS35L43_DSP1_XROM_UNPACKED24_0 ... + CS35L43_DSP1_XROM_UNPACKED24_6141: + case CS35L43_DSP1_YMEM_PACKED_0 ... CS35L43_DSP1_YMEM_PACKED_1532: + case CS35L43_DSP1_YMEM_UNPACKED32_0 ... + CS35L43_DSP1_YMEM_UNPACKED32_1022: + case CS35L43_DSP1_YMEM_UNPACKED24_0 ... + CS35L43_DSP1_YMEM_UNPACKED24_2045: + case CS35L43_DSP1_PMEM_0 ... CS35L43_DSP1_PMEM_5114: + return true; + default: + return false; + } +} + +bool cs35l43_precious_reg(struct device *dev, unsigned int reg) +{ + + switch (reg) { + case CS35L43_DSP1_XMEM_PACKED_0 ... CS35L43_DSP1_XMEM_PACKED_6143: + case CS35L43_DSP1_XMEM_UNPACKED32_0 ... + CS35L43_DSP1_XMEM_UNPACKED32_4095: + case CS35L43_DSP1_XROM_UNPACKED24_0 ... + CS35L43_DSP1_XROM_UNPACKED24_6141: + case CS35L43_DSP1_YMEM_PACKED_0 ... CS35L43_DSP1_YMEM_PACKED_1532: + case CS35L43_DSP1_YMEM_UNPACKED32_0 ... + CS35L43_DSP1_YMEM_UNPACKED32_1022: + case CS35L43_DSP1_PMEM_0 ... CS35L43_DSP1_PMEM_5114: + return true; + default: + return false; + } + + return false; +} + +bool cs35l43_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L43_GLOBAL_STATUS: + case CS35L43_CHIP_STATUS: + case CS35L43_CHIP_STATUS2: + case CS35L43_STATUS: + case CS35L43_ENABLES_AND_CODES_ANA: + case CS35L43_ENABLES_AND_CODES_DIG: + case CS35L43_VBST_CTL_1: + case CS35L43_VBST_CTL_2: + case CS35L43_VPBR_STATUS: + case CS35L43_VBBR_STATUS: + case CS35L43_SPKMON_OTP_3: + case CS35L43_IRQ1_STATUS: + case CS35L43_IRQ1_EINT_1: + case CS35L43_IRQ1_EINT_2: + case CS35L43_IRQ1_EINT_3: + case CS35L43_IRQ1_EINT_4: + case CS35L43_IRQ1_EINT_5: + case CS35L43_IRQ1_STS_1: + case CS35L43_IRQ1_STS_2: + case CS35L43_IRQ1_STS_3: + case CS35L43_IRQ1_STS_4: + case CS35L43_IRQ1_STS_5: + case CS35L43_GPIO_STATUS1: + case CS35L43_DSP_MBOX_1: + case CS35L43_DSP_MBOX_2: + case CS35L43_DSP_MBOX_3: + case CS35L43_DSP_MBOX_4: + case CS35L43_DSP_MBOX_5: + case CS35L43_DSP_MBOX_6: + case CS35L43_DSP_MBOX_7: + case CS35L43_DSP_MBOX_8: + case CS35L43_DSP_VIRTUAL1_MBOX_1: + case CS35L43_DSP_VIRTUAL1_MBOX_2: + case CS35L43_DSP_VIRTUAL1_MBOX_3: + case CS35L43_DSP_VIRTUAL1_MBOX_4: + case CS35L43_DSP_VIRTUAL1_MBOX_5: + case CS35L43_DSP_VIRTUAL1_MBOX_6: + case CS35L43_DSP_VIRTUAL1_MBOX_7: + case CS35L43_DSP_VIRTUAL1_MBOX_8: + case CS35L43_DSP_VIRTUAL2_MBOX_1: + case CS35L43_DSP_VIRTUAL2_MBOX_2: + case CS35L43_DSP_VIRTUAL2_MBOX_3: + case CS35L43_DSP_VIRTUAL2_MBOX_4: + case CS35L43_DSP_VIRTUAL2_MBOX_5: + case CS35L43_DSP_VIRTUAL2_MBOX_6: + case CS35L43_DSP_VIRTUAL2_MBOX_7: + case CS35L43_DSP_VIRTUAL2_MBOX_8: + case CS35L43_DSP1_CORE_SOFT_RESET: + case CS35L43_DSP1_SCRATCH1: + case CS35L43_DSP1_SCRATCH2: + case CS35L43_DSP1_SCRATCH3: + case CS35L43_DSP1_SCRATCH4: + case CS35L43_DSP1_MPU_LOCK_STATE: + case CS35L43_DSP1_MPU_XM_VIO_STATUS: + case CS35L43_DSP1_MPU_YM_VIO_STATUS: + case CS35L43_DSP1_MPU_PM_VIO_STATUS: + case CS35L43_DSP1_WDT_STATUS: + case CS35L43_DSP1_XMEM_PACKED_0 ... + CS35L43_DSP1_XMEM_PACKED_6143: + case CS35L43_DSP1_XMEM_UNPACKED32_0 ... + CS35L43_DSP1_XMEM_UNPACKED32_4095: + case CS35L43_DSP1_XMEM_UNPACKED24_0 ... + CS35L43_DSP1_XMEM_UNPACKED24_8191: + case CS35L43_DSP1_XROM_UNPACKED24_0 ... + CS35L43_DSP1_XROM_UNPACKED24_6141: + case CS35L43_DSP1_YMEM_PACKED_0 ... + CS35L43_DSP1_YMEM_PACKED_1532: + case CS35L43_DSP1_YMEM_UNPACKED32_0 ... + CS35L43_DSP1_YMEM_UNPACKED32_1022: + case CS35L43_DSP1_YMEM_UNPACKED24_0 ... + CS35L43_DSP1_YMEM_UNPACKED24_2045: + case CS35L43_DSP1_PMEM_0 ... + CS35L43_DSP1_PMEM_5114: + return true; + default: + return false; + } +} + +const struct reg_default cs35l43_reg[CS35L43_NUM_DEFAULTS] = { + { CS35L43_CTRL_ASYNC0, 0x00000000 }, + { CS35L43_CTRL_ASYNC1, 0x00000004 }, + { CS35L43_CTRL_ASYNC2, 0x00000000 }, + { CS35L43_CTRL_ASYNC3, 0x00000000 }, + { CS35L43_CTRL_IF_CONFIG1, 0x00020002 }, + { CS35L43_CTRL_IF_STATUS1, 0x00000000 }, + { CS35L43_CTRL_IF_STATUS2, 0x00000000 }, + { CS35L43_CTRL_IF_CONFIG2, 0x00000000 }, + { CS35L43_CTRL_IF_DEBUG1, 0x00000000 }, + { CS35L43_CTRL_IF_DEBUG2, 0x00000000 }, + { CS35L43_CTRL_IF_DEBUG3, 0x00000000 }, + { CS35L43_CIF_MON1, 0x00002003 }, + { CS35L43_CIF_MON2, 0x00000000 }, + { CS35L43_CIF_MON_PADDR, 0x00000000 }, + { CS35L43_CTRL_IF_SPARE1, 0x00000000 }, + { CS35L43_CTRL_IF_I2C, 0x00000004 }, + { CS35L43_CTRL_IF_I2C_1_CONTROL, 0x00000040 }, + { CS35L43_CTRL_IF_I2C_1_BROADCAST, 0x00000088 }, + { CS35L43_APB_MSTR_DSP_BRIDGE_ERR, 0x00000000 }, + { CS35L43_CIF1_BRIDGE_ERR, 0x00000000 }, + { CS35L43_CIF2_BRIDGE_ERR, 0x00000000 }, + { CS35L43_LRCK_PAD_CONTROL, 0x00000007 }, + { CS35L43_SCLK_PAD_CONTROL, 0x00000007 }, + { CS35L43_SDIN_PAD_CONTROL, 0x00000007 }, + { CS35L43_HPF_TST, 0x00000000 }, + { CS35L43_VC_TST1, 0x00000000 }, + { CS35L43_VC_TST2, 0x00000000 }, + { CS35L43_INTP_TST, 0x00000680 }, + { CS35L43_SRC_MAGCOMP_TST, 0x0000000D }, + { CS35L43_SRC_MAGCOMP_B0_OVERRIDE, 0x00000000 }, + { CS35L43_SRC_MAGCOMP_B1_OVERRIDE, 0x00000000 }, + { CS35L43_SRC_MAGCOMP_A1_N_OVERRIDE, 0x00000000 }, + { CS35L43_OTW_CONFIG, 0x00000001 }, + { CS35L43_AMP_ERROR_VOL_SEL, 0x00000000 }, + { CS35L43_VOL_STATUS_TO_DSP, 0x00000000 }, + { CS35L43_IRQ1_POL_1, 0x00000000 }, + { CS35L43_IRQ1_POL_2, 0x00000000 }, + { CS35L43_IRQ1_POL_3, 0x00000000 }, + { CS35L43_IRQ1_POL_4, 0x00000000 }, + { CS35L43_GPIO3_CTRL1, 0x80000001 }, + { CS35L43_GPIO4_CTRL1, 0x80000001 }, +}; + +const unsigned int cs35l43_hibernate_update_regs[CS35L43_POWER_SEQ_LENGTH] = { + CS35L43_ASPTX1_INPUT, + CS35L43_ASPTX2_INPUT, + CS35L43_ASPTX3_INPUT, + CS35L43_ASPTX4_INPUT, + CS35L43_DSP1RX1_INPUT, + CS35L43_DSP1RX2_INPUT, + CS35L43_DSP1RX3_INPUT, + CS35L43_DSP1RX4_INPUT, + CS35L43_DSP1RX5_INPUT, + CS35L43_DSP1RX6_INPUT, + CS35L43_DACPCM1_INPUT, + CS35L43_DACPCM2_INPUT, + CS35L43_ASP_FRAME_CONTROL1, + CS35L43_ASP_FRAME_CONTROL5, + CS35L43_AMP_CTRL, + CS35L43_AMP_GAIN, + CS35L43_GLOBAL_SAMPLE_RATE, + CS35L43_DSP1_SAMPLE_RATE_RX1, + CS35L43_DSP1_SAMPLE_RATE_RX2, + CS35L43_DSP1_SAMPLE_RATE_TX1, + CS35L43_DSP1_SAMPLE_RATE_TX2, + CS35L43_ALIVE_DCIN_WD, + CS35L43_MONITOR_FILT, + CS35L43_DAC_MSM_CONFIG, + CS35L43_ASP_CONTROL2, + CS35L43_ASP_CONTROL3, + CS35L43_ASP_DATA_CONTROL1, + CS35L43_ASP_DATA_CONTROL5, + CS35L43_GPIO_PAD_CONTROL, + CS35L43_VBST_CTL_1, + CS35L43_VBST_CTL_2, + CS35L43_BST_IPK_CTL, + CS35L43_VPBR_CONFIG, + CS35L43_GLOBAL_SYNC, + CS35L43_BLOCK_ENABLES, + CS35L43_BLOCK_ENABLES2, + CS35L43_NG_CONFIG, + CS35L43_MIXER_NGATE_CH1_CFG, + CS35L43_MIXER_NGATE_CH2_CFG, + CS35L43_FS_MON_0, + CS35L43_TST_DAC_MSM_CONFIG, +}; + +const struct cs35l43_pll_sysclk_config cs35l43_pll_sysclk[64] = { + { 32768, 0x00 }, + { 8000, 0x01 }, + { 11025, 0x02 }, + { 12000, 0x03 }, + { 16000, 0x04 }, + { 22050, 0x05 }, + { 24000, 0x06 }, + { 32000, 0x07 }, + { 44100, 0x08 }, + { 48000, 0x09 }, + { 88200, 0x0A }, + { 96000, 0x0B }, + { 128000, 0x0C }, + { 176400, 0x0D }, + { 192000, 0x0E }, + { 256000, 0x0F }, + { 352800, 0x10 }, + { 384000, 0x11 }, + { 512000, 0x12 }, + { 705600, 0x13 }, + { 750000, 0x14 }, + { 768000, 0x15 }, + { 1000000, 0x16 }, + { 1024000, 0x17 }, + { 1200000, 0x18 }, + { 1411200, 0x19 }, + { 1500000, 0x1A }, + { 1536000, 0x1B }, + { 2000000, 0x1C }, + { 2048000, 0x1D }, + { 2400000, 0x1E }, + { 2822400, 0x1F }, + { 3000000, 0x20 }, + { 3072000, 0x21 }, + { 3200000, 0x22 }, + { 4000000, 0x23 }, + { 4096000, 0x24 }, + { 4800000, 0x25 }, + { 5644800, 0x26 }, + { 6000000, 0x27 }, + { 6144000, 0x28 }, + { 6250000, 0x29 }, + { 6400000, 0x2A }, + { 6500000, 0x2B }, + { 6750000, 0x2C }, + { 7526400, 0x2D }, + { 8000000, 0x2E }, + { 8192000, 0x2F }, + { 9600000, 0x30 }, + { 11289600, 0x31 }, + { 12000000, 0x32 }, + { 12288000, 0x33 }, + { 12500000, 0x34 }, + { 12800000, 0x35 }, + { 13000000, 0x36 }, + { 13500000, 0x37 }, + { 19200000, 0x38 }, + { 22579200, 0x39 }, + { 24000000, 0x3A }, + { 24576000, 0x3B }, + { 25000000, 0x3C }, + { 25600000, 0x3D }, + { 26000000, 0x3E }, + { 27000000, 0x3F }, +}; + +const struct cs35l43_fs_mon_config cs35l43_fs_mon[7] = { + { 705600, 154, 244 }, + { 768000, 141, 224 }, + { 1411200, 77, 125 }, + { 1536000, 71, 115 }, + { 2822400, 39, 65 }, + { 3072000, 36, 60 }, + { 5644800, 20, 35 }, +}; + +const u8 cs35l43_write_seq_op_sizes[CS35L43_POWER_SEQ_NUM_OPS][2] = { + { CS35L43_POWER_SEQ_OP_WRITE_REG_FULL, + CS35L43_POWER_SEQ_OP_WRITE_REG_FULL_WORDS}, + { CS35L43_POWER_SEQ_OP_WRITE_FIELD, + CS35L43_POWER_SEQ_OP_WRITE_FIELD_WORDS}, + { CS35L43_POWER_SEQ_OP_WRITE_REG_ADDR8, + CS35L43_POWER_SEQ_OP_WRITE_REG_ADDR8_WORDS}, + { CS35L43_POWER_SEQ_OP_WRITE_REG_INCR, + CS35L43_POWER_SEQ_OP_WRITE_REG_INCR_WORDS}, + { CS35L43_POWER_SEQ_OP_WRITE_REG_L16, + CS35L43_POWER_SEQ_OP_WRITE_REG_L16_WORDS}, + { CS35L43_POWER_SEQ_OP_WRITE_REG_H16, + CS35L43_POWER_SEQ_OP_WRITE_REG_H16_WORDS}, + { CS35L43_POWER_SEQ_OP_DELAY, + CS35L43_POWER_SEQ_OP_DELAY_WORDS}, + { CS35L43_POWER_SEQ_OP_END, + CS35L43_POWER_SEQ_OP_END_WORDS}, +}; + +const struct dev_pm_ops cs35l43_pm_ops = { + SET_RUNTIME_PM_OPS(cs35l43_suspend_runtime, cs35l43_resume_runtime, NULL) + SET_SYSTEM_SLEEP_PM_OPS(cs35l43_sys_suspend, cs35l43_sys_resume) + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(cs35l43_sys_suspend_noirq, cs35l43_sys_resume_noirq) +}; diff --git a/sound/soc/codecs/cs35l43.c b/sound/soc/codecs/cs35l43.c new file mode 100644 index 000000000000..95d757efb632 --- /dev/null +++ b/sound/soc/codecs/cs35l43.c @@ -0,0 +1,3535 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * cs35l43.c -- CS35l43 ALSA SoC audio driver + * + * Copyright 2021 Cirrus Logic, Inc. + * + * Author: David Rhodes + * + * 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. + * + */ + +#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 "wm_adsp.h" +#include "cs35l43.h" +#include +#include +#include +#include + +#define DRV_NAME "cs35l43" + +static const char * const cs35l43_supplies[] = { + "VA", + "VP", +}; + +static int cs35l43_exit_hibernate(struct cs35l43_private *cs35l43); +static void cs35l43_pm_runtime_setup(struct cs35l43_private *cs35l43); +static void cs35l43_log_status(struct cs35l43_private *cs35l43); +static int cs35l43_check_dsp_regs(struct cs35l43_private *cs35l43); +static int cs35l43_log_dsp_err(struct cs35l43_private *cs35l43); + + +static void cs35l43_log_regmap_fail(struct cs35l43_private *cs35l43, + unsigned int reg) +{ + struct cirrus_amp *amp = cirrus_get_amp_from_suffix(cs35l43->pdata.mfd_suffix); + + dev_crit(cs35l43->dev, "Failed to access regmap, reg = 0x%x\n", reg); + if (amp && amp->i2c_callback) + amp->i2c_callback(cs35l43->pdata.mfd_suffix); +} + +static int cs35l43_regmap_read(struct cs35l43_private *cs35l43, + unsigned int reg, unsigned int *val) +{ + int ret = 0, retry = CS35L43_REGMAP_RETRY; + + do { + ret = regmap_read(cs35l43->regmap, reg, val); + if (ret) { + dev_err(cs35l43->dev, "%s: reg = 0x%x, ret = %d\n", + __func__, reg, ret); + usleep_range(1000, 1100); + } + } while (ret && --retry > 0); + + if (retry == 0) { + dev_crit(cs35l43->dev, "%s: Retry failed after %d attempts", + __func__, CS35L43_REGMAP_RETRY); + cs35l43_log_regmap_fail(cs35l43, reg); + } + + return ret; +} + +static int cs35l43_regmap_bulk_read(struct cs35l43_private *cs35l43, + unsigned int reg, void *val, size_t val_count) +{ + int ret = 0, retry = CS35L43_REGMAP_RETRY; + + do { + ret = regmap_bulk_read(cs35l43->regmap, reg, val, val_count); + if (ret) { + dev_err(cs35l43->dev, "%s: reg = 0x%x, ret = %d\n", + __func__, reg, ret); + usleep_range(1000, 1100); + } + } while (ret && --retry > 0); + + if (retry == 0) { + dev_crit(cs35l43->dev, "%s: Retry failed after %d attempts", + __func__, CS35L43_REGMAP_RETRY); + cs35l43_log_regmap_fail(cs35l43, reg); + } + + return ret; +} + +static int cs35l43_regmap_write(struct cs35l43_private *cs35l43, + unsigned int reg, unsigned int val) +{ + int ret = 0, retry = CS35L43_REGMAP_RETRY; + + do { + ret = regmap_write(cs35l43->regmap, reg, val); + if (ret) { + dev_err(cs35l43->dev, "%s: reg = 0x%x, ret = %d\n", + __func__, reg, ret); + usleep_range(1000, 1100); + } + } while (ret && --retry > 0); + + if (retry == 0) { + dev_crit(cs35l43->dev, "%s: Retry failed after %d attempts", + __func__, CS35L43_REGMAP_RETRY); + cs35l43_log_regmap_fail(cs35l43, reg); + } + + return ret; +} + +static int cs35l43_regmap_update_bits(struct cs35l43_private *cs35l43, + unsigned int reg, unsigned int mask, unsigned int val) +{ + int ret = 0, retry = CS35L43_REGMAP_RETRY; + + do { + ret = regmap_update_bits(cs35l43->regmap, reg, mask, val); + if (ret) { + dev_err(cs35l43->dev, "%s: reg = 0x%x, ret = %d\n", + __func__, reg, ret); + usleep_range(1000, 1100); + } + } while (ret && --retry > 0); + + if (retry == 0) { + dev_crit(cs35l43->dev, "%s: Retry failed after %d attempts", + __func__, CS35L43_REGMAP_RETRY); + cs35l43_log_regmap_fail(cs35l43, reg); + } + + return ret; +} + +static const DECLARE_TLV_DB_RANGE(dig_vol_tlv, + 0, 0, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), + 1, 913, TLV_DB_SCALE_ITEM(-10200, 25, 0)); +static DECLARE_TLV_DB_SCALE(amp_gain_tlv, 0, 1, 1); + +static const char * const cs35l43_pcm_sftramp_text[] = { + "Off", ".5ms", "1ms", "2ms", "4ms", "8ms", "15ms", "30ms"}; + +static SOC_ENUM_SINGLE_DECL(pcm_sft_ramp, + CS35L43_AMP_CTRL, 0, + cs35l43_pcm_sftramp_text); + +static const char * const cs35l43_tx_input_texts[] = { + "Zero", "ASPRX1", "ASPRX2", "VMON", "IMON", "VMON FS2", "IMON FS2", + "VPMON", "VBSTMON", "DSP", "DSP FS2"}; + +static const unsigned int cs35l43_tx_input_values[] = {0x00, + CS35L43_INPUT_SRC_ASPRX1, + CS35L43_INPUT_SRC_ASPRX2, + CS35L43_INPUT_SRC_VMON, + CS35L43_INPUT_SRC_IMON, + CS35L43_INPUT_SRC_VMON_FS2, + CS35L43_INPUT_SRC_IMON_FS2, + CS35L43_INPUT_SRC_VPMON, + CS35L43_INPUT_SRC_VBSTMON, + CS35L43_INPUT_DSP_TX5, + CS35L43_INPUT_DSP_TX6}; + +static SOC_VALUE_ENUM_SINGLE_DECL(cs35l43_asptx1_enum, + CS35L43_ASPTX1_INPUT, + 0, CS35L43_INPUT_MASK, + cs35l43_tx_input_texts, + cs35l43_tx_input_values); + +static const struct snd_kcontrol_new asp_tx1_mux = + SOC_DAPM_ENUM("ASPTX1 SRC", cs35l43_asptx1_enum); + +static SOC_VALUE_ENUM_SINGLE_DECL(cs35l43_asptx2_enum, + CS35L43_ASPTX2_INPUT, + 0, CS35L43_INPUT_MASK, + cs35l43_tx_input_texts, + cs35l43_tx_input_values); + +static const struct snd_kcontrol_new asp_tx2_mux = + SOC_DAPM_ENUM("ASPTX2 SRC", cs35l43_asptx2_enum); + +static SOC_VALUE_ENUM_SINGLE_DECL(cs35l43_asptx3_enum, + CS35L43_ASPTX3_INPUT, + 0, CS35L43_INPUT_MASK, + cs35l43_tx_input_texts, + cs35l43_tx_input_values); + +static const struct snd_kcontrol_new asp_tx3_mux = + SOC_DAPM_ENUM("ASPTX3 SRC", cs35l43_asptx3_enum); + +static SOC_VALUE_ENUM_SINGLE_DECL(cs35l43_asptx4_enum, + CS35L43_ASPTX4_INPUT, + 0, CS35L43_INPUT_MASK, + cs35l43_tx_input_texts, + cs35l43_tx_input_values); + +static const struct snd_kcontrol_new asp_tx4_mux = + SOC_DAPM_ENUM("ASPTX4 SRC", cs35l43_asptx4_enum); + +static SOC_VALUE_ENUM_SINGLE_DECL(cs35l43_dsprx1_enum, + CS35L43_DSP1RX1_INPUT, + 0, CS35L43_INPUT_MASK, + cs35l43_tx_input_texts, + cs35l43_tx_input_values); + +static const struct snd_kcontrol_new dsp_rx1_mux = + SOC_DAPM_ENUM("DSPRX1 SRC", cs35l43_dsprx1_enum); + +static SOC_VALUE_ENUM_SINGLE_DECL(cs35l43_dsprx2_enum, + CS35L43_DSP1RX2_INPUT, + 0, CS35L43_INPUT_MASK, + cs35l43_tx_input_texts, + cs35l43_tx_input_values); + +static const struct snd_kcontrol_new dsp_rx2_mux = + SOC_DAPM_ENUM("DSPRX2 SRC", cs35l43_dsprx2_enum); + +static SOC_VALUE_ENUM_SINGLE_DECL(cs35l43_dsprx3_enum, + CS35L43_DSP1RX3_INPUT, + 0, CS35L43_INPUT_MASK, + cs35l43_tx_input_texts, + cs35l43_tx_input_values); + +static const struct snd_kcontrol_new dsp_rx3_mux = + SOC_DAPM_ENUM("DSPRX3 SRC", cs35l43_dsprx3_enum); + +static SOC_VALUE_ENUM_SINGLE_DECL(cs35l43_dacpcm_enum, + CS35L43_DACPCM1_INPUT, + 0, CS35L43_INPUT_MASK, + cs35l43_tx_input_texts, + cs35l43_tx_input_values); + +static const struct snd_kcontrol_new dacpcm_mux = + SOC_DAPM_ENUM("PCM Source", cs35l43_dacpcm_enum); + +static SOC_VALUE_ENUM_SINGLE_DECL(cs35l43_dacpcm2_enum, + CS35L43_DACPCM2_INPUT, + 0, CS35L43_INPUT_MASK, + cs35l43_tx_input_texts, + cs35l43_tx_input_values); + +static const struct snd_kcontrol_new dacpcm2_mux = + SOC_DAPM_ENUM("High Rate PCM Source", cs35l43_dacpcm2_enum); + +static const char * const cs35l43_ultrasonic_mode_texts[] = { + "Disabled", "In Band", "Out of Band" +}; +static SOC_ENUM_SINGLE_DECL(cs35l43_ultrasonic_mode_enum, SND_SOC_NOPM, 0, + cs35l43_ultrasonic_mode_texts); + +static const char * const cs35l43_wd_mode_texts[] = {"Normal", "Mute"}; +static const unsigned int cs35l43_wd_mode_values[] = {0x0, 0x3}; +static SOC_VALUE_ENUM_SINGLE_DECL(cs35l43_dc_wd_mode_enum, CS35L43_ALIVE_DCIN_WD, + CS35L43_WD_MODE_SHIFT, 0x3, + cs35l43_wd_mode_texts, cs35l43_wd_mode_values); + +static const char * const cs35l43_bst_en_text[] = {"Disabled", "Enabled"}; +static const unsigned int cs35l43_bst_en_values[] = { + 0, CS35L43_BST_EN_DEFAULT}; + +static SOC_VALUE_ENUM_SINGLE_DECL(bst_en_ctl, + CS35L43_BLOCK_ENABLES, + CS35L43_BST_EN_SHIFT, 0x3, + cs35l43_bst_en_text, + cs35l43_bst_en_values); + +static const char * const cs35l43_amp_mute_text[] = {"Muted", "Unmuted"}; +static const unsigned int cs35l43_amp_mute_values[] = {1, 0}; + +static SOC_VALUE_ENUM_SINGLE_DECL(amp_mute_ctl, + SND_SOC_NOPM, + 0, 0, + cs35l43_amp_mute_text, + cs35l43_amp_mute_values); + +static int cs35l43_amp_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component; + struct cs35l43_private *cs35l43; + + component = snd_soc_kcontrol_component(kcontrol); + cs35l43 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = cs35l43->amp_mute; + + return 0; +} + +static int cs35l43_amp_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component; + struct cs35l43_private *cs35l43; + unsigned int vol; + + component = snd_soc_kcontrol_component(kcontrol); + cs35l43 = snd_soc_component_get_drvdata(component); + + if (ucontrol->value.integer.value[0] > 1 || + ucontrol->value.integer.value[0] < 0) { + dev_err(cs35l43->dev, "%s: Invalid value 0x%lx\n", + __func__, ucontrol->value.integer.value[0]); + return -EINVAL; + } + + cs35l43->amp_mute = ucontrol->value.integer.value[0]; + + if (cs35l43->pcm_muted == false) { + if (cs35l43->amp_mute == 0) /* Mute */ + vol = 0; + else /* Unmute */ + vol = cs35l43->pcm_vol; + + /* convert control val to register val */ + if (vol < CS35L43_AMP_VOL_CTRL_DEFAULT) + vol += CS35L43_AMP_VOL_PCM_MUTE; + else + vol -= CS35L43_AMP_VOL_CTRL_DEFAULT; + + regmap_update_bits(cs35l43->regmap, + CS35L43_AMP_CTRL, + CS35L43_AMP_VOL_PCM_MASK << + CS35L43_AMP_VOL_PCM_SHIFT, + vol << CS35L43_AMP_VOL_PCM_SHIFT); + } + + dev_info(cs35l43->dev, "%s: %s\n", __func__, + cs35l43_amp_mute_text[ucontrol->value.integer.value[0]]); + return 0; + +} + +static int cs35l43_pcm_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component; + struct cs35l43_private *cs35l43; + + component = snd_soc_kcontrol_component(kcontrol); + cs35l43 = snd_soc_component_get_drvdata(component); + ucontrol->value.integer.value[0] = cs35l43->pcm_vol; + + return 0; +} + +static int cs35l43_pcm_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component; + struct cs35l43_private *cs35l43; + + component = snd_soc_kcontrol_component(kcontrol); + cs35l43 = snd_soc_component_get_drvdata(component); + + if (ucontrol->value.integer.value[0] > 0x391 || + ucontrol->value.integer.value[0] < 0) { + dev_err(cs35l43->dev, "%s: Invalid value 0x%lx\n", + __func__, ucontrol->value.integer.value[0]); + return -EINVAL; + } + + dev_info(cs35l43->dev, "%s: 0x%lx\n", __func__, + ucontrol->value.integer.value[0]); + + cs35l43->pcm_vol = ucontrol->value.integer.value[0]; + + if (cs35l43->amp_mute == 0 || cs35l43->pcm_muted) + return 0; + else + return snd_soc_put_volsw_sx(kcontrol, ucontrol); +} + +static const char * const cs35l43_low_pwr_mode_text[] = {"Hibernate", "Standby"}; + +static SOC_ENUM_SINGLE_DECL(low_pwr_mode_ctl, + SND_SOC_NOPM, 0, cs35l43_low_pwr_mode_text); + +static int cs35l43_delta_select_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component; + struct cs35l43_private *cs35l43; + + component = snd_soc_kcontrol_component(kcontrol); + cs35l43 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = cs35l43->delta_applied; + + return 0; +} + +static int cs35l43_apply_delta_tuning(struct cs35l43_private *cs35l43) +{ + const struct firmware *firmware; + struct wm_adsp *dsp = &cs35l43->dsp; + int ret = 0; + char filename[NAME_MAX]; + + if (!cs35l43->delta_requested || + cs35l43->delta_applied == cs35l43->delta_requested) + return 0; + + dev_dbg(cs35l43->dev, "Applying delta file %d\n", cs35l43->delta_requested); + + snprintf(filename, NAME_MAX, "%s-delta-%d-spk-prot.bin", + cs35l43->pdata.dsp_part_name, cs35l43->delta_requested); + + ret = request_firmware(&firmware, filename, cs35l43->dev); + if (ret != 0) { + dev_err(cs35l43->dev, "Failed to request '%s'\n", filename); + return -EINVAL; + } + + ret = cs_dsp_load_coeff(&dsp->cs_dsp, firmware, filename); + if (ret) + dev_err(cs35l43->dev, "Error applying delta file %s: %d\n", + filename, ret); + else + cs35l43->delta_applied = cs35l43->delta_requested; + + return ret; +} + +static int cs35l43_delta_select_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_kcontrol_component(kcontrol); + struct cs35l43_private *cs35l43 = + snd_soc_component_get_drvdata(component); + + cs35l43->delta_requested = ucontrol->value.integer.value[0]; + + mutex_lock(&cs35l43->hb_lock); + if (cs35l43->hibernate_state == CS35L43_HIBERNATE_AWAKE) + cs35l43_apply_delta_tuning(cs35l43); + mutex_unlock(&cs35l43->hb_lock); + + return 0; +} + +static int cs35l43_ultrasonic_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component; + struct cs35l43_private *cs35l43; + + component = snd_soc_dapm_kcontrol_component(kcontrol); + cs35l43 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = cs35l43->ultrasonic_mode; + + return 0; +} + +static int cs35l43_ultrasonic_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component; + struct cs35l43_private *cs35l43; + unsigned int mon_rates, rx_rates, tx_rates, high_rate_enable; + + component = snd_soc_dapm_kcontrol_component(kcontrol); + cs35l43 = snd_soc_component_get_drvdata(component); + + cs35l43->ultrasonic_mode = ucontrol->value.integer.value[0]; + + switch (cs35l43->ultrasonic_mode) { + case CS35L43_ULTRASONIC_MODE_INBAND: + mon_rates = CS35L43_BASE_RATE; + rx_rates = CS35L43_HIGH_RATE; + tx_rates = CS35L43_HIGH_RATE; + high_rate_enable = 1; + + break; + case CS35L43_ULTRASONIC_MODE_OUT_OF_BAND: + mon_rates = CS35L43_BASE_RATE; + rx_rates = CS35L43_HIGH_RATE; + tx_rates = CS35L43_HIGH_RATE; + high_rate_enable = 1; + break; + case CS35L43_ULTRASONIC_MODE_DISABLED: + default: + mon_rates = CS35L43_BASE_RATE; + rx_rates = CS35L43_BASE_RATE; + tx_rates = CS35L43_BASE_RATE; + high_rate_enable = 0; + break; + } + + pm_runtime_get_sync(cs35l43->dev); + + cs35l43_regmap_update_bits(cs35l43, + CS35L43_DSP1_SAMPLE_RATE_RX1, + CS35L43_DSP_RX1_RATE_MASK, + rx_rates << CS35L43_DSP_RX1_RATE_SHIFT); + cs35l43_regmap_update_bits(cs35l43, + CS35L43_DSP1_SAMPLE_RATE_RX1, + CS35L43_DSP_RX2_RATE_MASK, + rx_rates << CS35L43_DSP_RX2_RATE_SHIFT); + cs35l43_regmap_update_bits(cs35l43, + CS35L43_DSP1_SAMPLE_RATE_RX1, + CS35L43_DSP_RX3_RATE_MASK, + rx_rates << CS35L43_DSP_RX3_RATE_SHIFT); + cs35l43_regmap_update_bits(cs35l43, + CS35L43_DSP1_SAMPLE_RATE_RX1, + CS35L43_DSP_RX4_RATE_MASK, + mon_rates << CS35L43_DSP_RX4_RATE_SHIFT); + cs35l43_regmap_update_bits(cs35l43, + CS35L43_DSP1_SAMPLE_RATE_RX2, + CS35L43_DSP_RX5_RATE_MASK, + mon_rates << CS35L43_DSP_RX5_RATE_SHIFT); + cs35l43_regmap_update_bits(cs35l43, + CS35L43_DSP1_SAMPLE_RATE_RX2, + CS35L43_DSP_RX6_RATE_MASK, + mon_rates << CS35L43_DSP_RX6_RATE_SHIFT); + + cs35l43_regmap_update_bits(cs35l43, + CS35L43_DSP1_SAMPLE_RATE_TX1, + CS35L43_DSP_TX1_RATE_MASK, + tx_rates << CS35L43_DSP_TX1_RATE_SHIFT); + cs35l43_regmap_update_bits(cs35l43, + CS35L43_DSP1_SAMPLE_RATE_TX1, + CS35L43_DSP_TX2_RATE_MASK, + tx_rates << CS35L43_DSP_TX2_RATE_SHIFT); + cs35l43_regmap_update_bits(cs35l43, + CS35L43_DSP1_SAMPLE_RATE_TX1, + CS35L43_DSP_TX3_RATE_MASK, + tx_rates << CS35L43_DSP_TX3_RATE_SHIFT); + cs35l43_regmap_update_bits(cs35l43, + CS35L43_DSP1_SAMPLE_RATE_TX1, + CS35L43_DSP_TX4_RATE_MASK, + tx_rates << CS35L43_DSP_TX4_RATE_SHIFT); + cs35l43_regmap_update_bits(cs35l43, + CS35L43_DSP1_SAMPLE_RATE_TX2, + CS35L43_DSP_TX5_RATE_MASK, + CS35L43_BASE_RATE << CS35L43_DSP_TX5_RATE_SHIFT); + cs35l43_regmap_update_bits(cs35l43, + CS35L43_DSP1_SAMPLE_RATE_TX2, + CS35L43_DSP_TX6_RATE_MASK, + tx_rates << CS35L43_DSP_TX6_RATE_SHIFT); + + if (high_rate_enable) { + cs35l43_regmap_update_bits(cs35l43, CS35L43_DAC_MSM_CONFIG, + CS35L43_AMP_PCM_FSX2_EN_MASK, + CS35L43_AMP_PCM_FSX2_EN_MASK); + cs35l43_regmap_update_bits(cs35l43, CS35L43_MONITOR_FILT, + CS35L43_VIMON_DUAL_RATE_MASK, + CS35L43_VIMON_DUAL_RATE_MASK); + } else { + cs35l43_regmap_update_bits(cs35l43, CS35L43_DAC_MSM_CONFIG, + CS35L43_AMP_PCM_FSX2_EN_MASK, 0); + cs35l43_regmap_update_bits(cs35l43, CS35L43_MONITOR_FILT, + CS35L43_VIMON_DUAL_RATE_MASK, 0); + } + + cs35l43_regmap_write(cs35l43, CS35L43_DSP_VIRTUAL1_MBOX_1, + CS35L43_MBOX_CMD_AUDIO_REINIT); + + pm_runtime_mark_last_busy(cs35l43->dev); + pm_runtime_put_autosuspend(cs35l43->dev); + + return 0; +} + +static int cs35l43_amp_en_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return snd_soc_dapm_get_volsw(kcontrol, ucontrol); +} + +static int cs35l43_amp_en_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm; + struct snd_soc_component *component; + struct cs35l43_private *cs35l43; + int ret = 0; + + dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + component = snd_soc_dapm_to_component(dapm); + cs35l43 = snd_soc_component_get_drvdata(component); + + if (ucontrol->value.integer.value[0] != cs35l43->amp_switch) { + if (ucontrol->value.integer.value[0]) { + /* Switch enable */ + dev_info(cs35l43->dev, "AMP Enable Switch: enable\n"); + if (cs35l43->low_pwr_mode == CS35L43_LOW_PWR_MODE_HIBERNATE) { + if (!pm_runtime_enabled(cs35l43->dev)) + pm_runtime_force_resume(cs35l43->dev); + } + ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol); + } else { + /* Switch disable */ + dev_info(cs35l43->dev, "AMP Enable Switch: disable\n"); + ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol); + if (cs35l43->low_pwr_mode == CS35L43_LOW_PWR_MODE_HIBERNATE) + pm_runtime_force_suspend(cs35l43->dev); + } + } + cs35l43->amp_switch = ucontrol->value.integer.value[0]; + return ret; +} + +static const struct snd_kcontrol_new amp_enable_ctrl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Switch", + .info = snd_soc_info_volsw, + .get = cs35l43_amp_en_get, + .put = cs35l43_amp_en_put, + .private_value = SOC_SINGLE_VALUE(SND_SOC_NOPM, 0, 1, 0, 0) +}; + +static int cs35l43_reinit_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + + return 0; +} + +static int cs35l43_reinit_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component; + struct cs35l43_private *cs35l43; + int ret = 0; + + component = snd_soc_kcontrol_component(kcontrol); + cs35l43 = snd_soc_component_get_drvdata(component); + + if (ucontrol->value.integer.value[0]) { + pm_runtime_get_sync(cs35l43->dev); + + ret = cs35l43_regmap_write(cs35l43, CS35L43_DSP_VIRTUAL1_MBOX_1, + CS35L43_MBOX_CMD_AUDIO_REINIT); + + pm_runtime_mark_last_busy(cs35l43->dev); + pm_runtime_put_autosuspend(cs35l43->dev); + } + + return ret; +} + +static const struct snd_kcontrol_new ultra_mux = + SOC_DAPM_ENUM_EXT("Ultrasonic Mode", cs35l43_ultrasonic_mode_enum, + cs35l43_ultrasonic_mode_get, cs35l43_ultrasonic_mode_put); + +static const struct snd_kcontrol_new cs35l43_aud_controls[] = { + {.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Digital PCM Volume", + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_READWRITE, + .tlv.p = dig_vol_tlv, + .info = snd_soc_info_volsw_sx, + .get = cs35l43_pcm_vol_get, + .put = cs35l43_pcm_vol_put, + .private_value = (unsigned long)&(struct soc_mixer_control) + {.reg = CS35L43_AMP_CTRL, .rreg = CS35L43_AMP_CTRL, + .shift = CS35L43_AMP_VOL_PCM_SHIFT, .rshift = CS35L43_AMP_VOL_PCM_SHIFT, + .max = 0x391, .min = 0x4CF} }, + SOC_SINGLE("DC Watchdog Enable", CS35L43_ALIVE_DCIN_WD, + CS35L43_DCIN_WD_EN_SHIFT, 1, 0), + SOC_SINGLE("DC Watchdog Threshold", CS35L43_ALIVE_DCIN_WD, + CS35L43_DCIN_WD_THLD_SHIFT, 0x28, 0), + SOC_SINGLE("DC Watchdog Duration", CS35L43_ALIVE_DCIN_WD, + CS35L43_DCIN_WD_DUR_SHIFT, 0x7, 0), + SOC_ENUM("DC Watchdog Mode", cs35l43_dc_wd_mode_enum), + SOC_SINGLE_TLV("Amp Gain", CS35L43_AMP_GAIN, + CS35L43_AMP_GAIN_PCM_SHIFT, 20, 0, + amp_gain_tlv), + SOC_SINGLE_EXT("Reinit", SND_SOC_NOPM, 0, 1, 0, + cs35l43_reinit_get, cs35l43_reinit_put), + SOC_ENUM("PCM Soft Ramp", pcm_sft_ramp), + SOC_SINGLE_RANGE("ASPTX1 Slot Position", CS35L43_ASP_FRAME_CONTROL1, 0, 0, 7, 0), + SOC_SINGLE_RANGE("ASPTX2 Slot Position", CS35L43_ASP_FRAME_CONTROL1, 8, 0, 7, 0), + SOC_SINGLE_RANGE("ASPTX3 Slot Position", CS35L43_ASP_FRAME_CONTROL1, 16, 0, 7, 0), + SOC_SINGLE_RANGE("ASPTX4 Slot Position", CS35L43_ASP_FRAME_CONTROL1, 24, 0, 7, 0), + SOC_SINGLE_RANGE("ASPRX1 Slot Position", CS35L43_ASP_FRAME_CONTROL5, 0, 0, 7, 0), + SOC_SINGLE_RANGE("ASPRX2 Slot Position", CS35L43_ASP_FRAME_CONTROL5, 8, 0, 7, 0), + SOC_SINGLE_RANGE("ASPRX3 Slot Position", CS35L43_ASP_FRAME_CONTROL5, 16, 0, 7, 0), + SOC_ENUM("Boost Enable", bst_en_ctl), + SOC_ENUM_EXT("AMP Mute", amp_mute_ctl, cs35l43_amp_mute_get, cs35l43_amp_mute_put), + SOC_SINGLE_EXT("Delta Select", SND_SOC_NOPM, 0, 10, 0, + cs35l43_delta_select_get, cs35l43_delta_select_put), + WM_ADSP2_PRELOAD_SWITCH("DSP1", 1), + WM_ADSP_FW_CONTROL("DSP1", 0), +}; + +static int cs35l43_dsp_preload_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); +static int cs35l43_dsp_audio_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); +static int cs35l43_main_amp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); + +static int cs35l43_write_seq_elem_update(struct cs35l43_write_seq_elem *write_seq_elem, + unsigned int addr, unsigned int value) +{ + switch (write_seq_elem->operation) { + case CS35L43_POWER_SEQ_OP_WRITE_REG_FULL: + write_seq_elem->words[0] = (addr & 0xFFFF0000) >> 16; + write_seq_elem->words[1] = ((addr & 0xFFFF) << 8) | + ((value & 0xFF000000) >> 24); + write_seq_elem->words[2] = (value & 0xFFFFFF); + + break; + case CS35L43_POWER_SEQ_OP_WRITE_REG_ADDR8: + write_seq_elem->words[0] = (CS35L43_POWER_SEQ_OP_WRITE_REG_ADDR8 << 16) | + ((addr & 0xFF) << 8) | + ((value & 0xFF000000) >> 24); + write_seq_elem->words[1] = (value & 0xFFFFFF); + break; + case CS35L43_POWER_SEQ_OP_WRITE_REG_L16: + write_seq_elem->words[0] = (CS35L43_POWER_SEQ_OP_WRITE_REG_L16 << 16) | + ((addr & 0xFFFF00) >> 8); + write_seq_elem->words[1] = ((addr & 0xFF) << 16) | (value & 0xFFFF); + break; + default: + break; + } + + return 0; +} + +static int cs35l43_write_seq_add(struct cs35l43_private *cs35l43, + struct cs35l43_write_seq *sequence, + unsigned int update_reg, unsigned int update_value, + bool read) +{ + struct device *dev = cs35l43->dev; + u32 *buf, *op_words, addr = 0, prev_addr = 0, value = 0; + u8 operation = CS35L43_POWER_SEQ_OP_END; + unsigned int i, j, num_words, ret = 0; + struct cs35l43_write_seq_elem *write_seq_elem; + + buf = kzalloc(sizeof(u32) * sequence->length, GFP_KERNEL); + if (!buf) { + dev_err(cs35l43->dev, "%s: failed to alloc write seq\n", + __func__); + return -ENOMEM; + } + + ret = wm_adsp_read_ctl(&cs35l43->dsp, sequence->name, + WMFW_ADSP2_XM, CS35L43_ALG_ID_PM, (void *)buf, + sequence->length * sizeof(u32)); + if (ret != 0) { + dev_err(dev, "%s: Failed to read control\n", __func__); + goto exit; + } + + for (i = 0; i < sequence->length; i++) { + buf[i] = be32_to_cpu(buf[i]); + dev_dbg(dev, "%s[%d] = 0x%x\n", sequence->name, i, buf[i]); + } + + list_for_each_entry(write_seq_elem, &sequence->list_head, list) { + switch (write_seq_elem->operation) { + case CS35L43_POWER_SEQ_OP_WRITE_REG_FULL: + addr = ((write_seq_elem->words[0] & 0xFFFF) << 16) | + ((write_seq_elem->words[1] & 0xFFFF00) >> 8); + value = ((write_seq_elem->words[1] & 0xFF) << 24) | + (write_seq_elem->words[2] & 0xFFFFFF); + break; + case CS35L43_POWER_SEQ_OP_WRITE_REG_ADDR8: + addr = (prev_addr & 0xFFFFFF00) | + ((write_seq_elem->words[0] & 0xFF00) >> 8); + value = ((write_seq_elem->words[0] & 0xFF) << 24) | + (write_seq_elem->words[1] & 0xFFFFFF); + break; + case CS35L43_POWER_SEQ_OP_WRITE_REG_L16: + addr = ((write_seq_elem->words[0] & 0xFFFF) << 8) | + ((write_seq_elem->words[1] & 0xFF0000) >> 16); + value = (write_seq_elem->words[1] & 0xFFFF); + break; + default: + break; + } + dev_dbg(dev, "write seq elem: addr=0x%x, prev_addr=0x%x, val=0x%x\n", + addr, prev_addr, value); + prev_addr = addr; + + if (addr == update_reg) { + if (read) + cs35l43_regmap_read(cs35l43, addr, &update_value); + + dev_dbg(dev, "%s: Updating register 0x%x with value 0x%x\n", + __func__, addr, update_value); + cs35l43_write_seq_elem_update(write_seq_elem, update_reg, update_value); + memcpy(buf + write_seq_elem->offset, write_seq_elem->words, + write_seq_elem->size * sizeof(u32)); + goto write_exit; + } + } + + i = 0; + while (i < sequence->length) { + operation = (buf[i] & CS35L43_POWER_SEQ_OP_MASK) >> + CS35L43_POWER_SEQ_OP_SHIFT; + + if (operation == CS35L43_POWER_SEQ_OP_END) + break; + + /* get num words for given operation */ + for (j = 0; j < CS35L43_POWER_SEQ_NUM_OPS; j++) { + if (cs35l43_write_seq_op_sizes[j][0] == operation) { + num_words = cs35l43_write_seq_op_sizes[j][1]; + break; + } + } + + i += num_words; + } + + if (operation != CS35L43_POWER_SEQ_OP_END || + i + CS35L43_POWER_SEQ_OP_WRITE_REG_FULL_WORDS + + CS35L43_POWER_SEQ_OP_END_WORDS > sequence->length) { + dev_err(dev, "WRITE SEQ END_OF_SCRIPT not found or sequence full\n"); + ret = -E2BIG; + goto exit; + } + + write_seq_elem = devm_kzalloc(dev, sizeof(*write_seq_elem), GFP_KERNEL); + if (!write_seq_elem) { + ret = -ENOMEM; + goto exit; + } + + write_seq_elem->size = CS35L43_POWER_SEQ_OP_WRITE_REG_FULL_WORDS; + write_seq_elem->offset = i; + write_seq_elem->operation = CS35L43_POWER_SEQ_OP_WRITE_REG_FULL; + + op_words = kzalloc(write_seq_elem->size * sizeof(u32), GFP_KERNEL); + if (!op_words) { + ret = -ENOMEM; + goto err_elem; + } + + write_seq_elem->words = op_words; + + if (read) + cs35l43_regmap_read(cs35l43, update_reg, &update_value); + + cs35l43_write_seq_elem_update(write_seq_elem, update_reg, update_value); + list_add_tail(&write_seq_elem->list, &sequence->list_head); + + sequence->num_ops++; + + memcpy(&buf[i], op_words, write_seq_elem->size * sizeof(u32)); + + dev_dbg(dev, "%s: Added register 0x%x with value 0x%x\n", + __func__, update_reg, update_value); + for (i = 0; i < write_seq_elem->size; i++) + dev_dbg(dev, "elem[%d]: 0x%x\n", i, write_seq_elem->words[i]); + + buf[write_seq_elem->offset + write_seq_elem->size] = 0xFFFFFFFF; + +write_exit: + for (i = 0; i < sequence->length; i++) { + dev_dbg(dev, "%s[%d] = 0x%x\n", sequence->name, i, buf[i]); + buf[i] = cpu_to_be32(buf[i]); + } + + ret = wm_adsp_write_ctl(&cs35l43->dsp, sequence->name, + WMFW_ADSP2_XM, CS35L43_ALG_ID_PM, (void *)buf, + sequence->length * sizeof(u32)); + goto exit; + +err_elem: + kfree(write_seq_elem); +exit: + kfree(buf); + return ret; +} + +static int cs35l43_write_seq_update(struct cs35l43_private *cs35l43, + struct cs35l43_write_seq *sequence) +{ + struct device *dev = cs35l43->dev; + u32 *buf; + u32 addr = 0, prev_addr = 0, value = 0, reg_value = 0; + unsigned int ret = 0, i; + struct cs35l43_write_seq_elem *write_seq_elem; + + buf = kzalloc(sizeof(u32) * sequence->length, GFP_KERNEL); + if (!buf) { + dev_err(cs35l43->dev, "%s: failed to alloc write seq\n", + __func__); + return -ENOMEM; + } + + ret = wm_adsp_read_ctl(&cs35l43->dsp, sequence->name, + WMFW_ADSP2_XM, CS35L43_ALG_ID_PM, (void *)buf, + sequence->length * sizeof(u32)); + if (ret != 0) { + dev_err(dev, "%s: Failed to read control\n", __func__); + goto err_free; + } + + for (i = 0; i < sequence->length; i++) { + buf[i] = be32_to_cpu(buf[i]); + dev_dbg(dev, "%s[%d] = 0x%x\n", sequence->name, i, buf[i]); + } + + dev_dbg(dev, "%s num ops: %d\n", sequence->name, sequence->num_ops); + dev_dbg(dev, "offset\tsize\twords\n"); + list_for_each_entry(write_seq_elem, &sequence->list_head, list) { + switch (write_seq_elem->operation) { + case CS35L43_POWER_SEQ_OP_WRITE_REG_FULL: + addr = ((write_seq_elem->words[0] & 0xFFFF) << 16) | + ((write_seq_elem->words[1] & 0xFFFF00) >> 8); + value = ((write_seq_elem->words[1] & 0xFF) << 24) | + (write_seq_elem->words[2] & 0xFFFFFF); + break; + case CS35L43_POWER_SEQ_OP_WRITE_REG_ADDR8: + addr = (prev_addr & 0xFFFFFF00) | + ((write_seq_elem->words[0] & 0xFF00) >> 8); + value = ((write_seq_elem->words[0] & 0xFF) << 24) | + (write_seq_elem->words[1] & 0xFFFFFF); + break; + case CS35L43_POWER_SEQ_OP_WRITE_REG_L16: + addr = ((write_seq_elem->words[0] & 0xFFFF) << 8) | + ((write_seq_elem->words[1] & 0xFF0000) >> 16); + value = (write_seq_elem->words[1] & 0xFFFF); + break; + default: + break; + } + dev_dbg(dev, "write seq elem: addr=0x%x, prev_addr=0x%x, val=0x%x\n", + addr, prev_addr, value); + prev_addr = addr; + + cs35l43_regmap_read(cs35l43, addr, ®_value); + + if (reg_value != value && addr != CS35L43_TEST_KEY_CTRL && + cs35l43_readable_reg(dev, addr)) { + dev_dbg(dev, + "%s: Updating register 0x%x with value 0x%x\t(prev value: 0x%x)\n", + __func__, addr, reg_value, value); + cs35l43_write_seq_elem_update(write_seq_elem, addr, reg_value); + memcpy(buf + write_seq_elem->offset, write_seq_elem->words, + write_seq_elem->size * sizeof(u32)); + for (i = 0; i < write_seq_elem->size; i++) + dev_dbg(dev, "elem[%d]: 0x%x\n", i, write_seq_elem->words[i]); + } + } + + for (i = 0; i < sequence->length; i++) { + dev_dbg(dev, "%s[%d] = 0x%x\n", sequence->name, i, buf[i]); + buf[i] = cpu_to_be32(buf[i]); + } + + ret = wm_adsp_write_ctl(&cs35l43->dsp, sequence->name, + WMFW_ADSP2_XM, CS35L43_ALG_ID_PM, (void *)buf, + sequence->length * sizeof(u32)); + +err_free: + kfree(buf); + return ret; + +} + +static int cs35l43_write_seq_init(struct cs35l43_private *cs35l43, + struct cs35l43_write_seq *sequence) +{ + struct device *dev = cs35l43->dev; + u32 *buf, *op_words; + u8 operation = CS35L43_POWER_SEQ_OP_END; + unsigned int i, j, num_words, ret = 0; + struct cs35l43_write_seq_elem *write_seq_elem; + + INIT_LIST_HEAD(&sequence->list_head); + sequence->num_ops = 0; + + buf = kzalloc(sizeof(u32) * sequence->length, GFP_KERNEL); + if (!buf) { + dev_err(cs35l43->dev, "%s: failed to alloc write seq\n", + __func__); + return -ENOMEM; + } + + ret = wm_adsp_read_ctl(&cs35l43->dsp, sequence->name, + WMFW_ADSP2_XM, CS35L43_ALG_ID_PM, (void *)buf, + sequence->length * sizeof(u32)); + if (ret != 0) { + dev_err(dev, "%s: Failed to read control\n", __func__); + goto err_free; + } + + + for (i = 0; i < sequence->length; i++) { + buf[i] = be32_to_cpu(buf[i]); + dev_dbg(dev, "%s[%d] = 0x%x\n", sequence->name, i, buf[i]); + } + + i = 0; + while (i < sequence->length) { + operation = (buf[i] & CS35L43_POWER_SEQ_OP_MASK) >> + CS35L43_POWER_SEQ_OP_SHIFT; + + if (operation == CS35L43_POWER_SEQ_OP_END) + break; + + /* get num words for given operation */ + for (j = 0; j < CS35L43_POWER_SEQ_NUM_OPS; j++) { + if (cs35l43_write_seq_op_sizes[j][0] == operation) { + num_words = cs35l43_write_seq_op_sizes[j][1]; + break; + } + } + + if (j == CS35L43_POWER_SEQ_NUM_OPS) { + dev_err(dev, "Failed to determine op size\n"); + ret = -EINVAL; + goto err_free; + } + + op_words = kzalloc(num_words * sizeof(u32), GFP_KERNEL); + if (!op_words) { + ret = -ENOMEM; + goto err_free; + } + + memcpy(op_words, &buf[i], num_words * sizeof(u32)); + + write_seq_elem = devm_kzalloc(dev, sizeof(*write_seq_elem), GFP_KERNEL); + if (!write_seq_elem) { + ret = -ENOMEM; + goto err_parse; + } + + write_seq_elem->size = num_words; + write_seq_elem->offset = i; + write_seq_elem->operation = operation; + write_seq_elem->words = op_words; + list_add_tail(&write_seq_elem->list, &sequence->list_head); + + sequence->num_ops++; + i += num_words; + } + + dev_dbg(dev, "%s num ops: %d\n", sequence->name, sequence->num_ops); + dev_dbg(dev, "offset\tsize\twords\n"); + list_for_each_entry(write_seq_elem, &sequence->list_head, list) { + dev_dbg(dev, "0x%04X\t%d", write_seq_elem->offset, + write_seq_elem->size); + for (j = 0; j < write_seq_elem->size; j++) + dev_dbg(dev, "0x%08X", *(write_seq_elem->words + j)); + } + + if (operation != CS35L43_POWER_SEQ_OP_END) { + dev_err(dev, "WRITE SEQ END_OF_SCRIPT not found\n"); + ret = -E2BIG; + } + + kfree(buf); + return ret; + +err_parse: + kfree(op_words); +err_free: + kfree(buf); + return ret; +} + +static int cs35l43_dsp_reset(struct cs35l43_private *cs35l43) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cs35l43->component); + struct snd_soc_dapm_widget fake_dapm_widget = {.dapm = dapm}; + + unsigned int val = 0, dsprx2 = CS35L43_INPUT_SRC_ASPRX1; + int ret, retry = 10; + + if (!dapm || !cs35l43->component || !cs35l43->dsp.cs_dsp.booted) + return -EINVAL; + + dev_info(cs35l43->dev, "%s\n", __func__); + + if (pm_runtime_enabled(cs35l43->dev)) + pm_runtime_get_sync(cs35l43->dev); + + regmap_write(cs35l43->regmap, CS35L43_DSP_VIRTUAL1_MBOX_1, + CS35L43_MBOX_CMD_PREVENT_HIBERNATE); + regmap_write(cs35l43->regmap, CS35L43_DSP_VIRTUAL1_MBOX_1, + CS35L43_MBOX_CMD_AUDIO_PAUSE); + usleep_range(5000, 5100); + + /* Disable DSP and reset state variables */ + regmap_write(cs35l43->regmap, CS35L43_DSP1_CCM_CORE_CONTROL, 0x80); + regmap_write(cs35l43->regmap, CS35L43_PWRMGT_CTL, 0); + + /* Disable WDT */ + regmap_write(cs35l43->regmap, CS35L43_DSP1_WDT_CONTROL, 0x111000); + + /* Clear WDT status */ + regmap_write(cs35l43->regmap, CS35L43_DSP1_WDT_STATUS, 0x03); + + ret = wm_adsp_write_ctl(&cs35l43->dsp, "CALL_RAM_INIT", WMFW_ADSP2_XM, + cs35l43->dsp.cs_dsp.fw_id, + &val, sizeof(u32)); + if (ret < 0) + dev_err(cs35l43->dev, "Failed to clear CALL_RAM_INIT\n"); + + ret = wm_adsp_write_ctl(&cs35l43->dsp, "HALO_STATE", WMFW_ADSP2_XM, + cs35l43->dsp.cs_dsp.fw_id, + &val, sizeof(u32)); + if (ret < 0) + dev_err(cs35l43->dev, "Failed to clear HALO_STATE\n"); + + ret = wm_adsp_write_ctl(&cs35l43->dsp, "AUDIO_STATE", WMFW_ADSP2_XM, 0x5f212, + &val, sizeof(u32)); + if (ret < 0) + dev_err(cs35l43->dev, "Failed to clear AUDIO_STATE\n"); + + regmap_write(cs35l43->regmap, CS35L43_DSP1_MPU_LOCK_STATE, 0x5555); + regmap_write(cs35l43->regmap, CS35L43_DSP1_MPU_LOCK_STATE, 0xAAAA); + + regmap_read(cs35l43->regmap, CS35L43_DSP1_MPU_XM_VIO_STATUS, &val); + regmap_write(cs35l43->regmap, CS35L43_DSP1_MPU_XM_VIO_STATUS, val); + regmap_read(cs35l43->regmap, CS35L43_DSP1_MPU_YM_VIO_STATUS, &val); + regmap_write(cs35l43->regmap, CS35L43_DSP1_MPU_YM_VIO_STATUS, val); + regmap_read(cs35l43->regmap, CS35L43_DSP1_MPU_PM_VIO_STATUS, &val); + regmap_write(cs35l43->regmap, CS35L43_DSP1_MPU_PM_VIO_STATUS, val); + + regmap_write(cs35l43->regmap, CS35L43_DSP1_CCM_CORE_CONTROL, 0x281); + + do { + usleep_range(10000, 10100); + wm_adsp_read_ctl(&cs35l43->dsp, "HALO_STATE", + WMFW_ADSP2_XM, cs35l43->dsp.cs_dsp.fw_id, &val, sizeof(u32)); + val = be32_to_cpu(val); + dev_info(cs35l43->dev, "halo_state: %x\n", val); + } while (val != 2 && retry-- >= 0); + + if (retry < 0) + dev_err(cs35l43->dev, "%s: Cold boot failed\n", __func__); + + regmap_write(cs35l43->regmap, CS35L43_DSP_VIRTUAL1_MBOX_1, + CS35L43_MBOX_CMD_PREVENT_HIBERNATE); + regmap_write(cs35l43->regmap, CS35L43_DSP_VIRTUAL1_MBOX_1, + CS35L43_MBOX_CMD_AUDIO_PAUSE); + usleep_range(5000, 5100); + + cs35l43_log_status(cs35l43); + + /* Disable WDT */ + regmap_write(cs35l43->regmap, CS35L43_DSP1_WDT_CONTROL, 0x111000); + + /* Clear WDT status */ + regmap_write(cs35l43->regmap, CS35L43_DSP1_WDT_STATUS, 0x03); + + val = 0; + ret = wm_adsp_write_ctl(&cs35l43->dsp, "AUDIO_STATE", WMFW_ADSP2_XM, 0x5f212, + &val, sizeof(u32)); + if (ret < 0) + dev_err(cs35l43->dev, "Failed to clear AUDIO_STATE\n"); + + + regmap_write(cs35l43->regmap, CS35L43_DSP1_CCM_CORE_CONTROL, 0x80); + + val = cpu_to_be32(1); + ret = wm_adsp_write_ctl(&cs35l43->dsp, "CALL_RAM_INIT", WMFW_ADSP2_XM, + cs35l43->dsp.cs_dsp.fw_id, + &val, sizeof(u32)); + if (ret < 0) + dev_err(cs35l43->dev, "Failed to set CALL_RAM_INIT\n"); + + cs35l43->dsp.preloaded = 0; + cs35l43_dsp_audio_ev(&fake_dapm_widget, NULL, SND_SOC_DAPM_PRE_PMD); + cs35l43_main_amp_event(&fake_dapm_widget, NULL, SND_SOC_DAPM_POST_PMD); + cs35l43_dsp_preload_ev(&fake_dapm_widget, NULL, SND_SOC_DAPM_PRE_PMD); + regmap_write(cs35l43->regmap, CS35L43_GLOBAL_ENABLES, 0); + usleep_range(5000, 5100); + cs35l43_dsp_preload_ev(&fake_dapm_widget, NULL, SND_SOC_DAPM_PRE_PMU); + cs35l43_dsp_preload_ev(&fake_dapm_widget, NULL, SND_SOC_DAPM_POST_PMU); + cs35l43_dsp_audio_ev(&fake_dapm_widget, NULL, SND_SOC_DAPM_POST_PMU); + + /* Force update from cache */ + regmap_read(cs35l43->regmap, CS35L43_DSP1RX2_INPUT, &dsprx2); + regcache_drop_region(cs35l43->regmap, CS35L43_DSP1RX2_INPUT, CS35L43_DSP1RX2_INPUT); + regmap_write(cs35l43->regmap, CS35L43_DSP1RX2_INPUT, dsprx2); + + cs35l43_main_amp_event(&fake_dapm_widget, NULL, SND_SOC_DAPM_POST_PMU); + cs35l43->dsp.preloaded = 1; + + if (cs35l43_check_dsp_regs(cs35l43) != 0) + dev_err(cs35l43->dev, "Failed to reset DSP\n"); + + /* Sync values that may have been changed by write seq */ + regcache_sync_region(cs35l43->regmap, CS35L43_DEVID, + CS35L43_MIXER_NGATE_CH2_CFG); + + if (cs35l43->low_pwr_mode == CS35L43_LOW_PWR_MODE_HIBERNATE) { + if (cs35l43->write_seq_initialized) + cs35l43_write_seq_update(cs35l43, &cs35l43->power_on_seq); + } + + if (pm_runtime_enabled(cs35l43->dev)) + pm_runtime_put_autosuspend(cs35l43->dev); + + return ret; +} + +static void cs35l43_error_work(struct work_struct *wk) +{ + struct cs35l43_private *cs35l43; + + cs35l43 = container_of(wk, struct cs35l43_private, err_work); + + mutex_lock(&cs35l43->err_lock); + cs35l43_dsp_reset(cs35l43); + mutex_unlock(&cs35l43->err_lock); +} + +static int cs35l43_dsp_preload_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct cs35l43_private *cs35l43 = + snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (cs35l43->dsp.cs_dsp.booted) + return 0; + if (cs35l43->limit_spi_clock) { + cs35l43_regmap_update_bits(cs35l43, + CS35L43_REFCLK_INPUT, + CS35L43_PLL_FORCE_EN_MASK, + CS35L43_PLL_FORCE_EN_MASK); + cs35l43->limit_spi_clock(cs35l43, false); + } + wm_adsp_early_event(w, kcontrol, event); + break; + case SND_SOC_DAPM_POST_PMU: + if (cs35l43->dsp.cs_dsp.running) + return 0; + cs35l43_regmap_write(cs35l43, CS35L43_PWRMGT_CTL, CS35L43_MEM_RDY); + wm_adsp_event(w, kcontrol, event); + + cirrus_cal_apply(cs35l43->pdata.mfd_suffix); + + cs35l43->first_event = 0; + + if (cs35l43->low_pwr_mode == CS35L43_LOW_PWR_MODE_STANDBY) + cs35l43_regmap_write(cs35l43, CS35L43_DSP_VIRTUAL1_MBOX_1, + CS35L43_MBOX_CMD_PREVENT_HIBERNATE); + + cs35l43->delta_applied = 0; + if (cs35l43->limit_spi_clock) { + cs35l43->limit_spi_clock(cs35l43, true); + cs35l43_regmap_update_bits(cs35l43, + CS35L43_REFCLK_INPUT, + CS35L43_PLL_FORCE_EN_MASK, + 0); + } + break; + case SND_SOC_DAPM_PRE_PMD: + if (cs35l43->dsp.preloaded) + return 0; + + if (cs35l43->low_pwr_mode == CS35L43_LOW_PWR_MODE_HIBERNATE && + cs35l43->hibernate_state == CS35L43_HIBERNATE_STANDBY) { + mutex_lock(&cs35l43->hb_lock); + cs35l43_exit_hibernate(cs35l43); + mutex_unlock(&cs35l43->hb_lock); + } + + wm_adsp_early_event(w, kcontrol, event); + wm_adsp_event(w, kcontrol, event); + cs35l43->hibernate_state = CS35L43_HIBERNATE_NOT_LOADED; + break; + default: + break; + } + + return 0; +} + +static int cs35l43_dsp_audio_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct cs35l43_private *cs35l43 = + snd_soc_component_get_drvdata(component); + unsigned int audio_state; + + dev_info(cs35l43->dev, "%s\n", __func__); + + if (!cs35l43->dsp.cs_dsp.running) + return 0; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + + if (!cs35l43->first_event) { + regmap_update_bits(cs35l43->regmap, CS35L43_IRQ1_MASK_1, + CS35L43_DSP_VIRTUAL2_MBOX_WR_EINT1_MASK, 0); + cs35l43->first_event = true; + } + + cs35l43_regmap_write(cs35l43, CS35L43_DSP_VIRTUAL1_MBOX_1, + CS35L43_MBOX_CMD_AUDIO_PLAY); + usleep_range(2000, 2200); + wm_adsp_read_ctl(&cs35l43->dsp, "AUDIO_STATE", + WMFW_ADSP2_XM, 0x5f212, &audio_state, sizeof(u32)); + audio_state = be32_to_cpu(audio_state); + dev_info(cs35l43->dev, "PMU audio state post: 0x%x\n", audio_state); + if (audio_state != CS35L43_AUDIO_STATE_WAITING && + audio_state != CS35L43_AUDIO_STATE_RUNNING && + audio_state != CS35L43_AUDIO_STATE_RAMPDOWN && + audio_state != CS35L43_AUDIO_STATE_AUX_NG_MUTED) { + dev_err(cs35l43->dev, "Failed to set MBOX cmd PLAY\n"); + cs35l43_log_dsp_err(cs35l43); + cs35l43->mbox_err_pmu++; + } else + cs35l43->mbox_err_pmu = 0; + + if (cs35l43_check_dsp_regs(cs35l43) != 0) { + if (!mutex_is_locked(&cs35l43->err_lock)) + queue_work(cs35l43->err_wq, &cs35l43->err_work); + } + break; + case SND_SOC_DAPM_PRE_PMD: + cs35l43_regmap_write(cs35l43, CS35L43_DSP_VIRTUAL1_MBOX_1, + CS35L43_MBOX_CMD_AUDIO_PAUSE); + usleep_range(2000, 2200); + wm_adsp_read_ctl(&cs35l43->dsp, "AUDIO_STATE", + WMFW_ADSP2_XM, 0x5f212, &audio_state, sizeof(u32)); + audio_state = be32_to_cpu(audio_state); + dev_info(cs35l43->dev, "PMD audio state post: 0x%x\n", audio_state); + if (audio_state != CS35L43_AUDIO_STATE_READY && + audio_state != CS35L43_AUDIO_STATE_RUNNING && + audio_state != CS35L43_AUDIO_STATE_RAMPDOWN) { + dev_err(cs35l43->dev, "Failed to set MBOX cmd PAUSE\n"); + cs35l43_log_dsp_err(cs35l43); + cs35l43->mbox_err_pmd++; + } else + cs35l43->mbox_err_pmd = 0; + + if (cs35l43_check_dsp_regs(cs35l43) != 0 || + cs35l43->mbox_err_pmu + cs35l43->mbox_err_pmd >= 2) { + if (!mutex_is_locked(&cs35l43->err_lock)) + queue_work(cs35l43->err_wq, &cs35l43->err_work); + } + break; + default: + break; + } + + return 0; +} + +static int cs35l43_check_dsp_regs(struct cs35l43_private *cs35l43) +{ + int ret = 0; + unsigned int val; + unsigned int pm_state, audio_state; + + ret = wm_adsp_read_ctl(&cs35l43->dsp, "HALO_STATE", WMFW_ADSP2_XM, + cs35l43->dsp.cs_dsp.fw_id, + &val, sizeof(u32)); + if (ret < 0) { + dev_err(cs35l43->dev, "Failed to read HALO_STATE\n"); + return ret; + } + + val = be32_to_cpu(val); + if (val != 2) { + dev_err(cs35l43->dev, "%s: Error HALO_STATE = %u\n", __func__, val); + return -EINVAL; + } + + ret = wm_adsp_read_ctl(&cs35l43->dsp, "ERROR", WMFW_ADSP2_XM, 0x5f212, + &val, sizeof(u32)); + if (ret < 0) { + dev_err(cs35l43->dev, "Failed to read AUDIO_SYSTEM ERROR\n"); + return ret; + } + + val = be32_to_cpu(val); + if (val != 0) { + dev_err(cs35l43->dev, "%s: Error AUDIO_SYSTEM ERROR = %u\n", __func__, val); + return -EINVAL; + } + + regmap_read(cs35l43->regmap, CS35L43_DSP1_SCRATCH1, &val); + if (val) + ret = -EINVAL; + regmap_read(cs35l43->regmap, CS35L43_DSP1_SCRATCH2, &val); + if (val) + ret = -EINVAL; + regmap_read(cs35l43->regmap, CS35L43_DSP1_SCRATCH3, &val); + if (val) + ret = -EINVAL; + regmap_read(cs35l43->regmap, CS35L43_DSP1_SCRATCH4, &val); + if (val) + ret = -EINVAL; + if (ret) { + dev_err(cs35l43->dev, "%s: Error DSP SCRATCH\n", __func__); + return ret; + } + + wm_adsp_read_ctl(&cs35l43->dsp, "PM_CUR_STATE", + WMFW_ADSP2_XM, CS35L43_ALG_ID_PM, &pm_state, sizeof(u32)); + pm_state = be32_to_cpu(pm_state); + wm_adsp_read_ctl(&cs35l43->dsp, "AUDIO_STATE", + WMFW_ADSP2_XM, 0x5f212, &audio_state, sizeof(u32)); + audio_state = be32_to_cpu(audio_state); + dev_info(cs35l43->dev, "PM_STATE: 0x%x\tAUDIO_STATE: 0x%x\n", + pm_state, audio_state); + if (pm_state == 0x2 && audio_state == CS35L43_AUDIO_STATE_RAMPDOWN) { + dev_err(cs35l43->dev, "%s: Error PM_STATE and AUDIO_STATE\n", __func__); + return -EINVAL; + } + + return 0; +} + +static int cs35l43_log_dsp_err(struct cs35l43_private *cs35l43) +{ + int i; + unsigned int reg; + struct cs35l43_dsp_reg regs[] = { + { "PM_CUR_STATE", CS35L43_ALG_ID_PM }, + { "AUDIO_STATE", 0x5f212 }, + { "ERROR", 0x5f212 }, + { "HALO_STATE", cs35l43->dsp.cs_dsp.fw_id }, + { "HALO_HEARTBEAT", cs35l43->dsp.cs_dsp.fw_id }, + { "AUDIO_BLK_SIZE", cs35l43->dsp.cs_dsp.fw_id }, + { "RAM_INIT_COUNT", 0x5f224 }, + { "HIBER_COUNT", 0x5f224 }, + { "WDT_WARN_COUNT", 0x5f224 }, + { "MIPS_OVERRUN_FLG", 0x5f224 } }; + + dev_info(cs35l43->dev, "%s\n", __func__); + + for (i = 0; i < ARRAY_SIZE(regs); i++) { + wm_adsp_read_ctl(&cs35l43->dsp, regs[i].name, + WMFW_ADSP2_XM, regs[i].id, ®, sizeof(u32)); + dev_info(cs35l43->dev, "%s (0x%x): 0x%x\n", + regs[i].name, regs[i].id, reg); + } + + regmap_read(cs35l43->regmap, CS35L43_DSP1_MPU_XM_VIO_STATUS, ®); + dev_info(cs35l43->dev, "%s: CS35L43_DSP1_MPU_XM_VIO_STATUS 0x%x\n", __func__, reg); + regmap_read(cs35l43->regmap, CS35L43_DSP1_MPU_YM_VIO_STATUS, ®); + dev_info(cs35l43->dev, "%s: CS35L43_DSP1_MPU_YM_VIO_STATUS 0x%x\n", __func__, reg); + regmap_read(cs35l43->regmap, CS35L43_DSP1_MPU_PM_VIO_STATUS, ®); + dev_info(cs35l43->dev, "%s: CS35L43_DSP1_MPU_PM_VIO_STATUS 0x%x\n", __func__, reg); + + return 0; +} + +static void cs35l43_pll_config(struct cs35l43_private *cs35l43) +{ + + cs35l43_regmap_update_bits(cs35l43, CS35L43_REFCLK_INPUT, + CS35L43_PLL_OPEN_LOOP_MASK, + CS35L43_PLL_OPEN_LOOP_MASK); + cs35l43_regmap_update_bits(cs35l43, CS35L43_REFCLK_INPUT, + CS35L43_PLL_REFCLK_FREQ_MASK, + cs35l43->extclk_cfg << CS35L43_PLL_REFCLK_FREQ_SHIFT); + cs35l43_regmap_update_bits(cs35l43, CS35L43_REFCLK_INPUT, + CS35L43_PLL_REFCLK_EN_MASK, 0); + cs35l43_regmap_update_bits(cs35l43, CS35L43_REFCLK_INPUT, + CS35L43_PLL_REFCLK_SEL_MASK, cs35l43->clk_id); + cs35l43_regmap_update_bits(cs35l43, CS35L43_REFCLK_INPUT, + CS35L43_PLL_OPEN_LOOP_MASK, + 0); + cs35l43_regmap_update_bits(cs35l43, CS35L43_REFCLK_INPUT, + CS35L43_PLL_REFCLK_EN_MASK, + CS35L43_PLL_REFCLK_EN_MASK); +} + +static int cs35l43_check_mailbox(struct cs35l43_private *cs35l43) +{ + unsigned int *mbox; + unsigned int write_ptr, read_ptr, read_idx, write_idx, type, msg; + int i, ret; + + mbox = kmalloc_array(8, sizeof(*mbox), GFP_KERNEL); + if (!mbox) + return -ENOMEM; + + cs35l43_regmap_bulk_read(cs35l43, CS35L43_DSP_MBOX_1, mbox, 8); + for (i = 0; i < 8; i++) + dev_dbg(cs35l43->dev, "mbox[%d]: 0x%x\n", i + 1, mbox[i]); + + ret = wm_adsp_read_ctl(&cs35l43->dsp, "QUEUE_WT", + WMFW_ADSP2_XM, CS35L43_ALG_ID_MAILBOX, &write_ptr, sizeof(u32)); + if (ret < 0) + return ret; + ret = wm_adsp_read_ctl(&cs35l43->dsp, "QUEUE_RD", + WMFW_ADSP2_XM, CS35L43_ALG_ID_MAILBOX, &read_ptr, sizeof(u32)); + if (ret < 0) + return ret; + + write_ptr = be32_to_cpu(write_ptr); + read_ptr = be32_to_cpu(read_ptr); + + dev_dbg(cs35l43->dev, "QUEUE_WT: 0x%x\n", write_ptr); + dev_dbg(cs35l43->dev, "QUEUE_RD: 0x%x\n", read_ptr); + read_idx = (read_ptr & 0x1F) / 4; + write_idx = (write_ptr & 0x1F) / 4; + + if (write_idx == 0 || write_idx == read_idx) + goto exit; + + do { + dev_info(cs35l43->dev, "MESSAGE: 0x%x\n", mbox[read_idx]); + + type = mbox[read_idx] >> 24; + msg = mbox[read_idx]; + + switch (type) { + case CS35L43_MBOX_TYPE_PWR: + if (msg == CS35L43_MBOX_MSG_AWAKE) + dev_info(cs35l43->dev, "AWAKE\n"); + break; + case CS35L43_MBOX_TYPE_SYS: + if (msg == CS35L43_MBOX_MSG_ACK) + dev_info(cs35l43->dev, "ACK\n"); + break; + case CS35L43_MBOX_TYPE_AUDIO: + break; + case CS35L43_MBOX_TYPE_ERROR: + dev_err(cs35l43->dev, "Mailbox error: 0x%x\n", msg); + cs35l43_log_dsp_err(cs35l43); + break; + case CS35L43_MBOX_TYPE_MEM_VAL: + dev_err(cs35l43->dev, "Memory Validation error: 0x%x\n", msg); + cs35l43_log_dsp_err(cs35l43); + break; + case CS35L43_MBOX_TYPE_EVENT: + dev_info(cs35l43->dev, "Mailbox Event: 0x%x\n", msg); + break; + case CS35L43_MBOX_TYPE_WDT: + dev_info(cs35l43->dev, "WDT Warn: 0x%x\n", msg); + break; + default: + dev_err(cs35l43->dev, "Unknown msg type: 0x%x\n", type); + } + + read_idx++; + read_idx = read_idx % 8; + if (read_idx == 0) + read_idx++; + } while (read_idx != write_idx); + + write_ptr = cpu_to_be32(write_ptr); + wm_adsp_write_ctl(&cs35l43->dsp, "QUEUE_RD", + WMFW_ADSP2_XM, CS35L43_ALG_ID_MAILBOX, &write_ptr, sizeof(u32)); + +exit: + kfree(mbox); + return 0; +} + +static void cs35l43_mbox_work(struct work_struct *wk) +{ + struct cs35l43_private *cs35l43; + + cs35l43 = container_of(wk, struct cs35l43_private, mbox_work); + + cs35l43_check_mailbox(cs35l43); +} + +static void cs35l43_irq_work(struct work_struct *wk) +{ + struct delayed_work *dwork = to_delayed_work(wk); + struct cs35l43_private *cs35l43 = + container_of(dwork, struct cs35l43_private, irq_work); + + cs35l43->irq_unhandled_events = 0; +} + +static const struct reg_sequence cs35l43_pup_patch[] = { + {0x00000040, 0x00000055}, + {0x00000040, 0x000000AA}, + {0x00002084, 0x000F1AA0}, + {0x00000040, 0x000000CC}, + {0x00000040, 0x00000033}, +}; + +static const struct reg_sequence cs35l43_pdn_patch[] = { + {0x00000040, 0x00000055}, + {0x00000040, 0x000000AA}, + {0x00002084, 0x000F1AA3}, + {0x00000040, 0x000000CC}, + {0x00000040, 0x00000033}, +}; + +static void cs35l43_log_status(struct cs35l43_private *cs35l43) +{ + unsigned int pm_state, audio_state, reg; + + dev_info(cs35l43->dev, "%s\n", __func__); + + wm_adsp_read_ctl(&cs35l43->dsp, "PM_CUR_STATE", + WMFW_ADSP2_XM, CS35L43_ALG_ID_PM, &pm_state, sizeof(u32)); + pm_state = be32_to_cpu(pm_state); + wm_adsp_read_ctl(&cs35l43->dsp, "AUDIO_STATE", + WMFW_ADSP2_XM, 0x5f212, &audio_state, sizeof(u32)); + audio_state = be32_to_cpu(audio_state); + if (pm_state == 0x2 && audio_state == CS35L43_AUDIO_STATE_RAMPDOWN) { + dev_err(cs35l43->dev, "%s: Error PM_STATE and AUDIO_STATE\n", __func__); + if (!mutex_is_locked(&cs35l43->err_lock)) + queue_work(cs35l43->err_wq, &cs35l43->err_work); + } + + dev_info(cs35l43->dev, "PM_STATE: 0x%x\tAUDIO_STATE: 0x%x\n", + pm_state, audio_state); + + cs35l43_regmap_read(cs35l43, CS35L43_DACPCM1_INPUT, ®); + dev_info(cs35l43->dev, "DACPCM1_INPUT: 0x%x\n", reg); + + cs35l43_regmap_read(cs35l43, CS35L43_AMP_GAIN, ®); + dev_info(cs35l43->dev, "AMP_GAIN: 0x%x\n", reg); + + cs35l43_regmap_read(cs35l43, CS35L43_AMP_CTRL, ®); + dev_info(cs35l43->dev, "AMP_CTRL: 0x%x\n", reg); + + cs35l43_regmap_read(cs35l43, CS35L43_IRQ1_EINT_1, ®); + dev_info(cs35l43->dev, "IRQ1_EINT1: 0x%x\n", reg); +} + +static int cs35l43_enter_hibernate(struct cs35l43_private *cs35l43) +{ + unsigned int pm_state, audio_state; + + if (cs35l43->hibernate_state != CS35L43_HIBERNATE_AWAKE) + return 0; + + dev_info(cs35l43->dev, "%s\n", __func__); + + + cs35l43_regmap_write(cs35l43, CS35L43_IRQ1_MASK_1, 0xFFFFFFFF); + cs35l43_regmap_write(cs35l43, CS35L43_IRQ1_MASK_2, 0xFFFFFFFF); + cs35l43_regmap_write(cs35l43, CS35L43_IRQ1_MASK_3, 0xFFFFFFFF); + + if (cs35l43->limit_spi_clock) + cs35l43_regmap_write(cs35l43, CS35L43_WAKESRC_CTL, + CS35L43_WKSRC_SPI); + else + cs35l43_regmap_write(cs35l43, CS35L43_WAKESRC_CTL, + CS35L43_WKSRC_I2C); + + wm_adsp_read_ctl(&cs35l43->dsp, "PM_CUR_STATE", + WMFW_ADSP2_XM, CS35L43_ALG_ID_PM, &pm_state, sizeof(u32)); + wm_adsp_read_ctl(&cs35l43->dsp, "AUDIO_STATE", + WMFW_ADSP2_XM, 0x5f212, &audio_state, sizeof(u32)); + + dev_info(cs35l43->dev, "PM_STATE: 0x%x\tAUDIO_STATE: 0x%x\n", + pm_state, audio_state); + + if (cs35l43->write_seq_initialized) + cs35l43_write_seq_update(cs35l43, &cs35l43->power_on_seq); + + mutex_lock(&cs35l43->dsp.cs_dsp.pwr_lock); + + cs35l43_regmap_write(cs35l43, CS35L43_GLOBAL_ENABLES, 0); + + usleep_range(2100, 2500); + + cs35l43_regmap_write(cs35l43, CS35L43_DSP_VIRTUAL1_MBOX_1, + CS35L43_MBOX_CMD_ALLOW_HIBERNATE); + cs35l43_regmap_write(cs35l43, CS35L43_DSP_VIRTUAL1_MBOX_1, + CS35L43_MBOX_CMD_HIBERNATE); + + cs35l43->hibernate_state = CS35L43_HIBERNATE_STANDBY; + cs35l43->dsp.hibernate = true; + /* Do changes in cache during hibernation */ + regcache_cache_only(cs35l43->regmap, true); + regcache_mark_dirty(cs35l43->regmap); + + mutex_unlock(&cs35l43->dsp.cs_dsp.pwr_lock); + + return 0; +} + +static int cs35l43_exit_hibernate(struct cs35l43_private *cs35l43) +{ + int timeout = 10, ret = 0; + unsigned int status; + + if (cs35l43->hibernate_state != CS35L43_HIBERNATE_STANDBY && + cs35l43->hibernate_state != CS35L43_HIBERNATE_UPDATE) + return 0; + + dev_info(cs35l43->dev, "%s\n", __func__); + + mutex_lock(&cs35l43->dsp.cs_dsp.pwr_lock); + + regcache_cache_only(cs35l43->regmap, false); + + do { + ret = regmap_write(cs35l43->regmap, CS35L43_DSP_VIRTUAL1_MBOX_1, + CS35L43_MBOX_CMD_WAKEUP); + usleep_range(1000, 1100); + } while (ret < 0 && timeout-- > 0); + + + if (timeout == 0) + dev_err(cs35l43->dev, "Timeout at MBOX_CMD_WAKEUP\n"); + else if (ret == 0) + dev_info(cs35l43->dev, "%s wakeup command success: %d\n", + __func__, 10 - timeout); + + cs35l43->dsp.hibernate = false; + + cs35l43_regmap_write(cs35l43, CS35L43_DSP_VIRTUAL1_MBOX_1, + CS35L43_MBOX_CMD_PREVENT_HIBERNATE); + + usleep_range(2000, 2100); + + ret = cs35l43_regmap_read(cs35l43, CS35L43_PWRMGT_STS, &status); + if (ret < 0 || !(status & CS35L43_WKSRC_STS_MASK)) + dev_err(cs35l43->dev, "Error during wakeup, PWRMGT_STS = 0x%x\n", status); + + mutex_unlock(&cs35l43->dsp.cs_dsp.pwr_lock); + + /* PM_CUR_STATE should be non-zero */ + wm_adsp_read_ctl(&cs35l43->dsp, "PM_CUR_STATE", + WMFW_ADSP2_XM, CS35L43_ALG_ID_PM, &status, sizeof(u32)); + if (!status) + dev_err(cs35l43->dev, "Error during wakeup, PM_CUR_STATE = 0x%x\n", status); + + /* First MBOX outbound message should be AWAKE = CMD_WAKEUP */ + ret = cs35l43_regmap_read(cs35l43, CS35L43_DSP_MBOX_2, &status); + if (ret < 0 || (status != CS35L43_MBOX_CMD_WAKEUP)) + dev_err(cs35l43->dev, "Error during wakeup, MBOX2 = 0x%x\n", status); + + /* + * At this point FW applies register values stored in the sequencer + * Do sync to apply register values changed in cache during hibernation + */ + regcache_sync_region(cs35l43->regmap, CS35L43_DEVID, + CS35L43_MIXER_NGATE_CH2_CFG); + + /* Update write seq with values that could have changed in the cache */ + if (cs35l43->write_seq_initialized) + cs35l43_write_seq_update(cs35l43, &cs35l43->power_on_seq); + + cs35l43->hibernate_state = CS35L43_HIBERNATE_AWAKE; + + cs35l43_regmap_write(cs35l43, CS35L43_IRQ1_MASK_1, 0xFFFFFFFF); + cs35l43_regmap_update_bits(cs35l43, CS35L43_IRQ1_MASK_1, + CS35L43_AMP_ERR_EINT1_MASK | + CS35L43_BST_SHORT_ERR_EINT1_MASK | + CS35L43_BST_DCM_UVP_ERR_EINT1_MASK | + CS35L43_BST_OVP_ERR_EINT1_MASK | + CS35L43_DSP_VIRTUAL2_MBOX_WR_EINT1_MASK | + CS35L43_DC_WATCHDOG_IRQ_RISE_EINT1_MASK | + CS35L43_WKSRC_STATUS6_EINT1_MASK | + CS35L43_WKSRC_STATUS_ANY_EINT1_MASK, 0); + cs35l43_regmap_write(cs35l43, CS35L43_IRQ1_MASK_2, 0xFFFFFFFF); + cs35l43_regmap_update_bits(cs35l43, CS35L43_IRQ1_MASK_2, + CS35L43_PLL_UNLOCK_FLAG_RISE_EINT1_MASK | + CS35L43_PLL_LOCK_EINT1_MASK, 0); + regmap_write(cs35l43->regmap, CS35L43_IRQ1_MASK_3, 0xFFFFFFFF); + regmap_update_bits(cs35l43->regmap, CS35L43_IRQ1_MASK_3, + CS35L43_DSP1_NMI_ERR_EINT1_MASK | + CS35L43_DSP1_MPU_ERR_EINT1_MASK | + CS35L43_DSP1_STRM_ARB_ERR_EINT1_MASK, 0); + + regmap_multi_reg_write_bypassed(cs35l43->regmap, + cs35l43_pdn_patch, + ARRAY_SIZE(cs35l43_pdn_patch)); + + return 0; +} + +int cs35l43_suspend_runtime(struct device *dev) +{ + struct cs35l43_private *cs35l43 = dev_get_drvdata(dev); + int i, ret = 0; + + mutex_lock(&cs35l43->hb_lock); + + if (!cs35l43->write_seq_initialized && cs35l43->first_event && + cs35l43->dsp.cs_dsp.running) { + cs35l43->power_on_seq.name = "PM_PWR_ON_SEQ"; + cs35l43->power_on_seq.length = CS35L43_POWER_SEQ_MAX_WORDS; + ret = cs35l43_write_seq_init(cs35l43, &cs35l43->power_on_seq); + if (ret == -EINVAL) { + /* Fall back to control used before 7.15.3 */ + cs35l43->power_on_seq.name = "POWER_ON_SEQUENCE"; + ret = cs35l43_write_seq_init(cs35l43, + &cs35l43->power_on_seq); + } + + if (ret) + goto err; + + cs35l43_write_seq_update(cs35l43, &cs35l43->power_on_seq); + + for (i = 0; i < ARRAY_SIZE(cs35l43_hibernate_update_regs); i++) { + if (cs35l43_hibernate_update_regs[i] == 0) + break; + cs35l43_write_seq_add(cs35l43, &cs35l43->power_on_seq, + cs35l43_hibernate_update_regs[i], + 0, true); + } + cs35l43->write_seq_initialized = true; + } + + if (cs35l43->hibernate_state == CS35L43_HIBERNATE_NOT_LOADED && + cs35l43->dsp.cs_dsp.running) + cs35l43->hibernate_state = CS35L43_HIBERNATE_AWAKE; + + if (cs35l43->dsp.cs_dsp.running) + ret = cs35l43_enter_hibernate(cs35l43); +err: + mutex_unlock(&cs35l43->hb_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(cs35l43_suspend_runtime); + +int cs35l43_resume_runtime(struct device *dev) +{ + struct cs35l43_private *cs35l43 = dev_get_drvdata(dev); + int ret = 0; + + mutex_lock(&cs35l43->hb_lock); + if (cs35l43->dsp.cs_dsp.running) + ret = cs35l43_exit_hibernate(cs35l43); + mutex_unlock(&cs35l43->hb_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(cs35l43_resume_runtime); + +static int cs35l43_hibernate_dapm(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct cs35l43_private *cs35l43 = + snd_soc_component_get_drvdata(component); + int ret = 0; + + if (cs35l43->low_pwr_mode == CS35L43_LOW_PWR_MODE_STANDBY) + return 0; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (pm_runtime_suspended(cs35l43->dev)) { + dev_info(cs35l43->dev, "resume from hibernate dapm\n"); + pm_runtime_resume(cs35l43->dev); + } + break; + + default: + dev_err(cs35l43->dev, "Invalid event = 0x%x\n", event); + ret = -EINVAL; + } + return ret; +} + +static int cs35l43_main_amp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct cs35l43_private *cs35l43 = + snd_soc_component_get_drvdata(component); + int ret = 0; + + dev_dbg(cs35l43->dev, "%s\n", __func__); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + dev_info(cs35l43->dev, "%s PMU\n", __func__); + if (cs35l43->dsp.cs_dsp.running) + cs35l43_apply_delta_tuning(cs35l43); + regmap_multi_reg_write_bypassed(cs35l43->regmap, + cs35l43_pup_patch, + ARRAY_SIZE(cs35l43_pup_patch)); + cs35l43_regmap_write(cs35l43, CS35L43_GLOBAL_ENABLES, 1); + cs35l43_regmap_update_bits(cs35l43, CS35L43_BLOCK_ENABLES, + CS35L43_AMP_EN_MASK, CS35L43_AMP_EN_MASK); + if (cs35l43->limit_spi_clock) + cs35l43->limit_spi_clock(cs35l43, false); + + cs35l43_log_status(cs35l43); + break; + case SND_SOC_DAPM_POST_PMD: + dev_info(cs35l43->dev, "%s PMD\n", __func__); + if (cs35l43->limit_spi_clock) + cs35l43->limit_spi_clock(cs35l43, true); + cs35l43_regmap_update_bits(cs35l43, CS35L43_BLOCK_ENABLES, + CS35L43_AMP_EN_MASK, 0); + + regmap_multi_reg_write_bypassed(cs35l43->regmap, + cs35l43_pdn_patch, + ARRAY_SIZE(cs35l43_pdn_patch)); + + if (cs35l43->low_pwr_mode == CS35L43_LOW_PWR_MODE_STANDBY) { + usleep_range(1000, 1100); + cs35l43_regmap_write(cs35l43, CS35L43_GLOBAL_ENABLES, 0); + } + break; + default: + dev_err(cs35l43->dev, "Invalid event = 0x%x\n", event); + ret = -EINVAL; + } + return ret; +} + +static const struct snd_soc_dapm_widget cs35l43_dapm_widgets[] = { + + SND_SOC_DAPM_OUT_DRV_E("Main AMP", SND_SOC_NOPM, 0, 0, NULL, 0, + cs35l43_main_amp_event, + SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SUPPLY("Hibernate", SND_SOC_NOPM, 0, 0, + cs35l43_hibernate_dapm, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_OUTPUT("AMP SPK"), + + SND_SOC_DAPM_SPK("DSP1 Preload", NULL), + SND_SOC_DAPM_SUPPLY_S("DSP1 Preloader", 100, + SND_SOC_NOPM, 0, 0, cs35l43_dsp_preload_ev, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_OUT_DRV_E("DSP1", SND_SOC_NOPM, 0, 0, NULL, 0, + cs35l43_dsp_audio_ev, SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_AIF_IN("ASPRX1", NULL, 0, CS35L43_ASP_ENABLES1, + CS35L43_ASP_RX1_EN_SHIFT, 0), + SND_SOC_DAPM_AIF_IN("ASPRX2", NULL, 0, CS35L43_ASP_ENABLES1, + CS35L43_ASP_RX2_EN_SHIFT, 0), + SND_SOC_DAPM_AIF_OUT("ASPTX1", NULL, 0, CS35L43_ASP_ENABLES1, + CS35L43_ASP_TX1_EN_SHIFT, 0), + SND_SOC_DAPM_AIF_OUT("ASPTX2", NULL, 0, CS35L43_ASP_ENABLES1, + CS35L43_ASP_TX2_EN_SHIFT, 0), + SND_SOC_DAPM_AIF_OUT("ASPTX3", NULL, 0, CS35L43_ASP_ENABLES1, + CS35L43_ASP_TX3_EN_SHIFT, 0), + SND_SOC_DAPM_AIF_OUT("ASPTX4", NULL, 0, CS35L43_ASP_ENABLES1, + CS35L43_ASP_TX4_EN_SHIFT, 0), + + SND_SOC_DAPM_MUX("ASP TX1 Source", SND_SOC_NOPM, 0, 0, &asp_tx1_mux), + SND_SOC_DAPM_MUX("ASP TX2 Source", SND_SOC_NOPM, 0, 0, &asp_tx2_mux), + SND_SOC_DAPM_MUX("ASP TX3 Source", SND_SOC_NOPM, 0, 0, &asp_tx3_mux), + SND_SOC_DAPM_MUX("ASP TX4 Source", SND_SOC_NOPM, 0, 0, &asp_tx4_mux), + SND_SOC_DAPM_MUX("DSP RX1 Source", SND_SOC_NOPM, 0, 0, &dsp_rx1_mux), + SND_SOC_DAPM_MUX("DSP RX2 Source", SND_SOC_NOPM, 0, 0, &dsp_rx2_mux), + SND_SOC_DAPM_MUX("DSP RX3 Source", SND_SOC_NOPM, 0, 0, &dsp_rx3_mux), + SND_SOC_DAPM_MUX("PCM Source", SND_SOC_NOPM, 0, 0, &dacpcm_mux), + SND_SOC_DAPM_MUX("High Rate PCM Source", SND_SOC_NOPM, 0, 0, &dacpcm2_mux), + SND_SOC_DAPM_MUX("Ultrasonic Mode", SND_SOC_NOPM, 0, 0, &ultra_mux), + + SND_SOC_DAPM_ADC("VMON ADC", NULL, CS35L43_BLOCK_ENABLES, + CS35L43_VMON_EN_SHIFT, 0), + SND_SOC_DAPM_ADC("IMON ADC", NULL, CS35L43_BLOCK_ENABLES, + CS35L43_IMON_EN_SHIFT, 0), + SND_SOC_DAPM_ADC("VPMON ADC", NULL, CS35L43_BLOCK_ENABLES, + CS35L43_VPMON_EN_SHIFT, 0), + SND_SOC_DAPM_ADC("VBSTMON ADC", NULL, CS35L43_BLOCK_ENABLES, + CS35L43_VBSTMON_EN_SHIFT, 0), + SND_SOC_DAPM_ADC("TEMPMON ADC", NULL, CS35L43_BLOCK_ENABLES, + CS35L43_TEMPMON_EN_SHIFT, 0), + SND_SOC_DAPM_SWITCH("AMP Enable", SND_SOC_NOPM, 0, 1, &_enable_ctrl), +}; + +static const struct snd_soc_dapm_route cs35l43_audio_map[] = { + + + { "DSP1", NULL, "DSP1 Preloader" }, + { "DSP1 Preload", NULL, "DSP1 Preloader" }, + + {"DSP1", NULL, "DSP RX1 Source"}, + {"DSP1", NULL, "DSP RX2 Source"}, + {"DSP1", NULL, "DSP RX3 Source"}, + + {"PCM Source", "ASPRX1", "ASPRX1"}, + {"PCM Source", "ASPRX2", "ASPRX2"}, + {"PCM Source", "DSP", "DSP1"}, + {"PCM Source", "DSP FS2", "DSP1"}, + {"High Rate PCM Source", "ASPRX1", "ASPRX1"}, + {"High Rate PCM Source", "ASPRX2", "ASPRX2"}, + {"High Rate PCM Source", "DSP", "DSP1"}, + {"High Rate PCM Source", "DSP FS2", "DSP1"}, + {"Ultrasonic Mode", "In Band", "High Rate PCM Source"}, + {"Ultrasonic Mode", "Out of Band", "High Rate PCM Source"}, + {"Main AMP", NULL, "Ultrasonic Mode"}, + {"Main AMP", NULL, "PCM Source"}, + {"AMP SPK", NULL, "Main AMP"}, + {"AMP SPK", NULL, "Hibernate"}, + + {"ASP TX1 Source", "ASPRX1", "ASPRX1"}, + {"ASP TX2 Source", "ASPRX1", "ASPRX1"}, + {"ASP TX3 Source", "ASPRX1", "ASPRX1"}, + {"ASP TX4 Source", "ASPRX1", "ASPRX1"}, + {"DSP RX1 Source", "ASPRX1", "ASPRX1"}, + {"DSP RX2 Source", "ASPRX1", "ASPRX1"}, + {"DSP RX3 Source", "ASPRX1", "ASPRX1"}, + {"ASP TX1 Source", "ASPRX2", "ASPRX2"}, + {"ASP TX2 Source", "ASPRX2", "ASPRX2"}, + {"ASP TX3 Source", "ASPRX2", "ASPRX2"}, + {"ASP TX4 Source", "ASPRX2", "ASPRX2"}, + {"DSP RX1 Source", "ASPRX2", "ASPRX2"}, + {"DSP RX2 Source", "ASPRX2", "ASPRX2"}, + {"DSP RX3 Source", "ASPRX2", "ASPRX2"}, + {"ASP TX1 Source", "VMON", "VMON ADC"}, + {"ASP TX2 Source", "VMON", "VMON ADC"}, + {"ASP TX3 Source", "VMON", "VMON ADC"}, + {"ASP TX4 Source", "VMON", "VMON ADC"}, + {"DSP RX1 Source", "VMON", "VMON ADC"}, + {"DSP RX2 Source", "VMON", "VMON ADC"}, + {"DSP RX3 Source", "VMON", "VMON ADC"}, + {"ASP TX1 Source", "VMON FS2", "VMON ADC"}, + {"ASP TX2 Source", "VMON FS2", "VMON ADC"}, + {"ASP TX3 Source", "VMON FS2", "VMON ADC"}, + {"ASP TX4 Source", "VMON FS2", "VMON ADC"}, + {"DSP RX1 Source", "VMON FS2", "VMON ADC"}, + {"DSP RX2 Source", "VMON FS2", "VMON ADC"}, + {"DSP RX3 Source", "VMON FS2", "VMON ADC"}, + {"ASP TX1 Source", "IMON", "IMON ADC"}, + {"ASP TX2 Source", "IMON", "IMON ADC"}, + {"ASP TX3 Source", "IMON", "IMON ADC"}, + {"ASP TX4 Source", "IMON", "IMON ADC"}, + {"DSP RX1 Source", "IMON", "IMON ADC"}, + {"DSP RX2 Source", "IMON", "IMON ADC"}, + {"DSP RX3 Source", "IMON", "IMON ADC"}, + {"ASP TX1 Source", "IMON FS2", "IMON ADC"}, + {"ASP TX2 Source", "IMON FS2", "IMON ADC"}, + {"ASP TX3 Source", "IMON FS2", "IMON ADC"}, + {"ASP TX4 Source", "IMON FS2", "IMON ADC"}, + {"DSP RX1 Source", "IMON FS2", "IMON ADC"}, + {"DSP RX2 Source", "IMON FS2", "IMON ADC"}, + {"DSP RX3 Source", "IMON FS2", "IMON ADC"}, + {"ASP TX1 Source", "VPMON", "VPMON ADC"}, + {"ASP TX2 Source", "VPMON", "VPMON ADC"}, + {"ASP TX3 Source", "VPMON", "VPMON ADC"}, + {"ASP TX4 Source", "VPMON", "VPMON ADC"}, + {"DSP RX1 Source", "VPMON", "VPMON ADC"}, + {"DSP RX2 Source", "VPMON", "VPMON ADC"}, + {"DSP RX3 Source", "VPMON", "VPMON ADC"}, + {"ASP TX1 Source", "VBSTMON", "VBSTMON ADC"}, + {"ASP TX2 Source", "VBSTMON", "VBSTMON ADC"}, + {"ASP TX3 Source", "VBSTMON", "VBSTMON ADC"}, + {"ASP TX4 Source", "VBSTMON", "VBSTMON ADC"}, + {"DSP RX1 Source", "VBSTMON", "VBSTMON ADC"}, + {"DSP RX2 Source", "VBSTMON", "VBSTMON ADC"}, + {"DSP RX3 Source", "VBSTMON", "VBSTMON ADC"}, + {"ASP TX1 Source", "DSP", "DSP1"}, + {"ASP TX2 Source", "DSP", "DSP1"}, + {"ASP TX3 Source", "DSP", "DSP1"}, + {"ASP TX4 Source", "DSP", "DSP1"}, + {"ASP TX1 Source", "DSP FS2", "DSP1"}, + {"ASP TX2 Source", "DSP FS2", "DSP1"}, + {"ASP TX3 Source", "DSP FS2", "DSP1"}, + {"ASP TX4 Source", "DSP FS2", "DSP1"}, + {"ASPTX1", NULL, "ASP TX1 Source"}, + {"ASPTX2", NULL, "ASP TX2 Source"}, + {"ASPTX3", NULL, "ASP TX3 Source"}, + {"ASPTX4", NULL, "ASP TX4 Source"}, + {"AMP Capture", NULL, "ASPTX1"}, + {"AMP Capture", NULL, "ASPTX2"}, + {"AMP Capture", NULL, "ASPTX3"}, + {"AMP Capture", NULL, "ASPTX4"}, + + {"DSP1", NULL, "IMON ADC"}, + {"DSP1", NULL, "VMON ADC"}, + {"DSP1", NULL, "VBSTMON ADC"}, + {"DSP1", NULL, "VPMON ADC"}, + {"DSP1", NULL, "TEMPMON ADC"}, + + {"AMP Enable", "Switch", "AMP Playback"}, + + {"ASPRX1", NULL, "AMP Enable"}, + {"ASPRX2", NULL, "AMP Enable"}, + {"VMON ADC", NULL, "AMP Enable"}, + {"IMON ADC", NULL, "AMP Enable"}, + {"VPMON ADC", NULL, "AMP Enable"}, + {"VBSTMON ADC", NULL, "AMP Enable"}, + {"TEMPMON ADC", NULL, "AMP Enable"}, +}; + + +static irqreturn_t cs35l43_irq(int irq, void *data) +{ + struct cs35l43_private *cs35l43 = data; + unsigned int status[3], masks[3], pin_status; + int ret = IRQ_NONE, i; + bool is_pm_runtime_enabled = pm_runtime_enabled(cs35l43->dev); + + if (cs35l43->low_pwr_mode == CS35L43_LOW_PWR_MODE_HIBERNATE && + !pm_runtime_enabled(cs35l43->dev) && + cs35l43->hibernate_state != CS35L43_HIBERNATE_NOT_LOADED) { + if (cs35l43->hibernate_state == CS35L43_HIBERNATE_AWAKE) { + /* hibernation entry is pending, ack EINTs and clear masks */ + cs35l43_regmap_write(cs35l43, CS35L43_IRQ1_EINT_1, 0xFFFFFFFF); + cs35l43_regmap_write(cs35l43, CS35L43_IRQ1_EINT_2, 0xFFFFFFFF); + cs35l43_regmap_write(cs35l43, CS35L43_IRQ1_EINT_3, 0xFFFFFFFF); + cs35l43_regmap_write(cs35l43, CS35L43_IRQ1_MASK_1, 0xFFFFFFFF); + cs35l43_regmap_write(cs35l43, CS35L43_IRQ1_MASK_2, 0xFFFFFFFF); + cs35l43_regmap_write(cs35l43, CS35L43_IRQ1_MASK_3, 0xFFFFFFFF); + return IRQ_HANDLED; + } + + cs35l43->irq_unhandled_events++; + cancel_delayed_work(&cs35l43->irq_work); + queue_delayed_work(cs35l43->irq_wq, &cs35l43->irq_work, + msecs_to_jiffies(CS35L43_IRQ_UNHANDLED_ALERT_INTERVAL_MS)); + + dev_info_ratelimited(cs35l43->dev, + "%s: ignoring: pwr_mode=%d, pm_runtime_enabled=%d, hibernate_state=%d, irq_unhandled_events=%d\n", + __func__, cs35l43->low_pwr_mode, is_pm_runtime_enabled, + cs35l43->hibernate_state, cs35l43->irq_unhandled_events); + + if (cs35l43->irq_unhandled_events > CS35L43_IRQ_UNHANDLED_ALERT_THRESH) { + dev_info(cs35l43->dev, "Excess IRQ events detected\n"); + pm_runtime_enable(cs35l43->dev); + } else + return ret; + } + + pm_runtime_get_sync(cs35l43->dev); + + for (i = 0; i < ARRAY_SIZE(status); i++) { + cs35l43_regmap_read(cs35l43, + CS35L43_IRQ1_EINT_1 + (i * 4), + &status[i]); + cs35l43_regmap_read(cs35l43, + CS35L43_IRQ1_MASK_1 + (i * 4), + &masks[i]); + } + + /* Check to see if unmasked bits are active */ + if (!(status[0] & ~masks[0]) && + !(status[1] & ~masks[1]) && + !(status[2] & ~masks[2])) { + ret = IRQ_NONE; + dev_info_ratelimited(cs35l43->dev, + "%s: ignoring due to mask/status bits\n", __func__); + for (i = 0; i < ARRAY_SIZE(status); i++) + dev_info_ratelimited(cs35l43->dev, "mask[%d]=0x%x status[%d]=0x%x\n", + i, masks[i], i, status[i]); + cs35l43_regmap_read(cs35l43, CS35L43_IRQ1_STATUS, &pin_status); + dev_info_ratelimited(cs35l43->dev, "pin_status: %d\n", pin_status); + goto done; + } + + /* + * The following interrupts require a + * protection release cycle to get the + * speaker out of Safe-Mode. + */ + if (status[0] & CS35L43_AMP_ERR_EINT1_MASK) { + dev_crit(cs35l43->dev, "Amp short error\n"); + cs35l43_regmap_write(cs35l43, CS35L43_IRQ1_EINT_1, + CS35L43_AMP_ERR_EINT1_MASK); + cs35l43_regmap_write(cs35l43, CS35L43_ERROR_RELEASE, 0); + cs35l43_regmap_update_bits(cs35l43, CS35L43_ERROR_RELEASE, + CS35L43_AMP_SHORT_ERR_RLS_MASK, + CS35L43_AMP_SHORT_ERR_RLS_MASK); + cs35l43_regmap_update_bits(cs35l43, CS35L43_ERROR_RELEASE, + CS35L43_AMP_SHORT_ERR_RLS_MASK, 0); + cirrus_bd_amp_err(cs35l43->pdata.mfd_suffix); + } + + if (status[0] & CS35L43_BST_OVP_ERR_EINT1_MASK) { + dev_crit(cs35l43->dev, "VBST Over Voltage error\n"); + cs35l43_regmap_update_bits(cs35l43, CS35L43_BLOCK_ENABLES, + CS35L43_BST_EN_MASK << + CS35L43_BST_EN_SHIFT, 0); + cs35l43_regmap_write(cs35l43, CS35L43_IRQ1_EINT_1, + CS35L43_BST_OVP_ERR_EINT1_MASK); + cs35l43_regmap_write(cs35l43, CS35L43_ERROR_RELEASE, 0); + cs35l43_regmap_update_bits(cs35l43, CS35L43_ERROR_RELEASE, + CS35L43_BST_OVP_ERR_RLS_MASK, + CS35L43_BST_OVP_ERR_RLS_MASK); + cs35l43_regmap_update_bits(cs35l43, CS35L43_ERROR_RELEASE, + CS35L43_BST_OVP_ERR_RLS_MASK, 0); + cs35l43_regmap_update_bits(cs35l43, CS35L43_BLOCK_ENABLES, + CS35L43_BST_EN_MASK << + CS35L43_BST_EN_SHIFT, + CS35L43_BST_EN_DEFAULT << + CS35L43_BST_EN_SHIFT); + } + + if (status[0] & CS35L43_BST_DCM_UVP_ERR_EINT1_MASK) { + dev_crit(cs35l43->dev, "DCM VBST Under Voltage Error\n"); + cs35l43_regmap_update_bits(cs35l43, CS35L43_BLOCK_ENABLES, + CS35L43_BST_EN_MASK << + CS35L43_BST_EN_SHIFT, 0); + cs35l43_regmap_write(cs35l43, CS35L43_IRQ1_EINT_1, + CS35L43_BST_DCM_UVP_ERR_EINT1_MASK); + cs35l43_regmap_write(cs35l43, CS35L43_ERROR_RELEASE, 0); + cs35l43_regmap_update_bits(cs35l43, CS35L43_ERROR_RELEASE, + CS35L43_BST_UVP_ERR_RLS_MASK, + CS35L43_BST_UVP_ERR_RLS_MASK); + cs35l43_regmap_update_bits(cs35l43, CS35L43_ERROR_RELEASE, + CS35L43_BST_UVP_ERR_RLS_MASK, 0); + cs35l43_regmap_update_bits(cs35l43, CS35L43_BLOCK_ENABLES, + CS35L43_BST_EN_MASK << + CS35L43_BST_EN_SHIFT, + CS35L43_BST_EN_DEFAULT << + CS35L43_BST_EN_SHIFT); + } + + if (status[0] & CS35L43_BST_SHORT_ERR_EINT1_MASK) { + dev_crit(cs35l43->dev, "LBST error: powering off!\n"); + cs35l43_regmap_update_bits(cs35l43, CS35L43_BLOCK_ENABLES, + CS35L43_BST_EN_MASK << + CS35L43_BST_EN_SHIFT, 0); + cs35l43_regmap_write(cs35l43, CS35L43_IRQ1_EINT_1, + CS35L43_BST_SHORT_ERR_EINT1_MASK); + cs35l43_regmap_write(cs35l43, CS35L43_ERROR_RELEASE, 0); + cs35l43_regmap_update_bits(cs35l43, CS35L43_ERROR_RELEASE, + CS35L43_BST_SHORT_ERR_RLS_MASK, + CS35L43_BST_SHORT_ERR_RLS_MASK); + cs35l43_regmap_update_bits(cs35l43, CS35L43_ERROR_RELEASE, + CS35L43_BST_SHORT_ERR_RLS_MASK, 0); + cs35l43_regmap_update_bits(cs35l43, CS35L43_BLOCK_ENABLES, + CS35L43_BST_EN_MASK << + CS35L43_BST_EN_SHIFT, + CS35L43_BST_EN_DEFAULT << + CS35L43_BST_EN_SHIFT); + cirrus_bd_bst_short(cs35l43->pdata.mfd_suffix); + } + + if (status[0] & CS35L43_DC_WATCHDOG_IRQ_RISE_EINT1_MASK) { + dev_err(cs35l43->dev, "DC Detect INT\n"); + cs35l43_regmap_write(cs35l43, CS35L43_IRQ1_EINT_1, + CS35L43_DC_WATCHDOG_IRQ_RISE_EINT1_MASK); + cs35l43_regmap_write(cs35l43, CS35L43_IRQ1_EINT_1, + CS35L43_DC_WATCHDOG_IRQ_RISE_EINT1_MASK); + } + + if (status[0] & CS35L43_WKSRC_STATUS_ANY_EINT1_MASK || + status[0] & CS35L43_WKSRC_STATUS6_EINT1_MASK) { + dev_info(cs35l43->dev, "Wakeup INT\n"); + cs35l43_regmap_write(cs35l43, CS35L43_IRQ1_EINT_1, + CS35L43_WKSRC_STATUS_ANY_EINT1_MASK); + cs35l43_regmap_write(cs35l43, CS35L43_IRQ1_EINT_1, + CS35L43_WKSRC_STATUS6_EINT1_MASK); + } + + + if (status[0] & CS35L43_DSP_VIRTUAL2_MBOX_WR_EINT1_MASK) { + dev_info(cs35l43->dev, "Received Mailbox INT\n"); + cs35l43_regmap_write(cs35l43, CS35L43_IRQ1_EINT_1, + CS35L43_DSP_VIRTUAL2_MBOX_WR_EINT1_MASK); + queue_work(cs35l43->mbox_wq, &cs35l43->mbox_work); + } + + if (status[1] & CS35L43_PLL_UNLOCK_FLAG_RISE_EINT1_MASK) { + dev_info(cs35l43->dev, "PLL Unlock INT\n"); + cs35l43_regmap_write(cs35l43, CS35L43_IRQ1_EINT_2, + CS35L43_PLL_UNLOCK_FLAG_RISE_EINT1_MASK); + } + + if (status[1] & CS35L43_PLL_LOCK_EINT1_MASK) { + dev_info(cs35l43->dev, "PLL Lock INT\n"); + cs35l43_regmap_write(cs35l43, CS35L43_IRQ1_EINT_2, + CS35L43_PLL_LOCK_EINT1_MASK); + } + + if (status[2] & CS35L43_DSP1_NMI_ERR_EINT1_MASK) { + dev_err(cs35l43->dev, "NMI Error INT\n"); + regmap_write(cs35l43->regmap, CS35L43_IRQ1_EINT_3, + CS35L43_DSP1_NMI_ERR_EINT1_MASK); + cs35l43_log_dsp_err(cs35l43); + + if (!mutex_is_locked(&cs35l43->err_lock)) + queue_work(cs35l43->err_wq, &cs35l43->err_work); + } + + if (status[2] & CS35L43_DSP1_MPU_ERR_EINT1_MASK) { + dev_err(cs35l43->dev, "MPU Error INT\n"); + regmap_write(cs35l43->regmap, CS35L43_IRQ1_EINT_3, + CS35L43_DSP1_MPU_ERR_EINT1_MASK); + cs35l43_log_dsp_err(cs35l43); + + if (!mutex_is_locked(&cs35l43->err_lock)) + queue_work(cs35l43->err_wq, &cs35l43->err_work); + } + + if (status[2] & CS35L43_DSP1_STRM_ARB_ERR_EINT1_MASK) { + dev_err(cs35l43->dev, "Stream Arb Error INT\n"); + regmap_write(cs35l43->regmap, CS35L43_IRQ1_EINT_3, + CS35L43_DSP1_STRM_ARB_ERR_EINT1_MASK); + cs35l43_log_dsp_err(cs35l43); + } + + ret = IRQ_HANDLED; + +done: + pm_runtime_mark_last_busy(cs35l43->dev); + pm_runtime_put_autosuspend(cs35l43->dev); + + return ret; +} + +static int cs35l43_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct cs35l43_private *cs35l43 = + snd_soc_component_get_drvdata(codec_dai->component); + + dev_dbg(cs35l43->dev, "%s\n", __func__); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + dev_warn(cs35l43->dev, + "%s: Master mode unsupported\n", __func__); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + cs35l43->asp_fmt = 0; + break; + case SND_SOC_DAIFMT_I2S: + cs35l43->asp_fmt = 2; + break; + default: + dev_warn(cs35l43->dev, + "%s: Invalid or unsupported DAI format\n", __func__); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_IF: + cs35l43->lrclk_fmt = 1; + cs35l43->sclk_fmt = 0; + break; + case SND_SOC_DAIFMT_IB_NF: + cs35l43->lrclk_fmt = 0; + cs35l43->sclk_fmt = 1; + break; + case SND_SOC_DAIFMT_IB_IF: + cs35l43->lrclk_fmt = 1; + cs35l43->sclk_fmt = 1; + break; + case SND_SOC_DAIFMT_NB_NF: + cs35l43->lrclk_fmt = 0; + cs35l43->sclk_fmt = 0; + break; + default: + dev_warn(cs35l43->dev, + "%s: Invalid DAI clock INV\n", __func__); + return -EINVAL; + } + + cs35l43_regmap_update_bits(cs35l43, CS35L43_ASP_CONTROL2, + CS35L43_ASP_FMT_MASK | CS35L43_ASP_BCLK_INV_MASK | + CS35L43_ASP_FSYNC_INV_MASK, + (cs35l43->asp_fmt << CS35L43_ASP_FMT_SHIFT) | + (cs35l43->lrclk_fmt << CS35L43_ASP_FSYNC_INV_SHIFT) | + (cs35l43->sclk_fmt << CS35L43_ASP_BCLK_INV_SHIFT)); + return 0; +} + +struct cs35l43_global_fs_config { + int rate; + int fs_cfg; +}; + +static const struct cs35l43_global_fs_config cs35l43_fs_rates[] = { + { 12000, 0x01 }, + { 24000, 0x02 }, + { 48000, 0x03 }, + { 96000, 0x04 }, + { 192000, 0x05 }, + { 11025, 0x09 }, + { 22050, 0x0A }, + { 44100, 0x0B }, + { 88200, 0x0C }, + { 176400, 0x0D }, + { 8000, 0x11 }, + { 16000, 0x12 }, + { 32000, 0x13 }, +}; + +static int cs35l43_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int i; + unsigned int rate = params_rate(params); + u8 asp_width, asp_wl; + struct cs35l43_private *cs35l43 = + snd_soc_component_get_drvdata(dai->component); + + dev_dbg(cs35l43->dev, "%s\n", __func__); + + for (i = 0; i < ARRAY_SIZE(cs35l43_fs_rates); i++) { + if (rate == cs35l43_fs_rates[i].rate) + break; + } + + if (i < ARRAY_SIZE(cs35l43_fs_rates) && + cs35l43->ultrasonic_mode == CS35L43_ULTRASONIC_MODE_DISABLED) + cs35l43_regmap_update_bits(cs35l43, CS35L43_GLOBAL_SAMPLE_RATE, + CS35L43_GLOBAL_FS_MASK, cs35l43_fs_rates[i].fs_cfg); + else if (cs35l43->ultrasonic_mode != CS35L43_ULTRASONIC_MODE_DISABLED) + /* Assume 48k base rate */ + cs35l43_regmap_update_bits(cs35l43, CS35L43_GLOBAL_SAMPLE_RATE, + CS35L43_GLOBAL_FS_MASK, 0x03); + else { + dev_err(cs35l43->dev, "%s: Unsupported rate\n", __func__); + return -EINVAL; + } + + asp_wl = params_width(params); + asp_width = cs35l43->slot_width ? + cs35l43->slot_width : params_physical_width(params); + dev_dbg(cs35l43->dev, "%s\n wl=%d, width=%d, rate=%d", __func__, + asp_wl, asp_width, rate); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + cs35l43_regmap_update_bits(cs35l43, CS35L43_ASP_CONTROL2, + CS35L43_ASP_RX_WIDTH_MASK, asp_width << + CS35L43_ASP_RX_WIDTH_SHIFT); + cs35l43_regmap_update_bits(cs35l43, CS35L43_ASP_DATA_CONTROL5, + CS35L43_ASP_RX_WL_MASK, asp_wl); + } else { + cs35l43_regmap_update_bits(cs35l43, CS35L43_ASP_CONTROL2, + CS35L43_ASP_TX_WIDTH_MASK, asp_width << + CS35L43_ASP_TX_WIDTH_SHIFT); + cs35l43_regmap_update_bits(cs35l43, CS35L43_ASP_DATA_CONTROL1, + CS35L43_ASP_TX_WL_MASK, asp_wl); + } + + return 0; +} + +static int cs35l43_convert_ramp_rate(struct cs35l43_private *cs35l43, + unsigned int ramp_rate) +{ + const unsigned int ramp_conv_table[] = {0, 1, 2, 4, 8, 16, 30, 60}; + + /* Convert the ramp_rate register setting into a time delay in ms. + * This assumes the starting volume is 0 dB + */ + if (ramp_rate >= ARRAY_SIZE(ramp_conv_table)) { + dev_err(cs35l43->dev, + "Invalid rate (%d) to convert\n", ramp_rate); + return -EINVAL; + } + + return ramp_conv_table[ramp_rate] * CS35L43_AMP_VOL_MIN / 12; +} + +static int cs35l43_pcm_mute(struct snd_soc_dai *dai, int mute, int direction) +{ + struct cs35l43_private *cs35l43 = + snd_soc_component_get_drvdata(dai->component); + unsigned int vol, vol_ramp; + int vol_ramp_ms; + + dev_info(cs35l43->dev, "%s mute=%d\n", __func__, mute); + + if (mute) { + regmap_update_bits(cs35l43->regmap, + CS35L43_AMP_CTRL, + CS35L43_AMP_VOL_PCM_MASK << + CS35L43_AMP_VOL_PCM_SHIFT, + CS35L43_AMP_VOL_PCM_MUTE << + CS35L43_AMP_VOL_PCM_SHIFT); + + regmap_read(cs35l43->regmap, + CS35L43_AMP_CTRL, &vol_ramp); + vol_ramp &= CS35L43_AMP_RAMP_PCM_MASK; + + vol_ramp_ms = cs35l43_convert_ramp_rate(cs35l43, + vol_ramp); + if (vol_ramp_ms < 0) + dev_err(cs35l43->dev, + "%s: Could not convert ramp rate\n", + __func__); + else if (vol_ramp_ms < 20) + usleep_range(vol_ramp_ms * 1000, + vol_ramp_ms * 1000 + 100); + else + msleep(vol_ramp_ms); + + cs35l43->pcm_muted = true; + } else { + if (cs35l43->amp_mute) { + vol = cs35l43->pcm_vol; + /* convert control val to register val */ + if (vol < CS35L43_AMP_VOL_CTRL_DEFAULT) + vol += CS35L43_AMP_VOL_PCM_MUTE; + else + vol -= CS35L43_AMP_VOL_CTRL_DEFAULT; + /* unmute */ + regmap_update_bits(cs35l43->regmap, + CS35L43_AMP_CTRL, + CS35L43_AMP_VOL_PCM_MASK << + CS35L43_AMP_VOL_PCM_SHIFT, + vol << CS35L43_AMP_VOL_PCM_SHIFT); + } + cs35l43->pcm_muted = false; + } + + return 0; +} + +static int cs35l43_get_clk_config(int freq) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs35l43_pll_sysclk); i++) { + if (cs35l43_pll_sysclk[i].freq == freq) + return cs35l43_pll_sysclk[i].clk_cfg; + } + + return -EINVAL; +} + +static const unsigned int cs35l43_src_rates[] = { + 8000, 12000, 11025, 16000, 22050, 24000, 32000, + 44100, 48000, 88200, 96000, 176400, 192000 +}; + +static const struct snd_pcm_hw_constraint_list cs35l43_constraints = { + .count = ARRAY_SIZE(cs35l43_src_rates), + .list = cs35l43_src_rates, +}; + +static int cs35l43_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct cs35l43_private *cs35l43 = + snd_soc_component_get_drvdata(dai->component); + int ret = 0; + + dev_dbg(cs35l43->dev, "%s\n", __func__); + + if (substream->runtime) + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &cs35l43_constraints); + return ret; +} + +static int cs35l43_get_fs_mon_config_index(int freq) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs35l43_fs_mon); i++) { + if (cs35l43_fs_mon[i].freq == freq) + return i; + } + + return -EINVAL; +} + +static int cs35l43_component_set_sysclk(struct snd_soc_component *component, + int clk_id, int source, unsigned int freq, + int dir) +{ + struct cs35l43_private *cs35l43 = + snd_soc_component_get_drvdata(component); + unsigned int fs1_val; + unsigned int fs2_val; + unsigned int val; + int fsIndex; + + dev_dbg(cs35l43->dev, "%s\n", __func__); + dev_dbg(cs35l43->dev, "%s id = %d, freq=%d\n", __func__, clk_id, freq); + + cs35l43->extclk_cfg = cs35l43_get_clk_config(freq); + cs35l43->clk_id = clk_id; + + if (freq <= 6000000) { + /* Use the lookup table */ + fsIndex = cs35l43_get_fs_mon_config_index(freq); + if (fsIndex < 0) { + dev_err(cs35l43->dev, "Invalid CLK Config freq: %u\n", freq); + return -EINVAL; + } + + fs1_val = cs35l43_fs_mon[fsIndex].fs1; + fs2_val = cs35l43_fs_mon[fsIndex].fs2; + } else { + /* Use hard-coded values */ + fs1_val = 18; + fs2_val = 33; + } + + val = fs1_val; + val |= (fs2_val << CS35L43_FS2_START_WINDOW_SHIFT) & CS35L43_FS2_START_WINDOW_MASK; + + if (cs35l43->extclk_cfg < 0) { + dev_err(cs35l43->dev, "Invalid CLK Config: %d, freq: %u\n", + cs35l43->extclk_cfg, freq); + return -EINVAL; + } + + if (cs35l43->hibernate_state != CS35L43_HIBERNATE_STANDBY) { + cs35l43_pll_config(cs35l43); + cs35l43_regmap_write(cs35l43, CS35L43_FS_MON_0, val); + if (cs35l43->limit_spi_clock) + cs35l43->limit_spi_clock(cs35l43, false); + } + + return 0; +} + +static int cs35l43_dai_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct cs35l43_private *cs35l43 = + snd_soc_component_get_drvdata(dai->component); + + dev_dbg(cs35l43->dev, "%s\n", __func__); + + return 0; +} + +int cs35l43_component_write(struct snd_soc_component *component, + unsigned int reg, unsigned int val) +{ + struct cs35l43_private *cs35l43 = + snd_soc_component_get_drvdata(component); + int ret = 0; + + mutex_lock(&cs35l43->hb_lock); + ret = cs35l43_regmap_write(cs35l43, reg, val); + mutex_unlock(&cs35l43->hb_lock); + + return ret; +} + +unsigned int cs35l43_component_read(struct snd_soc_component *component, + unsigned int reg) +{ + struct cs35l43_private *cs35l43 = + snd_soc_component_get_drvdata(component); + unsigned int val; + + cs35l43_regmap_read(cs35l43, reg, &val); + + return val; +} + +static int cs35l43_irq_gpio_config(struct cs35l43_private *cs35l43) +{ + int irq_pol = IRQF_TRIGGER_NONE; + + if (cs35l43->pdata.gpio1_out_enable) + cs35l43_regmap_update_bits(cs35l43, + CS35L43_GPIO1_CTRL1, + CS35L43_GP1_DIR_MASK, + 0); + if (cs35l43->pdata.gpio1_src_sel) + cs35l43_regmap_update_bits(cs35l43, + CS35L43_GPIO_PAD_CONTROL, + CS35L43_GP1_CTRL_MASK, + cs35l43->pdata.gpio1_src_sel << + CS35L43_GP1_CTRL_SHIFT); + + if (cs35l43->pdata.gpio2_out_enable) + cs35l43_regmap_update_bits(cs35l43, + CS35L43_GPIO2_CTRL1, + CS35L43_GP2_DIR_MASK, + 0); + + if (cs35l43->pdata.gpio2_src_sel) + cs35l43_regmap_update_bits(cs35l43, + CS35L43_GPIO_PAD_CONTROL, + CS35L43_GP2_CTRL_MASK, + cs35l43->pdata.gpio2_src_sel << + CS35L43_GP2_CTRL_SHIFT); + + if (cs35l43->pdata.gpio2_src_sel == + (CS35L43_GP2_CTRL_OPEN_DRAIN_ACTV_LO | CS35L43_VALID_PDATA) || + cs35l43->pdata.gpio2_src_sel == + (CS35L43_GP2_CTRL_PUSH_PULL_ACTV_LO | CS35L43_VALID_PDATA)) + irq_pol = IRQF_TRIGGER_LOW; + else if (cs35l43->pdata.gpio2_src_sel == + (CS35L43_GP2_CTRL_PUSH_PULL_ACTV_HI | CS35L43_VALID_PDATA)) + irq_pol = IRQF_TRIGGER_HIGH; + + return irq_pol; +} + +static int cs35l43_set_pdata(struct cs35l43_private *cs35l43) +{ + if (cs35l43->pdata.bst_vctrl) + cs35l43_regmap_update_bits(cs35l43, CS35L43_VBST_CTL_1, + CS35L43_BST_CTL_MASK, cs35l43->pdata.bst_vctrl); + + if (cs35l43->pdata.classh_disable) + cs35l43_regmap_update_bits(cs35l43, CS35L43_VBST_CTL_2, + CS35L43_BST_CTL_SEL_MASK, 0); + else { + if (cs35l43->pdata.bst_vctrl) + cs35l43_regmap_update_bits(cs35l43, CS35L43_VBST_CTL_2, + CS35L43_BST_CTL_LIM_EN_MASK, + CS35L43_BST_CTL_LIM_EN_MASK); + } + + if (cs35l43->pdata.bst_ipk) { + cs35l43_regmap_update_bits(cs35l43, + CS35L43_BST_IPK_CTL, + CS35L43_BST_IPK_MASK, + cs35l43->pdata.bst_ipk); + } + + if (cs35l43->pdata.dsp_ng_enable) { + cs35l43_regmap_update_bits(cs35l43, + CS35L43_MIXER_NGATE_CH1_CFG, + CS35L43_AUX_NGATE_CH1_EN_MASK, + CS35L43_AUX_NGATE_CH1_EN_MASK); + cs35l43_regmap_update_bits(cs35l43, + CS35L43_MIXER_NGATE_CH2_CFG, + CS35L43_AUX_NGATE_CH2_EN_MASK, + CS35L43_AUX_NGATE_CH2_EN_MASK); + + if (cs35l43->pdata.dsp_ng_pcm_thld) { + cs35l43_regmap_update_bits(cs35l43, + CS35L43_MIXER_NGATE_CH1_CFG, + CS35L43_AUX_NGATE_CH1_THR_MASK, + cs35l43->pdata.dsp_ng_pcm_thld); + cs35l43_regmap_update_bits(cs35l43, + CS35L43_MIXER_NGATE_CH2_CFG, + CS35L43_AUX_NGATE_CH2_THR_MASK, + cs35l43->pdata.dsp_ng_pcm_thld); + } + + if (cs35l43->pdata.dsp_ng_delay) { + cs35l43_regmap_update_bits(cs35l43, + CS35L43_MIXER_NGATE_CH1_CFG, + CS35L43_AUX_NGATE_CH1_HOLD_MASK, + cs35l43->pdata.dsp_ng_delay << + CS35L43_AUX_NGATE_CH1_HOLD_SHIFT); + cs35l43_regmap_update_bits(cs35l43, + CS35L43_MIXER_NGATE_CH2_CFG, + CS35L43_AUX_NGATE_CH2_HOLD_MASK, + cs35l43->pdata.dsp_ng_delay << + CS35L43_AUX_NGATE_CH2_HOLD_SHIFT); + } + } + + if (cs35l43->pdata.asp_sdout_hiz) + cs35l43_regmap_update_bits(cs35l43, + CS35L43_ASP_CONTROL3, + CS35L41_ASP_DOUT_HIZ_CTRL_MASK, + cs35l43->pdata.asp_sdout_hiz); + + if (cs35l43->pdata.hw_ng_sel) + cs35l43_regmap_update_bits(cs35l43, + CS35L43_NG_CONFIG, + CS35L43_NG_EN_SEL_MASK, + cs35l43->pdata.hw_ng_sel << + CS35L43_NG_EN_SEL_SHIFT); + + if (cs35l43->pdata.hw_ng_thld) + cs35l43_regmap_update_bits(cs35l43, + CS35L43_NG_CONFIG, + CS35L43_NG_PCM_THLD_MASK, + cs35l43->pdata.hw_ng_thld << + CS35L43_NG_PCM_THLD_SHIFT); + + if (cs35l43->pdata.hw_ng_delay) + cs35l43_regmap_update_bits(cs35l43, + CS35L43_NG_CONFIG, + CS35L43_NG_DELAY_MASK, + cs35l43->pdata.hw_ng_delay << + CS35L43_NG_DELAY_SHIFT); + + if (cs35l43->pdata.vpbr_rel_rate) + cs35l43_regmap_update_bits(cs35l43, + CS35L43_VPBR_CONFIG, + CS35L43_VPBR_REL_RATE_MASK, + cs35l43->pdata.vpbr_rel_rate << + CS35L43_VPBR_REL_RATE_SHIFT); + if (cs35l43->pdata.vpbr_wait) + cs35l43_regmap_update_bits(cs35l43, + CS35L43_VPBR_CONFIG, + CS35L43_VPBR_WAIT_MASK, + cs35l43->pdata.vpbr_wait << + CS35L43_VPBR_WAIT_SHIFT); + if (cs35l43->pdata.vpbr_atk_rate) + cs35l43_regmap_update_bits(cs35l43, + CS35L43_VPBR_CONFIG, + CS35L43_VPBR_ATK_RATE_MASK, + cs35l43->pdata.vpbr_atk_rate << + CS35L43_VPBR_ATK_RATE_SHIFT); + if (cs35l43->pdata.vpbr_atk_vol) + cs35l43_regmap_update_bits(cs35l43, + CS35L43_VPBR_CONFIG, + CS35L43_VPBR_ATK_VOL_MASK, + cs35l43->pdata.vpbr_atk_vol << + CS35L43_VPBR_ATK_VOL_SHIFT); + if (cs35l43->pdata.vpbr_max_att) + cs35l43_regmap_update_bits(cs35l43, + CS35L43_VPBR_CONFIG, + CS35L43_VPBR_MAX_ATT_MASK, + cs35l43->pdata.vpbr_max_att << + CS35L43_VPBR_MAX_ATT_SHIFT); + if (cs35l43->pdata.vpbr_thld) + cs35l43_regmap_update_bits(cs35l43, + CS35L43_VPBR_CONFIG, + CS35L43_VPBR_THLD1_MASK, + cs35l43->pdata.vpbr_thld << + CS35L43_VPBR_THLD1_SHIFT); + if (cs35l43->pdata.vpbr_enable) + cs35l43_regmap_update_bits(cs35l43, + CS35L43_BLOCK_ENABLES2, + CS35L43_VPBR_EN_MASK, + CS35L43_VPBR_EN_MASK); + + return 0; +} + +static int cs35l43_handle_of_data(struct device *dev, + struct cs35l43_platform_data *pdata, + struct cs35l43_private *cs35l43) +{ + struct device_node *np = dev->of_node; + int ret, val; + + if (!np) + return 0; + + pdata->dsp_ng_enable = of_property_read_bool(np, + "cirrus,dsp-noise-gate-enable"); + if (of_property_read_u32(np, + "cirrus,dsp-noise-gate-threshold", &val) >= 0) + pdata->dsp_ng_pcm_thld = val | CS35L43_VALID_PDATA; + if (of_property_read_u32(np, "cirrus,dsp-noise-gate-delay", &val) >= 0) + pdata->dsp_ng_delay = val | CS35L43_VALID_PDATA; + + if (of_property_read_u32(np, "cirrus,hw-noise-gate-select", &val) >= 0) + pdata->hw_ng_sel = val | CS35L43_VALID_PDATA; + if (of_property_read_u32(np, + "cirrus,hw-noise-gate-threshold", &val) >= 0) + pdata->hw_ng_thld = val | CS35L43_VALID_PDATA; + if (of_property_read_u32(np, "cirrus,hw-noise-gate-delay", &val) >= 0) + pdata->hw_ng_delay = val | CS35L43_VALID_PDATA; + + if (of_property_read_u32(np, "cirrus,gpio1-src-sel", &val) >= 0) + pdata->gpio1_src_sel = val | CS35L43_VALID_PDATA; + if (of_property_read_u32(np, "cirrus,gpio2-src-sel", &val) >= 0) + pdata->gpio2_src_sel = val | CS35L43_VALID_PDATA; + pdata->gpio1_out_enable = of_property_read_bool(np, + "cirrus,gpio1-output-enable"); + pdata->gpio2_out_enable = of_property_read_bool(np, + "cirrus,gpio2-output-enable"); + + pdata->vpbr_enable = of_property_read_bool(np, + "cirrus,vpbr-enable"); + if (of_property_read_u32(np, "cirrus,vpbr-rel-rate", &val) >= 0) + pdata->vpbr_rel_rate = val | CS35L43_VALID_PDATA; + if (of_property_read_u32(np, "cirrus,vpbr-wait", &val) >= 0) + pdata->vpbr_wait = val | CS35L43_VALID_PDATA; + if (of_property_read_u32(np, "cirrus,vpbr-atk-rate", &val) >= 0) + pdata->vpbr_atk_rate = val | CS35L43_VALID_PDATA; + if (of_property_read_u32(np, "cirrus,vpbr-atk-vol", &val) >= 0) + pdata->vpbr_atk_vol = val | CS35L43_VALID_PDATA; + if (of_property_read_u32(np, "cirrus,vpbr-max-att", &val) >= 0) + pdata->vpbr_max_att = val | CS35L43_VALID_PDATA; + if (of_property_read_u32(np, "cirrus,vpbr-thld", &val) >= 0) + pdata->vpbr_thld = val | CS35L43_VALID_PDATA; + + if (of_property_read_u32(np, "cirrus,asp-sdout-hiz", &val) >= 0) + pdata->asp_sdout_hiz = val | CS35L43_VALID_PDATA; + + if (of_property_read_u32(np, "cirrus,bst-ipk-ma", &val) >= 0) { + if ((val < 1600) || (val > 4500)) { + dev_err(dev, "Invalid boost inductor peak current: %d mA\n", + val); + return -EINVAL; + } + pdata->bst_ipk = ((val - 1600) / 50) + 0x10; + } + + pdata->classh_disable = of_property_read_bool(np, + "cirrus,classh-disable"); + ret = of_property_read_u32(np, "cirrus,boost-ctl-millivolt", &val); + if (ret >= 0) { + if (val < 2550 || val > 11000) { + dev_err(dev, + "Invalid Boost Voltage %u mV\n", val); + return -EINVAL; + } + pdata->bst_vctrl = ((val - 2550) / 100) + 1; + } + + ret = of_property_read_string(np, "cirrus,dsp-part-name", + &pdata->dsp_part_name); + if (ret < 0) + pdata->dsp_part_name = "cs35l43"; + + cs35l43->low_pwr_mode = of_property_read_bool(np, "cirrus,low-pwr-mode-standby"); + + return 0; +} + +static struct reg_sequence cs35l43_cal_pre_config[] = { + { CS35L43_NG_CONFIG, 0 }, + { CS35L43_MIXER_NGATE_CH1_CFG, 0 }, + { CS35L43_MIXER_NGATE_CH2_CFG, 0 }, +}; + +static struct reg_sequence cs35l43_cal_post_config[] = { + { CS35L43_NG_CONFIG, 0 }, + { CS35L43_MIXER_NGATE_CH1_CFG, 0 }, + { CS35L43_MIXER_NGATE_CH2_CFG, 0 }, +}; + +#define CS35L43_CAL_N_CONFIGS 1 + +static int cs35l43_cirrus_amp_probe(struct cs35l43_private *cs35l43, + struct snd_soc_component *component) +{ + unsigned int property, target_temp = 0, exit_temp = 0; + struct device_node *pwr_params; + bool pwr_enable = false; + const char *dsp_part_name; + const char *mfd_suffix; + int ret, bd_max_temp; + struct cirrus_amp_config amp_cfg = {0}; + bool calibration_disable; + unsigned int default_redc; + + ret = of_property_read_string(cs35l43->dev->of_node, + "cirrus,dsp-part-name", + &dsp_part_name); + if (ret < 0) + dsp_part_name = "cs35l43"; + + ret = of_property_read_string(cs35l43->dev->of_node, + "cirrus,mfd-suffix", + &mfd_suffix); + if (ret >= 0) + cs35l43->pdata.mfd_suffix = mfd_suffix; + + ret = of_property_read_u32(cs35l43->dev->of_node, + "cirrus,bd-max-temp", &bd_max_temp); + if (ret < 0) + bd_max_temp = -1; + + ret = of_property_read_u32(cs35l43->dev->of_node, + "cirrus,default-redc", &default_redc); + if (ret < 0) + default_redc = 0; + + calibration_disable = of_property_read_bool(cs35l43->dev->of_node, + "cirrus,calibration-disable"); + + pwr_params = of_get_child_by_name(cs35l43->dev->of_node, + "cirrus,pwr-params"); + if (pwr_params) { + pwr_enable = of_property_read_bool(pwr_params, + "cirrus,pwr-global-enable"); + ret = of_property_read_u32(pwr_params, "cirrus,pwr-target-temp", + &property); + if (ret >= 0) + target_temp = property; + + ret = of_property_read_u32(pwr_params, "cirrus,pwr-exit-temp", + &property); + if (ret >= 0) + exit_temp = property; + } + of_node_put(pwr_params); + + + cs35l43_regmap_read(cs35l43, CS35L43_NG_CONFIG, + &cs35l43_cal_post_config[0].def); + cs35l43_regmap_read(cs35l43, CS35L43_MIXER_NGATE_CH1_CFG, + &cs35l43_cal_post_config[1].def); + cs35l43_regmap_read(cs35l43, CS35L43_MIXER_NGATE_CH2_CFG, + &cs35l43_cal_post_config[2].def); + + amp_cfg.component = component; + amp_cfg.regmap = cs35l43->regmap; + amp_cfg.pre_config = cs35l43_cal_pre_config; + amp_cfg.post_config = cs35l43_cal_post_config; + amp_cfg.dsp_part_name = dsp_part_name; + amp_cfg.num_pre_configs = ARRAY_SIZE(cs35l43_cal_pre_config); + amp_cfg.num_post_configs = ARRAY_SIZE(cs35l43_cal_post_config); + amp_cfg.mbox_cmd = CS35L43_DSP_VIRTUAL1_MBOX_1; + amp_cfg.mbox_sts = 0; + amp_cfg.global_en = CS35L43_GLOBAL_ENABLES; + amp_cfg.global_en_mask = 1; + amp_cfg.bd_max_temp = bd_max_temp; + amp_cfg.target_temp = target_temp; + amp_cfg.exit_temp = exit_temp; + amp_cfg.pwr_enable = pwr_enable; + amp_cfg.default_redc = default_redc; + amp_cfg.perform_vimon_cal = true; + amp_cfg.calibration_disable = calibration_disable; + amp_cfg.vimon_alg_id = 0x5f224; + amp_cfg.halo_alg_id = 0x1810d6; + amp_cfg.bd_alg_id = 0x5f21f; + amp_cfg.bd_prefix = ""; + amp_cfg.cal_vpk_id = 0; + amp_cfg.cal_ipk_id = 0; + amp_cfg.amp_reinit = cs35l43_reinit; + amp_cfg.cal_ops_idx = CIRRUS_CAL_CS35L43_CAL_OPS_IDX; + amp_cfg.runtime_pm = true; + + ret = cirrus_amp_add(mfd_suffix, amp_cfg); + if (ret < 0) { + dev_err(cs35l43->dev, "Failed to register cirrus amp (%d)\n", + ret); + return -EPROBE_DEFER; + } + + return 0; +} + + +static int cs35l43_component_probe(struct snd_soc_component *component) +{ + int ret = 0; + struct cs35l43_private *cs35l43 = + snd_soc_component_get_drvdata(component); + + cs35l43_set_pdata(cs35l43); + cs35l43->component = component; + wm_adsp2_component_probe(&cs35l43->dsp, component); + cs35l43_cirrus_amp_probe(cs35l43, component); + + return ret; +} + +static void cs35l43_component_remove(struct snd_soc_component *component) +{ + +} + +static const struct cs_dsp_region cs35l43_dsp1_regions[] = { + { .type = WMFW_HALO_PM_PACKED, .base = CS35L43_DSP1_PMEM_0 }, + { .type = WMFW_HALO_XM_PACKED, .base = CS35L43_DSP1_XMEM_PACKED_0 }, + { .type = WMFW_HALO_YM_PACKED, .base = CS35L43_DSP1_YMEM_PACKED_0 }, + {. type = WMFW_ADSP2_XM, .base = CS35L43_DSP1_XMEM_UNPACKED24_0}, + {. type = WMFW_ADSP2_YM, .base = CS35L43_DSP1_YMEM_UNPACKED24_0}, +}; + +static int cs35l43_dsp_init(struct cs35l43_private *cs35l43) +{ + struct wm_adsp *dsp; + int ret; + + dsp = &cs35l43->dsp; + dsp->part = cs35l43->pdata.dsp_part_name; + dsp->cs_dsp.num = 1; + dsp->cs_dsp.type = WMFW_HALO; + dsp->cs_dsp.rev = 0; + dsp->fw = 9; /* 9 is WM_ADSP_FW_SPK_PROT in wm_adsp.c */ + dsp->cs_dsp.dev = cs35l43->dev; + dsp->cs_dsp.regmap = cs35l43->regmap; + dsp->cs_dsp.base = CS35L43_DSP1_CLOCK_FREQ; + dsp->cs_dsp.base_sysinfo = CS35L43_DSP1_SYS_INFO_ID; + dsp->cs_dsp.mem = cs35l43_dsp1_regions; + dsp->cs_dsp.num_mems = ARRAY_SIZE(cs35l43_dsp1_regions); + dsp->cs_dsp.lock_regions = 0xFFFFFFFF; + dsp->toggle_preload = true; + + ret = wm_halo_init(dsp); + if (ret != 0) { + dev_err(cs35l43->dev, "wm_halo_init failed\n"); + goto err; + } + + cs_dsp_stop(&dsp->cs_dsp); + + cs35l43_regmap_write(cs35l43, CS35L43_DSP1RX3_INPUT, CS35L43_INPUT_SRC_VBSTMON); + cs35l43_regmap_write(cs35l43, CS35L43_DSP1RX4_INPUT, CS35L43_INPUT_SRC_IMON); + cs35l43_regmap_write(cs35l43, CS35L43_DSP1RX5_INPUT, CS35L43_INPUT_SRC_VMON); + cs35l43_regmap_write(cs35l43, CS35L43_DSP1RX6_INPUT, CS35L43_INPUT_SRC_VPMON); + + return 0; + +err: + return ret; +} + +static int cs35l43_dai_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct cs35l43_private *cs35l43 = + snd_soc_component_get_drvdata(dai->component); + + cs35l43->slot_width = slot_width; + + return 0; +} + +static int cs35l43_compr_open(struct snd_soc_component *component, + struct snd_compr_stream *stream) +{ + struct snd_soc_pcm_runtime *rtd = stream->private_data; + struct cs35l43_private *cs35l43 = + snd_soc_component_get_drvdata(component); + struct snd_soc_dai *codec_dai = NULL; + int i; + + for_each_rtd_dais(rtd, i, codec_dai) { + if (!strcmp(codec_dai->name, "cs35l43-dsp-textlog")) + return wm_adsp_compr_open(&cs35l43->dsp, stream); + } + + dev_err(cs35l43->dev, "No DSP log DAI found\n"); + + return -EINVAL; +} + +static const struct snd_soc_dai_ops cs35l43_ops = { + .startup = cs35l43_pcm_startup, + .set_fmt = cs35l43_set_dai_fmt, + .hw_params = cs35l43_pcm_hw_params, + .set_sysclk = cs35l43_dai_set_sysclk, + .set_tdm_slot = cs35l43_dai_set_tdm_slot, + .mute_stream = cs35l43_pcm_mute, + .no_capture_mute = 1, +}; + +static struct snd_soc_dai_driver cs35l43_dai[] = { + { + .name = "cs35l43-pcm", + .id = 0, + .playback = { + .stream_name = "AMP Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS35L43_RX_FORMATS, + }, + .capture = { + .stream_name = "AMP Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS35L43_TX_FORMATS, + }, + .ops = &cs35l43_ops, + .symmetric_rate = 1, + }, + { + .name = "cs35l43-cpu-textlog", + .capture = { + .stream_name = "Audio Log CPU", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS35L43_RX_FORMATS, + }, + .compress_new = &snd_soc_new_compress, + }, + { + .name = "cs35l43-dsp-textlog", + .capture = { + .stream_name = "Audio Log DSP", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS35L43_RX_FORMATS, + }, + } +}; + +static const struct snd_compress_ops cs35l43_compr_ops = { + .open = &cs35l43_compr_open, + .free = &wm_adsp_compr_free, + .set_params = &wm_adsp_compr_set_params, + .get_caps = &wm_adsp_compr_get_caps, + .trigger = &wm_adsp_compr_trigger, + .pointer = &wm_adsp_compr_pointer, + .copy = &wm_adsp_compr_copy, +}; + +static const struct snd_soc_component_driver soc_component_dev_cs35l43 = { + .name = DRV_NAME, + .probe = cs35l43_component_probe, + .remove = cs35l43_component_remove, + + .dapm_widgets = cs35l43_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs35l43_dapm_widgets), + .dapm_routes = cs35l43_audio_map, + .num_dapm_routes = ARRAY_SIZE(cs35l43_audio_map), + + .controls = cs35l43_aud_controls, + .num_controls = ARRAY_SIZE(cs35l43_aud_controls), + + .set_sysclk = cs35l43_component_set_sysclk, + + .write = cs35l43_component_write, + .read = cs35l43_component_read, + + .compress_ops = &cs35l43_compr_ops, +}; + +static struct reg_sequence cs35l43_errata_patch[] = { + { CS35L43_TST_DAC_MSM_CONFIG, 0x11330000 }, + { CS35L43_BST_RSVD_1, 0x50000802 }, +}; + +int cs35l43_probe(struct cs35l43_private *cs35l43, + struct cs35l43_platform_data *pdata) +{ + int ret, i; + unsigned int regid, revid; + int irq_pol = IRQF_TRIGGER_HIGH; + + for (i = 0; i < ARRAY_SIZE(cs35l43_supplies); i++) + cs35l43->supplies[i].supply = cs35l43_supplies[i]; + + cs35l43->num_supplies = ARRAY_SIZE(cs35l43_supplies); + + ret = devm_regulator_bulk_get(cs35l43->dev, cs35l43->num_supplies, + cs35l43->supplies); + if (ret != 0) { + dev_err(cs35l43->dev, + "Failed to request core supplies: %d\n", + ret); + return ret; + } + + if (pdata) { + cs35l43->pdata = *pdata; + } else if (cs35l43->dev->of_node) { + ret = cs35l43_handle_of_data(cs35l43->dev, &cs35l43->pdata, + cs35l43); + if (ret != 0) + return ret; + } else { + ret = -EINVAL; + goto err; + } + + ret = regulator_bulk_enable(cs35l43->num_supplies, cs35l43->supplies); + if (ret != 0) { + dev_err(cs35l43->dev, + "Failed to enable core supplies: %d\n", ret); + return ret; + } + + /* returning NULL can be an option if in stereo mode */ + cs35l43->reset_gpio = devm_gpiod_get_optional(cs35l43->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(cs35l43->reset_gpio)) { + ret = PTR_ERR(cs35l43->reset_gpio); + cs35l43->reset_gpio = NULL; + if (ret == -EBUSY) { + dev_info(cs35l43->dev, + "Reset line busy, assuming shared reset\n"); + } else { + dev_err(cs35l43->dev, + "Failed to get reset GPIO: %d\n", ret); + goto err; + } + } + if (cs35l43->reset_gpio) { + /* satisfy minimum reset pulse width spec */ + usleep_range(2000, 2100); + gpiod_set_value_cansleep(cs35l43->reset_gpio, 1); + } + + usleep_range(2000, 2100); + + ret = cs35l43_regmap_read(cs35l43, CS35L43_DEVID, ®id); + if (ret < 0) { + dev_err(cs35l43->dev, "Get Device ID failed\n"); + goto err; + } + + ret = cs35l43_regmap_read(cs35l43, CS35L43_REVID, &revid); + if (ret < 0) { + dev_err(cs35l43->dev, "Get Revision ID failed\n"); + goto err; + } + + ret = regmap_register_patch(cs35l43->regmap, + cs35l43_errata_patch, + ARRAY_SIZE(cs35l43_errata_patch)); + if (ret < 0) { + dev_err(cs35l43->dev, "Failed to apply errata patch %d\n", ret); + goto err; + } + + cs35l43->hibernate_state = CS35L43_HIBERNATE_NOT_LOADED; + cs35l43->amp_switch = -1; + cs35l43->amp_mute = 1; /* Unmuted */ + cs35l43->pcm_vol = CS35L43_AMP_VOL_CTRL_DEFAULT; + mutex_init(&cs35l43->hb_lock); + mutex_init(&cs35l43->err_lock); + + cs35l43->err_wq = create_singlethread_workqueue("cs35l43_err"); + INIT_WORK(&cs35l43->err_work, cs35l43_error_work); + + cs35l43->mbox_wq = create_singlethread_workqueue("cs35l43_mbox"); + INIT_WORK(&cs35l43->mbox_work, cs35l43_mbox_work); + + cs35l43->irq_wq = create_singlethread_workqueue("cs35l43_irq"); + INIT_DELAYED_WORK(&cs35l43->irq_work, cs35l43_irq_work); + + cs35l43_dsp_init(cs35l43); + + irq_pol = cs35l43_irq_gpio_config(cs35l43); + cs35l43_regmap_write(cs35l43, CS35L43_IRQ1_MASK_1, 0xFFFFFFFF); + cs35l43_regmap_update_bits(cs35l43, CS35L43_IRQ1_MASK_1, + CS35L43_AMP_ERR_EINT1_MASK | + CS35L43_BST_SHORT_ERR_EINT1_MASK | + CS35L43_BST_DCM_UVP_ERR_EINT1_MASK | + CS35L43_BST_OVP_ERR_EINT1_MASK | + CS35L43_DC_WATCHDOG_IRQ_RISE_EINT1_MASK | + CS35L43_WKSRC_STATUS6_EINT1_MASK | + CS35L43_WKSRC_STATUS_ANY_EINT1_MASK, 0); + cs35l43_regmap_write(cs35l43, CS35L43_IRQ1_MASK_2, 0xFFFFFFFF); + cs35l43_regmap_update_bits(cs35l43, CS35L43_IRQ1_MASK_2, + CS35L43_PLL_UNLOCK_FLAG_RISE_EINT1_MASK | + CS35L43_PLL_LOCK_EINT1_MASK, 0); + regmap_write(cs35l43->regmap, CS35L43_IRQ1_MASK_3, 0xFFFFFFFF); + regmap_update_bits(cs35l43->regmap, CS35L43_IRQ1_MASK_3, + CS35L43_DSP1_NMI_ERR_EINT1_MASK | + CS35L43_DSP1_MPU_ERR_EINT1_MASK | + CS35L43_DSP1_STRM_ARB_ERR_EINT1_MASK, 0); + + cs35l43_regmap_update_bits(cs35l43, CS35L43_ALIVE_DCIN_WD, + CS35L43_DCIN_WD_EN_MASK, + CS35L43_DCIN_WD_EN_MASK); + cs35l43_regmap_update_bits(cs35l43, CS35L43_ALIVE_DCIN_WD, + CS35L43_DCIN_WD_THLD_MASK, + 1 << CS35L43_DCIN_WD_THLD_SHIFT); + + /* ACK core wakeup message before core disabled in dsp_init */ + regmap_write(cs35l43->regmap, CS35L43_IRQ1_EINT_1, + CS35L43_DSP_VIRTUAL2_MBOX_WR_EINT1_MASK); + + ret = devm_request_threaded_irq(cs35l43->dev, cs35l43->irq, NULL, + cs35l43_irq, IRQF_ONESHOT | IRQF_SHARED | + irq_pol, "cs35l43", cs35l43); + if (ret != 0) { + dev_err(cs35l43->dev, "Failed to request IRQ: %d\n", ret); + goto err; + } + + if (cs35l43->low_pwr_mode == CS35L43_LOW_PWR_MODE_HIBERNATE) + cs35l43_pm_runtime_setup(cs35l43); + + ret = snd_soc_register_component(cs35l43->dev, + &soc_component_dev_cs35l43, + cs35l43_dai, ARRAY_SIZE(cs35l43_dai)); + if (ret < 0) { + dev_err(cs35l43->dev, "%s: Register codec failed\n", __func__); + goto err_pm; + } + + if (cs35l43->low_pwr_mode == CS35L43_LOW_PWR_MODE_HIBERNATE) + pm_runtime_put_autosuspend(cs35l43->dev); + + dev_info(cs35l43->dev, "Cirrus Logic cs35l43 (%x), Revision: %02X\n", + regid, revid); + + return 0; + +err_pm: + pm_runtime_disable(cs35l43->dev); + pm_runtime_put_noidle(cs35l43->dev); + wm_adsp2_remove(&cs35l43->dsp); + mutex_destroy(&cs35l43->hb_lock); +err: + regulator_bulk_disable(cs35l43->num_supplies, cs35l43->supplies); + return ret; +} + +int cs35l43_remove(struct cs35l43_private *cs35l43) +{ + pm_runtime_get_sync(cs35l43->dev); + pm_runtime_disable(cs35l43->dev); + regulator_bulk_disable(cs35l43->num_supplies, cs35l43->supplies); + snd_soc_unregister_component(cs35l43->dev); + wm_adsp2_remove(&cs35l43->dsp); + pm_runtime_put_noidle(cs35l43->dev); + mutex_destroy(&cs35l43->hb_lock); + + return 0; +} + +int cs35l43_reinit(struct snd_soc_component *component) +{ + struct cs35l43_private *cs35l43 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct snd_soc_dapm_widget fake_dapm_widget = {.dapm = dapm}; + + if (!cs35l43) + return 0; + + if (cs35l43->reset_gpio) { + gpiod_direction_output(cs35l43->reset_gpio, 1); + + gpiod_set_value_cansleep(cs35l43->reset_gpio, 0); + usleep_range(1000, 1100); + gpiod_set_value_cansleep(cs35l43->reset_gpio, 1); + } + + usleep_range(2000, 2100); + + regcache_cache_only(cs35l43->regmap, false); + + cs35l43->hibernate_state = CS35L43_HIBERNATE_NOT_LOADED; + if (cs35l43->low_pwr_mode == CS35L43_LOW_PWR_MODE_HIBERNATE) + pm_runtime_get_sync(cs35l43->dev); + + cs_dsp_stop(&cs35l43->dsp.cs_dsp); + + if (cs35l43->dsp.preloaded) { + cs35l43->dsp.preloaded = 0; + cs35l43_dsp_preload_ev(&fake_dapm_widget, NULL, SND_SOC_DAPM_PRE_PMD); + usleep_range(5000, 5100); + cs35l43_dsp_preload_ev(&fake_dapm_widget, NULL, SND_SOC_DAPM_PRE_PMU); + cs35l43_dsp_preload_ev(&fake_dapm_widget, NULL, SND_SOC_DAPM_POST_PMU); + cs35l43->dsp.preloaded = 1; + } + + regcache_mark_dirty(cs35l43->regmap); + regcache_sync_region(cs35l43->regmap, CS35L43_DEVID, + CS35L43_MIXER_NGATE_CH2_CFG); + + if (cs35l43->low_pwr_mode == CS35L43_LOW_PWR_MODE_HIBERNATE) { + usleep_range(5000, 5100); + pm_runtime_put_autosuspend(cs35l43->dev); + } + + dev_info(cs35l43->dev, "%s complete\n", __func__); + return 0; +} +EXPORT_SYMBOL_GPL(cs35l43_reinit); + +static void cs35l43_pm_runtime_setup(struct cs35l43_private *cs35l43) +{ + struct device *dev = cs35l43->dev; + + pm_runtime_set_autosuspend_delay(dev, CS35L43_PM_AUTOSUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_set_active(dev); + pm_runtime_get_noresume(dev); + pm_runtime_enable(dev); +} + +int cs35l43_sys_suspend(struct device *dev) +{ + struct cs35l43_private *cs35l43 = dev_get_drvdata(dev); + struct i2c_client *i2c_client = to_i2c_client(dev); + + dev_dbg(cs35l43->dev, "System suspend, disabling IRQ\n"); + + disable_irq(i2c_client->irq); + + return 0; +} +EXPORT_SYMBOL(cs35l43_sys_suspend); + +int cs35l43_sys_suspend_noirq(struct device *dev) +{ + struct cs35l43_private *cs35l43 = dev_get_drvdata(dev); + struct i2c_client *i2c_client = to_i2c_client(dev); + + dev_dbg(cs35l43->dev, "Late system suspend, re-enabling IRQ\n"); + enable_irq(i2c_client->irq); + + return 0; +} +EXPORT_SYMBOL(cs35l43_sys_suspend_noirq); + +int cs35l43_sys_resume(struct device *dev) +{ + struct cs35l43_private *cs35l43 = dev_get_drvdata(dev); + struct i2c_client *i2c_client = to_i2c_client(dev); + + dev_dbg(cs35l43->dev, "System resume, re-enabling IRQ\n"); + + enable_irq(i2c_client->irq); + + return 0; +} +EXPORT_SYMBOL(cs35l43_sys_resume); + +int cs35l43_sys_resume_noirq(struct device *dev) +{ + struct cs35l43_private *cs35l43 = dev_get_drvdata(dev); + struct i2c_client *i2c_client = to_i2c_client(dev); + + dev_dbg(cs35l43->dev, "Early system resume, disabling IRQ\n"); + + disable_irq(i2c_client->irq); + + return 0; +} +EXPORT_SYMBOL(cs35l43_sys_resume_noirq); + +MODULE_DESCRIPTION("ASoC CS35L43 driver"); +MODULE_AUTHOR("David Rhodes, Cirrus Logic Inc, "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs35l43.h b/sound/soc/codecs/cs35l43.h new file mode 100644 index 000000000000..de796fea18a7 --- /dev/null +++ b/sound/soc/codecs/cs35l43.h @@ -0,0 +1,1010 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/************************************************/ +/* Software Reset and Hardware ID */ +/************************************************/ +#define CS35L43_DEVID 0x0000000 +#define CS35L43_REVID 0x0000004 +#define CS35L43_FABID 0x0000008 +#define CS35L43_RELID 0x000000C +#define CS35L43_OTPID 0x0000010 +#define CS35L43_SFT_RESET 0x0000020 + +/************************************************/ +/* Test Register Access */ +/************************************************/ +#define CS35L43_TEST_KEY_CTRL 0x0000040 +#define CS35L43_USER_KEY_CTRL 0x0000044 + +/************************************************/ +/* CTRL_ASYNC */ +/************************************************/ +#define CS35L43_CTRL_ASYNC0 0x0000050 +#define CS35L43_CTRL_ASYNC1 0x0000054 +#define CS35L43_CTRL_ASYNC2 0x0000058 +#define CS35L43_CTRL_ASYNC3 0x000005C + +/************************************************/ +/* Control Interface Configuration */ +/************************************************/ +#define CS35L43_CTRL_IF_CONFIG1 0x0000100 +#define CS35L43_CTRL_IF_STATUS1 0x0000104 +#define CS35L43_CTRL_IF_STATUS2 0x0000108 +#define CS35L43_CTRL_IF_CONFIG2 0x0000110 +#define CS35L43_CTRL_IF_DEBUG1 0x0000120 +#define CS35L43_CTRL_IF_DEBUG2 0x0000124 +#define CS35L43_CTRL_IF_DEBUG3 0x0000128 +#define CS35L43_CIF_MON1 0x0000140 +#define CS35L43_CIF_MON2 0x0000144 +#define CS35L43_CIF_MON_PADDR 0x0000148 +#define CS35L43_CTRL_IF_SPARE1 0x0000154 +#define CS35L43_CTRL_IF_I2C 0x0000158 +#define CS35L43_CTRL_IF_I2C_1_CONTROL 0x0000160 +#define CS35L43_CTRL_IF_I2C_1_BROADCAST 0x0000164 +#define CS35L43_APB_MSTR_DSP_BRIDGE_ERR 0x0000174 +#define CS35L43_CIF1_BRIDGE_ERR 0x0000178 +#define CS35L43_CIF2_BRIDGE_ERR 0x000017C + +/************************************************/ +/* OTP_IF_MEM */ +/************************************************/ +#define CS35L43_OTP_MEM0 0x0000400 +#define CS35L43_OTP_MEM31 0x000047C + +/************************************************/ +/* One-Time Programmable (OTP) Control */ +/************************************************/ +#define CS35L43_OTP_CTRL0 0x0000500 +#define CS35L43_OTP_CTRL1 0x0000504 +#define CS35L43_OTP_CTRL3 0x0000508 +#define CS35L43_OTP_CTRL4 0x000050C +#define CS35L43_OTP_CTRL5 0x0000510 +#define CS35L43_OTP_CTRL6 0x0000514 +#define CS35L43_OTP_CTRL7 0x0000518 +#define CS35L43_OTP_CTRL8 0x000051C + +/************************************************/ +/* Power, Global, and Release Control */ +/************************************************/ +#define CS35L43_DEVICE_ID 0x0002004 +#define CS35L43_FAB_ID 0x0002008 +#define CS35L43_REV_ID 0x000200C +#define CS35L43_GLOBAL_ENABLES 0x0002014 +#define CS35L43_BLOCK_ENABLES 0x0002018 +#define CS35L43_BLOCK_ENABLES2 0x000201C +#define CS35L43_GLOBAL_OVERRIDES 0x0002020 +#define CS35L43_GLOBAL_SYNC 0x0002024 +#define CS35L43_GLOBAL_STATUS 0x0002028 +#define CS35L43_DISCH_FILT 0x000202C +#define CS35L43_OSC_TRIM 0x0002030 +#define CS35L43_ERROR_RELEASE 0x0002034 +#define CS35L43_PLL_OVERRIDE 0x0002038 +#define CS35L43_CHIP_STATUS 0x0002040 +#define CS35L43_CHIP_STATUS2 0x0002044 +#define CS35L43_TST_OSC 0x0002084 + +/************************************************/ +/* Digital I/O Pad Control */ +/************************************************/ +#define CS35L43_LRCK_PAD_CONTROL 0x0002418 +#define CS35L43_SCLK_PAD_CONTROL 0x000241C +#define CS35L43_SDIN_PAD_CONTROL 0x0002420 +#define CS35L43_SDOUT_PAD_CONTROL 0x0002424 +#define CS35L43_GPIO_PAD_CONTROL 0x000242C +#define CS35L43_GPIO_GLOBAL_ENABLE_CONTROL 0x0002440 + +/************************************************/ +/* Hibernation Power Management */ +/************************************************/ +#define CS35L43_PWRMGT_CTL 0x0002900 +#define CS35L43_WAKESRC_CTL 0x0002904 +#define CS35L43_WAKEI2C_CTL 0x0002908 +#define CS35L43_PWRMGT_STS 0x000290C +#define CS35L43_PWRMGT_RST 0x0002910 +#define CS35L43_TEST_CTL 0x0002914 + +#define CS35L43_WKSRC_SPI 0x200 +#define CS35L43_WKSRC_I2C 0x400 + +/************************************************/ +/* Device Clocking and Sample Rate Control */ +/************************************************/ +#define CS35L43_REFCLK_INPUT 0x0002C04 +#define CS35L43_DSP_CLOCK_GEARING 0x0002C08 +#define CS35L43_GLOBAL_SAMPLE_RATE 0x0002C0C +#define CS35L43_FS_MON_0 0x0002D10 +#define CS35L43_DSP1_SAMPLE_RATE_RX1 0x0002D3C +#define CS35L43_DSP1_SAMPLE_RATE_RX2 0x0002D40 +#define CS35L43_DSP1_SAMPLE_RATE_TX1 0x0002D60 +#define CS35L43_DSP1_SAMPLE_RATE_TX2 0x0002D64 +#define CS35L43_LDOA_CTRL 0x000300C + +/************************************************/ +/* Multidevice Synchronization */ +/************************************************/ +#define CS35L43_SYNC_TX_RX_ENABLES 0x0003400 + +/************************************************/ +/* Digital Boost Converter */ +/************************************************/ +#define CS35L43_VBST_CTL_1 0x0003800 +#define CS35L43_VBST_CTL_2 0x0003804 +#define CS35L43_BST_IPK_CTL 0x0003808 +#define CS35L43_SOFT_RAMP 0x000380C +#define CS35L43_BST_LOOP_COEFF 0x0003810 +#define CS35L43_LBST_SLOPE 0x0003814 +#define CS35L43_BST_SW_FREQ 0x0003818 +#define CS35L43_BST_DCM_CTL 0x000381C +#define CS35L43_DCM_FORCE 0x0003820 +#define CS35L43_VBST_OVP 0x0003830 +#define CS35L43_BST_RSVD_1 0x0003850 + +/************************************************/ +/* VMON and IMON Signal Monitoring */ +/************************************************/ +#define CS35L43_MONITOR_FILT 0x0004008 + +/************************************************/ +/* Die Temperature Monitoring */ +/************************************************/ +#define CS35L43_WARN_LIMIT_THRESHOLD 0x0004220 +#define CS35L43_CONFIGURATION 0x0004224 +#define CS35L43_STATUS 0x0004300 +#define CS35L43_ENABLES_AND_CODES_ANA 0x0004304 +#define CS35L43_ENABLES_AND_CODES_DIG 0x0004308 + +/************************************************/ +/* ASP Data Interface */ +/************************************************/ +#define CS35L43_ASP_ENABLES1 0x0004800 +#define CS35L43_ASP_CONTROL1 0x0004804 +#define CS35L43_ASP_CONTROL2 0x0004808 +#define CS35L43_ASP_CONTROL3 0x000480C +#define CS35L43_ASP_FRAME_CONTROL1 0x0004810 +#define CS35L43_ASP_FRAME_CONTROL5 0x0004820 +#define CS35L43_ASP_DATA_CONTROL1 0x0004830 +#define CS35L43_ASP_DATA_CONTROL5 0x0004840 + +/************************************************/ +/* Data Routing */ +/************************************************/ +#define CS35L43_DACPCM1_INPUT 0x0004C00 +#define CS35L43_DACPCM2_INPUT 0x0004C08 +#define CS35L43_ASPTX1_INPUT 0x0004C20 +#define CS35L43_ASPTX2_INPUT 0x0004C24 +#define CS35L43_ASPTX3_INPUT 0x0004C28 +#define CS35L43_ASPTX4_INPUT 0x0004C2C +#define CS35L43_DSP1RX1_INPUT 0x0004C40 +#define CS35L43_DSP1RX2_INPUT 0x0004C44 +#define CS35L43_DSP1RX3_INPUT 0x0004C48 +#define CS35L43_DSP1RX4_INPUT 0x0004C4C +#define CS35L43_DSP1RX5_INPUT 0x0004C50 +#define CS35L43_DSP1RX6_INPUT 0x0004C54 +#define CS35L43_NGATE1_INPUT 0x0004C60 +#define CS35L43_NGATE2_INPUT 0x0004C64 + +/************************************************/ +/* Amplifier Volume Control */ +/************************************************/ +#define CS35L43_AMP_CTRL 0x0006000 +#define CS35L43_HPF_TST 0x0006004 +#define CS35L43_VC_TST1 0x0006008 +#define CS35L43_VC_TST2 0x000600C +#define CS35L43_INTP_TST 0x0006010 + +/************************************************/ +/* SRC_MAGCOMP */ +/************************************************/ +#define CS35L43_SRC_MAGCOMP_TST 0x0006200 +#define CS35L43_SRC_MAGCOMP_B0_OVERRIDE 0x0006204 +#define CS35L43_SRC_MAGCOMP_B1_OVERRIDE 0x0006208 +#define CS35L43_SRC_MAGCOMP_A1_N_OVERRIDE 0x000620C + +/************************************************/ +/* VP and VBST Brownout Prevention + Temp Warning */ +/************************************************/ +#define CS35L43_VPBR_CONFIG 0x0006404 +#define CS35L43_VBBR_CONFIG 0x0006408 +#define CS35L43_VPBR_STATUS 0x000640C +#define CS35L43_VBBR_STATUS 0x0006410 +#define CS35L43_OTW_CONFIG 0x0006414 +#define CS35L43_AMP_ERROR_VOL_SEL 0x0006418 +#define CS35L43_VOL_STATUS_TO_DSP 0x0006450 + +/************************************************/ +/* Power Management - Class H, Weak-FET, and Noise Gating */ +/************************************************/ +#define CS35L43_CLASSH_CONFIG 0x0006800 +#define CS35L43_WKFET_AMP_CONFIG 0x0006804 +#define CS35L43_NG_CONFIG 0x0006808 + +/************************************************/ +/* Dynamic Range Enhancement */ +/************************************************/ +#define CS35L43_AMP_GAIN 0x0006C04 + +/************************************************/ +/* Diagnostic Signal Generator */ +/************************************************/ +#define CS35L43_DAC_MSM_CONFIG 0x0007400 +#define CS35L43_TST_DAC_MSM_CONFIG 0x0007404 +#define CS35L43_ALIVE_DCIN_WD 0x0007424 + +/************************************************/ +/* Monitor Trim */ +/************************************************/ +#define CS35L43_SPKMON_OTP_3 0x000921C + +/************************************************/ +/* Interrupt Status and Mask Control */ +/************************************************/ +#define CS35L43_IRQ1_CFG 0x0010000 +#define CS35L43_IRQ1_STATUS 0x0010004 +#define CS35L43_IRQ1_EINT_1 0x0010010 +#define CS35L43_IRQ1_EINT_2 0x0010014 +#define CS35L43_IRQ1_EINT_3 0x0010018 +#define CS35L43_IRQ1_EINT_4 0x001001C +#define CS35L43_IRQ1_EINT_5 0x0010020 +#define CS35L43_IRQ1_STS_1 0x0010090 +#define CS35L43_IRQ1_STS_2 0x0010094 +#define CS35L43_IRQ1_STS_3 0x0010098 +#define CS35L43_IRQ1_STS_4 0x001009C +#define CS35L43_IRQ1_STS_5 0x00100A0 +#define CS35L43_IRQ1_MASK_1 0x0010110 +#define CS35L43_IRQ1_MASK_2 0x0010114 +#define CS35L43_IRQ1_MASK_3 0x0010118 +#define CS35L43_IRQ1_MASK_4 0x001011C +#define CS35L43_IRQ1_MASK_5 0x0010120 +#define CS35L43_IRQ1_FRC_1 0x0010190 +#define CS35L43_IRQ1_FRC_2 0x0010194 +#define CS35L43_IRQ1_FRC_3 0x0010198 +#define CS35L43_IRQ1_FRC_4 0x001019C +#define CS35L43_IRQ1_FRC_5 0x00101A0 +#define CS35L43_IRQ1_EDGE_1 0x0010210 +#define CS35L43_IRQ1_EDGE_4 0x001021C +#define CS35L43_IRQ1_POL_1 0x0010290 +#define CS35L43_IRQ1_POL_2 0x0010294 +#define CS35L43_IRQ1_POL_3 0x0010298 +#define CS35L43_IRQ1_POL_4 0x001029C +#define CS35L43_IRQ1_DB_2 0x0010314 + +/************************************************/ +/* GPIO Control */ +/************************************************/ +#define CS35L43_GPIO_STATUS1 0x0011000 +#define CS35L43_GPIO_FORCE 0x0011004 +#define CS35L43_GPIO1_CTRL1 0x0011008 +#define CS35L43_GPIO2_CTRL1 0x001100C +#define CS35L43_GPIO3_CTRL1 0x0011010 +#define CS35L43_GPIO4_CTRL1 0x0011014 + +/************************************************/ +/* DSP Noise Gate Control */ +/************************************************/ +#define CS35L43_MIXER_NGATE_CFG 0x0012000 +#define CS35L43_MIXER_NGATE_CH1_CFG 0x0012004 +#define CS35L43_MIXER_NGATE_CH2_CFG 0x0012008 + +/************************************************/ +/* DSP scratch space */ +/************************************************/ +#define CS35L43_DSP_MBOX_1 0x0013000 +#define CS35L43_DSP_MBOX_2 0x0013004 +#define CS35L43_DSP_MBOX_3 0x0013008 +#define CS35L43_DSP_MBOX_4 0x001300C +#define CS35L43_DSP_MBOX_5 0x0013010 +#define CS35L43_DSP_MBOX_6 0x0013014 +#define CS35L43_DSP_MBOX_7 0x0013018 +#define CS35L43_DSP_MBOX_8 0x001301C + +/************************************************/ +/* DSP virtual 1 scratch space */ +/************************************************/ +#define CS35L43_DSP_VIRTUAL1_MBOX_1 0x0013020 +#define CS35L43_DSP_VIRTUAL1_MBOX_2 0x0013024 +#define CS35L43_DSP_VIRTUAL1_MBOX_3 0x0013028 +#define CS35L43_DSP_VIRTUAL1_MBOX_4 0x001302C +#define CS35L43_DSP_VIRTUAL1_MBOX_5 0x0013030 +#define CS35L43_DSP_VIRTUAL1_MBOX_6 0x0013034 +#define CS35L43_DSP_VIRTUAL1_MBOX_7 0x0013038 +#define CS35L43_DSP_VIRTUAL1_MBOX_8 0x001303C + +/************************************************/ +/* DSP virtual 2 scratch space */ +/************************************************/ +#define CS35L43_DSP_VIRTUAL2_MBOX_1 0x0013040 +#define CS35L43_DSP_VIRTUAL2_MBOX_2 0x0013044 +#define CS35L43_DSP_VIRTUAL2_MBOX_3 0x0013048 +#define CS35L43_DSP_VIRTUAL2_MBOX_4 0x001304C +#define CS35L43_DSP_VIRTUAL2_MBOX_5 0x0013050 +#define CS35L43_DSP_VIRTUAL2_MBOX_6 0x0013054 +#define CS35L43_DSP_VIRTUAL2_MBOX_7 0x0013058 +#define CS35L43_DSP_VIRTUAL2_MBOX_8 0x001305C + +/************************************************/ +/* Halo X Memory */ +/************************************************/ +#define CS35L43_DSP1_XMEM_PACKED_0 0x2000000 +#define CS35L43_DSP1_XMEM_PACKED_6143 0x2005FFC +#define CS35L43_DSP1_XMEM_UNPACKED32_0 0x2400000 +#define CS35L43_DSP1_XMEM_UNPACKED32_4095 0x2403FFC +#define CS35L43_DSP1_XMEM_UNPACKED24_0 0x2800000 +#define CS35L43_DSP1_XMEM_UNPACKED24_8191 0x2807FFC + +#define CS35L43_DSP1_XROM_UNPACKED24_0 0x2808000 +#define CS35L43_DSP1_XROM_UNPACKED24_6141 0x280DFF4 + +/************************************************/ +/* Halo Control */ +/************************************************/ +#define CS35L43_DSP1_SYS_INFO_ID 0x25E0000 +#define CS35L43_DSP1_CLOCK_FREQ 0x2B80000 +#define CS35L43_DSP1_CORE_SOFT_RESET 0x2B80010 +#define CS35L43_DSP1_SCRATCH1 0x2B805C0 +#define CS35L43_DSP1_SCRATCH2 0x2B805C8 +#define CS35L43_DSP1_SCRATCH3 0x2B805D0 +#define CS35L43_DSP1_SCRATCH4 0x2B805D8 +#define CS35L43_DSP1_CCM_CORE_CONTROL 0x2BC1000 +#define CS35L43_DSP1_MPU_LOCK_STATE 0x2BC3140 +#define CS35L43_DSP1_MPU_XM_VIO_STATUS 0x2BC3104 +#define CS35L43_DSP1_MPU_YM_VIO_STATUS 0x2BC310C +#define CS35L43_DSP1_MPU_PM_VIO_STATUS 0x2BC3114 +#define CS35L43_DSP1_WDT_CONTROL 0x2BC7000 +#define CS35L43_DSP1_WDT_STATUS 0x2BC7008 + +/************************************************/ +/* Halo Y Memory */ +/************************************************/ +#define CS35L43_DSP1_YMEM_PACKED_0 0x2C00000 +#define CS35L43_DSP1_YMEM_PACKED_1532 0x2C017F0 +#define CS35L43_DSP1_YMEM_UNPACKED32_0 0x3000000 +#define CS35L43_DSP1_YMEM_UNPACKED32_1022 0x3000FF8 +#define CS35L43_DSP1_YMEM_UNPACKED24_0 0x3400000 +#define CS35L43_DSP1_YMEM_UNPACKED24_2045 0x3401FF4 + +/************************************************/ +/* Halo P Memory */ +/************************************************/ +#define CS35L43_DSP1_PMEM_0 0x3800000 +#define CS35L43_DSP1_PMEM_5114 0x3804FE8 + +/* #####################################################*/ +/* Fields */ +/* #####################################################*/ + +/************************************************/ +/* Power Control 2 */ +/************************************************/ +#define CS35L43_IMON_EN_MASK 0x00002000 +#define CS35L43_IMON_EN_SHIFT 13 +#define CS35L43_IMON_EN_WIDTH 1 +#define CS35L43_VMON_EN_MASK 0x00001000 +#define CS35L43_VMON_EN_SHIFT 12 +#define CS35L43_VMON_EN_WIDTH 1 +#define CS35L43_TEMPMON_EN_MASK 0x00000400 +#define CS35L43_TEMPMON_EN_SHIFT 10 +#define CS35L43_TEMPMON_EN_WIDTH 1 +#define CS35L43_VBSTMON_EN_MASK 0x00000200 +#define CS35L43_VBSTMON_EN_SHIFT 9 +#define CS35L43_VBSTMON_EN_WIDTH 1 +#define CS35L43_VPMON_EN_MASK 0x00000100 +#define CS35L43_VPMON_EN_SHIFT 8 +#define CS35L43_VPMON_EN_WIDTH 1 +#define CS35L43_BST_EN_MASK 0x00000030 +#define CS35L43_BST_EN_SHIFT 4 +#define CS35L43_BST_EN_WIDTH 2 +#define CS35L43_AMP_EN_MASK 0x00000001 +#define CS35L43_AMP_EN_SHIFT 0 +#define CS35L43_AMP_EN_WIDTH 1 +#define CS35L43_BST_EN_DEFAULT 2 + + +/************************************************/ +/* Power Control 3 */ +/************************************************/ +#define CS35L43_WKFET_AMP_EN_MASK 0x01000000 +#define CS35L43_WKFET_AMP_EN_SHIFT 24 +#define CS35L43_AMP_DRE_EN_MASK 0x00100000 +#define CS35L43_AMP_DRE_EN_SHIFT 20 +#define CS35L43_VPI_LIM_EN_MASK 0x00010000 +#define CS35L43_VPI_LIM_EN_SHIFT 16 +#define CS35L43_VBBR_EN_MASK 0x00002000 +#define CS35L43_VBBR_EN_SHIFT 13 +#define CS35L43_VPBR_EN_MASK 0x00001000 +#define CS35L43_VPBR_EN_SHIFT 12 +#define CS35L43_SYNC_EN_MASK 0x00000100 +#define CS35L43_SYNC_EN_SHIFT 8 +#define CS35L43_CLASSH_EN_MASK 0x00000010 +#define CS35L43_CLASSH_EN_SHIFT 4 + +/************************************************/ +/* Global Sync */ +/************************************************/ +#define CS35L43_AMP_MUTE_SHIFT 4 + +/************************************************/ +/* Hibernation Control */ +/************************************************/ +#define CS35L43_MEM_RDY 0x00000002 +#define CS35L43_WKSRC_STS_MASK 0x000003F0 + +/************************************************/ +/* Global Clocking Control */ +/************************************************/ +#define CS35L43_GLOBAL_FS_MASK 0x0000001F +#define CS35L43_GLOBAL_FS_SHIFT 0 +#define CS35L43_GLOBAL_FS_WIDTH 5 +#define CS35L43_FS1_START_WINDOW_MASK 0x00000FFF +#define CS35L43_FS2_START_WINDOW_SHIFT 12 +#define CS35L43_FS2_START_WINDOW_MASK 0x00FFF000 + +/************************************************/ +/* PLL Clocking Control */ +/************************************************/ +#define CS35L43_PLL_FORCE_EN_MASK 0x00010000 +#define CS35L43_PLL_FORCE_EN_SHIFT 16 +#define CS35L43_PLL_FORCE_EN_WIDTH 1 +#define CS35L43_PLL_OPEN_LOOP_MASK 0x00000800 +#define CS35L43_PLL_OPEN_LOOP_SHIFT 11 +#define CS35L43_PLL_OPEN_LOOP_WIDTH 1 +#define CS35L43_PLL_REFCLK_FREQ_MASK 0x000007E0 +#define CS35L43_PLL_REFCLK_FREQ_SHIFT 5 +#define CS35L43_PLL_REFCLK_FREQ_WIDTH 6 +#define CS35L43_PLL_REFCLK_EN_MASK 0x00000010 +#define CS35L43_PLL_REFCLK_EN_SHIFT 4 +#define CS35L43_PLL_REFCLK_EN_WIDTH 1 +#define CS35L43_PLL_REFCLK_SEL_MASK 0x00000007 +#define CS35L43_PLL_REFCLK_SEL_SHIFT 0 +#define CS35L43_PLL_REFCLK_SEL_WIDTH 3 + +/************************************************/ +/* GPIO Pad Interface Control */ +/************************************************/ +#define CS35L43_GP2_CTRL_MASK 0x07000000 +#define CS35L43_GP2_CTRL_SHIFT 24 +#define CS35L43_GP1_CTRL_MASK 0x00070000 +#define CS35L43_GP1_CTRL_SHIFT 16 + +/************************************************/ +/* GPIO_GPIO1_CTRL1 */ +/************************************************/ +#define CS35L43_GP1_DIR_MASK 0x80000000 +#define CS35L43_GP1_DIR_SHIFT 31 +#define CS35L43_GP1_DBTIME_MASK 0x000F0000 +#define CS35L43_GP1_DBTIME_SHIFT 16 +#define CS35L43_GP1_LVL_MASK 0x00008000 +#define CS35L43_GP1_LVL_SHIFT 15 +#define CS35L43_GP1_DB_MASK 0x00002000 +#define CS35L43_GP1_DB_SHIFT 13 +#define CS35L43_GP1_POL_MASK 0x00001000 +#define CS35L43_GP1_POL_SHIFT 12 +#define CS35L43_GP1_FN_MASK 0x0000007F +#define CS35L43_GP1_FN_SHIFT 0 + +/************************************************/ +/* GPIO_GPIO2_CTRL1 */ +/************************************************/ +#define CS35L43_GP2_DIR_MASK 0x80000000 +#define CS35L43_GP2_DIR_SHIFT 31 +#define CS35L43_GP2_DBTIME_MASK 0x000F0000 +#define CS35L43_GP2_DBTIME_SHIFT 16 +#define CS35L43_GP2_LVL_MASK 0x00008000 +#define CS35L43_GP2_LVL_SHIFT 15 +#define CS35L43_GP2_DB_MASK 0x00002000 +#define CS35L43_GP2_DB_SHIFT 13 +#define CS35L43_GP2_POL_MASK 0x00001000 +#define CS35L43_GP2_POL_SHIFT 12 +#define CS35L43_GP2_FN_MASK 0x0000007F +#define CS35L43_GP2_FN_SHIFT 0 + +#define CS35L43_GP2_CTRL_OPEN_DRAIN_ACTV_LO 2 +#define CS35L43_GP2_CTRL_PUSH_PULL_ACTV_LO 4 +#define CS35L43_GP2_CTRL_PUSH_PULL_ACTV_HI 5 + +/************************************************/ +/* Digital Boost Converter */ +/************************************************/ + +/************************************************/ +/* Boost Converter Voltage Control 1 */ +/************************************************/ +#define CS35L43_BST_CTL_MASK 0x000000FF +#define CS35L43_BST_CTL_SHIFT 0 + +/************************************************/ +/* Boost Converter Voltage Control 2 */ +/************************************************/ +#define CS35L43_BST_CTL_EXT_EN_MASK 0x00000100 +#define CS35L43_BST_CTL_EXT_EN_SHIFT 8 +#define CS35L43_BST_CTL_LIM_EN_MASK 0x00000004 +#define CS35L43_BST_CTL_LIM_EN_SHIFT 2 +#define CS35L43_BST_CTL_SEL_MASK 0x00000003 +#define CS35L43_BST_CTL_SEL_SHIFT 0 + +/************************************************/ +/* Boost Converter Peak Current */ +/************************************************/ +#define CS35L43_BST_IPK_MASK 0x0000007F +#define CS35L43_BST_IPK_SHIFT 0 + +/************************************************/ +/* DATAIF_ASP_ENABLES1 */ +/************************************************/ +#define CS35L43_ASP_RX3_EN_MASK 0x00040000 +#define CS35L43_ASP_RX3_EN_SHIFT 18 +#define CS35L43_ASP_RX3_EN_WIDTH 1 +#define CS35L43_ASP_RX2_EN_MASK 0x00020000 +#define CS35L43_ASP_RX2_EN_SHIFT 17 +#define CS35L43_ASP_RX2_EN_WIDTH 1 +#define CS35L43_ASP_RX1_EN_MASK 0x00010000 +#define CS35L43_ASP_RX1_EN_SHIFT 16 +#define CS35L43_ASP_RX1_EN_WIDTH 1 +#define CS35L43_ASP_TX4_EN_MASK 0x00000008 +#define CS35L43_ASP_TX4_EN_SHIFT 3 +#define CS35L43_ASP_TX4_EN_WIDTH 1 +#define CS35L43_ASP_TX3_EN_MASK 0x00000004 +#define CS35L43_ASP_TX3_EN_SHIFT 2 +#define CS35L43_ASP_TX3_EN_WIDTH 1 +#define CS35L43_ASP_TX2_EN_MASK 0x00000002 +#define CS35L43_ASP_TX2_EN_SHIFT 1 +#define CS35L43_ASP_TX2_EN_WIDTH 1 +#define CS35L43_ASP_TX1_EN_MASK 0x00000001 +#define CS35L43_ASP_TX1_EN_SHIFT 0 +#define CS35L43_ASP_TX1_EN_WIDTH 1 + +/************************************************/ +/* DATAIF_ASP_CONTROL1 */ +/************************************************/ +#define CS35L43_ASP_BCLK_FREQ_MASK 0x0000003F +#define CS35L43_ASP_BCLK_FREQ_SHIFT 0 +#define CS35L43_ASP_BCLK_FREQ_WIDTH 6 + +/************************************************/ +/* DATAIF_ASP_CONTROL2 */ +/************************************************/ +#define CS35L43_ASP_RX_WIDTH_MASK 0xFF000000 +#define CS35L43_ASP_RX_WIDTH_SHIFT 24 +#define CS35L43_ASP_RX_WIDTH_WIDTH 8 +#define CS35L43_ASP_TX_WIDTH_MASK 0x00FF0000 +#define CS35L43_ASP_TX_WIDTH_SHIFT 16 +#define CS35L43_ASP_TX_WIDTH_WIDTH 8 +#define CS35L43_ASP_FMT_MASK 0x00000700 +#define CS35L43_ASP_FMT_SHIFT 8 +#define CS35L43_ASP_FMT_WIDTH 3 +#define CS35L43_ASP_BCLK_INV_MASK 0x00000040 +#define CS35L43_ASP_BCLK_INV_SHIFT 6 +#define CS35L43_ASP_BCLK_INV_WIDTH 1 +#define CS35L43_ASP_BCLK_FRC_MASK 0x00000020 +#define CS35L43_ASP_BCLK_FRC_SHIFT 5 +#define CS35L43_ASP_BCLK_FRC_WIDTH 1 +#define CS35L43_ASP_BCLK_MSTR_MASK 0x00000010 +#define CS35L43_ASP_BCLK_MSTR_SHIFT 4 +#define CS35L43_ASP_BCLK_MSTR_WIDTH 1 +#define CS35L43_ASP_FSYNC_INV_MASK 0x00000004 +#define CS35L43_ASP_FSYNC_INV_SHIFT 2 +#define CS35L43_ASP_FSYNC_INV_WIDTH 1 +#define CS35L43_ASP_FSYNC_FRC_MASK 0x00000002 +#define CS35L43_ASP_FSYNC_FRC_SHIFT 1 +#define CS35L43_ASP_FSYNC_FRC_WIDTH 1 +#define CS35L43_ASP_FSYNC_MSTR_MASK 0x00000001 +#define CS35L43_ASP_FSYNC_MSTR_SHIFT 0 +#define CS35L43_ASP_FSYNC_MSTR_WIDTH 1 + +/************************************************/ +/* DATAIF_ASP_CONTROL3 */ +/************************************************/ +#define CS35L41_ASP_DOUT_HIZ_CTRL_SHIFT 0 +#define CS35L41_ASP_DOUT_HIZ_CTRL_MASK 0x00000003 + +/************************************************/ +/* DATAIF_ASP_DATA_CONTROL1 */ +/************************************************/ +#define CS35L43_ASP_TX_WL_MASK 0x0000003F +#define CS35L43_ASP_TX_WL_SHIFT 0 +#define CS35L43_ASP_TX_WL_WIDTH 6 + +/************************************************/ +/* DATAIF_ASP_DATA_CONTROL5 */ +/************************************************/ +#define CS35L43_ASP_RX_WL_MASK 0x0000003F +#define CS35L43_ASP_RX_WL_SHIFT 0 +#define CS35L43_ASP_RX_WL_WIDTH 6 + +#define CS35L43_INPUT_SRC_ASPRX1 0x08 +#define CS35L43_INPUT_SRC_ASPRX2 0x09 +#define CS35L43_INPUT_SRC_VMON 0x18 +#define CS35L43_INPUT_SRC_IMON 0x19 +#define CS35L43_INPUT_SRC_VMON_FS2 0x1A +#define CS35L43_INPUT_SRC_IMON_FS2 0x1B +#define CS35L43_INPUT_SRC_CLASSH 0x21 +#define CS35L43_INPUT_SRC_VPMON 0x28 +#define CS35L43_INPUT_SRC_VBSTMON 0x29 +#define CS35L43_INPUT_SRC_TEMPMON 0x3A +#define CS35L43_INPUT_DSP_TX1 0x32 +#define CS35L43_INPUT_DSP_TX2 0x33 +#define CS35L43_INPUT_DSP_TX3 0x34 +#define CS35L43_INPUT_DSP_TX4 0x35 +#define CS35L43_INPUT_DSP_TX5 0x36 +#define CS35L43_INPUT_DSP_TX6 0x37 +#define CS35L43_INPUT_MASK 0x3F + +/************************************************/ +/* DAC_MSM_ALIVE_DCIN_WD */ +/************************************************/ +#define CS35L43_WD_MODE_MASK 0x00000C00 +#define CS35L43_WD_MODE_SHIFT 10 +#define CS35L43_DCIN_WD_DUR_MASK 0x00000380 +#define CS35L43_DCIN_WD_DUR_SHIFT 7 +#define CS35L43_DCIN_WD_THLD_MASK 0x0000007E +#define CS35L43_DCIN_WD_THLD_SHIFT 1 +#define CS35L43_DCIN_WD_EN_MASK 0x00000001 +#define CS35L43_DCIN_WD_EN_SHIFT 0 + + +/************************************************/ +/* VPBR Configuration */ +/************************************************/ +#define CS35L43_VPBR_REL_RATE_MASK 0x00E00000 +#define CS35L43_VPBR_REL_RATE_SHIFT 21 +#define CS35L43_VPBR_WAIT_MASK 0x00180000 +#define CS35L43_VPBR_WAIT_SHIFT 19 +#define CS35L43_VPBR_ATK_RATE_MASK 0x00070000 +#define CS35L43_VPBR_ATK_RATE_SHIFT 16 +#define CS35L43_VPBR_ATK_VOL_MASK 0x0000F000 +#define CS35L43_VPBR_ATK_VOL_SHIFT 12 +#define CS35L43_VPBR_MAX_ATT_MASK 0x00000F00 +#define CS35L43_VPBR_MAX_ATT_SHIFT 8 +#define CS35L43_VPBR_THLD1_MASK 0x0000001F +#define CS35L43_VPBR_THLD1_SHIFT 0 + +/************************************************/ +/* IRQ1_IRQ1_EINT_1 */ +/************************************************/ +#define CS35L43_DSP_VIRTUAL2_MBOX_WR_EINT1_MASK 0x80000000 +#define CS35L43_DSP_VIRTUAL2_MBOX_WR_EINT1_SHIFT 31 +#define CS35L43_DSP_VIRTUAL1_MBOX_WR_EINT1_MASK 0x40000000 +#define CS35L43_DSP_VIRTUAL1_MBOX_WR_EINT1_SHIFT 30 +#define CS35L43_DC_WATCHDOG_IRQ_FALL_EINT1_MASK 0x20000000 +#define CS35L43_DC_WATCHDOG_IRQ_FALL_EINT1_SHIFT 29 +#define CS35L43_DC_WATCHDOG_IRQ_RISE_EINT1_MASK 0x10000000 +#define CS35L43_DC_WATCHDOG_IRQ_RISE_EINT1_SHIFT 28 +#define CS35L43_AMP_ERR_EINT1_MASK 0x08000000 +#define CS35L43_AMP_ERR_EINT1_SHIFT 27 +#define CS35L43_TEMP_ERR_EINT1_MASK 0x04000000 +#define CS35L43_TEMP_ERR_EINT1_SHIFT 26 +#define CS35L43_TEMP_WARN_FALL_EINT1_MASK 0x02000000 +#define CS35L43_TEMP_WARN_FALL_EINT1_SHIFT 25 +#define CS35L43_TEMP_WARN_RISE_EINT1_MASK 0x01000000 +#define CS35L43_TEMP_WARN_RISE_EINT1_SHIFT 24 +#define CS35L43_BST_IPK_FLAG_EINT1_MASK 0x00800000 +#define CS35L43_BST_IPK_FLAG_EINT1_SHIFT 23 +#define CS35L43_BST_SHORT_ERR_EINT1_MASK 0x00400000 +#define CS35L43_BST_SHORT_ERR_EINT1_SHIFT 22 +#define CS35L43_BST_DCM_UVP_ERR_EINT1_MASK 0x00200000 +#define CS35L43_BST_DCM_UVP_ERR_EINT1_SHIFT 21 +#define CS35L43_BST_OVP_ERR_EINT1_MASK 0x00100000 +#define CS35L43_BST_OVP_ERR_EINT1_SHIFT 20 +#define CS35L43_BST_OVP_FLAG_FALL_EINT1_MASK 0x00080000 +#define CS35L43_BST_OVP_FLAG_FALL_EINT1_SHIFT 19 +#define CS35L43_BST_OVP_FLAG_RISE_EINT1_MASK 0x00040000 +#define CS35L43_BST_OVP_FLAG_RISE_EINT1_SHIFT 18 +#define CS35L43_MSM_PUP_DONE_EINT1_MASK 0x00020000 +#define CS35L43_MSM_PUP_DONE_EINT1_SHIFT 17 +#define CS35L43_MSM_PDN_DONE_EINT1_MASK 0x00010000 +#define CS35L43_MSM_PDN_DONE_EINT1_SHIFT 16 +#define CS35L43_MSM_GLOBAL_EN_ASSERT_EINT1_MASK 0x00008000 +#define CS35L43_MSM_GLOBAL_EN_ASSERT_EINT1_SHIFT 15 +#define CS35L43_WKSRC_STATUS6_EINT1_MASK 0x00004000 +#define CS35L43_WKSRC_STATUS6_EINT1_SHIFT 14 +#define CS35L43_WKSRC_STATUS5_EINT1_MASK 0x00002000 +#define CS35L43_WKSRC_STATUS5_EINT1_SHIFT 13 +#define CS35L43_WKSRC_STATUS4_EINT1_MASK 0x00001000 +#define CS35L43_WKSRC_STATUS4_EINT1_SHIFT 12 +#define CS35L43_WKSRC_STATUS3_EINT1_MASK 0x00000800 +#define CS35L43_WKSRC_STATUS3_EINT1_SHIFT 11 +#define CS35L43_WKSRC_STATUS2_EINT1_MASK 0x00000400 +#define CS35L43_WKSRC_STATUS2_EINT1_SHIFT 10 +#define CS35L43_WKSRC_STATUS1_EINT1_MASK 0x00000200 +#define CS35L43_WKSRC_STATUS1_EINT1_SHIFT 9 +#define CS35L43_WKSRC_STATUS_ANY_EINT1_MASK 0x00000100 +#define CS35L43_WKSRC_STATUS_ANY_EINT1_SHIFT 8 +#define CS35L43_IRQ1_EINT_1_GPIO4_FALL_EINT1_MASK 0x00000080 +#define CS35L43_IRQ1_EINT_1_GPIO4_FALL_EINT1_SHIFT 7 +#define CS35L43_IRQ1_EINT_1_GPIO4_RISE_EINT1_MASK 0x00000040 +#define CS35L43_IRQ1_EINT_1_GPIO4_RISE_EINT1_SHIFT 6 +#define CS35L43_IRQ1_EINT_1_GPIO3_FALL_EINT1_MASK 0x00000020 +#define CS35L43_IRQ1_EINT_1_GPIO3_FALL_EINT1_SHIFT 5 +#define CS35L43_IRQ1_EINT_1_GPIO3_RISE_EINT1_MASK 0x00000010 +#define CS35L43_IRQ1_EINT_1_GPIO3_RISE_EINT1_SHIFT 4 +#define CS35L43_GPIO2_FALL_EINT1_MASK 0x00000008 +#define CS35L43_GPIO2_FALL_EINT1_SHIFT 3 +#define CS35L43_GPIO2_RISE_EINT1_MASK 0x00000004 +#define CS35L43_GPIO2_RISE_EINT1_SHIFT 2 +#define CS35L43_GPIO1_FALL_EINT1_MASK 0x00000002 +#define CS35L43_GPIO1_FALL_EINT1_SHIFT 1 +#define CS35L43_GPIO1_RISE_EINT1_MASK 0x00000001 +#define CS35L43_GPIO1_RISE_EINT1_SHIFT 0 + +/************************************************/ +/* IRQ1_IRQ1_EINT_2 */ +/************************************************/ +#define CS35L43_PWRMGT_SYNC_ERR_EINT1_MASK 0x20000000 +#define CS35L43_PWRMGT_SYNC_ERR_EINT1_SHIFT 29 +#define CS35L43_TIMER_CH2_EINT1_MASK 0x10000000 +#define CS35L43_TIMER_CH2_EINT1_SHIFT 28 +#define CS35L43_TIMER_CH1_EINT1_MASK 0x08000000 +#define CS35L43_TIMER_CH1_EINT1_SHIFT 27 +#define CS35L43_IMON_CLIPPED_EINT1_MASK 0x04000000 +#define CS35L43_IMON_CLIPPED_EINT1_SHIFT 26 +#define CS35L43_VMON_CLIPPED_EINT1_MASK 0x02000000 +#define CS35L43_VMON_CLIPPED_EINT1_SHIFT 25 +#define CS35L43_VBSTMON_CLIPPED_EINT1_MASK 0x01000000 +#define CS35L43_VBSTMON_CLIPPED_EINT1_SHIFT 24 +#define CS35L43_VPMON_CLIPPED_EINT1_MASK 0x00800000 +#define CS35L43_VPMON_CLIPPED_EINT1_SHIFT 23 +#define CS35L43_I2C_NACK_ERR_EINT1_MASK 0x00400000 +#define CS35L43_I2C_NACK_ERR_EINT1_SHIFT 22 +#define CS35L43_INTP_VC_DONE_EINT1_MASK 0x00200000 +#define CS35L43_INTP_VC_DONE_EINT1_SHIFT 21 +#define CS35L43_VBBR_ATT_CLR_EINT1_MASK 0x00100000 +#define CS35L43_VBBR_ATT_CLR_EINT1_SHIFT 20 +#define CS35L43_VBBR_FLAG_EINT1_MASK 0x00080000 +#define CS35L43_VBBR_FLAG_EINT1_SHIFT 19 +#define CS35L43_VPBR_ATT_CLR_EINT1_MASK 0x00040000 +#define CS35L43_VPBR_ATT_CLR_EINT1_SHIFT 18 +#define CS35L43_VPBR_FLAG_EINT1_MASK 0x00020000 +#define CS35L43_VPBR_FLAG_EINT1_SHIFT 17 +#define CS35L43_AMP_NG_ON_FALL_EINT1_MASK 0x00010000 +#define CS35L43_AMP_NG_ON_FALL_EINT1_SHIFT 16 +#define CS35L43_AMP_NG_ON_RISE_EINT1_MASK 0x00008000 +#define CS35L43_AMP_NG_ON_RISE_EINT1_SHIFT 15 +#define CS35L43_AUX_NG_CH2_EXIT_EINT1_MASK 0x00004000 +#define CS35L43_AUX_NG_CH2_EXIT_EINT1_SHIFT 14 +#define CS35L43_AUX_NG_CH2_ENTRY_EINT1_MASK 0x00002000 +#define CS35L43_AUX_NG_CH2_ENTRY_EINT1_SHIFT 13 +#define CS35L43_AUX_NG_CH1_EXIT_EINT1_MASK 0x00001000 +#define CS35L43_AUX_NG_CH1_EXIT_EINT1_SHIFT 12 +#define CS35L43_AUX_NG_CH1_ENTRY_EINT1_MASK 0x00000800 +#define CS35L43_AUX_NG_CH1_ENTRY_EINT1_SHIFT 11 +#define CS35L43_ASP_RXSLOT_CFG_ERR_EINT1_MASK 0x00000400 +#define CS35L43_ASP_RXSLOT_CFG_ERR_EINT1_SHIFT 10 +#define CS35L43_ASP_TXSLOT_CFG_ERR_EINT1_MASK 0x00000200 +#define CS35L43_ASP_TXSLOT_CFG_ERR_EINT1_SHIFT 9 +#define CS35L43_REFCLK_MISSING_RISE_EINT1_MASK 0x00000100 +#define CS35L43_REFCLK_MISSING_RISE_EINT1_SHIFT 8 +#define CS35L43_REFCLK_MISSING_FALL_EINT1_MASK 0x00000080 +#define CS35L43_REFCLK_MISSING_FALL_EINT1_SHIFT 7 +#define CS35L43_PLL_REFCLK_PRESENT_EINT1_MASK 0x00000040 +#define CS35L43_PLL_REFCLK_PRESENT_EINT1_SHIFT 6 +#define CS35L43_PLL_READY_RISE_EINT1_MASK 0x00000020 +#define CS35L43_PLL_READY_RISE_EINT1_SHIFT 5 +#define CS35L43_PLL_UNLOCK_FLAG_FALL_EINT1_MASK 0x00000010 +#define CS35L43_PLL_UNLOCK_FLAG_FALL_EINT1_SHIFT 4 +#define CS35L43_PLL_UNLOCK_FLAG_RISE_EINT1_MASK 0x00000008 +#define CS35L43_PLL_UNLOCK_FLAG_RISE_EINT1_SHIFT 3 +#define CS35L43_PLL_FREQ_LOCK_EINT1_MASK 0x00000004 +#define CS35L43_PLL_FREQ_LOCK_EINT1_SHIFT 2 +#define CS35L43_PLL_PHASE_LOCK_EINT1_MASK 0x00000002 +#define CS35L43_PLL_PHASE_LOCK_EINT1_SHIFT 1 +#define CS35L43_PLL_LOCK_EINT1_MASK 0x00000001 +#define CS35L43_PLL_LOCK_EINT1_SHIFT 0 + +/************************************************/ +/* IRQ1_IRQ1_EINT_3 */ +/************************************************/ +#define CS35L43_OTP_BOOT_ERR_EINT1_MASK 0x00040000 +#define CS35L43_OTP_BOOT_ERR_EINT1_SHIFT 18 +#define CS35L43_DSP1_NMI_ERR_EINT1_MASK 0x00000001 +#define CS35L43_DSP1_NMI_ERR_EINT1_SHIFT 0 +#define CS35L43_DSP1_MPU_ERR_EINT1_MASK 0x00000040 +#define CS35L43_DSP1_MPU_ERR_EINT1_SHIFT 6 +#define CS35L43_DSP1_STRM_ARB_ERR_EINT1_MASK 0x00000080 +#define CS35L43_DSP1_STRM_ARB_ERR_EINT1_SHIFT 7 + +/************************************************/ +/* Protection Release and Error Ignore Control */ +/************************************************/ +#define CS35L43_CLK_ERR_IGNORE_MASK 0x80000000 +#define CS35L43_CLK_ERR_IGNORE_SHIFT 31 +#define CS35L43_TEMP_ERR_IGNORE_MASK 0x40000000 +#define CS35L43_TEMP_ERR_IGNORE_SHIFT 30 +#define CS35L43_TEMP_WARN_IGNORE_MASK 0x20000000 +#define CS35L43_TEMP_WARN_IGNORE_SHIFT 29 +#define CS35L43_BST_UVP_ERR_IGNORE_MASK 0x10000000 +#define CS35L43_BST_UVP_ERR_IGNORE_SHIFT 28 +#define CS35L43_BST_OVP_ERR_IGNORE_MASK 0x08000000 +#define CS35L43_BST_OVP_ERR_IGNORE_SHIFT 27 +#define CS35L43_BST_SHORT_ERR_IGNORE_MASK 0x04000000 +#define CS35L43_BST_SHORT_ERR_IGNORE_SHIFT 26 +#define CS35L43_AMP_SHORT_ERR_IGNORE_MASK 0x02000000 +#define CS35L43_AMP_SHORT_ERR_IGNORE_SHIFT 25 +#define CS35L43_AMP_CAL_ERR_IGNORE_MASK 0x01000000 +#define CS35L43_AMP_CAL_ERR_IGNORE_SHIFT 24 +#define CS35L43_ERROR_RELEASE_CLK_ERR_RLS_MASK 0x00000080 +#define CS35L43_ERROR_RELEASE_CLK_ERR_RLS_SHIFT 7 +#define CS35L43_TEMP_ERR_RLS_MASK 0x00000040 +#define CS35L43_TEMP_ERR_RLS_SHIFT 6 +#define CS35L43_TEMP_WARN_RLS_MASK 0x00000020 +#define CS35L43_TEMP_WARN_RLS_SHIFT 5 +#define CS35L43_BST_UVP_ERR_RLS_MASK 0x00000010 +#define CS35L43_BST_UVP_ERR_RLS_SHIFT 4 +#define CS35L43_BST_OVP_ERR_RLS_MASK 0x00000008 +#define CS35L43_BST_OVP_ERR_RLS_SHIFT 3 +#define CS35L43_BST_SHORT_ERR_RLS_MASK 0x00000004 +#define CS35L43_BST_SHORT_ERR_RLS_SHIFT 2 +#define CS35L43_AMP_SHORT_ERR_RLS_MASK 0x00000002 +#define CS35L43_AMP_SHORT_ERR_RLS_SHIFT 1 +#define CS35L43_AMP_CAL_ERR_RLS_MASK 0x00000001 +#define CS35L43_AMP_CAL_ERR_RLS_SHIFT 0 + +/************************************************/ +/* Amplifier Gain Control */ +/************************************************/ +#define CS35L43_AMP_GAIN_ZC_MASK 0x00000400 +#define CS35L43_AMP_GAIN_ZC_SHIFT 10 +#define CS35L43_AMP_GAIN_PCM_MASK 0x000003E0 +#define CS35L43_AMP_GAIN_PCM_SHIFT 5 + +/************************************************/ +/* Amplifier Digital Volume Control */ +/************************************************/ +#define CS35L43_AMP_HPF_PCM_EN_MASK 0x00008000 +#define CS35L43_AMP_HPF_PCM_EN_SHIFT 15 +#define CS35L43_AMP_INV_PCM_MASK 0x00004000 +#define CS35L43_AMP_INV_PCM_SHIFT 14 +#define CS35L43_AMP_VOL_PCM_MASK 0x000007FF +#define CS35L43_AMP_VOL_PCM_SHIFT 3 +#define CS35L43_AMP_RAMP_PCM_MASK 0x00000007 +#define CS35L43_AMP_RAMP_PCM_SHIFT 0 +#define CS35L43_AMP_VOL_PCM_DEFAULT 0x0000 +#define CS35L43_AMP_VOL_PCM_MUTE 0x04CF +#define CS35L43_AMP_VOL_CTRL_DEFAULT 817 +#define CS35L43_AMP_VOL_MIN 102 + +/************************************************/ +/* Amplifier High Rate Control */ +/************************************************/ +#define CS35L43_VIMON_DUAL_RATE_MASK 0x00010000 +#define CS35L43_VIMON_DUAL_RATE_SHIFT 16 +#define CS35L43_AMP_PCM_FSX2_EN_MASK 0x00000001 +#define CS35L43_DSP_RX1_RATE_MASK 0x00000003 +#define CS35L43_DSP_RX2_RATE_MASK 0x00000300 +#define CS35L43_DSP_RX3_RATE_MASK 0x00030000 +#define CS35L43_DSP_RX4_RATE_MASK 0x03000000 +#define CS35L43_DSP_RX5_RATE_MASK 0x00000003 +#define CS35L43_DSP_RX6_RATE_MASK 0x00000300 +#define CS35L43_DSP_TX1_RATE_MASK 0x00000003 +#define CS35L43_DSP_TX2_RATE_MASK 0x00000300 +#define CS35L43_DSP_TX3_RATE_MASK 0x00030000 +#define CS35L43_DSP_TX4_RATE_MASK 0x03000000 +#define CS35L43_DSP_TX5_RATE_MASK 0x00000003 +#define CS35L43_DSP_TX6_RATE_MASK 0x00000300 +#define CS35L43_DSP_RX1_RATE_SHIFT 0 +#define CS35L43_DSP_RX2_RATE_SHIFT 8 +#define CS35L43_DSP_RX3_RATE_SHIFT 16 +#define CS35L43_DSP_RX4_RATE_SHIFT 24 +#define CS35L43_DSP_RX5_RATE_SHIFT 0 +#define CS35L43_DSP_RX6_RATE_SHIFT 8 +#define CS35L43_DSP_TX1_RATE_SHIFT 0 +#define CS35L43_DSP_TX2_RATE_SHIFT 8 +#define CS35L43_DSP_TX3_RATE_SHIFT 16 +#define CS35L43_DSP_TX4_RATE_SHIFT 24 +#define CS35L43_DSP_TX5_RATE_SHIFT 0 +#define CS35L43_DSP_TX6_RATE_SHIFT 8 +#define CS35L43_BASE_RATE 0x1 +#define CS35L43_HIGH_RATE 0x3 + +/************************************************/ +/* Monitor Trim */ +/************************************************/ +#define CS35L43_VMON_BST_COEFF_SHIFT 22 +#define CS35L43_VMON_BST_COEFF_MASK 0x3FC00000 +#define CS35L43_IMON_BST_COEFF_SHIFT 12 +#define CS35L43_IMON_BST_COEFF_MASK 0x003FF000 + +/************************************************/ +/* DSP Noise Gate Control */ +/************************************************/ + +/************************************************/ +/* NOISE_GATE_MIXER_NGATE_CFG */ +/************************************************/ +#define CS35L43_AUX_NGATE_FAST_MASK 0x00000001 +#define CS35L43_AUX_NGATE_FAST_SHIFT 0 + +/************************************************/ +/* NOISE_GATE_MIXER_NGATE_CH1_CFG */ +/************************************************/ +#define CS35L43_AUX_NGATE_CH1_FRC_MASK 0x00020000 +#define CS35L43_AUX_NGATE_CH1_FRC_SHIFT 17 +#define CS35L43_AUX_NGATE_CH1_EN_MASK 0x00010000 +#define CS35L43_AUX_NGATE_CH1_EN_SHIFT 16 +#define CS35L43_AUX_NGATE_CH1_HOLD_MASK 0x00000F00 +#define CS35L43_AUX_NGATE_CH1_HOLD_SHIFT 8 +#define CS35L43_AUX_NGATE_CH1_THR_MASK 0x00000007 +#define CS35L43_AUX_NGATE_CH1_THR_SHIFT 0 + +/************************************************/ +/* NOISE_GATE_MIXER_NGATE_CH2_CFG */ +/************************************************/ +#define CS35L43_AUX_NGATE_CH2_FRC_MASK 0x00020000 +#define CS35L43_AUX_NGATE_CH2_FRC_SHIFT 17 +#define CS35L43_AUX_NGATE_CH2_EN_MASK 0x00010000 +#define CS35L43_AUX_NGATE_CH2_EN_SHIFT 16 +#define CS35L43_AUX_NGATE_CH2_HOLD_MASK 0x00000F00 +#define CS35L43_AUX_NGATE_CH2_HOLD_SHIFT 8 +#define CS35L43_AUX_NGATE_CH2_THR_MASK 0x00000007 +#define CS35L43_AUX_NGATE_CH2_THR_SHIFT 0 + +/************************************************/ +/* Noise Gate Configuration */ +/************************************************/ +#define CS35L43_NG_FRC_MASK 0x00008000 +#define CS35L43_NG_FRC_SHIFT 15 +#define CS35L43_NG_EN_SEL_MASK 0x00003F00 +#define CS35L43_NG_EN_SEL_SHIFT 8 +#define CS35L43_NG_DELAY_MASK 0x00000070 +#define CS35L43_NG_DELAY_SHIFT 4 +#define CS35L43_NG_PCM_THLD_MASK 0x00000007 +#define CS35L43_NG_PCM_THLD_SHIFT 0 + +/* #####################################################*/ +/* Software Values */ +/* #####################################################*/ + +#define CS35L43_MBOX_TYPE_PWR 0x2 +#define CS35L43_MBOX_TYPE_MEM_VAL 0x6 +#define CS35L43_MBOX_TYPE_SYS 0xA +#define CS35L43_MBOX_TYPE_AUDIO 0xB +#define CS35L43_MBOX_TYPE_ERROR 0xC +#define CS35L43_MBOX_TYPE_EVENT 0xD +#define CS35L43_MBOX_TYPE_WDT 0xE + +#define CS35L43_MBOX_MSG_ACK 0x0A000000 +#define CS35L43_MBOX_MSG_AWAKE 0x02000002 + +#define CS35L43_MBOX_CMD_AUDIO_PLAY 0x0B000001 +#define CS35L43_MBOX_CMD_AUDIO_PAUSE 0x0B000002 +#define CS35L43_MBOX_CMD_AUDIO_REINIT 0x0B000003 + +#define CS35L43_MBOX_CMD_HIBERNATE 0x02000001 +#define CS35L43_MBOX_CMD_WAKEUP 0x02000002 +#define CS35L43_MBOX_CMD_PREVENT_HIBERNATE 0x02000003 +#define CS35L43_MBOX_CMD_ALLOW_HIBERNATE 0x02000004 +#define CS35L43_MBOX_CMD_SHUTDOWN 0x02000005 + +#define CS35L43_AUDIO_STATE_READY 0 +#define CS35L43_AUDIO_STATE_WAITING 1 +#define CS35L43_AUDIO_STATE_RUNNING 2 +#define CS35L43_AUDIO_STATE_ERROR 3 +#define CS35L43_AUDIO_STATE_MEM_ERR 4 +#define CS35L43_AUDIO_STATE_HW_ERR 5 +#define CS35L43_AUDIO_STATE_RAMPDOWN 6 +#define CS35L43_AUDIO_STATE_AUX_NG_MUTED 7 + +#define CS35L43_ALG_ID_MAILBOX 0x5f203 +#define CS35L43_ALG_ID_PM 0x5f206 + +#define CS35L43_POWER_SEQ_LENGTH 42 +#define CS35L43_POWER_SEQ_MAX_WORDS 129 +#define CS35L43_POWER_SEQ_NUM_OPS 8 +#define CS35L43_POWER_SEQ_OP_MASK GENMASK(23, 16) +#define CS35L43_POWER_SEQ_OP_SHIFT 16 +#define CS35L43_POWER_SEQ_OP_WRITE_REG_FULL 0x00 +#define CS35L43_POWER_SEQ_OP_WRITE_REG_FULL_WORDS 3 +#define CS35L43_POWER_SEQ_OP_WRITE_FIELD 0x01 +#define CS35L43_POWER_SEQ_OP_WRITE_FIELD_WORDS 4 +#define CS35L43_POWER_SEQ_OP_WRITE_REG_ADDR8 0x02 +#define CS35L43_POWER_SEQ_OP_WRITE_REG_ADDR8_WORDS 2 +#define CS35L43_POWER_SEQ_OP_WRITE_REG_INCR 0x03 +#define CS35L43_POWER_SEQ_OP_WRITE_REG_INCR_WORDS 2 +#define CS35L43_POWER_SEQ_OP_WRITE_REG_L16 0x04 +#define CS35L43_POWER_SEQ_OP_WRITE_REG_L16_WORDS 2 +#define CS35L43_POWER_SEQ_OP_WRITE_REG_H16 0x05 +#define CS35L43_POWER_SEQ_OP_WRITE_REG_H16_WORDS 2 +#define CS35L43_POWER_SEQ_OP_DELAY 0xFE +#define CS35L43_POWER_SEQ_OP_DELAY_WORDS 1 +#define CS35L43_POWER_SEQ_OP_END 0xFF +#define CS35L43_POWER_SEQ_OP_END_WORDS 1 + +#define CS35L43_SPI_MAX_FREQ_NO_PLL 4000000 diff --git a/sound/soc/codecs/cs40l26-a2h.c b/sound/soc/codecs/cs40l26-a2h.c new file mode 100644 index 000000000000..565bb0e94fe5 --- /dev/null +++ b/sound/soc/codecs/cs40l26-a2h.c @@ -0,0 +1,1054 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// cs40l26.c -- ALSA SoC Audio driver for Cirrus Logic Haptic Device: CS40L26 +// +// Copyright 2022 Cirrus Logic. Inc. + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE +#include +#else +#include +#endif + +static const struct cs40l26_pll_sysclk_config cs40l26_pll_sysclk[] = { + {CS40L26_PLL_CLK_FRQ_32768, CS40L26_PLL_CLK_CFG_32768}, + {CS40L26_PLL_CLK_FRQ_1536000, CS40L26_PLL_CLK_CFG_1536000}, + {CS40L26_PLL_CLK_FRQ_3072000, CS40L26_PLL_CLK_CFG_3072000}, + {CS40L26_PLL_CLK_FRQ_6144000, CS40L26_PLL_CLK_CFG_6144000}, + {CS40L26_PLL_CLK_FRQ_9600000, CS40L26_PLL_CLK_CFG_9600000}, + {CS40L26_PLL_CLK_FRQ_12288000, CS40L26_PLL_CLK_CFG_12288000}, +}; + +static int cs40l26_get_clk_config(u32 freq, u8 *clk_cfg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs40l26_pll_sysclk); i++) { + if (cs40l26_pll_sysclk[i].freq == freq) { + *clk_cfg = cs40l26_pll_sysclk[i].clk_cfg; + return 0; + } + } + + return -EINVAL; +} + +static int cs40l26_swap_ext_clk(struct cs40l26_codec *codec, u8 clk_src) +{ + struct regmap *regmap = codec->regmap; + struct device *dev = codec->dev; + u8 clk_cfg, clk_sel; + int error; + + switch (clk_src) { + case CS40L26_PLL_REFCLK_BCLK: + clk_sel = CS40L26_PLL_CLK_SEL_BCLK; + error = cs40l26_get_clk_config(codec->sysclk_rate, &clk_cfg); + break; + case CS40L26_PLL_REFCLK_MCLK: + clk_sel = CS40L26_PLL_CLK_SEL_MCLK; + error = cs40l26_get_clk_config(CS40L26_PLL_CLK_FRQ_32768, &clk_cfg); + break; + case CS40L26_PLL_REFCLK_FSYNC: + error = -EPERM; + break; + default: + error = -EINVAL; + } + + if (error) { + dev_err(dev, "Failed to get clock configuration\n"); + return error; + } + + error = cs40l26_set_pll_loop(codec->core, CS40L26_PLL_REFCLK_SET_OPEN_LOOP); + if (error) + return error; + + error = regmap_update_bits(regmap, CS40L26_REFCLK_INPUT, CS40L26_PLL_REFCLK_FREQ_MASK | + CS40L26_PLL_REFCLK_SEL_MASK, (clk_cfg << CS40L26_PLL_REFCLK_FREQ_SHIFT) | + clk_sel); + if (error) { + dev_err(dev, "Failed to update REFCLK input\n"); + return error; + } + + return cs40l26_set_pll_loop(codec->core, CS40L26_PLL_REFCLK_SET_CLOSED_LOOP); +} + +static int cs40l26_clk_en(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) +{ + struct cs40l26_codec *codec = + snd_soc_component_get_drvdata(snd_soc_dapm_to_component(w->dapm)); + struct cs40l26_private *cs40l26 = codec->core; + struct device *dev = cs40l26->dev; + int error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "%s: %s\n", __func__, event == SND_SOC_DAPM_POST_PMU ? "PMU" : "PMD"); +#else + dev_dbg(dev, "%s: %s\n", __func__, event == SND_SOC_DAPM_POST_PMU ? "PMU" : "PMD"); +#endif + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + mutex_lock(&cs40l26->lock); + cs40l26_vibe_state_update(cs40l26, CS40L26_VIBE_STATE_EVENT_ASP_START); + error = cs40l26_asp_start(cs40l26); + mutex_unlock(&cs40l26->lock); + if (error) + return error; + + if (!completion_done(&cs40l26->i2s_cont)) { + if (!wait_for_completion_timeout(&cs40l26->i2s_cont, + msecs_to_jiffies(CS40L26_ASP_START_TIMEOUT))) + dev_warn(codec->dev, "SVC calibration not complete\n"); + } + + error = cs40l26_swap_ext_clk(codec, CS40L26_PLL_REFCLK_BCLK); + if (error) + return error; + break; + case SND_SOC_DAPM_PRE_PMD: + error = cs40l26_swap_ext_clk(codec, CS40L26_PLL_REFCLK_MCLK); + if (error) + return error; + + mutex_lock(&cs40l26->lock); + cs40l26_vibe_state_update(cs40l26, CS40L26_VIBE_STATE_EVENT_ASP_STOP); + mutex_unlock(&cs40l26->lock); + + break; + default: + dev_err(dev, "Invalid event: %d\n", event); + return -EINVAL; + } + + return 0; +} + +static int cs40l26_dsp_tx(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) +{ struct cs40l26_codec *codec = + snd_soc_component_get_drvdata(snd_soc_dapm_to_component(w->dapm)); + struct cs40l26_private *cs40l26 = codec->core; + struct device *dev = cs40l26->dev; + const struct firmware *fw; + int error; + u32 reg; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "%s: %s\n", __func__, event == SND_SOC_DAPM_POST_PMU ? "PMU" : "PMD"); +#else + dev_dbg(dev, "%s: %s\n", __func__, event == SND_SOC_DAPM_POST_PMU ? "PMU" : "PMD"); +#endif + + if (codec->dsp_bypass) { + dev_err(dev, "Cannot use A2H while bypassing DSP\n"); + return -EPERM; + } + + error = cl_dsp_get_reg(cs40l26->dsp, "A2HEN", CL_DSP_XM_UNPACKED_TYPE, CS40L26_A2H_ALGO_ID, + ®); + if (error) + return error; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (codec->tuning != codec->tuning_prev) { + error = request_firmware(&fw, codec->bin_file, dev); + if (error) { + dev_err(codec->dev, "Failed to request %s\n", codec->bin_file); + return error; + } + + error = cl_dsp_coeff_file_parse(cs40l26->dsp, fw); + release_firmware(fw); + if (error) { + dev_warn(dev, "Failed to load %s, %d. Continuing...", + codec->bin_file, error); + return error; + } + + dev_info(dev, "%s Loaded Successfully\n", codec->bin_file); + + codec->tuning_prev = codec->tuning; + + error = cs40l26_mailbox_write(cs40l26, CS40L26_DSP_MBOX_CMD_A2H_REINIT); + if (error) + return error; + } + return regmap_write(cs40l26->regmap, reg, 1); + case SND_SOC_DAPM_PRE_PMD: + return regmap_write(cs40l26->regmap, reg, 0); + default: + dev_err(dev, "Invalid A2H event: %d\n", event); + return -EINVAL; + } +} + +static int cs40l26_asp_rx(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) +{ struct cs40l26_codec *codec = + snd_soc_component_get_drvdata(snd_soc_dapm_to_component(w->dapm)); + struct cs40l26_private *cs40l26 = codec->core; + struct regmap *regmap = cs40l26->regmap; + struct device *dev = cs40l26->dev; + u32 asp_en_mask = CS40L26_ASP_TX1_EN_MASK | CS40L26_ASP_TX2_EN_MASK | + CS40L26_ASP_RX1_EN_MASK | CS40L26_ASP_RX2_EN_MASK; + u32 asp_enables; + u8 data_src; + int error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "%s: %s\n", __func__, event == SND_SOC_DAPM_POST_PMU ? "PMU" : "PMD"); +#else + dev_dbg(dev, "%s: %s\n", __func__, event == SND_SOC_DAPM_POST_PMU ? "PMU" : "PMD"); +#endif + + mutex_lock(&cs40l26->lock); + + data_src = codec->dsp_bypass ? CS40L26_DATA_SRC_ASPRX1 : CS40L26_DATA_SRC_DSP1TX1; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + error = regmap_update_bits(regmap, CS40L26_DACPCM1_INPUT, + CS40L26_DATA_SRC_MASK, data_src); + if (error) { + dev_err(dev, "Failed to set DAC PCM input\n"); + goto err_mutex; + } + + error = regmap_update_bits(regmap, CS40L26_ASPTX1_INPUT, CS40L26_DATA_SRC_MASK, + data_src); + if (error) { + dev_err(dev, "Failed to set ASPTX1 input\n"); + goto err_mutex; + } + + asp_enables = 1 | (1 << CS40L26_ASP_TX2_EN_SHIFT) | (1 << CS40L26_ASP_RX1_EN_SHIFT) + | (1 << CS40L26_ASP_RX2_EN_SHIFT); + + error = regmap_update_bits(regmap, CS40L26_ASP_ENABLES1, asp_en_mask, asp_enables); + if (error) { + dev_err(dev, "Failed to enable ASP channels\n"); + goto err_mutex; + } + + break; + case SND_SOC_DAPM_PRE_PMD: + error = cs40l26_mailbox_write(cs40l26, CS40L26_DSP_MBOX_CMD_STOP_I2S); + if (error) + goto err_mutex; + + error = regmap_update_bits(regmap, CS40L26_ASP_ENABLES1, asp_en_mask, 0); + if (error) { + dev_err(dev, "Failed to clear ASPTX1 input\n"); + goto err_mutex; + } + + error = regmap_update_bits(regmap, CS40L26_ASPTX1_INPUT, CS40L26_DATA_SRC_MASK, + CS40L26_DATA_SRC_VMON); + if (error) + dev_err(dev, "Failed to set ASPTX1 input\n"); + break; + default: + dev_err(dev, "Invalid PCM event: %d\n", event); + error = -EINVAL; + } + +err_mutex: + mutex_unlock(&cs40l26->lock); + + return error; +} + +static int cs40l26_i2s_vmon_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct cs40l26_codec *codec = + snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol)); + struct cs40l26_private *cs40l26 = codec->core; + int error; + u32 val; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s\n", __func__); +#endif + error = cs40l26_pm_enter(cs40l26->dev); + if (error) + return error; + + error = regmap_read(cs40l26->regmap, CS40L26_SPKMON_VMON_DEC_OUT_DATA, &val); + if (error) { + dev_err(cs40l26->dev, "Failed to get VMON Data for I2S\n"); + goto pm_err; + } + + if (val & CS40L26_VMON_OVFL_FLAG_MASK) { + dev_err(cs40l26->dev, "I2S VMON overflow detected\n"); + error = -EOVERFLOW; + goto pm_err; + } + + ucontrol->value.enumerated.item[0] = val & CS40L26_VMON_DEC_OUT_DATA_MASK; + +pm_err: + cs40l26_pm_exit(cs40l26->dev); + + return error; +} + +static int cs40l26_dsp_bypass_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs40l26_codec *codec = + snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol)); + struct cs40l26_private *cs40l26 = codec->core; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s\n", __func__); +#endif + mutex_lock(&cs40l26->lock); + + if (codec->dsp_bypass) + ucontrol->value.enumerated.item[0] = 1; + else + ucontrol->value.enumerated.item[0] = 0; + + mutex_unlock(&cs40l26->lock); + + return 0; +} + +static int cs40l26_dsp_bypass_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs40l26_codec *codec = + snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol)); + struct cs40l26_private *cs40l26 = codec->core; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s\n", __func__); +#endif + mutex_lock(&cs40l26->lock); + + if (ucontrol->value.enumerated.item[0]) + codec->dsp_bypass = true; + else + codec->dsp_bypass = false; + + mutex_unlock(&cs40l26->lock); + + return 0; +} + +static int cs40l26_svc_en_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct cs40l26_codec *codec = + snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol)); + struct cs40l26_private *cs40l26 = codec->core; + struct regmap *regmap = cs40l26->regmap; + struct device *dev = cs40l26->dev; + unsigned int val = 0, reg; + int error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "%s\n", __func__); +#endif + error = cl_dsp_get_reg(cs40l26->dsp, "FLAGS", CL_DSP_XM_UNPACKED_TYPE, CS40L26_EXT_ALGO_ID, + ®); + if (error) + return error; + + error = cs40l26_pm_enter(dev); + if (error) + return error; + + error = regmap_read(regmap, reg, &val); + if (error) { + dev_err(cs40l26->dev, "Failed to read FLAGS\n"); + goto pm_err; + } + + if (val & CS40L26_SVC_EN_MASK) + ucontrol->value.enumerated.item[0] = 1; + else + ucontrol->value.enumerated.item[0] = 0; + +pm_err: + cs40l26_pm_exit(dev); + + return error; +} + +static int cs40l26_svc_en_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(snd_soc_kcontrol_component(kcontrol)); + struct cs40l26_codec *codec = + snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol)); + struct cs40l26_private *cs40l26 = codec->core; + struct regmap *regmap = cs40l26->regmap; + struct device *dev = cs40l26->dev; + unsigned int reg; +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + int error = 0; +#else + int error; +#endif + +#if IS_ENABLED(CONFIG_SEC_FACTORY) + return error; +#endif + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "%s\n", __func__); +#endif + error = cl_dsp_get_reg(cs40l26->dsp, "FLAGS", CL_DSP_XM_UNPACKED_TYPE, CS40L26_EXT_ALGO_ID, + ®); + if (error) + return error; + + error = cs40l26_pm_enter(dev); + if (error) + return error; + + snd_soc_dapm_mutex_lock(dapm); + + error = regmap_update_bits(regmap, reg, CS40L26_SVC_EN_MASK, + ucontrol->value.enumerated.item[0]); + if (error) + dev_err(cs40l26->dev, "Failed to specify SVC for streaming\n"); + + snd_soc_dapm_mutex_unlock(dapm); + + cs40l26_pm_exit(dev); + + return error; +} + +static int cs40l26_invert_streaming_data_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs40l26_codec *codec = + snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol)); + struct cs40l26_private *cs40l26 = codec->core; + struct regmap *regmap = cs40l26->regmap; + struct device *dev = cs40l26->dev; + unsigned int val = 0, reg; + int error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "%s\n", __func__); +#endif + error = cl_dsp_get_reg(cs40l26->dsp, "SOURCE_INVERT", + CL_DSP_XM_UNPACKED_TYPE, CS40L26_EXT_ALGO_ID, ®); + if (error) + return error; + + error = cs40l26_pm_enter(dev); + if (error) + return error; + + error = regmap_read(regmap, reg, &val); + if (error) { + dev_err(cs40l26->dev, "Failed to read SOURCE_INVERT\n"); + goto pm_err; + } + + if (val) + ucontrol->value.enumerated.item[0] = 1; + else + ucontrol->value.enumerated.item[0] = 0; + +pm_err: + cs40l26_pm_exit(dev); + + return error; +} + +static int cs40l26_invert_streaming_data_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(snd_soc_kcontrol_component(kcontrol)); + struct cs40l26_codec *codec = + snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol)); + struct cs40l26_private *cs40l26 = codec->core; + struct regmap *regmap = cs40l26->regmap; + struct device *dev = cs40l26->dev; + unsigned int reg; + int error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "%s\n", __func__); +#endif + error = cl_dsp_get_reg(cs40l26->dsp, "SOURCE_INVERT", + CL_DSP_XM_UNPACKED_TYPE, CS40L26_EXT_ALGO_ID, ®); + if (error) + return error; + + error = cs40l26_pm_enter(dev); + if (error) + return error; + + snd_soc_dapm_mutex_lock(dapm); + + error = regmap_write(regmap, reg, ucontrol->value.enumerated.item[0]); + if (error) + dev_err(cs40l26->dev, "Failed to specify invert streaming data\n"); + + snd_soc_dapm_mutex_unlock(dapm); + + cs40l26_pm_exit(dev); + + return error; +} + +static int cs40l26_tuning_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct cs40l26_codec *codec = + snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol)); +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + struct cs40l26_private *cs40l26 = codec->core; + + dev_info(cs40l26->dev, "%s\n", __func__); +#endif + ucontrol->value.enumerated.item[0] = codec->tuning; + + return 0; +} + +static int cs40l26_tuning_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct cs40l26_codec *codec = + snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol)); + struct cs40l26_private *cs40l26 = codec->core; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s\n", __func__); +#endif + if (ucontrol->value.enumerated.item[0] == codec->tuning) + return 0; + + if (cs40l26->asp_enable) + return -EBUSY; + + codec->tuning = ucontrol->value.enumerated.item[0]; + + memset(codec->bin_file, 0, PAGE_SIZE); + codec->bin_file[PAGE_SIZE - 1] = '\0'; + + if (codec->tuning > 0) + snprintf(codec->bin_file, PAGE_SIZE, "cs40l26-a2h%d.bin", codec->tuning); + else + snprintf(codec->bin_file, PAGE_SIZE, "cs40l26-a2h.bin"); + + return 0; +} + +static int cs40l26_a2h_level_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct cs40l26_codec *codec = + snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol)); + struct cs40l26_private *cs40l26 = codec->core; + struct regmap *regmap = cs40l26->regmap; + struct device *dev = cs40l26->dev; + unsigned int val = 0, reg; + int error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "%s\n", __func__); +#endif + error = cl_dsp_get_reg(cs40l26->dsp, "VOLUMELEVEL", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_A2H_ALGO_ID, ®); + if (error) + return error; + + error = cs40l26_pm_enter(dev); + if (error) + return error; + + error = regmap_read(regmap, reg, &val); + if (error) { + dev_err(dev, "Failed to get VOLUMELEVEL\n"); + goto pm_err; + } + + ucontrol->value.integer.value[0] = val; + +pm_err: + cs40l26_pm_exit(dev); + + return error; +} + +static int cs40l26_a2h_level_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(snd_soc_kcontrol_component(kcontrol)); + struct cs40l26_codec *codec = + snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol)); + struct cs40l26_private *cs40l26 = codec->core; + struct regmap *regmap = cs40l26->regmap; + struct device *dev = cs40l26->dev; + unsigned int val = 0, reg; + int error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "%s\n", __func__); +#endif + error = cl_dsp_get_reg(cs40l26->dsp, "VOLUMELEVEL", CL_DSP_XM_UNPACKED_TYPE, + CS40L26_A2H_ALGO_ID, ®); + if (error) + return error; + + error = cs40l26_pm_enter(dev); + if (error) + return error; + + snd_soc_dapm_mutex_lock(dapm); + + if (ucontrol->value.integer.value[0] > CS40L26_A2H_LEVEL_MAX) + val = CS40L26_A2H_LEVEL_MAX; + else if (ucontrol->value.integer.value[0] < CS40L26_A2H_LEVEL_MIN) + val = CS40L26_A2H_LEVEL_MIN; + else + val = ucontrol->value.integer.value[0]; + + error = regmap_write(regmap, reg, val); + if (error) + dev_err(dev, "Failed to set VOLUMELEVEL\n"); + + snd_soc_dapm_mutex_unlock(dapm); + + cs40l26_pm_exit(dev); + + return error; +} + +static int cs40l26_a2h_delay_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct cs40l26_codec *codec = + snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol)); + struct cs40l26_private *cs40l26 = codec->core; + struct regmap *regmap = cs40l26->regmap; + struct device *dev = cs40l26->dev; + unsigned int val = 0, reg; + int error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "%s\n", __func__); +#endif + error = cl_dsp_get_reg(cs40l26->dsp, "LRADELAYSAMPS", + CL_DSP_XM_UNPACKED_TYPE, CS40L26_A2H_ALGO_ID, ®); + if (error) + return error; + + error = cs40l26_pm_enter(dev); + if (error) + return error; + + error = regmap_read(regmap, reg, &val); + if (error) { + dev_err(dev, "Failed to get LRADELAYSAMPS\n"); + goto err; + } + + ucontrol->value.integer.value[0] = val; + +err: + cs40l26_pm_exit(dev); + + return error; +} + +static int cs40l26_a2h_delay_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(snd_soc_kcontrol_component(kcontrol)); + struct cs40l26_codec *codec = + snd_soc_component_get_drvdata(snd_soc_kcontrol_component(kcontrol)); + struct cs40l26_private *cs40l26 = codec->core; + struct regmap *regmap = cs40l26->regmap; + struct device *dev = cs40l26->dev; + unsigned int val = 0, reg; + int error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "%s\n", __func__); +#endif + error = cl_dsp_get_reg(cs40l26->dsp, "LRADELAYSAMPS", + CL_DSP_XM_UNPACKED_TYPE, CS40L26_A2H_ALGO_ID, ®); + if (error) + return error; + + error = cs40l26_pm_enter(dev); + if (error) + return error; + + snd_soc_dapm_mutex_lock(dapm); + + if (ucontrol->value.integer.value[0] > CS40L26_A2H_DELAY_MAX) + val = CS40L26_A2H_DELAY_MAX; + else if (ucontrol->value.integer.value[0] < 0) + val = 0; + else + val = ucontrol->value.integer.value[0]; + + error = regmap_write(regmap, reg, val); + if (error) + dev_err(dev, "Failed to set LRADELAYSAMPS\n"); + + snd_soc_dapm_mutex_unlock(dapm); + + cs40l26_pm_exit(dev); + + return error; +} + +static const struct snd_kcontrol_new cs40l26_controls[] = { + SOC_SINGLE_EXT("A2H Tuning", 0, 0, CS40L26_A2H_MAX_TUNINGS, 0, cs40l26_tuning_get, + cs40l26_tuning_put), + SOC_SINGLE_EXT("A2H Level", 0, 0, CS40L26_A2H_LEVEL_MAX, 0, cs40l26_a2h_level_get, + cs40l26_a2h_level_put), + SOC_SINGLE_EXT("SVC Algo Enable", 0, 0, 1, 0, cs40l26_svc_en_get, cs40l26_svc_en_put), + SOC_SINGLE_EXT("Invert streaming data", 0, 0, 1, 0, cs40l26_invert_streaming_data_get, + cs40l26_invert_streaming_data_put), + SOC_SINGLE_EXT("I2S VMON", 0, 0, CS40L26_VMON_DEC_OUT_DATA_MAX, 0, + cs40l26_i2s_vmon_get, NULL), + SOC_SINGLE_EXT("DSP Bypass", 0, 0, 1, 0, cs40l26_dsp_bypass_get, cs40l26_dsp_bypass_put), + SOC_SINGLE_EXT("A2H Delay", 0, 0, CS40L26_A2H_DELAY_MAX, 0, cs40l26_a2h_delay_get, + cs40l26_a2h_delay_put), +}; + +static const char * const cs40l26_out_mux_texts[] = { "Off", "PCM", "A2H" }; +static SOC_ENUM_SINGLE_VIRT_DECL(cs40l26_out_mux_enum, cs40l26_out_mux_texts); +static const struct snd_kcontrol_new cs40l26_out_mux = + SOC_DAPM_ENUM("Haptics Source", cs40l26_out_mux_enum); + +static const struct snd_soc_dapm_widget cs40l26_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY_S("ASP PLL", 0, SND_SOC_NOPM, 0, 0, cs40l26_clk_en, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_AIF_IN("ASPRX1", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("ASPRX2", NULL, 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_PGA_E("PCM", SND_SOC_NOPM, 0, 0, NULL, 0, cs40l26_asp_rx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_MIXER_E("A2H", SND_SOC_NOPM, 0, 0, NULL, 0, cs40l26_dsp_tx, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_MUX("Haptics Source", SND_SOC_NOPM, 0, 0, &cs40l26_out_mux), + SND_SOC_DAPM_OUTPUT("OUT"), +}; + +static const struct snd_soc_dapm_route cs40l26_dapm_routes[] = { + { "ASP Playback", NULL, "ASP PLL" }, + { "ASPRX1", NULL, "ASP Playback" }, + { "ASPRX2", NULL, "ASP Playback" }, + + { "PCM", NULL, "ASPRX1" }, + { "PCM", NULL, "ASPRX2" }, + { "A2H", NULL, "PCM" }, + + { "Haptics Source", "PCM", "PCM" }, + { "Haptics Source", "A2H", "A2H" }, + { "OUT", NULL, "Haptics Source" }, +}; + +static int cs40l26_component_set_sysclk(struct snd_soc_component *component, + int clk_id, int source, unsigned int freq, int dir) +{ + struct cs40l26_codec *codec = snd_soc_component_get_drvdata(component); + struct device *dev = codec->dev; + u8 clk_cfg; + int error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(dev, "%s clk_id(%d) source(%d) freq(%u) dir(%d)\n", + __func__, clk_id, source, freq, dir); +#endif + error = cs40l26_get_clk_config((u32) (CS40L26_PLL_CLK_FREQ_MASK & freq), &clk_cfg); + if (error) { + dev_err(dev, "Invalid Clock Frequency: %u Hz\n", freq); + return error; + } + + if (clk_id != 0) { + dev_err(dev, "Invalid Input Clock (ID: %d)\n", clk_id); + return -EINVAL; + } + + codec->sysclk_rate = (u32) (CS40L26_PLL_CLK_FREQ_MASK & freq); + + return 0; +} + +static int cs40l26_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct cs40l26_codec *codec = + snd_soc_component_get_drvdata(codec_dai->component); + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(codec->dev, "%s fmt(%u)\n", __func__, fmt); +#endif + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) { + dev_err(codec->dev, "Device can not be master\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + codec->daifmt = 0; + break; + case SND_SOC_DAIFMT_NB_IF: + codec->daifmt = CS40L26_ASP_FSYNC_INV_MASK; + break; + case SND_SOC_DAIFMT_IB_NF: + codec->daifmt = CS40L26_ASP_BCLK_INV_MASK; + break; + case SND_SOC_DAIFMT_IB_IF: + codec->daifmt = CS40L26_ASP_FSYNC_INV_MASK | CS40L26_ASP_BCLK_INV_MASK; + break; + default: + dev_err(codec->dev, "Invalid DAI clock INV\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + codec->daifmt |= ((CS40L26_ASP_FMT_TDM1_DSPA << CS40L26_ASP_FMT_SHIFT) & + CS40L26_ASP_FMT_MASK); + break; + case SND_SOC_DAIFMT_I2S: + codec->daifmt |= ((CS40L26_ASP_FMT_I2S << CS40L26_ASP_FMT_SHIFT) & + CS40L26_ASP_FMT_MASK); + break; + default: + dev_err(codec->dev, "Invalid DAI format: 0x%X\n", fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + return 0; +} + +static int cs40l26_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct cs40l26_codec *codec = snd_soc_component_get_drvdata(dai->component); + int error, lrck = params_rate(params); + u32 asp_rx_wl, asp_rx_width, ultrasonic; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(codec->dev, "%s\n", __func__); +#endif + error = cs40l26_pm_enter(codec->dev); + if (error) + return error; + + if (lrck == 48000) + ultrasonic = 0; + else if (lrck == 96000) + ultrasonic = 1; + else + error = -EINVAL; + + if (error) { + dev_err(codec->dev, "Invalid sample rate: %d Hz\n", lrck); + goto err_pm; + } + + error = regmap_update_bits(codec->regmap, CS40L26_MONITOR_FILT, + CS40L26_VIMON_DUAL_RATE_MASK, + FIELD_PREP(CS40L26_VIMON_DUAL_RATE_MASK, ultrasonic)); + if (error) + goto err_pm; + + asp_rx_wl = (u8) (params_width(params) & 0xFF); + error = regmap_update_bits(codec->regmap, CS40L26_ASP_DATA_CONTROL5, + CS40L26_ASP_RX_WL_MASK, asp_rx_wl); + if (error) { + dev_err(codec->dev, "Failed to update ASP RX WL\n"); + goto err_pm; + } + + if (!codec->tdm_width) + asp_rx_width = asp_rx_wl; + else + asp_rx_width = (u8) (codec->tdm_width & 0xFF); + + codec->daifmt |= ((asp_rx_width << CS40L26_ASP_RX_WIDTH_SHIFT) & + CS40L26_ASP_RX_WIDTH_MASK); + + error = regmap_update_bits(codec->regmap, CS40L26_ASP_CONTROL2, + CS40L26_ASP_FSYNC_INV_MASK | CS40L26_ASP_BCLK_INV_MASK | + CS40L26_ASP_FMT_MASK | CS40L26_ASP_RX_WIDTH_MASK, codec->daifmt); + if (error) { + dev_err(codec->dev, "Failed to update ASP RX width\n"); + goto err_pm; + } + + error = regmap_update_bits(codec->regmap, CS40L26_ASP_FRAME_CONTROL5, + CS40L26_ASP_RX1_SLOT_MASK | CS40L26_ASP_RX2_SLOT_MASK, + codec->tdm_slot[0] | (codec->tdm_slot[1] << CS40L26_ASP_RX2_SLOT_SHIFT)); + if (error) { + dev_err(codec->dev, "Failed to update ASP slot number\n"); + goto err_pm; + } + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(codec->dev, "ASP: %d bits in %d bit slots, slot #s: %d, %d\n", + asp_rx_wl, asp_rx_width, codec->tdm_slot[0], codec->tdm_slot[1]); +#else + dev_dbg(codec->dev, "ASP: %d bits in %d bit slots, slot #s: %d, %d\n", + asp_rx_wl, asp_rx_width, codec->tdm_slot[0], codec->tdm_slot[1]); +#endif + +err_pm: + cs40l26_pm_exit(codec->dev); + + return error; +} + +static int cs40l26_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct cs40l26_codec *codec = + snd_soc_component_get_drvdata(dai->component); + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(codec->dev, "%s\n", __func__); +#endif + + codec->tdm_width = slot_width; + codec->tdm_slots = slots; + + /* Reset to slots 0,1 if TDM is being disabled, and catch the case + * where both RX1 and RX2 would be set to slot 0 since that causes + * hardware to flag an error + */ + if (!slots || rx_mask == 0x1) + rx_mask = 0x3; + + codec->tdm_slot[0] = ffs(rx_mask) - 1; + rx_mask &= ~(1 << codec->tdm_slot[0]); + codec->tdm_slot[1] = ffs(rx_mask) - 1; + + return 0; +} + +static const struct snd_soc_dai_ops cs40l26_dai_ops = { + .set_fmt = cs40l26_set_dai_fmt, + .set_tdm_slot = cs40l26_set_tdm_slot, + .hw_params = cs40l26_pcm_hw_params, +}; + +static struct snd_soc_dai_driver cs40l26_dai[] = { + { + .name = "cs40l26-pcm", + .id = 0, + .playback = { + .stream_name = "ASP Playback", + .channels_min = 1, + .channels_max = 2, + .rates = CS40L26_RATES, + .formats = CS40L26_FORMATS, + }, + .ops = &cs40l26_dai_ops, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 11, 4) + .symmetric_rates = 1, +#else + .symmetric_rate = 1, +#endif + }, +}; + +static int cs40l26_codec_probe(struct snd_soc_component *component) +{ + struct cs40l26_codec *codec = snd_soc_component_get_drvdata(component); + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(codec->dev, "%s\n", __func__); +#endif + codec->bin_file = devm_kzalloc(codec->dev, PAGE_SIZE, GFP_KERNEL); + if (!codec->bin_file) + return -ENOMEM; + + codec->bin_file[PAGE_SIZE - 1] = '\0'; + snprintf(codec->bin_file, PAGE_SIZE, CS40L26_A2H_TUNING_FILE_NAME); + + /* Default audio SCLK frequency */ + codec->sysclk_rate = CS40L26_PLL_CLK_FRQ_1536000; + + codec->tdm_slot[0] = 0; + codec->tdm_slot[1] = 1; + + return 0; +} + +static const struct snd_soc_component_driver soc_codec_dev_cs40l26 = { + .probe = cs40l26_codec_probe, + .set_sysclk = cs40l26_component_set_sysclk, + + .dapm_widgets = cs40l26_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs40l26_dapm_widgets), + .dapm_routes = cs40l26_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(cs40l26_dapm_routes), + .controls = cs40l26_controls, + .num_controls = ARRAY_SIZE(cs40l26_controls), +}; + +static int cs40l26_codec_driver_probe(struct platform_device *pdev) +{ + struct cs40l26_private *cs40l26 = dev_get_drvdata(pdev->dev.parent); + struct cs40l26_codec *codec; + int error; + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(cs40l26->dev, "%s\n", __func__); +#endif + codec = devm_kzalloc(&pdev->dev, sizeof(struct cs40l26_codec), GFP_KERNEL); + if (!codec) + return -ENOMEM; + + codec->core = cs40l26; + codec->regmap = cs40l26->regmap; + codec->dev = &pdev->dev; + + platform_set_drvdata(pdev, codec); + + pm_runtime_enable(&pdev->dev); + + error = snd_soc_register_component(&pdev->dev, &soc_codec_dev_cs40l26, + cs40l26_dai, ARRAY_SIZE(cs40l26_dai)); + if (error < 0) + dev_err(&pdev->dev, "Failed to register codec: %d\n", error); + + return error; +} + +static int cs40l26_codec_driver_remove(struct platform_device *pdev) +{ + struct cs40l26_codec *codec = dev_get_drvdata(&pdev->dev); + +#ifdef CONFIG_CS40L26_SAMSUNG_FEATURE + dev_info(codec->dev, "%s\n", __func__); +#endif + pm_runtime_disable(codec->dev); + + snd_soc_unregister_component(codec->dev); + + return 0; +} + +static struct platform_driver cs40l26_codec_driver = { + .driver = { + .name = "cs40l26-codec", + }, + .probe = cs40l26_codec_driver_probe, + .remove = cs40l26_codec_driver_remove, +}; +module_platform_driver(cs40l26_codec_driver); + +MODULE_DESCRIPTION("ASoC CS40L26 driver"); +MODULE_AUTHOR("Fred Treven "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:cs40l26-codec"); diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index 2cfca78f0401..d074d6ba130f 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -398,6 +398,8 @@ static int wm_coeff_info(struct snd_kcontrol *kctl, static int wm_coeff_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) { + struct snd_soc_component *component = snd_soc_kcontrol_component(kctl); + struct wm_adsp *dsp = snd_soc_component_get_drvdata(component); struct soc_bytes_ext *bytes_ext = (struct soc_bytes_ext *)kctl->private_value; struct wm_coeff_ctl *ctl = bytes_ext_to_ctl(bytes_ext); @@ -405,9 +407,11 @@ static int wm_coeff_put(struct snd_kcontrol *kctl, char *p = ucontrol->value.bytes.data; int ret = 0; - mutex_lock(&cs_ctl->dsp->pwr_lock); - ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, p, cs_ctl->len); - mutex_unlock(&cs_ctl->dsp->pwr_lock); + if (!dsp->hibernate) { + mutex_lock(&cs_ctl->dsp->pwr_lock); + ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, p, cs_ctl->len); + mutex_unlock(&cs_ctl->dsp->pwr_lock); + } return ret; } @@ -415,20 +419,24 @@ static int wm_coeff_put(struct snd_kcontrol *kctl, static int wm_coeff_tlv_put(struct snd_kcontrol *kctl, const unsigned int __user *bytes, unsigned int size) { + struct snd_soc_component *component = snd_soc_kcontrol_component(kctl); + struct wm_adsp *dsp = snd_soc_component_get_drvdata(component); struct soc_bytes_ext *bytes_ext = (struct soc_bytes_ext *)kctl->private_value; struct wm_coeff_ctl *ctl = bytes_ext_to_ctl(bytes_ext); struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; int ret = 0; - mutex_lock(&cs_ctl->dsp->pwr_lock); + if (!dsp->hibernate) { + mutex_lock(&cs_ctl->dsp->pwr_lock); - if (copy_from_user(cs_ctl->cache, bytes, size)) - ret = -EFAULT; - else - ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, cs_ctl->cache, size); + if (copy_from_user(cs_ctl->cache, bytes, size)) + ret = -EFAULT; + else + ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, cs_ctl->cache, size); - mutex_unlock(&cs_ctl->dsp->pwr_lock); + mutex_unlock(&cs_ctl->dsp->pwr_lock); + } return ret; } @@ -436,6 +444,8 @@ static int wm_coeff_tlv_put(struct snd_kcontrol *kctl, static int wm_coeff_put_acked(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) { + struct snd_soc_component *component = snd_soc_kcontrol_component(kctl); + struct wm_adsp *dsp = snd_soc_component_get_drvdata(component); struct soc_bytes_ext *bytes_ext = (struct soc_bytes_ext *)kctl->private_value; struct wm_coeff_ctl *ctl = bytes_ext_to_ctl(bytes_ext); @@ -443,7 +453,7 @@ static int wm_coeff_put_acked(struct snd_kcontrol *kctl, unsigned int val = ucontrol->value.integer.value[0]; int ret; - if (val == 0) + if (val == 0 || dsp->hibernate) return 0; /* 0 means no event */ mutex_lock(&cs_ctl->dsp->pwr_lock); @@ -461,16 +471,20 @@ static int wm_coeff_put_acked(struct snd_kcontrol *kctl, static int wm_coeff_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) { + struct snd_soc_component *component = snd_soc_kcontrol_component(kctl); + struct wm_adsp *dsp = snd_soc_component_get_drvdata(component); struct soc_bytes_ext *bytes_ext = (struct soc_bytes_ext *)kctl->private_value; struct wm_coeff_ctl *ctl = bytes_ext_to_ctl(bytes_ext); struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; char *p = ucontrol->value.bytes.data; - int ret; + int ret = 0; - mutex_lock(&cs_ctl->dsp->pwr_lock); - ret = cs_dsp_coeff_read_ctrl(cs_ctl, 0, p, cs_ctl->len); - mutex_unlock(&cs_ctl->dsp->pwr_lock); + if (!dsp->hibernate) { + mutex_lock(&cs_ctl->dsp->pwr_lock); + ret = cs_dsp_coeff_read_ctrl(cs_ctl, 0, p, cs_ctl->len); + mutex_unlock(&cs_ctl->dsp->pwr_lock); + } return ret; } @@ -478,20 +492,24 @@ static int wm_coeff_get(struct snd_kcontrol *kctl, static int wm_coeff_tlv_get(struct snd_kcontrol *kctl, unsigned int __user *bytes, unsigned int size) { + struct snd_soc_component *component = snd_soc_kcontrol_component(kctl); + struct wm_adsp *dsp = snd_soc_component_get_drvdata(component); struct soc_bytes_ext *bytes_ext = (struct soc_bytes_ext *)kctl->private_value; struct wm_coeff_ctl *ctl = bytes_ext_to_ctl(bytes_ext); struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; int ret = 0; - mutex_lock(&cs_ctl->dsp->pwr_lock); + if (!dsp->hibernate) { + mutex_lock(&cs_ctl->dsp->pwr_lock); - ret = cs_dsp_coeff_read_ctrl(cs_ctl, 0, cs_ctl->cache, size); + ret = cs_dsp_coeff_read_ctrl(cs_ctl, 0, cs_ctl->cache, size); - if (!ret && copy_to_user(bytes, cs_ctl->cache, size)) - ret = -EFAULT; + if (!ret && copy_to_user(bytes, cs_ctl->cache, size)) + ret = -EFAULT; - mutex_unlock(&cs_ctl->dsp->pwr_lock); + mutex_unlock(&cs_ctl->dsp->pwr_lock); + } return ret; } @@ -675,13 +693,20 @@ static void wm_adsp_control_remove(struct cs_dsp_coeff_ctl *cs_ctl) int wm_adsp_write_ctl(struct wm_adsp *dsp, const char *name, int type, unsigned int alg, void *buf, size_t len) { - struct cs_dsp_coeff_ctl *cs_ctl = cs_dsp_get_ctl(&dsp->cs_dsp, name, type, alg); + struct cs_dsp_coeff_ctl *cs_ctl; struct wm_coeff_ctl *ctl; struct snd_kcontrol *kcontrol; char ctl_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; int ret; + if (dsp->hibernate) + return 0; + + mutex_lock(&dsp->cs_dsp.pwr_lock); + cs_ctl = cs_dsp_get_ctl(&dsp->cs_dsp, name, type, alg); ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, buf, len); + mutex_unlock(&dsp->cs_dsp.pwr_lock); + if (ret) return ret; @@ -699,7 +724,7 @@ int wm_adsp_write_ctl(struct wm_adsp *dsp, const char *name, int type, kcontrol = snd_soc_card_get_kcontrol(dsp->component->card, ctl_name); if (!kcontrol) { - adsp_err(dsp, "Can't find kcontrol %s\n", ctl_name); + adsp_dbg(dsp, "Can't find kcontrol %s\n", ctl_name); return -EINVAL; } @@ -713,8 +738,17 @@ EXPORT_SYMBOL_GPL(wm_adsp_write_ctl); int wm_adsp_read_ctl(struct wm_adsp *dsp, const char *name, int type, unsigned int alg, void *buf, size_t len) { - return cs_dsp_coeff_read_ctrl(cs_dsp_get_ctl(&dsp->cs_dsp, name, type, alg), + int ret = 0; + + + if (!dsp->hibernate) { + mutex_lock(&dsp->cs_dsp.pwr_lock); + ret = cs_dsp_coeff_read_ctrl(cs_dsp_get_ctl(&dsp->cs_dsp, name, type, alg), 0, buf, len); + mutex_unlock(&dsp->cs_dsp.pwr_lock); + } + + return ret; } EXPORT_SYMBOL_GPL(wm_adsp_read_ctl); @@ -1401,12 +1435,12 @@ static int wm_adsp_buffer_populate(struct wm_adsp_compr_buf *buf) ret = wm_adsp_buffer_read(buf, caps->region_defs[i].base_offset, ®ion->base_addr); if (ret < 0) - goto err; + return ret; ret = wm_adsp_buffer_read(buf, caps->region_defs[i].size_offset, &offset); if (ret < 0) - goto err; + return ret; region->cumulative_size = offset; @@ -1417,10 +1451,6 @@ static int wm_adsp_buffer_populate(struct wm_adsp_compr_buf *buf) } return 0; - -err: - kfree(buf->regions); - return ret; } static void wm_adsp_buffer_clear(struct wm_adsp_compr_buf *buf) diff --git a/sound/soc/codecs/wm_adsp.h b/sound/soc/codecs/wm_adsp.h index 375009a65828..296f4b302a24 100644 --- a/sound/soc/codecs/wm_adsp.h +++ b/sound/soc/codecs/wm_adsp.h @@ -39,6 +39,7 @@ struct wm_adsp { bool preloaded; bool fatal_error; + bool hibernate; struct list_head compr_list; struct list_head buffer_list; diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig index 2a61e620cd3b..bfc048c05d41 100644 --- a/sound/soc/samsung/Kconfig +++ b/sound/soc/samsung/Kconfig @@ -240,3 +240,19 @@ config SND_SOC_SAMSUNG_MIDAS_WM1811 Say Y if you want to add support for SoC audio on the Midas boards. endif #SND_SOC_SAMSUNG + +config SND_SOC_SAMSUNG_AUDIO + tristate "Audio support for Samsung Projects" + help + Say Y here to enable audio support for the Samsung Audio. + +config SND_DEBUG_PROC_TEST_FOR_ON_DEVICE + tristate "KUnit test for snd_debug_proc_test" + depends on SEC_KUNIT + depends on SND_SOC_SAMSUNG_AUDIO + +config SND_DEBUG_PROC_TEST_FOR_ONLY_UML + tristate "KUnit test for snd_debug_proc_test" + depends on SEC_KUNIT + depends on UML + depends on SND_SOC_SAMSUNG_AUDIO diff --git a/sound/soc/samsung/Makefile b/sound/soc/samsung/Makefile index 398e843f388c..ac5a082831b9 100644 --- a/sound/soc/samsung/Makefile +++ b/sound/soc/samsung/Makefile @@ -68,3 +68,6 @@ obj-$(CONFIG_SND_SOC_ARNDALE) += snd-soc-arndale.o obj-$(CONFIG_SND_SOC_SAMSUNG_TM2_WM5110) += snd-soc-tm2-wm5110.o obj-$(CONFIG_SND_SOC_SAMSUNG_ARIES_WM8994) += snd-soc-aries-wm8994.o obj-$(CONFIG_SND_SOC_SAMSUNG_MIDAS_WM1811) += snd-soc-midas-wm1811.o +obj-$(CONFIG_SND_SOC_SAMSUNG_AUDIO) += sec_audio_sysfs.o +obj-$(CONFIG_SND_SOC_SAMSUNG_AUDIO) += snd_debug_proc.o +GCOV_PROFILE_snd_debug_proc.o := $(CONFIG_SEC_KUNIT) diff --git a/sound/soc/samsung/sec_audio_sysfs.c b/sound/soc/samsung/sec_audio_sysfs.c new file mode 100644 index 000000000000..0beba3f7db05 --- /dev/null +++ b/sound/soc/samsung/sec_audio_sysfs.c @@ -0,0 +1,815 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ +/* + * sec_audio_sysfs.c + * + * Copyright (c) 2017 Samsung Electronics + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define EARJACK_DEV_ID 0 +#define CODEC_DEV_ID 1 +#define AMP_DEV_ID 2 +#define ADSP_DEV_ID 3 + +#define ADSP_SRCNT_MAX 1000 +#define ADSP_SRCNT_SUM_MAX 10000 + +/* bigdata add */ +#define DECLARE_AMP_BIGDATA_SYSFS(id) \ +static ssize_t audio_amp_##id##_temperature_max_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + int report = 0; \ + if (audio_data->get_amp_temperature_max) \ + report = audio_data->get_amp_temperature_max((id)); \ + else \ + dev_info(dev, "%s: No callback registered\n", __func__); \ + return snprintf(buf, PAGE_SIZE, "%d\n", report); \ +} \ +static DEVICE_ATTR(temperature_max_##id, S_IRUGO | S_IWUSR | S_IWGRP, \ + audio_amp_##id##_temperature_max_show, NULL); \ +static ssize_t audio_amp_##id##_temperature_keep_max_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + int report = 0; \ + if (audio_data->get_amp_temperature_keep_max) \ + report = audio_data->get_amp_temperature_keep_max((id)); \ + else \ + dev_info(dev, "%s: No callback registered\n", __func__); \ + return snprintf(buf, PAGE_SIZE, "%d\n", report); \ +} \ +static DEVICE_ATTR(temperature_keep_max_##id, S_IRUGO | S_IWUSR | S_IWGRP, \ + audio_amp_##id##_temperature_keep_max_show, NULL); \ +static ssize_t audio_amp_##id##_temperature_overcount_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + int report = 0; \ + if (audio_data->get_amp_temperature_overcount) \ + report = audio_data->get_amp_temperature_overcount((id)); \ + else \ + dev_info(dev, "%s: No callback registered\n", __func__); \ + return snprintf(buf, PAGE_SIZE, "%d\n", report); \ +} \ +static DEVICE_ATTR(temperature_overcount_##id, S_IRUGO | S_IWUSR | S_IWGRP, \ + audio_amp_##id##_temperature_overcount_show, NULL); \ +static ssize_t audio_amp_##id##_excursion_max_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + int report = 0; \ + if (audio_data->get_amp_excursion_max) \ + report = audio_data->get_amp_excursion_max((id)); \ + else \ + dev_info(dev, "%s: No callback registered\n", __func__); \ + return snprintf(buf, PAGE_SIZE, "%04d\n", report); \ +} \ +static DEVICE_ATTR(excursion_max_##id, S_IRUGO | S_IWUSR | S_IWGRP, \ + audio_amp_##id##_excursion_max_show, NULL); \ +static ssize_t audio_amp_##id##_excursion_overcount_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + int report = 0; \ + if (audio_data->get_amp_excursion_overcount) \ + report = audio_data->get_amp_excursion_overcount(id); \ + else \ + dev_info(dev, "%s: No callback registered\n", __func__); \ + return snprintf(buf, PAGE_SIZE, "%d\n", report); \ +} \ +static DEVICE_ATTR(excursion_overcount_##id, S_IRUGO | S_IWUSR | S_IWGRP, \ + audio_amp_##id##_excursion_overcount_show, NULL); \ +static ssize_t audio_amp_##id##_curr_temperature_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + int report = 0; \ + if (audio_data->get_amp_curr_temperature) \ + report = audio_data->get_amp_curr_temperature((id)); \ + else \ + dev_info(dev, "%s: No callback registered\n", __func__); \ + return snprintf(buf, PAGE_SIZE, "%d\n", report); \ +} \ +static DEVICE_ATTR(curr_temperature_##id, S_IRUGO | S_IWUSR | S_IWGRP, \ + audio_amp_##id##_curr_temperature_show, NULL); \ +static ssize_t audio_amp_##id##_surface_temperature_store(struct device *dev, \ + struct device_attribute *attr, const char *buf, size_t size) \ +{ \ + int ret, temp = 0; \ + ret = kstrtos32(buf, 10, &temp); \ + if (audio_data->set_amp_surface_temperature) \ + ret = audio_data->set_amp_surface_temperature((id), temp); \ + else \ + dev_info(dev, "%s: No callback registered\n", __func__); \ + return size; \ +} \ +static DEVICE_ATTR(surface_temperature_##id, S_IRUGO | S_IWUSR | S_IWGRP, \ + NULL, audio_amp_##id##_surface_temperature_store); \ +static ssize_t audio_amp_##id##_ready_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + int report = 0; \ + if (audio_data->get_amp_ready) \ + report = audio_data->get_amp_ready((id)); \ + else {\ + dev_info(dev, "%s: No callback registered\n", __func__); \ + report = -EACCES; \ + } \ + return snprintf(buf, PAGE_SIZE, "%d\n", report); \ +} \ +static DEVICE_ATTR(ready_##id, S_IRUGO | S_IWUSR | S_IWGRP, \ + audio_amp_##id##_ready_show, NULL); \ +static struct attribute *audio_amp_##id##_attr[] = { \ + &dev_attr_temperature_max_##id.attr, \ + &dev_attr_temperature_keep_max_##id.attr, \ + &dev_attr_temperature_overcount_##id.attr, \ + &dev_attr_excursion_max_##id.attr, \ + &dev_attr_excursion_overcount_##id.attr, \ + &dev_attr_curr_temperature_##id.attr, \ + &dev_attr_surface_temperature_##id.attr, \ + &dev_attr_ready_##id.attr, \ + NULL, \ +} + +static struct sec_audio_sysfs_data *audio_data; + +static int adsp_silent_reset_count; +static int adsp_silent_reset_count_sum; + +int audio_register_jack_select_cb(int (*set_jack) (int)) +{ + if (audio_data->set_jack_state) { + dev_err(audio_data->jack_dev, + "%s: Already registered\n", __func__); + return -EEXIST; + } + + audio_data->set_jack_state = set_jack; + + return 0; +} +EXPORT_SYMBOL_GPL(audio_register_jack_select_cb); + +static ssize_t audio_jack_select_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + if (audio_data->set_jack_state) { + if ((!size) || (buf[0] != '1')) { + dev_info(dev, "%s: Forced remove jack\n", __func__); + audio_data->set_jack_state(0); + } else { + dev_info(dev, "%s: Forced detect jack\n", __func__); + audio_data->set_jack_state(1); + } + } else { + dev_info(dev, "%s: No callback registered\n", __func__); + } + + return size; +} + +static DEVICE_ATTR(select_jack, S_IRUGO | S_IWUSR | S_IWGRP, + NULL, audio_jack_select_store); + +int audio_register_jack_state_cb(int (*jack_state) (void)) +{ + if (audio_data->get_jack_state) { + dev_err(audio_data->jack_dev, + "%s: Already registered\n", __func__); + return -EEXIST; + } + + audio_data->get_jack_state = jack_state; + + return 0; +} +EXPORT_SYMBOL_GPL(audio_register_jack_state_cb); + +static ssize_t audio_jack_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int report = 0; + + if (audio_data->get_jack_state) + report = audio_data->get_jack_state(); + else + dev_info(dev, "%s: No callback registered\n", __func__); + + return snprintf(buf, 4, "%d\n", report); +} + +static DEVICE_ATTR(state, S_IRUGO | S_IWUSR | S_IWGRP, + audio_jack_state_show, NULL); + +int audio_register_key_state_cb(int (*key_state) (void)) +{ + if (audio_data->get_key_state) { + dev_err(audio_data->jack_dev, + "%s: Already registered\n", __func__); + return -EEXIST; + } + + audio_data->get_key_state = key_state; + + return 0; +} +EXPORT_SYMBOL_GPL(audio_register_key_state_cb); + +static ssize_t audio_key_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int report = 0; + + if (audio_data->get_key_state) + report = audio_data->get_key_state(); + else + dev_info(dev, "%s: No callback registered\n", __func__); + + return snprintf(buf, 4, "%d\n", report); +} + +static DEVICE_ATTR(key_state, S_IRUGO | S_IWUSR | S_IWGRP, + audio_key_state_show, NULL); + +int audio_register_mic_adc_cb(int (*mic_adc) (void)) +{ + if (audio_data->get_mic_adc) { + dev_err(audio_data->jack_dev, + "%s: Already registered\n", __func__); + return -EEXIST; + } + + audio_data->get_mic_adc = mic_adc; + + return 0; +} +EXPORT_SYMBOL_GPL(audio_register_mic_adc_cb); + +static ssize_t audio_mic_adc_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int report = 0; + + if (audio_data->get_mic_adc) + report = audio_data->get_mic_adc(); + else + dev_info(dev, "%s: No callback registered\n", __func__); + + return snprintf(buf, 16, "%d\n", report); +} + +static DEVICE_ATTR(mic_adc, S_IRUGO | S_IWUSR | S_IWGRP, + audio_mic_adc_show, NULL); + +int audio_register_force_enable_antenna_cb(int (*force_enable_antenna) (int)) +{ + if (audio_data->set_force_enable_antenna) { + dev_err(audio_data->jack_dev, + "%s: Already registered\n", __func__); + return -EEXIST; + } + + audio_data->set_force_enable_antenna = force_enable_antenna; + + return 0; +} +EXPORT_SYMBOL_GPL(audio_register_force_enable_antenna_cb); + +static ssize_t force_enable_antenna_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + if (audio_data->set_force_enable_antenna) { + if ((!size) || (buf[0] != '1')) { + dev_info(dev, "%s: antenna disable\n", __func__); + audio_data->set_force_enable_antenna(0); + } else { + dev_info(dev, "%s: update antenna enable\n", __func__); + audio_data->set_force_enable_antenna(1); + } + } else { + dev_info(dev, "%s: No callback registered\n", __func__); + } + + return size; +} + +static DEVICE_ATTR(force_enable_antenna, S_IRUGO | S_IWUSR | S_IWGRP, + NULL, force_enable_antenna_store); + +int audio_register_antenna_state_cb(int (*antenna_state) (void)) +{ + if (audio_data->get_antenna_state) { + dev_err(audio_data->jack_dev, + "%s: Already registered\n", __func__); + return -EEXIST; + } + + audio_data->get_antenna_state = antenna_state; + + return 0; +} +EXPORT_SYMBOL_GPL(audio_register_antenna_state_cb); + +static ssize_t audio_antenna_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int report = 0; + + if (audio_data->get_antenna_state) + report = audio_data->get_antenna_state(); + else + dev_info(dev, "%s: No callback registered\n", __func__); + + return snprintf(buf, 4, "%d\n", report); +} + +static DEVICE_ATTR(antenna_state, S_IRUGO | S_IWUSR | S_IWGRP, + audio_antenna_state_show, NULL); + +static struct attribute *sec_audio_jack_attr[] = { + &dev_attr_select_jack.attr, + &dev_attr_state.attr, + &dev_attr_key_state.attr, + &dev_attr_mic_adc.attr, + &dev_attr_force_enable_antenna.attr, + &dev_attr_antenna_state.attr, + NULL, +}; + +static struct attribute_group sec_audio_jack_attr_group = { + .attrs = sec_audio_jack_attr, +}; + +int audio_register_codec_id_state_cb(int (*codec_id_state) (void)) +{ + if (audio_data->get_codec_id_state) { + dev_err(audio_data->codec_dev, + "%s: Already registered\n", __func__); + return -EEXIST; + } + + audio_data->get_codec_id_state = codec_id_state; + + return 0; +} +EXPORT_SYMBOL_GPL(audio_register_codec_id_state_cb); + +static ssize_t audio_check_codec_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int report = 0; + + if (audio_data->get_codec_id_state) + report = audio_data->get_codec_id_state(); + else + dev_info(dev, "%s: No callback registered\n", __func__); + + return snprintf(buf, 4, "%d\n", report); +} + +static DEVICE_ATTR(check_codec_id, S_IRUGO | S_IWUSR | S_IWGRP, + audio_check_codec_id_show, NULL); + + +static struct attribute *sec_audio_codec_attr[] = { + &dev_attr_check_codec_id.attr, + NULL, +}; + +static struct attribute_group sec_audio_codec_attr_group = { + .attrs = sec_audio_codec_attr, +}; + +/* bigdata */ +int audio_register_temperature_max_cb(int (*temperature_max) (enum amp_id)) +{ + if (audio_data->get_amp_temperature_max) { + dev_err(audio_data->amp_dev, + "%s: Already registered\n", __func__); + return -EEXIST; + } + + audio_data->get_amp_temperature_max = temperature_max; + + return 0; +} +EXPORT_SYMBOL_GPL(audio_register_temperature_max_cb); + +int audio_register_temperature_keep_max_cb(int (*temperature_keep_max) (enum amp_id)) +{ + if (audio_data->get_amp_temperature_keep_max) { + dev_err(audio_data->amp_dev, + "%s: Already registered\n", __func__); + return -EEXIST; + } + + audio_data->get_amp_temperature_keep_max = temperature_keep_max; + + return 0; +} +EXPORT_SYMBOL_GPL(audio_register_temperature_keep_max_cb); + +int audio_register_temperature_overcount_cb(int (*temperature_overcount) (enum amp_id)) +{ + if (audio_data->get_amp_temperature_overcount) { + dev_err(audio_data->amp_dev, + "%s: Already registered\n", __func__); + return -EEXIST; + } + + audio_data->get_amp_temperature_overcount = temperature_overcount; + + return 0; +} +EXPORT_SYMBOL_GPL(audio_register_temperature_overcount_cb); + +int audio_register_excursion_max_cb(int (*excursion_max) (enum amp_id)) +{ + if (audio_data->get_amp_excursion_max) { + dev_err(audio_data->amp_dev, + "%s: Already registered\n", __func__); + return -EEXIST; + } + + audio_data->get_amp_excursion_max = excursion_max; + + return 0; +} +EXPORT_SYMBOL_GPL(audio_register_excursion_max_cb); + +int audio_register_excursion_overcount_cb(int (*excursion_overcount) (enum amp_id)) +{ + if (audio_data->get_amp_excursion_overcount) { + dev_err(audio_data->amp_dev, + "%s: Already registered\n", __func__); + return -EEXIST; + } + + audio_data->get_amp_excursion_overcount = excursion_overcount; + + return 0; +} +EXPORT_SYMBOL_GPL(audio_register_excursion_overcount_cb); + +int audio_register_curr_temperature_cb(int (*curr_temperature) (enum amp_id)) +{ + if (audio_data->get_amp_curr_temperature) { + dev_err(audio_data->amp_dev, + "%s: Already registered\n", __func__); + return -EEXIST; + } + + audio_data->get_amp_curr_temperature = curr_temperature; + + return 0; +} +EXPORT_SYMBOL_GPL(audio_register_curr_temperature_cb); + +int audio_register_surface_temperature_cb(int (*surface_temperature) (enum amp_id, int temperature)) +{ + if (audio_data->set_amp_surface_temperature) { + dev_err(audio_data->amp_dev, + "%s: Already registered\n", __func__); + return -EEXIST; + } + + audio_data->set_amp_surface_temperature = surface_temperature; + + return 0; +} +EXPORT_SYMBOL_GPL(audio_register_surface_temperature_cb); + +int audio_register_ready_cb(int (*ready) (enum amp_id)) +{ + if (audio_data->get_amp_ready) { + dev_err(audio_data->amp_dev, + "%s: Already registered\n", __func__); + return -EEXIST; + } + + audio_data->get_amp_ready = ready; + + return 0; +} +EXPORT_SYMBOL_GPL(audio_register_ready_cb); + +DECLARE_AMP_BIGDATA_SYSFS(0); +DECLARE_AMP_BIGDATA_SYSFS(1); +DECLARE_AMP_BIGDATA_SYSFS(2); +DECLARE_AMP_BIGDATA_SYSFS(3); + +static struct attribute_group sec_audio_amp_big_data_attr_group[AMP_ID_MAX] = { + [AMP_0] = {.attrs = audio_amp_0_attr, }, + [AMP_1] = {.attrs = audio_amp_1_attr, }, + [AMP_2] = {.attrs = audio_amp_2_attr, }, + [AMP_3] = {.attrs = audio_amp_3_attr, }, +}; + +void send_adsp_silent_reset_ev(void) +{ + if (adsp_silent_reset_count < ADSP_SRCNT_MAX) + adsp_silent_reset_count++; + + if (adsp_silent_reset_count_sum < ADSP_SRCNT_SUM_MAX) + adsp_silent_reset_count_sum++; + + pr_info("%s: count %d\n", __func__, + (adsp_silent_reset_count + adsp_silent_reset_count_sum)); + sdp_info_print("%s: count %d\n", __func__, + (adsp_silent_reset_count + adsp_silent_reset_count_sum)); +} +EXPORT_SYMBOL_GPL(send_adsp_silent_reset_ev); + +static ssize_t srcnt_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int report = adsp_silent_reset_count; + adsp_silent_reset_count = 0; + + if (adsp_silent_reset_count_sum < ADSP_SRCNT_SUM_MAX) + adsp_silent_reset_count_sum += report; + + if (adsp_silent_reset_count_sum > ADSP_SRCNT_SUM_MAX) + adsp_silent_reset_count_sum = ADSP_SRCNT_SUM_MAX; + + dev_info(dev, "%s: %d\n", __func__, report); + + return snprintf(buf, 8, "%d\n", report); +} + +static DEVICE_ATTR_RO(srcnt); + +static ssize_t srcnt_keep_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int report = adsp_silent_reset_count_sum; + + dev_info(dev, "%s: %d\n", __func__, report); + + return snprintf(buf, 8, "%d\n", report); +} + +static DEVICE_ATTR_RO(srcnt_keep); + +static struct attribute *sec_audio_adsp_attrs[] = { + &dev_attr_srcnt.attr, + &dev_attr_srcnt_keep.attr, + NULL, +}; + +static struct attribute_group sec_audio_adsp_attr_group = { + .attrs = sec_audio_adsp_attrs, +}; + +static int sec_audio_sysfs_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + int i; + + if (audio_data == NULL) { + dev_err(&pdev->dev, "%s: no audio_data\n", __func__); + return -ENOMEM; + } + + audio_data->no_earjack = of_property_read_bool(np, "audio,no-earjack"); + + if (audio_data->no_earjack) { + dev_info(&pdev->dev, "%s: remove earjack sysfs dev\n", __func__); + if (audio_data->jack_dev) { + sysfs_remove_group(&audio_data->jack_dev->kobj, + &sec_audio_jack_attr_group); + device_destroy(audio_data->audio_class, EARJACK_DEV_ID); + } + } + + of_property_read_u32(np, "audio,num-amp", &audio_data->num_amp); + if (audio_data->num_amp > 0) { + for (i = audio_data->num_amp; i < AMP_ID_MAX; i++) { + sysfs_remove_group(&audio_data->amp_dev->kobj, + &sec_audio_amp_big_data_attr_group[i]); + } + } + + return 0; +} + +static int sec_audio_sysfs_remove(struct platform_device *pdev) +{ + int i; + + if (audio_data->num_amp == 0) + audio_data->num_amp = AMP_ID_MAX; + + for (i = 0; i < audio_data->num_amp; i++) { + sysfs_remove_group(&audio_data->amp_dev->kobj, + &sec_audio_amp_big_data_attr_group[i]); + } + + return 0; +} + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id sec_audio_sysfs_of_match[] = { + { .compatible = "samsung,audio-sysfs", }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_audio_sysfs_of_match); +#endif /* CONFIG_OF */ + +static struct platform_driver sec_audio_sysfs_driver = { + .driver = { + .name = "sec-audio-sysfs", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(sec_audio_sysfs_of_match), + }, + + .probe = sec_audio_sysfs_probe, + .remove = sec_audio_sysfs_remove, +}; + +static int __init sec_audio_sysfs_init(void) +{ + int ret = 0; + int i = 0; + + audio_data = kzalloc(sizeof(struct sec_audio_sysfs_data), GFP_KERNEL); + if (audio_data == NULL) + return -ENOMEM; + + audio_data->audio_class = class_create(THIS_MODULE, "audio"); + if (IS_ERR(audio_data->audio_class)) { + pr_err("%s: Failed to create audio class\n", __func__); + ret = PTR_ERR(audio_data->audio_class); + goto err_alloc; + } + + audio_data->jack_dev = + device_create(audio_data->audio_class, + NULL, EARJACK_DEV_ID, NULL, "earjack"); + if (IS_ERR(audio_data->jack_dev)) { + pr_err("%s: Failed to create earjack device\n", __func__); + ret = PTR_ERR(audio_data->jack_dev); + goto err_class; + } + + ret = sysfs_create_group(&audio_data->jack_dev->kobj, + &sec_audio_jack_attr_group); + if (ret) { + pr_err("%s: Failed to create earjack sysfs\n", __func__); + goto err_jack_device; + } + + audio_data->codec_dev = + device_create(audio_data->audio_class, + NULL, CODEC_DEV_ID, NULL, "codec"); + if (IS_ERR(audio_data->codec_dev)) { + pr_err("%s: Failed to create codec device\n", __func__); + ret = PTR_ERR(audio_data->codec_dev); + goto err_jack_attr; + } + + ret = sysfs_create_group(&audio_data->codec_dev->kobj, + &sec_audio_codec_attr_group); + if (ret) { + pr_err("%s: Failed to create codec sysfs\n", __func__); + goto err_codec_device; + } + + audio_data->amp_dev = + device_create(audio_data->audio_class, + NULL, AMP_DEV_ID, NULL, "amp"); + if (IS_ERR(audio_data->amp_dev)) { + pr_err("%s: Failed to create amp device\n", __func__); + ret = PTR_ERR(audio_data->amp_dev); + goto err_codec_attr; + } + + audio_data->num_amp = 0; + + for (i = 0; i < AMP_ID_MAX; i++) { + ret = sysfs_create_group(&audio_data->amp_dev->kobj, + &sec_audio_amp_big_data_attr_group[i]); + if (ret) { + pr_err("%s: Failed to create amp sysfs\n", __func__); + goto err_amp_attr; + } + } + + pr_err("%s: DSP DEVICE CREATE\n", __func__); + + audio_data->adsp_dev = + device_create(audio_data->audio_class, + NULL, ADSP_DEV_ID, NULL, "dsp"); + if (IS_ERR(audio_data->adsp_dev)) { + pr_err("%s: Failed to create adsp device\n", __func__); + ret = PTR_ERR(audio_data->adsp_dev); + goto err_amp_attr; + } + + ret = sysfs_create_group(&audio_data->adsp_dev->kobj, + &sec_audio_adsp_attr_group); + if (ret) { + pr_err("%s: Failed to create adsp sysfs\n", __func__); + goto err_adsp_device; + } + + ret = platform_driver_register(&sec_audio_sysfs_driver); + if (ret) { + pr_err("%s: fail to register sysfs driver\n", __func__); + goto err_adsp_attr; + } + + adsp_silent_reset_count = 0; + adsp_silent_reset_count_sum = 0; + + return ret; + +err_adsp_attr: + sysfs_remove_group(&audio_data->adsp_dev->kobj, + &sec_audio_adsp_attr_group); +err_adsp_device: + device_destroy(audio_data->audio_class, ADSP_DEV_ID); + audio_data->adsp_dev = NULL; +err_amp_attr: + while (--i >= 0) + sysfs_remove_group(&audio_data->amp_dev->kobj, + &sec_audio_amp_big_data_attr_group[i]); + device_destroy(audio_data->audio_class, AMP_DEV_ID); + audio_data->amp_dev = NULL; +err_codec_attr: + sysfs_remove_group(&audio_data->codec_dev->kobj, + &sec_audio_codec_attr_group); +err_codec_device: + device_destroy(audio_data->audio_class, CODEC_DEV_ID); + audio_data->codec_dev = NULL; +err_jack_attr: + sysfs_remove_group(&audio_data->jack_dev->kobj, + &sec_audio_jack_attr_group); +err_jack_device: + device_destroy(audio_data->audio_class, EARJACK_DEV_ID); + audio_data->jack_dev = NULL; +err_class: + class_destroy(audio_data->audio_class); + audio_data->audio_class = NULL; +err_alloc: + kfree(audio_data); + audio_data = NULL; + + return ret; +} +module_init(sec_audio_sysfs_init); + +static void __exit sec_audio_sysfs_exit(void) +{ + platform_driver_unregister(&sec_audio_sysfs_driver); + + if (audio_data->amp_dev) + device_destroy(audio_data->audio_class, AMP_DEV_ID); + + if (audio_data->codec_dev) { + sysfs_remove_group(&audio_data->codec_dev->kobj, + &sec_audio_codec_attr_group); + device_destroy(audio_data->audio_class, CODEC_DEV_ID); + } + + if (audio_data->jack_dev) { + sysfs_remove_group(&audio_data->jack_dev->kobj, + &sec_audio_jack_attr_group); + device_destroy(audio_data->audio_class, EARJACK_DEV_ID); + } + + if (audio_data->audio_class) + class_destroy(audio_data->audio_class); + + kfree(audio_data); +} +module_exit(sec_audio_sysfs_exit); + +MODULE_DESCRIPTION("Samsung Electronics Audio SYSFS driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/snd_debug_proc.c b/sound/soc/samsung/snd_debug_proc.c new file mode 100644 index 000000000000..3ce57450e4a3 --- /dev/null +++ b/sound/soc/samsung/snd_debug_proc.c @@ -0,0 +1,266 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ +/* + * snd_debug_proc.c + * + * Copyright (c) 2017 Samsung Electronics + * + * 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 + +static struct proc_dir_entry *procfs_sdp_dir; +static struct snd_debug_proc sdp_info; +static struct snd_debug_proc sdp_boot; + +struct snd_debug_proc *get_sdp_info(void) +{ + return &sdp_info; +} +EXPORT_SYMBOL(get_sdp_info); + +struct snd_debug_proc *get_sdp_boot(void) +{ + return &sdp_boot; +} +EXPORT_SYMBOL(get_sdp_boot); + +__visible_for_testing int append_timestamp(char *buf) +{ + u64 time; + unsigned long nsec; + + time = local_clock(); + nsec = do_div(time, NSEC_PER_SEC); + + return snprintf(buf, MAX_LOG_LINE_LEN, "[%6lu.%06ld] ", + (unsigned long)time, nsec / NSEC_PER_USEC); +} + +__visible_for_testing void save_info_log(char *buf, int len) +{ + if (sdp_info.buf_pos + len + 1 > AUD_LOG_BUF_SIZE) { + sdp_info.buf_pos = 0; + sdp_info.buf_full++; + } + + sdp_info.buf_pos += scnprintf(&sdp_info.log_buf[sdp_info.buf_pos], + len + 1, "%s", buf); +} + +__visible_for_testing void save_boot_log(char *buf, int len) +{ + if (sdp_boot.buf_pos + len + 1 > AUD_LOG_BUF_SIZE) { + pr_info("%s: log buffer is full\n", __func__); + return; + } + + sdp_boot.buf_pos += scnprintf(&sdp_boot.log_buf[sdp_boot.buf_pos], + len + 1, "%s", buf); +} + +__visible_for_testing ssize_t read_log(struct file *file, char __user *buf, size_t len, + loff_t *offset, struct snd_debug_proc *sdp) +{ + loff_t pos = *offset; + ssize_t count = 0; + size_t size = (sdp->buf_full > 0) ? + AUD_LOG_BUF_SIZE : (size_t)sdp->buf_pos; + + mutex_lock(&sdp->lock); + + pr_info("%s: pos(%d), full(%d), size(%ld)\n", __func__, + sdp->buf_pos, sdp->buf_full, size); + + if (pos >= size) { + mutex_unlock(&sdp->lock); + return 0; + } + + count = min(len, size); + if ((pos + count) > size) + count = size - pos; + + if (copy_to_user(buf, sdp->log_buf + pos, count)) { + mutex_unlock(&sdp->lock); + return -EFAULT; + } + + *offset += count; + + mutex_unlock(&sdp->lock); + + return count; +} + +void sdp_info_print(const char *fmt, ...) +{ + char buf[MAX_LOG_LINE_LEN] = {0, }; + unsigned int len = 0; + va_list args; + + if (!sdp_info.is_enabled) + return; + + mutex_lock(&sdp_info.lock); + + len = append_timestamp(buf); + + va_start(args, fmt); + len += vsnprintf(buf + len, MAX_LOG_LINE_LEN - len, fmt, args); + va_end(args); + + sdp_info.save_log(buf, len); + + mutex_unlock(&sdp_info.lock); +} +EXPORT_SYMBOL(sdp_info_print); + +static ssize_t sdp_info_log_read(struct file *file, char __user *buf, + size_t len, loff_t *offset) +{ + return read_log(file, buf, len, offset, &sdp_info); +} + +static const struct proc_ops sdp_info_log_ops = { + .proc_read = sdp_info_log_read, +}; + +void sdp_boot_print(const char *fmt, ...) +{ + char buf[MAX_LOG_LINE_LEN] = {0, }; + unsigned int len = 0; + va_list args; + + if (!sdp_boot.is_enabled) + return; + + mutex_lock(&sdp_boot.lock); + + len = append_timestamp(buf); + + va_start(args, fmt); + len += vsnprintf(buf + len, MAX_LOG_LINE_LEN - len, fmt, args); + va_end(args); + + sdp_boot.save_log(buf, len); + + mutex_unlock(&sdp_boot.lock); +} +EXPORT_SYMBOL(sdp_boot_print); + +static ssize_t sdp_boot_log_read(struct file *file, char __user *buf, + size_t len, loff_t *offset) +{ + return read_log(file, buf, len, offset, &sdp_boot); +} + +static const struct proc_ops sdp_boot_log_ops = { + .proc_read = sdp_boot_log_read, +}; + +__visible_for_testing int snd_debug_proc_probe(struct platform_device *pdev) +{ + struct proc_dir_entry *entry; + + procfs_sdp_dir = proc_mkdir(PROC_SDP_DIR, NULL); + if (unlikely(!procfs_sdp_dir)) { + pr_err("%s: failed to make %s\n", __func__, PROC_SDP_DIR); + return 0; + } + + entry = proc_create(SDP_INFO_LOG_NAME, 0444, + procfs_sdp_dir, &sdp_info_log_ops); + if (unlikely(!entry)) + pr_err("%s: proc sdp_info log fail\n", __func__); + else + proc_set_size(entry, AUD_LOG_BUF_SIZE); + + mutex_init(&sdp_info.lock); + sdp_info.is_enabled = true; + sdp_info.save_log = save_info_log; + + entry = proc_create(SDP_BOOT_LOG_NAME, 0444, + procfs_sdp_dir, &sdp_boot_log_ops); + if (unlikely(!entry)) + pr_err("%s: proc sdp_boot log fail\n", __func__); + else + proc_set_size(entry, AUD_LOG_BUF_SIZE); + + mutex_init(&sdp_boot.lock); + sdp_boot.is_enabled = true; + sdp_boot.save_log = save_boot_log; + + pr_info("%s: Enabling samsung snd debug proc\n", __func__); + + return 0; +} + +__visible_for_testing int snd_debug_proc_remove(struct platform_device *pdev) +{ + sdp_info.is_enabled = false; + sdp_boot.is_enabled = false; + mutex_destroy(&sdp_info.lock); + mutex_destroy(&sdp_boot.lock); + remove_proc_entry(SDP_INFO_LOG_NAME, procfs_sdp_dir); + remove_proc_entry(SDP_BOOT_LOG_NAME, procfs_sdp_dir); + remove_proc_entry(PROC_SDP_DIR, NULL); + + return 0; +} + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id snd_debug_proc_of_match[] = { + { .compatible = "samsung,snd-debug-proc", }, + {}, +}; +MODULE_DEVICE_TABLE(of, snd_debug_proc_of_match); +#endif /* CONFIG_OF */ + +static struct platform_driver snd_debug_proc_driver = { + .driver = { + .name = "snd-debug-proc", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(snd_debug_proc_of_match), + }, + + .probe = snd_debug_proc_probe, + .remove = snd_debug_proc_remove, +}; + +static int __init snd_dedug_proc_init(void) +{ + int ret = 0; + + ret = platform_driver_register(&snd_debug_proc_driver); + if (ret) + pr_err("%s: fail to register driver\n", __func__); + + return ret; +} +module_init(snd_dedug_proc_init); + +static void __exit snd_dedug_proc_exit(void) +{ + platform_driver_unregister(&snd_debug_proc_driver); +} +module_exit(snd_dedug_proc_exit); + +MODULE_DESCRIPTION("Samsung Electronics Sound Debug Proc driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/usb/usb_audio_qmi_svc.c b/sound/usb/usb_audio_qmi_svc.c index 799a38c86066..049a50cf6ff6 100644 --- a/sound/usb/usb_audio_qmi_svc.c +++ b/sound/usb/usb_audio_qmi_svc.c @@ -33,6 +33,9 @@ #include "pcm.h" #include "power.h" #include "usb_audio_qmi_v01.h" +#ifdef CONFIG_USB_NOTIFY_PROC_LOG +#include +#endif #define BUS_INTERVAL_FULL_SPEED 1000 /* in us */ #define BUS_INTERVAL_HIGHSPEED_AND_ABOVE 125 /* in us */ @@ -1545,6 +1548,9 @@ static void handle_uaudio_stream_req(struct qmi_handle *handle, u8 pcm_card_num, pcm_dev_num, direction; int info_idx = -EINVAL, datainterval = -EINVAL, ret = 0; +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + int on, type; +#endif uaudio_dbg("sq_node:%x sq_port:%x sq_family:%x\n", sq->sq_node, sq->sq_port, sq->sq_family); @@ -1631,6 +1637,14 @@ static void handle_uaudio_stream_req(struct qmi_handle *handle, if (atomic_dec_and_test(&chip->usage_count) && atomic_read(&chip->shutdown)) wake_up(&chip->shutdown_wait); +#ifdef CONFIG_USB_NOTIFY_PROC_LOG + if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK) + type = NOTIFY_PCM_PLAYBACK; + else + type = NOTIFY_PCM_CAPTURE; + on = req_msg->enable; + store_usblog_notify(type, (void *)&on, NULL); +#endif response: if (!req_msg->enable && ret != -EINVAL && ret != -ENODEV) {